markupeditor 0.9.34 → 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
  }
@@ -35146,6 +35146,28 @@ function callbackSelectImage() {
35146
35146
  _callback(JSON.stringify(messageDict));
35147
35147
  }
35148
35148
 
35149
+ /**
35150
+ * Callback to signal that the user wants to insert a link.
35151
+ *
35152
+ * The messageHandler will need to bring up a dialog to identify
35153
+ * the link and then execute insertLink.
35154
+ */
35155
+ function callbackInsertLink() {
35156
+ let messageDict = { 'messageType' : 'insertLink' };
35157
+ _callback(JSON.stringify(messageDict));
35158
+ }
35159
+
35160
+ /**
35161
+ * Callback to signal that the user wants to insert an image.
35162
+ *
35163
+ * The messageHandler will need to bring up a dialog to identify
35164
+ * the image and then execute insertImage.
35165
+ */
35166
+ function callbackInsertImage() {
35167
+ let messageDict = { 'messageType' : 'insertImage' };
35168
+ _callback(JSON.stringify(messageDict));
35169
+ }
35170
+
35149
35171
  /**
35150
35172
  * Callback to signal that user-provided CSS and/or script files have
35151
35173
  * been loaded.
@@ -35454,6 +35476,96 @@ function testPasteTextPreprocessing(html) {
35454
35476
  function testIsRecognizedLanguage(name) {
35455
35477
  return isRecognizedLanguage(name);
35456
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
+ }
35457
35569
  /**
35458
35570
  * For testing purposes, invoke presentCodeLanguages on the active document.
35459
35571
  */
@@ -36987,12 +37099,14 @@ var selectImage = false;
36987
37099
  var insertLink = false;
36988
37100
  var insertImage = false;
36989
37101
  var highlightCode = true;
37102
+ var markdownShorthand = true;
36990
37103
  var behaviorConfig = {
36991
37104
  focusAfterLoad: focusAfterLoad,
36992
37105
  selectImage: selectImage,
36993
37106
  insertLink: insertLink,
36994
37107
  insertImage: insertImage,
36995
- highlightCode: highlightCode
37108
+ highlightCode: highlightCode,
37109
+ markdownShorthand: markdownShorthand
36996
37110
  };
36997
37111
 
36998
37112
  /**
@@ -37031,7 +37145,9 @@ var behaviorConfig = {
37031
37145
  * "selectImage": false, // Whether to show a "Select..." button in the Insert Image dialog
37032
37146
  * "insertLink": false, // Whether to defer to the MarkupDelegate rather than use the default LinkDialog
37033
37147
  * "insertImage": false, // Whether to defer to the MarkupDelagate rather than use the default ImageDialog
37034
- * "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 (**, *, _, `, ~~)
37035
37151
  * }
37036
37152
  * ```
37037
37153
  */
@@ -40864,16 +40980,122 @@ function headingRule(nodeType, maxLevel) {
40864
40980
  nodeType, match => ({level: match[1].length}))
40865
40981
  }
40866
40982
 
40867
- // : (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
40868
41075
  // A set of input rules for creating the basic block quotes, lists,
40869
- // code blocks, and heading.
40870
- 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 = {}) {
40871
41083
  let rules = [], type;
40872
- if (type = schema.nodes.blockquote) rules.push(blockQuoteRule(type));
40873
- if (type = schema.nodes.ordered_list) rules.push(orderedListRule(type));
40874
- if (type = schema.nodes.bullet_list) rules.push(bulletListRule(type));
40875
- if (type = schema.nodes.code_block) rules.push(codeBlockRule(type));
40876
- 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
+ }
40877
41099
  return inputRules({rules})
40878
41100
  }
40879
41101
 
@@ -41154,7 +41376,7 @@ function openImageDialog() {
41154
41376
  function markupSetup(config, schema) {
41155
41377
  setPrefix('Markup');
41156
41378
  let plugins = [
41157
- buildInputRules(schema),
41379
+ buildInputRules(schema, config),
41158
41380
  keymap(buildKeymap(config, schema)),
41159
41381
  keymap(baseKeymap),
41160
41382
  dropCursor(),
@@ -42506,6 +42728,8 @@ const MU = {
42506
42728
  addHeader,
42507
42729
  addRow,
42508
42730
  borderTable,
42731
+ callbackInsertImage,
42732
+ callbackInsertLink,
42509
42733
  callbackSelectImage,
42510
42734
  cancelSearch,
42511
42735
  canUndo,
@@ -42566,6 +42790,8 @@ const MU = {
42566
42790
  testPasteHTMLPreprocessing,
42567
42791
  testPasteTextPreprocessing,
42568
42792
  testPresentCodeLanguages,
42793
+ testTypeKey,
42794
+ testTypeText,
42569
42795
  toggleBold,
42570
42796
  toggleCode,
42571
42797
  toggleItalic,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "markupeditor",
3
- "version": "0.9.34",
3
+ "version": "0.9.36",
4
4
  "description": "A web component and API for WYSIWYG HTML editing.",
5
5
  "keywords": [
6
6
  "wysiwyg",