You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

systemtagsfilelist.js 8.2KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348
  1. /**
  2. * Copyright (c) 2016 Vincent Petry <pvince81@owncloud.com>
  3. *
  4. * @author Joas Schilling <coding@schilljs.com>
  5. * @author John Molakvoæ <skjnldsv@protonmail.com>
  6. * @author Vincent Petry <vincent@nextcloud.com>
  7. *
  8. * @license GNU AGPL version 3 or any later version
  9. *
  10. * This program is free software: you can redistribute it and/or modify
  11. * it under the terms of the GNU Affero General Public License as
  12. * published by the Free Software Foundation, either version 3 of the
  13. * License, or (at your option) any later version.
  14. *
  15. * This program is distributed in the hope that it will be useful,
  16. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  17. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  18. * GNU Affero General Public License for more details.
  19. *
  20. * You should have received a copy of the GNU Affero General Public License
  21. * along with this program. If not, see <http://www.gnu.org/licenses/>.
  22. *
  23. */
  24. (function() {
  25. /**
  26. * @class OCA.SystemTags.FileList
  27. * @augments OCA.Files.FileList
  28. *
  29. * @classdesc SystemTags file list.
  30. * Contains a list of files filtered by system tags.
  31. *
  32. * @param {Object} $el container element with existing markup for the #controls and a table
  33. * @param {Array} [options] map of options, see other parameters
  34. * @param {Array.<string>} [options.systemTagIds] array of system tag ids to
  35. * filter by
  36. */
  37. const FileList = function($el, options) {
  38. this.initialize($el, options)
  39. }
  40. FileList.prototype = _.extend(
  41. {},
  42. OCA.Files.FileList.prototype,
  43. /** @lends OCA.SystemTags.FileList.prototype */ {
  44. id: 'systemtagsfilter',
  45. appName: t('systemtags', 'Tagged files'),
  46. /**
  47. * Array of system tag ids to filter by
  48. *
  49. * @type Array.<string>
  50. */
  51. _systemTagIds: [],
  52. _lastUsedTags: [],
  53. _clientSideSort: true,
  54. _allowSelection: false,
  55. _filterField: null,
  56. /**
  57. * @private
  58. * @param {Object} $el container element
  59. * @param {Object} [options] map of options, see other parameters
  60. */
  61. initialize($el, options) {
  62. OCA.Files.FileList.prototype.initialize.apply(this, arguments)
  63. if (this.initialized) {
  64. return
  65. }
  66. if (options && options.systemTagIds) {
  67. this._systemTagIds = options.systemTagIds
  68. }
  69. OC.Plugins.attach('OCA.SystemTags.FileList', this)
  70. const $controls = this.$el.find('#controls').empty()
  71. _.defer(_.bind(this._getLastUsedTags, this))
  72. this._initFilterField($controls)
  73. },
  74. destroy() {
  75. this.$filterField.remove()
  76. OCA.Files.FileList.prototype.destroy.apply(this, arguments)
  77. },
  78. _getLastUsedTags() {
  79. const self = this
  80. $.ajax({
  81. type: 'GET',
  82. url: OC.generateUrl('/apps/systemtags/lastused'),
  83. success(response) {
  84. self._lastUsedTags = response
  85. },
  86. })
  87. },
  88. _initFilterField($container) {
  89. const self = this
  90. this.$filterField = $('<input type="hidden" name="tags"/>')
  91. $container.append(this.$filterField)
  92. this.$filterField.select2({
  93. placeholder: t('systemtags', 'Select tags to filter by'),
  94. allowClear: false,
  95. multiple: true,
  96. toggleSelect: true,
  97. separator: ',',
  98. query: _.bind(this._queryTagsAutocomplete, this),
  99. id(tag) {
  100. return tag.id
  101. },
  102. initSelection(element, callback) {
  103. const val = $(element)
  104. .val()
  105. .trim()
  106. if (val) {
  107. const tagIds = val.split(',')
  108. const tags = []
  109. OC.SystemTags.collection.fetch({
  110. success() {
  111. _.each(tagIds, function(tagId) {
  112. const tag = OC.SystemTags.collection.get(
  113. tagId
  114. )
  115. if (!_.isUndefined(tag)) {
  116. tags.push(tag.toJSON())
  117. }
  118. })
  119. callback(tags)
  120. },
  121. })
  122. } else {
  123. // eslint-disable-next-line node/no-callback-literal
  124. callback([])
  125. }
  126. },
  127. formatResult(tag) {
  128. return OC.SystemTags.getDescriptiveTag(tag)
  129. },
  130. formatSelection(tag) {
  131. return OC.SystemTags.getDescriptiveTag(tag)[0]
  132. .outerHTML
  133. },
  134. sortResults(results) {
  135. results.sort(function(a, b) {
  136. const aLastUsed = self._lastUsedTags.indexOf(a.id)
  137. const bLastUsed = self._lastUsedTags.indexOf(b.id)
  138. if (aLastUsed !== bLastUsed) {
  139. if (bLastUsed === -1) {
  140. return -1
  141. }
  142. if (aLastUsed === -1) {
  143. return 1
  144. }
  145. return aLastUsed < bLastUsed ? -1 : 1
  146. }
  147. // Both not found
  148. return OC.Util.naturalSortCompare(a.name, b.name)
  149. })
  150. return results
  151. },
  152. escapeMarkup(m) {
  153. // prevent double markup escape
  154. return m
  155. },
  156. formatNoMatches() {
  157. return t('systemtags', 'No tags found')
  158. },
  159. })
  160. this.$filterField.on(
  161. 'change',
  162. _.bind(this._onTagsChanged, this)
  163. )
  164. return this.$filterField
  165. },
  166. /**
  167. * Autocomplete function for dropdown results
  168. *
  169. * @param {Object} query select2 query object
  170. */
  171. _queryTagsAutocomplete(query) {
  172. OC.SystemTags.collection.fetch({
  173. success() {
  174. const results = OC.SystemTags.collection.filterByName(
  175. query.term
  176. )
  177. query.callback({
  178. results: _.invoke(results, 'toJSON'),
  179. })
  180. },
  181. })
  182. },
  183. /**
  184. * Event handler for when the URL changed
  185. *
  186. * @param {Event} e the urlchanged event
  187. */
  188. _onUrlChanged(e) {
  189. if (e.dir) {
  190. const tags = _.filter(e.dir.split('/'), function(val) {
  191. return val.trim() !== ''
  192. })
  193. this.$filterField.select2('val', tags || [])
  194. this._systemTagIds = tags
  195. this.reload()
  196. }
  197. },
  198. _onTagsChanged(ev) {
  199. const val = $(ev.target)
  200. .val()
  201. .trim()
  202. if (val !== '') {
  203. this._systemTagIds = val.split(',')
  204. } else {
  205. this._systemTagIds = []
  206. }
  207. this.$el.trigger(
  208. $.Event('changeDirectory', {
  209. dir: this._systemTagIds.join('/'),
  210. })
  211. )
  212. this.reload()
  213. },
  214. updateEmptyContent() {
  215. const dir = this.getCurrentDirectory()
  216. if (dir === '/') {
  217. // root has special permissions
  218. if (!this._systemTagIds.length) {
  219. // no tags selected
  220. this.$el
  221. .find('#emptycontent')
  222. .html(
  223. '<div class="icon-systemtags"></div>'
  224. + '<h2>'
  225. + t(
  226. 'systemtags',
  227. 'Please select tags to filter by'
  228. )
  229. + '</h2>'
  230. )
  231. } else {
  232. // tags selected but no results
  233. this.$el
  234. .find('#emptycontent')
  235. .html(
  236. '<div class="icon-systemtags"></div>'
  237. + '<h2>'
  238. + t(
  239. 'systemtags',
  240. 'No files found for the selected tags'
  241. )
  242. + '</h2>'
  243. )
  244. }
  245. this.$el
  246. .find('#emptycontent')
  247. .toggleClass('hidden', !this.isEmpty)
  248. this.$el
  249. .find('#filestable thead th')
  250. .toggleClass('hidden', this.isEmpty)
  251. } else {
  252. OCA.Files.FileList.prototype.updateEmptyContent.apply(
  253. this,
  254. arguments
  255. )
  256. }
  257. },
  258. getDirectoryPermissions() {
  259. return OC.PERMISSION_READ | OC.PERMISSION_DELETE
  260. },
  261. updateStorageStatistics() {
  262. // no op because it doesn't have
  263. // storage info like free space / used space
  264. },
  265. reload() {
  266. // there is only root
  267. this._setCurrentDir('/', false)
  268. if (!this._systemTagIds.length) {
  269. // don't reload
  270. this.updateEmptyContent()
  271. this.setFiles([])
  272. return $.Deferred().resolve()
  273. }
  274. this._selectedFiles = {}
  275. this._selectionSummary.clear()
  276. if (this._currentFileModel) {
  277. this._currentFileModel.off()
  278. }
  279. this._currentFileModel = null
  280. this.$el.find('.select-all').prop('checked', false)
  281. this.showMask()
  282. this._reloadCall = this.filesClient.getFilteredFiles(
  283. {
  284. systemTagIds: this._systemTagIds,
  285. },
  286. {
  287. properties: this._getWebdavProperties(),
  288. }
  289. )
  290. if (this._detailsView) {
  291. // close sidebar
  292. this._updateDetailsView(null)
  293. }
  294. const callBack = this.reloadCallback.bind(this)
  295. return this._reloadCall.then(callBack, callBack)
  296. },
  297. reloadCallback(status, result) {
  298. if (result) {
  299. // prepend empty dir info because original handler
  300. result.unshift({})
  301. }
  302. return OCA.Files.FileList.prototype.reloadCallback.call(
  303. this,
  304. status,
  305. result
  306. )
  307. },
  308. }
  309. )
  310. OCA.SystemTags.FileList = FileList
  311. })()