diff --git a/src/org/wanxp/constant/Constsant.ts b/src/org/wanxp/constant/Constsant.ts index ec0e23b..60ad27f 100644 --- a/src/org/wanxp/constant/Constsant.ts +++ b/src/org/wanxp/constant/Constsant.ts @@ -1,4 +1,5 @@ import {i18nHelper} from "../lang/helper"; +import DoubanSearchResultSubject from "../douban/data/model/DoubanSearchResultSubject"; /** * 常量池 @@ -162,6 +163,58 @@ export enum SyncItemStatus { fail= 'fail', unHandle='unHandle', } +export enum NavigateType { + previous = "previous", + next = "next", + + nextNeedLogin = "nextNeedLogin" +} + +export const DoubanSearchResultSubjectPreviousPage:DoubanSearchResultSubject = { + cast: "", + datePublished: undefined, + desc: "", + genre: [], + id: "", + image: "", + publisher: "", + score: 0, + title: i18nHelper.getMessage("150102"), + type: "navigate", + url: NavigateType.previous +} + +export const DoubanSearchResultSubjectNextPage:DoubanSearchResultSubject = { + cast: "", + datePublished: undefined, + desc: "", + genre: [], + id: "", + image: "", + publisher: "", + score: 0, + title: i18nHelper.getMessage("150103"), + type: "navigate", + url: NavigateType.next +} + +export const DoubanSearchResultSubjectNextPageNeedLogin:DoubanSearchResultSubject = { + cast: "", + datePublished: undefined, + desc: "", + genre: [], + id: "", + image: "", + publisher: "", + score: 0, + title: i18nHelper.getMessage("150104"), + type: "navigate", + url: NavigateType.nextNeedLogin +} + +export const SEARCH_ITEM_PAGE_SIZE:number = 20; + + diff --git a/src/org/wanxp/douban/data/model/HandleContext.ts b/src/org/wanxp/douban/data/model/HandleContext.ts index 18030b1..cf930cc 100644 --- a/src/org/wanxp/douban/data/model/HandleContext.ts +++ b/src/org/wanxp/douban/data/model/HandleContext.ts @@ -8,6 +8,8 @@ import SyncStatusHolder from "../../sync/model/SyncStatusHolder"; import {SyncConfig} from "../../sync/model/SyncConfig"; import DoubanSubject from "./DoubanSubject"; import GlobalStatusHolder from "../../model/GlobalStatusHolder"; +import {SearchResultsPage} from "schema-dts"; +import {SearchPageInfo} from "./SearchPageInfo"; export default interface HandleContext { plugin:DoubanPlugin; @@ -21,4 +23,6 @@ export default interface HandleContext { action:string; syncConfig?: SyncConfig; listItem?:DoubanSubject; + + searchPage:SearchPageInfo; } diff --git a/src/org/wanxp/douban/data/model/SearchPage.ts b/src/org/wanxp/douban/data/model/SearchPage.ts new file mode 100644 index 0000000..eca44d7 --- /dev/null +++ b/src/org/wanxp/douban/data/model/SearchPage.ts @@ -0,0 +1,17 @@ +import {SearchPageInfo} from "./SearchPageInfo"; + +export class SearchPage extends SearchPageInfo{ + + private _list:any[]; + + + constructor(total: number, pageNum: number, pageSize: number, hasNext: boolean, list: any[]) { + super(total, pageNum, pageSize, hasNext); + this._list = list; + } + + public get list() { + return this._list; + } + +} diff --git a/src/org/wanxp/douban/data/model/SearchPageInfo.ts b/src/org/wanxp/douban/data/model/SearchPageInfo.ts new file mode 100644 index 0000000..c430432 --- /dev/null +++ b/src/org/wanxp/douban/data/model/SearchPageInfo.ts @@ -0,0 +1,58 @@ +export class SearchPageInfo { + private _total: number; + private _hasNext: boolean; + private _pageSize: number; + private _pageNum: number; + + constructor(total: number, pageNum: number, pageSize: number, hasNext: boolean) { + this._total = total; + this._pageNum = pageNum; + this._pageSize = pageSize; + this._hasNext = hasNext; + } + + public nextPage(): SearchPageInfo { + if (!this._hasNext) { + return this; + } + if (((this._pageNum + 1) * this._pageSize) > this.total) { + return this; + } + return new SearchPageInfo(this.total, this._pageNum + 1, + this._pageSize, ((this._pageNum + 2) * this._pageSize) > this.total); + } + + public previousPage(): SearchPageInfo { + if (this._pageNum == 0) { + return this; + } + return new SearchPageInfo(this.total, this._pageNum - 1, + this._pageSize, true); + } + + + public get hasNext() { + return this._hasNext; + } + + public get hasPrevious() { + return this._pageNum > 0; + } + + + public get start() { + return this._pageNum * this._pageSize + 1; + } + + public get total() { + return this._total; + } + + get pageSize(): number { + return this._pageSize; + } + + get pageNum(): number { + return this._pageNum; + } +} diff --git a/src/org/wanxp/douban/data/search/DoubanSearchFuzzySuggestModal.ts b/src/org/wanxp/douban/data/search/DoubanSearchFuzzySuggestModal.ts index 11469dc..eecae21 100644 --- a/src/org/wanxp/douban/data/search/DoubanSearchFuzzySuggestModal.ts +++ b/src/org/wanxp/douban/data/search/DoubanSearchFuzzySuggestModal.ts @@ -1,10 +1,21 @@ -import {FuzzySuggestModal} from "obsidian"; +import {FuzzySuggestModal, request, RequestUrlParam} from "obsidian"; import DoubanPlugin from "../../../main"; import DoubanSearchResultSubject from "../model/DoubanSearchResultSubject"; import {log} from "src/org/wanxp/utils/Logutil"; import {i18nHelper} from "../../../lang/helper"; import HandleContext from "../model/HandleContext"; +import {init} from "cjs-module-lexer"; +import { + DoubanSearchResultSubjectNextPage, + DoubanSearchResultSubjectNextPageNeedLogin, DoubanSearchResultSubjectPreviousPage, NavigateType +} from "../../../constant/Constsant"; +import {SearchPageInfo} from "../model/SearchPageInfo"; +import {flat} from "builtin-modules"; +import User from "../../user/User"; +import {load} from "cheerio"; +import Searcher from "./Search"; +import {SearchPage} from "../model/SearchPage"; export {DoubanFuzzySuggester} @@ -14,11 +25,13 @@ class DoubanFuzzySuggester extends FuzzySuggestModal private plugin: DoubanPlugin; private doubanSearchResultExtract: DoubanSearchResultSubject[]; private context: HandleContext; + private searchItem:string; - constructor(plugin: DoubanPlugin, context: HandleContext) { + constructor(plugin: DoubanPlugin, context: HandleContext, searchItem:string) { super(app); this.plugin = plugin; this.context = context; + this.searchItem = searchItem; this.setPlaceholder(i18nHelper.getMessage('150101')); } @@ -28,11 +41,24 @@ class DoubanFuzzySuggester extends FuzzySuggestModal } getItemText(item: DoubanSearchResultSubject): string { + if (this.isNavigate(item)) { + return item.title; + } let text: string = item.type + "/" + (item.score ? item.score : '-') + "/" + item.title + "/" + item.cast; return text; } + private isNavigate(item: DoubanSearchResultSubject) { + return item.type == "navigate"; + } + onChooseItem(item: DoubanSearchResultSubject, evt: MouseEvent | KeyboardEvent): void { + if(this.isNavigate(item)) { + if (this.handleNavigate(item)) { + this.start(); + } + return; + } this.plugin.showStatus(i18nHelper.getMessage('140204', item.title)); this.context.listItem = item; if (item) { @@ -41,11 +67,58 @@ class DoubanFuzzySuggester extends FuzzySuggestModal this.plugin.doubanExtractHandler.handle(item, this.context); } + async handleNavigate(item: DoubanSearchResultSubject):Promise { + const {searchPage} = this.context; + let currentPage:SearchPageInfo = searchPage; + let result:boolean = false; + switch (item.type) { + case NavigateType.previous: + currentPage = searchPage.previousPage(); + result = true; + break; + case NavigateType.next: + currentPage = searchPage.nextPage(); + result = true; + break; + case NavigateType.nextNeedLogin: + log.warn(i18nHelper.getMessage("140304")); + break; + } + const searchPageResult:SearchPage = await Searcher.loadSearchItem(this.searchItem, currentPage.start, this.plugin.settings, this.plugin.settingsManager); + this.updatePageResult(searchPageResult); + this.context.searchPage = new SearchPageInfo(searchPageResult.total, currentPage.pageNum, searchPageResult.pageSize, searchPageResult.hasNext); + return result; + } + + private updatePageResult(searchPageResult: SearchPage) { + this.initItems(searchPageResult.list); + + } + + + public showSearchList(doubanSearchResultExtractList: DoubanSearchResultSubject[]) { - this.doubanSearchResultExtract = doubanSearchResultExtractList; + this.initItems(doubanSearchResultExtractList); this.start(); } + private initItems(doubanSearchResultExtractList: DoubanSearchResultSubject[]) { + let doubanList: DoubanSearchResultSubject[] = doubanSearchResultExtractList; + const {searchPage} = this.context; + if (searchPage.hasNext) { + if (this.plugin.userComponent.isLogin()) { + doubanList.push(DoubanSearchResultSubjectNextPage) + }else { + doubanList.push(DoubanSearchResultSubjectNextPageNeedLogin) + } + } + if (searchPage.hasPrevious) { + doubanList.unshift(DoubanSearchResultSubjectPreviousPage); + } + this.doubanSearchResultExtract = doubanList; + + } + public start(): void { try { this.open(); diff --git a/src/org/wanxp/douban/data/search/Search.ts b/src/org/wanxp/douban/data/search/Search.ts index 686b69a..832f25f 100644 --- a/src/org/wanxp/douban/data/search/Search.ts +++ b/src/org/wanxp/douban/data/search/Search.ts @@ -8,6 +8,8 @@ import {load} from 'cheerio'; import {DoubanPluginSetting} from "../../setting/model/DoubanPluginSetting"; import {DEFAULT_SETTINGS} from "../../../constant/DefaultSettings"; import SettingsManager from "../../setting/SettingsManager"; +import User from "../../user/User"; +import {SearchPage} from "../model/SearchPage"; export default class Searcher { static search(searchItem: string, doubanSettings: DoubanPluginSetting, settingsManager:SettingsManager): Promise { @@ -40,4 +42,36 @@ export default class Searcher { ; }; + + static loadSearchItem(searchItem: string, start:number, doubanSettings: DoubanPluginSetting, settingsManager:SettingsManager): Promise { + const myHeaders:Record = JSON.parse(doubanSettings.searchHeaders); + if (doubanSettings.loginCookiesContent) { + myHeaders.Cookie = doubanSettings.loginCookiesContent + } + log.debug("请求更多页面"); + let requestUrlParam: RequestUrlParam = { + url: `https://www.douban.com/j/search?q=${searchItem}&start=${start}&subtype=item`, + method: "GET", + headers: myHeaders, + throw: true + }; + return requestUrl(requestUrlParam) + .then(requestUrlResponse => { + if (requestUrlResponse.status == 403) { + throw new Error(i18nHelper.getMessage('130106')); + } + return requestUrlResponse.text; + }) + .then(e=>SearchParserHandler.parseSearchJson(e, start)) + .catch(e => { + if(e.toString().indexOf('403') > 0) { + throw new Error(i18nHelper.getMessage('130106')); + }else { + throw log.error(i18nHelper.getMessage('130101').replace('{0}', e.toString()), e); + } + }); + ; + + }; + } diff --git a/src/org/wanxp/douban/data/search/SearchParser.ts b/src/org/wanxp/douban/data/search/SearchParser.ts index 66db1c8..ec5d47f 100644 --- a/src/org/wanxp/douban/data/search/SearchParser.ts +++ b/src/org/wanxp/douban/data/search/SearchParser.ts @@ -1,5 +1,8 @@ -import {CheerioAPI} from "cheerio"; +import {CheerioAPI, load} from "cheerio"; import DoubanSearchResultSubject from "../model/DoubanSearchResultSubject"; +import {SearchPage} from "../model/SearchPage"; +import {SEARCH_ITEM_PAGE_SIZE} from "../../../constant/Constsant"; +import {log} from "../../../utils/Logutil"; export default class SearchParserHandler { static parseSearch(dataHtml: CheerioAPI): DoubanSearchResultSubject[] { @@ -33,4 +36,16 @@ export default class SearchParserHandler { return result; }) }; + + static parseSearchJson(result: string, start:number): SearchPage { + log.debug("解析给多页面结果"); + const data:{total:number, limit:number, more:boolean, items:string[]} = JSON.parse(result); + const list:string[] = data.items; + const resultList:DoubanSearchResultSubject[] = list + .map(e => load(e)) + .map(e=>this.parseSearch(e)) + .map(e => e? e[0]:null); + return new SearchPage(data.total, start / SEARCH_ITEM_PAGE_SIZE, data.limit, data.more, resultList); + }; + } diff --git a/src/org/wanxp/lang/locale/en.ts b/src/org/wanxp/lang/locale/en.ts index a092226..fda9817 100644 --- a/src/org/wanxp/lang/locale/en.ts +++ b/src/org/wanxp/lang/locale/en.ts @@ -251,8 +251,13 @@ PS: This file could be delete if you want to. '140301': `Douban: Syncing[{0}]...`, '140303': `Douban: User Info Expire, Please login again`, '140302': `Douban: Sync complete`, + '140304': `Douban: Need Login, Please login in Obsidian-Douban plugin`, + '150101': `Choose an item...`, + '150102': `[Previous Page]...`, + '150103': `[Next Page]...`, + '150104': `[Next Page (Please Login First)]...`, diff --git a/src/org/wanxp/lang/locale/zh-cn.ts b/src/org/wanxp/lang/locale/zh-cn.ts index f952c8e..21903dd 100644 --- a/src/org/wanxp/lang/locale/zh-cn.ts +++ b/src/org/wanxp/lang/locale/zh-cn.ts @@ -253,9 +253,13 @@ export default { '140301': `Douban: 开始同步[{0}]...`, '140302': `Douban: 同步完成`, '140303': `Douban: 用户信息已过期,请至插件中重新登录`, + '140304': `Douban: 此功能需要登录, 请先至插件中登录豆瓣`, '150101': `选择一项内容...`, '121902': `重置为默认值`, + '150102': `[上一页]...`, + '150103': `[下一页]...`, + '150104': `[下一页(插件中登录开启此功能)]...`, //content '200101': `。`, diff --git a/src/org/wanxp/main.ts b/src/org/wanxp/main.ts index 395ab2a..7a2369c 100644 --- a/src/org/wanxp/main.ts +++ b/src/org/wanxp/main.ts @@ -22,6 +22,8 @@ import {DoubanSyncModal} from "./douban/component/DoubanSyncModal"; import SyncHandler from "./douban/sync/handler/SyncHandler"; import {SyncConfig} from "./douban/sync/model/SyncConfig"; import GlobalStatusHolder from "./douban/model/GlobalStatusHolder"; +import DoubanSearchResultSubject from "./douban/data/model/DoubanSearchResultSubject"; +import {SearchPageInfo} from "./douban/data/model/SearchPageInfo"; export default class DoubanPlugin extends Plugin { public settings: DoubanPluginSetting; @@ -111,8 +113,9 @@ export default class DoubanPlugin extends Plugin { async search(searchTerm: string, context: HandleContext) { try { this.showStatus(i18nHelper.getMessage('140201', searchTerm)); - const resultList = await Searcher.search(searchTerm, this.settings, context.plugin.settingsManager); + const resultList:DoubanSearchResultSubject[] = await Searcher.search(searchTerm, this.settings, context.plugin.settingsManager); this.showStatus(i18nHelper.getMessage('140202', resultList.length.toString())); + context.searchPage = new SearchPageInfo(0,0,20, true); new DoubanFuzzySuggester(this, context).showSearchList(resultList); } catch (e) { log.error(i18nHelper.getMessage('140206').replace('{0}', e.message), e);