← Назад в «Блог»
72

Документация по конфигуратору Yougile

Эта страница создана, чтобы собрать воедино информацию по конфигуратору Yougile и иметь базу данных для обучения ИИ. Она будет пополняться по мере необходимости в новых переменных, методах или при появлении нового кода.

Как планирую использовать этот конфигуратор — наполню его, отдам все Claude AI, чтобы иметь своего разработчика по скриптам Yougile.

Я просто хочу, чтобы это стало доступно и другим пользователям. Потому что собрать документацию в одном месте, это сложно. Потрачен не один час, чтобы все выковырять и оформить.

Разработчики Yougile, если вы это читаете — пожалуйста выгрузите все методы и переменные API конфигуратора, чтобы её можно было просто скопировать одним кликом или скачать файлом.

Что такое конфигуратор?

Конфигуратор — это среда, в которой можно создавать javascript-код, который будет дополнять функциональность системы 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»).

Items.get (id)

Получает объект YouGile по его id. Объекты YouGile: компания, проекты, доски, колонки, задачи. Вы можете найти id объектов во вкладке «Навигатор по объектам».

// Getting a task and assigning it to variable “t” for later use
var t = Items.get('bb12a559-81ba-40ad-adda-81f2d017e71b');

// Appending “!” to the task’s name
t.name = t.name + '!';

Items.onAdd (newObject)

Эта функция вызывается сразу после добавления нового объекта в систему (задачи, колонки, доски, проекта).

Items.onAdd = function (obj) {
    if (obj.type === 'Task') { // only when a task was added
        // Changing the name of the new task
        obj.name = obj.name + ' ...';
    }
};

Items.onBeforeAdd (location, name)

Эта функция вызывается, перед добавлением нового объекта в систему. Первый аргумент — куда добавляется новый объект (напр. если добавляется задача, то в первом аргументе колонка, куда добавляется), второй аргумент — имя нового объекта. Если функция возвращает false, добвление отменяется.

Items.onBeforeAdd = function (a, s) {
    if (a.type === 'Column') { // only when a task is added to a column
        // Prevent task addition with name “test”
        if (s === 'test') {
            return false;
        }
    }
    // We return true because we want to proceed with the addition
    // If it is needed to prevent addition, then return false
    return true;
};

Items.onRename (item, newName)

Эта функиця вызывается, когда какой-нибудь объект переименовывается (задача, колонка, доска или проект).

Items.onRename = function (obj, n) {
    if (obj.type === 'Task') { // only for task renaming
        obj.name = n + '!!!';
        // Return false to stop the default action — renaming the
        // object to a name specified by the user (n variable in our case)
        return false;
    }
    // Make the default action in all other cases
    return true;
};

Items.onDelete (item)

Эта функция вызывается, когда юзер удаляет какой-нибудь объект (задачу, колонку, доску или проект).

Items.onDelete = function (object) {
    // Preventing deletion of objects whose names start with 'Important'
    if (object.name.slice(0, 9) === 'Important') {
        Notify.warn('You’re not allowed to delete important things!');
        return false;
    }
    // Otherwise proceed with deletion
    return true;
};

Items.onClick (item)

Эта функция вызывается, когла пользователь щелкает на задачу, заголовок колонки, вкладку доски или на проект.

Items.onClick = function (object) {
    Notifier.success('You’ve clicked ' + object.name);
    return true;
};

Items.onList (item, items)

Эта функция вызывается, когда система показывает список объектов внутри какого-то объекта. Например, когда отрисовывается список задач внутри колонки или список колонок внутри доски и так далее.

Items.onList = function (obj, arr) {
    // obj - is a container which child objects are going to be listed
    // arr - is its actual children objects
    if (obj.type === 'Column') {
        // In case of task list in a column
        // Here obj is a column and arr is all its tasks
        // We return only 5 elements from the array of tasks,
        // this result is going to be shown to the user
        // So the user is going to see no more than 5 tasks in every column
        return arr.slice(0, 5);
    }
    // Otherwise, we return the default
    return arr;
};

Items.onMove (item, from, to)

Эта функция вызывается, когда пользователь перемещает объект из одного места в другое (например, перемещает задачу из одной колонки в другую).

Items.onMove = function (obj, from, to) {
    // Getting some board by id
    var brd = Items.get('... board id here');
    
    // Suppose we want to disable moving tasks in this board
    // So we check if the parent object of 'from' is this board
    // If so, then 'from' object is some column inside this board
    // and that’s why 'obj' is some task inside this column which the user is
    // trying to move somewhere. So we return false to prevent it.
    if (from.up().id === brd.id) {
        Notifier.warn('Moving tasks inside ' + brd.name + ' board is disallowed');
        return false;
    }
    
    return true;
};

Items.onUpdate (item)

Эта функция вызывается когда какой-то объект системы был обновлён (проект, доска, колонка, задача). Учитываются не только обновления текущим пользователем, но и другие обновления, приходящие с сервера. Если произошло обновление чата, то вызывается эта функция с соответсвующей задачей в качестве аргумента.

var column = Items.get('...');

// Let’s store the number of tasks
// in this column in some variable
var tasksCount = column.list().length;

// If we want to recalculate tasksCount
// on changes, then we can do it as follows
Items.onUpdate = function (obj) {
    if (obj.id === column.id) {
        tasksCount = column.list().length;
    }
};

Items.isHidden (item)

Items.isHidden = function (x) {
    // We do not want to show some column to user Mike
    if (Current.user.email === 'mike@example.com') {
        // Better to compare id
        if (x.id === '... here goes column id') {
            return true;
        }
    }
    return false;
};

Items.allowDelete (item)

Если эта функция возвращает false для какого-нибудь объекта в какой-нибудь ситуации, то кнопка удаления не показывается для этого объекта (задачи, колонки, доски или проекта).

Items.allowDelete = function (object) {
    if (object.type === 'Column') {
        // Hide all delete buttons from all columns
        return false;
    }
    return true;
};

Items.allowAdd (item)

Если эта функция возвращает false для какого-нибудь объекта, тогда кнопка добавления не отображается внутри этого объекта.

Items.allowAdd = function (a) {
    // Remove “Add a new task” button from all columns
    if (a.type === 'Column') {
        return false;
    }
    return true;
};

Items.allowRename (item)

Если эта функция возвращает false для некоторого объекта, тогда он не может быть переименован через интерфейс.

Items.allowRename = function (object) {
    // Disallow renaming to a specific user
    if (Current.user.id === '... user id here') {
        return false;
    }
    return true;
};

Items.allowDrag (item)

Если эта функция возвращает false для объекта, то его нельзя перетаскивать.

Items.allowDrag = function (object) {
    // Disallow dragging for a specific user
    if (Current.user.id === '... user id here') {
        return false;
    }
    return true;
};

Примеры скриптов от разработчиков

Работа 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. Стикер должен быть типа и называться «Сумма».

/**
 * Скрипт для автоматического добавления стикера "Сумма" к задачам.
 * Работает только для указанного проекта.
 * Извлекает сумму из названия задачи, независимо от ее позиции.
 * Добавляет стикер "Сумма", если он доступен в системе.
 * Поддерживает различные форматы записи суммы: рубли, тысячи, со знаком ₽.
 */

// 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|к)?/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);
}

Полезно
2
1
Непонятно
Поделиться
Отправить
Линкануть
Вотсапнуть

Канал о фрилансе

Там я делюсь фишками о фрилансе: деньги, клиенты, автоматизация, фриланс. Раньше это был закрытый материал с платным доступом. Сейчас он открыт. Иногда подписчикам будет открываться платные материалы. Подписаться

← Назад в «Блог»

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *