Merge pull request #57865 from nextcloud/backport/57855/stable33

[stable33] refactor(systemtags): migrate to Vue 3 and `script setup`
This commit is contained in:
Andy Scherzinger 2026-01-28 14:49:56 +01:00 committed by GitHub
commit f878a4e14f
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
385 changed files with 1518 additions and 2606 deletions

View file

@ -1,8 +1,9 @@
import type { Node } from '@nextcloud/files'
/**
/*!
* SPDX-FileCopyrightText: 2023 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
import type { Node } from '@nextcloud/files'
import type { FileStat, ResponseDataDetailed } from 'webdav'
import { getClient, getDefaultPropfind, getRootPath, resultToNode } from '@nextcloud/files/dav'

View file

@ -24,7 +24,6 @@ use OCP\BeforeSabrePubliclyLoadedEvent;
use OCP\EventDispatcher\IEventDispatcher;
use OCP\SystemTag\ManagerEvent;
use OCP\SystemTag\MapperEvent;
use OCP\Util;
class Application extends App implements IBootstrap {
public const APP_ID = 'systemtags';
@ -43,16 +42,6 @@ class Application extends App implements IBootstrap {
public function boot(IBootContext $context): void {
$context->injectFn(function (IEventDispatcher $dispatcher) use ($context): void {
/*
* @todo move the OCP events and then move the registration to `register`
*/
$dispatcher->addListener(
LoadAdditionalScriptsEvent::class,
function (): void {
Util::addInitScript(self::APP_ID, 'init');
}
);
$managerListener = function (ManagerEvent $event) use ($context): void {
/** @var Listener $listener */
$listener = $context->getServerContainer()->query(Listener::class);

View file

@ -30,6 +30,7 @@ class BeforeTemplateRenderedListener implements IEventListener {
return;
}
Util::addInitScript(Application::APP_ID, 'init');
Util::addStyle(Application::APP_ID, 'init');
$restrictSystemTagsCreationToAdmin = $this->appConfig->getValueBool(Application::APP_ID, 'restrict_creation_to_admin', false);
$this->initialState->provideInitialState('restrictSystemTagsCreationToAdmin', $restrictSystemTagsCreationToAdmin);

View file

@ -30,6 +30,7 @@ class LoadAdditionalScriptsListener implements IEventListener {
return;
}
Util::addInitScript(Application::APP_ID, 'init');
Util::addStyle(Application::APP_ID, 'init');
$restrictSystemTagsCreationToAdmin = $this->appConfig->getValueBool(Application::APP_ID, 'restrict_creation_to_admin', false);
$this->initialState->provideInitialState('restrictSystemTagsCreationToAdmin', $restrictSystemTagsCreationToAdmin);

View file

@ -28,8 +28,9 @@ class Admin implements ISettings {
$restrictSystemTagsCreationToAdmin = $this->appConfig->getValueBool(Application::APP_ID, 'restrict_creation_to_admin', false);
$this->initialStateService->provideInitialState('restrictSystemTagsCreationToAdmin', $restrictSystemTagsCreationToAdmin);
Util::addScript('systemtags', 'admin');
return new TemplateResponse('systemtags', 'admin', [], '');
Util::addStyle(Application::APP_ID, 'admin');
Util::addScript(Application::APP_ID, 'admin');
return new TemplateResponse(Application::APP_ID, 'admin', [], '');
}
/**

View file

@ -1,13 +1,10 @@
/**
/*!
* SPDX-FileCopyrightText: 2023 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
import { getCSPNonce } from '@nextcloud/auth'
import Vue from 'vue'
import { createApp } from 'vue'
import SystemTagsSection from './views/SystemTagsSection.vue'
__webpack_nonce__ = getCSPNonce()
const SystemTagsSectionView = Vue.extend(SystemTagsSection)
new SystemTagsSectionView().$mount('#vue-admin-systemtags')
const app = createApp(SystemTagsSection)
app.mount('#vue-admin-systemtags')

View file

@ -3,6 +3,219 @@
- SPDX-License-Identifier: AGPL-3.0-or-later
-->
<script setup lang="ts">
import type { Tag, TagWithId } from '../types.ts'
import { showSuccess } from '@nextcloud/dialogs'
import { t } from '@nextcloud/l10n'
import { computed, ref, useTemplateRef, watch } from 'vue'
import NcButton from '@nextcloud/vue/components/NcButton'
import NcLoadingIcon from '@nextcloud/vue/components/NcLoadingIcon'
import NcSelect from '@nextcloud/vue/components/NcSelect'
import NcSelectTags from '@nextcloud/vue/components/NcSelectTags'
import NcTextField from '@nextcloud/vue/components/NcTextField'
import { createTag, deleteTag, updateTag } from '../services/api.ts'
import { defaultBaseTag } from '../utils.ts'
const props = defineProps<{
tags: TagWithId[]
}>()
const emit = defineEmits<{
'tag:created': [tag: TagWithId]
'tag:updated': [tag: TagWithId]
'tag:deleted': [tag: TagWithId]
}>()
enum TagLevel {
Public = 'Public',
Restricted = 'Restricted',
Invisible = 'Invisible',
}
interface TagLevelOption {
id: TagLevel
label: string
}
const tagLevelOptions: TagLevelOption[] = [
{
id: TagLevel.Public,
label: t('systemtags', 'Public'),
},
{
id: TagLevel.Restricted,
label: t('systemtags', 'Restricted'),
},
{
id: TagLevel.Invisible,
label: t('systemtags', 'Invisible'),
},
]
const tagNameInputElement = useTemplateRef('tagNameInput')
const loading = ref(false)
const errorMessage = ref('')
const tagName = ref('')
const tagLevel = ref(TagLevel.Public)
const selectedTag = ref<null | TagWithId>(null)
watch(selectedTag, (tag: null | TagWithId) => {
tagName.value = tag ? tag.displayName : ''
tagLevel.value = tag ? getTagLevel(tag.userVisible, tag.userAssignable) : TagLevel.Public
})
const isCreating = computed(() => selectedTag.value === null)
const isCreateDisabled = computed(() => tagName.value === '')
const isUpdateDisabled = computed(() => (
tagName.value === ''
|| (
selectedTag.value?.displayName === tagName.value
&& getTagLevel(selectedTag.value?.userVisible, selectedTag.value?.userAssignable) === tagLevel.value
)
))
const isResetDisabled = computed(() => {
if (isCreating.value) {
return tagName.value === '' && tagLevel.value === TagLevel.Public
}
return selectedTag.value === null
})
const userVisible = computed((): boolean => {
const matchLevel: Record<TagLevel, boolean> = {
[TagLevel.Public]: true,
[TagLevel.Restricted]: true,
[TagLevel.Invisible]: false,
}
return matchLevel[tagLevel.value]
})
const userAssignable = computed(() => {
const matchLevel: Record<TagLevel, boolean> = {
[TagLevel.Public]: true,
[TagLevel.Restricted]: false,
[TagLevel.Invisible]: false,
}
return matchLevel[tagLevel.value]
})
const tagProperties = computed((): Omit<Tag, 'id' | 'canAssign'> => {
return {
displayName: tagName.value,
userVisible: userVisible.value,
userAssignable: userAssignable.value,
}
})
/**
* Handle tag selection
*
* @param tagId - The selected tag ID
*/
function onSelectTag(tagId: number | null) {
const tag = props.tags.find((search) => search.id === tagId) || null
selectedTag.value = tag
}
/**
* Handle form submission
*/
async function handleSubmit() {
if (isCreating.value) {
await create()
return
}
await update()
}
/**
* Create a new tag
*/
async function create() {
const tag: Tag = { ...defaultBaseTag, ...tagProperties.value }
loading.value = true
try {
const id = await createTag(tag)
const createdTag: TagWithId = { ...tag, id }
emit('tag:created', createdTag)
showSuccess(t('systemtags', 'Created tag'))
reset()
} catch {
errorMessage.value = t('systemtags', 'Failed to create tag')
}
loading.value = false
}
/**
* Update the selected tag
*/
async function update() {
if (selectedTag.value === null) {
return
}
const tag: TagWithId = { ...selectedTag.value, ...tagProperties.value }
loading.value = true
try {
await updateTag(tag)
selectedTag.value = tag
emit('tag:updated', tag)
showSuccess(t('systemtags', 'Updated tag'))
tagNameInputElement.value?.focus()
} catch {
errorMessage.value = t('systemtags', 'Failed to update tag')
}
loading.value = false
}
/**
* Delete the selected tag
*/
async function handleDelete() {
if (selectedTag.value === null) {
return
}
loading.value = true
try {
await deleteTag(selectedTag.value)
emit('tag:deleted', selectedTag.value)
showSuccess(t('systemtags', 'Deleted tag'))
reset()
} catch {
errorMessage.value = t('systemtags', 'Failed to delete tag')
}
loading.value = false
}
/**
* Reset the form
*/
function reset() {
selectedTag.value = null
errorMessage.value = ''
tagName.value = ''
tagLevel.value = TagLevel.Public
tagNameInputElement.value?.focus()
}
/**
* Get tag level based on visibility and assignability
*
* @param userVisible - Whether the tag is visible to users
* @param userAssignable - Whether the tag is assignable by users
*/
function getTagLevel(userVisible: boolean, userAssignable: boolean): TagLevel {
const matchLevel: Record<string, TagLevel> = {
[[true, true].join(',')]: TagLevel.Public,
[[true, false].join(',')]: TagLevel.Restricted,
[[false, false].join(',')]: TagLevel.Invisible,
}
return matchLevel[[userVisible, userAssignable].join(',')]!
}
</script>
<template>
<form
class="system-tag-form"
@ -17,14 +230,14 @@
<div class="system-tag-form__group">
<label for="system-tags-input">{{ t('systemtags', 'Search for a tag to edit') }}</label>
<NcSelectTags
:model-value="selectedTag"
input-id="system-tags-input"
:modelValue="selectedTag"
inputId="system-tags-input"
:placeholder="t('systemtags', 'Collaborative tags …')"
:fetch-tags="false"
:fetchTags="false"
:options="tags"
:multiple="false"
label-outside
@update:model-value="onSelectTag">
labelOutside
@update:modelValue="onSelectTag">
<template #no-options>
{{ t('systemtags', 'No tags to select') }}
</template>
@ -38,20 +251,20 @@
ref="tagNameInput"
v-model="tagName"
:error="Boolean(errorMessage)"
:helper-text="errorMessage"
label-outside />
:helperText="errorMessage"
labelOutside />
</div>
<div class="system-tag-form__group">
<label for="system-tag-level">{{ t('systemtags', 'Tag level') }}</label>
<NcSelect
v-model="tagLevel"
input-id="system-tag-level"
inputId="system-tag-level"
:options="tagLevelOptions"
:reduce="level => level.id"
:clearable="false"
:disabled="loading"
label-outside />
labelOutside />
</div>
<div class="system-tag-form__row">
@ -86,232 +299,6 @@
</form>
</template>
<script lang="ts">
import type { PropType } from 'vue'
import type { Tag, TagWithId } from '../types.js'
import { showSuccess } from '@nextcloud/dialogs'
import { translate as t } from '@nextcloud/l10n'
import { defineComponent } from 'vue'
import NcButton from '@nextcloud/vue/components/NcButton'
import NcLoadingIcon from '@nextcloud/vue/components/NcLoadingIcon'
import NcSelect from '@nextcloud/vue/components/NcSelect'
import NcSelectTags from '@nextcloud/vue/components/NcSelectTags'
import NcTextField from '@nextcloud/vue/components/NcTextField'
import { createTag, deleteTag, updateTag } from '../services/api.js'
import { defaultBaseTag } from '../utils.js'
enum TagLevel {
Public = 'Public',
Restricted = 'Restricted',
Invisible = 'Invisible',
}
interface TagLevelOption {
id: TagLevel
label: string
}
const tagLevelOptions: TagLevelOption[] = [
{
id: TagLevel.Public,
label: t('systemtags', 'Public'),
},
{
id: TagLevel.Restricted,
label: t('systemtags', 'Restricted'),
},
{
id: TagLevel.Invisible,
label: t('systemtags', 'Invisible'),
},
]
/**
*
* @param userVisible
* @param userAssignable
*/
function getTagLevel(userVisible: boolean, userAssignable: boolean): TagLevel {
const matchLevel: Record<string, TagLevel> = {
[[true, true].join(',')]: TagLevel.Public,
[[true, false].join(',')]: TagLevel.Restricted,
[[false, false].join(',')]: TagLevel.Invisible,
}
return matchLevel[[userVisible, userAssignable].join(',')]!
}
export default defineComponent({
name: 'SystemTagForm',
components: {
NcButton,
NcLoadingIcon,
NcSelect,
NcSelectTags,
NcTextField,
},
props: {
tags: {
type: Array as PropType<TagWithId[]>,
required: true,
},
},
emits: [
'tag:created',
'tag:updated',
'tag:deleted',
],
data() {
return {
loading: false,
tagLevelOptions,
selectedTag: null as null | TagWithId,
errorMessage: '',
tagName: '',
tagLevel: TagLevel.Public,
}
},
computed: {
isCreating(): boolean {
return this.selectedTag === null
},
isCreateDisabled(): boolean {
return this.tagName === ''
},
isUpdateDisabled(): boolean {
return (
this.tagName === ''
|| (
this.selectedTag?.displayName === this.tagName
&& getTagLevel(this.selectedTag?.userVisible, this.selectedTag?.userAssignable) === this.tagLevel
)
)
},
isResetDisabled(): boolean {
if (this.isCreating) {
return this.tagName === '' && this.tagLevel === TagLevel.Public
}
return this.selectedTag === null
},
userVisible(): boolean {
const matchLevel: Record<TagLevel, boolean> = {
[TagLevel.Public]: true,
[TagLevel.Restricted]: true,
[TagLevel.Invisible]: false,
}
return matchLevel[this.tagLevel]
},
userAssignable(): boolean {
const matchLevel: Record<TagLevel, boolean> = {
[TagLevel.Public]: true,
[TagLevel.Restricted]: false,
[TagLevel.Invisible]: false,
}
return matchLevel[this.tagLevel]
},
tagProperties(): Omit<Tag, 'id' | 'canAssign'> {
return {
displayName: this.tagName,
userVisible: this.userVisible,
userAssignable: this.userAssignable,
}
},
},
watch: {
selectedTag(tag: null | TagWithId) {
this.tagName = tag ? tag.displayName : ''
this.tagLevel = tag ? getTagLevel(tag.userVisible, tag.userAssignable) : TagLevel.Public
},
},
methods: {
t,
onSelectTag(tagId: number | null) {
const tag = this.tags.find((search) => search.id === tagId) || null
this.selectedTag = tag
},
async handleSubmit() {
if (this.isCreating) {
await this.create()
return
}
await this.update()
},
async create() {
const tag: Tag = { ...defaultBaseTag, ...this.tagProperties }
this.loading = true
try {
const id = await createTag(tag)
const createdTag: TagWithId = { ...tag, id }
this.$emit('tag:created', createdTag)
showSuccess(t('systemtags', 'Created tag'))
this.reset()
} catch {
this.errorMessage = t('systemtags', 'Failed to create tag')
}
this.loading = false
},
async update() {
if (this.selectedTag === null) {
return
}
const tag: TagWithId = { ...this.selectedTag, ...this.tagProperties }
this.loading = true
try {
await updateTag(tag)
this.selectedTag = tag
this.$emit('tag:updated', tag)
showSuccess(t('systemtags', 'Updated tag'))
this.$refs.tagNameInput?.focus()
} catch {
this.errorMessage = t('systemtags', 'Failed to update tag')
}
this.loading = false
},
async handleDelete() {
if (this.selectedTag === null) {
return
}
this.loading = true
try {
await deleteTag(this.selectedTag)
this.$emit('tag:deleted', this.selectedTag)
showSuccess(t('systemtags', 'Deleted tag'))
this.reset()
} catch {
this.errorMessage = t('systemtags', 'Failed to delete tag')
}
this.loading = false
},
reset() {
this.selectedTag = null
this.errorMessage = ''
this.tagName = ''
this.tagLevel = TagLevel.Public
this.$refs.tagNameInput?.focus()
},
},
})
</script>
<style lang="scss" scoped>
.system-tag-form {
display: flex;

View file

@ -6,20 +6,20 @@
<template>
<NcDialog
data-cy-systemtags-picker
:no-close="status === Status.LOADING"
:noClose="status === Status.LOADING"
:name="t('systemtags', 'Manage tags')"
:open="opened"
:class="'systemtags-picker--' + status"
class="systemtags-picker"
close-on-click-outside
out-transition
closeOnClickOutside
outTransition
@update:open="onCancel">
<NcEmptyContent
v-if="status === Status.LOADING || status === Status.DONE"
:name="t('systemtags', 'Applying tags changes…')">
<template #icon>
<NcLoadingIcon v-if="status === Status.LOADING" />
<CheckIcon v-else fill-color="var(--color-border-success)" />
<CheckIcon v-else fillColor="var(--color-border-success)" />
</template>
</NcEmptyContent>
@ -41,11 +41,12 @@
<li
v-for="tag in filteredTags"
:key="tag.id"
ref="tags"
:data-cy-systemtags-picker-tag="tag.id"
:style="tagListStyle(tag)"
class="systemtags-picker__tag">
<NcCheckboxRadioSwitch
:model-value="isChecked(tag)"
:modelValue="isChecked(tag)"
:disabled="!tag.canAssign"
:indeterminate="isIndeterminate(tag)"
:label="tag.displayName"
@ -58,23 +59,22 @@
<NcColorPicker
v-if="canEditOrCreateTag"
:data-cy-systemtags-picker-tag-color="tag.id"
:model-value="`#${tag.color || '000000'}`"
:modelValue="`#${tag.color || '000000'}`"
:shown="openedPicker === tag.id"
class="systemtags-picker__tag-color"
@update:value="onColorChange(tag, $event)"
@update:shown="openedPicker = $event ? tag.id : false"
@submit="openedPicker = false">
@submit="onColorChange(tag, $event)">
<NcButton :aria-label="t('systemtags', 'Change tag color')" variant="tertiary">
<template #icon>
<CircleIcon
v-if="tag.color"
:size="24"
fill-color="var(--color-circle-icon)"
fillColor="var(--color-circle-icon)"
class="button-color-circle" />
<CircleOutlineIcon
v-else
:size="24"
fill-color="var(--color-circle-icon)"
fillColor="var(--color-circle-icon)"
class="button-color-empty" />
<PencilIcon class="button-color-pencil" />
</template>
@ -108,6 +108,7 @@
{{ t('systemtags', 'Choose tags for the selected files') }}
</NcNoteCard>
<NcNoteCard v-else type="info">
<!-- eslint-disable-next-line vue/no-v-html -- we use this to format the message with chips -->
<span v-html="statusMessage" />
</NcNoteCard>
</div>
@ -134,8 +135,8 @@
<NcChip
ref="chip"
text="%s"
variant="primary"
no-close />
noClose
variant="primary" />
</div>
</NcDialog>
</template>
@ -221,7 +222,11 @@ export default defineComponent({
},
},
emits: ['close'],
emits: {
close(status: null | boolean) {
return status === null || typeof status === 'boolean'
},
},
setup() {
return {
@ -399,7 +404,7 @@ export default defineComponent({
methods: {
// Format & sanitize a tag chip for v-html tag rendering
formatTagChip(tag: TagWithId): string {
const chip = this.$refs.chip as NcChip
const chip = this.$refs.chip as InstanceType<typeof NcChip>
const chipCloneEl = chip.$el.cloneNode(true) as HTMLElement
if (tag.color) {
const style = this.tagListStyle(tag)
@ -476,12 +481,15 @@ export default defineComponent({
// Scroll to the newly created tag
await this.$nextTick()
const newTagEl = this.$el.querySelector(`input[type="checkbox"][label="${tag.displayName}"]`)
newTagEl?.scrollIntoView({
behavior: 'instant',
block: 'center',
inline: 'center',
})
if (Array.isArray(this.$refs.tags)) {
const newTagEl = this.$refs.tags
.find((el: HTMLElement) => el.dataset.cySystemtagsPickerTag === id.toString())
newTagEl?.scrollIntoView({
behavior: 'instant',
block: 'center',
inline: 'center',
})
}
} catch (error) {
showError((error as Error)?.message || t('systemtags', 'Failed to create tag'))
} finally {

View file

@ -3,6 +3,235 @@
- SPDX-License-Identifier: AGPL-3.0-or-later
-->
<script setup lang="ts">
import type { INode } from '@nextcloud/files'
import type { Tag, TagWithId } from '../types.ts'
import { showError } from '@nextcloud/dialogs'
import { emit, subscribe } from '@nextcloud/event-bus'
import { getSidebar } from '@nextcloud/files'
import { loadState } from '@nextcloud/initial-state'
import { t } from '@nextcloud/l10n'
import { onBeforeMount, onMounted, ref, watch } from 'vue'
import NcLoadingIcon from '@nextcloud/vue/components/NcLoadingIcon'
import NcSelectTags from '@nextcloud/vue/components/NcSelectTags'
import logger from '../logger.ts'
import { fetchLastUsedTagIds, fetchTags } from '../services/api.ts'
import { fetchNode } from '../services/davClient.ts'
import {
createTagForFile,
deleteTagForFile,
fetchTagsForFile,
setTagForFile,
} from '../services/files.ts'
import { defaultBaseTag } from '../utils.ts'
const props = defineProps<{
fileId: number
disabled?: boolean
}>()
const sortedTags = ref<TagWithId[]>([])
const selectedTags = ref<TagWithId[]>([])
const loadingTags = ref(false)
const loading = ref(false)
watch(() => props.fileId, async () => {
loadingTags.value = true
try {
selectedTags.value = await fetchTagsForFile(props.fileId)
} catch (error) {
showError(t('systemtags', 'Failed to load selected tags'))
logger.error('Failed to load selected tags', { error })
} finally {
loadingTags.value = false
}
}, { immediate: true })
onBeforeMount(async () => {
try {
const tags = await fetchTags()
const lastUsedOrder = await fetchLastUsedTagIds()
const lastUsedTags: TagWithId[] = []
const remainingTags: TagWithId[] = []
for (const tag of tags) {
if (lastUsedOrder.includes(tag.id)) {
lastUsedTags.push(tag)
continue
}
remainingTags.push(tag)
}
const sortByLastUsed = (a: TagWithId, b: TagWithId) => {
return lastUsedOrder.indexOf(a.id) - lastUsedOrder.indexOf(b.id)
}
lastUsedTags.sort(sortByLastUsed)
sortedTags.value = [...lastUsedTags, ...remainingTags]
} catch (error) {
showError(t('systemtags', 'Failed to load tags'))
logger.error('Failed to load tags', { error })
}
})
onMounted(() => {
subscribe('systemtags:node:updated', onTagUpdated)
})
/**
* Create a new tag
*
* @param newDisplayName - The display name of the tag to create
*/
function createOption(newDisplayName: string): Tag {
for (const tag of sortedTags.value) {
const { displayName, ...baseTag } = tag
if (
displayName === newDisplayName
&& Object.entries(baseTag)
.every(([key, value]) => defaultBaseTag[key] === value)
) {
// Return existing tag to prevent vue-select from thinking the tags are different and showing duplicate options
return tag
}
}
return {
...defaultBaseTag,
displayName: newDisplayName,
}
}
/**
* Filter out tags with no id to prevent duplicate selected options
*
* Created tags are added programmatically by `handleCreate()` with
* their respective ids returned from the server.
*
* @param currentTags - The selected tags
*/
function handleInput(currentTags: Tag[]) {
selectedTags.value = currentTags.filter((selectedTag) => Boolean(selectedTag.id)) as TagWithId[]
}
/**
* Handle tag selection
*
* @param tags - The selected tags
*/
async function handleSelect(tags: Tag[]) {
const lastTag = tags[tags.length - 1]!
if (!lastTag.id) {
// Ignore created tags handled by `handleCreate()`
return
}
const selectedTag = lastTag as TagWithId
loading.value = true
try {
await setTagForFile(selectedTag, props.fileId)
const sortToFront = (a: TagWithId, b: TagWithId) => {
if (a.id === selectedTag.id) {
return -1
} else if (b.id === selectedTag.id) {
return 1
}
return 0
}
sortedTags.value.sort(sortToFront)
} catch (error) {
showError(t('systemtags', 'Failed to select tag'))
logger.error('Failed to select tag', { error })
}
loading.value = false
updateAndDispatchNodeTagsEvent(props.fileId)
}
/**
* Handle tag creation
*
* @param tag - The created tag
*/
async function handleCreate(tag: Tag) {
loading.value = true
try {
const id = await createTagForFile(tag, props.fileId)
const createdTag = { ...tag, id }
sortedTags.value.unshift(createdTag)
selectedTags.value.push(createdTag)
} catch (error) {
const systemTagsCreationRestrictedToAdmin = loadState<true | false>('settings', 'restrictSystemTagsCreationToAdmin', false) === true
logger.error('Failed to create tag', { error })
if (systemTagsCreationRestrictedToAdmin) {
showError(t('systemtags', 'System admin disabled tag creation. You can only use existing ones.'))
return
}
showError(t('systemtags', 'Failed to create tag'))
}
loading.value = false
updateAndDispatchNodeTagsEvent(props.fileId)
}
/**
* Handle tag deselection
*
* @param tag - The deselected tag
*/
async function handleDeselect(tag: TagWithId) {
loading.value = true
try {
await deleteTagForFile(tag, props.fileId)
} catch (error) {
showError(t('systemtags', 'Failed to delete tag'))
logger.error('Failed to delete tag', { error })
}
loading.value = false
updateAndDispatchNodeTagsEvent(props.fileId)
}
/**
* Handle node updated event
*
* @param node - The updated node
*/
async function onTagUpdated(node: INode) {
if (node.fileid !== props.fileId) {
return
}
loadingTags.value = true
try {
selectedTags.value = await fetchTagsForFile(props.fileId)
} catch (error) {
showError(t('systemtags', 'Failed to load selected tags'))
logger.error('Failed to load selected tags', { error })
}
loadingTags.value = false
}
/**
* Update and dispatch system tags node updated event
*
* @param fileId - The file ID
*/
async function updateAndDispatchNodeTagsEvent(fileId: number) {
const sidebar = getSidebar()
const path = sidebar.node?.path ?? ''
try {
const node = await fetchNode(path)
if (node) {
emit('systemtags:node:updated', node)
}
} catch (error) {
logger.error('Failed to fetch node for system tags update', { error, fileId })
}
}
</script>
<template>
<div class="system-tags">
<NcLoadingIcon
@ -13,15 +242,15 @@
<NcSelectTags
v-show="!loadingTags"
class="system-tags__select"
:input-label="t('systemtags', 'Search or create collaborative tags')"
:inputLabel="t('systemtags', 'Search or create collaborative tags')"
:placeholder="t('systemtags', 'Collaborative tags …')"
:options="sortedTags"
:model-value="selectedTags"
:create-option="createOption"
:modelValue="selectedTags"
:createOption="createOption"
:disabled="disabled"
:taggable="true"
:passthru="true"
:fetch-tags="false"
:fetchTags="false"
:loading="loading"
@input="handleInput"
@option:selected="handleSelect"
@ -34,231 +263,6 @@
</div>
</template>
<script lang="ts">
import type { INode } from '@nextcloud/files'
import type { Tag, TagWithId } from '../types.js'
import { showError } from '@nextcloud/dialogs'
import { emit, subscribe } from '@nextcloud/event-bus'
import { getSidebar } from '@nextcloud/files'
import { loadState } from '@nextcloud/initial-state'
import { t } from '@nextcloud/l10n'
import Vue from 'vue'
import NcLoadingIcon from '@nextcloud/vue/components/NcLoadingIcon'
import NcSelectTags from '@nextcloud/vue/components/NcSelectTags'
import { fetchNode } from '../../../files/src/services/WebdavClient.js'
import logger from '../logger.js'
import { fetchLastUsedTagIds, fetchTags } from '../services/api.js'
import {
createTagForFile,
deleteTagForFile,
fetchTagsForFile,
setTagForFile,
} from '../services/files.js'
import { defaultBaseTag } from '../utils.js'
export default Vue.extend({
name: 'SystemTags',
components: {
NcLoadingIcon,
NcSelectTags,
},
props: {
fileId: {
type: Number,
required: true,
},
disabled: {
type: Boolean,
default: false,
},
},
data() {
return {
sortedTags: [] as TagWithId[],
selectedTags: [] as TagWithId[],
loadingTags: false,
loading: false,
}
},
watch: {
fileId: {
immediate: true,
async handler() {
this.loadingTags = true
try {
this.selectedTags = await fetchTagsForFile(this.fileId)
} catch (error) {
showError(t('systemtags', 'Failed to load selected tags'))
logger.error('Failed to load selected tags', { error })
}
this.loadingTags = false
},
},
},
async created() {
try {
const tags = await fetchTags()
const lastUsedOrder = await fetchLastUsedTagIds()
const lastUsedTags: TagWithId[] = []
const remainingTags: TagWithId[] = []
for (const tag of tags) {
if (lastUsedOrder.includes(tag.id)) {
lastUsedTags.push(tag)
continue
}
remainingTags.push(tag)
}
const sortByLastUsed = (a: TagWithId, b: TagWithId) => {
return lastUsedOrder.indexOf(a.id) - lastUsedOrder.indexOf(b.id)
}
lastUsedTags.sort(sortByLastUsed)
this.sortedTags = [...lastUsedTags, ...remainingTags]
} catch (error) {
showError(t('systemtags', 'Failed to load tags'))
logger.error('Failed to load tags', { error })
}
},
mounted() {
subscribe('systemtags:node:updated', this.onTagUpdated)
},
methods: {
t,
createOption(newDisplayName: string): Tag {
for (const tag of this.sortedTags) {
const { displayName, ...baseTag } = tag
if (
displayName === newDisplayName
&& Object.entries(baseTag)
.every(([key, value]) => defaultBaseTag[key] === value)
) {
// Return existing tag to prevent vue-select from thinking the tags are different and showing duplicate options
return tag
}
}
return {
...defaultBaseTag,
displayName: newDisplayName,
}
},
handleInput(selectedTags: Tag[]) {
/**
* Filter out tags with no id to prevent duplicate selected options
*
* Created tags are added programmatically by `handleCreate()` with
* their respective ids returned from the server
*/
this.selectedTags = selectedTags.filter((selectedTag) => Boolean(selectedTag.id)) as TagWithId[]
},
async handleSelect(tags: Tag[]) {
const lastTag = tags[tags.length - 1]
if (!lastTag.id) {
// Ignore created tags handled by `handleCreate()`
return
}
const selectedTag = lastTag as TagWithId
this.loading = true
try {
await setTagForFile(selectedTag, this.fileId)
const sortToFront = (a: TagWithId, b: TagWithId) => {
if (a.id === selectedTag.id) {
return -1
} else if (b.id === selectedTag.id) {
return 1
}
return 0
}
this.sortedTags.sort(sortToFront)
} catch (error) {
showError(t('systemtags', 'Failed to select tag'))
logger.error('Failed to select tag', { error })
}
this.loading = false
this.updateAndDispatchNodeTagsEvent(this.fileId)
},
async handleCreate(tag: Tag) {
this.loading = true
try {
const id = await createTagForFile(tag, this.fileId)
const createdTag = { ...tag, id }
this.sortedTags.unshift(createdTag)
this.selectedTags.push(createdTag)
} catch (error) {
const systemTagsCreationRestrictedToAdmin = loadState<true | false>('settings', 'restrictSystemTagsCreationToAdmin', false) === true
logger.error('Failed to create tag', { error })
if (systemTagsCreationRestrictedToAdmin) {
showError(t('systemtags', 'System admin disabled tag creation. You can only use existing ones.'))
return
}
showError(t('systemtags', 'Failed to create tag'))
}
this.loading = false
this.updateAndDispatchNodeTagsEvent(this.fileId)
},
async handleDeselect(tag: TagWithId) {
this.loading = true
try {
await deleteTagForFile(tag, this.fileId)
} catch (error) {
showError(t('systemtags', 'Failed to delete tag'))
logger.error('Failed to delete tag', { error })
}
this.loading = false
this.updateAndDispatchNodeTagsEvent(this.fileId)
},
async onTagUpdated(node: INode) {
if (node.fileid !== this.fileId) {
return
}
this.loadingTags = true
try {
this.selectedTags = await fetchTagsForFile(this.fileId)
} catch (error) {
showError(t('systemtags', 'Failed to load selected tags'))
logger.error('Failed to load selected tags', { error })
}
this.loadingTags = false
},
async updateAndDispatchNodeTagsEvent(fileId: number) {
const sidebar = getSidebar()
const path = sidebar.node?.path ?? ''
try {
const node = await fetchNode(path)
if (node) {
emit('systemtags:node:updated', node)
}
} catch (error) {
logger.error('Failed to fetch node for system tags update', { error, fileId })
}
},
},
})
</script>
<style lang="scss" scoped>
.system-tags {
display: flex;

View file

@ -3,6 +3,69 @@
- SPDX-License-Identifier: AGPL-3.0-or-later
-->
<script setup lang="ts">
import { showError, showSuccess } from '@nextcloud/dialogs'
import { loadState } from '@nextcloud/initial-state'
import { t } from '@nextcloud/l10n'
import { ref } from 'vue'
import NcCheckboxRadioSwitch from '@nextcloud/vue/components/NcCheckboxRadioSwitch'
import logger from '../logger.ts'
import { updateSystemTagsAdminRestriction } from '../services/api.ts'
// By default, system tags creation is not restricted to admins
const systemTagsCreationRestrictedToAdmin = ref(loadState('systemtags', 'restrictSystemTagsCreationToAdmin', false))
/**
* Update system tags admin restriction setting
*
* @param isRestricted - True if system tags creation should be restricted to admins
*/
async function updateSystemTagsDefault(isRestricted: boolean) {
try {
const responseData = await updateSystemTagsAdminRestriction(isRestricted)
logger.debug('updateSystemTagsDefault', { responseData })
handleResponse({
isRestricted,
status: responseData.ocs?.meta?.status,
})
} catch (e) {
handleResponse({
errorMessage: t('systemtags', 'Unable to update setting'),
error: e,
})
}
}
/**
* Handle response from updating system tags admin restriction
*
* @param context - The response context
* @param context.isRestricted - Whether system tags creation is restricted to admins
* @param context.status - The response status
* @param context.errorMessage - The error message, if any
* @param context.error - The error object, if any
*/
function handleResponse({ isRestricted, status, errorMessage, error }: {
isRestricted?: boolean
status?: string
errorMessage?: string
error?: unknown
}) {
if (status === 'ok') {
systemTagsCreationRestrictedToAdmin.value = !!isRestricted
showSuccess(isRestricted
? t('systemtags', 'System tag creation is now restricted to administrators')
: t('systemtags', 'System tag creation is now allowed for everybody'))
return
}
if (errorMessage) {
showError(errorMessage)
logger.error(errorMessage, { error })
}
}
</script>
<template>
<div id="system-tags-creation-control">
<h4 class="inlineblock">
@ -21,66 +84,3 @@
</NcCheckboxRadioSwitch>
</div>
</template>
<script lang="ts">
import { showError, showSuccess } from '@nextcloud/dialogs'
import { loadState } from '@nextcloud/initial-state'
import { t } from '@nextcloud/l10n'
import NcCheckboxRadioSwitch from '@nextcloud/vue/components/NcCheckboxRadioSwitch'
import logger from '../logger.ts'
import { updateSystemTagsAdminRestriction } from '../services/api.js'
export default {
name: 'SystemTagsCreationControl',
components: {
NcCheckboxRadioSwitch,
},
setup() {
return {
t,
}
},
data() {
return {
// By default, system tags creation is not restricted to admins
systemTagsCreationRestrictedToAdmin: loadState('systemtags', 'restrictSystemTagsCreationToAdmin', false),
}
},
methods: {
async updateSystemTagsDefault(isRestricted: boolean) {
try {
const responseData = await updateSystemTagsAdminRestriction(isRestricted)
logger.debug('updateSystemTagsDefault', responseData)
this.handleResponse({
isRestricted,
status: responseData.ocs?.meta?.status,
})
} catch (e) {
this.handleResponse({
errorMessage: t('systemtags', 'Unable to update setting'),
error: e,
})
}
},
handleResponse({ isRestricted, status, errorMessage, error }) {
if (status === 'ok') {
this.systemTagsCreationRestrictedToAdmin = isRestricted
showSuccess(isRestricted
? t('systemtags', 'System tag creation is now restricted to administrators')
: t('systemtags', 'System tag creation is now allowed for everybody'))
return
}
if (errorMessage) {
showError(errorMessage)
logger.error(errorMessage, error)
}
},
},
}
</script>

View file

@ -19,14 +19,14 @@ import { defineAsyncComponent } from 'vue'
* @param context.nodes - Nodes to modify tags for
*/
async function execBatch({ nodes }: ActionContext | ActionContextSingle): Promise<(null | boolean)[]> {
const response = await new Promise<null | boolean>((resolve) => {
spawnDialog(defineAsyncComponent(() => import('../components/SystemTagPicker.vue')), {
const response = await spawnDialog(
defineAsyncComponent(() => import('../components/SystemTagPicker.vue')),
{
nodes,
}, (status) => {
resolve(status as null | boolean)
})
})
return Array(nodes.length).fill(response)
},
)
return Array(nodes.length)
.fill(response)
}
export const action = new FileAction({
@ -55,7 +55,7 @@ export const action = new FileAction({
async exec(context: ActionContextSingle) {
const [result] = await execBatch(context)
return result
return result!
},
execBatch,

View file

@ -6,7 +6,7 @@
import svgTagMultiple from '@mdi/svg/svg/tag-multiple-outline.svg?raw'
import { getNavigation, View } from '@nextcloud/files'
import { translate as t } from '@nextcloud/l10n'
import { getContents } from '../services/systemtags.js'
import { getContents } from '../services/systemtags.ts'
export const systemTagsViewId = 'tags'

View file

@ -3,8 +3,9 @@
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
import type { OCSResponse } from '@nextcloud/typings/ocs'
import type { FileStat, ResponseDataDetailed, WebDAVClientError } from 'webdav'
import type { ServerTag, Tag, TagWithId } from '../types.js'
import type { ServerTag, Tag, TagWithId } from '../types.ts'
import axios from '@nextcloud/axios'
import { emit } from '@nextcloud/event-bus'
@ -13,7 +14,7 @@ import { confirmPassword } from '@nextcloud/password-confirmation'
import { generateOcsUrl, generateUrl } from '@nextcloud/router'
import logger from '../logger.ts'
import { formatTag, parseIdFromLocation, parseTags } from '../utils.ts'
import { davClient } from './davClient.js'
import { davClient } from './davClient.ts'
export const fetchTagsPayload = `<?xml version="1.0"?>
<d:propfind xmlns:d="DAV:" xmlns:oc="http://owncloud.org/ns" xmlns:nc="http://nextcloud.org/ns">
@ -29,7 +30,7 @@ export const fetchTagsPayload = `<?xml version="1.0"?>
</d:propfind>`
/**
*
* Fetch all tags.
*/
export async function fetchTags(): Promise<TagWithId[]> {
const path = '/systemtags'
@ -47,8 +48,9 @@ export async function fetchTags(): Promise<TagWithId[]> {
}
/**
* Fetch a single tag by its ID.
*
* @param tagId
* @param tagId - The ID of the tag to fetch
*/
export async function fetchTag(tagId: number): Promise<TagWithId> {
const path = '/systemtags/' + tagId
@ -57,7 +59,7 @@ export async function fetchTag(tagId: number): Promise<TagWithId> {
data: fetchTagsPayload,
details: true,
}) as ResponseDataDetailed<Required<FileStat>>
return parseTags([tag])[0]
return parseTags([tag])[0]!
} catch (error) {
logger.error(t('systemtags', 'Failed to load tag'), { error })
throw new Error(t('systemtags', 'Failed to load tag'))
@ -65,7 +67,7 @@ export async function fetchTag(tagId: number): Promise<TagWithId> {
}
/**
*
* Get the last used tag IDs.
*/
export async function fetchLastUsedTagIds(): Promise<number[]> {
const url = generateUrl('/apps/systemtags/lastused')
@ -109,8 +111,9 @@ export async function createTag(tag: Tag | ServerTag): Promise<number> {
}
/**
* Update a tag on the server.
*
* @param tag
* @param tag - The tag to update
*/
export async function updateTag(tag: TagWithId): Promise<void> {
const path = '/systemtags/' + tag.id
@ -139,8 +142,9 @@ export async function updateTag(tag: TagWithId): Promise<void> {
}
/**
* Delete a tag.
*
* @param tag
* @param tag - The tag to delete
*/
export async function deleteTag(tag: TagWithId): Promise<void> {
const path = '/systemtags/' + tag.id
@ -164,9 +168,10 @@ type TagObjectResponse = {
}
/**
* Get the objects for a tag.
*
* @param tag
* @param type
* @param tag - The tag to get the objects for
* @param type - The type of the objects
*/
export async function getTagObjects(tag: TagWithId, type: string): Promise<TagObjectResponse> {
const path = `/systemtags/${tag.id}/${type}`
@ -178,7 +183,7 @@ export async function getTagObjects(tag: TagWithId, type: string): Promise<TagOb
</d:prop>
</d:propfind>`
const response = await davClient.stat(path, { data, details: true })
const response = await davClient.stat(path, { data, details: true }) as ResponseDataDetailed<FileStat>
const etag = response?.data?.props?.getetag || '""'
const objects = Object.values(response?.data?.props?.['object-ids'] || []).flat() as TagObject[]
@ -228,15 +233,12 @@ export async function setTagObjects(tag: TagWithId, type: string, objectIds: Tag
})
}
type OcsResponse = {
ocs: NonNullable<unknown>
}
/**
* Update the system tags admin restriction setting.
*
* @param isAllowed
* @param isAllowed - True if system tags creation is allowed for non-admins
*/
export async function updateSystemTagsAdminRestriction(isAllowed: boolean): Promise<OcsResponse> {
export async function updateSystemTagsAdminRestriction(isAllowed: boolean): Promise<OCSResponse> {
// Convert to string for compatibility
const isAllowedString = isAllowed ? '1' : '0'
@ -247,9 +249,9 @@ export async function updateSystemTagsAdminRestriction(isAllowed: boolean): Prom
await confirmPassword()
const res = await axios.post(url, {
const { data } = await axios.post(url, {
value: isAllowedString,
})
return res.data
return data
}

View file

@ -1,30 +1,25 @@
/**
/*!
* SPDX-FileCopyrightText: 2023 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
import { getRequestToken, onRequestTokenUpdate } from '@nextcloud/auth'
import { generateRemoteUrl } from '@nextcloud/router'
import { createClient } from 'webdav'
import type { Node } from '@nextcloud/files'
import type { FileStat, ResponseDataDetailed } from 'webdav'
// init webdav client
const rootUrl = generateRemoteUrl('dav')
export const davClient = createClient(rootUrl)
import { getClient, getDefaultPropfind, getRootPath, resultToNode } from '@nextcloud/files/dav'
export const davClient = getClient()
// set CSRF token header
/**
* Fetches a node from the given path
*
* @param token
* @param path - The path to fetch the node from
*/
function setHeaders(token: string | null) {
davClient.setHeaders({
// Add this so the server knows it is an request from the browser
'X-Requested-With': 'XMLHttpRequest',
// Inject user auth
requesttoken: token ?? '',
})
export async function fetchNode(path: string): Promise<Node> {
const propfindPayload = getDefaultPropfind()
const result = await davClient.stat(`${getRootPath()}${path}`, {
details: true,
data: propfindPayload,
}) as ResponseDataDetailed<FileStat>
return resultToNode(result.data)
}
// refresh headers when request token changes
onRequestTokenUpdate(setHeaders)
setHeaders(getRequestToken())

View file

@ -4,17 +4,18 @@
*/
import type { FileStat, ResponseDataDetailed } from 'webdav'
import type { ServerTagWithId, Tag, TagWithId } from '../types.js'
import type { ServerTagWithId, Tag, TagWithId } from '../types.ts'
import { t } from '@nextcloud/l10n'
import logger from '../logger.ts'
import { formatTag, parseTags } from '../utils.js'
import { createTag, fetchTagsPayload } from './api.js'
import { davClient } from './davClient.js'
import { formatTag, parseTags } from '../utils.ts'
import { createTag, fetchTagsPayload } from './api.ts'
import { davClient } from './davClient.ts'
/**
* Fetch all tags for a given file (by id).
*
* @param fileId
* @param fileId - The id of the file to fetch tags for
*/
export async function fetchTagsForFile(fileId: number): Promise<TagWithId[]> {
const path = '/systemtags-relations/files/' + fileId
@ -50,9 +51,10 @@ export async function createTagForFile(tag: Tag, fileId: number): Promise<number
}
/**
* Set a tag for a given file (by id).
*
* @param tag
* @param fileId
* @param tag - The tag to set
* @param fileId - The id of the file to set the tag for
*/
export async function setTagForFile(tag: TagWithId | ServerTagWithId, fileId: number): Promise<void> {
const path = '/systemtags-relations/files/' + fileId + '/' + tag.id
@ -69,9 +71,10 @@ export async function setTagForFile(tag: TagWithId | ServerTagWithId, fileId: nu
}
/**
* Delete a tag for a given file (by id).
*
* @param tag
* @param fileId
* @param tag - The tag to delete
* @param fileId - The id of the file to delete the tag for
*/
export async function deleteTagForFile(tag: TagWithId, fileId: number): Promise<void> {
const path = '/systemtags-relations/files/' + fileId + '/' + tag.id

View file

@ -1,7 +1,8 @@
/**
/*!
* SPDX-FileCopyrightText: 2023 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
import type { ContentsWithRoot } from '@nextcloud/files'
import type { FileStat, ResponseDataDetailed } from 'webdav'
import type { TagWithId } from '../types.ts'
@ -16,8 +17,9 @@ const rootPath = '/systemtags'
const client = getClient()
/**
* Format the REPORT payload to filter files by tag
*
* @param tagId
* @param tagId - The tag ID
*/
function formatReportPayload(tagId: number) {
return `<?xml version="1.0"?>
@ -32,8 +34,9 @@ function formatReportPayload(tagId: number) {
}
/**
* Convert a tag to a Folder node
*
* @param tag
* @param tag - The tag
*/
function tagToNode(tag: TagWithId): Folder {
return new Folder({
@ -51,8 +54,9 @@ function tagToNode(tag: TagWithId): Folder {
}
/**
* Get the contents of a folder or tag
*
* @param path
* @param path - The path to the folder or tag
*/
export async function getContents(path = '/'): Promise<ContentsWithRoot> {
// List tags in the root
@ -71,9 +75,13 @@ export async function getContents(path = '/'): Promise<ContentsWithRoot> {
}
}
const tagId = parseInt(path.split('/', 2)[1])
const tag = tagsCache.find((tag) => tag.id === tagId)
const tagIdStr = path.split('/', 2)[1]
if (!tagIdStr || isNaN(parseInt(tagIdStr))) {
throw new Error('Invalid tag ID')
}
const tagId = parseInt(tagIdStr)
const tag = tagsCache.find((tag) => tag.id === tagId)
if (!tag) {
throw new Error('Tag not found')
}

View file

@ -5,10 +5,9 @@
import type { INode } from '@nextcloud/files'
import type { DAVResultResponseProps } from 'webdav'
import type { BaseTag, ServerTag, Tag, TagWithId } from './types.js'
import type { BaseTag, ServerTag, Tag, TagWithId } from './types.ts'
import camelCase from 'camelcase'
import Vue from 'vue'
import { emit } from '@nextcloud/event-bus'
export const defaultBaseTag: BaseTag = {
userVisible: true,
@ -16,13 +15,25 @@ export const defaultBaseTag: BaseTag = {
canAssign: true,
}
const propertyMappings = Object.freeze({
'display-name': 'displayName',
'user-visible': 'userVisible',
'user-assignable': 'userAssignable',
'can-assign': 'canAssign',
})
/**
* Parse tags from WebDAV response
*
* @param tags
* @param tags - Array of tags from WebDAV response
*/
export function parseTags(tags: { props: DAVResultResponseProps }[]): TagWithId[] {
return tags.map(({ props }) => Object.fromEntries(Object.entries(props)
.map(([key, value]) => [camelCase(key), camelCase(key) === 'displayName' ? String(value) : value]))) as TagWithId[]
.map(([key, value]) => {
key = propertyMappings[key] ?? key
value = key === 'displayName' ? String(value) : value
return [key, value]
})) as unknown as TagWithId)
}
/**
@ -49,8 +60,9 @@ export function parseIdFromLocation(url: string): number {
}
/**
* Format a tag for WebDAV operations
*
* @param initialTag
* @param initialTag - Tag to format
*/
export function formatTag(initialTag: Tag | ServerTag): ServerTag {
if ('name' in initialTag && !('displayName' in initialTag)) {
@ -65,8 +77,9 @@ export function formatTag(initialTag: Tag | ServerTag): ServerTag {
}
/**
* Get system tags from a node
*
* @param node
* @param node - The node to get tags from
*/
export function getNodeSystemTags(node: INode): string[] {
const attribute = node.attributes?.['system-tags']?.['system-tag']
@ -88,12 +101,14 @@ export function getNodeSystemTags(node: INode): string[] {
}
/**
* Set system tags on a node
*
* @param node
* @param tags
* @param node - The node to set tags on
* @param tags - The tags to set
*/
export function setNodeSystemTags(node: INode, tags: string[]): void {
Vue.set(node.attributes, 'system-tags', {
node.attributes['system-tags'] = {
'system-tag': tags,
})
}
emit('files:node:updated', node)
}

View file

@ -2,6 +2,7 @@
* SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
import Color from 'color'
type hexColor = `#${string & (

View file

@ -3,6 +3,64 @@
- SPDX-License-Identifier: AGPL-3.0-or-later
-->
<script setup lang="ts">
import type { TagWithId } from '../types.ts'
import { showError } from '@nextcloud/dialogs'
import { t } from '@nextcloud/l10n'
import { onBeforeMount, ref } from 'vue'
import NcLoadingIcon from '@nextcloud/vue/components/NcLoadingIcon'
import NcSettingsSection from '@nextcloud/vue/components/NcSettingsSection'
import SystemTagForm from '../components/SystemTagForm.vue'
import SystemTagsCreationControl from '../components/SystemTagsCreationControl.vue'
import logger from '../logger.ts'
import { fetchTags } from '../services/api.ts'
const loadingTags = ref(false)
const tags = ref<TagWithId[]>([])
onBeforeMount(async () => {
loadingTags.value = true
try {
tags.value = await fetchTags()
} catch (error) {
showError(t('systemtags', 'Failed to load tags'))
logger.error('Failed to load tags', { error })
}
loadingTags.value = false
})
/**
* Handle tag creation
*
* @param tag - The created tag
*/
function handleCreate(tag: TagWithId) {
tags.value.unshift(tag)
}
/**
* Handle tag update
*
* @param tag - The updated tag
*/
function handleUpdate(tag: TagWithId) {
const tagIndex = tags.value.findIndex((currTag) => currTag.id === tag.id)
tags.value.splice(tagIndex, 1)
tags.value.unshift(tag)
}
/**
* Handle tag deletion
*
* @param tag - The deleted tag
*/
function handleDelete(tag: TagWithId) {
const tagIndex = tags.value.findIndex((currTag) => currTag.id === tag.id)
tags.value.splice(tagIndex, 1)
}
</script>
<template>
<NcSettingsSection
:name="t('systemtags', 'Collaborative tags')"
@ -20,65 +78,3 @@
@tag:deleted="handleDelete" />
</NcSettingsSection>
</template>
<script lang="ts">
import type { TagWithId } from '../types.js'
import { showError } from '@nextcloud/dialogs'
import { t } from '@nextcloud/l10n'
import Vue from 'vue'
import NcLoadingIcon from '@nextcloud/vue/components/NcLoadingIcon'
import NcSettingsSection from '@nextcloud/vue/components/NcSettingsSection'
import SystemTagForm from '../components/SystemTagForm.vue'
import SystemTagsCreationControl from '../components/SystemTagsCreationControl.vue'
import logger from '../logger.js'
import { fetchTags } from '../services/api.js'
export default Vue.extend({
name: 'SystemTagsSection',
components: {
NcLoadingIcon,
NcSettingsSection,
SystemTagForm,
SystemTagsCreationControl,
},
data() {
return {
loadingTags: false,
tags: [] as TagWithId[],
}
},
async created() {
this.loadingTags = true
try {
this.tags = await fetchTags()
} catch (error) {
showError(t('systemtags', 'Failed to load tags'))
logger.error('Failed to load tags', { error })
}
this.loadingTags = false
},
methods: {
t,
handleCreate(tag: TagWithId) {
this.tags.unshift(tag)
},
handleUpdate(tag: TagWithId) {
const tagIndex = this.tags.findIndex((currTag) => currTag.id === tag.id)
this.tags.splice(tagIndex, 1)
this.tags.unshift(tag)
},
handleDelete(tag: TagWithId) {
const tagIndex = this.tags.findIndex((currTag) => currTag.id === tag.id)
this.tags.splice(tagIndex, 1)
},
},
})
</script>

View file

@ -70,10 +70,6 @@ module.exports = {
'vue-settings-personal-webauthn': path.join(__dirname, 'apps/settings/src', 'main-personal-webauth.js'),
'declarative-settings-forms': path.join(__dirname, 'apps/settings/src', 'main-declarative-settings-forms.ts'),
},
systemtags: {
init: path.join(__dirname, 'apps/systemtags/src', 'init.ts'),
admin: path.join(__dirname, 'apps/systemtags/src', 'admin.ts'),
},
updatenotification: {
init: path.join(__dirname, 'apps/updatenotification/src', 'init.ts'),
'view-changelog-page': path.join(__dirname, 'apps/updatenotification/src', 'view-changelog-page.ts'),

View file

@ -50,6 +50,10 @@ const modules = {
sharebymail: {
'admin-settings': resolve(import.meta.dirname, 'apps/sharebymail/src', 'settings-admin.ts'),
},
systemtags: {
init: resolve(import.meta.dirname, 'apps/systemtags/src', 'init.ts'),
admin: resolve(import.meta.dirname, 'apps/systemtags/src', 'admin.ts'),
},
theming: {
'settings-personal': resolve(import.meta.dirname, 'apps/theming/src', 'settings-personal.ts'),
'settings-admin': resolve(import.meta.dirname, 'apps/theming/src', 'settings-admin.ts'),

2
dist/1142-1142.js vendored

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View file

@ -1 +0,0 @@
1142-1142.js.license

2
dist/1598-1598.js vendored Normal file

File diff suppressed because one or more lines are too long

1
dist/1598-1598.js.map vendored Normal file

File diff suppressed because one or more lines are too long

1
dist/1598-1598.js.map.license vendored Symbolic link
View file

@ -0,0 +1 @@
1598-1598.js.license

2
dist/2605-2605.js vendored

File diff suppressed because one or more lines are too long

View file

@ -1,459 +0,0 @@
SPDX-License-Identifier: MIT
SPDX-License-Identifier: ISC
SPDX-License-Identifier: GPL-3.0-or-later
SPDX-License-Identifier: BSD-3-Clause
SPDX-License-Identifier: BSD-2-Clause
SPDX-License-Identifier: AGPL-3.0-or-later
SPDX-License-Identifier: (MPL-2.0 OR Apache-2.0)
SPDX-FileCopyrightText: xiaokai <kexiaokai@gmail.com>
SPDX-FileCopyrightText: string_decoder developers
SPDX-FileCopyrightText: readable-stream developers
SPDX-FileCopyrightText: qs developers
SPDX-FileCopyrightText: jden <jason@denizac.org>
SPDX-FileCopyrightText: inherits developers
SPDX-FileCopyrightText: escape-html developers
SPDX-FileCopyrightText: defunctzombie
SPDX-FileCopyrightText: debounce developers
SPDX-FileCopyrightText: atomiks
SPDX-FileCopyrightText: Varun A P
SPDX-FileCopyrightText: Tobias Koppers @sokra
SPDX-FileCopyrightText: T. Jameson Little <t.jameson.little@gmail.com>
SPDX-FileCopyrightText: Sindre Sorhus
SPDX-FileCopyrightText: Roman Shtylman <shtylman@gmail.com>
SPDX-FileCopyrightText: Rob Cresswell <robcresswell@pm.me>
SPDX-FileCopyrightText: Raynos <raynos2@gmail.com>
SPDX-FileCopyrightText: Perry Mitchell <perry@perrymitchell.net>
SPDX-FileCopyrightText: Paul Vorbach <paul@vorba.ch> (http://paul.vorba.ch)
SPDX-FileCopyrightText: Paul Vorbach <paul@vorb.de> (http://vorb.de)
SPDX-FileCopyrightText: Olivier Scherrer <pode.fr@gmail.com>
SPDX-FileCopyrightText: Nextcloud GmbH and Nextcloud contributors
SPDX-FileCopyrightText: Nathan Rajlich <nathan@tootallnate.net> (http://n8.io/)
SPDX-FileCopyrightText: Matt Zabriskie
SPDX-FileCopyrightText: Mathias Bynens
SPDX-FileCopyrightText: Julian Gruber
SPDX-FileCopyrightText: Joyent
SPDX-FileCopyrightText: José F. Romaniello <jfromaniello@gmail.com> (http://joseoncode.com)
SPDX-FileCopyrightText: Jordan Harband <ljharb@gmail.com>
SPDX-FileCopyrightText: Jordan Harband
SPDX-FileCopyrightText: Jordan Harbamd <ljharb@gmail.com>
SPDX-FileCopyrightText: John Hiesey
SPDX-FileCopyrightText: James Halliday
SPDX-FileCopyrightText: Isaac Z. Schlueter <i@izs.me> (http://blog.izs.me)
SPDX-FileCopyrightText: Irakli Gozalishvili <rfobic@gmail.com> (http://jeditoolkit.com)
SPDX-FileCopyrightText: Guillaume Chau <guillaume.b.chau@gmail.com>
SPDX-FileCopyrightText: Guillaume Chau
SPDX-FileCopyrightText: GitHub Inc.
SPDX-FileCopyrightText: Feross Aboukhadijeh
SPDX-FileCopyrightText: Felix Boehm <me@feedic.com>
SPDX-FileCopyrightText: Evan You
SPDX-FileCopyrightText: Eduardo San Martin Morote
SPDX-FileCopyrightText: Dylan Piercey <pierceydylan@gmail.com>
SPDX-FileCopyrightText: Dr.-Ing. Mario Heiderich, Cure53 <mario@cure53.de> (https://cure53.de/)
SPDX-FileCopyrightText: David Clark
SPDX-FileCopyrightText: Christoph Wurst
SPDX-FileCopyrightText: Ben Drucker
SPDX-FileCopyrightText: Arnout Kazemier
SPDX-FileCopyrightText: Anthony Fu <https://github.com/antfu>
SPDX-FileCopyrightText: Anthony Fu <anthonyfu117@hotmail.com>
SPDX-FileCopyrightText: Amit Gupta (https://solothought.com)
SPDX-FileCopyrightText: Amit Gupta (https://amitkumargupta.work/)
SPDX-FileCopyrightText: @nextcloud/dialogs developers
This file is generated from multiple sources. Included packages:
- @buttercup/fetch
- version: 0.2.1
- license: MIT
- @floating-ui/core
- version: 1.7.3
- license: MIT
- @floating-ui/utils
- version: 0.2.10
- license: MIT
- @nextcloud/auth
- version: 2.5.3
- license: GPL-3.0-or-later
- @nextcloud/axios
- version: 2.5.2
- license: GPL-3.0-or-later
- @nextcloud/browser-storage
- version: 0.5.0
- license: GPL-3.0-or-later
- @nextcloud/capabilities
- version: 1.2.1
- license: GPL-3.0-or-later
- @nextcloud/vue
- version: 9.3.3
- license: AGPL-3.0-or-later
- @vueuse/core
- version: 14.1.0
- license: MIT
- @vueuse/shared
- version: 14.1.0
- license: MIT
- focus-trap
- version: 7.6.6
- license: MIT
- vue-router
- version: 4.6.4
- license: MIT
- vue
- version: 3.5.26
- license: MIT
- @nextcloud/dialogs
- version: 7.2.0
- license: AGPL-3.0-or-later
- semver
- version: 7.7.2
- license: ISC
- @nextcloud/event-bus
- version: 3.3.3
- license: GPL-3.0-or-later
- @nextcloud/initial-state
- version: 3.0.0
- license: GPL-3.0-or-later
- @nextcloud/l10n
- version: 3.4.1
- license: GPL-3.0-or-later
- @nextcloud/logger
- version: 3.0.3
- license: GPL-3.0-or-later
- @nextcloud/vue
- version: 9.2.0
- license: AGPL-3.0-or-later
- @vue/reactivity
- version: 3.5.24
- license: MIT
- @vue/runtime-core
- version: 3.5.24
- license: MIT
- @vue/runtime-dom
- version: 3.5.24
- license: MIT
- @vue/shared
- version: 3.5.24
- license: MIT
- vue-router
- version: 4.6.3
- license: MIT
- vue
- version: 3.5.24
- license: MIT
- @nextcloud/password-confirmation
- version: 6.0.2
- license: MIT
- @nextcloud/paths
- version: 3.0.0
- license: GPL-3.0-or-later
- @nextcloud/router
- version: 3.1.0
- license: GPL-3.0-or-later
- @nextcloud/vue
- version: 8.35.3
- license: AGPL-3.0-or-later
- @vue/devtools-api
- version: 6.6.4
- license: MIT
- @vue/reactivity
- version: 3.5.26
- license: MIT
- @vue/runtime-core
- version: 3.5.26
- license: MIT
- @vue/runtime-dom
- version: 3.5.26
- license: MIT
- @vue/shared
- version: 3.5.26
- license: MIT
- @vueuse/core
- version: 11.3.0
- license: MIT
- @vueuse/shared
- version: 11.3.0
- license: MIT
- available-typed-arrays
- version: 1.0.7
- license: MIT
- axios
- version: 1.12.2
- license: MIT
- balanced-match
- version: 1.0.2
- license: MIT
- base-64
- version: 1.0.0
- license: MIT
- base64-js
- version: 1.5.1
- license: MIT
- brace-expansion
- version: 2.0.2
- license: MIT
- builtin-status-codes
- version: 3.0.0
- license: MIT
- byte-length
- version: 1.0.2
- license: MIT
- call-bind-apply-helpers
- version: 1.0.2
- license: MIT
- call-bind
- version: 1.0.8
- license: MIT
- call-bound
- version: 1.0.4
- license: MIT
- camelcase
- version: 9.0.0
- license: MIT
- charenc
- version: 0.0.2
- license: BSD-3-Clause
- crypt
- version: 0.0.2
- license: BSD-3-Clause
- css-loader
- version: 7.1.2
- license: MIT
- debounce
- version: 3.0.0
- license: MIT
- define-data-property
- version: 1.1.4
- license: MIT
- dompurify
- version: 3.3.1
- license: (MPL-2.0 OR Apache-2.0)
- dunder-proto
- version: 1.0.1
- license: MIT
- es-define-property
- version: 1.0.1
- license: MIT
- es-errors
- version: 1.3.0
- license: MIT
- es-object-atoms
- version: 1.1.1
- license: MIT
- escape-html
- version: 1.0.3
- license: MIT
- events
- version: 3.3.0
- license: MIT
- floating-vue
- version: 1.0.0-beta.19
- license: MIT
- focus-trap
- version: 7.8.0
- license: MIT
- for-each
- version: 0.3.5
- license: MIT
- function-bind
- version: 1.1.2
- license: MIT
- generator-function
- version: 2.0.1
- license: MIT
- get-intrinsic
- version: 1.3.0
- license: MIT
- get-proto
- version: 1.0.1
- license: MIT
- gopd
- version: 1.2.0
- license: MIT
- has-property-descriptors
- version: 1.0.2
- license: MIT
- has-symbols
- version: 1.1.0
- license: MIT
- has-tostringtag
- version: 1.0.2
- license: MIT
- hasown
- version: 2.0.2
- license: MIT
- hot-patcher
- version: 2.0.1
- license: MIT
- https-browserify
- version: 1.0.0
- license: MIT
- ieee754
- version: 1.2.1
- license: BSD-3-Clause
- inherits
- version: 2.0.4
- license: ISC
- is-arguments
- version: 1.2.0
- license: MIT
- is-buffer
- version: 1.1.6
- license: MIT
- is-callable
- version: 1.2.7
- license: MIT
- is-generator-function
- version: 1.1.2
- license: MIT
- is-regex
- version: 1.2.1
- license: MIT
- is-typed-array
- version: 1.1.15
- license: MIT
- layerr
- version: 3.0.0
- license: MIT
- math-intrinsics
- version: 1.1.0
- license: MIT
- md5
- version: 2.3.0
- license: BSD-3-Clause
- nested-property
- version: 4.0.0
- license: MIT
- buffer
- version: 6.0.3
- license: MIT
- object-inspect
- version: 1.13.4
- license: MIT
- path-posix
- version: 1.0.0
- license: ISC
- possible-typed-array-names
- version: 1.1.0
- license: MIT
- process
- version: 0.11.10
- license: MIT
- punycode
- version: 1.4.1
- license: MIT
- qs
- version: 6.14.1
- license: BSD-3-Clause
- querystringify
- version: 2.2.0
- license: MIT
- requires-port
- version: 1.0.0
- license: MIT
- safe-buffer
- version: 5.2.1
- license: MIT
- safe-regex-test
- version: 1.1.0
- license: MIT
- set-function-length
- version: 1.2.2
- license: MIT
- side-channel-list
- version: 1.0.0
- license: MIT
- side-channel-map
- version: 1.0.1
- license: MIT
- side-channel-weakmap
- version: 1.0.2
- license: MIT
- side-channel
- version: 1.1.0
- license: MIT
- readable-stream
- version: 3.6.2
- license: MIT
- stream-browserify
- version: 3.0.0
- license: MIT
- readable-stream
- version: 3.6.2
- license: MIT
- stream-http
- version: 3.2.0
- license: MIT
- string_decoder
- version: 1.3.0
- license: MIT
- style-loader
- version: 4.0.0
- license: MIT
- tabbable
- version: 6.4.0
- license: MIT
- toastify-js
- version: 1.12.0
- license: MIT
- url-join
- version: 5.0.0
- license: MIT
- url-parse
- version: 1.5.10
- license: MIT
- url
- version: 0.11.4
- license: MIT
- util-deprecate
- version: 1.0.2
- license: MIT
- util
- version: 0.12.5
- license: MIT
- vue-color
- version: 2.8.2
- license: MIT
- vue-demi
- version: 0.14.10
- license: MIT
- vue-loader
- version: 15.11.1
- license: MIT
- vue-material-design-icons
- version: 5.3.1
- license: MIT
- vue
- version: 2.7.16
- license: MIT
- entities
- version: 6.0.1
- license: BSD-2-Clause
- fast-xml-parser
- version: 4.5.3
- license: MIT
- minimatch
- version: 9.0.5
- license: ISC
- strnum
- version: 1.1.2
- license: MIT
- webdav
- version: 5.8.0
- license: MIT
- which-typed-array
- version: 1.1.19
- license: MIT
- xtend
- version: 4.0.2
- license: MIT
- base64-js
- version: 1.5.1
- license: MIT
- buffer
- version: 5.7.1
- license: MIT
- ieee754
- version: 1.2.1
- license: BSD-3-Clause
- nextcloud
- version: 1.0.0
- license: AGPL-3.0-or-later

File diff suppressed because one or more lines are too long

View file

@ -1 +0,0 @@
2605-2605.js.license

2
dist/6692-6692.js vendored

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View file

@ -1 +0,0 @@
6692-6692.js.license

2
dist/9429-9429.js vendored Normal file

File diff suppressed because one or more lines are too long

1
dist/9429-9429.js.map vendored Normal file

File diff suppressed because one or more lines are too long

1
dist/9429-9429.js.map.license vendored Symbolic link
View file

@ -0,0 +1 @@
9429-9429.js.license

View file

@ -0,0 +1,2 @@
import{b as g,q as y,s as v,c as p,u as o,o as n,J as h,w as _,g as V,t as b,v as x,r as M,j as d,e as f,F as q,C as w,E as K,G as U}from"./runtime-dom.esm-bundler-Bpt0bWgp.chunk.mjs";import{c as j}from"./index-DNyFZ0q1.chunk.mjs";import{a as C}from"./index-JpgrUA2Z-Btt9G24P.chunk.mjs";import{t as s}from"./translation-DoG5ZELJ-Bni_xMHF.chunk.mjs";import{g as E}from"./createElementId-DhjFt1I9-COdYgGCC.chunk.mjs";import{N}from"./logger-D3RVzcfQ-DhmPs7Vh.chunk.mjs";import{N as S}from"./NcSelect-rH_0zphV-RrpfdNvw.chunk.mjs";import{N as A}from"./NcCheckboxRadioSwitch-ChNSuhe6-D-zzyccP.chunk.mjs";import{N as L}from"./NcPasswordField-DYF18Cdo-BbpNSGg_.chunk.mjs";import{_ as z}from"./TrashCanOutline-BwjpsJlQ.chunk.mjs";import{C as c,a as k}from"./types-C3MFFrct.chunk.mjs";import{l as B}from"./logger-resIultJ.chunk.mjs";const P=g({__name:"ConfigurationEntry",props:y({configKey:{},configOption:{}},{modelValue:{type:[String,Boolean],default:""},modelModifiers:{}}),emits:["update:modelValue"],setup(e){const a=v(e,"modelValue");return(t,i)=>e.configOption.type!==o(c).Boolean?(n(),p(h(e.configOption.type===o(c).Password?o(L):o(z)),{key:0,modelValue:a.value,"onUpdate:modelValue":i[0]||(i[0]=l=>a.value=l),name:e.configKey,required:!(e.configOption.flags&o(k).Optional),label:e.configOption.value,title:e.configOption.tooltip},null,8,["modelValue","name","required","label","title"])):(n(),p(o(A),{key:1,modelValue:a.value,"onUpdate:modelValue":i[1]||(i[1]=l=>a.value=l),type:"switch",title:e.configOption.tooltip},{default:_(()=>[V(b(e.configOption.value),1)]),_:1},8,["modelValue","title"]))}}),R=g({__name:"AuthMechanismRsa",props:y({authMechanism:{}},{modelValue:{required:!0},modelModifiers:{}}),emits:["update:modelValue"],setup(e){const a=v(e,"modelValue"),t=M();x(t,()=>{t.value&&(a.value.private_key="",a.value.public_key="")});async function i(){try{const{data:l}=await j.post(E("/apps/files_external/ajax/public_key.php"),{keyLength:t.value});a.value.private_key=l.data.private_key,a.value.public_key=l.data.public_key}catch(l){B.error("Error generating RSA key pair",{error:l}),C(s("files_external","Error generating key pair"))}}return(l,m)=>(n(),d("div",null,[(n(!0),d(q,null,w(e.authMechanism.configuration,(r,u)=>K((n(),p(P,{key:r.value,modelValue:a.value[u],"onUpdate:modelValue":O=>a.value[u]=O,configKey:u,configOption:r},null,8,["modelValue","onUpdate:modelValue","configKey","configOption"])),[[U,!(r.flags&o(k).Hidden)]])),128)),f(o(S),{modelValue:t.value,"onUpdate:modelValue":m[0]||(m[0]=r=>t.value=r),clearable:!1,inputLabel:o(s)("files_external","Key size"),options:[1024,2048,4096],required:""},null,8,["modelValue","inputLabel"]),f(o(N),{disabled:!t.value,wide:"",onClick:i},{default:_(()=>[V(b(o(s)("files_external","Generate keys")),1)]),_:1},8,["disabled"])]))}}),$=Object.freeze(Object.defineProperty({__proto__:null,default:R},Symbol.toStringTag,{value:"Module"}));export{$ as A,P as _};
//# sourceMappingURL=AuthMechanismRsa-CpHwZ9ay.chunk.mjs.map

File diff suppressed because one or more lines are too long

View file

@ -1,2 +0,0 @@
import{b as g,p as y,q as v,c as p,u as o,o as n,K as h,w as _,g as V,t as b,s as x,r as K,j as d,e as f,F as M,C as q,E as w,G as U}from"./runtime-dom.esm-bundler-CBTFVsZ1.chunk.mjs";import{c as j}from"./index-CeZOua3E.chunk.mjs";import{a as C}from"./index-JpgrUA2Z-Cg8qxgsw.chunk.mjs";import{t as s}from"./translation-DoG5ZELJ-DRDc35uB.chunk.mjs";import{g as E}from"./createElementId-DhjFt1I9-CbtAsEAv.chunk.mjs";import{b as S}from"./NcNoteCard-Cok_4Fld-CEiA7MRo.chunk.mjs";import{N as A}from"./NcSelect-rH_0zphV-DIrEVY9H.chunk.mjs";import{N as L}from"./NcCheckboxRadioSwitch-ChNSuhe6-J_hhfCys.chunk.mjs";import{N}from"./NcPasswordField-DYF18Cdo-CDrhVNCN.chunk.mjs";import{_ as z}from"./TrashCanOutline-t5kbV5NX.chunk.mjs";import{C as c,a as k}from"./types-CxkvAyaz.chunk.mjs";import{l as B}from"./logger-resIultJ.chunk.mjs";const P=g({__name:"ConfigurationEntry",props:y({configKey:{},configOption:{}},{modelValue:{type:[String,Boolean],default:""},modelModifiers:{}}),emits:["update:modelValue"],setup(e){const a=v(e,"modelValue");return(t,i)=>e.configOption.type!==o(c).Boolean?(n(),p(h(e.configOption.type===o(c).Password?o(N):o(z)),{key:0,modelValue:a.value,"onUpdate:modelValue":i[0]||(i[0]=l=>a.value=l),name:e.configKey,required:!(e.configOption.flags&o(k).Optional),label:e.configOption.value,title:e.configOption.tooltip},null,8,["modelValue","name","required","label","title"])):(n(),p(o(L),{key:1,modelValue:a.value,"onUpdate:modelValue":i[1]||(i[1]=l=>a.value=l),type:"switch",title:e.configOption.tooltip},{default:_(()=>[V(b(e.configOption.value),1)]),_:1},8,["modelValue","title"]))}}),R=g({__name:"AuthMechanismRsa",props:y({authMechanism:{}},{modelValue:{required:!0},modelModifiers:{}}),emits:["update:modelValue"],setup(e){const a=v(e,"modelValue"),t=K();x(t,()=>{t.value&&(a.value.private_key="",a.value.public_key="")});async function i(){try{const{data:l}=await j.post(E("/apps/files_external/ajax/public_key.php"),{keyLength:t.value});a.value.private_key=l.data.private_key,a.value.public_key=l.data.public_key}catch(l){B.error("Error generating RSA key pair",{error:l}),C(s("files_external","Error generating key pair"))}}return(l,m)=>(n(),d("div",null,[(n(!0),d(M,null,q(e.authMechanism.configuration,(r,u)=>w((n(),p(P,{key:r.value,modelValue:a.value[u],"onUpdate:modelValue":O=>a.value[u]=O,configKey:u,configOption:r},null,8,["modelValue","onUpdate:modelValue","configKey","configOption"])),[[U,!(r.flags&o(k).Hidden)]])),128)),f(o(A),{modelValue:t.value,"onUpdate:modelValue":m[0]||(m[0]=r=>t.value=r),clearable:!1,inputLabel:o(s)("files_external","Key size"),options:[1024,2048,4096],required:""},null,8,["modelValue","inputLabel"]),f(o(S),{disabled:!t.value,wide:"",onClick:i},{default:_(()=>[V(b(o(s)("files_external","Generate keys")),1)]),_:1},8,["disabled"])]))}}),$=Object.freeze(Object.defineProperty({__proto__:null,default:R},Symbol.toStringTag,{value:"Module"}));export{$ as A,P as _};
//# sourceMappingURL=AuthMechanismRsa-mz_2_HLA.chunk.mjs.map

View file

@ -1,2 +1,2 @@
import{r as g,u as h,_ as p,b as C}from"./createElementId-DhjFt1I9-CbtAsEAv.chunk.mjs";import{b as _,j as i,o as e,k as o,l as s,m as y,g as b,t as r,u as d,e as k,y as u}from"./runtime-dom.esm-bundler-CBTFVsZ1.chunk.mjs";import{a as H}from"./index-xFugdZPW.chunk.mjs";const A={name:"HelpCircleIcon",emits:["click"],props:{title:{type:String},fillColor:{type:String,default:"currentColor"},size:{type:Number,default:24}}},v=["aria-hidden","aria-label"],V=["fill","width","height"],w={d:"M15.07,11.25L14.17,12.17C13.45,12.89 13,13.5 13,15H11V14.5C11,13.39 11.45,12.39 12.17,11.67L13.41,10.41C13.78,10.05 14,9.55 14,9C14,7.89 13.1,7 12,7A2,2 0 0,0 10,9H8A4,4 0 0,1 12,5A4,4 0 0,1 16,9C16,9.88 15.64,10.67 15.07,11.25M13,19H11V17H13M12,2A10,10 0 0,0 2,12A10,10 0 0,0 12,22A10,10 0 0,0 22,12C22,6.47 17.5,2 12,2Z"},z={key:0};function M(a,l,t,c,f,m){return e(),i("span",u(a.$attrs,{"aria-hidden":t.title?null:"true","aria-label":t.title,class:"material-design-icon help-circle-icon",role:"img",onClick:l[0]||(l[0]=n=>a.$emit("click",n))}),[(e(),i("svg",{fill:t.fillColor,class:"material-design-icon__svg",width:t.size,height:t.size,viewBox:"0 0 24 24"},[o("path",w,[t.title?(e(),i("title",z,r(t.title),1)):s("",!0)])],8,V))],16,v)}const S=p(A,[["render",M]]);g(h);const x={class:"settings-section"},$={class:"settings-section__name"},I=["aria-label","href","title"],N={key:0,class:"settings-section__desc"},U=_({__name:"NcSettingsSection",props:{name:{},description:{default:""},docUrl:{default:""}},setup(a){const l=C("External documentation");return(t,c)=>(e(),i("div",x,[o("h2",$,[b(r(t.name)+" ",1),t.docUrl?(e(),i("a",{key:0,"aria-label":d(l),class:"settings-section__info",href:t.docUrl,rel:"noreferrer nofollow",target:"_blank",title:d(l)},[k(S,{size:20})],8,I)):s("",!0)]),t.description?(e(),i("p",N,r(t.description),1)):s("",!0),y(t.$slots,"default",{},void 0,!0)]))}}),T=p(U,[["__scopeId","data-v-9cedb949"]]),B={name:"ContentCopyIcon",emits:["click"],props:{title:{type:String},fillColor:{type:String,default:"currentColor"},size:{type:Number,default:24}}},L=["aria-hidden","aria-label"],Z=["fill","width","height"],j={d:"M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"},E={key:0};function q(a,l,t,c,f,m){return e(),i("span",u(a.$attrs,{"aria-hidden":t.title?null:"true","aria-label":t.title,class:"material-design-icon content-copy-icon",role:"img",onClick:l[0]||(l[0]=n=>a.$emit("click",n))}),[(e(),i("svg",{fill:t.fillColor,class:"material-design-icon__svg",width:t.size,height:t.size,viewBox:"0 0 24 24"},[o("path",j,[t.title?(e(),i("title",E,r(t.title),1)):s("",!0)])],8,Z))],16,L)}const G=H(B,[["render",q]]);export{G as I,T as N};
//# sourceMappingURL=ContentCopy-2LIQisfo.chunk.mjs.map
import{r as g,z as h,_ as p,a as C}from"./createElementId-DhjFt1I9-COdYgGCC.chunk.mjs";import{b as _,j as i,o as e,k as o,l as s,m as k,g as y,t as r,u as d,e as H,z as f}from"./runtime-dom.esm-bundler-Bpt0bWgp.chunk.mjs";import{a as b}from"./index-xFugdZPW.chunk.mjs";const A={name:"HelpCircleIcon",emits:["click"],props:{title:{type:String},fillColor:{type:String,default:"currentColor"},size:{type:Number,default:24}}},v=["aria-hidden","aria-label"],z=["fill","width","height"],V={d:"M15.07,11.25L14.17,12.17C13.45,12.89 13,13.5 13,15H11V14.5C11,13.39 11.45,12.39 12.17,11.67L13.41,10.41C13.78,10.05 14,9.55 14,9C14,7.89 13.1,7 12,7A2,2 0 0,0 10,9H8A4,4 0 0,1 12,5A4,4 0 0,1 16,9C16,9.88 15.64,10.67 15.07,11.25M13,19H11V17H13M12,2A10,10 0 0,0 2,12A10,10 0 0,0 12,22A10,10 0 0,0 22,12C22,6.47 17.5,2 12,2Z"},w={key:0};function M(a,l,t,c,m,u){return e(),i("span",f(a.$attrs,{"aria-hidden":t.title?null:"true","aria-label":t.title,class:"material-design-icon help-circle-icon",role:"img",onClick:l[0]||(l[0]=n=>a.$emit("click",n))}),[(e(),i("svg",{fill:t.fillColor,class:"material-design-icon__svg",width:t.size,height:t.size,viewBox:"0 0 24 24"},[o("path",V,[t.title?(e(),i("title",w,r(t.title),1)):s("",!0)])],8,z))],16,v)}const S=p(A,[["render",M]]);g(h);const x={class:"settings-section"},$={class:"settings-section__name"},I=["aria-label","href","title"],N={key:0,class:"settings-section__desc"},U=_({__name:"NcSettingsSection",props:{name:{},description:{default:""},docUrl:{default:""}},setup(a){const l=C("External documentation");return(t,c)=>(e(),i("div",x,[o("h2",$,[y(r(t.name)+" ",1),t.docUrl?(e(),i("a",{key:0,"aria-label":d(l),class:"settings-section__info",href:t.docUrl,rel:"noreferrer nofollow",target:"_blank",title:d(l)},[H(S,{size:20})],8,I)):s("",!0)]),t.description?(e(),i("p",N,r(t.description),1)):s("",!0),k(t.$slots,"default",{},void 0,!0)]))}}),T=p(U,[["__scopeId","data-v-9cedb949"]]),B={name:"ContentCopyIcon",emits:["click"],props:{title:{type:String},fillColor:{type:String,default:"currentColor"},size:{type:Number,default:24}}},L=["aria-hidden","aria-label"],Z=["fill","width","height"],j={d:"M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"},E={key:0};function q(a,l,t,c,m,u){return e(),i("span",f(a.$attrs,{"aria-hidden":t.title?null:"true","aria-label":t.title,class:"material-design-icon content-copy-icon",role:"img",onClick:l[0]||(l[0]=n=>a.$emit("click",n))}),[(e(),i("svg",{fill:t.fillColor,class:"material-design-icon__svg",width:t.size,height:t.size,viewBox:"0 0 24 24"},[o("path",j,[t.title?(e(),i("title",E,r(t.title),1)):s("",!0)])],8,Z))],16,L)}const G=b(B,[["render",q]]);export{G as I,T as N};
//# sourceMappingURL=ContentCopy-B8R56AYC.chunk.mjs.map

File diff suppressed because one or more lines are too long

View file

@ -1,2 +0,0 @@
import{t}from"./translation-DoG5ZELJ-DRDc35uB.chunk.mjs";import{N as m}from"./index-JpgrUA2Z-Cg8qxgsw.chunk.mjs";import{N as d}from"./NcNoteCard-Cok_4Fld-CEiA7MRo.chunk.mjs";import{N as p}from"./NcPasswordField-DYF18Cdo-CDrhVNCN.chunk.mjs";import{_ as c}from"./TrashCanOutline-t5kbV5NX.chunk.mjs";import{b as g,c as f,o as h,w as x,e as s,u as e,r as n}from"./runtime-dom.esm-bundler-CBTFVsZ1.chunk.mjs";import"./index-Bndk0DrU.chunk.mjs";import"./index-xFugdZPW.chunk.mjs";import"./createElementId-DhjFt1I9-CbtAsEAv.chunk.mjs";import"./mdi-BiMJjngH.chunk.mjs";import"./index-CeZOua3E.chunk.mjs";import"./string_decoder-BO00msnV.chunk.mjs";import"./NcInputField-tt_Gi9ja-Cs0S2GF6.chunk.mjs";const $=g({__name:"CredentialsDialog",emits:["close"],setup(_){const o=n(""),r=n(""),u=[{label:t("files_external","Confirm"),type:"submit",variant:"primary"}];return(i,a)=>(h(),f(e(m),{buttons:u,class:"external-storage-auth",closeOnClickOutside:"","data-cy-external-storage-auth":"",isForm:"",name:e(t)("files_external","Storage credentials"),outTransition:"",onSubmit:a[2]||(a[2]=l=>i.$emit("close",{login:o.value,password:r.value})),"onUpdate:open":a[3]||(a[3]=l=>i.$emit("close"))},{default:x(()=>[s(e(d),{class:"external-storage-auth__header",text:e(t)("files_external","To access the storage, you need to provide the authentication credentials."),type:"info"},null,8,["text"]),s(e(c),{modelValue:o.value,"onUpdate:modelValue":a[0]||(a[0]=l=>o.value=l),autofocus:"",class:"external-storage-auth__login","data-cy-external-storage-auth-dialog-login":"",label:e(t)("files_external","Login"),placeholder:e(t)("files_external","Enter the storage login"),minlength:"2",name:"login",required:""},null,8,["modelValue","label","placeholder"]),s(e(p),{modelValue:r.value,"onUpdate:modelValue":a[1]||(a[1]=l=>r.value=l),class:"external-storage-auth__password","data-cy-external-storage-auth-dialog-password":"",label:e(t)("files_external","Password"),placeholder:e(t)("files_external","Enter the storage password"),name:"password",required:""},null,8,["modelValue","label","placeholder"])]),_:1},8,["name"]))}});export{$ as default};
//# sourceMappingURL=CredentialsDialog-BP3tMLcO.chunk.mjs.map

View file

@ -0,0 +1,2 @@
import{t}from"./translation-DoG5ZELJ-Bni_xMHF.chunk.mjs";import{N as u}from"./index-CezD717V.chunk.mjs";import{N as d}from"./NcNoteCard-Cok_4Fld-Df11KHe2.chunk.mjs";import{N as p}from"./NcPasswordField-DYF18Cdo-BbpNSGg_.chunk.mjs";import{_ as c}from"./TrashCanOutline-BwjpsJlQ.chunk.mjs";import{b as g,c as f,o as h,w as x,e as s,u as e,r as n}from"./runtime-dom.esm-bundler-Bpt0bWgp.chunk.mjs";import"./index-Bndk0DrU.chunk.mjs";import"./createElementId-DhjFt1I9-COdYgGCC.chunk.mjs";import"./logger-D3RVzcfQ-DhmPs7Vh.chunk.mjs";import"./mdi-0dI0vmBh.chunk.mjs";import"./index-DNyFZ0q1.chunk.mjs";import"./string_decoder-BO00msnV.chunk.mjs";import"./index-xFugdZPW.chunk.mjs";import"./NcInputField-tt_Gi9ja-81wMHLk1.chunk.mjs";const k=g({__name:"CredentialsDialog",emits:["close"],setup(_){const o=n(""),r=n(""),m=[{label:t("files_external","Confirm"),type:"submit",variant:"primary"}];return(i,a)=>(h(),f(e(u),{buttons:m,class:"external-storage-auth",closeOnClickOutside:"","data-cy-external-storage-auth":"",isForm:"",name:e(t)("files_external","Storage credentials"),outTransition:"",onSubmit:a[2]||(a[2]=l=>i.$emit("close",{login:o.value,password:r.value})),"onUpdate:open":a[3]||(a[3]=l=>i.$emit("close"))},{default:x(()=>[s(e(d),{class:"external-storage-auth__header",text:e(t)("files_external","To access the storage, you need to provide the authentication credentials."),type:"info"},null,8,["text"]),s(e(c),{modelValue:o.value,"onUpdate:modelValue":a[0]||(a[0]=l=>o.value=l),autofocus:"",class:"external-storage-auth__login","data-cy-external-storage-auth-dialog-login":"",label:e(t)("files_external","Login"),placeholder:e(t)("files_external","Enter the storage login"),minlength:"2",name:"login",required:""},null,8,["modelValue","label","placeholder"]),s(e(p),{modelValue:r.value,"onUpdate:modelValue":a[1]||(a[1]=l=>r.value=l),class:"external-storage-auth__password","data-cy-external-storage-auth-dialog-password":"",label:e(t)("files_external","Password"),placeholder:e(t)("files_external","Enter the storage password"),name:"password",required:""},null,8,["modelValue","label","placeholder"])]),_:1},8,["name"]))}});export{k as default};
//# sourceMappingURL=CredentialsDialog-CbO2pMBh.chunk.mjs.map

View file

@ -1 +1 @@
{"version":3,"file":"CredentialsDialog-BP3tMLcO.chunk.mjs","sources":["../build/frontend/apps/files_external/src/views/CredentialsDialog.vue"],"sourcesContent":["<!--\n - SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors\n - SPDX-License-Identifier: AGPL-3.0-or-later\n-->\n\n<script setup lang=\"ts\">\nimport { t } from '@nextcloud/l10n'\nimport { ref } from 'vue'\nimport NcDialog from '@nextcloud/vue/components/NcDialog'\nimport NcNoteCard from '@nextcloud/vue/components/NcNoteCard'\nimport NcPasswordField from '@nextcloud/vue/components/NcPasswordField'\nimport NcTextField from '@nextcloud/vue/components/NcTextField'\n\ndefineEmits<{\n\tclose: [payload?: { login: string, password: string }]\n}>()\n\nconst login = ref('')\nconst password = ref('')\n\nconst dialogButtons: InstanceType<typeof NcDialog>['buttons'] = [{\n\tlabel: t('files_external', 'Confirm'),\n\ttype: 'submit',\n\tvariant: 'primary',\n}]\n</script>\n\n<template>\n\t<NcDialog\n\t\t:buttons=\"dialogButtons\"\n\t\tclass=\"external-storage-auth\"\n\t\tcloseOnClickOutside\n\t\tdata-cy-external-storage-auth\n\t\tisForm\n\t\t:name=\"t('files_external', 'Storage credentials')\"\n\t\toutTransition\n\t\t@submit=\"$emit('close', { login, password })\"\n\t\t@update:open=\"$emit('close')\">\n\t\t<!-- Header -->\n\t\t<NcNoteCard\n\t\t\tclass=\"external-storage-auth__header\"\n\t\t\t:text=\"t('files_external', 'To access the storage, you need to provide the authentication credentials.')\"\n\t\t\ttype=\"info\" />\n\n\t\t<!-- Login -->\n\t\t<NcTextField\n\t\t\tv-model=\"login\"\n\t\t\tautofocus\n\t\t\tclass=\"external-storage-auth__login\"\n\t\t\tdata-cy-external-storage-auth-dialog-login\n\t\t\t:label=\"t('files_external', 'Login')\"\n\t\t\t:placeholder=\"t('files_external', 'Enter the storage login')\"\n\t\t\tminlength=\"2\"\n\t\t\tname=\"login\"\n\t\t\trequired />\n\n\t\t<!-- Password -->\n\t\t<NcPasswordField\n\t\t\tv-model=\"password\"\n\t\t\tclass=\"external-storage-auth__password\"\n\t\t\tdata-cy-external-storage-auth-dialog-password\n\t\t\t:label=\"t('files_external', 'Password')\"\n\t\t\t:placeholder=\"t('files_external', 'Enter the storage password')\"\n\t\t\tname=\"password\"\n\t\t\trequired />\n\t</NcDialog>\n</template>\n"],"names":["login","ref","password","dialogButtons","t","_createBlock","_unref","NcDialog","_cache","$event","$emit","_createVNode","NcNoteCard","NcTextField","NcPasswordField"],"mappings":"kvBAiBA,MAAMA,EAAQC,EAAI,EAAE,EACdC,EAAWD,EAAI,EAAE,EAEjBE,EAA0D,CAAC,CAChE,MAAOC,EAAE,iBAAkB,SAAS,EACpC,KAAM,SACN,QAAS,SAAA,CACT,oBAIAC,EAqCWC,EAAAC,CAAA,EAAA,CApCT,QAASJ,EACV,MAAM,wBACN,oBAAA,GACA,gCAAA,GACA,OAAA,GACC,KAAMG,EAAAF,CAAA,EAAC,iBAAA,qBAAA,EACR,cAAA,GACC,SAAMI,EAAA,CAAA,IAAAA,EAAA,CAAA,EAAAC,GAAEC,EAAAA,MAAK,QAAA,CAAA,MAAYV,EAAA,eAAOE,EAAA,MAAQ,GACxC,+BAAaQ,EAAAA,MAAK,OAAA,EAAA,aAEnB,IAGe,CAHfC,EAGeL,EAAAM,CAAA,EAAA,CAFd,MAAM,gCACL,KAAMN,EAAAF,CAAA,EAAC,iBAAA,4EAAA,EACR,KAAK,MAAA,mBAGNO,EASYL,EAAAO,CAAA,EAAA,YARFb,EAAA,2CAAAA,EAAK,MAAAS,GACd,UAAA,GACA,MAAM,+BACN,6CAAA,GACC,MAAOH,EAAAF,CAAA,EAAC,iBAAA,OAAA,EACR,YAAaE,EAAAF,CAAA,EAAC,iBAAA,yBAAA,EACf,UAAU,IACV,KAAK,QACL,SAAA,EAAA,+CAGDO,EAOYL,EAAAQ,CAAA,EAAA,YANFZ,EAAA,2CAAAA,EAAQ,MAAAO,GACjB,MAAM,kCACN,gDAAA,GACC,MAAOH,EAAAF,CAAA,EAAC,iBAAA,UAAA,EACR,YAAaE,EAAAF,CAAA,EAAC,iBAAA,4BAAA,EACf,KAAK,WACL,SAAA,EAAA"}
{"version":3,"file":"CredentialsDialog-CbO2pMBh.chunk.mjs","sources":["../build/frontend/apps/files_external/src/views/CredentialsDialog.vue"],"sourcesContent":["<!--\n - SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors\n - SPDX-License-Identifier: AGPL-3.0-or-later\n-->\n\n<script setup lang=\"ts\">\nimport { t } from '@nextcloud/l10n'\nimport { ref } from 'vue'\nimport NcDialog from '@nextcloud/vue/components/NcDialog'\nimport NcNoteCard from '@nextcloud/vue/components/NcNoteCard'\nimport NcPasswordField from '@nextcloud/vue/components/NcPasswordField'\nimport NcTextField from '@nextcloud/vue/components/NcTextField'\n\ndefineEmits<{\n\tclose: [payload?: { login: string, password: string }]\n}>()\n\nconst login = ref('')\nconst password = ref('')\n\nconst dialogButtons: InstanceType<typeof NcDialog>['buttons'] = [{\n\tlabel: t('files_external', 'Confirm'),\n\ttype: 'submit',\n\tvariant: 'primary',\n}]\n</script>\n\n<template>\n\t<NcDialog\n\t\t:buttons=\"dialogButtons\"\n\t\tclass=\"external-storage-auth\"\n\t\tcloseOnClickOutside\n\t\tdata-cy-external-storage-auth\n\t\tisForm\n\t\t:name=\"t('files_external', 'Storage credentials')\"\n\t\toutTransition\n\t\t@submit=\"$emit('close', { login, password })\"\n\t\t@update:open=\"$emit('close')\">\n\t\t<!-- Header -->\n\t\t<NcNoteCard\n\t\t\tclass=\"external-storage-auth__header\"\n\t\t\t:text=\"t('files_external', 'To access the storage, you need to provide the authentication credentials.')\"\n\t\t\ttype=\"info\" />\n\n\t\t<!-- Login -->\n\t\t<NcTextField\n\t\t\tv-model=\"login\"\n\t\t\tautofocus\n\t\t\tclass=\"external-storage-auth__login\"\n\t\t\tdata-cy-external-storage-auth-dialog-login\n\t\t\t:label=\"t('files_external', 'Login')\"\n\t\t\t:placeholder=\"t('files_external', 'Enter the storage login')\"\n\t\t\tminlength=\"2\"\n\t\t\tname=\"login\"\n\t\t\trequired />\n\n\t\t<!-- Password -->\n\t\t<NcPasswordField\n\t\t\tv-model=\"password\"\n\t\t\tclass=\"external-storage-auth__password\"\n\t\t\tdata-cy-external-storage-auth-dialog-password\n\t\t\t:label=\"t('files_external', 'Password')\"\n\t\t\t:placeholder=\"t('files_external', 'Enter the storage password')\"\n\t\t\tname=\"password\"\n\t\t\trequired />\n\t</NcDialog>\n</template>\n"],"names":["login","ref","password","dialogButtons","t","_createBlock","_unref","NcDialog","_cache","$event","$emit","_createVNode","NcNoteCard","NcTextField","NcPasswordField"],"mappings":"sxBAiBA,MAAMA,EAAQC,EAAI,EAAE,EACdC,EAAWD,EAAI,EAAE,EAEjBE,EAA0D,CAAC,CAChE,MAAOC,EAAE,iBAAkB,SAAS,EACpC,KAAM,SACN,QAAS,SAAA,CACT,oBAIAC,EAqCWC,EAAAC,CAAA,EAAA,CApCT,QAASJ,EACV,MAAM,wBACN,oBAAA,GACA,gCAAA,GACA,OAAA,GACC,KAAMG,EAAAF,CAAA,EAAC,iBAAA,qBAAA,EACR,cAAA,GACC,SAAMI,EAAA,CAAA,IAAAA,EAAA,CAAA,EAAAC,GAAEC,EAAAA,MAAK,QAAA,CAAA,MAAYV,EAAA,eAAOE,EAAA,MAAQ,GACxC,+BAAaQ,EAAAA,MAAK,OAAA,EAAA,aAEnB,IAGe,CAHfC,EAGeL,EAAAM,CAAA,EAAA,CAFd,MAAM,gCACL,KAAMN,EAAAF,CAAA,EAAC,iBAAA,4EAAA,EACR,KAAK,MAAA,mBAGNO,EASYL,EAAAO,CAAA,EAAA,YARFb,EAAA,2CAAAA,EAAK,MAAAS,GACd,UAAA,GACA,MAAM,+BACN,6CAAA,GACC,MAAOH,EAAAF,CAAA,EAAC,iBAAA,OAAA,EACR,YAAaE,EAAAF,CAAA,EAAC,iBAAA,yBAAA,EACf,UAAU,IACV,KAAK,QACL,SAAA,EAAA,+CAGDO,EAOYL,EAAAQ,CAAA,EAAA,YANFZ,EAAA,2CAAAA,EAAQ,MAAAO,GACjB,MAAM,kCACN,gDAAA,GACC,MAAOH,EAAAF,CAAA,EAAC,iBAAA,UAAA,EACR,YAAaE,EAAAF,CAAA,EAAC,iBAAA,4BAAA,EACf,KAAK,WACL,SAAA,EAAA"}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View file

@ -0,0 +1,2 @@
import{m as g}from"./logger-D3RVzcfQ-DhmPs7Vh.chunk.mjs";import{N as k}from"./PencilOutline-BAj10Ume.chunk.mjs";import{a as y}from"./index-CezD717V.chunk.mjs";import{r as b,d as P,a as x,N as u,_ as N}from"./createElementId-DhjFt1I9-COdYgGCC.chunk.mjs";import{b as S,i as $,j as f,o as s,l as n,k as z,c as i,m as c,g as h,t as v,p as L,w as e,e as M,u as j,n as w}from"./runtime-dom.esm-bundler-Bpt0bWgp.chunk.mjs";b(P);const A={key:0,class:"nc-chip__icon"},B={class:"nc-chip__text"},I=S({__name:"NcChip",props:{ariaLabelClose:{default:x("Close")},actionsContainer:{default:"body"},text:{default:""},iconPath:{default:void 0},iconSvg:{default:void 0},noClose:{type:Boolean},variant:{default:"secondary"}},emits:["close"],setup(m,{emit:_}){const t=m,C=_,l=$(),o=L(()=>!t.noClose),r=()=>!!l.actions,p=()=>!!(t.iconPath||t.iconSvg||l.icon);return(a,d)=>(s(),f("div",{class:w(["nc-chip",{[`nc-chip--${a.variant}`]:!0,"nc-chip--no-actions":a.noClose&&!r(),"nc-chip--no-icon":!p()}])},[p()?(s(),f("span",A,[c(a.$slots,"icon",{},()=>[a.iconPath||a.iconSvg?(s(),i(u,{key:0,inline:"",path:a.iconPath,svg:a.iconPath?void 0:a.iconSvg,size:18},null,8,["path","svg"])):n("",!0)],!0)])):n("",!0),z("span",B,[c(a.$slots,"default",{},()=>[h(v(a.text),1)],!0)]),o.value||r()?(s(),i(y,{key:1,class:"nc-chip__actions",container:a.actionsContainer,forceMenu:!o.value,variant:"tertiary-no-background"},{default:e(()=>[o.value?(s(),i(k,{key:0,closeAfterClick:"",onClick:d[0]||(d[0]=D=>C("close"))},{icon:e(()=>[M(u,{path:j(g),size:20},null,8,["path"])]),default:e(()=>[h(" "+v(a.ariaLabelClose),1)]),_:1})):n("",!0),c(a.$slots,"actions",{},void 0,!0)]),_:3},8,["container","forceMenu"])):n("",!0)],2))}}),F=N(I,[["__scopeId","data-v-8f5d3c40"]]);export{F as N};
//# sourceMappingURL=NcChip-CaOkERH3-AC49UWxN.chunk.mjs.map

File diff suppressed because one or more lines are too long

View file

@ -0,0 +1 @@
.material-design-icon[data-v-8f5d3c40]{display:flex;align-self:center;justify-self:center;align-items:center;justify-content:center}.nc-chip[data-v-8f5d3c40]{--chip-size: 24px;--chip-radius: calc(var(--chip-size) / 2);height:var(--chip-size);max-width:fit-content;display:flex;flex-direction:row;align-items:center;border-radius:var(--chip-radius);background-color:var(--color-background-hover)}.nc-chip--primary[data-v-8f5d3c40]{background-color:var(--color-primary-element);color:var(--color-primary-element-text)}.nc-chip--secondary[data-v-8f5d3c40]{background-color:var(--color-primary-element-light);color:var(--color-primary-element-light-text)}.nc-chip--error[data-v-8f5d3c40]{background-color:var(--color-error);color:var(--color-error-text)}.nc-chip--warning[data-v-8f5d3c40]{background-color:var(--color-warning);color:var(--color-warning-text)}.nc-chip--success[data-v-8f5d3c40]{background-color:var(--color-success);color:var(--color-success-text)}.nc-chip--no-actions .nc-chip__text[data-v-8f5d3c40]{padding-inline-end:calc(2 * var(--default-grid-baseline))}.nc-chip--no-icon .nc-chip__text[data-v-8f5d3c40]{padding-inline-start:calc(2 * var(--default-grid-baseline))}.nc-chip__text[data-v-8f5d3c40]{flex:1 auto;overflow:hidden;text-overflow:ellipsis;text-wrap:nowrap}.nc-chip__icon[data-v-8f5d3c40]{flex:0 0 var(--chip-size);margin-inline-end:var(--default-grid-baseline);line-height:1;display:flex;align-items:center;justify-content:center;overflow:hidden;height:var(--chip-size);width:var(--chip-size)}.nc-chip__actions[data-v-8f5d3c40]{flex:0 0 var(--chip-size);--default-clickable-area: var(--chip-size);--border-radius-element: var(--chip-radius)}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

1
dist/NcDateTime-DS-ziNw6.chunk.css vendored Normal file
View file

@ -0,0 +1 @@
.material-design-icon[data-v-32f01b7a]{display:flex;align-self:center;justify-self:center;align-items:center;justify-content:center}li.action[data-v-32f01b7a]:hover,li.action.active[data-v-32f01b7a]{border-radius:6px;padding:0}li.action[data-v-32f01b7a]:hover{background-color:var(--color-background-hover)}.action-link[data-v-32f01b7a]{display:flex;align-items:flex-start;width:100%;height:auto;margin:0;padding:0;padding-inline-end:calc((var(--default-clickable-area) - 16px) / 2);box-sizing:border-box;cursor:pointer;white-space:nowrap;color:var(--color-main-text);border:0;border-radius:0;background-color:transparent;box-shadow:none;font-weight:400;font-size:var(--default-font-size);line-height:var(--default-clickable-area)}.action-link>span[data-v-32f01b7a]{cursor:pointer;white-space:nowrap}.action-link__icon[data-v-32f01b7a]{width:var(--default-clickable-area);height:var(--default-clickable-area);opacity:1;background-position:calc((var(--default-clickable-area) - 16px) / 2) center;background-size:16px;background-repeat:no-repeat}.action-link[data-v-32f01b7a] .material-design-icon{width:var(--default-clickable-area);height:var(--default-clickable-area);opacity:1}.action-link[data-v-32f01b7a] .material-design-icon .material-design-icon__svg{vertical-align:middle}.action-link__longtext-wrapper[data-v-32f01b7a],.action-link__longtext[data-v-32f01b7a]{max-width:220px;line-height:1.6em;padding:calc((var(--default-clickable-area) - 1.6em) / 2) 0;cursor:pointer;text-align:start;overflow:hidden;text-overflow:ellipsis}.action-link__longtext[data-v-32f01b7a]{cursor:pointer;white-space:pre-wrap!important}.action-link__name[data-v-32f01b7a]{font-weight:700;text-overflow:ellipsis;overflow:hidden;white-space:nowrap;max-width:100%;display:block}.action-link__description[data-v-32f01b7a]{display:block;white-space:pre-wrap;font-size:var(--font-size-small);line-height:var(--default-line-height);color:var(--color-text-maxcontrast);cursor:pointer}.action-link__menu-icon[data-v-32f01b7a]{margin-inline:auto calc((var(--default-clickable-area) - 16px) / 2 * -1)}.material-design-icon[data-v-87267750]{display:flex;align-self:center;justify-self:center;align-items:center;justify-content:center}li.action[data-v-87267750]:hover,li.action.active[data-v-87267750]{border-radius:6px;padding:0}li.action[data-v-87267750]:hover{background-color:var(--color-background-hover)}.action-router[data-v-87267750]{display:flex;align-items:flex-start;width:100%;height:auto;margin:0;padding:0;padding-inline-end:calc((var(--default-clickable-area) - 16px) / 2);box-sizing:border-box;cursor:pointer;white-space:nowrap;color:var(--color-main-text);border:0;border-radius:0;background-color:transparent;box-shadow:none;font-weight:400;font-size:var(--default-font-size);line-height:var(--default-clickable-area)}.action-router>span[data-v-87267750]{cursor:pointer;white-space:nowrap}.action-router__icon[data-v-87267750]{width:var(--default-clickable-area);height:var(--default-clickable-area);opacity:1;background-position:calc((var(--default-clickable-area) - 16px) / 2) center;background-size:16px;background-repeat:no-repeat}.action-router[data-v-87267750] .material-design-icon{width:var(--default-clickable-area);height:var(--default-clickable-area);opacity:1}.action-router[data-v-87267750] .material-design-icon .material-design-icon__svg{vertical-align:middle}.action-router__longtext-wrapper[data-v-87267750],.action-router__longtext[data-v-87267750]{max-width:220px;line-height:1.6em;padding:calc((var(--default-clickable-area) - 1.6em) / 2) 0;cursor:pointer;text-align:start;overflow:hidden;text-overflow:ellipsis}.action-router__longtext[data-v-87267750]{cursor:pointer;white-space:pre-wrap!important}.action-router__name[data-v-87267750]{font-weight:700;text-overflow:ellipsis;overflow:hidden;white-space:nowrap;max-width:100%;display:block}.action-router__description[data-v-87267750]{display:block;white-space:pre-wrap;font-size:var(--font-size-small);line-height:var(--default-line-height);color:var(--color-text-maxcontrast);cursor:pointer}.action-router__menu-icon[data-v-87267750]{margin-inline:auto calc((var(--default-clickable-area) - 16px) / 2 * -1)}.action--disabled[data-v-87267750]{pointer-events:none;opacity:.5}.action--disabled[data-v-87267750]:hover,.action--disabled[data-v-87267750]:focus{cursor:default;opacity:.5}.action--disabled[data-v-87267750] *{opacity:1!important}

File diff suppressed because one or more lines are too long

View file

@ -0,0 +1,2 @@
import{A as d}from"./PencilOutline-BAj10Ume.chunk.mjs";import{d as _,z as C,A as S}from"./index-CezD717V.chunk.mjs";import{_ as f}from"./createElementId-DhjFt1I9-COdYgGCC.chunk.mjs";import{j as a,o,k as n,m as x,l as g,M as k,n as y,t as l,f as I,e as v,w as h,b as w,u as T,p as m,Y as p}from"./runtime-dom.esm-bundler-Bpt0bWgp.chunk.mjs";const L={name:"NcActionLink",mixins:[d],inject:{isInSemanticMenu:{from:_,default:!1}},props:{href:{type:String,required:!0,validator:e=>{try{return new URL(e)}catch{return e.startsWith("#")||e.startsWith("/")}}},download:{type:String,default:null},target:{type:String,default:"_self",validator:e=>e&&(!e.startsWith("_")||["_blank","_self","_parent","_top"].indexOf(e)>-1)},title:{type:String,default:null}}},M=["role"],U=["download","href","aria-label","target","title","role"],j={key:0,class:"action-link__longtext-wrapper"},A={class:"action-link__name"},N=["textContent"],$=["textContent"],R={key:2,class:"action-link__text"};function W(e,t,i,u,c,r){return o(),a("li",{class:"action",role:r.isInSemanticMenu&&"presentation"},[n("a",{download:i.download,href:i.href,"aria-label":e.ariaLabel,target:i.target,title:i.title,class:"action-link focusable",rel:"nofollow noreferrer noopener",role:r.isInSemanticMenu&&"menuitem",onClick:t[0]||(t[0]=(...s)=>e.onClick&&e.onClick(...s))},[x(e.$slots,"icon",{},()=>[n("span",{"aria-hidden":"true",class:y(["action-link__icon",[e.isIconUrl?"action-link__icon--url":e.icon]]),style:k({backgroundImage:e.isIconUrl?`url(${e.icon})`:null})},null,6)],!0),e.name?(o(),a("span",j,[n("strong",A,l(e.name),1),t[1]||(t[1]=n("br",null,null,-1)),n("span",{class:"action-link__longtext",textContent:l(e.text)},null,8,N)])):e.isLongText?(o(),a("span",{key:1,class:"action-link__longtext",textContent:l(e.text)},null,8,$)):(o(),a("span",R,l(e.text),1)),g("",!0)],8,U)],8,M)}const V=f(L,[["render",W],["__scopeId","data-v-32f01b7a"]]),q={name:"NcActionRouter",mixins:[d],inject:{isInSemanticMenu:{from:_,default:!1}},props:{to:{type:[String,Object],required:!0}}},B=["role"],O={key:0,class:"action-router__longtext-wrapper"},z={class:"action-router__name"},D=["textContent"],Y=["textContent"],E={key:2,class:"action-router__text"};function F(e,t,i,u,c,r){const s=I("RouterLink");return o(),a("li",{class:"action",role:r.isInSemanticMenu&&"presentation"},[v(s,{"aria-label":e.ariaLabel,class:"action-router focusable",rel:"nofollow noreferrer noopener",role:r.isInSemanticMenu&&"menuitem",title:e.title,to:i.to,onClick:e.onClick},{default:h(()=>[x(e.$slots,"icon",{},()=>[n("span",{"aria-hidden":"true",class:y(["action-router__icon",[e.isIconUrl?"action-router__icon--url":e.icon]]),style:k({backgroundImage:e.isIconUrl?`url(${e.icon})`:null})},null,6)],!0),e.name?(o(),a("span",O,[n("strong",z,l(e.name),1),t[0]||(t[0]=n("br",null,null,-1)),n("span",{class:"action-router__longtext",textContent:l(e.text)},null,8,D)])):e.isLongText?(o(),a("span",{key:1,class:"action-router__longtext",textContent:l(e.text)},null,8,Y)):(o(),a("span",E,l(e.text),1)),g("",!0)]),_:3},8,["aria-label","role","title","to","onClick"])],8,B)}const X=f(q,[["render",F],["__scopeId","data-v-87267750"]]),G=["data-timestamp","title","textContent"],Z=w({__name:"NcDateTime",props:{timestamp:{},format:{default:()=>({timeStyle:"medium",dateStyle:"short"})},relativeTime:{type:[Boolean,String],default:"long"},ignoreSeconds:{type:Boolean}},setup(e){const t=e,i=m(()=>({format:t.format})),u=m(()=>({ignoreSeconds:t.ignoreSeconds,relativeTime:t.relativeTime||"long",update:t.relativeTime!==!1})),c=S(p(()=>t.timestamp),i),r=C(p(()=>t.timestamp),u),s=m(()=>t.relativeTime?r.value:c.value);return(b,H)=>(o(),a("span",{class:"nc-datetime",dir:"auto","data-timestamp":b.timestamp,title:T(c),textContent:l(s.value)},null,8,G))}});export{V as N,Z as _,X as a};
//# sourceMappingURL=NcDateTime.vue_vue_type_script_setup_true_lang-BhB8yA4U-BmrwzOAy.chunk.mjs.map

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

Some files were not shown because too many files have changed in this diff Show more