blob: 2db72b94e7a612dd934570defeb612d195aa7b83 [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ínez21234882022-06-05 23:11:31 +020074 const newTabOptions: Parameters<typeof chrome.tabs.create>[0] = {
75 url,
76 openerTabId: initiatorTab.id,
77 };
78 if (initiatorTab.index) newTabOptions.index = initiatorTab.index + 1;
Adrià Vilanova Martínez9f03abd2022-06-02 00:51:00 +020079
80 if (contextMenuItem?.dataType !== DataType.DataTypeText) {
81 // Always create a simple new tab for data types other than text.
82 // @TODO(https://iavm.xyz/b/translateselectedtext/7): Review this
83 // behavior in the future.
Adrià Vilanova Martínez21234882022-06-05 23:11:31 +020084 chrome.tabs.create(newTabOptions);
Adrià Vilanova Martínez9f03abd2022-06-02 00:51:00 +020085 } else if (translatorTab && options.uniqueTab == 'yep') {
86 chrome.tabs.update(translatorTab, {url}, tab => {
87 chrome.tabs.highlight(
88 {windowId: tab.windowId, tabs: tab.index}, () => {
89 chrome.windows.update(tab.windowId, {focused: true});
90 });
Adrià Vilanova Martínezec599342022-05-31 11:57:35 +020091 });
Adrià Vilanova Martínez9f03abd2022-06-02 00:51:00 +020092 } else if (options.uniqueTab == 'popup') {
93 chrome.windows.create({type: 'popup', url, width: 1000, height: 382});
Adrià Vilanova Martínez53f7a7f2022-05-30 13:59:59 +020094 } else {
Adrià Vilanova Martínez21234882022-06-05 23:11:31 +020095 chrome.tabs.create(newTabOptions, tab => {
Adrià Vilanova Martínez9ff52fe2022-09-15 12:27:49 +020096 ExtSessionStorage.set({translatorTab: tab?.id});
avm999635a57c412020-12-27 00:26:45 +010097 });
Adrià Vilanova Martínez53f7a7f2022-05-30 13:59:59 +020098 }
99 })
100 .catch(err => {
Adrià Vilanova Martínez86fda492022-05-31 15:05:21 +0200101 console.error('Error handling translation click', err);
avm999635a57c412020-12-27 00:26:45 +0100102 });
avm999634a2a5d52016-06-04 16:17:29 +0200103}
104
Adrià Vilanova Martínez5bdc4732022-05-31 20:12:21 +0200105function createMenus(options: Options): Promise<void> {
Adrià Vilanova Martínez53f7a7f2022-05-30 13:59:59 +0200106 chrome.contextMenus.removeAll();
avm999634a2a5d52016-06-04 16:17:29 +0200107
Adrià Vilanova Martínez9f03abd2022-06-02 00:51:00 +0200108 const contextMenuItems: ContextMenuItems = {};
Adrià Vilanova Martínez51f151f2022-06-01 23:17:38 +0200109 const langs = options.targetLangs;
110 const isSingleEntry = Object.values(langs).length == 1;
avm999634a2a5d52016-06-04 16:17:29 +0200111
Adrià Vilanova Martínez9f03abd2022-06-02 00:51:00 +0200112 for (const type of MENU_ITEM_TYPES) {
113 let parentEl;
114 if (!isSingleEntry) {
115 parentEl = chrome.contextMenus.create({
116 'id': `${type.prefix}parent`,
117 'title': chrome.i18n.getMessage(`contextmenu${type.prefix}_title`),
118 'contexts': type.contexts,
119 });
Adrià Vilanova Martínez53f7a7f2022-05-30 13:59:59 +0200120 }
Adrià Vilanova Martínez9f03abd2022-06-02 00:51:00 +0200121
122 for (const language of Object.values(langs)) {
123 const languageDetails = isoLangs[language];
124 if (languageDetails === undefined) {
125 console.error(language + ' doesn\'t exist!');
126 continue;
127 }
128 let title;
129 if (isSingleEntry) {
130 title = chrome.i18n.getMessage(
131 `contextmenu${type.prefix}_title2`, languageDetails.name);
132 } else {
133 title = languageDetails.name + ' (' + languageDetails.nativeName + ')';
134 }
135 const id = chrome.contextMenus.create({
136 'id': `${type.prefix}tr_language_${language}`,
137 'title': title,
138 'parentId': parentEl,
139 'contexts': type.contexts,
140 });
141 contextMenuItems[id] = {
142 language,
143 dataType: type.dataType,
144 };
Adrià Vilanova Martínez53f7a7f2022-05-30 13:59:59 +0200145 }
Adrià Vilanova Martínez9f03abd2022-06-02 00:51:00 +0200146
147 if (!isSingleEntry) {
148 chrome.contextMenus.create({
149 'id': `${type.prefix}tr_separator`,
150 'type': 'separator',
151 'parentId': parentEl,
152 'contexts': type.contexts,
153 });
154 chrome.contextMenus.create({
155 'id': `${type.prefix}tr_options`,
156 'title': chrome.i18n.getMessage('contextmenu_edit'),
157 'parentId': parentEl,
158 'contexts': type.contexts,
159 });
160 }
Adrià Vilanova Martínez53f7a7f2022-05-30 13:59:59 +0200161 }
162
Adrià Vilanova Martínez9f03abd2022-06-02 00:51:00 +0200163 return ExtSessionStorage.set({contextMenuItems});
Adrià Vilanova Martínez53f7a7f2022-05-30 13:59:59 +0200164}
165
Adrià Vilanova Martínez9f03abd2022-06-02 00:51:00 +0200166chrome.storage.onChanged.addListener((_changes, areaName) => {
Adrià Vilanova Martínez53f7a7f2022-05-30 13:59:59 +0200167 if (areaName == 'sync') {
168 Options.getOptions(/* readOnly = */ false)
169 .then(options => {
Adrià Vilanova Martínez86fda492022-05-31 15:05:21 +0200170 return createMenus(options);
Adrià Vilanova Martínez53f7a7f2022-05-30 13:59:59 +0200171 })
172 .catch(err => {
173 console.error(
Adrià Vilanova Martínez86fda492022-05-31 15:05:21 +0200174 'Error setting up the extension after a change ' +
Adrià Vilanova Martínez53f7a7f2022-05-30 13:59:59 +0200175 'in the storage area.',
176 err);
177 });
178 }
avm999634a2a5d52016-06-04 16:17:29 +0200179});
180
Adrià Vilanova Martínez53f7a7f2022-05-30 13:59:59 +0200181Options.getOptions(/* readOnly = */ false)
182 .then(options => {
183 if (options.isFirstRun) {
184 chrome.notifications.create('install', {
185 type: 'basic',
186 iconUrl: 'icons/translate-128.png',
187 title: chrome.i18n.getMessage('notification_install_title'),
188 message: chrome.i18n.getMessage('notification_install_message'),
189 isClickable: true
190 });
191 }
192
Adrià Vilanova Martínez86fda492022-05-31 15:05:21 +0200193 return createMenus(options);
Adrià Vilanova Martínez53f7a7f2022-05-30 13:59:59 +0200194 })
195 .catch(err => {
Adrià Vilanova Martínez86fda492022-05-31 15:05:21 +0200196 console.error('Error initializing the extension.', err);
Adrià Vilanova Martínez53f7a7f2022-05-30 13:59:59 +0200197 });
198
avm99963ce257a92020-12-27 00:07:13 +0100199chrome.notifications.onClicked.addListener(notification_id => {
avm999635a57c412020-12-27 00:26:45 +0100200 switch (notification_id) {
201 case 'install':
Adrià Vilanova Martínez53f7a7f2022-05-30 13:59:59 +0200202 chrome.runtime.openOptionsPage();
avm999635a57c412020-12-27 00:26:45 +0100203 break;
204 }
205 chrome.notifications.clear(notification_id);
avm999634a2a5d52016-06-04 16:17:29 +0200206});
207
Adrià Vilanova Martínez21234882022-06-05 23:11:31 +0200208chrome.contextMenus.onClicked.addListener((info, tab) => {
Adrià Vilanova Martínez9f03abd2022-06-02 00:51:00 +0200209 for (const type of MENU_ITEM_TYPES) {
210 if (info.menuItemId == `${type.prefix}tr_options`) {
211 chrome.runtime.openOptionsPage();
212 return;
213 }
avm999635a57c412020-12-27 00:26:45 +0100214 }
Adrià Vilanova Martínez9f03abd2022-06-02 00:51:00 +0200215
Adrià Vilanova Martínez21234882022-06-05 23:11:31 +0200216 translationClick(info, tab);
avm999634a2a5d52016-06-04 16:17:29 +0200217});
avm99963ce257a92020-12-27 00:07:13 +0100218
Adrià Vilanova Martínez51f151f2022-06-01 23:17:38 +0200219chrome.tabs.onRemoved.addListener(tabId => {
Adrià Vilanova Martínez86fda492022-05-31 15:05:21 +0200220 ExtSessionStorage.get('translatorTab')
221 .then(items => {
222 if (tabId == items.translatorTab) {
223 ExtSessionStorage.set({translatorTab: null});
224 }
225 })
226 .catch(err => console.log(err));
Adrià Vilanova Martínezec599342022-05-31 11:57:35 +0200227});
228
Adrià Vilanova Martínez86fda492022-05-31 15:05:21 +0200229actionApi.onClicked.addListener(() => {
Adrià Vilanova Martínez53f7a7f2022-05-30 13:59:59 +0200230 chrome.runtime.openOptionsPage();
avm99963ce257a92020-12-27 00:07:13 +0100231});
Adrià Vilanova Martínez86fda492022-05-31 15:05:21 +0200232
Adrià Vilanova Martínez51f151f2022-06-01 23:17:38 +0200233chrome.runtime.onMessage.addListener(request => {
Adrià Vilanova Martínez86fda492022-05-31 15:05:21 +0200234 switch (request.action) {
235 case 'clearTranslatorTab':
236 ExtSessionStorage.set({translatorTab: null});
237 break;
238
239 default:
Adrià Vilanova Martínez51f151f2022-06-01 23:17:38 +0200240 console.error(
241 `Unknown action "${request.action}" received as a message.`);
Adrià Vilanova Martínez86fda492022-05-31 15:05:21 +0200242 }
Adrià Vilanova Martínez5bdc4732022-05-31 20:12:21 +0200243
244 return undefined;
Adrià Vilanova Martínez86fda492022-05-31 15:05:21 +0200245});