Embed options in manager (#828)
* Embed options in manager * fix indent again * Fix edit URL detected as manage URL when creating manager style from popup * Syntax, hash only, and prevent empty hash * Fix: move origin check to background * Rename eslintrc * Refactor: openURL * Add: fixme comment about openEditor * Fix: allow activating manager in other windows * Add: trimHash method * Fix: limit the scope of styleViaAPI * Breaking: add router, keep search params * Fix: focus options when activated * Add: some fixme * Fix: remove unused fixme * Fix: minor * Fix: remove unused message * Add: doc * Change: activate manager in other windows * Fix: make sure sender is available in getTabUrlPrefix * Add: openManage API * Change: reuse editor in openEditor * Fix: greedly pop the buffer * Fix: backward detection * Fix: remove unused important * Fix: remove unused workaround * Fix: avoid empty search param * Change: detect all kinds of manager in openManage * Fix: minor * Manage button text Co-authored-by: eight <eight04@gmail.com>
This commit is contained in:
parent
d3ee6d9498
commit
1f12d50aaf
|
@ -31,7 +31,7 @@ rules:
|
||||||
dot-location: [2, property]
|
dot-location: [2, property]
|
||||||
dot-notation: [0]
|
dot-notation: [0]
|
||||||
eol-last: [2]
|
eol-last: [2]
|
||||||
eqeqeq: [1, always]
|
eqeqeq: [1, smart]
|
||||||
func-call-spacing: [2, never]
|
func-call-spacing: [2, never]
|
||||||
func-name-matching: [0]
|
func-name-matching: [0]
|
||||||
func-names: [0]
|
func-names: [0]
|
||||||
|
@ -84,7 +84,7 @@ rules:
|
||||||
no-empty-function: [0]
|
no-empty-function: [0]
|
||||||
no-empty-pattern: [2]
|
no-empty-pattern: [2]
|
||||||
no-empty: [2, {allowEmptyCatch: true}]
|
no-empty: [2, {allowEmptyCatch: true}]
|
||||||
no-eq-null: [2]
|
no-eq-null: [0]
|
||||||
no-eval: [2]
|
no-eval: [2]
|
||||||
no-ex-assign: [2]
|
no-ex-assign: [2]
|
||||||
no-extend-native: [2]
|
no-extend-native: [2]
|
|
@ -398,11 +398,7 @@
|
||||||
"message": "Управление",
|
"message": "Управление",
|
||||||
"description": "Link to open the manage page."
|
"description": "Link to open the manage page."
|
||||||
},
|
},
|
||||||
"openOptionsManage": {
|
"openOptions": {
|
||||||
"message": "Прозорец за настройките",
|
|
||||||
"description": "Go to Options UI"
|
|
||||||
},
|
|
||||||
"openOptionsPopup": {
|
|
||||||
"message": "Настройки",
|
"message": "Настройки",
|
||||||
"description": "Go to Options UI"
|
"description": "Go to Options UI"
|
||||||
},
|
},
|
||||||
|
|
|
@ -723,11 +723,7 @@
|
||||||
"message": "Spravovat",
|
"message": "Spravovat",
|
||||||
"description": "Link to open the manage page."
|
"description": "Link to open the manage page."
|
||||||
},
|
},
|
||||||
"openOptionsManage": {
|
"openOptions": {
|
||||||
"message": "Možnosti rozhraní",
|
|
||||||
"description": "Go to Options UI"
|
|
||||||
},
|
|
||||||
"openOptionsPopup": {
|
|
||||||
"message": "Možnosti",
|
"message": "Možnosti",
|
||||||
"description": "Go to Options UI"
|
"description": "Go to Options UI"
|
||||||
},
|
},
|
||||||
|
|
|
@ -897,11 +897,7 @@
|
||||||
"message": "Verwalten",
|
"message": "Verwalten",
|
||||||
"description": "Link to open the manage page."
|
"description": "Link to open the manage page."
|
||||||
},
|
},
|
||||||
"openOptionsManage": {
|
"openOptions": {
|
||||||
"message": "Optionen",
|
|
||||||
"description": "Go to Options UI"
|
|
||||||
},
|
|
||||||
"openOptionsPopup": {
|
|
||||||
"message": "Optionen",
|
"message": "Optionen",
|
||||||
"description": "Go to Options UI"
|
"description": "Go to Options UI"
|
||||||
},
|
},
|
||||||
|
|
|
@ -927,11 +927,7 @@
|
||||||
"message": "Manage",
|
"message": "Manage",
|
||||||
"description": "Link to open the manage page."
|
"description": "Link to open the manage page."
|
||||||
},
|
},
|
||||||
"openOptionsManage": {
|
"openOptions": {
|
||||||
"message": "Options UI",
|
|
||||||
"description": "Go to Options UI"
|
|
||||||
},
|
|
||||||
"openOptionsPopup": {
|
|
||||||
"message": "Options",
|
"message": "Options",
|
||||||
"description": "Go to Options UI"
|
"description": "Go to Options UI"
|
||||||
},
|
},
|
||||||
|
|
|
@ -897,11 +897,7 @@
|
||||||
"message": "Administrar",
|
"message": "Administrar",
|
||||||
"description": "Link to open the manage page."
|
"description": "Link to open the manage page."
|
||||||
},
|
},
|
||||||
"openOptionsManage": {
|
"openOptions": {
|
||||||
"message": "Interfaz de opciones",
|
|
||||||
"description": "Go to Options UI"
|
|
||||||
},
|
|
||||||
"openOptionsPopup": {
|
|
||||||
"message": "Opciones",
|
"message": "Opciones",
|
||||||
"description": "Go to Options UI"
|
"description": "Go to Options UI"
|
||||||
},
|
},
|
||||||
|
|
|
@ -815,11 +815,7 @@
|
||||||
"message": "Halda",
|
"message": "Halda",
|
||||||
"description": "Link to open the manage page."
|
"description": "Link to open the manage page."
|
||||||
},
|
},
|
||||||
"openOptionsManage": {
|
"openOptions": {
|
||||||
"message": "Valikute liides",
|
|
||||||
"description": "Go to Options UI"
|
|
||||||
},
|
|
||||||
"openOptionsPopup": {
|
|
||||||
"message": "Valikud",
|
"message": "Valikud",
|
||||||
"description": "Go to Options UI"
|
"description": "Go to Options UI"
|
||||||
},
|
},
|
||||||
|
|
|
@ -913,11 +913,7 @@
|
||||||
"message": "Gestion",
|
"message": "Gestion",
|
||||||
"description": "Link to open the manage page."
|
"description": "Link to open the manage page."
|
||||||
},
|
},
|
||||||
"openOptionsManage": {
|
"openOptions": {
|
||||||
"message": "Paramètres d'interface graphique",
|
|
||||||
"description": "Go to Options UI"
|
|
||||||
},
|
|
||||||
"openOptionsPopup": {
|
|
||||||
"message": "Paramètres",
|
"message": "Paramètres",
|
||||||
"description": "Go to Options UI"
|
"description": "Go to Options UI"
|
||||||
},
|
},
|
||||||
|
|
|
@ -591,11 +591,7 @@
|
||||||
"message": "ניהול",
|
"message": "ניהול",
|
||||||
"description": "Link to open the manage page."
|
"description": "Link to open the manage page."
|
||||||
},
|
},
|
||||||
"openOptionsManage": {
|
"openOptions": {
|
||||||
"message": "אפשרויות UI",
|
|
||||||
"description": "Go to Options UI"
|
|
||||||
},
|
|
||||||
"openOptionsPopup": {
|
|
||||||
"message": "אפשרויות",
|
"message": "אפשרויות",
|
||||||
"description": "Go to Options UI"
|
"description": "Go to Options UI"
|
||||||
},
|
},
|
||||||
|
|
|
@ -677,11 +677,7 @@
|
||||||
"message": "Kezelés",
|
"message": "Kezelés",
|
||||||
"description": "Link to open the manage page."
|
"description": "Link to open the manage page."
|
||||||
},
|
},
|
||||||
"openOptionsManage": {
|
"openOptions": {
|
||||||
"message": "A beállítások felülete",
|
|
||||||
"description": "Go to Options UI"
|
|
||||||
},
|
|
||||||
"openOptionsPopup": {
|
|
||||||
"message": "Beállítások",
|
"message": "Beállítások",
|
||||||
"description": "Go to Options UI"
|
"description": "Go to Options UI"
|
||||||
},
|
},
|
||||||
|
|
|
@ -639,11 +639,7 @@
|
||||||
"message": "Gestisci gli stili installati",
|
"message": "Gestisci gli stili installati",
|
||||||
"description": "Link to open the manage page."
|
"description": "Link to open the manage page."
|
||||||
},
|
},
|
||||||
"openOptionsManage": {
|
"openOptions": {
|
||||||
"message": "Opzioni UI",
|
|
||||||
"description": "Go to Options UI"
|
|
||||||
},
|
|
||||||
"openOptionsPopup": {
|
|
||||||
"message": "Opzioni",
|
"message": "Opzioni",
|
||||||
"description": "Go to Options UI"
|
"description": "Go to Options UI"
|
||||||
},
|
},
|
||||||
|
|
|
@ -913,11 +913,7 @@
|
||||||
"message": "管理",
|
"message": "管理",
|
||||||
"description": "Link to open the manage page."
|
"description": "Link to open the manage page."
|
||||||
},
|
},
|
||||||
"openOptionsManage": {
|
"openOptions": {
|
||||||
"message": "オプション UI",
|
|
||||||
"description": "Go to Options UI"
|
|
||||||
},
|
|
||||||
"openOptionsPopup": {
|
|
||||||
"message": "オプション",
|
"message": "オプション",
|
||||||
"description": "Go to Options UI"
|
"description": "Go to Options UI"
|
||||||
},
|
},
|
||||||
|
|
|
@ -901,11 +901,7 @@
|
||||||
"message": "Beheren",
|
"message": "Beheren",
|
||||||
"description": "Link to open the manage page."
|
"description": "Link to open the manage page."
|
||||||
},
|
},
|
||||||
"openOptionsManage": {
|
"openOptions": {
|
||||||
"message": "Opties",
|
|
||||||
"description": "Go to Options UI"
|
|
||||||
},
|
|
||||||
"openOptionsPopup": {
|
|
||||||
"message": "Opties",
|
"message": "Opties",
|
||||||
"description": "Go to Options UI"
|
"description": "Go to Options UI"
|
||||||
},
|
},
|
||||||
|
|
|
@ -917,11 +917,7 @@
|
||||||
"message": "Zarządzaj",
|
"message": "Zarządzaj",
|
||||||
"description": "Link to open the manage page."
|
"description": "Link to open the manage page."
|
||||||
},
|
},
|
||||||
"openOptionsManage": {
|
"openOptions": {
|
||||||
"message": "Opcje interfejsu",
|
|
||||||
"description": "Go to Options UI"
|
|
||||||
},
|
|
||||||
"openOptionsPopup": {
|
|
||||||
"message": "Opcje",
|
"message": "Opcje",
|
||||||
"description": "Go to Options UI"
|
"description": "Go to Options UI"
|
||||||
},
|
},
|
||||||
|
|
|
@ -241,11 +241,7 @@
|
||||||
"message": "Nenhum estilo instalado para este site.",
|
"message": "Nenhum estilo instalado para este site.",
|
||||||
"description": "Text displayed when no styles are installed for the current site"
|
"description": "Text displayed when no styles are installed for the current site"
|
||||||
},
|
},
|
||||||
"openManage": {
|
"openOptions": {
|
||||||
"message": "Gerenciar estilos instalados",
|
|
||||||
"description": "Link to open the manage page."
|
|
||||||
},
|
|
||||||
"openOptionsPopup": {
|
|
||||||
"message": "Opções",
|
"message": "Opções",
|
||||||
"description": "Go to Options UI"
|
"description": "Go to Options UI"
|
||||||
},
|
},
|
||||||
|
|
|
@ -673,11 +673,7 @@
|
||||||
"message": "Gerir",
|
"message": "Gerir",
|
||||||
"description": "Link to open the manage page."
|
"description": "Link to open the manage page."
|
||||||
},
|
},
|
||||||
"openOptionsManage": {
|
"openOptions": {
|
||||||
"message": "interface de Opções",
|
|
||||||
"description": "Go to Options UI"
|
|
||||||
},
|
|
||||||
"openOptionsPopup": {
|
|
||||||
"message": "Opções",
|
"message": "Opções",
|
||||||
"description": "Go to Options UI"
|
"description": "Go to Options UI"
|
||||||
},
|
},
|
||||||
|
|
|
@ -617,11 +617,7 @@
|
||||||
"message": "Managerul",
|
"message": "Managerul",
|
||||||
"description": "Link to open the manage page."
|
"description": "Link to open the manage page."
|
||||||
},
|
},
|
||||||
"openOptionsManage": {
|
"openOptions": {
|
||||||
"message": "UI cu opțiuni",
|
|
||||||
"description": "Go to Options UI"
|
|
||||||
},
|
|
||||||
"openOptionsPopup": {
|
|
||||||
"message": "Opțiuni",
|
"message": "Opțiuni",
|
||||||
"description": "Go to Options UI"
|
"description": "Go to Options UI"
|
||||||
},
|
},
|
||||||
|
|
|
@ -921,11 +921,7 @@
|
||||||
"message": "Менеджер",
|
"message": "Менеджер",
|
||||||
"description": "Link to open the manage page."
|
"description": "Link to open the manage page."
|
||||||
},
|
},
|
||||||
"openOptionsManage": {
|
"openOptions": {
|
||||||
"message": "Настройки",
|
|
||||||
"description": "Go to Options UI"
|
|
||||||
},
|
|
||||||
"openOptionsPopup": {
|
|
||||||
"message": "Настройки",
|
"message": "Настройки",
|
||||||
"description": "Go to Options UI"
|
"description": "Go to Options UI"
|
||||||
},
|
},
|
||||||
|
|
|
@ -871,11 +871,7 @@
|
||||||
"message": "Hantera installerade stilar",
|
"message": "Hantera installerade stilar",
|
||||||
"description": "Link to open the manage page."
|
"description": "Link to open the manage page."
|
||||||
},
|
},
|
||||||
"openOptionsManage": {
|
"openOptions": {
|
||||||
"message": "Alternativ UI",
|
|
||||||
"description": "Go to Options UI"
|
|
||||||
},
|
|
||||||
"openOptionsPopup": {
|
|
||||||
"message": "Alternativ",
|
"message": "Alternativ",
|
||||||
"description": "Go to Options UI"
|
"description": "Go to Options UI"
|
||||||
},
|
},
|
||||||
|
|
|
@ -917,11 +917,7 @@
|
||||||
"message": "管理样式",
|
"message": "管理样式",
|
||||||
"description": "Link to open the manage page."
|
"description": "Link to open the manage page."
|
||||||
},
|
},
|
||||||
"openOptionsManage": {
|
"openOptions": {
|
||||||
"message": "设置用户界面",
|
|
||||||
"description": "Go to Options UI"
|
|
||||||
},
|
|
||||||
"openOptionsPopup": {
|
|
||||||
"message": "设置用户界面",
|
"message": "设置用户界面",
|
||||||
"description": "Go to Options UI"
|
"description": "Go to Options UI"
|
||||||
},
|
},
|
||||||
|
|
|
@ -917,11 +917,7 @@
|
||||||
"message": "管理已安裝樣式",
|
"message": "管理已安裝樣式",
|
||||||
"description": "Link to open the manage page."
|
"description": "Link to open the manage page."
|
||||||
},
|
},
|
||||||
"openOptionsManage": {
|
"openOptions": {
|
||||||
"message": "選項介面",
|
|
||||||
"description": "Go to Options UI"
|
|
||||||
},
|
|
||||||
"openOptionsPopup": {
|
|
||||||
"message": "選項",
|
"message": "選項",
|
||||||
"description": "Go to Options UI"
|
"description": "Go to Options UI"
|
||||||
},
|
},
|
||||||
|
|
|
@ -1,6 +1,8 @@
|
||||||
/* global download prefs openURL FIREFOX CHROME VIVALDI
|
/* global download prefs openURL FIREFOX CHROME VIVALDI
|
||||||
debounce URLS ignoreChromeError getTab
|
debounce URLS ignoreChromeError getTab
|
||||||
styleManager msg navigatorUtil iconUtil workerUtil contentScripts sync */
|
styleManager msg navigatorUtil iconUtil workerUtil contentScripts sync
|
||||||
|
findExistTab createTab activateTab isTabReplaceable getActiveTab */
|
||||||
|
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
// eslint-disable-next-line no-var
|
// eslint-disable-next-line no-var
|
||||||
|
@ -28,7 +30,11 @@ window.API_METHODS = Object.assign(window.API_METHODS || {}, {
|
||||||
removeExclusion: styleManager.removeExclusion,
|
removeExclusion: styleManager.removeExclusion,
|
||||||
|
|
||||||
getTabUrlPrefix() {
|
getTabUrlPrefix() {
|
||||||
return this.sender.tab.url.match(/^([\w-]+:\/+[^/#]+)/)[1];
|
const {url} = this.sender.tab;
|
||||||
|
if (url.startsWith(URLS.ownOrigin)) {
|
||||||
|
return 'stylus';
|
||||||
|
}
|
||||||
|
return url.match(/^([\w-]+:\/+[^/#]+)/)[1];
|
||||||
},
|
},
|
||||||
|
|
||||||
download(msg) {
|
download(msg) {
|
||||||
|
@ -69,7 +75,9 @@ window.API_METHODS = Object.assign(window.API_METHODS || {}, {
|
||||||
syncStop: sync.stop,
|
syncStop: sync.stop,
|
||||||
syncNow: sync.syncNow,
|
syncNow: sync.syncNow,
|
||||||
getSyncStatus: sync.getStatus,
|
getSyncStatus: sync.getStatus,
|
||||||
syncLogin: sync.login
|
syncLogin: sync.login,
|
||||||
|
|
||||||
|
openManage
|
||||||
});
|
});
|
||||||
|
|
||||||
// eslint-disable-next-line no-var
|
// eslint-disable-next-line no-var
|
||||||
|
@ -174,9 +182,8 @@ chrome.runtime.onInstalled.addListener(({reason}) => {
|
||||||
// *************************************************************************
|
// *************************************************************************
|
||||||
// browser commands
|
// browser commands
|
||||||
browserCommands = {
|
browserCommands = {
|
||||||
openManage() {
|
openManage,
|
||||||
openURL({url: 'manage.html'});
|
openOptions: () => openManage({options: true}),
|
||||||
},
|
|
||||||
styleDisableAll(info) {
|
styleDisableAll(info) {
|
||||||
prefs.set('disableAll', info ? info.checked : !prefs.get('disableAll'));
|
prefs.set('disableAll', info ? info.checked : !prefs.get('disableAll'));
|
||||||
},
|
},
|
||||||
|
@ -197,6 +204,10 @@ contextMenus = {
|
||||||
title: 'openStylesManager',
|
title: 'openStylesManager',
|
||||||
click: browserCommands.openManage,
|
click: browserCommands.openManage,
|
||||||
},
|
},
|
||||||
|
'open-options': {
|
||||||
|
title: 'openOptions',
|
||||||
|
click: browserCommands.openOptions,
|
||||||
|
},
|
||||||
'editor.contextDelete': {
|
'editor.contextDelete': {
|
||||||
presentIf: () => !FIREFOX && prefs.get('editor.contextDelete'),
|
presentIf: () => !FIREFOX && prefs.get('editor.contextDelete'),
|
||||||
title: 'editDeleteText',
|
title: 'editDeleteText',
|
||||||
|
@ -388,15 +399,55 @@ function onRuntimeMessage(msg, sender) {
|
||||||
return fn.apply(context, msg.args);
|
return fn.apply(context, msg.args);
|
||||||
}
|
}
|
||||||
|
|
||||||
// FIXME: popup.js also open editor but it doesn't use this API.
|
function openEditor(params) {
|
||||||
function openEditor({id}) {
|
/* Open the editor. Activate if it is already opened
|
||||||
let url = '/edit.html';
|
|
||||||
if (id) {
|
params: {
|
||||||
url += `?id=${id}`;
|
id?: Number,
|
||||||
|
domain?: String,
|
||||||
|
'url-prefix'?: String
|
||||||
}
|
}
|
||||||
if (chrome.windows && prefs.get('openEditInWindow')) {
|
*/
|
||||||
chrome.windows.create(Object.assign({url}, prefs.get('windowPosition')));
|
const searchParams = new URLSearchParams();
|
||||||
} else {
|
for (const key in params) {
|
||||||
openURL({url});
|
searchParams.set(key, params[key]);
|
||||||
}
|
}
|
||||||
|
const search = searchParams.toString();
|
||||||
|
return openURL({
|
||||||
|
url: 'edit.html' + (search && `?${search}`),
|
||||||
|
newWindow: prefs.get('openEditInWindow'),
|
||||||
|
windowPosition: prefs.get('windowPosition'),
|
||||||
|
currentWindow: null
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function openManage({options = false, search} = {}) {
|
||||||
|
let url = chrome.runtime.getURL('manage.html');
|
||||||
|
if (search) {
|
||||||
|
url += `?search=${encodeURIComponent(search)}`;
|
||||||
|
}
|
||||||
|
if (options) {
|
||||||
|
url += '#stylus-options';
|
||||||
|
}
|
||||||
|
return findExistTab({
|
||||||
|
url,
|
||||||
|
currentWindow: null,
|
||||||
|
ignoreHash: true,
|
||||||
|
ignoreSearch: true
|
||||||
|
})
|
||||||
|
.then(tab => {
|
||||||
|
if (tab) {
|
||||||
|
return Promise.all([
|
||||||
|
activateTab(tab),
|
||||||
|
tab.url !== url && msg.sendTab(tab.id, {method: 'pushState', url})
|
||||||
|
.catch(console.error)
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
return getActiveTab().then(tab => {
|
||||||
|
if (isTabReplaceable(tab, url)) {
|
||||||
|
return activateTab(tab, {url});
|
||||||
|
}
|
||||||
|
return createTab({url});
|
||||||
|
});
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -53,7 +53,7 @@ const contentScripts = (() => {
|
||||||
}
|
}
|
||||||
|
|
||||||
function injectToAllTabs() {
|
function injectToAllTabs() {
|
||||||
return queryTabs().then(tabs => {
|
return queryTabs({}).then(tabs => {
|
||||||
for (const tab of tabs) {
|
for (const tab of tabs) {
|
||||||
// skip lazy-loaded aka unloaded tabs that seem to start loading on message in FF
|
// skip lazy-loaded aka unloaded tabs that seem to start loading on message in FF
|
||||||
if (tab.width) {
|
if (tab.width) {
|
||||||
|
|
|
@ -202,18 +202,20 @@ const APPLY = (() => {
|
||||||
}
|
}
|
||||||
|
|
||||||
function applyOnMessage(request) {
|
function applyOnMessage(request) {
|
||||||
if (request.method === 'ping') {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
if (STYLE_VIA_API) {
|
if (STYLE_VIA_API) {
|
||||||
if (request.method === 'urlChanged') {
|
if (request.method === 'urlChanged') {
|
||||||
request.method = 'styleReplaceAll';
|
request.method = 'styleReplaceAll';
|
||||||
}
|
}
|
||||||
API.styleViaAPI(request);
|
if (/^(style|updateCount)/.test(request.method)) {
|
||||||
return;
|
API.styleViaAPI(request);
|
||||||
|
return;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
switch (request.method) {
|
switch (request.method) {
|
||||||
|
case 'ping':
|
||||||
|
return true;
|
||||||
|
|
||||||
case 'styleDeleted':
|
case 'styleDeleted':
|
||||||
styleInjector.remove(request.style.id);
|
styleInjector.remove(request.style.id);
|
||||||
break;
|
break;
|
||||||
|
@ -273,7 +275,11 @@ const APPLY = (() => {
|
||||||
if (parentDomain) {
|
if (parentDomain) {
|
||||||
return Promise.resolve();
|
return Promise.resolve();
|
||||||
}
|
}
|
||||||
return API.getTabUrlPrefix()
|
return msg.send({
|
||||||
|
method: 'invokeAPI',
|
||||||
|
name: 'getTabUrlPrefix',
|
||||||
|
args: []
|
||||||
|
})
|
||||||
.then(newDomain => {
|
.then(newDomain => {
|
||||||
parentDomain = newDomain;
|
parentDomain = newDomain;
|
||||||
});
|
});
|
||||||
|
|
|
@ -66,7 +66,7 @@ const regExpTester = (() => {
|
||||||
return rxData;
|
return rxData;
|
||||||
});
|
});
|
||||||
const getMatchInfo = m => m && {text: m[0], pos: m.index};
|
const getMatchInfo = m => m && {text: m[0], pos: m.index};
|
||||||
queryTabs().then(tabs => {
|
queryTabs({}).then(tabs => {
|
||||||
const supported = tabs.map(tab => tab.url)
|
const supported = tabs.map(tab => tab.url)
|
||||||
.filter(url => URLS.supported(url));
|
.filter(url => URLS.supported(url));
|
||||||
const unique = [...new Set(supported).values()];
|
const unique = [...new Set(supported).values()];
|
||||||
|
|
173
js/messaging.js
173
js/messaging.js
|
@ -1,6 +1,7 @@
|
||||||
/* exported getActiveTab onTabReady stringAsRegExp getTabRealURL openURL
|
/* exported getActiveTab onTabReady stringAsRegExp getTabRealURL openURL
|
||||||
getStyleWithNoCode tryRegExp sessionStorageHash download deepEqual
|
getStyleWithNoCode tryRegExp sessionStorageHash download deepEqual
|
||||||
closeCurrentTab capitalize */
|
closeCurrentTab capitalize CHROME_HAS_BORDER_BUG */
|
||||||
|
/* global promisify */
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
const CHROME = Boolean(chrome.app) && parseInt(navigator.userAgent.match(/Chrom\w+\/(?:\d+\.){2}(\d+)|$/)[1]);
|
const CHROME = Boolean(chrome.app) && parseInt(navigator.userAgent.match(/Chrom\w+\/(?:\d+\.){2}(\d+)|$/)[1]);
|
||||||
|
@ -28,6 +29,7 @@ if (!CHROME && !chrome.browserAction.openPopup) {
|
||||||
const URLS = {
|
const URLS = {
|
||||||
ownOrigin: chrome.runtime.getURL(''),
|
ownOrigin: chrome.runtime.getURL(''),
|
||||||
|
|
||||||
|
// FIXME delete?
|
||||||
optionsUI: [
|
optionsUI: [
|
||||||
chrome.runtime.getURL('options.html'),
|
chrome.runtime.getURL('options.html'),
|
||||||
'chrome://extensions/?options=' + chrome.runtime.id,
|
'chrome://extensions/?options=' + chrome.runtime.id,
|
||||||
|
@ -91,12 +93,13 @@ if (IS_BG) {
|
||||||
// Object.defineProperty(window, 'localStorage', {value: {}});
|
// Object.defineProperty(window, 'localStorage', {value: {}});
|
||||||
// Object.defineProperty(window, 'sessionStorage', {value: {}});
|
// Object.defineProperty(window, 'sessionStorage', {value: {}});
|
||||||
|
|
||||||
function queryTabs(options = {}) {
|
const createTab = promisify(chrome.tabs.create.bind(chrome.tabs));
|
||||||
return new Promise(resolve =>
|
const queryTabs = promisify(chrome.tabs.query.bind(chrome.tabs));
|
||||||
chrome.tabs.query(options, tabs =>
|
const updateTab = promisify(chrome.tabs.update.bind(chrome.tabs));
|
||||||
resolve(tabs)));
|
const moveTabs = promisify(chrome.tabs.move.bind(chrome.tabs));
|
||||||
}
|
// FIXME: is it possible that chrome.windows is undefined?
|
||||||
|
const updateWindow = promisify(chrome.windows.update.bind(chrome.windows));
|
||||||
|
const createWindow = promisify(chrome.windows.create.bind(chrome.windows));
|
||||||
|
|
||||||
function getTab(id) {
|
function getTab(id) {
|
||||||
return new Promise(resolve =>
|
return new Promise(resolve =>
|
||||||
|
@ -192,6 +195,39 @@ function onTabReady(tabOrId) {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function urlToMatchPattern(url, ignoreSearch) {
|
||||||
|
// https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/Match_patterns
|
||||||
|
if (!/^(http|https|ws|wss|ftp|data|file)$/.test(url.protocol)) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
if (ignoreSearch) {
|
||||||
|
return [
|
||||||
|
`${url.protocol}//${url.hostname}/${url.pathname}`,
|
||||||
|
`${url.protocol}//${url.hostname}/${url.pathname}?*`
|
||||||
|
];
|
||||||
|
}
|
||||||
|
// FIXME: is %2f allowed in pathname and search?
|
||||||
|
return `${url.protocol}//${url.hostname}/${url.pathname}${url.search}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
function findExistTab({url, currentWindow, ignoreHash = true, ignoreSearch = false}) {
|
||||||
|
url = new URL(url);
|
||||||
|
return queryTabs({url: urlToMatchPattern(url, ignoreSearch), currentWindow})
|
||||||
|
// FIXME: is tab.url always normalized?
|
||||||
|
.then(tabs => tabs.find(matchTab));
|
||||||
|
|
||||||
|
function matchTab(tab) {
|
||||||
|
const tabUrl = new URL(tab.url);
|
||||||
|
return tabUrl.protocol === url.protocol &&
|
||||||
|
tabUrl.username === url.username &&
|
||||||
|
tabUrl.password === url.password &&
|
||||||
|
tabUrl.hostname === url.hostname &&
|
||||||
|
tabUrl.port === url.port &&
|
||||||
|
tabUrl.pathname === url.pathname &&
|
||||||
|
(ignoreSearch || tabUrl.search === url.search) &&
|
||||||
|
(ignoreHash || tabUrl.hash === url.hash);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Opens a tab or activates an existing one,
|
* Opens a tab or activates an existing one,
|
||||||
|
@ -211,72 +247,77 @@ function onTabReady(tabOrId) {
|
||||||
* JSONifiable data to be sent to the tab via sendMessage()
|
* JSONifiable data to be sent to the tab via sendMessage()
|
||||||
* @returns {Promise<Tab>} Promise that resolves to the opened/activated tab
|
* @returns {Promise<Tab>} Promise that resolves to the opened/activated tab
|
||||||
*/
|
*/
|
||||||
function openURL({
|
function openURL(options) {
|
||||||
// https://github.com/eslint/eslint/issues/10639
|
if (typeof options === 'string') {
|
||||||
// eslint-disable-next-line no-undef
|
options = {url: options};
|
||||||
url = arguments[0],
|
|
||||||
index,
|
|
||||||
active,
|
|
||||||
currentWindow = true,
|
|
||||||
}) {
|
|
||||||
url = url.includes('://') ? url : chrome.runtime.getURL(url);
|
|
||||||
// [some] chromium forks don't handle their fake branded protocols
|
|
||||||
url = url.replace(/^(opera|vivaldi)/, 'chrome');
|
|
||||||
// FF doesn't handle moz-extension:// URLs (bug)
|
|
||||||
// FF decodes %2F in encoded parameters (bug)
|
|
||||||
// API doesn't handle the hash-fragment part
|
|
||||||
const urlQuery =
|
|
||||||
url.startsWith('moz-extension') ||
|
|
||||||
url.startsWith('chrome:') ?
|
|
||||||
undefined :
|
|
||||||
FIREFOX && url.includes('%2F') ?
|
|
||||||
url.replace(/%2F.*/, '*').replace(/#.*/, '') :
|
|
||||||
url.replace(/#.*/, '');
|
|
||||||
|
|
||||||
return queryTabs({url: urlQuery, currentWindow}).then(maybeSwitch);
|
|
||||||
|
|
||||||
function maybeSwitch(tabs = []) {
|
|
||||||
const urlWithSlash = url + '/';
|
|
||||||
const urlFF = FIREFOX && url.replace(/%2F/g, '/');
|
|
||||||
const tab = tabs.find(({url: u}) => u === url || u === urlFF || u === urlWithSlash);
|
|
||||||
if (!tab) {
|
|
||||||
return getActiveTab().then(maybeReplace);
|
|
||||||
}
|
|
||||||
if (index !== undefined && tab.index !== index) {
|
|
||||||
chrome.tabs.move(tab.id, {index});
|
|
||||||
}
|
|
||||||
return activateTab(tab);
|
|
||||||
}
|
}
|
||||||
|
let {
|
||||||
|
url,
|
||||||
|
index,
|
||||||
|
active,
|
||||||
|
currentWindow = true,
|
||||||
|
newWindow = false,
|
||||||
|
windowPosition
|
||||||
|
} = options;
|
||||||
|
|
||||||
// update current NTP or about:blank
|
if (!url.includes('://')) {
|
||||||
// except when 'url' is chrome:// or chrome-extension:// in incognito
|
url = chrome.runtime.getURL(url);
|
||||||
function maybeReplace(tab) {
|
|
||||||
const chromeInIncognito = tab && tab.incognito && url.startsWith('chrome');
|
|
||||||
const emptyTab = tab && URLS.emptyTab.includes(tab.url);
|
|
||||||
if (emptyTab && !chromeInIncognito) {
|
|
||||||
return new Promise(resolve =>
|
|
||||||
chrome.tabs.update({url}, resolve));
|
|
||||||
}
|
|
||||||
const options = {url, index, active};
|
|
||||||
// FF57+ supports openerTabId, but not in Android (indicated by the absence of chrome.windows)
|
|
||||||
if (tab && (!FIREFOX || FIREFOX >= 57 && chrome.windows) && !chromeInIncognito) {
|
|
||||||
options.openerTabId = tab.id;
|
|
||||||
}
|
|
||||||
return new Promise(resolve =>
|
|
||||||
chrome.tabs.create(options, resolve));
|
|
||||||
}
|
}
|
||||||
|
return findExistTab({url, currentWindow}).then(tab => {
|
||||||
|
if (tab) {
|
||||||
|
// update url if only hash is different?
|
||||||
|
// we can't update URL if !url.includes('#') since it refreshes the page
|
||||||
|
// FIXME: should we move the tab (i.e. specifying index)?
|
||||||
|
if (tab.url !== url && tab.url.split('#')[0] === url.split('#')[0] &&
|
||||||
|
url.includes('#')) {
|
||||||
|
return activateTab(tab, {url, index});
|
||||||
|
}
|
||||||
|
return activateTab(tab, {index});
|
||||||
|
}
|
||||||
|
if (newWindow) {
|
||||||
|
return createWindow(Object.assign({url}, windowPosition));
|
||||||
|
}
|
||||||
|
return getActiveTab().then(tab => {
|
||||||
|
if (isTabReplaceable(tab, url)) {
|
||||||
|
// don't move the tab in this case
|
||||||
|
return activateTab(tab, {url});
|
||||||
|
}
|
||||||
|
const options = {url, index, active};
|
||||||
|
// FF57+ supports openerTabId, but not in Android (indicated by the absence of chrome.windows)
|
||||||
|
// FIXME: is it safe to assume that the current tab is the opener?
|
||||||
|
if (tab && !tab.incognito && (!FIREFOX || FIREFOX >= 57 && chrome.windows)) {
|
||||||
|
options.openerTabId = tab.id;
|
||||||
|
}
|
||||||
|
return createTab(options);
|
||||||
|
});
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// replace empty tab (NTP or about:blank)
|
||||||
|
// except when new URL is chrome:// or chrome-extension:// and the empty tab is
|
||||||
|
// in incognito
|
||||||
|
function isTabReplaceable(tab, newUrl) {
|
||||||
|
if (!tab || !URLS.emptyTab.includes(tab.url)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
// FIXME: but why?
|
||||||
|
if (tab.incognito && newUrl.startsWith('chrome')) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
function activateTab(tab) {
|
function activateTab(tab, {url, index} = {}) {
|
||||||
|
const options = {active: true};
|
||||||
|
if (url) {
|
||||||
|
options.url = url;
|
||||||
|
}
|
||||||
return Promise.all([
|
return Promise.all([
|
||||||
new Promise(resolve => {
|
updateTab(tab.id, options),
|
||||||
chrome.tabs.update(tab.id, {active: true}, resolve);
|
updateWindow(tab.windowId, {focused: true}),
|
||||||
}),
|
index != null && moveTabs(tab.id, {index})
|
||||||
chrome.windows && new Promise(resolve => {
|
])
|
||||||
chrome.windows.update(tab.windowId, {focused: true}, resolve);
|
.then(() => tab);
|
||||||
}),
|
|
||||||
]).then(([tab]) => tab);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
99
js/router.js
Normal file
99
js/router.js
Normal file
|
@ -0,0 +1,99 @@
|
||||||
|
/* global deepEqual msg */
|
||||||
|
/* exported router */
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
const router = (() => {
|
||||||
|
const buffer = [];
|
||||||
|
const watchers = [];
|
||||||
|
document.addEventListener('DOMContentLoaded', () => update());
|
||||||
|
window.addEventListener('popstate', () => update());
|
||||||
|
window.addEventListener('hashchange', () => update());
|
||||||
|
msg.on(e => {
|
||||||
|
if (e.method === 'pushState' && e.url !== location.href) {
|
||||||
|
history.pushState(history.state, null, e.url);
|
||||||
|
update();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return {watch, updateSearch, getSearch, updateHash};
|
||||||
|
|
||||||
|
function watch(options, callback) {
|
||||||
|
/* Watch search params or hash and get notified on change.
|
||||||
|
|
||||||
|
options: {search?: Array<key: String>, hash?: String}
|
||||||
|
callback: (Array<value: String | null> | Boolean) => void
|
||||||
|
|
||||||
|
`hash` should always start with '#'.
|
||||||
|
When watching search params, callback receives a list of values.
|
||||||
|
When watching hash, callback receives a boolean.
|
||||||
|
*/
|
||||||
|
watchers.push({options, callback});
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateSearch(key, value) {
|
||||||
|
const search = new URLSearchParams(location.search.replace(/^\?/, ''));
|
||||||
|
if (!value) {
|
||||||
|
search.delete(key);
|
||||||
|
} else {
|
||||||
|
search.set(key, value);
|
||||||
|
}
|
||||||
|
const finalSearch = search.toString();
|
||||||
|
if (finalSearch) {
|
||||||
|
history.replaceState(history.state, null, `?${finalSearch}${location.hash}`);
|
||||||
|
} else {
|
||||||
|
history.replaceState(history.state, null, `${location.pathname}${location.hash}`);
|
||||||
|
}
|
||||||
|
update(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateHash(hash) {
|
||||||
|
/* hash: String
|
||||||
|
|
||||||
|
Send an empty string to remove the hash.
|
||||||
|
*/
|
||||||
|
if (buffer.length > 1) {
|
||||||
|
if (!hash && !buffer[buffer.length - 2].includes('#') ||
|
||||||
|
hash && buffer[buffer.length - 2].endsWith(hash)) {
|
||||||
|
history.back();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!hash) {
|
||||||
|
hash = ' ';
|
||||||
|
}
|
||||||
|
history.pushState(history.state, null, hash);
|
||||||
|
update();
|
||||||
|
}
|
||||||
|
|
||||||
|
function getSearch(key) {
|
||||||
|
return new URLSearchParams(location.search.replace(/^\?/, '')).get(key);
|
||||||
|
}
|
||||||
|
|
||||||
|
function update(replace) {
|
||||||
|
if (!buffer.length) {
|
||||||
|
buffer.push(location.href);
|
||||||
|
} else if (buffer[buffer.length - 1] === location.href) {
|
||||||
|
return;
|
||||||
|
} else if (replace) {
|
||||||
|
buffer[buffer.length - 1] = location.href;
|
||||||
|
} else if (buffer.length > 1 && buffer[buffer.length - 2] === location.href) {
|
||||||
|
buffer.pop();
|
||||||
|
} else {
|
||||||
|
buffer.push(location.href);
|
||||||
|
}
|
||||||
|
for (const {options, callback} of watchers) {
|
||||||
|
let state;
|
||||||
|
if (options.hash) {
|
||||||
|
state = options.hash === location.hash;
|
||||||
|
} else if (options.search) {
|
||||||
|
// TODO: remove .replace(/^\?/, '') when minimum_chrome_version >= 52 (https://crbug.com/601425)
|
||||||
|
const search = new URLSearchParams(location.search.replace(/^\?/, ''));
|
||||||
|
state = options.search.map(key => search.get(key));
|
||||||
|
}
|
||||||
|
if (!deepEqual(state, options.currentState)) {
|
||||||
|
options.currentState = state;
|
||||||
|
callback(state);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})();
|
|
@ -152,6 +152,7 @@
|
||||||
<script src="js/messaging.js"></script>
|
<script src="js/messaging.js"></script>
|
||||||
<script src="js/prefs.js"></script>
|
<script src="js/prefs.js"></script>
|
||||||
<script src="js/msg.js"></script>
|
<script src="js/msg.js"></script>
|
||||||
|
<script src="js/router.js"></script>
|
||||||
<script src="content/style-injector.js"></script>
|
<script src="content/style-injector.js"></script>
|
||||||
<script src="content/apply.js"></script>
|
<script src="content/apply.js"></script>
|
||||||
<script src="js/localization.js"></script>
|
<script src="js/localization.js"></script>
|
||||||
|
@ -358,7 +359,7 @@
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div id="options-buttons">
|
<div id="options-buttons">
|
||||||
<button id="manage-options-button" i18n-text="openOptionsManage"></button>
|
<button id="manage-options-button" i18n-text="openOptions"></button>
|
||||||
<button id="manage-shortcuts-button" class="chromium-only"
|
<button id="manage-shortcuts-button" class="chromium-only"
|
||||||
i18n-text="shortcuts"
|
i18n-text="shortcuts"
|
||||||
i18n-title="shortcutsNote"></button>
|
i18n-title="shortcutsNote"></button>
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
/* global installed messageBox sorter $ $$ $create t debounce prefs API onDOMready */
|
/* global installed messageBox sorter $ $$ $create t debounce prefs API router */
|
||||||
/* exported filterAndAppend */
|
/* exported filterAndAppend */
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
|
@ -9,11 +9,17 @@ const filtersSelector = {
|
||||||
numTotal: 0,
|
numTotal: 0,
|
||||||
};
|
};
|
||||||
|
|
||||||
// TODO: remove .replace(/^\?/, '') when minimum_chrome_version >= 52 (https://crbug.com/601425)
|
let initialized = false;
|
||||||
const urlFilterParam = new URLSearchParams(location.search.replace(/^\?/, '')).get('url');
|
|
||||||
if (location.search) {
|
router.watch({search: ['search']}, ([search]) => {
|
||||||
history.replaceState(0, document.title, location.origin + location.pathname);
|
$('#search').value = search || '';
|
||||||
}
|
if (!initialized) {
|
||||||
|
init();
|
||||||
|
initialized = true;
|
||||||
|
} else {
|
||||||
|
searchStyles();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
HTMLSelectElement.prototype.adjustWidth = function () {
|
HTMLSelectElement.prototype.adjustWidth = function () {
|
||||||
const option0 = this.selectedOptions[0];
|
const option0 = this.selectedOptions[0];
|
||||||
|
@ -30,11 +36,11 @@ HTMLSelectElement.prototype.adjustWidth = function () {
|
||||||
parent.replaceChild(this, singleSelect);
|
parent.replaceChild(this, singleSelect);
|
||||||
};
|
};
|
||||||
|
|
||||||
onDOMready().then(() => {
|
function init() {
|
||||||
$('#search').oninput = searchStyles;
|
$('#search').oninput = e => {
|
||||||
if (urlFilterParam) {
|
router.updateSearch('search', e.target.value);
|
||||||
$('#search').value = 'url:' + urlFilterParam;
|
};
|
||||||
}
|
|
||||||
$('#search-help').onclick = event => {
|
$('#search-help').onclick = event => {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
messageBox({
|
messageBox({
|
||||||
|
@ -120,6 +126,7 @@ onDOMready().then(() => {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
filterOnChange({forceRefilter: true});
|
filterOnChange({forceRefilter: true});
|
||||||
|
router.updateSearch('search', '');
|
||||||
};
|
};
|
||||||
|
|
||||||
// Adjust width after selects are visible
|
// Adjust width after selects are visible
|
||||||
|
@ -131,7 +138,7 @@ onDOMready().then(() => {
|
||||||
});
|
});
|
||||||
|
|
||||||
filterOnChange({forceRefilter: true});
|
filterOnChange({forceRefilter: true});
|
||||||
});
|
}
|
||||||
|
|
||||||
|
|
||||||
function filterOnChange({target: el, forceRefilter}) {
|
function filterOnChange({target: el, forceRefilter}) {
|
||||||
|
@ -271,7 +278,7 @@ function showFiltersStats() {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
function searchStyles({immediately, container}) {
|
function searchStyles({immediately, container} = {}) {
|
||||||
const el = $('#search');
|
const el = $('#search');
|
||||||
const query = el.value.trim();
|
const query = el.value.trim();
|
||||||
if (query === el.lastValue && !immediately && !container) {
|
if (query === el.lastValue && !immediately && !container) {
|
||||||
|
|
|
@ -7,8 +7,12 @@ const STYLISH_DUMP_FILE_EXT = '.txt';
|
||||||
const STYLUS_BACKUP_FILE_EXT = '.json';
|
const STYLUS_BACKUP_FILE_EXT = '.json';
|
||||||
|
|
||||||
onDOMready().then(() => {
|
onDOMready().then(() => {
|
||||||
$('#file-all-styles').onclick = exportToFile;
|
$('#file-all-styles').onclick = event => {
|
||||||
$('#unfile-all-styles').onclick = () => {
|
event.preventDefault();
|
||||||
|
exportToFile();
|
||||||
|
};
|
||||||
|
$('#unfile-all-styles').onclick = event => {
|
||||||
|
event.preventDefault();
|
||||||
importFromFile({fileTypeFilter: STYLUS_BACKUP_FILE_EXT});
|
importFromFile({fileTypeFilter: STYLUS_BACKUP_FILE_EXT});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -1109,6 +1109,22 @@ input[id^="manage.newUI"] {
|
||||||
-moz-osx-font-smoothing: grayscale;
|
-moz-osx-font-smoothing: grayscale;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#stylus-embedded-options {
|
||||||
|
position: fixed;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 100vh;
|
||||||
|
border: 0;
|
||||||
|
z-index: 2147483647;
|
||||||
|
background-color: hsla(0, 0%, 0%, .45);
|
||||||
|
animation: fadein .25s ease-in-out;
|
||||||
|
}
|
||||||
|
|
||||||
|
#stylus-embedded-options.fadeout {
|
||||||
|
animation: fadeout .25s ease-in-out;
|
||||||
|
}
|
||||||
|
|
||||||
@keyframes fadein {
|
@keyframes fadein {
|
||||||
from {
|
from {
|
||||||
opacity: 0;
|
opacity: 0;
|
||||||
|
|
|
@ -1,13 +1,13 @@
|
||||||
/*
|
/*
|
||||||
global messageBox getStyleWithNoCode
|
global messageBox getStyleWithNoCode
|
||||||
filterAndAppend urlFilterParam showFiltersStats
|
filterAndAppend showFiltersStats
|
||||||
checkUpdate handleUpdateInstalled
|
checkUpdate handleUpdateInstalled
|
||||||
objectDiff
|
objectDiff
|
||||||
configDialog
|
configDialog
|
||||||
sorter msg prefs API onDOMready $ $$ $create template setupLivePrefs
|
sorter msg prefs API onDOMready $ $$ $create template setupLivePrefs
|
||||||
URLS enforceInputRange t tWordBreak formatDate
|
URLS enforceInputRange t tWordBreak formatDate
|
||||||
getOwnTab getActiveTab openURL animateElement sessionStorageHash debounce
|
getOwnTab getActiveTab openURL animateElement sessionStorageHash debounce
|
||||||
scrollElementIntoView CHROME VIVALDI FIREFOX
|
scrollElementIntoView CHROME VIVALDI FIREFOX router
|
||||||
*/
|
*/
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
|
@ -35,7 +35,8 @@ const handleEvent = {};
|
||||||
|
|
||||||
Promise.all([
|
Promise.all([
|
||||||
API.getAllStyles(true),
|
API.getAllStyles(true),
|
||||||
urlFilterParam && API.searchDB({query: 'url:' + urlFilterParam}),
|
// FIXME: integrate this into filter.js
|
||||||
|
router.getSearch('search') && API.searchDB({query: router.getSearch('search')}),
|
||||||
Promise.all([
|
Promise.all([
|
||||||
onDOMready(),
|
onDOMready(),
|
||||||
prefs.initializing,
|
prefs.initializing,
|
||||||
|
@ -80,7 +81,9 @@ function onRuntimeMessage(msg) {
|
||||||
function initGlobalEvents() {
|
function initGlobalEvents() {
|
||||||
installed = $('#installed');
|
installed = $('#installed');
|
||||||
installed.onclick = handleEvent.entryClicked;
|
installed.onclick = handleEvent.entryClicked;
|
||||||
$('#manage-options-button').onclick = () => chrome.runtime.openOptionsPage();
|
$('#manage-options-button').onclick = () => {
|
||||||
|
router.updateHash('#stylus-options');
|
||||||
|
};
|
||||||
{
|
{
|
||||||
const btn = $('#manage-shortcuts-button');
|
const btn = $('#manage-shortcuts-button');
|
||||||
btn.onclick = btn.onclick || (() => openURL({url: URLS.configureCommands}));
|
btn.onclick = btn.onclick || (() => openURL({url: URLS.configureCommands}));
|
||||||
|
@ -700,3 +703,39 @@ function highlightEditedStyle() {
|
||||||
requestAnimationFrame(() => scrollElementIntoView(entry));
|
requestAnimationFrame(() => scrollElementIntoView(entry));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function embedOptions() {
|
||||||
|
let options = $('#stylus-embedded-options');
|
||||||
|
if (!options) {
|
||||||
|
options = document.createElement('iframe');
|
||||||
|
options.id = 'stylus-embedded-options';
|
||||||
|
options.src = '/options.html';
|
||||||
|
document.documentElement.appendChild(options);
|
||||||
|
}
|
||||||
|
options.focus();
|
||||||
|
}
|
||||||
|
|
||||||
|
function unembedOptions() {
|
||||||
|
const options = $('#stylus-embedded-options');
|
||||||
|
if (options) {
|
||||||
|
options.contentWindow.document.body.classList.add('scaleout');
|
||||||
|
options.classList.add('fadeout');
|
||||||
|
animateElement(options, {
|
||||||
|
className: 'fadeout',
|
||||||
|
onComplete: () => options.remove(),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
router.watch({hash: '#stylus-options'}, state => {
|
||||||
|
if (state) {
|
||||||
|
embedOptions();
|
||||||
|
} else {
|
||||||
|
unembedOptions();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
window.addEventListener('closeOptions', () => {
|
||||||
|
router.updateHash('');
|
||||||
|
});
|
||||||
|
|
|
@ -125,10 +125,6 @@
|
||||||
"default_popup": "popup.html"
|
"default_popup": "popup.html"
|
||||||
},
|
},
|
||||||
"default_locale": "en",
|
"default_locale": "en",
|
||||||
"options_ui": {
|
|
||||||
"page": "options.html",
|
|
||||||
"chrome_style": false
|
|
||||||
},
|
|
||||||
"applications": {
|
"applications": {
|
||||||
"gecko": {
|
"gecko": {
|
||||||
"id": "{7a7a4a92-a2a0-41d1-9fd7-1e92480d612d}",
|
"id": "{7a7a4a92-a2a0-41d1-9fd7-1e92480d612d}",
|
||||||
|
|
11
options.html
11
options.html
|
@ -21,8 +21,8 @@
|
||||||
|
|
||||||
<script src="js/polyfill.js"></script>
|
<script src="js/polyfill.js"></script>
|
||||||
<script src="js/dom.js"></script>
|
<script src="js/dom.js"></script>
|
||||||
<script src="js/messaging.js"></script>
|
|
||||||
<script src="js/promisify.js"></script>
|
<script src="js/promisify.js"></script>
|
||||||
|
<script src="js/messaging.js"></script>
|
||||||
<script src="js/msg.js"></script>
|
<script src="js/msg.js"></script>
|
||||||
<script src="js/localization.js"></script>
|
<script src="js/localization.js"></script>
|
||||||
<script src="js/prefs.js"></script>
|
<script src="js/prefs.js"></script>
|
||||||
|
@ -33,6 +33,13 @@
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
<body id="stylus-options">
|
<body id="stylus-options">
|
||||||
|
|
||||||
|
<div id="options-header">
|
||||||
|
<div id="options-title">
|
||||||
|
<div id="options-close-icon"><svg viewBox="0 0 20 20" class="svg-icon"><path d="M11.69,10l4.55,4.55-1.69,1.69L10,11.69,5.45,16.23,3.77,14.55,8.31,10,3.77,5.45,5.45,3.77,10,8.31l4.55-4.55,1.69,1.69Z"></path></svg></div>
|
||||||
|
Stylus</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div id="options">
|
<div id="options">
|
||||||
|
|
||||||
<div class="block">
|
<div class="block">
|
||||||
|
@ -204,7 +211,7 @@
|
||||||
|
|
||||||
<div class="block" id="actions">
|
<div class="block" id="actions">
|
||||||
<button data-cmd="reset" i18n-text="optionsResetButton" i18n-title="optionsReset"></button>
|
<button data-cmd="reset" i18n-text="optionsResetButton" i18n-title="optionsReset"></button>
|
||||||
<button data-cmd="open-manage" i18n-text="optionsOpenManager"></button>
|
<button data-cmd="open-manage" i18n-text="styleCancelEditLabel"></button>
|
||||||
<div data-cmd="check-updates">
|
<div data-cmd="check-updates">
|
||||||
<button i18n-text="optionsCheck" i18n-title="optionsCheckUpdate">
|
<button i18n-text="optionsCheck" i18n-title="optionsCheckUpdate">
|
||||||
<span id="update-progress"></span>
|
<span id="update-progress"></span>
|
||||||
|
|
|
@ -1,36 +1,80 @@
|
||||||
html.opera {
|
html {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
height: 100%;
|
height: 100vh;
|
||||||
}
|
background-color: none;
|
||||||
|
|
||||||
html.opera body {
|
|
||||||
width: auto;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
body {
|
body {
|
||||||
background: #fff;
|
background: none;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
font-family: "Helvetica Neue", Helvetica, sans-serif;
|
font-family: "Helvetica Neue", Helvetica, sans-serif;
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
min-width: 480px;
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
width: auto;
|
||||||
max-width: 800px;
|
max-width: 800px;
|
||||||
width: max-content;
|
max-height: calc(100vh - 32px);
|
||||||
overflow-x: hidden;
|
border: 1px solid #999;
|
||||||
|
box-shadow: 0px 5px 15px 3px hsla(0, 0%, 0%, .35);
|
||||||
|
animation: scalein .25s ease-in-out;
|
||||||
}
|
}
|
||||||
|
|
||||||
@supports (-moz-appearance:none) {
|
body.scaleout {
|
||||||
body {
|
animation: scaleout .25s ease-in-out;
|
||||||
--addons-page-left-padding: 6px;
|
}
|
||||||
/* compensate 'html.firefox .block' padding-left */
|
|
||||||
width: calc(100% - var(--addons-page-left-padding));
|
#options {
|
||||||
/* match the default FF theme */
|
background: #fff;
|
||||||
background-color: #f9f9fa;
|
overflow-y: auto;
|
||||||
}
|
}
|
||||||
html.firefox .block {
|
|
||||||
padding-left: var(--addons-page-left-padding);
|
#options-close-icon .svg-icon {
|
||||||
}
|
fill: #666;
|
||||||
|
transition: fill .5s;
|
||||||
|
}
|
||||||
|
|
||||||
|
#options-close-icon:hover .svg-icon {
|
||||||
|
fill: #000;
|
||||||
|
}
|
||||||
|
|
||||||
|
#options-close-icon {
|
||||||
|
display: inline-flex;
|
||||||
|
cursor: pointer;
|
||||||
|
position: absolute;
|
||||||
|
right: 6px;
|
||||||
|
top: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#options-close-icon .svg-icon {
|
||||||
|
height: 20px;
|
||||||
|
width: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#options-title {
|
||||||
|
font-weight: bold;
|
||||||
|
background-color: rgb(145, 208, 198);
|
||||||
|
padding: .75rem 26px .75rem calc(30% + 4px);
|
||||||
|
font-size: 22px;
|
||||||
|
letter-spacing: .5px;
|
||||||
|
position: relative;
|
||||||
|
min-height: 42px;
|
||||||
|
box-sizing: border-box;
|
||||||
|
border-bottom: 1px solid #999;
|
||||||
|
}
|
||||||
|
|
||||||
|
#options-title::before {
|
||||||
|
content: "";
|
||||||
|
width: 0;
|
||||||
|
height: 0;
|
||||||
|
padding: 0 32px 32px 0;
|
||||||
|
background: url(/images/icon/32.png);
|
||||||
|
position: absolute;
|
||||||
|
left: 26px;
|
||||||
|
top: 0;
|
||||||
|
bottom: 0;
|
||||||
|
margin: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
.firefox .chromium-only,
|
.firefox .chromium-only,
|
||||||
|
@ -152,23 +196,20 @@ input[type="color"] {
|
||||||
#actions {
|
#actions {
|
||||||
justify-content: space-around;
|
justify-content: space-around;
|
||||||
align-items: stretch;
|
align-items: stretch;
|
||||||
padding: 1em;
|
flex-wrap: wrap;
|
||||||
|
padding: .5em 1em 1em;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
background-color: rgba(0, 0, 0, .05);
|
background-color: rgba(0, 0, 0, .05);
|
||||||
margin: 0;
|
margin: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.firefox #actions,
|
|
||||||
.opera #actions {
|
|
||||||
background-color: transparent;
|
|
||||||
}
|
|
||||||
|
|
||||||
#actions button {
|
#actions button {
|
||||||
width: auto;
|
width: auto;
|
||||||
|
margin-top: .5em;
|
||||||
}
|
}
|
||||||
|
|
||||||
#actions button:not(:last-child) {
|
#actions button:not(:last-child) {
|
||||||
margin-right: 8px;
|
margin-right: 4px;
|
||||||
}
|
}
|
||||||
|
|
||||||
[data-cmd="check-updates"] button {
|
[data-cmd="check-updates"] button {
|
||||||
|
@ -298,13 +339,16 @@ html:not(.firefox):not(.opera) #updates {
|
||||||
fill: #000;
|
fill: #000;
|
||||||
}
|
}
|
||||||
|
|
||||||
#message-box.note > div {
|
#message-box.note {
|
||||||
max-width: calc(100vw - 6rem);
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
.opera #message-box.note,
|
#message-box.note > div {
|
||||||
.firefox #message-box.note {
|
max-width: calc(100% - 5rem);
|
||||||
background-color: transparent;
|
top: unset;
|
||||||
|
right: unset;
|
||||||
|
position: relative;
|
||||||
}
|
}
|
||||||
|
|
||||||
@keyframes fadeinout {
|
@keyframes fadeinout {
|
||||||
|
@ -336,6 +380,21 @@ html:not(.firefox):not(.opera) #updates {
|
||||||
text-transform: uppercase;
|
text-transform: uppercase;
|
||||||
}
|
}
|
||||||
|
|
||||||
.sync-options .actions {
|
.sync-options .actions button {
|
||||||
padding-top: 6px;
|
margin-top: .5em;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes scalein {
|
||||||
|
0% {
|
||||||
|
transform: scale3d(.3, .3, .3);
|
||||||
|
}
|
||||||
|
100% {
|
||||||
|
transform: scale3d(1, 1, 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes scaleout {
|
||||||
|
100% {
|
||||||
|
transform: scale3d(0, 0, 0);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -39,6 +39,10 @@ if (FIREFOX && 'update' in (chrome.commands || {})) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// actions
|
// actions
|
||||||
|
$('#options-close-icon').onclick = () => {
|
||||||
|
top.dispatchEvent(new CustomEvent('closeOptions'));
|
||||||
|
};
|
||||||
|
|
||||||
document.onclick = e => {
|
document.onclick = e => {
|
||||||
const target = e.target.closest('[data-cmd]');
|
const target = e.target.closest('[data-cmd]');
|
||||||
if (!target) {
|
if (!target) {
|
||||||
|
@ -49,7 +53,7 @@ document.onclick = e => {
|
||||||
|
|
||||||
switch (target.dataset.cmd) {
|
switch (target.dataset.cmd) {
|
||||||
case 'open-manage':
|
case 'open-manage':
|
||||||
openURL({url: 'manage.html'});
|
API.openManage();
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 'check-updates':
|
case 'check-updates':
|
||||||
|
@ -292,3 +296,9 @@ function customizeHotkeys() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
window.onkeydown = event => {
|
||||||
|
if (event.keyCode === 27) {
|
||||||
|
top.dispatchEvent(new CustomEvent('closeOptions'));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
|
@ -243,7 +243,7 @@
|
||||||
<div id="popup-options">
|
<div id="popup-options">
|
||||||
<button id="popup-manage-button" i18n-text="openManage"
|
<button id="popup-manage-button" i18n-text="openManage"
|
||||||
data-href="manage.html" i18n-title="popupManageTooltip"></button>
|
data-href="manage.html" i18n-title="popupManageTooltip"></button>
|
||||||
<button id="popup-options-button" i18n-text="openOptionsPopup"></button>
|
<button id="popup-options-button" i18n-text="openOptions"></button>
|
||||||
<button id="popup-wiki-button"
|
<button id="popup-wiki-button"
|
||||||
i18n-text="linkStylusWiki"
|
i18n-text="linkStylusWiki"
|
||||||
i18n-title="linkGetHelp"
|
i18n-title="linkGetHelp"
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
/* global configDialog hotkeys onTabReady msg
|
/* global configDialog hotkeys onTabReady msg
|
||||||
getActiveTab FIREFOX getTabRealURL URLS API onDOMready $ $$ prefs CHROME
|
getActiveTab FIREFOX getTabRealURL URLS API onDOMready $ $$ prefs
|
||||||
setupLivePrefs template t $create animateElement
|
setupLivePrefs template t $create animateElement
|
||||||
tryJSONparse debounce CHROME_HAS_BORDER_BUG */
|
tryJSONparse debounce CHROME_HAS_BORDER_BUG */
|
||||||
|
|
||||||
|
@ -7,6 +7,7 @@
|
||||||
|
|
||||||
let installed;
|
let installed;
|
||||||
let tabURL;
|
let tabURL;
|
||||||
|
let unsupportedURL;
|
||||||
const handleEvent = {};
|
const handleEvent = {};
|
||||||
|
|
||||||
const ENTRY_ID_PREFIX_RAW = 'style-';
|
const ENTRY_ID_PREFIX_RAW = 'style-';
|
||||||
|
@ -28,6 +29,8 @@ getActiveTab()
|
||||||
.then(([results]) => {
|
.then(([results]) => {
|
||||||
if (!results) {
|
if (!results) {
|
||||||
// unsupported URL;
|
// unsupported URL;
|
||||||
|
unsupportedURL = true;
|
||||||
|
$('#popup-manage-button').removeAttribute('title');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
showStyles(results.map(r => Object.assign(r.data, r)));
|
showStyles(results.map(r => Object.assign(r.data, r)));
|
||||||
|
@ -99,7 +102,7 @@ function initPopup() {
|
||||||
});
|
});
|
||||||
|
|
||||||
$('#popup-options-button').onclick = () => {
|
$('#popup-options-button').onclick = () => {
|
||||||
chrome.runtime.openOptionsPage();
|
API.openManage({options: true});
|
||||||
window.close();
|
window.close();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -180,7 +183,7 @@ function initPopup() {
|
||||||
? new URL(tabURL).pathname.slice(1)
|
? new URL(tabURL).pathname.slice(1)
|
||||||
// this URL
|
// this URL
|
||||||
: t('writeStyleForURL').replace(/ /g, '\u00a0'),
|
: t('writeStyleForURL').replace(/ /g, '\u00a0'),
|
||||||
onclick: handleEvent.openLink,
|
onclick: e => handleEvent.openEditor(e, {'url-prefix': tabURL}),
|
||||||
});
|
});
|
||||||
if (prefs.get('popup.breadcrumbs')) {
|
if (prefs.get('popup.breadcrumbs')) {
|
||||||
urlLink.onmouseenter =
|
urlLink.onmouseenter =
|
||||||
|
@ -203,7 +206,7 @@ function initPopup() {
|
||||||
href: 'edit.html?domain=' + encodeURIComponent(domain),
|
href: 'edit.html?domain=' + encodeURIComponent(domain),
|
||||||
textContent: numParts > 2 ? domain.split('.')[0] : domain,
|
textContent: numParts > 2 ? domain.split('.')[0] : domain,
|
||||||
title: `domain("${domain}")`,
|
title: `domain("${domain}")`,
|
||||||
onclick: handleEvent.openLink,
|
onclick: e => handleEvent.openEditor(e, {domain}),
|
||||||
});
|
});
|
||||||
domainLink.setAttribute('subdomain', numParts > 1 ? 'true' : '');
|
domainLink.setAttribute('subdomain', numParts > 1 ? 'true' : '');
|
||||||
matchTargets.appendChild(domainLink);
|
matchTargets.appendChild(domainLink);
|
||||||
|
@ -289,7 +292,7 @@ function createStyleElement(style) {
|
||||||
const editLink = $('.style-edit-link', entry);
|
const editLink = $('.style-edit-link', entry);
|
||||||
Object.assign(editLink, {
|
Object.assign(editLink, {
|
||||||
href: editLink.getAttribute('href') + style.id,
|
href: editLink.getAttribute('href') + style.id,
|
||||||
onclick: handleEvent.openLink,
|
onclick: e => handleEvent.openEditor(e, {id: style.id}),
|
||||||
});
|
});
|
||||||
const styleName = $('.style-name', entry);
|
const styleName = $('.style-name', entry);
|
||||||
Object.assign(styleName, {
|
Object.assign(styleName, {
|
||||||
|
@ -528,18 +531,10 @@ Object.assign(handleEvent, {
|
||||||
$('#regexp-explanation').remove();
|
$('#regexp-explanation').remove();
|
||||||
},
|
},
|
||||||
|
|
||||||
openLink(event) {
|
openEditor(event, options) {
|
||||||
if (!chrome.windows || !prefs.get('openEditInWindow', false)) {
|
|
||||||
handleEvent.openURLandHide.call(this, event);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
chrome.windows.create(
|
API.openEditor(options);
|
||||||
Object.assign({
|
window.close();
|
||||||
url: this.href
|
|
||||||
}, prefs.get('windowPosition', {}))
|
|
||||||
);
|
|
||||||
close();
|
|
||||||
},
|
},
|
||||||
|
|
||||||
maybeEdit(event) {
|
maybeEdit(event) {
|
||||||
|
@ -582,12 +577,16 @@ Object.assign(handleEvent, {
|
||||||
},
|
},
|
||||||
|
|
||||||
openManager(event) {
|
openManager(event) {
|
||||||
|
if (event.button === 2 && unsupportedURL) return;
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
if (!this.eventHandled) {
|
if (!this.eventHandled) {
|
||||||
|
// FIXME: this only works if popup is closed
|
||||||
this.eventHandled = true;
|
this.eventHandled = true;
|
||||||
this.dataset.href += event.shiftKey || event.button === 2 ?
|
API.openManage({
|
||||||
'?url=' + encodeURIComponent(tabURL) : '';
|
search: tabURL && (event.shiftKey || event.button === 2) ?
|
||||||
handleEvent.openURLandHide.call(this, event);
|
`url:${tabURL}` : null
|
||||||
|
});
|
||||||
|
window.close();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
|
@ -48,7 +48,8 @@ function uploadFileDropbox(client, stylesText) {
|
||||||
return client.filesUpload({path: '/' + DROPBOX_FILE, contents: stylesText});
|
return client.filesUpload({path: '/' + DROPBOX_FILE, contents: stylesText});
|
||||||
}
|
}
|
||||||
|
|
||||||
$('#sync-dropbox-export').onclick = () => {
|
$('#sync-dropbox-export').onclick = event => {
|
||||||
|
event.preventDefault();
|
||||||
const mode = localStorage.installType;
|
const mode = localStorage.installType;
|
||||||
const title = t('syncDropboxStyles');
|
const title = t('syncDropboxStyles');
|
||||||
const text = mode === 'normal' ? t('connectingDropbox') : t('connectingDropboxNotAllowed');
|
const text = mode === 'normal' ? t('connectingDropbox') : t('connectingDropboxNotAllowed');
|
||||||
|
@ -122,7 +123,8 @@ $('#sync-dropbox-export').onclick = () => {
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
$('#sync-dropbox-import').onclick = () => {
|
$('#sync-dropbox-import').onclick = event => {
|
||||||
|
event.preventDefault();
|
||||||
const mode = localStorage.installType;
|
const mode = localStorage.installType;
|
||||||
const title = t('retrieveDropboxSync');
|
const title = t('retrieveDropboxSync');
|
||||||
const text = mode === 'normal' ? t('connectingDropbox') : t('connectingDropboxNotAllowed');
|
const text = mode === 'normal' ? t('connectingDropbox') : t('connectingDropboxNotAllowed');
|
||||||
|
|
Loading…
Reference in New Issue
Block a user