mirror of
https://gitlab.com/Chill-Projet/chill-bundles.git
synced 2026-04-16 10:59:31 +00:00
Add IsCurrentlyEdited and CurrentlyEditingIcon components with lock display logic
- Introduced `IsCurrentlyEdited.vue` and `CurrentlyEditingIcon.vue` to display lock status and editing users with modal support. - Refactored button components (`WopiEditButton.vue`, `DesktopEditButton.vue`) to dynamically compute classes based on lock and editor states. - Updated `DocumentActionButtonsGroup.vue` to include lock-related components for enhanced editing status visibility. - Added new localization keys for lock-related messages and implemented `localizeList` helper for list formatting. - Amended `package.json` with `tsc-check` script for stricter TypeScript checks.
This commit is contained in:
@@ -87,7 +87,8 @@
|
||||
"specs-create-dir": "mkdir -p templates/api",
|
||||
"specs": "yarn run specs-create-dir && yarn run specs-build && yarn run specs-validate",
|
||||
"version": "node --version",
|
||||
"eslint": "eslint-baseline --fix \"src/**/*.{js,ts,vue}\""
|
||||
"eslint": "eslint-baseline --fix \"src/**/*.{js,ts,vue}\"",
|
||||
"tsc-check": "vue-tsc --noEmit"
|
||||
},
|
||||
"private": true
|
||||
}
|
||||
|
||||
@@ -14,8 +14,13 @@
|
||||
aria-expanded="false"
|
||||
>
|
||||
Actions
|
||||
<currently-editing-icon v-if="props.storedObject.lock !== null"></currently-editing-icon>
|
||||
</button>
|
||||
<ul class="dropdown-menu">
|
||||
<li v-if="null !== props.storedObject.lock">
|
||||
<is-currently-edited :stored-object="props.storedObject as StoredObject & {lock: StoredObjectLock}"></is-currently-edited>
|
||||
</li>
|
||||
<li v-if="null !== props.storedObject.lock"><hr class="dropdown-divider"></li>
|
||||
<li v-if="isEditableOnline">
|
||||
<wopi-edit-button
|
||||
:stored-object="props.storedObject"
|
||||
@@ -28,6 +33,7 @@
|
||||
:classes="{ 'dropdown-item': true }"
|
||||
:edit-link="props.davLink ?? ''"
|
||||
:expiration-link="props.davLinkExpiration ?? 0"
|
||||
:stored-object="props.storedObject"
|
||||
></desktop-edit-button>
|
||||
</li>
|
||||
<li v-if="isConvertibleToPdf">
|
||||
@@ -49,7 +55,7 @@
|
||||
<li v-if="isHistoryViewable">
|
||||
<history-button
|
||||
:stored-object="props.storedObject"
|
||||
:can-edit="canEdit && props.storedObject._permissions.canEdit"
|
||||
:can-edit="props.storedObject._permissions.canEdit"
|
||||
></history-button>
|
||||
</li>
|
||||
</ul>
|
||||
@@ -73,12 +79,14 @@ import {
|
||||
is_object_ready,
|
||||
} from "./StoredObjectButton/helpers";
|
||||
import {
|
||||
StoredObject,
|
||||
StoredObject, StoredObjectLock,
|
||||
StoredObjectStatusChange,
|
||||
WopiEditButtonExecutableBeforeLeaveFunction,
|
||||
} from "../types";
|
||||
import DesktopEditButton from "ChillDocStoreAssets/vuejs/StoredObjectButton/DesktopEditButton.vue";
|
||||
import HistoryButton from "ChillDocStoreAssets/vuejs/StoredObjectButton/HistoryButton.vue";
|
||||
import CurrentlyEditingIcon from "ChillDocStoreAssets/vuejs/StoredObjectButton/CurrentlyEditingIcon.vue";
|
||||
import IsCurrentlyEdited from "ChillDocStoreAssets/vuejs/StoredObjectButton/IsCurrentlyEdited.vue";
|
||||
|
||||
interface DocumentActionButtonsGroupConfig {
|
||||
storedObject: StoredObject;
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
<script setup lang="ts">
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16" class="icon-pen-fill">
|
||||
<defs>
|
||||
<linearGradient id="pen-grad" gradientUnits="userSpaceOnUse" x1="-16" y1="0" x2="0" y2="0">
|
||||
<stop offset="0%" stop-color="#334D5C"/>
|
||||
<stop offset="40%" stop-color="#334D5C"/>
|
||||
<stop offset="40%" stop-color="#43B29D"/>
|
||||
<stop offset="50%" stop-color="#43B29D"/>
|
||||
<stop offset="50%" stop-color="#328474"/>
|
||||
<stop offset="60%" stop-color="#328474"/>
|
||||
<stop offset="60%" stop-color="#EEC84A"/>
|
||||
<stop offset="70%" stop-color="#EEC84A"/>
|
||||
<stop offset="70%" stop-color="#E2793D"/>
|
||||
<stop offset="80%" stop-color="#E2793D"/>
|
||||
<stop offset="80%" stop-color="#DF4949"/>
|
||||
<stop offset="90%" stop-color="#DF4949"/>
|
||||
<stop offset="90%" stop-color="#cabb9f"/>
|
||||
<stop offset="100%" stop-color="#cabb9f"/>
|
||||
<stop offset="100%" stop-color="#334D5C"/>
|
||||
<animateTransform attributeName="gradientTransform"
|
||||
type="translate"
|
||||
from="0 0" to="32 32"
|
||||
dur="1.5s"
|
||||
repeatCount="indefinite"/>
|
||||
</linearGradient>
|
||||
</defs>
|
||||
|
||||
<path fill="url(#pen-grad)" d="m13.498.795.149-.149a1.207 1.207 0 1 1 1.707 1.708l-.149.148a1.5 1.5 0 0 1-.059 2.059L4.854 14.854a.5.5 0 0 1-.233.131l-4 1a.5.5 0 0 1-.606-.606l1-4a.5.5 0 0 1 .131-.232l9.642-9.642a.5.5 0 0 0-.642.056L6.854 4.854a.5.5 0 1 1-.708-.708L9.44.854A1.5 1.5 0 0 1 11.5.796a1.5 1.5 0 0 1 1.998-.001"/>
|
||||
</svg>
|
||||
</template>
|
||||
|
||||
<style scoped lang="scss">
|
||||
|
||||
</style>
|
||||
@@ -37,7 +37,7 @@
|
||||
</template>
|
||||
</modal>
|
||||
</teleport>
|
||||
<a :class="props.classes" @click="state.modalOpened = true">
|
||||
<a :class="classesComputed()" @click="state.modalOpened = true">
|
||||
<i class="fa fa-desktop"></i>
|
||||
Éditer sur le bureau
|
||||
</a>
|
||||
@@ -55,11 +55,13 @@ i.fa::before {
|
||||
<script setup lang="ts">
|
||||
import Modal from "ChillMainAssets/vuejs/_components/Modal.vue";
|
||||
import { computed, reactive } from "vue";
|
||||
import {StoredObject} from "ChillDocStoreAssets/types";
|
||||
|
||||
export interface DesktopEditButtonConfig {
|
||||
editLink: string;
|
||||
classes: Record<string, boolean>;
|
||||
expirationLink: number | Date;
|
||||
storedObject: StoredObject;
|
||||
}
|
||||
|
||||
interface DesktopEditButtonState {
|
||||
@@ -74,6 +76,19 @@ const buildCommand = computed<string>(
|
||||
() => "vnd.libreoffice.command:ofe|u|" + props.editLink,
|
||||
);
|
||||
|
||||
function classesComputed(): Record<string, boolean> {
|
||||
const cl = props.classes;
|
||||
cl['btn'] = true;
|
||||
|
||||
if (false === props.storedObject._editor?.webdav) {
|
||||
cl['disabled'] = true;
|
||||
} else {
|
||||
cl['disabled'] = false;
|
||||
}
|
||||
|
||||
return cl;
|
||||
}
|
||||
|
||||
const editionUntilFormatted = computed<string>(() => {
|
||||
let d;
|
||||
|
||||
|
||||
@@ -0,0 +1,61 @@
|
||||
<script setup lang="ts">
|
||||
|
||||
import {StoredObject, StoredObjectLock} from "ChillDocStoreAssets/types";
|
||||
import CurrentlyEditingIcon from "ChillDocStoreAssets/vuejs/StoredObjectButton/CurrentlyEditingIcon.vue";
|
||||
import Modal from "ChillMainAssets/vuejs/_components/Modal.vue";
|
||||
import {computed, reactive} from "vue";
|
||||
import {localizeList} from "ChillMainAssets/lib/localizationHelper/localizationHelper";
|
||||
import {User} from "ChillMainAssets/types";
|
||||
import {STORED_OBJECT_LOCK_IS_CURRENTLY_EDITED,
|
||||
STORED_OBJECT_LOCK_IS_CURRENTLY_EDITED_WITHOUT_USERS,
|
||||
STORED_OBJECT_LOCK_EDITING_SINCE,
|
||||
STORED_OBJECT_LOCK_LIST_OF_USERS_MAY_BE_INCOMPLETE,
|
||||
trans} from "translator";
|
||||
import {ISOToDatetime} from "ChillMainAssets/chill/js/date";
|
||||
|
||||
interface IsCurrentlyEditedConfig {
|
||||
storedObject: StoredObject & {lock: StoredObjectLock}
|
||||
}
|
||||
|
||||
const props = defineProps<IsCurrentlyEditedConfig>();
|
||||
|
||||
const state = reactive({
|
||||
modalOpened: false,
|
||||
});
|
||||
|
||||
const sinceDate = computed<Date|null>(() => ISOToDatetime(props.storedObject.lock.createdAt.datetime))
|
||||
|
||||
const onModalClose = function() {
|
||||
state.modalOpened = false;
|
||||
}
|
||||
|
||||
const openModal = function() {
|
||||
state.modalOpened = true;
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<teleport to="body">
|
||||
<modal v-if="state.modalOpened" @close="onModalClose">
|
||||
<template v-slot:body>
|
||||
<div class="modal-currently-edit-content">
|
||||
<p v-if="props.storedObject.lock.users.length > 0">{{ trans(STORED_OBJECT_LOCK_IS_CURRENTLY_EDITED, {byUsers: localizeList(props.storedObject.lock.users.map((u: User) => u.label))} ) }}.<template v-if="props.storedObject.lock.method === 'wopi'"> {{ trans(STORED_OBJECT_LOCK_LIST_OF_USERS_MAY_BE_INCOMPLETE) }}</template></p>
|
||||
<p v-else>{{ trans(STORED_OBJECT_LOCK_IS_CURRENTLY_EDITED_WITHOUT_USERS) }}</p>
|
||||
<p v-if="sinceDate !== null">{{ trans(STORED_OBJECT_LOCK_EDITING_SINCE, {since: sinceDate}) }}</p>
|
||||
</div>
|
||||
</template>
|
||||
</modal>
|
||||
</teleport>
|
||||
<button class="dropdown-item" type="button" @click="openModal()">
|
||||
<span class="currently-edited"><currently-editing-icon></currently-editing-icon></span>
|
||||
<span v-if="props.storedObject.lock.users.length > 0">{{ trans(STORED_OBJECT_LOCK_IS_CURRENTLY_EDITED, {byUsers: localizeList([props.storedObject.lock.users[0].label]) } ) }}<span v-if="props.storedObject.lock.users.length > 1"> (+{{ props.storedObject.lock.users.length - 1 }})</span></span>
|
||||
<span v-else>{{ trans(STORED_OBJECT_LOCK_IS_CURRENTLY_EDITED_WITHOUT_USERS) }}</span>
|
||||
</button>
|
||||
</template>
|
||||
|
||||
<style scoped lang="scss">
|
||||
span.currently-edited {
|
||||
margin-right: 0.15rem;
|
||||
}
|
||||
</style>
|
||||
@@ -1,8 +1,9 @@
|
||||
<template>
|
||||
<a
|
||||
:class="Object.assign(props.classes, { btn: true })"
|
||||
:class="classesComputed()"
|
||||
@click="beforeLeave($event)"
|
||||
:href="build_wopi_editor_link(props.storedObject.uuid, props.returnPath)"
|
||||
:aria-disabled="!props.storedObject._editor?.wopi"
|
||||
>
|
||||
<i class="fa fa-paragraph"></i>
|
||||
Editer en ligne
|
||||
@@ -10,7 +11,6 @@
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import WopiEditButton from "./WopiEditButton.vue";
|
||||
import { build_wopi_editor_link } from "./helpers";
|
||||
import {
|
||||
StoredObject,
|
||||
@@ -28,6 +28,19 @@ const props = defineProps<WopiEditButtonConfig>();
|
||||
|
||||
let executed = false;
|
||||
|
||||
function classesComputed(): Record<string, boolean> {
|
||||
const cl = props.classes;
|
||||
cl['btn'] = true;
|
||||
|
||||
if (false === props.storedObject._editor?.wopi) {
|
||||
cl['disabled'] = true;
|
||||
} else {
|
||||
cl['disabled'] = false;
|
||||
}
|
||||
|
||||
return cl;
|
||||
}
|
||||
|
||||
async function beforeLeave(event: Event): Promise<true> {
|
||||
if (props.executeBeforeLeave === undefined || executed === true) {
|
||||
return Promise.resolve(true);
|
||||
|
||||
@@ -9,3 +9,11 @@ workflow:
|
||||
doc_shared_automatically_at_explanation: >-
|
||||
Le document a été partagé avec vous le {at, date, long} à {at, time, short}
|
||||
|
||||
stored_object:
|
||||
lock:
|
||||
is_currently_edited: Le document est en cours d'édition par {byUsers}
|
||||
is_currently_edited_without_users: Le document est en cours d'édition
|
||||
list_of_users_may_be_incomplete: La liste des utilisateurs mentionné peut être incomplète.
|
||||
editing_since: >
|
||||
L'édition a débuté le {since, date, long} à {since, time, short}
|
||||
|
||||
|
||||
@@ -102,4 +102,14 @@ export function localizeDateTimeFormat(
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Format a list into the locale, using the Intl.ListFormat.
|
||||
*
|
||||
* This method is a stub while PHP supports the formatting of list using ICU.
|
||||
*/
|
||||
export function localizeList(list: Iterable<string>, options?: Intl.ListFormatOptions): string {
|
||||
const formatter = new Intl.ListFormat(getLocale(), options);
|
||||
return formatter.format(list);
|
||||
}
|
||||
|
||||
export default datetimeFormats;
|
||||
|
||||
Reference in New Issue
Block a user