@kvass/rich-text 1.3.3 → 1.4.1

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": "@kvass/rich-text",
3
- "version": "1.3.3",
3
+ "version": "1.4.1",
4
4
  "description": "rich text component for kvass",
5
5
  "main": "src/Component.vue",
6
6
  "scripts": {
package/src/Component.vue CHANGED
@@ -1,10 +1,10 @@
1
1
  <template>
2
- <div class="richtext">
3
- <BubbleMenu v-if="editor" :editor="editor">
4
- <div class="richtext__bubble">
2
+ <div class="richtext" :class="{ 'richtext--disabled': disabled }">
3
+ <BubbleMenu v-if="editor" :editor="editor" :should-show="bubbleShouldShow">
4
+ <div ref="bubble" class="richtext__bubble">
5
5
  <form
6
6
  class="richtext__bubble-form"
7
- v-if="['link', 'cta'].includes(bubbleState)"
7
+ v-show="['link', 'cta'].includes(bubbleState)"
8
8
  @submit.prevent="setLink(linkUrl)"
9
9
  >
10
10
  <input
@@ -13,9 +13,15 @@
13
13
  v-model="linkUrl"
14
14
  placeholder="https://"
15
15
  ref="linkInput"
16
- autofocus
17
16
  @keydown.esc="bubbleState = null"
18
17
  />
18
+ <select v-if="bubbleState === 'cta'" v-model="bubbleOptions.theme">
19
+ <option value="primary">{{ labels.primary }}</option>
20
+ <option value="secondary">{{ labels.secondary }}</option>
21
+ </select>
22
+ <button @click="setLink(linkUrl)" :disabled="!linkUrl" type="button">
23
+ <FontAwesomeIcon :icon="['far', 'check']" />
24
+ </button>
19
25
  <button @click="setLink(null)" type="button">
20
26
  <FontAwesomeIcon :icon="['far', 'times']" />
21
27
  </button>
@@ -25,7 +31,9 @@
25
31
  <button
26
32
  v-for="(item, index) in getCommands('bubble')"
27
33
  :key="'bubble' + index"
34
+ class="richtext__bubble-button"
28
35
  :class="{ 'is-active': editor.isActive(item.stateKey) }"
36
+ :disabled="disabled || item.disabled"
29
37
  @click="
30
38
  item.customFunc
31
39
  ? item.customFunc()
@@ -39,19 +47,26 @@
39
47
  </div>
40
48
  </BubbleMenu>
41
49
 
42
- <div v-if="editor && !disabled" class="richtext__menubar">
43
- <button
44
- v-for="(item, index) in getCommands('menu')"
45
- :key="'menubar' + index"
46
- type="button"
47
- :class="{ 'is-active': editor.isActive(item.stateKey) }"
48
- @click="editor.chain().focus()[item.commandKey](item.commandProps).run()"
49
- >
50
- <FontAwesomeIcon :icon="item.icon" size="sm" />
51
- </button>
52
- </div>
50
+ <EditorContent class="richtext__editor" ref="editor" :editor="editor" :editable="!disabled" />
53
51
 
54
- <EditorContent class="richtext__editor" :editor="editor" />
52
+ <div v-if="editor" class="richtext__menubar">
53
+ <div class="richtext__menubar-buttons">
54
+ <button
55
+ v-for="(item, index) in getCommands('menu')"
56
+ :key="'menubar' + index"
57
+ type="button"
58
+ :class="{ 'is-active': editor.isActive(item.stateKey) }"
59
+ :disabled="disabled || item.disabled"
60
+ @click="
61
+ item.customFunc
62
+ ? item.customFunc()
63
+ : editor.chain().focus()[item.commandKey](item.commandProps).run()
64
+ "
65
+ >
66
+ <FontAwesomeIcon :icon="item.icon" size="sm" />
67
+ </button>
68
+ </div>
69
+ </div>
55
70
  </div>
56
71
  </template>
57
72
 
@@ -59,10 +74,11 @@
59
74
  import { Editor, EditorContent, BubbleMenu } from '@tiptap/vue-2'
60
75
  import Link from '@tiptap/extension-link'
61
76
  import Image from '@tiptap/extension-image'
62
- import CallToAction from './extensions/CallToAction'
77
+ import CallToAction from './extensions/cta'
63
78
  import StarterKit from '@tiptap/starter-kit'
64
79
 
65
80
  const DefaultCommands = ['heading', 'bold', 'italic', 'orderedList', 'bulletList', 'link', 'cta']
81
+ const DefaultLabels = { primary: 'Primær', secondary: 'Sekundær' }
66
82
 
67
83
  export { DefaultCommands }
68
84
  export default {
@@ -76,18 +92,32 @@ export default {
76
92
  type: Array,
77
93
  default: () => DefaultCommands,
78
94
  },
95
+ labels: {
96
+ type: Object,
97
+ default: () => DefaultLabels,
98
+ },
79
99
  },
80
100
  watch: {
81
101
  value(value) {
82
102
  if (this.editor.getHTML() === value) return
83
103
  this.editor.commands.setContent(this.value, false)
84
104
  },
105
+ disabled(value) {
106
+ if (!this.editor) return
107
+ this.editor.setOptions({ editable: !value })
108
+ },
109
+ bubbleState(value) {
110
+ if (!value) return
111
+ this.$refs.editor.$el.children[0].focus()
112
+ setTimeout(() => this.$refs.linkInput.focus(), 1)
113
+ },
85
114
  },
86
115
  data() {
87
116
  return {
88
117
  editor: null,
89
118
  linkUrl: null,
90
119
  bubbleState: null,
120
+ bubbleOptions: { theme: 'primary' },
91
121
  }
92
122
  },
93
123
  computed: {
@@ -109,7 +139,7 @@ export default {
109
139
  icon: ['far', 'bold'],
110
140
  appearance: {
111
141
  menu: true,
112
- bubble: true,
142
+ bubble: false,
113
143
  },
114
144
  },
115
145
  {
@@ -118,7 +148,7 @@ export default {
118
148
  icon: ['far', 'italic'],
119
149
  appearance: {
120
150
  menu: true,
121
- bubble: true,
151
+ bubble: false,
122
152
  },
123
153
  },
124
154
  {
@@ -145,10 +175,11 @@ export default {
145
175
  customFunc: () => {
146
176
  this.bubbleState = 'link'
147
177
  },
178
+ disabled: this.editor.state.selection.empty,
148
179
  icon: ['far', 'link'],
149
180
  appearance: {
150
- menu: false,
151
- bubble: true,
181
+ menu: true,
182
+ bubble: false,
152
183
  },
153
184
  },
154
185
  {
@@ -167,10 +198,11 @@ export default {
167
198
  customFunc: () => {
168
199
  this.bubbleState = 'cta'
169
200
  },
201
+ disabled: this.editor.state.selection.empty,
170
202
  icon: ['far', 'mouse-pointer'],
171
203
  appearance: {
172
- menu: false,
173
- bubble: true,
204
+ menu: true,
205
+ bubble: false,
174
206
  },
175
207
  },
176
208
  ]
@@ -191,7 +223,7 @@ export default {
191
223
  else editor.unsetLink().run()
192
224
  break
193
225
  case 'cta':
194
- if (value) editor.setCta({ href: value }).run()
226
+ if (value) editor.setCta({ href: value, 'data-cta': this.bubbleOptions.theme }).run()
195
227
  else editor.unsetCta().run()
196
228
  break
197
229
  }
@@ -199,6 +231,14 @@ export default {
199
231
  this.bubbleState = null
200
232
  this.linkUrl = null
201
233
  },
234
+ bubbleShouldShow({ editor }) {
235
+ return (
236
+ this.availableCommands
237
+ .filter(a => a.appearance.bubble)
238
+ .map(a => a.stateKey)
239
+ .some(k => editor.isActive(k)) || !this.editor.state.selection.empty
240
+ )
241
+ },
202
242
  },
203
243
  mounted() {
204
244
  this.editor = new Editor({
@@ -226,11 +266,25 @@ export default {
226
266
  @import './styles/_mixins';
227
267
  .richtext {
228
268
  flex-grow: 1;
269
+ display: flex;
270
+ flex-direction: column-reverse;
271
+
272
+ &--disabled {
273
+ background-color: var(--vue-elder-input-color, #fafafa);
274
+ }
275
+
276
+ &:not(.richtext--disabled):focus-within {
277
+ outline: 1px solid var(--primary);
278
+ border-radius: var(--border-radius, 3px);
279
+ }
229
280
 
230
281
  &__editor {
231
- [contenteditable='true'] {
232
- padding: 1rem;
233
- min-height: 10rem;
282
+ flex-grow: 1;
283
+
284
+ .ProseMirror {
285
+ padding: 0rem 1rem 1rem;
286
+ min-height: 6rem;
287
+ height: 100%;
234
288
 
235
289
  p:first-child {
236
290
  margin-top: 0;
@@ -244,48 +298,73 @@ export default {
244
298
  }
245
299
 
246
300
  &:focus {
247
- outline: 1px solid css-alpha('primary', 0.3);
248
- border-radius: 0 0 var(--border-radius) var(--border-radius);
301
+ outline: none;
249
302
  }
250
303
 
251
304
  img {
252
305
  max-width: 100%;
253
306
  }
254
307
 
255
- a[data-cta='primary'] {
256
- background-color: var(--primary);
257
- color: white;
308
+ a[data-cta] {
258
309
  padding: 0.75em 1.5em;
259
310
  border-radius: var(--border-radius);
260
311
  text-decoration: none;
261
312
 
262
313
  display: inline-block;
263
314
  }
264
- }
265
315
 
266
- & > div[contenteditable='false'] {
267
- opacity: 0.4;
268
- background-color: var(--vue-elder-input-color, #f2f2f2);
316
+ a[data-cta='primary'] {
317
+ background-color: var(--primary);
318
+ color: var(--primary-contrast);
319
+ }
320
+
321
+ a[data-cta='secondary'] {
322
+ background-color: var(--secondary);
323
+ color: var(--secondary-contrast);
324
+ }
269
325
  }
270
326
  }
271
327
 
272
328
  &__bubble {
273
329
  display: flex;
274
330
  background-color: var(--dark);
331
+ color: white;
275
332
  border-radius: var(--border-radius);
276
333
 
277
334
  &-form {
278
335
  display: flex;
279
336
 
337
+ button,
338
+ select {
339
+ background-color: transparent;
340
+ color: inherit;
341
+ border: none;
342
+ border-left: 1px solid rgba(white, 0.1);
343
+ }
344
+
345
+ option {
346
+ color: black;
347
+ }
348
+
280
349
  button {
281
- padding: 0.5rem 1rem !important;
350
+ padding: 0.5rem;
351
+ min-width: 2.5rem;
352
+
353
+ &:disabled {
354
+ opacity: 0.5;
355
+ background-color: rgba(white, 0.1);
356
+ }
357
+ }
358
+
359
+ select {
360
+ padding: 0.5rem !important;
282
361
  }
283
362
  }
284
363
 
285
364
  &-input {
286
365
  font: inherit;
287
366
  border: none;
288
- padding: 5px;
367
+ padding: 0.5rem;
289
368
  background-color: transparent;
290
369
  color: #fff !important;
291
370
 
@@ -294,9 +373,8 @@ export default {
294
373
  }
295
374
  }
296
375
 
297
- button {
298
- border: none !important ;
299
- width: initial !important;
376
+ &__button {
377
+ width: initial;
300
378
  padding: 0.5rem 0.75rem !important;
301
379
  color: white;
302
380
 
@@ -316,24 +394,60 @@ export default {
316
394
  }
317
395
 
318
396
  &__menubar {
319
- background-color: css-alpha('border-color', 0.2);
320
- border-bottom: 1px solid var(--border-color);
321
- }
397
+ padding: 0.75rem;
398
+ position: sticky;
399
+ top: 0;
400
+ z-index: 1;
401
+
402
+ &-buttons {
403
+ width: max-content;
404
+ box-shadow: 0 2px 4px -2px rgba(black, 0.1);
405
+ }
322
406
 
323
- button {
324
- padding: 0.5rem 0.5rem;
325
- width: 2.5rem;
326
- border-right: 1px solid var(--border-color);
327
- transition: background-color 150ms ease-out;
328
- cursor: pointer;
407
+ button {
408
+ padding: 0.5rem;
409
+ min-width: 2.5rem;
410
+ border: 1px solid var(--border-color);
411
+ cursor: pointer;
412
+ background-color: #ffffff;
413
+
414
+ &:focus-visible {
415
+ position: relative;
416
+ border-radius: var(--border-radius, 3px);
417
+ outline: 1px solid var(--primary) !important;
418
+ }
329
419
 
330
- &:hover,
331
- &.is-active {
332
- background-color: css-lighten('primary', 47%);
420
+ &:first-child {
421
+ border-top-left-radius: var(--border-radius, 3px);
422
+ border-bottom-left-radius: var(--border-radius, 3px);
423
+ }
424
+
425
+ &:last-child {
426
+ border-top-right-radius: var(--border-radius, 3px);
427
+ border-bottom-right-radius: var(--border-radius, 3px);
428
+ }
429
+
430
+ & + button {
431
+ border-left: none;
432
+ }
433
+
434
+ &:disabled {
435
+ cursor: not-allowed;
436
+ }
437
+
438
+ &:hover:not(:disabled),
439
+ &.is-active {
440
+ background-color: darken(#ffffff, 3%);
441
+ }
333
442
  }
334
443
  }
444
+
335
445
  a {
336
446
  word-break: break-all;
337
447
  }
448
+
449
+ .tippy-box {
450
+ max-width: initial !important;
451
+ }
338
452
  }
339
453
  </style>
File without changes