From e16928f1b7a1accc6a9d959432b234ba61b315b4 Mon Sep 17 00:00:00 2001 From: Rias Date: Fri, 19 Jun 2026 11:15:26 +0200 Subject: [PATCH] Add H4-H6, strikethrough formatting & buttons --- README.md | 11 +++++---- scripts/generate-types.js | 8 +++++++ src/icons.js | 18 ++++++++++++++ src/overtype.d.ts | 14 +++++++++++ src/parser.js | 4 ++-- src/styles.js | 43 ++++++++++++++++++++++++++++++++-- src/themes.js | 12 ++++++++++ src/toolbar-buttons.js | 49 +++++++++++++++++++++++++++++++++++++++ test/overtype.test.js | 5 +++- test/test-types.ts | 27 ++++++++++++++++++++- 10 files changed, 181 insertions(+), 10 deletions(-) diff --git a/README.md b/README.md index d261b50..64a9154 100644 --- a/README.md +++ b/README.md @@ -144,7 +144,7 @@ const [editor] = new OverType('#editor', { value: '# Document\n\nSelect text and use the toolbar!' }); -// Default toolbar: Bold, Italic, Code | Link | H1, H2, H3 | Lists, Tasks | Quote | View Mode +// Default toolbar: Bold, Italic, Strikethrough, Code | Link | H1, H2, H3 | Lists, Tasks | Quote | View Mode ``` **Custom Toolbar (v2.0):** @@ -169,7 +169,7 @@ const [editor] = new OverType('#editor', { ] }); -// Available: bold, italic, code, link, h1, h2, h3, bulletList, +// Available: bold, italic, strikethrough, code, link, h1, h2, h3, h4, h5, h6, bulletList, // orderedList, taskList, quote, separator, viewMode ``` @@ -203,7 +203,7 @@ document.querySelector('#bold-btn').addEventListener('click', () => { }); ``` -Available actions include `toggleBold`, `toggleItalic`, `toggleCode`, `insertLink`, `toggleBulletList`, `toggleNumberedList`, `toggleQuote`, `toggleTaskList`, `insertHeader`, `toggleH1`/`H2`/`H3`, `getActiveFormats`, `hasFormat`, `expandSelection`, and `applyCustomFormat`. The same namespace is available as `window.markdownActions` and `OverType.markdownActions` in script-tag builds. +Available actions include `toggleBold`, `toggleItalic`, `toggleStrikethrough`, `toggleCode`, `insertLink`, `toggleBulletList`, `toggleNumberedList`, `toggleQuote`, `toggleTaskList`, `insertHeader`, `toggleH1`/`H2`/`H3`/`H4`/`H5`/`H6`, `getActiveFormats`, `hasFormat`, `expandSelection`, and `applyCustomFormat`. The same namespace is available as `window.markdownActions` and `OverType.markdownActions` in script-tag builds. See [examples/custom-toolbar.html](examples/custom-toolbar.html) for complete examples. @@ -345,6 +345,9 @@ const [editor] = new OverType('#editor', { h1: '#f95738', h2: '#ee964b', h3: '#3d8a51', + h4: '#0d3b66', + h5: '#5a7a9b', + h6: '#999999', strong: '#ee964b', em: '#f95738', link: '#0d3b66', @@ -697,7 +700,7 @@ When no text is selected, Tab and Shift+Tab use normal browser focus navigation. ## Supported Markdown -- **Headers** - `# H1`, `## H2`, `### H3` +- **Headers** - `# H1` through `###### H6` - **Bold** - `**text**` or `__text__` - **Italic** - `*text*` or `_text_` - **Strikethrough** - `~~text~~` or `~text~` (GFM) diff --git a/scripts/generate-types.js b/scripts/generate-types.js index c97f55f..198043e 100755 --- a/scripts/generate-types.js +++ b/scripts/generate-types.js @@ -337,6 +337,7 @@ export default OverType; export const markdownActions: { toggleBold(textarea: HTMLTextAreaElement): void; toggleItalic(textarea: HTMLTextAreaElement): void; + toggleStrikethrough(textarea: HTMLTextAreaElement): void; toggleCode(textarea: HTMLTextAreaElement): void; insertLink(textarea: HTMLTextAreaElement, options?: { url?: string; text?: string }): void; toggleBulletList(textarea: HTMLTextAreaElement): void; @@ -347,6 +348,9 @@ export const markdownActions: { toggleH1(textarea: HTMLTextAreaElement): void; toggleH2(textarea: HTMLTextAreaElement): void; toggleH3(textarea: HTMLTextAreaElement): void; + toggleH4(textarea: HTMLTextAreaElement): void; + toggleH5(textarea: HTMLTextAreaElement): void; + toggleH6(textarea: HTMLTextAreaElement): void; getActiveFormats(textarea: HTMLTextAreaElement): string[]; hasFormat(textarea: HTMLTextAreaElement, format: string): boolean; expandSelection(textarea: HTMLTextAreaElement, options?: object): void; @@ -359,12 +363,16 @@ export const markdownActions: { export const toolbarButtons: { bold: ToolbarButton; italic: ToolbarButton; + strikethrough: ToolbarButton; code: ToolbarButton; separator: ToolbarButton; link: ToolbarButton; h1: ToolbarButton; h2: ToolbarButton; h3: ToolbarButton; + h4: ToolbarButton; + h5: ToolbarButton; + h6: ToolbarButton; bulletList: ToolbarButton; orderedList: ToolbarButton; taskList: ToolbarButton; diff --git a/src/icons.js b/src/icons.js index 240cbc9..31ab4d5 100644 --- a/src/icons.js +++ b/src/icons.js @@ -14,6 +14,12 @@ export const italicIcon = ` `; +export const strikethroughIcon = ` + + + +`; + export const h1Icon = ` `; @@ -26,6 +32,18 @@ export const h3Icon = ` `; +export const h4Icon = ` + +`; + +export const h5Icon = ` + +`; + +export const h6Icon = ` + +`; + export const linkIcon = ` diff --git a/src/overtype.d.ts b/src/overtype.d.ts index dd9c207..a795b8d 100644 --- a/src/overtype.d.ts +++ b/src/overtype.d.ts @@ -12,6 +12,9 @@ export interface PreviewColors { h1?: string; h2?: string; h3?: string; + h4?: string; + h5?: string; + h6?: string; hr?: string; link?: string; strong?: string; @@ -33,6 +36,9 @@ export interface Theme { h1?: string; h2?: string; h3?: string; + h4?: string; + h5?: string; + h6?: string; hoverBg?: string; hr?: string; link?: string; @@ -245,6 +251,7 @@ export default OverType; export const markdownActions: { toggleBold(textarea: HTMLTextAreaElement): void; toggleItalic(textarea: HTMLTextAreaElement): void; + toggleStrikethrough(textarea: HTMLTextAreaElement): void; toggleCode(textarea: HTMLTextAreaElement): void; insertLink(textarea: HTMLTextAreaElement, options?: { url?: string; text?: string }): void; toggleBulletList(textarea: HTMLTextAreaElement): void; @@ -255,6 +262,9 @@ export const markdownActions: { toggleH1(textarea: HTMLTextAreaElement): void; toggleH2(textarea: HTMLTextAreaElement): void; toggleH3(textarea: HTMLTextAreaElement): void; + toggleH4(textarea: HTMLTextAreaElement): void; + toggleH5(textarea: HTMLTextAreaElement): void; + toggleH6(textarea: HTMLTextAreaElement): void; getActiveFormats(textarea: HTMLTextAreaElement): string[]; hasFormat(textarea: HTMLTextAreaElement, format: string): boolean; expandSelection(textarea: HTMLTextAreaElement, options?: object): void; @@ -267,12 +277,16 @@ export const markdownActions: { export const toolbarButtons: { bold: ToolbarButton; italic: ToolbarButton; + strikethrough: ToolbarButton; code: ToolbarButton; separator: ToolbarButton; link: ToolbarButton; h1: ToolbarButton; h2: ToolbarButton; h3: ToolbarButton; + h4: ToolbarButton; + h5: ToolbarButton; + h6: ToolbarButton; bulletList: ToolbarButton; orderedList: ToolbarButton; taskList: ToolbarButton; diff --git a/src/parser.js b/src/parser.js index d023eb0..4b7ff8a 100644 --- a/src/parser.js +++ b/src/parser.js @@ -80,12 +80,12 @@ export class MarkdownParser { } /** - * Parse headers (h1-h3 only) + * Parse headers (h1-h6) * @param {string} html - HTML line to parse * @returns {string} Parsed HTML with header styling */ static parseHeader(html) { - return html.replace(/^(#{1,3})\s(.+)$/, (match, hashes, content) => { + return html.replace(/^(#{1,6})\s(.+)$/, (match, hashes, content) => { const level = hashes.length; content = this.parseInlineElements(content); return `${hashes} ${content}`; diff --git a/src/styles.js b/src/styles.js index ced71df..73064c7 100644 --- a/src/styles.js +++ b/src/styles.js @@ -296,11 +296,23 @@ export function generateStyles(options = {}) { .overtype-wrapper .overtype-preview .h3 { color: var(--h3, #3d8a51) !important; } + .overtype-wrapper .overtype-preview .h4 { + color: var(--h4, #0d3b66) !important; + } + .overtype-wrapper .overtype-preview .h5 { + color: var(--h5, #5a7a9b) !important; + } + .overtype-wrapper .overtype-preview .h6 { + color: var(--h6, #999999) !important; + } /* Semantic headers - flatten in edit mode */ .overtype-wrapper .overtype-preview h1, .overtype-wrapper .overtype-preview h2, - .overtype-wrapper .overtype-preview h3 { + .overtype-wrapper .overtype-preview h3, + .overtype-wrapper .overtype-preview h4, + .overtype-wrapper .overtype-preview h5, + .overtype-wrapper .overtype-preview h6 { font-size: inherit !important; font-weight: bold !important; margin: 0 !important; @@ -319,6 +331,15 @@ export function generateStyles(options = {}) { .overtype-wrapper .overtype-preview h3 { color: var(--h3, #3d8a51) !important; } + .overtype-wrapper .overtype-preview h4 { + color: var(--h4, #0d3b66) !important; + } + .overtype-wrapper .overtype-preview h5 { + color: var(--h5, #5a7a9b) !important; + } + .overtype-wrapper .overtype-preview h6 { + color: var(--h6, #999999) !important; + } /* Lists - remove styling in edit mode */ .overtype-wrapper .overtype-preview ul, @@ -718,7 +739,10 @@ export function generateStyles(options = {}) { /* Headers - restore proper sizing in preview mode */ .overtype-container[data-mode="preview"] .overtype-wrapper .overtype-preview h1, .overtype-container[data-mode="preview"] .overtype-wrapper .overtype-preview h2, - .overtype-container[data-mode="preview"] .overtype-wrapper .overtype-preview h3 { + .overtype-container[data-mode="preview"] .overtype-wrapper .overtype-preview h3, + .overtype-container[data-mode="preview"] .overtype-wrapper .overtype-preview h4, + .overtype-container[data-mode="preview"] .overtype-wrapper .overtype-preview h5, + .overtype-container[data-mode="preview"] .overtype-wrapper .overtype-preview h6 { font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif !important; font-weight: 600 !important; margin: 0 !important; @@ -741,6 +765,21 @@ export function generateStyles(options = {}) { color: var(--preview-h3, var(--preview-h3-default)) !important; } + .overtype-container[data-mode="preview"] .overtype-wrapper .overtype-preview h4 { + font-size: 1em !important; + color: var(--preview-h4, var(--preview-h4-default)) !important; + } + + .overtype-container[data-mode="preview"] .overtype-wrapper .overtype-preview h5 { + font-size: 0.83em !important; + color: var(--preview-h5, var(--preview-h5-default)) !important; + } + + .overtype-container[data-mode="preview"] .overtype-wrapper .overtype-preview h6 { + font-size: 0.67em !important; + color: var(--preview-h6, var(--preview-h6-default)) !important; + } + /* Lists - restore list styling in preview mode */ .overtype-container[data-mode="preview"] .overtype-wrapper .overtype-preview ul { display: block !important; diff --git a/src/themes.js b/src/themes.js index 50d30b9..52207f1 100644 --- a/src/themes.js +++ b/src/themes.js @@ -17,6 +17,9 @@ export const solar = { h1: '#f95738', // Tomato - h1 headers h2: '#ee964b', // Sandy Brown - h2 headers h3: '#3d8a51', // Forest green - h3 headers + h4: '#0d3b66', // Yale Blue - h4 headers + h5: '#5a7a9b', // Muted blue - h5 headers + h6: '#999999', // Gray - h6 headers strong: '#ee964b', // Sandy Brown - bold text em: '#f95738', // Tomato - italic text del: '#ee964b', // Sandy Brown - deleted text (same as strong) @@ -46,6 +49,9 @@ export const solar = { h1: 'inherit', h2: 'inherit', h3: 'inherit', + h4: 'inherit', + h5: 'inherit', + h6: 'inherit', strong: 'inherit', em: 'inherit', link: '#0d3b66', @@ -71,6 +77,9 @@ export const cave = { h1: '#d4a5ff', // Rich lavender - h1 headers h2: '#f6ae2d', // Hunyadi Yellow - h2 headers h3: '#9fcfec', // Brighter blue - h3 headers + h4: '#c5dde8', // Light blue-gray - h4 headers + h5: '#9fcfec', // Brighter blue - h5 headers + h6: '#7a8c98', // Muted gray-blue - h6 headers strong: '#f6ae2d', // Hunyadi Yellow - bold text em: '#9fcfec', // Brighter blue - italic text del: '#f6ae2d', // Hunyadi Yellow - deleted text (same as strong) @@ -100,6 +109,9 @@ export const cave = { h1: 'inherit', h2: 'inherit', h3: 'inherit', + h4: 'inherit', + h5: 'inherit', + h6: 'inherit', strong: 'inherit', em: 'inherit', link: '#9fcfec', diff --git a/src/toolbar-buttons.js b/src/toolbar-buttons.js index c3e8514..5bd372b 100644 --- a/src/toolbar-buttons.js +++ b/src/toolbar-buttons.js @@ -38,6 +38,18 @@ export const toolbarButtons = { } }, + strikethrough: { + name: 'strikethrough', + actionId: 'toggleStrikethrough', + icon: icons.strikethroughIcon, + title: 'Strikethrough', + isActive: ({ activeFormats }) => activeFormats.includes('strikethrough'), + action: ({ editor }) => { + markdownActions.toggleStrikethrough(editor.textarea); + editor.textarea.dispatchEvent(new Event('input', { bubbles: true })); + } + }, + code: { name: 'code', actionId: 'toggleCode', @@ -102,6 +114,42 @@ export const toolbarButtons = { } }, + h4: { + name: 'h4', + actionId: 'toggleH4', + icon: icons.h4Icon, + title: 'Heading 4', + isActive: ({ activeFormats }) => activeFormats.includes('header-4'), + action: ({ editor }) => { + markdownActions.toggleH4(editor.textarea); + editor.textarea.dispatchEvent(new Event('input', { bubbles: true })); + } + }, + + h5: { + name: 'h5', + actionId: 'toggleH5', + icon: icons.h5Icon, + title: 'Heading 5', + isActive: ({ activeFormats }) => activeFormats.includes('header-5'), + action: ({ editor }) => { + markdownActions.toggleH5(editor.textarea); + editor.textarea.dispatchEvent(new Event('input', { bubbles: true })); + } + }, + + h6: { + name: 'h6', + actionId: 'toggleH6', + icon: icons.h6Icon, + title: 'Heading 6', + isActive: ({ activeFormats }) => activeFormats.includes('header-6'), + action: ({ editor }) => { + markdownActions.toggleH6(editor.textarea); + editor.textarea.dispatchEvent(new Event('input', { bubbles: true })); + } + }, + bulletList: { name: 'bulletList', actionId: 'toggleBulletList', @@ -191,6 +239,7 @@ export const toolbarButtons = { export const defaultToolbarButtons = [ toolbarButtons.bold, toolbarButtons.italic, + toolbarButtons.strikethrough, toolbarButtons.code, toolbarButtons.separator, toolbarButtons.link, diff --git a/test/overtype.test.js b/test/overtype.test.js index 1a5f87c..1580ab8 100644 --- a/test/overtype.test.js +++ b/test/overtype.test.js @@ -51,7 +51,10 @@ console.log('\nšŸ“ Parser Tests\n'); { input: '# Title', expected: '

# Title

' }, { input: '## Subtitle', expected: '

## Subtitle

' }, { input: '### Section', expected: '

### Section

' }, - { input: '#### Too Deep', expected: '
#### Too Deep
' } + { input: '#### Detail', expected: '

#### Detail

' }, + { input: '##### Fine Print', expected: '
##### Fine Print
' }, + { input: '###### Caption', expected: '
###### Caption
' }, + { input: '####### Too Deep', expected: '
####### Too Deep
' } ]; tests.forEach(test => { diff --git a/test/test-types.ts b/test/test-types.ts index 91eaca3..de85383 100644 --- a/test/test-types.ts +++ b/test/test-types.ts @@ -1,6 +1,13 @@ // Test file to verify TypeScript definitions work correctly // Run: npx tsc --noEmit test-types.ts -import OverType, { Theme, Options, Stats, OverType as OverTypeInstance } from '../src/overtype'; +import OverType, { + Theme, + Options, + Stats, + OverType as OverTypeInstance, + markdownActions, + toolbarButtons +} from '../src/overtype'; // Test basic initialization - constructor returns array const editors1: OverTypeInstance[] = new OverType('#editor'); @@ -93,6 +100,9 @@ const customTheme: Theme = { h1: '#000000', h2: '#111111', h3: '#222222', + h4: '#333333', + h5: '#444444', + h6: '#555555', strong: '#444444', em: '#555555', link: '#0066cc', @@ -114,6 +124,21 @@ const customTheme: Theme = { }; OverType.setTheme(customTheme); +// Test formatting additions exposed from markdown-actions and toolbar buttons +const formattingTextarea = document.createElement('textarea'); +markdownActions.toggleStrikethrough(formattingTextarea); +markdownActions.toggleH4(formattingTextarea); +markdownActions.toggleH5(formattingTextarea); +markdownActions.toggleH6(formattingTextarea); + +const formattingButtons = [ + toolbarButtons.strikethrough, + toolbarButtons.h4, + toolbarButtons.h5, + toolbarButtons.h6 +]; +formattingButtons.forEach(button => console.log(button.name)); + // Test accessing built-in themes const solarTheme: Theme = OverType.themes.solar; const caveTheme: Theme = OverType.themes.cave;