Commit 6308fe83 authored by Tasso Evangelista's avatar Tasso Evangelista

Detach context menu from spell checking modules

parent e9a36337
......@@ -19,7 +19,7 @@
"Close": "Schließen",
"Connect": "Verbinden",
"&Copy": "&Kopieren",
"Copy_Link": "Link kopieren",
"Copy link address": "Link kopieren",
"Copyright": "Copyright %s",
"Current_Version": "Aktuelle Version:",
"Cu&t": "&Ausschneiden",
......@@ -39,7 +39,7 @@
"Menu bar": "MenuBar",
"Minimize": "Minimieren",
"More_spelling_suggestions": "Mehr Rechtschreibvorschläge",
"Open_Link": "Link öffnen",
"Open link": "Link öffnen",
"New_Version": "Neue Version:",
"No": "Nein",
"No_suggestions": "Keine Vorschläge",
......
......@@ -21,7 +21,8 @@
"Close": "Close",
"Connect": "Connect",
"&Copy": "&Copy",
"Copy_Link": "Copy link",
"Copy link address": "Copy link address",
"Copy link text": "Copy link text",
"Copyright": "Copyright %s",
"Current_Version": "Current Version:",
"Cu&t": "Cu&t",
......@@ -58,7 +59,7 @@
"Open DevTools": "Open DevTools",
"Open &DevTools": "Open &DevTools",
"Open_Language_Dictionary": "Open Language Dictionary",
"Open_Link": "Open link",
"Open link": "Open link",
"&Paste": "&Paste",
"Quit": "Quit",
"&Quit %s": "&Quit %s",
......@@ -71,7 +72,7 @@
"Report issue": "Report issue",
"Reset app data": "Reset app data",
"Reset zoom": "Reset zoom",
"Save_Image": "Save Image",
"Save image as...": "Save image as...",
"Select_a_screen_to_share": "Select a screen to share",
"Select &all": "Select &all",
"Server_Failed_to_Load": "Server Failed to Load",
......
......@@ -21,7 +21,7 @@
"Close": "Fermer",
"Connect": "Connexion",
"&Copy": "&Copier",
"Copy_Link": "Copier le lien",
"Copy link address": "Copier le lien",
"Copyright": "Copyright %s",
"Current_Version": "Version utilisée :",
"Cu&t": "Cou&per",
......@@ -44,7 +44,7 @@
"Menu bar": "Menu bar",
"Minimize": "Réduire",
"More_spelling_suggestions": "Plus de suggestions d'orthographe",
"Open_Link": "Ouvrir le lien",
"Open link": "Ouvrir le lien",
"New_Version": "Nouvelle version :",
"No": "No",
"No_suggestions": "Pas de suggestions",
......
......@@ -21,7 +21,7 @@
"Close": "閉じる",
"Connect": "接続",
"&Copy": "コピー (&C)",
"Copy_Link": "リンクをコピー",
"Copy link address": "リンクをコピー",
"Copyright": "著作権 %s",
"Current_Version": "現在のバージョン:",
"Cu&t": "切り取り (&T)",
......@@ -58,7 +58,7 @@
"Open DevTools": "開発ツールを開く",
"Open &DevTools": "開発ツールを開く (&D)",
"Open_Language_Dictionary": "言語の辞書を開く",
"Open_Link": "リンクを開く",
"Open link": "リンクを開く",
"&Paste": "貼り付け (&P)",
"Quit": "終了",
"&Quit %s": "%s を終了 (&Q)",
......
......@@ -21,7 +21,8 @@
"Close": "Fechar",
"Connect": "Conectar",
"&Copy": "&Copiar",
"Copy_Link": "Copiar link",
"Copy link address": "Copiar endereço do link",
"Copy link text": "Copiar texto do link",
"Copyright": "Direitos Autorais %s",
"Current_Version": "Versão Atual:",
"Cu&t": "Cor&tar",
......@@ -44,7 +45,7 @@
"Menu bar": "Barra de menus",
"Minimize": "Minimizar",
"More_spelling_suggestions": "Mais sugestões de grafia",
"Open_Link": "Abrir link",
"Open link": "Abrir link",
"New_Version": "Nova Versão:",
"No": "Não",
"No_suggestions": "Sem sugestões",
......
......@@ -21,7 +21,7 @@
"Close": "Закрыть",
"Connect": "Подключиться",
"&Copy": "&Копировать",
"Copy_Link": "Копировать ссылку",
"Copy link address": "Копировать ссылку",
"Copyright": "Copyright %s",
"Current_Version": "Текущая версия:",
"Cu&t": "В&ырезать",
......@@ -58,7 +58,7 @@
"Open DevTools": "Открыть DevTools",
"Open &DevTools": "Открыть &DevTools",
"Open_Language_Dictionary": "Открыть словарь",
"Open_Link": "Открыть ссылку",
"Open link": "Открыть ссылку",
"&Paste": "&Вставить",
"Quit": "Выйти",
"&Quit %s": "Вы&йти из %s",
......
import { ipcRenderer } from 'electron';
import setupContextMenuPreload from './preload/contextMenu';
import setupEventsPreload from './preload/events';
import setupJitsiPreload from './preload/jitsi';
import setupLinksPreload from './preload/links';
import setupNotificationsPreload from './preload/notifications';
import setupSidebarPreload from './preload/sidebar';
import SpellCheck from './preload/SpellCheck';
import setupSpellcheckingPreload from './preload/spellchecking';
setupEventsPreload(window);
setupJitsiPreload(window);
setupLinksPreload(window);
setupNotificationsPreload(window);
setupSidebarPreload(window);
setupContextMenuPreload();
setupEventsPreload();
setupJitsiPreload();
setupLinksPreload();
setupNotificationsPreload();
setupSidebarPreload();
setupSpellcheckingPreload();
window.reloadServer = () => ipcRenderer.sendToHost('reload-server');
// Prevent redirect to url when dragging in
window.document.addEventListener('dragover', (e) => e.preventDefault());
window.document.addEventListener('drop', (e) => e.preventDefault());
const spellChecker = new SpellCheck();
spellChecker.enable();
This diff is collapsed.
import { clipboard, remote, shell } from 'electron';
import i18n from '../i18n/index';
import { spellchecking } from './spellchecking';
const { dialog, getCurrentWebContents, getCurrentWindow, Menu } = remote;
const createSpellCheckingMenuTemplate = async({
isEditable,
selectionText,
}) => {
if (!isEditable) {
return [];
}
const corrections = spellchecking.getCorrections(selectionText);
const handleBrowserForLanguage = () => {
const callback = async(filePaths) => {
try {
await spellchecking.installDictionaries(filePaths);
} catch (error) {
dialog.showErrorBox(i18n.__('Error'), `${ i18n.__('Error copying dictionary file') }: ${ name }`);
console.error(error);
}
};
dialog.showOpenDialog(getCurrentWindow(), {
title: i18n.__('Open_Language_Dictionary'),
defaultPath: spellchecking.dictionariesPath,
filters: [
{ name: i18n.__('Dictionaries'), extensions: ['aff', 'dic'] },
{ name: i18n.__('All files'), extensions: ['*'] },
],
properties: ['openFile', 'multiSelections'],
}, callback);
};
return [
...(corrections ? [
...(corrections.length === 0 ? (
[
{
label: i18n.__('No_suggestions'),
enabled: false,
},
]
) : (
corrections.slice(0, 6).map((correction) => ({
label: correction,
click: () => getCurrentWebContents().replaceMisspelling(correction),
}))
)),
...(corrections.length > 6 ? [
{
label: i18n.__('More_spelling_suggestions'),
submenu: corrections.slice(6).map((correction) => ({
label: correction,
click: () => getCurrentWebContents().replaceMisspelling(correction),
})),
},
] : []),
{
type: 'separator',
},
] : []),
{
label: i18n.__('Spelling_languages'),
enabled: spellchecking.dictionaries.length > 0,
submenu: [
...spellchecking.dictionaries.map((dictionaryName) => ({
label: dictionaryName,
type: 'checkbox',
checked: spellchecking.enabledDictionaries.includes(dictionaryName),
click: ({ checked }) => (checked ?
spellchecking.enable(dictionaryName) :
spellchecking.disable(dictionaryName)),
})),
{
type: 'separator',
},
{
label: i18n.__('Browse_for_language'),
click: handleBrowserForLanguage,
},
],
},
{
type: 'separator',
},
];
};
const createImageMenuTemplate = ({
mediaType,
srcURL,
}) => (
mediaType === 'image' ?
[
{
label: i18n.__('Save image as...'),
click: () => getCurrentWebContents().downloadURL(srcURL),
},
{
type: 'separator',
},
] :
[]
);
const createLinkMenuTemplate = ({
linkURL,
linkText,
}) => (
linkURL ?
[
{
label: i18n.__('Open link'),
click: () => shell.openExternal(linkURL),
},
{
label: i18n.__('Copy link text'),
click: () => clipboard.write({ text: linkText, bookmark: linkText }),
enabled: !!linkText,
},
{
label: i18n.__('Copy link address'),
click: () => clipboard.write({ text: linkURL, bookmark: linkText }),
},
{
type: 'separator',
},
] :
[]
);
const createDefaultMenuTemplate = ({
editFlags: {
canUndo = false,
canRedo = false,
canCut = false,
canCopy = false,
canPaste = false,
canSelectAll = false,
} = {},
} = {}) => [
{
label: i18n.__('&Undo'),
role: 'undo',
accelerator: 'CommandOrControl+Z',
enabled: canUndo,
},
{
label: i18n.__('&Redo'),
role: 'redo',
accelerator: process.platform === 'win32' ? 'Control+Y' : 'CommandOrControl+Shift+Z',
enabled: canRedo,
},
{
type: 'separator',
},
{
label: i18n.__('Cu&t'),
role: 'cut',
accelerator: 'CommandOrControl+X',
enabled: canCut,
},
{
label: i18n.__('&Copy'),
role: 'copy',
accelerator: 'CommandOrControl+C',
enabled: canCopy,
},
{
label: i18n.__('&Paste'),
role: 'paste',
accelerator: 'CommandOrControl+V',
enabled: canPaste,
},
{
label: i18n.__('Select &all'),
role: 'selectall',
accelerator: 'CommandOrControl+A',
enabled: canSelectAll,
},
];
const createMenuTemplate = async(params) => [
...(await createSpellCheckingMenuTemplate(params)),
...(await createImageMenuTemplate(params)),
...(await createLinkMenuTemplate(params)),
...(await createDefaultMenuTemplate(params)),
];
export default () => {
getCurrentWebContents().on('context-menu', (event, params) => {
event.preventDefault();
(async() => {
const menu = Menu.buildFromTemplate(await createMenuTemplate(params));
menu.popup({ window: getCurrentWindow() });
})();
});
};
import { ipcRenderer } from 'electron';
const handleWindowEventTriggered = (window, eventName) => (e) => {
ipcRenderer.sendToHost(eventName, e.detail);
};
const handleTitleChange = (window) => {
const handleTitleChange = () => {
const { Meteor, RocketChat, Tracker } = window;
if (!Meteor || !RocketChat || !Tracker) {
......@@ -24,7 +19,7 @@ const handleTitleChange = (window) => {
};
const handleUserPresenceChange = (window) => {
const handleUserPresenceChange = () => {
const { Meteor, UserPresence } = window;
if (!Meteor || !UserPresence) {
......@@ -45,23 +40,18 @@ const handleUserPresenceChange = (window) => {
};
const preventUrlLoadingOnDrop = ({ document }) => {
document.addEventListener('dragover', (e) => e.preventDefault());
document.addEventListener('drop', (e) => e.preventDefault());
};
export default () => {
document.addEventListener('dragover', (event) => event.preventDefault());
document.addEventListener('drop', (event) => event.preventDefault());
export default (window) => {
const eventsListened = ['unread-changed', 'get-sourceId', 'user-status-manually-set'];
for (const eventName of eventsListened) {
window.addEventListener(eventName, handleWindowEventTriggered(window, eventName));
window.addEventListener(eventName, (event) => ipcRenderer.sendToHost(eventName, event.detail));
}
window.addEventListener('load', () => {
handleTitleChange(window);
handleUserPresenceChange(window);
handleTitleChange();
handleUserPresenceChange();
});
preventUrlLoadingOnDrop(window);
};
......@@ -32,7 +32,7 @@ const wrapWindowOpen = (defaultWindowOpen) => (href, frameName, features) => {
};
const pollJitsiIframe = ({ document }) => {
const pollJitsiIframe = () => {
const jitsiIframe = document.querySelector('iframe[id^=jitsiConference]');
if (!jitsiIframe) {
return;
......@@ -42,12 +42,12 @@ const pollJitsiIframe = ({ document }) => {
};
export default (window) => {
export default () => {
window.JitsiMeetElectron = JitsiMeetElectron;
window.open = wrapWindowOpen(window.open);
window.addEventListener('load', () => {
setInterval(() => pollJitsiIframe(window), 1000);
setInterval(pollJitsiIframe, 1000);
});
};
import { shell } from 'electron';
const handleAnchorClick = ({ Meteor }) => (event) => {
const handleAnchorClick = (event) => {
const a = event.target.closest('a');
if (!a) {
......@@ -27,6 +28,7 @@ const handleAnchorClick = ({ Meteor }) => (event) => {
return;
}
const { Meteor } = window;
const isInsideDomain = Meteor && RegExp(`^${ Meteor.absoluteUrl() }`).test(href);
const isRelative = !/^([a-z]+:)?\/\//.test(href);
if (isInsideDomain || isRelative) {
......@@ -38,8 +40,8 @@ const handleAnchorClick = ({ Meteor }) => (event) => {
};
export default (window) => {
export default () => {
window.addEventListener('load', () => {
window.document.addEventListener('click', handleAnchorClick(window), true);
document.addEventListener('click', handleAnchorClick, true);
});
};
......@@ -77,7 +77,7 @@ const handleNotificationClosed = (event, id) => {
};
export default (window) => {
export default () => {
window.Notification = Notification;
ipcRenderer.on('notification-shown', handleNotificationShown);
ipcRenderer.on('notification-clicked', handleNotificationClicked);
......
import { ipcRenderer } from 'electron';
const requestSidebarColor = ({ document, requestAnimationFrame }) => function pollSidebarColor() {
const requestSidebarColor = function pollSidebarColor() {
const sidebar = document.querySelector('.sidebar');
if (sidebar) {
const { color, background } = window.getComputedStyle(sidebar);
......@@ -21,6 +21,6 @@ const requestSidebarColor = ({ document, requestAnimationFrame }) => function po
requestAnimationFrame(pollSidebarColor);
};
export default (window) => {
ipcRenderer.on('request-sidebar-color', requestSidebarColor(window));
export default () => {
ipcRenderer.on('request-sidebar-color', requestSidebarColor);
};
import { remote, webFrame } from 'electron';
import jetpack from 'fs-jetpack';
import path from 'path';
import spellchecker from 'spellchecker';
const { app } = remote;
class SpellCheck {
constructor() {
this.dictionaries = [];
this.enabledDictionaries = [];
this.isMultiLanguage = false;
this.dictionariesPath = null;
}
async load() {
await this.loadDictionaries();
this.setDefaultEnabledDictionaries();
}
async loadDictionaries() {
const embeddedDictionaries = spellchecker.getAvailableDictionaries();
const directory = jetpack.cwd(app.getAppPath(), app.getAppPath().endsWith('app.asar') ? '..' : '.', 'dictionaries');
const installedDictionaries = (await directory.findAsync({ matching: '*.{aff,dic}' }))
.map((fileName) => path.basename(fileName, path.extname(fileName)));
this.dictionariesPath = directory.path();
this.dictionaries = Array.from(new Set([...embeddedDictionaries, ...installedDictionaries])).sort();
this.isMultiLanguage = embeddedDictionaries.length > 0 && process.platform !== 'win32';
}
setDefaultEnabledDictionaries() {
const selectedDictionaries = (() => {
try {
const enabledDictionaries = JSON.parse(localStorage.getItem('spellcheckerDictionaries'));
return Array.isArray(enabledDictionaries) ? enabledDictionaries.map(String) : null;
} catch (error) {
console.error(error);
return null;
}
})();
if (selectedDictionaries) {
this.enable(...selectedDictionaries);
return;
}
const userLanguage = localStorage.getItem('userLanguage');
if (userLanguage && this.enable(this.userLanguage)) {
return;
}
const navigatorLanguage = navigator.language;
if (this.enable(navigatorLanguage)) {
return;
}
if (this.enable('en_US')) {
return;
}
}
filterDictionaries(...dictionaries) {
return dictionaries.map((dictionary) => {
const matches = /^(\w+?)[-_](\w+)$/.exec(dictionary);
const dictionaries = matches ?
[`${ matches[1] }_${ matches[2] }`, `${ matches[1] }-${ matches[2] }`, matches[1]] :
[dictionary];
return dictionaries.find((dictionary) => this.dictionaries.includes(dictionary));
}).filter(Boolean);
}
enable(...dictionaries) {
dictionaries = this.filterDictionaries(dictionaries);
if (this.isMultiLanguage) {
this.enabledDictionaries = [
...this.enabledDictionaries,
...dictionaries,
];
} else {
this.enabledDictionaries = [dictionaries[0]];
}
localStorage.setItem('spellcheckerDictionaries', JSON.stringify(this.enabledDictionaries));
return this.enabledDictionaries.length > 0;
}
disable(...dictionaries) {
dictionaries = this.filterDictionaries(dictionaries);
this.enabledDictionaries = this.enabledDictionaries.filter((dictionary) => !dictionaries.includes(dictionary));
localStorage.setItem('spellcheckerDictionaries', JSON.stringify(this.enabledDictionaries));
}
isCorrect(text) {
if (!this.enabledDictionaries.length) {
return true;
}
return this.enabledDictionaries.every((dictionary) => {
spellchecker.setDictionary(dictionary, this.dictionariesPath);
return !spellchecker.isMisspelled(text);
});
}
getCorrections(text) {
text = text.trim();
if (text === '' || this.isCorrect(text)) {
return null;
}
return Array.from(new Set(
this.enabledDictionaries.flatMap((language) => {
spellchecker.setDictionary(language, this.dictionariesPath);
return spellchecker.getCorrectionsForMisspelling(text);
})
));
}
async installDictionaries(filePaths) {
for (const filePath of filePaths) {
const name = filePath.basename(filePath, filePath.extname(filePath));
const basename = filePath.basename(filePath);
const newPath = filePath.join(this.dictionariesPath, basename);
await jetpack.copyAsync(filePath, newPath);
if (!this.dictionaries.includes(name)) {
this.dictionaries.push(name);
}
}
}
}
export const spellchecking = new SpellCheck;
export default () => {
spellchecking.load();
const spellCheck = (text) => spellchecking.isCorrect(text);
webFrame.setSpellCheckProvider('', false, { spellCheck });
};