refactor: api
This commit is contained in:
parent
c072f3a75b
commit
84bdd914bf
|
@ -7,10 +7,8 @@
|
|||
import breaks from '@bytemd/plugin-breaks';
|
||||
import mermaid from '@bytemd/plugin-mermaid';
|
||||
import footnotes from '@bytemd/plugin-footnotes';
|
||||
import importImage from '@bytemd/plugin-import-image';
|
||||
import frontmatter from '@bytemd/plugin-frontmatter';
|
||||
import mediumZoom from '@bytemd/plugin-medium-zoom';
|
||||
import importHtml from '@bytemd/plugin-import-html';
|
||||
|
||||
import en from 'bytemd/lib/locales/en-US';
|
||||
import zh from 'bytemd/lib/locales/zh-CN';
|
||||
|
@ -49,53 +47,24 @@
|
|||
|
||||
let enabled = {
|
||||
breaks: false,
|
||||
footnotes: true,
|
||||
frontmatter: true,
|
||||
gfm: true,
|
||||
highlight: true,
|
||||
math: true,
|
||||
mermaid: true,
|
||||
frontmatter: true,
|
||||
footnotes: true,
|
||||
'import-html': true,
|
||||
'import-image': true,
|
||||
'medium-zoom': true,
|
||||
mermaid: true,
|
||||
};
|
||||
|
||||
$: plugins = [
|
||||
enabled.breaks && breaks(),
|
||||
enabled.footnotes && footnotes(),
|
||||
enabled.frontmatter && frontmatter(),
|
||||
enabled.gfm && gfm({ locale: currentLocale.gfm }),
|
||||
enabled.highlight && highlight(),
|
||||
enabled.math && math({ locale: currentLocale.math }),
|
||||
enabled.mermaid && mermaid({ locale: currentLocale.mermaid }),
|
||||
enabled.footnotes && footnotes(),
|
||||
enabled['import-image'] &&
|
||||
importImage({
|
||||
upload(files) {
|
||||
return Promise.all(
|
||||
files.map((file) => {
|
||||
return ['https://picsum.photos/300'];
|
||||
})
|
||||
);
|
||||
},
|
||||
}),
|
||||
enabled.frontmatter && frontmatter(),
|
||||
enabled['import-html'] && importHtml(),
|
||||
enabled['medium-zoom'] && mediumZoom(),
|
||||
|
||||
// For test:
|
||||
// {
|
||||
// editorEffect(cm, el) {
|
||||
// console.log('on', cm, el);
|
||||
// return () => {
|
||||
// console.log('off', cm, el);
|
||||
// };
|
||||
// },
|
||||
// effect(el, result) {
|
||||
// console.log('on', el, result);
|
||||
// return () => {
|
||||
// console.log('off', el, result);
|
||||
// };
|
||||
// },
|
||||
// },
|
||||
enabled.mermaid && mermaid({ locale: currentLocale.mermaid }),
|
||||
].filter((x) => x);
|
||||
</script>
|
||||
|
||||
|
@ -122,6 +91,14 @@
|
|||
{mode}
|
||||
{plugins}
|
||||
locale={currentLocale.bytemd}
|
||||
uploadImages={(files) => {
|
||||
return Promise.all(
|
||||
files.map((file) => {
|
||||
// TODO:
|
||||
return 'https://picsum.photos/300';
|
||||
})
|
||||
);
|
||||
}}
|
||||
on:change={handleChange}
|
||||
/>
|
||||
</div>
|
||||
|
|
|
@ -149,7 +149,7 @@ editor.on('change', (e) => {
|
|||
| --- | --- | --- |
|
||||
| `value` | `string` (required) | Markdown text |
|
||||
| `plugins` | `BytemdPlugin[]` | ByteMD plugin list |
|
||||
| `sanitize` | `{ allowStyle?: boolean }` or `(schema: Schema) => Schema` | Sanitize strategy |
|
||||
| `sanitize` | `{ allowInlineStyle?: boolean }` or `(schema: Schema) => Schema` | Sanitize strategy |
|
||||
|
||||
### Editor
|
||||
|
||||
|
@ -157,10 +157,11 @@ editor.on('change', (e) => {
|
|||
|
||||
| Key | Type | Description |
|
||||
| --- | --- | --- |
|
||||
| `mode` | `'split'` or `'tab'` | Editor display mode |
|
||||
| `mode` | `split`, `tab`, `auto` | Editor display mode |
|
||||
| `previewDebounce` | `number` | Debounce time (ms) for preview, default: `300` |
|
||||
| `placeholder` | `string` | Editor placeholder |
|
||||
| `editorConfig` | [documentation](https://codemirror.net/doc/manual.html#config) | CodeMirror editor config |
|
||||
| `locale` | i18n locale. Available locales could be found at `bytemd/lib/locales` |
|
||||
|
||||
## Style customization
|
||||
|
||||
|
@ -203,15 +204,12 @@ The 2,5,7 steps are designed for user customization via ByteMD plugin API.
|
|||
| Package | Status | Description |
|
||||
| --- | --- | --- |
|
||||
| [@bytemd/plugin-breaks](./packages/plugin-breaks) | [![npm](https://img.shields.io/npm/v/@bytemd/plugin-breaks.svg)](https://npm.im/@bytemd/plugin-breaks) [![gzip size](https://img.badgesize.io/https://unpkg.com/@bytemd/plugin-breaks/dist/index.min.js?compression=gzip)](https://unpkg.com/@bytemd/plugin-breaks) | Support breaks |
|
||||
| [@bytemd/plugin-external-links](./packages/plugin-external-links) | [![npm](https://img.shields.io/npm/v/@bytemd/plugin-external-links.svg)](https://npm.im/@bytemd/plugin-external-links) [![gzip size](https://img.badgesize.io/https://unpkg.com/@bytemd/plugin-external-links/dist/index.min.js?compression=gzip)](https://unpkg.com/@bytemd/plugin-external-links) | Open external links in new window |
|
||||
| [@bytemd/plugin-footnotes](./packages/plugin-footnotes) | [![npm](https://img.shields.io/npm/v/@bytemd/plugin-footnotes.svg)](https://npm.im/@bytemd/plugin-footnotes) [![gzip size](https://img.badgesize.io/https://unpkg.com/@bytemd/plugin-footnotes/dist/index.min.js?compression=gzip)](https://unpkg.com/@bytemd/plugin-footnotes) | Support footnotes |
|
||||
| [@bytemd/plugin-frontmatter](./packages/plugin-frontmatter) | [![npm](https://img.shields.io/npm/v/@bytemd/plugin-frontmatter.svg)](https://npm.im/@bytemd/plugin-frontmatter) [![gzip size](https://img.badgesize.io/https://unpkg.com/@bytemd/plugin-frontmatter/dist/index.min.js?compression=gzip)](https://unpkg.com/@bytemd/plugin-frontmatter) | Parse frontmatter |
|
||||
| [@bytemd/plugin-gemoji](./packages/plugin-gemoji) | [![npm](https://img.shields.io/npm/v/@bytemd/plugin-gemoji.svg)](https://npm.im/@bytemd/plugin-gemoji) [![gzip size](https://img.badgesize.io/https://unpkg.com/@bytemd/plugin-gemoji/dist/index.min.js?compression=gzip)](https://unpkg.com/@bytemd/plugin-gemoji) | Support Gemoji shortcodes |
|
||||
| [@bytemd/plugin-gfm](./packages/plugin-gfm) | [![npm](https://img.shields.io/npm/v/@bytemd/plugin-gfm.svg)](https://npm.im/@bytemd/plugin-gfm) [![gzip size](https://img.badgesize.io/https://unpkg.com/@bytemd/plugin-gfm/dist/index.min.js?compression=gzip)](https://unpkg.com/@bytemd/plugin-gfm) | Support GFM (autolink literals, strikethrough, tables, tasklists) |
|
||||
| [@bytemd/plugin-highlight](./packages/plugin-highlight) | [![npm](https://img.shields.io/npm/v/@bytemd/plugin-highlight.svg)](https://npm.im/@bytemd/plugin-highlight) [![gzip size](https://img.badgesize.io/https://unpkg.com/@bytemd/plugin-highlight/dist/index.min.js?compression=gzip)](https://unpkg.com/@bytemd/plugin-highlight) | Highlight code blocks |
|
||||
| [@bytemd/plugin-highlight-ssr](./packages/plugin-highlight-ssr) | [![npm](https://img.shields.io/npm/v/@bytemd/plugin-highlight-ssr.svg)](https://npm.im/@bytemd/plugin-highlight-ssr) [![gzip size](https://img.badgesize.io/https://unpkg.com/@bytemd/plugin-highlight-ssr/dist/index.min.js?compression=gzip)](https://unpkg.com/@bytemd/plugin-highlight-ssr) | Highlight code blocks (SSR compatible) |
|
||||
| [@bytemd/plugin-import-html](./packages/plugin-import-html) | [![npm](https://img.shields.io/npm/v/@bytemd/plugin-import-html.svg)](https://npm.im/@bytemd/plugin-import-html) [![gzip size](https://img.badgesize.io/https://unpkg.com/@bytemd/plugin-import-html/dist/index.min.js?compression=gzip)](https://unpkg.com/@bytemd/plugin-import-html) | Import HTML by pasting or dropping |
|
||||
| [@bytemd/plugin-import-image](./packages/plugin-import-image) | [![npm](https://img.shields.io/npm/v/@bytemd/plugin-import-image.svg)](https://npm.im/@bytemd/plugin-import-image) [![gzip size](https://img.badgesize.io/https://unpkg.com/@bytemd/plugin-import-image/dist/index.min.js?compression=gzip)](https://unpkg.com/@bytemd/plugin-import-image) | Import image by pasting or dropping |
|
||||
| [@bytemd/plugin-math](./packages/plugin-math) | [![npm](https://img.shields.io/npm/v/@bytemd/plugin-math.svg)](https://npm.im/@bytemd/plugin-math) [![gzip size](https://img.badgesize.io/https://unpkg.com/@bytemd/plugin-math/dist/index.min.js?compression=gzip)](https://unpkg.com/@bytemd/plugin-math) | Support math formula |
|
||||
| [@bytemd/plugin-math-ssr](./packages/plugin-math-ssr) | [![npm](https://img.shields.io/npm/v/@bytemd/plugin-math-ssr.svg)](https://npm.im/@bytemd/plugin-math-ssr) [![gzip size](https://img.badgesize.io/https://unpkg.com/@bytemd/plugin-math-ssr/dist/index.min.js?compression=gzip)](https://unpkg.com/@bytemd/plugin-math-ssr) | Support math formula (SSR compatible) |
|
||||
| [@bytemd/plugin-medium-zoom](./packages/plugin-medium-zoom) | [![npm](https://img.shields.io/npm/v/@bytemd/plugin-medium-zoom.svg)](https://npm.im/@bytemd/plugin-medium-zoom) [![gzip size](https://img.badgesize.io/https://unpkg.com/@bytemd/plugin-medium-zoom/dist/index.min.js?compression=gzip)](https://unpkg.com/@bytemd/plugin-medium-zoom) | Zoom images like Medium |
|
||||
|
|
|
@ -39,6 +39,7 @@
|
|||
"@types/classnames": "^2.2.11",
|
||||
"@types/lodash-es": "^4.17.4",
|
||||
"classnames": "^2.2.6",
|
||||
"lodash-es": "^4.17.15"
|
||||
"lodash-es": "^4.17.15",
|
||||
"select-files": "^1.0.1"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -10,7 +10,11 @@
|
|||
import Toolbar from './toolbar.svelte';
|
||||
import Viewer from './viewer.svelte';
|
||||
import Toc from './toc.svelte';
|
||||
import { createUtils, findStartIndex, getBuiltinItems } from './editor';
|
||||
import {
|
||||
createEditorUtils,
|
||||
findStartIndex,
|
||||
getBuiltinActions,
|
||||
} from './editor';
|
||||
import Status from './status.svelte';
|
||||
import { icons } from './icons';
|
||||
import enUS from './locales/en-US';
|
||||
|
@ -24,13 +28,14 @@
|
|||
export let placeholder: EditorProps['placeholder'];
|
||||
export let editorConfig: EditorProps['editorConfig'];
|
||||
export let locale: NonNullable<EditorProps['locale']> = enUS;
|
||||
export let uploadImages: EditorProps['uploadImages'];
|
||||
|
||||
const dispatch = createEventDispatcher();
|
||||
|
||||
$: toolbarItems = getBuiltinItems(locale, plugins);
|
||||
$: actions = getBuiltinActions(locale, plugins, uploadImages);
|
||||
$: split = mode === 'split' || (mode === 'auto' && containerWidth >= 800);
|
||||
|
||||
let el: HTMLElement;
|
||||
let root: HTMLElement;
|
||||
let previewEl: HTMLElement;
|
||||
let textarea: HTMLTextAreaElement;
|
||||
let containerWidth = Infinity;
|
||||
|
@ -65,7 +70,7 @@
|
|||
return { edit, preview };
|
||||
})();
|
||||
|
||||
$: context = { editor, $el: el, utils: createUtils(editor) };
|
||||
$: context = { editor, root, ...createEditorUtils(editor) };
|
||||
|
||||
let cbs: ReturnType<NonNullable<BytemdPlugin['editorEffect']>>[] = [];
|
||||
let keyMap: KeyMap = {};
|
||||
|
@ -74,9 +79,9 @@
|
|||
// console.log('on', plugins);
|
||||
cbs = plugins.map((p) => p.editorEffect?.(context));
|
||||
keyMap = {};
|
||||
toolbarItems.forEach((item) => {
|
||||
if (item.shortcut) {
|
||||
keyMap[item.shortcut] = () => item.onClick(context);
|
||||
actions.forEach(({ shortcut, handler }) => {
|
||||
if (shortcut && handler) {
|
||||
keyMap[shortcut] = () => handler(context);
|
||||
}
|
||||
});
|
||||
editor.addKeyMap(keyMap);
|
||||
|
@ -97,7 +102,7 @@
|
|||
editor.setValue(value);
|
||||
}
|
||||
|
||||
$: if (editor && el && plugins && hast) {
|
||||
$: if (editor && root && plugins && hast) {
|
||||
off();
|
||||
tick().then(() => {
|
||||
on();
|
||||
|
@ -243,11 +248,32 @@
|
|||
passive: true,
|
||||
});
|
||||
|
||||
// handle image drop and paste
|
||||
const handleImages = async (itemList: DataTransferItemList | undefined) => {
|
||||
if (!uploadImages) return;
|
||||
const files = Array.from(itemList ?? [])
|
||||
.map((item) => {
|
||||
if (item.type.startsWith('image/')) {
|
||||
return item.getAsFile();
|
||||
}
|
||||
})
|
||||
.filter((f): f is File => f != null);
|
||||
const urls = await uploadImages(files);
|
||||
context.appendBlock(urls.map((url) => `![](${url})`).join('\n\n'));
|
||||
};
|
||||
|
||||
editor.on('drop', async (_, e) => {
|
||||
handleImages(e.dataTransfer?.items);
|
||||
});
|
||||
editor.on('paste', async (_, e) => {
|
||||
handleImages(e.clipboardData?.items);
|
||||
});
|
||||
|
||||
// @ts-ignore
|
||||
new ResizeObserver((entries) => {
|
||||
containerWidth = entries[0].borderBoxSize[0].inlineSize;
|
||||
// console.log(containerWidth);
|
||||
}).observe(el, { box: 'border-box' });
|
||||
}).observe(root, { box: 'border-box' });
|
||||
|
||||
// No need to call `on` because cm instance would change once after init
|
||||
});
|
||||
|
@ -259,7 +285,7 @@
|
|||
'bytemd-mode-split': split,
|
||||
'bytemd-fullscreen': fullscreen,
|
||||
})}
|
||||
bind:this={el}
|
||||
bind:this={root}
|
||||
>
|
||||
<Toolbar
|
||||
{context}
|
||||
|
@ -268,7 +294,7 @@
|
|||
{sidebar}
|
||||
{fullscreen}
|
||||
{locale}
|
||||
{toolbarItems}
|
||||
{actions}
|
||||
on:tab={(e) => {
|
||||
activeTab = e.detail;
|
||||
if (activeTab === 0 && editor) {
|
||||
|
@ -300,10 +326,13 @@
|
|||
}}
|
||||
/>
|
||||
<div class="bytemd-body">
|
||||
<div class="bytemd-editor" style={styles.edit}>
|
||||
<span class="bytemd-editor" style={styles.edit}>
|
||||
<textarea bind:this={textarea} style="display:none" />
|
||||
</div>
|
||||
<div bind:this={previewEl} class="bytemd-preview" style={styles.preview}>
|
||||
</span><span
|
||||
bind:this={previewEl}
|
||||
class="bytemd-preview"
|
||||
style={styles.preview}
|
||||
>
|
||||
<Viewer
|
||||
value={debouncedValue}
|
||||
{plugins}
|
||||
|
@ -312,8 +341,10 @@
|
|||
hast = e.detail;
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
<div class="bytemd-sidebar" style={sidebar ? undefined : 'display:none'}>
|
||||
</span><span
|
||||
class="bytemd-sidebar"
|
||||
style={sidebar ? undefined : 'display:none'}
|
||||
>
|
||||
<div
|
||||
class="bytemd-sidebar-close"
|
||||
on:click={() => {
|
||||
|
@ -323,7 +354,7 @@
|
|||
{@html icons.close}
|
||||
</div>
|
||||
{#if sidebar === 'help'}
|
||||
<Help {locale} {toolbarItems} />
|
||||
<Help {locale} {actions} />
|
||||
{:else if sidebar === 'toc'}
|
||||
<Toc
|
||||
{hast}
|
||||
|
@ -335,7 +366,7 @@
|
|||
}}
|
||||
/>
|
||||
{/if}
|
||||
</div>
|
||||
</span>
|
||||
</div>
|
||||
<Status
|
||||
{locale}
|
||||
|
|
|
@ -1,11 +1,12 @@
|
|||
import type { Editor } from 'codemirror';
|
||||
import type { BytemdPlugin, BytemdToolbarItem } from './types';
|
||||
import type { BytemdPlugin, BytemdAction, EditorProps } from './types';
|
||||
import type { BytemdLocale } from './locales/en-US';
|
||||
import { icons } from './icons';
|
||||
import selectFiles from 'select-files';
|
||||
|
||||
export type EditorUtils = ReturnType<typeof createUtils>;
|
||||
export type EditorUtils = ReturnType<typeof createEditorUtils>;
|
||||
|
||||
export function createUtils(editor: Editor) {
|
||||
export function createEditorUtils(editor: Editor) {
|
||||
return {
|
||||
/**
|
||||
* Wrap text with decorators, for example:
|
||||
|
@ -102,106 +103,117 @@ const getShortcutWithPrefix = (key: string) => {
|
|||
}
|
||||
};
|
||||
|
||||
export function getBuiltinItems(
|
||||
export function getBuiltinActions(
|
||||
locale: BytemdLocale,
|
||||
plugins: BytemdPlugin[]
|
||||
): BytemdToolbarItem[] {
|
||||
const items: BytemdToolbarItem[] = [
|
||||
plugins: BytemdPlugin[],
|
||||
uploadImages: EditorProps['uploadImages']
|
||||
): BytemdAction[] {
|
||||
const items: BytemdAction[] = [
|
||||
{
|
||||
icon: icons.heading,
|
||||
onClick({ utils }) {
|
||||
utils.replaceLines((lines) => lines.map((line) => '# ' + line));
|
||||
},
|
||||
...locale.heading,
|
||||
icon: icons.heading,
|
||||
handler({ replaceLines }) {
|
||||
replaceLines((lines) => lines.map((line) => '# ' + line));
|
||||
},
|
||||
},
|
||||
{
|
||||
...locale.bold,
|
||||
icon: icons.bold,
|
||||
shortcut: getShortcutWithPrefix('B'),
|
||||
onClick({ utils }) {
|
||||
utils.wrapText('**');
|
||||
handler({ wrapText }) {
|
||||
wrapText('**');
|
||||
},
|
||||
...locale.bold,
|
||||
},
|
||||
{
|
||||
...locale.italic,
|
||||
icon: icons.italic,
|
||||
shortcut: getShortcutWithPrefix('I'),
|
||||
onClick({ utils }) {
|
||||
utils.wrapText('_');
|
||||
handler({ wrapText }) {
|
||||
wrapText('_');
|
||||
},
|
||||
...locale.italic,
|
||||
},
|
||||
{
|
||||
icon: icons.quote,
|
||||
onClick({ utils }) {
|
||||
utils.replaceLines((lines) => lines.map((line) => '> ' + line));
|
||||
},
|
||||
...locale.quote,
|
||||
icon: icons.quote,
|
||||
handler({ replaceLines }) {
|
||||
replaceLines((lines) => lines.map((line) => '> ' + line));
|
||||
},
|
||||
},
|
||||
{
|
||||
...locale.link,
|
||||
icon: icons.link,
|
||||
shortcut: getShortcutWithPrefix('K'),
|
||||
onClick({ editor, utils }) {
|
||||
handler({ editor, wrapText }) {
|
||||
if (editor.somethingSelected()) {
|
||||
utils.wrapText('[', '](url)');
|
||||
wrapText('[', '](url)');
|
||||
const cursor = editor.getCursor();
|
||||
editor.setSelection(
|
||||
{ line: cursor.line, ch: cursor.ch + 2 },
|
||||
{ line: cursor.line, ch: cursor.ch + 5 }
|
||||
);
|
||||
} else {
|
||||
utils.wrapText('[', '](url)');
|
||||
wrapText('[', '](url)');
|
||||
}
|
||||
},
|
||||
...locale.link,
|
||||
},
|
||||
{
|
||||
icon: icons.code,
|
||||
onClick({ utils }) {
|
||||
utils.wrapText('`');
|
||||
},
|
||||
...locale.image,
|
||||
icon: icons.image,
|
||||
handler: uploadImages
|
||||
? async ({ appendBlock }) => {
|
||||
const fileList = await selectFiles({
|
||||
accept: 'image/*',
|
||||
multiple: true,
|
||||
});
|
||||
const files = Array.from(fileList ?? []);
|
||||
const urls = await uploadImages(files);
|
||||
appendBlock(urls.map((url) => `![](${url})`).join('\n\n'));
|
||||
}
|
||||
: undefined,
|
||||
},
|
||||
{
|
||||
...locale.code,
|
||||
icon: icons.code,
|
||||
handler({ wrapText }) {
|
||||
wrapText('`');
|
||||
},
|
||||
},
|
||||
{
|
||||
...locale.pre,
|
||||
icon: icons.codeBlock,
|
||||
onClick({ editor, utils }) {
|
||||
const { startLine } = utils.appendBlock('```js\n```');
|
||||
handler({ editor, appendBlock }) {
|
||||
const { startLine } = appendBlock('```js\n```');
|
||||
editor.setSelection(
|
||||
{ line: startLine, ch: 3 },
|
||||
{ line: startLine, ch: 5 }
|
||||
);
|
||||
},
|
||||
...locale.pre,
|
||||
},
|
||||
{
|
||||
icon: icons.ul,
|
||||
onClick({ utils }) {
|
||||
utils.replaceLines((lines) => lines.map((line) => '- ' + line));
|
||||
},
|
||||
...locale.ul,
|
||||
icon: icons.ul,
|
||||
handler({ replaceLines }) {
|
||||
replaceLines((lines) => lines.map((line) => '- ' + line));
|
||||
},
|
||||
},
|
||||
{
|
||||
icon: icons.ol,
|
||||
onClick({ utils }) {
|
||||
utils.replaceLines((lines) =>
|
||||
lines.map((line, i) => `${i + 1}. ${line}`)
|
||||
);
|
||||
},
|
||||
...locale.ol,
|
||||
icon: icons.ol,
|
||||
handler({ replaceLines }) {
|
||||
replaceLines((lines) => lines.map((line, i) => `${i + 1}. ${line}`));
|
||||
},
|
||||
},
|
||||
{
|
||||
icon: icons.hr,
|
||||
onClick({ utils }) {
|
||||
utils.appendBlock('---');
|
||||
},
|
||||
...locale.hr,
|
||||
icon: icons.hr,
|
||||
},
|
||||
];
|
||||
|
||||
plugins.forEach((p) => {
|
||||
if (Array.isArray(p.toolbar)) {
|
||||
items.push(...p.toolbar);
|
||||
} else if (p.toolbar) {
|
||||
items.push(p.toolbar);
|
||||
if (Array.isArray(p.action)) {
|
||||
items.push(...p.action);
|
||||
} else if (p.action) {
|
||||
items.push(p.action);
|
||||
}
|
||||
});
|
||||
return items;
|
||||
|
|
|
@ -1,14 +1,15 @@
|
|||
<script lang="ts">
|
||||
import type { BytemdLocale } from './locales/en-US';
|
||||
import type { BytemdToolbarItem } from './types';
|
||||
export let toolbarItems: BytemdToolbarItem[];
|
||||
import type { BytemdAction } from './types';
|
||||
|
||||
export let actions: BytemdAction[];
|
||||
export let locale: BytemdLocale;
|
||||
</script>
|
||||
|
||||
<div class="bytemd-help">
|
||||
<h2>{locale.sidebar.cheatsheet}</h2>
|
||||
<ul>
|
||||
{#each toolbarItems as item}
|
||||
{#each actions as item}
|
||||
{#if item.cheatsheet}
|
||||
<li>
|
||||
<span class="bytemd-help-icon">{@html item.icon}</span><span
|
||||
|
@ -22,7 +23,7 @@
|
|||
</ul>
|
||||
<h2>{locale.sidebar.shortcuts}</h2>
|
||||
<ul>
|
||||
{#each toolbarItems as item}
|
||||
{#each actions as item}
|
||||
{#if item.shortcut}
|
||||
<li>
|
||||
<span class="bytemd-help-icon">{@html item.icon}</span><span
|
||||
|
|
|
@ -2,6 +2,10 @@
|
|||
export const icons = {
|
||||
heading:
|
||||
'<svg width="16" height="16" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M3 1v13.33M12.997 1v13.33M3 7.665h9.998" stroke="currentColor" stroke-width="1.33" stroke-linecap="round" stroke-linejoin="round"/></svg>',
|
||||
h1:
|
||||
'<svg width="1em" height="1em" viewBox="0 0 48 48" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M6 8v32M25 8v32M6 24h19M34.226 24L39 19.017V40" stroke="currentColor" stroke-width="4" stroke-linecap="round" stroke-linejoin="round"/></svg>',
|
||||
h2:
|
||||
'<svg width="1em" height="1em" viewBox="0 0 48 48" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M6 8v32M24 8v32M7 24h16M32 25c0-3.167 2.667-5 5-5s5 1.833 5 5c0 5.7-10 9.933-10 15h10" stroke="currentColor" stroke-width="4" stroke-linecap="round" stroke-linejoin="round"/></svg>',
|
||||
bold:
|
||||
'<svg width="1em" height="1em" viewBox="0 0 48 48" fill="none" xmlns="http://www.w3.org/2000/svg"><path clip-rule="evenodd" d="M24 24c5.506 0 9.969-4.477 9.969-10S29.506 4 24 4H11v20h13zM28.031 44C33.537 44 38 39.523 38 34s-4.463-10-9.969-10H11v20h17.031z" stroke="currentColor" stroke-width="4" stroke-linecap="round" stroke-linejoin="round"/></svg>',
|
||||
italic:
|
||||
|
@ -10,6 +14,8 @@ export const icons = {
|
|||
'<svg width="1em" height="1em" viewBox="0 0 48 48" fill="none" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" clip-rule="evenodd" d="M18.853 9.116c-7.53 4.836-11.714 10.465-12.55 16.887C5 36 13.94 40.893 18.47 36.497 23 32.1 20.285 26.52 17.005 24.994c-3.28-1.525-5.286-.994-4.936-3.032.35-2.039 5.016-7.69 9.116-10.323a.749.749 0 00.114-1.02L20.285 9.3c-.44-.572-.862-.55-1.432-.185zM38.679 9.116c-7.53 4.836-11.714 10.465-12.55 16.887-1.303 9.997 7.637 14.89 12.167 10.494 4.53-4.397 1.815-9.977-1.466-11.503-3.28-1.525-5.286-.994-4.936-3.032.35-2.039 5.017-7.69 9.117-10.323a.749.749 0 00.113-1.02L40.11 9.3c-.44-.572-.862-.55-1.431-.185z" fill="currentColor"/></svg>',
|
||||
link:
|
||||
'<svg width="1em" height="1em" viewBox="0 0 48 48" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M26.24 16.373l-9.14-9.14c-2.661-2.661-7.035-2.603-9.769.131-2.733 2.734-2.792 7.107-.13 9.768l7.935 7.936M32.903 23.003l7.935 7.936c2.661 2.66 2.603 7.034-.13 9.768-2.735 2.734-7.108 2.792-9.77.131l-9.14-9.14M26.11 26.142c2.733-2.734 2.791-7.108.13-9.769M21.799 21.799c-2.734 2.733-2.793 7.107-.131 9.768" stroke="currentColor" stroke-width="4" stroke-linecap="round" stroke-linejoin="round"/></svg>',
|
||||
image:
|
||||
'<svg width="1em" height="1em" viewBox="0 0 48 48" xmlns="http://www.w3.org/2000/svg"><path fill="#fff" fill-opacity=".01" fill-rule="evenodd" d="M0 0h48v48H0z"/><g transform="translate(5 8)" stroke-width="4" stroke-linejoin="round" stroke="currentColor" fill="none"><path d="M2 0h34a2 2 0 012 2v28a2 2 0 01-2 2H2a2 2 0 01-2-2V2a2 2 0 012-2z" stroke-linecap="round"/><circle stroke-linecap="round" cx="9.5" cy="8.5" r="1.5"/><path d="M10 16l5 4 6-7 17 13v4a2 2 0 01-2 2H2a2 2 0 01-2-2v-4l10-10z"/></g></svg>',
|
||||
code:
|
||||
'<svg width="1em" height="1em" viewBox="0 0 48 48" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M16 13L4 25.432 16 37M32 13l12 12.432L32 37" stroke="currentColor" stroke-width="4" stroke-linecap="round" stroke-linejoin="round"/><path d="M28 4l-7 40" stroke="currentColor" stroke-width="4" stroke-linecap="round"/></svg>',
|
||||
codeBlock:
|
||||
|
|
|
@ -9,7 +9,7 @@ const locale = {
|
|||
},
|
||||
sidebar: {
|
||||
toc: 'Table of contents',
|
||||
cheatsheet: 'Cheat Sheet',
|
||||
cheatsheet: 'Markdown Cheat Sheet',
|
||||
shortcuts: 'Shortcuts',
|
||||
},
|
||||
status: {
|
||||
|
@ -38,6 +38,10 @@ const locale = {
|
|||
title: 'Link',
|
||||
cheatsheet: '[text](url)',
|
||||
},
|
||||
image: {
|
||||
title: 'Image',
|
||||
cheatsheet: '![alt](url)',
|
||||
},
|
||||
code: {
|
||||
title: 'Code',
|
||||
cheatsheet: '`code`',
|
||||
|
|
|
@ -38,7 +38,11 @@ const locale: BytemdLocale = {
|
|||
},
|
||||
link: {
|
||||
title: '链接',
|
||||
cheatsheet: '[链接](url)',
|
||||
cheatsheet: '[文本](url)',
|
||||
},
|
||||
image: {
|
||||
title: '图片',
|
||||
cheatsheet: '![alt](url)',
|
||||
},
|
||||
code: {
|
||||
title: '代码',
|
||||
|
|
|
@ -28,14 +28,12 @@
|
|||
let el: HTMLElement;
|
||||
export let tooltip: string;
|
||||
export let icon: string;
|
||||
export let style: string | undefined;
|
||||
export let active: boolean;
|
||||
</script>
|
||||
|
||||
<span
|
||||
bind:this={el}
|
||||
on:click={() => dispatch('click')}
|
||||
{style}
|
||||
class="bytemd-toolbar-icon"
|
||||
class:bytemd-toolbar-icon-active={active}
|
||||
>
|
||||
|
|
|
@ -4,77 +4,77 @@
|
|||
import { createEventDispatcher } from 'svelte';
|
||||
import ToolbarButton from './toolbar-button.svelte';
|
||||
import { capitalize } from 'lodash-es';
|
||||
import type { EditorProps, EditorContext, BytemdToolbarItem } from './types';
|
||||
import type { EditorProps, BytemdEditorContext, BytemdAction } from './types';
|
||||
import { icons } from './icons';
|
||||
|
||||
const dispatch = createEventDispatcher();
|
||||
|
||||
export let context: EditorContext;
|
||||
export let context: BytemdEditorContext;
|
||||
export let split: boolean;
|
||||
export let activeTab: number;
|
||||
export let fullscreen: boolean;
|
||||
export let sidebar: false | 'help' | 'toc';
|
||||
export let locale: NonNullable<EditorProps['locale']>;
|
||||
export let toolbarItems: BytemdToolbarItem[];
|
||||
export let actions: BytemdAction[];
|
||||
</script>
|
||||
|
||||
<div class="bytemd-toolbar">
|
||||
{#if !split}
|
||||
<div class="bytemd-tabs">
|
||||
<div class="bytemd-toolbar-left">
|
||||
{#if split}
|
||||
{#each actions as item}
|
||||
{#if item.handler}
|
||||
<ToolbarButton
|
||||
tooltip={item.title + (item.shortcut ? ` <${item.shortcut}>` : '')}
|
||||
icon={item.icon}
|
||||
active={false}
|
||||
on:click={() => item.handler && item.handler(context)}
|
||||
/>
|
||||
{/if}
|
||||
{/each}
|
||||
{:else}
|
||||
<span
|
||||
on:click={() => dispatch('tab', 0)}
|
||||
class:bytemd-tab-active={activeTab === 0}>{locale.toolbar.write}</span
|
||||
class="bytemd-toolbar-tab"
|
||||
class:bytemd-toolbar-tab-active={activeTab === 0}
|
||||
>{locale.toolbar.write}</span
|
||||
><span
|
||||
on:click={() => dispatch('tab', 1)}
|
||||
class:bytemd-tab-active={activeTab === 1}
|
||||
class="bytemd-toolbar-tab"
|
||||
class:bytemd-toolbar-tab-active={activeTab === 1}
|
||||
>{capitalize(locale.toolbar.preview)}</span
|
||||
>
|
||||
</div>
|
||||
{/if}
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
{#if split || activeTab === 0}
|
||||
{#each toolbarItems as item}
|
||||
<ToolbarButton
|
||||
tooltip={item.title}
|
||||
icon={item.icon}
|
||||
style={undefined}
|
||||
active={false}
|
||||
on:click={() => item.onClick(context)}
|
||||
/>
|
||||
{/each}
|
||||
{/if}
|
||||
|
||||
<ToolbarButton
|
||||
tooltip={locale.toolbar.about}
|
||||
icon={icons.info}
|
||||
style="float:right"
|
||||
active={false}
|
||||
on:click={() => {
|
||||
window.open('https://github.com/bytedance/bytemd');
|
||||
}}
|
||||
/><ToolbarButton
|
||||
tooltip={locale.toolbar.fullscreen}
|
||||
icon={fullscreen ? icons.fullscreenOff : icons.fullscreenOn}
|
||||
style="float:right"
|
||||
active={false}
|
||||
on:click={() => {
|
||||
dispatch('click', 'fullscreen');
|
||||
}}
|
||||
/><ToolbarButton
|
||||
tooltip={locale.toolbar.help}
|
||||
icon={icons.help}
|
||||
style="float:right"
|
||||
active={sidebar === 'help'}
|
||||
on:click={() => {
|
||||
dispatch('click', 'help');
|
||||
}}
|
||||
/><ToolbarButton
|
||||
tooltip={locale.toolbar.toc}
|
||||
icon={icons.toc}
|
||||
style="float:right"
|
||||
active={sidebar === 'toc'}
|
||||
on:click={() => {
|
||||
dispatch('click', 'toc');
|
||||
}}
|
||||
/>
|
||||
<div class="bytemd-toolbar-right">
|
||||
<ToolbarButton
|
||||
tooltip={locale.toolbar.toc}
|
||||
icon={icons.toc}
|
||||
active={sidebar === 'toc'}
|
||||
on:click={() => {
|
||||
dispatch('click', 'toc');
|
||||
}}
|
||||
/><ToolbarButton
|
||||
tooltip={locale.toolbar.help}
|
||||
icon={icons.help}
|
||||
active={sidebar === 'help'}
|
||||
on:click={() => {
|
||||
dispatch('click', 'help');
|
||||
}}
|
||||
/><ToolbarButton
|
||||
tooltip={locale.toolbar.fullscreen}
|
||||
icon={fullscreen ? icons.fullscreenOff : icons.fullscreenOn}
|
||||
active={false}
|
||||
on:click={() => {
|
||||
dispatch('click', 'fullscreen');
|
||||
}}
|
||||
/><ToolbarButton
|
||||
tooltip={locale.toolbar.about}
|
||||
icon={icons.info}
|
||||
active={false}
|
||||
on:click={() => {
|
||||
window.open('https://github.com/bytedance/bytemd');
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -5,50 +5,55 @@ import type { Editor, EditorConfiguration } from 'codemirror';
|
|||
import type { EditorUtils } from './editor';
|
||||
import type { BytemdLocale } from './locales/en-US';
|
||||
|
||||
export interface EditorContext {
|
||||
export interface BytemdContext {
|
||||
/**
|
||||
* The root element of the viewer
|
||||
*/
|
||||
markdownBody: HTMLElement;
|
||||
/**
|
||||
* Virtual file format used in [unified](https://unifiedjs.com/)
|
||||
*
|
||||
* Get the HTML output by calling `vfile.toString()`
|
||||
*/
|
||||
vfile: VFile;
|
||||
}
|
||||
|
||||
export interface BytemdEditorContext extends EditorUtils {
|
||||
/**
|
||||
* CodeMirror editor instance
|
||||
*/
|
||||
editor: Editor;
|
||||
/**
|
||||
* Root element, `$('.bytemd')`
|
||||
* The root element
|
||||
*/
|
||||
$el: HTMLElement;
|
||||
/**
|
||||
* Utilities for Editor
|
||||
*/
|
||||
utils: EditorUtils;
|
||||
root: HTMLElement;
|
||||
}
|
||||
|
||||
export interface ViewerContext {
|
||||
export interface BytemdAction {
|
||||
/**
|
||||
* Root element of the Viewer, `$('.markdown-body')`
|
||||
*/
|
||||
$el: HTMLElement;
|
||||
vfile: VFile;
|
||||
}
|
||||
|
||||
export interface BytemdToolbarItem {
|
||||
/**
|
||||
* Toolbar Icon (16x16), could be <img> or inline svg
|
||||
* Action icon (16x16), could be <img> or inline svg
|
||||
*/
|
||||
icon: string;
|
||||
/**
|
||||
* Tooltip of toolbar item
|
||||
* Action title
|
||||
*/
|
||||
title: string;
|
||||
/**
|
||||
* Toolbar icon click handler
|
||||
* Action handler, used for toolbar icon click and shortcut trigger
|
||||
*/
|
||||
onClick(context: EditorContext): void;
|
||||
handler?(context: BytemdEditorContext): void;
|
||||
/**
|
||||
* If specified, this record will be added to the Markdown cheat sheet
|
||||
* Markdown syntax cheat sheet
|
||||
*
|
||||
* If specified, this record will be added to the Markdown cheat sheet section
|
||||
*/
|
||||
cheatsheet?: string;
|
||||
/**
|
||||
* shortcut handler
|
||||
* Keyboard shortcut
|
||||
*
|
||||
* If specified, this record will be added to the Keyboard shortcut
|
||||
* If specified, this record will be added to the Keyboard shortcut section
|
||||
*
|
||||
* https://codemirror.net/doc/manual.html#keymaps
|
||||
*/
|
||||
shortcut?: string;
|
||||
}
|
||||
|
@ -67,17 +72,17 @@ export interface BytemdPlugin {
|
|||
*/
|
||||
rehype?: (p: Processor) => Processor;
|
||||
/**
|
||||
* Register toolbar items
|
||||
* Side effect for viewer, triggers when viewer props changes
|
||||
*/
|
||||
toolbar?: BytemdToolbarItem | BytemdToolbarItem[];
|
||||
effect?(context: BytemdContext): void | (() => void);
|
||||
/**
|
||||
* Side effect for editor, triggers when plugin list changes
|
||||
* Register actions in toolbar, cheatsheet and shortcuts
|
||||
*/
|
||||
editorEffect?(context: EditorContext): void | (() => void);
|
||||
action?: BytemdAction | BytemdAction[];
|
||||
/**
|
||||
* Side effect for viewer, triggers when HTML or plugin list changes
|
||||
* Side effect for editor, triggers when editor props changes
|
||||
*/
|
||||
effect?(context: ViewerContext): void | (() => void);
|
||||
editorEffect?(context: BytemdEditorContext): void | (() => void);
|
||||
}
|
||||
|
||||
export interface EditorProps extends ViewerProps {
|
||||
|
@ -108,9 +113,13 @@ export interface EditorProps extends ViewerProps {
|
|||
*/
|
||||
editorConfig?: Omit<EditorConfiguration, 'value' | 'mode' | 'placeholder'>;
|
||||
/**
|
||||
* Locale
|
||||
* i18n locale
|
||||
*/
|
||||
locale?: BytemdLocale;
|
||||
/**
|
||||
* Handle image uplodaer
|
||||
*/
|
||||
uploadImages?(files: File[]): Promise<string[]>;
|
||||
}
|
||||
|
||||
export interface ViewerProps {
|
||||
|
@ -134,7 +143,7 @@ export interface ViewerProps {
|
|||
/**
|
||||
* Allow inline styles. Default: `false`
|
||||
*/
|
||||
allowStyle?: boolean;
|
||||
allowInlineStyle?: boolean;
|
||||
}
|
||||
| ((schema: Schema) => Schema);
|
||||
}
|
||||
|
|
|
@ -30,7 +30,7 @@ export function getProcessor({
|
|||
|
||||
if (typeof sanitize === 'function') {
|
||||
schema = sanitize(schema);
|
||||
} else if (sanitize?.allowStyle) {
|
||||
} else if (sanitize?.allowInlineStyle) {
|
||||
schema.attributes!['*'].push('style');
|
||||
}
|
||||
|
||||
|
|
|
@ -21,12 +21,12 @@
|
|||
return h;
|
||||
}
|
||||
|
||||
let el: HTMLElement;
|
||||
let markdownBody: HTMLElement;
|
||||
let cbs: ReturnType<NonNullable<BytemdPlugin['effect']>>[] = [];
|
||||
|
||||
function on() {
|
||||
// console.log('von');
|
||||
cbs = plugins.map((p) => p.effect?.({ $el: el, vfile }));
|
||||
cbs = plugins.map((p) => p.effect?.({ markdownBody, vfile }));
|
||||
}
|
||||
function off() {
|
||||
// console.log('voff');
|
||||
|
@ -34,14 +34,16 @@
|
|||
}
|
||||
|
||||
onMount(() => {
|
||||
el.addEventListener('click', (e) => {
|
||||
markdownBody.addEventListener('click', (e) => {
|
||||
const $ = e.target as HTMLElement;
|
||||
if ($.tagName !== 'A') return;
|
||||
|
||||
const href = $.getAttribute('href');
|
||||
if (!href?.startsWith('#')) return;
|
||||
|
||||
el.querySelector('#user-content-' + href.slice(1))?.scrollIntoView();
|
||||
markdownBody
|
||||
.querySelector('#user-content-' + href.slice(1))
|
||||
?.scrollIntoView();
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -75,6 +77,6 @@
|
|||
$: html = `<!--${hashCode(value)}-->${vfile.toString()}`; // trigger re-render every time the value changes
|
||||
</script>
|
||||
|
||||
<div bind:this={el} class="markdown-body">
|
||||
<div bind:this={markdownBody} class="markdown-body">
|
||||
{@html html}
|
||||
</div>
|
||||
|
|
|
@ -17,11 +17,32 @@ $sidebar-width: 280px;
|
|||
&-toolbar {
|
||||
padding: 4px 12px;
|
||||
border-bottom: 1px solid $border-color;
|
||||
font-size: 0;
|
||||
background-color: $gray-000;
|
||||
user-select: none;
|
||||
overflow: hidden;
|
||||
|
||||
&-left {
|
||||
float: left;
|
||||
}
|
||||
|
||||
&-right {
|
||||
float: right;
|
||||
}
|
||||
|
||||
&-tab {
|
||||
cursor: pointer;
|
||||
padding-left: 8px;
|
||||
padding-right: 8px;
|
||||
line-height: 24px;
|
||||
font-size: 14px;
|
||||
&-active {
|
||||
color: $primary;
|
||||
}
|
||||
}
|
||||
|
||||
&-icon {
|
||||
display: inline-block;
|
||||
vertical-align: top;
|
||||
cursor: pointer;
|
||||
border-radius: 4px;
|
||||
margin-left: 6px;
|
||||
|
@ -40,29 +61,14 @@ $sidebar-width: 280px;
|
|||
}
|
||||
}
|
||||
|
||||
&-tabs {
|
||||
display: inline-block;
|
||||
vertical-align: top;
|
||||
span {
|
||||
cursor: pointer;
|
||||
padding-left: 8px;
|
||||
padding-right: 8px;
|
||||
line-height: 24px;
|
||||
font-size: 14px;
|
||||
&.bytemd-tab-active {
|
||||
color: $primary;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&-body {
|
||||
height: calc(100% - 33px - 25px);
|
||||
overflow: auto;
|
||||
font-size: 0;
|
||||
}
|
||||
|
||||
&-editor {
|
||||
display: inline-block;
|
||||
vertical-align: top;
|
||||
height: 100%;
|
||||
overflow: hidden;
|
||||
.CodeMirror {
|
||||
|
@ -86,6 +92,7 @@ $sidebar-width: 280px;
|
|||
|
||||
&-preview {
|
||||
display: inline-block;
|
||||
vertical-align: top;
|
||||
height: 100%;
|
||||
overflow: auto;
|
||||
.markdown-body {
|
||||
|
@ -95,6 +102,7 @@ $sidebar-width: 280px;
|
|||
|
||||
&-sidebar {
|
||||
display: inline-block;
|
||||
vertical-align: top; // Safari
|
||||
height: 100%;
|
||||
overflow: auto;
|
||||
font-size: 16px;
|
||||
|
@ -103,7 +111,6 @@ $sidebar-width: 280px;
|
|||
width: $sidebar-width;
|
||||
position: relative;
|
||||
padding: 16px;
|
||||
vertical-align: top; // Safari
|
||||
&-close {
|
||||
position: absolute;
|
||||
padding: 16px;
|
||||
|
@ -128,21 +135,24 @@ $sidebar-width: 280px;
|
|||
&-help {
|
||||
ul {
|
||||
line-height: 20px;
|
||||
font-size: 0;
|
||||
svg {
|
||||
font-size: 16px;
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
display: block;
|
||||
}
|
||||
span {
|
||||
font-size: 13px;
|
||||
display: inline-block;
|
||||
vertical-align: middle;
|
||||
vertical-align: top;
|
||||
}
|
||||
}
|
||||
li {
|
||||
list-style: none;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
&-icon {
|
||||
padding: 2px 0;
|
||||
}
|
||||
&-title {
|
||||
padding-left: 8px;
|
||||
}
|
||||
|
|
|
@ -1,26 +0,0 @@
|
|||
# @bytemd/plugin-external-links
|
||||
|
||||
[![npm](https://img.shields.io/npm/v/@bytemd/plugin-external-links.svg)](https://npm.im/@bytemd/plugin-external-links)
|
||||
|
||||
ByteMD plugin to open external links in new window
|
||||
|
||||
## Usage
|
||||
|
||||
```js
|
||||
import { Editor } from 'bytemd';
|
||||
import externalLinks from '@bytemd/plugin-external-links';
|
||||
|
||||
new Editor({
|
||||
target: document.body,
|
||||
props: {
|
||||
plugins: [
|
||||
externalLinks(),
|
||||
// ... other plugins
|
||||
],
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
## License
|
||||
|
||||
MIT
|
|
@ -1,30 +0,0 @@
|
|||
{
|
||||
"name": "@bytemd/plugin-external-links",
|
||||
"version": "1.3.0",
|
||||
"description": "ByteMD plugin to open external links in new window",
|
||||
"author": "Rongjian Zhang <pd4d10@gmail.com>",
|
||||
"license": "MIT",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/bytedance/bytemd.git",
|
||||
"directory": "packages/plugin-external-links"
|
||||
},
|
||||
"main": "dist/index.cjs.js",
|
||||
"module": "dist/index.esm.js",
|
||||
"unpkg": "dist/index.min.js",
|
||||
"jsdelivr": "dist/index.min.js",
|
||||
"types": "lib/index.d.ts",
|
||||
"files": [
|
||||
"dist",
|
||||
"lib"
|
||||
],
|
||||
"peerDependencies": {
|
||||
"bytemd": "*"
|
||||
},
|
||||
"dependencies": {
|
||||
"unist-util-visit": "^2.0.0"
|
||||
},
|
||||
"publishConfig": {
|
||||
"access": "public"
|
||||
}
|
||||
}
|
|
@ -1,46 +0,0 @@
|
|||
import type { BytemdPlugin } from 'bytemd';
|
||||
import type { Element } from 'hast';
|
||||
import visit from 'unist-util-visit';
|
||||
|
||||
type AnchorProps = Partial<Omit<HTMLAnchorElement, 'href'>>;
|
||||
|
||||
export interface ExternalLinksOptions {
|
||||
/**
|
||||
* Test if it is an external url
|
||||
*/
|
||||
test(href: string): boolean;
|
||||
/**
|
||||
* Internal links props
|
||||
*/
|
||||
internalProps?: AnchorProps;
|
||||
/**
|
||||
* External links props
|
||||
*/
|
||||
externalProps?: AnchorProps;
|
||||
}
|
||||
|
||||
export default function externalLinks({
|
||||
test,
|
||||
internalProps = {},
|
||||
externalProps = {
|
||||
target: '_blank',
|
||||
rel: 'nofollow noopener noreferrer',
|
||||
},
|
||||
}: ExternalLinksOptions): BytemdPlugin {
|
||||
return {
|
||||
rehype: (p) =>
|
||||
p.use(() => (tree) => {
|
||||
visit<Element>(tree, 'element', (node) => {
|
||||
if (node.tagName !== 'a' || !node.properties?.href) return;
|
||||
|
||||
const href = node.properties.href as string;
|
||||
if (!/https?:\/\//.test(href)) return; // only handle http and https
|
||||
|
||||
Object.assign(
|
||||
node.properties,
|
||||
test(href) ? externalProps : internalProps
|
||||
);
|
||||
});
|
||||
}),
|
||||
};
|
||||
}
|
|
@ -3,7 +3,7 @@ export const icons = {
|
|||
strikethrough:
|
||||
'<svg width="1em" height="1em" viewBox="0 0 48 48" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M5 24h38M24 24c16 6 10 20 0 20s-12-8-12-8M36 12s-3-8-12-8-12.564 7.6-8.39 14" stroke="currentColor" stroke-width="4" stroke-linecap="round" stroke-linejoin="round"/><path d="M12 36s4 8 12 8 12.564-7.6 8.39-14" stroke="currentColor" stroke-width="4" stroke-linecap="round" stroke-linejoin="round"/></svg>',
|
||||
task:
|
||||
'<svg width="1em" height="1em" viewBox="0 0 48 48" fill="none" xmlns="http://www.w3.org/2000/svg"><g clip-path="url(#icon-05f6552097271bd)" stroke="currentColor" stroke-width="4" stroke-linecap="round" stroke-linejoin="round"><path d="M42 20v19a3 3 0 01-3 3H9a3 3 0 01-3-3V9a3 3 0 013-3h21"/><path d="M16 20l10 8L41 7"/></g><defs><clipPath id="icon-05f6552097271bd"><path fill="currentColor" d="M0 0h48v48H0z"/></clipPath></defs></svg>',
|
||||
'<svg width="1em" height="1em" viewBox="0 0 48 48" fill="none" xmlns="http://www.w3.org/2000/svg"><g clip-path="url(#icon-5e4ef1fb097271bd)" stroke="currentColor" stroke-width="4" stroke-linecap="round" stroke-linejoin="round"><path d="M42 20v19a3 3 0 01-3 3H9a3 3 0 01-3-3V9a3 3 0 013-3h21"/><path d="M16 20l10 8L41 7"/></g><defs><clipPath id="icon-5e4ef1fb097271bd"><path fill="currentColor" d="M0 0h48v48H0z"/></clipPath></defs></svg>',
|
||||
table:
|
||||
'<svg width="1em" height="1em" viewBox="0 0 48 48" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M40.15 5H7.85A2.85 2.85 0 005 7.85v32.3A2.85 2.85 0 007.85 43h32.3A2.85 2.85 0 0043 40.15V7.85A2.85 2.85 0 0040.15 5z" stroke="currentColor" stroke-width="4"/><path d="M17 5v38M31 5v38M5 17h38M5 31h38" stroke="currentColor" stroke-width="4" stroke-linecap="round"/></svg>',
|
||||
};
|
||||
|
|
|
@ -14,25 +14,26 @@ export default function gfm({
|
|||
}: BytemdPluginGfmOptions = {}): BytemdPlugin {
|
||||
return {
|
||||
remark: (p) => p.use(remarkGfm, remarkGfmOptions),
|
||||
toolbar: [
|
||||
action: [
|
||||
{
|
||||
icon: icons.strikethrough,
|
||||
onClick({ utils }) {
|
||||
utils.wrapText('~~');
|
||||
},
|
||||
...locale.strike,
|
||||
},
|
||||
{
|
||||
icon: icons.task,
|
||||
onClick({ utils }) {
|
||||
utils.replaceLines((lines) => lines.map((line) => '- [ ] ' + line));
|
||||
icon: icons.strikethrough,
|
||||
handler({ wrapText }) {
|
||||
wrapText('~~');
|
||||
},
|
||||
...locale.task,
|
||||
},
|
||||
{
|
||||
...locale.task,
|
||||
icon: icons.task,
|
||||
handler({ replaceLines }) {
|
||||
replaceLines((lines) => lines.map((line) => '- [ ] ' + line));
|
||||
},
|
||||
},
|
||||
{
|
||||
...locale.table,
|
||||
icon: icons.table,
|
||||
onClick({ editor, utils }) {
|
||||
const { startLine } = utils.appendBlock(
|
||||
handler({ editor, appendBlock }) {
|
||||
const { startLine } = appendBlock(
|
||||
'| heading | |\n| --- | --- |\n| | |\n'
|
||||
);
|
||||
editor.setSelection(
|
||||
|
@ -40,7 +41,6 @@ export default function gfm({
|
|||
{ line: startLine, ch: 9 }
|
||||
);
|
||||
},
|
||||
...locale.table,
|
||||
},
|
||||
],
|
||||
};
|
||||
|
|
|
@ -10,9 +10,9 @@ export default function highlight({
|
|||
}: BytemdPluginHighlightOptions = {}): BytemdPlugin {
|
||||
let hljs: typeof H;
|
||||
return {
|
||||
effect({ $el }) {
|
||||
effect({ markdownBody }) {
|
||||
(async () => {
|
||||
const els = $el.querySelectorAll<HTMLElement>('pre>code');
|
||||
const els = markdownBody.querySelectorAll<HTMLElement>('pre>code');
|
||||
if (els.length === 0) return;
|
||||
|
||||
if (!hljs) {
|
||||
|
|
|
@ -1,26 +0,0 @@
|
|||
# @bytemd/plugin-import-html
|
||||
|
||||
[![npm](https://img.shields.io/npm/v/@bytemd/plugin-import-html.svg)](https://npm.im/@bytemd/plugin-import-html)
|
||||
|
||||
ByteMD plugin to import HTML by pasting or dropping
|
||||
|
||||
## Usage
|
||||
|
||||
```js
|
||||
import { Editor } from 'bytemd';
|
||||
import importHtml from '@bytemd/plugin-import-html';
|
||||
|
||||
new Editor({
|
||||
target: document.body,
|
||||
props: {
|
||||
plugins: [
|
||||
importHtml(),
|
||||
// ... other plugins
|
||||
],
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
## License
|
||||
|
||||
MIT
|
|
@ -1,35 +0,0 @@
|
|||
{
|
||||
"name": "@bytemd/plugin-import-html",
|
||||
"version": "1.3.0",
|
||||
"description": "ByteMD plugin to import HTML by pasting or dropping",
|
||||
"author": "Rongjian Zhang <pd4d10@gmail.com>",
|
||||
"license": "MIT",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/bytedance/bytemd.git",
|
||||
"directory": "packages/plugin-import-html"
|
||||
},
|
||||
"main": "dist/index.cjs.js",
|
||||
"module": "dist/index.esm.js",
|
||||
"unpkg": "dist/index.min.js",
|
||||
"jsdelivr": "dist/index.min.js",
|
||||
"types": "lib/index.d.ts",
|
||||
"files": [
|
||||
"dist",
|
||||
"lib"
|
||||
],
|
||||
"dependencies": {
|
||||
"mdast-util-gfm": "^0.1.0",
|
||||
"rehype-parse": "^7.0.0",
|
||||
"rehype-remark": "^8.0.0",
|
||||
"remark-gfm": "^1.0.0",
|
||||
"remark-stringify": "^9.0.0",
|
||||
"unified": "^9.0.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"bytemd": "*"
|
||||
},
|
||||
"publishConfig": {
|
||||
"access": "public"
|
||||
}
|
||||
}
|
|
@ -1,109 +0,0 @@
|
|||
import type { BytemdPlugin } from 'bytemd';
|
||||
import type { Processor } from 'unified';
|
||||
import type { RemarkStringifyOptions } from 'remark-stringify';
|
||||
|
||||
export interface ImportHtmlOptions {
|
||||
/**
|
||||
* Process HTML before being converted to markdown
|
||||
*/
|
||||
rehype?: (p: Processor) => Processor;
|
||||
/**
|
||||
* Output markdown text format
|
||||
*
|
||||
* https://github.com/syntax-tree/mdast-util-to-markdown#tomarkdowntree-options
|
||||
*/
|
||||
markdownFormat?: RemarkStringifyOptions;
|
||||
}
|
||||
|
||||
export default function importHtml({
|
||||
rehype,
|
||||
markdownFormat = {
|
||||
fences: true,
|
||||
listItemIndent: 'one',
|
||||
},
|
||||
}: ImportHtmlOptions = {}): BytemdPlugin {
|
||||
const handler = async (
|
||||
editor: CodeMirror.Editor,
|
||||
e: ClipboardEvent | DragEvent
|
||||
) => {
|
||||
const items = Array.from(
|
||||
(e instanceof ClipboardEvent
|
||||
? e.clipboardData?.items
|
||||
: e.dataTransfer?.items) ?? []
|
||||
);
|
||||
|
||||
// fix: text copied from VSCode would have a `vscode-editor-data` type, exclude this case
|
||||
if (items.length !== 2) return;
|
||||
|
||||
const htmlItem = items.find((item) => item.type === 'text/html');
|
||||
if (!htmlItem) return;
|
||||
|
||||
e.preventDefault();
|
||||
|
||||
let html: string;
|
||||
switch (htmlItem.kind) {
|
||||
case 'string': {
|
||||
html = await new Promise<string>((resolve) =>
|
||||
htmlItem.getAsString((v) => {
|
||||
resolve(v);
|
||||
})
|
||||
);
|
||||
break;
|
||||
}
|
||||
case 'file': {
|
||||
html = await htmlItem.getAsFile()!.text();
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
throw new Error();
|
||||
}
|
||||
}
|
||||
|
||||
// console.log(html);
|
||||
|
||||
const [
|
||||
{ default: unified },
|
||||
{ default: rehypeParse },
|
||||
{ default: rehypeRemark },
|
||||
{ default: remarkStringify },
|
||||
{ default: remarkGfm },
|
||||
{ toMarkdown: gfmExt },
|
||||
] = await Promise.all([
|
||||
import('unified'),
|
||||
import('rehype-parse'),
|
||||
// @ts-ignore
|
||||
import('rehype-remark'),
|
||||
import('remark-stringify'),
|
||||
import('remark-gfm'),
|
||||
// @ts-ignore
|
||||
import('mdast-util-gfm'),
|
||||
]);
|
||||
|
||||
let processor = unified().use(rehypeParse);
|
||||
if (rehype) {
|
||||
processor = rehype(processor);
|
||||
}
|
||||
processor = processor
|
||||
.use(rehypeRemark)
|
||||
.use(remarkGfm)
|
||||
.use(remarkStringify, {
|
||||
...markdownFormat,
|
||||
extensions: [gfmExt()],
|
||||
});
|
||||
|
||||
const result = await processor.process(html);
|
||||
editor.replaceSelection(result.toString());
|
||||
};
|
||||
|
||||
return {
|
||||
editorEffect({ editor }) {
|
||||
editor.on('paste', handler);
|
||||
editor.on('drop', handler);
|
||||
|
||||
return () => {
|
||||
editor.off('paste', handler);
|
||||
editor.off('drop', handler);
|
||||
};
|
||||
},
|
||||
};
|
||||
}
|
|
@ -1,26 +0,0 @@
|
|||
# @bytemd/plugin-import-image
|
||||
|
||||
[![npm](https://img.shields.io/npm/v/@bytemd/plugin-import-image.svg)](https://npm.im/@bytemd/plugin-import-image)
|
||||
|
||||
ByteMD plugin to import image by pasting or dropping
|
||||
|
||||
## Usage
|
||||
|
||||
```js
|
||||
import { Editor } from 'bytemd';
|
||||
import importImage from '@bytemd/plugin-import-image';
|
||||
|
||||
new Editor({
|
||||
target: document.body,
|
||||
props: {
|
||||
plugins: [
|
||||
importImage(),
|
||||
// ... other plugins
|
||||
],
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
## License
|
||||
|
||||
MIT
|
|
@ -1,27 +0,0 @@
|
|||
{
|
||||
"name": "@bytemd/plugin-import-image",
|
||||
"version": "1.3.2",
|
||||
"description": "ByteMD plugin to import image by pasting or dropping",
|
||||
"author": "Rongjian Zhang <pd4d10@gmail.com>",
|
||||
"license": "MIT",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/bytedance/bytemd.git",
|
||||
"directory": "packages/plugin-import-image"
|
||||
},
|
||||
"main": "dist/index.cjs.js",
|
||||
"module": "dist/index.esm.js",
|
||||
"unpkg": "dist/index.min.js",
|
||||
"jsdelivr": "dist/index.min.js",
|
||||
"types": "lib/index.d.ts",
|
||||
"files": [
|
||||
"dist",
|
||||
"lib"
|
||||
],
|
||||
"peerDependencies": {
|
||||
"bytemd": "*"
|
||||
},
|
||||
"publishConfig": {
|
||||
"access": "public"
|
||||
}
|
||||
}
|
|
@ -1,5 +0,0 @@
|
|||
// DO NOT EDIT, generated by scripts/icon.js
|
||||
export const icons = {
|
||||
image:
|
||||
'<svg width="1em" height="1em" viewBox="0 0 48 48" xmlns="http://www.w3.org/2000/svg"><path fill="#fff" fill-opacity=".01" fill-rule="evenodd" d="M0 0h48v48H0z"/><g transform="translate(5 8)" stroke-width="4" stroke-linejoin="round" stroke="currentColor" fill="none"><path d="M2 0h34a2 2 0 012 2v28a2 2 0 01-2 2H2a2 2 0 01-2-2V2a2 2 0 012-2z" stroke-linecap="round"/><circle stroke-linecap="round" cx="9.5" cy="8.5" r="1.5"/><path d="M10 16l5 4 6-7 17 13v4a2 2 0 01-2 2H2a2 2 0 01-2-2v-4l10-10z"/></g></svg>',
|
||||
};
|
|
@ -1,74 +0,0 @@
|
|||
import { BytemdPlugin, EditorContext } from 'bytemd';
|
||||
import { icons } from './icons';
|
||||
|
||||
export interface ImportImageOptions {
|
||||
/**
|
||||
* Upload the file and return a URL
|
||||
*/
|
||||
upload(files: File[]): Promise<string[]>;
|
||||
}
|
||||
|
||||
export default function importImage({
|
||||
upload,
|
||||
}: ImportImageOptions): BytemdPlugin {
|
||||
const handleFiles = async (files: File[], utils: EditorContext['utils']) => {
|
||||
const urls = await upload(files);
|
||||
utils.appendBlock(urls.map((url) => `![](${url})`).join('\n\n'));
|
||||
};
|
||||
|
||||
return {
|
||||
toolbar: {
|
||||
title: 'Image',
|
||||
icon: icons.image,
|
||||
onClick({ utils }) {
|
||||
const input = document.createElement('input');
|
||||
input.type = 'file';
|
||||
input.multiple = true;
|
||||
input.accept = 'image/*';
|
||||
input.addEventListener('input', (e) => {
|
||||
const files = Array.from(input.files ?? []).filter(
|
||||
// input accept would not work if 'all files' is selected
|
||||
(item) => item.type.startsWith('image/')
|
||||
);
|
||||
|
||||
if (files?.length) {
|
||||
handleFiles(files, utils);
|
||||
}
|
||||
});
|
||||
input.click();
|
||||
},
|
||||
},
|
||||
editorEffect({ editor, utils }) {
|
||||
const handler = async (
|
||||
_: CodeMirror.Editor,
|
||||
e: ClipboardEvent | DragEvent
|
||||
) => {
|
||||
const itemList =
|
||||
e instanceof ClipboardEvent
|
||||
? e.clipboardData?.items
|
||||
: e.dataTransfer?.items;
|
||||
|
||||
const files = Array.from(itemList ?? [])
|
||||
.map((item) => {
|
||||
if (item.type.startsWith('image/')) {
|
||||
return item.getAsFile();
|
||||
}
|
||||
})
|
||||
.filter((f): f is File => f != null);
|
||||
|
||||
if (files.length) {
|
||||
e.preventDefault();
|
||||
await handleFiles(files, utils);
|
||||
}
|
||||
};
|
||||
|
||||
editor.on('paste', handler);
|
||||
editor.on('drop', handler);
|
||||
|
||||
return () => {
|
||||
editor.off('paste', handler);
|
||||
editor.off('drop', handler);
|
||||
};
|
||||
},
|
||||
};
|
||||
}
|
|
@ -1,7 +1,7 @@
|
|||
// DO NOT EDIT, generated by scripts/icon.js
|
||||
export const icons = {
|
||||
inline:
|
||||
'<svg width="1em" height="1em" viewBox="0 0 48 48" fill="none" xmlns="http://www.w3.org/2000/svg"><path fill="#fff" fill-opacity=".01" d="M0 0h48v48H0z"/><path d="M24 2v44M35 6H20a9 9 0 100 18M13 42h15a9 9 0 100-18h-8" stroke="currentColor" stroke-width="4" stroke-linecap="round" stroke-linejoin="round"/></svg>',
|
||||
'<svg width="1em" height="1em" viewBox="0 0 48 48" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M37 9l-3-3H8l17 18L8 42h26l3-3M5 24h10M33 24h10" stroke="currentColor" stroke-width="4" stroke-linecap="round" stroke-linejoin="round"/></svg>',
|
||||
display:
|
||||
'<svg width="1em" height="1em" viewBox="0 0 48 48" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M40 9l-3-3H8l18 18L8 42h29l3-3" stroke="currentColor" stroke-width="4" stroke-linecap="round" stroke-linejoin="round"/></svg>',
|
||||
};
|
||||
|
|
|
@ -16,24 +16,21 @@ export default function math({
|
|||
return {
|
||||
remark: (u) => u.use(remarkMath),
|
||||
rehype: (u) => u.use(rehypeKatex, katexOptions),
|
||||
toolbar: [
|
||||
action: [
|
||||
{
|
||||
icon: icons.inline,
|
||||
onClick({ utils }) {
|
||||
utils.wrapText('$');
|
||||
},
|
||||
...locale.inline,
|
||||
icon: icons.inline,
|
||||
},
|
||||
{
|
||||
...locale.display,
|
||||
icon: icons.display,
|
||||
onClick({ editor, utils }) {
|
||||
const { startLine } = utils.appendBlock('$$\n\\TeX\n$$');
|
||||
handler({ editor, appendBlock }) {
|
||||
const { startLine } = appendBlock('$$\n\\TeX\n$$');
|
||||
editor.setSelection(
|
||||
{ line: startLine + 1, ch: 0 },
|
||||
{ line: startLine + 1, ch: 4 }
|
||||
);
|
||||
},
|
||||
...locale.display,
|
||||
},
|
||||
],
|
||||
};
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
const locale = {
|
||||
inline: {
|
||||
title: 'Math formula',
|
||||
title: 'Formula',
|
||||
cheatsheet: '$\\TeX$',
|
||||
},
|
||||
display: {
|
||||
title: 'Math formula block',
|
||||
title: 'Formula block',
|
||||
cheatsheet: '$$↵\\TeX↵$$',
|
||||
},
|
||||
};
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
// DO NOT EDIT, generated by scripts/icon.js
|
||||
export const icons = {
|
||||
inline:
|
||||
'<svg width="1em" height="1em" viewBox="0 0 48 48" fill="none" xmlns="http://www.w3.org/2000/svg"><path fill="#fff" fill-opacity=".01" d="M0 0h48v48H0z"/><path d="M24 2v44M35 6H20a9 9 0 100 18M13 42h15a9 9 0 100-18h-8" stroke="currentColor" stroke-width="4" stroke-linecap="round" stroke-linejoin="round"/></svg>',
|
||||
'<svg width="1em" height="1em" viewBox="0 0 48 48" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M37 9l-3-3H8l17 18L8 42h26l3-3M5 24h10M33 24h10" stroke="currentColor" stroke-width="4" stroke-linecap="round" stroke-linejoin="round"/></svg>',
|
||||
display:
|
||||
'<svg width="1em" height="1em" viewBox="0 0 48 48" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M40 9l-3-3H8l18 18L8 42h29l3-3" stroke="currentColor" stroke-width="4" stroke-linecap="round" stroke-linejoin="round"/></svg>',
|
||||
};
|
||||
|
|
|
@ -17,9 +17,9 @@ export default function math({
|
|||
|
||||
return {
|
||||
remark: (p) => p.use(remarkMath),
|
||||
effect({ $el }) {
|
||||
effect({ markdownBody }) {
|
||||
const renderMath = async (selector: string, displayMode: boolean) => {
|
||||
const els = $el.querySelectorAll<HTMLElement>(selector);
|
||||
const els = markdownBody.querySelectorAll<HTMLElement>(selector);
|
||||
if (els.length === 0) return;
|
||||
|
||||
if (!katex) {
|
||||
|
@ -38,24 +38,21 @@ export default function math({
|
|||
renderMath('.math.math-inline', false);
|
||||
renderMath('.math.math-display', true);
|
||||
},
|
||||
toolbar: [
|
||||
action: [
|
||||
{
|
||||
icon: icons.inline,
|
||||
onClick({ utils }) {
|
||||
utils.wrapText('$');
|
||||
},
|
||||
...locale.inline,
|
||||
icon: icons.inline,
|
||||
},
|
||||
{
|
||||
...locale.display,
|
||||
icon: icons.display,
|
||||
onClick({ editor, utils }) {
|
||||
const { startLine } = utils.appendBlock('$$\n\\TeX\n$$');
|
||||
handler({ editor, appendBlock }) {
|
||||
const { startLine } = appendBlock('$$\n\\TeX\n$$');
|
||||
editor.setSelection(
|
||||
{ line: startLine + 1, ch: 0 },
|
||||
{ line: startLine + 1, ch: 4 }
|
||||
);
|
||||
},
|
||||
...locale.display,
|
||||
},
|
||||
],
|
||||
};
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
const locale = {
|
||||
inline: {
|
||||
title: 'Math formula',
|
||||
title: 'Formula',
|
||||
cheatsheet: '$\\TeX$',
|
||||
},
|
||||
display: {
|
||||
title: 'Math formula block',
|
||||
title: 'Formula block',
|
||||
cheatsheet: '$$↵\\TeX↵$$',
|
||||
},
|
||||
};
|
||||
|
|
|
@ -5,11 +5,11 @@ export default function mediumZoom(options?: M.ZoomOptions): BytemdPlugin {
|
|||
let m: typeof M;
|
||||
|
||||
return {
|
||||
effect({ $el }) {
|
||||
const imgs = [...$el.querySelectorAll('img')].filter((e) => {
|
||||
effect({ markdownBody }) {
|
||||
const imgs = [...markdownBody.querySelectorAll('img')].filter((e) => {
|
||||
// Exclude images with anchor parent
|
||||
let $: HTMLElement | null = e;
|
||||
while ($ && $ !== $el) {
|
||||
while ($ && $ !== markdownBody) {
|
||||
if ($.tagName === 'A') return false;
|
||||
$ = $.parentElement;
|
||||
}
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
// DO NOT EDIT, generated by scripts/icon.js
|
||||
export const icons = {
|
||||
mermaid:
|
||||
'<svg width="1em" height="1em" viewBox="0 0 48 48" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M8 28a4 4 0 100-8 4 4 0 000 8zM42 8a2 2 0 100-4 2 2 0 000 4zM42 26a2 2 0 100-4 2 2 0 000 4zM42 44a2 2 0 100-4 2 2 0 000 4z" stroke="currentColor" stroke-width="4" stroke-linejoin="round"/><path d="M32 6H20v36h12M12 24h20" stroke="currentColor" stroke-width="4" stroke-linecap="round" stroke-linejoin="round"/></svg>',
|
||||
'<svg width="1em" height="1em" viewBox="0 0 48 48" fill="none" xmlns="http://www.w3.org/2000/svg"><path fill="#fff" fill-opacity=".01" d="M0 0h48v48H0z"/><path stroke="currentColor" stroke-width="4" stroke-linejoin="round" d="M17 6h14v9H17zM6 33h14v9H6zM28 33h14v9H28z"/><path d="M24 16v8M13 33v-9h22v9" stroke="currentColor" stroke-width="4" stroke-linecap="round" stroke-linejoin="round"/></svg>',
|
||||
};
|
||||
|
|
|
@ -16,9 +16,9 @@ export default function mermaid({
|
|||
let m: Mermaid;
|
||||
|
||||
return {
|
||||
effect({ $el }) {
|
||||
effect({ markdownBody }) {
|
||||
(async () => {
|
||||
const els = $el.querySelectorAll<HTMLElement>(
|
||||
const els = markdownBody.querySelectorAll<HTMLElement>(
|
||||
'pre>code.language-mermaid'
|
||||
);
|
||||
if (els.length === 0) return;
|
||||
|
@ -54,12 +54,10 @@ export default function mermaid({
|
|||
});
|
||||
})();
|
||||
},
|
||||
toolbar: {
|
||||
action: {
|
||||
icon: icons.mermaid,
|
||||
onClick({ editor, utils }) {
|
||||
const { startLine } = utils.appendBlock(
|
||||
'```mermaid\ngraph LR\nA--->B\n```'
|
||||
);
|
||||
handler({ editor, appendBlock }) {
|
||||
const { startLine } = appendBlock('```mermaid\ngraph LR\nA--->B\n```');
|
||||
editor.setSelection(
|
||||
{ line: startLine + 1, ch: 0 }, // @ts-ignore
|
||||
{ line: startLine + 2 }
|
||||
|
|
|
@ -14,10 +14,10 @@ export const Viewer: FC<ViewerProps> = ({ value, sanitize, plugins }) => {
|
|||
}, [value, sanitize, plugins]);
|
||||
|
||||
useEffect(() => {
|
||||
const $el = elRef.current;
|
||||
if (!$el || !vfile) return;
|
||||
const markdownBody = elRef.current;
|
||||
if (!markdownBody || !vfile) return;
|
||||
|
||||
const cbs = plugins?.map(({ effect }) => effect?.({ $el, vfile }));
|
||||
const cbs = plugins?.map(({ effect }) => effect?.({ markdownBody, vfile }));
|
||||
return () => {
|
||||
cbs?.forEach((cb) => cb && cb());
|
||||
};
|
||||
|
|
|
@ -40,7 +40,9 @@ export default {
|
|||
on() {
|
||||
if (this.plugins && this.vfile) {
|
||||
this.cbs = this.plugins.map(
|
||||
({ effect }) => effect && effect({ $el: this.$el, vfile: this.vfile })
|
||||
({ effect }) =>
|
||||
effect &&
|
||||
effect({ markdownBody: this.markdownBody, vfile: this.vfile })
|
||||
);
|
||||
}
|
||||
},
|
||||
|
@ -56,7 +58,9 @@ export default {
|
|||
const href = $.getAttribute('href');
|
||||
if (!href || !href.startsWith('#')) return;
|
||||
|
||||
const dest = this.$el.querySelector('#user-content-' + href.slice(1));
|
||||
const dest = this.markdownBody.querySelector(
|
||||
'#user-content-' + href.slice(1)
|
||||
);
|
||||
if (dest) dest.scrollIntoView();
|
||||
},
|
||||
},
|
||||
|
|
|
@ -8,15 +8,15 @@ const svgo = new Svgo();
|
|||
|
||||
const meta = {
|
||||
bytemd: {
|
||||
heading: () => `<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M3 1V14.33" stroke="currentColor" stroke-width="1.33" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M12.9971 1V14.33" stroke="currentColor" stroke-width="1.33" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M3 7.66498H12.9975" stroke="currentColor" stroke-width="1.33" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>`,
|
||||
heading: () =>
|
||||
`<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M3 1V14.33" stroke="currentColor" stroke-width="1.33" stroke-linecap="round" stroke-linejoin="round"/><path d="M12.9971 1V14.33" stroke="currentColor" stroke-width="1.33" stroke-linecap="round" stroke-linejoin="round"/><path d="M3 7.66498H12.9975" stroke="currentColor" stroke-width="1.33" stroke-linecap="round" stroke-linejoin="round"/></svg>`,
|
||||
h1: icons.H1,
|
||||
h2: icons.H2,
|
||||
bold: icons.TextBold,
|
||||
italic: icons.TextItalic,
|
||||
quote: icons.Quote,
|
||||
link: icons.LinkOne,
|
||||
image: icons.Pic,
|
||||
code: icons.Code,
|
||||
codeBlock: icons.CodeBrackets,
|
||||
ol: icons.OrderedList,
|
||||
|
@ -34,19 +34,16 @@ const meta = {
|
|||
task: icons.CheckCorrect,
|
||||
table: icons.InsertTable,
|
||||
},
|
||||
'plugin-import-image': {
|
||||
image: icons.Pic,
|
||||
},
|
||||
'plugin-math': {
|
||||
inline: icons.Dollar,
|
||||
inline: icons.Inline,
|
||||
display: icons.Formula,
|
||||
},
|
||||
'plugin-math-ssr': {
|
||||
inline: icons.Dollar,
|
||||
inline: icons.Inline,
|
||||
display: icons.Formula,
|
||||
},
|
||||
'plugin-mermaid': {
|
||||
mermaid: icons.MindMapping,
|
||||
mermaid: icons.ChartGraph,
|
||||
},
|
||||
};
|
||||
|
||||
|
|
82
yarn.lock
82
yarn.lock
|
@ -5321,13 +5321,6 @@ hast-to-hyperscript@^9.0.0:
|
|||
unist-util-is "^4.0.0"
|
||||
web-namespaces "^1.0.0"
|
||||
|
||||
hast-util-embedded@^1.0.0:
|
||||
version "1.0.6"
|
||||
resolved "https://registry.yarnpkg.com/hast-util-embedded/-/hast-util-embedded-1.0.6.tgz#ea7007323351cc43e19e1d6256b7cde66ad1aa03"
|
||||
integrity sha512-JQMW+TJe0UAIXZMjCJ4Wf6ayDV9Yv3PBDPsHD4ExBpAspJ6MOcCX+nzVF+UJVv7OqPcg852WEMSHQPoRA+FVSw==
|
||||
dependencies:
|
||||
hast-util-is-element "^1.1.0"
|
||||
|
||||
hast-util-from-parse5@^6.0.0:
|
||||
version "6.0.1"
|
||||
resolved "https://registry.yarnpkg.com/hast-util-from-parse5/-/hast-util-from-parse5-6.0.1.tgz#554e34abdeea25ac76f5bd950a1f0180e0b3bc2a"
|
||||
|
@ -5340,12 +5333,7 @@ hast-util-from-parse5@^6.0.0:
|
|||
vfile-location "^3.2.0"
|
||||
web-namespaces "^1.0.0"
|
||||
|
||||
hast-util-has-property@^1.0.0:
|
||||
version "1.0.4"
|
||||
resolved "https://registry.yarnpkg.com/hast-util-has-property/-/hast-util-has-property-1.0.4.tgz#9f137565fad6082524b382c1e7d7d33ca5059f36"
|
||||
integrity sha512-ghHup2voGfgFoHMGnaLHOjbYFACKrRh9KFttdCzMCbFoBMJXiNi2+XTrPP8+q6cDJM/RSqlCfVWrjp1H201rZg==
|
||||
|
||||
hast-util-is-element@^1.0.0, hast-util-is-element@^1.1.0:
|
||||
hast-util-is-element@^1.0.0:
|
||||
version "1.1.0"
|
||||
resolved "https://registry.yarnpkg.com/hast-util-is-element/-/hast-util-is-element-1.1.0.tgz#3b3ed5159a2707c6137b48637fbfe068e175a425"
|
||||
integrity sha512-oUmNua0bFbdrD/ELDSSEadRVtWZOf3iF6Lbv81naqsIV99RnSCieTbWuWCY8BAeEfKJTKl0gRdokv+dELutHGQ==
|
||||
|
@ -5394,24 +5382,6 @@ hast-util-to-html@^7.1.1:
|
|||
unist-util-is "^4.0.0"
|
||||
xtend "^4.0.0"
|
||||
|
||||
hast-util-to-mdast@^7.0.0:
|
||||
version "7.1.3"
|
||||
resolved "https://registry.yarnpkg.com/hast-util-to-mdast/-/hast-util-to-mdast-7.1.3.tgz#e4ad9098929355501773aed5e66c8181559eee04"
|
||||
integrity sha512-3vER9p8B8mCs5b2qzoBiWlC9VnTkFmr8Ufb1eKdcvhVY+nipt52YfMRshk5r9gOE1IZ9/xtlSxebGCv1ig9uKA==
|
||||
dependencies:
|
||||
extend "^3.0.0"
|
||||
hast-util-has-property "^1.0.0"
|
||||
hast-util-is-element "^1.1.0"
|
||||
hast-util-to-text "^2.0.0"
|
||||
mdast-util-phrasing "^2.0.0"
|
||||
mdast-util-to-string "^1.0.0"
|
||||
rehype-minify-whitespace "^4.0.3"
|
||||
repeat-string "^1.6.1"
|
||||
trim-trailing-lines "^1.1.0"
|
||||
unist-util-is "^4.0.0"
|
||||
unist-util-visit "^2.0.0"
|
||||
xtend "^4.0.1"
|
||||
|
||||
hast-util-to-parse5@^6.0.0:
|
||||
version "6.0.0"
|
||||
resolved "https://registry.yarnpkg.com/hast-util-to-parse5/-/hast-util-to-parse5-6.0.0.tgz#1ec44650b631d72952066cea9b1445df699f8479"
|
||||
|
@ -5432,7 +5402,7 @@ hast-util-to-text@^2.0.0:
|
|||
repeat-string "^1.0.0"
|
||||
unist-util-find-after "^3.0.0"
|
||||
|
||||
hast-util-whitespace@^1.0.0, hast-util-whitespace@^1.0.4:
|
||||
hast-util-whitespace@^1.0.0:
|
||||
version "1.0.4"
|
||||
resolved "https://registry.yarnpkg.com/hast-util-whitespace/-/hast-util-whitespace-1.0.4.tgz#e4fe77c4a9ae1cb2e6c25e02df0043d0164f6e41"
|
||||
integrity sha512-I5GTdSfhYfAPNztx2xJRQpG8cuDSNt599/7YUn7Gx/WxNMsG+a835k97TDkFgk123cwjfwINaZknkKkphx/f2A==
|
||||
|
@ -7217,13 +7187,6 @@ mdast-util-math@^0.1.0:
|
|||
mdast-util-to-markdown "^0.6.0"
|
||||
repeat-string "^1.0.0"
|
||||
|
||||
mdast-util-phrasing@^2.0.0:
|
||||
version "2.0.0"
|
||||
resolved "https://registry.yarnpkg.com/mdast-util-phrasing/-/mdast-util-phrasing-2.0.0.tgz#57e61f2be908be9f5fce54fcc2fa593687986267"
|
||||
integrity sha512-G1rNlW/sViwzbBYD7+k3mKGtoWV2v4GBFky66OYHfktHe7Hg9R+hH4xpeoOtjYiwTvle8C8wlKMpgqPCkaeK8Q==
|
||||
dependencies:
|
||||
unist-util-is "^4.0.0"
|
||||
|
||||
mdast-util-to-hast@^10.0.0:
|
||||
version "10.1.1"
|
||||
resolved "https://registry.yarnpkg.com/mdast-util-to-hast/-/mdast-util-to-hast-10.1.1.tgz#4dce367abdc57311a87cf95da54a4d115b9d25da"
|
||||
|
@ -7250,11 +7213,6 @@ mdast-util-to-markdown@^0.6.0, mdast-util-to-markdown@^0.6.1, mdast-util-to-mark
|
|||
repeat-string "^1.0.0"
|
||||
zwitch "^1.0.0"
|
||||
|
||||
mdast-util-to-string@^1.0.0:
|
||||
version "1.1.0"
|
||||
resolved "https://registry.yarnpkg.com/mdast-util-to-string/-/mdast-util-to-string-1.1.0.tgz#27055500103f51637bd07d01da01eb1967a43527"
|
||||
integrity sha512-jVU0Nr2B9X3MU4tSK7JP1CMkSvOj7X5l/GboG1tKRw52lLF1x2Ju92Ms9tNetCcbfX3hzlM73zYo2NKkWSfF/A==
|
||||
|
||||
mdast-util-to-string@^2.0.0:
|
||||
version "2.0.0"
|
||||
resolved "https://registry.yarnpkg.com/mdast-util-to-string/-/mdast-util-to-string-2.0.0.tgz#b8cfe6a713e1091cb5b728fc48885a4767f8b97b"
|
||||
|
@ -9402,16 +9360,6 @@ rehype-katex@^4.0.0:
|
|||
unified "^9.0.0"
|
||||
unist-util-visit "^2.0.0"
|
||||
|
||||
rehype-minify-whitespace@^4.0.3:
|
||||
version "4.0.5"
|
||||
resolved "https://registry.yarnpkg.com/rehype-minify-whitespace/-/rehype-minify-whitespace-4.0.5.tgz#5b4781786116216f6d5d7ceadf84e2489dd7b3cd"
|
||||
integrity sha512-QC3Z+bZ5wbv+jGYQewpAAYhXhzuH/TVRx7z08rurBmh9AbG8Nu8oJnvs9LWj43Fd/C7UIhXoQ7Wddgt+ThWK5g==
|
||||
dependencies:
|
||||
hast-util-embedded "^1.0.0"
|
||||
hast-util-is-element "^1.0.0"
|
||||
hast-util-whitespace "^1.0.4"
|
||||
unist-util-is "^4.0.0"
|
||||
|
||||
rehype-parse@^7.0.0:
|
||||
version "7.0.1"
|
||||
resolved "https://registry.yarnpkg.com/rehype-parse/-/rehype-parse-7.0.1.tgz#58900f6702b56767814afc2a9efa2d42b1c90c57"
|
||||
|
@ -9427,13 +9375,6 @@ rehype-raw@^5.0.0:
|
|||
dependencies:
|
||||
hast-util-raw "^6.0.0"
|
||||
|
||||
rehype-remark@^8.0.0:
|
||||
version "8.0.0"
|
||||
resolved "https://registry.yarnpkg.com/rehype-remark/-/rehype-remark-8.0.0.tgz#66233e5b6e096419353f4c5c0fb6808f7924dd57"
|
||||
integrity sha512-d1EmgsqWc1v9E/URuzozU8pa4AYHIcfOMLhgzQRHeaxYyMHJKIrpBMdRhl+IbqcHLD699Ho/vO+DpSZgKsGM8Q==
|
||||
dependencies:
|
||||
hast-util-to-mdast "^7.0.0"
|
||||
|
||||
rehype-sanitize@^4.0.0:
|
||||
version "4.0.0"
|
||||
resolved "https://registry.yarnpkg.com/rehype-sanitize/-/rehype-sanitize-4.0.0.tgz#b5241cf66bcedc49cd4e924a5f7a252f00a151ad"
|
||||
|
@ -9512,13 +9453,6 @@ remark-rehype@^8.0.0:
|
|||
dependencies:
|
||||
mdast-util-to-hast "^10.0.0"
|
||||
|
||||
remark-stringify@^9.0.0:
|
||||
version "9.0.1"
|
||||
resolved "https://registry.yarnpkg.com/remark-stringify/-/remark-stringify-9.0.1.tgz#576d06e910548b0a7191a71f27b33f1218862894"
|
||||
integrity sha512-mWmNg3ZtESvZS8fv5PTvaPckdL4iNlCHTt8/e/8oN08nArHRHjNZMKzA/YW3+p7/lYqIw4nx1XsjCBo/AxNChg==
|
||||
dependencies:
|
||||
mdast-util-to-markdown "^0.6.0"
|
||||
|
||||
remove-trailing-separator@^1.0.1:
|
||||
version "1.1.0"
|
||||
resolved "https://registry.yarnpkg.com/remove-trailing-separator/-/remove-trailing-separator-1.1.0.tgz#c24bce2a283adad5bc3f58e0d48249b92379d8ef"
|
||||
|
@ -9881,6 +9815,11 @@ scss-tokenizer@^0.2.3:
|
|||
js-base64 "^2.1.8"
|
||||
source-map "^0.4.2"
|
||||
|
||||
select-files@^1.0.1:
|
||||
version "1.0.1"
|
||||
resolved "https://registry.yarnpkg.com/select-files/-/select-files-1.0.1.tgz#974991a69c5dba26cfa57dcbc6d27f0917741acb"
|
||||
integrity sha512-8h4DSpjfFa0hyMP3z3ye4SxyhdaE5RgaXeScRpH7xl4YblnZSHwexmLdLNdSKwTO8H9ccDKj7Votz0io+18+BQ==
|
||||
|
||||
"semver@2 || 3 || 4 || 5", "semver@2.x || 3.x || 4 || 5", semver@^5.4.1, semver@^5.5.0, semver@^5.5.1, semver@^5.6.0, semver@^5.7.0, semver@^5.7.1:
|
||||
version "5.7.1"
|
||||
resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.1.tgz#a954f931aeba508d307bbf069eff0c01c96116f7"
|
||||
|
@ -10808,11 +10747,6 @@ trim-off-newlines@^1.0.0:
|
|||
resolved "https://registry.yarnpkg.com/trim-off-newlines/-/trim-off-newlines-1.0.1.tgz#9f9ba9d9efa8764c387698bcbfeb2c848f11adb3"
|
||||
integrity sha1-n5up2e+odkw4dpi8v+sshI8RrbM=
|
||||
|
||||
trim-trailing-lines@^1.1.0:
|
||||
version "1.1.4"
|
||||
resolved "https://registry.yarnpkg.com/trim-trailing-lines/-/trim-trailing-lines-1.1.4.tgz#bd4abbec7cc880462f10b2c8b5ce1d8d1ec7c2c0"
|
||||
integrity sha512-rjUWSqnfTNrjbB9NQWfPMH/xRK1deHeGsHoVfpxJ++XeYXE0d6B1En37AHfw3jtfTU7dzMzZL2jjpe8Qb5gLIQ==
|
||||
|
||||
trough@^1.0.0:
|
||||
version "1.0.5"
|
||||
resolved "https://registry.yarnpkg.com/trough/-/trough-1.0.5.tgz#b8b639cefad7d0bb2abd37d433ff8293efa5f406"
|
||||
|
@ -11495,7 +11429,7 @@ xmlchars@^2.2.0:
|
|||
resolved "https://registry.yarnpkg.com/xmlchars/-/xmlchars-2.2.0.tgz#060fe1bcb7f9c76fe2a17db86a9bc3ab894210cb"
|
||||
integrity sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==
|
||||
|
||||
xtend@^4.0.0, xtend@^4.0.1, xtend@~4.0.1:
|
||||
xtend@^4.0.0, xtend@~4.0.1:
|
||||
version "4.0.2"
|
||||
resolved "https://registry.yarnpkg.com/xtend/-/xtend-4.0.2.tgz#bb72779f5fa465186b1f438f674fa347fdb5db54"
|
||||
integrity sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==
|
||||
|
|
Loading…
Reference in New Issue