@bagelink/vue 0.0.1025 → 0.0.1031

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 (70) hide show
  1. package/README.md +1 -0
  2. package/dist/components/Image.vue.d.ts.map +1 -1
  3. package/dist/components/ToolBar.vue.d.ts +3 -0
  4. package/dist/components/ToolBar.vue.d.ts.map +1 -0
  5. package/dist/components/form/inputs/NumberInput.vue.d.ts.map +1 -1
  6. package/dist/components/form/inputs/RangeInput.vue.d.ts +2 -2
  7. package/dist/components/form/inputs/RangeInput.vue.d.ts.map +1 -1
  8. package/dist/components/form/inputs/RichText/components/EditorToolbar.vue.d.ts +12 -0
  9. package/dist/components/form/inputs/RichText/components/EditorToolbar.vue.d.ts.map +1 -0
  10. package/dist/components/form/inputs/RichText/composables/useCommands.d.ts +9 -0
  11. package/dist/components/form/inputs/RichText/composables/useCommands.d.ts.map +1 -0
  12. package/dist/components/form/inputs/RichText/composables/useEditor.d.ts +40 -20
  13. package/dist/components/form/inputs/RichText/composables/useEditor.d.ts.map +1 -1
  14. package/dist/components/form/inputs/RichText/composables/useEditorKeyboard.d.ts.map +1 -1
  15. package/dist/components/form/inputs/RichText/config.d.ts.map +1 -1
  16. package/dist/components/form/inputs/RichText/index.vue.d.ts +1 -0
  17. package/dist/components/form/inputs/RichText/index.vue.d.ts.map +1 -1
  18. package/dist/components/form/inputs/RichText/utils/commands.d.ts +17 -0
  19. package/dist/components/form/inputs/RichText/utils/commands.d.ts.map +1 -0
  20. package/dist/components/form/inputs/RichText/utils/debug.d.ts +48 -0
  21. package/dist/components/form/inputs/RichText/utils/debug.d.ts.map +1 -0
  22. package/dist/components/form/inputs/RichText/utils/formatting.d.ts +3 -1
  23. package/dist/components/form/inputs/RichText/utils/formatting.d.ts.map +1 -1
  24. package/dist/components/form/inputs/RichText/utils/selection.d.ts +13 -0
  25. package/dist/components/form/inputs/RichText/utils/selection.d.ts.map +1 -1
  26. package/dist/components/form/inputs/RichText/utils/table.d.ts +4 -0
  27. package/dist/components/form/inputs/RichText/utils/table.d.ts.map +1 -1
  28. package/dist/components/form/inputs/SignaturePad.vue.d.ts +2 -2
  29. package/dist/components/form/inputs/SignaturePad.vue.d.ts.map +1 -1
  30. package/dist/components/form/inputs/Upload/UploadFile.vue.d.ts +86 -0
  31. package/dist/components/form/inputs/Upload/UploadFile.vue.d.ts.map +1 -0
  32. package/dist/components/form/inputs/Upload/upload.d.ts +13 -0
  33. package/dist/components/form/inputs/Upload/upload.d.ts.map +1 -0
  34. package/dist/components/form/inputs/index.d.ts +1 -0
  35. package/dist/components/form/inputs/index.d.ts.map +1 -1
  36. package/dist/components/index.d.ts +1 -0
  37. package/dist/components/index.d.ts.map +1 -1
  38. package/dist/editor-B3mMCQSg.cjs +4 -0
  39. package/dist/editor-BKPRpAjr.js +4 -0
  40. package/dist/index.cjs +10059 -6970
  41. package/dist/index.mjs +10065 -6976
  42. package/dist/plugins/bagel.d.ts +1 -0
  43. package/dist/plugins/bagel.d.ts.map +1 -1
  44. package/dist/style.css +666 -458
  45. package/package.json +1 -1
  46. package/src/components/Image.vue +1 -2
  47. package/src/components/ToolBar.vue +9 -0
  48. package/src/components/form/inputs/NumberInput.vue +8 -1
  49. package/src/components/form/inputs/RangeInput.vue +6 -5
  50. package/src/components/form/inputs/RichText/composables/useCommands.ts +114 -0
  51. package/src/components/form/inputs/RichText/composables/useEditor.ts +257 -129
  52. package/src/components/form/inputs/RichText/composables/useEditorKeyboard.ts +64 -19
  53. package/src/components/form/inputs/RichText/config.ts +18 -2
  54. package/src/components/form/inputs/RichText/editor.css +17 -15
  55. package/src/components/form/inputs/RichText/index.vue +67 -13
  56. package/src/components/form/inputs/RichText/richTextTypes.d.ts +2 -0
  57. package/src/components/form/inputs/RichText/utils/commands.ts +37 -0
  58. package/src/components/form/inputs/RichText/utils/debug.ts +196 -0
  59. package/src/components/form/inputs/RichText/utils/formatting.ts +168 -288
  60. package/src/components/form/inputs/RichText/utils/selection.ts +77 -0
  61. package/src/components/form/inputs/RichText/utils/table.ts +66 -0
  62. package/src/components/form/inputs/SignaturePad.vue +2 -2
  63. package/src/components/form/inputs/Upload/UploadFile.vue +357 -0
  64. package/src/components/form/inputs/Upload/upload.css +232 -0
  65. package/src/components/form/inputs/Upload/upload.ts +22 -0
  66. package/src/components/form/inputs/Upload/upload.types.d.ts +43 -0
  67. package/src/components/form/inputs/index.ts +1 -0
  68. package/src/components/index.ts +2 -0
  69. package/src/plugins/bagel.ts +2 -2
  70. /package/src/components/form/inputs/RichText/components/{Toolbar.vue → EditorToolbar.vue} +0 -0
@@ -1,21 +1,66 @@
1
-
2
1
  export function useEditorKeyboard(doc: Document, handleToolbarAction: (action: string, value?: string) => void): void {
3
- doc.addEventListener('keydown', (e) => {
4
- if (e.ctrlKey || e.metaKey) {
5
- switch (e.key) {
6
- case 'b':
7
- e.preventDefault()
8
- handleToolbarAction('bold')
9
- break
10
- case 'i':
11
- e.preventDefault()
12
- handleToolbarAction('italic')
13
- break
14
- case 'u':
15
- e.preventDefault()
16
- handleToolbarAction('underline')
17
- break
18
- }
19
- }
20
- })
2
+ doc.addEventListener('keydown', (e) => {
3
+ if (e.ctrlKey || e.metaKey) {
4
+ switch (e.key) {
5
+ case 'b':
6
+ e.preventDefault()
7
+ handleToolbarAction('bold')
8
+ break
9
+ case 'i':
10
+ e.preventDefault()
11
+ handleToolbarAction('italic')
12
+ break
13
+ case 'u':
14
+ e.preventDefault()
15
+ handleToolbarAction('underline')
16
+ break
17
+ case 'z':
18
+ e.preventDefault()
19
+ if (e.shiftKey) {
20
+ handleToolbarAction('redo')
21
+ } else {
22
+ handleToolbarAction('undo')
23
+ }
24
+ break
25
+ case 'y':
26
+ e.preventDefault()
27
+ handleToolbarAction('redo')
28
+ break
29
+ // List shortcuts
30
+ case '.':
31
+ if (e.shiftKey) {
32
+ e.preventDefault()
33
+ handleToolbarAction('orderedList')
34
+ }
35
+ break
36
+ case '/':
37
+ if (e.shiftKey) {
38
+ e.preventDefault()
39
+ handleToolbarAction('unorderedList')
40
+ }
41
+ break
42
+ // Heading shortcuts
43
+ case '1':
44
+ case '2':
45
+ case '3':
46
+ case '4':
47
+ case '5':
48
+ case '6':
49
+ if (e.altKey) {
50
+ e.preventDefault()
51
+ handleToolbarAction(`h${e.key}`)
52
+ }
53
+ break
54
+ // Indentation
55
+ case ']':
56
+ e.preventDefault()
57
+ handleToolbarAction('indent')
58
+ break
59
+ case '[':
60
+ e.preventDefault()
61
+ handleToolbarAction('outdent')
62
+ break
63
+ }
64
+ }
65
+ })
21
66
  }
@@ -5,7 +5,15 @@ export const tableTools: ToolbarConfig = [
5
5
  'splitCells',
6
6
  'addRowBefore',
7
7
  'addRowAfter',
8
- 'deleteRow'
8
+ 'deleteRow',
9
+ 'insertColumnLeft',
10
+ 'insertColumnRight',
11
+ 'deleteColumn',
12
+ 'alignLeft',
13
+ 'alignCenter',
14
+ 'alignRight',
15
+ 'alignJustify',
16
+ 'deleteTable'
9
17
  ]
10
18
 
11
19
  export const defaultToolbarConfig: ToolbarConfig = [
@@ -19,10 +27,15 @@ export const defaultToolbarConfig: ToolbarConfig = [
19
27
  'italic',
20
28
  'underline',
21
29
  'separator',
30
+ 'fontColor',
31
+ 'bgColor',
32
+ 'separator',
22
33
  'p',
23
34
  'blockquote',
24
35
  'orderedList',
25
36
  'unorderedList',
37
+ 'indent',
38
+ 'outdent',
26
39
  'separator',
27
40
  'link',
28
41
  'image',
@@ -31,7 +44,10 @@ export const defaultToolbarConfig: ToolbarConfig = [
31
44
  'splitView',
32
45
  'clear',
33
46
  'insertTable',
34
- // ...tableTools,
47
+ ...tableTools,
48
+ 'separator',
49
+ 'undo',
50
+ 'redo',
35
51
  'fullScreen',
36
52
  ]
37
53
 
@@ -1,26 +1,28 @@
1
1
  body {
2
- margin: 0;
3
- padding: 8px;
4
- min-height: 200px;
5
- font-family: sans-serif;
6
- line-height: 1.5;
7
- color: inherit;
8
- background: transparent;
2
+ margin: 0;
3
+ padding: 8px;
4
+ min-height: 200px;
5
+ font-family: sans-serif;
6
+ line-height: 1.5;
7
+ color: inherit;
8
+ background: transparent;
9
+ max-width: 1060px;
10
+ margin: 0 auto;
9
11
  }
10
12
 
11
13
  table {
12
- border-collapse: collapse;
13
- margin-bottom: 1rem;
14
+ border-collapse: collapse;
15
+ margin-bottom: 1rem;
14
16
  }
15
17
 
16
18
  th,
17
19
  td {
18
- padding: 1rem;
19
- text-align: left;
20
- border: 1px solid #2a2a2a;
21
- line-height: 1.5;
20
+ padding: 1rem;
21
+ text-align: left;
22
+ border: 1px solid #2a2a2a;
23
+ line-height: 1.5;
22
24
  }
23
25
 
24
26
  th {
25
- background-color: #f4f4f4;
26
- }
27
+ background-color: #f4f4f4;
28
+ }
@@ -1,17 +1,21 @@
1
1
  <script setup lang="ts">
2
2
  import type { ToolbarConfig } from './richTextTypes'
3
- import { useModal, CodeEditor } from '@bagelink/vue'
4
- import { watch } from 'vue'
5
- import Toolbar from './components/Toolbar.vue'
3
+ import { CodeEditor, copyText, Btn } from '@bagelink/vue'
4
+ import { watch, computed } from 'vue'
5
+ import EditorToolbar from './components/EditorToolbar.vue'
6
+ import { useCommands } from './composables/useCommands'
6
7
  import { useEditor } from './composables/useEditor'
7
8
  import { useEditorKeyboard } from './composables/useEditorKeyboard'
8
9
 
9
- const props = defineProps<{ modelValue: string, toolbarConfig?: ToolbarConfig }>()
10
+ const props = defineProps<{ modelValue: string, toolbarConfig?: ToolbarConfig, debug?: boolean }>()
10
11
  const emit = defineEmits(['update:modelValue'])
11
12
 
12
13
  const iframe = $ref<HTMLIFrameElement>()
13
- const modal = useModal()
14
14
  const editor = useEditor()
15
+ const commands = useCommands(editor.state, props.debug ? editor.debug : undefined)
16
+
17
+ // Expose debug methods if debug mode is enabled
18
+ const debugMethods = computed(() => props.debug ? editor.debug : undefined)
15
19
 
16
20
  async function initEditor() {
17
21
  if (!iframe) {
@@ -25,14 +29,44 @@ async function initEditor() {
25
29
  doc.designMode = 'on'
26
30
  doc.body.contentEditable = 'true'
27
31
 
32
+ // Set default direction based on content
33
+ const hasRTL = /[\u0591-\u07FF\uFB1D-\uFDFD\uFE70-\uFEFC]/.test(props.modelValue)
34
+ doc.body.dir = hasRTL ? 'rtl' : 'ltr'
35
+
28
36
  const style = doc.createElement('style')
29
37
  style.textContent = (await import('./editor.css?inline')).default
30
38
  doc.head.appendChild(style)
31
39
 
32
- editor.init(doc, modal)
33
- useEditorKeyboard(doc, (action, value) => {
34
- editor.handleToolbarAction(action, value)
35
- })
40
+ editor.init(doc)
41
+ useEditorKeyboard(doc, commands.execute)
42
+
43
+ // Initial cleanup and ensure there's at least one paragraph
44
+ if (!doc.body.firstElementChild) {
45
+ const p = doc.createElement('p')
46
+ p.dir = doc.body.dir
47
+ p.innerHTML = '<br>'
48
+ doc.body.appendChild(p)
49
+ } else {
50
+ // Convert any direct text nodes to paragraphs
51
+ const walker = doc.createTreeWalker(doc.body, NodeFilter.SHOW_TEXT)
52
+ const textNodes: Text[] = []
53
+ let node: Node | null
54
+ while (node = walker.nextNode()) {
55
+ if (node.parentElement === doc.body) {
56
+ textNodes.push(node as Text)
57
+ }
58
+ }
59
+ textNodes.forEach((textNode) => {
60
+ if (textNode.textContent?.trim()) {
61
+ const p = doc.createElement('p')
62
+ p.dir = doc.body.dir
63
+ p.appendChild(textNode.cloneNode())
64
+ doc.body.replaceChild(p, textNode)
65
+ } else {
66
+ doc.body.removeChild(textNode)
67
+ }
68
+ })
69
+ }
36
70
 
37
71
  doc.body.focus()
38
72
  }
@@ -51,19 +85,33 @@ watch(() => editor.state.content, (newValue) => {
51
85
 
52
86
  <template>
53
87
  <div class="rich-text-editor rounded pt-05 px-05 pb-075 mb-05" :class="{ 'fullscreen-mode': editor.state.isFullscreen }">
54
- <Toolbar
88
+ <EditorToolbar
55
89
  v-if="editor.state.hasInit" :config="toolbarConfig" :selectedStyles="editor.state.selectedStyles"
56
- @action="editor.handleToolbarAction"
90
+ @action="commands.execute"
57
91
  />
58
- <div class="editor-container" :class="{ 'split-view': editor?.state.isSplitView }">
92
+ <div class="editor-container" :class="{ 'split-view': editor.state.isSplitView }">
59
93
  <div class="content-area radius-05">
60
94
  <iframe id="rich-text-iframe" ref="iframe" class="editableContent" title="Editor" @load="initEditor" />
61
95
  </div>
62
96
  <CodeEditor
63
- v-if="editor?.state.isSplitView" v-model="editor.state.content" language="html"
97
+ v-if="editor.state.isSplitView" v-model="editor.state.content" language="html"
64
98
  @update:modelValue="editor.updateContent('html')"
65
99
  />
66
100
  </div>
101
+ <div v-if="debug" class="flex">
102
+ <p class="text12 txt-gray mb-0 p-0">
103
+ Debug
104
+ </p>
105
+ <Btn thin color="gray" icon="delete" @click="debugMethods?.clearSession">
106
+ Clear Session
107
+ </Btn>
108
+ <Btn thin color="gray" icon="download" @click="debugMethods?.downloadSession">
109
+ Download Log
110
+ </Btn>
111
+ <Btn thin color="gray" icon="content_copy" @click="copyText(debugMethods?.exportDebugWithPrompt() || '')">
112
+ Copy Log
113
+ </Btn>
114
+ </div>
67
115
  </div>
68
116
  </template>
69
117
 
@@ -148,4 +196,10 @@ line-height: 1.65;
148
196
  .fullscreen-mode .code-editor{
149
197
  height: 100% !important;
150
198
  }
199
+
200
+ .debug-controls {
201
+ display: flex;
202
+ gap: 0.5rem;
203
+ justify-content: flex-end;
204
+ }
151
205
  </style>
@@ -20,6 +20,8 @@ export interface EditorState {
20
20
  range?: Range | null
21
21
  doc?: Document
22
22
  modal?: Modal
23
+ undoStack: string[]
24
+ redoStack: string[]
23
25
  }
24
26
 
25
27
  export type FormattingCommand =
@@ -0,0 +1,37 @@
1
+ import type { EditorState } from '../richTextTypes'
2
+
3
+ export interface Command {
4
+ name: string
5
+ execute: (state: EditorState, value?: string) => void
6
+ isActive?: (state: EditorState) => boolean
7
+ getValue?: (state: EditorState) => string | null
8
+ }
9
+
10
+ export interface CommandRegistry {
11
+ [key: string]: Command
12
+ }
13
+
14
+ export interface CommandExecutor {
15
+ execute: (command: string, value?: string) => void
16
+ isActive: (command: string) => boolean
17
+ getValue: (command: string) => string | null
18
+ }
19
+
20
+ export function createCommandExecutor(state: EditorState, commands: CommandRegistry): CommandExecutor {
21
+ return {
22
+ execute(command: string, value?: string) {
23
+ const cmd = commands[command]
24
+ if (cmd) {
25
+ cmd.execute(state, value)
26
+ }
27
+ },
28
+ isActive(command: string): boolean {
29
+ const cmd = commands[command]
30
+ return cmd.isActive?.(state) || false
31
+ },
32
+ getValue(command: string): string | null {
33
+ const cmd = commands[command]
34
+ return cmd.getValue?.(state) || null
35
+ }
36
+ }
37
+ }
@@ -0,0 +1,196 @@
1
+ import type { EditorState } from '../richTextTypes'
2
+
3
+ export interface EditorAction {
4
+ type: 'command' | 'paste' | 'keyboard' | 'selection' | 'input'
5
+ name: string
6
+ timestamp: number
7
+ details: Record<string, any>
8
+ state: EditorStateSnapshot
9
+ }
10
+
11
+ export interface EditorStateSnapshot {
12
+ content: string
13
+ selection: {
14
+ start: number
15
+ end: number
16
+ collapsed: boolean
17
+ text: string
18
+ } | null
19
+ activeStyles: string[]
20
+ caretPosition: {
21
+ node: string // Node description
22
+ offset: number
23
+ } | null
24
+ }
25
+
26
+ export interface DebugSession {
27
+ id: string
28
+ startTime: number
29
+ actions: EditorAction[]
30
+ }
31
+
32
+ export class EditorDebugger {
33
+ private session: DebugSession
34
+ private maxActions: number
35
+ private isDevelopment: boolean
36
+
37
+ constructor(maxActions = 1000) {
38
+ this.maxActions = maxActions
39
+ this.session = this.createNewSession()
40
+ this.isDevelopment = import.meta.env.DEV
41
+ }
42
+
43
+ private createNewSession(): DebugSession {
44
+ return {
45
+ id: `session_${Date.now()}`,
46
+ startTime: Date.now(),
47
+ actions: []
48
+ }
49
+ }
50
+
51
+ private getNodeDescription(node: Node): string {
52
+ if (node.nodeType === Node.TEXT_NODE) {
53
+ return `Text("${node.textContent?.slice(0, 20)}${node.textContent && node.textContent.length > 20 ? '...' : ''}")`
54
+ }
55
+ const element = node as Element
56
+ return `${element.tagName.toLowerCase()}${element.id ? `#${element.id}` : ''}`
57
+ }
58
+
59
+ private captureState(state: EditorState): EditorStateSnapshot {
60
+ if (!state.doc || !state.selection) {
61
+ return {
62
+ content: '',
63
+ selection: null,
64
+ activeStyles: [],
65
+ caretPosition: null
66
+ }
67
+ }
68
+
69
+ const { selection } = state
70
+ const range = selection.rangeCount ? selection.getRangeAt(0) : null
71
+
72
+ return {
73
+ content: state.doc.body.innerHTML,
74
+ selection: range
75
+ ? {
76
+ start: range.startOffset,
77
+ end: range.endOffset,
78
+ collapsed: range.collapsed,
79
+ text: range.toString()
80
+ }
81
+ : null,
82
+ activeStyles: Array.from(state.selectedStyles),
83
+ caretPosition: range
84
+ ? {
85
+ node: this.getNodeDescription(range.startContainer),
86
+ offset: range.startOffset
87
+ }
88
+ : null
89
+ }
90
+ }
91
+
92
+ logCommand(command: string, value: string | undefined, state: EditorState) {
93
+ this.addAction({
94
+ type: 'command',
95
+ name: command,
96
+ timestamp: Date.now(),
97
+ details: { value },
98
+ state: this.captureState(state)
99
+ })
100
+ }
101
+
102
+ logPaste(data: DataTransfer, state: EditorState) {
103
+ this.addAction({
104
+ type: 'paste',
105
+ name: 'paste',
106
+ timestamp: Date.now(),
107
+ details: {
108
+ html: data.getData('text/html'),
109
+ text: data.getData('text/plain'),
110
+ types: Array.from(data.types)
111
+ },
112
+ state: this.captureState(state)
113
+ })
114
+ }
115
+
116
+ logKeyboard(event: KeyboardEvent, state: EditorState) {
117
+ this.addAction({
118
+ type: 'keyboard',
119
+ name: 'keypress',
120
+ timestamp: Date.now(),
121
+ details: {
122
+ key: event.key,
123
+ code: event.code,
124
+ ctrl: event.ctrlKey,
125
+ alt: event.altKey,
126
+ shift: event.shiftKey,
127
+ meta: event.metaKey
128
+ },
129
+ state: this.captureState(state)
130
+ })
131
+ }
132
+
133
+ logSelection(state: EditorState) {
134
+ this.addAction({
135
+ type: 'selection',
136
+ name: 'selection_change',
137
+ timestamp: Date.now(),
138
+ details: {},
139
+ state: this.captureState(state)
140
+ })
141
+ }
142
+
143
+ logInput(inputType: string, data: string | null, state: EditorState) {
144
+ this.addAction({
145
+ type: 'input',
146
+ name: inputType,
147
+ timestamp: Date.now(),
148
+ details: { data },
149
+ state: this.captureState(state)
150
+ })
151
+ }
152
+
153
+ private addAction(action: EditorAction) {
154
+ this.session.actions.push(action)
155
+ if (this.session.actions.length > this.maxActions) {
156
+ this.session.actions.shift()
157
+ }
158
+
159
+ // Log to console in development
160
+ if (this.isDevelopment) {
161
+ console.log('Editor Action:', action)
162
+ }
163
+ }
164
+
165
+ getSession(): DebugSession {
166
+ return this.session
167
+ }
168
+
169
+ clearSession() {
170
+ this.session = this.createNewSession()
171
+ }
172
+
173
+ exportSession(): string {
174
+ return JSON.stringify(this.session, null, 2)
175
+ }
176
+
177
+ downloadSession() {
178
+ const blob = new Blob([this.exportSession()], { type: 'application/json' })
179
+ const url = URL.createObjectURL(blob)
180
+ const a = document.createElement('a')
181
+ a.href = url
182
+ a.download = `editor_session_${this.session.id}.json`
183
+ document.body.appendChild(a)
184
+ a.click()
185
+ document.body.removeChild(a)
186
+ URL.revokeObjectURL(url)
187
+ }
188
+
189
+ exportSessionWithPrompt(userMessage?: string): string {
190
+ const prompt = {
191
+ message: userMessage || 'here is a debug log, can you analyze and fix accordingly?',
192
+ session: this.session
193
+ }
194
+ return JSON.stringify(prompt, null, 2)
195
+ }
196
+ }