WIP, added nav, about & attribution pages, and updated styles to ensure tags flow properly
This commit is contained in:
parent
429087ce80
commit
6d3723cc11
4 changed files with 738 additions and 532 deletions
|
@ -3,5 +3,37 @@
|
|||
|
||||
</script>
|
||||
|
||||
<nav>
|
||||
<a href="/">Web Services</a>
|
||||
<a href="/about">About</a>
|
||||
</nav>
|
||||
|
||||
<!-- content provided by the +page.svelte file in this directory - and sub-directories -->
|
||||
<slot />
|
||||
|
||||
<footer>
|
||||
<div class="repo"><a href="https://forge.magnificent.nz/lightweight/webservices-app" title="Access the source code repository for this site">Code for this site</a></div>
|
||||
<div class="copyright">Copyleft 2024 by <a href="https://davelane.nz">Dave Lane</a></div>
|
||||
<div class="attribution"><a href="/attribution" title="All images used on this site are made available by their authors under a Creative Commons license">Attribution</a></div>
|
||||
</footer>
|
||||
|
||||
<style>
|
||||
body {
|
||||
width: 96%;
|
||||
margin: auto;
|
||||
}
|
||||
nav {
|
||||
margin: auto;
|
||||
}
|
||||
nav a {
|
||||
margin-right: 20px;
|
||||
}
|
||||
footer {
|
||||
display: inline-grid;
|
||||
margin: auto;
|
||||
grid-template-columns: 1fr 1fr 1fr;
|
||||
}
|
||||
footer div {
|
||||
text-align: center;
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -4,7 +4,38 @@
|
|||
const { webservices } = data;
|
||||
|
||||
// source: https://mokole.com/palette.html 20 colors with default settings otherwise
|
||||
const colours30 = [ "#808080", "#7f0000", "#006400", "#808000", "#483d8b", "#008b8b", "#cd853f", "#00008b", "#7f007f", "#8fbc8f", "#b03060", "#ff0000", "#ff8c00", "#00ff00", "#9400d3", "#00ff7f", "#dc143c", "#00ffff", "#00bfff", "#0000ff", "#f08080", "#adff2f", "#1e90ff", "#ffff54", "#90ee90", "#add8e6", "#ff1493", "#7b68ee", "#ee82ee", "#ffe4b5" ];
|
||||
const colours30 = [
|
||||
'#808080',
|
||||
'#7f0000',
|
||||
'#006400',
|
||||
'#808000',
|
||||
'#483d8b',
|
||||
'#008b8b',
|
||||
'#cd853f',
|
||||
'#00008b',
|
||||
'#7f007f',
|
||||
'#8fbc8f',
|
||||
'#b03060',
|
||||
'#ff0000',
|
||||
'#ff8c00',
|
||||
'#00ff00',
|
||||
'#9400d3',
|
||||
'#00ff7f',
|
||||
'#dc143c',
|
||||
'#00ffff',
|
||||
'#00bfff',
|
||||
'#0000ff',
|
||||
'#f08080',
|
||||
'#adff2f',
|
||||
'#1e90ff',
|
||||
'#ffff54',
|
||||
'#90ee90',
|
||||
'#add8e6',
|
||||
'#ff1493',
|
||||
'#7b68ee',
|
||||
'#ee82ee',
|
||||
'#ffe4b5'
|
||||
];
|
||||
|
||||
// digest and return useful info from the Webservices JSON feed
|
||||
function processData(webservices) {
|
||||
|
@ -30,14 +61,14 @@
|
|||
// pull out relevant info in useful chunks.
|
||||
for (let key in webservices.technologies) {
|
||||
let tech = webservices.technologies[key];
|
||||
if (tech.hasOwnProperty('categories') && tech.categories.constructor === Array ) {
|
||||
tech.categories.forEach(function(category, index) {
|
||||
if (tech.hasOwnProperty('categories') && tech.categories.constructor === Array) {
|
||||
tech.categories.forEach(function (category, index) {
|
||||
if (category_list.hasOwnProperty(category)) category_list[category]++;
|
||||
else category_list[category] = 1;
|
||||
});
|
||||
}
|
||||
if (tech.hasOwnProperty('analogues') && tech.analogues.constructor === Array ) {
|
||||
tech.analogues.forEach(function(analogue, index) {
|
||||
if (tech.hasOwnProperty('analogues') && tech.analogues.constructor === Array) {
|
||||
tech.analogues.forEach(function (analogue, index) {
|
||||
if (analogue_list.hasOwnProperty(analogue)) analogue_list[analogue]++;
|
||||
else analogue_list[analogue] = 1;
|
||||
});
|
||||
|
@ -52,7 +83,7 @@
|
|||
tech['name'] = key;
|
||||
current.push(tech);
|
||||
//console.log(tech.name + ': ' + tech.instances.length + ' instances...');
|
||||
tech.instances.forEach(function(instance, i) {
|
||||
tech.instances.forEach(function (instance, i) {
|
||||
instances++;
|
||||
if (instance.hasOwnProperty('status')) {
|
||||
let tag = instance.status;
|
||||
|
@ -73,14 +104,16 @@
|
|||
} else {
|
||||
future[key] = tech;
|
||||
}
|
||||
|
||||
};
|
||||
}
|
||||
for (let key in webservices.hosts) {
|
||||
//console.log('key: ', key);
|
||||
let host = webservices.hosts[key];
|
||||
host['name'] = key;
|
||||
//console.log('host: ', host);
|
||||
if (host.hasOwnProperty('domain') && !(host.hasOwnProperty('status') && host.status == 'retired')) {
|
||||
if (
|
||||
host.hasOwnProperty('domain') &&
|
||||
!(host.hasOwnProperty('status') && host.status == 'retired')
|
||||
) {
|
||||
hosts[key] = host;
|
||||
}
|
||||
}
|
||||
|
@ -114,10 +147,15 @@
|
|||
};
|
||||
}
|
||||
|
||||
// console.log(technologies);
|
||||
// console.log(technologies);
|
||||
// return true if a tech object includes valid instances
|
||||
function hasInstances(tech) {
|
||||
if (tech.hasOwnProperty('instances') && tech.instances.constructor === Array && tech.instances.length) return true;
|
||||
if (
|
||||
tech.hasOwnProperty('instances') &&
|
||||
tech.instances.constructor === Array &&
|
||||
tech.instances.length
|
||||
)
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
|
@ -125,8 +163,9 @@
|
|||
function getKeys(ob) {
|
||||
let keys = [];
|
||||
for (let key in ob) {
|
||||
//keys.push(key.replace(/ /g, '\u00a0'));
|
||||
keys.push(key);
|
||||
};
|
||||
}
|
||||
return keys;
|
||||
}
|
||||
|
||||
|
@ -135,9 +174,7 @@
|
|||
const set1 = new Set(a);
|
||||
const set2 = new Set(b);
|
||||
|
||||
const intersection = [...set1].filter(
|
||||
element => set2.has(element)
|
||||
);
|
||||
const intersection = [...set1].filter((element) => set2.has(element));
|
||||
|
||||
return intersection;
|
||||
}
|
||||
|
@ -167,14 +204,14 @@
|
|||
|
||||
//console.log('hosts:', hosts);
|
||||
|
||||
host_list.forEach(function(host) {
|
||||
host_list.forEach(function (host) {
|
||||
//console.log(host);
|
||||
if (hosts[host].hasOwnProperty('domain') && hosts[host].hasOwnProperty('affiliation')) {
|
||||
host_array[host] = {
|
||||
"colour": colours[i++],
|
||||
"domain": hosts[host].domain,
|
||||
"affiliation": hosts[host].affiliation
|
||||
}
|
||||
colour: colours[i++],
|
||||
domain: hosts[host].domain,
|
||||
affiliation: hosts[host].affiliation
|
||||
};
|
||||
}
|
||||
});
|
||||
return host_array;
|
||||
|
@ -191,12 +228,15 @@
|
|||
|
||||
for (const affiliate of affiliate_list) {
|
||||
//console.log('affiliate:', affiliate);
|
||||
if (affiliates[affiliate].hasOwnProperty('name') && affiliates[affiliate].hasOwnProperty('website')) {
|
||||
if (
|
||||
affiliates[affiliate].hasOwnProperty('name') &&
|
||||
affiliates[affiliate].hasOwnProperty('website')
|
||||
) {
|
||||
affiliate_result[affiliate] = {
|
||||
"colour": colours[i++],
|
||||
"name": affiliates[affiliate].name,
|
||||
"website": affiliates[affiliate].website
|
||||
}
|
||||
colour: colours[i++],
|
||||
name: affiliates[affiliate].name,
|
||||
website: affiliates[affiliate].website
|
||||
};
|
||||
}
|
||||
}
|
||||
return affiliate_result;
|
||||
|
@ -204,7 +244,7 @@
|
|||
|
||||
// sort technologies alphabetically by name
|
||||
function sortTechnologies(technologies) {
|
||||
return technologies.sort((a,b) => a.name.localeCompare(b.name));
|
||||
return technologies.sort((a, b) => a.name.localeCompare(b.name));
|
||||
}
|
||||
|
||||
// generic function to find the intersection of two arrays
|
||||
|
@ -213,9 +253,7 @@
|
|||
const set1 = new Set(a);
|
||||
const set2 = new Set(b);
|
||||
|
||||
const intersection = [...set1].filter(
|
||||
element => set2.has(element)
|
||||
);
|
||||
const intersection = [...set1].filter((element) => set2.has(element));
|
||||
console.log('intersection = ', intersection);
|
||||
return intersection;
|
||||
}
|
||||
|
@ -224,7 +262,7 @@
|
|||
function filterTechnologiesByCategoryList(technologies, list) {
|
||||
console.log('looking for tech in categories: ', list);
|
||||
const included = [];
|
||||
technologies.forEach(function(tech, index) {
|
||||
technologies.forEach(function (tech, index) {
|
||||
//console.log('tech(' + index + '): ', tech);
|
||||
if (hasInstances(tech)) {
|
||||
const intersection = inCommon(tech.categories, list);
|
||||
|
@ -242,7 +280,7 @@
|
|||
}
|
||||
}
|
||||
});
|
||||
console.log('found ' + included.length + ' technologies.')
|
||||
console.log('found ' + included.length + ' technologies.');
|
||||
return included;
|
||||
}
|
||||
|
||||
|
@ -363,10 +401,69 @@
|
|||
'GPL-2+',
|
||||
'GPL-3',
|
||||
'GPL-3+',
|
||||
'LGPL-3',
|
||||
'LGPL-3'
|
||||
];
|
||||
const category_filter_list = [
|
||||
'App Ecosystem',
|
||||
'Application Design',
|
||||
'Asset Management',
|
||||
'Association Management',
|
||||
'Association Management System',
|
||||
'Blog Syndication',
|
||||
'Book Reviews',
|
||||
'Calendar',
|
||||
'Collaborative',
|
||||
'Collaborative Markdown Editing',
|
||||
'Collaborative Wiki',
|
||||
'Content Management Systems',
|
||||
'Digital Media Sales',
|
||||
'Document Management',
|
||||
'Email Marketing Automation',
|
||||
'Email Services',
|
||||
'Enterprise Resource Planning',
|
||||
'Event Management',
|
||||
'Facial Recognition',
|
||||
'Facility Booking',
|
||||
'Federated Messaging',
|
||||
'Fediverse',
|
||||
'File Synchronisation',
|
||||
'Forum',
|
||||
'Framasoft',
|
||||
'Home Automation',
|
||||
'Home Security',
|
||||
'Image Backup',
|
||||
'Image Gallery',
|
||||
'Kanban Project Management',
|
||||
'Learning Management System',
|
||||
'Link Ranking',
|
||||
'Link Sharing',
|
||||
'Link Shortener',
|
||||
'Membership Management',
|
||||
'Messaging Cache',
|
||||
'Micro-blogging',
|
||||
'Multi-Domain Server',
|
||||
'Multimedia',
|
||||
'Music Discovery',
|
||||
'Music Streaming',
|
||||
'Network Infrastructure Monitoring',
|
||||
'Newsletters',
|
||||
'Online Forms',
|
||||
'Password Manager',
|
||||
'Photos',
|
||||
'Portfolio Management',
|
||||
'Privacy',
|
||||
'Productivity',
|
||||
'Reading List',
|
||||
'Remote Desktop Sharing',
|
||||
'Remote Incremental Encrypted System Backups',
|
||||
'Reverse Proxy',
|
||||
'Rich Messaging Client',
|
||||
'Rich Messaging Server',
|
||||
'Scheduling',
|
||||
'Server Monitoring',
|
||||
'Share Secrets Management',
|
||||
'Single Sign-On',
|
||||
'Social Bookmarking',
|
||||
'Social Media',
|
||||
'Software Development Forge',
|
||||
'Software Interface Designer',
|
||||
|
@ -382,15 +479,24 @@
|
|||
'Website Analytics'
|
||||
];
|
||||
|
||||
const filtered_technologies = filterTechnologiesByCategoryList(results.active_services, category_filter_list);
|
||||
const filtered_technologies = filterTechnologiesByCategoryList(
|
||||
results.active_services,
|
||||
category_filter_list
|
||||
);
|
||||
const technologies = sortTechnologies(filtered_technologies);
|
||||
|
||||
|
||||
</script>
|
||||
|
||||
<div class="webservices">
|
||||
<h1>Web Services</h1>
|
||||
|
||||
<div class="introduction">
|
||||
<p>
|
||||
This sites exists to provide an 'always-up-to-date', in-depth description of the <a
|
||||
href="https://tech.oeru.org/foss-libresoftware-its-about-clarity-and-values"
|
||||
title="What do I mean by 'libre' software?">libre software</a> web services <a href="https://davelane.nz" title="Who is this Dave Lane character?">I</a> have set
|
||||
up and maintain.
|
||||
</p>
|
||||
</div>
|
||||
<div class="summary">
|
||||
<ul>
|
||||
<li>Total number of services: {results.total_instances}</li>
|
||||
|
@ -398,35 +504,76 @@
|
|||
</div>
|
||||
<div class="filters">
|
||||
<div class="tag-list tech">
|
||||
<div class="tags tech categories">Categories: {#each category_list as category}<span class="tag category">{category}</span> {/each}</div>
|
||||
<div class="tags tech categories">
|
||||
Categories<br/> {#each category_list as category}<span class="tag category" style="white-space: nowrap; word-break: normal;">{category} xo</span><wbr/>{/each}
|
||||
</div>
|
||||
<!--<div class="tags tech analogues">{#each analogues as analogue}<span class="tag analogue">{analogue}</span> {/each}</div>-->
|
||||
<div class="tags tech licenses">Licenses: {#each license_list as license}<span class="tag license">{license}</span> {/each}</div>
|
||||
<div class="tags tech licenses">
|
||||
Licenses<br/> {#each license_list as license}<span class="tag license">{license} xo</span><wbr/>{/each}
|
||||
</div>
|
||||
</div>
|
||||
<div class="tag-list instance">
|
||||
<div class="tags instance statuses">Statuses: {#each status_list as status}<span class="tag status">{status}</span> {/each}</div> <div class="tags instance affiliates">Affiliates: {#each affiliate_list as affiliate}<span class="tag affiliate"><span class="marker circle" style="background-color: {affiliate_colours[affiliate].colour}"></span> {affiliate}</span> {/each}</div>
|
||||
<div class="tags instance hosts">Hosts: {#each host_list as host}<span class="tag host"><span class="marker square" style="background-color: {host_colours[host].colour}"></span> {host}</span> {/each}</div>
|
||||
<div class="tags instance statuses">
|
||||
Statuses<br/> {#each status_list as status}<span class="tag status">{status} xo</span><wbr/>{/each}
|
||||
</div>
|
||||
<div class="tags instance affiliates">
|
||||
Affiliates<br/> {#each affiliate_list as affiliate}<span class="tag affiliate"><span class="marker circle" style="background-color: {affiliate_colours[affiliate].colour}"></span> {affiliate} xo</span><wbr/>{/each}
|
||||
</div>
|
||||
<div class="tags instance hosts">
|
||||
Hosts<br/> {#each host_list as host}<span class="tag host"><span class="marker square" style="background-color: {host_colours[host].colour}"></span> {host} xo</span><wbr/>{/each}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="tiles">
|
||||
{#each filtered_technologies as technology}
|
||||
<div class="tile technology">
|
||||
<div class="links">
|
||||
{#if (technology.repository)}<span class="repository"><a href="{technology.repository}" title="The source code repository for {technology.name}">R</a></span>{/if}
|
||||
{#if (technology.wikipedia)}<span class="wikipedia"><a href="{technology.wikipedia}" title="Wikipedia Page for {technology.name}">W</a></span>{/if}
|
||||
{#if technology.repository}<span class="repository"
|
||||
><a
|
||||
href={technology.repository}
|
||||
title="The source code repository for {technology.name}">R</a
|
||||
></span
|
||||
>{/if}
|
||||
{#if technology.wikipedia}<span class="wikipedia"
|
||||
><a href={technology.wikipedia} title="Wikipedia Page for {technology.name}">W</a
|
||||
></span
|
||||
>{/if}
|
||||
</div>
|
||||
<h2><a href="{technology.website}">{technology.name}</a></h2>
|
||||
{#if (technology.license)}<p class="license" title="The libre license for this project is {technology.license}">License: <span class="value">{technology.license}</span></p>{/if}
|
||||
{#if (technology.categories)}<p class="categories">{pluraliser('Category','Categories',technology.categories.length)}: <span class="value">{toOxfordCommaString(technology.categories)}</span></p>{/if}
|
||||
{#if (technology.analogues)}<p class="analogues">Alternative to <span class="value">{toOxfordCommaString(technology.analogues)}</span></p>{/if}
|
||||
<p class="description">{technology.description}</p>
|
||||
<h2><a href={technology.website}>{technology.name}</a></h2>
|
||||
{#if technology.license}<p
|
||||
class="license"
|
||||
title="The libre license for this project is {technology.license}"
|
||||
>
|
||||
License: <span class="value">{technology.license}</span>
|
||||
</p>{/if}
|
||||
{#if technology.categories}<p class="categories">
|
||||
{pluraliser('Category', 'Categories', technology.categories.length)}:
|
||||
<span class="value">{toOxfordCommaString(technology.categories)}</span>
|
||||
</p>{/if}
|
||||
{#if technology.analogues}<p class="analogues">
|
||||
Alternative to <span class="value">{toOxfordCommaString(technology.analogues)}</span>
|
||||
</p>{/if}
|
||||
<p class="description">
|
||||
{technology.description}{#if technology.extended_description}<span
|
||||
title={technology.extended_description}>i</span
|
||||
>{/if}
|
||||
</p>
|
||||
{#if hasInstances(technology)}
|
||||
<div class="instances"><p>{#each technology.instances as instance}
|
||||
<a href="https://{instance.domain}" title="{technology.name} instance {instance.domain} hosted on '{instance.host}' by {instance.affiliation}"><span class="marker circle" style="background-color: {affiliate_colours[instance.affiliation].colour}"></span></a>{/each}</p>
|
||||
<div class="instances">
|
||||
<p>
|
||||
{#each technology.instances as instance}
|
||||
<a
|
||||
href="https://{instance.domain}"
|
||||
title="{technology.name} instance {instance.domain} hosted on '{instance.host}' by {instance.affiliation}"
|
||||
><span
|
||||
class="marker circle"
|
||||
style="background-color: {affiliate_colours[instance.affiliation].colour}"
|
||||
></span></a
|
||||
>{/each}
|
||||
</p>
|
||||
</div>
|
||||
{:else}
|
||||
<div class="instances">
|
||||
Nothing here yet...
|
||||
</div>
|
||||
<div class="instances">Nothing here yet...</div>
|
||||
{/if}
|
||||
</div>
|
||||
{/each}
|
||||
|
@ -436,11 +583,15 @@
|
|||
<style>
|
||||
.webservices {
|
||||
display: grid;
|
||||
width: 96%;
|
||||
margin: 0 auto 3em auto;
|
||||
padding: 0;
|
||||
}
|
||||
.webservices h1 {
|
||||
padding: 0;
|
||||
}
|
||||
.summary {
|
||||
background-color: #ccc;
|
||||
display: block;
|
||||
}
|
||||
.filters {
|
||||
/*background-color: #f1ff94;
|
||||
|
@ -455,9 +606,14 @@
|
|||
display: block;
|
||||
}
|
||||
.tag-list {
|
||||
white-space: normal;
|
||||
/* white-space: normal;
|
||||
word-break: normal;
|
||||
display: inline;
|
||||
display: inline;*/
|
||||
}
|
||||
.tags {
|
||||
/* white-space: normal;
|
||||
word-break: normal;
|
||||
display: inline;*/
|
||||
}
|
||||
.tag {
|
||||
font-size: 80%;
|
||||
|
@ -466,15 +622,16 @@
|
|||
padding: 6px 8px;
|
||||
border-radius: 14px;
|
||||
white-space: nowrap;
|
||||
word-break: keep-all;
|
||||
word-break: normal;
|
||||
box-shadow: 3px 3px 3px #71ba71;
|
||||
}
|
||||
.tag:after { content: "\00a0"; }
|
||||
.tag:hover { box-shadow: 4px 4px 3px #71ba71;}
|
||||
.tag.category {
|
||||
background-color: #a369a2;
|
||||
background-color: #9aa34d;
|
||||
color: #fff;
|
||||
}
|
||||
.tag.license {
|
||||
background-color: #a369a2;
|
||||
background-color: #6498a3;
|
||||
color: #fff;
|
||||
}
|
||||
.tag.status {
|
||||
|
@ -496,7 +653,7 @@
|
|||
}
|
||||
/* flip card stuff: https://www.w3schools.com/howto/howto_css_flip_card.asp */
|
||||
.tile {
|
||||
height: 500px;
|
||||
height: 515px;
|
||||
min-width: 270px;
|
||||
max-width: 400px;
|
||||
overflow: hidden;
|
||||
|
@ -515,18 +672,34 @@
|
|||
padding: 2px 6px;
|
||||
font-size: 90%;
|
||||
}
|
||||
.tile .links a { color: #e6c4fc; }
|
||||
.tile .links a:visited { color: #ced0ff; }
|
||||
.tile .links a:hover { color: #fff; }
|
||||
.tile:hover {
|
||||
box-shadow: 10px 10px 6px #71ba71;
|
||||
}
|
||||
.tile .links h2 a {
|
||||
color: #e6c4fc;
|
||||
}
|
||||
.tile .links a:visited {
|
||||
color: #ced0ff;
|
||||
}
|
||||
.tile .links a:hover,
|
||||
.tile:hover h2 a {
|
||||
color: #fff !important;
|
||||
}
|
||||
.tile h2 {
|
||||
background-color: #999;
|
||||
text-align: center;
|
||||
padding: 0.5em;
|
||||
margin: 0;
|
||||
}
|
||||
.tile h2 a { color: #e6c4fc; }
|
||||
.tile h2 a:visited { color: #ced0ff; }
|
||||
.tile h2 a:hover { color: #fff; }
|
||||
.tile h2 a {
|
||||
color: #e6c4fc;
|
||||
}
|
||||
.tile h2 a:visited {
|
||||
color: #ced0ff;
|
||||
}
|
||||
.tile h2 a:hover {
|
||||
color: #fff;
|
||||
}
|
||||
.tile .description {
|
||||
height: 260px;
|
||||
text-overflow: ellipsis;
|
||||
|
@ -547,17 +720,16 @@
|
|||
.instances .marker {
|
||||
margin-right: 3px;
|
||||
}
|
||||
.webservices li { list-style-type: none; }
|
||||
.value { font-weight: bold; color: #000; }
|
||||
.marker { vertical-align: middle; }
|
||||
/* .triangle {
|
||||
--side-size: 20px;
|
||||
border-left: var(--side-size) solid transparent;
|
||||
border-right: var(--side-size) solid transparent;
|
||||
border-bottom: calc(2 * var(--side-size) * 0.866) solid green;
|
||||
border-top: var(--side-size) solid transparent;
|
||||
display: inline-block;
|
||||
}*/
|
||||
.webservices li {
|
||||
list-style-type: none;
|
||||
}
|
||||
.value {
|
||||
font-weight: bold;
|
||||
color: #000;
|
||||
}
|
||||
.marker {
|
||||
vertical-align: middle;
|
||||
}
|
||||
.circle {
|
||||
height: 20px;
|
||||
width: 20px;
|
||||
|
|
1
src/routes/about/+page.svelte
Normal file
1
src/routes/about/+page.svelte
Normal file
|
@ -0,0 +1 @@
|
|||
<h1>About this site... </h1>
|
1
src/routes/attribution/+page.svelte
Normal file
1
src/routes/attribution/+page.svelte
Normal file
|
@ -0,0 +1 @@
|
|||
<h1>Attribution for included media</h1>
|
Loading…
Reference in a new issue