Skip to content
Snippets Groups Projects
Commit 94456bef authored by Damian Janus's avatar Damian Janus Committed by Efe
Browse files

fix: 3speak embedder to work with watch and embed

parent c6931c8f
No related branches found
No related tags found
1 merge request!557Move hive renderer to internal packages
import { ClientFunction, Selector } from "testcafe";
import {ClientFunction, Selector} from 'testcafe';
fixture`Getting Started`.page`./index.html`;
const defaultOptions = {
baseUrl: "https://hive.blog/",
baseUrl: 'https://hive.blog/',
breaks: true,
skipSanitization: false,
allowInsecureScriptTags: false,
addTargetBlankToLinks: true,
addNofollowToLinks: true,
cssClassForInternalLinks: "hive-class",
cssClassForExternalLinks: "hive-class external",
cssClassForInternalLinks: 'hive-class',
cssClassForExternalLinks: 'hive-class external',
doNotShowImages: false,
assetsWidth: 640,
assetsHeight: 480,
......@@ -18,38 +18,65 @@ const defaultOptions = {
usertagUrlFn: (account) => `https://hive.blog/@${account}`,
hashtagUrlFn: (hashtag) => `/trending/${hashtag}`,
isLinkSafeFn: (url) => true, // !!url.match(/^(\/(?!\/)|https:\/\/hive.blog)/),
addExternalCssClassToMatchingLinksFn: (url) => !url.match(/^(\/(?!\/)|https:\/\/hive.blog)/),
addExternalCssClassToMatchingLinksFn: (url) => !url.match(/^(\/(?!\/)|https:\/\/hive.blog)/)
};
const renderInBrowser = ClientFunction((options, markup) => {
const renderer = new HiveContentRenderer.DefaultRenderer(options);
// Create a new instance of SpoilerPlugin directly
// To test custom plugins
const spoilerPlugin = {
name: 'spoiler',
preProcess: (text) => {
return text.replace(
/^>! *\[(.*?)\] *([\s\S]*?)(?=^>! *\[|$)/gm,
(_, title, content) => {
const cleanContent = content
.split('\n')
.map(line => line.replace(/^> ?/, '').trim())
.join('\n')
.trim();
return `<details class="spoiler">
<summary>${title}</summary>
${cleanContent}
</details>`;
}
);
}
};
const mergedOptions = Object.assign({}, options, {
plugins: [spoilerPlugin]
});
const renderer = new HiveContentRenderer.DefaultRenderer(mergedOptions);
return renderer.render(markup);
});
test("Renders properly simple markup", async (t) => {
const markup = "# H1";
test('Renders properly simple markup', async (t) => {
const markup = '# H1';
await t
.click(Selector("#awaiter"))
.expect(renderInBrowser({ ...defaultOptions }, markup))
.eql("<h1>H1</h1>\n");
.click(Selector('#awaiter'))
.expect(renderInBrowser({...defaultOptions}, markup))
.eql('<h1>H1</h1>\n');
});
test("Does not crash on mixed-img markup", async (t) => {
test('Does not crash on mixed-img markup', async (t) => {
const markup = `<img src="![Sacrifice The Truth Logo.jpg](https://images.hive.blog/DQmUjNstssuPJpjPDDWfRnw1x2tY6AWWKcajDMGpPLA5iJf/Sacrifice%20The%20Truth%20Logo.jpg)"/>`;
const expected = `<p><img src="brokenimg.jpg" /></p>\n`;
await t
.click(Selector("#awaiter"))
.expect(renderInBrowser({ ...defaultOptions }, markup))
.click(Selector('#awaiter'))
.expect(renderInBrowser({...defaultOptions}, markup))
.eql(expected);
});
test("Renders properly simple link markup with classes hive-test, external", async (t) => {
const markup = "[Hive Link](https://hive.io)";
test('Renders properly simple link markup with classes hive-test, external', async (t) => {
const markup = '[Hive Link](https://hive.io)';
await t
.click(Selector("#awaiter"))
.expect(renderInBrowser({ ...defaultOptions }, markup))
.click(Selector('#awaiter'))
.expect(renderInBrowser({...defaultOptions}, markup))
.eql(`<p><a href="https://hive.io" class="hive-class external">Hive Link</a></p>\n`);
});
......@@ -135,6 +135,9 @@ https://youtu.be/B7C83L6iWJQ
isLinkSafeFn: (url) => true,
addExternalCssClassToMatchingLinksFn: (url) => true,
ipfsPrefix: "https://ipfs.io/ipfs/",
plugins: [
new HiveContentRenderer.SpoilerPlugin()
]
});
$(document).ready(() => {
......
import {DefaultRenderer} from './renderers/default/DefaultRenderer';
import {SpoilerPlugin} from './renderers/default/plugins/SpoilerPlugin';
export {DefaultRenderer} from './renderers/default/DefaultRenderer';
export {SpoilerPlugin} from './renderers/default/plugins/SpoilerPlugin';
export {RendererPlugin} from './renderers/default/plugins/RendererPlugin';
export const HiveContentRenderer = {
DefaultRenderer
DefaultRenderer,
SpoilerPlugin
};
export default HiveContentRenderer;
......@@ -3,6 +3,7 @@ import {JSDOM} from 'jsdom';
import 'mocha';
import {Log} from '../../Log';
import {DefaultRenderer, RendererOptions} from './DefaultRenderer';
import {SpoilerPlugin} from './plugins/SpoilerPlugin';
describe('DefaultRender', () => {
const defaultOptions: RendererOptions = {
......@@ -21,7 +22,8 @@ describe('DefaultRender', () => {
usertagUrlFn: (account: string) => `https://hive.blog/@${account}`,
hashtagUrlFn: (hashtag: string) => `/trending/${hashtag}`,
isLinkSafeFn: (_url: string) => true, // !!url.match(/^(\/(?!\/)|https:\/\/hive.blog)/),
addExternalCssClassToMatchingLinksFn: (url: string) => !url.match(/^(\/(?!\/)|https:\/\/hive.blog)/)
addExternalCssClassToMatchingLinksFn: (url: string) => !url.match(/^(\/(?!\/)|https:\/\/hive.blog)/),
plugins: [new SpoilerPlugin()]
};
const tests = [
......@@ -194,6 +196,11 @@ describe('DefaultRender', () => {
raw: 'https://vimeo.com/174544848',
expected:
'<p><div class="videoWrapper"><iframe src="https://player.vimeo.com/video/174544848" width="640" height="480" frameborder="0" webkitallowfullscreen mozallowfullscreen allowfullscreen></iframe></div></p>'
},
{
name: 'Renders spoiler tags correctly',
raw: '>! [Click to reveal] Hidden content\n> More hidden text',
expected: '<details class="spoiler"><summary>Click to reveal</summary><p>Hidden content\nMore hidden text</p></details>'
}
];
......
......@@ -3,6 +3,7 @@ import {Remarkable} from 'remarkable';
import {SecurityChecker} from '../../security/SecurityChecker';
import {HtmlDOMParser} from './embedder/HtmlDOMParser';
import {Localization, LocalizationOptions} from './Localization';
import type {RendererPlugin} from './plugins/RendererPlugin';
import {PreliminarySanitizer} from './sanitization/PreliminarySanitizer';
import {TagTransformingSanitizer} from './sanitization/TagTransformingSanitizer';
......@@ -10,6 +11,7 @@ export class DefaultRenderer {
private options: RendererOptions;
private tagTransformingSanitizer: TagTransformingSanitizer;
private domParser: HtmlDOMParser;
private plugins: RendererPlugin[] = [];
public constructor(options: RendererOptions, localization: LocalizationOptions = Localization.DEFAULT) {
this.validate(options);
......@@ -45,6 +47,8 @@ export class DefaultRenderer {
},
localization
);
this.plugins = options.plugins || [];
}
public render(input: string): string {
......@@ -53,8 +57,10 @@ export class DefaultRenderer {
}
private doRender(text: string): string {
text = PreliminarySanitizer.preliminarySanitize(text);
// Pre-process with plugins
text = this.runPluginPhase('preProcess', text);
text = PreliminarySanitizer.preliminarySanitize(text);
const isHtml = this.isHtml(text);
text = isHtml ? text : this.renderMarkdown(text);
......@@ -64,9 +70,19 @@ export class DefaultRenderer {
SecurityChecker.checkSecurity(text, {allowScriptTag: this.options.allowInsecureScriptTags});
text = this.domParser.embedder.insertAssets(text);
// Post-process with plugins
text = this.runPluginPhase('postProcess', text);
return text;
}
private runPluginPhase(phase: 'preProcess' | 'postProcess', text: string): string {
return this.plugins.reduce((processedText, plugin) => {
const processor = plugin[phase];
return processor ? processor(processedText) : processedText;
}, text);
}
private renderMarkdown(text: string): string {
const renderer = new Remarkable({
html: true, // remarkable renders first then sanitize runs...
......@@ -145,4 +161,5 @@ export interface RendererOptions {
usertagUrlFn: (account: string) => string;
isLinkSafeFn: (url: string) => boolean;
addExternalCssClassToMatchingLinksFn: (url: string) => boolean;
plugins?: RendererPlugin[];
}
......@@ -110,7 +110,7 @@ export class StaticConfig {
div, iframe, del,
a, p, b, i, q, br, ul, li, ol, img, h1, h2, h3, h4, h5, h6, hr,
blockquote, pre, code, em, strong, center, table, thead, tbody, tr, th, td,
strike, sup, sub
strike, sup, sub, details, summary
`
.trim()
.split(/,\s*/)
......
export interface RendererPlugin {
name: string;
preProcess?: (text: string) => string;
postProcess?: (text: string) => string;
}
export interface PluginOptions {
plugins?: RendererPlugin[];
}
import {RendererPlugin} from './RendererPlugin';
export class SpoilerPlugin implements RendererPlugin {
name = 'spoiler';
preProcess(text: string): string {
return text.replace(/^>! *\[(.*?)\] *([\s\S]*?)(?=^>! *\[|$)/gm, (_, title, content) => {
const cleanContent = content
.split('\n')
.map((line: string) => line.replace(/^> ?/, '').trim())
.join('\n')
.trim();
return `<details class="spoiler">
<summary>${title}</summary>
${cleanContent}
</details>`;
});
}
}
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment