Split index.js
to separate files (#17315)
* split `index.js` to separate files * tune clipboard * fix promise * fix document * remove intermediate empty file * fix async event listener * use `export function` instead of `export {}`, add more comments Co-authored-by: Lunny Xiao <xiaolunwen@gmail.com> Co-authored-by: 6543 <6543@obermui.de>
This commit is contained in:
parent
3728f1daa0
commit
1a7473ff45
42 changed files with 3686 additions and 3501 deletions
11
web_src/js/features/comp/ColorPicker.js
Normal file
11
web_src/js/features/comp/ColorPicker.js
Normal file
|
@ -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);
|
||||
});
|
||||
}
|
72
web_src/js/features/comp/CommentSimpleMDE.js
Normal file
72
web_src/js/features/comp/CommentSimpleMDE.js
Normal file
|
@ -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;
|
||||
}
|
91
web_src/js/features/comp/ImagePaste.js
Normal file
91
web_src/js/features/comp/ImagePaste.js
Normal file
|
@ -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}]()`, ``);
|
||||
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(``, pos);
|
||||
const input = $(`<input id="${data.uuid}" name="files" type="hidden">`).val(data.uuid);
|
||||
files.append(input);
|
||||
}
|
||||
});
|
||||
}
|
30
web_src/js/features/comp/LabelEdit.js
Normal file
30
web_src/js/features/comp/LabelEdit.js
Normal file
|
@ -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;
|
||||
});
|
||||
}
|
21
web_src/js/features/comp/MarkupContentPreview.js
Normal file
21
web_src/js/features/comp/MarkupContentPreview.js
Normal file
|
@ -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();
|
||||
});
|
||||
});
|
||||
}
|
48
web_src/js/features/comp/ReactionSelector.js
Normal file
48
web_src/js/features/comp/ReactionSelector.js
Normal file
|
@ -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);
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
36
web_src/js/features/comp/SearchUserBox.js
Normal file
36
web_src/js/features/comp/SearchUserBox.js
Normal file
|
@ -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
|
||||
});
|
||||
}
|
40
web_src/js/features/comp/WebHookEditor.js
Normal file
40
web_src/js/features/comp/WebHookEditor.js
Normal file
|
@ -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)
|
||||
);
|
||||
});
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue