fable-editor 1.1.0 → 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,16 @@ 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 |
16
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. |
17
- | Math formulas | Insert inline or multi-line LaTeX derivations, rendered with [KaTeX](https://katex.org) click an inserted formula to edit or delete it. Pasting LaTeX from the clipboard (`$…$`, `$$…$$`, `\(…\)`, `\[…\]`, `\begin{…}…\end{…}`) auto-renders it, and typing `$x^2$` inline converts as soon as the closing `$` is typed |
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 |
18
20
  | Documents | Import `.docx` files, source-code view, print preview |
19
21
  | Productivity | Undo/redo, revision history, autosave draft restore, word count, special characters & emoji pickers (category tabs + glyph grid), page breaks |
20
22
  | Fullscreen | Toggle fullscreen editing |
@@ -73,18 +75,26 @@ editor.destroy();
73
75
  | `videoFileTypes` | `string[]` | `['video/mp4','video/webm','video/ogg']` | `accept` list for the native video file picker. |
74
76
  | `videoUploadHandler` | `(file: File) => Promise<string>` | — | Resolve with a URL after uploading; omit to inline videos as base64. |
75
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`. |
76
81
  | `draftKey` | `string` | current page path | Storage key suffix for autosaved drafts. |
77
82
  | `onChange` / `onReady` | functions | — | Content-change and ready callbacks (usually set by the React/Angular wrapper instead). |
78
83
 
79
- > **Math formulas:** the "Insert math formula" toolbar/menu item renders LaTeX with [KaTeX](https://katex.org), which FableEditor uses as a rendering dependency but does not bundle the stylesheet for (to avoid shipping KaTeX's webfonts in every install). If you use this feature, also import KaTeX's CSS once in your app: `import 'katex/dist/katex.min.css'`.
84
+ ### Theming
80
85
 
81
- ### Math from the clipboard / keyboard
86
+ The editor's accent color, toolbar group background and UI font are driven by CSS variables, so they can be set per instance:
82
87
 
83
- Besides the dialog, formulas can enter the document two more ways — the regular paste pipeline is untouched, these only kick in when the clipboard is *entirely* one formula:
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
+ ```
84
96
 
85
- - **Paste** `$$…$$`, `\[…\]` or a `\begin{…}…\end{…}` environment a **block** formula on its own line. A derivation pasted as separate lines gets each step on its own row automatically.
86
- - **Paste** `\(…\)` or a LaTeX-looking `$…$` → an **inline** formula at the caret (same line). Plain text like `$5 and $10` is left alone.
87
- - **Type** `$x^2$` — the moment the closing `$` is typed, the run converts to an inline formula (only when it parses as valid LaTeX; a literal `$5` stays text).
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`.
88
98
 
89
99
  ### Toolbar & menubar configuration
90
100
 
@@ -100,7 +110,7 @@ new FableEditor({
100
110
  });
101
111
  ```
102
112
 
103
- 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 changecase lineheight wordspacing removeformat blocks ltr rtl quickimage quickvideo quicktable mathformula 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`.
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`.
104
114
 
105
115
  ## React
106
116
 
@@ -125,7 +135,7 @@ function App() {
125
135
 
126
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.
127
137
 
128
- Props: `value`, `defaultValue`, `onChange`, `language`, `height`, `menubar`, `toolbar`, `statusbar`, `readonly`, `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`.
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`.
129
139
 
130
140
  ## Angular
131
141
 
@@ -160,7 +170,7 @@ Use in a template:
160
170
  </fable-editor>
161
171
  ```
162
172
 
163
- 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`, `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`.
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`.
164
174
 
165
175
  ## Development & testing
166
176
 
@@ -7,14 +7,16 @@ 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 |
16
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. |
17
- | Math formulas | Insert inline or multi-line LaTeX derivations, rendered with [KaTeX](https://katex.org) click an inserted formula to edit or delete it. Pasting LaTeX from the clipboard (`$…$`, `$$…$$`, `\(…\)`, `\[…\]`, `\begin{…}…\end{…}`) auto-renders it, and typing `$x^2$` inline converts as soon as the closing `$` is typed |
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 |
18
20
  | Documents | Import `.docx` files, source-code view, print preview |
19
21
  | Productivity | Undo/redo, revision history, autosave draft restore, word count, special characters & emoji pickers (category tabs + glyph grid), page breaks |
20
22
  | Fullscreen | Toggle fullscreen editing |
@@ -73,18 +75,26 @@ editor.destroy();
73
75
  | `videoFileTypes` | `string[]` | `['video/mp4','video/webm','video/ogg']` | `accept` list for the native video file picker. |
74
76
  | `videoUploadHandler` | `(file: File) => Promise<string>` | — | Resolve with a URL after uploading; omit to inline videos as base64. |
75
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`. |
76
81
  | `draftKey` | `string` | current page path | Storage key suffix for autosaved drafts. |
77
82
  | `onChange` / `onReady` | functions | — | Content-change and ready callbacks (usually set by the React/Angular wrapper instead). |
78
83
 
79
- > **Math formulas:** the "Insert math formula" toolbar/menu item renders LaTeX with [KaTeX](https://katex.org), which FableEditor uses as a rendering dependency but does not bundle the stylesheet for (to avoid shipping KaTeX's webfonts in every install). If you use this feature, also import KaTeX's CSS once in your app: `import 'katex/dist/katex.min.css'`.
84
+ ### Theming
80
85
 
81
- ### Math from the clipboard / keyboard
86
+ The editor's accent color, toolbar group background and UI font are driven by CSS variables, so they can be set per instance:
82
87
 
83
- Besides the dialog, formulas can enter the document two more ways — the regular paste pipeline is untouched, these only kick in when the clipboard is *entirely* one formula:
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
+ ```
84
96
 
85
- - **Paste** `$$…$$`, `\[…\]` or a `\begin{…}…\end{…}` environment a **block** formula on its own line. A derivation pasted as separate lines gets each step on its own row automatically.
86
- - **Paste** `\(…\)` or a LaTeX-looking `$…$` → an **inline** formula at the caret (same line). Plain text like `$5 and $10` is left alone.
87
- - **Type** `$x^2$` — the moment the closing `$` is typed, the run converts to an inline formula (only when it parses as valid LaTeX; a literal `$5` stays text).
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`.
88
98
 
89
99
  ### Toolbar & menubar configuration
90
100
 
@@ -100,7 +110,7 @@ new FableEditor({
100
110
  });
101
111
  ```
102
112
 
103
- 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 changecase lineheight wordspacing removeformat blocks ltr rtl quickimage quickvideo quicktable mathformula 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`.
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`.
104
114
 
105
115
  ## React
106
116
 
@@ -125,7 +135,7 @@ function App() {
125
135
 
126
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.
127
137
 
128
- Props: `value`, `defaultValue`, `onChange`, `language`, `height`, `menubar`, `toolbar`, `statusbar`, `readonly`, `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`.
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`.
129
139
 
130
140
  ## Angular
131
141
 
@@ -160,7 +170,7 @@ Use in a template:
160
170
  </fable-editor>
161
171
  ```
162
172
 
163
- 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`, `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`.
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`.
164
174
 
165
175
  ## Development & testing
166
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,9 +10,14 @@ 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[];
17
22
  /** Category key = i18n string key for the tab label in the picker dialogs. */
18
23
  export interface GlyphCategory {
@@ -46,9 +46,11 @@ export declare class FableEditor implements FableEditorApi {
46
46
  private vphUploadTarget;
47
47
  private videoUploadHandler?;
48
48
  private onVideoUploadError?;
49
- private mathActive;
50
- private mathCtx;
51
49
  private selCtx;
50
+ private tbrMore;
51
+ private tbrExpanded;
52
+ private codeActive;
53
+ private codeCtx;
52
54
  private tplActive;
53
55
  private tplCtx;
54
56
  private docInput;
@@ -63,6 +65,10 @@ export declare class FableEditor implements FableEditorApi {
63
65
  private tArr;
64
66
  private dir;
65
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;
66
72
  /** Rewrites the literal word `body` in a user-supplied `contentStyle` string to a
67
73
  * selector scoped to this instance's content div. Everything else passes through
68
74
  * verbatim, since `.ed` is a plain div in the host page (not an iframe) and an
@@ -111,6 +117,10 @@ export declare class FableEditor implements FableEditorApi {
111
117
  private tsel;
112
118
  private setListStyle;
113
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;
114
124
  private colorMenu;
115
125
  private applyColor;
116
126
  private syncColorSwatches;
@@ -128,6 +138,7 @@ export declare class FableEditor implements FableEditorApi {
128
138
  private currentBlockStyle;
129
139
  private lineHeightMenu;
130
140
  private wordSpacingMenu;
141
+ private letterSpacingMenu;
131
142
  private closestAnchor;
132
143
  private linkDlg;
133
144
  private blocksMenu;
@@ -146,9 +157,31 @@ export declare class FableEditor implements FableEditorApi {
146
157
  * (the quick color chips); everything else yields one. */
147
158
  private buildToolbarRegistry;
148
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;
149
169
  private mItem;
150
170
  private buildMenubar;
151
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;
152
185
  private applyTableProps;
153
186
  private tablePropsDlg;
154
187
  private currentCell;
@@ -222,36 +255,30 @@ export declare class FableEditor implements FableEditorApi {
222
255
  * <video> tag. The General tab also offers the same upload path as before
223
256
  * (videoUploadHandler when configured, else inline base64). */
224
257
  private videoDlg;
225
- private renderMathHTML;
226
- /** Shared by the math dialog, math paste, and math typing: renders `src` with
227
- * KaTeX and inserts it at the caret as the standard math element (inline span /
228
- * block div carrying its LaTeX source in data-latex). Block formulas get the
229
- * same \begin{aligned} wrapping the dialog applies, so pasted derivations align
230
- * their steps and re-open correctly in the edit dialog. */
231
- private insertMathElement;
232
- /** Recognizes a plain-text clipboard payload that is entirely one math formula:
233
- * $$…$$ / \[…\] (block), \(…\) (inline), a \begin{…}…\end{…} environment (block),
234
- * or $…$ (inline — only when the inside looks like LaTeX, so "$50" stays text).
235
- * Multi-line block content (a derivation pasted as separate lines) gets its lines
236
- * joined with \\ so each step lands on its own line. Returns null for anything
237
- * else, letting the normal paste pipeline run unchanged. */
238
- private mathFromClipboard;
239
- /** A derivation pasted as separate lines has no explicit \\ row breaks — add them.
240
- * Content that already breaks rows itself (\\ present, or a \begin environment
241
- * managing its own layout) only has its newlines collapsed to spaces. */
242
- private joinDerivationLines;
243
- /** Typing "$…$" converts to an inline formula the moment the closing "$" is typed,
244
- * so math can be written in place without opening the dialog. Only kicks in when
245
- * the run since the opening "$" parses as valid LaTeX and contains LaTeX-ish
246
- * syntax (\ ^ _ { } =) — a literal "$5 and 3$" stays plain text. */
247
- private tryMathTyping;
248
- private mathDlg;
249
258
  private escapeAttr;
250
- private unescapeAttr;
251
- private clearMathSel;
252
- private selectMathEl;
253
- private buildMathCtxToolbar;
254
- private positionMathCtx;
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;
255
282
  private templateMenu;
256
283
  /** Miniature div-based mockup of a layout, mirroring what the block looks
257
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;
@@ -148,12 +157,6 @@ export interface I18nStrings {
148
157
  videourlph: string;
149
158
  insertvideo: string;
150
159
  dropvideo: string;
151
- mathformula: string;
152
- mathdlgttl: string;
153
- mathderivation: string;
154
- mathlatex: string;
155
- editmath: string;
156
- deletemath: string;
157
160
  videodlgttl: string;
158
161
  vidgeneral: string;
159
162
  vidembed: string;
@@ -163,6 +166,13 @@ export interface I18nStrings {
163
166
  vidaltsource: string;
164
167
  vidposter: string;
165
168
  emoji: string;
169
+ code: string;
170
+ codesample: string;
171
+ codedlgttl: string;
172
+ codelang: string;
173
+ editcode: string;
174
+ deletecode: string;
175
+ copycode: string;
166
176
  chcat_currency: string;
167
177
  chcat_text: string;
168
178
  chcat_math: string;
@@ -60,6 +60,17 @@ export interface EditorInitOptions {
60
60
  videoUploadHandler?: (file: File) => Promise<string>;
61
61
  /** Called when videoUploadHandler rejects, so the host app can surface it. */
62
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;
63
74
  }
64
75
  export interface FableEditorApi {
65
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,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiZmFibGUtZWRpdG9yLmNvbXBvbmVudC5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uLy4uLy4uL3NyYy9hbmd1bGFyL2ZhYmxlLWVkaXRvci5jb21wb25lbnQudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUEsT0FBTyxFQUNMLFNBQVMsRUFFVCxZQUFZLEVBQ1osS0FBSyxFQUlMLE1BQU0sRUFFTixTQUFTLEVBQ1YsTUFBTSxlQUFlLENBQUM7QUFDdkIsT0FBTyxFQUFFLFdBQVcsRUFBcUQsTUFBTSxTQUFTLENBQUM7O0FBUXpGLE1BQU0sT0FBTyxvQkFBb0I7SUFDVyxTQUFTLENBQThCO0lBRXhFLFFBQVEsR0FBbUIsSUFBSSxDQUFDO0lBQ2hDLE1BQU0sR0FBRyxHQUFHLENBQUM7SUFDYixPQUFPLEdBQXFCLElBQUksQ0FBQztJQUNqQyxPQUFPLEdBQXFCLElBQUksQ0FBQztJQUNqQyxTQUFTLEdBQUcsSUFBSSxDQUFDO0lBQ2pCLFFBQVEsR0FBRyxLQUFLLENBQUM7SUFDakIsSUFBSSxHQUErQyxFQUFFLENBQUM7SUFDdEQsS0FBSyxHQUFHLEVBQUUsQ0FBQztJQUVWLFlBQVksR0FBRyxJQUFJLFlBQVksRUFBVSxDQUFDO0lBQzFDLFdBQVcsR0FBRyxJQUFJLFlBQVksRUFBa0IsQ0FBQztJQUVuRCxNQUFNLENBQWU7SUFDckIsV0FBVyxHQUFHLEVBQUUsQ0FBQztJQUV6QixRQUFRO1FBQ04sSUFBSSxDQUFDLFdBQVcsR0FBRyxJQUFJLENBQUMsS0FBSyxDQUFDO1FBQzlCLElBQUksQ0FBQyxNQUFNLEdBQUcsSUFBSSxXQUFXLENBQUM7WUFDNUIsTUFBTSxFQUFFLElBQUksQ0FBQyxTQUFTLENBQUMsYUFBYTtZQUNwQyxRQUFRLEVBQUUsSUFBSSxDQUFDLFFBQVE7WUFDdkIsTUFBTSxFQUFFLElBQUksQ0FBQyxNQUFNO1lBQ25CLE9BQU8sRUFBRSxJQUFJLENBQUMsT0FBTztZQUNyQixPQUFPLEVBQUUsSUFBSSxDQUFDLE9BQU87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QUVJLFlBQVk7c0JBQXJCLE1BQU07Z0JBQ0csV0FBVztzQkFBcEIsTUFBTSIsInNvdXJjZXNDb250ZW50IjpbImltcG9ydCB7XG4gIENvbXBvbmVudCxcbiAgRWxlbWVudFJlZixcbiAgRXZlbnRFbWl0dGVyLFxuICBJbnB1dCxcbiAgT25DaGFuZ2VzLFxuICBPbkRlc3Ryb3ksXG4gIE9uSW5pdCxcbiAgT3V0cHV0LFxuICBTaW1wbGVDaGFuZ2VzLFxuICBWaWV3Q2hpbGRcbn0gZnJvbSAnQGFuZ3VsYXIvY29yZSc7XG5pbXBvcnQgeyBGYWJsZUVkaXRvciwgRWRpdG9ySW5pdE9wdGlvbnMsIEVkaXRvckxhbmd1YWdlLCBGYWJsZUVkaXRvckFwaSB9IGZyb20gJy4uL2NvcmUnO1xuXG5AQ29tcG9uZW50KHtcbiAgc2VsZWN0b3I6ICdmYWJsZS1lZGl0b3InLFxuICBzdGFuZGFsb25lOiB0cnVlLFxuICB0ZW1wbGF0ZTogYDxkaXYgI2NvbnRhaW5lcj48L2Rpdj5gLFxuICBzdHlsZXM6IFsnOmhvc3QgeyBkaXNwbGF5OiBibG9jazsgfSddXG59KVxuZXhwb3J0IGNsYXNzIEZhYmxlRWRpdG9yQ29tcG9uZW50IGltcGxlbWVudHMgT25Jbml0LCBPbkNoYW5nZXMsIE9uRGVzdHJveSB7XG4gIEBWaWV3Q2hpbGQoJ2NvbnRhaW5lcicsIHsgc3RhdGljOiB0cnVlIH0pIGNvbnRhaW5lciE6IEVsZW1lbnRSZWY8SFRNTERpdkVsZW1lbnQ+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==