@pigment/auto-translate 1.3.0 → 1.3.2
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 +27 -0
- package/dist/collections/translationExclusions.js +4 -4
- package/dist/collections/translationExclusions.js.map +1 -1
- package/dist/components/LockTranslation/actions/lockTranslations.d.ts +8 -0
- package/dist/components/LockTranslation/actions/lockTranslations.js +33 -0
- package/dist/components/LockTranslation/actions/lockTranslations.js.map +1 -0
- package/dist/components/LockTranslation/index.d.ts +10 -0
- package/dist/components/LockTranslation/index.js +98 -0
- package/dist/components/LockTranslation/index.js.map +1 -0
- package/dist/components/LockTranslation/style.css +87 -0
- package/dist/components/TranslationSettingsLock.css +87 -0
- package/dist/components/TranslationSettingsLock.d.ts +9 -0
- package/dist/components/TranslationSettingsLock.js +155 -0
- package/dist/components/TranslationSettingsLock.js.map +1 -0
- package/dist/exports/client.d.ts +1 -3
- package/dist/exports/client.js +2 -3
- package/dist/exports/client.js.map +1 -1
- package/dist/exports/rsc.d.ts +1 -0
- package/dist/exports/rsc.js +1 -0
- package/dist/exports/rsc.js.map +1 -1
- package/dist/globals/translationSettings.js +73 -8
- package/dist/globals/translationSettings.js.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.js +42 -26
- package/dist/index.js.map +1 -1
- package/dist/services/translationService.d.ts +5 -4
- package/dist/services/translationService.js +118 -117
- package/dist/services/translationService.js.map +1 -1
- package/dist/types/index.d.ts +1 -1
- package/dist/types/index.js.map +1 -1
- package/package.json +9 -9
package/README.md
CHANGED
|
@@ -8,6 +8,7 @@ A powerful auto-translation plugin for [Payload CMS](https://payloadcms.com) tha
|
|
|
8
8
|
- 🔒 **Field-Level Exclusion**: Toggle "do not translate" on specific fields in secondary languages
|
|
9
9
|
- 🎯 **Smart Translation**: Preserves excluded fields when updating default language content
|
|
10
10
|
- 🔧 **Flexible Configuration**: Configure per-collection settings and global exclusions
|
|
11
|
+
- 🔐 **Protected Settings**: Lock/unlock translation settings to prevent accidental changes
|
|
11
12
|
- 🤖 **OpenAI Integration**: Uses GPT-4o for high-quality translations (with custom provider support)
|
|
12
13
|
- 📦 **Zero UI Overhead**: Seamlessly integrates with Payload's admin panel
|
|
13
14
|
- ⚡ **Performance Optimized**: 10-15x faster translation with smart extraction and deduplication
|
|
@@ -357,6 +358,19 @@ pnpm test:e2e # Run end-to-end tests
|
|
|
357
358
|
|
|
358
359
|
---
|
|
359
360
|
|
|
361
|
+
## 🛠️ Development
|
|
362
|
+
|
|
363
|
+
The development server automatically seeds the database on first run with test data:
|
|
364
|
+
- Creates a dev user (if not exists)
|
|
365
|
+
- Creates test posts in the default language
|
|
366
|
+
- **Only seeds when database is empty** - subsequent restarts skip seeding
|
|
367
|
+
|
|
368
|
+
To re-seed the database:
|
|
369
|
+
1. Delete all posts from the admin panel
|
|
370
|
+
2. Restart dev server - auto-seeding will run
|
|
371
|
+
|
|
372
|
+
---
|
|
373
|
+
|
|
360
374
|
## 🔒 Access Control
|
|
361
375
|
|
|
362
376
|
The plugin respects Payload's access control. Translation operations run with the same permissions as the user making the update.
|
|
@@ -374,6 +388,19 @@ If you need custom access control for translation features, you can add hooks or
|
|
|
374
388
|
|
|
375
389
|
---
|
|
376
390
|
|
|
391
|
+
## 📖 Additional Documentation
|
|
392
|
+
|
|
393
|
+
For more detailed information, check out these guides:
|
|
394
|
+
|
|
395
|
+
- **[QUICKSTART.md](./docs/QUICKSTART.md)** - Quick setup and basic usage
|
|
396
|
+
- **[USAGE_GUIDE.md](./docs/USAGE_GUIDE.md)** - Detailed usage examples and best practices
|
|
397
|
+
- **[ARCHITECTURE.md](./docs/ARCHITECTURE.md)** - Technical architecture and implementation details
|
|
398
|
+
- **[INTEGRATION_EXAMPLES.md](./docs/INTEGRATION_EXAMPLES.md)** - Real-world integration examples
|
|
399
|
+
- **[TRANSLATION_SETTINGS_LOCK.md](./docs/TRANSLATION_SETTINGS_LOCK.md)** - Lock/unlock feature for translation settings
|
|
400
|
+
- **[FEATURE_SUMMARY.md](./docs/FEATURE_SUMMARY.md)** - Complete feature overview
|
|
401
|
+
|
|
402
|
+
---
|
|
403
|
+
|
|
377
404
|
## 🤝 Contributing
|
|
378
405
|
|
|
379
406
|
Contributions are welcome! Please feel free to submit a Pull Request.
|
|
@@ -8,18 +8,18 @@ export const getTranslationExclusionsCollection = (slug = 'translation-exclusion
|
|
|
8
8
|
},
|
|
9
9
|
admin: {
|
|
10
10
|
defaultColumns: [
|
|
11
|
-
'
|
|
11
|
+
'collectionSlug',
|
|
12
12
|
'documentId',
|
|
13
13
|
'locale',
|
|
14
14
|
'excludedPaths'
|
|
15
15
|
],
|
|
16
|
-
description: 'Stores field-level translation exclusions per document and locale. Each locale can have its own set of excluded fields. There should only be ONE record per (
|
|
16
|
+
description: 'Stores field-level translation exclusions per document and locale. Each locale can have its own set of excluded fields. There should only be ONE record per (collectionSlug, documentId, locale) combination.',
|
|
17
17
|
group: 'Settings',
|
|
18
|
-
useAsTitle: '
|
|
18
|
+
useAsTitle: 'collectionSlug'
|
|
19
19
|
},
|
|
20
20
|
fields: [
|
|
21
21
|
{
|
|
22
|
-
name: '
|
|
22
|
+
name: 'collectionSlug',
|
|
23
23
|
type: 'text',
|
|
24
24
|
admin: {
|
|
25
25
|
description: 'The collection this exclusion belongs to',
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/collections/translationExclusions.ts"],"sourcesContent":["import type { CollectionConfig } from 'payload'\n\nexport const getTranslationExclusionsCollection = (\n slug: string = 'translation-exclusions',\n): CollectionConfig => ({\n slug,\n access: {\n create: () => true,\n delete: () => true,\n read: () => true,\n update: () => true,\n },\n admin: {\n defaultColumns: ['
|
|
1
|
+
{"version":3,"sources":["../../src/collections/translationExclusions.ts"],"sourcesContent":["import type { CollectionConfig } from 'payload'\n\nexport const getTranslationExclusionsCollection = (\n slug: string = 'translation-exclusions',\n): CollectionConfig => ({\n slug,\n access: {\n create: () => true,\n delete: () => true,\n read: () => true,\n update: () => true,\n },\n admin: {\n defaultColumns: ['collectionSlug', 'documentId', 'locale', 'excludedPaths'],\n description:\n 'Stores field-level translation exclusions per document and locale. Each locale can have its own set of excluded fields. There should only be ONE record per (collectionSlug, documentId, locale) combination.',\n group: 'Settings',\n useAsTitle: 'collectionSlug',\n },\n fields: [\n {\n name: 'collectionSlug',\n type: 'text',\n admin: {\n description: 'The collection this exclusion belongs to',\n position: 'sidebar',\n readOnly: true,\n },\n index: true,\n required: true,\n },\n {\n name: 'documentId',\n type: 'text',\n admin: {\n description: 'The ID of the document',\n position: 'sidebar',\n readOnly: true,\n },\n index: true,\n required: true,\n },\n {\n name: 'locale',\n type: 'text',\n admin: {\n description: 'The locale these exclusions apply to (e.g., \"en\", \"de\", \"fr\")',\n position: 'sidebar',\n readOnly: true,\n },\n index: true,\n label: 'Locale',\n required: true,\n },\n {\n name: 'excludedPaths',\n type: 'array',\n admin: {\n description:\n 'Fields that should NOT be auto-translated in this specific locale. Each locale has its own independent set of exclusions.',\n },\n fields: [\n {\n name: 'path',\n type: 'text',\n admin: {\n description: 'Field path (e.g., \"title\", \"content.0.description\")',\n },\n label: 'Field Path',\n required: true,\n },\n ],\n label: `Excluded Fields for this Locale`,\n required: true,\n },\n ],\n})\n"],"names":["getTranslationExclusionsCollection","slug","access","create","delete","read","update","admin","defaultColumns","description","group","useAsTitle","fields","name","type","position","readOnly","index","required","label"],"mappings":"AAEA,OAAO,MAAMA,qCAAqC,CAChDC,OAAe,wBAAwB,GACjB,CAAA;QACtBA;QACAC,QAAQ;YACNC,QAAQ,IAAM;YACdC,QAAQ,IAAM;YACdC,MAAM,IAAM;YACZC,QAAQ,IAAM;QAChB;QACAC,OAAO;YACLC,gBAAgB;gBAAC;gBAAkB;gBAAc;gBAAU;aAAgB;YAC3EC,aACE;YACFC,OAAO;YACPC,YAAY;QACd;QACAC,QAAQ;YACN;gBACEC,MAAM;gBACNC,MAAM;gBACNP,OAAO;oBACLE,aAAa;oBACbM,UAAU;oBACVC,UAAU;gBACZ;gBACAC,OAAO;gBACPC,UAAU;YACZ;YACA;gBACEL,MAAM;gBACNC,MAAM;gBACNP,OAAO;oBACLE,aAAa;oBACbM,UAAU;oBACVC,UAAU;gBACZ;gBACAC,OAAO;gBACPC,UAAU;YACZ;YACA;gBACEL,MAAM;gBACNC,MAAM;gBACNP,OAAO;oBACLE,aAAa;oBACbM,UAAU;oBACVC,UAAU;gBACZ;gBACAC,OAAO;gBACPE,OAAO;gBACPD,UAAU;YACZ;YACA;gBACEL,MAAM;gBACNC,MAAM;gBACNP,OAAO;oBACLE,aACE;gBACJ;gBACAG,QAAQ;oBACN;wBACEC,MAAM;wBACNC,MAAM;wBACNP,OAAO;4BACLE,aAAa;wBACf;wBACAU,OAAO;wBACPD,UAAU;oBACZ;iBACD;gBACDC,OAAO,CAAC,+BAA+B,CAAC;gBACxCD,UAAU;YACZ;SACD;IACH,CAAA,EAAE"}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Client-side function to update the lockTranslationSettings field
|
|
3
|
+
* Makes a fetch request to update the global
|
|
4
|
+
*/ export async function updateLockTranslationSettingsField(isLocked) {
|
|
5
|
+
try {
|
|
6
|
+
const response = await fetch('/api/globals/translation-settings', {
|
|
7
|
+
body: JSON.stringify({
|
|
8
|
+
lockTranslationSettings: isLocked
|
|
9
|
+
}),
|
|
10
|
+
credentials: 'include',
|
|
11
|
+
headers: {
|
|
12
|
+
'Content-Type': 'application/json'
|
|
13
|
+
},
|
|
14
|
+
method: 'POST'
|
|
15
|
+
});
|
|
16
|
+
if (!response.ok) {
|
|
17
|
+
throw new Error(`Failed to update: ${response.statusText}`);
|
|
18
|
+
}
|
|
19
|
+
return {
|
|
20
|
+
isLocked,
|
|
21
|
+
success: true
|
|
22
|
+
};
|
|
23
|
+
} catch (error) {
|
|
24
|
+
// eslint-disable-next-line no-console
|
|
25
|
+
console.error('[LockTranslation] Error updating lock state:', error);
|
|
26
|
+
return {
|
|
27
|
+
isLocked: !isLocked,
|
|
28
|
+
success: false
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
//# sourceMappingURL=lockTranslations.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../../src/components/LockTranslation/actions/lockTranslations.ts"],"sourcesContent":["/**\n * Client-side function to update the lockTranslationSettings field\n * Makes a fetch request to update the global\n */\nexport async function updateLockTranslationSettingsField(\n isLocked: boolean,\n): Promise<{ isLocked: boolean; success: boolean }> {\n try {\n const response = await fetch('/api/globals/translation-settings', {\n body: JSON.stringify({\n lockTranslationSettings: isLocked,\n }),\n credentials: 'include',\n headers: {\n 'Content-Type': 'application/json',\n },\n method: 'POST',\n })\n\n if (!response.ok) {\n throw new Error(`Failed to update: ${response.statusText}`)\n }\n\n return {\n isLocked,\n success: true,\n }\n } catch (error) {\n // eslint-disable-next-line no-console\n console.error('[LockTranslation] Error updating lock state:', error)\n return {\n isLocked: !isLocked,\n success: false,\n }\n }\n}\n"],"names":["updateLockTranslationSettingsField","isLocked","response","fetch","body","JSON","stringify","lockTranslationSettings","credentials","headers","method","ok","Error","statusText","success","error","console"],"mappings":"AAAA;;;CAGC,GACD,OAAO,eAAeA,mCACpBC,QAAiB;IAEjB,IAAI;QACF,MAAMC,WAAW,MAAMC,MAAM,qCAAqC;YAChEC,MAAMC,KAAKC,SAAS,CAAC;gBACnBC,yBAAyBN;YAC3B;YACAO,aAAa;YACbC,SAAS;gBACP,gBAAgB;YAClB;YACAC,QAAQ;QACV;QAEA,IAAI,CAACR,SAASS,EAAE,EAAE;YAChB,MAAM,IAAIC,MAAM,CAAC,kBAAkB,EAAEV,SAASW,UAAU,EAAE;QAC5D;QAEA,OAAO;YACLZ;YACAa,SAAS;QACX;IACF,EAAE,OAAOC,OAAO;QACd,sCAAsC;QACtCC,QAAQD,KAAK,CAAC,gDAAgDA;QAC9D,OAAO;YACLd,UAAU,CAACA;YACXa,SAAS;QACX;IACF;AACF"}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import './style.css';
|
|
3
|
+
/**
|
|
4
|
+
* Component that provides lock/unlock functionality for translation settings
|
|
5
|
+
* - Fields are locked by default (read-only)
|
|
6
|
+
* - User must click "Unlock" to edit
|
|
7
|
+
* - After saving, fields automatically lock again
|
|
8
|
+
* - Updates the hidden lockTranslationSettings checkbox field to enable server-side access control
|
|
9
|
+
*/
|
|
10
|
+
export declare const LockTranslation: React.FC;
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { useForm, useFormFields } from '@payloadcms/ui';
|
|
4
|
+
import React, { useCallback, useEffect, useState } from 'react';
|
|
5
|
+
import { updateLockTranslationSettingsField } from './actions/lockTranslations.js';
|
|
6
|
+
import './style.css';
|
|
7
|
+
/**
|
|
8
|
+
* Component that provides lock/unlock functionality for translation settings
|
|
9
|
+
* - Fields are locked by default (read-only)
|
|
10
|
+
* - User must click "Unlock" to edit
|
|
11
|
+
* - After saving, fields automatically lock again
|
|
12
|
+
* - Updates the hidden lockTranslationSettings checkbox field to enable server-side access control
|
|
13
|
+
*/ export const LockTranslation = ()=>{
|
|
14
|
+
const lockField = useFormFields(([fields])=>fields?.lockTranslationSettings);
|
|
15
|
+
const { dispatchFields } = useForm();
|
|
16
|
+
const isLocked = Boolean(lockField?.value ?? true);
|
|
17
|
+
const [isLoading, setIsLoading] = useState(false);
|
|
18
|
+
// Apply lock state to fields by directly manipulating their disabled state
|
|
19
|
+
const applyLockStateToFields = useCallback((locked)=>{
|
|
20
|
+
const fieldsToLock = [
|
|
21
|
+
'systemPrompt',
|
|
22
|
+
'translationRules',
|
|
23
|
+
'model',
|
|
24
|
+
'temperature',
|
|
25
|
+
'maxTokens'
|
|
26
|
+
];
|
|
27
|
+
fieldsToLock.forEach((fieldPath)=>{
|
|
28
|
+
// Find the input/textarea elements for this field
|
|
29
|
+
const inputs = document.querySelectorAll(`[name="${fieldPath}"], textarea[id*="${fieldPath}"], input[id*="${fieldPath}"]`);
|
|
30
|
+
inputs.forEach((input)=>{
|
|
31
|
+
if (locked) {
|
|
32
|
+
input.setAttribute('disabled', 'true');
|
|
33
|
+
} else {
|
|
34
|
+
input.removeAttribute('disabled');
|
|
35
|
+
}
|
|
36
|
+
});
|
|
37
|
+
});
|
|
38
|
+
}, []);
|
|
39
|
+
// Apply initial lock state when component mounts or lock state changes
|
|
40
|
+
useEffect(()=>{
|
|
41
|
+
// Small delay to ensure form fields are rendered
|
|
42
|
+
const timer = setTimeout(()=>{
|
|
43
|
+
applyLockStateToFields(isLocked);
|
|
44
|
+
}, 100);
|
|
45
|
+
return ()=>clearTimeout(timer);
|
|
46
|
+
}, [
|
|
47
|
+
isLocked,
|
|
48
|
+
applyLockStateToFields
|
|
49
|
+
]);
|
|
50
|
+
const toggleLock = useCallback(async ()=>{
|
|
51
|
+
const newLockState = !isLocked;
|
|
52
|
+
setIsLoading(true);
|
|
53
|
+
try {
|
|
54
|
+
const result = await updateLockTranslationSettingsField(newLockState);
|
|
55
|
+
if (result.success) {
|
|
56
|
+
// Update the lockTranslationSettings field value
|
|
57
|
+
dispatchFields({
|
|
58
|
+
type: 'UPDATE',
|
|
59
|
+
path: 'lockTranslationSettings',
|
|
60
|
+
value: result.isLocked
|
|
61
|
+
});
|
|
62
|
+
// Apply lock state to fields
|
|
63
|
+
applyLockStateToFields(result.isLocked);
|
|
64
|
+
}
|
|
65
|
+
} catch (error) {
|
|
66
|
+
// eslint-disable-next-line no-console
|
|
67
|
+
console.error('[LockTranslation] Error:', error);
|
|
68
|
+
} finally{
|
|
69
|
+
setIsLoading(false);
|
|
70
|
+
}
|
|
71
|
+
}, [
|
|
72
|
+
isLocked,
|
|
73
|
+
dispatchFields,
|
|
74
|
+
applyLockStateToFields
|
|
75
|
+
]);
|
|
76
|
+
return /*#__PURE__*/ _jsx("div", {
|
|
77
|
+
className: "translation-settings-lock-container",
|
|
78
|
+
children: /*#__PURE__*/ _jsxs("button", {
|
|
79
|
+
className: `translation-settings-lock-button ${isLocked ? 'locked' : 'unlocked'}`,
|
|
80
|
+
disabled: isLoading,
|
|
81
|
+
onClick: toggleLock,
|
|
82
|
+
title: isLocked ? '🔒 Settings are locked to prevent accidental changes. Click to unlock and edit.' : '🔓 Settings are unlocked. Click to lock after saving your changes.',
|
|
83
|
+
type: "button",
|
|
84
|
+
children: [
|
|
85
|
+
/*#__PURE__*/ _jsx("span", {
|
|
86
|
+
className: "translation-settings-lock-icon",
|
|
87
|
+
children: isLoading ? '⏳' : isLocked ? '🔒' : '🔓'
|
|
88
|
+
}),
|
|
89
|
+
/*#__PURE__*/ _jsx("span", {
|
|
90
|
+
className: "translation-settings-lock-label",
|
|
91
|
+
children: isLoading ? 'Updating...' : isLocked ? 'Unlock Settings' : 'Lock Settings'
|
|
92
|
+
})
|
|
93
|
+
]
|
|
94
|
+
})
|
|
95
|
+
});
|
|
96
|
+
};
|
|
97
|
+
|
|
98
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../src/components/LockTranslation/index.tsx"],"sourcesContent":["'use client'\n\nimport { useForm, useFormFields } from '@payloadcms/ui'\nimport React, { useCallback, useEffect, useState } from 'react'\n\nimport { updateLockTranslationSettingsField } from './actions/lockTranslations.js'\nimport './style.css'\n\n/**\n * Component that provides lock/unlock functionality for translation settings\n * - Fields are locked by default (read-only)\n * - User must click \"Unlock\" to edit\n * - After saving, fields automatically lock again\n * - Updates the hidden lockTranslationSettings checkbox field to enable server-side access control\n */\nexport const LockTranslation: React.FC = () => {\n const lockField = useFormFields(([fields]) => fields?.lockTranslationSettings)\n const { dispatchFields } = useForm()\n const isLocked = Boolean(lockField?.value ?? true)\n const [isLoading, setIsLoading] = useState(false)\n\n // Apply lock state to fields by directly manipulating their disabled state\n const applyLockStateToFields = useCallback((locked: boolean) => {\n const fieldsToLock = ['systemPrompt', 'translationRules', 'model', 'temperature', 'maxTokens']\n\n fieldsToLock.forEach((fieldPath) => {\n // Find the input/textarea elements for this field\n const inputs = document.querySelectorAll<HTMLInputElement | HTMLTextAreaElement>(\n `[name=\"${fieldPath}\"], textarea[id*=\"${fieldPath}\"], input[id*=\"${fieldPath}\"]`,\n )\n\n inputs.forEach((input) => {\n if (locked) {\n input.setAttribute('disabled', 'true')\n } else {\n input.removeAttribute('disabled')\n }\n })\n })\n }, [])\n\n // Apply initial lock state when component mounts or lock state changes\n useEffect(() => {\n // Small delay to ensure form fields are rendered\n const timer = setTimeout(() => {\n applyLockStateToFields(isLocked)\n }, 100)\n\n return () => clearTimeout(timer)\n }, [isLocked, applyLockStateToFields])\n\n const toggleLock = useCallback(async () => {\n const newLockState = !isLocked\n setIsLoading(true)\n\n try {\n const result = await updateLockTranslationSettingsField(newLockState)\n\n if (result.success) {\n // Update the lockTranslationSettings field value\n dispatchFields({\n type: 'UPDATE',\n path: 'lockTranslationSettings',\n value: result.isLocked,\n })\n\n // Apply lock state to fields\n applyLockStateToFields(result.isLocked)\n }\n } catch (error) {\n // eslint-disable-next-line no-console\n console.error('[LockTranslation] Error:', error)\n } finally {\n setIsLoading(false)\n }\n }, [isLocked, dispatchFields, applyLockStateToFields])\n\n return (\n <div className=\"translation-settings-lock-container\">\n <button\n className={`translation-settings-lock-button ${isLocked ? 'locked' : 'unlocked'}`}\n disabled={isLoading}\n onClick={toggleLock}\n title={\n isLocked\n ? '🔒 Settings are locked to prevent accidental changes. Click to unlock and edit.'\n : '🔓 Settings are unlocked. Click to lock after saving your changes.'\n }\n type=\"button\"\n >\n <span className=\"translation-settings-lock-icon\">\n {isLoading ? '⏳' : isLocked ? '🔒' : '🔓'}\n </span>\n <span className=\"translation-settings-lock-label\">\n {isLoading ? 'Updating...' : isLocked ? 'Unlock Settings' : 'Lock Settings'}\n </span>\n </button>\n </div>\n )\n}\n"],"names":["useForm","useFormFields","React","useCallback","useEffect","useState","updateLockTranslationSettingsField","LockTranslation","lockField","fields","lockTranslationSettings","dispatchFields","isLocked","Boolean","value","isLoading","setIsLoading","applyLockStateToFields","locked","fieldsToLock","forEach","fieldPath","inputs","document","querySelectorAll","input","setAttribute","removeAttribute","timer","setTimeout","clearTimeout","toggleLock","newLockState","result","success","type","path","error","console","div","className","button","disabled","onClick","title","span"],"mappings":"AAAA;;AAEA,SAASA,OAAO,EAAEC,aAAa,QAAQ,iBAAgB;AACvD,OAAOC,SAASC,WAAW,EAAEC,SAAS,EAAEC,QAAQ,QAAQ,QAAO;AAE/D,SAASC,kCAAkC,QAAQ,gCAA+B;AAClF,OAAO,cAAa;AAEpB;;;;;;CAMC,GACD,OAAO,MAAMC,kBAA4B;IACvC,MAAMC,YAAYP,cAAc,CAAC,CAACQ,OAAO,GAAKA,QAAQC;IACtD,MAAM,EAAEC,cAAc,EAAE,GAAGX;IAC3B,MAAMY,WAAWC,QAAQL,WAAWM,SAAS;IAC7C,MAAM,CAACC,WAAWC,aAAa,GAAGX,SAAS;IAE3C,2EAA2E;IAC3E,MAAMY,yBAAyBd,YAAY,CAACe;QAC1C,MAAMC,eAAe;YAAC;YAAgB;YAAoB;YAAS;YAAe;SAAY;QAE9FA,aAAaC,OAAO,CAAC,CAACC;YACpB,kDAAkD;YAClD,MAAMC,SAASC,SAASC,gBAAgB,CACtC,CAAC,OAAO,EAAEH,UAAU,kBAAkB,EAAEA,UAAU,eAAe,EAAEA,UAAU,EAAE,CAAC;YAGlFC,OAAOF,OAAO,CAAC,CAACK;gBACd,IAAIP,QAAQ;oBACVO,MAAMC,YAAY,CAAC,YAAY;gBACjC,OAAO;oBACLD,MAAME,eAAe,CAAC;gBACxB;YACF;QACF;IACF,GAAG,EAAE;IAEL,uEAAuE;IACvEvB,UAAU;QACR,iDAAiD;QACjD,MAAMwB,QAAQC,WAAW;YACvBZ,uBAAuBL;QACzB,GAAG;QAEH,OAAO,IAAMkB,aAAaF;IAC5B,GAAG;QAAChB;QAAUK;KAAuB;IAErC,MAAMc,aAAa5B,YAAY;QAC7B,MAAM6B,eAAe,CAACpB;QACtBI,aAAa;QAEb,IAAI;YACF,MAAMiB,SAAS,MAAM3B,mCAAmC0B;YAExD,IAAIC,OAAOC,OAAO,EAAE;gBAClB,iDAAiD;gBACjDvB,eAAe;oBACbwB,MAAM;oBACNC,MAAM;oBACNtB,OAAOmB,OAAOrB,QAAQ;gBACxB;gBAEA,6BAA6B;gBAC7BK,uBAAuBgB,OAAOrB,QAAQ;YACxC;QACF,EAAE,OAAOyB,OAAO;YACd,sCAAsC;YACtCC,QAAQD,KAAK,CAAC,4BAA4BA;QAC5C,SAAU;YACRrB,aAAa;QACf;IACF,GAAG;QAACJ;QAAUD;QAAgBM;KAAuB;IAErD,qBACE,KAACsB;QAAIC,WAAU;kBACb,cAAA,MAACC;YACCD,WAAW,CAAC,iCAAiC,EAAE5B,WAAW,WAAW,YAAY;YACjF8B,UAAU3B;YACV4B,SAASZ;YACTa,OACEhC,WACI,oFACA;YAENuB,MAAK;;8BAEL,KAACU;oBAAKL,WAAU;8BACbzB,YAAY,MAAMH,WAAW,OAAO;;8BAEvC,KAACiC;oBAAKL,WAAU;8BACbzB,YAAY,gBAAgBH,WAAW,oBAAoB;;;;;AAKtE,EAAC"}
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
.translation-settings-lock-container {
|
|
2
|
+
display: inline-flex;
|
|
3
|
+
}
|
|
4
|
+
|
|
5
|
+
.translation-settings-lock-button {
|
|
6
|
+
display: inline-flex;
|
|
7
|
+
align-items: center;
|
|
8
|
+
gap: 0.5rem;
|
|
9
|
+
padding: 0.5rem 1rem;
|
|
10
|
+
border: 1.5px solid var(--theme-elevation-400);
|
|
11
|
+
border-radius: var(--border-radius-s, 4px);
|
|
12
|
+
background: var(--theme-elevation-0);
|
|
13
|
+
cursor: pointer;
|
|
14
|
+
font-size: 13px;
|
|
15
|
+
font-weight: 600;
|
|
16
|
+
transition: all 0.2s ease;
|
|
17
|
+
white-space: nowrap;
|
|
18
|
+
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.08);
|
|
19
|
+
outline: none;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
.translation-settings-lock-button:hover {
|
|
23
|
+
background: var(--theme-elevation-100);
|
|
24
|
+
border-color: var(--theme-elevation-500);
|
|
25
|
+
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.08);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
.translation-settings-lock-button.locked {
|
|
29
|
+
background: var(--theme-error-50);
|
|
30
|
+
border-color: var(--theme-error-500);
|
|
31
|
+
color: var(--theme-error-800);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
.translation-settings-lock-button.locked:hover {
|
|
35
|
+
background: var(--theme-error-100);
|
|
36
|
+
border-color: var(--theme-error-600);
|
|
37
|
+
transform: translateY(-1px);
|
|
38
|
+
box-shadow: 0 3px 6px rgba(0, 0, 0, 0.12);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
.translation-settings-lock-button.unlocked {
|
|
42
|
+
background: var(--theme-success-50);
|
|
43
|
+
border-color: var(--theme-success-500);
|
|
44
|
+
color: var(--theme-success-800);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
.translation-settings-lock-button.unlocked:hover {
|
|
48
|
+
background: var(--theme-success-100);
|
|
49
|
+
border-color: var(--theme-success-600);
|
|
50
|
+
transform: translateY(-1px);
|
|
51
|
+
box-shadow: 0 3px 6px rgba(0, 0, 0, 0.12);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
.translation-settings-lock-icon {
|
|
55
|
+
font-size: 16px;
|
|
56
|
+
line-height: 1;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
.translation-settings-lock-label {
|
|
60
|
+
font-weight: 600;
|
|
61
|
+
letter-spacing: 0.01em;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/* Style for locked input fields */
|
|
65
|
+
.translation-settings-locked {
|
|
66
|
+
background-color: var(--theme-elevation-50) !important;
|
|
67
|
+
cursor: not-allowed !important;
|
|
68
|
+
opacity: 0.7;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/* Style for locked save button */
|
|
72
|
+
.translation-settings-save-locked {
|
|
73
|
+
opacity: 0.5;
|
|
74
|
+
cursor: not-allowed !important;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/* Responsive adjustments */
|
|
78
|
+
@media (max-width: 768px) {
|
|
79
|
+
.translation-settings-lock-button {
|
|
80
|
+
font-size: 12px;
|
|
81
|
+
padding: 0.4rem 0.75rem;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
.translation-settings-lock-icon {
|
|
85
|
+
font-size: 14px;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
.translation-settings-lock-container {
|
|
2
|
+
display: inline-flex;
|
|
3
|
+
}
|
|
4
|
+
|
|
5
|
+
.translation-settings-lock-button {
|
|
6
|
+
display: inline-flex;
|
|
7
|
+
align-items: center;
|
|
8
|
+
gap: 0.5rem;
|
|
9
|
+
padding: 0.5rem 1rem;
|
|
10
|
+
border: 1.5px solid var(--theme-elevation-400);
|
|
11
|
+
border-radius: var(--border-radius-s, 4px);
|
|
12
|
+
background: var(--theme-elevation-0);
|
|
13
|
+
cursor: pointer;
|
|
14
|
+
font-size: 13px;
|
|
15
|
+
font-weight: 600;
|
|
16
|
+
transition: all 0.2s ease;
|
|
17
|
+
white-space: nowrap;
|
|
18
|
+
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.08);
|
|
19
|
+
outline: none;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
.translation-settings-lock-button:hover {
|
|
23
|
+
background: var(--theme-elevation-100);
|
|
24
|
+
border-color: var(--theme-elevation-500);
|
|
25
|
+
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.08);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
.translation-settings-lock-button.locked {
|
|
29
|
+
background: var(--theme-error-50);
|
|
30
|
+
border-color: var(--theme-error-500);
|
|
31
|
+
color: var(--theme-error-800);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
.translation-settings-lock-button.locked:hover {
|
|
35
|
+
background: var(--theme-error-100);
|
|
36
|
+
border-color: var(--theme-error-600);
|
|
37
|
+
transform: translateY(-1px);
|
|
38
|
+
box-shadow: 0 3px 6px rgba(0, 0, 0, 0.12);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
.translation-settings-lock-button.unlocked {
|
|
42
|
+
background: var(--theme-success-50);
|
|
43
|
+
border-color: var(--theme-success-500);
|
|
44
|
+
color: var(--theme-success-800);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
.translation-settings-lock-button.unlocked:hover {
|
|
48
|
+
background: var(--theme-success-100);
|
|
49
|
+
border-color: var(--theme-success-600);
|
|
50
|
+
transform: translateY(-1px);
|
|
51
|
+
box-shadow: 0 3px 6px rgba(0, 0, 0, 0.12);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
.translation-settings-lock-icon {
|
|
55
|
+
font-size: 16px;
|
|
56
|
+
line-height: 1;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
.translation-settings-lock-label {
|
|
60
|
+
font-weight: 600;
|
|
61
|
+
letter-spacing: 0.01em;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/* Style for locked input fields */
|
|
65
|
+
.translation-settings-locked {
|
|
66
|
+
background-color: var(--theme-elevation-50) !important;
|
|
67
|
+
cursor: not-allowed !important;
|
|
68
|
+
opacity: 0.7;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/* Style for locked save button */
|
|
72
|
+
.translation-settings-save-locked {
|
|
73
|
+
opacity: 0.5;
|
|
74
|
+
cursor: not-allowed !important;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/* Responsive adjustments */
|
|
78
|
+
@media (max-width: 768px) {
|
|
79
|
+
.translation-settings-lock-button {
|
|
80
|
+
font-size: 12px;
|
|
81
|
+
padding: 0.4rem 0.75rem;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
.translation-settings-lock-icon {
|
|
85
|
+
font-size: 14px;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import './TranslationSettingsLock.css';
|
|
3
|
+
/**
|
|
4
|
+
* Component that provides lock/unlock functionality for translation settings
|
|
5
|
+
* - Fields are locked by default (read-only)
|
|
6
|
+
* - User must click "Unlock" to edit
|
|
7
|
+
* - After saving, fields automatically lock again
|
|
8
|
+
*/
|
|
9
|
+
export declare const TranslationSettingsLock: React.FC;
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
import React, { useCallback, useEffect, useRef, useState } from 'react';
|
|
4
|
+
import './TranslationSettingsLock.css';
|
|
5
|
+
/**
|
|
6
|
+
* Component that provides lock/unlock functionality for translation settings
|
|
7
|
+
* - Fields are locked by default (read-only)
|
|
8
|
+
* - User must click "Unlock" to edit
|
|
9
|
+
* - After saving, fields automatically lock again
|
|
10
|
+
*/ export const TranslationSettingsLock = ()=>{
|
|
11
|
+
const [isLocked, setIsLocked] = useState(true);
|
|
12
|
+
const formRef = useRef(null);
|
|
13
|
+
const observerRef = useRef(null);
|
|
14
|
+
const debounceTimerRef = useRef(null);
|
|
15
|
+
// Find form element (memoized selector logic)
|
|
16
|
+
const findForm = useCallback(()=>{
|
|
17
|
+
if (formRef.current && document.contains(formRef.current)) {
|
|
18
|
+
return formRef.current;
|
|
19
|
+
}
|
|
20
|
+
const formElement = document.querySelector('form[id*="translation-settings"]') || document.querySelector('form[id*="Global"]') || document.querySelector('form.render-fields') || document.querySelector('form');
|
|
21
|
+
const form = formElement;
|
|
22
|
+
if (form) {
|
|
23
|
+
formRef.current = form;
|
|
24
|
+
}
|
|
25
|
+
return form;
|
|
26
|
+
}, []);
|
|
27
|
+
// Apply lock/unlock state to all form fields (optimized with caching)
|
|
28
|
+
const applyLockState = useCallback(()=>{
|
|
29
|
+
const form = findForm();
|
|
30
|
+
if (!form) {
|
|
31
|
+
return;
|
|
32
|
+
}
|
|
33
|
+
// Find all input fields - using more efficient selector
|
|
34
|
+
const inputs = form.querySelectorAll('input[type="text"], input[type="number"], textarea');
|
|
35
|
+
// Batch DOM updates for better performance
|
|
36
|
+
inputs.forEach((input)=>{
|
|
37
|
+
// Skip if this is the lock control itself
|
|
38
|
+
if (input.closest('.translation-settings-lock-container')) {
|
|
39
|
+
return;
|
|
40
|
+
}
|
|
41
|
+
// Use single operation to update multiple attributes
|
|
42
|
+
if (isLocked) {
|
|
43
|
+
input.setAttribute('readonly', 'true');
|
|
44
|
+
input.setAttribute('disabled', 'true');
|
|
45
|
+
input.classList.add('translation-settings-locked');
|
|
46
|
+
} else {
|
|
47
|
+
input.removeAttribute('readonly');
|
|
48
|
+
input.removeAttribute('disabled');
|
|
49
|
+
input.classList.remove('translation-settings-locked');
|
|
50
|
+
}
|
|
51
|
+
});
|
|
52
|
+
// Update the save button state
|
|
53
|
+
const saveButtonElement = document.querySelector('button#action-save');
|
|
54
|
+
const saveButton = saveButtonElement;
|
|
55
|
+
if (saveButton) {
|
|
56
|
+
if (isLocked) {
|
|
57
|
+
saveButton.setAttribute('disabled', 'true');
|
|
58
|
+
saveButton.classList.add('translation-settings-save-locked');
|
|
59
|
+
} else {
|
|
60
|
+
saveButton.removeAttribute('disabled');
|
|
61
|
+
saveButton.classList.remove('translation-settings-save-locked');
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
}, [
|
|
65
|
+
isLocked,
|
|
66
|
+
findForm
|
|
67
|
+
]);
|
|
68
|
+
// Debounced version of applyLockState for MutationObserver
|
|
69
|
+
const debouncedApplyLockState = useCallback(()=>{
|
|
70
|
+
if (debounceTimerRef.current) {
|
|
71
|
+
clearTimeout(debounceTimerRef.current);
|
|
72
|
+
}
|
|
73
|
+
debounceTimerRef.current = setTimeout(()=>{
|
|
74
|
+
applyLockState();
|
|
75
|
+
}, 100); // 100ms debounce
|
|
76
|
+
}, [
|
|
77
|
+
applyLockState
|
|
78
|
+
]);
|
|
79
|
+
// Setup MutationObserver for form changes
|
|
80
|
+
useEffect(()=>{
|
|
81
|
+
// Initial application
|
|
82
|
+
const initialTimeout = setTimeout(applyLockState, 100);
|
|
83
|
+
// Setup observer to watch for dynamic field additions
|
|
84
|
+
const form = findForm();
|
|
85
|
+
if (form) {
|
|
86
|
+
observerRef.current = new MutationObserver(debouncedApplyLockState);
|
|
87
|
+
// Only observe the form element, not the entire body (much more efficient)
|
|
88
|
+
observerRef.current.observe(form, {
|
|
89
|
+
attributes: false,
|
|
90
|
+
childList: true,
|
|
91
|
+
subtree: true
|
|
92
|
+
});
|
|
93
|
+
}
|
|
94
|
+
return ()=>{
|
|
95
|
+
clearTimeout(initialTimeout);
|
|
96
|
+
if (debounceTimerRef.current) {
|
|
97
|
+
clearTimeout(debounceTimerRef.current);
|
|
98
|
+
}
|
|
99
|
+
if (observerRef.current) {
|
|
100
|
+
observerRef.current.disconnect();
|
|
101
|
+
observerRef.current = null;
|
|
102
|
+
}
|
|
103
|
+
};
|
|
104
|
+
}, [
|
|
105
|
+
isLocked,
|
|
106
|
+
applyLockState,
|
|
107
|
+
debouncedApplyLockState,
|
|
108
|
+
findForm
|
|
109
|
+
]);
|
|
110
|
+
// Listen for form submission to auto-lock after save
|
|
111
|
+
useEffect(()=>{
|
|
112
|
+
const form = findForm();
|
|
113
|
+
if (!form) {
|
|
114
|
+
return;
|
|
115
|
+
}
|
|
116
|
+
const handleSubmit = ()=>{
|
|
117
|
+
// Lock after a delay to ensure save completes
|
|
118
|
+
setTimeout(()=>{
|
|
119
|
+
setIsLocked(true);
|
|
120
|
+
}, 2000);
|
|
121
|
+
};
|
|
122
|
+
form.addEventListener('submit', handleSubmit);
|
|
123
|
+
return ()=>{
|
|
124
|
+
form.removeEventListener('submit', handleSubmit);
|
|
125
|
+
};
|
|
126
|
+
}, [
|
|
127
|
+
findForm
|
|
128
|
+
]);
|
|
129
|
+
const toggleLock = useCallback(()=>{
|
|
130
|
+
setIsLocked((prev)=>!prev);
|
|
131
|
+
}, [
|
|
132
|
+
isLocked
|
|
133
|
+
]);
|
|
134
|
+
return /*#__PURE__*/ _jsx("div", {
|
|
135
|
+
className: "translation-settings-lock-container",
|
|
136
|
+
children: /*#__PURE__*/ _jsxs("button", {
|
|
137
|
+
className: `translation-settings-lock-button ${isLocked ? 'locked' : 'unlocked'}`,
|
|
138
|
+
onClick: toggleLock,
|
|
139
|
+
title: isLocked ? '🔒 Settings are locked to prevent accidental changes. Click to unlock and edit.' : '🔓 Settings are unlocked. Click to lock after saving your changes.',
|
|
140
|
+
type: "button",
|
|
141
|
+
children: [
|
|
142
|
+
/*#__PURE__*/ _jsx("span", {
|
|
143
|
+
className: "translation-settings-lock-icon",
|
|
144
|
+
children: isLocked ? '🔒' : '🔓'
|
|
145
|
+
}),
|
|
146
|
+
/*#__PURE__*/ _jsx("span", {
|
|
147
|
+
className: "translation-settings-lock-label",
|
|
148
|
+
children: isLocked ? 'Unlock Settings' : 'Lock Settings'
|
|
149
|
+
})
|
|
150
|
+
]
|
|
151
|
+
})
|
|
152
|
+
});
|
|
153
|
+
};
|
|
154
|
+
|
|
155
|
+
//# sourceMappingURL=TranslationSettingsLock.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/components/TranslationSettingsLock.tsx"],"sourcesContent":["'use client'\n\nimport React, { useCallback, useEffect, useRef, useState } from 'react'\n\nimport './TranslationSettingsLock.css'\n\n/**\n * Component that provides lock/unlock functionality for translation settings\n * - Fields are locked by default (read-only)\n * - User must click \"Unlock\" to edit\n * - After saving, fields automatically lock again\n */\nexport const TranslationSettingsLock: React.FC = () => {\n const [isLocked, setIsLocked] = useState(true)\n const formRef = useRef<HTMLFormElement | null>(null)\n const observerRef = useRef<MutationObserver | null>(null)\n const debounceTimerRef = useRef<NodeJS.Timeout | null>(null)\n\n // Find form element (memoized selector logic)\n const findForm = useCallback((): HTMLFormElement | null => {\n if (formRef.current && document.contains(formRef.current)) {\n return formRef.current\n }\n\n const formElement =\n document.querySelector('form[id*=\"translation-settings\"]') ||\n document.querySelector('form[id*=\"Global\"]') ||\n document.querySelector('form.render-fields') ||\n document.querySelector('form')\n\n const form = formElement as HTMLFormElement | null\n\n if (form) {\n formRef.current = form\n }\n\n return form\n }, [])\n\n // Apply lock/unlock state to all form fields (optimized with caching)\n const applyLockState = useCallback(() => {\n const form = findForm()\n\n if (!form) {\n return\n }\n\n // Find all input fields - using more efficient selector\n const inputs = form.querySelectorAll<HTMLInputElement | HTMLTextAreaElement>(\n 'input[type=\"text\"], input[type=\"number\"], textarea',\n )\n\n // Batch DOM updates for better performance\n inputs.forEach((input) => {\n // Skip if this is the lock control itself\n if (input.closest('.translation-settings-lock-container')) {\n return\n }\n\n // Use single operation to update multiple attributes\n if (isLocked) {\n input.setAttribute('readonly', 'true')\n input.setAttribute('disabled', 'true')\n input.classList.add('translation-settings-locked')\n } else {\n input.removeAttribute('readonly')\n input.removeAttribute('disabled')\n input.classList.remove('translation-settings-locked')\n }\n })\n\n // Update the save button state\n const saveButtonElement = document.querySelector('button#action-save')\n const saveButton = saveButtonElement as HTMLButtonElement | null\n\n if (saveButton) {\n if (isLocked) {\n saveButton.setAttribute('disabled', 'true')\n saveButton.classList.add('translation-settings-save-locked')\n } else {\n saveButton.removeAttribute('disabled')\n saveButton.classList.remove('translation-settings-save-locked')\n }\n }\n }, [isLocked, findForm])\n\n // Debounced version of applyLockState for MutationObserver\n const debouncedApplyLockState = useCallback(() => {\n if (debounceTimerRef.current) {\n clearTimeout(debounceTimerRef.current)\n }\n\n debounceTimerRef.current = setTimeout(() => {\n applyLockState()\n }, 100) // 100ms debounce\n }, [applyLockState])\n\n // Setup MutationObserver for form changes\n useEffect(() => {\n // Initial application\n const initialTimeout = setTimeout(applyLockState, 100)\n\n // Setup observer to watch for dynamic field additions\n const form = findForm()\n\n if (form) {\n observerRef.current = new MutationObserver(debouncedApplyLockState)\n\n // Only observe the form element, not the entire body (much more efficient)\n observerRef.current.observe(form, {\n attributes: false, // Don't watch attribute changes, only structure\n childList: true,\n subtree: true,\n })\n }\n\n return () => {\n clearTimeout(initialTimeout)\n if (debounceTimerRef.current) {\n clearTimeout(debounceTimerRef.current)\n }\n if (observerRef.current) {\n observerRef.current.disconnect()\n observerRef.current = null\n }\n }\n }, [isLocked, applyLockState, debouncedApplyLockState, findForm])\n\n // Listen for form submission to auto-lock after save\n useEffect(() => {\n const form = findForm()\n\n if (!form) {\n return\n }\n\n const handleSubmit = () => {\n // Lock after a delay to ensure save completes\n setTimeout(() => {\n setIsLocked(true)\n }, 2000)\n }\n\n form.addEventListener('submit', handleSubmit)\n\n return () => {\n form.removeEventListener('submit', handleSubmit)\n }\n }, [findForm])\n\n const toggleLock = useCallback(() => {\n setIsLocked((prev) => !prev)\n }, [isLocked])\n\n return (\n <div className=\"translation-settings-lock-container\">\n <button\n className={`translation-settings-lock-button ${isLocked ? 'locked' : 'unlocked'}`}\n onClick={toggleLock}\n title={\n isLocked\n ? '🔒 Settings are locked to prevent accidental changes. Click to unlock and edit.'\n : '🔓 Settings are unlocked. Click to lock after saving your changes.'\n }\n type=\"button\"\n >\n <span className=\"translation-settings-lock-icon\">{isLocked ? '🔒' : '🔓'}</span>\n <span className=\"translation-settings-lock-label\">\n {isLocked ? 'Unlock Settings' : 'Lock Settings'}\n </span>\n </button>\n </div>\n )\n}\n"],"names":["React","useCallback","useEffect","useRef","useState","TranslationSettingsLock","isLocked","setIsLocked","formRef","observerRef","debounceTimerRef","findForm","current","document","contains","formElement","querySelector","form","applyLockState","inputs","querySelectorAll","forEach","input","closest","setAttribute","classList","add","removeAttribute","remove","saveButtonElement","saveButton","debouncedApplyLockState","clearTimeout","setTimeout","initialTimeout","MutationObserver","observe","attributes","childList","subtree","disconnect","handleSubmit","addEventListener","removeEventListener","toggleLock","prev","div","className","button","onClick","title","type","span"],"mappings":"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"}
|
package/dist/exports/client.d.ts
CHANGED
package/dist/exports/client.js
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
*/ export { TranslationControl } from '../components/TranslationControl.js';
|
|
1
|
+
export { LockTranslation } from '../components/LockTranslation/index.js';
|
|
2
|
+
export { TranslationControl } from '../components/TranslationControl.js';
|
|
4
3
|
|
|
5
4
|
//# sourceMappingURL=client.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/exports/client.ts"],"sourcesContent":["
|
|
1
|
+
{"version":3,"sources":["../../src/exports/client.ts"],"sourcesContent":["export { LockTranslation } from '../components/LockTranslation/index.js'\nexport { TranslationControl } from '../components/TranslationControl.js'\nexport type * from '../types/index.js'\n"],"names":["LockTranslation","TranslationControl"],"mappings":"AAAA,SAASA,eAAe,QAAQ,yCAAwC;AACxE,SAASC,kBAAkB,QAAQ,sCAAqC"}
|