@oix1987/yjd 2.1.1 → 2.1.2
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/README.md +31 -3
- package/dist/core.esm.js +1 -1
- package/dist/core.esm.js.map +1 -1
- package/dist/rich-editor.esm.js +1 -1
- package/dist/rich-editor.esm.js.map +1 -1
- package/dist/rich-editor.min.js +1 -1
- package/dist/rich-editor.min.js.map +1 -1
- package/index.d.ts +9 -6
- package/index.js +3 -94
- package/lib/core/editor.js +78 -0
- package/package.json +1 -1
package/index.d.ts
CHANGED
|
@@ -149,6 +149,14 @@ export interface EditorOptions {
|
|
|
149
149
|
|
|
150
150
|
export class Editor {
|
|
151
151
|
constructor(selector: string | Element, options?: EditorOptions);
|
|
152
|
+
/**
|
|
153
|
+
* Progressive-enhance a <textarea> into an editor with two-way sync + a
|
|
154
|
+
* controller (getValue/setValue/destroy). Available from `/core` too.
|
|
155
|
+
*/
|
|
156
|
+
static fromTextarea(
|
|
157
|
+
textarea: HTMLTextAreaElement | string,
|
|
158
|
+
options?: EditorOptions & { format?: 'html' | 'markdown' }
|
|
159
|
+
): TextareaEditor;
|
|
152
160
|
/** The contentEditable element (public — apps may attach listeners to it). */
|
|
153
161
|
editor: HTMLElement;
|
|
154
162
|
on(event: string, handler: (data: any) => void): void;
|
|
@@ -200,12 +208,7 @@ export class RichEditor extends Editor {
|
|
|
200
208
|
static register(path: string, definition: any, suppressWarning?: boolean): void;
|
|
201
209
|
static get(path: string): any;
|
|
202
210
|
static create(selector: string | Element, options?: EditorOptions): RichEditor;
|
|
203
|
-
/**
|
|
204
|
-
* Progressive-enhance a <textarea> into an editor with TWO-WAY sync (editor
|
|
205
|
-
* edits update textarea.value + fire native events; writing textarea.value
|
|
206
|
-
* updates the editor). The returned editor also exposes a controller:
|
|
207
|
-
* getValue()/setValue()/destroy() (destroy restores the textarea).
|
|
208
|
-
*/
|
|
211
|
+
/** Inherited from Editor (returns a fully-featured RichEditor). */
|
|
209
212
|
static fromTextarea(
|
|
210
213
|
textarea: HTMLTextAreaElement | string,
|
|
211
214
|
options?: EditorOptions & { format?: 'html' | 'markdown' }
|
package/index.js
CHANGED
|
@@ -147,100 +147,9 @@ class RichEditor extends Editor {
|
|
|
147
147
|
return new RichEditor(selector, options);
|
|
148
148
|
}
|
|
149
149
|
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
*
|
|
154
|
-
* const ed = RichEditor.fromTextarea(document.querySelector('#body'), {
|
|
155
|
-
* // any editor option; `format` chooses how the textarea is read/written:
|
|
156
|
-
* format: 'html' | 'markdown', // default 'html'
|
|
157
|
-
* });
|
|
158
|
-
*
|
|
159
|
-
* The textarea's current value seeds the editor (parsed as HTML or Markdown
|
|
160
|
-
* per `format`). Binding is TWO-WAY: editor edits update textarea.value (and
|
|
161
|
-
* fire native input/change events), and writing `textarea.value = …` from app
|
|
162
|
-
* code (e.g. resetting a form) updates the editor. The returned editor also
|
|
163
|
-
* carries a small controller:
|
|
164
|
-
*
|
|
165
|
-
* const ed = RichEditor.fromTextarea('#body', { format: 'markdown' });
|
|
166
|
-
* ed.setValue(md); // load new content into the editor
|
|
167
|
-
* ed.getValue(); // current content (html or markdown per `format`)
|
|
168
|
-
* ed.destroy(); // remove the editor, restore the textarea + last value
|
|
169
|
-
*
|
|
170
|
-
* @param {HTMLTextAreaElement|string} textarea Element or selector.
|
|
171
|
-
* @param {object} [options] Editor options + optional `format`.
|
|
172
|
-
* @returns {RichEditor}
|
|
173
|
-
*/
|
|
174
|
-
static fromTextarea(textarea, options = {}) {
|
|
175
|
-
const ta = typeof textarea === 'string' ? document.querySelector(textarea) : textarea;
|
|
176
|
-
if (!ta) throw new Error('RichEditor.fromTextarea: textarea not found');
|
|
177
|
-
|
|
178
|
-
const format = options.format === 'markdown' ? 'markdown' : 'html';
|
|
179
|
-
const read = (ed) => (format === 'markdown' ? ed.getMarkdown() : ed.getContent());
|
|
180
|
-
const write = (ed, v) => (format === 'markdown' ? ed.setMarkdown(v || '') : ed.setHTML(v || ''));
|
|
181
|
-
|
|
182
|
-
// Mount point right after the textarea; hide the original.
|
|
183
|
-
const mount = document.createElement('div');
|
|
184
|
-
ta.after(mount);
|
|
185
|
-
ta.style.display = 'none';
|
|
186
|
-
ta.setAttribute('aria-hidden', 'true');
|
|
187
|
-
|
|
188
|
-
// Take over textarea.value so app writes flow into the editor and reads
|
|
189
|
-
// reflect it. Keep the native descriptor to restore on destroy + to set the
|
|
190
|
-
// real value (for form submission) without re-triggering our setter.
|
|
191
|
-
const nativeDesc = Object.getOwnPropertyDescriptor(HTMLTextAreaElement.prototype, 'value');
|
|
192
|
-
let raw = ta.value || '';
|
|
193
|
-
let syncing = false; // guards editor→textarea writes from re-entering setValue
|
|
194
|
-
|
|
195
|
-
const initial = raw;
|
|
196
|
-
const editor = new RichEditor(mount, {
|
|
197
|
-
width: '100%',
|
|
198
|
-
...options,
|
|
199
|
-
content: options.content != null ? options.content
|
|
200
|
-
: (format === 'markdown' ? markdownToHtml(initial) : initial),
|
|
201
|
-
});
|
|
202
|
-
|
|
203
|
-
Object.defineProperty(ta, 'value', {
|
|
204
|
-
configurable: true,
|
|
205
|
-
get() { return raw; },
|
|
206
|
-
set(v) {
|
|
207
|
-
raw = v == null ? '' : String(v);
|
|
208
|
-
nativeDesc.set.call(ta, raw); // keep the real textarea value for submits
|
|
209
|
-
if (!syncing) write(editor, raw); // app write → update editor
|
|
210
|
-
},
|
|
211
|
-
});
|
|
212
|
-
|
|
213
|
-
// editor edit → push to textarea + fire native events for app bindings.
|
|
214
|
-
const onChange = () => {
|
|
215
|
-
const next = read(editor);
|
|
216
|
-
if (raw === next) return;
|
|
217
|
-
raw = next;
|
|
218
|
-
syncing = true;
|
|
219
|
-
nativeDesc.set.call(ta, next);
|
|
220
|
-
ta.dispatchEvent(new Event('input', { bubbles: true }));
|
|
221
|
-
ta.dispatchEvent(new Event('change', { bubbles: true }));
|
|
222
|
-
syncing = false;
|
|
223
|
-
};
|
|
224
|
-
editor.on('change', onChange);
|
|
225
|
-
onChange(); // normalise textarea to the editor's serialization up front.
|
|
226
|
-
|
|
227
|
-
// Controller surface on the editor instance.
|
|
228
|
-
editor.textarea = ta;
|
|
229
|
-
editor.getValue = () => read(editor);
|
|
230
|
-
editor.setValue = (v) => { write(editor, v); };
|
|
231
|
-
const baseDestroy = editor.destroy.bind(editor);
|
|
232
|
-
editor.destroy = () => {
|
|
233
|
-
const last = read(editor);
|
|
234
|
-
editor.off('change', onChange);
|
|
235
|
-
baseDestroy();
|
|
236
|
-
mount.remove();
|
|
237
|
-
delete ta.value; // restore the prototype's value accessor
|
|
238
|
-
nativeDesc.set.call(ta, last);
|
|
239
|
-
ta.style.display = '';
|
|
240
|
-
ta.removeAttribute('aria-hidden');
|
|
241
|
-
};
|
|
242
|
-
return editor;
|
|
243
|
-
}
|
|
150
|
+
// fromTextarea() is inherited from the base Editor (defined in core so it is
|
|
151
|
+
// also available from the tree-shakeable /core entry). RichEditor.fromTextarea
|
|
152
|
+
// therefore returns a fully-featured RichEditor (via `new this(...)`).
|
|
244
153
|
}
|
|
245
154
|
|
|
246
155
|
// Export classes for extension. `yjd` is the brand-aligned name; `RichEditor`
|
package/lib/core/editor.js
CHANGED
|
@@ -1090,6 +1090,84 @@ export default class Editor {
|
|
|
1090
1090
|
return `${n >= 10 || i === 0 ? Math.round(n) : n.toFixed(1)} ${u[i]}`;
|
|
1091
1091
|
}
|
|
1092
1092
|
|
|
1093
|
+
/**
|
|
1094
|
+
* Progressive-enhance a <textarea> into an editor with TWO-WAY sync, returning
|
|
1095
|
+
* the editor with a controller (getValue/setValue/destroy). Defined on the
|
|
1096
|
+
* base Editor so it is available from the tree-shakeable `/core` entry too
|
|
1097
|
+
* (no need to pull the all-in-one build just for fromTextarea).
|
|
1098
|
+
*
|
|
1099
|
+
* const ed = Editor.fromTextarea('#body', { format: 'markdown' });
|
|
1100
|
+
*
|
|
1101
|
+
* @param {HTMLTextAreaElement|string} textarea
|
|
1102
|
+
* @param {object} [options] Editor options + optional `format: 'html'|'markdown'`.
|
|
1103
|
+
* @returns {Editor}
|
|
1104
|
+
*/
|
|
1105
|
+
static fromTextarea(textarea, options = {}) {
|
|
1106
|
+
const ta = typeof textarea === 'string' ? document.querySelector(textarea) : textarea;
|
|
1107
|
+
if (!ta) throw new Error('Editor.fromTextarea: textarea not found');
|
|
1108
|
+
|
|
1109
|
+
const format = options.format === 'markdown' ? 'markdown' : 'html';
|
|
1110
|
+
const read = (ed) => (format === 'markdown' ? ed.getMarkdown() : ed.getContent());
|
|
1111
|
+
const writeVal = (ed, v) => (format === 'markdown' ? ed.setMarkdown(v || '') : ed.setHTML(v || ''));
|
|
1112
|
+
|
|
1113
|
+
const mount = document.createElement('div');
|
|
1114
|
+
ta.after(mount);
|
|
1115
|
+
ta.style.display = 'none';
|
|
1116
|
+
ta.setAttribute('aria-hidden', 'true');
|
|
1117
|
+
|
|
1118
|
+
const nativeDesc = Object.getOwnPropertyDescriptor(HTMLTextAreaElement.prototype, 'value');
|
|
1119
|
+
let raw = ta.value || '';
|
|
1120
|
+
let syncing = false;
|
|
1121
|
+
|
|
1122
|
+
const initial = raw;
|
|
1123
|
+
// `this` is the class fromTextarea was called on (Editor or a subclass).
|
|
1124
|
+
const editor = new this(mount, {
|
|
1125
|
+
width: '100%',
|
|
1126
|
+
...options,
|
|
1127
|
+
content: options.content != null ? options.content
|
|
1128
|
+
: (format === 'markdown' ? markdownToHtml(initial) : initial),
|
|
1129
|
+
});
|
|
1130
|
+
|
|
1131
|
+
Object.defineProperty(ta, 'value', {
|
|
1132
|
+
configurable: true,
|
|
1133
|
+
get() { return raw; },
|
|
1134
|
+
set(v) {
|
|
1135
|
+
raw = v == null ? '' : String(v);
|
|
1136
|
+
nativeDesc.set.call(ta, raw);
|
|
1137
|
+
if (!syncing) writeVal(editor, raw);
|
|
1138
|
+
},
|
|
1139
|
+
});
|
|
1140
|
+
|
|
1141
|
+
const onChange = () => {
|
|
1142
|
+
const next = read(editor);
|
|
1143
|
+
if (raw === next) return;
|
|
1144
|
+
raw = next;
|
|
1145
|
+
syncing = true;
|
|
1146
|
+
nativeDesc.set.call(ta, next);
|
|
1147
|
+
ta.dispatchEvent(new Event('input', { bubbles: true }));
|
|
1148
|
+
ta.dispatchEvent(new Event('change', { bubbles: true }));
|
|
1149
|
+
syncing = false;
|
|
1150
|
+
};
|
|
1151
|
+
editor.on('change', onChange);
|
|
1152
|
+
onChange();
|
|
1153
|
+
|
|
1154
|
+
editor.textarea = ta;
|
|
1155
|
+
editor.getValue = () => read(editor);
|
|
1156
|
+
editor.setValue = (v) => { writeVal(editor, v); };
|
|
1157
|
+
const baseDestroy = editor.destroy.bind(editor);
|
|
1158
|
+
editor.destroy = () => {
|
|
1159
|
+
const last = read(editor);
|
|
1160
|
+
editor.off('change', onChange);
|
|
1161
|
+
baseDestroy();
|
|
1162
|
+
mount.remove();
|
|
1163
|
+
delete ta.value;
|
|
1164
|
+
nativeDesc.set.call(ta, last);
|
|
1165
|
+
ta.style.display = '';
|
|
1166
|
+
ta.removeAttribute('aria-hidden');
|
|
1167
|
+
};
|
|
1168
|
+
return editor;
|
|
1169
|
+
}
|
|
1170
|
+
|
|
1093
1171
|
/**
|
|
1094
1172
|
* Open the native picker for a non-image attachment, then insert it as a
|
|
1095
1173
|
* file chip via the options.file.upload hook.
|
package/package.json
CHANGED