@translation-cms/sync 1.2.28 → 1.2.30
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/dist/api.d.ts +105 -0
- package/dist/api.d.ts.map +1 -0
- package/dist/api.js +208 -0
- package/dist/api.js.map +1 -0
- package/dist/bin.d.ts +25 -0
- package/dist/bin.d.ts.map +1 -0
- package/dist/bin.js +208 -0
- package/dist/bin.js.map +1 -0
- package/dist/commands/init.d.ts +10 -0
- package/dist/commands/init.d.ts.map +1 -0
- package/dist/commands/init.js +107 -0
- package/dist/commands/init.js.map +1 -0
- package/dist/commands/pull.d.ts +10 -0
- package/dist/commands/pull.d.ts.map +1 -0
- package/dist/commands/pull.js +35 -0
- package/dist/commands/pull.js.map +1 -0
- package/dist/commands/status.d.ts +15 -0
- package/dist/commands/status.d.ts.map +1 -0
- package/dist/commands/status.js +62 -0
- package/dist/commands/status.js.map +1 -0
- package/dist/commands/sync.d.ts +24 -0
- package/dist/commands/sync.d.ts.map +1 -0
- package/dist/commands/sync.js +131 -0
- package/dist/commands/sync.js.map +1 -0
- package/dist/commands/watch.d.ts +18 -0
- package/dist/commands/watch.d.ts.map +1 -0
- package/dist/commands/watch.js +71 -0
- package/dist/commands/watch.js.map +1 -0
- package/dist/config/config-internals/args.d.ts +11 -0
- package/dist/config/config-internals/args.d.ts.map +1 -0
- package/dist/config/config-internals/args.js +22 -0
- package/dist/config/config-internals/args.js.map +1 -0
- package/dist/config/config-internals/env.d.ts +10 -0
- package/dist/config/config-internals/env.d.ts.map +1 -0
- package/dist/config/config-internals/env.js +35 -0
- package/dist/config/config-internals/env.js.map +1 -0
- package/dist/config/config-internals/file.d.ts +11 -0
- package/dist/config/config-internals/file.d.ts.map +1 -0
- package/dist/config/config-internals/file.js +28 -0
- package/dist/config/config-internals/file.js.map +1 -0
- package/dist/config/config-internals/resolve.d.ts +21 -0
- package/dist/config/config-internals/resolve.d.ts.map +1 -0
- package/dist/config/config-internals/resolve.js +73 -0
- package/dist/config/config-internals/resolve.js.map +1 -0
- package/dist/config/config-internals/root.d.ts +9 -0
- package/dist/config/config-internals/root.d.ts.map +1 -0
- package/dist/config/config-internals/root.js +22 -0
- package/dist/config/config-internals/root.js.map +1 -0
- package/dist/config/config-internals/types.d.ts +91 -0
- package/dist/config/config-internals/types.d.ts.map +1 -0
- package/dist/config/config-internals/types.js +9 -0
- package/dist/config/config-internals/types.js.map +1 -0
- package/dist/config/resolve-config.d.ts +11 -0
- package/dist/config/resolve-config.d.ts.map +1 -0
- package/dist/config/resolve-config.js +10 -0
- package/dist/config/resolve-config.js.map +1 -0
- package/dist/core/api-internals/helpers.d.ts +9 -0
- package/dist/core/api-internals/helpers.d.ts.map +1 -0
- package/dist/core/api-internals/helpers.js +14 -0
- package/dist/core/api-internals/helpers.js.map +1 -0
- package/dist/core/api-internals/pull.d.ts +19 -0
- package/dist/core/api-internals/pull.d.ts.map +1 -0
- package/dist/core/api-internals/pull.js +269 -0
- package/dist/core/api-internals/pull.js.map +1 -0
- package/dist/core/api-internals/route-config.d.ts +13 -0
- package/dist/core/api-internals/route-config.d.ts.map +1 -0
- package/dist/core/api-internals/route-config.js +34 -0
- package/dist/core/api-internals/route-config.js.map +1 -0
- package/dist/core/api-internals/sync.d.ts +19 -0
- package/dist/core/api-internals/sync.d.ts.map +1 -0
- package/dist/core/api-internals/sync.js +139 -0
- package/dist/core/api-internals/sync.js.map +1 -0
- package/dist/core/api-internals/types.d.ts +33 -0
- package/dist/core/api-internals/types.d.ts.map +1 -0
- package/dist/core/api-internals/types.js +5 -0
- package/dist/core/api-internals/types.js.map +1 -0
- package/dist/core/api.d.ts +11 -0
- package/dist/core/api.d.ts.map +1 -0
- package/dist/core/api.js +11 -0
- package/dist/core/api.js.map +1 -0
- package/dist/core/cache-internals/format.d.ts +20 -0
- package/dist/core/cache-internals/format.d.ts.map +1 -0
- package/dist/core/cache-internals/format.js +33 -0
- package/dist/core/cache-internals/format.js.map +1 -0
- package/dist/core/cache-internals/params.d.ts +33 -0
- package/dist/core/cache-internals/params.d.ts.map +1 -0
- package/dist/core/cache-internals/params.js +155 -0
- package/dist/core/cache-internals/params.js.map +1 -0
- package/dist/core/cache-internals/pull.d.ts +17 -0
- package/dist/core/cache-internals/pull.d.ts.map +1 -0
- package/dist/core/cache-internals/pull.js +34 -0
- package/dist/core/cache-internals/pull.js.map +1 -0
- package/dist/core/cache-internals/sync.d.ts +29 -0
- package/dist/core/cache-internals/sync.d.ts.map +1 -0
- package/dist/core/cache-internals/sync.js +104 -0
- package/dist/core/cache-internals/sync.js.map +1 -0
- package/dist/core/cache-internals/types.d.ts +48 -0
- package/dist/core/cache-internals/types.d.ts.map +1 -0
- package/dist/core/cache-internals/types.js +2 -0
- package/dist/core/cache-internals/types.js.map +1 -0
- package/dist/core/cache.d.ts +13 -0
- package/dist/core/cache.d.ts.map +1 -0
- package/dist/core/cache.js +33 -0
- package/dist/core/cache.js.map +1 -0
- package/dist/core/scanner-internals/ast.d.ts +40 -0
- package/dist/core/scanner-internals/ast.d.ts.map +1 -0
- package/dist/core/scanner-internals/ast.js +100 -0
- package/dist/core/scanner-internals/ast.js.map +1 -0
- package/dist/core/scanner-internals/file-walker.d.ts +10 -0
- package/dist/core/scanner-internals/file-walker.d.ts.map +1 -0
- package/dist/core/scanner-internals/file-walker.js +23 -0
- package/dist/core/scanner-internals/file-walker.js.map +1 -0
- package/dist/core/scanner-internals/import-resolver.d.ts +21 -0
- package/dist/core/scanner-internals/import-resolver.d.ts.map +1 -0
- package/dist/core/scanner-internals/import-resolver.js +119 -0
- package/dist/core/scanner-internals/import-resolver.js.map +1 -0
- package/dist/core/scanner-internals/key-extractor.d.ts +20 -0
- package/dist/core/scanner-internals/key-extractor.d.ts.map +1 -0
- package/dist/core/scanner-internals/key-extractor.js +274 -0
- package/dist/core/scanner-internals/key-extractor.js.map +1 -0
- package/dist/core/scanner-internals/nav-config-scanner.d.ts +30 -0
- package/dist/core/scanner-internals/nav-config-scanner.d.ts.map +1 -0
- package/dist/core/scanner-internals/nav-config-scanner.js +170 -0
- package/dist/core/scanner-internals/nav-config-scanner.js.map +1 -0
- package/dist/core/scanner-internals/route-detector.d.ts +27 -0
- package/dist/core/scanner-internals/route-detector.d.ts.map +1 -0
- package/dist/core/scanner-internals/route-detector.js +78 -0
- package/dist/core/scanner-internals/route-detector.js.map +1 -0
- package/dist/core/scanner-internals/types.d.ts +60 -0
- package/dist/core/scanner-internals/types.d.ts.map +1 -0
- package/dist/core/scanner-internals/types.js +29 -0
- package/dist/core/scanner-internals/types.js.map +1 -0
- package/dist/core/scanner.d.ts +21 -0
- package/dist/core/scanner.d.ts.map +1 -0
- package/dist/core/scanner.js +162 -0
- package/dist/core/scanner.js.map +1 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +3 -0
- package/dist/index.js.map +1 -0
- package/dist/next.d.ts +33 -0
- package/dist/next.d.ts.map +1 -0
- package/dist/next.js +110 -0
- package/dist/next.js.map +1 -0
- package/dist/preview/index.d.ts +39 -0
- package/dist/preview/index.d.ts.map +1 -0
- package/dist/preview/index.js +123 -0
- package/dist/preview/index.js.map +1 -0
- package/dist/preview/internals/highlight.d.ts +31 -0
- package/dist/preview/internals/highlight.d.ts.map +1 -0
- package/dist/preview/internals/highlight.js +184 -0
- package/dist/preview/internals/highlight.js.map +1 -0
- package/dist/preview/internals/interactions.d.ts +15 -0
- package/dist/preview/internals/interactions.d.ts.map +1 -0
- package/dist/preview/internals/interactions.js +38 -0
- package/dist/preview/internals/interactions.js.map +1 -0
- package/dist/preview/internals/interactive.d.ts +14 -0
- package/dist/preview/internals/interactive.d.ts.map +1 -0
- package/dist/preview/internals/interactive.js +122 -0
- package/dist/preview/internals/interactive.js.map +1 -0
- package/dist/preview/internals/locales.d.ts +9 -0
- package/dist/preview/internals/locales.d.ts.map +1 -0
- package/dist/preview/internals/locales.js +24 -0
- package/dist/preview/internals/locales.js.map +1 -0
- package/dist/preview/internals/state.d.ts +37 -0
- package/dist/preview/internals/state.d.ts.map +1 -0
- package/dist/preview/internals/state.js +74 -0
- package/dist/preview/internals/state.js.map +1 -0
- package/dist/preview/internals/styles.d.ts +8 -0
- package/dist/preview/internals/styles.d.ts.map +1 -0
- package/dist/preview/internals/styles.js +28 -0
- package/dist/preview/internals/styles.js.map +1 -0
- package/dist/preview/internals/types.d.ts +74 -0
- package/dist/preview/internals/types.d.ts.map +1 -0
- package/dist/preview/internals/types.js +5 -0
- package/dist/preview/internals/types.js.map +1 -0
- package/dist/preview/internals/ui-indicators.d.ts +15 -0
- package/dist/preview/internals/ui-indicators.d.ts.map +1 -0
- package/dist/preview/internals/ui-indicators.js +92 -0
- package/dist/preview/internals/ui-indicators.js.map +1 -0
- package/dist/scaffold/index.d.ts +3 -0
- package/dist/scaffold/index.d.ts.map +1 -0
- package/dist/scaffold/index.js +3 -0
- package/dist/scaffold/index.js.map +1 -0
- package/dist/scaffold/intenrals/scaffold.d.ts +24 -0
- package/dist/scaffold/intenrals/scaffold.d.ts.map +1 -0
- package/dist/scaffold/intenrals/scaffold.js +53 -0
- package/dist/scaffold/intenrals/scaffold.js.map +1 -0
- package/dist/scaffold/intenrals/templates.d.ts +10 -0
- package/dist/scaffold/intenrals/templates.d.ts.map +1 -0
- package/dist/scaffold/intenrals/templates.js +344 -0
- package/dist/scaffold/intenrals/templates.js.map +1 -0
- package/dist/scaffold/intenrals/types.d.ts +14 -0
- package/dist/scaffold/intenrals/types.d.ts.map +1 -0
- package/dist/scaffold/intenrals/types.js +5 -0
- package/dist/scaffold/intenrals/types.js.map +1 -0
- package/package.json +3 -3
package/dist/next.js
ADDED
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Next.js Plugin for automatic translation sync
|
|
3
|
+
*
|
|
4
|
+
* Usage in next.config.ts:
|
|
5
|
+
* import withTranslationsCMS from '@translation-cms/sync/next';
|
|
6
|
+
*
|
|
7
|
+
* export default withTranslationsCMS(nextConfig, {
|
|
8
|
+
* pullOnBuild: true, // default
|
|
9
|
+
* pullOnDev: true, // default
|
|
10
|
+
* });
|
|
11
|
+
*/
|
|
12
|
+
import path from 'path';
|
|
13
|
+
import { runPull } from './commands/pull.js';
|
|
14
|
+
import { resolveConfig, findProjectRoot, loadConfigFile, } from './config/resolve-config.js';
|
|
15
|
+
import { getLastPulledTime } from './core/cache.js';
|
|
16
|
+
import { pullTranslations } from './core/api.js';
|
|
17
|
+
/**
|
|
18
|
+
* Higher-order function that wraps your Next.js config with automatic translation syncing.
|
|
19
|
+
*
|
|
20
|
+
* @param userConfig - Your existing next.config.ts configuration
|
|
21
|
+
* @param pluginOptions - Plugin-specific options
|
|
22
|
+
* @returns Enhanced Next.js configuration with sync hooks
|
|
23
|
+
*/
|
|
24
|
+
export function withTranslationsCMS(userConfig, pluginOptions = {}) {
|
|
25
|
+
const { pullOnBuild = true, pullOnDev = true, watchInterval = 10_000, } = pluginOptions;
|
|
26
|
+
let initialized = false;
|
|
27
|
+
// Initialize and run pull if needed
|
|
28
|
+
const initTranslations = async () => {
|
|
29
|
+
if (initialized)
|
|
30
|
+
return;
|
|
31
|
+
initialized = true;
|
|
32
|
+
try {
|
|
33
|
+
const root = findProjectRoot();
|
|
34
|
+
const fileConfig = loadConfigFile(root);
|
|
35
|
+
const config = resolveConfig(root, fileConfig);
|
|
36
|
+
// Only pull if we have valid configuration
|
|
37
|
+
if (config.cmsUrl && config.projectId && config.apiKey) {
|
|
38
|
+
const isDev = process.env.NODE_ENV === 'development';
|
|
39
|
+
const shouldPull = (isDev && pullOnDev) || (!isDev && pullOnBuild);
|
|
40
|
+
if (shouldPull) {
|
|
41
|
+
await runPull(root, config, fileConfig, []);
|
|
42
|
+
}
|
|
43
|
+
// In dev mode, start a polling loop to auto-pull when the CMS publishes
|
|
44
|
+
if (isDev && watchInterval > 0) {
|
|
45
|
+
const outputDir = fileConfig.outputDir
|
|
46
|
+
? path.resolve(root, fileConfig.outputDir)
|
|
47
|
+
: path.join(root, 'public', 'locales');
|
|
48
|
+
startWatcher(config, outputDir, watchInterval);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
catch (error) {
|
|
53
|
+
// Log error but don't fail the build
|
|
54
|
+
// eslint-disable-next-line no-console
|
|
55
|
+
console.warn('Translation sync failed (continuing with build):', error instanceof Error ? error.message : String(error));
|
|
56
|
+
}
|
|
57
|
+
};
|
|
58
|
+
return {
|
|
59
|
+
...userConfig,
|
|
60
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
61
|
+
webpack: (config, context) => {
|
|
62
|
+
// Run sync on webpack initialization (first build/dev startup)
|
|
63
|
+
if (context.isServer && !initialized) {
|
|
64
|
+
// eslint-disable-next-line no-console
|
|
65
|
+
initTranslations().catch(console.error);
|
|
66
|
+
}
|
|
67
|
+
// Call any existing webpack config if provided
|
|
68
|
+
if (typeof userConfig?.webpack === 'function') {
|
|
69
|
+
return userConfig.webpack(config, context);
|
|
70
|
+
}
|
|
71
|
+
return config;
|
|
72
|
+
},
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* Poll the CMS status endpoint every `intervalMs` ms.
|
|
77
|
+
* When a publish newer than the last local pull is detected, re-pulls translations.
|
|
78
|
+
* Errors are silently swallowed so a temporarily unreachable CMS never crashes dev.
|
|
79
|
+
*/
|
|
80
|
+
function startWatcher(config, outputDir, intervalMs) {
|
|
81
|
+
const statusUrl = `${config.cmsUrl.replace(/\/$/, '')}/api/sync/${config.projectId}/status`;
|
|
82
|
+
const poll = async () => {
|
|
83
|
+
try {
|
|
84
|
+
const res = await fetch(statusUrl, {
|
|
85
|
+
headers: { 'x-api-key': config.apiKey },
|
|
86
|
+
signal: AbortSignal.timeout(5000),
|
|
87
|
+
});
|
|
88
|
+
if (!res.ok)
|
|
89
|
+
return;
|
|
90
|
+
const { lastPublishedAt } = (await res.json());
|
|
91
|
+
if (!lastPublishedAt)
|
|
92
|
+
return;
|
|
93
|
+
const lastPulled = getLastPulledTime(outputDir);
|
|
94
|
+
const publishedMs = new Date(lastPublishedAt).getTime();
|
|
95
|
+
if (lastPulled === null || publishedMs > lastPulled) {
|
|
96
|
+
// eslint-disable-next-line no-console
|
|
97
|
+
console.log('[CMS] New translations detected — pulling...');
|
|
98
|
+
await pullTranslations(config, { outputDir, force: true });
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
catch {
|
|
102
|
+
// Silently ignore — CMS may be unreachable or dev server starting up
|
|
103
|
+
}
|
|
104
|
+
};
|
|
105
|
+
setInterval(poll, intervalMs);
|
|
106
|
+
// eslint-disable-next-line no-console
|
|
107
|
+
console.log(`[CMS] Watching for translation updates every ${intervalMs / 1000}s`);
|
|
108
|
+
}
|
|
109
|
+
export default withTranslationsCMS;
|
|
110
|
+
//# sourceMappingURL=next.js.map
|
package/dist/next.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"next.js","sourceRoot":"","sources":["../src/next.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,OAAO,IAAI,MAAM,MAAM,CAAC;AAExB,OAAO,EAAE,OAAO,EAAE,MAAM,oBAAoB,CAAC;AAC7C,OAAO,EACH,aAAa,EACb,eAAe,EACf,cAAc,GAEjB,MAAM,4BAA4B,CAAC;AACpC,OAAO,EAAE,iBAAiB,EAAE,MAAM,iBAAiB,CAAC;AACpD,OAAO,EAAE,gBAAgB,EAAE,MAAM,eAAe,CAAC;AAcjD;;;;;;GAMG;AACH,MAAM,UAAU,mBAAmB,CAC/B,UAAkC,EAClC,gBAA8C,EAAE;IAEhD,MAAM,EACF,WAAW,GAAG,IAAI,EAClB,SAAS,GAAG,IAAI,EAChB,aAAa,GAAG,MAAM,GACzB,GAAG,aAAa,CAAC;IAElB,IAAI,WAAW,GAAG,KAAK,CAAC;IAExB,oCAAoC;IACpC,MAAM,gBAAgB,GAAG,KAAK,IAAI,EAAE;QAChC,IAAI,WAAW;YAAE,OAAO;QACxB,WAAW,GAAG,IAAI,CAAC;QAEnB,IAAI,CAAC;YACD,MAAM,IAAI,GAAG,eAAe,EAAE,CAAC;YAC/B,MAAM,UAAU,GAAG,cAAc,CAAC,IAAI,CAAC,CAAC;YACxC,MAAM,MAAM,GAAG,aAAa,CAAC,IAAI,EAAE,UAAU,CAAC,CAAC;YAE/C,2CAA2C;YAC3C,IAAI,MAAM,CAAC,MAAM,IAAI,MAAM,CAAC,SAAS,IAAI,MAAM,CAAC,MAAM,EAAE,CAAC;gBACrD,MAAM,KAAK,GAAG,OAAO,CAAC,GAAG,CAAC,QAAQ,KAAK,aAAa,CAAC;gBACrD,MAAM,UAAU,GACZ,CAAC,KAAK,IAAI,SAAS,CAAC,IAAI,CAAC,CAAC,KAAK,IAAI,WAAW,CAAC,CAAC;gBAEpD,IAAI,UAAU,EAAE,CAAC;oBACb,MAAM,OAAO,CAAC,IAAI,EAAE,MAAM,EAAE,UAAU,EAAE,EAAE,CAAC,CAAC;gBAChD,CAAC;gBAED,wEAAwE;gBACxE,IAAI,KAAK,IAAI,aAAa,GAAG,CAAC,EAAE,CAAC;oBAC7B,MAAM,SAAS,GAAG,UAAU,CAAC,SAAS;wBAClC,CAAC,CAAC,IAAI,CAAC,OAAO,CAAC,IAAI,EAAE,UAAU,CAAC,SAAS,CAAC;wBAC1C,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE,QAAQ,EAAE,SAAS,CAAC,CAAC;oBAC3C,YAAY,CAAC,MAAM,EAAE,SAAS,EAAE,aAAa,CAAC,CAAC;gBACnD,CAAC;YACL,CAAC;QACL,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACb,qCAAqC;YACrC,sCAAsC;YACtC,OAAO,CAAC,IAAI,CACR,kDAAkD,EAClD,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CACzD,CAAC;QACN,CAAC;IACL,CAAC,CAAC;IAEF,OAAO;QACH,GAAG,UAAU;QACb,8DAA8D;QAC9D,OAAO,EAAE,CAAC,MAAW,EAAE,OAAY,EAAE,EAAE;YACnC,+DAA+D;YAC/D,IAAI,OAAO,CAAC,QAAQ,IAAI,CAAC,WAAW,EAAE,CAAC;gBACnC,sCAAsC;gBACtC,gBAAgB,EAAE,CAAC,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC;YAC5C,CAAC;YAED,+CAA+C;YAC/C,IAAI,OAAO,UAAU,EAAE,OAAO,KAAK,UAAU,EAAE,CAAC;gBAC5C,OAAO,UAAU,CAAC,OAAO,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;YAC/C,CAAC;YAED,OAAO,MAAM,CAAC;QAClB,CAAC;KACJ,CAAC;AACN,CAAC;AAED;;;;GAIG;AACH,SAAS,YAAY,CACjB,MAAc,EACd,SAAiB,EACjB,UAAkB;IAElB,MAAM,SAAS,GAAG,GAAG,MAAM,CAAC,MAAM,CAAC,OAAO,CAAC,KAAK,EAAE,EAAE,CAAC,aAAa,MAAM,CAAC,SAAS,SAAS,CAAC;IAE5F,MAAM,IAAI,GAAG,KAAK,IAAI,EAAE;QACpB,IAAI,CAAC;YACD,MAAM,GAAG,GAAG,MAAM,KAAK,CAAC,SAAS,EAAE;gBAC/B,OAAO,EAAE,EAAE,WAAW,EAAE,MAAM,CAAC,MAAM,EAAE;gBACvC,MAAM,EAAE,WAAW,CAAC,OAAO,CAAC,IAAI,CAAC;aACpC,CAAC,CAAC;YAEH,IAAI,CAAC,GAAG,CAAC,EAAE;gBAAE,OAAO;YAEpB,MAAM,EAAE,eAAe,EAAE,GAAG,CAAC,MAAM,GAAG,CAAC,IAAI,EAAE,CAE5C,CAAC;YACF,IAAI,CAAC,eAAe;gBAAE,OAAO;YAE7B,MAAM,UAAU,GAAG,iBAAiB,CAAC,SAAS,CAAC,CAAC;YAChD,MAAM,WAAW,GAAG,IAAI,IAAI,CAAC,eAAe,CAAC,CAAC,OAAO,EAAE,CAAC;YAExD,IAAI,UAAU,KAAK,IAAI,IAAI,WAAW,GAAG,UAAU,EAAE,CAAC;gBAClD,sCAAsC;gBACtC,OAAO,CAAC,GAAG,CAAC,8CAA8C,CAAC,CAAC;gBAC5D,MAAM,gBAAgB,CAAC,MAAM,EAAE,EAAE,SAAS,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC;YAC/D,CAAC;QACL,CAAC;QAAC,MAAM,CAAC;YACL,qEAAqE;QACzE,CAAC;IACL,CAAC,CAAC;IAEF,WAAW,CAAC,IAAI,EAAE,UAAU,CAAC,CAAC;IAC9B,sCAAsC;IACtC,OAAO,CAAC,GAAG,CACP,gDAAgD,UAAU,GAAG,IAAI,GAAG,CACvE,CAAC;AACN,CAAC;AAED,eAAe,mBAAmB,CAAC"}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Translation CMS — Preview Listener
|
|
3
|
+
*
|
|
4
|
+
* Add this to your client app to enable live in-context preview of translations
|
|
5
|
+
* from the CMS. No per-component setup required.
|
|
6
|
+
*
|
|
7
|
+
* Usage in Next.js root layout:
|
|
8
|
+
* ```tsx
|
|
9
|
+
* 'use client';
|
|
10
|
+
* import { useEffect } from 'react';
|
|
11
|
+
* import { initPreviewListener, cleanupPreviewListener } from '@translation-cms/sync/preview';
|
|
12
|
+
*
|
|
13
|
+
* export function CMSPreview() {
|
|
14
|
+
* useEffect(() => {
|
|
15
|
+
* initPreviewListener({ onLocaleSwitch: (locale) => i18n.changeLanguage(locale) });
|
|
16
|
+
* return () => cleanupPreviewListener();
|
|
17
|
+
* }, []);
|
|
18
|
+
* return null;
|
|
19
|
+
* }
|
|
20
|
+
* ```
|
|
21
|
+
*
|
|
22
|
+
* Precise highlighting (recommended):
|
|
23
|
+
* Add `data-cms-key="namespace:key"` to elements that render a translation.
|
|
24
|
+
* The listener will target those elements directly, bypassing text-search heuristics.
|
|
25
|
+
* ```tsx
|
|
26
|
+
* <h1 data-cms-key="blog:post.title">{t('blog:post.title')}</h1>
|
|
27
|
+
* ```
|
|
28
|
+
*/
|
|
29
|
+
export type { PreviewListenerOptions } from './internals/types.js';
|
|
30
|
+
export { updateStateDisplay } from './internals/ui-indicators.js';
|
|
31
|
+
import type { PreviewListenerOptions } from './internals/types.js';
|
|
32
|
+
/**
|
|
33
|
+
* Initialize the preview listener.
|
|
34
|
+
* Safe to call multiple times — only initialises once.
|
|
35
|
+
*/
|
|
36
|
+
export declare function initPreviewListener(options?: PreviewListenerOptions): void;
|
|
37
|
+
/** Remove the preview listener and clear all highlights. */
|
|
38
|
+
export declare function cleanupPreviewListener(): void;
|
|
39
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/preview/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AAGH,YAAY,EAAE,sBAAsB,EAAE,MAAM,sBAAsB,CAAC;AACnE,OAAO,EAAE,kBAAkB,EAAE,MAAM,8BAA8B,CAAC;AAuBlE,OAAO,KAAK,EAIR,sBAAsB,EACzB,MAAM,sBAAsB,CAAC;AAM9B;;;GAGG;AACH,wBAAgB,mBAAmB,CAAC,OAAO,CAAC,EAAE,sBAAsB,GAAG,IAAI,CAoC1E;AAED,4DAA4D;AAC5D,wBAAgB,sBAAsB,IAAI,IAAI,CAS7C"}
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Translation CMS — Preview Listener
|
|
3
|
+
*
|
|
4
|
+
* Add this to your client app to enable live in-context preview of translations
|
|
5
|
+
* from the CMS. No per-component setup required.
|
|
6
|
+
*
|
|
7
|
+
* Usage in Next.js root layout:
|
|
8
|
+
* ```tsx
|
|
9
|
+
* 'use client';
|
|
10
|
+
* import { useEffect } from 'react';
|
|
11
|
+
* import { initPreviewListener, cleanupPreviewListener } from '@translation-cms/sync/preview';
|
|
12
|
+
*
|
|
13
|
+
* export function CMSPreview() {
|
|
14
|
+
* useEffect(() => {
|
|
15
|
+
* initPreviewListener({ onLocaleSwitch: (locale) => i18n.changeLanguage(locale) });
|
|
16
|
+
* return () => cleanupPreviewListener();
|
|
17
|
+
* }, []);
|
|
18
|
+
* return null;
|
|
19
|
+
* }
|
|
20
|
+
* ```
|
|
21
|
+
*
|
|
22
|
+
* Precise highlighting (recommended):
|
|
23
|
+
* Add `data-cms-key="namespace:key"` to elements that render a translation.
|
|
24
|
+
* The listener will target those elements directly, bypassing text-search heuristics.
|
|
25
|
+
* ```tsx
|
|
26
|
+
* <h1 data-cms-key="blog:post.title">{t('blog:post.title')}</h1>
|
|
27
|
+
* ```
|
|
28
|
+
*/
|
|
29
|
+
export { updateStateDisplay } from './internals/ui-indicators.js';
|
|
30
|
+
// Internal imports
|
|
31
|
+
import * as state from './internals/state.js';
|
|
32
|
+
import { highlightTranslationKey, clearHighlights, updateLiveText, } from './internals/highlight.js';
|
|
33
|
+
import { handleLocaleSwitch } from './internals/locales.js';
|
|
34
|
+
import { injectInteractionBlocker, removeInteractionBlocker, } from './internals/interactions.js';
|
|
35
|
+
import { injectStyles } from './internals/styles.js';
|
|
36
|
+
import { initInteractiveMode, cleanupInteractiveMode, } from './internals/interactive.js';
|
|
37
|
+
import { injectUIIndicators, removeUIIndicators, } from './internals/ui-indicators.js';
|
|
38
|
+
// ---------------------------------------------------------------------------
|
|
39
|
+
// Public functions
|
|
40
|
+
// ---------------------------------------------------------------------------
|
|
41
|
+
/**
|
|
42
|
+
* Initialize the preview listener.
|
|
43
|
+
* Safe to call multiple times — only initialises once.
|
|
44
|
+
*/
|
|
45
|
+
export function initPreviewListener(options) {
|
|
46
|
+
if (state.isInitialized)
|
|
47
|
+
return;
|
|
48
|
+
const isDev = typeof window !== 'undefined' &&
|
|
49
|
+
(window.location.hostname === 'localhost' ||
|
|
50
|
+
window.location.hostname === '127.0.0.1' ||
|
|
51
|
+
window.location.search.includes('preview=true'));
|
|
52
|
+
if (!isDev) {
|
|
53
|
+
console.log('[CMS Preview] Not in development mode, skipping...');
|
|
54
|
+
return;
|
|
55
|
+
}
|
|
56
|
+
state.initializeState(options ?? {});
|
|
57
|
+
state.setIsInitialized(true);
|
|
58
|
+
window.addEventListener('message', handleMessage);
|
|
59
|
+
injectStyles();
|
|
60
|
+
// Only inject the interaction blocker when NOT in interactive mode
|
|
61
|
+
// and when running inside an iframe (i.e. the CMS preview context)
|
|
62
|
+
if (window.top !== window && !options?.interactiveMode) {
|
|
63
|
+
injectInteractionBlocker();
|
|
64
|
+
}
|
|
65
|
+
// Initialize interactive mode if enabled
|
|
66
|
+
if (options?.interactiveMode) {
|
|
67
|
+
initInteractiveMode();
|
|
68
|
+
injectUIIndicators();
|
|
69
|
+
}
|
|
70
|
+
console.log(`%c[CMS Preview] Ready ${options?.interactiveMode ? '(interactive mode)' : ''}`, 'color: #10b981; font-weight: bold;');
|
|
71
|
+
}
|
|
72
|
+
/** Remove the preview listener and clear all highlights. */
|
|
73
|
+
export function cleanupPreviewListener() {
|
|
74
|
+
if (!state.isInitialized)
|
|
75
|
+
return;
|
|
76
|
+
window.removeEventListener('message', handleMessage);
|
|
77
|
+
removeInteractionBlocker();
|
|
78
|
+
cleanupInteractiveMode();
|
|
79
|
+
removeUIIndicators();
|
|
80
|
+
clearHighlights();
|
|
81
|
+
state.resetState();
|
|
82
|
+
console.log('[CMS Preview] Listener cleaned up');
|
|
83
|
+
}
|
|
84
|
+
// ---------------------------------------------------------------------------
|
|
85
|
+
// Message handler
|
|
86
|
+
// ---------------------------------------------------------------------------
|
|
87
|
+
function handleMessage(event) {
|
|
88
|
+
const { type } = (event.data ?? {});
|
|
89
|
+
if (type === 'CMS_HIGHLIGHT_KEY') {
|
|
90
|
+
const { key, value, locale } = event.data;
|
|
91
|
+
console.log(`[CMS Preview] Highlighting: ${key} (${locale}) = "${value}"`);
|
|
92
|
+
// Only switch locale when it actually changed. Switching unconditionally
|
|
93
|
+
// triggers a re-render in the consuming app, which replaces DOM nodes
|
|
94
|
+
// and immediately removes the highlight class we just applied.
|
|
95
|
+
if (locale && locale !== state.activeLocale) {
|
|
96
|
+
state.setActiveLocale(locale);
|
|
97
|
+
handleLocaleSwitch(locale);
|
|
98
|
+
}
|
|
99
|
+
state.setCurrentLiveValue(null);
|
|
100
|
+
state.setPendingHighlight({ key, value });
|
|
101
|
+
highlightTranslationKey(value, key);
|
|
102
|
+
// If no elements were found (e.g. dialog still animating open), schedule
|
|
103
|
+
// a backup retry after the animation should have settled.
|
|
104
|
+
if (state.highlightedElements.length === 0) {
|
|
105
|
+
setTimeout(() => {
|
|
106
|
+
if (state.pendingHighlight?.key === key) {
|
|
107
|
+
console.log(`[CMS Preview] Retrying highlight for: ${key}`);
|
|
108
|
+
highlightTranslationKey(value, key);
|
|
109
|
+
}
|
|
110
|
+
}, 600);
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
else if (type === 'CMS_LIVE_UPDATE') {
|
|
114
|
+
const { oldValue, newValue } = event.data;
|
|
115
|
+
updateLiveText(oldValue, newValue);
|
|
116
|
+
}
|
|
117
|
+
else if (type === 'CMS_SWITCH_LOCALE') {
|
|
118
|
+
const { locale } = event.data;
|
|
119
|
+
state.setActiveLocale(locale);
|
|
120
|
+
handleLocaleSwitch(locale);
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/preview/index.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Translation highlighting logic.
|
|
3
|
+
* Handles searching for and highlighting elements that display translations.
|
|
4
|
+
*/
|
|
5
|
+
/**
|
|
6
|
+
* Find and highlight all DOM elements that display the given translation.
|
|
7
|
+
*
|
|
8
|
+
* Search order (first strategy that yields a match wins):
|
|
9
|
+
* 0. `data-cms-key` attribute — precise, skips all heuristics
|
|
10
|
+
* 1. Exact text node match
|
|
11
|
+
* 2. Element content match — handles text split across siblings
|
|
12
|
+
* 3. Normalized text node — strips `{{interpolation}}` placeholders
|
|
13
|
+
* 4. Normalized element match
|
|
14
|
+
* 5. Key fallback — used when `value` is empty (i18next renders the key)
|
|
15
|
+
*/
|
|
16
|
+
export declare function highlightTranslationKey(value: string, key: string): void;
|
|
17
|
+
/**
|
|
18
|
+
* Remove the highlight class from all currently highlighted elements and
|
|
19
|
+
* reset the associated state (element list + search value).
|
|
20
|
+
*/
|
|
21
|
+
export declare function clearHighlights(): void;
|
|
22
|
+
/**
|
|
23
|
+
* Patch the live text of highlighted elements as the user types a new
|
|
24
|
+
* translation value in the CMS panel.
|
|
25
|
+
*
|
|
26
|
+
* Searches each highlighted element's text nodes for the old value (or the
|
|
27
|
+
* last live value if the user is already mid-edit) and replaces it in place —
|
|
28
|
+
* no DOM re-render needed.
|
|
29
|
+
*/
|
|
30
|
+
export declare function updateLiveText(oldValue: string, newValue: string): void;
|
|
31
|
+
//# sourceMappingURL=highlight.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"highlight.d.ts","sourceRoot":"","sources":["../../../src/preview/internals/highlight.ts"],"names":[],"mappings":"AAAA;;;GAGG;AA+EH;;;;;;;;;;GAUG;AACH,wBAAgB,uBAAuB,CAAC,KAAK,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,IAAI,CA0ExE;AAoBD;;;GAGG;AACH,wBAAgB,eAAe,IAAI,IAAI,CAKtC;AAED;;;;;;;GAOG;AACH,wBAAgB,cAAc,CAAC,QAAQ,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,IAAI,CAwBvE"}
|
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Translation highlighting logic.
|
|
3
|
+
* Handles searching for and highlighting elements that display translations.
|
|
4
|
+
*/
|
|
5
|
+
import * as state from './state.js';
|
|
6
|
+
/**
|
|
7
|
+
* Strip interpolation placeholders (`{{var}}` and `{var}`) and collapse
|
|
8
|
+
* whitespace so that translated strings with variables can still be matched
|
|
9
|
+
* against the rendered text in the DOM.
|
|
10
|
+
*/
|
|
11
|
+
function normalizeForSearch(value) {
|
|
12
|
+
return value
|
|
13
|
+
.replace(/\{\{[^}]+\}\}/g, '')
|
|
14
|
+
.replace(/\{[^}]+\}/g, '')
|
|
15
|
+
.replace(/\s+/g, ' ')
|
|
16
|
+
.trim();
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Strategy 0 (precise): find elements with `data-cms-key="<key>"` attribute.
|
|
20
|
+
* When present this is always preferred over text-content heuristics.
|
|
21
|
+
*/
|
|
22
|
+
function findByDataAttribute(key) {
|
|
23
|
+
return Array.from(document.querySelectorAll(`[data-cms-key="${CSS.escape(key)}"]`));
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Strategy 1: walk all text nodes in the document body and return the parent
|
|
27
|
+
* element of each node whose text includes `searchValue`.
|
|
28
|
+
* Excludes elements that are already highlighted to avoid double-wrapping.
|
|
29
|
+
*/
|
|
30
|
+
function findByTextNodes(searchValue) {
|
|
31
|
+
const walker = document.createTreeWalker(document.body, NodeFilter.SHOW_TEXT, null);
|
|
32
|
+
const results = [];
|
|
33
|
+
let node;
|
|
34
|
+
while ((node = walker.nextNode())) {
|
|
35
|
+
if (!node.textContent?.includes(searchValue))
|
|
36
|
+
continue;
|
|
37
|
+
const el = node.parentElement;
|
|
38
|
+
if (el && !el.classList.contains('cms-preview-highlight'))
|
|
39
|
+
results.push(el);
|
|
40
|
+
}
|
|
41
|
+
return results;
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Strategy 2: find the most specific (shortest textContent) element in the
|
|
45
|
+
* document whose combined `textContent` includes `searchValue`.
|
|
46
|
+
*
|
|
47
|
+
* This works when the translated string is split across sibling text nodes,
|
|
48
|
+
* which the text-node walker cannot detect.
|
|
49
|
+
*/
|
|
50
|
+
function findByElementContent(searchValue) {
|
|
51
|
+
const all = Array.from(document.body.querySelectorAll('*'));
|
|
52
|
+
let best = null;
|
|
53
|
+
let bestLen = Infinity;
|
|
54
|
+
for (const el of all) {
|
|
55
|
+
if (!el.textContent?.includes(searchValue))
|
|
56
|
+
continue;
|
|
57
|
+
if (el.classList.contains('cms-preview-highlight'))
|
|
58
|
+
continue;
|
|
59
|
+
const len = el.textContent.length;
|
|
60
|
+
if (len < bestLen) {
|
|
61
|
+
bestLen = len;
|
|
62
|
+
best = el;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
return best ? [best] : [];
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Find and highlight all DOM elements that display the given translation.
|
|
69
|
+
*
|
|
70
|
+
* Search order (first strategy that yields a match wins):
|
|
71
|
+
* 0. `data-cms-key` attribute — precise, skips all heuristics
|
|
72
|
+
* 1. Exact text node match
|
|
73
|
+
* 2. Element content match — handles text split across siblings
|
|
74
|
+
* 3. Normalized text node — strips `{{interpolation}}` placeholders
|
|
75
|
+
* 4. Normalized element match
|
|
76
|
+
* 5. Key fallback — used when `value` is empty (i18next renders the key)
|
|
77
|
+
*/
|
|
78
|
+
export function highlightTranslationKey(value, key) {
|
|
79
|
+
clearHighlights();
|
|
80
|
+
// --- Strategy 0: data-cms-key attribute (precise) -----------------------
|
|
81
|
+
const byAttr = findByDataAttribute(key);
|
|
82
|
+
if (byAttr.length > 0) {
|
|
83
|
+
applyHighlights(byAttr, 'data-cms-key attribute');
|
|
84
|
+
return;
|
|
85
|
+
}
|
|
86
|
+
// --- Heuristic fallback strategies --------------------------------------
|
|
87
|
+
const normalized = value ? normalizeForSearch(value) : '';
|
|
88
|
+
const hasInterpolation = value ? normalized !== value : false;
|
|
89
|
+
const candidates = [];
|
|
90
|
+
if (value && value.trim() !== '') {
|
|
91
|
+
// Primary: match on the raw translation value
|
|
92
|
+
candidates.push({ label: 'exact text node', search: value, fn: findByTextNodes }, {
|
|
93
|
+
label: 'element content',
|
|
94
|
+
search: value,
|
|
95
|
+
fn: findByElementContent,
|
|
96
|
+
});
|
|
97
|
+
// Secondary: also try the normalized form when the value contains interpolations
|
|
98
|
+
if (hasInterpolation && normalized) {
|
|
99
|
+
candidates.push({
|
|
100
|
+
label: 'normalized text node',
|
|
101
|
+
search: normalized,
|
|
102
|
+
fn: findByTextNodes,
|
|
103
|
+
}, {
|
|
104
|
+
label: 'normalized element',
|
|
105
|
+
search: normalized,
|
|
106
|
+
fn: findByElementContent,
|
|
107
|
+
});
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
else {
|
|
111
|
+
// Value is empty — i18next falls back to rendering the key itself as text
|
|
112
|
+
const keyWithoutNs = key.includes(':')
|
|
113
|
+
? key.split(':').slice(1).join(':')
|
|
114
|
+
: key;
|
|
115
|
+
candidates.push({ label: 'key (full)', search: key, fn: findByTextNodes }, { label: 'key (no ns)', search: keyWithoutNs, fn: findByTextNodes }, { label: 'key element', search: key, fn: findByElementContent }, {
|
|
116
|
+
label: 'key element (no ns)',
|
|
117
|
+
search: keyWithoutNs,
|
|
118
|
+
fn: findByElementContent,
|
|
119
|
+
});
|
|
120
|
+
}
|
|
121
|
+
for (const { label, search, fn } of candidates) {
|
|
122
|
+
const found = fn(search);
|
|
123
|
+
if (found.length === 0)
|
|
124
|
+
continue;
|
|
125
|
+
state.setCurrentSearchValue(search);
|
|
126
|
+
applyHighlights(found, label);
|
|
127
|
+
return;
|
|
128
|
+
}
|
|
129
|
+
console.warn(`[CMS Preview] No elements found for key: "${key}" value: "${value}"`);
|
|
130
|
+
}
|
|
131
|
+
/**
|
|
132
|
+
* Add the highlight CSS class to each element and scroll the first one into
|
|
133
|
+
* view. Registers elements with the state module so `clearHighlights` can
|
|
134
|
+
* remove them later.
|
|
135
|
+
*/
|
|
136
|
+
function applyHighlights(elements, matchLabel) {
|
|
137
|
+
console.log(`[CMS Preview] Highlighting ${elements.length} element(s) via ${matchLabel}`);
|
|
138
|
+
for (let i = 0; i < elements.length; i++) {
|
|
139
|
+
const el = elements[i];
|
|
140
|
+
if (!el)
|
|
141
|
+
continue;
|
|
142
|
+
el.classList.add('cms-preview-highlight');
|
|
143
|
+
state.addHighlightedElement(el);
|
|
144
|
+
if (i === 0)
|
|
145
|
+
el.scrollIntoView({ behavior: 'smooth', block: 'center' });
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
/**
|
|
149
|
+
* Remove the highlight class from all currently highlighted elements and
|
|
150
|
+
* reset the associated state (element list + search value).
|
|
151
|
+
*/
|
|
152
|
+
export function clearHighlights() {
|
|
153
|
+
for (const el of state.highlightedElements)
|
|
154
|
+
el.classList.remove('cms-preview-highlight');
|
|
155
|
+
state.setHighlightedElements([]);
|
|
156
|
+
state.setCurrentSearchValue(null);
|
|
157
|
+
}
|
|
158
|
+
/**
|
|
159
|
+
* Patch the live text of highlighted elements as the user types a new
|
|
160
|
+
* translation value in the CMS panel.
|
|
161
|
+
*
|
|
162
|
+
* Searches each highlighted element's text nodes for the old value (or the
|
|
163
|
+
* last live value if the user is already mid-edit) and replaces it in place —
|
|
164
|
+
* no DOM re-render needed.
|
|
165
|
+
*/
|
|
166
|
+
export function updateLiveText(oldValue, newValue) {
|
|
167
|
+
if (state.highlightedElements.length === 0)
|
|
168
|
+
return;
|
|
169
|
+
// Prefer the last live value so repeated keystrokes replace the previous
|
|
170
|
+
// intermediate value rather than the original translation
|
|
171
|
+
const searchFor = state.currentLiveValue ??
|
|
172
|
+
(oldValue || state.currentSearchValue) ??
|
|
173
|
+
oldValue;
|
|
174
|
+
for (const el of state.highlightedElements) {
|
|
175
|
+
const walker = document.createTreeWalker(el, NodeFilter.SHOW_TEXT, null);
|
|
176
|
+
let node;
|
|
177
|
+
while ((node = walker.nextNode())) {
|
|
178
|
+
if (node.nodeValue?.includes(searchFor))
|
|
179
|
+
node.nodeValue = node.nodeValue.replace(searchFor, newValue);
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
state.setCurrentLiveValue(newValue);
|
|
183
|
+
}
|
|
184
|
+
//# sourceMappingURL=highlight.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"highlight.js","sourceRoot":"","sources":["../../../src/preview/internals/highlight.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Interaction blocking for preview mode.
|
|
3
|
+
* Prevents users from clicking links or submitting forms while previewing.
|
|
4
|
+
*/
|
|
5
|
+
/**
|
|
6
|
+
* Inject a transparent full-page overlay that absorbs pointer events,
|
|
7
|
+
* and register capture-phase listeners for belt-and-suspenders blocking
|
|
8
|
+
* (covers keyboard-activated links/buttons the overlay doesn't intercept).
|
|
9
|
+
*/
|
|
10
|
+
export declare function injectInteractionBlocker(): void;
|
|
11
|
+
/**
|
|
12
|
+
* Remove interaction blocker and event listeners.
|
|
13
|
+
*/
|
|
14
|
+
export declare function removeInteractionBlocker(): void;
|
|
15
|
+
//# sourceMappingURL=interactions.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"interactions.d.ts","sourceRoot":"","sources":["../../../src/preview/internals/interactions.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAaH;;;;GAIG;AACH,wBAAgB,wBAAwB,IAAI,IAAI,CAU/C;AAED;;GAEG;AACH,wBAAgB,wBAAwB,IAAI,IAAI,CAK/C"}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Interaction blocking for preview mode.
|
|
3
|
+
* Prevents users from clicking links or submitting forms while previewing.
|
|
4
|
+
*/
|
|
5
|
+
import * as state from './state.js';
|
|
6
|
+
/**
|
|
7
|
+
* Capture-phase handler that swallows all clicks and form submits.
|
|
8
|
+
* Prevents links from navigating and buttons from firing actions.
|
|
9
|
+
*/
|
|
10
|
+
function blockInteraction(e) {
|
|
11
|
+
e.preventDefault();
|
|
12
|
+
e.stopPropagation();
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Inject a transparent full-page overlay that absorbs pointer events,
|
|
16
|
+
* and register capture-phase listeners for belt-and-suspenders blocking
|
|
17
|
+
* (covers keyboard-activated links/buttons the overlay doesn't intercept).
|
|
18
|
+
*/
|
|
19
|
+
export function injectInteractionBlocker() {
|
|
20
|
+
const overlay = document.createElement('div');
|
|
21
|
+
overlay.id = 'cms-preview-blocker';
|
|
22
|
+
overlay.style.cssText =
|
|
23
|
+
'position:fixed;inset:0;z-index:9997;pointer-events:all;cursor:default;';
|
|
24
|
+
document.body.appendChild(overlay);
|
|
25
|
+
state.setInteractionBlockerOverlay(overlay);
|
|
26
|
+
document.addEventListener('click', blockInteraction, true);
|
|
27
|
+
document.addEventListener('submit', blockInteraction, true);
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Remove interaction blocker and event listeners.
|
|
31
|
+
*/
|
|
32
|
+
export function removeInteractionBlocker() {
|
|
33
|
+
document.removeEventListener('click', blockInteraction, true);
|
|
34
|
+
document.removeEventListener('submit', blockInteraction, true);
|
|
35
|
+
state.interactionBlockerOverlay?.remove();
|
|
36
|
+
state.setInteractionBlockerOverlay(null);
|
|
37
|
+
}
|
|
38
|
+
//# sourceMappingURL=interactions.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"interactions.js","sourceRoot":"","sources":["../../../src/preview/internals/interactions.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,KAAK,KAAK,MAAM,YAAY,CAAC;AAEpC;;;GAGG;AACH,SAAS,gBAAgB,CAAC,CAAQ;IAC9B,CAAC,CAAC,cAAc,EAAE,CAAC;IACnB,CAAC,CAAC,eAAe,EAAE,CAAC;AACxB,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,wBAAwB;IACpC,MAAM,OAAO,GAAG,QAAQ,CAAC,aAAa,CAAC,KAAK,CAAC,CAAC;IAC9C,OAAO,CAAC,EAAE,GAAG,qBAAqB,CAAC;IACnC,OAAO,CAAC,KAAK,CAAC,OAAO;QACjB,wEAAwE,CAAC;IAC7E,QAAQ,CAAC,IAAI,CAAC,WAAW,CAAC,OAAO,CAAC,CAAC;IACnC,KAAK,CAAC,4BAA4B,CAAC,OAAO,CAAC,CAAC;IAE5C,QAAQ,CAAC,gBAAgB,CAAC,OAAO,EAAE,gBAAgB,EAAE,IAAI,CAAC,CAAC;IAC3D,QAAQ,CAAC,gBAAgB,CAAC,QAAQ,EAAE,gBAAgB,EAAE,IAAI,CAAC,CAAC;AAChE,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,wBAAwB;IACpC,QAAQ,CAAC,mBAAmB,CAAC,OAAO,EAAE,gBAAgB,EAAE,IAAI,CAAC,CAAC;IAC9D,QAAQ,CAAC,mBAAmB,CAAC,QAAQ,EAAE,gBAAgB,EAAE,IAAI,CAAC,CAAC;IAC/D,KAAK,CAAC,yBAAyB,EAAE,MAAM,EAAE,CAAC;IAC1C,KAAK,CAAC,4BAA4B,CAAC,IAAI,CAAC,CAAC;AAC7C,CAAC"}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Interactive preview mode: allow clicks, form interactions, navigation.
|
|
3
|
+
* Editors can explore the app while seeing translations highlighted.
|
|
4
|
+
*/
|
|
5
|
+
/**
|
|
6
|
+
* Initialize interactive mode:
|
|
7
|
+
* - Allow form interactions (typing, selecting)
|
|
8
|
+
* - Intercept navigation to report back to CMS
|
|
9
|
+
* - Capture/restore app state when navigating
|
|
10
|
+
*/
|
|
11
|
+
export declare function initInteractiveMode(): void;
|
|
12
|
+
/** Clean up interactive mode event listeners. */
|
|
13
|
+
export declare function cleanupInteractiveMode(): void;
|
|
14
|
+
//# sourceMappingURL=interactive.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"interactive.d.ts","sourceRoot":"","sources":["../../../src/preview/internals/interactive.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAMH;;;;;GAKG;AACH,wBAAgB,mBAAmB,IAAI,IAAI,CAc1C;AAED,iDAAiD;AACjD,wBAAgB,sBAAsB,IAAI,IAAI,CAG7C"}
|