autumnnote 1.0.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.
Files changed (50) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +874 -0
  3. package/dist/autumnnote.css +1 -0
  4. package/dist/autumnnote.es.js +5888 -0
  5. package/dist/autumnnote.es.js.map +1 -0
  6. package/dist/autumnnote.umd.js +74 -0
  7. package/dist/autumnnote.umd.js.map +1 -0
  8. package/package.json +55 -0
  9. package/src/js/Context.js +497 -0
  10. package/src/js/core/dom.js +315 -0
  11. package/src/js/core/env.js +25 -0
  12. package/src/js/core/func.js +153 -0
  13. package/src/js/core/key.js +66 -0
  14. package/src/js/core/lists.js +121 -0
  15. package/src/js/core/markdown.js +294 -0
  16. package/src/js/core/range.js +194 -0
  17. package/src/js/core/sanitise.js +78 -0
  18. package/src/js/editing/History.js +205 -0
  19. package/src/js/editing/Style.js +329 -0
  20. package/src/js/editing/Table.js +59 -0
  21. package/src/js/editing/Typing.js +142 -0
  22. package/src/js/index.js +126 -0
  23. package/src/js/module/Buttons.js +300 -0
  24. package/src/js/module/Clipboard.js +460 -0
  25. package/src/js/module/CodeTooltip.js +428 -0
  26. package/src/js/module/Codeview.js +122 -0
  27. package/src/js/module/ContextMenu.js +470 -0
  28. package/src/js/module/Editor.js +528 -0
  29. package/src/js/module/EmojiDialog.js +726 -0
  30. package/src/js/module/FindReplace.js +440 -0
  31. package/src/js/module/Fullscreen.js +80 -0
  32. package/src/js/module/IconDialog.js +620 -0
  33. package/src/js/module/ImageDialog.js +208 -0
  34. package/src/js/module/ImageResizer.js +216 -0
  35. package/src/js/module/ImageTooltip.js +286 -0
  36. package/src/js/module/LinkDialog.js +204 -0
  37. package/src/js/module/LinkTooltip.js +242 -0
  38. package/src/js/module/Placeholder.js +44 -0
  39. package/src/js/module/ShortcutsDialog.js +141 -0
  40. package/src/js/module/Statusbar.js +238 -0
  41. package/src/js/module/TableTooltip.js +568 -0
  42. package/src/js/module/Toolbar.js +562 -0
  43. package/src/js/module/VideoDialog.js +263 -0
  44. package/src/js/module/VideoResizer.js +227 -0
  45. package/src/js/module/VideoTooltip.js +252 -0
  46. package/src/js/renderer.js +107 -0
  47. package/src/js/settings.js +134 -0
  48. package/src/styles/_variables.scss +48 -0
  49. package/src/styles/autumnnote.scss +1740 -0
  50. package/types/index.d.ts +324 -0
@@ -0,0 +1,252 @@
1
+ // VideoTooltip.js - Hover tooltip for video wrappers inside the editor
2
+ // Displays a horizontal action bar below (or above) the selected video,
3
+ // similar in appearance and interaction to LinkTooltip.
4
+ import { createElement, on } from '../core/dom.js';
5
+
6
+ const ICONS = {
7
+ floatLeft: `<svg xmlns="http://www.w3.org/2000/svg" width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="2" y="4" width="8" height="8" rx="1"/><line x1="12" y1="6" x2="22" y2="6"/><line x1="12" y1="9" x2="22" y2="9"/><line x1="12" y1="12" x2="22" y2="12"/><line x1="2" y1="16" x2="22" y2="16"/><line x1="2" y1="20" x2="18" y2="20"/></svg>`,
8
+ floatRight: `<svg xmlns="http://www.w3.org/2000/svg" width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="14" y="4" width="8" height="8" rx="1"/><line x1="2" y1="6" x2="12" y2="6"/><line x1="2" y1="9" x2="12" y2="9"/><line x1="2" y1="12" x2="12" y2="12"/><line x1="2" y1="16" x2="22" y2="16"/><line x1="2" y1="20" x2="18" y2="20"/></svg>`,
9
+ floatNone: `<svg xmlns="http://www.w3.org/2000/svg" width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="3" y="3" width="8" height="8" rx="1"/><line x1="3" y1="15" x2="21" y2="15"/><line x1="3" y1="19" x2="17" y2="19"/></svg>`,
10
+ alignCenter: `<svg xmlns="http://www.w3.org/2000/svg" width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="7" y="4" width="10" height="8" rx="1"/><line x1="3" y1="16" x2="21" y2="16"/><line x1="6" y1="20" x2="18" y2="20"/></svg>`,
11
+ originalSize:`<svg xmlns="http://www.w3.org/2000/svg" width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="15 3 21 3 21 9"/><polyline points="9 21 3 21 3 15"/><line x1="21" y1="3" x2="14" y2="10"/><line x1="3" y1="21" x2="10" y2="14"/></svg>`,
12
+ deleteVideo: `<svg xmlns="http://www.w3.org/2000/svg" width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="3 6 5 6 21 6"/><path d="M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6m3 0V4a1 1 0 0 1 1-1h4a1 1 0 0 1 1 1v2"/></svg>`,
13
+ };
14
+
15
+ const SHOW_DELAY = 100;
16
+ const HIDE_DELAY = 180;
17
+
18
+ export class VideoTooltip {
19
+ /** @param {import('../Context.js').Context} context */
20
+ constructor(context) {
21
+ this.context = context;
22
+ this._el = null;
23
+ this._activeWrapper = null;
24
+ this._showTimer = null;
25
+ this._hideTimer = null;
26
+ this._disposers = [];
27
+ }
28
+
29
+ initialize() {
30
+ this._el = this._buildTooltip();
31
+ document.body.appendChild(this._el);
32
+
33
+ const editable = this.context.layoutInfo.editable;
34
+
35
+ this._disposers.push(
36
+ on(editable, 'mouseover', (e) => {
37
+ // The shield div sits on top of iframes — we detect hover via it or the wrapper
38
+ const wrapper = e.target.closest('.an-video-wrapper');
39
+ if (wrapper && editable.contains(wrapper)) {
40
+ this._scheduleShow(wrapper);
41
+ }
42
+ }),
43
+ on(editable, 'mouseout', (e) => {
44
+ const to = e.relatedTarget;
45
+ if (!to || (!editable.contains(to) && !this._el.contains(to))) {
46
+ this._scheduleHide();
47
+ }
48
+ }),
49
+ on(document, 'click', (e) => {
50
+ if (
51
+ this._activeWrapper &&
52
+ !this._activeWrapper.contains(e.target) &&
53
+ !this._el.contains(e.target)
54
+ ) {
55
+ this._hide();
56
+ }
57
+ }),
58
+ );
59
+
60
+ return this;
61
+ }
62
+
63
+ destroy() {
64
+ this._clearTimers();
65
+ this._disposers.forEach((d) => d());
66
+ this._disposers = [];
67
+ if (this._el && this._el.parentNode) this._el.parentNode.removeChild(this._el);
68
+ this._el = null;
69
+ }
70
+
71
+ // ---------------------------------------------------------------------------
72
+ // Build
73
+ // ---------------------------------------------------------------------------
74
+
75
+ _buildTooltip() {
76
+ const el = createElement('div', {
77
+ class: 'an-link-tooltip an-video-tooltip',
78
+ role: 'toolbar',
79
+ 'aria-label': 'Video actions',
80
+ });
81
+ el.style.display = 'none';
82
+
83
+ // Label
84
+ this._label = createElement('span', { class: 'an-link-tooltip-url' });
85
+ this._label.textContent = 'Video';
86
+ el.appendChild(this._label);
87
+
88
+ el.appendChild(createElement('div', { class: 'an-link-tooltip-sep' }));
89
+
90
+ this._floatLeftBtn = this._makeBtn(ICONS.floatLeft, 'Float Left', () => this._setFloat('left'));
91
+ this._floatNoneBtn = this._makeBtn(ICONS.floatNone, 'No Float', () => this._setFloat(''));
92
+ this._alignCenterBtn = this._makeBtn(ICONS.alignCenter, 'Align Center', () => this._setCenter());
93
+ this._floatRightBtn = this._makeBtn(ICONS.floatRight, 'Float Right', () => this._setFloat('right'));
94
+
95
+ el.appendChild(this._floatLeftBtn);
96
+ el.appendChild(this._floatNoneBtn);
97
+ el.appendChild(this._alignCenterBtn);
98
+ el.appendChild(this._floatRightBtn);
99
+
100
+ el.appendChild(createElement('div', { class: 'an-link-tooltip-sep' }));
101
+
102
+ this._originalBtn = this._makeBtn(ICONS.originalSize, 'Original Size', () => this._resetSize());
103
+
104
+ el.appendChild(this._originalBtn);
105
+
106
+ el.appendChild(createElement('div', { class: 'an-link-tooltip-sep' }));
107
+
108
+ this._deleteBtn = this._makeBtn(ICONS.deleteVideo, 'Delete Video', () => this._delete(), true);
109
+
110
+ el.appendChild(this._deleteBtn);
111
+
112
+ // Keep tooltip alive while hovering it
113
+ this._disposers.push(
114
+ on(el, 'mouseenter', () => this._clearTimers()),
115
+ on(el, 'mouseleave', () => this._scheduleHide()),
116
+ );
117
+
118
+ return el;
119
+ }
120
+
121
+ /**
122
+ * @param {string} icon
123
+ * @param {string} title
124
+ * @param {Function} handler
125
+ * @param {boolean} [isDanger]
126
+ */
127
+ _makeBtn(icon, title, handler, isDanger = false) {
128
+ const btn = createElement('button', {
129
+ type: 'button',
130
+ class: isDanger ? 'an-link-tooltip-btn an-link-tooltip-btn--danger' : 'an-link-tooltip-btn',
131
+ title,
132
+ });
133
+ btn.innerHTML = icon;
134
+ this._disposers.push(on(btn, 'click', (e) => {
135
+ e.preventDefault();
136
+ e.stopPropagation();
137
+ handler();
138
+ }));
139
+ return btn;
140
+ }
141
+
142
+ // ---------------------------------------------------------------------------
143
+ // Show / Hide
144
+ // ---------------------------------------------------------------------------
145
+
146
+ _scheduleShow(wrapper) {
147
+ if (this._activeWrapper === wrapper && this._el.style.display !== 'none') return;
148
+ clearTimeout(this._hideTimer);
149
+ this._hideTimer = null;
150
+ clearTimeout(this._showTimer);
151
+ this._showTimer = setTimeout(() => {
152
+ this._activeWrapper = wrapper;
153
+ this._show(wrapper);
154
+ }, SHOW_DELAY);
155
+ }
156
+
157
+ _scheduleHide() {
158
+ clearTimeout(this._showTimer);
159
+ this._showTimer = null;
160
+ if (this._hideTimer) return;
161
+ this._hideTimer = setTimeout(() => this._hide(), HIDE_DELAY);
162
+ }
163
+
164
+ _show(wrapper) {
165
+ this._el.style.display = 'flex';
166
+ this._positionNear(wrapper);
167
+ }
168
+
169
+ _hide() {
170
+ this._el.style.display = 'none';
171
+ this._activeWrapper = null;
172
+ this._clearTimers();
173
+ }
174
+
175
+ _clearTimers() {
176
+ clearTimeout(this._showTimer);
177
+ clearTimeout(this._hideTimer);
178
+ this._showTimer = null;
179
+ this._hideTimer = null;
180
+ }
181
+
182
+ _positionNear(wrapper) {
183
+ const rect = wrapper.getBoundingClientRect();
184
+ const tipW = this._el.offsetWidth || 220;
185
+ const tipH = this._el.offsetHeight || 32;
186
+ const margin = 6;
187
+
188
+ let top = rect.bottom + margin;
189
+ let left = rect.left + (rect.width - tipW) / 2;
190
+
191
+ if (top + tipH > window.innerHeight - margin) top = rect.top - tipH - margin;
192
+ if (left + tipW > window.innerWidth - margin) left = window.innerWidth - tipW - margin;
193
+ if (left < margin) left = margin;
194
+
195
+ this._el.style.top = `${top}px`;
196
+ this._el.style.left = `${left}px`;
197
+ }
198
+
199
+ // ---------------------------------------------------------------------------
200
+ // Actions
201
+ // ---------------------------------------------------------------------------
202
+
203
+ _setFloat(value) {
204
+ const wrapper = this._activeWrapper;
205
+ if (!wrapper) return;
206
+ wrapper.style.float = value;
207
+ wrapper.style.display = value ? 'inline-block' : '';
208
+ wrapper.style.marginLeft = value === 'right' ? '12px' : '';
209
+ wrapper.style.marginRight = value === 'left' ? '12px' : '';
210
+ this.context.invoke('editor.afterCommand');
211
+ this.context.invoke('videoResizer.updateOverlay');
212
+ this._positionNear(wrapper);
213
+ }
214
+
215
+ _setCenter() {
216
+ const wrapper = this._activeWrapper;
217
+ if (!wrapper) return;
218
+ wrapper.style.float = '';
219
+ wrapper.style.display = 'block';
220
+ wrapper.style.marginLeft = 'auto';
221
+ wrapper.style.marginRight = 'auto';
222
+ this.context.invoke('editor.afterCommand');
223
+ this.context.invoke('videoResizer.updateOverlay');
224
+ this._positionNear(wrapper);
225
+ }
226
+
227
+ _resetSize() {
228
+ const wrapper = this._activeWrapper;
229
+ if (!wrapper) return;
230
+ const embed = wrapper.querySelector('iframe, video');
231
+ wrapper.style.width = '';
232
+ wrapper.style.height = '';
233
+ if (embed) {
234
+ embed.removeAttribute('width');
235
+ embed.removeAttribute('height');
236
+ embed.style.width = '';
237
+ embed.style.height = '';
238
+ }
239
+ this.context.invoke('editor.afterCommand');
240
+ this.context.invoke('videoResizer.updateOverlay');
241
+ this._positionNear(wrapper);
242
+ }
243
+
244
+ _delete() {
245
+ const wrapper = this._activeWrapper;
246
+ if (!wrapper) return;
247
+ this._hide();
248
+ this.context.invoke('videoResizer.deselect');
249
+ if (wrapper.parentNode) wrapper.parentNode.removeChild(wrapper);
250
+ this.context.invoke('editor.afterCommand');
251
+ }
252
+ }
@@ -0,0 +1,107 @@
1
+ /**
2
+ * renderer.js - Builds the editor DOM structure
3
+ * Inspired by Summernote's renderer.js
4
+ */
5
+
6
+ import { createElement } from './core/dom.js';
7
+ import { sanitiseHTML } from './core/sanitise.js';
8
+
9
+ /**
10
+ * Renders the editor layout around the original element.
11
+ *
12
+ * Structure:
13
+ * <div class="an-container">
14
+ * <div class="an-toolbar">...</div>
15
+ * <div class="an-editable" contenteditable="true">...</div>
16
+ * <div class="an-statusbar">...</div>
17
+ * </div>
18
+ *
19
+ * @param {HTMLElement} targetEl - the original element to replace/wrap
20
+ * @param {import('./settings.js').AsnOptions} options
21
+ * @returns {{ container: HTMLElement, editable: HTMLElement }}
22
+ */
23
+ export function renderLayout(targetEl, options) {
24
+ const container = createElement('div', { class: 'an-container' });
25
+
26
+ // Editable area
27
+ const editable = createElement('div', {
28
+ class: 'an-editable',
29
+ contenteditable: options.readOnly ? 'false' : 'true',
30
+ spellcheck: String(options.spellcheck !== false),
31
+ 'aria-multiline': 'true',
32
+ 'aria-label': 'Rich text editor',
33
+ role: 'textbox',
34
+ });
35
+
36
+ // Restore auto-saved content when available; fall back to element content
37
+ let initialContent = '';
38
+ if (options.autoSave && options.autoSaveKey) {
39
+ try { initialContent = localStorage.getItem(options.autoSaveKey) || ''; } catch (_) {}
40
+ }
41
+ if (!initialContent) {
42
+ initialContent = targetEl.tagName === 'TEXTAREA'
43
+ ? (targetEl.value || '').trim()
44
+ : (targetEl.innerHTML || '').trim();
45
+ }
46
+ editable.innerHTML = sanitiseHTML(initialContent, { allowIframes: true });
47
+
48
+ // Apply default font family so the editable renders in the configured font
49
+ const defaultFont = options.defaultFontFamily || (options.fontFamilies && options.fontFamilies[0]);
50
+ if (defaultFont) {
51
+ editable.style.fontFamily = defaultFont;
52
+ }
53
+
54
+ // Apply default font size so the size dropdown shows the correct value on startup
55
+ if (options.defaultFontSize) {
56
+ editable.style.fontSize = options.defaultFontSize;
57
+ }
58
+
59
+ // Apply height options.
60
+ // `height` sets the initial visible height (takes priority).
61
+ // `minHeight` is the drag-resize floor — only applied when no explicit `height` is given.
62
+ if (options.height) {
63
+ editable.style.minHeight = `${options.height}px`;
64
+ } else if (options.minHeight) {
65
+ editable.style.minHeight = `${options.minHeight}px`;
66
+ }
67
+ if (options.maxHeight) {
68
+ editable.style.maxHeight = `${options.maxHeight}px`;
69
+ }
70
+
71
+ container.appendChild(editable);
72
+
73
+ // Apply dark theme
74
+ if (options.theme === 'dark') {
75
+ container.classList.add('an-theme-dark');
76
+ }
77
+
78
+ // Read-only mode
79
+ if (options.readOnly) {
80
+ container.classList.add('an-disabled');
81
+ }
82
+
83
+ // Text direction
84
+ if (options.direction === 'rtl') {
85
+ editable.setAttribute('dir', 'rtl');
86
+ container.classList.add('an-dir-rtl');
87
+ }
88
+
89
+ // Toolbar overflow
90
+ if (options.toolbarOverflow === 'scroll') {
91
+ container.classList.add('an-toolbar-overflow-scroll');
92
+ }
93
+
94
+ // Configure sticky toolbar
95
+ if (options.stickyToolbar) {
96
+ container.classList.add('an-sticky-toolbar');
97
+ if (options.stickyToolbarOffset) {
98
+ container.style.setProperty('--an-sticky-top', `${options.stickyToolbarOffset}px`);
99
+ }
100
+ }
101
+
102
+ // Hide the original element; keep it in DOM for form submission
103
+ targetEl.style.display = 'none';
104
+ targetEl.insertAdjacentElement('afterend', container);
105
+
106
+ return { container, editable };
107
+ }
@@ -0,0 +1,134 @@
1
+ /**
2
+ * settings.js - Default editor options
3
+ * Inspired by Summernote's settings.js
4
+ */
5
+
6
+ import { defaultToolbar } from './module/Buttons.js';
7
+
8
+ /**
9
+ * @typedef {object} AsnOptions
10
+ * @property {string} [placeholder] - Placeholder text when editor is empty
11
+ * @property {number} [height] - Editor height in px (min)
12
+ * @property {number} [minHeight] - Minimum height in px
13
+ * @property {number} [maxHeight] - Maximum height in px (0 = unlimited)
14
+ * @property {boolean} [focus] - Auto-focus on init
15
+ * @property {boolean} [resizable] - Show resize handle
16
+ * @property {Array} [toolbar] - Toolbar button group config
17
+ * @property {boolean} [pasteAsPlainText] - Force plain-text paste
18
+ * @property {boolean} [pasteCleanHTML] - Sanitise HTML on paste
19
+ * @property {boolean} [pasteStripAttributes] - Strip class/style/data-* from pasted HTML (default: false)
20
+ * @property {boolean} [allowImageUpload] - Allow file upload in image dialog
21
+ * @property {number} [maxImageSize] - Max upload size in MB
22
+ * @property {number} [tabSize] - Spaces per tab in non-list context
23
+ * @property {Function} [onChange] - Callback on content change
24
+ * @property {Function} [onFocus] - Callback on focus
25
+ * @property {Function} [onBlur] - Callback on blur
26
+ * @property {Function} [onImageUpload] - Custom upload handler: (files) => void
27
+ * @property {boolean} [stickyToolbar] - Stick the toolbar to the viewport top when scrolling
28
+ * @property {number} [stickyToolbarOffset] - Top offset in px for sticky toolbar (e.g. fixed nav height)
29
+ * @property {string} [theme] - 'light' (default) | 'dark'
30
+ * @property {boolean} [codeHighlight] - Auto-load Prism.js for syntax highlighting of code blocks
31
+ * @property {string} [codeHighlightCDN] - CDN base URL for Prism assets (defaults to cdnjs)
32
+ * @property {boolean} [markdownPaste] - Convert pasted Markdown text to HTML (default: true)
33
+ * @property {boolean} [readOnly] - Start editor in read-only / non-editable mode
34
+ * @property {boolean} [spellcheck] - Enable browser spellcheck in the editable area (default: true)
35
+ * @property {string} [direction] - Text direction: 'ltr' (default) | 'rtl'
36
+ * @property {string} [toolbarOverflow] - Toolbar overflow strategy: 'wrap' (default) | 'scroll'
37
+ * @property {boolean} [autoSave] - Auto-save content to localStorage on change
38
+ * @property {string} [autoSaveKey] - localStorage key used for auto-save (default: 'autumnnote-autosave')
39
+ * @property {number} [maxChars] - Maximum character count (0 = unlimited). Shows warning in statusbar.
40
+ * @property {number} [maxWords] - Maximum word count (0 = unlimited). Shows warning in statusbar.
41
+ * @property {boolean} [tableHeaderRow] - Insert a header row (<thead><th>) when creating tables
42
+ * @property {Function} [onPaste] - Callback fired on every paste: ({ text, html }) => void
43
+ * @property {Function} [onSelectionChange] - Callback fired on cursor/selection change: (context) => void
44
+ * @property {string[]} [colorSwatches] - Custom brand colour swatches prepended to the colour-picker palette
45
+ * @property {Function} [onDestroy] - Callback fired when the editor is destroyed: (context) => void
46
+ * @property {Function} [onCharLimitReached] - Callback fired when the character limit is hit: (context) => void
47
+ * @property {Function} [onWordLimitReached] - Callback fired when the word limit is hit: (context) => void
48
+ */
49
+
50
+ /** @type {AsnOptions} */
51
+ export const defaultOptions = {
52
+ placeholder: '',
53
+ height: 200,
54
+ minHeight: 100,
55
+ maxHeight: 0,
56
+ focus: false,
57
+ resizable: true,
58
+ toolbar: defaultToolbar,
59
+ // UI integration options
60
+ // If `useBootstrap` is true, toolbar buttons will use the Bootstrap button classes
61
+ // (set `toolbarButtonClass` to customize). Works with Bootstrap 4 and 5.
62
+ useBootstrap: false,
63
+ toolbarButtonClass: 'btn btn-sm btn-light',
64
+ // Icon options — uses Font Awesome by default. Consumers must include Font Awesome CSS.
65
+ useFontAwesome: true,
66
+ // Default FontAwesome prefix — 'fas' for FA5, 'fa-solid' for FA6. Change if needed.
67
+ fontAwesomeClass: 'fas',
68
+ pasteAsPlainText: false,
69
+ pasteCleanHTML: true,
70
+ pasteStripAttributes: false,
71
+ allowImageUpload: true,
72
+ maxImageSize: 5,
73
+ tabSize: 4,
74
+ onChange: null,
75
+ onFocus: null,
76
+ onBlur: null,
77
+ onInit: null,
78
+ onImageUpload: null,
79
+ onImageError: null,
80
+ stickyToolbar: false,
81
+ stickyToolbarOffset: 0,
82
+ theme: 'light',
83
+ codeHighlight: true,
84
+ codeHighlightCDN: 'https://cdnjs.cloudflare.com/ajax/libs/prism/1.29.0',
85
+ markdownPaste: true,
86
+ historyLimit: 100,
87
+ // Default font family applied to the editor and shown in the dropdown when no explicit font is set
88
+ defaultFontFamily: 'Arial',
89
+ // Default font size applied to the editor and shown in the size dropdown when no explicit size is set
90
+ defaultFontSize: '14px',
91
+ // Font families shown in the toolbar font-family dropdown
92
+ fontFamilies: [
93
+ 'Arial',
94
+ 'Arial Black',
95
+ 'Comic Sans MS',
96
+ 'Courier New',
97
+ 'Georgia',
98
+ 'Impact',
99
+ 'Tahoma',
100
+ 'Times New Roman',
101
+ 'Trebuchet MS',
102
+ 'Verdana',
103
+ ],
104
+ // Read-only mode — disables all editing when true
105
+ readOnly: false,
106
+ // Enable/disable browser spell-check on the editable area
107
+ spellcheck: true,
108
+ // Text direction: 'ltr' (default) or 'rtl'
109
+ direction: 'ltr',
110
+ // How the toolbar handles overflow: 'wrap' (default, wraps to next line) or 'scroll' (single scrollable row)
111
+ toolbarOverflow: 'wrap',
112
+ // Auto-save content to localStorage on every change
113
+ autoSave: false,
114
+ // localStorage key used when autoSave is enabled
115
+ autoSaveKey: 'autumnnote-autosave',
116
+ // Maximum character count (0 = unlimited)
117
+ maxChars: 0,
118
+ // Maximum word count (0 = unlimited)
119
+ maxWords: 0,
120
+ // Insert a header row (<thead>) when creating new tables
121
+ tableHeaderRow: false,
122
+ // Callback fired on every cursor/selection change inside the editor
123
+ onSelectionChange: null,
124
+ // Custom brand colour swatches to prepend to the toolbar colour-picker palette
125
+ colorSwatches: [],
126
+ // Callback fired after a paste event: function({ text, html })
127
+ onPaste: null,
128
+ // Callback fired just before the editor instance is destroyed
129
+ onDestroy: null,
130
+ // Callback fired when the character limit is reached: function(context)
131
+ onCharLimitReached: null,
132
+ // Callback fired when the word limit is reached: function(context)
133
+ onWordLimitReached: null,
134
+ };
@@ -0,0 +1,48 @@
1
+ // AutumnNote — SCSS variables
2
+ // Inspired by Summernote's variables
3
+
4
+ // Colors
5
+ $an-primary: #3b82f6 !default; // blue-500
6
+ $an-primary-hover: #2563eb !default; // blue-600
7
+ $an-border: #d1d5db !default; // gray-300
8
+ $an-bg: #ffffff !default;
9
+ $an-bg-toolbar: #f9fafb !default; // gray-50
10
+ $an-bg-btn-hover: #f3f4f6 !default; // gray-100
11
+ $an-bg-btn-active: #dbeafe !default; // blue-100
12
+ $an-text: #111827 !default; // gray-900
13
+ $an-muted: #6b7280 !default; // gray-500
14
+ $an-statusbar-bg: #f9fafb !default;
15
+
16
+ // Dark theme color overrides (applied via .an-theme-dark on .an-container)
17
+ $an-dark-bg: #1e1e2e !default;
18
+ $an-dark-bg-toolbar: #181825 !default;
19
+ $an-dark-bg-btn-hover: #313244 !default;
20
+ $an-dark-bg-btn-active: #1e3a5f !default;
21
+ $an-dark-border: #45475a !default;
22
+ $an-dark-text: #cdd6f4 !default;
23
+ $an-dark-muted: #6c7086 !default;
24
+ $an-dark-statusbar-bg: #181825 !default;
25
+
26
+ // Fonts
27
+ $an-font-family: system-ui, -apple-system, 'Segoe UI', Roboto, Arial, sans-serif !default;
28
+ $an-font-size: 14px !default;
29
+ $an-line-height: 1.6 !default;
30
+
31
+ // Sizing
32
+ $an-radius: 6px !default;
33
+ $an-radius-sm: 4px !default;
34
+ $an-btn-height: 30px !default;
35
+ $an-btn-padding: 0 8px !default;
36
+ $an-toolbar-gap: 4px !default;
37
+ $an-toolbar-pad: 6px 8px !default;
38
+ $an-editable-pad: 12px 14px !default;
39
+ $an-statusbar-h: 24px !default;
40
+
41
+ // Dialog
42
+ $an-dialog-z: 9999 !default;
43
+ $an-dialog-bg: rgba(0, 0, 0, 0.45) !default;
44
+ $an-dialog-width: 420px !default;
45
+ $an-dialog-radius: 8px !default;
46
+
47
+ // Transitions
48
+ $an-transition: 0.15s ease !default;