frappe-ui 0.0.13 → 0.0.15

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "frappe-ui",
3
- "version": "0.0.13",
3
+ "version": "0.0.15",
4
4
  "description": "A set of components and utilities for rapid UI development",
5
5
  "main": "./src/index.js",
6
6
  "scripts": {
@@ -17,7 +17,7 @@
17
17
  "author": "Frappe Technologies Pvt. Ltd.",
18
18
  "license": "MIT",
19
19
  "dependencies": {
20
- "@headlessui/vue": "^1.4.3",
20
+ "@headlessui/vue": "^1.5.0",
21
21
  "@popperjs/core": "^2.11.2",
22
22
  "@tailwindcss/forms": "^0.4.0",
23
23
  "@tailwindcss/typography": "^0.5.0",
@@ -1,7 +1,7 @@
1
1
  <template>
2
2
  <div class="inline-flex px-1 py-1 bg-white">
3
3
  <div class="inline-flex items-center gap-1">
4
- <template v-for="button in menuButtons" :key="button.label">
4
+ <template v-for="button in buttons" :key="button.label">
5
5
  <div
6
6
  class="border-l w-[2px] h-4"
7
7
  v-if="button.type === 'separator'"
@@ -9,8 +9,8 @@
9
9
  <button
10
10
  v-else
11
11
  class="flex p-1 text-gray-800 transition-colors rounded"
12
- :class="button.isActive() ? 'bg-gray-100' : 'hover:bg-gray-100'"
13
- @click="button.action"
12
+ :class="button.isActive(editor) ? 'bg-gray-100' : 'hover:bg-gray-100'"
13
+ @click="() => button.action(editor)"
14
14
  :title="button.label"
15
15
  >
16
16
  <FeatherIcon v-if="button.icon" :name="button.icon" class="w-4" />
@@ -26,96 +26,7 @@
26
26
  import { FeatherIcon } from 'frappe-ui'
27
27
  export default {
28
28
  name: 'TipTapMenu',
29
- props: ['editor'],
29
+ props: ['editor', 'buttons'],
30
30
  components: { FeatherIcon },
31
- computed: {
32
- menuButtons() {
33
- return [
34
- {
35
- label: 'Paragraph',
36
- icon: 'type',
37
- action: () => this.editor.chain().focus().setParagraph().run(),
38
- isActive: () => this.editor.isActive('paragraph'),
39
- },
40
- {
41
- label: 'Heading 2',
42
- text: 'H2',
43
- action: () =>
44
- this.editor.chain().focus().toggleHeading({ level: 2 }).run(),
45
- isActive: () => this.editor.isActive('heading', { level: 2 }),
46
- },
47
- {
48
- label: 'Heading 3',
49
- text: 'H3',
50
- action: () =>
51
- this.editor.chain().focus().toggleHeading({ level: 3 }).run(),
52
- isActive: () => this.editor.isActive('heading', { level: 3 }),
53
- },
54
- {
55
- type: 'separator',
56
- },
57
- {
58
- label: 'Bold',
59
- icon: 'bold',
60
- action: () => this.editor.chain().focus().toggleBold().run(),
61
- isActive: () => this.editor.isActive('bold'),
62
- },
63
- {
64
- label: 'Italic',
65
- icon: 'italic',
66
- action: () => this.editor.chain().focus().toggleItalic().run(),
67
- isActive: () => this.editor.isActive('italic'),
68
- },
69
- {
70
- type: 'separator',
71
- },
72
- {
73
- label: 'Bullet List',
74
- icon: 'list',
75
- action: () => this.editor.chain().focus().toggleBulletList().run(),
76
- isActive: () => this.editor.isActive('bulletList'),
77
- },
78
- {
79
- label: 'Numbered List',
80
- text: '1.',
81
- action: () => this.editor.chain().focus().toggleOrderedList().run(),
82
- isActive: () => this.editor.isActive('orderedList'),
83
- },
84
- {
85
- label: 'Blockquote',
86
- icon: 'chevron-right',
87
- action: () => this.editor.chain().focus().toggleBlockquote().run(),
88
- isActive: () => this.editor.isActive('blockquote'),
89
- },
90
- {
91
- label: 'Code',
92
- icon: 'code',
93
- action: () => this.editor.chain().focus().toggleCodeBlock().run(),
94
- isActive: () => this.editor.isActive('codeBlock'),
95
- },
96
- {
97
- label: 'Horizontal Rule',
98
- icon: 'minus',
99
- action: () => this.editor.chain().focus().setHorizontalRule().run(),
100
- isActive: () => false,
101
- },
102
- {
103
- type: 'separator',
104
- },
105
- {
106
- label: 'Undo',
107
- icon: 'corner-up-left',
108
- action: () => this.editor.chain().focus().undo().run(),
109
- isActive: () => false,
110
- },
111
- {
112
- label: 'Redo',
113
- icon: 'corner-up-right',
114
- action: () => this.editor.chain().focus().redo().run(),
115
- isActive: () => false,
116
- },
117
- ]
118
- },
119
- },
120
31
  }
121
32
  </script>
@@ -1,7 +1,7 @@
1
1
  <template>
2
- <div class="relative w-full" :class="{ 'pt-6': showMenu }" v-if="editor">
2
+ <div class="relative w-full" :class="$attrs.class" v-if="editor">
3
3
  <BubbleMenu
4
- v-if="showBubbleMenu"
4
+ v-if="bubbleMenuButtons"
5
5
  class="bubble-menu"
6
6
  :tippy-options="{ duration: 100 }"
7
7
  :editor="editor"
@@ -9,27 +9,48 @@
9
9
  <Menu
10
10
  :editor="editor"
11
11
  class="border border-gray-100 rounded-md shadow-sm"
12
+ :buttons="bubbleMenuButtons"
12
13
  />
13
14
  </BubbleMenu>
14
15
 
15
16
  <Menu
16
- v-if="showMenu"
17
- class="absolute top-0 left-0 right-0 border rounded-t-lg border-gray-50 border-b-gray-100"
17
+ v-if="fixedMenuButtons"
18
+ class="w-full border rounded-t-lg border-gray-50 border-b-gray-100"
18
19
  :editor="editor"
20
+ :buttons="fixedMenuButtons"
19
21
  />
20
- <editor-content
22
+
23
+ <FloatingMenu
24
+ v-if="floatingMenuButtons"
25
+ :tippy-options="{ duration: 100 }"
21
26
  :editor="editor"
22
- :class="$attrs.class || 'prose-sm prose'"
23
- />
27
+ class="flex"
28
+ >
29
+ <button
30
+ v-for="button in floatingMenuButtons"
31
+ :key="button.label"
32
+ class="flex p-1 text-gray-800 transition-colors rounded"
33
+ :class="button.isActive(editor) ? 'bg-gray-100' : 'hover:bg-gray-100'"
34
+ @click="() => button.action(editor)"
35
+ :title="button.label"
36
+ >
37
+ <FeatherIcon v-if="button.icon" :name="button.icon" class="w-4" />
38
+ <span class="inline-block h-4 text-sm leading-4 min-w-[1rem]" v-else>
39
+ {{ button.text }}
40
+ </span>
41
+ </button>
42
+ </FloatingMenu>
43
+ <editor-content :editor="editor" />
24
44
  </div>
25
45
  </template>
26
46
 
27
47
  <script>
28
- import { Editor, EditorContent, BubbleMenu } from '@tiptap/vue-3'
48
+ import { Editor, EditorContent, BubbleMenu, FloatingMenu } from '@tiptap/vue-3'
29
49
  import StarterKit from '@tiptap/starter-kit'
30
50
  import Placeholder from '@tiptap/extension-placeholder'
31
51
  import Image from '@tiptap/extension-image'
32
52
  import Menu from './Menu.vue'
53
+ import commands from './commands'
33
54
 
34
55
  export default {
35
56
  name: 'TextEditor',
@@ -37,14 +58,17 @@ export default {
37
58
  components: {
38
59
  EditorContent,
39
60
  BubbleMenu,
61
+ FloatingMenu,
40
62
  Menu,
41
63
  },
42
64
  props: [
43
65
  'content',
44
66
  'placeholder',
45
67
  'editorClass',
46
- 'showMenu',
47
- 'showBubbleMenu',
68
+ 'fixedMenu',
69
+ 'bubbleMenu',
70
+ 'floatingMenu',
71
+ 'extensions',
48
72
  ],
49
73
  emits: ['change'],
50
74
  expose: ['editor'],
@@ -58,7 +82,7 @@ export default {
58
82
  content: this.content || '<p></p>',
59
83
  editorProps: {
60
84
  attributes: {
61
- class: ['prose-p:my-1', this.editorClass].join(' '),
85
+ class: ['prose prose-sm prose-p:my-1', this.editorClass].join(' '),
62
86
  },
63
87
  },
64
88
  extensions: [
@@ -67,6 +91,7 @@ export default {
67
91
  Placeholder.configure({
68
92
  placeholder: this.placeholder || 'Write something...',
69
93
  }),
94
+ ...(this.extensions || []),
70
95
  ],
71
96
  onUpdate: ({ editor }) => {
72
97
  this.$emit('change', editor.getHTML())
@@ -76,6 +101,85 @@ export default {
76
101
  beforeUnmount() {
77
102
  this.editor.destroy()
78
103
  },
104
+ computed: {
105
+ fixedMenuButtons() {
106
+ if (!this.fixedMenu) return false
107
+
108
+ let buttons
109
+ if (Array.isArray(this.fixedMenu)) {
110
+ buttons = this.fixedMenu
111
+ } else {
112
+ buttons = [
113
+ 'Paragraph',
114
+ 'Heading 2',
115
+ 'Heading 3',
116
+ 'Separator',
117
+ 'Bold',
118
+ 'Italic',
119
+ 'Separator',
120
+ 'Bullet List',
121
+ 'Numbered List',
122
+ 'Blockquote',
123
+ 'Code',
124
+ 'Horizontal Rule',
125
+ 'Separator',
126
+ 'Undo',
127
+ 'Redo',
128
+ ]
129
+ }
130
+ return buttons.map(createEditorButton)
131
+ },
132
+ bubbleMenuButtons() {
133
+ if (!this.bubbleMenu) return false
134
+
135
+ let buttons
136
+ if (Array.isArray(this.bubbleMenu)) {
137
+ buttons = this.bubbleMenu
138
+ } else {
139
+ buttons = [
140
+ 'Paragraph',
141
+ 'Heading 2',
142
+ 'Heading 3',
143
+ 'Separator',
144
+ 'Bold',
145
+ 'Italic',
146
+ 'Separator',
147
+ 'Bullet List',
148
+ 'Numbered List',
149
+ 'Blockquote',
150
+ 'Code',
151
+ ]
152
+ }
153
+ return buttons.map(createEditorButton)
154
+ },
155
+ floatingMenuButtons() {
156
+ if (!this.floatingMenu) return false
157
+
158
+ let buttons
159
+ if (Array.isArray(this.floatingMenu)) {
160
+ buttons = this.floatingMenu
161
+ } else {
162
+ buttons = [
163
+ 'Paragraph',
164
+ 'Heading 2',
165
+ 'Heading 3',
166
+ 'Bullet List',
167
+ 'Numbered List',
168
+ 'Blockquote',
169
+ 'Code',
170
+ 'Horizontal Rule',
171
+ ]
172
+ }
173
+ return buttons.map(createEditorButton)
174
+ },
175
+ },
176
+ }
177
+
178
+ function createEditorButton(option) {
179
+ if (typeof option == 'object') {
180
+ return option
181
+ }
182
+ return commands[option]
79
183
  }
80
184
  </script>
81
185
  <style>
@@ -0,0 +1,79 @@
1
+ export default {
2
+ Paragraph: {
3
+ label: 'Paragraph',
4
+ icon: 'type',
5
+ action: (editor) => editor.chain().focus().setParagraph().run(),
6
+ isActive: (editor) => editor.isActive('paragraph'),
7
+ },
8
+ 'Heading 2': {
9
+ label: 'Heading 2',
10
+ text: 'H2',
11
+ action: (editor) =>
12
+ editor.chain().focus().toggleHeading({ level: 2 }).run(),
13
+ isActive: (editor) => editor.isActive('heading', { level: 2 }),
14
+ },
15
+ 'Heading 3': {
16
+ label: 'Heading 3',
17
+ text: 'H3',
18
+ action: (editor) =>
19
+ editor.chain().focus().toggleHeading({ level: 3 }).run(),
20
+ isActive: (editor) => editor.isActive('heading', { level: 3 }),
21
+ },
22
+ Bold: {
23
+ label: 'Bold',
24
+ icon: 'bold',
25
+ action: (editor) => editor.chain().focus().toggleBold().run(),
26
+ isActive: (editor) => editor.isActive('bold'),
27
+ },
28
+ Italic: {
29
+ label: 'Italic',
30
+ icon: 'italic',
31
+ action: (editor) => editor.chain().focus().toggleItalic().run(),
32
+ isActive: (editor) => editor.isActive('italic'),
33
+ },
34
+ 'Bullet List': {
35
+ label: 'Bullet List',
36
+ icon: 'list',
37
+ action: (editor) => editor.chain().focus().toggleBulletList().run(),
38
+ isActive: (editor) => editor.isActive('bulletList'),
39
+ },
40
+ 'Numbered List': {
41
+ label: 'Numbered List',
42
+ text: '1.',
43
+ action: (editor) => editor.chain().focus().toggleOrderedList().run(),
44
+ isActive: (editor) => editor.isActive('orderedList'),
45
+ },
46
+ Blockquote: {
47
+ label: 'Blockquote',
48
+ icon: 'chevron-right',
49
+ action: (editor) => editor.chain().focus().toggleBlockquote().run(),
50
+ isActive: (editor) => editor.isActive('blockquote'),
51
+ },
52
+ Code: {
53
+ label: 'Code',
54
+ icon: 'code',
55
+ action: (editor) => editor.chain().focus().toggleCodeBlock().run(),
56
+ isActive: (editor) => editor.isActive('codeBlock'),
57
+ },
58
+ 'Horizontal Rule': {
59
+ label: 'Horizontal Rule',
60
+ icon: 'minus',
61
+ action: (editor) => editor.chain().focus().setHorizontalRule().run(),
62
+ isActive: (editor) => false,
63
+ },
64
+ Undo: {
65
+ label: 'Undo',
66
+ icon: 'corner-up-left',
67
+ action: (editor) => editor.chain().focus().undo().run(),
68
+ isActive: (editor) => false,
69
+ },
70
+ Redo: {
71
+ label: 'Redo',
72
+ icon: 'corner-up-right',
73
+ action: (editor) => editor.chain().focus().redo().run(),
74
+ isActive: (editor) => false,
75
+ },
76
+ Separator: {
77
+ type: 'separator',
78
+ },
79
+ }
@@ -247,10 +247,64 @@ export function createDocumentResource(options, vm) {
247
247
  return out
248
248
  }
249
249
 
250
+ function createListResource(options, vm, getResource) {
251
+ if (!options.doctype) return
252
+
253
+ let out = reactive({
254
+ doctype: options.doctype,
255
+ fields: options.fields,
256
+ filters: options.filters,
257
+ data: null,
258
+ list: createResource({
259
+ method: 'frappe.client.get_list',
260
+ makeParams() {
261
+ return {
262
+ doctype: out.doctype,
263
+ fields: out.fields,
264
+ filters: out.filters,
265
+ }
266
+ },
267
+ onSuccess(data) {
268
+ out.data = data
269
+ },
270
+ }),
271
+ insert: createResource({
272
+ method: 'frappe.client.insert',
273
+ makeParams(values) {
274
+ return {
275
+ doc: {
276
+ doctype: out.doctype,
277
+ ...values,
278
+ },
279
+ }
280
+ },
281
+ onSuccess() {
282
+ out.list.fetch()
283
+ },
284
+ }),
285
+ update,
286
+ })
287
+
288
+ function update(updatedOptions) {
289
+ out.doctype = updatedOptions.doctype
290
+ out.fields = updatedOptions.fields
291
+ out.filters = updatedOptions.filters
292
+ out.list.fetch()
293
+ }
294
+
295
+ // fetch list
296
+ out.list.fetch()
297
+
298
+ return out
299
+ }
300
+
250
301
  function createResourceForOptions(options, vm, getResource) {
251
302
  if (options.type === 'document') {
252
303
  return createDocumentResource(options, vm, getResource)
253
304
  }
305
+ if (options.type === 'list') {
306
+ return createListResource(options, vm, getResource)
307
+ }
254
308
  return createResource(options, vm, getResource)
255
309
  }
256
310