mango-cms 0.1.17 → 0.1.19

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.
@@ -0,0 +1,482 @@
1
+ <template>
2
+ <div v-infinite-scroll="loadMore">
3
+ <slot :data="data" :loading="loading" :loadingPage="loadingPage" :totalResults="totalResults" :save="save" :saving="saving" />
4
+ </div>
5
+ </template>
6
+
7
+ <script setup>
8
+ import Mango from './mango'
9
+ import { siteName } from '@config/config/settings.json'
10
+ import rws from './reconnecting-websocket'
11
+ import { ref, watch, computed, nextTick, onUnmounted } from 'vue'
12
+ import { useRoute } from 'vue-router'
13
+
14
+ const props = defineProps({
15
+ collection: {type: String},
16
+ algoliaSearch: {type: String},
17
+ algoliaFilters: {type: String},
18
+ id: {type: String},
19
+ query: {type: Object},
20
+ graphql: {type: String},
21
+ debounce: {type: Number, default: 500},
22
+ infinite: {type: Boolean, default: false},
23
+ suspend: {type: Boolean, default: false},
24
+ disabled: {type: Boolean, default: false},
25
+ main: {type: Boolean, default: false},
26
+ globalSearch: {type: Boolean, default: false},
27
+ subscribe: {type: Boolean, default: false},
28
+ })
29
+
30
+ /*
31
+ If I comment this out, some things are fixed and others are broken...
32
+ */
33
+ // onBeforeRouteLeave((to, from) => {
34
+ // console.log('to, from', to.matched?.[0]?.components?.default?.name, from.matched?.[0]?.components?.default?.name)
35
+ // let sameComponent = to.matched?.[0]?.components?.default?.name == from.matched?.[0]?.components?.default?.name
36
+ // console.log('sameComponent', sameComponent)
37
+ // if (!sameComponent) active.value = false
38
+ // else active.value = true
39
+ // })
40
+
41
+ const emit = defineEmits(['update:data','update:loading'])
42
+
43
+ let webSocket
44
+
45
+ // const router = useRouter()
46
+ const route = useRoute()
47
+
48
+ let active = ref(true)
49
+ let autoPage = ref(null)
50
+ let loadingPage = ref(false)
51
+ let noneRemain = ref(false)
52
+ let saving = ref(false)
53
+ let inferedCollection = ref(props.collection || props.query?.collection || route.path.split('/')?.[1] || null)
54
+
55
+ let loading = ref(false)
56
+ let data = ref(null)
57
+ let error = ref(null)
58
+ let totalResults = ref(null)
59
+ let oldQuery = JSON.stringify(props.query || {})
60
+
61
+ let inferedId = computed(() => props.id !== undefined ? props.id : props.query?.id || route?.params?.id || null )
62
+ let searchingAlgolia = computed(() => !!props.algoliaSearch || !!props.algoliaFilters )
63
+
64
+ let debounceInit = computed(() => {
65
+ let timer;
66
+ return (...args) => {
67
+ if (active.value === false || props.disabled) return
68
+ loading.value = true
69
+ clearTimeout(timer);
70
+ timer = setTimeout(() => { init.apply(this, args); }, props.debounce);
71
+ };
72
+ })
73
+
74
+ watch(() => props.query, (n, o) => {
75
+ let newQuery = JSON.stringify(n)
76
+ if (oldQuery != newQuery) defer(newQuery, oldQuery, 'query')
77
+ }, { deep: true })
78
+
79
+ watch(inferedId, (n, o) => defer(null, null, 'id'))
80
+ watch(() => props.collection, (n, o) => defer(null, null, 'collection'))
81
+ watch(() => props.algoliaSearch, (n, o) => searchingAlgolia.value ? debounceInit.value() : init())
82
+ watch(() => props.algoliaFilters, (n, o) => searchingAlgolia.value ? debounceInit.value() : init())
83
+ watch(loading, (n, o) => emit('update:loading', loading.value))
84
+ watch(data, () => {
85
+ if (data?.value?.length && props.query?.limit && data?.value?.length < props.query?.limit) noneRemain.value = true
86
+ if (data?.value) emit('update:data', data.value)
87
+ })
88
+
89
+
90
+ function defer(n,o,origin) {
91
+ console.log('n,o,origin', n,o,origin)
92
+ if (n == o && origin == 'query') return console.log(`they're the same...`)
93
+ if (origin == 'query') oldQuery = n
94
+ nextTick(() => debounceInit.value())
95
+ }
96
+
97
+ async function loadMore() {
98
+
99
+ // console.log('loadMore')
100
+
101
+ if (!props.infinite || !data?.value?.length || loadingPage.value || noneRemain.value || props.disabled) return
102
+ loadingPage.value = true
103
+ emit('update:loadingPage', true)
104
+ let query = props.query ? JSON.parse(JSON.stringify(props.query)) : {}
105
+ if (query.search && !query.search?.wordSearch) delete query.search.wordSearch
106
+ autoPage.value ++
107
+ query.page = autoPage.value
108
+ if (searchingAlgolia.value) {
109
+ var nextPage = (await Mango[inferedCollection.value].search(props.algoliaSearch, query, props.algoliaFilters))?.hits
110
+ } else if (inferedCollection.value?.includes('/')) {
111
+ var nextPage = await Mango.relationRequest({...query, path: inferedCollection.value})
112
+ } else {
113
+ var nextPage = await Mango[inferedCollection.value](query)
114
+ }
115
+ if (nextPage.length) data.value = data.value.concat(nextPage)
116
+ if (!nextPage.length || (query.limit && nextPage.length < query.limit)) noneRemain.value = true
117
+ loadingPage.value = false
118
+ emit('update:loadingPage', false)
119
+
120
+ }
121
+
122
+ async function init() {
123
+
124
+ console.log('init')
125
+
126
+ // If the main entry is provided in ssr
127
+ if (props.main && window.mainEntry?.id == inferedId.value) {
128
+ data.value = window.mainEntry
129
+ window.mainEntry = null
130
+ emit('update:data', data.value)
131
+ return
132
+ }
133
+
134
+ if (active.value === false || props.disabled) return
135
+
136
+ console.log('active', active.value)
137
+
138
+ loading.value = true
139
+ data.value = null
140
+ noneRemain.value = false
141
+ autoPage.value = props.query?.page || 0
142
+
143
+ // Using the computed here won't work because it hasn't been computed yet
144
+ let searchingAlgolia = !!props.algoliaSearch || !!props.algoliaFilters
145
+
146
+ if (props.graphql){
147
+ data.value = await Mango.graphql(props.graphql)
148
+ emit('update:data', data.value)
149
+ loading.value = false
150
+ return
151
+ }
152
+
153
+ let collection = inferedCollection.value
154
+ let validatedCollection = Mango.collections.find(c => c.name == collection)
155
+ let id = props.id || props.query?.id || route?.params?.id || null
156
+ if (props.id !== undefined) id = props.id // So you can pass null to id and get a list
157
+
158
+ let query = props.query ? JSON.parse(JSON.stringify(props.query)) : {}
159
+ if (query.search && !query.search?.wordSearch) delete query.search.wordSearch // So empty search still returns something
160
+
161
+ if (inferedCollection.value?.includes('/')) {
162
+ data.value = await Mango.relationRequest({...query, path: inferedCollection.value})
163
+ emit('update:data', data.value)
164
+ loading.value = false
165
+ return
166
+ }
167
+
168
+ // Global Algolia Search
169
+ if (props.globalSearch) {
170
+ data.value = await Mango.search(props.algoliaSearch, query, props.algoliaFilters)
171
+ emit('update:data', data.value)
172
+ loading.value = false
173
+ return
174
+ }
175
+
176
+ if (!validatedCollection) return console.error(`🥭 ${collection} is not a valid collection.`)
177
+ if (id && !searchingAlgolia) collection = validatedCollection.singular
178
+ // if (id) query = id
179
+
180
+ console.log('collection, id:', collection, id)
181
+ // console.log('collection', collection)
182
+
183
+ if (searchingAlgolia) {
184
+ console.log('collection', collection)
185
+ let algoliaResponse = await Mango[collection].search(props.algoliaSearch, query, props.algoliaFilters)
186
+ data.value = algoliaResponse.hits
187
+ totalResults.value = algoliaResponse.nbHits
188
+ emit('update:data', data.value)
189
+ loading.value = false
190
+ } else {
191
+ if (id) data.value = await Mango[collection](id, query)
192
+ else data.value = await Mango[collection](query)
193
+
194
+ emit('update:data', data.value)
195
+ loading.value = false
196
+
197
+ if (id && props.main) document.title = data.value?.title || siteName
198
+ else document.title = siteName
199
+
200
+ if (props.subscribe) {
201
+
202
+ let sub = function() {
203
+ let subscribeCollection = `${validatedCollection.singular}Change`
204
+ console.log('subscribeCollection', subscribeCollection)
205
+ subscribe(subscribeCollection, id)
206
+ }
207
+
208
+ sub()
209
+
210
+ let hidden;
211
+ let visibilityChange;
212
+ if (typeof document.hidden !== "undefined") { // Opera 12.10 and Firefox 18 and later support
213
+ hidden = "hidden";
214
+ visibilityChange = "visibilitychange";
215
+ } else if (typeof document.msHidden !== "undefined") {
216
+ hidden = "msHidden";
217
+ visibilityChange = "msvisibilitychange";
218
+ } else if (typeof document.webkitHidden !== "undefined") {
219
+ hidden = "webkitHidden";
220
+ visibilityChange = "webkitvisibilitychange";
221
+ }
222
+
223
+ document.addEventListener(visibilityChange, () => {
224
+ if (!document[hidden]) {
225
+ // Ensure connectivity
226
+ console.log('ensure connectivity')
227
+ init()
228
+ }
229
+
230
+ // else {
231
+ // if (webSocket?.send) {
232
+ // webSocket.send(JSON.stringify({
233
+ // type: GQL.CONNECTION_TERMINATE
234
+ // }))
235
+ // }
236
+ // webSocket = null
237
+ // }
238
+ }, false)
239
+
240
+ }
241
+
242
+
243
+ return
244
+ }
245
+
246
+ }
247
+
248
+ const GQL = {
249
+ CONNECTION_INIT: 'connection_init',
250
+ CONNECTION_ACK: 'connection_ack',
251
+ CONNECTION_ERROR: 'connection_error',
252
+ CONNECTION_KEEP_ALIVE: 'ka',
253
+ START: 'start',
254
+ STOP: 'stop',
255
+ CONNECTION_TERMINATE: 'connection_terminate',
256
+ DATA: 'data',
257
+ ERROR: 'error',
258
+ COMPLETE: 'complete'
259
+ }
260
+ function subscribe(collection, id) {
261
+
262
+ webSocket = new rws(Mango.ws, 'graphql-ws')
263
+
264
+ webSocket.onopen = event => {
265
+
266
+ webSocket.send(JSON.stringify({
267
+ type: GQL.CONNECTION_INIT,
268
+ payload: {}
269
+ }))
270
+
271
+ webSocket.send(JSON.stringify({
272
+ type: GQL.START,
273
+ id: Date.now(),
274
+ payload: {
275
+ query: `
276
+ subscription {
277
+ ${collection}(id: "${id}") {
278
+ id
279
+ settings {
280
+ clueTimeLimit
281
+ guessTimeLimit
282
+ }
283
+ guesses {
284
+ word
285
+ playerId
286
+ time
287
+ }
288
+ clue {
289
+ text
290
+ number
291
+ time
292
+ team
293
+ words
294
+ guesses
295
+ }
296
+ words {
297
+ value
298
+ color
299
+ guessed
300
+ }
301
+ }
302
+ }`,
303
+ variables: {},
304
+ operationName: null
305
+ }
306
+ }))
307
+ }
308
+
309
+ webSocket.onmessage = event => {
310
+ const response = JSON.parse(event.data)
311
+ switch (response.type) {
312
+ case GQL.CONNECTION_ACK: {
313
+ console.log('success')
314
+ break
315
+ }
316
+ case GQL.CONNECTION_ERROR: {
317
+ console.error(response.payload)
318
+ break
319
+ }
320
+ case GQL.CONNECTION_KEEP_ALIVE: {
321
+ break
322
+ }
323
+ case GQL.DATA: {
324
+ console.log(response.id, response.payload.errors, response.payload.data)
325
+ if (response.payload.data) {
326
+ data.value = response.payload.data[collection]
327
+ emit('update:data', data.value)
328
+ }
329
+ break
330
+ }
331
+ case GQL.COMPLETE: {
332
+ console.log('completed', response.id)
333
+ break
334
+ }
335
+ }
336
+ }
337
+ }
338
+
339
+ onUnmounted(() => {
340
+ if (webSocket?.send) {
341
+ webSocket.send(JSON.stringify({
342
+ type: GQL.CONNECTION_TERMINATE
343
+ }))
344
+ }
345
+ })
346
+
347
+ // init()
348
+ // await init()
349
+
350
+ if (props.suspend) {
351
+ await init()
352
+ } else {
353
+ init()
354
+ }
355
+ </script>
356
+
357
+ <script>
358
+ export default {
359
+ directives: {
360
+ infiniteScroll: {
361
+ mounted(element, binding) {
362
+ const getScrollParent = (node) => {
363
+ while (node && node !== document.body) {
364
+ const overflowY = window.getComputedStyle(node).overflowY;
365
+ if (overflowY === 'auto' || overflowY === 'scroll') {
366
+ return node;
367
+ }
368
+ node = node.parentElement;
369
+ }
370
+ return window; // Default to window if no scrollable parent is found
371
+ };
372
+
373
+ const scrollParent = getScrollParent(element);
374
+
375
+ const handleScroll = () => {
376
+ const rect = element.getBoundingClientRect();
377
+ if (rect.bottom - 100 <= window.innerHeight) {
378
+ binding.value();
379
+ }
380
+ };
381
+
382
+ element._infiniteScrollHandler = handleScroll;
383
+ element._scrollParent = scrollParent;
384
+
385
+ scrollParent.addEventListener("scroll", handleScroll);
386
+ window.addEventListener("resize", handleScroll); // Handle viewport resize
387
+ },
388
+ unmounted(element) {
389
+ if (element._scrollParent) {
390
+ element._scrollParent.removeEventListener("scroll", element._infiniteScrollHandler);
391
+ }
392
+ window.removeEventListener("resize", element._infiniteScrollHandler);
393
+ delete element._infiniteScrollHandler;
394
+ delete element._scrollParent;
395
+ }
396
+ }
397
+ },
398
+ watch: {
399
+ // id: 'defer',
400
+ // inferedId: 'oldDefer',
401
+ // collection: 'oldDefer',
402
+ // query: {
403
+ // handler(n, o) {
404
+ // let oldQuery = JSON.stringify(o)
405
+ // let newQuery = JSON.stringify(n)
406
+ // if (oldQuery != newQuery) console.log(`but they're not the same!`)
407
+ // this.defer(newQuery, oldQuery, 'query')
408
+ // },
409
+ // deep: true,
410
+ // },
411
+ // algoliaSearch() {
412
+ // if (this.searchingAlgolia) this.debounceInit()
413
+ // else this.init()
414
+ // },
415
+ // data() {
416
+ // if (this.data?.length && this.query?.limit && this.data?.length < this.query?.limit) {
417
+ // this.noneRemain = true
418
+ // }
419
+ // if (this.data) this.$emit('update:data', this.data)
420
+ // }
421
+ },
422
+ computed: {
423
+ // searchingAlgolia() {return !!this.algoliaSearch},
424
+ // inferedId() { return this.id !== undefined ? this.id : this.query?.id || this.$route.params?.id || null },
425
+ // debounceInit() {
426
+ // let timer;
427
+ // return (...args) => {
428
+ // this.loading = true
429
+ // clearTimeout(timer);
430
+ // timer = setTimeout(() => { this.init.apply(this, args); }, this.debounce);
431
+ // };
432
+ // }
433
+ },
434
+ methods: {
435
+ // async loadMore() {
436
+
437
+ // console.log('loadMore')
438
+
439
+ // if (!this.infinite || !this.data?.length || this.loadingPage || this.noneRemain) return
440
+ // this.loadingPage = true
441
+ // let query = this.query ? JSON.parse(JSON.stringify(this.query)) : {}
442
+ // if (query.search && !query.search?.wordSearch) delete query.search.wordSearch
443
+ // this.autoPage ++
444
+ // query.page = this.autoPage
445
+ // if (this.searchingAlgolia) {
446
+ // var nextPage = await Mango[this.inferedCollection].search(this.algoliaSearch, query)
447
+ // } else {
448
+ // var nextPage = await Mango[this.inferedCollection](query)
449
+ // }
450
+ // if (nextPage.length) this.data = this.data.concat(nextPage)
451
+ // if (!nextPage.length || (query.limit && nextPage.length < query.limit)) this.noneRemain = true
452
+ // this.loadingPage = false
453
+
454
+ // },
455
+ // oldDefer(n,o,origin) {
456
+ // console.log('n,o,origin', n,o,origin)
457
+ // if (n == o) { console.log(`they're the same...`); return }
458
+ // this.$nextTick(() => this.init())
459
+ // },
460
+ async save(data) {
461
+ data = data || this.data
462
+ if (Array.isArray(data)) {
463
+ console.log('You can only call save after querying for an individual document.')
464
+ return
465
+ } else {
466
+ if (this.saving) return
467
+ this.saving = true
468
+ let response = await Mango[this.inferedCollection].save(data)
469
+ this.saving = false
470
+ // if (response?.id) return Swal.fire({ title: 'Success!', icon: 'success', confirmButtonText: 'Awesome!' })
471
+ // return Swal.fire({ title: 'Error 😬', icon: 'error' })
472
+ }
473
+ }
474
+ },
475
+ activated() { this.active = true },
476
+ beforeRouteUpdate() {
477
+ console.log('beforeRouteUpdate')
478
+ this.active = false
479
+ },
480
+ deactivated() { this.active = false },
481
+ }
482
+ </script>
@@ -0,0 +1,38 @@
1
+ import { reactive } from 'vue'
2
+
3
+ const toggle = () => {
4
+ console.log('toggle')
5
+ if (darkMode.enabled) {
6
+ window.localStorage.setItem('darkMode', 'false')
7
+ } else {
8
+ window.localStorage.setItem('darkMode', 'true')
9
+ }
10
+ checkDarkMode()
11
+
12
+ }
13
+
14
+ const darkMode = reactive({ enabled: false, toggle })
15
+
16
+ const systemChange = () => {
17
+ window.localStorage.removeItem('darkMode')
18
+ darkMode.enabled = window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches
19
+ }
20
+
21
+ const checkDarkMode = () => {
22
+ let storedValue = window.localStorage.getItem('darkMode')
23
+ let enabledLocally = storedValue != null
24
+ let localBoolean = storedValue == 'true' ? true : false
25
+ let systemPreference = window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches
26
+ darkMode.enabled = enabledLocally ? localBoolean : systemPreference
27
+ }
28
+
29
+ const useDarkMode = () => {
30
+
31
+ checkDarkMode()
32
+ window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', systemChange)
33
+
34
+ return { darkMode }
35
+
36
+ }
37
+
38
+ export default useDarkMode