| 
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