mindoodb-app-sdk 0.0.49 → 0.0.53
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/README.md +185 -1
- package/TESTING.md +58 -0
- package/dist/{chunk-TPYDERAK.js → chunk-6NTVTL6G.js} +151 -3
- package/dist/index.d.ts +138 -3
- package/dist/index.js +414 -18
- package/dist/testing/index.d.ts +107 -2
- package/dist/testing/index.js +620 -135
- package/dist/{types-C-eyC839.d.ts → types-CsKw_niD.d.ts} +250 -5
- package/package.json +3 -3
package/dist/index.js
CHANGED
|
@@ -6,8 +6,10 @@ import {
|
|
|
6
6
|
MINDOODB_APP_MAX_DRAG_PREVIEW_BYTES,
|
|
7
7
|
MINDOODB_APP_MAX_DRAG_PREVIEW_EDGE,
|
|
8
8
|
MINDOODB_APP_MAX_DRAG_TYPES,
|
|
9
|
+
MINDOODB_APP_VALUE_TAG,
|
|
9
10
|
MINIMAL_DRAG_PREVIEW_PNG,
|
|
10
11
|
MindooDBAppDragInputError,
|
|
12
|
+
MindooDBAppValue,
|
|
11
13
|
applyHostedDocumentOverscrollContain,
|
|
12
14
|
bindDragSource,
|
|
13
15
|
createMindooDBAppBridge,
|
|
@@ -21,7 +23,7 @@ import {
|
|
|
21
23
|
releaseMindooDBAppBridgeSessions,
|
|
22
24
|
sanitizeHostShortcutBindings,
|
|
23
25
|
snapshotElementToPng
|
|
24
|
-
} from "./chunk-
|
|
26
|
+
} from "./chunk-6NTVTL6G.js";
|
|
25
27
|
import {
|
|
26
28
|
MINDOODB_APP_BUNDLE_ARCHIVE_FILE_NAME,
|
|
27
29
|
MINDOODB_APP_BUNDLE_MANIFEST_FILE_NAME,
|
|
@@ -59,6 +61,9 @@ var MINDOODB_APP_DEFINITION_PERMISSIONS = [
|
|
|
59
61
|
"proposeapps"
|
|
60
62
|
];
|
|
61
63
|
var MINDOODB_APP_DEFINITION_REGISTRATION_PERMISSIONS = ["proposeapps"];
|
|
64
|
+
var MINDOODB_APP_LISTING_SUMMARY_MAX = 300;
|
|
65
|
+
var MINDOODB_APP_LISTING_DESCRIPTION_MAX = 4e3;
|
|
66
|
+
var MINDOODB_APP_LISTING_MAX_SCREENSHOTS = 8;
|
|
62
67
|
var LOGICAL_DATABASE_ID_PATTERN = /^[a-z0-9][a-z0-9._-]*$/i;
|
|
63
68
|
function isPlainObject(value) {
|
|
64
69
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
@@ -198,6 +203,156 @@ function readDatabases(value, errors) {
|
|
|
198
203
|
});
|
|
199
204
|
return result.length ? result : void 0;
|
|
200
205
|
}
|
|
206
|
+
function readLocalizedText(value, label, maxLength, errors) {
|
|
207
|
+
if (value === void 0 || value === null) {
|
|
208
|
+
return void 0;
|
|
209
|
+
}
|
|
210
|
+
if (typeof value === "string") {
|
|
211
|
+
const trimmed = value.trim();
|
|
212
|
+
if (trimmed.length > maxLength) {
|
|
213
|
+
errors.push(`${label} must not be longer than ${maxLength} characters.`);
|
|
214
|
+
return void 0;
|
|
215
|
+
}
|
|
216
|
+
return trimmed || void 0;
|
|
217
|
+
}
|
|
218
|
+
if (!isPlainObject(value)) {
|
|
219
|
+
errors.push(`${label} must be a string or an object of per-locale strings.`);
|
|
220
|
+
return void 0;
|
|
221
|
+
}
|
|
222
|
+
const result = {};
|
|
223
|
+
for (const [locale, text] of Object.entries(value)) {
|
|
224
|
+
if (typeof text !== "string") {
|
|
225
|
+
errors.push(`${label}.${locale} must be a string.`);
|
|
226
|
+
continue;
|
|
227
|
+
}
|
|
228
|
+
const trimmed = text.trim();
|
|
229
|
+
if (trimmed.length > maxLength) {
|
|
230
|
+
errors.push(`${label}.${locale} must not be longer than ${maxLength} characters.`);
|
|
231
|
+
continue;
|
|
232
|
+
}
|
|
233
|
+
if (trimmed) {
|
|
234
|
+
result[locale] = trimmed;
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
if (!Object.keys(result).length) {
|
|
238
|
+
return void 0;
|
|
239
|
+
}
|
|
240
|
+
if (!result.en) {
|
|
241
|
+
errors.push(`${label} must include an "en" entry as the fallback for other locales.`);
|
|
242
|
+
return void 0;
|
|
243
|
+
}
|
|
244
|
+
return result;
|
|
245
|
+
}
|
|
246
|
+
function readListingAssetPath(value, label, errors) {
|
|
247
|
+
if (value === void 0 || value === null) {
|
|
248
|
+
return void 0;
|
|
249
|
+
}
|
|
250
|
+
if (typeof value !== "string" || !value.trim()) {
|
|
251
|
+
errors.push(`${label} must be a non-empty string.`);
|
|
252
|
+
return void 0;
|
|
253
|
+
}
|
|
254
|
+
const trimmed = value.trim();
|
|
255
|
+
const scheme = /^([a-z][a-z0-9+.-]*):/i.exec(trimmed)?.[1]?.toLowerCase();
|
|
256
|
+
if (scheme && scheme !== "https") {
|
|
257
|
+
errors.push(`${label} must be a path on the app origin or an https: URL.`);
|
|
258
|
+
return void 0;
|
|
259
|
+
}
|
|
260
|
+
if (trimmed.startsWith("//")) {
|
|
261
|
+
errors.push(`${label} must be a path on the app origin or an https: URL.`);
|
|
262
|
+
return void 0;
|
|
263
|
+
}
|
|
264
|
+
return trimmed;
|
|
265
|
+
}
|
|
266
|
+
function readListing(value, errors) {
|
|
267
|
+
if (value === void 0 || value === null) {
|
|
268
|
+
return void 0;
|
|
269
|
+
}
|
|
270
|
+
if (!isPlainObject(value)) {
|
|
271
|
+
errors.push("App definition listing must be an object.");
|
|
272
|
+
return void 0;
|
|
273
|
+
}
|
|
274
|
+
const listing = {};
|
|
275
|
+
const summary = readLocalizedText(
|
|
276
|
+
value.summary,
|
|
277
|
+
"App definition listing.summary",
|
|
278
|
+
MINDOODB_APP_LISTING_SUMMARY_MAX,
|
|
279
|
+
errors
|
|
280
|
+
);
|
|
281
|
+
if (summary) listing.summary = summary;
|
|
282
|
+
const description = readLocalizedText(
|
|
283
|
+
value.description,
|
|
284
|
+
"App definition listing.description",
|
|
285
|
+
MINDOODB_APP_LISTING_DESCRIPTION_MAX,
|
|
286
|
+
errors
|
|
287
|
+
);
|
|
288
|
+
if (description) listing.description = description;
|
|
289
|
+
const icon = readListingAssetPath(value.icon, "App definition listing.icon", errors);
|
|
290
|
+
if (icon) listing.icon = icon;
|
|
291
|
+
if (value.screenshots !== void 0 && value.screenshots !== null) {
|
|
292
|
+
if (!Array.isArray(value.screenshots)) {
|
|
293
|
+
errors.push("App definition listing.screenshots must be an array.");
|
|
294
|
+
} else if (value.screenshots.length > MINDOODB_APP_LISTING_MAX_SCREENSHOTS) {
|
|
295
|
+
errors.push(
|
|
296
|
+
`App definition listing.screenshots must not list more than ${MINDOODB_APP_LISTING_MAX_SCREENSHOTS} images.`
|
|
297
|
+
);
|
|
298
|
+
} else {
|
|
299
|
+
const screenshots = [];
|
|
300
|
+
value.screenshots.forEach((entry, index) => {
|
|
301
|
+
const label = `App definition listing.screenshots[${index}]`;
|
|
302
|
+
const raw = typeof entry === "string" ? { file: entry } : entry;
|
|
303
|
+
if (!isPlainObject(raw)) {
|
|
304
|
+
errors.push(`${label} must be a path or an object with a file.`);
|
|
305
|
+
return;
|
|
306
|
+
}
|
|
307
|
+
const file = readListingAssetPath(raw.file, `${label}.file`, errors);
|
|
308
|
+
if (!file) {
|
|
309
|
+
return;
|
|
310
|
+
}
|
|
311
|
+
const caption = readLocalizedText(raw.caption, `${label}.caption`, MINDOODB_APP_LISTING_SUMMARY_MAX, errors);
|
|
312
|
+
screenshots.push(caption ? { file, caption } : { file });
|
|
313
|
+
});
|
|
314
|
+
if (screenshots.length) listing.screenshots = screenshots;
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
if (value.publisher !== void 0 && value.publisher !== null) {
|
|
318
|
+
const publisher = isPlainObject(value.publisher) ? value.publisher : {};
|
|
319
|
+
const name = typeof publisher.name === "string" ? publisher.name.trim() : "";
|
|
320
|
+
if (!name) {
|
|
321
|
+
errors.push("App definition listing.publisher must be an object with a non-empty name.");
|
|
322
|
+
} else {
|
|
323
|
+
const url = readListingAssetPath(publisher.url, "App definition listing.publisher.url", errors);
|
|
324
|
+
if (url && !/^https:\/\//i.test(url)) {
|
|
325
|
+
errors.push("App definition listing.publisher.url must be an absolute https: URL.");
|
|
326
|
+
} else {
|
|
327
|
+
listing.publisher = url ? { name, url } : { name };
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
return Object.keys(listing).length ? listing : void 0;
|
|
332
|
+
}
|
|
333
|
+
function resolveMindooDBAppLocalizedText(text, locale) {
|
|
334
|
+
if (text === void 0) {
|
|
335
|
+
return "";
|
|
336
|
+
}
|
|
337
|
+
if (typeof text === "string") {
|
|
338
|
+
return text;
|
|
339
|
+
}
|
|
340
|
+
const normalized = locale.trim();
|
|
341
|
+
const language = normalized.split(/[-_]/)[0]?.toLowerCase() ?? "";
|
|
342
|
+
return text[normalized] ?? text[normalized.toLowerCase()] ?? (language ? text[language] : void 0) ?? text.en;
|
|
343
|
+
}
|
|
344
|
+
function resolveMindooDBAppListingAssetUrl(path, originUrl) {
|
|
345
|
+
try {
|
|
346
|
+
const base = new URL(originUrl.endsWith("/") ? originUrl : `${originUrl}/`);
|
|
347
|
+
const resolved = new URL(path, base);
|
|
348
|
+
if (resolved.protocol !== "https:" && resolved.protocol !== base.protocol) {
|
|
349
|
+
return null;
|
|
350
|
+
}
|
|
351
|
+
return resolved.toString();
|
|
352
|
+
} catch {
|
|
353
|
+
return null;
|
|
354
|
+
}
|
|
355
|
+
}
|
|
201
356
|
function validateMindooDBAppDefinition(raw) {
|
|
202
357
|
const errors = [];
|
|
203
358
|
if (!isPlainObject(raw)) {
|
|
@@ -249,6 +404,7 @@ function validateMindooDBAppDefinition(raw) {
|
|
|
249
404
|
const allowGeolocation = readOptionalBoolean(raw, "allowGeolocation", errors);
|
|
250
405
|
const allowWebRtc = readOptionalBoolean(raw, "allowWebRtc", errors);
|
|
251
406
|
const allowWorkers = readOptionalBoolean(raw, "allowWorkers", errors);
|
|
407
|
+
const listing = readListing(raw.listing, errors);
|
|
252
408
|
if (defaultLaunchDatabaseId && databases && !databases.some((database) => database.logicalDatabaseId === defaultLaunchDatabaseId)) {
|
|
253
409
|
errors.push(
|
|
254
410
|
`App definition defaultLaunchDatabaseId "${defaultLaunchDatabaseId}" is not one of the declared databases.`
|
|
@@ -277,7 +433,8 @@ function validateMindooDBAppDefinition(raw) {
|
|
|
277
433
|
permissions,
|
|
278
434
|
launchParameters,
|
|
279
435
|
defaultLaunchDatabaseId,
|
|
280
|
-
databases
|
|
436
|
+
databases,
|
|
437
|
+
listing
|
|
281
438
|
},
|
|
282
439
|
errors: []
|
|
283
440
|
};
|
|
@@ -387,6 +544,22 @@ function expandAbbreviatedName(value) {
|
|
|
387
544
|
|
|
388
545
|
// src/client/hosting.ts
|
|
389
546
|
var MINDOODB_APP_HOSTING_QUERY_PARAM = "mindoodbAppHosting";
|
|
547
|
+
var MINDOODB_APP_LAUNCH_ID_QUERY_PARAM = "mindoodbAppLaunchId";
|
|
548
|
+
function readMindooDBAppLaunchId(search) {
|
|
549
|
+
const raw = search ?? (typeof window === "undefined" ? "" : window.location.search);
|
|
550
|
+
if (!raw) {
|
|
551
|
+
return null;
|
|
552
|
+
}
|
|
553
|
+
try {
|
|
554
|
+
const launchId = new URLSearchParams(raw).get(MINDOODB_APP_LAUNCH_ID_QUERY_PARAM)?.trim();
|
|
555
|
+
return launchId || null;
|
|
556
|
+
} catch {
|
|
557
|
+
return null;
|
|
558
|
+
}
|
|
559
|
+
}
|
|
560
|
+
function isLaunchedByHaven(search) {
|
|
561
|
+
return readMindooDBAppLaunchId(search) !== null;
|
|
562
|
+
}
|
|
390
563
|
function readMindooDBAppHostingMode(search) {
|
|
391
564
|
const raw = search ?? (typeof window === "undefined" ? "" : window.location.search);
|
|
392
565
|
if (!raw) {
|
|
@@ -402,6 +575,195 @@ function isHostedBundleRuntime(search) {
|
|
|
402
575
|
return readMindooDBAppHostingMode(search) === "hosted";
|
|
403
576
|
}
|
|
404
577
|
|
|
578
|
+
// src/landing.ts
|
|
579
|
+
var DEFAULT_HAVEN_URL = "https://haven.mindoodb.com";
|
|
580
|
+
var APP_ICON_FALLBACKS = ["appicon.png", "appicon.webp", "appicon.svg"];
|
|
581
|
+
var STRINGS = {
|
|
582
|
+
en: {
|
|
583
|
+
eyebrow: "App for MindooDB Haven",
|
|
584
|
+
cta: "Open in MindooDB Haven",
|
|
585
|
+
ctaHint: "Haven sets up your encrypted workspace and installs {label}. Already using Haven? Then the app is simply added.",
|
|
586
|
+
from: "from {origin}",
|
|
587
|
+
by: "by {publisher}",
|
|
588
|
+
screenshots: "Screenshots",
|
|
589
|
+
footer: "{label} runs inside MindooDB Haven, which keeps its data end-to-end encrypted and local-first. Opening this address on its own shows only this page.",
|
|
590
|
+
learnMore: "What is MindooDB?",
|
|
591
|
+
fallbackSummary: "This app runs inside MindooDB Haven."
|
|
592
|
+
},
|
|
593
|
+
de: {
|
|
594
|
+
eyebrow: "App f\xFCr MindooDB Haven",
|
|
595
|
+
cta: "In MindooDB Haven \xF6ffnen",
|
|
596
|
+
ctaHint: "Haven richtet deine verschl\xFCsselte Umgebung ein und installiert {label}. Du nutzt Haven schon? Dann wird die App einfach hinzugef\xFCgt.",
|
|
597
|
+
from: "von {origin}",
|
|
598
|
+
by: "von {publisher}",
|
|
599
|
+
screenshots: "Screenshots",
|
|
600
|
+
footer: "{label} l\xE4uft in MindooDB Haven, das ihre Daten Ende-zu-Ende-verschl\xFCsselt und lokal h\xE4lt. Direkt aufgerufen zeigt diese Adresse nur diese Seite.",
|
|
601
|
+
learnMore: "Was ist MindooDB?",
|
|
602
|
+
fallbackSummary: "Diese App l\xE4uft in MindooDB Haven."
|
|
603
|
+
}
|
|
604
|
+
};
|
|
605
|
+
function stringsFor(locale) {
|
|
606
|
+
return locale.toLowerCase().startsWith("de") ? STRINGS.de : STRINGS.en;
|
|
607
|
+
}
|
|
608
|
+
function format(template, values) {
|
|
609
|
+
return template.replace(/\{(\w+)\}/g, (match, key) => values[key] ?? match);
|
|
610
|
+
}
|
|
611
|
+
function resolveCurrentHavenAppUrl(href = window.location.href) {
|
|
612
|
+
const url = new URL(href);
|
|
613
|
+
url.search = "";
|
|
614
|
+
url.hash = "";
|
|
615
|
+
url.pathname = url.pathname.replace(/\/index\.html?$/i, "/");
|
|
616
|
+
return url.toString();
|
|
617
|
+
}
|
|
618
|
+
function buildHavenAppInstallUrl(appUrl, havenUrl = DEFAULT_HAVEN_URL) {
|
|
619
|
+
const url = new URL(havenUrl);
|
|
620
|
+
url.searchParams.set("app", appUrl);
|
|
621
|
+
return url.toString();
|
|
622
|
+
}
|
|
623
|
+
async function loadDefinition(appUrl) {
|
|
624
|
+
try {
|
|
625
|
+
const definitionUrl = new URL(MINDOODB_APP_DEFINITION_FILE_NAME, appUrl.endsWith("/") ? appUrl : `${appUrl}/`);
|
|
626
|
+
const response = await fetch(definitionUrl, { headers: { Accept: "application/json" } });
|
|
627
|
+
if (!response.ok) {
|
|
628
|
+
return null;
|
|
629
|
+
}
|
|
630
|
+
return validateMindooDBAppDefinition(await response.json()).definition;
|
|
631
|
+
} catch {
|
|
632
|
+
return null;
|
|
633
|
+
}
|
|
634
|
+
}
|
|
635
|
+
function el(tag, className, text) {
|
|
636
|
+
const node = document.createElement(tag);
|
|
637
|
+
if (className) node.className = className;
|
|
638
|
+
if (text !== void 0) node.textContent = text;
|
|
639
|
+
return node;
|
|
640
|
+
}
|
|
641
|
+
var STYLE_ID = "mdb-landing-style";
|
|
642
|
+
var STYLE = `
|
|
643
|
+
.mdb-landing{--bg:#f4f6fb;--card:#fff;--text:#172033;--muted:#5b6478;--line:#e2e6ef;--accent:#1f3a8a;--accent-text:#fff;
|
|
644
|
+
min-height:100vh;box-sizing:border-box;margin:0;padding:48px 16px;background:var(--bg);color:var(--text);
|
|
645
|
+
font:16px/1.55 system-ui,-apple-system,"Segoe UI",Roboto,sans-serif}
|
|
646
|
+
@media (prefers-color-scheme:dark){.mdb-landing{--bg:#0e1320;--card:#161d2e;--text:#e8ecf5;--muted:#9aa3b8;--line:#263049;--accent:#8fb0ff;--accent-text:#0e1320}}
|
|
647
|
+
.mdb-landing *{box-sizing:border-box}
|
|
648
|
+
.mdb-landing__card{max-width:880px;margin:0 auto;background:var(--card);border:1px solid var(--line);border-radius:24px;padding:40px;box-shadow:0 20px 60px rgba(15,23,42,.08)}
|
|
649
|
+
.mdb-landing__head{display:flex;gap:24px;align-items:center}
|
|
650
|
+
.mdb-landing__icon{width:96px;height:96px;border-radius:22px;object-fit:cover;flex:none;box-shadow:0 10px 30px rgba(15,23,42,.18)}
|
|
651
|
+
.mdb-landing__eyebrow{margin:0 0 4px;font-size:12px;letter-spacing:.12em;text-transform:uppercase;color:var(--muted)}
|
|
652
|
+
.mdb-landing__title{margin:0;font-size:clamp(26px,4vw,36px);line-height:1.15}
|
|
653
|
+
.mdb-landing__meta{margin:6px 0 0;color:var(--muted);font-size:14px}
|
|
654
|
+
.mdb-landing__summary{margin:28px 0 0;font-size:19px}
|
|
655
|
+
.mdb-landing__cta{display:flex;flex-wrap:wrap;gap:16px;align-items:center;margin:28px 0 0}
|
|
656
|
+
.mdb-landing__button{display:inline-block;padding:14px 22px;border-radius:12px;background:var(--accent);color:var(--accent-text);font-weight:600;text-decoration:none}
|
|
657
|
+
.mdb-landing__button:focus-visible{outline:3px solid var(--accent);outline-offset:3px}
|
|
658
|
+
.mdb-landing__hint{margin:0;max-width:420px;color:var(--muted);font-size:14px}
|
|
659
|
+
.mdb-landing__description{margin:32px 0 0}
|
|
660
|
+
.mdb-landing__description p{margin:0 0 12px}
|
|
661
|
+
.mdb-landing__shots-title{margin:32px 0 12px;font-size:15px;color:var(--muted);font-weight:600}
|
|
662
|
+
.mdb-landing__shots{display:grid;grid-auto-flow:column;grid-auto-columns:min(78%,420px);gap:16px;overflow-x:auto;padding-bottom:8px;scroll-snap-type:x mandatory}
|
|
663
|
+
.mdb-landing__shot{margin:0;scroll-snap-align:start}
|
|
664
|
+
.mdb-landing__shot img{width:100%;border-radius:14px;border:1px solid var(--line);display:block}
|
|
665
|
+
.mdb-landing__shot figcaption{margin-top:8px;font-size:13px;color:var(--muted)}
|
|
666
|
+
.mdb-landing__footer{margin:36px 0 0;padding-top:20px;border-top:1px solid var(--line);color:var(--muted);font-size:13px}
|
|
667
|
+
.mdb-landing__footer a{color:inherit}
|
|
668
|
+
@media (max-width:560px){.mdb-landing{padding:16px}.mdb-landing__card{padding:24px;border-radius:18px}.mdb-landing__head{flex-direction:column;align-items:flex-start}.mdb-landing__icon{width:72px;height:72px}}
|
|
669
|
+
`;
|
|
670
|
+
async function renderHavenAppLandingPage(options = {}) {
|
|
671
|
+
const appUrl = options.appUrl ?? resolveCurrentHavenAppUrl();
|
|
672
|
+
const locale = options.locale ?? (typeof navigator === "undefined" ? "en" : navigator.language || "en");
|
|
673
|
+
const strings = stringsFor(locale);
|
|
674
|
+
const definition = options.definition ?? await loadDefinition(appUrl);
|
|
675
|
+
const installUrl = buildHavenAppInstallUrl(appUrl, options.havenUrl);
|
|
676
|
+
const listing = definition?.listing;
|
|
677
|
+
const label = definition?.label ?? (document.title || new URL(appUrl).host);
|
|
678
|
+
const origin = new URL(appUrl).host;
|
|
679
|
+
if (!document.getElementById(STYLE_ID)) {
|
|
680
|
+
const style = el("style");
|
|
681
|
+
style.id = STYLE_ID;
|
|
682
|
+
style.textContent = STYLE;
|
|
683
|
+
document.head.append(style);
|
|
684
|
+
}
|
|
685
|
+
document.title = label;
|
|
686
|
+
const root = el("main", "mdb-landing");
|
|
687
|
+
root.dataset.testid = "haven-app-landing";
|
|
688
|
+
const card = el("article", "mdb-landing__card");
|
|
689
|
+
root.append(card);
|
|
690
|
+
const head = el("header", "mdb-landing__head");
|
|
691
|
+
const iconCandidates = (listing?.icon ? [listing.icon] : [...APP_ICON_FALLBACKS]).map((path) => resolveMindooDBAppListingAssetUrl(path, appUrl)).filter((url) => Boolean(url));
|
|
692
|
+
if (iconCandidates.length) {
|
|
693
|
+
const icon = el("img", "mdb-landing__icon");
|
|
694
|
+
let next = 0;
|
|
695
|
+
icon.alt = "";
|
|
696
|
+
icon.addEventListener("error", () => {
|
|
697
|
+
next += 1;
|
|
698
|
+
if (next < iconCandidates.length) {
|
|
699
|
+
icon.src = iconCandidates[next];
|
|
700
|
+
} else {
|
|
701
|
+
icon.remove();
|
|
702
|
+
}
|
|
703
|
+
});
|
|
704
|
+
icon.src = iconCandidates[0];
|
|
705
|
+
head.append(icon);
|
|
706
|
+
}
|
|
707
|
+
const titles = el("div");
|
|
708
|
+
titles.append(el("p", "mdb-landing__eyebrow", strings.eyebrow), el("h1", "mdb-landing__title", label));
|
|
709
|
+
const meta = [format(strings.from, { origin })];
|
|
710
|
+
if (listing?.publisher) {
|
|
711
|
+
meta.unshift(format(strings.by, { publisher: listing.publisher.name }));
|
|
712
|
+
}
|
|
713
|
+
titles.append(el("p", "mdb-landing__meta", meta.join(" \xB7 ")));
|
|
714
|
+
head.append(titles);
|
|
715
|
+
card.append(head);
|
|
716
|
+
const summary = resolveMindooDBAppLocalizedText(listing?.summary, locale) || definition?.description || strings.fallbackSummary;
|
|
717
|
+
card.append(el("p", "mdb-landing__summary", summary));
|
|
718
|
+
const cta = el("div", "mdb-landing__cta");
|
|
719
|
+
const button = el("a", "mdb-landing__button", strings.cta);
|
|
720
|
+
button.href = installUrl;
|
|
721
|
+
button.rel = "noopener";
|
|
722
|
+
button.dataset.testid = "haven-app-landing-install";
|
|
723
|
+
cta.append(button, el("p", "mdb-landing__hint", format(strings.ctaHint, { label })));
|
|
724
|
+
card.append(cta);
|
|
725
|
+
const description = resolveMindooDBAppLocalizedText(listing?.description, locale);
|
|
726
|
+
if (description) {
|
|
727
|
+
const block = el("section", "mdb-landing__description");
|
|
728
|
+
for (const paragraph of description.split(/\n\s*\n/)) {
|
|
729
|
+
if (paragraph.trim()) block.append(el("p", void 0, paragraph.trim()));
|
|
730
|
+
}
|
|
731
|
+
card.append(block);
|
|
732
|
+
}
|
|
733
|
+
const screenshots = (listing?.screenshots ?? []).map((shot) => ({
|
|
734
|
+
url: resolveMindooDBAppListingAssetUrl(shot.file, appUrl),
|
|
735
|
+
caption: resolveMindooDBAppLocalizedText(shot.caption, locale)
|
|
736
|
+
})).filter((shot) => Boolean(shot.url));
|
|
737
|
+
if (screenshots.length) {
|
|
738
|
+
card.append(el("h2", "mdb-landing__shots-title", strings.screenshots));
|
|
739
|
+
const strip = el("div", "mdb-landing__shots");
|
|
740
|
+
for (const shot of screenshots) {
|
|
741
|
+
const figure = el("figure", "mdb-landing__shot");
|
|
742
|
+
const image = el("img");
|
|
743
|
+
image.src = shot.url;
|
|
744
|
+
image.alt = shot.caption;
|
|
745
|
+
image.loading = "lazy";
|
|
746
|
+
figure.append(image);
|
|
747
|
+
if (shot.caption) figure.append(el("figcaption", void 0, shot.caption));
|
|
748
|
+
strip.append(figure);
|
|
749
|
+
}
|
|
750
|
+
card.append(strip);
|
|
751
|
+
}
|
|
752
|
+
const footer = el("footer", "mdb-landing__footer");
|
|
753
|
+
footer.append(format(strings.footer, { label }), " ");
|
|
754
|
+
const learnMore = el("a", void 0, strings.learnMore);
|
|
755
|
+
learnMore.href = "https://mindoodb.com";
|
|
756
|
+
learnMore.rel = "noopener";
|
|
757
|
+
footer.append(learnMore);
|
|
758
|
+
card.append(footer);
|
|
759
|
+
const container = options.container ?? document.body;
|
|
760
|
+
container.replaceChildren(root);
|
|
761
|
+
if (container === document.body) {
|
|
762
|
+
document.body.style.margin = "0";
|
|
763
|
+
}
|
|
764
|
+
return { definition, installUrl };
|
|
765
|
+
}
|
|
766
|
+
|
|
405
767
|
// src/client/havenStorageShim.ts
|
|
406
768
|
var DEFAULT_QUOTA_BYTES = 256 * 1024;
|
|
407
769
|
var DEFAULT_MAX_VALUE_BYTES = 64 * 1024;
|
|
@@ -729,11 +1091,11 @@ var MindooDBRichTextHandle = class {
|
|
|
729
1091
|
this.pollTimer = null;
|
|
730
1092
|
}
|
|
731
1093
|
}
|
|
732
|
-
reconcile(
|
|
1094
|
+
reconcile(document2, snapshot) {
|
|
733
1095
|
const previous = JSON.stringify(this.spansInternal);
|
|
734
1096
|
this.spansInternal = cloneSpans(snapshot.spans);
|
|
735
1097
|
this.baseMaterializedText = spansToPlainText(snapshot.spans);
|
|
736
|
-
this.heads = snapshot.heads ??
|
|
1098
|
+
this.heads = snapshot.heads ?? document2.heads ?? [];
|
|
737
1099
|
this.dirtyInternal = false;
|
|
738
1100
|
const changed = JSON.stringify(this.spansInternal) !== previous;
|
|
739
1101
|
if (changed) {
|
|
@@ -752,14 +1114,14 @@ var MindooDBRichTextHandle = class {
|
|
|
752
1114
|
if (this.dirtyInternal) {
|
|
753
1115
|
return false;
|
|
754
1116
|
}
|
|
755
|
-
const
|
|
1117
|
+
const document2 = await this.options.database.documents.get(
|
|
756
1118
|
this.options.document.id
|
|
757
1119
|
);
|
|
758
|
-
if (!
|
|
1120
|
+
if (!document2) {
|
|
759
1121
|
return false;
|
|
760
1122
|
}
|
|
761
1123
|
const snapshot = await this.refresh();
|
|
762
|
-
return this.reconcile(
|
|
1124
|
+
return this.reconcile(document2, snapshot);
|
|
763
1125
|
}
|
|
764
1126
|
async flush(updateSpansConfig) {
|
|
765
1127
|
if (!this.dirtyInternal) {
|
|
@@ -776,7 +1138,7 @@ var MindooDBRichTextHandle = class {
|
|
|
776
1138
|
const localSpansAtFlush = cloneSpans(this.spansInternal);
|
|
777
1139
|
const localTextAtFlush = spansToPlainText(localSpansAtFlush);
|
|
778
1140
|
const textChanged = localTextAtFlush !== this.baseMaterializedText;
|
|
779
|
-
const
|
|
1141
|
+
const document2 = textChanged ? await this.flushTextSteps(localTextAtFlush) : await this.options.database.documents.update(
|
|
780
1142
|
this.options.document.id,
|
|
781
1143
|
{
|
|
782
1144
|
richText: [{
|
|
@@ -794,13 +1156,13 @@ var MindooDBRichTextHandle = class {
|
|
|
794
1156
|
const reconciled = JSON.stringify(snapshot.spans) !== JSON.stringify(localSpansAtFlush);
|
|
795
1157
|
this.spansInternal = cloneSpans(snapshot.spans);
|
|
796
1158
|
this.baseMaterializedText = spansToPlainText(snapshot.spans);
|
|
797
|
-
this.heads = snapshot.heads ??
|
|
1159
|
+
this.heads = snapshot.heads ?? document2.heads ?? [];
|
|
798
1160
|
this.dirtyInternal = false;
|
|
799
1161
|
if (reconciled) {
|
|
800
1162
|
this.emitChange();
|
|
801
1163
|
}
|
|
802
1164
|
return {
|
|
803
|
-
document,
|
|
1165
|
+
document: document2,
|
|
804
1166
|
snapshot,
|
|
805
1167
|
reconciled
|
|
806
1168
|
};
|
|
@@ -938,11 +1300,11 @@ var MindooDBTextBuffer = class {
|
|
|
938
1300
|
* Returns whether the text value changed. Use this after an external refresh
|
|
939
1301
|
* or after receiving a document update through another app-level mechanism.
|
|
940
1302
|
*/
|
|
941
|
-
reconcile(
|
|
942
|
-
const nextValue = readTextAtPath(
|
|
1303
|
+
reconcile(document2) {
|
|
1304
|
+
const nextValue = readTextAtPath(document2.data, this.options.path);
|
|
943
1305
|
const changed = nextValue !== this.valueInternal;
|
|
944
1306
|
this.valueInternal = nextValue;
|
|
945
|
-
this.heads =
|
|
1307
|
+
this.heads = document2.heads ? [...document2.heads] : [];
|
|
946
1308
|
this.pendingEdits = [];
|
|
947
1309
|
return changed;
|
|
948
1310
|
}
|
|
@@ -964,20 +1326,20 @@ var MindooDBTextBuffer = class {
|
|
|
964
1326
|
};
|
|
965
1327
|
}
|
|
966
1328
|
const localValueAtFlush = this.valueInternal;
|
|
967
|
-
const
|
|
1329
|
+
const document2 = await this.options.database.documents.update(this.options.document.id, {
|
|
968
1330
|
text: [{
|
|
969
1331
|
path: this.options.path,
|
|
970
1332
|
baseHeads: this.heads,
|
|
971
1333
|
edits: this.pendingEdits.map((edit) => ({ ...edit }))
|
|
972
1334
|
}]
|
|
973
1335
|
});
|
|
974
|
-
const nextValue = readTextAtPath(
|
|
1336
|
+
const nextValue = readTextAtPath(document2.data, this.options.path);
|
|
975
1337
|
const reconciled = nextValue !== localValueAtFlush;
|
|
976
1338
|
this.valueInternal = nextValue;
|
|
977
|
-
this.heads =
|
|
1339
|
+
this.heads = document2.heads ? [...document2.heads] : [];
|
|
978
1340
|
this.pendingEdits = [];
|
|
979
1341
|
return {
|
|
980
|
-
document,
|
|
1342
|
+
document: document2,
|
|
981
1343
|
value: this.valueInternal,
|
|
982
1344
|
reconciled
|
|
983
1345
|
};
|
|
@@ -1031,10 +1393,29 @@ async function queryDocuments(db, filter, sortBy, options) {
|
|
|
1031
1393
|
const expression = typeof filter === "string" ? parseMindooDBFormulaBooleanExpression(filter) : filter;
|
|
1032
1394
|
return db.query({ filter: expression, sortBy }, options);
|
|
1033
1395
|
}
|
|
1396
|
+
|
|
1397
|
+
// src/wordChunks.ts
|
|
1398
|
+
function inheritDocumentEncryption(parent, input) {
|
|
1399
|
+
if (input.decryptionKeyId || input.recipients) return input;
|
|
1400
|
+
if (!parent.decryptionKeyId) return input;
|
|
1401
|
+
return { ...input, decryptionKeyId: parent.decryptionKeyId };
|
|
1402
|
+
}
|
|
1403
|
+
async function watchWordChunks(database, parentId, listener) {
|
|
1404
|
+
const subscription = await database.documents.liveQuery(
|
|
1405
|
+
{
|
|
1406
|
+
filter: `v.and(v.eq(v.field("type"), "wordChunk"), v.eq(v.field("parentId"), ${JSON.stringify(parentId)}))`
|
|
1407
|
+
},
|
|
1408
|
+
(result) => {
|
|
1409
|
+
listener(result.rows.map((row) => row.docId));
|
|
1410
|
+
}
|
|
1411
|
+
);
|
|
1412
|
+
return () => subscription.dispose();
|
|
1413
|
+
}
|
|
1034
1414
|
export {
|
|
1035
1415
|
DEFAULT_DRAG_LONG_PRESS_MS,
|
|
1036
1416
|
DEFAULT_DRAG_POINTER_THRESHOLD_PX,
|
|
1037
1417
|
DEFAULT_HAVEN_HOST_SHORTCUTS,
|
|
1418
|
+
DEFAULT_HAVEN_URL,
|
|
1038
1419
|
MINDOODB_APP_BUNDLE_ARCHIVE_FILE_NAME,
|
|
1039
1420
|
MINDOODB_APP_BUNDLE_MANIFEST_FILE_NAME,
|
|
1040
1421
|
MINDOODB_APP_BUNDLE_MANIFEST_FORMAT,
|
|
@@ -1045,19 +1426,26 @@ export {
|
|
|
1045
1426
|
MINDOODB_APP_DEFINITION_REGISTRATION_PERMISSIONS,
|
|
1046
1427
|
MINDOODB_APP_DEFINITION_VERSION,
|
|
1047
1428
|
MINDOODB_APP_HOSTING_QUERY_PARAM,
|
|
1429
|
+
MINDOODB_APP_LAUNCH_ID_QUERY_PARAM,
|
|
1430
|
+
MINDOODB_APP_LISTING_DESCRIPTION_MAX,
|
|
1431
|
+
MINDOODB_APP_LISTING_MAX_SCREENSHOTS,
|
|
1432
|
+
MINDOODB_APP_LISTING_SUMMARY_MAX,
|
|
1048
1433
|
MINDOODB_APP_MAX_DRAG_PAYLOAD_BYTES,
|
|
1049
1434
|
MINDOODB_APP_MAX_DRAG_PREVIEW_BYTES,
|
|
1050
1435
|
MINDOODB_APP_MAX_DRAG_PREVIEW_EDGE,
|
|
1051
1436
|
MINDOODB_APP_MAX_DRAG_TYPES,
|
|
1052
1437
|
MINDOODB_APP_SEAL_SIGNING_DOMAIN,
|
|
1438
|
+
MINDOODB_APP_VALUE_TAG,
|
|
1053
1439
|
MINDOODB_APP_WELL_KNOWN_DRAG_TYPES,
|
|
1054
1440
|
MINIMAL_DRAG_PREVIEW_PNG,
|
|
1055
1441
|
MindooDBAppDragInputError,
|
|
1442
|
+
MindooDBAppValue,
|
|
1056
1443
|
MindooDBRichTextHandle,
|
|
1057
1444
|
MindooDBTextBuffer,
|
|
1058
1445
|
abbreviateCanonicalName,
|
|
1059
1446
|
applyHostedDocumentOverscrollContain,
|
|
1060
1447
|
bindDragSource,
|
|
1448
|
+
buildHavenAppInstallUrl,
|
|
1061
1449
|
buildMindooDBAppBundleContentHashInput,
|
|
1062
1450
|
canPreviewAttachment,
|
|
1063
1451
|
createMindooDBAppBridge,
|
|
@@ -1065,10 +1453,12 @@ export {
|
|
|
1065
1453
|
createMindooDBTextBuffer,
|
|
1066
1454
|
createViewLanguage,
|
|
1067
1455
|
expandAbbreviatedName,
|
|
1456
|
+
inheritDocumentEncryption,
|
|
1068
1457
|
installHavenStorageShim,
|
|
1069
1458
|
installHostShortcutCapture,
|
|
1070
1459
|
isHostShortcutAction,
|
|
1071
1460
|
isHostedBundleRuntime,
|
|
1461
|
+
isLaunchedByHaven,
|
|
1072
1462
|
isMindooDBAppBundleHash,
|
|
1073
1463
|
isMindooDBAppDragType,
|
|
1074
1464
|
matchHostShortcut,
|
|
@@ -1078,10 +1468,16 @@ export {
|
|
|
1078
1468
|
normalizeMindooDBAppBundlePath,
|
|
1079
1469
|
queryDocuments,
|
|
1080
1470
|
readMindooDBAppHostingMode,
|
|
1471
|
+
readMindooDBAppLaunchId,
|
|
1081
1472
|
releaseMindooDBAppBridgeSessions,
|
|
1473
|
+
renderHavenAppLandingPage,
|
|
1474
|
+
resolveCurrentHavenAppUrl,
|
|
1082
1475
|
resolveMindooDBAppDefinitionUrl,
|
|
1476
|
+
resolveMindooDBAppListingAssetUrl,
|
|
1477
|
+
resolveMindooDBAppLocalizedText,
|
|
1083
1478
|
sanitizeHostShortcutBindings,
|
|
1084
1479
|
snapshotElementToPng,
|
|
1085
1480
|
validateMindooDBAppBundleManifest,
|
|
1086
|
-
validateMindooDBAppDefinition
|
|
1481
|
+
validateMindooDBAppDefinition,
|
|
1482
|
+
watchWordChunks
|
|
1087
1483
|
};
|