mirror of
https://github.com/the1812/Bilibili-Evolved.git
synced 2025-11-04 21:22:45 +08:00
commit
88953d7052
@ -4,9 +4,9 @@ module.exports = {
|
||||
es2020: true,
|
||||
},
|
||||
extends: [
|
||||
'plugin:vue/recommended',
|
||||
'plugin:@typescript-eslint/recommended',
|
||||
'airbnb-base',
|
||||
'plugin:vue/vue3-recommended',
|
||||
'plugin:@typescript-eslint/recommended',
|
||||
'plugin:prettier/recommended',
|
||||
],
|
||||
globals: {
|
||||
@ -30,10 +30,11 @@ module.exports = {
|
||||
'import/no-default-export': 'error',
|
||||
'import/no-named-default': 'off',
|
||||
|
||||
'@typescript-eslint/no-non-null-assertion': 'off',
|
||||
'@typescript-eslint/consistent-type-imports': 'error',
|
||||
'@typescript-eslint/member-delimiter-style': 'off',
|
||||
'@typescript-eslint/explicit-function-return-type': 'off',
|
||||
'@typescript-eslint/explicit-module-boundary-types': 'off',
|
||||
'@typescript-eslint/no-unused-vars': 'error',
|
||||
'@typescript-eslint/no-shadow': ['error', { builtinGlobals: false }],
|
||||
'@typescript-eslint/no-explicit-any': 'off',
|
||||
'@typescript-eslint/no-use-before-define': ['error'],
|
||||
@ -52,6 +53,7 @@ module.exports = {
|
||||
'vue/require-prop-types': 'off',
|
||||
'vue/one-component-per-file': 'off',
|
||||
'vue/singleline-html-element-content-newline': 'off',
|
||||
'vue/multi-word-component-names': 'off',
|
||||
|
||||
// 使用 @typescript-eslint/no-unused-vars, 否则 interface 都是 unused
|
||||
'no-unused-vars': 'off',
|
||||
@ -77,6 +79,7 @@ module.exports = {
|
||||
|
||||
'arrow-body-style': 'off',
|
||||
'prefer-arrow-callback': 'off',
|
||||
'prefer-regex-literals': 'off',
|
||||
'object-curly-newline': 'off',
|
||||
'linebreak-style': 'off',
|
||||
camelcase: 'off',
|
||||
|
||||
2
.vscode/tasks.json
vendored
2
.vscode/tasks.json
vendored
@ -54,7 +54,7 @@
|
||||
},
|
||||
{
|
||||
"type": "shell",
|
||||
"command": "pnpm tsc -p tsconfig.type-check.json --noEmit",
|
||||
"command": "pnpm vue-tsc -p tsconfig.type-check.json --noEmit",
|
||||
"group": "build",
|
||||
"problemMatcher": [],
|
||||
"label": "生产:类型检查 prod:type"
|
||||
|
||||
@ -153,9 +153,6 @@ pnpm install
|
||||
### 全局
|
||||
全局变量, 无需 `import` 就可以直接使用. (Tampermonkey API 这里不再列出了, 可根据代码提示使用)
|
||||
|
||||
- `Vue`: Vue 库的主对象, 在创建 `.vue` 组件时, 其中的 `<script>` 可以直接使用 `Vue.extend()`
|
||||
> 出于历史原因, 项目中用的还是 Vue 2, 由于其糟糕的 TypeScript 支持, 在 VS Code + Vetur 的环境下浏览 `.vue` 文件可能会报各种奇奇怪怪的类型错误, 无视就好. (类型是否正确以 `pnpm run type` 的结果为准)
|
||||
|
||||
- `lodash`: 包含所有 Lodash 库提供的方法
|
||||
- `dq` / `dqa`: `document.querySelector` 和 `document.querySelectorAll` 的简写, `dqa` 会返回真实数组
|
||||
> 在 `bwp-video` 出现后, 这两个查询函数还会自动将对 `video` 的查询扩展到 `bwp-video`
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import { readFileSync, existsSync } from 'fs'
|
||||
import { existsSync, readFileSync } from 'fs'
|
||||
|
||||
interface DevServerConfig {
|
||||
port?: number
|
||||
|
||||
@ -1,8 +1,9 @@
|
||||
import webpack from 'webpack'
|
||||
import exitHook from 'async-exit-hook'
|
||||
import webpack from 'webpack'
|
||||
|
||||
import webpackConfig from '../../webpack/webpack.dev'
|
||||
import { sendMessage } from './web-socket-server'
|
||||
import { defaultWatcherHandler } from './watcher-common'
|
||||
import { sendMessage } from './web-socket-server'
|
||||
|
||||
export const startCoreWatcher = () =>
|
||||
new Promise<void>(resolve => {
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import { startDevServer } from './server'
|
||||
import { startCoreWatcher } from './core-watcher'
|
||||
import { startDevServer } from './server'
|
||||
import { startWebSocketServer } from './web-socket-server'
|
||||
|
||||
startDevServer().then(server => {
|
||||
|
||||
@ -1,9 +1,11 @@
|
||||
import { Watching, Configuration, webpack } from 'webpack'
|
||||
import exitHook from 'async-exit-hook'
|
||||
import type { Configuration, Watching } from 'webpack'
|
||||
import { webpack } from 'webpack'
|
||||
|
||||
import { fromId } from '../../registry/lib/id'
|
||||
import { devServerConfig } from './config'
|
||||
import { defaultWatcherHandler } from './watcher-common'
|
||||
import { sendMessage } from './web-socket-server'
|
||||
import { devServerConfig } from './config'
|
||||
|
||||
export const watchers: { url: string; instance: Watching }[] = []
|
||||
export const parseRegistryUrl = (url: string) => {
|
||||
|
||||
@ -1,11 +1,13 @@
|
||||
import { createServer, Server } from 'http'
|
||||
import { Configuration } from 'webpack'
|
||||
import exitHook from 'async-exit-hook'
|
||||
import type { Server } from 'http'
|
||||
import { createServer } from 'http'
|
||||
import handler from 'serve-handler'
|
||||
import { devServerConfig } from './config'
|
||||
import type { Configuration } from 'webpack'
|
||||
|
||||
import { buildByEntry } from '../../registry/webpack/config'
|
||||
import { devServerConfig } from './config'
|
||||
import { parseRegistryUrl, startRegistryWatcher, watchers } from './registry-watcher'
|
||||
import { exitWebSocketServer } from './web-socket-server'
|
||||
import { watchers, parseRegistryUrl, startRegistryWatcher } from './registry-watcher'
|
||||
|
||||
export const startDevServer = () =>
|
||||
new Promise<Server>(resolve => {
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import { Stats } from 'webpack'
|
||||
import type { Stats } from 'webpack'
|
||||
|
||||
export const defaultWatcherHandler = (
|
||||
initCallback: (result: Stats) => void,
|
||||
|
||||
@ -1,7 +1,8 @@
|
||||
import exitHook from 'async-exit-hook'
|
||||
import { Server } from 'http'
|
||||
import type { Server } from 'http'
|
||||
import { WebSocketServer } from 'ws'
|
||||
import { Payload } from './payload'
|
||||
|
||||
import type { Payload } from './payload'
|
||||
import { stopInstance, watchers } from './registry-watcher'
|
||||
|
||||
let server: WebSocketServer
|
||||
@ -32,9 +33,6 @@ export const startWebSocketServer = (httpServer: Server) =>
|
||||
const payload: Payload = JSON.parse(data.toString())
|
||||
console.log('收到 DevClient 消息:', payload)
|
||||
switch (payload.type) {
|
||||
default: {
|
||||
break
|
||||
}
|
||||
case 'itemStop': {
|
||||
const { path } = payload
|
||||
const watcherIndex = watchers.findIndex(it => it.url === path)
|
||||
@ -50,6 +48,9 @@ export const startWebSocketServer = (httpServer: Server) =>
|
||||
sendMessage({ type: 'querySessionsResponse', sessions: watchers.map(it => it.url) })
|
||||
break
|
||||
}
|
||||
default: {
|
||||
break
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('无效信息', data)
|
||||
|
||||
22
package.json
22
package.json
@ -11,7 +11,7 @@
|
||||
"build-github-config": "ts-node ./.github-json/index.ts",
|
||||
"lint": "eslint --quiet --fix . --ext .ts,.vue",
|
||||
"lint-check": "eslint . --ext .ts,.vue",
|
||||
"type": "tsc -p tsconfig.type-check.json --noEmit"
|
||||
"type": "vue-tsc -p tsconfig.type-check.json --noEmit"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/core": "7.20.12",
|
||||
@ -30,21 +30,22 @@
|
||||
"@types/serve-handler": "^6.1.1",
|
||||
"@types/sortablejs": "^1.10.7",
|
||||
"@types/streamsaver": "^2.0.1",
|
||||
"@types/webpack-env": "^1.15.1",
|
||||
"@types/webpack-env": "^1.16.4",
|
||||
"@types/ws": "^8.2.3",
|
||||
"@typescript-eslint/eslint-plugin": "^5.50.0",
|
||||
"@typescript-eslint/parser": "^5.50.0",
|
||||
"@vue/tsconfig": "^0.1.3",
|
||||
"async-exit-hook": "^2.0.1",
|
||||
"autoprefixer": "^10.0.1",
|
||||
"babel-loader": "^8.1.0",
|
||||
"browserslist": "^4.21.4",
|
||||
"css-loader": "^5.0.0",
|
||||
"eslint": "^7.32.0",
|
||||
"eslint-config-airbnb-base": "^14.1.0",
|
||||
"eslint": "^8.36.0",
|
||||
"eslint-config-airbnb-base": "^15.0.0",
|
||||
"eslint-config-prettier": "^8.6.0",
|
||||
"eslint-plugin-import": "^2.20.1",
|
||||
"eslint-plugin-prettier": "^4.2.1",
|
||||
"eslint-plugin-vue": "7.1.0",
|
||||
"eslint-plugin-vue": "9.9.0",
|
||||
"fast-sass-loader": "^2.0.0",
|
||||
"glob": "^10.2.6",
|
||||
"postcss": "^8.1.0",
|
||||
@ -60,8 +61,8 @@
|
||||
"to-string-loader": "^1.2.0",
|
||||
"ts-node": "^10.7.0",
|
||||
"typescript": "^4.9.5",
|
||||
"vue-loader": "^15.8.3",
|
||||
"vue-template-compiler": "^2.6.11",
|
||||
"vue-loader": "^17.2.2",
|
||||
"vue-tsc": "^1.8.1",
|
||||
"webpack": "^5.31.2",
|
||||
"webpack-bundle-analyzer": "^4.5.0",
|
||||
"webpack-cli": "^4.6.0",
|
||||
@ -72,17 +73,20 @@
|
||||
"@popperjs/core": "^2.6.0",
|
||||
"color": "^3.1.2",
|
||||
"fuse.js": "^6.4.6",
|
||||
"jszip": "^3.7.1",
|
||||
"jszip": "3.10.1",
|
||||
"lodash": "^4.17.21",
|
||||
"marked": "^1.2.5",
|
||||
"protobufjs": "^6.11.2",
|
||||
"streamsaver": "^2.0.6",
|
||||
"tippy.js": "^6.3.1",
|
||||
"vue": "^2.6.11"
|
||||
"vue": "^3.3.4"
|
||||
},
|
||||
"pnpm": {
|
||||
"overrides": {
|
||||
"caniuse-lite": "^1.0.30001481"
|
||||
},
|
||||
"patchedDependencies": {
|
||||
"jszip@3.10.1": "patches/jszip@3.10.1.patch"
|
||||
}
|
||||
},
|
||||
"keywords": [
|
||||
|
||||
176
patches/jszip@3.10.1.patch
Normal file
176
patches/jszip@3.10.1.patch
Normal file
@ -0,0 +1,176 @@
|
||||
diff --git a/.idea/.gitignore b/.idea/.gitignore
|
||||
new file mode 100644
|
||||
index 0000000000000000000000000000000000000000..10b731c518c79596ed8690544cfbc87a98cf7e36
|
||||
--- /dev/null
|
||||
+++ b/.idea/.gitignore
|
||||
@@ -0,0 +1,5 @@
|
||||
+# 默认忽略的文件
|
||||
+/shelf/
|
||||
+/workspace.xml
|
||||
+# 基于编辑器的 HTTP 客户端请求
|
||||
+/httpRequests/
|
||||
diff --git a/.idea/76e50e522c18ecb7026f8d1c95ab7351.iml b/.idea/76e50e522c18ecb7026f8d1c95ab7351.iml
|
||||
new file mode 100644
|
||||
index 0000000000000000000000000000000000000000..0c8867d7e175f46d4bcd66698ac13f4ca00cf592
|
||||
--- /dev/null
|
||||
+++ b/.idea/76e50e522c18ecb7026f8d1c95ab7351.iml
|
||||
@@ -0,0 +1,12 @@
|
||||
+<?xml version="1.0" encoding="UTF-8"?>
|
||||
+<module type="WEB_MODULE" version="4">
|
||||
+ <component name="NewModuleRootManager">
|
||||
+ <content url="file://$MODULE_DIR$">
|
||||
+ <excludeFolder url="file://$MODULE_DIR$/temp" />
|
||||
+ <excludeFolder url="file://$MODULE_DIR$/.tmp" />
|
||||
+ <excludeFolder url="file://$MODULE_DIR$/tmp" />
|
||||
+ </content>
|
||||
+ <orderEntry type="inheritedJdk" />
|
||||
+ <orderEntry type="sourceFolder" forTests="false" />
|
||||
+ </component>
|
||||
+</module>
|
||||
\ No newline at end of file
|
||||
diff --git a/.idea/inspectionProfiles/Project_Default.xml b/.idea/inspectionProfiles/Project_Default.xml
|
||||
new file mode 100644
|
||||
index 0000000000000000000000000000000000000000..03d9549ea8e4ada36fb3ecbc30fef08175b7d728
|
||||
--- /dev/null
|
||||
+++ b/.idea/inspectionProfiles/Project_Default.xml
|
||||
@@ -0,0 +1,6 @@
|
||||
+<component name="InspectionProjectProfileManager">
|
||||
+ <profile version="1.0">
|
||||
+ <option name="myName" value="Project Default" />
|
||||
+ <inspection_tool class="Eslint" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
+ </profile>
|
||||
+</component>
|
||||
\ No newline at end of file
|
||||
diff --git a/.idea/modules.xml b/.idea/modules.xml
|
||||
new file mode 100644
|
||||
index 0000000000000000000000000000000000000000..86959bb4dfd3ede12f4d2769b074ab205b6cb759
|
||||
--- /dev/null
|
||||
+++ b/.idea/modules.xml
|
||||
@@ -0,0 +1,8 @@
|
||||
+<?xml version="1.0" encoding="UTF-8"?>
|
||||
+<project version="4">
|
||||
+ <component name="ProjectModuleManager">
|
||||
+ <modules>
|
||||
+ <module fileurl="file://$PROJECT_DIR$/.idea/76e50e522c18ecb7026f8d1c95ab7351.iml" filepath="$PROJECT_DIR$/.idea/76e50e522c18ecb7026f8d1c95ab7351.iml" />
|
||||
+ </modules>
|
||||
+ </component>
|
||||
+</project>
|
||||
\ No newline at end of file
|
||||
diff --git a/.idea/workspace.xml b/.idea/workspace.xml
|
||||
new file mode 100644
|
||||
index 0000000000000000000000000000000000000000..e1e646f4406f21f50f8aeb462daf20493f72c77e
|
||||
--- /dev/null
|
||||
+++ b/.idea/workspace.xml
|
||||
@@ -0,0 +1,44 @@
|
||||
+<?xml version="1.0" encoding="UTF-8"?>
|
||||
+<project version="4">
|
||||
+ <component name="ChangeListManager">
|
||||
+ <list default="true" id="61a15d74-703e-43e7-9226-85dcec01205b" name="变更" comment="" />
|
||||
+ <option name="SHOW_DIALOG" value="false" />
|
||||
+ <option name="HIGHLIGHT_CONFLICTS" value="true" />
|
||||
+ <option name="HIGHLIGHT_NON_ACTIVE_CHANGELIST" value="false" />
|
||||
+ <option name="LAST_RESOLUTION" value="IGNORE" />
|
||||
+ </component>
|
||||
+ <component name="MarkdownSettingsMigration">
|
||||
+ <option name="stateVersion" value="1" />
|
||||
+ </component>
|
||||
+ <component name="ProjectId" id="2L8WtYspVZmmp1sF8ITjwQ8QlvA" />
|
||||
+ <component name="ProjectViewState">
|
||||
+ <option name="hideEmptyMiddlePackages" value="true" />
|
||||
+ <option name="showLibraryContents" value="true" />
|
||||
+ </component>
|
||||
+ <component name="PropertiesComponent"><![CDATA[{
|
||||
+ "keyToString": {
|
||||
+ "RunOnceActivity.OpenProjectViewOnStart": "true",
|
||||
+ "RunOnceActivity.ShowReadmeOnStart": "true",
|
||||
+ "WebServerToolWindowFactoryState": "false",
|
||||
+ "last_opened_file_path": "C:/Users/zheyang_w/AppData/Local/Temp/76e50e522c18ecb7026f8d1c95ab7351",
|
||||
+ "node.js.detected.package.eslint": "true",
|
||||
+ "node.js.selected.package.eslint": "(autodetect)",
|
||||
+ "vue.rearranger.settings.migration": "true"
|
||||
+ }
|
||||
+}]]></component>
|
||||
+ <component name="SpellCheckerSettings" RuntimeDictionaries="0" Folders="0" CustomDictionaries="0" DefaultDictionary="应用程序级" UseSingleDictionary="true" transferred="true" />
|
||||
+ <component name="TaskManager">
|
||||
+ <task active="true" id="Default" summary="默认任务">
|
||||
+ <changelist id="61a15d74-703e-43e7-9226-85dcec01205b" name="变更" comment="" />
|
||||
+ <created>1675253404526</created>
|
||||
+ <option name="number" value="Default" />
|
||||
+ <option name="presentableId" value="Default" />
|
||||
+ <updated>1675253404526</updated>
|
||||
+ <workItem from="1675253405601" duration="191000" />
|
||||
+ </task>
|
||||
+ <servers />
|
||||
+ </component>
|
||||
+ <component name="TypeScriptGeneratedFilesManager">
|
||||
+ <option name="version" value="3" />
|
||||
+ </component>
|
||||
+</project>
|
||||
\ No newline at end of file
|
||||
diff --git a/index.d.ts b/index.d.ts
|
||||
index b1c930821f256a3223c34b6aa3ca3493a6b86a96..4d480a5691f8727f6e7ee155697488440ae0bb86 100644
|
||||
--- a/index.d.ts
|
||||
+++ b/index.d.ts
|
||||
@@ -4,13 +4,10 @@
|
||||
// Definitions: https://github.com/DefinitelyTyped/DefinitelyTyped
|
||||
// TypeScript Version: 2.3
|
||||
|
||||
-/// <reference types="node" />
|
||||
-
|
||||
interface JSZipSupport {
|
||||
arraybuffer: boolean;
|
||||
uint8array: boolean;
|
||||
blob: boolean;
|
||||
- nodebuffer: boolean;
|
||||
}
|
||||
|
||||
type Compression = 'STORE' | 'DEFLATE';
|
||||
@@ -32,7 +29,6 @@ interface InputByType {
|
||||
uint8array: Uint8Array;
|
||||
arraybuffer: ArrayBuffer;
|
||||
blob: Blob;
|
||||
- stream: NodeJS.ReadableStream;
|
||||
}
|
||||
|
||||
interface OutputByType {
|
||||
@@ -44,7 +40,6 @@ interface OutputByType {
|
||||
uint8array: Uint8Array;
|
||||
arraybuffer: ArrayBuffer;
|
||||
blob: Blob;
|
||||
- nodebuffer: Buffer;
|
||||
}
|
||||
|
||||
// This private `_data` property on a JSZipObject uses this interface.
|
||||
@@ -94,7 +89,6 @@ declare namespace JSZip {
|
||||
* @return Promise the promise of the result.
|
||||
*/
|
||||
async<T extends OutputType>(type: T, onUpdate?: OnUpdateCallback): Promise<OutputByType[T]>;
|
||||
- nodeStream(type?: 'nodebuffer', onUpdate?: OnUpdateCallback): NodeJS.ReadableStream;
|
||||
}
|
||||
|
||||
interface JSZipFileOptions {
|
||||
@@ -167,7 +161,7 @@ declare namespace JSZip {
|
||||
checkCRC32?: boolean;
|
||||
optimizedBinaryString?: boolean;
|
||||
createFolders?: boolean;
|
||||
- decodeFileName?: (bytes: string[] | Uint8Array | Buffer) => string;
|
||||
+ decodeFileName?: (bytes: string[] | Uint8Array) => string;
|
||||
}
|
||||
|
||||
type DataEventCallback<T> = (dataChunk: T, metadata: JSZipMetadata) => void
|
||||
@@ -284,15 +278,6 @@ interface JSZip {
|
||||
*/
|
||||
generateAsync<T extends JSZip.OutputType>(options?: JSZip.JSZipGeneratorOptions<T>, onUpdate?: JSZip.OnUpdateCallback): Promise<OutputByType[T]>;
|
||||
|
||||
- /**
|
||||
- * Generates a new archive asynchronously
|
||||
- *
|
||||
- * @param options Optional options for the generator
|
||||
- * @param onUpdate The optional function called on each internal update with the metadata.
|
||||
- * @return A Node.js `ReadableStream`
|
||||
- */
|
||||
- generateNodeStream(options?: JSZip.JSZipGeneratorOptions<'nodebuffer'>, onUpdate?: JSZip.OnUpdateCallback): NodeJS.ReadableStream;
|
||||
-
|
||||
/**
|
||||
* Generates the complete zip file with the internal stream implementation
|
||||
*
|
||||
8119
pnpm-lock.yaml
generated
8119
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
1
registry/dist/components/style/clear-home.js
vendored
1
registry/dist/components/style/clear-home.js
vendored
@ -1 +0,0 @@
|
||||
!function(e,a){"object"==typeof exports&&"object"==typeof module?module.exports=a():"function"==typeof define&&define.amd?define([],a):"object"==typeof exports?exports["style/clear-home"]=a():e["style/clear-home"]=a()}(globalThis,(()=>(()=>{"use strict";var e={d:(a,l)=>{for(var t in l)e.o(l,t)&&!e.o(a,t)&&Object.defineProperty(a,t,{enumerable:!0,get:l[t]})},o:(e,a)=>Object.prototype.hasOwnProperty.call(e,a)},a={};e.d(a,{component:()=>c});const l=coreApis.componentApis.define,t=coreApis.settings,s=[];let d=!1,o=!0;const i=(0,l.defineOptionsMetadata)({广告:{displayName:"广告",defaultValue:!0},番剧:{displayName:"番剧",defaultValue:!0},电影:{displayName:"电影",defaultValue:!0},国创:{displayName:"国创",defaultValue:!0},电视剧:{displayName:"电视剧",defaultValue:!0},综艺:{displayName:"综艺",defaultValue:!0},纪录片:{displayName:"纪录片",defaultValue:!0},动画:{displayName:"动画",defaultValue:!0},游戏:{displayName:"游戏",defaultValue:!0},鬼畜:{displayName:"鬼畜",defaultValue:!0},音乐:{displayName:"音乐",defaultValue:!0},舞蹈:{displayName:"舞蹈",defaultValue:!0},影视:{displayName:"影视",defaultValue:!0},娱乐:{displayName:"娱乐",defaultValue:!0},知识:{displayName:"知识",defaultValue:!0},科技:{displayName:"科技",defaultValue:!0},资讯:{displayName:"资讯",defaultValue:!0},美食:{displayName:"美食",defaultValue:!0},生活:{displayName:"生活",defaultValue:!0},汽车:{displayName:"汽车",defaultValue:!0},时尚:{displayName:"时尚",defaultValue:!0},运动:{displayName:"运动",defaultValue:!0},动物圈:{displayName:"动物圈",defaultValue:!0},VLOG:{displayName:"VLOG",defaultValue:!0},搞笑:{displayName:"搞笑",defaultValue:!0},单机游戏:{displayName:"单机游戏",defaultValue:!0},虚拟UP主:{displayName:"虚拟UP主",defaultValue:!0},公益:{displayName:"公益",defaultValue:!0},公开课:{displayName:"公开课",defaultValue:!0},专栏:{displayName:"专栏",defaultValue:!0},直播:{displayName:"直播",defaultValue:!0},赛事:{displayName:"赛事",defaultValue:!0},活动:{displayName:"活动",defaultValue:!0},课堂:{displayName:"课堂",defaultValue:!0},社区中心:{displayName:"社区中心",defaultValue:!0},新歌热榜:{displayName:"新歌热榜",defaultValue:!0},漫画:{displayName:"漫画",defaultValue:!0}});function u(e){console.log(e);for(const a of e)if(a.classList)if(a.classList.contains("floor-single-card")){const e=a.querySelector(".badge").textContent;e&&s.includes(e)&&(a.remove(),console.log("remove",e))}else a.classList.contains("bili-live-card")?d&&(a.remove(),console.log("remove live")):(a.classList.contains("bili-video-card")||a.classList.contains("feed-card"))&&o&&(a.querySelector(".bili-video-card__info--ad")||a.querySelector(".bili-video-card__info--creative-ad"))&&(a.remove(),console.log("remove ads"))}function n(e){const a=[];for(const l of e){const e=l.target;if(!(e.classList&&e.classList.contains("carousel-transform")||"SPAN"===e.tagName)&&l.addedNodes.length>0)for(const e of l.addedNodes){const l=e;"#text"===l.nodeName||l.classList&&l.classList.contains("bili-watch-later")||a.push(l)}}a.length>0&&u(a)}const c=(0,l.defineComponentMetadata)({name:"clear-home",author:{name:"RieN7",link:"https://github.com/rien7"},tags:[componentsTags.style],displayName:"首页净化",description:"删除首页特定类型的卡片",entry:async e=>{let{metadata:a,settings:l}=e;const i=document.querySelector("main > .feed2 > .recommended-container_floor-aside > .container");if(!i)return;new MutationObserver(n).observe(i,{childList:!0}),Object.keys(l.options).forEach((e=>{(0,t.addComponentListener)(`${a.name}.${e}`,(a=>{if(a){if("广告"===e)return void(o=!0);s.push(e),"直播"===e&&(d=!0)}}),!0)}));u(i.children)},options:i,commitHash:"1167f721dc805a6bfd3d4bcba753607eff4bacd5",coreVersion:"2.7.0"});return a=a.component})()));
|
||||
@ -1,5 +1,5 @@
|
||||
import { defineComponentMetadata } from '@/components/define'
|
||||
import { FeedsCard } from '@/components/feeds/api'
|
||||
import type { FeedsCard } from '@/components/feeds/api'
|
||||
import { feedsUrls } from '@/core/utils/urls'
|
||||
|
||||
const entry = async () => {
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
import { defineComponentMetadata } from '@/components/define'
|
||||
import { FeedsCard, feedsCardTypes } from '@/components/feeds/api'
|
||||
import type { FeedsCard } from '@/components/feeds/api'
|
||||
import { feedsCardTypes } from '@/components/feeds/api'
|
||||
import { feedsUrls } from '@/core/utils/urls'
|
||||
|
||||
let enabled = true
|
||||
|
||||
@ -49,6 +49,7 @@
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts">
|
||||
import { defineComponent } from 'vue'
|
||||
import { VIcon, TextBox, DpiImage, VEmpty, VLoading } from '@/ui'
|
||||
import { getJsonWithCredentials, responsiveGetPages } from '@/core/ajax'
|
||||
|
||||
@ -64,7 +65,7 @@ interface LiveInfo {
|
||||
link: string
|
||||
}
|
||||
|
||||
export default Vue.extend({
|
||||
export default defineComponent({
|
||||
components: {
|
||||
VIcon,
|
||||
TextBox,
|
||||
|
||||
@ -35,9 +35,8 @@ const entry = async () => {
|
||||
if (!container) {
|
||||
console.error('container not found')
|
||||
}
|
||||
const LiveList = await import('./LiveList.vue').then(m => m.default)
|
||||
const liveList = mountVueComponent(LiveList)
|
||||
container.appendChild(liveList.$el)
|
||||
const [el] = mountVueComponent(await import('./LiveList.vue'))
|
||||
container.appendChild(el)
|
||||
}
|
||||
|
||||
export const component = defineComponentMetadata({
|
||||
|
||||
@ -12,22 +12,17 @@
|
||||
<div class="filter-patterns">
|
||||
<div v-for="p of patterns" :key="p" class="pattern">
|
||||
{{ p }}
|
||||
<VIcon
|
||||
title="删除"
|
||||
icon="mdi-trash-can-outline"
|
||||
:size="16"
|
||||
@click.native="deletePattern(p)"
|
||||
/>
|
||||
<VIcon title="删除" icon="mdi-trash-can-outline" :size="16" @click="deletePattern(p)" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="add-pattern">
|
||||
<TextBox
|
||||
v-model="newPattern"
|
||||
v-model:text="newPattern"
|
||||
placeholder="支持正则表达式 /^xxx$/"
|
||||
type="text"
|
||||
@keydown.enter="addPattern(newPattern)"
|
||||
/>
|
||||
<VButton type="transparent" @click.native="addPattern(newPattern)">
|
||||
<VButton type="transparent" @click="addPattern(newPattern)">
|
||||
<VIcon title="添加" icon="mdi-plus" :size="18" />
|
||||
</VButton>
|
||||
</div>
|
||||
@ -37,10 +32,12 @@
|
||||
v-for="[id, type] of Object.entries(allSideCards)"
|
||||
:key="id"
|
||||
class="filter-side-card-switch feeds-filter-switch"
|
||||
@click="toggleBlockSide(id)"
|
||||
@click="toggleBlockSide(Number(id))"
|
||||
>
|
||||
<label :class="{ disabled: sideDisabled(id) }">
|
||||
<span class="name" :class="{ disabled: sideDisabled(id) }">{{ type.displayName }}</span>
|
||||
<label :class="{ disabled: sideDisabled(Number(id)) }">
|
||||
<span class="name" :class="{ disabled: sideDisabled(Number(id)) }">{{
|
||||
type.displayName
|
||||
}}</span>
|
||||
<VIcon :size="16" class="disabled" icon="mdi-cancel"></VIcon>
|
||||
<VIcon :size="16" icon="mdi-check"></VIcon>
|
||||
</label>
|
||||
@ -50,18 +47,21 @@
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import {
|
||||
import { defineAsyncComponent, defineComponent } from 'vue'
|
||||
import type {
|
||||
feedsCardsManager,
|
||||
FeedsCard,
|
||||
FeedsCardType,
|
||||
feedsCardTypes,
|
||||
forEachFeedsCard,
|
||||
RepostFeedsCard,
|
||||
} from '@/components/feeds/api'
|
||||
|
||||
import { feedsCardTypes, forEachFeedsCard } from '@/components/feeds/api'
|
||||
import { attributes, attributesSubtree } from '@/core/observer'
|
||||
import { getComponentSettings } from '@/core/settings'
|
||||
import { select } from '@/core/spin-query'
|
||||
import { attributes, attributesSubtree } from '@/core/observer'
|
||||
import { VIcon, TextBox, VButton } from '@/ui'
|
||||
import { FeedsFilterOptions } from './options'
|
||||
import { TextBox, VButton, VIcon } from '@/ui'
|
||||
|
||||
import type { FeedsFilterOptions } from './options'
|
||||
import { hasBlockedPattern } from './pattern'
|
||||
|
||||
const { options } = getComponentSettings<FeedsFilterOptions>('feedsFilter')
|
||||
@ -99,12 +99,12 @@ const sideCards: { [id: number]: SideCardType } = {
|
||||
displayName: '发布动态',
|
||||
},
|
||||
}
|
||||
let cardsManager: typeof import('@/components/feeds/api').feedsCardsManager
|
||||
let cardsManager: typeof feedsCardsManager
|
||||
const sideBlock = 'feeds-filter-side-block-'
|
||||
|
||||
export default Vue.extend({
|
||||
export default defineComponent({
|
||||
components: {
|
||||
FilterTypeSwitch: () => import('./FilterTypeSwitch.vue'),
|
||||
FilterTypeSwitch: defineAsyncComponent(() => import('./FilterTypeSwitch.vue')),
|
||||
VIcon,
|
||||
TextBox,
|
||||
VButton,
|
||||
@ -120,12 +120,15 @@ export default Vue.extend({
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
patterns() {
|
||||
patterns: {
|
||||
handler() {
|
||||
options.patterns = this.patterns
|
||||
if (cardsManager) {
|
||||
cardsManager.cards.forEach(card => this.updateCard(lodash.clone(card)))
|
||||
}
|
||||
},
|
||||
deep: true,
|
||||
},
|
||||
},
|
||||
async mounted() {
|
||||
this.updateBlockSide()
|
||||
@ -197,7 +200,7 @@ export default Vue.extend({
|
||||
updateBlockSide() {
|
||||
Object.entries(sideCards).forEach(([id, type]) => {
|
||||
const name = sideBlock + type.className
|
||||
document.body.classList[this.blockSideCards.includes(id) ? 'add' : 'remove'](name)
|
||||
document.body.classList[this.blockSideCards.includes(Number(id)) ? 'add' : 'remove'](name)
|
||||
})
|
||||
},
|
||||
toggleBlockSide(id: number) {
|
||||
|
||||
@ -10,12 +10,15 @@
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent } from 'vue'
|
||||
import type { PropType } from 'vue'
|
||||
import type { FeedsCardType } from '@/components/feeds/api'
|
||||
import { getComponentSettings } from '@/core/settings'
|
||||
import { VIcon } from '@/ui'
|
||||
import { FeedsFilterOptions } from './options'
|
||||
import type { FeedsFilterOptions } from './options'
|
||||
|
||||
const { options } = getComponentSettings<FeedsFilterOptions>('feedsFilter')
|
||||
export default Vue.extend({
|
||||
export default defineComponent({
|
||||
components: {
|
||||
VIcon,
|
||||
},
|
||||
@ -25,12 +28,12 @@ export default Vue.extend({
|
||||
required: true,
|
||||
},
|
||||
type: {
|
||||
type: Object,
|
||||
type: Object as PropType<FeedsCardType>,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
data() {
|
||||
const optionKey = this.type.id >= 0 ? 'types' : 'specialTypes'
|
||||
const optionKey = this.type.id >= 0 ? 'types' : ('specialTypes' as 'types' | 'specialTypes')
|
||||
const disabled = options[optionKey].includes(this.type.id)
|
||||
return {
|
||||
disabled,
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
import { defineComponentMetadata } from '@/components/define'
|
||||
import { feedsCardsManager } from '@/components/feeds/api'
|
||||
|
||||
import { feedsFilterPlugin } from './plugin'
|
||||
import { options } from './options'
|
||||
|
||||
@ -18,9 +19,11 @@ const entry = async () => {
|
||||
if (leftPanel === null) {
|
||||
return
|
||||
}
|
||||
const FeedsFilterCard = await import('./FeedsFilterCard.vue')
|
||||
const { mountVueComponent } = await import('@/core/utils')
|
||||
leftPanel.insertAdjacentElement('afterbegin', mountVueComponent(FeedsFilterCard).$el)
|
||||
leftPanel.insertAdjacentElement(
|
||||
'afterbegin',
|
||||
mountVueComponent(await import('./FeedsFilterCard.vue'))[0],
|
||||
)
|
||||
}
|
||||
|
||||
export const component = defineComponentMetadata({
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import { OptionsOfMetadata, defineOptionsMetadata } from '@/components/define'
|
||||
import { type OptionsOfMetadata, defineOptionsMetadata } from '@/components/define'
|
||||
|
||||
export const options = defineOptionsMetadata({
|
||||
types: {
|
||||
|
||||
@ -1,7 +1,9 @@
|
||||
import { FeedsContentFilter } from '@/components/feeds/api'
|
||||
import type { FeedsContentFilter } from '@/components/feeds/api'
|
||||
import { getComponentSettings } from '@/core/settings'
|
||||
import { PluginMetadata } from '@/plugins/plugin'
|
||||
import { BlockableCard, hasBlockedPattern } from './pattern'
|
||||
import type { PluginMetadata } from '@/plugins/plugin'
|
||||
|
||||
import type { BlockableCard } from './pattern'
|
||||
import { hasBlockedPattern } from './pattern'
|
||||
|
||||
const bangumiFields = {
|
||||
username: 'title',
|
||||
|
||||
@ -1,9 +1,9 @@
|
||||
import { defineComponentMetadata } from '@/components/define'
|
||||
import { styledComponentEntry } from '@/components/styled-component'
|
||||
import { feedsUrlsWithoutDetail } from '@/core/utils/urls'
|
||||
import { feedsCardsManager } from '@/components/feeds/api'
|
||||
import { select } from '@/core/spin-query'
|
||||
import { styledComponentEntry } from '@/components/styled-component'
|
||||
import { childListSubtree } from '@/core/observer'
|
||||
import { select } from '@/core/spin-query'
|
||||
import { feedsUrlsWithoutDetail } from '@/core/utils/urls'
|
||||
|
||||
const entry = async () => {
|
||||
const { forEachFeedsCard } = await import('@/components/feeds/api')
|
||||
|
||||
@ -2,9 +2,9 @@
|
||||
<div class="multiple-widgets">
|
||||
<VPopup
|
||||
ref="medalPopup"
|
||||
v-model="medalOpen"
|
||||
v-model:open="medalOpen"
|
||||
class="badge-popup widgets-popup medal"
|
||||
:trigger-element="$refs.medalButton"
|
||||
:trigger-element="medalButton"
|
||||
>
|
||||
<ul>
|
||||
<li
|
||||
@ -28,9 +28,9 @@
|
||||
|
||||
<VPopup
|
||||
ref="titlePopup"
|
||||
v-model="titleOpen"
|
||||
v-model:open="titleOpen"
|
||||
class="badge-popup widgets-popup title"
|
||||
:trigger-element="$refs.titleButton"
|
||||
:trigger-element="titleButton"
|
||||
>
|
||||
<ul>
|
||||
<li
|
||||
@ -51,21 +51,32 @@
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent, ref } from 'vue'
|
||||
import type { Ref } from 'vue'
|
||||
import { addComponentListener, getComponentSettings } from '@/core/settings'
|
||||
import { descendingSort } from '@/core/utils/sort'
|
||||
import { DefaultWidget, VPopup } from '@/ui'
|
||||
import { Medal, Title, Badge, getMedalList, getTitleList } from './badge'
|
||||
|
||||
const { options } = getComponentSettings('badgeHelper')
|
||||
export default Vue.extend({
|
||||
import type { Badge } from './badge'
|
||||
import { getMedalList, getTitleList, Medal, Title } from './badge'
|
||||
import type { Options } from './index'
|
||||
|
||||
const { options } = getComponentSettings<Options>('badgeHelper')
|
||||
export default defineComponent({
|
||||
components: {
|
||||
DefaultWidget,
|
||||
VPopup,
|
||||
},
|
||||
setup: () => ({
|
||||
medalPopup: ref(null) as Ref<InstanceType<typeof VPopup> | null>,
|
||||
titlePopup: ref(null) as Ref<InstanceType<typeof VPopup> | null>,
|
||||
medalButton: ref(null) as Ref<InstanceType<typeof DefaultWidget> | null>,
|
||||
titleButton: ref(null) as Ref<InstanceType<typeof DefaultWidget> | null>,
|
||||
}),
|
||||
data() {
|
||||
return {
|
||||
medalList: [],
|
||||
titleList: [],
|
||||
medalList: [] as Medal[],
|
||||
titleList: [] as Title[],
|
||||
medalOpen: false,
|
||||
titleOpen: false,
|
||||
grayEffect: true,
|
||||
|
||||
@ -1,23 +1,12 @@
|
||||
import { defineComponentMetadata } from '@/components/define'
|
||||
import { defineAsyncComponent } from 'vue'
|
||||
|
||||
import type { OptionsOfMetadata } from '@/components/define'
|
||||
import { defineComponentMetadata, defineOptionsMetadata } from '@/components/define'
|
||||
import { getNumberValidator, getUID, none } from '@/core/utils'
|
||||
|
||||
import { autoMatchMedal } from './auto-match'
|
||||
|
||||
export const component = defineComponentMetadata({
|
||||
name: 'badgeHelper',
|
||||
displayName: '直播勋章快速更换',
|
||||
description: {
|
||||
'zh-CN':
|
||||
'在直播区中, 可从功能面板中直接切换勋章和头衔. 默认显示 256 个 (同时也是上限), 可在选项中修改.',
|
||||
},
|
||||
entry: () => autoMatchMedal(),
|
||||
reload: none,
|
||||
unload: none,
|
||||
tags: [componentsTags.live],
|
||||
widget: {
|
||||
component: () => import('./BadgeHelper.vue').then(m => m.default),
|
||||
condition: () => Boolean(getUID()),
|
||||
},
|
||||
options: {
|
||||
const options = defineOptionsMetadata({
|
||||
autoMatchMedal: {
|
||||
defaultValue: true,
|
||||
displayName: '自动佩戴当前直播间勋章',
|
||||
@ -36,6 +25,25 @@ export const component = defineComponentMetadata({
|
||||
displayName: '显示勋章的未点亮状态',
|
||||
defaultValue: true,
|
||||
},
|
||||
})
|
||||
|
||||
export type Options = OptionsOfMetadata<typeof options>
|
||||
|
||||
export const component = defineComponentMetadata({
|
||||
name: 'badgeHelper',
|
||||
displayName: '直播勋章快速更换',
|
||||
description: {
|
||||
'zh-CN':
|
||||
'在直播区中, 可从功能面板中直接切换勋章和头衔. 默认显示 256 个 (同时也是上限), 可在选项中修改.',
|
||||
},
|
||||
entry: () => autoMatchMedal(),
|
||||
reload: none,
|
||||
unload: none,
|
||||
tags: [componentsTags.live],
|
||||
widget: {
|
||||
component: defineAsyncComponent(() => import('./BadgeHelper.vue')),
|
||||
condition: () => Boolean(getUID()),
|
||||
},
|
||||
options,
|
||||
urlInclude: ['//live.bilibili.com'],
|
||||
})
|
||||
|
||||
@ -6,17 +6,19 @@
|
||||
:value="value"
|
||||
maxlength="30"
|
||||
@keydown.enter="send()"
|
||||
@input="updateValue($event.target.value)"
|
||||
@input="updateValue(($event.target as HTMLInputElement).value)"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts">
|
||||
import { defineComponent } from 'vue'
|
||||
import { select } from '@/core/spin-query'
|
||||
import { raiseEvent } from '@/core/utils'
|
||||
|
||||
import { originalTextAreaSelector, sendButtonSelector } from './original-elements'
|
||||
|
||||
let changeEventHook = false
|
||||
export default Vue.extend({
|
||||
export default defineComponent({
|
||||
data() {
|
||||
return {
|
||||
originalTextArea: null,
|
||||
@ -52,7 +54,7 @@ export default Vue.extend({
|
||||
changeEventHook = true
|
||||
}
|
||||
},
|
||||
beforeDestroy() {
|
||||
beforeUnmount() {
|
||||
this.originalTextArea.removeEventListener('input', this.listenChange)
|
||||
this.originalTextArea.removeEventListener('change', this.listenChange)
|
||||
},
|
||||
|
||||
@ -1,14 +1,15 @@
|
||||
import { waitForControlBar } from '@/components/live/live-control-bar'
|
||||
import { defineComponentMetadata } from '@/components/define'
|
||||
import { waitForControlBar } from '@/components/live/live-control-bar'
|
||||
import { getUID } from '@/core/utils'
|
||||
import { liveUrls } from '@/core/utils/urls'
|
||||
|
||||
import { leftControllerSelector } from './original-elements'
|
||||
|
||||
const entry = async () => {
|
||||
if (!getUID()) {
|
||||
return
|
||||
}
|
||||
let danmakuSendBarElement: Element
|
||||
let danmakuSendBarElement: Element | undefined
|
||||
waitForControlBar({
|
||||
callback: async controlBar => {
|
||||
const leftController = dq(controlBar, leftControllerSelector) as HTMLDivElement
|
||||
@ -20,8 +21,7 @@ const entry = async () => {
|
||||
}
|
||||
if (!danmakuSendBarElement) {
|
||||
const { mountVueComponent } = await import('@/core/utils')
|
||||
const DanmakuSendBar = await import('./DanmakuSendbar.vue')
|
||||
danmakuSendBarElement = mountVueComponent(DanmakuSendBar).$el
|
||||
danmakuSendBarElement = mountVueComponent(await import('./DanmakuSendbar.vue'))[0]
|
||||
}
|
||||
leftController.insertAdjacentElement('afterend', danmakuSendBarElement)
|
||||
},
|
||||
|
||||
@ -1,8 +1,9 @@
|
||||
import { waitForControlBar } from '@/components/live/live-control-bar'
|
||||
import { defineComponentMetadata } from '@/components/define'
|
||||
import { waitForControlBar } from '@/components/live/live-control-bar'
|
||||
import { select as spinSelect } from '@/core/spin-query'
|
||||
import { addStyle, removeStyle } from '@/core/style'
|
||||
import { liveUrls } from '@/core/utils/urls'
|
||||
|
||||
import componentStyle from './gift-box.scss'
|
||||
|
||||
/**
|
||||
|
||||
@ -4,9 +4,10 @@
|
||||
</a>
|
||||
</template>
|
||||
<script lang="ts">
|
||||
import { defineComponent } from 'vue'
|
||||
import { DefaultWidget } from '@/ui'
|
||||
|
||||
export default Vue.extend({
|
||||
export default defineComponent({
|
||||
components: {
|
||||
DefaultWidget,
|
||||
},
|
||||
|
||||
@ -1,3 +1,5 @@
|
||||
import { defineAsyncComponent } from 'vue'
|
||||
|
||||
import { defineComponentMetadata } from '@/components/define'
|
||||
import { matchUrlPattern } from '@/core/utils'
|
||||
|
||||
@ -13,7 +15,7 @@ export const component = defineComponentMetadata({
|
||||
/^https:\/\/live\.bilibili\.com\/[\d]+/,
|
||||
],
|
||||
widget: {
|
||||
component: () => import('./Widget.vue').then(m => m.default),
|
||||
component: defineAsyncComponent(() => import('./Widget.vue')),
|
||||
condition: () => matchUrlPattern(/^https:\/\/live\.bilibili\.com\/([\d]+)/),
|
||||
},
|
||||
})
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import { toggleStyle } from '@/components/styled-component'
|
||||
import { defineComponentMetadata } from '@/components/define'
|
||||
import { toggleStyle } from '@/components/styled-component'
|
||||
|
||||
export const component = defineComponentMetadata({
|
||||
...toggleStyle('alwaysShowDuration', () => import('./always-show-duration.scss')),
|
||||
|
||||
@ -1,9 +1,6 @@
|
||||
import {
|
||||
OptionsOfMetadata,
|
||||
defineComponentMetadata,
|
||||
defineOptionsMetadata,
|
||||
} from '@/components/define'
|
||||
import { ComponentEntry } from '@/components/types'
|
||||
import type { OptionsOfMetadata } from '@/components/define'
|
||||
import { defineComponentMetadata, defineOptionsMetadata } from '@/components/define'
|
||||
import type { ComponentEntry } from '@/components/types'
|
||||
import { addComponentListener } from '@/core/settings'
|
||||
|
||||
type Options = OptionsOfMetadata<typeof options>
|
||||
|
||||
@ -9,13 +9,15 @@
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent, reactive } from 'vue'
|
||||
import { addComponentListener } from '@/core/settings'
|
||||
import { getUID } from '@/core/utils'
|
||||
import { ascendingSort } from '@/core/utils/sort'
|
||||
import { registerAndGetData } from '@/plugins/data'
|
||||
|
||||
import { getBuiltInItems } from './built-in-items'
|
||||
import type { CustomNavbarItemInit } from './custom-navbar-item'
|
||||
import {
|
||||
CustomNavbarItemInit,
|
||||
CustomNavbarItem,
|
||||
CustomNavbarItems,
|
||||
CustomNavbarRenderedItems,
|
||||
@ -23,10 +25,13 @@ import {
|
||||
import CustomNavbarItemComponent from './CustomNavbarItem.vue'
|
||||
import { checkTransparentFill } from './transparent-fill'
|
||||
|
||||
const [initItems] = registerAndGetData(CustomNavbarItems, getBuiltInItems())
|
||||
const [renderedItems] = registerAndGetData(CustomNavbarRenderedItems, {
|
||||
const [initItems] = registerAndGetData(CustomNavbarItems, reactive(getBuiltInItems()))
|
||||
const [renderedItems] = registerAndGetData(
|
||||
CustomNavbarRenderedItems,
|
||||
reactive({
|
||||
items: [] as CustomNavbarItem[],
|
||||
})
|
||||
}),
|
||||
)
|
||||
const getItems = () => {
|
||||
const isLogin = Boolean(getUID())
|
||||
const items = (initItems as CustomNavbarItemInit[])
|
||||
@ -41,7 +46,7 @@ const getItems = () => {
|
||||
renderedItems.items = items
|
||||
return items
|
||||
}
|
||||
export default Vue.extend({
|
||||
export default defineComponent({
|
||||
components: {
|
||||
NavbarItem: CustomNavbarItemComponent,
|
||||
},
|
||||
@ -49,14 +54,17 @@ export default Vue.extend({
|
||||
return {
|
||||
initItems,
|
||||
items: getItems(),
|
||||
styles: [],
|
||||
styles: [] as string[],
|
||||
height: CustomNavbarItem.navbarOptions.height,
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
initItems() {
|
||||
initItems: {
|
||||
handler() {
|
||||
this.items = getItems()
|
||||
},
|
||||
deep: true,
|
||||
},
|
||||
},
|
||||
async mounted() {
|
||||
addComponentListener(
|
||||
|
||||
@ -46,7 +46,7 @@
|
||||
:is="item.popupContent"
|
||||
v-if="item.requestedPopup"
|
||||
ref="popup"
|
||||
:container="$refs.popupContainer"
|
||||
:container="popupContainer"
|
||||
:item="item"
|
||||
></component>
|
||||
</div>
|
||||
@ -56,9 +56,13 @@
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import type { Ref } from 'vue'
|
||||
import { defineComponent, ref } from 'vue'
|
||||
import { addComponentListener, removeComponentListener } from '@/core/settings'
|
||||
import CustomNavbarLink from './CustomNavbarLink.vue'
|
||||
|
||||
import type { PopupContentInstance } from './custom-navbar-item'
|
||||
import { CustomNavbarItem } from './custom-navbar-item'
|
||||
import CustomNavbarLink from './CustomNavbarLink.vue'
|
||||
|
||||
const isOpenInNewTab = (item: CustomNavbarItem) => {
|
||||
const { name } = item
|
||||
@ -68,7 +72,23 @@ const isOpenInNewTab = (item: CustomNavbarItem) => {
|
||||
}
|
||||
return options.openInNewTab
|
||||
}
|
||||
export default Vue.extend({
|
||||
function trigger(this: InstanceType<typeof ThisComponent>, initialPopup: boolean) {
|
||||
const { popup } = this
|
||||
if (!popup) {
|
||||
return
|
||||
}
|
||||
const allowRefresh =
|
||||
CustomNavbarItem.navbarOptions.refreshOnPopup &&
|
||||
popup.popupRefresh &&
|
||||
typeof popup.popupRefresh === 'function'
|
||||
if (!initialPopup && allowRefresh) {
|
||||
popup.popupRefresh()
|
||||
}
|
||||
if (popup.popupShow && typeof popup.popupShow === 'function') {
|
||||
popup.popupShow()
|
||||
}
|
||||
}
|
||||
const ThisComponent = defineComponent({
|
||||
components: {
|
||||
CustomNavbarLink,
|
||||
},
|
||||
@ -78,6 +98,10 @@ export default Vue.extend({
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
setup: () => ({
|
||||
popup: ref(null) as Ref<PopupContentInstance | null>,
|
||||
popupContainer: ref(null) as Ref<HTMLDivElement | null>,
|
||||
}),
|
||||
data() {
|
||||
return {
|
||||
newTab: isOpenInNewTab(this.item),
|
||||
@ -98,7 +122,7 @@ export default Vue.extend({
|
||||
removeComponentListener('customNavbar.openInNewTab', listener)
|
||||
}
|
||||
},
|
||||
beforeDestroy() {
|
||||
beforeUnmount() {
|
||||
this.cancelListeners?.()
|
||||
},
|
||||
methods: {
|
||||
@ -119,22 +143,10 @@ export default Vue.extend({
|
||||
'iframe-container': item.iframeName,
|
||||
}
|
||||
},
|
||||
triggerPopupShow: lodash.debounce(function trigger(initialPopup: boolean) {
|
||||
const { popup } = this.$refs
|
||||
if (!popup) {
|
||||
return
|
||||
}
|
||||
const allowRefresh =
|
||||
CustomNavbarItem.navbarOptions.refreshOnPopup &&
|
||||
popup.popupRefresh &&
|
||||
typeof popup.popupRefresh === 'function'
|
||||
if (!initialPopup && allowRefresh) {
|
||||
popup.popupRefresh()
|
||||
}
|
||||
if (popup.popupShow && typeof popup.popupShow === 'function') {
|
||||
popup.popupShow()
|
||||
}
|
||||
}, 300),
|
||||
triggerPopupShow: lodash.debounce(trigger, 300) as unknown as (
|
||||
this: any,
|
||||
initialPopup: boolean,
|
||||
) => void,
|
||||
async requestPopup() {
|
||||
const { item } = this as {
|
||||
item: CustomNavbarItem
|
||||
@ -165,6 +177,7 @@ export default Vue.extend({
|
||||
// },
|
||||
},
|
||||
})
|
||||
export default ThisComponent
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
|
||||
@ -1,10 +1,12 @@
|
||||
<template>
|
||||
<a v-bind="$attrs" :target="newTab ? '_blank' : null" v-on="$listeners">
|
||||
<a :target="newTab ? '_blank' : null">
|
||||
<slot />
|
||||
</a>
|
||||
</template>
|
||||
<script lang="ts">
|
||||
export default Vue.extend({
|
||||
import { defineComponent } from 'vue'
|
||||
|
||||
export default defineComponent({
|
||||
props: {
|
||||
newTab: {
|
||||
type: Boolean,
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import { CustomNavbarItemInit } from './custom-navbar-item'
|
||||
import type { CustomNavbarItemInit } from './custom-navbar-item'
|
||||
import { messages } from './messages/messages'
|
||||
import { ranking } from './ranking/ranking'
|
||||
import { userInfo } from './user-info/user-info'
|
||||
|
||||
@ -1,8 +1,20 @@
|
||||
import { createPopper, Instance as Popper } from '@popperjs/core'
|
||||
import { VueModule, Executable } from '@/core/common-types'
|
||||
import { getComponentSettings, addComponentListener } from '@/core/settings'
|
||||
import type { Instance as Popper } from '@popperjs/core'
|
||||
import { createPopper } from '@popperjs/core'
|
||||
|
||||
import type { Component, ComponentPublicInstance } from 'vue'
|
||||
import { addComponentListener, getComponentSettings } from '@/core/settings'
|
||||
|
||||
import type { CustomNavbarOptions } from '.'
|
||||
|
||||
export interface PopupContentInstance extends ComponentPublicInstance {
|
||||
popupRefresh?(): void
|
||||
popupShow(): void
|
||||
}
|
||||
|
||||
export interface PopupContent {
|
||||
new (): PopupContentInstance
|
||||
}
|
||||
|
||||
export const CustomNavbarItems = 'customNavbar.items'
|
||||
export const CustomNavbarRenderedItems = 'customNavbar.renderedItems'
|
||||
/**
|
||||
@ -13,8 +25,8 @@ export interface CustomNavbarItemInit {
|
||||
name: string
|
||||
/** 显示名称 */
|
||||
displayName: string
|
||||
/** 内容 */
|
||||
content: Executable<VueModule> | string
|
||||
/** 内容。被创建时传入属性:item: CustomNavbarItem */
|
||||
content: Component | string
|
||||
|
||||
/** 设定CSS flex样式 (grow, shrink, basis) */
|
||||
flexStyle?: string
|
||||
@ -27,7 +39,7 @@ export interface CustomNavbarItemInit {
|
||||
/** `content`指定的内容mount之后要执行的代码 */
|
||||
contentMounted?: (item: CustomNavbarItem) => Promise<void> | void
|
||||
/** 点击运行的代码段 */
|
||||
clickAction?: Executable
|
||||
clickAction?: (event: MouseEvent) => void
|
||||
/** 获取或设置提示数字, 将显示在顶部 */
|
||||
notifyCount?: number
|
||||
/** 是否在触屏状态下不响应点击 */
|
||||
@ -35,8 +47,8 @@ export interface CustomNavbarItemInit {
|
||||
/** 是否仅在登录后显示 */
|
||||
loginRequired?: boolean
|
||||
|
||||
/** 弹窗内容 */
|
||||
popupContent?: Executable<VueModule>
|
||||
/** 弹窗内容。创建其实例时传入参数有:container: HTMLElement, item: CustomNavbarItem */
|
||||
popupContent?: PopupContent | undefined
|
||||
/** 设为大于0的值时, 表示预计的弹窗宽度, 将会用于边缘检测, 防止超出viewport */
|
||||
boundingWidth?: number
|
||||
/** 不使用默认的弹窗padding */
|
||||
@ -50,19 +62,19 @@ export interface CustomNavbarItemInit {
|
||||
export class CustomNavbarItem implements Required<CustomNavbarItemInit> {
|
||||
name: string
|
||||
displayName: string
|
||||
content: Executable<VueModule> | string
|
||||
content: Component | string
|
||||
|
||||
flexStyle = '0 0 auto'
|
||||
disabled = false
|
||||
href: string = null
|
||||
active = false
|
||||
clickAction: Executable = none
|
||||
clickAction: (event: MouseEvent) => void = none
|
||||
contentMounted: (item: CustomNavbarItem) => Promise<void> | void = none
|
||||
notifyCount = 0
|
||||
touch = false
|
||||
loginRequired = false
|
||||
|
||||
popupContent: Executable<VueModule> = null
|
||||
popupContent: PopupContent | undefined
|
||||
popper: Popper = null
|
||||
boundingWidth = 0
|
||||
noPopupPadding = false
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import { ComponentEntry } from '@/components/types'
|
||||
import type { ComponentEntry } from '@/components/types'
|
||||
import { addComponentListener } from '@/core/settings'
|
||||
import { isIframe, isNotHtml, matchUrlPattern, mountVueComponent } from '@/core/utils'
|
||||
import { setupNotifyStyle } from './notify-style'
|
||||
@ -42,14 +42,10 @@ export const entry: ComponentEntry = async ({ metadata: { name } }) => {
|
||||
true,
|
||||
)
|
||||
}
|
||||
const CustomNavbar = await import('./CustomNavbar.vue')
|
||||
const customNavbar: Vue & {
|
||||
styles: string[]
|
||||
toggleStyle: (value: boolean, style: string) => void
|
||||
} = mountVueComponent(CustomNavbar)
|
||||
document.body.insertAdjacentElement('beforeend', customNavbar.$el)
|
||||
const [el, vm] = mountVueComponent(await import('./CustomNavbar.vue'))
|
||||
document.body.insertAdjacentElement('beforeend', el)
|
||||
;['fill', 'shadow', 'blur'].forEach(style => {
|
||||
addComponentListener(`${name}.${style}`, value => customNavbar.toggleStyle(value, style), true)
|
||||
addComponentListener(`${name}.${style}`, value => vm.toggleStyle(value, style), true)
|
||||
})
|
||||
setupNotifyStyle()
|
||||
}
|
||||
|
||||
@ -3,38 +3,39 @@
|
||||
class="favorites-folder-select"
|
||||
round
|
||||
:items="folders"
|
||||
:key-mapper="f => f.id"
|
||||
:key-mapper="f => (f as FavoritesFolder).id"
|
||||
:value="folder"
|
||||
@change="change($event)"
|
||||
@update:value="change($event)"
|
||||
>
|
||||
<template #item="{ item }"> {{ item.name }} ({{ item.count }}) </template>
|
||||
</VDropdown>
|
||||
</template>
|
||||
<script lang="ts">
|
||||
import { VDropdown } from '@/ui'
|
||||
import { getUID } from '@/core/utils'
|
||||
import { defineComponent } from 'vue'
|
||||
import type { PropType } from 'vue'
|
||||
import { getJsonWithCredentials } from '@/core/ajax'
|
||||
import { getComponentSettings } from '@/core/settings'
|
||||
import { FavoritesFolder, notSelectedFolder } from './favorites-folder'
|
||||
import { getUID } from '@/core/utils'
|
||||
import { VDropdown } from '@/ui'
|
||||
|
||||
import type { FavoritesFolder } from './favorites-folder'
|
||||
import { notSelectedFolder } from './favorites-folder'
|
||||
|
||||
const navbarOptions = getComponentSettings('customNavbar').options
|
||||
export default Vue.extend({
|
||||
export default defineComponent({
|
||||
components: {
|
||||
VDropdown,
|
||||
},
|
||||
model: {
|
||||
prop: 'folder',
|
||||
event: 'change',
|
||||
},
|
||||
props: {
|
||||
folder: {
|
||||
type: Object,
|
||||
type: Object as PropType<FavoritesFolder>,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
emits: ['update:folder'],
|
||||
data() {
|
||||
return {
|
||||
folders: [],
|
||||
folders: [] as FavoritesFolder[],
|
||||
}
|
||||
},
|
||||
async created() {
|
||||
@ -59,16 +60,16 @@ export default Vue.extend({
|
||||
const { lastFavoriteFolder } = navbarOptions
|
||||
const folder = this.folders.find((f: FavoritesFolder) => f.id === lastFavoriteFolder)
|
||||
if (folder) {
|
||||
this.$emit('change', folder)
|
||||
this.$emit('update:folder', folder)
|
||||
} else {
|
||||
this.$emit('change', this.folders[0])
|
||||
this.$emit('update:folder', this.folders[0])
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
change(folder: FavoritesFolder) {
|
||||
navbarOptions.lastFavoriteFolder = folder.id
|
||||
this.$emit('change', folder)
|
||||
this.$emit('update:folder', folder)
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
@ -1,9 +1,9 @@
|
||||
<template>
|
||||
<div class="favorites-list">
|
||||
<div ref="el" class="favorites-list">
|
||||
<div class="header">
|
||||
<FavoritesFolderSelect v-model="folder"></FavoritesFolderSelect>
|
||||
<FavoritesFolderSelect v-model:folder="folder"></FavoritesFolderSelect>
|
||||
<div class="search">
|
||||
<TextBox v-model="search" linear placeholder="搜索"></TextBox>
|
||||
<TextBox v-model:text="search" linear placeholder="搜索"></TextBox>
|
||||
</div>
|
||||
<a class="operation" :href="playLink" title="播放全部" target="_blank">
|
||||
<VButton round class="play-all">
|
||||
@ -65,16 +65,18 @@
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts">
|
||||
import { VLoading, VEmpty, VIcon, VButton, TextBox, DpiImage, ScrollTrigger } from '@/ui'
|
||||
import { formatDate, formatDuration } from '@/core/utils/formatters'
|
||||
import { getUID } from '@/core/utils'
|
||||
import { defineComponent } from 'vue'
|
||||
import type { VideoCard } from '@/components/feeds/video-card'
|
||||
import { getJsonWithCredentials } from '@/core/ajax'
|
||||
import { logError } from '@/core/utils/log'
|
||||
import { VideoCard } from '@/components/feeds/video-card'
|
||||
import { getComponentSettings } from '@/core/settings'
|
||||
import { getUID } from '@/core/utils'
|
||||
import { formatDate, formatDuration } from '@/core/utils/formatters'
|
||||
import { logError } from '@/core/utils/log'
|
||||
import { DpiImage, ScrollTrigger, TextBox, VButton, VEmpty, VIcon, VLoading } from '@/ui'
|
||||
|
||||
import { popupProps, usePopup } from '../mixins'
|
||||
import { notSelectedFolder } from './favorites-folder'
|
||||
import FavoritesFolderSelect from './FavoritesFolderSelect.vue'
|
||||
import { popperMixin } from '../mixins'
|
||||
|
||||
/*
|
||||
新版收藏夹 API
|
||||
@ -110,7 +112,7 @@ const favoriteItemMapper = (item: any): FavoritesItemInfo => ({
|
||||
upFaceUrl: item.upper.face.replace('http:', 'https:'),
|
||||
upID: item.upper.mid,
|
||||
})
|
||||
async function searchAllList() {
|
||||
async function searchAllList(this: InstanceType<typeof ThisComponent>) {
|
||||
if (!this.searching) {
|
||||
return
|
||||
}
|
||||
@ -148,7 +150,7 @@ async function searchAllList() {
|
||||
this.loading = false
|
||||
}
|
||||
}
|
||||
export default Vue.extend({
|
||||
const ThisComponent = defineComponent({
|
||||
components: {
|
||||
FavoritesFolderSelect,
|
||||
VLoading,
|
||||
@ -159,12 +161,13 @@ export default Vue.extend({
|
||||
DpiImage,
|
||||
ScrollTrigger,
|
||||
},
|
||||
mixins: [popperMixin],
|
||||
props: popupProps,
|
||||
setup: usePopup,
|
||||
data() {
|
||||
return {
|
||||
loading: true,
|
||||
cards: [],
|
||||
filteredCards: [],
|
||||
cards: [] as FavoritesItemInfo[],
|
||||
filteredCards: [] as FavoritesItemInfo[],
|
||||
page: 1,
|
||||
hasMorePage: true,
|
||||
searchPage: 1,
|
||||
@ -174,24 +177,24 @@ export default Vue.extend({
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
searching() {
|
||||
searching(): boolean {
|
||||
return this.search !== ''
|
||||
},
|
||||
moreLink() {
|
||||
moreLink(): string {
|
||||
const { id } = this.folder
|
||||
if (id === 0) {
|
||||
return `https://space.bilibili.com/${getUID()}/favlist`
|
||||
}
|
||||
return `https://space.bilibili.com/${getUID()}/favlist?fid=${id}`
|
||||
},
|
||||
playLink() {
|
||||
playLink(): string {
|
||||
const { id } = this.folder
|
||||
if (id === 0) {
|
||||
return undefined
|
||||
}
|
||||
return `https://www.bilibili.com/medialist/play/ml${id}`
|
||||
},
|
||||
canLoadMore() {
|
||||
canLoadMore(): boolean {
|
||||
if (this.searching) {
|
||||
return this.hasMoreSearchPage
|
||||
}
|
||||
@ -258,7 +261,7 @@ export default Vue.extend({
|
||||
logError(error)
|
||||
}
|
||||
},
|
||||
debounceSearchAllList: lodash.debounce(searchAllList, 200),
|
||||
debounceSearchAllList: lodash.debounce(searchAllList, 200) as unknown as () => Promise<void>,
|
||||
scrollTrigger() {
|
||||
if (this.searching) {
|
||||
this.debounceSearchAllList()
|
||||
@ -268,6 +271,7 @@ export default Vue.extend({
|
||||
},
|
||||
},
|
||||
})
|
||||
export default ThisComponent
|
||||
</script>
|
||||
<style lang="scss">
|
||||
@import 'common';
|
||||
@ -340,7 +344,7 @@ export default Vue.extend({
|
||||
@include no-scrollbar();
|
||||
padding: 0 12px;
|
||||
padding-bottom: 12px;
|
||||
&-enter,
|
||||
&-enter-from,
|
||||
&-leave-to {
|
||||
opacity: 0;
|
||||
transform: translateY(-16px) scale(0.9);
|
||||
|
||||
@ -1,5 +1,7 @@
|
||||
import { defineAsyncComponent } from 'vue'
|
||||
import { getUID } from '@/core/utils'
|
||||
import { CustomNavbarItemInit } from '../custom-navbar-item'
|
||||
|
||||
import type { CustomNavbarItemInit } from '../custom-navbar-item'
|
||||
|
||||
const href = `https://space.bilibili.com/${getUID()}/favlist`
|
||||
export const favorites: CustomNavbarItemInit = {
|
||||
@ -14,5 +16,5 @@ export const favorites: CustomNavbarItemInit = {
|
||||
|
||||
boundingWidth: 380,
|
||||
noPopupPadding: true,
|
||||
popupContent: () => import('./NavbarFavorites.vue').then(m => m.default),
|
||||
popupContent: defineAsyncComponent(() => import('./NavbarFavorites.vue')),
|
||||
}
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<div class="navbar-feeds">
|
||||
<div ref="el" class="navbar-feeds">
|
||||
<TabControl ref="tabControl" :tabs="tabs" more-link="https://t.bilibili.com/">
|
||||
<template #more-link>
|
||||
所有动态
|
||||
@ -9,18 +9,25 @@
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts">
|
||||
import { TabControl, VIcon } from '@/ui'
|
||||
import type { Ref } from 'vue'
|
||||
import { defineComponent, ref } from 'vue'
|
||||
import { feedsCardTypes } from '@/components/feeds/api'
|
||||
import { getNotifyCount } from '@/components/feeds/notify'
|
||||
import { popperMixin } from '../mixins'
|
||||
import { TabControl, VIcon } from '@/ui'
|
||||
|
||||
import { popupProps, usePopup } from '../mixins'
|
||||
import { tabs } from './tabs/tabs'
|
||||
|
||||
export default Vue.extend({
|
||||
export default defineComponent({
|
||||
components: {
|
||||
TabControl,
|
||||
VIcon,
|
||||
},
|
||||
mixins: [popperMixin],
|
||||
props: popupProps,
|
||||
setup: props => ({
|
||||
...usePopup(props),
|
||||
tabControl: ref(null) as Ref<InstanceType<typeof TabControl> | null>,
|
||||
}),
|
||||
data() {
|
||||
return {
|
||||
tabs,
|
||||
@ -37,7 +44,7 @@ export default Vue.extend({
|
||||
async refreshNotifyCount() {
|
||||
// const totalJson = await getFeeds(navbarFeedsTypeList)
|
||||
// this.item.notifyCount = lodash.get(totalJson, 'data.update_num', 0)
|
||||
const { tabControl } = this.$refs
|
||||
const { tabControl } = this
|
||||
tabs.forEach(async tab => {
|
||||
if (tabControl.selectedTab === tab) {
|
||||
return
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
import { CustomNavbarItemInit } from '../custom-navbar-item'
|
||||
import { defineAsyncComponent } from 'vue'
|
||||
import type { CustomNavbarItemInit } from '../custom-navbar-item'
|
||||
|
||||
export const feeds: CustomNavbarItemInit = {
|
||||
name: 'feeds',
|
||||
@ -20,7 +21,7 @@ export const feeds: CustomNavbarItemInit = {
|
||||
},
|
||||
loginRequired: true,
|
||||
|
||||
popupContent: () => import('./NavbarFeeds.vue').then(m => m.default),
|
||||
popupContent: defineAsyncComponent(() => import('./NavbarFeeds.vue')),
|
||||
boundingWidth: 300,
|
||||
noPopupPadding: true,
|
||||
}
|
||||
|
||||
@ -1,3 +1,31 @@
|
||||
<script setup lang="ts">
|
||||
import { feedsCardTypes } from '@/components/feeds/api'
|
||||
import type { BangumiCard as BangumiCardData } from '@/components/feeds/bangumi-card'
|
||||
import { isNewID } from '@/components/feeds/notify'
|
||||
import BangumiCard from '@/components/feeds/BangumiCard.vue'
|
||||
import { ScrollTrigger, VEmpty, VLoading } from '@/ui'
|
||||
|
||||
import { useNextPage } from './next-page'
|
||||
|
||||
const { loading, cards, hasMorePage, nextPage } = useNextPage(
|
||||
feedsCardTypes.bangumi,
|
||||
(card: any): BangumiCardData & { new: boolean } => {
|
||||
const cardJson = JSON.parse(card.card)
|
||||
return {
|
||||
id: card.desc.dynamic_id_str,
|
||||
title: cardJson.apiSeasonInfo.title,
|
||||
coverUrl: cardJson.apiSeasonInfo.cover,
|
||||
epCoverUrl: cardJson.cover,
|
||||
epTitle: cardJson.new_desc,
|
||||
url: cardJson.url,
|
||||
get new() {
|
||||
return isNewID(this.id)
|
||||
},
|
||||
}
|
||||
},
|
||||
)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="bangumi-feeds">
|
||||
<VLoading v-if="loading"></VLoading>
|
||||
@ -10,35 +38,7 @@
|
||||
</template>
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts">
|
||||
import { feedsCardTypes } from '@/components/feeds/api'
|
||||
import { isNewID } from '@/components/feeds/notify'
|
||||
import { BangumiCard } from '@/components/feeds/bangumi-card'
|
||||
import BangumiCardComponent from '@/components/feeds/BangumiCard.vue'
|
||||
import { nextPageMixin } from './next-page'
|
||||
|
||||
export default Vue.extend({
|
||||
components: {
|
||||
BangumiCard: BangumiCardComponent,
|
||||
},
|
||||
mixins: [
|
||||
nextPageMixin(feedsCardTypes.bangumi, (card: any) => {
|
||||
const cardJson = JSON.parse(card.card)
|
||||
return {
|
||||
id: card.desc.dynamic_id_str,
|
||||
title: cardJson.apiSeasonInfo.title,
|
||||
coverUrl: cardJson.apiSeasonInfo.cover,
|
||||
epCoverUrl: cardJson.cover,
|
||||
epTitle: cardJson.new_desc,
|
||||
url: cardJson.url,
|
||||
get new() {
|
||||
return isNewID(this.id)
|
||||
},
|
||||
} as BangumiCard
|
||||
}),
|
||||
],
|
||||
})
|
||||
</script>
|
||||
<style lang="scss">
|
||||
.bangumi-feeds {
|
||||
display: flex;
|
||||
|
||||
@ -1,28 +1,15 @@
|
||||
<template>
|
||||
<div class="column-feeds">
|
||||
<VLoading v-if="loading"></VLoading>
|
||||
<VEmpty v-else-if="!loading && cards.length === 0"></VEmpty>
|
||||
<template v-else>
|
||||
<div class="columns-feeds-content">
|
||||
<ColumnCard v-for="c of cards" :key="c.id" :is-new="c.new" :data="c"></ColumnCard>
|
||||
</div>
|
||||
<ScrollTrigger v-if="hasMorePage" @trigger="nextPage()"></ScrollTrigger>
|
||||
</template>
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts">
|
||||
<script setup lang="ts">
|
||||
import { ScrollTrigger, VEmpty, VLoading } from '@/ui'
|
||||
import { feedsCardTypes } from '@/components/feeds/api'
|
||||
import type { ColumnCard as ColumnCardData } from '@/components/feeds/column-card'
|
||||
import ColumnCard from '@/components/feeds/ColumnCard.vue'
|
||||
import { isNewID } from '@/components/feeds/notify'
|
||||
import { ColumnCard } from '@/components/feeds/column-card'
|
||||
import ColumnCardComponent from '@/components/feeds/ColumnCard.vue'
|
||||
import { nextPageMixin } from './next-page'
|
||||
|
||||
export default Vue.extend({
|
||||
components: {
|
||||
ColumnCard: ColumnCardComponent,
|
||||
},
|
||||
mixins: [
|
||||
nextPageMixin(feedsCardTypes.column, (card: any) => {
|
||||
import { useNextPage } from './next-page'
|
||||
|
||||
const { loading, cards, hasMorePage, nextPage } = useNextPage(
|
||||
feedsCardTypes.column,
|
||||
(card: any): ColumnCardData & { new: boolean } => {
|
||||
const cardJson = JSON.parse(card.card)
|
||||
return {
|
||||
id: card.desc.dynamic_id_str,
|
||||
@ -37,11 +24,24 @@ export default Vue.extend({
|
||||
get new() {
|
||||
return isNewID(this.id)
|
||||
},
|
||||
} as ColumnCard
|
||||
}),
|
||||
],
|
||||
})
|
||||
}
|
||||
},
|
||||
)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="column-feeds">
|
||||
<VLoading v-if="loading"></VLoading>
|
||||
<VEmpty v-else-if="!loading && cards.length === 0"></VEmpty>
|
||||
<template v-else>
|
||||
<div class="columns-feeds-content">
|
||||
<ColumnCard v-for="c of cards" :key="c.id" :is-new="c.new" :data="c"></ColumnCard>
|
||||
</div>
|
||||
<ScrollTrigger v-if="hasMorePage" @trigger="nextPage()"></ScrollTrigger>
|
||||
</template>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="scss">
|
||||
.column-feeds {
|
||||
display: flex;
|
||||
|
||||
@ -16,11 +16,13 @@
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts">
|
||||
import { VLoading, VEmpty, DpiImage } from '@/ui'
|
||||
import { responsiveGetPages, getJsonWithCredentials } from '@/core/ajax'
|
||||
import { LiveFeedItem } from './live-feed-item'
|
||||
import { defineComponent } from 'vue'
|
||||
import { getJsonWithCredentials, responsiveGetPages } from '@/core/ajax'
|
||||
import { DpiImage, VEmpty, VLoading } from '@/ui'
|
||||
|
||||
export default Vue.extend({
|
||||
import type { LiveFeedItem } from './live-feed-item'
|
||||
|
||||
export default defineComponent({
|
||||
components: {
|
||||
VLoading,
|
||||
VEmpty,
|
||||
@ -29,7 +31,7 @@ export default Vue.extend({
|
||||
data() {
|
||||
return {
|
||||
loading: true,
|
||||
rawItems: [],
|
||||
rawItems: [] as unknown[],
|
||||
hasMorePage: true,
|
||||
}
|
||||
},
|
||||
@ -42,7 +44,7 @@ export default Vue.extend({
|
||||
upName: card.uname,
|
||||
url: card.link,
|
||||
})
|
||||
return (this.rawItems as any[]).map(parseLiveCard)
|
||||
return this.rawItems.map(parseLiveCard)
|
||||
},
|
||||
},
|
||||
async created() {
|
||||
@ -66,7 +68,7 @@ export default Vue.extend({
|
||||
@include v-center();
|
||||
.live-feeds-content {
|
||||
align-self: stretch;
|
||||
&-enter,
|
||||
&-enter-from,
|
||||
&-leave-to {
|
||||
opacity: 0;
|
||||
transform: translateY(-16px) scale(0.9);
|
||||
|
||||
@ -1,3 +1,104 @@
|
||||
<script setup lang="ts">
|
||||
import { computed } from 'vue'
|
||||
import { feedsCardTypes, groupVideoFeeds } from '@/components/feeds/api'
|
||||
import { isNewID } from '@/components/feeds/notify'
|
||||
import type { VideoCard as VideoCardData } from '@/components/feeds/video-card'
|
||||
import VideoCard from '@/components/feeds/VideoCard.vue'
|
||||
import { formatCount, formatDuration } from '@/core/utils/formatters'
|
||||
import { VLoading, VEmpty, ScrollTrigger } from '@/ui'
|
||||
|
||||
import { useNextPage } from './next-page'
|
||||
|
||||
const formatPubTime = (pubTime: number) => {
|
||||
const now = Number(new Date())
|
||||
const pubDate = new Date(pubTime)
|
||||
const time = [pubDate.getHours(), pubDate.getMinutes(), pubDate.getSeconds()]
|
||||
.map(it => it.toString().padStart(2, '0'))
|
||||
.join(':')
|
||||
let date: number[]
|
||||
if (new Date(now).getFullYear() !== pubDate.getFullYear()) {
|
||||
date = [pubDate.getFullYear(), pubDate.getMonth() + 1, pubDate.getDate()]
|
||||
} else {
|
||||
date = [pubDate.getMonth() + 1, pubDate.getDate()]
|
||||
}
|
||||
return `${date.map(it => it.toString().padStart(2, '0')).join('-')} ${time}`
|
||||
}
|
||||
|
||||
const formatPubTimeText = (pubTime: number) => {
|
||||
const now = Number(new Date())
|
||||
const oneDayBefore = now - 1000 * 3600 * 24
|
||||
if (oneDayBefore < pubTime) {
|
||||
const diffHours = Math.round((now - pubTime) / 1000 / 3600)
|
||||
if (diffHours === 0) {
|
||||
const diffMinutes = Math.round((now - pubTime) / 1000 / 60)
|
||||
if (diffMinutes === 0) {
|
||||
return '刚刚'
|
||||
}
|
||||
return `${diffMinutes}分钟前`
|
||||
}
|
||||
return `${diffHours}小时前`
|
||||
}
|
||||
const pubDate = new Date(pubTime)
|
||||
let date: number[]
|
||||
if (new Date(now).getFullYear() !== pubDate.getFullYear()) {
|
||||
date = [pubDate.getFullYear(), pubDate.getMonth() + 1, pubDate.getDate()]
|
||||
} else {
|
||||
date = [pubDate.getMonth() + 1, pubDate.getDate()]
|
||||
}
|
||||
return `${date.map(it => it.toString().padStart(2, '0')).join('-')}`
|
||||
}
|
||||
|
||||
const jsonMapper = (card: any): VideoCardData & { new: boolean } => {
|
||||
const cardJson = JSON.parse(card.card)
|
||||
return {
|
||||
id: card.desc.dynamic_id_str,
|
||||
aid: cardJson.aid,
|
||||
bvid: card.desc.bvid,
|
||||
videoUrl: `https://www.bilibili.com/${card.desc.bvid}`,
|
||||
coverUrl: cardJson.pic,
|
||||
title: cardJson.title,
|
||||
duration: cardJson.duration,
|
||||
durationText: formatDuration(cardJson.duration),
|
||||
description: cardJson.desc,
|
||||
pubTime: formatPubTime(cardJson.pubdate * 1000),
|
||||
pubTimeText: formatPubTimeText(cardJson.pubdate * 1000),
|
||||
upFaceUrl: card.desc.user_profile.info.face,
|
||||
upName: card.desc.user_profile.info.uname,
|
||||
upID: card.desc.user_profile.info.uid,
|
||||
watchlater: true,
|
||||
playCount: formatCount(cardJson.stat.view),
|
||||
get new() {
|
||||
return isNewID(this.id)
|
||||
},
|
||||
} as VideoCardData & { new: boolean }
|
||||
}
|
||||
|
||||
const onCardsUpdate = (cards: (VideoCardData & { new: boolean })[]) => {
|
||||
return groupVideoFeeds(cards)
|
||||
}
|
||||
|
||||
const { loading, cards, hasMorePage, nextPage } = useNextPage<VideoCardData & { new: boolean }>(
|
||||
feedsCardTypes.video,
|
||||
jsonMapper,
|
||||
onCardsUpdate,
|
||||
)
|
||||
|
||||
const columnedCards = computed(
|
||||
(): {
|
||||
left: (VideoCardData & { new: boolean })[]
|
||||
right: (VideoCardData & { new: boolean })[]
|
||||
} => {
|
||||
return {
|
||||
left: cards.value.filter((_, index) => index % 2 === 0),
|
||||
right: cards.value.filter((_, index) => index % 2 !== 0),
|
||||
} as {
|
||||
left: (VideoCardData & { new: boolean })[]
|
||||
right: (VideoCardData & { new: boolean })[]
|
||||
}
|
||||
},
|
||||
)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="video-feeds">
|
||||
<VLoading v-if="loading"></VLoading>
|
||||
@ -29,97 +130,7 @@
|
||||
</template>
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts">
|
||||
import { VideoCard } from '@/components/feeds/video-card'
|
||||
import { formatDuration, formatCount } from '@/core/utils/formatters'
|
||||
import { isNewID } from '@/components/feeds/notify'
|
||||
import { feedsCardTypes, groupVideoFeeds } from '@/components/feeds/api'
|
||||
import VideoCardComponent from '@/components/feeds/VideoCard.vue'
|
||||
import { nextPageMixin } from './next-page'
|
||||
|
||||
const formatPubTime = (pubTime: number) => {
|
||||
const now = Number(new Date())
|
||||
const pubDate = new Date(pubTime)
|
||||
const time = [pubDate.getHours(), pubDate.getMinutes(), pubDate.getSeconds()]
|
||||
.map(it => it.toString().padStart(2, '0'))
|
||||
.join(':')
|
||||
let date: number[]
|
||||
if (new Date(now).getFullYear() !== pubDate.getFullYear()) {
|
||||
date = [pubDate.getFullYear(), pubDate.getMonth() + 1, pubDate.getDate()]
|
||||
} else {
|
||||
date = [pubDate.getMonth() + 1, pubDate.getDate()]
|
||||
}
|
||||
return `${date.map(it => it.toString().padStart(2, '0')).join('-')} ${time}`
|
||||
}
|
||||
const formatPubTimeText = (pubTime: number) => {
|
||||
const now = Number(new Date())
|
||||
const oneDayBefore = now - 1000 * 3600 * 24
|
||||
if (oneDayBefore < pubTime) {
|
||||
const diffHours = Math.round((now - pubTime) / 1000 / 3600)
|
||||
if (diffHours === 0) {
|
||||
const diffMinutes = Math.round((now - pubTime) / 1000 / 60)
|
||||
if (diffMinutes === 0) {
|
||||
return '刚刚'
|
||||
}
|
||||
return `${diffMinutes}分钟前`
|
||||
}
|
||||
return `${diffHours}小时前`
|
||||
}
|
||||
const pubDate = new Date(pubTime)
|
||||
let date: number[]
|
||||
if (new Date(now).getFullYear() !== pubDate.getFullYear()) {
|
||||
date = [pubDate.getFullYear(), pubDate.getMonth() + 1, pubDate.getDate()]
|
||||
} else {
|
||||
date = [pubDate.getMonth() + 1, pubDate.getDate()]
|
||||
}
|
||||
return `${date.map(it => it.toString().padStart(2, '0')).join('-')}`
|
||||
}
|
||||
export default Vue.extend({
|
||||
components: {
|
||||
VideoCard: VideoCardComponent,
|
||||
},
|
||||
mixins: [
|
||||
nextPageMixin(feedsCardTypes.video, (card: any) => {
|
||||
const cardJson = JSON.parse(card.card)
|
||||
return {
|
||||
id: card.desc.dynamic_id_str,
|
||||
aid: cardJson.aid,
|
||||
bvid: card.desc.bvid,
|
||||
videoUrl: `https://www.bilibili.com/${card.desc.bvid}`,
|
||||
coverUrl: cardJson.pic,
|
||||
title: cardJson.title,
|
||||
duration: cardJson.duration,
|
||||
durationText: formatDuration(cardJson.duration),
|
||||
description: cardJson.desc,
|
||||
pubTime: formatPubTime(cardJson.pubdate * 1000),
|
||||
pubTimeText: formatPubTimeText(cardJson.pubdate * 1000),
|
||||
upFaceUrl: card.desc.user_profile.info.face,
|
||||
upName: card.desc.user_profile.info.uname,
|
||||
upID: card.desc.user_profile.info.uid,
|
||||
watchlater: true,
|
||||
playCount: formatCount(cardJson.stat.view),
|
||||
get new() {
|
||||
return isNewID(this.id)
|
||||
},
|
||||
} as VideoCard
|
||||
}),
|
||||
],
|
||||
computed: {
|
||||
columnedCards() {
|
||||
const { cards } = this as { cards: VideoCard[] }
|
||||
return {
|
||||
left: cards.filter((_, index) => index % 2 === 0),
|
||||
right: cards.filter((_, index) => index % 2 !== 0),
|
||||
}
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
onCardsUpdate(cards: VideoCard[]) {
|
||||
return groupVideoFeeds(cards)
|
||||
},
|
||||
},
|
||||
})
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
.video-feeds {
|
||||
display: flex;
|
||||
@ -139,7 +150,7 @@ export default Vue.extend({
|
||||
justify-content: space-between;
|
||||
width: 356px;
|
||||
.cards {
|
||||
&-enter,
|
||||
&-enter-from,
|
||||
&-leave-to {
|
||||
opacity: 0;
|
||||
transform: translateY(-16px) scale(0.9);
|
||||
|
||||
@ -1,86 +1,81 @@
|
||||
import {
|
||||
getFeeds,
|
||||
FeedsCardType,
|
||||
applyContentFilter,
|
||||
isPreOrderedVideo,
|
||||
} from '@/components/feeds/api'
|
||||
import { descendingStringSort } from '@/core/utils/sort'
|
||||
import { logError } from '@/core/utils/log'
|
||||
import { computed, ref, type Ref, type ComputedRef } from 'vue'
|
||||
import type { FeedsCardType } from '@/components/feeds/api'
|
||||
import { applyContentFilter, getFeeds, isPreOrderedVideo } from '@/components/feeds/api'
|
||||
import { setLatestID } from '@/components/feeds/notify'
|
||||
import { VLoading, VEmpty, ScrollTrigger } from '@/ui'
|
||||
import { logError } from '@/core/utils/log'
|
||||
import { descendingStringSort } from '@/core/utils/sort'
|
||||
|
||||
/**
|
||||
* 获取用于支持顶栏动态无限滚动的Vue Mixin
|
||||
* 用于支持顶栏动态无限滚动
|
||||
* @param type 动态类型
|
||||
* @param jsonMapper 解析JSON数据的映射函数
|
||||
* @param onCardsUpdate 卡片列表更新时用于修改的回调函数
|
||||
*/
|
||||
export const nextPageMixin = <MappedItem extends { id: string }, RawItem>(
|
||||
export const useNextPage = <MappedItem extends { id: string } = { id: string }, RawItem = unknown>(
|
||||
type: FeedsCardType,
|
||||
jsonMapper: (obj: RawItem) => MappedItem,
|
||||
) =>
|
||||
Vue.extend({
|
||||
components: {
|
||||
VLoading,
|
||||
VEmpty,
|
||||
ScrollTrigger,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
loading: true,
|
||||
cards: [],
|
||||
hasMorePage: true,
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
sortedCards() {
|
||||
return ([...this.cards] as MappedItem[]).sort(descendingStringSort(it => it.id))
|
||||
},
|
||||
},
|
||||
async created() {
|
||||
await this.nextPage()
|
||||
const cards = this.sortedCards as MappedItem[]
|
||||
if (cards.length > 0) {
|
||||
setLatestID(cards[0].id)
|
||||
// console.log('setLatestID', cards[0].id)
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
async nextPage() {
|
||||
onCardsUpdate?: (cards: MappedItem[]) => MappedItem[],
|
||||
): {
|
||||
loading: Ref<boolean>
|
||||
cards: Ref<MappedItem[]>
|
||||
hasMorePage: Ref<boolean>
|
||||
sortedCards: ComputedRef<MappedItem[]>
|
||||
nextPage: () => Promise<void>
|
||||
} => {
|
||||
const loading = ref(true)
|
||||
const cards: Ref<MappedItem[]> = ref([])
|
||||
const hasMorePage = ref(true)
|
||||
|
||||
const sortedCards = computed(() => [...cards.value].sort(descendingStringSort(it => it.id)))
|
||||
|
||||
const nextPage = async () => {
|
||||
try {
|
||||
const cards: MappedItem[] = this.sortedCards
|
||||
const lastCardID = cards[cards.length - 1]?.id ?? 0
|
||||
const lastCardID = sortedCards.value[sortedCards.value.length - 1]?.id ?? 0
|
||||
|
||||
const json = await getFeeds(type, lastCardID)
|
||||
console.log(json)
|
||||
if (json.code !== 0) {
|
||||
this.hasMorePage = false
|
||||
hasMorePage.value = false
|
||||
throw new Error(json.message)
|
||||
}
|
||||
const jsonCards = lodash.get(json, 'data.cards', []).map(jsonMapper) as MappedItem[]
|
||||
const jsonCards = lodash.get<RawItem[]>(json, 'data.cards', []).map(jsonMapper)
|
||||
|
||||
let concatCards = applyContentFilter(
|
||||
cards
|
||||
sortedCards.value
|
||||
.concat(jsonCards)
|
||||
.sort(descendingStringSort(it => it.id))
|
||||
.filter(card => !isPreOrderedVideo(card)),
|
||||
)
|
||||
|
||||
if (concatCards.length > 0 && this.onCardsUpdate) {
|
||||
concatCards = this.onCardsUpdate(concatCards)
|
||||
if (concatCards.length > 0 && onCardsUpdate) {
|
||||
concatCards = onCardsUpdate(concatCards)
|
||||
}
|
||||
console.log('nextPage get', concatCards)
|
||||
this.cards = concatCards
|
||||
if (this.cards.length === 0) {
|
||||
this.hasMorePage = false
|
||||
cards.value = concatCards
|
||||
if (cards.value.length === 0) {
|
||||
hasMorePage.value = false
|
||||
return
|
||||
}
|
||||
this.hasMorePage =
|
||||
lastCardID === 0 ? true : Boolean(lodash.get(json, 'data.has_more', true))
|
||||
hasMorePage.value = lastCardID === 0 ? true : Boolean(lodash.get(json, 'data.has_more', true))
|
||||
} catch (error) {
|
||||
logError(error)
|
||||
} finally {
|
||||
this.loading = false
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
nextPage().then(() => {
|
||||
if (sortedCards.value.length > 0) {
|
||||
setLatestID(sortedCards.value[0].id)
|
||||
// console.log('setLatestID', sortedCards.value[0].id)
|
||||
}
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
return {
|
||||
loading,
|
||||
cards,
|
||||
hasMorePage,
|
||||
sortedCards,
|
||||
nextPage,
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,31 +1,32 @@
|
||||
import { TabMappings } from '@/ui/tab-mapping'
|
||||
import { defineAsyncComponent } from 'vue'
|
||||
import type { TabMappings } from '@/ui/tab-mapping'
|
||||
|
||||
export const tabs: TabMappings = [
|
||||
{
|
||||
name: 'video',
|
||||
displayName: '视频',
|
||||
component: () => import('./VideoFeeds.vue').then(m => m.default),
|
||||
component: defineAsyncComponent(() => import('./VideoFeeds.vue')),
|
||||
activeLink: 'https://t.bilibili.com/?tab=video',
|
||||
count: 0,
|
||||
},
|
||||
{
|
||||
name: 'bangumi',
|
||||
displayName: '番剧',
|
||||
component: () => import('./BangumiFeeds.vue').then(m => m.default),
|
||||
component: defineAsyncComponent(() => import('./BangumiFeeds.vue')),
|
||||
activeLink: 'https://t.bilibili.com/?tab=pgc',
|
||||
count: 0,
|
||||
},
|
||||
{
|
||||
name: 'column',
|
||||
displayName: '专栏',
|
||||
component: () => import('./ColumnFeeds.vue').then(m => m.default),
|
||||
component: defineAsyncComponent(() => import('./ColumnFeeds.vue')),
|
||||
activeLink: 'https://t.bilibili.com/?tab=article',
|
||||
count: 0,
|
||||
},
|
||||
{
|
||||
name: 'live',
|
||||
displayName: '直播',
|
||||
component: () => import('./LiveFeeds.vue').then(m => m.default),
|
||||
component: defineAsyncComponent(() => import('./LiveFeeds.vue')),
|
||||
activeLink: 'https://link.bilibili.com/p/center/index#/user-center/follow/1',
|
||||
count: 0,
|
||||
},
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import { CustomNavbarItemInit } from '../custom-navbar-item'
|
||||
import type { CustomNavbarItemInit } from '../custom-navbar-item'
|
||||
|
||||
const count = 4
|
||||
export const blanks: CustomNavbarItemInit[] = new Array(count).fill(0).map((_, index) => ({
|
||||
|
||||
@ -1,9 +1,9 @@
|
||||
<template>
|
||||
<div class="custom-navbar-history-list">
|
||||
<div ref="el" class="custom-navbar-history-list">
|
||||
<div class="header">
|
||||
<div class="header-row">
|
||||
<div class="search">
|
||||
<TextBox v-model="search" placeholder="搜索" linear></TextBox>
|
||||
<TextBox v-model:text="search" placeholder="搜索" linear></TextBox>
|
||||
</div>
|
||||
<div class="operations">
|
||||
<div class="operation" @click="toggleHistoryPause">
|
||||
@ -30,7 +30,7 @@
|
||||
:class="{ checked: t.checked }"
|
||||
:checked="t.checked"
|
||||
:disabled="loading"
|
||||
@change="toggleTypeFilter(t)"
|
||||
@update:checked="toggleTypeFilter(t)"
|
||||
>
|
||||
{{ t.displayName }}
|
||||
</RadioButton>
|
||||
@ -105,23 +105,29 @@
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts">
|
||||
import { defineComponent } from 'vue'
|
||||
import { bilibiliApi, getJsonWithCredentials, postTextWithCredentials } from '@/core/ajax'
|
||||
import { formData, getCsrf } from '@/core/utils'
|
||||
import { descendingSort } from '@/core/utils/sort'
|
||||
import {
|
||||
VButton,
|
||||
VIcon,
|
||||
RadioButton,
|
||||
TextBox,
|
||||
VLoading,
|
||||
VEmpty,
|
||||
ScrollTrigger,
|
||||
DpiImage,
|
||||
RadioButton,
|
||||
ScrollTrigger,
|
||||
TextBox,
|
||||
VButton,
|
||||
VEmpty,
|
||||
VIcon,
|
||||
VLoading,
|
||||
} from '@/ui'
|
||||
import { popperMixin } from '../mixins'
|
||||
import { types, TypeFilter, HistoryItem, getHistoryItems, group, HistoryType } from './types'
|
||||
|
||||
export default Vue.extend({
|
||||
import { popupProps, usePopup } from '../mixins'
|
||||
import type { HistoryItem, TypeFilter } from './types'
|
||||
import { getHistoryItems, group, HistoryType, types } from './types'
|
||||
|
||||
function search(this: InstanceType<typeof ThisComponent>) {
|
||||
this.reloadHistoryItems()
|
||||
}
|
||||
const ThisComponent = defineComponent({
|
||||
components: {
|
||||
VButton,
|
||||
VIcon,
|
||||
@ -132,28 +138,27 @@ export default Vue.extend({
|
||||
ScrollTrigger,
|
||||
DpiImage,
|
||||
},
|
||||
mixins: [popperMixin],
|
||||
props: popupProps,
|
||||
setup: usePopup,
|
||||
data() {
|
||||
return {
|
||||
types,
|
||||
search: '',
|
||||
viewTime: 0,
|
||||
cards: [],
|
||||
groups: [],
|
||||
cards: [] as HistoryItem[],
|
||||
groups: [] as { name: string; items: HistoryItem[] }[],
|
||||
loading: true,
|
||||
hasMorePage: true,
|
||||
paused: false,
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
canNextPage() {
|
||||
canNextPage(): boolean {
|
||||
return this.search === '' && !this.loading && this.hasMorePage
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
search: lodash.debounce(function search() {
|
||||
this.reloadHistoryItems()
|
||||
}, 200),
|
||||
search: lodash.debounce(search, 200) as unknown as () => void,
|
||||
},
|
||||
async created() {
|
||||
try {
|
||||
@ -239,6 +244,7 @@ export default Vue.extend({
|
||||
},
|
||||
},
|
||||
})
|
||||
export default ThisComponent
|
||||
</script>
|
||||
<style lang="scss">
|
||||
@import 'common';
|
||||
@ -253,7 +259,7 @@ export default Vue.extend({
|
||||
@include v-stretch();
|
||||
justify-content: center;
|
||||
@mixin items-animation {
|
||||
&-enter,
|
||||
&-enter-from,
|
||||
&-leave-to {
|
||||
opacity: 0;
|
||||
transform: translateY(-16px) scale(0.9);
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
import { CustomNavbarItemInit } from '../custom-navbar-item'
|
||||
import { defineAsyncComponent } from 'vue'
|
||||
import type { CustomNavbarItemInit } from '../custom-navbar-item'
|
||||
|
||||
const href = 'https://www.bilibili.com/account/history'
|
||||
export const history: CustomNavbarItemInit = {
|
||||
@ -13,5 +14,5 @@ export const history: CustomNavbarItemInit = {
|
||||
|
||||
boundingWidth: 400,
|
||||
noPopupPadding: true,
|
||||
popupContent: () => import('./NavbarHistory.vue').then(m => m.default),
|
||||
popupContent: defineAsyncComponent(() => import('./NavbarHistory.vue')),
|
||||
}
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import { getJsonWithCredentials, bilibiliApi } from '@/core/ajax'
|
||||
import { bilibiliApi, getJsonWithCredentials } from '@/core/ajax'
|
||||
import { fixed } from '@/core/utils'
|
||||
import { formatDuration } from '@/core/utils/formatters'
|
||||
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<div class="home-popup" role="list">
|
||||
<div ref="el" class="home-popup" role="list">
|
||||
<div
|
||||
v-for="[name, data] of Object.entries(categories)"
|
||||
:key="name"
|
||||
@ -30,16 +30,20 @@
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts">
|
||||
import { categories, Category } from '@/components/utils/categories/data'
|
||||
import { popperMixin } from '../mixins'
|
||||
import { defineComponent } from 'vue'
|
||||
import type { Category } from '@/components/utils/categories/data'
|
||||
import { categories } from '@/components/utils/categories/data'
|
||||
|
||||
import { popupProps, usePopup } from '../mixins'
|
||||
|
||||
const clone = lodash.cloneDeep(categories)
|
||||
Object.values(clone).forEach((data: any) => {
|
||||
data.count = null
|
||||
})
|
||||
let regionCountFetched = false
|
||||
export default Vue.extend({
|
||||
mixins: [popperMixin],
|
||||
export default defineComponent({
|
||||
props: popupProps,
|
||||
setup: usePopup,
|
||||
data() {
|
||||
return {
|
||||
categories: clone,
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
import { CustomNavbarItemInit } from '../custom-navbar-item'
|
||||
import { defineAsyncComponent } from 'vue'
|
||||
import type { CustomNavbarItemInit } from '../custom-navbar-item'
|
||||
|
||||
export const home: CustomNavbarItemInit = {
|
||||
name: 'home',
|
||||
@ -9,5 +10,5 @@ export const home: CustomNavbarItemInit = {
|
||||
touch: true,
|
||||
|
||||
boundingWidth: 366,
|
||||
popupContent: () => import('./NavbarHome.vue').then(m => m.default),
|
||||
popupContent: defineAsyncComponent(() => import('./NavbarHome.vue')),
|
||||
}
|
||||
|
||||
@ -1,21 +1,24 @@
|
||||
<template>
|
||||
<iframe :src="item.src" frameborder="0" :width="item.width" :height="item.height"></iframe>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import type { PropType } from 'vue'
|
||||
<script setup lang="ts">
|
||||
import type { NavbarIframeConfig } from './iframe'
|
||||
import { popperMixin } from '../mixins'
|
||||
import { CustomNavbarItem } from '../custom-navbar-item'
|
||||
import type { CustomNavbarItem } from '../custom-navbar-item'
|
||||
import { usePopup } from '../mixins'
|
||||
|
||||
export default Vue.extend({
|
||||
name: 'IframePopup',
|
||||
mixins: [popperMixin],
|
||||
props: {
|
||||
item: {
|
||||
type: CustomNavbarItem as unknown as PropType<CustomNavbarItem & NavbarIframeConfig>,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
})
|
||||
const props = defineProps<{
|
||||
item: CustomNavbarItem & NavbarIframeConfig
|
||||
container: HTMLElement
|
||||
}>()
|
||||
|
||||
const { el, popupShow } = usePopup(props)
|
||||
|
||||
defineExpose({ popupShow })
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<iframe
|
||||
ref="el"
|
||||
:src="item.src"
|
||||
frameborder="0"
|
||||
:width="item.width"
|
||||
:height="item.height"
|
||||
></iframe>
|
||||
</template>
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
import { CustomNavbarItemInit } from '../custom-navbar-item'
|
||||
import { defineAsyncComponent } from 'vue'
|
||||
import type { CustomNavbarItemInit } from '../custom-navbar-item'
|
||||
|
||||
export interface NavbarIframeConfig {
|
||||
src: string
|
||||
@ -16,7 +17,7 @@ const getIframeItem = (config: NavbarIframeConfig): CustomNavbarItemInit & Navba
|
||||
|
||||
touch: true,
|
||||
|
||||
popupContent: () => import('./IframePopup.vue').then(m => m.default),
|
||||
popupContent: defineAsyncComponent(() => import('./IframePopup.vue')),
|
||||
boundingWidth: config.width,
|
||||
noPopupPadding: true,
|
||||
transparentPopup: true,
|
||||
|
||||
@ -1,14 +1,13 @@
|
||||
import {
|
||||
defineComponentMetadata,
|
||||
defineOptionsMetadata,
|
||||
OptionsOfMetadata,
|
||||
} from '@/components/define'
|
||||
import { LaunchBarActionProvider } from '@/components/launch-bar/launch-bar-action'
|
||||
import { urlInclude, urlExclude } from './urls'
|
||||
import { entry } from './entry'
|
||||
import { defineAsyncComponent } from 'vue'
|
||||
import type { OptionsOfMetadata } from '@/components/define'
|
||||
import { defineComponentMetadata, defineOptionsMetadata } from '@/components/define'
|
||||
import type { LaunchBarActionProvider } from '@/components/launch-bar/launch-bar-action'
|
||||
import { getNumberValidator } from '@/core/utils'
|
||||
import { NavbarNotifyStyle } from './notify-style'
|
||||
|
||||
import { entry } from './entry'
|
||||
import { urlExclude, urlInclude } from './urls'
|
||||
|
||||
const styleID = 'custom-navbar-style'
|
||||
const options = defineOptionsMetadata({
|
||||
hidden: {
|
||||
@ -127,7 +126,7 @@ export const component = defineComponentMetadata({
|
||||
// const { addImportantStyle } = await import('@/core/style')
|
||||
// addImportantStyle(style, styleID)
|
||||
},
|
||||
extraOptions: () => import('./settings/ExtraOptions.vue').then(m => m.default),
|
||||
extraOptions: defineAsyncComponent(() => import('./settings/ExtraOptions.vue')),
|
||||
plugin: {
|
||||
displayName: '自定义顶栏 - 功能扩展',
|
||||
setup: ({ addData }) => {
|
||||
|
||||
@ -10,11 +10,12 @@
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { addComponentListener } from '@/core/settings'
|
||||
import { defineComponent } from 'vue'
|
||||
import { getJson } from '@/core/ajax'
|
||||
import { addComponentListener } from '@/core/settings'
|
||||
import { VIcon } from '@/ui'
|
||||
|
||||
export default Vue.extend({
|
||||
export default defineComponent({
|
||||
name: 'NavbarLogo',
|
||||
components: {
|
||||
VIcon,
|
||||
|
||||
@ -1,9 +1,10 @@
|
||||
import { CustomNavbarItemInit } from '../custom-navbar-item'
|
||||
import { defineAsyncComponent } from 'vue'
|
||||
import type { CustomNavbarItemInit } from '../custom-navbar-item'
|
||||
|
||||
export const logo: CustomNavbarItemInit = {
|
||||
name: 'logo',
|
||||
displayName: 'Logo',
|
||||
content: () => import('./NavbarLogo.vue').then(m => m.default),
|
||||
content: defineAsyncComponent(() => import('./NavbarLogo.vue')),
|
||||
|
||||
href: 'https://www.bilibili.com/',
|
||||
}
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<div class="messages-popup" role="list">
|
||||
<div ref="el" class="messages-popup" role="list">
|
||||
<div v-for="e of entries" :key="e.name" class="message-entry" role="listitem">
|
||||
<a
|
||||
:data-prop="e.prop"
|
||||
@ -14,8 +14,10 @@
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent } from 'vue'
|
||||
import { getJsonWithCredentials } from '@/core/ajax'
|
||||
import { popperMixin } from '../mixins'
|
||||
|
||||
import { popupProps, usePopup } from '../mixins'
|
||||
|
||||
interface MessageEntry {
|
||||
prop?: string
|
||||
@ -58,9 +60,10 @@ const entries = [
|
||||
name: '消息设置',
|
||||
},
|
||||
] as MessageEntry[]
|
||||
export default Vue.extend({
|
||||
export default defineComponent({
|
||||
name: 'MessagesPopup',
|
||||
mixins: [popperMixin],
|
||||
props: popupProps,
|
||||
setup: usePopup,
|
||||
data() {
|
||||
return {
|
||||
entries: entries.map(e => {
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
import { CustomNavbarItemInit } from '../custom-navbar-item'
|
||||
import { defineAsyncComponent } from 'vue'
|
||||
import type { CustomNavbarItemInit } from '../custom-navbar-item'
|
||||
|
||||
const messagesUrl = 'https://message.bilibili.com/'
|
||||
export const messages: CustomNavbarItemInit = {
|
||||
@ -11,6 +12,6 @@ export const messages: CustomNavbarItemInit = {
|
||||
loginRequired: true,
|
||||
touch: true,
|
||||
|
||||
popupContent: () => import('./NavbarMessages.vue').then(m => m.default),
|
||||
popupContent: defineAsyncComponent(() => import('./NavbarMessages.vue')),
|
||||
lazy: false,
|
||||
}
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
import { type Ref, ref, onMounted } from 'vue'
|
||||
import { CustomNavbarItem } from './custom-navbar-item'
|
||||
|
||||
export const popperMixin = Vue.extend({
|
||||
props: {
|
||||
export const popupProps = {
|
||||
item: {
|
||||
type: CustomNavbarItem,
|
||||
required: true,
|
||||
@ -10,18 +10,26 @@ export const popperMixin = Vue.extend({
|
||||
type: HTMLElement,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
const navBarItem = this.item as CustomNavbarItem
|
||||
const containerElement = this.container as HTMLElement
|
||||
if (containerElement) {
|
||||
navBarItem?.usePopper(containerElement, this.$el.parentElement)
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
popupShow() {
|
||||
const navBarItem = this.item as CustomNavbarItem
|
||||
navBarItem?.popper?.update()
|
||||
},
|
||||
},
|
||||
|
||||
export const usePopup = (props: {
|
||||
item: CustomNavbarItem
|
||||
container: HTMLElement
|
||||
}): {
|
||||
el: Ref<HTMLElement | null>
|
||||
popupShow: () => void
|
||||
} => {
|
||||
const el = ref<HTMLElement | null>(null)
|
||||
onMounted(() => {
|
||||
const navBarItem = props.item
|
||||
const containerElement = props.container
|
||||
if (containerElement) {
|
||||
navBarItem?.usePopper(containerElement, el.value.parentElement)
|
||||
}
|
||||
})
|
||||
const popupShow = (): void => {
|
||||
const navBarItem = props.item
|
||||
navBarItem?.popper?.update().then()
|
||||
}
|
||||
return { el, popupShow }
|
||||
}
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<div class="ranking-popup" role="list">
|
||||
<div ref="el" class="ranking-popup" role="list">
|
||||
<div v-for="e of entries" :key="e.name" class="ranking-entry" role="listitem">
|
||||
<a target="_blank" :href="e.href">{{ e.name }}</a>
|
||||
</div>
|
||||
@ -7,7 +7,8 @@
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { popperMixin } from '../mixins'
|
||||
import { defineComponent } from 'vue'
|
||||
import { popupProps, usePopup } from '../mixins'
|
||||
|
||||
interface RankingEntry {
|
||||
href: string
|
||||
@ -39,9 +40,10 @@ const entries = [
|
||||
name: '短剧榜',
|
||||
},
|
||||
] as RankingEntry[]
|
||||
export default Vue.extend({
|
||||
export default defineComponent({
|
||||
name: 'RankingPopup',
|
||||
mixins: [popperMixin],
|
||||
props: popupProps,
|
||||
setup: usePopup,
|
||||
data() {
|
||||
return {
|
||||
entries,
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
import { CustomNavbarItemInit } from '../custom-navbar-item'
|
||||
import { defineAsyncComponent } from 'vue'
|
||||
import type { CustomNavbarItemInit } from '../custom-navbar-item'
|
||||
|
||||
const rankingUrl = 'https://www.bilibili.com/v/popular/rank/'
|
||||
export const ranking: CustomNavbarItemInit = {
|
||||
@ -10,5 +11,5 @@ export const ranking: CustomNavbarItemInit = {
|
||||
active: document.URL.startsWith(rankingUrl),
|
||||
touch: true,
|
||||
|
||||
popupContent: () => import('./NavbarRanking.vue').then(m => m.default),
|
||||
popupContent: defineAsyncComponent(() => import('./NavbarRanking.vue')),
|
||||
}
|
||||
|
||||
@ -4,9 +4,10 @@
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts">
|
||||
import { defineComponent } from 'vue'
|
||||
import LaunchBar from '@/components/launch-bar/LaunchBar.vue'
|
||||
|
||||
export default Vue.extend({
|
||||
export default defineComponent({
|
||||
components: {
|
||||
LaunchBar,
|
||||
},
|
||||
|
||||
@ -1,9 +1,10 @@
|
||||
import { CustomNavbarItemInit } from '../custom-navbar-item'
|
||||
import { defineAsyncComponent } from 'vue'
|
||||
import type { CustomNavbarItemInit } from '../custom-navbar-item'
|
||||
|
||||
export const search: CustomNavbarItemInit = {
|
||||
name: 'search',
|
||||
displayName: '搜索',
|
||||
content: () => import('./NavbarSearch.vue').then(m => m.default),
|
||||
content: defineAsyncComponent(() => import('./NavbarSearch.vue')),
|
||||
|
||||
// 禁用元素本身的 hover 效果之类的, 作为 content 的 NavbarSearch 是依然能够响应的
|
||||
disabled: true,
|
||||
|
||||
@ -11,15 +11,21 @@
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts">
|
||||
import type { Ref } from 'vue'
|
||||
import { defineComponent, ref } from 'vue'
|
||||
import { getUID } from '@/core/utils'
|
||||
import { VIcon, VButton } from '@/ui'
|
||||
import { setTriggerElement, loadNavbarSettings, toggleNavbarSettings } from './vm'
|
||||
import { VButton, VIcon } from '@/ui'
|
||||
|
||||
export default Vue.extend({
|
||||
import { loadNavbarSettings, setTriggerElement, toggleNavbarSettings } from './vm'
|
||||
|
||||
export default defineComponent({
|
||||
components: {
|
||||
VIcon,
|
||||
VButton,
|
||||
},
|
||||
setup: () => ({
|
||||
button: ref(null) as Ref<InstanceType<typeof VButton> | null>,
|
||||
}),
|
||||
data() {
|
||||
return {
|
||||
login: Boolean(getUID()),
|
||||
@ -29,7 +35,7 @@ export default Vue.extend({
|
||||
async loadNavbarSettings() {
|
||||
const isFirstLoad = await loadNavbarSettings()
|
||||
if (isFirstLoad) {
|
||||
const triggerButton = this.$refs.button.$el as HTMLElement
|
||||
const triggerButton = this.button.$el as HTMLElement
|
||||
setTriggerElement(triggerButton)
|
||||
}
|
||||
},
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
<template>
|
||||
<VPopup
|
||||
ref="popup"
|
||||
v-model="open"
|
||||
v-model:open="open"
|
||||
class="custom-navbar-settings"
|
||||
fixed
|
||||
:lazy="false"
|
||||
@ -27,7 +27,7 @@
|
||||
@mouseover="peekPadding(true)"
|
||||
@mouseout="peekPadding(false)"
|
||||
>
|
||||
<VSlider v-model="padding" :min="0" :max="40" :step="0.5"></VSlider>
|
||||
<VSlider v-model:value="padding" :min="0" :max="40" :step="0.5"></VSlider>
|
||||
<div class="padding-value">{{ padding.toFixed(1) }}%</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -68,36 +68,42 @@
|
||||
</VPopup>
|
||||
</template>
|
||||
<script lang="ts">
|
||||
import { SortableEvent } from 'sortablejs'
|
||||
import { VPopup, VIcon, VSlider, VLoading } from '@/ui'
|
||||
import type { Ref } from 'vue'
|
||||
import { defineComponent, ref } from 'vue'
|
||||
import type { SortableEvent } from 'sortablejs'
|
||||
|
||||
import { SortableJSLibrary } from '@/core/runtime-library'
|
||||
import { addComponentListener } from '@/core/settings'
|
||||
import { dqa } from '@/core/utils'
|
||||
import { SortableJSLibrary } from '@/core/runtime-library'
|
||||
import { getData } from '@/plugins/data'
|
||||
import { VIcon, VLoading, VPopup, VSlider } from '@/ui'
|
||||
|
||||
import { CustomNavbarItem, CustomNavbarRenderedItems } from '../custom-navbar-item'
|
||||
import { checkSequentialOrder, sortItems } from './orders'
|
||||
|
||||
const { navbarOptions } = CustomNavbarItem
|
||||
function padding(this: InstanceType<typeof ThisComponent>, newValue: number) {
|
||||
navbarOptions.padding = newValue
|
||||
}
|
||||
const [rendered] = getData(CustomNavbarRenderedItems) as [
|
||||
{
|
||||
items: CustomNavbarItem[]
|
||||
},
|
||||
]
|
||||
export default Vue.extend({
|
||||
const ThisComponent = defineComponent({
|
||||
components: {
|
||||
VPopup,
|
||||
VIcon,
|
||||
VSlider,
|
||||
VLoading,
|
||||
},
|
||||
props: {
|
||||
triggerElement: {
|
||||
type: HTMLElement,
|
||||
default: null,
|
||||
},
|
||||
},
|
||||
setup: () => ({
|
||||
popup: ref(null) as Ref<InstanceType<typeof VPopup> | null>,
|
||||
navbarSortList: ref(null) as Ref<HTMLDivElement | null>,
|
||||
}),
|
||||
data() {
|
||||
return {
|
||||
triggerElement: null as HTMLElement | null,
|
||||
open: false,
|
||||
padding: navbarOptions.padding,
|
||||
rendered,
|
||||
@ -106,9 +112,7 @@ export default Vue.extend({
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
padding: lodash.debounce((newValue: number) => {
|
||||
navbarOptions.padding = newValue
|
||||
}, 200),
|
||||
padding: lodash.debounce(padding, 200) as unknown as (newValue: number) => void,
|
||||
},
|
||||
async mounted() {
|
||||
addComponentListener('customNavbar.padding', (newValue: number) => {
|
||||
@ -116,7 +120,7 @@ export default Vue.extend({
|
||||
this.padding = newValue
|
||||
}
|
||||
})
|
||||
const list: HTMLElement = this.$refs.navbarSortList
|
||||
const list: HTMLElement = this.navbarSortList
|
||||
const Sortable = await SortableJSLibrary
|
||||
Sortable.create(list, {
|
||||
delay: 100,
|
||||
@ -134,7 +138,7 @@ export default Vue.extend({
|
||||
},
|
||||
methods: {
|
||||
toggle() {
|
||||
this.$refs.popup.toggle()
|
||||
this.popup.toggle()
|
||||
},
|
||||
peekPadding(peek: boolean) {
|
||||
dqa('.custom-navbar .padding').forEach(it => it.classList.toggle('peek', peek))
|
||||
@ -143,7 +147,7 @@ export default Vue.extend({
|
||||
item.element?.classList.toggle('peek', peek)
|
||||
},
|
||||
onSort(e: SortableEvent) {
|
||||
const container = this.$refs.navbarSortList as HTMLElement
|
||||
const container = this.navbarSortList as HTMLElement
|
||||
const element = e.item
|
||||
console.log(`${element.getAttribute('data-name')} ${e.oldIndex}->${e.newIndex}`)
|
||||
const ordersMap = Object.fromEntries(
|
||||
@ -164,6 +168,7 @@ export default Vue.extend({
|
||||
},
|
||||
},
|
||||
})
|
||||
export default ThisComponent
|
||||
</script>
|
||||
<style lang="scss">
|
||||
@import 'common';
|
||||
|
||||
@ -1,9 +1,7 @@
|
||||
import { mountVueComponent } from '@/core/utils'
|
||||
import type NavbarSettings from './NavbarSettings.vue'
|
||||
|
||||
let navbarSettingsVM: Vue & {
|
||||
toggle: () => void
|
||||
triggerElement: HTMLElement
|
||||
}
|
||||
let navbarSettingsVM: InstanceType<typeof NavbarSettings> | undefined
|
||||
export const setTriggerElement = (element: HTMLElement) => {
|
||||
if (!navbarSettingsVM) {
|
||||
return
|
||||
@ -14,9 +12,9 @@ export const loadNavbarSettings = async () => {
|
||||
if (navbarSettingsVM) {
|
||||
return false
|
||||
}
|
||||
const NavbarSettings = await import('./NavbarSettings.vue').then(m => m.default)
|
||||
navbarSettingsVM = mountVueComponent(NavbarSettings)
|
||||
document.body.insertAdjacentElement('beforeend', navbarSettingsVM.$el)
|
||||
const [el, vm] = mountVueComponent(await import('./NavbarSettings.vue'))
|
||||
navbarSettingsVM = vm
|
||||
document.body.insertAdjacentElement('beforeend', el)
|
||||
return true
|
||||
}
|
||||
export const toggleNavbarSettings = async () => {
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import { CustomNavbarItemInit } from '../custom-navbar-item'
|
||||
import type { CustomNavbarItemInit } from '../custom-navbar-item'
|
||||
|
||||
interface SimpleLinkConfig {
|
||||
name: string
|
||||
|
||||
@ -2,9 +2,10 @@
|
||||
<SubscriptionsList type="bangumi" :filter="filter"></SubscriptionsList>
|
||||
</template>
|
||||
<script lang="ts">
|
||||
import { defineComponent } from 'vue'
|
||||
import SubscriptionsList from './SubscriptionsList.vue'
|
||||
|
||||
export default Vue.extend({
|
||||
export default defineComponent({
|
||||
components: {
|
||||
SubscriptionsList,
|
||||
},
|
||||
|
||||
@ -2,9 +2,10 @@
|
||||
<SubscriptionsList type="cinema" :filter="filter"></SubscriptionsList>
|
||||
</template>
|
||||
<script lang="ts">
|
||||
import { defineComponent } from 'vue'
|
||||
import SubscriptionsList from './SubscriptionsList.vue'
|
||||
|
||||
export default Vue.extend({
|
||||
export default defineComponent({
|
||||
components: {
|
||||
SubscriptionsList,
|
||||
},
|
||||
|
||||
@ -1,21 +1,25 @@
|
||||
<template>
|
||||
<div class="navbar-subscriptions">
|
||||
<div ref="el" class="navbar-subscriptions">
|
||||
<TabControl ref="tabControl" :tabs="tabs" :more-link="moreLink">
|
||||
<template #header-item>
|
||||
<div class="navbar-subscriptions-filter">
|
||||
<VDropdown v-model="selectedFilter" round :items="filterItems" />
|
||||
<VDropdown v-model:value="selectedFilter" round :items="filterItems" />
|
||||
</div>
|
||||
</template>
|
||||
</TabControl>
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts">
|
||||
import { TabControl, VDropdown } from '@/ui'
|
||||
import { TabMapping, TabMappings } from '@/ui/tab-mapping'
|
||||
import type { Ref } from 'vue'
|
||||
import { defineComponent, defineAsyncComponent, ref } from 'vue'
|
||||
import { getUID } from '@/core/utils'
|
||||
import { popperMixin } from '../mixins'
|
||||
import { TabControl, VDropdown } from '@/ui'
|
||||
import type { TabMapping, TabMappings } from '@/ui/tab-mapping'
|
||||
|
||||
import { popupProps, usePopup } from '../mixins'
|
||||
import { SubscriptionTypes } from './subscriptions'
|
||||
import { SubscriptionStatus, SubscriptionStatusFilter } from './types'
|
||||
import type { SubscriptionStatusFilter } from './types'
|
||||
import { SubscriptionStatus } from './types'
|
||||
|
||||
const filterItems: {
|
||||
name: string
|
||||
@ -55,12 +59,16 @@ const filterItems: {
|
||||
displayName: '看过',
|
||||
},
|
||||
]
|
||||
export default Vue.extend({
|
||||
export default defineComponent({
|
||||
components: {
|
||||
TabControl,
|
||||
VDropdown,
|
||||
},
|
||||
mixins: [popperMixin],
|
||||
props: popupProps,
|
||||
setup: props => ({
|
||||
...usePopup(props),
|
||||
tabControl: ref(null) as Ref<InstanceType<typeof TabControl> | null>,
|
||||
}),
|
||||
data() {
|
||||
const uid = getUID()
|
||||
return {
|
||||
@ -81,7 +89,7 @@ export default Vue.extend({
|
||||
name: SubscriptionTypes.Bangumi,
|
||||
displayName: '追番',
|
||||
activeLink: `https://space.bilibili.com/${this.uid}/bangumi`,
|
||||
component: () => import('./BangumiSubscriptions.vue').then(m => m.default),
|
||||
component: defineAsyncComponent(() => import('./BangumiSubscriptions.vue')),
|
||||
propsData: {
|
||||
filter: this.selectedFilter.value,
|
||||
},
|
||||
@ -90,7 +98,7 @@ export default Vue.extend({
|
||||
name: SubscriptionTypes.Cinema,
|
||||
displayName: '追剧',
|
||||
activeLink: `https://space.bilibili.com/${this.uid}/cinema`,
|
||||
component: () => import('./CinemaSubscriptions.vue').then(m => m.default),
|
||||
component: defineAsyncComponent(() => import('./CinemaSubscriptions.vue')),
|
||||
propsData: {
|
||||
filter: this.selectedFilter.value,
|
||||
},
|
||||
|
||||
@ -41,22 +41,24 @@
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent } from 'vue'
|
||||
import { getJsonWithCredentials } from '@/core/ajax'
|
||||
import { getUID } from '@/core/utils'
|
||||
import { logError } from '@/core/utils/log'
|
||||
import { DpiImage, VLoading, VEmpty, VIcon, ScrollTrigger } from '@/ui'
|
||||
import { getJsonWithCredentials } from '@/core/ajax'
|
||||
import { DpiImage, ScrollTrigger, VEmpty, VIcon, VLoading } from '@/ui'
|
||||
|
||||
import { SubscriptionTypes } from './subscriptions'
|
||||
import { SubscriptionItem, SubscriptionStatus, SubscriptionStatusFilter } from './types'
|
||||
import { type SubscriptionItem, SubscriptionStatus, type SubscriptionStatusFilter } from './types'
|
||||
|
||||
const getStatusText = (status: SubscriptionStatus) => {
|
||||
switch (status) {
|
||||
case SubscriptionStatus.ToView:
|
||||
return '想看'
|
||||
case SubscriptionStatus.Viewed:
|
||||
return '看过'
|
||||
case SubscriptionStatus.Viewing:
|
||||
default:
|
||||
return '在看'
|
||||
case SubscriptionStatus.Viewed:
|
||||
return '看过'
|
||||
}
|
||||
}
|
||||
const subscriptionSorter = (a: SubscriptionItem, b: SubscriptionItem) => {
|
||||
@ -70,7 +72,7 @@ const subscriptionSorter = (a: SubscriptionItem, b: SubscriptionItem) => {
|
||||
}
|
||||
return statusA - statusB
|
||||
}
|
||||
export default Vue.extend({
|
||||
export default defineComponent({
|
||||
components: {
|
||||
DpiImage,
|
||||
VLoading,
|
||||
@ -92,7 +94,7 @@ export default Vue.extend({
|
||||
return {
|
||||
loading: true,
|
||||
hasMorePage: true,
|
||||
cards: [],
|
||||
cards: [] as any[],
|
||||
page: 1,
|
||||
}
|
||||
},
|
||||
@ -114,7 +116,7 @@ export default Vue.extend({
|
||||
const followStatus = filter.viewAll ? 0 : (filter.status as number)
|
||||
const params = new URLSearchParams({
|
||||
type: this.type !== SubscriptionTypes.Bangumi ? '2' : '1',
|
||||
pn: this.page,
|
||||
pn: this.page.toString(),
|
||||
ps: '16',
|
||||
vmid: getUID(),
|
||||
follow_status: followStatus.toString(),
|
||||
|
||||
@ -1,5 +1,7 @@
|
||||
import { defineAsyncComponent } from 'vue'
|
||||
import { getUID } from '@/core/utils'
|
||||
import { CustomNavbarItemInit } from '../custom-navbar-item'
|
||||
|
||||
import type { CustomNavbarItemInit } from '../custom-navbar-item'
|
||||
|
||||
export enum SubscriptionTypes {
|
||||
Bangumi = 'bangumi',
|
||||
@ -22,5 +24,5 @@ export const subscriptions: CustomNavbarItemInit = {
|
||||
|
||||
boundingWidth: 380,
|
||||
noPopupPadding: true,
|
||||
popupContent: () => import('./NavbarSubscriptions.vue').then(m => m.default),
|
||||
popupContent: defineAsyncComponent(() => import('./NavbarSubscriptions.vue')),
|
||||
}
|
||||
|
||||
@ -5,9 +5,10 @@
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts">
|
||||
import { defineComponent } from 'vue'
|
||||
import { VIcon } from '@/ui'
|
||||
|
||||
export default Vue.extend({
|
||||
export default defineComponent({
|
||||
components: {
|
||||
VIcon,
|
||||
},
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<div role="list" class="upload-popup">
|
||||
<div ref="el" role="list" class="upload-popup">
|
||||
<div role="listitem">
|
||||
<a target="_blank" href="https://member.bilibili.com/platform/upload/text/apply">专栏投稿</a>
|
||||
</div>
|
||||
@ -23,10 +23,12 @@
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts">
|
||||
import { popperMixin } from '../mixins'
|
||||
import { defineComponent } from 'vue'
|
||||
import { popupProps, usePopup } from '../mixins'
|
||||
|
||||
export default Vue.extend({
|
||||
mixins: [popperMixin],
|
||||
export default defineComponent({
|
||||
props: popupProps,
|
||||
setup: usePopup,
|
||||
})
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
|
||||
@ -1,12 +1,13 @@
|
||||
import { CustomNavbarItemInit } from '../custom-navbar-item'
|
||||
import { defineAsyncComponent } from 'vue'
|
||||
import type { CustomNavbarItemInit } from '../custom-navbar-item'
|
||||
|
||||
export const upload: CustomNavbarItemInit = {
|
||||
name: 'upload',
|
||||
displayName: '投稿',
|
||||
content: () => import('./NavbarUpload.vue').then(m => m.default),
|
||||
content: defineAsyncComponent(() => import('./NavbarUpload.vue')),
|
||||
|
||||
touch: true,
|
||||
href: 'https://member.bilibili.com/platform/upload/video/frame',
|
||||
|
||||
popupContent: () => import('./UploadPopup.vue').then(m => m.default),
|
||||
popupContent: defineAsyncComponent(() => import('./UploadPopup.vue')),
|
||||
}
|
||||
|
||||
@ -6,13 +6,14 @@
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent } from 'vue'
|
||||
import { getUserInfo } from '@/core/user-info'
|
||||
import { getDpiSourceSet } from '@/core/utils'
|
||||
import { EmptyImageUrl } from '@/core/utils/constants'
|
||||
|
||||
const noFaceUrl = '//static.hdslb.com/images/member/noface.gif'
|
||||
const notLoginFaceUrl = 'https://static.hdslb.com/images/akari.jpg'
|
||||
export default Vue.extend({
|
||||
export default defineComponent({
|
||||
name: 'UserFace',
|
||||
data() {
|
||||
return {
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<div class="user-info-panel">
|
||||
<div ref="el" class="user-info-panel">
|
||||
<div v-if="isLogin && userInfo.isLogin === true" class="logged-in">
|
||||
<a class="name" target="_blank" href="https://space.bilibili.com/">{{ userInfo.uname }}</a>
|
||||
<a class="type" target="_blank" href="https://account.bilibili.com/account/big">{{
|
||||
@ -83,7 +83,7 @@
|
||||
:href="'https://space.bilibili.com/' + userInfo.mid + '/fans/follow'"
|
||||
target="_blank"
|
||||
>
|
||||
<div class="stats-number">{{ stat.following | count }}</div>
|
||||
<div class="stats-number">{{ count(stat.following) }}</div>
|
||||
关注
|
||||
</a>
|
||||
<a
|
||||
@ -91,7 +91,7 @@
|
||||
:href="'https://space.bilibili.com/' + userInfo.mid + '/fans/fans'"
|
||||
target="_blank"
|
||||
>
|
||||
<div class="stats-number">{{ stat.follower | count }}</div>
|
||||
<div class="stats-number">{{ count(stat.follower) }}</div>
|
||||
粉丝
|
||||
</a>
|
||||
<a
|
||||
@ -99,7 +99,7 @@
|
||||
:href="'https://space.bilibili.com/' + userInfo.mid + '/dynamic'"
|
||||
target="_blank"
|
||||
>
|
||||
<div class="stats-number">{{ stat.dynamic_count | count }}</div>
|
||||
<div class="stats-number">{{ count(stat.dynamic_count) }}</div>
|
||||
动态
|
||||
</a>
|
||||
</div>
|
||||
@ -155,26 +155,26 @@
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { getUID, getCsrf, formData } from '@/core/utils'
|
||||
import { formatCount } from '@/core/utils/formatters'
|
||||
import { logError } from '@/core/utils/log'
|
||||
import { defineComponent } from 'vue'
|
||||
import { getJsonWithCredentials, postTextWithCredentials } from '@/core/ajax'
|
||||
import { getUserInfo } from '@/core/user-info'
|
||||
import { popperMixin } from '../mixins'
|
||||
import { formData, getCsrf, getUID } from '@/core/utils'
|
||||
import { formatCount } from '@/core/utils/formatters'
|
||||
import { logError } from '@/core/utils/log'
|
||||
|
||||
import { popupProps, usePopup } from '../mixins'
|
||||
|
||||
type PrivilegeType = 1 | 2
|
||||
export default Vue.extend({
|
||||
export default defineComponent({
|
||||
components: {
|
||||
VIcon: coreApis.ui.VIcon,
|
||||
},
|
||||
filters: {
|
||||
count: formatCount,
|
||||
},
|
||||
mixins: [popperMixin],
|
||||
props: popupProps,
|
||||
setup: usePopup,
|
||||
data() {
|
||||
return {
|
||||
userInfo: {},
|
||||
stat: {},
|
||||
userInfo: {} as any,
|
||||
stat: {} as any,
|
||||
isLogin: Boolean(getUID()),
|
||||
privileges: {
|
||||
bCoin: {
|
||||
@ -189,7 +189,7 @@ export default Vue.extend({
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
level() {
|
||||
level(): { icon: string; colored?: boolean } {
|
||||
const baseLevel = `lv${this.userInfo.level_info.current_level}`
|
||||
if (this.userInfo.is_senior_member) {
|
||||
return {
|
||||
@ -201,7 +201,14 @@ export default Vue.extend({
|
||||
icon: baseLevel,
|
||||
}
|
||||
},
|
||||
userType() {
|
||||
userType():
|
||||
| '未登录'
|
||||
| '注册会员'
|
||||
| '正式会员'
|
||||
| '小会员'
|
||||
| '大会员'
|
||||
| '年度小会员'
|
||||
| '年度大会员' {
|
||||
if (!this.userInfo.isLogin) {
|
||||
return '未登录'
|
||||
}
|
||||
@ -218,7 +225,7 @@ export default Vue.extend({
|
||||
}
|
||||
return '正式会员'
|
||||
},
|
||||
levelProgressStyle() {
|
||||
levelProgressStyle(): Record<string, string> {
|
||||
if (!this.userInfo.isLogin) {
|
||||
return {}
|
||||
}
|
||||
@ -248,6 +255,7 @@ export default Vue.extend({
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
count: formatCount,
|
||||
async privilegeReceive(type: PrivilegeType) {
|
||||
const typeMapping = {
|
||||
1: 'bCoin',
|
||||
|
||||
@ -1,15 +1,17 @@
|
||||
import { defineAsyncComponent } from 'vue'
|
||||
import { getUID } from '@/core/utils'
|
||||
import { CustomNavbarItemInit } from '../custom-navbar-item'
|
||||
|
||||
import type { CustomNavbarItemInit } from '../custom-navbar-item'
|
||||
|
||||
export const userInfo: CustomNavbarItemInit = {
|
||||
name: 'userInfo',
|
||||
displayName: '个人信息',
|
||||
content: () => import('./UserFace.vue').then(m => m.default),
|
||||
content: defineAsyncComponent(() => import('./UserFace.vue')),
|
||||
|
||||
href: getUID() ? 'https://space.bilibili.com' : null,
|
||||
touch: true,
|
||||
|
||||
popupContent: () => import('./UserInfoPopup.vue').then(m => m.default),
|
||||
popupContent: defineAsyncComponent(() => import('./UserInfoPopup.vue')),
|
||||
lazy: false,
|
||||
noPopupPadding: true,
|
||||
boundingWidth: 240,
|
||||
|
||||
@ -1,9 +1,9 @@
|
||||
<template>
|
||||
<div class="watchlater-list">
|
||||
<div ref="el" class="watchlater-list">
|
||||
<div class="header">
|
||||
<div class="watchlater-list-summary">共 {{ filteredCards.length }} 个</div>
|
||||
<div class="search">
|
||||
<TextBox v-model="search" linear placeholder="搜索"></TextBox>
|
||||
<TextBox v-model:text="search" linear placeholder="搜索"></TextBox>
|
||||
</div>
|
||||
<a
|
||||
class="operation"
|
||||
@ -51,16 +51,14 @@
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts">
|
||||
import { defineComponent } from 'vue'
|
||||
import type { RawWatchlaterItem } from '@/components/video/watchlater'
|
||||
import { getWatchlaterList, toggleWatchlater, watchlaterList } from '@/components/video/watchlater'
|
||||
import { getComponentSettings } from '@/core/settings'
|
||||
import { formatDuration } from '@/core/utils/formatters'
|
||||
import {
|
||||
watchlaterList,
|
||||
getWatchlaterList,
|
||||
RawWatchlaterItem,
|
||||
toggleWatchlater,
|
||||
} from '@/components/video/watchlater'
|
||||
import { VLoading, VEmpty, TextBox, VButton, VIcon, DpiImage } from '@/ui'
|
||||
import { popperMixin } from '../mixins'
|
||||
import { DpiImage, TextBox, VButton, VEmpty, VIcon, VLoading } from '@/ui'
|
||||
|
||||
import { popupProps, usePopup } from '../mixins'
|
||||
|
||||
interface WatchlaterCard {
|
||||
aid: number
|
||||
@ -74,7 +72,15 @@ interface WatchlaterCard {
|
||||
upFaceUrl: string
|
||||
upID: number
|
||||
}
|
||||
export default Vue.extend({
|
||||
function updateFilteredCards(this: InstanceType<typeof ThisComponent>) {
|
||||
const search = this.search.toLowerCase()
|
||||
const cardsList = this.$el.querySelector('.watchlater-list-content') as HTMLElement
|
||||
cardsList.scrollTo(0, 0)
|
||||
this.filteredCards = (this.cards as WatchlaterCard[]).filter(
|
||||
card => card.title.toLowerCase().includes(search) || card.upName.toLowerCase().includes(search),
|
||||
)
|
||||
}
|
||||
const ThisComponent = defineComponent({
|
||||
components: {
|
||||
VLoading,
|
||||
VEmpty,
|
||||
@ -83,14 +89,15 @@ export default Vue.extend({
|
||||
VIcon,
|
||||
DpiImage,
|
||||
},
|
||||
mixins: [popperMixin],
|
||||
props: popupProps,
|
||||
setup: usePopup,
|
||||
data() {
|
||||
const redirect = getComponentSettings('watchlaterRedirect')
|
||||
return {
|
||||
watchlaterList,
|
||||
loading: true,
|
||||
cards: [],
|
||||
filteredCards: [],
|
||||
cards: [] as WatchlaterCard[],
|
||||
filteredCards: [] as WatchlaterCard[],
|
||||
search: '',
|
||||
redirect: redirect.enabled && redirect.options.navbar,
|
||||
}
|
||||
@ -160,17 +167,10 @@ export default Vue.extend({
|
||||
this.cards.splice(index, 1)
|
||||
await this.toggleWatchlater(aid)
|
||||
},
|
||||
updateFilteredCards: lodash.debounce(function updateFilteredCards() {
|
||||
const search = this.search.toLowerCase()
|
||||
const cardsList = this.$el.querySelector('.watchlater-list-content') as HTMLElement
|
||||
cardsList.scrollTo(0, 0)
|
||||
this.filteredCards = (this.cards as WatchlaterCard[]).filter(
|
||||
card =>
|
||||
card.title.toLowerCase().includes(search) || card.upName.toLowerCase().includes(search),
|
||||
)
|
||||
}, 100),
|
||||
updateFilteredCards: lodash.debounce(updateFilteredCards, 100) as unknown as () => void,
|
||||
},
|
||||
})
|
||||
export default ThisComponent
|
||||
</script>
|
||||
<style lang="scss">
|
||||
@import 'common';
|
||||
@ -250,7 +250,7 @@ export default Vue.extend({
|
||||
padding: 0 12px;
|
||||
padding-bottom: 12px;
|
||||
.watchlater-card {
|
||||
&.cards-enter,
|
||||
&.cards-enter-from,
|
||||
&.cards-leave-to {
|
||||
opacity: 0;
|
||||
transform: translateY(-16px) scale(0.9);
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
import { CustomNavbarItemInit } from '../custom-navbar-item'
|
||||
import { defineAsyncComponent } from 'vue'
|
||||
import type { CustomNavbarItemInit } from '../custom-navbar-item'
|
||||
|
||||
export const watchlater: CustomNavbarItemInit = {
|
||||
name: 'watchlater',
|
||||
@ -12,5 +13,5 @@ export const watchlater: CustomNavbarItemInit = {
|
||||
|
||||
boundingWidth: 380,
|
||||
noPopupPadding: true,
|
||||
popupContent: () => import('./NavbarWatchlater.vue').then(m => m.default),
|
||||
popupContent: defineAsyncComponent(() => import('./NavbarWatchlater.vue')),
|
||||
}
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
import { defineComponentMetadata } from '@/components/define'
|
||||
import { LifeCycleEventTypes } from '@/core/life-cycle'
|
||||
|
||||
import { darkExcludes } from '../dark-urls'
|
||||
|
||||
export const component = defineComponentMetadata({
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
import { defineComponentMetadata } from '@/components/define'
|
||||
|
||||
import { darkExcludes } from './dark-urls'
|
||||
|
||||
const changeDelay = 200
|
||||
|
||||
@ -1,11 +1,10 @@
|
||||
import {
|
||||
defineComponentMetadata,
|
||||
defineOptionsMetadata,
|
||||
OptionsOfMetadata,
|
||||
} from '@/components/define'
|
||||
import type { OptionsOfMetadata } from '@/components/define'
|
||||
import { defineComponentMetadata, defineOptionsMetadata } from '@/components/define'
|
||||
import { fullyLoaded } from '@/core/life-cycle'
|
||||
import { ComponentSettings, getComponentSettings } from '@/core/settings'
|
||||
import { Range } from '@/ui/range'
|
||||
import type { ComponentSettings } from '@/core/settings'
|
||||
import { getComponentSettings } from '@/core/settings'
|
||||
import type { Range } from '@/ui/range'
|
||||
|
||||
import { darkExcludes } from '../dark-urls'
|
||||
|
||||
class ScheduleTime {
|
||||
|
||||
@ -4,7 +4,9 @@
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts">
|
||||
export default Vue.extend({})
|
||||
import { defineComponent } from 'vue'
|
||||
|
||||
export default defineComponent({})
|
||||
</script>
|
||||
<style lang="scss">
|
||||
@import 'common';
|
||||
|
||||
@ -68,11 +68,12 @@
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts">
|
||||
import { defineComponent, ref, type Ref } from 'vue'
|
||||
import type { SortableEvent } from 'sortablejs'
|
||||
import { SortableJSLibrary } from '@/core/runtime-library'
|
||||
import { ascendingSort } from '@/core/utils/sort'
|
||||
import { VLoading, VIcon, VButton } from '@/ui'
|
||||
import { FreshLayoutItem, FreshLayoutItemSettings } from './layouts/fresh-layout-item'
|
||||
import type { FreshLayoutItem, FreshLayoutItemSettings } from './layouts/fresh-layout-item'
|
||||
import { layouts } from './layouts/layouts'
|
||||
import { freshHomeOptions } from './options'
|
||||
|
||||
@ -81,8 +82,11 @@ interface SortItem {
|
||||
layoutSettings: FreshLayoutItemSettings
|
||||
}
|
||||
|
||||
export default Vue.extend({
|
||||
export default defineComponent({
|
||||
components: { VLoading, VIcon, VButton },
|
||||
setup: () => ({
|
||||
sortList: ref(null) satisfies Ref<HTMLElement | null>,
|
||||
}),
|
||||
data() {
|
||||
return {
|
||||
loaded: false,
|
||||
@ -91,7 +95,7 @@ export default Vue.extend({
|
||||
}
|
||||
},
|
||||
async mounted() {
|
||||
const list: HTMLElement = this.$refs.sortList
|
||||
const list = this.sortList
|
||||
const Sortable = await SortableJSLibrary
|
||||
console.log({ list })
|
||||
Sortable.create(list, {
|
||||
|
||||
@ -8,11 +8,12 @@
|
||||
</HomeRedesignBase>
|
||||
</template>
|
||||
<script lang="ts">
|
||||
import { defineComponent } from 'vue'
|
||||
import HomeRedesignBase from '../HomeRedesignBase.vue'
|
||||
import FreshLayoutItem from './FreshLayoutItem.vue'
|
||||
import { layouts } from './layouts/layouts'
|
||||
|
||||
export default Vue.extend({
|
||||
export default defineComponent({
|
||||
components: {
|
||||
HomeRedesignBase,
|
||||
FreshLayoutItem,
|
||||
|
||||
@ -20,18 +20,23 @@
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts">
|
||||
import type { PropType } from 'vue'
|
||||
import { defineComponent } from 'vue'
|
||||
import { freshHomeOptions } from './options'
|
||||
import type { FreshLayoutItem, FreshLayoutItemSettings } from './layouts/fresh-layout-item'
|
||||
|
||||
export default Vue.extend({
|
||||
export default defineComponent({
|
||||
props: {
|
||||
item: {
|
||||
required: true,
|
||||
type: Object,
|
||||
type: Object as PropType<FreshLayoutItem>,
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
options: freshHomeOptions.layoutOptions[this.item.name] ?? {},
|
||||
options: (freshHomeOptions.layoutOptions[this.item.name] ?? {
|
||||
linebreak: false,
|
||||
}) as FreshLayoutItemSettings | { linebreak: boolean; order?: number; hidden?: boolean },
|
||||
}
|
||||
},
|
||||
})
|
||||
|
||||
@ -1,15 +1,22 @@
|
||||
<template>
|
||||
<div class="fresh-home-video-card-wrapper">
|
||||
<VideoCard v-bind="$attrs" orientation="vertical" />
|
||||
<VideoCard v-bind="attrs" orientation="vertical" />
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts">
|
||||
import { defineComponent } from 'vue'
|
||||
import VideoCard from '@/components/feeds/VideoCard.vue'
|
||||
|
||||
export default Vue.extend({
|
||||
export default defineComponent({
|
||||
components: {
|
||||
VideoCard,
|
||||
},
|
||||
inheritAttrs: false,
|
||||
computed: {
|
||||
attrs(): any {
|
||||
return this.$attrs
|
||||
},
|
||||
},
|
||||
})
|
||||
</script>
|
||||
<style lang="scss">
|
||||
|
||||
@ -13,13 +13,18 @@
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts">
|
||||
import { VEmpty, VLoading } from '@/ui'
|
||||
import type { Ref, PropType } from 'vue'
|
||||
import { defineComponent, ref } from 'vue'
|
||||
|
||||
import type { VideoCard } from '@/components/feeds/video-card'
|
||||
import { enableHorizontalScroll } from '@/core/horizontal-scroll'
|
||||
import { addComponentListener } from '@/core/settings'
|
||||
import VideoCardWrapper from './VideoCardWrapper.vue'
|
||||
import { setupScrollMask, cleanUpScrollMask } from './scroll-mask'
|
||||
import { VEmpty, VLoading } from '@/ui'
|
||||
|
||||
export default Vue.extend({
|
||||
import { cleanUpScrollMask, setupScrollMask } from './scroll-mask'
|
||||
import VideoCardWrapper from './VideoCardWrapper.vue'
|
||||
|
||||
export default defineComponent({
|
||||
components: {
|
||||
VEmpty,
|
||||
VLoading,
|
||||
@ -27,7 +32,7 @@ export default Vue.extend({
|
||||
},
|
||||
props: {
|
||||
videos: {
|
||||
type: Array,
|
||||
type: Array as PropType<VideoCard[]>,
|
||||
default: () => [],
|
||||
},
|
||||
loading: {
|
||||
@ -35,21 +40,28 @@ export default Vue.extend({
|
||||
default: true,
|
||||
},
|
||||
},
|
||||
setup: () => ({
|
||||
content: ref(null) as Ref<HTMLDivElement | null>,
|
||||
cards: ref(null) as Ref<InstanceType<typeof VideoCardWrapper>[] | null>,
|
||||
}),
|
||||
watch: {
|
||||
videos() {
|
||||
videos: {
|
||||
handler() {
|
||||
this.setupIntersection()
|
||||
},
|
||||
loaded() {
|
||||
if (this.loaded) {
|
||||
deep: true,
|
||||
},
|
||||
loaded(value) {
|
||||
if (value) {
|
||||
this.setupIntersection()
|
||||
}
|
||||
},
|
||||
},
|
||||
beforeDestroy() {
|
||||
beforeUnmount() {
|
||||
cleanUpScrollMask(this.$el)
|
||||
},
|
||||
mounted() {
|
||||
const container = this.$refs.content as HTMLElement
|
||||
const container = this.content as HTMLElement
|
||||
let cancel: () => void
|
||||
addComponentListener(
|
||||
'freshHome.horizontalWheelScroll',
|
||||
@ -68,11 +80,11 @@ export default Vue.extend({
|
||||
await this.$nextTick()
|
||||
setupScrollMask({
|
||||
container: this.$el,
|
||||
items: this.$refs.cards.map((c: Vue) => c.$el),
|
||||
items: this.cards.map(c => c.$el),
|
||||
})
|
||||
},
|
||||
offsetPage(offset: number) {
|
||||
const container = this.$refs.content as HTMLElement
|
||||
const container = this.content as HTMLElement
|
||||
const style = getComputedStyle(container)
|
||||
const containerWidth = container.clientWidth
|
||||
const wrapperWidth =
|
||||
|
||||
@ -19,9 +19,8 @@ export const component = defineComponentMetadata({
|
||||
true,
|
||||
)
|
||||
contentLoaded(async () => {
|
||||
const FreshHome = await import('./FreshHome.vue')
|
||||
const freshHome = mountVueComponent(FreshHome)
|
||||
document.body.appendChild(freshHome.$el)
|
||||
const [el] = mountVueComponent(await import('./FreshHome.vue'))
|
||||
document.body.appendChild(el)
|
||||
})
|
||||
},
|
||||
options: freshHomeOptionsMetadata,
|
||||
|
||||
@ -22,11 +22,13 @@
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts">
|
||||
import { defineComponent } from 'vue'
|
||||
import { addData } from '@/plugins/data'
|
||||
import { VButton, VIcon } from '@/ui'
|
||||
|
||||
import BlackRoomColored from './black-room.svg'
|
||||
import LiveColored from './live.svg'
|
||||
import TopicColored from './topic.svg'
|
||||
import BlackRoomColored from './black-room.svg'
|
||||
|
||||
addData('ui.icons', (icons: Record<string, string>) => {
|
||||
icons['live-colored'] = LiveColored
|
||||
@ -54,7 +56,7 @@ const others = [
|
||||
icon: 'black-room-colored',
|
||||
},
|
||||
]
|
||||
export default Vue.extend({
|
||||
export default defineComponent({
|
||||
components: {
|
||||
VButton,
|
||||
VIcon,
|
||||
|
||||
@ -1,7 +1,8 @@
|
||||
import { FreshLayoutItem } from '../fresh-layout-item'
|
||||
import { defineAsyncComponent } from 'vue'
|
||||
import type { FreshLayoutItem } from '../fresh-layout-item'
|
||||
|
||||
export const areas: FreshLayoutItem = {
|
||||
name: 'areas',
|
||||
displayName: '栏目',
|
||||
component: () => import('./Areas.vue').then(m => m.default),
|
||||
component: defineAsyncComponent(() => import('./Areas.vue')),
|
||||
}
|
||||
|
||||
@ -52,10 +52,12 @@
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts">
|
||||
import { VButton, VIcon, DpiImage } from '@/ui'
|
||||
import { getBlackboards } from './api'
|
||||
import { defineComponent } from 'vue'
|
||||
import { DpiImage, VButton, VIcon } from '@/ui'
|
||||
|
||||
export default Vue.extend({
|
||||
import { type Blackboard, getBlackboards } from './api'
|
||||
|
||||
export default defineComponent({
|
||||
components: {
|
||||
VButton,
|
||||
VIcon,
|
||||
@ -63,12 +65,12 @@ export default Vue.extend({
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
blackboards: [],
|
||||
blackboards: [] as Blackboard[],
|
||||
timer: 0,
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
cardsContainer() {
|
||||
cardsContainer(): Element | null {
|
||||
return this.$el.querySelector('.fresh-home-blackboard-cards')
|
||||
},
|
||||
},
|
||||
@ -79,7 +81,7 @@ export default Vue.extend({
|
||||
mounted() {
|
||||
this.createTimer()
|
||||
},
|
||||
beforeDestroy() {
|
||||
beforeUnmount() {
|
||||
this.destroyTimer()
|
||||
},
|
||||
methods: {
|
||||
|
||||
@ -1,7 +1,8 @@
|
||||
import { FreshLayoutItem } from '../fresh-layout-item'
|
||||
import { defineAsyncComponent } from 'vue'
|
||||
import type { FreshLayoutItem } from '../fresh-layout-item'
|
||||
|
||||
export const blackboard: FreshLayoutItem = {
|
||||
name: 'blackboard',
|
||||
displayName: '活动',
|
||||
component: () => import('./Blackboard.vue').then(m => m.default),
|
||||
component: defineAsyncComponent(() => import('./Blackboard.vue')),
|
||||
}
|
||||
|
||||
@ -4,7 +4,7 @@
|
||||
<div class="fresh-home-header-title">分区</div>
|
||||
<div class="fresh-home-header-center-area">
|
||||
<div class="fresh-home-header-tabs">
|
||||
<div ref="tabs" class="default-tabs">
|
||||
<div ref="tabsRef" class="default-tabs">
|
||||
<div
|
||||
v-for="t of tabs"
|
||||
:key="t.name"
|
||||
@ -35,13 +35,15 @@
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts">
|
||||
import { ArrayContent } from '@/core/common-types'
|
||||
import type { Ref } from 'vue'
|
||||
import { defineComponent, ref } from 'vue'
|
||||
import type { TabType } from './categories'
|
||||
import { Reorder } from '@/core/reorder'
|
||||
import { ascendingSort } from '@/core/utils/sort'
|
||||
import { VButton, VIcon } from '@/ui'
|
||||
import { freshHomeOptions } from '../../options'
|
||||
import { supportedCategories } from './filter'
|
||||
import { getContent } from './content/content'
|
||||
import { supportedCategories } from './filter'
|
||||
|
||||
const tabs = Object.entries(supportedCategories).map(([name, category]) => ({
|
||||
id: category.code as number,
|
||||
@ -51,12 +53,14 @@ const tabs = Object.entries(supportedCategories).map(([name, category]) => ({
|
||||
href: category.link,
|
||||
order: 0,
|
||||
}))
|
||||
type TabType = ArrayContent<typeof tabs>
|
||||
export default Vue.extend({
|
||||
export default defineComponent({
|
||||
components: {
|
||||
VButton,
|
||||
VIcon,
|
||||
},
|
||||
setup: () => ({
|
||||
tabsRef: ref(null) as Ref<HTMLDivElement | null>,
|
||||
}),
|
||||
data() {
|
||||
const orderMap = (freshHomeOptions.categoriesOrder ?? {}) as Record<string, number>
|
||||
const orderedTabs = [...tabs].sort(ascendingSort(t => orderMap[t.name]))
|
||||
@ -69,7 +73,7 @@ export default Vue.extend({
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
const tabsContainer = this.$refs.tabs as HTMLElement
|
||||
const tabsContainer = this.tabsRef as HTMLElement
|
||||
const reorder = new Reorder(tabsContainer)
|
||||
reorder.addEventListener('reorder', ({ detail: items }) => {
|
||||
const newOrder = Object.fromEntries(
|
||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user