Эта страница создана, чтобы собрать воедино информацию по конфигуратору Yougile и иметь базу данных для обучения ИИ. Она будет пополняться по мере необходимости в новых переменных, методах или при появлении нового кода.
Как планирую использовать этот конфигуратор — наполню его, отдам все Claude AI, чтобы иметь своего разработчика по скриптам Yougile.
Я просто хочу, чтобы это стало доступно и другим пользователям. Потому что собрать документацию в одном месте, это сложно. Потрачен не один час, чтобы все выковырять и оформить.
Разработчики Yougile, если вы это читаете — пожалуйста выгрузите все методы и переменные API конфигуратора, чтобы её можно было просто скопировать одним кликом или скачать файлом.
Оглавление статьи
Что такое конфигуратор?
Конфигуратор — это среда, в которой можно создавать javascript-код, который будет дополнять функциональность системы Yougile.
Полезные ссылки
- https://ru.yougile.com/team/api-editor — страница конфгуратора. Нужно быть авторизованным. Откроются скрипты именно вашего проекта.
- https://www.youtube.com/watch?v=UOKxyTRv0Bo — 10-минутное видео с примерами реализации скриптов, решающих конкретные задачи.
- https://ru.yougile.com/media/docs/yougile-api-manual.pdf — короткий мануал в несколько станиц, который может помочь вам лучше понять как устроено API YouGile.
Навигатор по API
Notifier
// Notifier.error(message)
// Показывает нотификацию ошибки.
Notifier.error('Task was added successfully');
// Notifier.warn(message)
// Показывает нотификацию предупреждения.
Notifier.warn('This task is readonly!');
// Notifier.success(message)
// Показывает нотификацию успешно выполненного действия.
Notifier.success('Task was added successfully');
//Notifier.show(title, message, onClick)
//Показывает браузерную нотификацию.
Notifier.show('New message', 'Hi, how’s it going?');
// Notifier.playSound(soundNumber)
// Проигрывает звук. Доступны 7 вариантов звуков.
// Звук по умолчанию, как в чатах
Notifier.playSound();
// Другой звук (доступно 7 вариантов)
Notifier.playSound(2);
Current
// Current.user
// Текущий пользователь (см. “user variable”).
if (Current.user.name === 'John') {
console.log('Hi John!');
}
// Current.project
// Открытый проект (см. “item variable”). Только для чтения.
var project = Current.project;
console.log('Открытый проект:', project);
// Current.board
// Открытая доска (см. “item variable”). Только для чтения.
var board = Current.board;
console.log('Открытая доска:', board);
// Current.chat
// Открытый чат задачи (см. “item variable”). Только для чтения.
var chat = Current.chat;
console.log('Открытый чат задачи:', chat);
// Current.company
// Возвращает компанию, в которой запускается скрипт.
var company = Current.company;
console.log('Company is ' + company.name);
// Current.onProjectChange(oldProject, newProject)
// Эта функция вызывается, когда пользователь переходит в другой проект.
Current.onProjectChange = function (oldProject, newProject) {
if (newProject) {
console.log('Navigated to ' + newProject.name);
} else {
console.warn('Exited project');
}
};
// Current.onBoardChange(oldBoard, newBoard)
// Эта функция вызывается, когда пользователь переходит в новую доску.
Current.onBoardChange = function (oldBoard, newBoard) {
console.log('Board ' + newBoard.name + ' opened!');
};
// Current.onChatChange(oldChat, newChat)
// Эта функция вызывается, когда открывается чат по другой задаче.
Current.onChatChange = function (oldChat, newChat) {
console.log('Opened chat for ' + newChat.name + ' task!');
};
Items
Позволяет манипулировать всеми объектами внутри YouGile (задачи, колонки, доски, проекты) и описывает поведение по умолчанию для этих объектов. Проекты, борды, колонки и задачи структурированы в некую иерархичную систему, похожую на файловую систему компьютера (смотри вкладку «Навигатор по объектам»). «Items» позволяет получить доступ и управлять ими на этот манер. И когда вы обращаетесь к какой-либо задаче или доске через Items, вы получаете переменную типа item (см. «item variable»).
/**
* Демонстрация основных методов API YouGile с примерами использования.
* Включает работу с задачами, обработку событий и управление интерфейсом.
*
* Основные функции:
* - Получение объектов по ID
* - Обработка событий добавления/удаления/перемещения
* - Управление правами на действия
* - Обработка обновлений
*/
// Получение объекта по ID
// Позволяет получить любой объект YouGile (компания, проект, доска, колонка, задача)
const getTaskExample = () => {
const task = Items.get('bb12a559-81ba-40ad-adda-81f2d017e71b');
task.name = task.name + '!';
};
// Обработка добавления нового объекта
Items.onAdd = function(obj) {
if (obj.type === 'Task') {
// Модифицируем название новой задачи
obj.name = obj.name + ' ...';
// Логируем для отладки
console.log('Добавлена новая задача:', obj.name);
}
};
// Предварительная проверка перед добавлением
Items.onBeforeAdd = function(location, name) {
if (location.type === 'Column') {
// Запрещаем добавление задач с названием "test"
if (name.toLowerCase() === 'test') {
Notifier.warn('Нельзя создавать задачи с названием "test"');
return false;
}
}
return true;
};
// Обработка переименования объектов
Items.onRename = function(item, newName) {
if (item.type === 'Task') {
// Добавляем специальную метку к важным задачам
if (newName.toLowerCase().includes('важно')) {
item.name = '⚡ ' + newName;
return false; // Прерываем стандартное переименование
}
}
return true;
};
// Обработка удаления объектов
Items.onDelete = function(item) {
// Запрещаем удаление важных объектов
if (item.name.startsWith('Important') || item.name.startsWith('Важно')) {
Notifier.error('Нельзя удалять важные объекты!');
return false;
}
return true;
};
// Обработка кликов на объекты
Items.onClick = function(item) {
// Показываем уведомление при клике на задачу
if (item.type === 'Task') {
Notifier.success(`Открыта задача: ${item.name}`);
}
return true;
};
// Фильтрация списка объектов
Items.onList = function(container, items) {
if (container.type === 'Column') {
// Показываем только 10 последних задач в колонке
return items.slice(-10);
}
return items;
};
// Обработка перемещения объектов
Items.onMove = function(item, from, to) {
// Запрещаем перемещение задач в архивную колонку неадминам
if (to.name === 'Архив' && !Current.user.isAdmin) {
Notifier.error('Только администраторы могут перемещать задачи в архив');
return false;
}
return true;
};
// Обработка обновлений объектов
Items.onUpdate = function(item) {
if (item.type === 'Task') {
// Логируем изменения в задаче
console.log(`Задача ${item.name} обновлена`);
// Если задача помечена как выполненная, отправляем уведомление
if (item.isCompleted()) {
Notifier.success(`Задача ${item.name} выполнена!`);
}
}
};
// Управление видимостью объектов
Items.isHidden = function(item) {
// Скрываем конфиденциальные задачи от обычных пользователей
if (item.type === 'Task' && !Current.user.isAdmin) {
if (item.name.includes('[Конфиденциально]')) {
return true;
}
}
return false;
};
// Управление правами на удаление
Items.allowDelete = function(item) {
// Запрещаем удаление для обычных пользователей
if (!Current.user.isAdmin) {
return false;
}
return true;
};
// Управление правами на добавление
Items.allowAdd = function(container) {
// Запрещаем добавление в заархивированные колонки
if (container.type === 'Column' && container.name === 'Архив') {
return false;
}
return true;
};
// Управление правами на переименование
Items.allowRename = function(item) {
// Запрещаем переименование важных задач
if (item.type === 'Task' &&
(item.name.startsWith('Important') || item.name.startsWith('Важно'))) {
return false;
}
return true;
};
// Управление возможностью перетаскивания
Items.allowDrag = function(item) {
// Запрещаем перетаскивание для заблокированных задач
if (item.type === 'Task' && item.name.includes('[Заблокировано]')) {
return false;
}
return true;
};
Item variable
/**
* Документация по API переменных элементов (items) в системе Yougile
* Содержит все доступные методы и свойства для работы с элементами системы
*/
// Базовые свойства
item.type; // Возвращает тип элемента: 'Task', 'Column', 'Board', 'Project'. Только для чтения
item.name; // Получает или устанавливает имя элемента
item.deleted; // Получает или устанавливает состояние удаления
item.timestamp; // Возвращает временную метку создания (unix timestamp). Только для чтения
item.path; // Возвращает форматированный путь к элементу. Пример: 'Моя компания » Проект » Доска » Колонка'
item.ui; // Возвращает UI элемент для данного объекта (см. UIElement)
/**
* Открывает элемент (проект или доску)
* Для задач/колонок открывает их содержащую доску
* @example
* // Открыть проект "Мой проект"
* Company.find('Мой проект').open();
*/
item.open();
/**
* Перемещает элемент к новому родителю
* @param {Item} newParent - Новый родительский элемент
*/
item.move(newParent);
/**
* Ищет дочерний элемент по имени
* @param {string} name - Имя для поиска
* @returns {Item} Найденный дочерний элемент
*/
item.find(name);
/**
* Получает родительский элемент
* @returns {Item} Родительский элемент
* @example
* const task = Items.get('taskId');
* const column = task.up(); // Получить колонку
* const board = column.up(); // Получить доску
*/
item.up();
/**
* Получает массив дочерних элементов
* @returns {Item[]} Массив дочерних элементов
* @example
* const board = Items.get('boardId');
* const columns = board.list(); // Получить все колонки доски
*/
item.list();
/**
* Добавляет новый дочерний элемент
* @param {string} name - Имя для нового элемента
* @returns {Item} Созданный элемент
* @example
* const column = Items.get('columnId');
* column.add('Новая задача');
*
* // Сохранить ссылку для последующих изменений
* const task = column.add('Другая задача');
* task.name = 'Обновленное имя';
*/
item.add(name);
/**
* Получает пользовательские данные, прикрепленные к элементу
* @returns {Object} Пользовательские данные в формате JSON
* @example
* const task = Items.get('taskId');
* const data = task.getData();
*/
item.getData();
/**
* Устанавливает пользовательские данные для элемента
* Данные синхронизируются с сервером и доступны всем пользователям
* @param {Object} json - JSON данные для сохранения
* @example
* const task = Items.get('taskId');
* task.setData({
* clientId: '9023929875',
* contact: {
* phone: '+1234567',
* email: 'user@example.com'
* }
* });
*
* // Изменение существующих данных
* const data = task.getData();
* data.orderNum = 2341;
* task.setData(data);
*/
item.setData(json);
/**
* Принудительно вызывает событие обновления элемента
* Система перепишет все, что связано с элементом и выполнит Items.onUpdate
*/
item.triggerUpdate();
/**
* Проверяет, находится ли элемент в архиве
* @returns {boolean} True, если в архиве
*/
item.isArchived();
/**
* Проверяет, выполнена ли задача
* @returns {boolean} True, если выполнена
*/
item.isCompleted();
/**
* Устанавливает состояние выполнения задачи
* @param {boolean} value - Новое состояние выполнения
*/
item.setCompleted(value);
/**
* Устанавливает состояние архивации задачи
* Работает только для задач
* @param {boolean} value - Новое состояние архивации
* @example
* const task = Items.get('taskId');
* const completed = task.isTaskCompleted();
* task.setTaskCompleted(!completed);
*/
item.setTaskCompleted(value);
/**
* Получает временную метку завершения задачи
* @returns {number} Unix timestamp завершения
*/
item.getTaskCompletionTime();
/**
* Делает колонку зеркалом других колонок
* @param {Item[]} columns - Массив колонок для отзеркаливания
* @example
* const column = Items.get('columnId');
* const board = Items.get('boardId');
*
* // Отзеркалить все колонки доски
* column.setMirror(board.list());
*
* // Убрать зеркало (сделать обычной колонкой)
* column.setMirror([]);
*/
item.setMirror(columns);
/**
* Проверяет, является ли колонка зеркалом
* @returns {boolean} True, если колонка является зеркалом
*/
item.isMirror();
/**
* Проверяет, является ли колонка отчетом
* @returns {boolean} True, если колонка является отчетом
*/
item.isReport();
/**
* Получает массив отзеркаленных колонок
* @returns {Item[]} Массив отзеркаленных колонок
* @example
* const column = Items.get('columnId');
* const mirrored = column.listMirrored();
* if (mirrored.length > 0) {
* // Колонка является зеркалом
* }
*/
item.listMirrored();
Примеры скриптов от разработчиков
Работа Javascript в чате
/**
* Выполняет javascript в чате,
* если сообщение начинается с >>>,
* то оно выполняется как javascript.
* Пример:
* >>> 12 * 12;
* » 144
*/
Chat.onPostMessage = function (msg) {
if (msg.text.slice(0, 3) === '>>>') {
var res = eval(msg.text.slice(3));
msg.text += 'n » ' + res;
msg.setData({js: true});
highlight(msg.getTask());
}
};
// подсвечивает сообщения в чате с выполненным js
function highlight(chat) {
Chat.listMessages(chat).forEach(function (msg) {
if (msg.getData().js) {
msg.ui.style = {
fontFamily: 'Menlo, Monaco, Courier New, monospace',
background: '#fffded'
};
}
});
}
// подсвечиваем сообщения с js при старте скрипта
// и при открытии нового чата
if (Current.chat) {
highlight(Current.chat);
}
Current.onChatChange = function (oldChat, newChat) {
if (newChat) {
highlight(newChat);
}
};
Добавляет кнопку «время ответа» в панель стикеров досок.
/**
* Добавляет кнопку “время ответа” в панель стикеров досок.
* Если кнопка нажата, то на всех задачах доски отображается
* время ответа на сообщения в чате.
*
* Чтобы время считалось, нужно, чтобы на задачу был назначен
* ответственный (стикер пользователь). Тогда время ответа —
* это среднее время, за которое отвественный пользователь
* отвечает другим пользователям в чате.
*
* Если назначенный на задачу пользователь не ответил на
* последнее сообщение, задача посвечивается красным.
*/
// добавим кнопку “время ответа” в панель стикеров
var trigger = UI.text('время ответа');
UI.components.stickerPanel.add(trigger);
trigger.style = {
fontSize: '12px',
lineHeight: '16px',
padding: '0px 10px 1px',
display: 'inline-block',
borderRadius: '10px',
cursor: 'pointer',
border: '1px solid #fff',
verticalAlign: 'top',
marginTop: '3px',
marginRight: '6px',
color: '#fff',
background: 'rgba(100, 100, 100, 0.4)'
};
// эта кнопка включает/выключает отображение времени ответа в доске
trigger.onClick = function () {
var data = Current.board.getData();
data.responseTimes = !data.responseTimes;
Current.board.setData(data);
redraw();
};
redraw(); // redraw отрисовывает время ответов в задачах доски см. ниже
Current.onBoardChange = redraw;
// при обновлении задач из доски, пересчитаем время ответов
Items.onUpdate = function (item) {
if (!Current.board || !Current.board.getData().responseTimes) {
return;
}
if (item.type === 'Task' && item.up().up().id === Current.board.id) {
updateTask(item);
redrawTask(item);
}
};
// отрисовывает время ответов в задачах доски
function redraw() {
if (!Current.board) {
return;
}
var enabled = Current.board.getData().responseTimes;
Current.board.list().forEach(function (col) {
col.list().forEach(function (task) {
if (enabled) {
updateTask(task);
redrawTask(task);
} else {
task.ui.clear();
}
});
});
var enabled = Current.board.getData().responseTimes;
if (enabled) {
trigger.style.background = 'rgba(70, 200, 50, 0.6)';
trigger.refresh();
} else {
trigger.style.background = 'rgba(100, 100, 100, 0.4)';
trigger.refresh();
}
}
// обновляет время ответа в задаче
function updateTask(task) {
var assigned = Users.getAssigned(task);
if (!assigned) {
return;
}
var data = task.getData();
if (data.lastMessageTime &&
data.lastMessageTime >= lastMessageTime) {
return;
}
var msg = Chat.listMessages(task);
if (msg.length === 0) {
return;
}
var count = 0;
var totalTime = 0;
var lastQuestionTime = 0;
var lastMessageTime = 0;
var asked = false;
for (var i = 0; i < msg.length; i++) {
var m = msg[i];
if (m.from.name === '_') { // YouGile system message
continue;
}
lastMessageTime = m.timestamp;
if (m.from.id !== assigned.id) {
if (!asked) {
lastQuestionTime = m.timestamp;
asked = true;
}
} else {
if (asked) {
totalTime += m.timestamp - lastQuestionTime;
asked = false;
count++;
}
}
}
if (count > 0 || asked) {
var result = Math.round(totalTime / count / 1000);
data.responseTime = result;
data.noResponse = asked;
data.lastMessageTime = lastMessageTime;
task.setData(data);
}
}
// переписовывает всемя ответа в задаче
function redrawTask(task) {
task.ui.clear();
var assigned = Users.getAssigned(task);
if (!assigned) {
var warn = UI.text('(пользователь не назачен)');
warn.style = {
fontSize: '13px',
color: '#999'
};
task.ui.add(warn);
return;
}
var time = task.getData().responseTime;
var noResponse = task.getData().noResponse;
if (time || noResponse) {
var txt = UI.text(formatTime(time));
txt.style = {
fontSize: '13px',
color: time < 3600 ? '#999' : '#e77'
};
if (noResponse) {
task.ui.style.background = '#ffede5';
task.ui.refresh();
}
if (time) {
task.ui.add(txt);
}
}
}
// конвертирует количество секунд в
// удобный для чтения формат, напр.: 200 => 3m 20s
function formatTime(sec) {
var s = sec % 60;
var m = (sec - s) / 60;
var h = (m - m % 60) / 60;
m = m % 60;
var d = (h - h % 24) / 24;
h = h % 24;
var parts = [];
parts.push(s + 's');
if (m) {
parts.push(m + 'm');
if (h) {
parts.push(h + 'h');
if (d) {
parts.push(d + 'd');
}
}
}
parts.reverse();
return parts.join(' ');
}
Настраивать свой фон для каждой доски отдельно.
/**
* Позволяет настраивать свой фон для каждой доски отдельно.
* Интерфейс изменения фона виден администраторам.
* Интерфейс открывается по нажатию Ctrl+1
*/
if (Current.user.isAdmin) {
Notifier.success('Нажмите Ctrl+1 чтобы открыть настройку фона');
}
// интерфейс настройки фона
var dialog = UI.panel(); // панель выбора фона
dialog.style.display = 'none'; // сначала не видна, нужно нажать Ctrl+1
var allBg = UI.panel(); // здесь будут уже загруженные фоны
var addBgBtn = UI.button('Загрузить новый фон');
var cancelBtn = UI.button('Отмена');
var clearBtn = UI.button('Очистить фон');
// добавляем все кнопки в панель
dialog.add(addBgBtn);
dialog.add(cancelBtn);
dialog.add(clearBtn);
dialog.add(allBg);
// события при нажатии на кнопки
cancelBtn.onClick = closeDialog;
clearBtn.onClick = function () {
setBg('');
};
addBgBtn.onClick = function () {
closeDialog();
App.chooseAndUpload(function (url) {
var data = Current.company.getData();
data.backgrounds = data.backgrounds || [];
data.backgrounds.push(url);
Current.company.setData(data);
setBg(url);
});
};
// по нажатию Ctrl+1 показываем панель настройки
Events.onKeyDown = function (e) {
if (e.keyCode === 49 && Events.isCtrlPressed()) { // Ctrl+1
// чтобы Ctrl+1 не перехватывалось системными комбинациями клавиш
e.preventDefault();
if (!Current.board) {
Notifier.warn('Зайдите в доску, чтобы установить её фон ' +
'и тогда нажмите Ctrl+1 ещё раз!');
return;
}
if (dialog.style.display === 'none') {
openDialog();
} else {
closeDialog();
}
}
};
// открытие интерфейса настройки
// и перерисовка списка загруженных фонов
// (возможно, кто-то добавил фон)
function openDialog() {
dialog.style.display = 'block';
dialog.refresh();
allBg.clear();
// добавляем загруженные фоны
var all = Current.company.getData().backgrounds || [];
all.forEach(function (url) {
var prev = UI.image(App.getPreviewUrl(url));
prev.style = imgStyle();
allBg.add(prev);
prev.onClick = function () {
setBg(url);
closeDialog();
};
});
}
// скрывает интерфейс настройки
function closeDialog() {
dialog.style.display = 'none';
dialog.refresh();
}
// устанавливает фон с заданным url в текущую доску
function setBg(url) {
var boardData = Current.board.getData();
boardData.bg = url;
Current.board.setData(boardData);
refreshBg();
}
// прикрепляет интерфейс настройки к текущей
// доске, но он при этом не виден, чтобы
// он показался используется openDialog();
function addBgChooser() {
if (!Current.user.isAdmin || !Current.board) {
return;
}
Current.board.ui.add(dialog);
}
// отрисовывает нужный фон,
// соответствуюший текущей доске
function refreshBg() {
if (!Current.board) {
return;
}
var bg = Current.board.getData().bg || '';
UI.background = bg;
}
refreshBg();
addBgChooser();
Current.onBoardChange = function () {
closeDialog();
addBgChooser();
refreshBg();
};
// styles
//
function imgStyle () {
return {
display: 'inline-block',
margin: '10px',
cursor: 'pointer',
height: '100px',
overflow: 'hidden',
boxShadow: '0 1px 12px -2px #fff',
borderRadius: '2px'
};
};
function btnStyle() {
return {
background: 'none',
color: '#fff',
background: '#344',
border: '1px solid #bbb',
margin: '2px 4px 0 0'
};
}
addBgBtn.style = btnStyle();
cancelBtn.style = btnStyle();
clearBtn.style = btnStyle();
Другие скрипты
// Скрипт: Убирает адресата из названия задачи в определенной колонке (для интеграции с почтой)
const column1 = "7c270d09-33a3-473c-8963-0b1c54a10f25"; // тут пишем нужную колонку
// Ниже можно написать и другие колонки по образцу выше
const columns = [column1]; // указываем эти колонки через запятую
function cutFirst(text) {
return text.substr(text.indexOf(" ") + 1);
}
Items.onUpdate = function (obj) {
// При обновлении
if (columns.includes(obj.id)) {
// Если колонка совпадает
for (const task of obj.list()) {
// Перебираем ее задачи
if (task.name.includes("@")) {
// Если есть @
task.name = cutFirst(task.name); // Обрезаем название по первому пробелу
}
}
}
};
// Скрипт: Множит по клику на карточку задачи через Shift задачи на исполнителей
Items.onClick = function (object) { // при клике
if (object.type === 'Task') { // на задачу
if (Events.isShiftPressed()) { // с зажатым Shift
const originalName = object.name; // берем название
const executors = Users.getAssigned(object, true); // исполнителей
const checklists = Chat.getSubtasks(object); // чеклисты
const description = Chat.getDescription(object); // описание
const usersInChat = Chat.listUsers(object);
function stickersInTask() { // функция для сбора стикеров в задаче
const stickers = Stickers.getAll().filter(sticker => Stickers.isPinned(object, sticker));
const values = stickers.map(sticker => ({
'key': sticker,
'value': Stickers.getValue(object, sticker)
}));
return values;
}
if (executors.length > 1) { // если несколько исполнителей
Users.assignUser(executors[0], object); // оставляем в задаче первого исполнителя
object.name = `[${executors[0].name}] ${originalName}`; // ставим его имя в название
usersInChat.forEach(user => Chat.removeUser(object, user)); // чистим чат
Chat.addUser(object, executors[0]); // подписываем
executors.shift(); // удаляем из массива
executors.forEach(executor => { // у остальных
object.up().add(`[${executor.name}] ${originalName}`); // добавляем по задаче
const currentTask = object.up().find(`[${executor.name}] ${originalName}`); // ищем ее
stickersInTask().forEach(sticker => { // для каждого стикера
setTimeout(function () { // таймаут для стабильности
Stickers.pin(currentTask, sticker.key, sticker.value); // ставим стикеры
Chat.setSubtasks(currentTask, checklists); // чеклисты
Chat.setDescription(currentTask, description); // описание
Users.assignUser(executor, currentTask); // исполнителя
usersInChat.forEach(user => Chat.removeUser(currentTask, user)); // чистим чат
Chat.addUser(currentTask, executor); // подписываем
}, 1000);
});
});
}
return false; // не открываем задачу
}
}
return true; // иначе открываем
};
// Скрипт: Добавляет к названию задачи названия проекта и доски
Items.onAdd = function (object) { // при добавлении
if (object.type === 'Task' && object.up()) { // если задача на доске
object.name = `[${object.up().up().up().name}, ${object.up().up().name}] ${object.name}`; // переименовываем
}
};
// Скрипт: Берет первые три цифры из описания и крепит их к задаче числовым стикером
const yourNumSticker = Stickers.get('Число'); // напишите название своего стикера
Items.onUpdate = function(obj) { // при обновлении
if (obj.type === 'Task') { // если задача
let descDump = obj.getData().description; // берем наш дамп описания задачи
if (descDump === undefined) {
descDump = '';
} // если дампа нет, то оставляем его пустым
const currenDesc = Chat.getDescription(obj); // берем описание задачи
if (descDump !== currenDesc) { // если разное с дампом
const twoLetters = parseInt(currenDesc.slice(0, 6).replaceAll(/</?[^>]+(>|$)/gi, "")); // берем первые знаки как числа
const numSticker = yourNumSticker; // берем стикер
if (!Number.isNaN(twoLetters)) { // если нормальное число
Stickers.pin(obj, numSticker, twoLetters); // крепим стикер
}
}
obj.setData({
description: currenDesc
}); // сохраняем текущее описание в дамп на будущее
}
};
// Скрипт: При клике на карточку задачи через Shift назначать себя исполнителем, а через Alt — еще удалить участников чата
Items.onClick = function (object) { // при клике
if (object.type === 'Task') { // на задачу
if (Events.isShiftPressed()) { // если зажата Shift
Users.assignUser(Current.user, object); // назначаем себя на задачу
Chat.addUser(object, Current.user); // добавляем себя в чат
return false; // не открываем задачу
}
if (Events.isAltPressed()) { // если зажата Alt
const listInChat = Chat.listUsers(object); // собираем участников чатов
listInChat.forEach(user => Chat.removeUser(object, user)); // удаляем их из чата
Users.assignUser(Current.user, object); // назначаем себя
Chat.addUser(object, Current.user); // подписываем себя на чат
return false; // не открываем задачу
}
}
return true; // открываем задачу
};
// Скрипт: Прочитывает все чаты по Ctrl + Shift + Backspace
Events.onKeyUp = function (event) { // следим за клавиатурой
if (Events.isShiftPressed() &&
Events.isCtrlPressed() &&
event.keyCode === 8) { // и Backspace
const warn = confirm("Вы действительно хотите прочитать все чаты?"); // размещаем предупреждение
if (warn) { // если все ок
for (const project of Current.company.list()) // разбиваем компанию на проекты
for (const boards of project.list()) // проекты на доски
for (const columns of boards.list()) // доски на колонки
for (const task of columns.list()) { // колонки на задачи
if (Chat.isUserInChat(task, Current.user)) { // берем только подписки
Chat.markRead(task, Current.user); // прочитываем задачи
}
}
}
}
};
// Скрипт: Открывает созданную задачу, если не зажата Alt. Если зажата — добавляет как обычно
Items.onAdd = function (task) { // при добавлении
if (task.type === 'Task') { // задачи
!Events.isAltPressed() ? Chat.open(task) : true; // при незажатой Alt открывает чат, иначе просто добавляет задачу
}
};
// Скрипт: Создает несколько задач из списка
Items.onBeforeAdd = function (location, name) { // перед добавлением
if (name.includes('n')) { // если есть перенос строк
const tasks = name.split('n').reverse(); // режем строки на массивы по переносу обратным порядком
tasks.forEach(name => location.add(name)); // добавляем задачи
return false; // не добавляя изначальную
}
};
// Скрипт: Добавляет второго исполнителя ко всем задачам первого исполнителя
// Для запуска скрипта нужно из-под администратора нажать Ctrl + Shift + / , в первом поле заполнить почту уже существующего исполнителя, во втором — нового исполнителя.
// Скрипт включает второго пользователя во все проекты существующего под ролью Сотрудник, не забудьте ее сменить.
// Работает только в браузере с возможностью открыть запрос (prompt).
Events.onKeyUp = function (event) { // следим за клавиатурой
if (Events.isShiftPressed() &&
Events.isCtrlPressed() &&
Current.user.isAdmin &&
event.keyCode === 191) { // если вызывает админ через Shift, Ctrl и /
const assignedUserEmail = prompt('Введите email текущего исполнителя'); // запрашиваем первый email
const userToAssignEmail = prompt('Введите email нового исполнителя'); // запрашиваем второй
const assignedUser = Users.listAll().find(user => user.email === assignedUserEmail); // найдем текущего исполнителя
const userToAssign = Users.listAll().find(user => user.email === userToAssignEmail); // найдем второго исполнителя
// если email не найдены, то показываем ошибку
if (assignedUser === undefined) { alert('Email текущего исполнителя не найден'); }
if (userToAssign === undefined) { alert('Email нового исполнителя не найден'); }
if (assignedUser !== undefined && userToAssign !== undefined) { // если все нормально
for (const project of Current.company.list())
for (const boards of project.list()) // проекты на доски
for (const columns of boards.list()) // доски на колонки
for (const task of columns.list()) { // колонки на задачи
if (Users.isUserAssigned(assignedUser, task)) { // если старый сотрудник подписан
const usersInTask = Users.getAssigned(task, true); // получим список уже подписанных сотрудников
const newUsersList = [...usersInTask, userToAssign]; // добавим в него нового
Users.assignUser(newUsersList, task); // применим этот список
Users.addToChat(userToAssign, task); // подпишем в чат
if (Users.isInProject(assignedUser, project)) { // в проекты, в которые добавлен текущий исполнитель
Users.addToProject(userToAssign, project); // подпишем второго исполнителя
}
}
}
}
}
};
// Скрипт: Ограничивает количество задач в колонке
// В скрипте нужно заполнить два поля: id колонки в column и ограничение количества задач в ней
const column = '0da6cadd-7717-41cd-8dbe-6b571d15301a'; // id колонки
const count = 10; // максимальное количество задач в колонке
Items.onBeforeAdd = function (location, name) { // смотрим перед добавлением
if (location.id === column) { // если колонка совпадает с нужной
const columnCount = location.list().filter(x => x.isArchived() === false).length; // получаем количество неархивных задач
if (columnCount >= count) { // если больше нашего количества
alert('В колонке больше 10 задач!'); // выводим сообщение
return false; // запрещаем добавление
}
} else {
return true; // разрешаем
}
};
Items.onMove = function (object, from, to) { // при перемещении задачи
if (to.id === column) { // если колонка назначения совпадает с нужной
const columnCount = to.list().filter(x => x.isArchived() === false).length; // получаем количество неархивных задач
if (columnCount >= count) { // если больше нашего количества
alert('В колонке больше 10 задач!'); // выводим сообщение
return false; // запрещаем добавление
}
} else {
return true; // разрешаем
}
};
// Скрипт: Отписывает от всех задач доски через кнопку
// Работает только в браузере с возможностью открыть диалог подтверждения (confirm).
Current.onBoardChange = function (oldBoard, newBoard) {
// При смене доски
const boardList = Current.project.list(); // собираем все проекты
boardList.forEach(function (board) {
// Для каждой доски
const btn = UI.button("Отписаться от всех задач"); // создаем кнопку
board.ui.clear(); // предварительно очищаем, чтобы удалить предыдущий и не дублировать
btn.onClick = function (e) {
// В кнопке
const warn = confirm("Вы действительно хотите отписаться от всех задач доски?"); // размещаем предупреждение
if (warn) {
// Если все ок
for (const column of Current.board.list()) // для колонок в доске
for (const task of column.list()) {
// для задач в колонке
Chat.removeUser(task, Current.user); // отписываем пользователя
}
}
};
board.ui.add(btn); // добавляем кнопку в интерфейс
});
};
// Скрипт: Подписка на все чаты задач (при их создании)
// Скрипт работает только для администраторов компании, внутри скрипта нужно указать их список
// Не работает для подзадач.
// Нужно заполнить e-mail-ы администраторов в listOfEmails, которые будут подписываться на чаты
const listOfEmails = ["example1@example.com", "example2@example.com"]; // список email-ов для подписки
const findId = listOfEmails.map((email) => Users.get(email)); // переведем почты в id
const justAdmins = findId.filter((user) => user.isAdmin); // отфильтруем только администраторов
Items.onAdd = function (task) { // при добавлении
if (task.type === "Task") { // задачи
justAdmins.forEach((admin) => Chat.addUser(task, admin)); // подписываем на чат
}
};
// Скрипт: Уведомление в чат о перемещении задачи
// Не работает для подзадач.
Items.onMove = function (task, from, to) { // при перемещении
if (task.type === 'Task') { // задачи
Chat.postMessage(task, '', `Сотрудник ${Current.user.name} переместил задачу из колонки «${from.name}» в колонку «${to.name}».`); // пишем сообщение в чат
}
};
// Скрипт: Архивация задач при выполнении
// Может быть реализовано штатным функционалом YouGile: перейдите в Настройки доски > Основные и убедитесь, что галочка "Автоматически архивировать выполненные задачи" включена.
Items.onUpdate = function(obj) { // вызываем при обновлении
if (obj.isCompleted() === true) { // если задача выполнена
obj.setTaskCompleted(true); // то архивируем ее
}
};
// Скрипт: Считает количество задач в колонке и печатает его в интерфейсе
// Пишет количество задач в колонках при отрисовке страницы и обновлении объектов.
// Вносить в него ничего не надо, можно в p.style поменять что-то на свой вкус.
Current.onBoardChange = function (oldBoard, newBoard) { // работаем при смене доски
const listOfColumns = Current.board.list(); // получаем список колонок
listOfColumns.forEach(function (i) { // перебираем каждую колонку
if (!Items.get(i.id).isMirror() && !Items.get(i.id).isReport()) { // если колонка не зеркало и не сводка
const column = Items.get(i.id); // получаем id колонки
const numberTasks = column.list(); // берем общее количество задач в колонке
const tasksIsNotArchived = numberTasks.filter(x => x.isArchived() === false); // забираем задачи не в архиве
const p = UI.panel(); // создаем контейнер
column.ui.clear(); // предварительно очищаем, чтобы удалить предыдущий и не дублировать
column.ui.add(p);
p.style = {
margin: 'auto',
width: '100px',
padding: 'auto',
color: '#572'
}; // оформляем контейнер
p.add(UI.text('Задач: ' + tasksIsNotArchived.length)); // пишем текст
}
});
};
Items.onUpdate = function (obj) { // вызываем функцию при обновлении, чтобы работало в реальном времени
Current.onBoardChange();
};
// Скрипт: Запрашивает обязательный комментарий при перемещении задачи в другую колонку и печатает его в описание
// Работает только в браузере с возможностью открыть запрос (prompt).
Items.onMove = function(obj, from, to) {
if (to.id === 'id колонки') { // если id колонки совпадает
let comment = prompt('Введите комментарий'); // запрашиваем комментарий
if (!comment) { // если комментария нет
return false; // отказываем
} else {
Chat.postMessage(obj, '', `Задача перемещена пользователем ${Current.user.name}`); // пишем в чат, что переместили задачу
Chat.setDescription(obj, Chat.getDescription(obj) + '<br> <p><b>Комментарий пользователя ' + Current.user.name + ': </b></p>' +
'<p>' + comment + '</p>'); // пишем в описание комментарий
}
return true;
}
};
// Скрипт: Назначение пользователя в определенной колонке
// Добавляет исполнителя в задачу, если ее переместить в колонку и у задачи нет исполнителя.
// Надо вписать id колонки и id пользователя.
// Для текущего пользователя (кто перемещает задачу) можно вместо id пользователя вписать Current.user.id без кавычек, это сделает его исполнителем.
Items.onMove = function(obj, from, to) {
const task = Items.get(obj.id); // берем id таска
const user = Users.get('id пользователя'); // берем id пользователя
if (to.id === 'id колонки' && !Users.getAssigned(task)) { // если id колонки совпадает и в задаче нет исполнителя
Users.assignUser(user, task); // назначаем пользователя
}
};
// Скрипт: Скрывает задачу не из "белого списка" аккаунтов, если на нее навесить определенный стикер
// userRestrict — массив, в который вносятся email-ы, для которых задача скрывается
Items.isHidden = function(object) { // функция скрытия
const stickerRestrict = Stickers.get('Ограничение'); // забираем нужный текстовый стикер
const allowToSee = Stickers.getValue(object, stickerRestrict); // забираем состояние этого стикера
const userRestrict = ['example1@example.com', 'example2@example.com']; // массив почт, для которых задача скроется
function checkEmail(email) { // функция, которая возвращает совпадение запрещенного списка с текущим пользователем
return email === Current.user.email;
}
const restrict = userRestrict.find(checkEmail); // ищет совпадение
if (object.type === 'Task' && allowToSee === 'Запрет' && restrict !== undefined) { // если задача со стикером "Запрет" и текущий пользователь попадает в список
return true; // скрываем
}
return false; // в остальных случаях должно быть открыто
};
// Скрипт: Сообщение в чат при удалении исполнителя
Stickers.onUnpin = function(task, sticker) { // при откреплении стикера
if (sticker.name === 'User') { // если стикер — исполнитель
const userID = Stickers.getValue(task, sticker); // берем id исполнителя
const userName = Users.get(userID).name; // получаем его имя
Chat.postMessage(task, '', `${userName} удален из исполнителей задачи`); // пишем в чат задачи
}
return true;
};
// Скрипт: Сообщение в чат при добавлении файла в описание задачи
// Ниже — единожды запускаемый процесс просмотра всех задач для фиксации дампа описания
for (const project of Current.company.list())
for (const boards of project.list())
for (const columns of boards.list())
for (const task of columns.list()) {
const dump = task.getData(); // читаем дамп
const desc = Chat.getDescription(task); // и описание
if (dump.description !== desc) { // если не совпадают
dump.description = desc;
task.setData(dump); // пишем дамп
}
}
// Дальше работаем по обновлению задач
Items.onUpdate = function(obj) { // при обновлении
if (obj.id === Current.chat.id) { // фильтруем по открытой задаче (избегаем множества срабатываний)
const dump = obj.getData(); // читаем дамп
const currenDesc = Chat.getDescription(obj); // берем описание задачи
const diff = findDiff(dump.description, currenDesc); // сравниваем и находим разницу
if (diff.includes('rel="noopener noreferrer"')) { // если в разнице есть свойство вложения
Chat.postMessage(obj, '', 'Файл добавлен в описание'); // пишем в чат
}
dump.description = currenDesc; // переписываем дамп в любом случае
obj.setData(dump); // сохраняем текущее описание в дамп на будущее
}
};
function findDiff(str1, str2) { // это функция, которая сравнивает две строки и выводит между ними разницу
let diff = "";
str2.split('').forEach(function(val, i) {
if (val != str1.charAt(i))
diff += val;
});
return diff;
}
// Скрипт: Сообщение в чат о прикреплении/изменении дедлайна
// Реагирует на создание и изменение дедлайна задачи, отписывая сообщение в чат задачи.
// Удаление дедлайна игнорирует
Stickers.onPin = function(task, sticker, value) { // при прикреплении стикера
if (sticker.type === 'Deadline' && value !== undefined) { // если это дедлайн и у него есть значение
Chat.postMessage(task, '', 'Прикреплен/изменен дедлайн'); // пишем сообщение в чат
}
};
// Скрипт: Не дает добавить задачу, если есть с таким же названием в компании
// Не работает для подзадач.
Items.onBeforeAdd = function (location, name) { // перед добавлением
if (location.type === 'Column') { // задачи
for (const project of Current.company.list())
for (const boards of project.list())
for (const columns of boards.list())
for (const task of columns.list()) {
if (name == task.name) { // если название совпадает
Notifier.error('Задача с таким названием уже есть'); // показываем ошибку
return false; // запрещаем
}
}
return true; // разрешаем, если ничего не запретилось
}
};
// Скрипт: Отправка сообщения в чат при изменении чеклистов
Items.onUpdate = function(obj) { //при обновлении
if (obj.id === Current.chat.id) { //если объект — текущий чат
const checkListsFact = Chat.getSubtasks(obj); // получаем чеклисты
const checkListsDump = obj.getData().checklists; // получаем дамп записи чеклистов
if (JSON.stringify(checkListsFact) !== JSON.stringify(checkListsDump)) { //если дамп и чеклисты не совпадают
if (checkListsDump === undefined) { //если дамп пуст
obj.setData({
checklists: checkListsFact
}); //записываем дамп
} else if (checkListsFact.length !== checkListsDump.length) { //иначе сравниваем длину чеклста и дампа.
// если разница есть → что-то делали со списком чеклистов
obj.setData({
checklists: checkListsFact
}); // записываем дамп
Chat.postMessage(obj, Users.get(''), 'Изменен список чеклиста') // пишем в чат об изменении списка чеклиста
} else { //иначе → если менялся не список чеклиста, то менялся пункт чеклиста
obj.setData({
checklists: checkListsFact
}); // записываем дамп
Chat.postMessage(obj, Users.get(''), 'Изменен пункт чеклиста') // пишем в чат, что менялся пункт чеклиста
}
}
}
};
// Скрипт: Копирует id задачи в буфер обмена по Ctrl + Alt + C
Events.onKeyDown = function (event) {
if (
Current.chat &&
Current.chat.type === "Task" && //если задача
Events.isCtrlPressed() && //и зажат контрол
Events.isAltPressed() && //и альт
event.keyCode === 67 // и C
) {
const companyTail = Current.company.id.substring(Current.company.id.length - 12) // режеим хвостик
const taskTail = Current.chat.id.substring(Current.chat.id.length - 12); //режеим хвостик
App.copyToClipboard(
`https://yougile.com/team/${companyTail}/#chat:${taskTail}`,
); // в буфер
Notifier.success("Ссылка на тикет скопирована"); // уведомление
}
}
Мои скрипты
Множество задач из списка
Создать множество задачи из большого сообщения, которое вставляем в название страницы и нажимаем enter. В начале кода стоит удаление символов [ ] и даты. Вы перепишите функцию под свои «лишние» символы, которые надо удалить.
// Функция для удаления символов и дат в начале строки
function cleanTaskText(text) {
// Удаляем символы - [ ] и дату в формате ДД.ММ.ГГГГ или с тире
text = text.replace(/^s*-s*[s*]s*d{2}.d{2}.d{4}s*[-–—]?s*/, '');
// Обрезаем лишние пробелы в начале и конце строки
return text.trim();
}
// Отслеживаем добавление задачи
Items.onBeforeAdd = function (location, name) {
if (name.includes('n')) { // Если есть переносы строк
const tasks = name.split('n').reverse(); // Разделяем текст по строкам и переворачиваем массив
tasks.forEach(taskName => {
const cleanedTask = cleanTaskText(taskName); // Очищаем от символов и дат
if (cleanedTask) {
location.add(cleanedTask); // Добавляем очищенную задачу
}
});
return false; // Не добавляем исходный текст
}
};
Парсер числа из названия и установка стикера
Добавляет стикер суммы, если в начале есть сумма. Если стоит 10к, то конвертируется в 10 000. Стикер должен быть типа и называться «Сумма».
/**
* Скрипт для автоматического добавления стикера "Сумма" к задачам.
* Работает только для указанного проекта.
* Извлекает сумму из названия задачи, независимо от ее позиции.
* Добавляет стикер "Сумма", если он доступен в системе.
* Поддерживает различные форматы записи суммы: рубли, тысячи, со знаком ₽.
* Обрабатывает только числа со специальными обозначениями:
* - руб, рублей, р, ₽ - значение берется как есть
* - k, к - значение умножается на 10000
*
* Примеры:
* "Саше 10к" -> стикер 100000
* "Саше 5500₽" -> стикер 5500
* "Саше 1000 рублей" -> стикер 1000
* "Саше 125" -> стикер не добавляется
*/
// ID целевого проекта
const TARGET_PROJECT_ID = '23c1c7b1-0402-4643-8026-100f887910dc';
// Получаем целевой проект
const targetProject = Items.get(TARGET_PROJECT_ID);
if (!targetProject) {
console.error(`Проект с ID ${TARGET_PROJECT_ID} не найден.`);
return;
}
// Глобальная переменная для хранения стикера "Сумма"
let sumSticker = null;
// Функция для инициализации стикера "Сумма"
function initSumSticker() {
sumSticker = Stickers.get('Сумма');
if (!sumSticker) {
console.error("Стикер 'Сумма' не найден в системе.");
return false;
}
console.log("Стикер 'Сумма' успешно инициализирован.");
return true;
}
// Функция для проверки, принадлежит ли задача целевому проекту
function isTaskInTargetProject(task) {
let currentItem = task;
while (currentItem) {
if (currentItem.id === TARGET_PROJECT_ID) {
return true;
}
currentItem = currentItem.up();
}
return false;
}
// Функция для извлечения суммы из названия задачи
function extractSum(taskName) {
const regex = /(d+(?:s*d+)*(?:,d+)?)s*(руб(?:лей)?|р|₽|k|к)(?![а-яёa-z])/i;
const match = taskName.match(regex);
if (match) {
let sum = parseFloat(match[1].replace(/s/g, '').replace(',', '.'));
const unit = match[2] ? match[2].toLowerCase() : '';
if (unit === 'k' || unit === 'к') {
sum *= 1000;
}
return Math.round(sum);
}
return null;
}
// Обработчик события создания задачи
Items.onAdd = function(task) {
if (task && task.type === 'Task' && isTaskInTargetProject(task)) {
console.log(`Создана новая задача: "${task.name}" в целевом проекте`);
if (!sumSticker && !initSumSticker()) {
console.log("Не удалось инициализировать стикер 'Сумма'. Стикер не будет добавлен.");
return;
}
const taskName = task.name;
const sum = extractSum(taskName);
if (sum !== null) {
setTimeout(() => {
try {
Stickers.pin(task, sumSticker, sum.toString());
console.log(`Стикер 'Сумма' со значением ${sum} добавлен к задаче '${taskName}'`);
} catch (error) {
console.error(`Ошибка при добавлении стикера: ${error.message}`);
}
}, 500);
} else {
console.log(`Сумма не найдена в названии задачи '${taskName}'`);
}
}
};
// Функция для добавления стикеров к существующим задачам
function addStickerToExistingTasks() {
if (!sumSticker && !initSumSticker()) {
console.log("Не удалось инициализировать стикер 'Сумма'. Обработка существующих задач прервана.");
return;
}
console.log("Начало обработки существующих задач...");
for (const board of targetProject.list()) {
console.log(`Обработка доски: ${board.name}`);
for (const column of board.list()) {
for (const task of column.list()) {
if (task && task.type === 'Task') {
const sum = extractSum(task.name);
if (sum !== null) {
try {
Stickers.pin(task, sumSticker, sum.toString());
console.log(`Стикер 'Сумма' со значением ${sum} добавлен к существующей задаче '${task.name}'`);
} catch (error) {
console.error(`Ошибка при добавлении стикера к задаче '${task.name}': ${error.message}`);
}
}
}
}
}
}
console.log("Обработка существующих задач завершена.");
}
// Инициализация стикера при загрузке скрипта
if (initSumSticker()) {
console.log(`Скрипт добавления стикера "Сумма" загружен и готов к работе для проекта с ID ${TARGET_PROJECT_ID}`);
} else {
console.error("Скрипт не может работать без стикера 'Сумма'. Пожалуйста, создайте этот стикер в системе.");
}
// Раскомментируйте следующую строку, чтобы добавить стикеры к существующим задачам
// addStickerToExistingTasks();
Добавить стикер месяца к задаче
Добавляет стикер месяца к задаче. У меня стоит форматирование «Сентябрь 24». При создании задачи проверяет, какой месяц и добавляет стикер. По задумке, если будет январь 2025, то он создаст стикер Январь 25. Но надо дожить, чтобы проверить)
let buttonPanel;
function createButton(text, onClick) {
const button = UI.button(text);
button.style = {
margin: '0 10px 0 0',
padding: '10px 15px',
fontSize: '14px',
fontWeight: 'bold',
backgroundColor: '#333',
color: '#fff', // Добавим белый цвет текста для контраста
border: 'none',
borderRadius: '6px',
cursor: 'pointer'
};
button.onClick = onClick;
return button;
}
function countSubscribedTasks(board) {
return board.list().reduce((count, column) =>
count + column.list().filter(task => Chat.isUserInChat(task, Current.user)).length, 0);
}
function countTasksAsExecutor(board) {
const executorSticker = Stickers.get('User');
return board.list().reduce((count, column) =>
count + column.list().filter(task =>
Stickers.getValue(task, executorSticker) === Current.user.id
).length, 0);
}
function unsubscribeFromAllTasks(board) {
let unsubscribedCount = 0;
board.list().forEach(column => {
column.list().forEach(task => {
if (Chat.isUserInChat(task, Current.user)) {
Chat.removeUser(task, Current.user);
unsubscribedCount++;
}
});
});
Notifier.success(`Вы отписались от ${unsubscribedCount} задач на этой доске.`);
}
function removeAsExecutorFromAllTasks(board) {
const executorSticker = Stickers.get('User');
let removedCount = 0;
board.list().forEach(column => {
column.list().forEach(task => {
if (Stickers.getValue(task, executorSticker) === Current.user.id) {
Stickers.unpin(task, executorSticker);
removedCount++;
}
});
});
Notifier.success(`Вы удалены как исполнитель из ${removedCount} задач на этой доске.`);
}
function createOrUpdateButtonPanel(board) {
if (!buttonPanel) {
buttonPanel = UI.panel();
buttonPanel.style = {
display: 'flex',
justifyContent: 'center',
margin: '10px 0'
};
} else {
buttonPanel.clear();
}
const unsubscribeButton = createButton(
`Отписаться от всех задач доски`,
() => {
const count = countSubscribedTasks(board);
if (confirm(`Вы уверены, что хотите отписаться от ${count} задач на этой доске?`)) {
unsubscribeFromAllTasks(board);
}
}
);
const removeExecutorButton = createButton(
`Удалить меня как исполнителя со всех задач`,
() => {
const count = countTasksAsExecutor(board);
if (confirm(`Вы уверены, что хотите удалить себя как исполнителя из ${count} задач на этой доске?`)) {
removeAsExecutorFromAllTasks(board);
}
}
);
buttonPanel.add(unsubscribeButton);
buttonPanel.add(removeExecutorButton);
return buttonPanel;
}
function addButtonPanelToBoard(board) {
const panel = createOrUpdateButtonPanel(board);
board.ui.add(panel);
}
Current.onBoardChange = function(oldBoard, newBoard) {
if (newBoard) {
addButtonPanelToBoard(newBoard);
}
};
if (Current.board) {
addButtonPanelToBoard(Current.board);
}
Добавить кнопки отписки и удаления себя от всех задач на доске
Добавляет две кнопки: отписать и удалить себя со всех задач на доске. Не на проекте, а на доске. При клике показывает окно, в котором указывает, из скольких задач отпишет или удалит.
let buttonPanel;
function createButton(text, onClick) {
const button = UI.button(text);
button.style = {
margin: '0 10px 0 0',
padding: '10px 15px',
fontSize: '14px',
fontWeight: 'bold',
backgroundColor: '#333',
color: '#fff', // Добавим белый цвет текста для контраста
border: 'none',
borderRadius: '6px',
cursor: 'pointer'
};
button.onClick = onClick;
return button;
}
function countSubscribedTasks(board) {
return board.list().reduce((count, column) =>
count + column.list().filter(task => Chat.isUserInChat(task, Current.user)).length, 0);
}
function countTasksAsExecutor(board) {
const executorSticker = Stickers.get('User');
return board.list().reduce((count, column) =>
count + column.list().filter(task =>
Stickers.getValue(task, executorSticker) === Current.user.id
).length, 0);
}
function unsubscribeFromAllTasks(board) {
let unsubscribedCount = 0;
board.list().forEach(column => {
column.list().forEach(task => {
if (Chat.isUserInChat(task, Current.user)) {
Chat.removeUser(task, Current.user);
unsubscribedCount++;
}
});
});
Notifier.success(`Вы отписались от ${unsubscribedCount} задач на этой доске.`);
}
function removeAsExecutorFromAllTasks(board) {
const executorSticker = Stickers.get('User');
let removedCount = 0;
board.list().forEach(column => {
column.list().forEach(task => {
if (Stickers.getValue(task, executorSticker) === Current.user.id) {
Stickers.unpin(task, executorSticker);
removedCount++;
}
});
});
Notifier.success(`Вы удалены как исполнитель из ${removedCount} задач на этой доске.`);
}
function createOrUpdateButtonPanel(board) {
if (!buttonPanel) {
buttonPanel = UI.panel();
buttonPanel.style = {
display: 'flex',
justifyContent: 'center',
margin: '10px 0'
};
} else {
buttonPanel.clear();
}
const unsubscribeButton = createButton(
`Отписаться от всех задач доски`,
() => {
const count = countSubscribedTasks(board);
if (confirm(`Вы уверены, что хотите отписаться от ${count} задач на этой доске?`)) {
unsubscribeFromAllTasks(board);
}
}
);
const removeExecutorButton = createButton(
`Удалить меня как исполнителя со всех задач`,
() => {
const count = countTasksAsExecutor(board);
if (confirm(`Вы уверены, что хотите удалить себя как исполнителя из ${count} задач на этой доске?`)) {
removeAsExecutorFromAllTasks(board);
}
}
);
buttonPanel.add(unsubscribeButton);
buttonPanel.add(removeExecutorButton);
return buttonPanel;
}
function addButtonPanelToBoard(board) {
const panel = createOrUpdateButtonPanel(board);
board.ui.add(panel);
}
Current.onBoardChange = function(oldBoard, newBoard) {
if (newBoard) {
addButtonPanelToBoard(newBoard);
}
};
if (Current.board) {
addButtonPanelToBoard(Current.board);
}
Проставить месяц и сумму у всех задач на доске
Пройтись по всем задачам, проставить тег месяца исходя из даты создания задачи и проставить сумму, если в названии есть числовое значение
/**
* Скрипт автоматически добавляет стикеры "Месяц" и "Сумма" ко всем задачам в проекте.
*
* Функциональность:
* - Извлекает суммы из названий задач в различных форматах (XXXX₽, XXXXр, XXXX руб, XXXXk)
* - Добавляет стикер месяца в формате "Месяц ГГ" на основе даты создания задачи
* - Работает со всеми колонками указанной доски
*
* Требования:
* 1. В системе должны быть созданы стикеры:
* - "Сумма" (числовой тип)
* - "Месяц" (текстовый тип)
*/
// ID проекта и доски для обработки
const PROJECT_ID = 'ВСТАВИТЬ СЮДА ID';
const BOARD_ID = 'ВСТАВИТЬ СЮДА ID';
// Инициализация проекта и доски
const project = Items.get(PROJECT_ID);
const board = Items.get(BOARD_ID);
// Проверка наличия всех необходимых элементов
if (!project || !board) {
Notifier.error('Ошибка: Не найден проект или доска');
console.error('Элементы не найдены:', { project, board });
return;
}
// Инициализация стикеров
const sumSticker = Stickers.get('Сумма');
const monthSticker = Stickers.get('Месяц');
// Проверка наличия необходимых стикеров
if (!sumSticker || !monthSticker) {
Notifier.error('Ошибка: Не найдены стикеры "Сумма" и/или "Месяц"');
console.error('Стикеры не найдены:', {sumSticker, monthSticker});
return;
}
// Функция для извлечения суммы из названия задачи
function extractSum(taskName) {
const regex = /(d+(?:s*d+)*(?:,d+)?)s*(руб(?:лей)?|р|₽|k|к)?/i;
const match = taskName.match(regex);
if (match) {
let sum = parseFloat(match[1].replace(/s/g, '').replace(',', '.'));
const unit = match[2] ? match[2].toLowerCase() : '';
if (unit === 'k' || unit === 'к') {
sum *= 1000;
}
return Math.round(sum);
}
return null;
}
// Функция для получения месяца из даты создания задачи
function getMonthFromTaskCreation(task) {
try {
const creationDate = new Date(task.timestamp);
const monthNames = [
'Январь', 'Февраль', 'Март', 'Апрель',
'Май', 'Июнь', 'Июль', 'Август',
'Сентябрь', 'Октябрь', 'Ноябрь', 'Декабрь'
];
const month = monthNames[creationDate.getMonth()];
const year = creationDate.getFullYear().toString().slice(-2);
return `${month} ${year}`;
} catch (error) {
console.error(`Ошибка при получении месяца создания задачи "${task.name}":`, error);
return null;
}
}
// Функция для обновления стикеров задачи
function updateTaskStickers(task) {
try {
// Обработка суммы
const sum = extractSum(task.name);
if (sum !== null) {
Stickers.pin(task, sumSticker, sum.toString());
console.log(`Добавлен стикер суммы ${sum} к задаче "${task.name}"`);
} else {
console.log(`Сумма не найдена в задаче "${task.name}"`);
}
// Обработка месяца
const monthYear = getMonthFromTaskCreation(task);
if (monthYear !== null) {
Stickers.pin(task, monthSticker, monthYear);
console.log(`Добавлен стикер месяца ${monthYear} к задаче "${task.name}"`);
}
} catch (error) {
console.error(`Ошибка при обработке задачи "${task.name}":`, error);
Notifier.error(`Ошибка при обработке задачи "${task.name}"`);
}
}
// Основная функция обновления стикеров
function updateBoardStickers() {
let processedCount = 0;
let errorCount = 0;
let noSumCount = 0;
// Получаем все колонки доски
const columns = board.list();
console.log(`Найдено ${columns.length} колонок на доске "${board.name}"`);
// Обрабатываем задачи в каждой колонке
columns.forEach(column => {
console.log(`Обработка колонки "${column.name}"`);
const tasks = column.list();
tasks.forEach(task => {
if (task.type === 'Task') {
try {
const sumBefore = Stickers.getValue(task, sumSticker);
updateTaskStickers(task);
processedCount++;
// Проверяем, была ли найдена сумма
const sumAfter = Stickers.getValue(task, sumSticker);
if (!sumAfter && !sumBefore) {
noSumCount++;
}
} catch (error) {
errorCount++;
console.error(`Ошибка при обработке задачи "${task.name}":`, error);
}
}
});
});
// Выводим итоговую статистику
const resultMessage = `Обработано ${processedCount} задач${errorCount > 0 ? `, ошибок: ${errorCount}` : ''}${noSumCount > 0 ? `, задач без суммы: ${noSumCount}` : ''}`;
Notifier.success(resultMessage);
console.log(resultMessage);
}
// Создаем кнопку в том же стиле, что и существующие
const updateButton = UI.button('Обновить стикеры месяца и суммы');
updateButton.style = {
margin: '0 10px 0 0',
padding: '10px 15px',
fontSize: '14px',
fontWeight: 'bold',
backgroundColor: '#333',
color: '#fff',
border: 'none',
borderRadius: '6px',
cursor: 'pointer'
};
updateButton.onClick = updateBoardStickers;
// Добавляем кнопку на доску
const panel = UI.panel();
panel.style = {
display: 'flex',
justifyContent: 'center',
margin: '10px 0'
};
panel.add(updateButton);
if (board) {
board.ui.add(panel);
console.log('Скрипт обновления стикеров загружен и готов к использованию');
} else {
console.error('Не удалось добавить панель управления: доска не найдена');
}
console.log('Скрипт обновления стикеров загружен и готов к использованию');
Добавить в каждую колонку кнопку «Скопировать выполненные задачи»
Копирует и нумерует названия всех задач. Я делал, чтобы формировать отчёты о том, что сделано.
/**
* Скрипт для экспорта названий выполненных задач из выбранного столбца.
*
* Добавляет кнопку экспорта в интерфейс каждой колонки.
* При нажатии формируется текст только с выполненными задачами,
* который копируется в буфер обмена.
*
* В экспорт попадают только выполненные и неархивированные задачи.
*/
// Функция для создания кнопки экспорта
function createExportButton(column) {
const button = UI.button('📋 Скопировать выполненные задачи');
button.style = {
margin: '5px',
padding: '8px 12px',
fontSize: '14px',
backgroundColor: '#4CAF50',
color: '#ffffff',
border: 'none',
borderRadius: '4px',
cursor: 'pointer'
};
button.onClick = () => exportTasks(column);
return button;
}
// Функция для форматирования даты
function formatDate(date) {
const d = new Date(date);
const day = String(d.getDate()).padStart(2, '0');
const month = String(d.getMonth() + 1).padStart(2, '0');
const year = d.getFullYear();
return `${day}.${month}.${year}`;
}
// Функция для форматирования даты выполнения
function formatCompletionDate(timestamp) {
if (!timestamp) return 'Дата не указана';
return formatDate(new Date(timestamp * 1000)); // конвертируем unix timestamp в дату
}
// Функция для экспорта задач
function exportTasks(column) {
// Получаем все выполненные и неархивные задачи из колонки
const tasks = column.list().filter(task =>
!task.isArchived() &&
task.isCompleted()
);
if (tasks.length === 0) {
Notifier.warn(`В колонке "${column.name}" нет выполненных задач`);
return;
}
// Формируем текст для экспорта
let content = `Выполненные задачи из колонки "${column.name}"n`;
content += `Дата выгрузки: ${formatDate(new Date())}n`;
content += `Количество выполненных задач: ${tasks.length}nn`;
// Добавляем названия задач с датой выполнения
tasks.forEach((task, index) => {
const completionDate = formatCompletionDate(task.getTaskCompletionTime());
content += `${index + 1}. ${task.name}n`;
content += ` Дата выполнения: ${completionDate}n`;
});
// Копируем в буфер обмена
App.copyToClipboard(content);
Notifier.success(`Выполненные задачи из колонки "${column.name}" скопированы в буфер обмена`);
}
// Добавляем кнопки экспорта при загрузке доски
Current.onBoardChange = function(oldBoard, newBoard) {
if (newBoard) {
newBoard.list().forEach(column => {
column.ui.clear();
const exportButton = createExportButton(column);
column.ui.add(exportButton);
});
}
};
// Добавляем кнопки на текущую доску при запуске скрипта
if (Current.board) {
Current.board.list().forEach(column => {
column.ui.clear();
const exportButton = createExportButton(column);
column.ui.add(exportButton);
});
}
Канал о фрилансе
Там я делюсь фишками о фрилансе: деньги, клиенты, автоматизация, фриланс. Раньше это был закрытый материал с платным доступом. Сейчас он открыт. Иногда подписчикам будет открываться платные материалы.
Подписаться