mirror of https://github.com/halo-dev/halo-admin
refactor: journal editing using the markdown editor (#506)
Signed-off-by: Ryan Wang <i@ryanc.cc>pull/507/head
parent
7e1e58387e
commit
b50e1abe98
|
@ -23,7 +23,7 @@
|
||||||
"@codemirror/basic-setup": "^0.19.1",
|
"@codemirror/basic-setup": "^0.19.1",
|
||||||
"@codemirror/lang-html": "^0.19.4",
|
"@codemirror/lang-html": "^0.19.4",
|
||||||
"@codemirror/lang-java": "^0.19.1",
|
"@codemirror/lang-java": "^0.19.1",
|
||||||
"@halo-dev/admin-api": "^1.0.0-alpha.50",
|
"@halo-dev/admin-api": "^1.0.0-alpha.51",
|
||||||
"@halo-dev/editor": "^3.0.0-alpha.2",
|
"@halo-dev/editor": "^3.0.0-alpha.2",
|
||||||
"ant-design-vue": "^1.7.8",
|
"ant-design-vue": "^1.7.8",
|
||||||
"dayjs": "^1.10.7",
|
"dayjs": "^1.10.7",
|
||||||
|
|
|
@ -6,7 +6,7 @@ specifiers:
|
||||||
'@codemirror/basic-setup': ^0.19.1
|
'@codemirror/basic-setup': ^0.19.1
|
||||||
'@codemirror/lang-html': ^0.19.4
|
'@codemirror/lang-html': ^0.19.4
|
||||||
'@codemirror/lang-java': ^0.19.1
|
'@codemirror/lang-java': ^0.19.1
|
||||||
'@halo-dev/admin-api': ^1.0.0-alpha.50
|
'@halo-dev/admin-api': ^1.0.0-alpha.51
|
||||||
'@halo-dev/editor': ^3.0.0-alpha.2
|
'@halo-dev/editor': ^3.0.0-alpha.2
|
||||||
'@vue/cli-plugin-babel': ~5.0.1
|
'@vue/cli-plugin-babel': ~5.0.1
|
||||||
'@vue/cli-plugin-eslint': ~5.0.1
|
'@vue/cli-plugin-eslint': ~5.0.1
|
||||||
|
@ -52,7 +52,7 @@ dependencies:
|
||||||
'@codemirror/basic-setup': 0.19.1
|
'@codemirror/basic-setup': 0.19.1
|
||||||
'@codemirror/lang-html': 0.19.4
|
'@codemirror/lang-html': 0.19.4
|
||||||
'@codemirror/lang-java': 0.19.1
|
'@codemirror/lang-java': 0.19.1
|
||||||
'@halo-dev/admin-api': 1.0.0-alpha.50
|
'@halo-dev/admin-api': 1.0.0-alpha.51
|
||||||
'@halo-dev/editor': 3.0.0-alpha.2
|
'@halo-dev/editor': 3.0.0-alpha.2
|
||||||
ant-design-vue: 1.7.8_9065e7474e033a8e4b95615fc8e6c36c
|
ant-design-vue: 1.7.8_9065e7474e033a8e4b95615fc8e6c36c
|
||||||
dayjs: 1.10.7
|
dayjs: 1.10.7
|
||||||
|
@ -1577,8 +1577,8 @@ packages:
|
||||||
- supports-color
|
- supports-color
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
/@halo-dev/admin-api/1.0.0-alpha.50:
|
/@halo-dev/admin-api/1.0.0-alpha.51:
|
||||||
resolution: {integrity: sha512-/kfkBPRqnhMRCMhKWMWz+rboUAxGCM5Tcv13BaBKlG04wEY7ewgrXrFMvxfYd0luIik+knluwKj1OwWix+2ZhA==}
|
resolution: {integrity: sha512-xREMnlN9GNTiw4Y2qJvpRu3SgmqhJ9l2e4ctfJ3vzG4AJacttHku5hKUxdOFE95sL7UFXEcE3MlNQqVJkHPksw==}
|
||||||
engines: {node: '>=12'}
|
engines: {node: '>=12'}
|
||||||
dependencies:
|
dependencies:
|
||||||
'@halo-dev/rest-api-client': 1.0.0-alpha.50
|
'@halo-dev/rest-api-client': 1.0.0-alpha.50
|
||||||
|
|
|
@ -4,7 +4,8 @@
|
||||||
ref="editor"
|
ref="editor"
|
||||||
v-model="originalContentData"
|
v-model="originalContentData"
|
||||||
:boxShadow="false"
|
:boxShadow="false"
|
||||||
:toolbars="markdownEditorToolbars"
|
:subfield="subfield"
|
||||||
|
:toolbars="toolbars"
|
||||||
:uploadRequest="handleAttachmentUpload"
|
:uploadRequest="handleAttachmentUpload"
|
||||||
autofocus
|
autofocus
|
||||||
@change="handleChange"
|
@change="handleChange"
|
||||||
|
@ -19,7 +20,7 @@
|
||||||
import haloEditor from '@halo-dev/editor'
|
import haloEditor from '@halo-dev/editor'
|
||||||
import '@halo-dev/editor/dist/lib/style.css'
|
import '@halo-dev/editor/dist/lib/style.css'
|
||||||
import apiClient from '@/utils/api-client'
|
import apiClient from '@/utils/api-client'
|
||||||
import { markdownEditorToolbars } from '@/core/constant'
|
import { editorToolbars } from '@/core/constant'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'MarkdownEditor',
|
name: 'MarkdownEditor',
|
||||||
|
@ -31,11 +32,20 @@ export default {
|
||||||
type: String,
|
type: String,
|
||||||
required: false,
|
required: false,
|
||||||
default: ''
|
default: ''
|
||||||
|
},
|
||||||
|
toolbars: {
|
||||||
|
type: Object,
|
||||||
|
default: () => {
|
||||||
|
return editorToolbars
|
||||||
|
}
|
||||||
|
},
|
||||||
|
subfield: {
|
||||||
|
type: Boolean,
|
||||||
|
default: true
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
markdownEditorToolbars,
|
|
||||||
attachmentSelectVisible: false
|
attachmentSelectVisible: false
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
export const markdownEditorToolbars = {
|
export const editorToolbars = {
|
||||||
bold: true,
|
bold: true,
|
||||||
italic: true,
|
italic: true,
|
||||||
header: true,
|
header: true,
|
||||||
|
@ -24,6 +24,28 @@ export const markdownEditorToolbars = {
|
||||||
preview: true
|
preview: true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const simpleEditorToolbars = {
|
||||||
|
bold: true,
|
||||||
|
italic: true,
|
||||||
|
header: true,
|
||||||
|
underline: true,
|
||||||
|
strikethrough: true,
|
||||||
|
superscript: true,
|
||||||
|
subscript: true,
|
||||||
|
quote: true,
|
||||||
|
ol: true,
|
||||||
|
ul: true,
|
||||||
|
link: true,
|
||||||
|
imagelink: true,
|
||||||
|
code: true,
|
||||||
|
table: true,
|
||||||
|
undo: true,
|
||||||
|
redo: true,
|
||||||
|
subfield: true,
|
||||||
|
htmlcode: true,
|
||||||
|
preview: true
|
||||||
|
}
|
||||||
|
|
||||||
export const actionLogTypes = {
|
export const actionLogTypes = {
|
||||||
BLOG_INITIALIZED: {
|
BLOG_INITIALIZED: {
|
||||||
value: 0,
|
value: 0,
|
||||||
|
|
|
@ -1,9 +1,11 @@
|
||||||
<template>
|
<template>
|
||||||
<a-card :bodyStyle="{ padding: '16px' }" :bordered="false">
|
<a-card :bodyStyle="{ padding: '16px' }" :bordered="false">
|
||||||
<template slot="title">
|
<template #title>
|
||||||
速记
|
速记
|
||||||
<a-tooltip slot="action" title="内容将保存到页面/所有页面/日志页面">
|
<a-tooltip title="内容将保存到页面/所有页面/日志页面">
|
||||||
<a-icon class="cursor-pointer" type="info-circle-o" />
|
<router-link :to="{ name: 'JournalList' }" style="color: inherit">
|
||||||
|
<a-icon class="cursor-pointer hover:text-blue-400 transition-all" type="info-circle-o" />
|
||||||
|
</router-link>
|
||||||
</a-tooltip>
|
</a-tooltip>
|
||||||
</template>
|
</template>
|
||||||
<a-form-model ref="journalForm" :model="form.model" :rules="form.rules" layout="vertical">
|
<a-form-model ref="journalForm" :model="form.model" :rules="form.rules" layout="vertical">
|
||||||
|
|
|
@ -38,44 +38,50 @@
|
||||||
<div class="mt-4">
|
<div class="mt-4">
|
||||||
<a-empty v-if="!list.loading && list.data.length === 0" />
|
<a-empty v-if="!list.loading && list.data.length === 0" />
|
||||||
<a-list v-else :dataSource="list.data" :loading="list.loading" :pagination="false" itemLayout="vertical">
|
<a-list v-else :dataSource="list.data" :loading="list.loading" :pagination="false" itemLayout="vertical">
|
||||||
<a-list-item :key="index" slot="renderItem" slot-scope="item, index">
|
<template #renderItem="item, index">
|
||||||
<template slot="actions">
|
<a-list-item :key="index">
|
||||||
<a-button class="!p-0" type="link">
|
<template #actions>
|
||||||
<a-icon type="like-o" />
|
<a-button class="!p-0" type="link">
|
||||||
{{ item.likes }}
|
<a-icon type="like-o" />
|
||||||
</a-button>
|
{{ item.likes }}
|
||||||
<a-button class="!p-0" type="link" @click="handleOpenJournalCommentsDrawer(item)">
|
</a-button>
|
||||||
<a-icon type="message" />
|
<a-button class="!p-0" type="link" @click="handleOpenJournalCommentsDrawer(item)">
|
||||||
{{ item.commentCount }}
|
<a-icon type="message" />
|
||||||
</a-button>
|
{{ item.commentCount }}
|
||||||
<a-button v-if="item.type === 'INTIMATE'" class="!p-0" disabled type="link">
|
</a-button>
|
||||||
<a-icon type="lock" />
|
<a-button v-if="item.type === 'INTIMATE'" class="!p-0" disabled type="link">
|
||||||
</a-button>
|
<a-icon type="lock" />
|
||||||
<a-button v-else class="!p-0" type="link">
|
</a-button>
|
||||||
<a-icon type="unlock" />
|
<a-button v-else class="!p-0" type="link">
|
||||||
</a-button>
|
<a-icon type="unlock" />
|
||||||
</template>
|
</a-button>
|
||||||
<template slot="extra">
|
|
||||||
<a-button class="!p-0" type="link" @click="handleOpenEditModal(item)">编辑</a-button>
|
|
||||||
<a-divider type="vertical" />
|
|
||||||
<a-popconfirm
|
|
||||||
cancelText="取消"
|
|
||||||
okText="确定"
|
|
||||||
title="你确定要删除这条日志?"
|
|
||||||
@confirm="handleDelete(item.id)"
|
|
||||||
>
|
|
||||||
<a-button class="!p-0" type="link">删除</a-button>
|
|
||||||
</a-popconfirm>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<a-list-item-meta>
|
|
||||||
<template slot="description">
|
|
||||||
<div class="journal-list-content" v-html="item.content"></div>
|
|
||||||
</template>
|
</template>
|
||||||
<span slot="title">{{ item.createTime | moment }}</span>
|
<template #extra>
|
||||||
<a-avatar slot="avatar" :src="user.avatar" size="large" />
|
<a-button class="!p-0" type="link" @click="handleOpenEditModal(item)">编辑</a-button>
|
||||||
</a-list-item-meta>
|
<a-divider type="vertical" />
|
||||||
</a-list-item>
|
<a-popconfirm
|
||||||
|
cancelText="取消"
|
||||||
|
okText="确定"
|
||||||
|
title="你确定要删除这条日志?"
|
||||||
|
@confirm="handleDelete(item.id)"
|
||||||
|
>
|
||||||
|
<a-button class="!p-0" type="link">删除</a-button>
|
||||||
|
</a-popconfirm>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<a-list-item-meta>
|
||||||
|
<template #description>
|
||||||
|
<div class="journal-list-content" v-html="item.content"></div>
|
||||||
|
</template>
|
||||||
|
<template #title>
|
||||||
|
<span>{{ item.createTime | moment }}</span>
|
||||||
|
</template>
|
||||||
|
<template #avatar>
|
||||||
|
<a-avatar :src="user.avatar" size="large" />
|
||||||
|
</template>
|
||||||
|
</a-list-item-meta>
|
||||||
|
</a-list-item>
|
||||||
|
</template>
|
||||||
<div class="page-wrapper">
|
<div class="page-wrapper">
|
||||||
<a-pagination
|
<a-pagination
|
||||||
:current="pagination.page"
|
:current="pagination.page"
|
||||||
|
@ -104,8 +110,9 @@
|
||||||
@click="optionModal.visible = true"
|
@click="optionModal.visible = true"
|
||||||
></a-button>
|
></a-button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<a-modal v-model="optionModal.visible" :afterClose="() => (optionModal.visible = false)" title="页面设置">
|
<a-modal v-model="optionModal.visible" :afterClose="() => (optionModal.visible = false)" title="页面设置">
|
||||||
<template slot="footer">
|
<template #footer>
|
||||||
<a-button key="submit" type="primary" @click="handleSaveOptions()">保存</a-button>
|
<a-button key="submit" type="primary" @click="handleSaveOptions()">保存</a-button>
|
||||||
</template>
|
</template>
|
||||||
<a-form layout="vertical">
|
<a-form layout="vertical">
|
||||||
|
@ -119,15 +126,8 @@
|
||||||
</a-modal>
|
</a-modal>
|
||||||
|
|
||||||
<!-- 编辑日志弹窗 -->
|
<!-- 编辑日志弹窗 -->
|
||||||
<a-modal v-model="form.visible">
|
<a-modal v-model="form.visible" :title="formTitle" :width="820">
|
||||||
<template slot="title">
|
<template #footer>
|
||||||
{{ formTitle }}
|
|
||||||
<a-tooltip slot="action" title="只能输入250字">
|
|
||||||
<a-icon type="info-circle-o" />
|
|
||||||
</a-tooltip>
|
|
||||||
</template>
|
|
||||||
<template slot="footer">
|
|
||||||
<a-button type="dashed" @click="attachmentSelect.visible = true">附件库</a-button>
|
|
||||||
<ReactiveButton
|
<ReactiveButton
|
||||||
:errored="form.saveErrored"
|
:errored="form.saveErrored"
|
||||||
:loading="form.saving"
|
:loading="form.saving"
|
||||||
|
@ -141,12 +141,15 @@
|
||||||
</template>
|
</template>
|
||||||
<a-form-model ref="journalForm" :model="form.model" :rules="form.rules" layout="vertical">
|
<a-form-model ref="journalForm" :model="form.model" :rules="form.rules" layout="vertical">
|
||||||
<a-form-model-item prop="sourceContent">
|
<a-form-model-item prop="sourceContent">
|
||||||
<a-input
|
<div id="editor" style="height: 520px">
|
||||||
ref="sourceContentInput"
|
<MarkdownEditor
|
||||||
v-model="form.model.sourceContent"
|
v-if="form.visible"
|
||||||
:autoSize="{ minRows: 8 }"
|
:originalContent.sync="form.model.sourceContent"
|
||||||
type="textarea"
|
:subfield="false"
|
||||||
/>
|
:toolbars="simpleEditorToolbars"
|
||||||
|
@change="onContentChange"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
</a-form-model-item>
|
</a-form-model-item>
|
||||||
<a-form-model-item>
|
<a-form-model-item>
|
||||||
<a-switch v-model="form.isPublic" checkedChildren="公开" defaultChecked unCheckedChildren="私密" />
|
<a-switch v-model="form.isPublic" checkedChildren="公开" defaultChecked unCheckedChildren="私密" />
|
||||||
|
@ -154,8 +157,6 @@
|
||||||
</a-form-model>
|
</a-form-model>
|
||||||
</a-modal>
|
</a-modal>
|
||||||
|
|
||||||
<AttachmentSelectModal :visible.sync="attachmentSelect.visible" @confirm="handleSelectAttachment" />
|
|
||||||
|
|
||||||
<TargetCommentListModal
|
<TargetCommentListModal
|
||||||
:target-id="list.selected.id"
|
:target-id="list.selected.id"
|
||||||
:title="`「${$options.filters.moment(list.selected.createTime)}」的评论`"
|
:title="`「${$options.filters.moment(list.selected.createTime)}」的评论`"
|
||||||
|
@ -179,13 +180,17 @@ import TargetCommentListModal from '@/components/Comment/TargetCommentListModal'
|
||||||
// libs
|
// libs
|
||||||
import { mixin, mixinDevice } from '@/mixins/mixin.js'
|
import { mixin, mixinDevice } from '@/mixins/mixin.js'
|
||||||
import { mapActions, mapGetters } from 'vuex'
|
import { mapActions, mapGetters } from 'vuex'
|
||||||
|
import { simpleEditorToolbars } from '@/core/constant'
|
||||||
|
import { deepClone } from '@/utils/util'
|
||||||
import apiClient from '@/utils/api-client'
|
import apiClient from '@/utils/api-client'
|
||||||
|
import MarkdownEditor from '@/components/Editor/MarkdownEditor'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
mixins: [mixin, mixinDevice],
|
mixins: [mixin, mixinDevice],
|
||||||
components: { PageView, TargetCommentListModal },
|
components: { MarkdownEditor, PageView, TargetCommentListModal },
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
|
simpleEditorToolbars,
|
||||||
list: {
|
list: {
|
||||||
data: [],
|
data: [],
|
||||||
loading: false,
|
loading: false,
|
||||||
|
@ -212,7 +217,7 @@ export default {
|
||||||
form: {
|
form: {
|
||||||
model: {},
|
model: {},
|
||||||
rules: {
|
rules: {
|
||||||
sourceContent: [{ required: true, message: '* 内容不能为空', trigger: ['change'] }]
|
sourceContent: [{ required: true, message: '* 内容不能为空', trigger: [] }]
|
||||||
},
|
},
|
||||||
visible: false,
|
visible: false,
|
||||||
saving: false,
|
saving: false,
|
||||||
|
@ -222,9 +227,6 @@ export default {
|
||||||
journalCommentDrawer: {
|
journalCommentDrawer: {
|
||||||
visible: false
|
visible: false
|
||||||
},
|
},
|
||||||
attachmentSelect: {
|
|
||||||
visible: false
|
|
||||||
},
|
|
||||||
optionModal: {
|
optionModal: {
|
||||||
visible: false,
|
visible: false,
|
||||||
options: []
|
options: []
|
||||||
|
@ -289,18 +291,15 @@ export default {
|
||||||
},
|
},
|
||||||
handleOpenPublishModal() {
|
handleOpenPublishModal() {
|
||||||
this.form.visible = true
|
this.form.visible = true
|
||||||
this.form.model = {}
|
this.form.model = {
|
||||||
this.$nextTick(() => {
|
sourceContent: '',
|
||||||
this.$refs.sourceContentInput.focus()
|
content: ''
|
||||||
})
|
}
|
||||||
},
|
},
|
||||||
handleOpenEditModal(item) {
|
handleOpenEditModal(item) {
|
||||||
this.form.model = item
|
this.form.model = deepClone(item)
|
||||||
this.form.isPublic = item.type !== 'INTIMATE'
|
this.form.isPublic = item.type !== 'INTIMATE'
|
||||||
this.form.visible = true
|
this.form.visible = true
|
||||||
this.$nextTick(() => {
|
|
||||||
this.$refs.sourceContentInput.focus()
|
|
||||||
})
|
|
||||||
},
|
},
|
||||||
handleDelete(id) {
|
handleDelete(id) {
|
||||||
apiClient.journal.delete(id).finally(() => {
|
apiClient.journal.delete(id).finally(() => {
|
||||||
|
@ -311,11 +310,18 @@ export default {
|
||||||
this.list.selected = journal
|
this.list.selected = journal
|
||||||
this.journalCommentDrawer.visible = true
|
this.journalCommentDrawer.visible = true
|
||||||
},
|
},
|
||||||
|
|
||||||
|
onContentChange({ originalContent, renderContent }) {
|
||||||
|
this.form.model.sourceContent = originalContent
|
||||||
|
this.form.model.content = renderContent
|
||||||
|
},
|
||||||
|
|
||||||
handleSaveOrUpdate() {
|
handleSaveOrUpdate() {
|
||||||
const _this = this
|
const _this = this
|
||||||
_this.$refs.journalForm.validate(valid => {
|
_this.$refs.journalForm.validate(valid => {
|
||||||
if (valid) {
|
if (valid) {
|
||||||
_this.form.model.type = _this.form.isPublic ? 'PUBLIC' : 'INTIMATE'
|
_this.form.model.type = _this.form.isPublic ? 'PUBLIC' : 'INTIMATE'
|
||||||
|
_this.form.model.keepRaw = true
|
||||||
_this.form.saving = true
|
_this.form.saving = true
|
||||||
if (_this.form.model.id) {
|
if (_this.form.model.id) {
|
||||||
apiClient.journal
|
apiClient.journal
|
||||||
|
@ -343,6 +349,7 @@ export default {
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
|
|
||||||
handleSaveOrUpdateCallback() {
|
handleSaveOrUpdateCallback() {
|
||||||
if (this.form.saveErrored) {
|
if (this.form.saveErrored) {
|
||||||
this.form.saveErrored = false
|
this.form.saveErrored = false
|
||||||
|
@ -370,11 +377,13 @@ export default {
|
||||||
this.list.params.size = size
|
this.list.params.size = size
|
||||||
this.handleListJournals()
|
this.handleListJournals()
|
||||||
},
|
},
|
||||||
|
|
||||||
onJournalCommentsDrawerClose() {
|
onJournalCommentsDrawerClose() {
|
||||||
this.form.model = {}
|
this.form.model = {}
|
||||||
this.journalCommentDrawer.visible = false
|
this.journalCommentDrawer.visible = false
|
||||||
this.handleListJournals()
|
this.handleListJournals()
|
||||||
},
|
},
|
||||||
|
|
||||||
handleSaveOptions() {
|
handleSaveOptions() {
|
||||||
apiClient.option
|
apiClient.option
|
||||||
.save(this.optionModal.options)
|
.save(this.optionModal.options)
|
||||||
|
@ -387,9 +396,6 @@ export default {
|
||||||
this.refreshOptionsCache()
|
this.refreshOptionsCache()
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
handleSelectAttachment({ markdown }) {
|
|
||||||
this.$set(this.form.model, 'sourceContent', (this.form.model.sourceContent || '') + '\n' + markdown.join('\n'))
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Select previous journal
|
* Select previous journal
|
||||||
|
|
Loading…
Reference in New Issue