mirror of
https://github.com/nextcloud/server.git
synced 2026-04-28 09:37:29 -04:00
feat(systemtags): add colors in bulk tagging action
Signed-off-by: skjnldsv <skjnldsv@protonmail.com>
This commit is contained in:
parent
adf8a454dd
commit
cb472bebfe
11 changed files with 487 additions and 320 deletions
|
|
@ -11,7 +11,7 @@
|
|||
<summary>Collaborative tagging functionality which shares tags among people.</summary>
|
||||
<description>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.)</description>
|
||||
<version>1.21.0</version>
|
||||
<version>1.21.1</version>
|
||||
<licence>agpl</licence>
|
||||
<author>Vincent Petry</author>
|
||||
<author>Joas Schilling</author>
|
||||
|
|
|
|||
|
|
@ -31,34 +31,57 @@
|
|||
</div>
|
||||
|
||||
<!-- Tags list -->
|
||||
<div class="systemtags-picker__tags"
|
||||
<ul class="systemtags-picker__tags"
|
||||
data-cy-systemtags-picker-tags>
|
||||
<NcCheckboxRadioSwitch v-for="tag in filteredTags"
|
||||
<li v-for="tag in filteredTags"
|
||||
:key="tag.id"
|
||||
:label="tag.displayName"
|
||||
:checked="isChecked(tag)"
|
||||
:indeterminate="isIndeterminate(tag)"
|
||||
:disabled="!tag.canAssign"
|
||||
:data-cy-systemtags-picker-tag="tag.id"
|
||||
class="systemtags-picker__tag"
|
||||
@update:checked="onCheckUpdate(tag, $event)">
|
||||
{{ formatTagName(tag) }}
|
||||
</NcCheckboxRadioSwitch>
|
||||
<NcButton v-if="canCreateTag"
|
||||
:disabled="status === Status.CREATING_TAG"
|
||||
alignment="start"
|
||||
class="systemtags-picker__tag-create"
|
||||
native-type="submit"
|
||||
type="tertiary"
|
||||
data-cy-systemtags-picker-button-create
|
||||
@click="onNewTag">
|
||||
{{ input.trim() }}<br>
|
||||
<span class="systemtags-picker__tag-create-subline">{{ t('systemtags', 'Create new tag') }}</span>
|
||||
<template #icon>
|
||||
<PlusIcon />
|
||||
</template>
|
||||
</NcButton>
|
||||
</div>
|
||||
:style="tagListStyle(tag)"
|
||||
class="systemtags-picker__tag">
|
||||
<NcCheckboxRadioSwitch :checked="isChecked(tag)"
|
||||
:disabled="!tag.canAssign"
|
||||
:indeterminate="isIndeterminate(tag)"
|
||||
:label="tag.displayName"
|
||||
class="systemtags-picker__tag-checkbox"
|
||||
@update:checked="onCheckUpdate(tag, $event)">
|
||||
{{ formatTagName(tag) }}
|
||||
</NcCheckboxRadioSwitch>
|
||||
|
||||
<!-- Color picker -->
|
||||
<NcColorPicker :data-cy-systemtags-picker-tag-color="tag.id"
|
||||
:value="`#${tag.color}`"
|
||||
:shown.sync="openedPicker"
|
||||
class="systemtags-picker__tag-color"
|
||||
@update:value="onColorChange(tag, $event)"
|
||||
@submit="openedPicker = false">
|
||||
<NcButton :aria-label="t('systemtags', 'Change tag color')" type="tertiary">
|
||||
<template #icon>
|
||||
<CircleIcon v-if="tag.color" :size="24" fill-color="var(--color-circle-icon)" />
|
||||
<CircleOutlineIcon v-else :size="24" fill-color="var(--color-circle-icon)" />
|
||||
<PencilIcon />
|
||||
</template>
|
||||
</NcButton>
|
||||
</NcColorPicker>
|
||||
</li>
|
||||
|
||||
<!-- Create new tag -->
|
||||
<li>
|
||||
<NcButton v-if="canCreateTag"
|
||||
:disabled="status === Status.CREATING_TAG"
|
||||
alignment="start"
|
||||
class="systemtags-picker__tag-create"
|
||||
native-type="submit"
|
||||
type="tertiary"
|
||||
data-cy-systemtags-picker-button-create
|
||||
@click="onNewTag">
|
||||
{{ input.trim() }}<br>
|
||||
<span class="systemtags-picker__tag-create-subline">{{ t('systemtags', 'Create new tag') }}</span>
|
||||
<template #icon>
|
||||
<PlusIcon />
|
||||
</template>
|
||||
</NcButton>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<!-- Note -->
|
||||
<div class="systemtags-picker__note">
|
||||
|
|
@ -110,19 +133,28 @@ import escapeHTML from 'escape-html'
|
|||
import NcButton from '@nextcloud/vue/dist/Components/NcButton.js'
|
||||
import NcCheckboxRadioSwitch from '@nextcloud/vue/dist/Components/NcCheckboxRadioSwitch.js'
|
||||
import NcChip from '@nextcloud/vue/dist/Components/NcChip.js'
|
||||
import NcColorPicker from '@nextcloud/vue/dist/Components/NcColorPicker.js'
|
||||
import NcDialog from '@nextcloud/vue/dist/Components/NcDialog.js'
|
||||
import NcEmptyContent from '@nextcloud/vue/dist/Components/NcEmptyContent.js'
|
||||
import NcLoadingIcon from '@nextcloud/vue/dist/Components/NcLoadingIcon.js'
|
||||
import NcNoteCard from '@nextcloud/vue/dist/Components/NcNoteCard.js'
|
||||
import NcTextField from '@nextcloud/vue/dist/Components/NcTextField.js'
|
||||
import TagIcon from 'vue-material-design-icons/Tag.vue'
|
||||
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'
|
||||
|
||||
import { createTag, fetchTag, fetchTags, getTagObjects, setTagObjects, updateTag } from '../services/api'
|
||||
import { getNodeSystemTags, setNodeSystemTags } from '../utils'
|
||||
import { createTag, fetchTag, fetchTags, getTagObjects, setTagObjects } from '../services/api'
|
||||
import { elementColor, invertTextColor, isDarkModeEnabled } from '../utils/colorUtils'
|
||||
import logger from '../services/logger'
|
||||
|
||||
const mainBackgroundColor = getComputedStyle(document.body)
|
||||
.getPropertyValue('--color-main-background')
|
||||
.replace('#', '') || (isDarkModeEnabled() ? '000000' : 'ffffff')
|
||||
|
||||
type TagListCount = {
|
||||
string: number
|
||||
}
|
||||
|
|
@ -139,15 +171,19 @@ export default defineComponent({
|
|||
|
||||
components: {
|
||||
CheckIcon,
|
||||
CircleIcon,
|
||||
CircleOutlineIcon,
|
||||
NcButton,
|
||||
NcCheckboxRadioSwitch,
|
||||
// eslint-disable-next-line vue/no-unused-components
|
||||
NcChip,
|
||||
NcColorPicker,
|
||||
NcDialog,
|
||||
NcEmptyContent,
|
||||
NcLoadingIcon,
|
||||
NcNoteCard,
|
||||
NcTextField,
|
||||
PencilIcon,
|
||||
PlusIcon,
|
||||
TagIcon,
|
||||
},
|
||||
|
|
@ -171,6 +207,7 @@ export default defineComponent({
|
|||
return {
|
||||
status: Status.BASE,
|
||||
opened: true,
|
||||
openedPicker: false,
|
||||
|
||||
input: '',
|
||||
tags: [] as TagWithId[],
|
||||
|
|
@ -329,7 +366,14 @@ export default defineComponent({
|
|||
// Format & sanitize a tag chip for v-html tag rendering
|
||||
formatTagChip(tag: TagWithId): string {
|
||||
const chip = this.$refs.chip as NcChip
|
||||
const chipHtml = chip.$el.outerHTML
|
||||
const chipCloneEl = chip.$el.cloneNode(true) as HTMLElement
|
||||
if (tag.color) {
|
||||
const style = this.tagListStyle(tag)
|
||||
Object.entries(style).forEach(([key, value]) => {
|
||||
chipCloneEl.style.setProperty(key, value)
|
||||
})
|
||||
}
|
||||
const chipHtml = chipCloneEl.outerHTML
|
||||
return chipHtml.replace('%s', escapeHTML(sanitize(tag.displayName)))
|
||||
},
|
||||
|
||||
|
|
@ -345,6 +389,11 @@ export default defineComponent({
|
|||
return tag.displayName
|
||||
},
|
||||
|
||||
onColorChange(tag: TagWithId, color: string) {
|
||||
tag.color = color.replace('#', '')
|
||||
updateTag(tag)
|
||||
},
|
||||
|
||||
isChecked(tag: TagWithId): boolean {
|
||||
return tag.displayName in this.tagList
|
||||
&& this.tagList[tag.displayName] === this.nodes.length
|
||||
|
|
@ -480,6 +529,28 @@ export default defineComponent({
|
|||
showInfo(t('systemtags', 'File tags modification canceled'))
|
||||
this.$emit('close', null)
|
||||
},
|
||||
|
||||
tagListStyle(tag: TagWithId): Record<string, string> {
|
||||
// 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,
|
||||
'--color-primary-element-text': textColor,
|
||||
}
|
||||
},
|
||||
},
|
||||
})
|
||||
</script>
|
||||
|
|
@ -506,6 +577,48 @@ export default defineComponent({
|
|||
gap: var(--default-grid-baseline);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
li {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
width: 100%;
|
||||
|
||||
// Make switch full width
|
||||
:deep(.checkbox-radio-switch) {
|
||||
width: 100%;
|
||||
|
||||
.checkbox-content {
|
||||
// adjust width
|
||||
max-width: none;
|
||||
// recalculate padding
|
||||
box-sizing: border-box;
|
||||
min-height: calc(var(--default-grid-baseline) * 2 + var(--default-clickable-area));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.systemtags-picker__tag-color button {
|
||||
margin-inline-start: calc(var(--default-grid-baseline) * 2);
|
||||
|
||||
span.pencil-icon {
|
||||
display: none;
|
||||
color: var(--color-main-text);
|
||||
}
|
||||
|
||||
&:focus,
|
||||
&:hover,
|
||||
&[aria-expanded='true'] {
|
||||
.pencil-icon {
|
||||
display: block;
|
||||
}
|
||||
.circle-icon,
|
||||
.circle-outline-icon {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.systemtags-picker__tag-create {
|
||||
:deep(span) {
|
||||
text-align: start;
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
4
apps/systemtags/src/event-bus.d.ts
vendored
4
apps/systemtags/src/event-bus.d.ts
vendored
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -3,10 +3,12 @@
|
|||
* SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
*/
|
||||
import { action } from './inlineSystemTagsAction'
|
||||
import { describe, expect, test } from 'vitest'
|
||||
import { File, Permission, View, FileAction } from '@nextcloud/files'
|
||||
import { beforeEach, describe, expect, test, vi } from 'vitest'
|
||||
import { emit, subscribe } from '@nextcloud/event-bus'
|
||||
import { File, Permission, View, FileAction } from '@nextcloud/files'
|
||||
import { setNodeSystemTags } from '../utils'
|
||||
import * as serviceTagApi from '../services/api'
|
||||
import { set } from 'lodash'
|
||||
|
||||
const view = {
|
||||
id: 'files',
|
||||
|
|
@ -53,6 +55,13 @@ describe('Inline system tags action conditions tests', () => {
|
|||
})
|
||||
|
||||
describe('Inline system tags action render tests', () => {
|
||||
|
||||
beforeEach(() => {
|
||||
vi.spyOn(serviceTagApi, 'fetchTags').mockImplementation(async () => {
|
||||
return []
|
||||
})
|
||||
})
|
||||
|
||||
test('Render something even when Node does not have system tags', async () => {
|
||||
const file = new File({
|
||||
id: 1,
|
||||
|
|
@ -165,7 +174,9 @@ describe('Inline system tags action render tests', () => {
|
|||
|
||||
// Subscribe to the event
|
||||
const eventPromise = new Promise((resolve) => {
|
||||
subscribe('systemtags:node:updated', resolve)
|
||||
subscribe('systemtags:node:updated', () => {
|
||||
setTimeout(resolve, 100)
|
||||
})
|
||||
})
|
||||
|
||||
// Change tags
|
||||
|
|
|
|||
|
|
@ -3,18 +3,38 @@
|
|||
* 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'
|
||||
import logger from '../services/logger'
|
||||
|
||||
// Init tag cache
|
||||
const cache: TagWithId[] = []
|
||||
|
||||
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 = cache.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')
|
||||
}
|
||||
|
|
@ -35,6 +55,17 @@ const renderInline = async function(node: Node): Promise<HTMLElement> {
|
|||
return systemTagsElement
|
||||
}
|
||||
|
||||
// Fetch the tags if the cache is empty
|
||||
if (cache.length === 0) {
|
||||
try {
|
||||
// Best would be to support attributes from webdav,
|
||||
// but currently the library does not support it
|
||||
cache.push(...await fetchTags())
|
||||
} catch (error) {
|
||||
logger.error('Failed to fetch tags', { error })
|
||||
}
|
||||
}
|
||||
|
||||
systemTagsElement.append(renderTag(tags[0]))
|
||||
if (tags.length === 2) {
|
||||
// Special case only two tags:
|
||||
|
|
@ -84,6 +115,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 +124,29 @@ const updateSystemTagsHtml = function(node: Node) {
|
|||
})
|
||||
}
|
||||
|
||||
// Add and remove tags from the cache
|
||||
const addTag = function(tag: TagWithId) {
|
||||
cache.push(tag)
|
||||
}
|
||||
const removeTag = function(tag: TagWithId) {
|
||||
cache.splice(cache.findIndex((t) => t.id === tag.id), 1)
|
||||
}
|
||||
const updateTag = function(tag: TagWithId) {
|
||||
const index = cache.findIndex((t) => t.id === tag.id)
|
||||
if (index !== -1) {
|
||||
cache[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)
|
||||
|
|
|
|||
|
|
@ -13,9 +13,10 @@ 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 = `<?xml version="1.0"?>
|
||||
<d:propfind xmlns:d="DAV:" xmlns:oc="http://owncloud.org/ns">
|
||||
<d:propfind xmlns:d="DAV:" xmlns:oc="http://owncloud.org/ns" xmlns:nc="http://nextcloud.org/ns">
|
||||
<d:prop>
|
||||
<oc:id />
|
||||
<oc:display-name />
|
||||
|
|
@ -23,6 +24,7 @@ export const fetchTagsPayload = `<?xml version="1.0"?>
|
|||
<oc:user-assignable />
|
||||
<oc:can-assign />
|
||||
<d:getetag />
|
||||
<nc:color />
|
||||
</d:prop>
|
||||
</d:propfind>`
|
||||
|
||||
|
|
@ -81,6 +83,7 @@ export const createTag = async (tag: Tag | ServerTag): Promise<number> => {
|
|||
})
|
||||
const contentLocation = headers.get('content-location')
|
||||
if (contentLocation) {
|
||||
emit('systemtags:tag:created', tag)
|
||||
return parseIdFromLocation(contentLocation)
|
||||
}
|
||||
logger.error(t('systemtags', 'Missing "Content-Location" header'))
|
||||
|
|
@ -98,12 +101,13 @@ export const createTag = async (tag: Tag | ServerTag): Promise<number> => {
|
|||
export const updateTag = async (tag: TagWithId): Promise<void> => {
|
||||
const path = '/systemtags/' + tag.id
|
||||
const data = `<?xml version="1.0"?>
|
||||
<d:propertyupdate xmlns:d="DAV:" xmlns:oc="http://owncloud.org/ns">
|
||||
<d:propertyupdate xmlns:d="DAV:" xmlns:oc="http://owncloud.org/ns" xmlns:nc="http://nextcloud.org/ns">
|
||||
<d:set>
|
||||
<d:prop>
|
||||
<oc:display-name>${tag.displayName}</oc:display-name>
|
||||
<oc:user-visible>${tag.userVisible}</oc:user-visible>
|
||||
<oc:user-assignable>${tag.userAssignable}</oc:user-assignable>
|
||||
<nc:color>${tag.color}</nc:color>
|
||||
</d:prop>
|
||||
</d:set>
|
||||
</d:propertyupdate>`
|
||||
|
|
@ -113,6 +117,7 @@ export const updateTag = async (tag: TagWithId): Promise<void> => {
|
|||
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'))
|
||||
|
|
@ -123,6 +128,7 @@ export const deleteTag = async (tag: TagWithId): Promise<void> => {
|
|||
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'))
|
||||
|
|
|
|||
|
|
@ -8,6 +8,8 @@ export interface BaseTag {
|
|||
userVisible: boolean
|
||||
userAssignable: boolean
|
||||
readonly canAssign: boolean // Computed server-side
|
||||
etag?: string
|
||||
color?: string
|
||||
}
|
||||
|
||||
export type Tag = BaseTag & {
|
||||
|
|
|
|||
193
apps/systemtags/src/utils/colorUtils.ts
Normal file
193
apps/systemtags/src/utils/colorUtils.ts
Normal file
|
|
@ -0,0 +1,193 @@
|
|||
/**
|
||||
* SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors
|
||||
* SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
*/
|
||||
import Color from 'color'
|
||||
|
||||
type hexColor = `#${string & (
|
||||
`${string}${string}${string}` |
|
||||
`${string}${string}${string}${string}${string}${string}`
|
||||
)}`;
|
||||
|
||||
/**
|
||||
* Is the current theme dark?
|
||||
*/
|
||||
export function isDarkModeEnabled() {
|
||||
const darkModePreference = window.matchMedia('(prefers-color-scheme: dark)').matches
|
||||
const darkModeSetting = document.body.getAttribute('data-themes')?.includes('dark')
|
||||
return darkModeSetting || darkModePreference || false
|
||||
}
|
||||
|
||||
/**
|
||||
* Is the current theme high contrast?
|
||||
*/
|
||||
export function isHighContrastModeEnabled() {
|
||||
const highContrastPreference = window.matchMedia('(forced-colors: active)').matches
|
||||
const highContrastSetting = document.body.getAttribute('data-themes')?.includes('highcontrast')
|
||||
return highContrastSetting || highContrastPreference || false
|
||||
}
|
||||
|
||||
/**
|
||||
* Should we invert the text on this background color?
|
||||
* @param color RGB color value as a hex string
|
||||
* @return boolean
|
||||
*/
|
||||
export function invertTextColor(color: hexColor): boolean {
|
||||
return colorContrast(color, '#ffffff') < 4.5
|
||||
}
|
||||
|
||||
/**
|
||||
* Is this color too bright?
|
||||
* @param color RGB color value as a hex string
|
||||
* @return boolean
|
||||
*/
|
||||
export function isBrightColor(color: hexColor): boolean {
|
||||
return calculateLuma(color) > 0.6
|
||||
}
|
||||
|
||||
/**
|
||||
* Get color for on-page elements
|
||||
* theme color by default, grey if theme color is too bright.
|
||||
* @param color the color to contrast against, e.g. #ffffff
|
||||
* @param backgroundColor the background color to contrast against, e.g. #000000
|
||||
*/
|
||||
export function elementColor(
|
||||
color: hexColor,
|
||||
backgroundColor: hexColor,
|
||||
): hexColor {
|
||||
const brightBackground = isBrightColor(backgroundColor)
|
||||
const blurredBackground = mix(
|
||||
backgroundColor,
|
||||
brightBackground ? color : '#ffffff',
|
||||
66,
|
||||
)
|
||||
|
||||
let contrast = colorContrast(color, blurredBackground)
|
||||
const minContrast = isHighContrastModeEnabled() ? 5.6 : 3.2
|
||||
|
||||
let iteration = 0
|
||||
let result = color
|
||||
const epsilon = 1.0 / 255.0
|
||||
while (contrast < minContrast && iteration++ < 100) {
|
||||
const hsl = hexToHSL(result)
|
||||
const l = Math.max(
|
||||
0,
|
||||
Math.min(255, hsl.l + (brightBackground ? -epsilon : epsilon)),
|
||||
)
|
||||
result = hslToHex({ h: hsl.h, s: hsl.s, l })
|
||||
contrast = colorContrast(result, blurredBackground)
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
/**
|
||||
* Get color for on-page text:
|
||||
* black if background is bright, white if background is dark.
|
||||
* @param color1 the color to contrast against, e.g. #ffffff
|
||||
* @param color2 the background color to contrast against, e.g. #000000
|
||||
* @param factor the factor to mix the colors between -100 and 100, e.g. 66
|
||||
*/
|
||||
export function mix(color1: hexColor, color2: hexColor, factor: number): hexColor {
|
||||
if (factor < -100 || factor > 100) {
|
||||
throw new RangeError('Factor must be between -100 and 100')
|
||||
}
|
||||
return new Color(color2).mix(new Color(color1), (factor + 100) / 200).hex()
|
||||
}
|
||||
|
||||
/**
|
||||
* Lighten a color by a factor
|
||||
* @param color the color to lighten, e.g. #000000
|
||||
* @param factor the factor to lighten the color by between -100 and 100, e.g. -41
|
||||
*/
|
||||
export function lighten(color: hexColor, factor: number): hexColor {
|
||||
if (factor < -100 || factor > 100) {
|
||||
throw new RangeError('Factor must be between -100 and 100')
|
||||
}
|
||||
return new Color(color).lighten((factor + 100) / 200).hex()
|
||||
}
|
||||
|
||||
/**
|
||||
* Darken a color by a factor
|
||||
* @param color the color to darken, e.g. #ffffff
|
||||
* @param factor the factor to darken the color by between -100 and 100, e.g. 32
|
||||
*/
|
||||
export function darken(color: hexColor, factor: number): hexColor {
|
||||
if (factor < -100 || factor > 100) {
|
||||
throw new RangeError('Factor must be between -100 and 100')
|
||||
}
|
||||
return new Color(color).darken((factor + 100) / 200).hex()
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate the luminance of a color
|
||||
* @param color the color to calculate the luminance of, e.g. #ffffff
|
||||
*/
|
||||
export function calculateLuminance(color: hexColor): number {
|
||||
return hexToHSL(color).l
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate the luma of a color
|
||||
* @param color the color to calculate the luma of, e.g. #ffffff
|
||||
*/
|
||||
export function calculateLuma(color: hexColor): number {
|
||||
const rgb = hexToRGB(color).map((value) => {
|
||||
value /= 255
|
||||
return value <= 0.03928
|
||||
? value / 12.92
|
||||
: Math.pow((value + 0.055) / 1.055, 2.4)
|
||||
})
|
||||
const [red, green, blue] = rgb
|
||||
return 0.2126 * red + 0.7152 * green + 0.0722 * blue
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate the contrast between two colors
|
||||
* @param color1 the first color to calculate the contrast of, e.g. #ffffff
|
||||
* @param color2 the second color to calculate the contrast of, e.g. #000000
|
||||
*/
|
||||
export function colorContrast(color1: hexColor, color2: hexColor): number {
|
||||
const luminance1 = calculateLuma(color1) + 0.05
|
||||
const luminance2 = calculateLuma(color2) + 0.05
|
||||
return Math.max(luminance1, luminance2) / Math.min(luminance1, luminance2)
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert hex color to RGB
|
||||
* @param color RGB color value as a hex string
|
||||
*/
|
||||
export function hexToRGB(color: hexColor): [number, number, number] {
|
||||
return new Color(color).rgb().array()
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert RGB color to hex
|
||||
* @param color RGB color value as a hex string
|
||||
*/
|
||||
export function hexToHSL(color: hexColor): { h: number; s: number; l: number } {
|
||||
const hsl = new Color(color).hsl()
|
||||
return { h: hsl.color[0], s: hsl.color[1], l: hsl.color[2] }
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert HSL color to hex
|
||||
* @param hsl HSL color value as an object
|
||||
* @param hsl.h hue
|
||||
* @param hsl.s saturation
|
||||
* @param hsl.l lightness
|
||||
*/
|
||||
export function hslToHex(hsl: { h: number; s: number; l: number }): hexColor {
|
||||
return new Color(hsl).hex()
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert RGB color to hex
|
||||
* @param r red
|
||||
* @param g green
|
||||
* @param b blue
|
||||
*/
|
||||
export function rgbToHex(r: number, g: number, b: number): hexColor {
|
||||
const hex = ((1 << 24) | (r << 16) | (g << 8) | b).toString(16).slice(1)
|
||||
return `#${hex}`
|
||||
}
|
||||
342
package-lock.json
generated
342
package-lock.json
generated
|
|
@ -42,6 +42,7 @@
|
|||
"camelcase": "^8.0.0",
|
||||
"cancelable-promise": "^4.3.1",
|
||||
"clipboard": "^2.0.11",
|
||||
"color": "^4.2.3",
|
||||
"core-js": "^3.38.1",
|
||||
"davclient.js": "github:owncloud/davclient.js.git#0.2.2",
|
||||
"debounce": "^2.1.0",
|
||||
|
|
@ -5378,26 +5379,6 @@
|
|||
"url": "https://github.com/chalk/chalk?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/@testing-library/dom/node_modules/color-convert": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
|
||||
"integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"color-name": "~1.1.4"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=7.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@testing-library/dom/node_modules/color-name": {
|
||||
"version": "1.1.4",
|
||||
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
|
||||
"integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@testing-library/dom/node_modules/has-flag": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
|
||||
|
|
@ -5471,26 +5452,6 @@
|
|||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/@testing-library/jest-dom/node_modules/color-convert": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
|
||||
"integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"color-name": "~1.1.4"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=7.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@testing-library/jest-dom/node_modules/color-name": {
|
||||
"version": "1.1.4",
|
||||
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
|
||||
"integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@testing-library/jest-dom/node_modules/dom-accessibility-api": {
|
||||
"version": "0.6.3",
|
||||
"resolved": "https://registry.npmjs.org/dom-accessibility-api/-/dom-accessibility-api-0.6.3.tgz",
|
||||
|
|
@ -5617,26 +5578,6 @@
|
|||
"url": "https://github.com/chalk/chalk?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/@testing-library/vue/node_modules/color-convert": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
|
||||
"integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"color-name": "~1.1.4"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=7.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@testing-library/vue/node_modules/color-name": {
|
||||
"version": "1.1.4",
|
||||
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
|
||||
"integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@testing-library/vue/node_modules/has-flag": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
|
||||
|
|
@ -9179,6 +9120,47 @@
|
|||
"node": ">=6"
|
||||
}
|
||||
},
|
||||
"node_modules/color": {
|
||||
"version": "4.2.3",
|
||||
"resolved": "https://registry.npmjs.org/color/-/color-4.2.3.tgz",
|
||||
"integrity": "sha512-1rXeuUUiGGrykh+CeBdu5Ie7OJwinCgQY0bc7GCRxy5xVHy+moaqkpL/jqQq0MtQOeYcrqEz4abc5f0KtU7W4A==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"color-convert": "^2.0.1",
|
||||
"color-string": "^1.9.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12.5.0"
|
||||
}
|
||||
},
|
||||
"node_modules/color-convert": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
|
||||
"integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"color-name": "~1.1.4"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=7.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/color-name": {
|
||||
"version": "1.1.4",
|
||||
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
|
||||
"integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/color-string": {
|
||||
"version": "1.9.1",
|
||||
"resolved": "https://registry.npmjs.org/color-string/-/color-string-1.9.1.tgz",
|
||||
"integrity": "sha512-shrVawQFojnZv6xM40anx4CkoDP+fZsw/ZerEMsW/pyzsRbElpsL/DBVW7q3ExxwusdNXI3lXpuhEZkzs8p5Eg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"color-name": "^1.0.0",
|
||||
"simple-swizzle": "^0.2.2"
|
||||
}
|
||||
},
|
||||
"node_modules/colord": {
|
||||
"version": "2.9.3",
|
||||
"resolved": "https://registry.npmjs.org/colord/-/colord-2.9.3.tgz",
|
||||
|
|
@ -10141,26 +10123,6 @@
|
|||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/cypress/node_modules/color-convert": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
|
||||
"integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"color-name": "~1.1.4"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=7.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/cypress/node_modules/color-name": {
|
||||
"version": "1.1.4",
|
||||
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
|
||||
"integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/cypress/node_modules/has-flag": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
|
||||
|
|
@ -12444,28 +12406,6 @@
|
|||
"url": "https://github.com/chalk/chalk?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/eslint/node_modules/color-convert": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
|
||||
"integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"color-name": "~1.1.4"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=7.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/eslint/node_modules/color-name": {
|
||||
"version": "1.1.4",
|
||||
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
|
||||
"integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true
|
||||
},
|
||||
"node_modules/eslint/node_modules/escape-string-regexp": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz",
|
||||
|
|
@ -16125,24 +16065,6 @@
|
|||
"url": "https://github.com/chalk/chalk?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/jake/node_modules/color-convert": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
|
||||
"integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"color-name": "~1.1.4"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=7.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/jake/node_modules/color-name": {
|
||||
"version": "1.1.4",
|
||||
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
|
||||
"integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/jake/node_modules/has-flag": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
|
||||
|
|
@ -17173,26 +17095,6 @@
|
|||
"url": "https://github.com/chalk/chalk?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/log-symbols/node_modules/color-convert": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
|
||||
"integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"color-name": "~1.1.4"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=7.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/log-symbols/node_modules/color-name": {
|
||||
"version": "1.1.4",
|
||||
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
|
||||
"integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/log-symbols/node_modules/has-flag": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
|
||||
|
|
@ -17251,26 +17153,6 @@
|
|||
"url": "https://github.com/chalk/ansi-styles?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/log-update/node_modules/color-convert": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
|
||||
"integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"color-name": "~1.1.4"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=7.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/log-update/node_modules/color-name": {
|
||||
"version": "1.1.4",
|
||||
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
|
||||
"integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/log-update/node_modules/slice-ansi": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-4.0.0.tgz",
|
||||
|
|
@ -20468,13 +20350,6 @@
|
|||
"postcss": "^8.2.9"
|
||||
}
|
||||
},
|
||||
"node_modules/postcss-values-parser/node_modules/color-name": {
|
||||
"version": "1.1.4",
|
||||
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
|
||||
"integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/precinct": {
|
||||
"version": "12.1.2",
|
||||
"resolved": "https://registry.npmjs.org/precinct/-/precinct-12.1.2.tgz",
|
||||
|
|
@ -20915,24 +20790,6 @@
|
|||
"wrap-ansi": "^6.2.0"
|
||||
}
|
||||
},
|
||||
"node_modules/qrcode/node_modules/color-convert": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
|
||||
"integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"color-name": "~1.1.4"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=7.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/qrcode/node_modules/color-name": {
|
||||
"version": "1.1.4",
|
||||
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
|
||||
"integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/qrcode/node_modules/pngjs": {
|
||||
"version": "5.0.0",
|
||||
"resolved": "https://registry.npmjs.org/pngjs/-/pngjs-5.0.0.tgz",
|
||||
|
|
@ -22765,6 +22622,21 @@
|
|||
"integrity": "sha512-+OmPgi01yHK/bRNQDoehUcV8fqs9nNJkG2DoWCnnLvj0lmowab7BH3v9776BG0y7dGEOLh0F7mfd37k+ht26Yw==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/simple-swizzle": {
|
||||
"version": "0.2.2",
|
||||
"resolved": "https://registry.npmjs.org/simple-swizzle/-/simple-swizzle-0.2.2.tgz",
|
||||
"integrity": "sha512-JA//kQgZtbuY83m+xT+tXJkmJncGMTFT+C+g2h2R9uxkYIrE2yy9sgmcLhCnw57/WSD+Eh3J97FPEDFnbXnDUg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"is-arrayish": "^0.3.1"
|
||||
}
|
||||
},
|
||||
"node_modules/simple-swizzle/node_modules/is-arrayish": {
|
||||
"version": "0.3.2",
|
||||
"resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.3.2.tgz",
|
||||
"integrity": "sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/sinon": {
|
||||
"version": "5.0.7",
|
||||
"resolved": "https://registry.npmjs.org/sinon/-/sinon-5.0.7.tgz",
|
||||
|
|
@ -22823,26 +22695,6 @@
|
|||
"url": "https://github.com/chalk/ansi-styles?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/slice-ansi/node_modules/color-convert": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
|
||||
"integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"color-name": "~1.1.4"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=7.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/slice-ansi/node_modules/color-name": {
|
||||
"version": "1.1.4",
|
||||
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
|
||||
"integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/smart-buffer": {
|
||||
"version": "4.2.0",
|
||||
"resolved": "https://registry.npmjs.org/smart-buffer/-/smart-buffer-4.2.0.tgz",
|
||||
|
|
@ -24250,26 +24102,6 @@
|
|||
"url": "https://github.com/chalk/ansi-styles?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/table/node_modules/color-convert": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
|
||||
"integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"color-name": "~1.1.4"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=7.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/table/node_modules/color-name": {
|
||||
"version": "1.1.4",
|
||||
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
|
||||
"integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/table/node_modules/json-schema-traverse": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz",
|
||||
|
|
@ -24979,26 +24811,6 @@
|
|||
"url": "https://github.com/chalk/chalk?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/ts-loader/node_modules/color-convert": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
|
||||
"integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"color-name": "~1.1.4"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=7.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/ts-loader/node_modules/color-name": {
|
||||
"version": "1.1.4",
|
||||
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
|
||||
"integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/ts-loader/node_modules/has-flag": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
|
||||
|
|
@ -27496,26 +27308,6 @@
|
|||
"url": "https://github.com/chalk/ansi-styles?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/wrap-ansi-cjs/node_modules/color-convert": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
|
||||
"integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"color-name": "~1.1.4"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=7.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/wrap-ansi-cjs/node_modules/color-name": {
|
||||
"version": "1.1.4",
|
||||
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
|
||||
"integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/wrap-ansi/node_modules/ansi-styles": {
|
||||
"version": "4.3.0",
|
||||
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
|
||||
|
|
@ -27532,26 +27324,6 @@
|
|||
"url": "https://github.com/chalk/ansi-styles?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/wrap-ansi/node_modules/color-convert": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
|
||||
"integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"color-name": "~1.1.4"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=7.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/wrap-ansi/node_modules/color-name": {
|
||||
"version": "1.1.4",
|
||||
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
|
||||
"integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/wrappy": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
|
||||
|
|
|
|||
|
|
@ -73,6 +73,7 @@
|
|||
"camelcase": "^8.0.0",
|
||||
"cancelable-promise": "^4.3.1",
|
||||
"clipboard": "^2.0.11",
|
||||
"color": "^4.2.3",
|
||||
"core-js": "^3.38.1",
|
||||
"davclient.js": "github:owncloud/davclient.js.git#0.2.2",
|
||||
"debounce": "^2.1.0",
|
||||
|
|
|
|||
Loading…
Reference in a new issue