diff options
Diffstat (limited to 'core/src/OCP')
-rw-r--r-- | core/src/OCP/accessibility.js | 28 | ||||
-rw-r--r-- | core/src/OCP/appconfig.js | 102 | ||||
-rw-r--r-- | core/src/OCP/collaboration.js | 65 | ||||
-rw-r--r-- | core/src/OCP/comments.js | 61 | ||||
-rw-r--r-- | core/src/OCP/index.js | 35 | ||||
-rw-r--r-- | core/src/OCP/loader.js | 64 | ||||
-rw-r--r-- | core/src/OCP/toast.js | 67 | ||||
-rw-r--r-- | core/src/OCP/whatsnew.js | 151 |
8 files changed, 573 insertions, 0 deletions
diff --git a/core/src/OCP/accessibility.js b/core/src/OCP/accessibility.js new file mode 100644 index 00000000000..4a1399f3f96 --- /dev/null +++ b/core/src/OCP/accessibility.js @@ -0,0 +1,28 @@ +/** + * SPDX-FileCopyrightText: 2022 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +import { loadState } from '@nextcloud/initial-state' + +/** + * Set the page heading + * + * @param {string} heading page title from the history api + * @since 27.0.0 + */ +export function setPageHeading(heading) { + const headingEl = document.getElementById('page-heading-level-1') + if (headingEl) { + headingEl.textContent = heading + } +} +export default { + /** + * @return {boolean} Whether the user opted-out of shortcuts so that they should not be registered + */ + disableKeyboardShortcuts() { + return loadState('theming', 'shortcutsDisabled', false) + }, + setPageHeading, +} diff --git a/core/src/OCP/appconfig.js b/core/src/OCP/appconfig.js new file mode 100644 index 00000000000..78f94922d53 --- /dev/null +++ b/core/src/OCP/appconfig.js @@ -0,0 +1,102 @@ +/** + * SPDX-FileCopyrightText: 2016 Nextcloud GmbH and Nextcloud contributors + * SPDX-FileCopyrightText: 2016 ownCloud, Inc. + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +import $ from 'jquery' +import { generateOcsUrl } from '@nextcloud/router' + +import OC from '../OC/index.js' + +/** + * @param {string} method 'post' or 'delete' + * @param {string} endpoint endpoint + * @param {object} [options] destructuring object + * @param {object} [options.data] option data + * @param {Function} [options.success] success callback + * @param {Function} [options.error] error callback + */ +function call(method, endpoint, options) { + if ((method === 'post' || method === 'delete') && OC.PasswordConfirmation.requiresPasswordConfirmation()) { + OC.PasswordConfirmation.requirePasswordConfirmation(_.bind(call, this, method, endpoint, options)) + return + } + + options = options || {} + $.ajax({ + type: method.toUpperCase(), + url: generateOcsUrl('apps/provisioning_api/api/v1/config/apps') + endpoint, + data: options.data || {}, + success: options.success, + error: options.error, + }) +} + +/** + * @param {object} [options] destructuring object + * @param {Function} [options.success] success callback + * @since 11.0.0 + */ +export function getApps(options) { + call('get', '', options) +} + +/** + * @param {string} app app id + * @param {object} [options] destructuring object + * @param {Function} [options.success] success callback + * @param {Function} [options.error] error callback + * @since 11.0.0 + */ +export function getKeys(app, options) { + call('get', '/' + app, options) +} + +/** + * @param {string} app app id + * @param {string} key key + * @param {string | Function} defaultValue default value + * @param {object} [options] destructuring object + * @param {Function} [options.success] success callback + * @param {Function} [options.error] error callback + * @since 11.0.0 + */ +export function getValue(app, key, defaultValue, options) { + options = options || {} + options.data = { + defaultValue, + } + + call('get', '/' + app + '/' + key, options) +} + +/** + * @param {string} app app id + * @param {string} key key + * @param {string} value value + * @param {object} [options] destructuring object + * @param {Function} [options.success] success callback + * @param {Function} [options.error] error callback + * @since 11.0.0 + */ +export function setValue(app, key, value, options) { + options = options || {} + options.data = { + value, + } + + call('post', '/' + app + '/' + key, options) +} + +/** + * @param {string} app app id + * @param {string} key key + * @param {object} [options] destructuring object + * @param {Function} [options.success] success callback + * @param {Function} [options.error] error callback + * @since 11.0.0 + */ +export function deleteKey(app, key, options) { + call('delete', '/' + app + '/' + key, options) +} diff --git a/core/src/OCP/collaboration.js b/core/src/OCP/collaboration.js new file mode 100644 index 00000000000..82ff34392cf --- /dev/null +++ b/core/src/OCP/collaboration.js @@ -0,0 +1,65 @@ +/** + * SPDX-FileCopyrightText: 2018 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +import escapeHTML from 'escape-html' + +/** + * @typedef TypeDefinition + * @function action This action is executed to let the user select a resource + * @param {string} icon Contains the icon css class for the type + * @function Object() { [native code] } + */ + +/** + * @type {TypeDefinition[]} + */ +const types = {} + +/** + * Those translations will be used by the vue component but they should be shipped with the server + * FIXME: Those translations should be added to the library + * + * @return {Array} + */ +export const l10nProjects = () => { + return [ + t('core', 'Add to a project'), + t('core', 'Show details'), + t('core', 'Hide details'), + t('core', 'Rename project'), + t('core', 'Failed to rename the project'), + t('core', 'Failed to create a project'), + t('core', 'Failed to add the item to the project'), + t('core', 'Connect items to a project to make them easier to find'), + t('core', 'Type to search for existing projects'), + ] +} + +export default { + /** + * + * @param {string} type type + * @param {TypeDefinition} typeDefinition typeDefinition + */ + registerType(type, typeDefinition) { + types[type] = typeDefinition + }, + trigger(type) { + return types[type].action() + }, + getTypes() { + return Object.keys(types) + }, + getIcon(type) { + return types[type].typeIconClass || '' + }, + getLabel(type) { + return escapeHTML(types[type].typeString || type) + }, + getLink(type, id) { + /* TODO: Allow action to be executed instead of href as well */ + return typeof types[type] !== 'undefined' ? types[type].link(id) : '' + }, +} diff --git a/core/src/OCP/comments.js b/core/src/OCP/comments.js new file mode 100644 index 00000000000..34699a477d1 --- /dev/null +++ b/core/src/OCP/comments.js @@ -0,0 +1,61 @@ +/** + * SPDX-FileCopyrightText: 2017 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +import $ from 'jquery' + +/* + * Detects links: + * Either the http(s) protocol is given or two strings, basically limited to ascii with the last + * word being at least one digit long, + * followed by at least another character + * + * The downside: anything not ascii is excluded. Not sure how common it is in areas using different + * alphabets… the upside: fake domains with similar looking characters won't be formatted as links + * + * This is a copy of the backend regex in IURLGenerator, make sure to adjust both when changing + */ +const urlRegex = /(\s|^)(https?:\/\/)([-A-Z0-9+_.]+(?::[0-9]+)?(?:\/[-A-Z0-9+&@#%?=~_|!:,.;()]*)*)(\s|$)/ig + +/** + * @param {any} content - + */ +export function plainToRich(content) { + return this.formatLinksRich(content) +} + +/** + * @param {any} content - + */ +export function richToPlain(content) { + return this.formatLinksPlain(content) +} + +/** + * @param {any} content - + */ +export function formatLinksRich(content) { + return content.replace(urlRegex, function(_, leadingSpace, protocol, url, trailingSpace) { + let linkText = url + if (!protocol) { + protocol = 'https://' + } else if (protocol === 'http://') { + linkText = protocol + url + } + + return leadingSpace + '<a class="external" target="_blank" rel="noopener noreferrer" href="' + protocol + url + '">' + linkText + '</a>' + trailingSpace + }) +} + +/** + * @param {any} content - + */ +export function formatLinksPlain(content) { + const $content = $('<div></div>').html(content) + $content.find('a').each(function() { + const $this = $(this) + $this.html($this.attr('href')) + }) + return $content.html() +} diff --git a/core/src/OCP/index.js b/core/src/OCP/index.js new file mode 100644 index 00000000000..94f4e8e5eb3 --- /dev/null +++ b/core/src/OCP/index.js @@ -0,0 +1,35 @@ +/** + * SPDX-FileCopyrightText: 2016 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +import { loadState } from '@nextcloud/initial-state' + +import * as AppConfig from './appconfig.js' +import * as Comments from './comments.js' +import * as WhatsNew from './whatsnew.js' + +import Accessibility from './accessibility.js' +import Collaboration from './collaboration.js' +import Loader from './loader.js' +import Toast from './toast.js' + +/** @namespace OCP */ +export default { + Accessibility, + AppConfig, + Collaboration, + Comments, + InitialState: { + /** + * @deprecated 18.0.0 add https://www.npmjs.com/package/@nextcloud/initial-state to your app + */ + loadState, + }, + Loader, + /** + * @deprecated 19.0.0 use the `@nextcloud/dialogs` package instead + */ + Toast, + WhatsNew, +} diff --git a/core/src/OCP/loader.js b/core/src/OCP/loader.js new file mode 100644 index 00000000000..d307eb27996 --- /dev/null +++ b/core/src/OCP/loader.js @@ -0,0 +1,64 @@ +/** + * SPDX-FileCopyrightText: 2018 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +import { generateFilePath } from '@nextcloud/router' + +const loadedScripts = {} +const loadedStylesheets = {} +/** + * @namespace OCP + * @class Loader + */ +export default { + + /** + * Load a script asynchronously + * + * @param {string} app the app name + * @param {string} file the script file name + * @return {Promise} + */ + loadScript(app, file) { + const key = app + file + if (Object.prototype.hasOwnProperty.call(loadedScripts, key)) { + return Promise.resolve() + } + loadedScripts[key] = true + return new Promise(function(resolve, reject) { + const scriptPath = generateFilePath(app, 'js', file) + const script = document.createElement('script') + script.src = scriptPath + script.setAttribute('nonce', btoa(OC.requestToken)) + script.onload = () => resolve() + script.onerror = () => reject(new Error(`Failed to load script from ${scriptPath}`)) + document.head.appendChild(script) + }) + }, + + /** + * Load a stylesheet file asynchronously + * + * @param {string} app the app name + * @param {string} file the script file name + * @return {Promise} + */ + loadStylesheet(app, file) { + const key = app + file + if (Object.prototype.hasOwnProperty.call(loadedStylesheets, key)) { + return Promise.resolve() + } + loadedStylesheets[key] = true + return new Promise(function(resolve, reject) { + const stylePath = generateFilePath(app, 'css', file) + const link = document.createElement('link') + link.href = stylePath + link.type = 'text/css' + link.rel = 'stylesheet' + link.onload = () => resolve() + link.onerror = () => reject(new Error(`Failed to load stylesheet from ${stylePath}`)) + document.head.appendChild(link) + }) + }, +} diff --git a/core/src/OCP/toast.js b/core/src/OCP/toast.js new file mode 100644 index 00000000000..f93344bbc8e --- /dev/null +++ b/core/src/OCP/toast.js @@ -0,0 +1,67 @@ +/** + * SPDX-FileCopyrightText: 2019 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +import { + showError, + showInfo, showMessage, + showSuccess, + showWarning, +} from '@nextcloud/dialogs' + +/** @typedef {import('toastify-js')} Toast */ + +export default { + /** + * @deprecated 19.0.0 use `showSuccess` from the `@nextcloud/dialogs` package instead + * + * @param {string} text the toast text + * @param {object} options options + * @return {Toast} + */ + success(text, options) { + return showSuccess(text, options) + }, + /** + * @deprecated 19.0.0 use `showWarning` from the `@nextcloud/dialogs` package instead + * + * @param {string} text the toast text + * @param {object} options options + * @return {Toast} + */ + warning(text, options) { + return showWarning(text, options) + }, + /** + * @deprecated 19.0.0 use `showError` from the `@nextcloud/dialogs` package instead + * + * @param {string} text the toast text + * @param {object} options options + * @return {Toast} + */ + error(text, options) { + return showError(text, options) + }, + /** + * @deprecated 19.0.0 use `showInfo` from the `@nextcloud/dialogs` package instead + * + * @param {string} text the toast text + * @param {object} options options + * @return {Toast} + */ + info(text, options) { + return showInfo(text, options) + }, + /** + * @deprecated 19.0.0 use `showMessage` from the `@nextcloud/dialogs` package instead + * + * @param {string} text the toast text + * @param {object} options options + * @return {Toast} + */ + message(text, options) { + return showMessage(text, options) + }, + +} diff --git a/core/src/OCP/whatsnew.js b/core/src/OCP/whatsnew.js new file mode 100644 index 00000000000..acada6a8383 --- /dev/null +++ b/core/src/OCP/whatsnew.js @@ -0,0 +1,151 @@ +/** + * SPDX-FileCopyrightText: 2017 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +import _ from 'underscore' +import $ from 'jquery' +import { generateOcsUrl } from '@nextcloud/router' + +/** + * @param {any} options - + */ +export function query(options) { + options = options || {} + const dismissOptions = options.dismiss || {} + $.ajax({ + type: 'GET', + url: options.url || generateOcsUrl('core/whatsnew?format=json'), + success: options.success || function(data, statusText, xhr) { + onQuerySuccess(data, statusText, xhr, dismissOptions) + }, + error: options.error || onQueryError, + }) +} + +/** + * @param {any} version - + * @param {any} options - + */ +export function dismiss(version, options) { + options = options || {} + $.ajax({ + type: 'POST', + url: options.url || generateOcsUrl('core/whatsnew'), + data: { version: encodeURIComponent(version) }, + success: options.success || onDismissSuccess, + error: options.error || onDismissError, + }) + // remove element immediately + $('.whatsNewPopover').remove() +} + +/** + * @param {any} data - + * @param {any} statusText - + * @param {any} xhr - + * @param {any} dismissOptions - + */ +function onQuerySuccess(data, statusText, xhr, dismissOptions) { + console.debug('querying Whats New data was successful: ' + statusText) + console.debug(data) + + if (xhr.status !== 200) { + return + } + + let item, menuItem, text, icon + + const div = document.createElement('div') + div.classList.add('popovermenu', 'open', 'whatsNewPopover', 'menu-left') + + const list = document.createElement('ul') + + // header + item = document.createElement('li') + menuItem = document.createElement('span') + menuItem.className = 'menuitem' + + text = document.createElement('span') + text.innerText = t('core', 'New in') + ' ' + data.ocs.data.product + text.className = 'caption' + menuItem.appendChild(text) + + icon = document.createElement('span') + icon.className = 'icon-close' + icon.onclick = function() { + dismiss(data.ocs.data.version, dismissOptions) + } + menuItem.appendChild(icon) + + item.appendChild(menuItem) + list.appendChild(item) + + // Highlights + for (const i in data.ocs.data.whatsNew.regular) { + const whatsNewTextItem = data.ocs.data.whatsNew.regular[i] + item = document.createElement('li') + + menuItem = document.createElement('span') + menuItem.className = 'menuitem' + + icon = document.createElement('span') + icon.className = 'icon-checkmark' + menuItem.appendChild(icon) + + text = document.createElement('p') + text.innerHTML = _.escape(whatsNewTextItem) + menuItem.appendChild(text) + + item.appendChild(menuItem) + list.appendChild(item) + } + + // Changelog URL + if (!_.isUndefined(data.ocs.data.changelogURL)) { + item = document.createElement('li') + + menuItem = document.createElement('a') + menuItem.href = data.ocs.data.changelogURL + menuItem.rel = 'noreferrer noopener' + menuItem.target = '_blank' + + icon = document.createElement('span') + icon.className = 'icon-link' + menuItem.appendChild(icon) + + text = document.createElement('span') + text.innerText = t('core', 'View changelog') + menuItem.appendChild(text) + + item.appendChild(menuItem) + list.appendChild(item) + } + + div.appendChild(list) + document.body.appendChild(div) +} + +/** + * @param {any} x - + * @param {any} t - + * @param {any} e - + */ +function onQueryError(x, t, e) { + console.debug('querying Whats New Data resulted in an error: ' + t + e) + console.debug(x) +} + +/** + * @param {any} data - + */ +function onDismissSuccess(data) { + // noop +} + +/** + * @param {any} data - + */ +function onDismissError(data) { + console.debug('dismissing Whats New data resulted in an error: ' + data) +} |