@yonailab/levit-vue 0.0.0-stage → 0.4.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 +44 -2
- package/dist/index.d.ts +161 -0
- package/dist/index.js +84 -0
- package/package.json +52 -4
package/README.md
CHANGED
|
@@ -1,3 +1,45 @@
|
|
|
1
|
-
#
|
|
1
|
+
# @yonailab/levit-vue
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
[Levit](https://github.com/azusawa/levit) 에디터의 Vue 3 컴포넌트입니다.
|
|
4
|
+
|
|
5
|
+
```bash
|
|
6
|
+
npm install @yonailab/levit @yonailab/levit-vue
|
|
7
|
+
```
|
|
8
|
+
|
|
9
|
+
## 컴포넌트
|
|
10
|
+
|
|
11
|
+
```vue
|
|
12
|
+
<script setup lang="ts">
|
|
13
|
+
import { ref } from 'vue';
|
|
14
|
+
import { LevitEditor } from '@yonailab/levit-vue';
|
|
15
|
+
import { ko, type DocumentData } from '@yonailab/levit';
|
|
16
|
+
import '@yonailab/levit/levit.css';
|
|
17
|
+
|
|
18
|
+
const doc = ref<DocumentData>();
|
|
19
|
+
</script>
|
|
20
|
+
|
|
21
|
+
<template>
|
|
22
|
+
<LevitEditor v-model="doc" placeholder="메모를 입력하세요" :messages="ko" />
|
|
23
|
+
</template>
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
- `v-model`로 문서 JSON을 주고받습니다. 돌아온 값을 다시 넣어도 커서는 그대로입니다.
|
|
27
|
+
- 에디터 옵션(`plugins`, `messages`, `uploadImage`, `highlightCode`)을 props로 넘깁니다.
|
|
28
|
+
- `:bubble-menu="false"`, `:slash-menu="false"`로 메뉴를 끌 수 있습니다.
|
|
29
|
+
- `@ready`로 에디터 인스턴스를 받고, `@error`로 업로드 실패 같은 오류를 받습니다.
|
|
30
|
+
- 컴포넌트 ref의 `editor`로도 인스턴스에 접근할 수 있습니다.
|
|
31
|
+
|
|
32
|
+
## 컴포저블
|
|
33
|
+
|
|
34
|
+
```vue
|
|
35
|
+
<script setup lang="ts">
|
|
36
|
+
import { useLevitEditor } from '@yonailab/levit-vue';
|
|
37
|
+
|
|
38
|
+
const { element, editor } = useLevitEditor({ placeholder: '입력하세요' });
|
|
39
|
+
</script>
|
|
40
|
+
|
|
41
|
+
<template>
|
|
42
|
+
<button @click="editor?.chain().toggleBold().run()">굵게</button>
|
|
43
|
+
<div ref="element" />
|
|
44
|
+
</template>
|
|
45
|
+
```
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
import { type PropType, type Ref, type ShallowRef } from 'vue';
|
|
2
|
+
import { type DocumentData, type EditorOptions, type LevitEditor as Editor, type LevitMessages, type LevitPlugin } from '@yonailab/levit';
|
|
3
|
+
export type { Editor };
|
|
4
|
+
/** Editor options, plus whether to add the bubble and slash menus. */
|
|
5
|
+
export interface UseLevitEditorOptions extends Omit<EditorOptions, 'element'> {
|
|
6
|
+
/** Show the formatting toolbar on selected text. Default: true. */
|
|
7
|
+
bubbleMenu?: boolean;
|
|
8
|
+
/** Open the block menu with "/". Default: true. */
|
|
9
|
+
slashMenu?: boolean;
|
|
10
|
+
/** Called once the editor exists. */
|
|
11
|
+
onReady?: (editor: Editor) => void;
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Creates a Levit editor on the element you bind to `element` (with
|
|
15
|
+
* `ref="element"` or `h('div', { ref: element })`). The editor is made on
|
|
16
|
+
* mount and destroyed before unmount.
|
|
17
|
+
*/
|
|
18
|
+
export declare function useLevitEditor(options?: UseLevitEditorOptions): {
|
|
19
|
+
element: Ref<HTMLElement | null>;
|
|
20
|
+
editor: ShallowRef<Editor | null>;
|
|
21
|
+
};
|
|
22
|
+
/**
|
|
23
|
+
* A Levit editor with its menus. Bind the document with v-model; listen to
|
|
24
|
+
* `ready` for the editor instance, or read it from the component ref.
|
|
25
|
+
*/
|
|
26
|
+
export declare const LevitEditor: import("vue").DefineComponent<import("vue").ExtractPropTypes<{
|
|
27
|
+
modelValue: {
|
|
28
|
+
type: PropType<DocumentData>;
|
|
29
|
+
default: undefined;
|
|
30
|
+
};
|
|
31
|
+
placeholder: {
|
|
32
|
+
type: StringConstructor;
|
|
33
|
+
default: undefined;
|
|
34
|
+
};
|
|
35
|
+
plugins: {
|
|
36
|
+
type: PropType<LevitPlugin[]>;
|
|
37
|
+
default: undefined;
|
|
38
|
+
};
|
|
39
|
+
messages: {
|
|
40
|
+
type: PropType<Partial<LevitMessages>>;
|
|
41
|
+
default: undefined;
|
|
42
|
+
};
|
|
43
|
+
uploadImage: {
|
|
44
|
+
type: PropType<EditorOptions["uploadImage"]>;
|
|
45
|
+
default: undefined;
|
|
46
|
+
};
|
|
47
|
+
highlightCode: {
|
|
48
|
+
type: PropType<EditorOptions["highlightCode"]>;
|
|
49
|
+
default: undefined;
|
|
50
|
+
};
|
|
51
|
+
bubbleMenu: {
|
|
52
|
+
type: BooleanConstructor;
|
|
53
|
+
default: boolean;
|
|
54
|
+
};
|
|
55
|
+
slashMenu: {
|
|
56
|
+
type: BooleanConstructor;
|
|
57
|
+
default: boolean;
|
|
58
|
+
};
|
|
59
|
+
onError: {
|
|
60
|
+
type: PropType<(error: unknown) => void>;
|
|
61
|
+
default: undefined;
|
|
62
|
+
};
|
|
63
|
+
}>, () => import("vue").VNode<import("vue").RendererNode, import("vue").RendererElement, {
|
|
64
|
+
[key: string]: any;
|
|
65
|
+
}>, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
|
|
66
|
+
'update:modelValue': (_value: DocumentData) => true;
|
|
67
|
+
ready: (_editor: Editor) => true;
|
|
68
|
+
}, string, import("vue").PublicProps, Readonly<import("vue").ExtractPropTypes<{
|
|
69
|
+
modelValue: {
|
|
70
|
+
type: PropType<DocumentData>;
|
|
71
|
+
default: undefined;
|
|
72
|
+
};
|
|
73
|
+
placeholder: {
|
|
74
|
+
type: StringConstructor;
|
|
75
|
+
default: undefined;
|
|
76
|
+
};
|
|
77
|
+
plugins: {
|
|
78
|
+
type: PropType<LevitPlugin[]>;
|
|
79
|
+
default: undefined;
|
|
80
|
+
};
|
|
81
|
+
messages: {
|
|
82
|
+
type: PropType<Partial<LevitMessages>>;
|
|
83
|
+
default: undefined;
|
|
84
|
+
};
|
|
85
|
+
uploadImage: {
|
|
86
|
+
type: PropType<EditorOptions["uploadImage"]>;
|
|
87
|
+
default: undefined;
|
|
88
|
+
};
|
|
89
|
+
highlightCode: {
|
|
90
|
+
type: PropType<EditorOptions["highlightCode"]>;
|
|
91
|
+
default: undefined;
|
|
92
|
+
};
|
|
93
|
+
bubbleMenu: {
|
|
94
|
+
type: BooleanConstructor;
|
|
95
|
+
default: boolean;
|
|
96
|
+
};
|
|
97
|
+
slashMenu: {
|
|
98
|
+
type: BooleanConstructor;
|
|
99
|
+
default: boolean;
|
|
100
|
+
};
|
|
101
|
+
onError: {
|
|
102
|
+
type: PropType<(error: unknown) => void>;
|
|
103
|
+
default: undefined;
|
|
104
|
+
};
|
|
105
|
+
}>> & Readonly<{
|
|
106
|
+
"onUpdate:modelValue"?: ((_value: DocumentData) => any) | undefined;
|
|
107
|
+
onReady?: ((_editor: Editor) => any) | undefined;
|
|
108
|
+
}>, {
|
|
109
|
+
placeholder: string;
|
|
110
|
+
uploadImage: ((file: File) => Promise<string>) | undefined;
|
|
111
|
+
onError: (error: unknown) => void;
|
|
112
|
+
highlightCode: ((code: string, language: string) => string) | undefined;
|
|
113
|
+
plugins: LevitPlugin[];
|
|
114
|
+
messages: Partial<{
|
|
115
|
+
editorLabel: string;
|
|
116
|
+
placeholder: string;
|
|
117
|
+
formattingToolbar: string;
|
|
118
|
+
bold: string;
|
|
119
|
+
italic: string;
|
|
120
|
+
underline: string;
|
|
121
|
+
strikethrough: string;
|
|
122
|
+
highlight: string;
|
|
123
|
+
code: string;
|
|
124
|
+
link: string;
|
|
125
|
+
linkUrl: string;
|
|
126
|
+
linkPlaceholder: string;
|
|
127
|
+
quote: string;
|
|
128
|
+
slashMenuLabel: string;
|
|
129
|
+
paragraph: string;
|
|
130
|
+
paragraphDescription: string;
|
|
131
|
+
heading1: string;
|
|
132
|
+
heading1Description: string;
|
|
133
|
+
heading2: string;
|
|
134
|
+
heading2Description: string;
|
|
135
|
+
heading3: string;
|
|
136
|
+
heading3Description: string;
|
|
137
|
+
quoteDescription: string;
|
|
138
|
+
codeBlock: string;
|
|
139
|
+
codeBlockDescription: string;
|
|
140
|
+
bulletList: string;
|
|
141
|
+
bulletListDescription: string;
|
|
142
|
+
orderedList: string;
|
|
143
|
+
orderedListDescription: string;
|
|
144
|
+
taskList: string;
|
|
145
|
+
taskListDescription: string;
|
|
146
|
+
divider: string;
|
|
147
|
+
dividerDescription: string;
|
|
148
|
+
image: string;
|
|
149
|
+
imageDescription: string;
|
|
150
|
+
imageUrlPrompt: string;
|
|
151
|
+
markDone: string;
|
|
152
|
+
markNotDone: string;
|
|
153
|
+
codeLanguagePrompt: string;
|
|
154
|
+
codeLanguageLabel: string;
|
|
155
|
+
setCodeLanguage: string;
|
|
156
|
+
plainText: string;
|
|
157
|
+
}>;
|
|
158
|
+
bubbleMenu: boolean;
|
|
159
|
+
slashMenu: boolean;
|
|
160
|
+
modelValue: DocumentData;
|
|
161
|
+
}, {}, {}, {}, string, import("vue").ComponentProvideOptions, true, {}, any>;
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import { defineComponent, h, onBeforeUnmount, onMounted, ref, shallowRef, watch, } from 'vue';
|
|
2
|
+
import { createBubbleMenu, createLevitEditor, createSlashMenu, sameContent, } from '@yonailab/levit';
|
|
3
|
+
/**
|
|
4
|
+
* Creates a Levit editor on the element you bind to `element` (with
|
|
5
|
+
* `ref="element"` or `h('div', { ref: element })`). The editor is made on
|
|
6
|
+
* mount and destroyed before unmount.
|
|
7
|
+
*/
|
|
8
|
+
export function useLevitEditor(options = {}) {
|
|
9
|
+
const element = ref(null);
|
|
10
|
+
// shallowRef: Vue must not wrap the editor in a reactive proxy.
|
|
11
|
+
const editor = shallowRef(null);
|
|
12
|
+
let cleanup = () => { };
|
|
13
|
+
onMounted(() => {
|
|
14
|
+
if (!element.value)
|
|
15
|
+
return;
|
|
16
|
+
const { bubbleMenu = true, slashMenu = true, ...editorOptions } = options;
|
|
17
|
+
const instance = createLevitEditor(element.value, editorOptions);
|
|
18
|
+
const menus = [bubbleMenu && createBubbleMenu(instance), slashMenu && createSlashMenu(instance)];
|
|
19
|
+
editor.value = instance;
|
|
20
|
+
options.onReady?.(instance);
|
|
21
|
+
cleanup = () => {
|
|
22
|
+
for (const menu of menus)
|
|
23
|
+
if (menu)
|
|
24
|
+
menu.destroy();
|
|
25
|
+
instance.destroy();
|
|
26
|
+
editor.value = null;
|
|
27
|
+
};
|
|
28
|
+
});
|
|
29
|
+
onBeforeUnmount(() => cleanup());
|
|
30
|
+
return { element, editor };
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* A Levit editor with its menus. Bind the document with v-model; listen to
|
|
34
|
+
* `ready` for the editor instance, or read it from the component ref.
|
|
35
|
+
*/
|
|
36
|
+
export const LevitEditor = defineComponent({
|
|
37
|
+
name: 'LevitEditor',
|
|
38
|
+
props: {
|
|
39
|
+
modelValue: { type: Object, default: undefined },
|
|
40
|
+
placeholder: { type: String, default: undefined },
|
|
41
|
+
plugins: { type: Array, default: undefined },
|
|
42
|
+
messages: { type: Object, default: undefined },
|
|
43
|
+
uploadImage: { type: Function, default: undefined },
|
|
44
|
+
highlightCode: { type: Function, default: undefined },
|
|
45
|
+
bubbleMenu: { type: Boolean, default: true },
|
|
46
|
+
slashMenu: { type: Boolean, default: true },
|
|
47
|
+
// A prop rather than an emit, so without an @error listener the editor
|
|
48
|
+
// falls back to logging errors to the console.
|
|
49
|
+
onError: { type: Function, default: undefined },
|
|
50
|
+
},
|
|
51
|
+
emits: {
|
|
52
|
+
'update:modelValue': (_value) => true,
|
|
53
|
+
ready: (_editor) => true,
|
|
54
|
+
},
|
|
55
|
+
setup(props, { emit, expose }) {
|
|
56
|
+
const { element, editor } = useLevitEditor({
|
|
57
|
+
initialContent: props.modelValue,
|
|
58
|
+
placeholder: props.placeholder,
|
|
59
|
+
plugins: props.plugins,
|
|
60
|
+
messages: props.messages,
|
|
61
|
+
uploadImage: props.uploadImage,
|
|
62
|
+
highlightCode: props.highlightCode,
|
|
63
|
+
bubbleMenu: props.bubbleMenu,
|
|
64
|
+
slashMenu: props.slashMenu,
|
|
65
|
+
onChange: (ed) => emit('update:modelValue', ed.getJSON()),
|
|
66
|
+
onError: props.onError,
|
|
67
|
+
onReady: (ed) => emit('ready', ed),
|
|
68
|
+
});
|
|
69
|
+
// Replace the content only when the bound value differs, so the value
|
|
70
|
+
// that comes back from update:modelValue does not reset the caret.
|
|
71
|
+
watch(() => props.modelValue, (value) => {
|
|
72
|
+
if (!editor.value || value === undefined)
|
|
73
|
+
return;
|
|
74
|
+
if (!sameContent(value, editor.value.getJSON()))
|
|
75
|
+
editor.value.setJSON(value);
|
|
76
|
+
});
|
|
77
|
+
expose({
|
|
78
|
+
get editor() {
|
|
79
|
+
return editor.value;
|
|
80
|
+
},
|
|
81
|
+
});
|
|
82
|
+
return () => h('div', { ref: element });
|
|
83
|
+
},
|
|
84
|
+
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,54 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@yonailab/levit-vue",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"
|
|
5
|
-
"
|
|
6
|
-
|
|
3
|
+
"version": "0.4.0",
|
|
4
|
+
"description": "Vue components for the Levit editor.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "./dist/index.js",
|
|
7
|
+
"types": "./dist/index.d.ts",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": {
|
|
10
|
+
"types": "./dist/index.d.ts",
|
|
11
|
+
"default": "./dist/index.js"
|
|
12
|
+
}
|
|
13
|
+
},
|
|
14
|
+
"sideEffects": false,
|
|
15
|
+
"files": [
|
|
16
|
+
"dist",
|
|
17
|
+
"README.md"
|
|
18
|
+
],
|
|
19
|
+
"scripts": {
|
|
20
|
+
"build": "tsc -p tsconfig.build.json",
|
|
21
|
+
"test": "vitest run",
|
|
22
|
+
"typecheck": "tsc --noEmit",
|
|
23
|
+
"check:package": "node ../levit/scripts/check-package.mjs --wrapper",
|
|
24
|
+
"prepublishOnly": "npm run typecheck && npm test && npm run build && npm run check:package"
|
|
25
|
+
},
|
|
26
|
+
"peerDependencies": {
|
|
27
|
+
"@yonailab/levit": "^0.4.0",
|
|
28
|
+
"vue": "^3.3"
|
|
29
|
+
},
|
|
30
|
+
"keywords": [
|
|
31
|
+
"editor",
|
|
32
|
+
"wysiwyg",
|
|
33
|
+
"rich-text",
|
|
34
|
+
"levit",
|
|
35
|
+
"vue"
|
|
36
|
+
],
|
|
37
|
+
"author": "yonailab",
|
|
38
|
+
"repository": {
|
|
39
|
+
"type": "git",
|
|
40
|
+
"url": "git+https://github.com/azusawa/levit.git",
|
|
41
|
+
"directory": "packages/vue"
|
|
42
|
+
},
|
|
43
|
+
"homepage": "https://github.com/azusawa/levit/tree/main/packages/vue#readme",
|
|
44
|
+
"bugs": {
|
|
45
|
+
"url": "https://github.com/azusawa/levit/issues"
|
|
46
|
+
},
|
|
47
|
+
"license": "MIT",
|
|
48
|
+
"publishConfig": {
|
|
49
|
+
"access": "public"
|
|
50
|
+
},
|
|
51
|
+
"devDependencies": {
|
|
52
|
+
"vue": "^3.5.45"
|
|
53
|
+
}
|
|
54
|
+
}
|