gitea源码

PullRequestMergeForm.vue 9.4KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253
  1. <script lang="ts" setup>
  2. import {computed, onMounted, onUnmounted, shallowRef, watch} from 'vue';
  3. import {SvgIcon} from '../svg.ts';
  4. import {toggleElem} from '../utils/dom.ts';
  5. const {csrfToken, pageData} = window.config;
  6. const mergeForm = pageData.pullRequestMergeForm;
  7. const mergeTitleFieldValue = shallowRef('');
  8. const mergeMessageFieldValue = shallowRef('');
  9. const deleteBranchAfterMerge = shallowRef(false);
  10. const autoMergeWhenSucceed = shallowRef(false);
  11. const mergeStyle = shallowRef('');
  12. const mergeStyleDetail = shallowRef({
  13. hideMergeMessageTexts: false,
  14. textDoMerge: '',
  15. mergeTitleFieldText: '',
  16. mergeMessageFieldText: '',
  17. hideAutoMerge: false,
  18. });
  19. const mergeStyleAllowedCount = shallowRef(0);
  20. const showMergeStyleMenu = shallowRef(false);
  21. const showActionForm = shallowRef(false);
  22. const mergeButtonStyleClass = computed(() => {
  23. if (mergeForm.allOverridableChecksOk) return 'primary';
  24. return autoMergeWhenSucceed.value ? 'primary' : 'red';
  25. });
  26. const forceMerge = computed(() => {
  27. return mergeForm.canMergeNow && !mergeForm.allOverridableChecksOk;
  28. });
  29. watch(mergeStyle, (val) => {
  30. mergeStyleDetail.value = mergeForm.mergeStyles.find((e: any) => e.name === val);
  31. for (const elem of document.querySelectorAll('[data-pull-merge-style]')) {
  32. toggleElem(elem, elem.getAttribute('data-pull-merge-style') === val);
  33. }
  34. });
  35. onMounted(() => {
  36. mergeStyleAllowedCount.value = mergeForm.mergeStyles.reduce((v: any, msd: any) => v + (msd.allowed ? 1 : 0), 0);
  37. let mergeStyle = mergeForm.mergeStyles.find((e: any) => e.allowed && e.name === mergeForm.defaultMergeStyle)?.name;
  38. if (!mergeStyle) mergeStyle = mergeForm.mergeStyles.find((e: any) => e.allowed)?.name;
  39. switchMergeStyle(mergeStyle, !mergeForm.canMergeNow);
  40. document.addEventListener('mouseup', hideMergeStyleMenu);
  41. });
  42. onUnmounted(() => {
  43. document.removeEventListener('mouseup', hideMergeStyleMenu);
  44. });
  45. function hideMergeStyleMenu() {
  46. showMergeStyleMenu.value = false;
  47. }
  48. function toggleActionForm(show: boolean) {
  49. showActionForm.value = show;
  50. if (!show) return;
  51. deleteBranchAfterMerge.value = mergeForm.defaultDeleteBranchAfterMerge;
  52. mergeTitleFieldValue.value = mergeStyleDetail.value.mergeTitleFieldText;
  53. mergeMessageFieldValue.value = mergeStyleDetail.value.mergeMessageFieldText;
  54. }
  55. function switchMergeStyle(name: string, autoMerge = false) {
  56. mergeStyle.value = name;
  57. autoMergeWhenSucceed.value = autoMerge;
  58. }
  59. function clearMergeMessage() {
  60. mergeMessageFieldValue.value = mergeForm.defaultMergeMessage;
  61. }
  62. </script>
  63. <template>
  64. <!--
  65. if this component is shown, either the user is an admin (can do a merge without checks), or they are a writer who has the permission to do a merge
  66. if the user is a writer and can't do a merge now (canMergeNow==false), then only show the Auto Merge for them
  67. How to test the UI manually:
  68. * Method 1: manually set some variables in pull.tmpl, eg: {{$notAllOverridableChecksOk = true}} {{$canMergeNow = false}}
  69. * Method 2: make a protected branch, then set state=pending/success :
  70. curl -X POST ${root_url}/api/v1/repos/${owner}/${repo}/statuses/${sha} \
  71. -H "accept: application/json" -H "authorization: Basic $base64_auth" -H "Content-Type: application/json" \
  72. -d '{"context": "test/context", "description": "description", "state": "${state}", "target_url": "http://localhost"}'
  73. -->
  74. <div>
  75. <!-- eslint-disable-next-line vue/no-v-html -->
  76. <div v-if="mergeForm.hasPendingPullRequestMerge" v-html="mergeForm.hasPendingPullRequestMergeTip" class="ui info message"/>
  77. <!-- another similar form is in pull.tmpl (manual merge)-->
  78. <form class="ui form form-fetch-action" v-if="showActionForm" :action="mergeForm.baseLink+'/merge'" method="post">
  79. <input type="hidden" name="_csrf" :value="csrfToken">
  80. <input type="hidden" name="head_commit_id" v-model="mergeForm.pullHeadCommitID">
  81. <input type="hidden" name="merge_when_checks_succeed" v-model="autoMergeWhenSucceed">
  82. <input type="hidden" name="force_merge" v-model="forceMerge">
  83. <template v-if="!mergeStyleDetail.hideMergeMessageTexts">
  84. <div class="field">
  85. <input type="text" name="merge_title_field" v-model="mergeTitleFieldValue">
  86. </div>
  87. <div class="field">
  88. <textarea name="merge_message_field" rows="5" :placeholder="mergeForm.mergeMessageFieldPlaceHolder" v-model="mergeMessageFieldValue"/>
  89. <template v-if="mergeMessageFieldValue !== mergeForm.defaultMergeMessage">
  90. <button @click.prevent="clearMergeMessage" class="btn tw-mt-1 tw-p-1 interact-fg" :data-tooltip-content="mergeForm.textClearMergeMessageHint">
  91. {{ mergeForm.textClearMergeMessage }}
  92. </button>
  93. </template>
  94. </div>
  95. </template>
  96. <div class="field" v-if="mergeStyle === 'manually-merged'">
  97. <input type="text" name="merge_commit_id" :placeholder="mergeForm.textMergeCommitId">
  98. </div>
  99. <button class="ui button" :class="mergeButtonStyleClass" type="submit" name="do" :value="mergeStyle">
  100. {{ mergeStyleDetail.textDoMerge }}
  101. <template v-if="autoMergeWhenSucceed">
  102. {{ mergeForm.textAutoMergeButtonWhenSucceed }}
  103. </template>
  104. </button>
  105. <button class="ui button merge-cancel" @click="toggleActionForm(false)">
  106. {{ mergeForm.textCancel }}
  107. </button>
  108. <div class="ui checkbox tw-ml-1" v-if="mergeForm.isPullBranchDeletable">
  109. <input name="delete_branch_after_merge" type="checkbox" v-model="deleteBranchAfterMerge" id="delete-branch-after-merge">
  110. <label for="delete-branch-after-merge">{{ mergeForm.textDeleteBranch }}</label>
  111. </div>
  112. </form>
  113. <div v-if="!showActionForm" class="tw-flex">
  114. <!-- the merge button -->
  115. <div class="ui buttons merge-button" :class="[mergeForm.emptyCommit ? '' : mergeForm.allOverridableChecksOk ? 'primary' : 'red']" @click="toggleActionForm(true)">
  116. <button class="ui button">
  117. <svg-icon name="octicon-git-merge"/>
  118. <span class="button-text">
  119. {{ mergeStyleDetail.textDoMerge }}
  120. <template v-if="autoMergeWhenSucceed">
  121. {{ mergeForm.textAutoMergeButtonWhenSucceed }}
  122. </template>
  123. </span>
  124. </button>
  125. <div class="ui dropdown icon button" @click.stop="showMergeStyleMenu = !showMergeStyleMenu">
  126. <svg-icon name="octicon-triangle-down" :size="14"/>
  127. <div class="menu" :class="{'show':showMergeStyleMenu}">
  128. <template v-for="msd in mergeForm.mergeStyles">
  129. <!-- if can merge now, show one action "merge now", and an action "auto merge when succeed" -->
  130. <div class="item" v-if="msd.allowed && mergeForm.canMergeNow" :key="msd.name" @click.stop="switchMergeStyle(msd.name)">
  131. <div class="action-text">
  132. {{ msd.textDoMerge }}
  133. </div>
  134. <div v-if="!msd.hideAutoMerge" class="auto-merge-small" @click.stop="switchMergeStyle(msd.name, true)">
  135. <svg-icon name="octicon-clock" :size="14"/>
  136. <div class="auto-merge-tip">
  137. {{ mergeForm.textAutoMergeWhenSucceed }}
  138. </div>
  139. </div>
  140. </div>
  141. <!-- if can NOT merge now, only show one action "auto merge when succeed" -->
  142. <div class="item" v-if="msd.allowed && !mergeForm.canMergeNow && !msd.hideAutoMerge" :key="msd.name" @click.stop="switchMergeStyle(msd.name, true)">
  143. <div class="action-text">
  144. {{ msd.textDoMerge }} {{ mergeForm.textAutoMergeButtonWhenSucceed }}
  145. </div>
  146. </div>
  147. </template>
  148. </div>
  149. </div>
  150. </div>
  151. <!-- the cancel auto merge button -->
  152. <form v-if="mergeForm.hasPendingPullRequestMerge" :action="mergeForm.baseLink+'/cancel_auto_merge'" method="post" class="tw-ml-4">
  153. <input type="hidden" name="_csrf" :value="csrfToken">
  154. <button class="ui button">
  155. {{ mergeForm.textAutoMergeCancelSchedule }}
  156. </button>
  157. </form>
  158. </div>
  159. </div>
  160. </template>
  161. <style scoped>
  162. /* to keep UI the same, at the moment we are still using some Fomantic UI styles, but we do not use their scripts, so we need to fine tune some styles */
  163. .ui.dropdown .menu.show {
  164. display: block;
  165. }
  166. .ui.checkbox label {
  167. cursor: pointer;
  168. }
  169. /* make the dropdown list left-aligned */
  170. .ui.merge-button {
  171. position: relative;
  172. }
  173. .ui.merge-button .ui.dropdown {
  174. position: static;
  175. }
  176. .ui.merge-button > .ui.dropdown:last-child > .menu:not(.left) {
  177. left: 0;
  178. right: auto;
  179. }
  180. .ui.merge-button .ui.dropdown .menu > .item {
  181. display: flex;
  182. align-items: stretch;
  183. padding: 0 !important; /* polluted by semantic.css: .ui.dropdown .menu > .item { !important } */
  184. }
  185. /* merge style list item */
  186. .action-text {
  187. padding: 0.8rem;
  188. flex: 1
  189. }
  190. .auto-merge-small {
  191. width: 40px;
  192. display: flex;
  193. align-items: center;
  194. justify-content: center;
  195. position: relative;
  196. }
  197. .auto-merge-small .auto-merge-tip {
  198. display: none;
  199. left: 38px;
  200. top: -1px;
  201. bottom: -1px;
  202. position: absolute;
  203. align-items: center;
  204. color: var(--color-info-text);
  205. background-color: var(--color-info-bg);
  206. border: 1px solid var(--color-info-border);
  207. border-left: none;
  208. padding-right: 1rem;
  209. }
  210. .auto-merge-small:hover {
  211. color: var(--color-info-text);
  212. background-color: var(--color-info-bg);
  213. border: 1px solid var(--color-info-border);
  214. }
  215. .auto-merge-small:hover .auto-merge-tip {
  216. display: flex;
  217. }
  218. </style>