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:
2026-04-14 15:24:34 +02:00
parent b171afba2f
commit a27173ee4a
8 changed files with 160 additions and 6 deletions

View File

@@ -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
}

View File

@@ -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;

View File

@@ -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>

View File

@@ -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;

View File

@@ -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>

View File

@@ -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);

View File

@@ -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}

View File

@@ -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;