blob: 1bed97815c3f1e2e0134aacff3c327e118d3440b [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ínez51f151f2022-06-01 23:17:38 +020058function translationClick(info: chrome.contextMenus.OnClickData): void {
59 const optionsPromise = Options.getOptions();
60 const ssPromise =
Adrià Vilanova Martínez9f03abd2022-06-02 00:51:00 +020061 ExtSessionStorage.get(['contextMenuItems', 'translatorTab']);
Adrià Vilanova Martínez86fda492022-05-31 15:05:21 +020062 Promise.all([optionsPromise, ssPromise])
63 .then(returnValues => {
64 const [options, sessionStorageItems] = returnValues;
Adrià Vilanova Martínez9f03abd2022-06-02 00:51:00 +020065 const contextMenuItems: ContextMenuItems =
66 sessionStorageItems.contextMenuItems;
67 const contextMenuItem = contextMenuItems?.[info.menuItemId];
68 const translatorTab: number = sessionStorageItems.translatorTab;
69
70 const url = URLFactory.getTranslationURL(
71 contextMenuItem?.language, info, contextMenuItem?.dataType);
72
73 if (contextMenuItem?.dataType !== DataType.DataTypeText) {
74 // Always create a simple new tab for data types other than text.
75 // @TODO(https://iavm.xyz/b/translateselectedtext/7): Review this
76 // behavior in the future.
77 chrome.tabs.create({url});
78 } else if (translatorTab && options.uniqueTab == 'yep') {
79 chrome.tabs.update(translatorTab, {url}, tab => {
80 chrome.tabs.highlight(
81 {windowId: tab.windowId, tabs: tab.index}, () => {
82 chrome.windows.update(tab.windowId, {focused: true});
83 });
Adrià Vilanova Martínezec599342022-05-31 11:57:35 +020084 });
Adrià Vilanova Martínez9f03abd2022-06-02 00:51:00 +020085 } else if (options.uniqueTab == 'popup') {
86 chrome.windows.create({type: 'popup', url, width: 1000, height: 382});
Adrià Vilanova Martínez53f7a7f2022-05-30 13:59:59 +020087 } else {
Adrià Vilanova Martínez9f03abd2022-06-02 00:51:00 +020088 chrome.tabs.create({url}, tab => {
Adrià Vilanova Martínez86fda492022-05-31 15:05:21 +020089 ExtSessionStorage.set({translatorTab: tab.id});
avm999635a57c412020-12-27 00:26:45 +010090 });
Adrià Vilanova Martínez53f7a7f2022-05-30 13:59:59 +020091 }
92 })
93 .catch(err => {
Adrià Vilanova Martínez86fda492022-05-31 15:05:21 +020094 console.error('Error handling translation click', err);
avm999635a57c412020-12-27 00:26:45 +010095 });
avm999634a2a5d52016-06-04 16:17:29 +020096}
97
Adrià Vilanova Martínez5bdc4732022-05-31 20:12:21 +020098function createMenus(options: Options): Promise<void> {
Adrià Vilanova Martínez53f7a7f2022-05-30 13:59:59 +020099 chrome.contextMenus.removeAll();
avm999634a2a5d52016-06-04 16:17:29 +0200100
Adrià Vilanova Martínez9f03abd2022-06-02 00:51:00 +0200101 const contextMenuItems: ContextMenuItems = {};
Adrià Vilanova Martínez51f151f2022-06-01 23:17:38 +0200102 const langs = options.targetLangs;
103 const isSingleEntry = Object.values(langs).length == 1;
avm999634a2a5d52016-06-04 16:17:29 +0200104
Adrià Vilanova Martínez9f03abd2022-06-02 00:51:00 +0200105 for (const type of MENU_ITEM_TYPES) {
106 let parentEl;
107 if (!isSingleEntry) {
108 parentEl = chrome.contextMenus.create({
109 'id': `${type.prefix}parent`,
110 'title': chrome.i18n.getMessage(`contextmenu${type.prefix}_title`),
111 'contexts': type.contexts,
112 });
Adrià Vilanova Martínez53f7a7f2022-05-30 13:59:59 +0200113 }
Adrià Vilanova Martínez9f03abd2022-06-02 00:51:00 +0200114
115 for (const language of Object.values(langs)) {
116 const languageDetails = isoLangs[language];
117 if (languageDetails === undefined) {
118 console.error(language + ' doesn\'t exist!');
119 continue;
120 }
121 let title;
122 if (isSingleEntry) {
123 title = chrome.i18n.getMessage(
124 `contextmenu${type.prefix}_title2`, languageDetails.name);
125 } else {
126 title = languageDetails.name + ' (' + languageDetails.nativeName + ')';
127 }
128 const id = chrome.contextMenus.create({
129 'id': `${type.prefix}tr_language_${language}`,
130 'title': title,
131 'parentId': parentEl,
132 'contexts': type.contexts,
133 });
134 contextMenuItems[id] = {
135 language,
136 dataType: type.dataType,
137 };
Adrià Vilanova Martínez53f7a7f2022-05-30 13:59:59 +0200138 }
Adrià Vilanova Martínez9f03abd2022-06-02 00:51:00 +0200139
140 if (!isSingleEntry) {
141 chrome.contextMenus.create({
142 'id': `${type.prefix}tr_separator`,
143 'type': 'separator',
144 'parentId': parentEl,
145 'contexts': type.contexts,
146 });
147 chrome.contextMenus.create({
148 'id': `${type.prefix}tr_options`,
149 'title': chrome.i18n.getMessage('contextmenu_edit'),
150 'parentId': parentEl,
151 'contexts': type.contexts,
152 });
153 }
Adrià Vilanova Martínez53f7a7f2022-05-30 13:59:59 +0200154 }
155
Adrià Vilanova Martínez9f03abd2022-06-02 00:51:00 +0200156 return ExtSessionStorage.set({contextMenuItems});
Adrià Vilanova Martínez53f7a7f2022-05-30 13:59:59 +0200157}
158
Adrià Vilanova Martínez9f03abd2022-06-02 00:51:00 +0200159chrome.storage.onChanged.addListener((_changes, areaName) => {
Adrià Vilanova Martínez53f7a7f2022-05-30 13:59:59 +0200160 if (areaName == 'sync') {
161 Options.getOptions(/* readOnly = */ false)
162 .then(options => {
Adrià Vilanova Martínez86fda492022-05-31 15:05:21 +0200163 return createMenus(options);
Adrià Vilanova Martínez53f7a7f2022-05-30 13:59:59 +0200164 })
165 .catch(err => {
166 console.error(
Adrià Vilanova Martínez86fda492022-05-31 15:05:21 +0200167 'Error setting up the extension after a change ' +
Adrià Vilanova Martínez53f7a7f2022-05-30 13:59:59 +0200168 'in the storage area.',
169 err);
170 });
171 }
avm999634a2a5d52016-06-04 16:17:29 +0200172});
173
Adrià Vilanova Martínez53f7a7f2022-05-30 13:59:59 +0200174Options.getOptions(/* readOnly = */ false)
175 .then(options => {
176 if (options.isFirstRun) {
177 chrome.notifications.create('install', {
178 type: 'basic',
179 iconUrl: 'icons/translate-128.png',
180 title: chrome.i18n.getMessage('notification_install_title'),
181 message: chrome.i18n.getMessage('notification_install_message'),
182 isClickable: true
183 });
184 }
185
Adrià Vilanova Martínez86fda492022-05-31 15:05:21 +0200186 return createMenus(options);
Adrià Vilanova Martínez53f7a7f2022-05-30 13:59:59 +0200187 })
188 .catch(err => {
Adrià Vilanova Martínez86fda492022-05-31 15:05:21 +0200189 console.error('Error initializing the extension.', err);
Adrià Vilanova Martínez53f7a7f2022-05-30 13:59:59 +0200190 });
191
avm99963ce257a92020-12-27 00:07:13 +0100192chrome.notifications.onClicked.addListener(notification_id => {
avm999635a57c412020-12-27 00:26:45 +0100193 switch (notification_id) {
194 case 'install':
Adrià Vilanova Martínez53f7a7f2022-05-30 13:59:59 +0200195 chrome.runtime.openOptionsPage();
avm999635a57c412020-12-27 00:26:45 +0100196 break;
197 }
198 chrome.notifications.clear(notification_id);
avm999634a2a5d52016-06-04 16:17:29 +0200199});
200
Adrià Vilanova Martínez51f151f2022-06-01 23:17:38 +0200201chrome.contextMenus.onClicked.addListener(info => {
Adrià Vilanova Martínez9f03abd2022-06-02 00:51:00 +0200202 for (const type of MENU_ITEM_TYPES) {
203 if (info.menuItemId == `${type.prefix}tr_options`) {
204 chrome.runtime.openOptionsPage();
205 return;
206 }
avm999635a57c412020-12-27 00:26:45 +0100207 }
Adrià Vilanova Martínez9f03abd2022-06-02 00:51:00 +0200208
209 translationClick(info);
avm999634a2a5d52016-06-04 16:17:29 +0200210});
avm99963ce257a92020-12-27 00:07:13 +0100211
Adrià Vilanova Martínez51f151f2022-06-01 23:17:38 +0200212chrome.tabs.onRemoved.addListener(tabId => {
Adrià Vilanova Martínez86fda492022-05-31 15:05:21 +0200213 ExtSessionStorage.get('translatorTab')
214 .then(items => {
215 if (tabId == items.translatorTab) {
216 ExtSessionStorage.set({translatorTab: null});
217 }
218 })
219 .catch(err => console.log(err));
Adrià Vilanova Martínezec599342022-05-31 11:57:35 +0200220});
221
Adrià Vilanova Martínez86fda492022-05-31 15:05:21 +0200222actionApi.onClicked.addListener(() => {
Adrià Vilanova Martínez53f7a7f2022-05-30 13:59:59 +0200223 chrome.runtime.openOptionsPage();
avm99963ce257a92020-12-27 00:07:13 +0100224});
Adrià Vilanova Martínez86fda492022-05-31 15:05:21 +0200225
Adrià Vilanova Martínez51f151f2022-06-01 23:17:38 +0200226chrome.runtime.onMessage.addListener(request => {
Adrià Vilanova Martínez86fda492022-05-31 15:05:21 +0200227 switch (request.action) {
228 case 'clearTranslatorTab':
229 ExtSessionStorage.set({translatorTab: null});
230 break;
231
232 default:
Adrià Vilanova Martínez51f151f2022-06-01 23:17:38 +0200233 console.error(
234 `Unknown action "${request.action}" received as a message.`);
Adrià Vilanova Martínez86fda492022-05-31 15:05:21 +0200235 }
Adrià Vilanova Martínez5bdc4732022-05-31 20:12:21 +0200236
237 return undefined;
Adrià Vilanova Martínez86fda492022-05-31 15:05:21 +0200238});