From 886e3a1480b8f4b0381a8e3bd33fcf60785b0a35 Mon Sep 17 00:00:00 2001 From: Relisiuol <49282232+relisiuol@users.noreply.github.com> Date: Wed, 5 Oct 2022 10:41:34 +0200 Subject: [PATCH 1/8] Fixes for appearance and some options added. - Fixed the appearance in the hope to perfectly match Sileo appearance. - Fixed `defaultTintColor` to inherit its value to subviews. - Added support for option `backgroundColor`. - Added `ignoredViewNames` option to ignore views based on their names. - Added `linkForm` option to choose which page/link should be used to render `form-` links. - Added `linkHeaderless` option to choose which page/link should be used to render `depiction-` links. - Added `proxyIframeUrl` option to use a proxy for iframe elements. - Added `proxyImageUrl` option to use a proxy for image elements. - Added `proxyVideoUrl` option to use a proxy for video elements. --- README.md | 10 +++++++++- index.html | 31 ++++++++++++++++++++++++++++--- lib/index.scss | 15 +++++++++++---- lib/index.ts | 27 +++++++++++++++------------ lib/markdown.css | 1 + lib/renderable.ts | 11 +++++++++++ lib/util/index.ts | 24 +++++++++++++++--------- lib/util/urls.ts | 36 ++++++++++++++++++------------------ lib/views/auto_stack.ts | 11 +++++++---- lib/views/base.ts | 11 +++++++---- lib/views/button.ts | 16 +++++++++++----- lib/views/header.ts | 9 ++++++--- lib/views/image.ts | 13 ++++++++++--- lib/views/index.ts | 6 +++++- lib/views/label.ts | 9 ++++++--- lib/views/layer.ts | 11 +++++++---- lib/views/markdown.ts | 11 +++++++---- lib/views/min_version.ts | 10 +++++++--- lib/views/rating.ts | 9 ++++++--- lib/views/review.ts | 11 +++++++---- lib/views/screenshots.ts | 37 +++++++++++++++++++++++++++---------- lib/views/spacer.ts | 9 ++++++--- lib/views/stack.ts | 11 +++++++---- lib/views/tab.ts | 21 +++++++++++++-------- lib/views/table_button.ts | 12 ++++++++---- lib/views/table_text.ts | 9 ++++++--- lib/views/video.ts | 14 +++++++++++--- lib/views/web.ts | 13 ++++++++++--- 28 files changed, 282 insertions(+), 126 deletions(-) diff --git a/README.md b/README.md index b682c88..94b0497 100644 --- a/README.md +++ b/README.md @@ -14,7 +14,7 @@ $ npm i @parcility/kennel Kennel was written to be as easy to interact with as possible. -`render(depiction: any, options?: Parital): Promise` +`render(depiction: any, options?: Partial): Promise` > Render a depiction to either a HTMLElement or a string. > @@ -23,6 +23,14 @@ Kennel was written to be as easy to interact with as possible. > `options`: The settings used for rendering. > `options.ssr`: Output a string instead of a DOM element. > `options.defaultTintColor`: The css color used for the tint. +> `options.backgroundColor`: The css color used for the background. +> `options.ignoredViewNames`: An array of view class names to ignore/not render. +> `options.linkForm`: Link to a webpage to render `form-` links. +> `options.linkHeaderless`: Link to a webpage to render `depiction-` links. +> `options.proxyIframeUrl`: The specific proxy url to use for iframe only. +> `options.proxyImageUrl`: The specific proxy url to use for image only. +> `options.proxyVideoUrl`: The specific proxy url to use for video only. +> `options.proxyUrl`: The default proxy url to use for iframe, image or video. `hydrate(target?: ParentNode): void` diff --git a/index.html b/index.html index 8756798..5d2ecd5 100644 --- a/index.html +++ b/index.html @@ -14,7 +14,7 @@ @media screen and (prefers-color-scheme: dark) { body { color: #fff; - background-color: #000; + background-color: #111; } } @@ -25,7 +25,12 @@ async function renderDepiction([name, depictionImport]) { const { default: depiction } = await depictionImport(); - let [dom, ssr] = await Promise.all([render(depiction), render(depiction, { ssr: true })]); + let [dom, ssr, proxy, ignoredViewNames] = await Promise.all([ + render(depiction), + render(depiction, { ssr: true }), + render(depiction, { proxyImageUrl: "https://api.ios-repo-updates.com/image.php?url=" }), + render(depiction, { ignoredViewNames: ["DepictionImageView", "DepictionVideoView"] }) + ]); const details = document.createElement("div"); const content = document.createElement("div"); const summary = document.createElement("h3"); @@ -52,7 +57,27 @@ ssrContent.innerHTML = ssr; ssrDetails.append(ssrSummary, ssrContent); - content.append(domDetails, ssrDetails); + // add proxy el + const proxyDetails = document.createElement("details"); + const proxySummary = document.createElement("summary"); + proxySummary.innerHTML = "Proxy"; + const proxyContent = document.createElement("div"); + proxyContent.style.maxWidth = "32rem"; + proxyContent.style.margin = "auto"; + proxyContent.appendChild(proxy); + proxyDetails.append(proxySummary, proxyContent); + + // add ignoredViewNames el + const ignoredViewNamesDetails = document.createElement("details"); + const ignoredViewNamesSummary = document.createElement("summary"); + ignoredViewNamesSummary.innerHTML = "Ignored view names"; + const ignoredViewNamesContent = document.createElement("div"); + ignoredViewNamesContent.style.maxWidth = "32rem"; + ignoredViewNamesContent.style.margin = "auto"; + ignoredViewNamesContent.appendChild(ignoredViewNames); + ignoredViewNamesDetails.append(ignoredViewNamesSummary, ignoredViewNamesContent); + + content.append(domDetails, ssrDetails, proxyDetails, ignoredViewNamesDetails); details.appendChild(content); // add to body diff --git a/lib/index.scss b/lib/index.scss index 7792c12..6b02fce 100644 --- a/lib/index.scss +++ b/lib/index.scss @@ -29,6 +29,7 @@ .nd-stack { display: flex; flex-direction: column; + margin: 0.5rem; &.nd-stack-landscape { gap: 1rem; justify-content: space-between; @@ -38,6 +39,9 @@ & > * { flex: 1; } + .nd-button.nd-button-not-link { + margin: 0; + } } } @@ -97,7 +101,7 @@ span { background: linear-gradient( to right, - #ff8a00 var(--kennel-rating-progress), + rgba(121, 121, 121, 1) var(--kennel-rating-progress), rgba(121, 121, 121, 0.15) var(--kennel-rating-progress) ); background-clip: text; @@ -108,7 +112,7 @@ .nd-review { padding: 1rem 1rem; - background-color: rgba(121, 121, 121, 0.05); + background-color: rgba(121, 121, 121, 0.1); margin-bottom: 1rem; border-radius: 0.5rem; @@ -242,10 +246,13 @@ cursor: pointer; grid-area: tab; text-align: center; - padding: 0.5rem 1rem; + padding: 0 1rem; + } + & > input + label > span { + padding: 0.5rem 0; } - & > input:checked + label { + & > input:checked + label > span { color: var(--kennel-tint-color); border-bottom: solid 1px; } diff --git a/lib/index.ts b/lib/index.ts index aeea122..9759150 100644 --- a/lib/index.ts +++ b/lib/index.ts @@ -1,14 +1,9 @@ /// import "./index.scss"; -import { createElement, renderElement, setStyles } from "./renderable"; -import { constructView, constructViews, defaultIfNotType, KennelError, makeViews } from "./util"; +import { RenderOptions, createElement, renderElement, setStyles } from "./renderable"; +import { constructView, constructViews, defaultIfNotType, undefIfNotType, KennelError, makeViews } from "./util"; import { DepictionBaseView, mountable } from "./views"; -interface RenderOptions { - ssr: boolean; - defaultTintColor: string; -} - export async function render, U extends T["ssr"] extends true ? string : HTMLElement>( depiction: any, options?: T @@ -16,26 +11,34 @@ export async function render, U extends T["ssr" let tintColor = defaultIfNotType(depiction["tintColor"], "color", options?.defaultTintColor as string) as | string | undefined; + let backgroundColor = undefIfNotType(depiction["backgroundColor"], "color") as + | string + | undefined; // process the depiction let processed: DepictionBaseView[] | undefined; if (Array.isArray(depiction.tabs)) { depiction.className = "DepictionTabView"; - let view = constructView(depiction); + let view = constructView(depiction, options); if (view) { processed = [view]; } } else if (Array.isArray(depiction.views)) { - processed = constructViews(depiction.views); + processed = constructViews(depiction.views, options); } if (!processed) throw new KennelError("Unable to process depiction. No child was found."); // build an element to render let el = createElement("form", { class: "nd-root" }); + let styleOptions: any = {}; if (tintColor) { - setStyles(el, { - "--kennel-tint-color": tintColor, - }); + styleOptions["--kennel-tint-color"] = tintColor; + } + if (backgroundColor) { + styleOptions["background-color"] = backgroundColor; + } + if (Object.keys(styleOptions).length > 0) { + setStyles(el, styleOptions); } el.children = await makeViews(processed); diff --git a/lib/markdown.css b/lib/markdown.css index e3ab26b..b6b7f5a 100644 --- a/lib/markdown.css +++ b/lib/markdown.css @@ -73,4 +73,5 @@ code { a { color: var(--kennel-tint-color); + text-decoration: none; } diff --git a/lib/renderable.ts b/lib/renderable.ts index 66b060c..dd417f6 100644 --- a/lib/renderable.ts +++ b/lib/renderable.ts @@ -1,6 +1,17 @@ import createDOMPurify, { DOMPurifyI } from "dompurify"; import { escapeHTML } from "./util"; +export interface RenderOptions { + ssr: boolean; + defaultTintColor: string; + backgroundColor: string; + ignoredViewNames: array; + proxyUrl: string; + proxyIframeUrl: string; + proxyImageUrl: string; + proxyVideoUrl: string; +} + const PURIFY_OPTIONS: createDOMPurify.Config = { RETURN_DOM_FRAGMENT: false, RETURN_DOM: false, diff --git a/lib/util/index.ts b/lib/util/index.ts index 6825cbc..8663c92 100644 --- a/lib/util/index.ts +++ b/lib/util/index.ts @@ -1,4 +1,4 @@ -import { RenderableElement, setStyles } from "../renderable"; +import { RenderOptions, RenderableElement, setStyles } from "../renderable"; import { DepictionBaseView, views } from "../views"; import { isValidColor } from "./colors"; import { isValidHttpUrl, isValidHttpUrlExtended } from "./urls"; @@ -41,7 +41,7 @@ export function fontWeightParse(fontWeight: string): string { } } -export function buttonLinkHandler(el: RenderableElement, url: string, label?: string) { +export function buttonLinkHandler(url: string, label?: string, options?: Partial) { let link = url; // javascript: links should do nothing. const jsXssIndex = url.indexOf("javascript:"); @@ -51,12 +51,12 @@ export function buttonLinkHandler(el: RenderableElement, url: string, label?: st } else if (url.indexOf("depiction-") == 0) { url = url.substring(10); if (!label) label = "Depiction"; - link = `https://api.parcility.co/render/headerless?url=${encodeURIComponent(url)}&name=${label}`; + link = ((options && options.linkHeaderless) ?? 'https://api.parcility.co/render/headerless?url=') + `${encodeURIComponent(url)}&name=${label}`; } else if (url.indexOf("form-") == 0) { url = url.substring(5); - link = `https://api.parcility.co/render/form?url=${encodeURIComponent(url)}`; + link = ((options && options.linkForm) ?? 'https://api.parcility.co/render/form?url=') + `${encodeURIComponent(url)}&name=${label}`; } - el.attributes.href = link; + return [link, label]; } // Alignment @@ -107,18 +107,24 @@ export function applyAlignmentMargin(el: RenderableElement, alignment: Alignment // Processing -export function constructView(view: any): DepictionBaseView | undefined { +export function constructView( + view: any, + options?: Partial +): DepictionBaseView | undefined { let v = views.get(view.class); try { - if (v) return new v(view); + if (v && (!options || !options.ignoredViewNames || !options.ignoredViewNames.includes(view.class))) return new v(view, options); } catch (error) { console.error(error); } return undefined; } -export function constructViews(views: any[]): DepictionBaseView[] { - return views.map(constructView).filter(Boolean) as DepictionBaseView[]; +export function constructViews ( + views: any[], + options?: Partial +): DepictionBaseView[] { + return views.map((view) => constructView(view, options)).filter(Boolean) as DepictionBaseView[]; } export async function makeView(view: DepictionBaseView): Promise { diff --git a/lib/util/urls.ts b/lib/util/urls.ts index e821db5..c26c2ef 100644 --- a/lib/util/urls.ts +++ b/lib/util/urls.ts @@ -1,23 +1,23 @@ export function isValidHttpUrl(string: string): boolean { - let url; - try { - url = new URL(string); - } catch (_) { - return false; - } - return url.protocol === "http:" || url.protocol === "https:"; + let url; + try { + url = new URL(string); + } catch (_) { + return false; + } + return url.protocol === "http:" || url.protocol === "https:"; } export function isValidHttpUrlExtended(string: string): boolean { - if(isValidHttpUrl(string)) { - return true; - } - let url; - try { - url = new URL(string); - } catch (_) { - return false; - } - return url.protocol == "depiction-http:" || url.protocol == "depiction-https:" || - url.protocol == "form-http:" || url.protocol == "form-https:"; + if(isValidHttpUrl(string)) { + return true; + } + let url; + try { + url = new URL(string); + } catch (_) { + return false; + } + return url.protocol == "depiction-http:" || url.protocol == "depiction-https:" || + url.protocol == "form-http:" || url.protocol == "form-https:"; } \ No newline at end of file diff --git a/lib/views/auto_stack.ts b/lib/views/auto_stack.ts index ecbc8c7..fcddac4 100644 --- a/lib/views/auto_stack.ts +++ b/lib/views/auto_stack.ts @@ -1,4 +1,4 @@ -import { createElement, RenderableElement, setStyles } from "../renderable"; +import { RenderOptions, createElement, RenderableElement, setStyles } from "../renderable"; import { constructView, guardIfNotType, makeViews, undefIfNotType } from "../util"; import DepictionBaseView from "./base"; @@ -9,15 +9,18 @@ export default class DepictionAutoStackView extends DepictionBaseView { backgroundColor?: string; static viewName = "DepictionAutoStackView"; - constructor(depiction: any) { - super(depiction); + constructor( + depiction: any, + options?: Partial + ) { + super(depiction, options); let views = guardIfNotType(depiction["views"], "array"); this.horizontalSpacing = guardIfNotType(depiction["horizontalSpacing"], "number"); for (let view of views) { guardIfNotType(view["class"], "string"); guardIfNotType(view["preferredWidth"], "number"); - let v = constructView(view); + let v = constructView(view, options); if (!v) throw new Error("Invalid view"); this.views.push(v); } diff --git a/lib/views/base.ts b/lib/views/base.ts index 30a24e7..0aa0256 100644 --- a/lib/views/base.ts +++ b/lib/views/base.ts @@ -1,13 +1,16 @@ -import { RenderableElement } from "../renderable"; -import { undefIfNotType } from "../util"; +import { RenderOptions, RenderableElement } from "../renderable"; +import { defaultIfNotType, undefIfNotType } from "../util"; export default abstract class DepictionBaseView { tintColor?: string; static viewName = "DepictionBaseView"; - constructor(depiction: any) { + constructor( + depiction: any, + options?: Partial + ) { if (depiction) { - this.tintColor = undefIfNotType(depiction["tintColor"], "color"); + this.tintColor = undefIfNotType(defaultIfNotType(depiction["tintColor"], "color", options?.defaultTintColor as string), "color"); } } diff --git a/lib/views/button.ts b/lib/views/button.ts index 0f6adef..af3b53e 100644 --- a/lib/views/button.ts +++ b/lib/views/button.ts @@ -1,4 +1,4 @@ -import { createElement, RenderableElement, setClassList, setStyles } from "../renderable"; +import { RenderOptions, createElement, RenderableElement, setClassList, setStyles } from "../renderable"; import { buttonLinkHandler, constructView, defaultIfNotType, guardIfNotType, makeView, undefIfNotType } from "../util"; import DepictionBaseView from "./base"; @@ -11,8 +11,11 @@ export default class DepictionButtonView extends DepictionBaseView { openExternal: boolean; static viewName = "DepictionButtonView"; - constructor(dictionary: any) { - super(dictionary); + constructor( + dictionary: any, + options?: Partial + ) { + super(dictionary, options); this.isLink = defaultIfNotType(dictionary["isLink"], "boolean", false); this.yPadding = defaultIfNotType(dictionary["yPadding"], "number", 0); let action = undefIfNotType(dictionary["action"], "urlExtended"); @@ -21,11 +24,14 @@ export default class DepictionButtonView extends DepictionBaseView { } else { this.action = action; } + + [this.action, this.text] = buttonLinkHandler(this.action, this.text, options); + this.openExternal = defaultIfNotType(dictionary["openExternal"], "boolean", false); let dict = dictionary["view"]; if (typeof dict === "object") { - this.children = constructView(dict); + this.children = constructView(dict, options); } if (!this.children) { @@ -41,7 +47,6 @@ export default class DepictionButtonView extends DepictionBaseView { setClassList(el, ["nd-button", this.isLink ? "nd-button-link" : "nd-button-not-link"]); let styles: any = {}; if (this.tintColor) styles["--kennel-tint-color"] = this.tintColor; - buttonLinkHandler(el, this.action, this.text); if (this.isLink) { styles.color = "var(--kennel-tint-color)"; } else { @@ -49,6 +54,7 @@ export default class DepictionButtonView extends DepictionBaseView { styles["color"] = "white"; } + el.attributes.href = this.action; if (this.openExternal) { el.attributes.target = "_blank"; } diff --git a/lib/views/header.ts b/lib/views/header.ts index 0e618bc..568e06f 100644 --- a/lib/views/header.ts +++ b/lib/views/header.ts @@ -1,4 +1,4 @@ -import { createElement, setClassList, setStyles } from "../renderable"; +import { RenderOptions, createElement, setClassList, setStyles } from "../renderable"; import { defaultIfNotType, textAlignment } from "../util"; import DepictionBaseView from "./base"; @@ -11,8 +11,11 @@ export default class DepictionHeaderView extends DepictionBaseView { alignment: string; static viewName = "DepictionHeaderView"; - constructor(dictionary: any) { - super(dictionary); + constructor( + dictionary: any, + options?: Partial + ) { + super(dictionary, options); if (typeof dictionary["title"] === "string") { this.title = dictionary.title; } diff --git a/lib/views/image.ts b/lib/views/image.ts index 18740b1..2bf7f97 100644 --- a/lib/views/image.ts +++ b/lib/views/image.ts @@ -1,4 +1,4 @@ -import { createElement, RenderableElement, setStyles } from "../renderable"; +import { RenderOptions, createElement, RenderableElement, setStyles } from "../renderable"; import { Alignment, applyAlignmentMargin, defaultIfNotType, getAlignment, guardIfNotType, KennelError } from "../util"; import DepictionBaseView from "./base"; @@ -11,12 +11,19 @@ export default class DepictionImageView extends DepictionBaseView { borderRadius: number; static viewName = "DepictionImageView"; - constructor(dictionary: any) { - super(dictionary); + constructor( + dictionary: any, + options?: Partial + ) { + super(dictionary, options); this.url = guardIfNotType(dictionary["URL"], "url"); this.width = defaultIfNotType(dictionary["width"], "number", 0); this.height = defaultIfNotType(dictionary["height"], "number", 0); + if(options?.proxyImageUrl || options?.proxyUrl) { + this.url = (options?.proxyImageUrl ?? options?.proxyUrl) + encodeURIComponent(this.url); + } + if (this.width === 0 || this.height === 0) throw new KennelError("Invalid image size"); this.borderRadius = guardIfNotType(dictionary["cornerRadius"], "number"); diff --git a/lib/views/index.ts b/lib/views/index.ts index 51858f5..f3d066f 100644 --- a/lib/views/index.ts +++ b/lib/views/index.ts @@ -1,5 +1,6 @@ export type { default as DepictionBaseView } from "./base"; +import { RenderOptions } from "../renderable"; import AutoStackView from "./auto_stack"; import ButtonView from "./button"; import HeaderView from "./header"; @@ -24,7 +25,10 @@ import WebView from "./web"; import DepictionBaseView from "./base"; export type DepictionViewConstructor = { - new (dictionary: any): T; + new ( + dictionary: any, + options?: Partial + ): T; viewName: string; hydrate?(el: HTMLElement): void; }; diff --git a/lib/views/label.ts b/lib/views/label.ts index e716791..1fc2171 100644 --- a/lib/views/label.ts +++ b/lib/views/label.ts @@ -1,4 +1,4 @@ -import { createElement, RenderableElement, setStyles } from "../renderable"; +import { RenderOptions, createElement, RenderableElement, setStyles } from "../renderable"; import { defaultIfNotType, fontWeightParse, parseSize, textAlignment, undefIfNotType } from "../util"; import DepictionBaseView from "./base"; @@ -13,8 +13,11 @@ export default class DepictionLabelView extends DepictionBaseView { fontSize: number; static viewName = "DepictionLabelView"; - constructor(dictionary: any) { - super(dictionary); + constructor( + dictionary: any, + options?: Partial + ) { + super(dictionary, options); if (typeof dictionary["text"] === "string") { this.text = dictionary.text; } diff --git a/lib/views/layer.ts b/lib/views/layer.ts index c54bd60..1711ea9 100644 --- a/lib/views/layer.ts +++ b/lib/views/layer.ts @@ -1,4 +1,4 @@ -import { createElement } from "../renderable"; +import { RenderOptions, createElement } from "../renderable"; import { constructViews, guardIfNotType, makeViews } from "../util"; import DepictionBaseView from "./base"; @@ -6,10 +6,13 @@ export default class DepictionLayerView extends DepictionBaseView { views: DepictionBaseView[]; static viewName = "DepictionLayerView"; - constructor(dictionary: any) { - super(dictionary); + constructor( + dictionary: any, + options?: Partial + ) { + super(dictionary, options); let rawViews = guardIfNotType(dictionary["views"], "array"); - this.views = constructViews(rawViews); + this.views = constructViews(rawViews, options); } async make() { diff --git a/lib/views/markdown.ts b/lib/views/markdown.ts index c50d624..5ff0ccf 100644 --- a/lib/views/markdown.ts +++ b/lib/views/markdown.ts @@ -1,6 +1,6 @@ import { marked } from "marked"; import markdownStyles from "../markdown.css?raw"; -import { createElement, createRawNode, createShadowedElement, renderElementString, setStyles } from "../renderable"; +import { RenderOptions, createElement, createRawNode, createShadowedElement, renderElementString, setStyles } from "../renderable"; import { defaultIfNotType, escapeHTML, guardIfNotType, makeView } from "../util"; import DepictionBaseView from "./base"; import DepictionSeparatorView from "./separator"; @@ -21,8 +21,11 @@ export default class DepictionMarkdownView extends DepictionBaseView { useRawFormat: boolean; static viewName = "DepictionMarkdownView"; - constructor(dictionary: any) { - super(dictionary); + constructor( + dictionary: any, + options?: Partial + ) { + super(dictionary, options); let md = guardIfNotType(dictionary["markdown"], "string"); this.useMargins = defaultIfNotType(dictionary["useMargins"], "boolean", true); this.useSpacing = defaultIfNotType(dictionary["useSpacing"], "boolean", true); @@ -33,7 +36,7 @@ export default class DepictionMarkdownView extends DepictionBaseView { let xssWarn = `

[Warning: This depiction may be trying to maliciously run code in your browser.]


`; rendered = rendered.replace( /
/gi, - await renderElementString(await makeView(new DepictionSeparatorView(undefined))) + await renderElementString(await makeView(new DepictionSeparatorView(undefined, undefined))) ); if ( rendered.toLowerCase().indexOf("