### Install Teamwork App Code Source: https://github.com/1312inc/webasyst-teamwork/blob/main/README.md Clone the Teamwork app repository into the correct Webasyst directory. Ensure you are in the /PATH_TO_WEBASYST/wa-apps/ directory before executing the clone command. ```bash cd /PATH_TO_WEBASYST/wa-apps/ mkdir tasks git clone git://github.com/1312inc/Webasyst-Teamwork.git ./ ``` -------------------------------- ### Conditional Review Widget Display Source: https://github.com/1312inc/webasyst-teamwork/blob/main/templates/actions/log/Log.html Conditionally displays a review widget if the installer is available and the reviewWidget method exists. This is typically used for app reviews or setup guidance. ```html {if $wa->installer && method_exists($wa->installer, 'reviewWidget')} {$wa->installer->reviewWidget('app/tasks')} {/if} ``` -------------------------------- ### Register Teamwork App in Webasyst Source: https://github.com/1312inc/webasyst-teamwork/blob/main/README.md Add the 'tasks' entry to the `wa-config/apps.php` file to register the Teamwork app. This ensures Webasyst recognizes the app as installed. ```php 'tasks' => true, ``` -------------------------------- ### Get Task AI Drawer Source: https://github.com/1312inc/webasyst-teamwork/blob/main/templates/actions/tasks/TasksEdit.html Fetches and initializes the AI drawer for task fields. It handles caching, AJAX requests, and displays an alert for timeouts. ```javascript function getTaskAiDrawer(task_title, task_content) { if (taskAiDrawer) { return $.Deferred().resolve(taskAiDrawer).promise(); } if (taskAiDrawerPromise) { return taskAiDrawerPromise; } taskAiDrawerPromise = $.ajax({ url: '?module=tasks&action=ai&type=taskfields&html=1', method: 'GET', dataType: 'json', timeout: 10000 }) .then(function (response) { taskAiDrawer = $.waDrawer({ html: response.data?.html, esc: false, onBgClick: function () { hideTaskAiDrawer(); }, onOpen: function ($wrapper) { initTaskAiDrawer($wrapper, task_title, task_content); } }); return taskAiDrawer; }) .fail(function (_jqXHR, textStatus) { if (textStatus === 'timeout') { $.waDialog.alert({ text: '[`Could not connect to Webasyst AI service. Please try again in a minute.`]' }); } }) .always(function () { taskAiDrawerPromise = null; }); return taskAiDrawerPromise; } ``` -------------------------------- ### Backend Template Logic and Initialization Source: https://github.com/1312inc/webasyst-teamwork/blob/main/templates/actions/backend/Backend.html This snippet shows backend template logic for setting variables, iterating through priorities, and initializing the TasksController with account details, priorities, and user information. It also includes asset loading and header rendering. ```html {$_app_name = $wa->appName(true)} {$_account_name = $wa->accountName(true)} {$_title = $wa->title()|escape} {$loc = explode('__', $wa->locale())} {$wa->title()|default:$_app_name} — {$_account_name} {strip}{$priorities = []} {foreach $wa->tasks->config('priorities') as $priority} {$priorities[$priority.value] = _w($priority.name)|escape} {/foreach}{/strip} window.TasksController.initBeforeJQuery({ accountName: {$wa->accountName(false)|json_encode}, priorities: {$priorities|json_encode}, app_icons: {$app_icons|json_encode}, contact_id: {$wa->user('id')}, is_admin: {if !empty($is_admin)}{$is_admin}{else}0{/if}, text_editor: {$text_editor|json_encode} }); window.static_url = '{$wa_app_static_url}'; {$wa->css()} {$wa->js()} {if $wa->locale() != 'en_US'} {/if} {"* @event backend_assets.%plugin_id% "} {foreach $backend_assets as $item} {$item} {/foreach} {$wa->header()} ``` -------------------------------- ### Initialize Task AI Drawer with Event Bindings Source: https://github.com/1312inc/webasyst-teamwork/blob/main/templates/actions/tasks/TasksEdit.html Initializes the task AI drawer, setting up initial values, and binding various event handlers for user interactions like trying free AI, pasting, copying, and hiding the drawer. ```javascript function initTaskAiDrawer($wrapper, task_title, task_content) { const AUTO_VALUE = '__auto__'; let try_free = !$wrapper.find('.js-premium-wrapper').length; let lastGeneratedContent = ''; let lastGeneratedHtml = ''; bindTaskAiDrawerHideControls($wrapper); if (!try_free) { $wrapper.find('.js-premium-wrapper').show(); $wrapper.find('.dialog-ai-fields-container-wrapper').addClass('hidden'); $wrapper.find('.drawer-footer').addClass('hidden'); $wrapper.find('.drawer-header').addClass('hidden'); } $wrapper.find('name="objective"').val(task_title + '\n' + task_content); $wrapper.find('.js-try-free-ai').off('.taskAiDrawer').on('click.taskAiDrawer', function() { const $self = $(this); $.post('?module=tasks&action=ai&type=tryFree', (r) => { if (r.status === 'ok') { if (r.data.is_max_count) { $wrapper.find('.js-after-try-free-ai').show(); $self.remove(); } $wrapper.find('.js-premium-wrapper').hide(); $wrapper.find('.dialog-ai-fields-container-wrapper').removeClass('hidden'); $wrapper.find('.drawer-footer').removeClass('hidden'); $wrapper.find('.drawer-header').removeClass('hidden'); try_free = true; } }); }); $wrapper.find('.js-paste-ai-answer-result').off('.taskAiDrawer').on('click.taskAiDrawer', function(event) { event.preventDefault(); if (!lastGeneratedContent) { return; } window.lexicalEditor.updateContent('redactor-task-{$task_uuid}', lastGeneratedContent, { isMarkdown: true }); hideTaskAiDrawer(); }); $wrapper.find('.js-copy-ai-answer-result').off('.taskAiDrawer').on('click.taskAiDrawer', function(event) { event.preventDefault(); if (!lastGeneratedContent) { return; } copyTextToClipboard(lastGeneratedContent, lastGeneratedHtml) .then(function () { showTaskAiCopyNotification({ className: 'success', content: '[`Copied`]' }); }) .catch(function () { showTaskAiCopyNotification({ className: 'danger', content: '[`Copying error`]' }); }); }); $wrapper.find('.js-write-ai-action').off('.taskAiDrawer').on('click.taskAiDrawer', function () { if (!try_free) { return false; } const $error_block = ``` -------------------------------- ### Gantt Chart Initialization (JavaScript) Source: https://github.com/1312inc/webasyst-teamwork/blob/main/templates/actions/milestones/MilestonesDefault.html Initializes a GanttChart instance using provided milestone data and configuration options. Requires the GanttChart library to be loaded. ```javascript (() => { const milestones = {json_encode(array_values($milestones))}; new GanttChart({ data: milestones, leftColId: 'left-col', timelineId: 'timeline', rightWrapperId: 'right-wrapper', timelineHeaderId: 'timeline-header', zoomSliderId: 'zoom-slider', locale: '{$wa->locale()}' }); })(); ``` -------------------------------- ### Get Spellcheck Source HTML Source: https://github.com/1312inc/webasyst-teamwork/blob/main/templates/actions/tasks/TasksEdit.html Retrieves the HTML content from the lexical editor input or the escaped value of the main element. Used as the source for spellchecking. ```javascript function getSpellcheckSourceHtml () { const $editorInput = $el.parent().find('.lexical-editor .editor-input').first(); const sourceHtml = $editorInput.length ? $editorInput.html().trim() : ''; if (sourceHtml) { return sourceHtml; } const escapedHtml = escapeHtml($el.val()).replace(/\r?\n/g, '
'); return '
' + escapedHtml + '
'; } ``` -------------------------------- ### Initialize Task Information Source: https://github.com/1312inc/webasyst-teamwork/blob/main/templates/actions/tasks/TasksInfo.html Initializes task-specific JavaScript objects and sets up navigation and title elements. This code should be executed on page load. ```javascript ( function($) { window.Tasks = { }; $.tasks.forceHash("#/task/{$task.project_id}.{$task.number}/"); $.tasks.setTitle({$task.name|json_encode}); $.tasks.setAppLinksOptions({$links_data|json_encode}); })(jQuery); ``` -------------------------------- ### Add Project Link Source: https://github.com/1312inc/webasyst-teamwork/blob/main/templates/actions/settings/SettingsSidebar.html Provides a link to add a new project. ```html ##### [\`Projects\`] [](#/settings/project/add/) ``` -------------------------------- ### Log Navigation and Filtering Initialization Source: https://github.com/1312inc/webasyst-teamwork/blob/main/templates/actions-legacy/log/Log.html Initializes log navigation, sets up time frame selectors, and updates filter links based on the current hash, ensuring correct navigation and display of selected filters. ```javascript (function() { "use strict"; var $main_menu = $('.t-header-wrapper'); var list_hash = $.tasks.cleanHash(window.location.hash) || '#/log/'; var list_params = list_hash.substr(6).split('/') [0]; initLogsTimeframeSelector($('.t-logs-timeframe')); // see d3chart-logs.js // Set on filters $main_menu.find(".t-nav-item[data-value]").each(function() { var $nav_item = $(this), filter_id = $nav_item.closest(".t-menu-item").data("filter-id"), params = $.tasks.replaceParam(list_params, filter_id, $nav_item.data('value')), item_hash = '#/log/'+params+(params ? '/' : ''); $nav_item.find("a").attr('href', item_hash); if (item_hash == list_hash) { $nav_item.closest(".t-menu-item").find(".t-selected-item").html($nav_item.find('a').html()); } }); })(); ``` -------------------------------- ### Get Repeat Measure Title Source: https://github.com/1312inc/webasyst-teamwork/blob/main/templates/actions/tasks/TasksEdit.html Retrieves the localized title for a repeat measure based on its name and frequency. It uses predefined titles and pluralization rules to return the correct string. ```javascript function getRepeatMeasureTitle(measure, frequency) { const measureTitles = repeatMeasureTitles[measure]; if (!measureTitles) { return ''; } const pluralCategory = repeatPluralRules ? repeatPluralRules.select(frequency) : (frequency === 1 ? 'one' : 'other'); return measureTitles[pluralCategory] || measureTitles.other || measureTitles.many || measureTitles.few || measureTitles.one || ''; } ``` -------------------------------- ### JavaScript: Get Status Data for Selector Source: https://github.com/1312inc/webasyst-teamwork/blob/main/templates/actions/reports/ReportsLtdc.html Retrieves the currently selected status from a UI element or URL hash. Handles cases where no status is explicitly selected by finding a default or throwing an error. ```javascript function getStatusData($li) { var result; if (!$li || !$li.length) { $li = $wrapper.find('ul li.selected').first(); if (!$li.length) { // Determine active timeframe from url hash and find corresponding
  • var params = window.location.hash.substring(hash_prefix.length).split('/')[0] || ''; var exploded_params = $.tasks.deparam(params); if (exploded_params[field_id]) { $wrapper.find('ul li').each(function() { var status_data = getStatusData($(this)); console.log(status_data, exploded_params); if (status_data.status == exploded_params[field_id]) { $li = $(this); return false; } }); if ($li) { result = { $li: $li, status: exploded_params[field_id], }; return result; } } } if (!$li.length) { $li = $wrapper.find('ul li[data-default-choice]').first(); } if (!$li.length) { throw new Error("Something's badly wrong with the status selector."); } } result = { $li: $li, status: ($li && $li.data('status-id')) || '', }; return result; } ``` -------------------------------- ### Initialize Backend JavaScript Controller Source: https://github.com/1312inc/webasyst-teamwork/blob/main/templates/actions-legacy/backend/Backend.html Initializes the TasksController with account details, priorities, app icons, and contact ID. This script runs before jQuery is fully loaded. ```html {$_app_name = $wa->appName(true)} {$_account_name = $wa->accountName(true)} {$_title = $wa->title()|escape} {$wa->title()|default:$_app_name} — {$_account_name} {strip}{$priorities = []} {foreach $wa->tasks->config('priorities') as $priority} {$priorities[$priority.value] = $priority.name|escape} {/foreach}{/strip} window.TasksController.initBeforeJQuery({ accountName: {$wa->accountName(false)|json_encode}, priorities: {$priorities|json_encode}, app_icons: {$app_icons|json_encode}, contact_id: {$wa->user('id')} }); {$wa->css()} {$wa->js()} {if $wa->locale() != 'en_US'} {/if} {\* @event backend_assets.%plugin_id% *} {foreach $backend_assets as $item} {$item} {/foreach} {if empty($sidebar_width)} {$sidebar_width = 200} {/if} ``` -------------------------------- ### Normalize and Get Comparable Text for AI Source: https://github.com/1312inc/webasyst-teamwork/blob/main/templates/actions/tasks/TasksEdit.html Normalizes text by trimming whitespace and replacing multiple spaces with single ones. Retrieves text from either a lexical editor input or a standard input value. ```javascript function normalizeWriteAiText (value) { return String(value || '') .replace(/\s+/g, ' ') .trim(); } function getWriteAiComparableText () { const $editorInput = $el.parent().find('.lexical-editor .editor-input').first(); if ($editorInput.length) { return normalizeWriteAiText($editorInput.text()); } return normalizeWriteAiText($el.val()); } ``` -------------------------------- ### Get Timeframe Data Source: https://github.com/1312inc/webasyst-teamwork/blob/main/templates/actions/reports/ReportsCfd.html Retrieves the selected timeframe and grouping data from the UI. It handles default selections, URL hash parameters, and custom date ranges. Use this function to determine the current reporting period. ```javascript function getTimeframeData($li) { var result; if (!$li || !$li.length) { $li = $wrapper.find('ul li.selected').first(); if (!$li.length) { // Determine active timeframe from url hash and find corresponding
  • var params = window.location.hash.substring(hash_prefix.length).split('/')[0] || ''; var timeframe = $.tasks.deparam(params); if (timeframe.timeframe && timeframe.groupby) { $wrapper.find('ul li').each(function() { var tf = getTimeframeData($(this)); if (tf.timeframe == timeframe.timeframe && tf.groupby == timeframe.groupby) { $li = $(this); return false; } }); if ($li) { result = { $li: $li, timeframe: timeframe.timeframe, groupby: timeframe.groupby }; if (result.timeframe == 'custom') { result.from = timeframe.from || null; result.to = timeframe.to || null; } return result; } } } if (!$li.length) { $li = $wrapper.find('ul li[data-default-choice]').first(); } if (!$li.length) { // throw new Error("Something's badly wrong with the timeframe selector."); } } result = { $li: $li, timeframe: ($li && $li.data('timeframe')) || 30, groupby: ($li && $li.data('groupby')) || 'days' }; if (result.timeframe == 'custom') { result.from = $custom_wrapper.find('[name="from"]') .datepicker('getDate'); if (result.from) { result.from = result.from.getTime() / 1000; } result.to = $custom_wrapper.find('[name="to"]') .datepicker('getDate'); if (result.to) { result.to = result.to.getTime() / 1000; } } return result; } ``` -------------------------------- ### Initialize TaskEdit Script Source: https://github.com/1312inc/webasyst-teamwork/blob/main/templates/actions-legacy/tasks/TasksEdit.html Initializes the TaskEdit JavaScript class. This script should be placed before other plugin code to ensure proper initialization. It sets up task details, user information, and locale-specific messages. ```javascript (function($) { "use strict"; {if $task.id} $.tasks.setTitle({$task.name|json_encode}); {/if} {$_files_hash = md5(uniqid($task.name, true))} window.taskEdit = new TaskEdit({ $task: $(".t-task-page-wrapper"), task_id: {$task.id|default:'null'}, is_page: {if $is_page}true{else}false{/if}, priority: "{$task.priority}", project_color: "{$project.color}", project_id: "{$task.project_id}", projects_users: {json_encode($projects_users)}, projects_priority_users: {json_encode($projects_priority_users)}, files_hash: {$_files_hash|json_encode}, messages: { date_invalid: {"_w('Invalid date.')|json_encode} } }); $.wa.locale = $.extend($.wa.locale || {}, { "unsaved_data": "[\`You are about to abandon task editor without saving the task. All unsaved data will be lost. Are you sure?\`]", "saved_data_draft": "[\`You are about to leave the page without saving the task. Are you sure?\`]", "unsaved_task": "[\`There are unsaved task draft.\`]", "continue_editing": "[\`Do you want to continue editing?\`]" }); })(jQuery); {strip} ``` -------------------------------- ### Show Filter Options Source: https://github.com/1312inc/webasyst-teamwork/blob/main/templates/actions/tasks/includes/TasksHeaderFiltersHelpers.inc.html Renders a list of filter options, including icons and user pictures if available. Initializes a dropdown for the filter menu. ```smarty {function show_filter filter=[] class='' data_keys=[]}{ strip} *  [](javascript:void(0);) {foreach $filter.options as $o}* [{if !empty($o.icon_html) && ifempty($o.icon) != 'unknown'} {$o.icon_html} {/if} {if !empty($o.project.icon_html) && ifempty($o.project.icon) != 'unknown'} {$o.project.icon_html} {/if} {if array_key_exists('photo', $o)} {show_userpic o=$o} {/if} {$o.name|escape|truncate:42}](javascript:void(0);) {/foreach} ( function($) { $("#t-menu-dropdown-{$filter.id}").waDropdown( { hover: false } ); })(jQuery); {/strip}{/function} ``` -------------------------------- ### Conditional Upgrade URL Generation Source: https://github.com/1312inc/webasyst-teamwork/blob/main/templates/actions/upgrade/Upgrade.html Generates the appropriate upgrade URL based on whether the Webasyst installation is cloud-hosted or self-hosted. For cloud, it points to the general hosting upgrade page. For self-hosted, it constructs a specific upgrade URL including the domain. ```html {$_is_premium = $wa->tasks->isPremium()} {$_is_cloud = $wa->tasks->isCloud()} {$_premium_pricing = $wa->tasks->getPremiumPricing()} {if $_is_cloud} {$_upgrade_url = '[`https://www.webasyst.com/my/hosting/`]'} {else} {$_upgrade_url = '[`https://www.webasyst.com/my/buy/upgrade-to-premium/tasks/`]?domain='|cat:str_replace('http://','',str_replace('https://','',$wa->domainUrl()))} {/if} ``` -------------------------------- ### Webasyst Teamwork Custom Fields Logic Source: https://github.com/1312inc/webasyst-teamwork/blob/main/templates/actions/fields/Fields.html This snippet demonstrates the backend logic for handling custom fields in Webasyst Teamwork tasks, including checking premium status, generating unique IDs, and defining functions to get field options and input elements. It iterates through task types to determine checked status for field links. ```html {$\_we\_are\_all\_in = $wa->tasks->isPremium()} {$\_wrapper\_id = uniqid('t-settings-task-fields-wrapper')} {function get\_options field = \[ \]} {$\_i\_name = "fields\[{$field.id}\]"} {foreach ifset($field, 'data', 'values', \[ \]) as $\_value} {/foreach} {/function} {function get\_input\_elements field = \[ \]} {$i\_name = "fields\[{$field.id}\]"} {foreach $input\_elements as $\_input\_element} {$\_input\_element} {/foreach} {foreach $task\_types as $\_task\_type} {$\_checked = false} {if in\_array($\_task\_type.id, ifset($type\_field\_links, $field.id, \[ \]))} {$\_checked = true} {/if} {$\_task\_type.name} {/foreach} {/function} {$\_sidebar\_html|default:''} {if !$\_we\_are\_all\_in} ### \[\`Premium only\`\] \[\`Upgrade to premium to have the feature enabled and this message to disappear permanently.\`\] \[\[\`See what’s there in premium\`\]\]({$wa_app_url}#/upgrade/) * * {/if} \[\`Custom fields\`\] ===================== \[\`Task custom fields depend on task types so make sure create at least one task type first.\`\] \[\[\`New field\`\]\](javascript:void(0)) {foreach $fields as $field}* {get\_input\_elements field = $field} {get\_options field = $field} * * {/foreach} ``` -------------------------------- ### Initialize Personal Tasks Settings Source: https://github.com/1312inc/webasyst-teamwork/blob/main/templates/actions/settings/SettingsPersonal.html Initializes the personal tasks settings interface using jQuery and a TasksSettingsPersonal class. Ensure jQuery is loaded before this script. ```javascript (function ($) { new TasksSettingsPersonal({ $wrapper: $('#{$wrapper_id}') }); })(jQuery); ``` -------------------------------- ### Project List Rendering Source: https://github.com/1312inc/webasyst-teamwork/blob/main/templates/actions/settings/SettingsSidebar.html Dynamically renders a list of projects with their names and icons. Includes conditional rendering for projects with colors. ```html {foreach $projects as $p}* [{strip} {$p.icon\_html} {$p.name|default:'[\`(no name)\`]'|escape} {if !empty($p.color) && ($p.color != "t-white")} {/if} {/strip}](#/settings/project/{$p.id}/) {/foreach} ``` -------------------------------- ### TaskEdit Initialization Source: https://github.com/1312inc/webasyst-teamwork/blob/main/templates/actions/tasks/TasksEdit.html Initializes the TaskEdit JavaScript class with various task-related properties. This script should be placed at the beginning of the HTML to ensure proper initialization. ```javascript {$is_page = !$wa->get("is_dialog")} {$task_uuid = $task.uuid|default:tasksUuid4::generate()} {$loc = explode('_', $wa->locale())} window.taskLinks = {$links_data|json_encode}; (function($) { "use strict"; {if $task.id} $.tasks.setTitle({$task.name|json_encode}); {/if} {$_files_hash = md5(uniqid($task.name, true))} window.taskEdit = new TaskEdit({ $task: $(".t-task-page-wrapper"), task_id: {$task.id|default:'null'}, task_uuid: '{$task_uuid}', is_page: {if $is_page}true{else}false{/if}, priority: "{$task.priority}", project_color: "{$project.color}", project_id: "{$task.project_id}", projects_users: {json_encode($projects_users)}, projects_priority_users: {json_encode($projects_priority_users)}, files_hash: {$_files_hash|json_encode}, milestones: {$milestones|json_encode}, messages: { date_invalid: {"_w('Invalid date.')|json_encode"} } }); $.wa.locale = $.extend($.wa.locale || {}, { "unsaved_data": "[`You are about to abandon task editor without saving the task. All unsaved data will be lost. Are you sure?`]", "saved_data_draft": "[`You are about to leave the page without saving the task. Are you sure?`]", "unsaved_task": "[`There are unsaved task draft.`]", "continue_editing": "[`Do you want to continue editing?`]", "attach_pl_checklist": "[`To attach a Pocket Lists checklist, save the task first.`]", }); })(jQuery); {strip} ``` -------------------------------- ### Doing Now Widget Template (Legacy 1.3) Source: https://github.com/1312inc/webasyst-teamwork/blob/main/widgets/doingnow/templates/Default.html Template for displaying recent user task activity in the 'Doing Now' widget for legacy UI versions (1.3). It shows user names, time since update, and task details. ```html {else} {\* 1.3 LEGACY *} ###### {\_wp($title)|escape} {foreach $users as $user} * {$user.contact.name|escape} {\_wp($user.last_log.create_datetime)|escape} [{$user.last_log.project_id}.{$user.last_log.task_number} {$user.last_log.task_name|truncate:128}]({rtrim($url_root_absolute, '/')}{$wa_backend_url}tasks/#/task/{$user.last_log.project_id}.{$user.last_log.task_number}/) {/foreach} {/if} ``` -------------------------------- ### Initialize Rich Text Editor and Handle Drafts Source: https://github.com/1312inc/webasyst-teamwork/blob/main/templates/actions/tasks/TaskStatusForm.DropArea.html Initializes a lexical editor instance for comments, handling rich text mode, task ID, draft storage, placeholder, image uploads, mentions, and language settings. It also loads draft content and sets up textarea autocomplete for markdown mode. ```javascript (function () { if (lexicalEditor) { const $el = document.getElementById('redactor-comment-{$ident}'); const action = $el.closest('form').dataset.taskAction; lexicalEditor.createLexicalInstance({ el: $el, isRichMode: TasksController ? TasksController.options.text_editor === 'wysiwyg' : false, taskId: '{if !empty($task)}{$task.id}{/if}', storage: TasksController ? TasksController.getDraftKeyActionText({if !empty($task)}{$task.id}{else}null{/if}, action) : '', placeholder: '[`Add comment`]', imageUploadUrl: '?module=attachments&action=imageUpload', imageUploadTaskUuid: document.querySelector('.article').dataset.taskUuid || '', mentions: {}, mentionsFetchUrlWA: true, lang: '{$loc[0]}' }); // Load draft TasksController.storageDataInMdMode($el, 'getDraftKeyActionText', {if !empty($task)}{$task.id}{else}null{/if}, action); if(TasksController.options.text_editor === 'markdown') { $($el).textareaAutocomplete({ urlEntity: '?module=tasks&action=entityAutocomplete', urlMention: '?module=tasks&action=mentionAutocomplete', autoFocus: false, delay: 300 }); } } })(); ``` -------------------------------- ### Initialize Kanban Task Settings Source: https://github.com/1312inc/webasyst-teamwork/blob/main/templates/actions/kanban/KanbanHideFilter.inc.html Initializes Kanban task settings, likely for managing task limits and milestones. This code should be used within a context where jQuery is available. ```javascript (function () { var kanban_task_settings = new KanbanTaskSettings({$limits}, {$milestone_id}); kanban_task_settings.init(); })(jQuery); ``` -------------------------------- ### Render Backend Header and Sidebar Source: https://github.com/1312inc/webasyst-teamwork/blob/main/templates/actions-legacy/backend/Backend.html Renders the main header, potentially with asynchronous loading, and includes the sidebar content from a separate file. ```html {$wa->header()|replace:'>':' async>'} {\* SIDEBAR *} {include file="./Sidebar.html" inline} ``` -------------------------------- ### Pagination and Load More Source: https://github.com/1312inc/webasyst-teamwork/blob/main/templates/actions-legacy/log/Log.html Displays pagination information and a 'Load more...' link if more logs are available and lazy loading is enabled. ```html {sprintf("[\`%s of %d\`]", _w('%d record','%d records', $offset + $count), $total_count)} {if $click_to_load_more && $next_page_url} [[\`Load more...`]] (javascript:void(0)) {/if}  [\`Loading...`] ``` -------------------------------- ### Initialize AI Drawer for Task Content Source: https://github.com/1312inc/webasyst-teamwork/blob/main/templates/actions/tasks/TasksEdit.html Handles the click event for a button that generates an AI response for a task. It retrieves task title and content, then calls a function to display an AI drawer. ```javascript $writeAiBtn.on('click', function (e) { e.preventDefault(); const $form = $(this).closest('form'); const task_title = $form.find('[name="data[name]"]').val(); const task_content = $form.find('[name="data[text]"]').val(); getTaskAiDrawer(task_title, task_content).then(function (drawer) { if (drawer && !drawer.is_visible) { drawer.show(); } }); }); ``` -------------------------------- ### Initialize Task Assignment and Role Management Source: https://github.com/1312inc/webasyst-teamwork/blob/main/templates/actions/tasks/TasksEdit.html Initializes the team list for task assignment and sets up the RoleManager for handling user roles and permissions. This script is executed when the DOM is ready and handles project and user selections. ```javascript async function ($) { var initialProjectID = $("#projectSelect").find("li:first a").data('value'); const teamList = await $.tasks.teamList({ container: $("#t-edit-task-form .t-team-list-wrapper"), targetField: $("#t-edit-task-form [name="data[assigned_contact_id]"]"), inviteField: $("#t-edit-task-form .t-team-invite"), projectId: {if !empty($task.id)}{$task.project_id}{else}initialProjectID{/if}, assignedContactId: {if !empty($task.id)}{$task.assigned_contact_id|default:"\"\""}{else}""{/if}, updateMode: {if !empty($task.id)}true{else}false{/if} }); if (teamList.length) { const RoleManager = { $chipsWrapper: $(".t-team-roles-list-wrapper .chips"), isTaskUpdate: {if !empty($task.id)}true{else}false{/if}, /** * Экранирует HTML */ escapeHtml: function(text) { const div = document.createElement('div'); div.textContent = text; return div.innerHTML; }, /** * Проверяет, добавлен ли пользователь к роли */ isUserInRole: function(roleId, userId) { return this.$chipsWrapper.find(`li[data-role-id="${ roleId }"][data-user-id="${ userId }"]`).length > 0; }, /** * Создает HTML для chip элемента пользователя с ролью */ createUserChip: function(roleId, roleColor, userId, userPhotoUrl, userName, roleName) { const safeUserName = this.escapeHtml(userName); const safeRoleName = this.escapeHtml(roleName); const safePhotoUrl = this.escapeHtml(userPhotoUrl); const $li = $('
  • ', { 'data-role-id': roleId, 'data-user-id': userId, 'style': ` --menu-link-color: ${ roleColor }; --menu-link-color-hover: ${ roleColor }; --chips-background-color: var(--background-color-blank); --chips-background-color-hover: var(--background-color-blank); ` }); const $link = $('', { href: 'javascript:void(0);' }); $link.append($('', { src: userPhotoUrl, alt: safeUserName, class: 'userpic' })); $link.append($('', { text: safeUserName })); $link.append($('', { class: 'custom-ml-4 hint', text: safeRoleName })); $link.append($('', { class: 'fas fa-times custom-ml-16 custom-mr-4 js-remove-user' })); const $input = $('', { type: 'hidden', name: `data[roles_user][add][${ roleId }][]`, value: userId }); $li.append($link).append($input); return $li; }, /** * Добавляет пользователя к роли */ addUserToRole: function(roleId, roleColor, userId, userPhotoUrl, userName, roleName) { if (this.isUserInRole(roleId, userId)) { return false; } const $chip = this.createUserChip(roleId, roleColor, userId, userPhotoUrl, userName, roleName); this.$chipsWrapper.prepend($chip); return true; }, /** * снимает роль с пользователя */ removeUserFromRole: function($chip) { const roleId = $chip.data('role-id'); const userId = $chip.data('user-id'); $chip.remove(); if (this.isTaskUpdate) { const $removeInput = $('', { type: 'hidden', name: `data[roles_user][remove][${ roleId }][]`, value: userId }); this.$chipsWrapper.prepend($removeInput); } }, init: function() { const self = this; this.$chipsWrapper.on('click', '.js-remove-user', function(event) { event.preventDefault(); event.stopPropagation(); const $chip = $(this).closest('li'); self.removeUserFromRole($chip); }); } }; RoleManager.init(); const $roleDropdowns = RoleManager.$chipsWrapper.find('.js-role-dropdown'); $roleDropdowns.waDropdown({ hover: true, items: '.menu > li > span', update_title: false }); function updateUserListFragment(users) { const userListFragment = document.createDocumentFragment(); $roleDropdowns.find('.menu').empty(); users.forEach(user => { const $li = $('
  • ', { 'data-user-id': user.id, 'data-user-photo-url': user.photo_url, 'data-user-name': user.name }); const $span = $('', { class: 'item' }); $span.append($('', { class: 'icon userpic userpic20', css: { 'background-image': `url('${user.photo_url}')` } })); $span.append($('', { text: user.name })); $li.append($span); userListFragment.appendChild($li[0]); }); $roleDropdowns.find('.menu').append(userListFragment); } $(document).on('tasks:projectUsersChanged', function(event, users) { updateUserListFragment(users); }); updateUserListFragment(teamList); $roleDropdowns.on('click', '.menu li', function(event) { event.preventDefault(); event.stopPropagation(); const $this = $(this); const $dropdown = $this.closest('.js-role-dropdown'); RoleManager.addUserToRole( $dropdown.data('role-id'), $dropdown.data('role-color'), $this.data('user-id'), $this.data('user-photo-url'), $this.data('user-name'), $dropdown.data('role-name') ); }); } }(jQuery); ``` -------------------------------- ### Initialize Tasks Updater Source: https://github.com/1312inc/webasyst-teamwork/blob/main/templates/actions/tasks/Tasks.html Initializes the tasks updater with a given URL. This is typically used for background updates. ```javascript (function() { "use strict"; $.tasks.initTasksUpdater({$updater_url|json_encode}); })(); ``` -------------------------------- ### Header Link HTML for other UI versions Source: https://github.com/1312inc/webasyst-teamwork/blob/main/plugins/templates/templates/component/header/HeaderLink.html Renders a list of template links for UI versions other than 1.3. Includes a placeholder for JavaScript initialization. ```html {else} [\`Template\`] {foreach $templates as $template}* [{$template.name|escape}](# "{$template.name|escape}") {/foreach} (function ($) { $('#{$wrapper_id} .dropdown').waDropdown({ hover: false, items: ".menu > li > a", }); var $wrapper = $("#{$wrapper_id}"); new TasksTemplatesPluginTaskEdit({ $wrapper: $wrapper }); })(jQuery); {/if} ``` -------------------------------- ### Initialize Tasks Application Source: https://github.com/1312inc/webasyst-teamwork/blob/main/templates/actions/tasks/Tasks.html Initializes the main Tasks application object and its header component. It sets up initial counts, list status, and messages. ```javascript (function() { "use strict"; window.Tasks = {}; window.tasksHeader = new TasksHeader({ total_count: {$total_count|json_encode}, is_in_my_list: {$is_in_my_list|json_encode}, hash_type: '{$hash_type}', entity_id: '{$entity_id}', messages: { cant_create_list: "[\`Cannot create a list from another list.\`]", tasks_count: {"_w('%d task','%d tasks', $total_count)|json_encode}, no_tasks: {"_w('no tasks')|json_encode}, } }); })(); ``` -------------------------------- ### Initialize Preview Mode Source: https://github.com/1312inc/webasyst-teamwork/blob/main/plugins/preview/templates/common-legacy/Link.html Initializes the TasksPreviewPlugin with a wrapper element and a task ID. This is used to enable preview functionality for specific tasks. ```javascript {$wrapper_id = uniqid('t-plugin-preview-wrapper')} [\[`Preview`\]](javascript:void(0); "[`Preview mode`]") new TasksPreviewPlugin({ '$wrapper': $("#{$wrapper_id}"), 'task_id': {$task.id|json_encode} }); ``` -------------------------------- ### Initialize Lazyloader and PrettyPrint for Tasks Source: https://github.com/1312inc/webasyst-teamwork/blob/main/templates/actions-legacy/tasks/Tasks.html Initializes lazy loading for tasks and PrettyPrint for task content. This script must be within the '#t-tasks-wrapper' for lazy loading to function correctly. It requires jQuery. ```javascript (function($) { "use strict"; {\* Note that this script has to be inside #t-tasks-wrapper for lazy loading to work correctly. \*} $.tasks.initLazyloader({ lazyloading_wrapper: $('#end-of-tasks'), next_page_url: {$next_page_url|json_encode}, is_lazy: {if $click_to_load_more}false{else}true{/if}, list_selector: '#t-tasks-wrapper', item_id_data_attr: 'task-id' }); {\* Init PrettyPrint on Tasks List \*} $.tasks.initPrettyPrint(); })(jQuery); ``` -------------------------------- ### Log Pagination and Load More Source: https://github.com/1312inc/webasyst-teamwork/blob/main/templates/actions/log/Log.html Handles pagination for log entries, displaying the current range of records out of the total count. Includes a 'Load more...' button for fetching additional logs if available. ```html {if $click_to_load_more && $next_page_url} {sprintf("[ %s of %d ]", _w('%d record','%d records', $offset + $count), $total_count)} [[ Load more... ]](javascript:void(0)) {/if} ``` -------------------------------- ### Initialize TasksHeader Source: https://github.com/1312inc/webasyst-teamwork/blob/main/templates/actions-legacy/tasks/Tasks.html Initializes the TasksHeader component with total count, list status, and custom messages. Ensure the script is placed correctly for proper initialization. ```javascript (function() { "use strict"; window.Tasks = { }; window.tasksHeader = new TasksHeader({ total_count: {$total_count|json_encode}, is_in_my_list: {$is_in_my_list|json_encode}, messages: { cant_create_list: "[\`Cannot create a list from another list.\`]", tasks_count: {"_w('%d task','%d tasks', $total_count)|json_encode}, no_tasks: {"_w('no tasks')|json_encode}, } }); })(); ``` -------------------------------- ### Initialize TasksPreviewPlugin Source: https://github.com/1312inc/webasyst-teamwork/blob/main/plugins/preview/templates/common/Link.html Instantiates the TasksPreviewPlugin with a unique wrapper ID and the current task ID. This is used to enable preview mode for tasks. ```javascript {$wrapper_id = uniqid('t-plugin-preview-wrapper')} [[\`Preview\`\]](javascript:void(0); "[\`Preview mode\`]") new TasksPreviewPlugin({ '$wrapper': $(\'#{$wrapper_id}\'), 'task_id': {$task.id|json_encode} }); ``` -------------------------------- ### Task List Skeleton Loading Source: https://github.com/1312inc/webasyst-teamwork/blob/main/templates/actions/backend/Backend.html Initializes the loader with task or list skeletons, or a generic loading icon if the entity is not specified. ```javascript (function() { var $loaderPlace = document.querySelector('#loaderPlace'); if($loaderPlace){ $loaderPlace.innerHTML = window.loadingEntity === 'task' ? document.querySelector('#taskSkeleton').innerHTML : window.loadingEntity === 'list' ? document.querySelector('#tasksListSkeleton').innerHTML : ''; } })(); ``` -------------------------------- ### Show My Lists and Selection Menu Source: https://github.com/1312inc/webasyst-teamwork/blob/main/templates/actions/tasks/includes/TasksHeaderFilters.inc.html jQuery code to show 'My Lists' and 'Selection Menu' elements. ```javascript (function ($) { $('#t-my-lists-box').show(); $('#t-selection-menu').show(); })(jQuery); ``` -------------------------------- ### Doing Now Widget CSS (Legacy 1.3) Source: https://github.com/1312inc/webasyst-teamwork/blob/main/widgets/doingnow/templates/Default.html Includes the legacy CSS for the 'Doing Now' widget for versions prior to UI 2.0. This ensures backward compatibility. ```css {include file='../css/doingnow.css' inline} ``` -------------------------------- ### Initialize Dropdown Menu Source: https://github.com/1312inc/webasyst-teamwork/blob/main/templates/actions/tasks/Tasks.html Initializes a dropdown menu for the selection menu in tasks. This script should be placed within the appropriate HTML context. ```javascript ( function($) { $("#t-selection-menu .dropdown").waDropdown({ hover: false, hide: false }); } )(jQuery); ``` -------------------------------- ### JavaScript for Project Ordering Source: https://github.com/1312inc/webasyst-teamwork/blob/main/templates/actions-legacy/settings/SettingsOrdering.html Handles the distribution of projects between important and regular lists, manages checkbox changes to move projects, and animates the movement. It also initializes drag-and-drop for the important projects list. ```javascript ( function($) { "use strict"; {if !empty($saved)} (function() { $.tasks.reloadSidebar(); var $hsaved = $('#content .t-saved').slideDown(); setTimeout(function() { $hsaved.slideUp(); }, 3000); })(); {/if} var $form = $('#project-ordering-form'); var $top_list = $('#top-list'); var $bottom_list = $('#bottom-list'); var $submit = $form.find(':submit'); // Submit form via XHR $form.submit(function(e) { e.preventDefault(); $form.showLoading(); $.tasks.showLoading(); $('#content .t-saved').slideUp(); $.post($form.attr('action'), $form.serialize(), function (r) { $('#content').html(r); }); }); // Distribute rows between top and bottom lists initially $('#initial-data li').each(function() { var $li = $(this); if ($li.find('input[type="checkbox"]').prop('checked')) { $li.appendTo($top_list); } else { $li.appendTo($bottom_list); } }); // Checkbox controls where the project goes: top list or bottom list $form.on('change', 'input[type="checkbox"]:not(.clone)', function() { var $checkbox = $(this); var $li = $checkbox.closest('li'); // Something has to be changed at all? if ($checkbox[0].checked && $.contains($top_list[0], $checkbox[0])) { return; } else if (!$checkbox[0].checked && !$.contains($top_list[0], $checkbox[0])) { return; } $submit.prop('disabled', false); // Replace $li with an absolute-positioned clone var initial_position = $li.position(); var $li_clone = $li.clone().addClass('clone').css({ top: initial_position.top, left: initial_position.left }).insertAfter($li); $li.detach().css('visibility', 'hidden'); // Attach hidden $li immidiately to destination if ($checkbox[0].checked) { $li.appendTo($top_list); } else { // Find appropriate place for the $li respecting ordering by project name var project_name = $li.find('.js-project-name').html(); $bottom_list.children().each(function() { var $sibling = $(this); var sibling_project_name = $sibling.find('.js-project-name').html(); if (sibling_project_name > project_name) { $li.insertBefore($sibling); return false; } }); if (!$li.parent().length) { $li.appendTo($bottom_list); } } // Run animation on a clone from its current position to $li, var final_position = $li.position(); $li_clone.animate({ top: final_position.top, left: final_position.left }, 300, function() { $li_clone.remove(); $li.css('visibility', 'visible'); }); }); // Drag-and-drop in top list $top_list.sortable({ axis: 'y', items: 'li:not(.clone)', distance: 5, containment: 'parent', tolerance: 'pointer', handle: '.drag-handle', update: function (e, ui) { $submit.prop('disabled', false); } }); })(jQuery); ``` -------------------------------- ### Premium Upgrade Button and Pricing Display Source: https://github.com/1312inc/webasyst-teamwork/blob/main/templates/actions/upgrade/Upgrade.html Renders an 'Upgrade to premium' button and displays pricing details. It shows the original price if available and the current price, with specific text for cloud vs. self-hosted plans. Includes a conditional promotional code for self-hosted licenses before a specific date. ```html {capture "getpremiumbutton"} {if !$_is_premium} [[`Upgrade to premium`]]({$_upgrade_url}) #### {if !empty($_premium_pricing.compare_price)}{$_premium_pricing.compare_price}{/if} {if $_is_cloud} [`All inclusive in the cloud`] {else} {$_premium_pricing.price} {/if} {if $_is_cloud} [`In Webasyst Cloud, simply upgrade to the **Premium** plan and get all premium features at once with no extra purchase required.`] {else} [`Per-server license for the entire company. Unlimited users, all premium features, free updates forever.`] {/if} {if !$_is_cloud && date('Y-m-d') <= '2026-08-31'} [`Promocode`] `**SUMMER26**` **-15% / {date('Y.m')}** {/if} {/if} {/capture} ``` -------------------------------- ### HTML Helper for Project List Rendering Source: https://github.com/1312inc/webasyst-teamwork/blob/main/templates/actions/settings/SettingsPersonalHelpers.inc.html Generates a list of projects, checking notification settings to determine if the project option should be disabled. ```html {function project_list} {$_projects = $projects|default:[]} {if (!in_array("project", $settings.notification.task))} {$_disabled = 'disabled="disabled"'} {/if} {$settings_projects = $settings.notification.project|array_fill_keys:true} {if $_projects} {foreach $_projects as $_project}* {$ extit{\_ extit{project.name|escape}}} {/foreach} {/if} {/function} ``` -------------------------------- ### JavaScript Initialization (UI 1.3) Source: https://github.com/1312inc/webasyst-teamwork/blob/main/plugins/releases/templates/component/task/Edit.html Initializes the TasksReleasesPluginTaskEdit JavaScript class for UI version 1.3. Requires jQuery. ```javascript (function ($) { new TasksReleasesPluginTaskEdit({ 'wrapper': $('#{$wrapper_id}'), '$editor': $('#t-edit-task-form'), 'milestones': {$milestones|json_encode}, 'task': {$task_info|json_encode} }); })(jQuery); ``` -------------------------------- ### Task Types UI (Default) Source: https://github.com/1312inc/webasyst-teamwork/blob/main/plugins/releases/templates/actions/types/Types.html Renders the task types settings interface for the default UI. Includes a JavaScript initialization for the plugin. ```html {$wrapper_id = uniqid('t-plugin-releases-settings-task-types-wrapper')} {$sidebar_html|default:''} [\`Task types\'] ================== [\`Settings successfully updated\'] [[\`New type\']](javascript:void(0)) {$\_types = $types} {$\_empty = $empty} {$\_empty.id = '\_template\_'} {$\_types[$\_empty.id] = $\_empty} {foreach $\_types as $\_type} {$\_is_template = $\_type.id == '\_template\_'}* {$\_type.name|escape} [\`Color\'] # {/foreach} (function ($) { new TasksReleasesPluginTaskTypes({ $wrapper: $('#{$wrapper_id}') }); })(jQuery); {/if} ``` -------------------------------- ### Task Header Filters Include Source: https://github.com/1312inc/webasyst-teamwork/blob/main/templates/actions/tasks/includes/TasksHeaderFilters.inc.html Includes helper file for task header filters. ```html {include file="./TasksHeaderFiltersHelpers.inc.html" inline} ```