Skip to content

Commit

Permalink
- ADD: Added admin HTML editor for home and about page.
Browse files Browse the repository at this point in the history
-
  • Loading branch information
sebastian-raubach committed Dec 11, 2024
1 parent 620e98f commit c1e55dd
Show file tree
Hide file tree
Showing 7 changed files with 437 additions and 108 deletions.
364 changes: 264 additions & 100 deletions package-lock.json

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@
"vue-sidebar-menu": "~4.8.1",
"vue-typeahead-bootstrap": "~2.12.0",
"vue-upload-component": "~2.8.22",
"vue2-editor": "^2.10.3",
"vue2-leaflet": "~2.7.1",
"vuedraggable": "^2.24.3",
"vuex": "~3.6.2",
Expand Down
142 changes: 142 additions & 0 deletions src/components/util/HtmlTemplateEditor.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
<template>
<div v-if="i18nKey">
<b-checkbox v-if="isAdmin" switch v-model="editingEnabled">{{ $t('buttonEdit') }}</b-checkbox>

<div v-if="isAdmin && editingEnabled" class="mb-3">
<VueEditor
v-model="text"
:editorToolbar="customToolbar"
useCustomImageHandler
@image-added="handleImageAdded"
@image-removed="handleImageRemoved" />

<b-button @click="save" variant="success"><MdiIcon :path="mdiContentSave" /> {{ $t('buttonSubmit') }}</b-button>
</div>
<p v-html="text" v-else />
</div>
</template>

<script>
import { mapGetters } from 'vuex'
import { mdiContentSave } from '@mdi/js'
import MdiIcon from '@/components/icons/MdiIcon'
import { VueEditor, Quill } from 'vue2-editor'
import { apiDeleteTemplateImageByName, apiPatchTemplateI18n, apiPostTemplateImage } from '@/mixins/api/misc'
import { USER_TYPE_ADMINISTRATOR, userIsAtLeast } from '@/mixins/api/auth'
import Vue from 'vue'
import { getImageUrl } from '@/mixins/image'
// Make sure images added use bootstrap's 'img-fluid' class
const Image = Quill.import('formats/image')
Image.className = 'img-fluid'
Quill.register(Image, true)
export default {
components: {
MdiIcon,
VueEditor
},
props: {
i18nKey: {
type: String,
default: null
}
},
beforeRouteLeave: function (to, from, next) {
if (this.isAdmin && this.editingEnabled) {
// Ask for confirmation. check this isn't a navigation initiated by finalising the trial setup
this.$bvModal.msgBoxConfirm(this.$t('modalTextHtmlEditorLeaveSure'), {
title: this.$t('modalTitleSure'),
okVariant: 'danger',
okTitle: this.$t('genericYes'),
cancelTitle: this.$t('genericNo')
})
.then(value => {
if (value) {
next()
}
})
}
},
computed: {
...mapGetters([
'storeLocale',
'storeToken'
]),
isAdmin: function () {
return this.storeToken && userIsAtLeast(this.storeToken.userType, USER_TYPE_ADMINISTRATOR)
}
},
data: function () {
return {
mdiContentSave,
text: '',
editingEnabled: false,
customToolbar: [
['bold', 'italic', 'underline'],
[{ list: 'ordered' }, { list: 'bullet' }],
[{ script: 'sub' }, { script: 'super' }],
[{ header: [1, 2, 3, 4, 5, 6, false] }],
[{ align: [] }],
['link', 'image']
],
imageMapping: {}
}
},
watch: {
'$i18n.locale': {
handler: function () {
this.text = this.$t(this.i18nKey)
},
immediate: true
}
},
methods: {
handleImageRemoved: function (file, Editor, cursorLocation) {
const start = file.indexOf('src/') + 4
const end = file.indexOf('?')
const name = file.substring(start, end)
apiDeleteTemplateImageByName(name, () => {
console.log('success')
})
},
handleImageAdded: function (file, Editor, cursorLocation, resetUploader) {
const formData = new FormData()
formData.append('image', file)
console.log(file)
apiPostTemplateImage(formData, result => {
Vue.set(this.imageMapping, file.name, result)
Editor.insertEmbed(cursorLocation, 'image', getImageUrl(result, {
name: result,
type: 'template',
size: 'large'
}))
resetUploader()
})
},
save: function () {
this.$bvModal.msgBoxConfirm(this.$t('modalTitleSure'), {
okVariant: 'success',
okTitle: this.$t('genericYes'),
cancelTitle: this.$t('genericNo')
})
.then(value => {
if (value) {
const payload = {}
payload[this.i18nKey] = this.text
apiPatchTemplateI18n(payload, this.storeLocale, () => {
this.$router.go(0)
})
}
})
}
}
}
</script>
8 changes: 7 additions & 1 deletion src/mixins/api/misc.js
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,8 @@ const apiPostTemplateCarouselConfig = (data, onSuccess, onError) => authAxios({

const apiPatchTemplateAboutConfig = (data, onSuccess, onError) => authAxios({ url: 'settings/about', method: 'PATCH', data: data, success: onSuccess, error: onError })

const apiPatchTemplateI18n = (data, locale, onSuccess, onError) => authAxios({ url: `settings/template/i18n/${locale}`, method: 'PATCH', data: data, success: onSuccess, error: onError })

const apiPatchImage = (image, onSuccess, onError) => authAxios({ url: `image/${image.imageId}`, method: 'PATCH', data: image, success: onSuccess, error: onError })

const apiPostNewsTable = (queryData, onSuccess, onError) => {
Expand Down Expand Up @@ -152,6 +154,8 @@ const apiPostStoryUpload = (formData, onSuccess, onError) => authForm({ url: 'st

const apiPostStoryStepUpload = (storyId, formData, onSuccess, onError) => authForm({ url: `story/${storyId}/step`, formData: formData, success: onSuccess, error: onError })

const apiPostTemplateImage = (formData, onSuccess, onError) => authForm({ url: 'image/upload/template', formData: formData, success: onSuccess, error: onError })

const apiDeleteStoryStep = (storyId, storyStepId, onSuccess, onError) => authAxios({ url: `story/${storyId}/step/${storyStepId}`, method: 'DELETE', success: onSuccess, error: onError })

export {
Expand Down Expand Up @@ -209,5 +213,7 @@ export {
apiPostStoryUpload,
apiPostStoryStepUpload,
apiPatchStory,
apiDeleteStoryStep
apiDeleteStoryStep,
apiPatchTemplateI18n,
apiPostTemplateImage
}
1 change: 1 addition & 0 deletions src/plugins/i18n/en_GB.json
Original file line number Diff line number Diff line change
Expand Up @@ -550,6 +550,7 @@
"modalButtonSelectGermplasmLocationSelectFromTable": "Select existing",
"modalButtonSelectGermplasmLocationSelectOnMap": "Select on map",
"modalTextFielDBookColumnMapping": "Use this section to map the columns in the input file with their corresponding purpose, i.e. select the column containing the germplasm identifier information in the 'Germplasm' dropdown.",
"modalTextHtmlEditorLeaveSure": "Are you sure you want to leave this page? Any unsaved changes to the editor will be lost.",
"operatorsAnd": "And",
"operatorsOr": "Or",
"page404Text": "It might be best to start over or go back.",
Expand Down
12 changes: 8 additions & 4 deletions src/views/HomeView.vue
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,8 @@

<!-- Heading and welcome text -->
<h1>{{ $t('pageDashboardTitle') }}</h1>
<p v-html="$t('pageDashboardText')" />

<HtmlTemplateEditor i18nKey="pageDashboardText" />

<!-- Publications -->
<div v-if="showPublicationSection && (showPublications || (storeToken && userIsAtLeast(storeToken.userType, USER_TYPE_DATA_CURATOR)))" class="mb-4">
Expand Down Expand Up @@ -47,11 +48,12 @@ import ImageCarousel from '@/components/images/ImageCarousel'
import MdiIcon from '@/components/icons/MdiIcon'
import NewsSection from '@/components/news/NewsSection'
import PublicationsWidget from '@/components/util/PublicationsWidget'
import HtmlTemplateEditor from '@/components/util/HtmlTemplateEditor'
import { apiGetOverviewStats } from '@/mixins/api/stats'
import { getNumberWithSuffix } from '@/mixins/formatting'
import { statCategories } from '@/mixins/types'
import { userIsAtLeast, USER_TYPE_DATA_CURATOR } from '@/mixins/api/auth'
import { userIsAtLeast, USER_TYPE_DATA_CURATOR, USER_TYPE_ADMINISTRATOR } from '@/mixins/api/auth'
import { mdiPresentationPlay } from '@mdi/js'
Expand All @@ -64,7 +66,8 @@ export default {
ImageCarousel,
MdiIcon,
NewsSection,
PublicationsWidget
PublicationsWidget,
HtmlTemplateEditor
},
data: function () {
return {
Expand All @@ -78,7 +81,8 @@ export default {
operator: 'and',
values: [1]
}],
USER_TYPE_DATA_CURATOR
USER_TYPE_DATA_CURATOR,
USER_TYPE_ADMINISTRATOR
}
},
computed: {
Expand Down
17 changes: 14 additions & 3 deletions src/views/about/AboutProject.vue
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@
<div>
<h1>{{ $t('pageAboutProjectTitle') }}</h1>
<hr />
<p v-html="$t('pageAboutProjectText')"/>

<HtmlTemplateEditor i18nKey="pageAboutProjectText" />

<template v-if="(groupedItems && groupedItems.length > 0) || isAdmin">
<h3>{{ $t('pageAboutProjectPartnersTitle') }}</h3>
Expand All @@ -13,15 +14,17 @@
<draggable v-model="group.items" group="partners" tag="b-row" handle=".drag-handle" class="mt-3">
<b-col cols=12 lg=6 class="mb-3 col-xxl-4" v-for="(item, itemIndex) in group.items" :key="`about-item-${itemIndex}`">
<b-card no-body class="h-100">
<b-row no-gutters class="h-100">
<b-row no-gutters class="h-100 position-relative">
<b-form-checkbox class="editing-switch" switch v-model="item.isEditing" v-if="isAdmin" />
<b-col md=4 class="d-flex align-items-center justify-content-center bg-light">
<b-card-img class="about-card-image p-4" :src="`${storeBaseUrl}image/src/?name=${item.image}&type=template`" alt="Image"></b-card-img>
</b-col>
<b-col md=8>
<b-card-body class="d-flex flex-column justify-content-between align-items-start h-100">
<div class="w-100">
<div class="d-flex flex-row justify-content-between align-items-center mb-3">
<b-card-title class="mb-0">{{ item.name }}</b-card-title>
<b-form-input v-model="item.name" v-if="isAdmin && item.isEditing" />
<b-card-title class="mb-0" v-else>{{ item.name }}</b-card-title>
<MdiIcon className="drag-handle" :path="mdiDrag" v-if="isAdmin" />
</div>
<b-card-text class="text-muted" v-if="item.description">{{ item.description }}</b-card-text>
Expand Down Expand Up @@ -54,6 +57,7 @@ import { apiGetTemplateAboutConfig, apiPatchTemplateAboutConfig } from '@/mixins
import { userIsAtLeast, USER_TYPE_ADMINISTRATOR } from '@/mixins/api/auth'
import { mdiDrag, mdiPlusBox, mdiContentSave, mdiDelete, mdiOpenInNew } from '@mdi/js'
import HtmlTemplateEditor from '@/components/util/HtmlTemplateEditor'
import AddAboutPartnerModal from '@/components/modals/AddAboutPartnerModal'
import MdiIcon from '@/components/icons/MdiIcon'
import draggable from 'vuedraggable'
Expand All @@ -62,6 +66,7 @@ const NO_GROUP = '--NO_GROUP--'
export default {
components: {
HtmlTemplateEditor,
AddAboutPartnerModal,
MdiIcon,
draggable
Expand Down Expand Up @@ -130,6 +135,7 @@ export default {
if (result) {
result.forEach(i => {
const groupName = i.group || NO_GROUP
i.isEditing = false
const match = groups.find(g => g.name === groupName)
Expand Down Expand Up @@ -173,4 +179,9 @@ export default {
max-width: 300px;
max-height: 300px;
}
.editing-switch {
position: absolute;
bottom: 1em;
right: 1em;
}
</style>

0 comments on commit c1e55dd

Please sign in to comment.