Skip to content

Commit

Permalink
feat: 添加章节切换菜单 (#7)
Browse files Browse the repository at this point in the history
  • Loading branch information
aooiuu committed May 8, 2024
1 parent 5bce67d commit 876a289
Show file tree
Hide file tree
Showing 22 changed files with 610 additions and 174 deletions.
7 changes: 4 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
{
"name": "@any-reader/monorepo",
"type": "module",
"version": "0.1.0-alpha.3",
"version": "1.0.0",
"packageManager": "pnpm@8.6.5",
"description": "",
"license": "MIT",
"license": "GPL3",
"author": "aooiuu",
"homepage": "https://github.com/aooiuu/any-reader#readme",
"repository": {
Expand All @@ -28,12 +28,13 @@
"build:core": "npm -C packages/core run build",
"build:shared": "npm -C packages/shared run build",
"run:web": "npm -C packages/web run dev",
"build:web": "npm -C packages/web run build",
"build:web-w": "npm -C packages/web run build:w",
"run:vsc": "npm-run-all build:core build:web-w",
"docs": "npm -C docs run docs:dev",
"build:docs": "npm -C docs run docs:build",
"server": "npm -C packages/server run dev",
"build": "npm-run-all build:core build:shared",
"build": "npm-run-all build:core build:shared build:web",
"run:server": "run-p server run:web",
"test": "jest",
"coveralls": "jest --coverage",
Expand Down
11 changes: 6 additions & 5 deletions packages/shared/src/RecordFile.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import * as fs from 'fs-extra'
// @ts-expect-error
import { ensureFile, readJson, writeJson } from 'fs-extra/esm'
import type { Rule, SearchItem } from '@any-reader/core'

export interface RecordFileRow extends SearchItem {
Expand All @@ -18,8 +19,8 @@ export class RecordFile {

// 初始化
async init() {
await fs.ensureFile(this.filePath)
this.history = await fs.readJson(this.filePath).catch(() => this.history)
await ensureFile(this.filePath)
this.history = await readJson(this.filePath).catch(() => this.history)
}

// 获取所有记录
Expand All @@ -29,8 +30,8 @@ export class RecordFile {

// 保存配置文件
async writeFile() {
await fs.ensureFile(this.filePath)
await fs.writeJson(this.filePath, this.history, { spaces: 2 })
await ensureFile(this.filePath)
await writeJson(this.filePath, this.history, { spaces: 2 })
}

// 删除记录
Expand Down
29 changes: 17 additions & 12 deletions packages/shared/src/localBookManager.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import * as path from 'node:path'
import * as fs from 'fs-extra'
import * as fs from 'node:fs'
import EPub from 'epub'
import Encoding from 'encoding-japanese'
import * as iconv from 'iconv-lite'
import { LOCAL_BOOK_DIR } from './constants'

enum BOOK_TYPE {
export enum BOOK_TYPE {
TXT = 1,
EPUB = 2,
}
Expand All @@ -30,9 +30,17 @@ export function checkDir() {
fs.mkdirSync(LOCAL_BOOK_DIR)
}

export function path2bookFile(filePath: string): BookFile {
return {
type: getBookType(filePath),
name: path.basename(filePath, path.extname(filePath)),
path: filePath,
}
}

// 获取书籍类型
function getBookType(extname: string) {
return extname === '.txt' ? BOOK_TYPE.TXT : BOOK_TYPE.EPUB
export function getBookType(filePath: string) {
return path.extname(filePath) === '.txt' ? BOOK_TYPE.TXT : BOOK_TYPE.EPUB
}

// 获取所有书籍
Expand All @@ -43,18 +51,15 @@ export async function getBookList(): Promise<BookFile[]> {
const files = fs.readdirSync(dir)

return files
.filter(f => ['.txt', '.epub'].includes(path.extname(f)))
.map((f) => {
return {
type: getBookType(path.extname(f)),
name: path.basename(f, path.extname(f)),
path: path.join(dir, f),
}
.filter(filePath => ['.txt', '.epub'].includes(path.extname(filePath)))
.map((filePath) => {
return path2bookFile(path.join(dir, filePath))
})
}

// 获取章节
export async function getChapter(bookFile: BookFile): Promise<BookChapter[]> {
export async function getChapter(filePath: string): Promise<BookChapter[]> {
const bookFile = path2bookFile(filePath)
if (bookFile.type === BOOK_TYPE.TXT) {
return [
{
Expand Down
11 changes: 6 additions & 5 deletions packages/shared/src/ruleFileManager.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import * as fs from 'fs-extra'
// @ts-expect-error
import { ensureFile, readJson, writeJson } from 'fs-extra/esm'
import { v4 as uuidV4 } from 'uuid'
import type { Rule } from '@any-reader/core'
import { decodeRule } from '@any-reader/core'
Expand All @@ -8,7 +9,7 @@ let ruleList: Rule[] = []

async function readRuleList(): Promise<Rule[]> {
try {
const list = await fs.readJson(BOOK_SOURCE_PATH)
const list = await readJson(BOOK_SOURCE_PATH)
for (let i = 0; i < list.length; i++) {
const rule = list[i]
if (typeof rule === 'string' && rule.includes('eso://'))
Expand All @@ -23,14 +24,14 @@ async function readRuleList(): Promise<Rule[]> {

// 初始化
export async function init() {
await fs.ensureFile(BOOK_SOURCE_PATH)
await ensureFile(BOOK_SOURCE_PATH)
ruleList = await readRuleList()
}

// 保存配置文件
async function writeFile() {
await fs.ensureFile(BOOK_SOURCE_PATH)
return fs.writeJson(BOOK_SOURCE_PATH, ruleList, { spaces: 2 })
await ensureFile(BOOK_SOURCE_PATH)
return writeJson(BOOK_SOURCE_PATH, ruleList, { spaces: 2 })
}

export function list(): Rule[] {
Expand Down
5 changes: 3 additions & 2 deletions packages/vscode/package.json
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
{
"name": "any-reader",
"displayName": "any-reader",
"description": "any-reader for vscode",
"description": "自定义规则多站点聚合搜索阅读小说、漫画。包含JS规则解析库和VSCode插件。支持本地小说 TXT、EPUB",
"icon": "resources/icon.png",
"version": "0.6.1",
"version": "0.6.2",
"preview": true,
"publisher": "aooiu",
"qna": "https://github.com/aooiuu/any-reader/issues",
Expand Down Expand Up @@ -262,6 +262,7 @@
"easy-post-message": "^0.1.0",
"explorer-opener": "^1.0.1",
"fs-extra": "^11.1.1",
"qs": "^6.12.1",
"uuid": "^9.0.1"
}
}
97 changes: 10 additions & 87 deletions packages/vscode/src/App.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,15 @@
import * as vscode from 'vscode';
import { openExplorer } from 'explorer-opener';
import { ContentType, Rule, RuleManager, SearchItem } from '@any-reader/core';
import { Rule, RuleManager, SearchItem } from '@any-reader/core';
import { CONSTANTS } from '@any-reader/shared';
import { BookChapter, getContent, checkDir } from '@any-reader/shared/localBookManager';
import { checkDir } from '@any-reader/shared/localBookManager';
import { COMMANDS, BOOK_SOURCE_PATH } from './constants';
import { config } from './config';
import bookProvider from './treeview/book';
import historyProvider from './treeview/history';
import sourceProvider from './treeview/source';
import favoritesProvider from './treeview/favorites';
import localProvider from './treeview/localBook';
import bookManager, { TreeNode } from './treeview/bookManager';
import bookManager from './treeview/bookManager';
import { treeItemDecorationProvider } from './treeview/TreeItemDecorationProvider';
import * as ruleFileManager from './utils/ruleFileManager';
import historyManager from './utils/historyManager';
Expand All @@ -27,6 +26,7 @@ class App {
// 初始化配置文件
await Promise.all([ruleFileManager.init(), historyManager.init(), favoritesManager.init()]);

// 注册命令
const registerCommand = vscode.commands.registerCommand;
[
vscode.window.registerFileDecorationProvider(treeItemDecorationProvider),
Expand All @@ -35,10 +35,10 @@ class App {
registerCommand(COMMANDS.getChapter, this.getChapter, this),
registerCommand(COMMANDS.discover, this.discover, this),
registerCommand(COMMANDS.searchBookByRule, this.searchBookByRule, this),
registerCommand(COMMANDS.getContent, this.getContent, this),
registerCommand(COMMANDS.getContent, this.webView.getContent, this.webView),
registerCommand(COMMANDS.openLocalBookDir, this.openLocalBookDir, this),
registerCommand(COMMANDS.refreshLocalBooks, this.refreshLocalBooks, this),
registerCommand(COMMANDS.getContentLocalBook, this.getContentLocalBook, this),
registerCommand(COMMANDS.getContentLocalBook, this.webView.getContentLocalBook, this.webView),
registerCommand(COMMANDS.star, this.star, this),
registerCommand(COMMANDS.unstar, this.unstar, this),
registerCommand(COMMANDS.home, () => this.webView.navigateTo('/'), this.webView),
Expand Down Expand Up @@ -95,7 +95,9 @@ class App {
bookProvider.refresh();
}

// 获取章节
/**
* 获取章节
*/
async getChapter(history: RecordFileRow, config: { saveHistory: SearchItem }) {
await vscode.window.withProgress(
{
Expand All @@ -107,13 +109,7 @@ class App {
const rule = await ruleFileManager.findById(history.ruleId);
const ruleManager = new RuleManager(rule);
const chapterItems = await ruleManager.getChapter(history.url);

bookManager.list = chapterItems.map((chapterItem: any) => ({
rule,
type: 2,
data: chapterItem
}));
bookProvider.refresh();
bookProvider.setChapters(chapterItems, rule, history.url);

if (config?.saveHistory) {
historyManager.add(config.saveHistory, rule);
Expand All @@ -123,48 +119,6 @@ class App {
);
}

// 获取文章详情
async getContent(article: TreeNode) {
await vscode.window.withProgress(
{
location: vscode.ProgressLocation.Window,
title: 'loading...',
cancellable: false
},
async () => {
const textArr = await bookManager.getContent(article);
if (!textArr?.length) {
vscode.window.showWarningMessage('empty content');
} else {
let content = '';
if (article.rule.contentType === ContentType.VIDEO) {
this.webView.navigateTo('/player?url=' + textArr[0]);
} else if (article.rule.contentType === ContentType.MANGA) {
content = textArr.map((src) => `<img src="${src}"/>`).join('');
} else {
content = textArr.join('');
}
this.openWebviewPanel(article.data.name, content);
}
}
);
}

// 阅读本地书籍
async getContentLocalBook(item: BookChapter) {
await vscode.window.withProgress(
{
location: vscode.ProgressLocation.Window,
title: 'loading...',
cancellable: false
},
async () => {
const content = await getContent(item).catch(() => '');
this.openWebviewPanel(item.name, content);
}
);
}

// 打开本地书籍目录
openLocalBookDir() {
checkDir();
Expand All @@ -176,37 +130,6 @@ class App {
localProvider.refresh();
}

openWebviewPanel(title: string, content: string) {
if (!content) {
return;
}
if (config.app.get('hideImage', false)) {
content = content.replace(/<img .*?>/gim, '');
}
const injectedHtml = config.app.get('injectedHtml', '');
const css = `
html,
body {
margin: 0;
padding: 0;
height: 100%;
width: 100%;
}
body {
font-size: 1em;
}
p {
margin: 0;
padding: 0;
}
`;
this.webView.openWebviewPanel(
title,
`${injectedHtml}<style>${css}</style><div style="white-space: pre-wrap; height: 100%; width: 100%; padding: 10px
; box-sizing: border-box;">${content}</div>`
);
}

// 获取本地书源列表
async getBookSource() {
sourceProvider.refresh();
Expand Down
4 changes: 4 additions & 0 deletions packages/vscode/src/treeview/TreeItemDecorationProvider.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
/**
* [侧边栏 - 规则] 右侧规则类型显示
*/

import { FileDecoration, FileDecorationProvider, ProviderResult, Uri } from 'vscode';
import { CONTENT_TYPE_TEXT } from '@any-reader/core';

Expand Down
33 changes: 30 additions & 3 deletions packages/vscode/src/treeview/book.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,17 @@
/**
* 侧边栏 - 阅读
*/

import * as vscode from 'vscode';
import { COMMANDS } from '../constants';
import bookManager, { TreeNode } from './bookManager';
import favoritesManager from '../utils/favoritesManager';
import { ChapterItem, Rule } from '@any-reader/core';

export class BookProvider implements vscode.TreeDataProvider<TreeNode> {
readonly _onDidChangeTreeData = new vscode.EventEmitter<TreeNode | undefined>();
readonly onDidChangeTreeData = this._onDidChangeTreeData.event;
readonly cache = new Map();

refresh(): void {
this._onDidChangeTreeData.fire(undefined);
Expand All @@ -28,13 +34,34 @@ export class BookProvider implements vscode.TreeDataProvider<TreeNode> {
};
}

getChildren(element?: TreeNode): Promise<TreeNode[]> {
// 获取缓存列表
getChildrenCache(ruleId: string, url: string) {
const key = `${ruleId}@${url}`;
return this.cache.get(key) || this.cache.get('_') || [];
}

async getChildren(element?: TreeNode): Promise<TreeNode[]> {
if (!element) {
return bookManager.getChildren();
const items = await bookManager.getChildren();
return items;
} else {
return bookManager.getChapter(element);
const chapters = await bookManager.getChapter(element);
this.cache.set(`${element.rule.id}@${element.url}`, chapters);
return chapters;
}
}

setChapters(chapterItems: ChapterItem[], rule: Rule, url: string) {
this.cache.clear();
bookManager.list = chapterItems.map((chapterItem: ChapterItem) => ({
rule,
type: 2,
data: chapterItem,
url
}));
this.cache.set('_', bookManager.list);
this.refresh();
}
}

export default new BookProvider();
Loading

0 comments on commit 876a289

Please sign in to comment.