Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
227 changes: 37 additions & 190 deletions src/App.vue
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,6 @@ import {
} from './features/editor/editorRuntime'
import {
removeLocalDraft,
renameLocalDraft,
upsertLocalDraft,
type LocalDraftRecord,
} from './lib/localDrafts'
Expand All @@ -133,7 +132,6 @@ import {
areAllDocumentsPinned,
getPinnedDocumentIds,
prunePinnedDocuments,
togglePinnedDocuments,
type PinnedDocumentRecord,
} from './lib/pinnedDocuments'
import {
Expand All @@ -154,8 +152,7 @@ import {
type HomeDocumentText,
} from './features/home/homeDocuments'
import { useDocumentSelection } from './features/home/useDocumentSelection'
import { renameAndroidRecentDocumentWorkflow } from './features/android-documents/renameRecentDocumentWorkflow'
import { shareRecentDocumentsWorkflow } from './features/android-documents/shareRecentDocumentsWorkflow'
import { createHomeDocumentActions } from './features/home/homeDocumentActions'
import {
DEFAULT_SETTINGS_PAGE,
SETTINGS_PAGES,
Expand Down Expand Up @@ -1794,191 +1791,35 @@ async function openFileFromAndroid() {
}
}

function getSelectedDocumentRecords() {
const selectedIds = homeSelection.selectedIds.value
return documentItems.value.filter(record => selectedIds.has(record.id))
}

// Pins may only be pruned against everything in storage: the home list hides
// recovery drafts and setting-disabled drafts without deleting them.
function getAllStoredDocumentIds() {
return [
...localDrafts.value.map(draft => draft.id),
...androidRecentDocuments.value.map(record => record.id),
]
}

function pinSelectedDocuments() {
const selectedIds = [...homeSelection.selectedIds.value]
if (selectedIds.length === 0) {
return
}

persistPinnedDocuments(
togglePinnedDocuments(pinnedDocuments.value, selectedIds, new Date().toISOString()),
)
appLog.info('toggled home document pins', { count: selectedIds.length })
homeSelection.clear()
}

function deleteSelectedDocuments() {
const selectedRecords = getSelectedDocumentRecords()
if (selectedRecords.length === 0) {
return
}

const draftIds = new Set(
selectedRecords.filter(record => record.kind === 'local-draft').map(record => record.id),
)
const androidRecords = selectedRecords.filter(record => record.kind === 'android-document')

if (draftIds.size > 0) {
persistLocalDrafts(localDrafts.value.filter(draft => !draftIds.has(draft.id)))
}

if (androidRecords.length > 0) {
const androidIds = new Set(androidRecords.map(record => record.id))
persistAndroidRecentDocuments(
androidRecentDocuments.value.filter(record => !androidIds.has(record.id)),
)
}

persistPinnedDocuments(prunePinnedDocuments(pinnedDocuments.value, getAllStoredDocumentIds()))

// A deleted document must not resurrect from the open editor session.
if (selectedRecords.some(record => record.id === documentState.value.id)) {
documentState.value = createUntitledDocument({ autosaveTarget: 'local-draft' })
currentAndroidDocumentCanWrite.value = false
}

appLog.info('deleted home documents', {
drafts: draftIds.size,
androidDocuments: androidRecords.length,
})
homeSelection.clear()
}

async function shareSelectedDocuments() {
const selectedRecords = getSelectedDocumentRecords()
if (selectedRecords.length === 0) {
return
}

const result = await shareRecentDocumentsWorkflow({
documents: selectedRecords,
readAndroidMarkdownDocument: sourceUri =>
readAndroidMarkdownDocument(sourceUri, androidMarkdownSettings.value),
shareAndroidMarkdownDocument,
shareAndroidMarkdownDocuments,
getAndroidDocumentUserMessage,
imageSharingSettings: imageSharingSettings.value,
markdownSaveSettings: markdownSaveSettings.value,
logger: androidDocumentLog,
})

if (result.kind === 'failed') {
// Keep the selection so a transient failure can be retried as-is.
homeNotice.value = result.status
return
}

homeNotice.value = null
homeSelection.clear()
}

async function renameSelectedDocument(id: string, name: string) {
const record = documentItems.value.find(item => item.id === id)
if (!record) {
appLog.warn('rename target not found', { id })
return
}

if (record.kind === 'local-draft') {
persistLocalDrafts(renameLocalDraft(localDrafts.value, id, name))
if (documentState.value.id === id) {
// Keeps the new name flowing through the next autosave of an open session.
documentState.value = { ...documentState.value, displayName: name.trim() }
}
appLog.info('renamed local draft', { id })
homeNotice.value = null
homeSelection.clear()
return
}

const result = await renameAndroidRecentDocumentWorkflow({
record,
newName: name,
readAndroidMarkdownDocument: sourceUri =>
readAndroidMarkdownDocument(sourceUri, androidMarkdownSettings.value),
renameAndroidMarkdownDocument,
getAndroidDocumentUserMessage,
logger: androidDocumentLog,
})

if (result.kind === 'failed') {
homeNotice.value = result.status
return
}

const remainingRecentDocuments = androidRecentDocuments.value.filter(
item => item.id !== result.previousId,
)
persistAndroidRecentDocuments(
result.accessRetained
? [result.updatedRecord, ...remainingRecentDocuments]
: remainingRecentDocuments,
)

// Renaming can change the SAF URI; migrate everything keyed by it.
if (result.updatedRecord.id !== result.previousId) {
if (result.accessRetained && pinnedDocumentIds.value.has(result.previousId)) {
persistPinnedDocuments(
pinnedDocuments.value.map(pin =>
pin.id === result.previousId ? { ...pin, id: result.updatedRecord.id } : pin,
),
)
}

const previousUri = record.sourceUri
const nextUri = result.updatedRecord.sourceUri
if (previousUri && nextUri && previousUri !== nextUri) {
const previousRecoveryId = getAndroidRecoveryDraftId(previousUri)
const recoveryDraft = localDrafts.value.find(draft => draft.id === previousRecoveryId)
if (recoveryDraft) {
persistLocalDrafts([
{ ...recoveryDraft, id: getAndroidRecoveryDraftId(nextUri) },
...removeLocalDraft(localDrafts.value, previousRecoveryId),
])
}
}
}

if (!result.accessRetained) {
// A session grant cannot back a durable home entry or pin. Recovery
// content remains local under the migrated URI until the user reopens the
// renamed file through Android and obtains a new persisted grant.
persistPinnedDocuments(
pinnedDocuments.value.filter(
pin => pin.id !== result.previousId && pin.id !== result.updatedRecord.id,
),
)
}

if (documentState.value.id === result.previousId) {
documentState.value = {
...documentState.value,
id: result.updatedRecord.id,
sourceUri: result.updatedRecord.sourceUri,
displayName: result.updatedRecord.displayName,
}
}

// The rename itself succeeded, but a session-scoped URI must not survive in
// durable Recents. Tell the user how to restore the entry explicitly.
homeNotice.value = result.accessRetained ? null : RENAME_TEMPORARY_ACCESS_MESSAGE
homeSelection.clear()
}

const {
pinSelectedDocuments,
deleteSelectedDocuments,
shareSelectedDocuments,
renameSelectedDocument,
} = createHomeDocumentActions({
documentItems,
selection: homeSelection,
homeNotice,
localDrafts,
persistLocalDrafts,
androidRecentDocuments,
persistAndroidRecentDocuments,
pinnedDocuments,
persistPinnedDocuments,
documentState,
currentAndroidDocumentCanWrite,
readAndroidMarkdownDocument: sourceUri =>
readAndroidMarkdownDocument(sourceUri, androidMarkdownSettings.value),
shareAndroidMarkdownDocument,
shareAndroidMarkdownDocuments,
renameAndroidMarkdownDocument,
getAndroidDocumentUserMessage,
imageSharingSettings,
markdownSaveSettings,
renameTemporaryAccessMessage: RENAME_TEMPORARY_ACCESS_MESSAGE,
appLogger: appLog,
documentLogger: androidDocumentLog,
})
async function openDocument(id: string) {
const recentDocument = documentItems.value.find(record => record.id === id)
if (!recentDocument) {
Expand Down Expand Up @@ -2409,7 +2250,13 @@ onMounted(() => {
documentState.value = createUntitledDocument({ autosaveTarget: 'local-draft' })
}

const prunedPins = prunePinnedDocuments(pinnedDocuments.value, getAllStoredDocumentIds())
// Prune against everything in storage, not the filtered home list — hidden
// recovery drafts and setting-disabled drafts still exist and keep pins.
const storedDocumentIds = [
...localDrafts.value.map(draft => draft.id),
...androidRecentDocuments.value.map(record => record.id),
]
const prunedPins = prunePinnedDocuments(pinnedDocuments.value, storedDocumentIds)
if (prunedPins.length !== pinnedDocuments.value.length) {
persistPinnedDocuments(prunedPins)
}
Expand Down
Loading
Loading