@ollang-dev/sdk 0.3.3 → 0.3.5
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/browser/index.js +29 -16
- package/dist/browser/ollang-browser.min.js +1 -1
- package/dist/browser/script-loader.js +1 -7
- package/dist/index.js +3 -2
- package/dist/logger.d.ts +6 -0
- package/dist/logger.js +30 -0
- package/dist/tms/config.js +5 -4
- package/dist/tms/multi-content-tms.js +5 -4
- package/dist/tms/server/index.js +72 -77
- package/dist/tms/tms.js +37 -42
- package/dist/tms/ui-dist/assets/{index-HvrqZV5Z.js → index-BPh6OEAp.js} +5 -5
- package/dist/tms/ui-dist/index.html +1 -1
- package/package.json +1 -1
package/dist/browser/index.js
CHANGED
|
@@ -87,10 +87,13 @@ class OllangBrowser {
|
|
|
87
87
|
}
|
|
88
88
|
this.interceptApiCalls();
|
|
89
89
|
this.loadI18nFiles().then(() => {
|
|
90
|
-
|
|
90
|
+
if (this.config.debug)
|
|
91
|
+
console.log(`✅ Loaded ${this.i18nTexts.size} i18n texts from files`);
|
|
91
92
|
this.detectFrameworkI18n();
|
|
92
93
|
setTimeout(() => {
|
|
93
|
-
|
|
94
|
+
if (this.config.debug) {
|
|
95
|
+
console.log(`🔍 Starting capture with ${this.i18nTexts.size} i18n texts and ${this.strapiContentMap.size} Strapi contents tracked`);
|
|
96
|
+
}
|
|
94
97
|
this.startCapture();
|
|
95
98
|
}, 3000);
|
|
96
99
|
});
|
|
@@ -180,7 +183,9 @@ class OllangBrowser {
|
|
|
180
183
|
});
|
|
181
184
|
this.extractStrapiFields(entry.attributes, contentType, entryId, '');
|
|
182
185
|
}
|
|
183
|
-
|
|
186
|
+
if (this.config.debug) {
|
|
187
|
+
console.log(`📦 Strapi [${contentType}]: captured ${entries.length} entries, total tracked: ${this.strapiContentMap.size}`);
|
|
188
|
+
}
|
|
184
189
|
}
|
|
185
190
|
extractStrapiFields(obj, contentType, entryId, fieldPath, depth = 0) {
|
|
186
191
|
if (!obj || typeof obj !== 'object')
|
|
@@ -398,11 +403,13 @@ class OllangBrowser {
|
|
|
398
403
|
const translations = this.getAngularTranslations();
|
|
399
404
|
if (translations) {
|
|
400
405
|
this.extractTextsFromObject(translations);
|
|
401
|
-
|
|
406
|
+
if (this.config.debug)
|
|
407
|
+
console.log('✅ Loaded Angular translations from runtime');
|
|
402
408
|
}
|
|
403
409
|
}
|
|
404
410
|
catch (e) {
|
|
405
|
-
|
|
411
|
+
if (this.config.debug)
|
|
412
|
+
console.warn('Could not auto-detect Angular translations');
|
|
406
413
|
}
|
|
407
414
|
}, 1500);
|
|
408
415
|
if (window.i18next) {
|
|
@@ -411,7 +418,8 @@ class OllangBrowser {
|
|
|
411
418
|
const translations = i18n.store.data[i18n.language];
|
|
412
419
|
if (translations) {
|
|
413
420
|
this.extractTextsFromObject(translations);
|
|
414
|
-
|
|
421
|
+
if (this.config.debug)
|
|
422
|
+
console.log('✅ Loaded React i18next translations');
|
|
415
423
|
}
|
|
416
424
|
}
|
|
417
425
|
}
|
|
@@ -421,12 +429,14 @@ class OllangBrowser {
|
|
|
421
429
|
Object.values(vueI18n.messages).forEach((msgs) => {
|
|
422
430
|
this.extractTextsFromObject(msgs);
|
|
423
431
|
});
|
|
424
|
-
|
|
432
|
+
if (this.config.debug)
|
|
433
|
+
console.log('✅ Loaded Vue i18n translations');
|
|
425
434
|
}
|
|
426
435
|
}
|
|
427
436
|
if (window.__NEXT_DATA__?.props?.pageProps?.messages) {
|
|
428
437
|
this.extractTextsFromObject(window.__NEXT_DATA__.props.pageProps.messages);
|
|
429
|
-
|
|
438
|
+
if (this.config.debug)
|
|
439
|
+
console.log('✅ Loaded Next.js translations');
|
|
430
440
|
}
|
|
431
441
|
}
|
|
432
442
|
getAngularTranslations() {
|
|
@@ -486,7 +496,8 @@ class OllangBrowser {
|
|
|
486
496
|
if (response.ok) {
|
|
487
497
|
const data = await response.json();
|
|
488
498
|
this.extractTextsFromObject(data);
|
|
489
|
-
|
|
499
|
+
if (this.config.debug)
|
|
500
|
+
console.log(`✅ Auto-loaded i18n: ${url}`);
|
|
490
501
|
}
|
|
491
502
|
}
|
|
492
503
|
catch (error) { }
|
|
@@ -501,7 +512,8 @@ class OllangBrowser {
|
|
|
501
512
|
this.extractTextsFromObject(data);
|
|
502
513
|
}
|
|
503
514
|
catch (error) {
|
|
504
|
-
|
|
515
|
+
if (this.config.debug)
|
|
516
|
+
console.warn(`Failed to load i18n file: ${fileUrl}`);
|
|
505
517
|
}
|
|
506
518
|
}
|
|
507
519
|
}
|
|
@@ -1025,7 +1037,8 @@ class OllangBrowser {
|
|
|
1025
1037
|
this.extractTextsFromObject(texts);
|
|
1026
1038
|
}
|
|
1027
1039
|
const added = this.i18nTexts.size - before;
|
|
1028
|
-
|
|
1040
|
+
if (this.config.debug)
|
|
1041
|
+
console.log(`✅ Added ${added} new i18n texts (total: ${this.i18nTexts.size})`);
|
|
1029
1042
|
if (added > 0 && this.capturedContent.size > 0) {
|
|
1030
1043
|
this.clear();
|
|
1031
1044
|
this.scanPage();
|
|
@@ -1060,7 +1073,6 @@ class OllangBrowser {
|
|
|
1060
1073
|
this.showPanelContent();
|
|
1061
1074
|
}
|
|
1062
1075
|
catch (e) {
|
|
1063
|
-
console.error('Failed to validate API key:', e);
|
|
1064
1076
|
this.showApiKeyFormInPanel();
|
|
1065
1077
|
}
|
|
1066
1078
|
}
|
|
@@ -1846,7 +1858,8 @@ class OllangBrowser {
|
|
|
1846
1858
|
}
|
|
1847
1859
|
}
|
|
1848
1860
|
catch (e) {
|
|
1849
|
-
|
|
1861
|
+
if (this.config.debug)
|
|
1862
|
+
console.warn('Failed to load folders');
|
|
1850
1863
|
}
|
|
1851
1864
|
}
|
|
1852
1865
|
updateFolderOptions() {
|
|
@@ -2017,7 +2030,7 @@ class OllangBrowser {
|
|
|
2017
2030
|
}
|
|
2018
2031
|
catch (e) {
|
|
2019
2032
|
if (this.config.debug)
|
|
2020
|
-
console.warn('[Ollang] Could not fetch Strapi field config
|
|
2033
|
+
console.warn('[Ollang] Could not fetch Strapi field config');
|
|
2021
2034
|
}
|
|
2022
2035
|
}
|
|
2023
2036
|
const pathMatchesAllowed = (allowedPath, key) => {
|
|
@@ -2256,7 +2269,8 @@ class OllangBrowser {
|
|
|
2256
2269
|
}
|
|
2257
2270
|
}
|
|
2258
2271
|
catch (e) {
|
|
2259
|
-
|
|
2272
|
+
if (this.config.debug)
|
|
2273
|
+
console.warn('Failed to list scans');
|
|
2260
2274
|
}
|
|
2261
2275
|
const payload = {
|
|
2262
2276
|
url: window.location.href,
|
|
@@ -2283,7 +2297,6 @@ class OllangBrowser {
|
|
|
2283
2297
|
this.showContent(list);
|
|
2284
2298
|
}
|
|
2285
2299
|
catch (e) {
|
|
2286
|
-
console.error('Push to Ollang error:', e);
|
|
2287
2300
|
this.showStatus(`❌ Failed to push to Ollang: ${e.message}`, 'error');
|
|
2288
2301
|
}
|
|
2289
2302
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
!function(t,e){"object"==typeof exports&&"object"==typeof module?module.exports=e():"function"==typeof define&&define.amd?define([],e):"object"==typeof exports?exports.Ollang=e():t.Ollang=e()}(this,()=>(()=>{"use strict";var t={243(t,e){Object.defineProperty(e,"__esModule",{value:!0}),e.OllangBrowser=void 0;class n{constructor(t){if(this.observer=null,this.capturedContent=new Map,this.i18nTexts=new Set,this.i18nNormalized=new Set,this.excludedTexts=new Set,this.selectedContentIds=new Set,this.folders=[],this.selectedFolder="",this.strapiContentMap=new Map,this.strapiMediaMap=new Map,this.detectedStrapiUrls=new Set,this.strapiEntries=new Map,this.strapiLongTextMap=new Map,this.capturedTexts=new Map,this.apiKeyStorageKey="ollang_browser_api_key",this.config={baseUrl:"http://localhost:5972",autoDetectCMS:!0,captureSelectors:["h1","h2","h3","h4","h5","h6","p","span","div","a","button","li","td","th","label"],excludeSelectors:["script","style","noscript",".no-translate","#ollang-debug-panel",".ollang-debug-panel",'[id^="ollang-"]','[class*="ollang-"]'],captureAttributes:["data-cms-field","data-cms-id","data-field-id"],debounceMs:2e3,onContentDetected:()=>{},...t},!this.config.apiKey&&"undefined"!=typeof window){const t=this.getStoredApiKey();t&&(this.config.apiKey=t)}this.selectedFolder=t.selectedFolder||"",this.init()}getStoredApiKey(){try{if("undefined"==typeof window)return null;const t=window.localStorage.getItem(this.apiKeyStorageKey);return t&&t.trim()?t:null}catch{return null}}saveApiKey(t){try{if("undefined"==typeof window)return;window.localStorage.setItem(this.apiKeyStorageKey,t)}catch{}}init(){if("undefined"==typeof window)throw new Error("OllangBrowser can only be used in browser environment");this.interceptApiCalls(),this.loadI18nFiles().then(()=>{console.log(`✅ Loaded ${this.i18nTexts.size} i18n texts from files`),this.detectFrameworkI18n(),setTimeout(()=>{console.log(`🔍 Starting capture with ${this.i18nTexts.size} i18n texts and ${this.strapiContentMap.size} Strapi contents tracked`),this.startCapture()},3e3)})}interceptApiCalls(){this.interceptFetch(),this.interceptXHR()}interceptFetch(){const t=this,e=window.fetch.bind(window);window.fetch=function(n,i){const s="string"==typeof n?n:n?.url||String(n);return e(n,i).then(e=>(t.isStrapiApiUrl(s)&&e.clone().json().then(e=>{t.processStrapiResponse(s,e)}).catch(()=>{}),e))}}interceptXHR(){const t=this,e=XMLHttpRequest.prototype.open,n=XMLHttpRequest.prototype.send;XMLHttpRequest.prototype.open=function(t,n,i,s,o){return this._ollangUrl="string"==typeof n?n:String(n),e.call(this,t,n,i??!0,s??null,o??null)},XMLHttpRequest.prototype.send=function(e){return this.addEventListener("load",function(){if(t.isStrapiApiUrl(this._ollangUrl))try{const e=JSON.parse(this.responseText);t.processStrapiResponse(this._ollangUrl,e)}catch(t){}}),n.call(this,e)}}isStrapiApiUrl(t){return!!t&&(!(!this.config.strapiUrl||!t.includes(this.config.strapiUrl))||[/\/api\/([\w-]+)(\?|\/|$)/,/cms\./,/strapi/i].some(e=>e.test(t)))}extractContentTypeFromUrl(t){const e=t.match(/\/api\/([\w-]+)/);return e?e[1]:null}processStrapiResponse(t,e){const n=this.extractContentTypeFromUrl(t);if(!n)return;try{const e=new URL(t,window.location.origin),n=e.origin!==window.location.origin?e.origin:"";n&&this.detectedStrapiUrls.add(n)}catch(t){}const i=e?.data;if(!i)return;const s=Array.isArray(i)?i:[i];for(const e of s){if(!e||!e.attributes)continue;const i=e.id;this.strapiEntries.set(`${n}:${i}`,{contentType:n,entryId:i,attributes:e.attributes,url:t}),this.extractStrapiFields(e.attributes,n,i,"")}console.log(`📦 Strapi [${n}]: captured ${s.length} entries, total tracked: ${this.strapiContentMap.size}`)}extractStrapiFields(t,e,i,s,o=0){if(t&&"object"==typeof t&&!(o>n.MAX_RECURSION_DEPTH))for(const a of Object.keys(t)){const r=t[a],l=s?`${s}.${a}`:a;if(!n.SKIP_RELATION_KEYS.has(a))if("string"==typeof r&&r.trim().length>=2){if(this.isNonTranslatableField(a,r))continue;const t=this.normalizeText(r);if(t.length<2)continue;if(t.length>n.MAX_FIELD_LENGTH||n.LONG_TEXT_FIELDS.has(a)){const n=`${e}:${i}:${l}`;this.strapiLongTextMap.set(n,{contentType:e,entryId:i,field:l,rawText:r}),this.extractParagraphsFromHtml(r,e,i,l),this.config.debug&&console.log(`📝 Stored long field ${l} (${t.length} chars) for API capture`);continue}this.strapiContentMap.set(t,{contentType:e,entryId:i,field:l,rawText:r})}else if(Array.isArray(r))r.forEach((t,s)=>{if("object"==typeof t&&null!==t)this.extractStrapiFields(t,e,i,`${l}[${s}]`,o+1);else if("string"==typeof t&&t.trim().length>=2){const o=this.normalizeText(t);o.length>=2&&o.length<=n.MAX_FIELD_LENGTH&&this.strapiContentMap.set(o,{contentType:e,entryId:i,field:`${l}[${s}]`,rawText:t})}});else if("object"==typeof r&&null!==r){if("formats"===a||"provider_metadata"===a)continue;if(this.isStrapiMediaObject(r)){this.extractStrapiMedia(r,e,i,l);continue}if("data"===a)continue;this.extractStrapiFields(r,e,i,l,o+1)}}}isStrapiMediaObject(t){if(!t||"object"!=typeof t)return!1;const e=t.data;if(!e)return!1;const n=Array.isArray(e)?e[0]:e;return!(!n?.attributes?.url||!n?.attributes?.mime)}extractStrapiMedia(t,e,n,i){const s=this.config.strapiUrl||[...this.detectedStrapiUrls][0]||"",o=Array.isArray(t.data)?t.data:[t.data];for(const t of o){if(!t?.attributes?.url)continue;const o=t.attributes,a=o.url,r=o.mime||"",l=r.startsWith("image/")||/\.(jpg|jpeg|png|gif|svg|webp|avif)(\?|$)/i.test(a),d=r.startsWith("video/")||/\.(mp4|webm|ogg|mov)(\?|$)/i.test(a);if(!l&&!d)continue;const c=a.startsWith("http")?a:`${s}${a}`,p={contentType:e,entryId:n,field:i,url:c,mime:r,alt:o.alternativeText||o.caption||o.name||void 0};this.strapiMediaMap.set(c,p),a.startsWith("http")||this.strapiMediaMap.set(a,p)}}extractParagraphsFromHtml(t,e,i,s){const o=t.split(/<\/p>|<br\s*\/?>|<\/h[1-6]>|<\/li>|<\/div>/i).map(t=>this.normalizeText(t)).filter(t=>t.length>=10);for(const t of o)t.length>n.MAX_FIELD_LENGTH||this.strapiContentMap.has(t)||this.strapiContentMap.set(t,{contentType:e,entryId:i,field:s,rawText:t})}isNonTranslatableField(t,e){return!!(n.SKIP_KEYS.has(t)||/^https?:\/\//.test(e)||/^\d{4}-\d{2}-\d{2}T/.test(e)||/^[a-f0-9]{16,}$/.test(e)||/^\.(jpg|jpeg|png|gif|svg|webp)$/i.test(e)||/^#[0-9a-fA-F]{3,8}$/.test(e)||"fullName"===t||"firstName"===t||"lastName"===t||/^image\//.test(e)||/^video\//.test(e))}normalizeText(t){return t?t.replace(/<[^>]*>/g," ").replace(/ /g," ").replace(/&/g,"&").replace(/</g,"<").replace(/>/g,">").replace(/"/g,'"').replace(/'/g,"'").replace(/\s+/g," ").trim():""}findStrapiMatch(t){if(!t||0===this.strapiContentMap.size)return null;const e=this.normalizeText(t);if(e.length<3)return null;const n=this.strapiContentMap.get(e);if(n)return n;let i=null,s=0;for(const[t,n]of this.strapiContentMap){const o=e.length,a=t.length;if(!(a<10||o<10)){if(o>=a&&e.includes(t)){const t=a/o;t>s&&t>.6&&(s=t,i=n)}if(a>=o&&t.includes(e)){const t=o/a;t>s&&t>.5&&(s=t,i=n)}if(o>30&&a>30){const r=Math.min(o,a,200);if(e.substring(0,r)===t.substring(0,r)){const t=r/Math.max(o,a);t>s&&t>.5&&(s=t,i=n)}}}}return i&&s>.5?i:null}isI18nText(t){return!!this.i18nTexts.has(t)||!!this.i18nTexts.has(t.trim())||this.i18nNormalized.has(this.normalizeText(t))}detectFrameworkI18n(){if(setTimeout(()=>{try{const t=this.getAngularTranslations();t&&(this.extractTextsFromObject(t),console.log("✅ Loaded Angular translations from runtime"))}catch(t){console.warn("Could not auto-detect Angular translations:",t)}},1500),window.i18next){const t=window.i18next;if(t.store&&t.language){const e=t.store.data[t.language];e&&(this.extractTextsFromObject(e),console.log("✅ Loaded React i18next translations"))}}if(window.__VUE_I18N__){const t=window.__VUE_I18N__;t.messages&&(Object.values(t.messages).forEach(t=>{this.extractTextsFromObject(t)}),console.log("✅ Loaded Vue i18n translations"))}window.__NEXT_DATA__?.props?.pageProps?.messages&&(this.extractTextsFromObject(window.__NEXT_DATA__.props.pageProps.messages),console.log("✅ Loaded Next.js translations"))}getAngularTranslations(){if(window.ng&&window.ng.probe){const t=document.querySelector("app-root");if(t)try{const e=window.ng.probe(t);if(e?.injector){const t=e.injector.get("TranslateService");if(t?.translations)return t.translations}}catch(t){}}if(window.__ANGULAR_TRANSLATIONS__)return window.__ANGULAR_TRANSLATIONS__;try{const t=localStorage.getItem("translations")||sessionStorage.getItem("translations");if(t)return JSON.parse(t)}catch(t){}return null}async loadI18nFiles(){if(!this.config.i18nFiles||0===this.config.i18nFiles.length){const t=["/assets/i18n/","/locales/","/i18n/","/translations/","/messages/"],e=["en","tr","de","es","fr","it","pt","ru","ja","zh","ko","ar","kr","da","fi","nb","nl","sv"];for(const n of t)for(const t of e){const e=`${n}${t}.json`;try{const t=await fetch(e);if(t.ok){const n=await t.json();this.extractTextsFromObject(n),console.log(`✅ Auto-loaded i18n: ${e}`)}}catch(t){}}return}for(const t of this.config.i18nFiles)try{const e=await fetch(t),n=await e.json();this.extractTextsFromObject(n)}catch(e){console.warn(`Failed to load i18n file: ${t}`,e)}}extractTextsFromObject(t){for(const e in t){const n=t[e];"string"==typeof n?(this.i18nTexts.add(n),this.i18nTexts.add(n.trim()),this.i18nNormalized.add(this.normalizeText(n))):"object"==typeof n&&null!==n&&this.extractTextsFromObject(n)}}startCapture(){this.config.autoDetectCMS&&this.detectCMS(),this.startObserving(),this.scanPage()}detectCMS(){(window.__CONTENTFUL_SPACE_ID__||document.querySelector("[data-contentful-entry-id]"))&&(this.config.cmsType="contentful"),(window.__STRAPI__||document.querySelector("[data-strapi-field]"))&&(this.config.cmsType="strapi"),(window.__SANITY__||document.querySelector("[data-sanity]"))&&(this.config.cmsType="sanity"),(document.body.classList.contains("wordpress")||window.wp)&&(this.config.cmsType="wordpress"),!this.config.cmsType&&this.strapiContentMap.size>0&&(this.config.cmsType="strapi"),this.detectedStrapiUrls.size>0&&(this.config.cmsType="strapi",this.config.strapiUrl||(this.config.strapiUrl=[...this.detectedStrapiUrls][0]))}startObserving(){this.observer=new MutationObserver(t=>{this.handleMutations(t)}),this.observer.observe(document.body,{childList:!0,subtree:!0,characterData:!0,attributes:!0,attributeFilter:this.config.captureAttributes})}handleMutations(t){const e=new Set;t.forEach(t=>{"childList"===t.type?t.addedNodes.forEach(t=>e.add(t)):"characterData"!==t.type&&"attributes"!==t.type||e.add(t.target)}),e.forEach(t=>{if(t.nodeType===Node.ELEMENT_NODE){const e=t;this.scanElement(e),e.querySelectorAll&&this.config.captureSelectors.forEach(t=>{try{e.querySelectorAll(t).forEach(t=>this.scanElement(t))}catch(t){}})}})}captureApiContent(){for(const[,t]of this.strapiLongTextMap){const e=this.generateId(`api:${t.contentType}:${t.entryId}`,t.field);if(this.capturedContent.has(e))continue;const n=`${t.contentType}:${t.entryId}`,i=this.strapiEntries.get(n),s=i?.attributes?.route,o={id:e,text:t.rawText,type:"cms",selector:`api://${t.contentType}/${t.entryId}/${t.field}`,xpath:"",tagName:"richtext",attributes:{},url:window.location.href,timestamp:Date.now(),cmsType:"strapi",cmsField:t.field,cmsId:String(t.entryId),strapiContentType:t.contentType,strapiEntryId:t.entryId,strapiField:t.field,strapiRoute:s};this.capturedContent.set(e,o),this.config.onContentDetected([o])}for(const[,t]of this.strapiEntries){const e=t.attributes?.route;if(e)for(const[,n]of this.capturedContent)n.strapiContentType!==t.contentType||n.strapiEntryId!==t.entryId||n.strapiRoute||(n.strapiRoute=e)}}scanPage(){this.config.captureSelectors.forEach(t=>{try{document.querySelectorAll(t).forEach(t=>this.scanElement(t))}catch(t){}}),this.scanMediaElements(),this.captureApiContent()}scanMediaElements(){if(0===this.strapiMediaMap.size)return;const t=["img[src]","video[src]","source[src]","video[poster]"];for(const e of t)document.querySelectorAll(e).forEach(t=>{if(t.closest("#ollang-debug-panel"))return;const n=e.endsWith("[poster]")?t.poster:t.src||t.getAttribute("src")||"";if(!n)return;let i=this.strapiMediaMap.get(n);if(!i)try{const t=new URL(n).pathname;i=this.strapiMediaMap.get(t)}catch{i=this.strapiMediaMap.get(n)}if(!i)return;const s=this.generateId(n,i.contentType+i.entryId);if(this.capturedContent.has(s))return;const o=(i.mime||"").startsWith("video/")||/\.(mp4|webm|ogg|mov)/i.test(n)?"video":"image",a={id:s,text:i.alt||i.field,type:"cms",selector:this.generateSelector(t),xpath:this.generateXPath(t),tagName:t.tagName.toLowerCase(),attributes:this.extractAttributes(t),url:window.location.href,timestamp:Date.now(),cmsType:"strapi",cmsField:i.field,cmsId:String(i.entryId),strapiContentType:i.contentType,strapiEntryId:i.entryId,strapiField:i.field,mediaUrl:i.url,mediaType:o,mediaAlt:i.alt};this.capturedContent.set(s,a),this.config.onContentDetected([a])})}scanElement(t){if(this.config.excludeSelectors.some(e=>{try{return t.matches(e)}catch(t){return!1}}))return;if(t.closest("#ollang-debug-panel"))return;const e=this.getDirectText(t);if(!e||e.trim().length<3)return;const n=e.trim();if(/^\d+$/.test(n))return;if(n.length<5&&!n.includes(" "))return;if(this.excludedTexts.has(n))return;const i=this.normalizeText(n);if(this.capturedTexts.has(i))return;const s=this.findStrapiMatch(n);if(s){if("description"===s.field)return void(this.config.debug&&console.log(`ℹ️ Skipping DOM capture for long field ${s.contentType}/${s.entryId}/${s.field}`));const e=this.createCapturedContent(t,n,s);return void(this.capturedContent.has(e.id)||(this.capturedContent.set(e.id,e),this.capturedTexts.set(i,e.id),this.config.onContentDetected([e]),this.config.debug&&console.log(`✅ CMS [${s.contentType}/${s.entryId}/${s.field}]: "${n.substring(0,60)}..."`)))}if(t.hasAttribute("data-cms")||t.hasAttribute("data-cms-field")||t.hasAttribute("data-strapi-field")||t.hasAttribute("data-strapi-component")||t.hasAttribute("data-contentful-entry-id")||t.hasAttribute("data-contentful-field-id")||t.hasAttribute("data-sanity")||t.hasAttribute("data-sanity-edit-target")){const e=this.createCapturedContent(t,n,null);return void(this.capturedContent.has(e.id)||(this.capturedContent.set(e.id,e),this.capturedTexts.set(i,e.id),this.config.onContentDetected([e])))}if(this.i18nTexts.size>0&&this.strapiContentMap.size>0&&!this.isI18nText(n)){if(this.isLikelyStaticContent(t,n))return;const e=this.createCapturedContent(t,n,null);e.type="dynamic-unmatched",this.capturedContent.has(e.id)||(this.capturedContent.set(e.id,e),this.capturedTexts.set(i,e.id),this.config.onContentDetected([e]))}}getDirectText(t){let e="";for(const n of Array.from(t.childNodes))n.nodeType===Node.TEXT_NODE&&(e+=n.textContent);return e.trim()?e.trim():0===t.children.length?(t.textContent||"").trim():""}isLikelyStaticContent(t,e){return"A"===t.tagName&&e.length<30||"BUTTON"===t.tagName&&e.length<30||!(!t.closest("footer")&&!t.closest("header, nav"))||!!/^(©|\||\+|→|←|×|✓|✗|▶|•)/.test(e)||!!/©\s*\d{4}/.test(e)}createCapturedContent(t,e,n){const i=this.generateSelector(t),s=this.generateXPath(t),o=this.extractCMSField(t),a=this.extractCMSId(t),r={id:this.generateId(i,e),text:e,type:n||o||a?"cms":"dynamic",selector:i,xpath:s,tagName:t.tagName.toLowerCase(),attributes:this.extractAttributes(t),url:window.location.href,timestamp:Date.now()};return this.config.cmsType&&(r.cmsType=this.config.cmsType),n?(r.cmsType="strapi",r.strapiContentType=n.contentType,r.strapiEntryId=n.entryId,r.strapiField=n.field,r.cmsField=n.field,r.cmsId=String(n.entryId)):(o&&(r.cmsField=o),a&&(r.cmsId=a)),r}extractCMSField(t){for(const e of this.config.captureAttributes){const n=t.getAttribute(e);if(n)return n}return t.getAttribute("data-cms-field")||t.getAttribute("data-contentful-field-id")||t.getAttribute("data-strapi-field")||t.getAttribute("data-sanity-edit-target")||void 0}extractCMSId(t){return t.getAttribute("data-cms-id")||t.getAttribute("data-contentful-entry-id")||t.getAttribute("data-strapi-id")||t.getAttribute("data-sanity-document-id")||void 0}extractAttributes(t){const e={};return Array.from(t.attributes).forEach(t=>{t.name.startsWith("data-")&&(e[t.name]=t.value)}),e}generateSelector(t){if(t.id)return`#${t.id}`;const e=[];let n=t;for(;n&&n!==document.body;){let t=n.tagName.toLowerCase();if(n.className){const e=Array.from(n.classList).filter(t=>!t.startsWith("ng-")&&!t.startsWith("tw-")).slice(0,2);e.length&&(t+="."+e.join("."))}e.unshift(t),n=n.parentElement}return e.join(" > ")}generateXPath(t){if(t.id)return`//*[@id="${t.id}"]`;const e=[];let n=t;for(;n&&n!==document.body;){let t=1,i=n.previousElementSibling;for(;i;)i.tagName===n.tagName&&t++,i=i.previousElementSibling;e.unshift(`${n.tagName.toLowerCase()}[${t}]`),n=n.parentElement}return"/"+e.join("/")}generateId(t,e){const n=t+e;let i=0;for(let t=0;t<n.length;t++)i=(i<<5)-i+n.charCodeAt(t),i&=i;return Math.abs(i).toString(36)}capture(){return this.scanPage(),this.groupCmsEntries(),Array.from(this.capturedContent.values())}groupCmsEntries(){const t=new Map,e=[];for(const[,n]of this.capturedContent){const i=n.strapiContentType&&null!=n.strapiEntryId,s=!!n.mediaUrl;if(i&&!s){const e=`${n.strapiContentType}:${n.strapiEntryId}`;t.has(e)||t.set(e,{items:[],entryData:this.strapiEntries.get(e)}),t.get(e).items.push(n)}else e.push(n)}for(const[e,n]of t){const[t,i]=e.split(":"),s=Number(i);if(!n.items.some(t=>"description"===t.strapiField)){const e=`${t}:${s}:description`,i=this.strapiLongTextMap.get(e);i&&n.items.push({id:`api-desc-${t}-${s}`,text:i.rawText,type:"cms",selector:`api://${t}/${s}/description`,xpath:"",tagName:"richtext",attributes:{},url:window.location.href,timestamp:Date.now(),cmsType:"strapi",cmsField:"description",cmsId:String(s),strapiContentType:t,strapiEntryId:s,strapiField:"description"})}}this.capturedContent.clear();for(const[e,n]of t){const[t,i]=e.split(":"),s=Number(i),o=n.items.find(t=>t.strapiField?.includes("title"))||n.items[0],a=n.entryData?.attributes?.route||n.items.find(t=>t.strapiRoute)?.strapiRoute||void 0,r={};for(const t of n.items)t.strapiField&&(r[t.strapiField]=t.text);const l=`cms-entry-${t}-${s}`,d=Object.keys(r).length,c={...o,id:l,text:o.text,tagName:`entry:${d} fields`,strapiRoute:a,cmsFields:r};this.capturedContent.set(l,c)}for(const t of e)this.capturedContent.set(t.id,t)}getCapturedContent(){return Array.from(this.capturedContent.values())}getCmsContent(){return Array.from(this.capturedContent.values()).filter(t=>"cms"===t.type)}getStrapiMetadata(){return{trackedTexts:this.strapiContentMap.size,entries:this.strapiEntries.size,detectedUrls:[...this.detectedStrapiUrls],cmsType:this.config.cmsType}}clear(){this.capturedContent.clear(),this.capturedTexts.clear()}destroy(){this.observer&&(this.observer.disconnect(),this.observer=null);const t=document.getElementById("ollang-debug-panel");t&&t.remove()}addI18nTexts(t){const e=this.i18nTexts.size;Array.isArray(t)?t.forEach(t=>{this.i18nTexts.add(t),this.i18nTexts.add(t.trim()),this.i18nNormalized.add(this.normalizeText(t))}):this.extractTextsFromObject(t);const n=this.i18nTexts.size-e;console.log(`✅ Added ${n} new i18n texts (total: ${this.i18nTexts.size})`),n>0&&this.capturedContent.size>0&&(this.clear(),this.scanPage())}getI18nTextsCount(){return this.i18nTexts.size}getEntryRoutes(){const t={};for(const[e,n]of this.strapiEntries)n.attributes?.route&&(t[e]=n.attributes.route);return t}async showDebugPanel(){if(document.getElementById("ollang-debug-panel"))return;const t=this.createDebugPanel();if(document.body.appendChild(t),this.config.apiKey)try{if(!await this.validateApiKey())return void this.showApiKeyFormInPanel();this.showPanelContent()}catch(t){console.error("Failed to validate API key:",t),this.showApiKeyFormInPanel()}else this.showApiKeyFormInPanel()}async validateApiKey(){try{const t=(this.config.baseUrl||"").replace(/\/$/,"");if(!t)return!1;const e=await fetch(`${t}/scans/folders`,{headers:{"Content-Type":"application/json","x-api-key":this.config.apiKey}});if(!e.ok)return!1;const n=await e.json(),i=Array.isArray(n)?n:n.folders;return i&&Array.isArray(i)&&(this.folders=i,!this.selectedFolder&&i.length>0&&(this.selectedFolder=i[0].name)),!0}catch(t){return!1}}showApiKeyFormInPanel(){const t=document.getElementById("ollang-panel-content");if(!t)return;t.innerHTML='\n <div style="padding: 20px;">\n <h3 style="margin: 0 0 15px 0; font-size: 16px; color: #333;">Ollang API Key Required</h3>\n <p style="margin: 0 0 20px 0; color: #666; font-size: 13px; line-height: 1.5;">\n Enter your Ollang API key for this app. Not the same as any Strapi token.\n </p>\n <div style="margin-bottom: 15px;">\n <label style="display: block; margin-bottom: 6px; font-weight: 500; color: #333; font-size: 13px;">Ollang API Key</label>\n <input type="text" id="ollang-apikey-input" placeholder="Ollang API key"\n style="width: 100%; padding: 8px 10px; border: 2px solid #ddd; border-radius: 4px; font-size: 13px; box-sizing: border-box; font-family: monospace;" />\n </div>\n <div style="display: flex; align-items: center; gap: 8px; margin-bottom: 15px;">\n <div id="ollang-status-indicator" style="width: 10px; height: 10px; border-radius: 50%; background: #ccc; transition: background-color 0.3s;"></div>\n <span id="ollang-status-text" style="font-size: 12px; color: #666;">Not validated</span>\n </div>\n <button id="ollang-validate-btn" class="ollang-btn" style="width: 100%;">Validate & Continue</button>\n </div>\n ';const e=document.getElementById("ollang-apikey-input"),n=document.getElementById("ollang-validate-btn"),i=document.getElementById("ollang-status-indicator"),s=document.getElementById("ollang-status-text"),o=async()=>{const t=e.value.trim();if(!t)return void this.showStatus("Please enter Ollang API key","error");n.disabled=!0,n.textContent="Validating...",i.style.background="#ffc107",s.textContent="Validating...",s.style.color="#ffc107";const o=this.config.apiKey;this.config.apiKey=t;try{await this.validateApiKey()?(this.saveApiKey(t),i.style.background="#28a745",s.textContent="Valid API key ✓",s.style.color="#28a745",await new Promise(t=>setTimeout(t,500)),this.showPanelContent()):(i.style.background="#dc3545",s.textContent="Invalid API key ✗",s.style.color="#dc3545",this.config.apiKey=o,n.disabled=!1,n.textContent="Validate & Continue")}catch(t){i.style.background="#dc3545",s.textContent="Validation failed ✗",s.style.color="#dc3545",this.config.apiKey=o,n.disabled=!1,n.textContent="Validate & Continue"}};n.addEventListener("click",o),e.addEventListener("keypress",t=>{"Enter"===t.key&&o()}),setTimeout(()=>e.focus(),100)}showPanelContent(){const t=document.getElementById("ollang-panel-content");if(!t)return;t.innerHTML="";const e=document.createElement("div");e.id="ollang-stats",e.style.cssText="padding: 15px; border-bottom: 1px solid #ddd; background: #f8f9fa;",this.updateStats(e);const n=document.createElement("div");n.id="ollang-status",n.style.cssText="padding: 10px 15px; border-bottom: 1px solid #ddd; background: #e7f3ff; font-size: 12px; display: none;",n.innerHTML='<span id="ollang-status-text">Ready</span><button id="ollang-status-close" style="float: right; background: none; border: none; cursor: pointer; font-size: 14px;">×</button>';const i=document.createElement("div");i.style.cssText="padding: 12px 16px; border-bottom: 1px solid #e2e8f0; display: flex; flex-direction: column; gap: 10px;",i.innerHTML='\n <div style="display: flex; gap: 8px;">\n <button id="ollang-capture" class="ollang-btn">Capture</button>\n <button id="ollang-clear" class="ollang-btn ollang-btn-ghost">Clear</button>\n </div>\n <div style="display: flex; align-items: flex-end; justify-content: space-between; gap: 12px;">\n <div style="display: flex; flex-direction: column; gap: 4px; flex: 1;">\n <span style="font-size: 11px; font-weight: 500; color: #64748b;">Folder</span>\n <div style="display: flex; gap: 6px; align-items: center;">\n <div id="ollang-folder-dropdown" class="ollang-folder-dropdown">\n <button id="ollang-folder-trigger" type="button" class="ollang-folder-trigger">\n <span id="ollang-folder-label" class="ollang-folder-label">Select folder...</span>\n <span class="ollang-folder-arrow">▾</span>\n </button>\n <div id="ollang-folder-menu" class="ollang-folder-menu"></div>\n </div>\n <button id="ollang-new-folder" class="ollang-btn-sm">+ New</button>\n </div>\n </div>\n <button id="ollang-push-tms" class="ollang-btn ollang-btn-primary">Push to Ollang</button>\n </div>\n <div id="ollang-strapi-schema-block" style="margin-top: 10px; padding-top: 10px; border-top: 1px solid #e2e8f0;">\n <span style="font-size: 11px; font-weight: 500; color: #64748b;">Strapi schema (optional)</span>\n <p style="margin: 4px 0 8px 0; font-size: 11px; color: #94a3b8;">Fetch schema here so Push uses Content-Type Builder fields. Use your Strapi API token (not the Ollang API token).</p>\n <div style="display: flex; flex-direction: column; gap: 6px;">\n <input type="text" id="ollang-strapi-url" placeholder="Strapi URL (e.g. https://api.example.com)" style="width: 100%; padding: 6px 8px; border: 1px solid #ddd; border-radius: 4px; font-size: 12px; box-sizing: border-box;" />\n <input type="password" id="ollang-strapi-jwt" placeholder="Strapi Admin JWT token" style="width: 100%; padding: 6px 8px; border: 1px solid #ddd; border-radius: 4px; font-size: 12px; box-sizing: border-box;" />\n <button id="ollang-fetch-schema" class="ollang-btn-sm">Fetch schema</button>\n </div>\n </div>\n ';const s=document.createElement("div");s.id="ollang-selection-info",s.style.cssText="padding: 8px 16px; border-bottom: 1px solid #e2e8f0; background: #f8fafc; font-size: 12px; display: none;",s.innerHTML='\n <div class="ollang-selection-bar">\n <div class="ollang-selection-count">\n <span class="ollang-selection-dot"></span>\n <span id="ollang-selected-count">0</span>\n <span class="ollang-selection-label">items selected</span>\n </div>\n <div class="ollang-selection-actions">\n <button id="ollang-select-all" class="ollang-btn ollang-btn-ghost">Select All</button>\n <button id="ollang-deselect-all" class="ollang-btn ollang-btn-ghost">Deselect All</button>\n <button id="ollang-select-cms-only" class="ollang-btn ollang-selection-cms">Select CMS Only</button>\n </div>\n </div>\n ';const o=document.createElement("div");o.id="ollang-content-list",o.style.cssText="flex: 1; overflow-y: auto; padding: 15px;",this.loadFolders(),t.appendChild(e),t.appendChild(n),t.appendChild(i),t.appendChild(s),t.appendChild(o),setTimeout(()=>{document.getElementById("ollang-capture")?.addEventListener("click",()=>{this.capture(),this.updateStats(e),this.showContent(o),this.showStatus(`Captured ${this.capturedContent.size} items (${this.getCmsContent().length} CMS)`,"success")}),document.getElementById("ollang-clear")?.addEventListener("click",()=>{this.clear(),this.updateStats(e),o.innerHTML='<p style="color: #999; text-align: center;">No content captured</p>',this.showStatus("Cleared all content","success")}),document.getElementById("ollang-select-all")?.addEventListener("click",()=>{Array.from(this.capturedContent.values()).forEach(t=>this.selectedContentIds.add(t.id)),this.showContent(o)}),document.getElementById("ollang-deselect-all")?.addEventListener("click",()=>{this.selectedContentIds.clear(),this.showContent(o)}),document.getElementById("ollang-select-cms-only")?.addEventListener("click",()=>{this.selectedContentIds.clear(),this.getCmsContent().forEach(t=>this.selectedContentIds.add(t.id)),this.showContent(o)}),document.getElementById("ollang-push-tms")?.addEventListener("click",()=>this.pushToOllang()),document.getElementById("ollang-fetch-schema")?.addEventListener("click",()=>this.fetchStrapiSchemaInPanel());const t=document.getElementById("ollang-strapi-url");t&&!t.value&&(t.value=this.config.strapiUrl||[...this.detectedStrapiUrls][0]||""),this.updateFolderOptions();const n=document.getElementById("ollang-folder-dropdown"),i=document.getElementById("ollang-folder-trigger"),s=document.getElementById("ollang-folder-menu");if(i&&s&&n){const t=t=>{t??"true"!==s.getAttribute("data-open")?(s.style.display="block",s.setAttribute("data-open","true")):(s.style.display="none",s.setAttribute("data-open","false"))};i.addEventListener("click",e=>{e.stopPropagation(),t()}),document.addEventListener("click",e=>{n.contains(e.target)||t(!1)})}document.getElementById("ollang-new-folder")?.addEventListener("click",()=>this.showNewFolderDialog()),document.getElementById("ollang-status-close")?.addEventListener("click",()=>this.hideStatus())},0),setInterval(()=>this.updateStats(e),2e3)}createDebugPanel(){const t=document.createElement("div");t.id="ollang-debug-panel",t.style.cssText=["position: fixed","right: 20px","left: auto","transform: none","bottom: 0","width: min(540px, 100% - 40px)","min-height: 320px","max-height: 90vh","background: #ffffff","border-radius: 12px 12px 0 0","border: 1px solid rgba(15, 23, 42, 0.12)","box-shadow: 0 18px 45px rgba(15, 23, 42, 0.25)","z-index: 999999",'font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif',"display: flex","flex-direction: column","overflow: hidden","backdrop-filter: blur(10px)","-webkit-backdrop-filter: blur(10px)","background-clip: padding-box"].join("; ")+";";const e=document.createElement("div");e.style.cssText=["height: 6px","cursor: ns-resize","display: flex","align-items: center","justify-content: center","background: transparent"].join("; ")+";";const n=document.createElement("div");n.style.cssText="width: 36px; height: 3px; border-radius: 999px; background: rgba(148, 163, 184, 0.95);",e.appendChild(n);let i=!1,s=0,o=0;const a=e=>{if(!i)return;const n=s-e.clientY,a=Math.min(Math.max(o+n,140),Math.round(.7*window.innerHeight));t.style.height=`${a}px`},r=()=>{i&&(i=!1,document.removeEventListener("mousemove",a),document.removeEventListener("mouseup",r))};e.addEventListener("mousedown",e=>{i=!0,s=e.clientY,o=t.getBoundingClientRect().height,document.addEventListener("mousemove",a),document.addEventListener("mouseup",r)});const l=document.createElement("div");l.style.cssText=["padding: 10px 16px","border-bottom: 1px solid rgba(148, 163, 184, 0.25)","display: flex","justify-content: space-between","align-items: center","background: #ffffff","border-radius: 12px 12px 0 0","color: #0f172a","box-shadow: 0 1px 0 rgba(15, 23, 42, 0.04)"].join("; ")+";",l.innerHTML='\n <div style="display: flex; align-items: center; gap: 10px;">\n <div style="width: 32px; height: 32px; border-radius: 999px; background: #ffffff; display: flex; align-items: center; justify-content: center; box-shadow: 0 0 0 1px rgba(148, 163, 184, 0.45); padding: 4px;">\n <svg viewBox="0 0 37 32" xmlns="http://www.w3.org/2000/svg" style="width: 26px; height: 22px; display: block;">\n <path d="M35.8246 10.862C34.5972 11.5165 33.2999 12.0249 31.9585 12.3772C30.4527 5.10884 24.8838 0.0517578 18.3428 0.0517578H18.2347C15.3756 0.184498 13.2599 1.58635 12.4149 3.89149C11.2871 6.96393 12.7167 11.1501 15.9666 14.3132C18.6573 16.9259 22.7585 18.4605 26.9677 18.4378C26.2857 21.1303 24.6634 23.4766 22.405 25.037C20.1466 26.5973 17.4072 27.2645 14.7005 26.9134C11.9939 26.5622 9.50584 25.2168 7.70306 23.1296C5.90027 21.0423 4.90653 18.3565 4.90817 15.5759C4.90817 12.9858 6.04543 9.13633 9.25081 6.75996L9.56849 6.52687V0.699269L9.28261 0.854665C8.27975 1.42954 7.30632 2.0563 6.36626 2.73246C1.67098 6.21284 0.0126953 11.6552 0.0126953 15.592C0.0174583 19.7867 1.59692 23.8205 4.427 26.8662C7.25707 29.9119 11.1233 31.7386 15.2329 31.9718C19.3424 32.2049 23.3837 30.8267 26.528 28.12C29.6723 25.4132 31.6812 21.583 32.1427 17.4148C32.5049 17.282 33.0036 17.1428 33.5278 16.9939C34.4967 16.7187 35.4973 16.4338 36.0247 16.1133L36.1168 16.0583V10.7325L35.8246 10.862ZM27.1297 13.4326C24.7312 13.4746 21.4972 12.7851 19.3529 10.6968C17.504 8.89676 16.6495 6.63372 17.0085 5.64626C17.1705 5.21243 17.8598 5.08294 18.3999 5.05056C21.9642 5.0797 26.1639 8.21686 27.1297 13.4326Z" fill="#6148f9" />\n </svg>\n </div>\n <div style="display: flex; flex-direction: column;">\n <span style="font-size: 13px; font-weight: 600; color: #0f172a; letter-spacing: 0.02em;">Ollang</span>\n <span style="font-size: 11px; font-weight: 500; color: #64748b;">CMS Detect</span>\n </div>\n </div>\n <button id="ollang-close"\n style="background: #f8fafc; border-radius: 999px; border: 1px solid rgba(148, 163, 184, 0.6); width: 26px; height: 26px; display: flex; align-items: center; justify-content: center; color: #0f172a; cursor: pointer; font-size: 18px; line-height: 1; padding: 0;">\n ×\n </button>\n ';const d=document.createElement("div");d.id="ollang-panel-content",d.style.cssText="flex: 1; overflow-y: auto; display: flex; flex-direction: column;";const c=document.createElement("style");return c.textContent="\n .ollang-btn {\n padding: 6px 12px;\n border-radius: 6px;\n border: 1px solid #e2e8f0;\n background: #ffffff;\n color: #0f172a;\n cursor: pointer;\n font-size: 12px;\n font-weight: 500;\n display: inline-flex;\n align-items: center;\n justify-content: center;\n gap: 4px;\n box-shadow: 0 1px 2px rgba(15, 23, 42, 0.04);\n }\n .ollang-btn:hover {\n background: #f8fafc;\n border-color: #cbd5f5;\n }\n .ollang-btn:disabled {\n background: #f8fafc;\n border-color: #e2e8f0;\n color: #94a3b8;\n cursor: not-allowed;\n box-shadow: none;\n }\n .ollang-btn-primary {\n background: #1d4ed8;\n border-color: #1d4ed8;\n color: #ffffff;\n }\n .ollang-btn-primary:hover {\n background: #1e40af;\n border-color: #1e40af;\n }\n .ollang-btn-ghost {\n background: #f8fafc;\n border-color: #e2e8f0;\n color: #0f172a;\n }\n .ollang-btn-link {\n background: none;\n border: none;\n color: #2563eb;\n cursor: pointer;\n font-size: 11px;\n text-decoration: underline;\n padding: 0;\n }\n .ollang-btn-sm {\n padding: 4px 10px;\n background: #0f172a;\n color: #ffffff;\n border-radius: 999px;\n border: none;\n cursor: pointer;\n font-size: 11px;\n font-weight: 500;\n }\n .ollang-btn-sm:hover {\n background: #020617;\n }\n .ollang-selection-bar {\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: 8px;\n }\n .ollang-selection-count {\n display: inline-flex;\n align-items: center;\n gap: 6px;\n color: #0f172a;\n }\n .ollang-selection-dot {\n width: 8px;\n height: 8px;\n border-radius: 999px;\n background: #22c55e;\n box-shadow: 0 0 0 3px rgba(34, 197, 94, 0.25);\n }\n .ollang-selection-label {\n font-size: 11px;\n color: #64748b;\n }\n .ollang-selection-actions {\n display: inline-flex;\n align-items: center;\n gap: 8px;\n }\n .ollang-selection-cms {\n color: #16a34a;\n }\n .ollang-folder-dropdown {\n position: relative;\n min-width: 220px;\n }\n .ollang-folder-trigger {\n width: 100%;\n padding: 6px 10px;\n border-radius: 999px;\n border: 1px solid #e2e8f0;\n background: #ffffff;\n color: #0f172a;\n font-size: 12px;\n display: flex;\n align-items: center;\n justify-content: space-between;\n cursor: pointer;\n box-shadow: 0 1px 2px rgba(15, 23, 42, 0.04);\n }\n .ollang-folder-trigger:hover {\n border-color: #cbd5f5;\n background: #f8fafc;\n }\n .ollang-folder-label {\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n }\n .ollang-folder-arrow {\n font-size: 10px;\n color: #94a3b8;\n margin-left: 6px;\n }\n .ollang-folder-menu {\n position: absolute;\n top: calc(100% + 4px);\n left: 0;\n right: 0;\n max-height: 200px;\n overflow-y: auto;\n background: #ffffff;\n border-radius: 12px;\n border: 1px solid #e2e8f0;\n box-shadow: 0 10px 25px rgba(15, 23, 42, 0.15);\n padding: 4px;\n display: none;\n z-index: 10;\n }\n .ollang-folder-option {\n width: 100%;\n text-align: left;\n padding: 6px 8px;\n border-radius: 8px;\n border: none;\n background: transparent;\n font-size: 12px;\n color: #0f172a;\n cursor: pointer;\n }\n .ollang-folder-option:hover {\n background: #eff6ff;\n }\n .ollang-folder-option-active {\n background: #1d4ed8;\n color: #ffffff;\n }\n .ollang-content-item {\n background: #f8fafc;\n padding: 10px;\n margin: 6px 0;\n border-radius: 8px;\n font-size: 12px;\n border: 1px solid #e2e8f0;\n display: flex;\n gap: 10px;\n align-items: flex-start;\n }\n .ollang-content-item.cms-matched {\n border-color: #22c55e;\n background: #f0fdf4;\n }\n .ollang-content-item.selected {\n background: #eff6ff;\n border-color: #2563eb;\n }\n .ollang-content-checkbox {\n margin-top: 2px;\n cursor: pointer;\n width: 16px;\n height: 16px;\n }\n .ollang-content-body {\n flex: 1;\n }\n .ollang-content-text {\n font-weight: 600;\n margin-bottom: 5px;\n color: #0f172a;\n }\n .ollang-content-meta {\n color: #64748b;\n font-size: 11px;\n }\n .ollang-cms-badge {\n display: inline-block;\n padding: 1px 6px;\n border-radius: 999px;\n font-size: 10px;\n font-weight: 600;\n }\n .ollang-badge-cms {\n background: #dcfce7;\n color: #15803d;\n }\n .ollang-badge-dynamic {\n background: #fef9c3;\n color: #854d0e;\n }\n .ollang-badge-unmatched {\n background: #fee2e2;\n color: #b91c1c;\n }\n #ollang-apikey-input:focus {\n outline: none;\n border-color: #2563eb;\n }\n .ollang-badge-image {\n background: #ede9fe;\n color: #6d28d9;\n }\n .ollang-badge-video {\n background: #fce7f3;\n color: #be185d;\n }\n .ollang-media-item {\n align-items: center;\n }\n .ollang-media-preview {\n width: 52px;\n height: 40px;\n border-radius: 6px;\n object-fit: cover;\n flex-shrink: 0;\n border: 1px solid #e2e8f0;\n background: #f1f5f9;\n }\n .ollang-media-video-thumb {\n width: 52px;\n height: 40px;\n border-radius: 6px;\n background: #0f172a;\n display: flex;\n align-items: center;\n justify-content: center;\n flex-shrink: 0;\n border: 1px solid #e2e8f0;\n }\n .ollang-media-play {\n color: #ffffff;\n font-size: 14px;\n opacity: 0.9;\n }\n ",t.appendChild(c),t.appendChild(e),t.appendChild(l),t.appendChild(d),setTimeout(()=>{document.getElementById("ollang-close")?.addEventListener("click",()=>t.remove())},0),t}updateStats(t){if(!t)return;const e=this.capturedContent.size,n=this.getCmsContent().length,i=Array.from(this.capturedContent.values()).filter(t=>!!t.mediaUrl),s=i.filter(t=>"image"===t.mediaType).length,o=i.filter(t=>"video"===t.mediaType).length;t.innerHTML=`\n <div style="display: flex; flex-direction: column; gap: 2px;">\n <div style="font-size: 12px; font-weight: 600; color: #0f172a;">\n ${e} captured\n <span style="font-weight: 400; color: #64748b; margin-left: 4px;">(${n} text · ${s} image · ${o} video)</span>\n </div>\n <div style="font-size: 11px; color: #94a3b8;">\n ${this.config.cmsType||"Auto-detect"}${this.config.strapiUrl?" · "+this.config.strapiUrl:""}\n · tracked: ${this.strapiContentMap.size} texts, ${this.strapiMediaMap.size} media\n </div>\n </div>\n `}showContent(t){const e=Array.from(this.capturedContent.values());if(0===e.length)return t.innerHTML='<p style="color: #999; text-align: center;">No content captured yet</p>',void this.updateSelectionInfo();e.sort((t,e)=>"cms"===t.type&&"cms"!==e.type?-1:"cms"!==t.type&&"cms"===e.type?1:e.text.length-t.text.length),t.innerHTML=e.map(t=>{const e=this.selectedContentIds.has(t.id),n="cms"===t.type,i=n?"ollang-badge-cms":"dynamic-unmatched"===t.type?"ollang-badge-unmatched":"ollang-badge-dynamic",s=n?"CMS":"dynamic-unmatched"===t.type?"Unmatched":"Dynamic";if(t.mediaUrl){const i="video"===t.mediaType,o=i?"Video":"Image",a=i?"ollang-badge-video":"ollang-badge-image",r=i?'<div class="ollang-media-preview ollang-media-video-thumb">\n <span class="ollang-media-play">▶</span>\n </div>':`<img class="ollang-media-preview" src="${this.escapeHtml(t.mediaUrl)}" alt="${this.escapeHtml(t.mediaAlt||"")}" loading="lazy" />`;return`<div class="ollang-content-item ${n?"cms-matched":""} ollang-media-item ${e?"selected":""}" data-id="${t.id}">\n <input type="checkbox" class="ollang-content-checkbox" data-id="${t.id}" ${e?"checked":""}>\n ${r}\n <div class="ollang-content-body">\n <div class="ollang-content-text">${this.escapeHtml((t.mediaAlt||t.strapiField||t.mediaUrl).substring(0,80))}</div>\n <div class="ollang-content-meta">\n <span class="ollang-cms-badge ollang-badge-cms">${s}</span>\n <span class="ollang-cms-badge ${a}">${o}</span>\n ${t.strapiContentType?" "+t.strapiContentType:""}${t.strapiEntryId?"#"+t.strapiEntryId:""}${t.strapiField?" → "+t.strapiField:""}\n </div>\n </div>\n </div>`}const o=t.cmsFields?`<strong>${t.strapiContentType}#${t.strapiEntryId}</strong> (${Object.keys(t.cmsFields).join(", ")})`:`<${t.tagName}>${t.strapiContentType?" | "+t.strapiContentType:""}${t.strapiEntryId?"#"+t.strapiEntryId:""}${t.strapiField?" → "+t.strapiField:""}`;return`<div class="ollang-content-item ${n?"cms-matched":""} ${e?"selected":""}" data-id="${t.id}">\n <input type="checkbox" class="ollang-content-checkbox" data-id="${t.id}" ${e?"checked":""}>\n <div class="ollang-content-body">\n <div class="ollang-content-text">${this.escapeHtml(t.text.substring(0,80))}${t.text.length>80?"...":""}</div>\n <div class="ollang-content-meta"><span class="ollang-cms-badge ${i}">${s}</span> ${o}</div>\n </div></div>`}).join(""),t.querySelectorAll(".ollang-content-checkbox").forEach(e=>{e.addEventListener("change",e=>{const n=e.target,i=n.dataset.id,s=t.querySelector(`[data-id="${i}"]`);n.checked?(this.selectedContentIds.add(i),s?.classList.add("selected")):(this.selectedContentIds.delete(i),s?.classList.remove("selected")),this.updateSelectionInfo()})}),this.updateSelectionInfo()}updateSelectionInfo(){const t=document.getElementById("ollang-selection-info"),e=document.getElementById("ollang-selected-count"),n=document.getElementById("ollang-push-tms");t&&e&&(this.selectedContentIds.size>0?(t.style.display="block",e.textContent=String(this.selectedContentIds.size)):t.style.display="none"),n&&(n.disabled=0===this.selectedContentIds.size)}exportContent(){const t=JSON.stringify(Array.from(this.capturedContent.values()),null,2),e=new Blob([t],{type:"application/json"}),n=URL.createObjectURL(e),i=document.createElement("a");i.href=n,i.download=`ollang-captured-${Date.now()}.json`,i.click(),URL.revokeObjectURL(n)}escapeHtml(t){const e=document.createElement("div");return e.textContent=t,e.innerHTML}showStatus(t,e="info"){const n=document.getElementById("ollang-status"),i=document.getElementById("ollang-status-text");n&&i&&(i.textContent=t,n.style.backgroundColor={success:"#d4edda",error:"#f8d7da",info:"#e7f3ff"}[e],n.style.display="block","error"!==e&&setTimeout(()=>{n&&(n.style.display="none")},5e3))}hideStatus(){const t=document.getElementById("ollang-status");t&&(t.style.display="none")}async loadFolders(){try{if(this.folders.length>0)return void this.updateFolderOptions();if(!this.config.apiKey)return;const t=(this.config.baseUrl||"").replace(/\/$/,"");if(!t)return;const e=await fetch(`${t}/scans/folders`,{headers:{"Content-Type":"application/json","x-api-key":this.config.apiKey}});if(e.ok){const t=await e.json(),n=Array.isArray(t)?t:t.folders;n?.length>0&&(this.folders=n,this.selectedFolder||(this.selectedFolder=n[0].name),this.updateFolderOptions())}}catch(t){console.warn("Failed to load folders:",t)}}updateFolderOptions(){const t=document.getElementById("ollang-folder-label"),e=document.getElementById("ollang-folder-menu");if(!t||!e)return;const n=this.selectedFolder||(this.folders[0]?.name??"");n?(this.selectedFolder=n,t.textContent=n):t.textContent="Select folder...",e.innerHTML="",this.folders.forEach(n=>{const i=document.createElement("button");i.type="button",i.className="ollang-folder-option"+(n.name===this.selectedFolder?" ollang-folder-option-active":""),i.textContent=n.name,i.addEventListener("click",()=>{this.selectedFolder=n.name,t.textContent=n.name,e.querySelectorAll(".ollang-folder-option").forEach(t=>t.classList.remove("ollang-folder-option-active")),i.classList.add("ollang-folder-option-active"),e.setAttribute("data-open","false"),e.style.display="none"}),e.appendChild(i)})}showNewFolderDialog(){if(document.getElementById("ollang-new-folder-container"))return;const t=document.querySelector("#ollang-debug-panel #ollang-push-tms")?.parentElement;if(!t)return;const e=document.createElement("div");e.id="ollang-new-folder-container",e.style.cssText="width: 100%; padding: 8px 15px 0 15px; display: flex; gap: 6px; align-items: center;";const n=document.createElement("input");n.type="text",n.placeholder="Enter folder name",n.style.cssText="flex: 1; padding: 4px 8px; border: 1px solid #ddd; border-radius: 4px; font-size: 12px;";const i=document.createElement("button");i.textContent="Create",i.className="ollang-btn-sm";const s=document.createElement("button");s.textContent="Cancel",s.className="ollang-btn-link";const o=()=>{e.parentElement&&e.remove()};i.addEventListener("click",()=>{const t=n.value.trim();t?(this.selectedFolder=t,this.folders.find(e=>e.name===t)||this.folders.push({id:t,name:t}),this.updateFolderOptions(),this.showStatus(`Folder "${t}" selected.`,"success"),o()):this.showStatus("Please enter a folder name","error")}),s.addEventListener("click",o),e.appendChild(n),e.appendChild(i),e.appendChild(s),t.parentElement?.insertBefore(e,t.nextSibling),setTimeout(()=>n.focus(),0)}async fetchStrapiSchemaInPanel(){const t=(this.config.baseUrl||"").replace(/\/$/,"");if(!t)return void this.showStatus("Missing Ollang baseUrl","error");const e=document.getElementById("ollang-strapi-url"),n=document.getElementById("ollang-strapi-jwt"),i=document.getElementById("ollang-fetch-schema"),s=e?.value?.trim(),o=n?.value?.trim();if(s&&o){i&&(i.disabled=!0),this.showStatus("Fetching Strapi schema...","info");try{const e=await fetch(`${t}/api/strapi-schema`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({strapiUrl:s,strapiToken:o})}),n=await e.json().catch(()=>({}));if(e.ok&&n.success){const t=n.contentTypes?.length??0;this.showStatus(`Schema loaded: ${t} content-type(s)`,"success")}else this.showStatus(n.error||`Failed (${e.status})`,"error")}catch(t){this.showStatus("Network error: "+(t instanceof Error?t.message:String(t)),"error")}finally{i&&(i.disabled=!1)}}else this.showStatus("Enter Strapi URL and Strapi API token","error")}async pushToOllang(){if(0===this.selectedContentIds.size)return void this.showStatus("Please select at least one item","error");if(!this.selectedFolder)return void this.showStatus("Please select a folder first","error");if(!this.config.apiKey)return void this.showStatus("Please enter Ollang API token first","error");const t=(this.config.baseUrl||"").replace(/\/$/,"");if(!t)return void this.showStatus("Missing baseUrl","error");const e=Array.from(this.capturedContent.values()).filter(t=>this.selectedContentIds.has(t.id)),n=e.filter(t=>!(!t.mediaUrl||"cms"!==t.type&&"strapi"!==t.cmsType&&!t.strapiContentType)),i=e.filter(t=>!t.mediaUrl||!("cms"===t.type||"strapi"===t.cmsType||t.strapiContentType));if(0!==i.length||0!==n.length){this.showStatus(`Pushing ${i.length} text and ${n.length} media items to Ollang...`,"info");try{const s=i.some(t=>"cms"===t.type||"strapi"===t.cmsType||!!t.strapiContentType)||n.length>0;let o={};const a=this.config.strapiUrl||[...this.detectedStrapiUrls][0]||"";if(s&&a&&t)try{const e=`${t}/api/strapi-field-config?strapiUrl=${encodeURIComponent(a.replace(/\/$/,""))}`,n=await fetch(e,{headers:{"Content-Type":"application/json","x-api-key":this.config.apiKey}});if(n.ok){const t=await n.json();t.fieldsByContentType&&Object.keys(t.fieldsByContentType).length>0&&(o=t.fieldsByContentType,this.config.debug&&console.log("[Ollang] Using dynamic Strapi field config:",o))}}catch(t){this.config.debug&&console.warn("[Ollang] Could not fetch Strapi field config:",t)}const r=(t,e)=>{if(t===e)return!0;if(t.includes("[]")){const n=t.replace(/\./g,"\\.").replace(/\[\]/g,"\\.\\d+");return new RegExp(`^${n}$`).test(e)}return!1},l=(t,e)=>{if(!t||!e)return;const n=e.split(".");let i=t;for(const t of n){if(null==i)return;i=i[t]}return i},d=t=>{if(null!=t){if("string"==typeof t||"number"==typeof t)return String(t);if("object"==typeof t){const e=t?.data?.attributes?.url??t?.url;if("string"==typeof e)return e;try{return JSON.stringify(t)}catch{return}}return String(t)}},c=new Map,p=[];for(const t of i)if(t.strapiContentType&&null!=t.strapiEntryId){const e=`${t.strapiContentType}:${t.strapiEntryId}`;c.has(e)||c.set(e,{items:[],entryData:this.strapiEntries.get(e)}),c.get(e).items.push(t)}else p.push(t);for(const[t,e]of c){const[n,i]=t.split(":"),s=Number(i);if(!e.items.some(t=>"description"===t.strapiField)){const t=`${n}:${s}:description`,i=this.strapiLongTextMap.get(t);i&&e.items.push({id:`api-desc-${n}-${s}`,text:i.rawText,type:"cms",selector:`api://${n}/${s}/description`,xpath:"",tagName:"richtext",attributes:{},url:window.location.href,timestamp:Date.now(),cmsType:"strapi",cmsField:"description",cmsId:String(s),strapiContentType:n,strapiEntryId:s,strapiField:"description"})}}const g=Array.from(c.entries()).map(([t,e])=>{const[n,i]=t.split(":"),s=Number(i),a=e.items.find(t=>t.strapiField?.includes("title"))||e.items[0],c=e.entryData?.attributes?.route||e.items.find(t=>t.strapiRoute)?.strapiRoute||null,p={};for(const t of e.items)t.strapiField&&(p[t.strapiField]=t.text);const g=o[n]??(n.endsWith("s")?o[n.slice(0,-1)]:void 0);if(g&&g.length>0){const t={};for(const[e,n]of Object.entries(p))g.some(t=>r(t,e))&&(t[e]=n);Object.keys(p).forEach(t=>delete p[t]),Object.assign(p,t);const n=e.entryData?.attributes;if(n)for(const t of g){const e=t.replace(/\[\]/g,".0");if(Object.keys(p).find(e=>r(t,e)))continue;const i=l(n,e),s=d(i);void 0!==s&&""!==s&&(p[t]=s)}}return{id:`cms-entry-${n}-${s}`,text:a.text,type:"cms",source:{file:a.selector||"browser-dom",line:0,column:0,context:a.xpath||""},strapiContentType:n,strapiEntryId:s,strapiField:a.strapiField||"header.title",strapiRoute:c,cmsFields:p,selected:!1,status:"scanned"}}),u=p.map(t=>({id:`cms-${t.id}`,text:t.text,type:"cms"===t.type?"cms":"dynamic",source:{file:t.selector||"browser-dom",line:0,column:0,context:t.xpath||""},selected:!1,status:"scanned"})),h=Array.from(this.strapiEntries.values()).map(t=>({contentType:t.contentType,entryId:t.entryId,route:t.attributes?.route||null,locale:t.attributes?.locale||null,title:t.attributes?.header?.title||t.attributes?.title||null})),f={texts:[...g,...u],media:n.map(t=>({id:`media-${t.id}`,mediaUrl:t.mediaUrl,mediaType:t.mediaType,alt:t.mediaAlt,type:"cms-media",source:{file:t.selector||"browser-dom",line:0,column:0,context:t.xpath||""},metadata:{selector:t.selector,xpath:t.xpath,tagName:t.tagName,attributes:t.attributes,cmsType:t.cmsType,cmsField:t.cmsField,cmsId:t.cmsId,strapiContentType:t.strapiContentType,strapiEntryId:t.strapiEntryId,strapiField:t.strapiField,strapiRoute:t.strapiRoute},selected:!1,status:"scanned"})),isCms:s,cms:{strapi:{entries:h}},routes:this.getEntryRoutes(),timestamp:(new Date).toISOString(),projectRoot:window.location.origin,sourceLanguage:"en",targetLanguages:[],projectId:this.config.projectId||"unknown",folderName:this.selectedFolder};let m=null;try{const e=await fetch(`${t}/scans`,{headers:{"Content-Type":"application/json","x-api-key":this.config.apiKey}});if(e.ok){const t=await e.json();if(Array.isArray(t)){const e=window.location.href;let n=null;for(const i of t){const t="string"==typeof i.scanData?JSON.parse(i.scanData):i.scanData;if(t?.folderName!==this.selectedFolder)continue;const s=i.url===e||i.originalUrl===e||t?.projectRoot===window.location.origin;if((!n||s)&&(n=i,s))break}n&&(m=n.id||n._id)}}}catch(t){console.warn("Failed to list scans:",t)}const y={url:window.location.href,scanData:f,originalFilename:`cms-scan-${Date.now()}.json`,folderName:this.selectedFolder},b=m?`${t}/scans/${m}`:`${t}/scans`,x=m?"PATCH":"POST",w=await fetch(b,{method:x,headers:{"Content-Type":"application/json","x-api-key":this.config.apiKey},body:JSON.stringify(y)});if(!w.ok){const t=await w.json();throw new Error(t.message||`Failed: ${w.statusText}`)}const v=await w.json();this.showStatus(`✅ Pushed ${e.length} items to Ollang! Scan ID: ${v.id||"N/A"}`,"success"),this.selectedContentIds.clear();const C=document.getElementById("ollang-content-list");C&&this.showContent(C)}catch(t){console.error("Push to Ollang error:",t),this.showStatus(`❌ Failed to push to Ollang: ${t.message}`,"error")}}else this.showStatus("Nothing to push. Please select at least one text or media item.","info")}}e.OllangBrowser=n,n.MAX_FIELD_LENGTH=500,n.LONG_TEXT_FIELDS=new Set(["description","content","body","html","markdown","richText","text"]),n.MAX_RECURSION_DEPTH=4,n.SKIP_RELATION_KEYS=new Set(["author","editor","localizations","category","categories"]),n.SKIP_KEYS=new Set(["id","createdAt","updatedAt","publishedAt","publishedDate","locale","route","url","path","slug","hash","ext","mime","provider","previewUrl","provider_metadata","background","name","alternativeText","caption","isInvisible","views","size","width","height","isStory","summary"])}},e={};function n(i){var s=e[i];if(void 0!==s)return s.exports;var o=e[i]={exports:{}};return t[i](o,o.exports,n),o.exports}var i={};return(()=>{var t=i;Object.defineProperty(t,"__esModule",{value:!0});const e=n(243);!function(){if("undefined"==typeof window)return;const t=document.currentScript;let n;if(window.ollangConfig)n=window.ollangConfig;else{if(!t)return void console.warn("Ollang: No configuration found. Provide window.ollangConfig or use data attributes.");n={apiKey:t.dataset.apiKey||"",projectId:t.dataset.projectId,baseUrl:t.dataset.baseUrl,strapiUrl:t.dataset.strapiUrl||"",autoDetectCMS:"false"!==t.dataset.autoDetectCms,cmsType:t.dataset.cmsType,debounceMs:parseInt(t.dataset.debounceMs||"1000"),debug:"true"===t.dataset.debug}}function i(){window.Ollang=e.OllangBrowser;const t=new e.OllangBrowser(n);window.ollangInstance=t,window.ollang=t,console.log("✅ Ollang Browser SDK initialized (v2 - API Interception)"),"true"===new URLSearchParams(window.location.search).get("ollang-localize")&&setTimeout(()=>{t.showDebugPanel().catch(t=>{console.error("Failed to show debug panel:",t)})},1e3)}n.apiKey||console.log("Ollang: API key not provided. User will be prompted to enter it."),"loading"===document.readyState?document.addEventListener("DOMContentLoaded",i):i()}()})(),i})());
|
|
1
|
+
!function(t,e){"object"==typeof exports&&"object"==typeof module?module.exports=e():"function"==typeof define&&define.amd?define([],e):"object"==typeof exports?exports.Ollang=e():t.Ollang=e()}(this,()=>(()=>{"use strict";var t={243(t,e){Object.defineProperty(e,"__esModule",{value:!0}),e.OllangBrowser=void 0;class n{constructor(t){if(this.observer=null,this.capturedContent=new Map,this.i18nTexts=new Set,this.i18nNormalized=new Set,this.excludedTexts=new Set,this.selectedContentIds=new Set,this.folders=[],this.selectedFolder="",this.strapiContentMap=new Map,this.strapiMediaMap=new Map,this.detectedStrapiUrls=new Set,this.strapiEntries=new Map,this.strapiLongTextMap=new Map,this.capturedTexts=new Map,this.apiKeyStorageKey="ollang_browser_api_key",this.config={baseUrl:"http://localhost:5972",autoDetectCMS:!0,captureSelectors:["h1","h2","h3","h4","h5","h6","p","span","div","a","button","li","td","th","label"],excludeSelectors:["script","style","noscript",".no-translate","#ollang-debug-panel",".ollang-debug-panel",'[id^="ollang-"]','[class*="ollang-"]'],captureAttributes:["data-cms-field","data-cms-id","data-field-id"],debounceMs:2e3,onContentDetected:()=>{},...t},!this.config.apiKey&&"undefined"!=typeof window){const t=this.getStoredApiKey();t&&(this.config.apiKey=t)}this.selectedFolder=t.selectedFolder||"",this.init()}getStoredApiKey(){try{if("undefined"==typeof window)return null;const t=window.localStorage.getItem(this.apiKeyStorageKey);return t&&t.trim()?t:null}catch{return null}}saveApiKey(t){try{if("undefined"==typeof window)return;window.localStorage.setItem(this.apiKeyStorageKey,t)}catch{}}init(){if("undefined"==typeof window)throw new Error("OllangBrowser can only be used in browser environment");this.interceptApiCalls(),this.loadI18nFiles().then(()=>{this.config.debug&&console.log(`✅ Loaded ${this.i18nTexts.size} i18n texts from files`),this.detectFrameworkI18n(),setTimeout(()=>{this.config.debug&&console.log(`🔍 Starting capture with ${this.i18nTexts.size} i18n texts and ${this.strapiContentMap.size} Strapi contents tracked`),this.startCapture()},3e3)})}interceptApiCalls(){this.interceptFetch(),this.interceptXHR()}interceptFetch(){const t=this,e=window.fetch.bind(window);window.fetch=function(n,i){const s="string"==typeof n?n:n?.url||String(n);return e(n,i).then(e=>(t.isStrapiApiUrl(s)&&e.clone().json().then(e=>{t.processStrapiResponse(s,e)}).catch(()=>{}),e))}}interceptXHR(){const t=this,e=XMLHttpRequest.prototype.open,n=XMLHttpRequest.prototype.send;XMLHttpRequest.prototype.open=function(t,n,i,s,o){return this._ollangUrl="string"==typeof n?n:String(n),e.call(this,t,n,i??!0,s??null,o??null)},XMLHttpRequest.prototype.send=function(e){return this.addEventListener("load",function(){if(t.isStrapiApiUrl(this._ollangUrl))try{const e=JSON.parse(this.responseText);t.processStrapiResponse(this._ollangUrl,e)}catch(t){}}),n.call(this,e)}}isStrapiApiUrl(t){return!!t&&(!(!this.config.strapiUrl||!t.includes(this.config.strapiUrl))||[/\/api\/([\w-]+)(\?|\/|$)/,/cms\./,/strapi/i].some(e=>e.test(t)))}extractContentTypeFromUrl(t){const e=t.match(/\/api\/([\w-]+)/);return e?e[1]:null}processStrapiResponse(t,e){const n=this.extractContentTypeFromUrl(t);if(!n)return;try{const e=new URL(t,window.location.origin),n=e.origin!==window.location.origin?e.origin:"";n&&this.detectedStrapiUrls.add(n)}catch(t){}const i=e?.data;if(!i)return;const s=Array.isArray(i)?i:[i];for(const e of s){if(!e||!e.attributes)continue;const i=e.id;this.strapiEntries.set(`${n}:${i}`,{contentType:n,entryId:i,attributes:e.attributes,url:t}),this.extractStrapiFields(e.attributes,n,i,"")}this.config.debug&&console.log(`📦 Strapi [${n}]: captured ${s.length} entries, total tracked: ${this.strapiContentMap.size}`)}extractStrapiFields(t,e,i,s,o=0){if(t&&"object"==typeof t&&!(o>n.MAX_RECURSION_DEPTH))for(const a of Object.keys(t)){const r=t[a],l=s?`${s}.${a}`:a;if(!n.SKIP_RELATION_KEYS.has(a))if("string"==typeof r&&r.trim().length>=2){if(this.isNonTranslatableField(a,r))continue;const t=this.normalizeText(r);if(t.length<2)continue;if(t.length>n.MAX_FIELD_LENGTH||n.LONG_TEXT_FIELDS.has(a)){const n=`${e}:${i}:${l}`;this.strapiLongTextMap.set(n,{contentType:e,entryId:i,field:l,rawText:r}),this.extractParagraphsFromHtml(r,e,i,l),this.config.debug&&console.log(`📝 Stored long field ${l} (${t.length} chars) for API capture`);continue}this.strapiContentMap.set(t,{contentType:e,entryId:i,field:l,rawText:r})}else if(Array.isArray(r))r.forEach((t,s)=>{if("object"==typeof t&&null!==t)this.extractStrapiFields(t,e,i,`${l}[${s}]`,o+1);else if("string"==typeof t&&t.trim().length>=2){const o=this.normalizeText(t);o.length>=2&&o.length<=n.MAX_FIELD_LENGTH&&this.strapiContentMap.set(o,{contentType:e,entryId:i,field:`${l}[${s}]`,rawText:t})}});else if("object"==typeof r&&null!==r){if("formats"===a||"provider_metadata"===a)continue;if(this.isStrapiMediaObject(r)){this.extractStrapiMedia(r,e,i,l);continue}if("data"===a)continue;this.extractStrapiFields(r,e,i,l,o+1)}}}isStrapiMediaObject(t){if(!t||"object"!=typeof t)return!1;const e=t.data;if(!e)return!1;const n=Array.isArray(e)?e[0]:e;return!(!n?.attributes?.url||!n?.attributes?.mime)}extractStrapiMedia(t,e,n,i){const s=this.config.strapiUrl||[...this.detectedStrapiUrls][0]||"",o=Array.isArray(t.data)?t.data:[t.data];for(const t of o){if(!t?.attributes?.url)continue;const o=t.attributes,a=o.url,r=o.mime||"",l=r.startsWith("image/")||/\.(jpg|jpeg|png|gif|svg|webp|avif)(\?|$)/i.test(a),d=r.startsWith("video/")||/\.(mp4|webm|ogg|mov)(\?|$)/i.test(a);if(!l&&!d)continue;const c=a.startsWith("http")?a:`${s}${a}`,p={contentType:e,entryId:n,field:i,url:c,mime:r,alt:o.alternativeText||o.caption||o.name||void 0};this.strapiMediaMap.set(c,p),a.startsWith("http")||this.strapiMediaMap.set(a,p)}}extractParagraphsFromHtml(t,e,i,s){const o=t.split(/<\/p>|<br\s*\/?>|<\/h[1-6]>|<\/li>|<\/div>/i).map(t=>this.normalizeText(t)).filter(t=>t.length>=10);for(const t of o)t.length>n.MAX_FIELD_LENGTH||this.strapiContentMap.has(t)||this.strapiContentMap.set(t,{contentType:e,entryId:i,field:s,rawText:t})}isNonTranslatableField(t,e){return!!(n.SKIP_KEYS.has(t)||/^https?:\/\//.test(e)||/^\d{4}-\d{2}-\d{2}T/.test(e)||/^[a-f0-9]{16,}$/.test(e)||/^\.(jpg|jpeg|png|gif|svg|webp)$/i.test(e)||/^#[0-9a-fA-F]{3,8}$/.test(e)||"fullName"===t||"firstName"===t||"lastName"===t||/^image\//.test(e)||/^video\//.test(e))}normalizeText(t){return t?t.replace(/<[^>]*>/g," ").replace(/ /g," ").replace(/&/g,"&").replace(/</g,"<").replace(/>/g,">").replace(/"/g,'"').replace(/'/g,"'").replace(/\s+/g," ").trim():""}findStrapiMatch(t){if(!t||0===this.strapiContentMap.size)return null;const e=this.normalizeText(t);if(e.length<3)return null;const n=this.strapiContentMap.get(e);if(n)return n;let i=null,s=0;for(const[t,n]of this.strapiContentMap){const o=e.length,a=t.length;if(!(a<10||o<10)){if(o>=a&&e.includes(t)){const t=a/o;t>s&&t>.6&&(s=t,i=n)}if(a>=o&&t.includes(e)){const t=o/a;t>s&&t>.5&&(s=t,i=n)}if(o>30&&a>30){const r=Math.min(o,a,200);if(e.substring(0,r)===t.substring(0,r)){const t=r/Math.max(o,a);t>s&&t>.5&&(s=t,i=n)}}}}return i&&s>.5?i:null}isI18nText(t){return!!this.i18nTexts.has(t)||!!this.i18nTexts.has(t.trim())||this.i18nNormalized.has(this.normalizeText(t))}detectFrameworkI18n(){if(setTimeout(()=>{try{const t=this.getAngularTranslations();t&&(this.extractTextsFromObject(t),this.config.debug&&console.log("✅ Loaded Angular translations from runtime"))}catch(t){this.config.debug&&console.warn("Could not auto-detect Angular translations")}},1500),window.i18next){const t=window.i18next;if(t.store&&t.language){const e=t.store.data[t.language];e&&(this.extractTextsFromObject(e),this.config.debug&&console.log("✅ Loaded React i18next translations"))}}if(window.__VUE_I18N__){const t=window.__VUE_I18N__;t.messages&&(Object.values(t.messages).forEach(t=>{this.extractTextsFromObject(t)}),this.config.debug&&console.log("✅ Loaded Vue i18n translations"))}window.__NEXT_DATA__?.props?.pageProps?.messages&&(this.extractTextsFromObject(window.__NEXT_DATA__.props.pageProps.messages),this.config.debug&&console.log("✅ Loaded Next.js translations"))}getAngularTranslations(){if(window.ng&&window.ng.probe){const t=document.querySelector("app-root");if(t)try{const e=window.ng.probe(t);if(e?.injector){const t=e.injector.get("TranslateService");if(t?.translations)return t.translations}}catch(t){}}if(window.__ANGULAR_TRANSLATIONS__)return window.__ANGULAR_TRANSLATIONS__;try{const t=localStorage.getItem("translations")||sessionStorage.getItem("translations");if(t)return JSON.parse(t)}catch(t){}return null}async loadI18nFiles(){if(!this.config.i18nFiles||0===this.config.i18nFiles.length){const t=["/assets/i18n/","/locales/","/i18n/","/translations/","/messages/"],e=["en","tr","de","es","fr","it","pt","ru","ja","zh","ko","ar","kr","da","fi","nb","nl","sv"];for(const n of t)for(const t of e){const e=`${n}${t}.json`;try{const t=await fetch(e);if(t.ok){const n=await t.json();this.extractTextsFromObject(n),this.config.debug&&console.log(`✅ Auto-loaded i18n: ${e}`)}}catch(t){}}return}for(const t of this.config.i18nFiles)try{const e=await fetch(t),n=await e.json();this.extractTextsFromObject(n)}catch(e){this.config.debug&&console.warn(`Failed to load i18n file: ${t}`)}}extractTextsFromObject(t){for(const e in t){const n=t[e];"string"==typeof n?(this.i18nTexts.add(n),this.i18nTexts.add(n.trim()),this.i18nNormalized.add(this.normalizeText(n))):"object"==typeof n&&null!==n&&this.extractTextsFromObject(n)}}startCapture(){this.config.autoDetectCMS&&this.detectCMS(),this.startObserving(),this.scanPage()}detectCMS(){(window.__CONTENTFUL_SPACE_ID__||document.querySelector("[data-contentful-entry-id]"))&&(this.config.cmsType="contentful"),(window.__STRAPI__||document.querySelector("[data-strapi-field]"))&&(this.config.cmsType="strapi"),(window.__SANITY__||document.querySelector("[data-sanity]"))&&(this.config.cmsType="sanity"),(document.body.classList.contains("wordpress")||window.wp)&&(this.config.cmsType="wordpress"),!this.config.cmsType&&this.strapiContentMap.size>0&&(this.config.cmsType="strapi"),this.detectedStrapiUrls.size>0&&(this.config.cmsType="strapi",this.config.strapiUrl||(this.config.strapiUrl=[...this.detectedStrapiUrls][0]))}startObserving(){this.observer=new MutationObserver(t=>{this.handleMutations(t)}),this.observer.observe(document.body,{childList:!0,subtree:!0,characterData:!0,attributes:!0,attributeFilter:this.config.captureAttributes})}handleMutations(t){const e=new Set;t.forEach(t=>{"childList"===t.type?t.addedNodes.forEach(t=>e.add(t)):"characterData"!==t.type&&"attributes"!==t.type||e.add(t.target)}),e.forEach(t=>{if(t.nodeType===Node.ELEMENT_NODE){const e=t;this.scanElement(e),e.querySelectorAll&&this.config.captureSelectors.forEach(t=>{try{e.querySelectorAll(t).forEach(t=>this.scanElement(t))}catch(t){}})}})}captureApiContent(){for(const[,t]of this.strapiLongTextMap){const e=this.generateId(`api:${t.contentType}:${t.entryId}`,t.field);if(this.capturedContent.has(e))continue;const n=`${t.contentType}:${t.entryId}`,i=this.strapiEntries.get(n),s=i?.attributes?.route,o={id:e,text:t.rawText,type:"cms",selector:`api://${t.contentType}/${t.entryId}/${t.field}`,xpath:"",tagName:"richtext",attributes:{},url:window.location.href,timestamp:Date.now(),cmsType:"strapi",cmsField:t.field,cmsId:String(t.entryId),strapiContentType:t.contentType,strapiEntryId:t.entryId,strapiField:t.field,strapiRoute:s};this.capturedContent.set(e,o),this.config.onContentDetected([o])}for(const[,t]of this.strapiEntries){const e=t.attributes?.route;if(e)for(const[,n]of this.capturedContent)n.strapiContentType!==t.contentType||n.strapiEntryId!==t.entryId||n.strapiRoute||(n.strapiRoute=e)}}scanPage(){this.config.captureSelectors.forEach(t=>{try{document.querySelectorAll(t).forEach(t=>this.scanElement(t))}catch(t){}}),this.scanMediaElements(),this.captureApiContent()}scanMediaElements(){if(0===this.strapiMediaMap.size)return;const t=["img[src]","video[src]","source[src]","video[poster]"];for(const e of t)document.querySelectorAll(e).forEach(t=>{if(t.closest("#ollang-debug-panel"))return;const n=e.endsWith("[poster]")?t.poster:t.src||t.getAttribute("src")||"";if(!n)return;let i=this.strapiMediaMap.get(n);if(!i)try{const t=new URL(n).pathname;i=this.strapiMediaMap.get(t)}catch{i=this.strapiMediaMap.get(n)}if(!i)return;const s=this.generateId(n,i.contentType+i.entryId);if(this.capturedContent.has(s))return;const o=(i.mime||"").startsWith("video/")||/\.(mp4|webm|ogg|mov)/i.test(n)?"video":"image",a={id:s,text:i.alt||i.field,type:"cms",selector:this.generateSelector(t),xpath:this.generateXPath(t),tagName:t.tagName.toLowerCase(),attributes:this.extractAttributes(t),url:window.location.href,timestamp:Date.now(),cmsType:"strapi",cmsField:i.field,cmsId:String(i.entryId),strapiContentType:i.contentType,strapiEntryId:i.entryId,strapiField:i.field,mediaUrl:i.url,mediaType:o,mediaAlt:i.alt};this.capturedContent.set(s,a),this.config.onContentDetected([a])})}scanElement(t){if(this.config.excludeSelectors.some(e=>{try{return t.matches(e)}catch(t){return!1}}))return;if(t.closest("#ollang-debug-panel"))return;const e=this.getDirectText(t);if(!e||e.trim().length<3)return;const n=e.trim();if(/^\d+$/.test(n))return;if(n.length<5&&!n.includes(" "))return;if(this.excludedTexts.has(n))return;const i=this.normalizeText(n);if(this.capturedTexts.has(i))return;const s=this.findStrapiMatch(n);if(s){if("description"===s.field)return void(this.config.debug&&console.log(`ℹ️ Skipping DOM capture for long field ${s.contentType}/${s.entryId}/${s.field}`));const e=this.createCapturedContent(t,n,s);return void(this.capturedContent.has(e.id)||(this.capturedContent.set(e.id,e),this.capturedTexts.set(i,e.id),this.config.onContentDetected([e]),this.config.debug&&console.log(`✅ CMS [${s.contentType}/${s.entryId}/${s.field}]: "${n.substring(0,60)}..."`)))}if(t.hasAttribute("data-cms")||t.hasAttribute("data-cms-field")||t.hasAttribute("data-strapi-field")||t.hasAttribute("data-strapi-component")||t.hasAttribute("data-contentful-entry-id")||t.hasAttribute("data-contentful-field-id")||t.hasAttribute("data-sanity")||t.hasAttribute("data-sanity-edit-target")){const e=this.createCapturedContent(t,n,null);return void(this.capturedContent.has(e.id)||(this.capturedContent.set(e.id,e),this.capturedTexts.set(i,e.id),this.config.onContentDetected([e])))}if(this.i18nTexts.size>0&&this.strapiContentMap.size>0&&!this.isI18nText(n)){if(this.isLikelyStaticContent(t,n))return;const e=this.createCapturedContent(t,n,null);e.type="dynamic-unmatched",this.capturedContent.has(e.id)||(this.capturedContent.set(e.id,e),this.capturedTexts.set(i,e.id),this.config.onContentDetected([e]))}}getDirectText(t){let e="";for(const n of Array.from(t.childNodes))n.nodeType===Node.TEXT_NODE&&(e+=n.textContent);return e.trim()?e.trim():0===t.children.length?(t.textContent||"").trim():""}isLikelyStaticContent(t,e){return"A"===t.tagName&&e.length<30||"BUTTON"===t.tagName&&e.length<30||!(!t.closest("footer")&&!t.closest("header, nav"))||!!/^(©|\||\+|→|←|×|✓|✗|▶|•)/.test(e)||!!/©\s*\d{4}/.test(e)}createCapturedContent(t,e,n){const i=this.generateSelector(t),s=this.generateXPath(t),o=this.extractCMSField(t),a=this.extractCMSId(t),r={id:this.generateId(i,e),text:e,type:n||o||a?"cms":"dynamic",selector:i,xpath:s,tagName:t.tagName.toLowerCase(),attributes:this.extractAttributes(t),url:window.location.href,timestamp:Date.now()};return this.config.cmsType&&(r.cmsType=this.config.cmsType),n?(r.cmsType="strapi",r.strapiContentType=n.contentType,r.strapiEntryId=n.entryId,r.strapiField=n.field,r.cmsField=n.field,r.cmsId=String(n.entryId)):(o&&(r.cmsField=o),a&&(r.cmsId=a)),r}extractCMSField(t){for(const e of this.config.captureAttributes){const n=t.getAttribute(e);if(n)return n}return t.getAttribute("data-cms-field")||t.getAttribute("data-contentful-field-id")||t.getAttribute("data-strapi-field")||t.getAttribute("data-sanity-edit-target")||void 0}extractCMSId(t){return t.getAttribute("data-cms-id")||t.getAttribute("data-contentful-entry-id")||t.getAttribute("data-strapi-id")||t.getAttribute("data-sanity-document-id")||void 0}extractAttributes(t){const e={};return Array.from(t.attributes).forEach(t=>{t.name.startsWith("data-")&&(e[t.name]=t.value)}),e}generateSelector(t){if(t.id)return`#${t.id}`;const e=[];let n=t;for(;n&&n!==document.body;){let t=n.tagName.toLowerCase();if(n.className){const e=Array.from(n.classList).filter(t=>!t.startsWith("ng-")&&!t.startsWith("tw-")).slice(0,2);e.length&&(t+="."+e.join("."))}e.unshift(t),n=n.parentElement}return e.join(" > ")}generateXPath(t){if(t.id)return`//*[@id="${t.id}"]`;const e=[];let n=t;for(;n&&n!==document.body;){let t=1,i=n.previousElementSibling;for(;i;)i.tagName===n.tagName&&t++,i=i.previousElementSibling;e.unshift(`${n.tagName.toLowerCase()}[${t}]`),n=n.parentElement}return"/"+e.join("/")}generateId(t,e){const n=t+e;let i=0;for(let t=0;t<n.length;t++)i=(i<<5)-i+n.charCodeAt(t),i&=i;return Math.abs(i).toString(36)}capture(){return this.scanPage(),this.groupCmsEntries(),Array.from(this.capturedContent.values())}groupCmsEntries(){const t=new Map,e=[];for(const[,n]of this.capturedContent){const i=n.strapiContentType&&null!=n.strapiEntryId,s=!!n.mediaUrl;if(i&&!s){const e=`${n.strapiContentType}:${n.strapiEntryId}`;t.has(e)||t.set(e,{items:[],entryData:this.strapiEntries.get(e)}),t.get(e).items.push(n)}else e.push(n)}for(const[e,n]of t){const[t,i]=e.split(":"),s=Number(i);if(!n.items.some(t=>"description"===t.strapiField)){const e=`${t}:${s}:description`,i=this.strapiLongTextMap.get(e);i&&n.items.push({id:`api-desc-${t}-${s}`,text:i.rawText,type:"cms",selector:`api://${t}/${s}/description`,xpath:"",tagName:"richtext",attributes:{},url:window.location.href,timestamp:Date.now(),cmsType:"strapi",cmsField:"description",cmsId:String(s),strapiContentType:t,strapiEntryId:s,strapiField:"description"})}}this.capturedContent.clear();for(const[e,n]of t){const[t,i]=e.split(":"),s=Number(i),o=n.items.find(t=>t.strapiField?.includes("title"))||n.items[0],a=n.entryData?.attributes?.route||n.items.find(t=>t.strapiRoute)?.strapiRoute||void 0,r={};for(const t of n.items)t.strapiField&&(r[t.strapiField]=t.text);const l=`cms-entry-${t}-${s}`,d=Object.keys(r).length,c={...o,id:l,text:o.text,tagName:`entry:${d} fields`,strapiRoute:a,cmsFields:r};this.capturedContent.set(l,c)}for(const t of e)this.capturedContent.set(t.id,t)}getCapturedContent(){return Array.from(this.capturedContent.values())}getCmsContent(){return Array.from(this.capturedContent.values()).filter(t=>"cms"===t.type)}getStrapiMetadata(){return{trackedTexts:this.strapiContentMap.size,entries:this.strapiEntries.size,detectedUrls:[...this.detectedStrapiUrls],cmsType:this.config.cmsType}}clear(){this.capturedContent.clear(),this.capturedTexts.clear()}destroy(){this.observer&&(this.observer.disconnect(),this.observer=null);const t=document.getElementById("ollang-debug-panel");t&&t.remove()}addI18nTexts(t){const e=this.i18nTexts.size;Array.isArray(t)?t.forEach(t=>{this.i18nTexts.add(t),this.i18nTexts.add(t.trim()),this.i18nNormalized.add(this.normalizeText(t))}):this.extractTextsFromObject(t);const n=this.i18nTexts.size-e;this.config.debug&&console.log(`✅ Added ${n} new i18n texts (total: ${this.i18nTexts.size})`),n>0&&this.capturedContent.size>0&&(this.clear(),this.scanPage())}getI18nTextsCount(){return this.i18nTexts.size}getEntryRoutes(){const t={};for(const[e,n]of this.strapiEntries)n.attributes?.route&&(t[e]=n.attributes.route);return t}async showDebugPanel(){if(document.getElementById("ollang-debug-panel"))return;const t=this.createDebugPanel();if(document.body.appendChild(t),this.config.apiKey)try{if(!await this.validateApiKey())return void this.showApiKeyFormInPanel();this.showPanelContent()}catch(t){this.showApiKeyFormInPanel()}else this.showApiKeyFormInPanel()}async validateApiKey(){try{const t=(this.config.baseUrl||"").replace(/\/$/,"");if(!t)return!1;const e=await fetch(`${t}/scans/folders`,{headers:{"Content-Type":"application/json","x-api-key":this.config.apiKey}});if(!e.ok)return!1;const n=await e.json(),i=Array.isArray(n)?n:n.folders;return i&&Array.isArray(i)&&(this.folders=i,!this.selectedFolder&&i.length>0&&(this.selectedFolder=i[0].name)),!0}catch(t){return!1}}showApiKeyFormInPanel(){const t=document.getElementById("ollang-panel-content");if(!t)return;t.innerHTML='\n <div style="padding: 20px;">\n <h3 style="margin: 0 0 15px 0; font-size: 16px; color: #333;">Ollang API Key Required</h3>\n <p style="margin: 0 0 20px 0; color: #666; font-size: 13px; line-height: 1.5;">\n Enter your Ollang API key for this app. Not the same as any Strapi token.\n </p>\n <div style="margin-bottom: 15px;">\n <label style="display: block; margin-bottom: 6px; font-weight: 500; color: #333; font-size: 13px;">Ollang API Key</label>\n <input type="text" id="ollang-apikey-input" placeholder="Ollang API key"\n style="width: 100%; padding: 8px 10px; border: 2px solid #ddd; border-radius: 4px; font-size: 13px; box-sizing: border-box; font-family: monospace;" />\n </div>\n <div style="display: flex; align-items: center; gap: 8px; margin-bottom: 15px;">\n <div id="ollang-status-indicator" style="width: 10px; height: 10px; border-radius: 50%; background: #ccc; transition: background-color 0.3s;"></div>\n <span id="ollang-status-text" style="font-size: 12px; color: #666;">Not validated</span>\n </div>\n <button id="ollang-validate-btn" class="ollang-btn" style="width: 100%;">Validate & Continue</button>\n </div>\n ';const e=document.getElementById("ollang-apikey-input"),n=document.getElementById("ollang-validate-btn"),i=document.getElementById("ollang-status-indicator"),s=document.getElementById("ollang-status-text"),o=async()=>{const t=e.value.trim();if(!t)return void this.showStatus("Please enter Ollang API key","error");n.disabled=!0,n.textContent="Validating...",i.style.background="#ffc107",s.textContent="Validating...",s.style.color="#ffc107";const o=this.config.apiKey;this.config.apiKey=t;try{await this.validateApiKey()?(this.saveApiKey(t),i.style.background="#28a745",s.textContent="Valid API key ✓",s.style.color="#28a745",await new Promise(t=>setTimeout(t,500)),this.showPanelContent()):(i.style.background="#dc3545",s.textContent="Invalid API key ✗",s.style.color="#dc3545",this.config.apiKey=o,n.disabled=!1,n.textContent="Validate & Continue")}catch(t){i.style.background="#dc3545",s.textContent="Validation failed ✗",s.style.color="#dc3545",this.config.apiKey=o,n.disabled=!1,n.textContent="Validate & Continue"}};n.addEventListener("click",o),e.addEventListener("keypress",t=>{"Enter"===t.key&&o()}),setTimeout(()=>e.focus(),100)}showPanelContent(){const t=document.getElementById("ollang-panel-content");if(!t)return;t.innerHTML="";const e=document.createElement("div");e.id="ollang-stats",e.style.cssText="padding: 15px; border-bottom: 1px solid #ddd; background: #f8f9fa;",this.updateStats(e);const n=document.createElement("div");n.id="ollang-status",n.style.cssText="padding: 10px 15px; border-bottom: 1px solid #ddd; background: #e7f3ff; font-size: 12px; display: none;",n.innerHTML='<span id="ollang-status-text">Ready</span><button id="ollang-status-close" style="float: right; background: none; border: none; cursor: pointer; font-size: 14px;">×</button>';const i=document.createElement("div");i.style.cssText="padding: 12px 16px; border-bottom: 1px solid #e2e8f0; display: flex; flex-direction: column; gap: 10px;",i.innerHTML='\n <div style="display: flex; gap: 8px;">\n <button id="ollang-capture" class="ollang-btn">Capture</button>\n <button id="ollang-clear" class="ollang-btn ollang-btn-ghost">Clear</button>\n </div>\n <div style="display: flex; align-items: flex-end; justify-content: space-between; gap: 12px;">\n <div style="display: flex; flex-direction: column; gap: 4px; flex: 1;">\n <span style="font-size: 11px; font-weight: 500; color: #64748b;">Folder</span>\n <div style="display: flex; gap: 6px; align-items: center;">\n <div id="ollang-folder-dropdown" class="ollang-folder-dropdown">\n <button id="ollang-folder-trigger" type="button" class="ollang-folder-trigger">\n <span id="ollang-folder-label" class="ollang-folder-label">Select folder...</span>\n <span class="ollang-folder-arrow">▾</span>\n </button>\n <div id="ollang-folder-menu" class="ollang-folder-menu"></div>\n </div>\n <button id="ollang-new-folder" class="ollang-btn-sm">+ New</button>\n </div>\n </div>\n <button id="ollang-push-tms" class="ollang-btn ollang-btn-primary">Push to Ollang</button>\n </div>\n <div id="ollang-strapi-schema-block" style="margin-top: 10px; padding-top: 10px; border-top: 1px solid #e2e8f0;">\n <span style="font-size: 11px; font-weight: 500; color: #64748b;">Strapi schema (optional)</span>\n <p style="margin: 4px 0 8px 0; font-size: 11px; color: #94a3b8;">Fetch schema here so Push uses Content-Type Builder fields. Use your Strapi API token (not the Ollang API token).</p>\n <div style="display: flex; flex-direction: column; gap: 6px;">\n <input type="text" id="ollang-strapi-url" placeholder="Strapi URL (e.g. https://api.example.com)" style="width: 100%; padding: 6px 8px; border: 1px solid #ddd; border-radius: 4px; font-size: 12px; box-sizing: border-box;" />\n <input type="password" id="ollang-strapi-jwt" placeholder="Strapi Admin JWT token" style="width: 100%; padding: 6px 8px; border: 1px solid #ddd; border-radius: 4px; font-size: 12px; box-sizing: border-box;" />\n <button id="ollang-fetch-schema" class="ollang-btn-sm">Fetch schema</button>\n </div>\n </div>\n ';const s=document.createElement("div");s.id="ollang-selection-info",s.style.cssText="padding: 8px 16px; border-bottom: 1px solid #e2e8f0; background: #f8fafc; font-size: 12px; display: none;",s.innerHTML='\n <div class="ollang-selection-bar">\n <div class="ollang-selection-count">\n <span class="ollang-selection-dot"></span>\n <span id="ollang-selected-count">0</span>\n <span class="ollang-selection-label">items selected</span>\n </div>\n <div class="ollang-selection-actions">\n <button id="ollang-select-all" class="ollang-btn ollang-btn-ghost">Select All</button>\n <button id="ollang-deselect-all" class="ollang-btn ollang-btn-ghost">Deselect All</button>\n <button id="ollang-select-cms-only" class="ollang-btn ollang-selection-cms">Select CMS Only</button>\n </div>\n </div>\n ';const o=document.createElement("div");o.id="ollang-content-list",o.style.cssText="flex: 1; overflow-y: auto; padding: 15px;",this.loadFolders(),t.appendChild(e),t.appendChild(n),t.appendChild(i),t.appendChild(s),t.appendChild(o),setTimeout(()=>{document.getElementById("ollang-capture")?.addEventListener("click",()=>{this.capture(),this.updateStats(e),this.showContent(o),this.showStatus(`Captured ${this.capturedContent.size} items (${this.getCmsContent().length} CMS)`,"success")}),document.getElementById("ollang-clear")?.addEventListener("click",()=>{this.clear(),this.updateStats(e),o.innerHTML='<p style="color: #999; text-align: center;">No content captured</p>',this.showStatus("Cleared all content","success")}),document.getElementById("ollang-select-all")?.addEventListener("click",()=>{Array.from(this.capturedContent.values()).forEach(t=>this.selectedContentIds.add(t.id)),this.showContent(o)}),document.getElementById("ollang-deselect-all")?.addEventListener("click",()=>{this.selectedContentIds.clear(),this.showContent(o)}),document.getElementById("ollang-select-cms-only")?.addEventListener("click",()=>{this.selectedContentIds.clear(),this.getCmsContent().forEach(t=>this.selectedContentIds.add(t.id)),this.showContent(o)}),document.getElementById("ollang-push-tms")?.addEventListener("click",()=>this.pushToOllang()),document.getElementById("ollang-fetch-schema")?.addEventListener("click",()=>this.fetchStrapiSchemaInPanel());const t=document.getElementById("ollang-strapi-url");t&&!t.value&&(t.value=this.config.strapiUrl||[...this.detectedStrapiUrls][0]||""),this.updateFolderOptions();const n=document.getElementById("ollang-folder-dropdown"),i=document.getElementById("ollang-folder-trigger"),s=document.getElementById("ollang-folder-menu");if(i&&s&&n){const t=t=>{t??"true"!==s.getAttribute("data-open")?(s.style.display="block",s.setAttribute("data-open","true")):(s.style.display="none",s.setAttribute("data-open","false"))};i.addEventListener("click",e=>{e.stopPropagation(),t()}),document.addEventListener("click",e=>{n.contains(e.target)||t(!1)})}document.getElementById("ollang-new-folder")?.addEventListener("click",()=>this.showNewFolderDialog()),document.getElementById("ollang-status-close")?.addEventListener("click",()=>this.hideStatus())},0),setInterval(()=>this.updateStats(e),2e3)}createDebugPanel(){const t=document.createElement("div");t.id="ollang-debug-panel",t.style.cssText=["position: fixed","right: 20px","left: auto","transform: none","bottom: 0","width: min(540px, 100% - 40px)","min-height: 320px","max-height: 90vh","background: #ffffff","border-radius: 12px 12px 0 0","border: 1px solid rgba(15, 23, 42, 0.12)","box-shadow: 0 18px 45px rgba(15, 23, 42, 0.25)","z-index: 999999",'font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif',"display: flex","flex-direction: column","overflow: hidden","backdrop-filter: blur(10px)","-webkit-backdrop-filter: blur(10px)","background-clip: padding-box"].join("; ")+";";const e=document.createElement("div");e.style.cssText=["height: 6px","cursor: ns-resize","display: flex","align-items: center","justify-content: center","background: transparent"].join("; ")+";";const n=document.createElement("div");n.style.cssText="width: 36px; height: 3px; border-radius: 999px; background: rgba(148, 163, 184, 0.95);",e.appendChild(n);let i=!1,s=0,o=0;const a=e=>{if(!i)return;const n=s-e.clientY,a=Math.min(Math.max(o+n,140),Math.round(.7*window.innerHeight));t.style.height=`${a}px`},r=()=>{i&&(i=!1,document.removeEventListener("mousemove",a),document.removeEventListener("mouseup",r))};e.addEventListener("mousedown",e=>{i=!0,s=e.clientY,o=t.getBoundingClientRect().height,document.addEventListener("mousemove",a),document.addEventListener("mouseup",r)});const l=document.createElement("div");l.style.cssText=["padding: 10px 16px","border-bottom: 1px solid rgba(148, 163, 184, 0.25)","display: flex","justify-content: space-between","align-items: center","background: #ffffff","border-radius: 12px 12px 0 0","color: #0f172a","box-shadow: 0 1px 0 rgba(15, 23, 42, 0.04)"].join("; ")+";",l.innerHTML='\n <div style="display: flex; align-items: center; gap: 10px;">\n <div style="width: 32px; height: 32px; border-radius: 999px; background: #ffffff; display: flex; align-items: center; justify-content: center; box-shadow: 0 0 0 1px rgba(148, 163, 184, 0.45); padding: 4px;">\n <svg viewBox="0 0 37 32" xmlns="http://www.w3.org/2000/svg" style="width: 26px; height: 22px; display: block;">\n <path d="M35.8246 10.862C34.5972 11.5165 33.2999 12.0249 31.9585 12.3772C30.4527 5.10884 24.8838 0.0517578 18.3428 0.0517578H18.2347C15.3756 0.184498 13.2599 1.58635 12.4149 3.89149C11.2871 6.96393 12.7167 11.1501 15.9666 14.3132C18.6573 16.9259 22.7585 18.4605 26.9677 18.4378C26.2857 21.1303 24.6634 23.4766 22.405 25.037C20.1466 26.5973 17.4072 27.2645 14.7005 26.9134C11.9939 26.5622 9.50584 25.2168 7.70306 23.1296C5.90027 21.0423 4.90653 18.3565 4.90817 15.5759C4.90817 12.9858 6.04543 9.13633 9.25081 6.75996L9.56849 6.52687V0.699269L9.28261 0.854665C8.27975 1.42954 7.30632 2.0563 6.36626 2.73246C1.67098 6.21284 0.0126953 11.6552 0.0126953 15.592C0.0174583 19.7867 1.59692 23.8205 4.427 26.8662C7.25707 29.9119 11.1233 31.7386 15.2329 31.9718C19.3424 32.2049 23.3837 30.8267 26.528 28.12C29.6723 25.4132 31.6812 21.583 32.1427 17.4148C32.5049 17.282 33.0036 17.1428 33.5278 16.9939C34.4967 16.7187 35.4973 16.4338 36.0247 16.1133L36.1168 16.0583V10.7325L35.8246 10.862ZM27.1297 13.4326C24.7312 13.4746 21.4972 12.7851 19.3529 10.6968C17.504 8.89676 16.6495 6.63372 17.0085 5.64626C17.1705 5.21243 17.8598 5.08294 18.3999 5.05056C21.9642 5.0797 26.1639 8.21686 27.1297 13.4326Z" fill="#6148f9" />\n </svg>\n </div>\n <div style="display: flex; flex-direction: column;">\n <span style="font-size: 13px; font-weight: 600; color: #0f172a; letter-spacing: 0.02em;">Ollang</span>\n <span style="font-size: 11px; font-weight: 500; color: #64748b;">CMS Detect</span>\n </div>\n </div>\n <button id="ollang-close"\n style="background: #f8fafc; border-radius: 999px; border: 1px solid rgba(148, 163, 184, 0.6); width: 26px; height: 26px; display: flex; align-items: center; justify-content: center; color: #0f172a; cursor: pointer; font-size: 18px; line-height: 1; padding: 0;">\n ×\n </button>\n ';const d=document.createElement("div");d.id="ollang-panel-content",d.style.cssText="flex: 1; overflow-y: auto; display: flex; flex-direction: column;";const c=document.createElement("style");return c.textContent="\n .ollang-btn {\n padding: 6px 12px;\n border-radius: 6px;\n border: 1px solid #e2e8f0;\n background: #ffffff;\n color: #0f172a;\n cursor: pointer;\n font-size: 12px;\n font-weight: 500;\n display: inline-flex;\n align-items: center;\n justify-content: center;\n gap: 4px;\n box-shadow: 0 1px 2px rgba(15, 23, 42, 0.04);\n }\n .ollang-btn:hover {\n background: #f8fafc;\n border-color: #cbd5f5;\n }\n .ollang-btn:disabled {\n background: #f8fafc;\n border-color: #e2e8f0;\n color: #94a3b8;\n cursor: not-allowed;\n box-shadow: none;\n }\n .ollang-btn-primary {\n background: #1d4ed8;\n border-color: #1d4ed8;\n color: #ffffff;\n }\n .ollang-btn-primary:hover {\n background: #1e40af;\n border-color: #1e40af;\n }\n .ollang-btn-ghost {\n background: #f8fafc;\n border-color: #e2e8f0;\n color: #0f172a;\n }\n .ollang-btn-link {\n background: none;\n border: none;\n color: #2563eb;\n cursor: pointer;\n font-size: 11px;\n text-decoration: underline;\n padding: 0;\n }\n .ollang-btn-sm {\n padding: 4px 10px;\n background: #0f172a;\n color: #ffffff;\n border-radius: 999px;\n border: none;\n cursor: pointer;\n font-size: 11px;\n font-weight: 500;\n }\n .ollang-btn-sm:hover {\n background: #020617;\n }\n .ollang-selection-bar {\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: 8px;\n }\n .ollang-selection-count {\n display: inline-flex;\n align-items: center;\n gap: 6px;\n color: #0f172a;\n }\n .ollang-selection-dot {\n width: 8px;\n height: 8px;\n border-radius: 999px;\n background: #22c55e;\n box-shadow: 0 0 0 3px rgba(34, 197, 94, 0.25);\n }\n .ollang-selection-label {\n font-size: 11px;\n color: #64748b;\n }\n .ollang-selection-actions {\n display: inline-flex;\n align-items: center;\n gap: 8px;\n }\n .ollang-selection-cms {\n color: #16a34a;\n }\n .ollang-folder-dropdown {\n position: relative;\n min-width: 220px;\n }\n .ollang-folder-trigger {\n width: 100%;\n padding: 6px 10px;\n border-radius: 999px;\n border: 1px solid #e2e8f0;\n background: #ffffff;\n color: #0f172a;\n font-size: 12px;\n display: flex;\n align-items: center;\n justify-content: space-between;\n cursor: pointer;\n box-shadow: 0 1px 2px rgba(15, 23, 42, 0.04);\n }\n .ollang-folder-trigger:hover {\n border-color: #cbd5f5;\n background: #f8fafc;\n }\n .ollang-folder-label {\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n }\n .ollang-folder-arrow {\n font-size: 10px;\n color: #94a3b8;\n margin-left: 6px;\n }\n .ollang-folder-menu {\n position: absolute;\n top: calc(100% + 4px);\n left: 0;\n right: 0;\n max-height: 200px;\n overflow-y: auto;\n background: #ffffff;\n border-radius: 12px;\n border: 1px solid #e2e8f0;\n box-shadow: 0 10px 25px rgba(15, 23, 42, 0.15);\n padding: 4px;\n display: none;\n z-index: 10;\n }\n .ollang-folder-option {\n width: 100%;\n text-align: left;\n padding: 6px 8px;\n border-radius: 8px;\n border: none;\n background: transparent;\n font-size: 12px;\n color: #0f172a;\n cursor: pointer;\n }\n .ollang-folder-option:hover {\n background: #eff6ff;\n }\n .ollang-folder-option-active {\n background: #1d4ed8;\n color: #ffffff;\n }\n .ollang-content-item {\n background: #f8fafc;\n padding: 10px;\n margin: 6px 0;\n border-radius: 8px;\n font-size: 12px;\n border: 1px solid #e2e8f0;\n display: flex;\n gap: 10px;\n align-items: flex-start;\n }\n .ollang-content-item.cms-matched {\n border-color: #22c55e;\n background: #f0fdf4;\n }\n .ollang-content-item.selected {\n background: #eff6ff;\n border-color: #2563eb;\n }\n .ollang-content-checkbox {\n margin-top: 2px;\n cursor: pointer;\n width: 16px;\n height: 16px;\n }\n .ollang-content-body {\n flex: 1;\n }\n .ollang-content-text {\n font-weight: 600;\n margin-bottom: 5px;\n color: #0f172a;\n }\n .ollang-content-meta {\n color: #64748b;\n font-size: 11px;\n }\n .ollang-cms-badge {\n display: inline-block;\n padding: 1px 6px;\n border-radius: 999px;\n font-size: 10px;\n font-weight: 600;\n }\n .ollang-badge-cms {\n background: #dcfce7;\n color: #15803d;\n }\n .ollang-badge-dynamic {\n background: #fef9c3;\n color: #854d0e;\n }\n .ollang-badge-unmatched {\n background: #fee2e2;\n color: #b91c1c;\n }\n #ollang-apikey-input:focus {\n outline: none;\n border-color: #2563eb;\n }\n .ollang-badge-image {\n background: #ede9fe;\n color: #6d28d9;\n }\n .ollang-badge-video {\n background: #fce7f3;\n color: #be185d;\n }\n .ollang-media-item {\n align-items: center;\n }\n .ollang-media-preview {\n width: 52px;\n height: 40px;\n border-radius: 6px;\n object-fit: cover;\n flex-shrink: 0;\n border: 1px solid #e2e8f0;\n background: #f1f5f9;\n }\n .ollang-media-video-thumb {\n width: 52px;\n height: 40px;\n border-radius: 6px;\n background: #0f172a;\n display: flex;\n align-items: center;\n justify-content: center;\n flex-shrink: 0;\n border: 1px solid #e2e8f0;\n }\n .ollang-media-play {\n color: #ffffff;\n font-size: 14px;\n opacity: 0.9;\n }\n ",t.appendChild(c),t.appendChild(e),t.appendChild(l),t.appendChild(d),setTimeout(()=>{document.getElementById("ollang-close")?.addEventListener("click",()=>t.remove())},0),t}updateStats(t){if(!t)return;const e=this.capturedContent.size,n=this.getCmsContent().length,i=Array.from(this.capturedContent.values()).filter(t=>!!t.mediaUrl),s=i.filter(t=>"image"===t.mediaType).length,o=i.filter(t=>"video"===t.mediaType).length;t.innerHTML=`\n <div style="display: flex; flex-direction: column; gap: 2px;">\n <div style="font-size: 12px; font-weight: 600; color: #0f172a;">\n ${e} captured\n <span style="font-weight: 400; color: #64748b; margin-left: 4px;">(${n} text · ${s} image · ${o} video)</span>\n </div>\n <div style="font-size: 11px; color: #94a3b8;">\n ${this.config.cmsType||"Auto-detect"}${this.config.strapiUrl?" · "+this.config.strapiUrl:""}\n · tracked: ${this.strapiContentMap.size} texts, ${this.strapiMediaMap.size} media\n </div>\n </div>\n `}showContent(t){const e=Array.from(this.capturedContent.values());if(0===e.length)return t.innerHTML='<p style="color: #999; text-align: center;">No content captured yet</p>',void this.updateSelectionInfo();e.sort((t,e)=>"cms"===t.type&&"cms"!==e.type?-1:"cms"!==t.type&&"cms"===e.type?1:e.text.length-t.text.length),t.innerHTML=e.map(t=>{const e=this.selectedContentIds.has(t.id),n="cms"===t.type,i=n?"ollang-badge-cms":"dynamic-unmatched"===t.type?"ollang-badge-unmatched":"ollang-badge-dynamic",s=n?"CMS":"dynamic-unmatched"===t.type?"Unmatched":"Dynamic";if(t.mediaUrl){const i="video"===t.mediaType,o=i?"Video":"Image",a=i?"ollang-badge-video":"ollang-badge-image",r=i?'<div class="ollang-media-preview ollang-media-video-thumb">\n <span class="ollang-media-play">▶</span>\n </div>':`<img class="ollang-media-preview" src="${this.escapeHtml(t.mediaUrl)}" alt="${this.escapeHtml(t.mediaAlt||"")}" loading="lazy" />`;return`<div class="ollang-content-item ${n?"cms-matched":""} ollang-media-item ${e?"selected":""}" data-id="${t.id}">\n <input type="checkbox" class="ollang-content-checkbox" data-id="${t.id}" ${e?"checked":""}>\n ${r}\n <div class="ollang-content-body">\n <div class="ollang-content-text">${this.escapeHtml((t.mediaAlt||t.strapiField||t.mediaUrl).substring(0,80))}</div>\n <div class="ollang-content-meta">\n <span class="ollang-cms-badge ollang-badge-cms">${s}</span>\n <span class="ollang-cms-badge ${a}">${o}</span>\n ${t.strapiContentType?" "+t.strapiContentType:""}${t.strapiEntryId?"#"+t.strapiEntryId:""}${t.strapiField?" → "+t.strapiField:""}\n </div>\n </div>\n </div>`}const o=t.cmsFields?`<strong>${t.strapiContentType}#${t.strapiEntryId}</strong> (${Object.keys(t.cmsFields).join(", ")})`:`<${t.tagName}>${t.strapiContentType?" | "+t.strapiContentType:""}${t.strapiEntryId?"#"+t.strapiEntryId:""}${t.strapiField?" → "+t.strapiField:""}`;return`<div class="ollang-content-item ${n?"cms-matched":""} ${e?"selected":""}" data-id="${t.id}">\n <input type="checkbox" class="ollang-content-checkbox" data-id="${t.id}" ${e?"checked":""}>\n <div class="ollang-content-body">\n <div class="ollang-content-text">${this.escapeHtml(t.text.substring(0,80))}${t.text.length>80?"...":""}</div>\n <div class="ollang-content-meta"><span class="ollang-cms-badge ${i}">${s}</span> ${o}</div>\n </div></div>`}).join(""),t.querySelectorAll(".ollang-content-checkbox").forEach(e=>{e.addEventListener("change",e=>{const n=e.target,i=n.dataset.id,s=t.querySelector(`[data-id="${i}"]`);n.checked?(this.selectedContentIds.add(i),s?.classList.add("selected")):(this.selectedContentIds.delete(i),s?.classList.remove("selected")),this.updateSelectionInfo()})}),this.updateSelectionInfo()}updateSelectionInfo(){const t=document.getElementById("ollang-selection-info"),e=document.getElementById("ollang-selected-count"),n=document.getElementById("ollang-push-tms");t&&e&&(this.selectedContentIds.size>0?(t.style.display="block",e.textContent=String(this.selectedContentIds.size)):t.style.display="none"),n&&(n.disabled=0===this.selectedContentIds.size)}exportContent(){const t=JSON.stringify(Array.from(this.capturedContent.values()),null,2),e=new Blob([t],{type:"application/json"}),n=URL.createObjectURL(e),i=document.createElement("a");i.href=n,i.download=`ollang-captured-${Date.now()}.json`,i.click(),URL.revokeObjectURL(n)}escapeHtml(t){const e=document.createElement("div");return e.textContent=t,e.innerHTML}showStatus(t,e="info"){const n=document.getElementById("ollang-status"),i=document.getElementById("ollang-status-text");n&&i&&(i.textContent=t,n.style.backgroundColor={success:"#d4edda",error:"#f8d7da",info:"#e7f3ff"}[e],n.style.display="block","error"!==e&&setTimeout(()=>{n&&(n.style.display="none")},5e3))}hideStatus(){const t=document.getElementById("ollang-status");t&&(t.style.display="none")}async loadFolders(){try{if(this.folders.length>0)return void this.updateFolderOptions();if(!this.config.apiKey)return;const t=(this.config.baseUrl||"").replace(/\/$/,"");if(!t)return;const e=await fetch(`${t}/scans/folders`,{headers:{"Content-Type":"application/json","x-api-key":this.config.apiKey}});if(e.ok){const t=await e.json(),n=Array.isArray(t)?t:t.folders;n?.length>0&&(this.folders=n,this.selectedFolder||(this.selectedFolder=n[0].name),this.updateFolderOptions())}}catch(t){this.config.debug&&console.warn("Failed to load folders")}}updateFolderOptions(){const t=document.getElementById("ollang-folder-label"),e=document.getElementById("ollang-folder-menu");if(!t||!e)return;const n=this.selectedFolder||(this.folders[0]?.name??"");n?(this.selectedFolder=n,t.textContent=n):t.textContent="Select folder...",e.innerHTML="",this.folders.forEach(n=>{const i=document.createElement("button");i.type="button",i.className="ollang-folder-option"+(n.name===this.selectedFolder?" ollang-folder-option-active":""),i.textContent=n.name,i.addEventListener("click",()=>{this.selectedFolder=n.name,t.textContent=n.name,e.querySelectorAll(".ollang-folder-option").forEach(t=>t.classList.remove("ollang-folder-option-active")),i.classList.add("ollang-folder-option-active"),e.setAttribute("data-open","false"),e.style.display="none"}),e.appendChild(i)})}showNewFolderDialog(){if(document.getElementById("ollang-new-folder-container"))return;const t=document.querySelector("#ollang-debug-panel #ollang-push-tms")?.parentElement;if(!t)return;const e=document.createElement("div");e.id="ollang-new-folder-container",e.style.cssText="width: 100%; padding: 8px 15px 0 15px; display: flex; gap: 6px; align-items: center;";const n=document.createElement("input");n.type="text",n.placeholder="Enter folder name",n.style.cssText="flex: 1; padding: 4px 8px; border: 1px solid #ddd; border-radius: 4px; font-size: 12px;";const i=document.createElement("button");i.textContent="Create",i.className="ollang-btn-sm";const s=document.createElement("button");s.textContent="Cancel",s.className="ollang-btn-link";const o=()=>{e.parentElement&&e.remove()};i.addEventListener("click",()=>{const t=n.value.trim();t?(this.selectedFolder=t,this.folders.find(e=>e.name===t)||this.folders.push({id:t,name:t}),this.updateFolderOptions(),this.showStatus(`Folder "${t}" selected.`,"success"),o()):this.showStatus("Please enter a folder name","error")}),s.addEventListener("click",o),e.appendChild(n),e.appendChild(i),e.appendChild(s),t.parentElement?.insertBefore(e,t.nextSibling),setTimeout(()=>n.focus(),0)}async fetchStrapiSchemaInPanel(){const t=(this.config.baseUrl||"").replace(/\/$/,"");if(!t)return void this.showStatus("Missing Ollang baseUrl","error");const e=document.getElementById("ollang-strapi-url"),n=document.getElementById("ollang-strapi-jwt"),i=document.getElementById("ollang-fetch-schema"),s=e?.value?.trim(),o=n?.value?.trim();if(s&&o){i&&(i.disabled=!0),this.showStatus("Fetching Strapi schema...","info");try{const e=await fetch(`${t}/api/strapi-schema`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({strapiUrl:s,strapiToken:o})}),n=await e.json().catch(()=>({}));if(e.ok&&n.success){const t=n.contentTypes?.length??0;this.showStatus(`Schema loaded: ${t} content-type(s)`,"success")}else this.showStatus(n.error||`Failed (${e.status})`,"error")}catch(t){this.showStatus("Network error: "+(t instanceof Error?t.message:String(t)),"error")}finally{i&&(i.disabled=!1)}}else this.showStatus("Enter Strapi URL and Strapi API token","error")}async pushToOllang(){if(0===this.selectedContentIds.size)return void this.showStatus("Please select at least one item","error");if(!this.selectedFolder)return void this.showStatus("Please select a folder first","error");if(!this.config.apiKey)return void this.showStatus("Please enter Ollang API token first","error");const t=(this.config.baseUrl||"").replace(/\/$/,"");if(!t)return void this.showStatus("Missing baseUrl","error");const e=Array.from(this.capturedContent.values()).filter(t=>this.selectedContentIds.has(t.id)),n=e.filter(t=>!(!t.mediaUrl||"cms"!==t.type&&"strapi"!==t.cmsType&&!t.strapiContentType)),i=e.filter(t=>!t.mediaUrl||!("cms"===t.type||"strapi"===t.cmsType||t.strapiContentType));if(0!==i.length||0!==n.length){this.showStatus(`Pushing ${i.length} text and ${n.length} media items to Ollang...`,"info");try{const s=i.some(t=>"cms"===t.type||"strapi"===t.cmsType||!!t.strapiContentType)||n.length>0;let o={};const a=this.config.strapiUrl||[...this.detectedStrapiUrls][0]||"";if(s&&a&&t)try{const e=`${t}/api/strapi-field-config?strapiUrl=${encodeURIComponent(a.replace(/\/$/,""))}`,n=await fetch(e,{headers:{"Content-Type":"application/json","x-api-key":this.config.apiKey}});if(n.ok){const t=await n.json();t.fieldsByContentType&&Object.keys(t.fieldsByContentType).length>0&&(o=t.fieldsByContentType,this.config.debug&&console.log("[Ollang] Using dynamic Strapi field config:",o))}}catch(t){this.config.debug&&console.warn("[Ollang] Could not fetch Strapi field config")}const r=(t,e)=>{if(t===e)return!0;if(t.includes("[]")){const n=t.replace(/\./g,"\\.").replace(/\[\]/g,"\\.\\d+");return new RegExp(`^${n}$`).test(e)}return!1},l=(t,e)=>{if(!t||!e)return;const n=e.split(".");let i=t;for(const t of n){if(null==i)return;i=i[t]}return i},d=t=>{if(null!=t){if("string"==typeof t||"number"==typeof t)return String(t);if("object"==typeof t){const e=t?.data?.attributes?.url??t?.url;if("string"==typeof e)return e;try{return JSON.stringify(t)}catch{return}}return String(t)}},c=new Map,p=[];for(const t of i)if(t.strapiContentType&&null!=t.strapiEntryId){const e=`${t.strapiContentType}:${t.strapiEntryId}`;c.has(e)||c.set(e,{items:[],entryData:this.strapiEntries.get(e)}),c.get(e).items.push(t)}else p.push(t);for(const[t,e]of c){const[n,i]=t.split(":"),s=Number(i);if(!e.items.some(t=>"description"===t.strapiField)){const t=`${n}:${s}:description`,i=this.strapiLongTextMap.get(t);i&&e.items.push({id:`api-desc-${n}-${s}`,text:i.rawText,type:"cms",selector:`api://${n}/${s}/description`,xpath:"",tagName:"richtext",attributes:{},url:window.location.href,timestamp:Date.now(),cmsType:"strapi",cmsField:"description",cmsId:String(s),strapiContentType:n,strapiEntryId:s,strapiField:"description"})}}const g=Array.from(c.entries()).map(([t,e])=>{const[n,i]=t.split(":"),s=Number(i),a=e.items.find(t=>t.strapiField?.includes("title"))||e.items[0],c=e.entryData?.attributes?.route||e.items.find(t=>t.strapiRoute)?.strapiRoute||null,p={};for(const t of e.items)t.strapiField&&(p[t.strapiField]=t.text);const g=o[n]??(n.endsWith("s")?o[n.slice(0,-1)]:void 0);if(g&&g.length>0){const t={};for(const[e,n]of Object.entries(p))g.some(t=>r(t,e))&&(t[e]=n);Object.keys(p).forEach(t=>delete p[t]),Object.assign(p,t);const n=e.entryData?.attributes;if(n)for(const t of g){const e=t.replace(/\[\]/g,".0");if(Object.keys(p).find(e=>r(t,e)))continue;const i=l(n,e),s=d(i);void 0!==s&&""!==s&&(p[t]=s)}}return{id:`cms-entry-${n}-${s}`,text:a.text,type:"cms",source:{file:a.selector||"browser-dom",line:0,column:0,context:a.xpath||""},strapiContentType:n,strapiEntryId:s,strapiField:a.strapiField||"header.title",strapiRoute:c,cmsFields:p,selected:!1,status:"scanned"}}),u=p.map(t=>({id:`cms-${t.id}`,text:t.text,type:"cms"===t.type?"cms":"dynamic",source:{file:t.selector||"browser-dom",line:0,column:0,context:t.xpath||""},selected:!1,status:"scanned"})),h=Array.from(this.strapiEntries.values()).map(t=>({contentType:t.contentType,entryId:t.entryId,route:t.attributes?.route||null,locale:t.attributes?.locale||null,title:t.attributes?.header?.title||t.attributes?.title||null})),f={texts:[...g,...u],media:n.map(t=>({id:`media-${t.id}`,mediaUrl:t.mediaUrl,mediaType:t.mediaType,alt:t.mediaAlt,type:"cms-media",source:{file:t.selector||"browser-dom",line:0,column:0,context:t.xpath||""},metadata:{selector:t.selector,xpath:t.xpath,tagName:t.tagName,attributes:t.attributes,cmsType:t.cmsType,cmsField:t.cmsField,cmsId:t.cmsId,strapiContentType:t.strapiContentType,strapiEntryId:t.strapiEntryId,strapiField:t.strapiField,strapiRoute:t.strapiRoute},selected:!1,status:"scanned"})),isCms:s,cms:{strapi:{entries:h}},routes:this.getEntryRoutes(),timestamp:(new Date).toISOString(),projectRoot:window.location.origin,sourceLanguage:"en",targetLanguages:[],projectId:this.config.projectId||"unknown",folderName:this.selectedFolder};let m=null;try{const e=await fetch(`${t}/scans`,{headers:{"Content-Type":"application/json","x-api-key":this.config.apiKey}});if(e.ok){const t=await e.json();if(Array.isArray(t)){const e=window.location.href;let n=null;for(const i of t){const t="string"==typeof i.scanData?JSON.parse(i.scanData):i.scanData;if(t?.folderName!==this.selectedFolder)continue;const s=i.url===e||i.originalUrl===e||t?.projectRoot===window.location.origin;if((!n||s)&&(n=i,s))break}n&&(m=n.id||n._id)}}}catch(t){this.config.debug&&console.warn("Failed to list scans")}const y={url:window.location.href,scanData:f,originalFilename:`cms-scan-${Date.now()}.json`,folderName:this.selectedFolder},b=m?`${t}/scans/${m}`:`${t}/scans`,x=m?"PATCH":"POST",w=await fetch(b,{method:x,headers:{"Content-Type":"application/json","x-api-key":this.config.apiKey},body:JSON.stringify(y)});if(!w.ok){const t=await w.json();throw new Error(t.message||`Failed: ${w.statusText}`)}const C=await w.json();this.showStatus(`✅ Pushed ${e.length} items to Ollang! Scan ID: ${C.id||"N/A"}`,"success"),this.selectedContentIds.clear();const v=document.getElementById("ollang-content-list");v&&this.showContent(v)}catch(t){this.showStatus(`❌ Failed to push to Ollang: ${t.message}`,"error")}}else this.showStatus("Nothing to push. Please select at least one text or media item.","info")}}e.OllangBrowser=n,n.MAX_FIELD_LENGTH=500,n.LONG_TEXT_FIELDS=new Set(["description","content","body","html","markdown","richText","text"]),n.MAX_RECURSION_DEPTH=4,n.SKIP_RELATION_KEYS=new Set(["author","editor","localizations","category","categories"]),n.SKIP_KEYS=new Set(["id","createdAt","updatedAt","publishedAt","publishedDate","locale","route","url","path","slug","hash","ext","mime","provider","previewUrl","provider_metadata","background","name","alternativeText","caption","isInvisible","views","size","width","height","isStory","summary"])}},e={};function n(i){var s=e[i];if(void 0!==s)return s.exports;var o=e[i]={exports:{}};return t[i](o,o.exports,n),o.exports}var i={};return(()=>{var t=i;Object.defineProperty(t,"__esModule",{value:!0});const e=n(243);!function(){if("undefined"==typeof window)return;const t=document.currentScript;let n;if(window.ollangConfig)n=window.ollangConfig;else{if(!t)return void console.warn("Ollang: No configuration found. Provide window.ollangConfig or use data attributes.");n={apiKey:t.dataset.apiKey||"",projectId:t.dataset.projectId,baseUrl:t.dataset.baseUrl,strapiUrl:t.dataset.strapiUrl||"",autoDetectCMS:"false"!==t.dataset.autoDetectCms,cmsType:t.dataset.cmsType,debounceMs:parseInt(t.dataset.debounceMs||"1000"),debug:"true"===t.dataset.debug}}function i(){window.Ollang=e.OllangBrowser;const t=new e.OllangBrowser(n);window.ollangInstance=t,window.ollang=t,"true"===new URLSearchParams(window.location.search).get("ollang-localize")&&setTimeout(()=>{t.showDebugPanel().catch(()=>{})},1e3)}"loading"===document.readyState?document.addEventListener("DOMContentLoaded",i):i()}()})(),i})());
|
|
@@ -25,22 +25,16 @@ const index_1 = require("./index");
|
|
|
25
25
|
console.warn('Ollang: No configuration found. Provide window.ollangConfig or use data attributes.');
|
|
26
26
|
return;
|
|
27
27
|
}
|
|
28
|
-
if (!config.apiKey) {
|
|
29
|
-
console.log('Ollang: API key not provided. User will be prompted to enter it.');
|
|
30
|
-
}
|
|
31
28
|
function initOllang() {
|
|
32
29
|
window.Ollang = index_1.OllangBrowser;
|
|
33
30
|
const instance = new index_1.OllangBrowser(config);
|
|
34
31
|
window.ollangInstance = instance;
|
|
35
32
|
window.ollang = instance;
|
|
36
|
-
console.log('✅ Ollang Browser SDK initialized (v2 - API Interception)');
|
|
37
33
|
const urlParams = new URLSearchParams(window.location.search);
|
|
38
34
|
const hasLocalizeParam = urlParams.get('ollang-localize') === 'true';
|
|
39
35
|
if (hasLocalizeParam) {
|
|
40
36
|
setTimeout(() => {
|
|
41
|
-
instance.showDebugPanel().catch((
|
|
42
|
-
console.error('Failed to show debug panel:', err);
|
|
43
|
-
});
|
|
37
|
+
instance.showDebugPanel().catch(() => { });
|
|
44
38
|
}, 1000);
|
|
45
39
|
}
|
|
46
40
|
}
|
package/dist/index.js
CHANGED
|
@@ -23,6 +23,7 @@ const uploads_1 = require("./resources/uploads");
|
|
|
23
23
|
const customInstructions_1 = require("./resources/customInstructions");
|
|
24
24
|
const scans_1 = require("./resources/scans");
|
|
25
25
|
const cms_1 = require("./resources/cms");
|
|
26
|
+
const logger_js_1 = require("./logger.js");
|
|
26
27
|
class Ollang {
|
|
27
28
|
constructor(config) {
|
|
28
29
|
this.client = new client_1.OllangClient(config);
|
|
@@ -37,11 +38,11 @@ class Ollang {
|
|
|
37
38
|
async initializeScanSession(projectId, folderName) {
|
|
38
39
|
try {
|
|
39
40
|
this.scanSession = await this.scans.getOrCreateSession(projectId, folderName);
|
|
40
|
-
|
|
41
|
+
logger_js_1.logger.debug('Scan session initialized:', this.scanSession.id);
|
|
41
42
|
return this.scanSession;
|
|
42
43
|
}
|
|
43
44
|
catch (error) {
|
|
44
|
-
|
|
45
|
+
logger_js_1.logger.error('Failed to initialize scan session', error);
|
|
45
46
|
throw error;
|
|
46
47
|
}
|
|
47
48
|
}
|
package/dist/logger.d.ts
ADDED
package/dist/logger.js
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.logger = void 0;
|
|
4
|
+
const isDebug = () => typeof process !== 'undefined' &&
|
|
5
|
+
(process.env.OLLANG_DEBUG === 'true' || process.env.DEBUG?.includes('ollang'));
|
|
6
|
+
exports.logger = {
|
|
7
|
+
debug(...args) {
|
|
8
|
+
if (isDebug())
|
|
9
|
+
console.log('[ollang]', ...args);
|
|
10
|
+
},
|
|
11
|
+
info(...args) {
|
|
12
|
+
console.log(...args);
|
|
13
|
+
},
|
|
14
|
+
warn(msg) {
|
|
15
|
+
console.warn(msg);
|
|
16
|
+
},
|
|
17
|
+
error(msg, error) {
|
|
18
|
+
if (error instanceof Error) {
|
|
19
|
+
console.error(msg, error.message);
|
|
20
|
+
if (isDebug())
|
|
21
|
+
console.error(error.stack);
|
|
22
|
+
}
|
|
23
|
+
else if (error !== undefined) {
|
|
24
|
+
console.error(msg, typeof error === 'string' ? error : '');
|
|
25
|
+
}
|
|
26
|
+
else {
|
|
27
|
+
console.error(msg);
|
|
28
|
+
}
|
|
29
|
+
},
|
|
30
|
+
};
|
package/dist/tms/config.js
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.configManager = exports.ConfigManager = exports.DEFAULT_TMS_CONFIG = exports.DEFAULT_PANEL_CONFIG = void 0;
|
|
4
4
|
exports.createConfig = createConfig;
|
|
5
|
+
const logger_js_1 = require("../logger.js");
|
|
5
6
|
exports.DEFAULT_PANEL_CONFIG = {
|
|
6
7
|
position: 'bottom-right',
|
|
7
8
|
theme: 'auto',
|
|
@@ -31,7 +32,7 @@ exports.DEFAULT_TMS_CONFIG = {
|
|
|
31
32
|
},
|
|
32
33
|
ollang: {
|
|
33
34
|
apiKey: '',
|
|
34
|
-
baseUrl: '
|
|
35
|
+
baseUrl: 'https://api-integration.ollang.com',
|
|
35
36
|
defaultLevel: 0,
|
|
36
37
|
},
|
|
37
38
|
ui: exports.DEFAULT_PANEL_CONFIG,
|
|
@@ -90,12 +91,12 @@ class ConfigManager {
|
|
|
90
91
|
if (fs.existsSync(configPath)) {
|
|
91
92
|
const content = fs.readFileSync(configPath, 'utf-8');
|
|
92
93
|
const config = JSON.parse(content);
|
|
93
|
-
|
|
94
|
+
logger_js_1.logger.debug(`Loaded config from: ${path.basename(configPath)}`);
|
|
94
95
|
return config;
|
|
95
96
|
}
|
|
96
97
|
}
|
|
97
98
|
catch (error) {
|
|
98
|
-
|
|
99
|
+
logger_js_1.logger.error(`Failed to load config from ${configPath}`, error);
|
|
99
100
|
}
|
|
100
101
|
}
|
|
101
102
|
return null;
|
|
@@ -151,7 +152,7 @@ class ConfigManager {
|
|
|
151
152
|
throw new Error('At least one include path is required for detection');
|
|
152
153
|
}
|
|
153
154
|
if (!config.ollang.apiKey) {
|
|
154
|
-
|
|
155
|
+
logger_js_1.logger.warn('OLLANG_API_KEY not set. Translation features will not work.');
|
|
155
156
|
}
|
|
156
157
|
}
|
|
157
158
|
toJSON() {
|
|
@@ -6,6 +6,7 @@ const text_detector_js_1 = require("./detector/text-detector.js");
|
|
|
6
6
|
const video_detector_js_1 = require("./detector/video-detector.js");
|
|
7
7
|
const image_detector_js_1 = require("./detector/image-detector.js");
|
|
8
8
|
const config_js_1 = require("./config.js");
|
|
9
|
+
const logger_js_1 = require("../logger.js");
|
|
9
10
|
class MultiContentTMS {
|
|
10
11
|
constructor(customConfig = {}) {
|
|
11
12
|
this.configManager = new config_js_1.ConfigManager();
|
|
@@ -19,7 +20,7 @@ class MultiContentTMS {
|
|
|
19
20
|
this.imageDetector = new image_detector_js_1.ImageDetector();
|
|
20
21
|
}
|
|
21
22
|
async scanAll() {
|
|
22
|
-
|
|
23
|
+
logger_js_1.logger.debug('Scanning project for all content types...');
|
|
23
24
|
const detectionConfig = {
|
|
24
25
|
includePaths: this.config.detection.includePaths.map((p) => `${this.config.projectRoot}/${p}`),
|
|
25
26
|
excludePaths: this.config.detection.excludePaths,
|
|
@@ -74,7 +75,7 @@ class MultiContentTMS {
|
|
|
74
75
|
}
|
|
75
76
|
}
|
|
76
77
|
async translateVideos(videos, targetLanguage, level) {
|
|
77
|
-
|
|
78
|
+
logger_js_1.logger.debug('Creating AI Dubbing orders for videos...');
|
|
78
79
|
if (videos.length === 0) {
|
|
79
80
|
throw new Error('No videos to translate');
|
|
80
81
|
}
|
|
@@ -205,11 +206,11 @@ class MultiContentTMS {
|
|
|
205
206
|
async waitForCompletion(orderId, maxWaitSeconds = 600) {
|
|
206
207
|
const maxAttempts = maxWaitSeconds / 5;
|
|
207
208
|
let attempts = 0;
|
|
208
|
-
|
|
209
|
+
logger_js_1.logger.debug(`Waiting for order ${orderId} to complete...`);
|
|
209
210
|
while (attempts < maxAttempts) {
|
|
210
211
|
const order = await this.ollangClient.orders.get(orderId);
|
|
211
212
|
if (order.status === 'completed') {
|
|
212
|
-
|
|
213
|
+
logger_js_1.logger.debug(`Order ${orderId} completed`);
|
|
213
214
|
return order;
|
|
214
215
|
}
|
|
215
216
|
if (order.status === 'failed' || order.status === 'cancelled') {
|