Merge pull request #4277 from timongh/update-to-vue3

Update to vue3
This commit is contained in:
Grant Howard 2023-11-08 07:57:39 +08:00 committed by GitHub
commit 88953d7052
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
488 changed files with 7358 additions and 7965 deletions

View File

@ -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
View File

@ -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"

View File

@ -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`

View File

@ -1,4 +1,4 @@
import { readFileSync, existsSync } from 'fs'
import { existsSync, readFileSync } from 'fs'
interface DevServerConfig {
port?: number

View File

@ -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 => {

View File

@ -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 => {

View File

@ -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) => {

View File

@ -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 => {

View File

@ -1,4 +1,4 @@
import { Stats } from 'webpack'
import type { Stats } from 'webpack'
export const defaultWatcherHandler = (
initCallback: (result: Stats) => void,

View File

@ -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)

View File

@ -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
View 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

File diff suppressed because it is too large Load Diff

View File

@ -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})()));

View File

@ -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 () => {

View File

@ -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

View File

@ -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,

View File

@ -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({

View File

@ -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) {

View File

@ -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,

View File

@ -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({

View File

@ -1,4 +1,4 @@
import { OptionsOfMetadata, defineOptionsMetadata } from '@/components/define'
import { type OptionsOfMetadata, defineOptionsMetadata } from '@/components/define'
export const options = defineOptionsMetadata({
types: {

View File

@ -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',

View File

@ -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')

View File

@ -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,

View File

@ -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'],
})

View File

@ -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)
},

View File

@ -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)
},

View File

@ -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'
/**

View File

@ -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,
},

View File

@ -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]+)/),
},
})

View File

@ -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')),

View File

@ -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>

View File

@ -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(

View File

@ -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">

View File

@ -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,

View File

@ -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'

View File

@ -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

View File

@ -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()
}

View File

@ -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)
},
},
})

View File

@ -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);

View File

@ -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')),
}

View File

@ -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

View File

@ -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,
}

View File

@ -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;

View File

@ -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;

View File

@ -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);

View File

@ -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);

View File

@ -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,
}
}

View File

@ -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,
},

View File

@ -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) => ({

View File

@ -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);

View File

@ -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')),
}

View File

@ -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'

View File

@ -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,

View File

@ -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')),
}

View File

@ -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>

View File

@ -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,

View File

@ -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 }) => {

View File

@ -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,

View File

@ -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/',
}

View File

@ -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 => {

View File

@ -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,
}

View File

@ -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 }
}

View File

@ -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,

View File

@ -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')),
}

View File

@ -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,
},

View File

@ -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,

View File

@ -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)
}
},

View File

@ -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';

View File

@ -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 () => {

View File

@ -1,4 +1,4 @@
import { CustomNavbarItemInit } from '../custom-navbar-item'
import type { CustomNavbarItemInit } from '../custom-navbar-item'
interface SimpleLinkConfig {
name: string

View File

@ -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,
},

View File

@ -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,
},

View File

@ -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,
},

View File

@ -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(),

View File

@ -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')),
}

View File

@ -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,
},

View File

@ -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>

View File

@ -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')),
}

View File

@ -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 {

View File

@ -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',

View File

@ -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,

View File

@ -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);

View File

@ -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')),
}

View File

@ -1,5 +1,6 @@
import { defineComponentMetadata } from '@/components/define'
import { LifeCycleEventTypes } from '@/core/life-cycle'
import { darkExcludes } from '../dark-urls'
export const component = defineComponentMetadata({

View File

@ -1,4 +1,5 @@
import { defineComponentMetadata } from '@/components/define'
import { darkExcludes } from './dark-urls'
const changeDelay = 200

View File

@ -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 {

View File

@ -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';

View File

@ -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, {

View File

@ -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,

View File

@ -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 },
}
},
})

View File

@ -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">

View File

@ -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 =

View File

@ -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,

View File

@ -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,

View File

@ -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')),
}

View File

@ -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: {

View File

@ -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')),
}

View File

@ -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