diff --git a/apps/systemtags/appinfo/info.xml b/apps/systemtags/appinfo/info.xml index bfc33c6ff661a..e2e84cce1c8f9 100644 --- a/apps/systemtags/appinfo/info.xml +++ b/apps/systemtags/appinfo/info.xml @@ -11,7 +11,7 @@ Collaborative tagging functionality which shares tags among people. Collaborative tagging functionality which shares tags among people. Great for teams. (If you are a provider with a multi-tenancy installation, it is advised to deactivate this app as tags are shared.) - 1.21.0 + 1.21.1 agpl Vincent Petry Joas Schilling diff --git a/apps/systemtags/src/components/SystemTagPicker.vue b/apps/systemtags/src/components/SystemTagPicker.vue index 86d81fb45a6b4..affbbf473df59 100644 --- a/apps/systemtags/src/components/SystemTagPicker.vue +++ b/apps/systemtags/src/components/SystemTagPicker.vue @@ -49,12 +49,13 @@ @@ -138,6 +139,7 @@ import NcNoteCard from '@nextcloud/vue/dist/Components/NcNoteCard.js' import NcTextField from '@nextcloud/vue/dist/Components/NcTextField.js' import CheckIcon from 'vue-material-design-icons/CheckCircle.vue' import CircleIcon from 'vue-material-design-icons/Circle.vue' +import CircleOutlineIcon from 'vue-material-design-icons/CircleOutline.vue' import PencilIcon from 'vue-material-design-icons/Pencil.vue' import PlusIcon from 'vue-material-design-icons/Plus.vue' import TagIcon from 'vue-material-design-icons/Tag.vue' @@ -147,9 +149,6 @@ import { getNodeSystemTags, setNodeSystemTags } from '../utils' import { elementColor, invertTextColor, isDarkModeEnabled } from '../utils/colorUtils' import logger from '../services/logger' -const primaryColor = getComputedStyle(document.body) - .getPropertyValue('--color-primary-element') - .replace('#', '') || '0069c3' const mainBackgroundColor = getComputedStyle(document.body) .getPropertyValue('--color-main-background') .replace('#', '') || (isDarkModeEnabled() ? '000000' : 'ffffff') @@ -171,6 +170,7 @@ export default defineComponent({ components: { CheckIcon, CircleIcon, + CircleOutlineIcon, NcButton, NcCheckboxRadioSwitch, // eslint-disable-next-line vue/no-unused-components @@ -196,7 +196,6 @@ export default defineComponent({ setup() { return { emit, - primaryColor, Status, t, } @@ -529,9 +528,20 @@ export default defineComponent({ }, tagListStyle(tag: TagWithId): Record { - const primaryElement = elementColor(`#${tag.color || primaryColor}`, `#${mainBackgroundColor}`) + // No color, no style + if (!tag.color) { + return { + // See inline system tag color + '--color-circle-icon': 'var(--color-text-maxcontrast)', + } + } + + // Make the checkbox color the same as the tag color + // as well as the circle icon color picker + const primaryElement = elementColor(`#${tag.color}`, `#${mainBackgroundColor}`) const textColor = invertTextColor(primaryElement) ? '#000000' : '#ffffff' return { + '--color-circle-icon': 'var(--color-primary-element)', '--color-primary': primaryElement, '--color-primary-text': textColor, '--color-primary-element': primaryElement, @@ -587,7 +597,6 @@ export default defineComponent({ .systemtags-picker__tag-color button { margin-inline-start: calc(var(--default-grid-baseline) * 2); - color: var(--color-primary-element); span.pencil-icon { display: none; @@ -600,7 +609,8 @@ export default defineComponent({ .pencil-icon { display: block; } - .circle-icon { + .circle-icon, + .circle-outline-icon { display: none; } } diff --git a/apps/systemtags/src/css/fileEntryInlineSystemTags.scss b/apps/systemtags/src/css/fileEntryInlineSystemTags.scss index 4cf72ed429fb8..d6b6d3a28bdfc 100644 --- a/apps/systemtags/src/css/fileEntryInlineSystemTags.scss +++ b/apps/systemtags/src/css/fileEntryInlineSystemTags.scss @@ -22,7 +22,7 @@ white-space: nowrap; overflow: hidden; text-overflow: ellipsis; - line-height: 22px; // min-size - 2 * 5px padding + line-height: 20px; // min-size - 2 * 5px padding - 2 * 1px border text-align: center; &--more { @@ -34,6 +34,14 @@ & + .files-list__system-tag { margin-inline-start: 5px; } + + // With color + &[data-systemtag-color] { + border-color: var(--systemtag-color); + color: var(--systemtag-color); + border-width: 2px; + line-height: 18px; // min-size - 2 * 5px padding - 2 * 2px border + } } @media (min-width: 512px) { diff --git a/apps/systemtags/src/event-bus.d.ts b/apps/systemtags/src/event-bus.d.ts index 4009f3f372b5a..f17be3dca5393 100644 --- a/apps/systemtags/src/event-bus.d.ts +++ b/apps/systemtags/src/event-bus.d.ts @@ -3,10 +3,14 @@ * SPDX-License-Identifier: AGPL-3.0-or-later */ import type { Node } from '@nextcloud/files' +import type { TagWithId } from './types' declare module '@nextcloud/event-bus' { interface NextcloudEvents { 'systemtags:node:updated': Node + 'systemtags:tag:deleted': TagWithId + 'systemtags:tag:updated': TagWithId + 'systemtags:tag:created': TagWithId } } diff --git a/apps/systemtags/src/files_actions/inlineSystemTagsAction.ts b/apps/systemtags/src/files_actions/inlineSystemTagsAction.ts index afd54a4f7dee0..74e8fcd4d5ac9 100644 --- a/apps/systemtags/src/files_actions/inlineSystemTagsAction.ts +++ b/apps/systemtags/src/files_actions/inlineSystemTagsAction.ts @@ -3,18 +3,40 @@ * SPDX-License-Identifier: AGPL-3.0-or-later */ import type { Node } from '@nextcloud/files' +import type { TagWithId } from '../types' import { FileAction } from '@nextcloud/files' import { subscribe } from '@nextcloud/event-bus' import { t } from '@nextcloud/l10n' import '../css/fileEntryInlineSystemTags.scss' +import { elementColor, isDarkModeEnabled } from '../utils/colorUtils' +import { fetchTags } from '../services/api' import { getNodeSystemTags } from '../utils' +// Init tag cache +const tags: TagWithId[] = [] +fetchTags().then((fetchedTags) => { + tags.push(...fetchedTags) +}) + const renderTag = function(tag: string, isMore = false): HTMLElement { const tagElement = document.createElement('li') tagElement.classList.add('files-list__system-tag') + tagElement.setAttribute('data-systemtag-name', tag) tagElement.textContent = tag + // Set the color if it exists + const cachedTag = tags.find((t) => t.displayName === tag) + if (cachedTag?.color) { + // Make sure contrast is good and follow WCAG guidelines + const mainBackgroundColor = getComputedStyle(document.body) + .getPropertyValue('--color-main-background') + .replace('#', '') || (isDarkModeEnabled() ? '000000' : 'ffffff') + const primaryElement = elementColor(`#${cachedTag.color}`, `#${mainBackgroundColor}`) + tagElement.style.setProperty('--systemtag-color', primaryElement) + tagElement.setAttribute('data-systemtag-color', 'true') + } + if (isMore) { tagElement.classList.add('files-list__system-tag--more') } @@ -84,6 +106,7 @@ export const action = new FileAction({ order: 0, }) +// Update the system tags html when the node is updated const updateSystemTagsHtml = function(node: Node) { renderInline(node).then((systemTagsHtml) => { document.querySelectorAll(`[data-systemtags-fileid="${node.fileid}"]`).forEach((element) => { @@ -92,4 +115,29 @@ const updateSystemTagsHtml = function(node: Node) { }) } +// Add and remove tags from the cache +const addTag = function(tag: TagWithId) { + tags.push(tag) +} +const removeTag = function(tag: TagWithId) { + tags.splice(tags.findIndex((t) => t.id === tag.id), 1) +} +const updateTag = function(tag: TagWithId) { + const index = tags.findIndex((t) => t.id === tag.id) + if (index !== -1) { + tags[index] = tag + } + updateSystemTagsColorAttribute(tag) +} +// Update the color attribute of the system tags +const updateSystemTagsColorAttribute = function(tag: TagWithId) { + document.querySelectorAll(`[data-systemtag-name="${tag.displayName}"]`).forEach((element) => { + (element as HTMLElement).style.setProperty('--systemtag-color', `#${tag.color}`) + }) +} + +// Subscribe to the events subscribe('systemtags:node:updated', updateSystemTagsHtml) +subscribe('systemtags:tag:created', addTag) +subscribe('systemtags:tag:deleted', removeTag) +subscribe('systemtags:tag:updated', updateTag) diff --git a/apps/systemtags/src/services/api.ts b/apps/systemtags/src/services/api.ts index b98bfcb47cff3..ae99b2292b6f2 100644 --- a/apps/systemtags/src/services/api.ts +++ b/apps/systemtags/src/services/api.ts @@ -13,6 +13,7 @@ import { t } from '@nextcloud/l10n' import { davClient } from './davClient.js' import { formatTag, parseIdFromLocation, parseTags } from '../utils' import { logger } from '../logger.js' +import { emit } from '@nextcloud/event-bus' export const fetchTagsPayload = ` @@ -82,6 +83,7 @@ export const createTag = async (tag: Tag | ServerTag): Promise => { }) const contentLocation = headers.get('content-location') if (contentLocation) { + emit('systemtags:tag:created', tag) return parseIdFromLocation(contentLocation) } logger.error(t('systemtags', 'Missing "Content-Location" header')) @@ -115,6 +117,7 @@ export const updateTag = async (tag: TagWithId): Promise => { method: 'PROPPATCH', data, }) + emit('systemtags:tag:updated', tag) } catch (error) { logger.error(t('systemtags', 'Failed to update tag'), { error }) throw new Error(t('systemtags', 'Failed to update tag')) @@ -125,6 +128,7 @@ export const deleteTag = async (tag: TagWithId): Promise => { const path = '/systemtags/' + tag.id try { await davClient.deleteFile(path) + emit('systemtags:tag:deleted', tag) } catch (error) { logger.error(t('systemtags', 'Failed to delete tag'), { error }) throw new Error(t('systemtags', 'Failed to delete tag'))