Two build targets — core and full

dist/ribbit/ribbit-core.js    — editor without extra features
dist/ribbit/ribbit.js         — editor with all features

Added a theme feature flag for vim keybindings
This commit is contained in:
gsb 2026-04-29 07:48:06 +00:00
parent 3368e719fd
commit 2e28598243
5 changed files with 48 additions and 22 deletions

View File

@ -7,10 +7,12 @@
"dist/ribbit/" "dist/ribbit/"
], ],
"scripts": { "scripts": {
"build": "mkdir -p dist/ribbit && npm run build:check && npm run build:js && npm run build:min && npm run build:css", "build": "mkdir -p dist/ribbit && npm run build:check && npm run build:js && npm run build:min && npm run build:core && npm run build:core-min && npm run build:css",
"build:check": "tsc --noEmit", "build:check": "tsc --noEmit",
"build:js": "esbuild src/ts/ribbit-editor.ts --bundle --format=iife --global-name=ribbit --sourcemap --outfile=dist/ribbit/ribbit.js", "build:js": "esbuild src/ts/ribbit-editor.ts --bundle --format=iife --global-name=ribbit --sourcemap --outfile=dist/ribbit/ribbit.js",
"build:min": "esbuild src/ts/ribbit-editor.ts --bundle --format=iife --global-name=ribbit --minify --outfile=dist/ribbit/ribbit.min.js", "build:min": "esbuild src/ts/ribbit-editor.ts --bundle --format=iife --global-name=ribbit --minify --outfile=dist/ribbit/ribbit.min.js",
"build:core": "esbuild src/ts/ribbit-core.ts --bundle --format=iife --global-name=ribbit --sourcemap --outfile=dist/ribbit/ribbit-core.js",
"build:core-min": "esbuild src/ts/ribbit-core.ts --bundle --format=iife --global-name=ribbit --minify --outfile=dist/ribbit/ribbit-core.min.js",
"build:css": "cp src/static/ribbit-core.css dist/ribbit/ && cp -r src/static/themes dist/ribbit/", "build:css": "cp src/static/ribbit-core.css dist/ribbit/ && cp -r src/static/themes dist/ribbit/",
"test": "npm run build && jest --verbose", "test": "npm run build && jest --verbose",
"test:coverage": "npm run build && jest --coverage" "test:coverage": "npm run build && jest --coverage"

21
src/ts/ribbit-core.ts Normal file
View File

@ -0,0 +1,21 @@
/*
* ribbit-core.ts lightweight entry point without optional features (vim).
*
* Same API as ribbit-editor.ts but excludes VimHandler.
*/
import { HopDown } from './hopdown';
import { defaultTags, defaultBlockTags, defaultInlineTags, inlineTag } from './tags';
import { defaultTheme } from './default-theme';
import { Ribbit, camelCase, decodeHtmlEntities, encodeHtmlEntities } from './ribbit';
import { type MacroDef } from './macros';
export { RibbitEditor as Editor } from './ribbit-editor';
export { Ribbit as Viewer };
export { HopDown };
export { inlineTag };
export { defaultTags, defaultBlockTags, defaultInlineTags };
export { defaultTheme };
export { camelCase, decodeHtmlEntities, encodeHtmlEntities };
export { ToolbarManager } from './toolbar';
export type { MacroDef };

View File

@ -23,7 +23,7 @@ import { type MacroDef } from './macros';
* editor.view(); // switch to read-only view * editor.view(); // switch to read-only view
*/ */
export class RibbitEditor extends Ribbit { export class RibbitEditor extends Ribbit {
private vim!: VimHandler; private vim?: VimHandler;
run(): void { run(): void {
this.states = { this.states = {
@ -32,17 +32,19 @@ export class RibbitEditor extends Ribbit {
WYSIWYG: 'wysiwyg' WYSIWYG: 'wysiwyg'
}; };
this.vim = new VimHandler((mode) => { if (this.theme.features?.vim) {
if (mode === 'normal') { this.vim = new VimHandler((mode) => {
this.toolbar.disable(); if (mode === 'normal') {
this.element.classList.add('vim-normal'); this.toolbar.disable();
this.element.classList.remove('vim-insert'); this.element.classList.add('vim-normal');
} else { this.element.classList.remove('vim-insert');
this.toolbar.enable(); } else {
this.element.classList.add('vim-insert'); this.toolbar.enable();
this.element.classList.remove('vim-normal'); this.element.classList.add('vim-insert');
} this.element.classList.remove('vim-normal');
}); }
});
}
this.#bindEvents(); this.#bindEvents();
this.element.classList.add('loaded'); this.element.classList.add('loaded');
@ -218,7 +220,7 @@ export class RibbitEditor extends Ribbit {
wysiwyg(): void { wysiwyg(): void {
if (this.getState() === this.states.WYSIWYG) return; if (this.getState() === this.states.WYSIWYG) return;
this.vim.detach(); this.vim?.detach();
this.element.contentEditable = 'true'; this.element.contentEditable = 'true';
this.element.innerHTML = this.getHTML(); this.element.innerHTML = this.getHTML();
Array.from(this.element.querySelectorAll('.macro')).forEach(el => { Array.from(this.element.querySelectorAll('.macro')).forEach(el => {
@ -238,7 +240,7 @@ export class RibbitEditor extends Ribbit {
if (this.state === this.states.EDIT) return; if (this.state === this.states.EDIT) return;
this.element.contentEditable = 'true'; this.element.contentEditable = 'true';
this.element.innerHTML = encodeHtmlEntities(this.getMarkdown()); this.element.innerHTML = encodeHtmlEntities(this.getMarkdown());
this.vim.attach(this.element); this.vim?.attach(this.element);
this.setState(this.states.EDIT); this.setState(this.states.EDIT);
} }

View File

@ -68,6 +68,7 @@ export interface InlineTagDef {
export interface RibbitThemeFeatures { export interface RibbitThemeFeatures {
sourceMode?: boolean; sourceMode?: boolean;
vim?: boolean;
} }
/** /**

View File

@ -6,14 +6,14 @@ describe('VimHandler', () => {
beforeEach(() => resetDOM('hello world')); beforeEach(() => resetDOM('hello world'));
it('starts in insert mode', () => { it('starts in insert mode', () => {
const editor = new r.Editor({}); const editor = new r.Editor({ currentTheme: 'vim', themes: [{ name: 'vim', features: { sourceMode: true, vim: true }, tags: r.defaultTags }] });
editor.run(); editor.run();
editor.edit(); editor.edit();
expect(editor.element.classList.contains('vim-insert')).toBe(true); expect(editor.element.classList.contains('vim-insert')).toBe(true);
}); });
it('Esc enters normal mode', () => { it('Esc enters normal mode', () => {
const editor = new r.Editor({}); const editor = new r.Editor({ currentTheme: 'vim', themes: [{ name: 'vim', features: { sourceMode: true, vim: true }, tags: r.defaultTags }] });
editor.run(); editor.run();
editor.edit(); editor.edit();
editor.element.dispatchEvent(new r.window.KeyboardEvent('keydown', { key: 'Escape' })); editor.element.dispatchEvent(new r.window.KeyboardEvent('keydown', { key: 'Escape' }));
@ -22,7 +22,7 @@ describe('VimHandler', () => {
}); });
it('i returns to insert mode', () => { it('i returns to insert mode', () => {
const editor = new r.Editor({}); const editor = new r.Editor({ currentTheme: 'vim', themes: [{ name: 'vim', features: { sourceMode: true, vim: true }, tags: r.defaultTags }] });
editor.run(); editor.run();
editor.edit(); editor.edit();
// Enter normal mode // Enter normal mode
@ -34,7 +34,7 @@ describe('VimHandler', () => {
}); });
it('disables toolbar in normal mode', () => { it('disables toolbar in normal mode', () => {
const editor = new r.Editor({ autoToolbar: false }); const editor = new r.Editor({ autoToolbar: false, currentTheme: 'vim', themes: [{ name: 'vim', features: { sourceMode: true, vim: true }, tags: r.defaultTags }] });
editor.run(); editor.run();
editor.toolbar.render(); editor.toolbar.render();
editor.edit(); editor.edit();
@ -45,7 +45,7 @@ describe('VimHandler', () => {
}); });
it('re-enables toolbar in insert mode', () => { it('re-enables toolbar in insert mode', () => {
const editor = new r.Editor({ autoToolbar: false }); const editor = new r.Editor({ autoToolbar: false, currentTheme: 'vim', themes: [{ name: 'vim', features: { sourceMode: true, vim: true }, tags: r.defaultTags }] });
editor.run(); editor.run();
editor.toolbar.render(); editor.toolbar.render();
editor.edit(); editor.edit();
@ -56,7 +56,7 @@ describe('VimHandler', () => {
}); });
it('detaches when leaving edit mode', () => { it('detaches when leaving edit mode', () => {
const editor = new r.Editor({}); const editor = new r.Editor({ currentTheme: 'vim', themes: [{ name: 'vim', features: { sourceMode: true, vim: true }, tags: r.defaultTags }] });
editor.run(); editor.run();
editor.edit(); editor.edit();
editor.element.dispatchEvent(new r.window.KeyboardEvent('keydown', { key: 'Escape' })); editor.element.dispatchEvent(new r.window.KeyboardEvent('keydown', { key: 'Escape' }));
@ -68,7 +68,7 @@ describe('VimHandler', () => {
}); });
it('only activates in edit mode', () => { it('only activates in edit mode', () => {
const editor = new r.Editor({}); const editor = new r.Editor({ currentTheme: 'vim', themes: [{ name: 'vim', features: { sourceMode: true, vim: true }, tags: r.defaultTags }] });
editor.run(); editor.run();
editor.wysiwyg(); editor.wysiwyg();
// Esc in wysiwyg should not add vim classes // Esc in wysiwyg should not add vim classes