@stacksjs/defaults 0.70.88 → 0.70.90
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/app/Actions/Blog/BlogDestroyAction.ts +25 -0
- package/app/Actions/Blog/BlogIndexAction.ts +18 -0
- package/app/Actions/Blog/BlogShowAction.ts +25 -0
- package/app/Actions/Blog/BlogStoreAction.ts +21 -0
- package/app/Actions/Blog/BlogUpdateAction.ts +23 -0
- package/app/Actions/Blog/payload.ts +56 -0
- package/app/Actions/Cms/PostStoreAction.ts +10 -5
- package/app/Actions/Dashboard/Content/AuthorDestroyAction.ts +38 -0
- package/app/Actions/Dashboard/Content/AuthorIndexAction.ts +53 -39
- package/app/Actions/Dashboard/Content/AuthorStoreAction.ts +61 -0
- package/app/Actions/Dashboard/Content/CategoryDestroyAction.ts +30 -0
- package/app/Actions/Dashboard/Content/CategoryIndexAction.ts +40 -17
- package/app/Actions/Dashboard/Content/CategoryStoreAction.ts +58 -0
- package/app/Actions/Dashboard/Content/CommentDestroyAction.ts +30 -0
- package/app/Actions/Dashboard/Content/CommentIndexAction.ts +46 -31
- package/app/Actions/Dashboard/Content/CommentUpdateAction.ts +48 -0
- package/app/Actions/Dashboard/Content/PageDestroyAction.ts +25 -0
- package/app/Actions/Dashboard/Content/PageIndexAction.ts +43 -15
- package/app/Actions/Dashboard/Content/PageStoreAction.ts +49 -0
- package/app/Actions/Dashboard/Content/PostDestroyAction.ts +37 -0
- package/app/Actions/Dashboard/Content/PostIndexAction.ts +64 -83
- package/app/Actions/Dashboard/Content/PostStoreAction.ts +50 -0
- package/app/Actions/Dashboard/Content/PostUpdateAction.ts +54 -0
- package/app/Actions/Dashboard/Content/TagDestroyAction.ts +30 -0
- package/app/Actions/Dashboard/Content/TagIndexAction.ts +39 -13
- package/app/Actions/Dashboard/Content/TagStoreAction.ts +60 -0
- package/app/Actions/Dashboard/Content/comment-input.ts +35 -0
- package/app/Actions/Dashboard/Content/content-input.ts +65 -0
- package/app/Actions/Dashboard/Content/post-input.ts +54 -0
- package/app/Models/Content/Post.ts +4 -1
- package/package.json +1 -1
- package/resources/components/Dashboard/FileViewer/MediaCard.stx +119 -0
- package/resources/components/Dashboard/FileViewer/MediaInfoPanel.stx +143 -0
- package/resources/components/Dashboard/SidebarModern.stx +1 -0
- package/resources/functions/dashboard/files-tree.ts +162 -0
- package/resources/functions/dashboard/sidebar.ts +1 -0
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Build the dashboard File Manager tree from a real Storage disk.
|
|
3
|
+
*
|
|
4
|
+
* The `content/files` page seeds its grid with this at request time (from a
|
|
5
|
+
* `<script server>` block) so it browses actual files through @stacksjs/storage
|
|
6
|
+
* instead of demo data. Defaults to the `public` disk, which carries real media
|
|
7
|
+
* plus public URLs, so image/video previews render. Bounded in depth and entry
|
|
8
|
+
* count so a large disk can't blow up SSR, and returns `null` on any error or an
|
|
9
|
+
* empty disk so the page falls back to its built-in demo tree.
|
|
10
|
+
*
|
|
11
|
+
* Usage from a page's `<script server>`:
|
|
12
|
+
*
|
|
13
|
+
* const { buildStorageFileTree } =
|
|
14
|
+
* await import('../../../resources/functions/dashboard/files-tree')
|
|
15
|
+
* const tree = await buildStorageFileTree({ disk: 'public' })
|
|
16
|
+
*/
|
|
17
|
+
import { Storage } from '@stacksjs/storage'
|
|
18
|
+
|
|
19
|
+
export interface FileTreeNode {
|
|
20
|
+
id: number
|
|
21
|
+
name: string
|
|
22
|
+
type: string
|
|
23
|
+
size?: number
|
|
24
|
+
path: string
|
|
25
|
+
lastModified: string
|
|
26
|
+
starred: boolean
|
|
27
|
+
shared: boolean
|
|
28
|
+
mime_type?: string
|
|
29
|
+
url?: string
|
|
30
|
+
items?: FileTreeNode[]
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export interface BuildFileTreeOptions {
|
|
34
|
+
/** Storage disk to list. @default 'public' */
|
|
35
|
+
disk?: string
|
|
36
|
+
/** How many directory levels to descend. @default 3 */
|
|
37
|
+
maxDepth?: number
|
|
38
|
+
/** Hard cap on total entries, to bound SSR cost. @default 600 */
|
|
39
|
+
maxEntries?: number
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function extensionOf(name: string): string {
|
|
43
|
+
const dot = name.lastIndexOf('.')
|
|
44
|
+
return dot > 0 ? name.slice(dot + 1).toLowerCase() : 'file'
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function baseNameOf(path: string): string {
|
|
48
|
+
const trimmed = path.replace(/\/+$/, '')
|
|
49
|
+
const idx = trimmed.lastIndexOf('/')
|
|
50
|
+
return idx >= 0 ? trimmed.slice(idx + 1) : trimmed
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function normalizePath(path: string): string {
|
|
54
|
+
return `/${path}`.replace(/\/+/g, '/')
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* List `disk` into a File-Manager tree, or `null` when it can't be listed or is
|
|
59
|
+
* empty (so callers can fall back to demo data). Never throws.
|
|
60
|
+
*/
|
|
61
|
+
export async function buildStorageFileTree(options: BuildFileTreeOptions = {}): Promise<FileTreeNode | null> {
|
|
62
|
+
const { disk = 'public', maxDepth = 3, maxEntries = 600 } = options
|
|
63
|
+
|
|
64
|
+
let adapter: any
|
|
65
|
+
try {
|
|
66
|
+
adapter = Storage.disk(disk)
|
|
67
|
+
}
|
|
68
|
+
catch {
|
|
69
|
+
return null
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
let idCounter = 1
|
|
73
|
+
let entryCount = 0
|
|
74
|
+
|
|
75
|
+
async function walk(dir: string, name: string, depth: number): Promise<FileTreeNode> {
|
|
76
|
+
const node: FileTreeNode = {
|
|
77
|
+
id: idCounter++,
|
|
78
|
+
name,
|
|
79
|
+
type: 'folder',
|
|
80
|
+
path: normalizePath(dir),
|
|
81
|
+
lastModified: new Date().toISOString(),
|
|
82
|
+
starred: false,
|
|
83
|
+
shared: true,
|
|
84
|
+
items: [],
|
|
85
|
+
}
|
|
86
|
+
if (depth <= 0)
|
|
87
|
+
return node
|
|
88
|
+
|
|
89
|
+
const entries: { path: string, type: string }[] = []
|
|
90
|
+
try {
|
|
91
|
+
for await (const entry of adapter.list(dir)) {
|
|
92
|
+
entries.push({ path: String(entry.path), type: String(entry.type) })
|
|
93
|
+
if (entries.length >= maxEntries)
|
|
94
|
+
break
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
catch {
|
|
98
|
+
return node
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// Folders first, then files, each alphabetical — matches Finder ordering.
|
|
102
|
+
entries.sort((a, b) => {
|
|
103
|
+
if (a.type !== b.type)
|
|
104
|
+
return a.type === 'directory' ? -1 : 1
|
|
105
|
+
return baseNameOf(a.path).localeCompare(baseNameOf(b.path))
|
|
106
|
+
})
|
|
107
|
+
|
|
108
|
+
for (const entry of entries) {
|
|
109
|
+
if (entryCount >= maxEntries)
|
|
110
|
+
break
|
|
111
|
+
const base = baseNameOf(entry.path)
|
|
112
|
+
if (!base || base.startsWith('.'))
|
|
113
|
+
continue
|
|
114
|
+
entryCount++
|
|
115
|
+
|
|
116
|
+
if (entry.type === 'directory') {
|
|
117
|
+
node.items!.push(await walk(entry.path, base, depth - 1))
|
|
118
|
+
continue
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
let size = 0
|
|
122
|
+
let lastModified = new Date().toISOString()
|
|
123
|
+
let mimeType = ''
|
|
124
|
+
let url = ''
|
|
125
|
+
try {
|
|
126
|
+
const stat = await adapter.stat(entry.path)
|
|
127
|
+
size = Number(stat?.size ?? 0)
|
|
128
|
+
if (stat?.lastModified)
|
|
129
|
+
lastModified = new Date(stat.lastModified).toISOString()
|
|
130
|
+
mimeType = String(stat?.mimeType ?? '')
|
|
131
|
+
}
|
|
132
|
+
catch {}
|
|
133
|
+
try {
|
|
134
|
+
url = await adapter.publicUrl(entry.path)
|
|
135
|
+
}
|
|
136
|
+
catch {}
|
|
137
|
+
|
|
138
|
+
node.items!.push({
|
|
139
|
+
id: idCounter++,
|
|
140
|
+
name: base,
|
|
141
|
+
type: extensionOf(base),
|
|
142
|
+
size,
|
|
143
|
+
path: normalizePath(entry.path),
|
|
144
|
+
lastModified,
|
|
145
|
+
mime_type: mimeType,
|
|
146
|
+
url,
|
|
147
|
+
starred: false,
|
|
148
|
+
shared: true,
|
|
149
|
+
})
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
return node
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
try {
|
|
156
|
+
const tree = await walk('', 'Home', maxDepth)
|
|
157
|
+
return tree.items && tree.items.length > 0 ? tree : null
|
|
158
|
+
}
|
|
159
|
+
catch {
|
|
160
|
+
return null
|
|
161
|
+
}
|
|
162
|
+
}
|
|
@@ -341,6 +341,7 @@ export function buildNavSections(
|
|
|
341
341
|
sections.push(['content', 'content', [
|
|
342
342
|
{ to: '/content/dashboard', icon: 'dashboard', text: 'Dashboard' },
|
|
343
343
|
{ to: '/content/files', icon: 'files', text: 'Files' },
|
|
344
|
+
{ to: '/content/blog', icon: 'post', text: 'Blog' },
|
|
344
345
|
{ to: '/content/pages', icon: 'file', text: 'Pages' },
|
|
345
346
|
{ to: '/content/posts', icon: 'post', text: 'Posts' },
|
|
346
347
|
{ to: '/content/categories', icon: 'tags', text: 'Categories' },
|