From ba20878704c2b7ec4cc42c9dc90c435595974586 Mon Sep 17 00:00:00 2001 From: Ferdinand Thiessen Date: Wed, 15 Feb 2023 02:35:07 +0100 Subject: [PATCH 001/206] fix: High contrast mode optimization Signed-off-by: Ferdinand Thiessen --- src/components/NcAppNavigation/NcAppNavigation.vue | 7 +++++++ .../NcAppNavigationItem/NcAppNavigationItem.vue | 8 ++++++++ src/components/NcListItem/NcListItem.vue | 13 +++++++++++++ 3 files changed, 28 insertions(+) diff --git a/src/components/NcAppNavigation/NcAppNavigation.vue b/src/components/NcAppNavigation/NcAppNavigation.vue index 751b16e832..4027dada15 100644 --- a/src/components/NcAppNavigation/NcAppNavigation.vue +++ b/src/components/NcAppNavigation/NcAppNavigation.vue @@ -185,6 +185,13 @@ export default { } } +// add extra border for high contrast mode +[data-themes*="highcontrast"] { + .app-navigation { + border-right: 1px solid var(--color-border); + } +} + // When on mobile, we make the navigation slide over the appcontent @media only screen and (max-width: $breakpoint-mobile) { .app-navigation:not(.app-navigation--close) { diff --git a/src/components/NcAppNavigationItem/NcAppNavigationItem.vue b/src/components/NcAppNavigationItem/NcAppNavigationItem.vue index e5840da78e..d5c910a90e 100644 --- a/src/components/NcAppNavigationItem/NcAppNavigationItem.vue +++ b/src/components/NcAppNavigationItem/NcAppNavigationItem.vue @@ -950,4 +950,12 @@ export default { } } +// Add more contrast for active entry +[data-themes*="highcontrast"] { + .app-navigation-entry { + &:active { + background-color: var(--color-primary-light-hover) !important; + } + } +} diff --git a/src/components/NcListItem/NcListItem.vue b/src/components/NcListItem/NcListItem.vue index f95e8e2757..79a943a018 100644 --- a/src/components/NcListItem/NcListItem.vue +++ b/src/components/NcListItem/NcListItem.vue @@ -661,6 +661,19 @@ export default { } } +// Add more contrast for active entry +[data-themes*="highcontrast"] { + .list-item__wrapper { + &--active, + &:active, + &.active { + .list-item { + background-color: var(--color-primary-light-hover); + } + } + } +} + .line-one { display: flex; align-items: center; From c396fbcdd090fa3b301be1b6a032657ca187d2a0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Raimund=20Schl=C3=BC=C3=9Fler?= Date: Thu, 23 Feb 2023 16:40:02 +0100 Subject: [PATCH 002/206] Make VNodes component non-functional MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Raimund Schlüßler --- src/components/NcVNodes/NcVNodes.vue | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/src/components/NcVNodes/NcVNodes.vue b/src/components/NcVNodes/NcVNodes.vue index bc2262494c..fa27f7c631 100644 --- a/src/components/NcVNodes/NcVNodes.vue +++ b/src/components/NcVNodes/NcVNodes.vue @@ -1,5 +1,5 @@ - - - - -
- - + +
+ + + + + +
+ + +
+ + {{ nameTitleFallback }} + +
+ +
+
+ + + +
+
+ {{ nameTitleFallback }} +
- - {{ nameTitleFallback }} - -
- -
- - - - -
-
- {{ nameTitleFallback }} -
-
- -
-
- -
- - - + +
+
+ +
+ - {{ editLabel }} - - - - - - -
+ + + {{ editLabel }} + + + + + +
+
- - - + + +
+
    @@ -315,6 +320,7 @@ import { directive as ClickOutside } from 'v-click-outside' import NcActions from '../NcActions/index.js' import NcActionButton from '../NcActionButton/index.js' import NcLoadingIcon from '../NcLoadingIcon/index.js' +import NcVNodes from '../NcVNodes/index.js' import NcAppNavigationIconCollapsible from './NcAppNavigationIconCollapsible.vue' import isMobile from '../../mixins/isMobile/index.js' import NcInputConfirmCancel from './NcInputConfirmCancel.vue' @@ -330,9 +336,10 @@ export default { components: { NcActions, NcActionButton, - NcLoadingIcon, NcAppNavigationIconCollapsible, NcInputConfirmCancel, + NcLoadingIcon, + NcVNodes, Pencil, Undo, }, @@ -399,7 +406,7 @@ export default { */ to: { type: [String, Object], - default: '', + default: null, }, /** @@ -570,6 +577,10 @@ export default { return this.name }, + isRouterLink() { + return this.to && !this.href + }, + collapsible() { return this.allowCollapse && !!this.$slots.default }, @@ -598,25 +609,6 @@ export default { return false }, - // This is used to decide which outer element type to use - navElement() { - if (this.to && !this.href) { - return { - is: 'router-link', - tag: 'div', - to: this.to, - exact: this.exact, - } - } - return { - is: 'div', - } - }, - - isActive() { - return this.to && this.$route === this.to - }, - editButtonAriaLabel() { return this.editLabel ? this.editLabel : t('Edit item') }, diff --git a/src/components/NcVNodes/NcVNodes.vue b/src/components/NcVNodes/NcVNodes.vue index fa27f7c631..bbdb40c2f5 100644 --- a/src/components/NcVNodes/NcVNodes.vue +++ b/src/components/NcVNodes/NcVNodes.vue @@ -29,7 +29,7 @@ export default { */ vnodes: { type: [Array, Object], - default: () => [], + default: null, }, }, /** @@ -39,7 +39,7 @@ export default { * @return {object} The created VNode */ render(h) { - return this.vnodes + return this.vnodes || this.$slots?.default || this.$scopedSlots?.default?.() }, } From 7b3f763441cc8f2b665814d9bead976f3e296471 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Raimund=20Schl=C3=BC=C3=9Fler?= Date: Fri, 24 Feb 2023 17:07:43 +0100 Subject: [PATCH 004/206] Use router-link slot in NcListItem MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Raimund Schlüßler --- src/components/NcListItem/NcListItem.vue | 180 +++++++++++------------ 1 file changed, 85 insertions(+), 95 deletions(-) diff --git a/src/components/NcListItem/NcListItem.vue b/src/components/NcListItem/NcListItem.vue index 8f3a51e4e6..5fe0910eb8 100644 --- a/src/components/NcListItem/NcListItem.vue +++ b/src/components/NcListItem/NcListItem.vue @@ -199,71 +199,88 @@ + diff --git a/src/components/NcRichText/NcReferencePicker/NcCustomPickerElement.vue b/src/components/NcRichText/NcReferencePicker/NcCustomPickerElement.vue new file mode 100644 index 0000000000..89e5e9e2e8 --- /dev/null +++ b/src/components/NcRichText/NcReferencePicker/NcCustomPickerElement.vue @@ -0,0 +1,67 @@ + + + + + diff --git a/src/components/NcRichText/NcReferencePicker/NcProviderList.vue b/src/components/NcRichText/NcReferencePicker/NcProviderList.vue new file mode 100644 index 0000000000..46e4323f29 --- /dev/null +++ b/src/components/NcRichText/NcReferencePicker/NcProviderList.vue @@ -0,0 +1,142 @@ + + + + + diff --git a/src/components/NcRichText/NcReferencePicker/NcRawLinkInput.vue b/src/components/NcRichText/NcReferencePicker/NcRawLinkInput.vue new file mode 100644 index 0000000000..44f9af28e6 --- /dev/null +++ b/src/components/NcRichText/NcReferencePicker/NcRawLinkInput.vue @@ -0,0 +1,155 @@ + + + + + diff --git a/src/components/NcRichText/NcReferencePicker/NcReferencePicker.vue b/src/components/NcRichText/NcReferencePicker/NcReferencePicker.vue new file mode 100644 index 0000000000..e24ca743ca --- /dev/null +++ b/src/components/NcRichText/NcReferencePicker/NcReferencePicker.vue @@ -0,0 +1,175 @@ + + + + + diff --git a/src/components/NcRichText/NcReferencePicker/NcReferencePickerModal.vue b/src/components/NcRichText/NcReferencePicker/NcReferencePickerModal.vue new file mode 100644 index 0000000000..e18803d669 --- /dev/null +++ b/src/components/NcRichText/NcReferencePicker/NcReferencePickerModal.vue @@ -0,0 +1,179 @@ + + + + + + + diff --git a/src/components/NcRichText/NcReferencePicker/NcSearch.vue b/src/components/NcRichText/NcReferencePicker/NcSearch.vue new file mode 100644 index 0000000000..3791d20a69 --- /dev/null +++ b/src/components/NcRichText/NcReferencePicker/NcSearch.vue @@ -0,0 +1,327 @@ + + + + + diff --git a/src/components/NcRichText/NcReferencePicker/NcSearchResult.vue b/src/components/NcRichText/NcReferencePicker/NcSearchResult.vue new file mode 100644 index 0000000000..91a680ae8c --- /dev/null +++ b/src/components/NcRichText/NcReferencePicker/NcSearchResult.vue @@ -0,0 +1,77 @@ + + + + + diff --git a/src/components/NcRichText/NcReferencePicker/customPickerElements.js b/src/components/NcRichText/NcReferencePicker/customPickerElements.js new file mode 100644 index 0000000000..2b40a6d538 --- /dev/null +++ b/src/components/NcRichText/NcReferencePicker/customPickerElements.js @@ -0,0 +1,64 @@ +if (!window._vue_richtext_custom_picker_elements) { + window._vue_richtext_custom_picker_elements = {} +} + +/** + * Representation of the render callback result + * It contains a dom element and an object (Vue instance or other fancy things) + */ +class NcCustomPickerRenderResult { + + /** + * @param {HTMLElement} element + * @param {object} object + */ + constructor(element, object) { + this.element = element + this.object = object + } + +} + +const isCustomPickerElementRegistered = (id) => { + return !!window._vue_richtext_custom_picker_elements[id] +} + +const registerCustomPickerElement = (id, callback, onDestroy = (el) => {}) => { + if (window._vue_richtext_custom_picker_elements[id]) { + console.error('Custom reference picker element for id ' + id + ' already registered') + return + } + + window._vue_richtext_custom_picker_elements[id] = { + id, + callback, + onDestroy, + } +} + +const renderCustomPickerElement = (el, { providerId, accessible }) => { + if (!window._vue_richtext_custom_picker_elements[providerId]) { + console.error('Custom reference picker element for reference provider ID ' + providerId + ' not registered') + return + } + + return window._vue_richtext_custom_picker_elements[providerId].callback(el, { providerId, accessible }) +} + +const destroyCustomPickerElement = (providerId, el, renderResult) => { + if (!window._vue_richtext_custom_picker_elements[providerId]) { + return + } + + window._vue_richtext_custom_picker_elements[providerId].onDestroy(el, renderResult) +} + +window._registerCustomPickerElement = registerCustomPickerElement + +export { + NcCustomPickerRenderResult, + registerCustomPickerElement, + renderCustomPickerElement, + destroyCustomPickerElement, + isCustomPickerElementRegistered, +} diff --git a/src/components/NcRichText/NcReferencePicker/providerHelper.js b/src/components/NcRichText/NcReferencePicker/providerHelper.js new file mode 100644 index 0000000000..f93a05b04a --- /dev/null +++ b/src/components/NcRichText/NcReferencePicker/providerHelper.js @@ -0,0 +1,124 @@ +import { isCustomPickerElementRegistered } from './customPickerElements.js' + +import axios from '@nextcloud/axios' +import { loadState } from '@nextcloud/initial-state' +import { generateOcsUrl, imagePath } from '@nextcloud/router' + +export const anyLinkProviderId = 'any-link' + +const anyLinkProvider = { + id: anyLinkProviderId, + // TODO translate + title: 'Any link', + icon_url: imagePath('core', 'filetypes/link.svg'), +} + +// only get the provider list once, even if functions of this file are imported multiple times by different apps/components +if (!window._vue_richtext_reference_providers) { + window._vue_richtext_reference_providers = loadState('core', 'reference-provider-list', []) +} + +// single timestamps object used by every entity in the page +if (!window._vue_richtext_reference_provider_timestamps) { + window._vue_richtext_reference_provider_timestamps = loadState('core', 'reference-provider-timestamps', {}) +} + +/** + * @param {string} providerId The provider ID + * @return {object} The provider object + */ +export function getProvider(providerId) { + if (providerId === anyLinkProviderId) { + return anyLinkProvider + } + return getProviders().find(p => p.id === providerId) +} + +/** + * @return {Array} Raw provider list as it was provided by the server + */ +export function getProviders() { + return window._vue_richtext_reference_providers.filter(p => { + // avoid providers with no associated search provider and no custom component registered + const keep = (!!p.search_providers_ids && p.search_providers_ids.length > 0) || isCustomPickerElementRegistered(p.id) + if (!keep) { + console.debug('[link picker]', p.id, 'reference provider is discoverable but does not have any related search provider or custom picker component registered') + } + return keep + }) +} + +/** + * Helper function to sort a list of providers according to 2 factors: + * - their "last used timestamp" + * - their "order" property (coming from the provider declaration in the server implementation) + * + * @param {Array} providerList list of provider objects + * @return {Array} the sorted provider list + */ +export function sortProviders(providerList) { + const timestamps = window._vue_richtext_reference_provider_timestamps + + return providerList.sort((a, b) => { + return a.order === b.order + ? 0 + : a.order > b.order + ? 1 + : -1 + }).sort((a, b) => { + const ta = timestamps[a.id] + const tb = timestamps[b.id] + return ta === tb + ? 0 + : tb === undefined + ? -1 + : ta === undefined + ? 1 + : ta > tb + ? -1 + : 1 + }) +} + +/** + * Helper function to search a provider from a search query + * Result is a sorted list of providers + * + * @param {string} query + * @param {number} limit (optional) max number of results + * @return {Array} the sorted/filtered provider list + */ +export function searchProvider(query, limit = null) { + const providers = getProviders() + const escapedQuery = query.replace(/[/\-\\^$*+?.()|[\]{}]/g, '\\$&') + const regexp = new RegExp(escapedQuery, 'i') + const sortedProviders = sortProviders(providers) + const filteredSortedProviders = sortedProviders.filter(p => { + return p.title.match(regexp) + }) + const searchResult = limit + ? filteredSortedProviders.slice(0, limit) + : filteredSortedProviders + // append the 'any link' provider in the full list or when there is no result + if (query === '' || searchResult.length === 0) { + searchResult.push(anyLinkProvider) + } + return searchResult +} + +/** + * Update the "last used timestamp" on the server side and then locally in the frontend + * + * @param providerId + */ +export function touchProvider(providerId) { + const timestamp = Math.floor(Date.now() / 1000) + const params = { + timestamp, + } + const url = generateOcsUrl('references/provider/{providerId}', { providerId }) + axios.put(url, params) + .then((response) => { + window._vue_richtext_reference_provider_timestamps[providerId] = timestamp + }) +} diff --git a/src/components/NcRichText/NcReferencePicker/referencePickerModal.js b/src/components/NcRichText/NcReferencePicker/referencePickerModal.js new file mode 100644 index 0000000000..9e51315c12 --- /dev/null +++ b/src/components/NcRichText/NcReferencePicker/referencePickerModal.js @@ -0,0 +1,41 @@ +import NcReferencePickerModal from './NcReferencePickerModal.vue' +import { getProvider } from './providerHelper.js' + +import Vue from 'vue' + +/** + * Creates a reference picker modal and return a promise which provides the result + * + * @param {string} providerId Optional ID of initial selected provider + * @param {boolean} isInsideViewer Should be true if this function is called while the Viewer is displayed + * @return {Promise} + */ +export async function getLinkWithPicker(providerId = null, isInsideViewer = undefined) { + return await new Promise((resolve, reject) => { + const modalId = 'referencePickerModal' + const modalElement = document.createElement('div') + modalElement.id = modalId + document.body.append(modalElement) + + const initialProvider = providerId === null + ? null + : (getProvider(providerId) ?? null) + + const View = Vue.extend(NcReferencePickerModal) + const view = new View({ + propsData: { + initialProvider, + isInsideViewer, + }, + }).$mount(modalElement) + + view.$on('cancel', () => { + view.$destroy() + reject(new Error('User cancellation')) + }) + view.$on('submit', (link) => { + view.$destroy() + resolve(link) + }) + }) +} diff --git a/src/components/NcRichText/NcReferencePicker/utils.js b/src/components/NcRichText/NcReferencePicker/utils.js new file mode 100644 index 0000000000..19dd8fa0fa --- /dev/null +++ b/src/components/NcRichText/NcReferencePicker/utils.js @@ -0,0 +1,27 @@ +let mytimer = 0 +/** + * + * @param callback + * @param ms + */ +export function delay(callback, ms) { + return function() { + const context = this + const args = arguments + clearTimeout(mytimer) + mytimer = setTimeout(function() { + callback.apply(context, args) + }, ms || 0) + } +} +/** + * + * @param str + */ +export function isUrl(str) { + try { + return Boolean(new URL(str)) + } catch (error) { + return false + } +} diff --git a/src/components/NcRichText/NcReferenceWidget.vue b/src/components/NcRichText/NcReferenceWidget.vue new file mode 100644 index 0000000000..6527d19596 --- /dev/null +++ b/src/components/NcRichText/NcReferenceWidget.vue @@ -0,0 +1,194 @@ + + + diff --git a/src/components/NcRichText/NcRichText.vue b/src/components/NcRichText/NcRichText.vue new file mode 100644 index 0000000000..9b7ceea601 --- /dev/null +++ b/src/components/NcRichText/NcRichText.vue @@ -0,0 +1,244 @@ + + +```vue +