refactor: journal editing using the markdown editor (#506)

Signed-off-by: Ryan Wang <i@ryanc.cc>
pull/507/head
Ryan Wang 2022-03-13 10:46:50 +08:00 committed by GitHub
parent 7e1e58387e
commit b50e1abe98
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 123 additions and 83 deletions

View File

@ -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",

View File

@ -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

View File

@ -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
} }
}, },

View File

@ -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,

View File

@ -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">

View File

@ -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