@kubex/zinc 1.1.162 → 1.1.164

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.
@@ -1,5 +1,6 @@
1
- import {ACTION_GROUPS, EDITOR_ACTIONS, slashItems} from './actions';
1
+ import {ACTION_GROUPS, EDITOR_ACTIONS, matchesShortcut, shortcutLabel, slashItems} from './actions';
2
2
  import {classMap} from "lit/directives/class-map.js";
3
+ import {continueList} from './list-continuation';
3
4
  import {type CSSResultGroup, html, type PropertyValues, type TemplateResult, unsafeCSS} from 'lit';
4
5
  import {defaultValue} from "../../internal/default-value";
5
6
  import {FormControlController} from "../../internal/form";
@@ -279,7 +280,8 @@ function renderConditionalParts(parts: ConditionalPart[]): TemplateResult {
279
280
  ${parts.map(part => {
280
281
  if (part.kind === 'nested') return renderConditionalWrapper(part.label, part.parts);
281
282
  const text = part.lines.join('\n').trim();
282
- return text ? html`<div class="remarkd-editor__rendered--parsed">${unsafeHTML(safeParse(text))}</div>` : '';
283
+ return text ? html`
284
+ <div class="remarkd-editor__rendered--parsed">${unsafeHTML(safeParse(text))}</div>` : '';
283
285
  })}
284
286
  </div>`;
285
287
  }
@@ -953,7 +955,17 @@ export default class ZnRemarkdEditor extends ZincElement implements ZincFormCont
953
955
  // The slash menu claims navigation, Enter and Escape in the capture phase.
954
956
  if (this.slashController.open) return;
955
957
 
956
- if (e.key === 'Enter' && e.shiftKey) {
958
+ const shortcutAction = EDITOR_ACTIONS.find(action =>
959
+ action.shortcut && matchesShortcut(e, action.shortcut) && this.actionAvailable(action));
960
+ if (shortcutAction) {
961
+ e.preventDefault();
962
+ this.activateAction(shortcutAction);
963
+ } else if (this.isPlainEnter(e)) {
964
+ const edit = continueList(input.value, input.selectionStart, input.selectionEnd);
965
+ if (!edit) return;
966
+ e.preventDefault();
967
+ this.applyDraftEdit(input, edit.value, edit.caret, edit.caret);
968
+ } else if (e.key === 'Enter' && e.shiftKey) {
957
969
  e.preventDefault();
958
970
  this.suppressBlurCommit = true;
959
971
  const next = this.commitEdit();
@@ -967,6 +979,21 @@ export default class ZnRemarkdEditor extends ZincElement implements ZincFormCont
967
979
  }
968
980
  };
969
981
 
982
+ private isPlainEnter(e: KeyboardEvent): boolean {
983
+ return e.key === 'Enter' && !e.isComposing && !e.shiftKey && !e.metaKey && !e.ctrlKey && !e.altKey;
984
+ }
985
+
986
+ private handleRawKeydown = (e: KeyboardEvent) => {
987
+ if (!this.isPlainEnter(e)) return;
988
+ const input = e.target as HTMLTextAreaElement;
989
+ const edit = continueList(input.value, input.selectionStart, input.selectionEnd);
990
+ if (!edit) return;
991
+ e.preventDefault();
992
+ input.value = edit.value;
993
+ input.setSelectionRange(edit.caret, edit.caret);
994
+ this.handleRawInput(e);
995
+ };
996
+
970
997
  /** Whether the block being edited is nothing but the slash command. */
971
998
  private isSlashBlock(): boolean {
972
999
  const value = this.shadowRoot?.querySelector<HTMLTextAreaElement>('.remarkd-editor__input')?.value ?? '';
@@ -1232,7 +1259,7 @@ export default class ZnRemarkdEditor extends ZincElement implements ZincFormCont
1232
1259
  fetch(url, {headers: {Accept: 'application/json'}})
1233
1260
  .then(res => {
1234
1261
  if (!res.ok) throw new Error(`link search failed: ${res.status}`);
1235
- return res.json() as Promise<{items?: LinkOption[]}>;
1262
+ return res.json() as Promise<{ items?: LinkOption[] }>;
1236
1263
  })
1237
1264
  .then(data => {
1238
1265
  if (token !== this.linkSearchToken) return;
@@ -1329,7 +1356,7 @@ export default class ZnRemarkdEditor extends ZincElement implements ZincFormCont
1329
1356
  this.includeRequest = fetch(this.includeUrl, {headers: {Accept: 'application/json'}})
1330
1357
  .then(res => {
1331
1358
  if (!res.ok) throw new Error(`include list request failed: ${res.status}`);
1332
- return res.json() as Promise<{items?: IncludeOption[]}>;
1359
+ return res.json() as Promise<{ items?: IncludeOption[] }>;
1333
1360
  })
1334
1361
  .then(data => {
1335
1362
  const items = data.items ?? [];
@@ -1367,7 +1394,7 @@ export default class ZnRemarkdEditor extends ZincElement implements ZincFormCont
1367
1394
  fetch(url, {headers: {Accept: 'application/json'}})
1368
1395
  .then(res => {
1369
1396
  if (!res.ok) throw new Error(`link resolve failed: ${res.status}`);
1370
- return res.json() as Promise<{items?: LinkOption[]}>;
1397
+ return res.json() as Promise<{ items?: LinkOption[] }>;
1371
1398
  })
1372
1399
  .then(data => {
1373
1400
  const known = new Map((data.items ?? []).map(item => [item.ref, item]));
@@ -1403,7 +1430,7 @@ export default class ZnRemarkdEditor extends ZincElement implements ZincFormCont
1403
1430
  /**
1404
1431
  * Applies an inline mark to the open block's textarea: wraps the selection, toggles the
1405
1432
  * mark off when it is already wrapped, or inserts a selected placeholder when there is
1406
- * no selection. Goes through `editingDraft` so `zn-input` still fires.
1433
+ * no selection.
1407
1434
  */
1408
1435
  private applyInline(mark: InlineMark) {
1409
1436
  const input = this.shadowRoot?.querySelector<HTMLTextAreaElement>('.remarkd-editor__input');
@@ -1450,10 +1477,15 @@ export default class ZnRemarkdEditor extends ZincElement implements ZincFormCont
1450
1477
  caret = [start + mark.before.length, start + mark.before.length + body.length];
1451
1478
  }
1452
1479
 
1453
- this.editingDraft = next;
1454
- input.value = next;
1455
- input.setSelectionRange(caret[0], caret[1]);
1456
- this.editShell = this.computeEditShell(next);
1480
+ this.applyDraftEdit(input, next, caret[0], caret[1]);
1481
+ }
1482
+
1483
+ /** Writes a programmatic edit to the open block's textarea. Goes through `editingDraft` so `zn-input` still fires. */
1484
+ private applyDraftEdit(input: HTMLTextAreaElement, value: string, selectionStart: number, selectionEnd: number) {
1485
+ this.editingDraft = value;
1486
+ input.value = value;
1487
+ input.setSelectionRange(selectionStart, selectionEnd);
1488
+ this.editShell = this.computeEditShell(value);
1457
1489
  this.autosize(input);
1458
1490
  input.focus({preventScroll: true});
1459
1491
  this.emit('zn-input');
@@ -1529,7 +1561,7 @@ export default class ZnRemarkdEditor extends ZincElement implements ZincFormCont
1529
1561
  private renderImageControls() {
1530
1562
  const data = this.imageEdit;
1531
1563
  if (!data) return '';
1532
- const aligns: {value: ImageBlockData['align']; icon: string; label: string}[] = [
1564
+ const aligns: { value: ImageBlockData['align']; icon: string; label: string }[] = [
1533
1565
  {value: '', icon: 'align-left@lu', label: 'Align left'},
1534
1566
  {value: 'center', icon: 'align-center@lu', label: 'Align center'},
1535
1567
  {value: 'right', icon: 'align-right@lu', label: 'Align right'},
@@ -1558,7 +1590,8 @@ export default class ZnRemarkdEditor extends ZincElement implements ZincFormCont
1558
1590
  <span>Alignment</span>
1559
1591
  <zn-button-group>
1560
1592
  ${aligns.map(align => html`
1561
- <zn-button type="button" icon-button="small" icon=${align.icon}
1593
+ <zn-button icon-button="small"
1594
+ icon=${align.icon}
1562
1595
  ?plain=${data.align !== align.value}
1563
1596
  tooltip=${align.label}
1564
1597
  @click=${() => this.updateImageEdit({align: align.value})}></zn-button>`)}
@@ -1581,12 +1614,15 @@ export default class ZnRemarkdEditor extends ZincElement implements ZincFormCont
1581
1614
  </label>
1582
1615
  </div>
1583
1616
  <div class="remarkd-editor__image-buttons">
1584
- <zn-button type="button" color="primary" size="small" @click=${this.saveImageEdit}>Save</zn-button>
1585
- <zn-button type="button" color="secondary" size="small" @click=${this.closeImageEdit}>Cancel</zn-button>
1617
+ <zn-button color="primary" size="small" @click=${this.saveImageEdit}>Save</zn-button>
1618
+ <zn-button color="secondary" size="small" @click=${this.closeImageEdit}>Cancel</zn-button>
1586
1619
  <span class="remarkd-editor__image-buttons-spacer"></span>
1587
- <zn-button type="button" icon-button="small" plain icon="code@lu"
1588
- tooltip="Edit source" @click=${this.editImageSource}></zn-button>
1589
- <zn-button type="button" icon-button="small" plain icon="trash-2@lu" color="error"
1620
+ <zn-button icon-button="small"
1621
+ plain
1622
+ icon="code@lu"
1623
+ tooltip="Edit source"
1624
+ @click=${this.editImageSource}></zn-button>
1625
+ <zn-button icon-button="small" plain icon="trash-2@lu" color="error"
1590
1626
  tooltip="Delete image" @click=${this.deleteImageBlock}></zn-button>
1591
1627
  </div>
1592
1628
  </div>`;
@@ -1744,12 +1780,19 @@ export default class ZnRemarkdEditor extends ZincElement implements ZincFormCont
1744
1780
  @pointerdown=${this.handleHandlePointerDown}>
1745
1781
  <zn-icon src="grip-vertical@lu" size="16"></zn-icon>
1746
1782
  </span>
1747
- <zn-button type="button" icon-button="small" plain icon="plus@lu" icon-size="16"
1783
+ <zn-button icon-button="small"
1784
+ plain
1785
+ icon="plus@lu"
1786
+ icon-size="16"
1748
1787
  tooltip="Add block below"
1749
1788
  @click=${() => this.insertDraftBlock(index + 1)}></zn-button>
1750
1789
  </div>
1751
1790
  <zn-button class="remarkd-editor__delete"
1752
- type="button" icon-button="small" plain icon="x@lu" icon-size="16" color="error"
1791
+ icon-button="small"
1792
+ plain
1793
+ icon="x@lu"
1794
+ icon-size="16"
1795
+ color="error"
1753
1796
  tooltip="Delete block"
1754
1797
  @click=${() => this.deleteBlock(index)}></zn-button>`}
1755
1798
  <div part="rendered"
@@ -1808,8 +1851,12 @@ export default class ZnRemarkdEditor extends ZincElement implements ZincFormCont
1808
1851
  /** A single toolbar action button, shared by the toolbar bar and the overflow menu. */
1809
1852
  private renderAction(action: EditorAction) {
1810
1853
  return html`
1811
- <zn-button type="button" icon-button plain icon=${action.icon} icon-size="18"
1812
- tooltip=${action.label}
1854
+ <zn-button icon-button="small"
1855
+ plain
1856
+ icon=${action.icon}
1857
+ icon-size="18"
1858
+ label=${action.label}
1859
+ tooltip=${action.shortcut ? `${action.label} (${shortcutLabel(action.shortcut)})` : action.label}
1813
1860
  ?disabled=${this.isActionDisabled(action)}
1814
1861
  @mousedown=${(e: MouseEvent) => e.preventDefault()}
1815
1862
  @click=${() => this.activateAction(action)}></zn-button>`;
@@ -1823,6 +1870,8 @@ export default class ZnRemarkdEditor extends ZincElement implements ZincFormCont
1823
1870
  @click=${() => this.activateAction(action)}>
1824
1871
  <zn-icon slot="prefix" src=${action.icon} size="18"></zn-icon>
1825
1872
  ${action.label}
1873
+ ${action.shortcut ? html`
1874
+ <span slot="suffix" class="remarkd-editor__shortcut">${shortcutLabel(action.shortcut)}</span>` : ''}
1826
1875
  </zn-menu-item>`;
1827
1876
  }
1828
1877
 
@@ -1869,17 +1918,31 @@ export default class ZnRemarkdEditor extends ZincElement implements ZincFormCont
1869
1918
  // the textarea out from under it.
1870
1919
  if (!this.linkPickerOpen) this.suppressBlurCommit = false;
1871
1920
  }}>
1872
- <zn-button slot="trigger" type="button" icon-button plain icon="ellipsis@lu"
1873
- icon-size="18" tooltip="More"
1921
+ <zn-button slot="trigger"
1922
+ icon-button="small"
1923
+ plain
1924
+ icon="ellipsis@lu"
1925
+ icon-size="18"
1926
+ tooltip="More"
1874
1927
  @mousedown=${(e: MouseEvent) => e.preventDefault()}></zn-button>
1875
1928
  <zn-menu>
1876
- ${overflowed.flatMap(entry => entry.actions).map(action => this.renderMenuAction(action))}
1929
+ ${overflowed.map(entry => html`
1930
+ <zn-menu-item>
1931
+ <zn-icon slot="prefix" src=${entry.actions[0].icon} size="18"></zn-icon>
1932
+ ${entry.group.label}
1933
+ <zn-menu slot="submenu">
1934
+ ${entry.actions.map(action => this.renderMenuAction(action))}
1935
+ </zn-menu>
1936
+ </zn-menu-item>`)}
1877
1937
  </zn-menu>
1878
1938
  </zn-dropdown>` : ''}`;
1879
1939
  })()}
1880
1940
  ${this.allowRaw ? html`
1881
- <zn-button part="raw-toggle" class="remarkd-editor__raw-toggle"
1882
- type="button" icon-button icon="code-xml@lu" icon-size="18"
1941
+ <zn-button part="raw-toggle"
1942
+ class="remarkd-editor__raw-toggle"
1943
+ icon-button="small"
1944
+ icon="code-xml@lu"
1945
+ icon-size="18"
1883
1946
  ?plain=${!this.rawMode}
1884
1947
  tooltip=${this.rawMode ? 'Show blocks' : 'Show raw source'}
1885
1948
  @click=${this.toggleRawMode}></zn-button>` : ''}
@@ -1915,6 +1978,7 @@ export default class ZnRemarkdEditor extends ZincElement implements ZincFormCont
1915
1978
  spellcheck="false"
1916
1979
  .value=${this.value}
1917
1980
  @input=${this.handleRawInput}
1981
+ @keydown=${this.handleRawKeydown}
1918
1982
  @blur=${this.commitRaw}></textarea>`;
1919
1983
  }
1920
1984
 
@@ -1944,12 +2008,17 @@ export default class ZnRemarkdEditor extends ZincElement implements ZincFormCont
1944
2008
  @input=${(e: Event) => {
1945
2009
  this.includeQuery = (e.target as HTMLInputElement).value;
1946
2010
  }}>
1947
- <zn-button type="button" icon-button="small" plain icon="x@lu"
1948
- tooltip="Cancel" @click=${this.closeIncludePicker}></zn-button>
2011
+ <zn-button icon-button="small"
2012
+ plain
2013
+ icon="x@lu"
2014
+ tooltip="Cancel"
2015
+ @click=${this.closeIncludePicker}></zn-button>
1949
2016
  </div>
1950
2017
  ${this.includeOptions === null
1951
- ? html`<div class="remarkd-editor__include-picker-empty">${
1952
- this.includeLoadFailed ? 'Could not load Includes' : 'Loading…'}</div>`
2018
+ ? html`
2019
+ <div class="remarkd-editor__include-picker-empty">${
2020
+ this.includeLoadFailed ? 'Could not load Includes' : 'Loading…'}
2021
+ </div>`
1953
2022
  : items.length
1954
2023
  ? items.map(item => html`
1955
2024
  <button type="button" class="remarkd-editor__include-option"
@@ -1959,7 +2028,8 @@ export default class ZnRemarkdEditor extends ZincElement implements ZincFormCont
1959
2028
  <span class="remarkd-editor__include-option-meta">${item.scope ?? ''}${
1960
2029
  item.languages ? ` · ${item.languages}` : ' · No content'}</span>
1961
2030
  </button>`)
1962
- : html`<div class="remarkd-editor__include-picker-empty">No Includes found</div>`}
2031
+ : html`
2032
+ <div class="remarkd-editor__include-picker-empty">No Includes found</div>`}
1963
2033
  </div>`;
1964
2034
  }
1965
2035
 
@@ -1975,12 +2045,17 @@ export default class ZnRemarkdEditor extends ZincElement implements ZincFormCont
1975
2045
  this.linkQuery = (e.target as HTMLInputElement).value;
1976
2046
  this.searchLinks(this.linkQuery);
1977
2047
  }}>
1978
- <zn-button type="button" icon-button="small" plain icon="x@lu"
1979
- tooltip="Cancel" @click=${this.closeLinkPicker}></zn-button>
2048
+ <zn-button icon-button="small"
2049
+ plain
2050
+ icon="x@lu"
2051
+ tooltip="Cancel"
2052
+ @click=${this.closeLinkPicker}></zn-button>
1980
2053
  </div>
1981
2054
  ${this.linkResults === null
1982
- ? html`<div class="remarkd-editor__link-picker-empty">${
1983
- this.linkSearchFailed ? 'Could not search for articles' : 'Searching…'}</div>`
2055
+ ? html`
2056
+ <div class="remarkd-editor__link-picker-empty">${
2057
+ this.linkSearchFailed ? 'Could not search for articles' : 'Searching…'}
2058
+ </div>`
1984
2059
  : this.linkResults.length
1985
2060
  ? this.linkResults.map(item => html`
1986
2061
  <button type="button" class="remarkd-editor__link-option"
@@ -1990,7 +2065,8 @@ export default class ZnRemarkdEditor extends ZincElement implements ZincFormCont
1990
2065
  [kinds[item.kind] ?? item.kind, item.context, item.status === 'published' ? '' : item.status]
1991
2066
  .filter(Boolean).join(' · ')}</span>
1992
2067
  </button>`)
1993
- : html`<div class="remarkd-editor__link-picker-empty">No articles found</div>`}
2068
+ : html`
2069
+ <div class="remarkd-editor__link-picker-empty">No articles found</div>`}
1994
2070
  </div>`;
1995
2071
  }
1996
2072
 
@@ -2024,7 +2100,9 @@ export default class ZnRemarkdEditor extends ZincElement implements ZincFormCont
2024
2100
  accept="image/*"
2025
2101
  droparea
2026
2102
  @zn-change=${this.handleImagePicked}></zn-file>
2027
- <zn-button type="button" icon-button="small" plain icon="x@lu"
2103
+ <zn-button icon-button="small"
2104
+ plain
2105
+ icon="x@lu"
2028
2106
  tooltip="Cancel"
2029
2107
  @click=${this.closeImagePicker}></zn-button>
2030
2108
  </div>`;
@@ -534,3 +534,8 @@
534
534
  text-decoration-style: wavy;
535
535
  text-decoration-color: rgb(var(--zn-color-error));
536
536
  }
537
+
538
+ .remarkd-editor__shortcut {
539
+ color: rgb(var(--zn-text-muted));
540
+ font-size: var(--zn-font-size-x-small);
541
+ }