From ba72969c51d42e651befdf04b9acf64c1cbd1a4e Mon Sep 17 00:00:00 2001 From: SirStendec Date: Mon, 6 Nov 2023 20:47:19 -0500 Subject: [PATCH] 4.60.0 * Added: When searching in the FFZ Control Center, you can now use the tag `@modified` to filter by settings that have been changed in the current profile. * Added: Add-ons now have Changelog buttons that navigate to a changelog showing only entries for that add-on. * Changed: The Changelog pages now has nicer formatting for each commit, including add-on icons and clickable links to add-on sub-pages when viewing the Add-on Changelog. * API Added: To prevent FrankerFaceZ from loading into a page, include `disable_frankerfacez` in the URL query parameters. * Experiment Changed: Fix incorrect roll-out percentage for API-Based Link Lookups. This should be fully enabled. * Experiment Changed: Lower the percentage of users in the MQTT-Based PubSub experiment. --- package.json | 2 +- src/addons.js | 13 +- src/entry.js | 3 + src/entry_ext.js | 3 + src/experiments.json | 10 +- .../main_menu/components/addon-list.vue | 17 +- src/modules/main_menu/components/addon.vue | 22 ++ .../main_menu/components/changelog.vue | 206 ++++++++++++++++-- .../main_menu/components/experiments.vue | 26 ++- .../main_menu/components/main-menu.vue | 32 ++- .../main_menu/components/menu-container.vue | 27 ++- .../main_menu/components/menu-page.vue | 31 ++- .../main_menu/components/menu-tree.vue | 41 +++- src/modules/main_menu/index.js | 8 +- .../styles/color_normalizer.scss | 10 + src/std-components/tab-container.vue | 31 ++- styles/widgets.scss | 6 + 17 files changed, 416 insertions(+), 72 deletions(-) diff --git a/package.json b/package.json index ededf836..cda56e00 100755 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "frankerfacez", "author": "Dan Salvato LLC", - "version": "4.59.1", + "version": "4.60.0", "description": "FrankerFaceZ is a Twitch enhancement suite.", "private": true, "license": "Apache-2.0", diff --git a/src/addons.js b/src/addons.js index 5fc9dd52..4be77b01 100644 --- a/src/addons.js +++ b/src/addons.js @@ -7,7 +7,7 @@ import Module from 'utilities/module'; import { EXTENSION, SERVER_OR_EXT } from 'utilities/constants'; import { createElement } from 'utilities/dom'; -import { timeout, has } from 'utilities/object'; +import { timeout, has, deep_copy } from 'utilities/object'; import { getBuster } from 'utilities/time'; const fetchJSON = (url, options) => fetch(url, options).then(r => r.ok ? r.json() : null).catch(() => null); @@ -51,6 +51,7 @@ export default class AddonManager extends Module { getExtraTerms: () => Object.values(this.addons).map(addon => addon.search_terms), + getFFZ: () => this, isReady: () => this.enabled, getAddons: () => Object.values(this.addons), hasAddon: id => this.hasAddon(id), @@ -216,6 +217,16 @@ export default class AddonManager extends Module { this.addons[id] = [addon.id]; } + if ( ! old ) + this.settings.addUI(`addon-changelog.${addon.id}`, { + path: `Add-Ons > Changelog > ${addon.name}`, + component: 'changelog', + force_seen: true, + addons: true, + addon: deep_copy(addon), + getFFZ: () => this + }); + this.emit(':added'); } diff --git a/src/entry.js b/src/entry.js index 7b3ae680..19f74228 100644 --- a/src/entry.js +++ b/src/entry.js @@ -5,6 +5,9 @@ if ( /^(?:localhost\.rig|blog|im|chatdepot|tmi|api|brand|dev|gql|passport)\./.test(location.hostname) ) return; + if ( /disable_frankerfacez/.test(location.search) ) + return; + const DEBUG = localStorage.ffzDebugMode == 'true' && document.body.classList.contains('ffz-dev'), HOST = location.hostname, SERVER = DEBUG ? '//localhost:8000' : '//cdn.frankerfacez.com', diff --git a/src/entry_ext.js b/src/entry_ext.js index 93e47954..267dfc14 100644 --- a/src/entry_ext.js +++ b/src/entry_ext.js @@ -5,6 +5,9 @@ if ( /^(?:localhost\.rig|blog|im|chatdepot|tmi|api|brand|dev|gql|passport)\./.test(location.hostname) ) return; + if ( /disable_frankerfacez/.test(location.search) ) + return; + const browser = globalThis.browser ?? globalThis.chrome, HOST = location.hostname, diff --git a/src/experiments.json b/src/experiments.json index 36d46575..5ceea43e 100644 --- a/src/experiments.json +++ b/src/experiments.json @@ -11,17 +11,17 @@ "name": "API-Based Link Lookups", "description": "Use the new API to look up links instead of the socket cluster.", "groups": [ - {"value": true, "weight": 30}, - {"value": "cf", "weight": 10}, - {"value": false, "weight": 60} + {"value": true, "weight": 0}, + {"value": "cf", "weight": 100}, + {"value": false, "weight": 0} ] }, "cf_pubsub": { "name": "MQTT-Based PubSub", "description": "An experimental new pubsub system that should be more reliable than the existing socket cluster.", "groups": [ - {"value": true, "weight": 10}, - {"value": false, "weight": 90} + {"value": true, "weight": 5}, + {"value": false, "weight": 95} ] } } \ No newline at end of file diff --git a/src/modules/main_menu/components/addon-list.vue b/src/modules/main_menu/components/addon-list.vue index 4a94f10a..b0bd341b 100644 --- a/src/modules/main_menu/components/addon-list.vue +++ b/src/modules/main_menu/components/addon-list.vue @@ -60,6 +60,7 @@ :item="item" :context="context" @navigate="navigate" + @change-item="changeItem" /> @@ -68,7 +69,7 @@ visible: visible_addons.length, total: listed_addons.length }) }} - +
-

+

+
+ +
+ {{ t('setting.add_ons.specific-changelog', '{name} Changelog', { + name: addon.name + }) }} +

+

{{ t('setting.add_ons.changelog.title', 'Add-Ons Changelog') }} -

-

+

+

{{ t('home.changelog', 'Changelog') }} -

+
@@ -38,10 +52,30 @@
{{ t('home.changelog.current', 'Current Version') }}
-
+
+ +
+
+ {{ commit.title }} + +
{{ commit.title }}
-
+
+ v{{ commit.version }} +
+
({{ formatDate(commit.date) }})
+
+ {{ entry.key }} + +
@@ -111,7 +149,21 @@ import {get} from 'utilities/object'; -const TITLE_MATCH = /^v?(\d+\.\d+\.\d+(?:-[^\n]+)?)\n+/; +const TITLE_MATCH = /^(.+?)?\s*v?(\d+\.\d+\.\d+(?:\-[a-z0-9-]+)?)$/i, + SETTING_REGEX = /\]\(~([^)]+)\)/g, + CHANGE_REGEX = /^\*\s*([^:]+?):\s*(.+)$/i, + ISSUE_REGEX = /(^|\s)#(\d+)\b/g; + + +function linkify(text, repo) { + text = text.replace(SETTING_REGEX, (_, link) => { + return `](~${link})` + }); + + return text.replace(ISSUE_REGEX, (_, space, number) => { + return `${space}[#${number}](https://github.com/FrankerFaceZ/${repo}/issues/${number})`; + }); +} export default { @@ -120,6 +172,7 @@ export default { data() { return { error: false, + addon: this.item.addon, addons: this.item.addons, nonversioned: false, loading: false, @@ -130,35 +183,130 @@ export default { computed: { display() { + window.thing = this; + const out = [], + addons = this.addons ? this.item.getFFZ().resolve('addons') : null, old_commit = this.t('home.changelog.nonversioned', 'Non-Versioned Commit'); for(const commit of this.commits) { - let message = commit.commit.message, + const input = commit.commit.message; + let title = old_commit, + title_nav = null, + icon = null, + version = null, author = null, - title = old_commit; + sections = {}, + description = []; - if ( this.addons ) { - title = null; - author = commit.author; + if ( /\bskiplog\b/i.test(input) && ! this.nonversion ) + continue; - } else { - const match = TITLE_MATCH.exec(message); - - if ( match ) { - title = match[1]; - message = message.slice(match[0].length); - } else if ( ! this.nonversioned ) - continue; - } + const lines = input.split(/\r?\n/), + first = lines.shift(), + match = first ? TITLE_MATCH.exec(first) : null; const date = new Date(commit.commit.author.date), - active = commit.sha === window.FrankerFaceZ.version_info.commit; + active = commit.sha === window.FrankerFaceZ.version_info.commit, + has_content = lines.length && match; + + if ( ! this.nonversion && ! has_content ) + continue; + + let last_bit = null; + + if ( match ) { + title = match[1]; + version = match[2]; + } + + if ( has_content ) + for(const line of lines) { + const trimmed = line.trim(); + if ( ! trimmed.length ) { + if ( ! last_bit && description.length ) + description.push(line); + continue; + } + + const m = CHANGE_REGEX.exec(trimmed); + if ( ! m ) { + if ( ! last_bit ) + description.push(line); + else + last_bit.push(trimmed); + + } else { + const section = sections[m[1]] = sections[m[1]] || []; + last_bit = [m[2]]; + section.push(last_bit); + } + } + + else { + lines.unshift(first); + description = lines; + } + + let message = description.join('\n').trim(); + + const segments = []; + + for(const [key, val] of Object.entries(sections)) { + if ( ! val?.length ) + continue; + + const bit = val.map(x => `* ${x.join(' ')}`).join('\n').trim(); + + segments.push({ + key, + value: linkify(bit, this.addons ? 'add-ons' : 'frankerfacez') + }); + } + + if ( this.addons ) { + author = commit.author; + + if ( title ) { + const ltitle = title.toLowerCase(); + + if ( addons?.addons ) + for(const addon of Object.values(addons.addons)) { + if ((addon.short_name && addon.short_name.toLowerCase() === ltitle) || + (addon.name && addon.name.toLowerCase() === ltitle) || + (addon.id && addon.id.toLowerCase() === ltitle) + ) { + icon = addon.icon; + + title_nav = [`add_ons.changelog.${addon.id}`]; + if ( addon.short_name ) + title_nav.push(`add_ons.changelog.${addon.short_name.toSnakeCase()}`); + if ( addon.name ) + title_nav.push(`add_ons.changelog.${addon.name.toSnakeCase()}`); + + break; + } + } + + // Default Icon + if ( ! icon ) + icon = 'https://cdn.frankerfacez.com/badge/2/4/solid'; + } + } + + if ( this.addon ) { + icon = null; + title = null; + } out.push({ + icon, title, + title_nav, + version, author, message, + segments, active, hash: commit.sha && commit.sha.slice(0,7), link: commit.html_url, @@ -177,6 +325,11 @@ export default { }, methods: { + titleNav(nav) { + if ( Array.isArray(nav) ) + this.$emit('navigate', ...nav); + }, + formatDate(value) { if ( ! value ) return ''; @@ -197,8 +350,15 @@ export default { this.loading = true; + const url = new URL(`https://api.github.com/repos/frankerfacez/${this.addons ? 'add-ons' : 'frankerfacez'}/commits`); + if ( until ) + url.searchParams.append('until', until); + + if ( this.addon ) + url.searchParams.append('path', `src/${this.addon.id}`); + try { - const resp = await fetch(`https://api.github.com/repos/frankerfacez/${this.addons ? 'add-ons' : 'frankerfacez'}/commits${until ? `?until=${until}` : ''}`), + const resp = await fetch(url), data = resp.ok ? await resp.json() : null; if ( ! data || ! Array.isArray(data) ) { diff --git a/src/modules/main_menu/components/experiments.vue b/src/modules/main_menu/components/experiments.vue index 5f2dffa5..bb455f8c 100644 --- a/src/modules/main_menu/components/experiments.vue +++ b/src/modules/main_menu/components/experiments.vue @@ -252,12 +252,15 @@ export default { }, visible_ffz() { - const items = this.sorted_ffz, - f = this.filter && this.filter.toLowerCase(); - if ( ! f ) - return items; + let out = this.sorted_ffz; - return items.filter(x => matches(x, f)); + if ( this.filter?.query ) + out = out.filter(x => matches(x, this.filter.query)); + + if ( this.filter?.flags && this.filter.flags.has('modified') ) + out = out.filter(x => this.item.hasOverride(x.key)); + + return out; }, sorted_twitch() { @@ -265,12 +268,15 @@ export default { }, visible_twitch() { - const items = this.sorted_twitch, - f = this.filter && this.filter.toLowerCase(); - if ( ! f ) - return items; + let out = this.sorted_twitch; - return items.filter(x => matches(x, f)); + if ( this.filter?.query ) + out = out.filter(x => matches(x, this.filter.query)); + + if ( this.filter?.flags && this.filter.flags.has('modified') ) + out = out.filter(x => this.item.hasTwitchOverride(x.key)); + + return out; } }, diff --git a/src/modules/main_menu/components/main-menu.vue b/src/modules/main_menu/components/main-menu.vue index fee567a6..7c2b919b 100644 --- a/src/modules/main_menu/components/main-menu.vue +++ b/src/modules/main_menu/components/main-menu.vue @@ -79,6 +79,7 @@ :current-item="currentItem" :modal="nav" :filter="filter" + :context="context" @change-item="changeItem" @mark-seen="markSeen" @mark-expanded="markExpanded" @@ -128,6 +129,10 @@ import displace from 'displacejs'; import { getDialogNextZ } from 'src/utilities/dialog'; +const VALID_FLAGS = [ + 'modified' +]; + export default { data() { const out = this.$vnode.data; @@ -139,7 +144,32 @@ export default { computed: { filter() { - return this.query.toLowerCase() + let query = this.query.toLowerCase(); + + let flags = new Set; + query = query.replace(/(?<=^|\s)@(\S+)(?:\s+|$)/g, (match, flag, index) => { + if ( VALID_FLAGS.includes(flag) ) { + flags.add(flag); + return ''; + } + + return match; + }); + + query = query.trim(); + if ( ! query.length ) + query = null; + + if ( ! flags.size ) + flags = null; + + if ( ! query && ! flags ) + return null; + + return { + flags, + query + } } }, diff --git a/src/modules/main_menu/components/menu-container.vue b/src/modules/main_menu/components/menu-container.vue index 1b804431..329be5a7 100644 --- a/src/modules/main_menu/components/menu-container.vue +++ b/src/modules/main_menu/components/menu-container.vue @@ -57,11 +57,32 @@ export default { this.$emit('navigate', ...args); }, - shouldShow(item) { - if ( ! this.filter || ! this.filter.length || ! item.search_terms ) + shouldShow(item, is_walking = false) { + if ( ! this.filter || item.no_filter ) return true; - return item.search_terms.includes(this.filter); + if ( this.filter.flags ) { + if ( this.filter.flags.has('modified') ) { + // We need to tree walk for this one. + if ( ! is_walking ) { + for(const key of ['tabs', 'contents', 'items']) + if ( item[key] ) + for(const thing of item[key]) + if ( this.shouldShow(thing) ) + return true; + } + + if ( ! item.setting || ! this.context.currentProfile.has(item.setting) ) + return false; + } + } + + if ( this.filter.query ) { + if ( ! item.search_terms || ! item.search_terms.includes(this.filter.query) ) + return false; + } + + return true; } } } diff --git a/src/modules/main_menu/components/menu-page.vue b/src/modules/main_menu/components/menu-page.vue index b0b8fbf0..029f0645 100644 --- a/src/modules/main_menu/components/menu-page.vue +++ b/src/modules/main_menu/components/menu-page.vue @@ -149,15 +149,36 @@ export default { }, methods: { - shouldShow(item) { - if ( ! this.filter || ! this.filter.length || ! item.search_terms ) + shouldShow(item, is_walking = false) { + if ( ! this.filter || item.no_filter ) return true; - return item.no_filter || item.search_terms.includes(this.filter); + if ( this.filter.flags ) { + if ( this.filter.flags.has('modified') ) { + // We need to tree walk for this one. + if ( ! is_walking ) { + for(const key of ['tabs', 'contents', 'items']) + if ( item[key] ) + for(const thing of item[key]) + if ( this.shouldShow(thing) ) + return true; + } + + if ( ! item.setting || ! this.context.currentProfile.has(item.setting) ) + return false; + } + } + + if ( this.filter.query ) { + if ( ! item.search_terms || ! item.search_terms.includes(this.filter.query) ) + return false; + } + + return true; }, countMatches(item, seen) { - if ( ! this.filter || ! this.filter.length || ! item ) + if ( ! this.filter || ! item ) return 0; if ( seen && seen.has(item) ) @@ -175,7 +196,7 @@ export default { for(const thing of item[key]) count += this.countMatches(thing, seen); - if ( item.setting && item.search_terms && item.search_terms.includes(this.filter) ) + if ( item.setting && this.shouldShow(item, true) ) count++; return count; diff --git a/src/modules/main_menu/components/menu-tree.vue b/src/modules/main_menu/components/menu-tree.vue index 2bef5f5d..9178239d 100644 --- a/src/modules/main_menu/components/menu-tree.vue +++ b/src/modules/main_menu/components/menu-tree.vue @@ -13,7 +13,7 @@
  • @@ -26,7 +26,7 @@ >
  • Changelog @{"profile_warning": false}', - component: 'changelog' + component: 'changelog', + getFFZ: () => this }); this.settings.addUI('addon-changelog', { - path: 'Add-Ons > Changelog @{"sort": -1000, "profile_warning": false}', + path: 'Add-Ons > Changelog @{"sort": -1000, "profile_warning": false, "hide_children": true}', component: 'changelog', force_seen: true, - addons: true + addons: true, + getFFZ: () => this }); this.settings.addUI('legal', { diff --git a/src/sites/twitch-twilight/styles/color_normalizer.scss b/src/sites/twitch-twilight/styles/color_normalizer.scss index 37744101..553ad417 100644 --- a/src/sites/twitch-twilight/styles/color_normalizer.scss +++ b/src/sites/twitch-twilight/styles/color_normalizer.scss @@ -103,6 +103,16 @@ background: linear-gradient(90deg, var(--color-background-body) 60%, transparent) !important; } + .directory-header-new__banner-cover { + background: linear-gradient(0deg, + var(--color-background-body), + color-mix(in srgb, var(--color-background-body) 25%, transparent) + ), linear-gradient(90deg, + var(--color-background-body), + color-mix(in srgb, var(--color-background-body) 25%, transparent) + ); + } + .emote-picker__emote-link { &:hover { background-color: var(--color-background-button-text-hover) !important; diff --git a/src/std-components/tab-container.vue b/src/std-components/tab-container.vue index 7ee5872c..f62cdef7 100644 --- a/src/std-components/tab-container.vue +++ b/src/std-components/tab-container.vue @@ -119,7 +119,7 @@ export default { methods: { countMatches(item, seen) { - if ( ! this.filter || ! this.filter.length || ! item ) + if ( ! this.filter || ! item ) return 0; if ( seen && seen.has(item) ) @@ -137,7 +137,7 @@ export default { for(const thing of item[key]) count += this.countMatches(thing, seen); - if ( item.setting && item.search_terms && item.search_terms.includes(this.filter) ) + if ( item.setting && this.shouldShow(item, true) ) count++; return count; @@ -209,11 +209,32 @@ export default { } }, - shouldShow(item) { - if ( ! this.filter || ! this.filter.length || ! item.search_terms ) + shouldShow(item, is_walking = false) { + if ( ! this.filter || item.no_filter ) return true; - return item.search_terms.includes(this.filter); + if ( this.filter.flags ) { + if ( this.filter.flags.has('modified') ) { + // We need to tree walk for this one. + if ( ! is_walking ) { + for(const key of ['tabs', 'contents', 'items']) + if ( item[key] ) + for(const thing of item[key]) + if ( this.shouldShow(thing) ) + return true; + } + + if ( ! item.setting || ! this.context.currentProfile.has(item.setting) ) + return false; + } + } + + if ( this.filter.query ) { + if ( ! item.search_terms || ! item.search_terms.includes(this.filter.query) ) + return false; + } + + return true; } } } diff --git a/styles/widgets.scss b/styles/widgets.scss index 0686bb8e..6208b4b2 100644 --- a/styles/widgets.scss +++ b/styles/widgets.scss @@ -397,6 +397,12 @@ textarea.ffz-input { } } +.ffz--changelog-segment { + ul { + list-style: disc; + } +} + .ffz--clear-settings code, .ffz--experiments code { user-select: none;