Readme

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:

import { 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

import { 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:

modifyFetchHandler(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.
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;
Val Town is a social website to write and deploy JavaScript.
Build APIs and schedule functions from your browser.
Comments
14
stevekrouse avatar

Seems like it adds this artifact:

Screenshot 2024-02-27 at 17.18.20.png

stevekrouse avatar
stevekrouse avatar

Also, clicking it seems to go to this val, not the val where my code lives

andreterron avatar

Should be fixed now!

stevekrouse avatar

Thanks!

New issue: on mobile it's getting in the way of my important navbar element. Demo: https://www.val.town/v/stevekrouse/dateme_code_on_val_town

So I had to remove it. It'd be awesome to have it be configurable to show up on other spots on the page or be smaller or be a footer :)

andreterron avatar

For now, I removed it for screens below 768px

andreterron avatar

It looks like the css itself is fairly fixed: https://github.com/simonwhitaker/github-fork-ribbon-css/blob/gh-pages/gh-fork-ribbon.css

I see a couple options on how to customize this:

  1. Let users pass a custom element
  2. Let users add their custom <style> tag to be injected in the shadow DOM
  3. We fork github-fork-ribbon-css and use that instead. Then we'd do css wrangling to enable placement and color customization.

To show it on other spots or to make it smaller, either option works. In order for it to be a footer, I think the best way is option (1).

As a user of the wrapper, what would you prefer?

stevekrouse avatar

I don't want it removed on small screens always. I have it on other small screens looking good now.

I like the idea of passing a custom element or providing other options.

This little GitHub icon is really elegant: https://counterscale.dev/

stevekrouse avatar

Would be awesome if these "inject" utilities were able to inject arbitrary stuff, ie like this https://www.val.town/v/stevekrouse/ReloadScript

stevekrouse avatar

modifyFetchHandler should work on fetch handlers that don't return Promises as well

andreterron avatar

I don't want it removed on small screens always.

I'll add a style parameter, so that it can be customized any time it's used. I'll also see if a minWidth parameter could make sense as well.

This little GitHub icon is really elegant

That doesn't feel like it's generic enough to be achieved with an injector. For this and similar scenarios, we can provide separate vals that (a) infer the val URL and (b) returns the Val Town logo URL. Users can then build a more integrated component in their website.

Would be awesome if these "inject" utilities were able to inject arbitrary stuff

True! @andreterron/InjectHTMLElementStream can inject anything. We can also build generic injectors for strings, handlers, responses, etc.

modifyFetchHandler should work on fetch handlers that don't return Promises as well

Good catch!

andreterron avatar

Added the style parameter, and updated the modifyFetchHandler to support non-Promise returns!

stevekrouse avatar

Nice! Worked well - updated @stevekrouse/dateme

stevekrouse avatar

I don't know when or why, but now on @stevekrouse/dateme, the ribbon links to this page, not the dateme val. I will set it manually. I wonder why the rootVal inference broke

v50
March 5, 2024