Selaa lähdekoodia

feat: MarkdownDialog

Sv443 8 kuukautta sitten
vanhempi
commit
c8f91a5a00
5 muutettua tiedostoa jossa 128 lisäystä ja 2 poistoa
  1. 54 1
      contributing.md
  2. 53 0
      src/components/MarkdownDialog.ts
  3. 1 0
      src/components/index.ts
  4. 18 0
      src/index.ts
  5. 2 1
      src/interface.ts

+ 54 - 1
contributing.md

@@ -370,6 +370,7 @@ Functions marked with 🔒 need to be passed a per-session and per-plugin authen
 - DOM:
   - [BytmDialog](#bytmdialog) - A class for creating and managing dialogs
   - [ExImDialog](#eximdialog) - Subclass of BytmDialog for allowing users to export and import serializable data
+  - [MarkdownDialog](#markdowndialog) - Subclass of BytmDialog for displaying markdown content
   - [addSelectorListener()](#addselectorlistener) - Adds a listener that checks for changes in DOM elements matching a CSS selector
   - [onInteraction()](#oninteraction) - Adds accessible event listeners to the specified element for button or link-like keyboard and mouse interactions
   - [getVideoTime()](#getvideotime) - Returns the current video time (on both YT and YTM)
@@ -1464,7 +1465,6 @@ Functions marked with 🔒 need to be passed a per-session and per-plugin authen
 > 
 > async function exportData() {
 >   // compress the data to save space
->   // note that this requires the `@grant unsafeWindow` directive in the metadata block!
 >   const exportData = JSON.stringify({ foo: "bar" });
 >   return await unsafeWindow.BYTM.UserUtils.compress(exportData, "deflate");
 > }
@@ -1512,6 +1512,59 @@ Functions marked with 🔒 need to be passed a per-session and per-plugin authen
 
 <br>
 
+> #### MarkdownDialog
+> Usage:
+> ```ts
+> new unsafeWindow.BYTM.MarkdownDialog(options: MarkdownDialogOptions): MarkdownDialog
+> ```
+>
+> A subclass of [BytmDialog](#bytmdialog) that can be used to create and manage a dialog that renders its entire body using GitHub-flavored Markdown (and HTML mixins).  
+> Note: the provided `id` will be prefixed with `md-` to avoid conflicts with other dialogs.  
+>   
+> Features:
+> - Has all the features of the [BytmDialog](#bytmdialog) class
+> - Can be used to display any kind of information in a dialog that can be written in Markdown
+> - Supports GitHub-flavored Markdown and HTML mixins like `<details>` and `<summary>`
+>   
+> Options properties:  
+> All properties from the [BytmDialog](#bytmdialog) class are available here as well, except for `renderBody` (which was replaced by `body`)  
+> | Property | Description |
+> | :-- | :-- |
+> | `body: string \| (() => string \| Promise<string>)` | Markdown content to render in the dialog. Can be a string or a sync or async function that returns a string. |
+>   
+> Methods:  
+> The methods from the [`NanoEmitter`](#nanoemitter) and [`BytmDialog`](#bytmdialog) classes are also available here.  
+> - `static parseMd(md: string): Promise<string>`  
+>   Parses the provided Markdown string (with GitHub flavor and HTML mixins) and returns the HTML representation as a string.
+> - `protected renderBody(): Promise<void>`  
+>   Renders the Markdown content to the dialog's body element. You can only override this method if you create a subclass of `MarkdownDialog`  
+>   If you do, you can use `parseMd()` to render a custom mixture of Markdown HTML and JavaScript-created elements to the body.
+> 
+> <details><summary><b>Example <i>(click to expand)</i></b></summary>
+> 
+> ```ts
+> const markdownDialog = new unsafeWindow.BYTM.MarkdownDialog({
+>   id: "my-markdown-dialog",
+>   width: 400,
+>   height: 600,
+>   body: `\
+> # Look at this table:
+> | Column 1 | Column 2 |
+> | --: | :-- |
+> | Hello | World |
+> 
+> <br /><br /><br /><br />
+> 
+> <details>
+>   <summary>Click me!</summary>
+>   I'm a hidden text block!
+> </details>`,
+> });
+> ```
+> </details>
+
+<br>
+
 > #### createHotkeyInput()
 > Usage:  
 > ```ts

+ 53 - 0
src/components/MarkdownDialog.ts

@@ -0,0 +1,53 @@
+import { BytmDialog, type BytmDialogOptions } from "./BytmDialog.js";
+import { marked } from "marked";
+
+type MarkdownDialogOptions = Omit<BytmDialogOptions, "renderBody"> & {
+  /** The markdown to render */
+  body: string | (() => string | Promise<string>);
+};
+
+export class MarkdownDialog extends BytmDialog {
+  protected opts: MarkdownDialogOptions;
+
+  constructor(options: MarkdownDialogOptions) {
+    super({
+      ...options,
+      id: `md-${options.id}`,
+      renderBody: () => this.renderBody(),
+    });
+    this.opts = options;
+  }
+
+  /** Parses the passed markdown string and returns it as an HTML string */
+  public static async parseMd(md: string): Promise<string> {
+    return await marked.parse(md, {
+      async: true,
+      gfm: true,
+      breaks: true,
+    });
+  }
+
+
+  /** Renders the dialog body elements from a markdown string using what's set in `this.opts.body` */
+  protected async renderBody(): Promise<HTMLElement> {
+    const panesCont = document.createElement("div");
+    panesCont.classList.add("bytm-exim-dialog-panes-cont");
+
+    const markdownPane = document.createElement("div");
+    markdownPane.classList.add("bytm-exim-dialog-pane");
+    markdownPane.classList.add("bytm-exim-dialog-markdown-pane");
+
+    const mdCont = typeof this.opts.body === "string"
+      ? this.opts.body
+      : await this.opts.body();
+
+    const markdownEl = document.createElement("div");
+    markdownEl.classList.add("bytm-exim-dialog-markdown");
+    markdownEl.innerHTML = await MarkdownDialog.parseMd(mdCont);
+
+    markdownPane.appendChild(markdownEl);
+    panesCont.appendChild(markdownPane);
+
+    return panesCont;
+  }
+}

+ 1 - 0
src/components/index.ts

@@ -3,6 +3,7 @@ export * from "./circularButton.js";
 export * from "./ExImDialog.js";
 export * from "./hotkeyInput.js";
 export * from "./longButton.js";
+export * from "./MarkdownDialog.js";
 export * from "./ripple.js";
 export * from "./toast.js";
 export * from "./toggleInput.js";

+ 18 - 0
src/index.ts

@@ -31,6 +31,7 @@ import {
   initVersionCheck,
 } from "./features/index.js";
 import { storeSerializer } from "./storeSerializer.js";
+import { MarkdownDialog } from "./components/index.js";
 
 //#region cns. watermark
 
@@ -54,6 +55,7 @@ import { storeSerializer } from "./storeSerializer.js";
     "─ This library for semver comparison: https://github.com/omichelsen/compare-versions",
     "─ This tiny event listener library: https://github.com/ai/nanoevents",
     "─ This markdown parser library: https://github.com/markedjs/marked",
+    "─ TypeScript and the tslib runtime: https://github.com/microsoft/TypeScript",
   ].join("\n"));
 }
 
@@ -449,6 +451,22 @@ function registerDevCommands() {
 
   GM.registerMenuCommand("Throw Error", () => error("Test error thrown by user command:", new SyntaxError("Test error")));
 
+  GM.registerMenuCommand("Example MarkdownDialog", async () => {
+    const mdDlg = new MarkdownDialog({
+      id: "example",
+      width: 500,
+      height: 400,
+      renderHeader() {
+        const header = document.createElement("h1");
+        header.textContent = "Example Markdown Dialog";
+        return header;
+      },
+      body: "## This is a test dialog\n```ts\nconsole.log(\"Hello, world!\");\n```\n\n- List item 1\n- List item 2\n- List item 3",
+    });
+
+    await mdDlg.open();
+  });
+
   log("Registered dev menu commands");
 }
 

+ 2 - 1
src/interface.ts

@@ -7,7 +7,7 @@ import { getFeatures, setFeatures } from "./config.js";
 import { autoLikeStore, featInfo, fetchLyricsUrlTop, getLyricsCacheEntry, sanitizeArtists, sanitizeSong } from "./features/index.js";
 import { allSiteEvents, type SiteEventsMap } from "./siteEvents.js";
 import { LogLevel, type FeatureConfig, type FeatureInfo, type LyricsCacheEntry, type PluginDef, type PluginInfo, type PluginRegisterResult, type PluginDefResolvable, type PluginEventMap, type PluginItem, type BytmObject, type AutoLikeData, type InterfaceFunctions } from "./types.js";
-import { BytmDialog, ExImDialog, createCircularBtn, createHotkeyInput, createRipple, createToggleInput, showIconToast, showToast } from "./components/index.js";
+import { BytmDialog, ExImDialog, MarkdownDialog, createCircularBtn, createHotkeyInput, createRipple, createToggleInput, showIconToast, showToast } from "./components/index.js";
 
 const { getUnsafeWindow, randomId, NanoEmitter } = UserUtils;
 
@@ -173,6 +173,7 @@ export function initInterface() {
     NanoEmitter,
     BytmDialog,
     ExImDialog,
+    MarkdownDialog,
     // libraries
     UserUtils,
     compareVersions,