aboutsummaryrefslogtreecommitdiff
path: root/front-end/src/views/Blog
diff options
context:
space:
mode:
Diffstat (limited to 'front-end/src/views/Blog')
-rw-r--r--front-end/src/views/Blog/ckeditor/Editor.vue9
-rw-r--r--front-end/src/views/Blog/ckeditor/build.ts264
-rw-r--r--front-end/src/views/Blog/ckeditor/uploadAdapter.ts91
-rw-r--r--front-end/src/views/Blog/components/Channels.vue2
-rw-r--r--front-end/src/views/Blog/components/Content.vue22
-rw-r--r--front-end/src/views/Blog/components/Content/ContentEditor.vue19
-rw-r--r--front-end/src/views/Blog/components/Content/ContentTable.vue10
-rw-r--r--front-end/src/views/Blog/components/FeedFields.vue4
-rw-r--r--front-end/src/views/Blog/components/Posts.vue29
-rw-r--r--front-end/src/views/Blog/components/Posts/PostEdit.vue21
-rw-r--r--front-end/src/views/Blog/components/Posts/PostTable.vue19
11 files changed, 304 insertions, 186 deletions
diff --git a/front-end/src/views/Blog/ckeditor/Editor.vue b/front-end/src/views/Blog/ckeditor/Editor.vue
index 4fc534b..c3d637c 100644
--- a/front-end/src/views/Blog/ckeditor/Editor.vue
+++ b/front-end/src/views/Blog/ckeditor/Editor.vue
@@ -79,8 +79,9 @@ import { BlogState } from '../blog-api'
import { Converter } from 'showdown'
//Import the editor config
-import { config } from './build.ts'
+import { useCkConfig } from './build.ts'
import ContentSearch from '../components/ContentSearch.vue';
+import { useUploadAdapter } from './uploadAdapter';
const emit = defineEmits(['change', 'load', 'mode-change'])
@@ -153,6 +154,12 @@ tryOnMounted(() => defer(() =>
//CKEditor 5 superbuild in global scope
const { ClassicEditor } = window['CKEDITOR']
+ //Init the ck config
+ const config = useCkConfig([
+ //Add the upload adapter
+ useUploadAdapter(props.blog.content, apiCall, toaster.general)
+ ]);
+
//Init editor when loading is complete
editor = await ClassicEditor.create(editorFrame.value, config);
diff --git a/front-end/src/views/Blog/ckeditor/build.ts b/front-end/src/views/Blog/ckeditor/build.ts
index fe3e0b9..3150a55 100644
--- a/front-end/src/views/Blog/ckeditor/build.ts
+++ b/front-end/src/views/Blog/ckeditor/build.ts
@@ -1,132 +1,140 @@
-export const config = {
- // https://ckeditor.com/docs/ckeditor5/latest/features/toolbar/toolbar.html#extended-toolbar-configuration-format
- toolbar: {
- items: [
- 'findAndReplace', 'selectAll', '|',
- 'heading', '|',
- 'bold', 'italic', 'strikethrough', 'underline', 'code', 'subscript', 'superscript', 'removeFormat', '|',
- 'bulletedList', 'numberedList', 'todoList', '|',
- 'outdent', 'indent', 'alignment', '|',
- 'undo', 'redo',
- '-',
- 'fontSize', 'fontFamily', 'fontColor', 'fontBackgroundColor', 'highlight', '|',
- 'link', 'insertImage', 'blockQuote', 'insertTable', 'mediaEmbed', 'codeBlock', 'htmlEmbed', '|',
- 'specialCharacters', 'horizontalLine', 'pageBreak', '|',
- 'exportPDF', 'exportWord', 'sourceEditing'
- ],
- shouldNotGroupWhenFull: true
- },
- // Changing the language of the interface requires loading the language file using the <script> tag.
- // language: 'es',
- list: {
- properties: {
- styles: true,
- startIndex: true,
- reversed: true
- }
- },
- // https://ckeditor.com/docs/ckeditor5/latest/features/headings.html#configuration
- heading: {
- options: [
- { model: 'paragraph', title: 'Paragraph', class: 'ck-heading_paragraph' },
- { model: 'heading1', view: 'h1', title: 'Heading 1', class: 'ck-heading_heading1' },
- { model: 'heading2', view: 'h2', title: 'Heading 2', class: 'ck-heading_heading2' },
- { model: 'heading3', view: 'h3', title: 'Heading 3', class: 'ck-heading_heading3' },
- { model: 'heading4', view: 'h4', title: 'Heading 4', class: 'ck-heading_heading4' },
- { model: 'heading5', view: 'h5', title: 'Heading 5', class: 'ck-heading_heading5' },
- { model: 'heading6', view: 'h6', title: 'Heading 6', class: 'ck-heading_heading6' }
- ]
- },
- // https://ckeditor.com/docs/ckeditor5/latest/features/editor-placeholder.html#using-the-editor-configuration
- placeholder: 'Edit CMNext post content using CKEditor 5 ',
- // https://ckeditor.com/docs/ckeditor5/latest/features/font.html#configuring-the-font-family-feature
- fontFamily: {
- options: [
- 'default',
- 'Arial, Helvetica, sans-serif',
- 'Courier New, Courier, monospace',
- 'Georgia, serif',
- 'Lucida Sans Unicode, Lucida Grande, sans-serif',
- 'Tahoma, Geneva, sans-serif',
- 'Times New Roman, Times, serif',
- 'Trebuchet MS, Helvetica, sans-serif',
- 'Verdana, Geneva, sans-serif'
- ],
- supportAllValues: true
- },
- // https://ckeditor.com/docs/ckeditor5/latest/features/font.html#configuring-the-font-size-feature
- fontSize: {
- options: [10, 12, 14, 'default', 18, 20, 22],
- supportAllValues: true
- },
- // Be careful with the setting below. It instructs CKEditor to accept ALL HTML markup.
- // https://ckeditor.com/docs/ckeditor5/latest/features/general-html-support.html#enabling-all-html-features
- htmlSupport: {
- allow: [
- {
- name: /.*/,
- attributes: true,
- classes: true,
- styles: true
+import type { Editor, EditorConfig, PluginConstructor } from '@ckeditor/ckeditor5-core';
+
+export const useCkConfig = (manualPlugins: PluginConstructor<Editor>[]): EditorConfig => {
+
+ return {
+ //Add the upload adapter plugin
+ extraPlugins: manualPlugins,
+
+ // https://ckeditor.com/docs/ckeditor5/latest/features/toolbar/toolbar.html#extended-toolbar-configuration-format
+ toolbar: {
+ items: [
+ 'findAndReplace', 'selectAll', '|',
+ 'heading', '|',
+ 'bold', 'italic', 'strikethrough', 'underline', 'code', 'subscript', 'superscript', 'removeFormat', '|',
+ 'bulletedList', 'numberedList', 'todoList', '|',
+ 'outdent', 'indent', 'alignment', '|',
+ 'undo', 'redo',
+ '-',
+ 'fontSize', 'fontFamily', 'fontColor', 'fontBackgroundColor', 'highlight', '|',
+ 'link', 'insertImage', 'blockQuote', 'insertTable', 'mediaEmbed', 'codeBlock', 'htmlEmbed', '|',
+ 'specialCharacters', 'horizontalLine', 'pageBreak', '|',
+ 'exportPDF', 'exportWord', 'sourceEditing'
+ ],
+ shouldNotGroupWhenFull: true
+ },
+ // Changing the language of the interface requires loading the language file using the <script> tag.
+ // language: 'es',
+ list: {
+ properties: {
+ styles: true,
+ startIndex: true,
+ reversed: true
}
- ]
- },
- // Be careful with enabling previews
- // https://ckeditor.com/docs/ckeditor5/latest/features/html-embed.html#content-previews
- htmlEmbed: {
- showPreviews: true
- },
- // https://ckeditor.com/docs/ckeditor5/latest/features/link.html#custom-link-attributes-decorators
- link: {
- decorators: {
- addTargetToExternalLinks: true,
- defaultProtocol: 'https://',
- toggleDownloadable: {
- mode: 'manual',
- label: 'Downloadable',
- attributes: {
- download: 'file'
+ },
+ // https://ckeditor.com/docs/ckeditor5/latest/features/headings.html#configuration
+ heading: {
+ options: [
+ { model: 'paragraph', title: 'Paragraph', class: 'ck-heading_paragraph' },
+ { model: 'heading1', view: 'h1', title: 'Heading 1', class: 'h1' },
+ { model: 'heading2', view: 'h2', title: 'Heading 2', class: 'h2' },
+ { model: 'heading3', view: 'h3', title: 'Heading 3', class: 'h3' },
+ { model: 'heading4', view: 'h4', title: 'Heading 4', class: 'h4' },
+ { model: 'heading5', view: 'h5', title: 'Heading 5', class: 'h5' },
+ { model: 'heading6', view: 'h6', title: 'Heading 6', class: 'h6' }
+ ]
+ },
+ // https://ckeditor.com/docs/ckeditor5/latest/features/editor-placeholder.html#using-the-editor-configuration
+ placeholder: 'Edit CMNext post content using CKEditor 5 ',
+ // https://ckeditor.com/docs/ckeditor5/latest/features/font.html#configuring-the-font-family-feature
+ fontFamily: {
+ options: [
+ 'default',
+ 'Arial, Helvetica, sans-serif',
+ 'Courier New, Courier, monospace',
+ 'Georgia, serif',
+ 'Lucida Sans Unicode, Lucida Grande, sans-serif',
+ 'Tahoma, Geneva, sans-serif',
+ 'Times New Roman, Times, serif',
+ 'Trebuchet MS, Helvetica, sans-serif',
+ 'Verdana, Geneva, sans-serif'
+ ],
+ supportAllValues: true
+ },
+ // https://ckeditor.com/docs/ckeditor5/latest/features/font.html#configuring-the-font-size-feature
+ fontSize: {
+ options: [10, 12, 14, 'default', 18, 20, 22],
+ supportAllValues: true
+ },
+ // Be careful with the setting below. It instructs CKEditor to accept ALL HTML markup.
+ // https://ckeditor.com/docs/ckeditor5/latest/features/general-html-support.html#enabling-all-html-features
+ htmlSupport: {
+ allow: [
+ {
+ name: /.*/,
+ attributes: true,
+ classes: true,
+ styles: true
+ }
+ ]
+ },
+ // Be careful with enabling previews
+ // https://ckeditor.com/docs/ckeditor5/latest/features/html-embed.html#content-previews
+ htmlEmbed: {
+ showPreviews: true
+ },
+ // https://ckeditor.com/docs/ckeditor5/latest/features/link.html#custom-link-attributes-decorators
+ link: {
+ decorators: {
+ addTargetToExternalLinks: true,
+ defaultProtocol: 'https://',
+ toggleDownloadable: {
+ mode: 'manual',
+ label: 'Downloadable',
+ attributes: {
+ download: 'file'
+ }
}
}
- }
- },
- // https://ckeditor.com/docs/ckeditor5/latest/features/mentions.html#configuration
- mention: {
- },
- // The "super-build" contains more premium features that require additional configuration, disable them below.
- // Do not turn them on unless you read the documentation and know how to configure them and setup the editor.
- removePlugins: [
- // These two are commercial, but you can try them out without registering to a trial.
- // 'ExportPdf',
- // 'ExportWord',
- 'CKBox',
- 'CKFinder',
- 'EasyImage',
- // This sample uses the Base64UploadAdapter to handle image uploads as it requires no configuration.
- // https://ckeditor.com/docs/ckeditor5/latest/features/images/image-upload/base64-upload-adapter.html
- // Storing images as Base64 is usually a very bad idea.
- // Replace it on production website with other solutions:
- // https://ckeditor.com/docs/ckeditor5/latest/features/images/image-upload/image-upload.html
- // 'Base64UploadAdapter',
- 'RealTimeCollaborativeComments',
- 'RealTimeCollaborativeTrackChanges',
- 'RealTimeCollaborativeRevisionHistory',
- 'PresenceList',
- 'Comments',
- 'TrackChanges',
- 'TrackChangesData',
- 'RevisionHistory',
- 'Pagination',
- 'WProofreader',
- // Careful, with the Mathtype plugin CKEditor will not load when loading this sample
- // from a local file system (file://) - load this site via HTTP server if you enable MathType
- 'MathType',
- 'DocumentOutline',
- 'PasteFromOfficeEnhanced',
- 'Template',
- 'SlashCommand',
- 'AIAssistant',
- 'FormatPainter',
- 'TableOfContents'
- ],
+ },
+ // https://ckeditor.com/docs/ckeditor5/latest/features/mentions.html#configuration
+ mention: {
+ },
+ // The "super-build" contains more premium features that require additional configuration, disable them below.
+ // Do not turn them on unless you read the documentation and know how to configure them and setup the editor.
+ removePlugins: [
+ // These two are commercial, but you can try them out without registering to a trial.
+ // 'ExportPdf',
+ // 'ExportWord',
+ 'CKBox',
+ 'CKFinder',
+ 'EasyImage',
+ // This sample uses the Base64UploadAdapter to handle image uploads as it requires no configuration.
+ // https://ckeditor.com/docs/ckeditor5/latest/features/images/image-upload/base64-upload-adapter.html
+ // Storing images as Base64 is usually a very bad idea.
+ // Replace it on production website with other solutions:
+ // https://ckeditor.com/docs/ckeditor5/latest/features/images/image-upload/image-upload.html
+ 'Base64UploadAdapter',
+ 'RealTimeCollaborativeComments',
+ 'RealTimeCollaborativeTrackChanges',
+ 'RealTimeCollaborativeRevisionHistory',
+ 'PresenceList',
+ 'Comments',
+ 'TrackChanges',
+ 'TrackChangesData',
+ 'RevisionHistory',
+ 'Pagination',
+ 'WProofreader',
+ // Careful, with the Mathtype plugin CKEditor will not load when loading this sample
+ // from a local file system (file://) - load this site via HTTP server if you enable MathType
+ 'MathType',
+ 'DocumentOutline',
+ 'PasteFromOfficeEnhanced',
+ 'Template',
+ 'SlashCommand',
+ 'AIAssistant',
+ 'FormatPainter',
+ 'TableOfContents'
+ ],
+ }
} \ No newline at end of file
diff --git a/front-end/src/views/Blog/ckeditor/uploadAdapter.ts b/front-end/src/views/Blog/ckeditor/uploadAdapter.ts
new file mode 100644
index 0000000..1c22842
--- /dev/null
+++ b/front-end/src/views/Blog/ckeditor/uploadAdapter.ts
@@ -0,0 +1,91 @@
+// Copyright (C) 2023 Vaughn Nugent
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Affero General Public License as
+// published by the Free Software Foundation, either version 3 of the
+// License, or (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Affero General Public License for more details.
+//
+// You should have received a copy of the GNU Affero General Public License
+// along with this program. If not, see <https://www.gnu.org/licenses/>.
+
+import { ComputedContent } from "@vnuge/cmnext-admin";
+import { IToaster } from "@vnuge/vnlib.browser";
+import { isNil } from "lodash-es";
+import type { AxiosRequestConfig } from "axios";
+import type { Editor } from "@ckeditor/ckeditor5-core";
+import type { UploadAdapter, UploadResponse, FileLoader } from '@ckeditor/ckeditor5-upload'
+
+export type ApiCall = (callback: (data: any) => Promise<any>) => Promise<any>;
+export type CKEditorPlugin = (editor: Editor) => void;
+
+/**
+ * Creates a CKEditor plugin that adds an upload adapter to the editor
+ * @param content The content api instance
+ * @param apiCall A callback function that wraps the api call
+ * @returns A CKEditor plugin initializer
+ */
+export const useUploadAdapter = (content: ComputedContent, apiCall: ApiCall, toaster?: IToaster): CKEditorPlugin =>{
+
+ const createUploadAdapter = (loader: FileLoader): UploadAdapter => {
+
+ const abortController = new AbortController();
+
+ /**
+ * Init request config local to a
+ */
+ const requestConfig = {
+ signal: abortController.signal,
+ onUploadProgress: (progressEvent: ProgressEvent) => {
+ loader.uploadTotal = progressEvent.total;
+ loader.uploaded = Math.round(progressEvent.loaded * 100);
+ }
+ } as unknown as AxiosRequestConfig;
+
+ const upload = async (): Promise<UploadResponse> => {
+ //Get the file
+ const file = await loader.file;
+
+ if(isNil(file)){
+ return{ default: '' }
+ }
+
+ //Exec server operations
+ const url = await apiCall(async () => {
+
+ //Upload the file
+ const meta = await content.uploadContent(file, file.name, requestConfig);
+
+ toaster?.info({
+ title: 'Upload Complete',
+ text: `Successfully uploaded file ${file.name} ID:${meta.id}`
+ })
+
+ //Get the public url
+ return await content.getPublicUrl(meta);
+
+ }) as string
+
+ //Reload content
+ content.refresh();
+
+ //Default url as the returned file url
+ return { default: url }
+ }
+
+ const abort = () => {
+ abortController.abort('Upload aborted');
+ }
+
+ return { upload, abort }
+ }
+
+ return function (editor: Editor): void {
+ //Add the upload adapter factory to the editor
+ editor.plugins.get('FileRepository').createUploadAdapter = createUploadAdapter;
+ };
+} \ No newline at end of file
diff --git a/front-end/src/views/Blog/components/Channels.vue b/front-end/src/views/Blog/components/Channels.vue
index 417d3f9..2a160b3 100644
--- a/front-end/src/views/Blog/components/Channels.vue
+++ b/front-end/src/views/Blog/components/Channels.vue
@@ -1,7 +1,7 @@
<template>
<div id="channel-editor">
<EditorTable title="Manage channels" :show-edit="showEdit" :pagination="pagination" @open-new="openNew">
- <template v-slot:table>
+ <template #table>
<ChannelTable
:channels="items"
@open-edit="openEdit"
diff --git a/front-end/src/views/Blog/components/Content.vue b/front-end/src/views/Blog/components/Content.vue
index ba78773..d8a9f24 100644
--- a/front-end/src/views/Blog/components/Content.vue
+++ b/front-end/src/views/Blog/components/Content.vue
@@ -1,11 +1,12 @@
<template>
<div id="content-editor" class="">
<EditorTable title="Manage content" :show-edit="showEdit" :pagination="pagination" @open-new="openNew">
- <template v-slot:table>
+ <template #table>
<ContentTable
:content="items"
@open-edit="openEdit"
@copy-link="copyLink"
+ @delete="onDelete"
/>
</template>
<template #editor>
@@ -40,7 +41,7 @@
import { computed, toRefs } from 'vue';
import { BlogState } from '../blog-api';
import { isEmpty } from 'lodash-es';
-import { apiCall } from '@vnuge/vnlib.browser';
+import { apiCall, useConfirm } from '@vnuge/vnlib.browser';
import { useClipboard } from '@vueuse/core';
import { ContentMeta, useFilteredPages } from '@vnuge/cmnext-admin';
import EditorTable from './EditorTable.vue';
@@ -67,6 +68,7 @@ const { selectedId,
//Setup content filter
const { items, pagination } = useFilteredPages(props.blog.content, 15)
+ const { reveal } = useConfirm()
const showEdit = computed(() => !isEmpty(selectedId.value));
const loadingProgress = computed(() => `${progress?.value}%`);
@@ -142,12 +144,28 @@ const onSubmit = async (value : OnSubmitValue) => {
}
const onDelete = async (item: ContentMeta) => {
+ //Show confirm
+ const { isCanceled } = await reveal({
+ title: 'Delete File?',
+ text: `Are you sure you want to delete ${item.name}? This action cannot be undone.`,
+ })
+ if (isCanceled) {
+ return;
+ }
+
+ if (!confirm(`Are you sure you want to delete ${item.name} forever?`)) {
+ return;
+ }
+
//Exec delete call
await apiCall(async () => {
await deleteContent(item);
//Close the edit panel
closeEdit(true);
})
+
+ //Refresh content after delete
+ props.blog.content.refresh();
}
diff --git a/front-end/src/views/Blog/components/Content/ContentEditor.vue b/front-end/src/views/Blog/components/Content/ContentEditor.vue
index 8da6b0a..756cec3 100644
--- a/front-end/src/views/Blog/components/Content/ContentEditor.vue
+++ b/front-end/src/views/Blog/components/Content/ContentEditor.vue
@@ -180,23 +180,8 @@ const onSubmit = async () => {
const onClose = () => emit('close');
-const onDelete = async () => {
- //Show confirm
- const { isCanceled } = await reveal({
- title: 'Delete File?',
- text: 'Are you sure you want to delete this file? This action cannot be undone.',
- })
- if (isCanceled) {
- return;
- }
-
- if (!confirm('Are you sure you want to delete this file forever?')) {
- return;
- }
-
- //Emit the delete event with the original post
- emit('delete', metaBuffer)
-}
+//Emit delete event
+const onDelete = () => emit('delete', metaBuffer)
const removeNewFile = () =>{
file.value = undefined;
diff --git a/front-end/src/views/Blog/components/Content/ContentTable.vue b/front-end/src/views/Blog/components/Content/ContentTable.vue
index e5cbe58..3afe320 100644
--- a/front-end/src/views/Blog/components/Content/ContentTable.vue
+++ b/front-end/src/views/Blog/components/Content/ContentTable.vue
@@ -28,14 +28,17 @@
</td>
<td class="w-24">
<fieldset :disabled="waiting">
+ <button class="btn xs no-border" @click="openEdit(item)">
+ <fa-icon icon="pencil" />
+ </button>
<button class="btn xs no-border" @click="copyLink(item)">
<fa-icon icon="link" />
</button>
<button class="btn xs no-border" @click="copy(item.id)">
<fa-icon icon="copy" />
</button>
- <button class="btn xs no-border" @click="openEdit(item)">
- <fa-icon icon="pencil" />
+ <button class="btn xs no-border red" @click="deleteItem(item)">
+ <fa-icon icon="trash" />
</button>
</fieldset>
</td>
@@ -50,7 +53,7 @@ import { useClipboard } from '@vueuse/core';
import { useWait } from '@vnuge/vnlib.browser';
import { ContentMeta } from '@vnuge/cmnext-admin';
-const emit = defineEmits(['open-edit', 'copy-link'])
+const emit = defineEmits(['open-edit', 'copy-link', 'delete'])
const props = defineProps<{
content: ContentMeta[]
@@ -71,5 +74,6 @@ const getItemName = (item : ContentMeta) => truncate(item.name || '', { length:
const openEdit = async (item: ContentMeta) => emit('open-edit', item)
const copyLink = (item : ContentMeta) => emit('copy-link', item)
+const deleteItem = (item : ContentMeta) => emit('delete', item)
</script> \ No newline at end of file
diff --git a/front-end/src/views/Blog/components/FeedFields.vue b/front-end/src/views/Blog/components/FeedFields.vue
index 90e1454..0397c48 100644
--- a/front-end/src/views/Blog/components/FeedFields.vue
+++ b/front-end/src/views/Blog/components/FeedFields.vue
@@ -36,11 +36,11 @@
</template>
<script setup lang="ts">
-import { computed, ref } from 'vue';
+import { computed, defineAsyncComponent, ref } from 'vue';
import { FeedProperty, UseXmlProperties } from '@vnuge/cmnext-admin';
import { BlogState } from '../blog-api';
-import JsonEditorVue from 'json-editor-vue'
import EpAdder from './podcast-helpers/EpisodeAdder.vue';
+const JsonEditorVue = defineAsyncComponent(() => import('json-editor-vue'))
const props = defineProps<{
properties: UseXmlProperties,
diff --git a/front-end/src/views/Blog/components/Posts.vue b/front-end/src/views/Blog/components/Posts.vue
index 0407a26..d52a0ac 100644
--- a/front-end/src/views/Blog/components/Posts.vue
+++ b/front-end/src/views/Blog/components/Posts.vue
@@ -1,10 +1,11 @@
<template>
<div id="post-editor" class="">
<EditorTable title="Manage posts" :show-edit="showEdit" :pagination="pagination" @open-new="openNew">
- <template v-slot:table>
+ <template #table>
<PostTable
:posts="items"
@open-edit="openEdit"
+ @delete="onDelete"
/>
</template>
<template #editor>
@@ -20,14 +21,15 @@
</template>
<script setup lang="ts">
-import { computed } from 'vue';
+import { computed, defineAsyncComponent } from 'vue';
import { isEmpty } from 'lodash-es';
import { PostMeta, useFilteredPages } from '@vnuge/cmnext-admin';
-import { apiCall, debugLog } from '@vnuge/vnlib.browser';
+import { apiCall, debugLog, useConfirm } from '@vnuge/vnlib.browser';
+import { BlogState } from '../blog-api';
import EditorTable from './EditorTable.vue';
-import PostEditor from './Posts/PostEdit.vue';
import PostTable from './Posts/PostTable.vue';
-import { BlogState } from '../blog-api';
+
+const PostEditor = defineAsyncComponent(() => import('./Posts/PostEdit.vue'))
const emit = defineEmits(['reload'])
@@ -37,6 +39,7 @@ const props = defineProps<{
const { selectedId, publishPost, updatePost, deletePost } = props.blog.posts;
const { updatePostContent } = props.blog.content;
+const { reveal } = useConfirm()
const showEdit = computed(() => !isEmpty(selectedId.value));
@@ -98,12 +101,28 @@ const onSubmit = async ({post, content } : { post:PostMeta, content:string }) =>
}
const onDelete = async (post: PostMeta) => {
+
+ //Show confirm
+ const { isCanceled } = await reveal({
+ title: 'Delete Post?',
+ text: `Are you sure you want to delete post '${post.title}?' This action cannot be undone.`,
+ })
+ if (isCanceled) {
+ return;
+ }
+
+ if (!confirm(`Are you sure you want to delete post '${post.id}' forever?`)) {
+ return;
+ }
+
//Exec delete call
await apiCall(async () => {
await deletePost(post);
//Close the edit panel
closeEdit(true);
})
+
+ props.blog.posts.refresh();
}
</script>
diff --git a/front-end/src/views/Blog/components/Posts/PostEdit.vue b/front-end/src/views/Blog/components/Posts/PostEdit.vue
index 6ea0bac..0268508 100644
--- a/front-end/src/views/Blog/components/Posts/PostEdit.vue
+++ b/front-end/src/views/Blog/components/Posts/PostEdit.vue
@@ -46,7 +46,7 @@ import { BlogState } from '../../blog-api';
import { reactiveComputed } from '@vueuse/core';
import { isNil, isString, split } from 'lodash-es';
import { PostMeta, useXmlProperties } from '@vnuge/cmnext-admin';
-import { apiCall, useConfirm, useUser } from '@vnuge/vnlib.browser';
+import { apiCall, useUser } from '@vnuge/vnlib.browser';
import { getPostForm } from '../../form-helpers';
import Editor from '../../ckeditor/Editor.vue';
import FeedFields from '../FeedFields.vue';
@@ -56,7 +56,6 @@ const props = defineProps<{
blog: BlogState
}>()
-const { reveal } = useConfirm();
const { getProfile } = useUser();
const { schema, getValidator } = getPostForm();
@@ -117,23 +116,7 @@ const onContentChanged = (content: string) => {
v$.value.content.$model = content;
}
-const onDelete = async () => {
- //Show confirm
- const { isCanceled } = await reveal({
- title: 'Delete Post?',
- text: 'Are you sure you want to delete this post? This action cannot be undone.',
- })
- if (isCanceled) {
- return;
- }
-
- if (!confirm('Are you sure you want to delete this post forever?')) {
- return;
- }
-
- //Emit the delete event with the original post
- emit('delete', posts.selectedItem.value)
-}
+const onDelete = () => emit('delete', posts.selectedItem.value)
const setMeAsAuthor = () => {
apiCall(async () => {
diff --git a/front-end/src/views/Blog/components/Posts/PostTable.vue b/front-end/src/views/Blog/components/Posts/PostTable.vue
index 96c511c..c1583a6 100644
--- a/front-end/src/views/Blog/components/Posts/PostTable.vue
+++ b/front-end/src/views/Blog/components/Posts/PostTable.vue
@@ -11,7 +11,7 @@
</thead>
<tbody>
<tr v-for="post in posts" :key="post.id" class="table-row">
- <td>
+ <td class="truncate max-w-[16rem]">
{{ post.title }}
</td>
<td>
@@ -20,18 +20,21 @@
<td>
{{ getDateString(post.date) }}
</td>
- <td>
+ <td class="truncate max-w-[10rem]">
{{ post.author }}
</td>
- <td>
- {{ getSummaryString(post.summary) }}
+ <td class="truncate max-w-[16rem]">
+ {{ post.summary }}
</td>
<td class="w-20">
+ <button class="btn xs no-border" @click="openEdit(post)">
+ <fa-icon icon="pencil" />
+ </button>
<button class="btn xs no-border" @click="copy(post.id)">
<fa-icon icon="copy" />
</button>
- <button class="btn xs no-border" @click="openEdit(post)">
- <fa-icon icon="pencil" />
+ <button class="btn xs no-border red" @click="onDelete(post)">
+ <fa-icon icon="trash" />
</button>
</td>
</tr>
@@ -45,7 +48,7 @@ import { useClipboard } from '@vueuse/core';
import { PostMeta } from '@vnuge/cmnext-admin';
import { useGeneralToaster } from '@vnuge/vnlib.browser';
-const emit = defineEmits(['reload', 'open-edit'])
+const emit = defineEmits(['reload', 'open-edit', 'delete'])
const props = defineProps<{
posts: PostMeta[],
@@ -59,8 +62,8 @@ const { info } = useGeneralToaster()
const openEdit = async (post: PostMeta) => emit('open-edit', post)
const getDateString = (time?: number) => new Date((time || 0) * 1000).toLocaleString();
-const getSummaryString = (summary?: string) => truncate(summary || '', { length: 40 })
const getPostId = (post: PostMeta) => truncate(post.id || '', { length: 20 })
+const onDelete = (post: PostMeta) => emit('delete', post)
watch(copied, (c) => c ? info({'title':'Copied to clipboard'}) : null)
</script> \ No newline at end of file