@demos-europe/demosplan-ui 0.0.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.
Files changed (133) hide show
  1. package/LICENSE +21 -0
  2. package/buildTokens.js +59 -0
  3. package/components/DpButton/DpButton.stories.mdx +136 -0
  4. package/components/DpButton/DpButton.vue +118 -0
  5. package/components/DpDetails/DpDetails.stories.mdx +55 -0
  6. package/components/DpDetails/DpDetails.vue +58 -0
  7. package/components/DpIcon/DpIcon.stories.mdx +396 -0
  8. package/components/DpIcon/DpIcon.vue +51 -0
  9. package/components/DpIcon/util/iconVariables.js +148 -0
  10. package/components/DpInput/DpInput.stories.mdx +127 -0
  11. package/components/DpInput/DpInput.vue +284 -0
  12. package/components/DpLabel/DpLabel.stories.mdx +103 -0
  13. package/components/DpLabel/DpLabel.vue +112 -0
  14. package/components/DpLoading/DpLoading.stories.mdx +63 -0
  15. package/components/DpLoading/DpLoading.vue +63 -0
  16. package/components/core/DpAccordion.vue +108 -0
  17. package/components/core/DpAnonymizeText.vue +136 -0
  18. package/components/core/DpAutocomplete.vue +133 -0
  19. package/components/core/DpBulkEditHeader.vue +53 -0
  20. package/components/core/DpButtonIcon.vue +47 -0
  21. package/components/core/DpButtonRow.vue +155 -0
  22. package/components/core/DpCard.vue +54 -0
  23. package/components/core/DpChangeStateAtDate.vue +223 -0
  24. package/components/core/DpCheckboxGroup.vue +93 -0
  25. package/components/core/DpContextualHelp.vue +54 -0
  26. package/components/core/DpCopyPasteButton.vue +47 -0
  27. package/components/core/DpDashboardTaskCard.vue +123 -0
  28. package/components/core/DpDataTable/DataTableSearch.js +44 -0
  29. package/components/core/DpDataTable/DpColumnSelector.vue +133 -0
  30. package/components/core/DpDataTable/DpDataTable.vue +647 -0
  31. package/components/core/DpDataTable/DpDataTableExtended.vue +377 -0
  32. package/components/core/DpDataTable/DpResizeHandle.vue +37 -0
  33. package/components/core/DpDataTable/DpSelectPageItemCount.vue +70 -0
  34. package/components/core/DpDataTable/DpTableHeader.vue +197 -0
  35. package/components/core/DpDataTable/DpTableRow.vue +355 -0
  36. package/components/core/DpDataTable/DpWrapTrigger.vue +48 -0
  37. package/components/core/DpDataTable/lib/ResizableColumns.js +83 -0
  38. package/components/core/DpEditableList.vue +161 -0
  39. package/components/core/DpEditor/DpBoilerPlate.vue +140 -0
  40. package/components/core/DpEditor/DpBoilerPlateModal.vue +166 -0
  41. package/components/core/DpEditor/DpEditor.vue +1281 -0
  42. package/components/core/DpEditor/DpLinkModal.vue +117 -0
  43. package/components/core/DpEditor/DpRecommendationModal/DpInsertableRecommendation.vue +137 -0
  44. package/components/core/DpEditor/DpRecommendationModal.vue +283 -0
  45. package/components/core/DpEditor/DpResizableImage.vue +121 -0
  46. package/components/core/DpEditor/DpUploadModal.vue +121 -0
  47. package/components/core/DpEditor/libs/Decoration.js +66 -0
  48. package/components/core/DpEditor/libs/SegmentRangeChangePlugin.js +35 -0
  49. package/components/core/DpEditor/libs/editorAnonymize.js +66 -0
  50. package/components/core/DpEditor/libs/editorBuildSuggestion.js +269 -0
  51. package/components/core/DpEditor/libs/editorCustomDelete.js +32 -0
  52. package/components/core/DpEditor/libs/editorCustomImage.js +100 -0
  53. package/components/core/DpEditor/libs/editorCustomInsert.js +32 -0
  54. package/components/core/DpEditor/libs/editorCustomLink.js +46 -0
  55. package/components/core/DpEditor/libs/editorCustomMark.js +32 -0
  56. package/components/core/DpEditor/libs/editorInsertAtCursorPos.js +41 -0
  57. package/components/core/DpEditor/libs/editorObscure.js +60 -0
  58. package/components/core/DpEditor/libs/editorUnAnonymize.js +56 -0
  59. package/components/core/DpEditor/libs/handleWordPaste.js +360 -0
  60. package/components/core/DpEditor/libs/preventDrop.js +31 -0
  61. package/components/core/DpEditor/libs/preventKeyboardInput.js +27 -0
  62. package/components/core/DpEditor/libs/preventPaste.js +28 -0
  63. package/components/core/DpFlyout.vue +119 -0
  64. package/components/core/DpInlineNotification.vue +116 -0
  65. package/components/core/DpModal.vue +208 -0
  66. package/components/core/DpObscure.vue +29 -0
  67. package/components/core/DpPager.vue +139 -0
  68. package/components/core/DpProgressBar.vue +67 -0
  69. package/components/core/DpRegisterFlyout.vue +58 -0
  70. package/components/core/DpResettableInput.vue +140 -0
  71. package/components/core/DpSkeletonBox.vue +32 -0
  72. package/components/core/DpSlidebar.vue +86 -0
  73. package/components/core/DpSlidingPagination.vue +45 -0
  74. package/components/core/DpSplitButton.vue +77 -0
  75. package/components/core/DpSwitcher.vue +62 -0
  76. package/components/core/DpTableCardList/DpTableCard.vue +61 -0
  77. package/components/core/DpTableCardList/DpTableCardListHeader.vue +83 -0
  78. package/components/core/DpTabs/DpTab.vue +52 -0
  79. package/components/core/DpTabs/DpTabs.vue +165 -0
  80. package/components/core/DpTextWrapper.vue +65 -0
  81. package/components/core/DpToggleForm.vue +72 -0
  82. package/components/core/DpTooltipIcon.vue +52 -0
  83. package/components/core/DpTransitionExpand.vue +87 -0
  84. package/components/core/DpTreeList/DpTreeList.vue +334 -0
  85. package/components/core/DpTreeList/DpTreeListCheckbox.vue +79 -0
  86. package/components/core/DpTreeList/DpTreeListNode.vue +348 -0
  87. package/components/core/DpTreeList/DpTreeListToggle.vue +71 -0
  88. package/components/core/DpTreeList/utils/constants.js +14 -0
  89. package/components/core/DpUpload/DpUpload.vue +223 -0
  90. package/components/core/DpUpload/DpUploadFiles.vue +269 -0
  91. package/components/core/DpUpload/DpUploadedFile.vue +80 -0
  92. package/components/core/DpUpload/DpUploadedFileList.vue +56 -0
  93. package/components/core/DpUpload/utils/GetFileIdsByHash.js +42 -0
  94. package/components/core/DpUpload/utils/UppyTranslations.js +31 -0
  95. package/components/core/DpVideoPlayer.vue +115 -0
  96. package/components/core/HeightLimit.vue +121 -0
  97. package/components/core/MultistepNav.vue +89 -0
  98. package/components/core/form/DpCheckbox.vue +108 -0
  99. package/components/core/form/DpDateRangePicker.vue +186 -0
  100. package/components/core/form/DpDatepicker.vue +160 -0
  101. package/components/core/form/DpDatetimePicker.vue +194 -0
  102. package/components/core/form/DpFormRow.vue +79 -0
  103. package/components/core/form/DpMultiselect.vue +164 -0
  104. package/components/core/form/DpRadio.vue +128 -0
  105. package/components/core/form/DpSearchField.vue +110 -0
  106. package/components/core/form/DpSelect.vue +149 -0
  107. package/components/core/form/DpTextArea.vue +152 -0
  108. package/components/core/form/DpTimePicker.vue +374 -0
  109. package/components/core/form/DpToggle.vue +78 -0
  110. package/components/core/index.js +132 -0
  111. package/components/core/notify/DpNotifyContainer.vue +122 -0
  112. package/components/core/notify/DpNotifyMessage.vue +95 -0
  113. package/components/core/shared/DpStickyElement.vue +95 -0
  114. package/components/index.js +24 -0
  115. package/components/shared/translations.js +15 -0
  116. package/directives/CleanHtml/CleanHtml.js +50 -0
  117. package/directives/CleanHtml/CleanHtml.stories.mdx +64 -0
  118. package/directives/Tooltip/Tooltip.js +40 -0
  119. package/directives/Tooltip/Tooltip.stories.mdx +42 -0
  120. package/directives/index.js +17 -0
  121. package/lib/index.js +14 -0
  122. package/lib/prefixClass.js +47 -0
  123. package/mixins/index.js +14 -0
  124. package/mixins/prefixClassMixin.js +22 -0
  125. package/package.json +52 -0
  126. package/shared/props.js +86 -0
  127. package/style/index.css +7 -0
  128. package/tailwind.config.js +24 -0
  129. package/tokens/color.json +358 -0
  130. package/tokens/color.stories.mdx +45 -0
  131. package/tokens/fontSize.json +100 -0
  132. package/tokens/space.json +33 -0
  133. package/utils/lengthHint.js +69 -0
@@ -0,0 +1,121 @@
1
+ <license>
2
+ (c) 2010-present DEMOS E-Partizipation GmbH.
3
+
4
+ This file is part of the package @demos-europe/demosplan-ui,
5
+ for more information see the license file.
6
+
7
+ All rights reserved
8
+ </license>
9
+
10
+ <template>
11
+ <dp-modal
12
+ ref="uploadModal"
13
+ content-classes="u-2-of-3-lap-up u-1-of-2-desk-up">
14
+ <template>
15
+ <h3
16
+ v-if="editAltTextOnly"
17
+ class="u-mb">
18
+ {{ Translator.trans('image.edit') }}
19
+ </h3>
20
+ <h3
21
+ v-else
22
+ class="u-mb">
23
+ {{ Translator.trans('image.insert') }}
24
+ </h3>
25
+ <div v-show="editAltTextOnly === false">
26
+ <dp-upload-files
27
+ allowed-file-types="img"
28
+ id="imageFile"
29
+ :max-file-size="20 * 1024 * 1024/* 20 MiB */"
30
+ :max-number-of-files="1"
31
+ ref="uploader"
32
+ :translations="{ dropHereOr: Translator.trans('form.button.upload.image', { browse: '{browse}', maxUploadSize: '20MB' }) }"
33
+ @upload-success="setFile" />
34
+ </div>
35
+ <dp-input
36
+ id="altText"
37
+ v-model="altText"
38
+ class="u-mb"
39
+ :label="{
40
+ hint: Translator.trans('image.alt.explanation'),
41
+ text: Translator.trans('alternative.text')
42
+ }" />
43
+ <div class="u-mt text--right width-100p space-inline-s">
44
+ <button
45
+ class="btn btn--primary"
46
+ type="button"
47
+ @click="emitAndClose()">
48
+ {{ Translator.trans('insert') }}
49
+ </button>
50
+ <button
51
+ class="btn btn--secondary"
52
+ type="button"
53
+ @click="resetAndClose()">
54
+ {{ Translator.trans('abort') }}
55
+ </button>
56
+ </div>
57
+ </template>
58
+ </dp-modal>
59
+ </template>
60
+
61
+ <script>
62
+ import { DpInput } from 'demosplan-ui/components'
63
+ import DpModal from '../DpModal'
64
+ import DpUploadFiles from '../DpUpload/DpUploadFiles'
65
+
66
+ export default {
67
+ name: 'DpUploadModal',
68
+
69
+ components: {
70
+ DpInput,
71
+ DpModal,
72
+ DpUploadFiles
73
+ },
74
+
75
+ data () {
76
+ return {
77
+ fileUrl: '',
78
+ altText: '',
79
+ editAltTextOnly: false
80
+ }
81
+ },
82
+
83
+ methods: {
84
+ emitAndClose () {
85
+ if (this.editAltTextOnly) {
86
+ this.$emit('add-alt', this.altText)
87
+ } else if (this.fileUrl) {
88
+ this.$emit('insert-image', this.fileUrl, this.altText)
89
+ }
90
+ this.resetAndClose()
91
+ },
92
+
93
+ resetAndClose () {
94
+ this.altText = ''
95
+ this.fileUrl = ''
96
+ this.editAltTextOnly = false
97
+ this.$emit('close')
98
+ this.toggleModal()
99
+ },
100
+
101
+ setFile ({ hash }) {
102
+ this.fileUrl = Routing.generate('core_file', { hash: hash })
103
+ // Force-update the component so that DpModal updates and therefore check for new focusable elements
104
+ this.$forceUpdate()
105
+ },
106
+
107
+ toggleModal (data) {
108
+ const willCloseModal = this.$refs.uploadModal.isOpenModal === true
109
+
110
+ if (willCloseModal) {
111
+ this.$refs.uploader.clearFilesList()
112
+ } else if (data) {
113
+ this.editAltTextOnly = data.editAltOnly
114
+ this.altText = data.currentAlt
115
+ }
116
+
117
+ this.$refs.uploadModal.toggle()
118
+ }
119
+ }
120
+ }
121
+ </script>
@@ -0,0 +1,66 @@
1
+ /**
2
+ * (c) 2010-present DEMOS E-Partizipation GmbH.
3
+ *
4
+ * This file is part of the package @demos-europe/demosplan-ui,
5
+ * for more information see the license file.
6
+ *
7
+ * All rights reserved
8
+ */
9
+
10
+ import { Decoration, DecorationSet } from 'prosemirror-view'
11
+ import { Plugin, TextSelection } from 'prosemirror-state'
12
+
13
+ const addHandle = (tr, set, pos, id) => {
14
+ const widget = document.createElement('span')
15
+ widget.classList.add('handle')
16
+ widget.setAttribute('data-position', pos)
17
+ widget.innerHTML = `<span id="container" data-position="${pos}"><div id="bubble" data-position="${pos}"></div>|</span>`
18
+ const deco = Decoration.widget(pos, widget, { id: id })
19
+ set = set.add(tr.doc, [deco])
20
+ return set
21
+ }
22
+
23
+ const removeHandle = (set, id) => {
24
+ set = set.remove(set.find(null, null,
25
+ spec => spec.id === id))
26
+ return set
27
+ }
28
+
29
+ const placeholderPlugin = new Plugin({
30
+ state: {
31
+ init () { return DecorationSet.empty },
32
+ apply (tr, set) {
33
+ // Adjust decoration positions to changes made by the transaction
34
+ set = set.map(tr.mapping, tr.doc)
35
+ // See if the transaction adds or removes any placeholders
36
+ const action = tr.getMeta(this)
37
+ if (action && action.add) {
38
+ set = addHandle(tr, set, action.add.pos, action.add.id)
39
+ } else if (action && action.remove) {
40
+ set = removeHandle(set, action.remove.id)
41
+ } else if (action && action.move) {
42
+ set = removeHandle(set, action.move.id)
43
+ set = addHandle(tr, set, action.move.pos, action.move.id)
44
+ }
45
+ return set
46
+ }
47
+ },
48
+ props: {
49
+ decorations (state) { return this.getState(state) }
50
+ },
51
+ filterTransaction (tr, state) {
52
+ const action = tr.getMeta(this)
53
+ /**
54
+ * The behaviour of text selections in prosemirror differs in Chrome and Firefox.
55
+ * In our case, Chrome treats the current caret position as selection range while Firefox applies the correct selection
56
+ * from initial caret position to current caret position.
57
+ * To fix this behaviour we modify the transaction and set the correct selection in this method.
58
+ */
59
+ if (action && action.move) {
60
+ tr.setSelection(new TextSelection(state.doc.resolve(action.move.initialPos), state.doc.resolve(action.move.pos)))
61
+ }
62
+ return true
63
+ }
64
+ })
65
+
66
+ export { placeholderPlugin }
@@ -0,0 +1,35 @@
1
+ /**
2
+ * (c) 2010-present DEMOS E-Partizipation GmbH.
3
+ *
4
+ * This file is part of the package @demos-europe/demosplan-ui,
5
+ * for more information see the license file.
6
+ *
7
+ * All rights reserved
8
+ */
9
+
10
+ import { Plugin } from 'prosemirror-state'
11
+
12
+ export default new Plugin({
13
+ /*
14
+ * State: {
15
+ * init () {
16
+ * return {}
17
+ * },
18
+ * apply (tr, set) {
19
+ * console.log(set)
20
+ * }
21
+ * },
22
+ */
23
+ appendTransaction (transactions) {
24
+ // Console.log(transactions)
25
+ }
26
+ /*
27
+ * View () {
28
+ * return {
29
+ * update (editorView, prevState) {
30
+ * debugger
31
+ * }
32
+ * }
33
+ * }
34
+ */
35
+ })
@@ -0,0 +1,66 @@
1
+ /**
2
+ * (c) 2010-present DEMOS E-Partizipation GmbH.
3
+ *
4
+ * This file is part of the package @demos-europe/demosplan-ui,
5
+ * for more information see the license file.
6
+ *
7
+ * All rights reserved
8
+ */
9
+
10
+ /*
11
+ * This is the anonymize-extension for tiptap, built on the basis of tiptap bold-extension.
12
+ * On mark-anonymize in tiptap, we wrap up the marked content in <span class='anonymize'></span> tags, and then before
13
+ * saving the changes we convert them to <dp-obscure>, so that they are correctly saved in BE. But to display the
14
+ * <span class='u-obscure'> tags in the editor we need to use the toDOM function provided by tiptap/prosemirror.
15
+ *
16
+ * InputRules and pasteRules help to handle diverse behaviour when we want to obscure only part of words or we want to
17
+ * use more than one tool (e.g. obscure and bold) simultaneously, etc.
18
+ */
19
+
20
+ import { markInputRule, markPasteRule, toggleMark } from 'tiptap-commands'
21
+ import { Mark } from 'tiptap'
22
+
23
+ export default class EditorAnonymize extends Mark {
24
+ get name () {
25
+ return 'anonymize'
26
+ }
27
+
28
+ get schema () {
29
+ return {
30
+ attrs: {
31
+ title: {
32
+ default: null
33
+ }
34
+ },
35
+ spanning: false,
36
+ parseDOM: [{
37
+ tag: '.anonymize-me',
38
+ getAttrs: dom => ({
39
+ title: dom.getAttribute('title')
40
+ })
41
+ }],
42
+ toDOM: node => {
43
+ return ['span', {
44
+ ...node.attrs,
45
+ class: 'anonymize-me'
46
+ }, 0]
47
+ }
48
+ }
49
+ }
50
+
51
+ commands ({ type }) {
52
+ return () => toggleMark(type)
53
+ }
54
+
55
+ inputRules ({ type }) {
56
+ return [
57
+ markInputRule(/(?:<o>)([^<o>]+)(?:<o>)$/, type)
58
+ ]
59
+ }
60
+
61
+ pasteRules ({ type }) {
62
+ return [
63
+ markPasteRule(/(?:<o>)([^<o>]+)(?:<o>)/g, type)
64
+ ]
65
+ }
66
+ }
@@ -0,0 +1,269 @@
1
+ /**
2
+ * (c) 2010-present DEMOS E-Partizipation GmbH.
3
+ *
4
+ * This file is part of the package @demos-europe/demosplan-ui,
5
+ * for more information see the license file.
6
+ *
7
+ * All rights reserved
8
+ */
9
+
10
+ import tippy, { sticky } from 'tippy.js'
11
+ import { Mention } from 'tiptap-extensions'
12
+
13
+ /**
14
+ * A utility function to attach event listeners to the suggestion popup.
15
+ *
16
+ * @param args {Object} One or more objects containing eventType and callback { eventType: 'click', handler: () => ({}) }
17
+ */
18
+ function attachEventListeners (...args) {
19
+ const listWrapper = document.querySelector('[data-suggestion-id="suggestion-popup"]')
20
+ args.forEach(({ eventType, handler }) => attachSingleEventListener(listWrapper, eventType, handler))
21
+ }
22
+
23
+ /**
24
+ * A utility function to attach event listeners to an element.
25
+ *
26
+ * @param el {Element} A DOM Element that the event listener should be attached to.
27
+ * @param eventType {String} An event type that should be listened to (e.g. 'click')
28
+ * @param handler {Function} A callback that should be triggered when the event occurs.
29
+ */
30
+ function attachSingleEventListener (el, eventType, handler) {
31
+ if (el) {
32
+ el.removeEventListener(eventType, handler)
33
+ el.addEventListener(eventType, handler)
34
+ }
35
+ }
36
+
37
+ /**
38
+ * Used to highlight the currently selected suggestion item in the suggestion popup.
39
+ *
40
+ * @param listIndex {Number} The index of the suggestion item that should be highlighted.
41
+ */
42
+ function highlightActiveElement (listIndex) {
43
+ const wrapper = document.querySelector('[data-suggestion-id="suggestion-popup"]')
44
+ if (wrapper) {
45
+ const nodeList = wrapper.childNodes
46
+ nodeList.forEach((node, idx) => idx === listIndex
47
+ ? node.classList.add('suggestion__list-item--is-active')
48
+ : node.classList.remove('suggestion__list-item--is-active'))
49
+ }
50
+ }
51
+
52
+ /**
53
+ * Used to generate content for the suggestion popup.
54
+ *
55
+ * @param items {Array} The suggestions that will be rendered insided the suggestion popup ([{ id: <id>, name: <name> }]).
56
+ *
57
+ * @return {HTMLUListElement}
58
+ */
59
+ function createFragment (items) {
60
+ const fragment = document.createElement('ul')
61
+ fragment.setAttribute('data-suggestion-id', 'suggestion-popup')
62
+ fragment.setAttribute('class', 'o-list suggestion__popup')
63
+
64
+ for (let i = 0; i < items.length; i++) {
65
+ const li = document.createElement('li')
66
+ li.setAttribute('class', 'o-list__item suggestion__list-item u-ph-0_5 u-pv-0_25')
67
+ li.setAttribute('data-suggestion-item', `${i}`)
68
+
69
+ const button = document.createElement('button')
70
+ button.setAttribute('class', 'btn--blank')
71
+ button.appendChild(document.createTextNode(items[i].name))
72
+
73
+ li.appendChild(button)
74
+ fragment.appendChild(li)
75
+ }
76
+ if (items.length === 0) {
77
+ const li = document.createElement('li')
78
+ li.appendChild(document.createTextNode('Keine passenden Platzhalter gefunden'))
79
+ fragment.appendChild(li)
80
+ }
81
+
82
+ return fragment
83
+ }
84
+
85
+ /**
86
+ * Used to create a suggestion plugin which triggers on certain characters (e.g. @) and inserts a Mention node into tiptap
87
+ * when a suggestion was selected.
88
+ *
89
+ * @param matcher {Object} The char that should trigger a suggestion and some config to determine possible suggestion trigger positions ({ char: <char>, allowSpaces: <boolean>, startOfLine: <boolean> }).
90
+ * @param suggestions {Array} All available suggestions ([{ id: <id>, name: <name> }]).
91
+ * @param vueEditorInstance {VueInstance} Used to focus the tiptap editor on insertion of a suggestion.
92
+ *
93
+ * @return {Mention} A mention extension that can be used by tiptap.
94
+ */
95
+ function createSuggestion ({ matcher, suggestions }, vueEditorInstance) {
96
+ /*
97
+ *Adding the whitespace to the name is a hack to make the suggestions work
98
+ *@see https://github.com/ueberdosis/tiptap/issues/932
99
+ *after the upgrade to tiptap 2 this workaround should be checked again
100
+ *and may hopefully be removed again
101
+ */
102
+ const fixedSuggestions = suggestions.map(el => ({ id: el.id, name: el.name + ' ' }))
103
+ let popup = null
104
+ let selectedElement = 0
105
+ let filteredSuggestions = fixedSuggestions
106
+ let insertSuggestion = (...args) => ({ args })
107
+ let suggestionRange = null
108
+
109
+ /**
110
+ * Triggers insertion of a suggestion.
111
+ */
112
+ function clickHandler (e) {
113
+ let el = e.target
114
+ if (el.tagName === 'BUTTON') {
115
+ el = el.parentElement
116
+ }
117
+ const itemIdx = parseInt(el.getAttribute('data-suggestion-item'))
118
+ selectSuggestion(fixedSuggestions[itemIdx])
119
+ vueEditorInstance.editor.focus()
120
+ }
121
+
122
+ /**
123
+ * Removes the suggestion popup from the DOM.
124
+ */
125
+ function destroyPopup () {
126
+ if (popup) {
127
+ popup[0].destroy()
128
+ popup = null
129
+ }
130
+ }
131
+
132
+ /**
133
+ * Increments the currently selected suggestion.
134
+ */
135
+ function downHandler () {
136
+ if (selectedElement < fixedSuggestions.length - 1) {
137
+ selectedElement += 1
138
+ highlightActiveElement(selectedElement)
139
+ }
140
+ }
141
+
142
+ /**
143
+ * Triggers insertion of a suggestion.
144
+ */
145
+ function enterHandler () {
146
+ selectSuggestion(fixedSuggestions[selectedElement])
147
+ }
148
+
149
+ /**
150
+ * Used to render a popup containing applicable suggestions.
151
+ *
152
+ * @param items {Array} The suggestions that will be available to the suggestion popup ([{ id: <id>, name: <name> }]).
153
+ * @param listIndex {Number} The index of the currently selected suggestion.
154
+ * @param node {TipTapVirtualNode} A virtual node used for placement of the suggestion popup.
155
+ */
156
+ function renderPopup (items, listIndex, node) {
157
+ const boundingClientRect = node.getBoundingClientRect()
158
+ const { x, y } = boundingClientRect
159
+ if (x === 0 && y === 0) {
160
+ return
161
+ }
162
+ if (popup) {
163
+ popup[0].setContent(createFragment(items))
164
+ } else {
165
+ popup = tippy('#mainContent', {
166
+ getReferenceClientRect: () => boundingClientRect,
167
+ appendTo: () => document.body,
168
+ interactive: true,
169
+ sticky: true, // Make sure position of tippy is updated when content changes
170
+ plugins: [sticky],
171
+ content: createFragment(items),
172
+ trigger: 'manual', // Manual
173
+ showOnCreate: true,
174
+ theme: 'dark',
175
+ placement: 'top-start',
176
+ inertia: true,
177
+ duration: [400, 200]
178
+ })
179
+ }
180
+ highlightActiveElement(listIndex)
181
+ attachEventListeners({ eventType: 'click', handler: clickHandler })
182
+ }
183
+
184
+ /**
185
+ * Used to insert a suggestion into tiptap.
186
+ *
187
+ * @param suggestion {Object} The suggestion that should be inserted ({ id: <id>, name: <name> })
188
+ */
189
+ function selectSuggestion (suggestion) {
190
+ insertSuggestion({
191
+ range: suggestionRange,
192
+ attrs: {
193
+ id: suggestion.id,
194
+ label: suggestion.name
195
+ }
196
+ })
197
+ }
198
+
199
+ /**
200
+ * Triggers insertion of a suggestion.
201
+ */
202
+ function spaceHandler () {
203
+ if (fixedSuggestions.length === 1) {
204
+ selectSuggestion(fixedSuggestions[0])
205
+ }
206
+ }
207
+
208
+ /**
209
+ * Decrements the currently selected suggestion.
210
+ */
211
+ function upHandler () {
212
+ if (selectedElement > 0) {
213
+ selectedElement -= 1
214
+ highlightActiveElement(selectedElement)
215
+ }
216
+ }
217
+
218
+ return new Mention({
219
+ items: fixedSuggestions,
220
+ matcher: matcher,
221
+ mentionClass: 'suggestion__node',
222
+ // Is called when a suggestion starts
223
+ onEnter: ({ items, range, command, virtualNode }) => {
224
+ filteredSuggestions = items
225
+ suggestionRange = range
226
+ selectedElement = 0
227
+ renderPopup(filteredSuggestions, selectedElement, virtualNode)
228
+ insertSuggestion = command
229
+ },
230
+ // Is called when a suggestion has changed
231
+ onChange: ({ items, range, virtualNode }) => {
232
+ filteredSuggestions = items
233
+ suggestionRange = range
234
+ selectedElement = 0
235
+ renderPopup(filteredSuggestions, selectedElement, virtualNode)
236
+ },
237
+ // Is called when a suggestion is cancelled
238
+ onExit: () => {
239
+ // Reset all saved values
240
+ filteredSuggestions = []
241
+ suggestionRange = null
242
+ selectedElement = 0
243
+ destroyPopup()
244
+ },
245
+ // Is called on every keyDown event while a suggestion is active
246
+ onKeyDown: ({ event }) => {
247
+ if (event.key === 'ArrowUp') {
248
+ upHandler()
249
+ return true
250
+ }
251
+ if (event.key === 'ArrowDown') {
252
+ downHandler()
253
+ return true
254
+ }
255
+ if (event.key === 'Enter') {
256
+ enterHandler()
257
+ return true
258
+ }
259
+
260
+ if (event.key === ' ') {
261
+ spaceHandler()
262
+ return true
263
+ }
264
+ return false
265
+ }
266
+ })
267
+ }
268
+
269
+ export { createSuggestion }
@@ -0,0 +1,32 @@
1
+ /**
2
+ * (c) 2010-present DEMOS E-Partizipation GmbH.
3
+ *
4
+ * This file is part of the package @demos-europe/demosplan-ui,
5
+ * for more information see the license file.
6
+ *
7
+ * All rights reserved
8
+ */
9
+
10
+ import { Mark } from 'tiptap'
11
+ import { toggleMark } from 'tiptap-commands'
12
+
13
+ export default class EditorCustomDelete extends Mark {
14
+ get name () {
15
+ return 'delete'
16
+ }
17
+
18
+ get schema () {
19
+ return {
20
+ parseDOM: [
21
+ {
22
+ tag: 'del'
23
+ }
24
+ ],
25
+ toDOM: () => ['del', { title: Translator.trans('text.deleted') }, 0]
26
+ }
27
+ }
28
+
29
+ commands ({ type }) {
30
+ return () => toggleMark(type)
31
+ }
32
+ }
@@ -0,0 +1,100 @@
1
+ /**
2
+ * (c) 2010-present DEMOS E-Partizipation GmbH.
3
+ *
4
+ * This file is part of the package @demos-europe/demosplan-ui,
5
+ * for more information see the license file.
6
+ *
7
+ * All rights reserved
8
+ */
9
+
10
+ import DpResizableImage from '../DpResizableImage'
11
+ import { Node } from 'tiptap'
12
+ import { nodeInputRule } from 'tiptap-commands'
13
+
14
+ /**
15
+ * Matches following attributes in Markdown-typed image: [, alt, src, title]
16
+ *
17
+ * Example:
18
+ * ![Lorem](image.jpg) -> [, "Lorem", "image.jpg"]
19
+ * ![](image.jpg "Ipsum") -> [, "", "image.jpg", "Ipsum"]
20
+ * ![Lorem](image.jpg "Ipsum") -> [, "Lorem", "image.jpg", "Ipsum"]
21
+ */
22
+ const IMAGE_INPUT_REGEX = /!\[(.+|:?)]\((\S+)(?:(?:\s+)["'](\S+)["'])?\)/
23
+
24
+ export default class Image extends Node {
25
+ get name () {
26
+ return 'image'
27
+ }
28
+
29
+ get schema () {
30
+ return {
31
+ inline: true,
32
+ attrs: {
33
+ src: {},
34
+ alt: {
35
+ default: null
36
+ },
37
+ title: {
38
+ default: null
39
+ },
40
+ width: {
41
+ default: null
42
+ },
43
+ height: {
44
+ default: null
45
+ }
46
+ },
47
+ group: 'inline',
48
+ draggable: true,
49
+ parseDOM: [
50
+ {
51
+ tag: 'img[src]',
52
+ getAttrs: dom => {
53
+ return ({
54
+ src: dom.getAttribute('src'),
55
+ title: dom.getAttribute('title'),
56
+ alt: dom.getAttribute('alt'),
57
+ width: dom.getAttribute('width'),
58
+ height: dom.getAttribute('height')
59
+ })
60
+ }
61
+ }
62
+ ],
63
+ toDOM: node => {
64
+ return ['img', { ...node.attrs, unselectable: 'on' }]
65
+ }
66
+ }
67
+ }
68
+
69
+ commands ({ type }) {
70
+ return {
71
+ insertImage: attrs => (state, dispatch) => {
72
+ const { selection } = state
73
+ const position = selection.$cursor ? selection.$cursor.pos : selection.$to.pos
74
+ const node = type.create(attrs)
75
+ const transaction = state.tr.insert(position, node)
76
+ dispatch(transaction)
77
+ }
78
+ }
79
+ }
80
+
81
+ inputRules ({ type }) {
82
+ return [
83
+ nodeInputRule(IMAGE_INPUT_REGEX, type, match => {
84
+ const [, alt, src, title, width, height] = match
85
+ return {
86
+ src,
87
+ alt,
88
+ title,
89
+ width,
90
+ height
91
+ }
92
+ })
93
+ ]
94
+ }
95
+
96
+ // Return a vue component
97
+ get view () {
98
+ return DpResizableImage
99
+ }
100
+ }