@kubex/zinc 1.1.94 → 1.1.96
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/dist/custom-elements.json +653 -89
- package/dist/vscode.html-custom-data.json +73 -18
- package/dist/web-types.json +145 -35
- package/dist/zn.d.ts +204 -39
- package/dist/zn.min.css +1 -1
- package/dist/zn.min.js +374 -308
- package/docs/pages/components/flow-builder.md +17 -1
- package/docs/pages/components/page-builder.md +31 -0
- package/docs/pages/components/slash-menu.md +132 -6
- package/docs/pages/components/textarea.md +16 -0
- package/package.json +1 -1
- package/scss/_root.scss +7 -1
- package/src/components/button/button.scss +5 -2
- package/src/components/flow-builder/flow-builder.component.ts +16 -1
- package/src/components/flow-builder/flow-builder.test.ts +206 -1
- package/src/components/flow-builder/flow-geometry.test.ts +102 -0
- package/src/components/flow-builder/flow.types.ts +82 -15
- package/src/components/flow-builder/modules/flow-canvas/flow-canvas.component.ts +163 -108
- package/src/components/flow-builder/modules/flow-step/flow-step.component.ts +2 -0
- package/src/components/inline-edit/inline-edit.component.ts +6 -1
- package/src/components/input/input.component.ts +12 -2
- package/src/components/page/page.scss +7 -2
- package/src/components/page-builder/modules/page-section-card/page-section-card.component.ts +16 -9
- package/src/components/page-builder/modules/page-section-card/page-section-card.scss +13 -0
- package/src/components/page-builder/modules/page-section-card/page-section-card.test.ts +9 -0
- package/src/components/page-builder/page-builder.component.ts +62 -4
- package/src/components/page-builder/page-builder.test.ts +94 -0
- package/src/components/remarkd-editor/remarkd-editor.component.ts +227 -12
- package/src/components/remarkd-editor/remarkd-editor.scss +81 -0
- package/src/components/remarkd-editor/remarkd-editor.test.ts +258 -0
- package/src/components/settings-container/settings-container.scss +2 -1
- package/src/components/slash-item/slash-item.component.ts +1 -1
- package/src/components/slash-menu/slash-menu-items.ts +48 -0
- package/src/components/slash-menu/slash-menu.component.ts +134 -27
- package/src/components/slash-menu/slash-menu.scss +90 -12
- package/src/components/slash-menu/slash-menu.test.ts +107 -0
- package/src/components/textarea/textarea.component.ts +12 -2
- package/src/components/textarea/textarea.test.ts +2 -2
- package/src/components/translations/translations.component.ts +5 -1
|
@@ -4,12 +4,10 @@ import {defaultValue} from "../../internal/default-value";
|
|
|
4
4
|
import {FormControlController} from "../../internal/form";
|
|
5
5
|
import {property, query, state} from 'lit/decorators.js';
|
|
6
6
|
import {parse as remarkdParse} from "remarkd-js";
|
|
7
|
-
import {SlashMenuController} from "../slash-menu/slash-menu-controller";
|
|
8
7
|
import {unsafeHTML} from "lit/directives/unsafe-html.js";
|
|
9
8
|
import {watch} from "../../internal/watch";
|
|
10
9
|
import ZincElement from '../../internal/zinc-element';
|
|
11
|
-
import ZnSlashMenu from "../slash-menu";
|
|
12
|
-
import type {SlashMenuItem} from "../slash-menu/slash-menu-items";
|
|
10
|
+
import ZnSlashMenu, {SlashMenuController, type SlashMenuItem} from "../slash-menu";
|
|
13
11
|
import type {ZincFormControl} from '../../internal/zinc-element';
|
|
14
12
|
import type ZnFile from "../file";
|
|
15
13
|
|
|
@@ -28,6 +26,7 @@ interface BlockType {
|
|
|
28
26
|
/** Where the caret lands within `prefix`. Defaults to the end. */
|
|
29
27
|
caretOffset?: number;
|
|
30
28
|
image?: boolean;
|
|
29
|
+
include?: boolean;
|
|
31
30
|
}
|
|
32
31
|
|
|
33
32
|
interface ImageBlockData {
|
|
@@ -39,6 +38,21 @@ interface ImageBlockData {
|
|
|
39
38
|
height: string;
|
|
40
39
|
}
|
|
41
40
|
|
|
41
|
+
interface IncludeBlockData {
|
|
42
|
+
target: string;
|
|
43
|
+
label: string;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
interface IncludeOption {
|
|
47
|
+
id: string;
|
|
48
|
+
title: string;
|
|
49
|
+
description?: string;
|
|
50
|
+
scope?: string;
|
|
51
|
+
keywords?: string[];
|
|
52
|
+
languages?: string;
|
|
53
|
+
url?: string;
|
|
54
|
+
}
|
|
55
|
+
|
|
42
56
|
const BLOCK_TYPES: BlockType[] = [
|
|
43
57
|
{label: 'Text', icon: 'text@lu', prefix: ''},
|
|
44
58
|
{label: 'Heading 1', icon: 'heading-1@lu', prefix: '# '},
|
|
@@ -49,11 +63,25 @@ const BLOCK_TYPES: BlockType[] = [
|
|
|
49
63
|
{label: 'Warning', icon: 'triangle-alert@lu', prefix: 'WARNING: '},
|
|
50
64
|
{label: 'Code', icon: 'code@lu', prefix: '```\n\n```', caretOffset: 4},
|
|
51
65
|
{label: 'Image', icon: 'image@lu', image: true},
|
|
66
|
+
{label: 'Include', icon: 'blocks@lu', include: true},
|
|
52
67
|
];
|
|
53
68
|
|
|
69
|
+
/** remarkd's include directive on a line of its own: `include::<target>[<label>]`. */
|
|
70
|
+
const INCLUDE_LINE = /^include::([^[]+)\[(.*)]\s*$/;
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* The marker a document body carries for an embed. Mirrors app-kb's
|
|
74
|
+
* `model.IncludeEmbedMarker`: brackets and newlines in the title would break the
|
|
75
|
+
* directive, so they are replaced rather than escaped.
|
|
76
|
+
*/
|
|
77
|
+
function includeMarker(id: string, title: string): string {
|
|
78
|
+
const label = title.replace(/[\r\n]+/g, ' ').replace(/\[/g, '(').replace(/]/g, ')').trim();
|
|
79
|
+
return `include::${id}[${label}]`;
|
|
80
|
+
}
|
|
81
|
+
|
|
54
82
|
/** The block types as slash menu insertions. Image opens the picker, so it inserts nothing. */
|
|
55
|
-
const SLASH_ITEMS: SlashMenuItem[] = BLOCK_TYPES.map(item => item.image
|
|
56
|
-
? {label: item.label, icon: item.icon, action: 'image'}
|
|
83
|
+
const SLASH_ITEMS: SlashMenuItem[] = BLOCK_TYPES.map(item => item.image || item.include
|
|
84
|
+
? {label: item.label, icon: item.icon, action: item.image ? 'image' : 'include'}
|
|
57
85
|
: {label: item.label, icon: item.icon, value: item.prefix ?? '', caretOffset: item.caretOffset});
|
|
58
86
|
|
|
59
87
|
/**
|
|
@@ -80,6 +108,8 @@ const SLASH_ITEMS: SlashMenuItem[] = BLOCK_TYPES.map(item => item.image
|
|
|
80
108
|
* @csspart raw - The full-document textarea shown in raw source mode.
|
|
81
109
|
* @csspart slash-menu - The `zn-slash-menu` opened by typing "/" in an empty block.
|
|
82
110
|
* @csspart image-controls - The caption / alignment / size panel shown when an image block is clicked.
|
|
111
|
+
* @csspart include - The chip rendered in place of an `include::` directive.
|
|
112
|
+
* @csspart include-picker - The inline Include picker opened from the toolbar or "/include".
|
|
83
113
|
*/
|
|
84
114
|
export default class ZnRemarkdEditor extends ZincElement implements ZincFormControl {
|
|
85
115
|
static styles: CSSResultGroup = unsafeCSS(styles);
|
|
@@ -95,11 +125,14 @@ export default class ZnRemarkdEditor extends ZincElement implements ZincFormCont
|
|
|
95
125
|
menu: () => this.mountSlashMenu(),
|
|
96
126
|
// The menu only belongs in a block that is nothing but the slash command — a
|
|
97
127
|
// block prefix like "## " is not valid remarkd part-way through a line.
|
|
98
|
-
items: () => this.isSlashBlock()
|
|
128
|
+
items: () => this.isSlashBlock()
|
|
129
|
+
? SLASH_ITEMS.filter(item => item.action !== 'include' || !!this.includeUrl)
|
|
130
|
+
: [],
|
|
99
131
|
onSelect: item => this.handleSlashSelect(item)
|
|
100
132
|
});
|
|
101
133
|
|
|
102
134
|
private editingDraft = '';
|
|
135
|
+
private includeRequest: Promise<IncludeOption[]> | null = null;
|
|
103
136
|
private rawEntryValue = '';
|
|
104
137
|
private suppressValueSync = false;
|
|
105
138
|
private suppressBlurCommit = false;
|
|
@@ -109,6 +142,12 @@ export default class ZnRemarkdEditor extends ZincElement implements ZincFormCont
|
|
|
109
142
|
|
|
110
143
|
@state() private blocks: string[] = [];
|
|
111
144
|
@state() private editingIndex: number | null = null;
|
|
145
|
+
/**
|
|
146
|
+
* Lists the blocks most recently inserted here above the rest of the slash menu, remembered in
|
|
147
|
+
* `localStorage` under this key. Leave unset to offer no recently used section.
|
|
148
|
+
*/
|
|
149
|
+
@property({attribute: 'slash-recent-key'}) slashRecentKey = '';
|
|
150
|
+
|
|
112
151
|
/** Renders the slash menu only once it has been needed. */
|
|
113
152
|
@state() private hasSlashMenu = false;
|
|
114
153
|
@state() private imagePickerIndex: number | null = null;
|
|
@@ -117,6 +156,10 @@ export default class ZnRemarkdEditor extends ZincElement implements ZincFormCont
|
|
|
117
156
|
@state() private dragIndex: number | null = null;
|
|
118
157
|
@state() private editShell = '';
|
|
119
158
|
@state() private rawMode = false;
|
|
159
|
+
@state() private includeOptions: IncludeOption[] | null = null;
|
|
160
|
+
@state() private includeLoadFailed = false;
|
|
161
|
+
@state() private includePickerIndex: number | null = null;
|
|
162
|
+
@state() private includeQuery = '';
|
|
120
163
|
|
|
121
164
|
private pendingDragHandle: HTMLElement | null = null;
|
|
122
165
|
private dragStartX = 0;
|
|
@@ -142,6 +185,13 @@ export default class ZnRemarkdEditor extends ZincElement implements ZincFormCont
|
|
|
142
185
|
*/
|
|
143
186
|
@property({attribute: 'attachment-url'}) attachmentUrl = '';
|
|
144
187
|
|
|
188
|
+
/**
|
|
189
|
+
* Endpoint listing the Includes this document may embed, as
|
|
190
|
+
* `{"items":[{id,title,description,scope,keywords,languages,url}]}`. Labels the
|
|
191
|
+
* chips rendered for `include::` directives and feeds the include picker.
|
|
192
|
+
*/
|
|
193
|
+
@property({attribute: 'include-url'}) includeUrl = '';
|
|
194
|
+
|
|
145
195
|
/** Adds a toolbar toggle that swaps the block view for the full remarkd source. */
|
|
146
196
|
@property({type: Boolean, attribute: 'allow-raw', reflect: true}) allowRaw = false;
|
|
147
197
|
|
|
@@ -201,6 +251,7 @@ export default class ZnRemarkdEditor extends ZincElement implements ZincFormCont
|
|
|
201
251
|
protected firstUpdated(_changedProperties: PropertyValues) {
|
|
202
252
|
super.firstUpdated(_changedProperties);
|
|
203
253
|
this.formControlController.updateValidity();
|
|
254
|
+
if (this.hasIncludeBlock()) void this.loadIncludeOptions();
|
|
204
255
|
}
|
|
205
256
|
|
|
206
257
|
disconnectedCallback() {
|
|
@@ -215,6 +266,15 @@ export default class ZnRemarkdEditor extends ZincElement implements ZincFormCont
|
|
|
215
266
|
return;
|
|
216
267
|
}
|
|
217
268
|
this.blocks = this.splitBlocks(this.value || '');
|
|
269
|
+
if (this.hasUpdated && this.hasIncludeBlock()) void this.loadIncludeOptions();
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
@watch('includeUrl', {waitUntilFirstUpdate: true})
|
|
273
|
+
handleIncludeUrlChange() {
|
|
274
|
+
this.includeRequest = null;
|
|
275
|
+
this.includeOptions = null;
|
|
276
|
+
this.includeLoadFailed = false;
|
|
277
|
+
if (this.hasIncludeBlock()) void this.loadIncludeOptions();
|
|
218
278
|
}
|
|
219
279
|
|
|
220
280
|
/**
|
|
@@ -246,6 +306,12 @@ export default class ZnRemarkdEditor extends ZincElement implements ZincFormCont
|
|
|
246
306
|
fence = marker;
|
|
247
307
|
continue;
|
|
248
308
|
}
|
|
309
|
+
if (INCLUDE_LINE.test(trimmed)) {
|
|
310
|
+
push();
|
|
311
|
+
current.push(line);
|
|
312
|
+
push();
|
|
313
|
+
continue;
|
|
314
|
+
}
|
|
249
315
|
if (trimmed === '') {
|
|
250
316
|
push();
|
|
251
317
|
continue;
|
|
@@ -337,6 +403,13 @@ export default class ZnRemarkdEditor extends ZincElement implements ZincFormCont
|
|
|
337
403
|
return found ? data : null;
|
|
338
404
|
}
|
|
339
405
|
|
|
406
|
+
/** Parses a block that is nothing but an include directive. */
|
|
407
|
+
private parseIncludeBlock(block: string): IncludeBlockData | null {
|
|
408
|
+
const match = INCLUDE_LINE.exec((block ?? '').trim());
|
|
409
|
+
if (!match) return null;
|
|
410
|
+
return {target: match[1].trim(), label: match[2].trim()};
|
|
411
|
+
}
|
|
412
|
+
|
|
340
413
|
private serializeImageBlock(data: ImageBlockData): string {
|
|
341
414
|
const lines: string[] = [];
|
|
342
415
|
if (data.caption.trim()) lines.push(`.${data.caption.trim()}`);
|
|
@@ -549,14 +622,18 @@ export default class ZnRemarkdEditor extends ZincElement implements ZincFormCont
|
|
|
549
622
|
|
|
550
623
|
/** Returns false for items the controller should not insert text for. */
|
|
551
624
|
private handleSlashSelect(item: SlashMenuItem): boolean {
|
|
552
|
-
if (item.action !== 'image') return true;
|
|
625
|
+
if (item.action !== 'image' && item.action !== 'include') return true;
|
|
553
626
|
|
|
554
627
|
const index = this.editingIndex ?? this.blocks.length;
|
|
555
628
|
// Let the controller strip the "/…" text first, then drop the empty draft
|
|
556
|
-
// block and run the
|
|
629
|
+
// block and run the picker in its place.
|
|
557
630
|
void this.updateComplete.then(() => {
|
|
558
631
|
this.blur();
|
|
559
|
-
|
|
632
|
+
if (item.action === 'image') {
|
|
633
|
+
this.pickImage(index);
|
|
634
|
+
} else {
|
|
635
|
+
this.pickInclude(index);
|
|
636
|
+
}
|
|
560
637
|
});
|
|
561
638
|
return false;
|
|
562
639
|
}
|
|
@@ -682,9 +759,28 @@ export default class ZnRemarkdEditor extends ZincElement implements ZincFormCont
|
|
|
682
759
|
|
|
683
760
|
private pickImage(index: number) {
|
|
684
761
|
if (this.disabled || this.readonly) return;
|
|
762
|
+
this.includePickerIndex = null;
|
|
685
763
|
this.imagePickerIndex = index;
|
|
686
764
|
}
|
|
687
765
|
|
|
766
|
+
private pickInclude(index: number) {
|
|
767
|
+
if (this.disabled || this.readonly || !this.includeUrl) return;
|
|
768
|
+
this.imagePickerIndex = null;
|
|
769
|
+
this.includeQuery = '';
|
|
770
|
+
this.includePickerIndex = index;
|
|
771
|
+
void this.loadIncludeOptions();
|
|
772
|
+
}
|
|
773
|
+
|
|
774
|
+
private closeIncludePicker = () => {
|
|
775
|
+
this.includePickerIndex = null;
|
|
776
|
+
};
|
|
777
|
+
|
|
778
|
+
private insertInclude(item: IncludeOption) {
|
|
779
|
+
const index = this.includePickerIndex ?? this.blocks.length;
|
|
780
|
+
this.includePickerIndex = null;
|
|
781
|
+
this.addBlockAt(index, includeMarker(item.id, item.title));
|
|
782
|
+
}
|
|
783
|
+
|
|
688
784
|
private closeImagePicker = () => {
|
|
689
785
|
this.imagePickerIndex = null;
|
|
690
786
|
};
|
|
@@ -731,6 +827,53 @@ export default class ZnRemarkdEditor extends ZincElement implements ZincFormCont
|
|
|
731
827
|
return data.uploadPath;
|
|
732
828
|
}
|
|
733
829
|
|
|
830
|
+
/**
|
|
831
|
+
* Resolves an app-relative path the way the console's pagelet handler does:
|
|
832
|
+
* an app fragment's URLs sit under the app base, and the console puts the app's
|
|
833
|
+
* `gaid` on the host element. Links rendered here live in the shadow root,
|
|
834
|
+
* where a click retargets to the host, so that handler never sees them — the
|
|
835
|
+
* href has to carry the base itself.
|
|
836
|
+
*/
|
|
837
|
+
private appPath(path: string): string {
|
|
838
|
+
const gaid = this.getAttribute('gaid') ?? '';
|
|
839
|
+
const trimmed = path.replace(/^\/+/, '');
|
|
840
|
+
if (!gaid || trimmed.startsWith(gaid)) return path;
|
|
841
|
+
return `/${gaid}/${trimmed}`;
|
|
842
|
+
}
|
|
843
|
+
|
|
844
|
+
private hasIncludeBlock(): boolean {
|
|
845
|
+
return this.blocks.some(block => this.parseIncludeBlock(block) !== null);
|
|
846
|
+
}
|
|
847
|
+
|
|
848
|
+
/** Fetches the include list once; every later caller shares the same promise. */
|
|
849
|
+
private loadIncludeOptions(): Promise<IncludeOption[]> {
|
|
850
|
+
if (!this.includeUrl) return Promise.resolve([]);
|
|
851
|
+
if (!this.includeRequest) {
|
|
852
|
+
this.includeRequest = fetch(this.includeUrl, {headers: {Accept: 'application/json'}})
|
|
853
|
+
.then(res => {
|
|
854
|
+
if (!res.ok) throw new Error(`include list request failed: ${res.status}`);
|
|
855
|
+
return res.json() as Promise<{items?: IncludeOption[]}>;
|
|
856
|
+
})
|
|
857
|
+
.then(data => {
|
|
858
|
+
const items = data.items ?? [];
|
|
859
|
+
this.includeOptions = items;
|
|
860
|
+
this.includeLoadFailed = false;
|
|
861
|
+
return items;
|
|
862
|
+
})
|
|
863
|
+
.catch(error => {
|
|
864
|
+
// A list that never arrived is not evidence an embed is broken: leave
|
|
865
|
+
// the options unresolved so chips keep their marker labels, and let
|
|
866
|
+
// the next picker open try again.
|
|
867
|
+
console.error('[zn-remarkd-editor] include list failed', error);
|
|
868
|
+
this.includeOptions = null;
|
|
869
|
+
this.includeLoadFailed = true;
|
|
870
|
+
this.includeRequest = null;
|
|
871
|
+
return [] as IncludeOption[];
|
|
872
|
+
});
|
|
873
|
+
}
|
|
874
|
+
return this.includeRequest;
|
|
875
|
+
}
|
|
876
|
+
|
|
734
877
|
private autosize(input: HTMLTextAreaElement) {
|
|
735
878
|
input.style.height = 'auto';
|
|
736
879
|
input.style.height = `${input.scrollHeight}px`;
|
|
@@ -796,6 +939,8 @@ export default class ZnRemarkdEditor extends ZincElement implements ZincFormCont
|
|
|
796
939
|
const index = this.editingIndex !== null ? this.commitEdit() : this.blocks.length;
|
|
797
940
|
if (item.image) {
|
|
798
941
|
this.pickImage(index);
|
|
942
|
+
} else if (item.include) {
|
|
943
|
+
this.pickInclude(index);
|
|
799
944
|
} else {
|
|
800
945
|
this.insertDraftBlock(index, item.prefix ?? '');
|
|
801
946
|
}
|
|
@@ -867,6 +1012,33 @@ export default class ZnRemarkdEditor extends ZincElement implements ZincFormCont
|
|
|
867
1012
|
</div>`;
|
|
868
1013
|
}
|
|
869
1014
|
|
|
1015
|
+
private renderIncludeChip(data: IncludeBlockData) {
|
|
1016
|
+
const option = this.includeOptions?.find(item => item.id === data.target);
|
|
1017
|
+
// A path-like target is remarkd's file include, which this editor cannot
|
|
1018
|
+
// resolve and must not call broken.
|
|
1019
|
+
const filePath = /[/.]/.test(data.target);
|
|
1020
|
+
const pending = !this.includeUrl || this.includeOptions === null;
|
|
1021
|
+
const missing = !option && !pending && !filePath;
|
|
1022
|
+
return html`
|
|
1023
|
+
<div part="include"
|
|
1024
|
+
class=${classMap({
|
|
1025
|
+
'remarkd-editor__include': true,
|
|
1026
|
+
'remarkd-editor__include--missing': missing,
|
|
1027
|
+
})}>
|
|
1028
|
+
<zn-icon src=${missing ? 'triangle-alert@lu' : 'blocks@lu'} size="16"></zn-icon>
|
|
1029
|
+
<span class="remarkd-editor__include-title">${option?.title || data.label || data.target}</span>
|
|
1030
|
+
${option?.scope ? html`
|
|
1031
|
+
<span class="remarkd-editor__include-scope">${option.scope}</span>` : ''}
|
|
1032
|
+
<span class="remarkd-editor__include-meta">${missing
|
|
1033
|
+
? `Include not found · ${data.target}`
|
|
1034
|
+
: !option && filePath ? 'File include' : 'Included content'}</span>
|
|
1035
|
+
${option?.url ? html`
|
|
1036
|
+
<a class="remarkd-editor__include-link" href=${this.appPath(option.url)}
|
|
1037
|
+
target="_blank" rel="noreferrer"
|
|
1038
|
+
@click=${(e: MouseEvent) => e.stopPropagation()}>Open</a>` : ''}
|
|
1039
|
+
</div>`;
|
|
1040
|
+
}
|
|
1041
|
+
|
|
870
1042
|
private renderBlock(block: string, index: number) {
|
|
871
1043
|
if (this.editingIndex === index) {
|
|
872
1044
|
if (this.imageEdit) {
|
|
@@ -890,6 +1062,7 @@ export default class ZnRemarkdEditor extends ZincElement implements ZincFormCont
|
|
|
890
1062
|
</div>`;
|
|
891
1063
|
}
|
|
892
1064
|
|
|
1065
|
+
const include = this.parseIncludeBlock(block);
|
|
893
1066
|
return html`
|
|
894
1067
|
<div part="block"
|
|
895
1068
|
class=${classMap({
|
|
@@ -914,7 +1087,8 @@ export default class ZnRemarkdEditor extends ZincElement implements ZincFormCont
|
|
|
914
1087
|
tooltip="Delete block"
|
|
915
1088
|
@click=${() => this.deleteBlock(index)}></zn-button>`}
|
|
916
1089
|
<div part="rendered" class="remarkd-editor__rendered remarkd-rendered"
|
|
917
|
-
@click=${(e: MouseEvent) => this.handleRenderedClick(e, index)}>${
|
|
1090
|
+
@click=${(e: MouseEvent) => this.handleRenderedClick(e, index)}>${
|
|
1091
|
+
include ? this.renderIncludeChip(include) : unsafeHTML(remarkdParse(block))}
|
|
918
1092
|
</div>
|
|
919
1093
|
</div>`;
|
|
920
1094
|
}
|
|
@@ -932,7 +1106,9 @@ export default class ZnRemarkdEditor extends ZincElement implements ZincFormCont
|
|
|
932
1106
|
@drop=${this.handleDrop}>
|
|
933
1107
|
${editable ? html`
|
|
934
1108
|
<div part="toolbar" class="remarkd-editor__toolbar">
|
|
935
|
-
${this.rawMode ? '' : BLOCK_TYPES
|
|
1109
|
+
${this.rawMode ? '' : BLOCK_TYPES
|
|
1110
|
+
.filter(item => !item.include || !!this.includeUrl)
|
|
1111
|
+
.map(item => html`
|
|
936
1112
|
<zn-button type="button" icon-button plain icon=${item.icon} icon-size="18"
|
|
937
1113
|
tooltip=${item.label}
|
|
938
1114
|
@click=${() => this.handleToolbarInsert(item)}></zn-button>`)}
|
|
@@ -954,7 +1130,10 @@ export default class ZnRemarkdEditor extends ZincElement implements ZincFormCont
|
|
|
954
1130
|
</div>`}
|
|
955
1131
|
</div>
|
|
956
1132
|
${this.hasSlashMenu ? html`
|
|
957
|
-
<zn-slash-menu
|
|
1133
|
+
<zn-slash-menu
|
|
1134
|
+
part="slash-menu"
|
|
1135
|
+
heading="Insert block"
|
|
1136
|
+
recent-key=${this.slashRecentKey}></zn-slash-menu>` : ''}
|
|
958
1137
|
<textarea class="remarkd-editor__validation"
|
|
959
1138
|
.value=${this.value}
|
|
960
1139
|
?required=${this.required}
|
|
@@ -980,9 +1159,45 @@ export default class ZnRemarkdEditor extends ZincElement implements ZincFormCont
|
|
|
980
1159
|
if (this.imagePickerIndex !== null) {
|
|
981
1160
|
views.splice(this.imagePickerIndex, 0, this.renderImagePicker());
|
|
982
1161
|
}
|
|
1162
|
+
if (this.includePickerIndex !== null) {
|
|
1163
|
+
views.splice(this.includePickerIndex, 0, this.renderIncludePicker());
|
|
1164
|
+
}
|
|
983
1165
|
return views;
|
|
984
1166
|
}
|
|
985
1167
|
|
|
1168
|
+
private renderIncludePicker() {
|
|
1169
|
+
const filter = this.includeQuery.trim().toLowerCase();
|
|
1170
|
+
const items = (this.includeOptions ?? []).filter(item => !filter || [
|
|
1171
|
+
item.title, item.description ?? '', (item.keywords ?? []).join(' '),
|
|
1172
|
+
].some(field => field.toLowerCase().includes(filter)));
|
|
1173
|
+
return html`
|
|
1174
|
+
<div part="include-picker" class="remarkd-editor__include-picker">
|
|
1175
|
+
<div class="remarkd-editor__include-picker-head">
|
|
1176
|
+
<input class="remarkd-editor__include-filter"
|
|
1177
|
+
placeholder="Find an Include"
|
|
1178
|
+
.value=${this.includeQuery}
|
|
1179
|
+
@input=${(e: Event) => {
|
|
1180
|
+
this.includeQuery = (e.target as HTMLInputElement).value;
|
|
1181
|
+
}}>
|
|
1182
|
+
<zn-button type="button" icon-button="small" plain icon="x@lu"
|
|
1183
|
+
tooltip="Cancel" @click=${this.closeIncludePicker}></zn-button>
|
|
1184
|
+
</div>
|
|
1185
|
+
${this.includeOptions === null
|
|
1186
|
+
? html`<div class="remarkd-editor__include-picker-empty">${
|
|
1187
|
+
this.includeLoadFailed ? 'Could not load Includes' : 'Loading…'}</div>`
|
|
1188
|
+
: items.length
|
|
1189
|
+
? items.map(item => html`
|
|
1190
|
+
<button type="button" class="remarkd-editor__include-option"
|
|
1191
|
+
?disabled=${!item.languages}
|
|
1192
|
+
@click=${() => this.insertInclude(item)}>
|
|
1193
|
+
<span class="remarkd-editor__include-option-title">${item.title}</span>
|
|
1194
|
+
<span class="remarkd-editor__include-option-meta">${item.scope ?? ''}${
|
|
1195
|
+
item.languages ? ` · ${item.languages}` : ' · No content'}</span>
|
|
1196
|
+
</button>`)
|
|
1197
|
+
: html`<div class="remarkd-editor__include-picker-empty">No Includes found</div>`}
|
|
1198
|
+
</div>`;
|
|
1199
|
+
}
|
|
1200
|
+
|
|
986
1201
|
private renderImagePicker() {
|
|
987
1202
|
return html`
|
|
988
1203
|
<div class="remarkd-editor__image-picker">
|
|
@@ -224,6 +224,87 @@
|
|
|
224
224
|
}
|
|
225
225
|
}
|
|
226
226
|
|
|
227
|
+
.remarkd-editor__include {
|
|
228
|
+
display: flex;
|
|
229
|
+
align-items: center;
|
|
230
|
+
gap: var(--zn-spacing-small);
|
|
231
|
+
padding: var(--zn-spacing-small);
|
|
232
|
+
border: 1px dashed rgb(var(--zn-border-color));
|
|
233
|
+
border-radius: var(--zn-border-radius);
|
|
234
|
+
background: var(--zn-color-neutral-0, white);
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
.remarkd-editor__include-title {
|
|
238
|
+
font-weight: 600;
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
.remarkd-editor__include-meta {
|
|
242
|
+
color: var(--zn-color-neutral-400, #9aa4b2);
|
|
243
|
+
font-size: var(--zn-font-size-small);
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
.remarkd-editor__include--missing {
|
|
247
|
+
border-style: solid;
|
|
248
|
+
border-color: rgb(var(--zn-color-error));
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
.remarkd-editor__include-scope {
|
|
252
|
+
color: var(--zn-color-neutral-400, #9aa4b2);
|
|
253
|
+
font-size: var(--zn-font-size-small);
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
.remarkd-editor__include-link {
|
|
257
|
+
margin-left: auto;
|
|
258
|
+
font-size: var(--zn-font-size-small);
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
.remarkd-editor__include-picker {
|
|
262
|
+
display: flex;
|
|
263
|
+
flex-direction: column;
|
|
264
|
+
gap: var(--zn-spacing-x-small);
|
|
265
|
+
padding: var(--zn-spacing-small);
|
|
266
|
+
border: 1px solid rgb(var(--zn-border-color));
|
|
267
|
+
border-radius: var(--zn-border-radius);
|
|
268
|
+
background: var(--zn-color-neutral-0, white);
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
.remarkd-editor__include-picker-head {
|
|
272
|
+
display: flex;
|
|
273
|
+
align-items: center;
|
|
274
|
+
gap: var(--zn-spacing-x-small);
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
.remarkd-editor__include-filter {
|
|
278
|
+
flex: 1 1 auto;
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
.remarkd-editor__include-option {
|
|
282
|
+
display: flex;
|
|
283
|
+
justify-content: space-between;
|
|
284
|
+
gap: var(--zn-spacing-small);
|
|
285
|
+
padding: var(--zn-spacing-x-small) var(--zn-spacing-small);
|
|
286
|
+
border: 0;
|
|
287
|
+
border-radius: var(--zn-border-radius);
|
|
288
|
+
background: none;
|
|
289
|
+
cursor: pointer;
|
|
290
|
+
text-align: left;
|
|
291
|
+
|
|
292
|
+
&:hover:not(:disabled) {
|
|
293
|
+
background: var(--zn-color-neutral-100);
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
&:disabled {
|
|
297
|
+
cursor: default;
|
|
298
|
+
opacity: 0.6;
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
.remarkd-editor__include-option-meta,
|
|
303
|
+
.remarkd-editor__include-picker-empty {
|
|
304
|
+
color: var(--zn-color-neutral-400, #9aa4b2);
|
|
305
|
+
font-size: var(--zn-font-size-small);
|
|
306
|
+
}
|
|
307
|
+
|
|
227
308
|
.remarkd-editor__image-controls {
|
|
228
309
|
display: flex;
|
|
229
310
|
flex-direction: column;
|