aboutsummaryrefslogtreecommitdiff
path: root/resources/vue/apps/WikiEditor.vue
diff options
context:
space:
mode:
authorMarcus Eibrink-Lunzenauer <lunzenauer@elan-ev.de>2025-05-06 15:41:54 +0000
committerMarcus Eibrink-Lunzenauer <lunzenauer@elan-ev.de>2025-05-06 15:41:54 +0000
commit298cdf462bcc4605fba1cacc44ac40e5e0827113 (patch)
tree4528d15195711f1d2f5eae29ee3dca23c1ce4311 /resources/vue/apps/WikiEditor.vue
parent6592d6f08f849e1cac0e9866a9957070b557a947 (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.vue287
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>