@godxjp/editor 31.18.0 → 31.20.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.
package/README.md CHANGED
@@ -5,7 +5,7 @@ rendered by [`@godxjp/markdown`](../markdown) (the same sanitiser as the publish
5
5
  pasted, dropped or attached through an upload function **the host** provides.
6
6
 
7
7
  ```bash
8
- pnpm add @godxjp/editor @godxjp/markdown @godxjp/ui lucide-react
8
+ pnpm add @godxjp/editor @godxjp/markdown @godxjp/ui
9
9
  ```
10
10
 
11
11
  ```tsx
@@ -30,7 +30,9 @@ import { FormField } from "@godxjp/ui/data-entry";
30
30
  `readOnly`, its actions stay focusable but do nothing (`aria-disabled`).
31
31
  - Files: `upload(file) => Promise<{ url, name? }>`. A placeholder marks each upload and is replaced
32
32
  by `![name](url)` (images) or `[name](url)`; a failure removes it and says which file failed.
33
- `uploadBlockedReason` refuses files with the host's message. Without `upload`, files are ignored.
33
+ `uploadBlockedReason` refuses files with the host's message — with or without `upload`: a
34
+ pasted or dropped file shows it, and the attach button names the reason and shows it when
35
+ pressed instead of opening the picker. With neither prop, files are left to the browser.
34
36
  - Strings come from the kit's catalogue in ja / en / vi; `labels` overrides any of them.
35
37
 
36
38
  ## Props
package/dist/index.d.ts CHANGED
@@ -164,7 +164,11 @@ type MarkdownEditorProps = Omit<React.TextareaHTMLAttributes<HTMLTextAreaElement
164
164
  * (an app's own storage today, the media service later). Omit it and files are not accepted.
165
165
  */
166
166
  upload?: (file: File) => Promise<MarkdownEditorUploadResult>;
167
- /** When set, files are refused with this message (a quota, a read-only space). */
167
+ /**
168
+ * When set, files are refused with this message (a quota, a read-only space) — with or without
169
+ * `upload`. Pasted / dropped files show it; the attach button carries it in its name and tooltip
170
+ * and shows it when pressed, instead of opening a file picker whose choice would be refused.
171
+ */
168
172
  uploadBlockedReason?: string | null;
169
173
  /** The preview. Default: `<Prose><Markdown>{value}</Markdown></Prose>` from `@godxjp/markdown`. */
170
174
  renderPreview?: (value: string) => React.ReactNode;
@@ -197,7 +201,11 @@ declare const MarkdownEditor: React.ForwardRefExoticComponent<Omit<React.Textare
197
201
  * (an app's own storage today, the media service later). Omit it and files are not accepted.
198
202
  */
199
203
  upload?: (file: File) => Promise<MarkdownEditorUploadResult>;
200
- /** When set, files are refused with this message (a quota, a read-only space). */
204
+ /**
205
+ * When set, files are refused with this message (a quota, a read-only space) — with or without
206
+ * `upload`. Pasted / dropped files show it; the attach button carries it in its name and tooltip
207
+ * and shows it when pressed, instead of opening a file picker whose choice would be refused.
208
+ */
201
209
  uploadBlockedReason?: string | null;
202
210
  /** The preview. Default: `<Prose><Markdown>{value}</Markdown></Prose>` from `@godxjp/markdown`. */
203
211
  renderPreview?: (value: string) => React.ReactNode;
package/dist/index.js CHANGED
@@ -36,12 +36,29 @@ function wrapSelection(text, { start, end }, before, after, placeholder) {
36
36
  select: { start: outerStart, end: outerStart + selected.length }
37
37
  };
38
38
  }
39
- const body = selected || placeholder;
39
+ if (!selected) {
40
+ return {
41
+ from: start,
42
+ to: end,
43
+ insert: `${before}${placeholder}${after}`,
44
+ select: { start: start + before.length, end: start + before.length + placeholder.length }
45
+ };
46
+ }
47
+ const count = (value) => before === after ? value.split(before).length - 1 : 0;
48
+ let from = start;
49
+ let to = end;
50
+ if (text.slice(start - before.length, start) === before && count(text.slice(0, start)) % 2 === 1) {
51
+ from = start - before.length;
52
+ }
53
+ if (text.slice(end, end + after.length) === after && count(text.slice(0, end)) % 2 === 1) {
54
+ to = end + after.length;
55
+ }
56
+ const body = text.slice(from, to).split(before).join("").split(after).join("");
40
57
  return {
41
- from: start,
42
- to: end,
58
+ from,
59
+ to,
43
60
  insert: `${before}${body}${after}`,
44
- select: { start: start + before.length, end: start + before.length + body.length }
61
+ select: { start: from + before.length, end: from + before.length + body.length }
45
62
  };
46
63
  }
47
64
  function lineRange(text, { start, end }) {
@@ -290,12 +307,14 @@ var MarkdownEditor = React.forwardRef(
290
307
  const [uploadError, setUploadError] = React.useState(null);
291
308
  const uploadSeq = React.useRef(0);
292
309
  const fileInput = React.useRef(null);
310
+ const takesFiles = Boolean(upload) || Boolean(uploadBlockedReason);
293
311
  const acceptFiles = async (files) => {
294
- if (!upload || files.length === 0 || !editable) return;
312
+ if (files.length === 0 || !editable) return;
295
313
  if (uploadBlockedReason) {
296
314
  setUploadError(uploadBlockedReason);
297
315
  return;
298
316
  }
317
+ if (!upload) return;
299
318
  setUploadError(null);
300
319
  for (const file of files) {
301
320
  const placeholder = `![${label("uploading")} ${file.name.replace(/[[\]\\]/g, "")} #${++uploadSeq.current}]()`;
@@ -361,12 +380,14 @@ var MarkdownEditor = React.forwardRef(
361
380
  },
362
381
  { key: "quote", label: label("quote"), icon: /* @__PURE__ */ jsx(Quote, {}), run: () => lines("> ") }
363
382
  ];
364
- if (upload) {
383
+ if (takesFiles) {
365
384
  builtIn.push({
366
385
  key: "attach",
367
- label: label("attach"),
386
+ // A blocked attach says why in its own name and tooltip, BEFORE anyone opens the picker.
387
+ label: uploadBlockedReason ? `${label("attach")} \u2014 ${uploadBlockedReason}` : label("attach"),
368
388
  icon: /* @__PURE__ */ jsx(Paperclip, {}),
369
- run: () => fileInput.current?.click()
389
+ // …and a press repeats the reason instead of opening a picker whose choice would be refused.
390
+ run: () => uploadBlockedReason ? setUploadError(uploadBlockedReason) : fileInput.current?.click()
370
391
  });
371
392
  }
372
393
  const all = [...builtIn, ...actions];
@@ -397,7 +418,7 @@ var MarkdownEditor = React.forwardRef(
397
418
  onPaste: (event) => {
398
419
  onPaste?.(event);
399
420
  const files = Array.from(event.clipboardData?.files ?? []);
400
- if (!event.defaultPrevented && upload && files.length > 0) {
421
+ if (!event.defaultPrevented && takesFiles && files.length > 0) {
401
422
  event.preventDefault();
402
423
  void acceptFiles(files);
403
424
  }
@@ -405,7 +426,7 @@ var MarkdownEditor = React.forwardRef(
405
426
  onDrop: (event) => {
406
427
  onDrop?.(event);
407
428
  const files = Array.from(event.dataTransfer?.files ?? []);
408
- if (!event.defaultPrevented && upload && files.length > 0) {
429
+ if (!event.defaultPrevented && takesFiles && files.length > 0) {
409
430
  event.preventDefault();
410
431
  void acceptFiles(files);
411
432
  }
@@ -450,7 +471,7 @@ var MarkdownEditor = React.forwardRef(
450
471
  /* @__PURE__ */ jsx("div", { hidden: mode === "preview", children: textarea }),
451
472
  mode === "preview" ? preview : null
452
473
  ] }),
453
- upload ? /* @__PURE__ */ jsx(
474
+ upload && !uploadBlockedReason ? /* @__PURE__ */ jsx(
454
475
  "input",
455
476
  {
456
477
  ref: fileInput,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@godxjp/editor",
3
- "version": "31.18.0",
3
+ "version": "31.20.0",
4
4
  "description": "Markdown editor for GoDX apps: formatting toolbar, write / preview / side by side with @godxjp/markdown, paste / drop / attach through a host upload function, IME-safe, ja / en / vi.",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -28,10 +28,12 @@
28
28
  "directory": "packages/editor"
29
29
  },
30
30
  "license": "Apache-2.0",
31
+ "dependencies": {
32
+ "lucide-react": "^1.37.0"
33
+ },
31
34
  "peerDependencies": {
32
- "@godxjp/markdown": "~31.18.0",
33
- "@godxjp/ui": "~31.18.0",
34
- "lucide-react": ">=1.0.0",
35
+ "@godxjp/markdown": "~31.20.0",
36
+ "@godxjp/ui": "~31.20.0",
35
37
  "react": ">=19.0.0"
36
38
  },
37
39
  "keywords": [