@edgedev/create-edge-app 1.1.23 → 1.1.26
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/.env +1 -0
- package/.env.dev +1 -0
- package/README.md +55 -20
- package/{agent.md → agents.md} +2 -0
- package/bin/cli.js +6 -6
- package/edge/components/auth/login.vue +384 -0
- package/edge/components/auth/register.vue +396 -0
- package/edge/components/auth.vue +108 -0
- package/edge/components/autoFileUpload.vue +215 -0
- package/edge/components/billing.vue +8 -0
- package/edge/components/buttonDivider.vue +14 -0
- package/edge/components/chip.vue +34 -0
- package/edge/components/clipboardButton.vue +42 -0
- package/edge/components/cms/block.vue +529 -0
- package/edge/components/cms/blockApi.vue +212 -0
- package/edge/components/cms/blockEditor.vue +725 -0
- package/edge/components/cms/blockInput.vue +66 -0
- package/edge/components/cms/blockPicker.vue +486 -0
- package/edge/components/cms/blockRender.vue +78 -0
- package/edge/components/cms/blockSheetContent.vue +28 -0
- package/edge/components/cms/codeEditor.vue +466 -0
- package/edge/components/cms/fontUpload.vue +327 -0
- package/edge/components/cms/htmlContent.vue +807 -0
- package/edge/components/cms/init_blocks/api_with_subarrays.html +17 -0
- package/edge/components/cms/init_blocks/array_with_collection.html +7 -0
- package/edge/components/cms/init_blocks/array_with_objects.html +7 -0
- package/edge/components/cms/init_blocks/carousel.html +103 -0
- package/edge/components/cms/init_blocks/contact_us.html +69 -0
- package/edge/components/cms/init_blocks/content_with_left_image.html +27 -0
- package/edge/components/cms/init_blocks/footer.html +24 -0
- package/edge/components/cms/init_blocks/header_divider.html +7 -0
- package/edge/components/cms/init_blocks/hero.html +35 -0
- package/edge/components/cms/init_blocks/hero_carousel.html +52 -0
- package/edge/components/cms/init_blocks/newsletter.html +117 -0
- package/edge/components/cms/init_blocks/post_content.html +7 -0
- package/edge/components/cms/init_blocks/post_title_header.html +21 -0
- package/edge/components/cms/init_blocks/posts_list.html +20 -0
- package/edge/components/cms/init_blocks/properties_showcase.html +100 -0
- package/edge/components/cms/init_blocks/property_carousel.html +59 -0
- package/edge/components/cms/init_blocks/property_detail.html +112 -0
- package/edge/components/cms/init_blocks/property_detail_header.html +34 -0
- package/edge/components/cms/init_blocks/property_results.html +137 -0
- package/edge/components/cms/init_blocks/property_search.html +75 -0
- package/edge/components/cms/init_blocks/simple_array.html +7 -0
- package/edge/components/cms/mediaCard.vue +116 -0
- package/edge/components/cms/mediaManager.vue +386 -0
- package/edge/components/cms/menu.vue +1103 -0
- package/edge/components/cms/optionsSelect.vue +107 -0
- package/edge/components/cms/page.vue +1785 -0
- package/edge/components/cms/posts.vue +1083 -0
- package/edge/components/cms/site.vue +1298 -0
- package/edge/components/cms/themeDefaultMenu.vue +548 -0
- package/edge/components/cms/themeEditor.vue +426 -0
- package/edge/components/dashboard.vue +776 -0
- package/edge/components/editor.vue +671 -0
- package/edge/components/fileTree.vue +72 -0
- package/edge/components/files.vue +89 -0
- package/edge/components/formSubtypes/myOrgs.vue +214 -0
- package/edge/components/formSubtypes/users.vue +336 -0
- package/edge/components/functionChips.vue +57 -0
- package/edge/components/gError.vue +98 -0
- package/edge/components/gHelper.vue +67 -0
- package/edge/components/gInput.vue +1331 -0
- package/edge/components/loggingIn.vue +41 -0
- package/edge/components/menu.vue +137 -0
- package/edge/components/menuContent.vue +132 -0
- package/edge/components/myAccount.vue +317 -0
- package/edge/components/myOrganizations.vue +75 -0
- package/edge/components/myProfile.vue +122 -0
- package/edge/components/orgSwitcher.vue +25 -0
- package/edge/components/organizationMembers.vue +522 -0
- package/edge/components/organizationSettings.vue +271 -0
- package/edge/components/shad/breadcrumbs.vue +35 -0
- package/edge/components/shad/button.vue +43 -0
- package/edge/components/shad/checkbox.vue +73 -0
- package/edge/components/shad/combobox.vue +238 -0
- package/edge/components/shad/datepicker.vue +184 -0
- package/edge/components/shad/dialog.vue +32 -0
- package/edge/components/shad/dropdownMenu.vue +54 -0
- package/edge/components/shad/dropdownMenuItem.vue +21 -0
- package/edge/components/shad/form.vue +59 -0
- package/edge/components/shad/html.vue +877 -0
- package/edge/components/shad/input.vue +139 -0
- package/edge/components/shad/number.vue +109 -0
- package/edge/components/shad/select.vue +151 -0
- package/edge/components/shad/selectTags.vue +278 -0
- package/edge/components/shad/switch.vue +67 -0
- package/edge/components/shad/tags.vue +137 -0
- package/edge/components/shad/textarea.vue +102 -0
- package/edge/components/shad/typeMoney.vue +167 -0
- package/edge/components/sideBar.vue +288 -0
- package/edge/components/sideBarContent.vue +268 -0
- package/edge/components/sidebarProvider.vue +33 -0
- package/edge/components/tooltip.vue +16 -0
- package/edge/components/userMenu.vue +148 -0
- package/edge/components/v/alert.vue +59 -0
- package/edge/components/v/alertTitle.vue +18 -0
- package/edge/components/v/card.vue +53 -0
- package/edge/components/v/cardActions.vue +18 -0
- package/edge/components/v/cardText.vue +18 -0
- package/edge/components/v/cardTitle.vue +20 -0
- package/edge/components/v/col.vue +56 -0
- package/edge/components/v/list.vue +46 -0
- package/edge/components/v/listItem.vue +26 -0
- package/edge/components/v/listItemTitle.vue +18 -0
- package/edge/components/v/row.vue +42 -0
- package/edge/components/v/toolbar.vue +24 -0
- package/edge/composables/global.ts +519 -0
- package/edge-pull.sh +2 -0
- package/edge-push.sh +1 -0
- package/edge-status.sh +14 -0
- package/firebase.json +5 -2
- package/firebase_init.sh +21 -6
- package/package.json +1 -1
- package/plugins/firebase.client.ts +1 -0
- package/edge-components-install.sh +0 -1
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
<script setup>
|
|
2
|
+
import { useVModel } from '@vueuse/core'
|
|
3
|
+
|
|
4
|
+
const props = defineProps({
|
|
5
|
+
field: {
|
|
6
|
+
type: String,
|
|
7
|
+
required: true,
|
|
8
|
+
},
|
|
9
|
+
label: {
|
|
10
|
+
type: String,
|
|
11
|
+
required: true,
|
|
12
|
+
},
|
|
13
|
+
modelValue: {
|
|
14
|
+
type: [Object, String, Array, Number, Boolean],
|
|
15
|
+
required: true,
|
|
16
|
+
},
|
|
17
|
+
type: {
|
|
18
|
+
type: String,
|
|
19
|
+
required: true,
|
|
20
|
+
},
|
|
21
|
+
schema: {
|
|
22
|
+
type: Object,
|
|
23
|
+
required: false,
|
|
24
|
+
default: () => ({}),
|
|
25
|
+
},
|
|
26
|
+
})
|
|
27
|
+
|
|
28
|
+
const emit = defineEmits(['update:modelValue', 'delete'])
|
|
29
|
+
|
|
30
|
+
const modelValue = useVModel(props, 'modelValue', emit)
|
|
31
|
+
|
|
32
|
+
const state = reactive({
|
|
33
|
+
mounted: false,
|
|
34
|
+
})
|
|
35
|
+
|
|
36
|
+
onBeforeMount(async () => {
|
|
37
|
+
if (props.type === 'option' && !modelValue.value) {
|
|
38
|
+
modelValue.value = props.schema.value
|
|
39
|
+
}
|
|
40
|
+
await nextTick()
|
|
41
|
+
state.mounted = true
|
|
42
|
+
})
|
|
43
|
+
</script>
|
|
44
|
+
|
|
45
|
+
<template>
|
|
46
|
+
<div v-if="state.mounted">
|
|
47
|
+
<div v-if="props.type === 'richtext'">
|
|
48
|
+
<edge-shad-html v-model="modelValue" :enabled-toggles="['bold', 'italic', 'strike', 'bulletlist', 'orderedlist', 'underline']" :name="field" :label="label" />
|
|
49
|
+
</div>
|
|
50
|
+
<div v-else-if="props.type === 'textarea'">
|
|
51
|
+
<edge-shad-textarea v-model="modelValue" :name="field" :label="label" />
|
|
52
|
+
</div>
|
|
53
|
+
<div v-else-if="props.type === 'array'">
|
|
54
|
+
<edge-shad-tags v-model="modelValue" :label="label" :name="field" />
|
|
55
|
+
</div>
|
|
56
|
+
<div v-else-if="props.type === 'number'">
|
|
57
|
+
<edge-shad-number v-model="modelValue" :name="field" :label="label" />
|
|
58
|
+
</div>
|
|
59
|
+
<div v-else-if="props.type === 'option'">
|
|
60
|
+
<edge-shad-select v-model="modelValue" :name="field" :label="label" :item-title="props.schema.option.optionsKey" :item-value="props.schema.option.optionsValue" :items="props.schema.option.options" />
|
|
61
|
+
</div>
|
|
62
|
+
<div v-else>
|
|
63
|
+
<edge-shad-input v-model="modelValue" :name="field" :label="label" />
|
|
64
|
+
</div>
|
|
65
|
+
</div>
|
|
66
|
+
</template>
|
|
@@ -0,0 +1,486 @@
|
|
|
1
|
+
<script setup>
|
|
2
|
+
import { Plus } from 'lucide-vue-next'
|
|
3
|
+
|
|
4
|
+
const SHARED_PICKER_STATE = reactive({
|
|
5
|
+
hostId: null,
|
|
6
|
+
open: false,
|
|
7
|
+
activeInstance: null,
|
|
8
|
+
activeProps: {
|
|
9
|
+
siteId: '',
|
|
10
|
+
theme: null,
|
|
11
|
+
viewportMode: 'auto',
|
|
12
|
+
},
|
|
13
|
+
blocksLoaded: [],
|
|
14
|
+
selectedTags: ['Quick Picks'],
|
|
15
|
+
})
|
|
16
|
+
|
|
17
|
+
const HANDLERS = new Map()
|
|
18
|
+
let blocksSnapshotStarted = false
|
|
19
|
+
|
|
20
|
+
const props = defineProps({
|
|
21
|
+
blockOverride: {
|
|
22
|
+
type: Object,
|
|
23
|
+
default: null,
|
|
24
|
+
},
|
|
25
|
+
theme: {
|
|
26
|
+
type: Object,
|
|
27
|
+
default: null,
|
|
28
|
+
},
|
|
29
|
+
listOnly: {
|
|
30
|
+
type: Boolean,
|
|
31
|
+
default: false,
|
|
32
|
+
},
|
|
33
|
+
siteId: {
|
|
34
|
+
type: String,
|
|
35
|
+
default: '',
|
|
36
|
+
},
|
|
37
|
+
viewportMode: {
|
|
38
|
+
type: String,
|
|
39
|
+
default: 'auto',
|
|
40
|
+
},
|
|
41
|
+
})
|
|
42
|
+
|
|
43
|
+
const emit = defineEmits(['pick'])
|
|
44
|
+
|
|
45
|
+
const localState = reactive({
|
|
46
|
+
open: false,
|
|
47
|
+
blocksLoaded: [],
|
|
48
|
+
selectedTags: ['Quick Picks'],
|
|
49
|
+
})
|
|
50
|
+
|
|
51
|
+
const isSharedMode = computed(() => !props.blockOverride && !props.listOnly)
|
|
52
|
+
const instanceId = Symbol('blockPicker')
|
|
53
|
+
|
|
54
|
+
const edgeFirebase = inject('edgeFirebase')
|
|
55
|
+
|
|
56
|
+
const activeState = computed(() => isSharedMode.value ? SHARED_PICKER_STATE : localState)
|
|
57
|
+
const pickerState = activeState
|
|
58
|
+
|
|
59
|
+
const sheetOpen = computed({
|
|
60
|
+
get: () => isSharedMode.value ? SHARED_PICKER_STATE.open : localState.open,
|
|
61
|
+
set: (val) => {
|
|
62
|
+
if (isSharedMode.value)
|
|
63
|
+
SHARED_PICKER_STATE.open = val
|
|
64
|
+
else
|
|
65
|
+
localState.open = val
|
|
66
|
+
},
|
|
67
|
+
})
|
|
68
|
+
|
|
69
|
+
const isSharedHost = computed(() => isSharedMode.value && SHARED_PICKER_STATE.hostId === instanceId)
|
|
70
|
+
|
|
71
|
+
const activeProps = computed(() => isSharedMode.value ? SHARED_PICKER_STATE.activeProps : {
|
|
72
|
+
siteId: props.siteId,
|
|
73
|
+
theme: props.theme,
|
|
74
|
+
viewportMode: props.viewportMode,
|
|
75
|
+
})
|
|
76
|
+
|
|
77
|
+
const themeId = computed(() => {
|
|
78
|
+
const siteId = activeProps.value.siteId
|
|
79
|
+
if (!siteId)
|
|
80
|
+
return null
|
|
81
|
+
const site = edgeFirebase.data?.[`${edgeGlobal.edgeState.organizationDocPath}/sites`][siteId]
|
|
82
|
+
return site?.theme || null
|
|
83
|
+
})
|
|
84
|
+
|
|
85
|
+
const activeSiteId = computed(() => activeProps.value.siteId || '')
|
|
86
|
+
const pickerTheme = computed(() => activeProps.value.theme || null)
|
|
87
|
+
const pickerViewport = computed(() => activeProps.value.viewportMode || 'auto')
|
|
88
|
+
|
|
89
|
+
const blocks = computed(() => {
|
|
90
|
+
let blocks = []
|
|
91
|
+
if (edgeFirebase?.data?.[`${edgeGlobal.edgeState.organizationDocPath}/blocks`]) {
|
|
92
|
+
blocks = Object.values(edgeFirebase.data[`${edgeGlobal.edgeState.organizationDocPath}/blocks`])
|
|
93
|
+
}
|
|
94
|
+
if (themeId.value) {
|
|
95
|
+
blocks = blocks.filter(block => block.themes && block.themes.includes(themeId.value))
|
|
96
|
+
}
|
|
97
|
+
return blocks
|
|
98
|
+
})
|
|
99
|
+
|
|
100
|
+
const filteredBlocks = computed(() => {
|
|
101
|
+
const selected = pickerState.value.selectedTags
|
|
102
|
+
if (!selected.length)
|
|
103
|
+
return blocks.value
|
|
104
|
+
|
|
105
|
+
return blocks.value.filter((block) => {
|
|
106
|
+
const blockTags = Array.isArray(block.tags) ? block.tags : []
|
|
107
|
+
return selected.some(tag => blockTags.includes(tag))
|
|
108
|
+
})
|
|
109
|
+
})
|
|
110
|
+
|
|
111
|
+
async function ensureBlocksSnapshot() {
|
|
112
|
+
if (blocksSnapshotStarted)
|
|
113
|
+
return
|
|
114
|
+
blocksSnapshotStarted = true
|
|
115
|
+
await edgeFirebase.startSnapshot(`${edgeGlobal.edgeState.organizationDocPath}/blocks`)
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
// --- Begin: auto-size buttons to visual (scaled) height ---
|
|
119
|
+
const btnRefs = {}
|
|
120
|
+
const innerRefs = {}
|
|
121
|
+
let ro
|
|
122
|
+
|
|
123
|
+
function ensureObserver() {
|
|
124
|
+
if (!ro) {
|
|
125
|
+
ro = new ResizeObserver((entries) => {
|
|
126
|
+
for (const entry of entries) {
|
|
127
|
+
const id = entry.target.getAttribute('data-block-id')
|
|
128
|
+
const h = entry.target.getBoundingClientRect().height
|
|
129
|
+
const btn = btnRefs[id]
|
|
130
|
+
if (btn)
|
|
131
|
+
btn.style.height = `${Math.ceil(h)}px`
|
|
132
|
+
}
|
|
133
|
+
})
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
function setBtnRef(id, el) {
|
|
138
|
+
if (el)
|
|
139
|
+
btnRefs[id] = el
|
|
140
|
+
else delete btnRefs[id]
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
function setInnerRef(id, el) {
|
|
144
|
+
if (el) {
|
|
145
|
+
innerRefs[id] = el
|
|
146
|
+
ensureObserver()
|
|
147
|
+
el.setAttribute('data-block-id', id)
|
|
148
|
+
ro.observe(el)
|
|
149
|
+
}
|
|
150
|
+
else if (innerRefs[id]) {
|
|
151
|
+
ro?.unobserve(innerRefs[id])
|
|
152
|
+
delete innerRefs[id]
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
function syncAllHeights() {
|
|
157
|
+
for (const id in innerRefs) {
|
|
158
|
+
const el = innerRefs[id]
|
|
159
|
+
const h = el.getBoundingClientRect().height
|
|
160
|
+
const btn = btnRefs[id]
|
|
161
|
+
if (btn)
|
|
162
|
+
btn.style.height = `${Math.ceil(h)}px`
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
onBeforeUnmount(() => {
|
|
167
|
+
ro?.disconnect()
|
|
168
|
+
})
|
|
169
|
+
// --- End: auto-size buttons to visual (scaled) height ---
|
|
170
|
+
|
|
171
|
+
// Make object-literal-ish configs JSON-parseable (handles: title: "Main Header")
|
|
172
|
+
|
|
173
|
+
// --- End robust tag parsing ---
|
|
174
|
+
|
|
175
|
+
watch(sheetOpen, async (open) => {
|
|
176
|
+
if (open) {
|
|
177
|
+
await nextTick()
|
|
178
|
+
syncAllHeights()
|
|
179
|
+
}
|
|
180
|
+
})
|
|
181
|
+
|
|
182
|
+
onBeforeMount(async () => {
|
|
183
|
+
await ensureBlocksSnapshot()
|
|
184
|
+
})
|
|
185
|
+
|
|
186
|
+
onMounted(() => {
|
|
187
|
+
HANDLERS.set(instanceId, block => emit('pick', block))
|
|
188
|
+
if (isSharedMode.value && !SHARED_PICKER_STATE.hostId)
|
|
189
|
+
SHARED_PICKER_STATE.hostId = instanceId
|
|
190
|
+
})
|
|
191
|
+
|
|
192
|
+
onBeforeUnmount(() => {
|
|
193
|
+
HANDLERS.delete(instanceId)
|
|
194
|
+
if (SHARED_PICKER_STATE.hostId === instanceId) {
|
|
195
|
+
const next = HANDLERS.keys().next().value || null
|
|
196
|
+
SHARED_PICKER_STATE.hostId = next
|
|
197
|
+
if (!next)
|
|
198
|
+
SHARED_PICKER_STATE.open = false
|
|
199
|
+
}
|
|
200
|
+
})
|
|
201
|
+
|
|
202
|
+
const openPicker = () => {
|
|
203
|
+
if (isSharedMode.value) {
|
|
204
|
+
SHARED_PICKER_STATE.activeInstance = instanceId
|
|
205
|
+
SHARED_PICKER_STATE.activeProps = {
|
|
206
|
+
siteId: props.siteId,
|
|
207
|
+
theme: props.theme,
|
|
208
|
+
viewportMode: props.viewportMode,
|
|
209
|
+
}
|
|
210
|
+
if (!SHARED_PICKER_STATE.hostId)
|
|
211
|
+
SHARED_PICKER_STATE.hostId = instanceId
|
|
212
|
+
sheetOpen.value = true
|
|
213
|
+
}
|
|
214
|
+
else {
|
|
215
|
+
sheetOpen.value = true
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
const chooseBlock = (block) => {
|
|
220
|
+
let blockModelData = edgeGlobal.dupObject(block)
|
|
221
|
+
|
|
222
|
+
if (blockModelData?.synced && activeSiteId.value) {
|
|
223
|
+
console.log('Original block data:', blockModelData)
|
|
224
|
+
const pages = edgeFirebase.data?.[`${edgeGlobal.edgeState.organizationDocPath}/sites/${activeSiteId.value}/pages`] || {}
|
|
225
|
+
const pageEntries = Object.entries(pages)
|
|
226
|
+
for (const [pageId, pageData] of pageEntries) {
|
|
227
|
+
console.log('Checking page for synced block data:', pageId, pageData)
|
|
228
|
+
if (pageData.content && Array.isArray(pageData.content)) {
|
|
229
|
+
for (const pageBlock of pageData.content) {
|
|
230
|
+
if (pageBlock.blockId === block.docId) {
|
|
231
|
+
blockModelData = edgeGlobal.dupObject(pageBlock)
|
|
232
|
+
break
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
if (pageData.postContent && Array.isArray(pageData.postContent)) {
|
|
237
|
+
for (const pageBlock of pageData.postContent) {
|
|
238
|
+
if (pageBlock.blockId === block.docId) {
|
|
239
|
+
blockModelData = edgeGlobal.dupObject(pageBlock)
|
|
240
|
+
break
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
blockModelData.name = block.name
|
|
247
|
+
blockModelData.blockId = block.docId
|
|
248
|
+
console.log('Chosen block:', blockModelData)
|
|
249
|
+
if (isSharedMode.value) {
|
|
250
|
+
const handler = HANDLERS.get(SHARED_PICKER_STATE.activeInstance)
|
|
251
|
+
handler?.(blockModelData)
|
|
252
|
+
sheetOpen.value = false
|
|
253
|
+
}
|
|
254
|
+
else {
|
|
255
|
+
emit('pick', blockModelData)
|
|
256
|
+
sheetOpen.value = false
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
const loadingRender = (content) => {
|
|
260
|
+
content = content.replaceAll('{{loading}}', '')
|
|
261
|
+
content = content.replaceAll('{{loaded}}', 'hidden')
|
|
262
|
+
return content
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
const blockLoaded = (isLoading, index) => {
|
|
266
|
+
if (!isLoading && !pickerState.value.blocksLoaded.includes(index)) {
|
|
267
|
+
pickerState.value.blocksLoaded.push(index)
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
const getTagsFromBlocks = computed(() => {
|
|
272
|
+
const tagsSet = new Set()
|
|
273
|
+
|
|
274
|
+
Object.values(blocks.value || {}).forEach((block) => {
|
|
275
|
+
if (block.tags && Array.isArray(block.tags)) {
|
|
276
|
+
block.tags.forEach(tag => tagsSet.add(tag))
|
|
277
|
+
}
|
|
278
|
+
})
|
|
279
|
+
|
|
280
|
+
// Convert to array of objects
|
|
281
|
+
const tagsArray = Array.from(tagsSet).map(tag => ({ name: tag, title: tag }))
|
|
282
|
+
|
|
283
|
+
// Sort alphabetically
|
|
284
|
+
tagsArray.sort((a, b) => a.title.localeCompare(b.title))
|
|
285
|
+
|
|
286
|
+
// Remove "Quick Picks" if it exists
|
|
287
|
+
const filtered = tagsArray.filter(tag => tag.name !== 'Quick Picks')
|
|
288
|
+
|
|
289
|
+
// Always prepend it
|
|
290
|
+
return [{ name: 'Quick Picks', title: 'Quick Picks' }, ...filtered]
|
|
291
|
+
})
|
|
292
|
+
|
|
293
|
+
const hasActiveFilters = computed(() => pickerState.value.selectedTags.length > 0)
|
|
294
|
+
|
|
295
|
+
watch(getTagsFromBlocks, (tags) => {
|
|
296
|
+
const available = new Set(tags.map(tag => tag.name))
|
|
297
|
+
const filtered = pickerState.value.selectedTags.filter(tag => available.has(tag))
|
|
298
|
+
if (filtered.length !== pickerState.value.selectedTags.length)
|
|
299
|
+
pickerState.value.selectedTags = filtered
|
|
300
|
+
})
|
|
301
|
+
|
|
302
|
+
const toggleTag = (tag) => {
|
|
303
|
+
pickerState.value.selectedTags = []
|
|
304
|
+
pickerState.value.selectedTags.push(tag)
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
const clearTagFilters = () => {
|
|
308
|
+
pickerState.value.selectedTags = []
|
|
309
|
+
}
|
|
310
|
+
</script>
|
|
311
|
+
|
|
312
|
+
<template>
|
|
313
|
+
<div v-if="props.blockOverride">
|
|
314
|
+
<edge-cms-block-api
|
|
315
|
+
:content="props.blockOverride.content"
|
|
316
|
+
:values="props.blockOverride.values"
|
|
317
|
+
:meta="props.blockOverride.meta"
|
|
318
|
+
:theme="pickerTheme"
|
|
319
|
+
:site-id="activeSiteId"
|
|
320
|
+
:viewport-mode="pickerViewport"
|
|
321
|
+
@pending="blockLoaded($event, 'block')"
|
|
322
|
+
/>
|
|
323
|
+
<edge-cms-block-render
|
|
324
|
+
v-if="!pickerState.blocksLoaded.includes('block')"
|
|
325
|
+
:content="loadingRender(props.blockOverride.content)"
|
|
326
|
+
:values="props.blockOverride.values"
|
|
327
|
+
:meta="props.blockOverride.meta"
|
|
328
|
+
:theme="pickerTheme"
|
|
329
|
+
:site-id="activeSiteId"
|
|
330
|
+
:viewport-mode="pickerViewport"
|
|
331
|
+
/>
|
|
332
|
+
</div>
|
|
333
|
+
<div v-else-if="props.listOnly" class="p-6 h-[calc(100vh-50px)] overflow-hidden flex flex-col gap-4">
|
|
334
|
+
<div v-if="getTagsFromBlocks.length" class="flex flex-wrap items-center gap-2 text-sm">
|
|
335
|
+
<button
|
|
336
|
+
v-for="tagOption in getTagsFromBlocks"
|
|
337
|
+
:key="tagOption.name"
|
|
338
|
+
type="button"
|
|
339
|
+
class="px-3 py-1 rounded-full border transition-colors focus:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background"
|
|
340
|
+
:class="pickerState.selectedTags.includes(tagOption.name) ? 'bg-primary text-primary-foreground border-primary shadow-sm' : 'bg-background text-muted-foreground hover:bg-muted border-border'"
|
|
341
|
+
@click="toggleTag(tagOption.name)"
|
|
342
|
+
>
|
|
343
|
+
{{ tagOption.title }}
|
|
344
|
+
</button>
|
|
345
|
+
<button
|
|
346
|
+
v-if="hasActiveFilters"
|
|
347
|
+
type="button"
|
|
348
|
+
class="ml-auto px-3 py-1 text-xs font-medium uppercase tracking-wide text-muted-foreground border border-transparent hover:text-primary hover:border-primary/30 rounded-full transition-colors"
|
|
349
|
+
@click="clearTagFilters"
|
|
350
|
+
>
|
|
351
|
+
Clear filters
|
|
352
|
+
</button>
|
|
353
|
+
</div>
|
|
354
|
+
<div class="space-y-4 overflow-y-auto pr-1">
|
|
355
|
+
<template v-if="filteredBlocks.length">
|
|
356
|
+
<template v-for="block in filteredBlocks" :key="block.docId">
|
|
357
|
+
<button
|
|
358
|
+
:ref="el => setBtnRef(block.docId, el)"
|
|
359
|
+
type="button"
|
|
360
|
+
class="p-0 text-left hover:bg-primary text-slate-500 border !hover:text-white border-dashed cursor-pointer w-full overflow-hidden relative"
|
|
361
|
+
>
|
|
362
|
+
<div class="scale-wrapper">
|
|
363
|
+
<div
|
|
364
|
+
:ref="el => setInnerRef(block.docId, el)"
|
|
365
|
+
class="scale-inner scale p-4"
|
|
366
|
+
:data-block-id="block.docId"
|
|
367
|
+
>
|
|
368
|
+
<div class="text-4xl relative text-inherit text-center">
|
|
369
|
+
{{ block.name }}
|
|
370
|
+
</div>
|
|
371
|
+
<edge-cms-block-api :site-id="activeSiteId" :content="block.content" :theme="pickerTheme" :values="block.values" :meta="block.meta" :viewport-mode="pickerViewport" @pending="blockLoaded($event, block.docId)" />
|
|
372
|
+
<edge-cms-block-render
|
|
373
|
+
v-if="!pickerState.blocksLoaded.includes(block.docId)"
|
|
374
|
+
:content="loadingRender(block.content)"
|
|
375
|
+
:values="block.values"
|
|
376
|
+
:meta="block.meta"
|
|
377
|
+
:theme="pickerTheme"
|
|
378
|
+
:viewport-mode="pickerViewport"
|
|
379
|
+
/>
|
|
380
|
+
</div>
|
|
381
|
+
</div>
|
|
382
|
+
</button>
|
|
383
|
+
</template>
|
|
384
|
+
</template>
|
|
385
|
+
<p v-else class="text-sm text-muted-foreground">
|
|
386
|
+
No blocks match the selected tags yet.
|
|
387
|
+
</p>
|
|
388
|
+
</div>
|
|
389
|
+
</div>
|
|
390
|
+
<div v-else>
|
|
391
|
+
<div class="flex justify-center items-center">
|
|
392
|
+
<edge-shad-button
|
|
393
|
+
class="!my-1 px-2 h-[24px] bg-secondary text-secondary-foreground hover:text-white"
|
|
394
|
+
@click="openPicker"
|
|
395
|
+
>
|
|
396
|
+
<Plus class="w-4 h-4" />
|
|
397
|
+
</edge-shad-button>
|
|
398
|
+
</div>
|
|
399
|
+
<Sheet v-if="!isSharedMode || isSharedHost" v-model:open="sheetOpen">
|
|
400
|
+
<SheetContent side="left" class="w-full md:w-1/2 max-w-none sm:max-w-none max-w-2xl">
|
|
401
|
+
<SheetHeader>
|
|
402
|
+
<SheetTitle>Pick a Block</SheetTitle>
|
|
403
|
+
<SheetDescription />
|
|
404
|
+
</SheetHeader>
|
|
405
|
+
|
|
406
|
+
<edge-shad-form>
|
|
407
|
+
<div class="p-6 h-[calc(100vh-50px)] overflow-hidden flex flex-col gap-4">
|
|
408
|
+
<span class="text-xs text-muted-foreground">Block Filter</span>
|
|
409
|
+
<div v-if="getTagsFromBlocks.length" class="flex flex-wrap items-center gap-2 text-sm">
|
|
410
|
+
<button
|
|
411
|
+
v-for="tagOption in getTagsFromBlocks"
|
|
412
|
+
:key="tagOption.name"
|
|
413
|
+
type="button"
|
|
414
|
+
class="px-3 py-1 rounded-full border transition-colors focus:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background"
|
|
415
|
+
:class="pickerState.selectedTags.includes(tagOption.name) ? 'bg-primary text-primary-foreground border-primary shadow-sm' : 'bg-background text-muted-foreground hover:bg-muted border-border'"
|
|
416
|
+
@click="toggleTag(tagOption.name)"
|
|
417
|
+
>
|
|
418
|
+
{{ tagOption.title }}
|
|
419
|
+
</button>
|
|
420
|
+
<button
|
|
421
|
+
v-if="hasActiveFilters"
|
|
422
|
+
type="button"
|
|
423
|
+
class="ml-auto px-3 py-1 text-xs font-medium uppercase tracking-wide text-muted-foreground border border-transparent hover:text-primary hover:border-primary/30 rounded-full transition-colors"
|
|
424
|
+
@click="clearTagFilters"
|
|
425
|
+
>
|
|
426
|
+
Clear filters
|
|
427
|
+
</button>
|
|
428
|
+
</div>
|
|
429
|
+
<div class="space-y-4 overflow-y-auto pr-1">
|
|
430
|
+
<template v-if="filteredBlocks.length">
|
|
431
|
+
<template v-for="block in filteredBlocks" :key="block.docId">
|
|
432
|
+
<button
|
|
433
|
+
:ref="el => setBtnRef(block.docId, el)"
|
|
434
|
+
type="button"
|
|
435
|
+
class="p-0 text-left hover:bg-primary text-slate-500 border !hover:text-white border-dashed cursor-pointer w-full overflow-hidden relative"
|
|
436
|
+
@click="chooseBlock(block)"
|
|
437
|
+
>
|
|
438
|
+
<div class="scale-wrapper">
|
|
439
|
+
<div
|
|
440
|
+
:ref="el => setInnerRef(block.docId, el)"
|
|
441
|
+
class="scale-inner scale p-4"
|
|
442
|
+
:data-block-id="block.docId"
|
|
443
|
+
>
|
|
444
|
+
<div class="text-4xl relative text-inherit text-center">
|
|
445
|
+
{{ block.name }}
|
|
446
|
+
</div>
|
|
447
|
+
<edge-cms-block-api :site-id="activeSiteId" :content="block.content" :theme="pickerTheme" :values="block.values" :meta="block.meta" :viewport-mode="pickerViewport" @pending="blockLoaded($event, block.docId)" />
|
|
448
|
+
<edge-cms-block-render
|
|
449
|
+
v-if="!pickerState.blocksLoaded.includes(block.docId)"
|
|
450
|
+
:content="loadingRender(block.content)"
|
|
451
|
+
:values="block.values"
|
|
452
|
+
:meta="block.meta"
|
|
453
|
+
:theme="pickerTheme"
|
|
454
|
+
:viewport-mode="pickerViewport"
|
|
455
|
+
/>
|
|
456
|
+
</div>
|
|
457
|
+
</div>
|
|
458
|
+
</button>
|
|
459
|
+
</template>
|
|
460
|
+
</template>
|
|
461
|
+
<p v-else class="text-sm text-muted-foreground">
|
|
462
|
+
No blocks match the selected tags yet.
|
|
463
|
+
</p>
|
|
464
|
+
</div>
|
|
465
|
+
</div>
|
|
466
|
+
</edge-shad-form>
|
|
467
|
+
</SheetContent>
|
|
468
|
+
</Sheet>
|
|
469
|
+
</div>
|
|
470
|
+
</template>
|
|
471
|
+
|
|
472
|
+
<style lang="scss">
|
|
473
|
+
.scale-wrapper {
|
|
474
|
+
width: 100%;
|
|
475
|
+
overflow: hidden;
|
|
476
|
+
position: relative;
|
|
477
|
+
}
|
|
478
|
+
.scale-inner {
|
|
479
|
+
transform-origin: top left;
|
|
480
|
+
display: inline-block;
|
|
481
|
+
}
|
|
482
|
+
.scale {
|
|
483
|
+
transform: scale(0.5);
|
|
484
|
+
width: 200%; /* 100 / 0.25 */
|
|
485
|
+
}
|
|
486
|
+
</style>
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
<script setup>
|
|
2
|
+
import { renderTemplate } from '@edgedev/template-engine'
|
|
3
|
+
|
|
4
|
+
const props = defineProps({
|
|
5
|
+
content: {
|
|
6
|
+
type: String,
|
|
7
|
+
required: true,
|
|
8
|
+
},
|
|
9
|
+
values: {
|
|
10
|
+
type: Object,
|
|
11
|
+
required: true,
|
|
12
|
+
},
|
|
13
|
+
meta: {
|
|
14
|
+
type: Object,
|
|
15
|
+
required: true,
|
|
16
|
+
},
|
|
17
|
+
theme: {
|
|
18
|
+
type: Object,
|
|
19
|
+
default: null,
|
|
20
|
+
},
|
|
21
|
+
isolated: {
|
|
22
|
+
type: Boolean,
|
|
23
|
+
default: true,
|
|
24
|
+
},
|
|
25
|
+
viewportMode: {
|
|
26
|
+
type: String,
|
|
27
|
+
default: 'auto',
|
|
28
|
+
},
|
|
29
|
+
})
|
|
30
|
+
|
|
31
|
+
const emit = defineEmits(['loaded'])
|
|
32
|
+
|
|
33
|
+
const defaultTheme = {
|
|
34
|
+
extend: {
|
|
35
|
+
colors: {
|
|
36
|
+
brand: '#3B82F6',
|
|
37
|
+
accent: '#F59E0B',
|
|
38
|
+
surface: '#FAFAFA',
|
|
39
|
+
subtle: '#F3F4F6',
|
|
40
|
+
text: '#1F2937',
|
|
41
|
+
muted: '#9CA3AF',
|
|
42
|
+
success: '#22C55E',
|
|
43
|
+
danger: '#EF4444',
|
|
44
|
+
},
|
|
45
|
+
fontFamily: {
|
|
46
|
+
sans: ['Overpass', 'sans-serif'],
|
|
47
|
+
serif: ['Kode Mono', 'monospace'],
|
|
48
|
+
mono: ['Overpass', 'sans-serif'],
|
|
49
|
+
brand: ['Kode Mono', 'monospace'],
|
|
50
|
+
},
|
|
51
|
+
},
|
|
52
|
+
apply: {},
|
|
53
|
+
slots: {},
|
|
54
|
+
variants: {
|
|
55
|
+
light: { apply: {} },
|
|
56
|
+
dark: { apply: {}, slots: {} },
|
|
57
|
+
},
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
const theme = computed(() => props.theme ?? defaultTheme)
|
|
61
|
+
|
|
62
|
+
const rendered = computed(() => {
|
|
63
|
+
return renderTemplate(props.content, props.values, props.meta)
|
|
64
|
+
})
|
|
65
|
+
</script>
|
|
66
|
+
|
|
67
|
+
<template>
|
|
68
|
+
<edge-cms-html-content
|
|
69
|
+
:html="rendered"
|
|
70
|
+
:theme="theme"
|
|
71
|
+
:isolated="props.isolated"
|
|
72
|
+
:viewport-mode="props.viewportMode"
|
|
73
|
+
@loaded="emit('loaded')"
|
|
74
|
+
/>
|
|
75
|
+
</template>
|
|
76
|
+
|
|
77
|
+
<style scoped>
|
|
78
|
+
</style>
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
<script setup>
|
|
2
|
+
import { injectDialogRootContext } from 'reka-ui'
|
|
3
|
+
|
|
4
|
+
defineOptions({
|
|
5
|
+
inheritAttrs: false,
|
|
6
|
+
})
|
|
7
|
+
|
|
8
|
+
const rootContext = injectDialogRootContext()
|
|
9
|
+
|
|
10
|
+
const handleCloseAutoFocus = (event) => {
|
|
11
|
+
event.preventDefault()
|
|
12
|
+
const triggerEl = rootContext.triggerElement.value
|
|
13
|
+
if (!triggerEl)
|
|
14
|
+
return
|
|
15
|
+
try {
|
|
16
|
+
triggerEl.focus({ preventScroll: true })
|
|
17
|
+
}
|
|
18
|
+
catch {
|
|
19
|
+
triggerEl.focus()
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
</script>
|
|
23
|
+
|
|
24
|
+
<template>
|
|
25
|
+
<SheetContent v-bind="$attrs" @close-auto-focus="handleCloseAutoFocus">
|
|
26
|
+
<slot />
|
|
27
|
+
</SheetContent>
|
|
28
|
+
</template>
|