@oxygen-cms/ui 1.7.2 → 1.8.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/{.eslintrc.js → .eslintrc.json} +3 -3
- package/package.json +14 -4
- package/src/CrudApi.js +23 -9
- package/src/MediaApi.js +3 -3
- package/src/PagesApi.js +48 -0
- package/src/PartialsApi.js +30 -0
- package/src/components/AuthenticatedLayout.vue +3 -1
- package/src/components/CodeEditor.vue +1 -1
- package/src/components/GroupsChooser.vue +2 -2
- package/src/components/GroupsList.vue +2 -2
- package/src/components/PageActions.vue +28 -0
- package/src/components/PageEdit.vue +164 -0
- package/src/components/PageNestedPagination.vue +27 -0
- package/src/components/PageNestedRow.vue +52 -0
- package/src/components/PageStatusIcon.vue +33 -0
- package/src/components/PageTable.vue +156 -0
- package/src/components/PartialActions.vue +28 -0
- package/src/components/PartialList.vue +74 -0
- package/src/components/PartialStatusIcon.vue +29 -0
- package/src/components/PartialTable.vue +65 -0
- package/src/components/ResourceList.vue +132 -0
- package/src/components/UserManagement.vue +2 -2
- package/src/components/UserProfileForm.vue +1 -1
- package/src/components/UserProfilePage.vue +1 -1
- package/src/components/content/CommandsList.vue +108 -0
- package/src/components/content/ContentEditor.vue +489 -0
- package/src/components/content/GridCellNodeView.vue +82 -0
- package/src/components/content/GridRowNodeView.vue +53 -0
- package/src/components/content/HtmlNodeView.vue +89 -0
- package/src/components/content/MarkMenu.vue +116 -0
- package/src/components/content/MediaNodeView.vue +83 -0
- package/src/components/content/ObjectLinkNodeView.vue +181 -0
- package/src/components/content/PartialNodeView.vue +217 -0
- package/src/components/content/commands.js +72 -0
- package/src/components/content/suggestion.js +211 -0
- package/src/components/media/MediaChooseDirectory.vue +2 -2
- package/src/components/media/MediaDirectory.vue +1 -1
- package/src/components/media/MediaInsertModal.vue +11 -2
- package/src/components/media/MediaItem.vue +1 -1
- package/src/components/media/MediaItemPreview.vue +18 -2
- package/src/components/media/MediaList.vue +4 -5
- package/src/components/media/MediaUpload.vue +1 -1
- package/src/components/media/media.scss +1 -0
- package/src/components/pages/PageList.vue +65 -0
- package/src/components/users/CreateUserModal.vue +1 -1
- package/src/components/util.css +1 -1
- package/src/icons.js +33 -5
- package/src/main.js +4 -0
- package/src/modules/PagesPartials.js +74 -2
- package/src/styles/pages-table.scss +34 -0
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<NodeViewWrapper>
|
|
3
|
+
<div :class="{wrapper: true, editable: isEditable, selected: isEditable && selected}">
|
|
4
|
+
<template v-if="editing && isEditable">
|
|
5
|
+
<CodeEditor lang="html" height="20em" :value="node.attrs.code" @input="onInput"></CodeEditor>
|
|
6
|
+
<b-field class="toolbar">
|
|
7
|
+
<p class="control"><b-button size="is-small" icon-left="save" @click="save"></b-button></p>
|
|
8
|
+
<p class="control"><b-button size="is-small" icon-left="trash" @click="removeSelf"></b-button></p>
|
|
9
|
+
</b-field>
|
|
10
|
+
</template>
|
|
11
|
+
<template v-else>
|
|
12
|
+
<b-field v-if="isEditable" class="toolbar">
|
|
13
|
+
<p class="control"><b-button size="is-small" data-drag-handle icon-left="grip-vertical"></b-button></p>
|
|
14
|
+
<p class="control"><b-button size="is-small" icon-left="pencil-alt" @click="editing = true"></b-button></p>
|
|
15
|
+
<p class="control"><b-button size="is-small" icon-left="trash" @click="removeSelf"></b-button></p>
|
|
16
|
+
</b-field>
|
|
17
|
+
<div v-html="node.attrs.code"></div>
|
|
18
|
+
</template>
|
|
19
|
+
</div>
|
|
20
|
+
</NodeViewWrapper>
|
|
21
|
+
</template>
|
|
22
|
+
|
|
23
|
+
<script>
|
|
24
|
+
import { nodeViewProps, NodeViewWrapper } from '@tiptap/vue-2'
|
|
25
|
+
import CodeEditor from "../CodeEditor.vue";
|
|
26
|
+
export default {
|
|
27
|
+
name: "HtmlNodeView",
|
|
28
|
+
components: {CodeEditor, NodeViewWrapper},
|
|
29
|
+
props: nodeViewProps,
|
|
30
|
+
data() {
|
|
31
|
+
return {
|
|
32
|
+
editing: false,
|
|
33
|
+
latestValue: null
|
|
34
|
+
}
|
|
35
|
+
},
|
|
36
|
+
computed: {
|
|
37
|
+
isEditable() {
|
|
38
|
+
return this.editor && this.editor.isEditable;
|
|
39
|
+
}
|
|
40
|
+
},
|
|
41
|
+
watch: {
|
|
42
|
+
'node': 'syncLatestValue'
|
|
43
|
+
},
|
|
44
|
+
mounted() {
|
|
45
|
+
if(!this.node.attrs.code) {
|
|
46
|
+
this.editing = true;
|
|
47
|
+
}
|
|
48
|
+
this.syncLatestValue();
|
|
49
|
+
},
|
|
50
|
+
methods: {
|
|
51
|
+
onInput(value) {
|
|
52
|
+
this.latestValue = value;
|
|
53
|
+
},
|
|
54
|
+
save() {
|
|
55
|
+
console.log("save", this.latestValue);
|
|
56
|
+
this.updateAttributes({
|
|
57
|
+
code: this.latestValue
|
|
58
|
+
});
|
|
59
|
+
this.editing = false;
|
|
60
|
+
},
|
|
61
|
+
syncLatestValue() {
|
|
62
|
+
this.latestValue = this.node.attrs.code;
|
|
63
|
+
},
|
|
64
|
+
removeSelf() {
|
|
65
|
+
this.deleteNode();
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
</script>
|
|
70
|
+
|
|
71
|
+
<style scoped>
|
|
72
|
+
.wrapper {
|
|
73
|
+
position: relative;
|
|
74
|
+
}
|
|
75
|
+
.wrapper.editable {
|
|
76
|
+
min-height: 4rem;
|
|
77
|
+
outline: 1px dashed green;
|
|
78
|
+
}
|
|
79
|
+
.wrapper.selected {
|
|
80
|
+
outline: 2px solid green;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
.toolbar {
|
|
84
|
+
z-index: 9;
|
|
85
|
+
position: absolute;
|
|
86
|
+
top: 0.5rem;
|
|
87
|
+
right: 0.5rem;
|
|
88
|
+
}
|
|
89
|
+
</style>
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<bubble-menu ref="bubbleMenu" :editor="editor" :should-show="shouldShowMarksMenu">
|
|
3
|
+
<div class="bubble-menu">
|
|
4
|
+
<p class="control"><b-button size="is-small" :type="editor.isActive('bold') ? 'is-info' : 'is-light'" icon-left="bold" @click="toggleMark('bold')"></b-button></p>
|
|
5
|
+
<p class="control"><b-button size="is-small" :type="editor.isActive('italic') ? 'is-info' : 'is-light'" icon-left="italic" @click="toggleMark('italic')"></b-button></p>
|
|
6
|
+
<p class="control"><b-button size="is-small" :type="editor.isActive('underline') ? 'is-info' : 'is-light'" icon-left="underline" @click="toggleMark('underline')"></b-button></p>
|
|
7
|
+
<p class="control"><b-button size="is-small" :type="editor.isActive('strike') ? 'is-info' : 'is-light'" icon-left="strikethrough" @click="toggleMark('strike')"></b-button></p>
|
|
8
|
+
<p class="control"><b-button size="is-small" :type="linkPanelActive ? 'is-info' : 'is-light'" icon-left="link" @click="toggleMark('link')"></b-button>
|
|
9
|
+
</p>
|
|
10
|
+
<p class="control"><b-button size="is-small" type="is-light" icon-left="remove-format" @click="editor.chain().focus().unsetAllMarks().run()"></b-button></p>
|
|
11
|
+
</div>
|
|
12
|
+
<div v-show="linkPanelActive" type="is-light">
|
|
13
|
+
<div class="modal-card" style="z-index: 10000;">
|
|
14
|
+
<header class="modal-card-head">
|
|
15
|
+
<p class="modal-card-title">Edit Link</p>
|
|
16
|
+
<b-button icon-left="external-link-alt" @click="openLink">Open link</b-button>
|
|
17
|
+
</header>
|
|
18
|
+
<div class="modal-card-body">
|
|
19
|
+
<b-field label="URL" label-position="on-border">
|
|
20
|
+
<b-input :value="linkUrl" @input="v => onLinkUpdate({linkUrl: v, linkOpenInNewWindow: linkOpenInNewWindow})"></b-input>
|
|
21
|
+
</b-field>
|
|
22
|
+
<b-switch :value="linkOpenInNewWindow" @input="v => onLinkUpdate({linkOpenInNewWindow: v, linkUrl: linkUrl})">Open in new window</b-switch>
|
|
23
|
+
<br>
|
|
24
|
+
</div>
|
|
25
|
+
</div>
|
|
26
|
+
</div>
|
|
27
|
+
</bubble-menu>
|
|
28
|
+
</template>
|
|
29
|
+
|
|
30
|
+
<script>
|
|
31
|
+
import {debounce} from "lodash";
|
|
32
|
+
import {BubbleMenu, isTextSelection} from "@tiptap/vue-2";
|
|
33
|
+
|
|
34
|
+
export default {
|
|
35
|
+
name: "MarkMenu",
|
|
36
|
+
components: {BubbleMenu},
|
|
37
|
+
props: {
|
|
38
|
+
editor: {type: Object, required: true}
|
|
39
|
+
},
|
|
40
|
+
data() {
|
|
41
|
+
return {
|
|
42
|
+
linkPanelActive: false,
|
|
43
|
+
linkUrl: '',
|
|
44
|
+
linkOpenInNewWindow: false
|
|
45
|
+
}
|
|
46
|
+
},
|
|
47
|
+
methods: {
|
|
48
|
+
openLink() {
|
|
49
|
+
window.open(this.linkUrl, '_blank').focus();
|
|
50
|
+
},
|
|
51
|
+
onLinkUpdate: debounce(function ({ linkUrl, linkOpenInNewWindow }) {
|
|
52
|
+
this.linkUrl = linkUrl;
|
|
53
|
+
this.linkOpenInNewWindow = linkOpenInNewWindow;
|
|
54
|
+
let props = { href: linkUrl };
|
|
55
|
+
if(linkOpenInNewWindow) {
|
|
56
|
+
props.target = '_blank';
|
|
57
|
+
}
|
|
58
|
+
console.log('setLink', props);
|
|
59
|
+
let oldSelection = this.editor.state.selection.getBookmark();
|
|
60
|
+
this.editor.chain().extendMarkRange('link').setLink(props).run();
|
|
61
|
+
this.editor.view.dispatch(this.editor.state.tr.setSelection(oldSelection.resolve(this.editor.state.doc)));
|
|
62
|
+
}, 1000),
|
|
63
|
+
toggleMark(ty) {
|
|
64
|
+
let oldSelection = this.editor.state.selection.getBookmark();
|
|
65
|
+
let cmd = this.editor.chain().focus()
|
|
66
|
+
if(this.editor.isActive(ty))
|
|
67
|
+
{
|
|
68
|
+
cmd = cmd.extendMarkRange(ty);
|
|
69
|
+
}
|
|
70
|
+
let uppercaseTy = ty.charAt(0).toUpperCase() + ty.slice(1);;
|
|
71
|
+
let toggle = cmd['toggle' + uppercaseTy];
|
|
72
|
+
toggle().run();
|
|
73
|
+
this.editor.view.dispatch(this.editor.state.tr.setSelection(oldSelection.resolve(this.editor.state.doc)));
|
|
74
|
+
},
|
|
75
|
+
shouldShowMarksMenu({view, state, from, to}) {
|
|
76
|
+
const { doc, selection } = state
|
|
77
|
+
const { empty } = selection
|
|
78
|
+
|
|
79
|
+
let isNodeSelection = selection.node;
|
|
80
|
+
|
|
81
|
+
// Sometime check for `empty` is not enough.
|
|
82
|
+
// Doubleclick an empty paragraph returns a node size of 2.
|
|
83
|
+
// So we check also for an empty text size.
|
|
84
|
+
const isEmptyTextBlock = !doc.textBetween(from, to).length
|
|
85
|
+
&& isTextSelection(state.selection)
|
|
86
|
+
|
|
87
|
+
// When clicking on an element inside the bubble menu the editor "blur" event
|
|
88
|
+
// is called and the bubble menu item is focussed. In this case we should
|
|
89
|
+
// consider the menu as part of the editor and keep showing the menu
|
|
90
|
+
const isChildOfMenu = this.$refs.bubbleMenu.$el.contains(document.activeElement)
|
|
91
|
+
|
|
92
|
+
const hasEditorFocus = view.hasFocus() || isChildOfMenu
|
|
93
|
+
|
|
94
|
+
const isMarkActive = this.editor.isActive('link') || this.editor.isActive('bold') || this.editor.isActive('italic') || this.editor.isActive('underline') || this.editor.isActive('strike');
|
|
95
|
+
|
|
96
|
+
if(this.editor.isActive('link'))
|
|
97
|
+
{
|
|
98
|
+
let attrs = this.editor.getAttributes('link');
|
|
99
|
+
this.linkUrl = attrs.href;
|
|
100
|
+
this.linkOpenInNewWindow = attrs.target === '_blank';
|
|
101
|
+
this.linkPanelActive = true;
|
|
102
|
+
} else {
|
|
103
|
+
this.linkPanelActive = false;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
return hasEditorFocus && ((!empty && !isEmptyTextBlock && !isNodeSelection) || isMarkActive) && this.editor.isEditable;
|
|
107
|
+
},
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
</script>
|
|
111
|
+
|
|
112
|
+
<style scoped>
|
|
113
|
+
.bubble-menu {
|
|
114
|
+
display: flex;
|
|
115
|
+
}
|
|
116
|
+
</style>
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<NodeViewWrapper :class="{selected: isEditable && selected}">
|
|
3
|
+
<MediaInsertModal :multiselect-allowed="false" :active="mediaSelectActive" @select="selectItem" @close="removeSelf"></MediaInsertModal>
|
|
4
|
+
<MediaItemPreview v-if="item" :item="item">
|
|
5
|
+
<b-field v-if="isEditable && selected">
|
|
6
|
+
<p class="control">
|
|
7
|
+
<b-button icon-left="grip-vertical" size="is-small" data-drag-handle></b-button>
|
|
8
|
+
</p>
|
|
9
|
+
<p class="control">
|
|
10
|
+
<b-button size="is-small" icon-left="file-image" @click="mediaSelectActive=true"></b-button>
|
|
11
|
+
</p>
|
|
12
|
+
<p class="control">
|
|
13
|
+
<b-button size="is-small" icon-left="trash" @click="removeSelf"></b-button>
|
|
14
|
+
</p>
|
|
15
|
+
</b-field>
|
|
16
|
+
</MediaItemPreview>
|
|
17
|
+
<em v-else>No media item selected</em>
|
|
18
|
+
</NodeViewWrapper>
|
|
19
|
+
</template>
|
|
20
|
+
|
|
21
|
+
<script>
|
|
22
|
+
import { nodeViewProps, NodeViewWrapper } from '@tiptap/vue-2'
|
|
23
|
+
import MediaInsertModal from "../media/MediaInsertModal.vue";
|
|
24
|
+
import MediaItemPreview from "../media/MediaItemPreview.vue";
|
|
25
|
+
import MediaApi from "../../MediaApi.js";
|
|
26
|
+
|
|
27
|
+
export default {
|
|
28
|
+
name: "MediaNodeView",
|
|
29
|
+
components: {MediaItemPreview, MediaInsertModal, NodeViewWrapper },
|
|
30
|
+
props: nodeViewProps,
|
|
31
|
+
data() {
|
|
32
|
+
return {
|
|
33
|
+
mediaApi: new MediaApi(),
|
|
34
|
+
mediaSelectActive: false,
|
|
35
|
+
item: null
|
|
36
|
+
}
|
|
37
|
+
},
|
|
38
|
+
computed: {
|
|
39
|
+
isEditable() {
|
|
40
|
+
return this.editor.isEditable;
|
|
41
|
+
}
|
|
42
|
+
},
|
|
43
|
+
watch: {
|
|
44
|
+
'node.attrs.id': 'fetchMediaItem'
|
|
45
|
+
},
|
|
46
|
+
mounted() {
|
|
47
|
+
if(!this.node.attrs.id) {
|
|
48
|
+
this.mediaSelectActive = true;
|
|
49
|
+
}
|
|
50
|
+
this.fetchMediaItem()
|
|
51
|
+
},
|
|
52
|
+
methods: {
|
|
53
|
+
async fetchMediaItem() {
|
|
54
|
+
if(!this.node.attrs.id) {
|
|
55
|
+
this.item = null;
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
58
|
+
if(this.item !== null && this.item.id === this.node.attrs.id) { return; }
|
|
59
|
+
console.log('fetching media item');
|
|
60
|
+
this.item = (await this.mediaApi.get(this.node.attrs.id)).item;
|
|
61
|
+
},
|
|
62
|
+
selectItem(items) {
|
|
63
|
+
console.assert(items.length === 1);
|
|
64
|
+
this.updateAttributes({
|
|
65
|
+
id: items[0].id
|
|
66
|
+
});
|
|
67
|
+
this.mediaSelectActive = false;
|
|
68
|
+
this.editor.chain().focus().run();
|
|
69
|
+
},
|
|
70
|
+
removeSelf() {
|
|
71
|
+
this.deleteNode()
|
|
72
|
+
this.editor.chain().focus().insertContent({type: 'paragraph', text: ''}).run();
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
</script>
|
|
77
|
+
|
|
78
|
+
<style scoped lang="scss">
|
|
79
|
+
@import "../../styles/_variables.scss";
|
|
80
|
+
.selected {
|
|
81
|
+
outline: 2px solid $danger;
|
|
82
|
+
}
|
|
83
|
+
</style>
|
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<NodeViewWrapper class="object-link">
|
|
3
|
+
<b-skeleton v-if="!resolvedHref"></b-skeleton>
|
|
4
|
+
<a v-else-if="!isEditable" :href="getApiHost() + resolvedHref">{{ node.attrs.content ? node.attrs.content : defaultTitle }}</a>
|
|
5
|
+
<a v-else href="#" @click="onLinkClicked">{{ node.attrs.content ? node.attrs.content : defaultTitle }}</a>
|
|
6
|
+
<b-modal :active="node.attrs.type === 'page' && !node.attrs.id" has-modal-card aria-modal width="80%" class="choose-page" @close="deleteNode">
|
|
7
|
+
<div class="modal-card">
|
|
8
|
+
<div class="modal-card-head">
|
|
9
|
+
<p class="modal-card-title">Choose a page</p>
|
|
10
|
+
</div>
|
|
11
|
+
<div class="modal-card-body is-flex">
|
|
12
|
+
<PageList>
|
|
13
|
+
<template #actions="props">
|
|
14
|
+
<b-button type="is-primary" @click="selectedPage(props.row)">Select</b-button>
|
|
15
|
+
</template>
|
|
16
|
+
</PageList>
|
|
17
|
+
</div>
|
|
18
|
+
</div>
|
|
19
|
+
</b-modal>
|
|
20
|
+
<MediaInsertModal :active="node.attrs.type === 'media' && !node.attrs.id" :multiselect-allowed="false" close-verb="Remove link" :action-verb="shouldReturnToEditModal ? 'Choose' : 'Insert link'" @close="deleteNode" @select="selectedMedia"></MediaInsertModal>
|
|
21
|
+
<b-modal :active="editLinkModalActive" has-modal-card aria-modal @update:active="v => editLinkModalActive = v">
|
|
22
|
+
<div class="modal-card">
|
|
23
|
+
<div class="modal-card-head is-flex">
|
|
24
|
+
<div>
|
|
25
|
+
<p class="modal-card-title">Edit Link</p>
|
|
26
|
+
</div>
|
|
27
|
+
<div class="is-flex-grow-1"></div>
|
|
28
|
+
<b-button icon-left="trash" @click="deleteNode">Remove</b-button>
|
|
29
|
+
</div>
|
|
30
|
+
<div class="modal-card-body">
|
|
31
|
+
<b-field label="Title">
|
|
32
|
+
<b-input :value="node.attrs.content" :placeholder="defaultTitle" @input="v => updateAttributes({ content: v })"></b-input>
|
|
33
|
+
</b-field>
|
|
34
|
+
<div class="label">Target</div>
|
|
35
|
+
<div class="card">
|
|
36
|
+
<div class="card-content">
|
|
37
|
+
<div v-if="node.attrs.type === 'media' && resolvedObject" class="media is-align-items-center">
|
|
38
|
+
<div class="media-content">
|
|
39
|
+
<p class="title is-4">{{ resolvedObject.name }}</p>
|
|
40
|
+
<p class="subtitle is-6">{{ resolvedObject.fullPath }}</p>
|
|
41
|
+
</div>
|
|
42
|
+
<div class="media-right buttons">
|
|
43
|
+
<b-button icon-left="images" @click="clearId(true)">Change...</b-button>
|
|
44
|
+
</div>
|
|
45
|
+
</div>
|
|
46
|
+
<div v-if="node.attrs.type === 'page' && resolvedObject" class="media">
|
|
47
|
+
<div class="media-icon">
|
|
48
|
+
<b-icon icon="file-alt" size="is-large" class="mr-4 huge-icon"></b-icon>
|
|
49
|
+
</div>
|
|
50
|
+
<div class="media-content">
|
|
51
|
+
<p class="title is-4">{{ resolvedObject.title }}</p>
|
|
52
|
+
<p class="subtitle is-6">{{ resolvedObject.slug }}</p>
|
|
53
|
+
</div>
|
|
54
|
+
<div class="media-right buttons">
|
|
55
|
+
<b-button icon-left="images" @click="clearId(true)">Change...</b-button>
|
|
56
|
+
</div>
|
|
57
|
+
</div>
|
|
58
|
+
</div>
|
|
59
|
+
<div v-if="node.attrs.type === 'media' && resolvedObject" class="card-image">
|
|
60
|
+
<MediaItemPreview :item="resolvedObject" />
|
|
61
|
+
</div>
|
|
62
|
+
</div>
|
|
63
|
+
<!-- <b-button @click="clearId">Select different object...</b-button>-->
|
|
64
|
+
</div>
|
|
65
|
+
</div>
|
|
66
|
+
</b-modal>
|
|
67
|
+
</NodeViewWrapper>
|
|
68
|
+
</template>
|
|
69
|
+
|
|
70
|
+
<script>
|
|
71
|
+
import { nodeViewProps, NodeViewWrapper } from '@tiptap/vue-2'
|
|
72
|
+
import {getApiRoot} from "../../CrudApi.js";
|
|
73
|
+
import {FetchBuilder, getApiHost} from "../../api.js";
|
|
74
|
+
import MediaInsertModal from "../media/MediaInsertModal.vue";
|
|
75
|
+
import MediaItemPreview from "../media/MediaItemPreview.vue";
|
|
76
|
+
import {getDirectoryPathString} from "../../MediaDirectoryApi.js";
|
|
77
|
+
import PageList from "../pages/PageList.vue";
|
|
78
|
+
|
|
79
|
+
export default {
|
|
80
|
+
name: "ObjectLinkNodeView",
|
|
81
|
+
components: {MediaInsertModal, NodeViewWrapper, MediaItemPreview, PageList },
|
|
82
|
+
props: nodeViewProps,
|
|
83
|
+
data() {
|
|
84
|
+
return {
|
|
85
|
+
getDirectoryPathString: getDirectoryPathString,
|
|
86
|
+
editLinkModalActive: false,
|
|
87
|
+
shouldReturnToEditModal: false,
|
|
88
|
+
resolvedHref: null,
|
|
89
|
+
resolvedObject: null,
|
|
90
|
+
defaultTitle: '',
|
|
91
|
+
getApiHost
|
|
92
|
+
}
|
|
93
|
+
},
|
|
94
|
+
computed: {
|
|
95
|
+
isEditable() {
|
|
96
|
+
return this.editor.isEditable;
|
|
97
|
+
}
|
|
98
|
+
},
|
|
99
|
+
watch: {
|
|
100
|
+
'node.attrs.id': 'resolveUrl',
|
|
101
|
+
'node.attrs.type': 'resolveUrl'
|
|
102
|
+
},
|
|
103
|
+
mounted() {
|
|
104
|
+
this.resolveUrl()
|
|
105
|
+
},
|
|
106
|
+
methods: {
|
|
107
|
+
async resolveUrl() {
|
|
108
|
+
if(!this.node.attrs.id) {
|
|
109
|
+
this.resolvedHref = null;
|
|
110
|
+
return;
|
|
111
|
+
}
|
|
112
|
+
let result = await this.resolveLink({ type: this.node.attrs.type, id: this.node.attrs.id });
|
|
113
|
+
console.log(result);
|
|
114
|
+
// trim the leading '/' from the URL
|
|
115
|
+
this.resolvedHref = result.url.substring(1);
|
|
116
|
+
this.defaultTitle = result.title;
|
|
117
|
+
this.resolvedObject = result.object;
|
|
118
|
+
},
|
|
119
|
+
async resolveLink(params) {
|
|
120
|
+
return await (FetchBuilder.default(this.$buefy, 'get').withQueryParams(params).fetch(getApiRoot() + 'object-link/resolve'));
|
|
121
|
+
},
|
|
122
|
+
async selectedItem(info) {
|
|
123
|
+
let resolved = await this.resolveLink(info);
|
|
124
|
+
if(this.shouldReturnToEditModal) {
|
|
125
|
+
this.editLinkModalActive = true;
|
|
126
|
+
}
|
|
127
|
+
this.updateAttributes({
|
|
128
|
+
...info,
|
|
129
|
+
content: resolved.title
|
|
130
|
+
});
|
|
131
|
+
},
|
|
132
|
+
async selectedPage(page) {
|
|
133
|
+
return await this.selectedItem({ type: 'page', id: page.id });
|
|
134
|
+
},
|
|
135
|
+
async selectedMedia(items) {
|
|
136
|
+
if(items.length !== 1) {
|
|
137
|
+
throw new Error('expected exactly 1 item');
|
|
138
|
+
}
|
|
139
|
+
return await this.selectedItem({ type: 'media', id: items[0].id});
|
|
140
|
+
},
|
|
141
|
+
clearId(shouldReturnToEditModal) {
|
|
142
|
+
this.shouldReturnToEditModal = shouldReturnToEditModal;
|
|
143
|
+
this.editLinkModalActive = false;
|
|
144
|
+
this.updateAttributes({
|
|
145
|
+
id: null
|
|
146
|
+
});
|
|
147
|
+
},
|
|
148
|
+
onLinkClicked() {
|
|
149
|
+
this.editLinkModalActive = true
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
</script>
|
|
154
|
+
|
|
155
|
+
<style scoped>
|
|
156
|
+
.object-link {
|
|
157
|
+
display: inline;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
.media-icon ::v-deep .huge-icon {
|
|
161
|
+
width: 6rem;
|
|
162
|
+
height: 6rem;
|
|
163
|
+
font-size: 300%;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
.choose-page ::v-deep .modal-card {
|
|
167
|
+
width: 100%;
|
|
168
|
+
height: 100%;
|
|
169
|
+
max-width: 1600px;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
.choose-page ::v-deep .animation-content {
|
|
173
|
+
max-height: calc(100vh - 10rem);
|
|
174
|
+
height: 100%;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
::v-deep .b-skeleton {
|
|
178
|
+
width: 10rem;
|
|
179
|
+
margin: 0 0.5rem;
|
|
180
|
+
}
|
|
181
|
+
</style>
|