diff --git a/src/_locales/en/messages.json b/src/_locales/en/messages.json
index 3599e4cdf..07502b1c6 100644
--- a/src/_locales/en/messages.json
+++ b/src/_locales/en/messages.json
@@ -244,5 +244,21 @@
"options_addSubscriptionDialog_addButton": {
"message": "Add",
"description": "The text of the add button on the add-subscription dialog."
+ },
+ "options_extraSiteSupportTitle": {
+ "message": "Extra site support",
+ "description": "The title of the extra-site-support section."
+ },
+ "options_extraSiteSupportDescription": {
+ "message": "You can enable these site supporting below. Please notice this will require the user permission to access the site you enabled",
+ "description": "The title of the extra-site-support section."
+ },
+ "options_enableSite": {
+ "message": "Enable",
+ "description": "The text of enable extra site support"
+ },
+ "options_enabled": {
+ "message": "Enabled",
+ "description": "The text for site already enabled"
}
}
diff --git a/src/_locales/zh_TW/messages.json b/src/_locales/zh_TW/messages.json
index 9f9f8c832..fdea8aa00 100644
--- a/src/_locales/zh_TW/messages.json
+++ b/src/_locales/zh_TW/messages.json
@@ -184,5 +184,18 @@
},
"options_addSubscriptionDialog_addButton": {
"message": "Add"
+ },
+ "options_extraSiteSupportTitle": {
+ "message": "支援額外的網站"
+ },
+ "options_extraSiteSupportDescription": {
+ "message": "你可以開啟以下網站的支援,請注意你必須允許此擴充功能存取你要支援的網站"
+ },
+ "options_enableSite": {
+ "message": "啟用",
+ "description": "The text of enable extra site support"
+ },
+ "options_enabled": {
+ "message": "已啟用"
}
}
diff --git a/src/options.html b/src/options.html
index 4926f635a..e1609a084 100644
--- a/src/options.html
+++ b/src/options.html
@@ -58,6 +58,35 @@
+
diff --git a/src/ts/background.ts b/src/ts/background.ts
index 25e3734cc..ce14fdb83 100644
--- a/src/ts/background.ts
+++ b/src/ts/background.ts
@@ -14,6 +14,7 @@ import {
sendMessage,
addMessageListener,
BackgroundPage,
+ SiteID,
} from './common';
const backgroundPage = (window as Window) as BackgroundPage;
@@ -438,12 +439,128 @@ backgroundPage.removeCachedAuthToken = async function(token: string): Promise {
+ return new Promise((resolve, reject): void => {
+ chrome.tabs.executeScript(
+ tabId,
+ { code: loadCheck, runAt: 'document_start' },
+ (result): void => {
+ if (chrome.runtime.lastError) {
+ reject(chrome.runtime.lastError);
+ return;
+ }
+
+ resolve(result && result[0]);
+ },
+ );
+ });
+}
+
+function checkSiteAccess(url: string, request = false): Promise {
+ const method = request ? 'request' : 'contains';
+ return new Promise((resolve, reject) => {
+ const u = new URL(url);
+ chrome.permissions[method]({ origins: [`${u.protocol}//${u.hostname}/*`] }, granted => {
+ if (chrome.runtime.lastError) {
+ reject(new Error(chrome.runtime.lastError.message));
+ } else {
+ resolve(granted);
+ }
+ });
+ });
+}
+
+// #if BROWSER === 'chrome'
+chrome.tabs.onUpdated.addListener((tabId, { status }): void => {
+ if (status !== 'loading') {
+ return;
+ }
+
+ chrome.tabs.get(
+ tabId,
+ async (tab): Promise => {
+ const loadCheck = `window['__UBLACKLIST_CONTENT_SCRIPT_DYNAMIC_INJECTED__']`;
+
+ if (!tab.url || !(await checkSiteAccess(tab.url)) || !wasPreviouslyLoaded(tabId, loadCheck)) {
+ return;
+ }
+ for (const { matches } of Object.values(EXTRA_SITE_INFO)) {
+ for (const url of matches) {
+ if (tab.url.startsWith(url)) {
+ chrome.tabs.insertCSS({ file: 'css/content.css', runAt: 'document_start' });
+ chrome.tabs.executeScript({ file: 'js/content.js', runAt: 'document_start' });
+ chrome.tabs.executeScript({ code: `${loadCheck}=true`, runAt: 'document_start' });
+ }
+ }
+ }
+ },
+ );
+});
+// #endif
+
+backgroundPage.hasSiteEnable = function(site: SiteID): Promise {
+ return checkSiteAccess(EXTRA_SITE_INFO[site].baseUrl);
+};
+
+backgroundPage.enableSite = async function(site: SiteID): Promise {
+ // #if BROWSER === 'firefox'
+ const info = EXTRA_SITE_INFO[site];
+ if (info.registration) {
+ await info.registration.unregister();
+ }
+
+ EXTRA_SITE_INFO[site].registration = await chrome.contentScripts.register({
+ css: [{ file: 'css/content.css' }],
+ js: [{ file: 'js/content.js' }],
+ runAt: 'document_start',
+ matches: EXTRA_SITE_INFO[site].matches,
+ });
+ // #endif
+};
+
+backgroundPage.requestEnableSite = function(site: SiteID): Promise {
+ return checkSiteAccess(EXTRA_SITE_INFO[site].baseUrl, true).then(async granted => {
+ if (granted) {
+ await backgroundPage.enableSite(site);
+ }
+ return granted;
+ });
+};
+
+// #endregion Extra Site
+
async function main(): Promise {
addMessageListener('setBlacklist', ({ blacklist }) => {
backgroundPage.setBlacklist(blacklist);
});
const { syncInterval, updateInterval } = await getOptions('syncInterval', 'updateInterval');
+ if (await backgroundPage.hasSiteEnable('startpage')) {
+ await backgroundPage.enableSite('startpage');
+ }
backgroundPage.syncBlacklist();
setInterval(() => {
backgroundPage.syncBlacklist();
diff --git a/src/ts/common.ts b/src/ts/common.ts
index dad3b722c..5324dd06b 100644
--- a/src/ts/common.ts
+++ b/src/ts/common.ts
@@ -185,6 +185,8 @@ export function addMessageListener(type: string, listener: (args: T) => void)
// #region BackgroundPage
+export type SiteID = 'startpage';
+
export interface BackgroundPage extends Window {
// Blacklist
setBlacklist(blacklist: string): Promise;
@@ -197,6 +199,11 @@ export interface BackgroundPage extends Window {
updateSubscription(id: SubscriptionId): Promise;
updateAllSubscriptions(): Promise;
+ // Extra Site
+ hasSiteEnable(site: SiteID): Promise;
+ requestEnableSite(site: SiteID): Promise;
+ enableSite(site: SiteID): Promise;
+
// Auth
getAuthToken(interactive: boolean): Promise;
removeCachedAuthToken(token: string): Promise;
diff --git a/src/ts/content.ts b/src/ts/content.ts
index 9734a6bd7..54d2514a8 100644
--- a/src/ts/content.ts
+++ b/src/ts/content.ts
@@ -14,6 +14,60 @@ function $(id: string): HTMLElement | null {
return document.getElementById(id) as HTMLElement | null;
}
+const CONTROL_INFO = [
+ {
+ site: /^www.google/,
+ insert: [
+ (control: HTMLElement): boolean => {
+ const resultStats = $('resultStats') as HTMLDivElement | null;
+ if (!resultStats) {
+ return false;
+ }
+ resultStats.appendChild(control);
+ return true;
+ },
+ (control: HTMLElement): boolean => {
+ const abCtls = $('ab_ctls') as HTMLOListElement | null;
+ if (!abCtls) {
+ return false;
+ }
+ const li = document.createElement('li');
+ li.className = 'ab_ctl';
+ li.id = 'ubImageSearchControl';
+ li.appendChild(control);
+ abCtls.appendChild(li);
+ return true;
+ },
+ ],
+ },
+ {
+ site: /^www.startpage/,
+ insert: [
+ (control: HTMLElement): boolean => {
+ const searchFilter = document.querySelector('.search-filters-toolbar__container');
+ if (!searchFilter) {
+ return false;
+ }
+ const div = document.createElement('div');
+ div.className = 'search-filters-toolbar__advanced';
+ div.appendChild(control);
+ searchFilter.appendChild(div);
+ return true;
+ },
+ (control: HTMLElement): boolean => {
+ const imageFilter = document.querySelector('.images-filters-toolbar__container');
+ if (!imageFilter) {
+ return false;
+ }
+ const div = document.createElement('div');
+ div.appendChild(control);
+ imageFilter.appendChild(div);
+ return true;
+ },
+ ],
+ },
+];
+
class Main {
blacklists: BlacklistAggregation | null = null;
blacklistUpdate: BlacklistUpdate | null = null;
@@ -210,22 +264,13 @@ class Main {
control.appendChild(showButton);
control.appendChild(hideButton);
- const resultStats = $('resultStats') as HTMLDivElement | null;
- if (resultStats) {
- resultStats.appendChild(control);
- } else {
- const abCtls = $('ab_ctls') as HTMLOListElement | null;
- if (abCtls) {
- const li = document.createElement('li');
- li.className = 'ab_ctl';
- li.id = 'ubImageSearchControl';
- li.appendChild(control);
- abCtls.appendChild(li);
- } else {
- return;
+ for (const info of CONTROL_INFO) {
+ if (info.site.exec(window.location.hostname)) {
+ if (!info.insert.some((insert): boolean => insert(control))) {
+ return;
+ }
}
}
-
this.updateControl();
}
diff --git a/src/ts/inspector.ts b/src/ts/inspector.ts
index 80b6e6dd8..a3056f5d5 100644
--- a/src/ts/inspector.ts
+++ b/src/ts/inspector.ts
@@ -187,6 +187,26 @@ const ENTRY_INFO: EntryInfo[] = [
actionClass: 'ubNewsSearchAction',
display: 'default',
},
+ {
+ id: 'StartPage.Search.Default',
+ target: '.w-gl__result',
+ targetDepth: 0,
+ pageLink: '> .w-gl__result-title',
+ pageLinkType: 'default',
+ actionParent: '',
+ actionClass: 'ubDefaultAction',
+ display: 'default',
+ },
+ {
+ id: 'StartPage.Search.Image',
+ target: '.ig-gl__list .image-container',
+ targetDepth: 0,
+ pageLink: 'span.site',
+ pageLinkType: 'default',
+ actionParent: '',
+ actionClass: 'ubImageSearchAction',
+ display: 'imageSearch',
+ },
];
export interface InspectResult {
@@ -223,7 +243,7 @@ export function inspectEntry(elem: HTMLElement): InspectResult | null {
}
pageUrl = parsed.searchParams.get('q') || pageUrl;
} else {
- pageUrl = (pageLink as HTMLAnchorElement).href;
+ pageUrl = pageLink.getAttribute('href') as string;
}
let actionParent!: HTMLElement;
if (info.actionParent) {
diff --git a/src/ts/options.ts b/src/ts/options.ts
index 9d5636f8f..e0a9911ba 100644
--- a/src/ts/options.ts
+++ b/src/ts/options.ts
@@ -1,26 +1,28 @@
import dayjs from 'dayjs';
+import relativeTime from 'dayjs/plugin/relativeTime';
import 'dayjs/locale/en';
import 'dayjs/locale/ja';
import 'dayjs/locale/ru';
import 'dayjs/locale/tr';
import 'dayjs/locale/zh-cn';
import 'dayjs/locale/zh-tw';
-import relativeTime from 'dayjs/plugin/relativeTime';
import {
- lines,
- unlines,
+ BackgroundPage,
Result,
- isErrorResult,
- SubscriptionId,
+ SiteID,
Subscription,
+ SubscriptionId,
Subscriptions,
- getOptions,
- setOptions,
addMessageListener,
- BackgroundPage,
getBackgroundPage,
+ getOptions,
+ isErrorResult,
+ lines,
+ setOptions,
+ unlines,
} from './common';
+
let backgroundPage: BackgroundPage;
async function requestSiteAccess(url: string): Promise {
@@ -75,6 +77,8 @@ function $(id: 'showSubscriptionDialog_background'): HTMLDivElement;
function $(id: 'showSubscriptionDialog_name'): HTMLParagraphElement;
function $(id: 'showSubscriptionDialog_blacklist'): HTMLTextAreaElement;
function $(id: 'showSubscriptionDialog_ok'): HTMLButtonElement;
+function $(id: 'startpageSupport'): HTMLButtonElement;
+function $(id: 'startpageSupportOn'): HTMLButtonElement;
function $(id: string): Element | null {
return document.getElementById(id) as Element | null;
}
@@ -129,6 +133,44 @@ function setupGeneralSection(blacklist: string, hideBlockLinks: boolean): void {
// #endregion General
+// #region ExtraSiteSupport
+
+async function bindSiteSupportEvent(
+ site: SiteID,
+ $grantButton: HTMLButtonElement,
+ $grantedButton: HTMLButtonElement,
+): Promise {
+ async function turnOn(): Promise {
+ $grantedButton.classList.remove('is-hidden');
+ $grantButton.classList.add('is-hidden');
+ }
+
+ if (await backgroundPage.hasSiteEnable(site)) {
+ turnOn();
+ return;
+ }
+ $grantButton.addEventListener(
+ 'click',
+ async (): Promise => {
+ try {
+ chrome.permissions.request({ origins: ['https://www.startpage.com/*'] }, granted => {
+ if (granted) {
+ turnOn();
+ }
+ });
+ } catch {
+ // ignore
+ }
+ },
+ );
+}
+
+function setupExtraSiteSupport(): void {
+ bindSiteSupportEvent('startpage', $('startpageSupport'), $('startpageSupportOn'));
+}
+
+// #endregion ExtraSiteSupport
+
// #region Sync
function onSyncChanged(sync: boolean): void {
@@ -341,6 +383,7 @@ async function main(): Promise {
'subscriptions',
);
setupGeneralSection(blacklist, hideBlockLinks);
+ setupExtraSiteSupport();
setupSyncSection(sync, syncResult);
// #if BROWSER === 'firefox'
const { os } = await browser.runtime.getPlatformInfo();