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
JavaScript
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 };