Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 20 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,15 +14,34 @@ $ npm i @parcility/kennel

Kennel was written to be as easy to interact with as possible.

`render(depiction: any, options?: Parital<RenderOptions>): Promise<HTMLElement | string>`
`render(depiction: any, options?: Partial<RenderOptions>): Promise<HTMLElement | string>`

> Render a depiction to either a HTMLElement or a string.
>
> `depiction`: An object that stores the native depiction's contents.
>
> `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`

Expand Down
31 changes: 28 additions & 3 deletions index.html
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
@media screen and (prefers-color-scheme: dark) {
body {
color: #fff;
background-color: #000;
background-color: #111;
}
}
</style>
Expand All @@ -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: ["DepictionHeaderView", "DepictionImageView", "DepictionSeparatorView", "DepictionVideoView", "DepictionWebView"] })
]);
const details = document.createElement("div");
const content = document.createElement("div");
const summary = document.createElement("h3");
Expand All @@ -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
Expand Down
82 changes: 69 additions & 13 deletions lib/index.scss
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@

& > * {
flex: 1;
flex-shrink: 1;
}
}
}
Expand Down Expand Up @@ -97,7 +98,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;
Expand All @@ -108,7 +109,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;

Expand Down Expand Up @@ -139,32 +140,33 @@

.nd-button {
box-sizing: border-box;
margin: 0.5rem 0;
display: block;
text-decoration: none;
margin: 0.5rem;

& > p {
margin: 0;
& > a {
display: block;
text-decoration: none;
}

&.nd-button-link {
& > .nd-button-link {
color: var(--kennel-tint-color);
margin: 0;
}

&.nd-button-not-link {
& > .nd-button-not-link {
box-sizing: border-box;
text-align: center;
display: block;
border: none;
-webkit-appearance: none;
-moz-appearance: none;
padding: 0.5rem 1rem;
margin: 0.5rem 0.5rem;
font: inherit;
border-radius: 0.5rem;
appearance: none;
width: 100%;
& > span {
margin: 0 0.5rem;
}
}
}

Expand Down Expand Up @@ -242,10 +244,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;
}
Expand Down Expand Up @@ -293,7 +298,7 @@
& > * {
margin-right: 1rem;
scroll-snap-align: center;
flex-shrink: 0;
flex-shrink: 1;
border-radius: var(--screenshot-item-radius);
width: var(--screenshot-item-width);
height: var(--screenshot-item-height);
Expand All @@ -304,4 +309,55 @@
}
}
}
.nd-featured-banners {
overflow: auto hidden;
white-space: nowrap;
margin: 0.5rem;

& > .nd-banner-item:first-child {
margin-left: 0;
}

& > .nd-banner-item {
color: #fff;
display: inline-block;
margin: 0 0.25rem;
position: relative;
overflow: hidden;
height: var(--banner-item-height);
width: var(--banner-item-width);
border-radius: var(--banner-item-radius);

& > img {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%,-50%) scale(1.1);
height: 100%;
width: 100%;
object-fit: cover;
}

& > span {
display: block;
position: absolute;
bottom: 0;
right: 0;
left: 0;
top: 0;
width: 100%;
height: 100%;
line-height: 900%;
padding: 0.75rem;
font-size: 1.5rem;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;

&.nd-banner-shadow {
text-shadow: 0 0 1.5rem #000, 0 0 1.5rem #000, 0 0 1.5rem #000;
}
}
}
}
}
27 changes: 15 additions & 12 deletions lib/index.ts
Original file line number Diff line number Diff line change
@@ -1,41 +1,44 @@
/// <reference types="vite/client" />
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<T extends Partial<RenderOptions>, U extends T["ssr"] extends true ? string : HTMLElement>(
depiction: any,
options?: T
): Promise<U> {
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);

Expand Down
1 change: 1 addition & 0 deletions lib/markdown.css
Original file line number Diff line number Diff line change
Expand Up @@ -73,4 +73,5 @@ code {

a {
color: var(--kennel-tint-color);
text-decoration: none;
}
13 changes: 13 additions & 0 deletions lib/renderable.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,19 @@
import createDOMPurify, { DOMPurifyI } from "dompurify";
import { escapeHTML } from "./util";

export interface RenderOptions {
ssr: boolean;
defaultTintColor: string;
backgroundColor: string;
ignoredViewNames: any[];
linkForm: string;
linkHeaderless: string;
proxyUrl: string;
proxyIframeUrl: string;
proxyImageUrl: string;
proxyVideoUrl: string;
}

const PURIFY_OPTIONS: createDOMPurify.Config = {
RETURN_DOM_FRAGMENT: false,
RETURN_DOM: false,
Expand Down
29 changes: 16 additions & 13 deletions lib/util/index.ts
Original file line number Diff line number Diff line change
@@ -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";
Expand Down Expand Up @@ -41,22 +41,19 @@ export function fontWeightParse(fontWeight: string): string {
}
}

export function buttonLinkHandler(el: RenderableElement, url: string, label?: string) {
let link = url;
export function buttonLinkHandler(url: string, label?: string, options?: Partial<RenderOptions>): [string, string] {
// javascript: links should do nothing.
const jsXssIndex = url.indexOf("javascript:");
if (jsXssIndex !== -1) {
link = url.substring(0, jsXssIndex) + encodeURIComponent(url.substring(jsXssIndex));
url = url.substring(0, jsXssIndex) + encodeURIComponent(url.substring(jsXssIndex));
// depiction- links should link to a depiction. Use Parcility's API for this.
} 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}`;
url = (options?.linkHeaderless ?? 'https://api.parcility.co/render/headerless?url=') + `${encodeURIComponent(url.substring(10))}&name=${label}`;
} else if (url.indexOf("form-") == 0) {
url = url.substring(5);
link = `https://api.parcility.co/render/form?url=${encodeURIComponent(url)}`;
url = (options?.linkForm ?? 'https://api.parcility.co/render/form?url=') + `${encodeURIComponent(url.substring(5))}&name=${label}`;
}
el.attributes.href = link;
return [url, (label ?? "")];
}

// Alignment
Expand Down Expand Up @@ -107,18 +104,24 @@ export function applyAlignmentMargin(el: RenderableElement, alignment: Alignment

// Processing

export function constructView(view: any): DepictionBaseView | undefined {
export function constructView(
view: any,
options?: Partial<RenderOptions>
): 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<RenderOptions>
): DepictionBaseView[] {
return views.map((view) => constructView(view, options)).filter(Boolean) as DepictionBaseView[];
}

export async function makeView(view: DepictionBaseView): Promise<RenderableElement> {
Expand Down
Loading