blob: 02dd05ac6b4af58766ad42251f2520f9107c7082 [file] [log] [blame]
Adrià Vilanova Martínez5bdc4732022-05-31 20:12:21 +02001import actionApi from './common/actionApi';
2import {isoLangs} from './common/consts';
3import Options from './common/options';
4import ExtSessionStorage from './common/sessionStorage';
Adrià Vilanova Martínez9f03abd2022-06-02 00:51:00 +02005import URLFactory from './common/urlFactory';
avm999634a2a5d52016-06-04 16:17:29 +02006
Adrià Vilanova Martínez9f03abd2022-06-02 00:51:00 +02007type NonEmptyArray<T> = [T, ...T[]];
8
9// Data types that the extension can translate.
10export enum DataType {
11 DataTypeText,
12 DataTypeURL,
Adrià Vilanova Martínez5bdc4732022-05-31 20:12:21 +020013}
14
Adrià Vilanova Martínez9f03abd2022-06-02 00:51:00 +020015// Information about a context menu item which we have added to the browser.
16interface MenuItemInfo {
17 language: string; // Target language displayed in the item.
18 dataType: DataType; // Data type handled by the context menu item.
avm99963ce257a92020-12-27 00:07:13 +010019}
20
Adrià Vilanova Martínez9f03abd2022-06-02 00:51:00 +020021// Object with the context menu items that have been added by the extension and
22// information about them.
23interface ContextMenuItems {
24 [id: string]: MenuItemInfo;
25}
26
27// Definition of the types of context menu items that the extension can inject.
28interface MenuItemType {
29 // Type of data which can be translated with this type of context menu items.
30 dataType: DataType;
31 // Contexts in which this type of context menu item will be shown.
32 contexts: NonEmptyArray<chrome.contextMenus.ContextType>;
33 // Prefix of the i18n messages for this type of context menu item, and used to
34 // generate the unique IDs of context menu items.
35 prefix: string;
36}
37type MenuItemTypes = MenuItemType[];
38
39const MENU_ITEM_TYPES: MenuItemTypes = [
40 {
41 dataType: DataType.DataTypeText,
42 contexts: ['selection'],
43 prefix: '',
44 },
45 /*
46 * @TODO(https://iavm.xyz/b/translateselectedtext/7): Delete this compile-time
47 * directive after the experimentation phase is done to launch the feature.
48 * #!if canary || !production
49 */
50 {
51 dataType: DataType.DataTypeURL,
52 contexts: ['link'],
53 prefix: 'link',
54 },
55 // #!endif
56];
57
Adrià Vilanova Martínez21234882022-06-05 23:11:31 +020058function translationClick(
59 info: chrome.contextMenus.OnClickData,
60 initiatorTab: chrome.tabs.Tab): void {
Adrià Vilanova Martínez51f151f2022-06-01 23:17:38 +020061 const optionsPromise = Options.getOptions();
62 const ssPromise =
Adrià Vilanova Martínez9f03abd2022-06-02 00:51:00 +020063 ExtSessionStorage.get(['contextMenuItems', 'translatorTab']);
Adrià Vilanova Martínez86fda492022-05-31 15:05:21 +020064 Promise.all([optionsPromise, ssPromise])
65 .then(returnValues => {
66 const [options, sessionStorageItems] = returnValues;
Adrià Vilanova Martínez9f03abd2022-06-02 00:51:00 +020067 const contextMenuItems: ContextMenuItems =
68 sessionStorageItems.contextMenuItems;
69 const contextMenuItem = contextMenuItems?.[info.menuItemId];
70 const translatorTab: number = sessionStorageItems.translatorTab;
71
72 const url = URLFactory.getTranslationURL(
73 contextMenuItem?.language, info, contextMenuItem?.dataType);
Adrià Vilanova Martínez7ba7ec12024-05-28 22:17:38 +020074 const newTabOptions: Parameters<typeof chrome.tabs.create>[0] = {url};
75
76 if (initiatorTab.id > 0) newTabOptions.openerTabId = initiatorTab.id;
Adrià Vilanova Martínez21234882022-06-05 23:11:31 +020077 if (initiatorTab.index) newTabOptions.index = initiatorTab.index + 1;
Adrià Vilanova Martínez9f03abd2022-06-02 00:51:00 +020078
79 if (contextMenuItem?.dataType !== DataType.DataTypeText) {
80 // Always create a simple new tab for data types other than text.
81 // @TODO(https://iavm.xyz/b/translateselectedtext/7): Review this
82 // behavior in the future.
Adrià Vilanova Martínez21234882022-06-05 23:11:31 +020083 chrome.tabs.create(newTabOptions);
Adrià Vilanova Martínez9f03abd2022-06-02 00:51:00 +020084 } else if (translatorTab && options.uniqueTab == 'yep') {
85 chrome.tabs.update(translatorTab, {url}, tab => {
86 chrome.tabs.highlight(
87 {windowId: tab.windowId, tabs: tab.index}, () => {
88 chrome.windows.update(tab.windowId, {focused: true});
89 });
Adrià Vilanova Martínezec599342022-05-31 11:57:35 +020090 });
Adrià Vilanova Martínez9f03abd2022-06-02 00:51:00 +020091 } else if (options.uniqueTab == 'popup') {
92 chrome.windows.create({type: 'popup', url, width: 1000, height: 382});
Adrià Vilanova Martínez53f7a7f2022-05-30 13:59:59 +020093 } else {
Adrià Vilanova Martínez21234882022-06-05 23:11:31 +020094 chrome.tabs.create(newTabOptions, tab => {
Adrià Vilanova Martínez9ff52fe2022-09-15 12:27:49 +020095 ExtSessionStorage.set({translatorTab: tab?.id});
avm999635a57c412020-12-27 00:26:45 +010096 });
Adrià Vilanova Martínez53f7a7f2022-05-30 13:59:59 +020097 }
98 })
99 .catch(err => {
Adrià Vilanova Martínez86fda492022-05-31 15:05:21 +0200100 console.error('Error handling translation click', err);
avm999635a57c412020-12-27 00:26:45 +0100101 });
avm999634a2a5d52016-06-04 16:17:29 +0200102}
103
Adrià Vilanova Martínez5bdc4732022-05-31 20:12:21 +0200104function createMenus(options: Options): Promise<void> {
Adrià Vilanova Martínez53f7a7f2022-05-30 13:59:59 +0200105 chrome.contextMenus.removeAll();
avm999634a2a5d52016-06-04 16:17:29 +0200106
Adrià Vilanova Martínez9f03abd2022-06-02 00:51:00 +0200107 const contextMenuItems: ContextMenuItems = {};
Adrià Vilanova Martínez51f151f2022-06-01 23:17:38 +0200108 const langs = options.targetLangs;
109 const isSingleEntry = Object.values(langs).length == 1;
avm999634a2a5d52016-06-04 16:17:29 +0200110
Adrià Vilanova Martínez9f03abd2022-06-02 00:51:00 +0200111 for (const type of MENU_ITEM_TYPES) {
112 let parentEl;
113 if (!isSingleEntry) {
114 parentEl = chrome.contextMenus.create({
115 'id': `${type.prefix}parent`,
116 'title': chrome.i18n.getMessage(`contextmenu${type.prefix}_title`),
117 'contexts': type.contexts,
118 });
Adrià Vilanova Martínez53f7a7f2022-05-30 13:59:59 +0200119 }
Adrià Vilanova Martínez9f03abd2022-06-02 00:51:00 +0200120
121 for (const language of Object.values(langs)) {
122 const languageDetails = isoLangs[language];
123 if (languageDetails === undefined) {
124 console.error(language + ' doesn\'t exist!');
125 continue;
126 }
127 let title;
128 if (isSingleEntry) {
129 title = chrome.i18n.getMessage(
130 `contextmenu${type.prefix}_title2`, languageDetails.name);
131 } else {
132 title = languageDetails.name + ' (' + languageDetails.nativeName + ')';
133 }
134 const id = chrome.contextMenus.create({
135 'id': `${type.prefix}tr_language_${language}`,
136 'title': title,
137 'parentId': parentEl,
138 'contexts': type.contexts,
139 });
140 contextMenuItems[id] = {
141 language,
142 dataType: type.dataType,
143 };
Adrià Vilanova Martínez53f7a7f2022-05-30 13:59:59 +0200144 }
Adrià Vilanova Martínez9f03abd2022-06-02 00:51:00 +0200145
146 if (!isSingleEntry) {
147 chrome.contextMenus.create({
148 'id': `${type.prefix}tr_separator`,
149 'type': 'separator',
150 'parentId': parentEl,
151 'contexts': type.contexts,
152 });
153 chrome.contextMenus.create({
154 'id': `${type.prefix}tr_options`,
155 'title': chrome.i18n.getMessage('contextmenu_edit'),
156 'parentId': parentEl,
157 'contexts': type.contexts,
158 });
159 }
Adrià Vilanova Martínez53f7a7f2022-05-30 13:59:59 +0200160 }
161
Adrià Vilanova Martínez9f03abd2022-06-02 00:51:00 +0200162 return ExtSessionStorage.set({contextMenuItems});
Adrià Vilanova Martínez53f7a7f2022-05-30 13:59:59 +0200163}
164
Adrià Vilanova Martínez9f03abd2022-06-02 00:51:00 +0200165chrome.storage.onChanged.addListener((_changes, areaName) => {
Adrià Vilanova Martínez53f7a7f2022-05-30 13:59:59 +0200166 if (areaName == 'sync') {
167 Options.getOptions(/* readOnly = */ false)
168 .then(options => {
Adrià Vilanova Martínez86fda492022-05-31 15:05:21 +0200169 return createMenus(options);
Adrià Vilanova Martínez53f7a7f2022-05-30 13:59:59 +0200170 })
171 .catch(err => {
172 console.error(
Adrià Vilanova Martínez86fda492022-05-31 15:05:21 +0200173 'Error setting up the extension after a change ' +
Adrià Vilanova Martínez53f7a7f2022-05-30 13:59:59 +0200174 'in the storage area.',
175 err);
176 });
177 }
avm999634a2a5d52016-06-04 16:17:29 +0200178});
179
Adrià Vilanova Martínez53f7a7f2022-05-30 13:59:59 +0200180Options.getOptions(/* readOnly = */ false)
181 .then(options => {
182 if (options.isFirstRun) {
183 chrome.notifications.create('install', {
184 type: 'basic',
185 iconUrl: 'icons/translate-128.png',
186 title: chrome.i18n.getMessage('notification_install_title'),
187 message: chrome.i18n.getMessage('notification_install_message'),
188 isClickable: true
189 });
190 }
191
Adrià Vilanova Martínez86fda492022-05-31 15:05:21 +0200192 return createMenus(options);
Adrià Vilanova Martínez53f7a7f2022-05-30 13:59:59 +0200193 })
194 .catch(err => {
Adrià Vilanova Martínez86fda492022-05-31 15:05:21 +0200195 console.error('Error initializing the extension.', err);
Adrià Vilanova Martínez53f7a7f2022-05-30 13:59:59 +0200196 });
197
avm99963ce257a92020-12-27 00:07:13 +0100198chrome.notifications.onClicked.addListener(notification_id => {
avm999635a57c412020-12-27 00:26:45 +0100199 switch (notification_id) {
200 case 'install':
Adrià Vilanova Martínez53f7a7f2022-05-30 13:59:59 +0200201 chrome.runtime.openOptionsPage();
avm999635a57c412020-12-27 00:26:45 +0100202 break;
203 }
204 chrome.notifications.clear(notification_id);
avm999634a2a5d52016-06-04 16:17:29 +0200205});
206
Adrià Vilanova Martínez21234882022-06-05 23:11:31 +0200207chrome.contextMenus.onClicked.addListener((info, tab) => {
Adrià Vilanova Martínez9f03abd2022-06-02 00:51:00 +0200208 for (const type of MENU_ITEM_TYPES) {
209 if (info.menuItemId == `${type.prefix}tr_options`) {
210 chrome.runtime.openOptionsPage();
211 return;
212 }
avm999635a57c412020-12-27 00:26:45 +0100213 }
Adrià Vilanova Martínez9f03abd2022-06-02 00:51:00 +0200214
Adrià Vilanova Martínez21234882022-06-05 23:11:31 +0200215 translationClick(info, tab);
avm999634a2a5d52016-06-04 16:17:29 +0200216});
avm99963ce257a92020-12-27 00:07:13 +0100217
Adrià Vilanova Martínez51f151f2022-06-01 23:17:38 +0200218chrome.tabs.onRemoved.addListener(tabId => {
Adrià Vilanova Martínez86fda492022-05-31 15:05:21 +0200219 ExtSessionStorage.get('translatorTab')
220 .then(items => {
221 if (tabId == items.translatorTab) {
222 ExtSessionStorage.set({translatorTab: null});
223 }
224 })
225 .catch(err => console.log(err));
Adrià Vilanova Martínezec599342022-05-31 11:57:35 +0200226});
227
Adrià Vilanova Martínez86fda492022-05-31 15:05:21 +0200228actionApi.onClicked.addListener(() => {
Adrià Vilanova Martínez53f7a7f2022-05-30 13:59:59 +0200229 chrome.runtime.openOptionsPage();
avm99963ce257a92020-12-27 00:07:13 +0100230});
Adrià Vilanova Martínez86fda492022-05-31 15:05:21 +0200231
Adrià Vilanova Martínez51f151f2022-06-01 23:17:38 +0200232chrome.runtime.onMessage.addListener(request => {
Adrià Vilanova Martínez86fda492022-05-31 15:05:21 +0200233 switch (request.action) {
234 case 'clearTranslatorTab':
235 ExtSessionStorage.set({translatorTab: null});
236 break;
237
238 default:
Adrià Vilanova Martínez51f151f2022-06-01 23:17:38 +0200239 console.error(
240 `Unknown action "${request.action}" received as a message.`);
Adrià Vilanova Martínez86fda492022-05-31 15:05:21 +0200241 }
Adrià Vilanova Martínez5bdc4732022-05-31 20:12:21 +0200242
243 return undefined;
Adrià Vilanova Martínez86fda492022-05-31 15:05:21 +0200244});