Skip to content
Open
Show file tree
Hide file tree
Changes from 9 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
22 changes: 22 additions & 0 deletions src/_locales/dict.browser.json
Original file line number Diff line number Diff line change
Expand Up @@ -423,6 +423,28 @@
"zh_CN": "编辑标题",
"zh_TW": "編輯標題"
},
"KbToggleBindUrl": {
"en": "Bind/unbind tab URL",
"de": "Tab-URL binden/lösen",
"fr": "Lier/délier l'URL de l'onglet",
"hu": "Lap URL kötése/feloldása",
"pl": "Powiąż/odwiąż URL karty",
"ru": "Привязать/отвязать URL вкладки",
"zh_CN": "绑定/解绑标签页网址",
"zh_TW": "綁定/解綁分頁網址",
"ja": "タブURLをバインド/解除"
},
"KbReturnToBoundUrl": {
"en": "Return to bound URL",
"de": "Zur gebundenen URL zurück",
"fr": "Revenir à l'URL liée",
"hu": "Vissza a kötött URL-hez",
"pl": "Wróć do powiązanego URL",
"ru": "Вернуться к привязанному URL",
"zh_CN": "返回绑定的网址",
"zh_TW": "返回綁定的網址",
"ja": "バインドされたURLに戻る"
},
"KbOpenNewTabInCurrPanel": {
"en": "Open a new tab in the active panel",
"de": "Öffne neuen Tab im aktiven Panel",
Expand Down
33 changes: 33 additions & 0 deletions src/_locales/dict.common.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1933,6 +1933,39 @@ export const commonTranslations: Translations = {
zh_TW: '取消釘選分頁',
ja: 'ピン留め解除',
},
'menu.tab.anchor': {
Comment thread
VictorVow marked this conversation as resolved.
Outdated
en: 'Bind URL',
de: 'Ankern',
fr: 'Ancrer',
hu: 'Horgonyzás',
pl: 'Zakotwicz',
ru: 'Привязать',
zh_CN: '锚定',
zh_TW: '錨定',
ja: 'アンカー設定',
Comment thread
VictorVow marked this conversation as resolved.
Outdated
},
'menu.tab.unanchor': {
en: 'Unbind URL',
de: 'Anker entfernen',
fr: 'Désancrer',
hu: 'Horgony eltávolítása',
pl: 'Usuń zakotwiczenie',
ru: 'Снять привязку',
zh_CN: '取消锚定',
zh_TW: '取消錨定',
ja: 'アンカー解除',
},
'menu.tab.return_to_bound': {
en: 'Return to bound URL',
de: 'Zur gebundenen URL zurück',
fr: 'Revenir à l’URL liée',
hu: 'Vissza a kötött URL-hez',
pl: 'Wróć do powiązanego URL',
ru: 'Вернуться к привязанному URL',
zh_CN: '返回绑定的网址',
zh_TW: '返回綁定的網址',
ja: 'バインドされたURLに戻る',
},
'menu.tab.duplicate': {
en: 'Duplicate',
de: 'Duplizieren',
Expand Down
22 changes: 22 additions & 0 deletions src/_locales/dict.setup-page.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5622,6 +5622,28 @@ Przykłady: "*", "ctrl+$", "ctrl+alt+g"`,
zh_TW: '編輯標題',
ja: 'タイトルを編集',
},
'settings.tab_action_toggle_bind_url': {
en: 'bind/unbind URL',
de: 'URL binden/lösen',
fr: 'lier/délier l’URL',
hu: 'URL kötése/feloldása',
pl: 'powiąż/odwiąż URL',
ru: 'привязать/отвязать URL',
zh_CN: '绑定/解绑网址',
zh_TW: '綁定/解綁網址',
ja: 'URLをバインド/解除',
},
'settings.tab_action_return_to_bound': {
en: 'return to bound URL',
de: 'zur gebundenen URL zurück',
fr: 'revenir à l’URL liée',
hu: 'vissza a kötött URL-hez',
pl: 'wróć do powiązanego URL',
ru: 'вернуться к привязанному URL',
zh_CN: '返回绑定的网址',
zh_TW: '返回綁定的網址',
ja: 'バインドされたURLに戻る',
},
'settings.tab_action_none': {
en: 'none',
de: 'Nichts',
Expand Down
2 changes: 2 additions & 0 deletions src/defaults/menu.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,8 @@ export const TABS_MENU: MenuConf = [
},
'separator-2',
'pin',
'bind_url',
'return_to_bound',
'duplicate',
'discard',
'copyTabsUrls',
Expand Down
5 changes: 4 additions & 1 deletion src/defaults/mocks.tabs.fg.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,8 @@ export class MTab implements Tab {
reopenInContainer?: string | undefined
customTitle?: string | undefined
customColor?: string | undefined
anchorUrl?: string | undefined
anchorTitle?: string | undefined
moving?: boolean | undefined
previewImg?: string | undefined
removing?: boolean | undefined
Expand Down Expand Up @@ -64,7 +66,8 @@ export class MTab implements Tab {
branchColor: null,
customColor: null,
isGroup: false,
preview: false,
anchored: false,
anchoredAway: false,
}
sessionData?: TabSessionData | undefined
titleEl?: HTMLElement | undefined
Expand Down
10 changes: 5 additions & 5 deletions src/defaults/settings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -310,13 +310,13 @@ export const SETTINGS_OPTIONS = {
scrollThroughTabs: ['panel', 'global', 'psp', 'psg', 'none'],
discardFoldedDelayUnit: ['sec', 'min'],
tabDoubleClick: ['reload', 'duplicate', 'dup_child', 'pin', 'mute', 'clear_cookies', 'exp',
'new_after', 'new_child', 'close', 'edit_title', 'none'],
'new_after', 'new_child', 'close', 'edit_title', 'toggle_bind_url', 'return_to_bound', 'none'],
tabLongLeftClick: ['reload', 'discard', 'duplicate', 'dup_child', 'pin', 'mute', 'clear_cookies', 'new_after',
'new_child', 'edit_title', 'none'],
'new_child', 'edit_title', 'toggle_bind_url', 'return_to_bound', 'none'],
tabLongRightClick: ['reload', 'discard', 'duplicate', 'dup_child', 'pin', 'mute', 'clear_cookies', 'new_after',
'new_child', 'edit_title', 'none'],
tabMiddleClick: ['close', 'discard', 'discard_or_close', 'duplicate', 'dup_child', 'none'],
tabPinnedMiddleClick: ['close', 'discard', 'discard_or_close', 'duplicate', 'unpin', 'none'],
'new_child', 'edit_title', 'toggle_bind_url', 'return_to_bound', 'none'],
tabMiddleClick: ['close', 'discard', 'discard_or_close', 'duplicate', 'dup_child', 'toggle_bind_url', 'return_to_bound', 'none'],
tabPinnedMiddleClick: ['close', 'discard', 'discard_or_close', 'duplicate', 'unpin', 'toggle_bind_url', 'return_to_bound', 'none'],
tabMiddleClickModifier: ['discard', 'discard_or_close', 'duplicate', 'dup_child', 'edit_title', 'none'],
tabCloseMiddleClick: ['close', 'discard', 'discard_or_close'],
tabsPanelLeftClickAction: ['prev', 'expand', 'parent', 'tab', 'none'],
Expand Down
6 changes: 6 additions & 0 deletions src/manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -415,6 +415,12 @@
"edit_title": {
"description": "__MSG_KbEditTitle__"
},
"toggle_bind_url": {
"description": "__MSG_KbToggleBindUrl__"
},
"return_to_bound_url": {
"description": "__MSG_KbReturnToBoundUrl__"
},
"sort_tabs_by_title_asc": {
"description": "__MSG_KbSortTabsByTitleAsc__"
},
Expand Down
2 changes: 2 additions & 0 deletions src/page.setup/components/keybindings.vue
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,8 @@
KeybindingField(:keybinding="Keybindings.reactive.byName.group_tabs_act")
KeybindingField(:keybinding="Keybindings.reactive.byName.flatten_tabs")
KeybindingField(:keybinding="Keybindings.reactive.byName.edit_title")
KeybindingField(:keybinding="Keybindings.reactive.byName.toggle_bind_url")
KeybindingField(:keybinding="Keybindings.reactive.byName.return_to_bound_url")

section
h2 {{translate('settings.kb_tabs_open')}}
Expand Down
1 change: 1 addition & 0 deletions src/page.setup/components/settings.tabs.vue
Original file line number Diff line number Diff line change
Expand Up @@ -665,6 +665,7 @@ import CountField from '../../components/count-field.vue'
import ToggleField from '../../components/toggle-field.vue'
import SelectField from '../../components/select-field.vue'
import NumField from '../../components/num-field.vue'
import TextField from 'src/components/text-field.vue'

const el = ref<HTMLElement | null>(null)
const newTabPosEl = ref<HTMLElement | null>(null)
Expand Down
1 change: 1 addition & 0 deletions src/page.setup/setup.html
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,7 @@
<inject>svg://src/assets/sync.svg#icon_sync</inject>
<inject>svg://src/assets/private-window.svg#icon_priv_win</inject>
<inject>svg://src/assets/undo.svg#icon_undo</inject>
<inject>svg://src/assets/home.svg#icon_home</inject>
</div>
</body>
</html>
14 changes: 14 additions & 0 deletions src/services/keybindings.fg.ts
Original file line number Diff line number Diff line change
Expand Up @@ -266,6 +266,8 @@ function onCmd(name: string): void {
else if (name === 'group_tabs_act') onKeyGroupTabs(true)
else if (name === 'flatten_tabs') onKeyFlattenTabs()
else if (name === 'edit_title') onKeyEditTitle()
else if (name === 'toggle_bind_url') onKeyToggleBindUrl()
else if (name === 'return_to_bound_url') onKeyReturnToBoundUrl()
else if (name === 'sel_child_tabs') onKeySelChildTabs()
else if (name === 'sort_tabs_by_title_asc') onKeySortTabs(SortBy.Title, 1)
else if (name === 'sort_tabs_by_title_des') onKeySortTabs(SortBy.Title, -1)
Expand Down Expand Up @@ -1530,6 +1532,18 @@ function onKeyPinTabs() {
else Tabs.pinTabs(ids)
}

function onKeyToggleBindUrl() {
const ids = Selection.isTabs() ? Selection.ids() : [Tabs.activeId]
if (!ids.length) return
Tabs.toggleAnchorTabs(ids)
}

function onKeyReturnToBoundUrl() {
const tabId = Selection.isTabs() ? Selection.getFirst() : Tabs.activeId
if (tabId === NOID) return
Tabs.returnToAnchor(tabId)
}

function onKeyHidePanel() {
let panelId = Selection.getFirst()
if (!Sidebar.panelsById[panelId]) panelId = Sidebar.activePanelId
Expand Down
24 changes: 24 additions & 0 deletions src/services/menu.fg.options.tabs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -260,6 +260,30 @@ export const tabsMenuOptions: Record<string, () => MenuOption | MenuOption[] | u
}
},

bind_url: () => {
const selected = Selection.ids()
const firstTab = Tabs.byId[selected[0]]
if (!firstTab) return
return {
label: translate('menu.tab.' + (firstTab.anchorUrl ? 'unanchor' : 'anchor')),
icon: 'icon_home',
onClick: firstTab.anchorUrl
? () => Tabs.unanchorTabs(selected)
: () => Tabs.anchorTabs(selected),
}
},

return_to_bound: () => {
const selected = Selection.ids()
const firstTab = Tabs.byId[selected[0]]
if (!firstTab?.anchorUrl || firstTab.url === firstTab.anchorUrl) return
return {
label: translate('menu.tab.return_to_bound'),
icon: 'icon_undo',
onClick: () => Tabs.returnToAnchor(firstTab.id),
}
},

reload: () => {
return {
label: translate('menu.tab.reload'),
Expand Down
5 changes: 5 additions & 0 deletions src/services/tabs.fg.handlers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -820,6 +820,11 @@ function onTabUpdated(tabId: ID, change: browser.tabs.ChangeInfo, nativeTab: Nat
IPPC.onHashMsg(tab, change.url)
}

// Handle anchor tab URL change
if (tab.anchorUrl) {
tab.reactive.anchoredAway = change.url !== tab.anchorUrl
}

// Reset favicon (to cached)
if (tab.internal || !Utils.sameStart(change.url, tab.url, 16)) {
change.favIconUrl = Favicons.getFavicon(change.url)
Expand Down
52 changes: 52 additions & 0 deletions src/services/tabs.fg.ts
Original file line number Diff line number Diff line change
Expand Up @@ -153,6 +153,8 @@ export function mutateNativeTabToSideberyTab(nativeTab: T.NativeTab): T.Tab {
branchColor: null,
color: null,
isGroup: tab.isGroup,
anchored: false,
anchoredAway: false,
}
}

Expand Down Expand Up @@ -504,6 +506,12 @@ function restoreTab(
tab.reactive.folded = tab.folded = !!props.folded
if (props.customTitle) tab.customTitle = props.customTitle
if (props.customColor) tab.reactive.customColor = tab.customColor = props.customColor
if (props.anchorUrl) {
tab.anchorUrl = props.anchorUrl
tab.anchorTitle = props.anchorTitle
tab.reactive.anchored = true
tab.reactive.anchoredAway = tab.url !== props.anchorUrl
}
} else {
Logs.warn(`Tabs.restoreTab: no props for: "${tab.id} i${tab.index} url${tab.url}"`)
}
Expand Down Expand Up @@ -730,6 +738,8 @@ export function cacheTabsData(delay = 300): void {
if (tab.cookieStoreId !== D.CONTAINER_ID) info.ctx = tab.cookieStoreId
if (tab.customTitle) info.customTitle = tab.customTitle
if (tab.customColor) info.customColor = tab.customColor
if (tab.anchorUrl) info.anchorUrl = tab.anchorUrl
if (tab.anchorTitle) info.anchorTitle = tab.anchorTitle
data.push(info)
}

Expand Down Expand Up @@ -1240,6 +1250,48 @@ export function repinTabs(tabIds: ID[]): void {
}
}

/**
* Anchor tabs
*/
export function anchorTabs(tabIds: ID[]): void {
for (const tabId of tabIds) {
const tab = Tabs.byId[tabId]
if (!tab) continue
tab.anchorUrl = tab.url
tab.anchorTitle = tab.customTitle ?? tab.title
tab.reactive.anchored = true
tab.reactive.anchoredAway = false
}
Tabs.cacheTabsData()
}

export function unanchorTabs(tabIds: ID[]): void {
for (const tabId of tabIds) {
const tab = Tabs.byId[tabId]
if (!tab) continue
tab.anchorUrl = undefined
tab.anchorTitle = undefined
tab.reactive.anchored = false
tab.reactive.anchoredAway = false
}
Tabs.cacheTabsData()
}

export function toggleAnchorTabs(tabIds: ID[]): void {
const firstTab = Tabs.byId[tabIds[0]]
if (!firstTab) return
if (firstTab.anchorUrl) unanchorTabs(tabIds)
else anchorTabs(tabIds)
}

export function returnToAnchor(tabId: ID): void {
const tab = Tabs.byId[tabId]
if (!tab?.anchorUrl) return
browser.tabs.update(tabId, { url: tab.anchorUrl }).catch(err => {
Logs.err('Tabs.returnToAnchor: Cannot navigate to anchor URL:', err)
})
}

/**
* Duplicate tabs
*/
Expand Down
22 changes: 21 additions & 1 deletion src/sidebar/components/tab.vue
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@
:data-colorized="!!tabColor"
:data-unread="tab.reactive.unread"
:data-edit="tab.reactive.customTitleEdit"
:data-anchored="tab.reactive.anchored"
:data-anchored-away="tab.reactive.anchoredAway"
:title="tab.reactive.tooltip"
:draggable="!tab.reactive.customTitleEdit"
@dragstart="onDragStart"
Expand Down Expand Up @@ -47,6 +49,8 @@
.progress-spinner(v-if="Settings.state.animations")
svg.progress-spinner(v-else): use(href="#icon_hourglass")
.child-count(v-if="tab.reactive.folded && tab.reactive.branchLen") {{tab.reactive.branchLen}}
.bind-mark(v-if="tab.reactive.anchored")
svg.bind-mark-icon: use(href="#icon_home")
.audio(
v-if="tab.reactive.mediaAudible || tab.reactive.mediaMuted || tab.reactive.mediaPaused"
@mousedown.stop.prevent="onAudioMouseDown($event, tab)"
Expand Down Expand Up @@ -340,6 +344,12 @@ function onMouseDown(e: MouseEvent): void {
} else if (Settings.state.tabPinnedMiddleClick === 'unpin') {
Tabs.unpinTabs([tab.id])
return
} else if (Settings.state.tabPinnedMiddleClick === 'toggle_bind_url') {
Tabs.toggleAnchorTabs(selectedTabs)
return
} else if (Settings.state.tabPinnedMiddleClick === 'return_to_bound') {
Tabs.returnToAnchor(tab.id)
return
}
}

Expand All @@ -357,6 +367,10 @@ function onMouseDown(e: MouseEvent): void {
Tabs.duplicateTabs([tab.id])
} else if (Settings.state.tabMiddleClick === 'dup_child') {
Tabs.duplicateTabs([tab.id], true)
} else if (Settings.state.tabMiddleClick === 'toggle_bind_url') {
Tabs.toggleAnchorTabs(selectedTabs)
} else if (Settings.state.tabMiddleClick === 'return_to_bound') {
Tabs.returnToAnchor(tab.id)
}
}
}
Expand Down Expand Up @@ -392,7 +406,11 @@ function longClickFeedback(e: MouseEvent) {
else if (action === 'new_after') Tabs.createTabAfter(tab.id)
else if (action === 'new_child' && !tab.pinned) Tabs.createChildTab(tab.id)
else if (action === 'edit_title' && !tab.pinned) Tabs.editTabTitle([tab.id])
else noop = true
else if (action === 'toggle_bind_url') Tabs.toggleAnchorTabs([tab.id])
else if (action === 'return_to_bound') {
if (tab.anchorUrl && tab.url !== tab.anchorUrl) Tabs.returnToAnchor(tab.id)
else noop = true
} else noop = true

if (!noop) Tabs.triggerFlashAnimation(tab)

Expand Down Expand Up @@ -511,6 +529,8 @@ function onDoubleClick(): void {
if (shouldBeConvertedToGroup()) convertToGroup()
else Tabs.removeTabs([tab.id])
} else if (dc === 'edit_title') Tabs.editTabTitle([tab.id])
else if (dc === 'toggle_bind_url') Tabs.toggleAnchorTabs([tab.id])
else if (dc === 'return_to_bound') Tabs.returnToAnchor(tab.id)
}

function onDragStart(e: DragEvent): void {
Expand Down
Loading