andreterron/codeOnVT_ribbonElement

References

Referenced 7 times

Code on Val Town

Screenshot 2024-02-27 at 1.25.46 PM.png

Adds a "Code on Val Town" ribbon to your page. This lets your website visitors navigate to the code behind it.

This uses github-fork-ribbon-css under the hood.

Usage

Here are 2 different ways to add the "Code on Val Town" ribbon:

1. Wrap your fetch handler (recommended)

Create valimport { modifyFetchHandler } from "https://esm.town/v/andreterron/codeOnValTown?v=45"; import { html } from "https://esm.town/v/stevekrouse/html?v=5"; export default modifyFetchHandler(async (req: Request): Promise<Response> => { return html(`<h2>Hello world!</h2>`); });

Example: @andreterron/openable_handler

2. Wrap your HTML string

Create valimport { modifyHtmlString } from "https://esm.town/v/andreterron/codeOnValTown?v=45"; import { html } from "https://esm.town/v/stevekrouse/html?v=5"; export default async (req: Request): Promise<Response> => { return html(modifyHtmlString(`<h2>Hello world!</h2>`)); };

Example: @andreterron/openable_html

Other ways

We made sure this was very modular, so you can also add the ribbon using these methods:

Customization

Linking to the val

These functions infer the val using the call stack or the request URL. If the inference isn't working, or if you want to ensure it links to a specific val, pass the val argument:

  • modifyFetchHandler(handler, {val: { handle: "andre", name: "foo" }})
  • modifyHtmlString("<html>...", {val: { handle: "andre", name: "foo" }})

Styling

You can set the style parameter to a css string to customize the ribbon. Check out github-fork-ribbon-css to learn more about how to style the element.

  • modifyFetchHandler(handler, {style: ".github-fork-ribbon:before { background-color: #333; }"})
  • modifyHtmlString("<html>...", {style: ".github-fork-ribbon:before { background-color: #333; }"})

Here's how you can hide the ribbon on small screens:

Create valmodifyFetchHandler(handler, {style: `@media (max-width: 768px) { .github-fork-ribbon { display: none !important; } }`})

To-dos

  • Let users customize the ribbon. Some ideas are the text, color or placement.
Readme
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
import { modifyResponse } from "https://esm.town/v/andreterron/codeOnVT_modifyResponse?v=9";
import { ribbonElement } from "https://esm.town/v/andreterron/codeOnVT_ribbonElement?v=7";
import { inferRequestVal } from "https://esm.town/v/andreterron/inferRequestVal?v=2";
import { rootValRef } from "https://esm.town/v/andreterron/rootValRef?v=3";
import { ValRef } from "https://esm.town/v/andreterron/ValRef?v=1";
/**
* @param bodyText HTML string that will be used to inject the element.
* @param val Define which val should open. Defaults to the root reference.
*/
export function modifyHtmlString(
bodyText: string,
{ val, style }: { val?: ValRef; style?: string } = {},
) {
val = val?.handle && val?.name ? val : rootValRef();
if (!val) {
console.error("Failed to infer val. Please set the options.val parameter to the desired `{ handle, name }`");
return bodyText;
}
const element = ribbonElement({ val, style });
const bodyIndex = bodyText.lastIndexOf("</body>");
const htmlIndex = bodyText.lastIndexOf("</html>");
// Append the element before </body> or </html>. Get the last occurrence of each
// and use whichever one comes first.
if (bodyIndex !== -1 && (htmlIndex === -1 || bodyIndex < htmlIndex)) {
return bodyText.slice(0, bodyIndex) + element + bodyText.slice(bodyIndex);
} else if (htmlIndex !== -1) {
return bodyText.slice(0, htmlIndex) + element + bodyText.slice(htmlIndex);
} else {
return bodyText + element;
}
}
/**
* @param handler Fetch handler
* @param val Define which val should open
*/
export function modifyFetchHandler(
handler: (req: Request) => Response | Promise<Response>,
{ val, style }: { val?: ValRef; style?: string } = {},
): (req: Request) => Promise<Response> {
return async (req: Request): Promise<Response> => {
const res = await handler(req);
val = val?.handle && val?.name ? val : inferRequestVal(req);
return modifyResponse(res, { val, style });
};
}
export default modifyFetchHandler;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
// SPDX-License-Identifier: 0BSD
import { extractValInfo } from "https://esm.town/v/pomdtr/extractValInfo?v=26";
import { gfm } from "https://esm.town/v/pomdtr/gfm?v=24";
import { appendFragment } from "https://esm.town/v/vladimyr/appendFragment";
import { fetchVal } from "https://esm.town/v/vladimyr/fetchVal";
import { linkifyReadme } from "https://esm.town/v/vladimyr/linkifyReadme";
export type HtmlProcessor = (html: string, author: string, name: string) => Promise<string>;
export function serveReadme(
valUrl: string | URL,
transformHtml: HtmlProcessor = decorateHtml,
) {
return async (): Promise<Response> => {
const { author, name } = extractValInfo(valUrl);
const { readme } = await fetchVal(author, name);
if (!readme) {
return new Response(null, { status: 404 });
}
let html = await readmeToHtml(readme, author, name);
html = await transformHtml(html, author, name);
return new Response(html, { headers: { "content-type": "text/html; charset=utf-8" } });
};
}
export { decorateHtml as transformHtml };
async function decorateHtml(html: string, author: string, name: string) {
html = await appendCodeOnValTownRibbon(html, author, name);
html = await appendPersonalizedFooter(html);
return html;
}
export async function appendCodeOnValTownRibbon(html: string, author: string, name: string) {
const { ribbonElement } = await import("https://esm.town/v/andreterron/codeOnVT_ribbonElement?v=7");
const fragment = ribbonElement({ val: { handle: author, name } });
return appendFragment(fragment, html);
}
export async function appendPersonalizedFooter(html: string) {
const { MyFooter: createMyFooter } = await import("https://esm.town/v/vladimyr/MyFooter");
const style = `<style>
footer .logo-white,
footer .logo-black {
background: none;
}
/* hack to remove underline under logo */
footer a + a:hover {
text-decoration: none !important;
}
</style>`;
const footer = await createMyFooter();
const fragment = style + footer;
return appendFragment(fragment, html);
}
async function readmeToHtml(readme: string, author: string, name: string) {
return linkifyReadme(
await gfm(readme, {
title: `${name} | @${author} | Val Town`,
favicon: new URL("https://www.val.town/favicon.svg"),
}),
);
}

Prevents your http vals from being viewed inside iframes. If it's a valtown iframe, a helpful message is displayed. Usage:

Create valimport modifyIframeResponse from "https://esm.town/v/nbbaier/modifyIframeResponse"; export default modifyIframeResponse(async (req: Rquest): Promise<Response> => { return Response.json({ ok: true }) });
Readme
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
function modifyResponse(
res: Response,
) {
res.headers.set("X-Frame-Options", "DENY");
return new Response(
res.body,
res,
);
}
function modifyIframeResponse(
handler: (req: Request) => Response | Promise<Response>,
): (req: Request) => Promise<Response> {
return async (req: Request): Promise<Response> => {
if (req.headers.get("referer") == "https://www.val.town/") {
return new Response(
`This val isn't available in iframes! <a href="/" target="blank_">Open in a new tab.</a>`,
{
status: 400,
headers: {
"Content-type": "text/html",
},
},
);
}
const res = await handler(req);
return modifyResponse(res);
};
}
export default modifyIframeResponse;

Injects the "Code on Val Town" ribbon on an HTML string stream

Usage

  • new InjectCodeOnValTownStream({ handle: "andre", name: "foo" }) - define which val to link to;
  • new InjectCodeOnValTownStream() - infer the val from the call stack.

Example: @andreterron/openable_stream

Create valimport { InjectCodeOnValTownStream } from "https://esm.town/v/andreterron/InjectCodeOnValTownStream"; import { blob } from "https://esm.town/v/std/blob?v=11"; import { html } from "https://esm.town/v/stevekrouse/html?v=5"; export default async (req: Request): Promise<Response> => { await blob.set( "openable_test", `<h2>Hello world!</h2> <style>* { font-family: sans-serif }</style>`, ); const value = await blob.get("openable_test"); return html( value.body .pipeThrough(new TextDecoderStream()) .pipeThrough(new InjectCodeOnValTownStream()) .pipeThrough(new TextEncoderStream()), ); };
Readme
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import { ribbonElement } from "https://esm.town/v/andreterron/codeOnVT_ribbonElement?v=7";
import { InjectHTMLElementStream } from "https://esm.town/v/andreterron/InjectHTMLElementStream?v=9";
import { rootValRef } from "https://esm.town/v/andreterron/rootValRef?v=3";
import { ValRef } from "https://esm.town/v/andreterron/ValRef?v=1";
/**
* @param val Define which val should open. Defaults to the root reference.
*/
export class InjectCodeOnValTownStream extends InjectHTMLElementStream {
constructor({ val, style }: { val?: ValRef; style?: string } = {}) {
const valRef = val?.handle && val?.name ? val : rootValRef();
const element = valRef ? ribbonElement({ val: valRef, style }) : "";
super(element);
}
}

Code on Val Town

Screenshot 2024-02-27 at 1.25.46 PM.png

Adds a "Code on Val Town" ribbon to your page. This lets your website visitors navigate to the code behind it.

This uses github-fork-ribbon-css under the hood.

Usage

Here are 2 different ways to add the "Code on Val Town" ribbon:

1. Wrap your fetch handler (recommended)

Create valimport { modifyFetchHandler } from "https://esm.town/v/andreterron/codeOnValTown?v=45"; import { html } from "https://esm.town/v/stevekrouse/html?v=5"; export default modifyFetchHandler(async (req: Request): Promise<Response> => { return html(`<h2>Hello world!</h2>`); });

Example: @andreterron/openable_handler

2. Wrap your HTML string

Create valimport { modifyHtmlString } from "https://esm.town/v/andreterron/codeOnValTown?v=45"; import { html } from "https://esm.town/v/stevekrouse/html?v=5"; export default async (req: Request): Promise<Response> => { return html(modifyHtmlString(`<h2>Hello world!</h2>`)); };

Example: @andreterron/openable_html

Other ways

We made sure this was very modular, so you can also add the ribbon using these methods:

Customization

Linking to the val

These functions infer the val using the call stack or the request URL. If the inference isn't working, or if you want to ensure it links to a specific val, pass the val argument:

  • modifyFetchHandler(handler, {val: { handle: "andre", name: "foo" }})
  • modifyHtmlString("<html>...", {val: { handle: "andre", name: "foo" }})

Styling

You can set the style parameter to a css string to customize the ribbon. Check out github-fork-ribbon-css to learn more about how to style the element.

  • modifyFetchHandler(handler, {style: ".github-fork-ribbon:before { background-color: #333; }"})
  • modifyHtmlString("<html>...", {style: ".github-fork-ribbon:before { background-color: #333; }"})

Here's how you can hide the ribbon on small screens:

Create valmodifyFetchHandler(handler, {style: `@media (max-width: 768px) { .github-fork-ribbon { display: none !important; } }`})

To-dos

  • Let users customize the ribbon. Some ideas are the text, color or placement.
Readme
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
import { modifyResponse } from "https://esm.town/v/andreterron/codeOnVT_modifyResponse?v=9";
import { ribbonElement } from "https://esm.town/v/andreterron/codeOnVT_ribbonElement?v=7";
import { inferRequestVal } from "https://esm.town/v/andreterron/inferRequestVal?v=2";
import { rootValRef } from "https://esm.town/v/andreterron/rootValRef?v=3";
import { ValRef } from "https://esm.town/v/andreterron/ValRef?v=1";
import { MiddlewareHandler } from "npm:hono";
/**
* @param bodyText HTML string that will be used to inject the element.
* @param val Define which val should open. Defaults to the root reference.
*/
export function modifyHtmlString(
bodyText: string,
{ val, style }: { val?: ValRef; style?: string } = {},
) {
val = val?.handle && val?.name ? val : rootValRef();
if (!val) {
console.error("Failed to infer val. Please set the options.val parameter to the desired `{ handle, name }`");
return bodyText;
}
const element = ribbonElement({ val, style });
const bodyIndex = bodyText.lastIndexOf("</body>");
const htmlIndex = bodyText.lastIndexOf("</html>");
// Append the element before </body> or </html>. Get the last occurrence of each
// and use whichever one comes first.
if (bodyIndex !== -1 && (htmlIndex === -1 || bodyIndex < htmlIndex)) {
return bodyText.slice(0, bodyIndex) + element + bodyText.slice(bodyIndex);
} else if (htmlIndex !== -1) {
return bodyText.slice(0, htmlIndex) + element + bodyText.slice(htmlIndex);
} else {
return bodyText + element;
}
}
/**
* @param handler Fetch handler
* @param val Define which val should open
*/
export function modifyFetchHandler(
handler: (req: Request) => Response | Promise<Response>,
{ val, style }: { val?: ValRef; style?: string } = {},
): (req: Request) => Promise<Response> {
return async (req: Request): Promise<Response> => {
const res = await handler(req);
val = val?.handle && val?.name ? val : inferRequestVal(req);
return modifyResponse(res, { val, style });
};
}
export const honoMiddleware = (options: { val?: ValRef; style?: string } = {}): MiddlewareHandler => {
return async function(c, next) {
await next();
if (c.res.headers.get("Content-Type")?.startsWith("text/html")) {
const text = await c.res.text();
c.res = new Response(modifyHtmlString(text, options), c.res);
}
};
};
export default modifyFetchHandler;

Code on Val Town

Screenshot 2024-02-27 at 1.25.46 PM.png

Adds a "Code on Val Town" ribbon to your page. This lets your website visitors navigate to the code behind it.

This uses github-fork-ribbon-css under the hood.

Usage

Here are 2 different ways to add the "Code on Val Town" ribbon:

1. Wrap your fetch handler (recommended)

Create valimport { modifyFetchHandler } from "https://esm.town/v/andreterron/codeOnValTown?v=45"; import { html } from "https://esm.town/v/stevekrouse/html?v=5"; export default modifyFetchHandler(async (req: Request): Promise<Response> => { return html(`<h2>Hello world!</h2>`); });

Example: @andreterron/openable_handler

2. Wrap your HTML string

Create valimport { modifyHtmlString } from "https://esm.town/v/andreterron/codeOnValTown?v=45"; import { html } from "https://esm.town/v/stevekrouse/html?v=5"; export default async (req: Request): Promise<Response> => { return html(modifyHtmlString(`<h2>Hello world!</h2>`)); };

Example: @andreterron/openable_html

Other ways

We made sure this was very modular, so you can also add the ribbon using these methods:

Customization

Linking to the val

These functions infer the val using the call stack or the request URL. If the inference isn't working, or if you want to ensure it links to a specific val, pass the val argument:

  • modifyFetchHandler(handler, {val: { handle: "andre", name: "foo" }})
  • modifyHtmlString("<html>...", {val: { handle: "andre", name: "foo" }})

Styling

You can set the style parameter to a css string to customize the ribbon. Check out github-fork-ribbon-css to learn more about how to style the element.

  • modifyFetchHandler(handler, {style: ".github-fork-ribbon:before { background-color: #333; }"})
  • modifyHtmlString("<html>...", {style: ".github-fork-ribbon:before { background-color: #333; }"})

Here's how you can hide the ribbon on small screens:

Create valmodifyFetchHandler(handler, {style: `@media (max-width: 768px) { .github-fork-ribbon { display: none !important; } }`})

To-dos

  • Let users customize the ribbon. Some ideas are the text, color or placement.
Readme
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
import { ribbonElement } from "https://esm.town/v/andreterron/codeOnVT_ribbonElement?v=7";
import { InjectHTMLElementStream } from "https://esm.town/v/andreterron/InjectHTMLElementStream?v=9";
import { rootValRef } from "https://esm.town/v/andreterron/rootValRef?v=3";
import type { ValRef } from "https://esm.town/v/andreterron/ValRef?v=1";
import { createWrappedHandler, type RequestHandler } from "https://esm.town/v/vladimyr/pipeResponse?v=20";
/**
* @param bodyText HTML string that will be used to inject the element.
* @param val Define which val should open. Defaults to the root reference.
*/
export function modifyHtmlString(
bodyText: string,
{ val, style }: { val?: ValRef; style?: string } = {},
) {
val = val?.handle && val?.name ? val : rootValRef();
if (!val) {
console.error("Failed to infer val. Please set the options.val parameter to the desired `{ handle, name }`");
return bodyText;
}
const element = ribbonElement({ val, style });
const bodyIndex = bodyText.lastIndexOf("</body>");
const htmlIndex = bodyText.lastIndexOf("</html>");
// Append the element before </body> or </html>. Get the last occurrence of each
// and use whichever one comes first.
if (bodyIndex !== -1 && (htmlIndex === -1 || bodyIndex < htmlIndex)) {
return bodyText.slice(0, bodyIndex) + element + bodyText.slice(bodyIndex);
} else if (htmlIndex !== -1) {
return bodyText.slice(0, htmlIndex) + element + bodyText.slice(htmlIndex);
} else {
return bodyText + element;
}
}
/**
* @param handler Fetch handler
* @param val Define which val should open
*/
export function modifyFetchHandler(
handler: RequestHandler,
{ val, style }: { val?: ValRef; style?: string } = {},
) {
val = val?.handle && val?.name ? val : rootValRef();
if (!val) {
console.error("Failed to infer val. Please set the options.val parameter to the desired `{ handle, name }`");
return handler;
}
const element = ribbonElement({ val, style });
return createWrappedHandler(
handler,
new InjectHTMLElementStream(element),
);
}
export default modifyFetchHandler;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
import { ribbonElement } from "https://esm.town/v/andreterron/codeOnVT_ribbonElement?v=7";
import { extractValInfo } from "https://esm.town/v/pomdtr/extractValInfo?v=26";
import { appendFragment } from "https://esm.town/v/vladimyr/appendFragment";
const { author, name } = extractValInfo(import.meta.url);
const html = `<html>
<head>
<title>test</title>
</head>
<body>
<p><code>@vladimyr/appendFragment</code> test</p>
</body>
</html>
`.trim();
export default async function(req: Request): Promise<Response> {
return new Response(
appendFragment(
ribbonElement({ val: { handle: author, name } }),
html,
),
{ headers: { "content-type": "text/html" } },
);
}
1
Next