Skip to content

Commit 64b8364

Browse files
committed
添加“稍后阅读”功能
1 parent 66da865 commit 64b8364

File tree

11 files changed

+136
-35
lines changed

11 files changed

+136
-35
lines changed

extension/pages/popup.html

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
<main>
1414
<nav>
1515
<ul class="tabs">
16+
<li data-target="tab-reading">稍后阅读</li>
1617
<li data-target="tab-hot">最热</li>
1718
<li data-target="tab-latest">最新</li>
1819
<li data-target="tab-message">消息</li>
@@ -65,6 +66,7 @@
6566

6667
<section>
6768
<div class="tab-contents">
69+
<div id="tab-reading" class="tab-content"></div>
6870
<div id="tab-hot" class="tab-content"></div>
6971
<div id="tab-latest" class="tab-content"></div>
7072
<div id="tab-message" class="tab-content"></div>

src/components/popup.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -112,7 +112,7 @@ export function createPopup(props: CreatePopupProps): PopupControl {
112112
})
113113
.catch(() => {
114114
handlePopupClose()
115-
createToast({ message: 'Popup 渲染失败' })
115+
createToast({ message: 'Popup 渲染失败' })
116116
})
117117

118118
onOpen?.()

src/contents/decode-base64.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ import { createToast } from '../components/toast'
1313
const dataTitle = '点击复制'
1414

1515
if (window.__V2P_DecodeStatus__ === 'decodeed') {
16-
createToast({ message: '已解析完本页所有的 Base64 字符串' })
16+
createToast({ message: '已解析完本页所有的 Base64 字符串' })
1717
} else {
1818
// 不能从 global.ts 中引入,否则会出现脚本执行错误,此错误发生原因未知。
1919
const $topicContentBox = $('#Main .box:has(.topic_content)')
@@ -88,7 +88,7 @@ if (window.__V2P_DecodeStatus__ === 'decodeed') {
8888
createToast({ message: '本页未发现 Base64 字符串' })
8989
} else {
9090
window.__V2P_DecodeStatus__ = 'decodeed'
91-
createToast({ message: `已解析本页所有的 Base64 字符串,共 ${count} 条` })
91+
createToast({ message: `已解析本页所有的 Base64 字符串,共 ${count} 条` })
9292
}
9393

9494
$('.v2p-decode').on('click', (ev) => {

src/contents/reading-list.ts

Lines changed: 25 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,33 @@
11
import { createToast } from '../components/toast'
2+
import { StorageKey } from '../constants'
3+
import { getStorage, setStorage } from '../utils'
24

5+
const url = $('head meta[property="og:url"]').prop('content')
36
const title = $('head meta[property="og:title"]').prop('content')
4-
const desc = $('head meta[property="og:description"]').prop('content')
7+
const content = $('head meta[property="og:description"]').prop('content')
58

6-
if (!(typeof title === 'string' || typeof desc === 'string')) {
9+
if (!(typeof url === 'string' || typeof title === 'string' || typeof content === 'string')) {
710
const message = '无法识别将该主题的元数据'
811
createToast({ message })
912
throw new Error(message)
1013
}
14+
15+
void (async () => {
16+
const storage = await getStorage()
17+
18+
const currentData = storage[StorageKey.ReadingList]?.data || []
19+
const exist = currentData.findIndex((it) => it.url === url) !== -1
20+
21+
if (exist) {
22+
createToast({ message: '该主题已存在于稍后阅读' })
23+
} else {
24+
await setStorage(StorageKey.ReadingList, {
25+
data: [
26+
{ url, title: (title as string).replace(' - V2EX', ''), content, addedTime: Date.now() },
27+
...currentData,
28+
],
29+
})
30+
31+
createToast({ message: '📖 已添加进稍后阅读' })
32+
}
33+
})()

src/contents/topic/reply.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -173,7 +173,7 @@ export function handleReply() {
173173
.catch(() => {
174174
replacePlaceholder('')
175175

176-
window.alert('上传图片失败,请打开控制台查看原因')
176+
window.alert('上传图片失败,请打开控制台查看原因')
177177
})
178178
.finally(() => {
179179
$uploadBar.removeClass('v2p-reply-upload-bar-disabled').text(uploadTip)

src/pages/popup.helper.ts

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,12 @@
1+
import type { ReadingItem, Topic } from '../types'
2+
import { escapeHTML } from '../utils'
13
import { TabId } from './popup.var'
24

35
export function isTabId(tabId: any): tabId is TabId {
46
if (typeof tabId === 'string') {
57
if (
68
/* eslint-disable @typescript-eslint/no-unsafe-enum-comparison */
9+
tabId === TabId.Reading ||
710
tabId === TabId.Hot ||
811
tabId === TabId.Latest ||
912
tabId === TabId.Message ||
@@ -17,6 +20,40 @@ export function isTabId(tabId: any): tabId is TabId {
1720
return false
1821
}
1922

23+
export const generateReadingItmes = (items: ReadingItem[]) => {
24+
return items
25+
.map((topic) => {
26+
return `
27+
<li class="topic-item">
28+
<a href="${topic.url}" target="_blank">
29+
<span class="title">${escapeHTML(topic.title)}</span>
30+
<span class="content">${topic.content.replace(/<[^>]*>/g, '')}</span>
31+
32+
<div class="topic-item-actions">
33+
<button class="topic-item-action-remove" data-url="${topic.url}">移除</button>
34+
</div>
35+
</a>
36+
</li>
37+
`
38+
})
39+
.join('')
40+
}
41+
42+
export const generateTopicItmes = (topics: Topic[]) => {
43+
return topics
44+
.map((topic) => {
45+
return `
46+
<li class="topic-item">
47+
<a href="${topic.url}" target="_blank">
48+
<span class="title">${escapeHTML(topic.title)}</span>
49+
<span class="content">${topic.content.replace(/<[^>]*>/g, '')}</span>
50+
</a>
51+
</li>
52+
`
53+
})
54+
.join('')
55+
}
56+
2057
/**
2158
* 计算 local storage 的数据大小。
2259
*/

src/pages/popup.ts

Lines changed: 36 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -19,15 +19,14 @@ import {
1919
setV2P_Settings,
2020
} from '../services'
2121
import type { Topic } from '../types'
22+
import { formatTimestamp, getStorage, getStorageSync, isSameDay, setStorage } from '../utils'
2223
import {
23-
escapeHTML,
24-
formatTimestamp,
25-
getStorage,
26-
getStorageSync,
27-
isSameDay,
28-
setStorage,
29-
} from '../utils'
30-
import { calculateLocalStorageSize, formatSizeUnits, isTabId } from './popup.helper'
24+
calculateLocalStorageSize,
25+
formatSizeUnits,
26+
generateReadingItmes,
27+
generateTopicItmes,
28+
isTabId,
29+
} from './popup.helper'
3130
import type { PopupStorageData, RemoteDataStore } from './popup.type'
3231
import { defaultValue, TabId } from './popup.var'
3332

@@ -46,6 +45,7 @@ const errorDisplay = `
4645
`
4746

4847
const topicContentData: Record<TabId, RemoteDataStore> = {
48+
[TabId.Reading]: {},
4949
[TabId.Hot]: {
5050
data: undefined,
5151
lastFetchTime: undefined,
@@ -206,21 +206,6 @@ function loadSettings() {
206206
}
207207

208208
function initTabs() {
209-
const generateTopicItmes = (topics: Topic[]) => {
210-
return topics
211-
.map((topic) => {
212-
return `
213-
<li class="topic-item">
214-
<a href="${topic.url}" target="_blank">
215-
<span class="title">${escapeHTML(topic.title)}</span>
216-
<span class="content">${topic.content.replace(/<[^>]*>/g, '')}</span>
217-
</a>
218-
</li>
219-
`
220-
})
221-
.join('')
222-
}
223-
224209
const getCurrentActiveTab = () => {
225210
return $('.tabs > li.active')
226211
}
@@ -236,6 +221,34 @@ function initTabs() {
236221

237222
const $tabContent = $(`#${tabId}`)
238223

224+
if (tabId === TabId.Reading) {
225+
const readingData = storage[StorageKey.ReadingList]?.data
226+
if (readingData) {
227+
const $readingList = $(`<ul class="list">`).append(generateReadingItmes(readingData))
228+
229+
$tabContent.empty().append($readingList)
230+
231+
$('.topic-item-action-remove').on('click', (ev) => {
232+
ev.preventDefault()
233+
ev.stopPropagation()
234+
235+
const url = ev.target.dataset.url
236+
237+
if (url) {
238+
const newReadingData = readingData.filter((it) => it.url !== url)
239+
240+
void setStorage(StorageKey.ReadingList, { data: newReadingData })
241+
242+
$tabContent
243+
.find(`.topic-item:has(.topic-item-action-remove[data-url="${url}"])`)
244+
.remove()
245+
}
246+
})
247+
} else {
248+
$tabContent.empty().append('<div>暂未添加稍后阅读</div>')
249+
}
250+
}
251+
239252
if (tabId === TabId.Hot || tabId === TabId.Latest) {
240253
const loaded = $tabContent.find('.list').length > 0
241254

src/pages/popup.var.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
export const enum TabId {
2+
Reading = 'tab-reading',
23
Hot = 'tab-hot',
34
Latest = 'tab-latest',
45
Message = 'tab-message',

src/styles/popup.scss

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -150,6 +150,8 @@ button {
150150
}
151151

152152
.topic-item {
153+
position: relative;
154+
153155
> a {
154156
display: block;
155157
padding: 20px 15px;
@@ -176,6 +178,28 @@ button {
176178
font-size: 13px;
177179
}
178180
}
181+
182+
.topic-item-actions {
183+
position: absolute;
184+
top: 0;
185+
right: 0;
186+
display: inline-flex;
187+
background: var(--v2p-color-bg-widget);
188+
opacity: 0;
189+
backdrop-filter: blur(16px);
190+
}
191+
192+
&:hover {
193+
.topic-item-actions {
194+
opacity: 1;
195+
}
196+
}
197+
198+
.topic-item-action-remove {
199+
padding: 5px 10px;
200+
color: #ef4444;
201+
background-color: #fee2e2;
202+
}
179203
}
180204

181205
.notice-item {

src/types.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -47,9 +47,9 @@ export interface Tag {
4747

4848
export type MemberTag = Record<Member['username'], { tags?: Tag[] }>
4949

50-
export interface ReadingItem {
51-
title: Topic['title']
52-
desc: string
50+
export interface ReadingItem extends Pick<Topic, 'url' | 'title' | 'content'> {
51+
addedTime: number
52+
read?: boolean
5353
}
5454

5555
export interface StorageItems {
@@ -59,7 +59,7 @@ export interface StorageItems {
5959
[StorageKey.Daily]?: DailyInfo
6060
[StorageKey.MemberTag]?: MemberTag
6161
[StorageKey.ReadingList]?: {
62-
data: ReadingItem[]
62+
data?: ReadingItem[]
6363
}
6464
}
6565

0 commit comments

Comments
 (0)