UNPKG

peertube-plugin-transposer-connector

Version:

Transposer connector is a PeerTube language tool plugin to transcribe and translate with Whisper

1,322 lines (1,142 loc) 47.3 kB
async function register({ registerClientRoute, peertubeHelpers }) { const { notifier } = peertubeHelpers; function getInstanceBaseUrl() { return window.location.origin; } function getQueryParam(name) { var url = window.location.href; name = name.replace(/[\[\]]/g, "\\$&"); var regex = new RegExp("[?&]" + name + "(=([^&#]*)|&|#|$)"), results = regex.exec(url); if (!results) return null; if (!results[2]) return ""; return decodeURIComponent(results[2].replace(/\+/g, " ")); } async function getPluginSettings() { try { const response = await fetch(`${getInstanceBaseUrl()}/plugins/settings`); if (!response.ok) throw new Error(`Failed to fetch settings: ${response.status}`); const settings = await response.json(); return settings; } catch (error) { console.error("Error fetching plugin settings:", error); return {}; } } function safeString(value) { if (value === null || value === undefined) return ""; return String(value); } async function triggerTranslation( input, source_language_id, target_language_ids ) { try { const settings = await getPluginSettings(); const transposerApiUrl = settings["trigger-transcription-and-translation"] || "https://api.transposer.eu"; const url = `${transposerApiUrl}/translate`; const requestData = { input, source_language_id, target_language_ids, }; const response = await fetch(url, { method: "POST", headers: { "Content-Type": "application/json", Accept: "application/json", }, body: JSON.stringify(requestData), }); if (!response.ok) { const errorText = await response.text(); throw new Error( `Translation API error: ${response.status} - ${errorText}` ); } const translationResult = await response.json(); return translationResult; } catch (error) { console.error(`Translation request failed: ${error.message}`); throw error; } } registerClientRoute({ route: "/transposer/translate/metadata", onMount: ({ rootEl }) => { rootEl.innerHTML = getPluginContainerHTML(); var tbody; var metadata; var defaultLanguage = null; const videoId = getQueryParam("videoId"); let isSaving = false; let notificationTimeout = null; let formChanged = false; function getPluginContainerHTML() { return ` <div id="plugincontainer"> <h1>Translate Metadata</h1> <div id="languageNoticeContainer"></div> <button id="saveButton">Save Metadata</button><button id="backButton">Back</button><button id="translateEverything">Translate Everything</button><br><br> <div id="confirmationDialog" class="modal" style="display:none;"> <div class="modal-content"> <h3>Translation Options</h3> <p>How would you like to apply translations?</p> <button id="translateAll">Overwrite All</button> <button id="translateEmpty">Fill Empty Only</button> <button id="cancelTranslation">Cancel</button> </div> </div> <div id="unsavedChangesDialog" class="modal" style="display:none;"> <div class="modal-content"> <h3>Unsaved Changes</h3> <p>You have unsaved changes. Do you want to save them before leaving?</p> <button id="saveAndLeave">Save and Leave</button> <button id="leaveWithoutSaving">Leave Without Saving</button> <button id="stayOnPage">Stay on Page</button> </div> </div> </div>`; } const saveButton = rootEl.querySelector("#saveButton"); function showGroupedNotification(message, type = "info", timeout = 3000) { if (notificationTimeout) { clearTimeout(notificationTimeout); notificationTimeout = null; } if (type === "success") { notifier.success(message); } else if (type === "error") { notifier.error(message); } else if ( type === "warning" && typeof notifier.warning === "function" ) { notifier.warning(message); } else { notifier.info(message); } notificationTimeout = setTimeout(() => { notificationTimeout = null; }, timeout); } function saveMetadata() { if (isSaving) { console.log("Save already in progress, ignoring duplicate request"); return; } isSaving = true; console.log("Save Metadata into DB"); const languageInputs = {}; const languageRows = tbody.querySelectorAll("tr"); languageRows.forEach((row) => { const languageCell = row.querySelector("td:first-child"); const languageCode = languageCell.getAttribute("language-code"); const inputCells = row.querySelectorAll("td input, td textarea"); const inputs = Array.from(inputCells).map((input) => safeString(input.value) ); if (languageCode != null) { languageInputs[languageCode] = { title: inputs[0], description: inputs[1], category: inputs[2], tags: inputs[3], }; } }); console.log("languageInputs", languageInputs); if (!videoId) { console.error("Video ID not found in query parameters."); isSaving = false; return; } fetch( `${peertubeHelpers.getBaseRouterRoute()}/language/metadata/video/${videoId}/metadata`, { method: "POST", headers: { "Content-Type": "application/json", ...peertubeHelpers.getAuthHeader(), }, body: JSON.stringify(languageInputs), } ) .then((response) => { if (!response.ok) { return response.text().then((text) => { console.error("Error response:", text); throw new Error("Failed to save metadata."); }); } showGroupedNotification("Saved metadata successfully.", "success"); formChanged = false; }) .catch((error) => { console.error("Error saving metadata:", error); showGroupedNotification("Failed to save metadata.", "error"); }) .finally(() => { isSaving = false; }); } saveButton.addEventListener("click", saveMetadata); const backButton = rootEl.querySelector("#backButton"); backButton.addEventListener("click", () => { console.log("back button clicked"); if (formChanged) { rootEl.querySelector("#unsavedChangesDialog").style.display = "block"; } else { navigateBack(); } }); function navigateBack() { window.open(`${getInstanceBaseUrl()}/p/transposer`, "_self"); } const saveAndLeaveButton = rootEl.querySelector("#saveAndLeave"); saveAndLeaveButton.addEventListener("click", () => { saveMetadata(); rootEl.querySelector("#unsavedChangesDialog").style.display = "none"; setTimeout(() => { navigateBack(); }, 500); }); const leaveWithoutSavingButton = rootEl.querySelector( "#leaveWithoutSaving" ); leaveWithoutSavingButton.addEventListener("click", () => { rootEl.querySelector("#unsavedChangesDialog").style.display = "none"; formChanged = false; navigateBack(); }); const stayOnPageButton = rootEl.querySelector("#stayOnPage"); stayOnPageButton.addEventListener("click", () => { rootEl.querySelector("#unsavedChangesDialog").style.display = "none"; }); const translateEverythingButton = rootEl.querySelector( "#translateEverything" ); translateEverythingButton.addEventListener("click", () => { if (!defaultLanguage) { showGroupedNotification( "No default language set. Please set a video language first.", "error" ); return; } const confirmationDialog = rootEl.querySelector("#confirmationDialog"); confirmationDialog.style.display = "block"; }); const translateAllButton = rootEl.querySelector("#translateAll"); translateAllButton.addEventListener("click", () => { performBulkTranslation(false); rootEl.querySelector("#confirmationDialog").style.display = "none"; }); const translateEmptyButton = rootEl.querySelector("#translateEmpty"); translateEmptyButton.addEventListener("click", () => { performBulkTranslation(true); rootEl.querySelector("#confirmationDialog").style.display = "none"; }); const cancelTranslationButton = rootEl.querySelector("#cancelTranslation"); cancelTranslationButton.addEventListener("click", () => { rootEl.querySelector("#confirmationDialog").style.display = "none"; }); function checkDefaultLanguage() { if (!defaultLanguage) { showGroupedNotification( "No default language set. Please set a video language first.", "error" ); return false; } return true; } async function performBulkTranslation(emptyOnly) { if (!checkDefaultLanguage()) return; const defaultRow = tbody.querySelector("tr:first-child"); const defaultValues = []; defaultRow.querySelectorAll("td").forEach((cell, index) => { if (index > 0) { defaultValues.push(safeString(cell.textContent)); } }); const defaultTitle = defaultValues[0]; const defaultDescription = defaultValues[1]; const defaultCategory = defaultValues[2]; const defaultTags = defaultValues[3]; showGroupedNotification("Starting translations..."); const targetLanguages = []; const languageRows = Array.from(tbody.querySelectorAll("tr")).slice(1); for (const row of languageRows) { const languageCell = row.querySelector("td:first-child"); const languageCode = languageCell.getAttribute("language-code"); if (languageCode) { targetLanguages.push(languageCode); } } const translationStatus = {}; targetLanguages.forEach((lang) => { translationStatus[lang] = { success: 0, fail: 0 }; }); if ( defaultTitle && typeof defaultTitle === "string" && defaultTitle.trim() !== "" ) { try { const titleResult = await triggerTranslation( defaultTitle, defaultLanguage, targetLanguages ); for (const langCode of targetLanguages) { const row = findRowByLanguageCode(langCode); if (!row) continue; const titleInput = row.querySelector("td:nth-child(2) input"); if (emptyOnly && titleInput.value.trim() !== "") { continue; } if ( titleResult?.payload?.[langCode]?.text && titleResult.payload[langCode].success === true ) { titleInput.value = titleResult.payload[langCode].text; titleInput.style.backgroundColor = "#e6ffe6"; translationStatus[langCode].success++; formChanged = true; } else { titleInput.style.backgroundColor = "#ffe6e6"; translationStatus[langCode].fail++; } } } catch (error) { console.error("Error translating titles:", error); for (const langCode of targetLanguages) { const row = findRowByLanguageCode(langCode); if (!row) continue; const titleInput = row.querySelector("td:nth-child(2) input"); if (emptyOnly && titleInput.value.trim() !== "") { continue; } titleInput.style.backgroundColor = "#ffe6e6"; translationStatus[langCode].fail++; } } } if ( defaultDescription && typeof defaultDescription === "string" && defaultDescription.trim() !== "" ) { try { const descResult = await triggerTranslation( defaultDescription, defaultLanguage, targetLanguages ); for (const langCode of targetLanguages) { const row = findRowByLanguageCode(langCode); if (!row) continue; const descInput = row.querySelector("td:nth-child(3) textarea"); if (emptyOnly && descInput.value.trim() !== "") { continue; } if ( descResult?.payload?.[langCode]?.text && descResult.payload[langCode].success === true ) { descInput.value = descResult.payload[langCode].text; descInput.style.backgroundColor = "#e6ffe6"; translationStatus[langCode].success++; formChanged = true; } else { descInput.style.backgroundColor = "#ffe6e6"; translationStatus[langCode].fail++; } } } catch (error) { console.error("Error translating descriptions:", error); for (const langCode of targetLanguages) { const row = findRowByLanguageCode(langCode); if (!row) continue; const descInput = row.querySelector("td:nth-child(3) textarea"); if (emptyOnly && descInput.value.trim() !== "") { continue; } descInput.style.backgroundColor = "#ffe6e6"; translationStatus[langCode].fail++; } } } if ( defaultCategory && typeof defaultCategory === "string" && defaultCategory.trim() !== "" ) { try { const catResult = await triggerTranslation( defaultCategory, defaultLanguage, targetLanguages ); for (const langCode of targetLanguages) { const row = findRowByLanguageCode(langCode); if (!row) continue; const catInput = row.querySelector("td:nth-child(4) input"); if (emptyOnly && catInput.value.trim() !== "") { continue; } if ( catResult?.payload?.[langCode]?.text && catResult.payload[langCode].success === true ) { catInput.value = catResult.payload[langCode].text; catInput.style.backgroundColor = "#e6ffe6"; translationStatus[langCode].success++; formChanged = true; } else { catInput.style.backgroundColor = "#ffe6e6"; translationStatus[langCode].fail++; } } } catch (error) { console.error("Error translating categories:", error); for (const langCode of targetLanguages) { const row = findRowByLanguageCode(langCode); if (!row) continue; const catInput = row.querySelector("td:nth-child(4) input"); if (emptyOnly && catInput.value.trim() !== "") { continue; } catInput.style.backgroundColor = "#ffe6e6"; translationStatus[langCode].fail++; } } } if ( defaultTags && typeof defaultTags === "string" && defaultTags.trim() !== "" ) { try { const tagsResult = await triggerTranslation( defaultTags, defaultLanguage, targetLanguages ); for (const langCode of targetLanguages) { const row = findRowByLanguageCode(langCode); if (!row) continue; const tagsInput = row.querySelector("td:nth-child(5) input"); if (emptyOnly && tagsInput.value.trim() !== "") { continue; } if ( tagsResult?.payload?.[langCode]?.text && tagsResult.payload[langCode].success === true ) { tagsInput.value = tagsResult.payload[langCode].text; tagsInput.style.backgroundColor = "#e6ffe6"; translationStatus[langCode].success++; formChanged = true; } else { tagsInput.style.backgroundColor = "#ffe6e6"; translationStatus[langCode].fail++; } } } catch (error) { console.error("Error translating tags:", error); for (const langCode of targetLanguages) { const row = findRowByLanguageCode(langCode); if (!row) continue; const tagsInput = row.querySelector("td:nth-child(5) input"); if (emptyOnly && tagsInput.value.trim() !== "") { continue; } tagsInput.style.backgroundColor = "#ffe6e6"; translationStatus[langCode].fail++; } } } let totalSuccess = 0; let totalFail = 0; let languageResults = []; for (const langCode of targetLanguages) { const row = findRowByLanguageCode(langCode); if (!row) continue; const languageName = row.querySelector("td:first-child").textContent; const success = translationStatus[langCode].success; const fail = translationStatus[langCode].fail; totalSuccess += success; totalFail += fail; if (success > 0 || fail > 0) { languageResults.push({ name: languageName, success: success, fail: fail, }); } } if (totalSuccess > 0 && totalFail === 0) { showGroupedNotification( `Translations completed successfully for ${languageResults.length} languages!`, "success" ); } else if (totalSuccess > 0 && totalFail > 0) { showGroupedNotification( `Translations partially completed: ${totalSuccess} succeeded, ${totalFail} failed.`, "warning" ); } else if (totalFail > 0) { showGroupedNotification(`All translations failed.`, "error"); } else { showGroupedNotification(`No translations were performed.`, "info"); } } function findRowByLanguageCode(langCode) { const rows = tbody.querySelectorAll("tr"); for (const row of rows) { const langCell = row.querySelector("td:first-child"); if (langCell && langCell.getAttribute("language-code") === langCode) { return row; } } return null; } async function translateSingleField( input, sourceText, sourceLanguage, targetLanguage ) { if (!checkDefaultLanguage()) return false; try { if (sourceText === null || sourceText === undefined) { sourceText = ""; } else if (typeof sourceText !== "string") { sourceText = String(sourceText); } if (sourceText.trim() === "") { input.style.backgroundColor = "#ffe6e6"; console.error("Cannot translate empty source text"); return false; } input.style.backgroundColor = "#fff8e6"; const translationResult = await triggerTranslation( sourceText, sourceLanguage, [targetLanguage] ); if ( translationResult && translationResult.payload && translationResult.payload[targetLanguage] && translationResult.payload[targetLanguage].text && translationResult.payload[targetLanguage].success === true ) { input.value = translationResult.payload[targetLanguage].text; input.style.backgroundColor = "#e6ffe6"; formChanged = true; return true; } input.style.backgroundColor = "#ffe6e6"; console.error("Invalid translation result:", translationResult); return false; } catch (error) { console.error("Translation error:", error); input.style.backgroundColor = "#ffe6e6"; return false; } } function formatCategories2String(category) { if (!category) return ""; return category.label; } function updateLanguageNotice() { const languageNoticeContainer = document.getElementById( "languageNoticeContainer" ); if (!languageNoticeContainer) return; if (defaultLanguage) { languageNoticeContainer.innerHTML = ` <div class="language-notice success"> <p>Default language for translations: <strong>${defaultLanguage}</strong></p> </div> `; } else { languageNoticeContainer.innerHTML = ` <div class="language-notice warning"> <p>⚠️ No default language set. Please set a video language first in the video settings.</p> </div> `; const translateButtons = document.querySelectorAll( ".translate-button, .translate-field-btn" ); translateButtons.forEach((btn) => { btn.disabled = true; btn.title = "Set a video language first"; }); document.getElementById("translateEverything").disabled = true; document.getElementById("translateEverything").title = "Set a video language first"; } } function trackFormChanges() { tbody.addEventListener("input", () => { formChanged = true; }); } function setupBeforeUnloadHandler() { window.addEventListener("beforeunload", (e) => { if (formChanged) { const message = "You have unsaved changes. Are you sure you want to leave?"; e.returnValue = message; return message; } }); } function getMetadataPage() { fetch(`${getInstanceBaseUrl()}/api/v1/videos/languages`) .then((response) => response.json()) .then((data) => { const desiredDataKeys = [ "ar", "bs", "de", "en", "et", "fr", "it", "hr", "nl", "pl", "pt", "ru", "sr", "sk", "sl", "es", "cs", "tr", "uk", "hu", "bg", "el", "fi", "he", "ro", "sv", ]; var metadataEndpoint = `${getInstanceBaseUrl()}/api/v1/videos/${videoId}`; fetch(metadataEndpoint, { method: "GET", }) .then((res) => { console.log("api:", metadataEndpoint); console.log("result:", res); return res.json(); }) .then((videoMetadata) => { metadata = videoMetadata; if (metadata && metadata.language && metadata.language.id) { defaultLanguage = metadata.language.id; console.log("Default language set to:", defaultLanguage); } else { defaultLanguage = null; console.warn("No language found in video metadata"); } updateLanguageNotice(); for (const [languageCode, languageName] of Object.entries( data )) { if (desiredDataKeys.includes(languageCode)) { console.log(languageName); } } const metadataLanguageEndpoint = `${peertubeHelpers.getBaseRouterRoute()}/language/metadata/video/${videoId}`; fetch(metadataLanguageEndpoint, { method: "GET", headers: peertubeHelpers.getAuthHeader(), }) .then((res) => { console.log("api:", metadataLanguageEndpoint); console.log("result:", res); return res.json(); }) .then((languageMetadata) => { console.log("languageMetadata", languageMetadata); var types = [ "Title", "Description", "Category", "Tags", "Actions", ]; var table = document.createElement("table"); var thead = document.createElement("thead"); tbody = document.createElement("tbody"); var headerRow = document.createElement("tr"); var headerCellLanguage = document.createElement("th"); headerCellLanguage.textContent = "Sprache"; headerRow.appendChild(headerCellLanguage); types.forEach(function (type) { var headerCellType = document.createElement("th"); headerCellType.textContent = type; headerRow.appendChild(headerCellType); }); thead.appendChild(headerRow); var defaultRow = document.createElement("tr"); var defaultCell = document.createElement("td"); defaultCell.textContent = "Default"; defaultRow.appendChild(defaultCell); types.forEach(function (type, index) { var defaultCell = document.createElement("td"); if (type === "Actions") { defaultCell.textContent = ""; defaultRow.appendChild(defaultCell); return; } var defaultValue = metadata[type.toLowerCase()]; if (type.toLowerCase() == "title") { defaultValue = metadata["name"]; } if (type.toLowerCase() == "category") { defaultValue = formatCategories2String( metadata[type.toLowerCase()] ); } console.log(defaultValue); defaultCell.textContent = defaultValue || ""; defaultRow.appendChild(defaultCell); }); tbody.appendChild(defaultRow); for (const [languageCode, languageName] of Object.entries( data )) { if (desiredDataKeys.includes(languageCode)) { var languageRow = document.createElement("tr"); var languageCell = document.createElement("td"); languageCell.textContent = languageName; languageCell.setAttribute( "language-code", languageCode ); languageRow.appendChild(languageCell); let fieldsContainer = []; types.forEach(function (type, index) { var valueCell = document.createElement("td"); if (type === "Actions") { const translateFieldsDiv = document.createElement("div"); translateFieldsDiv.className = "translate-fields"; const translateButton = document.createElement("button"); translateButton.textContent = "Translate All Fields"; translateButton.className = "translate-button"; translateButton.setAttribute( "data-lang", languageCode ); if (!defaultLanguage) { translateButton.disabled = true; translateButton.title = "Set a video language first"; } translateButton.addEventListener( "click", async () => { if (!checkDefaultLanguage()) return; const defaultTitle = metadata["name"]; const defaultDescription = metadata["description"]; const defaultCategory = formatCategories2String( metadata["category"] ); const defaultTags = metadata["tags"]; const inputFields = fieldsContainer; let allSuccessful = true; let successCount = 0; let failCount = 0; if (inputFields[0]) { const titleSuccess = await translateSingleField( inputFields[0], defaultTitle, defaultLanguage, languageCode ); if (!titleSuccess) { allSuccessful = false; failCount++; } else { successCount++; } } if (inputFields[1]) { const descSuccess = await translateSingleField( inputFields[1], defaultDescription, defaultLanguage, languageCode ); if (!descSuccess) { allSuccessful = false; failCount++; } else { successCount++; } } if (inputFields[2]) { const catSuccess = await translateSingleField( inputFields[2], defaultCategory, defaultLanguage, languageCode ); if (!catSuccess) { allSuccessful = false; failCount++; } else { successCount++; } } if (inputFields[3]) { const tagsSuccess = await translateSingleField( inputFields[3], defaultTags, defaultLanguage, languageCode ); if (!tagsSuccess) { allSuccessful = false; failCount++; } else { successCount++; } } if (allSuccessful && successCount > 0) { showGroupedNotification( `Translation for ${languageName} completed successfully!`, "success" ); } else if (successCount > 0 && failCount > 0) { showGroupedNotification( `Translation for ${languageName} partially completed (${successCount} succeeded, ${failCount} failed).`, "warning" ); } else if (failCount > 0) { showGroupedNotification( `Translation for ${languageName} failed.`, "error" ); } else { showGroupedNotification( `No translations performed for ${languageName}.`, "info" ); } } ); translateFieldsDiv.appendChild(translateButton); valueCell.appendChild(translateFieldsDiv); } else { var valueInput; if (type === "Description") { valueInput = document.createElement("textarea"); } else { valueInput = document.createElement("input"); valueInput.type = "text"; } valueInput.style.minWidth = "200px"; const fieldContainer = document.createElement("div"); fieldContainer.className = "field-container"; fieldContainer.style.display = "flex"; fieldContainer.style.alignItems = "center"; var valueFromMetadata = languageMetadata[type.toLowerCase()] && languageMetadata[type.toLowerCase()][ languageCode ]; valueInput.value = valueFromMetadata || ""; // Allow empty values // Add input change tracking valueInput.addEventListener("input", () => { formChanged = true; }); fieldContainer.appendChild(valueInput); // Add translate button for this field const translateFieldBtn = document.createElement("button"); translateFieldBtn.textContent = "T"; translateFieldBtn.title = `Translate ${type}`; translateFieldBtn.className = "translate-field-btn"; translateFieldBtn.style.marginLeft = "5px"; translateFieldBtn.style.padding = "2px 5px"; // Disable if no default language if (!defaultLanguage) { translateFieldBtn.disabled = true; translateFieldBtn.title = "Set a video language first"; } // Add click handler for individual field translation translateFieldBtn.addEventListener( "click", async () => { if (!checkDefaultLanguage()) return; // Get the corresponding default text let defaultText = ""; switch (type) { case "Title": defaultText = metadata["name"]; break; case "Description": defaultText = metadata["description"]; break; case "Category": defaultText = formatCategories2String( metadata["category"] ); break; case "Tags": defaultText = metadata["tags"]; break; } const success = await translateSingleField( valueInput, defaultText, defaultLanguage, languageCode ); if (success) { showGroupedNotification( `${type} translated to ${languageName}`, "success" ); } else { showGroupedNotification( `Failed to translate ${type} to ${languageName}`, "error" ); } } ); fieldContainer.appendChild(translateFieldBtn); valueCell.appendChild(fieldContainer); // Store input field for later reference fieldsContainer.push(valueInput); } languageRow.appendChild(valueCell); }); tbody.appendChild(languageRow); } } table.appendChild(thead); table.appendChild(tbody); var pluginContainer = document.getElementById("plugincontainer"); pluginContainer.appendChild(table); // Initialize tracking form changes after the form is built trackFormChanges(); setupBeforeUnloadHandler(); // Reset the formChanged flag after initial load setTimeout(() => { formChanged = false; }, 500); }); }); }); } function initialize() { getMetadataPage(); const style = document.createElement("style"); style.innerHTML = getThemeStyles(); rootEl.appendChild(style); } function getThemeStyles() { return ` #plugincontainer { max-width: 100%; padding: 20px; position: relative; } #plugincontainer h1 { color: var(--mainColor); } #plugincontainer #formcontainer { display: flex; flex-wrap: wrap; } #plugincontainer .modifyButton, #plugincontainer .saveButton, #plugincontainer .cancelButton, #plugincontainer .deleteButton { cursor: pointer; padding: 20px; } #plugincontainer input, #plugincontainer textarea, #plugincontainer select { display: block; font-size: 16px; padding: var(--input-y-padding, 8px) var(--input-x-padding, 12px); width: 100%; max-width: 100%; color: var(--input-fg, #000); background-color: var(--input-bg, #fff); border: 1px solid var(--input-border-color, #ccc); border-radius: var(--input-border-radius, 4px); line-height: 24px; transition: background-color 0.3s ease; } #plugincontainer p { font-size: 18px; } #plugincontainer table { width: 100%; border-collapse: collapse; margin: 25px 0; font-size: 0.9em; min-width: 130px; min-height: 130px; box-shadow: none; } #plugincontainer table tr { text-align: left; } #plugincontainer table th, #plugincontainer table td { padding: 12px 15px; border-bottom: 1px solid var(--inputPlaceholderColor, #ccc); } #plugincontainer table th { background-color: var(--greyBackgroundColor, #f8f9fa); color: var(--mainForegroundColor); } #plugincontainer button { color: var(--on-primary, var(--mainForegroundColor)); background-color: var(--primary, var(--mainColor)); border: 1px solid var(--primary, var(--mainColor)); padding: var(--input-y-padding, 8px) var(--input-x-padding, 12px); border-radius: 4px; cursor: pointer; margin: 0 5px; transition: background-color 0.3s; } #plugincontainer button:hover { background-color: var(--secondaryColor); } #plugincontainer button:disabled { background-color: var(--greyBackgroundColor, #cccccc); color: var(--greyForegroundColor, #666666); cursor: not-allowed; } /* Specific button styles */ #plugincontainer .translate-button { width: auto; } #plugincontainer .translate-field-btn { background-color: var(--greyBackgroundColor, #6c757d); font-size: 0.8em; width: auto; padding: 2px 5px; margin-left: 5px; } #plugincontainer .translate-field-btn:hover { background-color: var(--secondaryColor, #5a6268); } /* Language notice styles */ #plugincontainer .language-notice { margin: 10px 0; padding: 10px; border-radius: 4px; } #plugincontainer .language-notice.success { background-color: #e6ffe6; border: 1px solid #b3ffb3; } #plugincontainer .language-notice.warning { background-color: #fff8e6; border: 1px solid #ffe0b3; } #plugincontainer .language-notice p { margin: 0; font-size: 14px; } /* Field container styles */ #plugincontainer .field-container { display: flex; align-items: center; width: 100%; } #plugincontainer .field-container input, #plugincontainer .field-container textarea { flex: 1; min-width: 200px; } /* Modal styles */ #plugincontainer .modal { position: fixed; top: 0; left: 0; width: 100%; height: 100%; background-color: rgba(0,0,0,0.5); display: flex; justify-content: center; align-items: center; z-index: 1000; } #plugincontainer .modal-content { background-color: var(--backgroundPrimary, white); padding: 20px; border-radius: 5px; width: 400px; text-align: center; box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2); position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%); } #plugincontainer .modal-content h3 { margin-top: 0; color: var(--primary, var(--mainColor)); } #plugincontainer .modal-content button { margin: 10px; padding: 10px 15px; } /* For the translate background colors */ #plugincontainer input[style*="background-color: #e6ffe6"], #plugincontainer textarea[style*="background-color: #e6ffe6"] { background-color: #e6ffe6 !important; } #plugincontainer input[style*="background-color: #ffe6e6"], #plugincontainer textarea[style*="background-color: #ffe6e6"] { background-color: #ffe6e6 !important; } #plugincontainer input[style*="background-color: #fff8e6"], #plugincontainer textarea[style*="background-color: #fff8e6"] { background-color: #fff8e6 !important; } /* Unsaved changes dialog style */ #plugincontainer #unsavedChangesDialog .modal-content { background-color: var(--backgroundPrimary, white); border: 2px solid var(--mainColor); } `; } initialize(); }, }); } export { register };