diff options
Diffstat (limited to 'web_src/js/features/comp')
-rw-r--r-- | web_src/js/features/comp/ColorPicker.js | 11 | ||||
-rw-r--r-- | web_src/js/features/comp/CommentSimpleMDE.js | 72 | ||||
-rw-r--r-- | web_src/js/features/comp/ImagePaste.js | 91 | ||||
-rw-r--r-- | web_src/js/features/comp/LabelEdit.js | 30 | ||||
-rw-r--r-- | web_src/js/features/comp/MarkupContentPreview.js | 21 | ||||
-rw-r--r-- | web_src/js/features/comp/ReactionSelector.js | 48 | ||||
-rw-r--r-- | web_src/js/features/comp/SearchUserBox.js | 36 | ||||
-rw-r--r-- | web_src/js/features/comp/WebHookEditor.js | 40 |
8 files changed, 349 insertions, 0 deletions
diff --git a/web_src/js/features/comp/ColorPicker.js b/web_src/js/features/comp/ColorPicker.js new file mode 100644 index 0000000000..669e1d1f18 --- /dev/null +++ b/web_src/js/features/comp/ColorPicker.js @@ -0,0 +1,11 @@ +import createColorPicker from '../colorpicker.js'; + +export function initCompColorPicker() { + createColorPicker($('.color-picker')); + + $('.precolors .color').on('click', function () { + const color_hex = $(this).data('color-hex'); + $('.color-picker').val(color_hex); + $('.minicolors-swatch-color').css('background-color', color_hex); + }); +} diff --git a/web_src/js/features/comp/CommentSimpleMDE.js b/web_src/js/features/comp/CommentSimpleMDE.js new file mode 100644 index 0000000000..fbc0ec8baf --- /dev/null +++ b/web_src/js/features/comp/CommentSimpleMDE.js @@ -0,0 +1,72 @@ +import attachTribute from '../tribute.js'; + +export function createCommentSimpleMDE($editArea) { + if ($editArea.length === 0) { + return null; + } + + const simplemde = new SimpleMDE({ + autoDownloadFontAwesome: false, + element: $editArea[0], + forceSync: true, + renderingConfig: { + singleLineBreaks: false + }, + indentWithTabs: false, + tabSize: 4, + spellChecker: false, + toolbar: ['bold', 'italic', 'strikethrough', '|', + 'heading-1', 'heading-2', 'heading-3', 'heading-bigger', 'heading-smaller', '|', + 'code', 'quote', '|', { + name: 'checkbox-empty', + action(e) { + const cm = e.codemirror; + cm.replaceSelection(`\n- [ ] ${cm.getSelection()}`); + cm.focus(); + }, + className: 'fa fa-square-o', + title: 'Add Checkbox (empty)', + }, + { + name: 'checkbox-checked', + action(e) { + const cm = e.codemirror; + cm.replaceSelection(`\n- [x] ${cm.getSelection()}`); + cm.focus(); + }, + className: 'fa fa-check-square-o', + title: 'Add Checkbox (checked)', + }, '|', + 'unordered-list', 'ordered-list', '|', + 'link', 'image', 'table', 'horizontal-rule', '|', + 'clean-block', '|', + { + name: 'revert-to-textarea', + action(e) { + e.toTextArea(); + }, + className: 'fa fa-file', + title: 'Revert to simple textarea', + }, + ] + }); + $(simplemde.codemirror.getInputField()).addClass('js-quick-submit'); + simplemde.codemirror.setOption('extraKeys', { + Enter: () => { + const tributeContainer = document.querySelector('.tribute-container'); + if (!tributeContainer || tributeContainer.style.display === 'none') { + return CodeMirror.Pass; + } + }, + Backspace: (cm) => { + if (cm.getInputField().trigger) { + cm.getInputField().trigger('input'); + } + cm.execCommand('delCharBefore'); + } + }); + attachTribute(simplemde.codemirror.getInputField(), {mentions: true, emoji: true}); + $editArea.data('simplemde', simplemde); + $(simplemde.codemirror.getInputField()).data('simplemde', simplemde); + return simplemde; +} diff --git a/web_src/js/features/comp/ImagePaste.js b/web_src/js/features/comp/ImagePaste.js new file mode 100644 index 0000000000..b6881dd282 --- /dev/null +++ b/web_src/js/features/comp/ImagePaste.js @@ -0,0 +1,91 @@ +const {AppSubUrl, csrf} = window.config; + +async function uploadFile(file, uploadUrl) { + const formData = new FormData(); + formData.append('file', file, file.name); + + const res = await fetch(uploadUrl, { + method: 'POST', + headers: {'X-Csrf-Token': csrf}, + body: formData, + }); + return await res.json(); +} + +function clipboardPastedImages(e) { + if (!e.clipboardData) return []; + + const files = []; + for (const item of e.clipboardData.items || []) { + if (!item.type || !item.type.startsWith('image/')) continue; + files.push(item.getAsFile()); + } + + if (files.length) { + e.preventDefault(); + e.stopPropagation(); + } + return files; +} + + +function insertAtCursor(field, value) { + if (field.selectionStart || field.selectionStart === 0) { + const startPos = field.selectionStart; + const endPos = field.selectionEnd; + field.value = field.value.substring(0, startPos) + value + field.value.substring(endPos, field.value.length); + field.selectionStart = startPos + value.length; + field.selectionEnd = startPos + value.length; + } else { + field.value += value; + } +} + +function replaceAndKeepCursor(field, oldval, newval) { + if (field.selectionStart || field.selectionStart === 0) { + const startPos = field.selectionStart; + const endPos = field.selectionEnd; + field.value = field.value.replace(oldval, newval); + field.selectionStart = startPos + newval.length - oldval.length; + field.selectionEnd = endPos + newval.length - oldval.length; + } else { + field.value = field.value.replace(oldval, newval); + } +} + +export function initCompImagePaste($target) { + $target.each(function () { + const dropzone = this.querySelector('.dropzone'); + if (!dropzone) { + return; + } + const uploadUrl = dropzone.dataset.uploadUrl; + const dropzoneFiles = dropzone.querySelector('.files'); + for (const textarea of this.querySelectorAll('textarea')) { + textarea.addEventListener('paste', async (e) => { + for (const img of clipboardPastedImages(e)) { + const name = img.name.substr(0, img.name.lastIndexOf('.')); + insertAtCursor(textarea, `![${name}]()`); + const data = await uploadFile(img, uploadUrl); + replaceAndKeepCursor(textarea, `![${name}]()`, `![${name}](${AppSubUrl}/attachments/${data.uuid})`); + const input = $(`<input id="${data.uuid}" name="files" type="hidden">`).val(data.uuid); + dropzoneFiles.appendChild(input[0]); + } + }, false); + } + }); +} + +export function initSimpleMDEImagePaste(simplemde, dropzone, files) { + const uploadUrl = dropzone.dataset.uploadUrl; + simplemde.codemirror.on('paste', async (_, e) => { + for (const img of clipboardPastedImages(e)) { + const name = img.name.substr(0, img.name.lastIndexOf('.')); + const data = await uploadFile(img, uploadUrl); + const pos = simplemde.codemirror.getCursor(); + simplemde.codemirror.replaceRange(`![${name}](${AppSubUrl}/attachments/${data.uuid})`, pos); + const input = $(`<input id="${data.uuid}" name="files" type="hidden">`).val(data.uuid); + files.append(input); + } + }); +} diff --git a/web_src/js/features/comp/LabelEdit.js b/web_src/js/features/comp/LabelEdit.js new file mode 100644 index 0000000000..7d71e6effa --- /dev/null +++ b/web_src/js/features/comp/LabelEdit.js @@ -0,0 +1,30 @@ +import {initCompColorPicker} from './ColorPicker.js'; + +export function initCompLabelEdit(selector) { + if (!$(selector).length) return; + // Create label + const $newLabelPanel = $('.new-label.segment'); + $('.new-label.button').on('click', () => { + $newLabelPanel.show(); + }); + $('.new-label.segment .cancel').on('click', () => { + $newLabelPanel.hide(); + }); + + initCompColorPicker(); + + $('.edit-label-button').on('click', function () { + $('.edit-label .color-picker').minicolors('value', $(this).data('color')); + $('#label-modal-id').val($(this).data('id')); + $('.edit-label .new-label-input').val($(this).data('title')); + $('.edit-label .new-label-desc-input').val($(this).data('description')); + $('.edit-label .color-picker').val($(this).data('color')); + $('.edit-label .minicolors-swatch-color').css('background-color', $(this).data('color')); + $('.edit-label.modal').modal({ + onApprove() { + $('.edit-label.form').trigger('submit'); + } + }).modal('show'); + return false; + }); +} diff --git a/web_src/js/features/comp/MarkupContentPreview.js b/web_src/js/features/comp/MarkupContentPreview.js new file mode 100644 index 0000000000..0b05c4efae --- /dev/null +++ b/web_src/js/features/comp/MarkupContentPreview.js @@ -0,0 +1,21 @@ +import {initMarkupContent} from '../../markup/content.js'; + +const {csrf} = window.config; + +export function initCompMarkupContentPreviewTab($form) { + const $tabMenu = $form.find('.tabular.menu'); + $tabMenu.find('.item').tab(); + $tabMenu.find(`.item[data-tab="${$tabMenu.data('preview')}"]`).on('click', function () { + const $this = $(this); + $.post($this.data('url'), { + _csrf: csrf, + mode: 'comment', + context: $this.data('context'), + text: $form.find(`.tab[data-tab="${$tabMenu.data('write')}"] textarea`).val() + }, (data) => { + const $previewPanel = $form.find(`.tab[data-tab="${$tabMenu.data('preview')}"]`); + $previewPanel.html(data); + initMarkupContent(); + }); + }); +} diff --git a/web_src/js/features/comp/ReactionSelector.js b/web_src/js/features/comp/ReactionSelector.js new file mode 100644 index 0000000000..d11c9667b9 --- /dev/null +++ b/web_src/js/features/comp/ReactionSelector.js @@ -0,0 +1,48 @@ +const {csrf} = window.config; + +export function initCompReactionSelector(parent) { + let reactions = ''; + if (!parent) { + parent = $(document); + reactions = '.reactions > '; + } + + parent.find(`${reactions}a.label`).popup({position: 'bottom left', metadata: {content: 'title', title: 'none'}}); + + parent.find(`.select-reaction > .menu > .item, ${reactions}a.label`).on('click', function (e) { + e.preventDefault(); + + if ($(this).hasClass('disabled')) return; + + const actionURL = $(this).hasClass('item') ? $(this).closest('.select-reaction').data('action-url') : $(this).data('action-url'); + const url = `${actionURL}/${$(this).hasClass('blue') ? 'unreact' : 'react'}`; + $.ajax({ + type: 'POST', + url, + data: { + _csrf: csrf, + content: $(this).data('content') + } + }).done((resp) => { + if (resp && (resp.html || resp.empty)) { + const content = $(this).closest('.content'); + let react = content.find('.segment.reactions'); + if ((!resp.empty || resp.html === '') && react.length > 0) { + react.remove(); + } + if (!resp.empty) { + react = $('<div class="ui attached segment reactions"></div>'); + const attachments = content.find('.segment.bottom:first'); + if (attachments.length > 0) { + react.insertBefore(attachments); + } else { + react.appendTo(content); + } + react.html(resp.html); + react.find('.dropdown').dropdown(); + initCompReactionSelector(react); + } + } + }); + }); +} diff --git a/web_src/js/features/comp/SearchUserBox.js b/web_src/js/features/comp/SearchUserBox.js new file mode 100644 index 0000000000..9019f17de3 --- /dev/null +++ b/web_src/js/features/comp/SearchUserBox.js @@ -0,0 +1,36 @@ +import {htmlEscape} from 'escape-goat'; + +const {AppSubUrl} = window.config; + +export function initSearchUserBox() { + const $searchUserBox = $('#search-user-box'); + $searchUserBox.search({ + minCharacters: 2, + apiSettings: { + url: `${AppSubUrl}/api/v1/users/search?q={query}`, + onResponse(response) { + const items = []; + const searchQueryUppercase = $searchUserBox.find('input').val().toUpperCase(); + $.each(response.data, (_i, item) => { + let title = item.login; + if (item.full_name && item.full_name.length > 0) { + title += ` (${htmlEscape(item.full_name)})`; + } + const resultItem = { + title, + image: item.avatar_url + }; + if (searchQueryUppercase === item.login.toUpperCase()) { + items.unshift(resultItem); + } else { + items.push(resultItem); + } + }); + + return {results: items}; + } + }, + searchFields: ['login', 'full_name'], + showNoResults: false + }); +} diff --git a/web_src/js/features/comp/WebHookEditor.js b/web_src/js/features/comp/WebHookEditor.js new file mode 100644 index 0000000000..6911c6cb16 --- /dev/null +++ b/web_src/js/features/comp/WebHookEditor.js @@ -0,0 +1,40 @@ +const {csrf} = window.config; + +export function initWebHookEditor() { + if ($('.new.webhook').length === 0) { + return; + } + + $('.events.checkbox input').on('change', function () { + if ($(this).is(':checked')) { + $('.events.fields').show(); + } + }); + $('.non-events.checkbox input').on('change', function () { + if ($(this).is(':checked')) { + $('.events.fields').hide(); + } + }); + + const updateContentType = function () { + const visible = $('#http_method').val() === 'POST'; + $('#content_type').parent().parent()[visible ? 'show' : 'hide'](); + }; + updateContentType(); + $('#http_method').on('change', () => { + updateContentType(); + }); + + // Test delivery + $('#test-delivery').on('click', function () { + const $this = $(this); + $this.addClass('loading disabled'); + $.post($this.data('link'), { + _csrf: csrf + }).done( + setTimeout(() => { + window.location.href = $this.data('redirect'); + }, 5000) + ); + }); +} |