mirror of
https://github.com/CommunitySolidServer/CommunitySolidServer.git
synced 2024-10-03 14:55:10 +00:00
feat: Add HTML container listing.
This commit is contained in:
parent
c0dac12111
commit
1394b9cb56
27
config/util/representation-conversion/converters/errors.json
Normal file
27
config/util/representation-conversion/converters/errors.json
Normal file
@ -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"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
@ -14,6 +14,16 @@
|
|||||||
"@type": "HandlebarsTemplateEngine",
|
"@type": "HandlebarsTemplateEngine",
|
||||||
"template": "$PACKAGE_ROOT/templates/main.html.hbs"
|
"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"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
@ -2,9 +2,10 @@
|
|||||||
"@context": "https://linkedsoftwaredependencies.org/bundles/npm/@solid/community-server/^1.0.0/components/context.jsonld",
|
"@context": "https://linkedsoftwaredependencies.org/bundles/npm/@solid/community-server/^1.0.0/components/context.jsonld",
|
||||||
"import": [
|
"import": [
|
||||||
"files-scs:config/util/representation-conversion/converters/content-type-replacer.json",
|
"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/quad-to-rdf.json",
|
||||||
"files-scs:config/util/representation-conversion/converters/rdf-to-quad.json",
|
"files-scs:config/util/representation-conversion/converters/rdf-to-quad.json"
|
||||||
"files-scs:config/util/representation-conversion/converters/markdown.json"
|
|
||||||
],
|
],
|
||||||
"@graph": [
|
"@graph": [
|
||||||
{
|
{
|
||||||
@ -26,18 +27,9 @@
|
|||||||
"converters": [
|
"converters": [
|
||||||
{ "@id": "urn:solid-server:default:RdfToQuadConverter" },
|
{ "@id": "urn:solid-server:default:RdfToQuadConverter" },
|
||||||
{ "@id": "urn:solid-server:default:QuadToRdfConverter" },
|
{ "@id": "urn:solid-server:default:QuadToRdfConverter" },
|
||||||
{ "@type": "ErrorToQuadConverter" },
|
{ "@id": "urn:solid-server:default:ContainerToTemplateConverter" },
|
||||||
{
|
{ "@id": "urn:solid-server:default: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"
|
|
||||||
},
|
|
||||||
{ "@id": "urn:solid-server:default:MarkdownToHtmlConverter" }
|
{ "@id": "urn:solid-server:default:MarkdownToHtmlConverter" }
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
34
package-lock.json
generated
34
package-lock.json
generated
@ -18,6 +18,7 @@
|
|||||||
"@types/bcrypt": "^5.0.0",
|
"@types/bcrypt": "^5.0.0",
|
||||||
"@types/cors": "^2.8.10",
|
"@types/cors": "^2.8.10",
|
||||||
"@types/end-of-stream": "^1.4.0",
|
"@types/end-of-stream": "^1.4.0",
|
||||||
|
"@types/lodash.orderby": "^4.6.6",
|
||||||
"@types/marked": "^2.0.3",
|
"@types/marked": "^2.0.3",
|
||||||
"@types/mime-types": "^2.1.0",
|
"@types/mime-types": "^2.1.0",
|
||||||
"@types/n3": "^1.10.0",
|
"@types/n3": "^1.10.0",
|
||||||
@ -47,6 +48,7 @@
|
|||||||
"fetch-sparql-endpoint": "^2.0.1",
|
"fetch-sparql-endpoint": "^2.0.1",
|
||||||
"handlebars": "^4.7.7",
|
"handlebars": "^4.7.7",
|
||||||
"jose": "^3.11.6",
|
"jose": "^3.11.6",
|
||||||
|
"lodash.orderby": "^4.6.0",
|
||||||
"marked": "^2.1.3",
|
"marked": "^2.1.3",
|
||||||
"mime-types": "^2.1.31",
|
"mime-types": "^2.1.31",
|
||||||
"n3": "^1.10.0",
|
"n3": "^1.10.0",
|
||||||
@ -6004,8 +6006,7 @@
|
|||||||
"node_modules/@types/lodash": {
|
"node_modules/@types/lodash": {
|
||||||
"version": "4.14.170",
|
"version": "4.14.170",
|
||||||
"resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.170.tgz",
|
"resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.170.tgz",
|
||||||
"integrity": "sha512-bpcvu/MKHHeYX+qeEN8GE7DIravODWdACVA1ctevD8CN24RhPZIKMn9ntfAsrvLfSX3cR5RrBKAbYm9bGs0A+Q==",
|
"integrity": "sha512-bpcvu/MKHHeYX+qeEN8GE7DIravODWdACVA1ctevD8CN24RhPZIKMn9ntfAsrvLfSX3cR5RrBKAbYm9bGs0A+Q=="
|
||||||
"dev": true
|
|
||||||
},
|
},
|
||||||
"node_modules/@types/lodash.clonedeep": {
|
"node_modules/@types/lodash.clonedeep": {
|
||||||
"version": "4.5.6",
|
"version": "4.5.6",
|
||||||
@ -6016,6 +6017,14 @@
|
|||||||
"@types/lodash": "*"
|
"@types/lodash": "*"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@types/lodash.orderby": {
|
||||||
|
"version": "4.6.6",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/lodash.orderby/-/lodash.orderby-4.6.6.tgz",
|
||||||
|
"integrity": "sha512-wQzu6xK+bSwhu45OeMI7fjywiIZiiaBzJB8W3fwnF1SJXHoOXRLutrSnVmq4yHPOM036qsy8lx9wHQcAbXNjJw==",
|
||||||
|
"dependencies": {
|
||||||
|
"@types/lodash": "*"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@types/lru-cache": {
|
"node_modules/@types/lru-cache": {
|
||||||
"version": "5.1.0",
|
"version": "5.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/@types/lru-cache/-/lru-cache-5.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/@types/lru-cache/-/lru-cache-5.1.0.tgz",
|
||||||
@ -14304,6 +14313,11 @@
|
|||||||
"integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==",
|
"integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
|
"node_modules/lodash.orderby": {
|
||||||
|
"version": "4.6.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/lodash.orderby/-/lodash.orderby-4.6.0.tgz",
|
||||||
|
"integrity": "sha1-5pfwTOXXhSL1TZM4syuBozk+TrM="
|
||||||
|
},
|
||||||
"node_modules/lodash.template": {
|
"node_modules/lodash.template": {
|
||||||
"version": "4.5.0",
|
"version": "4.5.0",
|
||||||
"resolved": "https://registry.npmjs.org/lodash.template/-/lodash.template-4.5.0.tgz",
|
"resolved": "https://registry.npmjs.org/lodash.template/-/lodash.template-4.5.0.tgz",
|
||||||
@ -23673,8 +23687,7 @@
|
|||||||
"@types/lodash": {
|
"@types/lodash": {
|
||||||
"version": "4.14.170",
|
"version": "4.14.170",
|
||||||
"resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.170.tgz",
|
"resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.170.tgz",
|
||||||
"integrity": "sha512-bpcvu/MKHHeYX+qeEN8GE7DIravODWdACVA1ctevD8CN24RhPZIKMn9ntfAsrvLfSX3cR5RrBKAbYm9bGs0A+Q==",
|
"integrity": "sha512-bpcvu/MKHHeYX+qeEN8GE7DIravODWdACVA1ctevD8CN24RhPZIKMn9ntfAsrvLfSX3cR5RrBKAbYm9bGs0A+Q=="
|
||||||
"dev": true
|
|
||||||
},
|
},
|
||||||
"@types/lodash.clonedeep": {
|
"@types/lodash.clonedeep": {
|
||||||
"version": "4.5.6",
|
"version": "4.5.6",
|
||||||
@ -23685,6 +23698,14 @@
|
|||||||
"@types/lodash": "*"
|
"@types/lodash": "*"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"@types/lodash.orderby": {
|
||||||
|
"version": "4.6.6",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/lodash.orderby/-/lodash.orderby-4.6.6.tgz",
|
||||||
|
"integrity": "sha512-wQzu6xK+bSwhu45OeMI7fjywiIZiiaBzJB8W3fwnF1SJXHoOXRLutrSnVmq4yHPOM036qsy8lx9wHQcAbXNjJw==",
|
||||||
|
"requires": {
|
||||||
|
"@types/lodash": "*"
|
||||||
|
}
|
||||||
|
},
|
||||||
"@types/lru-cache": {
|
"@types/lru-cache": {
|
||||||
"version": "5.1.0",
|
"version": "5.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/@types/lru-cache/-/lru-cache-5.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/@types/lru-cache/-/lru-cache-5.1.0.tgz",
|
||||||
@ -30166,6 +30187,11 @@
|
|||||||
"integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==",
|
"integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
|
"lodash.orderby": {
|
||||||
|
"version": "4.6.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/lodash.orderby/-/lodash.orderby-4.6.0.tgz",
|
||||||
|
"integrity": "sha1-5pfwTOXXhSL1TZM4syuBozk+TrM="
|
||||||
|
},
|
||||||
"lodash.template": {
|
"lodash.template": {
|
||||||
"version": "4.5.0",
|
"version": "4.5.0",
|
||||||
"resolved": "https://registry.npmjs.org/lodash.template/-/lodash.template-4.5.0.tgz",
|
"resolved": "https://registry.npmjs.org/lodash.template/-/lodash.template-4.5.0.tgz",
|
||||||
|
@ -84,6 +84,7 @@
|
|||||||
"@types/bcrypt": "^5.0.0",
|
"@types/bcrypt": "^5.0.0",
|
||||||
"@types/cors": "^2.8.10",
|
"@types/cors": "^2.8.10",
|
||||||
"@types/end-of-stream": "^1.4.0",
|
"@types/end-of-stream": "^1.4.0",
|
||||||
|
"@types/lodash.orderby": "^4.6.6",
|
||||||
"@types/marked": "^2.0.3",
|
"@types/marked": "^2.0.3",
|
||||||
"@types/mime-types": "^2.1.0",
|
"@types/mime-types": "^2.1.0",
|
||||||
"@types/n3": "^1.10.0",
|
"@types/n3": "^1.10.0",
|
||||||
@ -113,6 +114,7 @@
|
|||||||
"fetch-sparql-endpoint": "^2.0.1",
|
"fetch-sparql-endpoint": "^2.0.1",
|
||||||
"handlebars": "^4.7.7",
|
"handlebars": "^4.7.7",
|
||||||
"jose": "^3.11.6",
|
"jose": "^3.11.6",
|
||||||
|
"lodash.orderby": "^4.6.0",
|
||||||
"marked": "^2.1.3",
|
"marked": "^2.1.3",
|
||||||
"mime-types": "^2.1.31",
|
"mime-types": "^2.1.31",
|
||||||
"n3": "^1.10.0",
|
"n3": "^1.10.0",
|
||||||
|
@ -216,6 +216,7 @@ export * from './storage/accessors/SparqlDataAccessor';
|
|||||||
// Storage/Conversion
|
// Storage/Conversion
|
||||||
export * from './storage/conversion/ChainedConverter';
|
export * from './storage/conversion/ChainedConverter';
|
||||||
export * from './storage/conversion/ConstantConverter';
|
export * from './storage/conversion/ConstantConverter';
|
||||||
|
export * from './storage/conversion/ContainerToTemplateConverter';
|
||||||
export * from './storage/conversion/ContentTypeReplacer';
|
export * from './storage/conversion/ContentTypeReplacer';
|
||||||
export * from './storage/conversion/ConversionUtil';
|
export * from './storage/conversion/ConversionUtil';
|
||||||
export * from './storage/conversion/ErrorToQuadConverter';
|
export * from './storage/conversion/ErrorToQuadConverter';
|
||||||
|
@ -1,6 +1,4 @@
|
|||||||
import type { Readable } from 'stream';
|
import type { Readable } from 'stream';
|
||||||
import { promisify } from 'util';
|
|
||||||
import eos from 'end-of-stream';
|
|
||||||
import type { AuxiliaryIdentifierStrategy } from '../ldp/auxiliary/AuxiliaryIdentifierStrategy';
|
import type { AuxiliaryIdentifierStrategy } from '../ldp/auxiliary/AuxiliaryIdentifierStrategy';
|
||||||
import type { Patch } from '../ldp/http/Patch';
|
import type { Patch } from '../ldp/http/Patch';
|
||||||
import { BasicRepresentation } from '../ldp/representation/BasicRepresentation';
|
import { BasicRepresentation } from '../ldp/representation/BasicRepresentation';
|
||||||
@ -9,10 +7,10 @@ import type { RepresentationPreferences } from '../ldp/representation/Representa
|
|||||||
import type { ResourceIdentifier } from '../ldp/representation/ResourceIdentifier';
|
import type { ResourceIdentifier } from '../ldp/representation/ResourceIdentifier';
|
||||||
import { getLoggerFor } from '../logging/LogUtil';
|
import { getLoggerFor } from '../logging/LogUtil';
|
||||||
import type { ExpiringReadWriteLocker } from '../util/locking/ExpiringReadWriteLocker';
|
import type { ExpiringReadWriteLocker } from '../util/locking/ExpiringReadWriteLocker';
|
||||||
|
import { endOfStream } from '../util/StreamUtil';
|
||||||
import type { AtomicResourceStore } from './AtomicResourceStore';
|
import type { AtomicResourceStore } from './AtomicResourceStore';
|
||||||
import type { Conditions } from './Conditions';
|
import type { Conditions } from './Conditions';
|
||||||
import type { ResourceStore } from './ResourceStore';
|
import type { ResourceStore } from './ResourceStore';
|
||||||
const endOfStream = promisify(eos);
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Store that for every call acquires a lock before executing it on the requested resource,
|
* Store that for every call acquires a lock before executing it on the requested resource,
|
||||||
|
80
src/storage/conversion/ContainerToTemplateConverter.ts
Normal file
80
src/storage/conversion/ContainerToTemplateConverter.ts
Normal file
@ -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]) : '/';
|
||||||
|
}
|
||||||
|
}
|
@ -1,12 +1,16 @@
|
|||||||
import type { Writable, ReadableOptions, DuplexOptions } from 'stream';
|
import type { Writable, ReadableOptions, DuplexOptions } from 'stream';
|
||||||
import { Readable, Transform } from 'stream';
|
import { Readable, Transform } from 'stream';
|
||||||
|
import { promisify } from 'util';
|
||||||
import arrayifyStream from 'arrayify-stream';
|
import arrayifyStream from 'arrayify-stream';
|
||||||
|
import eos from 'end-of-stream';
|
||||||
import pump from 'pump';
|
import pump from 'pump';
|
||||||
import { getLoggerFor } from '../logging/LogUtil';
|
import { getLoggerFor } from '../logging/LogUtil';
|
||||||
import { isHttpRequest } from '../server/HttpRequest';
|
import { isHttpRequest } from '../server/HttpRequest';
|
||||||
import type { Guarded } from './GuardedStream';
|
import type { Guarded } from './GuardedStream';
|
||||||
import { guardStream } from './GuardedStream';
|
import { guardStream } from './GuardedStream';
|
||||||
|
|
||||||
|
export const endOfStream = promisify(eos);
|
||||||
|
|
||||||
const logger = getLoggerFor('StreamUtil');
|
const logger = getLoggerFor('StreamUtil');
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -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",
|
"@type": "SingleRootIdentifierStrategy",
|
||||||
"baseUrl": {
|
"baseUrl": {
|
||||||
"@id": "urn:solid-server:template:variable:baseUrl"
|
"@id": "urn:solid-server:template:variable:baseUrl"
|
||||||
@ -25,7 +26,7 @@
|
|||||||
"@id": "urn:solid-server:template:DataAccessor"
|
"@id": "urn:solid-server:template:DataAccessor"
|
||||||
},
|
},
|
||||||
"identifierStrategy": {
|
"identifierStrategy": {
|
||||||
"@id": "urn:solid-server:template:IdentifierStrategy"
|
"@id": "urn:solid-server:default:IdentifierStrategy"
|
||||||
},
|
},
|
||||||
"auxiliaryStrategy": {
|
"auxiliaryStrategy": {
|
||||||
"@id": "urn:solid-server:default:AuxiliaryStrategy"
|
"@id": "urn:solid-server:default:AuxiliaryStrategy"
|
||||||
|
@ -12,7 +12,7 @@
|
|||||||
"@id": "urn:solid-server:template:DataAccessor",
|
"@id": "urn:solid-server:template:DataAccessor",
|
||||||
"@type": "InMemoryDataAccessor",
|
"@type": "InMemoryDataAccessor",
|
||||||
"identifierStrategy": {
|
"identifierStrategy": {
|
||||||
"@id": "urn:solid-server:template:IdentifierStrategy"
|
"@id": "urn:solid-server:default:IdentifierStrategy"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
8
templates/container.md.hbs
Normal file
8
templates/container.md.hbs
Normal file
@ -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>
|
@ -174,3 +174,12 @@ form ul.actions > li {
|
|||||||
display: inline;
|
display: inline;
|
||||||
margin-right: 1em;
|
margin-right: 1em;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ul.container > li {
|
||||||
|
margin: 0.25em 0;
|
||||||
|
list-style-type: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
ul.container > li.container > a {
|
||||||
|
font-weight: 800;
|
||||||
|
}
|
||||||
|
@ -0,0 +1,95 @@
|
|||||||
|
import { namedNode as nn, quad } from '@rdfjs/data-model';
|
||||||
|
import { BasicRepresentation } from '../../../../src/ldp/representation/BasicRepresentation';
|
||||||
|
import { ContainerToTemplateConverter } from '../../../../src/storage/conversion/ContainerToTemplateConverter';
|
||||||
|
import { readableToString } from '../../../../src/util/StreamUtil';
|
||||||
|
import type { TemplateEngine } from '../../../../src/util/templates/TemplateEngine';
|
||||||
|
import { LDP, RDF } from '../../../../src/util/Vocabularies';
|
||||||
|
|
||||||
|
describe('A ContainerToTemplateConverter', (): void => {
|
||||||
|
const preferences = {};
|
||||||
|
let templateEngine: jest.Mocked<TemplateEngine>;
|
||||||
|
let converter: ContainerToTemplateConverter;
|
||||||
|
|
||||||
|
beforeEach(async(): Promise<void> => {
|
||||||
|
templateEngine = {
|
||||||
|
render: jest.fn().mockReturnValue(Promise.resolve('<html>')),
|
||||||
|
};
|
||||||
|
converter = new ContainerToTemplateConverter(templateEngine, 'text/html');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('supports containers.', async(): Promise<void> => {
|
||||||
|
const container = { path: 'http://test.com/foo/bar/container/' };
|
||||||
|
const representation = new BasicRepresentation([], 'internal/quads', false);
|
||||||
|
await expect(converter.canHandle({ identifier: container, representation, preferences }))
|
||||||
|
.resolves.toBeUndefined();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('does not support documents.', async(): Promise<void> => {
|
||||||
|
const document = { path: 'http://test.com/foo/bar/document' };
|
||||||
|
const representation = new BasicRepresentation([], 'internal/quads', false);
|
||||||
|
await expect(converter.canHandle({ identifier: document, representation, preferences }))
|
||||||
|
.rejects.toThrow('Can only convert containers');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('calls the template with the contained resources.', async(): Promise<void> => {
|
||||||
|
const container = { path: 'http://test.com/foo/bar/my-container/' };
|
||||||
|
const representation = new BasicRepresentation([
|
||||||
|
quad(nn(container.path), RDF.terms.type, LDP.terms.BasicContainer),
|
||||||
|
quad(nn(container.path), LDP.terms.contains, nn(`${container.path}b`)),
|
||||||
|
quad(nn(container.path), LDP.terms.contains, nn(`${container.path}a`)),
|
||||||
|
quad(nn(container.path), LDP.terms.contains, nn(`${container.path}a`)),
|
||||||
|
quad(nn(container.path), LDP.terms.contains, nn(`${container.path}ccc`)),
|
||||||
|
quad(nn(container.path), LDP.terms.contains, nn(`${container.path}d/`)),
|
||||||
|
quad(nn(`${container.path}d/`), LDP.terms.contains, nn(`${container.path}d`)),
|
||||||
|
], 'internal/quads', false);
|
||||||
|
const converted = await converter.handle({ identifier: container, representation, preferences });
|
||||||
|
|
||||||
|
expect(converted.binary).toBe(true);
|
||||||
|
expect(converted.metadata.contentType).toBe('text/html');
|
||||||
|
await expect(readableToString(converted.data)).resolves.toBe('<html>');
|
||||||
|
|
||||||
|
expect(templateEngine.render).toHaveBeenCalledTimes(1);
|
||||||
|
expect(templateEngine.render).toHaveBeenCalledWith({
|
||||||
|
container: 'my-container',
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
identifier: `${container.path}d/`,
|
||||||
|
name: 'd/',
|
||||||
|
container: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
identifier: `${container.path}a`,
|
||||||
|
name: 'a',
|
||||||
|
container: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
identifier: `${container.path}b`,
|
||||||
|
name: 'b',
|
||||||
|
container: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
identifier: `${container.path}ccc`,
|
||||||
|
name: 'ccc',
|
||||||
|
container: false,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('converts the root container.', async(): Promise<void> => {
|
||||||
|
const container = { path: 'http://test.com/' };
|
||||||
|
const representation = new BasicRepresentation([
|
||||||
|
quad(nn(container.path), RDF.terms.type, LDP.terms.BasicContainer),
|
||||||
|
quad(nn(container.path), LDP.terms.contains, nn(`${container.path}a`)),
|
||||||
|
quad(nn(container.path), LDP.terms.contains, nn(`${container.path}b`)),
|
||||||
|
quad(nn(container.path), LDP.terms.contains, nn(`${container.path}c`)),
|
||||||
|
], 'internal/quads', false);
|
||||||
|
await converter.handle({ identifier: container, representation, preferences });
|
||||||
|
|
||||||
|
expect(templateEngine.render).toHaveBeenCalledTimes(1);
|
||||||
|
expect(templateEngine.render).toHaveBeenCalledWith({
|
||||||
|
container: '/',
|
||||||
|
children: expect.objectContaining({ length: 3 }),
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
Loading…
x
Reference in New Issue
Block a user