editium 1.0.1

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.
@@ -0,0 +1,99 @@
1
+ import { BaseElement, BaseText } from 'slate';
2
+ export type FormatType = 'bold' | 'italic' | 'underline' | 'code' | 'strikethrough' | 'superscript' | 'subscript';
3
+ export type AlignmentType = 'left' | 'center' | 'right' | 'justify';
4
+ export type BlockType = 'paragraph' | 'heading-one' | 'heading-two' | 'heading-three' | 'heading-four' | 'heading-five' | 'heading-six' | 'heading-seven' | 'heading-eight' | 'bulleted-list' | 'numbered-list' | 'list-item' | 'blockquote' | 'code-block' | 'horizontal-rule' | 'image' | 'table' | 'table-row' | 'table-cell';
5
+ export type ToolbarItem = FormatType | BlockType | AlignmentType | 'link' | 'indent' | 'outdent' | 'undo' | 'redo' | 'separator' | 'view-output' | 'text-color' | 'bg-color' | 'table' | 'find-replace' | 'fullscreen';
6
+ export declare const ALL_TOOLBAR_ITEMS: ToolbarItem[];
7
+ export interface CustomElement extends BaseElement {
8
+ type: BlockType;
9
+ url?: string;
10
+ align?: AlignmentType;
11
+ children: CustomText[];
12
+ }
13
+ export interface CustomText extends BaseText {
14
+ text: string;
15
+ bold?: boolean;
16
+ italic?: boolean;
17
+ underline?: boolean;
18
+ code?: boolean;
19
+ strikethrough?: boolean;
20
+ superscript?: boolean;
21
+ subscript?: boolean;
22
+ color?: string;
23
+ backgroundColor?: string;
24
+ search?: boolean;
25
+ searchCurrent?: boolean;
26
+ }
27
+ export interface LinkElement extends BaseElement {
28
+ type: 'link';
29
+ url: string;
30
+ title?: string;
31
+ target?: '_blank' | '_self';
32
+ children: CustomText[];
33
+ }
34
+ export interface ImageElement extends BaseElement {
35
+ type: 'image';
36
+ url: string;
37
+ alt?: string;
38
+ width?: number;
39
+ height?: number;
40
+ align?: AlignmentType;
41
+ children: CustomText[];
42
+ }
43
+ export interface TableElement extends BaseElement {
44
+ type: 'table';
45
+ align?: AlignmentType;
46
+ width?: number;
47
+ children: TableRowElement[];
48
+ }
49
+ export interface TableRowElement extends BaseElement {
50
+ type: 'table-row';
51
+ children: TableCellElement[];
52
+ }
53
+ export interface TableCellElement extends BaseElement {
54
+ type: 'table-cell';
55
+ align?: AlignmentType;
56
+ children: (CustomElement | CustomText)[];
57
+ }
58
+ export interface EditiumProps {
59
+ /** Initial editor content. Default: empty paragraph with empty text node */
60
+ initialValue?: string | CustomElement[];
61
+ /** Callback when content changes. Default: undefined (no callback) */
62
+ onChange?: (html: string, json: CustomElement[]) => void;
63
+ /** Toolbar items to display. Default: 'all' */
64
+ toolbar?: ToolbarItem[] | 'all';
65
+ /** Placeholder text when editor is empty. Default: 'Start typing...' */
66
+ placeholder?: string;
67
+ /** CSS class name for the editor container. Default: '' */
68
+ className?: string;
69
+ /** Inline styles for the editor container. Default: {} */
70
+ style?: React.CSSProperties;
71
+ /** Whether the editor is read-only. Default: false */
72
+ readOnly?: boolean;
73
+ /** Custom image upload handler. Default: undefined */
74
+ onImageUpload?: (file: File) => Promise<string>;
75
+ /** Search query string for highlighting. Default: '' */
76
+ searchQuery?: string;
77
+ /** Array of search match locations. Default: [] */
78
+ searchMatches?: Array<{
79
+ path: any;
80
+ offset: number;
81
+ text: string;
82
+ }>;
83
+ /** Index of current search match. Default: 0 */
84
+ currentMatchIndex?: number;
85
+ /** Whether to show word count. Default: true */
86
+ showWordCount?: boolean;
87
+ /** Editor height. Default: '200px' */
88
+ height?: string | number;
89
+ /** Minimum editor height. Default: '150px' */
90
+ minHeight?: string | number;
91
+ /** Maximum editor height. Default: '250px' */
92
+ maxHeight?: string | number;
93
+ }
94
+ declare module 'slate' {
95
+ interface CustomTypes {
96
+ Element: CustomElement | LinkElement | ImageElement | TableElement | TableRowElement | TableCellElement;
97
+ Text: CustomText;
98
+ }
99
+ }
@@ -0,0 +1,55 @@
1
+ import { Editor, Path } from 'slate';
2
+ import { CustomElement, FormatType, BlockType, LinkElement, AlignmentType, ImageElement } from './types';
3
+ export declare const isAlignmentActive: (editor: Editor, alignment: AlignmentType) => boolean;
4
+ export declare const toggleAlignment: (editor: Editor, alignment: AlignmentType) => void;
5
+ export declare const indentListItem: (editor: Editor) => void;
6
+ export declare const outdentListItem: (editor: Editor) => void;
7
+ export declare const isMarkActive: (editor: Editor, format: FormatType) => boolean;
8
+ export declare const isBlockActive: (editor: Editor, format: BlockType) => boolean;
9
+ export declare const toggleMark: (editor: Editor, format: FormatType) => void;
10
+ export declare const applyColor: (editor: Editor, color: string | null) => void;
11
+ export declare const applyBackgroundColor: (editor: Editor, color: string | null) => void;
12
+ export declare const getActiveColor: (editor: Editor) => string | null;
13
+ export declare const getActiveBackgroundColor: (editor: Editor) => string | null;
14
+ export declare const toggleBlock: (editor: Editor, format: BlockType) => void;
15
+ export declare const insertHorizontalRule: (editor: Editor) => void;
16
+ export declare const insertImage: (editor: Editor, url: string, alt?: string, width?: number) => void;
17
+ export declare const isValidImageUrl: (url: string) => boolean;
18
+ export declare const insertLink: (editor: Editor, url: string, title?: string, target?: "_blank" | "_self") => void;
19
+ export declare const isLinkActive: (editor: Editor) => boolean;
20
+ export declare const getLinkAtCursor: (editor: Editor) => LinkElement | null;
21
+ export declare const unwrapLink: (editor: Editor) => void;
22
+ export declare const wrapLink: (editor: Editor, url: string, title?: string, target?: "_blank" | "_self") => void;
23
+ export declare const insertTable: (editor: Editor, rows?: number, cols?: number) => void;
24
+ export declare const isInTable: (editor: Editor) => boolean;
25
+ export declare const addTableRow: (editor: Editor) => void;
26
+ export declare const removeTableRow: (editor: Editor) => void;
27
+ export declare const addTableColumn: (editor: Editor) => void;
28
+ export declare const removeTableColumn: (editor: Editor) => void;
29
+ export declare const setTableAlignment: (editor: Editor, alignment: AlignmentType) => void;
30
+ export declare const findAllMatches: (editor: Editor, searchQuery: string) => Array<{
31
+ path: Path;
32
+ offset: number;
33
+ text: string;
34
+ }>;
35
+ export declare const navigateToMatch: (editor: Editor, match: {
36
+ path: Path;
37
+ offset: number;
38
+ text: string;
39
+ }) => void;
40
+ export declare const replaceMatch: (editor: Editor, match: {
41
+ path: Path;
42
+ offset: number;
43
+ text: string;
44
+ }, replaceText: string) => void;
45
+ export declare const replaceAllMatches: (editor: Editor, matches: Array<{
46
+ path: Path;
47
+ offset: number;
48
+ text: string;
49
+ }>, replaceText: string) => void;
50
+ export declare const serializeToHtml: (nodes: (CustomElement | LinkElement | ImageElement)[]) => string;
51
+ export declare const defaultInitialValue: CustomElement[];
52
+ export declare const getTextContent: (nodes: any[]) => string;
53
+ export declare const countWords: (text: string) => number;
54
+ export declare const countCharacters: (text: string) => number;
55
+ export declare const countCharactersNoSpaces: (text: string) => number;
package/package.json ADDED
@@ -0,0 +1,105 @@
1
+ {
2
+ "name": "editium",
3
+ "version": "1.0.1",
4
+ "type": "module",
5
+ "description": "A powerful and feature-rich React rich text editor component built with Slate.js, featuring comprehensive formatting options, tables, images, find & replace, and more",
6
+ "main": "dist/index.js",
7
+ "module": "dist/index.esm.js",
8
+ "types": "dist/index.d.ts",
9
+ "files": [
10
+ "dist",
11
+ "vanilla",
12
+ "README.md",
13
+ "LICENSE"
14
+ ],
15
+ "scripts": {
16
+ "build": "rollup -c",
17
+ "build:watch": "rollup -c -w",
18
+ "dev": "npm run build:watch",
19
+ "example": "cd example && npm start",
20
+ "prepublishOnly": "npm run build",
21
+ "test": "vitest run",
22
+ "test:watch": "vitest",
23
+ "test:ui": "vitest --ui",
24
+ "test:coverage": "vitest run --coverage",
25
+ "semantic-release": "semantic-release"
26
+ },
27
+ "keywords": [
28
+ "react",
29
+ "editor",
30
+ "rich-text",
31
+ "slate",
32
+ "wysiwyg",
33
+ "text-editor",
34
+ "contenteditable",
35
+ "markdown",
36
+ "formatting",
37
+ "tables",
38
+ "images",
39
+ "find-replace",
40
+ "fullscreen",
41
+ "word-count",
42
+ "character-count",
43
+ "slate-editor",
44
+ "react-editor",
45
+ "text-formatting",
46
+ "content-editor",
47
+ "document-editor"
48
+ ],
49
+ "author": {
50
+ "name": "NabarupDev",
51
+ "url": "https://github.com/NabarupDev"
52
+ },
53
+ "repository": {
54
+ "type": "git",
55
+ "url": "https://github.com/NabarupDev/Editium.git"
56
+ },
57
+ "bugs": {
58
+ "url": "https://github.com/NabarupDev/Editium/issues"
59
+ },
60
+ "homepage": "https://github.com/NabarupDev/Editium#readme",
61
+ "license": "MIT",
62
+ "peerDependencies": {
63
+ "react": ">=16.8.0",
64
+ "react-dom": ">=16.8.0"
65
+ },
66
+ "dependencies": {
67
+ "@heroicons/react": "^2.2.0",
68
+ "slate": "^0.103.0",
69
+ "slate-history": "^0.100.0",
70
+ "slate-react": "^0.108.0"
71
+ },
72
+ "devDependencies": {
73
+ "@rollup/plugin-commonjs": "^25.0.7",
74
+ "@rollup/plugin-node-resolve": "^15.2.3",
75
+ "@rollup/plugin-typescript": "^11.1.5",
76
+ "@semantic-release/changelog": "^6.0.3",
77
+ "@semantic-release/git": "^10.0.1",
78
+ "@semantic-release/github": "^11.0.6",
79
+ "@testing-library/jest-dom": "^6.9.1",
80
+ "@testing-library/react": "^16.3.0",
81
+ "@testing-library/user-event": "^14.6.1",
82
+ "@types/react": "^18.2.45",
83
+ "@types/react-dom": "^18.2.18",
84
+ "@vitejs/plugin-react": "^5.0.4",
85
+ "@vitest/ui": "^3.2.4",
86
+ "conventional-changelog-conventionalcommits": "^9.1.0",
87
+ "jsdom": "^27.0.0",
88
+ "react": "^18.2.0",
89
+ "react-dom": "^18.2.0",
90
+ "rollup": "^4.9.1",
91
+ "rollup-plugin-peer-deps-external": "^2.2.4",
92
+ "rollup-plugin-postcss": "^4.0.2",
93
+ "semantic-release": "^24.2.9",
94
+ "tslib": "^2.8.1",
95
+ "typescript": "^5.3.3",
96
+ "vitest": "^3.2.4"
97
+ },
98
+ "engines": {
99
+ "node": ">=14.0.0",
100
+ "npm": ">=6.0.0"
101
+ },
102
+ "publishConfig": {
103
+ "access": "public"
104
+ }
105
+ }
@@ -0,0 +1,409 @@
1
+ # Editium Vanilla JavaScript Editor
2
+
3
+ A lightweight, powerful rich text editor built with pure vanilla JavaScript. Zero dependencies, framework-agnostic, and production-ready.
4
+
5
+ ## Features
6
+
7
+ - **Zero Dependencies** - Pure vanilla JavaScript, no external frameworks required
8
+ - **Lightweight** - Minimal footprint with maximum functionality
9
+ - **Rich Text Editing** - Comprehensive formatting options (bold, italic, underline, strikethrough, etc.)
10
+ - **Advanced Features** - Tables, images, code blocks, and more
11
+ - **Customizable** - Flexible toolbar and styling options
12
+ - **Production Ready** - Battle-tested and reliable
13
+ - **Multiple Formats** - Export to HTML, plain text, or JSON
14
+ - **Keyboard Shortcuts** - Efficient editing with standard shortcuts
15
+ - **Responsive** - Works seamlessly on all screen sizes
16
+ - **Accessible** - ARIA support and keyboard navigation
17
+
18
+ ## Installation
19
+
20
+ ### CDN (Recommended)
21
+
22
+ **Single Bundle** - All-in-one file including JavaScript, CSS, and icons:
23
+
24
+ ```html
25
+ <script src="https://unpkg.com/editium@1.0.0/vanilla/editium.bundle.js"></script>
26
+ ```
27
+
28
+ **Alternative CDNs:**
29
+ ```html
30
+ <!-- jsDelivr -->
31
+ <script src="https://cdn.jsdelivr.net/npm/editium@1.0.0/vanilla/editium.bundle.js"></script>
32
+ ```
33
+
34
+ **Separate Files** - For more control:
35
+
36
+ ```html
37
+ <!-- unpkg -->
38
+ <link rel="stylesheet" href="https://unpkg.com/editium@1.0.0/vanilla/editium.css">
39
+ <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.6.0/css/all.min.css">
40
+ <script src="https://unpkg.com/editium@1.0.0/vanilla/editium.js"></script>
41
+
42
+ <!-- jsDelivr -->
43
+ <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/editium@1.0.0/vanilla/editium.css">
44
+ <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.6.0/css/all.min.css">
45
+ <script src="https://cdn.jsdelivr.net/npm/editium@1.0.0/vanilla/editium.js"></script>
46
+ ```
47
+
48
+ ### NPM
49
+
50
+ ```bash
51
+ npm install editium
52
+ ```
53
+
54
+ ```javascript
55
+ import 'editium/vanilla/editium.css';
56
+ import Editium from 'editium/vanilla/editium.js';
57
+
58
+ const editor = new Editium({
59
+ container: document.getElementById('editor'),
60
+ placeholder: 'Start typing...',
61
+ toolbar: 'all'
62
+ });
63
+ ```
64
+
65
+ ### Self-Hosted
66
+
67
+ Download the files from the [GitHub repository](https://github.com/NabarupDev/Editium) and include them in your project:
68
+
69
+ ```html
70
+ <link rel="stylesheet" href="path/to/editium.css">
71
+ <script src="path/to/editium.js"></script>
72
+ ```
73
+
74
+ ## Quick Start
75
+
76
+ ```html
77
+ <!DOCTYPE html>
78
+ <html lang="en">
79
+ <head>
80
+ <meta charset="UTF-8">
81
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
82
+ <title>Editium Editor</title>
83
+ <script src="https://unpkg.com/editium@1.0.0/vanilla/editium.bundle.js"></script>
84
+ </head>
85
+ <body>
86
+ <div id="editor"></div>
87
+
88
+ <script>
89
+ const editor = new Editium({
90
+ container: document.getElementById('editor'),
91
+ placeholder: 'Start typing...',
92
+ toolbar: 'all',
93
+ showWordCount: true
94
+ });
95
+ </script>
96
+ </body>
97
+ </html>
98
+ ```
99
+
100
+ ## Configuration
101
+
102
+ ### Options
103
+
104
+ ```javascript
105
+ const editor = new Editium({
106
+ container: document.getElementById('editor'),
107
+ placeholder: 'Start typing...',
108
+ toolbar: 'all',
109
+ showWordCount: true,
110
+ readOnly: false,
111
+ className: 'custom-class',
112
+ height: '300px',
113
+ minHeight: '200px',
114
+ maxHeight: '400px',
115
+ onChange: (content) => {
116
+ console.log('Content changed:', content);
117
+ },
118
+ onImageUpload: async (file) => {
119
+ const url = await uploadToServer(file);
120
+ return url;
121
+ }
122
+ });
123
+ ```
124
+
125
+ ### Available Options
126
+
127
+ | Option | Type | Default | Description |
128
+ |--------|------|---------|-------------|
129
+ | `container` | HTMLElement | required | DOM element to attach the editor |
130
+ | `placeholder` | string | `''` | Placeholder text when editor is empty |
131
+ | `toolbar` | string \| array | `'all'` | Toolbar configuration ('all' or array of items) |
132
+ | `showWordCount` | boolean | `false` | Display word and character count |
133
+ | `readOnly` | boolean | `false` | Make editor read-only |
134
+ | `className` | string | `''` | Custom CSS class for wrapper |
135
+ | `height` | string \| number | `'200px'` | Default editor height |
136
+ | `minHeight` | string \| number | `'150px'` | Minimum height before content shrinks |
137
+ | `maxHeight` | string \| number | `'250px'` | Maximum height before scrolling |
138
+ | `onChange` | function | `null` | Callback when content changes |
139
+ | `onImageUpload` | function | `null` | Custom image upload handler |
140
+
141
+ ### Height Configuration
142
+
143
+ Heights can be specified as strings (`'300px'`, `'20rem'`, `'50vh'`) or numbers (`300` converts to `'300px'`).
144
+
145
+ ```javascript
146
+ const editor = new Editium({
147
+ container: document.getElementById('editor'),
148
+ height: 400,
149
+ minHeight: 200,
150
+ maxHeight: 600
151
+ });
152
+ ```
153
+
154
+ ## Toolbar
155
+
156
+ ### Predefined Toolbar
157
+
158
+ ```javascript
159
+ toolbar: 'all'
160
+ ```
161
+
162
+ ### Custom Toolbar
163
+
164
+ ```javascript
165
+ toolbar: [
166
+ 'bold', 'italic', 'underline',
167
+ 'separator',
168
+ 'heading-one', 'heading-two',
169
+ 'separator',
170
+ 'bulleted-list', 'numbered-list',
171
+ 'separator',
172
+ 'link', 'image', 'table',
173
+ 'separator',
174
+ 'undo', 'redo'
175
+ ]
176
+ ```
177
+
178
+ ### Available Items
179
+
180
+ #### Text Formatting
181
+ - `bold`, `italic`, `underline`, `strikethrough`
182
+ - `code`, `superscript`, `subscript`
183
+
184
+ #### Block Formats
185
+ - `paragraph`
186
+ - `heading-one`, `heading-two`, `heading-three`, `heading-four`, `heading-five`, `heading-six`
187
+ - `blockquote`, `code-block`
188
+
189
+ #### Alignment
190
+ - `left`, `center`, `right`, `justify`
191
+
192
+ #### Colors
193
+ - `text-color`, `bg-color`
194
+
195
+ #### Lists
196
+ - `bulleted-list`, `numbered-list`
197
+ - `indent`, `outdent`
198
+
199
+ #### Insert
200
+ - `link`, `image`, `table`, `horizontal-rule`
201
+
202
+ #### History
203
+ - `undo`, `redo`
204
+
205
+ #### View
206
+ - `preview`, `view-html`, `view-json`, `find-replace`, `fullscreen`
207
+
208
+ #### Other
209
+ - `separator`
210
+
211
+ ## API
212
+
213
+ ```javascript
214
+ // Get content
215
+ const html = editor.getHTML();
216
+ const text = editor.getText();
217
+ const json = editor.getJSON();
218
+
219
+ // Set content
220
+ editor.setContent('<p>New content</p>');
221
+
222
+ // Clear and focus
223
+ editor.clear();
224
+ editor.focus();
225
+
226
+ // Cleanup
227
+ editor.destroy();
228
+ ```
229
+
230
+ ## Usage Examples
231
+
232
+ ### Basic Editor
233
+
234
+ ```html
235
+ <div id="basic-editor"></div>
236
+
237
+ <script>
238
+ const basicEditor = new Editium({
239
+ container: document.getElementById('basic-editor'),
240
+ placeholder: 'Write something...',
241
+ toolbar: ['bold', 'italic', 'underline', 'separator', 'link']
242
+ });
243
+ </script>
244
+ ```
245
+
246
+ ### Full-Featured Editor
247
+
248
+ ```html
249
+ <div id="full-editor"></div>
250
+
251
+ <script>
252
+ const fullEditor = new Editium({
253
+ container: document.getElementById('full-editor'),
254
+ placeholder: 'Start writing your document...',
255
+ toolbar: 'all',
256
+ showWordCount: true,
257
+ onChange: (content) => {
258
+ console.log('Content updated:', content.html);
259
+ }
260
+ });
261
+ </script>
262
+ ```
263
+
264
+ ### Read-Only Viewer
265
+
266
+ ```html
267
+ <div id="viewer"></div>
268
+
269
+ <script>
270
+ const viewer = new Editium({
271
+ container: document.getElementById('viewer'),
272
+ readOnly: true,
273
+ toolbar: []
274
+ });
275
+
276
+ viewer.setContent('<h1>Document Title</h1><p>Document content...</p>');
277
+ </script>
278
+ ```
279
+
280
+ ## Advanced Features
281
+
282
+ ### Custom Image Upload Handler
283
+
284
+ ```html
285
+ <div id="editor-with-upload"></div>
286
+
287
+ <script>
288
+ const editorWithUpload = new Editium({
289
+ container: document.getElementById('editor-with-upload'),
290
+ toolbar: 'all',
291
+ onImageUpload: async (file) => {
292
+ const formData = new FormData();
293
+ formData.append('image', file);
294
+
295
+ const response = await fetch('/api/upload', {
296
+ method: 'POST',
297
+ body: formData
298
+ });
299
+
300
+ const data = await response.json();
301
+ return data.url;
302
+ }
303
+ });
304
+ </script>
305
+ ```
306
+
307
+ ### Saving and Loading Content
308
+
309
+ ```html
310
+ <div id="editor"></div>
311
+ <button onclick="saveContent()">Save</button>
312
+
313
+ <script>
314
+ const editor = new Editium({
315
+ container: document.getElementById('editor'),
316
+ toolbar: 'all'
317
+ });
318
+
319
+ function saveContent() {
320
+ const content = {
321
+ html: editor.getHTML(),
322
+ text: editor.getText(),
323
+ json: editor.getJSON()
324
+ };
325
+
326
+ localStorage.setItem('editorContent', JSON.stringify(content));
327
+
328
+ // Or send to server
329
+ fetch('/api/save', {
330
+ method: 'POST',
331
+ headers: { 'Content-Type': 'application/json' },
332
+ body: JSON.stringify(content)
333
+ });
334
+ }
335
+
336
+ // Load saved content
337
+ const savedContent = localStorage.getItem('editorContent');
338
+ if (savedContent) {
339
+ const content = JSON.parse(savedContent);
340
+ editor.setContent(content.html);
341
+ }
342
+ </script>
343
+ ```
344
+
345
+ ## Keyboard Shortcuts
346
+
347
+ - `Ctrl/Cmd + B` - Bold
348
+ - `Ctrl/Cmd + I` - Italic
349
+ - `Ctrl/Cmd + U` - Underline
350
+ - `Ctrl/Cmd + Z` - Undo
351
+ - `Ctrl/Cmd + Y` - Redo
352
+ - `F11` - Toggle fullscreen
353
+ - `Ctrl/Cmd + F` - Find & Replace
354
+
355
+ ## Customization
356
+
357
+ Custom styling example:
358
+
359
+ ```css
360
+ /* Customize wrapper */
361
+ .editium-wrapper {
362
+ border: 2px solid #007bff;
363
+ border-radius: 8px;
364
+ }
365
+
366
+ /* Customize toolbar */
367
+ .editium-toolbar {
368
+ background-color: #f0f0f0;
369
+ }
370
+
371
+ /* Customize editor area */
372
+ .editium-editor {
373
+ min-height: 300px;
374
+ font-size: 16px;
375
+ line-height: 1.8;
376
+ }
377
+
378
+ /* Custom placeholder color */
379
+ .editium-editor:empty:before {
380
+ color: #999;
381
+ }
382
+ ```
383
+
384
+ ## Browser Compatibility
385
+
386
+ - Chrome (latest)
387
+ - Firefox (latest)
388
+ - Safari (latest)
389
+ - Edge (latest)
390
+
391
+ ## Links
392
+
393
+ - **NPM Package**: https://www.npmjs.com/package/editium
394
+ - **GitHub Repository**: https://github.com/NabarupDev/Editium
395
+ - **Issues**: https://github.com/NabarupDev/Editium/issues
396
+ - **unpkg CDN**: https://unpkg.com/editium@1.0.0/vanilla/
397
+ - **jsDelivr CDN**: https://cdn.jsdelivr.net/npm/editium@1.0.0/vanilla/
398
+
399
+ ## License
400
+
401
+ MIT License - See [LICENSE](../LICENSE) file for details.
402
+
403
+ ## Contributing
404
+
405
+ Contributions are welcome! Please submit a Pull Request.
406
+
407
+ ---
408
+
409
+ Made with ❤️ by [NabarupDev](https://github.com/NabarupDev)