Skip to content
Snippets Groups Projects
Commit ca4cfc0f authored by Bartłomiej Górnicki's avatar Bartłomiej Górnicki
Browse files

Merge branch '7-extends-options-with-addcssclasstomatchinglinksfn' into 'master'

Closes #7

See merge request !11
parents 235f3e6e 87cb97f0
No related branches found
No related tags found
1 merge request!557Move hive renderer to internal packages
......@@ -2,7 +2,6 @@
[![npm](https://img.shields.io/npm/v/@hiveio/content-renderer.svg?style=flat-square)](https://www.npmjs.com/package/@hiveio/content-renderer) [![License](https://img.shields.io/github/license/wise-team/steem-content-renderer.svg?style=flat-square)](https://github.com/wise-team/steem-content-renderer/blob/master/LICENSE) [![PRs Welcome](https://img.shields.io/badge/PRs-welcome-brightgreen.svg?style=flat-square)](http://makeapullrequest.com)
👉 **[Online demo](https://hive.pages.syncad.com/hive-renderer/)**
Portable library that renders Hive posts and comments to string. It supports markdown and html and mimics the behaviour of condenser frontend.
......@@ -42,6 +41,7 @@ const renderer = new DefaultRenderer({
usertagUrlFn: (account: string) => "/@" + account,
hashtagUrlFn: (hashtag: string) => "/trending/" + hashtag,
isLinkSafeFn: (url: string) => true,
addExternalCssClassToMatchingLinksFn: (url: string) => true,
});
const safeHtmlStr = renderer.render(postContent);
......@@ -69,6 +69,7 @@ See [demo](https://hive.pages.syncad.com/hive-renderer/) and [its source](https:
usertagUrlFn: (account) => "/@" + account,
hashtagUrlFn: (hashtag) => "/trending/" + hashtag,
isLinkSafeFn: (url) => true,
addExternalCssClassToMatchingLinksFn: (url: string) => true,
});
$(document).ready(() => {
......
......@@ -9,7 +9,8 @@ const defaultOptions = {
allowInsecureScriptTags: false,
addTargetBlankToLinks: true,
addNofollowToLinks: true,
addCssClassToLinks: "hive-class",
cssClassForInternalLinks: "hive-class",
cssClassForExternalLinks: "hive-class external",
doNotShowImages: false,
ipfsPrefix: "",
assetsWidth: 640,
......@@ -18,6 +19,7 @@ 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)/),
};
const renderInBrowser = ClientFunction((options, markup) => {
......@@ -44,11 +46,11 @@ test("Does not crash on mixed-img markup", async (t) => {
.eql(expected);
});
test("Renders properly simple link markup with class hive-test", async (t) => {
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))
.eql(`<p><a href="https://hive.io" class="hive-class">Hive Link</a></p>\n`);
.eql(`<p><a href="https://hive.io" class="hive-class external">Hive Link</a></p>\n`);
});
......@@ -7,7 +7,7 @@ const renderer = new HiveContentRenderer.DefaultRenderer({
allowInsecureScriptTags: false,
addNofollowToLinks: true,
addTargetBlankToLink: true,
addCssClassToLinks: "hive-class",
cssClassForInternalLinks: "hive-class",
doNotShowImages: false,
ipfsPrefix: "",
assetsWidth: 640,
......@@ -16,6 +16,7 @@ const renderer = new HiveContentRenderer.DefaultRenderer({
usertagUrlFn: (account) => "/@" + account,
hashtagUrlFn: (hashtag) => "/trending/" + hashtag,
isLinkSafeFn: (url) => true,
addExternalCssClassToMatchingLinksFn: (url) => true,
});
const input = `
......
......@@ -7,7 +7,8 @@ const renderer = new HiveContentRenderer.DefaultRenderer({
allowInsecureScriptTags: false,
addNofollowToLinks: true,
addTargetBlankToLink: true,
addCssClassToLinks: "hive-class",
cssClassForInternalLinks: "hive-class",
cssClassForExternalLinks: "external",
doNotShowImages: false,
ipfsPrefix: "",
assetsWidth: 640,
......@@ -16,6 +17,7 @@ const renderer = new HiveContentRenderer.DefaultRenderer({
usertagUrlFn: (account) => "/@" + account,
hashtagUrlFn: (hashtag) => "/trending/" + hashtag,
isLinkSafeFn: (url) => true,
addExternalCssClassToMatchingLinksFn: (url) => true,
});
const input = `
......
......@@ -129,7 +129,8 @@ https://youtu.be/B7C83L6iWJQ
allowInsecureScriptTags: false,
addNofollowToLinks: true,
addTargetBlankToLinks: true,
addCssClassToLinks: "hive-test",
cssClassForInternalLinks: "hive-test",
cssClassForExternalLinks: "external",
doNotShowImages: false,
ipfsPrefix: "",
assetsWidth: 640,
......@@ -138,6 +139,7 @@ https://youtu.be/B7C83L6iWJQ
usertagUrlFn: (account) => "https://hive.blog/@" + account,
hashtagUrlFn: (hashtag) => "https://hive.blog/trending/" + hashtag,
isLinkSafeFn: (url) => true,
addExternalCssClassToMatchingLinksFn: (url) => true,
});
$(document).ready(() => {
......
......@@ -15,7 +15,8 @@ describe("DefaultRender", () => {
skipSanitization: false,
allowInsecureScriptTags: false,
addTargetBlankToLinks: true,
addCssClassToLinks: "hive-test",
cssClassForInternalLinks: "hive-test",
cssClassForExternalLinks: "hive-test external",
addNofollowToLinks: true,
doNotShowImages: false,
ipfsPrefix: "",
......@@ -25,6 +26,7 @@ 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)/),
};
const tests = [
......@@ -38,8 +40,7 @@ describe("DefaultRender", () => {
{
name: "Renders hive mentions correctly",
raw: "Content @noisy another content",
expected:
'<p>Content <a href="https://hive.blog/@noisy" class="hive-test">@noisy</a> another content</p>',
expected: '<p>Content <a href="https://hive.blog/@noisy" class="hive-test">@noisy</a> another content</p>',
},
{
name: "Renders hive hashtags correctly",
......@@ -85,13 +86,18 @@ describe("DefaultRender", () => {
{
name: "Allow for anchor id tags",
raw: "<a id='anchor'></a>",
expected: '<p><a href="#" id="anchor" class="hive-test"></a></p>',
expected: '<p><a href="#" id="anchor" class="hive-test external"></a></p>',
},
{
name: "Allows links embedded via <a> tags with additional class added when condition is matching",
raw: '<a href="https://www.google.com" class="hive-test">Google</a>',
expected: '<p><a href="https://www.google.com" class="hive-test external">Google</a></p>',
},
{
{
name: "Should remove additional unsafe attributes from a tag",
raw: "<a fake='test'></a>",
expected: '<p><a href="#" class="hive-test"></a></p>',
}
expected: '<p><a href="#" class="hive-test external"></a></p>',
},
];
tests.forEach((test) =>
......
......@@ -29,9 +29,11 @@ export class DefaultRenderer {
iframeHeight: this.options.assetsHeight,
addNofollowToLinks: this.options.addNofollowToLinks,
addTargetBlankToLinks: this.options.addTargetBlankToLinks,
addCssClassToLinks: this.options.addCssClassToLinks,
cssClassForInternalLinks: this.options.cssClassForInternalLinks,
cssClassForExternalLinks: this.options.cssClassForExternalLinks,
noImage: this.options.doNotShowImages,
isLinkSafeFn: this.options.isLinkSafeFn,
addExternalCssClassToMatchingLinksFn: this.options.addExternalCssClassToMatchingLinksFn,
},
localization,
);
......@@ -120,7 +122,8 @@ export namespace DefaultRenderer {
allowInsecureScriptTags: boolean;
addNofollowToLinks: boolean;
addTargetBlankToLinks?: boolean;
addCssClassToLinks?: string;
cssClassForInternalLinks?: string;
cssClassForExternalLinks?: string;
doNotShowImages: boolean;
ipfsPrefix: string;
assetsWidth: number;
......@@ -129,6 +132,7 @@ export namespace DefaultRenderer {
hashtagUrlFn: (hashtag: string) => string;
usertagUrlFn: (account: string) => string;
isLinkSafeFn: (url: string) => boolean;
addExternalCssClassToMatchingLinksFn: (url: string) => boolean;
}
export namespace Options {
......@@ -138,7 +142,8 @@ export namespace DefaultRenderer {
ow(o.skipSanitization, "Options.skipSanitization", ow.boolean);
ow(o.addNofollowToLinks, "Options.addNofollowToLinks", ow.boolean);
ow(o.addTargetBlankToLinks, "Options.addTargetBlankToLinks", ow.optional.boolean);
ow(o.addCssClassToLinks, "Options.addCssClassToLinks", ow.optional.string);
ow(o.cssClassForInternalLinks, "Options.cssClassForInternalLinks", ow.optional.string);
ow(o.cssClassForExternalLinks, "Options.cssClassForExternalLinks", ow.optional.string);
ow(o.doNotShowImages, "Options.doNotShowImages", ow.boolean);
ow(o.ipfsPrefix, "Options.ipfsPrefix", ow.string);
ow(o.assetsWidth, "Options.assetsWidth", ow.number.integer.positive);
......@@ -147,6 +152,11 @@ export namespace DefaultRenderer {
ow(o.hashtagUrlFn, "Options.hashtagUrlFn", ow.function);
ow(o.usertagUrlFn, "Options.usertagUrlFn", ow.function);
ow(o.isLinkSafeFn, "TagTransformingSanitizer.Options.isLinkSafeFn", ow.function);
ow(
o.addExternalCssClassToMatchingLinksFn,
"TagTransformingSanitizer.Options.addExternalCssClassToMatchingLinksFn",
ow.function,
);
}
}
}
......@@ -169,8 +169,14 @@ export class TagTransformingSanitizer {
attys.title = this.localization.phishingWarning;
attys.target = this.options.addTargetBlankToLinks ? "_blank" : "_self";
}
if (this.options.addCssClassToLinks) {
attys.class = this.options.addCssClassToLinks ? this.options.addCssClassToLinks : "";
if (this.options.addExternalCssClassToMatchingLinksFn(href)) {
attys.class = this.options.cssClassForExternalLinks
? this.options.cssClassForExternalLinks
: "";
} else {
attys.class = this.options.cssClassForInternalLinks
? this.options.cssClassForInternalLinks
: "";
}
const retTag: sanitize.Tag = {
tagName,
......@@ -189,9 +195,11 @@ export namespace TagTransformingSanitizer {
iframeHeight: number;
addNofollowToLinks: boolean;
addTargetBlankToLinks?: boolean;
addCssClassToLinks?: string;
cssClassForInternalLinks?: string;
cssClassForExternalLinks?: string;
noImage: boolean;
isLinkSafeFn: (url: string) => boolean;
addExternalCssClassToMatchingLinksFn: (url: string) => boolean;
}
export namespace Options {
......@@ -200,9 +208,23 @@ export namespace TagTransformingSanitizer {
ow(o.iframeHeight, "TagTransformingSanitizer.Options.iframeHeight", ow.number.integer.positive);
ow(o.addNofollowToLinks, "TagTransformingSanitizer.Options.addNofollowToLinks", ow.boolean);
ow(o.addTargetBlankToLinks, "TagTransformingSanitizer.Options.addTargetBlankToLinks", ow.optional.boolean);
ow(o.addCssClassToLinks, "TagTransformingSanitizer.Options.addCssClassToLinks", ow.optional.string);
ow(
o.cssClassForInternalLinks,
"TagTransformingSanitizer.Options.cssClassForInternalLinks",
ow.optional.string,
);
ow(
o.cssClassForExternalLinks,
"TagTransformingSanitizer.Options.cssClassForExternalLinks",
ow.optional.string,
);
ow(o.noImage, "TagTransformingSanitizer.Options.noImage", ow.boolean);
ow(o.isLinkSafeFn, "TagTransformingSanitizer.Options.isLinkSafeFn", ow.function);
ow(
o.addExternalCssClassToMatchingLinksFn,
"TagTransformingSanitizer.Options.addExternalCssClassToMatchingLinksFn",
ow.function,
);
}
}
}
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