@edgedev/create-edge-app 1.1.19 → 1.1.23
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/agent.md +78 -0
- package/package.json +2 -1
- package/pages/app/dashboard/blocks/[block].vue +38 -0
- package/pages/app/dashboard/blocks/index.vue +347 -0
- package/pages/app/dashboard/media/index.vue +24 -0
- package/pages/app/dashboard/sites/[site]/[[page]].vue +50 -0
- package/pages/app/dashboard/sites/[site].vue +42 -0
- package/pages/app/dashboard/sites/index.vue +114 -0
- package/pages/app/dashboard/templates/[page].vue +44 -0
- package/pages/app/dashboard/templates.vue +35 -0
- package/pages/app/dashboard/themes/[theme].vue +37 -0
- package/pages/app/dashboard/themes/index.vue +44 -0
- package/pages/app.vue +38 -0
- package/plugins/icons.ts +92 -1
- package/plugins/monacoEditor.ts +36 -0
package/agent.md
ADDED
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
# Edge App Agent Guide
|
|
2
|
+
|
|
3
|
+
This project is Nuxt 3 + Vue 3, SPA mode. Follow these rules so new code matches how we already build apps.
|
|
4
|
+
|
|
5
|
+
## Core stack and style
|
|
6
|
+
- Use `<script setup>` with JavaScript only (no TypeScript, no Options API).
|
|
7
|
+
- Components and composables from `edge/composables/**` are auto-imported; avoid manual imports unless needed.
|
|
8
|
+
- Utilities: use `cn` from `@/lib/utils` for class merging, `lucide-vue-next` for icons, Tailwind for layout/styling. Keep comments minimal and useful.
|
|
9
|
+
- Components under `edge/components` are globally registered with the `edge-` prefix (e.g., `edge-dashboard`, `edge-editor`, `edge-shad-button`).
|
|
10
|
+
|
|
11
|
+
## Firebase and data access
|
|
12
|
+
- Never import Firebase SDKs directly. All Auth/Firestore/Functions/Storage access goes through the injected `edgeFirebase` plugin (`plugins/firebase.client.ts` from `@edgedev/firebase`).
|
|
13
|
+
- Get the instance via `const edgeFirebase = inject('edgeFirebase')`. Use the wrapper methods already used in the codebase: `startSnapshot/stopSnapshot`, `startUsersSnapshot`, `SearchStaticData`, `getDocData`, `storeDoc`, `changeDoc`, `removeDoc`, `runFunction`, `setUserMeta`, `addUser`, `removeUserRoles`, etc.
|
|
14
|
+
- Always scope Firestore paths to the active org using `edgeGlobal.edgeState.organizationDocPath` (e.g., ``${edgeGlobal.edgeState.organizationDocPath}/things``). Organization is set via `projectSetOrg`/`edgeGlobal.setOrganization` and lives in `edgeGlobal.edgeState.currentOrganization`.
|
|
15
|
+
- For reads that should live-update, start a snapshot; for static one-off queries, use `new edgeFirebase.SearchStaticData().getData(path, query, order, limit)`. Clean up snapshots when appropriate.
|
|
16
|
+
- Auth state comes from `edgeFirebase.user` (see `edge/components/auth.vue` patterns). Use provided helpers instead of rolling your own auth flows.
|
|
17
|
+
|
|
18
|
+
## Preferred UI building blocks
|
|
19
|
+
- Lists/tables: use `edge-dashboard`. Pass `collection`, `filter`/`filters`, search/sort props, and point it at the org-scoped collection path. It handles snapshots, search, pagination, delete dialogs, and navigation to item routes.
|
|
20
|
+
- Editing/creation: use `edge-editor`. Provide `collection`, `docId` (`'new'` when creating), `newDocSchema` (fields with default values), and optional `schema`/overrides. It manages working copy, validation hooks, unsaved-change guards, and redirects.
|
|
21
|
+
- Surrounding layout: `pages/app.vue` already wires the sidebar, menu, and panels. Put new dashboard/edit pages under `/pages/app/dashboard/...` and keep them lean—compose existing Edge components rather than building new scaffolding.
|
|
22
|
+
- Form controls: prefer the Edge shadcn wrappers in `edge/components/shad` (e.g., `edge-shad-input`, `edge-shad-select`, `edge-shad-button`) and specialized controls like `edge-g-input`, `edge-auto-file-upload`, etc., instead of raw HTML inputs.
|
|
23
|
+
|
|
24
|
+
## Typical collection pattern
|
|
25
|
+
```vue
|
|
26
|
+
<script setup>
|
|
27
|
+
const edgeFirebase = inject('edgeFirebase')
|
|
28
|
+
const route = useRoute()
|
|
29
|
+
|
|
30
|
+
const collection = 'things'
|
|
31
|
+
const docId = computed(() => route.params.docId || 'new')
|
|
32
|
+
|
|
33
|
+
const newDocSchema = {
|
|
34
|
+
name: { value: '' },
|
|
35
|
+
description: { value: '' },
|
|
36
|
+
}
|
|
37
|
+
</script>
|
|
38
|
+
|
|
39
|
+
<template>
|
|
40
|
+
<edge-dashboard
|
|
41
|
+
v-if="!route.params.docId"
|
|
42
|
+
:collection="collection"
|
|
43
|
+
class="h-full"
|
|
44
|
+
header-class="bg-secondary"
|
|
45
|
+
/>
|
|
46
|
+
|
|
47
|
+
<edge-editor
|
|
48
|
+
v-else
|
|
49
|
+
:collection="collection"
|
|
50
|
+
:doc-id="docId"
|
|
51
|
+
:new-doc-schema="newDocSchema"
|
|
52
|
+
/>
|
|
53
|
+
</template>
|
|
54
|
+
```
|
|
55
|
+
Adjust props (search, filters, pagination, save overrides) using the existing component APIs rather than reinventing logic.
|
|
56
|
+
|
|
57
|
+
### Slots and layout flexibility
|
|
58
|
+
- Both `edge-dashboard` and `edge-editor` expose many slots (headers, footers, actions, item rendering, etc.)—use them to build richer UI without replacing the core components. You can significantly change look/behavior via slots while keeping Edge logic intact.
|
|
59
|
+
- Dashboard and editor don’t need to live on the same page; feel free to separate list/detail routes or co-locate them as needed, but always compose using the Edge components.
|
|
60
|
+
- Nuxt page structure can be rearranged to meet requirements, as long as navigation/layout still leans on the shared Edge tools (sidebar/menu, shadcn components, dashboard/editor, cms pieces).
|
|
61
|
+
|
|
62
|
+
## Routing, state, and menus
|
|
63
|
+
- Organization-aware pages assume `edgeGlobal.edgeState.currentOrganization` is set; if you need menu items, extend `edgeGlobal.edgeState.menuItems` (see `pages/app.vue`).
|
|
64
|
+
- Use `useState('auth')` and other Nuxt composables already present for global auth/route handling. Keep SSR considerations minimal (app runs client-side).
|
|
65
|
+
|
|
66
|
+
## Shadcn usage
|
|
67
|
+
- shadcn components live under `components/ui` and Edge-wrapped variants under `edge/components/shad`. Prefer the Edge variants to keep styling consistent and take advantage of shared props/slots.
|
|
68
|
+
- Styling: stick to Tailwind utility classes alongside the Edge components; when building new components, use Tailwind + `cn` for class composition instead of custom CSS where possible.
|
|
69
|
+
|
|
70
|
+
## Do/Don't
|
|
71
|
+
- Do reuse Edge components (`dashboard`, `editor`, `cms` blocks, auth widgets) before adding new ones.
|
|
72
|
+
- Do keep Firestore paths, queries, and role checks consistent with `edgeGlobal` helpers (`isAdminGlobal`, `getRoleName`, etc.).
|
|
73
|
+
- Don’t introduce TypeScript, Options API, raw Firebase SDK calls, or ad-hoc forms/tables when an Edge component exists.
|
|
74
|
+
- Don’t edit code inside the `edge` folder unless absolutely required; it is a shared repo. If a change is unavoidable, keep it generic (no project-specific hacks) and call out the suggestion instead of making the edit when possible.
|
|
75
|
+
|
|
76
|
+
## Firebase Functions guidance
|
|
77
|
+
- Review `functions/config.js`, `functions/edgeFirebase.js`, and `functions/cms.js` to mirror established patterns, but do not edit those files.
|
|
78
|
+
- When adding new cloud functions, create a new JS file under `functions/` and export handlers using the shared imports from `config.js`. Wire it up by requiring it in `functions/index.js` (same pattern as `stripe.js`), instead of modifying restricted files.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@edgedev/create-edge-app",
|
|
3
|
-
"version": "1.1.
|
|
3
|
+
"version": "1.1.23",
|
|
4
4
|
"description": "Create Edge Starter App",
|
|
5
5
|
"bin": {
|
|
6
6
|
"create-edge-app": "./bin/cli.js"
|
|
@@ -22,6 +22,7 @@
|
|
|
22
22
|
"@capacitor/push-notifications": "5.1.0",
|
|
23
23
|
"@chenfengyuan/vue-number-input": "2",
|
|
24
24
|
"@edgedev/firebase": "latest",
|
|
25
|
+
"@edgedev/template-engine": "^0.1.12",
|
|
25
26
|
"@guolao/vue-monaco-editor": "^1.5.5",
|
|
26
27
|
"@tiptap/extension-image": "^2.11.5",
|
|
27
28
|
"@tiptap/extension-text-style": "^2.11.5",
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
<script setup>
|
|
2
|
+
const route = useRoute()
|
|
3
|
+
const state = reactive({
|
|
4
|
+
mounted: false,
|
|
5
|
+
head: null,
|
|
6
|
+
})
|
|
7
|
+
definePageMeta({
|
|
8
|
+
middleware: 'auth',
|
|
9
|
+
})
|
|
10
|
+
|
|
11
|
+
const blockId = computed(() => {
|
|
12
|
+
if (route.params.block) {
|
|
13
|
+
return route.params.block
|
|
14
|
+
}
|
|
15
|
+
return ''
|
|
16
|
+
})
|
|
17
|
+
|
|
18
|
+
onMounted(() => {
|
|
19
|
+
state.mounted = true
|
|
20
|
+
})
|
|
21
|
+
|
|
22
|
+
useHead(() => (state.head || {}))
|
|
23
|
+
|
|
24
|
+
const setHead = (newHead) => {
|
|
25
|
+
state.head = newHead
|
|
26
|
+
}
|
|
27
|
+
</script>
|
|
28
|
+
|
|
29
|
+
<template>
|
|
30
|
+
<div
|
|
31
|
+
v-if="edgeGlobal.edgeState.organizationDocPath && state.mounted"
|
|
32
|
+
>
|
|
33
|
+
<edge-cms-block-editor
|
|
34
|
+
:block-id="blockId"
|
|
35
|
+
@head="setHead"
|
|
36
|
+
/>
|
|
37
|
+
</div>
|
|
38
|
+
</template>
|
|
@@ -0,0 +1,347 @@
|
|
|
1
|
+
<script setup>
|
|
2
|
+
const edgeFirebase = inject('edgeFirebase')
|
|
3
|
+
const state = reactive({
|
|
4
|
+
filter: '',
|
|
5
|
+
mounted: false,
|
|
6
|
+
picksFilter: [],
|
|
7
|
+
themesFilter: [],
|
|
8
|
+
})
|
|
9
|
+
|
|
10
|
+
const previewState = reactive({
|
|
11
|
+
loaded: [],
|
|
12
|
+
})
|
|
13
|
+
|
|
14
|
+
definePageMeta({
|
|
15
|
+
middleware: 'auth',
|
|
16
|
+
})
|
|
17
|
+
|
|
18
|
+
const rawInitBlockFiles = import.meta.glob('../../../../edge/components/cms/init_blocks/*.html', {
|
|
19
|
+
as: 'raw',
|
|
20
|
+
eager: true,
|
|
21
|
+
})
|
|
22
|
+
|
|
23
|
+
const INITIAL_BLOCKS = Object.entries(rawInitBlockFiles).map(([path, content]) => {
|
|
24
|
+
const fileName = path.split('/').pop() || ''
|
|
25
|
+
const baseName = fileName.replace(/\.html$/i, '')
|
|
26
|
+
const formattedName = baseName
|
|
27
|
+
.split('_')
|
|
28
|
+
.filter(Boolean)
|
|
29
|
+
.map(segment => segment.charAt(0).toUpperCase() + segment.slice(1))
|
|
30
|
+
.join(' ')
|
|
31
|
+
return {
|
|
32
|
+
docId: baseName,
|
|
33
|
+
name: formattedName,
|
|
34
|
+
content,
|
|
35
|
+
}
|
|
36
|
+
})
|
|
37
|
+
|
|
38
|
+
const router = useRouter()
|
|
39
|
+
|
|
40
|
+
const seedInitialBlocks = async () => {
|
|
41
|
+
console.log('Seeding initial blocks...')
|
|
42
|
+
console.log(`Found ${INITIAL_BLOCKS.length} initial blocks to seed.`)
|
|
43
|
+
if (!INITIAL_BLOCKS.length)
|
|
44
|
+
return 0
|
|
45
|
+
|
|
46
|
+
const organizationPath = edgeGlobal.edgeState.organizationDocPath
|
|
47
|
+
if (!organizationPath)
|
|
48
|
+
return 0
|
|
49
|
+
|
|
50
|
+
const collectionPath = `${organizationPath}/blocks`
|
|
51
|
+
let created = 0
|
|
52
|
+
|
|
53
|
+
for (const block of INITIAL_BLOCKS) {
|
|
54
|
+
if (!block.docId)
|
|
55
|
+
continue
|
|
56
|
+
try {
|
|
57
|
+
await edgeFirebase.storeDoc(collectionPath, {
|
|
58
|
+
docId: block.docId,
|
|
59
|
+
name: block.name,
|
|
60
|
+
content: block.content,
|
|
61
|
+
tags: [],
|
|
62
|
+
themes: [],
|
|
63
|
+
synced: false,
|
|
64
|
+
version: 1,
|
|
65
|
+
})
|
|
66
|
+
created++
|
|
67
|
+
console.log(`Seeded block "${block.docId}"`)
|
|
68
|
+
}
|
|
69
|
+
catch (error) {
|
|
70
|
+
console.error(`Failed to seed block "${block.docId}"`, error)
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
return created
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
onBeforeMount(async () => {
|
|
78
|
+
restoreFilters()
|
|
79
|
+
if (!edgeFirebase.data?.[`organizations/${edgeGlobal.edgeState.currentOrganization}/themes`]) {
|
|
80
|
+
await edgeFirebase.startSnapshot(`organizations/${edgeGlobal.edgeState.currentOrganization}/themes`)
|
|
81
|
+
}
|
|
82
|
+
state.mounted = true
|
|
83
|
+
})
|
|
84
|
+
|
|
85
|
+
const getThemeFromId = (themeId) => {
|
|
86
|
+
const theme = edgeFirebase.data[`organizations/${edgeGlobal.edgeState.currentOrganization}/themes`]?.[themeId]
|
|
87
|
+
console.log('getThemeFromId', themeId, theme.name)
|
|
88
|
+
return theme?.name || 'Unknown'
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
const loadingRender = (content) => {
|
|
92
|
+
const safeContent = typeof content === 'string' ? content : ''
|
|
93
|
+
return safeContent.replaceAll('{{loading}}', '').replaceAll('{{loaded}}', 'hidden')
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
const markPreviewLoaded = (isLoading, id) => {
|
|
97
|
+
if (!isLoading && !previewState.loaded.includes(id))
|
|
98
|
+
previewState.loaded.push(id)
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
const hasPreviewLoaded = id => previewState.loaded.includes(id)
|
|
102
|
+
|
|
103
|
+
const FILTER_STORAGE_KEY = 'edge.blocks.filters'
|
|
104
|
+
|
|
105
|
+
const restoreFilters = () => {
|
|
106
|
+
if (typeof localStorage === 'undefined')
|
|
107
|
+
return
|
|
108
|
+
try {
|
|
109
|
+
const raw = localStorage.getItem(FILTER_STORAGE_KEY)
|
|
110
|
+
if (!raw)
|
|
111
|
+
return
|
|
112
|
+
const parsed = JSON.parse(raw)
|
|
113
|
+
state.filter = parsed.filter ?? ''
|
|
114
|
+
state.picksFilter = Array.isArray(parsed.picksFilter) ? parsed.picksFilter : []
|
|
115
|
+
state.themesFilter = Array.isArray(parsed.themesFilter) ? parsed.themesFilter : []
|
|
116
|
+
}
|
|
117
|
+
catch (err) {
|
|
118
|
+
console.warn('Failed to restore block filters', err)
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
const persistFilters = () => {
|
|
123
|
+
if (typeof localStorage === 'undefined')
|
|
124
|
+
return
|
|
125
|
+
const payload = {
|
|
126
|
+
filter: state.filter,
|
|
127
|
+
picksFilter: state.picksFilter,
|
|
128
|
+
themesFilter: state.themesFilter,
|
|
129
|
+
}
|
|
130
|
+
localStorage.setItem(FILTER_STORAGE_KEY, JSON.stringify(payload))
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
watch(
|
|
134
|
+
() => [state.filter, state.picksFilter, state.themesFilter],
|
|
135
|
+
persistFilters,
|
|
136
|
+
{ deep: true },
|
|
137
|
+
)
|
|
138
|
+
|
|
139
|
+
const tagOptions = computed(() => {
|
|
140
|
+
const tagsSet = new Set()
|
|
141
|
+
const blocks = edgeFirebase.data?.[`${edgeGlobal.edgeState.organizationDocPath}/blocks`] || {}
|
|
142
|
+
Object.values(blocks).forEach((block) => {
|
|
143
|
+
if (Array.isArray(block.tags))
|
|
144
|
+
block.tags.forEach(tag => tagsSet.add(tag))
|
|
145
|
+
})
|
|
146
|
+
return Array.from(tagsSet).sort((a, b) => a.localeCompare(b)).map(tag => ({ name: tag, title: tag }))
|
|
147
|
+
})
|
|
148
|
+
|
|
149
|
+
const themeOptions = computed(() => {
|
|
150
|
+
const themes = edgeFirebase.data?.[`organizations/${edgeGlobal.edgeState.currentOrganization}/themes`] || {}
|
|
151
|
+
return Object.entries(themes)
|
|
152
|
+
.map(([id, theme]) => ({ name: id, title: theme.name || id }))
|
|
153
|
+
.sort((a, b) => a.title.localeCompare(b.title))
|
|
154
|
+
})
|
|
155
|
+
|
|
156
|
+
const listFilters = computed(() => {
|
|
157
|
+
const filters = []
|
|
158
|
+
if (state.picksFilter.length)
|
|
159
|
+
filters.push({ filterFields: ['tags'], value: state.picksFilter })
|
|
160
|
+
if (state.themesFilter.length)
|
|
161
|
+
filters.push({ filterFields: ['themes'], value: state.themesFilter })
|
|
162
|
+
return filters
|
|
163
|
+
})
|
|
164
|
+
</script>
|
|
165
|
+
|
|
166
|
+
<template>
|
|
167
|
+
<div
|
|
168
|
+
v-if="edgeGlobal.edgeState.organizationDocPath && state.mounted"
|
|
169
|
+
>
|
|
170
|
+
<edge-dashboard
|
|
171
|
+
:filter="state.filter"
|
|
172
|
+
:filters="listFilters"
|
|
173
|
+
collection="blocks"
|
|
174
|
+
class="pt-0 flex-1"
|
|
175
|
+
>
|
|
176
|
+
<template #header-start="slotProps">
|
|
177
|
+
<component :is="slotProps.icon" class="mr-2" />
|
|
178
|
+
Blocks
|
|
179
|
+
<edge-shad-button
|
|
180
|
+
v-if="slotProps.recordCount === 0"
|
|
181
|
+
variant="outline"
|
|
182
|
+
class="ml-4 h-8 text-xs"
|
|
183
|
+
@click="seedInitialBlocks"
|
|
184
|
+
>
|
|
185
|
+
Seed Blocks
|
|
186
|
+
</edge-shad-button>
|
|
187
|
+
</template>
|
|
188
|
+
<template #header-center>
|
|
189
|
+
<edge-shad-form class="w-full">
|
|
190
|
+
<div class="w-full px-4 md:px-6 pb-2 flex flex-col gap-3 md:flex-row md:items-center">
|
|
191
|
+
<div class="grow">
|
|
192
|
+
<edge-shad-input
|
|
193
|
+
v-model="state.filter"
|
|
194
|
+
name="filter"
|
|
195
|
+
placeholder="Search blocks..."
|
|
196
|
+
class="w-full"
|
|
197
|
+
/>
|
|
198
|
+
</div>
|
|
199
|
+
<div>
|
|
200
|
+
<edge-shad-select-tags
|
|
201
|
+
v-model="state.picksFilter"
|
|
202
|
+
:items="tagOptions"
|
|
203
|
+
name="tags"
|
|
204
|
+
placeholder="Filter tags"
|
|
205
|
+
/>
|
|
206
|
+
</div>
|
|
207
|
+
<div>
|
|
208
|
+
<edge-shad-select-tags
|
|
209
|
+
v-model="state.themesFilter"
|
|
210
|
+
:items="themeOptions"
|
|
211
|
+
name="themes"
|
|
212
|
+
placeholder="Filter themes"
|
|
213
|
+
/>
|
|
214
|
+
</div>
|
|
215
|
+
</div>
|
|
216
|
+
</edge-shad-form>
|
|
217
|
+
</template>
|
|
218
|
+
<template #list="slotProps">
|
|
219
|
+
<div
|
|
220
|
+
class="grid gap-4 pt-4 w-full"
|
|
221
|
+
style="grid-template-columns: repeat(auto-fill, minmax(260px, 1fr));"
|
|
222
|
+
>
|
|
223
|
+
<div
|
|
224
|
+
v-for="item in slotProps.filtered"
|
|
225
|
+
:key="item.docId"
|
|
226
|
+
role="button"
|
|
227
|
+
tabindex="0"
|
|
228
|
+
class="w-full h-full"
|
|
229
|
+
@click="router.push(`/app/dashboard/blocks/${item.docId}`)"
|
|
230
|
+
@keyup.enter="router.push(`/app/dashboard/blocks/${item.docId}`)"
|
|
231
|
+
>
|
|
232
|
+
<Card class="h-full cursor-pointer border border-white/5 bg-gradient-to-br from-slate-950/85 via-slate-950/65 to-slate-900/60 hover:border-primary/50 hover:shadow-[0_22px_55px_-24px_rgba(0,0,0,0.7)] transition">
|
|
233
|
+
<CardContent class="flex flex-col gap-1 p-4 sm:p-5">
|
|
234
|
+
<div class="flex items-start justify-between gap-3">
|
|
235
|
+
<p class="text-lg font-semibold leading-snug line-clamp-2 text-white">
|
|
236
|
+
{{ item.name }}
|
|
237
|
+
</p>
|
|
238
|
+
<edge-shad-button
|
|
239
|
+
size="icon"
|
|
240
|
+
variant="ghost"
|
|
241
|
+
class="h-8 w-8 text-white/80 hover:text-white hover:bg-white/10"
|
|
242
|
+
@click.stop="slotProps.deleteItem(item.docId)"
|
|
243
|
+
>
|
|
244
|
+
<Trash class="h-4 w-4" />
|
|
245
|
+
</edge-shad-button>
|
|
246
|
+
</div>
|
|
247
|
+
<div v-if="item.content" class="block-preview">
|
|
248
|
+
<div class="scale-wrapper">
|
|
249
|
+
<div class="scale-inner scale p-4">
|
|
250
|
+
<edge-cms-block-render
|
|
251
|
+
:content="loadingRender(item.content)"
|
|
252
|
+
:values="item.values"
|
|
253
|
+
:meta="item.meta"
|
|
254
|
+
/>
|
|
255
|
+
</div>
|
|
256
|
+
</div>
|
|
257
|
+
<div class="preview-overlay" />
|
|
258
|
+
</div>
|
|
259
|
+
<div v-else class="block-preview-empty">
|
|
260
|
+
Preview unavailable for this block.
|
|
261
|
+
</div>
|
|
262
|
+
<div class="flex flex-wrap items-center gap-1 text-[11px] text-slate-300 uppercase tracking-wide overflow-hidden">
|
|
263
|
+
<edge-chip
|
|
264
|
+
v-for="tag in item.tags?.slice(0, 3) ?? []"
|
|
265
|
+
:key="tag"
|
|
266
|
+
class="bg-primary/40 text-white px-2 py-0.5 text-[10px]"
|
|
267
|
+
>
|
|
268
|
+
{{ tag }}
|
|
269
|
+
</edge-chip>
|
|
270
|
+
<span v-if="item.tags?.length > 3" class="text-white/60">+{{ item.tags.length - 3 }}</span>
|
|
271
|
+
<edge-chip
|
|
272
|
+
v-for="theme in item.themes?.slice(0, 2) ?? []"
|
|
273
|
+
:key="theme"
|
|
274
|
+
class="bg-slate-800 text-white px-2 py-0.5 text-[10px]"
|
|
275
|
+
>
|
|
276
|
+
{{ getThemeFromId(theme) }}
|
|
277
|
+
</edge-chip>
|
|
278
|
+
<span v-if="item.themes?.length > 2" class="text-white/60">+{{ item.themes.length - 2 }}</span>
|
|
279
|
+
<span
|
|
280
|
+
v-if="!(item.tags?.length) && !(item.themes?.length)"
|
|
281
|
+
class="text-slate-500 lowercase"
|
|
282
|
+
>
|
|
283
|
+
none
|
|
284
|
+
</span>
|
|
285
|
+
</div>
|
|
286
|
+
</CardContent>
|
|
287
|
+
</Card>
|
|
288
|
+
</div>
|
|
289
|
+
</div>
|
|
290
|
+
</template>
|
|
291
|
+
</edge-dashboard>
|
|
292
|
+
</div>
|
|
293
|
+
</template>
|
|
294
|
+
|
|
295
|
+
<style scoped>
|
|
296
|
+
.block-preview {
|
|
297
|
+
position: relative;
|
|
298
|
+
height: 220px;
|
|
299
|
+
border-radius: 14px;
|
|
300
|
+
border: 1px solid rgba(255, 255, 255, 0.06);
|
|
301
|
+
background:
|
|
302
|
+
radial-gradient(140% 120% at 15% 15%, rgba(96, 165, 250, 0.08), transparent),
|
|
303
|
+
radial-gradient(120% 120% at 85% 0%, rgba(168, 85, 247, 0.07), transparent),
|
|
304
|
+
linear-gradient(145deg, rgba(10, 14, 26, 0.95), rgba(17, 24, 39, 0.7));
|
|
305
|
+
overflow: hidden;
|
|
306
|
+
box-shadow:
|
|
307
|
+
inset 0 1px 0 rgba(255, 255, 255, 0.02),
|
|
308
|
+
0 18px 38px rgba(0, 0, 0, 0.35);
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
.block-preview-empty {
|
|
312
|
+
height: 220px;
|
|
313
|
+
border-radius: 14px;
|
|
314
|
+
border: 1px dashed rgba(255, 255, 255, 0.08);
|
|
315
|
+
background: linear-gradient(135deg, rgba(10, 14, 26, 0.65), rgba(17, 24, 39, 0.5));
|
|
316
|
+
color: rgba(255, 255, 255, 0.6);
|
|
317
|
+
display: grid;
|
|
318
|
+
place-items: center;
|
|
319
|
+
font-size: 13px;
|
|
320
|
+
letter-spacing: 0.01em;
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
.preview-overlay {
|
|
324
|
+
pointer-events: none;
|
|
325
|
+
position: absolute;
|
|
326
|
+
inset: 0;
|
|
327
|
+
background: linear-gradient(180deg, rgba(15, 23, 42, 0) 20%, rgba(15, 23, 42, 0.35) 100%);
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
.scale-wrapper {
|
|
331
|
+
width: 100%;
|
|
332
|
+
height: 100%;
|
|
333
|
+
overflow: hidden;
|
|
334
|
+
position: relative;
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
.scale-inner {
|
|
338
|
+
transform-origin: top left;
|
|
339
|
+
display: inline-block;
|
|
340
|
+
min-width: 100%;
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
.scale {
|
|
344
|
+
transform: scale(0.25);
|
|
345
|
+
width: 400%;
|
|
346
|
+
}
|
|
347
|
+
</style>
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
<script setup>
|
|
2
|
+
const state = reactive({
|
|
3
|
+
mounted: false,
|
|
4
|
+
})
|
|
5
|
+
definePageMeta({
|
|
6
|
+
middleware: 'auth',
|
|
7
|
+
})
|
|
8
|
+
|
|
9
|
+
onMounted(() => {
|
|
10
|
+
state.mounted = true
|
|
11
|
+
})
|
|
12
|
+
</script>
|
|
13
|
+
|
|
14
|
+
<template>
|
|
15
|
+
<div
|
|
16
|
+
v-if="edgeGlobal.edgeState.organizationDocPath && state.mounted"
|
|
17
|
+
>
|
|
18
|
+
<edge-cms-media-manager />
|
|
19
|
+
</div>
|
|
20
|
+
</template>
|
|
21
|
+
|
|
22
|
+
<style>
|
|
23
|
+
|
|
24
|
+
</style>
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
<script setup>
|
|
2
|
+
const route = useRoute()
|
|
3
|
+
|
|
4
|
+
// const edgeGlobal = inject('edgeGlobal')
|
|
5
|
+
|
|
6
|
+
const state = reactive({
|
|
7
|
+
mounted: false,
|
|
8
|
+
head: null,
|
|
9
|
+
})
|
|
10
|
+
|
|
11
|
+
const page = computed(() => {
|
|
12
|
+
if (route.params?.page) {
|
|
13
|
+
return route.params.page
|
|
14
|
+
}
|
|
15
|
+
return ''
|
|
16
|
+
})
|
|
17
|
+
|
|
18
|
+
const site = computed(() => {
|
|
19
|
+
if (route.params?.site) {
|
|
20
|
+
return route.params.site
|
|
21
|
+
}
|
|
22
|
+
return ''
|
|
23
|
+
})
|
|
24
|
+
|
|
25
|
+
definePageMeta({
|
|
26
|
+
middleware: 'auth',
|
|
27
|
+
})
|
|
28
|
+
|
|
29
|
+
onMounted(() => {
|
|
30
|
+
state.mounted = true
|
|
31
|
+
})
|
|
32
|
+
|
|
33
|
+
useHead(() => (state.head || {}))
|
|
34
|
+
|
|
35
|
+
const setHead = (newHead) => {
|
|
36
|
+
state.head = newHead
|
|
37
|
+
}
|
|
38
|
+
</script>
|
|
39
|
+
|
|
40
|
+
<template>
|
|
41
|
+
<div
|
|
42
|
+
v-if="edgeGlobal.edgeState.organizationDocPath && state.mounted"
|
|
43
|
+
>
|
|
44
|
+
<edge-cms-page
|
|
45
|
+
:site="site"
|
|
46
|
+
:page="page"
|
|
47
|
+
@head="setHead"
|
|
48
|
+
/>
|
|
49
|
+
</div>
|
|
50
|
+
</template>
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
<script setup>
|
|
2
|
+
const route = useRoute()
|
|
3
|
+
|
|
4
|
+
// const edgeGlobal = inject('edgeGlobal')
|
|
5
|
+
|
|
6
|
+
const state = reactive({
|
|
7
|
+
mounted: false,
|
|
8
|
+
})
|
|
9
|
+
|
|
10
|
+
const page = computed(() => {
|
|
11
|
+
if (route.params?.page) {
|
|
12
|
+
return route.params.page
|
|
13
|
+
}
|
|
14
|
+
return ''
|
|
15
|
+
})
|
|
16
|
+
|
|
17
|
+
const site = computed(() => {
|
|
18
|
+
if (route.params?.site) {
|
|
19
|
+
return route.params.site
|
|
20
|
+
}
|
|
21
|
+
return ''
|
|
22
|
+
})
|
|
23
|
+
|
|
24
|
+
definePageMeta({
|
|
25
|
+
middleware: 'auth',
|
|
26
|
+
})
|
|
27
|
+
|
|
28
|
+
onMounted(() => {
|
|
29
|
+
state.mounted = true
|
|
30
|
+
})
|
|
31
|
+
</script>
|
|
32
|
+
|
|
33
|
+
<template>
|
|
34
|
+
<div
|
|
35
|
+
v-if="edgeGlobal.edgeState.organizationDocPath && state.mounted"
|
|
36
|
+
>
|
|
37
|
+
<edge-cms-site
|
|
38
|
+
:site="site"
|
|
39
|
+
:page="page"
|
|
40
|
+
/>
|
|
41
|
+
</div>
|
|
42
|
+
</template>
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
<script setup>
|
|
2
|
+
import { toTypedSchema } from '@vee-validate/zod'
|
|
3
|
+
import * as z from 'zod'
|
|
4
|
+
import { Loader2 } from 'lucide-vue-next'
|
|
5
|
+
const route = useRoute()
|
|
6
|
+
// const edgeGlobal = inject('edgeGlobal')
|
|
7
|
+
|
|
8
|
+
const edgeFirebase = inject('edgeFirebase')
|
|
9
|
+
const isAiBusy = status => status === 'queued' || status === 'running'
|
|
10
|
+
|
|
11
|
+
const state = reactive({
|
|
12
|
+
filter: '',
|
|
13
|
+
newDocs: {
|
|
14
|
+
sites: {
|
|
15
|
+
name: { bindings: { 'field-type': 'text', 'label': 'Name', 'helper': 'Name' }, cols: '12', value: '' },
|
|
16
|
+
},
|
|
17
|
+
},
|
|
18
|
+
})
|
|
19
|
+
|
|
20
|
+
const schemas = {
|
|
21
|
+
sites: toTypedSchema(z.object({
|
|
22
|
+
name: z.string({
|
|
23
|
+
required_error: 'Name is required',
|
|
24
|
+
}).min(1, { message: 'Name is required' }),
|
|
25
|
+
})),
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const collection = computed(() => {
|
|
29
|
+
if (route.params.collection) {
|
|
30
|
+
return route.params.collection
|
|
31
|
+
}
|
|
32
|
+
return ''
|
|
33
|
+
})
|
|
34
|
+
const docId = computed(() => {
|
|
35
|
+
if (route.params.docId) {
|
|
36
|
+
return route.params.docId
|
|
37
|
+
}
|
|
38
|
+
return ''
|
|
39
|
+
})
|
|
40
|
+
definePageMeta({
|
|
41
|
+
middleware: 'auth',
|
|
42
|
+
})
|
|
43
|
+
|
|
44
|
+
onBeforeMount(() => {
|
|
45
|
+
// edgeGlobal.showLeftPanel(true)
|
|
46
|
+
})
|
|
47
|
+
const isAdmin = computed(() => {
|
|
48
|
+
return edgeGlobal.isAdminGlobal(edgeFirebase).value
|
|
49
|
+
})
|
|
50
|
+
const queryField = computed(() => {
|
|
51
|
+
if (!isAdmin.value) {
|
|
52
|
+
return 'users'
|
|
53
|
+
}
|
|
54
|
+
return ''
|
|
55
|
+
})
|
|
56
|
+
|
|
57
|
+
const queryValue = computed(() => {
|
|
58
|
+
if (!isAdmin.value) {
|
|
59
|
+
return [edgeFirebase?.user?.uid]
|
|
60
|
+
}
|
|
61
|
+
return ''
|
|
62
|
+
})
|
|
63
|
+
|
|
64
|
+
const queryOperator = computed(() => {
|
|
65
|
+
if (!isAdmin.value) {
|
|
66
|
+
return 'array-contains-any'
|
|
67
|
+
}
|
|
68
|
+
return ''
|
|
69
|
+
})
|
|
70
|
+
</script>
|
|
71
|
+
|
|
72
|
+
<template>
|
|
73
|
+
<div
|
|
74
|
+
v-if="edgeGlobal.edgeState.organizationDocPath"
|
|
75
|
+
>
|
|
76
|
+
<edge-dashboard :load-first-if-one="!isAdmin" :filter="state.filter" :query-field="queryField" :query-value="queryValue" :query-operator="queryOperator" collection="sites" class="flex-1 pt-0">
|
|
77
|
+
<template #list="slotProps">
|
|
78
|
+
<template v-for="item in slotProps.filtered" :key="item.docId">
|
|
79
|
+
<edge-shad-button
|
|
80
|
+
variant="text"
|
|
81
|
+
class="cursor-pointer w-full flex justify-between items-center py-2 gap-3"
|
|
82
|
+
:to="isAiBusy(item.aiBootstrapStatus) ? undefined : `/app/dashboard/sites/${item.docId}`"
|
|
83
|
+
:disabled="isAiBusy(item.aiBootstrapStatus)"
|
|
84
|
+
>
|
|
85
|
+
<div>
|
|
86
|
+
<Avatar class="cursor-pointer p-0 h-8 w-8 mr-2">
|
|
87
|
+
<FilePenLine class="h-5 w-5" />
|
|
88
|
+
</Avatar>
|
|
89
|
+
</div>
|
|
90
|
+
<div class="grow text-left">
|
|
91
|
+
<div class="text-lg">
|
|
92
|
+
{{ item.name }}
|
|
93
|
+
</div>
|
|
94
|
+
<div v-if="isAiBusy(item.aiBootstrapStatus)" class="flex items-center gap-2 text-xs text-muted-foreground">
|
|
95
|
+
<Loader2 class="h-3 w-3 animate-spin" />
|
|
96
|
+
<span>AI is preparing this site</span>
|
|
97
|
+
</div>
|
|
98
|
+
</div>
|
|
99
|
+
<div>
|
|
100
|
+
<edge-shad-button
|
|
101
|
+
size="icon"
|
|
102
|
+
class="bg-slate-600 h-7 w-7"
|
|
103
|
+
@click.stop="slotProps.deleteItem(item.docId)"
|
|
104
|
+
>
|
|
105
|
+
<Trash class="h-5 w-5" />
|
|
106
|
+
</edge-shad-button>
|
|
107
|
+
</div>
|
|
108
|
+
</edge-shad-button>
|
|
109
|
+
<Separator class="dark:bg-slate-600" />
|
|
110
|
+
</template>
|
|
111
|
+
</template>
|
|
112
|
+
</edge-dashboard>
|
|
113
|
+
</div>
|
|
114
|
+
</template>
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
<script setup>
|
|
2
|
+
const route = useRoute()
|
|
3
|
+
|
|
4
|
+
const state = reactive({
|
|
5
|
+
mounted: false,
|
|
6
|
+
head: null,
|
|
7
|
+
})
|
|
8
|
+
|
|
9
|
+
const page = computed(() => {
|
|
10
|
+
if (route.params?.page) {
|
|
11
|
+
return route.params.page
|
|
12
|
+
}
|
|
13
|
+
return ''
|
|
14
|
+
})
|
|
15
|
+
|
|
16
|
+
const site = computed(() => 'templates')
|
|
17
|
+
|
|
18
|
+
definePageMeta({
|
|
19
|
+
middleware: 'auth',
|
|
20
|
+
})
|
|
21
|
+
|
|
22
|
+
onMounted(() => {
|
|
23
|
+
state.mounted = true
|
|
24
|
+
})
|
|
25
|
+
|
|
26
|
+
useHead(() => (state.head || {}))
|
|
27
|
+
|
|
28
|
+
const setHead = (newHead) => {
|
|
29
|
+
state.head = newHead
|
|
30
|
+
}
|
|
31
|
+
</script>
|
|
32
|
+
|
|
33
|
+
<template>
|
|
34
|
+
<div
|
|
35
|
+
v-if="edgeGlobal.edgeState.organizationDocPath && state.mounted"
|
|
36
|
+
>
|
|
37
|
+
<edge-cms-page
|
|
38
|
+
:site="site"
|
|
39
|
+
:page="page"
|
|
40
|
+
:is-template-site="true"
|
|
41
|
+
@head="setHead"
|
|
42
|
+
/>
|
|
43
|
+
</div>
|
|
44
|
+
</template>
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
<script setup>
|
|
2
|
+
const route = useRoute()
|
|
3
|
+
|
|
4
|
+
// const edgeGlobal = inject('edgeGlobal')
|
|
5
|
+
|
|
6
|
+
const state = reactive({
|
|
7
|
+
mounted: false,
|
|
8
|
+
})
|
|
9
|
+
|
|
10
|
+
const page = computed(() => {
|
|
11
|
+
if (route.params?.page) {
|
|
12
|
+
return route.params.page
|
|
13
|
+
}
|
|
14
|
+
return ''
|
|
15
|
+
})
|
|
16
|
+
|
|
17
|
+
definePageMeta({
|
|
18
|
+
middleware: 'auth',
|
|
19
|
+
})
|
|
20
|
+
|
|
21
|
+
onMounted(() => {
|
|
22
|
+
state.mounted = true
|
|
23
|
+
})
|
|
24
|
+
</script>
|
|
25
|
+
|
|
26
|
+
<template>
|
|
27
|
+
<div
|
|
28
|
+
v-if="edgeGlobal.edgeState.organizationDocPath && state.mounted"
|
|
29
|
+
>
|
|
30
|
+
<edge-cms-site
|
|
31
|
+
site="templates"
|
|
32
|
+
:page="page"
|
|
33
|
+
/>
|
|
34
|
+
</div>
|
|
35
|
+
</template>
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
<script setup>
|
|
2
|
+
const route = useRoute()
|
|
3
|
+
const state = reactive({
|
|
4
|
+
mounted: false,
|
|
5
|
+
})
|
|
6
|
+
definePageMeta({
|
|
7
|
+
middleware: 'auth',
|
|
8
|
+
head: null,
|
|
9
|
+
})
|
|
10
|
+
|
|
11
|
+
const themeId = computed(() => {
|
|
12
|
+
if (route.params.theme) {
|
|
13
|
+
return route.params.theme
|
|
14
|
+
}
|
|
15
|
+
return ''
|
|
16
|
+
})
|
|
17
|
+
|
|
18
|
+
useHead(() => (state.head || {}))
|
|
19
|
+
|
|
20
|
+
onMounted(() => {
|
|
21
|
+
state.mounted = true
|
|
22
|
+
})
|
|
23
|
+
const setHead = (newHead) => {
|
|
24
|
+
state.head = newHead
|
|
25
|
+
}
|
|
26
|
+
</script>
|
|
27
|
+
|
|
28
|
+
<template>
|
|
29
|
+
<div
|
|
30
|
+
v-if="edgeGlobal.edgeState.organizationDocPath && state.mounted"
|
|
31
|
+
>
|
|
32
|
+
<edge-cms-theme-editor
|
|
33
|
+
:theme-id="themeId"
|
|
34
|
+
@head="setHead"
|
|
35
|
+
/>
|
|
36
|
+
</div>
|
|
37
|
+
</template>
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
<script setup>
|
|
2
|
+
const state = reactive({
|
|
3
|
+
filter: '',
|
|
4
|
+
})
|
|
5
|
+
|
|
6
|
+
definePageMeta({
|
|
7
|
+
middleware: 'auth',
|
|
8
|
+
})
|
|
9
|
+
</script>
|
|
10
|
+
|
|
11
|
+
<template>
|
|
12
|
+
<div
|
|
13
|
+
v-if="edgeGlobal.edgeState.organizationDocPath"
|
|
14
|
+
>
|
|
15
|
+
<edge-dashboard :filter="state.filter" collection="themes" class="pt-0 flex-1">
|
|
16
|
+
<template #list="slotProps">
|
|
17
|
+
<template v-for="item in slotProps.filtered" :key="item.docId">
|
|
18
|
+
<edge-shad-button variant="text" class="cursor-pointer w-full flex justify-between items-center py-2 gap-3" :to="`/app/dashboard/themes/${item.docId}`">
|
|
19
|
+
<div>
|
|
20
|
+
<Avatar class="cursor-pointer p-0 h-8 w-8 mr-2">
|
|
21
|
+
<FilePenLine class="h-5 w-5" />
|
|
22
|
+
</Avatar>
|
|
23
|
+
</div>
|
|
24
|
+
<div class="grow text-left">
|
|
25
|
+
<div class="text-lg">
|
|
26
|
+
{{ item.name }}
|
|
27
|
+
</div>
|
|
28
|
+
</div>
|
|
29
|
+
<div>
|
|
30
|
+
<edge-shad-button
|
|
31
|
+
size="icon"
|
|
32
|
+
class="bg-slate-600 h-7 w-7"
|
|
33
|
+
@click.stop="slotProps.deleteItem(item.docId)"
|
|
34
|
+
>
|
|
35
|
+
<Trash class="h-5 w-5" />
|
|
36
|
+
</edge-shad-button>
|
|
37
|
+
</div>
|
|
38
|
+
</edge-shad-button>
|
|
39
|
+
<Separator class="dark:bg-slate-600" />
|
|
40
|
+
</template>
|
|
41
|
+
</template>
|
|
42
|
+
</edge-dashboard>
|
|
43
|
+
</div>
|
|
44
|
+
</template>
|
package/pages/app.vue
CHANGED
|
@@ -37,6 +37,44 @@ const menuBuilder = () => {
|
|
|
37
37
|
icon: 'Package',
|
|
38
38
|
devOnly: true,
|
|
39
39
|
},
|
|
40
|
+
{
|
|
41
|
+
title: 'Sites',
|
|
42
|
+
to: '/app/dashboard/sites',
|
|
43
|
+
icon: 'LayoutPanelTop',
|
|
44
|
+
devOnly: true,
|
|
45
|
+
submenu: [
|
|
46
|
+
{
|
|
47
|
+
title: 'Sites',
|
|
48
|
+
to: '/app/dashboard/sites',
|
|
49
|
+
icon: 'LayoutPanelTop',
|
|
50
|
+
devOnly: true,
|
|
51
|
+
},
|
|
52
|
+
{
|
|
53
|
+
title: 'Media',
|
|
54
|
+
to: '/app/dashboard/media',
|
|
55
|
+
icon: 'Image',
|
|
56
|
+
devOnly: true,
|
|
57
|
+
},
|
|
58
|
+
{
|
|
59
|
+
title: 'Blocks',
|
|
60
|
+
to: '/app/dashboard/blocks',
|
|
61
|
+
icon: 'Blocks',
|
|
62
|
+
devOnly: true,
|
|
63
|
+
},
|
|
64
|
+
{
|
|
65
|
+
title: 'Templates',
|
|
66
|
+
to: '/app/dashboard/templates',
|
|
67
|
+
icon: 'LayoutList',
|
|
68
|
+
devOnly: true,
|
|
69
|
+
},
|
|
70
|
+
{
|
|
71
|
+
title: 'Themes',
|
|
72
|
+
to: '/app/dashboard/themes',
|
|
73
|
+
icon: 'Paintbrush',
|
|
74
|
+
devOnly: true,
|
|
75
|
+
},
|
|
76
|
+
],
|
|
77
|
+
},
|
|
40
78
|
{
|
|
41
79
|
title: 'Settings',
|
|
42
80
|
to: '/app/account/my-profile',
|
package/plugins/icons.ts
CHANGED
|
@@ -1,11 +1,19 @@
|
|
|
1
1
|
import {
|
|
2
2
|
AlertCircle,
|
|
3
|
+
AlignHorizontalJustifyStart,
|
|
3
4
|
ArrowLeft,
|
|
4
5
|
ArrowRight,
|
|
6
|
+
Award,
|
|
7
|
+
BadgeCheck,
|
|
8
|
+
Blocks,
|
|
9
|
+
Bold,
|
|
5
10
|
BookImage,
|
|
11
|
+
BookOpen,
|
|
12
|
+
Bot,
|
|
6
13
|
Box,
|
|
7
14
|
Braces,
|
|
8
15
|
Brackets,
|
|
16
|
+
Building,
|
|
9
17
|
CalendarIcon,
|
|
10
18
|
Check,
|
|
11
19
|
ChevronDown,
|
|
@@ -16,34 +24,72 @@ import {
|
|
|
16
24
|
ChevronUp,
|
|
17
25
|
ChevronsUpDown,
|
|
18
26
|
CircleUser,
|
|
27
|
+
CircleX,
|
|
28
|
+
CloudUpload,
|
|
29
|
+
Code,
|
|
19
30
|
Copy,
|
|
31
|
+
Download,
|
|
20
32
|
Eye,
|
|
21
33
|
EyeOff,
|
|
34
|
+
File,
|
|
35
|
+
FileCode2,
|
|
36
|
+
FileImage,
|
|
22
37
|
FilePenLine,
|
|
38
|
+
FilePlus2,
|
|
39
|
+
FileSpreadsheet,
|
|
40
|
+
FileText,
|
|
41
|
+
FolderOpen,
|
|
42
|
+
FolderTree,
|
|
23
43
|
Fullscreen,
|
|
44
|
+
Globe,
|
|
24
45
|
Grip,
|
|
25
46
|
Group,
|
|
47
|
+
Heading1,
|
|
48
|
+
Heading2,
|
|
49
|
+
Heading3,
|
|
50
|
+
Heading4,
|
|
26
51
|
Hourglass,
|
|
27
52
|
Image,
|
|
28
|
-
|
|
53
|
+
Inbox,
|
|
54
|
+
Info,
|
|
55
|
+
Italic,
|
|
56
|
+
Layers3,
|
|
57
|
+
LayoutDashboard,
|
|
58
|
+
LayoutList,
|
|
59
|
+
LayoutPanelTop,
|
|
60
|
+
Link,
|
|
29
61
|
List,
|
|
62
|
+
ListOrdered,
|
|
30
63
|
ListPlus,
|
|
31
64
|
Loader2,
|
|
32
65
|
LogOut,
|
|
66
|
+
Menu,
|
|
33
67
|
MenuSquare,
|
|
34
68
|
MoreHorizontal,
|
|
35
69
|
Newspaper,
|
|
36
70
|
Package,
|
|
71
|
+
Paintbrush,
|
|
37
72
|
PauseCircle,
|
|
38
73
|
Pencil,
|
|
39
74
|
PlusIcon,
|
|
75
|
+
Printer,
|
|
76
|
+
Save,
|
|
40
77
|
Settings,
|
|
41
78
|
Settings2,
|
|
79
|
+
SquareCode,
|
|
80
|
+
StickyNote,
|
|
81
|
+
Store,
|
|
82
|
+
Strikethrough,
|
|
83
|
+
TextQuote,
|
|
42
84
|
Trash,
|
|
43
85
|
TrashIcon,
|
|
86
|
+
TriangleAlert,
|
|
87
|
+
Underline,
|
|
44
88
|
Upload,
|
|
45
89
|
User,
|
|
90
|
+
UserCheck,
|
|
46
91
|
Users,
|
|
92
|
+
View,
|
|
47
93
|
X,
|
|
48
94
|
ZoomIn,
|
|
49
95
|
} from 'lucide-vue-next'
|
|
@@ -97,4 +143,49 @@ export default defineNuxtPlugin((nuxtApp) => {
|
|
|
97
143
|
nuxtApp.vueApp.component('ChevronRightCircle', ChevronRightCircle)
|
|
98
144
|
nuxtApp.vueApp.component('Fullscreen', Fullscreen)
|
|
99
145
|
nuxtApp.vueApp.component('MenuSquare', MenuSquare)
|
|
146
|
+
nuxtApp.vueApp.component('Blocks', Blocks)
|
|
147
|
+
nuxtApp.vueApp.component('LayoutPanelTop', LayoutPanelTop)
|
|
148
|
+
nuxtApp.vueApp.component('Building', Building)
|
|
149
|
+
nuxtApp.vueApp.component('Store', Store)
|
|
150
|
+
nuxtApp.vueApp.component('UserCheck', UserCheck)
|
|
151
|
+
nuxtApp.vueApp.component('Award', Award)
|
|
152
|
+
nuxtApp.vueApp.component('BadgeCheck', BadgeCheck)
|
|
153
|
+
nuxtApp.vueApp.component('BookOpen', BookOpen)
|
|
154
|
+
nuxtApp.vueApp.component('AlignHorizontalJustifyStart', AlignHorizontalJustifyStart)
|
|
155
|
+
nuxtApp.vueApp.component('Bold', Bold)
|
|
156
|
+
nuxtApp.vueApp.component('Bot', Bot)
|
|
157
|
+
nuxtApp.vueApp.component('CircleX', CircleX)
|
|
158
|
+
nuxtApp.vueApp.component('CloudUpload', CloudUpload)
|
|
159
|
+
nuxtApp.vueApp.component('Code', Code)
|
|
160
|
+
nuxtApp.vueApp.component('Download', Download)
|
|
161
|
+
nuxtApp.vueApp.component('File', File)
|
|
162
|
+
nuxtApp.vueApp.component('FileCode2', FileCode2)
|
|
163
|
+
nuxtApp.vueApp.component('FileImage', FileImage)
|
|
164
|
+
nuxtApp.vueApp.component('FilePlus2', FilePlus2)
|
|
165
|
+
nuxtApp.vueApp.component('FileSpreadsheet', FileSpreadsheet)
|
|
166
|
+
nuxtApp.vueApp.component('FileText', FileText)
|
|
167
|
+
nuxtApp.vueApp.component('FolderOpen', FolderOpen)
|
|
168
|
+
nuxtApp.vueApp.component('FolderTree', FolderTree)
|
|
169
|
+
nuxtApp.vueApp.component('Globe', Globe)
|
|
170
|
+
nuxtApp.vueApp.component('Heading1', Heading1)
|
|
171
|
+
nuxtApp.vueApp.component('Heading2', Heading2)
|
|
172
|
+
nuxtApp.vueApp.component('Heading3', Heading3)
|
|
173
|
+
nuxtApp.vueApp.component('Heading4', Heading4)
|
|
174
|
+
nuxtApp.vueApp.component('Inbox', Inbox)
|
|
175
|
+
nuxtApp.vueApp.component('Italic', Italic)
|
|
176
|
+
nuxtApp.vueApp.component('Layers3', Layers3)
|
|
177
|
+
nuxtApp.vueApp.component('LayoutList', LayoutList)
|
|
178
|
+
nuxtApp.vueApp.component('Link', Link)
|
|
179
|
+
nuxtApp.vueApp.component('ListOrdered', ListOrdered)
|
|
180
|
+
nuxtApp.vueApp.component('Menu', Menu)
|
|
181
|
+
nuxtApp.vueApp.component('Printer', Printer)
|
|
182
|
+
nuxtApp.vueApp.component('SquareCode', SquareCode)
|
|
183
|
+
nuxtApp.vueApp.component('StickyNote', StickyNote)
|
|
184
|
+
nuxtApp.vueApp.component('Strikethrough', Strikethrough)
|
|
185
|
+
nuxtApp.vueApp.component('TextQuote', TextQuote)
|
|
186
|
+
nuxtApp.vueApp.component('TriangleAlert', TriangleAlert)
|
|
187
|
+
nuxtApp.vueApp.component('Underline', Underline)
|
|
188
|
+
nuxtApp.vueApp.component('View', View)
|
|
189
|
+
nuxtApp.vueApp.component('Save', Save)
|
|
190
|
+
nuxtApp.vueApp.component('Paintbrush', Paintbrush)
|
|
100
191
|
})
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { install as VueMonacoEditorPlugin, loader } from '@guolao/vue-monaco-editor'
|
|
2
|
+
import * as monaco from 'monaco-editor'
|
|
3
|
+
import EditorWorker from 'monaco-editor/esm/vs/editor/editor.worker?worker'
|
|
4
|
+
import JsonWorker from 'monaco-editor/esm/vs/language/json/json.worker?worker'
|
|
5
|
+
import CssWorker from 'monaco-editor/esm/vs/language/css/css.worker?worker'
|
|
6
|
+
import HtmlWorker from 'monaco-editor/esm/vs/language/html/html.worker?worker'
|
|
7
|
+
import TsWorker from 'monaco-editor/esm/vs/language/typescript/ts.worker?worker'
|
|
8
|
+
import { defineNuxtPlugin } from '#app'
|
|
9
|
+
|
|
10
|
+
self.MonacoEnvironment = {
|
|
11
|
+
getWorker(_, label) {
|
|
12
|
+
if (label === 'json') {
|
|
13
|
+
return new JsonWorker()
|
|
14
|
+
}
|
|
15
|
+
if (label === 'css' || label === 'scss' || label === 'less') {
|
|
16
|
+
return new CssWorker()
|
|
17
|
+
}
|
|
18
|
+
if (label === 'html' || label === 'handlebars' || label === 'razor') {
|
|
19
|
+
return new HtmlWorker()
|
|
20
|
+
}
|
|
21
|
+
if (label === 'typescript' || label === 'javascript') {
|
|
22
|
+
return new TsWorker()
|
|
23
|
+
}
|
|
24
|
+
return new EditorWorker()
|
|
25
|
+
},
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
loader.config({ monaco })
|
|
29
|
+
|
|
30
|
+
export default defineNuxtPlugin((nuxtApp) => {
|
|
31
|
+
nuxtApp.vueApp.use(VueMonacoEditorPlugin, {
|
|
32
|
+
paths: {
|
|
33
|
+
vs: 'https://cdn.jsdelivr.net/npm/monaco-editor@0.43.0/min/vs',
|
|
34
|
+
},
|
|
35
|
+
})
|
|
36
|
+
})
|