From ac7a698c4f59041e7e59f779aff6fda55a448183 Mon Sep 17 00:00:00 2001 From: gsb Date: Wed, 29 Apr 2026 01:17:32 +0000 Subject: [PATCH] Add themes support Usage: const editor = new RibbitEditor({ themes: [ { name: 'dark', features: { sourceMode: false } }, { name: 'minimal', tags: minimalTags }, ], currentTheme: 'dark', }); The built-in theme is 'ribbit-default' and is always available. Additional themes from the themes array are registered on top. --- package.json | 7 +- src/ribbit.css | 58 ----------- src/static/ribbit-core.css | 22 ++++ src/static/themes/ribbit-default/theme.css | 52 ++++++++++ src/ts/default-theme.ts | 16 +++ src/{ => ts}/hopdown.ts | 0 src/{ => ts}/ribbit-editor.ts | 9 +- src/{ => ts}/ribbit.ts | 38 ++++++- src/{ => ts}/tags.ts | 0 src/ts/theme-manager.ts | 114 +++++++++++++++++++++ src/{ => ts}/types.ts | 10 ++ test/test_hopdown.js | 4 +- tsconfig.json | 4 +- 13 files changed, 264 insertions(+), 70 deletions(-) delete mode 100644 src/ribbit.css create mode 100644 src/static/ribbit-core.css create mode 100644 src/static/themes/ribbit-default/theme.css create mode 100644 src/ts/default-theme.ts rename src/{ => ts}/hopdown.ts (100%) rename src/{ => ts}/ribbit-editor.ts (93%) rename src/{ => ts}/ribbit.ts (75%) rename src/{ => ts}/tags.ts (100%) create mode 100644 src/ts/theme-manager.ts rename src/{ => ts}/types.ts (88%) diff --git a/package.json b/package.json index 724109e..2195c64 100644 --- a/package.json +++ b/package.json @@ -9,10 +9,11 @@ "src/" ], "scripts": { - "build": "npm run build:check && npm run build:js && npm run build:min", + "build": "mkdir -p dist/ribbit && npm run build:check && npm run build:js && npm run build:min && npm run build:css", "build:check": "tsc --noEmit", - "build:js": "esbuild src/ribbit-editor.ts --bundle --format=iife --sourcemap --outfile=dist/ribbit.js", - "build:min": "esbuild src/ribbit-editor.ts --bundle --format=iife --minify --outfile=dist/ribbit.min.js", + "build:js": "esbuild src/ts/ribbit-editor.ts --bundle --format=iife --sourcemap --outfile=dist/ribbit/ribbit.js", + "build:min": "esbuild src/ts/ribbit-editor.ts --bundle --format=iife --minify --outfile=dist/ribbit/ribbit.min.js", + "build:css": "cp src/static/ribbit-core.css dist/ribbit/ && cp -r src/static/themes dist/ribbit/", "test": "npm run build && node test/test_hopdown.js" }, "license": "MIT", diff --git a/src/ribbit.css b/src/ribbit.css deleted file mode 100644 index 96365d8..0000000 --- a/src/ribbit.css +++ /dev/null @@ -1,58 +0,0 @@ -/* - * ribbit.css — editor styles for the ribbit WYSIWYG markdown editor. - * - * Provides base content formatting and editor state styles. - * Override with your own theme CSS for custom look and feel. - */ - -/* ── Content formatting ──────────────────────────────── */ - -a { text-decoration: none; } - -q, blockquote { - margin-left: 30px; - font-size: 1.3em; - font-style: italic; - color: #555; -} - -table { width: 100%; } -th { border-bottom: 1px solid #000; padding: 3px; } -th, td { padding: 2px; } -table td table { max-width: 95%; } - -pre { - border: 1px dashed black; - border-radius: 5px; - padding: 10px; - margin: 5px; - background: #EEE; -} - -code { - display: inline-block; - border: 1px dashed black; - border-radius: 5px; - padding: 5px; - background: #EEE; - margin: 3px; -} - -/* ── Editor states ───────────────────────────────────── */ - -#ribbit { - display: none; -} - -#ribbit.loaded { - display: block; -} - -#ribbit.edit { - font-family: monospace; - white-space: pre; -} - -#ribbit.wysiwyg .md { - opacity: 0.5; -} diff --git a/src/static/ribbit-core.css b/src/static/ribbit-core.css new file mode 100644 index 0000000..f352fce --- /dev/null +++ b/src/static/ribbit-core.css @@ -0,0 +1,22 @@ +/* + * ribbit-core.css — functional editor styles. Always load this. + * These styles control editor state visibility and behavior. + * They should not be overridden by themes. + */ + +#ribbit { + display: none; +} + +#ribbit.loaded { + display: block; +} + +#ribbit.edit { + font-family: monospace; + white-space: pre; +} + +#ribbit.wysiwyg .md { + opacity: 0.5; +} diff --git a/src/static/themes/ribbit-default/theme.css b/src/static/themes/ribbit-default/theme.css new file mode 100644 index 0000000..ed2163a --- /dev/null +++ b/src/static/themes/ribbit-default/theme.css @@ -0,0 +1,52 @@ +/* + * default.css — the default ribbit theme. + * Provides basic aesthetic styling for rendered markdown content. + * Replace this file with your own theme to customize the look. + */ + +@import "../ribbit-core.css"; + +a { + text-decoration: none; +} + +blockquote { + margin-left: 30px; + font-size: 1.3em; + font-style: italic; + color: #555; +} + +table { + width: 100%; +} + +th { + border-bottom: 1px solid #000; + padding: 3px; +} + +th, td { + padding: 2px; +} + +table td table { + max-width: 95%; +} + +pre { + border: 1px dashed black; + border-radius: 5px; + padding: 10px; + margin: 5px; + background: #EEE; +} + +code { + display: inline-block; + border: 1px dashed black; + border-radius: 5px; + padding: 5px; + background: #EEE; + margin: 3px; +} diff --git a/src/ts/default-theme.ts b/src/ts/default-theme.ts new file mode 100644 index 0000000..e7c26f0 --- /dev/null +++ b/src/ts/default-theme.ts @@ -0,0 +1,16 @@ +/* + * default-theme.ts — the default ribbit theme. + * + * Enables all default tags and all editor features. + */ + +import type { RibbitTheme } from './types'; +import { defaultTags } from './tags'; + +export const defaultTheme: RibbitTheme = { + name: 'ribbit-default', + tags: defaultTags, + features: { + sourceMode: true, + }, +}; diff --git a/src/hopdown.ts b/src/ts/hopdown.ts similarity index 100% rename from src/hopdown.ts rename to src/ts/hopdown.ts diff --git a/src/ribbit-editor.ts b/src/ts/ribbit-editor.ts similarity index 93% rename from src/ribbit-editor.ts rename to src/ts/ribbit-editor.ts index e505708..95ff926 100644 --- a/src/ribbit-editor.ts +++ b/src/ts/ribbit-editor.ts @@ -2,9 +2,9 @@ * ribbit-editor.ts — WYSIWYG editing extension for Ribbit. */ -import hopdown from './hopdown'; import { HopDown } from './hopdown'; import { defaultTags, defaultBlockTags, defaultInlineTags, inlineTag } from './tags'; +import { defaultTheme } from './default-theme'; import { Ribbit, RibbitPlugin, RibbitSettings, camelCase, decodeHtmlEntities, encodeHtmlEntities } from './ribbit'; /** @@ -46,7 +46,7 @@ export class RibbitEditor extends Ribbit { } htmlToMarkdown(html?: string): string { - return hopdown.toMarkdown(html || this.element.innerHTML); + return this.converter.toMarkdown(html || this.element.innerHTML); } getMarkdown(): string { @@ -80,6 +80,9 @@ export class RibbitEditor extends Ribbit { } edit(): void { + if (!this.theme.features?.sourceMode) { + return; + } if (this.state === this.states.EDIT) return; this.changed = false; this.element.contentEditable = 'true'; @@ -101,11 +104,11 @@ export class RibbitEditor extends Ribbit { // Attach public API to window for