|  | 
|  | 1 | +import $ from 'jquery'; | 
|  | 2 | +import {POST} from '../modules/fetch.js'; | 
|  | 3 | +import {hideElem, showElem, toggleElem} from '../utils/dom.js'; | 
|  | 4 | +import {showErrorToast} from '../modules/toast.js'; | 
|  | 5 | + | 
|  | 6 | +export function initGlobalButtonClickOnEnter() { | 
|  | 7 | +  $(document).on('keypress', 'div.ui.button,span.ui.button', (e) => { | 
|  | 8 | +    if (e.code === ' ' || e.code === 'Enter') { | 
|  | 9 | +      $(e.target).trigger('click'); | 
|  | 10 | +      e.preventDefault(); | 
|  | 11 | +    } | 
|  | 12 | +  }); | 
|  | 13 | +} | 
|  | 14 | + | 
|  | 15 | +export function initGlobalDeleteButton() { | 
|  | 16 | +  // ".delete-button" shows a confirmation modal defined by `data-modal-id` attribute. | 
|  | 17 | +  // Some model/form elements will be filled by `data-id` / `data-name` / `data-data-xxx` attributes. | 
|  | 18 | +  // If there is a form defined by `data-form`, then the form will be submitted as-is (without any modification). | 
|  | 19 | +  // If there is no form, then the data will be posted to `data-url`. | 
|  | 20 | +  // TODO: it's not encouraged to use this method. `show-modal` does far better than this. | 
|  | 21 | +  for (const btn of document.querySelectorAll('.delete-button')) { | 
|  | 22 | +    btn.addEventListener('click', (e) => { | 
|  | 23 | +      e.preventDefault(); | 
|  | 24 | + | 
|  | 25 | +      // eslint-disable-next-line github/no-dataset -- code depends on the camel-casing | 
|  | 26 | +      const dataObj = btn.dataset; | 
|  | 27 | + | 
|  | 28 | +      const modalId = btn.getAttribute('data-modal-id'); | 
|  | 29 | +      const modal = document.querySelector(`.delete.modal${modalId ? `#${modalId}` : ''}`); | 
|  | 30 | + | 
|  | 31 | +      // set the modal "display name" by `data-name` | 
|  | 32 | +      const modalNameEl = modal.querySelector('.name'); | 
|  | 33 | +      if (modalNameEl) modalNameEl.textContent = btn.getAttribute('data-name'); | 
|  | 34 | + | 
|  | 35 | +      // fill the modal elements with data-xxx attributes: `data-data-organization-name="..."` => `<span class="dataOrganizationName">...</span>` | 
|  | 36 | +      for (const [key, value] of Object.entries(dataObj)) { | 
|  | 37 | +        if (key.startsWith('data')) { | 
|  | 38 | +          const textEl = modal.querySelector(`.${key}`); | 
|  | 39 | +          if (textEl) textEl.textContent = value; | 
|  | 40 | +        } | 
|  | 41 | +      } | 
|  | 42 | + | 
|  | 43 | +      $(modal).modal({ | 
|  | 44 | +        closable: false, | 
|  | 45 | +        onApprove: async () => { | 
|  | 46 | +          // if `data-type="form"` exists, then submit the form by the selector provided by `data-form="..."` | 
|  | 47 | +          if (btn.getAttribute('data-type') === 'form') { | 
|  | 48 | +            const formSelector = btn.getAttribute('data-form'); | 
|  | 49 | +            const form = document.querySelector(formSelector); | 
|  | 50 | +            if (!form) throw new Error(`no form named ${formSelector} found`); | 
|  | 51 | +            form.submit(); | 
|  | 52 | +          } | 
|  | 53 | + | 
|  | 54 | +          // prepare an AJAX form by data attributes | 
|  | 55 | +          const postData = new FormData(); | 
|  | 56 | +          for (const [key, value] of Object.entries(dataObj)) { | 
|  | 57 | +            if (key.startsWith('data')) { // for data-data-xxx (HTML) -> dataXxx (form) | 
|  | 58 | +              postData.append(key.slice(4), value); | 
|  | 59 | +            } | 
|  | 60 | +            if (key === 'id') { // for data-id="..." | 
|  | 61 | +              postData.append('id', value); | 
|  | 62 | +            } | 
|  | 63 | +          } | 
|  | 64 | + | 
|  | 65 | +          const response = await POST(btn.getAttribute('data-url'), {data: postData}); | 
|  | 66 | +          if (response.ok) { | 
|  | 67 | +            const data = await response.json(); | 
|  | 68 | +            window.location.href = data.redirect; | 
|  | 69 | +          } | 
|  | 70 | +        }, | 
|  | 71 | +      }).modal('show'); | 
|  | 72 | +    }); | 
|  | 73 | +  } | 
|  | 74 | +} | 
|  | 75 | + | 
|  | 76 | +export function initGlobalButtons() { | 
|  | 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 | +  }); | 
|  | 95 | + | 
|  | 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 | +  }); | 
|  | 112 | +} | 
|  | 113 | + | 
|  | 114 | +export function initGlobalShowModal() { | 
|  | 115 | +  // A ".show-modal" button will show a modal dialog defined by its "data-modal" attribute. | 
|  | 116 | +  // Each "data-modal-{target}" attribute will be filled to target element's value or text-content. | 
|  | 117 | +  // * First, try to query '#target' | 
|  | 118 | +  // * Then, try to query '.target' | 
|  | 119 | +  // * Then, try to query 'target' as HTML tag | 
|  | 120 | +  // 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'); | 
|  | 127 | +    } | 
|  | 128 | +    const modalAttrPrefix = 'data-modal-'; | 
|  | 129 | +    for (const attrib of this.attributes) { | 
|  | 130 | +      if (!attrib.name.startsWith(modalAttrPrefix)) { | 
|  | 131 | +        continue; | 
|  | 132 | +      } | 
|  | 133 | + | 
|  | 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 | +      } | 
|  | 149 | +    } | 
|  | 150 | + | 
|  | 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 | +  }); | 
|  | 159 | +} | 
0 commit comments