paste to form file field

ctrl-v to paste clipboard file into file form field

  1. // ==UserScript==
  2. // @name paste to form file field
  3. // @namespace http://gholk.github.io
  4. // @description ctrl-v to paste clipboard file into file form field
  5. // @match https://www.google.com/imghp
  6. // @version 5.1.0
  7. // @grant GM.xmlHttpRequest
  8. // @license GPLv3
  9. // ==/UserScript==
  10.  
  11. /* todo
  12. * append file when multiple
  13. * multiple image selection
  14. * option paste from anchor or image
  15. */
  16.  
  17. document.body.addEventListener('paste', (paste) => {
  18. if (paste.clipboardData.files.length == 0) return
  19. putFileIntoForm(paste.clipboardData.files)
  20. })
  21. document.body.addEventListener('dragover', (over) => over.preventDefault())
  22. document.body.addEventListener('drop', async (drop) => {
  23. drop.preventDefault()
  24. const data = drop.dataTransfer
  25. console.debug('type', ...data.types)
  26. let fileList
  27. if (typeof GM != 'undefined' && data.files.length == 0) {
  28. let urlList
  29. if (~data.types.indexOf('text/html')) {
  30. const html = data.getData('text/html')
  31. const dom = parseHtml(html)
  32. urlList = dom.querySelectorAll('img')
  33. console.debug('query img url')
  34. if (urlList.length == 0) {
  35. console.debug('no image, query anchor')
  36. urlList = dom.querySelectorAll('a')
  37. }
  38. urlList = Array.from(urlList).map(node => node.src || node.href)
  39. }
  40. if (urlList.length == 0 && ~data.types.indexOf('text/plain')) {
  41. console.debug('no url found, try plain text')
  42. urlList = data.getData('text/plain')
  43. .split('\n').filter(u => u.charAt(0) != '#')
  44. }
  45. try {
  46. fileList = await Promise.all(urlList.map(fetchFile))
  47. }
  48. catch (e) {
  49. console.error(e)
  50. return
  51. }
  52. fileList = createFileList(...fileList)
  53. }
  54. else fileList = drop.dataTransfer.files
  55. console.debug('file list:', fileList)
  56. putFileIntoForm(fileList)
  57. })
  58.  
  59. function createFileList(...fileList) {
  60. // attribute to https://stackoverflow.com/a/56447852/8362703
  61. const data = new DataTransfer()
  62. for (const file of fileList) data.items.add(file)
  63. return data.files
  64. }
  65. function parseHtml(html) {
  66. const parser = new DOMParser()
  67. const dom = parser.parseFromString(html, 'text/html')
  68. return dom
  69. }
  70.  
  71. async function fetchFile(url) {
  72. function xhrToFileType(xhr) {
  73. for (const row of xhr.responseHeaders.split(/\n/)) {
  74. // line-end with \r\n
  75. const scan = row.match(/^content-type: ([-_+\w]+)\/([-_+\w]+)/i)
  76. if (scan) {
  77. console.debug(row)
  78. return scan[2]
  79. }
  80. }
  81. }
  82. return await new Promise((resolve, reject) => {
  83. GM.xmlHttpRequest({
  84. method: 'GET', url,
  85. responseType: 'blob',
  86. onload(xhr) {
  87. const blob = xhr.response
  88. const type = xhrToFileType(xhr)
  89. let file
  90. if (type) {
  91. file = new File(
  92. [blob], `drop-image.${type}`,
  93. {type: `image/${type}`}
  94. )
  95. }
  96. else file = new File([blob], `drop-image`)
  97. resolve(file)
  98. },
  99. onerror(xhr) {
  100. reject(xhr.statusText)
  101. }
  102. })
  103. })
  104. }
  105.  
  106. function putFileIntoForm(fileList) {
  107. const input = document.querySelector('input[type = "file"]')
  108. if (!input) return
  109. console.debug('fileList:', ...fileList)
  110. // attribute to https://stackoverflow.com/a/50427897/8362703
  111. input.files = fileList
  112. const change = new Event('change', {bubbles: true, cancelable: false})
  113. input.dispatchEvent(change)
  114. }