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.
@@ -7,6 +7,8 @@ Date.prototype.monthDays = function () {
7
7
 
8
8
  let startAutomations = function () {
9
9
 
10
+ let now = new Date()
11
+
10
12
  // console.log('starting!')
11
13
 
12
14
  setInterval(() => {
@@ -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.23",
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,10 @@
1
+ self.addEventListener('message', (event) => {
2
+ if (event.data && event.data.type === 'CLEAR_CACHE') {
3
+ caches.keys().then((cacheNames) => {
4
+ cacheNames.forEach((cacheName) => {
5
+ caches.delete(cacheName);
6
+ });
7
+ });
8
+ console.log('BUSTED cache')
9
+ }
10
+ });
@@ -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, syncing) {
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
- // Handle images
115
- if (savedImage?.type?.includes?.('image')) entry.image = savedImage
116
- if (savedImage?.includes?.('http')) entry.image = savedImage
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
- let { response: mangoResponse, warnings } = await Mango[this.collection].save(entry, null, true)
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 Mango[this.collection].save(entry), 5000)
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 save = (data, options = {}) => {
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>
@@ -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) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mango-cms",
3
- "version": "0.2.23",
3
+ "version": "0.2.25",
4
4
  "main": "./index.js",
5
5
  "exports": {
6
6
  ".": "./index.js",