@radio-garden/ditojs-admin 2.85.2-0.5067ad799

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 (153) hide show
  1. package/README.md +180 -0
  2. package/dist/dito-admin.css +1 -0
  3. package/dist/dito-admin.es.js +12106 -0
  4. package/dist/dito-admin.umd.js +7 -0
  5. package/package.json +96 -0
  6. package/src/DitoAdmin.js +293 -0
  7. package/src/DitoComponent.js +34 -0
  8. package/src/DitoContext.js +318 -0
  9. package/src/DitoTypeComponent.js +42 -0
  10. package/src/DitoUser.js +12 -0
  11. package/src/appState.js +12 -0
  12. package/src/components/DitoAccount.vue +60 -0
  13. package/src/components/DitoAffix.vue +68 -0
  14. package/src/components/DitoAffixes.vue +200 -0
  15. package/src/components/DitoButtons.vue +80 -0
  16. package/src/components/DitoClipboard.vue +186 -0
  17. package/src/components/DitoContainer.vue +374 -0
  18. package/src/components/DitoCreateButton.vue +146 -0
  19. package/src/components/DitoDialog.vue +242 -0
  20. package/src/components/DitoDraggable.vue +117 -0
  21. package/src/components/DitoEditButtons.vue +135 -0
  22. package/src/components/DitoErrors.vue +83 -0
  23. package/src/components/DitoForm.vue +521 -0
  24. package/src/components/DitoFormInner.vue +26 -0
  25. package/src/components/DitoFormNested.vue +17 -0
  26. package/src/components/DitoHeader.vue +84 -0
  27. package/src/components/DitoLabel.vue +200 -0
  28. package/src/components/DitoMenu.vue +186 -0
  29. package/src/components/DitoNavigation.vue +40 -0
  30. package/src/components/DitoNotifications.vue +170 -0
  31. package/src/components/DitoPagination.vue +42 -0
  32. package/src/components/DitoPane.vue +334 -0
  33. package/src/components/DitoPanel.vue +256 -0
  34. package/src/components/DitoPanels.vue +61 -0
  35. package/src/components/DitoRoot.vue +524 -0
  36. package/src/components/DitoSchema.vue +846 -0
  37. package/src/components/DitoSchemaInlined.vue +97 -0
  38. package/src/components/DitoScopes.vue +76 -0
  39. package/src/components/DitoSidebar.vue +50 -0
  40. package/src/components/DitoSpinner.vue +95 -0
  41. package/src/components/DitoTableCell.vue +64 -0
  42. package/src/components/DitoTableHead.vue +121 -0
  43. package/src/components/DitoTabs.vue +103 -0
  44. package/src/components/DitoTrail.vue +124 -0
  45. package/src/components/DitoTreeItem.vue +420 -0
  46. package/src/components/DitoUploadFile.vue +199 -0
  47. package/src/components/DitoVNode.vue +14 -0
  48. package/src/components/DitoView.vue +143 -0
  49. package/src/components/index.js +42 -0
  50. package/src/directives/resize.js +83 -0
  51. package/src/index.js +1 -0
  52. package/src/mixins/ContextMixin.js +68 -0
  53. package/src/mixins/DataMixin.js +131 -0
  54. package/src/mixins/DitoMixin.js +591 -0
  55. package/src/mixins/DomMixin.js +29 -0
  56. package/src/mixins/EmitterMixin.js +158 -0
  57. package/src/mixins/ItemMixin.js +144 -0
  58. package/src/mixins/LoadingMixin.js +23 -0
  59. package/src/mixins/NumberMixin.js +118 -0
  60. package/src/mixins/OptionsMixin.js +304 -0
  61. package/src/mixins/PulldownMixin.js +63 -0
  62. package/src/mixins/ResourceMixin.js +398 -0
  63. package/src/mixins/RouteMixin.js +190 -0
  64. package/src/mixins/SchemaParentMixin.js +33 -0
  65. package/src/mixins/SortableMixin.js +49 -0
  66. package/src/mixins/SourceMixin.js +734 -0
  67. package/src/mixins/TextMixin.js +26 -0
  68. package/src/mixins/TypeMixin.js +280 -0
  69. package/src/mixins/ValidationMixin.js +119 -0
  70. package/src/mixins/ValidatorMixin.js +57 -0
  71. package/src/mixins/ValueMixin.js +31 -0
  72. package/src/styles/_base.scss +17 -0
  73. package/src/styles/_button.scss +191 -0
  74. package/src/styles/_imports.scss +3 -0
  75. package/src/styles/_info.scss +19 -0
  76. package/src/styles/_layout.scss +19 -0
  77. package/src/styles/_pulldown.scss +38 -0
  78. package/src/styles/_scroll.scss +13 -0
  79. package/src/styles/_settings.scss +88 -0
  80. package/src/styles/_table.scss +223 -0
  81. package/src/styles/_tippy.scss +45 -0
  82. package/src/styles/style.scss +9 -0
  83. package/src/types/DitoTypeButton.vue +143 -0
  84. package/src/types/DitoTypeCheckbox.vue +27 -0
  85. package/src/types/DitoTypeCheckboxes.vue +65 -0
  86. package/src/types/DitoTypeCode.vue +199 -0
  87. package/src/types/DitoTypeColor.vue +272 -0
  88. package/src/types/DitoTypeComponent.vue +31 -0
  89. package/src/types/DitoTypeComputed.vue +50 -0
  90. package/src/types/DitoTypeDate.vue +99 -0
  91. package/src/types/DitoTypeLabel.vue +23 -0
  92. package/src/types/DitoTypeList.vue +364 -0
  93. package/src/types/DitoTypeMarkup.vue +700 -0
  94. package/src/types/DitoTypeMultiselect.vue +522 -0
  95. package/src/types/DitoTypeNumber.vue +66 -0
  96. package/src/types/DitoTypeObject.vue +136 -0
  97. package/src/types/DitoTypePanel.vue +18 -0
  98. package/src/types/DitoTypeProgress.vue +40 -0
  99. package/src/types/DitoTypeRadio.vue +45 -0
  100. package/src/types/DitoTypeSection.vue +80 -0
  101. package/src/types/DitoTypeSelect.vue +133 -0
  102. package/src/types/DitoTypeSlider.vue +66 -0
  103. package/src/types/DitoTypeSpacer.vue +11 -0
  104. package/src/types/DitoTypeSwitch.vue +40 -0
  105. package/src/types/DitoTypeText.vue +101 -0
  106. package/src/types/DitoTypeTextarea.vue +48 -0
  107. package/src/types/DitoTypeTreeList.vue +193 -0
  108. package/src/types/DitoTypeUpload.vue +503 -0
  109. package/src/types/index.js +30 -0
  110. package/src/utils/SchemaGraph.js +147 -0
  111. package/src/utils/accessor.js +75 -0
  112. package/src/utils/agent.js +47 -0
  113. package/src/utils/data.js +92 -0
  114. package/src/utils/filter.js +266 -0
  115. package/src/utils/math.js +14 -0
  116. package/src/utils/options.js +48 -0
  117. package/src/utils/path.js +5 -0
  118. package/src/utils/resource.js +44 -0
  119. package/src/utils/route.js +53 -0
  120. package/src/utils/schema.js +1121 -0
  121. package/src/utils/type.js +81 -0
  122. package/src/utils/uid.js +15 -0
  123. package/src/utils/units.js +5 -0
  124. package/src/validators/_creditcard.js +6 -0
  125. package/src/validators/_decimals.js +11 -0
  126. package/src/validators/_domain.js +6 -0
  127. package/src/validators/_email.js +6 -0
  128. package/src/validators/_hostname.js +6 -0
  129. package/src/validators/_integer.js +6 -0
  130. package/src/validators/_max.js +6 -0
  131. package/src/validators/_min.js +6 -0
  132. package/src/validators/_password.js +5 -0
  133. package/src/validators/_range.js +6 -0
  134. package/src/validators/_required.js +9 -0
  135. package/src/validators/_url.js +6 -0
  136. package/src/validators/index.js +12 -0
  137. package/src/verbs.js +17 -0
  138. package/types/index.d.ts +3298 -0
  139. package/types/tests/admin.test-d.ts +27 -0
  140. package/types/tests/component-buttons.test-d.ts +44 -0
  141. package/types/tests/component-list.test-d.ts +159 -0
  142. package/types/tests/component-misc.test-d.ts +137 -0
  143. package/types/tests/component-object.test-d.ts +69 -0
  144. package/types/tests/component-section.test-d.ts +174 -0
  145. package/types/tests/component-select.test-d.ts +107 -0
  146. package/types/tests/components.test-d.ts +81 -0
  147. package/types/tests/context.test-d.ts +31 -0
  148. package/types/tests/fixtures.ts +24 -0
  149. package/types/tests/form.test-d.ts +109 -0
  150. package/types/tests/instance.test-d.ts +20 -0
  151. package/types/tests/schema-features.test-d.ts +402 -0
  152. package/types/tests/variance.test-d.ts +125 -0
  153. package/types/tests/view.test-d.ts +146 -0
@@ -0,0 +1,700 @@
1
+ <template lang="pug">
2
+ .dito-markup(:id="dataPath")
3
+ .dito-buttons.dito-buttons--toolbar(
4
+ v-if="groupedButtons.length > 0"
5
+ )
6
+ .dito-buttons__group(
7
+ v-for="buttons in groupedButtons"
8
+ )
9
+ button.dito-button(
10
+ v-for="{ name, icon, isActive, onClick } in buttons"
11
+ :key="name"
12
+ :class="{ 'dito-button--active': isActive }"
13
+ @click="onClick"
14
+ )
15
+ DitoIcon(:name="icon")
16
+ EditorContent.dito-markup-editor(
17
+ ref="editor"
18
+ :editor="editor"
19
+ :style="styles"
20
+ )
21
+ .dito-resize(
22
+ v-if="resizable"
23
+ @mousedown.stop.prevent="onDragResize"
24
+ )
25
+ </template>
26
+
27
+ <script>
28
+ import DitoTypeComponent from '../DitoTypeComponent.js'
29
+ import DomMixin from '../mixins/DomMixin.js'
30
+ import { getSchemaAccessor } from '../utils/accessor.js'
31
+ // Tiptap:
32
+ import { Editor, EditorContent, Mark, getMarkAttributes } from '@tiptap/vue-3'
33
+ import { Slice, Fragment } from '@tiptap/pm/model'
34
+ // Essentials:
35
+ import { Document } from '@tiptap/extension-document'
36
+ import { Text } from '@tiptap/extension-text'
37
+ // Marks:
38
+ import { Bold } from '@tiptap/extension-bold'
39
+ import { Code } from '@tiptap/extension-code'
40
+ import { Italic } from '@tiptap/extension-italic'
41
+ import { Link } from '@tiptap/extension-link'
42
+ import { Strike } from '@tiptap/extension-strike'
43
+ import { Subscript } from '@tiptap/extension-subscript'
44
+ import { Superscript } from '@tiptap/extension-superscript'
45
+ import { Underline } from '@tiptap/extension-underline'
46
+ // Nodes:
47
+ import { Blockquote } from '@tiptap/extension-blockquote'
48
+ import { CodeBlock } from '@tiptap/extension-code-block'
49
+ import { HardBreak } from '@tiptap/extension-hard-break'
50
+ import { Heading } from '@tiptap/extension-heading'
51
+ import { Paragraph } from '@tiptap/extension-paragraph'
52
+ import { HorizontalRule } from '@tiptap/extension-horizontal-rule'
53
+ import { OrderedList } from '@tiptap/extension-ordered-list'
54
+ import { BulletList } from '@tiptap/extension-bullet-list'
55
+ import { ListItem } from '@tiptap/extension-list-item'
56
+ import { Footnotes, FootnoteReference, Footnote } from 'tiptap-footnotes'
57
+ // TODO:
58
+ // import { Image } from '@tiptap/extension-image'
59
+ // import { Mention } from '@tiptap/extension-mention'
60
+ // import { CodeBlockHighlight } from '@tiptap/extension-code-block-highlight'
61
+ // import { Table } from '@tiptap/extension-table'
62
+ // import { TableCell } from '@tiptap/extension-table-cell'
63
+ // import { TableHeader } from '@tiptap/extension-table-header'
64
+ // import { TableNodes } from '@tiptap/extension-table-nodes'
65
+ // import { TableRow } from '@tiptap/extension-table-row'
66
+ // import { TaskList } from '@tiptap/extension-task-list'
67
+ // import { TaskItem } from '@tiptap/extension-task-item'
68
+ // Tools:
69
+ import { History } from '@tiptap/extension-history'
70
+
71
+ import { DitoIcon } from '@ditojs/ui/src'
72
+ import { isArray, isObject, hyphenate, debounce, camelize } from '@ditojs/utils'
73
+
74
+ // @vue/component
75
+ export default DitoTypeComponent.register('markup', {
76
+ mixins: [DomMixin],
77
+ components: {
78
+ EditorContent,
79
+ DitoIcon
80
+ },
81
+
82
+ data() {
83
+ return {
84
+ editor: null,
85
+ height: null
86
+ }
87
+ },
88
+
89
+ computed: {
90
+ lines() {
91
+ return this.schema.lines || 10
92
+ },
93
+
94
+ hardBreak() {
95
+ return !!this.schema.hardBreak
96
+ },
97
+
98
+ styles() {
99
+ return {
100
+ height: this.height || `calc(${this.lines}em * var(--line-height))`
101
+ }
102
+ },
103
+
104
+ markButtons() {
105
+ return this.getButtons('marks', {
106
+ bold: true,
107
+ italic: true,
108
+ underline: true,
109
+ strike: true,
110
+ small: true,
111
+ code: true,
112
+ subscript: true,
113
+ superscript: true,
114
+ link: {
115
+ onClick: editor => this.onClickLink(editor)
116
+ }
117
+ })
118
+ },
119
+
120
+ basicNodeButtons() {
121
+ return this.getButtons('nodes', {
122
+ paragraph: {
123
+ command: 'setParagraph'
124
+ },
125
+ heading: {
126
+ attribute: 'level',
127
+ values: [1, 2, 3, 4, 5, 6]
128
+ }
129
+ })
130
+ },
131
+
132
+ advancedNodeButtons() {
133
+ return this.getButtons('nodes', {
134
+ bulletList: true,
135
+ orderedList: true,
136
+ blockquote: true,
137
+ codeBlock: true
138
+ })
139
+ },
140
+
141
+ toolButtons() {
142
+ return this.getButtons('tools', {
143
+ undo: true,
144
+ redo: true,
145
+ footnotes: {
146
+ command: 'addFootnote'
147
+ }
148
+ })
149
+ },
150
+
151
+ groupedButtons() {
152
+ return [
153
+ this.markButtons,
154
+ this.basicNodeButtons,
155
+ this.advancedNodeButtons,
156
+ this.toolButtons
157
+ ].filter(buttons => buttons.length > 0)
158
+ },
159
+
160
+ parseOptions() {
161
+ return {
162
+ preserveWhitespace: {
163
+ 'collapse': false,
164
+ 'preserve': true,
165
+ 'preserve-all': 'full'
166
+ }[this.whitespace]
167
+ }
168
+ },
169
+
170
+ editorOptions() {
171
+ return {
172
+ editable: !this.readyonly,
173
+ autoFocus: this.autofocus,
174
+ disableInputRules: !this.enableRules.input,
175
+ disablePasteRules: !this.enableRules.paste,
176
+ parseOptions: this.parseOptions,
177
+ editorProps: this.hardBreak
178
+ ? {
179
+ handlePaste: (view, event, slice) => {
180
+ const nodes = []
181
+
182
+ slice.content.forEach((node, offset, index) => {
183
+ if (index > 0 && node.type.name === 'paragraph') {
184
+ // Add hard break between paragraphs
185
+ nodes.push(view.state.schema.nodes.hardBreak.create())
186
+ }
187
+
188
+ // Extract content from paragraphs, keep other nodes as-is
189
+ if (node.type.name === 'paragraph') {
190
+ node.content.forEach(child => nodes.push(child))
191
+ } else {
192
+ nodes.push(node)
193
+ }
194
+ })
195
+
196
+ const paragraph = view.state.schema.nodes.paragraph.create(
197
+ null,
198
+ Fragment.from(nodes)
199
+ )
200
+
201
+ view.dispatch(
202
+ view.state.tr.replaceSelection(
203
+ new Slice(Fragment.from(paragraph), 0, 0)
204
+ )
205
+ )
206
+
207
+ return true
208
+ }
209
+ }
210
+ : {}
211
+ }
212
+ },
213
+
214
+ resizable: getSchemaAccessor('resizable', {
215
+ type: Boolean,
216
+ default: false
217
+ }),
218
+
219
+ whitespace: getSchemaAccessor('whitespace', {
220
+ type: String,
221
+ default: 'collapse'
222
+ // Possible values are: 'collapse', 'preserve', 'preserve-all'
223
+ }),
224
+
225
+ enableRules: getSchemaAccessor('enableRules', {
226
+ type: [Object, Boolean],
227
+ default: false,
228
+ get(enableRules) {
229
+ return isObject(enableRules)
230
+ ? enableRules
231
+ : {
232
+ input: !!enableRules,
233
+ paste: !!enableRules
234
+ }
235
+ }
236
+ })
237
+ },
238
+
239
+ watch: {
240
+ readyonly: 'updateEditorOptions',
241
+ autofocus: 'updateEditorOptions',
242
+ enableRules: 'updateEditorOptions'
243
+ },
244
+
245
+ created() {
246
+ let changed = false
247
+ let ignoreWatch = false
248
+
249
+ const onFocus = () => this.onFocus()
250
+
251
+ const onBlur = () => {
252
+ this.onBlur()
253
+ updateValue()
254
+ }
255
+
256
+ const onUpdate = () => {
257
+ setValueDebounced()
258
+ this.onInput()
259
+ }
260
+
261
+ const setValueDebounced = debounce(() => {
262
+ ignoreWatch = true
263
+ updateValue()
264
+ }, 100)
265
+
266
+ const updateValue = () => {
267
+ const content = this.editor.getHTML()
268
+ const value = this.hardBreak
269
+ ? content.replace(/^<p>(.*?)<\/p>$/s, '$1')
270
+ : content
271
+ if (value !== this.value) {
272
+ changed = true
273
+ this.value = value
274
+ }
275
+ if (!this.focused && changed) {
276
+ this.onChange()
277
+ changed = false
278
+ }
279
+ }
280
+
281
+ this.$watch('value', value => {
282
+ if (ignoreWatch) {
283
+ ignoreWatch = false
284
+ } else {
285
+ const content = this.hardBreak
286
+ ? `<p>${value}</p>`
287
+ : value
288
+ this.editor.commands.setContent(content, {
289
+ emitUpdate: false,
290
+ parseOptions: this.parseOptions
291
+ })
292
+ }
293
+ })
294
+
295
+ this.editor = new Editor({
296
+ ...this.editorOptions,
297
+ onFocus,
298
+ onBlur,
299
+ onUpdate,
300
+ extensions: this.getExtensions(),
301
+ content: this.value || ''
302
+ })
303
+ },
304
+
305
+ unmounted() {
306
+ this.editor.destroy()
307
+ },
308
+
309
+ methods: {
310
+ onDragResize(event) {
311
+ const getPoint = ({ clientX: x, clientY: y }) => ({ x, y })
312
+
313
+ let prevY = getPoint(event).y
314
+ let height = parseFloat(getComputedStyle(this.$refs.editor.$el).height)
315
+
316
+ const mousemove = event => {
317
+ const { y } = getPoint(event)
318
+ height += y - prevY
319
+ prevY = y
320
+ this.height = `${Math.max(height, 0)}px`
321
+ }
322
+
323
+ const handlers = this.domOn(document, {
324
+ mousemove,
325
+
326
+ mouseup(event) {
327
+ mousemove(event)
328
+ handlers.remove()
329
+ }
330
+ })
331
+ },
332
+
333
+ updateEditorOptions() {
334
+ this.editor.setOptions(this.editorOptions)
335
+ },
336
+
337
+ async onClickLink(editor) {
338
+ const attributes = await this.rootComponent.showDialog({
339
+ components: {
340
+ DitoIcon,
341
+ href: {
342
+ type: 'url',
343
+ label: 'Link',
344
+ autofocus: true
345
+ },
346
+ title: {
347
+ type: 'text',
348
+ label: 'Title'
349
+ }
350
+ },
351
+ buttons: {
352
+ cancel: {},
353
+ apply: { type: 'submit' },
354
+ remove: {
355
+ events: {
356
+ click({ dialogComponent }) {
357
+ dialogComponent.resolve(null)
358
+ }
359
+ }
360
+ }
361
+ },
362
+ data: getMarkAttributes(this.editor.state, 'link')
363
+ })
364
+ if (attributes) {
365
+ let { href, title } = attributes
366
+ if (href) {
367
+ // See if `href` can be parsed as a URL, and if not,
368
+ // prefix it with a default protocol.
369
+ try {
370
+ new URL(href)
371
+ } catch {
372
+ href = `https://${href}`
373
+ }
374
+ }
375
+ editor.commands.setLink({ href, title })
376
+ } else if (attributes === null) {
377
+ editor.commands.unsetLink()
378
+ }
379
+ },
380
+
381
+ getExtensions() {
382
+ const {
383
+ marks = {},
384
+ nodes = {},
385
+ tools = {}
386
+ } = this.schema
387
+ return [
388
+ // Essentials:
389
+ tools.footnotes
390
+ ? Document.extend({ content: 'block+ footnotes?' })
391
+ : Document,
392
+
393
+ Text,
394
+ Paragraph, // button can be controlled, but node needs to be on.
395
+
396
+ // Marks: `schema.marks`
397
+ marks.bold && Bold,
398
+ marks.italic && Italic,
399
+ marks.underline && Underline,
400
+ marks.strike && Strike,
401
+ marks.small && Small,
402
+ marks.code && Code,
403
+ marks.subscript && Superscript,
404
+ marks.superscript && Subscript,
405
+ marks.link && LinkWithTitle,
406
+
407
+ // Nodes: `schema.nodes`
408
+ nodes.blockquote && Blockquote,
409
+ nodes.codeBlock && CodeBlock,
410
+ nodes.heading && Heading.configure({ levels: nodes.heading }),
411
+ nodes.horizontalRule && HorizontalRule,
412
+ (nodes.orderedList || nodes.bulletList) && ListItem,
413
+ nodes.bulletList && BulletList,
414
+ nodes.orderedList && OrderedList,
415
+
416
+ // Footnotes:
417
+ ...(tools.footnotes ? [Footnotes, Footnote, FootnoteReference] : []),
418
+
419
+ // TODO:
420
+ // nodes.todoList && TodoItem,
421
+ // nodes.todoList && TodoList,
422
+
423
+ // Tools: `schema.tools`
424
+ tools.history && History,
425
+
426
+ HardBreak.extend({
427
+ addKeyboardShortcuts: () => {
428
+ const setHardBreak = () => this.editor.commands.setHardBreak()
429
+ return {
430
+ 'Mod-Enter': setHardBreak,
431
+ 'Shift-Enter': setHardBreak,
432
+ ...(this.hardBreak ? { Enter: setHardBreak } : null)
433
+ }
434
+ }
435
+ })
436
+ ].filter(extension => !!extension)
437
+ },
438
+
439
+ getButtons(settingsName, descriptions) {
440
+ const list = []
441
+ const { commands } = this.editor
442
+
443
+ const addButton = ({ name, icon, command, attributes, onClick }) => {
444
+ list.push({
445
+ name,
446
+ icon,
447
+ isActive: this.editor.isActive(name, attributes),
448
+ onClick: () => {
449
+ command ??=
450
+ name in commands
451
+ ? name
452
+ : `toggle${camelize(name, true)}`
453
+ if (command in commands) {
454
+ const apply = attributes =>
455
+ this.editor.chain()[command](attributes).focus().run()
456
+ onClick
457
+ ? onClick(this.editor, attributes)
458
+ : apply(attributes)
459
+ }
460
+ }
461
+ })
462
+ }
463
+
464
+ const settings = this.schema[settingsName]
465
+ if (settings) {
466
+ for (const [name, description] of Object.entries(descriptions)) {
467
+ const settingName = ['undo', 'redo'].includes(name) ? 'history' : name
468
+ const setting = settings[settingName]
469
+ const icon = hyphenate(name)
470
+ if (setting) {
471
+ if (description === true) {
472
+ addButton({ name, icon })
473
+ } else if (isObject(description)) {
474
+ const { command, attribute, values, onClick } = description
475
+ if (attribute) {
476
+ if (isArray(values) && isArray(setting)) {
477
+ // Support heading level attrs:
478
+ for (const value of values) {
479
+ if (setting.includes(value)) {
480
+ addButton({
481
+ name,
482
+ icon: `${icon}-${value}`,
483
+ command,
484
+ attributes: { [attribute]: value },
485
+ onClick
486
+ })
487
+ }
488
+ }
489
+ }
490
+ } else {
491
+ addButton({ name, icon, command, onClick })
492
+ }
493
+ }
494
+ }
495
+ }
496
+ }
497
+ return list
498
+ },
499
+
500
+ focusElement() {
501
+ this.editor.commands.focus()
502
+ },
503
+
504
+ blurElement() {
505
+ this.editor.commands.blur()
506
+ }
507
+ }
508
+ })
509
+
510
+ const Small = Mark.create({
511
+ name: 'small',
512
+
513
+ parseHTML() {
514
+ return [{ tag: 'small' }]
515
+ },
516
+
517
+ renderHTML() {
518
+ return ['small', 0]
519
+ },
520
+
521
+ addCommands() {
522
+ return {
523
+ setSmall:
524
+ attributes =>
525
+ ({ commands }) => {
526
+ return commands.setMark(this.name, attributes)
527
+ },
528
+ toggleSmall:
529
+ attributes =>
530
+ ({ commands }) => {
531
+ return commands.toggleMark(this.name, attributes)
532
+ },
533
+ unsetSmall:
534
+ () =>
535
+ ({ commands }) => {
536
+ return commands.unsetMark(this.name)
537
+ }
538
+ }
539
+ }
540
+ })
541
+
542
+ const LinkWithTitle = Link.extend({
543
+ inclusive: false,
544
+
545
+ addAttributes() {
546
+ return {
547
+ href: {
548
+ default: null
549
+ },
550
+ title: {
551
+ default: null
552
+ }
553
+ }
554
+ },
555
+
556
+ parseHTML() {
557
+ return [
558
+ {
559
+ tag: 'a',
560
+ getAttrs: element => ({
561
+ href: element.getAttribute('href'),
562
+ title: element.getAttribute('title')
563
+ })
564
+ }
565
+ ]
566
+ },
567
+
568
+ renderHTML({ HTMLAttributes }) {
569
+ return ['a', HTMLAttributes, 0]
570
+ }
571
+ })
572
+ </script>
573
+
574
+ <style lang="scss">
575
+ @import '../styles/_imports';
576
+
577
+ .dito-markup {
578
+ @extend %input;
579
+
580
+ position: relative;
581
+
582
+ .ProseMirror {
583
+ height: 100%;
584
+ outline: none;
585
+ }
586
+
587
+ .dito-markup-editor {
588
+ overflow-y: scroll;
589
+ margin-top: $input-padding-ver;
590
+ // Move padding "inside" editor to correctly position scrollbar
591
+ margin-right: -$input-padding-hor;
592
+ padding-right: $input-padding-hor;
593
+ }
594
+
595
+ .dito-buttons--toolbar {
596
+ margin: 0;
597
+ }
598
+
599
+ h1,
600
+ h2,
601
+ h3,
602
+ p,
603
+ ul,
604
+ ol,
605
+ pre,
606
+ blockquote {
607
+ margin: 1rem 0;
608
+
609
+ &:first-child {
610
+ margin-top: 0;
611
+ }
612
+
613
+ &:last-child {
614
+ margin-bottom: 0;
615
+ }
616
+ }
617
+
618
+ h1,
619
+ h2,
620
+ h3 {
621
+ font-weight: bold;
622
+ }
623
+
624
+ h1 {
625
+ font-size: 1.4rem;
626
+ }
627
+
628
+ h2 {
629
+ font-size: 1.2rem;
630
+ }
631
+
632
+ ul {
633
+ list-style: disc;
634
+ }
635
+
636
+ code {
637
+ font-family: $font-family-mono;
638
+ }
639
+
640
+ pre {
641
+ padding: 0.7rem 1rem;
642
+ border-radius: $border-radius;
643
+ background: $color-darker;
644
+ color: $color-white;
645
+ overflow-x: auto;
646
+
647
+ code {
648
+ display: block;
649
+ }
650
+ }
651
+
652
+ p code {
653
+ display: inline-block;
654
+ padding: 0 0.3rem;
655
+ border-radius: $border-radius;
656
+ background: $color-lighter;
657
+ }
658
+
659
+ a {
660
+ pointer-events: none;
661
+ cursor: default;
662
+ color: blue;
663
+ text-decoration: underline;
664
+ }
665
+
666
+ ul,
667
+ ol {
668
+ padding-left: 2rem;
669
+ }
670
+
671
+ li {
672
+ & > p,
673
+ & > ol,
674
+ & > ul {
675
+ margin: 0;
676
+ }
677
+ }
678
+
679
+ blockquote {
680
+ border-left: 3px solid $color-lighter;
681
+ padding-left: 1em;
682
+ font-style: italic;
683
+
684
+ p {
685
+ margin: 0;
686
+ }
687
+ }
688
+
689
+ ol.footnotes {
690
+ margin-top: 1em;
691
+ padding: 1em 0;
692
+ list-style-type: decimal;
693
+ padding-left: 2em;
694
+
695
+ &:has(li) {
696
+ border-top: 1px solid $color-light;
697
+ }
698
+ }
699
+ }
700
+ </style>