@mulmoclaude/markdown-utils 2.0.0 → 2.2.0

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.
@@ -32,8 +32,34 @@
32
32
  // and must not be a backslash.
33
33
  // 4. The character AFTER the closing `$` must not be an ASCII digit
34
34
  // (`$5-$10`).
35
- // 5. The body must be non-empty, single-line, and must not consist
36
- // only of digits and separators (`$1,000$`).
35
+ // 5. The body must be non-empty, single-line, and must be something a
36
+ // formula could be ABOUT: a number written the way money is
37
+ // (`$1,000$`, `$1.000,50$` — a price written twice) or separators
38
+ // with no digits at all (`$+$`) are not.
39
+ //
40
+ // Rule 5 used to reject EVERY digits-and-separators body, and that was
41
+ // too wide: `1秒を $10000$ 個のステップに割る` and `答えは $1$` are the
42
+ // ordinary way to write a number in a maths article, and both came out
43
+ // as a literal `$10000$` / `$1$` sitting in the prose. The signature of
44
+ // a price is its SHAPE — digits in threes, or more than one separator —
45
+ // and the two shapes that actually
46
+ // appear in currency prose are already dead: `$100 と $200` by rule 3
47
+ // (whitespace before the close) and `$5-$10` by rule 4 (a digit after
48
+ // it). What stays admitted is a body like `$5$`, which a person quoting
49
+ // a price does not write: they write `$5`, and it is the DOUBLED
50
+ // delimiter that makes it maths.
51
+ //
52
+ // The SHAPE is matched rather than a particular separator, because a
53
+ // comma groups thousands in English and marks the decimal in most of
54
+ // Europe, and a dot does the opposite (codex, #2985). `$1,5$` is one and
55
+ // a half and typesets; `$1.000,50$` carries both a grouping AND a
56
+ // decimal mark, which no one computes with, and does not.
57
+ //
58
+ // A number with ONE separator typesets whatever follows it. Reading
59
+ // three digits after it as money cost every three-decimal constant a
60
+ // maths article writes (`$3.141$`, `$1.414$`, `$2.718$`) and bought only
61
+ // `$1,500$` — a shape currency prose never reaches, because rules 1-4
62
+ // kill every realistic way of writing a price first (#2991).
37
63
  //
38
64
  // Rules 2-5 are enforced in the tokenizer, where the whole match is in
39
65
  // hand. Rule 1 needs the character BEFORE the match, which a marked
@@ -49,9 +75,54 @@
49
75
  import { escapeHtml } from "@mulmoclaude/common";
50
76
  const ASCII_ALNUM = /[A-Za-z0-9]/;
51
77
  const ASCII_DIGIT = /\d/;
52
- /** Digits, separators and currency-ish punctuation only — `$1,000$`
53
- * is a price range, not an equation. */
54
- const NUMERIC_ONLY = /^[\s\d.,:;%+-]*$/;
78
+ const DIGITS = /^\d+$/;
79
+ /** Every character that separates the digit runs of a written number,
80
+ * in any locale: `1,000.50`, `1.000,50`, `1 000,50`. Which one groups
81
+ * and which one marks the decimal is exactly what cannot be known, so
82
+ * none of them is read as one or the other. */
83
+ const NUMBER_SEPARATORS = /[.,\s]/;
84
+ /** A bare number written the way MONEY is written rather than the way a
85
+ * quantity is — `1,000`, `1.000`, `1 000`, `1.000,50`, `12,345,678`.
86
+ *
87
+ * Read from the SHAPE, not from which separator appeared: a comma
88
+ * groups thousands in English and marks the decimal in most of Europe,
89
+ * and a dot does the opposite, so a rule that names one of them fails
90
+ * half the world's authors either way (codex, #2985).
91
+ *
92
+ * Two separators or more is money: `1.000,50` groups AND marks a
93
+ * decimal, which is a formatted amount rather than a quantity anyone
94
+ * computes with.
95
+ *
96
+ * ONE separator is not, whatever follows it. That branch used to read
97
+ * "exactly three digits after it" as money, and the cost was every
98
+ * three-decimal constant a maths article writes — `3.141`, `1.414`,
99
+ * `2.718`, `1.618`, `0.577`, `6.022`, `9.807` all came out as literal
100
+ * `$…$` in the prose (#2991). What it bought was `$1,500$`, and that
101
+ * turns out to be a shape currency prose never reaches: every realistic
102
+ * form dies earlier, at rules 1-4 —
103
+ *
104
+ * `$1,000 と $2,000` rule 3, whitespace before the close
105
+ * `合計 $1,000から$500` rule 3
106
+ * `$1,000-$2,000` rule 4, a digit after the close
107
+ * `US$1,000` rule 1, alphanumeric before the open
108
+ * `価格は $1,000 です` no closing `$` at all
109
+ *
110
+ * — leaving only `$1,000$`, both delimiters written out. That is the
111
+ * same shape this file already reads as maths for `$5$`: a person
112
+ * quoting a price writes `$1,000`, and it is the DOUBLED delimiter that
113
+ * makes it a formula. Rule 5 was applying one reading to `$5$` and the
114
+ * opposite to `$1,500$`; now it applies the same one to both. */
115
+ function isMoneyShaped(body) {
116
+ const runs = body.split(NUMBER_SEPARATORS);
117
+ // Anything that is not digits-and-separators is not a written number at
118
+ // all — `x=1` and `\pi` land here and are maths by this rule.
119
+ if (!runs.every((run) => DIGITS.test(run)))
120
+ return false;
121
+ return runs.length > 2;
122
+ }
123
+ /** The same set with the digits removed: a body of punctuation has
124
+ * nothing to typeset. */
125
+ const SEPARATORS_ONLY = /^[\s.,:;%+-]*$/;
55
126
  /** Index of the first `$` in `src` that could legally open math, or
56
127
  * `undefined` when there is none. Marked uses this to cut the
57
128
  * preceding text token, so returning a position is what gets the
@@ -86,8 +157,11 @@ export function isPlausibleInlineMath(body, after) {
86
157
  // Rule 4: `$5-$10`.
87
158
  if (ASCII_DIGIT.test(after))
88
159
  return false;
89
- // Rule 5.
90
- if (NUMERIC_ONLY.test(body))
160
+ // Rule 5. The shape of money is what says "price"; a plain number is
161
+ // just a number, and a number in a maths article is maths.
162
+ if (isMoneyShaped(body))
163
+ return false;
164
+ if (SEPARATORS_ONLY.test(body))
91
165
  return false;
92
166
  return true;
93
167
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mulmoclaude/markdown-utils",
3
- "version": "2.0.0",
3
+ "version": "2.2.0",
4
4
  "description": "Browser-safe markdown / image rendering utilities shared by the MulmoClaude host and the markdown plugin",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -35,7 +35,7 @@
35
35
  "dependencies": {
36
36
  "@mulmoclaude/common": "^1.2.0",
37
37
  "dompurify": "^3.4.13",
38
- "js-yaml": "^5.2.3",
38
+ "js-yaml": "^5.4.1",
39
39
  "marked": "^18.0.11"
40
40
  },
41
41
  "peerDependencies": {
@@ -47,7 +47,7 @@
47
47
  "mathjax-full": "^3.2.2",
48
48
  "mermaid": "^11.17.2",
49
49
  "typescript": "^6.0.3",
50
- "vue": "^3.5.41"
50
+ "vue": "^3.5.42"
51
51
  },
52
52
  "homepage": "https://github.com/receptron/mulmoclaude/tree/main/packages/markdown-utils#readme",
53
53
  "repository": {