2626 // 立即注入样式,保证按钮和弹窗都美观
2727 injectStyle ( ) ;
2828
29- if ( window . __gitlab_export_btn_injected ) return ;
30- window . __gitlab_export_btn_injected = true ;
29+ if ( window . __gitlab_export_btn_injected && window . __gitlab_copy_diff_btn_injected ) return ;
30+
31+ // 检查是否是merge request页面
32+ const isMergeRequestPage = window . location . href . includes ( '/merge_requests/' ) ;
33+
34+ if ( ! window . __gitlab_export_btn_injected ) {
35+ window . __gitlab_export_btn_injected = true ;
36+ }
37+
38+ if ( isMergeRequestPage && ! window . __gitlab_copy_diff_btn_injected ) {
39+ window . __gitlab_copy_diff_btn_injected = true ;
40+ }
3141
3242 // 插入导出按钮
3343 function insertExportBtn ( ) {
414424 return '' ;
415425 }
416426
417- const observer = new MutationObserver ( insertExportBtn ) ;
427+ // 插入复制git diff按钮
428+ function insertCopyDiffBtn ( ) {
429+ // 检查是否是merge request页面
430+ if ( ! window . location . href . includes ( '/merge_requests/' ) ) return ;
431+
432+ // 如果按钮已存在,则不重复添加
433+ if ( document . getElementById ( 'gitlab-copy-diff-btn' ) ) return ;
434+
435+ // 查找Edit按钮
436+ const editBtn = document . querySelector ( '.detail-page-header-actions a.js-issuable-edit' ) ;
437+ if ( ! editBtn ) return ;
438+
439+ // 创建复制git diff按钮
440+ const btn = document . createElement ( 'button' ) ;
441+ btn . id = 'gitlab-copy-diff-btn' ;
442+ btn . textContent = '复制git diff' ;
443+ btn . className = 'gl-button btn btn-md btn-default gl-display-none gl-md-display-block' ;
444+ btn . style . marginRight = '8px' ;
445+ btn . onclick = copyGitDiff ;
446+
447+ // 创建按钮内部的span元素,与Edit按钮结构保持一致
448+ const spanText = document . createElement ( 'span' ) ;
449+ spanText . className = 'gl-button-text' ;
450+ spanText . textContent = '复制git diff' ;
451+ btn . textContent = '' ; // 清空按钮文本
452+ btn . appendChild ( spanText ) ;
453+
454+ // 插入按钮到Edit按钮前面
455+ editBtn . parentNode . insertBefore ( btn , editBtn ) ;
456+
457+ // 同时在移动端下拉菜单中添加复制git diff选项
458+ try {
459+ const mobileEditItem = document . querySelector ( '[data-testid="edit-merge-request"]' ) ;
460+ if ( mobileEditItem ) {
461+ const mobileMenu = mobileEditItem . parentNode ;
462+
463+ const copyDiffItem = document . createElement ( 'li' ) ;
464+ copyDiffItem . className = 'gl-new-dropdown-item' ;
465+ copyDiffItem . tabIndex = 0 ;
466+ copyDiffItem . dataset . testid = 'copy-git-diff' ;
467+
468+ const copyDiffButton = document . createElement ( 'button' ) ;
469+ copyDiffButton . tabIndex = - 1 ;
470+ copyDiffButton . type = 'button' ;
471+ copyDiffButton . className = 'gl-new-dropdown-item-content' ;
472+ copyDiffButton . onclick = copyGitDiff ;
473+
474+ const textWrapper = document . createElement ( 'span' ) ;
475+ textWrapper . className = 'gl-new-dropdown-item-text-wrapper' ;
476+ textWrapper . textContent = '复制git diff' ;
477+
478+ copyDiffButton . appendChild ( textWrapper ) ;
479+ copyDiffItem . appendChild ( copyDiffButton ) ;
480+
481+ // 插入到Edit选项前面
482+ mobileMenu . insertBefore ( copyDiffItem , mobileEditItem ) ;
483+ }
484+ } catch ( e ) {
485+ console . error ( '添加移动端复制git diff选项失败:' , e ) ;
486+ }
487+ }
488+
489+ // 复制git diff功能
490+ function copyGitDiff ( event ) {
491+ // 阻止事件冒泡,防止触发其他事件
492+ if ( event ) {
493+ event . preventDefault ( ) ;
494+ event . stopPropagation ( ) ;
495+ }
496+
497+ // 获取当前页面URL
498+ const currentUrl = window . location . href ;
499+
500+ // 构建.diff URL
501+ const diffUrl = currentUrl . replace ( / \/ m e r g e _ r e q u e s t s \/ ( \d + ) .* $ / , '/merge_requests/$1.diff' ) ;
502+
503+ // 判断是桌面按钮还是移动端菜单项
504+ const isMobileMenu = event && event . currentTarget && event . currentTarget . classList . contains ( 'gl-new-dropdown-item-content' ) ;
505+
506+ // 获取按钮元素和文本元素
507+ let btn , textElement , originalText ;
508+
509+ if ( isMobileMenu ) {
510+ btn = event . currentTarget ;
511+ textElement = btn . querySelector ( '.gl-new-dropdown-item-text-wrapper' ) ;
512+ originalText = textElement . textContent ;
513+ textElement . textContent = '加载中...' ;
514+ } else {
515+ btn = document . getElementById ( 'gitlab-copy-diff-btn' ) ;
516+ textElement = btn . querySelector ( '.gl-button-text' ) ;
517+ originalText = textElement . textContent ;
518+ textElement . textContent = '加载中...' ;
519+ btn . disabled = true ;
520+ }
521+
522+ // 获取diff内容
523+ fetch ( diffUrl )
524+ . then ( response => {
525+ if ( ! response . ok ) {
526+ throw new Error ( '获取diff失败' ) ;
527+ }
528+ return response . text ( ) ;
529+ } )
530+ . then ( diffText => {
531+ // 复制到剪贴板
532+ navigator . clipboard . writeText ( diffText )
533+ . then ( ( ) => {
534+ // 显示成功状态
535+ textElement . textContent = '复制成功!' ;
536+
537+ // 创建并显示一个临时的成功提示
538+ showToast ( 'Git diff已复制到剪贴板' ) ;
539+
540+ setTimeout ( ( ) => {
541+ textElement . textContent = originalText ;
542+ if ( ! isMobileMenu ) {
543+ btn . disabled = false ;
544+ }
545+ } , 2000 ) ;
546+ } )
547+ . catch ( err => {
548+ console . error ( '复制失败:' , err ) ;
549+ textElement . textContent = '复制失败' ;
550+ setTimeout ( ( ) => {
551+ textElement . textContent = originalText ;
552+ if ( ! isMobileMenu ) {
553+ btn . disabled = false ;
554+ }
555+ } , 2000 ) ;
556+ } ) ;
557+ } )
558+ . catch ( error => {
559+ console . error ( '获取diff失败:' , error ) ;
560+ textElement . textContent = '获取失败' ;
561+ setTimeout ( ( ) => {
562+ textElement . textContent = originalText ;
563+ if ( ! isMobileMenu ) {
564+ btn . disabled = false ;
565+ }
566+ } , 2000 ) ;
567+ } ) ;
568+ }
569+
570+ // 显示一个临时的toast提示
571+ function showToast ( message ) {
572+ // 检查是否已存在toast
573+ let toast = document . querySelector ( '.gitlab-toast' ) ;
574+ if ( toast ) {
575+ toast . remove ( ) ;
576+ }
577+
578+ // 创建toast元素
579+ toast = document . createElement ( 'div' ) ;
580+ toast . className = 'gitlab-toast' ;
581+ toast . textContent = message ;
582+ toast . style . cssText = `
583+ position: fixed;
584+ bottom: 20px;
585+ left: 50%;
586+ transform: translateX(-50%);
587+ background-color: #1f75cb;
588+ color: white;
589+ padding: 10px 20px;
590+ border-radius: 4px;
591+ z-index: 9999;
592+ box-shadow: 0 2px 10px rgba(0, 0, 0, 0.2);
593+ font-size: 14px;
594+ font-weight: 500;
595+ ` ;
596+
597+ // 添加到页面
598+ document . body . appendChild ( toast ) ;
599+
600+ // 2秒后自动消失
601+ setTimeout ( ( ) => {
602+ toast . style . opacity = '0' ;
603+ toast . style . transition = 'opacity 0.5s' ;
604+ setTimeout ( ( ) => toast . remove ( ) , 500 ) ;
605+ } , 2000 ) ;
606+ }
607+
608+ // 根据页面类型执行不同的操作
609+ function handlePageChange ( ) {
610+ if ( window . location . href . includes ( '/users/' ) && window . location . href . includes ( '/activity' ) ) {
611+ insertExportBtn ( ) ;
612+ } else if ( window . location . href . includes ( '/merge_requests/' ) ) {
613+ insertCopyDiffBtn ( ) ;
614+ }
615+ }
616+
617+ const observer = new MutationObserver ( handlePageChange ) ;
418618 observer . observe ( document . body , { childList : true , subtree : true } ) ;
419- insertExportBtn ( ) ;
619+ handlePageChange ( ) ;
420620} ) ( ) ;
0 commit comments