index.js 7.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239
  1. const { Plugin } = require('@uppy/core')
  2. const Translator = require('@uppy/utils/lib/Translator')
  3. const { h } = require('preact')
  4. const { RequestClient } = require('@uppy/companion-client')
  5. const UrlUI = require('./UrlUI.js')
  6. const toArray = require('@uppy/utils/lib/toArray')
  7. /**
  8. * Url
  9. *
  10. */
  11. module.exports = class Url extends Plugin {
  12. constructor (uppy, opts) {
  13. super(uppy, opts)
  14. this.id = this.opts.id || 'Url'
  15. this.title = this.opts.title || 'Link'
  16. this.type = 'acquirer'
  17. this.icon = () => <svg aria-hidden="true" width="23" height="23" viewBox="0 0 23 23" xmlns="http://www.w3.org/2000/svg">
  18. <path d="M20.485 11.236l-2.748 2.737c-.184.182-.367.365-.642.547-1.007.73-2.107 1.095-3.298 1.095-1.65 0-3.298-.73-4.398-2.19-.275-.365-.183-1.003.183-1.277.367-.273 1.008-.182 1.283.183 1.191 1.642 3.482 1.915 5.13.73a.714.714 0 0 0 .367-.365l2.75-2.737c1.373-1.46 1.373-3.74-.093-5.108a3.72 3.72 0 0 0-5.13 0L12.33 6.4a.888.888 0 0 1-1.283 0 .88.88 0 0 1 0-1.277l1.558-1.55a5.38 5.38 0 0 1 7.605 0c2.29 2.006 2.382 5.564.274 7.662zm-8.979 6.294L9.95 19.081a3.72 3.72 0 0 1-5.13 0c-1.467-1.368-1.467-3.74-.093-5.108l2.75-2.737.366-.365c.824-.547 1.74-.82 2.748-.73 1.008.183 1.833.639 2.382 1.46.275.365.917.456 1.283.182.367-.273.458-.912.183-1.277-.916-1.186-2.199-1.915-3.573-2.098-1.374-.273-2.84.091-4.031 1.004l-.55.547-2.749 2.737c-2.107 2.189-2.015 5.655.092 7.753C4.727 21.453 6.101 22 7.475 22c1.374 0 2.749-.547 3.848-1.55l1.558-1.551a.88.88 0 0 0 0-1.278c-.367-.364-1.008-.456-1.375-.09z" fill="#FF814F" fill-rule="nonzero" />
  19. </svg>
  20. // Set default options and locale
  21. const defaultLocale = {
  22. strings: {
  23. import: 'Import',
  24. enterUrlToImport: 'Enter URL to import a file',
  25. failedToFetch: 'Companion failed to fetch this URL, please make sure it’s correct',
  26. enterCorrectUrl: 'Incorrect URL: Please make sure you are entering a direct link to a file'
  27. }
  28. }
  29. const defaultOptions = {
  30. locale: defaultLocale
  31. }
  32. this.opts = Object.assign({}, defaultOptions, opts)
  33. this.locale = Object.assign({}, defaultLocale, this.opts.locale)
  34. this.locale.strings = Object.assign({}, defaultLocale.strings, this.opts.locale.strings)
  35. this.translator = new Translator({locale: this.locale})
  36. this.i18n = this.translator.translate.bind(this.translator)
  37. this.hostname = this.opts.serverUrl
  38. if (!this.hostname) {
  39. throw new Error('Companion hostname is required, please consult https://uppy.io/docs/companion')
  40. }
  41. // Bind all event handlers for referencability
  42. this.getMeta = this.getMeta.bind(this)
  43. this.addFile = this.addFile.bind(this)
  44. this.handleDrop = this.handleDrop.bind(this)
  45. this.handleDragOver = this.handleDragOver.bind(this)
  46. this.handleDragLeave = this.handleDragLeave.bind(this)
  47. this.handlePaste = this.handlePaste.bind(this)
  48. this.client = new RequestClient(uppy, {
  49. serverUrl: this.opts.serverUrl,
  50. serverHeaders: this.opts.serverHeaders
  51. })
  52. }
  53. getFileNameFromUrl (url) {
  54. return url.substring(url.lastIndexOf('/') + 1)
  55. }
  56. checkIfCorrectURL (url) {
  57. if (!url) return false
  58. const protocol = url.match(/^([a-z0-9]+):\/\//)[1]
  59. if (protocol !== 'http' && protocol !== 'https') {
  60. return false
  61. }
  62. return true
  63. }
  64. addProtocolToURL (url) {
  65. const protocolRegex = /^[a-z0-9]+:\/\//
  66. const defaultProtocol = 'http://'
  67. if (protocolRegex.test(url)) {
  68. return url
  69. }
  70. return defaultProtocol + url
  71. }
  72. getMeta (url) {
  73. return this.client.post('url/meta', { url })
  74. .then((res) => {
  75. if (res.error) {
  76. this.uppy.log('[URL] Error:')
  77. this.uppy.log(res.error)
  78. throw new Error('Failed to fetch the file')
  79. }
  80. return res
  81. })
  82. }
  83. addFile (url) {
  84. url = this.addProtocolToURL(url)
  85. if (!this.checkIfCorrectURL(url)) {
  86. this.uppy.log(`[URL] Incorrect URL entered: ${url}`)
  87. this.uppy.info(this.i18n('enterCorrectUrl'), 'error', 4000)
  88. return
  89. }
  90. return this.getMeta(url)
  91. .then((meta) => {
  92. const tagFile = {
  93. source: this.id,
  94. name: this.getFileNameFromUrl(url),
  95. type: meta.type,
  96. data: {
  97. size: meta.size
  98. },
  99. isRemote: true,
  100. body: {
  101. url: url
  102. },
  103. remote: {
  104. serverUrl: this.opts.serverUrl,
  105. url: `${this.hostname}/url/get`,
  106. body: {
  107. fileId: url,
  108. url: url
  109. },
  110. providerOptions: this.client.opts
  111. }
  112. }
  113. return tagFile
  114. })
  115. .then((tagFile) => {
  116. this.uppy.log('[Url] Adding remote file')
  117. try {
  118. this.uppy.addFile(tagFile)
  119. } catch (err) {
  120. // Nothing, restriction errors handled in Core
  121. }
  122. })
  123. .then(() => {
  124. // Close the Dashboard panel if plugin is installed
  125. // into Dashboard (could be other parent UI plugin)
  126. // const parent = this.uppy.getPlugin(this.parent)
  127. // if (parent && parent.hideAllPanels) {
  128. // parent.hideAllPanels()
  129. // }
  130. })
  131. .catch((err) => {
  132. this.uppy.log(err)
  133. this.uppy.info({
  134. message: this.i18n('failedToFetch'),
  135. details: err
  136. }, 'error', 4000)
  137. })
  138. }
  139. handleDrop (e) {
  140. e.preventDefault()
  141. if (e.dataTransfer.items) {
  142. const items = toArray(e.dataTransfer.items)
  143. items.forEach((item) => {
  144. if (item.kind === 'string' && item.type === 'text/uri-list') {
  145. item.getAsString((url) => {
  146. this.uppy.log(`[URL] Adding file from dropped url: ${url}`)
  147. this.addFile(url)
  148. })
  149. }
  150. })
  151. }
  152. }
  153. handleDragOver (e) {
  154. e.preventDefault()
  155. this.el.classList.add('drag')
  156. }
  157. handleDragLeave (e) {
  158. e.preventDefault()
  159. this.el.classList.remove('drag')
  160. }
  161. handlePaste (e) {
  162. if (!e.clipboardData.items) {
  163. return
  164. }
  165. const items = toArray(e.clipboardData.items)
  166. // When a file is pasted, it appears as two items: file name string, then
  167. // the file itself; Url then treats file name string as URL, which is wrong.
  168. // This makes sure Url ignores paste event if it contains an actual file
  169. const hasFiles = items.filter(item => item.kind === 'file').length > 0
  170. if (hasFiles) return
  171. items.forEach((item) => {
  172. if (item.kind === 'string' && item.type === 'text/plain') {
  173. item.getAsString((url) => {
  174. this.uppy.log(`[URL] Adding file from pasted url: ${url}`)
  175. this.addFile(url)
  176. })
  177. }
  178. })
  179. }
  180. render (state) {
  181. return <UrlUI
  182. i18n={this.i18n}
  183. addFile={this.addFile} />
  184. }
  185. onMount () {
  186. if (this.el) {
  187. this.el.addEventListener('drop', this.handleDrop)
  188. this.el.addEventListener('dragover', this.handleDragOver)
  189. this.el.addEventListener('dragleave', this.handleDragLeave)
  190. this.el.addEventListener('paste', this.handlePaste)
  191. }
  192. }
  193. install () {
  194. const target = this.opts.target
  195. if (target) {
  196. this.mount(target, this)
  197. }
  198. }
  199. uninstall () {
  200. if (this.el) {
  201. this.el.removeEventListener('drop', this.handleDrop)
  202. this.el.removeEventListener('dragover', this.handleDragOver)
  203. this.el.removeEventListener('dragleave', this.handleDragLeave)
  204. this.el.removeEventListener('paste', this.handlePaste)
  205. }
  206. this.unmount()
  207. }
  208. }