@semantic-components/editor 0.63.0 → 0.64.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/esm2022/index.js +2 -0
- package/esm2022/index.js.map +1 -0
- package/esm2022/lib/components/editor/editor-align-center-button.js +38 -0
- package/esm2022/lib/components/editor/editor-align-center-button.js.map +1 -0
- package/esm2022/lib/components/editor/editor-align-justify-button.js +38 -0
- package/esm2022/lib/components/editor/editor-align-justify-button.js.map +1 -0
- package/esm2022/lib/components/editor/editor-align-left-button.js +37 -0
- package/esm2022/lib/components/editor/editor-align-left-button.js.map +1 -0
- package/esm2022/lib/components/editor/editor-align-right-button.js +37 -0
- package/esm2022/lib/components/editor/editor-align-right-button.js.map +1 -0
- package/esm2022/lib/components/editor/editor-blockquote-button.js +37 -0
- package/esm2022/lib/components/editor/editor-blockquote-button.js.map +1 -0
- package/esm2022/lib/components/editor/editor-bold-button.js +37 -0
- package/esm2022/lib/components/editor/editor-bold-button.js.map +1 -0
- package/esm2022/lib/components/editor/editor-bullet-list-button.js +37 -0
- package/esm2022/lib/components/editor/editor-bullet-list-button.js.map +1 -0
- package/esm2022/lib/components/editor/editor-char-count.js +40 -0
- package/esm2022/lib/components/editor/editor-char-count.js.map +1 -0
- package/esm2022/lib/components/editor/editor-clear-formatting-button.js +36 -0
- package/esm2022/lib/components/editor/editor-clear-formatting-button.js.map +1 -0
- package/esm2022/lib/components/editor/editor-code-button.js +46 -0
- package/esm2022/lib/components/editor/editor-code-button.js.map +1 -0
- package/esm2022/lib/components/editor/editor-content.js +78 -0
- package/esm2022/lib/components/editor/editor-content.js.map +1 -0
- package/esm2022/lib/components/editor/editor-count.js +27 -0
- package/esm2022/lib/components/editor/editor-count.js.map +1 -0
- package/esm2022/lib/components/editor/editor-footer.js +27 -0
- package/esm2022/lib/components/editor/editor-footer.js.map +1 -0
- package/esm2022/lib/components/editor/editor-header.js +27 -0
- package/esm2022/lib/components/editor/editor-header.js.map +1 -0
- package/esm2022/lib/components/editor/editor-heading-select.js +48 -0
- package/esm2022/lib/components/editor/editor-heading-select.js.map +1 -0
- package/esm2022/lib/components/editor/editor-horizontal-rule-button.js +36 -0
- package/esm2022/lib/components/editor/editor-horizontal-rule-button.js.map +1 -0
- package/esm2022/lib/components/editor/editor-italic-button.js +37 -0
- package/esm2022/lib/components/editor/editor-italic-button.js.map +1 -0
- package/esm2022/lib/components/editor/editor-link-button.js +52 -0
- package/esm2022/lib/components/editor/editor-link-button.js.map +1 -0
- package/esm2022/lib/components/editor/editor-numbered-list-button.js +37 -0
- package/esm2022/lib/components/editor/editor-numbered-list-button.js.map +1 -0
- package/esm2022/lib/components/editor/editor-redo-button.js +36 -0
- package/esm2022/lib/components/editor/editor-redo-button.js.map +1 -0
- package/esm2022/lib/components/editor/editor-separator.js +23 -0
- package/esm2022/lib/components/editor/editor-separator.js.map +1 -0
- package/esm2022/lib/components/editor/editor-strikethrough-button.js +37 -0
- package/esm2022/lib/components/editor/editor-strikethrough-button.js.map +1 -0
- package/esm2022/lib/components/editor/editor-toolbar-group.js +27 -0
- package/esm2022/lib/components/editor/editor-toolbar-group.js.map +1 -0
- package/esm2022/lib/components/editor/editor-toolbar.js +29 -0
- package/esm2022/lib/components/editor/editor-toolbar.js.map +1 -0
- package/esm2022/lib/components/editor/editor-underline-button.js +37 -0
- package/esm2022/lib/components/editor/editor-underline-button.js.map +1 -0
- package/esm2022/lib/components/editor/editor-undo-button.js +36 -0
- package/esm2022/lib/components/editor/editor-undo-button.js.map +1 -0
- package/esm2022/lib/components/editor/editor-word-count.js +42 -0
- package/esm2022/lib/components/editor/editor-word-count.js.map +1 -0
- package/esm2022/lib/components/editor/editor.js +197 -0
- package/esm2022/lib/components/editor/editor.js.map +1 -0
- package/{src/lib/components/editor/index.ts → esm2022/lib/components/editor/index.js} +1 -8
- package/esm2022/lib/components/editor/index.js.map +1 -0
- package/esm2022/lib/components/index.js +2 -0
- package/esm2022/lib/components/index.js.map +1 -0
- package/esm2022/semantic-components-editor.js +5 -0
- package/esm2022/semantic-components-editor.js.map +1 -0
- package/lib/components/editor/editor-align-center-button.d.ts +9 -0
- package/lib/components/editor/editor-align-justify-button.d.ts +9 -0
- package/lib/components/editor/editor-align-left-button.d.ts +9 -0
- package/lib/components/editor/editor-align-right-button.d.ts +9 -0
- package/lib/components/editor/editor-blockquote-button.d.ts +9 -0
- package/lib/components/editor/editor-bold-button.d.ts +9 -0
- package/lib/components/editor/editor-bullet-list-button.d.ts +9 -0
- package/lib/components/editor/editor-char-count.d.ts +10 -0
- package/lib/components/editor/editor-clear-formatting-button.d.ts +9 -0
- package/lib/components/editor/editor-code-button.d.ts +9 -0
- package/lib/components/editor/editor-content.d.ts +18 -0
- package/lib/components/editor/editor-count.d.ts +7 -0
- package/lib/components/editor/editor-footer.d.ts +7 -0
- package/lib/components/editor/editor-header.d.ts +7 -0
- package/lib/components/editor/editor-heading-select.d.ts +9 -0
- package/lib/components/editor/editor-horizontal-rule-button.d.ts +9 -0
- package/lib/components/editor/editor-italic-button.d.ts +9 -0
- package/lib/components/editor/editor-link-button.d.ts +9 -0
- package/lib/components/editor/editor-numbered-list-button.d.ts +9 -0
- package/lib/components/editor/editor-redo-button.d.ts +9 -0
- package/lib/components/editor/editor-separator.d.ts +7 -0
- package/lib/components/editor/editor-strikethrough-button.d.ts +9 -0
- package/lib/components/editor/editor-toolbar-group.d.ts +7 -0
- package/lib/components/editor/editor-toolbar.d.ts +7 -0
- package/lib/components/editor/editor-underline-button.d.ts +9 -0
- package/lib/components/editor/editor-undo-button.d.ts +9 -0
- package/lib/components/editor/editor-word-count.d.ts +10 -0
- package/lib/components/editor/editor.d.ts +29 -0
- package/lib/components/editor/index.d.ts +28 -0
- package/package.json +15 -3
- package/semantic-components-editor.d.ts +5 -0
- package/eslint.config.mjs +0 -48
- package/ng-package.json +0 -8
- package/project.json +0 -28
- package/src/lib/components/editor/README.md +0 -354
- package/src/lib/components/editor/editor-align-center-button.ts +0 -45
- package/src/lib/components/editor/editor-align-justify-button.ts +0 -45
- package/src/lib/components/editor/editor-align-left-button.ts +0 -44
- package/src/lib/components/editor/editor-align-right-button.ts +0 -44
- package/src/lib/components/editor/editor-blockquote-button.ts +0 -44
- package/src/lib/components/editor/editor-bold-button.ts +0 -44
- package/src/lib/components/editor/editor-bullet-list-button.ts +0 -44
- package/src/lib/components/editor/editor-char-count.ts +0 -42
- package/src/lib/components/editor/editor-clear-formatting-button.ts +0 -42
- package/src/lib/components/editor/editor-code-button.ts +0 -52
- package/src/lib/components/editor/editor-content.ts +0 -107
- package/src/lib/components/editor/editor-count.ts +0 -28
- package/src/lib/components/editor/editor-footer.ts +0 -30
- package/src/lib/components/editor/editor-header.ts +0 -27
- package/src/lib/components/editor/editor-heading-select.ts +0 -48
- package/src/lib/components/editor/editor-horizontal-rule-button.ts +0 -42
- package/src/lib/components/editor/editor-italic-button.ts +0 -44
- package/src/lib/components/editor/editor-link-button.ts +0 -58
- package/src/lib/components/editor/editor-numbered-list-button.ts +0 -44
- package/src/lib/components/editor/editor-redo-button.ts +0 -42
- package/src/lib/components/editor/editor-separator.ts +0 -25
- package/src/lib/components/editor/editor-strikethrough-button.ts +0 -44
- package/src/lib/components/editor/editor-toolbar-group.ts +0 -27
- package/src/lib/components/editor/editor-toolbar.ts +0 -32
- package/src/lib/components/editor/editor-underline-button.ts +0 -44
- package/src/lib/components/editor/editor-undo-button.ts +0 -42
- package/src/lib/components/editor/editor-word-count.ts +0 -43
- package/src/lib/components/editor/editor.ts +0 -211
- package/tsconfig.json +0 -28
- package/tsconfig.lib.json +0 -12
- package/tsconfig.lib.prod.json +0 -7
- /package/{src/index.ts → index.d.ts} +0 -0
- /package/{src/lib/components/index.ts → lib/components/index.d.ts} +0 -0
|
@@ -1,354 +0,0 @@
|
|
|
1
|
-
# Editor Component
|
|
2
|
-
|
|
3
|
-
Composable WYSIWYG editor built with [Tiptap](https://tiptap.dev/), a headless rich-text editor framework powered by ProseMirror.
|
|
4
|
-
|
|
5
|
-
## Installation
|
|
6
|
-
|
|
7
|
-
The editor component requires Tiptap packages as peer dependencies. Install them alongside `@semantic-components/ui`:
|
|
8
|
-
|
|
9
|
-
```bash
|
|
10
|
-
npm install @semantic-components/ui \
|
|
11
|
-
@tiptap/core \
|
|
12
|
-
@tiptap/starter-kit \
|
|
13
|
-
@tiptap/extension-text-align \
|
|
14
|
-
@tiptap/extension-placeholder
|
|
15
|
-
```
|
|
16
|
-
|
|
17
|
-
Or if you already have `@semantic-components/ui` installed:
|
|
18
|
-
|
|
19
|
-
```bash
|
|
20
|
-
npm install @tiptap/core @tiptap/starter-kit @tiptap/extension-text-align @tiptap/extension-placeholder
|
|
21
|
-
```
|
|
22
|
-
|
|
23
|
-
**Note**: Link and Underline extensions are included in `@tiptap/starter-kit` v3, so no need to install them separately.
|
|
24
|
-
|
|
25
|
-
## Features
|
|
26
|
-
|
|
27
|
-
- **Rich Text Editing**: Bold, italic, underline, strikethrough, headings, lists, blockquotes, code, links, and more
|
|
28
|
-
- **Keyboard Shortcuts**: Built-in shortcuts for all formatting commands (Ctrl+B, Ctrl+I, Ctrl+U, Ctrl+Z, Ctrl+Y, etc.)
|
|
29
|
-
- **Customizable Toolbar**: Composable architecture allows full control over toolbar layout
|
|
30
|
-
- **Two-Way Binding**: Seamless synchronization with Angular signals
|
|
31
|
-
- **Accessibility**: Full keyboard navigation and ARIA support
|
|
32
|
-
- **Extensible**: Built on Tiptap, allowing custom extensions and commands
|
|
33
|
-
|
|
34
|
-
## Implementation
|
|
35
|
-
|
|
36
|
-
The editor is powered by [Tiptap](https://tiptap.dev/), a headless rich-text editor framework built on ProseMirror. This provides:
|
|
37
|
-
|
|
38
|
-
- **Robust editing**: Industry-standard editing engine
|
|
39
|
-
- **Extensibility**: Add custom extensions and commands
|
|
40
|
-
- **Keyboard shortcuts**: Built-in shortcuts for all formatting
|
|
41
|
-
- **Better browser support**: Works consistently across modern browsers
|
|
42
|
-
- **Future-ready**: Foundation for collaboration, mentions, slash commands, etc.
|
|
43
|
-
|
|
44
|
-
### Extensions Included
|
|
45
|
-
|
|
46
|
-
The editor comes pre-configured with these Tiptap extensions:
|
|
47
|
-
|
|
48
|
-
- **StarterKit**: Bold, Italic, Strike, Underline, Code, Heading, Lists, Blockquote, History, HardBreak, Paragraph, Text, Link
|
|
49
|
-
- Link configured with: `openOnClick: false`, `target="_blank"`, `rel="noopener noreferrer"`
|
|
50
|
-
- **TextAlign**: Text alignment (left, center, right, justify)
|
|
51
|
-
- **Placeholder**: Customizable placeholder text
|
|
52
|
-
|
|
53
|
-
### Bundle Size
|
|
54
|
-
|
|
55
|
-
- **Total**: ~30 KB (minified)
|
|
56
|
-
- **Gzipped**: ~10 KB
|
|
57
|
-
|
|
58
|
-
This is an acceptable trade-off for the significant improvements in reliability, extensibility, and developer experience.
|
|
59
|
-
|
|
60
|
-
## Basic Usage
|
|
61
|
-
|
|
62
|
-
```typescript
|
|
63
|
-
import { Component, signal } from '@angular/core';
|
|
64
|
-
import { ScEditor, ScEditorContent, ScEditorToolbar, ScEditorToolbarGroup, ScEditorBoldButton, ScEditorItalicButton, ScEditorUnderlineButton, ScEditorFooter, ScEditorCount, ScEditorWordCount, ScEditorCharCount } from '@semantic-components/ui-lab';
|
|
65
|
-
|
|
66
|
-
@Component({
|
|
67
|
-
selector: 'app-example',
|
|
68
|
-
imports: [ScEditor, ScEditorContent, ScEditorToolbar, ScEditorToolbarGroup, ScEditorBoldButton, ScEditorItalicButton, ScEditorUnderlineButton, ScEditorFooter, ScEditorCount, ScEditorWordCount, ScEditorCharCount],
|
|
69
|
-
template: `
|
|
70
|
-
<div sc-editor class="border rounded-lg overflow-hidden">
|
|
71
|
-
<div sc-editor-toolbar>
|
|
72
|
-
<div sc-editor-toolbar-group>
|
|
73
|
-
<button sc-editor-bold>Bold</button>
|
|
74
|
-
<button sc-editor-italic>Italic</button>
|
|
75
|
-
<button sc-editor-underline>Underline</button>
|
|
76
|
-
</div>
|
|
77
|
-
</div>
|
|
78
|
-
|
|
79
|
-
<div sc-editor-content [(value)]="content" placeholder="Start typing..."></div>
|
|
80
|
-
|
|
81
|
-
<div sc-editor-footer>
|
|
82
|
-
<div sc-editor-count>
|
|
83
|
-
<span sc-editor-word-count></span>
|
|
84
|
-
<span sc-editor-char-count></span>
|
|
85
|
-
</div>
|
|
86
|
-
</div>
|
|
87
|
-
</div>
|
|
88
|
-
`,
|
|
89
|
-
})
|
|
90
|
-
export class ExampleComponent {
|
|
91
|
-
readonly content = signal('');
|
|
92
|
-
}
|
|
93
|
-
```
|
|
94
|
-
|
|
95
|
-
## API
|
|
96
|
-
|
|
97
|
-
### ScEditor (Directive)
|
|
98
|
-
|
|
99
|
-
Root directive that manages editor state and provides context to child components.
|
|
100
|
-
|
|
101
|
-
**Selector**: `[sc-editor]`
|
|
102
|
-
|
|
103
|
-
**Inputs**:
|
|
104
|
-
|
|
105
|
-
- `disabled: boolean` - Disables the editor
|
|
106
|
-
- `readonly: boolean` - Makes the editor read-only
|
|
107
|
-
|
|
108
|
-
**Signals** (accessed via `inject(SC_EDITOR)`):
|
|
109
|
-
|
|
110
|
-
- `isBold()` - Whether bold is active
|
|
111
|
-
- `isItalic()` - Whether italic is active
|
|
112
|
-
- `isUnderline()` - Whether underline is active
|
|
113
|
-
- `isStrikethrough()` - Whether strikethrough is active
|
|
114
|
-
- `isOrderedList()` - Whether ordered list is active
|
|
115
|
-
- `isUnorderedList()` - Whether unordered list is active
|
|
116
|
-
- `isBlockquote()` - Whether blockquote is active
|
|
117
|
-
- `alignment()` - Current text alignment ('left' | 'center' | 'right' | 'justify')
|
|
118
|
-
- `currentHeading()` - Current heading level ('p' | 'h1' | 'h2' | 'h3' | 'h4' | 'h5' | 'h6')
|
|
119
|
-
- `canUndo()` - Whether undo is available
|
|
120
|
-
- `canRedo()` - Whether redo is available
|
|
121
|
-
- `editorInstance()` - The Tiptap Editor instance (for advanced usage)
|
|
122
|
-
|
|
123
|
-
**Methods**:
|
|
124
|
-
|
|
125
|
-
- `execCommand(command: string, value?: string)` - Execute a formatting command
|
|
126
|
-
- `updateToolbarState()` - Update toolbar button states (called automatically)
|
|
127
|
-
- `initializeEditor(element: HTMLElement, content: string, placeholder: string)` - Initialize Tiptap (called automatically)
|
|
128
|
-
- `destroyEditor()` - Cleanup Tiptap instance (called automatically)
|
|
129
|
-
|
|
130
|
-
### ScEditorContent (Component)
|
|
131
|
-
|
|
132
|
-
Content area where text editing happens.
|
|
133
|
-
|
|
134
|
-
**Selector**: `div[sc-editor-content]`
|
|
135
|
-
|
|
136
|
-
**Inputs**:
|
|
137
|
-
|
|
138
|
-
- `value: string` - Two-way bindable HTML content (use with `[(value)]`)
|
|
139
|
-
- `placeholder: string` - Placeholder text (default: "Start typing...")
|
|
140
|
-
- `ariaLabel: string` - ARIA label (default: "Rich text editor")
|
|
141
|
-
- `class: string` - Additional CSS classes (use Tailwind utilities for sizing, e.g., `min-h-[300px] max-h-[500px]`)
|
|
142
|
-
|
|
143
|
-
**Outputs**:
|
|
144
|
-
|
|
145
|
-
- `focus` - Emitted when editor gains focus
|
|
146
|
-
- `blur` - Emitted when editor loses focus
|
|
147
|
-
|
|
148
|
-
### Toolbar Components
|
|
149
|
-
|
|
150
|
-
All toolbar buttons inject `SC_EDITOR` and call the appropriate commands.
|
|
151
|
-
|
|
152
|
-
**Available Buttons**:
|
|
153
|
-
|
|
154
|
-
- `ScEditorBoldButton` - Toggle bold
|
|
155
|
-
- `ScEditorItalicButton` - Toggle italic
|
|
156
|
-
- `ScEditorUnderlineButton` - Toggle underline
|
|
157
|
-
- `ScEditorStrikethroughButton` - Toggle strikethrough
|
|
158
|
-
- `ScEditorCodeButton` - Toggle inline code
|
|
159
|
-
- `ScEditorLinkButton` - Insert/edit link (prompts for URL)
|
|
160
|
-
- `ScEditorBulletListButton` - Toggle bullet list
|
|
161
|
-
- `ScEditorNumberedListButton` - Toggle numbered list
|
|
162
|
-
- `ScEditorBlockquoteButton` - Toggle blockquote
|
|
163
|
-
- `ScEditorAlignLeftButton` - Align left
|
|
164
|
-
- `ScEditorAlignCenterButton` - Align center
|
|
165
|
-
- `ScEditorAlignRightButton` - Align right
|
|
166
|
-
- `ScEditorAlignJustifyButton` - Align justify
|
|
167
|
-
- `ScEditorUndoButton` - Undo last action
|
|
168
|
-
- `ScEditorRedoButton` - Redo last action
|
|
169
|
-
- `ScEditorClearFormattingButton` - Remove all formatting
|
|
170
|
-
|
|
171
|
-
**Containers**:
|
|
172
|
-
|
|
173
|
-
- `ScEditorToolbar` - Toolbar container
|
|
174
|
-
- `ScEditorToolbarGroup` - Group related buttons
|
|
175
|
-
- `ScEditorSeparator` - Visual separator between groups
|
|
176
|
-
- `ScEditorFooter` - Footer container
|
|
177
|
-
- `ScEditorHeader` - Header container
|
|
178
|
-
|
|
179
|
-
**Utilities**:
|
|
180
|
-
|
|
181
|
-
- `ScEditorCount` - Container for count components (uses `<ng-content>`, allows custom layout)
|
|
182
|
-
- `ScEditorWordCount` - Display word count (e.g., "42 words")
|
|
183
|
-
- `ScEditorCharCount` - Display character count (e.g., "256 characters")
|
|
184
|
-
- `ScEditorHeadingSelect` - Dropdown to select heading level
|
|
185
|
-
|
|
186
|
-
## Keyboard Shortcuts
|
|
187
|
-
|
|
188
|
-
All standard keyboard shortcuts are handled by Tiptap:
|
|
189
|
-
|
|
190
|
-
- **Ctrl+B** / **Cmd+B** - Bold
|
|
191
|
-
- **Ctrl+I** / **Cmd+I** - Italic
|
|
192
|
-
- **Ctrl+U** / **Cmd+U** - Underline
|
|
193
|
-
- **Ctrl+Z** / **Cmd+Z** - Undo
|
|
194
|
-
- **Ctrl+Shift+Z** / **Cmd+Shift+Z** - Redo
|
|
195
|
-
- **Ctrl+Y** / **Cmd+Y** - Redo
|
|
196
|
-
- **Ctrl+E** / **Cmd+E** - Inline code
|
|
197
|
-
|
|
198
|
-
## Advanced Usage
|
|
199
|
-
|
|
200
|
-
### Accessing the Tiptap Editor Instance
|
|
201
|
-
|
|
202
|
-
For advanced use cases, you can access the underlying Tiptap editor:
|
|
203
|
-
|
|
204
|
-
```typescript
|
|
205
|
-
import { inject, effect } from '@angular/core';
|
|
206
|
-
import { SC_EDITOR } from '@semantic-components/ui-lab';
|
|
207
|
-
|
|
208
|
-
export class MyComponent {
|
|
209
|
-
readonly editor = inject(SC_EDITOR);
|
|
210
|
-
|
|
211
|
-
constructor() {
|
|
212
|
-
effect(() => {
|
|
213
|
-
const instance = this.editor.editorInstance();
|
|
214
|
-
if (instance) {
|
|
215
|
-
// Access Tiptap API
|
|
216
|
-
console.log(instance.getJSON()); // Get content as JSON
|
|
217
|
-
console.log(instance.getHTML()); // Get content as HTML
|
|
218
|
-
console.log(instance.getText()); // Get plain text
|
|
219
|
-
|
|
220
|
-
// Execute custom commands
|
|
221
|
-
instance.chain().focus().toggleBold().run();
|
|
222
|
-
}
|
|
223
|
-
});
|
|
224
|
-
}
|
|
225
|
-
}
|
|
226
|
-
```
|
|
227
|
-
|
|
228
|
-
### Custom Commands
|
|
229
|
-
|
|
230
|
-
You can execute any Tiptap command via the editor instance:
|
|
231
|
-
|
|
232
|
-
```typescript
|
|
233
|
-
this.editor.editorInstance()?.chain().focus().setHeading({ level: 2 }).insertContent('Hello world!').run();
|
|
234
|
-
```
|
|
235
|
-
|
|
236
|
-
## Migration from execCommand
|
|
237
|
-
|
|
238
|
-
Previous versions used the deprecated `document.execCommand()` API. The current version uses Tiptap internally while maintaining the same external API for backwards compatibility.
|
|
239
|
-
|
|
240
|
-
**Breaking Changes**: None! The external API remains identical.
|
|
241
|
-
|
|
242
|
-
**Advanced Users**: If you directly accessed `contentElement()` for DOM manipulation, you should now use `editorInstance()` to interact with the editor via Tiptap's API.
|
|
243
|
-
|
|
244
|
-
## Future Enhancements
|
|
245
|
-
|
|
246
|
-
Built on Tiptap, these features can be easily added in the future:
|
|
247
|
-
|
|
248
|
-
1. **Collaboration** - Real-time co-editing with `@tiptap/extension-collaboration`
|
|
249
|
-
2. **Mentions** - @-mentions with autocomplete
|
|
250
|
-
3. **Slash Commands** - `/` for quick formatting
|
|
251
|
-
4. **Tables** - Rich table support
|
|
252
|
-
5. **Images** - Drag-and-drop image upload
|
|
253
|
-
6. **Custom Extensions** - Domain-specific formatting
|
|
254
|
-
7. **Markdown Support** - Input/output markdown
|
|
255
|
-
8. **Character Limits** - Built-in extension for content limits
|
|
256
|
-
|
|
257
|
-
## Styling
|
|
258
|
-
|
|
259
|
-
The editor uses Tailwind CSS with CSS variables for theming. All styles respect the current theme (light/dark mode) and use OKLCH color format.
|
|
260
|
-
|
|
261
|
-
**CSS Variables Used**:
|
|
262
|
-
|
|
263
|
-
- `--border` - Border color (blockquote, hr)
|
|
264
|
-
- `--muted` - Muted background (code, pre)
|
|
265
|
-
- `--muted-foreground` - Muted text (placeholder, blockquote)
|
|
266
|
-
- `--primary` - Primary color (links)
|
|
267
|
-
|
|
268
|
-
Colors are defined in OKLCH format and automatically adapt to light/dark themes.
|
|
269
|
-
|
|
270
|
-
**Customization**:
|
|
271
|
-
|
|
272
|
-
```html
|
|
273
|
-
<!-- Custom font and text size -->
|
|
274
|
-
<div sc-editor-content class="font-serif text-lg"></div>
|
|
275
|
-
|
|
276
|
-
<!-- Custom height constraints -->
|
|
277
|
-
<div sc-editor-content class="min-h-[300px] max-h-[600px]"></div>
|
|
278
|
-
|
|
279
|
-
<!-- Remove default padding -->
|
|
280
|
-
<div sc-editor-content class="p-0"></div>
|
|
281
|
-
|
|
282
|
-
<!-- Default: Both counts with standard layout -->
|
|
283
|
-
<div sc-editor-footer>
|
|
284
|
-
<div sc-editor-count>
|
|
285
|
-
<span sc-editor-word-count></span>
|
|
286
|
-
<span sc-editor-char-count></span>
|
|
287
|
-
</div>
|
|
288
|
-
</div>
|
|
289
|
-
|
|
290
|
-
<!-- Only word count -->
|
|
291
|
-
<div sc-editor-footer>
|
|
292
|
-
<div sc-editor-count>
|
|
293
|
-
<span sc-editor-word-count></span>
|
|
294
|
-
</div>
|
|
295
|
-
</div>
|
|
296
|
-
|
|
297
|
-
<!-- Only character count -->
|
|
298
|
-
<div sc-editor-footer>
|
|
299
|
-
<div sc-editor-count>
|
|
300
|
-
<span sc-editor-char-count></span>
|
|
301
|
-
</div>
|
|
302
|
-
</div>
|
|
303
|
-
|
|
304
|
-
<!-- Custom separator and styling -->
|
|
305
|
-
<div sc-editor-footer>
|
|
306
|
-
<div sc-editor-count>
|
|
307
|
-
<span sc-editor-word-count class="text-blue-600"></span>
|
|
308
|
-
<span class="text-muted-foreground">•</span>
|
|
309
|
-
<span sc-editor-char-count class="text-green-600"></span>
|
|
310
|
-
</div>
|
|
311
|
-
</div>
|
|
312
|
-
|
|
313
|
-
<!-- Footer with additional content -->
|
|
314
|
-
<div sc-editor-footer>
|
|
315
|
-
<button class="text-sm text-primary hover:underline">Save draft</button>
|
|
316
|
-
<div sc-editor-count>
|
|
317
|
-
<span sc-editor-word-count></span>
|
|
318
|
-
<span sc-editor-char-count></span>
|
|
319
|
-
</div>
|
|
320
|
-
</div>
|
|
321
|
-
|
|
322
|
-
<!-- Without using ScEditorCount container -->
|
|
323
|
-
<div sc-editor-footer class="justify-between">
|
|
324
|
-
<div class="flex gap-2">
|
|
325
|
-
<span sc-editor-word-count class="text-xs"></span>
|
|
326
|
-
<span sc-editor-char-count class="text-xs"></span>
|
|
327
|
-
</div>
|
|
328
|
-
<button>Submit</button>
|
|
329
|
-
</div>
|
|
330
|
-
```
|
|
331
|
-
|
|
332
|
-
Default classes applied: `block outline-none overflow-y-auto min-h-[150px] max-h-[400px] p-4 prose prose-sm max-w-none dark:prose-invert`
|
|
333
|
-
|
|
334
|
-
## Accessibility
|
|
335
|
-
|
|
336
|
-
The editor follows WCAG AA standards:
|
|
337
|
-
|
|
338
|
-
- Full keyboard navigation
|
|
339
|
-
- ARIA labels on all buttons
|
|
340
|
-
- Focus management
|
|
341
|
-
- Screen reader support
|
|
342
|
-
- High contrast mode support
|
|
343
|
-
|
|
344
|
-
## Browser Support
|
|
345
|
-
|
|
346
|
-
Works in all modern browsers that support ES2015+ and ProseMirror:
|
|
347
|
-
|
|
348
|
-
- Chrome/Edge 90+
|
|
349
|
-
- Firefox 88+
|
|
350
|
-
- Safari 14+
|
|
351
|
-
|
|
352
|
-
## License
|
|
353
|
-
|
|
354
|
-
MIT
|
|
@@ -1,45 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
Component,
|
|
3
|
-
ChangeDetectionStrategy,
|
|
4
|
-
computed,
|
|
5
|
-
input,
|
|
6
|
-
inject,
|
|
7
|
-
ViewEncapsulation,
|
|
8
|
-
} from '@angular/core';
|
|
9
|
-
import { cn } from '@semantic-components/ui';
|
|
10
|
-
import { SC_EDITOR } from './editor';
|
|
11
|
-
|
|
12
|
-
@Component({
|
|
13
|
-
selector: 'button[sc-editor-align-center]',
|
|
14
|
-
template: `
|
|
15
|
-
<ng-content />
|
|
16
|
-
`,
|
|
17
|
-
host: {
|
|
18
|
-
'data-slot': 'editor-align-center',
|
|
19
|
-
type: 'button',
|
|
20
|
-
'[class]': 'class()',
|
|
21
|
-
'[disabled]': 'editor.disabled()',
|
|
22
|
-
'[attr.aria-pressed]': 'editor.alignment() === "center"',
|
|
23
|
-
'[attr.title]': '"Align center"',
|
|
24
|
-
'(click)': 'onClick()',
|
|
25
|
-
},
|
|
26
|
-
encapsulation: ViewEncapsulation.None,
|
|
27
|
-
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
28
|
-
})
|
|
29
|
-
export class ScEditorAlignCenterButton {
|
|
30
|
-
readonly editor = inject(SC_EDITOR);
|
|
31
|
-
readonly classInput = input<string>('', { alias: 'class' });
|
|
32
|
-
|
|
33
|
-
protected readonly class = computed(() =>
|
|
34
|
-
cn(
|
|
35
|
-
'p-1.5 rounded hover:bg-accent disabled:opacity-50 [&_svg]:size-4',
|
|
36
|
-
this.editor.alignment() === 'center' &&
|
|
37
|
-
'bg-accent text-accent-foreground',
|
|
38
|
-
this.classInput(),
|
|
39
|
-
),
|
|
40
|
-
);
|
|
41
|
-
|
|
42
|
-
onClick(): void {
|
|
43
|
-
this.editor.execCommand('justifyCenter');
|
|
44
|
-
}
|
|
45
|
-
}
|
|
@@ -1,45 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
Component,
|
|
3
|
-
ChangeDetectionStrategy,
|
|
4
|
-
computed,
|
|
5
|
-
input,
|
|
6
|
-
inject,
|
|
7
|
-
ViewEncapsulation,
|
|
8
|
-
} from '@angular/core';
|
|
9
|
-
import { cn } from '@semantic-components/ui';
|
|
10
|
-
import { SC_EDITOR } from './editor';
|
|
11
|
-
|
|
12
|
-
@Component({
|
|
13
|
-
selector: 'button[sc-editor-align-justify]',
|
|
14
|
-
template: `
|
|
15
|
-
<ng-content />
|
|
16
|
-
`,
|
|
17
|
-
host: {
|
|
18
|
-
'data-slot': 'editor-align-justify',
|
|
19
|
-
type: 'button',
|
|
20
|
-
'[class]': 'class()',
|
|
21
|
-
'[disabled]': 'editor.disabled()',
|
|
22
|
-
'[attr.aria-pressed]': 'editor.alignment() === "justify"',
|
|
23
|
-
'[attr.title]': '"Justify"',
|
|
24
|
-
'(click)': 'onClick()',
|
|
25
|
-
},
|
|
26
|
-
encapsulation: ViewEncapsulation.None,
|
|
27
|
-
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
28
|
-
})
|
|
29
|
-
export class ScEditorAlignJustifyButton {
|
|
30
|
-
readonly editor = inject(SC_EDITOR);
|
|
31
|
-
readonly classInput = input<string>('', { alias: 'class' });
|
|
32
|
-
|
|
33
|
-
protected readonly class = computed(() =>
|
|
34
|
-
cn(
|
|
35
|
-
'p-1.5 rounded hover:bg-accent disabled:opacity-50 [&_svg]:size-4',
|
|
36
|
-
this.editor.alignment() === 'justify' &&
|
|
37
|
-
'bg-accent text-accent-foreground',
|
|
38
|
-
this.classInput(),
|
|
39
|
-
),
|
|
40
|
-
);
|
|
41
|
-
|
|
42
|
-
onClick(): void {
|
|
43
|
-
this.editor.execCommand('justifyFull');
|
|
44
|
-
}
|
|
45
|
-
}
|
|
@@ -1,44 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
Component,
|
|
3
|
-
ChangeDetectionStrategy,
|
|
4
|
-
computed,
|
|
5
|
-
input,
|
|
6
|
-
inject,
|
|
7
|
-
ViewEncapsulation,
|
|
8
|
-
} from '@angular/core';
|
|
9
|
-
import { cn } from '@semantic-components/ui';
|
|
10
|
-
import { SC_EDITOR } from './editor';
|
|
11
|
-
|
|
12
|
-
@Component({
|
|
13
|
-
selector: 'button[sc-editor-align-left]',
|
|
14
|
-
template: `
|
|
15
|
-
<ng-content />
|
|
16
|
-
`,
|
|
17
|
-
host: {
|
|
18
|
-
'data-slot': 'editor-align-left',
|
|
19
|
-
type: 'button',
|
|
20
|
-
'[class]': 'class()',
|
|
21
|
-
'[disabled]': 'editor.disabled()',
|
|
22
|
-
'[attr.aria-pressed]': 'editor.alignment() === "left"',
|
|
23
|
-
'[attr.title]': '"Align left"',
|
|
24
|
-
'(click)': 'onClick()',
|
|
25
|
-
},
|
|
26
|
-
encapsulation: ViewEncapsulation.None,
|
|
27
|
-
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
28
|
-
})
|
|
29
|
-
export class ScEditorAlignLeftButton {
|
|
30
|
-
readonly editor = inject(SC_EDITOR);
|
|
31
|
-
readonly classInput = input<string>('', { alias: 'class' });
|
|
32
|
-
|
|
33
|
-
protected readonly class = computed(() =>
|
|
34
|
-
cn(
|
|
35
|
-
'p-1.5 rounded hover:bg-accent disabled:opacity-50 [&_svg]:size-4',
|
|
36
|
-
this.editor.alignment() === 'left' && 'bg-accent text-accent-foreground',
|
|
37
|
-
this.classInput(),
|
|
38
|
-
),
|
|
39
|
-
);
|
|
40
|
-
|
|
41
|
-
onClick(): void {
|
|
42
|
-
this.editor.execCommand('justifyLeft');
|
|
43
|
-
}
|
|
44
|
-
}
|
|
@@ -1,44 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
Component,
|
|
3
|
-
ChangeDetectionStrategy,
|
|
4
|
-
computed,
|
|
5
|
-
input,
|
|
6
|
-
inject,
|
|
7
|
-
ViewEncapsulation,
|
|
8
|
-
} from '@angular/core';
|
|
9
|
-
import { cn } from '@semantic-components/ui';
|
|
10
|
-
import { SC_EDITOR } from './editor';
|
|
11
|
-
|
|
12
|
-
@Component({
|
|
13
|
-
selector: 'button[sc-editor-align-right]',
|
|
14
|
-
template: `
|
|
15
|
-
<ng-content />
|
|
16
|
-
`,
|
|
17
|
-
host: {
|
|
18
|
-
'data-slot': 'editor-align-right',
|
|
19
|
-
type: 'button',
|
|
20
|
-
'[class]': 'class()',
|
|
21
|
-
'[disabled]': 'editor.disabled()',
|
|
22
|
-
'[attr.aria-pressed]': 'editor.alignment() === "right"',
|
|
23
|
-
'[attr.title]': '"Align right"',
|
|
24
|
-
'(click)': 'onClick()',
|
|
25
|
-
},
|
|
26
|
-
encapsulation: ViewEncapsulation.None,
|
|
27
|
-
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
28
|
-
})
|
|
29
|
-
export class ScEditorAlignRightButton {
|
|
30
|
-
readonly editor = inject(SC_EDITOR);
|
|
31
|
-
readonly classInput = input<string>('', { alias: 'class' });
|
|
32
|
-
|
|
33
|
-
protected readonly class = computed(() =>
|
|
34
|
-
cn(
|
|
35
|
-
'p-1.5 rounded hover:bg-accent disabled:opacity-50 [&_svg]:size-4',
|
|
36
|
-
this.editor.alignment() === 'right' && 'bg-accent text-accent-foreground',
|
|
37
|
-
this.classInput(),
|
|
38
|
-
),
|
|
39
|
-
);
|
|
40
|
-
|
|
41
|
-
onClick(): void {
|
|
42
|
-
this.editor.execCommand('justifyRight');
|
|
43
|
-
}
|
|
44
|
-
}
|
|
@@ -1,44 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
Component,
|
|
3
|
-
ChangeDetectionStrategy,
|
|
4
|
-
computed,
|
|
5
|
-
input,
|
|
6
|
-
inject,
|
|
7
|
-
ViewEncapsulation,
|
|
8
|
-
} from '@angular/core';
|
|
9
|
-
import { cn } from '@semantic-components/ui';
|
|
10
|
-
import { SC_EDITOR } from './editor';
|
|
11
|
-
|
|
12
|
-
@Component({
|
|
13
|
-
selector: 'button[sc-editor-blockquote]',
|
|
14
|
-
template: `
|
|
15
|
-
<ng-content />
|
|
16
|
-
`,
|
|
17
|
-
host: {
|
|
18
|
-
'data-slot': 'editor-blockquote',
|
|
19
|
-
type: 'button',
|
|
20
|
-
'[class]': 'class()',
|
|
21
|
-
'[disabled]': 'editor.disabled()',
|
|
22
|
-
'[attr.aria-pressed]': 'editor.isBlockquote()',
|
|
23
|
-
'[attr.title]': '"Blockquote"',
|
|
24
|
-
'(click)': 'onClick()',
|
|
25
|
-
},
|
|
26
|
-
encapsulation: ViewEncapsulation.None,
|
|
27
|
-
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
28
|
-
})
|
|
29
|
-
export class ScEditorBlockquoteButton {
|
|
30
|
-
readonly editor = inject(SC_EDITOR);
|
|
31
|
-
readonly classInput = input<string>('', { alias: 'class' });
|
|
32
|
-
|
|
33
|
-
protected readonly class = computed(() =>
|
|
34
|
-
cn(
|
|
35
|
-
'p-1.5 rounded hover:bg-accent disabled:opacity-50 [&_svg]:size-4',
|
|
36
|
-
this.editor.isBlockquote() && 'bg-accent text-accent-foreground',
|
|
37
|
-
this.classInput(),
|
|
38
|
-
),
|
|
39
|
-
);
|
|
40
|
-
|
|
41
|
-
onClick(): void {
|
|
42
|
-
this.editor.execCommand('formatBlock', 'blockquote');
|
|
43
|
-
}
|
|
44
|
-
}
|
|
@@ -1,44 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
Component,
|
|
3
|
-
ChangeDetectionStrategy,
|
|
4
|
-
computed,
|
|
5
|
-
input,
|
|
6
|
-
inject,
|
|
7
|
-
ViewEncapsulation,
|
|
8
|
-
} from '@angular/core';
|
|
9
|
-
import { cn } from '@semantic-components/ui';
|
|
10
|
-
import { SC_EDITOR } from './editor';
|
|
11
|
-
|
|
12
|
-
@Component({
|
|
13
|
-
selector: 'button[sc-editor-bold]',
|
|
14
|
-
template: `
|
|
15
|
-
<ng-content />
|
|
16
|
-
`,
|
|
17
|
-
host: {
|
|
18
|
-
'data-slot': 'editor-bold',
|
|
19
|
-
type: 'button',
|
|
20
|
-
'[class]': 'class()',
|
|
21
|
-
'[disabled]': 'editor.disabled()',
|
|
22
|
-
'[attr.aria-pressed]': 'editor.isBold()',
|
|
23
|
-
'[attr.title]': '"Bold (Ctrl+B)"',
|
|
24
|
-
'(click)': 'onClick()',
|
|
25
|
-
},
|
|
26
|
-
encapsulation: ViewEncapsulation.None,
|
|
27
|
-
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
28
|
-
})
|
|
29
|
-
export class ScEditorBoldButton {
|
|
30
|
-
readonly editor = inject(SC_EDITOR);
|
|
31
|
-
readonly classInput = input<string>('', { alias: 'class' });
|
|
32
|
-
|
|
33
|
-
protected readonly class = computed(() =>
|
|
34
|
-
cn(
|
|
35
|
-
'p-1.5 rounded hover:bg-accent disabled:opacity-50 [&_svg]:size-4',
|
|
36
|
-
this.editor.isBold() && 'bg-accent text-accent-foreground',
|
|
37
|
-
this.classInput(),
|
|
38
|
-
),
|
|
39
|
-
);
|
|
40
|
-
|
|
41
|
-
onClick(): void {
|
|
42
|
-
this.editor.execCommand('bold');
|
|
43
|
-
}
|
|
44
|
-
}
|
|
@@ -1,44 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
Component,
|
|
3
|
-
ChangeDetectionStrategy,
|
|
4
|
-
computed,
|
|
5
|
-
input,
|
|
6
|
-
inject,
|
|
7
|
-
ViewEncapsulation,
|
|
8
|
-
} from '@angular/core';
|
|
9
|
-
import { cn } from '@semantic-components/ui';
|
|
10
|
-
import { SC_EDITOR } from './editor';
|
|
11
|
-
|
|
12
|
-
@Component({
|
|
13
|
-
selector: 'button[sc-editor-bullet-list]',
|
|
14
|
-
template: `
|
|
15
|
-
<ng-content />
|
|
16
|
-
`,
|
|
17
|
-
host: {
|
|
18
|
-
'data-slot': 'editor-bullet-list',
|
|
19
|
-
type: 'button',
|
|
20
|
-
'[class]': 'class()',
|
|
21
|
-
'[disabled]': 'editor.disabled()',
|
|
22
|
-
'[attr.aria-pressed]': 'editor.isUnorderedList()',
|
|
23
|
-
'[attr.title]': '"Bullet list"',
|
|
24
|
-
'(click)': 'onClick()',
|
|
25
|
-
},
|
|
26
|
-
encapsulation: ViewEncapsulation.None,
|
|
27
|
-
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
28
|
-
})
|
|
29
|
-
export class ScEditorBulletListButton {
|
|
30
|
-
readonly editor = inject(SC_EDITOR);
|
|
31
|
-
readonly classInput = input<string>('', { alias: 'class' });
|
|
32
|
-
|
|
33
|
-
protected readonly class = computed(() =>
|
|
34
|
-
cn(
|
|
35
|
-
'p-1.5 rounded hover:bg-accent disabled:opacity-50 [&_svg]:size-4',
|
|
36
|
-
this.editor.isUnorderedList() && 'bg-accent text-accent-foreground',
|
|
37
|
-
this.classInput(),
|
|
38
|
-
),
|
|
39
|
-
);
|
|
40
|
-
|
|
41
|
-
onClick(): void {
|
|
42
|
-
this.editor.execCommand('insertUnorderedList');
|
|
43
|
-
}
|
|
44
|
-
}
|