Skip to content

Commit

Permalink
feat: Add HTML container listing.
Browse files Browse the repository at this point in the history
  • Loading branch information
RubenVerborgh authored and joachimvh committed Jul 22, 2021
1 parent c0dac12 commit 1394b9c
Show file tree
Hide file tree
Showing 14 changed files with 277 additions and 24 deletions.
27 changes: 27 additions & 0 deletions config/util/representation-conversion/converters/errors.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
{
"@context": "https://linkedsoftwaredependencies.org/bundles/npm/@solid/community-server/^1.0.0/components/context.jsonld",
"import": [
"files-scs:config/util/representation-conversion/converters/content-type-replacer.json",
"files-scs:config/util/representation-conversion/converters/quad-to-rdf.json",
"files-scs:config/util/representation-conversion/converters/rdf-to-quad.json",
"files-scs:config/util/representation-conversion/converters/markdown.json"
],
"@graph": [
{
"@id": "urn:solid-server:default:ErrorToQuadConverter",
"@type": "ErrorToQuadConverter",
},
{
"comment": "Converts an error into a Markdown description of its details.",
"@id": "urn:solid-server:default:ErrorToTemplateConverter",
"@type": "ErrorToTemplateConverter",
"templateEngine": {
"@type": "HandlebarsTemplateEngine",
"template": "$PACKAGE_ROOT/templates/error/main.md.hbs"
},
"templatePath": "$PACKAGE_ROOT/templates/error/descriptions/",
"extension": ".md.hbs",
"contentType": "text/markdown"
}
]
}
10 changes: 10 additions & 0 deletions config/util/representation-conversion/converters/markdown.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,16 @@
"@type": "HandlebarsTemplateEngine",
"template": "$PACKAGE_ROOT/templates/main.html.hbs"
}
},
{
"comment": "Converts a container into a Markdown listing of its contents.",
"@id": "urn:solid-server:default:ContainerToTemplateConverter",
"@type": "ContainerToTemplateConverter",
"templateEngine": {
"@type": "HandlebarsTemplateEngine",
"template": "$PACKAGE_ROOT/templates/container.md.hbs"
},
"contentType": "text/markdown"
}
]
}
20 changes: 6 additions & 14 deletions config/util/representation-conversion/default.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,10 @@
"@context": "https://linkedsoftwaredependencies.org/bundles/npm/@solid/community-server/^1.0.0/components/context.jsonld",
"import": [
"files-scs:config/util/representation-conversion/converters/content-type-replacer.json",
"files-scs:config/util/representation-conversion/converters/errors.json",
"files-scs:config/util/representation-conversion/converters/markdown.json",
"files-scs:config/util/representation-conversion/converters/quad-to-rdf.json",
"files-scs:config/util/representation-conversion/converters/rdf-to-quad.json",
"files-scs:config/util/representation-conversion/converters/markdown.json"
"files-scs:config/util/representation-conversion/converters/rdf-to-quad.json"
],
"@graph": [
{
Expand All @@ -26,18 +27,9 @@
"converters": [
{ "@id": "urn:solid-server:default:RdfToQuadConverter" },
{ "@id": "urn:solid-server:default:QuadToRdfConverter" },
{ "@type": "ErrorToQuadConverter" },
{
"comment": "Converts an error into a Markdown description of its details.",
"@type": "ErrorToTemplateConverter",
"templateEngine": {
"@type": "HandlebarsTemplateEngine",
"template": "$PACKAGE_ROOT/templates/error/main.md.hbs"
},
"templatePath": "$PACKAGE_ROOT/templates/error/descriptions/",
"extension": ".md.hbs",
"contentType": "text/markdown"
},
{ "@id": "urn:solid-server:default:ContainerToTemplateConverter" },
{ "@id": "urn:solid-server:default:ErrorToQuadConverter" },
{ "@id": "urn:solid-server:default:ErrorToTemplateConverter" },
{ "@id": "urn:solid-server:default:MarkdownToHtmlConverter" }
]
}
Expand Down
34 changes: 30 additions & 4 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,7 @@
"@types/bcrypt": "^5.0.0",
"@types/cors": "^2.8.10",
"@types/end-of-stream": "^1.4.0",
"@types/lodash.orderby": "^4.6.6",
"@types/marked": "^2.0.3",
"@types/mime-types": "^2.1.0",
"@types/n3": "^1.10.0",
Expand Down Expand Up @@ -113,6 +114,7 @@
"fetch-sparql-endpoint": "^2.0.1",
"handlebars": "^4.7.7",
"jose": "^3.11.6",
"lodash.orderby": "^4.6.0",
"marked": "^2.1.3",
"mime-types": "^2.1.31",
"n3": "^1.10.0",
Expand Down
1 change: 1 addition & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -216,6 +216,7 @@ export * from './storage/accessors/SparqlDataAccessor';
// Storage/Conversion
export * from './storage/conversion/ChainedConverter';
export * from './storage/conversion/ConstantConverter';
export * from './storage/conversion/ContainerToTemplateConverter';
export * from './storage/conversion/ContentTypeReplacer';
export * from './storage/conversion/ConversionUtil';
export * from './storage/conversion/ErrorToQuadConverter';
Expand Down
4 changes: 1 addition & 3 deletions src/storage/LockingResourceStore.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,4 @@
import type { Readable } from 'stream';
import { promisify } from 'util';
import eos from 'end-of-stream';
import type { AuxiliaryIdentifierStrategy } from '../ldp/auxiliary/AuxiliaryIdentifierStrategy';
import type { Patch } from '../ldp/http/Patch';
import { BasicRepresentation } from '../ldp/representation/BasicRepresentation';
Expand All @@ -9,10 +7,10 @@ import type { RepresentationPreferences } from '../ldp/representation/Representa
import type { ResourceIdentifier } from '../ldp/representation/ResourceIdentifier';
import { getLoggerFor } from '../logging/LogUtil';
import type { ExpiringReadWriteLocker } from '../util/locking/ExpiringReadWriteLocker';
import { endOfStream } from '../util/StreamUtil';
import type { AtomicResourceStore } from './AtomicResourceStore';
import type { Conditions } from './Conditions';
import type { ResourceStore } from './ResourceStore';
const endOfStream = promisify(eos);

/**
* Store that for every call acquires a lock before executing it on the requested resource,
Expand Down
80 changes: 80 additions & 0 deletions src/storage/conversion/ContainerToTemplateConverter.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
import type { Readable } from 'stream';
import orderBy from 'lodash.orderby';
import type { Quad } from 'rdf-js';
import { BasicRepresentation } from '../../ldp/representation/BasicRepresentation';
import type { Representation } from '../../ldp/representation/Representation';
import { INTERNAL_QUADS } from '../../util/ContentTypes';
import { NotImplementedHttpError } from '../../util/errors/NotImplementedHttpError';
import { isContainerIdentifier, isContainerPath } from '../../util/PathUtil';
import { endOfStream } from '../../util/StreamUtil';
import type { TemplateEngine } from '../../util/templates/TemplateEngine';
import { LDP } from '../../util/Vocabularies';
import type { RepresentationConverterArgs } from './RepresentationConverter';
import { TypedRepresentationConverter } from './TypedRepresentationConverter';

interface ResourceDetails {
name: string;
identifier: string;
container: boolean;
}

/**
* A {@link RepresentationConverter} that creates a templated representation of a container.
*/
export class ContainerToTemplateConverter extends TypedRepresentationConverter {
private readonly templateEngine: TemplateEngine;
private readonly contentType: string;

public constructor(templateEngine: TemplateEngine, contentType: string) {
super(INTERNAL_QUADS, contentType);
this.templateEngine = templateEngine;
this.contentType = contentType;
}

public async canHandle(args: RepresentationConverterArgs): Promise<void> {
if (!isContainerIdentifier(args.identifier)) {
throw new NotImplementedHttpError('Can only convert containers.');
}
await super.canHandle(args);
}

public async handle({ identifier, representation }: RepresentationConverterArgs): Promise<Representation> {
const rendered = await this.templateEngine.render({
container: this.getLocalName(identifier.path),
children: await this.getChildResources(identifier.path, representation.data),
});
return new BasicRepresentation(rendered, representation.metadata, this.contentType);
}

/**
* Collects the children of the container as simple objects.
*/
private async getChildResources(container: string, quads: Readable): Promise<ResourceDetails[]> {
// Collect the needed bits of information from the containment triples
const resources = new Set<string>();
quads.on('data', ({ subject, predicate, object }: Quad): void => {
if (subject.value === container && predicate.equals(LDP.terms.contains)) {
resources.add(object.value);
}
});
await endOfStream(quads);

// Create a simplified object for every resource
const children = [ ...resources ].map((resource: string): ResourceDetails => ({
identifier: resource,
name: this.getLocalName(resource),
container: isContainerPath(resource),
}));

// Sort the resulting list
return orderBy(children, [ 'container', 'identifier' ], [ 'desc', 'asc' ]);
}

/**
* Derives a short name for the given resource.
*/
private getLocalName(iri: string, keepTrailingSlash = false): string {
const match = /:\/+[^/]+.*\/(([^/]+)\/?)$/u.exec(iri);
return match ? decodeURIComponent(match[keepTrailingSlash ? 1 : 2]) : '/';
}
}
4 changes: 4 additions & 0 deletions src/util/StreamUtil.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,16 @@
import type { Writable, ReadableOptions, DuplexOptions } from 'stream';
import { Readable, Transform } from 'stream';
import { promisify } from 'util';
import arrayifyStream from 'arrayify-stream';
import eos from 'end-of-stream';
import pump from 'pump';
import { getLoggerFor } from '../logging/LogUtil';
import { isHttpRequest } from '../server/HttpRequest';
import type { Guarded } from './GuardedStream';
import { guardStream } from './GuardedStream';

export const endOfStream = promisify(eos);

const logger = getLoggerFor('StreamUtil');

/**
Expand Down
5 changes: 3 additions & 2 deletions templates/config/defaults.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,8 @@
},

{
"@id": "urn:solid-server:template:IdentifierStrategy",
"comment": "Custom pods always use the suffix strategy with their pod URL as base.",
"@id": "urn:solid-server:default:IdentifierStrategy",
"@type": "SingleRootIdentifierStrategy",
"baseUrl": {
"@id": "urn:solid-server:template:variable:baseUrl"
Expand All @@ -25,7 +26,7 @@
"@id": "urn:solid-server:template:DataAccessor"
},
"identifierStrategy": {
"@id": "urn:solid-server:template:IdentifierStrategy"
"@id": "urn:solid-server:default:IdentifierStrategy"
},
"auxiliaryStrategy": {
"@id": "urn:solid-server:default:AuxiliaryStrategy"
Expand Down
2 changes: 1 addition & 1 deletion templates/config/memory.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
"@id": "urn:solid-server:template:DataAccessor",
"@type": "InMemoryDataAccessor",
"identifierStrategy": {
"@id": "urn:solid-server:template:IdentifierStrategy"
"@id": "urn:solid-server:default:IdentifierStrategy"
}
}
]
Expand Down
8 changes: 8 additions & 0 deletions templates/container.md.hbs
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
# Contents of {{container}}
<ul class="container">
{{#each children}}
<li class="{{#if container}}container{{else}}document{{/if}}">
<a href="{{identifier}}">{{name}}</a>
</li>
{{/each}}
</ul>
9 changes: 9 additions & 0 deletions templates/styles/main.css
Original file line number Diff line number Diff line change
Expand Up @@ -174,3 +174,12 @@ form ul.actions > li {
display: inline;
margin-right: 1em;
}

ul.container > li {
margin: 0.25em 0;
list-style-type: none;
}

ul.container > li.container > a {
font-weight: 800;
}
Loading

0 comments on commit 1394b9c

Please sign in to comment.