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