Skip to content

Commit

Permalink
Merge pull request #17 from festoney8/dev
Browse files Browse the repository at this point in the history
merge dev to main, v2.3.2
  • Loading branch information
festoney8 authored Jan 12, 2024
2 parents 3fa1faa + f5fcc35 commit 2a653e3
Show file tree
Hide file tree
Showing 14 changed files with 799 additions and 523 deletions.
2 changes: 2 additions & 0 deletions .github/workflows/dev-ci.yml
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
name: dev-ci
on:
push:
paths-ignore:
- '**/*.md'
branches:
- dev
pull_request:
Expand Down
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
# CHANGELOG

## 2.3.2

- 新增:互斥开关
- 优化:N 选 1 的单选选项使用互斥开关(首页布局、热门页布局、顶栏收藏/稍后再看)

## 2.3.1

- 新增:热门/排行榜页 456列布局
Expand Down
11 changes: 9 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -66,18 +66,23 @@
- **「版权视频播放页」大部分功能与「播放页」一致且互相同步,小部分独有功能已用 "★" 重点标出**
- 「动态页」评论区相关功能与「播放页」一致且互相同步
- 「直播页」顶栏与普通顶栏不同,通用功能对其不生效(仅搜索栏少数功能与通用同步)
- 「首页」默认 10 个推荐位,在显示 5 列的情况下,若同时隐藏广告和分区视频,会产生一个待载入空位(骨架),可调节骨架相关选项改善观感
- 「首页」默认 10 个推荐位,在显示 5 列的情况下,若同时隐藏广告和分区视频,会产生一个待载入空位(骨架),可调节骨架相关选项改善观感;在显示 6 列的情况下,会自动载入一轮推荐视频,有一段时间的载入延迟
- 「强制页面布局」功能,适用于和 浏览器缩放比率(100%/110%/125%)、增大字号、隐藏元素 等功能混搭使用,调节出满意的页面效果
- 已知 bug,Firefox 的「强制页面布局」可能出现过长文字换行问题

## 浏览器适配

### Chrome / Edge

- **要求 Chrome 内核版本 >= 105**,浏览器内核版本过低会导致部分功能失效,如:无法净化顶栏

> 鉴于 Google 在推行 [Manifest V3](https://developer.chrome.com/docs/extensions/migrating/checklist),未来会影响油猴插件,参考[Tampermonkey changelog](https://www.tampermonkey.net/changelog.php#v5.0.0)
> 可考虑启用浏览器开发者模式,Chrome 和 Edge 均可在插件管理页找到开关。
### Firefox

- **Firefox版本 103~120,按如下步骤开启高级设定**
- 在浏览器内打开网址 [about:config](about:config)若出现提示页面,点击「接受风险并继续」
- 在浏览器内打开网址 [about:config](about:config)若出现风险提示,点击「接受风险并继续」
- 搜索 `layout.css.has-selector.enabled` ,将这一项的开关改为 `true`,并刷新标签页
- **Firefox版本 > 121,无需修改设定**

Expand Down Expand Up @@ -219,6 +224,8 @@
## Ref

- [vite-plugin-monkey](https://github.com/lisonge/vite-plugin-monkey)
- [Manifest V3](https://developer.chrome.com/docs/extensions/migrating/checklist)
- [TamperMonkey 5.0](https://www.tampermonkey.net/changelog.php#v5.0.0)

## Contribution

Expand Down
16 changes: 8 additions & 8 deletions src/core/group.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { debug, error } from '../utils/logger'
import { NormalItem } from './item'
import { CheckboxItem, RadioItem } from './item'

interface IGroup {
readonly groupHTML: myHTML
Expand All @@ -23,13 +23,13 @@ export class Group implements IGroup {
/**
* Group是每个页面的规则组,每个页面有多个组
* @param groupID group的唯一ID
* @param title group标题, 显示在group顶部
* @param title group标题, 显示在group顶部, 可使用换行符'\n', 可使用HTML
* @param items group内功能列表
*/
constructor(
private groupID: string,
private title: string,
private items: NormalItem[],
private items: (CheckboxItem | RadioItem)[],
) {
this.groupID = 'bili-cleaner-group-' + groupID
}
Expand All @@ -39,7 +39,7 @@ export class Group implements IGroup {
const e = document.createElement('div')
e.innerHTML = this.groupHTML.trim()
e.querySelector('.bili-cleaner-group')!.id = this.groupID
e.querySelector('.bili-cleaner-group-title')!.textContent = this.title
e.querySelector('.bili-cleaner-group-title')!.innerHTML = this.title.replaceAll('\n', '<br>')

const groupList = document.getElementById('bili-cleaner-group-list') as HTMLFormElement
groupList.appendChild(e)
Expand All @@ -49,7 +49,7 @@ export class Group implements IGroup {
try {
this.items.forEach((e) => {
e.insertItem(this.groupID)
if (e instanceof NormalItem) {
if (typeof e.watchItem === 'function') {
e.watchItem()
}
})
Expand All @@ -66,7 +66,7 @@ export class Group implements IGroup {
enableGroup(enableFunc = true) {
try {
this.items.forEach((e) => {
if (e instanceof NormalItem) {
if (typeof e.enableItem === 'function') {
e.enableItem(enableFunc)
}
})
Expand All @@ -80,7 +80,7 @@ export class Group implements IGroup {
reloadGroup() {
try {
this.items.forEach((e) => {
if (e instanceof NormalItem) {
if (typeof e.reloadItem === 'function') {
e.reloadItem()
}
})
Expand All @@ -94,7 +94,7 @@ export class Group implements IGroup {
disableGroup() {
try {
this.items.forEach((e) => {
if (e instanceof NormalItem) {
if (typeof e.removeItemCSS === 'function') {
e.removeItemCSS()
}
})
Expand Down
208 changes: 196 additions & 12 deletions src/core/item.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,20 @@
import { GM_getValue, GM_setValue } from '$'
import { debug, error } from '../utils/logger'

/** Iitem是插件的每项功能设定, 在每个panel group内显示为一行功能 */
/** IItem是插件的每项功能设定, 在每个group内显示为一行组件 */
interface IItem {
readonly uncheckedHTML?: myHTML
readonly checkedHTML?: myHTML
readonly nodeHTML: myHTML
insertItem(groupID: string): void
insertItemCSS?(): void
removeItemCSS?(): void
watchItem?(): void
enableItem?(): void
reloadItem?(): void
}

export class NormalItem implements IItem {
uncheckedHTML = `<input class="bili-cleaner-item-switch" type="checkbox">`
checkedHTML = `<input class="bili-cleaner-item-switch" type="checkbox" checked>`
/** 普通开关 */
export class CheckboxItem implements IItem {
nodeHTML = `<input class="bili-cleaner-item-switch" type="checkbox">`
private isEnable: boolean | undefined
// item对应的HTML input node
private itemEle: HTMLInputElement | undefined
Expand Down Expand Up @@ -59,10 +63,9 @@ export class NormalItem implements IItem {
this.getStatus()
const e = document.createElement('label')
e.id = this.itemID
e.innerHTML = `${this.nodeHTML}<span>${this.description.replaceAll('\n', '<br>')}</span>`
if (this.isEnable) {
e.innerHTML = `${this.checkedHTML}<span>${this.description.replaceAll('\n', '<br>')}</span>`
} else {
e.innerHTML = `${this.uncheckedHTML}<span>${this.description.replaceAll('\n', '<br>')}</span>`
e.querySelector('input')!.checked = true
}
const itemGroupList = document.querySelector(`#${groupID} .bili-cleaner-item-list`) as HTMLFormElement
if (itemGroupList) {
Expand Down Expand Up @@ -111,9 +114,9 @@ export class NormalItem implements IItem {
/** 监听item chekbox开关 */
watchItem() {
try {
this.itemEle = document.getElementById(this.itemID) as HTMLInputElement
this.itemEle = document.querySelector(`#${this.itemID} input`) as HTMLInputElement
this.itemEle.addEventListener('change', (event: Event) => {
// this指向class, 使用event.target访问checkbox
// this指向class, 使用event.target访问input
if ((<HTMLInputElement>event.target).checked) {
this.setStatus(true)
this.insertItemCSS()
Expand Down Expand Up @@ -142,8 +145,8 @@ export class NormalItem implements IItem {
this.insertItemCSS()
if (enableFunc && this.itemFunc instanceof Function) {
this.itemFunc()
debug(`enableItem ${this.itemID} OK`)
}
debug(`enableItem ${this.itemID} OK`)
} catch (err) {
error(`enableItem ${this.itemID} Error`)
error(err)
Expand All @@ -166,3 +169,184 @@ export class NormalItem implements IItem {
}
}
}

/** 互斥开关 */
export class RadioItem implements IItem {
nodeHTML = `<input class="bili-cleaner-item-switch" type="radio">`
private isEnable: boolean | undefined
private itemEle: HTMLInputElement | undefined

/**
* @param itemID item的唯一ID, 与GM database中的Key对应, 使用相同ID可共享item状态
* @param description item的功能介绍, 显示在panel内, \n可用来换行
* @param radioName radio input的name, 用于一组互斥选项
* @param radioItemIDList 当前item所在互斥组的ID列表, 用于修改其他item状态
* @param defaultStatus item默认开启状态, 第一次安装时使用, 对于所有用户均开启的项目默认给true
* @param itemFunc 功能函数
* @param isItemFuncReload 功能函数是否在URL变动时重新运行
* @param itemCSS item的CSS
*/
constructor(
private itemID: string,
private description: string,
private radioName: string,
private radioItemIDList: string[],
private defaultStatus: boolean,
private itemFunc: (() => void) | undefined,
private isItemFuncReload: boolean,
private itemCSS: myCSS | null,
) {
this.isEnable = undefined
this.itemEle = undefined
}
/**
* 设定并记录item开关状态
* @param targetID 设定对象itemID, 默认null 给this对象设定
* @param value 开关状态
*/
setStatus(value: boolean, targetID: string | null = null) {
if (!targetID) {
GM_setValue(`BILICLEANER_${this.itemID}`, value)
this.isEnable = value
} else {
GM_setValue(`BILICLEANER_${targetID}`, value)
}
}
/** 获取item开关状态, 若第一次安装时不存在该key, 使用默认值 */
getStatus() {
this.isEnable = GM_getValue(`BILICLEANER_${this.itemID}`)
if (this.defaultStatus && this.isEnable === undefined) {
this.isEnable = this.defaultStatus
this.setStatus(this.isEnable)
}
}
/**
* 在相应group内添加item
* @param groupID item所属groupID, 由Group调用insertItem时传入
*/
insertItem(groupID: string) {
try {
this.getStatus()
const e = document.createElement('label')
e.id = this.itemID
e.innerHTML = `${this.nodeHTML}<span>${this.description.replaceAll('\n', '<br>')}</span>`
if (this.isEnable) {
e.querySelector('input')!.checked = true
}
// 设定radio input所属互斥组
e.querySelector('input')!.name = this.radioName
const itemGroupList = document.querySelector(`#${groupID} .bili-cleaner-item-list`) as HTMLFormElement
if (itemGroupList) {
itemGroupList.appendChild(e)
debug(`insertItem ${this.itemID} OK`)
}
} catch (err) {
error(`insertItem ${this.itemID} err`)
error(err)
}
}
/** 启用CSS片段, 向<html>插入style */
insertItemCSS() {
if (!this.itemCSS) {
return
}
try {
if (document.querySelector(`html>style[bili-cleaner-css=${this.itemID}]`)) {
debug(`insertItemCSS ${this.itemID} CSS exist, ignore`)
return
}
const style = document.createElement('style')
// 简单压缩, 若使用innerText, 多行CSS插入后会产生<br>标签
style.innerHTML = this.itemCSS.replace(/\n\s*/g, '').trim()
// 指定CSS片段ID,用于实时启用停用
style.setAttribute('bili-cleaner-css', this.itemID)
// 放弃在head内插入style
// 改为在html内插入style标签(与head/body同级), 避免版权视频播放页head内规则丢失bug
document.documentElement.appendChild(style)
debug(`insertItemCSS ${this.itemID} OK`)
} catch (err) {
error(`insertItemCSS ${this.itemID} failed`)
error(err)
}
}
/** 停用CSS片段, 从<html>移除style */
removeItemCSS() {
if (this.itemCSS) {
const style = document.querySelector(`html>style[bili-cleaner-css=${this.itemID}]`) as HTMLStyleElement
if (style) {
style.parentNode?.removeChild(style)
debug(`removeItemCSS ${this.itemID} OK`)
}
}
}
/** 监听item option开关 */
watchItem() {
try {
this.itemEle = document.querySelector(`#${this.itemID} input`) as HTMLInputElement
this.itemEle.addEventListener('change', (event: Event) => {
if ((<HTMLInputElement>event.target).checked) {
debug(`radioItem ${this.itemID} checked`)
this.setStatus(true)
this.insertItemCSS()
if (this.itemFunc !== undefined) {
this.itemFunc()
}
// 相同name的其他option自动置为uncheck, 但这一行为无法被监听, 需传入itemID逐一修改
this.radioItemIDList.forEach((targetID) => {
if (targetID !== this.itemID) {
// 移除CSS, 修改互斥item状态
const style = document.querySelector(
`html>style[bili-cleaner-css=${targetID}]`,
) as HTMLStyleElement
if (style) {
style.parentNode?.removeChild(style)
debug(`removeItemCSS ${targetID} OK`)
}
this.setStatus(false, targetID)
debug(`disable same name radioItem ${targetID}, OK`)
}
})
}
})
debug(`watchItem ${this.itemID} OK`)
} catch (err) {
error(`watchItem ${this.itemID} err`)
error(err)
}
}
/**
* 执行item功能, 添加CSS, 执行func
* @param enableFunc 是否执行func, 默认true
*/
enableItem(enableFunc = true) {
this.getStatus()
if (this.isEnable) {
try {
this.insertItemCSS()
if (enableFunc && this.itemFunc instanceof Function) {
this.itemFunc()
}
debug(`enableItem ${this.itemID} OK`)
} catch (err) {
error(`enableItem ${this.itemID} Error`)
error(err)
}
}
}
/**
* 重载item, 用于非页面刷新但URL变动情况, 此时已注入CSS只重新运行func, 如: 非刷新式切换视频
*/
reloadItem() {
// 存在其他item修改当前item状态的情况
this.getStatus()
if (this.isItemFuncReload && this.isEnable && this.itemFunc instanceof Function) {
try {
this.itemFunc()
debug(`reloadItem ${this.itemID} OK`)
} catch (err) {
error(`reloadItem ${this.itemID} Error`)
error(err)
}
}
}
}
Loading

0 comments on commit 2a653e3

Please sign in to comment.