From f64955c69d91e578e580b409ba31ac4b3477da96 Mon Sep 17 00:00:00 2001 From: vnugent Date: Wed, 12 Jul 2023 01:28:23 -0400 Subject: Initial commit --- front-end/src/views/Account/[comp].vue | 136 +++++++++ .../views/Account/components/oauth/CreateApp.vue | 183 ++++++++++++ .../src/views/Account/components/oauth/Oauth.vue | 93 ++++++ .../Account/components/oauth/SingleApplication.vue | 190 ++++++++++++ .../src/views/Account/components/oauth/o2Api.ts | 176 +++++++++++ .../views/Account/components/profile/Profile.vue | 199 +++++++++++++ .../Account/components/profile/profile-schema.ts | 310 ++++++++++++++++++++ .../src/views/Account/components/settings/Fido.vue | 53 ++++ .../Account/components/settings/PasswordReset.vue | 235 +++++++++++++++ .../src/views/Account/components/settings/Pki.vue | 182 ++++++++++++ .../views/Account/components/settings/Security.vue | 81 ++++++ .../views/Account/components/settings/Settings.vue | 16 + .../Account/components/settings/TotpSettings.vue | 263 +++++++++++++++++ front-end/src/views/Blog/blog-api/index.ts | 22 ++ front-end/src/views/Blog/ckeditor/Editor.vue | 155 ++++++++++ front-end/src/views/Blog/ckeditor/build.ts | 125 ++++++++ front-end/src/views/Blog/components/Channels.vue | 99 +++++++ .../views/Blog/components/Channels/ChannelEdit.vue | 157 ++++++++++ .../Blog/components/Channels/ChannelTable.vue | 48 +++ front-end/src/views/Blog/components/Content.vue | 133 +++++++++ .../Blog/components/Content/ContentEditor.vue | 225 ++++++++++++++ .../views/Blog/components/Content/ContentTable.vue | 75 +++++ .../src/views/Blog/components/ContentSearch.vue | 115 ++++++++ .../src/views/Blog/components/EditorTable.vue | 96 ++++++ front-end/src/views/Blog/components/FeedFields.vue | 140 +++++++++ front-end/src/views/Blog/components/Posts.vue | 113 +++++++ .../src/views/Blog/components/Posts/PostEdit.vue | 155 ++++++++++ .../src/views/Blog/components/Posts/PostTable.vue | 62 ++++ .../components/podcast-helpers/EpisodeAdder.vue | 164 +++++++++++ .../components/podcast-helpers/podcast-form.ts | 174 +++++++++++ front-end/src/views/Blog/form-helpers/channels.ts | 227 +++++++++++++++ front-end/src/views/Blog/form-helpers/index.ts | 17 ++ front-end/src/views/Blog/form-helpers/posts.ts | 116 ++++++++ front-end/src/views/Blog/index.vue | 324 +++++++++++++++++++++ front-end/src/views/Login/components/Social.vue | 57 ++++ front-end/src/views/Login/components/Totp.vue | 65 +++++ front-end/src/views/Login/components/UserPass.vue | 92 ++++++ front-end/src/views/Login/index.vue | 182 ++++++++++++ front-end/src/views/Login/pki/index.vue | 80 +++++ front-end/src/views/Login/social/[type].vue | 127 ++++++++ .../src/views/Register/components/CompleteReg.vue | 116 ++++++++ front-end/src/views/Register/index.vue | 161 ++++++++++ front-end/src/views/[...all].vue | 24 ++ front-end/src/views/index.vue | 18 ++ 44 files changed, 5781 insertions(+) create mode 100644 front-end/src/views/Account/[comp].vue create mode 100644 front-end/src/views/Account/components/oauth/CreateApp.vue create mode 100644 front-end/src/views/Account/components/oauth/Oauth.vue create mode 100644 front-end/src/views/Account/components/oauth/SingleApplication.vue create mode 100644 front-end/src/views/Account/components/oauth/o2Api.ts create mode 100644 front-end/src/views/Account/components/profile/Profile.vue create mode 100644 front-end/src/views/Account/components/profile/profile-schema.ts create mode 100644 front-end/src/views/Account/components/settings/Fido.vue create mode 100644 front-end/src/views/Account/components/settings/PasswordReset.vue create mode 100644 front-end/src/views/Account/components/settings/Pki.vue create mode 100644 front-end/src/views/Account/components/settings/Security.vue create mode 100644 front-end/src/views/Account/components/settings/Settings.vue create mode 100644 front-end/src/views/Account/components/settings/TotpSettings.vue create mode 100644 front-end/src/views/Blog/blog-api/index.ts create mode 100644 front-end/src/views/Blog/ckeditor/Editor.vue create mode 100644 front-end/src/views/Blog/ckeditor/build.ts create mode 100644 front-end/src/views/Blog/components/Channels.vue create mode 100644 front-end/src/views/Blog/components/Channels/ChannelEdit.vue create mode 100644 front-end/src/views/Blog/components/Channels/ChannelTable.vue create mode 100644 front-end/src/views/Blog/components/Content.vue create mode 100644 front-end/src/views/Blog/components/Content/ContentEditor.vue create mode 100644 front-end/src/views/Blog/components/Content/ContentTable.vue create mode 100644 front-end/src/views/Blog/components/ContentSearch.vue create mode 100644 front-end/src/views/Blog/components/EditorTable.vue create mode 100644 front-end/src/views/Blog/components/FeedFields.vue create mode 100644 front-end/src/views/Blog/components/Posts.vue create mode 100644 front-end/src/views/Blog/components/Posts/PostEdit.vue create mode 100644 front-end/src/views/Blog/components/Posts/PostTable.vue create mode 100644 front-end/src/views/Blog/components/podcast-helpers/EpisodeAdder.vue create mode 100644 front-end/src/views/Blog/components/podcast-helpers/podcast-form.ts create mode 100644 front-end/src/views/Blog/form-helpers/channels.ts create mode 100644 front-end/src/views/Blog/form-helpers/index.ts create mode 100644 front-end/src/views/Blog/form-helpers/posts.ts create mode 100644 front-end/src/views/Blog/index.vue create mode 100644 front-end/src/views/Login/components/Social.vue create mode 100644 front-end/src/views/Login/components/Totp.vue create mode 100644 front-end/src/views/Login/components/UserPass.vue create mode 100644 front-end/src/views/Login/index.vue create mode 100644 front-end/src/views/Login/pki/index.vue create mode 100644 front-end/src/views/Login/social/[type].vue create mode 100644 front-end/src/views/Register/components/CompleteReg.vue create mode 100644 front-end/src/views/Register/index.vue create mode 100644 front-end/src/views/[...all].vue create mode 100644 front-end/src/views/index.vue (limited to 'front-end/src/views') diff --git a/front-end/src/views/Account/[comp].vue b/front-end/src/views/Account/[comp].vue new file mode 100644 index 0000000..75fd086 --- /dev/null +++ b/front-end/src/views/Account/[comp].vue @@ -0,0 +1,136 @@ + + + + + diff --git a/front-end/src/views/Account/components/oauth/CreateApp.vue b/front-end/src/views/Account/components/oauth/CreateApp.vue new file mode 100644 index 0000000..2321743 --- /dev/null +++ b/front-end/src/views/Account/components/oauth/CreateApp.vue @@ -0,0 +1,183 @@ + + + + + \ No newline at end of file diff --git a/front-end/src/views/Blog/ckeditor/build.ts b/front-end/src/views/Blog/ckeditor/build.ts new file mode 100644 index 0000000..83b46a7 --- /dev/null +++ b/front-end/src/views/Blog/ckeditor/build.ts @@ -0,0 +1,125 @@ +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 + + \ No newline at end of file diff --git a/front-end/src/views/Blog/components/Channels/ChannelEdit.vue b/front-end/src/views/Blog/components/Channels/ChannelEdit.vue new file mode 100644 index 0000000..56376fe --- /dev/null +++ b/front-end/src/views/Blog/components/Channels/ChannelEdit.vue @@ -0,0 +1,157 @@ + + + + + \ No newline at end of file diff --git a/front-end/src/views/Blog/components/Channels/ChannelTable.vue b/front-end/src/views/Blog/components/Channels/ChannelTable.vue new file mode 100644 index 0000000..cdf15e0 --- /dev/null +++ b/front-end/src/views/Blog/components/Channels/ChannelTable.vue @@ -0,0 +1,48 @@ + + + diff --git a/front-end/src/views/Blog/components/Content.vue b/front-end/src/views/Blog/components/Content.vue new file mode 100644 index 0000000..00f8602 --- /dev/null +++ b/front-end/src/views/Blog/components/Content.vue @@ -0,0 +1,133 @@ + + + \ No newline at end of file diff --git a/front-end/src/views/Blog/components/Content/ContentEditor.vue b/front-end/src/views/Blog/components/Content/ContentEditor.vue new file mode 100644 index 0000000..4de7f8a --- /dev/null +++ b/front-end/src/views/Blog/components/Content/ContentEditor.vue @@ -0,0 +1,225 @@ + + + + + \ No newline at end of file diff --git a/front-end/src/views/Blog/components/Content/ContentTable.vue b/front-end/src/views/Blog/components/Content/ContentTable.vue new file mode 100644 index 0000000..c47a063 --- /dev/null +++ b/front-end/src/views/Blog/components/Content/ContentTable.vue @@ -0,0 +1,75 @@ + + + \ No newline at end of file diff --git a/front-end/src/views/Blog/components/ContentSearch.vue b/front-end/src/views/Blog/components/ContentSearch.vue new file mode 100644 index 0000000..37fd438 --- /dev/null +++ b/front-end/src/views/Blog/components/ContentSearch.vue @@ -0,0 +1,115 @@ + + + + + \ No newline at end of file diff --git a/front-end/src/views/Blog/components/EditorTable.vue b/front-end/src/views/Blog/components/EditorTable.vue new file mode 100644 index 0000000..4ec5a33 --- /dev/null +++ b/front-end/src/views/Blog/components/EditorTable.vue @@ -0,0 +1,96 @@ + + + + + \ 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 new file mode 100644 index 0000000..90e1454 --- /dev/null +++ b/front-end/src/views/Blog/components/FeedFields.vue @@ -0,0 +1,140 @@ + + + + + \ No newline at end of file diff --git a/front-end/src/views/Blog/components/Posts.vue b/front-end/src/views/Blog/components/Posts.vue new file mode 100644 index 0000000..5ebeeac --- /dev/null +++ b/front-end/src/views/Blog/components/Posts.vue @@ -0,0 +1,113 @@ + + + + + \ No newline at end of file diff --git a/front-end/src/views/Blog/components/Posts/PostEdit.vue b/front-end/src/views/Blog/components/Posts/PostEdit.vue new file mode 100644 index 0000000..4f7b52b --- /dev/null +++ b/front-end/src/views/Blog/components/Posts/PostEdit.vue @@ -0,0 +1,155 @@ + + + + \ No newline at end of file diff --git a/front-end/src/views/Blog/components/Posts/PostTable.vue b/front-end/src/views/Blog/components/Posts/PostTable.vue new file mode 100644 index 0000000..e5e45f2 --- /dev/null +++ b/front-end/src/views/Blog/components/Posts/PostTable.vue @@ -0,0 +1,62 @@ + + + \ No newline at end of file diff --git a/front-end/src/views/Blog/components/podcast-helpers/EpisodeAdder.vue b/front-end/src/views/Blog/components/podcast-helpers/EpisodeAdder.vue new file mode 100644 index 0000000..79b21cf --- /dev/null +++ b/front-end/src/views/Blog/components/podcast-helpers/EpisodeAdder.vue @@ -0,0 +1,164 @@ + + + + \ No newline at end of file diff --git a/front-end/src/views/Blog/components/podcast-helpers/podcast-form.ts b/front-end/src/views/Blog/components/podcast-helpers/podcast-form.ts new file mode 100644 index 0000000..ab8ad8a --- /dev/null +++ b/front-end/src/views/Blog/components/podcast-helpers/podcast-form.ts @@ -0,0 +1,174 @@ +// 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 . + +import { computed, Ref } from 'vue'; +import { helpers, required, maxLength, alphaNum, numeric } from "@vuelidate/validators" +import useVuelidate from "@vuelidate/core" +import { MaybeRef } from '@vueuse/core'; +import { useVuelidateWrapper } from '@vnuge/vnlib.browser'; +import { ContentMeta, FeedProperty } from '@vnuge/cmnext-admin'; + +export interface EnclosureEntity{ + fileId: string; + contentUrl: string; + contentLength: number; + contentType: string; +} + +export interface PodcastEntity extends EnclosureEntity{ + episodeType: string; + duration: number; +} + +export const getPodcastForm = (editMode?: Ref) => { + const schema = computed(() => { + return { + fields: [ + { + id: 'episode-type', + type: 'text', + label: 'Episode Type', + name: 'episodeType', + placeholder: '', + description: 'The itunes episode type, typically "full" or "trailer"', + }, + { + id: 'episode-duration', + type: 'text', + label: 'Duration', + name: 'duration', + placeholder: '', + description: 'The duration in seconds for the episode', + }, + { + id: 'ep-content-id', + type: 'text', + label: 'File Id', + name: 'fileId', + placeholder: '', + description: 'The file id of the episode already in the channel', + disabled: true, + }, + { + id: 'content-url', + type: 'text', + label: 'Content url', + name: 'contentUrl', + placeholder: '', + description: 'This the relative url to the episode content file', + disabled: true, + }, + { + id: 'content-length', + type: 'text', + label: 'Content length', + name: 'contentLength', + placeholder: '', + description: 'This the length in bytes of the episode content file', + disabled: true, + }, + { + id: 'content-type', + type: 'text', + label: 'The MIME content type', + name: 'contentType', + placeholder: '', + description: 'The MIME content type for the episode content file', + disabled: true, + } + ] + } + }); + + + const alphaNumSlash = helpers.regex(/^[a-zA-Z0-9\/]*$/); + + const rules = { + fileId: { + required:helpers.withMessage('The file id is required', required), + maxLength: helpers.withMessage('The file id must be less than 64 characters', maxLength(64)), + alphaNumeric: helpers.withMessage('The file id must be alpha numeric', alphaNum) + }, + episodeType: { + required: helpers.withMessage('The episode type is required', required), + maxLength: helpers.withMessage('The episode type must be less than 64 characters', maxLength(64)), + alphaNumeric: helpers.withMessage('The episode type must be alpha numeric', alphaNum) + }, + duration: { + required: helpers.withMessage('The duration is required', required), + numeric: helpers.withMessage('The duration must be a number', numeric) + }, + contentUrl: { + required: helpers.withMessage('The content url is required', required), + maxLength: helpers.withMessage('The content url must be less than 256 characters', maxLength(256)) + }, + contentLength: { + required: helpers.withMessage('The content length is required', required), + numeric: helpers.withMessage('The content length must be a number', numeric) + }, + contentType: { + required: helpers.withMessage('The content type is required', required), + maxLength: helpers.withMessage('The content type must be less than 64 characters', maxLength(64)), + alphaNumeric: helpers.withMessage('The content type must be in MIME format', alphaNumSlash) + } + } + + const getValidator = (buffer: MaybeRef) => { + const v$ = useVuelidate(rules, buffer, { $lazy: true, $autoDirty: true }); + const { validate } = useVuelidateWrapper(v$); + + return { v$, validate, reset: v$.value.$reset }; + } + + const setEnclosureContent = (enclosure: EnclosureEntity, content: ContentMeta, url: string) => { + enclosure.fileId = content.id; + enclosure.contentLength = content.length + enclosure.contentType = content.content_type; + enclosure.contentUrl = url; + } + + const exportProperties = (podcast: PodcastEntity) : FeedProperty[] => { + return [ + { + name: 'episodeType', + namespace: 'itunes', + value: podcast.episodeType + }, + { + name: 'duration', + namespace: 'itunes', + value: podcast.duration?.toString() + }, + //Setup the enclosure + { + name:"enclosure", + attributes:{ + url: podcast.contentUrl, + length: podcast.contentLength?.toString(), + type: podcast.contentType + }, + } + ] + } + + return { + schema, + rules, + getValidator, + setEnclosureContent, + exportProperties + }; +} + diff --git a/front-end/src/views/Blog/form-helpers/channels.ts b/front-end/src/views/Blog/form-helpers/channels.ts new file mode 100644 index 0000000..cd33a20 --- /dev/null +++ b/front-end/src/views/Blog/form-helpers/channels.ts @@ -0,0 +1,227 @@ +// 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 . + +import { MaybeRef, computed, watch, Ref } from 'vue' +import { helpers, required, maxLength, numeric } from "@vuelidate/validators" +import { useVuelidateWrapper } from '@vnuge/vnlib.browser'; +import { BlogChannel, ChannelFeed } from '@vnuge/cmnext-admin'; +import useVuelidate from "@vuelidate/core" + +export const getChannelForm = (editMode?: Ref) => { + const channelSchema = computed(() => { + return { + fields: [ + { + id: 'channel-name', + type: 'text', + label: 'Channel Name', + name: 'name', + placeholder: 'Enter the name of the channel', + description: 'A simple human readable name for the channel' + }, + { + id: 'channel-path', + type: 'text', + label: 'Root Path', + name: 'path', + placeholder: 'Enter the root path to the channel', + description: editMode?.value ? 'You may not edit the channel directory' : 'The path in your bucket to the working directory for the channel', + disabled: editMode?.value + }, + { + id: 'channel-index', + type: 'text', + label: 'Index File', + name: 'index', + placeholder: 'Enter the index file for the channel', + description: editMode?.value ? + 'You may not edit the index file path' + : 'The name or path of the post index file, stored under the root directory of the channel', + disabled: editMode?.value + }, + { + id: 'channel-content-dir', + type: 'text', + label: 'Content Directory', + name: 'content', + placeholder: 'Enter the content directory for the channel', + description: editMode?.value ? + 'You may not edit the content directory path' + : 'The name or path of the content directory, stored under the root directory of the channel', + disabled: editMode?.value + }, + { + id: 'index-file-example', + type: 'text', + label: 'Index Path', + name: 'example', + placeholder: 'Your index file path', + description: 'This is the location within your bucket where the index file will be stored', + disabled: true, + } + ] + } + }); + + const feedSchema = { + fields: [ + { + id: 'channel-feed-url', + type: 'text', + label: 'Publish Url', + name: 'url', + placeholder: 'Enter the feed url for the channel', + description: 'The rss syndication url for your blog channel, the http url your blog resides at.' + }, + { + id: 'channel-feed-path', + type: 'text', + label: 'Feed File', + name: 'path', + placeholder: 'feed.xml', + description: 'The path to the feed xml file within the channel directory' + }, + { + id: 'channel-feed-image', + type: 'text', + label: 'Image Url', + name: 'image', + placeholder: 'Enter the url for the default feed image', + description: 'The full http url to the default feed image' + }, + { + id: 'channel-feed-author', + type: 'text', + label: 'Feed Author', + name: 'author', + placeholder: 'Your name', + description: 'The author name for the feed' + }, + { + id: 'channel-feed-contact', + type: 'text', + label: 'Feed Contact', + name: 'contact', + placeholder: 'Your contact email address', + description: 'The webmaster contact email address' + }, + { + id: 'channel-feed-max-items', + type: 'number', + label: 'Feed Max Items', + name: 'maxItems', + placeholder: 'Enter the feed max items for the channel', + description: 'The maximum number of posts to publish in the feed' + }, + { + id: 'channel-feed-description', + type: 'textarea', + label: 'Feed Description', + name: 'description', + placeholder: 'Enter the feed description for the channel', + } + ] + } + + const alphaNumSpace = helpers.regex(/^[a-zA-Z0-9 ]*$/); + const httpUrl = helpers.regex(/^(http|https):\/\/[^ "]+$/); + + const channelRules = { + name: { + required: helpers.withMessage('Channel name is required', required), + maxlength: helpers.withMessage('Channel name must be less than 50 characters', maxLength(50)), + alphaNumSpace: helpers.withMessage('Channel name must be alphanumeric', alphaNumSpace), + }, + path: { + required: helpers.withMessage('Channel path is required', required), + maxlength: helpers.withMessage('Channel path must be less than 50 characters', maxLength(50)), + }, + index: { + required: helpers.withMessage('Channel index is required', required), + maxlength: helpers.withMessage('Channel index must be less than 50 characters', maxLength(50)), + }, + content: { + required: helpers.withMessage('Channel content directory is required', required), + maxlength: helpers.withMessage('Channel content directory must be less than 50 characters', maxLength(50)), + }, + example: {} + } + + const feedRules = { + url: { + required: helpers.withMessage('Channel feed url is required', required), + maxlength: helpers.withMessage('Channel feed url must be less than 100 characters', maxLength(100)), + url: helpers.withMessage('Channel feed url must be a valid url', httpUrl), + }, + path: { + required: helpers.withMessage('Channel feed path is required', required), + maxlength: helpers.withMessage('Channel feed path must be less than 50 characters', maxLength(50)), + }, + image: { + maxlength: helpers.withMessage('Channel feed image must be less than 200 characters', maxLength(200)), + }, + contact: { + maxlength: helpers.withMessage('Channel feed contact must be less than 50 characters', maxLength(50)), + }, + description: { + alphaNumSpace: helpers.withMessage('Channel feed description must be alphanumeric', alphaNumSpace), + maxlength: helpers.withMessage('Channel feed description must be less than 50 characters', maxLength(200)), + }, + maxItems: { + numeric: helpers.withMessage('Channel feed max items must be a number', numeric), + }, + author: { + alphaNumSpace: helpers.withMessage('Channel feed author must be alphanumeric', alphaNumSpace), + maxlength: helpers.withMessage('Channel feed author must be less than 50 characters', maxLength(50)), + } + } + + const getChannelValidator = (buffer: MaybeRef) => { + + const v$ = useVuelidate(channelRules, buffer, { $lazy: true, $autoDirty: true }); + + const updateExample = () => { + if (!v$.value.path.$model || !v$.value.index.$model) { + v$.value.example.$model = ''; + return; + } + //Update the example path + v$.value.example.$model = `${v$.value.path.$model}/${v$.value.index.$model}`; + } + + watch(v$, updateExample); + + updateExample(); + + const { validate } = useVuelidateWrapper(v$); + return { v$, validate, reset: v$.value.$reset }; + } + + const getFeedValidator = (buffer: MaybeRef) => { + const v$ = useVuelidate(feedRules, buffer, { $lazy: true, $autoDirty: true }); + const { validate } = useVuelidateWrapper(v$); + return { v$, validate, reset: v$.value.$reset }; + } + + return { + channelSchema, + feedSchema, + channelRules, + feedRules, + getChannelValidator, + getFeedValidator + }; +} + diff --git a/front-end/src/views/Blog/form-helpers/index.ts b/front-end/src/views/Blog/form-helpers/index.ts new file mode 100644 index 0000000..d3df0f7 --- /dev/null +++ b/front-end/src/views/Blog/form-helpers/index.ts @@ -0,0 +1,17 @@ +// 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 . + +export * from './channels' +export * from './posts' \ No newline at end of file diff --git a/front-end/src/views/Blog/form-helpers/posts.ts b/front-end/src/views/Blog/form-helpers/posts.ts new file mode 100644 index 0000000..da805e7 --- /dev/null +++ b/front-end/src/views/Blog/form-helpers/posts.ts @@ -0,0 +1,116 @@ +// 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 . + +import { MaybeRef, computed } from "vue"; +import { useVuelidateWrapper } from "@vnuge/vnlib.browser" +import { PostMeta } from '@vnuge/cmnext-admin' +import { helpers, required, maxLength } from "@vuelidate/validators" +import useVuelidate from "@vuelidate/core" + +export const getPostForm = () => { + + const schema = computed(() => { + return { + fields: [ + { + id: 'post-title', + type: 'text', + label: 'Post Title', + name: 'title', + placeholder: 'Enter the title of the post', + description: 'A simple human readable title for the post' + }, + { + id: 'post-author', + type: 'text', + label: 'Post Author', + name: 'author', + placeholder: 'Enter the author of the post', + description: 'The author of the post' + }, + { + id: 'post-tags', + type: 'text', + label: 'Post Tags', + name: 'tags', + placeholder: 'Enter the tags for the post', + description: 'A comma separated list of tags for the post' + }, + { + id: 'post-image', + type: 'text', + label: 'Post Image', + name: 'image', + placeholder: 'Enter the image url for the post', + description: 'The full http url to the post image' + }, + { + id: 'post-summary', + type: 'textarea', + label: 'Post Summary', + name: 'summary', + placeholder: 'Enter the summary of the post', + description: 'A short summary of the post, also the description for the rss feed' + }, + { + id: 'existing-post-id', + type: 'text', + label: 'Post Id', + name: 'id', + placeholder: '', + description: 'The id of the post, this cannot be changed', + disabled: true, + } + ] + } + }); + + const alphaNumSpace = helpers.regex(/^[a-zA-Z0-9 ]*$/); + const httpUrl = helpers.regex(/^(http|https):\/\/[^ "]+$/); + + const rules = { + title: { + required: helpers.withMessage('Post title is required', required), + maxlength: helpers.withMessage('Post title must be less than 50 characters', maxLength(50)), + alphaNumSpace: helpers.withMessage('Post title must be alphanumeric', alphaNumSpace), + }, + summary: { + required: helpers.withMessage('Post summary is required', required), + maxlength: helpers.withMessage('Post summary must be less than 50 characters', maxLength(200)), + }, + author: { + required: helpers.withMessage('Post author is required', required), + maxlength: helpers.withMessage('Post author must be less than 50 characters', maxLength(50)), + }, + tags: {}, + image: { + maxlength: helpers.withMessage('Post image must be less than 200 characters', maxLength(200)), + httpUrl: helpers.withMessage('Post image must be a valid http url', httpUrl), + }, + content: { + required: helpers.withMessage('Post content is required', required), + maxLength: maxLength(50000), + }, + id: {} + } + + const getValidator = (buffer: MaybeRef) => { + const v$ = useVuelidate(rules, buffer, { $lazy: true, $autoDirty: true }); + const { validate } = useVuelidateWrapper(v$); + return { v$, validate, reset: v$.value.$reset }; + } + + return { schema, rules, getValidator }; +} \ No newline at end of file diff --git a/front-end/src/views/Blog/index.vue b/front-end/src/views/Blog/index.vue new file mode 100644 index 0000000..6bfcb6e --- /dev/null +++ b/front-end/src/views/Blog/index.vue @@ -0,0 +1,324 @@ + + + + + \ No newline at end of file diff --git a/front-end/src/views/Login/components/Social.vue b/front-end/src/views/Login/components/Social.vue new file mode 100644 index 0000000..34c5a1e --- /dev/null +++ b/front-end/src/views/Login/components/Social.vue @@ -0,0 +1,57 @@ + + + \ No newline at end of file diff --git a/front-end/src/views/Login/components/Totp.vue b/front-end/src/views/Login/components/Totp.vue new file mode 100644 index 0000000..50a5be3 --- /dev/null +++ b/front-end/src/views/Login/components/Totp.vue @@ -0,0 +1,65 @@ + + + + + \ No newline at end of file diff --git a/front-end/src/views/Login/components/UserPass.vue b/front-end/src/views/Login/components/UserPass.vue new file mode 100644 index 0000000..e218cb8 --- /dev/null +++ b/front-end/src/views/Login/components/UserPass.vue @@ -0,0 +1,92 @@ + + + \ No newline at end of file diff --git a/front-end/src/views/Login/index.vue b/front-end/src/views/Login/index.vue new file mode 100644 index 0000000..f3a3f59 --- /dev/null +++ b/front-end/src/views/Login/index.vue @@ -0,0 +1,182 @@ + + + + + diff --git a/front-end/src/views/Login/pki/index.vue b/front-end/src/views/Login/pki/index.vue new file mode 100644 index 0000000..ae1a4a8 --- /dev/null +++ b/front-end/src/views/Login/pki/index.vue @@ -0,0 +1,80 @@ + + + \ No newline at end of file diff --git a/front-end/src/views/Login/social/[type].vue b/front-end/src/views/Login/social/[type].vue new file mode 100644 index 0000000..5a803bd --- /dev/null +++ b/front-end/src/views/Login/social/[type].vue @@ -0,0 +1,127 @@ + + + + + diff --git a/front-end/src/views/Register/components/CompleteReg.vue b/front-end/src/views/Register/components/CompleteReg.vue new file mode 100644 index 0000000..b8f8ef0 --- /dev/null +++ b/front-end/src/views/Register/components/CompleteReg.vue @@ -0,0 +1,116 @@ + + + + + diff --git a/front-end/src/views/Register/index.vue b/front-end/src/views/Register/index.vue new file mode 100644 index 0000000..92a5992 --- /dev/null +++ b/front-end/src/views/Register/index.vue @@ -0,0 +1,161 @@ + + + + + diff --git a/front-end/src/views/[...all].vue b/front-end/src/views/[...all].vue new file mode 100644 index 0000000..1f439fc --- /dev/null +++ b/front-end/src/views/[...all].vue @@ -0,0 +1,24 @@ + + + + + \ No newline at end of file diff --git a/front-end/src/views/index.vue b/front-end/src/views/index.vue new file mode 100644 index 0000000..07b0a09 --- /dev/null +++ b/front-end/src/views/index.vue @@ -0,0 +1,18 @@ + + + + + \ No newline at end of file -- cgit