From dd136858f1ea40ad3c94191d647487fa4f31926c Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Fri, 18 Oct 2024 20:33:49 +0200 Subject: Adding upstream version 9.0.0. Signed-off-by: Daniel Baumann --- web_src/js/features/admin/common.js | 258 +++++++ web_src/js/features/admin/config.js | 24 + web_src/js/features/admin/emails.js | 14 + web_src/js/features/admin/users.js | 39 ++ web_src/js/features/autofocus-end.js | 6 + web_src/js/features/captcha.js | 51 ++ web_src/js/features/citation.js | 50 ++ web_src/js/features/clipboard.js | 32 + web_src/js/features/code-frequency.js | 21 + web_src/js/features/codeeditor.js | 191 ++++++ web_src/js/features/colorpicker.js | 66 ++ web_src/js/features/common-global.js | 463 +++++++++++++ web_src/js/features/common-issue-list.js | 68 ++ web_src/js/features/common-issue-list.test.js | 16 + web_src/js/features/common-organization.js | 16 + web_src/js/features/comp/ComboMarkdownEditor.js | 413 +++++++++++ web_src/js/features/comp/ConfirmModal.js | 30 + web_src/js/features/comp/EasyMDEToolbarActions.js | 152 +++++ web_src/js/features/comp/LabelEdit.js | 96 +++ web_src/js/features/comp/Paste.js | 144 ++++ web_src/js/features/comp/QuickSubmit.js | 17 + web_src/js/features/comp/ReactionSelector.js | 38 ++ web_src/js/features/comp/SearchUserBox.js | 51 ++ web_src/js/features/comp/TextExpander.js | 61 ++ web_src/js/features/comp/WebHookEditor.js | 28 + web_src/js/features/contextpopup.js | 43 ++ web_src/js/features/contributors.js | 29 + web_src/js/features/copycontent.js | 56 ++ web_src/js/features/dropzone.js | 7 + web_src/js/features/emoji.js | 38 ++ web_src/js/features/eventsource.sharedworker.js | 141 ++++ web_src/js/features/file-fold.js | 19 + web_src/js/features/heatmap.js | 40 ++ web_src/js/features/imagediff.js | 271 ++++++++ web_src/js/features/install.js | 119 ++++ web_src/js/features/notification.js | 192 ++++++ web_src/js/features/org-team.js | 26 + web_src/js/features/pull-view-file.js | 96 +++ web_src/js/features/recent-commits.js | 21 + web_src/js/features/repo-branch.js | 42 ++ web_src/js/features/repo-code.js | 195 ++++++ web_src/js/features/repo-code.test.js | 17 + web_src/js/features/repo-commit.js | 27 + web_src/js/features/repo-common.js | 83 +++ web_src/js/features/repo-diff-commit.js | 53 ++ web_src/js/features/repo-diff-commitselect.js | 10 + web_src/js/features/repo-diff-filetree.js | 17 + web_src/js/features/repo-diff.js | 232 +++++++ web_src/js/features/repo-editor.js | 203 ++++++ web_src/js/features/repo-findfile.js | 117 ++++ web_src/js/features/repo-findfile.test.js | 34 + web_src/js/features/repo-graph.js | 155 +++++ web_src/js/features/repo-home.js | 147 ++++ web_src/js/features/repo-issue-content.js | 154 +++++ web_src/js/features/repo-issue-list.js | 245 +++++++ web_src/js/features/repo-issue-pr-form.js | 10 + web_src/js/features/repo-issue-pr-status.js | 10 + web_src/js/features/repo-issue.js | 794 ++++++++++++++++++++++ web_src/js/features/repo-issue.test.js | 24 + web_src/js/features/repo-legacy.js | 610 +++++++++++++++++ web_src/js/features/repo-migrate.js | 64 ++ web_src/js/features/repo-migration.js | 69 ++ web_src/js/features/repo-projects.js | 188 +++++ web_src/js/features/repo-release.js | 95 +++ web_src/js/features/repo-search.js | 22 + web_src/js/features/repo-settings.js | 120 ++++ web_src/js/features/repo-template.js | 51 ++ web_src/js/features/repo-unicode-escape.js | 27 + web_src/js/features/repo-wiki.js | 89 +++ web_src/js/features/sshkey-helper.js | 10 + web_src/js/features/stopwatch.js | 167 +++++ web_src/js/features/tablesort.js | 22 + web_src/js/features/tribute.js | 57 ++ web_src/js/features/user-auth-webauthn.js | 194 ++++++ web_src/js/features/user-auth.js | 22 + web_src/js/features/user-settings.js | 63 ++ 76 files changed, 7882 insertions(+) create mode 100644 web_src/js/features/admin/common.js create mode 100644 web_src/js/features/admin/config.js create mode 100644 web_src/js/features/admin/emails.js create mode 100644 web_src/js/features/admin/users.js create mode 100644 web_src/js/features/autofocus-end.js create mode 100644 web_src/js/features/captcha.js create mode 100644 web_src/js/features/citation.js create mode 100644 web_src/js/features/clipboard.js create mode 100644 web_src/js/features/code-frequency.js create mode 100644 web_src/js/features/codeeditor.js create mode 100644 web_src/js/features/colorpicker.js create mode 100644 web_src/js/features/common-global.js create mode 100644 web_src/js/features/common-issue-list.js create mode 100644 web_src/js/features/common-issue-list.test.js create mode 100644 web_src/js/features/common-organization.js create mode 100644 web_src/js/features/comp/ComboMarkdownEditor.js create mode 100644 web_src/js/features/comp/ConfirmModal.js create mode 100644 web_src/js/features/comp/EasyMDEToolbarActions.js create mode 100644 web_src/js/features/comp/LabelEdit.js create mode 100644 web_src/js/features/comp/Paste.js create mode 100644 web_src/js/features/comp/QuickSubmit.js create mode 100644 web_src/js/features/comp/ReactionSelector.js create mode 100644 web_src/js/features/comp/SearchUserBox.js create mode 100644 web_src/js/features/comp/TextExpander.js create mode 100644 web_src/js/features/comp/WebHookEditor.js create mode 100644 web_src/js/features/contextpopup.js create mode 100644 web_src/js/features/contributors.js create mode 100644 web_src/js/features/copycontent.js create mode 100644 web_src/js/features/dropzone.js create mode 100644 web_src/js/features/emoji.js create mode 100644 web_src/js/features/eventsource.sharedworker.js create mode 100644 web_src/js/features/file-fold.js create mode 100644 web_src/js/features/heatmap.js create mode 100644 web_src/js/features/imagediff.js create mode 100644 web_src/js/features/install.js create mode 100644 web_src/js/features/notification.js create mode 100644 web_src/js/features/org-team.js create mode 100644 web_src/js/features/pull-view-file.js create mode 100644 web_src/js/features/recent-commits.js create mode 100644 web_src/js/features/repo-branch.js create mode 100644 web_src/js/features/repo-code.js create mode 100644 web_src/js/features/repo-code.test.js create mode 100644 web_src/js/features/repo-commit.js create mode 100644 web_src/js/features/repo-common.js create mode 100644 web_src/js/features/repo-diff-commit.js create mode 100644 web_src/js/features/repo-diff-commitselect.js create mode 100644 web_src/js/features/repo-diff-filetree.js create mode 100644 web_src/js/features/repo-diff.js create mode 100644 web_src/js/features/repo-editor.js create mode 100644 web_src/js/features/repo-findfile.js create mode 100644 web_src/js/features/repo-findfile.test.js create mode 100644 web_src/js/features/repo-graph.js create mode 100644 web_src/js/features/repo-home.js create mode 100644 web_src/js/features/repo-issue-content.js create mode 100644 web_src/js/features/repo-issue-list.js create mode 100644 web_src/js/features/repo-issue-pr-form.js create mode 100644 web_src/js/features/repo-issue-pr-status.js create mode 100644 web_src/js/features/repo-issue.js create mode 100644 web_src/js/features/repo-issue.test.js create mode 100644 web_src/js/features/repo-legacy.js create mode 100644 web_src/js/features/repo-migrate.js create mode 100644 web_src/js/features/repo-migration.js create mode 100644 web_src/js/features/repo-projects.js create mode 100644 web_src/js/features/repo-release.js create mode 100644 web_src/js/features/repo-search.js create mode 100644 web_src/js/features/repo-settings.js create mode 100644 web_src/js/features/repo-template.js create mode 100644 web_src/js/features/repo-unicode-escape.js create mode 100644 web_src/js/features/repo-wiki.js create mode 100644 web_src/js/features/sshkey-helper.js create mode 100644 web_src/js/features/stopwatch.js create mode 100644 web_src/js/features/tablesort.js create mode 100644 web_src/js/features/tribute.js create mode 100644 web_src/js/features/user-auth-webauthn.js create mode 100644 web_src/js/features/user-auth.js create mode 100644 web_src/js/features/user-settings.js (limited to 'web_src/js/features') diff --git a/web_src/js/features/admin/common.js b/web_src/js/features/admin/common.js new file mode 100644 index 0000000..1a5bd6e --- /dev/null +++ b/web_src/js/features/admin/common.js @@ -0,0 +1,258 @@ +import $ from 'jquery'; +import {checkAppUrl} from '../common-global.js'; +import {hideElem, showElem, toggleElem} from '../../utils/dom.js'; +import {POST} from '../../modules/fetch.js'; + +const {appSubUrl} = window.config; + +function onSecurityProtocolChange() { + if (Number(document.getElementById('security_protocol')?.value) > 0) { + showElem('.has-tls'); + } else { + hideElem('.has-tls'); + } +} + +export function initAdminCommon() { + if (!document.querySelector('.page-content.admin')) return; + + // check whether appUrl(ROOT_URL) is correct, if not, show an error message + checkAppUrl(); + + // New user + if ($('.admin.new.user').length > 0 || $('.admin.edit.user').length > 0) { + document.getElementById('login_type')?.addEventListener('change', function () { + if (this.value?.substring(0, 1) === '0') { + document.getElementById('user_name')?.removeAttribute('disabled'); + document.getElementById('login_name')?.removeAttribute('required'); + hideElem('.non-local'); + showElem('.local'); + document.getElementById('user_name')?.focus(); + + if (this.getAttribute('data-password') === 'required') { + document.getElementById('password')?.setAttribute('required', 'required'); + } + } else { + if (document.querySelector('.admin.edit.user')) { + document.getElementById('user_name')?.setAttribute('disabled', 'disabled'); + } + document.getElementById('login_name')?.setAttribute('required', 'required'); + showElem('.non-local'); + hideElem('.local'); + document.getElementById('login_name')?.focus(); + + document.getElementById('password')?.removeAttribute('required'); + } + }); + } + + function onUsePagedSearchChange() { + const searchPageSizeElements = document.querySelectorAll('.search-page-size'); + if (document.getElementById('use_paged_search').checked) { + showElem('.search-page-size'); + for (const el of searchPageSizeElements) { + el.querySelector('input')?.setAttribute('required', 'required'); + } + } else { + hideElem('.search-page-size'); + for (const el of searchPageSizeElements) { + el.querySelector('input')?.removeAttribute('required'); + } + } + } + + function onOAuth2Change(applyDefaultValues) { + hideElem('.open_id_connect_auto_discovery_url, .oauth2_use_custom_url'); + for (const input of document.querySelectorAll('.open_id_connect_auto_discovery_url input[required]')) { + input.removeAttribute('required'); + } + + const provider = document.getElementById('oauth2_provider')?.value; + switch (provider) { + case 'openidConnect': + for (const input of document.querySelectorAll('.open_id_connect_auto_discovery_url input')) { + input.setAttribute('required', 'required'); + } + showElem('.open_id_connect_auto_discovery_url'); + break; + default: { + const customURLSettings = document.getElementById(`${provider}_customURLSettings`); + if (!customURLSettings) break; + const customURLRequired = (customURLSettings.getAttribute('data-required') === 'true'); + document.getElementById('oauth2_use_custom_url').checked = customURLRequired; + if (customURLRequired || customURLSettings.getAttribute('data-available') === 'true') { + showElem('.oauth2_use_custom_url'); + } + } + } + onOAuth2UseCustomURLChange(applyDefaultValues); + } + + function onOAuth2UseCustomURLChange(applyDefaultValues) { + const provider = document.getElementById('oauth2_provider')?.value; + hideElem('.oauth2_use_custom_url_field'); + for (const input of document.querySelectorAll('.oauth2_use_custom_url_field input[required]')) { + input.removeAttribute('required'); + } + + if (document.getElementById('oauth2_use_custom_url')?.checked) { + for (const custom of ['token_url', 'auth_url', 'profile_url', 'email_url', 'tenant']) { + const customInput = document.getElementById(`${provider}_${custom}`); + if (!customInput) continue; + if (applyDefaultValues) { + document.getElementById(`oauth2_${custom}`).value = customInput.value; + } + if (customInput.getAttribute('data-available') === 'true') { + for (const input of document.querySelectorAll(`.oauth2_${custom} input`)) { + input.setAttribute('required', 'required'); + } + showElem(`.oauth2_${custom}`); + } + } + } + } + + function onEnableLdapGroupsChange() { + toggleElem(document.getElementById('ldap-group-options'), $('.js-ldap-group-toggle')[0].checked); + } + + // New authentication + if (document.querySelector('.admin.new.authentication')) { + document.getElementById('auth_type')?.addEventListener('change', function () { + hideElem('.ldap, .dldap, .smtp, .pam, .oauth2, .has-tls, .search-page-size, .sspi'); + + for (const input of document.querySelectorAll('.ldap input[required], .binddnrequired input[required], .dldap input[required], .smtp input[required], .pam input[required], .oauth2 input[required], .has-tls input[required], .sspi input[required]')) { + input.removeAttribute('required'); + } + + document.querySelector('.binddnrequired')?.classList.remove('required'); + + const authType = this.value; + switch (authType) { + case '2': // LDAP + showElem('.ldap'); + for (const input of document.querySelectorAll('.binddnrequired input, .ldap div.required:not(.dldap) input')) { + input.setAttribute('required', 'required'); + } + document.querySelector('.binddnrequired')?.classList.add('required'); + break; + case '3': // SMTP + showElem('.smtp'); + showElem('.has-tls'); + for (const input of document.querySelectorAll('.smtp div.required input, .has-tls')) { + input.setAttribute('required', 'required'); + } + break; + case '4': // PAM + showElem('.pam'); + for (const input of document.querySelectorAll('.pam input')) { + input.setAttribute('required', 'required'); + } + break; + case '5': // LDAP + showElem('.dldap'); + for (const input of document.querySelectorAll('.dldap div.required:not(.ldap) input')) { + input.setAttribute('required', 'required'); + } + break; + case '6': // OAuth2 + showElem('.oauth2'); + for (const input of document.querySelectorAll('.oauth2 div.required:not(.oauth2_use_custom_url,.oauth2_use_custom_url_field,.open_id_connect_auto_discovery_url) input')) { + input.setAttribute('required', 'required'); + } + onOAuth2Change(true); + break; + case '7': // SSPI + showElem('.sspi'); + for (const input of document.querySelectorAll('.sspi div.required input')) { + input.setAttribute('required', 'required'); + } + break; + } + if (authType === '2' || authType === '5') { + onSecurityProtocolChange(); + onEnableLdapGroupsChange(); + } + if (authType === '2') { + onUsePagedSearchChange(); + } + }); + $('#auth_type').trigger('change'); + document.getElementById('security_protocol')?.addEventListener('change', onSecurityProtocolChange); + document.getElementById('use_paged_search')?.addEventListener('change', onUsePagedSearchChange); + document.getElementById('oauth2_provider')?.addEventListener('change', () => onOAuth2Change(true)); + document.getElementById('oauth2_use_custom_url')?.addEventListener('change', () => onOAuth2UseCustomURLChange(true)); + $('.js-ldap-group-toggle').on('change', onEnableLdapGroupsChange); + } + // Edit authentication + if (document.querySelector('.admin.edit.authentication')) { + const authType = document.getElementById('auth_type')?.value; + if (authType === '2' || authType === '5') { + document.getElementById('security_protocol')?.addEventListener('change', onSecurityProtocolChange); + $('.js-ldap-group-toggle').on('change', onEnableLdapGroupsChange); + onEnableLdapGroupsChange(); + if (authType === '2') { + document.getElementById('use_paged_search')?.addEventListener('change', onUsePagedSearchChange); + } + } else if (authType === '6') { + document.getElementById('oauth2_provider')?.addEventListener('change', () => onOAuth2Change(true)); + document.getElementById('oauth2_use_custom_url')?.addEventListener('change', () => onOAuth2UseCustomURLChange(false)); + onOAuth2Change(false); + } + } + + if (document.querySelector('.admin.authentication')) { + $('#auth_name').on('input', function () { + // appSubUrl is either empty or is a path that starts with `/` and doesn't have a trailing slash. + document.getElementById('oauth2-callback-url').textContent = `${window.location.origin}${appSubUrl}/user/oauth2/${encodeURIComponent(this.value)}/callback`; + }).trigger('input'); + } + + // Notice + if (document.querySelector('.admin.notice')) { + const detailModal = document.getElementById('detail-modal'); + + // Attach view detail modals + $('.view-detail').on('click', function () { + const description = this.closest('tr').querySelector('.notice-description').textContent; + detailModal.querySelector('.content pre').textContent = description; + $(detailModal).modal('show'); + return false; + }); + + // Select actions + const checkboxes = document.querySelectorAll('.select.table .ui.checkbox input'); + + $('.select.action').on('click', function () { + switch ($(this).data('action')) { + case 'select-all': + for (const checkbox of checkboxes) { + checkbox.checked = true; + } + break; + case 'deselect-all': + for (const checkbox of checkboxes) { + checkbox.checked = false; + } + break; + case 'inverse': + for (const checkbox of checkboxes) { + checkbox.checked = !checkbox.checked; + } + break; + } + }); + document.getElementById('delete-selection')?.addEventListener('click', async function (e) { + e.preventDefault(); + this.classList.add('is-loading', 'disabled'); + const data = new FormData(); + for (const checkbox of checkboxes) { + if (checkbox.checked) { + data.append('ids[]', checkbox.closest('.ui.checkbox').getAttribute('data-id')); + } + } + await POST(this.getAttribute('data-link'), {data}); + window.location.href = this.getAttribute('data-redirect'); + }); + } +} diff --git a/web_src/js/features/admin/config.js b/web_src/js/features/admin/config.js new file mode 100644 index 0000000..c382342 --- /dev/null +++ b/web_src/js/features/admin/config.js @@ -0,0 +1,24 @@ +import {showTemporaryTooltip} from '../../modules/tippy.js'; +import {POST} from '../../modules/fetch.js'; + +const {appSubUrl} = window.config; + +export function initAdminConfigs() { + const elAdminConfig = document.querySelector('.page-content.admin.config'); + if (!elAdminConfig) return; + + for (const el of elAdminConfig.querySelectorAll('input[type="checkbox"][data-config-dyn-key]')) { + el.addEventListener('change', async () => { + try { + const resp = await POST(`${appSubUrl}/admin/config`, { + data: new URLSearchParams({key: el.getAttribute('data-config-dyn-key'), value: el.checked}), + }); + const json = await resp.json(); + if (json.errorMessage) throw new Error(json.errorMessage); + } catch (ex) { + showTemporaryTooltip(el, ex.toString()); + el.checked = !el.checked; + } + }); + } +} diff --git a/web_src/js/features/admin/emails.js b/web_src/js/features/admin/emails.js new file mode 100644 index 0000000..46fafa7 --- /dev/null +++ b/web_src/js/features/admin/emails.js @@ -0,0 +1,14 @@ +import $ from 'jquery'; + +export function initAdminEmails() { + function linkEmailAction(e) { + const $this = $(this); + $('#form-uid').val($this.data('uid')); + $('#form-email').val($this.data('email')); + $('#form-primary').val($this.data('primary')); + $('#form-activate').val($this.data('activate')); + $('#change-email-modal').modal('show'); + e.preventDefault(); + } + $('.link-email-action').on('click', linkEmailAction); +} diff --git a/web_src/js/features/admin/users.js b/web_src/js/features/admin/users.js new file mode 100644 index 0000000..7cac603 --- /dev/null +++ b/web_src/js/features/admin/users.js @@ -0,0 +1,39 @@ +export function initAdminUserListSearchForm() { + const searchForm = window.config.pageData.adminUserListSearchForm; + if (!searchForm) return; + + const form = document.querySelector('#user-list-search-form'); + if (!form) return; + + for (const button of form.querySelectorAll(`button[name=sort][value="${searchForm.SortType}"]`)) { + button.classList.add('active'); + } + + if (searchForm.StatusFilterMap) { + for (const [k, v] of Object.entries(searchForm.StatusFilterMap)) { + if (!v) continue; + for (const input of form.querySelectorAll(`input[name="status_filter[${k}]"][value="${v}"]`)) { + input.checked = true; + } + } + } + + for (const radio of form.querySelectorAll('input[type=radio]')) { + radio.addEventListener('click', () => { + form.submit(); + }); + } + + const resetButtons = form.querySelectorAll('.j-reset-status-filter'); + for (const button of resetButtons) { + button.addEventListener('click', (e) => { + e.preventDefault(); + for (const input of form.querySelectorAll('input[type=radio]')) { + if (input.name.startsWith('status_filter[')) { + input.checked = false; + } + } + form.submit(); + }); + } +} diff --git a/web_src/js/features/autofocus-end.js b/web_src/js/features/autofocus-end.js new file mode 100644 index 0000000..da71ce9 --- /dev/null +++ b/web_src/js/features/autofocus-end.js @@ -0,0 +1,6 @@ +export function initAutoFocusEnd() { + for (const el of document.querySelectorAll('.js-autofocus-end')) { + el.focus(); // expects only one such element on one page. If there are many, then the last one gets the focus. + el.setSelectionRange(el.value.length, el.value.length); + } +} diff --git a/web_src/js/features/captcha.js b/web_src/js/features/captcha.js new file mode 100644 index 0000000..c803a50 --- /dev/null +++ b/web_src/js/features/captcha.js @@ -0,0 +1,51 @@ +import {isDarkTheme} from '../utils.js'; + +export async function initCaptcha() { + const captchaEl = document.querySelector('#captcha'); + if (!captchaEl) return; + + const siteKey = captchaEl.getAttribute('data-sitekey'); + const isDark = isDarkTheme(); + + const params = { + sitekey: siteKey, + theme: isDark ? 'dark' : 'light', + }; + + switch (captchaEl.getAttribute('data-captcha-type')) { + case 'g-recaptcha': { + if (window.grecaptcha) { + window.grecaptcha.ready(() => { + window.grecaptcha.render(captchaEl, params); + }); + } + break; + } + case 'cf-turnstile': { + if (window.turnstile) { + window.turnstile.render(captchaEl, params); + } + break; + } + case 'h-captcha': { + if (window.hcaptcha) { + window.hcaptcha.render(captchaEl, params); + } + break; + } + case 'm-captcha': { + const {default: mCaptcha} = await import(/* webpackChunkName: "mcaptcha-vanilla-glue" */'@mcaptcha/vanilla-glue'); + mCaptcha.INPUT_NAME = 'm-captcha-response'; + const instanceURL = captchaEl.getAttribute('data-instance-url'); + + mCaptcha.default({ + siteKey: { + instanceUrl: new URL(instanceURL), + key: siteKey, + }, + }); + break; + } + default: + } +} diff --git a/web_src/js/features/citation.js b/web_src/js/features/citation.js new file mode 100644 index 0000000..7e26bff --- /dev/null +++ b/web_src/js/features/citation.js @@ -0,0 +1,50 @@ +import $ from 'jquery'; +import {getCurrentLocale} from '../utils.js'; + +const {pageData} = window.config; + +async function initInputCitationValue(inputContent) { + const [{Cite, plugins}] = await Promise.all([ + import(/* webpackChunkName: "citation-js-core" */'@citation-js/core'), + import(/* webpackChunkName: "citation-js-formats" */'@citation-js/plugin-software-formats'), + import(/* webpackChunkName: "citation-js-bibtex" */'@citation-js/plugin-bibtex'), + ]); + const {citationFileContent} = pageData; + const config = plugins.config.get('@bibtex'); + config.constants.fieldTypes.doi = ['field', 'literal']; + config.constants.fieldTypes.version = ['field', 'literal']; + const citationFormatter = new Cite(citationFileContent); + const lang = getCurrentLocale() || 'en-US'; + const bibtexOutput = citationFormatter.format('bibtex', {lang}); + inputContent.value = bibtexOutput; +} + +export async function initCitationFileCopyContent() { + if (!pageData.citationFileContent) return; + + const inputContent = document.getElementById('citation-copy-content'); + + if (!inputContent) return; + + document.getElementById('cite-repo-button')?.addEventListener('click', async (e) => { + const dropdownBtn = e.target.closest('.ui.dropdown.button'); + dropdownBtn.classList.add('is-loading'); + + try { + try { + await initInputCitationValue(inputContent); + } catch (e) { + console.error(`initCitationFileCopyContent error: ${e}`, e); + return; + } + + inputContent.addEventListener('click', () => { + inputContent.select(); + }); + } finally { + dropdownBtn.classList.remove('is-loading'); + } + + $('#cite-repo-modal').modal('show'); + }); +} diff --git a/web_src/js/features/clipboard.js b/web_src/js/features/clipboard.js new file mode 100644 index 0000000..daf7e2a --- /dev/null +++ b/web_src/js/features/clipboard.js @@ -0,0 +1,32 @@ +import {showTemporaryTooltip} from '../modules/tippy.js'; +import {toAbsoluteUrl} from '../utils.js'; +import {clippie} from 'clippie'; + +const {copy_success, copy_error} = window.config.i18n; + +// Enable clipboard copy from HTML attributes. These properties are supported: +// - data-clipboard-text: Direct text to copy +// - data-clipboard-target: Holds a selector for a or