summaryrefslogtreecommitdiffstats
path: root/web_src/js/features/comp
diff options
context:
space:
mode:
Diffstat (limited to 'web_src/js/features/comp')
-rw-r--r--web_src/js/features/comp/ColorPicker.js11
-rw-r--r--web_src/js/features/comp/CommentSimpleMDE.js72
-rw-r--r--web_src/js/features/comp/ImagePaste.js91
-rw-r--r--web_src/js/features/comp/LabelEdit.js30
-rw-r--r--web_src/js/features/comp/MarkupContentPreview.js21
-rw-r--r--web_src/js/features/comp/ReactionSelector.js48
-rw-r--r--web_src/js/features/comp/SearchUserBox.js36
-rw-r--r--web_src/js/features/comp/WebHookEditor.js40
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)
+ );
+ });
+}