mirror of https://github.com/nextcloud/server.git
Merge pull request #35772 from nextcloud/feat/files2vue-navigation
Port Files navigation to vuepull/35995/head
commit
e235c6438c
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@ -0,0 +1,144 @@
|
||||
<?php
|
||||
/**
|
||||
* @copyright Copyright (c) 2022 John Molakvoæ <skjnldsv@protonmail.com>
|
||||
*
|
||||
* @author John Molakvoæ <skjnldsv@protonmail.com>
|
||||
*
|
||||
* @license GNU AGPL version 3 or any later version
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
namespace OCA\Files\Service;
|
||||
|
||||
use OCA\Files\AppInfo\Application;
|
||||
use OCP\IConfig;
|
||||
use OCP\IUser;
|
||||
use OCP\IUserSession;
|
||||
|
||||
class UserConfig {
|
||||
const ALLOWED_CONFIGS = [
|
||||
[
|
||||
// Whether to crop the files previews or not in the files list
|
||||
'key' => 'crop_image_previews',
|
||||
'default' => true,
|
||||
'allowed' => [true, false],
|
||||
],
|
||||
[
|
||||
// Whether to show the hidden files or not in the files list
|
||||
'key' => 'show_hidden',
|
||||
'default' => false,
|
||||
'allowed' => [true, false],
|
||||
],
|
||||
];
|
||||
|
||||
protected IConfig $config;
|
||||
protected ?IUser $user = null;
|
||||
|
||||
public function __construct(IConfig $config, IUserSession $userSession) {
|
||||
$this->config = $config;
|
||||
$this->user = $userSession->getUser();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the list of all allowed user config keys
|
||||
* @return string[]
|
||||
*/
|
||||
public function getAllowedConfigKeys(): array {
|
||||
return array_map(function($config) {
|
||||
return $config['key'];
|
||||
}, self::ALLOWED_CONFIGS);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the list of allowed config values for a given key
|
||||
*
|
||||
* @param string $key a valid config key
|
||||
* @return array
|
||||
*/
|
||||
private function getAllowedConfigValues(string $key): array {
|
||||
foreach (self::ALLOWED_CONFIGS as $config) {
|
||||
if ($config['key'] === $key) {
|
||||
return $config['allowed'];
|
||||
}
|
||||
}
|
||||
return [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the default config value for a given key
|
||||
*
|
||||
* @param string $key a valid config key
|
||||
* @return string|bool
|
||||
*/
|
||||
private function getDefaultConfigValue(string $key) {
|
||||
foreach (self::ALLOWED_CONFIGS as $config) {
|
||||
if ($config['key'] === $key) {
|
||||
return $config['default'];
|
||||
}
|
||||
}
|
||||
return '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Set a user config
|
||||
*
|
||||
* @param string $key
|
||||
* @param string|bool $value
|
||||
* @throws \Exception
|
||||
* @throws \InvalidArgumentException
|
||||
*/
|
||||
public function setConfig(string $key, $value): void {
|
||||
if ($this->user === null) {
|
||||
throw new \Exception('No user logged in');
|
||||
}
|
||||
|
||||
if (!in_array($key, $this->getAllowedConfigKeys())) {
|
||||
throw new \InvalidArgumentException('Unknown config key');
|
||||
}
|
||||
|
||||
if (!in_array($value, $this->getAllowedConfigValues($key))) {
|
||||
throw new \InvalidArgumentException('Invalid config value');
|
||||
}
|
||||
|
||||
if (is_bool($value)) {
|
||||
$value = $value ? '1' : '0';
|
||||
}
|
||||
|
||||
$this->config->setUserValue($this->user->getUID(), Application::APP_ID, $key, $value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the current user configs array
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getConfigs(): array {
|
||||
if ($this->user === null) {
|
||||
throw new \Exception('No user logged in');
|
||||
}
|
||||
|
||||
$userId = $this->user->getUID();
|
||||
$userConfigs = array_map(function(string $key) use ($userId) {
|
||||
$value = $this->config->getUserValue($userId, Application::APP_ID, $key, $this->getDefaultConfigValue($key));
|
||||
// If the default is expected to be a boolean, we need to cast the value
|
||||
if (is_bool($this->getDefaultConfigValue($key))) {
|
||||
return $value === '1';
|
||||
}
|
||||
return $value;
|
||||
}, $this->getAllowedConfigKeys());
|
||||
|
||||
return array_combine($this->getAllowedConfigKeys(), $userConfigs);
|
||||
}
|
||||
}
|
@ -1,57 +0,0 @@
|
||||
/**
|
||||
* @copyright Copyright (c) 2019 Gary Kim <gary@garykim.dev>
|
||||
* @copyright Copyright (c) 2019 John Molakvoæ <skjnldsv@protonmail.com>
|
||||
*
|
||||
* @author Gary Kim <gary@garykim.dev>
|
||||
* @author John Molakvoæ <skjnldsv@protonmail.com>
|
||||
*
|
||||
* @license AGPL-3.0-or-later
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
import Vue from 'vue'
|
||||
import Settings from './services/Settings'
|
||||
import SettingsView from './views/Settings'
|
||||
import Setting from './models/Setting'
|
||||
|
||||
Vue.prototype.t = t
|
||||
|
||||
// Init Files App Settings Service
|
||||
if (!window.OCA.Files) {
|
||||
window.OCA.Files = {}
|
||||
}
|
||||
Object.assign(window.OCA.Files, { Settings: new Settings() })
|
||||
Object.assign(window.OCA.Files.Settings, { Setting })
|
||||
|
||||
window.addEventListener('DOMContentLoaded', function() {
|
||||
if (window.TESTING) {
|
||||
return
|
||||
}
|
||||
// Init Vue app
|
||||
// eslint-disable-next-line
|
||||
new Vue({
|
||||
el: '#files-app-settings',
|
||||
render: h => h(SettingsView),
|
||||
})
|
||||
|
||||
const appSettingsHeader = document.getElementById('app-settings-header')
|
||||
if (appSettingsHeader) {
|
||||
appSettingsHeader.addEventListener('click', e => {
|
||||
const opened = e.currentTarget.children[0].classList.contains('opened')
|
||||
OCA.Files.Settings.settings.forEach(e => opened ? e.close() : e.open())
|
||||
})
|
||||
}
|
||||
})
|
@ -0,0 +1,55 @@
|
||||
/**
|
||||
* @copyright Copyright (c) 2022 John Molakvoæ <skjnldsv@protonmail.com>
|
||||
*
|
||||
* @author John Molakvoæ <skjnldsv@protonmail.com>
|
||||
*
|
||||
* @license AGPL-3.0-or-later
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
import { loadState } from '@nextcloud/initial-state'
|
||||
import logger from '../logger.js'
|
||||
|
||||
/**
|
||||
* Fetch and register the legacy files views
|
||||
*/
|
||||
export default function() {
|
||||
const legacyViews = Object.values(loadState('files', 'navigation', {}))
|
||||
|
||||
if (legacyViews.length > 0) {
|
||||
logger.debug('Legacy files views detected. Processing...', legacyViews)
|
||||
legacyViews.forEach(view => {
|
||||
registerLegacyView(view)
|
||||
if (view.sublist) {
|
||||
view.sublist.forEach(subview => registerLegacyView({ ...subview, parent: view.id }))
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
const registerLegacyView = function({ id, name, order, icon, parent, classes = '', expanded, params }) {
|
||||
OCP.Files.Navigation.register({
|
||||
id,
|
||||
name,
|
||||
order,
|
||||
params,
|
||||
parent,
|
||||
expanded: expanded === true,
|
||||
iconClass: icon ? `icon-${icon}` : 'nav-icon-' + id,
|
||||
legacy: true,
|
||||
sticky: classes.includes('pinned'),
|
||||
})
|
||||
}
|
@ -1,3 +1,39 @@
|
||||
import './files-app-settings'
|
||||
import './templates'
|
||||
import './legacy/filelistSearch'
|
||||
import './templates.js'
|
||||
import './legacy/filelistSearch.js'
|
||||
import processLegacyFilesViews from './legacy/navigationMapper.js'
|
||||
|
||||
import Vue from 'vue'
|
||||
import NavigationService from './services/Navigation.ts'
|
||||
import NavigationView from './views/Navigation.vue'
|
||||
|
||||
import SettingsService from './services/Settings.js'
|
||||
import SettingsModel from './models/Setting.js'
|
||||
|
||||
import router from './router/router.js'
|
||||
|
||||
// Init private and public Files namespace
|
||||
window.OCA.Files = window.OCA.Files ?? {}
|
||||
window.OCP.Files = window.OCP.Files ?? {}
|
||||
|
||||
// Init Navigation Service
|
||||
const Navigation = new NavigationService()
|
||||
Object.assign(window.OCP.Files, { Navigation })
|
||||
|
||||
// Init Files App Settings Service
|
||||
const Settings = new SettingsService()
|
||||
Object.assign(window.OCA.Files, { Settings })
|
||||
Object.assign(window.OCA.Files.Settings, { Setting: SettingsModel })
|
||||
|
||||
// Init Navigation View
|
||||
const View = Vue.extend(NavigationView)
|
||||
const FilesNavigationRoot = new View({
|
||||
name: 'FilesNavigationRoot',
|
||||
propsData: {
|
||||
Navigation,
|
||||
},
|
||||
router,
|
||||
})
|
||||
FilesNavigationRoot.$mount('#app-navigation-files')
|
||||
|
||||
// Init legacy files views
|
||||
processLegacyFilesViews()
|
||||
|
@ -0,0 +1,57 @@
|
||||
/**
|
||||
* @copyright Copyright (c) 2022 John Molakvoæ <skjnldsv@protonmail.com>
|
||||
*
|
||||
* @author John Molakvoæ <skjnldsv@protonmail.com>
|
||||
*
|
||||
* @license AGPL-3.0-or-later
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
import Vue from 'vue'
|
||||
import Router from 'vue-router'
|
||||
import { generateUrl } from '@nextcloud/router'
|
||||
import { stringify } from 'query-string'
|
||||
|
||||
Vue.use(Router)
|
||||
|
||||
const router = new Router({
|
||||
mode: 'history',
|
||||
|
||||
// if index.php is in the url AND we got this far, then it's working:
|
||||
// let's keep using index.php in the url
|
||||
base: generateUrl('/apps/files', ''),
|
||||
linkActiveClass: 'active',
|
||||
|
||||
routes: [
|
||||
{
|
||||
path: '/',
|
||||
// Pretending we're using the default view
|
||||
alias: '/files',
|
||||
},
|
||||
{
|
||||
path: '/:view/:fileid?',
|
||||
name: 'filelist',
|
||||
props: true,
|
||||
},
|
||||
],
|
||||
|
||||
// Custom stringifyQuery to prevent encoding of slashes in the url
|
||||
stringifyQuery(query) {
|
||||
const result = stringify(query).replace(/%2F/gmi, '/')
|
||||
return result ? ('?' + result) : ''
|
||||
},
|
||||
})
|
||||
|
||||
export default router
|
@ -0,0 +1,217 @@
|
||||
/**
|
||||
* @copyright Copyright (c) 2022 John Molakvoæ <skjnldsv@protonmail.com>
|
||||
*
|
||||
* @author John Molakvoæ <skjnldsv@protonmail.com>
|
||||
*
|
||||
* @license AGPL-3.0-or-later
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
import type Node from '@nextcloud/files/dist/files/node'
|
||||
import isSvg from 'is-svg'
|
||||
|
||||
import logger from '../logger'
|
||||
|
||||
export interface Column {
|
||||
/** Unique column ID */
|
||||
id: string
|
||||
/** Translated column title */
|
||||
title: string
|
||||
/** Property key from Node main or additional attributes.
|
||||
Will be used if no custom sort function is provided.
|
||||
Sorting will be done by localCompare */
|
||||
property: string
|
||||
/** Special function used to sort Nodes between them */
|
||||
sortFunction?: (nodeA: Node, nodeB: Node) => number;
|
||||
/** Custom summary of the column to display at the end of the list.
|
||||
Will not be displayed if nothing is provided */
|
||||
summary?: (node: Node[]) => string
|
||||
}
|
||||
|
||||
export interface Navigation {
|
||||
/** Unique view ID */
|
||||
id: string
|
||||
/** Translated view name */
|
||||
name: string
|
||||
/** Method return the content of the provided path */
|
||||
getFiles: (path: string) => Node[]
|
||||
/** The view icon as an inline svg */
|
||||
icon: string
|
||||
/** The view order */
|
||||
order: number
|
||||
/** This view column(s). Name and actions are
|
||||
by default always included */
|
||||
columns?: Column[]
|
||||
/** The empty view element to render your empty content into */
|
||||
emptyView?: (div: HTMLDivElement) => void
|
||||
/** The parent unique ID */
|
||||
parent?: string
|
||||
/** This view is sticky (sent at the bottom) */
|
||||
sticky?: boolean
|
||||
/** This view has children and is expanded or not */
|
||||
expanded?: boolean
|
||||
|
||||
/**
|
||||
* This view is sticky a legacy view.
|
||||
* Here until all the views are migrated to Vue.
|
||||
* @deprecated It will be removed in a near future
|
||||
*/
|
||||
legacy?: boolean
|
||||
/**
|
||||
* An icon class.
|
||||
* @deprecated It will be removed in a near future
|
||||
*/
|
||||
iconClass?: string
|
||||
}
|
||||
|
||||
export default class {
|
||||
|
||||
private _views: Navigation[] = []
|
||||
private _currentView: Navigation | null = null
|
||||
|
||||
constructor() {
|
||||
logger.debug('Navigation service initialized')
|
||||
}
|
||||
|
||||
register(view: Navigation) {
|
||||
try {
|
||||
isValidNavigation(view)
|
||||
isUniqueNavigation(view, this._views)
|
||||
} catch (e) {
|
||||
if (e instanceof Error) {
|
||||
logger.error(e.message, { view })
|
||||
}
|
||||
throw e
|
||||
}
|
||||
|
||||
if (view.legacy) {
|
||||
logger.warn('Legacy view detected, please migrate to Vue')
|
||||
}
|
||||
|
||||
if (view.iconClass) {
|
||||
view.legacy = true
|
||||
}
|
||||
|
||||
this._views.push(view)
|
||||
}
|
||||
|
||||
get views(): Navigation[] {
|
||||
return this._views
|
||||
}
|
||||
|
||||
setActive(view: Navigation | null) {
|
||||
this._currentView = view
|
||||
}
|
||||
|
||||
get active(): Navigation | null {
|
||||
return this._currentView
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Make sure the given view is unique
|
||||
* and not already registered.
|
||||
*/
|
||||
const isUniqueNavigation = function(view: Navigation, views: Navigation[]): boolean {
|
||||
if (views.find(search => search.id === view.id)) {
|
||||
throw new Error(`Navigation id ${view.id} is already registered`)
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
/**
|
||||
* Typescript cannot validate an interface.
|
||||
* Please keep in sync with the Navigation interface requirements.
|
||||
*/
|
||||
const isValidNavigation = function(view: Navigation): boolean {
|
||||
if (!view.id || typeof view.id !== 'string') {
|
||||
throw new Error('Navigation id is required and must be a string')
|
||||
}
|
||||
|
||||
if (!view.name || typeof view.name !== 'string') {
|
||||
throw new Error('Navigation name is required and must be a string')
|
||||
}
|
||||
|
||||
/**
|
||||
* Legacy handle their content and icon differently
|
||||
* TODO: remove when support for legacy views is removed
|
||||
*/
|
||||
if (!view.legacy) {
|
||||
if (!view.getFiles || typeof view.getFiles !== 'function') {
|
||||
throw new Error('Navigation getFiles is required and must be a function')
|
||||
}
|
||||
|
||||
if (!view.icon || typeof view.icon !== 'string' || !isSvg(view.icon)) {
|
||||
throw new Error('Navigation icon is required and must be a valid svg string')
|
||||
}
|
||||
}
|
||||
|
||||
if (!('order' in view) || typeof view.order !== 'number') {
|
||||
throw new Error('Navigation order is required and must be a number')
|
||||
}
|
||||
|
||||
// Optional properties
|
||||
if (view.columns) {
|
||||
view.columns.forEach(isValidColumn)
|
||||
}
|
||||
|
||||
if (view.emptyView && typeof view.emptyView !== 'function') {
|
||||
throw new Error('Navigation emptyView must be a function')
|
||||
}
|
||||
|
||||
if (view.parent && typeof view.parent !== 'string') {
|
||||
throw new Error('Navigation parent must be a string')
|
||||
}
|
||||
|
||||
if ('sticky' in view && typeof view.sticky !== 'boolean') {
|
||||
throw new Error('Navigation sticky must be a boolean')
|
||||
}
|
||||
|
||||
if ('expanded' in view && typeof view.expanded !== 'boolean') {
|
||||
throw new Error('Navigation expanded must be a boolean')
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
/**
|
||||
* Typescript cannot validate an interface.
|
||||
* Please keep in sync with the Column interface requirements.
|
||||
*/
|
||||
const isValidColumn = function(column: Column): boolean {
|
||||
if (!column.id || typeof column.id !== 'string') {
|
||||
throw new Error('Column id is required')
|
||||
}
|
||||
|
||||
if (!column.title || typeof column.title !== 'string') {
|
||||
throw new Error('Column title is required')
|
||||
}
|
||||
|
||||
if (!column.property || typeof column.property !== 'string') {
|
||||
throw new Error('Column property is required')
|
||||
}
|
||||
|
||||
// Optional properties
|
||||
if (column.sortFunction && typeof column.sortFunction !== 'function') {
|
||||
throw new Error('Column sortFunction must be a function')
|
||||
}
|
||||
|
||||
if (column.summary && typeof column.summary !== 'function') {
|
||||
throw new Error('Column summary must be a function')
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
@ -0,0 +1,116 @@
|
||||
/* eslint-disable import/first */
|
||||
import FolderSvg from '@mdi/svg/svg/folder.svg'
|
||||
import ShareSvg from '@mdi/svg/svg/share-variant.svg'
|
||||
|
||||
import NavigationService from '../services/Navigation'
|
||||
import NavigationView from './Navigation.vue'
|
||||
import router from '../router/router.js'
|
||||
|
||||
const Navigation = new NavigationService()
|
||||
|
||||
describe('Navigation renders', () => {
|
||||
it('renders', () => {
|
||||
cy.mount(NavigationView, {
|
||||
propsData: {
|
||||
Navigation,
|
||||
},
|
||||
})
|
||||
|
||||
cy.get('[data-cy-files-navigation]').should('be.visible')
|
||||
cy.get('[data-cy-files-navigation-settings-button]').should('be.visible')
|
||||
})
|
||||
})
|
||||
|
||||
describe('Navigation API', () => {
|
||||
it('Check API entries rendering', () => {
|
||||
Navigation.register({
|
||||
id: 'files',
|
||||
name: 'Files',
|
||||
getFiles: () => [],
|
||||
icon: FolderSvg,
|
||||
order: 1,
|
||||
})
|
||||
|
||||
cy.mount(NavigationView, {
|
||||
propsData: {
|
||||
Navigation,
|
||||
},
|
||||
router,
|
||||
})
|
||||
|
||||
cy.get('[data-cy-files-navigation]').should('be.visible')
|
||||
cy.get('[data-cy-files-navigation-item]').should('have.length', 1)
|
||||
cy.get('[data-cy-files-navigation-item="files"]').should('be.visible')
|
||||
cy.get('[data-cy-files-navigation-item="files"]').should('contain.text', 'Files')
|
||||
})
|
||||
|
||||
it('Adds a new entry and render', () => {
|
||||
Navigation.register({
|
||||
id: 'sharing',
|
||||
name: 'Sharing',
|
||||
getFiles: () => [],
|
||||
icon: ShareSvg,
|
||||
order: 2,
|
||||
})
|
||||
|
||||
cy.mount(NavigationView, {
|
||||
propsData: {
|
||||
Navigation,
|
||||
},
|
||||
router,
|
||||
})
|
||||
|
||||
cy.get('[data-cy-files-navigation]').should('be.visible')
|
||||
cy.get('[data-cy-files-navigation-item]').should('have.length', 2)
|
||||
cy.get('[data-cy-files-navigation-item="sharing"]').should('be.visible')
|
||||
cy.get('[data-cy-files-navigation-item="sharing"]').should('contain.text', 'Sharing')
|
||||
})
|
||||
|
||||
it('Adds a new children, render and open menu', () => {
|
||||
Navigation.register({
|
||||
id: 'sharingin',
|
||||
name: 'Shared with me',
|
||||
getFiles: () => [],
|
||||
parent: 'sharing',
|
||||
icon: ShareSvg,
|
||||
order: 1,
|
||||
})
|
||||
|
||||
cy.mount(NavigationView, {
|
||||
propsData: {
|
||||
Navigation,
|
||||
},
|
||||
router,
|
||||
})
|
||||
|
||||
cy.get('[data-cy-files-navigation]').should('be.visible')
|
||||
cy.get('[data-cy-files-navigation-item]').should('have.length', 3)
|
||||
|
||||
// Intercept collapse preference request
|
||||
cy.intercept('POST', '*/apps/files/api/v1/toggleShowFolder/*', {
|
||||
statusCode: 200,
|
||||
}).as('toggleShowFolder')
|
||||
|
||||
// Toggle the sharing entry children
|
||||
cy.get('[data-cy-files-navigation-item="sharing"] button.icon-collapse').should('exist')
|
||||
cy.get('[data-cy-files-navigation-item="sharing"] button.icon-collapse').click({ force: true })
|
||||
cy.wait('@toggleShowFolder')
|
||||
|
||||
// Validate children
|
||||
cy.get('[data-cy-files-navigation-item="sharingin"]').should('be.visible')
|
||||
cy.get('[data-cy-files-navigation-item="sharingin"]').should('contain.text', 'Shared with me')
|
||||
|
||||
})
|
||||
|
||||
it('Throws when adding a duplicate entry', () => {
|
||||
expect(() => {
|
||||
Navigation.register({
|
||||
id: 'files',
|
||||
name: 'Files',
|
||||
getFiles: () => [],
|
||||
icon: FolderSvg,
|
||||
order: 1,
|
||||
})
|
||||
}).to.throw('Navigation id files is already registered')
|
||||
})
|
||||
})
|
@ -0,0 +1,264 @@
|
||||
<!--
|
||||
- @copyright Copyright (c) 2019 Gary Kim <gary@garykim.dev>
|
||||
-
|
||||
- @author Gary Kim <gary@garykim.dev>
|
||||
-
|
||||
- @license GNU AGPL version 3 or any later version
|
||||
-
|
||||
- This program is free software: you can redistribute it and/or modify
|
||||
- it under the terms of the GNU Affero General Public License as
|
||||
- published by the Free Software Foundation, either version 3 of the
|
||||
- License, or (at your option) any later version.
|
||||
-
|
||||
- This program is distributed in the hope that it will be useful,
|
||||
- but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
- GNU Affero General Public License for more details.
|
||||
-
|
||||
- You should have received a copy of the GNU Affero General Public License
|
||||
- along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
-
|
||||
-->
|
||||
<template>
|
||||
<NcAppNavigation data-cy-files-navigation>
|
||||
<template #list>
|
||||
<NcAppNavigationItem v-for="view in parentViews"
|
||||
:key="view.id"
|
||||
:allow-collapse="true"
|
||||
:data-cy-files-navigation-item="view.id"
|
||||
:icon="view.iconClass"
|
||||
:open="view.expanded"
|
||||
:pinned="view.sticky"
|
||||
:title="view.name"
|
||||
:to="generateToNavigation(view)"
|
||||
@update:open="onToggleExpand(view)">
|
||||
<NcAppNavigationItem v-for="child in childViews[view.id]"
|
||||
:key="child.id"
|
||||
:data-cy-files-navigation-item="child.id"
|
||||
:exact="true"
|
||||
:icon="child.iconClass"
|
||||
:title="child.name"
|
||||
:to="generateToNavigation(child)" />
|
||||
</NcAppNavigationItem>
|
||||
</template>
|
||||
|
||||
<!-- Settings toggle -->
|
||||
<template #footer>
|
||||
<ul class="app-navigation-entry__settings">
|
||||
<NcAppNavigationItem :aria-label="t('files', 'Open the files app settings')"
|
||||
:title="t('files', 'Files settings')"
|
||||
data-cy-files-navigation-settings-button
|
||||
@click.prevent.stop="openSettings">
|
||||
<Cog slot="icon" :size="20" />
|
||||
</NcAppNavigationItem>
|
||||
</ul>
|
||||
</template>
|
||||
|
||||
<!-- Settings modal-->
|
||||
<SettingsModal :open="settingsOpened"
|
||||
data-cy-files-navigation-settings
|
||||
@close="onSettingsClose" />
|
||||
</NcAppNavigation>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { emit, subscribe } from '@nextcloud/event-bus'
|
||||
import { generateUrl } from '@nextcloud/router'
|
||||
import axios from '@nextcloud/axios'
|
||||
import Cog from 'vue-material-design-icons/Cog.vue'
|
||||
import NcAppNavigation from '@nextcloud/vue/dist/Components/NcAppNavigation.js'
|
||||
import NcAppNavigationItem from '@nextcloud/vue/dist/Components/NcAppNavigationItem.js'
|
||||
|
||||
import logger from '../logger.js'
|
||||
import Navigation from '../services/Navigation.ts'
|
||||
import SettingsModal from './Settings.vue'
|
||||
|
||||
import { translate } from '@nextcloud/l10n'
|
||||
|
||||
export default {
|
||||
name: 'Navigation',
|
||||
|
||||
components: {
|
||||
Cog,
|
||||
NcAppNavigation,
|
||||
NcAppNavigationItem,
|
||||
SettingsModal,
|
||||
},
|
||||
|
||||
props: {
|
||||
// eslint-disable-next-line vue/prop-name-casing
|
||||
Navigation: {
|
||||
type: Navigation,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
|
||||
data() {
|
||||
return {
|
||||
settingsOpened: false,
|
||||
}
|
||||
},
|
||||
|
||||
computed: {
|
||||
currentViewId() {
|
||||
return this.$route?.params?.view || 'files'
|
||||
},
|
||||
currentView() {
|
||||
return this.views.find(view => view.id === this.currentViewId)
|
||||
},
|
||||
|
||||
/** @return {Navigation[]} */
|
||||
views() {
|
||||
return this.Navigation.views
|
||||
},
|
||||
parentViews() {
|
||||
return this.views
|
||||
// filter child views
|
||||
.filter(view => !view.parent)
|
||||
// sort views by order
|
||||
.sort((a, b) => {
|
||||
return a.order - b.order
|
||||
})
|
||||
},
|
||||
childViews() {
|
||||
return this.views
|
||||
// filter parent views
|
||||
.filter(view => !!view.parent)
|
||||
// create a map of parents and their children
|
||||
.reduce((list, view) => {
|
||||
list[view.parent] = [...(list[view.parent] || []), view]
|
||||
// Sort children by order
|
||||
list[view.parent].sort((a, b) => {
|
||||
return a.order - b.order
|
||||
})
|
||||
return list
|
||||
}, {})
|
||||
},
|
||||
},
|
||||
|
||||
watch: {
|
||||
currentView(view, oldView) {
|
||||
logger.debug('View changed', { id: view.id, view })
|
||||
this.showView(view, oldView)
|
||||
},
|
||||
},
|
||||
|
||||
beforeMount() {
|
||||
if (this.currentView) {
|
||||
logger.debug('Navigation mounted. Showing requested view', { view: this.currentView })
|
||||
this.showView(this.currentView)
|
||||
}
|
||||
|
||||
subscribe('files:legacy-navigation:changed', this.onLegacyNavigationChanged)
|
||||
},
|
||||
|
||||
methods: {
|
||||
/**
|
||||
* @param {Navigation} view the new active view
|
||||
* @param {Navigation} oldView the old active view
|
||||
*/
|
||||
showView(view, oldView) {
|
||||
// Closing any opened sidebar
|
||||
window?.OCA?.Files?.Sidebar?.close?.()
|
||||
|
||||
if (view.legacy) {
|
||||
const newAppContent = document.querySelector('#app-content #app-content-' + this.currentView.id + '.viewcontainer')
|
||||
document.querySelectorAll('#app-content .viewcontainer').forEach(el => {
|
||||
el.classList.add('hidden')
|
||||
})
|
||||
newAppContent.classList.remove('hidden')
|
||||
|
||||
// Triggering legacy navigation events
|
||||
const { dir = '/' } = OC.Util.History.parseUrlQuery()
|
||||
const params = { itemId: view.id, dir }
|
||||
|
||||
logger.debug('Triggering legacy navigation event', params)
|
||||
window.jQuery(newAppContent).trigger(new window.jQuery.Event('show', params))
|
||||
window.jQuery(newAppContent).trigger(new window.jQuery.Event('urlChanged', params))
|
||||
|
||||
}
|
||||
|
||||
this.Navigation.setActive(view)
|
||||
emit('files:navigation:changed', view)
|
||||
},
|
||||
|
||||
/**
|
||||
* Coming from the legacy files app.
|
||||
* TODO: remove when all views are migrated.
|
||||
*
|
||||
* @param {Navigation} view the new active view
|
||||
*/
|
||||
onLegacyNavigationChanged({ id } = { id: 'files' }) {
|
||||
const view = this.Navigation.views.find(view => view.id === id)
|
||||
if (view && view.legacy && view.id !== this.currentView.id) {
|
||||
// Force update the current route as the request comes
|
||||
// from the legacy files app router
|
||||
this.$router.replace({ ...this.$route, params: { view: view.id } })
|
||||
this.Navigation.setActive(view)
|
||||
this.showView(view)
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Expand/collapse a a view with children and permanently
|
||||
* save this setting in the server.
|
||||
*
|
||||
* @param {Navigation} view the view to toggle
|
||||
*/
|
||||
onToggleExpand(view) {
|
||||
// Invert state
|
||||
view.expanded = !view.expanded
|
||||
axios.post(generateUrl(`/apps/files/api/v1/toggleShowFolder/${view.id}`), { show: view.expanded })
|
||||
},
|
||||
|
||||
/**
|
||||
* Generate the route to a view
|
||||
* @param {Navigation} view the view to toggle
|
||||
*/
|
||||
generateToNavigation(view) {
|
||||
if (view.params) {
|
||||
const { dir, fileid } = view.params
|
||||
return { name: 'filelist', params: view.params, query: { dir, fileid } }
|
||||
}
|
||||
return { name: 'filelist', params: { view: view.id } }
|
||||
},
|
||||
|
||||
/**
|
||||
* Open the settings modal
|
||||
*/
|
||||
openSettings() {
|
||||
this.settingsOpened = true
|
||||
},
|
||||
|
||||
/**
|
||||
* Close the settings modal
|
||||
*/
|
||||
onSettingsClose() {
|
||||
this.settingsOpened = false
|
||||
},
|
||||
|
||||
t: translate,
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
// TODO: remove when https://github.com/nextcloud/nextcloud-vue/pull/3539 is in
|
||||
.app-navigation::v-deep .app-navigation-entry-icon {
|
||||
background-repeat: no-repeat;
|
||||
background-position: center;
|
||||
}
|
||||
|
||||
.app-navigation > ul.app-navigation__list {
|
||||
// Use flex gap value for more elegant spacing
|
||||
padding-bottom: var(--default-grid-baseline, 4px);
|
||||
}
|
||||
|
||||
.app-navigation-entry__settings {
|
||||
height: auto !important;
|
||||
overflow: hidden !important;
|
||||
padding-top: 0 !important;
|
||||
// Prevent shrinking or growing
|
||||
flex: 0 0 auto;
|
||||
}
|
||||
</style>
|
@ -1,244 +0,0 @@
|
||||
/**
|
||||
* @copyright 2014 Vincent Petry <pvince81@owncloud.com>
|
||||
*
|
||||
* @author John Molakvoæ <skjnldsv@protonmail.com>
|
||||
* @author Vincent Petry <vincent@nextcloud.com>
|
||||
*
|
||||
* @license AGPL-3.0-or-later
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
describe('OCA.Files.App tests', function() {
|
||||
var App = OCA.Files.App;
|
||||
var pushStateStub;
|
||||
var replaceStateStub;
|
||||
var parseUrlQueryStub;
|
||||
|
||||
beforeEach(function() {
|
||||
$('#testArea').append(
|
||||
'<div id="content" class="app-files">' +
|
||||
'<div id="app-navigation">' +
|
||||
'<ul><li data-id="files"><a>Files</a></li>' +
|
||||
'<li data-id="other"><a>Other</a></li>' +
|
||||
'</div>' +
|
||||
'<div id="app-content">' +
|
||||
'<div id="app-content-files" class="hidden">' +
|
||||
'</div>' +
|
||||
'<div id="app-content-other" class="hidden">' +
|
||||
'</div>' +
|
||||
'</div>' +
|
||||
'</div>' +
|
||||
'</div>'
|
||||
);
|
||||
|
||||
OCA.Files.fileActions = new OCA.Files.FileActions();
|
||||
|
||||
pushStateStub = sinon.stub(OC.Util.History, 'pushState');
|
||||
replaceStateStub = sinon.stub(OC.Util.History, 'replaceState');
|
||||
parseUrlQueryStub = sinon.stub(OC.Util.History, 'parseUrlQuery');
|
||||
parseUrlQueryStub.returns({});
|
||||
|
||||
App.initialize();
|
||||
});
|
||||
afterEach(function() {
|
||||
App.destroy();
|
||||
|
||||
pushStateStub.restore();
|
||||
replaceStateStub.restore();
|
||||
parseUrlQueryStub.restore();
|
||||
});
|
||||
|
||||
describe('initialization', function() {
|
||||
it('initializes the default file list with the default file actions', function() {
|
||||
expect(App.fileList).toBeDefined();
|
||||
expect(App.fileList.fileActions.actions.all).toBeDefined();
|
||||
expect(App.fileList.$el.is('#app-content-files')).toEqual(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('URL handling', function() {
|
||||
it('pushes the state to the URL when current app changed directory', function() {
|
||||
$('#app-content-files').trigger(new $.Event('changeDirectory', {dir: 'sub dir'}));
|
||||
expect(pushStateStub.calledOnce).toEqual(true);
|
||||
var params = OC.parseQueryString(pushStateStub.getCall(0).args[0]);
|
||||
expect(params.dir).toEqual('sub dir');
|
||||
expect(params.view).not.toBeDefined();
|
||||
|
||||
$('li[data-id=other]>a').click();
|
||||
pushStateStub.reset();
|
||||
|
||||
$('#app-content-other').trigger(new $.Event('changeDirectory', {dir: 'sub dir'}));
|
||||
expect(pushStateStub.calledOnce).toEqual(true);
|
||||
params = OC.parseQueryString(pushStateStub.getCall(0).args[0]);
|
||||
expect(params.dir).toEqual('sub dir');
|
||||
expect(params.view).toEqual('other');
|
||||
});
|
||||
it('replaces the state to the URL when fileid is known', function() {
|
||||
$('#app-content-files').trigger(new $.Event('changeDirectory', {dir: 'sub dir'}));
|
||||
expect(pushStateStub.calledOnce).toEqual(true);
|
||||
var params = OC.parseQueryString(pushStateStub.getCall(0).args[0]);
|
||||
expect(params.dir).toEqual('sub dir');
|
||||
expect(params.view).not.toBeDefined();
|
||||
expect(replaceStateStub.notCalled).toEqual(true);
|
||||
|
||||
parseUrlQueryStub.returns({dir: 'sub dir'});
|
||||
|
||||
$('#app-content-files').trigger(new $.Event('afterChangeDirectory', {dir: 'sub dir', fileId: 123}));
|
||||
|
||||
expect(pushStateStub.calledOnce).toEqual(true);
|
||||
expect(replaceStateStub.calledOnce).toEqual(true);
|
||||
params = OC.parseQueryString(replaceStateStub.getCall(0).args[0]);
|
||||
expect(params.dir).toEqual('sub dir');
|
||||
expect(params.view).not.toBeDefined();
|
||||
expect(params.fileid).toEqual('123');
|
||||
});
|
||||
describe('onpopstate', function() {
|
||||
it('sends "urlChanged" event to current app', function() {
|
||||
var handler = sinon.stub();
|
||||
$('#app-content-files').on('urlChanged', handler);
|
||||
App._onPopState({view: 'files', dir: '/somedir'});
|
||||
expect(handler.calledOnce).toEqual(true);
|
||||
expect(handler.getCall(0).args[0].view).toEqual('files');
|
||||
expect(handler.getCall(0).args[0].dir).toEqual('/somedir');
|
||||
});
|
||||
it('sends "show" event to current app and sets navigation', function() {
|
||||
var showHandlerFiles = sinon.stub();
|
||||
var showHandlerOther = sinon.stub();
|
||||
var hideHandlerFiles = sinon.stub();
|
||||
var hideHandlerOther = sinon.stub();
|
||||
$('#app-content-files').on('show', showHandlerFiles);
|
||||
$('#app-content-files').on('hide', hideHandlerFiles);
|
||||
$('#app-content-other').on('show', showHandlerOther);
|
||||
$('#app-content-other').on('hide', hideHandlerOther);
|
||||
App._onPopState({view: 'other', dir: '/somedir'});
|
||||
expect(showHandlerFiles.notCalled).toEqual(true);
|
||||
expect(hideHandlerFiles.calledOnce).toEqual(true);
|
||||
expect(showHandlerOther.calledOnce).toEqual(true);
|
||||
expect(hideHandlerOther.notCalled).toEqual(true);
|
||||
|
||||
showHandlerFiles.reset();
|
||||
showHandlerOther.reset();
|
||||
hideHandlerFiles.reset();
|
||||
hideHandlerOther.reset();
|
||||
|
||||
App._onPopState({view: 'files', dir: '/somedir'});
|
||||
expect(showHandlerFiles.calledOnce).toEqual(true);
|
||||
expect(hideHandlerFiles.notCalled).toEqual(true);
|
||||
expect(showHandlerOther.notCalled).toEqual(true);
|
||||
expect(hideHandlerOther.calledOnce).toEqual(true);
|
||||
|
||||
expect(App.navigation.getActiveItem()).toEqual('files');
|
||||
expect($('#app-content-files').hasClass('hidden')).toEqual(false);
|
||||
expect($('#app-content-other').hasClass('hidden')).toEqual(true);
|
||||
});
|
||||
it('does not send "show" or "hide" event to current app when already visible', function() {
|
||||
var showHandler = sinon.stub();
|
||||
var hideHandler = sinon.stub();
|
||||
$('#app-content-files').on('show', showHandler);
|
||||
$('#app-content-files').on('hide', hideHandler);
|
||||
App._onPopState({view: 'files', dir: '/somedir'});
|
||||
expect(showHandler.notCalled).toEqual(true);
|
||||
expect(hideHandler.notCalled).toEqual(true);
|
||||
});
|
||||
it('state defaults to files app with root dir', function() {
|
||||
var handler = sinon.stub();
|
||||
parseUrlQueryStub.returns({});
|
||||
$('#app-content-files').on('urlChanged', handler);
|
||||
App._onPopState();
|
||||
expect(handler.calledOnce).toEqual(true);
|
||||
expect(handler.getCall(0).args[0].view).toEqual('files');
|
||||
expect(handler.getCall(0).args[0].dir).toEqual('/');
|
||||
});
|
||||
it('activates files app if invalid view is passed', function() {
|
||||
App._onPopState({view: 'invalid', dir: '/somedir'});
|
||||
|
||||
expect(App.navigation.getActiveItem()).toEqual('files');
|
||||
expect($('#app-content-files').hasClass('hidden')).toEqual(false);
|
||||
});
|
||||
});
|
||||
describe('navigation', function() {
|
||||
it('switches the navigation item and panel visibility when onpopstate', function() {
|
||||
App._onPopState({view: 'other', dir: '/somedir'});
|
||||
expect(App.navigation.getActiveItem()).toEqual('other');
|
||||
expect($('#app-content-files').hasClass('hidden')).toEqual(true);
|
||||
expect($('#app-content-other').hasClass('hidden')).toEqual(false);
|
||||
expect($('li[data-id=files] > a').hasClass('active')).toEqual(false);
|
||||
expect($('li[data-id=other] > a').hasClass('active')).toEqual(true);
|
||||
|
||||
App._onPopState({view: 'files', dir: '/somedir'});
|
||||
|
||||
expect(App.navigation.getActiveItem()).toEqual('files');
|
||||
expect($('#app-content-files').hasClass('hidden')).toEqual(false);
|
||||
expect($('#app-content-other').hasClass('hidden')).toEqual(true);
|
||||
expect($('li[data-id=files] > a').hasClass('active')).toEqual(true);
|
||||
expect($('li[data-id=other] > a').hasClass('active')).toEqual(false);
|
||||
});
|
||||
it('clicking on navigation switches the panel visibility', function() {
|
||||
$('li[data-id=other] > a').click();
|
||||
expect(App.navigation.getActiveItem()).toEqual('other');
|
||||
expect($('#app-content-files').hasClass('hidden')).toEqual(true);
|
||||
expect($('#app-content-other').hasClass('hidden')).toEqual(false);
|
||||
expect($('li[data-id=files] > a').hasClass('active')).toEqual(false);
|
||||
expect($('li[data-id=other] > a').hasClass('active')).toEqual(true);
|
||||
|
||||
$('li[data-id=files] > a').click();
|
||||
expect(App.navigation.getActiveItem()).toEqual('files');
|
||||
expect($('#app-content-files').hasClass('hidden')).toEqual(false);
|
||||
expect($('#app-content-other').hasClass('hidden')).toEqual(true);
|
||||
expect($('li[data-id=files] > a').hasClass('active')).toEqual(true);
|
||||
expect($('li[data-id=other] > a').hasClass('active')).toEqual(false);
|
||||
});
|
||||
it('clicking on navigation sends "show" and "urlChanged" event', function() {
|
||||
var handler = sinon.stub();
|
||||
var showHandler = sinon.stub();
|
||||
$('#app-content-other').on('urlChanged', handler);
|
||||
$('#app-content-other').on('show', showHandler);
|
||||
$('li[data-id=other] > a').click();
|
||||
expect(handler.calledOnce).toEqual(true);
|
||||
expect(handler.getCall(0).args[0].view).toEqual('other');
|
||||
expect(handler.getCall(0).args[0].dir).toEqual('/');
|
||||
expect(showHandler.calledOnce).toEqual(true);
|
||||
});
|
||||
it('clicking on activate navigation only sends "urlChanged" event', function() {
|
||||
var handler = sinon.stub();
|
||||
var showHandler = sinon.stub();
|
||||
$('#app-content-files').on('urlChanged', handler);
|
||||
$('#app-content-files').on('show', showHandler);
|
||||
$('li[data-id=files] > a').click();
|
||||
expect(handler.calledOnce).toEqual(true);
|
||||
expect(handler.getCall(0).args[0].view).toEqual('files');
|
||||
expect(handler.getCall(0).args[0].dir).toEqual('/');
|
||||
expect(showHandler.notCalled).toEqual(true);
|
||||
});
|
||||
});
|
||||
describe('viewer mode', function() {
|
||||
it('toggles the sidebar when viewer mode is enabled', function() {
|
||||
$('#app-content-files').trigger(
|
||||
new $.Event('changeViewerMode', {viewerModeEnabled: true}
|
||||
));
|
||||
expect($('#app-navigation').hasClass('hidden')).toEqual(true);
|
||||
expect($('.app-files').hasClass('viewer-mode no-sidebar')).toEqual(true);
|
||||
|
||||
$('#app-content-files').trigger(
|
||||
new $.Event('changeViewerMode', {viewerModeEnabled: false}
|
||||
));
|
||||
|
||||
expect($('#app-navigation').hasClass('hidden')).toEqual(false);
|
||||
expect($('.app-files').hasClass('viewer-mode no-sidebar')).toEqual(false);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
@ -0,0 +1,12 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1.0">
|
||||
<title>Components App</title>
|
||||
</head>
|
||||
<body>
|
||||
<div data-cy-root></div>
|
||||
</body>
|
||||
</html>
|
@ -0,0 +1,35 @@
|
||||
/**
|
||||
* @copyright Copyright (c) 2022 John Molakvoæ <skjnldsv@protonmail.com>
|
||||
*
|
||||
* @author John Molakvoæ <skjnldsv@protonmail.com>
|
||||
*
|
||||
* @license AGPL-3.0-or-later
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
import { mount } from 'cypress/vue2'
|
||||
|
||||
type MountParams = Parameters<typeof mount>;
|
||||
type OptionsParam = MountParams[1];
|
||||
|
||||
declare global {
|
||||
namespace Cypress {
|
||||
interface Chainable<Subject = any> {
|
||||
mount: typeof mount;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Cypress.Commands.add('mount', mount);
|
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@ -1 +1 @@
|
||||
{"version":3,"file":"core-files_fileinfo.js?v=d5c54f8e5b3834c089a0","mappings":";CA0BA,SAAUA,GAUT,IAAMC,EAAW,SAASC,GACzB,IAAMC,EAAOC,KACbC,EAAEC,KAAKJ,GAAM,SAASK,EAAOC,GACvBH,EAAEI,WAAWF,KACjBJ,EAAKK,GAAOD,EAEb,IAEIF,EAAEK,YAAYN,KAAKO,MACvBP,KAAKO,GAAKC,SAASV,EAAKS,GAAI,KAI7BP,KAAKS,KAAOX,EAAKW,MAAQ,GAEP,QAAdT,KAAKU,KACRV,KAAKW,SAAW,uBAEhBX,KAAKW,SAAWX,KAAKW,UAAY,2BAG7BX,KAAKU,OACa,yBAAlBV,KAAKW,SACRX,KAAKU,KAAO,MAEZV,KAAKU,KAAO,OAGd,EAKDb,EAASe,UAAY,CAMpBL,GAAI,KAOJM,KAAM,KAQNJ,KAAM,KAONE,SAAU,KASVG,KAAM,KAQNJ,KAAM,KAQNK,YAAa,KAObC,MAAO,KAOPC,KAAM,KASNC,UAAW,KAKXC,YAAY,EAKZC,iBAAkB,KAKlBC,gBAAiB,GAEjBC,qBAAsB,EAEtBC,YAAa,WACZ,IAAK,IAAMC,KAAKxB,KAAKqB,gBAAiB,CACrC,IAAMI,EAAOzB,KAAKqB,gBAAgBG,GAClC,GAAmB,gBAAfC,EAAKC,OAAwC,aAAbD,EAAKrB,IACxC,OAAOqB,EAAKE,OAEb,CAED,OAAO,CACP,GAGG/B,EAAGgC,QACPhC,EAAGgC,MAAQ,CAAC,GAEbhC,EAAGgC,MAAM/B,SAAWA,CAzJrB,EA0JGD","sources":["webpack:///nextcloud/core/src/files/fileinfo.js"],"sourcesContent":["/**\n * Copyright (c) 2015\n *\n * @author Julius Härtl <jus@bitgrid.net>\n * @author Robin Appelman <robin@icewind.nl>\n * @author Roeland Jago Douma <roeland@famdouma.nl>\n * @author Vincent Petry <vincent@nextcloud.com>\n *\n * @license AGPL-3.0-or-later\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program. If not, see <http://www.gnu.org/licenses/>.\n *\n */\n\n/* eslint-disable */\n(function(OC) {\n\n\t/**\n\t * @class OC.Files.FileInfo\n\t * @classdesc File information\n\t *\n\t * @param {Object} data file data, see attributes for details\n\t *\n\t * @since 8.2\n\t */\n\tconst FileInfo = function(data) {\n\t\tconst self = this\n\t\t_.each(data, function(value, key) {\n\t\t\tif (!_.isFunction(value)) {\n\t\t\t\tself[key] = value\n\t\t\t}\n\t\t})\n\n\t\tif (!_.isUndefined(this.id)) {\n\t\t\tthis.id = parseInt(data.id, 10)\n\t\t}\n\n\t\t// TODO: normalize path\n\t\tthis.path = data.path || ''\n\n\t\tif (this.type === 'dir') {\n\t\t\tthis.mimetype = 'httpd/unix-directory'\n\t\t} else {\n\t\t\tthis.mimetype = this.mimetype || 'application/octet-stream'\n\t\t}\n\n\t\tif (!this.type) {\n\t\t\tif (this.mimetype === 'httpd/unix-directory') {\n\t\t\t\tthis.type = 'dir'\n\t\t\t} else {\n\t\t\t\tthis.type = 'file'\n\t\t\t}\n\t\t}\n\t}\n\n\t/**\n\t * @memberof OC.Files\n\t */\n\tFileInfo.prototype = {\n\t\t/**\n\t\t * File id\n\t\t *\n\t\t * @type int\n\t\t */\n\t\tid: null,\n\n\t\t/**\n\t\t * File name\n\t\t *\n\t\t * @type String\n\t\t */\n\t\tname: null,\n\n\t\t/**\n\t\t * Path leading to the file, without the file name,\n\t\t * and with a leading slash.\n\t\t *\n\t\t * @type String\n\t\t */\n\t\tpath: null,\n\n\t\t/**\n\t\t * Mime type\n\t\t *\n\t\t * @type String\n\t\t */\n\t\tmimetype: null,\n\n\t\t/**\n\t\t * Icon URL.\n\t\t *\n\t\t * Can be used to override the mime type icon.\n\t\t *\n\t\t * @type String\n\t\t */\n\t\ticon: null,\n\n\t\t/**\n\t\t * File type. 'file' for files, 'dir' for directories.\n\t\t *\n\t\t * @type String\n\t\t * @deprecated rely on mimetype instead\n\t\t */\n\t\ttype: null,\n\n\t\t/**\n\t\t * Permissions.\n\t\t *\n\t\t * @see OC#PERMISSION_ALL for permissions\n\t\t * @type int\n\t\t */\n\t\tpermissions: null,\n\n\t\t/**\n\t\t * Modification time\n\t\t *\n\t\t * @type int\n\t\t */\n\t\tmtime: null,\n\n\t\t/**\n\t\t * Etag\n\t\t *\n\t\t * @type String\n\t\t */\n\t\tetag: null,\n\n\t\t/**\n\t\t * Mount type.\n\t\t *\n\t\t * One of null, \"external-root\", \"shared\" or \"shared-root\"\n\t\t *\n\t\t * @type string\n\t\t */\n\t\tmountType: null,\n\n\t\t/**\n\t\t * @type boolean\n\t\t */\n\t\thasPreview: true,\n\n\t\t/**\n\t\t * @type int\n\t\t */\n\t\tsharePermissions: null,\n\n\t\t/**\n\t\t * @type Array\n\t\t */\n\t\tshareAttributes: [],\n\n\t\tquotaAvailableBytes: -1,\n\n\t\tcanDownload: function() {\n\t\t\tfor (const i in this.shareAttributes) {\n\t\t\t\tconst attr = this.shareAttributes[i]\n\t\t\t\tif (attr.scope === 'permissions' && attr.key === 'download') {\n\t\t\t\t\treturn attr.enabled\n\t\t\t\t}\n\t\t\t}\n\n\t\t\treturn true\n\t\t},\n\t}\n\n\tif (!OC.Files) {\n\t\tOC.Files = {}\n\t}\n\tOC.Files.FileInfo = FileInfo\n})(OC)\n"],"names":["OC","FileInfo","data","self","this","_","each","value","key","isFunction","isUndefined","id","parseInt","path","type","mimetype","prototype","name","icon","permissions","mtime","etag","mountType","hasPreview","sharePermissions","shareAttributes","quotaAvailableBytes","canDownload","i","attr","scope","enabled","Files"],"sourceRoot":""}
|
||||
{"version":3,"file":"core-files_fileinfo.js?v=d5c54f8e5b3834c089a0","mappings":";CA0BA,SAAUA,GAUT,IAAMC,EAAW,SAASC,GACzB,IAAMC,EAAOC,KACbC,EAAEC,KAAKJ,GAAM,SAASK,EAAOC,GACvBH,EAAEI,WAAWF,KACjBJ,EAAKK,GAAOD,EAEd,IAEKF,EAAEK,YAAYN,KAAKO,MACvBP,KAAKO,GAAKC,SAASV,EAAKS,GAAI,KAI7BP,KAAKS,KAAOX,EAAKW,MAAQ,GAEP,QAAdT,KAAKU,KACRV,KAAKW,SAAW,uBAEhBX,KAAKW,SAAWX,KAAKW,UAAY,2BAG7BX,KAAKU,OACa,yBAAlBV,KAAKW,SACRX,KAAKU,KAAO,MAEZV,KAAKU,KAAO,OAGf,EAKAb,EAASe,UAAY,CAMpBL,GAAI,KAOJM,KAAM,KAQNJ,KAAM,KAONE,SAAU,KASVG,KAAM,KAQNJ,KAAM,KAQNK,YAAa,KAObC,MAAO,KAOPC,KAAM,KASNC,UAAW,KAKXC,YAAY,EAKZC,iBAAkB,KAKlBC,gBAAiB,GAEjBC,qBAAsB,EAEtBC,YAAa,WACZ,IAAK,IAAMC,KAAKxB,KAAKqB,gBAAiB,CACrC,IAAMI,EAAOzB,KAAKqB,gBAAgBG,GAClC,GAAmB,gBAAfC,EAAKC,OAAwC,aAAbD,EAAKrB,IACxC,OAAOqB,EAAKE,OAEd,CAEA,OAAO,CACR,GAGI/B,EAAGgC,QACPhC,EAAGgC,MAAQ,CAAC,GAEbhC,EAAGgC,MAAM/B,SAAWA,CACpB,CA1JD,CA0JGD","sources":["webpack:///nextcloud/core/src/files/fileinfo.js"],"sourcesContent":["/**\n * Copyright (c) 2015\n *\n * @author Julius Härtl <jus@bitgrid.net>\n * @author Robin Appelman <robin@icewind.nl>\n * @author Roeland Jago Douma <roeland@famdouma.nl>\n * @author Vincent Petry <vincent@nextcloud.com>\n *\n * @license AGPL-3.0-or-later\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program. If not, see <http://www.gnu.org/licenses/>.\n *\n */\n\n/* eslint-disable */\n(function(OC) {\n\n\t/**\n\t * @class OC.Files.FileInfo\n\t * @classdesc File information\n\t *\n\t * @param {Object} data file data, see attributes for details\n\t *\n\t * @since 8.2\n\t */\n\tconst FileInfo = function(data) {\n\t\tconst self = this\n\t\t_.each(data, function(value, key) {\n\t\t\tif (!_.isFunction(value)) {\n\t\t\t\tself[key] = value\n\t\t\t}\n\t\t})\n\n\t\tif (!_.isUndefined(this.id)) {\n\t\t\tthis.id = parseInt(data.id, 10)\n\t\t}\n\n\t\t// TODO: normalize path\n\t\tthis.path = data.path || ''\n\n\t\tif (this.type === 'dir') {\n\t\t\tthis.mimetype = 'httpd/unix-directory'\n\t\t} else {\n\t\t\tthis.mimetype = this.mimetype || 'application/octet-stream'\n\t\t}\n\n\t\tif (!this.type) {\n\t\t\tif (this.mimetype === 'httpd/unix-directory') {\n\t\t\t\tthis.type = 'dir'\n\t\t\t} else {\n\t\t\t\tthis.type = 'file'\n\t\t\t}\n\t\t}\n\t}\n\n\t/**\n\t * @memberof OC.Files\n\t */\n\tFileInfo.prototype = {\n\t\t/**\n\t\t * File id\n\t\t *\n\t\t * @type int\n\t\t */\n\t\tid: null,\n\n\t\t/**\n\t\t * File name\n\t\t *\n\t\t * @type String\n\t\t */\n\t\tname: null,\n\n\t\t/**\n\t\t * Path leading to the file, without the file name,\n\t\t * and with a leading slash.\n\t\t *\n\t\t * @type String\n\t\t */\n\t\tpath: null,\n\n\t\t/**\n\t\t * Mime type\n\t\t *\n\t\t * @type String\n\t\t */\n\t\tmimetype: null,\n\n\t\t/**\n\t\t * Icon URL.\n\t\t *\n\t\t * Can be used to override the mime type icon.\n\t\t *\n\t\t * @type String\n\t\t */\n\t\ticon: null,\n\n\t\t/**\n\t\t * File type. 'file' for files, 'dir' for directories.\n\t\t *\n\t\t * @type String\n\t\t * @deprecated rely on mimetype instead\n\t\t */\n\t\ttype: null,\n\n\t\t/**\n\t\t * Permissions.\n\t\t *\n\t\t * @see OC#PERMISSION_ALL for permissions\n\t\t * @type int\n\t\t */\n\t\tpermissions: null,\n\n\t\t/**\n\t\t * Modification time\n\t\t *\n\t\t * @type int\n\t\t */\n\t\tmtime: null,\n\n\t\t/**\n\t\t * Etag\n\t\t *\n\t\t * @type String\n\t\t */\n\t\tetag: null,\n\n\t\t/**\n\t\t * Mount type.\n\t\t *\n\t\t * One of null, \"external-root\", \"shared\" or \"shared-root\"\n\t\t *\n\t\t * @type string\n\t\t */\n\t\tmountType: null,\n\n\t\t/**\n\t\t * @type boolean\n\t\t */\n\t\thasPreview: true,\n\n\t\t/**\n\t\t * @type int\n\t\t */\n\t\tsharePermissions: null,\n\n\t\t/**\n\t\t * @type Array\n\t\t */\n\t\tshareAttributes: [],\n\n\t\tquotaAvailableBytes: -1,\n\n\t\tcanDownload: function() {\n\t\t\tfor (const i in this.shareAttributes) {\n\t\t\t\tconst attr = this.shareAttributes[i]\n\t\t\t\tif (attr.scope === 'permissions' && attr.key === 'download') {\n\t\t\t\t\treturn attr.enabled\n\t\t\t\t}\n\t\t\t}\n\n\t\t\treturn true\n\t\t},\n\t}\n\n\tif (!OC.Files) {\n\t\tOC.Files = {}\n\t}\n\tOC.Files.FileInfo = FileInfo\n})(OC)\n"],"names":["OC","FileInfo","data","self","this","_","each","value","key","isFunction","isUndefined","id","parseInt","path","type","mimetype","prototype","name","icon","permissions","mtime","etag","mountType","hasPreview","sharePermissions","shareAttributes","quotaAvailableBytes","canDownload","i","attr","scope","enabled","Files"],"sourceRoot":""}
|
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@ -1,3 +1,3 @@
|
||||
/*! For license information please see core-unsupported-browser-redirect.js.LICENSE.txt */
|
||||
!function(){var e,n={25714:function(e,n,r){"use strict";var o,t,i,u=r(79753),l=r(81655),c=r(31e3),a=r.n(c),d=r(77727),f=r.n(d),s=(0,l.ZI)({allowHigherVersions:!0,browsers:f()}),p=(a()(f()),(0,r(62556).getBuilder)("core").clearOnLogout().persist().build()),b=r(22200),h=r(17499),g=null===(o=(0,b.getCurrentUser)())?(0,h.IY)().setApp("core").build():(0,h.IY)().setApp("core").setUid(o.uid).build(),v=r(48764).Buffer,w=(0,u.generateUrl)("/unsupported"),O="true"===p.getItem("unsupported-browser-ignore");window.TESTING||null!==(t=OC)&&void 0!==t&&null!==(i=t.config)&&void 0!==i&&i.no_unsupported_browser_warning||function(){if(s.test(navigator.userAgent))g.debug("this browser is officially supported ! 🚀");else if(O)g.debug("this browser is NOT supported but has been manually overridden ! ⚠️");else if(-1===window.location.pathname.indexOf(w)){var e=window.location.href.replace(window.location.origin,""),n=v.from(e).toString("base64");history.pushState(null,null,"".concat(w,"?redirect_url=").concat(n)),window.location.reload()}}()},72950:function(){}},r={};function o(e){var t=r[e];if(void 0!==t)return t.exports;var i=r[e]={id:e,loaded:!1,exports:{}};return n[e].call(i.exports,i,i.exports,o),i.loaded=!0,i.exports}o.m=n,o.amdD=function(){throw new Error("define cannot be used indirect")},o.amdO={},e=[],o.O=function(n,r,t,i){if(!r){var u=1/0;for(d=0;d<e.length;d++){r=e[d][0],t=e[d][1],i=e[d][2];for(var l=!0,c=0;c<r.length;c++)(!1&i||u>=i)&&Object.keys(o.O).every((function(e){return o.O[e](r[c])}))?r.splice(c--,1):(l=!1,i<u&&(u=i));if(l){e.splice(d--,1);var a=t();void 0!==a&&(n=a)}}return n}i=i||0;for(var d=e.length;d>0&&e[d-1][2]>i;d--)e[d]=e[d-1];e[d]=[r,t,i]},o.n=function(e){var n=e&&e.__esModule?function(){return e.default}:function(){return e};return o.d(n,{a:n}),n},o.d=function(e,n){for(var r in n)o.o(n,r)&&!o.o(e,r)&&Object.defineProperty(e,r,{enumerable:!0,get:n[r]})},o.g=function(){if("object"==typeof globalThis)return globalThis;try{return this||new Function("return this")()}catch(e){if("object"==typeof window)return window}}(),o.o=function(e,n){return Object.prototype.hasOwnProperty.call(e,n)},o.r=function(e){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},o.nmd=function(e){return e.paths=[],e.children||(e.children=[]),e},o.j=8876,function(){o.b=document.baseURI||self.location.href;var e={8876:0};o.O.j=function(n){return 0===e[n]};var n=function(n,r){var t,i,u=r[0],l=r[1],c=r[2],a=0;if(u.some((function(n){return 0!==e[n]}))){for(t in l)o.o(l,t)&&(o.m[t]=l[t]);if(c)var d=c(o)}for(n&&n(r);a<u.length;a++)i=u[a],o.o(e,i)&&e[i]&&e[i][0](),e[i]=0;return o.O(d)},r=self.webpackChunknextcloud=self.webpackChunknextcloud||[];r.forEach(n.bind(null,0)),r.push=n.bind(null,r.push.bind(r))}(),o.nc=void 0;var t=o.O(void 0,[7874],(function(){return o(25714)}));t=o.O(t)}();
|
||||
//# sourceMappingURL=core-unsupported-browser-redirect.js.map?v=22e284cc4a754bfd50a8
|
||||
!function(){var e,n={25714:function(e,n,r){"use strict";var o,t,i,u=r(79753),l=r(81655),c=r(31e3),a=r.n(c),d=r(77727),f=r.n(d),s=(0,l.ZI)({allowHigherVersions:!0,browsers:f()}),p=(a()(f()),(0,r(62556).getBuilder)("core").clearOnLogout().persist().build()),b=r(45994),h=r(17499),v=null===(o=(0,b.ts)())?(0,h.IY)().setApp("core").build():(0,h.IY)().setApp("core").setUid(o.uid).build(),w=r(48764).Buffer,g=(0,u.generateUrl)("/unsupported"),O="true"===p.getItem("unsupported-browser-ignore");window.TESTING||null!==(t=OC)&&void 0!==t&&null!==(i=t.config)&&void 0!==i&&i.no_unsupported_browser_warning||function(){if(s.test(navigator.userAgent))v.debug("this browser is officially supported ! 🚀");else if(O)v.debug("this browser is NOT supported but has been manually overridden ! ⚠️");else if(-1===window.location.pathname.indexOf(g)){var e=window.location.href.replace(window.location.origin,""),n=w.from(e).toString("base64");history.pushState(null,null,"".concat(g,"?redirect_url=").concat(n)),window.location.reload()}}()},72950:function(){}},r={};function o(e){var t=r[e];if(void 0!==t)return t.exports;var i=r[e]={id:e,loaded:!1,exports:{}};return n[e].call(i.exports,i,i.exports,o),i.loaded=!0,i.exports}o.m=n,o.amdD=function(){throw new Error("define cannot be used indirect")},o.amdO={},e=[],o.O=function(n,r,t,i){if(!r){var u=1/0;for(d=0;d<e.length;d++){r=e[d][0],t=e[d][1],i=e[d][2];for(var l=!0,c=0;c<r.length;c++)(!1&i||u>=i)&&Object.keys(o.O).every((function(e){return o.O[e](r[c])}))?r.splice(c--,1):(l=!1,i<u&&(u=i));if(l){e.splice(d--,1);var a=t();void 0!==a&&(n=a)}}return n}i=i||0;for(var d=e.length;d>0&&e[d-1][2]>i;d--)e[d]=e[d-1];e[d]=[r,t,i]},o.n=function(e){var n=e&&e.__esModule?function(){return e.default}:function(){return e};return o.d(n,{a:n}),n},o.d=function(e,n){for(var r in n)o.o(n,r)&&!o.o(e,r)&&Object.defineProperty(e,r,{enumerable:!0,get:n[r]})},o.g=function(){if("object"==typeof globalThis)return globalThis;try{return this||new Function("return this")()}catch(e){if("object"==typeof window)return window}}(),o.o=function(e,n){return Object.prototype.hasOwnProperty.call(e,n)},o.r=function(e){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},o.nmd=function(e){return e.paths=[],e.children||(e.children=[]),e},o.j=8876,function(){o.b=document.baseURI||self.location.href;var e={8876:0};o.O.j=function(n){return 0===e[n]};var n=function(n,r){var t,i,u=r[0],l=r[1],c=r[2],a=0;if(u.some((function(n){return 0!==e[n]}))){for(t in l)o.o(l,t)&&(o.m[t]=l[t]);if(c)var d=c(o)}for(n&&n(r);a<u.length;a++)i=u[a],o.o(e,i)&&e[i]&&e[i][0](),e[i]=0;return o.O(d)},r=self.webpackChunknextcloud=self.webpackChunknextcloud||[];r.forEach(n.bind(null,0)),r.push=n.bind(null,r.push.bind(r))}(),o.nc=void 0;var t=o.O(void 0,[7874],(function(){return o(25714)}));t=o.O(t)}();
|
||||
//# sourceMappingURL=core-unsupported-browser-redirect.js.map?v=0035fd90d8be1902df52
|
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@ -1 +1 @@
|
||||
{"version":3,"file":"files_sharing-collaboration.js?v=b736a508139452dd8b55","mappings":"AAwBoBA,KAAKC,GAAGC,cAE5BC,OAAOC,IAAIC,cAAcC,aAAa,OAAQ,CAC7CC,OAAQ,WACP,OAAO,IAAIC,SAAQ,SAACC,EAASC,GAC5BT,GAAGU,QAAQC,WAAWC,EAAE,gBAAiB,mBAAmB,SAASC,GACrDb,GAAGc,MAAMC,YACjBC,YAAYH,GAAGI,MAAK,SAACC,EAAQC,GACnCX,EAAQW,EAASC,GACjB,IAAEC,MAAK,WACPZ,EAAO,IAAIa,MAAM,uBACjB,GACD,IAAE,EAAO,MAAM,EAAOtB,GAAGU,QAAQa,uBAAwB,GAAI,CAAEC,uBAAuB,GACvF,GACD,EACDC,WAAYb,EAAE,gBAAiB,kBAC/Bc,cAAe","sources":["webpack:///nextcloud/apps/files_sharing/src/collaborationresourceshandler.js"],"sourcesContent":["/**\n * @copyright Copyright (c) 2016 John Molakvoæ <skjnldsv@protonmail.com>\n *\n * @author John Molakvoæ <skjnldsv@protonmail.com>\n * @author Julius Härtl <jus@bitgrid.net>\n *\n * @license AGPL-3.0-or-later\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program. If not, see <http://www.gnu.org/licenses/>.\n *\n */\n\n// eslint-disable-next-line camelcase\n__webpack_nonce__ = btoa(OC.requestToken)\n\nwindow.OCP.Collaboration.registerType('file', {\n\taction: () => {\n\t\treturn new Promise((resolve, reject) => {\n\t\t\tOC.dialogs.filepicker(t('files_sharing', 'Link to a file'), function(f) {\n\t\t\t\tconst client = OC.Files.getClient()\n\t\t\t\tclient.getFileInfo(f).then((status, fileInfo) => {\n\t\t\t\t\tresolve(fileInfo.id)\n\t\t\t\t}).fail(() => {\n\t\t\t\t\treject(new Error('Cannot get fileinfo'))\n\t\t\t\t})\n\t\t\t}, false, null, false, OC.dialogs.FILEPICKER_TYPE_CHOOSE, '', { allowDirectoryChooser: true })\n\t\t})\n\t},\n\ttypeString: t('files_sharing', 'Link to a file'),\n\ttypeIconClass: 'icon-files-dark',\n})\n"],"names":["btoa","OC","requestToken","window","OCP","Collaboration","registerType","action","Promise","resolve","reject","dialogs","filepicker","t","f","Files","getClient","getFileInfo","then","status","fileInfo","id","fail","Error","FILEPICKER_TYPE_CHOOSE","allowDirectoryChooser","typeString","typeIconClass"],"sourceRoot":""}
|
||||
{"version":3,"file":"files_sharing-collaboration.js?v=b736a508139452dd8b55","mappings":"AAwBoBA,KAAKC,GAAGC,cAE5BC,OAAOC,IAAIC,cAAcC,aAAa,OAAQ,CAC7CC,OAAQ,WACP,OAAO,IAAIC,SAAQ,SAACC,EAASC,GAC5BT,GAAGU,QAAQC,WAAWC,EAAE,gBAAiB,mBAAmB,SAASC,GACrDb,GAAGc,MAAMC,YACjBC,YAAYH,GAAGI,MAAK,SAACC,EAAQC,GACnCX,EAAQW,EAASC,GAClB,IAAGC,MAAK,WACPZ,EAAO,IAAIa,MAAM,uBAClB,GACD,IAAG,EAAO,MAAM,EAAOtB,GAAGU,QAAQa,uBAAwB,GAAI,CAAEC,uBAAuB,GACxF,GACD,EACAC,WAAYb,EAAE,gBAAiB,kBAC/Bc,cAAe","sources":["webpack:///nextcloud/apps/files_sharing/src/collaborationresourceshandler.js"],"sourcesContent":["/**\n * @copyright Copyright (c) 2016 John Molakvoæ <skjnldsv@protonmail.com>\n *\n * @author John Molakvoæ <skjnldsv@protonmail.com>\n * @author Julius Härtl <jus@bitgrid.net>\n *\n * @license AGPL-3.0-or-later\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program. If not, see <http://www.gnu.org/licenses/>.\n *\n */\n\n// eslint-disable-next-line camelcase\n__webpack_nonce__ = btoa(OC.requestToken)\n\nwindow.OCP.Collaboration.registerType('file', {\n\taction: () => {\n\t\treturn new Promise((resolve, reject) => {\n\t\t\tOC.dialogs.filepicker(t('files_sharing', 'Link to a file'), function(f) {\n\t\t\t\tconst client = OC.Files.getClient()\n\t\t\t\tclient.getFileInfo(f).then((status, fileInfo) => {\n\t\t\t\t\tresolve(fileInfo.id)\n\t\t\t\t}).fail(() => {\n\t\t\t\t\treject(new Error('Cannot get fileinfo'))\n\t\t\t\t})\n\t\t\t}, false, null, false, OC.dialogs.FILEPICKER_TYPE_CHOOSE, '', { allowDirectoryChooser: true })\n\t\t})\n\t},\n\ttypeString: t('files_sharing', 'Link to a file'),\n\ttypeIconClass: 'icon-files-dark',\n})\n"],"names":["btoa","OC","requestToken","window","OCP","Collaboration","registerType","action","Promise","resolve","reject","dialogs","filepicker","t","f","Files","getClient","getFileInfo","then","status","fileInfo","id","fail","Error","FILEPICKER_TYPE_CHOOSE","allowDirectoryChooser","typeString","typeIconClass"],"sourceRoot":""}
|
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue