mirror of
				https://gitee.com/gitea/gitea
				synced 2025-11-04 16:40:24 +08:00 
			
		
		
		
	Use clippie module to copy to clipboard (#23801)
Externalize clipboard copying to the [clippie](https://github.com/silverwind/clippie) module which I feel I can maintain outside this repo for shared benefit with my other projects. The module is feature-equivalent to the previous code and has one improvement where it sets `aria-hidden` on the fallback textarea, preventing screen readers from picking it up. Also it support `Array` of `content` as well to copy multiple items at once, in case it's ever needed.
This commit is contained in:
		
							
								
								
									
										6
									
								
								package-lock.json
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										6
									
								
								package-lock.json
									
									
									
										generated
									
									
									
								
							@@ -19,6 +19,7 @@
 | 
				
			|||||||
        "add-asset-webpack-plugin": "2.0.1",
 | 
					        "add-asset-webpack-plugin": "2.0.1",
 | 
				
			||||||
        "ansi-to-html": "0.7.2",
 | 
					        "ansi-to-html": "0.7.2",
 | 
				
			||||||
        "asciinema-player": "3.2.0",
 | 
					        "asciinema-player": "3.2.0",
 | 
				
			||||||
 | 
					        "clippie": "3.1.4",
 | 
				
			||||||
        "css-loader": "6.7.3",
 | 
					        "css-loader": "6.7.3",
 | 
				
			||||||
        "dropzone": "6.0.0-beta.2",
 | 
					        "dropzone": "6.0.0-beta.2",
 | 
				
			||||||
        "easymde": "2.18.0",
 | 
					        "easymde": "2.18.0",
 | 
				
			||||||
@@ -2762,6 +2763,11 @@
 | 
				
			|||||||
        "url": "https://github.com/chalk/strip-ansi?sponsor=1"
 | 
					        "url": "https://github.com/chalk/strip-ansi?sponsor=1"
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
 | 
					    "node_modules/clippie": {
 | 
				
			||||||
 | 
					      "version": "3.1.4",
 | 
				
			||||||
 | 
					      "resolved": "https://registry.npmjs.org/clippie/-/clippie-3.1.4.tgz",
 | 
				
			||||||
 | 
					      "integrity": "sha512-jrW6sG1zcTEQr5MtCXJzszNmHWV9Fkaco8sAqFeuOApNFP/lRFcUi4JABMmxBJwFZLIvbw2BY3G5E+BjBqZMdQ=="
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
    "node_modules/cliui": {
 | 
					    "node_modules/cliui": {
 | 
				
			||||||
      "version": "7.0.4",
 | 
					      "version": "7.0.4",
 | 
				
			||||||
      "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz",
 | 
					      "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz",
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -19,6 +19,7 @@
 | 
				
			|||||||
    "add-asset-webpack-plugin": "2.0.1",
 | 
					    "add-asset-webpack-plugin": "2.0.1",
 | 
				
			||||||
    "ansi-to-html": "0.7.2",
 | 
					    "ansi-to-html": "0.7.2",
 | 
				
			||||||
    "asciinema-player": "3.2.0",
 | 
					    "asciinema-player": "3.2.0",
 | 
				
			||||||
 | 
					    "clippie": "3.1.4",
 | 
				
			||||||
    "css-loader": "6.7.3",
 | 
					    "css-loader": "6.7.3",
 | 
				
			||||||
    "dropzone": "6.0.0-beta.2",
 | 
					    "dropzone": "6.0.0-beta.2",
 | 
				
			||||||
    "easymde": "2.18.0",
 | 
					    "easymde": "2.18.0",
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,48 +1,9 @@
 | 
				
			|||||||
import {showTemporaryTooltip} from '../modules/tippy.js';
 | 
					import {showTemporaryTooltip} from '../modules/tippy.js';
 | 
				
			||||||
import {toAbsoluteUrl} from '../utils.js';
 | 
					import {toAbsoluteUrl} from '../utils.js';
 | 
				
			||||||
 | 
					import {clippie} from 'clippie';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const {copy_success, copy_error} = window.config.i18n;
 | 
					const {copy_success, copy_error} = window.config.i18n;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export async function copyToClipboard(content) {
 | 
					 | 
				
			||||||
  if (content instanceof Blob) {
 | 
					 | 
				
			||||||
    const item = new ClipboardItem({[content.type]: content});
 | 
					 | 
				
			||||||
    await navigator.clipboard.write([item]);
 | 
					 | 
				
			||||||
  } else { // text
 | 
					 | 
				
			||||||
    try {
 | 
					 | 
				
			||||||
      await navigator.clipboard.writeText(content);
 | 
					 | 
				
			||||||
    } catch {
 | 
					 | 
				
			||||||
      return fallbackCopyToClipboard(content);
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
  return true;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
// Fallback to use if navigator.clipboard doesn't exist. Achieved via creating
 | 
					 | 
				
			||||||
// a temporary textarea element, selecting the text, and using document.execCommand
 | 
					 | 
				
			||||||
function fallbackCopyToClipboard(text) {
 | 
					 | 
				
			||||||
  if (!document.execCommand) return false;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  const tempTextArea = document.createElement('textarea');
 | 
					 | 
				
			||||||
  tempTextArea.value = text;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  // avoid scrolling
 | 
					 | 
				
			||||||
  tempTextArea.style.top = 0;
 | 
					 | 
				
			||||||
  tempTextArea.style.left = 0;
 | 
					 | 
				
			||||||
  tempTextArea.style.position = 'fixed';
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  document.body.appendChild(tempTextArea);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  tempTextArea.select();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  // if unsecure (not https), there is no navigator.clipboard, but we can still
 | 
					 | 
				
			||||||
  // use document.execCommand to copy to clipboard
 | 
					 | 
				
			||||||
  const success = document.execCommand('copy');
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  document.body.removeChild(tempTextArea);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  return success;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
// For all DOM elements with [data-clipboard-target] or [data-clipboard-text],
 | 
					// For all DOM elements with [data-clipboard-target] or [data-clipboard-text],
 | 
				
			||||||
// this copy-to-clipboard will work for them
 | 
					// this copy-to-clipboard will work for them
 | 
				
			||||||
export function initGlobalCopyToClipboardListener() {
 | 
					export function initGlobalCopyToClipboardListener() {
 | 
				
			||||||
@@ -61,7 +22,7 @@ export function initGlobalCopyToClipboardListener() {
 | 
				
			|||||||
        e.preventDefault();
 | 
					        e.preventDefault();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        (async() => {
 | 
					        (async() => {
 | 
				
			||||||
          const success = await copyToClipboard(text);
 | 
					          const success = await clippie(text);
 | 
				
			||||||
          showTemporaryTooltip(target, success ? copy_success : copy_error);
 | 
					          showTemporaryTooltip(target, success ? copy_success : copy_error);
 | 
				
			||||||
        })();
 | 
					        })();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,11 +1,11 @@
 | 
				
			|||||||
import {copyToClipboard} from './clipboard.js';
 | 
					import {clippie} from 'clippie';
 | 
				
			||||||
import {showTemporaryTooltip} from '../modules/tippy.js';
 | 
					import {showTemporaryTooltip} from '../modules/tippy.js';
 | 
				
			||||||
import {convertImage} from '../utils.js';
 | 
					import {convertImage} from '../utils.js';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const {i18n} = window.config;
 | 
					const {i18n} = window.config;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
async function doCopy(content, btn) {
 | 
					async function doCopy(content, btn) {
 | 
				
			||||||
  const success = await copyToClipboard(content);
 | 
					  const success = await clippie(content);
 | 
				
			||||||
  showTemporaryTooltip(btn, success ? i18n.copy_success : i18n.copy_error);
 | 
					  showTemporaryTooltip(btn, success ? i18n.copy_success : i18n.copy_error);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -2,7 +2,7 @@ import $ from 'jquery';
 | 
				
			|||||||
import {svg} from '../svg.js';
 | 
					import {svg} from '../svg.js';
 | 
				
			||||||
import {invertFileFolding} from './file-fold.js';
 | 
					import {invertFileFolding} from './file-fold.js';
 | 
				
			||||||
import {createTippy} from '../modules/tippy.js';
 | 
					import {createTippy} from '../modules/tippy.js';
 | 
				
			||||||
import {copyToClipboard} from './clipboard.js';
 | 
					import {clippie} from 'clippie';
 | 
				
			||||||
import {toAbsoluteUrl} from '../utils.js';
 | 
					import {toAbsoluteUrl} from '../utils.js';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export const singleAnchorRegex = /^#(L|n)([1-9][0-9]*)$/;
 | 
					export const singleAnchorRegex = /^#(L|n)([1-9][0-9]*)$/;
 | 
				
			||||||
@@ -190,7 +190,7 @@ export function initRepoCodeView() {
 | 
				
			|||||||
    currentTarget.closest('tr').outerHTML = blob;
 | 
					    currentTarget.closest('tr').outerHTML = blob;
 | 
				
			||||||
  });
 | 
					  });
 | 
				
			||||||
  $(document).on('click', '.copy-line-permalink', async (e) => {
 | 
					  $(document).on('click', '.copy-line-permalink', async (e) => {
 | 
				
			||||||
    const success = await copyToClipboard(toAbsoluteUrl(e.currentTarget.getAttribute('data-url')));
 | 
					    const success = await clippie(toAbsoluteUrl(e.currentTarget.getAttribute('data-url')));
 | 
				
			||||||
    if (!success) return;
 | 
					    if (!success) return;
 | 
				
			||||||
    document.querySelector('.code-line-button')?._tippy?.hide();
 | 
					    document.querySelector('.code-line-button')?._tippy?.hide();
 | 
				
			||||||
  });
 | 
					  });
 | 
				
			||||||
 
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user