Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "link-map",
"displayName": "Link Map",
"version": "1.0.11",
"version": "1.0.12",
"browserslist": "Chrome >= 96",
"description": "Vertical Tabs Sidebar, But In Tree Structure",
"author": "Garin",
Expand All @@ -15,7 +15,7 @@
"url": "https://garin@linkmap.cc"
},
"engines": {
"node": "^18.12.1"
"node": ">=18.12.1"
},
"scripts": {
"preinstall": "npx only-allow pnpm",
Expand Down
12 changes: 12 additions & 0 deletions public/_locales/en/messages.json
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,18 @@
"themeDark": {
"message": "Dark"
},
"displayMode": {
"message": "Open As"
},
"displayModeSidebar": {
"message": "Chrome Sidebar"
},
"displayModeFloatingModal": {
"message": "Floating Modal"
},
"displayModePopup": {
"message": "Popup Window"
},
"commandTriggerLinkMap": {
"message": "Open Link Map"
},
Expand Down
12 changes: 12 additions & 0 deletions public/_locales/zh_CN/messages.json
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,18 @@
"themeDark": {
"message": "黑暗模式"
},
"displayMode": {
"message": "打开方式"
},
"displayModeSidebar": {
"message": "Chrome 侧边栏"
},
"displayModeFloatingModal": {
"message": "悬浮弹窗"
},
"displayModePopup": {
"message": "弹出窗口"
},
"commandTriggerLinkMap": {
"message": "打开Link Map"
},
Expand Down
2 changes: 1 addition & 1 deletion src/background/event-bus.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,5 +25,5 @@ export async function sendMessageToExt<K extends DataTypeKey>(
}

export function isContentScriptPage(url?: string) {
return url === browser.runtime.getURL(EXT_HOME_PAGE_PATH);
return url?.startsWith(browser.runtime.getURL(EXT_HOME_PAGE_PATH)) ?? false;
}
158 changes: 147 additions & 11 deletions src/background/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,30 @@ import { isContentScriptPage, sendMessageToExt } from './event-bus';
try {
setLogLevel();

async function getPrimaryTabForWindow(windowId?: number) {
if (windowId == null) {
return null;
}
const tabs = await browser.tabs.query({ windowId });
return tabs[0] ?? null;
}

async function storeExtPageInfo(window: browser.Windows.Window) {
const extTab = window.tabs?.[0] ?? (await getPrimaryTabForWindow(window.id));
if (!extTab?.id || !extTab.windowId) {
throw new Error('Failed to resolve Link Map tab for created window.');
}
await setExtPageInfo({
windowId: extTab.windowId,
tabId: extTab.id,
});
}

async function enableSidePanelByDefault() {
if (!chrome.sidePanel?.setPanelBehavior) return;
await chrome.sidePanel.setPanelBehavior({ openPanelOnActionClick: true });
}

async function syncTabsCountInBadge() {
const allTabs = await browser.tabs.query({});
await browser.action.setBadgeBackgroundColor({ color: '#2b2d31' });
Expand All @@ -43,10 +67,15 @@ try {
}
const db = new TabMasterDB();
await db.initSetting();
await enableSidePanelByDefault();
await syncTabsCountInBadge();
await removeExtPageInfo();
});

browser.runtime.onStartup.addListener(async () => {
await enableSidePanelByDefault();
});

async function openNewExtWindow() {
const displayInfos = await chrome.system.display.getInfo();
const primaryDisplayInfo = displayInfos.find((item) => item.isPrimary);
Expand All @@ -62,41 +91,131 @@ try {
left,
focused: true,
});
const extTab = extWindow.tabs![0];
await setExtPageInfo({
windowId: extTab.windowId!,
tabId: extTab.id!,
await storeExtPageInfo(extWindow);
}

async function openFloatingModalWindow() {
const displayInfos = await chrome.system.display.getInfo();
const primaryDisplayInfo = displayInfos.find((item) => item.isPrimary);
const workAreaWidth = primaryDisplayInfo?.workArea.width ?? 1440;
const workAreaHeight = primaryDisplayInfo?.workArea.height ?? 960;
const workAreaLeft = primaryDisplayInfo?.workArea.left ?? 0;
const workAreaTop = primaryDisplayInfo?.workArea.top ?? 0;
const width = Math.min(1120, Math.floor(workAreaWidth * 0.7));
const height = Math.min(820, Math.floor(workAreaHeight * 0.82));
const left = workAreaLeft + Math.max(0, Math.floor((workAreaWidth - width) / 2));
const top = workAreaTop + Math.max(0, Math.floor((workAreaHeight - height) / 2));
const extWindow = await browser.windows.create({
url: 'tree.html?display=floating-modal',
type: 'popup',
width,
height,
top,
left,
focused: true,
});
await storeExtPageInfo(extWindow);
}

onMessage('tree-ready', async (msg) => {
const { windowId, tabId } = msg.data;
await setExtPageInfo({ windowId, tabId });
});

const focusOrCreateExtWindow = async () => {
const focusOrCreateExtWindow = async (
createExtWindow = openNewExtWindow,
shouldReuseWindow: (url?: string) => boolean = (url) =>
url === browser.runtime.getURL('tree.html'),
) => {
const extIdPair = await getExtPageInfo();
if (extIdPair == null) {
await openNewExtWindow();
await createExtWindow();
} else {
// 页面已打开,则窗口focused
try {
const extTab = await browser.tabs.get(extIdPair.tabId);
if (!shouldReuseWindow(extTab.url)) {
await createExtWindow();
return;
}
// 页面已打开,则窗口focused
await browser.windows.update(extIdPair.windowId, { focused: true });
} catch {
// 防止localStorage数据未清除,但是页面已经关闭的情况
await openNewExtWindow();
await createExtWindow();
}
}
};

const closeFloatingModalIfOpen = async () => {
const extIdPair = await getExtPageInfo();
if (extIdPair == null) {
return false;
}
try {
const extTab = await browser.tabs.get(extIdPair.tabId);
const isFloatingModal =
extTab.url === browser.runtime.getURL('tree.html?display=floating-modal');
if (!isFloatingModal) {
return false;
}
await browser.windows.remove(extIdPair.windowId);
await removeExtPageInfo();
return true;
} catch {
await removeExtPageInfo();
return false;
}
};

const closeFloatingModalOnBlur = async (focusedWindowId: number) => {
const extIdPair = await getExtPageInfo();
if (!extIdPair || focusedWindowId === extIdPair.windowId) {
return;
}
await closeFloatingModalIfOpen();
};

const openLinkMap = async (windowId?: number, shouldToggleFloatingModal = false) => {
const setting = await new TabMasterDB().getSetting();
const shouldUseSidePanel = setting?.display === 'embedded-sidebar';
const shouldUseFloatingModal = setting?.display === 'floating-modal';
if (shouldUseSidePanel && chrome.sidePanel?.open) {
try {
if (windowId) {
await chrome.sidePanel.open({ windowId });
} else {
const lastFocusedWindow = await browser.windows.getLastFocused();
if (lastFocusedWindow.id) {
await chrome.sidePanel.open({ windowId: lastFocusedWindow.id });
return;
}
}
return;
} catch (error) {
log.warn('Failed to open side panel, falling back to popup window.', error);
}
}
if (shouldUseFloatingModal) {
if (shouldToggleFloatingModal && (await closeFloatingModalIfOpen())) {
return;
}
await focusOrCreateExtWindow(
openFloatingModalWindow,
(url) => url === browser.runtime.getURL('tree.html?display=floating-modal'),
);
return;
}
await focusOrCreateExtWindow();
};

/**
* 点击插件按钮:打开一个TreeView页面
* 将extIdPair更新到localStorage中
* This Method Wouldn't Fire if popup has benn set
*/
browser.action.onClicked.addListener((tab) => {
setPrevFocusWindowId(tab.windowId!);
focusOrCreateExtWindow();
openLinkMap(tab.windowId);
});

// #### 浏览器Fire的事件
Expand Down Expand Up @@ -138,6 +257,7 @@ try {

browser.tabs.onActivated.addListener(({ tabId, windowId }) => {
log.debug('[bg]: tab activated!');
setPrevFocusWindowId(windowId);
sendMessageToExt('activated-tab', { windowId, tabId });
});
/**
Expand Down Expand Up @@ -180,8 +300,20 @@ try {
sendMessageToExt('remove-window', { windowId });
});

browser.windows.onFocusChanged.addListener((windowId) => {
browser.windows.onFocusChanged.addListener(async (windowId) => {
log.debug('[bg]: window focus changed!');
await closeFloatingModalOnBlur(windowId);
if (windowId !== browser.windows.WINDOW_ID_NONE) {
const [activeTab] = await browser.tabs.query({ active: true, windowId });
if (
!activeTab ||
isContentScriptPage(activeTab.url) ||
isContentScriptPage(activeTab.pendingUrl)
) {
return;
}
await setPrevFocusWindowId(windowId);
}
sendMessageToExt('window-focus', { windowId });
});

Expand Down Expand Up @@ -212,9 +344,13 @@ try {

browser.commands.onCommand.addListener(async (command) => {
if (command === 'openLinkMap') {
await focusOrCreateExtWindow();
await openLinkMap(undefined, true);
}
});

enableSidePanelByDefault().catch((error) => {
log.warn('Failed to enable side panel action behavior.', error);
});
} catch (error) {
log.error(error);
}
8 changes: 6 additions & 2 deletions src/manifest.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,19 +7,20 @@ export const commandKeyMap = {
openLinkMap: 'openLinkMap',
};

const manifest: Manifest.WebExtensionManifest = {
const manifest: Manifest.WebExtensionManifest & { side_panel: { default_path: string } } = {
name: pkg.displayName,
version: pkg.version,
description: '__MSG_extDesc__',
manifest_version: 3,
minimum_chrome_version: pkg.browserslist.split(' ')[2],
minimum_chrome_version: '114',
permissions: [
'tabs',
'storage',
'activeTab',
'windows',
'downloads',
'system.display',
'sidePanel' as Manifest.Permission,
'favicon',
],
content_security_policy: {
Expand Down Expand Up @@ -60,6 +61,9 @@ const manifest: Manifest.WebExtensionManifest = {
'128': 'icons/x128.png',
},
},
side_panel: {
default_path: 'tree.html',
},
// 实现options页面后使用
// options_ui: {
// page: 'options.html',
Expand Down
8 changes: 6 additions & 2 deletions src/storage/idb.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,15 +13,15 @@ export type ThemeType = 'light' | 'dark' | 'auto';
export interface Setting {
id: number;
theme: ThemeType;
display: 'popup' | 'tab' | 'embedded-sidebar';
display: 'popup' | 'tab' | 'embedded-sidebar' | 'floating-modal';
autoScrollToActiveTab: boolean;
createNewTabByLevel: boolean;
}

export const DEFAULT_SETTING: Setting = {
id: 1,
theme: 'dark',
display: 'popup',
display: 'floating-modal',
autoScrollToActiveTab: false,
createNewTabByLevel: false,
};
Expand Down Expand Up @@ -57,6 +57,10 @@ export class TabMasterDB extends Dexie {
const currentSetting = await this.getSetting();
if (!currentSetting) {
await this.setting.put(DEFAULT_SETTING);
return;
}
if (currentSetting.display === 'popup' || currentSetting.display === 'embedded-sidebar') {
await this.setting.update(1, { display: DEFAULT_SETTING.display });
}
}

Expand Down
5 changes: 3 additions & 2 deletions src/styles/app.less
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

html {
button.ant-btn {
font-size: 12px;
font-size: var(--control-font-size);
color: var(--btn-icon-color);
background-color: var(--btn-bg-color);
border: 1px solid var(--btn-border-color);
Expand Down Expand Up @@ -51,7 +51,8 @@ html {
}

body {
--main-font-size: 11px;
--main-font-size: 13px;
--control-font-size: 14px;
--font-family: inter, ui-sans-serif, system-ui, -apple-system, blinkmacsystemfont, 'Segoe UI',
roboto, 'Helvetica Neue', arial, 'Noto Sans', sans-serif, 'Apple Color Emoji',
'Segoe UI Emoji', 'Segoe UI Symbol', 'Noto Color Emoji';
Expand Down
Loading