markupeditor 0.9.35 → 0.9.37

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.
@@ -3,5 +3,6 @@
3
3
  "selectImage": false,
4
4
  "insertLink": false,
5
5
  "insertImage": false,
6
- "highlightCode": true
6
+ "highlightCode": true,
7
+ "markdownShorthand": true
7
8
  }
@@ -35476,6 +35476,96 @@ function testPasteTextPreprocessing(html) {
35476
35476
  function testIsRecognizedLanguage(name) {
35477
35477
  return isRecognizedLanguage(name);
35478
35478
  }
35479
+ /**
35480
+ * For testing purposes, simulate typing `text` one character at a time
35481
+ * through the `handleTextInput` prop, so input rules (setup/inputrules.js)
35482
+ * fire as they do for live typing. Does not fire the DOM `input` event, so
35483
+ * `callbackInput` (markupeditor.js) is not exercised. Skips `\r`/`\n`
35484
+ * characters rather than inserting them, matching real typing, which never
35485
+ * routes a newline through `handleTextInput`.
35486
+ *
35487
+ * @param {string} text The text to type, one Unicode code point at a time.
35488
+ */
35489
+ function testTypeText(text) {
35490
+ const view = activeView();
35491
+ for (const ch of text) {
35492
+ if (/[\r\n]/.test(ch)) continue
35493
+ const { from, to } = view.state.selection;
35494
+ const deflt = () => view.state.tr.insertText(ch, from, to);
35495
+ const handled = view.someProp('handleTextInput', f => f(view, from, to, ch, deflt));
35496
+ if (!handled) {
35497
+ view.dispatch(deflt());
35498
+ }
35499
+ }
35500
+ }
35501
+ // Modifier segments testTypeKey recognizes when parsing a keymap-style key
35502
+ // name. "Mod" (prosemirror-keymap's platform-dependent Cmd/Ctrl alias) is
35503
+ // deliberately excluded: it has no single fixed KeyboardEvent flag, and the
35504
+ // one scenario that needed testTypeKey (Backspace, no modifiers) never
35505
+ // required it.
35506
+ const _testTypeKeyModifiers = new Set(['Shift', 'Ctrl', 'Control', 'Alt', 'Cmd', 'Meta']);
35507
+
35508
+ // Reverse lookup from KeyboardEvent.key name to legacy keyCode, built from
35509
+ // w3c-keyname's public `base` table (keyCode -> name). Where more than one
35510
+ // keyCode maps to the same name (e.g. 10 and 13 both name "Enter"), the
35511
+ // higher/later entry wins, matching prosemirror-view's hardcoded
35512
+ // convention (its internal key-event helper in src/dom.ts pairs "Enter"
35513
+ // with 13 and "Backspace" with 8 at its call sites).
35514
+ const _testKeyCodeByName = Object.fromEntries(
35515
+ Object.entries(base).map(([code, name]) => [name, Number(code)])
35516
+ );
35517
+
35518
+ function _testKeyCodeFor(key) {
35519
+ return _testKeyCodeByName[key] ?? 0
35520
+ }
35521
+
35522
+ /**
35523
+ * For testing purposes, simulate a single keydown through the `handleKeyDown`
35524
+ * prop, so keymap bindings (setup/keymap.js) fire as they do for a live
35525
+ * keypress. Exercises only handleKeyDown-routed (keymap-bound) behavior: for
35526
+ * anything the browser would otherwise handle natively instead of a
35527
+ * registered binding claiming it (e.g. Backspace when there's no input-rule
35528
+ * state to undo, IME composition), this is a silent no-op — it returns
35529
+ * `false` and does nothing, unlike a real keypress.
35530
+ *
35531
+ * @param {string} key A keymap-style key name (e.g., "Backspace",
35532
+ * "Shift-Enter"): zero or more modifier prefixes
35533
+ * (Shift-, Ctrl-/Control-, Alt-, Cmd-/Meta-)
35534
+ * followed by a KeyboardEvent.key value.
35535
+ * @throws {Error} If `key` contains an unrecognized modifier
35536
+ * prefix, including the unsupported "Mod-".
35537
+ * @returns {boolean} Whether a handler returned `true` for the key.
35538
+ * NOT a reliable "the document changed" signal:
35539
+ * some commands (e.g. setStyleCommand,
35540
+ * toggleFormatCommand) dispatch without returning
35541
+ * `true`, so the document can still mutate while
35542
+ * this returns `false`.
35543
+ */
35544
+ function testTypeKey(key) {
35545
+ const view = activeView();
35546
+ // Split like prosemirror-keymap's normalizeKeyName does, so a
35547
+ // trailing "-" (or a bare "-") is recognized as the literal key rather
35548
+ // than an empty string, e.g. "Ctrl--" -> ["Ctrl", "-"].
35549
+ const parts = key.split(/-(?!$)/);
35550
+ const eventKey = parts.pop();
35551
+ const modifiers = new Set(parts);
35552
+ for (const modifier of modifiers) {
35553
+ if (!_testTypeKeyModifiers.has(modifier)) {
35554
+ throw new Error(`testTypeKey: unrecognized modifier "${modifier}" in "${key}"`)
35555
+ }
35556
+ }
35557
+ const event = new KeyboardEvent('keydown', {
35558
+ key: eventKey,
35559
+ keyCode: _testKeyCodeFor(eventKey),
35560
+ shiftKey: modifiers.has('Shift'),
35561
+ ctrlKey: modifiers.has('Ctrl') || modifiers.has('Control'),
35562
+ altKey: modifiers.has('Alt'),
35563
+ metaKey: modifiers.has('Cmd') || modifiers.has('Meta'),
35564
+ bubbles: true,
35565
+ cancelable: true,
35566
+ });
35567
+ return view.someProp('handleKeyDown', f => f(view, event)) ?? false
35568
+ }
35479
35569
  /**
35480
35570
  * For testing purposes, invoke presentCodeLanguages on the active document.
35481
35571
  */
@@ -35533,6 +35623,19 @@ function insertLinkCommand(url) {
35533
35623
  return commandAdapter;
35534
35624
  }
35535
35625
 
35626
+ /**
35627
+ * Insert a link to the header identified by hTag ('H1'-'H6') and index (its position among
35628
+ * headers with that tag), assigning the header a unique id if it doesn't already have one.
35629
+ *
35630
+ * @param {string} hTag One of the strings `H1`-`H6`
35631
+ * @param {number} index Within existing elements with tag `hTag`, this is the index into them that is identified
35632
+ */
35633
+ function insertInternalLink(hTag, index) {
35634
+ const view = activeView();
35635
+ let command = insertInternalLinkCommand(hTag, index);
35636
+ let result = command(view.state, view.dispatch, view);
35637
+ return result
35638
+ }
35536
35639
  function insertInternalLinkCommand(hTag, index) {
35537
35640
  const commandAdapter = (state, dispatch, view) => {
35538
35641
  // Find the node matching hTag that is index into the nodes matching hTag
@@ -35647,12 +35750,34 @@ function headerMatching(hTag, index, state) {
35647
35750
  }
35648
35751
  }
35649
35752
 
35753
+ /**
35754
+ * Return a JSON-stringified array describing every header in the document, in document order
35755
+ * within each level. Each entry is `{hTag, index, text}`, where `hTag` and `index` identify the
35756
+ * header the same way `insertInternalLink(hTag, index)` and `idForInternalLinkCommand(hTag, index)` do.
35757
+ *
35758
+ * @returns {string} JSON-stringified array of `{hTag, index, text}`
35759
+ */
35760
+ function getHeaders() {
35761
+ const view = activeView();
35762
+ const headersByLevel = headers(view.state);
35763
+ let result = [];
35764
+ for (let level = 1; level < 7; level++) {
35765
+ let hNodes = headersByLevel[level];
35766
+ if (hNodes) {
35767
+ hNodes.forEach((h, index) => {
35768
+ result.push({hTag: 'H' + level.toString(), index: index, text: h.node.textContent});
35769
+ });
35770
+ }
35771
+ }
35772
+ return JSON.stringify(result)
35773
+ }
35774
+
35650
35775
  /**
35651
35776
  * Return all the headers that exist in `state.doc` as arrays keyed by level.
35652
- *
35777
+ *
35653
35778
  * @ignore
35654
35779
  * @param {EditorState} state The state of the ProseMirror editor
35655
- */
35780
+ */
35656
35781
  function headers(state) {
35657
35782
  let headers = {};
35658
35783
  let hType = state.schema.nodes.heading;
@@ -37009,12 +37134,14 @@ var selectImage = false;
37009
37134
  var insertLink = false;
37010
37135
  var insertImage = false;
37011
37136
  var highlightCode = true;
37137
+ var markdownShorthand = true;
37012
37138
  var behaviorConfig = {
37013
37139
  focusAfterLoad: focusAfterLoad,
37014
37140
  selectImage: selectImage,
37015
37141
  insertLink: insertLink,
37016
37142
  insertImage: insertImage,
37017
- highlightCode: highlightCode
37143
+ highlightCode: highlightCode,
37144
+ markdownShorthand: markdownShorthand
37018
37145
  };
37019
37146
 
37020
37147
  /**
@@ -37053,7 +37180,9 @@ var behaviorConfig = {
37053
37180
  * "selectImage": false, // Whether to show a "Select..." button in the Insert Image dialog
37054
37181
  * "insertLink": false, // Whether to defer to the MarkupDelegate rather than use the default LinkDialog
37055
37182
  * "insertImage": false, // Whether to defer to the MarkupDelagate rather than use the default ImageDialog
37056
- * "highlightCode": true // Whether to highlight code blocks and support language identificaton in UI
37183
+ * "highlightCode": true, // Whether to highlight code blocks and support language identificaton in UI
37184
+ * "markdownShorthand": true // Whether typing markdown shorthand converts to formatting: block-level
37185
+ * // (`> `, list markers, ``` ``` ```, `#`) and inline (**, *, _, `, ~~)
37057
37186
  * }
37058
37187
  * ```
37059
37188
  */
@@ -40886,16 +41015,122 @@ function headingRule(nodeType, maxLevel) {
40886
41015
  nodeType, match => ({level: match[1].length}))
40887
41016
  }
40888
41017
 
40889
- // : (Schema) → Plugin
41018
+ // : (RegExp, MarkType, number, number) → InputRule
41019
+ // Given a regexp matching a markdown emphasis pattern ending at the
41020
+ // cursor, a mark type, and the lengths of the opening and closing
41021
+ // delimiters, returns an input rule that deletes only the
41022
+ // delimiters -- never the content between them -- and applies the
41023
+ // mark over the untouched range. This preserves any marks (links,
41024
+ // nested emphasis, etc.) already present in the converted range
41025
+ // instead of flattening it to plain text.
41026
+ //
41027
+ // prosemirror-inputrules invokes this handler from handleTextInput,
41028
+ // before the browser's pending keystroke has actually been applied
41029
+ // to the document -- match[0] (built from existing text plus the
41030
+ // not-yet-inserted text) can therefore be longer than end - start.
41031
+ // That trailing slice is inserted first so the full matched range
41032
+ // genuinely exists in the document before the delimiter surgery
41033
+ // below operates on it. Suppressed when the cursor already sits
41034
+ // inside a code mark, since markdown shorthand characters are meant
41035
+ // to stay literal there. Clears the mark from stored marks afterward
41036
+ // so text typed immediately after the conversion does not inherit
41037
+ // it.
41038
+ //
41039
+ // The leading delimiter is deleted before the trailing one, so the
41040
+ // transaction's last content-shifting step is the trailing-delimiter
41041
+ // delete, ending at the cursor -- prosemirror-history's grouping
41042
+ // heuristic only looks at that step's range when deciding whether
41043
+ // the next keystroke belongs to the same undo group (addMark's own
41044
+ // step contributes no range there), so ending at the cursor is what
41045
+ // keeps immediately-following typing (e.g. "**foo**bar") merged with
41046
+ // the conversion into a single undo.
41047
+ function markInputRule(regexp, markType, openLen, closeLen) {
41048
+ return new InputRule(regexp, (state, match, start, end) => {
41049
+ const codeType = state.schema.marks.code;
41050
+ if (codeType && state.doc.resolve(start).marks().some(mark => mark.type === codeType)) return null
41051
+ const tr = state.tr;
41052
+ const fullEnd = start + match[0].length;
41053
+ if (fullEnd > end) tr.insertText(match[0].slice(end - start), end);
41054
+ tr.delete(start, start + openLen);
41055
+ const closeStart = fullEnd - openLen - closeLen;
41056
+ tr.delete(closeStart, closeStart + closeLen);
41057
+ tr.addMark(start, closeStart, markType.create());
41058
+ tr.removeStoredMark(markType);
41059
+ return tr
41060
+ })
41061
+ }
41062
+
41063
+ // : (MarkType) → InputRule
41064
+ // Given the strong mark type, returns an input rule that converts
41065
+ // `**text**` at the cursor into bold.
41066
+ function strongRule(markType) {
41067
+ return markInputRule(/\*\*([^*\s](?:[^*]*[^*\s])?)\*\*$/, markType, 2, 2)
41068
+ }
41069
+
41070
+ // : (MarkType) → InputRule
41071
+ // Given the em mark type, returns an input rule that converts
41072
+ // `*text*` at the cursor into italic. The negative lookbehind on the
41073
+ // opening `*` defers matching until a preceding, still-unconsumed
41074
+ // `*` is no longer in the way, so this rule does not fire on a
41075
+ // partially typed `**text**` before bold's closing marker
41076
+ // completes.
41077
+ function emStarRule(markType) {
41078
+ return markInputRule(/(?<!\*)\*([^*\s](?:[^*]*[^*\s])?)\*$/, markType, 1, 1)
41079
+ }
41080
+
41081
+ // : (MarkType) → InputRule
41082
+ // Given the em mark type, returns an input rule that converts
41083
+ // `_text_` at the cursor into italic. The negative lookbehind on the
41084
+ // opening `_` blocks intraword underscores (`\w` includes `_`), per
41085
+ // CommonMark's flanking rule for `_` emphasis -- unlike `*`, `_`
41086
+ // emphasis must not fire inside a word, so identifiers like
41087
+ // `snake_case_name` stay literal.
41088
+ function emUnderscoreRule(markType) {
41089
+ return markInputRule(/(?<!\w)_([^_\s](?:[^_]*[^_\s])?)_$/, markType, 1, 1)
41090
+ }
41091
+
41092
+ // : (MarkType) → InputRule
41093
+ // Given the code mark type, returns an input rule that converts a
41094
+ // backtick-delimited `text` at the cursor into inline code.
41095
+ function codeRule(markType) {
41096
+ return markInputRule(/(?<!`)`([^`\s](?:[^`]*[^`\s])?)`$/, markType, 1, 1)
41097
+ }
41098
+
41099
+ // : (MarkType) → InputRule
41100
+ // Given the s (strikethrough) mark type, returns an input rule that
41101
+ // converts `~~text~~` at the cursor into strikethrough. No
41102
+ // self-referential lookbehind, matching strongRule -- both are
41103
+ // double-delimiter patterns with no single-character sibling rule to
41104
+ // defer against, unlike emStarRule's genuine need to defer to bold.
41105
+ function strikethroughRule(markType) {
41106
+ return markInputRule(/~~([^~\s](?:[^~]*[^~\s])?)~~$/, markType, 2, 2)
41107
+ }
41108
+
41109
+ // : (Schema, object) → Plugin
40890
41110
  // A set of input rules for creating the basic block quotes, lists,
40891
- // code blocks, and heading.
40892
- function buildInputRules(schema) {
41111
+ // code blocks, heading, and markdown formatting shorthand (bold,
41112
+ // italic, inline code, strikethrough). All ten rules are gated
41113
+ // together by config.behavior.markdownShorthand. config defaults to
41114
+ // {}, so markdownShorthand defaults to enabled (only an explicit
41115
+ // `false` turns it off) when buildInputRules is called without a
41116
+ // config, matching BehaviorConfig's default.
41117
+ function buildInputRules(schema, config = {}) {
40893
41118
  let rules = [], type;
40894
- if (type = schema.nodes.blockquote) rules.push(blockQuoteRule(type));
40895
- if (type = schema.nodes.ordered_list) rules.push(orderedListRule(type));
40896
- if (type = schema.nodes.bullet_list) rules.push(bulletListRule(type));
40897
- if (type = schema.nodes.code_block) rules.push(codeBlockRule(type));
40898
- if (type = schema.nodes.heading) rules.push(headingRule(type, 6));
41119
+ const markdownShorthand = config.behavior?.markdownShorthand !== false;
41120
+ if (markdownShorthand) {
41121
+ if (type = schema.nodes.blockquote) rules.push(blockQuoteRule(type));
41122
+ if (type = schema.nodes.ordered_list) rules.push(orderedListRule(type));
41123
+ if (type = schema.nodes.bullet_list) rules.push(bulletListRule(type));
41124
+ if (type = schema.nodes.code_block) rules.push(codeBlockRule(type));
41125
+ if (type = schema.nodes.heading) rules.push(headingRule(type, 6));
41126
+ if (type = schema.marks.strong) rules.push(strongRule(type));
41127
+ if (type = schema.marks.em) {
41128
+ rules.push(emStarRule(type));
41129
+ rules.push(emUnderscoreRule(type));
41130
+ }
41131
+ if (type = schema.marks.code) rules.push(codeRule(type));
41132
+ if (type = schema.marks.s) rules.push(strikethroughRule(type));
41133
+ }
40899
41134
  return inputRules({rules})
40900
41135
  }
40901
41136
 
@@ -41176,7 +41411,7 @@ function openImageDialog() {
41176
41411
  function markupSetup(config, schema) {
41177
41412
  setPrefix('Markup');
41178
41413
  let plugins = [
41179
- buildInputRules(schema),
41414
+ buildInputRules(schema, config),
41180
41415
  keymap(buildKeymap(config, schema)),
41181
41416
  keymap(baseKeymap),
41182
41417
  dropCursor(),
@@ -42549,6 +42784,7 @@ const MU = {
42549
42784
  getDataImages,
42550
42785
  getLocalImages,
42551
42786
  getHTML,
42787
+ getHeaders,
42552
42788
  getHeight,
42553
42789
  getImageAttributes,
42554
42790
  getLinkAttributes,
@@ -42556,6 +42792,7 @@ const MU = {
42556
42792
  getTestHTML,
42557
42793
  indent: indent$1,
42558
42794
  insertImage: insertImage$1,
42795
+ insertInternalLink,
42559
42796
  insertLink: insertLink$1,
42560
42797
  insertTable,
42561
42798
  insertHRule,
@@ -42590,6 +42827,8 @@ const MU = {
42590
42827
  testPasteHTMLPreprocessing,
42591
42828
  testPasteTextPreprocessing,
42592
42829
  testPresentCodeLanguages,
42830
+ testTypeKey,
42831
+ testTypeText,
42593
42832
  toggleBold,
42594
42833
  toggleCode,
42595
42834
  toggleItalic,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "markupeditor",
3
- "version": "0.9.35",
3
+ "version": "0.9.37",
4
4
  "description": "A web component and API for WYSIWYG HTML editing.",
5
5
  "keywords": [
6
6
  "wysiwyg",