diff --git a/sdkjs-plugins/content/datepicker/CHANGELOG.md b/sdkjs-plugins/content/datepicker/CHANGELOG.md
new file mode 100644
index 00000000..2c9a2d06
--- /dev/null
+++ b/sdkjs-plugins/content/datepicker/CHANGELOG.md
@@ -0,0 +1,5 @@
+# Change Log
+
+## 1.0.0
+
+- Initial release.
diff --git a/sdkjs-plugins/content/datepicker/README.md b/sdkjs-plugins/content/datepicker/README.md
new file mode 100644
index 00000000..b2d6c8b1
--- /dev/null
+++ b/sdkjs-plugins/content/datepicker/README.md
@@ -0,0 +1,22 @@
+Overview
+Inserting formatted dates into spreadsheet cells with a calendar interface.
+
+Installation
+Install the Insert QR Plugin from the plugin marketplace.
+Usage
+Activate the DatePicker:
+
+Go to the plugin tab.
+Activate the DatePicker plugin in the "Plugins" section
+Select Cells:
+
+Select the cells area where you would like to print the formatted date.
+Select the date:
+
+Click the "Select the date" option to select desired date from the calendar.
+Select the format:
+
+Click the "Select format" option to format the previously selected date.
+Print the date:
+
+Print the formatted date across the selected cell area.
\ No newline at end of file
diff --git a/sdkjs-plugins/content/datepicker/config.json b/sdkjs-plugins/content/datepicker/config.json
new file mode 100644
index 00000000..e10a2ebd
--- /dev/null
+++ b/sdkjs-plugins/content/datepicker/config.json
@@ -0,0 +1,112 @@
+{
+ "name": "Date picker",
+ "nameLocale": {
+ "cs": "Výběr data",
+ "de": "Datumsauswahl",
+ "es": "Selector de fecha",
+ "fr": "Sélecteur de date",
+ "it": "Selettore data",
+ "ja": "日付ピッカー",
+ "pt": "Seletor de data",
+ "ru": "Выбор даты",
+ "sq": "Zgjedhësi i datës",
+ "sr": "Избор датума",
+ "sp": "Izbor datuma",
+ "zh": "日期选择器"
+ },
+ "guid": "asc.{409e5493-56ae-45e1-8423-c73c84a06c50}",
+ "version": "1.0.0",
+ "description": "A plugin to insert formatted dates into cells.",
+ "descriptionLocale": {
+ "cs": "Plugin pro vkládání formátovaných dat do buněk.",
+ "de": "Ein Plugin zum Einfügen formatierter Daten in Zellen.",
+ "es": "Un complemento para insertar fechas formateadas en celdas.",
+ "fr": "Un plugin pour insérer des dates formatées dans les cellules.",
+ "it": "Un plugin per inserire date formattate nelle celle.",
+ "ja": "セルにフォーマットされた日付を挿入するプラグイン。",
+ "pt": "Um plugin para inserir datas formatadas em células.",
+ "ru": "Плагин для вставки отформатированных дат в ячейки.",
+ "sq": "Një shtojcë për të futur data të formatuara në qeliza.",
+ "sr": "Додатак за убацивање форматираних датума у ћелије.",
+ "sp": "Dodatak za ubacivanje formatiranih datuma u ćelije.",
+ "zh": "用于在单元格中插入格式化日期的插件。"
+ },
+ "type": "panel",
+ "icons": ["resources/light/icon.png", "resources/light/icon@2x.png"],
+ "variations": [
+ {
+ "description": "Insert formatted dates into spreadsheet cells with a calendar interface.",
+ "descriptionLocale": {
+ "cs": "Vkládejte formátovaná data do buněk tabulky pomocí kalendářního rozhraní.",
+ "de": "Fügen Sie formatierte Daten mit einer Kalender-Oberfläche in Tabellenzellen ein.",
+ "es": "Inserte fechas formateadas en celdas de hojas de cálculo con una interfaz de calendario.",
+ "fr": "Insérez des dates formatées dans les cellules de feuille de calcul avec une interface de calendrier.",
+ "it": "Inserisci date formattate nelle celle del foglio di calcolo con un'interfaccia calendario.",
+ "ja": "カレンダーインターフェースを使用して、スプレッドシートのセルにフォーマットされた日付を挿入します。",
+ "pt-BR": "Insira datas formatadas em células de planilha com uma interface de calendário.",
+ "ru": "Вставляйте отформатированные даты в ячейки электронной таблицы с помощью календарного интерфейса.",
+ "sq": "Futni data të formatuara në qelizat e fletës së llogaritjes me një ndërfaqe kalendari.",
+ "sr": "Убацујте форматиране датуме у ћелије табеле помоћу календарског интерфејса.",
+ "sp": "Ubacujte formatirane datume u ćelije tabele pomoću kalendarskog interfejsa.",
+ "zh": "使用日历界面将格式化日期插入电子表格单元格。"
+ },
+ "url": "index.html",
+ "icons": ["resources/light/icon.png", "resources/light/icon@2x.png"],
+ "icons2": [
+ {
+ "style": "light",
+ "100%": {
+ "normal": "resources/light/icon.svg"
+ },
+ "125%": {
+ "normal": "resources/light/icon@1.25x.svg"
+ },
+ "150%": {
+ "normal": "resources/light/icon@1.5x.svg"
+ },
+ "175%": {
+ "normal": "resources/light/icon@1.75x.svg"
+ },
+ "200%": {
+ "normal": "resources/light/icon@2x.svg"
+ }
+ },
+ {
+ "style": "dark",
+ "100%": {
+ "normal": "resources/dark/icon-dark.svg"
+ },
+ "125%": {
+ "normal": "resources/dark/icon-dark@1.25x.svg"
+ },
+ "150%": {
+ "normal": "resources/dark/icon-dark@1.5x.svg"
+ },
+ "175%": {
+ "normal": "resources/dark/icon-dark@1.75x.svg"
+ },
+ "200%": {
+ "normal": "resources/dark/icon-dark@2x.svg"
+ }
+ }
+ ],
+ "isViewer": false,
+ "EditorsSupport": ["cell"],
+ "isVisual": true,
+ "isModal": false,
+ "isInsideMode": true,
+ "initDataType": "none",
+ "initData": "",
+ "isUpdateOleOnResize": true,
+ "buttons": [],
+ "store": {
+ "screenshots": [
+ "resources/store/screenshot1.png",
+ "resources/store/screenshot2.png",
+ "resources/store/screenshot3.png",
+ "resources/store/screenshot4.png"
+ ]
+ }
+ }
+ ]
+}
diff --git a/sdkjs-plugins/content/datepicker/deploy/datepicker.plugin b/sdkjs-plugins/content/datepicker/deploy/datepicker.plugin
new file mode 100644
index 00000000..ccc0eb0b
Binary files /dev/null and b/sdkjs-plugins/content/datepicker/deploy/datepicker.plugin differ
diff --git a/sdkjs-plugins/content/datepicker/index.html b/sdkjs-plugins/content/datepicker/index.html
new file mode 100644
index 00000000..dc12774c
--- /dev/null
+++ b/sdkjs-plugins/content/datepicker/index.html
@@ -0,0 +1,545 @@
+
+
+
+
+ OnlyOffice Date Picker
+
+
+
+
+
+
+
+ Please do not close the plugin panel.
+
+
+
+
+
+
+
+
+
+
+
diff --git a/sdkjs-plugins/content/datepicker/resources/button/calendar.png b/sdkjs-plugins/content/datepicker/resources/button/calendar.png
new file mode 100644
index 00000000..bb41660a
Binary files /dev/null and b/sdkjs-plugins/content/datepicker/resources/button/calendar.png differ
diff --git a/sdkjs-plugins/content/datepicker/resources/button/calendar.svg b/sdkjs-plugins/content/datepicker/resources/button/calendar.svg
new file mode 100644
index 00000000..2eb39c53
--- /dev/null
+++ b/sdkjs-plugins/content/datepicker/resources/button/calendar.svg
@@ -0,0 +1,9 @@
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/sdkjs-plugins/content/datepicker/resources/dark/icon-dark.png b/sdkjs-plugins/content/datepicker/resources/dark/icon-dark.png
new file mode 100644
index 00000000..4dea0e03
Binary files /dev/null and b/sdkjs-plugins/content/datepicker/resources/dark/icon-dark.png differ
diff --git a/sdkjs-plugins/content/datepicker/resources/dark/icon-dark.svg b/sdkjs-plugins/content/datepicker/resources/dark/icon-dark.svg
new file mode 100644
index 00000000..c72d9be8
--- /dev/null
+++ b/sdkjs-plugins/content/datepicker/resources/dark/icon-dark.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/sdkjs-plugins/content/datepicker/resources/dark/icon-dark@1.25x.png b/sdkjs-plugins/content/datepicker/resources/dark/icon-dark@1.25x.png
new file mode 100644
index 00000000..1475a31f
Binary files /dev/null and b/sdkjs-plugins/content/datepicker/resources/dark/icon-dark@1.25x.png differ
diff --git a/sdkjs-plugins/content/datepicker/resources/dark/icon-dark@1.25x.svg b/sdkjs-plugins/content/datepicker/resources/dark/icon-dark@1.25x.svg
new file mode 100644
index 00000000..ef9ce661
--- /dev/null
+++ b/sdkjs-plugins/content/datepicker/resources/dark/icon-dark@1.25x.svg
@@ -0,0 +1,7 @@
+
+
+
+
+
+
+
diff --git a/sdkjs-plugins/content/datepicker/resources/dark/icon-dark@1.5x.png b/sdkjs-plugins/content/datepicker/resources/dark/icon-dark@1.5x.png
new file mode 100644
index 00000000..f56cec23
Binary files /dev/null and b/sdkjs-plugins/content/datepicker/resources/dark/icon-dark@1.5x.png differ
diff --git a/sdkjs-plugins/content/datepicker/resources/dark/icon-dark@1.5x.svg b/sdkjs-plugins/content/datepicker/resources/dark/icon-dark@1.5x.svg
new file mode 100644
index 00000000..0add96e6
--- /dev/null
+++ b/sdkjs-plugins/content/datepicker/resources/dark/icon-dark@1.5x.svg
@@ -0,0 +1,7 @@
+
+
+
+
+
+
+
diff --git a/sdkjs-plugins/content/datepicker/resources/dark/icon-dark@1.75x.png b/sdkjs-plugins/content/datepicker/resources/dark/icon-dark@1.75x.png
new file mode 100644
index 00000000..bef9b39f
Binary files /dev/null and b/sdkjs-plugins/content/datepicker/resources/dark/icon-dark@1.75x.png differ
diff --git a/sdkjs-plugins/content/datepicker/resources/dark/icon-dark@1.75x.svg b/sdkjs-plugins/content/datepicker/resources/dark/icon-dark@1.75x.svg
new file mode 100644
index 00000000..355f1257
--- /dev/null
+++ b/sdkjs-plugins/content/datepicker/resources/dark/icon-dark@1.75x.svg
@@ -0,0 +1,7 @@
+
+
+
+
+
+
+
diff --git a/sdkjs-plugins/content/datepicker/resources/dark/icon-dark@2x.png b/sdkjs-plugins/content/datepicker/resources/dark/icon-dark@2x.png
new file mode 100644
index 00000000..ed3bd2f0
Binary files /dev/null and b/sdkjs-plugins/content/datepicker/resources/dark/icon-dark@2x.png differ
diff --git a/sdkjs-plugins/content/datepicker/resources/dark/icon-dark@2x.svg b/sdkjs-plugins/content/datepicker/resources/dark/icon-dark@2x.svg
new file mode 100644
index 00000000..f19405b9
--- /dev/null
+++ b/sdkjs-plugins/content/datepicker/resources/dark/icon-dark@2x.svg
@@ -0,0 +1,7 @@
+
+
+
+
+
+
+
diff --git a/sdkjs-plugins/content/datepicker/resources/light/1.svg b/sdkjs-plugins/content/datepicker/resources/light/1.svg
new file mode 100644
index 00000000..fcb0acb6
--- /dev/null
+++ b/sdkjs-plugins/content/datepicker/resources/light/1.svg
@@ -0,0 +1,290 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/sdkjs-plugins/content/datepicker/resources/light/2.svg b/sdkjs-plugins/content/datepicker/resources/light/2.svg
new file mode 100644
index 00000000..fcb0acb6
--- /dev/null
+++ b/sdkjs-plugins/content/datepicker/resources/light/2.svg
@@ -0,0 +1,290 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/sdkjs-plugins/content/datepicker/resources/light/3.svg b/sdkjs-plugins/content/datepicker/resources/light/3.svg
new file mode 100644
index 00000000..fcb0acb6
--- /dev/null
+++ b/sdkjs-plugins/content/datepicker/resources/light/3.svg
@@ -0,0 +1,290 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/sdkjs-plugins/content/datepicker/resources/light/4.svg b/sdkjs-plugins/content/datepicker/resources/light/4.svg
new file mode 100644
index 00000000..fcb0acb6
--- /dev/null
+++ b/sdkjs-plugins/content/datepicker/resources/light/4.svg
@@ -0,0 +1,290 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/sdkjs-plugins/content/datepicker/resources/light/5.svg b/sdkjs-plugins/content/datepicker/resources/light/5.svg
new file mode 100644
index 00000000..fcb0acb6
--- /dev/null
+++ b/sdkjs-plugins/content/datepicker/resources/light/5.svg
@@ -0,0 +1,290 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/sdkjs-plugins/content/datepicker/resources/light/icon.svg b/sdkjs-plugins/content/datepicker/resources/light/icon.svg
new file mode 100644
index 00000000..6c6189ba
--- /dev/null
+++ b/sdkjs-plugins/content/datepicker/resources/light/icon.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/sdkjs-plugins/content/datepicker/resources/light/icon2.png b/sdkjs-plugins/content/datepicker/resources/light/icon2.png
new file mode 100644
index 00000000..436b9c6f
Binary files /dev/null and b/sdkjs-plugins/content/datepicker/resources/light/icon2.png differ
diff --git a/sdkjs-plugins/content/datepicker/resources/light/icon@1.25x.png b/sdkjs-plugins/content/datepicker/resources/light/icon@1.25x.png
new file mode 100644
index 00000000..e22b2b41
Binary files /dev/null and b/sdkjs-plugins/content/datepicker/resources/light/icon@1.25x.png differ
diff --git a/sdkjs-plugins/content/datepicker/resources/light/icon@1.25x2.svg b/sdkjs-plugins/content/datepicker/resources/light/icon@1.25x2.svg
new file mode 100644
index 00000000..ef9ce661
--- /dev/null
+++ b/sdkjs-plugins/content/datepicker/resources/light/icon@1.25x2.svg
@@ -0,0 +1,7 @@
+
+
+
+
+
+
+
diff --git a/sdkjs-plugins/content/datepicker/resources/light/icon@1.5x.png b/sdkjs-plugins/content/datepicker/resources/light/icon@1.5x.png
new file mode 100644
index 00000000..ee183244
Binary files /dev/null and b/sdkjs-plugins/content/datepicker/resources/light/icon@1.5x.png differ
diff --git a/sdkjs-plugins/content/datepicker/resources/light/icon@1.5x.svg b/sdkjs-plugins/content/datepicker/resources/light/icon@1.5x.svg
new file mode 100644
index 00000000..a8ecce25
--- /dev/null
+++ b/sdkjs-plugins/content/datepicker/resources/light/icon@1.5x.svg
@@ -0,0 +1,7 @@
+
+
+
+
+
+
+
diff --git a/sdkjs-plugins/content/datepicker/resources/light/icon@1.75x.svg b/sdkjs-plugins/content/datepicker/resources/light/icon@1.75x.svg
new file mode 100644
index 00000000..a1a3b790
--- /dev/null
+++ b/sdkjs-plugins/content/datepicker/resources/light/icon@1.75x.svg
@@ -0,0 +1,7 @@
+
+
+
+
+
+
+
diff --git a/sdkjs-plugins/content/datepicker/resources/light/icon@1.75x2.png b/sdkjs-plugins/content/datepicker/resources/light/icon@1.75x2.png
new file mode 100644
index 00000000..682c5517
Binary files /dev/null and b/sdkjs-plugins/content/datepicker/resources/light/icon@1.75x2.png differ
diff --git a/sdkjs-plugins/content/datepicker/resources/light/icon@2x.png b/sdkjs-plugins/content/datepicker/resources/light/icon@2x.png
new file mode 100644
index 00000000..cf919eb9
Binary files /dev/null and b/sdkjs-plugins/content/datepicker/resources/light/icon@2x.png differ
diff --git a/sdkjs-plugins/content/datepicker/resources/light/icon@2x.svg b/sdkjs-plugins/content/datepicker/resources/light/icon@2x.svg
new file mode 100644
index 00000000..68ab1f4c
--- /dev/null
+++ b/sdkjs-plugins/content/datepicker/resources/light/icon@2x.svg
@@ -0,0 +1,7 @@
+
+
+
+
+
+
+
diff --git a/sdkjs-plugins/content/datepicker/resources/store/screenshot1.png b/sdkjs-plugins/content/datepicker/resources/store/screenshot1.png
new file mode 100644
index 00000000..00094b8c
Binary files /dev/null and b/sdkjs-plugins/content/datepicker/resources/store/screenshot1.png differ
diff --git a/sdkjs-plugins/content/datepicker/resources/store/screenshot2.png b/sdkjs-plugins/content/datepicker/resources/store/screenshot2.png
new file mode 100644
index 00000000..866a531b
Binary files /dev/null and b/sdkjs-plugins/content/datepicker/resources/store/screenshot2.png differ
diff --git a/sdkjs-plugins/content/datepicker/resources/store/screenshot3.png b/sdkjs-plugins/content/datepicker/resources/store/screenshot3.png
new file mode 100644
index 00000000..9b4a3f0f
Binary files /dev/null and b/sdkjs-plugins/content/datepicker/resources/store/screenshot3.png differ
diff --git a/sdkjs-plugins/content/datepicker/resources/store/screenshot4.png b/sdkjs-plugins/content/datepicker/resources/store/screenshot4.png
new file mode 100644
index 00000000..e9d800a9
Binary files /dev/null and b/sdkjs-plugins/content/datepicker/resources/store/screenshot4.png differ
diff --git a/sdkjs-plugins/content/datepicker/scripts/code.js b/sdkjs-plugins/content/datepicker/scripts/code.js
new file mode 100644
index 00000000..d3dceb7d
--- /dev/null
+++ b/sdkjs-plugins/content/datepicker/scripts/code.js
@@ -0,0 +1,1022 @@
+// Helper function to translate and clean up English keys
+function tr(key) {
+ if (window.Asc && window.Asc.plugin && window.Asc.plugin.tr) {
+ const translated = window.Asc.plugin.tr(key);
+
+ // If translation returns the same as key (meaning English), clean it up
+ if (translated === key) {
+ // Remove context markers for English display
+ return key.replace(/ \(full\)|\(short\)/g, "");
+ }
+
+ return translated;
+ }
+
+ // Fallback: clean up the key for display
+ return key.replace(/ \(full\)|\(short\)/g, "");
+}
+
+// Helper function to get proper weekday abbreviations
+function getWeekdayAbbreviation(fullWeekdayName, index) {
+ // Check if this looks like Albanian format (starts with "E " followed by another word)
+ if (fullWeekdayName.startsWith("E ") && fullWeekdayName.length > 2) {
+ // Extract the first letter of the second word
+ const secondWord = fullWeekdayName.substring(2).trim();
+ return secondWord.charAt(0).toUpperCase();
+ }
+
+ // For other languages, use the first 2 characters as before
+ return fullWeekdayName.substring(0, 2);
+}
+
+// Custom Calendar Class with Translation Support
+class CustomCalendar {
+ constructor(input, options = {}) {
+ this.input = input;
+ this.calendar = document.getElementById("customCalendar");
+ this.calendarDays = document.getElementById("calendarDays");
+ this.calendarTitle = document.getElementById("monthYearTitle");
+ this.prevBtn = document.getElementById("prevBtn");
+ this.nextBtn = document.getElementById("nextBtn");
+ this.calendarIcon = document.getElementById("calendarIcon");
+ this.monthView = document.getElementById("monthView");
+ this.yearView = document.getElementById("yearView");
+ this.weekdays = document.getElementById("calendarWeekdays");
+
+ this.currentDate = new Date();
+ this.selectedDate = new Date();
+ this.viewYear = this.currentDate.getFullYear();
+ this.viewMonth = this.currentDate.getMonth();
+ this.isOpen = false;
+ this.currentView = "days";
+
+ // Initialize with English names - will be translated on onTranslate
+ this.months = [
+ "January",
+ "February",
+ "March",
+ "April",
+ "May (full)",
+ "June",
+ "July",
+ "August",
+ "September",
+ "October",
+ "November",
+ "December",
+ ];
+
+ this.monthsShort = [
+ "Jan",
+ "Feb",
+ "Mar",
+ "Apr",
+ "May (short)",
+ "Jun",
+ "Jul",
+ "Aug",
+ "Sep",
+ "Oct",
+ "Nov",
+ "Dec",
+ ];
+
+ this.weekdays_full = [
+ "Sunday",
+ "Monday",
+ "Tuesday",
+ "Wednesday",
+ "Thursday",
+ "Friday",
+ "Saturday",
+ ];
+
+ this.init();
+ }
+
+ // Method to update translations when language changes
+ updateLocalization() {
+ if (window.Asc && window.Asc.plugin && window.Asc.plugin.tr) {
+ // Update months array with translations using English keys
+ this.months = [
+ tr("January"),
+ tr("February"),
+ tr("March"),
+ tr("April"),
+ tr("May (full)"),
+ tr("June"),
+ tr("July"),
+ tr("August"),
+ tr("September"),
+ tr("October"),
+ tr("November"),
+ tr("December"),
+ ];
+
+ this.monthsShort = [
+ tr("Jan"),
+ tr("Feb"),
+ tr("Mar"),
+ tr("Apr"),
+ tr("May (short)"),
+ tr("Jun"),
+ tr("Jul"),
+ tr("Aug"),
+ tr("Sep"),
+ tr("Oct"),
+ tr("Nov"),
+ tr("Dec"),
+ ];
+
+ this.weekdays_full = [
+ tr("Sunday"),
+ tr("Monday"),
+ tr("Tuesday"),
+ tr("Wednesday"),
+ tr("Thursday"),
+ tr("Friday"),
+ tr("Saturday"),
+ ];
+
+ // Update month view with translated names
+ const monthElements = document.querySelectorAll(".calendar-month");
+ monthElements.forEach((el, index) => {
+ el.textContent = this.monthsShort[index];
+ });
+
+ // Update the calendar display if it's been rendered
+ if (this.calendarTitle) {
+ this.updateTitle();
+ }
+ }
+ }
+
+ init() {
+ this.render();
+ this.bindEvents();
+ this.updateInput();
+ this.setupIcon();
+ this.applyThemeToIcon();
+ }
+
+ applyThemeToIcon() {
+ const body = document.body;
+ const mainContent = document.getElementById("mainContent");
+ const form = document.getElementById("mainForm");
+
+ let backgroundColor = window.getComputedStyle(body).backgroundColor;
+
+ if (
+ !backgroundColor ||
+ backgroundColor === "rgba(0, 0, 0, 0)" ||
+ backgroundColor === "transparent"
+ ) {
+ if (mainContent) {
+ backgroundColor = window.getComputedStyle(mainContent).backgroundColor;
+ }
+ if (
+ (!backgroundColor ||
+ backgroundColor === "rgba(0, 0, 0, 0)" ||
+ backgroundColor === "transparent") &&
+ form
+ ) {
+ backgroundColor = window.getComputedStyle(form).backgroundColor;
+ }
+ }
+
+ let isDark = false;
+
+ if (backgroundColor && backgroundColor.includes("rgb")) {
+ const rgb = backgroundColor.match(/\d+/g);
+ if (rgb && rgb.length >= 3) {
+ const r = parseInt(rgb[0]);
+ const g = parseInt(rgb[1]);
+ const b = parseInt(rgb[2]);
+
+ const luminance = (0.299 * r + 0.587 * g + 0.114 * b) / 255;
+ isDark = luminance < 0.5;
+ }
+ }
+
+ if (isDark) {
+ this.calendarIcon.classList.add("dark-theme");
+ this.calendarIcon.classList.remove("light-theme");
+ } else {
+ this.calendarIcon.classList.add("light-theme");
+ this.calendarIcon.classList.remove("dark-theme");
+ }
+ }
+
+ async setupIcon() {
+ try {
+ // Try multiple path resolution strategies
+ const paths = [
+ // Relative to plugin root
+ "./resources/button/calendar.png",
+ "resources/button/calendar.png",
+ // Absolute from plugin base
+ `${window.location.origin}${window.location.pathname
+ .split("/")
+ .slice(0, -1)
+ .join("/")}/resources/button/calendar.png`,
+ ];
+
+ let success = false;
+ for (const imagePath of paths) {
+ success = await testBackgroundImage(this.calendarIcon, imagePath);
+ if (success) {
+ console.log("Icon loaded from:", imagePath);
+ // Set the background image explicitly
+ this.calendarIcon.style.backgroundImage = `url('${imagePath}')`;
+ break;
+ }
+ }
+
+ if (!success) {
+ console.warn("Failed to load calendar icon from all paths");
+ // Fallback: use a unicode calendar symbol
+ this.calendarIcon.textContent = "📅";
+ this.calendarIcon.style.backgroundImage = "none";
+ }
+ } catch (error) {
+ console.error("Error setting up icon:", error);
+ this.calendarIcon.textContent = "📅";
+ }
+ }
+
+ bindEvents() {
+ this.input.addEventListener("click", () => this.toggle());
+ this.calendarIcon.addEventListener("click", () => this.toggle());
+
+ this.prevBtn.addEventListener("click", () => this.handleNavPrev());
+ this.nextBtn.addEventListener("click", () => this.handleNavNext());
+
+ this.calendarTitle.addEventListener("click", () => this.handleTitleClick());
+
+ document.querySelectorAll(".calendar-month").forEach((monthEl) => {
+ monthEl.addEventListener("click", () => {
+ this.viewMonth = parseInt(monthEl.dataset.month);
+ this.showDayView();
+ });
+ });
+
+ document.addEventListener("click", (e) => {
+ if (!this.input.parentNode.contains(e.target)) {
+ this.hide();
+ }
+ });
+
+ this.calendar.addEventListener("click", (e) => {
+ e.stopPropagation();
+ });
+ }
+
+ handleNavPrev() {
+ if (this.currentView === "days") {
+ this.previousMonth();
+ } else if (this.currentView === "months") {
+ this.viewYear--;
+ this.updateTitle();
+ } else if (this.currentView === "years") {
+ this.viewYear -= 12;
+ this.showYearView();
+ }
+ }
+
+ handleNavNext() {
+ if (this.currentView === "days") {
+ this.nextMonth();
+ } else if (this.currentView === "months") {
+ this.viewYear++;
+ this.updateTitle();
+ } else if (this.currentView === "years") {
+ this.viewYear += 12;
+ this.showYearView();
+ }
+ }
+
+ handleTitleClick() {
+ if (this.currentView === "days") {
+ this.showMonthView();
+ } else if (this.currentView === "months") {
+ this.showYearView();
+ } else {
+ this.showDayView();
+ }
+ }
+
+ toggle() {
+ this.isOpen ? this.hide() : this.show();
+ }
+
+ show() {
+ this.calendar.classList.add("show");
+ this.calendarIcon.classList.add("active");
+ this.isOpen = true;
+ this.applyThemeToCalendar();
+ }
+
+ applyThemeToCalendar() {
+ const body = document.body;
+ const mainContent = document.getElementById("mainContent");
+ const form = document.getElementById("mainForm");
+
+ let backgroundColor = window.getComputedStyle(body).backgroundColor;
+
+ if (
+ !backgroundColor ||
+ backgroundColor === "rgba(0, 0, 0, 0)" ||
+ backgroundColor === "transparent"
+ ) {
+ if (mainContent) {
+ backgroundColor = window.getComputedStyle(mainContent).backgroundColor;
+ }
+ if (
+ (!backgroundColor ||
+ backgroundColor === "rgba(0, 0, 0, 0)" ||
+ backgroundColor === "transparent") &&
+ form
+ ) {
+ backgroundColor = window.getComputedStyle(form).backgroundColor;
+ }
+ }
+
+ let isDark = false;
+ let r = 255,
+ g = 255,
+ b = 255;
+
+ if (backgroundColor && backgroundColor.includes("rgb")) {
+ const rgb = backgroundColor.match(/\d+/g);
+ if (rgb && rgb.length >= 3) {
+ r = parseInt(rgb[0]);
+ g = parseInt(rgb[1]);
+ b = parseInt(rgb[2]);
+
+ const luminance = (0.299 * r + 0.587 * g + 0.114 * b) / 255;
+ isDark = luminance < 0.5;
+ }
+ }
+
+ let calendarBgColor, calendarTextColor;
+
+ if (isDark) {
+ const darkerR = Math.max(0, r - 15);
+ const darkerG = Math.max(0, g - 15);
+ const darkerB = Math.max(0, b - 15);
+ calendarBgColor = `rgb(${darkerR}, ${darkerG}, ${darkerB})`;
+ calendarTextColor = "#ffffff";
+ } else {
+ const lighterR = Math.min(255, r + 15);
+ const lighterG = Math.min(255, g + 15);
+ const lighterB = Math.min(255, b + 15);
+ calendarBgColor = `rgb(${lighterR}, ${lighterG}, ${lighterB})`;
+ calendarTextColor = "#000000";
+ }
+
+ this.calendar.style.setProperty(
+ "background-color",
+ calendarBgColor,
+ "important"
+ );
+ this.calendar.style.setProperty("color", calendarTextColor, "important");
+
+ if (isDark) {
+ this.calendarIcon.classList.add("dark-theme");
+ this.calendarIcon.classList.remove("light-theme");
+ } else {
+ this.calendarIcon.classList.add("light-theme");
+ this.calendarIcon.classList.remove("dark-theme");
+ }
+
+ const allElements = this.calendar.querySelectorAll("*");
+ allElements.forEach((element) => {
+ if (
+ !element.matches(".calendar-day:nth-child(7n+1)") &&
+ !element.matches(".calendar-day:nth-child(7n)")
+ ) {
+ element.style.setProperty("color", calendarTextColor, "important");
+ }
+ });
+
+ const selectedElements = this.calendar.querySelectorAll(
+ ".calendar-day.selected, .calendar-month.active, .calendar-year.active"
+ );
+ selectedElements.forEach((element) => {
+ if (isDark) {
+ element.style.setProperty("background-color", "#9ca3af", "important");
+ element.style.setProperty("color", "#ffffff", "important");
+ } else {
+ element.style.setProperty("background-color", "#d1d5db", "important");
+ element.style.setProperty("color", "#000000", "important");
+ }
+ });
+ }
+
+ hide() {
+ this.calendar.classList.remove("show");
+ this.calendarIcon.classList.remove("active");
+ this.isOpen = false;
+ this.showDayView();
+ }
+
+ showMonthView() {
+ this.currentView = "months";
+ this.monthView.style.display = "grid";
+ this.yearView.style.display = "none";
+ this.calendarDays.style.display = "none";
+ this.weekdays.style.display = "none";
+
+ document.querySelectorAll(".calendar-month").forEach((el) => {
+ el.classList.remove("active");
+ if (parseInt(el.dataset.month) === this.viewMonth) {
+ el.classList.add("active");
+ }
+ });
+
+ this.updateTitle();
+ }
+
+ showYearView() {
+ this.currentView = "years";
+ this.monthView.style.display = "none";
+ this.yearView.style.display = "grid";
+ this.calendarDays.style.display = "none";
+ this.weekdays.style.display = "none";
+
+ this.generateYears();
+ this.updateTitle();
+ }
+
+ showDayView() {
+ this.currentView = "days";
+ this.monthView.style.display = "none";
+ this.yearView.style.display = "none";
+ this.calendarDays.style.display = "grid";
+ this.weekdays.style.display = "grid";
+
+ this.render();
+ }
+
+ generateYears() {
+ this.yearView.innerHTML = "";
+ const currentYear = this.viewYear;
+ const startYear = currentYear - 6;
+
+ for (let year = startYear; year < startYear + 12; year++) {
+ const yearEl = document.createElement("div");
+ yearEl.className = "calendar-year";
+ yearEl.textContent = year;
+ yearEl.dataset.year = year;
+ if (year === currentYear) yearEl.classList.add("active");
+
+ yearEl.addEventListener("click", () => {
+ this.viewYear = year;
+ this.showMonthView();
+ });
+ this.yearView.appendChild(yearEl);
+ }
+ }
+
+ previousMonth() {
+ this.viewMonth--;
+ if (this.viewMonth < 0) {
+ this.viewMonth = 11;
+ this.viewYear--;
+ }
+ this.render();
+ }
+
+ nextMonth() {
+ this.viewMonth++;
+ if (this.viewMonth > 11) {
+ this.viewMonth = 0;
+ this.viewYear++;
+ }
+ this.render();
+ }
+
+ render() {
+ this.updateTitle();
+ this.calendarDays.innerHTML = "";
+
+ const firstDay = new Date(this.viewYear, this.viewMonth, 1);
+ const startDate = new Date(firstDay);
+ startDate.setDate(startDate.getDate() - firstDay.getDay());
+
+ // 42 days = 6 rows × 7 days (standard calendar grid to show complete weeks)
+ for (let i = 0; i < 42; i++) {
+ const date = new Date(startDate);
+ date.setDate(startDate.getDate() + i);
+
+ const dayEl = document.createElement("div");
+ dayEl.classList.add("calendar-day");
+ dayEl.textContent = date.getDate();
+
+ if (date.getMonth() !== this.viewMonth) {
+ dayEl.classList.add("other-month");
+ }
+
+ if (this.isSameDay(date, this.currentDate)) {
+ dayEl.classList.add("today");
+ }
+
+ if (this.isSameDay(date, this.selectedDate)) {
+ dayEl.classList.add("selected");
+ }
+
+ dayEl.addEventListener("click", () => {
+ this.selectDate(date);
+ });
+
+ this.calendarDays.appendChild(dayEl);
+ }
+ }
+
+ updateTitle() {
+ if (this.currentView === "days") {
+ this.calendarTitle.textContent = `${this.months[this.viewMonth]} ${
+ this.viewYear
+ }`;
+ } else if (this.currentView === "months") {
+ this.calendarTitle.textContent = this.viewYear;
+ } else {
+ const startYear = this.viewYear - 6;
+ this.calendarTitle.textContent = `${startYear}-${startYear + 11}`;
+ }
+ }
+
+ selectDate(date) {
+ this.selectedDate = new Date(date);
+ this.updateInput();
+ this.render();
+ this.hide();
+ this.input.dispatchEvent(new Event("datechange"));
+
+ // Use requestAnimationFrame to apply styles after render
+ requestAnimationFrame(() => {
+ this.applyGreyToSelected();
+ });
+ }
+
+ applyGreyToSelected() {
+ const calendarBg = window.getComputedStyle(this.calendar).backgroundColor;
+ const isDark = this.isBackgroundDark(calendarBg);
+
+ const selectedElements = this.calendar.querySelectorAll(
+ ".calendar-day.selected, .calendar-month.active, .calendar-year.active"
+ );
+ selectedElements.forEach((element) => {
+ if (isDark) {
+ element.style.setProperty("background-color", "#9ca3af", "important");
+ element.style.setProperty("color", "#ffffff", "important");
+ } else {
+ element.style.setProperty("background-color", "#d1d5db", "important");
+ element.style.setProperty("color", "#000000", "important");
+ }
+ });
+ }
+
+ isBackgroundDark(backgroundColor) {
+ if (backgroundColor && backgroundColor.includes("rgb")) {
+ const rgb = backgroundColor.match(/\d+/g);
+ if (rgb && rgb.length >= 3) {
+ const r = parseInt(rgb[0]);
+ const g = parseInt(rgb[1]);
+ const b = parseInt(rgb[2]);
+ const luminance = (0.299 * r + 0.587 * g + 0.114 * b) / 255;
+ return luminance < 0.5;
+ }
+ }
+ return false;
+ }
+
+ updateInput() {
+ const formatSelect = document.getElementById("dateFormat");
+ this.input.value = this.formatDate(this.selectedDate, formatSelect.value);
+ }
+
+ getDate() {
+ return this.selectedDate;
+ }
+
+ setDate(date) {
+ this.selectedDate = new Date(date);
+ this.viewYear = date.getFullYear();
+ this.viewMonth = date.getMonth();
+ this.updateInput();
+ this.render();
+ }
+
+ isSameDay(date1, date2) {
+ return (
+ date1.getDate() === date2.getDate() &&
+ date1.getMonth() === date2.getMonth() &&
+ date1.getFullYear() === date2.getFullYear()
+ );
+ }
+
+ formatDate(date, format) {
+ const day = date.getDate();
+ const dayPadded = String(day).padStart(2, "0");
+ const month = date.getMonth() + 1;
+ const monthPadded = String(month).padStart(2, "0");
+ const year = date.getFullYear();
+ const yearShort = String(year).slice(-2);
+
+ const weekday = this.weekdays_full[date.getDay()];
+ const monthFull = this.months[month - 1];
+ const monthShort = this.monthsShort[month - 1];
+
+ switch (format) {
+ case "MM/DD/YYYY":
+ return `${monthPadded}/${dayPadded}/${year}`;
+ case "dddd, MMMM D, YYYY":
+ return `${weekday}, ${monthFull} ${day}, ${year}`;
+ case "MMMM D, YYYY":
+ return `${monthFull} ${day}, ${year}`;
+ case "M/D/YY":
+ return `${month}/${day}/${yearShort}`;
+ case "YYYY-MM-DD":
+ return `${year}-${monthPadded}-${dayPadded}`;
+ case "D-MMM-YY":
+ return `${dayPadded}-${monthShort}-${yearShort}`;
+ case "M.D.YYYY":
+ return `${month}.${day}.${year}`;
+ default:
+ return `${monthPadded}/${dayPadded}/${year}`;
+ }
+ }
+}
+
+// Global calendar instance
+let globalCalendar = null;
+
+// Background image detection utility
+function testBackgroundImage(element, url) {
+ return new Promise((resolve) => {
+ const testImg = new Image();
+ testImg.onload = () => {
+ element.classList.add("has-bg-image");
+ resolve(true);
+ };
+ testImg.onerror = () => {
+ element.classList.remove("has-bg-image");
+ resolve(false);
+ };
+ testImg.src = url;
+ });
+}
+
+// Initialize plugin
+window.Asc = window.Asc || {};
+window.Asc.plugin = window.Asc.plugin || {};
+window.Asc.scope = window.Asc.scope || {};
+
+window.Asc.plugin.init = function () {
+ if (this.executeMethod) window.pluginAPI = this;
+
+ showLoadingScreen(tr("Initializing plugin..."));
+
+ // Use requestAnimationFrame to ensure loading screen renders before heavy work
+ requestAnimationFrame(() => {
+ initializeDatePicker();
+
+ // Hide loading screen after datepicker renders
+ requestAnimationFrame(() => {
+ hideLoadingScreen();
+ });
+ });
+};
+
+window.Asc.plugin.onTranslate = function () {
+ // Update instruction text
+ const instructionText = document.getElementById("instructionText");
+ if (instructionText) {
+ instructionText.innerHTML = tr(
+ "Select the date and format , then click the Insert date button. The date will be displayed in the selected cell."
+ );
+ }
+
+ // Update labels
+ const selectDateLabel = document.getElementById("selectDateLabel");
+ if (selectDateLabel) {
+ selectDateLabel.innerHTML = tr("Select date");
+ }
+
+ const selectDateFormatLabel = document.getElementById(
+ "selectDateFormatLabel"
+ );
+ if (selectDateFormatLabel) {
+ selectDateFormatLabel.innerHTML = tr("Select date format");
+ }
+
+ // Update button text
+ const insertDateBtn = document.getElementById("insertDate");
+ if (insertDateBtn) {
+ insertDateBtn.innerHTML = tr("Insert date");
+ }
+
+ // Update loading texts
+ const pleaseDoNotClose = document.getElementById("pleaseDoNotClose");
+ if (pleaseDoNotClose) {
+ pleaseDoNotClose.innerHTML = tr(
+ "Please do not close the plugin panel."
+ );
+ }
+
+ const loadingText = document.getElementById("loadingText");
+ if (loadingText) {
+ loadingText.innerHTML = tr("Loading...");
+ }
+
+ // Update input placeholder
+ const dateInput = document.getElementById("dateInput");
+ if (dateInput) {
+ dateInput.placeholder = tr("Select a date");
+ }
+
+ // Update weekday abbreviations in calendar
+ const weekdayElements = document.querySelectorAll("[data-day]");
+ const weekdayKeys = [
+ "Sunday",
+ "Monday",
+ "Tuesday",
+ "Wednesday",
+ "Thursday",
+ "Friday",
+ "Saturday",
+ ];
+
+ weekdayElements.forEach((el, index) => {
+ if (weekdayKeys[index]) {
+ const translated = tr(weekdayKeys[index]);
+ const abbreviation = getWeekdayAbbreviation(translated, index);
+ el.textContent = abbreviation;
+ }
+ });
+
+ // Update calendar instance if it exists
+ if (globalCalendar) {
+ globalCalendar.updateLocalization();
+ globalCalendar.render();
+ }
+};
+
+function showLoadingScreen(message = "Loading...") {
+ const loadingOverlay = document.getElementById("loadingOverlay");
+ const loadingText = document.getElementById("loadingText");
+ if (loadingOverlay && loadingText) {
+ loadingText.textContent = tr(message);
+ loadingOverlay.style.display = "flex";
+ document.getElementById("mainContent").classList.add("loading");
+ }
+}
+
+function hideLoadingScreen() {
+ const loadingOverlay = document.getElementById("loadingOverlay");
+ if (loadingOverlay) {
+ loadingOverlay.style.display = "none";
+ document.getElementById("mainContent").classList.remove("loading");
+ }
+}
+
+// FIXED: Using Asc.scope instead of eval for security and maintainability
+function insertDateValue(formattedDate, selectedDate, selectedFormat) {
+ if (!window.pluginAPI) {
+ console.error("Plugin API not available");
+ throw new Error("Plugin API not available");
+ }
+
+ try {
+ // Convert JavaScript Date to Excel serial number
+ // Excel dates are days since December 30, 1899
+ const excelEpoch = new Date(1899, 11, 30);
+ const excelSerialNumber = Math.floor(
+ (selectedDate - excelEpoch) / (24 * 60 * 60 * 1000)
+ );
+
+ // Store the date value and format in Asc.scope
+ window.Asc.scope.dateValue = excelSerialNumber;
+ window.Asc.scope.formatCode = getExcelFormatCode(selectedFormat);
+
+ // Use Asc.scope - insert as actual date number
+ window.pluginAPI.callCommand(function () {
+ try {
+ // Access the date from Asc.scope
+ var dateValue = Asc.scope.dateValue;
+ var formatCode = Asc.scope.formatCode;
+ var oWorksheet = Api.GetActiveSheet();
+
+ if (!oWorksheet) {
+ return false;
+ }
+
+ var oSelection = oWorksheet.GetSelection();
+ if (!oSelection) {
+ var oActiveCell = oWorksheet.GetActiveCell();
+ if (oActiveCell) {
+ oActiveCell.SetValue(dateValue);
+ oActiveCell.SetNumberFormat(formatCode);
+ return true;
+ }
+ return false;
+ }
+
+ try {
+ oSelection.Clear();
+ oSelection.SetValue(dateValue);
+ oSelection.SetNumberFormat(formatCode);
+ return true;
+ } catch (directError) {
+ try {
+ var oRange = oSelection;
+ if (oRange.GetRowsCount && oRange.GetColsCount) {
+ var rowCount = oRange.GetRowsCount();
+ var colCount = oRange.GetColsCount();
+
+ for (var row = 0; row < rowCount; row++) {
+ for (var col = 0; col < colCount; col++) {
+ var oCell = oRange.GetRows(row).GetCells(col);
+ if (oCell) {
+ oCell.SetValue(dateValue);
+ oCell.SetNumberFormat(formatCode);
+ }
+ }
+ }
+ return true;
+ } else {
+ oSelection.SetValue(dateValue);
+ oSelection.SetNumberFormat(formatCode);
+ return true;
+ }
+ } catch (cellError) {
+ var oActiveCell = oWorksheet.GetActiveCell();
+ if (oActiveCell) {
+ oActiveCell.SetValue(dateValue);
+ oActiveCell.SetNumberFormat(formatCode);
+ return true;
+ }
+ return false;
+ }
+ }
+ } catch (e) {
+ return false;
+ }
+ });
+
+ // Clean up the scope after use
+ delete window.Asc.scope.dateValue;
+ delete window.Asc.scope.formatCode;
+
+ return true;
+ } catch (e) {
+ console.error("Error in insertDateValue:", e);
+ // Clean up on error too
+ delete window.Asc.scope.dateValue;
+ delete window.Asc.scope.formatCode;
+ throw e;
+ }
+}
+
+// Helper function to convert our format strings to Excel number format codes
+function getExcelFormatCode(format) {
+ switch (format) {
+ case "MM/DD/YYYY":
+ return "mm/dd/yyyy";
+ case "dddd, MMMM D, YYYY":
+ return "dddd, mmmm dd, yyyy";
+ case "MMMM D, YYYY":
+ return "mmmm dd, yyyy";
+ case "M/D/YY":
+ return "mm/dd/yy";
+ case "YYYY-MM-DD":
+ return "yyyy-mm-dd";
+ case "D-MMM-YY":
+ return "dd-mmm-yy";
+ case "M.D.YYYY":
+ return "mm.dd.yyyy";
+ default:
+ return "mm/dd/yyyy";
+ }
+}
+
+function validateAndGetFormat(formatSelect) {
+ const format = formatSelect.value;
+ const validFormats = [
+ "MM/DD/YYYY",
+ "dddd, MMMM D, YYYY",
+ "MMMM D, YYYY",
+ "M/D/YY",
+ "YYYY-MM-DD",
+ "D-MMM-YY",
+ "M.D.YYYY",
+ ];
+
+ if (validFormats.includes(format)) {
+ return format;
+ } else {
+ console.warn("Invalid format detected, falling back to default");
+ formatSelect.value = "MM/DD/YYYY";
+ return "MM/DD/YYYY";
+ }
+}
+
+function initializeDatePicker() {
+ const input = document.getElementById("dateInput");
+ const formatSelect = document.getElementById("dateFormat");
+ const insertBtn = document.getElementById("insertDate");
+
+ if (!input || !formatSelect || !insertBtn) return;
+
+ input.setAttribute("data-initialized", "true");
+ const calendar = new CustomCalendar(input);
+
+ // Store global reference for translation updates
+ globalCalendar = calendar;
+
+ function updateFormatOptions(selectedDate) {
+ const formats = [
+ "MM/DD/YYYY",
+ "dddd, MMMM D, YYYY",
+ "MMMM D, YYYY",
+ "M/D/YY",
+ "YYYY-MM-DD",
+ "D-MMM-YY",
+ "M.D.YYYY",
+ ];
+ const currentValue = formatSelect.value;
+ formatSelect.innerHTML = "";
+ formats.forEach((format) => {
+ const option = document.createElement("option");
+ option.value = format;
+ option.textContent = calendar.formatDate(selectedDate, format);
+ formatSelect.appendChild(option);
+ });
+ formatSelect.value = currentValue;
+ }
+
+ updateFormatOptions(new Date());
+
+ formatSelect.addEventListener("change", () => {
+ calendar.updateInput();
+ });
+
+ input.addEventListener("datechange", () =>
+ updateFormatOptions(calendar.getDate())
+ );
+
+ insertBtn.addEventListener("click", () => {
+ const selectedDate = calendar.getDate();
+ if (!selectedDate) {
+ return;
+ }
+
+ // Disable controls
+ insertBtn.disabled = true;
+ formatSelect.disabled = true;
+
+ const currentFormat = validateAndGetFormat(formatSelect);
+ const mainContent = document.getElementById("mainContent");
+ if (mainContent) {
+ mainContent.classList.add("loading");
+ }
+
+ showLoadingScreen(tr("Inserting date..."));
+
+ // Use requestAnimationFrame to ensure loading screen is visible
+ requestAnimationFrame(() => {
+ try {
+ const formattedDate = calendar.formatDate(selectedDate, currentFormat);
+
+ // Insert the date (synchronous operation)
+ insertDateValue(formattedDate, selectedDate, currentFormat);
+
+ // Success! Reset to today's date
+ const todaysDate = new Date();
+ formatSelect.selectedIndex = 0;
+ updateFormatOptions(todaysDate);
+ calendar.setDate(todaysDate);
+ } catch (error) {
+ console.error("Failed to insert date:", error);
+ alert(tr("Failed to insert date. Please try again."));
+ } finally {
+ // Re-enable controls and hide loading after a brief delay for visual feedback
+ requestAnimationFrame(() => {
+ insertBtn.disabled = false;
+ formatSelect.disabled = false;
+ hideLoadingScreen();
+
+ if (mainContent) {
+ mainContent.classList.remove("loading");
+ }
+ });
+ }
+ });
+ });
+
+ // Initial translation update if translations are already available
+ if (window.Asc.plugin.tr) {
+ window.Asc.plugin.onTranslate();
+ }
+}
diff --git a/sdkjs-plugins/content/datepicker/translations/cs-CS.json b/sdkjs-plugins/content/datepicker/translations/cs-CS.json
new file mode 100644
index 00000000..81a1a96a
--- /dev/null
+++ b/sdkjs-plugins/content/datepicker/translations/cs-CS.json
@@ -0,0 +1,43 @@
+{
+ "Select the date and format , then click the Insert date button. The date will be displayed in the selected cell.": "Vyberte datum a formát , poté klikněte na tlačítko Vložit datum . Datum se zobrazí ve vybrané buňce.",
+ "Select date": "Vybrat datum",
+ "Select a date": "Vyberte datum",
+ "Select date format": "Vybrat formát data",
+ "Insert date": "Vložit datum",
+ "Loading...": "Načítání...",
+ "Loading plugin...": "Načítání pluginu...",
+ "Initializing plugin...": "Inicializace pluginu...",
+ "Inserting date...": "Vkládání data...",
+ "Please do not close the plugin panel.": "Prosím nezavírejte panel pluginu.",
+ "January": "Leden",
+ "February": "Únor",
+ "March": "Březen",
+ "April": "Duben",
+ "May (full)": "Květen",
+ "June": "Červen",
+ "July": "Červenec",
+ "August": "Srpen",
+ "September": "Září",
+ "October": "Říjen",
+ "November": "Listopad",
+ "December": "Prosinec",
+ "Sunday": "Neděle",
+ "Monday": "Pondělí",
+ "Tuesday": "Úterý",
+ "Wednesday": "Středa",
+ "Thursday": "Čtvrtek",
+ "Friday": "Pátek",
+ "Saturday": "Sobota",
+ "Jan": "Led",
+ "Feb": "Úno",
+ "Mar": "Bře",
+ "Apr": "Dub",
+ "May (short)": "Kvě",
+ "Jun": "Čer",
+ "Jul": "Čvc",
+ "Aug": "Srp",
+ "Sep": "Zář",
+ "Oct": "Říj",
+ "Nov": "Lis",
+ "Dec": "Pro"
+}
diff --git a/sdkjs-plugins/content/datepicker/translations/de-DE.json b/sdkjs-plugins/content/datepicker/translations/de-DE.json
new file mode 100644
index 00000000..c6773c8e
--- /dev/null
+++ b/sdkjs-plugins/content/datepicker/translations/de-DE.json
@@ -0,0 +1,43 @@
+{
+ "Select the date and format , then click the Insert date button. The date will be displayed in the selected cell.": "Wählen Sie das Datum und Format , dann klicken Sie auf die Schaltfläche Datum einfügen . Das Datum wird in der ausgewählten Zelle angezeigt.",
+ "Select date": "Datum auswählen",
+ "Select a date": "Datum auswählen",
+ "Select date format": "Datumsformat auswählen",
+ "Insert date": "Datum einfügen",
+ "Loading...": "Wird geladen...",
+ "Loading plugin...": "Plugin wird geladen...",
+ "Initializing plugin...": "Plugin wird initialisiert...",
+ "Inserting date...": "Datum wird eingefügt...",
+ "Please do not close the plugin panel.": "Bitte schließen Sie nicht das Plugin-Panel.",
+ "January": "Januar",
+ "February": "Februar",
+ "March": "März",
+ "April": "April",
+ "May (full)": "Mai",
+ "June": "Juni",
+ "July": "Juli",
+ "August": "August",
+ "September": "September",
+ "October": "Oktober",
+ "November": "November",
+ "December": "Dezember",
+ "Sunday": "Sonntag",
+ "Monday": "Montag",
+ "Tuesday": "Dienstag",
+ "Wednesday": "Mittwoch",
+ "Thursday": "Donnerstag",
+ "Friday": "Freitag",
+ "Saturday": "Samstag",
+ "Jan": "Jan",
+ "Feb": "Feb",
+ "Mar": "Mär",
+ "Apr": "Apr",
+ "May (short)": "Mai",
+ "Jun": "Jun",
+ "Jul": "Jul",
+ "Aug": "Aug",
+ "Sep": "Sep",
+ "Oct": "Okt",
+ "Nov": "Nov",
+ "Dec": "Dez"
+}
diff --git a/sdkjs-plugins/content/datepicker/translations/es-ES.json b/sdkjs-plugins/content/datepicker/translations/es-ES.json
new file mode 100644
index 00000000..b8acb3a8
--- /dev/null
+++ b/sdkjs-plugins/content/datepicker/translations/es-ES.json
@@ -0,0 +1,43 @@
+{
+ "Select the date and format , then click the Insert date button. The date will be displayed in the selected cell.": "Seleccione la fecha y formato , luego haga clic en el botón Insertar fecha . La fecha se mostrará en la celda seleccionada.",
+ "Select date": "Seleccionar fecha",
+ "Select a date": "Seleccione una fecha",
+ "Select date format": "Seleccionar formato de fecha",
+ "Insert date": "Insertar fecha",
+ "Loading...": "Cargando...",
+ "Loading plugin...": "Cargando plugin...",
+ "Initializing plugin...": "Inicializando plugin...",
+ "Inserting date...": "Insertando fecha...",
+ "Please do not close the plugin panel.": "Por favor no cierre el panel del plugin.",
+ "January": "Enero",
+ "February": "Febrero",
+ "March": "Marzo",
+ "April": "Abril",
+ "May (full)": "Mayo",
+ "June": "Junio",
+ "July": "Julio",
+ "August": "Agosto",
+ "September": "Septiembre",
+ "October": "Octubre",
+ "November": "Noviembre",
+ "December": "Diciembre",
+ "Sunday": "Domingo",
+ "Monday": "Lunes",
+ "Tuesday": "Martes",
+ "Wednesday": "Miércoles",
+ "Thursday": "Jueves",
+ "Friday": "Viernes",
+ "Saturday": "Sábado",
+ "Jan": "Ene",
+ "Feb": "Feb",
+ "Mar": "Mar",
+ "Apr": "Abr",
+ "May (short)": "May",
+ "Jun": "Jun",
+ "Jul": "Jul",
+ "Aug": "Ago",
+ "Sep": "Sep",
+ "Oct": "Oct",
+ "Nov": "Nov",
+ "Dec": "Dic"
+}
diff --git a/sdkjs-plugins/content/datepicker/translations/fr-FR.json b/sdkjs-plugins/content/datepicker/translations/fr-FR.json
new file mode 100644
index 00000000..bb147d73
--- /dev/null
+++ b/sdkjs-plugins/content/datepicker/translations/fr-FR.json
@@ -0,0 +1,43 @@
+{
+ "Select the date and format , then click the Insert date button. The date will be displayed in the selected cell.": "Sélectionnez la date et le format , puis cliquez sur le bouton Insérer la date . La date sera affichée dans la cellule sélectionnée.",
+ "Select date": "Sélectionner la date",
+ "Select a date": "Sélectionnez une date",
+ "Select date format": "Sélectionner le format de date",
+ "Insert date": "Insérer la date",
+ "Loading...": "Chargement...",
+ "Loading plugin...": "Chargement du plugin...",
+ "Initializing plugin...": "Initialisation du plugin...",
+ "Inserting date...": "Insertion de la date...",
+ "Please do not close the plugin panel.": "Veuillez ne pas fermer le panneau du plugin.",
+ "January": "Janvier",
+ "February": "Février",
+ "March": "Mars",
+ "April": "Avril",
+ "May (full)": "Mai",
+ "June": "Juin",
+ "July": "Juillet",
+ "August": "Août",
+ "September": "Septembre",
+ "October": "Octobre",
+ "November": "Novembre",
+ "December": "Décembre",
+ "Sunday": "Dimanche",
+ "Monday": "Lundi",
+ "Tuesday": "Mardi",
+ "Wednesday": "Mercredi",
+ "Thursday": "Jeudi",
+ "Friday": "Vendredi",
+ "Saturday": "Samedi",
+ "Jan": "Jan",
+ "Feb": "Fév",
+ "Mar": "Mar",
+ "Apr": "Avr",
+ "May (short)": "Mai",
+ "Jun": "Jun",
+ "Jul": "Jul",
+ "Aug": "Aoû",
+ "Sep": "Sep",
+ "Oct": "Oct",
+ "Nov": "Nov",
+ "Dec": "Déc"
+}
diff --git a/sdkjs-plugins/content/datepicker/translations/it-IT.json b/sdkjs-plugins/content/datepicker/translations/it-IT.json
new file mode 100644
index 00000000..55a0b91a
--- /dev/null
+++ b/sdkjs-plugins/content/datepicker/translations/it-IT.json
@@ -0,0 +1,43 @@
+{
+ "Select the date and format , then click the Insert date button. The date will be displayed in the selected cell.": "Seleziona la data e il formato , poi clicca sul pulsante Inserisci data . La data verrà visualizzata nella cella selezionata.",
+ "Select date": "Seleziona data",
+ "Select a date": "Seleziona una data",
+ "Select date format": "Seleziona formato data",
+ "Insert date": "Inserisci data",
+ "Loading...": "Caricamento...",
+ "Loading plugin...": "Caricamento plugin...",
+ "Initializing plugin...": "Inizializzazione plugin...",
+ "Inserting date...": "Inserimento data...",
+ "Please do not close the plugin panel.": "Si prega di non chiudere il pannello del plugin.",
+ "January": "Gennaio",
+ "February": "Febbraio",
+ "March": "Marzo",
+ "April": "Aprile",
+ "May (full)": "Maggio",
+ "June": "Giugno",
+ "July": "Luglio",
+ "August": "Agosto",
+ "September": "Settembre",
+ "October": "Ottobre",
+ "November": "Novembre",
+ "December": "Dicembre",
+ "Sunday": "Domenica",
+ "Monday": "Lunedì",
+ "Tuesday": "Martedì",
+ "Wednesday": "Mercoledì",
+ "Thursday": "Giovedì",
+ "Friday": "Venerdì",
+ "Saturday": "Sabato",
+ "Jan": "Gen",
+ "Feb": "Feb",
+ "Mar": "Mar",
+ "Apr": "Apr",
+ "May (short)": "Mag",
+ "Jun": "Giu",
+ "Jul": "Lug",
+ "Aug": "Ago",
+ "Sep": "Set",
+ "Oct": "Ott",
+ "Nov": "Nov",
+ "Dec": "Dic"
+}
diff --git a/sdkjs-plugins/content/datepicker/translations/ja-JA.json b/sdkjs-plugins/content/datepicker/translations/ja-JA.json
new file mode 100644
index 00000000..467c3194
--- /dev/null
+++ b/sdkjs-plugins/content/datepicker/translations/ja-JA.json
@@ -0,0 +1,43 @@
+{
+ "Select the date and format , then click the Insert date button. The date will be displayed in the selected cell.": "日付と形式 を選択し、日付を挿入 ボタンをクリックしてください。選択したセルに日付が表示されます。",
+ "Select date": "日付を選択",
+ "Select a date": "日付を選択してください",
+ "Select date format": "日付形式を選択",
+ "Insert date": "日付を挿入",
+ "Loading...": "読み込み中...",
+ "Loading plugin...": "プラグインを読み込み中...",
+ "Initializing plugin...": "プラグインを初期化中...",
+ "Inserting date...": "日付を挿入中...",
+ "Please do not close the plugin panel.": "プラグインパネルを閉じないで ください。",
+ "January": "1月",
+ "February": "2月",
+ "March": "3月",
+ "April": "4月",
+ "May (full)": "5月",
+ "June": "6月",
+ "July": "7月",
+ "August": "8月",
+ "September": "9月",
+ "October": "10月",
+ "November": "11月",
+ "December": "12月",
+ "Sunday": "日曜日",
+ "Monday": "月曜日",
+ "Tuesday": "火曜日",
+ "Wednesday": "水曜日",
+ "Thursday": "木曜日",
+ "Friday": "金曜日",
+ "Saturday": "土曜日",
+ "Jan": "1月",
+ "Feb": "2月",
+ "Mar": "3月",
+ "Apr": "4月",
+ "May (short)": "5月",
+ "Jun": "6月",
+ "Jul": "7月",
+ "Aug": "8月",
+ "Sep": "9月",
+ "Oct": "10月",
+ "Nov": "11月",
+ "Dec": "12月"
+}
diff --git a/sdkjs-plugins/content/datepicker/translations/langs.json b/sdkjs-plugins/content/datepicker/translations/langs.json
new file mode 100644
index 00000000..439c8daf
--- /dev/null
+++ b/sdkjs-plugins/content/datepicker/translations/langs.json
@@ -0,0 +1,14 @@
+[
+ "ru-RU",
+ "de-DE",
+ "fr-FR",
+ "es-ES",
+ "pt-BR",
+ "it-IT",
+ "ja-JA",
+ "zh-ZH",
+ "cs-CS",
+ "sq-AL",
+ "sr-RS",
+ "sr-Latn-RS"
+]
diff --git a/sdkjs-plugins/content/datepicker/translations/pt-BR.json b/sdkjs-plugins/content/datepicker/translations/pt-BR.json
new file mode 100644
index 00000000..4527543e
--- /dev/null
+++ b/sdkjs-plugins/content/datepicker/translations/pt-BR.json
@@ -0,0 +1,43 @@
+{
+ "Select the date and format , then click the Insert date button. The date will be displayed in the selected cell.": "Selecione a data e formato , depois clique no botão Inserir data . A data será exibida na célula selecionada.",
+ "Select date": "Selecionar data",
+ "Select a date": "Selecione uma data",
+ "Select date format": "Selecionar formato de data",
+ "Insert date": "Inserir data",
+ "Loading...": "Carregando...",
+ "Loading plugin...": "Carregando plugin...",
+ "Initializing plugin...": "Inicializando plugin...",
+ "Inserting date...": "Inserindo data...",
+ "Please do not close the plugin panel.": "Por favor não feche o painel do plugin.",
+ "January": "Janeiro",
+ "February": "Fevereiro",
+ "March": "Março",
+ "April": "Abril",
+ "May (full)": "Maio",
+ "June": "Junho",
+ "July": "Julho",
+ "August": "Agosto",
+ "September": "Setembro",
+ "October": "Outubro",
+ "November": "Novembro",
+ "December": "Dezembro",
+ "Sunday": "Domingo",
+ "Monday": "Segunda-feira",
+ "Tuesday": "Terça-feira",
+ "Wednesday": "Quarta-feira",
+ "Thursday": "Quinta-feira",
+ "Friday": "Sexta-feira",
+ "Saturday": "Sábado",
+ "Jan": "Jan",
+ "Feb": "Fev",
+ "Mar": "Mar",
+ "Apr": "Abr",
+ "May (short)": "Mai",
+ "Jun": "Jun",
+ "Jul": "Jul",
+ "Aug": "Ago",
+ "Sep": "Set",
+ "Oct": "Out",
+ "Nov": "Nov",
+ "Dec": "Dez"
+}
diff --git a/sdkjs-plugins/content/datepicker/translations/ru-RU.json b/sdkjs-plugins/content/datepicker/translations/ru-RU.json
new file mode 100644
index 00000000..a7b39a32
--- /dev/null
+++ b/sdkjs-plugins/content/datepicker/translations/ru-RU.json
@@ -0,0 +1,43 @@
+{
+ "Select the date and format , then click the Insert date button. The date will be displayed in the selected cell.": "Выберите дату и формат , затем нажмите кнопку Вставить дату . Дата будет отображена в выбранной ячейке.",
+ "Select date": "Выбрать дату",
+ "Select a date": "Выберите дату",
+ "Select date format": "Выбрать формат даты",
+ "Insert date": "Вставить дату",
+ "Loading...": "Загрузка...",
+ "Loading plugin...": "Загрузка плагина...",
+ "Initializing plugin...": "Инициализация плагина...",
+ "Inserting date...": "Вставка даты...",
+ "Please do not close the plugin panel.": "Пожалуйста, не закрывайте панель плагина.",
+ "January": "Январь",
+ "February": "Февраль",
+ "March": "Март",
+ "April": "Апрель",
+ "May (full)": "Май",
+ "June": "Июнь",
+ "July": "Июль",
+ "August": "Август",
+ "September": "Сентябрь",
+ "October": "Октябрь",
+ "November": "Ноябрь",
+ "December": "Декабрь",
+ "Sunday": "Воскресенье",
+ "Monday": "Понедельник",
+ "Tuesday": "Вторник",
+ "Wednesday": "Среда",
+ "Thursday": "Четверг",
+ "Friday": "Пятница",
+ "Saturday": "Суббота",
+ "Jan": "Янв",
+ "Feb": "Фев",
+ "Mar": "Мар",
+ "Apr": "Апр",
+ "May (short)": "Май",
+ "Jun": "Июн",
+ "Jul": "Июл",
+ "Aug": "Авг",
+ "Sep": "Сен",
+ "Oct": "Окт",
+ "Nov": "Ноя",
+ "Dec": "Дек"
+}
diff --git a/sdkjs-plugins/content/datepicker/translations/sq-AL.json b/sdkjs-plugins/content/datepicker/translations/sq-AL.json
new file mode 100644
index 00000000..277f1374
--- /dev/null
+++ b/sdkjs-plugins/content/datepicker/translations/sq-AL.json
@@ -0,0 +1,43 @@
+{
+ "Select the date and format , then click the Insert date button. The date will be displayed in the selected cell.": "Zgjidhni datën dhe formatin , pastaj klikoni butonin Fut datën . Data do të shfaqet në qelizën e zgjedhur.",
+ "Select date": "Zgjidhni datën",
+ "Select a date": "Zgjidhni një datë",
+ "Select date format": "Zgjidhni formatin e datës",
+ "Insert date": "Fut datën",
+ "Loading...": "Duke ngarkuar...",
+ "Loading plugin...": "Duke ngarkuar plugin-in...",
+ "Initializing plugin...": "Duke inicializuar plugin-in...",
+ "Inserting date...": "Duke futur datën...",
+ "Please do not close the plugin panel.": "Ju lutem mos e mbyllni panelin e plugin-it.",
+ "January": "Janar",
+ "February": "Shkurt",
+ "March": "Mars",
+ "April": "Prill",
+ "May (full)": "Maj",
+ "June": "Qershor",
+ "July": "Korrik",
+ "August": "Gusht",
+ "September": "Shtator",
+ "October": "Tetor",
+ "November": "Nëntor",
+ "December": "Dhjetor",
+ "Sunday": "E diel",
+ "Monday": "E hënë",
+ "Tuesday": "E martë",
+ "Wednesday": "E mërkurë",
+ "Thursday": "E enjte",
+ "Friday": "E premte",
+ "Saturday": "E shtunë",
+ "Jan": "Jan",
+ "Feb": "Shk",
+ "Mar": "Mar",
+ "Apr": "Pri",
+ "May (short)": "Maj",
+ "Jun": "Qer",
+ "Jul": "Kor",
+ "Aug": "Gus",
+ "Sep": "Sht",
+ "Oct": "Tet",
+ "Nov": "Nën",
+ "Dec": "Dhj"
+}
diff --git a/sdkjs-plugins/content/datepicker/translations/sr-Latn.json b/sdkjs-plugins/content/datepicker/translations/sr-Latn.json
new file mode 100644
index 00000000..040cef64
--- /dev/null
+++ b/sdkjs-plugins/content/datepicker/translations/sr-Latn.json
@@ -0,0 +1,43 @@
+{
+ "Select the date and format , then click the Insert date button. The date will be displayed in the selected cell.": "Izaberite datum i format , zatim kliknite na dugme Ubaci datum . Datum će biti prikazan u izabranoj ćeliji.",
+ "Select date": "Izaberi datum",
+ "Select a date": "Izaberite datum",
+ "Select date format": "Izaberi format datuma",
+ "Insert date": "Ubaci datum",
+ "Loading...": "Učitavanje...",
+ "Loading plugin...": "Učitavanje dodatka...",
+ "Initializing plugin...": "Inicijalizacija dodatka...",
+ "Inserting date...": "Ubacivanje datuma...",
+ "Please do not close the plugin panel.": "Molimo ne zatvarajte panel dodatka.",
+ "January": "Januar",
+ "February": "Februar",
+ "March": "Mart",
+ "April": "April",
+ "May (full)": "Maj",
+ "June": "Jun",
+ "July": "Jul",
+ "August": "Avgust",
+ "September": "Septembar",
+ "October": "Oktobar",
+ "November": "Novembar",
+ "December": "Decembar",
+ "Sunday": "Nedelja",
+ "Monday": "Ponedeljak",
+ "Tuesday": "Utorak",
+ "Wednesday": "Sreda",
+ "Thursday": "Četvrtak",
+ "Friday": "Petak",
+ "Saturday": "Subota",
+ "Jan": "Jan",
+ "Feb": "Feb",
+ "Mar": "Mar",
+ "Apr": "Apr",
+ "May (short)": "Maj",
+ "Jun": "Jun",
+ "Jul": "Jul",
+ "Aug": "Avg",
+ "Sep": "Sep",
+ "Oct": "Okt",
+ "Nov": "Nov",
+ "Dec": "Dec"
+}
diff --git a/sdkjs-plugins/content/datepicker/translations/sr-RS.json b/sdkjs-plugins/content/datepicker/translations/sr-RS.json
new file mode 100644
index 00000000..ca9ce1d9
--- /dev/null
+++ b/sdkjs-plugins/content/datepicker/translations/sr-RS.json
@@ -0,0 +1,43 @@
+{
+ "Select the date and format , then click the Insert date button. The date will be displayed in the selected cell.": "Изаберите датум и формат , затим кликните на дугме Убаци датум . Датум ће бити приказан у изабраној ћелији.",
+ "Select date": "Изабери датум",
+ "Select a date": "Изаберите датум",
+ "Select date format": "Изабери формат датума",
+ "Insert date": "Убаци датум",
+ "Loading...": "Учитавање...",
+ "Loading plugin...": "Учитавање додатка...",
+ "Initializing plugin...": "Иницијализација додатка...",
+ "Inserting date...": "Убацивање датума...",
+ "Please do not close the plugin panel.": "Молимо не затварајте панел додатка.",
+ "January": "Јануар",
+ "February": "Фебруар",
+ "March": "Март",
+ "April": "Април",
+ "May (full)": "Мај",
+ "June": "Јун",
+ "July": "Јул",
+ "August": "Август",
+ "September": "Септембар",
+ "October": "Октобар",
+ "November": "Новембар",
+ "December": "Децембар",
+ "Sunday": "Недеља",
+ "Monday": "Понедељак",
+ "Tuesday": "Уторак",
+ "Wednesday": "Среда",
+ "Thursday": "Четвртак",
+ "Friday": "Петак",
+ "Saturday": "Субота",
+ "Jan": "Јан",
+ "Feb": "Феб",
+ "Mar": "Мар",
+ "Apr": "Апр",
+ "May (short)": "Мај",
+ "Jun": "Јун",
+ "Jul": "Јул",
+ "Aug": "Авг",
+ "Sep": "Сеп",
+ "Oct": "Окт",
+ "Nov": "Нов",
+ "Dec": "Дец"
+}
diff --git a/sdkjs-plugins/content/datepicker/translations/zh-ZH.json b/sdkjs-plugins/content/datepicker/translations/zh-ZH.json
new file mode 100644
index 00000000..c2069c73
--- /dev/null
+++ b/sdkjs-plugins/content/datepicker/translations/zh-ZH.json
@@ -0,0 +1,43 @@
+{
+ "Select the date and format , then click the Insert date button. The date will be displayed in the selected cell.": "选择日期和格式 ,然后点击插入日期 按钮。日期将显示在选定的单元格中。",
+ "Select date": "选择日期",
+ "Select a date": "选择一个日期",
+ "Select date format": "选择日期格式",
+ "Insert date": "插入日期",
+ "Loading...": "加载中...",
+ "Loading plugin...": "加载插件中...",
+ "Initializing plugin...": "初始化插件中...",
+ "Inserting date...": "插入日期中...",
+ "Please do not close the plugin panel.": "请不要关闭 插件面板。",
+ "January": "一月",
+ "February": "二月",
+ "March": "三月",
+ "April": "四月",
+ "May (full)": "五月",
+ "June": "六月",
+ "July": "七月",
+ "August": "八月",
+ "September": "九月",
+ "October": "十月",
+ "November": "十一月",
+ "December": "十二月",
+ "Sunday": "星期日",
+ "Monday": "星期一",
+ "Tuesday": "星期二",
+ "Wednesday": "星期三",
+ "Thursday": "星期四",
+ "Friday": "星期五",
+ "Saturday": "星期六",
+ "Jan": "1月",
+ "Feb": "2月",
+ "Mar": "3月",
+ "Apr": "4月",
+ "May (short)": "5月",
+ "Jun": "6月",
+ "Jul": "7月",
+ "Aug": "8月",
+ "Sep": "9月",
+ "Oct": "10月",
+ "Nov": "11月",
+ "Dec": "12月"
+}
diff --git a/sdkjs-plugins/content/news/CHANGELOG.md b/sdkjs-plugins/content/news/CHANGELOG.md
new file mode 100644
index 00000000..23477f53
--- /dev/null
+++ b/sdkjs-plugins/content/news/CHANGELOG.md
@@ -0,0 +1,4 @@
+# Change Log
+
+## 1.0.0 - Initial Release
+
diff --git a/sdkjs-plugins/content/news/README.md b/sdkjs-plugins/content/news/README.md
new file mode 100644
index 00000000..86338435
--- /dev/null
+++ b/sdkjs-plugins/content/news/README.md
@@ -0,0 +1,85 @@
+# News Plugin
+
+## Overview
+
+Search through millions of articles from multiple news sources using various news API providers. This plugin allows you to search for articles by keywords and browse top headlines by category and country, then open them directly in your browser.
+
+The plugin supports multiple news API providers. Choose your preferred provider and get a free API key to start using the plugin.
+
+## Features
+
+- **Multiple Providers**: Support for GNews and TheNewsAPI with easy provider switching
+- **Keyword Search**: Search for articles using specific keywords or phrases
+- **Top Headlines**: Browse top headlines by category (Business, Technology, Sports, etc.)
+- **Provider-Specific Options**: Dynamic UI that adapts to each provider's capabilities
+- **Language Support**: Support for multiple languages (English, Spanish, French, German, etc.)
+- **Advanced Settings**: Sort by publication date or relevance, filter by domains (TheNewsAPI)
+- **Display Options**: Choose what to display (available for GNews)
+- **Direct Access**: Click any article to open it in a new browser tab
+
+## Supported Providers
+
+### GNews ([gnews.io](https://gnews.io))
+- 100 requests/day (free tier)
+- Search in title, description, or content
+- 60,000+ news sources
+
+### TheNewsAPI ([thenewsapi.com](https://www.thenewsapi.com))
+- 150 requests/day (free tier)
+- Filter by specific domains
+- Locale-based filtering
+
+## How to use
+
+1. **Choose Provider**: Select your preferred news provider (GNews or TheNewsAPI)
+2. **Get API Key**: Register at your chosen provider's website and get your free API key
+3. **Install Plugin**: Open the plugin from the Plugins tab in ONLYOFFICE
+4. **Setup**: Enter your API key when prompted
+5. **Search**: Use the Search tab to find articles by keywords
+6. **Headlines**: Use the Top Headlines tab to browse news by category
+7. **Open Articles**: Click on any article to open it in your browser
+
+### Search Tab
+
+1. Enter keywords in the search field
+2. **(GNews only)** Choose what to display using the checkboxes (Title, Description, Content)
+3. Optionally configure advanced settings (language, sort order, domains for TheNewsAPI)
+4. Click "Find" to search
+
+### Top Headlines Tab
+
+1. Optionally enter keywords to filter headlines
+2. Select a category (General, Business, Technology, etc.)
+3. Select a country
+4. Click "Find" to get headlines
+
+## Requirements
+
+- ONLYOFFICE Document Editor
+- Internet connection
+- Free API key from your chosen provider ([gnews.io](https://gnews.io) or [thenewsapi.com](https://www.thenewsapi.com))
+
+## Known Issues
+
+- Requires internet connection to fetch articles
+- API rate limits apply based on your provider's plan
+- Some regions may have limited access to certain news sources
+- Each provider requires its own API key
+
+## Support
+
+For issues and feature requests, please contact the plugin developer.
+
+## Documentation
+
+- [ARCHITECTURE.md](ARCHITECTURE.md) - Technical architecture and code organization
+- [PROVIDER_FEATURES.md](PROVIDER_FEATURES.md) - Detailed comparison of provider features and capabilities
+- [CHANGELOG.md](CHANGELOG.md) - Version history and changes
+
+## API Information
+
+This plugin supports multiple news API providers:
+- [GNews API](https://gnews.io) - 100 requests/day free tier
+- [TheNewsAPI](https://www.thenewsapi.com) - 150 requests/day free tier
+
+Please refer to each provider's documentation for API limits and terms of service.
diff --git a/sdkjs-plugins/content/news/config.json b/sdkjs-plugins/content/news/config.json
new file mode 100644
index 00000000..322e2480
--- /dev/null
+++ b/sdkjs-plugins/content/news/config.json
@@ -0,0 +1,102 @@
+{
+ "name": "News",
+ "guid": "asc.{0616AE85-5DBE-4B6B-A0A9-455C4F1503AD}",
+ "version": "1.0.0",
+ "baseUrl": "",
+ "variations": [
+ {
+ "description": "Search through millions of articles from over 80,000 large and small news sources and blogs.",
+ "descriptionLocale": {
+ "ru": "Поиск среди миллионов статей из более чем 80 000 крупных и малых новостных источников и блогов.",
+ "fr": "Recherchez parmi des millions d'articles",
+ "es": "Busque entre millones de artículos de más de 80,000 fuentes de noticias y blogs grandes y pequeños.",
+ "de": "Durchsuchen Sie Millionen von Artikeln aus über 80.000 großen und kleinen Nachrichtenquellen und Blogs.",
+ "br": "Pesquise entre milhões de artigos de mais de 80.000 fontes de notícias e blogs grandes e pequenos.",
+ "it": "Cerca tra milioni di articoli di oltre 80.000 fonti, tra notizie e blog grandi e piccoli.",
+ "zh": "在来自8万多家新闻媒体和博客的数百万篇文章中快速搜索所需信息。",
+ "ja": "80,000を超える大小のニュースソースとブログから数百万の記事を検索します。",
+ "cs-CZ": "Prohledejte miliony článků z více než 80 000 velkých a malých zpravodajských zdrojů a blogů."
+ },
+ "url": "index.html",
+ "icons": [
+ "resources/store/icons/icon.png",
+ "resources/store/icons/icon@2x.png"
+ ],
+ "isViewer": false,
+ "EditorsSupport": ["word", "cell", "slide"],
+ "isVisual": true,
+ "isModal": false,
+ "isInsideMode": true,
+ "initDataType": "none",
+ "initData": "",
+ "isUpdateOleOnResize": true,
+ "buttons": [],
+ "size": [300, 0],
+ "store": {
+ "background": {
+ "light": "#F5F5F5",
+ "dark": "#444444"
+ },
+ "screenshots": [
+ "resources/store/screenshots/screenshot1.png",
+ "resources/store/screenshots/screenshot2.png",
+ "resources/store/screenshots/screenshot3.png"
+ ],
+ "icons": {
+ "light": "resources/store/icons",
+ "dark": "resources/store/icons"
+ },
+ "categories": ["specAbilities", "work"]
+ }
+ },
+ {
+ "description": "About",
+ "descriptionLocale": {
+ "de": "Über",
+ "es": "Acerca de",
+ "fr": "À propos",
+ "it": "Informazioni",
+ "pt": "Sobre",
+ "ru": "О программе",
+ "ja": "について",
+ "zh": "关于",
+ "cs": "O aplikaci",
+ "si": "O programu",
+ "sq": "Rreth"
+ },
+ "url": "index_about.html",
+ "icons": [
+ "resources/store/icons/icon.png",
+ "resources/store/icons/icon@2x.png"
+ ],
+ "isViewer": false,
+ "EditorsSupport": ["word"],
+ "isVisual": true,
+ "isModal": true,
+ "isInsideMode": false,
+ "initDataType": "none",
+ "initData": "",
+ "isUpdateOleOnResize": true,
+ "buttons": [
+ {
+ "text": "Ok",
+ "textLocale": {
+ "de": "OK",
+ "es": "Aceptar",
+ "fr": "OK",
+ "it": "OK",
+ "pt": "OK",
+ "ru": "OK",
+ "ja": "OK",
+ "zh": "确定",
+ "cs": "OK",
+ "si": "V redu",
+ "sq": "OK"
+ },
+ "primary": true
+ }
+ ],
+ "size": [392, 147]
+ }
+ ]
+}
diff --git a/sdkjs-plugins/content/news/deploy/news.plugin b/sdkjs-plugins/content/news/deploy/news.plugin
new file mode 100644
index 00000000..1b123fc4
Binary files /dev/null and b/sdkjs-plugins/content/news/deploy/news.plugin differ
diff --git a/sdkjs-plugins/content/news/index.html b/sdkjs-plugins/content/news/index.html
new file mode 100644
index 00000000..07a7a8d7
--- /dev/null
+++ b/sdkjs-plugins/content/news/index.html
@@ -0,0 +1,262 @@
+
+
+
+
+ News
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ News Provider:
+
+ GNews
+ TheNewsAPI
+ WorldNewsAPI
+
+
+
+
+
+
+
+ Login
+
+
+
+
+
+
+
+
+
+
+ Search
+
+
+ Top Headlines
+
+
+
+
+
+
+
+
+
+
+
+
+ Back to search
+
+
+
+
+
diff --git a/sdkjs-plugins/content/news/index_about.html b/sdkjs-plugins/content/news/index_about.html
new file mode 100644
index 00000000..2e06deb0
--- /dev/null
+++ b/sdkjs-plugins/content/news/index_about.html
@@ -0,0 +1,66 @@
+
+
+
+
+ About
+
+
+
+
+
+
+
+ News Plugin
+
+ This plugin provides a convenient sidebar interface to search and browse
+ news articles from multiple providers including GNews and TheNewsAPI. Search for topics and
+ open relevant news articles in new tabs without leaving your document.
+
+
+ GNews.io |
+ TheNewsAPI.com
+
+
+
diff --git a/sdkjs-plugins/content/news/pre_release.md b/sdkjs-plugins/content/news/pre_release.md
new file mode 100644
index 00000000..cc4561c5
--- /dev/null
+++ b/sdkjs-plugins/content/news/pre_release.md
@@ -0,0 +1,53 @@
+# Change Log
+
+## 0.4.0
+
+- Added WorldNewsAPI as a third news provider
+- WorldNewsAPI: Advanced filtering
+
+## 0.3.0
+
+- Added support for multiple news providers (GNews and TheNewsAPI)
+- Provider selection dropdown with automatic API key clearing on switch
+- Plugin renamed from "GNews API" to "News"
+- Dynamic advanced settings that adapt to selected provider
+- TheNewsAPI: Added domain filtering and search field options
+- New providers.js module for centralized configuration
+
+## 0.2.0
+
+- Major refactoring: Split monolithic code into modular architecture
+- Created separate modules: storage.js, api.js, ui.js, translations.js
+- Enhanced error handling and separation of concerns
+- Zero breaking changes - all existing functionality preserved
+
+## 0.1.3
+
+- Added persistent API key storage using localStorage - users no longer need to re-enter API key each time
+- Fixed "Show advanced settings" translation - now properly translates in all languages
+- Enhanced API key management with automatic loading and pre-filling of stored keys
+- Improved plugin initialization to automatically show search interface when API key is stored
+- Added comprehensive error handling for localStorage operations with fallback support
+
+## 0.1.2
+
+- Added larger icon sizes for marketplace submission
+- Prepared marketplace documentation and assets
+
+## 0.1.1
+
+- Fixed bug where "Back to search" triggered search when checking display options
+- Improved status message handling - no longer shows success message when updating display options
+- Removed hardcoded article limit for headlines to show actual API results
+- Enhanced scrolling support for small plugin heights
+- Repositioned "Reconfigure" button to prevent overlap issues
+
+## 0.1.0
+
+- Initial development version
+- Keyword search functionality
+- Top headlines browsing
+- Support for multiple languages and countries
+- Advanced search settings (sort by date/relevance)
+- Display options for title, description, and content
+- Direct article opening in browser
diff --git a/sdkjs-plugins/content/news/resources/dark/icon.png b/sdkjs-plugins/content/news/resources/dark/icon.png
new file mode 100644
index 00000000..b97aa168
Binary files /dev/null and b/sdkjs-plugins/content/news/resources/dark/icon.png differ
diff --git a/sdkjs-plugins/content/news/resources/dark/icon.svg b/sdkjs-plugins/content/news/resources/dark/icon.svg
new file mode 100644
index 00000000..997de505
--- /dev/null
+++ b/sdkjs-plugins/content/news/resources/dark/icon.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/sdkjs-plugins/content/news/resources/dark/icon@1.25x.png b/sdkjs-plugins/content/news/resources/dark/icon@1.25x.png
new file mode 100644
index 00000000..53b29039
Binary files /dev/null and b/sdkjs-plugins/content/news/resources/dark/icon@1.25x.png differ
diff --git a/sdkjs-plugins/content/news/resources/dark/icon@1.5x.png b/sdkjs-plugins/content/news/resources/dark/icon@1.5x.png
new file mode 100644
index 00000000..839f5832
Binary files /dev/null and b/sdkjs-plugins/content/news/resources/dark/icon@1.5x.png differ
diff --git a/sdkjs-plugins/content/news/resources/dark/icon@1.75x.png b/sdkjs-plugins/content/news/resources/dark/icon@1.75x.png
new file mode 100644
index 00000000..839f5832
Binary files /dev/null and b/sdkjs-plugins/content/news/resources/dark/icon@1.75x.png differ
diff --git a/sdkjs-plugins/content/news/resources/dark/icon@2x.png b/sdkjs-plugins/content/news/resources/dark/icon@2x.png
new file mode 100644
index 00000000..0a2e3853
Binary files /dev/null and b/sdkjs-plugins/content/news/resources/dark/icon@2x.png differ
diff --git a/sdkjs-plugins/content/news/resources/light/icon.png b/sdkjs-plugins/content/news/resources/light/icon.png
new file mode 100644
index 00000000..b97aa168
Binary files /dev/null and b/sdkjs-plugins/content/news/resources/light/icon.png differ
diff --git a/sdkjs-plugins/content/news/resources/light/icon.svg b/sdkjs-plugins/content/news/resources/light/icon.svg
new file mode 100644
index 00000000..997de505
--- /dev/null
+++ b/sdkjs-plugins/content/news/resources/light/icon.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/sdkjs-plugins/content/news/resources/light/icon@1.25x.png b/sdkjs-plugins/content/news/resources/light/icon@1.25x.png
new file mode 100644
index 00000000..53b29039
Binary files /dev/null and b/sdkjs-plugins/content/news/resources/light/icon@1.25x.png differ
diff --git a/sdkjs-plugins/content/news/resources/light/icon@1.5x.png b/sdkjs-plugins/content/news/resources/light/icon@1.5x.png
new file mode 100644
index 00000000..839f5832
Binary files /dev/null and b/sdkjs-plugins/content/news/resources/light/icon@1.5x.png differ
diff --git a/sdkjs-plugins/content/news/resources/light/icon@1.75x.png b/sdkjs-plugins/content/news/resources/light/icon@1.75x.png
new file mode 100644
index 00000000..839f5832
Binary files /dev/null and b/sdkjs-plugins/content/news/resources/light/icon@1.75x.png differ
diff --git a/sdkjs-plugins/content/news/resources/light/icon@2x.png b/sdkjs-plugins/content/news/resources/light/icon@2x.png
new file mode 100644
index 00000000..0a2e3853
Binary files /dev/null and b/sdkjs-plugins/content/news/resources/light/icon@2x.png differ
diff --git a/sdkjs-plugins/content/news/resources/store/icons/icon.png b/sdkjs-plugins/content/news/resources/store/icons/icon.png
new file mode 100644
index 00000000..b97aa168
Binary files /dev/null and b/sdkjs-plugins/content/news/resources/store/icons/icon.png differ
diff --git a/sdkjs-plugins/content/news/resources/store/icons/icon.svg b/sdkjs-plugins/content/news/resources/store/icons/icon.svg
new file mode 100644
index 00000000..997de505
--- /dev/null
+++ b/sdkjs-plugins/content/news/resources/store/icons/icon.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/sdkjs-plugins/content/news/resources/store/icons/icon@1.25x.png b/sdkjs-plugins/content/news/resources/store/icons/icon@1.25x.png
new file mode 100644
index 00000000..53b29039
Binary files /dev/null and b/sdkjs-plugins/content/news/resources/store/icons/icon@1.25x.png differ
diff --git a/sdkjs-plugins/content/news/resources/store/icons/icon@1.5x.png b/sdkjs-plugins/content/news/resources/store/icons/icon@1.5x.png
new file mode 100644
index 00000000..839f5832
Binary files /dev/null and b/sdkjs-plugins/content/news/resources/store/icons/icon@1.5x.png differ
diff --git a/sdkjs-plugins/content/news/resources/store/icons/icon@1.75x.png b/sdkjs-plugins/content/news/resources/store/icons/icon@1.75x.png
new file mode 100644
index 00000000..839f5832
Binary files /dev/null and b/sdkjs-plugins/content/news/resources/store/icons/icon@1.75x.png differ
diff --git a/sdkjs-plugins/content/news/resources/store/icons/icon@2x.png b/sdkjs-plugins/content/news/resources/store/icons/icon@2x.png
new file mode 100644
index 00000000..0a2e3853
Binary files /dev/null and b/sdkjs-plugins/content/news/resources/store/icons/icon@2x.png differ
diff --git a/sdkjs-plugins/content/news/resources/store/screenshots/screenshot1.png b/sdkjs-plugins/content/news/resources/store/screenshots/screenshot1.png
new file mode 100644
index 00000000..20798010
Binary files /dev/null and b/sdkjs-plugins/content/news/resources/store/screenshots/screenshot1.png differ
diff --git a/sdkjs-plugins/content/news/resources/store/screenshots/screenshot2.png b/sdkjs-plugins/content/news/resources/store/screenshots/screenshot2.png
new file mode 100644
index 00000000..7916b396
Binary files /dev/null and b/sdkjs-plugins/content/news/resources/store/screenshots/screenshot2.png differ
diff --git a/sdkjs-plugins/content/news/resources/store/screenshots/screenshot3.png b/sdkjs-plugins/content/news/resources/store/screenshots/screenshot3.png
new file mode 100644
index 00000000..f96df2b8
Binary files /dev/null and b/sdkjs-plugins/content/news/resources/store/screenshots/screenshot3.png differ
diff --git a/sdkjs-plugins/content/news/resources/style/style.css b/sdkjs-plugins/content/news/resources/style/style.css
new file mode 100644
index 00000000..ee2d27da
--- /dev/null
+++ b/sdkjs-plugins/content/news/resources/style/style.css
@@ -0,0 +1,344 @@
+:root {
+ /* Common variables */
+ --space: 8px;
+ --space-lg: 16px;
+
+ --font-family: Arial, sans-serif;
+ --font-size: 12px;
+ --font-size-sm: 10px;
+}
+
+/* Light Theme (Default) */
+body, body[data-theme="light"] {
+ --color-primary: #333;
+ --color-text: #333;
+ --color-text-muted: #666;
+ --color-border: #ddd;
+ --color-bg-hover: #f5f5f5;
+
+ --color-bg: #ffffff;
+ --color-bg-element: #ffffff;
+ --color-bg-subtle: #f8f9fa;
+
+ --color-success-bg: #d4edda;
+ --color-success-text: #155724;
+ --color-success-border: #c3e6cb;
+
+ --color-error-bg: #f8d7da;
+ --color-error-text: #721c24;
+ --color-error-border: #f5c6cb;
+}
+
+/* Dark Theme */
+body[data-theme="dark"] {
+ --color-primary: #ccc;
+ --color-text: #e0e0e0;
+ --color-text-muted: #999;
+ --color-border: #444;
+ --color-bg-hover: #2a2a2a;
+
+ --color-bg: #1a1a1a;
+ --color-bg-element: #2d2d2d;
+ --color-bg-subtle: #2d2d2d;
+
+ --color-success-bg: #1e4d2b;
+ --color-success-text: #90ee90;
+ --color-success-border: #2d5a34;
+
+ --color-error-bg: #4d1e1e;
+ --color-error-text: #ff9999;
+ --color-error-border: #5a2d2d;
+}
+
+* {
+ box-sizing: border-box;
+}
+
+body {
+ font: var(--font-size) var(--font-family);
+ padding: var(--space-lg);
+ margin: 0;
+ color: var(--color-text);
+ background-color: var(--color-bg);
+ height: 100vh;
+ transition: background-color 0.2s, color 0.2s;
+}
+
+input,
+select,
+textarea {
+ width: 100%;
+ padding: var(--space);
+ border: 1px solid var(--color-border);
+ border-radius: 4px;
+ font-size: var(--font-size);
+ font-family: var(--font-family);
+ background-color: var(--color-bg-element);
+ color: var(--color-text);
+}
+
+select option {
+ background-color: var(--color-bg-element);
+ color: var(--color-text);
+}
+
+label {
+ display: block;
+ margin-bottom: var(--space);
+ font-weight: bold;
+ color: var(--color-text);
+}
+
+.btn,
+button,
+.insert-btn,
+.tab-btn {
+ padding: var(--space) var(--space-lg);
+ border: 1px solid var(--color-border);
+ border-radius: 1px;
+ background: var(--color-bg-element);
+ color: var(--color-text);
+ cursor: pointer;
+ font-size: var(--font-size);
+ text-decoration: none;
+ display: inline-block;
+ transition: all 0.2s;
+ width: 100%;
+}
+
+.btn-link {
+ padding: 0;
+ background: none;
+ border: none;
+ text-decoration: underline;
+ text-align: left;
+ color: var(--color-primary);
+}
+
+.btn-back {
+ background: var(--color-bg-element);
+ border: none;
+ padding: var(--space) var(--space-lg);
+ font-size: var(--font-size);
+ color: var(--color-text);
+}
+
+.btn:hover,
+button:hover,
+.insert-btn:hover,
+.btn-back:hover,
+.btn-link:hover,
+.tab-btn:hover {
+ background: var(--color-bg-hover);
+}
+
+.btn-small {
+ padding: 4px var(--space);
+ font-size: var(--font-size-sm);
+}
+
+.form-group,
+.button-group,
+.filter-section {
+ margin-bottom: var(--space-lg);
+}
+
+.filter-header {
+ font-weight: bold;
+ margin-bottom: var(--space);
+ color: var(--color-text);
+}
+
+.results-section {
+ margin-top: var(--space-lg);
+ border-top: 1px solid var(--color-border);
+ padding-top: var(--space-lg);
+}
+
+/* Scrolling support for main containers */
+.setup-screen,
+.search-screen {
+ max-height: 100vh;
+}
+
+.search-form-container,
+.results-section {
+ max-height: calc(100vh - 100px);
+ overflow-y: auto;
+ overflow-x: hidden;
+}
+
+.articles-container {
+ max-height: calc(100vh - 200px);
+ overflow-y: auto;
+ overflow-x: hidden;
+}
+
+.article-item {
+ padding: var(--space-lg);
+ border-bottom: 1px solid var(--color-border);
+ cursor: pointer;
+ transition: background 0.2s;
+}
+
+.article-item:hover {
+ background: var(--color-bg-hover);
+}
+
+.article-title {
+ font-weight: bold;
+ margin-bottom: var(--space);
+ color: var(--color-text);
+}
+
+.article-description {
+ font-size: var(--font-size-sm);
+ margin-bottom: var(--space);
+ display: -webkit-box;
+ -webkit-line-clamp: 2;
+ -webkit-box-orient: vertical;
+ overflow: hidden;
+ color: var(--color-text);
+}
+
+.article-meta {
+ font-size: var(--font-size-sm);
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ margin-bottom: var(--space);
+ color: var(--color-text-muted);
+}
+
+.article-meta .open-icon {
+ cursor: pointer;
+ width: 16px;
+ height: 16px;
+ opacity: 0.7;
+ transition: opacity 0.2s;
+ color: var(--color-text-muted);
+}
+
+.article-meta .open-icon:hover {
+ opacity: 1;
+}
+
+#status {
+ padding: var(--space);
+ border-radius: 4px;
+ margin-top: var(--space);
+}
+
+.success {
+ background: var(--color-success-bg);
+ color: var(--color-success-text);
+ border: 1px solid var(--color-success-border);
+}
+
+.error {
+ background: var(--color-error-bg);
+ color: var(--color-error-text);
+ border: 1px solid var(--color-error-border);
+}
+
+.tab-navigation {
+ display: flex;
+ border-bottom: 1px solid var(--color-border);
+ margin-bottom: var(--space-lg);
+}
+
+.tab-btn {
+ flex: 1;
+ border: none;
+ border-bottom: 1px solid var(--color-border);
+ background: none;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ gap: var(--space);
+ color: var(--color-text-muted);
+ padding: var(--space-lg);
+}
+
+.tab-btn svg {
+ width: 16px;
+ height: 16px;
+}
+
+.tab-btn.active {
+ color: var(--color-primary);
+ border-bottom: none;
+ font-weight: bold;
+ background: var(--color-bg);
+ margin-bottom: -1px;
+}
+
+.tab-btn:not(.active) {
+ color: var(--color-text-muted);
+}
+
+.tab-btn:hover:not(.active) {
+ color: var(--color-text);
+ background: var(--color-bg-hover);
+}
+
+.no-results {
+ text-align: center;
+ font-style: italic;
+ padding: var(--space-lg);
+ color: var(--color-text-muted);
+}
+
+.settings-actions .btn-link {
+ text-decoration: underline dotted;
+ display: block;
+ margin-bottom: var(--space);
+}
+
+.checkbox-item {
+ display: flex;
+ align-items: center;
+ margin-bottom: var(--space);
+ color: var(--color-text);
+}
+
+.checkbox-item input {
+ width: auto;
+ margin-right: var(--space);
+}
+
+.help-text {
+ font-size: var(--font-size-sm);
+ margin-top: var(--space);
+ color: var(--color-text-muted);
+}
+
+.help-text a {
+ color: var(--color-primary);
+}
+
+.api-info {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ padding: var(--space);
+ background: var(--color-bg-subtle);
+ border-radius: 4px;
+ margin-bottom: var(--space-lg);
+ color: var(--color-text);
+}
+
+.results-header-container {
+ display: flex;
+ align-items: center;
+ gap: var(--space);
+ margin-bottom: var(--space-lg);
+}
+
+.results-header {
+ color: var(--color-text);
+}
+
+.setup-header p {
+ color: var(--color-text-muted);
+}
diff --git a/sdkjs-plugins/content/news/scripts/api.js b/sdkjs-plugins/content/news/scripts/api.js
new file mode 100644
index 00000000..cccb4885
--- /dev/null
+++ b/sdkjs-plugins/content/news/scripts/api.js
@@ -0,0 +1,192 @@
+/**
+ * API Manager for News Plugin
+ * Handles all interactions with news APIs
+ */
+
+(function (window) {
+ "use strict";
+
+ /**
+ * API Manager
+ */
+ var APIManager = {
+ apiKey: "",
+ provider: null,
+
+ /**
+ * Set the API key for subsequent requests
+ * @param {string} key - The API key
+ */
+ setApiKey: function (key) {
+ this.apiKey = key;
+ },
+
+ /**
+ * Set the provider for subsequent requests
+ * @param {string} providerId - The provider ID
+ */
+ setProvider: function (providerId) {
+ this.provider = window.NewsProviders.setProvider(providerId);
+ },
+
+ /**
+ * Get current provider
+ */
+ getProvider: function () {
+ return this.provider || window.NewsProviders.getCurrentProvider();
+ },
+
+ /**
+ * Build URL with query parameters
+ * @param {string} base - Base URL
+ * @param {object} params - Query parameters
+ * @returns {string} - Complete URL with parameters
+ */
+ buildUrl: function (base, params) {
+ const url = new URL(base);
+ Object.keys(params).forEach(function (key) {
+ if (params[key] !== null && params[key] !== undefined && params[key] !== "") {
+ url.searchParams.append(key, params[key]);
+ }
+ });
+ return url.toString();
+ },
+
+ /**
+ * Perform a generic API call
+ * @param {string} url - The API endpoint URL
+ * @param {function} callback - Callback function(result)
+ */
+ performAPICall: function (url, callback) {
+ var provider = this.getProvider();
+
+ // Prepare fetch options
+ var fetchOptions = {
+ method: 'GET',
+ };
+
+ // WorldNewsAPI uses header-based authentication
+ if (provider.id === 'worldnewsapi') {
+ fetchOptions.headers = {
+ 'x-api-key': this.apiKey,
+ };
+ }
+
+ fetch(url, fetchOptions)
+ .then(function (response) {
+ if (response.ok) return response.json();
+
+ // Handle specific HTTP errors
+ throw new Error(
+ response.status === 401
+ ? "Invalid API token"
+ : response.status === 429
+ ? "API rate limit exceeded"
+ : response.status === 400
+ ? "Bad request - check your parameters"
+ : "HTTP " + response.status
+ );
+ })
+ .then(function (data) {
+ // Use provider-specific response parser
+ var result = provider.parseResponse(data);
+ callback(result);
+ })
+ .catch(function (error) {
+ console.error("API error:", error);
+
+ const message = error.message.includes("Invalid API token")
+ ? "Invalid API token. Please check your token and try again."
+ : error.message.includes("rate limit")
+ ? "API rate limit exceeded. Please try again later."
+ : error.message.includes("CORS")
+ ? "Network error: CORS issue"
+ : "API failed: " + error.message;
+
+ callback({ success: false, error: message, articles: [] });
+ });
+ },
+
+ /**
+ * Validate an API key
+ * @param {string} apiKey - The API key to validate
+ * @param {function} callback - Callback function(isValid, message)
+ */
+ validateApiKey: function (apiKey, callback) {
+ try {
+ var provider = this.getProvider();
+ const testUrl = provider.buildValidationUrl(apiKey);
+
+ // Prepare fetch options
+ var fetchOptions = {
+ method: 'GET',
+ };
+
+ // WorldNewsAPI uses header-based authentication
+ if (provider.id === 'worldnewsapi') {
+ fetchOptions.headers = {
+ 'x-api-key': apiKey,
+ };
+ }
+
+ fetch(testUrl, fetchOptions)
+ .then(function (response) {
+ if (response.ok) return response.json();
+ throw new Error(
+ response.status === 401
+ ? "Invalid API token"
+ : "API validation failed"
+ );
+ })
+ .then(function (data) {
+ callback(true, "API key is valid");
+ })
+ .catch(function (error) {
+ console.error("Validation error:", error);
+ callback(false, error.message);
+ });
+ } catch (error) {
+ console.error("Validation error:", error);
+ callback(false, "Error validating API key: " + error.message);
+ }
+ },
+
+ /**
+ * Search for articles
+ * @param {string} query - Search query
+ * @param {object} settings - Additional settings (language, sortBy, etc.)
+ * @param {function} callback - Callback function(result)
+ */
+ search: function (query, settings, callback) {
+ try {
+ var provider = this.getProvider();
+ const searchUrl = provider.buildSearchUrl(this.apiKey, query, settings);
+
+ this.performAPICall(searchUrl, callback);
+ } catch (error) {
+ console.error("Search error:", error);
+ callback({ success: false, error: error.message, articles: [] });
+ }
+ },
+
+ /**
+ * Get top headlines
+ * @param {object} params - Parameters (category, country, language, query)
+ * @param {function} callback - Callback function(result)
+ */
+ getTopHeadlines: function (params, callback) {
+ try {
+ var provider = this.getProvider();
+ const headlinesUrl = provider.buildHeadlinesUrl(this.apiKey, params);
+
+ this.performAPICall(headlinesUrl, callback);
+ } catch (error) {
+ console.error("Headlines error:", error);
+ callback({ success: false, error: error.message, articles: [] });
+ }
+ },
+ };
+
+ // Export to global scope
+ window.GNewsAPI = APIManager;
+})(window);
diff --git a/sdkjs-plugins/content/news/scripts/code_new.js b/sdkjs-plugins/content/news/scripts/code_new.js
new file mode 100644
index 00000000..be32e090
--- /dev/null
+++ b/sdkjs-plugins/content/news/scripts/code_new.js
@@ -0,0 +1,383 @@
+/**
+ * News Plugin - Main Entry Point
+ * Coordinates between API, UI, Storage, and Translation modules
+ */
+
+(function (window, undefined) {
+ "use strict";
+
+ // Plugin state
+ let savedApiKey = "";
+ let savedProvider = "gnews";
+
+ /**
+ * Initialize the plugin
+ */
+ window.Asc.plugin.init = function () {
+ try {
+ // Load saved provider
+ savedProvider = window.GNewsStorage.loadProvider();
+ window.GNewsAPI.setProvider(savedProvider);
+ window.NewsProviders.setProvider(savedProvider);
+ console.log("Loaded provider:", savedProvider);
+
+ // Load saved API key
+ savedApiKey = window.GNewsStorage.loadApiKey();
+ if (savedApiKey) {
+ window.GNewsAPI.setApiKey(savedApiKey);
+ console.log("Loaded API key from storage");
+ }
+
+ // Initialize UI after a short delay
+ setTimeout(function () {
+ window.GNewsUI.initializeDisplayOptions();
+ window.GNewsUI.createAdvancedSettings();
+ window.GNewsUI.initializeProviderSelector(savedProvider);
+ window.GNewsUI.updateProviderInfo();
+
+ if (savedApiKey) {
+ window.GNewsUI.showSearchInterface();
+ setTimeout(function () {
+ const searchInput = window.GNewsUI.$("search-query");
+ if (searchInput) searchInput.focus();
+ }, 100);
+ } else {
+ window.GNewsUI.showApiSetup();
+ }
+
+ setupEventListeners();
+ window.GNewsTranslations.applyTranslations();
+ }, 50);
+ } catch (error) {
+ console.error("Init error:", error);
+ }
+ };
+
+ /**
+ * Handle theme changes
+ */
+ window.Asc.plugin.onThemeChanged = function (theme) {
+ // Set the data-theme attribute on the body
+ document.body.setAttribute("data-theme", theme.type);
+ console.log("Applied theme:", theme.type);
+ };
+
+ /**
+ * Handle translation changes
+ */
+ window.Asc.plugin.onTranslate = function () {
+ window.GNewsTranslations.applyTranslations();
+ };
+
+ /**
+ * Setup event listeners for Enter key support
+ */
+ function setupEventListeners() {
+ const apiKeyInput = window.GNewsUI.$("api-key-setup");
+ const queryInput = window.GNewsUI.$("search-query");
+ const headlinesQueryInput = window.GNewsUI.$("headlines-query");
+
+ if (apiKeyInput) {
+ apiKeyInput.addEventListener("keypress", function (e) {
+ if (e.key === "Enter") {
+ window.saveApiKey();
+ }
+ });
+ }
+
+ if (queryInput) {
+ queryInput.addEventListener("keypress", function (e) {
+ if (e.key === "Enter" && !e.shiftKey) {
+ e.preventDefault();
+ window.searchNews();
+ }
+ });
+ }
+
+ if (headlinesQueryInput) {
+ headlinesQueryInput.addEventListener("keypress", function (e) {
+ if (e.key === "Enter" && !e.shiftKey) {
+ e.preventDefault();
+ window.getTopHeadlines();
+ }
+ });
+ }
+ }
+
+ /**
+ * Save and validate API key
+ */
+ window.saveApiKey = function () {
+ const apiKeyInput = window.GNewsUI.$("api-key-setup");
+ const apiKey = apiKeyInput.value.trim();
+
+ if (!apiKey) {
+ window.GNewsUI.showStatus("Please enter an API key", true);
+ return;
+ }
+
+ if (apiKey.length < 10) {
+ window.GNewsUI.showStatus("API key seems too short", true);
+ return;
+ }
+
+ window.GNewsUI.setElementState("save-api-btn", true, "Validating...");
+ window.GNewsUI.showStatus("Validating API token...", false);
+
+ window.GNewsAPI.validateApiKey(apiKey, function (isValid, message) {
+ window.GNewsUI.setElementState("save-api-btn", false, "Login");
+
+ if (isValid) {
+ savedApiKey = apiKey;
+ window.GNewsAPI.setApiKey(apiKey);
+
+ if (window.GNewsStorage.saveApiKey(apiKey)) {
+ window.GNewsUI.showStatus("API key saved successfully!", false);
+ } else {
+ window.GNewsUI.showStatus(
+ "API key validated but couldn't save to storage",
+ true
+ );
+ }
+
+ setTimeout(function () {
+ window.GNewsUI.showSearchInterface();
+ const searchInput = window.GNewsUI.$("search-query");
+ if (searchInput) searchInput.focus();
+ }, 1000);
+ } else {
+ window.GNewsUI.showStatus(message || "Invalid API key", true);
+ }
+ });
+ };
+
+ /**
+ * Change/reconfigure API key
+ */
+ window.changeApiKey = function () {
+ savedApiKey = "";
+ window.GNewsAPI.setApiKey("");
+
+ if (window.GNewsStorage.removeApiKey()) {
+ window.GNewsUI.showStatus("API key removed", false);
+ } else {
+ window.GNewsUI.showStatus("Couldn't remove API key from storage", true);
+ }
+
+ window.GNewsUI.showApiSetup();
+ const apiKeyInput = window.GNewsUI.$("api-key-setup");
+ if (apiKeyInput) {
+ apiKeyInput.value = "";
+ apiKeyInput.focus();
+ }
+
+ const status = window.GNewsUI.$("status");
+ if (status) {
+ setTimeout(function () {
+ status.textContent = "";
+ status.className = "";
+ }, 2000);
+ }
+ };
+
+ /**
+ * Change provider
+ */
+ window.changeProvider = function () {
+ const providerSelect = window.GNewsUI.$("provider-select");
+ if (!providerSelect) return;
+
+ const newProvider = providerSelect.value;
+ savedProvider = newProvider;
+
+ window.GNewsAPI.setProvider(newProvider);
+ window.NewsProviders.setProvider(newProvider);
+ window.GNewsStorage.saveProvider(newProvider);
+ window.GNewsUI.currentProvider = newProvider;
+ window.GNewsUI.updateProviderInfo();
+
+
+ // Clear API key since different providers need different keys
+ if (savedApiKey) {
+ window.GNewsUI.showStatus(
+ "Provider changed. Please enter your API key for " +
+ window.NewsProviders.getCurrentProvider().name,
+ false
+ );
+ savedApiKey = "";
+ window.GNewsAPI.setApiKey("");
+ window.GNewsStorage.removeApiKey();
+
+ const apiKeyInput = window.GNewsUI.$("api-key-setup");
+ if (apiKeyInput) {
+ apiKeyInput.value = "";
+ }
+ }
+ };
+
+ /**
+ * Switch between tabs
+ */
+ window.switchTab = function (tabName) {
+ window.GNewsUI.switchTab(tabName);
+ };
+
+ /**
+ * Toggle advanced settings
+ */
+ window.toggleAdvancedSettings = function () {
+ window.GNewsUI.toggleAdvancedSettings();
+ };
+
+ window.advancedSettings = function () {
+ window.toggleAdvancedSettings();
+ };
+
+ /**
+ * Perform news search
+ */
+ window.searchNews = function () {
+ const queryInput = window.GNewsUI.$("search-query");
+ if (!queryInput) {
+ window.GNewsUI.showStatus("Search input not found", true);
+ return;
+ }
+
+ const query = queryInput.value.trim();
+ if (!query) {
+ window.GNewsUI.showStatus("Please enter a search query", true);
+ return;
+ }
+
+ if (!savedApiKey) {
+ window.GNewsUI.showStatus("Please configure your API key first", true);
+ return;
+ }
+
+ window.GNewsUI.setElementState("search-btn", true, "Searching...");
+ window.GNewsUI.showStatus("Searching...", false);
+
+ const settings = window.GNewsUI.getAdvancedSettings("search");
+
+ window.GNewsAPI.search(query, settings, function (result) {
+ window.GNewsUI.setElementState("search-btn", false, "Find");
+
+ if (result.success) {
+ window.GNewsUI.displaySearchResults(result.articles, true);
+ } else {
+ window.GNewsUI.showStatus(result.error || "Search failed", true);
+ }
+ });
+ };
+
+ /**
+ * Get top headlines
+ */
+ window.getTopHeadlines = function () {
+ if (!savedApiKey) {
+ window.GNewsUI.showStatus("Please configure your API key first", true);
+ return;
+ }
+
+ const query = window.GNewsUI.$("headlines-query").value;
+ const category = window.GNewsUI.$("headlines-category").value;
+ const country = window.GNewsUI.$("headlines-country").value;
+ const settings = window.GNewsUI.getAdvancedSettings("headlines");
+
+ window.GNewsUI.setElementState("headlines-btn", true, "Loading...");
+ window.GNewsUI.showStatus("Loading headlines...", false);
+
+ const params = {
+ category: category,
+ country: country,
+ language: settings.language,
+ query: query,
+ domains: settings.domains,
+ exclude_domains: settings.exclude_domains,
+ };
+
+ window.GNewsAPI.getTopHeadlines(params, function (result) {
+ window.GNewsUI.setElementState("headlines-btn", false, "Find");
+
+ if (result.success) {
+ window.GNewsUI.displaySearchResults(result.articles, true);
+ } else {
+ window.GNewsUI.showStatus(result.error || "Failed to load headlines", true);
+ }
+ });
+ };
+
+ /**
+ * Insert single article (open in browser)
+ */
+ window.insertSingleArticle = function (index) {
+ if (index < 0 || index >= window.GNewsUI.currentArticles.length) {
+ window.GNewsUI.showStatus("Invalid article index", true);
+ return;
+ }
+
+ const article = window.GNewsUI.currentArticles[index];
+ window.GNewsUI.openArticleLink(article);
+ };
+
+ /**
+ * Go back to search form
+ */
+ window.goBackToSearch = function () {
+ window.GNewsUI.goBackToSearch();
+ };
+
+ /**
+ * Clear results
+ */
+ window.clearResults = function () {
+ window.GNewsUI.goBackToSearch();
+ };
+
+ /**
+ * Update article display when checkboxes change
+ */
+ window.updateArticleDisplay = function () {
+ if (window.GNewsUI.currentArticles.length > 0) {
+ window.GNewsUI.displaySearchResults(window.GNewsUI.currentArticles, false);
+ }
+ };
+
+ /**
+ * Handle plugin button clicks
+ */
+ window.Asc.plugin.button = function (id) {
+ if (id === -1 || id === 0) {
+ this.executeCommand("close", "");
+ }
+ };
+
+ /**
+ * Handle external mouse events
+ */
+ window.Asc.plugin.onExternalMouseUp = function () {
+ return false;
+ };
+
+ /**
+ * Handle beforeunload event
+ */
+ window.addEventListener("beforeunload", function (e) {
+ window.GNewsUI.currentArticles = [];
+ window.GNewsUI.currentTab = "search";
+ });
+
+ /**
+ * Handle plugin commands
+ */
+ window.Asc.plugin.executeCommand = function (command, data) {
+ if (command === "close") {
+ this.executeCommand("close", "");
+ }
+ };
+
+ /**
+ * Handle method return values
+ */
+ window.Asc.plugin.onMethodReturn = function (returnValue) {};
+})(window, undefined);
diff --git a/sdkjs-plugins/content/news/scripts/providers.js b/sdkjs-plugins/content/news/scripts/providers.js
new file mode 100644
index 00000000..73839dec
--- /dev/null
+++ b/sdkjs-plugins/content/news/scripts/providers.js
@@ -0,0 +1,421 @@
+/**
+ * News API Providers Configuration
+ * Defines available news API providers and their configurations
+ */
+
+(function (window) {
+ "use strict";
+
+ /**
+ * Provider Configurations
+ */
+ var Providers = {
+ GNEWS: {
+ id: "gnews",
+ name: "GNews",
+ baseUrl: "https://gnews.io/api/v4",
+ website: "https://gnews.io",
+
+ // API endpoints
+ endpoints: {
+ search: "/search",
+ topHeadlines: "/top-headlines",
+ },
+
+ // Build search URL
+ buildSearchUrl: function (apiKey, query, settings) {
+ var urlParams = {
+ q: query,
+ token: apiKey,
+ lang: settings.language || "en",
+ sortby: settings.sortBy || "publishedAt",
+ country: settings.country || "us",
+ };
+
+ // Add "in" parameter (search in title, description, or content)
+ if (settings.searchIn) {
+ urlParams.in = settings.searchIn;
+ }
+
+ return this.buildUrl(this.baseUrl + this.endpoints.search, urlParams);
+ },
+
+ // Build top headlines URL
+ buildHeadlinesUrl: function (apiKey, params) {
+ var urlParams = {
+ token: apiKey,
+ lang: params.language || "en",
+ };
+ if (params.category) urlParams.category = params.category;
+ if (params.country) urlParams.country = params.country;
+ if (params.query) urlParams.q = params.query;
+
+ return this.buildUrl(this.baseUrl + this.endpoints.topHeadlines, urlParams);
+ },
+
+ // Build validation URL
+ buildValidationUrl: function (apiKey) {
+ return this.buildUrl(this.baseUrl + this.endpoints.search, {
+ q: "technology",
+ token: apiKey,
+ max: 1,
+ lang: "en",
+ });
+ },
+
+ // Parse API response
+ parseResponse: function (data) {
+ if (data.articles && Array.isArray(data.articles)) {
+ return {
+ success: true,
+ articles: data.articles.map(function (article) {
+ return {
+ title: article.title,
+ description: article.description,
+ content: article.content,
+ url: article.url,
+ publishedAt: article.publishedAt,
+ source: article.source,
+ };
+ }),
+ };
+ }
+ return {
+ success: false,
+ error: data.errors ? data.errors.join(", ") : "No articles found",
+ articles: [],
+ };
+ },
+
+ // Helper to build URL with params
+ buildUrl: function (base, params) {
+ var url = new URL(base);
+ Object.keys(params).forEach(function (key) {
+ if (params[key] !== null && params[key] !== undefined && params[key] !== "") {
+ url.searchParams.append(key, params[key]);
+ }
+ });
+ return url.toString();
+ },
+ },
+
+ THENEWSAPI: {
+ id: "thenewsapi",
+ name: "TheNewsAPI",
+ baseUrl: "https://api.thenewsapi.com/v1/news",
+ website: "https://www.thenewsapi.com",
+
+ // API endpoints
+ endpoints: {
+ search: "/all",
+ topHeadlines: "/top",
+ },
+
+ // Build search URL
+ buildSearchUrl: function (apiKey, query, settings) {
+ var urlParams = {
+ search: query,
+ api_token: apiKey,
+ language: settings.language || "en",
+ sort: settings.sortBy === "relevance" ? "relevance_score" : "published_at",
+ };
+
+ // Add domains filter if provided
+ if (settings.domains && settings.domains.trim() !== "") {
+ urlParams.domains = settings.domains.trim();
+ }
+
+ // Add search_fields if provided (for searching in specific fields)
+ if (settings.search_fields && settings.search_fields.trim() !== "") {
+ urlParams.search_fields = settings.search_fields.trim();
+ }
+
+ // Add exclude_domains if provided
+ if (settings.exclude_domains && settings.exclude_domains.trim() !== "") {
+ urlParams.exclude_domains = settings.exclude_domains.trim();
+ }
+
+ return this.buildUrl(this.baseUrl + this.endpoints.search, urlParams);
+ },
+
+ // Build top headlines URL
+ buildHeadlinesUrl: function (apiKey, params) {
+ var urlParams = {
+ api_token: apiKey,
+ language: params.language || "en",
+ };
+
+ // Add locale (country) parameter
+ if (params.country) {
+ urlParams.locale = params.country;
+ }
+
+ // Add categories filter if provided
+ if (params.category && params.category !== "") {
+ urlParams.categories = params.category;
+ }
+
+ // Add search query if provided
+ if (params.query && params.query.trim() !== "") {
+ urlParams.search = params.query.trim();
+ }
+
+ // Add domains filter if provided
+ if (params.domains && params.domains.trim() !== "") {
+ urlParams.domains = params.domains.trim();
+ }
+
+ // Add exclude_domains if provided
+ if (params.exclude_domains && params.exclude_domains.trim() !== "") {
+ urlParams.exclude_domains = params.exclude_domains.trim();
+ }
+
+ return this.buildUrl(this.baseUrl + this.endpoints.topHeadlines, urlParams);
+ },
+
+ // Build validation URL
+ buildValidationUrl: function (apiKey) {
+ return this.buildUrl(this.baseUrl + this.endpoints.search, {
+ search: "technology",
+ api_token: apiKey,
+ limit: 1,
+ language: "en",
+ });
+ },
+
+ // Parse API response
+ parseResponse: function (data) {
+ if (data.data && Array.isArray(data.data)) {
+ return {
+ success: true,
+ articles: data.data.map(function (article) {
+ return {
+ title: article.title,
+ description: article.description || article.snippet,
+ content: article.snippet || article.description,
+ url: article.url,
+ publishedAt: article.published_at,
+ source: {
+ name: article.source || "Unknown",
+ },
+ };
+ }),
+ };
+ }
+ return {
+ success: false,
+ error: data.error ? data.error.message : "No articles found",
+ articles: [],
+ };
+ },
+
+ // Helper to build URL with params
+ buildUrl: function (base, params) {
+ var url = new URL(base);
+ Object.keys(params).forEach(function (key) {
+ if (params[key] !== null && params[key] !== undefined && params[key] !== "") {
+ url.searchParams.append(key, params[key]);
+ }
+ });
+ return url.toString();
+ },
+ },
+
+ WORLDNEWSAPI: {
+ id: "worldnewsapi",
+ name: "WorldNewsAPI",
+ baseUrl: "https://api.worldnewsapi.com",
+ website: "https://worldnewsapi.com",
+
+ // API endpoints
+ endpoints: {
+ search: "/search-news",
+ topHeadlines: "/top-news",
+ },
+
+ // Build search URL
+ buildSearchUrl: function (apiKey, query, settings) {
+ var urlParams = {
+ text: query,
+ language: settings.language || "en",
+ number: 100,
+ };
+
+ // WorldNewsAPI only supports 'publish-time' or empty for sort
+ // 'publish-time' = sort by date, empty/omitted = sort by relevance (default)
+ if (settings.sortBy && settings.sortBy === "publish-time") {
+ urlParams.sort = "publish-time";
+ }
+ // If sortBy is 'relevance' or anything else, don't add sort parameter (defaults to relevance)
+
+ // Add text-match-indexes (search in title, content, or both)
+ if (settings.searchIn) {
+ var searchInMap = {
+ 'title': 'title',
+ 'description': 'content',
+ 'content': 'content',
+ 'title,description': 'title,content',
+ 'title,content': 'title,content'
+ };
+ urlParams["text-match-indexes"] = searchInMap[settings.searchIn] || 'title,content';
+ }
+
+ // Add source-country if provided
+ if (settings.country) {
+ urlParams["source-country"] = settings.country;
+ }
+
+ // Add news-sources filter if provided
+ if (settings.domains && settings.domains.trim() !== "") {
+ urlParams["news-sources"] = settings.domains.trim();
+ }
+
+ // Add categories filter if provided
+ if (settings.categories && settings.categories.trim() !== "") {
+ urlParams.categories = settings.categories.trim();
+ }
+
+ // Add authors filter if provided
+ if (settings.authors && settings.authors.trim() !== "") {
+ urlParams.authors = settings.authors.trim();
+ }
+
+ return this.buildUrl(this.baseUrl + this.endpoints.search, urlParams);
+ },
+
+ // Build top headlines URL
+ buildHeadlinesUrl: function (apiKey, params) {
+ var urlParams = {
+ language: params.language || "en",
+ };
+
+ // source-country is required for top-news endpoint
+ urlParams["source-country"] = params.country || "us";
+
+ // Add date if needed (defaults to today)
+ if (params.date) {
+ urlParams.date = params.date;
+ }
+
+ // Add headlines-only parameter if needed
+ if (params.headlinesOnly) {
+ urlParams["headlines-only"] = true;
+ }
+
+ return this.buildUrl(this.baseUrl + this.endpoints.topHeadlines, urlParams);
+ },
+
+ // Build validation URL
+ buildValidationUrl: function (apiKey) {
+ return this.buildUrl(this.baseUrl + this.endpoints.search, {
+ text: "technology",
+ language: "en",
+ number: 1,
+ });
+ },
+
+ // Parse API response
+ parseResponse: function (data) {
+ // Handle search-news response
+ if (data.news && Array.isArray(data.news)) {
+ return {
+ success: true,
+ articles: data.news.map(function (article) {
+ return {
+ title: article.title,
+ description: article.summary || article.text,
+ content: article.text,
+ url: article.url,
+ publishedAt: article.publish_date,
+ source: {
+ name: article.source_country || "Unknown",
+ },
+ };
+ }),
+ };
+ }
+
+ // Handle top-news response (clustered news)
+ if (data.top_news && Array.isArray(data.top_news)) {
+ var allArticles = [];
+ data.top_news.forEach(function (cluster) {
+ if (cluster.news && Array.isArray(cluster.news)) {
+ cluster.news.forEach(function (article) {
+ allArticles.push({
+ title: article.title,
+ description: article.summary || article.text,
+ content: article.text,
+ url: article.url,
+ publishedAt: article.publish_date,
+ source: {
+ name: article.source_country || data.country || "Unknown",
+ },
+ });
+ });
+ }
+ });
+ return {
+ success: true,
+ articles: allArticles,
+ };
+ }
+
+ return {
+ success: false,
+ error: data.message || "No articles found",
+ articles: [],
+ };
+ },
+
+ // Helper to build URL with params
+ buildUrl: function (base, params) {
+ var url = new URL(base);
+ Object.keys(params).forEach(function (key) {
+ if (params[key] !== null && params[key] !== undefined && params[key] !== "") {
+ url.searchParams.append(key, params[key]);
+ }
+ });
+ return url.toString();
+ },
+ },
+ };
+
+ /**
+ * Provider Manager
+ */
+ var ProviderManager = {
+ currentProvider: null,
+
+ /**
+ * Get all available providers
+ */
+ getProviders: function () {
+ return [Providers.GNEWS, Providers.THENEWSAPI, Providers.WORLDNEWSAPI];
+ },
+
+ /**
+ * Get provider by ID
+ */
+ getProvider: function (providerId) {
+ return Providers[providerId.toUpperCase()] || null;
+ },
+
+ /**
+ * Set current provider
+ */
+ setProvider: function (providerId) {
+ this.currentProvider = this.getProvider(providerId);
+ return this.currentProvider;
+ },
+
+ /**
+ * Get current provider
+ */
+ getCurrentProvider: function () {
+ return this.currentProvider || Providers.GNEWS;
+ },
+ };
+
+ // Export to global scope
+ window.NewsProviders = ProviderManager;
+})(window);
diff --git a/sdkjs-plugins/content/news/scripts/storage.js b/sdkjs-plugins/content/news/scripts/storage.js
new file mode 100644
index 00000000..1b5e71e1
--- /dev/null
+++ b/sdkjs-plugins/content/news/scripts/storage.js
@@ -0,0 +1,141 @@
+/**
+ * Storage Manager for News Plugin
+ * Handles all localStorage operations for API key persistence
+ */
+
+(function (window) {
+ "use strict";
+
+ const STORAGE_KEY = "news-api-key";
+ const PROVIDER_KEY = "news-provider";
+
+ /**
+ * Storage Manager
+ */
+ var StorageManager = {
+ /**
+ * Check if localStorage is available
+ */
+ isAvailable: function () {
+ try {
+ return "localStorage" in window && window.localStorage !== null;
+ } catch (e) {
+ console.error("localStorage not available:", e);
+ return false;
+ }
+ },
+
+ /**
+ * Save API key to localStorage
+ * @param {string} apiKey - The API key to save
+ * @returns {boolean} - Success status
+ */
+ saveApiKey: function (apiKey) {
+ if (!this.isAvailable()) {
+ console.error("localStorage is not available");
+ return false;
+ }
+
+ try {
+ localStorage.setItem(STORAGE_KEY, apiKey);
+ return true;
+ } catch (error) {
+ console.error("Failed to save API key to localStorage:", error);
+ return false;
+ }
+ },
+
+ /**
+ * Load API key from localStorage
+ * @returns {string} - The stored API key or empty string
+ */
+ loadApiKey: function () {
+ if (!this.isAvailable()) {
+ return "";
+ }
+
+ try {
+ return localStorage.getItem(STORAGE_KEY) || "";
+ } catch (error) {
+ console.error("Failed to load API key from localStorage:", error);
+ return "";
+ }
+ },
+
+ /**
+ * Remove API key from localStorage
+ * @returns {boolean} - Success status
+ */
+ removeApiKey: function () {
+ if (!this.isAvailable()) {
+ return false;
+ }
+
+ try {
+ localStorage.removeItem(STORAGE_KEY);
+ return true;
+ } catch (error) {
+ console.error("Failed to remove API key from localStorage:", error);
+ return false;
+ }
+ },
+
+ /**
+ * Save provider selection to localStorage
+ * @param {string} providerId - The provider ID to save
+ * @returns {boolean} - Success status
+ */
+ saveProvider: function (providerId) {
+ if (!this.isAvailable()) {
+ console.error("localStorage is not available");
+ return false;
+ }
+
+ try {
+ localStorage.setItem(PROVIDER_KEY, providerId);
+ return true;
+ } catch (error) {
+ console.error("Failed to save provider to localStorage:", error);
+ return false;
+ }
+ },
+
+ /**
+ * Load provider selection from localStorage
+ * @returns {string} - The stored provider ID or "gnews" as default
+ */
+ loadProvider: function () {
+ if (!this.isAvailable()) {
+ return "gnews";
+ }
+
+ try {
+ return localStorage.getItem(PROVIDER_KEY) || "gnews";
+ } catch (error) {
+ console.error("Failed to load provider from localStorage:", error);
+ return "gnews";
+ }
+ },
+
+ /**
+ * Remove provider selection from localStorage
+ * @returns {boolean} - Success status
+ */
+ removeProvider: function () {
+ if (!this.isAvailable()) {
+ return false;
+ }
+
+ try {
+ localStorage.removeItem(PROVIDER_KEY);
+ return true;
+ } catch (error) {
+ console.error("Failed to remove provider from localStorage:", error);
+ return false;
+ }
+ },
+ };
+
+ // Export to global scope
+ window.GNewsStorage = StorageManager;
+})(window);
diff --git a/sdkjs-plugins/content/news/scripts/translations.js b/sdkjs-plugins/content/news/scripts/translations.js
new file mode 100644
index 00000000..adc790ed
--- /dev/null
+++ b/sdkjs-plugins/content/news/scripts/translations.js
@@ -0,0 +1,215 @@
+/**
+ * Translation Manager for News Plugin
+ * Handles all UI translations
+ */
+
+(function (window) {
+ "use strict";
+
+ var TranslationManager = {
+ /**
+ * Helper function to get element by ID
+ */
+ $: function (id) {
+ return document.getElementById(id);
+ },
+
+ /**
+ * Safely translate element text content
+ */
+ translateElement: function (elementId, key) {
+ const element = this.$(elementId);
+ if (element && window.Asc && window.Asc.plugin && window.Asc.plugin.tr) {
+ element.textContent = window.Asc.plugin.tr(key);
+ }
+ },
+
+ /**
+ * Safely translate input placeholder
+ */
+ translatePlaceholder: function (elementId, key) {
+ const element = this.$(elementId);
+ if (element && window.Asc && window.Asc.plugin && window.Asc.plugin.tr) {
+ element.placeholder = window.Asc.plugin.tr(key);
+ }
+ },
+
+ /**
+ * Apply all translations to the UI
+ */
+ applyTranslations: function () {
+ // Main interface elements
+ this.translateElement("description-text", "Search through millions of articles");
+ this.translateElement("description-text-2", "Search through millions of articles");
+ this.translateElement("api-key-label", "API key");
+ this.translateElement("provider-label", "News Provider");
+ this.translatePlaceholder("api-key-setup", "Enter your API key");
+ this.translateElement("get-api-key-text", "Get your free API key");
+
+ // Check button text before translating
+ const saveApiBtn = this.$("save-api-btn");
+ if (saveApiBtn && saveApiBtn.textContent.trim() === "Login") {
+ this.translateElement("save-api-btn", "Login");
+ }
+
+ // Tab labels
+ this.translateElement("search-tab-text", "Search");
+ this.translateElement("headlines-tab-text", "Top Headlines");
+
+ // Form labels
+ this.translateElement("search-prompt-label", "Prompt");
+ this.translateElement("headlines-prompt-label", "Prompt");
+ this.translatePlaceholder("search-query", "Search with keywords");
+ this.translatePlaceholder("headlines-query", "Search with keywords");
+ this.translateElement("search-in-label", "Search in");
+ this.translateElement("title-label", "Title");
+ this.translateElement("description-label", "Description");
+ this.translateElement("content-label", "Content");
+ this.translateElement("advanced-settings-label", "Advanced Settings");
+ this.translateElement("headlines-advanced-settings-label", "Advanced Settings");
+
+ // Buttons
+ const searchBtn = this.$("search-btn");
+ if (searchBtn && searchBtn.textContent.trim() === "Find") {
+ this.translateElement("search-btn", "Find");
+ }
+
+ const headlinesBtn = this.$("headlines-btn");
+ if (headlinesBtn && headlinesBtn.textContent.trim() === "Find") {
+ this.translateElement("headlines-btn", "Find");
+ }
+
+ this.translateElement("advanced-settings-btn", "Show advanced settings");
+ this.translateElement("reconfigure-btn", "Reconfigure");
+ this.translateElement("back-to-search-btn", "Back to search");
+
+ // Category and country options
+ this.translateElement("category-label", "Category");
+ this.translateElement("country-label", "Country");
+
+ // Select options
+ this.translateSelectOptions();
+ this.translateAdvancedSettings();
+ },
+
+ /**
+ * Translate select dropdown options
+ */
+ translateSelectOptions: function () {
+ const options = [
+ { id: "all-categories-option", key: "All Categories" },
+ { id: "general-option", key: "General" },
+ { id: "business-option", key: "Business" },
+ { id: "entertainment-option", key: "Entertainment" },
+ { id: "health-option", key: "Health" },
+ { id: "science-option", key: "Science" },
+ { id: "sports-option", key: "Sports" },
+ { id: "technology-option", key: "Technology" },
+ { id: "us-option", key: "United States" },
+ { id: "gb-option", key: "United Kingdom" },
+ { id: "ca-option", key: "Canada" },
+ { id: "au-option", key: "Australia" },
+ { id: "de-option", key: "Germany" },
+ { id: "fr-option", key: "France" },
+ { id: "jp-option", key: "Japan" },
+ { id: "in-option", key: "India" },
+ ];
+
+ var self = this;
+ options.forEach(function (option) {
+ const element = self.$(option.id);
+ if (element && window.Asc && window.Asc.plugin && window.Asc.plugin.tr) {
+ element.textContent = window.Asc.plugin.tr(option.key);
+ }
+ });
+ },
+
+ /**
+ * Translate advanced settings options
+ */
+ translateAdvancedSettings: function () {
+ var self = this;
+ ["search", "headlines"].forEach(function (prefix) {
+ const sortBySelect = self.$(prefix + "-sortby");
+ if (sortBySelect && window.Asc && window.Asc.plugin && window.Asc.plugin.tr) {
+ const options = sortBySelect.querySelectorAll("option");
+ if (options[0]) {
+ options[0].textContent = window.Asc.plugin.tr("Publication Date");
+ }
+ if (options[1]) {
+ options[1].textContent = window.Asc.plugin.tr("Relevance");
+ }
+ }
+
+ const langSelect = self.$(prefix + "-lang");
+ if (langSelect && window.Asc && window.Asc.plugin && window.Asc.plugin.tr) {
+ const langOptions = langSelect.querySelectorAll("option");
+ const langKeys = [
+ "English",
+ "Spanish",
+ "French",
+ "German",
+ "Italian",
+ "Portuguese",
+ "Japanese",
+ "Chinese",
+ "Arabic",
+ "Russian",
+ "Hindi",
+ "Korean",
+ ];
+ langOptions.forEach(function (option, index) {
+ if (langKeys[index]) {
+ option.textContent = window.Asc.plugin.tr(langKeys[index]);
+ }
+ });
+ }
+
+ // Translate domains label if it exists (TheNewsAPI only)
+ const domainsLabel = document.querySelector('label[for="' + prefix + '-domains"]');
+ if (domainsLabel && window.Asc && window.Asc.plugin && window.Asc.plugin.tr) {
+ domainsLabel.textContent = window.Asc.plugin.tr("Domains") + ':';
+ }
+
+ // Translate domains placeholder if it exists
+ const domainsInput = self.$(prefix + "-domains");
+ if (domainsInput && window.Asc && window.Asc.plugin && window.Asc.plugin.tr) {
+ domainsInput.placeholder = window.Asc.plugin.tr("e.g., bbc.co.uk, cnn.com");
+ }
+
+ // Translate authors label if it exists (WorldNewsAPI only)
+ const authorsLabel = document.querySelector('label[for="' + prefix + '-authors"]');
+ if (authorsLabel && window.Asc && window.Asc.plugin && window.Asc.plugin.tr) {
+ authorsLabel.textContent = window.Asc.plugin.tr("Authors") + ':';
+ }
+
+ // Translate authors placeholder if it exists
+ const authorsInput = self.$(prefix + "-authors");
+ if (authorsInput && window.Asc && window.Asc.plugin && window.Asc.plugin.tr) {
+ authorsInput.placeholder = window.Asc.plugin.tr("e.g., John Doe, Jane Smith");
+ }
+
+ // Translate categories label if it exists (WorldNewsAPI and TheNewsAPI)
+ const categoriesLabel = document.querySelector('label[for="' + prefix + '-categories"]');
+ if (categoriesLabel && window.Asc && window.Asc.plugin && window.Asc.plugin.tr) {
+ categoriesLabel.textContent = window.Asc.plugin.tr("Categories Filter") + ':';
+ }
+
+ // Translate categories placeholder if it exists
+ const categoriesInput = self.$(prefix + "-categories");
+ if (categoriesInput && window.Asc && window.Asc.plugin && window.Asc.plugin.tr) {
+ categoriesInput.placeholder = window.Asc.plugin.tr("e.g., politics, sports");
+ }
+
+ // Translate locale label if it exists (TheNewsAPI only)
+ const localeLabel = document.querySelector('label[for="' + prefix + '-locale"]');
+ if (localeLabel && window.Asc && window.Asc.plugin && window.Asc.plugin.tr) {
+ localeLabel.textContent = window.Asc.plugin.tr("Locale") + ':';
+ }
+ });
+ },
+ };
+
+ // Export to global scope
+ window.GNewsTranslations = TranslationManager;
+})(window);
diff --git a/sdkjs-plugins/content/news/scripts/ui.js b/sdkjs-plugins/content/news/scripts/ui.js
new file mode 100644
index 00000000..4d71bb5f
--- /dev/null
+++ b/sdkjs-plugins/content/news/scripts/ui.js
@@ -0,0 +1,691 @@
+/**
+ * UI Manager for News Plugin
+ * Handles all UI state management and DOM manipulation
+ */
+
+(function (window) {
+ "use strict";
+
+ var UIManager = {
+ currentArticles: [],
+ currentTab: "search",
+ currentProvider: "gnews",
+
+ /**
+ * Helper function to get element by ID
+ */
+ $: function (id) {
+ return document.getElementById(id);
+ },
+
+ /**
+ * Set element state (disabled/enabled and text)
+ */
+ setElementState: function (id, disabled, text) {
+ const el = this.$(id);
+ if (el) {
+ if (disabled !== undefined) el.disabled = disabled;
+ if (text !== undefined) el.textContent = text;
+ }
+ },
+
+ /**
+ * Show status message
+ */
+ showStatus: function (message, isError) {
+ const status = this.$("status");
+ if (!status) return;
+
+ status.textContent = message;
+ status.className = isError ? "error" : "success";
+
+ setTimeout(function () {
+ status.textContent = "";
+ status.className = "";
+ }, 4000);
+ },
+
+ /**
+ * Show API setup screen
+ */
+ showApiSetup: function () {
+ this.$("api-setup").style.display = "block";
+ this.$("search-interface").style.display = "none";
+ },
+
+ /**
+ * Show search interface
+ */
+ showSearchInterface: function () {
+ this.$("api-setup").style.display = "none";
+ this.$("search-interface").style.display = "block";
+ },
+
+ /**
+ * Update provider information in UI
+ */
+ updateProviderInfo: function (providerId) {
+ if (providerId) {
+ this.currentProvider = providerId;
+ }
+
+ var provider = window.NewsProviders.getCurrentProvider();
+
+ // Update API key link
+ var apiKeyLink = this.$("provider-website-link");
+ if (apiKeyLink && provider) {
+ apiKeyLink.href = provider.website;
+ apiKeyLink.textContent = provider.website.replace("https://", "").replace("www.", "");
+ }
+
+ // Update placeholder
+ var apiKeyInput = this.$("api-key-setup");
+ if (apiKeyInput && provider) {
+ apiKeyInput.placeholder = "Enter your " + provider.name + " API key";
+ }
+
+ // Recreate advanced settings with provider-specific options
+ this.createAdvancedSettings();
+ },
+
+ /**
+ * Create provider selector HTML
+ */
+ createProviderSelector: function () {
+ var providers = window.NewsProviders.getProviders();
+ var html = 'News Provider: ';
+
+ providers.forEach(function (provider) {
+ html += '' + provider.name + ' ';
+ });
+
+ html += '
';
+ return html;
+ },
+
+ /**
+ * Initialize provider selector
+ */
+ initializeProviderSelector: function (selectedProvider) {
+ var providerSelect = this.$("provider-select");
+ if (providerSelect && selectedProvider) {
+ providerSelect.value = selectedProvider;
+ this.currentProvider = selectedProvider;
+
+ // Update UI for the selected provider
+ window.NewsProviders.setProvider(selectedProvider);
+ this.updateProviderInfo(selectedProvider);
+ }
+ },
+
+ /**
+ * Show search form
+ */
+ showSearchForm: function () {
+ const searchForm = document.querySelector(".search-form-container");
+ const resultsSection = this.$("results-section");
+
+ if (searchForm) searchForm.style.display = "block";
+ if (resultsSection) resultsSection.style.display = "none";
+ },
+
+ /**
+ * Show search results
+ */
+ showSearchResults: function () {
+ const searchForm = document.querySelector(".search-form-container");
+ const resultsSection = this.$("results-section");
+
+ if (searchForm) searchForm.style.display = "none";
+ if (resultsSection) resultsSection.style.display = "block";
+ },
+
+ /**
+ * Switch between tabs
+ */
+ switchTab: function (tabName) {
+ this.currentTab = tabName;
+
+ const searchTab = this.$("search-tab");
+ const headlinesTab = this.$("headlines-tab");
+ const searchContent = this.$("search-content");
+ const headlinesContent = this.$("headlines-content");
+
+ if (tabName === "search") {
+ searchTab.classList.add("active");
+ headlinesTab.classList.remove("active");
+ searchContent.style.display = "block";
+ headlinesContent.style.display = "none";
+ } else {
+ searchTab.classList.remove("active");
+ headlinesTab.classList.add("active");
+ searchContent.style.display = "none";
+ headlinesContent.style.display = "block";
+ }
+
+ const advancedBtn = this.$("advanced-settings-btn");
+ if (advancedBtn && window.Asc && window.Asc.plugin && window.Asc.plugin.tr) {
+ advancedBtn.textContent = window.Asc.plugin.tr("Show advanced settings");
+ }
+
+ const searchAdvanced = this.$("search-advanced-settings");
+ const headlinesAdvanced = this.$("headlines-advanced-settings");
+ if (searchAdvanced) searchAdvanced.style.display = "none";
+ if (headlinesAdvanced) headlinesAdvanced.style.display = "none";
+
+ this.showSearchForm();
+ this.currentArticles = [];
+ },
+
+ /**
+ * Toggle advanced settings visibility
+ */
+ toggleAdvancedSettings: function () {
+ const advancedSection = this.$(this.currentTab + "-advanced-settings");
+ const advancedBtn = this.$("advanced-settings-btn");
+
+ if (advancedSection && advancedBtn && window.Asc && window.Asc.plugin && window.Asc.plugin.tr) {
+ if (advancedSection.style.display === "none") {
+ advancedSection.style.display = "block";
+ advancedBtn.textContent = window.Asc.plugin.tr("Hide advanced settings");
+ } else {
+ advancedSection.style.display = "none";
+ advancedBtn.textContent = window.Asc.plugin.tr("Show advanced settings");
+ }
+ }
+ },
+
+ /**
+ * Get display options from checkboxes
+ */
+ getDisplayOptions: function () {
+ if (this.currentTab === "headlines") {
+ return { title: true, description: true, content: false };
+ }
+
+ const showTitle = this.$("show-title").checked;
+ const showDescription = this.$("show-description").checked;
+ const showContent = this.$("show-content").checked;
+
+ if (!showTitle && !showDescription && !showContent) {
+ return { title: true, description: true, content: false };
+ }
+
+ return {
+ title: showTitle,
+ description: showDescription,
+ content: showContent,
+ };
+ },
+
+ /**
+ * Get advanced settings for a tab
+ */
+ getAdvancedSettings: function (tabPrefix) {
+ const sortElement = this.$(tabPrefix + "-sortby");
+ const langElement = this.$(tabPrefix + "-lang");
+ const domainsElement = this.$(tabPrefix + "-domains");
+ const excludeDomainsElement = this.$(tabPrefix + "-exclude-domains");
+ const searchFieldsElement = this.$(tabPrefix + "-search-fields");
+ const authorsElement = this.$(tabPrefix + "-authors");
+ const categoriesElement = this.$(tabPrefix + "-categories");
+
+ var settings = {
+ sortBy: sortElement ? sortElement.value || "publishedAt" : "publishedAt",
+ language: langElement ? langElement.value || "en" : "en",
+ };
+
+ // Add "Search In" settings (for GNews and WorldNewsAPI)
+ if (tabPrefix === "search") {
+ const showTitle = this.$("show-title");
+ const showDescription = this.$("show-description");
+ const showContent = this.$("show-content");
+
+ if (showTitle && showDescription && showContent) {
+ var searchInParts = [];
+ if (showTitle.checked) searchInParts.push("title");
+ if (showDescription.checked || showContent.checked) searchInParts.push("description");
+
+ if (searchInParts.length > 0) {
+ settings.searchIn = searchInParts.join(",");
+ }
+ }
+ }
+
+ // Add provider-specific settings
+ if (domainsElement && domainsElement.value) {
+ settings.domains = domainsElement.value;
+ }
+
+ if (excludeDomainsElement && excludeDomainsElement.value) {
+ settings.exclude_domains = excludeDomainsElement.value;
+ }
+
+ if (searchFieldsElement && searchFieldsElement.value) {
+ settings.search_fields = searchFieldsElement.value;
+ }
+
+ if (authorsElement && authorsElement.value) {
+ settings.authors = authorsElement.value;
+ }
+
+ if (categoriesElement && categoriesElement.value) {
+ settings.categories = categoriesElement.value;
+ }
+
+ return settings;
+ },
+
+ /**
+ * Get provider-specific advanced settings configuration
+ */
+ getProviderAdvancedConfig: function (providerId) {
+ if (providerId === "gnews") {
+ return {
+ supportsDomains: false,
+ supportsSearchIn: true,
+ supportsLocale: false,
+ supportsSearchFields: false,
+ supportsExcludeDomains: false,
+ supportsCategories: false,
+ supportsExcludeCategories: false,
+ supportsAuthors: false,
+ supportsHeadlinesQuery: true,
+ supportsHeadlinesCategory: true,
+ sortOptions: [
+ { value: "publishedAt", label: "Publication Date" },
+ { value: "relevance", label: "Relevance" }
+ ]
+ };
+ } else if (providerId === "thenewsapi") {
+ return {
+ supportsDomains: true,
+ supportsSearchIn: false,
+ supportsLocale: false,
+ supportsSearchFields: true,
+ supportsExcludeDomains: true,
+ supportsCategories: true,
+ supportsExcludeCategories: true,
+ supportsAuthors: false,
+ supportsHeadlinesQuery: true,
+ supportsHeadlinesCategory: true,
+ sortOptions: [
+ { value: "published_at", label: "Publication Date" },
+ { value: "relevance_score", label: "Relevance" }
+ ]
+ };
+ } else if (providerId === "worldnewsapi") {
+ return {
+ supportsDomains: true,
+ supportsSearchIn: true,
+ supportsLocale: false,
+ supportsSearchFields: false,
+ supportsExcludeDomains: false,
+ supportsCategories: true,
+ supportsExcludeCategories: false,
+ supportsAuthors: true,
+ supportsHeadlinesQuery: false,
+ supportsHeadlinesCategory: false,
+ sortOptions: [
+ { value: "publish-time", label: "Publication Date" },
+ { value: "relevance", label: "Relevance" }
+ ]
+ };
+ }
+ // Default configuration
+ return {
+ supportsDomains: false,
+ supportsSearchIn: true,
+ supportsLocale: false,
+ supportsSearchFields: false,
+ supportsExcludeDomains: false,
+ supportsCategories: false,
+ supportsExcludeCategories: false,
+ supportsAuthors: false,
+ supportsHeadlinesQuery: true,
+ supportsHeadlinesCategory: true,
+ sortOptions: [
+ { value: "publishedAt", label: "Publication Date" },
+ { value: "relevance", label: "Relevance" }
+ ]
+ };
+ },
+
+ /**
+ * Create advanced settings HTML based on provider
+ */
+ createAdvancedSettings: function () {
+ var config = this.getProviderAdvancedConfig(this.currentProvider);
+ var tr = window.Asc && window.Asc.plugin && window.Asc.plugin.tr ? window.Asc.plugin.tr : function(s) { return s; };
+
+ // Helper function to build base HTML (sort + language)
+ var buildBaseHTML = function() {
+ var html = "";
+
+ // Sort by dropdown (common to all providers)
+ html += '';
+ html += '' + tr("Sort by") + ': ';
+ html += '';
+ config.sortOptions.forEach(function(opt) {
+ html += '' + tr(opt.label) + ' ';
+ });
+ html += ' ';
+ html += '
';
+
+ // Language dropdown (common to all providers)
+ html += '';
+ html += '' + tr("Language") + ': ';
+ html += '';
+ html += '' + tr("English") + ' ';
+ html += '' + tr("Spanish") + ' ';
+ html += '' + tr("French") + ' ';
+ html += '' + tr("German") + ' ';
+ html += '' + tr("Italian") + ' ';
+ html += '' + tr("Portuguese") + ' ';
+ html += '' + tr("Japanese") + ' ';
+ html += '' + tr("Chinese") + ' ';
+ html += '' + tr("Arabic") + ' ';
+ html += '' + tr("Russian") + ' ';
+ html += '' + tr("Hindi") + ' ';
+ html += '' + tr("Korean") + ' ';
+ html += ' ';
+ html += '
';
+
+ return html;
+ };
+
+ // Helper function to build provider-specific fields
+ var buildProviderFields = function(isHeadlines) {
+ var html = "";
+
+ // For WorldNewsAPI Top Headlines, skip domains, authors, and categories
+ if (isHeadlines && this.currentProvider === "worldnewsapi") {
+ // Don't add domains, authors, or categories for WorldNewsAPI headlines
+ return html;
+ }
+
+ // Provider-specific: Domains (TheNewsAPI and WorldNewsAPI search)
+ if (config.supportsDomains) {
+ html += '';
+ }
+
+ // Provider-specific: Exclude Domains (TheNewsAPI)
+ if (config.supportsExcludeDomains) {
+ html += '';
+ }
+
+ // Provider-specific: Authors (WorldNewsAPI search only)
+ if (config.supportsAuthors) {
+ html += '';
+ }
+
+ // Provider-specific: Categories (WorldNewsAPI search and TheNewsAPI)
+ if (config.supportsCategories) {
+ html += '';
+ }
+
+ return html;
+ }.bind(this);
+
+ // Build HTML for search tab
+ var searchHTML = buildBaseHTML() + buildProviderFields(false);
+
+ // Build HTML for headlines tab
+ var headlinesHTML = buildBaseHTML() + buildProviderFields(true);
+
+ // Provider-specific: Search Fields (TheNewsAPI, search only)
+ if (config.supportsSearchFields) {
+ var searchFieldsHTML = '';
+
+ searchHTML += searchFieldsHTML;
+ }
+
+ // Apply HTML to search tab
+ var searchOptionsEl = this.$('search-advanced-options');
+ if (searchOptionsEl) {
+ searchOptionsEl.innerHTML = searchHTML.replace(/PREFIX/g, 'search');
+ }
+
+ // Apply HTML to headlines tab
+ var headlinesOptionsEl = this.$('headlines-advanced-options');
+ if (headlinesOptionsEl) {
+ headlinesOptionsEl.innerHTML = headlinesHTML.replace(/PREFIX/g, 'headlines');
+ }
+
+ // Update "Search In" visibility for search tab
+ this.updateSearchInVisibility(config.supportsSearchIn);
+
+ // Update headlines query visibility based on provider
+ this.updateHeadlinesQueryVisibility(config.supportsHeadlinesQuery);
+
+ // Update headlines category visibility based on provider
+ this.updateHeadlinesCategoryVisibility(config.supportsHeadlinesCategory);
+ },
+
+ /**
+ * Update "Search In" section visibility
+ */
+ updateSearchInVisibility: function (visible) {
+ var searchInSection = this.$('search-in-section');
+ if (searchInSection) {
+ searchInSection.style.display = visible ? 'block' : 'none';
+ }
+ },
+
+ /**
+ * Update headlines query field visibility based on provider support
+ */
+ updateHeadlinesQueryVisibility: function (visible) {
+ var headlinesQueryGroup = this.$('headlines-query');
+ if (headlinesQueryGroup && headlinesQueryGroup.parentElement) {
+ // Hide/show the entire form-group containing the query field
+ headlinesQueryGroup.parentElement.style.display = visible ? 'block' : 'none';
+ }
+ },
+
+ /**
+ * Update headlines category field visibility based on provider support
+ */
+ updateHeadlinesCategoryVisibility: function (visible) {
+ var headlinesCategoryGroup = this.$('headlines-category');
+ if (headlinesCategoryGroup && headlinesCategoryGroup.parentElement) {
+ // Hide/show the entire form-group containing the category field
+ headlinesCategoryGroup.parentElement.style.display = visible ? 'block' : 'none';
+ }
+ },
+
+ /**
+ * Initialize display options checkboxes
+ */
+ initializeDisplayOptions: function () {
+ const checkboxes = ["show-title", "show-description", "show-content"];
+ var self = this;
+ checkboxes.forEach(function (id, index) {
+ const el = self.$(id);
+ if (el) el.checked = index < 2;
+ });
+ },
+
+ /**
+ * Display search results
+ */
+ displaySearchResults: function (articles, showStatusMessage) {
+ this.currentArticles = articles;
+ const resultsList = this.$("articles-list");
+
+ if (!resultsList) return;
+
+ if (articles.length === 0) {
+ // Clear the results header when no articles found
+ const resultsHeader = document.querySelector(".results-header");
+ if (resultsHeader) {
+ resultsHeader.textContent = "";
+ }
+
+ resultsList.innerHTML =
+ '' +
+ (window.Asc.plugin.tr
+ ? window.Asc.plugin.tr("No articles found")
+ : "No articles found. Try a different search query.") +
+ "
";
+ this.showSearchResults();
+ if (showStatusMessage) {
+ this.showStatus("No articles found", true);
+ }
+ return;
+ }
+
+ const displayOptions = this.getDisplayOptions();
+ this.updateResultsHeader(articles.length, displayOptions);
+
+ let html = "";
+ articles.forEach(function (article, index) {
+ html += '';
+ html += '
';
+ html += '
' + UIManager.escapeHtml(article.title) + " ";
+ html +=
+ '
' +
+ UIManager.escapeHtml(article.source.name) +
+ " • " +
+ new Date(article.publishedAt).toLocaleDateString() +
+ "
";
+ if (article.description) {
+ html +=
+ '
' +
+ UIManager.escapeHtml(article.description) +
+ "
";
+ }
+ html += "
";
+ html +=
+ '
' +
+ (window.Asc.plugin.tr ? window.Asc.plugin.tr("Open") : "Open") +
+ " ";
+ html += "
";
+ });
+
+ resultsList.innerHTML = html;
+ this.showSearchResults();
+
+ if (showStatusMessage) {
+ this.showStatus("Found " + articles.length + " articles", false);
+ }
+ },
+
+ /**
+ * Update results header
+ */
+ updateResultsHeader: function (count, displayOptions) {
+ const resultsHeader = document.querySelector(".results-header");
+ if (!resultsHeader) return;
+
+ if (count === 0) {
+ resultsHeader.textContent = "";
+ return;
+ }
+
+ const searchFields = [];
+ if (displayOptions.title) {
+ searchFields.push(
+ window.Asc.plugin.tr ? window.Asc.plugin.tr("title") : "title"
+ );
+ }
+ if (displayOptions.description) {
+ searchFields.push(
+ window.Asc.plugin.tr
+ ? window.Asc.plugin.tr("description")
+ : "description"
+ );
+ }
+ if (displayOptions.content) {
+ searchFields.push(
+ window.Asc.plugin.tr ? window.Asc.plugin.tr("content") : "content"
+ );
+ }
+
+ const allFieldsText = window.Asc.plugin.tr
+ ? window.Asc.plugin.tr("all fields")
+ : "all fields";
+ const searchFieldsText =
+ searchFields.length === 0 ? allFieldsText : searchFields.join(", ");
+ const successText = window.Asc.plugin.tr
+ ? window.Asc.plugin.tr("Success! {0} results were found by {1}")
+ : "Success! {0} results were found by {1}";
+ const headerText = successText
+ .replace("{0}", count)
+ .replace("{1}", searchFieldsText);
+
+ resultsHeader.textContent = headerText;
+ },
+
+ /**
+ * Escape HTML to prevent XSS
+ */
+ escapeHtml: function (text) {
+ const div = document.createElement("div");
+ div.textContent = text;
+ return div.innerHTML;
+ },
+
+ /**
+ * Open article link in browser
+ */
+ openArticleLink: function (article) {
+ if (!article.url) {
+ this.showStatus("Article URL not available", true);
+ return;
+ }
+
+ try {
+ window.open(article.url, "_blank");
+ } catch (error) {
+ console.error("Failed to open article:", error);
+ this.showStatus("Failed to open article", true);
+ }
+ },
+
+ /**
+ * Clear results and go back to search
+ */
+ goBackToSearch: function () {
+ this.showSearchForm();
+ this.currentArticles = [];
+ const status = this.$("status");
+ if (status) {
+ status.textContent = "";
+ status.className = "";
+ }
+ },
+ };
+
+ // Export to global scope
+ window.GNewsUI = UIManager;
+})(window);
diff --git a/sdkjs-plugins/content/news/translations/cs-CZ.json b/sdkjs-plugins/content/news/translations/cs-CZ.json
new file mode 100644
index 00000000..366c06ec
--- /dev/null
+++ b/sdkjs-plugins/content/news/translations/cs-CZ.json
@@ -0,0 +1,59 @@
+{
+ "Search through millions of articles": "Prohledejte miliony článků z více než 80 000 velkých a malých zpravodajských zdrojů a blogů.",
+ "API key": "API klíč:",
+ "Enter your API key": "Zadejte svůj API klíč",
+ "Get your free API key": "Získejte svůj bezplatný API klíč z",
+ "Login": "Přihlášení",
+ "Search": "Hledat",
+ "Top Headlines": "Hlavní zprávy",
+ "Prompt": "Dotaz",
+ "Search with keywords": "Hledejte pomocí jednotlivých klíčových slov nebo obklopte úplné fráze uvozovkami",
+ "Search in": "Hledat v",
+ "Title": "Název",
+ "Description": "Popis",
+ "Content": "Obsah",
+ "Advanced Settings": "Pokročilá nastavení",
+ "Find": "Najít",
+ "Category": "Kategorie:",
+ "All Categories": "Všechny kategorie",
+ "General": "Obecné",
+ "Business": "Podnikání",
+ "Entertainment": "Zábava",
+ "Health": "Zdraví",
+ "Science": "Věda",
+ "Sports": "Sport",
+ "Technology": "Technologie",
+ "Country": "Země:",
+ "United States": "Spojené státy",
+ "United Kingdom": "Velká Británie",
+ "Canada": "Kanada",
+ "Australia": "Austrálie",
+ "Germany": "Německo",
+ "France": "Francie",
+ "Japan": "Japonsko",
+ "India": "Indie",
+ "Show advanced settings": "Zobrazit pokročilá nastavení",
+ "Hide advanced settings": "Skrýt pokročilá nastavení",
+ "Reconfigure": "Překonfigurovat",
+ "Search Results": "Výsledky hledání",
+ "Back to search": "Zpět k hledání",
+ "Sort by": "Řadit podle:",
+ "Publication Date": "Datum publikace (nejnovější první)",
+ "Relevance": "Relevance (nejlepší shoda první)",
+ "Language": "Jazyk:",
+ "English": "Angličtina",
+ "Spanish": "Španělština",
+ "French": "Francouzština",
+ "German": "Němčina",
+ "Italian": "Italština",
+ "Portuguese": "Portugalština",
+ "Japanese": "Japonština",
+ "Chinese": "Čínština",
+ "Arabic": "Arabština",
+ "Russian": "Ruština",
+ "Hindi": "Hindština",
+ "Korean": "Korejština",
+ "No articles found for your search": "Pro vaše vyhledávání nebyly nalezeny žádné články",
+ "Success! {0} results were found by {1}": "Úspěch! Nalezeno {0} výsledků pomocí {1}",
+ "all fields": "všechna pole"
+}
diff --git a/sdkjs-plugins/content/news/translations/de-DE.json b/sdkjs-plugins/content/news/translations/de-DE.json
new file mode 100644
index 00000000..61e1886c
--- /dev/null
+++ b/sdkjs-plugins/content/news/translations/de-DE.json
@@ -0,0 +1,59 @@
+{
+ "Search through millions of articles": "Durchsuchen Sie Millionen von Artikeln aus über 80.000 großen und kleinen Nachrichtenquellen und Blogs.",
+ "API key": "API-Schlüssel:",
+ "Enter your API key": "Geben Sie Ihren API-Schlüssel ein",
+ "Get your free API key": "Erhalten Sie Ihren kostenlosen API-Schlüssel von",
+ "Login": "Anmelden",
+ "Search": "Suchen",
+ "Top Headlines": "Top-Schlagzeilen",
+ "Prompt": "Anfrage",
+ "Search with keywords": "Suchen Sie mit einzelnen Schlüsselwörtern oder setzen Sie vollständige Phrasen in Anführungszeichen",
+ "Search in": "Suchen in",
+ "Title": "Titel",
+ "Description": "Beschreibung",
+ "Content": "Inhalt",
+ "Advanced Settings": "Erweiterte Einstellungen",
+ "Find": "Finden",
+ "Category": "Kategorie:",
+ "All Categories": "Alle Kategorien",
+ "General": "Allgemein",
+ "Business": "Geschäft",
+ "Entertainment": "Unterhaltung",
+ "Health": "Gesundheit",
+ "Science": "Wissenschaft",
+ "Sports": "Sport",
+ "Technology": "Technologie",
+ "Country": "Land:",
+ "United States": "Vereinigte Staaten",
+ "United Kingdom": "Vereinigtes Königreich",
+ "Canada": "Kanada",
+ "Australia": "Australien",
+ "Germany": "Deutschland",
+ "France": "Frankreich",
+ "Japan": "Japan",
+ "India": "Indien",
+ "Show advanced settings": "Erweiterte Einstellungen anzeigen",
+ "Hide advanced settings": "Erweiterte Einstellungen ausblenden",
+ "Reconfigure": "Neu konfigurieren",
+ "Search Results": "Suchergebnisse",
+ "Back to search": "Zurück zur Suche",
+ "Sort by": "Sortieren nach:",
+ "Publication Date": "Veröffentlichungsdatum (neueste zuerst)",
+ "Relevance": "Relevanz (beste Übereinstimmung zuerst)",
+ "Language": "Sprache:",
+ "English": "Englisch",
+ "Spanish": "Spanisch",
+ "French": "Französisch",
+ "German": "Deutsch",
+ "Italian": "Italienisch",
+ "Portuguese": "Portugiesisch",
+ "Japanese": "Japanisch",
+ "Chinese": "Chinesisch",
+ "Arabic": "Arabisch",
+ "Russian": "Russisch",
+ "Hindi": "Hindi",
+ "Korean": "Koreanisch",
+ "No articles found for your search": "Keine Artikel für Ihre Suche gefunden",
+ "Success! {0} results were found by {1}": "Erfolg! {0} Ergebnisse wurden durch {1} gefunden",
+ "all fields": "alle Felder"
+}
diff --git a/sdkjs-plugins/content/news/translations/en-US.json b/sdkjs-plugins/content/news/translations/en-US.json
new file mode 100644
index 00000000..e56dc2fd
--- /dev/null
+++ b/sdkjs-plugins/content/news/translations/en-US.json
@@ -0,0 +1,59 @@
+{
+ "Search through millions of articles": "Search through millions of articles from over 80,000 large and small news sources and blogs.",
+ "API key": "API key:",
+ "Enter your API key": "Enter your API key",
+ "Get your free API key": "Get your free API key from",
+ "Login": "Login",
+ "Search": "Search",
+ "Top Headlines": "Top Headlines",
+ "Prompt": "Prompt",
+ "Search with keywords": "Search with singular keywords, or surround complete phrases with quotation marks",
+ "Search in": "Search in",
+ "Title": "Title",
+ "Description": "Description",
+ "Content": "Content",
+ "Advanced Settings": "Advanced Settings",
+ "Find": "Find",
+ "Category": "Category:",
+ "All Categories": "All Categories",
+ "General": "General",
+ "Business": "Business",
+ "Entertainment": "Entertainment",
+ "Health": "Health",
+ "Science": "Science",
+ "Sports": "Sports",
+ "Technology": "Technology",
+ "Country": "Country:",
+ "United States": "United States",
+ "United Kingdom": "United Kingdom",
+ "Canada": "Canada",
+ "Australia": "Australia",
+ "Germany": "Germany",
+ "France": "France",
+ "Japan": "Japan",
+ "India": "India",
+ "Show advanced settings": "Show advanced settings",
+ "Hide advanced settings": "Hide advanced settings",
+ "Reconfigure": "Reconfigure",
+ "Search Results": "Search Results",
+ "Back to search": "Back to search",
+ "Sort by": "Sort by:",
+ "Publication Date": "Publication Date (newest first)",
+ "Relevance": "Relevance (best match first)",
+ "Language": "Language:",
+ "English": "English",
+ "Spanish": "Spanish",
+ "French": "French",
+ "German": "German",
+ "Italian": "Italian",
+ "Portuguese": "Portuguese",
+ "Japanese": "Japanese",
+ "Chinese": "Chinese",
+ "Arabic": "Arabic",
+ "Russian": "Russian",
+ "Hindi": "Hindi",
+ "Korean": "Korean",
+ "No articles found for your search": "No articles found for your search",
+ "Success! {0} results were found by {1}": "Success! {0} results were found by {1}",
+ "all fields": "all fields"
+}
diff --git a/sdkjs-plugins/content/news/translations/es-ES.json b/sdkjs-plugins/content/news/translations/es-ES.json
new file mode 100644
index 00000000..9e1c80be
--- /dev/null
+++ b/sdkjs-plugins/content/news/translations/es-ES.json
@@ -0,0 +1,59 @@
+{
+ "Search through millions of articles": "Busque entre millones de artículos de más de 80,000 fuentes de noticias y blogs grandes y pequeños.",
+ "API key": "Clave API:",
+ "Enter your API key": "Ingrese su clave API",
+ "Get your free API key": "Obtenga su clave API gratuita desde",
+ "Login": "Iniciar sesión",
+ "Search": "Buscar",
+ "Top Headlines": "Titulares principales",
+ "Prompt": "Consulta",
+ "Search with keywords": "Busque con palabras clave singulares o rodee frases completas con comillas",
+ "Search in": "Buscar en",
+ "Title": "Título",
+ "Description": "Descripción",
+ "Content": "Contenido",
+ "Advanced Settings": "Configuración avanzada",
+ "Find": "Buscar",
+ "Category": "Categoría:",
+ "All Categories": "Todas las categorías",
+ "General": "General",
+ "Business": "Negocios",
+ "Entertainment": "Entretenimiento",
+ "Health": "Salud",
+ "Science": "Ciencia",
+ "Sports": "Deportes",
+ "Technology": "Tecnología",
+ "Country": "País:",
+ "United States": "Estados Unidos",
+ "United Kingdom": "Reino Unido",
+ "Canada": "Canadá",
+ "Australia": "Australia",
+ "Germany": "Alemania",
+ "France": "Francia",
+ "Japan": "Japón",
+ "India": "India",
+ "Show advanced settings": "Mostrar configuración avanzada",
+ "Hide advanced settings": "Ocultar configuración avanzada",
+ "Reconfigure": "Reconfigurar",
+ "Search Results": "Resultados de búsqueda",
+ "Back to search": "Volver a la búsqueda",
+ "Sort by": "Ordenar por:",
+ "Publication Date": "Fecha de publicación (más reciente primero)",
+ "Relevance": "Relevancia (mejor coincidencia primero)",
+ "Language": "Idioma:",
+ "English": "Inglés",
+ "Spanish": "Español",
+ "French": "Francés",
+ "German": "Alemán",
+ "Italian": "Italiano",
+ "Portuguese": "Portugués",
+ "Japanese": "Japonés",
+ "Chinese": "Chino",
+ "Arabic": "Árabe",
+ "Russian": "Ruso",
+ "Hindi": "Hindi",
+ "Korean": "Coreano",
+ "No articles found for your search": "No se encontraron artículos para su búsqueda",
+ "Success! {0} results were found by {1}": "¡Éxito! Se encontraron {0} resultados por {1}",
+ "all fields": "todos los campos"
+}
diff --git a/sdkjs-plugins/content/news/translations/fr-FR.json b/sdkjs-plugins/content/news/translations/fr-FR.json
new file mode 100644
index 00000000..26522a92
--- /dev/null
+++ b/sdkjs-plugins/content/news/translations/fr-FR.json
@@ -0,0 +1,59 @@
+{
+ "Search through millions of articles": "Recherchez parmi des millions d'articles",
+ "API key": "Clé API",
+ "Enter your API key": "Entrez votre clé API",
+ "Get your free API key": "Obtenez votre clé API gratuite",
+ "Login": "Connexion",
+ "Search": "Rechercher",
+ "Top Headlines": "Titres principaux",
+ "Prompt": "Requête",
+ "Search with keywords": "Recherche par mots-clés",
+ "Search in": "Rechercher dans",
+ "Title": "Titre",
+ "Description": "Description",
+ "Content": "Contenu",
+ "Advanced Settings": "Paramètres avancés",
+ "Find": "Rechercher",
+ "Category": "Catégorie :",
+ "All Categories": "Toutes les catégories",
+ "General": "Général",
+ "Business": "Business",
+ "Entertainment": "Divertissement",
+ "Health": "Santé",
+ "Science": "Science",
+ "Sports": "Sports",
+ "Technology": "Technologie",
+ "Country": "Pays :",
+ "United States": "États-Unis",
+ "United Kingdom": "Royaume-Uni",
+ "Canada": "Canada",
+ "Australia": "Australie",
+ "Germany": "Allemagne",
+ "France": "France",
+ "Japan": "Japon",
+ "India": "Inde",
+ "Show advanced settings": "Afficher les paramètres avancés",
+ "Hide advanced settings": "Masquer les paramètres avancés",
+ "Reconfigure": "Reconfigurer",
+ "Search Results": "Résultats de recherche",
+ "Back to search": "Retour à la recherche",
+ "Sort by": "Trier par :",
+ "Publication Date": "Date de publication",
+ "Relevance": "Pertinence",
+ "Language": "Langue :",
+ "English": "Anglais",
+ "Spanish": "Espagnol",
+ "French": "Français",
+ "German": "Allemand",
+ "Italian": "Italien",
+ "Portuguese": "Portugais",
+ "Japanese": "Japonais",
+ "Chinese": "Chinois",
+ "Arabic": "Arabe",
+ "Russian": "Russe",
+ "Hindi": "Hindi",
+ "Korean": "Coréen",
+ "No articles found for your search": "Aucun article trouvé pour votre recherche",
+ "Success! {0} results were found by {1}": "Succès ! {0} résultats ont été trouvés par {1}",
+ "all fields": "tous les champs"
+}
diff --git a/sdkjs-plugins/content/news/translations/it-IT.json b/sdkjs-plugins/content/news/translations/it-IT.json
new file mode 100644
index 00000000..bfbbe7d1
--- /dev/null
+++ b/sdkjs-plugins/content/news/translations/it-IT.json
@@ -0,0 +1,59 @@
+{
+ "Search through millions of articles": "Cerca tra milioni di articoli di oltre 80.000 fonti, tra notizie e blog grandi e piccoli.",
+ "API key": "Chiave API:",
+ "Enter your API key": "Inserisci la tua chiave API",
+ "Get your free API key": "Ottieni la tua chiave API gratuita",
+ "Login": "Accedi",
+ "Search": "Cerca",
+ "Top Headlines": "Titoli principali",
+ "Prompt": "Richiesta",
+ "Search with keywords": "Cerca con parole chiave singole o con frasi complete con virgolette",
+ "Search in": "Cerca in",
+ "Title": "Titolo",
+ "Description": "Descrizione",
+ "Content": "Contenuto",
+ "Advanced Settings": "Impostazioni avanzate",
+ "Find": "Trova",
+ "Category": "Categoria:",
+ "All Categories": "Tutte le categorie",
+ "General": "Generale",
+ "Business": "Affari",
+ "Entertainment": "Intrattenimento",
+ "Health": "Salute",
+ "Science": "Scienza",
+ "Sports": "Sport",
+ "Technology": "Tecnologia",
+ "Country": "Paese:",
+ "United States": "Stati Uniti",
+ "United Kingdom": "Regno Unito",
+ "Canada": "Canada",
+ "Australia": "Australia",
+ "Germany": "Germania",
+ "France": "Francia",
+ "Japan": "Giappone",
+ "India": "India",
+ "Show advanced settings": "Mostra impostazioni avanzate",
+ "Hide advanced settings": "Nascondi impostazioni avanzate",
+ "Reconfigure": "Riconfigura",
+ "Search Results": "Risultati della ricerca",
+ "Back to search": "Torna alla ricerca",
+ "Sort by": "Ordina per:",
+ "Publication Date": "Data di pubblicazione (più recente prima)",
+ "Relevance": "Rilevanza (migliore corrispondenza prima)",
+ "Language": "Lingua:",
+ "English": "Inglese",
+ "Spanish": "Spagnolo",
+ "French": "Francese",
+ "German": "Tedesco",
+ "Italian": "Italiano",
+ "Portuguese": "Portoghese",
+ "Japanese": "Giapponese",
+ "Chinese": "Cinese",
+ "Arabic": "Arabo",
+ "Russian": "Russo",
+ "Hindi": "Hindi",
+ "Korean": "Coreano",
+ "No articles found for your search": "Nessun articolo trovato per la tua ricerca",
+ "Success! {0} results were found by {1}": "Successo! {0} risultati sono stati trovati da {1}",
+ "all fields": "tutti i campi"
+}
diff --git a/sdkjs-plugins/content/news/translations/ja-JA.json b/sdkjs-plugins/content/news/translations/ja-JA.json
new file mode 100644
index 00000000..1b5b8855
--- /dev/null
+++ b/sdkjs-plugins/content/news/translations/ja-JA.json
@@ -0,0 +1,59 @@
+{
+ "Search through millions of articles": "80,000を超える大小のニュースソースとブログから数百万の記事を検索します。",
+ "API key": "APIキー:",
+ "Enter your API key": "APIキーを入力してください",
+ "Get your free API key": "無料のAPIキーを取得",
+ "Login": "ログイン",
+ "Search": "検索",
+ "Top Headlines": "トップニュース",
+ "Prompt": "プロンプト",
+ "Search with keywords": "単一のキーワードで検索するか、完全なフレーズを引用符で囲んでください",
+ "Search in": "検索対象",
+ "Title": "タイトル",
+ "Description": "説明",
+ "Content": "コンテンツ",
+ "Advanced Settings": "詳細設定",
+ "Find": "検索",
+ "Category": "カテゴリ:",
+ "All Categories": "すべてのカテゴリ",
+ "General": "一般",
+ "Business": "ビジネス",
+ "Entertainment": "エンターテイメント",
+ "Health": "健康",
+ "Science": "科学",
+ "Sports": "スポーツ",
+ "Technology": "技術",
+ "Country": "国:",
+ "United States": "アメリカ合衆国",
+ "United Kingdom": "イギリス",
+ "Canada": "カナダ",
+ "Australia": "オーストラリア",
+ "Germany": "ドイツ",
+ "France": "フランス",
+ "Japan": "日本",
+ "India": "インド",
+ "Show advanced settings": "詳細設定を表示",
+ "Hide advanced settings": "詳細設定を非表示",
+ "Reconfigure": "再設定",
+ "Search Results": "検索結果",
+ "Back to search": "検索に戻る",
+ "Sort by": "並び順:",
+ "Publication Date": "公開日(新しい順)",
+ "Relevance": "関連性(最適な一致順)",
+ "Language": "言語:",
+ "English": "英語",
+ "Spanish": "スペイン語",
+ "French": "フランス語",
+ "German": "ドイツ語",
+ "Italian": "イタリア語",
+ "Portuguese": "ポルトガル語",
+ "Japanese": "日本語",
+ "Chinese": "中国語",
+ "Arabic": "アラビア語",
+ "Russian": "ロシア語",
+ "Hindi": "ヒンディー語",
+ "Korean": "韓国語",
+ "No articles found for your search": "検索条件に一致する記事が見つかりませんでした",
+ "Success! {0} results were found by {1}": "成功!{1}で{0}件の結果が見つかりました",
+ "all fields": "すべてのフィールド"
+}
diff --git a/sdkjs-plugins/content/news/translations/langs.json b/sdkjs-plugins/content/news/translations/langs.json
new file mode 100644
index 00000000..9163f7ab
--- /dev/null
+++ b/sdkjs-plugins/content/news/translations/langs.json
@@ -0,0 +1,12 @@
+[
+ "cs-CZ",
+ "de-DE",
+ "es-ES",
+ "fr-FR",
+ "ja-JA",
+ "ru-RU",
+ "pt-BR",
+ "si-LK",
+ "zh-ZH",
+ "it-IT"
+]
diff --git a/sdkjs-plugins/content/news/translations/pt-BR.json b/sdkjs-plugins/content/news/translations/pt-BR.json
new file mode 100644
index 00000000..4acfa7f1
--- /dev/null
+++ b/sdkjs-plugins/content/news/translations/pt-BR.json
@@ -0,0 +1,59 @@
+{
+ "Search through millions of articles": "Pesquise entre milhões de artigos de mais de 80.000 fontes de notícias e blogs grandes e pequenos.",
+ "API key": "Chave da API:",
+ "Enter your API key": "Digite sua chave da API",
+ "Get your free API key": "Obtenha sua chave da API gratuita em",
+ "Login": "Entrar",
+ "Search": "Pesquisar",
+ "Top Headlines": "Principais manchetes",
+ "Prompt": "Consulta",
+ "Search with keywords": "Pesquise com palavras-chave singulares ou coloque frases completas entre aspas",
+ "Search in": "Pesquisar em",
+ "Title": "Título",
+ "Description": "Descrição",
+ "Content": "Conteúdo",
+ "Advanced Settings": "Configurações avançadas",
+ "Find": "Encontrar",
+ "Category": "Categoria:",
+ "All Categories": "Todas as categorias",
+ "General": "Geral",
+ "Business": "Negócios",
+ "Entertainment": "Entretenimento",
+ "Health": "Saúde",
+ "Science": "Ciência",
+ "Sports": "Esportes",
+ "Technology": "Tecnologia",
+ "Country": "País:",
+ "United States": "Estados Unidos",
+ "United Kingdom": "Reino Unido",
+ "Canada": "Canadá",
+ "Australia": "Austrália",
+ "Germany": "Alemanha",
+ "France": "França",
+ "Japan": "Japão",
+ "India": "Índia",
+ "Show advanced settings": "Mostrar configurações avançadas",
+ "Hide advanced settings": "Ocultar configurações avançadas",
+ "Reconfigure": "Reconfigurar",
+ "Search Results": "Resultados da pesquisa",
+ "Back to search": "Voltar à pesquisa",
+ "Sort by": "Ordenar por:",
+ "Publication Date": "Data de publicação (mais recente primeiro)",
+ "Relevance": "Relevância (melhor correspondência primeiro)",
+ "Language": "Idioma:",
+ "English": "Inglês",
+ "Spanish": "Espanhol",
+ "French": "Francês",
+ "German": "Alemão",
+ "Italian": "Italiano",
+ "Portuguese": "Português",
+ "Japanese": "Japonês",
+ "Chinese": "Chinês",
+ "Arabic": "Árabe",
+ "Russian": "Russo",
+ "Hindi": "Hindi",
+ "Korean": "Coreano",
+ "No articles found for your search": "Nenhum artigo encontrado para sua pesquisa",
+ "Success! {0} results were found by {1}": "Sucesso! {0} resultados foram encontrados por {1}",
+ "all fields": "todos os campos"
+}
diff --git a/sdkjs-plugins/content/news/translations/ru-RU.json b/sdkjs-plugins/content/news/translations/ru-RU.json
new file mode 100644
index 00000000..469edd00
--- /dev/null
+++ b/sdkjs-plugins/content/news/translations/ru-RU.json
@@ -0,0 +1,59 @@
+{
+ "Search through millions of articles": "Поиск среди миллионов статей из более чем 80 000 крупных и малых новостных источников и блогов.",
+ "API key": "ключ API:",
+ "Enter your API key": "Введите ваш ключ API",
+ "Get your free API key": "Получите бесплатный ключ API из",
+ "Login": "Вход",
+ "Search": "Поиск",
+ "Top Headlines": "Главные новости",
+ "Prompt": "Запрос",
+ "Search with keywords": "Искать по отдельным ключевым словам или заключать целые фразы в кавычки",
+ "Search in": "Искать в",
+ "Title": "Заголовок",
+ "Description": "Описание",
+ "Content": "Содержание",
+ "Advanced Settings": "Расширенные настройки",
+ "Find": "Найти",
+ "Category": "Категория:",
+ "All Categories": "Все категории",
+ "General": "Общее",
+ "Business": "Бизнес",
+ "Entertainment": "Развлечения",
+ "Health": "Здоровье",
+ "Science": "Наука",
+ "Sports": "Спорт",
+ "Technology": "Технологии",
+ "Country": "Страна:",
+ "United States": "Соединённые Штаты",
+ "United Kingdom": "Великобритания",
+ "Canada": "Канада",
+ "Australia": "Австралия",
+ "Germany": "Германия",
+ "France": "Франция",
+ "Japan": "Япония",
+ "India": "Индия",
+ "Show advanced settings": "Показать расширенные настройки",
+ "Hide advanced settings": "Скрыть расширенные настройки",
+ "Reconfigure": "Перенастроить",
+ "Search Results": "Результаты поиска",
+ "Back to search": "Вернуться к поиску",
+ "Sort by": "Сортировать по:",
+ "Publication Date": "Дата публикации (сначала недавние)",
+ "Relevance": "Релевантность (сначала наилучшее совпадение)",
+ "Language": "Язык:",
+ "English": "Английский",
+ "Spanish": "Испанский",
+ "French": "Французский",
+ "German": "Немецкий",
+ "Italian": "Итальянский",
+ "Portuguese": "Португальский",
+ "Japanese": "Японский",
+ "Chinese": "Китайский",
+ "Arabic": "Арабский",
+ "Russian": "Русский",
+ "Hindi": "Хинди",
+ "Korean": "Корейский",
+ "No articles found for your search": "Статьи по вашему запросу не найдены",
+ "Success! {0} results were found by {1}": "Успех! Найдено {0} результатов по {1}",
+ "all fields": "все поля"
+}
diff --git a/sdkjs-plugins/content/news/translations/si-LK.json b/sdkjs-plugins/content/news/translations/si-LK.json
new file mode 100644
index 00000000..5b558ef8
--- /dev/null
+++ b/sdkjs-plugins/content/news/translations/si-LK.json
@@ -0,0 +1,59 @@
+{
+ "Search through millions of articles": "ලක්ෂ ගණනක් ලිපි සෙවීමට",
+ "API key": "API යතුර:",
+ "Enter your API key": "ඔබගේ API යතුර ඇතුළත් කරන්න",
+ "Get your free API key": "ඔබගේ නොමිලේ API යතුර ලබා ගන්න",
+ "Login": "ඇතුළු වන්න",
+ "Search": "සෙවීම",
+ "Top Headlines": "උපරිම ශීර්ෂ සේරියාව",
+ "Prompt": "උදව් වචනය",
+ "Search with keywords": "යතුරු වචන භාවිතයෙන් සෙවීම, හෝ සම්පූර්ණ වාක්ය උද්ධෘත ලකුණු තුළ දැමීම",
+ "Search in": "සෙවීම කරන්න:",
+ "Title": "ශීර්ෂය",
+ "Description": "විස්තරය",
+ "Content": "අන්තර්ගතය",
+ "Advanced Settings": "උසස් සැකසුම්",
+ "Find": "සොයන්න",
+ "Category": "වර්ගය:",
+ "All Categories": "සියලුම වර්ග",
+ "General": "සාමාන්ය",
+ "Business": "ව්යාපාර",
+ "Entertainment": "විනෝදාස්වාදය",
+ "Health": "සෞඛ්යය",
+ "Science": "විද්යාව",
+ "Sports": "ක්රීඩා",
+ "Technology": "තාක්ෂණය",
+ "Country": "රට:",
+ "United States": "එක්සත් ජනපදය",
+ "United Kingdom": "එක්සත් රාජධානිය",
+ "Canada": "කැනඩාව",
+ "Australia": "ඕස්ට්රේලියාව",
+ "Germany": "ජර්මනිය",
+ "France": "ප්රංශය",
+ "Japan": "ජපානය",
+ "India": "ඉන්දියාව",
+ "Show advanced settings": "උසස් සැකසුම් පෙන්වන්න",
+ "Hide advanced settings": "උසස් සැකසුම් සඟවන්න",
+ "Reconfigure": "නැවත සැකසීම",
+ "Search Results": "සෙවීමේ ප්රතිඵල",
+ "Back to search": "සෙවීමට ආපසු",
+ "Sort by": "පෙළගස්වන්න:",
+ "Publication Date": "ප්රකාශන දිනය (අලුත්ම මුලින්)",
+ "Relevance": "සම්බන්ධතාවය (හොඳම ගැලපීම මුලින්)",
+ "Language": "භාෂාව:",
+ "English": "ඉංග්රීසි",
+ "Spanish": "ස්පාඤ්ඤ",
+ "French": "ප්රංශ",
+ "German": "ජර්මානු",
+ "Italian": "ඉතාලි",
+ "Portuguese": "පෘතුගීසි",
+ "Japanese": "ජපන්",
+ "Chinese": "චීන",
+ "Arabic": "අරාබි",
+ "Russian": "රුසියානු",
+ "Hindi": "හින්දි",
+ "Korean": "කොරියානු",
+ "No articles found for your search": "ඔබගේ සෙවීම සඳහා කිසිදු ලිපියක් හමු නොවීය",
+ "Success! {0} results were found by {1}": "සාර්ථකයි! {1} මගින් {0} ප්රතිඵල හමු විය",
+ "all fields": "සියලුම ක්ෂේත්ර"
+}
diff --git a/sdkjs-plugins/content/news/translations/zh-ZH.json b/sdkjs-plugins/content/news/translations/zh-ZH.json
new file mode 100644
index 00000000..48e84443
--- /dev/null
+++ b/sdkjs-plugins/content/news/translations/zh-ZH.json
@@ -0,0 +1,59 @@
+{
+ "Search through millions of articles": "在来自8万多家新闻媒体和博客的数百万篇文章中快速搜索所需信息。",
+ "API key": "API 密钥:",
+ "Enter your API key": "输入您的 API 密钥",
+ "Get your free API key": "获取免费API密钥:",
+ "Login": "登录",
+ "Search": "搜索",
+ "Top Headlines": "头条新闻",
+ "Prompt": "查询",
+ "Search with keywords": "使用单个关键词搜索,或用引号括起完整短语搜素",
+ "Search in": "搜索范围:",
+ "Title": "标题",
+ "Description": "描述",
+ "Content": "内容",
+ "Advanced Settings": "高级设置",
+ "Find": "搜素",
+ "Category": "类别:",
+ "All Categories": "所有类别",
+ "General": "综合",
+ "Business": "商业",
+ "Entertainment": "娱乐",
+ "Health": "健康",
+ "Science": "科学",
+ "Sports": "体育",
+ "Technology": "科技",
+ "Country": "国家:",
+ "United States": "美国",
+ "United Kingdom": "英国",
+ "Canada": "加拿大",
+ "Australia": "澳大利亚",
+ "Germany": "德国",
+ "France": "法国",
+ "Japan": "日本",
+ "India": "印度",
+ "Show advanced settings": "显示高级设置",
+ "Hide advanced settings": "隐藏高级设置",
+ "Reconfigure": "重新配置",
+ "Search Results": "搜索结果",
+ "Back to search": "返回搜索",
+ "Sort by": "排序方式:",
+ "Publication Date": "发布时间(最新优先)",
+ "Relevance": "相关性(最佳匹配优先)",
+ "Language": "语言:",
+ "English": "英语",
+ "Spanish": "西班牙语",
+ "French": "法语",
+ "German": "德语",
+ "Italian": "意大利语",
+ "Portuguese": "葡萄牙语",
+ "Japanese": "日语",
+ "Chinese": "中文",
+ "Arabic": "阿拉伯语",
+ "Russian": "俄语",
+ "Hindi": "印地语",
+ "Korean": "韩语",
+ "No articles found for your search": "未找到与您搜索相关的文章",
+ "Success! {0} results were found by {1}": "成功!通过{1}找到了{0}个结果",
+ "all fields": "所有字段"
+}
diff --git a/sdkjs-plugins/content/texthighlighter/CHANGELOG.md b/sdkjs-plugins/content/texthighlighter/CHANGELOG.md
new file mode 100644
index 00000000..2c9a2d06
--- /dev/null
+++ b/sdkjs-plugins/content/texthighlighter/CHANGELOG.md
@@ -0,0 +1,5 @@
+# Change Log
+
+## 1.0.0
+
+- Initial release.
diff --git a/sdkjs-plugins/content/texthighlighter/README.md b/sdkjs-plugins/content/texthighlighter/README.md
new file mode 100644
index 00000000..b956e732
--- /dev/null
+++ b/sdkjs-plugins/content/texthighlighter/README.md
@@ -0,0 +1,55 @@
+## Overview
+
+The **Text Highlighter** plugin allows users to highlight selected text in the Document, Presentation, and Text Editors with customizable colors. It provides an easy way to emphasize important information and visually organize content.
+
+The plugin supports:
+
+* Highlighting text with multiple color options.
+* Removing existing highlights.
+* Compatibility across different editors (Document, Presentation, Text).
+
+---
+
+## Installation
+
+1. Install the **Text Highlighter Plugin** from the plugin marketplace.
+2. Once installed, it will appear in the plugin tab of the editor.
+
+---
+
+## Usage
+
+### Activate the Text Highlighter Plugin
+
+* Go to the **Plugin** tab.
+* Click the **Text Highlighter** plugin in the "Plugins" section.
+
+### Highlight Text
+
+1. Select the text you want to highlight.
+2. Choose your highlight color from the available options.
+3. Click the **Highlight** button to apply the chosen color.
+
+### Remove Highlight
+
+* Select the highlighted text.
+* Click the **Remove Highlight** option to clear the applied color.
+
+### Supported Editors
+
+* **Document Editor**: Works for paragraphs, words, and inline selections.
+* **Presentation Editor**: Supports highlighting within text boxes and slides.
+* **Text Editor**: Provides basic highlighting functionality for selected content.
+
+---
+
+## Note for Spreadsheet Editor
+
+Highlighting text is not supported in the Spreadsheet Editor, as cells do not handle inline text formatting in the same way.
+
+---
+
+## Additional Information
+
+If you need more details about how to use or develop plugins for ONLYOFFICE, please refer to the [ONLYOFFICE Plugin Documentation](https://api.onlyoffice.com/docspace/plugins-sdk/get-started).
+
diff --git a/sdkjs-plugins/content/texthighlighter/codealike.json b/sdkjs-plugins/content/texthighlighter/codealike.json
new file mode 100644
index 00000000..a4b892c4
--- /dev/null
+++ b/sdkjs-plugins/content/texthighlighter/codealike.json
@@ -0,0 +1 @@
+{"projectId":"9c15a3e0-4231-11f0-aa14-d53a61904019","projectName":"text-highlighter"}
\ No newline at end of file
diff --git a/sdkjs-plugins/content/texthighlighter/config.json b/sdkjs-plugins/content/texthighlighter/config.json
new file mode 100644
index 00000000..07ed8e97
--- /dev/null
+++ b/sdkjs-plugins/content/texthighlighter/config.json
@@ -0,0 +1,103 @@
+{
+ "name": "Text Highlighter",
+ "nameLocale": {
+ "en-US": "Text Highlighter",
+ "ru": "Выделение текста",
+ "de": "Texthervorhebung",
+ "fr": "Surligneur de texte",
+ "es": "Resaltador de texto",
+ "pt-BR": "Marcador de Texto",
+ "it": "Evidenziatore di Testo",
+ "ja": "テキストハイライター",
+ "uk": "Виділення тексту",
+ "zh": "文本高亮",
+ "cs-CS": "Zvýrazňovač textu",
+ "sq-AL": "Theksues Teksti",
+ "sr-Cyrl-RS": "Истакнувач текста",
+ "sr-Latn-RS": "Istaknuvač teksta"
+ },
+
+ "version": "1.0.0",
+ "baseUrl": "https://raza2004.github.io/onlyoffice.github.io/sdkjs-plugins/content/texthighlighter/",
+ "guid": "asc.{07FD8DFA-DFE0-4089-AL24-0730933CC804}",
+
+ "manifestVersion": "7.3.0",
+
+
+ "variations": [
+ {
+ "description": "This plugin allows you to search for text and apply highlighting, color, and formatting styles in the document.",
+ "descriptionLocale": {
+ "en": "This plugin allows you to search for text and apply highlighting, color, and formatting styles in the document.",
+ "ru": "Этот плагин позволяет вам искать текст и применять выделение, цвет и стили форматирования в документе.",
+ "de": "Dieses Plugin ermöglicht das Suchen von Text und das Anwenden von Hervorhebungen, Farben und Formatierungen im Dokument.",
+ "fr": "Ce plugin vous permet de rechercher du texte et d'appliquer des styles de surlignage, de couleur et de formatage dans le document.",
+ "es": "Este complemento le permite buscar texto y aplicar estilos de resaltado, color y formato en el documento.",
+ "pt-BR": "Este plugin permite pesquisar texto e aplicar estilos de destaque, cor e formatação no documento.",
+ "it": "Questo plugin consente di cercare testo e applicare stili di evidenziazione, colore e formattazione nel documento.",
+ "ja": "このプラグインを使用すると、ドキュメント内のテキストを検索し、ハイライト、色、およびフォーマットスタイルを適用できます。",
+ "zh": "此插件允许您在文档中搜索文本并应用高亮、颜色和格式样式。",
+ "uk": "Цей плагін дозволяє виконувати пошук тексту та застосовувати виділення, колір і стилі форматування у вашому документі.",
+ "cs-CS": "Tento plugin umožňuje vyhledávat text a aplikovat zvýraznění, barvu a formátovací styly v dokumentu.",
+ "sq-AL": "Ky shtojcë ju lejon të kërkoni tekst dhe të aplikoni stilet e theksimit, ngjyrës dhe formatimit në dokument.",
+ "sr-Cyrl-RS": "Овај додатак вам омогућава да претражујете текст и примењујете стилове истакнутог текста, боје и форматирања у документу.",
+ "sr-Latn-RS": "Ovaj dodatak vam omogućava da pretražujete tekst i primenjujete stilove istaknutog teksta, boje i formatiranja u dokumentu."
+
+ },
+ "url": "index.html",
+ "type": "panel",
+ "size": [300, 600],
+ "isViewer" : true,
+ "EditorsSupport" : ["word", "pdf"],
+ "isVisual" : true,
+ "isModal" : false,
+ "isInsideMode" : true,
+ "initDataType" : "text",
+ "isUpdateOleOnResize" : true,
+ "initOnSelectionChanged": true,
+ "events": ["onSelectionChanged", "onClick", "onApply", "onGetSelectedText"],
+ "methods": ["ApplyHighlight", "GetSelectedText"],
+ "buttons": [],
+ "icons": ["resources/light/icon.svg","resources/light/icon@2x.svg" ],
+ "icons2": [
+ {
+ "theme": "flat",
+ "style": "light",
+ "100%": { "normal": "resources/light/icon.svg" },
+ "125%": { "normal": "resources/light/icon@1.25x.svg" },
+ "150%": { "normal": "resources/light/icon@1.5x.svg" },
+ "175%": { "normal": "resources/light/icon@1.75x.svg" },
+ "200%": { "normal": "resources/light/icon@2x.svg" },
+ "default": { "normal": "resources/light/icon.svg" }
+ },
+ {
+ "theme": "flatDark",
+ "style": "dark",
+ "100%": { "normal": "resources/dark/icon.svg" },
+ "125%": { "normal": "resources/dark/icon@1.25x.svg" },
+ "150%": { "normal": "resources/dark/icon@1.5x.svg" },
+ "175%": { "normal": "resources/dark/icon@1.75x.svg" },
+ "200%": { "normal": "resources/dark/icon@2x.svg" }
+ }
+ ],
+
+ "store": {
+ "background": {
+ "light": "#107cbc",
+ "dark": "#5f55af"
+ },
+ "screenshots" : [
+ "resources/store/screenshots/screen_1.png",
+ "resources/store/screenshots/screen_2.png",
+ "resources/store/screenshots/screen_3.png",
+ "resources/store/screenshots/screen_4.png"
+ ],
+ "categories": ["work","specAbilities"],
+ "icons": {
+ "light": "resources/store/icons",
+ "dark": "resources/store/icons"
+ }
+ }
+ }
+ ]
+}
\ No newline at end of file
diff --git a/sdkjs-plugins/content/texthighlighter/deploy/texthighlighter.plugin b/sdkjs-plugins/content/texthighlighter/deploy/texthighlighter.plugin
new file mode 100644
index 00000000..5038bc49
Binary files /dev/null and b/sdkjs-plugins/content/texthighlighter/deploy/texthighlighter.plugin differ
diff --git a/sdkjs-plugins/content/texthighlighter/index.html b/sdkjs-plugins/content/texthighlighter/index.html
new file mode 100644
index 00000000..bd3cd22c
--- /dev/null
+++ b/sdkjs-plugins/content/texthighlighter/index.html
@@ -0,0 +1,97 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/sdkjs-plugins/content/texthighlighter/resources/dark/icon.svg b/sdkjs-plugins/content/texthighlighter/resources/dark/icon.svg
new file mode 100644
index 00000000..1f4dd726
--- /dev/null
+++ b/sdkjs-plugins/content/texthighlighter/resources/dark/icon.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/sdkjs-plugins/content/texthighlighter/resources/dark/icon@1.25x.svg b/sdkjs-plugins/content/texthighlighter/resources/dark/icon@1.25x.svg
new file mode 100644
index 00000000..10aa63e4
--- /dev/null
+++ b/sdkjs-plugins/content/texthighlighter/resources/dark/icon@1.25x.svg
@@ -0,0 +1,15 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/sdkjs-plugins/content/texthighlighter/resources/dark/icon@1.5x.svg b/sdkjs-plugins/content/texthighlighter/resources/dark/icon@1.5x.svg
new file mode 100644
index 00000000..1c071a5f
--- /dev/null
+++ b/sdkjs-plugins/content/texthighlighter/resources/dark/icon@1.5x.svg
@@ -0,0 +1,15 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/sdkjs-plugins/content/texthighlighter/resources/dark/icon@1.75x.svg b/sdkjs-plugins/content/texthighlighter/resources/dark/icon@1.75x.svg
new file mode 100644
index 00000000..f5bdfd7f
--- /dev/null
+++ b/sdkjs-plugins/content/texthighlighter/resources/dark/icon@1.75x.svg
@@ -0,0 +1,15 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/sdkjs-plugins/content/texthighlighter/resources/dark/icon@2x.svg b/sdkjs-plugins/content/texthighlighter/resources/dark/icon@2x.svg
new file mode 100644
index 00000000..c167cf36
--- /dev/null
+++ b/sdkjs-plugins/content/texthighlighter/resources/dark/icon@2x.svg
@@ -0,0 +1,15 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/sdkjs-plugins/content/texthighlighter/resources/light/icon.svg b/sdkjs-plugins/content/texthighlighter/resources/light/icon.svg
new file mode 100644
index 00000000..ec5d8b45
--- /dev/null
+++ b/sdkjs-plugins/content/texthighlighter/resources/light/icon.svg
@@ -0,0 +1,12 @@
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/sdkjs-plugins/content/texthighlighter/resources/light/icon@1.25x.svg b/sdkjs-plugins/content/texthighlighter/resources/light/icon@1.25x.svg
new file mode 100644
index 00000000..822bd1a5
--- /dev/null
+++ b/sdkjs-plugins/content/texthighlighter/resources/light/icon@1.25x.svg
@@ -0,0 +1,8 @@
+
+
+
+
+
+
+
+
diff --git a/sdkjs-plugins/content/texthighlighter/resources/light/icon@1.5x.svg b/sdkjs-plugins/content/texthighlighter/resources/light/icon@1.5x.svg
new file mode 100644
index 00000000..2dc31bcc
--- /dev/null
+++ b/sdkjs-plugins/content/texthighlighter/resources/light/icon@1.5x.svg
@@ -0,0 +1,8 @@
+
+
+
+
+
+
+
+
diff --git a/sdkjs-plugins/content/texthighlighter/resources/light/icon@1.75x.svg b/sdkjs-plugins/content/texthighlighter/resources/light/icon@1.75x.svg
new file mode 100644
index 00000000..af17193a
--- /dev/null
+++ b/sdkjs-plugins/content/texthighlighter/resources/light/icon@1.75x.svg
@@ -0,0 +1,15 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/sdkjs-plugins/content/texthighlighter/resources/light/icon@2x.svg b/sdkjs-plugins/content/texthighlighter/resources/light/icon@2x.svg
new file mode 100644
index 00000000..312d16dd
--- /dev/null
+++ b/sdkjs-plugins/content/texthighlighter/resources/light/icon@2x.svg
@@ -0,0 +1,8 @@
+
+
+
+
+
+
+
+
diff --git a/sdkjs-plugins/content/texthighlighter/resources/store/icons/icon.png b/sdkjs-plugins/content/texthighlighter/resources/store/icons/icon.png
new file mode 100644
index 00000000..9defea17
Binary files /dev/null and b/sdkjs-plugins/content/texthighlighter/resources/store/icons/icon.png differ
diff --git a/sdkjs-plugins/content/texthighlighter/resources/store/icons/icon.svg b/sdkjs-plugins/content/texthighlighter/resources/store/icons/icon.svg
new file mode 100644
index 00000000..ec5d8b45
--- /dev/null
+++ b/sdkjs-plugins/content/texthighlighter/resources/store/icons/icon.svg
@@ -0,0 +1,12 @@
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/sdkjs-plugins/content/texthighlighter/resources/store/icons/icon@1.25x.png b/sdkjs-plugins/content/texthighlighter/resources/store/icons/icon@1.25x.png
new file mode 100644
index 00000000..49379d80
Binary files /dev/null and b/sdkjs-plugins/content/texthighlighter/resources/store/icons/icon@1.25x.png differ
diff --git a/sdkjs-plugins/content/texthighlighter/resources/store/icons/icon@1.5x.png b/sdkjs-plugins/content/texthighlighter/resources/store/icons/icon@1.5x.png
new file mode 100644
index 00000000..1f4b3f7f
Binary files /dev/null and b/sdkjs-plugins/content/texthighlighter/resources/store/icons/icon@1.5x.png differ
diff --git a/sdkjs-plugins/content/texthighlighter/resources/store/icons/icon@1.75x.png b/sdkjs-plugins/content/texthighlighter/resources/store/icons/icon@1.75x.png
new file mode 100644
index 00000000..4aeeb439
Binary files /dev/null and b/sdkjs-plugins/content/texthighlighter/resources/store/icons/icon@1.75x.png differ
diff --git a/sdkjs-plugins/content/texthighlighter/resources/store/icons/icon@2x.png b/sdkjs-plugins/content/texthighlighter/resources/store/icons/icon@2x.png
new file mode 100644
index 00000000..4aeeb439
Binary files /dev/null and b/sdkjs-plugins/content/texthighlighter/resources/store/icons/icon@2x.png differ
diff --git a/sdkjs-plugins/content/texthighlighter/resources/store/screenshots/screen_1.png b/sdkjs-plugins/content/texthighlighter/resources/store/screenshots/screen_1.png
new file mode 100644
index 00000000..e100a3b0
Binary files /dev/null and b/sdkjs-plugins/content/texthighlighter/resources/store/screenshots/screen_1.png differ
diff --git a/sdkjs-plugins/content/texthighlighter/resources/store/screenshots/screen_2.png b/sdkjs-plugins/content/texthighlighter/resources/store/screenshots/screen_2.png
new file mode 100644
index 00000000..609fde79
Binary files /dev/null and b/sdkjs-plugins/content/texthighlighter/resources/store/screenshots/screen_2.png differ
diff --git a/sdkjs-plugins/content/texthighlighter/resources/store/screenshots/screen_3.png b/sdkjs-plugins/content/texthighlighter/resources/store/screenshots/screen_3.png
new file mode 100644
index 00000000..0815a6c7
Binary files /dev/null and b/sdkjs-plugins/content/texthighlighter/resources/store/screenshots/screen_3.png differ
diff --git a/sdkjs-plugins/content/texthighlighter/resources/store/screenshots/screen_4.png b/sdkjs-plugins/content/texthighlighter/resources/store/screenshots/screen_4.png
new file mode 100644
index 00000000..19cbfd79
Binary files /dev/null and b/sdkjs-plugins/content/texthighlighter/resources/store/screenshots/screen_4.png differ
diff --git a/sdkjs-plugins/content/texthighlighter/scripts/code.js b/sdkjs-plugins/content/texthighlighter/scripts/code.js
new file mode 100644
index 00000000..15935349
--- /dev/null
+++ b/sdkjs-plugins/content/texthighlighter/scripts/code.js
@@ -0,0 +1,320 @@
+(function (window) {
+ "use strict";
+
+ // === 1) Dropdown toggle bindings (run once on DOM load)
+ document.addEventListener("DOMContentLoaded", () => {
+ document.querySelectorAll(".dropdown-header").forEach((header) => {
+ header.addEventListener("click", () => {
+ header.parentElement.classList.toggle("open");
+ });
+ });
+ });
+
+ // 2) Theme change handler
+ function onThemeChanged(theme) {
+ // Let OnlyOffice apply its base styling
+ window.Asc.plugin.onThemeChangedBase(theme);
+
+ // Toggle our dark-mode class
+ document.body.classList.toggle("dark-mode", theme.type === "dark");
+ }
+ window.Asc.plugin.attachEvent?.("onThemeChanged", onThemeChanged);
+ window.Asc.plugin.onThemeChanged = onThemeChanged;
+
+ // UI elements (filled in init)
+ let searchInput, ignoreCaseBox, applyBtn;
+ let stateInput, stateNo, stateDone;
+ let loader, foundCountSpan;
+ let highlightMore1, highlightMore2, revertBtn;
+
+ window.Asc.plugin.init = function (text) {
+ // Cache DOM nodes
+ searchInput = document.getElementById("searchText");
+ ignoreCaseBox = document.getElementById("ignoreCase");
+ applyBtn = document.getElementById("ApplyButton");
+ stateInput = document.getElementById("state-input");
+ stateNo = document.getElementById("state-no-results");
+ stateDone = document.getElementById("state-done");
+ loader = document.getElementById("loader");
+ foundCountSpan = document.getElementById("foundCount");
+ highlightMore1 = document.getElementById("HighlightMore1");
+ highlightMore2 = document.getElementById("HighlightMore2");
+ revertBtn = document.getElementById("RevertToOriginal");
+
+ Asc.scope.textColor = "#000000";
+ const pickrEl = document.getElementById("textColorPicker");
+ if (pickrEl) {
+ const pickr = Pickr.create({
+ el: pickrEl,
+ theme: "classic",
+ default: "#000000",
+ components: {
+ preview: true,
+ opacity: true,
+ hue: true,
+ interaction: {
+ hex: true,
+ rgba: false,
+ input: true,
+ clear: false,
+ save: true,
+ },
+ },
+ });
+
+ pickr.on("init", (instance) => {
+ const hex = instance.getColor().toHEXA().toString();
+ Asc.scope.textColor = hex;
+ Asc.scope.lastTxtColor = hex;
+ });
+
+ pickr.on("save", (color) => {
+ const hex = color.toHEXA().toString();
+ Asc.scope.textColor = hex;
+ Asc.scope.lastTxtColor = hex;
+ pickr.hide();
+ });
+ }
+
+ // Simple state-switchers
+ function showInput() {
+ stateInput.style.display = "";
+ stateNo.style.display = "none";
+ stateDone.style.display = "none";
+ loader.style.display = "none";
+ }
+
+ // Wire up UI
+ applyBtn.disabled = true;
+ searchInput.addEventListener("input", () => {
+ applyBtn.disabled = !searchInput.value.trim();
+ Asc.scope.appliedToSelection = false;
+ });
+
+ applyBtn.addEventListener("click", onApply);
+ highlightMore1.addEventListener("click", showInput);
+ highlightMore2.addEventListener("click", showInput);
+ revertBtn.addEventListener("click", onRevert);
+
+ // If the plugin was opened with a selection, use it
+ if (text && text.trim()) {
+ searchInput.value = text.trim();
+ applyBtn.disabled = false;
+ }
+
+ // React when user changes selection in the document
+ if (window.Asc.plugin.attachEvent) {
+ window.Asc.plugin.attachEvent("onSelectionChanged", (sel) => {
+ if (sel && sel.text) {
+ searchInput.value = sel.text;
+ applyBtn.disabled = false;
+ }
+ });
+ }
+
+ // Initialize last-term storage
+ Asc.scope.lastTerm = "";
+ Asc.scope.lastCaseSens = false;
+
+ // Show the initial state
+ showInput();
+ };
+
+ // 3) Apply highlights
+ function onApply() {
+ // Reset undo count for each new apply action
+ Asc.scope.undoCount = 1;
+
+ const caseSens = !ignoreCaseBox.checked;
+ const hlColor = document.getElementById("highlightColor").value;
+ const txtColor = Asc.scope.textColor || "#000000";
+ const doBold = document.getElementById("boldCheckbox").checked;
+ const doItalic = document.getElementById("italicCheckbox").checked;
+ const doUnder = document.getElementById("underlineCheckbox").checked;
+ const doStrike = document.getElementById("strikeCheckbox").checked;
+
+ // Store properties in Asc.scope
+ Asc.scope.caseSens = caseSens;
+ Asc.scope.hlColor = hlColor;
+ Asc.scope.txtColor = txtColor;
+ Asc.scope.doBold = doBold;
+ Asc.scope.doItalic = doItalic;
+ Asc.scope.doUnder = doUnder;
+ Asc.scope.doStrike = doStrike;
+ Asc.scope.lastTerm = searchInput.value.trim();
+
+ // Transition UI
+ stateInput.style.display = "none";
+ stateNo.style.display = "none";
+ stateDone.style.display = "none";
+ loader.style.display = "";
+
+ window.Asc.plugin.callCommand(function () {
+ const doc = Api.GetDocument();
+ const core = doc.GetCore();
+ core.SetLanguage("fr-FR");
+
+ const language = core.GetLanguage();
+
+ const range = doc.GetRangeBySelect();
+ const textPr = Api.CreateTextPr();
+
+ // Set all text properties
+ textPr.SetHighlight(Asc.scope.hlColor);
+ if (Asc.scope.doBold) textPr.SetBold(true);
+ if (Asc.scope.doItalic) textPr.SetItalic(true);
+ if (Asc.scope.doUnder) textPr.SetUnderline(true);
+ if (Asc.scope.doStrike) textPr.SetStrikeout(true);
+
+ if (Asc.scope.txtColor !== "#000000") {
+ const rgb = Asc.scope.txtColor
+ .slice(1)
+ .match(/.{2}/g)
+ .map((h) => parseInt(h, 16));
+ textPr.SetColor(rgb[0], rgb[1], rgb[2], false);
+ }
+
+ if (range && range.GetText && range.GetText() !== "") {
+ // Apply to selected range
+ range.SetTextPr(textPr);
+ Asc.scope.appliedViaSelection = true;
+ return 1;
+ } else if (Asc.scope.lastTerm) {
+ // Search mode if no selection
+ const results = doc.Search(Asc.scope.lastTerm, Asc.scope.caseSens);
+ results.forEach((result) => {
+ result.SetTextPr(textPr);
+ });
+ Asc.scope.appliedViaSelection = false;
+ return results.length;
+ } else {
+ // Apply to entire document
+ const paragraphs = doc.GetAllParagraphs();
+ paragraphs.forEach((para) => {
+ para.SetTextPr(textPr);
+ });
+ Asc.scope.appliedViaSelection = true;
+ return paragraphs.length;
+ }
+ }, false);
+ }
+
+ // 4) Revert highlights
+
+ function onRevert() {
+ // If nothing was applied, exit safely
+ if (!Asc.scope.undoCount || Asc.scope.undoCount <= 0) {
+ resetToMainView();
+ return;
+ }
+
+ loader.style.display = "";
+
+ const performUndo = (stepsRemaining) => {
+ if (stepsRemaining <= 0) {
+ // Done undoing → clean state
+ Asc.scope.undoCount = 0;
+ Asc.scope.lastTerm = "";
+ Asc.scope.appliedViaSelection = false;
+
+ loader.style.display = "none";
+
+ // Show clean UI again
+ resetToMainView();
+ return;
+ }
+
+ // Perform 1 undo → then recursively undo the rest
+ window.Asc.plugin.executeMethod("Undo", null, () => {
+ setTimeout(() => performUndo(stepsRemaining - 1), 100);
+ });
+ };
+
+ // Start the undo chain
+ performUndo(Asc.scope.undoCount);
+ }
+ function resetToMainView() {
+ stateInput.style.display = "";
+ stateNo.style.display = "none";
+ stateDone.style.display = "none";
+ loader.style.display = "none";
+ }
+
+ // 5) After each callCommand
+ window.Asc.plugin.onCommandCallback = function (count) {
+ const n = Number(count) || 0;
+ loader.style.display = "none";
+
+ if (n === 0) {
+ stateNo.style.display = "";
+ stateDone.style.display = "none";
+ stateInput.style.display = "none";
+ } else if (Asc.scope.appliedViaSelection) {
+ // show input again, so user can immediately re‐apply or revert
+ stateInput.style.display = "none";
+ stateDone.style.display = "";
+ stateNo.style.display = "";
+ } else {
+ // regular search‐based Done UI
+ foundCountSpan.textContent = n;
+ stateDone.style.display = "";
+ stateInput.style.display = "none";
+ stateNo.style.display = "none";
+ }
+ };
+
+ Asc.scope.undoCount = 1; // one full operation to undo
+
+ // 6) Translation hookup
+ window.Asc.plugin.onTranslate = function () {
+ const $ = (id) => document.getElementById(id);
+ const trSafe = (key) => {
+ const t = window.Asc.plugin.tr(key);
+ return t && t !== key ? t : null; // null → fallback to English text from HTML
+ };
+
+ // Simple text replacements
+ const applyTr = (id) => {
+ const el = $(id);
+ if (!el) return;
+ const translated = trSafe(id);
+ if (translated) el.innerHTML = translated; // fallback happens automatically
+ };
+
+ // IDs to translate
+ const idsToTranslate = [
+ "PluginInstructions",
+ "TextToSearch",
+ "IgnoreCase",
+ "HighlightColor",
+ "HighlightYellow",
+ "HighlightGreen",
+ "HighlightBlue",
+ "HighlightRed",
+ "HighlightNone",
+ "TextColorHeader",
+ "TextFormattingHeader",
+ "FormatBold",
+ "FormatItalic",
+ "FormatUnderline",
+ "FormatStrike",
+ "ApplyButton",
+ "SearchNoResults",
+ "SearchDone",
+ "MatchesFound",
+ "HighlightMore1",
+ "HighlightMore2",
+ "RevertToOriginal",
+ "LoadingMessage",
+ ];
+
+ idsToTranslate.forEach(applyTr);
+
+ // Placeholder translation
+ const input = $("searchText");
+ if (input) {
+ const placeholder = trSafe("SearchPlaceholder");
+ if (placeholder) input.placeholder = placeholder;
+ }
+ };
+})(window);
diff --git a/sdkjs-plugins/content/texthighlighter/styles.css b/sdkjs-plugins/content/texthighlighter/styles.css
new file mode 100644
index 00000000..a1a90da2
--- /dev/null
+++ b/sdkjs-plugins/content/texthighlighter/styles.css
@@ -0,0 +1,329 @@
+body {
+ background: #fff;
+ overflow-y: scroll;
+
+ color: #333;
+}
+
+.container {
+ padding: 13px;
+ overflow-y: auto;
+ height: 95vh;
+}
+
+.container::-webkit-scrollbar {
+ display: none;
+}
+
+.header {
+ font-size: 0.8em;
+ margin-top: 10px;
+ font-family: Arial, Helvetica, sans-serif;
+ margin-bottom: 20px;
+}
+
+/* */
+#highlightColor {
+ color: black;
+ padding: 0.2em;
+ max-width: 80px;
+ font-size: 0.8em;
+ border: 1px solid #ccc;
+ border-radius: 4px;
+ background-color: #f9f9f9;
+ cursor: pointer;
+}
+
+#highlightColor:hover {
+ border-color: #aaa;
+}
+
+#highlightColor option {
+ padding: 0.5em;
+ background-color: #fff;
+ color: #252525;
+}
+
+#highlightColor option:hover {
+ background-color: #ddd;
+}
+
+#highlightColor option[value="yellow"] {
+ background-color: #ffffcc;
+}
+
+#highlightColor option[value="green"] {
+ background-color: #ccffcc;
+}
+
+#highlightColor option[value="blue"] {
+ background-color: #4781d1;
+}
+
+#highlightColor option[value="red"] {
+ background-color: #ffcccc;
+}
+
+
+/* */
+/* Hide scrollbar for IE, Edge and Firefox */
+.container {
+ -ms-overflow-style: none;
+ /* IE and Edge */
+ scrollbar-width: none;
+ /* Firefox */
+}
+
+.container p {
+ font-size: 12px;
+ margin: 10px 0;
+ text-align: center;
+}
+
+#state-no-results button,
+#state-done button {
+ width: 100%;
+ margin-top: 8px;
+}
+
+#state-done p:first-child {
+ font-weight: bold;
+}
+
+/* Dropdown panel wrapper */
+.dropdown {
+ border: 1px solid #ddd;
+ border-radius: 4px;
+ margin-bottom: 12px;
+ overflow: hidden;
+}
+
+/* Header always visible */
+.dropdown-header {
+ padding: 10px;
+ background: #f9f9f9;
+ cursor: pointer;
+ position: relative;
+ font-weight: bold;
+}
+
+/* The little arrow */
+.dropdown-header .arrow {
+ position: absolute;
+ right: 12px;
+ transition: transform 0.2s;
+}
+
+/* Collapsed by default */
+.dropdown-content {
+ max-height: 0;
+ overflow: hidden;
+ transition: max-height 0.3s ease;
+ background: #fff;
+ padding: 0 10px;
+}
+
+/* When open, allow comfortable height */
+.dropdown.open .dropdown-content {
+ max-height: 150px;
+ /* adjust if you need more space */
+ padding: 10px;
+}
+
+/* Rotate arrow when open */
+.dropdown.open .dropdown-header .arrow {
+ transform: rotate(90deg);
+}
+
+/* Formatting options laid out */
+.format-options {
+ display: flex;
+ flex-wrap: wrap;
+ gap: 8px;
+}
+
+.format-options label {
+ display: flex;
+ align-items: center;
+ gap: 4px;
+ background: #f0f0f0;
+ padding: 6px 10px;
+ border-radius: 4px;
+ cursor: pointer;
+}
+
+
+.section {
+ margin-bottom: 10px;
+ padding: 0 2px 12px 0;
+
+}
+
+.section label {
+ font-weight: bold;
+ align-items: center;
+ font-size: 15px;
+ margin-bottom: 6px;
+}
+
+input[type="text"] {
+ width: 100%;
+ border-radius: 4px;
+ margin-top: 6px;
+ padding: 8px;
+ box-sizing: border-box;
+}
+
+.format-options {
+ display: flex;
+ flex-wrap: wrap;
+ gap: 10px;
+}
+
+.format-option {
+ display: flex;
+ align-items: center;
+ padding: 5px 10px;
+ background: #f0f0f0;
+ border-radius: 4px;
+ cursor: pointer;
+}
+
+.format-option:hover {
+ background: #e0e0e0;
+}
+
+.format-option.active {
+ background: #446995;
+ color: white;
+}
+
+.applyButton {
+ background: #446995;
+ color: white;
+ border: none;
+ padding: 10px 20px;
+ cursor: pointer;
+ border-radius: 4px;
+ font-weight: bold;
+ width: 100%;
+ margin-top: 18px;
+ margin-bottom: 15px;
+}
+
+.applyButton:disabled {
+ background: #cccccc;
+ cursor: not-allowed;
+}
+
+.message {
+ margin-top: 15px;
+ padding: 10px;
+ border-radius: 4px;
+ text-align: center;
+}
+
+#loader {
+ text-align: center;
+ font-style: italic;
+ color: #666;
+}
+
+body.dark-mode {
+ background: #404040;
+ color: #ddd;
+}
+
+body.dark-mode input[type="text"],
+body.dark-mode select,
+body.dark-mode .dropdown-content {
+ background: #2a2a2a;
+ color: #ddd;
+ border-color: #444;
+}
+
+body.dark-mode .dropdown-header {
+ background: #2a2a2a;
+ color: #ddd;
+ border-color: #444;
+}
+
+body.dark-mode .dropdown.open .dropdown-content {
+ background: #2a2a2a;
+}
+
+body.dark-mode .format-options label {
+ background: #333;
+ color: #eee;
+}
+
+body.dark-mode .format-options label:hover {
+ background: #3a3a3a;
+}
+
+body.dark-mode button {
+ background: #fff !important;
+ opacity: 0.8 !important;
+ color: #333 !important;
+}
+
+body.dark-mode button:disabled {
+ background: #fff !important;
+ opacity: 0.5 !important;
+ color: #333 !important;
+}
+
+body.dark-mode .loader {
+ color: #aaa;
+}
+
+/* ─── Desktop/wide screens ─────────────────────────────────── */
+@media (min-width: 1300px) {
+ .container {
+ max-width: 500px;
+ /* widen the panel */
+ font-size: 0.9rem;
+ /* bump up base text */
+ padding: 18px;
+ }
+
+ h3 {
+ font-size: 1.1rem;
+ margin-bottom: 1rem;
+ }
+
+ input[type="text"],
+ select {
+ font-size: 0.8rem;
+ padding: 10px;
+ }
+
+ .dropdown-header {
+ font-size: 1rem;
+ padding: 12px;
+ }
+
+ .dropdown-content input,
+ .format-options label {
+ font-size: 0.85rem;
+ }
+
+ .applyButton,
+ #state-no-results button,
+ #state-done button {
+ font-size: 0.85rem;
+ padding: 12px;
+ }
+
+ /* Make the container scroll if it grows taller than the sidebar */
+}
+
+#textColorPicker {
+ width: 100%;
+ height: 32px;
+ border: 1px solid #444;
+ border-radius: 4px;
+ cursor: pointer;
+ background: #000;
+ /* initial default color swatch */
+}
\ No newline at end of file
diff --git a/sdkjs-plugins/content/texthighlighter/translations/cs-CS.json b/sdkjs-plugins/content/texthighlighter/translations/cs-CS.json
new file mode 100644
index 00000000..1c9b5214
--- /dev/null
+++ b/sdkjs-plugins/content/texthighlighter/translations/cs-CS.json
@@ -0,0 +1,42 @@
+{
+ "TextHighlighter": "Zvýrazňovač textu",
+ "PluginInstructions": "Zadejte nebo vyberte slovo nebo frázi pro hledání v textu dokumentu a nakonfigurujte potřebná nastavení.",
+
+ "TextToSearch": "Text k vyhledání:",
+ "SearchPlaceholder": "Zadejte text nebo vyberte v dokumentu",
+ "IgnoreCase": "Ignorovat velikost písmen",
+
+ "HighlightColor": "Barva zvýraznění:",
+ "HighlightYellow": "Žlutá",
+ "HighlightGreen": "Zelená",
+ "HighlightBlue": "Modrá",
+ "HighlightRed": "Červená",
+ "HighlightNone": "Bez výplně",
+
+ "TextColorHeader": "Barva textu",
+ "ArrowDropdown": "▸",
+ "TextColorPicker": "Vyberte barvu textu",
+
+ "TextFormattingHeader": "Formátování textu",
+ "FormatBold": "Tučné",
+ "FormatItalic": "Kurzíva",
+ "FormatUnderline": "Podtržení",
+ "FormatStrike": "Přeškrtnutí",
+
+ "ApplyButton": "Použít",
+
+ "SearchNoResults": "Hledání dokončeno. Nebyly nalezeny žádné shody. ",
+ "HighlightMore": "Zvýraznit více",
+ "HighlightMore2": "Zvýraznit více",
+
+ "SearchDone": "Hotovo! Hledání bylo dokončeno a styly byly aplikovány.",
+ "MatchesFound": "Nalezené shody:",
+ "RevertToOriginal": "Vrátit zpět",
+
+ "LoadingMessage": "Zpracovávám...",
+
+ "AboutTitle": "Zvýrazňovač textu",
+ "AboutVersion": "Verze 1.0.0",
+ "AboutDescription": "Tento plugin umožňuje vyhledávat text a aplikovat zvýraznění, barvu a formátování ve vašem dokumentu.",
+ "AboutCopyright": "© 2025 ONLYOFFICE Plugin"
+}
diff --git a/sdkjs-plugins/content/texthighlighter/translations/de-DE.json b/sdkjs-plugins/content/texthighlighter/translations/de-DE.json
new file mode 100644
index 00000000..0734e115
--- /dev/null
+++ b/sdkjs-plugins/content/texthighlighter/translations/de-DE.json
@@ -0,0 +1,42 @@
+{
+ "TextHighlighter": "Texthervorhebung",
+ "PluginInstructions": "Geben Sie ein Wort oder einen Satz ein oder wählen Sie es aus dem Dokument aus, und konfigurieren Sie die gewünschten Einstellungen.",
+
+ "TextToSearch": "Zu suchender Text:",
+ "SearchPlaceholder": "Text eingeben oder im Dokument auswählen",
+ "IgnoreCase": "Groß-/Kleinschreibung ignorieren",
+
+ "HighlightColor": "Hervorhebungsfarbe:",
+ "HighlightYellow": "Gelb",
+ "HighlightGreen": "Grün",
+ "HighlightBlue": "Blau",
+ "HighlightRed": "Rot",
+ "HighlightNone": "Keine Füllung",
+ "ArrowDropdown": "▸",
+
+ "TextColorHeader": "Textfarbe",
+ "TextColorPicker": "Textfarbe wählen",
+
+ "TextFormattingHeader": "Textformatierung",
+ "FormatBold": "Fett",
+ "FormatItalic": "Kursiv",
+ "FormatUnderline": "Unterstrichen",
+ "FormatStrike": "Durchgestrichen",
+
+ "ApplyButton": "Anwenden",
+
+ "SearchNoResults": "Suche abgeschlossen. Keine Treffer gefunden. ",
+ "HighlightMore": "Mehr hervorheben",
+ "HighlightMore2": "Mehr hervorheben",
+
+ "SearchDone": "Fertig! Die Suche ist abgeschlossen und die Stile wurden angewendet.",
+ "MatchesFound": "Gefundene Treffer:",
+ "RevertToOriginal": "Wiederherstellen",
+
+ "LoadingMessage": "Verarbeitung läuft...",
+
+ "AboutTitle": "Texthervorhebung",
+ "AboutVersion": "Version 1.0.0",
+ "AboutDescription": "Dieses Plugin ermöglicht das Suchen von Text und das Anwenden von Hervorhebungen, Farben und Formatierungen im Dokument.",
+ "AboutCopyright": "© 2025 ONLYOFFICE-Plugin"
+}
diff --git a/sdkjs-plugins/content/texthighlighter/translations/en-US.json b/sdkjs-plugins/content/texthighlighter/translations/en-US.json
new file mode 100644
index 00000000..e28b17b0
--- /dev/null
+++ b/sdkjs-plugins/content/texthighlighter/translations/en-US.json
@@ -0,0 +1,42 @@
+{
+ "TextHighlighter": "Text Highlighter",
+ "PluginInstructions": "Enter or select a word or phrase to search in the document text, and configure the necessary settings.",
+
+ "TextToSearch": "Text to search:",
+ "SearchPlaceholder": "Enter text or select in document",
+ "IgnoreCase": "Ignore case",
+
+ "HighlightColor": "Highlight color :",
+ "HighlightYellow": "Yellow",
+ "HighlightGreen": "Green",
+ "HighlightBlue": "Blue",
+ "HighlightRed": "Red",
+ "HighlightNone": "No Fill",
+ "ArrowDropdown": "▸",
+
+ "TextColorHeader": "Text color",
+ "TextColorPicker": "Choose text color",
+
+ "TextFormattingHeader": "Text formatting",
+ "FormatBold": "Bold",
+ "FormatItalic": "Italic",
+ "FormatUnderline": "Underline",
+ "FormatStrike": "Strikethrough",
+
+ "ApplyButton": "Apply",
+
+ "SearchNoResults": "Search completed. No matches found. ",
+ "HighlightMore": "Highlight more",
+ "HighlightMore2": "Highlight more",
+
+ "SearchDone": "Done! The search is complete and styles have been applied.",
+ "MatchesFound": "Matches found:",
+ "RevertToOriginal": "Revert",
+
+ "LoadingMessage": "Processing...",
+
+ "AboutTitle": "Text Highlighter",
+ "AboutVersion": "Version 1.0.0",
+ "AboutDescription": "This plugin allows you to search text and apply highlighting, color, and formatting styles in your document.",
+ "AboutCopyright": "© 2025 ONLYOFFICE Plugin"
+}
diff --git a/sdkjs-plugins/content/texthighlighter/translations/es-ES.json b/sdkjs-plugins/content/texthighlighter/translations/es-ES.json
new file mode 100644
index 00000000..4fede6e1
--- /dev/null
+++ b/sdkjs-plugins/content/texthighlighter/translations/es-ES.json
@@ -0,0 +1,42 @@
+{
+ "TextHighlighter": "Resaltador de Texto",
+ "PluginInstructions": "Introduce o selecciona una palabra o frase para buscar en el texto del documento, y configura los ajustes necesarios.",
+
+ "TextToSearch": "Texto a buscar:",
+ "SearchPlaceholder": "Introduce texto o selecciona en el documento",
+ "IgnoreCase": "Ignorar mayúsculas/minúsculas",
+
+ "HighlightColor": "Color de resaltado:",
+ "HighlightYellow": "Amarillo",
+ "HighlightGreen": "Verde",
+ "HighlightBlue": "Azul",
+ "HighlightRed": "Rojo",
+ "HighlightNone": "Sin relleno",
+ "ArrowDropdown": "▸",
+
+ "TextColorHeader": "Color del texto",
+ "TextColorPicker": "Elige el color del texto",
+
+ "TextFormattingHeader": "Formato del texto",
+ "FormatBold": "Negrita",
+ "FormatItalic": "Cursiva",
+ "FormatUnderline": "Subrayado",
+ "FormatStrike": "Tachado",
+
+ "ApplyButton": "Aplicar",
+
+ "SearchNoResults": "Búsqueda completada. No se encontraron coincidencias. ",
+ "HighlightMore": "Resaltar más",
+ "HighlightMore2": "Resaltar más",
+
+ "SearchDone": "¡Hecho! La búsqueda se completó y se aplicaron los estilos.",
+ "MatchesFound": "Coincidencias encontradas:",
+ "RevertToOriginal": "Revertir",
+
+ "LoadingMessage": "Procesando...",
+
+ "AboutTitle": "Resaltador de Texto",
+ "AboutVersion": "Versión 1.0.0",
+ "AboutDescription": "Este complemento te permite buscar texto y aplicar resaltado, color y estilos de formato en tu documento.",
+ "AboutCopyright": "© 2025 Complemento ONLYOFFICE"
+}
diff --git a/sdkjs-plugins/content/texthighlighter/translations/fr-FR.json b/sdkjs-plugins/content/texthighlighter/translations/fr-FR.json
new file mode 100644
index 00000000..b9f83546
--- /dev/null
+++ b/sdkjs-plugins/content/texthighlighter/translations/fr-FR.json
@@ -0,0 +1,41 @@
+{
+ "TextHighlighter": "Surligneur de texte",
+ "PluginInstructions": "Entrez ou sélectionnez un mot ou une expression à rechercher dans le texte du document, puis configurez les paramètres nécessaires.",
+
+ "TextToSearch": "Texte à rechercher :",
+ "SearchPlaceholder": "Entrez du texte ou sélectionnez-le dans le document",
+ "IgnoreCase": "Ignorer la casse",
+
+ "HighlightColor": "Couleur de surlignage :",
+ "HighlightYellow": "Jaune",
+ "HighlightGreen": "Vert",
+ "HighlightBlue": "Bleu",
+ "HighlightRed": "Rouge",
+ "HighlightNone": "Pas de remplissage",
+"ArrowDropdown": "▸",
+ "TextColorHeader": "Couleur du texte",
+ "TextColorPicker": "Choisir la couleur du texte",
+
+ "TextFormattingHeader": "Formatage du texte",
+ "FormatBold": "Gras",
+ "FormatItalic": "Italique",
+ "FormatUnderline": "Souligné",
+ "FormatStrike": "Barré",
+
+ "ApplyButton": "Appliquer",
+
+ "SearchNoResults": "Recherche terminée. Aucun résultat trouvé. ",
+ "HighlightMore": "Surligner plus",
+ "HighlightMore2": "Surligner plus",
+
+ "SearchDone": "Terminé ! La recherche est terminée et les styles ont été appliqués.",
+ "MatchesFound": "Correspondances trouvées :",
+ "RevertToOriginal": "Revenir",
+
+ "LoadingMessage": "Traitement en cours...",
+
+ "AboutTitle": "Surligneur de texte",
+ "AboutVersion": "Version 1.0.0",
+ "AboutDescription": "Ce plugin permet de rechercher du texte et d'appliquer des styles de surlignage, de couleur et de formatage dans le document.",
+ "AboutCopyright": "© 2025 Plugin ONLYOFFICE"
+}
diff --git a/sdkjs-plugins/content/texthighlighter/translations/it-IT.json b/sdkjs-plugins/content/texthighlighter/translations/it-IT.json
new file mode 100644
index 00000000..d1c6a961
--- /dev/null
+++ b/sdkjs-plugins/content/texthighlighter/translations/it-IT.json
@@ -0,0 +1,42 @@
+{
+ "TextHighlighter": "Evidenziatore di Testo",
+ "PluginInstructions": "Inserisci o seleziona una parola o frase da cercare nel testo del documento e configura le impostazioni necessarie.",
+
+ "TextToSearch": "Testo da cercare:",
+ "SearchPlaceholder": "Inserisci testo o seleziona nel documento",
+ "IgnoreCase": "Ignora maiuscole/minuscole",
+
+ "HighlightColor": "Colore evidenziazione:",
+ "HighlightYellow": "Giallo",
+ "HighlightGreen": "Verde",
+ "HighlightBlue": "Blu",
+ "HighlightRed": "Rosso",
+ "HighlightNone": "Nessun riempimento",
+
+ "TextColorHeader": "Colore del testo",
+ "TextColorPicker": "Scegli colore del testo",
+ "ArrowDropdown": "▸",
+
+ "TextFormattingHeader": "Formattazione testo",
+ "FormatBold": "Grassetto",
+ "FormatItalic": "Corsivo",
+ "FormatUnderline": "Sottolineato",
+ "FormatStrike": "Barrato",
+
+ "ApplyButton": "Applica",
+
+ "SearchNoResults": "Ricerca completata. Nessuna corrispondenza trovata. ",
+ "HighlightMore": "Evidenzia di più",
+ "HighlightMore2": "Evidenzia di più",
+
+ "SearchDone": "Fatto! La ricerca è completa e gli stili sono stati applicati.",
+ "MatchesFound": "Corrispondenze trovate:",
+ "RevertToOriginal": "Ripristina",
+
+ "LoadingMessage": "Elaborazione...",
+
+ "AboutTitle": "Evidenziatore di Testo",
+ "AboutVersion": "Versione 1.0.0",
+ "AboutDescription": "Questo plugin consente di cercare il testo e applicare evidenziazioni, colori e formattazioni nel documento.",
+ "AboutCopyright": "© 2025 Plugin ONLYOFFICE"
+}
diff --git a/sdkjs-plugins/content/texthighlighter/translations/ja-JA.json b/sdkjs-plugins/content/texthighlighter/translations/ja-JA.json
new file mode 100644
index 00000000..236f0a88
--- /dev/null
+++ b/sdkjs-plugins/content/texthighlighter/translations/ja-JA.json
@@ -0,0 +1,42 @@
+{
+ "TextHighlighter": "テキストハイライター",
+ "PluginInstructions": "検索する単語またはフレーズ を入力または選択し、必要な設定を構成します。",
+
+ "TextToSearch": "検索するテキスト:",
+ "SearchPlaceholder": "テキストを入力またはドキュメントから選択",
+ "IgnoreCase": "大文字と小文字を区別しない",
+
+ "HighlightColor": "ハイライトの色:",
+ "HighlightYellow": "黄色",
+ "HighlightGreen": "緑",
+ "HighlightBlue": "青",
+ "HighlightRed": "赤",
+ "HighlightNone": "塗りなし",
+ "ArrowDropdown": "▸",
+
+ "TextColorHeader": "テキストの色",
+ "TextColorPicker": "テキストの色を選択",
+
+ "TextFormattingHeader": "テキストの書式",
+ "FormatBold": "太字",
+ "FormatItalic": "斜体",
+ "FormatUnderline": "下線",
+ "FormatStrike": "取り消し線",
+
+ "ApplyButton": "適用",
+
+ "SearchNoResults": "検索完了。一致するものは見つかりませんでした。 ",
+ "HighlightMore": "さらにハイライト",
+ "HighlightMore2": "さらにハイライト",
+
+ "SearchDone": "完了!検索が完了し、スタイルが適用されました。",
+ "MatchesFound": "一致件数:",
+ "RevertToOriginal": "元に戻す",
+
+ "LoadingMessage": "処理中...",
+
+ "AboutTitle": "テキストハイライター",
+ "AboutVersion": "バージョン 1.0.0",
+ "AboutDescription": "このプラグインでは、テキストの検索およびハイライト、色、書式スタイルの適用が可能です。",
+ "AboutCopyright": "© 2025 ONLYOFFICE プラグイン"
+}
diff --git a/sdkjs-plugins/content/texthighlighter/translations/langs.json b/sdkjs-plugins/content/texthighlighter/translations/langs.json
new file mode 100644
index 00000000..0b245632
--- /dev/null
+++ b/sdkjs-plugins/content/texthighlighter/translations/langs.json
@@ -0,0 +1,13 @@
+[
+ "ru-RU",
+ "de-DE",
+ "fr-FR",
+ "es-ES",
+ "pt-BR",
+ "it-IT",
+ "ja-JA",
+ "zh-ZH",
+ "cs-CS",
+ "sq-AL",
+ "uk-UA"
+]
\ No newline at end of file
diff --git a/sdkjs-plugins/content/texthighlighter/translations/pt-BR.json b/sdkjs-plugins/content/texthighlighter/translations/pt-BR.json
new file mode 100644
index 00000000..155931dd
--- /dev/null
+++ b/sdkjs-plugins/content/texthighlighter/translations/pt-BR.json
@@ -0,0 +1,33 @@
+{
+ "TextHighlighter": "Realçador de Texto",
+ "PluginInstructions": "Digite ou selecione uma palavra ou frase para buscar no texto do documento e configure as opções necessárias.",
+ "TextToSearch": "Texto para buscar:",
+ "SearchPlaceholder": "Digite ou selecione no documento",
+ "IgnoreCase": "Ignorar maiúsculas/minúsculas",
+ "HighlightColor": "Cor do destaque:",
+ "HighlightYellow": "Amarelo",
+ "HighlightGreen": "Verde",
+ "HighlightBlue": "Azul",
+ "HighlightRed": "Vermelho",
+ "HighlightNone": "Sem preenchimento",
+ "TextColorHeader": "Cor do texto",
+ "TextColorPicker": "Escolher cor do texto",
+ "TextFormattingHeader": "Formatação do texto",
+ "FormatBold": "Negrito",
+ "FormatItalic": "Itálico",
+ "FormatUnderline": "Sublinhado",
+ "FormatStrike": "Tachado",
+ "ApplyButton": "Aplicar",
+ "SearchNoResults": "Busca concluída. Nenhuma correspondência encontrada. ",
+ "HighlightMore": "Realçar mais",
+ "HighlightMore2": "Realçar mais",
+ "ArrowDropdown": "▸",
+ "SearchDone": "Concluído! A busca foi finalizada e os estilos foram aplicados.",
+ "MatchesFound": "Correspondências encontradas:",
+ "RevertToOriginal": "Reverter",
+ "LoadingMessage": "Processando...",
+ "AboutTitle": "Realçador de Texto",
+ "AboutVersion": "Versão 1.0.0",
+ "AboutDescription": "Este plugin permite buscar texto e aplicar realce, cor e estilos de formatação no seu documento.",
+ "AboutCopyright": "© 2025 Plugin ONLYOFFICE"
+}
\ No newline at end of file
diff --git a/sdkjs-plugins/content/texthighlighter/translations/ru-RU.json b/sdkjs-plugins/content/texthighlighter/translations/ru-RU.json
new file mode 100644
index 00000000..754c58d2
--- /dev/null
+++ b/sdkjs-plugins/content/texthighlighter/translations/ru-RU.json
@@ -0,0 +1,33 @@
+{
+ "TextHighlighter": "Выделение текста",
+ "PluginInstructions": "Введите или выберите слово или фразу для поиска в тексте документа и настройте необходимые параметры.",
+ "TextToSearch": "Текст для поиска:",
+ "SearchPlaceholder": "Введите текст или выберите в документе",
+ "IgnoreCase": "Игнорировать регистр",
+ "HighlightColor": "Цвет выделения:",
+ "HighlightYellow": "Жёлтый",
+ "HighlightGreen": "Зелёный",
+ "HighlightBlue": "Синий",
+ "HighlightRed": "Красный",
+ "HighlightNone": "Без заливки",
+ "TextColorHeader": "Цвет текста",
+ "TextColorPicker": "Выбрать цвет текста",
+ "TextFormattingHeader": "Форматирование текста",
+ "FormatBold": "Жирный",
+ "FormatItalic": "Курсив",
+ "FormatUnderline": "Подчёркнутый",
+ "FormatStrike": "Зачёркнутый",
+ "ApplyButton": "Применить",
+ "SearchNoResults": "Поиск завершён. Совпадения не найдены. ",
+ "HighlightMore": "Выделить ещё",
+ "HighlightMore2": "Выделить ещё",
+ "ArrowDropdown": "▸",
+ "SearchDone": "Готово! Поиск завершён, стили применены.",
+ "MatchesFound": "Найдено совпадений:",
+ "RevertToOriginal": "Вернуть",
+ "LoadingMessage": "Обработка...",
+ "AboutTitle": "Выделение текста",
+ "AboutVersion": "Версия 1.0.0",
+ "AboutDescription": "Этот плагин позволяет искать текст и применять стили выделения, цвета и форматирования в документе.",
+ "AboutCopyright": "© 2025 Плагин ONLYOFFICE"
+}
\ No newline at end of file
diff --git a/sdkjs-plugins/content/texthighlighter/translations/sq-AL.json b/sdkjs-plugins/content/texthighlighter/translations/sq-AL.json
new file mode 100644
index 00000000..3a768c87
--- /dev/null
+++ b/sdkjs-plugins/content/texthighlighter/translations/sq-AL.json
@@ -0,0 +1,42 @@
+{
+ "TextHighlighter": "Theksues Teksti",
+ "PluginInstructions": "Shkruani ose zgjidhni një fjalë ose frazë për të kërkuar në tekstin e dokumentit dhe konfiguroni cilësimet e nevojshme.",
+
+ "TextToSearch": "Teksti për të kërkuar:",
+ "SearchPlaceholder": "Shkruani tekstin ose zgjidhni në dokument",
+ "IgnoreCase": "Injoro shkronjat e mëdha/vogla",
+
+ "HighlightColor": "Ngjyra e theksimit:",
+ "HighlightYellow": "E verdhë",
+ "HighlightGreen": "E gjelbër",
+ "HighlightBlue": "Blu",
+ "HighlightRed": "E kuqe",
+ "HighlightNone": "Pa mbushje",
+
+ "TextColorHeader": "Ngjyra e tekstit",
+ "TextColorPicker": "Zgjidh ngjyrën e tekstit",
+
+ "TextFormattingHeader": "Formatimi i tekstit",
+ "FormatBold": "Trashë",
+ "FormatItalic": "Pjerrët",
+ "FormatUnderline": "Nënvizuar",
+ "FormatStrike": "Me vijë në mes",
+
+ "ApplyButton": "Zbato",
+
+ "SearchNoResults": "Kërkimi përfundoi. Asnjë përputhje nuk u gjet. ",
+ "HighlightMore": "Thekso më shumë",
+ "HighlightMore2": "Thekso më shumë",
+ "ArrowDropdown": "▸",
+
+ "SearchDone": "U krye! Kërkimi përfundoi dhe stilet u aplikuan.",
+ "MatchesFound": "Përputhje të gjetura:",
+ "RevertToOriginal": "Kthehu",
+
+ "LoadingMessage": "Duke përpunuar...",
+
+ "AboutTitle": "Theksues Teksti",
+ "AboutVersion": "Versioni 1.0.0",
+ "AboutDescription": "Ky shtesë ju lejon të kërkoni tekst dhe të aplikoni theksim, ngjyra dhe formate në dokumentin tuaj.",
+ "AboutCopyright": "© 2025 ONLYOFFICE Plugin"
+}
diff --git a/sdkjs-plugins/content/texthighlighter/translations/sr-Cyrl-RS.json b/sdkjs-plugins/content/texthighlighter/translations/sr-Cyrl-RS.json
new file mode 100644
index 00000000..debe96ec
--- /dev/null
+++ b/sdkjs-plugins/content/texthighlighter/translations/sr-Cyrl-RS.json
@@ -0,0 +1,42 @@
+{
+ "TextHighlighter": "Означивач текста",
+ "PluginInstructions": "Унесите или изаберите реч или фразу за претрагу у тексту документа и подесите потребна подешавања.",
+
+ "TextToSearch": "Текст за претрагу:",
+ "SearchPlaceholder": "Унесите текст или изаберите у документу",
+ "IgnoreCase": "Игнориши велика/мала слова",
+
+ "HighlightColor": "Боја означавања:",
+ "HighlightYellow": "Жута",
+ "HighlightGreen": "Зелена",
+ "HighlightBlue": "Плава",
+ "HighlightRed": "Црвена",
+ "HighlightNone": "Без испуне",
+
+ "TextColorHeader": "Боја текста",
+ "TextColorPicker": "Изабери боју текста",
+
+ "TextFormattingHeader": "Форматирање текста",
+ "FormatBold": "Подебљано",
+ "FormatItalic": "Курзив",
+ "FormatUnderline": "Подвучено",
+ "FormatStrike": "Прецртано",
+
+ "ApplyButton": "Примени",
+
+ "SearchNoResults": "Претрага завршена. Нема пронађених поклапања. ",
+ "HighlightMore": "Означи више",
+ "HighlightMore2": "Означи више",
+ "ArrowDropdown": "▸",
+
+ "SearchDone": "Готово! Претрага је завршена и стилови су примењени.",
+ "MatchesFound": "Пронађена поклапања:",
+ "RevertToOriginal": "Врати на оригинал",
+
+ "LoadingMessage": "Обрада у току...",
+
+ "AboutTitle": "Означивач текста",
+ "AboutVersion": "Верзија 1.0.0",
+ "AboutDescription": "Овај додатак омогућава претрагу текста и примену означавања, боја и форматирања у документу.",
+ "AboutCopyright": "© 2025 ONLYOFFICE Plugin"
+}
diff --git a/sdkjs-plugins/content/texthighlighter/translations/sr-Latn-RS.json b/sdkjs-plugins/content/texthighlighter/translations/sr-Latn-RS.json
new file mode 100644
index 00000000..b654b63f
--- /dev/null
+++ b/sdkjs-plugins/content/texthighlighter/translations/sr-Latn-RS.json
@@ -0,0 +1,42 @@
+{
+ "TextHighlighter": "Označivač teksta",
+ "PluginInstructions": "Unesite ili izaberite reč ili frazu za pretragu u tekstu dokumenta i podesite potrebna podešavanja.",
+
+ "TextToSearch": "Tekst za pretragu:",
+ "SearchPlaceholder": "Unesite tekst ili izaberite u dokumentu",
+ "IgnoreCase": "Ignoriši velika/mala slova",
+
+ "HighlightColor": "Boja označavanja:",
+ "HighlightYellow": "Žuta",
+ "HighlightGreen": "Zelena",
+ "HighlightBlue": "Plava",
+ "HighlightRed": "Crvena",
+ "HighlightNone": "Bez ispune",
+
+ "TextColorHeader": "Boja teksta",
+ "TextColorPicker": "Izaberi boju teksta",
+
+ "TextFormattingHeader": "Formatiranje teksta",
+ "FormatBold": "Podebljano",
+ "FormatItalic": "Kurziv",
+ "FormatUnderline": "Podvučeno",
+ "FormatStrike": "Precrtano",
+
+ "ApplyButton": "Primeni",
+
+ "SearchNoResults": "Pretraga završena. Nema pronađenih poklapanja. ",
+ "HighlightMore": "Označi više",
+ "HighlightMore2": "Označi više",
+ "ArrowDropdown": "▸",
+
+ "SearchDone": "Gotovo! Pretraga je završena i stilovi su primenjeni.",
+ "MatchesFound": "Pronađena poklapanja:",
+ "RevertToOriginal": "Vrati na original",
+
+ "LoadingMessage": "Obrada u toku...",
+
+ "AboutTitle": "Označivač teksta",
+ "AboutVersion": "Verzija 1.0.0",
+ "AboutDescription": "Ovaj dodatak omogućava pretragu teksta i primenu označavanja, boja i formatiranja u dokumentu.",
+ "AboutCopyright": "© 2025 ONLYOFFICE Plugin "
+}
diff --git a/sdkjs-plugins/content/texthighlighter/translations/uk-UA.json b/sdkjs-plugins/content/texthighlighter/translations/uk-UA.json
new file mode 100644
index 00000000..60a8720e
--- /dev/null
+++ b/sdkjs-plugins/content/texthighlighter/translations/uk-UA.json
@@ -0,0 +1,42 @@
+{
+ "TextHighlighter": "Виділення тексту",
+ "PluginInstructions": "Введіть або виберіть слово чи фразу для пошуку в документі та налаштуйте необхідні параметри.",
+
+ "TextToSearch": "Текст для пошуку:",
+ "SearchPlaceholder": "Введіть текст або виберіть у документі",
+ "IgnoreCase": "Ігнорувати регістр",
+
+ "HighlightColor": "Колір виділення:",
+ "HighlightYellow": "Жовтий",
+ "HighlightGreen": "Зелений",
+ "HighlightBlue": "Синій",
+ "HighlightRed": "Червоний",
+ "HighlightNone": "Без заповнення",
+ "ArrowDropdown": "▸",
+
+ "TextColorHeader": "Колір тексту",
+ "TextColorPicker": "Виберіть колір тексту",
+
+ "TextFormattingHeader": "Форматування тексту",
+ "FormatBold": "Жирний",
+ "FormatItalic": "Курсив",
+ "FormatUnderline": "Підкреслення",
+ "FormatStrike": "Закреслення",
+
+ "ApplyButton": "Застосувати",
+
+ "SearchNoResults": "Пошук завершено. Збігів не знайдено. ",
+ "HighlightMore": "Виділити ще",
+ "HighlightMore2": "Виділити ще",
+
+ "SearchDone": "Готово! Пошук завершено, стилі застосовано.",
+ "MatchesFound": "Знайдено збігів:",
+ "RevertToOriginal": "Повернути",
+
+ "LoadingMessage": "Обробка...",
+
+ "AboutTitle": "Виділення тексту",
+ "AboutVersion": "Версія 1.0.0",
+ "AboutDescription": "Цей плагін дозволяє виконувати пошук тексту та застосовувати виділення, колір і стилі форматування у вашому документі.",
+ "AboutCopyright": "© 2025 Плагін ONLYOFFICE"
+}
diff --git a/sdkjs-plugins/content/texthighlighter/translations/zh-ZH.json b/sdkjs-plugins/content/texthighlighter/translations/zh-ZH.json
new file mode 100644
index 00000000..81217b41
--- /dev/null
+++ b/sdkjs-plugins/content/texthighlighter/translations/zh-ZH.json
@@ -0,0 +1,41 @@
+{
+ "TextHighlighter": "文本高亮器",
+ "PluginInstructions": "输入或选择要搜索的单词或短语 ,并配置所需设置。",
+
+ "TextToSearch": "搜索文本:",
+ "SearchPlaceholder": "输入文本或在文档中选择",
+ "IgnoreCase": "忽略大小写",
+
+ "HighlightColor": "高亮颜色:",
+ "HighlightYellow": "黄色",
+ "HighlightGreen": "绿色",
+ "HighlightBlue": "蓝色",
+ "HighlightRed": "红色",
+ "HighlightNone": "无填充",
+
+ "TextColorHeader": "文字颜色",
+ "TextColorPicker": "选择文字颜色",
+
+ "TextFormattingHeader": "文字格式",
+ "FormatBold": "加粗",
+ "FormatItalic": "斜体",
+ "FormatUnderline": "下划线",
+ "FormatStrike": "删除线",
+
+ "ApplyButton": "应用",
+
+ "SearchNoResults": "搜索完成。未找到匹配项。 ",
+ "HighlightMore": "继续高亮",
+ "HighlightMore2": "继续高亮",
+ "ArrowDropdown": "▸",
+ "SearchDone": "完成!搜索已完成并应用样式。",
+ "MatchesFound": "找到匹配项:",
+ "RevertToOriginal": "恢复",
+
+ "LoadingMessage": "处理中...",
+
+ "AboutTitle": "文本高亮器",
+ "AboutVersion": "版本 1.0.0",
+ "AboutDescription": "该插件允许您搜索文本并在文档中应用高亮、颜色和格式样式。",
+ "AboutCopyright": "© 2025 ONLYOFFICE 插件"
+}
diff --git a/store/config.json b/store/config.json
index 529f71db..23eace60 100644
--- a/store/config.json
+++ b/store/config.json
@@ -35,5 +35,8 @@
{ "name": "pomodoro", "discussion": "" },
{ "name": "videoembedder", "discussion": "" },
{ "name": "mathpix", "discussion": "" },
- { "name": "icons", "discussion": "" }
+ { "name": "icons", "discussion": "" },
+ { "name": "datepicker", "discussion": "" },
+ { "name": "news", "discussion": "" },
+ { "name": "texthighlighter", "discussion": "" }
]