fable-editor 1.0.2 → 1.2.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/README.md CHANGED
@@ -7,14 +7,18 @@ A rich text editor packaged as an npm library with first-class React and Angular
7
7
  | Feature | Details |
8
8
  |---|---|
9
9
  | Toolbar & menubar | Configurable via `toolbar`/`menubar` strings (`\|`-separated groups), or use the built-in defaults. Menus: File, Edit, View, Insert, Format, Tools, Table, Help |
10
- | Tables | Resize handles, row/column insert & delete, cell/row/column/table properties, context toolbar |
10
+ | Responsive toolbar | On narrow screens (mobile/tablet) or in a narrow host container — the toolbar collapses to a single row with a trailing `…` button that expands/collapses the full set. Automatic, width-driven, no configuration needed |
11
+ | Tables | Resize handles, row/column insert & delete, row/column reordering (move a row up/down or a column left/right from the context toolbar or Table menu), cell background color picker (build header rows in one click), cell/row/column/table properties, context toolbar |
11
12
  | Paste handling | PowerPaste-style clean paste from Word / Google Docs / Excel |
12
13
  | Internationalization | English / Arabic, automatic RTL/LTR switching |
13
- | Fonts & formatting | Configurable font list (`fontFamilyFormats`), sizes, line-height, word-spacing, text/background color, change case |
14
+ | Fonts & formatting | Configurable font list (`fontFamilyFormats`), sizes, line-height, word-spacing, letter-spacing, text/background color, change case |
15
+ | Theming | `primaryColor` (accent, default `#df3c2b`), `toolbarGroupBackground` and `uiFontFamily` (default Noto Sans stack) options restyle the editor chrome via CSS variables |
14
16
  | Custom content styling | `contentStyle` injects scoped CSS into the editable area (e.g. default font/size) |
15
17
  | Images | Placeholder upload UI, drag-and-drop, configurable accepted file types (`imageFileTypes`), pluggable async upload handler |
18
+ | Video | Insert/edit dialog with **General** (source URL / upload / width / height), **Embed** (paste embed code) and **Advanced** (alternative source, poster image) tabs. YouTube / Vimeo / Dailymotion page URLs become embedded players — including when pasted directly into the editor. Template media slots can host a video instead of an image. |
19
+ | Code | Inline `code` (toolbar button, or type `` `snippet` `` — converts on the closing backtick) and code sample blocks: a dialog with language dropdown + paste-in textarea inserts a styled block with a language header; click a block to edit, copy or delete it. No dependencies, no highlighting engine |
16
20
  | Documents | Import `.docx` files, source-code view, print preview |
17
- | Productivity | Undo/redo, revision history, autosave draft restore, word count, special characters, page breaks |
21
+ | Productivity | Undo/redo, revision history, autosave draft restore, word count, special characters & emoji pickers (category tabs + glyph grid), page breaks |
18
22
  | Fullscreen | Toggle fullscreen editing |
19
23
  | Help | Built-in shortcuts/help dialog |
20
24
 
@@ -68,9 +72,46 @@ editor.destroy();
68
72
  | `imageFileTypes` | `string[]` | common image MIME types | `accept` list for the native image file picker. |
69
73
  | `imageUploadHandler` | `(file: File) => Promise<string>` | — | Resolve with a URL after uploading; omit to inline images as base64. |
70
74
  | `onImageUploadError` | `(error, file) => void` | — | Called when `imageUploadHandler` rejects. |
75
+ | `videoFileTypes` | `string[]` | `['video/mp4','video/webm','video/ogg']` | `accept` list for the native video file picker. |
76
+ | `videoUploadHandler` | `(file: File) => Promise<string>` | — | Resolve with a URL after uploading; omit to inline videos as base64. |
77
+ | `onVideoUploadError` | `(error, file) => void` | — | Called when `videoUploadHandler` rejects. |
78
+ | `primaryColor` | `string` | `'#df3c2b'` | Accent color for active states, primary buttons and selection outlines. Any CSS color. Applied as the `--fable-primary` CSS variable. |
79
+ | `toolbarGroupBackground` | `string` | `'#f1f3f6'` | Background of each toolbar button group (the rounded pills). Applied as `--fable-tgrp-bg`. |
80
+ | `uiFontFamily` | `string` | Noto Sans stack | Font family for the editor UI (menubar, toolbar, dialogs). Load the Noto Sans webfont in your app to use the default, or pass your own family. Applied as `--fable-ui-font`. |
71
81
  | `draftKey` | `string` | current page path | Storage key suffix for autosaved drafts. |
72
82
  | `onChange` / `onReady` | functions | — | Content-change and ready callbacks (usually set by the React/Angular wrapper instead). |
73
83
 
84
+ ### Theming
85
+
86
+ The editor's accent color, toolbar group background and UI font are driven by CSS variables, so they can be set per instance:
87
+
88
+ ```ts
89
+ new FableEditor({
90
+ target: el,
91
+ primaryColor: '#df3c2b', // accent (default)
92
+ toolbarGroupBackground: '#f1f3f6', // toolbar pill background (default)
93
+ uiFontFamily: "'Noto Sans', sans-serif" // UI font (default stack)
94
+ });
95
+ ```
96
+
97
+ The same values are available as React props / Angular inputs. The UI font defaults to a **Noto Sans** stack — load the webfont in your app (e.g. from Google Fonts) or pass any family you've already loaded. Alternatively, set the variables yourself in CSS: `--fable-primary`, `--fable-tgrp-bg`, `--fable-ui-font`.
98
+
99
+ ### Toolbar & menubar configuration
100
+
101
+ The `toolbar`/`menubar` options accept the same style of layout string as TinyMCE — `|` separates visual groups, spaces separate items:
102
+
103
+ ```ts
104
+ new FableEditor({
105
+ target: document.querySelector('#default-editor')!,
106
+ toolbar:
107
+ 'undo redo | styles | bold italic underline strikethrough | ' +
108
+ 'alignleft aligncenter alignright alignjustify | bullist numlist outdent indent | link image',
109
+ menubar: 'file edit insert format'
110
+ });
111
+ ```
112
+
113
+ Available toolbar items: `undo redo preview print importword revhistory fontfamily fontsize fontsizeincrease fontsizedecrease bold italic underline strikethrough forecolor backcolor alignleft aligncenter alignright alignjustify bullist numlist outdent indent link blockquote code changecase lineheight wordspacing letterspacing removeformat blocks ltr rtl quickimage quickvideo quicktable codesample template charmap emoji fullscreen sourcecode`. The TinyMCE names `styles` (→ `blocks`), `image` (→ `quickimage`), `media` (→ `quickvideo`) and `table` (→ `quicktable`) are accepted as aliases, so a typical TinyMCE toolbar string works unchanged. Unknown tokens are skipped with a console warning. Menubar keys: `file edit view insert format tools table help`.
114
+
74
115
  ## React
75
116
 
76
117
  ```tsx
@@ -86,12 +127,15 @@ function App() {
86
127
  onChange={setValue}
87
128
  language="en"
88
129
  height={400}
130
+ toolbar="undo redo | styles | bold italic underline strikethrough | alignleft aligncenter alignright alignjustify | bullist numlist outdent indent | link image"
89
131
  />
90
132
  );
91
133
  }
92
134
  ```
93
135
 
94
- Props: `value`, `defaultValue`, `onChange`, `language`, `height`, `menubar`, `toolbar`, `statusbar`, `readonly`, `fontFamilyFormats`, `contentStyle`, `imageFileTypes`, `imageUploadHandler`, `onImageUploadError`, `init`, `className`, `style` (see the [`init` options](#init-options) table above every option is also a top-level prop). A ref exposes `getContent`, `setContent`, `insertContent`, `setLanguage`, `focus`, `destroy`.
136
+ `toolbar`/`menubar` accept the same layout strings as the core option (see [Toolbar & menubar configuration](#toolbar--menubar-configuration)), so each app can show exactly the controls it needs.
137
+
138
+ Props: `value`, `defaultValue`, `onChange`, `language`, `height`, `menubar`, `toolbar`, `statusbar`, `readonly`, `primaryColor`, `toolbarGroupBackground`, `uiFontFamily`, `fontFamilyFormats`, `contentStyle`, `imageFileTypes`, `imageUploadHandler`, `onImageUploadError`, `videoFileTypes`, `videoUploadHandler`, `onVideoUploadError`, `init`, `className`, `style` (see the [`init` options](#init-options) table above — every option is also a top-level prop). A ref exposes `getContent`, `setContent`, `insertContent`, `setLanguage`, `focus`, `destroy`.
95
139
 
96
140
  ## Angular
97
141
 
@@ -118,10 +162,15 @@ export class AppModule {}
118
162
  Use in a template:
119
163
 
120
164
  ```html
121
- <fable-editor [(ngModel)]="content" language="en" [height]="400"></fable-editor>
165
+ <fable-editor
166
+ [(ngModel)]="content"
167
+ language="en"
168
+ [height]="400"
169
+ toolbar="undo redo | styles | bold italic underline strikethrough | alignleft aligncenter alignright alignjustify | bullist numlist outdent indent | link image">
170
+ </fable-editor>
122
171
  ```
123
172
 
124
- Inputs: `language`, `height`, `menubar`, `toolbar`, `statusbar`, `readonly`, `init`. `menubar`/`toolbar` accept a string directly (e.g. `[toolbar]="'undo redo | bold italic'"`); `fontFamilyFormats`, `contentStyle`, `imageFileTypes`, `imageUploadHandler`, and `onImageUploadError` aren't top-level inputs — pass them via `[init]="{ contentStyle: '...' }"` (see the [`init` options](#init-options) table above). Outputs: `editorChange`, `editorReady`. Works with `ngModel` and `formControlName`.
173
+ Inputs: `language`, `height`, `menubar`, `toolbar`, `statusbar`, `readonly`, `primaryColor`, `toolbarGroupBackground`, `uiFontFamily`, `init`. `menubar`/`toolbar` accept a string directly (e.g. `[toolbar]="'undo redo | bold italic'"`); `fontFamilyFormats`, `contentStyle`, `imageFileTypes`, `imageUploadHandler`, `onImageUploadError`, `videoFileTypes`, `videoUploadHandler`, and `onVideoUploadError` aren't top-level inputs — pass them via `[init]="{ contentStyle: '...' }"` (see the [`init` options](#init-options) table above). Outputs: `editorChange`, `editorReady`. Works with `ngModel` and `formControlName`.
125
174
 
126
175
  ## Development & testing
127
176
 
@@ -7,14 +7,18 @@ A rich text editor packaged as an npm library with first-class React and Angular
7
7
  | Feature | Details |
8
8
  |---|---|
9
9
  | Toolbar & menubar | Configurable via `toolbar`/`menubar` strings (`\|`-separated groups), or use the built-in defaults. Menus: File, Edit, View, Insert, Format, Tools, Table, Help |
10
- | Tables | Resize handles, row/column insert & delete, cell/row/column/table properties, context toolbar |
10
+ | Responsive toolbar | On narrow screens (mobile/tablet) or in a narrow host container — the toolbar collapses to a single row with a trailing `…` button that expands/collapses the full set. Automatic, width-driven, no configuration needed |
11
+ | Tables | Resize handles, row/column insert & delete, row/column reordering (move a row up/down or a column left/right from the context toolbar or Table menu), cell background color picker (build header rows in one click), cell/row/column/table properties, context toolbar |
11
12
  | Paste handling | PowerPaste-style clean paste from Word / Google Docs / Excel |
12
13
  | Internationalization | English / Arabic, automatic RTL/LTR switching |
13
- | Fonts & formatting | Configurable font list (`fontFamilyFormats`), sizes, line-height, word-spacing, text/background color, change case |
14
+ | Fonts & formatting | Configurable font list (`fontFamilyFormats`), sizes, line-height, word-spacing, letter-spacing, text/background color, change case |
15
+ | Theming | `primaryColor` (accent, default `#df3c2b`), `toolbarGroupBackground` and `uiFontFamily` (default Noto Sans stack) options restyle the editor chrome via CSS variables |
14
16
  | Custom content styling | `contentStyle` injects scoped CSS into the editable area (e.g. default font/size) |
15
17
  | Images | Placeholder upload UI, drag-and-drop, configurable accepted file types (`imageFileTypes`), pluggable async upload handler |
18
+ | Video | Insert/edit dialog with **General** (source URL / upload / width / height), **Embed** (paste embed code) and **Advanced** (alternative source, poster image) tabs. YouTube / Vimeo / Dailymotion page URLs become embedded players — including when pasted directly into the editor. Template media slots can host a video instead of an image. |
19
+ | Code | Inline `code` (toolbar button, or type `` `snippet` `` — converts on the closing backtick) and code sample blocks: a dialog with language dropdown + paste-in textarea inserts a styled block with a language header; click a block to edit, copy or delete it. No dependencies, no highlighting engine |
16
20
  | Documents | Import `.docx` files, source-code view, print preview |
17
- | Productivity | Undo/redo, revision history, autosave draft restore, word count, special characters, page breaks |
21
+ | Productivity | Undo/redo, revision history, autosave draft restore, word count, special characters & emoji pickers (category tabs + glyph grid), page breaks |
18
22
  | Fullscreen | Toggle fullscreen editing |
19
23
  | Help | Built-in shortcuts/help dialog |
20
24
 
@@ -68,9 +72,46 @@ editor.destroy();
68
72
  | `imageFileTypes` | `string[]` | common image MIME types | `accept` list for the native image file picker. |
69
73
  | `imageUploadHandler` | `(file: File) => Promise<string>` | — | Resolve with a URL after uploading; omit to inline images as base64. |
70
74
  | `onImageUploadError` | `(error, file) => void` | — | Called when `imageUploadHandler` rejects. |
75
+ | `videoFileTypes` | `string[]` | `['video/mp4','video/webm','video/ogg']` | `accept` list for the native video file picker. |
76
+ | `videoUploadHandler` | `(file: File) => Promise<string>` | — | Resolve with a URL after uploading; omit to inline videos as base64. |
77
+ | `onVideoUploadError` | `(error, file) => void` | — | Called when `videoUploadHandler` rejects. |
78
+ | `primaryColor` | `string` | `'#df3c2b'` | Accent color for active states, primary buttons and selection outlines. Any CSS color. Applied as the `--fable-primary` CSS variable. |
79
+ | `toolbarGroupBackground` | `string` | `'#f1f3f6'` | Background of each toolbar button group (the rounded pills). Applied as `--fable-tgrp-bg`. |
80
+ | `uiFontFamily` | `string` | Noto Sans stack | Font family for the editor UI (menubar, toolbar, dialogs). Load the Noto Sans webfont in your app to use the default, or pass your own family. Applied as `--fable-ui-font`. |
71
81
  | `draftKey` | `string` | current page path | Storage key suffix for autosaved drafts. |
72
82
  | `onChange` / `onReady` | functions | — | Content-change and ready callbacks (usually set by the React/Angular wrapper instead). |
73
83
 
84
+ ### Theming
85
+
86
+ The editor's accent color, toolbar group background and UI font are driven by CSS variables, so they can be set per instance:
87
+
88
+ ```ts
89
+ new FableEditor({
90
+ target: el,
91
+ primaryColor: '#df3c2b', // accent (default)
92
+ toolbarGroupBackground: '#f1f3f6', // toolbar pill background (default)
93
+ uiFontFamily: "'Noto Sans', sans-serif" // UI font (default stack)
94
+ });
95
+ ```
96
+
97
+ The same values are available as React props / Angular inputs. The UI font defaults to a **Noto Sans** stack — load the webfont in your app (e.g. from Google Fonts) or pass any family you've already loaded. Alternatively, set the variables yourself in CSS: `--fable-primary`, `--fable-tgrp-bg`, `--fable-ui-font`.
98
+
99
+ ### Toolbar & menubar configuration
100
+
101
+ The `toolbar`/`menubar` options accept the same style of layout string as TinyMCE — `|` separates visual groups, spaces separate items:
102
+
103
+ ```ts
104
+ new FableEditor({
105
+ target: document.querySelector('#default-editor')!,
106
+ toolbar:
107
+ 'undo redo | styles | bold italic underline strikethrough | ' +
108
+ 'alignleft aligncenter alignright alignjustify | bullist numlist outdent indent | link image',
109
+ menubar: 'file edit insert format'
110
+ });
111
+ ```
112
+
113
+ Available toolbar items: `undo redo preview print importword revhistory fontfamily fontsize fontsizeincrease fontsizedecrease bold italic underline strikethrough forecolor backcolor alignleft aligncenter alignright alignjustify bullist numlist outdent indent link blockquote code changecase lineheight wordspacing letterspacing removeformat blocks ltr rtl quickimage quickvideo quicktable codesample template charmap emoji fullscreen sourcecode`. The TinyMCE names `styles` (→ `blocks`), `image` (→ `quickimage`), `media` (→ `quickvideo`) and `table` (→ `quicktable`) are accepted as aliases, so a typical TinyMCE toolbar string works unchanged. Unknown tokens are skipped with a console warning. Menubar keys: `file edit view insert format tools table help`.
114
+
74
115
  ## React
75
116
 
76
117
  ```tsx
@@ -86,12 +127,15 @@ function App() {
86
127
  onChange={setValue}
87
128
  language="en"
88
129
  height={400}
130
+ toolbar="undo redo | styles | bold italic underline strikethrough | alignleft aligncenter alignright alignjustify | bullist numlist outdent indent | link image"
89
131
  />
90
132
  );
91
133
  }
92
134
  ```
93
135
 
94
- Props: `value`, `defaultValue`, `onChange`, `language`, `height`, `menubar`, `toolbar`, `statusbar`, `readonly`, `fontFamilyFormats`, `contentStyle`, `imageFileTypes`, `imageUploadHandler`, `onImageUploadError`, `init`, `className`, `style` (see the [`init` options](#init-options) table above every option is also a top-level prop). A ref exposes `getContent`, `setContent`, `insertContent`, `setLanguage`, `focus`, `destroy`.
136
+ `toolbar`/`menubar` accept the same layout strings as the core option (see [Toolbar & menubar configuration](#toolbar--menubar-configuration)), so each app can show exactly the controls it needs.
137
+
138
+ Props: `value`, `defaultValue`, `onChange`, `language`, `height`, `menubar`, `toolbar`, `statusbar`, `readonly`, `primaryColor`, `toolbarGroupBackground`, `uiFontFamily`, `fontFamilyFormats`, `contentStyle`, `imageFileTypes`, `imageUploadHandler`, `onImageUploadError`, `videoFileTypes`, `videoUploadHandler`, `onVideoUploadError`, `init`, `className`, `style` (see the [`init` options](#init-options) table above — every option is also a top-level prop). A ref exposes `getContent`, `setContent`, `insertContent`, `setLanguage`, `focus`, `destroy`.
95
139
 
96
140
  ## Angular
97
141
 
@@ -118,10 +162,15 @@ export class AppModule {}
118
162
  Use in a template:
119
163
 
120
164
  ```html
121
- <fable-editor [(ngModel)]="content" language="en" [height]="400"></fable-editor>
165
+ <fable-editor
166
+ [(ngModel)]="content"
167
+ language="en"
168
+ [height]="400"
169
+ toolbar="undo redo | styles | bold italic underline strikethrough | alignleft aligncenter alignright alignjustify | bullist numlist outdent indent | link image">
170
+ </fable-editor>
122
171
  ```
123
172
 
124
- Inputs: `language`, `height`, `menubar`, `toolbar`, `statusbar`, `readonly`, `init`. `menubar`/`toolbar` accept a string directly (e.g. `[toolbar]="'undo redo | bold italic'"`); `fontFamilyFormats`, `contentStyle`, `imageFileTypes`, `imageUploadHandler`, and `onImageUploadError` aren't top-level inputs — pass them via `[init]="{ contentStyle: '...' }"` (see the [`init` options](#init-options) table above). Outputs: `editorChange`, `editorReady`. Works with `ngModel` and `formControlName`.
173
+ Inputs: `language`, `height`, `menubar`, `toolbar`, `statusbar`, `readonly`, `primaryColor`, `toolbarGroupBackground`, `uiFontFamily`, `init`. `menubar`/`toolbar` accept a string directly (e.g. `[toolbar]="'undo redo | bold italic'"`); `fontFamilyFormats`, `contentStyle`, `imageFileTypes`, `imageUploadHandler`, `onImageUploadError`, `videoFileTypes`, `videoUploadHandler`, and `onVideoUploadError` aren't top-level inputs — pass them via `[init]="{ contentStyle: '...' }"` (see the [`init` options](#init-options) table above). Outputs: `editorChange`, `editorReady`. Works with `ngModel` and `formControlName`.
125
174
 
126
175
  ## Development & testing
127
176
 
@@ -9,6 +9,9 @@ export declare class FableEditorComponent implements OnInit, OnChanges, OnDestro
9
9
  toolbar: boolean | string;
10
10
  statusbar: boolean;
11
11
  readonly: boolean;
12
+ primaryColor?: string;
13
+ toolbarGroupBackground?: string;
14
+ uiFontFamily?: string;
12
15
  init: Partial<Omit<EditorInitOptions, 'target'>>;
13
16
  value: string;
14
17
  editorChange: EventEmitter<string>;
@@ -25,5 +28,5 @@ export declare class FableEditorComponent implements OnInit, OnChanges, OnDestro
25
28
  focus(): void;
26
29
  writeValue(value: string): void;
27
30
  static ɵfac: i0.ɵɵFactoryDeclaration<FableEditorComponent, never>;
28
- static ɵcmp: i0.ɵɵComponentDeclaration<FableEditorComponent, "fable-editor", never, { "language": { "alias": "language"; "required": false; }; "height": { "alias": "height"; "required": false; }; "menubar": { "alias": "menubar"; "required": false; }; "toolbar": { "alias": "toolbar"; "required": false; }; "statusbar": { "alias": "statusbar"; "required": false; }; "readonly": { "alias": "readonly"; "required": false; }; "init": { "alias": "init"; "required": false; }; "value": { "alias": "value"; "required": false; }; }, { "editorChange": "editorChange"; "editorReady": "editorReady"; }, never, never, true, never>;
31
+ static ɵcmp: i0.ɵɵComponentDeclaration<FableEditorComponent, "fable-editor", never, { "language": { "alias": "language"; "required": false; }; "height": { "alias": "height"; "required": false; }; "menubar": { "alias": "menubar"; "required": false; }; "toolbar": { "alias": "toolbar"; "required": false; }; "statusbar": { "alias": "statusbar"; "required": false; }; "readonly": { "alias": "readonly"; "required": false; }; "primaryColor": { "alias": "primaryColor"; "required": false; }; "toolbarGroupBackground": { "alias": "toolbarGroupBackground"; "required": false; }; "uiFontFamily": { "alias": "uiFontFamily"; "required": false; }; "init": { "alias": "init"; "required": false; }; "value": { "alias": "value"; "required": false; }; }, { "editorChange": "editorChange"; "editorReady": "editorReady"; }, never, never, true, never>;
29
32
  }
@@ -10,8 +10,23 @@ export declare const MIN_FONT_PX = 1;
10
10
  export declare const MAX_FONT_PX = 100;
11
11
  export declare const LINE_HEIGHTS: string[];
12
12
  export declare const WORD_SPACINGS: string[];
13
+ export declare const LETTER_SPACINGS: string[];
14
+ /** Languages offered by the code-sample dialog: [value stored in data-lang, label]. */
15
+ export declare const CODE_LANGS: [string, string][];
13
16
  export declare const BLOCKS: [string, string][];
14
17
  /** Quick-access text color chips shown directly in the toolbar. */
15
18
  export declare const QUICK_COLORS: string[];
19
+ /** 25 swatches — a 5×5 grid in the picker (pastels, brights, darks, grays,
20
+ * then pink/brown/cyan + black/white). Shared by fore/back color and cell fill. */
16
21
  export declare const COLORS: string[];
22
+ /** Category key = i18n string key for the tab label in the picker dialogs. */
23
+ export interface GlyphCategory {
24
+ key: string;
25
+ chars: string[];
26
+ }
27
+ /** Special-character map, grouped for the two-pane picker (tabs left, glyphs right).
28
+ * `CHARS` below is the legacy flat list and is kept exported for back-compat. */
29
+ export declare const CHAR_CATEGORIES: GlyphCategory[];
30
+ /** Emoji map for the emoji picker — same two-pane dialog as the character map. */
31
+ export declare const EMOJI_CATEGORIES: GlyphCategory[];
17
32
  export declare const CHARS: string[];
@@ -37,7 +37,20 @@ export declare class FableEditor implements FableEditorApi {
37
37
  private imageUploadHandler?;
38
38
  private onImageUploadError?;
39
39
  private contentStyle?;
40
+ private vidInput;
41
+ private vidActive;
42
+ private vidCtx;
43
+ private vidHideTimer;
44
+ private vphActive;
45
+ private vphCtx;
46
+ private vphUploadTarget;
47
+ private videoUploadHandler?;
48
+ private onVideoUploadError?;
40
49
  private selCtx;
50
+ private tbrMore;
51
+ private tbrExpanded;
52
+ private codeActive;
53
+ private codeCtx;
41
54
  private tplActive;
42
55
  private tplCtx;
43
56
  private docInput;
@@ -52,6 +65,10 @@ export declare class FableEditor implements FableEditorApi {
52
65
  private tArr;
53
66
  private dir;
54
67
  private initDOM;
68
+ /** Theme options land as CSS variables on the document root rather than the editor
69
+ * shell, because dialogs, popups and floating context toolbars are appended to
70
+ * document.body and would not inherit variables scoped to the shell. */
71
+ private applyTheme;
55
72
  /** Rewrites the literal word `body` in a user-supplied `contentStyle` string to a
56
73
  * selector scoped to this instance's content div. Everything else passes through
57
74
  * verbatim, since `.ed` is a plain div in the host page (not an iframe) and an
@@ -100,6 +117,10 @@ export declare class FableEditor implements FableEditorApi {
100
117
  private tsel;
101
118
  private setListStyle;
102
119
  private listMenu;
120
+ /** Shared color-picker popup body — swatch grid plus the current-color /
121
+ * no-color / custom-picker footer — so the fore/back toolbar menus and the
122
+ * table cell-background menu all show the identical popup. */
123
+ private buildPaletteInto;
103
124
  private colorMenu;
104
125
  private applyColor;
105
126
  private syncColorSwatches;
@@ -117,6 +138,7 @@ export declare class FableEditor implements FableEditorApi {
117
138
  private currentBlockStyle;
118
139
  private lineHeightMenu;
119
140
  private wordSpacingMenu;
141
+ private letterSpacingMenu;
120
142
  private closestAnchor;
121
143
  private linkDlg;
122
144
  private blocksMenu;
@@ -135,9 +157,31 @@ export declare class FableEditor implements FableEditorApi {
135
157
  * (the quick color chips); everything else yields one. */
136
158
  private buildToolbarRegistry;
137
159
  private buildToolbar;
160
+ /** xs/sm/md editors (< 992px wide) collapse an overflowing toolbar to one row +
161
+ * "…" expander; at lg widths and up the toolbar wraps to multiple rows as before. */
162
+ private static readonly TBR_COLLAPSE_MAX;
163
+ /** Responsive one-line toolbar: on mobile/tablet widths (or a narrow host
164
+ * container) a toolbar that can't fit on a single row collapses to one
165
+ * clipped row plus a trailing "…" button that expands/collapses the full
166
+ * set. Driven by the editor's own width, so it also applies to narrow
167
+ * panels on desktop. */
168
+ private updateToolbarOverflow;
138
169
  private mItem;
139
170
  private buildMenubar;
140
171
  private tableOp;
172
+ /** Reorders the current row/column: 'rowup'/'rowdown' swap the caret's row with
173
+ * its neighbour, 'colleft'/'colright' move the caret's column one position in
174
+ * DOM order across every row. The caret travels with the moved cell. */
175
+ private tableMove;
176
+ /** Align the whole table via auto margins — same semantics as the align
177
+ * field in the table-properties dialog. */
178
+ private alignTable;
179
+ /** Color grid applying a background to the caret's cell — the direct way to
180
+ * build header rows (the toolbar's backcolor highlights text, not cells). */
181
+ private buildCellFillInto;
182
+ /** style.backgroundColor reads back as rgb(...) — normalise to #rrggbb for the picker. */
183
+ private cssToHex;
184
+ private cellFillMenu;
141
185
  private applyTableProps;
142
186
  private tablePropsDlg;
143
187
  private currentCell;
@@ -156,7 +200,11 @@ export declare class FableEditor implements FableEditorApi {
156
200
  private helpDlg;
157
201
  private wordCountDlg;
158
202
  private previewDlg;
203
+ /** Two-pane picker shared by the special-character map and the emoji map:
204
+ * fixed category tabs on the left, the active category's glyphs on the right. */
205
+ private glyphPickerDlg;
159
206
  private charMap;
207
+ private emojiMap;
160
208
  private printDoc;
161
209
  private pickImage;
162
210
  private imgPhHTML;
@@ -165,6 +213,9 @@ export declare class FableEditor implements FableEditorApi {
165
213
  private removeImgPlaceholder;
166
214
  private selectImgPlaceholder;
167
215
  private renderPhCtxButtons;
216
+ /** Swaps a template slot's placeholder between image and video kinds, so the
217
+ * same media slot can be filled with either. */
218
+ private swapPlaceholderKind;
168
219
  private renderPhCtxUrlInput;
169
220
  private readImageFileInto;
170
221
  /** Runs the host-supplied imageUploadHandler instead of inlining base64,
@@ -172,6 +223,62 @@ export declare class FableEditor implements FableEditorApi {
172
223
  private uploadImageFile;
173
224
  private replacePlaceholderWithImage;
174
225
  private positionImgPhCtx;
226
+ private pickVideo;
227
+ private vidPhHTML;
228
+ private insertVideoPlaceholder;
229
+ private clearVphPlaceholderSel;
230
+ private removeVphPlaceholder;
231
+ private selectVphPlaceholder;
232
+ private renderVphCtxButtons;
233
+ private renderVphCtxUrlInput;
234
+ private readVideoFileInto;
235
+ /** Runs the host-supplied videoUploadHandler instead of inlining base64,
236
+ * so apps can route the file to S3 / Azure Blob / their own server. */
237
+ private uploadVideoFile;
238
+ private replacePlaceholderWithVideo;
239
+ /** Swaps a video placeholder for a hosted player iframe (YouTube & co.). */
240
+ private replacePlaceholderWithEmbed;
241
+ private positionVphCtx;
242
+ private cancelVidHide;
243
+ private clearVidHandles;
244
+ private selectVideo;
245
+ private buildVideoCtxToolbar;
246
+ private setVideoAlign;
247
+ private positionVidCtx;
248
+ /** Maps a page URL from a known video host to its iframe player URL. Returns
249
+ * null for anything else (e.g. direct .mp4 file URLs). */
250
+ private videoEmbedUrl;
251
+ private videoEmbedHTML;
252
+ /** Insert/edit-video dialog with General (source/width/height), Embed (embed
253
+ * code) and Advanced (alternative source, poster image) tabs. Hosted page
254
+ * URLs (YouTube & co.) become iframe players; anything else becomes a
255
+ * <video> tag. The General tab also offers the same upload path as before
256
+ * (videoUploadHandler when configured, else inline base64). */
257
+ private videoDlg;
258
+ private escapeAttr;
259
+ private escHtml;
260
+ /** The inline <code> run the caret sits in, if any (code blocks don't count). */
261
+ private closestInlineCode;
262
+ /** Wraps the selection in an inline <code> run, or unwraps the run the caret is
263
+ * in. Selections spanning multiple blocks are left alone (a <code> can't wrap
264
+ * across paragraphs without producing invalid markup). */
265
+ private toggleInlineCode;
266
+ /** Typing `snippet` converts to an inline <code> run the moment the closing
267
+ * backtick is typed, so code can be marked up without reaching for the toolbar. */
268
+ private tryCodeTyping;
269
+ /** Custom dropdown for dialog fields (a native <select> popup can't be styled):
270
+ * a field-styled button showing the current choice that opens a floating,
271
+ * scrollable option list. Read the current choice via `.value()`. */
272
+ private dlgSelect;
273
+ /** Insert/edit-code-sample dialog: language dropdown + monospace textarea to
274
+ * paste or type code into. Inserts a non-editable <pre class="code-block">
275
+ * whose header shows the language; click the block to edit / copy / delete. */
276
+ private codeDlg;
277
+ private clearCodeSel;
278
+ private selectCodeEl;
279
+ private buildCodeCtxToolbar;
280
+ private copyCodeBlock;
281
+ private positionCodeCtx;
175
282
  private templateMenu;
176
283
  /** Miniature div-based mockup of a layout, mirroring what the block looks
177
284
  like in the editor: grey box = image slot, bars = heading/paragraph. */
@@ -38,9 +38,14 @@ export interface I18nStrings {
38
38
  rowabove: string;
39
39
  rowbelow: string;
40
40
  delrow: string;
41
+ moverowup: string;
42
+ moverowdown: string;
41
43
  colbefore: string;
42
44
  colafter: string;
43
45
  delcol: string;
46
+ movecolleft: string;
47
+ movecolright: string;
48
+ cellbg: string;
44
49
  deltable: string;
45
50
  ltr: string;
46
51
  rtl: string;
@@ -87,6 +92,9 @@ export interface I18nStrings {
87
92
  tblcellpadding: string;
88
93
  tblborder: string;
89
94
  tblalign: string;
95
+ tblalignleft: string;
96
+ tblaligncenter: string;
97
+ tblalignright: string;
90
98
  alignnone: string;
91
99
  quote: string;
92
100
  link: string;
@@ -95,6 +103,7 @@ export interface I18nStrings {
95
103
  unlink: string;
96
104
  lineheight: string;
97
105
  wordspacing: string;
106
+ letterspacing: string;
98
107
  changecase: string;
99
108
  lowercase: string;
100
109
  uppercase: string;
@@ -140,6 +149,46 @@ export interface I18nStrings {
140
149
  tplimgtop: string;
141
150
  tplimgcenter: string;
142
151
  deltemplate: string;
152
+ video: string;
153
+ quickvideo: string;
154
+ deletevideo: string;
155
+ uploadvideo: string;
156
+ videolink: string;
157
+ videourlph: string;
158
+ insertvideo: string;
159
+ dropvideo: string;
160
+ videodlgttl: string;
161
+ vidgeneral: string;
162
+ vidembed: string;
163
+ vidadvanced: string;
164
+ vidsource: string;
165
+ vidembedhint: string;
166
+ vidaltsource: string;
167
+ vidposter: string;
168
+ emoji: string;
169
+ code: string;
170
+ codesample: string;
171
+ codedlgttl: string;
172
+ codelang: string;
173
+ editcode: string;
174
+ deletecode: string;
175
+ copycode: string;
176
+ chcat_currency: string;
177
+ chcat_text: string;
178
+ chcat_math: string;
179
+ chcat_arrows: string;
180
+ chcat_symbols: string;
181
+ chcat_latin: string;
182
+ chcat_arabic: string;
183
+ emcat_smileys: string;
184
+ emcat_people: string;
185
+ emcat_animals: string;
186
+ emcat_food: string;
187
+ emcat_travel: string;
188
+ emcat_activities: string;
189
+ emcat_objects: string;
190
+ emcat_symbols: string;
191
+ emcat_flags: string;
143
192
  }
144
193
  export declare const I18N: Record<EditorLanguage, I18nStrings>;
145
194
  export declare function getStrings(lang: EditorLanguage): I18nStrings;
@@ -50,6 +50,27 @@ export interface EditorInitOptions {
50
50
  imageUploadHandler?: (file: File) => Promise<string>;
51
51
  /** Called when imageUploadHandler rejects, so the host app can surface it. */
52
52
  onImageUploadError?: (error: unknown, file: File) => void;
53
+ /** MIME types accepted by the native video file picker (the `accept` attribute).
54
+ * Defaults to `['video/mp4','video/webm','video/ogg']`. */
55
+ videoFileTypes?: string[];
56
+ /** Called when a user picks or drops a video. Resolve with the URL to
57
+ * insert (e.g. after uploading to S3/Azure Blob/your own server). If
58
+ * omitted, videos are inlined as base64 data URIs instead. Mirrors
59
+ * `imageUploadHandler` — the engine owns all placeholder/progress/insert UI. */
60
+ videoUploadHandler?: (file: File) => Promise<string>;
61
+ /** Called when videoUploadHandler rejects, so the host app can surface it. */
62
+ onVideoUploadError?: (error: unknown, file: File) => void;
63
+ /** Accent color used for active states, primary buttons and selection outlines.
64
+ * Any CSS color. Defaults to `#df3c2b`. Applied as the `--fable-primary` CSS
65
+ * variable on the document root (dialogs/popups render outside the editor shell). */
66
+ primaryColor?: string;
67
+ /** Background of each toolbar button group (the rounded pills). Any CSS
68
+ * color/gradient. Defaults to `#f1f3f6`. Applied as `--fable-tgrp-bg`. */
69
+ toolbarGroupBackground?: string;
70
+ /** Font family for the editor UI (menubar, toolbar, dialogs, popups). Defaults to
71
+ * a 'Noto Sans' stack — load the Noto Sans webfont in your app to use it, or pass
72
+ * your own family here. Applied as `--fable-ui-font`. */
73
+ uiFontFamily?: string;
53
74
  }
54
75
  export interface FableEditorApi {
55
76
  getContent(): string;
@@ -9,6 +9,9 @@ export class FableEditorComponent {
9
9
  toolbar = true;
10
10
  statusbar = true;
11
11
  readonly = false;
12
+ primaryColor;
13
+ toolbarGroupBackground;
14
+ uiFontFamily;
12
15
  init = {};
13
16
  value = '';
14
17
  editorChange = new EventEmitter();
@@ -25,6 +28,9 @@ export class FableEditorComponent {
25
28
  toolbar: this.toolbar,
26
29
  statusbar: this.statusbar,
27
30
  readonly: this.readonly,
31
+ primaryColor: this.primaryColor,
32
+ toolbarGroupBackground: this.toolbarGroupBackground,
33
+ uiFontFamily: this.uiFontFamily,
28
34
  initialContent: this.value,
29
35
  ...this.init,
30
36
  onChange: (content) => {
@@ -69,7 +75,7 @@ export class FableEditorComponent {
69
75
  this.editor?.setContent(value ?? '');
70
76
  }
71
77
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: FableEditorComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
72
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "18.2.14", type: FableEditorComponent, isStandalone: true, selector: "fable-editor", inputs: { language: "language", height: "height", menubar: "menubar", toolbar: "toolbar", statusbar: "statusbar", readonly: "readonly", init: "init", value: "value" }, outputs: { editorChange: "editorChange", editorReady: "editorReady" }, viewQueries: [{ propertyName: "container", first: true, predicate: ["container"], descendants: true, static: true }], usesOnChanges: true, ngImport: i0, template: `<div #container></div>`, isInline: true, styles: [":host{display:block}\n"] });
78
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "18.2.14", type: FableEditorComponent, isStandalone: true, selector: "fable-editor", inputs: { language: "language", height: "height", menubar: "menubar", toolbar: "toolbar", statusbar: "statusbar", readonly: "readonly", primaryColor: "primaryColor", toolbarGroupBackground: "toolbarGroupBackground", uiFontFamily: "uiFontFamily", init: "init", value: "value" }, outputs: { editorChange: "editorChange", editorReady: "editorReady" }, viewQueries: [{ propertyName: "container", first: true, predicate: ["container"], descendants: true, static: true }], usesOnChanges: true, ngImport: i0, template: `<div #container></div>`, isInline: true, styles: [":host{display:block}\n"] });
73
79
  }
74
80
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: FableEditorComponent, decorators: [{
75
81
  type: Component,
@@ -89,6 +95,12 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
89
95
  type: Input
90
96
  }], readonly: [{
91
97
  type: Input
98
+ }], primaryColor: [{
99
+ type: Input
100
+ }], toolbarGroupBackground: [{
101
+ type: Input
102
+ }], uiFontFamily: [{
103
+ type: Input
92
104
  }], init: [{
93
105
  type: Input
94
106
  }], value: [{
@@ -98,4 +110,4 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
98
110
  }], editorReady: [{
99
111
  type: Output
100
112
  }] } });
101
- //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiZmFibGUtZWRpdG9yLmNvbXBvbmVudC5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uLy4uLy4uL3NyYy9hbmd1bGFyL2ZhYmxlLWVkaXRvci5jb21wb25lbnQudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUEsT0FBTyxFQUNMLFNBQVMsRUFFVCxZQUFZLEVBQ1osS0FBSyxFQUlMLE1BQU0sRUFFTixTQUFTLEVBQ1YsTUFBTSxlQUFlLENBQUM7QUFDdkIsT0FBTyxFQUFFLFdBQVcsRUFBcUQsTUFBTSxTQUFTLENBQUM7O0FBUXpGLE1BQU0sT0FBTyxvQkFBb0I7SUFDVyxTQUFTLENBQThCO0lBRXhFLFFBQVEsR0FBbUIsSUFBSSxDQUFDO0lBQ2hDLE1BQU0sR0FBRyxHQUFHLENBQUM7SUFDYixPQUFPLEdBQXFCLElBQUksQ0FBQztJQUNqQyxPQUFPLEdBQXFCLElBQUksQ0FBQztJQUNqQyxTQUFTLEdBQUcsSUFBSSxDQUFDO0lBQ2pCLFFBQVEsR0FBRyxLQUFLLENBQUM7SUFDakIsSUFBSSxHQUErQyxFQUFFLENBQUM7SUFDdEQsS0FBSyxHQUFHLEVBQUUsQ0FBQztJQUVWLFlBQVksR0FBRyxJQUFJLFlBQVksRUFBVSxDQUFDO0lBQzFDLFdBQVcsR0FBRyxJQUFJLFlBQVksRUFBa0IsQ0FBQztJQUVuRCxNQUFNLENBQWU7SUFDckIsV0FBVyxHQUFHLEVBQUUsQ0FBQztJQUV6QixRQUFRO1FBQ04sSUFBSSxDQUFDLFdBQVcsR0FBRyxJQUFJLENBQUMsS0FBSyxDQUFDO1FBQzlCLElBQUksQ0FBQyxNQUFNLEdBQUcsSUFBSSxXQUFXLENBQUM7WUFDNUIsTUFBTSxFQUFFLElBQUksQ0FBQyxTQUFTLENBQUMsYUFBYTtZQUNwQyxRQUFRLEVBQUUsSUFBSSxDQUFDLFFBQVE7WUFDdkIsTUFBTSxFQUFFLElBQUksQ0FBQyxNQUFNO1lBQ25CLE9BQU8sRUFBRSxJQUFJLENBQUMsT0FBTztZQUNyQixPQUFPLEVBQUUsSUFBSSxDQUFDLE9BQU87WUFDckIsU0FBUyxFQUFFLElBQUksQ0FBQyxTQUFTO1lBQ3pCLFFBQVEsRUFBRSxJQUFJLENBQUMsUUFBUTtZQUN2QixjQUFjLEVBQUUsSUFBSSxDQUFDLEtBQUs7WUFDMUIsR0FBRyxJQUFJLENBQUMsSUFBSTtZQUNaLFFBQVEsRUFBRSxDQUFDLE9BQU8sRUFBRSxFQUFFO2dCQUNwQixJQUFJLENBQUMsV0FBVyxHQUFHLE9BQU8sQ0FBQztnQkFDM0IsSUFBSSxDQUFDLFlBQVksQ0FBQyxJQUFJLENBQUMsT0FBTyxDQUFDLENBQUM7WUFDbEMsQ0FBQztZQUNELE9BQU8sRUFBRSxDQUFDLEdBQUcsRUFBRSxFQUFFLENBQUMsSUFBSSxDQUFDLFdBQVcsQ0FBQyxJQUFJLENBQUMsR0FBRyxDQUFDO1NBQzdDLENBQUMsQ0FBQztJQUNMLENBQUM7SUFFRCxXQUFXLENBQUMsT0FBc0I7UUFDaEMsSUFBSSxDQUFDLElBQUksQ0FBQyxNQUFNO1lBQUUsT0FBTztRQUN6QixJQUFJLE9BQU8sQ0FBQyxVQUFVLENBQUMsSUFBSSxDQUFDLE9BQU8sQ0FBQyxVQUFVLENBQUMsQ0FBQyxXQUFXLEVBQUUsQ0FBQztZQUM1RCxJQUFJLENBQUMsTUFBTSxDQUFDLFdBQVcsQ0FBQyxJQUFJLENBQUMsUUFBUSxDQUFDLENBQUM7UUFDekMsQ0FBQztRQUNELElBQUksT0FBTyxDQUFDLE9BQU8sQ0FBQyxJQUFJLENBQUMsT0FBTyxDQUFDLE9BQU8sQ0FBQyxDQUFDLFdBQVcsSUFBSSxJQUFJLENBQUMsS0FBSyxLQUFLLElBQUksQ0FBQyxXQUFXLEVBQUUsQ0FBQztZQUN6RixJQUFJLENBQUMsV0FBVyxHQUFHLElBQUksQ0FBQyxLQUFLLENBQUM7WUFDOUIsSUFBSSxDQUFDLE1BQU0sQ0FBQyxVQUFVLENBQUMsSUFBSSxDQUFDLEtBQUssSUFBSSxFQUFFLENBQUMsQ0FBQztRQUMzQyxDQUFDO0lBQ0gsQ0FBQztJQUVELFdBQVc7UUFDVCxJQUFJLENBQUMsTUFBTSxFQUFFLE9BQU8sRUFBRSxDQUFDO1FBQ3ZCLElBQUksQ0FBQyxNQUFNLEdBQUcsU0FBUyxDQUFDO0lBQzFCLENBQUM7SUFFRCxVQUFVO1FBQ1IsT0FBTyxJQUFJLENBQUMsTUFBTSxFQUFFLFVBQVUsRUFBRSxJQUFJLEVBQUUsQ0FBQztJQUN6QyxDQUFDO0lBRUQsVUFBVSxDQUFDLElBQVk7UUFDckIsSUFBSSxDQUFDLFdBQVcsR0FBRyxJQUFJLENBQUM7UUFDeEIsSUFBSSxDQUFDLE1BQU0sRUFBRSxVQUFVLENBQUMsSUFBSSxJQUFJLEVBQUUsQ0FBQyxDQUFDO0lBQ3RDLENBQUM7SUFFRCxhQUFhLENBQUMsSUFBWTtRQUN4QixJQUFJLENBQUMsTUFBTSxFQUFFLGFBQWEsQ0FBQyxJQUFJLENBQUMsQ0FBQztJQUNuQyxDQUFDO0lBRUQsV0FBVyxDQUFDLElBQW9CO1FBQzlCLElBQUksQ0FBQyxNQUFNLEVBQUUsV0FBVyxDQUFDLElBQUksQ0FBQyxDQUFDO0lBQ2pDLENBQUM7SUFFRCxLQUFLO1FBQ0gsSUFBSSxDQUFDLE1BQU0sRUFBRSxLQUFLLEVBQUUsQ0FBQztJQUN2QixDQUFDO0lBRUQsVUFBVSxDQUFDLEtBQWE7UUFDdEIsSUFBSSxDQUFDLE1BQU0sRUFBRSxVQUFVLENBQUMsS0FBSyxJQUFJLEVBQUUsQ0FBQyxDQUFDO0lBQ3ZDLENBQUM7d0dBN0VVLG9CQUFvQjs0RkFBcEIsb0JBQW9CLGtjQUhyQix3QkFBd0I7OzRGQUd2QixvQkFBb0I7a0JBTmhDLFNBQVM7K0JBQ0UsY0FBYyxjQUNaLElBQUksWUFDTix3QkFBd0I7OEJBSVEsU0FBUztzQkFBbEQsU0FBUzt1QkFBQyxXQUFXLEVBQUUsRUFBRSxNQUFNLEVBQUUsSUFBSSxFQUFFO2dCQUUvQixRQUFRO3NCQUFoQixLQUFLO2dCQUNHLE1BQU07c0JBQWQsS0FBSztnQkFDRyxPQUFPO3NCQUFmLEtBQUs7Z0JBQ0csT0FBTztzQkFBZixLQUFLO2dCQUNHLFNBQVM7c0JBQWpCLEtBQUs7Z0JBQ0csUUFBUTtzQkFBaEIsS0FBSztnQkFDRyxJQUFJO3NCQUFaLEtBQUs7Z0JBQ0csS0FBSztzQkFBYixLQUFLO2dCQUVJLFlBQVk7c0JBQXJCLE1BQU07Z0JBQ0csV0FBVztzQkFBcEIsTUFBTSIsInNvdXJjZXNDb250ZW50IjpbImltcG9ydCB7XG4gIENvbXBvbmVudCxcbiAgRWxlbWVudFJlZixcbiAgRXZlbnRFbWl0dGVyLFxuICBJbnB1dCxcbiAgT25DaGFuZ2VzLFxuICBPbkRlc3Ryb3ksXG4gIE9uSW5pdCxcbiAgT3V0cHV0LFxuICBTaW1wbGVDaGFuZ2VzLFxuICBWaWV3Q2hpbGRcbn0gZnJvbSAnQGFuZ3VsYXIvY29yZSc7XG5pbXBvcnQgeyBGYWJsZUVkaXRvciwgRWRpdG9ySW5pdE9wdGlvbnMsIEVkaXRvckxhbmd1YWdlLCBGYWJsZUVkaXRvckFwaSB9IGZyb20gJy4uL2NvcmUnO1xuXG5AQ29tcG9uZW50KHtcbiAgc2VsZWN0b3I6ICdmYWJsZS1lZGl0b3InLFxuICBzdGFuZGFsb25lOiB0cnVlLFxuICB0ZW1wbGF0ZTogYDxkaXYgI2NvbnRhaW5lcj48L2Rpdj5gLFxuICBzdHlsZXM6IFsnOmhvc3QgeyBkaXNwbGF5OiBibG9jazsgfSddXG59KVxuZXhwb3J0IGNsYXNzIEZhYmxlRWRpdG9yQ29tcG9uZW50IGltcGxlbWVudHMgT25Jbml0LCBPbkNoYW5nZXMsIE9uRGVzdHJveSB7XG4gIEBWaWV3Q2hpbGQoJ2NvbnRhaW5lcicsIHsgc3RhdGljOiB0cnVlIH0pIGNvbnRhaW5lciE6IEVsZW1lbnRSZWY8SFRNTERpdkVsZW1lbnQ+O1xuXG4gIEBJbnB1dCgpIGxhbmd1YWdlOiBFZGl0b3JMYW5ndWFnZSA9ICdlbic7XG4gIEBJbnB1dCgpIGhlaWdodCA9IDMwMjtcbiAgQElucHV0KCkgbWVudWJhcjogYm9vbGVhbiB8IHN0cmluZyA9IHRydWU7XG4gIEBJbnB1dCgpIHRvb2xiYXI6IGJvb2xlYW4gfCBzdHJpbmcgPSB0cnVlO1xuICBASW5wdXQoKSBzdGF0dXNiYXIgPSB0cnVlO1xuICBASW5wdXQoKSByZWFkb25seSA9IGZhbHNlO1xuICBASW5wdXQoKSBpbml0OiBQYXJ0aWFsPE9taXQ8RWRpdG9ySW5pdE9wdGlvbnMsICd0YXJnZXQnPj4gPSB7fTtcbiAgQElucHV0KCkgdmFsdWUgPSAnJztcblxuICBAT3V0cHV0KCkgZWRpdG9yQ2hhbmdlID0gbmV3IEV2ZW50RW1pdHRlcjxzdHJpbmc+KCk7XG4gIEBPdXRwdXQoKSBlZGl0b3JSZWFkeSA9IG5ldyBFdmVudEVtaXR0ZXI8RmFibGVFZGl0b3JBcGk+KCk7XG5cbiAgcHJpdmF0ZSBlZGl0b3I/OiBGYWJsZUVkaXRvcjtcbiAgcHJpdmF0ZSBsYXN0Q29udGVudCA9ICcnO1xuXG4gIG5nT25Jbml0KCk6IHZvaWQge1xuICAgIHRoaXMubGFzdENvbnRlbnQgPSB0aGlzLnZhbHVlO1xuICAgIHRoaXMuZWRpdG9yID0gbmV3IEZhYmxlRWRpdG9yKHtcbiAgICAgIHRhcmdldDogdGhpcy5jb250YWluZXIubmF0aXZlRWxlbWVudCxcbiAgICAgIGxhbmd1YWdlOiB0aGlzLmxhbmd1YWdlLFxuICAgICAgaGVpZ2h0OiB0aGlzLmhlaWdodCxcbiAgICAgIG1lbnViYXI6IHRoaXMubWVudWJhcixcbiAgICAgIHRvb2xiYXI6IHRoaXMudG9vbGJhcixcbiAgICAgIHN0YXR1c2JhcjogdGhpcy5zdGF0dXNiYXIsXG4gICAgICByZWFkb25seTogdGhpcy5yZWFkb25seSxcbiAgICAgIGluaXRpYWxDb250ZW50OiB0aGlzLnZhbHVlLFxuICAgICAgLi4udGhpcy5pbml0LFxuICAgICAgb25DaGFuZ2U6IChjb250ZW50KSA9PiB7XG4gICAgICAgIHRoaXMubGFzdENvbnRlbnQgPSBjb250ZW50O1xuICAgICAgICB0aGlzLmVkaXRvckNoYW5nZS5lbWl0KGNvbnRlbnQpO1xuICAgICAgfSxcbiAgICAgIG9uUmVhZHk6IChhcGkpID0+IHRoaXMuZWRpdG9yUmVhZHkuZW1pdChhcGkpXG4gICAgfSk7XG4gIH1cblxuICBuZ09uQ2hhbmdlcyhjaGFuZ2VzOiBTaW1wbGVDaGFuZ2VzKTogdm9pZCB7XG4gICAgaWYgKCF0aGlzLmVkaXRvcikgcmV0dXJuO1xuICAgIGlmIChjaGFuZ2VzWydsYW5ndWFnZSddICYmICFjaGFuZ2VzWydsYW5ndWFnZSddLmZpcnN0Q2hhbmdlKSB7XG4gICAgICB0aGlzLmVkaXRvci5zZXRMYW5ndWFnZSh0aGlzLmxhbmd1YWdlKTtcbiAgICB9XG4gICAgaWYgKGNoYW5nZXNbJ3ZhbHVlJ10gJiYgIWNoYW5nZXNbJ3ZhbHVlJ10uZmlyc3RDaGFuZ2UgJiYgdGhpcy52YWx1ZSAhPT0gdGhpcy5sYXN0Q29udGVudCkge1xuICAgICAgdGhpcy5sYXN0Q29udGVudCA9IHRoaXMudmFsdWU7XG4gICAgICB0aGlzLmVkaXRvci5zZXRDb250ZW50KHRoaXMudmFsdWUgPz8gJycpO1xuICAgIH1cbiAgfVxuXG4gIG5nT25EZXN0cm95KCk6IHZvaWQge1xuICAgIHRoaXMuZWRpdG9yPy5kZXN0cm95KCk7XG4gICAgdGhpcy5lZGl0b3IgPSB1bmRlZmluZWQ7XG4gIH1cblxuICBnZXRDb250ZW50KCk6IHN0cmluZyB7XG4gICAgcmV0dXJuIHRoaXMuZWRpdG9yPy5nZXRDb250ZW50KCkgPz8gJyc7XG4gIH1cblxuICBzZXRDb250ZW50KGh0bWw6IHN0cmluZyk6IHZvaWQge1xuICAgIHRoaXMubGFzdENvbnRlbnQgPSBodG1sO1xuICAgIHRoaXMuZWRpdG9yPy5zZXRDb250ZW50KGh0bWwgPz8gJycpO1xuICB9XG5cbiAgaW5zZXJ0Q29udGVudChodG1sOiBzdHJpbmcpOiB2b2lkIHtcbiAgICB0aGlzLmVkaXRvcj8uaW5zZXJ0Q29udGVudChodG1sKTtcbiAgfVxuXG4gIHNldExhbmd1YWdlKGxhbmc6IEVkaXRvckxhbmd1YWdlKTogdm9pZCB7XG4gICAgdGhpcy5lZGl0b3I/LnNldExhbmd1YWdlKGxhbmcpO1xuICB9XG5cbiAgZm9jdXMoKTogdm9pZCB7XG4gICAgdGhpcy5lZGl0b3I/LmZvY3VzKCk7XG4gIH1cblxuICB3cml0ZVZhbHVlKHZhbHVlOiBzdHJpbmcpOiB2b2lkIHtcbiAgICB0aGlzLmVkaXRvcj8uc2V0Q29udGVudCh2YWx1ZSA/PyAnJyk7XG4gIH1cbn1cbiJdfQ==
113
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiZmFibGUtZWRpdG9yLmNvbXBvbmVudC5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uLy4uLy4uL3NyYy9hbmd1bGFyL2ZhYmxlLWVkaXRvci5jb21wb25lbnQudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUEsT0FBTyxFQUNMLFNBQVMsRUFFVCxZQUFZLEVBQ1osS0FBSyxFQUlMLE1BQU0sRUFFTixTQUFTLEVBQ1YsTUFBTSxlQUFlLENBQUM7QUFDdkIsT0FBTyxFQUFFLFdBQVcsRUFBcUQsTUFBTSxTQUFTLENBQUM7O0FBUXpGLE1BQU0sT0FBTyxvQkFBb0I7SUFDVyxTQUFTLENBQThCO0lBRXhFLFFBQVEsR0FBbUIsSUFBSSxDQUFDO0lBQ2hDLE1BQU0sR0FBRyxHQUFHLENBQUM7SUFDYixPQUFPLEdBQXFCLElBQUksQ0FBQztJQUNqQyxPQUFPLEdBQXFCLElBQUksQ0FBQztJQUNqQyxTQUFTLEdBQUcsSUFBSSxDQUFDO0lBQ2pCLFFBQVEsR0FBRyxLQUFLLENBQUM7SUFDakIsWUFBWSxDQUFVO0lBQ3RCLHNCQUFzQixDQUFVO0lBQ2hDLFlBQVksQ0FBVTtJQUN0QixJQUFJLEdBQStDLEVBQUUsQ0FBQztJQUN0RCxLQUFLLEdBQUcsRUFBRSxDQUFDO0lBRVYsWUFBWSxHQUFHLElBQUksWUFBWSxFQUFVLENBQUM7SUFDMUMsV0FBVyxHQUFHLElBQUksWUFBWSxFQUFrQixDQUFDO0lBRW5ELE1BQU0sQ0FBZTtJQUNyQixXQUFXLEdBQUcsRUFBRSxDQUFDO0lBRXpCLFFBQVE7UUFDTixJQUFJLENBQUMsV0FBVyxHQUFHLElBQUksQ0FBQyxLQUFLLENBQUM7UUFDOUIsSUFBSSxDQUFDLE1BQU0sR0FBRyxJQUFJLFdBQVcsQ0FBQztZQUM1QixNQUFNLEVBQUUsSUFBSSxDQUFDLFNBQVMsQ0FBQyxhQUFhO1lBQ3BDLFFBQVEsRUFBRSxJQUFJLENBQUMsUUFBUTtZQUN2QixNQUFNLEVBQUUsSUFBSSxDQUFDLE1BQU07WUFDbkIsT0FBTyxF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O3NCQUFkLEtBQUs7Z0JBQ0csT0FBTztzQkFBZixLQUFLO2dCQUNHLE9BQU87c0JBQWYsS0FBSztnQkFDRyxTQUFTO3NCQUFqQixLQUFLO2dCQUNHLFFBQVE7c0JBQWhCLEtBQUs7Z0JBQ0csWUFBWTtzQkFBcEIsS0FBSztnQkFDRyxzQkFBc0I7c0JBQTlCLEtBQUs7Z0JBQ0csWUFBWTtzQkFBcEIsS0FBSztnQkFDRyxJQUFJO3NCQUFaLEtBQUs7Z0JBQ0csS0FBSztzQkFBYixLQUFLO2dCQUVJLFlBQVk7c0JBQXJCLE1BQU07Z0JBQ0csV0FBVztzQkFBcEIsTUFBTSIsInNvdXJjZXNDb250ZW50IjpbImltcG9ydCB7XG4gIENvbXBvbmVudCxcbiAgRWxlbWVudFJlZixcbiAgRXZlbnRFbWl0dGVyLFxuICBJbnB1dCxcbiAgT25DaGFuZ2VzLFxuICBPbkRlc3Ryb3ksXG4gIE9uSW5pdCxcbiAgT3V0cHV0LFxuICBTaW1wbGVDaGFuZ2VzLFxuICBWaWV3Q2hpbGRcbn0gZnJvbSAnQGFuZ3VsYXIvY29yZSc7XG5pbXBvcnQgeyBGYWJsZUVkaXRvciwgRWRpdG9ySW5pdE9wdGlvbnMsIEVkaXRvckxhbmd1YWdlLCBGYWJsZUVkaXRvckFwaSB9IGZyb20gJy4uL2NvcmUnO1xuXG5AQ29tcG9uZW50KHtcbiAgc2VsZWN0b3I6ICdmYWJsZS1lZGl0b3InLFxuICBzdGFuZGFsb25lOiB0cnVlLFxuICB0ZW1wbGF0ZTogYDxkaXYgI2NvbnRhaW5lcj48L2Rpdj5gLFxuICBzdHlsZXM6IFsnOmhvc3QgeyBkaXNwbGF5OiBibG9jazsgfSddXG59KVxuZXhwb3J0IGNsYXNzIEZhYmxlRWRpdG9yQ29tcG9uZW50IGltcGxlbWVudHMgT25Jbml0LCBPbkNoYW5nZXMsIE9uRGVzdHJveSB7XG4gIEBWaWV3Q2hpbGQoJ2NvbnRhaW5lcicsIHsgc3RhdGljOiB0cnVlIH0pIGNvbnRhaW5lciE6IEVsZW1lbnRSZWY8SFRNTERpdkVsZW1lbnQ+O1xuXG4gIEBJbnB1dCgpIGxhbmd1YWdlOiBFZGl0b3JMYW5ndWFnZSA9ICdlbic7XG4gIEBJbnB1dCgpIGhlaWdodCA9IDMwMjtcbiAgQElucHV0KCkgbWVudWJhcjogYm9vbGVhbiB8IHN0cmluZyA9IHRydWU7XG4gIEBJbnB1dCgpIHRvb2xiYXI6IGJvb2xlYW4gfCBzdHJpbmcgPSB0cnVlO1xuICBASW5wdXQoKSBzdGF0dXNiYXIgPSB0cnVlO1xuICBASW5wdXQoKSByZWFkb25seSA9IGZhbHNlO1xuICBASW5wdXQoKSBwcmltYXJ5Q29sb3I/OiBzdHJpbmc7XG4gIEBJbnB1dCgpIHRvb2xiYXJHcm91cEJhY2tncm91bmQ/OiBzdHJpbmc7XG4gIEBJbnB1dCgpIHVpRm9udEZhbWlseT86IHN0cmluZztcbiAgQElucHV0KCkgaW5pdDogUGFydGlhbDxPbWl0PEVkaXRvckluaXRPcHRpb25zLCAndGFyZ2V0Jz4+ID0ge307XG4gIEBJbnB1dCgpIHZhbHVlID0gJyc7XG5cbiAgQE91dHB1dCgpIGVkaXRvckNoYW5nZSA9IG5ldyBFdmVudEVtaXR0ZXI8c3RyaW5nPigpO1xuICBAT3V0cHV0KCkgZWRpdG9yUmVhZHkgPSBuZXcgRXZlbnRFbWl0dGVyPEZhYmxlRWRpdG9yQXBpPigpO1xuXG4gIHByaXZhdGUgZWRpdG9yPzogRmFibGVFZGl0b3I7XG4gIHByaXZhdGUgbGFzdENvbnRlbnQgPSAnJztcblxuICBuZ09uSW5pdCgpOiB2b2lkIHtcbiAgICB0aGlzLmxhc3RDb250ZW50ID0gdGhpcy52YWx1ZTtcbiAgICB0aGlzLmVkaXRvciA9IG5ldyBGYWJsZUVkaXRvcih7XG4gICAgICB0YXJnZXQ6IHRoaXMuY29udGFpbmVyLm5hdGl2ZUVsZW1lbnQsXG4gICAgICBsYW5ndWFnZTogdGhpcy5sYW5ndWFnZSxcbiAgICAgIGhlaWdodDogdGhpcy5oZWlnaHQsXG4gICAgICBtZW51YmFyOiB0aGlzLm1lbnViYXIsXG4gICAgICB0b29sYmFyOiB0aGlzLnRvb2xiYXIsXG4gICAgICBzdGF0dXNiYXI6IHRoaXMuc3RhdHVzYmFyLFxuICAgICAgcmVhZG9ubHk6IHRoaXMucmVhZG9ubHksXG4gICAgICBwcmltYXJ5Q29sb3I6IHRoaXMucHJpbWFyeUNvbG9yLFxuICAgICAgdG9vbGJhckdyb3VwQmFja2dyb3VuZDogdGhpcy50b29sYmFyR3JvdXBCYWNrZ3JvdW5kLFxuICAgICAgdWlGb250RmFtaWx5OiB0aGlzLnVpRm9udEZhbWlseSxcbiAgICAgIGluaXRpYWxDb250ZW50OiB0aGlzLnZhbHVlLFxuICAgICAgLi4udGhpcy5pbml0LFxuICAgICAgb25DaGFuZ2U6IChjb250ZW50KSA9PiB7XG4gICAgICAgIHRoaXMubGFzdENvbnRlbnQgPSBjb250ZW50O1xuICAgICAgICB0aGlzLmVkaXRvckNoYW5nZS5lbWl0KGNvbnRlbnQpO1xuICAgICAgfSxcbiAgICAgIG9uUmVhZHk6IChhcGkpID0+IHRoaXMuZWRpdG9yUmVhZHkuZW1pdChhcGkpXG4gICAgfSk7XG4gIH1cblxuICBuZ09uQ2hhbmdlcyhjaGFuZ2VzOiBTaW1wbGVDaGFuZ2VzKTogdm9pZCB7XG4gICAgaWYgKCF0aGlzLmVkaXRvcikgcmV0dXJuO1xuICAgIGlmIChjaGFuZ2VzWydsYW5ndWFnZSddICYmICFjaGFuZ2VzWydsYW5ndWFnZSddLmZpcnN0Q2hhbmdlKSB7XG4gICAgICB0aGlzLmVkaXRvci5zZXRMYW5ndWFnZSh0aGlzLmxhbmd1YWdlKTtcbiAgICB9XG4gICAgaWYgKGNoYW5nZXNbJ3ZhbHVlJ10gJiYgIWNoYW5nZXNbJ3ZhbHVlJ10uZmlyc3RDaGFuZ2UgJiYgdGhpcy52YWx1ZSAhPT0gdGhpcy5sYXN0Q29udGVudCkge1xuICAgICAgdGhpcy5sYXN0Q29udGVudCA9IHRoaXMudmFsdWU7XG4gICAgICB0aGlzLmVkaXRvci5zZXRDb250ZW50KHRoaXMudmFsdWUgPz8gJycpO1xuICAgIH1cbiAgfVxuXG4gIG5nT25EZXN0cm95KCk6IHZvaWQge1xuICAgIHRoaXMuZWRpdG9yPy5kZXN0cm95KCk7XG4gICAgdGhpcy5lZGl0b3IgPSB1bmRlZmluZWQ7XG4gIH1cblxuICBnZXRDb250ZW50KCk6IHN0cmluZyB7XG4gICAgcmV0dXJuIHRoaXMuZWRpdG9yPy5nZXRDb250ZW50KCkgPz8gJyc7XG4gIH1cblxuICBzZXRDb250ZW50KGh0bWw6IHN0cmluZyk6IHZvaWQge1xuICAgIHRoaXMubGFzdENvbnRlbnQgPSBodG1sO1xuICAgIHRoaXMuZWRpdG9yPy5zZXRDb250ZW50KGh0bWwgPz8gJycpO1xuICB9XG5cbiAgaW5zZXJ0Q29udGVudChodG1sOiBzdHJpbmcpOiB2b2lkIHtcbiAgICB0aGlzLmVkaXRvcj8uaW5zZXJ0Q29udGVudChodG1sKTtcbiAgfVxuXG4gIHNldExhbmd1YWdlKGxhbmc6IEVkaXRvckxhbmd1YWdlKTogdm9pZCB7XG4gICAgdGhpcy5lZGl0b3I/LnNldExhbmd1YWdlKGxhbmcpO1xuICB9XG5cbiAgZm9jdXMoKTogdm9pZCB7XG4gICAgdGhpcy5lZGl0b3I/LmZvY3VzKCk7XG4gIH1cblxuICB3cml0ZVZhbHVlKHZhbHVlOiBzdHJpbmcpOiB2b2lkIHtcbiAgICB0aGlzLmVkaXRvcj8uc2V0Q29udGVudCh2YWx1ZSA/PyAnJyk7XG4gIH1cbn1cbiJdfQ==