mango-cms 0.0.13

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.
Files changed (59) hide show
  1. package/README.md +17 -0
  2. package/bin/mango +4 -0
  3. package/frontend-starter/README.md +8 -0
  4. package/frontend-starter/dist/_redirects +1 -0
  5. package/frontend-starter/dist/assets/index.00922bd5.js +99 -0
  6. package/frontend-starter/dist/assets/index.1781f175.css +1 -0
  7. package/frontend-starter/dist/favicon.png +0 -0
  8. package/frontend-starter/dist/index.html +53 -0
  9. package/frontend-starter/dist/index.js +66 -0
  10. package/frontend-starter/index.html +25 -0
  11. package/frontend-starter/index.js +197 -0
  12. package/frontend-starter/package-lock.json +5454 -0
  13. package/frontend-starter/package.json +40 -0
  14. package/frontend-starter/postcss.config.js +6 -0
  15. package/frontend-starter/public/_redirects +1 -0
  16. package/frontend-starter/public/favicon.png +0 -0
  17. package/frontend-starter/public/index.js +66 -0
  18. package/frontend-starter/src/App.vue +27 -0
  19. package/frontend-starter/src/components/layout/login.vue +212 -0
  20. package/frontend-starter/src/components/layout/modal.vue +113 -0
  21. package/frontend-starter/src/components/layout/spinner.vue +17 -0
  22. package/frontend-starter/src/components/pages/404.vue +28 -0
  23. package/frontend-starter/src/components/pages/home.vue +74 -0
  24. package/frontend-starter/src/components/partials/button.vue +31 -0
  25. package/frontend-starter/src/helpers/Mango.vue +455 -0
  26. package/frontend-starter/src/helpers/breakpoints.js +34 -0
  27. package/frontend-starter/src/helpers/darkMode.js +38 -0
  28. package/frontend-starter/src/helpers/email.js +32 -0
  29. package/frontend-starter/src/helpers/formatPhone.js +18 -0
  30. package/frontend-starter/src/helpers/localDB.js +315 -0
  31. package/frontend-starter/src/helpers/mango.js +338 -0
  32. package/frontend-starter/src/helpers/model.js +9 -0
  33. package/frontend-starter/src/helpers/multiSelect.vue +252 -0
  34. package/frontend-starter/src/helpers/pills.vue +75 -0
  35. package/frontend-starter/src/helpers/reconnecting-websocket.js +357 -0
  36. package/frontend-starter/src/helpers/uploadFile.vue +157 -0
  37. package/frontend-starter/src/helpers/uploadFiles.vue +100 -0
  38. package/frontend-starter/src/helpers/uploadImages.vue +89 -0
  39. package/frontend-starter/src/helpers/user.js +40 -0
  40. package/frontend-starter/src/index.css +281 -0
  41. package/frontend-starter/src/main.js +145 -0
  42. package/frontend-starter/tailwind.config.js +46 -0
  43. package/frontend-starter/vite.config.js +10 -0
  44. package/frontend-starter/yarn.lock +3380 -0
  45. package/mango-cms-0.0.13.tgz +0 -0
  46. package/package.json +24 -0
  47. package/src/cli.js +93 -0
  48. package/src/default-config/automation/index.js +37 -0
  49. package/src/default-config/collections/examples.js +60 -0
  50. package/src/default-config/config/.collections.json +1 -0
  51. package/src/default-config/config/globalFields.js +15 -0
  52. package/src/default-config/config/settings.json +23 -0
  53. package/src/default-config/config/statuses.js +0 -0
  54. package/src/default-config/config/users.js +35 -0
  55. package/src/default-config/endpoints/index.js +19 -0
  56. package/src/default-config/fields/vimeo.js +36 -0
  57. package/src/default-config/hooks/test.js +5 -0
  58. package/src/default-config/plugins/mango-stand/index.js +206 -0
  59. package/src/main.js +278 -0
@@ -0,0 +1,315 @@
1
+ import axios from "axios"
2
+ import Mango from "./mango"
3
+ import Swal from "sweetalert2"
4
+
5
+ // Function for setting cookies
6
+ let setCookie = function (cname, cvalue) {
7
+ var d = new Date();
8
+ d.setTime(d.getTime() + (365 * 24 * 60 * 60 * 1000));
9
+ var expires = "expires=" + d.toUTCString();
10
+ document.cookie = cname + "=" + cvalue + ";" + expires + ";path=/";
11
+ }
12
+
13
+ function timeout(ms) {
14
+ return new Promise((resolve, reject) => {
15
+ setTimeout(() => {
16
+ reject(new Error(`Timeout after ${ms} ms`));
17
+ }, ms);
18
+ });
19
+ }
20
+
21
+ async function runWithTimeout(callback, ms) {
22
+ try {
23
+ let response = await Promise.race([
24
+ callback(),
25
+ timeout(ms)
26
+ ]);
27
+ console.log('timeout', response)
28
+ return response;
29
+ } catch (error) {
30
+ console.error(error.message);
31
+ }
32
+ }
33
+
34
+ export default class LocalDB {
35
+
36
+ constructor(collection, api) {
37
+
38
+ this.collection = collection
39
+
40
+ this.getDb()
41
+
42
+ this.ready = false
43
+ this.db = null
44
+ this.loading = false
45
+ this.saving = false
46
+ this.deleting = false
47
+ this.entry = null
48
+ this.entries = null
49
+
50
+ this.api = api
51
+
52
+ }
53
+
54
+ async getDb() {
55
+
56
+ let request = window.indexedDB.open(this.collection, 1);
57
+
58
+ request.onerror = e => {
59
+ this.ready = false
60
+ console.error('Error opening db', e);
61
+ };
62
+
63
+ request.onsuccess = e => {
64
+ this.db = e.target.result
65
+ this.ready = true
66
+ };
67
+
68
+ request.onupgradeneeded = e => {
69
+ console.log('onupgradeneeded');
70
+ this.db = e.target.result;
71
+ let objectStore = this.db.createObjectStore(this.collection, { autoIncrement: true, keyPath: 'id' });
72
+ };
73
+
74
+ }
75
+
76
+ async save(entry, syncing) {
77
+
78
+ if (!this.ready) return
79
+
80
+ this.saving = true;
81
+
82
+ let entryExists = !!entry.id
83
+ let onlyLocal = !isNaN(entry?.id) || !entryExists
84
+
85
+ let existingEntry
86
+
87
+ if (onlyLocal && entryExists) {
88
+ entry.id = Number(entry.id)
89
+ existingEntry = await this.get(entry.id)
90
+ } else if (entryExists) {
91
+ try {
92
+ existingEntry = await this.get(entry.id)
93
+ } catch {
94
+ // console.log('must just be in cloud')
95
+ }
96
+ }
97
+
98
+ return await new Promise(async (resolve, reject) => {
99
+
100
+ let trans = this.db.transaction([this.collection], 'readwrite');
101
+ let store = trans.objectStore(this.collection);
102
+
103
+ if (existingEntry) entry = { ...existingEntry, ...entry }
104
+
105
+ // Remove Vue Proxy stuff so indexedDB is happy
106
+ let savedImage = entry.image
107
+ delete entry.image
108
+ 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
113
+
114
+ // Handle images
115
+ if (savedImage?.type?.includes?.('image')) entry.image = savedImage
116
+ if (savedImage?.includes?.('http')) entry.image = savedImage
117
+
118
+
119
+ let method = entryExists ? 'put' : 'add'
120
+ let request = store[method](entry)
121
+
122
+ request.onsuccess = async () => {
123
+ this.saving = false
124
+ let localId = request.result
125
+
126
+ if (window?.offlineMode) return resolve({ ...entry, id: localId })
127
+
128
+ // If it only exists in the localDB, remove the ID for Mango
129
+ if (onlyLocal) delete entry.id
130
+
131
+ try {
132
+
133
+ // Try to upload the image
134
+ if (entry?.image?.type?.includes?.('image')) {
135
+ await runWithTimeout(async () => {
136
+ console.log('upload')
137
+ const formData = new FormData()
138
+ formData.append('file', entry.image)
139
+ const response = await axios.post(`${this.api}/upload`, formData)
140
+ console.log('upload response:', response)
141
+ const path = response?.data?.paths?.[0]
142
+ const url = `${this.api}${path}`
143
+ entry.image = url
144
+ }, 10 * 1000)
145
+ }
146
+
147
+ // Save to the cloud - this will throw if offline)
148
+
149
+ // If the entry is over a day old...
150
+ let olderThanOneDay = new Date(entry?.updatedLocally || '1/1/2024') < new Date(new Date().getTime() - 24 * 60 * 60 * 1000)
151
+ if (onlyLocal && olderThanOneDay) await this.delete(localId)
152
+
153
+ delete entry.updatedLocally
154
+
155
+ // Save to Mango (timeout if already created, else no timeout)
156
+ let response
157
+ if (onlyLocal) {
158
+ // Check the internet speed, if too slow, throw and queue for later
159
+ let speedTest = await axios.get(`/images/logo.jpg?v=${Date.now()}`, { timeout: 1000 })
160
+ let { response: mangoResponse, warnings } = await Mango[this.collection].save(entry, null, true)
161
+ if (warnings?.length) Swal.fire('WARNING:', warnings?.join(', '), 'warning')
162
+ console.log('mangoResponse', mangoResponse)
163
+ response = mangoResponse
164
+ }
165
+ else response = await runWithTimeout(async () => await Mango[this.collection].save(entry), 5000)
166
+
167
+ // If successfull, delete from local queue
168
+ if (response?.id) {
169
+ await this.delete(localId)
170
+ resolve(response);
171
+ } else {
172
+ console.log('response', response)
173
+ // Mango will throw if offline so delete this entry and save it to the failed queue
174
+ // await Mango.failedleads.save({ data: { raw: entry }, error: response, user: window.localStorage.getItem('user') })
175
+ // await this.delete(localId)
176
+
177
+ // Unauthorized
178
+ if (typeof response == 'string' && response?.includes?.('not have permission')) {
179
+ // Logout if credentials are bad
180
+ window.localStorage.removeItem('user')
181
+ window.localStorage.removeItem('token')
182
+ window.localStorage.removeItem('email')
183
+ window.localStorage.removeItem('auth')
184
+ setCookie(`Authorization`, ``)
185
+ window.location.reload()
186
+ }
187
+
188
+ // Existing Address
189
+ if (typeof response == 'string' && response?.includes?.('already visited this address')) {
190
+ await this.delete(localId)
191
+ Swal.fire('Existing Address', response, 'warning')
192
+ if (window.location.href.includes(`leads/${localId}`)) window.location.href = '/'
193
+ resolve({ stop: true });
194
+ }
195
+
196
+ // Address Lockout
197
+ if (typeof response == 'string' && response?.includes?.('asked not to be disturbed')) {
198
+ await this.delete(localId)
199
+ Swal.fire('Address Lockout', response, 'warning')
200
+ if (window.location.href.includes(`leads/${localId}`)) window.location.href = '/'
201
+ resolve({ stop: true });
202
+ }
203
+
204
+ console.log(response, onlyLocal);
205
+ resolve({ ...entry, id: localId });
206
+ }
207
+
208
+ } catch (e) {
209
+ console.log(e.message)
210
+ resolve({ ...entry, id: localId });
211
+ }
212
+ };
213
+
214
+ request.onerror = (e) => {
215
+ this.saving = false
216
+ reject(e);
217
+ };
218
+
219
+ });
220
+
221
+ }
222
+
223
+ async get(id) {
224
+
225
+ if (!this.ready) return
226
+
227
+ this.loading = true;
228
+
229
+ return await new Promise((resolve, reject) => {
230
+ let trans = this.db.transaction([this.collection], 'readonly');
231
+ let store = trans.objectStore(this.collection);
232
+ let request = store.get(id);
233
+
234
+ request.onsuccess = () => {
235
+ this.loading = false;
236
+ resolve(request.result);
237
+ };
238
+
239
+ request.onerror = (e) => {
240
+ this.loading = false;
241
+ reject(e);
242
+ };
243
+ });
244
+
245
+ }
246
+
247
+ async getEntries({ pageIndex = 0, pageSize = 1000 } = {}) {
248
+
249
+ if (!this.ready) return []
250
+
251
+ this.loading = true;
252
+
253
+ let response = await new Promise((resolve, reject) => {
254
+ let trans = this.db.transaction([this.collection], 'readonly');
255
+ let store = trans.objectStore(this.collection);
256
+ let cursorRequest = store.openCursor();
257
+
258
+ let entries = [];
259
+ let skippedEntries = pageIndex * pageSize;
260
+
261
+ cursorRequest.onsuccess = (e) => {
262
+ let cursor = e.target.result;
263
+ if (cursor) {
264
+ if (skippedEntries > 0) {
265
+ // Skip the entries before the current page
266
+ cursor.advance(skippedEntries);
267
+ skippedEntries = 0;
268
+ } else {
269
+ entries.push(cursor.value);
270
+ if (entries.length < pageSize) {
271
+ cursor.continue();
272
+ } else {
273
+ resolve(entries);
274
+ }
275
+ }
276
+ } else {
277
+ // No more entries to read; resolve with what we have
278
+ resolve(entries);
279
+ }
280
+ };
281
+
282
+ cursorRequest.onerror = (e) => {
283
+ reject(e);
284
+ };
285
+ });
286
+
287
+ // console.log('response', response);
288
+ this.loading = false;
289
+ return response;
290
+ }
291
+
292
+ async delete(id) {
293
+
294
+ this.deleting = true;
295
+
296
+ return new Promise((resolve, reject) => {
297
+
298
+ let trans = this.db.transaction([this.collection], 'readwrite');
299
+ let store = trans.objectStore(this.collection);
300
+ let request = store.delete(id);
301
+
302
+ request.onsuccess = () => {
303
+ this.deleting = false
304
+ resolve(request.result);
305
+ };
306
+
307
+ request.onerror = (e) => {
308
+ this.deleting = false
309
+ reject(e);
310
+ };
311
+
312
+ });
313
+ }
314
+
315
+ }
@@ -0,0 +1,338 @@
1
+ // import collections from '../../../mango/config/.collections.json'
2
+ // import { algoliaAppId, algoliaSearchKey, algoliaIndex, port, domain } from '../../../mango/config/settings'
3
+ import collections from '../../../config/config/.collections.json'
4
+ import { algoliaAppId, algoliaSearchKey, algoliaIndex, port, domain } from '../../../config/config/settings'
5
+ import axios from "axios";
6
+ import { ref } from 'vue'
7
+ import algoliasearch from 'algoliasearch/lite'
8
+
9
+ let endpoints = {
10
+ authors: ['get'],
11
+ // scripture: { validate: ['post'] }
12
+ }
13
+
14
+ // console.log('collections', collections)
15
+
16
+ const client = algoliasearch(algoliaAppId, algoliaSearchKey);
17
+ const algolia = client.initIndex(algoliaIndex);
18
+
19
+ let api = `https://${domain}`
20
+ let ws = `wss://${domain}/graphql`
21
+
22
+ if (process.env.NODE_ENV != 'production') {
23
+ api = `http://localhost:${port}`
24
+ ws = `ws://localhost:${port}/graphql`
25
+ }
26
+
27
+ const Mango = collections.reduce((a, c) => {
28
+
29
+ let runQuery = ({ limit, page, search, fields, id, sort, depthLimit } = {}) => {
30
+
31
+ let fullQuery
32
+
33
+
34
+ const params = { limit, page, search, fields, sort, depthLimit }
35
+
36
+ if (params.search != undefined) params.search = JSON.stringify(params.search)
37
+ if (params.fields != undefined) params.fields = JSON.stringify(params.fields)
38
+ if (params.sort != undefined) params.sort = JSON.stringify(params.sort)
39
+
40
+ const query = Object.keys(params)
41
+ .filter(key => params[key] != undefined)
42
+ ?.map(key => `${encodeURIComponent(key)}=${encodeURIComponent(params[key])}`)
43
+ ?.join('&') || ''
44
+ // console.log(query)
45
+
46
+ fullQuery = `${api}/${c.name}/${id || ''}?${query}`
47
+
48
+ let Authorization = window.localStorage.getItem('token')
49
+
50
+ return new Promise((resolve, reject) => {
51
+ axios.get(fullQuery, { headers: { Authorization } })
52
+ .then(response => resolve(response?.data?.response))
53
+ .catch(e => reject(e))
54
+ })
55
+
56
+ }
57
+
58
+ let runGraphql = (query) => {
59
+
60
+ let Authorization = window.localStorage.getItem('token')
61
+ query = { query }
62
+ return new Promise((resolve, reject) => {
63
+ axios.post(`${api}/graphql`, query, { headers: { Authorization } })
64
+ .then(response => resolve(response?.data?.data))
65
+ .catch(e => reject(e))
66
+ })
67
+
68
+ }
69
+
70
+ let runAlgolia = (search, query, algoliaFilters) => {
71
+
72
+ search = search || ''
73
+
74
+ let filters = `collection:${c.name}`
75
+ if (algoliaFilters) filters += ` AND ${algoliaFilters}`
76
+
77
+ let algoliaQuery = {
78
+ page: query?.page || 0,
79
+ filters,
80
+ hitsPerPage: query?.limit || 10
81
+ }
82
+
83
+ if (query?.fields) algoliaQuery.attributesToRetrieve = query.fields
84
+
85
+ return new Promise((resolve, reject) => {
86
+ algolia.search(search, algoliaQuery)
87
+ .then(({ hits, nbHits }) => {
88
+ hits.forEach(h => h.id = h.objectID)
89
+ resolve({ hits, nbHits })
90
+ })
91
+ })
92
+
93
+ }
94
+
95
+ let save = (data) => {
96
+ let { id } = data
97
+ let method = id ? 'put' : 'post'
98
+
99
+ // // Remove _id and computed fields
100
+ delete data.collection
101
+ delete data._id
102
+ delete data.id
103
+
104
+ for (let field of c.fields) {
105
+ if (field.computed) delete data[field.name]
106
+ if (field.relationship) data[field.name] = Array.isArray(data[field.name]) ? data[field.name].map(r => r.id) : data[field.name]?.id ? data[field.name].id : data[field.name]
107
+ }
108
+ for (let name in data) {
109
+ if (name.includes('__')) delete data[name]
110
+ }
111
+
112
+ let payload = { ...data }
113
+
114
+ let Authorization = window.localStorage.getItem('token')
115
+
116
+ return new Promise((resolve, reject) => {
117
+ axios[method](`${api}/${c.name}/${id || ''}`, payload, { headers: { Authorization } })
118
+ .then(response => resolve(response?.data?.response))
119
+ .catch(e => reject(e))
120
+ })
121
+ }
122
+
123
+ let deleteEntry = (data) => {
124
+ let id = data.id || data
125
+
126
+ let Authorization = window.localStorage.getItem('token')
127
+
128
+ return new Promise((resolve, reject) => {
129
+ axios.delete(`${api}/${c.name}/${id || ''}`, { headers: { Authorization } })
130
+ .then(response => resolve(response?.data))
131
+ .catch(e => reject(e))
132
+ })
133
+ }
134
+
135
+ a[c.name] = runQuery
136
+ a[c.name]['save'] = save
137
+ a[c.name]['delete'] = deleteEntry
138
+ a[c.singular] = (id, query) => runQuery({ id, ...query })
139
+
140
+ a[c.name]['search'] = runAlgolia
141
+ a[c.name]['search']['init'] = (search, query, algoliaFilters) => {
142
+ let loading = ref(true)
143
+ let data = ref(null)
144
+ let error = ref(null)
145
+ let totalResults = ref(null)
146
+
147
+ let response = runAlgolia(search, query, algoliaFilters)
148
+ .then(response => {
149
+ data.value = response.hits
150
+ totalResults.value = response.nbHits
151
+ loading.value = false
152
+ })
153
+ .catch(e => {
154
+ loading.value = false
155
+ error.value = e
156
+ })
157
+
158
+ return { data, loading, error }
159
+ }
160
+
161
+ a[c.name]['init'] = ({ limit, page, search, fields, id, sort } = {}) => {
162
+
163
+ let loading = ref(true)
164
+ let data = ref(null)
165
+ let error = ref(null)
166
+
167
+ let response = runQuery({ limit, page, search, fields, id, sort })
168
+ .then(response => {
169
+ data.value = response
170
+ loading.value = false
171
+ })
172
+ .catch(e => {
173
+ loading.value = false
174
+ error.value = e
175
+ })
176
+
177
+ return { data, loading, error }
178
+
179
+ }
180
+
181
+ a[c.singular]['init'] = (id) => a[c.name]['init']({ id })
182
+
183
+ a.relationRequest = ({ limit, page, search, fields, id, sort, depthLimit, path } = {}) => {
184
+
185
+ let fullQuery
186
+
187
+
188
+ const params = { limit, page, search, fields, sort, depthLimit }
189
+
190
+ if (params.search != undefined) params.search = JSON.stringify(params.search)
191
+ if (params.fields != undefined) params.fields = JSON.stringify(params.fields)
192
+ if (params.sort != undefined) params.sort = JSON.stringify(params.sort)
193
+
194
+ const query = Object.keys(params)
195
+ .filter(key => params[key] != undefined)
196
+ ?.map(key => `${encodeURIComponent(key)}=${encodeURIComponent(params[key])}`)
197
+ ?.join('&') || ''
198
+ // console.log(query)
199
+
200
+ fullQuery = `${api}/${path}?${query}`
201
+
202
+ let Authorization = window.localStorage.getItem('token')
203
+
204
+ return new Promise((resolve, reject) => {
205
+ axios.get(fullQuery, { headers: { Authorization } })
206
+ .then(response => resolve(response?.data?.response))
207
+ .catch(e => reject(e))
208
+ })
209
+
210
+ }
211
+ a.relationRequest.init = (query) => {
212
+
213
+ let loading = ref(true)
214
+ let data = ref(null)
215
+ let error = ref(null)
216
+
217
+ let response = a.relationRequest(query)
218
+ .then(response => {
219
+ data.value = response
220
+ loading.value = false
221
+ })
222
+ .catch(e => {
223
+ loading.value = false
224
+ error.value = e
225
+ })
226
+
227
+ return { data, loading, error }
228
+
229
+ }
230
+
231
+
232
+ a.graphql = runGraphql
233
+ a.graphql.init = (query) => {
234
+
235
+ let loading = ref(true)
236
+ let data = ref(null)
237
+ let error = ref(null)
238
+
239
+ let response = runGraphql(query)
240
+ .then(response => {
241
+ data.value = response
242
+ loading.value = false
243
+ })
244
+ .catch(e => {
245
+ loading.value = false
246
+ error.value = e
247
+ })
248
+
249
+ return { data, loading, error }
250
+
251
+ }
252
+
253
+ return a
254
+
255
+ }, {})
256
+
257
+ Mango.search = (search, query, algoliaFilters) => {
258
+
259
+ search = search || ''
260
+
261
+ let filters = ``
262
+ if (algoliaFilters) filters += `${algoliaFilters}`
263
+
264
+ let algoliaQuery = {
265
+ page: query?.page || 0,
266
+ filters,
267
+ hitsPerPage: query?.limit || 10
268
+ }
269
+
270
+ if (query?.fields) algoliaQuery.attributesToRetrieve = query.fields
271
+
272
+ return new Promise((resolve, reject) => {
273
+ algolia.search(search, algoliaQuery)
274
+ .then(({ hits }) => {
275
+ hits.forEach(h => h.id = h.objectID)
276
+ resolve(hits)
277
+ })
278
+ })
279
+
280
+ }
281
+
282
+ Mango.login = ({ email, password }) => {
283
+ return new Promise((resolve, reject) => {
284
+ axios.post(`${api}/endpoints/account/login`, { email, password })
285
+ .then(response => {
286
+ window.localStorage.setItem('token', response.data.token)
287
+ window.localStorage.setItem('user', response.data.memberId)
288
+ window.localStorage.setItem('email', email)
289
+ resolve(response.data)
290
+ })
291
+ .catch(e => reject(e))
292
+ })
293
+ }
294
+
295
+ Mango.endpoints = Object.keys(endpoints).reduce((a, c) => {
296
+
297
+ a[c] = {}
298
+
299
+ for (let method of endpoints[c]) {
300
+ a[c][method] = () => {
301
+
302
+ return new Promise((resolve, reject) => {
303
+ console.log('method', method)
304
+ console.log('`${api}/endpoints/${c}`', `${api}/endpoints/${c}`)
305
+ axios[method](`${api}/endpoints/${c}`)
306
+ .then(response => resolve(response?.data))
307
+ .catch(e => reject(e))
308
+ })
309
+
310
+ }
311
+ }
312
+
313
+ for (let method of endpoints[c]) {
314
+ a[c][method]['init'] = () => {
315
+
316
+ let loading = ref(true)
317
+ let data = ref(null)
318
+ let error = ref(null)
319
+
320
+ let response = a[c][method]().then(r => {
321
+ data.value = r
322
+ loading.value = false
323
+ })
324
+
325
+ return { data, loading, error }
326
+ }
327
+ }
328
+
329
+ return a
330
+
331
+ }, {})
332
+
333
+ Mango.collections = collections
334
+ Mango.ws = ws
335
+
336
+ Mango.online = async () => { try { return (await axios.get(`${api}/endpoints/test`))?.data?.includes('🥭') } catch (e) { return false } }
337
+
338
+ export default Mango
@@ -0,0 +1,9 @@
1
+
2
+ import { computed } from 'vue'
3
+
4
+ export function useModel(props, emit, name = 'modelValue') {
5
+ return computed({
6
+ get: () => props[name],
7
+ set: (value) => emit(`update:${name}`, value)
8
+ })
9
+ }