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
110 changes: 110 additions & 0 deletions src/_locales/dict.setup-page.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2853,6 +2853,116 @@ Przykłady: "*", "ctrl+$", "ctrl+alt+g"`,
zh_TW: '包括輸入新網址的第一次標題更新',
ja: '新しいURLの最初のタイトル変更を含む',
},
'settings.tabs_notification_badge_scope': {
en: 'Show notification badges over tab favicons',
de: 'Benachrichtigungs-Badges auf Tab-Favicons anzeigen',
fr: 'Afficher les badges de notification sur les favicons d\'onglet',
hu: 'Értesítési jelzők megjelenítése a lap ikonjain',
pl: 'Pokaż oznaczenia powiadomień na ikonach kart',
ru: 'Показывать бейджи уведомлений на иконках вкладок',
zh_CN: '在标签页图标上显示通知标记',
zh_TW: '在分頁圖示上顯示通知標記',
ja: 'タブのファビコンに通知バッジを表示する',
},
'settings.tabs_notification_badge_scope_all': {
en: 'on',
de: 'Ein',
fr: 'tous',
hu: 'be',
pl: 'włączone',
ru: 'вкл',
zh_CN: '打开',
zh_TW: '開啟',
ja: 'オン',
},
'settings.tabs_notification_badge_scope_pin': {
en: 'only for pinned',
de: 'Nur für angeheftete',
fr: 'onglets épinglés seulement',
hu: 'csak rögzített',
pl: 'tylko dla przypiętych',
ru: 'только для закрепленных',
zh_CN: '仅用于固定',
zh_TW: '僅釘選',
ja: '固定のみ',
},
'settings.tabs_notification_badge_scope_norm': {
en: 'only for not pinned',
de: 'Nur für nicht angeheftete',
fr: 'onlets non épinglés seulement',
hu: 'csak nem rögzített',
pl: 'tylko dla nieprzypiętych',
ru: 'только для не закрепленных',
zh_CN: '仅用于未固定',
zh_TW: '僅未釘選',
ja: '固定以外のみ',
},
'settings.tabs_notification_badge_scope_none': {
en: 'off',
de: 'Aus',
fr: 'aucun',
hu: 'ki',
pl: 'wyłączone',
ru: 'выкл',
zh_CN: '关闭',
zh_TW: '關閉',
ja: 'オフ',
},
'settings.tabs_notification_badge_style': {
en: 'Show notification badge with count or a dot',
de: 'Benachrichtigungs-Badge mit Zähler oder Punkt anzeigen',
fr: 'Afficher le badge de notification avec compteur ou point',
hu: 'Értesítési jelző megjelenítése számlálóval vagy ponttal',
pl: 'Pokaż powiadomienie z liczbą lub kropką',
ru: 'Показывать бейдж уведомлений с числом или точкой',
zh_CN: '显示带计数或点的通知标记',
zh_TW: '顯示帶計數或點的通知標記',
ja: '通知バッジをカウントまたはドットで表示する',
},
'settings.tabs_notification_badge_style_count': {
en: 'count',
de: 'Zähler',
fr: 'compteur',
hu: 'számláló',
pl: 'licznik',
ru: 'счётчик',
zh_CN: '计数',
zh_TW: '計數',
ja: 'カウント',
},
'settings.tabs_notification_badge_style_dot': {
en: 'dot',
de: 'Punkt',
fr: 'point',
hu: 'pont',
pl: 'kropka',
ru: 'точка',
zh_CN: '点',
zh_TW: '點',
ja: 'ドット',
},
'settings.tabs_notification_badge_regexp_pattern': {
en: 'Regular expression for matching notification count in tab title',
de: 'Regulärer Ausdruck zur Übereinstimmung der Benachrichtigungszahl im Tab-Titel',
fr: 'Expression régulière pour correspondre au nombre de notifications dans le titre de l\'onglet',
hu: 'Reguláris kifejezés az értesítések számának megfeleltetéséhez a lap címében',
pl: 'Wyrażenie regularne dla dopasowania liczby powiadomień w tytule karty',
ru: 'Регулярное выражение для сопоставления количества уведомлений в заголовке вкладки',
zh_CN: '用于匹配标签页标题中通知数的正则表达式',
zh_TW: '用於匹配分頁標題中通知數的規則運算式',
ja: 'タブのタイトルで通知数をマッチするための正規表現',
},
'settings.tabs_notification_badge_regexp_pattern_info': {
en: 'Returns the first found capturing group in the pattern',
de: 'Gibt die erste gefundene Erfassungsgruppe im Muster zurück',
fr: 'Renvoie le premier groupe de capture trouvé dans le motif',
hu: 'Visszaadja a minta első megtalált rögzítő csoportját',
pl: 'Zwraca pierwszą znalezioną grupę przechwytującą we wzorcu',
ru: 'Возвращает первую найденную группу захвата в шаблоне',
zh_CN: '返回模式中找到的第一个捕获组',
zh_TW: '返回模式中找到的第一個捕獲組',
ja: 'パターン内で最初に見つかったキャプチャグループを返します',
},
'settings.tabs_reload_limit': {
en: 'Limit the count of simultaneously reloading tabs',
de: 'Beschränke die Anzahl gleichzeitig neu ladender Tabs',
Expand Down
7 changes: 6 additions & 1 deletion src/components/text-field.vue
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
:valid="props.valid"
:width="props.inputWidth"
@update:value="emit('update:value', $event)"
@blur="onBlur"
@keydown="emit('keydown', $event)")
.note(v-if="props.note") {{props.note}}
</template>
Expand All @@ -39,7 +40,7 @@ interface TextFieldProps {
inputWidth?: string
}

const emit = defineEmits(['update:value', 'keydown'])
const emit = defineEmits(['update:value', 'blur', 'keydown'])
const props = withDefaults(defineProps<TextFieldProps>(), { padding: 0, tabindex: '0' })

const inputEl = ref<TextInputComponent | null>(null)
Expand All @@ -48,6 +49,10 @@ function focus(): void {
inputEl.value?.focus()
}

function onBlur(): void {
if (inputEl.value) emit('blur', inputEl.value)
}

function error() {
inputEl.value?.error()
}
Expand Down
5 changes: 5 additions & 0 deletions src/defaults/settings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,9 @@ export const DEFAULT_SETTINGS: SettingsState = {
tabsUnreadMark: false,
tabsUpdateMark: 'all',
tabsUpdateMarkFirst: true,
tabsNotificationBadgeScope: 'none',
tabsNotificationBadgeStyle: 'count',
tabsNotificationBadgeRegExpPattern: String.raw`\((\d+)\)|\[(\d+)\]`,
tabsReloadLimit: 5,
tabsReloadLimitNotif: true,
showNewTabBtns: true,
Expand Down Expand Up @@ -310,6 +313,8 @@ export const SETTINGS_OPTIONS = {
tabRmBtn: ['always', 'hover', 'none'],
activateAfterClosing: ['prev_act', 'next', 'prev', 'none'],
tabsUpdateMark: ['all', 'pin', 'norm', 'none'],
tabsNotificationBadgeScope: ['all', 'pin', 'norm', 'none'],
tabsNotificationBadgeStyle: ['count', 'dot'],
pinnedTabsPosition: ['panel', 'top', 'left', 'right'],
tabsTreeLimit: [1, 2, 3, 4, 5, 'none'],
previewTabsMode: ['i', 'p', 'w'],
Expand Down
56 changes: 56 additions & 0 deletions src/page.setup/components/settings.tabs.vue
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,33 @@ section(ref="el")
label="settings.tabs_update_mark_first"
v-model:value="Settings.state.tabsUpdateMarkFirst"
@update:value="Settings.saveDebounced(150)")
SelectField(
label="settings.tabs_notification_badge_scope"
optLabel="settings.tabs_notification_badge_scope_"
v-model:value="Settings.state.tabsNotificationBadgeScope"
:opts="Settings.getOpts('tabsNotificationBadgeScope')"
:folded="true"
@update:value="Settings.saveDebounced(150)")
.sub-fields
SelectField(
label="settings.tabs_notification_badge_style"
optLabel="settings.tabs_notification_badge_style_"
v-model:value="Settings.state.tabsNotificationBadgeStyle"
:opts="Settings.getOpts('tabsNotificationBadgeStyle')"
:folded="false"
:inactive="Settings.state.tabsNotificationBadgeScope === 'none'"
@update:value="Settings.saveDebounced(150)")
TextField(
ref="tabsNotificationBadgeRegExpPatternEl"
label="settings.tabs_notification_badge_regexp_pattern"
:note="translate('settings.tabs_notification_badge_regexp_pattern_info')"
:line="true"
:valid="tabsNotificationBadgeRegExpPatternValid"
input-width="50"
v-model:value="tabsNotificationBadgeRegExpPatternInput"
:inactive="Settings.state.tabsNotificationBadgeScope === 'none'"
@update:value="onTabsNotificationBadgeRegExpPatternUpdate"
@blur="onTabsNotificationBadgeRegExpPatternBlur")
CountField.-inline(
label="settings.tabs_reload_limit"
v-model:value="Settings.state.tabsReloadLimit"
Expand Down Expand Up @@ -497,15 +524,18 @@ section(ref="el")

<script lang="ts" setup>
import { ref, computed, onMounted } from 'vue'
import * as Utils from 'src/utils'
import { translate } from 'src/dict'
import { SETTINGS_OPTIONS } from 'src/defaults'
import { Settings } from 'src/services/settings'
import { Permissions } from 'src/services/permissions'
import { SetupPage } from 'src/services/_services'
import { Tabs } from 'src/services/tabs.fg'
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 '../../components/text-field.vue'

const el = ref<HTMLElement | null>(null)
const newTabPosEl = ref<HTMLElement | null>(null)
Expand Down Expand Up @@ -630,6 +660,32 @@ async function togglePreviewTabs() {
Settings.saveDebounced(150)
}

const tabsNotificationBadgeRegExpPatternEl = ref<TextInputComponent | null>(null)
const tabsNotificationBadgeRegExpPatternValid = ref('')
const tabsNotificationBadgeRegExpPatternInput = ref(Settings.state.tabsNotificationBadgeRegExpPattern)
const tabsNotificationBadgeRegExpPatternValidate = Utils.debounce((value: string): void => {
if (!value) {
tabsNotificationBadgeRegExpPatternValid.value = ''
} else {
try {
new RegExp(value)
tabsNotificationBadgeRegExpPatternValid.value = 'valid'
Settings.state.tabsNotificationBadgeRegExpPattern = value
} catch (e) {
tabsNotificationBadgeRegExpPatternValid.value = 'invalid'
}
}
})

function onTabsNotificationBadgeRegExpPatternUpdate(value: string): void {
tabsNotificationBadgeRegExpPatternValidate(321, value)
Settings.saveDebounced(500)
}

function onTabsNotificationBadgeRegExpPatternBlur(): void {
if (tabsNotificationBadgeRegExpPatternValid.value === 'invalid') tabsNotificationBadgeRegExpPatternEl.value.error()
}

onMounted(() => {
SetupPage.registerEl('settings_tabs', el.value)
SetupPage.registerEl('settings_new_tab_position', newTabPosEl.value)
Expand Down
6 changes: 6 additions & 0 deletions src/services/settings.actions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -192,6 +192,8 @@ export function updateSettingsFg(settings?: SettingsState | null): void {
const colorizeTabsBranchesChanged = prev.colorizeTabsBranches !== next.colorizeTabsBranches
const colorizeTabsBranchesSrcChanged =
prev.colorizeTabsBranchesSrc !== next.colorizeTabsBranchesSrc
const tabsNotificationBadgeScopeChanged = prev.tabsNotificationBadgeScope !== next.tabsNotificationBadgeScope
const tabsNotificationBadgeRegExpPatternChanged = prev.tabsNotificationBadgeRegExpPattern !== next.tabsNotificationBadgeRegExpPattern
const tabsUpdateMarkChanged = prev.tabsUpdateMark !== next.tabsUpdateMark
const navTabsPanelMidClickAction =
prev.navTabsPanelMidClickAction !== next.navTabsPanelMidClickAction
Expand Down Expand Up @@ -293,6 +295,10 @@ export function updateSettingsFg(settings?: SettingsState | null): void {
Tabs.colorizeTabs()
}

if (tabsNotificationBadgeScopeChanged || tabsNotificationBadgeRegExpPatternChanged) {
Tabs.updateNotificationBadgeCountTabs()
}

if (tabsUpdateMarkChanged && next.tabsUpdateMark === 'none') {
for (const tab of Tabs.list) {
tab.reactive.updated = tab.updated = false
Expand Down
30 changes: 30 additions & 0 deletions src/services/tabs.fg.actions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,7 @@ export function mutateNativeTabToSideberyTab(nativeTab: NativeTab): Tab {
sel: tab.sel,
selLock: tab.selLock,
warn: tab.warn,
notificationBadgeCount: null,
updated: tab.updated,
unread: !!tab.unread,
flash: false,
Expand Down Expand Up @@ -113,6 +114,7 @@ export function createReactiveProps(tab: Tab): ReactiveTabProps {
sel: tab.sel,
selLock: tab.selLock,
warn: tab.warn,
notificationBadgeCount: null,
updated: tab.updated,
unread: !!tab.unread,
flash: false,
Expand All @@ -129,6 +131,33 @@ export function createReactiveProps(tab: Tab): ReactiveTabProps {
}
}

export function updateNotificationBadgeCountTabs(): void {
const regexp = new RegExp(Settings.state.tabsNotificationBadgeRegExpPattern)
for (const tab of Tabs.list) {
updateNotificationBadgeCountTab(tab, regexp)
}
}

export function updateNotificationBadgeCountTab(tab: Tab, regexp: RegExp | undefined = undefined): void {
if (
(Settings.state.tabsNotificationBadgeScope === 'none') ||
(Settings.state.tabsNotificationBadgeScope === 'norm' && tab.pinned) ||
(Settings.state.tabsNotificationBadgeScope === 'pin' && !tab.pinned)
) {
tab.reactive.notificationBadgeCount = null
return
}

const matches = (regexp ?? (new RegExp(Settings.state.tabsNotificationBadgeRegExpPattern))).exec(tab.title)
if (!matches) {
tab.reactive.notificationBadgeCount = null
} else {
const notificationBadgeCount = matches.find((e, i) => i > 0 && e) ?? null
console.log(matches)
tab.reactive.notificationBadgeCount = notificationBadgeCount
}
}

export function getStatus(tab: Tab): TabStatus {
if (tab.status === 'loading') return TabStatus.Loading
if (tab.status === 'pending') return TabStatus.Pending
Expand Down Expand Up @@ -203,6 +232,7 @@ export async function load(src?: LoadSrc): Promise<void> {

if (Settings.state.colorizeTabs) Tabs.colorizeTabs()
if (Settings.state.colorizeTabsBranches) Tabs.colorizeBranches()
if (Settings.state.tabsNotificationBadgeScope !== 'none') Tabs.updateNotificationBadgeCountTabs()

Tabs.ready = true

Expand Down
1 change: 1 addition & 0 deletions src/services/tabs.fg.handlers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -995,6 +995,7 @@ function updTabReactiveProps(change: browser.tabs.ChangeInfo, tab: Tab) {
if (change.pinned !== undefined) tab.reactive.pinned = change.pinned
if (change.status !== undefined) tab.reactive.status = Tabs.getStatus(tab)
if (change.title !== undefined) Tabs.renderTitle(tab)
if (change.title !== undefined || change.pinned !== undefined) Tabs.updateNotificationBadgeCountTab(tab)
if (change.url !== undefined) tab.reactive.url = change.url
}

Expand Down
5 changes: 5 additions & 0 deletions src/sidebar/components/tab.vue
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
:data-muted="tab.reactive.mediaMuted"
:data-paused="tab.reactive.mediaPaused"
:data-discarded="tab.reactive.discarded"
:data-notification-count="tab.reactive.notificationBadgeCount"
:data-updated="tab.reactive.updated"
:data-lvl="tab.reactive.lvl"
:data-group="tab.reactive.isGroup"
Expand Down Expand Up @@ -44,6 +45,10 @@
@mousedown.stop="onExpandMouseDown"
@mouseup="onExpandMouseUp")
svg.exp-icon: use(href="#icon_expand")
.notification-badge-count(
v-if="Settings.state.tabsNotificationBadgeStyle === 'count' && Settings.state.tabsNotificationBadgeScope !== 'none'") {{tab.reactive.notificationBadgeCount}}
.notification-badge-dot(
v-if="Settings.state.tabsNotificationBadgeStyle === 'dot' && Settings.state.tabsNotificationBadgeScope !== 'none'")
.badge
.progress-spinner(v-if="Settings.state.animations")
svg.progress-spinner(v-else): use(href="#icon_hourglass")
Expand Down
Loading