diff options
Diffstat (limited to 'front-end/src')
-rw-r--r-- | front-end/src/views/Blog/ckeditor/Editor.vue | 9 | ||||
-rw-r--r-- | front-end/src/views/Blog/ckeditor/build.ts | 264 | ||||
-rw-r--r-- | front-end/src/views/Blog/ckeditor/uploadAdapter.ts | 91 | ||||
-rw-r--r-- | front-end/src/views/Blog/components/Channels.vue | 2 | ||||
-rw-r--r-- | front-end/src/views/Blog/components/Content.vue | 22 | ||||
-rw-r--r-- | front-end/src/views/Blog/components/Content/ContentEditor.vue | 19 | ||||
-rw-r--r-- | front-end/src/views/Blog/components/Content/ContentTable.vue | 10 | ||||
-rw-r--r-- | front-end/src/views/Blog/components/FeedFields.vue | 4 | ||||
-rw-r--r-- | front-end/src/views/Blog/components/Posts.vue | 29 | ||||
-rw-r--r-- | front-end/src/views/Blog/components/Posts/PostEdit.vue | 21 | ||||
-rw-r--r-- | front-end/src/views/Blog/components/Posts/PostTable.vue | 19 |
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 |