@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/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
- * Progressive-enhance a <textarea>: hide it, mount an editor in its place,
152
- * and keep the textarea's value in sync so existing form submits keep working.
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`
@@ -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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@oix1987/yjd",
3
- "version": "2.1.1",
3
+ "version": "2.1.2",
4
4
  "description": "A powerful rich text editor with real-time content change tracking for web applications using base javascript",
5
5
  "license": "ISC",
6
6
  "author": "Oix1987",