чат по заявке через комментарии к строке списка (#14)
Tests / frontend-test (push) Successful in 1m15s
Tests / frontend-test (push) Successful in 1m15s
Обсуждение заявки поверх штатных комментариев SharePoint (PnPjs item.comments) — без новых колонок и провижининга, права на переписку раздаёт сам SharePoint по правам на элемент. Встроено в ApproveFeature и EditFeature (единственные входы в просмотр заявки для всех типов), поэтому доступно автору, всем согласующим (включая уже решивших и ещё не дошедших до своего шага) и исполнителям, а не только текущему шагу маршрута, как DecisionPanel. Автора комментария всегда ставит реальная учётка — отладочная подмена личности на запись в SharePoint не влияет, в UI есть предупреждение. --------- Co-authored-by: gorbenko.pavel <gorbenko.pavel@tansu.kz> Reviewed-on: http://git.tnsu.kz/timur.gorodetskiy/request-portal/pulls/14
This commit was merged in pull request #14.
This commit is contained in:
@@ -0,0 +1,77 @@
|
||||
/**
|
||||
* Имена файлов чата. Модуль чистый (без SharePoint) и покрыт тестами: от
|
||||
* формата имени зависит и то, что файл вообще запишется, и то, каким он
|
||||
* покажется в переписке.
|
||||
*
|
||||
* Имя в библиотеке: «<метка времени>-<номер>__<исходное имя>», например
|
||||
* «20260824-153012-1__акт приёмки.pdf». Префикс нужен, чтобы два человека
|
||||
* (или один дважды) могли прикрепить к одной заявке файлы с одинаковым
|
||||
* названием — без него второй файл перезаписал бы первый.
|
||||
*/
|
||||
|
||||
/** Разделитель служебного префикса и исходного имени. */
|
||||
const SEPARATOR = '__';
|
||||
|
||||
/**
|
||||
* Запрещённые SharePoint символы в имени файла (" * : < > ? / \ |), плюс # % ~:
|
||||
* формально они допустимы в SPO, но ломают прямые ссылки на файл.
|
||||
*/
|
||||
const FORBIDDEN_CHARS = /["*:<>?/\\|#%~]/g;
|
||||
|
||||
/**
|
||||
* Предел длины исходной части имени. Общий предел пути в SharePoint — 400
|
||||
* символов, и в него входят адрес сайта, имя библиотеки и папка заявки;
|
||||
* 120 символов на само имя оставляют запас и при этом не режут нормальные
|
||||
* названия документов.
|
||||
*/
|
||||
const MAX_NAME_LENGTH = 120;
|
||||
|
||||
function splitExtension(name: string): { base: string; extension: string } {
|
||||
const dot = name.lastIndexOf('.');
|
||||
// Точка в начале — это не расширение, а имя вида «.gitignore».
|
||||
if (dot <= 0) return { base: name, extension: '' };
|
||||
return { base: name.slice(0, dot), extension: name.slice(dot) };
|
||||
}
|
||||
|
||||
/**
|
||||
* Привести имя файла к тому, что SharePoint примет: убрать запрещённые символы
|
||||
* и обрезать слишком длинное имя, СОХРАНИВ расширение (без него файл не
|
||||
* откроется нужным приложением).
|
||||
*/
|
||||
export function sanitizeFileName(name: string): string {
|
||||
const cleaned = name.replace(FORBIDDEN_CHARS, '_').replace(/\s+/g, ' ').trim();
|
||||
// Имя, оканчивающееся точкой, SharePoint тоже не принимает.
|
||||
const withoutTrailingDots = cleaned.replace(/\.+$/, '');
|
||||
if (!withoutTrailingDots) return 'файл';
|
||||
|
||||
if (withoutTrailingDots.length <= MAX_NAME_LENGTH) return withoutTrailingDots;
|
||||
|
||||
const { base, extension } = splitExtension(withoutTrailingDots);
|
||||
const room = MAX_NAME_LENGTH - extension.length;
|
||||
// Расширение длиннее всего лимита — вырожденный случай, режем как есть.
|
||||
if (room <= 0) return withoutTrailingDots.slice(0, MAX_NAME_LENGTH);
|
||||
return `${base.slice(0, room)}${extension}`;
|
||||
}
|
||||
|
||||
function pad2(value: number): string {
|
||||
return value < 10 ? `0${value}` : `${value}`;
|
||||
}
|
||||
|
||||
/** Метка времени для префикса: «20260824-153012». */
|
||||
export function fileStamp(date: Date): string {
|
||||
const day = `${date.getFullYear()}${pad2(date.getMonth() + 1)}${pad2(date.getDate())}`;
|
||||
const time = `${pad2(date.getHours())}${pad2(date.getMinutes())}${pad2(date.getSeconds())}`;
|
||||
return `${day}-${time}`;
|
||||
}
|
||||
|
||||
/** Имя, под которым файл ляжет в библиотеку. index — порядок файла в сообщении. */
|
||||
export function storedFileName(stamp: string, index: number, originalName: string): string {
|
||||
return `${stamp}-${index}${SEPARATOR}${sanitizeFileName(originalName)}`;
|
||||
}
|
||||
|
||||
/** Исходное имя из складского: то, что видит пользователь в переписке. */
|
||||
export function displayFileName(storedName: string): string {
|
||||
const at = storedName.indexOf(SEPARATOR);
|
||||
// Разделителя нет — файл положили не порталом; показываем имя как есть.
|
||||
return at < 0 ? storedName : storedName.slice(at + SEPARATOR.length);
|
||||
}
|
||||
Reference in New Issue
Block a user