Bugfixes on tooltip z-indexing. Search using breadcrumbs in table. Link fixes.
This commit is contained in:
@@ -41,19 +41,12 @@ const props = defineProps<{
|
||||
|
||||
const longest = ref<Array<string>>([])
|
||||
|
||||
watchEffect(() => {
|
||||
const longcut = longest.value.slice(0, props.path.length)
|
||||
const same = longcut.every((value, index) => value === props.path[index])
|
||||
if (!same) longest.value = props.path
|
||||
else if (props.path.length > longcut.length) {
|
||||
longest.value = longcut.concat(props.path.slice(longcut.length))
|
||||
}
|
||||
})
|
||||
|
||||
const isCurrent = (index: number) => index == props.path.length ? 'location' : undefined
|
||||
|
||||
const navigate = (index: number) => {
|
||||
links[index].focus()
|
||||
const link = links[index]
|
||||
if (!link) throw Error(`No link at index ${index} (path: ${props.path})`)
|
||||
link.focus()
|
||||
router.replace(`/${longest.value.slice(0, index).join('/')}`)
|
||||
}
|
||||
|
||||
@@ -62,6 +55,18 @@ const move = (dir: number) => {
|
||||
if (index < 0 || index > longest.value.length) return
|
||||
navigate(index)
|
||||
}
|
||||
|
||||
watchEffect(() => {
|
||||
const longcut = longest.value.slice(0, props.path.length)
|
||||
const same = longcut.every((value, index) => value === props.path[index])
|
||||
if (!same) longest.value = props.path
|
||||
else if (props.path.length > longcut.length) {
|
||||
longest.value = longcut.concat(props.path.slice(longcut.length))
|
||||
}
|
||||
})
|
||||
watchEffect(() => {
|
||||
if (links.length) navigate(props.path.length)
|
||||
})
|
||||
</script>
|
||||
|
||||
<style>
|
||||
|
||||
@@ -56,83 +56,90 @@
|
||||
<td class="size right">{{ editing.sizedisp }}</td>
|
||||
<td class="menu"></td>
|
||||
</tr>
|
||||
<tr
|
||||
<template
|
||||
v-for="doc of sorted(props.documents as FolderDocument[])"
|
||||
:key="doc.key"
|
||||
:id="`file-${doc.key}`"
|
||||
:class="{
|
||||
file: doc.type === 'file',
|
||||
folder: doc.type === 'folder',
|
||||
cursor: cursor === doc
|
||||
}"
|
||||
@click="cursor = cursor === doc ? null : doc"
|
||||
@contextmenu.prevent="contextMenu($event, doc)"
|
||||
>
|
||||
<td class="selection" @click.up.stop="cursor = cursor === doc ? doc : null">
|
||||
<input
|
||||
type="checkbox"
|
||||
tabindex="-1"
|
||||
:checked="documentStore.selected.has(doc.key)"
|
||||
@change="
|
||||
($event.target as HTMLInputElement).checked
|
||||
? documentStore.selected.add(doc.key)
|
||||
: documentStore.selected.delete(doc.key)
|
||||
"
|
||||
/>
|
||||
</td>
|
||||
<td class="name">
|
||||
<template v-if="editing === doc"
|
||||
><FileRenameInput
|
||||
:doc="doc"
|
||||
:rename="rename"
|
||||
:exit="
|
||||
() => {
|
||||
editing = null
|
||||
}
|
||||
"
|
||||
/></template>
|
||||
<template v-else>
|
||||
<span class="loc" v-if="doc.loc.join('/') !== props.path.join('/')">{{ doc.loc.join('/') + '/'}}</span>
|
||||
<a
|
||||
:href="url_for(doc)"
|
||||
:key="doc.key">
|
||||
<tr v-if="doc.loc !== prevloc && ((prevloc = doc.loc) || true)">
|
||||
<th colspan="5"><BreadCrumb :path="doc.loc ? doc.loc.split('/') : []" /></th>
|
||||
</tr>
|
||||
|
||||
<tr
|
||||
:id="`file-${doc.key}`"
|
||||
:class="{
|
||||
file: doc.type === 'file',
|
||||
folder: doc.type === 'folder',
|
||||
cursor: cursor === doc
|
||||
}"
|
||||
@click="cursor = cursor === doc ? null : doc"
|
||||
@contextmenu.prevent="contextMenu($event, doc)"
|
||||
>
|
||||
<td class="selection" @click.up.stop="cursor = cursor === doc ? doc : null">
|
||||
<input
|
||||
type="checkbox"
|
||||
tabindex="-1"
|
||||
@contextmenu.prevent
|
||||
@focus.stop="cursor = doc"
|
||||
@blur="ev => { if (!editing) cursor = null }"
|
||||
>{{ doc.name }}</a
|
||||
:checked="documentStore.selected.has(doc.key)"
|
||||
@change="
|
||||
($event.target as HTMLInputElement).checked
|
||||
? documentStore.selected.add(doc.key)
|
||||
: documentStore.selected.delete(doc.key)
|
||||
"
|
||||
/>
|
||||
</td>
|
||||
<td class="name">
|
||||
<template v-if="editing === doc"
|
||||
><FileRenameInput
|
||||
:doc="doc"
|
||||
:rename="rename"
|
||||
:exit="
|
||||
() => {
|
||||
editing = null
|
||||
}
|
||||
"
|
||||
/></template>
|
||||
<template v-else>
|
||||
<a
|
||||
:href="url_for(doc)"
|
||||
tabindex="-1"
|
||||
@contextmenu.prevent
|
||||
@focus.stop="cursor = doc"
|
||||
@blur="ev => { if (!editing) cursor = null }"
|
||||
@keyup.left="router.back()"
|
||||
@keyup.right.stop="ev => { if (doc.type === 'folder') (ev.target as HTMLElement).click() }"
|
||||
>{{ doc.name }}</a
|
||||
>
|
||||
<button
|
||||
v-if="cursor == doc"
|
||||
class="rename-button"
|
||||
@click="() => (editing = doc)"
|
||||
>
|
||||
🖊️
|
||||
</button>
|
||||
</template>
|
||||
</td>
|
||||
<td class="modified right">
|
||||
<time
|
||||
:data-tooltip="new Date(1000 * doc.mtime).toISOString().replace('T', '\n').replace('.000Z', ' UTC')"
|
||||
>{{ doc.modified }}</time
|
||||
>
|
||||
</td>
|
||||
<td class="size right">{{ doc.sizedisp }}</td>
|
||||
<td class="menu">
|
||||
<button
|
||||
v-if="cursor == doc"
|
||||
class="rename-button"
|
||||
@click="() => (editing = doc)"
|
||||
tabindex="-1"
|
||||
@click.stop="contextMenu($event, doc)"
|
||||
>
|
||||
🖊️
|
||||
⋮
|
||||
</button>
|
||||
</template>
|
||||
</td>
|
||||
<td class="modified right">
|
||||
<time
|
||||
:data-tooltip="new Date(1000 * doc.mtime).toISOString().replace('T', '\n').replace('.000Z', ' UTC')"
|
||||
>{{ doc.modified }}</time
|
||||
>
|
||||
</td>
|
||||
<td class="size right">{{ doc.sizedisp }}</td>
|
||||
<td class="menu">
|
||||
<button
|
||||
tabindex="-1"
|
||||
@click.stop="contextMenu($event, doc)"
|
||||
>
|
||||
⋮
|
||||
</button>
|
||||
</td>
|
||||
</tr>
|
||||
</td>
|
||||
</tr>
|
||||
</template>
|
||||
</tbody>
|
||||
</table>
|
||||
<div v-else class="empty-container">Nothing to see here</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, computed, watchEffect } from 'vue'
|
||||
import { ref, computed, watchEffect, onBeforeUpdate } from 'vue'
|
||||
import { useDocumentStore } from '@/stores/documents'
|
||||
import type { Document, FolderDocument } from '@/repositories/Document'
|
||||
import FileRenameInput from './FileRenameInput.vue'
|
||||
@@ -147,15 +154,12 @@ const props = withDefaults(
|
||||
}>(),
|
||||
{}
|
||||
)
|
||||
|
||||
const documentStore = useDocumentStore()
|
||||
const router = useRouter()
|
||||
const linkBasePath = computed(() => props.path.join('/'))
|
||||
const filesBasePath = computed(() => `/files/${linkBasePath.value}`)
|
||||
const url_for = (doc: FolderDocument) =>
|
||||
doc.type === 'folder'
|
||||
? `#${linkBasePath.value}/${doc.name}/`
|
||||
: `${filesBasePath.value}/${doc.name}`
|
||||
const url_for = (doc: FolderDocument) => {
|
||||
const p = doc.loc ? `${doc.loc}/${doc.name}` : doc.name
|
||||
return doc.type === 'folder' ? `#/${p}/` : `/files/${p}`
|
||||
}
|
||||
const cursor = ref<FolderDocument | null>(null)
|
||||
// File rename
|
||||
const editing = ref<FolderDocument | null>(null)
|
||||
@@ -174,7 +178,7 @@ const rename = (doc: FolderDocument, newName: string) => {
|
||||
control.send(
|
||||
JSON.stringify({
|
||||
op: 'rename',
|
||||
path: `${decodeURIComponent(linkBasePath.value)}/${oldName}`,
|
||||
path: `${doc.loc}/${oldName}`,
|
||||
to: newName
|
||||
})
|
||||
)
|
||||
@@ -185,6 +189,7 @@ defineExpose({
|
||||
newFolder() {
|
||||
const now = Date.now() / 1000
|
||||
editing.value = {
|
||||
loc,
|
||||
key: 'new',
|
||||
name: 'New Folder',
|
||||
type: 'folder',
|
||||
@@ -252,12 +257,18 @@ defineExpose({
|
||||
scrolltimer = null
|
||||
}, 300)
|
||||
}
|
||||
if (moveto === N) focusBreadcrumb()
|
||||
}
|
||||
})
|
||||
const focusBreadcrumb = () => {
|
||||
const el = document.querySelector('.breadcrumb') as HTMLElement | null
|
||||
if (el) el.focus()
|
||||
}
|
||||
let scrolltimer: any = null
|
||||
let scrolltr: any = null
|
||||
|
||||
watchEffect(() => {
|
||||
if (cursor.value && cursor.value !== editing.value) editing.value = null
|
||||
if (editing.value) cursor.value = editing.value
|
||||
if (cursor.value) {
|
||||
const a = document.querySelector(
|
||||
`#file-${cursor.value.key} .name a`
|
||||
@@ -265,7 +276,13 @@ watchEffect(() => {
|
||||
if (a) a.focus()
|
||||
}
|
||||
})
|
||||
const mkdir = (doc: FolderDocument, name: string) => {
|
||||
watchEffect(() => {
|
||||
if (!props.documents.length && cursor.value) {
|
||||
cursor.value = null
|
||||
focusBreadcrumb()
|
||||
}
|
||||
})
|
||||
const mkdir = (doc: Document, name: string) => {
|
||||
const control = createWebSocket('/api/control', (ev: MessageEvent) => {
|
||||
const msg = JSON.parse(ev.data)
|
||||
if ('error' in msg) {
|
||||
@@ -273,14 +290,14 @@ const mkdir = (doc: FolderDocument, name: string) => {
|
||||
editing.value = null
|
||||
} else {
|
||||
console.log('mkdir', msg)
|
||||
router.push(`/${linkBasePath.value}/${name}/`)
|
||||
router.push(`/${doc.loc}/${name}/`)
|
||||
}
|
||||
})
|
||||
control.onopen = () => {
|
||||
control.send(
|
||||
JSON.stringify({
|
||||
op: 'mkdir',
|
||||
path: `${decodeURIComponent(linkBasePath.value)}/${name}`
|
||||
path: `${doc.loc}/${name}`
|
||||
})
|
||||
)
|
||||
}
|
||||
@@ -332,10 +349,11 @@ const allSelected = computed({
|
||||
}
|
||||
}
|
||||
})
|
||||
watchEffect(() => {
|
||||
if (cursor.value && cursor.value !== editing.value) editing.value = null
|
||||
if (editing.value) cursor.value = editing.value
|
||||
})
|
||||
|
||||
const loc = computed(() => props.path.join('/'))
|
||||
let prevloc = ''
|
||||
onBeforeUpdate(() => { prevloc = loc.value })
|
||||
|
||||
const contextMenu = (ev: Event, doc: Document) => {
|
||||
cursor.value = doc
|
||||
console.log('Context menu', ev, doc)
|
||||
@@ -475,4 +493,7 @@ tbody .selection input {
|
||||
font-size: 3rem;
|
||||
color: var(--accent-color);
|
||||
}
|
||||
.loc {
|
||||
color: #888;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -12,22 +12,17 @@ const toggleSearchInput = () => {
|
||||
nextTick(() => {
|
||||
const input = search.value
|
||||
if (input) input.focus()
|
||||
else if (searchButton.value) searchButton.value.blur()
|
||||
executeSearch()
|
||||
//else if (searchButton.value) document.querySelector('.breadcrumb')!.focus()
|
||||
})
|
||||
}
|
||||
|
||||
const executeSearch = () => {
|
||||
documentStore.setFilter(search.value?.value ?? '')
|
||||
}
|
||||
|
||||
defineExpose({
|
||||
toggleSearchInput
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<nav>
|
||||
<nav class="headermain">
|
||||
<div class="buttons">
|
||||
<UploadButton />
|
||||
<SvgButton
|
||||
|
||||
Reference in New Issue
Block a user