diff --git a/packages/mobile/src/components/PostDetail.vue b/packages/mobile/src/components/PostDetail.vue
index fbfc842..49006f0 100644
--- a/packages/mobile/src/components/PostDetail.vue
+++ b/packages/mobile/src/components/PostDetail.vue
@@ -77,6 +77,8 @@
+
高赞回复
@@ -305,10 +307,7 @@ export default {
return []
},
topReply() {
- return this.post.replyList
- .filter(v => v.thankCount >= this.config.topReplyLoveMinCount)
- .sort((a, b) => b.thankCount - a.thankCount)
- .slice(0, this.config.topReplyCount)
+ return this.post.topReplyList
},
replyList() {
// console.log('this.post.nestedReplies', this.post.nestedReplies)
@@ -358,6 +357,7 @@ export default {
}
},
'post.headerTemplate'(n, o) {
+ nextTick(this.mountTopicTipBox)
let mountEl = document.querySelector('.main-wrapper .post-wrapper .html-wrapper .header')
if (mountEl) {
this.showOpTag = true
@@ -394,6 +394,7 @@ export default {
},
mounted() {
this.debounceScroll = debounce(this.scroll, 300, false)
+ this.mountTopicTipBox()
if (this.isLogin) {
const observer = new IntersectionObserver(
([e]) => e.target.toggleAttribute('stuck', e.intersectionRatio < 1),
@@ -542,6 +543,16 @@ export default {
collapseTopReplyList() {
$(this.$refs.topReply).slideToggle('fast')
},
+ mountTopicTipBox() {
+ if (!this.isPost) return
+
+ const mountEl = this.$refs.topicTipBox
+ const tipBox = document.querySelector('#topic-tip-box')
+ if (!mountEl || !tipBox) return
+
+ mountEl.append(tipBox)
+ tipBox.style.display = ''
+ },
goBottom() {
this.isSticky = false
setTimeout(() => {
@@ -675,6 +686,16 @@ export default {
}
}
+ .native-topic-tip {
+ width: 100%;
+
+ :deep(#topic-tip-box) {
+ display: block;
+ width: 100%;
+ box-sizing: border-box;
+ }
+ }
+
.loading-wrapper {
height: 20rem;
display: flex;
diff --git a/packages/mobile/src/main.ts b/packages/mobile/src/main.ts
index d7bfcef..7bf23ac 100644
--- a/packages/mobile/src/main.ts
+++ b/packages/mobile/src/main.ts
@@ -1,5 +1,3 @@
-import {createApp} from 'vue';
-import App from './pages/App.vue';
import {GM_notification} from "gmApi"
import './global.d.ts'
import {PageType, Post, Reply} from "@v2next/core/types"
@@ -7,23 +5,50 @@ import {DefaultUser, functions, getDefaultConfig, getDefaultPost} from "@v2next/
let isMobile = !document.querySelector('#Rightbar');
+function isReplyCell(cell: Element) {
+ return /^r_\d+$/.test(cell.id)
+}
+
function findReplyBoxMobile(boxs: any) {
- // 根据内容的元素属性特征定位回复区域
+ let fallbackBox
+
+ // 根据回复 cell 的稳定 id 特征定位回复区域
for (let i = 1; i < boxs.length; i++) {
const box = boxs[i];
const cells = box.querySelectorAll ? box.querySelectorAll('.cell') : [];
-
- if (cells && cells.length > 0) {
+
+ if (!cells || !cells.length || box.id === 'topic-tip-box' || box.id === 'reply-box') {
+ continue
+ }
+
+ if (Array.from(cells).some((cell: any) => isReplyCell(cell))) {
+ return box;
+ }
+
+ if (!fallbackBox) {
const firstCell = cells[0];
- if (firstCell.querySelector?.('.snow')) {
- return box;
- }
+ if (firstCell.querySelector?.('.snow')) {
+ fallbackBox = box;
+ } else if (box.querySelector?.('.page_normal')) {
+ fallbackBox = box;
+ }
}
}
-
+
// 回退逻辑
console.warn('[V2Next] 无法智能检测回复区域,使用默认位置');
- return boxs[1];
+ return fallbackBox;
+}
+
+function applyNestedReplyCounts(post: Post) {
+ const replyByFloor = new Map(post.replyList.map(reply => [Number(reply.floor), reply]))
+
+ post.nestedReplies.forEach((nestedReply) => {
+ const reply = replyByFloor.get(Number(nestedReply.floor))
+ if (reply) {
+ reply.replyCount = nestedReply.replyCount
+ }
+ })
}
let $section = document.createElement('section')
@@ -227,7 +252,7 @@ function run() {
box = findReplyBoxMobile(boxs)
}
- let cells: any = box.querySelectorAll('.cell')
+ let cells: any = box?.querySelectorAll('.cell')
if (cells && cells.length) {
// post.fr = boxs[1].querySelector('.inner')!.innerHTML
@@ -239,19 +264,17 @@ function run() {
let repliesMap: any[] = []
//如果第二条有id,就说明是第二条是回复。只有一页回复
- if (cells[1].id) {
+ if (cells[1] && isReplyCell(cells[1])) {
repliesMap.push({i: pageNo, replyList: this.parsePageReplies(cells.slice(1))})
let replyList = functions.getAllReply(repliesMap)
- post.replyList = replyList
- post.replyCount = replyList.length
- post.allReplyUsers = Array.from(new Set(replyList.map((v: any) => v.username)))
- post.nestedReplies = functions.createNestedList(replyList)
- post.nestedRedundReplies = functions.createNestedRedundantList(replyList)
- post.nestedReplies.map((v) => {
- post.replyList[Number(v.floor) - 1].replyCount = v.replyCount
- })
+ functions.createList(post, replyList)
+ applyNestedReplyCounts(post)
return post
} else {
+ if (!cells[1]) {
+ return post
+ }
+
let promiseList: any = []
// console.log(this.current.repliesMap)
return new Promise((resolve, reject) => {
@@ -270,20 +293,15 @@ function run() {
results.filter((result) => result.status === "fulfilled").map(v => repliesMap.push(v.value))
let replyList = functions.getAllReply(repliesMap)
- post.replyList = replyList
- post.replyCount = replyList.length
- post.allReplyUsers = Array.from(new Set(replyList.map((v: any) => v.username)))
- post.nestedReplies = functions.createNestedList(replyList)
- post.nestedRedundReplies = functions.createNestedRedundantList(replyList)
- post.nestedReplies.map((v) => {
- post.replyList[Number(v.floor) - 1].replyCount = v.replyCount
- })
+ functions.createList(post, replyList)
+ applyNestedReplyCounts(post)
resolve(post)
}
)
})
}
}
+ return post
},
//请求帖子其他页的回复
fetchPostOtherPageReplies(href: string, pageNo: number) {
@@ -298,7 +316,11 @@ function run() {
box = findReplyBoxMobile(boxs)
}
})
- let cells: any = box!.querySelectorAll('.cell')
+ let cells: any = box?.querySelectorAll('.cell')
+ if (!cells || !cells.length) {
+ resolve({i: pageNo, replyList: []})
+ return
+ }
cells = Array.from(cells)
resolve({i: pageNo, replyList: this.parsePageReplies(cells.slice(2, cells.length - 1))})
}).catch((r: any) => {
@@ -1007,7 +1029,12 @@ if (isMobile) {
})();
run()
- let vueApp = createApp(App)
- vueApp.config.unwrapInjectedRef = true
- vueApp.mount($section);
-}
\ No newline at end of file
+ Promise.all([
+ import('vue'),
+ import('./pages/App.vue'),
+ ]).then(([{createApp}, {default: App}]) => {
+ let vueApp = createApp(App)
+ vueApp.config.unwrapInjectedRef = true
+ vueApp.mount($section);
+ })
+}
diff --git a/packages/mobile/src/pages/App.vue b/packages/mobile/src/pages/App.vue
index 65aebdd..6f7f344 100644
--- a/packages/mobile/src/pages/App.vue
+++ b/packages/mobile/src/pages/App.vue
@@ -388,17 +388,10 @@ export default {
regenerateReplyList() {
// console.log('重新生成列表')
if (this.current.replyList.length) {
- this.current.replyCount = this.current.replyList.length
- let res = functions.createNestedList(this.current.replyList)
- if (res) {
- this.current.nestedReplies = res
- }
- let dup_res = functions.createNestedRedundantList(this.current.replyList)
- if (dup_res) {
- this.current.nestedRedundReplies = dup_res
- }
+ functions.createList(this.current, this.current.replyList)
} else {
this.current.replyCount = 0
+ this.current.topReplyList = []
this.current.nestedReplies = []
this.current.nestedRedundReplies = []
}
@@ -679,4 +672,3 @@ export default {
gap: 1rem;
}
-
diff --git a/packages/mobile/vite.config.ts b/packages/mobile/vite.config.ts
index 9f42e69..379997a 100644
--- a/packages/mobile/vite.config.ts
+++ b/packages/mobile/vite.config.ts
@@ -51,6 +51,18 @@ export default defineConfig({
},
build: {
fileName:'V2Next-Mobile.user.js',
+ cssSideEffects: (css: string) => `
+ (() => {
+ if (document.querySelector('#Rightbar')) return;
+ if (typeof GM_addStyle === 'function') {
+ GM_addStyle(${JSON.stringify(css)});
+ return;
+ }
+ const style = document.createElement('style');
+ style.textContent = ${JSON.stringify(css)};
+ document.head.append(style);
+ })();
+ `,
externalGlobals: {
vue: cdn.jsdelivr('Vue', 'dist/vue.global.prod.js'),
},
diff --git a/packages/pc/src/App.vue b/packages/pc/src/App.vue
index 5a55baa..abafe2f 100644
--- a/packages/pc/src/App.vue
+++ b/packages/pc/src/App.vue
@@ -540,6 +540,10 @@ export default {
showPost() {
this.show = true
$(`#Wrapper #Main .box:lt(3)`).each(function () {
+ if (this.id === 'topic-tip-box' || this.querySelector('#topic-tip-box')) {
+ $(this).show()
+ return
+ }
$(this).hide()
})
},
@@ -1066,4 +1070,3 @@ export default {
}
}
-
diff --git a/packages/pc/src/components/PostDetail.vue b/packages/pc/src/components/PostDetail.vue
index f7e17be..c73e523 100644
--- a/packages/pc/src/components/PostDetail.vue
+++ b/packages/pc/src/components/PostDetail.vue
@@ -81,6 +81,7 @@
:api-url="'topic/'+post.id"/>
+
@@ -512,6 +513,7 @@ export default {
this.$refs?.detail?.scrollTo({top: 0})
})
}
+ this.moveTopicTipBox()
},
'post.headerTemplate'(n, o) {
let mountEl = document.querySelector('.main-wrapper .post-wrapper .html-wrapper .header')
@@ -527,6 +529,7 @@ export default {
this.currentFloor = ''
nextTick(() => {
this.$refs?.main?.focus()
+ this.moveTopicTipBox()
})
} else {
this.isSticky = false
@@ -537,6 +540,7 @@ export default {
},
mounted() {
nextTick(() => {
+ this.moveTopicTipBox()
setTimeout(() => {
this.postDetailWidth = this.$refs.mainWrapper?.getBoundingClientRect().width || 0
}, 500)
@@ -582,6 +586,14 @@ export default {
eventBus.off(CMD.SHOW_CALL)
},
methods: {
+ moveTopicTipBox() {
+ nextTick(() => {
+ let topicTipBox = document.querySelector('#topic-tip-box')
+ if (!topicTipBox || !this.$refs.topicTipMount) return
+ this.$refs.topicTipMount.appendChild(topicTipBox)
+ topicTipBox.style.display = ''
+ })
+ },
addTag() {
eventBus.emit(CMD.ADD_TAG, this.post.member.username)
},
@@ -979,6 +991,17 @@ export default {
}
}
+ .topic-tip-mount {
+ width: 100%;
+ margin-bottom: 2rem;
+
+ :deep(#topic-tip-box) {
+ width: 100%;
+ margin: 0;
+ box-sizing: border-box;
+ }
+ }
+
.loading-wrapper {
height: 20rem;
display: flex;
diff --git a/packages/pc/src/main.ts b/packages/pc/src/main.ts
index 176eeba..4efbafc 100644
--- a/packages/pc/src/main.ts
+++ b/packages/pc/src/main.ts
@@ -1,5 +1,3 @@
-import {createApp} from 'vue';
-import App from './App.vue';
import './global.d.ts'
import {
GM_registerMenuCommand,
@@ -64,6 +62,13 @@ function run() {
window.postList = []
window.isDeadline = dayjs().isAfter(dayjs('2024-11-26'))
// window.isDeadline = true
+ function hasV2pConflict() {
+ if (window.user.username) {
+ return $(window.atob('LnYycC1ob3Zlci1idG4=')).length !== 0
+ }
+ return $(window.atob('LnYycC1mb290ZXI=')).length !== 0
+ }
+
window.parse = {
//解析主题内容
async parsePostContent(post: Post, body: JQuery, htmlText: string) {
@@ -418,13 +423,7 @@ function run() {
//检测
if (window.pageType === PageType.Home) {
const a = () => {
- let d
- if (window.user.username) {
- d = $(window.atob('LnYycC1ob3Zlci1idG4=')).length
- } else {
- d = $(window.atob('LnYycC1mb290ZXI=')).length
- }
- if (d !== 0) {
+ if (hasV2pConflict()) {
window.stopMe = true
localStorage.setItem('d', '1')
functions.cbChecker({type: 'syncData'})
@@ -996,12 +995,14 @@ function run() {
window.parse.parsePagePostList(list, box)
break
case PageType.Post:
- let d: string = localStorage.getItem('d')
- if (d) {
+ if (hasV2pConflict()) {
window.stopMe = true
+ localStorage.setItem('d', '1')
functions.cbChecker({type: 'syncData'})
return
}
+ localStorage.setItem('d', '')
+
box = document.querySelector('#Wrapper #Main .box')
// @ts-ignore
box.after($section)
@@ -1178,9 +1179,14 @@ function run() {
if (!isMobile) {
console.log('V2EX PC端')
run()
- let vueApp = createApp(App)
- vueApp.config.unwrapInjectedRef = true
- vueApp.mount($section);
+ Promise.all([
+ import('vue'),
+ import('./App.vue'),
+ ]).then(([{createApp}, {default: App}]) => {
+ let vueApp = createApp(App)
+ vueApp.config.unwrapInjectedRef = true
+ vueApp.mount($section);
+ })
// functions.loadAndRunScript("https://update.greasyfork.org/scripts/448472/v2%E6%96%B0%E5%B8%96%E6%8C%82%E4%BB%B6.user.js")
functions.loadAndRunScript("https://update.greasyfork.org/scripts/448472/1074290/v2%E6%96%B0%E5%B8%96%E6%8C%82%E4%BB%B6.user.js")
diff --git a/packages/pc/vite.config.ts b/packages/pc/vite.config.ts
index 8116b95..760c742 100644
--- a/packages/pc/vite.config.ts
+++ b/packages/pc/vite.config.ts
@@ -55,6 +55,18 @@ export default defineConfig({
},
build: {
fileName: 'V2Next.user.js',
+ cssSideEffects: (css: string) => `
+ (() => {
+ if (!document.querySelector('#Rightbar')) return;
+ if (typeof GM_addStyle === 'function') {
+ GM_addStyle(${JSON.stringify(css)});
+ return;
+ }
+ const style = document.createElement('style');
+ style.textContent = ${JSON.stringify(css)};
+ document.head.append(style);
+ })();
+ `,
externalGlobals: {
vue: cdn.jsdelivr('Vue', 'dist/vue.global.prod.min.js'),
dayjs: cdn.jsdelivr('dayjs', 'dayjs.min.js'),