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'))