Rename store

This commit is contained in:
Leo Vasanko 2023-11-13 08:26:32 -08:00
parent e4a62e1197
commit f0fc4a7d30
9 changed files with 65 additions and 184 deletions

View File

@ -17,7 +17,7 @@ import type { ComputedRef } from 'vue'
import type HeaderMain from '@/components/HeaderMain.vue' import type HeaderMain from '@/components/HeaderMain.vue'
import { onMounted, onUnmounted, ref, watchEffect } from 'vue' import { onMounted, onUnmounted, ref, watchEffect } from 'vue'
import { loadSession, watchConnect, watchDisconnect } from '@/repositories/WS' import { loadSession, watchConnect, watchDisconnect } from '@/repositories/WS'
import { useDocumentStore } from '@/stores/documents' import { useMainStore } from '@/stores/main'
import { computed } from 'vue' import { computed } from 'vue'
import Router from '@/router/index' import Router from '@/router/index'
@ -27,7 +27,7 @@ interface Path {
pathList: string[] pathList: string[]
query: string query: string
} }
const documentStore = useDocumentStore() const store = useMainStore()
const path: ComputedRef<Path> = computed(() => { const path: ComputedRef<Path> = computed(() => {
const p = decodeURIComponent(Router.currentRoute.value.path).split('//') const p = decodeURIComponent(Router.currentRoute.value.path).split('//')
const pathList = p[0].split('/').filter(value => value !== '') const pathList = p[0].split('/').filter(value => value !== '')
@ -39,7 +39,7 @@ const path: ComputedRef<Path> = computed(() => {
} }
}) })
watchEffect(() => { watchEffect(() => {
document.title = path.value.path.replace(/\/$/, '').split('/').pop() || documentStore.server.name || 'Cista Storage' document.title = path.value.path.replace(/\/$/, '').split('/').pop() || store.server.name || 'Cista Storage'
}) })
onMounted(loadSession) onMounted(loadSession)
onMounted(watchConnect) onMounted(watchConnect)
@ -48,7 +48,7 @@ const headerMain = ref<typeof HeaderMain | null>(null)
let vert = 0 let vert = 0
let timer: any = null let timer: any = null
const globalShortcutHandler = (event: KeyboardEvent) => { const globalShortcutHandler = (event: KeyboardEvent) => {
const fileExplorer = documentStore.fileExplorer as any const fileExplorer = store.fileExplorer as any
if (!fileExplorer) return if (!fileExplorer) return
const c = fileExplorer.isCursor() const c = fileExplorer.isCursor()
const keyup = event.type === 'keyup' const keyup = event.type === 'keyup'
@ -124,3 +124,4 @@ onUnmounted(() => {
}) })
export type { Path } export type { Path }
</script> </script>
@/stores/main

View File

@ -36,11 +36,11 @@
<input <input
type="checkbox" type="checkbox"
tabindex="-1" tabindex="-1"
:checked="documentStore.selected.has(doc.key)" :checked="store.selected.has(doc.key)"
@change=" @change="
($event.target as HTMLInputElement).checked ($event.target as HTMLInputElement).checked
? documentStore.selected.add(doc.key) ? store.selected.add(doc.key)
: documentStore.selected.delete(doc.key) : store.selected.delete(doc.key)
" "
/> />
</td> </td>
@ -80,7 +80,7 @@
<script setup lang="ts"> <script setup lang="ts">
import { ref, computed, watchEffect, shallowRef, onMounted, onUnmounted } from 'vue' import { ref, computed, watchEffect, shallowRef, onMounted, onUnmounted } from 'vue'
import { useDocumentStore } from '@/stores/documents' import { useMainStore } from '@/stores/main'
import { Doc } from '@/repositories/Document' import { Doc } from '@/repositories/Document'
import FileRenameInput from './FileRenameInput.vue' import FileRenameInput from './FileRenameInput.vue'
import { connect, controlUrl } from '@/repositories/WS' import { connect, controlUrl } from '@/repositories/WS'
@ -91,7 +91,7 @@ const props = defineProps<{
path: Array<string> path: Array<string>
documents: Doc[] documents: Doc[]
}>() }>()
const documentStore = useDocumentStore() const store = useMainStore()
const router = useRouter() const router = useRouter()
const url_for = (doc: Doc) => { const url_for = (doc: Doc) => {
const p = doc.loc ? `${doc.loc}/${doc.name}` : doc.name const p = doc.loc ? `${doc.loc}/${doc.name}` : doc.name
@ -159,10 +159,10 @@ defineExpose({
cursorSelect() { cursorSelect() {
const doc = cursor.value const doc = cursor.value
if (!doc) return if (!doc) return
if (documentStore.selected.has(doc.key)) { if (store.selected.has(doc.key)) {
documentStore.selected.delete(doc.key) store.selected.delete(doc.key)
} else { } else {
documentStore.selected.add(doc.key) store.selected.add(doc.key)
} }
this.cursorMove(1) this.cursorMove(1)
}, },
@ -187,8 +187,8 @@ defineExpose({
for (let p = begin; p !== end; p = increment(p, 1)) { for (let p = begin; p !== end; p = increment(p, 1)) {
if (p === N) continue if (p === N) continue
const key = documents[p].key const key = documents[p].key
if (documentStore.selected.has(key)) documentStore.selected.delete(key) if (store.selected.has(key)) store.selected.delete(key)
else documentStore.selected.add(key) else store.selected.add(key)
} }
} }
// @ts-ignore // @ts-ignore
@ -278,7 +278,7 @@ const selectionIndeterminate = computed({
get: () => { get: () => {
return ( return (
props.documents.length > 0 && props.documents.length > 0 &&
props.documents.some((doc: Doc) => documentStore.selected.has(doc.key)) && props.documents.some((doc: Doc) => store.selected.has(doc.key)) &&
!allSelected.value !allSelected.value
) )
}, },
@ -289,16 +289,16 @@ const allSelected = computed({
get: () => { get: () => {
return ( return (
props.documents.length > 0 && props.documents.length > 0 &&
props.documents.every((doc: Doc) => documentStore.selected.has(doc.key)) props.documents.every((doc: Doc) => store.selected.has(doc.key))
) )
}, },
set: (value: boolean) => { set: (value: boolean) => {
console.log('Setting allSelected', value) console.log('Setting allSelected', value)
for (const doc of props.documents) { for (const doc of props.documents) {
if (value) { if (value) {
documentStore.selected.add(doc.key) store.selected.add(doc.key)
} else { } else {
documentStore.selected.delete(doc.key) store.selected.delete(doc.key)
} }
} }
} }
@ -456,3 +456,4 @@ tbody .selection input {
color: #888; color: #888;
} }
</style> </style>
@/stores/main

View File

@ -1,15 +1,15 @@
<template> <template>
<nav class="headermain"> <nav class="headermain">
<div class="buttons"> <div class="buttons">
<template v-if="documentStore.error"> <template v-if="store.error">
<div class="error-message" @click="documentStore.error = ''">{{ documentStore.error }}</div> <div class="error-message" @click="store.error = ''">{{ store.error }}</div>
<div class="smallgap"></div> <div class="smallgap"></div>
</template> </template>
<UploadButton :path="props.path" /> <UploadButton :path="props.path" />
<SvgButton <SvgButton
name="create-folder" name="create-folder"
data-tooltip="New folder" data-tooltip="New folder"
@click="() => documentStore.fileExplorer!.newFolder()" @click="() => store.fileExplorer!.newFolder()"
/> />
<slot></slot> <slot></slot>
<div class="spacer smallgap"></div> <div class="spacer smallgap"></div>
@ -32,12 +32,12 @@
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { useDocumentStore } from '@/stores/documents' import { useMainStore } from '@/stores/main'
import { ref, nextTick, watchEffect } from 'vue' import { ref, nextTick, watchEffect } from 'vue'
import ContextMenu from '@imengyu/vue3-context-menu' import ContextMenu from '@imengyu/vue3-context-menu'
import router from '@/router'; import router from '@/router';
const documentStore = useDocumentStore() const store = useMainStore()
const showSearchInput = ref<boolean>(false) const showSearchInput = ref<boolean>(false)
const search = ref<HTMLInputElement | null>() const search = ref<HTMLInputElement | null>()
const searchButton = ref<HTMLButtonElement | null>() const searchButton = ref<HTMLButtonElement | null>()
@ -76,10 +76,10 @@ watchEffect(() => {
const settingsMenu = (e: Event) => { const settingsMenu = (e: Event) => {
// show the context menu // show the context menu
const items = [] const items = []
if (documentStore.user.isLoggedIn) { if (store.user.isLoggedIn) {
items.push({ label: `Logout ${documentStore.user.username ?? ''}`, onClick: () => documentStore.logout() }) items.push({ label: `Logout ${store.user.username ?? ''}`, onClick: () => store.logout() })
} else { } else {
items.push({ label: 'Login', onClick: () => documentStore.loginDialog() }) items.push({ label: 'Login', onClick: () => store.loginDialog() })
} }
ContextMenu.showContextMenu({ ContextMenu.showContextMenu({
// @ts-ignore // @ts-ignore
@ -115,3 +115,4 @@ input[type='search'] {
max-width: 30vw; max-width: 30vw;
} }
</style> </style>
@/stores/main

View File

@ -1,29 +1,29 @@
<template> <template>
<template v-if="documentStore.selected.size"> <template v-if="store.selected.size">
<div class="smallgap"></div> <div class="smallgap"></div>
<p class="select-text">{{ documentStore.selected.size }} selected </p> <p class="select-text">{{ store.selected.size }} selected </p>
<SvgButton name="download" data-tooltip="Download" @click="download" /> <SvgButton name="download" data-tooltip="Download" @click="download" />
<SvgButton name="copy" data-tooltip="Copy here" @click="op('cp', dst)" /> <SvgButton name="copy" data-tooltip="Copy here" @click="op('cp', dst)" />
<SvgButton name="paste" data-tooltip="Move here" @click="op('mv', dst)" /> <SvgButton name="paste" data-tooltip="Move here" @click="op('mv', dst)" />
<SvgButton name="trash" data-tooltip="Delete " @click="op('rm')" /> <SvgButton name="trash" data-tooltip="Delete " @click="op('rm')" />
<button class="action-button unselect" data-tooltip="Unselect all" @click="documentStore.selected.clear()"></button> <button class="action-button unselect" data-tooltip="Unselect all" @click="store.selected.clear()"></button>
</template> </template>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import {connect, controlUrl} from '@/repositories/WS' import {connect, controlUrl} from '@/repositories/WS'
import { useDocumentStore } from '@/stores/documents' import { useMainStore } from '@/stores/main'
import { computed } from 'vue' import { computed } from 'vue'
import type { SelectedItems } from '@/repositories/Document' import type { SelectedItems } from '@/repositories/Document'
const documentStore = useDocumentStore() const store = useMainStore()
const props = defineProps({ const props = defineProps({
path: Array<string> path: Array<string>
}) })
const dst = computed(() => props.path!.join('/')) const dst = computed(() => props.path!.join('/'))
const op = (op: string, dst?: string) => { const op = (op: string, dst?: string) => {
const sel = documentStore.selectedFiles const sel = store.selectedFiles
const msg = { const msg = {
op, op,
sel: sel.keys.map(key => { sel: sel.keys.map(key => {
@ -38,12 +38,12 @@ const op = (op: string, dst?: string) => {
const res = JSON.parse(ev.data) const res = JSON.parse(ev.data)
if ('error' in res) { if ('error' in res) {
console.error('Control socket error', msg, res.error) console.error('Control socket error', msg, res.error)
documentStore.error = res.error.message store.error = res.error.message
return return
} else if (res.status === 'ack') { } else if (res.status === 'ack') {
console.log('Control ack OK', res) console.log('Control ack OK', res)
control.close() control.close()
documentStore.selected.clear() store.selected.clear()
return return
} else console.log('Unknown control response', msg, res) } else console.log('Unknown control response', msg, res)
} }
@ -108,17 +108,17 @@ const filesystemdl = async (sel: SelectedItems, handle: FileSystemDirectoryHandl
} }
const download = async () => { const download = async () => {
const sel = documentStore.selectedFiles const sel = store.selectedFiles
console.log('Download', sel) console.log('Download', sel)
if (sel.keys.length === 0) { if (sel.keys.length === 0) {
console.warn('Attempted download but no files found. Missing selected keys:', sel.missing) console.warn('Attempted download but no files found. Missing selected keys:', sel.missing)
documentStore.selected.clear() store.selected.clear()
return return
} }
// Plain old a href download if only one file (ignoring any folders) // Plain old a href download if only one file (ignoring any folders)
const files = sel.recursive.filter(([rel, full, doc]) => !doc.dir) const files = sel.recursive.filter(([rel, full, doc]) => !doc.dir)
if (files.length === 1) { if (files.length === 1) {
documentStore.selected.clear() store.selected.clear()
return linkdl(`/files/${files[0][1]}`) return linkdl(`/files/${files[0][1]}`)
} }
// Use FileSystem API if multiple files and the browser supports it // Use FileSystem API if multiple files and the browser supports it
@ -130,7 +130,7 @@ const download = async () => {
mode: 'readwrite' mode: 'readwrite'
}) })
filesystemdl(sel, handle).then(() => { filesystemdl(sel, handle).then(() => {
documentStore.selected.clear() store.selected.clear()
}) })
return return
} catch (e) { } catch (e) {
@ -140,7 +140,7 @@ const download = async () => {
// Otherwise, zip and download // Otherwise, zip and download
const name = sel.keys.length === 1 ? sel.docs[sel.keys[0]].name : 'download' const name = sel.keys.length === 1 ? sel.docs[sel.keys[0]].name : 'download'
linkdl(`/zip/${Array.from(sel.keys).join('+')}/${name}.zip`) linkdl(`/zip/${Array.from(sel.keys).join('+')}/${name}.zip`)
documentStore.selected.clear() store.selected.clear()
} }
</script> </script>
@ -152,3 +152,4 @@ const download = async () => {
text-overflow: ellipsis; text-overflow: ellipsis;
} }
</style> </style>
@/stores/main

View File

@ -39,10 +39,10 @@
import { reactive, ref } from 'vue' import { reactive, ref } from 'vue'
import { loginUser } from '@/repositories/User' import { loginUser } from '@/repositories/User'
import type { ISimpleError } from '@/repositories/Client' import type { ISimpleError } from '@/repositories/Client'
import { useDocumentStore } from '@/stores/documents' import { useMainStore } from '@/stores/main'
const confirmLoading = ref<boolean>(false) const confirmLoading = ref<boolean>(false)
const store = useDocumentStore() const store = useMainStore()
const loginForm = reactive({ const loginForm = reactive({
username: '', username: '',
@ -99,3 +99,4 @@ const login = async () => {
height: 1em; height: 1em;
} }
</style> </style>
@/stores/main

View File

@ -1,12 +1,12 @@
<script setup lang="ts"> <script setup lang="ts">
import { connect, uploadUrl } from '@/repositories/WS'; import { connect, uploadUrl } from '@/repositories/WS';
import { useDocumentStore } from '@/stores/documents' import { useMainStore } from '@/stores/main'
import { collator } from '@/utils'; import { collator } from '@/utils';
import { computed, onMounted, onUnmounted, reactive, ref } from 'vue' import { computed, onMounted, onUnmounted, reactive, ref } from 'vue'
const fileInput = ref() const fileInput = ref()
const folderInput = ref() const folderInput = ref()
const documentStore = useDocumentStore() const store = useMainStore()
const props = defineProps({ const props = defineProps({
path: Array<string> path: Array<string>
}) })
@ -75,7 +75,7 @@ const uploadFiles = (infiles: File[]) => {
const uploadCloudFiles = (files: CloudFile[]) => { const uploadCloudFiles = (files: CloudFile[]) => {
const dotfiles = files.filter(f => f.cloudName.includes('/.')) const dotfiles = files.filter(f => f.cloudName.includes('/.'))
if (dotfiles.length) { if (dotfiles.length) {
documentStore.error = "Won't upload dotfiles" store.error = "Won't upload dotfiles"
console.log("Dotfiles omitted", dotfiles) console.log("Dotfiles omitted", dotfiles)
files = files.filter(f => !f.cloudName.includes('/.')) files = files.filter(f => !f.cloudName.includes('/.'))
} }
@ -171,13 +171,13 @@ const WSCreate = async () => await new Promise<WebSocket>(resolve => {
open(ev: Event) { resolve(ws) }, open(ev: Event) { resolve(ws) },
error(ev: Event) { error(ev: Event) {
console.error('Upload socket error', ev) console.error('Upload socket error', ev)
documentStore.error = 'Upload socket error' store.error = 'Upload socket error'
}, },
message(ev: MessageEvent) { message(ev: MessageEvent) {
const res = JSON.parse(ev!.data) const res = JSON.parse(ev!.data)
if ('error' in res) { if ('error' in res) {
console.error('Upload socket error', res.error) console.error('Upload socket error', res.error)
documentStore.error = res.error.message store.error = res.error.message
return return
} }
if (res.status === 'ack') { if (res.status === 'ack') {
@ -302,3 +302,4 @@ span {
.position { min-width: 4em } .position { min-width: 4em }
.speed { min-width: 4em } .speed { min-width: 4em }
</style> </style>
@/stores/main

View File

@ -1,4 +1,4 @@
import { useDocumentStore } from "@/stores/documents" import { useMainStore } from "@/stores/main"
import type { FileEntry, UpdateEntry, errorEvent } from "./Document" import type { FileEntry, UpdateEntry, errorEvent } from "./Document"
export const controlUrl = '/api/control' export const controlUrl = '/api/control'
@ -12,7 +12,7 @@ let wsWatch = null as WebSocket | null
export const loadSession = () => { export const loadSession = () => {
const s = localStorage['cista-files'] const s = localStorage['cista-files']
if (!s) return false if (!s) return false
const store = useDocumentStore() const store = useMainStore()
try { try {
tree = JSON.parse(s) tree = JSON.parse(s)
store.updateRoot(tree) store.updateRoot(tree)
@ -39,7 +39,7 @@ export const watchConnect = () => {
clearTimeout(watchTimeout) clearTimeout(watchTimeout)
watchTimeout = null watchTimeout = null
} }
const store = useDocumentStore() const store = useMainStore()
if (store.error !== 'Reconnecting...') store.error = 'Connecting...' if (store.error !== 'Reconnecting...') store.error = 'Connecting...'
console.log(store.error) console.log(store.error)
@ -81,7 +81,7 @@ export const watchDisconnect = () => {
let watchTimeout: any = null let watchTimeout: any = null
const watchReconnect = (event: MessageEvent) => { const watchReconnect = (event: MessageEvent) => {
const store = useDocumentStore() const store = useMainStore()
if (store.connected) { if (store.connected) {
console.warn("Disconnected from server", event) console.warn("Disconnected from server", event)
store.connected = false store.connected = false
@ -114,7 +114,7 @@ const handleWatchMessage = (event: MessageEvent) => {
} }
function handleRootMessage({ root }: { root: FileEntry[] }) { function handleRootMessage({ root }: { root: FileEntry[] }) {
const store = useDocumentStore() const store = useMainStore()
console.log('Watch root', root) console.log('Watch root', root)
store.updateRoot(root) store.updateRoot(root)
tree = root tree = root
@ -122,7 +122,7 @@ function handleRootMessage({ root }: { root: FileEntry[] }) {
} }
function handleUpdateMessage(updateData: { update: UpdateEntry[] }) { function handleUpdateMessage(updateData: { update: UpdateEntry[] }) {
const store = useDocumentStore() const store = useMainStore()
const update = updateData.update const update = updateData.update
console.log('Watch update', update) console.log('Watch update', update)
if (!tree) return console.error('Watch update before root') if (!tree) return console.error('Watch update before root')
@ -146,7 +146,7 @@ function handleUpdateMessage(updateData: { update: UpdateEntry[] }) {
} }
function handleError(msg: errorEvent) { function handleError(msg: errorEvent) {
const store = useDocumentStore() const store = useMainStore()
if (msg.error.code === 401) { if (msg.error.code === 401) {
store.user.isOpenLoginModal = true store.user.isOpenLoginModal = true
store.user.isLoggedIn = false store.user.isLoggedIn = false

View File

@ -1,126 +0,0 @@
import type { FileEntry, FUID, SelectedItems } from '@/repositories/Document'
import { Doc } from '@/repositories/Document'
import { defineStore } from 'pinia'
import { collator } from '@/utils'
import { logoutUser } from '@/repositories/User'
import { watchConnect } from '@/repositories/WS'
import { shallowRef } from 'vue'
type User = {
username: string
privileged: boolean
isOpenLoginModal: boolean
isLoggedIn: boolean
}
export const useDocumentStore = defineStore({
id: 'documents',
state: () => ({
document: shallowRef<Doc[]>([]),
selected: new Set<FUID>(),
fileExplorer: null as any,
error: '' as string,
connected: false,
server: {} as Record<string, any>,
user: {
username: '',
privileged: false,
isLoggedIn: false,
isOpenLoginModal: false
} as User
}),
actions: {
updateRoot(root: FileEntry[]) {
const docs = []
let loc = [] as string[]
for (const [level, name, key, mtime, size, isfile] of root) {
loc = loc.slice(0, level - 1)
docs.push(new Doc({
name,
loc: level ? loc.join('/') : '/',
key,
size,
mtime,
dir: !isfile,
}))
loc.push(name)
}
this.document = docs
},
login(username: string, privileged: boolean) {
this.user.username = username
this.user.privileged = privileged
this.user.isLoggedIn = true
this.user.isOpenLoginModal = false
if (!this.connected) watchConnect()
},
loginDialog() {
this.user.isOpenLoginModal = true
},
async logout() {
console.log("Logout")
await logoutUser()
this.$reset()
localStorage.clear()
history.go() // Reload page
}
},
getters: {
isUserLogged(): boolean {
return this.user.isLoggedIn
},
recentDocuments(): Doc[] {
const ret = [...this.document]
ret.sort((a, b) => b.mtime - a.mtime)
return ret
},
largeDocuments(): Doc[] {
const ret = [...this.document]
ret.sort((a, b) => b.size - a.size)
return ret
},
selectedFiles(): SelectedItems {
const selected = this.selected
const found = new Set<FUID>()
const ret: SelectedItems = {
missing: new Set(),
docs: {},
keys: [],
recursive: [],
}
for (const doc of this.document) {
if (selected.has(doc.key)) {
found.add(doc.key)
ret.keys.push(doc.key)
ret.docs[doc.key] = doc
}
}
// What did we not select?
for (const key of selected) if (!found.has(key)) ret.missing.add(key)
// Build a flat list including contents recursively
const relnames = new Set<string>()
function add(rel: string, full: string, doc: Doc) {
if (!doc.dir && relnames.has(rel)) throw Error(`Multiple selections conflict for: ${rel}`)
relnames.add(rel)
ret.recursive.push([rel, full, doc])
}
for (const key of ret.keys) {
const base = ret.docs[key]
const basepath = base.loc ? `${base.loc}/${base.name}` : base.name
const nremove = base.loc.length
add(base.name, basepath, base)
for (const doc of this.document) {
if (doc.loc === basepath || doc.loc.startsWith(basepath) && doc.loc[basepath.length] === '/') {
const full = doc.loc ? `${doc.loc}/${doc.name}` : doc.name
const rel = full.slice(nremove)
add(rel, full, doc)
}
}
}
// Sort by rel (name stored as on download)
ret.recursive.sort((a, b) => collator.compare(a[0], b[0]))
return ret
}
}
})

View File

@ -10,11 +10,11 @@
<script setup lang="ts"> <script setup lang="ts">
import { watchEffect, ref, computed } from 'vue' import { watchEffect, ref, computed } from 'vue'
import { useDocumentStore } from '@/stores/documents' import { useMainStore } from '@/stores/main'
import Router from '@/router/index' import Router from '@/router/index'
import { needleFormat, localeIncludes, collator } from '@/utils'; import { needleFormat, localeIncludes, collator } from '@/utils';
const documentStore = useDocumentStore() const store = useMainStore()
const fileExplorer = ref() const fileExplorer = ref()
const props = defineProps<{ const props = defineProps<{
path: Array<string> path: Array<string>
@ -24,12 +24,12 @@ const documents = computed(() => {
const loc = props.path.join('/') const loc = props.path.join('/')
const query = props.query const query = props.query
// List the current location // List the current location
if (!query) return documentStore.document.filter(doc => doc.loc === loc) if (!query) return store.document.filter(doc => doc.loc === loc)
// Find up to 100 newest documents that match the search // Find up to 100 newest documents that match the search
const needle = needleFormat(query) const needle = needleFormat(query)
let limit = 100 let limit = 100
let docs = [] let docs = []
for (const doc of documentStore.recentDocuments) { for (const doc of store.recentDocuments) {
if (localeIncludes(doc.haystack, needle)) { if (localeIncludes(doc.haystack, needle)) {
docs.push(doc) docs.push(doc)
if (--limit === 0) break if (--limit === 0) break
@ -53,6 +53,7 @@ const documents = computed(() => {
}) })
watchEffect(() => { watchEffect(() => {
documentStore.fileExplorer = fileExplorer.value store.fileExplorer = fileExplorer.value
}) })
</script> </script>
@/stores/main