Skip to content

Commit 2e6e154

Browse files
authored
Merge pull request #36534 from nextcloud/feat/files2vue-trashbin
[F2V] migrate files_trashbin to vue
2 parents 8eb9505 + 5b3900e commit 2e6e154

File tree

164 files changed

+8405
-10673
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

164 files changed

+8405
-10673
lines changed

apps/comments/src/services/DavClient.js

Lines changed: 10 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -20,19 +20,18 @@
2020
*
2121
*/
2222

23-
import { createClient, getPatcher } from 'webdav'
24-
import axios from '@nextcloud/axios'
25-
23+
import { createClient } from 'webdav'
2624
import { getRootPath } from '../utils/davUtils.js'
27-
28-
// Add this so the server knows it is an request from the browser
29-
axios.defaults.headers['X-Requested-With'] = 'XMLHttpRequest'
30-
31-
// force our axios
32-
const patcher = getPatcher()
33-
patcher.patch('request', axios)
25+
import { getRequestToken } from '@nextcloud/auth'
3426

3527
// init webdav client
36-
const client = createClient(getRootPath())
28+
const client = createClient(getRootPath(), {
29+
headers: {
30+
// Add this so the server knows it is an request from the browser
31+
'X-Requested-With': 'XMLHttpRequest',
32+
// Inject user auth
33+
requesttoken: getRequestToken() ?? '',
34+
},
35+
})
3736

3837
export default client
Lines changed: 23 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -20,26 +20,29 @@
2020
*
2121
*/
2222

23-
import { parseXML, prepareFileFromProps } from 'webdav/dist/node/tools/dav.js'
24-
import { processResponsePayload } from 'webdav/dist/node/response.js'
25-
import { decodeHtmlEntities } from '../utils/decodeHtmlEntities.js'
23+
import { parseXML, type DAVResult, type FileStat } from 'webdav'
24+
25+
// https://github.com/perry-mitchell/webdav-client/issues/339
26+
import { processResponsePayload } from '../../../../node_modules/webdav/dist/node/response.js'
27+
import { prepareFileFromProps } from '../../../../node_modules/webdav/dist/node/tools/dav.js'
2628
import client from './DavClient.js'
2729

2830
export const DEFAULT_LIMIT = 20
31+
2932
/**
3033
* Retrieve the comments list
3134
*
3235
* @param {object} data destructuring object
3336
* @param {string} data.commentsType the ressource type
3437
* @param {number} data.ressourceId the ressource ID
3538
* @param {object} [options] optional options for axios
39+
* @param {number} [options.offset] the pagination offset
3640
* @return {object[]} the comments list
3741
*/
38-
export default async function({ commentsType, ressourceId }, options = {}) {
39-
let response = null
42+
export const getComments = async function({ commentsType, ressourceId }, options: { offset: number }) {
4043
const ressourcePath = ['', commentsType, ressourceId].join('/')
4144

42-
return await client.customRequest(ressourcePath, Object.assign({
45+
const response = await client.customRequest(ressourcePath, Object.assign({
4346
method: 'REPORT',
4447
data: `<?xml version="1.0"?>
4548
<oc:filter-comments
@@ -51,42 +54,30 @@ export default async function({ commentsType, ressourceId }, options = {}) {
5154
<oc:offset>${options.offset || 0}</oc:offset>
5255
</oc:filter-comments>`,
5356
}, options))
54-
// See example on how it's done normally
55-
// https://github.com/perry-mitchell/webdav-client/blob/9de2da4a2599e06bd86c2778145b7ade39fe0b3c/source/interface/stat.js#L19
56-
// Waiting for proper REPORT integration https://github.com/perry-mitchell/webdav-client/issues/207
57-
.then(res => {
58-
response = res
59-
return res.data
60-
})
61-
.then(parseXML)
62-
.then(xml => processMultistatus(xml, true))
63-
.then(comments => processResponsePayload(response, comments, true))
64-
.then(response => response.data)
57+
58+
const responseData = await response.text()
59+
const result = await parseXML(responseData)
60+
const stat = getDirectoryFiles(result, true)
61+
return processResponsePayload(response, stat, true)
6562
}
6663

67-
// https://github.com/perry-mitchell/webdav-client/blob/9de2da4a2599e06bd86c2778145b7ade39fe0b3c/source/interface/directoryContents.js#L32
68-
/**
69-
* @param {any} result -
70-
* @param {any} isDetailed -
71-
*/
72-
function processMultistatus(result, isDetailed = false) {
64+
// https://github.com/perry-mitchell/webdav-client/blob/8d9694613c978ce7404e26a401c39a41f125f87f/source/operations/directoryContents.ts
65+
const getDirectoryFiles = function(
66+
result: DAVResult,
67+
isDetailed = false,
68+
): Array<FileStat> {
7369
// Extract the response items (directory contents)
7470
const {
7571
multistatus: { response: responseItems },
7672
} = result
73+
74+
// Map all items to a consistent output structure (results)
7775
return responseItems.map(item => {
7876
// Each item should contain a stat object
7977
const {
8078
propstat: { prop: props },
8179
} = item
82-
// Decode HTML entities
83-
const decodedProps = {
84-
...props,
85-
// Decode twice to handle potentially double-encoded entities
86-
// FIXME Remove this once https://github.com/nextcloud/server/issues/29306 is resolved
87-
actorDisplayName: decodeHtmlEntities(props.actorDisplayName, 2),
88-
message: decodeHtmlEntities(props.message, 2),
89-
}
90-
return prepareFileFromProps(decodedProps, decodedProps.id.toString(), isDetailed)
80+
81+
return prepareFileFromProps(props, props.id.toString(), isDetailed)
9182
})
9283
}

apps/comments/src/utils/cancelableRequest.js

Lines changed: 6 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -20,26 +20,15 @@
2020
*
2121
*/
2222

23-
import axios from '@nextcloud/axios'
24-
25-
/**
26-
* Create a cancel token
27-
*
28-
* @return {import('axios').CancelTokenSource}
29-
*/
30-
const createCancelToken = () => axios.CancelToken.source()
31-
3223
/**
3324
* Creates a cancelable axios 'request object'.
3425
*
3526
* @param {Function} request the axios promise request
3627
* @return {object}
3728
*/
3829
const cancelableRequest = function(request) {
39-
/**
40-
* Generate an axios cancel token
41-
*/
42-
const cancelToken = createCancelToken()
30+
const controller = new AbortController()
31+
const signal = controller.signal
4332

4433
/**
4534
* Execute the request
@@ -48,15 +37,16 @@ const cancelableRequest = function(request) {
4837
* @param {object} [options] optional config for the request
4938
*/
5039
const fetch = async function(url, options) {
51-
return request(
40+
const response = await request(
5241
url,
53-
Object.assign({ cancelToken: cancelToken.token }, options)
42+
Object.assign({ signal }, options)
5443
)
44+
return response
5545
}
5646

5747
return {
5848
request: fetch,
59-
cancel: cancelToken.cancel,
49+
abort: () => controller.abort(),
6050
}
6151
}
6252

apps/comments/src/views/Comments.vue

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -94,7 +94,7 @@ import MessageReplyTextIcon from 'vue-material-design-icons/MessageReplyText.vue
9494
import AlertCircleOutlineIcon from 'vue-material-design-icons/AlertCircleOutline.vue'
9595
9696
import Comment from '../components/Comment.vue'
97-
import getComments, { DEFAULT_LIMIT } from '../services/GetComments.js'
97+
import { getComments, DEFAULT_LIMIT } from '../services/GetComments.ts'
9898
import cancelableRequest from '../utils/cancelableRequest.js'
9999
100100
Vue.use(VTooltip)
@@ -206,14 +206,14 @@ export default {
206206
this.error = ''
207207
208208
// Init cancellable request
209-
const { request, cancel } = cancelableRequest(getComments)
210-
this.cancelRequest = cancel
209+
const { request, abort } = cancelableRequest(getComments)
210+
this.cancelRequest = abort
211211
212212
// Fetch comments
213-
const comments = await request({
213+
const { data: comments } = await request({
214214
commentsType: this.commentsType,
215215
ressourceId: this.ressourceId,
216-
}, { offset: this.offset })
216+
}, { offset: this.offset }) || { data: [] }
217217
218218
this.logger.debug(`Processed ${comments.length} comments`, { comments })
219219

apps/dav/src/service/CalendarService.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@
2020
*/
2121
import { getClient } from '../dav/client.js'
2222
import logger from './logger.js'
23-
import { parseXML } from 'webdav/dist/node/tools/dav.js'
23+
import { parseXML } from 'webdav'
2424

2525
import {
2626
slotsToVavailability,

apps/files/appinfo/routes.php

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -133,6 +133,11 @@
133133
'url' => '/directEditing/{token}',
134134
'verb' => 'GET'
135135
],
136+
[
137+
'name' => 'api#serviceWorker',
138+
'url' => '/preview-service-worker.js',
139+
'verb' => 'GET'
140+
],
136141
[
137142
'name' => 'view#index',
138143
'url' => '/{view}',

apps/files/js/app.js

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,6 @@
5151
* Initializes the files app
5252
*/
5353
initialize: function() {
54-
this.navigation = OCP.Files.Navigation;
5554
this.$showHiddenFiles = $('input#showhiddenfilesToggle');
5655
var showHidden = $('#showHiddenFiles').val() === "1";
5756
this.$showHiddenFiles.prop('checked', showHidden);
@@ -117,7 +116,9 @@
117116
},
118117
],
119118
sorting: {
120-
mode: $('#defaultFileSorting').val(),
119+
mode: $('#defaultFileSorting').val() === 'basename'
120+
? 'name'
121+
: $('#defaultFileSorting').val(),
121122
direction: $('#defaultFileSortingDirection').val()
122123
},
123124
config: this._filesConfig,
@@ -135,8 +136,6 @@
135136
OC.Plugins.attach('OCA.Files.App', this);
136137

137138
this._setupEvents();
138-
// trigger URL change event handlers
139-
this._onPopState({ ...OC.Util.History.parseUrlQuery(), view: this.navigation?.active?.id });
140139

141140
this._debouncedPersistShowHiddenFilesState = _.debounce(this._persistShowHiddenFilesState, 1200);
142141
this._debouncedPersistCropImagePreviewsState = _.debounce(this._persistCropImagePreviewsState, 1200);
@@ -145,6 +144,10 @@
145144
OCP.WhatsNew.query(); // for Nextcloud server
146145
sessionStorage.setItem('WhatsNewServerCheck', Date.now());
147146
}
147+
148+
window._nc_event_bus.emit('files:legacy-view:initialized', this);
149+
150+
this.navigation = OCP.Files.Navigation
148151
},
149152

150153
/**
@@ -225,7 +228,8 @@
225228
* @return view id
226229
*/
227230
getActiveView: function() {
228-
return this.navigation.active
231+
return this.navigation
232+
&& this.navigation.active
229233
&& this.navigation.active.id;
230234
},
231235

@@ -314,7 +318,7 @@
314318
view: 'files'
315319
}, params);
316320

317-
var lastId = this.navigation.active;
321+
var lastId = this.getActiveView();
318322
if (!this.navigation.views.find(view => view.id === params.view)) {
319323
params.view = 'files';
320324
}

apps/files/js/filelist.js

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2181,8 +2181,10 @@
21812181

21822182
if (persist && OC.getCurrentUser().uid) {
21832183
$.post(OC.generateUrl('/apps/files/api/v1/sorting'), {
2184-
mode: sort,
2185-
direction: direction
2184+
// Compatibility with new files-to-vue API
2185+
mode: sort === 'name' ? 'basename' : sort,
2186+
direction: direction,
2187+
view: 'files'
21862188
});
21872189
}
21882190
},

apps/files/lib/Controller/ApiController.php

Lines changed: 39 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -42,10 +42,12 @@
4242
use OCA\Files\Service\UserConfig;
4343
use OCP\AppFramework\Controller;
4444
use OCP\AppFramework\Http;
45+
use OCP\AppFramework\Http\ContentSecurityPolicy;
4546
use OCP\AppFramework\Http\DataResponse;
4647
use OCP\AppFramework\Http\FileDisplayResponse;
4748
use OCP\AppFramework\Http\JSONResponse;
4849
use OCP\AppFramework\Http\Response;
50+
use OCP\AppFramework\Http\StreamResponse;
4951
use OCP\Files\File;
5052
use OCP\Files\Folder;
5153
use OCP\Files\NotFoundException;
@@ -279,20 +281,29 @@ public function getStorageStats($dir = '/'): JSONResponse {
279281
*
280282
* @param string $mode
281283
* @param string $direction
282-
* @return Response
284+
* @return JSONResponse
283285
* @throws \OCP\PreConditionNotMetException
284286
*/
285-
public function updateFileSorting($mode, $direction) {
286-
$allowedMode = ['name', 'size', 'mtime'];
287+
public function updateFileSorting($mode, string $direction = 'asc', string $view = 'files'): JSONResponse {
287288
$allowedDirection = ['asc', 'desc'];
288-
if (!in_array($mode, $allowedMode) || !in_array($direction, $allowedDirection)) {
289-
$response = new Response();
290-
$response->setStatus(Http::STATUS_UNPROCESSABLE_ENTITY);
291-
return $response;
289+
if (!in_array($direction, $allowedDirection)) {
290+
return new JSONResponse(['message' => 'Invalid direction parameter'], Http::STATUS_UNPROCESSABLE_ENTITY);
292291
}
293-
$this->config->setUserValue($this->userSession->getUser()->getUID(), 'files', 'file_sorting', $mode);
294-
$this->config->setUserValue($this->userSession->getUser()->getUID(), 'files', 'file_sorting_direction', $direction);
295-
return new Response();
292+
293+
$userId = $this->userSession->getUser()->getUID();
294+
295+
$sortingJson = $this->config->getUserValue($userId, 'files', 'files_sorting_configs', '{}');
296+
$sortingConfig = json_decode($sortingJson, true) ?: [];
297+
$sortingConfig[$view] = [
298+
'mode' => $mode,
299+
'direction' => $direction,
300+
];
301+
302+
$this->config->setUserValue($userId, 'files', 'files_sorting_configs', json_encode($sortingConfig));
303+
return new JSONResponse([
304+
'message' => 'ok',
305+
'data' => $sortingConfig,
306+
]);
296307
}
297308

298309
/**
@@ -417,4 +428,22 @@ public function getNodeType($folderpath) {
417428
$node = $this->userFolder->get($folderpath);
418429
return $node->getType();
419430
}
431+
432+
/**
433+
* @NoAdminRequired
434+
* @NoCSRFRequired
435+
*/
436+
public function serviceWorker(): StreamResponse {
437+
$response = new StreamResponse(__DIR__ . '/../../../../dist/preview-service-worker.js');
438+
$response->setHeaders([
439+
'Content-Type' => 'application/javascript',
440+
'Service-Worker-Allowed' => '/'
441+
]);
442+
$policy = new ContentSecurityPolicy();
443+
$policy->addAllowedWorkerSrcDomain("'self'");
444+
$policy->addAllowedScriptDomain("'self'");
445+
$policy->addAllowedConnectDomain("'self'");
446+
$response->setContentSecurityPolicy($policy);
447+
return $response;
448+
}
420449
}

apps/files/lib/Controller/ViewController.php

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -249,6 +249,10 @@ public function index($dir = '', $view = '', $fileid = null, $fileNotFound = fal
249249
$this->initialState->provideInitialState('navigation', $navItems);
250250
$this->initialState->provideInitialState('config', $this->userConfig->getConfigs());
251251

252+
// File sorting user config
253+
$filesSortingConfig = json_decode($this->config->getUserValue($userId, 'files', 'files_sorting_configs', '{}'), true);
254+
$this->initialState->provideInitialState('filesSortingConfig', $filesSortingConfig);
255+
252256
// render the container content for every navigation item
253257
foreach ($navItems as $item) {
254258
$content = '';
@@ -292,8 +296,8 @@ public function index($dir = '', $view = '', $fileid = null, $fileNotFound = fal
292296
$params['ownerDisplayName'] = $storageInfo['ownerDisplayName'] ?? '';
293297
$params['isPublic'] = false;
294298
$params['allowShareWithLink'] = $this->shareManager->shareApiAllowLinks() ? 'yes' : 'no';
295-
$params['defaultFileSorting'] = $this->config->getUserValue($userId, 'files', 'file_sorting', 'name');
296-
$params['defaultFileSortingDirection'] = $this->config->getUserValue($userId, 'files', 'file_sorting_direction', 'asc');
299+
$params['defaultFileSorting'] = $filesSortingConfig['files']['mode'] ?? 'basename';
300+
$params['defaultFileSortingDirection'] = $filesSortingConfig['files']['direction'] ?? 'asc';
297301
$params['showgridview'] = $this->config->getUserValue($userId, 'files', 'show_grid', false);
298302
$showHidden = (bool) $this->config->getUserValue($userId, 'files', 'show_hidden', false);
299303
$params['showHiddenFiles'] = $showHidden ? 1 : 0;

0 commit comments

Comments
 (0)