@kvass/rich-text 1.3.4 → 1.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kvass/rich-text",
3
- "version": "1.3.4",
3
+ "version": "1.4.0",
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 && !disabled" :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" :editable="!disabled" />
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,6 +92,10 @@ 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) {
@@ -86,12 +106,18 @@ export default {
86
106
  if (!this.editor) return
87
107
  this.editor.setOptions({ editable: !value })
88
108
  },
109
+ bubbleState(value) {
110
+ if (!value) return
111
+ this.$refs.editor.$el.children[0].focus()
112
+ setTimeout(() => this.$refs.linkInput.focus(), 1)
113
+ },
89
114
  },
90
115
  data() {
91
116
  return {
92
117
  editor: null,
93
118
  linkUrl: null,
94
119
  bubbleState: null,
120
+ bubbleOptions: { theme: 'primary' },
95
121
  }
96
122
  },
97
123
  computed: {
@@ -113,7 +139,7 @@ export default {
113
139
  icon: ['far', 'bold'],
114
140
  appearance: {
115
141
  menu: true,
116
- bubble: true,
142
+ bubble: false,
117
143
  },
118
144
  },
119
145
  {
@@ -122,7 +148,7 @@ export default {
122
148
  icon: ['far', 'italic'],
123
149
  appearance: {
124
150
  menu: true,
125
- bubble: true,
151
+ bubble: false,
126
152
  },
127
153
  },
128
154
  {
@@ -149,10 +175,11 @@ export default {
149
175
  customFunc: () => {
150
176
  this.bubbleState = 'link'
151
177
  },
178
+ disabled: this.editor.state.selection.empty,
152
179
  icon: ['far', 'link'],
153
180
  appearance: {
154
- menu: false,
155
- bubble: true,
181
+ menu: true,
182
+ bubble: false,
156
183
  },
157
184
  },
158
185
  {
@@ -171,10 +198,11 @@ export default {
171
198
  customFunc: () => {
172
199
  this.bubbleState = 'cta'
173
200
  },
201
+ disabled: this.editor.state.selection.empty,
174
202
  icon: ['far', 'mouse-pointer'],
175
203
  appearance: {
176
- menu: false,
177
- bubble: true,
204
+ menu: true,
205
+ bubble: false,
178
206
  },
179
207
  },
180
208
  ]
@@ -195,7 +223,7 @@ export default {
195
223
  else editor.unsetLink().run()
196
224
  break
197
225
  case 'cta':
198
- if (value) editor.setCta({ href: value }).run()
226
+ if (value) editor.setCta({ href: value, 'data-cta': this.bubbleOptions.theme }).run()
199
227
  else editor.unsetCta().run()
200
228
  break
201
229
  }
@@ -203,6 +231,14 @@ export default {
203
231
  this.bubbleState = null
204
232
  this.linkUrl = null
205
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
+ },
206
242
  },
207
243
  mounted() {
208
244
  this.editor = new Editor({
@@ -230,11 +266,25 @@ export default {
230
266
  @import './styles/_mixins';
231
267
  .richtext {
232
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
+ }
233
280
 
234
281
  &__editor {
235
- [contenteditable='true'] {
236
- padding: 1rem;
237
- min-height: 10rem;
282
+ flex-grow: 1;
283
+
284
+ .ProseMirror {
285
+ padding: 0rem 1rem 1rem;
286
+ min-height: 6rem;
287
+ height: 100%;
238
288
 
239
289
  p:first-child {
240
290
  margin-top: 0;
@@ -248,47 +298,69 @@ export default {
248
298
  }
249
299
 
250
300
  &:focus {
251
- outline: 1px solid css-alpha('primary', 0.3);
252
- border-radius: 0 0 var(--border-radius) var(--border-radius);
301
+ outline: none;
253
302
  }
254
303
 
255
304
  img {
256
305
  max-width: 100%;
257
306
  }
258
307
 
259
- a[data-cta='primary'] {
260
- background-color: var(--primary);
261
- color: white;
308
+ a[data-cta] {
262
309
  padding: 0.75em 1.5em;
263
310
  border-radius: var(--border-radius);
264
311
  text-decoration: none;
265
312
 
266
313
  display: inline-block;
267
314
  }
268
- }
269
315
 
270
- & > div[contenteditable='false'] {
271
- background-color: var(--vue-elder-input-color, #fafafa);
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
+ }
272
325
  }
273
326
  }
274
327
 
275
328
  &__bubble {
276
329
  display: flex;
277
330
  background-color: var(--dark);
331
+ color: white;
278
332
  border-radius: var(--border-radius);
279
333
 
280
334
  &-form {
281
335
  display: flex;
282
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
+
283
345
  button {
284
- padding: 0.5rem 1rem !important;
346
+ padding: 0.5rem;
347
+ min-width: 2.5rem;
348
+
349
+ &:disabled {
350
+ opacity: 0.5;
351
+ background-color: rgba(white, 0.1);
352
+ }
353
+ }
354
+
355
+ select {
356
+ padding: 0.5rem !important;
285
357
  }
286
358
  }
287
359
 
288
360
  &-input {
289
361
  font: inherit;
290
362
  border: none;
291
- padding: 5px;
363
+ padding: 0.5rem;
292
364
  background-color: transparent;
293
365
  color: #fff !important;
294
366
 
@@ -297,9 +369,8 @@ export default {
297
369
  }
298
370
  }
299
371
 
300
- button {
301
- border: none !important ;
302
- width: initial !important;
372
+ &__button {
373
+ width: initial;
303
374
  padding: 0.5rem 0.75rem !important;
304
375
  color: white;
305
376
 
@@ -319,28 +390,60 @@ export default {
319
390
  }
320
391
 
321
392
  &__menubar {
322
- background-color: css-alpha('border-color', 0.2);
323
- border-bottom: 1px solid var(--border-color);
324
- }
393
+ padding: 0.75rem;
394
+ position: sticky;
395
+ top: 0;
396
+ z-index: 1;
325
397
 
326
- button {
327
- padding: 0.5rem 0.5rem;
328
- width: 2.5rem;
329
- border-right: 1px solid var(--border-color);
330
- transition: background-color 150ms ease-out;
331
- cursor: pointer;
332
-
333
- &:disabled {
334
- cursor: not-allowed;
398
+ &-buttons {
399
+ width: max-content;
400
+ box-shadow: 0 2px 4px -2px rgba(black, 0.1);
335
401
  }
336
402
 
337
- &:hover:not(:disabled),
338
- &.is-active {
339
- background-color: css-lighten('primary', 47%);
403
+ button {
404
+ padding: 0.5rem;
405
+ min-width: 2.5rem;
406
+ border: 1px solid var(--border-color);
407
+ cursor: pointer;
408
+ background-color: #ffffff;
409
+
410
+ &:focus-visible {
411
+ position: relative;
412
+ border-radius: var(--border-radius, 3px);
413
+ outline: 1px solid var(--primary) !important;
414
+ }
415
+
416
+ &:first-child {
417
+ border-top-left-radius: var(--border-radius, 3px);
418
+ border-bottom-left-radius: var(--border-radius, 3px);
419
+ }
420
+
421
+ &:last-child {
422
+ border-top-right-radius: var(--border-radius, 3px);
423
+ border-bottom-right-radius: var(--border-radius, 3px);
424
+ }
425
+
426
+ & + button {
427
+ border-left: none;
428
+ }
429
+
430
+ &:disabled {
431
+ cursor: not-allowed;
432
+ }
433
+
434
+ &:hover:not(:disabled),
435
+ &.is-active {
436
+ background-color: darken(#ffffff, 3%);
437
+ }
340
438
  }
341
439
  }
440
+
342
441
  a {
343
442
  word-break: break-all;
344
443
  }
444
+
445
+ .tippy-box {
446
+ max-width: initial !important;
447
+ }
345
448
  }
346
449
  </style>
File without changes