@wyttime04/react-vditor 0.1.0 → 1.0.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 +71 -24
- package/dist/Vditor.js +4 -5
- package/dist/VditorPreview.js +2 -3
- package/dist/index.d.ts +17 -4
- package/dist/index.js +4 -5
- package/dist/options/default.js +81 -0
- package/dist/options/gfm.js +32 -0
- package/dist/toolbar/index.js +34 -0
- package/dist/transforms/gfm.js +14 -0
- package/dist/transforms/index.js +7 -0
- package/dist/transforms/link.js +25 -0
- package/dist/upload.js +32 -0
- package/dist/utils/merge.js +15 -0
- package/package.json +1 -1
- package/dist/cdn.js +0 -9
- package/dist/gfm.js +0 -121
- package/dist/uploadImage.js +0 -51
package/README.md
CHANGED
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
# @wyttime04/react-vditor
|
|
2
2
|
|
|
3
|
+
[](https://github.com/wyttime04/react-vditor)
|
|
4
|
+
[](https://www.npmjs.com/package/@wyttime04/react-vditor)
|
|
5
|
+
[](https://github.com/wyttime04/react-vditor/blob/main/LICENSE)
|
|
6
|
+
[](https://github.com/Vanessa219/vditor)
|
|
7
|
+
[](https://github.com/wyttime04/react-vditor#相容性)
|
|
8
|
+
|
|
3
9
|
以 [vditor](https://github.com/Vanessa219/vditor) 4.0.0 為基礎的 React 元件,**僅支援 GFM 語法,並可於完全離線的環境運作**。
|
|
4
10
|
|
|
5
11
|
- mermaid、數學式、echarts 等 GFM 以外的附加渲染一律停用,相關區塊以一般程式碼區塊呈現
|
|
@@ -62,7 +68,7 @@ const ref = useRef(null);
|
|
|
62
68
|
// 僅渲染內容,不建立編輯器
|
|
63
69
|
<VditorPreview value={markdown}/>
|
|
64
70
|
|
|
65
|
-
// 轉換為 HTML
|
|
71
|
+
// 轉換為 HTML
|
|
66
72
|
const html = await md2html(markdown);
|
|
67
73
|
```
|
|
68
74
|
|
|
@@ -163,9 +169,19 @@ props 沿用 `Vditor.preview` 的 `IPreviewOptions`,另增 `value`(要渲染
|
|
|
163
169
|
|
|
164
170
|
### `md2html(markdown, options?)`
|
|
165
171
|
|
|
166
|
-
將 Markdown 轉換為 HTML,回傳 `Promise<string>`。GFM
|
|
172
|
+
將 Markdown 轉換為 HTML,回傳 `Promise<string>`。GFM 以外的區塊與網址不合法的連結,處理方式與預覽相同
|
|
167
173
|
(vditor 原生的 `md2html` 不套用 `transform`,因此由本函式處理)。
|
|
168
174
|
|
|
175
|
+
### `gfmTransform`, `linkTransform`, `previewTransform`
|
|
176
|
+
|
|
177
|
+
預覽輸出所套用的 HTML 轉換,型別皆為 `(html: string) => string`。
|
|
178
|
+
|
|
179
|
+
| 函式 | 說明 |
|
|
180
|
+
|---|---|
|
|
181
|
+
| `gfmTransform` | GFM 以外的區塊(mermaid、數學式等)轉為一般程式碼區塊 |
|
|
182
|
+
| `linkTransform` | 網址不合法的連結轉為純文字,規則詳見「[預設值與原生 vditor 的差異](#預設值與原生-vditor-的差異)」 |
|
|
183
|
+
| `previewTransform` | 依序套用 `gfmTransform`、`linkTransform`;`<VditorPreview>`、`md2html()` 與編輯器的預覽區皆使用此轉換 |
|
|
184
|
+
|
|
169
185
|
## 主題
|
|
170
186
|
|
|
171
187
|
`<Vditor>` 與 `<VditorPreview>` 提供 `light`, `dark` 兩種主題,另可設為 `auto`,依作業系統的外觀設定(`prefers-color-scheme`)自動切換,並在系統設定變更時即時套用。
|
|
@@ -234,7 +250,7 @@ const App = () => {
|
|
|
234
250
|
"headings", "bold", "italic", "strike", "link", "|",
|
|
235
251
|
"list", "ordered-list", "check", "outdent", "indent", "|",
|
|
236
252
|
"quote", "line", "code", "inline-code", "insert-before", "insert-after", "|",
|
|
237
|
-
"upload
|
|
253
|
+
"upload", "table", "|",
|
|
238
254
|
"undo", "redo", "|",
|
|
239
255
|
"fullscreen", "edit-mode",
|
|
240
256
|
{name: "more", toolbar: ["both", "outline", "preview"]},
|
|
@@ -244,14 +260,13 @@ const App = () => {
|
|
|
244
260
|
與 vditor 原生的預設工具列相比:
|
|
245
261
|
|
|
246
262
|
- 移除 `export`(寫死連線至 CDN)、`devtools`(依賴 echarts)、`info`(寫死 unpkg 的圖片網址)
|
|
247
|
-
- 移除 `emoji`, `record`, `
|
|
248
|
-
- 新增 `upload-image`
|
|
263
|
+
- 移除 `emoji`, `record`, `code-theme`, `content-theme`, `help`
|
|
249
264
|
|
|
250
265
|
傳入 `toolbar` prop 時,會完整取代預設工具列。
|
|
251
266
|
|
|
252
|
-
###
|
|
267
|
+
### 上傳
|
|
253
268
|
|
|
254
|
-
|
|
269
|
+
工具列的 `upload` 按鈕、拖放與貼上加入的檔案,皆交由 `upload.handler` 處理,預設僅接受圖片。
|
|
255
270
|
|
|
256
271
|
#### 範例
|
|
257
272
|
|
|
@@ -272,35 +287,35 @@ const editorRef = useRef(null);
|
|
|
272
287
|
/>
|
|
273
288
|
```
|
|
274
289
|
|
|
275
|
-
|
|
290
|
+
允許上傳文件時,將 `upload.imageOnly` 設為 `false`:
|
|
276
291
|
|
|
277
292
|
```jsx
|
|
278
|
-
<Vditor
|
|
293
|
+
<Vditor upload={{handler, imageOnly: false, accept: "image/*,.pdf"}}/>
|
|
279
294
|
```
|
|
280
295
|
|
|
281
296
|
#### 行為
|
|
282
297
|
|
|
283
|
-
1.
|
|
284
|
-
2.
|
|
298
|
+
1. 檔案依 `upload.imageOnly` 與 `upload.accept` 過濾,僅將符合者交由 `upload.handler` 處理
|
|
299
|
+
2. 全部不符合時不呼叫 `handler`,並顯示「檔案類型不允許上傳」
|
|
285
300
|
3. `handler` 回傳字串時,以提示訊息顯示(例如「檔案過大」)
|
|
286
301
|
|
|
287
|
-
|
|
302
|
+
插入圖片或連結語法由 `handler` 負責,例如呼叫 ref 的 `insertValue`。
|
|
288
303
|
|
|
289
304
|
#### 相關設定
|
|
290
305
|
|
|
291
306
|
| 設定 | 說明 |
|
|
292
307
|
|---|---|
|
|
293
|
-
| `upload.
|
|
294
|
-
| `upload.
|
|
295
|
-
| `
|
|
296
|
-
|
|
|
308
|
+
| `upload.imageOnly` | 本套件新增,預設 `true`:僅接受圖片,按鈕提示為「上傳圖片」(支援 zh_TW、zh_CN、en_US,其他語言顯示英文)。設為 `false` 時僅依 `upload.accept` 過濾,按鈕提示為 vditor 的「上傳圖片或文件」 |
|
|
309
|
+
| `upload.accept` | 格式同 `<input accept>`,例如 `image/png,.pdf`。`imageOnly` 為 `true` 時僅能進一步限縮圖片類型;為 `false` 且未設定時不限制 |
|
|
310
|
+
| `upload.handler` | 處理上傳的檔案。上述過濾僅作用於 `handler`;改用 vditor 的 `upload.url` 上傳時,由 vditor 依 `upload.accept` 檢查 |
|
|
311
|
+
| `upload.multiple` | 為 `true`(vditor 預設)時可一次選取多個檔案,否則僅能選取一個 |
|
|
312
|
+
| 以物件指定按鈕 | 如需自訂提示文字或圖示:`{name: "upload", tip: "…", icon: "<svg>…</svg>"}`,指定的欄位優先於預設值。自訂的 `tip` 為固定字串,不會隨語言切換 |
|
|
297
313
|
|
|
298
314
|
#### 限制
|
|
299
315
|
|
|
300
|
-
-
|
|
301
|
-
-
|
|
302
|
-
|
|
303
|
-
- `upload.handler` 只在建立編輯器時讀取,詳見「[props 變更時的行為](#props-變更時的行為)」
|
|
316
|
+
- 設定 `upload.handler` 時 vditor 不會檢查檔案大小(`upload.max`),請於 `handler` 中自行驗證
|
|
317
|
+
- `upload.multiple` 不為 `true` 時,vditor 會先只取第一個檔案再交由過濾,同時拖入多個檔案可能因此全部被擋下
|
|
318
|
+
- `upload` 的設定只在建立編輯器時讀取,詳見「[props 變更時的行為](#props-變更時的行為)」
|
|
304
319
|
|
|
305
320
|
## 預設值與原生 vditor 的差異
|
|
306
321
|
|
|
@@ -311,16 +326,48 @@ const editorRef = useRef(null);
|
|
|
311
326
|
| `theme` | `light` | 僅支援 `light` / `dark` / `auto`,詳見「[主題](#主題)」 |
|
|
312
327
|
| `toolbar` | 詳見「[預設工具列](#預設工具列)」 | |
|
|
313
328
|
| `cache` | `{enable: false}` | vditor 啟用快取時必須提供 `cache.id` |
|
|
329
|
+
| `upload.imageOnly` | `true` | 本套件新增的選項:僅接受圖片,詳見「[上傳](#上傳)」 |
|
|
314
330
|
| `preview.markdown.footnotes` | `false` | GFM 不包含註腳 |
|
|
315
331
|
| `preview.markdown.codeBlockPreview`, `mathBlockPreview` | `false` | ir / wysiwyg 模式不顯示程式碼區塊的預覽面板,避免渲染 mermaid 等區塊 |
|
|
316
332
|
| `preview.render.media.enable` | `false` | 不將影片、音訊連結嵌入為 iframe |
|
|
317
|
-
| `preview.transform` | GFM
|
|
333
|
+
| `preview.transform` | GFM 以外的區塊轉為一般程式碼區塊;網址不合法的連結轉為純文字 | 網址規則同 DOMPurify 的 `IS_ALLOWED_URI`:允許 `http(s)`、`mailto`、`tel` 等協定與相對路徑。使用端傳入的 `transform` 會於其後執行 |
|
|
318
334
|
| `preview.theme.current` | `""`(固定) | 內容主題改由 `theme` 決定,不使用 vditor 整頁共用的 `<link>` |
|
|
319
335
|
| 行內數學式 `$…$` | 視為一般文字 | GFM 不包含數學式 |
|
|
320
336
|
|
|
337
|
+
## XSS 防護
|
|
338
|
+
|
|
339
|
+
內建的過濾如下:
|
|
340
|
+
|
|
341
|
+
| 來源 | 處理內容 |
|
|
342
|
+
|---|---|
|
|
343
|
+
| Lute 的 `sanitize`(vditor 預設啟用) | 移除 `<script>`、`<style>`、`on*` 事件屬性與 `javascript:` 網址 |
|
|
344
|
+
| `linkTransform` | 網址不合法的連結轉為純文字,詳見「[預設值與原生 vditor 的差異](#預設值與原生-vditor-的差異)」 |
|
|
345
|
+
|
|
346
|
+
`<iframe>`、`<embed>`、`<form>` 等元素與 `style` 屬性仍會保留,建議透過 `transform` 以 DOMPurify 等工具再行過濾:
|
|
347
|
+
|
|
348
|
+
```jsx
|
|
349
|
+
import DOMPurify from "dompurify";
|
|
350
|
+
|
|
351
|
+
// iframe、embed、object 由 DOMPurify 預設移除;表單元素與 style 屬性需另行禁止
|
|
352
|
+
const sanitize = (html) => DOMPurify.sanitize(html, {
|
|
353
|
+
FORBID_TAGS: ["form", "button", "textarea", "select"],
|
|
354
|
+
FORBID_ATTR: ["style"],
|
|
355
|
+
});
|
|
356
|
+
|
|
357
|
+
<Vditor preview={{transform: sanitize}}/>
|
|
358
|
+
<VditorPreview value={markdown} transform={sanitize}/>
|
|
359
|
+
const html = await md2html(markdown, {transform: sanitize});
|
|
360
|
+
```
|
|
361
|
+
|
|
362
|
+
- 使用端的 `transform` 於 `previewTransform` 之後執行
|
|
363
|
+
- 任務清單的勾選框為 `<input type="checkbox">`:需要任務清單時不可禁止 `input`;不需要時可將 `input` 加入 `FORBID_TAGS`
|
|
364
|
+
- 程式碼區塊的複製按鈕與上色由 vditor 於 `transform` 之後加入,不受影響
|
|
365
|
+
- `<Vditor>` 的 `preview.transform` 僅作用於預覽區(sv 模式的分屏預覽與預覽模式);wysiwyg、ir 模式的編輯區不經過 `transform`
|
|
366
|
+
|
|
321
367
|
## 已知限制
|
|
322
368
|
|
|
323
369
|
- ir / wysiwyg 模式中的程式碼區塊不會上色(停用預覽面板所致);sv 模式的預覽區與 `<VditorPreview>` 仍會上色
|
|
370
|
+
- 網址不合法的連結轉為純文字,僅作用於 `<VditorPreview>`、`md2html()` 與編輯器的預覽區;ir / wysiwyg 模式的編輯區不套用 `transform`,連結仍會顯示
|
|
324
371
|
- `$$ … $$` 區塊會以程式碼區塊呈現(Lute 未提供停用此語法的設定)
|
|
325
372
|
- `:smile:` 等 emoji 代碼會轉為對應字元,YAML front matter 會以程式碼區塊呈現;兩者雖非 GFM 語法,仍予以保留
|
|
326
373
|
- vditor 無論設定為何,都會整頁載入 highlight.js 的 `github.min.css`;`dark` 主題的程式碼樣式以局部化且優先權較高的規則覆寫
|
|
@@ -340,9 +387,9 @@ npm test # 建置測試頁、檢查型別,並執行 puppeteer 測
|
|
|
340
387
|
|
|
341
388
|
### 開發用畫面
|
|
342
389
|
|
|
343
|
-
`dev/`
|
|
344
|
-
|
|
345
|
-
|
|
390
|
+
`dev/` 提供開發用畫面,展示 `<Vditor>`、`<VditorPreview>` 與 `md2html()` 的輸出,並列出編輯器的事件紀錄。
|
|
391
|
+
可切換主題(透過 `VditorThemeProvider`),並呼叫 ref 的 `insertValue`, `disabled` / `enable`, `getValue`。
|
|
392
|
+
上傳的圖片(工具列、拖放、貼上)不會實際上傳,以 `blob:` 網址直接顯示。
|
|
346
393
|
|
|
347
394
|
`dev/main.jsx` 與使用端相同,以 `import "@wyttime04/react-vditor"` 及 `import "@wyttime04/react-vditor/index.css"` 引用套件,
|
|
348
395
|
由 `dev/webpack.config.cjs` 依 mode 決定來源:
|
package/dist/Vditor.js
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import * as React from "react";
|
|
2
2
|
import VditorCore from "vditor";
|
|
3
|
-
import {
|
|
4
|
-
import { gfmEditorOptions } from "./gfm.js";
|
|
3
|
+
import { editorOptions } from "./options/default.js";
|
|
5
4
|
import { themeClassName, useResolvedTheme } from "./theme.js";
|
|
6
5
|
|
|
7
6
|
/**
|
|
@@ -93,11 +92,11 @@ export const Vditor = /*#__PURE__*/React.forwardRef(function Vditor(props, ref)
|
|
|
93
92
|
} = propsRef.current;
|
|
94
93
|
// 先用空內容建立,after 裡關掉行內數學式之後才放入真正的內容;否則初次渲染就會去載入 katex
|
|
95
94
|
const instance = new VditorCore(host, {
|
|
96
|
-
...
|
|
95
|
+
...editorOptions({
|
|
97
96
|
...options,
|
|
98
97
|
value: "",
|
|
99
98
|
theme: editorTheme(themeRef.current)
|
|
100
|
-
}
|
|
99
|
+
}),
|
|
101
100
|
...callbacks
|
|
102
101
|
});
|
|
103
102
|
return () => {
|
|
@@ -162,7 +161,7 @@ export const Vditor = /*#__PURE__*/React.forwardRef(function Vditor(props, ref)
|
|
|
162
161
|
return contentRef.current ?? "";
|
|
163
162
|
}
|
|
164
163
|
if (name === "getCurrentMode") {
|
|
165
|
-
return propsRef.current.mode || "wysiwyg"; // 跟
|
|
164
|
+
return propsRef.current.mode || "wysiwyg"; // 跟 options/default.js 的預設 mode 一致
|
|
166
165
|
}
|
|
167
166
|
return undefined;
|
|
168
167
|
};
|
package/dist/VditorPreview.js
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import * as React from "react";
|
|
2
2
|
import VditorCore from "vditor";
|
|
3
|
-
import {
|
|
4
|
-
import { gfmPreviewOptions } from "./gfm.js";
|
|
3
|
+
import { previewOptions } from "./options/default.js";
|
|
5
4
|
import { themeClassName, useResolvedTheme } from "./theme.js";
|
|
6
5
|
|
|
7
6
|
/**
|
|
@@ -21,7 +20,7 @@ export const VditorPreview = props => {
|
|
|
21
20
|
// 選項常常是每次 render 都新建的物件;用內容比較,避免每次 render 都重新渲染
|
|
22
21
|
const optionsKey = JSON.stringify(options);
|
|
23
22
|
React.useEffect(() => {
|
|
24
|
-
VditorCore.preview(elementRef.current, value,
|
|
23
|
+
VditorCore.preview(elementRef.current, value, previewOptions(options));
|
|
25
24
|
}, [value, optionsKey]); // options 以內容比較(optionsKey),不放物件本身
|
|
26
25
|
|
|
27
26
|
// 主題只看外層容器的 class(index.css 裡局部化的樣式),換主題不用重新渲染;
|
package/dist/index.d.ts
CHANGED
|
@@ -17,10 +17,17 @@ export type VditorRef = Pick<VditorCore,
|
|
|
17
17
|
| "getValue" | "getCurrentMode" | "getSelection" | "getCursorPosition" | "isUploading"
|
|
18
18
|
| "html2md" | "exportJSON" | "getHTML" | "getCommentIds">;
|
|
19
19
|
|
|
20
|
-
/** props 就是 vditor 的 IOptions(theme 換成 VditorTheme),另外加 className、style */
|
|
21
|
-
export type VditorProps = Omit<IOptions, "theme"> & {
|
|
20
|
+
/** props 就是 vditor 的 IOptions(theme 換成 VditorTheme,upload 多了 imageOnly),另外加 className、style */
|
|
21
|
+
export type VditorProps = Omit<IOptions, "theme" | "upload"> & {
|
|
22
22
|
/** 預設取最近的 VditorThemeProvider,沒有就是 light */
|
|
23
23
|
theme?: VditorTheme;
|
|
24
|
+
upload?: IUpload & {
|
|
25
|
+
/**
|
|
26
|
+
* 預設 true:upload 按鈕、拖曳、貼上只收圖片,accept 再從中限縮,按鈕提示為「上傳圖片」。
|
|
27
|
+
* false 時只依 accept 過濾,按鈕提示為「上傳圖片或文件」
|
|
28
|
+
*/
|
|
29
|
+
imageOnly?: boolean;
|
|
30
|
+
};
|
|
24
31
|
className?: string;
|
|
25
32
|
style?: React.CSSProperties;
|
|
26
33
|
};
|
|
@@ -41,8 +48,14 @@ export type VditorPreviewProps = Omit<Partial<IPreviewOptions>, "theme" | "mode"
|
|
|
41
48
|
/** 只渲染 Markdown、不建立編輯器 */
|
|
42
49
|
export declare const VditorPreview: (props: VditorPreviewProps) => React.ReactElement;
|
|
43
50
|
|
|
44
|
-
/**
|
|
51
|
+
/** 非 GFM 的區塊(mermaid、數學式等)一律變成普通程式碼區塊 */
|
|
45
52
|
export declare const gfmTransform: (html: string) => string;
|
|
46
53
|
|
|
47
|
-
/**
|
|
54
|
+
/** 網址不合法的連結變成純文字,只留下連結文字 */
|
|
55
|
+
export declare const linkTransform: (html: string) => string;
|
|
56
|
+
|
|
57
|
+
/** 預覽輸出使用的轉換:依序套用 gfmTransform、linkTransform */
|
|
58
|
+
export declare const previewTransform: (html: string) => string;
|
|
59
|
+
|
|
60
|
+
/** Markdown 轉 HTML,跟預覽一樣套用 previewTransform */
|
|
48
61
|
export declare const md2html: (markdown: string, options?: Partial<IPreviewOptions>) => Promise<string>;
|
package/dist/index.js
CHANGED
|
@@ -1,13 +1,12 @@
|
|
|
1
1
|
import VditorCore from "vditor";
|
|
2
|
-
import {
|
|
3
|
-
import { gfmPreviewOptions, gfmTransform } from "./gfm.js";
|
|
2
|
+
import { previewOptions } from "./options/default.js";
|
|
4
3
|
export { Vditor } from "./Vditor.js";
|
|
5
4
|
export { VditorPreview } from "./VditorPreview.js";
|
|
6
5
|
export { VditorThemeProvider } from "./theme.js";
|
|
7
|
-
export { gfmTransform };
|
|
6
|
+
export { gfmTransform, linkTransform, previewTransform } from "./transforms/index.js";
|
|
8
7
|
|
|
9
|
-
/** Markdown 轉 HTML
|
|
8
|
+
/** Markdown 轉 HTML,跟預覽一樣套用 previewTransform(原生 md2html 不吃 transform) */
|
|
10
9
|
export const md2html = async (markdown, options = {}) => {
|
|
11
|
-
const merged =
|
|
10
|
+
const merged = previewOptions(options);
|
|
12
11
|
return merged.transform(await VditorCore.md2html(markdown, merged));
|
|
13
12
|
};
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
// 套件的預設選項:預設值合併 gfm.js 的 GFM 限定設定,再疊上使用端的選項
|
|
2
|
+
import { TOOLBAR, expandToolbar } from "../toolbar/index.js";
|
|
3
|
+
import { previewTransform } from "../transforms/index.js";
|
|
4
|
+
import { uploadableOnly } from "../upload.js";
|
|
5
|
+
import { merge } from "../utils/merge.js";
|
|
6
|
+
import { gfmEditorOptions, gfmPreviewOptions } from "./gfm.js";
|
|
7
|
+
|
|
8
|
+
/* global __webpack_public_path__ */
|
|
9
|
+
/**
|
|
10
|
+
* vditor 在執行期會去 `${cdn}/dist/...` 載入 lute、語系、圖示等資源。
|
|
11
|
+
* webpack plugin 把它們輸出在 output 目錄底下的 vditor/,所以預設 cdn 跟著 webpack 的 publicPath 走。
|
|
12
|
+
*/
|
|
13
|
+
const defaultCdn = () => {
|
|
14
|
+
// 不是用 webpack 打包時,typeof 未宣告的識別字會得到 "undefined",不會拋錯
|
|
15
|
+
const base = typeof __webpack_public_path__ === "string" ? __webpack_public_path__ : "/";
|
|
16
|
+
return base === "" || base.endsWith("/") ? `${base}vditor` : `${base}/vditor`;
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
/** 使用端自己的 transform 接在 previewTransform 之後 */
|
|
20
|
+
const withPreviewTransform = transform => transform ? html => transform(previewTransform(html)) : previewTransform;
|
|
21
|
+
|
|
22
|
+
/** 編輯器選項 */
|
|
23
|
+
export const editorOptions = options => {
|
|
24
|
+
const defaults = merge({
|
|
25
|
+
cdn: defaultCdn(),
|
|
26
|
+
lang: "zh_TW",
|
|
27
|
+
mode: "wysiwyg",
|
|
28
|
+
// 傳 HTMLElement 給 vditor 時,開快取一定要有 cache.id,否則直接拋錯
|
|
29
|
+
cache: {
|
|
30
|
+
enable: false
|
|
31
|
+
},
|
|
32
|
+
toolbar: TOOLBAR,
|
|
33
|
+
preview: {
|
|
34
|
+
actions: []
|
|
35
|
+
},
|
|
36
|
+
// 本套件的選項:upload 按鈕、拖曳、貼上預設只收圖片
|
|
37
|
+
upload: {
|
|
38
|
+
imageOnly: true
|
|
39
|
+
}
|
|
40
|
+
}, gfmEditorOptions());
|
|
41
|
+
const merged = merge(defaults, options);
|
|
42
|
+
const {
|
|
43
|
+
upload
|
|
44
|
+
} = merged;
|
|
45
|
+
merged.upload = {
|
|
46
|
+
...upload,
|
|
47
|
+
// upload 按鈕的檔案選擇框用 accept 決定列出哪些檔案;imageOnly 而沒指定 accept 時只列出圖片
|
|
48
|
+
accept: upload.imageOnly && !upload.accept ? "image/*" : upload.accept,
|
|
49
|
+
...(upload.handler && {
|
|
50
|
+
handler: uploadableOnly(upload.handler, upload)
|
|
51
|
+
})
|
|
52
|
+
};
|
|
53
|
+
merged.preview = {
|
|
54
|
+
...merged.preview,
|
|
55
|
+
transform: withPreviewTransform(options.preview?.transform),
|
|
56
|
+
// 內容主題不交給 vditor(它用整頁共用的 <link>),改由元件容器的 class 套用 index.css 裡局部化的主題
|
|
57
|
+
theme: {
|
|
58
|
+
...merged.preview.theme,
|
|
59
|
+
current: ""
|
|
60
|
+
}
|
|
61
|
+
};
|
|
62
|
+
merged.toolbar = expandToolbar(merged.toolbar, merged.lang, upload.imageOnly);
|
|
63
|
+
return merged;
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
/** Vditor.preview 的選項 */
|
|
67
|
+
export const previewOptions = options => {
|
|
68
|
+
const defaults = merge({
|
|
69
|
+
cdn: defaultCdn(),
|
|
70
|
+
lang: "zh_TW",
|
|
71
|
+
mode: "light"
|
|
72
|
+
}, gfmPreviewOptions());
|
|
73
|
+
const merged = merge(defaults, options);
|
|
74
|
+
merged.transform = withPreviewTransform(options.transform);
|
|
75
|
+
// 同編輯器:內容主題改由元件容器的 class 決定
|
|
76
|
+
merged.theme = {
|
|
77
|
+
...merged.theme,
|
|
78
|
+
current: ""
|
|
79
|
+
};
|
|
80
|
+
return merged;
|
|
81
|
+
};
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
// GFM 限定的設定:關掉 vditor 預設會渲染、但不屬於 GFM 的語法。
|
|
2
|
+
// 每次回傳新物件,避免不同的編輯器、預覽共用同一份選項物件
|
|
3
|
+
|
|
4
|
+
/** 編輯器 */
|
|
5
|
+
export const gfmEditorOptions = () => ({
|
|
6
|
+
preview: {
|
|
7
|
+
markdown: {
|
|
8
|
+
footnotes: false,
|
|
9
|
+
// GFM 以外、預設開著的只有註腳
|
|
10
|
+
codeBlockPreview: false,
|
|
11
|
+
// ir / wysiwyg 的程式碼區塊不開預覽面板,否則會去渲染 mermaid 等區塊
|
|
12
|
+
mathBlockPreview: false // 同上,$$ 區塊
|
|
13
|
+
},
|
|
14
|
+
render: {
|
|
15
|
+
media: {
|
|
16
|
+
enable: false
|
|
17
|
+
}
|
|
18
|
+
} // 不把影片、音訊連結嵌成 iframe
|
|
19
|
+
}
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
/** Vditor.preview */
|
|
23
|
+
export const gfmPreviewOptions = () => ({
|
|
24
|
+
markdown: {
|
|
25
|
+
footnotes: false
|
|
26
|
+
},
|
|
27
|
+
render: {
|
|
28
|
+
media: {
|
|
29
|
+
enable: false
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
});
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 預設工具列。跟 vditor 原本的相比:
|
|
3
|
+
* - 拿掉 export(寫死連 CDN)、devtools(要 echarts)、info(寫死 unpkg 的 logo)
|
|
4
|
+
* - 拿掉 emoji、record、code-theme、content-theme、help
|
|
5
|
+
*/
|
|
6
|
+
export const TOOLBAR = ["headings", "bold", "italic", "strike", "link", "|", "list", "ordered-list", "check", "outdent", "indent", "|", "quote", "line", "code", "inline-code", "insert-before", "insert-after", "|", "upload", "table", "|", "undo", "redo", "|", "fullscreen", "edit-mode", {
|
|
7
|
+
name: "more",
|
|
8
|
+
toolbar: ["both", "outline", "preview"]
|
|
9
|
+
}];
|
|
10
|
+
|
|
11
|
+
/** upload.imageOnly 時 upload 按鈕的提示。vditor 語系檔沒有這個字,照各語系「上傳圖片或文件」的譯法拿掉「或文件」;沒列到的語言用英文 */
|
|
12
|
+
const IMAGE_ONLY_TIPS = {
|
|
13
|
+
en_US: "Upload image",
|
|
14
|
+
zh_CN: "上传图片",
|
|
15
|
+
zh_TW: "上傳圖片"
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* upload.imageOnly 時,upload 按鈕(含 more 之類的子選單裡的)的提示改成「上傳圖片」;使用端在物件裡給的 tip 優先。
|
|
20
|
+
* 不是 imageOnly 時沿用 vditor 語系檔的「上傳圖片或文件」
|
|
21
|
+
*/
|
|
22
|
+
export const expandToolbar = (toolbar, lang, imageOnly) => toolbar.map(item => {
|
|
23
|
+
if (imageOnly && (item === "upload" || item?.name === "upload")) {
|
|
24
|
+
return {
|
|
25
|
+
name: "upload",
|
|
26
|
+
tip: IMAGE_ONLY_TIPS[lang] || IMAGE_ONLY_TIPS.en_US,
|
|
27
|
+
...(typeof item === "string" ? {} : item)
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
return item?.toolbar ? {
|
|
31
|
+
...item,
|
|
32
|
+
toolbar: expandToolbar(item.toolbar, lang, imageOnly)
|
|
33
|
+
} : item;
|
|
34
|
+
});
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
// 非 GFM 的區塊(mermaid、數學式、echarts…)一律變成普通程式碼區塊;webpack plugin 也不會輸出那些渲染套件。
|
|
2
|
+
|
|
3
|
+
/** Lute 會把這些語言輸出成 <div class="language-xxx"> 等渲染器畫圖 */
|
|
4
|
+
const DIV_LANGUAGES = "mermaid|echarts|abc|graphviz|mindmap|flowchart|plantuml|infographic|math";
|
|
5
|
+
/** 這些語言輸出成 <pre><code class="language-xxx">,但渲染器一樣會找上門 */
|
|
6
|
+
const CODE_LANGUAGES = "smiles|wavedrom|markmap|math";
|
|
7
|
+
const DIV_PATTERN = new RegExp(`<div(?: data-code="[^"]*")? class="language-(${DIV_LANGUAGES})">([\\s\\S]*?)</div>`, "g");
|
|
8
|
+
const CODE_PATTERN = new RegExp(`<code class="language-(${CODE_LANGUAGES})">`, "g");
|
|
9
|
+
const INLINE_MATH_PATTERN = /<span class="language-math">([\s\S]*?)<\/span>/g;
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* class 改成 language-plaintext,渲染器就找不到它們,也就不會去載入沒輸出的套件;原本的語言留在 data-lang。
|
|
13
|
+
*/
|
|
14
|
+
export const gfmTransform = html => html.replace(DIV_PATTERN, '<pre><code class="language-plaintext" data-lang="$1">$2</code></pre>').replace(CODE_PATTERN, '<code class="language-plaintext" data-lang="$1">').replace(INLINE_MATH_PATTERN, "$$$1$$");
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
// 預覽輸出的 HTML 轉換:各個 transform 都從這裡引用
|
|
2
|
+
import { gfmTransform } from "./gfm.js";
|
|
3
|
+
import { linkTransform } from "./link.js";
|
|
4
|
+
export { gfmTransform, linkTransform };
|
|
5
|
+
|
|
6
|
+
/** 預覽輸出使用的轉換:非 GFM 的區塊變成普通程式碼區塊,再把網址不合法的連結變成純文字 */
|
|
7
|
+
export const previewTransform = html => linkTransform(gfmTransform(html));
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
// 網址不合法的連結變成純文字:拿掉 <a>,只留下連結文字(含粗體等格式)。
|
|
2
|
+
// Lute 的 sanitize 只會清掉 javascript: 開頭的 href,data:、vbscript: 等仍會輸出成連結,所以這裡再檢查一次。
|
|
3
|
+
|
|
4
|
+
// 規則取自 DOMPurify 的 src/regexp.ts:放行 http(s)、ftp(s)、mailto、tel 等協定,以及相對路徑
|
|
5
|
+
const IS_ALLOWED_URI = /^(?:(?:(?:f|ht)tps?|mailto|tel|callto|sms|cid|xmpp|matrix):|[^a-z]|[a-z+.\-]+(?:[^a-z+.\-:]|$))/i;
|
|
6
|
+
const ATTR_WHITESPACE = /[\u0000-\u0020\u00A0\u1680\u180E\u2000-\u2029\u205F\u3000]/g;
|
|
7
|
+
const isAllowedUri = url => IS_ALLOWED_URI.test(url.replace(ATTR_WHITESPACE, ""));
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* 用 <template> 解析,裡面的 script 不會執行、圖片也不會載入。
|
|
11
|
+
* 沒有要拆的連結時原樣回傳:重新序列化會改變輸出的寫法(例如 <img … /> 變成 <img …>)
|
|
12
|
+
*/
|
|
13
|
+
export const linkTransform = html => {
|
|
14
|
+
const template = document.createElement("template");
|
|
15
|
+
template.innerHTML = html;
|
|
16
|
+
let changed = false;
|
|
17
|
+
template.content.querySelectorAll("a").forEach(a => {
|
|
18
|
+
const href = a.getAttribute("href");
|
|
19
|
+
if (href === null || !isAllowedUri(href)) {
|
|
20
|
+
a.replaceWith(...a.childNodes);
|
|
21
|
+
changed = true;
|
|
22
|
+
}
|
|
23
|
+
});
|
|
24
|
+
return changed ? template.innerHTML : html;
|
|
25
|
+
};
|
package/dist/upload.js
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
// 上傳檔案的過濾。設了 upload.handler 時,vditor 不檢查檔案類型就把所有檔案交給 handler
|
|
2
|
+
// (拖曳、貼上、工具列的 upload 按鈕都是),這裡依 upload.imageOnly 與 upload.accept 補上檢查。
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* 檔案是否符合 accept,規則同 <input accept>:.png 比對副檔名、image/* 比對類型前綴、image/png 比對完整類型。
|
|
6
|
+
* accept 為空時不限制
|
|
7
|
+
*/
|
|
8
|
+
const isAccepted = (file, accept) => !accept || accept.split(",").some(item => {
|
|
9
|
+
const type = item.trim().toLowerCase();
|
|
10
|
+
if (type.startsWith(".")) {
|
|
11
|
+
return file.name.toLowerCase().endsWith(type);
|
|
12
|
+
}
|
|
13
|
+
if (type.endsWith("/*")) {
|
|
14
|
+
return file.type.startsWith(type.slice(0, -1));
|
|
15
|
+
}
|
|
16
|
+
return file.type === type;
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
/** imageOnly 時必須是圖片,accept 再從中限縮;否則只看 accept */
|
|
20
|
+
const isUploadable = (file, {
|
|
21
|
+
imageOnly,
|
|
22
|
+
accept
|
|
23
|
+
}) => (!imageOnly || isAccepted(file, "image/*")) && isAccepted(file, accept);
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* 包裝 upload.handler:只把可上傳的檔案交給它。
|
|
27
|
+
* 一個都不符合時回傳提示文字,vditor 會顯示出來;不帶檔名,因為 vditor 用 innerHTML 顯示提示
|
|
28
|
+
*/
|
|
29
|
+
export const uploadableOnly = (handler, upload) => files => {
|
|
30
|
+
const uploadable = files.filter(file => isUploadable(file, upload));
|
|
31
|
+
return uploadable.length > 0 ? handler(uploadable) : window.VditorI18n.fileTypeError;
|
|
32
|
+
};
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
const isPlainObject = value => Object.prototype.toString.call(value) === "[object Object]";
|
|
2
|
+
|
|
3
|
+
/** 物件逐層合併,陣列與其他值直接覆蓋 */
|
|
4
|
+
export const merge = (base, override) => {
|
|
5
|
+
if (!isPlainObject(base) || !isPlainObject(override)) {
|
|
6
|
+
return override === undefined ? base : override;
|
|
7
|
+
}
|
|
8
|
+
const result = {
|
|
9
|
+
...base
|
|
10
|
+
};
|
|
11
|
+
Object.keys(override).forEach(key => {
|
|
12
|
+
result[key] = merge(result[key], override[key]);
|
|
13
|
+
});
|
|
14
|
+
return result;
|
|
15
|
+
};
|
package/package.json
CHANGED
package/dist/cdn.js
DELETED
|
@@ -1,9 +0,0 @@
|
|
|
1
|
-
// vditor 在執行期會去 `${cdn}/dist/...` 載入 lute、語系、圖示等資源。
|
|
2
|
-
// webpack plugin 把它們輸出在 output 目錄底下的 vditor/,所以預設 cdn 跟著 webpack 的 publicPath 走。
|
|
3
|
-
|
|
4
|
-
/* global __webpack_public_path__ */
|
|
5
|
-
export const defaultCdn = () => {
|
|
6
|
-
// 不是用 webpack 打包時,typeof 未宣告的識別字會得到 "undefined",不會拋錯
|
|
7
|
-
const base = typeof __webpack_public_path__ === "string" ? __webpack_public_path__ : "/";
|
|
8
|
-
return base === "" || base.endsWith("/") ? `${base}vditor` : `${base}/vditor`;
|
|
9
|
-
};
|
package/dist/gfm.js
DELETED
|
@@ -1,121 +0,0 @@
|
|
|
1
|
-
// 只支援 GFM 的預設設定。vditor 原本的附加渲染(mermaid、數學式、echarts…)一律不渲染,
|
|
2
|
-
// 顯示成普通程式碼區塊;webpack plugin 也不會輸出那些渲染套件。
|
|
3
|
-
import { UPLOAD_IMAGE, uploadImageItem } from "./uploadImage.js";
|
|
4
|
-
|
|
5
|
-
/** Lute 會把這些語言輸出成 <div class="language-xxx"> 等渲染器畫圖 */
|
|
6
|
-
const DIV_LANGUAGES = "mermaid|echarts|abc|graphviz|mindmap|flowchart|plantuml|infographic|math";
|
|
7
|
-
/** 這些語言輸出成 <pre><code class="language-xxx">,但渲染器一樣會找上門 */
|
|
8
|
-
const CODE_LANGUAGES = "smiles|wavedrom|markmap|math";
|
|
9
|
-
const DIV_PATTERN = new RegExp(`<div(?: data-code="[^"]*")? class="language-(${DIV_LANGUAGES})">([\\s\\S]*?)</div>`, "g");
|
|
10
|
-
const CODE_PATTERN = new RegExp(`<code class="language-(${CODE_LANGUAGES})">`, "g");
|
|
11
|
-
const INLINE_MATH_PATTERN = /<span class="language-math">([\s\S]*?)<\/span>/g;
|
|
12
|
-
|
|
13
|
-
/**
|
|
14
|
-
* 預覽用的 HTML 轉換:非 GFM 的區塊一律變成普通程式碼區塊。
|
|
15
|
-
* class 改成 language-plaintext,渲染器就找不到它們,也就不會去載入沒輸出的套件;原本的語言留在 data-lang。
|
|
16
|
-
*/
|
|
17
|
-
export const gfmTransform = html => html.replace(DIV_PATTERN, '<pre><code class="language-plaintext" data-lang="$1">$2</code></pre>').replace(CODE_PATTERN, '<code class="language-plaintext" data-lang="$1">').replace(INLINE_MATH_PATTERN, "$$$1$$");
|
|
18
|
-
|
|
19
|
-
/**
|
|
20
|
-
* 預設工具列。跟 vditor 原本的相比:
|
|
21
|
-
* - 拿掉 export(寫死連 CDN)、devtools(要 echarts)、info(寫死 unpkg 的 logo)
|
|
22
|
-
* - 拿掉 emoji、record、upload、code-theme、content-theme、help
|
|
23
|
-
* - 加上只收圖片的 upload-image
|
|
24
|
-
*/
|
|
25
|
-
const TOOLBAR = ["headings", "bold", "italic", "strike", "link", "|", "list", "ordered-list", "check", "outdent", "indent", "|", "quote", "line", "code", "inline-code", "insert-before", "insert-after", "|", UPLOAD_IMAGE, "table", "|", "undo", "redo", "|", "fullscreen", "edit-mode", {
|
|
26
|
-
name: "more",
|
|
27
|
-
toolbar: ["both", "outline", "preview"]
|
|
28
|
-
}];
|
|
29
|
-
|
|
30
|
-
/** 工具列裡的 upload-image(含 more 之類的子選單)換成完整的按鈕設定 */
|
|
31
|
-
const expandToolbar = (toolbar, lang) => toolbar.map(item => {
|
|
32
|
-
if (item === UPLOAD_IMAGE || item?.name === UPLOAD_IMAGE) {
|
|
33
|
-
return uploadImageItem(item, lang);
|
|
34
|
-
}
|
|
35
|
-
return item?.toolbar ? {
|
|
36
|
-
...item,
|
|
37
|
-
toolbar: expandToolbar(item.toolbar, lang)
|
|
38
|
-
} : item;
|
|
39
|
-
});
|
|
40
|
-
const isPlainObject = value => Object.prototype.toString.call(value) === "[object Object]";
|
|
41
|
-
|
|
42
|
-
/** 物件逐層合併,陣列與其他值直接覆蓋 */
|
|
43
|
-
const merge = (base, override) => {
|
|
44
|
-
if (!isPlainObject(base) || !isPlainObject(override)) {
|
|
45
|
-
return override === undefined ? base : override;
|
|
46
|
-
}
|
|
47
|
-
const result = {
|
|
48
|
-
...base
|
|
49
|
-
};
|
|
50
|
-
Object.keys(override).forEach(key => {
|
|
51
|
-
result[key] = merge(result[key], override[key]);
|
|
52
|
-
});
|
|
53
|
-
return result;
|
|
54
|
-
};
|
|
55
|
-
|
|
56
|
-
/** 使用端自己的 transform 接在 gfmTransform 之後 */
|
|
57
|
-
const withGfmTransform = transform => transform ? html => transform(gfmTransform(html)) : gfmTransform;
|
|
58
|
-
|
|
59
|
-
/** 編輯器選項:GFM 預設值,再疊上使用端的選項 */
|
|
60
|
-
export const gfmEditorOptions = (options, cdn) => {
|
|
61
|
-
const merged = merge({
|
|
62
|
-
cdn,
|
|
63
|
-
lang: "zh_TW",
|
|
64
|
-
mode: "wysiwyg",
|
|
65
|
-
// 傳 HTMLElement 給 vditor 時,開快取一定要有 cache.id,否則直接拋錯
|
|
66
|
-
cache: {
|
|
67
|
-
enable: false
|
|
68
|
-
},
|
|
69
|
-
toolbar: TOOLBAR,
|
|
70
|
-
preview: {
|
|
71
|
-
actions: [],
|
|
72
|
-
markdown: {
|
|
73
|
-
footnotes: false,
|
|
74
|
-
// GFM 以外、預設開著的只有註腳
|
|
75
|
-
codeBlockPreview: false,
|
|
76
|
-
// ir / wysiwyg 的程式碼區塊不開預覽面板,否則會去渲染 mermaid 等區塊
|
|
77
|
-
mathBlockPreview: false // 同上,$$ 區塊
|
|
78
|
-
},
|
|
79
|
-
render: {
|
|
80
|
-
media: {
|
|
81
|
-
enable: false
|
|
82
|
-
}
|
|
83
|
-
} // 不把影片、音訊連結嵌成 iframe
|
|
84
|
-
}
|
|
85
|
-
}, options);
|
|
86
|
-
merged.preview = {
|
|
87
|
-
...merged.preview,
|
|
88
|
-
transform: withGfmTransform(options.preview?.transform),
|
|
89
|
-
// 內容主題不交給 vditor(它用整頁共用的 <link>),改由元件容器的 class 套用 index.css 裡局部化的主題
|
|
90
|
-
theme: {
|
|
91
|
-
...merged.preview.theme,
|
|
92
|
-
current: ""
|
|
93
|
-
}
|
|
94
|
-
};
|
|
95
|
-
merged.toolbar = expandToolbar(merged.toolbar, merged.lang);
|
|
96
|
-
return merged;
|
|
97
|
-
};
|
|
98
|
-
|
|
99
|
-
/** Vditor.preview 的選項:GFM 預設值,再疊上使用端的選項 */
|
|
100
|
-
export const gfmPreviewOptions = (options, cdn) => {
|
|
101
|
-
const merged = merge({
|
|
102
|
-
cdn,
|
|
103
|
-
lang: "zh_TW",
|
|
104
|
-
mode: "light",
|
|
105
|
-
markdown: {
|
|
106
|
-
footnotes: false
|
|
107
|
-
},
|
|
108
|
-
render: {
|
|
109
|
-
media: {
|
|
110
|
-
enable: false
|
|
111
|
-
}
|
|
112
|
-
}
|
|
113
|
-
}, options);
|
|
114
|
-
merged.transform = withGfmTransform(options.transform);
|
|
115
|
-
// 同編輯器:內容主題改由元件容器的 class 決定
|
|
116
|
-
merged.theme = {
|
|
117
|
-
...merged.theme,
|
|
118
|
-
current: ""
|
|
119
|
-
};
|
|
120
|
-
return merged;
|
|
121
|
-
};
|
package/dist/uploadImage.js
DELETED
|
@@ -1,51 +0,0 @@
|
|
|
1
|
-
// 「上傳圖片」按鈕:toolbar 裡寫 "upload-image" 就會換成這顆按鈕。
|
|
2
|
-
// vditor 內建的 upload 只有一顆,檔案類型看的是共用的 upload.accept,所以另做一顆只收圖片的。
|
|
3
|
-
|
|
4
|
-
/** 按鈕的 tip。vditor 語系檔沒有這個字,照各語系「上傳圖片或文件」的譯法拿掉「或文件」;沒列到的語言用英文 */
|
|
5
|
-
const TIPS = {
|
|
6
|
-
en_US: "Upload image",
|
|
7
|
-
zh_CN: "上传图片",
|
|
8
|
-
zh_TW: "上傳圖片"
|
|
9
|
-
};
|
|
10
|
-
export const UPLOAD_IMAGE = "upload-image";
|
|
11
|
-
|
|
12
|
-
/**
|
|
13
|
-
* 選完圖片後交給 upload.handler,做法與 vditor 內建上傳在設了 handler 時相同:
|
|
14
|
-
* upload.multiple 不是 true 時只能選一張;handler 回傳字串時當成錯誤訊息顯示。
|
|
15
|
-
* 只支援 upload.handler,不支援 upload.url(那條路是 vditor 內部的 XHR 上傳流程,沒有公開的 API 可以呼叫)。
|
|
16
|
-
*/
|
|
17
|
-
const pickImages = vditor => {
|
|
18
|
-
const {
|
|
19
|
-
handler,
|
|
20
|
-
multiple
|
|
21
|
-
} = vditor.options.upload;
|
|
22
|
-
if (!handler) {
|
|
23
|
-
vditor.tip.show("please config: options.upload.handler");
|
|
24
|
-
return;
|
|
25
|
-
}
|
|
26
|
-
const input = document.createElement("input");
|
|
27
|
-
input.type = "file";
|
|
28
|
-
input.accept = "image/*";
|
|
29
|
-
input.multiple = multiple === true;
|
|
30
|
-
input.addEventListener("change", async () => {
|
|
31
|
-
// 檔案選擇框可以切成「所有檔案」,所以再過濾一次
|
|
32
|
-
const images = [...input.files].filter(file => file.type.startsWith("image/"));
|
|
33
|
-
if (images.length === 0) {
|
|
34
|
-
return;
|
|
35
|
-
}
|
|
36
|
-
const message = await handler(images);
|
|
37
|
-
if (typeof message === "string") {
|
|
38
|
-
vditor.tip.show(message);
|
|
39
|
-
}
|
|
40
|
-
});
|
|
41
|
-
input.click();
|
|
42
|
-
};
|
|
43
|
-
|
|
44
|
-
/** "upload-image"(字串或物件)換成完整的按鈕設定;使用端在物件裡給的欄位優先 */
|
|
45
|
-
export const uploadImageItem = (item, lang) => ({
|
|
46
|
-
name: UPLOAD_IMAGE,
|
|
47
|
-
icon: '<svg><use xlink:href="#vditor-icon-upload"></use></svg>',
|
|
48
|
-
tip: TIPS[lang] || TIPS.en_US,
|
|
49
|
-
click: (event, vditor) => pickImages(vditor),
|
|
50
|
-
...(typeof item === "string" ? {} : item)
|
|
51
|
-
});
|