markupeditor 0.9.35 → 0.9.36

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
  */
@@ -37009,12 +37099,14 @@ var selectImage = false;
37009
37099
  var insertLink = false;
37010
37100
  var insertImage = false;
37011
37101
  var highlightCode = true;
37102
+ var markdownShorthand = true;
37012
37103
  var behaviorConfig = {
37013
37104
  focusAfterLoad: focusAfterLoad,
37014
37105
  selectImage: selectImage,
37015
37106
  insertLink: insertLink,
37016
37107
  insertImage: insertImage,
37017
- highlightCode: highlightCode
37108
+ highlightCode: highlightCode,
37109
+ markdownShorthand: markdownShorthand
37018
37110
  };
37019
37111
 
37020
37112
  /**
@@ -37053,7 +37145,9 @@ var behaviorConfig = {
37053
37145
  * "selectImage": false, // Whether to show a "Select..." button in the Insert Image dialog
37054
37146
  * "insertLink": false, // Whether to defer to the MarkupDelegate rather than use the default LinkDialog
37055
37147
  * "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
37148
+ * "highlightCode": true, // Whether to highlight code blocks and support language identificaton in UI
37149
+ * "markdownShorthand": true // Whether typing markdown shorthand converts to formatting: block-level
37150
+ * // (`> `, list markers, ``` ``` ```, `#`) and inline (**, *, _, `, ~~)
37057
37151
  * }
37058
37152
  * ```
37059
37153
  */
@@ -40886,16 +40980,122 @@ function headingRule(nodeType, maxLevel) {
40886
40980
  nodeType, match => ({level: match[1].length}))
40887
40981
  }
40888
40982
 
40889
- // : (Schema) → Plugin
40983
+ // : (RegExp, MarkType, number, number) → InputRule
40984
+ // Given a regexp matching a markdown emphasis pattern ending at the
40985
+ // cursor, a mark type, and the lengths of the opening and closing
40986
+ // delimiters, returns an input rule that deletes only the
40987
+ // delimiters -- never the content between them -- and applies the
40988
+ // mark over the untouched range. This preserves any marks (links,
40989
+ // nested emphasis, etc.) already present in the converted range
40990
+ // instead of flattening it to plain text.
40991
+ //
40992
+ // prosemirror-inputrules invokes this handler from handleTextInput,
40993
+ // before the browser's pending keystroke has actually been applied
40994
+ // to the document -- match[0] (built from existing text plus the
40995
+ // not-yet-inserted text) can therefore be longer than end - start.
40996
+ // That trailing slice is inserted first so the full matched range
40997
+ // genuinely exists in the document before the delimiter surgery
40998
+ // below operates on it. Suppressed when the cursor already sits
40999
+ // inside a code mark, since markdown shorthand characters are meant
41000
+ // to stay literal there. Clears the mark from stored marks afterward
41001
+ // so text typed immediately after the conversion does not inherit
41002
+ // it.
41003
+ //
41004
+ // The leading delimiter is deleted before the trailing one, so the
41005
+ // transaction's last content-shifting step is the trailing-delimiter
41006
+ // delete, ending at the cursor -- prosemirror-history's grouping
41007
+ // heuristic only looks at that step's range when deciding whether
41008
+ // the next keystroke belongs to the same undo group (addMark's own
41009
+ // step contributes no range there), so ending at the cursor is what
41010
+ // keeps immediately-following typing (e.g. "**foo**bar") merged with
41011
+ // the conversion into a single undo.
41012
+ function markInputRule(regexp, markType, openLen, closeLen) {
41013
+ return new InputRule(regexp, (state, match, start, end) => {
41014
+ const codeType = state.schema.marks.code;
41015
+ if (codeType && state.doc.resolve(start).marks().some(mark => mark.type === codeType)) return null
41016
+ const tr = state.tr;
41017
+ const fullEnd = start + match[0].length;
41018
+ if (fullEnd > end) tr.insertText(match[0].slice(end - start), end);
41019
+ tr.delete(start, start + openLen);
41020
+ const closeStart = fullEnd - openLen - closeLen;
41021
+ tr.delete(closeStart, closeStart + closeLen);
41022
+ tr.addMark(start, closeStart, markType.create());
41023
+ tr.removeStoredMark(markType);
41024
+ return tr
41025
+ })
41026
+ }
41027
+
41028
+ // : (MarkType) → InputRule
41029
+ // Given the strong mark type, returns an input rule that converts
41030
+ // `**text**` at the cursor into bold.
41031
+ function strongRule(markType) {
41032
+ return markInputRule(/\*\*([^*\s](?:[^*]*[^*\s])?)\*\*$/, markType, 2, 2)
41033
+ }
41034
+
41035
+ // : (MarkType) → InputRule
41036
+ // Given the em mark type, returns an input rule that converts
41037
+ // `*text*` at the cursor into italic. The negative lookbehind on the
41038
+ // opening `*` defers matching until a preceding, still-unconsumed
41039
+ // `*` is no longer in the way, so this rule does not fire on a
41040
+ // partially typed `**text**` before bold's closing marker
41041
+ // completes.
41042
+ function emStarRule(markType) {
41043
+ return markInputRule(/(?<!\*)\*([^*\s](?:[^*]*[^*\s])?)\*$/, markType, 1, 1)
41044
+ }
41045
+
41046
+ // : (MarkType) → InputRule
41047
+ // Given the em mark type, returns an input rule that converts
41048
+ // `_text_` at the cursor into italic. The negative lookbehind on the
41049
+ // opening `_` blocks intraword underscores (`\w` includes `_`), per
41050
+ // CommonMark's flanking rule for `_` emphasis -- unlike `*`, `_`
41051
+ // emphasis must not fire inside a word, so identifiers like
41052
+ // `snake_case_name` stay literal.
41053
+ function emUnderscoreRule(markType) {
41054
+ return markInputRule(/(?<!\w)_([^_\s](?:[^_]*[^_\s])?)_$/, markType, 1, 1)
41055
+ }
41056
+
41057
+ // : (MarkType) → InputRule
41058
+ // Given the code mark type, returns an input rule that converts a
41059
+ // backtick-delimited `text` at the cursor into inline code.
41060
+ function codeRule(markType) {
41061
+ return markInputRule(/(?<!`)`([^`\s](?:[^`]*[^`\s])?)`$/, markType, 1, 1)
41062
+ }
41063
+
41064
+ // : (MarkType) → InputRule
41065
+ // Given the s (strikethrough) mark type, returns an input rule that
41066
+ // converts `~~text~~` at the cursor into strikethrough. No
41067
+ // self-referential lookbehind, matching strongRule -- both are
41068
+ // double-delimiter patterns with no single-character sibling rule to
41069
+ // defer against, unlike emStarRule's genuine need to defer to bold.
41070
+ function strikethroughRule(markType) {
41071
+ return markInputRule(/~~([^~\s](?:[^~]*[^~\s])?)~~$/, markType, 2, 2)
41072
+ }
41073
+
41074
+ // : (Schema, object) → Plugin
40890
41075
  // A set of input rules for creating the basic block quotes, lists,
40891
- // code blocks, and heading.
40892
- function buildInputRules(schema) {
41076
+ // code blocks, heading, and markdown formatting shorthand (bold,
41077
+ // italic, inline code, strikethrough). All ten rules are gated
41078
+ // together by config.behavior.markdownShorthand. config defaults to
41079
+ // {}, so markdownShorthand defaults to enabled (only an explicit
41080
+ // `false` turns it off) when buildInputRules is called without a
41081
+ // config, matching BehaviorConfig's default.
41082
+ function buildInputRules(schema, config = {}) {
40893
41083
  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));
41084
+ const markdownShorthand = config.behavior?.markdownShorthand !== false;
41085
+ if (markdownShorthand) {
41086
+ if (type = schema.nodes.blockquote) rules.push(blockQuoteRule(type));
41087
+ if (type = schema.nodes.ordered_list) rules.push(orderedListRule(type));
41088
+ if (type = schema.nodes.bullet_list) rules.push(bulletListRule(type));
41089
+ if (type = schema.nodes.code_block) rules.push(codeBlockRule(type));
41090
+ if (type = schema.nodes.heading) rules.push(headingRule(type, 6));
41091
+ if (type = schema.marks.strong) rules.push(strongRule(type));
41092
+ if (type = schema.marks.em) {
41093
+ rules.push(emStarRule(type));
41094
+ rules.push(emUnderscoreRule(type));
41095
+ }
41096
+ if (type = schema.marks.code) rules.push(codeRule(type));
41097
+ if (type = schema.marks.s) rules.push(strikethroughRule(type));
41098
+ }
40899
41099
  return inputRules({rules})
40900
41100
  }
40901
41101
 
@@ -41176,7 +41376,7 @@ function openImageDialog() {
41176
41376
  function markupSetup(config, schema) {
41177
41377
  setPrefix('Markup');
41178
41378
  let plugins = [
41179
- buildInputRules(schema),
41379
+ buildInputRules(schema, config),
41180
41380
  keymap(buildKeymap(config, schema)),
41181
41381
  keymap(baseKeymap),
41182
41382
  dropCursor(),
@@ -42590,6 +42790,8 @@ const MU = {
42590
42790
  testPasteHTMLPreprocessing,
42591
42791
  testPasteTextPreprocessing,
42592
42792
  testPresentCodeLanguages,
42793
+ testTypeKey,
42794
+ testTypeText,
42593
42795
  toggleBold,
42594
42796
  toggleCode,
42595
42797
  toggleItalic,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "markupeditor",
3
- "version": "0.9.35",
3
+ "version": "0.9.36",
4
4
  "description": "A web component and API for WYSIWYG HTML editing.",
5
5
  "keywords": [
6
6
  "wysiwyg",