Skip to content

Commit daac69e

Browse files
author
codewec
committed
feat: allow ping all elements
1 parent b686053 commit daac69e

9 files changed

Lines changed: 106 additions & 34 deletions

app/composables/api/buildActionsApi.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ export function buildActionsApi(options: BuildEditoroApiOptions) {
1010

1111
return {
1212
openCreateModal: uiStore.openCreateModal,
13+
openPath: options.actions.openPath,
1314
createEntry: entryActions.createEntry,
1415
openRenameModal: entryActions.openRenameModal,
1516
renameSelectedEntry: entryActions.renameSelectedEntry,

app/composables/editor/useEditoroEditorPins.ts

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ type EditoroEditorPinsOptions = {
66

77
const PINNED_FILES_COOKIE_KEY = 'editoro.pinned-files'
88

9-
function parsePinnedFilePaths(raw: string | undefined) {
9+
function parsePinnedFilePaths(raw: unknown) {
1010
if (!raw) {
1111
return []
1212
}
@@ -37,6 +37,10 @@ function areSamePaths(left: string[], right: string[]) {
3737
return left.length === right.length && left.every((value, index) => value === right[index])
3838
}
3939

40+
function hasHiddenSegment(path: string) {
41+
return path.split('/').some(segment => segment.startsWith('.'))
42+
}
43+
4044
/**
4145
* Handles pinned file tabs persistence and mutation.
4246
* Used by `app/stores/editoroEditor.ts`.
@@ -105,8 +109,15 @@ export function useEditoroEditorPins(options: EditoroEditorPinsOptions) {
105109
togglePinnedFile(options.activeFilePath.value)
106110
}
107111

108-
function reconcilePinnedFiles(existingFilePaths: Set<string>) {
109-
const nextPaths = pinnedFilePaths.value.filter(path => existingFilePaths.has(path))
112+
function reconcilePinnedFiles(existingFilePaths: Set<string>, preserveHiddenPaths = false) {
113+
const nextPaths = pinnedFilePaths.value.filter((path) => {
114+
if (existingFilePaths.has(path)) {
115+
return true
116+
}
117+
118+
return preserveHiddenPaths && hasHiddenSegment(path)
119+
})
120+
110121
if (nextPaths.length === pinnedFilePaths.value.length) {
111122
return
112123
}

app/composables/useEditoroContext.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,9 @@ export function useEditoroContext() {
4242
const fileSelection = useEditoroFileSelection({
4343
selectedNode: treeRefs.selectedNode,
4444
activeFilePath: editorRefs.activeFilePath,
45+
pinnedFilePaths: editorRefs.pinnedFilePaths,
46+
showHiddenEntries: refs.preferencesRefs.showHiddenEntries,
47+
setShowHiddenEntries: preferencesStore.setShowHiddenEntries,
4548
treeInitialized: treeRefs.treeInitialized,
4649
editorStore,
4750
loadTree
@@ -72,7 +75,8 @@ export function useEditoroContext() {
7275
entryActions,
7376
actions: {
7477
refreshTree: treeActions.refreshTree,
75-
toggleShowHiddenEntries: treeActions.toggleShowHiddenEntries
78+
toggleShowHiddenEntries: treeActions.toggleShowHiddenEntries,
79+
openPath: fileSelection.openPath
7680
}
7781
}
7882
}

app/composables/useEditoroFileSelection.ts

Lines changed: 36 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
import type { Ref } from 'vue'
66
import type { TreeNode } from '~/types/editoro'
77
import { isImagePath, isMarkdownPath } from '~/utils/editoro-file'
8+
import { hasHiddenPathSegment } from '~/utils/editoro-path'
89

910
type EditorStoreLike = {
1011
clearEditor: () => void
@@ -14,6 +15,9 @@ type EditorStoreLike = {
1415
type FileSelectionOptions = {
1516
selectedNode: Ref<TreeNode | undefined>
1617
activeFilePath: Ref<string>
18+
pinnedFilePaths: Ref<string[]>
19+
showHiddenEntries: Ref<boolean>
20+
setShowHiddenEntries: (nextValue: boolean) => void
1721
treeInitialized: Ref<boolean>
1822
editorStore: EditorStoreLike
1923
loadTree: (preferPath?: string) => Promise<void>
@@ -36,6 +40,27 @@ export function useEditoroFileSelection(options: FileSelectionOptions) {
3640
return ''
3741
}
3842

43+
function getPinnedFallbackPath() {
44+
return options.pinnedFilePaths.value[0] || ''
45+
}
46+
47+
function getInitialPreferredPath() {
48+
return getFileQueryValue() || getPinnedFallbackPath()
49+
}
50+
51+
async function openPath(path: string) {
52+
if (!path) {
53+
return false
54+
}
55+
56+
if (hasHiddenPathSegment(path) && !options.showHiddenEntries.value) {
57+
options.setShowHiddenEntries(true)
58+
}
59+
60+
await options.loadTree(path)
61+
return options.selectedNode.value?.path === path
62+
}
63+
3964
async function syncRouteWithFile(filePath?: string) {
4065
const current = getFileQueryValue()
4166
const next = filePath || ''
@@ -74,36 +99,36 @@ export function useEditoroFileSelection(options: FileSelectionOptions) {
7499

75100
async function initializeFromRouteOnMounted() {
76101
const filePath = getFileQueryValue()
102+
const preferredPath = getInitialPreferredPath()
77103

78-
async function ensureRouteFileSelected(path: string) {
104+
async function ensurePathSelected(path: string) {
79105
const selected = options.selectedNode.value
80-
if (selected?.type === 'file' && selected.path === path) {
106+
if (selected?.path === path) {
81107
return true
82108
}
83109

84-
await options.loadTree(path)
85-
86-
const nextSelected = options.selectedNode.value
87-
return nextSelected?.type === 'file' && nextSelected.path === path
110+
return await openPath(path)
88111
}
89112

90113
if (!options.treeInitialized.value) {
91-
await options.loadTree(filePath || undefined)
114+
await options.loadTree(preferredPath || undefined)
92115

93-
if (filePath && !(await ensureRouteFileSelected(filePath))) {
116+
if (filePath && !(await ensurePathSelected(filePath))) {
94117
await syncRouteWithFile(undefined)
95118
}
96119

97120
return
98121
}
99122

100123
if (filePath) {
101-
const isOpened = await ensureRouteFileSelected(filePath)
124+
const isOpened = await ensurePathSelected(filePath)
102125
if (!isOpened) {
103126
await syncRouteWithFile(undefined)
104127
options.editorStore.clearEditor()
105128
return
106129
}
130+
} else if (preferredPath) {
131+
await ensurePathSelected(preferredPath)
107132
}
108133

109134
// Hydration case: selected node can already be restored from SSR state,
@@ -126,6 +151,8 @@ export function useEditoroFileSelection(options: FileSelectionOptions) {
126151

127152
return {
128153
getFileQueryValue,
154+
getInitialPreferredPath,
155+
openPath,
129156
syncRouteWithFile,
130157
handleSelectedNodeChange,
131158
initializeFromRouteOnMounted

app/composables/useEditoroLifecycle.ts

Lines changed: 17 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,11 @@
55
import type { Ref } from 'vue'
66
import type { TreeNode } from '~/types/editoro'
77
import { fetchTree } from '~/services/files-api'
8-
import { collectFilePaths } from '~/utils/editoro-path'
8+
import { collectNodePaths } from '~/utils/editoro-path'
99

1010
type FileSelectionLike = {
1111
getFileQueryValue: () => string
12+
getInitialPreferredPath: () => string
1213
handleSelectedNodeChange: (node?: TreeNode) => Promise<void>
1314
initializeFromRouteOnMounted: () => Promise<void>
1415
}
@@ -19,7 +20,7 @@ type TreeStoreLike = {
1920

2021
type EditorStoreLike = {
2122
clearPendingSave: () => void
22-
reconcilePinnedFiles: (existingFilePaths: Set<string>) => void
23+
reconcilePinnedFiles: (existingFilePaths: Set<string>, preserveHiddenPaths?: boolean) => void
2324
}
2425

2526
type UiStoreLike = {
@@ -38,13 +39,21 @@ type LifecycleOptions = {
3839
}
3940

4041
export async function useEditoroLifecycle(options: LifecycleOptions) {
41-
watch([options.treeItems, options.treeInitialized], ([items, initialized]) => {
42+
const reconcilePinnedEntries = (items: TreeNode[], initialized: boolean) => {
4243
if (!initialized) {
4344
return
4445
}
4546

46-
options.editorStore.reconcilePinnedFiles(collectFilePaths(items))
47-
}, { immediate: true })
47+
options.editorStore.reconcilePinnedFiles(
48+
collectNodePaths(items),
49+
!options.showHiddenEntries.value
50+
)
51+
}
52+
53+
// Reconcile only on client/after setup to avoid hydration mismatch.
54+
watch([options.treeItems, options.treeInitialized], ([items, initialized]) => {
55+
reconcilePinnedEntries(items, initialized)
56+
}, { flush: 'post' })
4857

4958
watch(options.selectedNode, async (node) => {
5059
await options.fileSelection.handleSelectedNodeChange(node)
@@ -57,15 +66,16 @@ export async function useEditoroLifecycle(options: LifecycleOptions) {
5766

5867
onMounted(async () => {
5968
await options.fileSelection.initializeFromRouteOnMounted()
69+
reconcilePinnedEntries(options.treeItems.value, options.treeInitialized.value)
6070
})
6171

62-
const initialFilePath = options.fileSelection.getFileQueryValue()
72+
const initialPreferredPath = options.fileSelection.getInitialPreferredPath()
6373
const { data: initialTreeData } = await useAsyncData(
6474
'editoro-tree-initial',
6575
() => fetchTree(options.showHiddenEntries.value)
6676
)
6777

6878
if (initialTreeData.value?.items) {
69-
options.treeStore.applyTreeData(initialTreeData.value.items, initialFilePath || undefined)
79+
options.treeStore.applyTreeData(initialTreeData.value.items, initialPreferredPath || undefined)
7080
}
7181
}

app/composables/useEditoroViewModel.ts

Lines changed: 6 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@ export function useEditoroViewModel(options: ViewModelOptions) {
6262
const headerBadges = computed<EditorPinnedBadge[]>(() => {
6363
const badges: EditorPinnedBadge[] = []
6464
const currentNode = options.selectedNode.value
65-
const currentPath = currentNode?.type === 'file' ? currentNode.path : ''
65+
const currentPath = currentNode?.path || ''
6666

6767
for (const path of options.pinnedFilePaths.value) {
6868
const node = findNodeByPath(options.treeItems.value, path)
@@ -76,25 +76,18 @@ export function useEditoroViewModel(options: ViewModelOptions) {
7676
})
7777
}
7878

79-
// Show non-pinned current file as the right-most badge.
80-
if (currentNode?.type === 'file' && !options.pinnedFilePaths.value.includes(currentNode.path)) {
79+
// Show non-pinned selected node as the right-most badge.
80+
if (currentNode && !options.pinnedFilePaths.value.includes(currentNode.path)) {
8181
badges.push({
8282
key: `current:${currentNode.path}`,
8383
path: currentNode.path,
84-
label: currentNode.label || getBaseName(currentNode.path) || currentNode.path,
84+
label: currentNode.type === 'directory'
85+
? currentNode.path
86+
: (currentNode.label || getBaseName(currentNode.path) || currentNode.path),
8587
isCurrent: true,
8688
isPinned: false,
8789
canPin: true
8890
})
89-
} else if (currentNode?.type === 'directory') {
90-
badges.push({
91-
key: `selected:${currentNode.path}`,
92-
path: currentNode.path,
93-
label: currentNode.path,
94-
isCurrent: true,
95-
isPinned: false,
96-
canPin: false
97-
})
9891
} else if (!currentNode) {
9992
badges.push({
10093
key: 'root',

app/composables/workspace/useEditoroMainHeaderBindings.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ export function useEditoroMainHeaderBindings(state: EditoroState) {
2727
return
2828
}
2929

30-
state.tree.selectNodeByPath(path)
30+
void state.actions.openPath(path)
3131
},
3232
togglePin: (path: string) => {
3333
if (!path) {

app/stores/editoroPreferences.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,10 @@ export const useEditoroPreferencesStore = defineStore('editoro-preferences', ()
4949
showHiddenEntries.value = !showHiddenEntries.value
5050
}
5151

52+
function setShowHiddenEntries(nextValue: boolean) {
53+
showHiddenEntries.value = !!nextValue
54+
}
55+
5256
function openSettingsModal() {
5357
settingsLocale.value = locale.value
5458
settingsColorMode.value = (colorMode.preference as ColorModePreference) || 'system'
@@ -99,6 +103,7 @@ export const useEditoroPreferencesStore = defineStore('editoro-preferences', ()
99103
settingsColorMode,
100104
initialize,
101105
toggleShowHiddenEntries,
106+
setShowHiddenEntries,
102107
openSettingsModal,
103108
closeSettingsModal,
104109
setLocalePreference,

app/utils/editoro-path.ts

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,3 +76,24 @@ export function collectFilePaths(items: TreeNode[]) {
7676
walk(items)
7777
return files
7878
}
79+
80+
export function collectNodePaths(items: TreeNode[]) {
81+
const nodes = new Set<string>()
82+
83+
const walk = (entries: TreeNode[]) => {
84+
for (const entry of entries) {
85+
nodes.add(entry.path)
86+
87+
if (entry.type === 'directory' && entry.children.length) {
88+
walk(entry.children)
89+
}
90+
}
91+
}
92+
93+
walk(items)
94+
return nodes
95+
}
96+
97+
export function hasHiddenPathSegment(path: string) {
98+
return path.split('/').some(segment => segment.startsWith('.'))
99+
}

0 commit comments

Comments
 (0)