mango-cms 0.2.13 → 0.2.15

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/cli.js CHANGED
@@ -271,6 +271,37 @@ program
271
271
  }
272
272
  });
273
273
 
274
+ // --- Mango UI Dev Command ---
275
+ program
276
+ .command('ui <action>')
277
+ .description('Run Mango UI commands (e.g., dev, build, preview)')
278
+ .action(async (action) => {
279
+ const allowed = ['dev', 'build', 'preview'];
280
+ if (!allowed.includes(action)) {
281
+ console.error(`Unknown UI action: ${action}`);
282
+ process.exit(1);
283
+ }
284
+ const uiDir = path.join(__dirname, 'ui');
285
+ const viteConfig = path.join(uiDir, 'vite.config.js');
286
+ // Pass through the Mango config context via cwd and env
287
+ try {
288
+ execSync(
289
+ `npx vite ${action} --config "${viteConfig}"`,
290
+ {
291
+ cwd: uiDir,
292
+ stdio: 'inherit',
293
+ env: {
294
+ ...process.env,
295
+ // Optionally add more env vars here if needed
296
+ }
297
+ }
298
+ );
299
+ } catch (error) {
300
+ console.error(`Error running Mango UI (${action}):`, error.message);
301
+ process.exit(1);
302
+ }
303
+ });
304
+
274
305
  // Helper function for starting Mango CMS
275
306
  async function startMangoCMS() {
276
307
  try {
@@ -7,6 +7,9 @@
7
7
  "preview": "vite preview",
8
8
  "mango": "mango"
9
9
  },
10
+ "resolutions": {
11
+ "strip-ansi": "^6.0.1"
12
+ },
10
13
  "dependencies": {
11
14
  "@headlessui/vue": "^1.6.7",
12
15
  "@mapbox/mapbox-gl-geocoder": "^5.0.0",
@@ -1,113 +1,108 @@
1
1
  <template>
2
- <div
3
- ref="modal"
4
- @click.prevent.stop="close"
5
- tabindex="-1"
6
- class="
7
- fixed w-full h-screen bg-gray-800/50 dark:bg-black/60 backdrop-blur-sm md:backdrop-blur-md opacity-0 transition-all duration-500
8
- flex items-start sm:items-center justify-center z-[100] inset-0 !m-0 overscroll-none cursor-default
9
- "
10
- :class="{'!opacity-100': fadeIn}"
11
- aria-modal="true"
12
- v-show="active"
13
- role="dialog"
14
- >
15
- <button @click.prevent.stop="close" class="absolute top-2 right-3"><i class="fa fa-times md:text-4xl" /></button>
16
- <div
17
- ref="modalContent"
18
- @click.stop
19
- class="shadow-card rounded w-full p-4 md:p-8 m-2 dark:bg-gray-800 bg-white relative
20
- border dark:border-gray-700 space-y-4 md:space-y-8 max-h-[75vh]"
21
- :class="[dialogClasses, maxWidth, allowOverflow ? 'overflow-visible' : 'overflow-y-scroll']"
22
- >
23
- <slot :close="close"/>
24
- </div>
25
- </div>
26
- <!-- max-w-md max-w-lg max-w-xl max-w-sm -->
2
+ <div
3
+ ref="modal"
4
+ @click.prevent.stop="close"
5
+ tabindex="-1"
6
+ class="fixed w-full h-screen bg-gray-800/50 dark:bg-black/60 backdrop-blur-sm md:backdrop-blur-md opacity-0 transition-all duration-500 flex items-start sm:items-center justify-center z-[100] inset-0 !m-0 overscroll-none cursor-default"
7
+ :class="{ '!opacity-100': fadeIn }"
8
+ aria-modal="true"
9
+ v-show="active"
10
+ role="dialog"
11
+ >
12
+ <button @click.prevent.stop="close" class="absolute top-2 right-3"><i class="fa fa-times md:text-4xl" /></button>
13
+ <div
14
+ ref="modalContent"
15
+ @click.stop
16
+ class="shadow-card rounded w-full p-4 md:p-8 m-2 dark:bg-gray-800 bg-white relative border dark:border-gray-700 space-y-4 md:space-y-8 max-h-[75vh]"
17
+ :class="[dialogClasses, maxWidth, allowOverflow ? 'overflow-visible' : 'overflow-y-scroll']"
18
+ >
19
+ <slot :close="close" />
20
+ </div>
21
+ </div>
22
+ <!-- max-w-md max-w-lg max-w-xl max-w-sm -->
27
23
  </template>
28
24
 
29
25
  <script>
30
26
  export default {
31
- props: {
32
- dialogClasses: {type: String, default: ''},
33
- maxWidth: {default: 'max-w-md'},
34
- active: {default: true},
35
- allowOverflow: {type: Boolean, default: false},
36
- },
37
- data() {
38
- return {
39
- fadeIn: false
40
- }
41
- },
42
- watch: {
43
- active: {
44
- handler() {
45
- if (this.active) this.freeze()
46
- else this.thaw()
47
- },
48
- immediate: true
49
- }
50
- },
51
- beforeDestroy() {
52
- this.thaw();
53
- },
54
- unmounted() {
55
- this.thaw();
56
- },
57
- methods: {
58
- freeze() {
59
- this.$nextTick(() => {
60
- this.$refs.modalContent.focus()
61
- this.fadeIn = true
62
- document.body.style.overflow = 'hidden';
63
- document.getElementById('app').setAttribute('aria-hidden', 'true');
64
- this.$refs.modal.setAttribute('aria-modal', 'true');
65
- this.attachListeners();
66
- });
67
- },
68
- thaw() {
69
- document.body.style.overflow = '';
70
- document.getElementById('app').setAttribute('aria-hidden', 'false');
71
- this.$refs?.modal?.removeAttribute('aria-modal');
72
- this.removeListeners();
73
- },
74
- close() {
75
- this.fadeIn = false
76
- this.thaw()
77
- setTimeout(() => {
78
- this.$emit('hide')
79
- }, 500);
80
- },
81
- attachListeners() {
82
- window.addEventListener('keydown', this.handleKeydown);
83
- },
84
- removeListeners() {
85
- window.removeEventListener('keydown', this.handleKeydown);
86
- },
87
- handleKeydown(e) {
88
- if (e.key === 'Tab') {
89
- this.manageFocus(e);
90
- } else if (e.key === 'Escape') {
91
- this.close()
92
- }
93
- },
94
- manageFocus(e) {
95
- let focusable = Array.from(this.$refs.modal.querySelectorAll('button, [href], input:not([type="hidden"]), select, textarea, [tabindex]:not([tabindex="-1"])'));
96
- focusable = focusable.filter(el => window.getComputedStyle(el).display !== 'none');
97
- console.log('focusable', focusable)
27
+ props: {
28
+ dialogClasses: { type: String, default: '' },
29
+ maxWidth: { default: 'max-w-md' },
30
+ active: { default: true },
31
+ allowOverflow: { type: Boolean, default: false },
32
+ },
33
+ data() {
34
+ return {
35
+ fadeIn: false,
36
+ }
37
+ },
38
+ watch: {
39
+ active: {
40
+ handler() {
41
+ if (this.active) this.freeze()
42
+ else this.thaw()
43
+ },
44
+ immediate: true,
45
+ },
46
+ },
47
+ beforeDestroy() {
48
+ this.thaw()
49
+ },
50
+ unmounted() {
51
+ this.thaw()
52
+ },
53
+ methods: {
54
+ freeze() {
55
+ this.$nextTick(() => {
56
+ this.$refs.modalContent.focus()
57
+ this.fadeIn = true
58
+ document.body.style.overflow = 'hidden'
59
+ document.getElementById('app').setAttribute('aria-hidden', 'true')
60
+ this.$refs?.modal?.setAttribute('aria-modal', 'true')
61
+ this.attachListeners()
62
+ })
63
+ },
64
+ thaw() {
65
+ document.body.style.overflow = ''
66
+ document.getElementById('app').setAttribute('aria-hidden', 'false')
67
+ this.$refs?.modal?.removeAttribute('aria-modal')
68
+ this.removeListeners()
69
+ },
70
+ close() {
71
+ this.fadeIn = false
72
+ this.thaw()
73
+ setTimeout(() => {
74
+ this.$emit('hide')
75
+ }, 500)
76
+ },
77
+ attachListeners() {
78
+ window.addEventListener('keydown', this.handleKeydown)
79
+ },
80
+ removeListeners() {
81
+ window.removeEventListener('keydown', this.handleKeydown)
82
+ },
83
+ handleKeydown(e) {
84
+ if (e.key === 'Tab') {
85
+ this.manageFocus(e)
86
+ } else if (e.key === 'Escape') {
87
+ this.close()
88
+ }
89
+ },
90
+ manageFocus(e) {
91
+ let focusable = Array.from(this.$refs.modal.querySelectorAll('button, [href], input:not([type="hidden"]), select, textarea, [tabindex]:not([tabindex="-1"])'))
92
+ focusable = focusable.filter((el) => window.getComputedStyle(el).display !== 'none')
93
+ console.log('focusable', focusable)
98
94
 
99
- if (!focusable.includes(document.activeElement)) {
100
- e.preventDefault();
101
- focusable[0].focus();
102
- } else if (e.shiftKey && document.activeElement === focusable[0]) {
103
- e.preventDefault();
104
- focusable[focusable.length - 1].focus();
105
- } else if (document.activeElement === focusable[focusable.length - 1]) {
106
- e.preventDefault();
107
- focusable[0].focus();
108
- }
109
-
110
- }
111
- }
95
+ if (!focusable.includes(document.activeElement)) {
96
+ e.preventDefault()
97
+ focusable[0].focus()
98
+ } else if (e.shiftKey && document.activeElement === focusable[0]) {
99
+ e.preventDefault()
100
+ focusable[focusable.length - 1].focus()
101
+ } else if (document.activeElement === focusable[focusable.length - 1]) {
102
+ e.preventDefault()
103
+ focusable[0].focus()
104
+ }
105
+ },
106
+ },
112
107
  }
113
108
  </script>
@@ -40,8 +40,8 @@
40
40
  <div class="text-lg">Looks like the local Mango server isn't up and running yet. Please start it by running:</div>
41
41
  <div class="w-full">
42
42
  <div class="flex items-center space-x-2 relative">
43
- <code class="bg-gray-200 dark:bg-black p-3 border border-gray-700 rounded-lg flex-grow dark:selection:bg-sky-700">cd mango; yarn; yarn watch;</code>
44
- <button @click="copyToClipboard('cd mango; yarn; yarn watch;')" class="p-2 rounded-lg bg-gray-200 dark:bg-black border border-gray-700 hover:bg-gray-300 dark:hover:bg-gray-800 transition-colors relative group">
43
+ <code class="bg-gray-200 dark:bg-black p-3 border border-gray-700 rounded-lg flex-grow dark:selection:bg-sky-700">yarn mango dev</code>
44
+ <button @click="copyToClipboard('yarn mango dev')" class="p-2 rounded-lg bg-gray-200 dark:bg-black border border-gray-700 hover:bg-gray-300 dark:hover:bg-gray-800 transition-colors relative group">
45
45
  <svg v-if="!copied" xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
46
46
  <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 5H6a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2v-1M8 5a2 2 0 002 2h2a2 2 0 002-2M8 5a2 2 0 012-2h2a2 2 0 012 2m0 0h2a2 2 0 012 2v3m2 4H10m0 0l3-3m-3 3l3 3" />
47
47
  </svg>
@@ -60,15 +60,14 @@ let oldQuery = JSON.stringify(props.query || {})
60
60
  let inferedId = computed(() => props.id !== undefined ? props.id : props.query?.id || route?.params?.id || null )
61
61
  let searchingAlgolia = computed(() => !!props.algoliaSearch || !!props.algoliaFilters )
62
62
 
63
- let debounceInit = computed(() => {
64
- let timer;
65
- return (...args) => {
66
- if (active.value === false || props.disabled) return
67
- loading.value = true
68
- clearTimeout(timer);
69
- timer = setTimeout(() => { init.apply(this, args); }, props.debounce);
70
- };
71
- })
63
+ let timer;
64
+ let debounceInit = (...args) => {
65
+ console.log('debouncing')
66
+ if (active.value === false || props.disabled) return
67
+ loading.value = true
68
+ clearTimeout(timer);
69
+ timer = setTimeout(() => { init.apply(this, args); }, props.debounce);
70
+ };
72
71
 
73
72
  watch(() => props.query, (n, o) => {
74
73
  let newQuery = JSON.stringify(n)
@@ -77,8 +76,8 @@ watch(() => props.query, (n, o) => {
77
76
 
78
77
  watch(inferedId, (n, o) => defer(null, null, 'id'))
79
78
  watch(() => props.collection, (n, o) => defer(null, null, 'collection'))
80
- watch(() => props.algoliaSearch, (n, o) => searchingAlgolia.value ? debounceInit.value() : init())
81
- watch(() => props.algoliaFilters, (n, o) => searchingAlgolia.value ? debounceInit.value() : init())
79
+ watch(() => props.algoliaSearch, (n, o) => searchingAlgolia.value ? debounceInit() : init())
80
+ watch(() => props.algoliaFilters, (n, o) => searchingAlgolia.value ? debounceInit() : init())
82
81
  watch(loading, (n, o) => emit('update:loading', loading.value))
83
82
  watch(data, () => {
84
83
  if (data?.value?.length && props.query?.limit && data?.value?.length < props.query?.limit) noneRemain.value = true
@@ -90,7 +89,7 @@ function defer(n,o,origin) {
90
89
  console.log('n,o,origin', n,o,origin)
91
90
  if (n == o && origin == 'query') return console.log(`they're the same...`)
92
91
  if (origin == 'query') oldQuery = n
93
- nextTick(() => debounceInit.value())
92
+ nextTick(() => debounceInit())
94
93
  }
95
94
 
96
95
  async function loadMore() {
@@ -9,32 +9,49 @@ let configPath = path.resolve(projectRoot, 'mango')
9
9
 
10
10
  // If config doesn't exist in current directory, check parent
11
11
  if (!fs.existsSync(configPath)) {
12
- projectRoot = path.resolve(projectRoot, '..')
13
- configPath = path.resolve(projectRoot, 'mango')
12
+ projectRoot = path.resolve(projectRoot, '..')
13
+ configPath = path.resolve(projectRoot, 'mango')
14
14
 
15
- // If config still doesn't exist, throw error
16
- if (!fs.existsSync(configPath)) {
17
- throw new Error('Mango folder not found. Please ensure your mango folder exists either in the current directory or parent directory.')
18
- }
15
+ // If config still doesn't exist, throw error
16
+ if (!fs.existsSync(configPath)) {
17
+ throw new Error('Mango folder not found. Please ensure your mango folder exists either in the current directory or parent directory.')
18
+ }
19
19
  }
20
20
 
21
+ const collectionsPath = path.resolve(configPath, 'config/.collections.json')
22
+
21
23
  // https://vitejs.dev/config/
22
24
  export default defineConfig({
23
- plugins: [vue()],
24
- server: {
25
- host: '0.0.0.0',
26
- },
27
- resolve: {
28
- alias: {
29
- '@config': configPath,
30
- '@mango': configPath,
31
- '@settings': path.resolve(configPath, 'config/settings.json'),
32
- '@collections': path.resolve(configPath, 'config/.collections.json'),
33
- '@plugins': path.resolve(configPath, 'plugins'),
34
- 'vue': path.resolve(__dirname, 'node_modules/vue'),
35
- }
36
- },
37
- optimizeDeps: {
38
- exclude: ['vue'], // Prevent Vite from optimizing Vue separately
39
- },
25
+ plugins: [
26
+ vue(),
27
+ {
28
+ name: 'watch-collections',
29
+ configureServer(server) {
30
+ server.watcher.add(collectionsPath)
31
+ server.watcher.on('change', (file) => {
32
+ if (file === collectionsPath) {
33
+ server.ws.send({
34
+ type: 'full-reload',
35
+ })
36
+ }
37
+ })
38
+ },
39
+ },
40
+ ],
41
+ server: {
42
+ host: '0.0.0.0',
43
+ },
44
+ resolve: {
45
+ alias: {
46
+ '@config': configPath,
47
+ '@mango': configPath,
48
+ '@settings': path.resolve(configPath, 'config/settings.json'),
49
+ '@collections': path.resolve(configPath, 'config/.collections.json'),
50
+ '@plugins': path.resolve(configPath, 'plugins'),
51
+ vue: path.resolve(__dirname, 'node_modules/vue'),
52
+ },
53
+ },
54
+ optimizeDeps: {
55
+ exclude: ['vue', '@collections', '@settings'], // Prevent Vite from optimizing Vue separately
56
+ },
40
57
  })
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mango-cms",
3
- "version": "0.2.13",
3
+ "version": "0.2.15",
4
4
  "main": "./index.js",
5
5
  "exports": {
6
6
  ".": "./index.js",