|
1 |
| -import $ from 'jquery'; |
2 | 1 | import {POST} from '../modules/fetch.ts';
|
3 |
| -import {hideElem, showElem, toggleElem} from '../utils/dom.ts'; |
4 |
| -import {showErrorToast} from '../modules/toast.ts'; |
| 2 | +import {addDelegatedEventListener, hideElem, queryElems, showElem, toggleElem} from '../utils/dom.ts'; |
| 3 | +import {fomanticQuery} from '../modules/fomantic/base.ts'; |
5 | 4 |
|
6 | 5 | export function initGlobalButtonClickOnEnter(): void {
|
7 |
| - $(document).on('keypress', 'div.ui.button,span.ui.button', (e) => { |
| 6 | + addDelegatedEventListener(document, 'keypress', 'div.ui.button, span.ui.button', (el, e: KeyboardEvent) => { |
8 | 7 | if (e.code === ' ' || e.code === 'Enter') {
|
9 |
| - $(e.target).trigger('click'); |
10 | 8 | e.preventDefault();
|
| 9 | + el.click(); |
11 | 10 | }
|
12 | 11 | });
|
13 | 12 | }
|
@@ -40,7 +39,7 @@ export function initGlobalDeleteButton(): void {
|
40 | 39 | }
|
41 | 40 | }
|
42 | 41 |
|
43 |
| - $(modal).modal({ |
| 42 | + fomanticQuery(modal).modal({ |
44 | 43 | closable: false,
|
45 | 44 | onApprove: async () => {
|
46 | 45 | // if `data-type="form"` exists, then submit the form by the selector provided by `data-form="..."`
|
@@ -73,87 +72,92 @@ export function initGlobalDeleteButton(): void {
|
73 | 72 | }
|
74 | 73 | }
|
75 | 74 |
|
76 |
| -export function initGlobalButtons(): void { |
77 |
| - // There are many "cancel button" elements in modal dialogs, Fomantic UI expects they are button-like elements but never submit a form. |
78 |
| - // However, Gitea misuses the modal dialog and put the cancel buttons inside forms, so we must prevent the form submission. |
79 |
| - // There are a few cancel buttons in non-modal forms, and there are some dynamically created forms (eg: the "Edit Issue Content") |
80 |
| - $(document).on('click', 'form button.ui.cancel.button', (e) => { |
81 |
| - e.preventDefault(); |
82 |
| - }); |
83 |
| - |
84 |
| - $('.show-panel').on('click', function (e) { |
85 |
| - // a '.show-panel' element can show a panel, by `data-panel="selector"` |
86 |
| - // if it has "toggle" class, it toggles the panel |
87 |
| - e.preventDefault(); |
88 |
| - const sel = this.getAttribute('data-panel'); |
89 |
| - if (this.classList.contains('toggle')) { |
90 |
| - toggleElem(sel); |
91 |
| - } else { |
92 |
| - showElem(sel); |
93 |
| - } |
94 |
| - }); |
| 75 | +function onShowPanelClick(e) { |
| 76 | + // a '.show-panel' element can show a panel, by `data-panel="selector"` |
| 77 | + // if it has "toggle" class, it toggles the panel |
| 78 | + const el = e.currentTarget; |
| 79 | + e.preventDefault(); |
| 80 | + const sel = el.getAttribute('data-panel'); |
| 81 | + if (el.classList.contains('toggle')) { |
| 82 | + toggleElem(sel); |
| 83 | + } else { |
| 84 | + showElem(sel); |
| 85 | + } |
| 86 | +} |
95 | 87 |
|
96 |
| - $('.hide-panel').on('click', function (e) { |
97 |
| - // a `.hide-panel` element can hide a panel, by `data-panel="selector"` or `data-panel-closest="selector"` |
98 |
| - e.preventDefault(); |
99 |
| - let sel = this.getAttribute('data-panel'); |
100 |
| - if (sel) { |
101 |
| - hideElem($(sel)); |
102 |
| - return; |
103 |
| - } |
104 |
| - sel = this.getAttribute('data-panel-closest'); |
105 |
| - if (sel) { |
106 |
| - hideElem($(this).closest(sel)); |
107 |
| - return; |
108 |
| - } |
109 |
| - // should never happen, otherwise there is a bug in code |
110 |
| - showErrorToast('Nothing to hide'); |
111 |
| - }); |
| 88 | +function onHidePanelClick(e) { |
| 89 | + // a `.hide-panel` element can hide a panel, by `data-panel="selector"` or `data-panel-closest="selector"` |
| 90 | + const el = e.currentTarget; |
| 91 | + e.preventDefault(); |
| 92 | + let sel = el.getAttribute('data-panel'); |
| 93 | + if (sel) { |
| 94 | + hideElem(sel); |
| 95 | + return; |
| 96 | + } |
| 97 | + sel = el.getAttribute('data-panel-closest'); |
| 98 | + if (sel) { |
| 99 | + hideElem(el.closest(sel)); |
| 100 | + return; |
| 101 | + } |
| 102 | + throw new Error('no panel to hide'); // should never happen, otherwise there is a bug in code |
112 | 103 | }
|
113 | 104 |
|
114 |
| -export function initGlobalShowModal() { |
| 105 | +function onShowModalClick(e) { |
115 | 106 | // A ".show-modal" button will show a modal dialog defined by its "data-modal" attribute.
|
116 | 107 | // Each "data-modal-{target}" attribute will be filled to target element's value or text-content.
|
117 | 108 | // * First, try to query '#target'
|
118 | 109 | // * Then, try to query '.target'
|
119 | 110 | // * Then, try to query 'target' as HTML tag
|
120 | 111 | // If there is a ".{attr}" part like "data-modal-form.action", then the form's "action" attribute will be set.
|
121 |
| - $('.show-modal').on('click', function (e) { |
122 |
| - e.preventDefault(); |
123 |
| - const modalSelector = this.getAttribute('data-modal'); |
124 |
| - const $modal = $(modalSelector); |
125 |
| - if (!$modal.length) { |
126 |
| - throw new Error('no modal for this action'); |
| 112 | + const el = e.currentTarget; |
| 113 | + e.preventDefault(); |
| 114 | + const modalSelector = el.getAttribute('data-modal'); |
| 115 | + const elModal = document.querySelector(modalSelector); |
| 116 | + if (!elModal) throw new Error('no modal for this action'); |
| 117 | + |
| 118 | + const modalAttrPrefix = 'data-modal-'; |
| 119 | + for (const attrib of el.attributes) { |
| 120 | + if (!attrib.name.startsWith(modalAttrPrefix)) { |
| 121 | + continue; |
127 | 122 | }
|
128 |
| - const modalAttrPrefix = 'data-modal-'; |
129 |
| - for (const attrib of this.attributes) { |
130 |
| - if (!attrib.name.startsWith(modalAttrPrefix)) { |
131 |
| - continue; |
132 |
| - } |
133 | 123 |
|
134 |
| - const attrTargetCombo = attrib.name.substring(modalAttrPrefix.length); |
135 |
| - const [attrTargetName, attrTargetAttr] = attrTargetCombo.split('.'); |
136 |
| - // try to find target by: "#target" -> ".target" -> "target tag" |
137 |
| - let $attrTarget = $modal.find(`#${attrTargetName}`); |
138 |
| - if (!$attrTarget.length) $attrTarget = $modal.find(`.${attrTargetName}`); |
139 |
| - if (!$attrTarget.length) $attrTarget = $modal.find(`${attrTargetName}`); |
140 |
| - if (!$attrTarget.length) continue; // TODO: show errors in dev mode to remind developers that there is a bug |
141 |
| - |
142 |
| - if (attrTargetAttr) { |
143 |
| - $attrTarget[0][attrTargetAttr] = attrib.value; |
144 |
| - } else if ($attrTarget[0].matches('input, textarea')) { |
145 |
| - $attrTarget.val(attrib.value); // FIXME: add more supports like checkbox |
146 |
| - } else { |
147 |
| - $attrTarget[0].textContent = attrib.value; // FIXME: it should be more strict here, only handle div/span/p |
148 |
| - } |
| 124 | + const attrTargetCombo = attrib.name.substring(modalAttrPrefix.length); |
| 125 | + const [attrTargetName, attrTargetAttr] = attrTargetCombo.split('.'); |
| 126 | + // try to find target by: "#target" -> "[name=target]" -> ".target" -> "<target> tag" |
| 127 | + const attrTarget = elModal.querySelector(`#${attrTargetName}`) || |
| 128 | + elModal.querySelector(`[name=${attrTargetName}]`) || |
| 129 | + elModal.querySelector(`.${attrTargetName}`) || |
| 130 | + elModal.querySelector(`${attrTargetName}`); |
| 131 | + if (!attrTarget) { |
| 132 | + if (!window.config.runModeIsProd) throw new Error(`attr target "${attrTargetCombo}" not found for modal`); |
| 133 | + continue; |
149 | 134 | }
|
150 | 135 |
|
151 |
| - $modal.modal('setting', { |
152 |
| - onApprove: () => { |
153 |
| - // "form-fetch-action" can handle network errors gracefully, |
154 |
| - // so keep the modal dialog to make users can re-submit the form if anything wrong happens. |
155 |
| - if ($modal.find('.form-fetch-action').length) return false; |
156 |
| - }, |
157 |
| - }).modal('show'); |
158 |
| - }); |
| 136 | + if (attrTargetAttr) { |
| 137 | + attrTarget[attrTargetAttr] = attrib.value; |
| 138 | + } else if (attrTarget.matches('input, textarea')) { |
| 139 | + attrTarget.value = attrib.value; // FIXME: add more supports like checkbox |
| 140 | + } else { |
| 141 | + attrTarget.textContent = attrib.value; // FIXME: it should be more strict here, only handle div/span/p |
| 142 | + } |
| 143 | + } |
| 144 | + |
| 145 | + fomanticQuery(elModal).modal('setting', { |
| 146 | + onApprove: () => { |
| 147 | + // "form-fetch-action" can handle network errors gracefully, |
| 148 | + // so keep the modal dialog to make users can re-submit the form if anything wrong happens. |
| 149 | + if (elModal.querySelector('.form-fetch-action')) return false; |
| 150 | + }, |
| 151 | + }).modal('show'); |
| 152 | +} |
| 153 | + |
| 154 | +export function initGlobalButtons(): void { |
| 155 | + // There are many "cancel button" elements in modal dialogs, Fomantic UI expects they are button-like elements but never submit a form. |
| 156 | + // However, Gitea misuses the modal dialog and put the cancel buttons inside forms, so we must prevent the form submission. |
| 157 | + // There are a few cancel buttons in non-modal forms, and there are some dynamically created forms (eg: the "Edit Issue Content") |
| 158 | + addDelegatedEventListener(document, 'click', 'form button.ui.cancel.button', (_ /* el */, e) => e.preventDefault()); |
| 159 | + |
| 160 | + queryElems(document, '.show-panel', (el) => el.addEventListener('click', onShowPanelClick)); |
| 161 | + queryElems(document, '.hide-panel', (el) => el.addEventListener('click', onHidePanelClick)); |
| 162 | + queryElems(document, '.show-modal', (el) => el.addEventListener('click', onShowModalClick)); |
159 | 163 | }
|
0 commit comments