diff options
| author | Marcus Eibrink-Lunzenauer <lunzenauer@elan-ev.de> | 2025-05-06 15:41:54 +0000 |
|---|---|---|
| committer | Marcus Eibrink-Lunzenauer <lunzenauer@elan-ev.de> | 2025-05-06 15:41:54 +0000 |
| commit | 298cdf462bcc4605fba1cacc44ac40e5e0827113 (patch) | |
| tree | 4528d15195711f1d2f5eae29ee3dca23c1ce4311 /resources/vue/apps/WikiEditor.vue | |
| parent | 6592d6f08f849e1cac0e9866a9957070b557a947 (diff) | |
Move all `VueApp::create` related Vue apps to own directory.
Closes #5572
Merge request studip/studip!4194
Diffstat (limited to 'resources/vue/apps/WikiEditor.vue')
| -rw-r--r-- | resources/vue/apps/WikiEditor.vue | 287 |
1 files changed, 287 insertions, 0 deletions
diff --git a/resources/vue/apps/WikiEditor.vue b/resources/vue/apps/WikiEditor.vue new file mode 100644 index 0000000..1191c1a --- /dev/null +++ b/resources/vue/apps/WikiEditor.vue @@ -0,0 +1,287 @@ +<template> + <div> + <ContentBar isContentBar icon="wiki" :toc="toc"> + <template #info-text> + {{ $gettext('Zuletzt gespeichert') }}: + <studip-date-time :timestamp="Math.floor(lastSaveDate / 1000)" :relative="true" /> + </template> + <template #breadcrumb-list><content-bar-breadcrumbs :toc="toc" /></template> + </ContentBar> + <form :action="saveUrl" method="post" class="default" v-show="isEditing"> + <input type="hidden" :name="csrf.name" :value="csrf.value" /> + + <textarea + class="wiki-editor size-l" + ref="wiki_editor" + data-editor="extraPlugins=WikiLink" + name="content" + v-model="content" + ></textarea> + + <div></div> + <label> + <input type="checkbox" v-model="autosave" :disabled="storingAutosave"> + {{ $gettext('Automatisches Speichern aktivieren.') }} + </label> + <p class="last-save-date"> + {{ $gettext('Zuletzt gespeichert') }}: + <studip-date-time :timestamp="Math.floor(lastSaveDate / 1000)" :relative="true"></studip-date-time> + </p> + + <footer data-dialog-button=""> + <button + class="button" + :title=" + isChanged + ? $gettext('Den aktuellen Stand speichern.') + : $gettext('Der aktuelle Stand wurde bereits gespeichert.') + " + @click="toggleSecurityHandler(false)" + > + {{ $gettext('Speichern') }} + </button> + <a :href="cancelUrl" class="button"> + {{ $gettext('Verlassen') }} + </a> + <button + v-for="user in requestingUsers" + :key="user.user_id" + @click.prevent="delegateEditMode(user.user_id)" + class="button" + > + {{ $gettext('Schreibmodus an %{name} übergeben', { name: user.fullname }, true) }} + </button> + </footer> + </form> + + <div v-if="!isEditing"> + <div v-html="content"></div> + <div data-dialog-button> + <button + class="button" + v-if="!editingWasRequested" + :title="$gettext('Beantragen Sie, dass Sie den Text jetzt bearbeiten wollen.')" + @click.prevent="applyEditing()" + > + {{ $gettext('Bearbeiten beantragen') }} + </button> + <button + class="cancel button" + v-else + :title="$gettext('Klicken Sie, um die Anfrage zum Bearbeiten abzubrechen')" + @click.prevent="cancelApplyEditing()" + > + {{ $gettext('Bearbeiten beantragt') }} + </button> + <a :href="cancelUrl" class="button"> + {{ $gettext('Verlassen') }} + </a> + </div> + </div> + + <wiki-editor-online-users :users="onlineUsers"></wiki-editor-online-users> + </div> +</template> +<script> +import WikiEditorOnlineUsers from '@/vue/components/WikiEditorOnlineUsers.vue'; +import StudipDateTime from '@/vue/components/StudipDateTime.vue'; +import JSUpdater from '@/assets/javascripts/lib/jsupdater'; +import ContentBar from '@/vue/components/ContentBar.vue'; +import ContentBarBreadcrumbs from '@/vue/components/ContentBarBreadcrumbs.vue'; +import { markRaw } from 'vue'; + +export default { + name: 'wiki-editor', + components: { ContentBarBreadcrumbs, ContentBar, StudipDateTime, WikiEditorOnlineUsers }, + props: { + cancelUrl: { + type: String, + required: true, + }, + chdate: { + type: String, + required: true, + }, + editing: { + type: Boolean, + default: true, + }, + enableAutosave: { + type: Boolean, + default: false, + }, + offlineThreshold: { + type: Number, + default: 60 * 1000, + }, + pageContent: { + type: String, + default: '', + }, + pageId: { + type: Number, + required: true, + }, + saveUrl: { + type: String, + required: true, + }, + users: { + type: Array, + default: () => [], + }, + toc: { + type: Object, + required: true, + }, + }, + data() { + return { + autosave: this.enableAutosave, + content: this.pageContent, + editor: null, + isChanged: false, + isEditing: this.editing, + lastFocussedDate: null, + lastSaveDate: new Date(this.chdate), + onlineUsers: this.users, + storingAutosave: false, + }; + }, + computed: { + csrf() { + return STUDIP.CSRF_TOKEN; + }, + editingWasRequested() { + return this.onlineUsers.filter((u) => u.user_id === STUDIP.USER_ID).some((u) => u.editing_request); + }, + isOnlineAndEditing() { + return this.isEditing && new Date() - this.lastFocussedDate < this.offlineThreshold; + }, + requestingUsers() { + return this.onlineUsers + .filter((u) => u.editing_request) + .sort((a, b) => a.fullname.localeCompare(b.fullname)); + }, + }, + methods: { + applyEditing() { + const url = STUDIP.URLHelper.getURL(`dispatch.php/course/wiki/apply_editing/${this.pageId}`); + $.post(url).done((output) => { + if (output.me_online.editing > 0) { + this.isEditing = true; + this.focusEditor(); + } + this.onlineUsers = output.users; + }); + }, + cancelApplyEditing() { + const url = STUDIP.URLHelper.getURL(`dispatch.php/course/wiki/cancel_apply_editing/${this.pageId}`); + $.post(url).done((output) => { + this.onlineUsers = output.users; + }); + }, + delegateEditMode(user_id) { + const url = STUDIP.URLHelper.getURL( + `dispatch.php/course/wiki/delegate_edit_mode/${this.pageId}/${user_id}`, + ); + $.post(url).done(() => { + this.isEditing = false; + }); + }, + focusEditor() { + this.$nextTick(() => { + this.editor.editing.view.focus(); + }); + }, + getUpdaterData() { + if (this.editor.editing.view.document.isFocused) { + this.lastFocussedDate = new Date(); + } + + const data = { + id: this.pageId, + online: this.isOnlineAndEditing, + }; + + if (this.autosave && this.isChanged) { + data.content = this.editor.getData(); + this.isChanged = false; + } + + return data; + }, + toggleSecurityHandler(state = true) { + if (state) { + window.addEventListener('beforeunload', this.securityHandler); + } else { + window.removeEventListener('beforeunload', this.securityHandler); + } + }, + securityHandler(event) { + event.preventDefault(); + + event.returnValue = true; + }, + }, + mounted() { + const textarea = this.$refs['wiki_editor']; + + STUDIP.wysiwyg.replace(textarea).then((editor) => { + editor.model.document.on('change:data', () => { + this.isChanged = editor.getData() !== this.content; + }); + + if (this.isEditing) { + this.focusEditor(); + } + + this.editor = markRaw(editor); + }); + + JSUpdater.register( + 'wiki_editor_status', + (content) => { + this.onlineUsers = content.users; + this.isEditing = content.editing; + + if ('chdate' in content) { + this.lastSaveDate = new Date(content.chdate); + } + + if ('content' in content) { + this.content = content.content; + } + + if (!this.isEditing && 'wysiwyg' in content) { + this.editor.setData(content.wysiwyg); + } + }, + () => this.getUpdaterData(), + ); + }, + watch: { + isChanged(current) { + this.toggleSecurityHandler(current); + }, + autosave(current) { + this.storingAutosave = true; + + const id = [STUDIP.USER_ID, 'WIKI_ENABLE_AUTOSAVE'].join('_'); + + const data = { + id, + type: 'config-values', + attributes: { value: current } + }; + + STUDIP.jsonapi.withPromises() + .patch(`config-values/${id}`, { data: { data } }) + .then(response => { + this.autosave = response.data.attributes.value; + this.storingAutosave = false; + }); + } + }, +}; +</script> |
