@grandaniel/vue-markdown-editor 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.
Files changed (41) hide show
  1. package/eslint.config.ts +0 -0
  2. package/package.json +43 -0
  3. package/src/components/MarkdownEditor/Composable/HeadlineTypeMap.ts +36 -0
  4. package/src/components/MarkdownEditor/Composable/useMarkdownEditor.ts +21 -0
  5. package/src/components/MarkdownEditor/Composable/useMarkdownProcessor.ts +186 -0
  6. package/src/components/MarkdownEditor/Composable/useReflectiveState.ts +84 -0
  7. package/src/components/MarkdownEditor/ContextMenu/MarkdownEditorContextMenu.vue +38 -0
  8. package/src/components/MarkdownEditor/ContextMenu/MarkdownEditorContextMenuBlockItem.vue +43 -0
  9. package/src/components/MarkdownEditor/ContextMenu/MarkdownEditorContextMenuInlineItem.vue +52 -0
  10. package/src/components/MarkdownEditor/ContextMenu/MarkdownEditorImageContextMenu.vue +35 -0
  11. package/src/components/MarkdownEditor/ContextMenu/MarkdownEditorTextSelectionContextMenu.vue +45 -0
  12. package/src/components/MarkdownEditor/Factory/MarkdownNodeFactory.ts +49 -0
  13. package/src/components/MarkdownEditor/MarkdownComponentRegistry.ts +48 -0
  14. package/src/components/MarkdownEditor/MarkdownEditor.vue +333 -0
  15. package/src/components/MarkdownEditor/MarkdownEditorModal.vue +169 -0
  16. package/src/components/MarkdownEditor/MarkdownEditorModule.vue +129 -0
  17. package/src/components/MarkdownEditor/Modules/MarkdownModuleHeadline1.vue +60 -0
  18. package/src/components/MarkdownEditor/Modules/MarkdownModuleHeadline2.vue +60 -0
  19. package/src/components/MarkdownEditor/Modules/MarkdownModuleHeadline3.vue +61 -0
  20. package/src/components/MarkdownEditor/Modules/MarkdownModuleImage.vue +185 -0
  21. package/src/components/MarkdownEditor/Modules/MarkdownModuleImageState.ts +10 -0
  22. package/src/components/MarkdownEditor/Modules/MarkdownModuleList.vue +7 -0
  23. package/src/components/MarkdownEditor/Modules/MarkdownModuleListState.ts +10 -0
  24. package/src/components/MarkdownEditor/Modules/MarkdownModuleParagraph.vue +54 -0
  25. package/src/components/MarkdownEditor/Modules/MarkdownModuleTextState.ts +8 -0
  26. package/src/components/MarkdownEditor/Modules/__tests__/MarkdownModuleHeadline1.test.js +20 -0
  27. package/src/components/MarkdownEditor/Modules/__tests__/MarkdownModuleHeadline1.test.ts +25 -0
  28. package/src/components/MarkdownEditor/Modules/__tests__/__snapshots__/MarkdownModuleHeadline1.test.ts.snap +7 -0
  29. package/src/components/MarkdownEditor/Styles/Mixins.scss +7 -0
  30. package/src/components/MarkdownEditor/TipTap/SingleLineExtension.ts +21 -0
  31. package/src/components/MarkdownEditor/Types/MarkdownAstNode.ts +22 -0
  32. package/src/components/MarkdownEditor/Types/MarkdownAstNodeType.ts +28 -0
  33. package/src/components/MarkdownEditor/Types/TextishEmits.ts +14 -0
  34. package/src/components/MarkdownEditor/index.ts +1 -0
  35. package/src/components/index.ts +1 -0
  36. package/src/index.ts +1 -0
  37. package/tsconfig.app.json +11 -0
  38. package/tsconfig.json +11 -0
  39. package/tsconfig.node.json +12 -0
  40. package/vite.config.ts +24 -0
  41. package/vitest.config.ts +21 -0
@@ -0,0 +1,60 @@
1
+ <template>
2
+ <EditorContent
3
+ ref="editorRef"
4
+ :editor="editor"
5
+ />
6
+ </template>
7
+
8
+ <script lang="ts" setup>
9
+ import StarterKit from "@tiptap/starter-kit";
10
+ import { EditorContent, useEditor } from "@tiptap/vue-3";
11
+ import { ref } from "vue";
12
+ import useReflectiveState from "../Composable/useReflectiveState";
13
+ import { HeadingDocument, PreventNewline } from "../TipTap/SingleLineExtension";
14
+ import type { TextishEmits } from "../Types/TextishEmits";
15
+ import type MarkdownModuleTextState from "./MarkdownModuleTextState";
16
+
17
+ const modelValue = defineModel<MarkdownModuleTextState>({
18
+ required: true,
19
+ });
20
+
21
+ const editorRef = ref<InstanceType<typeof EditorContent>>();
22
+
23
+ const emit = defineEmits<TextishEmits>();
24
+
25
+ const state = useReflectiveState({
26
+ modelRef: modelValue,
27
+ emit,
28
+ editorRef,
29
+ });
30
+
31
+ const editor = useEditor({
32
+ extensions: [
33
+ HeadingDocument,
34
+ StarterKit.configure({
35
+ document: false,
36
+ paragraph: false,
37
+ heading: {
38
+ levels: [2],
39
+ },
40
+ codeBlock: false,
41
+ blockquote: false,
42
+ horizontalRule: false,
43
+ hardBreak: false,
44
+ bulletList: false,
45
+ orderedList: false,
46
+ listItem: false,
47
+ }),
48
+ PreventNewline,
49
+ ],
50
+ content: `<h2>${state.editorContent.value}</h2>`,
51
+ onUpdate: (event) => state.handleTipTapUpdateEvent(event),
52
+ });
53
+
54
+ defineExpose(state.expose);
55
+ </script>
56
+
57
+ <style lang="scss" scoped>
58
+ @use "../Styles/Mixins.scss" as *;
59
+ @include reset-contenteditable;
60
+ </style>
@@ -0,0 +1,61 @@
1
+ <template>
2
+ <EditorContent
3
+ ref="editorRef"
4
+ :editor="editor"
5
+ />
6
+ </template>
7
+
8
+ <script lang="ts" setup>
9
+ import StarterKit from "@tiptap/starter-kit";
10
+ import { EditorContent, useEditor } from "@tiptap/vue-3";
11
+ import { ref } from "vue";
12
+ import useReflectiveState from "../Composable/useReflectiveState";
13
+ import { HeadingDocument, PreventNewline } from "../TipTap/SingleLineExtension";
14
+ import type { TextishEmits } from "../Types/TextishEmits";
15
+ import type MarkdownModuleTextState from "./MarkdownModuleTextState";
16
+
17
+ const modelValue = defineModel<MarkdownModuleTextState>({
18
+ required: true,
19
+ });
20
+
21
+ const editorRef = ref<InstanceType<typeof EditorContent>>();
22
+
23
+ const emit = defineEmits<TextishEmits>();
24
+
25
+ const state = useReflectiveState({
26
+ modelRef: modelValue,
27
+ emit,
28
+ editorRef,
29
+ });
30
+
31
+ const editor = useEditor({
32
+ extensions: [
33
+ HeadingDocument,
34
+ StarterKit.configure({
35
+
36
+ document: false,
37
+ paragraph: false,
38
+ heading: {
39
+ levels: [3],
40
+ },
41
+ codeBlock: false,
42
+ blockquote: false,
43
+ horizontalRule: false,
44
+ hardBreak: false,
45
+ bulletList: false,
46
+ orderedList: false,
47
+ listItem: false,
48
+ }),
49
+ PreventNewline,
50
+ ],
51
+ content: `<h3>${state.editorContent.value}</h3>`,
52
+ onUpdate: (event) => state.handleTipTapUpdateEvent(event),
53
+ });
54
+
55
+ defineExpose(state.expose);
56
+ </script>
57
+
58
+ <style lang="scss" scoped>
59
+ @use "../Styles/Mixins.scss" as *;
60
+ @include reset-contenteditable;
61
+ </style>
@@ -0,0 +1,185 @@
1
+ <template>
2
+ <div
3
+ ref="divRef"
4
+ class="markdown-module-image"
5
+ @click="handleClick"
6
+ >
7
+ <img
8
+ :src="modelValue.src"
9
+ :alt="modelValue.alt"
10
+ >
11
+ <span v-if="modelValue.alt">{{ modelValue.caption }}</span>
12
+
13
+ <MarkdownEditorImageContextMenu
14
+ v-if="showContextMenu"
15
+ :x="contextMenuPosition.x"
16
+ :y="contextMenuPosition.y"
17
+ @edit-attributes="openAttributesModal"
18
+ />
19
+
20
+ <MarkdownEditorModal
21
+ :show="showModal"
22
+ title="Edit Image Attributes"
23
+ @close="closeModal"
24
+ @confirm="saveAttributes"
25
+ >
26
+ <div class="markdown-module-image-form">
27
+ <div class="markdown-module-image-form-field">
28
+ <label for="image-src">Image Source (URL)</label>
29
+ <input
30
+ id="image-src"
31
+ v-model="editForm.src"
32
+ type="text"
33
+ placeholder="https://example.com/image.jpg"
34
+ >
35
+ </div>
36
+
37
+ <div class="markdown-module-image-form-field">
38
+ <label for="image-alt">Alt Text</label>
39
+ <input
40
+
41
+ id="image-alt"
42
+ v-model="editForm.alt"
43
+ type="text"
44
+ placeholder="Description of the image"
45
+ >
46
+ </div>
47
+
48
+ <div class="markdown-module-image-form-field">
49
+ <label for="image-caption">Caption</label>
50
+ <input
51
+ id="image-caption"
52
+ v-model="editForm.caption"
53
+ type="text"
54
+ placeholder="Optional caption"
55
+ >
56
+ </div>
57
+ </div>
58
+ </MarkdownEditorModal>
59
+ </div>
60
+ </template>
61
+
62
+ <script lang="ts" setup>
63
+ import { ref } from "vue";
64
+ import MarkdownEditorModal from "../MarkdownEditorModal.vue";
65
+ import type MarkdownModuleImageState from "./MarkdownModuleImageState";
66
+
67
+ const divRef = ref<HTMLDivElement>();
68
+
69
+ const modelValue = defineModel<MarkdownModuleImageState>({
70
+ required: true,
71
+ });
72
+
73
+ // Context menu state
74
+ const showContextMenu = ref(false);
75
+ const contextMenuPosition = ref({ x: 0, y: 0 });
76
+
77
+ // Modal state
78
+ const showModal = ref(false);
79
+ const editForm = ref({
80
+ src: "",
81
+ alt: "",
82
+ caption: "",
83
+ });
84
+
85
+ function handleClick(event: MouseEvent) {
86
+ const rect = (event.currentTarget as HTMLElement).getBoundingClientRect();
87
+ contextMenuPosition.value = {
88
+ x: event.clientX,
89
+ y: rect.bottom + 5,
90
+ };
91
+ showContextMenu.value = true;
92
+ }
93
+
94
+ function openAttributesModal() {
95
+ showContextMenu.value = false;
96
+ editForm.value = {
97
+ src: modelValue.value.src || "",
98
+ alt: modelValue.value.alt || "",
99
+ caption: modelValue.value.caption || "",
100
+ };
101
+ showModal.value = true;
102
+ }
103
+
104
+ function closeModal() {
105
+ showModal.value = false;
106
+ }
107
+
108
+ function saveAttributes() {
109
+ modelValue.value.src = editForm.value.src;
110
+ modelValue.value.alt = editForm.value.alt;
111
+ modelValue.value.caption = editForm.value.caption;
112
+ showModal.value = false;
113
+ }
114
+
115
+ function focus() {
116
+ if (divRef.value) {
117
+ divRef.value.focus();
118
+ }
119
+ }
120
+
121
+ defineExpose({ focus });
122
+ </script>
123
+
124
+ <style lang="scss" scoped>
125
+ .markdown-module-image {
126
+ cursor: pointer;
127
+ position: relative;
128
+ text-align: center;
129
+
130
+ img {
131
+ max-width: 100%;
132
+ height: auto;
133
+ }
134
+
135
+ span {
136
+ display: block;
137
+ margin-top: 0.25rem;
138
+ font-size: 0.875rem;
139
+ color: #6b7280;
140
+ }
141
+ }
142
+
143
+ .markdown-module-image-form {
144
+ display: flex;
145
+ flex-direction: column;
146
+ gap: 1.25rem;
147
+ }
148
+
149
+ .markdown-module-image-form-field {
150
+ display: flex;
151
+ flex-direction: column;
152
+ gap: 0.5rem;
153
+
154
+ label {
155
+ font-size: 0.875rem;
156
+ font-weight: 500;
157
+ color: #374151;
158
+ }
159
+
160
+ input {
161
+ padding: 0.625rem 0.875rem;
162
+ font-size: 0.875rem;
163
+ border: 1px solid #d1d5db;
164
+ border-radius: 0.5rem;
165
+ background: #ffffff;
166
+ color: #111827;
167
+ transition: all 0.15s ease;
168
+
169
+ &:focus {
170
+ outline: none;
171
+ border-color: #3b82f6;
172
+ box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.1);
173
+ }
174
+
175
+ &::placeholder {
176
+ color: #9ca3af;
177
+ }
178
+ }
179
+ }
180
+ </style>
181
+
182
+ <style lang="scss" scoped>
183
+ @use "../Styles/Mixins.scss" as *;
184
+ @include reset-contenteditable;
185
+ </style>
@@ -0,0 +1,10 @@
1
+ export default class MarkdownModuleImageState {
2
+ src: string;
3
+ alt: string;
4
+ caption: string;
5
+
6
+ constructor(object: MarkdownModuleImageState) {
7
+ Object.assign(this, object);
8
+ }
9
+ }
10
+
@@ -0,0 +1,7 @@
1
+ <template>
2
+ <div />
3
+ </template>
4
+
5
+ <script lang="ts" setup></script>
6
+
7
+ <style></style>
@@ -0,0 +1,10 @@
1
+ import type MarkdownModuleTextState from "./MarkdownModuleTextState";
2
+
3
+ export default class MarkdownModuleListState {
4
+ items: MarkdownModuleTextState[];
5
+
6
+ constructor(object: MarkdownModuleListState) {
7
+ Object.assign(this, object);
8
+ }
9
+ }
10
+
@@ -0,0 +1,54 @@
1
+ <template>
2
+ <EditorContent
3
+ ref="editorRef"
4
+ :editor="editor"
5
+ />
6
+ </template>
7
+
8
+ <script lang="ts" setup>
9
+ import { Placeholder } from "@tiptap/extensions";
10
+ import StarterKit from "@tiptap/starter-kit";
11
+ import { EditorContent, useEditor } from "@tiptap/vue-3";
12
+ import { ref } from "vue";
13
+ import useReflectiveState from "../Composable/useReflectiveState";
14
+ import { PreventNewline } from "../TipTap/SingleLineExtension";
15
+ import type { TextishEmits } from "../Types/TextishEmits";
16
+ import type MarkdownModuleTextState from "./MarkdownModuleTextState";
17
+
18
+ const modelValue = defineModel<MarkdownModuleTextState>({
19
+ required: true,
20
+ });
21
+
22
+ const editorRef = ref<InstanceType<typeof EditorContent>>();
23
+
24
+ const emit = defineEmits<TextishEmits>();
25
+
26
+ const state = useReflectiveState({
27
+ modelRef: modelValue,
28
+ emit,
29
+ editorRef,
30
+ });
31
+
32
+ const editor = useEditor({
33
+ extensions: [
34
+ Placeholder.configure({
35
+ placeholder: "Type something...",
36
+ }),
37
+ StarterKit.configure({
38
+ heading: false,
39
+ codeBlock: false,
40
+ blockquote: false,
41
+ horizontalRule: false,
42
+ hardBreak: false,
43
+ }),
44
+ PreventNewline,
45
+ ],
46
+ content: state.editorContent.value,
47
+ onUpdate: (event) => state.handleTipTapUpdateEvent(event),
48
+ });
49
+
50
+ defineExpose(state.expose);
51
+ </script>
52
+
53
+ <style lang="scss" scoped>
54
+ </style>
@@ -0,0 +1,8 @@
1
+ export default class MarkdownModuleTextState {
2
+ text: string;
3
+
4
+ constructor(object: MarkdownModuleTextState) {
5
+ Object.assign(this, object);
6
+ }
7
+ }
8
+
@@ -0,0 +1,20 @@
1
+ import { shallowMount } from "@vue/test-utils";
2
+ import { describe, expect, it } from "vitest";
3
+ import MarkdownModuleHeadline1 from "../MarkdownModuleHeadline1.vue";
4
+ import MarkdownModuleTextState from "../MarkdownModuleTextState";
5
+ describe("MarkdownModuleHeadline1", () => {
6
+ function getComponent() {
7
+ return shallowMount(MarkdownModuleHeadline1, {
8
+ props: {
9
+ modelValue: new MarkdownModuleTextState({ text: "" }),
10
+ },
11
+ });
12
+ }
13
+ it("renders correctly with default props", async () => {
14
+ // Arrange
15
+ const wrapper = getComponent();
16
+ // Act
17
+ // Assert
18
+ expect(wrapper.element).toMatchSnapshot();
19
+ });
20
+ });
@@ -0,0 +1,25 @@
1
+ import { shallowMount } from "@vue/test-utils";
2
+ import { describe, expect, it } from "vitest";
3
+ import MarkdownModuleHeadline1 from "../MarkdownModuleHeadline1.vue";
4
+ import MarkdownModuleTextState from "../MarkdownModuleTextState";
5
+
6
+ describe("MarkdownModuleHeadline1", () => {
7
+ function getComponent() {
8
+ return shallowMount(MarkdownModuleHeadline1, {
9
+ props: {
10
+ modelValue: new MarkdownModuleTextState({ text: "" }),
11
+ },
12
+ });
13
+ }
14
+
15
+ it("renders correctly with default props", async () => {
16
+ // Arrange
17
+ const wrapper = getComponent();
18
+
19
+ // Act
20
+
21
+ // Assert
22
+ expect(wrapper.element).toMatchSnapshot();
23
+ });
24
+ });
25
+
@@ -0,0 +1,7 @@
1
+ // Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
2
+
3
+ exports[`MarkdownModuleHeadline1 > renders correctly with default props 1`] = `
4
+ <editor-content-stub
5
+ data-v-e0053ac5=""
6
+ />
7
+ `;
@@ -0,0 +1,7 @@
1
+ @mixin reset-contenteditable {
2
+ * {
3
+ outline: none;
4
+ border: none;
5
+ background: transparent;
6
+ }
7
+ }
@@ -0,0 +1,21 @@
1
+ import { Extension } from "@tiptap/core";
2
+ import { Document } from "@tiptap/extension-document";
3
+
4
+ export const SingleLineDocument = Document.extend({
5
+ content: "block",
6
+ });
7
+
8
+ export const HeadingDocument = Document.extend({
9
+ content: "heading+",
10
+ });
11
+
12
+ export const PreventNewline = Extension.create({
13
+ name: "preventNewline",
14
+ addKeyboardShortcuts() {
15
+ return {
16
+ "Enter": () => true,
17
+ "Shift-Enter": () => true,
18
+ };
19
+ },
20
+ });
21
+
@@ -0,0 +1,22 @@
1
+ import type MarkdownModuleImageState from "../Modules/MarkdownModuleImageState";
2
+ import type MarkdownModuleTextState from "../Modules/MarkdownModuleTextState";
3
+ import type MarkdownNodeType from "./MarkdownAstNodeType";
4
+
5
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
6
+ export class MarkdownAstNode<TState extends object = Record<string, any>> {
7
+ id: symbol;
8
+ type: MarkdownNodeType;
9
+ componentState: TState;
10
+
11
+ editingState: {
12
+ cursorPosition: number;
13
+ };
14
+
15
+ constructor(value: Omit<MarkdownAstNode<TState>, "id">) {
16
+ Object.assign(this, value);
17
+ this.id = Symbol();
18
+ }
19
+ }
20
+
21
+ export type TextNode = MarkdownAstNode<MarkdownModuleTextState>;
22
+ export type ImageNode = MarkdownAstNode<MarkdownModuleImageState>;
@@ -0,0 +1,28 @@
1
+ enum MarkdownNodeType {
2
+ PARAGRAPH,
3
+ HEADLINE1,
4
+ HEADLINE2,
5
+ HEADLINE3,
6
+ IMAGE,
7
+ LIST,
8
+ }
9
+
10
+ export type TextishNodeType
11
+ = | MarkdownNodeType.PARAGRAPH
12
+ | MarkdownNodeType.LIST
13
+ | MarkdownNodeType.HEADLINE1
14
+ | MarkdownNodeType.HEADLINE2
15
+ | MarkdownNodeType.HEADLINE3;
16
+
17
+ export function isTextNodeType(type: MarkdownNodeType): type is TextishNodeType {
18
+ return (
19
+ type === MarkdownNodeType.PARAGRAPH
20
+ || type === MarkdownNodeType.LIST
21
+ || type === MarkdownNodeType.HEADLINE1
22
+ || type === MarkdownNodeType.HEADLINE2
23
+ || type === MarkdownNodeType.HEADLINE3
24
+ );
25
+ }
26
+
27
+ export default MarkdownNodeType;
28
+
@@ -0,0 +1,14 @@
1
+ import type MarkdownModuleTextState from "../Modules/MarkdownModuleTextState";
2
+ import type MarkdownAstNodeType from "./MarkdownAstNodeType";
3
+
4
+ export interface TextishEmits {
5
+ "update:model-value": [componentState: MarkdownModuleTextState];
6
+ "update:cursor-position": [cursorPosition: number];
7
+ "change-type": [newType: MarkdownAstNodeType];
8
+ }
9
+
10
+ export interface TextishEmitFunction {
11
+ (event: "update:model-value", value: MarkdownModuleTextState): void;
12
+ (event: "update:cursor-position", value: number): void;
13
+ (event: "change-type", value: MarkdownAstNodeType): void;
14
+ }
@@ -0,0 +1 @@
1
+ export { default as MarkdownEditor } from './MarkdownEditor.vue';
@@ -0,0 +1 @@
1
+ export * from './MarkdownEditor/';
package/src/index.ts ADDED
@@ -0,0 +1 @@
1
+ export * from './components/';
@@ -0,0 +1,11 @@
1
+ {
2
+ "extends": "@vue/tsconfig/tsconfig.dom.json",
3
+ "include": ["src/**/*", "src/**/*.vue"],
4
+ "exclude": ["src/**/__tests__/*"],
5
+ "compilerOptions": {
6
+ "strict": true,
7
+ "paths": {
8
+ "@/*": ["./src/*"]
9
+ },
10
+ }
11
+ }
package/tsconfig.json ADDED
@@ -0,0 +1,11 @@
1
+ {
2
+ "files": [],
3
+ "references": [
4
+ {
5
+ "path": "./tsconfig.node.json"
6
+ },
7
+ {
8
+ "path": "./tsconfig.app.json"
9
+ },
10
+ ]
11
+ }
@@ -0,0 +1,12 @@
1
+ {
2
+ "extends": "@tsconfig/node22/tsconfig.json",
3
+ "include": ["vite.config.ts", "eslint.config.ts"],
4
+ "compilerOptions": {
5
+ "noEmit": true,
6
+ "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo",
7
+
8
+ "module": "ESNext",
9
+ "moduleResolution": "Bundler",
10
+ "types": ["node"]
11
+ }
12
+ }
package/vite.config.ts ADDED
@@ -0,0 +1,24 @@
1
+ import { defineConfig } from 'vite';
2
+ import vue from '@vitejs/plugin-vue';
3
+ import { fileURLToPath, URL } from 'node:url';
4
+ import { resolve } from 'node:path';
5
+
6
+ export default defineConfig({
7
+ plugins: [vue()],
8
+ resolve: {
9
+ alias: {
10
+ '@': fileURLToPath(new URL('./src', import.meta.url)),
11
+ },
12
+ },
13
+ build: {
14
+ emptyOutDir: false,
15
+ lib: {
16
+ formats: ['es'],
17
+ entry: resolve(import.meta.dirname, 'src', 'index.ts'),
18
+ fileName: 'vue-markdown-editor',
19
+ },
20
+ rollupOptions: {
21
+ external: ['vue', 'date-fns', '@date-fns/tz', '@floating-ui/vue', '@vueuse/core'],
22
+ },
23
+ },
24
+ });
@@ -0,0 +1,21 @@
1
+ import { fileURLToPath } from 'node:url';
2
+ import { mergeConfig, defineConfig, configDefaults } from 'vitest/config';
3
+ import viteConfig from './vite.config';
4
+ import { resolve } from 'node:path';
5
+
6
+ export default mergeConfig(
7
+ viteConfig,
8
+ defineConfig({
9
+ test: {
10
+ environment: 'happy-dom',
11
+ include: ['src/**/*.test.ts'],
12
+ root: fileURLToPath(new URL('./', import.meta.url)),
13
+ coverage: {
14
+ reporter: ['lcov'],
15
+ reportsDirectory: resolve(__dirname, 'coverage'),
16
+ include: ['src/**/*.ts', 'src/**/*.vue'],
17
+ exclude: ['src/types'],
18
+ },
19
+ },
20
+ }),
21
+ );