mango-cms 0.2.23 → 0.2.25
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/default/mango/automation/index.js +2 -0
- package/default/package.json +3 -2
- package/default/public/swBustCache.js +10 -0
- package/default/public/swNetworkFirst.js +30 -0
- package/default/src/helpers/localDB.js +32 -16
- package/default/src/helpers/mango.js +81 -1
- package/default/src/helpers/syncEntries.vue +50 -0
- package/default/vite.config.js +15 -0
- package/package.json +1 -1
package/default/package.json
CHANGED
|
@@ -25,7 +25,7 @@
|
|
|
25
25
|
"dayjs": "^1.10.7",
|
|
26
26
|
"express": "^4.18.1",
|
|
27
27
|
"google-maps": "^4.3.3",
|
|
28
|
-
"mango-cms": "^0.2.
|
|
28
|
+
"mango-cms": "^0.2.25",
|
|
29
29
|
"mapbox-gl": "^2.7.0",
|
|
30
30
|
"sweetalert2": "^11.4.0",
|
|
31
31
|
"vite": "^6.2.2",
|
|
@@ -35,7 +35,8 @@
|
|
|
35
35
|
"vue-upload-component": "^3.1.2",
|
|
36
36
|
"vue3-clipboard": "^1.0.0",
|
|
37
37
|
"vue3-touch-events": "^4.1.0",
|
|
38
|
-
"vuedraggable": "^4.1.0"
|
|
38
|
+
"vuedraggable": "^4.1.0",
|
|
39
|
+
"socket.io-client": "^4.8.1"
|
|
39
40
|
},
|
|
40
41
|
"devDependencies": {
|
|
41
42
|
"autoprefixer": "^10.4.0",
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
self.addEventListener('fetch', (event) => {
|
|
2
|
+
if (event.request.method === 'GET' && event.request.url.startsWith('http')) {
|
|
3
|
+
// Check if the request URL contains '/opportunities'
|
|
4
|
+
if (event.request.url.includes('/opportunities') || event.request.url.includes('/rhdiStats')) {
|
|
5
|
+
// If it does, respond with the network request directly without caching
|
|
6
|
+
event.respondWith(fetch(event.request));
|
|
7
|
+
} else {
|
|
8
|
+
event.respondWith(
|
|
9
|
+
caches.match(event.request).then((cachedResponse) => {
|
|
10
|
+
const fetchPromise = fetch(event.request).then((networkResponse) => {
|
|
11
|
+
// Clone the network response before consuming it
|
|
12
|
+
const clonedResponse = networkResponse.clone();
|
|
13
|
+
|
|
14
|
+
// If the network request is successful, update the cache
|
|
15
|
+
caches.open('user-data-cache').then((cache) => {
|
|
16
|
+
cache.put(event.request, clonedResponse);
|
|
17
|
+
});
|
|
18
|
+
return networkResponse;
|
|
19
|
+
}).catch(() => {
|
|
20
|
+
// If the network request fails, return the cached response (if available)
|
|
21
|
+
return cachedResponse;
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
// Return the cached response immediately if available, or the network response if not
|
|
25
|
+
return cachedResponse || fetchPromise;
|
|
26
|
+
})
|
|
27
|
+
);
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
});
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import axios from "axios"
|
|
2
|
-
import Mango from "./mango"
|
|
2
|
+
// import Mango from "./mango"
|
|
3
3
|
import Swal from "sweetalert2"
|
|
4
4
|
|
|
5
5
|
// Function for setting cookies
|
|
@@ -73,7 +73,7 @@ export default class LocalDB {
|
|
|
73
73
|
|
|
74
74
|
}
|
|
75
75
|
|
|
76
|
-
async save(entry,
|
|
76
|
+
async save(mangoSave, entry, options = {}) {
|
|
77
77
|
|
|
78
78
|
if (!this.ready) return
|
|
79
79
|
|
|
@@ -102,18 +102,23 @@ export default class LocalDB {
|
|
|
102
102
|
|
|
103
103
|
if (existingEntry) entry = { ...existingEntry, ...entry }
|
|
104
104
|
|
|
105
|
+
// Handle if the field is an image or array of images
|
|
106
|
+
let imageFields = {}
|
|
107
|
+
for (let key in entry) {
|
|
108
|
+
if (entry[key]?.type?.includes?.('image') || entry[key]?.type?.includes?.('array') && entry[key]?.[0]?.type?.includes?.('image')) {
|
|
109
|
+
imageFields[key] = entry[key]
|
|
110
|
+
delete entry[key]
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
105
114
|
// Remove Vue Proxy stuff so indexedDB is happy
|
|
106
|
-
let savedImage = entry.image
|
|
107
|
-
delete entry.image
|
|
108
115
|
entry = JSON.parse(JSON.stringify(entry))
|
|
109
|
-
if (!syncing) entry.updatedLocally = new Date()
|
|
110
|
-
|
|
111
|
-
// Format Address for offline
|
|
112
|
-
// if (!entry.address?.id && entry.address?.formatted) entry.address = entry.address.formatted
|
|
116
|
+
if (!options.syncing) entry.updatedLocally = new Date()
|
|
113
117
|
|
|
114
|
-
//
|
|
115
|
-
|
|
116
|
-
|
|
118
|
+
// Add images back in after json formatting
|
|
119
|
+
for (let key in imageFields) {
|
|
120
|
+
entry[key] = imageFields[key]
|
|
121
|
+
}
|
|
117
122
|
|
|
118
123
|
|
|
119
124
|
let method = entryExists ? 'put' : 'add'
|
|
@@ -152,15 +157,26 @@ export default class LocalDB {
|
|
|
152
157
|
|
|
153
158
|
delete entry.updatedLocally
|
|
154
159
|
|
|
160
|
+
// // Save to Mango (timeout if already created, else no timeout)
|
|
161
|
+
// let response
|
|
162
|
+
// if (onlyLocal) {
|
|
163
|
+
// response = await Mango[this.collection].save(entry, { bypassLocal: true })
|
|
164
|
+
// // if (warnings?.length) Swal.fire('WARNING:', warnings?.join(', '), 'warning')
|
|
165
|
+
// // console.log('mangoResponse', mangoResponse)
|
|
166
|
+
// // response = mangoResponse
|
|
167
|
+
// }
|
|
168
|
+
// else response = await runWithTimeout(async () => await Mango[this.collection].save(entry, { bypassLocal: true }), 5000)
|
|
169
|
+
|
|
170
|
+
|
|
155
171
|
// Save to Mango (timeout if already created, else no timeout)
|
|
156
172
|
let response
|
|
157
173
|
if (onlyLocal) {
|
|
158
|
-
|
|
159
|
-
if (warnings?.length) Swal.fire('WARNING:', warnings?.join(', '), 'warning')
|
|
160
|
-
console.log('mangoResponse', mangoResponse)
|
|
161
|
-
response = mangoResponse
|
|
174
|
+
response = await mangoSave(entry, { bypassLocal: true })
|
|
175
|
+
// if (warnings?.length) Swal.fire('WARNING:', warnings?.join(', '), 'warning')
|
|
176
|
+
// console.log('mangoResponse', mangoResponse)
|
|
177
|
+
// response = mangoResponse
|
|
162
178
|
}
|
|
163
|
-
else response = await runWithTimeout(async () => await
|
|
179
|
+
else response = await runWithTimeout(async () => await mangoSave(entry, { bypassLocal: true }), 5000)
|
|
164
180
|
|
|
165
181
|
// If successfull, delete from local queue
|
|
166
182
|
if (response?.id) {
|
|
@@ -5,6 +5,11 @@ import { algoliaAppId, algoliaSearchKey, algoliaIndex, port, mangoDomain, useDev
|
|
|
5
5
|
import axios from "axios";
|
|
6
6
|
import { ref } from 'vue'
|
|
7
7
|
import algoliasearch from 'algoliasearch/dist/algoliasearch-lite.esm.browser'
|
|
8
|
+
import LocalDB from './localDB'
|
|
9
|
+
import { io } from 'socket.io-client';
|
|
10
|
+
|
|
11
|
+
import { useRoute } from 'vue-router'
|
|
12
|
+
let route = useRoute()
|
|
8
13
|
|
|
9
14
|
let endpoints = {
|
|
10
15
|
authors: ['get'],
|
|
@@ -48,6 +53,8 @@ function getQuery(params) {
|
|
|
48
53
|
|
|
49
54
|
const Mango = collections.reduce((a, c) => {
|
|
50
55
|
|
|
56
|
+
let localDB = new LocalDB(c.name, api)
|
|
57
|
+
|
|
51
58
|
let runQuery = ({ limit, page, search, fields, id, sort, depthLimit, verbose } = {}) => {
|
|
52
59
|
|
|
53
60
|
let fullQuery
|
|
@@ -121,7 +128,8 @@ const Mango = collections.reduce((a, c) => {
|
|
|
121
128
|
|
|
122
129
|
}
|
|
123
130
|
|
|
124
|
-
let
|
|
131
|
+
let mangoSave = (data, options = {}) => {
|
|
132
|
+
|
|
125
133
|
let { id } = data
|
|
126
134
|
let method = id ? 'put' : 'post'
|
|
127
135
|
|
|
@@ -152,6 +160,11 @@ const Mango = collections.reduce((a, c) => {
|
|
|
152
160
|
})
|
|
153
161
|
}
|
|
154
162
|
|
|
163
|
+
let save = (data, options = {}) => {
|
|
164
|
+
if (!options.bypassLocal) return localDB.save(save, data, options)
|
|
165
|
+
else return mangoSave(data, options)
|
|
166
|
+
}
|
|
167
|
+
|
|
155
168
|
let deleteEntry = (data) => {
|
|
156
169
|
let id = data.id || data
|
|
157
170
|
|
|
@@ -164,10 +177,77 @@ const Mango = collections.reduce((a, c) => {
|
|
|
164
177
|
})
|
|
165
178
|
}
|
|
166
179
|
|
|
180
|
+
|
|
181
|
+
let sync = () => {
|
|
182
|
+
|
|
183
|
+
let remainingEntries = ref([])
|
|
184
|
+
let syncedEntries = ref([])
|
|
185
|
+
let online = ref(navigator.onLine)
|
|
186
|
+
let syncing = ref(false)
|
|
187
|
+
|
|
188
|
+
setInterval(async () => {
|
|
189
|
+
|
|
190
|
+
// console.log('syncing', syncing.value, route?.params?.id)
|
|
191
|
+
online.value = navigator.onLine
|
|
192
|
+
|
|
193
|
+
if (syncing.value) return
|
|
194
|
+
|
|
195
|
+
syncing.value = true
|
|
196
|
+
|
|
197
|
+
let entries = await localDB.getEntries()
|
|
198
|
+
remainingEntries.value = entries?.filter(e => (new Date() - new Date(e.updatedLocally)) > 30*1000)
|
|
199
|
+
|
|
200
|
+
for (let [index, entry] of remainingEntries.value.entries()) {
|
|
201
|
+
|
|
202
|
+
// Don't sync what we're actively working on
|
|
203
|
+
if (route?.params?.id == entry.id || (!isNaN(entry.id) && window.location.pathname.includes(`/${entry.id}`))) {
|
|
204
|
+
console.log('skipping', entry.id)
|
|
205
|
+
continue
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
try {
|
|
209
|
+
let response = await save(entry, {syncing: true})
|
|
210
|
+
if (response?.id) remainingEntries.value.splice(index, 1)
|
|
211
|
+
} catch(e) {
|
|
212
|
+
console.log('Error saving entry', e, entry)
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
await new Promise(resolve => setTimeout(resolve, 10*1000))
|
|
216
|
+
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
syncing.value = false
|
|
220
|
+
syncedEntries.value = []
|
|
221
|
+
|
|
222
|
+
}, 500)
|
|
223
|
+
|
|
224
|
+
|
|
225
|
+
return { remainingEntries, syncedEntries, online, syncing }
|
|
226
|
+
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
let subscribe = (callback, room) => {
|
|
230
|
+
let socket = io(`${api}/${c.name}`, { transports: ['websocket'] })
|
|
231
|
+
let userId = window.localStorage.getItem('auth').split(':')[1]
|
|
232
|
+
room = room || userId
|
|
233
|
+
socket.emit('subscribeToThread', room)
|
|
234
|
+
if (callback) socket.on('message', callback)
|
|
235
|
+
}
|
|
236
|
+
|
|
167
237
|
a[c.name] = runQuery
|
|
168
238
|
a[c.name]['save'] = save
|
|
169
239
|
a[c.name]['delete'] = deleteEntry
|
|
240
|
+
a[c.name]['subscribe'] = subscribe
|
|
170
241
|
a[c.singular] = (id, query) => runQuery({ id, ...query })
|
|
242
|
+
a[c.singular]['subscribe'] = (id, callback) => {
|
|
243
|
+
if (!id) return console.error('No id provided')
|
|
244
|
+
return subscribe(callback, id)
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
a[c.name]['local'] = localDB.getEntries
|
|
248
|
+
a[c.singular]['local'] = localDB.get
|
|
249
|
+
a[c.singular]['local']['delete'] = localDB.delete
|
|
250
|
+
a[c.name]['sync'] = sync
|
|
171
251
|
|
|
172
252
|
a[c.name]['search'] = runAlgolia
|
|
173
253
|
a[c.name]['search']['init'] = (search, query, algoliaFilters) => {
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div class="flex justify-between items-center bg-merlin-500/40 backdrop-blur-sm fixed top-0 left-0 w-full z-50">
|
|
3
|
+
|
|
4
|
+
<div v-if="remainingEntries.length" class="w-full px-4 py-2 flex justify-between relative">
|
|
5
|
+
<div>Syncing:</div>
|
|
6
|
+
<div>{{ syncedEntries.length }}/{{ remainingEntries.length + syncedEntries.length }}</div>
|
|
7
|
+
|
|
8
|
+
<div class="h-1 bg-red-500 absolute -bottom-1 left-0" :style="`width: ${progress}%;`" />
|
|
9
|
+
</div>
|
|
10
|
+
|
|
11
|
+
</div>
|
|
12
|
+
</template>
|
|
13
|
+
|
|
14
|
+
<script>
|
|
15
|
+
import Toggle from '../partials/toggle.vue'
|
|
16
|
+
|
|
17
|
+
export default {
|
|
18
|
+
components: { Toggle },
|
|
19
|
+
inject: ['store'],
|
|
20
|
+
data() {
|
|
21
|
+
return {
|
|
22
|
+
remainingEntries: [],
|
|
23
|
+
online: navigator.onLine,
|
|
24
|
+
syncing: false,
|
|
25
|
+
syncedEntries: [],
|
|
26
|
+
}
|
|
27
|
+
},
|
|
28
|
+
beforeDestroy() {
|
|
29
|
+
// clearInterval(this.onlineInterval)
|
|
30
|
+
},
|
|
31
|
+
async mounted() {
|
|
32
|
+
|
|
33
|
+
let response = this.$mango.opportunities.sync()
|
|
34
|
+
this.online = response.online
|
|
35
|
+
this.syncing = response.syncing
|
|
36
|
+
this.remainingEntries = response.remainingEntries
|
|
37
|
+
this.syncedEntries = response.syncedEntries
|
|
38
|
+
|
|
39
|
+
},
|
|
40
|
+
computed: {
|
|
41
|
+
progress() {
|
|
42
|
+
return Math.round(this.syncedEntries.length / (this.remainingEntries.length+this.syncedEntries.length) * 100)
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
</script>
|
|
47
|
+
|
|
48
|
+
<style>
|
|
49
|
+
|
|
50
|
+
</style>
|
package/default/vite.config.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { defineConfig } from 'vite'
|
|
2
2
|
import vue from '@vitejs/plugin-vue'
|
|
3
|
+
// import { VitePWA } from 'vite-plugin-pwa'
|
|
3
4
|
import path from 'path'
|
|
4
5
|
import fs from 'fs'
|
|
5
6
|
|
|
@@ -24,6 +25,20 @@ const collectionsPath = path.resolve(configPath, 'config/.collections.json')
|
|
|
24
25
|
export default defineConfig({
|
|
25
26
|
plugins: [
|
|
26
27
|
vue(),
|
|
28
|
+
// VitePWA({
|
|
29
|
+
// registerType: 'autoUpdate',
|
|
30
|
+
// includeAssets: ['images/*.jpg'],
|
|
31
|
+
// // devOptions: {
|
|
32
|
+
// // enabled: true,
|
|
33
|
+
// // },
|
|
34
|
+
// workbox: {
|
|
35
|
+
// importScripts: [
|
|
36
|
+
// '/swBustCache.js',
|
|
37
|
+
// '/swNetworkFirst.js?v=6'
|
|
38
|
+
// ],
|
|
39
|
+
// // globPatterns: ['**/*.{js,css,html,ico,png,svg,vue,jpg,jpeg}']
|
|
40
|
+
// }
|
|
41
|
+
// }),
|
|
27
42
|
{
|
|
28
43
|
name: 'watch-collections',
|
|
29
44
|
configureServer(server) {
|