├── .eslintignore
├── .github
└── workflows
│ ├── build.yml
│ └── deploy.yml
├── .gitignore
├── .gitmodules
├── .npmignore
├── .prettierrc.js
├── .stylelintrc.js
├── .vscode
└── settings.json
├── LICENSE
├── README.md
├── commitlint.config.js
├── docs
├── .vitepress
│ ├── config.mts
│ └── theme
│ │ ├── GLayout.vue
│ │ ├── index.ts
│ │ └── style.css
├── development
│ └── getting-started.md
├── en
│ └── index.md
├── getting-started.md
├── index.md
├── parts
│ ├── screenshot.md
│ └── wip.md
├── public
│ ├── logo.png
│ ├── logo.webp
│ ├── qq_qr.png
│ ├── screenshot
│ │ ├── 1.png
│ │ ├── 10.png
│ │ ├── 11.png
│ │ ├── 12.png
│ │ ├── 13.png
│ │ ├── 14.png
│ │ ├── 15.png
│ │ ├── 16.png
│ │ ├── 17.png
│ │ ├── 18.png
│ │ ├── 19.png
│ │ ├── 2.png
│ │ ├── 20.png
│ │ ├── 21.png
│ │ ├── 22.png
│ │ ├── 23.png
│ │ ├── 24.png
│ │ ├── 25.png
│ │ ├── 26.png
│ │ ├── 27.png
│ │ ├── 28.png
│ │ ├── 3.png
│ │ ├── 4.png
│ │ ├── 5.png
│ │ ├── 6.png
│ │ ├── 7.png
│ │ ├── 8.png
│ │ └── 9.png
│ └── team
│ │ ├── duchuanbo.jpg
│ │ ├── kongqi.jpg
│ │ ├── tiger.jpg
│ │ └── 吉王义昊.webp
├── team.md
└── tutorial
│ ├── basic.md
│ ├── community.md
│ ├── concepts.md
│ ├── functions.md
│ └── setting.md
├── husky.sh
├── package-lock.json
├── package.json
├── quickapp.config.js
├── src
├── app.ux
├── common
│ ├── css
│ │ └── page.css
│ ├── icon
│ │ ├── author.png
│ │ ├── back.png
│ │ ├── book-code.png
│ │ ├── catalog.png
│ │ ├── check-active.png
│ │ ├── check.png
│ │ ├── close.png
│ │ ├── code.png
│ │ ├── down.png
│ │ ├── explore-active.png
│ │ ├── explore.png
│ │ ├── group.png
│ │ ├── help.png
│ │ ├── history.png
│ │ ├── home-active.png
│ │ ├── home.png
│ │ ├── info.png
│ │ ├── latest.png
│ │ ├── left-double.png
│ │ ├── left.png
│ │ ├── library.png
│ │ ├── magic.png
│ │ ├── menu.png
│ │ ├── password.png
│ │ ├── refresh.png
│ │ ├── right-double.png
│ │ ├── right.png
│ │ ├── search.png
│ │ ├── setting.png
│ │ ├── source.png
│ │ ├── submit.png
│ │ ├── trash.png
│ │ ├── ui.png
│ │ ├── up.png
│ │ ├── user-active.png
│ │ ├── user.png
│ │ └── web.png
│ ├── image
│ │ ├── bookcover.png
│ │ ├── cover.jpg
│ │ ├── qr-feed.png
│ │ ├── qr-github.png
│ │ └── qr-qq.png
│ └── logo
│ │ ├── 192-round.png
│ │ └── 96-round.png
├── config-watch.json
├── i18n
│ ├── defaults.json
│ ├── en.json
│ └── zh-CN.json
├── manifest.json
├── pages
│ ├── about
│ │ └── about.ux
│ ├── catalog
│ │ └── catalog.ux
│ ├── content
│ │ └── content.ux
│ ├── detail
│ │ └── detail.ux
│ ├── index
│ │ └── index.ux
│ ├── search
│ │ └── search.ux
│ ├── searchResult
│ │ └── searchResult.ux
│ ├── setting
│ │ └── setting.ux
│ └── source
│ │ └── source.ux
├── third-party
│ └── JSONPath
│ │ └── jsonpath.ts
└── utils
│ ├── book.ts
│ ├── chapter.ts
│ ├── cookie.ts
│ ├── fetch.ts
│ ├── index.ts
│ ├── jsExtension.ts
│ ├── source.ts
│ └── tsimports.js
└── tsconfig.json
/.eslintignore:
--------------------------------------------------------------------------------
1 | /.nyc_output
2 | /coverage
3 | /node_modules
4 | /tests/fixtures
5 | /sign
6 | /dist
7 | /build
8 |
--------------------------------------------------------------------------------
/.github/workflows/build.yml:
--------------------------------------------------------------------------------
1 | name: Node.js CI
2 |
3 | on:
4 | push:
5 | branches: [ "main" ]
6 | pull_request:
7 | branches: [ "main" ]
8 |
9 | jobs:
10 | build:
11 |
12 | runs-on: ubuntu-latest
13 |
14 | strategy:
15 | matrix:
16 | node-version: [22.x]
17 | # See supported Node.js release schedule at https://nodejs.org/en/about/releases/
18 |
19 | steps:
20 | - uses: actions/checkout@v4
21 | - name: Use Node.js ${{ matrix.node-version }}
22 | uses: actions/setup-node@v4
23 | with:
24 | node-version: ${{ matrix.node-version }}
25 | cache: 'npm'
26 | - run: git submodule init && git submodule update
27 | - run: npm i
28 | - run: npm run build
29 |
30 | - name: upload artifact
31 | uses: actions/upload-artifact@v4
32 | with:
33 | path: dist/*.rpk
34 |
--------------------------------------------------------------------------------
/.github/workflows/deploy.yml:
--------------------------------------------------------------------------------
1 | # Simple workflow for deploying static content to GitHub Pages
2 | name: Deploy to Pages
3 |
4 | on:
5 | # Runs on pushes targeting the default branch
6 | push:
7 | branches: ["main"]
8 |
9 | # Allows you to run this workflow manually from the Actions tab
10 | workflow_dispatch:
11 |
12 | # Sets permissions of the GITHUB_TOKEN to allow deployment to GitHub Pages
13 | permissions:
14 | contents: read
15 | pages: write
16 | id-token: write
17 |
18 | # Allow only one concurrent deployment, skipping runs queued between the run in-progress and latest queued.
19 | # However, do NOT cancel in-progress runs as we want to allow these production deployments to complete.
20 | concurrency:
21 | group: "pages"
22 | cancel-in-progress: false
23 |
24 | jobs:
25 | # Single deploy job since we're just deploying
26 | deploy:
27 | environment:
28 | name: github-pages
29 | url: ${{ steps.deployment.outputs.page_url }}
30 | runs-on: ubuntu-latest
31 | steps:
32 | - name: Checkout
33 | uses: actions/checkout@v4
34 |
35 | - name: Setup Pages
36 | uses: actions/configure-pages@v5
37 |
38 | - name: Build VitePress
39 | run: |
40 | npm install
41 | npm run docs:build
42 | shell: bash
43 |
44 | - name: Upload artifact
45 | uses: actions/upload-pages-artifact@v3
46 | with:
47 | # Upload entire repository
48 | path: "./docs/.vitepress/dist"
49 |
50 | - name: Deploy to GitHub Pages
51 | id: deployment
52 | uses: actions/deploy-pages@v4
53 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | /.nyc_output
2 | /coverage
3 | /node_modules
4 | /sign
5 | /dist
6 | /build
7 | .husky/
8 | docs/.vitepress/dist
9 | docs/.vitepress/cache
10 | .idea
11 | src/utils/test.ts
12 |
--------------------------------------------------------------------------------
/.gitmodules:
--------------------------------------------------------------------------------
1 | [submodule "src/third-party/Vela_input_method"]
2 | path = src/third-party/Vela_input_method
3 | url = https://github.com/NEORUAA/Vela_input_method.git
4 | [submodule "src/third-party/GBK.js"]
5 | path = src/third-party/GBK.js
6 | url = https://github.com/cnwhy/GBK.js.git
7 |
--------------------------------------------------------------------------------
/.npmignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | build
3 | dist
4 |
5 | .DS_Store
6 | Thumbs.db
7 |
8 | *.log
9 | *.iml
10 | .idea/
11 | .vscode/
12 |
13 | .nyc_output
14 | /coverage
15 |
16 | /logs
17 |
--------------------------------------------------------------------------------
/.prettierrc.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | printWidth: 100, // 指定代码长度,超出换行
3 | tabWidth: 2, // tab 键的宽度
4 | useTabs: false, // 使用空格替代tab
5 | semi: false, // 结尾加上分号
6 | singleQuote: false, // 使用单引号
7 | quoteProps: "consistent", // 要求对象字面量属性是否使用引号包裹,(‘as-needed’: 没有特殊要求,禁止使用,'consistent': 保持一致 , preserve: 不限制,想用就用)
8 | trailingComma: "none", // 不添加对象和数组最后一个元素的逗号
9 | bracketSpacing: false, // 对象中对空格和空行进行处理
10 | jsxBracketSameLine: false, // 在多行JSX元素的最后一行追加 >
11 | requirePragma: false, // 是否严格按照文件顶部的特殊注释格式化代码
12 | insertPragma: false, // 是否在格式化的文件顶部插入Pragma标记,以表明该文件被prettier格式化过了
13 | proseWrap: "preserve", // 按照文件原样折行
14 | htmlWhitespaceSensitivity: "ignore", // html文件的空格敏感度,控制空格是否影响布局
15 | endOfLine: "auto", // 结尾是 \n \r \n\r auto
16 | overrides: [
17 | {
18 | files: "*.ux",
19 | options: {parser: "vue"}
20 | }
21 | ]
22 | }
23 |
--------------------------------------------------------------------------------
/.stylelintrc.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | extends: [
3 | "stylelint-config-standard",
4 | "stylelint-config-recess-order"
5 | // "stylelint-selector-bem-pattern"
6 | ],
7 | ignoreFiles: ["node_modules", "test", "dist", "**/*.js"],
8 | rules: {
9 | "no-descending-specificity": null,
10 | "color-hex-case": "lower",
11 | "color-hex-length": "short",
12 | "at-rule-no-unknown": null,
13 | "block-no-empty": null,
14 | "selector-pseudo-class-no-unknown": [
15 | true,
16 | {
17 | ignorePseudoClasses: ["blur"]
18 | }
19 | ],
20 | "property-no-unknown": [
21 | true,
22 | {
23 | ignoreProperties: [
24 | "placeholder-color",
25 | "gradient-start",
26 | "gradient-center",
27 | "gradient-end",
28 | "caret-color",
29 | "selected-color",
30 | "block-color"
31 | ]
32 | }
33 | ],
34 | "max-line-length": null,
35 | // "indentation": 2,
36 | // "no-empty-source": null,
37 | "selector-type-no-unknown": [
38 | true,
39 | {
40 | ignoreTypes: ["selected-color", "block-color"]
41 | }
42 | ]
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/.vscode/settings.json:
--------------------------------------------------------------------------------
1 | {
2 | "Codegeex.CommitMessageStyle": "ConventionalCommits",
3 | "Codegeex.GenerationPreference": "line by line"
4 | }
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Mozilla Public License Version 2.0
2 | ==================================
3 |
4 | 1. Definitions
5 | --------------
6 |
7 | 1.1. "Contributor"
8 | means each individual or legal entity that creates, contributes to
9 | the creation of, or owns Covered Software.
10 |
11 | 1.2. "Contributor Version"
12 | means the combination of the Contributions of others (if any) used
13 | by a Contributor and that particular Contributor's Contribution.
14 |
15 | 1.3. "Contribution"
16 | means Covered Software of a particular Contributor.
17 |
18 | 1.4. "Covered Software"
19 | means Source Code Form to which the initial Contributor has attached
20 | the notice in Exhibit A, the Executable Form of such Source Code
21 | Form, and Modifications of such Source Code Form, in each case
22 | including portions thereof.
23 |
24 | 1.5. "Incompatible With Secondary Licenses"
25 | means
26 |
27 | (a) that the initial Contributor has attached the notice described
28 | in Exhibit B to the Covered Software; or
29 |
30 | (b) that the Covered Software was made available under the terms of
31 | version 1.1 or earlier of the License, but not also under the
32 | terms of a Secondary License.
33 |
34 | 1.6. "Executable Form"
35 | means any form of the work other than Source Code Form.
36 |
37 | 1.7. "Larger Work"
38 | means a work that combines Covered Software with other material, in
39 | a separate file or files, that is not Covered Software.
40 |
41 | 1.8. "License"
42 | means this document.
43 |
44 | 1.9. "Licensable"
45 | means having the right to grant, to the maximum extent possible,
46 | whether at the time of the initial grant or subsequently, any and
47 | all of the rights conveyed by this License.
48 |
49 | 1.10. "Modifications"
50 | means any of the following:
51 |
52 | (a) any file in Source Code Form that results from an addition to,
53 | deletion from, or modification of the contents of Covered
54 | Software; or
55 |
56 | (b) any new file in Source Code Form that contains any Covered
57 | Software.
58 |
59 | 1.11. "Patent Claims" of a Contributor
60 | means any patent claim(s), including without limitation, method,
61 | process, and apparatus claims, in any patent Licensable by such
62 | Contributor that would be infringed, but for the grant of the
63 | License, by the making, using, selling, offering for sale, having
64 | made, import, or transfer of either its Contributions or its
65 | Contributor Version.
66 |
67 | 1.12. "Secondary License"
68 | means either the GNU General Public License, Version 2.0, the GNU
69 | Lesser General Public License, Version 2.1, the GNU Affero General
70 | Public License, Version 3.0, or any later versions of those
71 | licenses.
72 |
73 | 1.13. "Source Code Form"
74 | means the form of the work preferred for making modifications.
75 |
76 | 1.14. "You" (or "Your")
77 | means an individual or a legal entity exercising rights under this
78 | License. For legal entities, "You" includes any entity that
79 | controls, is controlled by, or is under common control with You. For
80 | purposes of this definition, "control" means (a) the power, direct
81 | or indirect, to cause the direction or management of such entity,
82 | whether by contract or otherwise, or (b) ownership of more than
83 | fifty percent (50%) of the outstanding shares or beneficial
84 | ownership of such entity.
85 |
86 | 2. License Grants and Conditions
87 | --------------------------------
88 |
89 | 2.1. Grants
90 |
91 | Each Contributor hereby grants You a world-wide, royalty-free,
92 | non-exclusive license:
93 |
94 | (a) under intellectual property rights (other than patent or trademark)
95 | Licensable by such Contributor to use, reproduce, make available,
96 | modify, display, perform, distribute, and otherwise exploit its
97 | Contributions, either on an unmodified basis, with Modifications, or
98 | as part of a Larger Work; and
99 |
100 | (b) under Patent Claims of such Contributor to make, use, sell, offer
101 | for sale, have made, import, and otherwise transfer either its
102 | Contributions or its Contributor Version.
103 |
104 | 2.2. Effective Date
105 |
106 | The licenses granted in Section 2.1 with respect to any Contribution
107 | become effective for each Contribution on the date the Contributor first
108 | distributes such Contribution.
109 |
110 | 2.3. Limitations on Grant Scope
111 |
112 | The licenses granted in this Section 2 are the only rights granted under
113 | this License. No additional rights or licenses will be implied from the
114 | distribution or licensing of Covered Software under this License.
115 | Notwithstanding Section 2.1(b) above, no patent license is granted by a
116 | Contributor:
117 |
118 | (a) for any code that a Contributor has removed from Covered Software;
119 | or
120 |
121 | (b) for infringements caused by: (i) Your and any other third party's
122 | modifications of Covered Software, or (ii) the combination of its
123 | Contributions with other software (except as part of its Contributor
124 | Version); or
125 |
126 | (c) under Patent Claims infringed by Covered Software in the absence of
127 | its Contributions.
128 |
129 | This License does not grant any rights in the trademarks, service marks,
130 | or logos of any Contributor (except as may be necessary to comply with
131 | the notice requirements in Section 3.4).
132 |
133 | 2.4. Subsequent Licenses
134 |
135 | No Contributor makes additional grants as a result of Your choice to
136 | distribute the Covered Software under a subsequent version of this
137 | License (see Section 10.2) or under the terms of a Secondary License (if
138 | permitted under the terms of Section 3.3).
139 |
140 | 2.5. Representation
141 |
142 | Each Contributor represents that the Contributor believes its
143 | Contributions are its original creation(s) or it has sufficient rights
144 | to grant the rights to its Contributions conveyed by this License.
145 |
146 | 2.6. Fair Use
147 |
148 | This License is not intended to limit any rights You have under
149 | applicable copyright doctrines of fair use, fair dealing, or other
150 | equivalents.
151 |
152 | 2.7. Conditions
153 |
154 | Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted
155 | in Section 2.1.
156 |
157 | 3. Responsibilities
158 | -------------------
159 |
160 | 3.1. Distribution of Source Form
161 |
162 | All distribution of Covered Software in Source Code Form, including any
163 | Modifications that You create or to which You contribute, must be under
164 | the terms of this License. You must inform recipients that the Source
165 | Code Form of the Covered Software is governed by the terms of this
166 | License, and how they can obtain a copy of this License. You may not
167 | attempt to alter or restrict the recipients' rights in the Source Code
168 | Form.
169 |
170 | 3.2. Distribution of Executable Form
171 |
172 | If You distribute Covered Software in Executable Form then:
173 |
174 | (a) such Covered Software must also be made available in Source Code
175 | Form, as described in Section 3.1, and You must inform recipients of
176 | the Executable Form how they can obtain a copy of such Source Code
177 | Form by reasonable means in a timely manner, at a charge no more
178 | than the cost of distribution to the recipient; and
179 |
180 | (b) You may distribute such Executable Form under the terms of this
181 | License, or sublicense it under different terms, provided that the
182 | license for the Executable Form does not attempt to limit or alter
183 | the recipients' rights in the Source Code Form under this License.
184 |
185 | 3.3. Distribution of a Larger Work
186 |
187 | You may create and distribute a Larger Work under terms of Your choice,
188 | provided that You also comply with the requirements of this License for
189 | the Covered Software. If the Larger Work is a combination of Covered
190 | Software with a work governed by one or more Secondary Licenses, and the
191 | Covered Software is not Incompatible With Secondary Licenses, this
192 | License permits You to additionally distribute such Covered Software
193 | under the terms of such Secondary License(s), so that the recipient of
194 | the Larger Work may, at their option, further distribute the Covered
195 | Software under the terms of either this License or such Secondary
196 | License(s).
197 |
198 | 3.4. Notices
199 |
200 | You may not remove or alter the substance of any license notices
201 | (including copyright notices, patent notices, disclaimers of warranty,
202 | or limitations of liability) contained within the Source Code Form of
203 | the Covered Software, except that You may alter any license notices to
204 | the extent required to remedy known factual inaccuracies.
205 |
206 | 3.5. Application of Additional Terms
207 |
208 | You may choose to offer, and to charge a fee for, warranty, support,
209 | indemnity or liability obligations to one or more recipients of Covered
210 | Software. However, You may do so only on Your own behalf, and not on
211 | behalf of any Contributor. You must make it absolutely clear that any
212 | such warranty, support, indemnity, or liability obligation is offered by
213 | You alone, and You hereby agree to indemnify every Contributor for any
214 | liability incurred by such Contributor as a result of warranty, support,
215 | indemnity or liability terms You offer. You may include additional
216 | disclaimers of warranty and limitations of liability specific to any
217 | jurisdiction.
218 |
219 | 4. Inability to Comply Due to Statute or Regulation
220 | ---------------------------------------------------
221 |
222 | If it is impossible for You to comply with any of the terms of this
223 | License with respect to some or all of the Covered Software due to
224 | statute, judicial order, or regulation then You must: (a) comply with
225 | the terms of this License to the maximum extent possible; and (b)
226 | describe the limitations and the code they affect. Such description must
227 | be placed in a text file included with all distributions of the Covered
228 | Software under this License. Except to the extent prohibited by statute
229 | or regulation, such description must be sufficiently detailed for a
230 | recipient of ordinary skill to be able to understand it.
231 |
232 | 5. Termination
233 | --------------
234 |
235 | 5.1. The rights granted under this License will terminate automatically
236 | if You fail to comply with any of its terms. However, if You become
237 | compliant, then the rights granted under this License from a particular
238 | Contributor are reinstated (a) provisionally, unless and until such
239 | Contributor explicitly and finally terminates Your grants, and (b) on an
240 | ongoing basis, if such Contributor fails to notify You of the
241 | non-compliance by some reasonable means prior to 60 days after You have
242 | come back into compliance. Moreover, Your grants from a particular
243 | Contributor are reinstated on an ongoing basis if such Contributor
244 | notifies You of the non-compliance by some reasonable means, this is the
245 | first time You have received notice of non-compliance with this License
246 | from such Contributor, and You become compliant prior to 30 days after
247 | Your receipt of the notice.
248 |
249 | 5.2. If You initiate litigation against any entity by asserting a patent
250 | infringement claim (excluding declaratory judgment actions,
251 | counter-claims, and cross-claims) alleging that a Contributor Version
252 | directly or indirectly infringes any patent, then the rights granted to
253 | You by any and all Contributors for the Covered Software under Section
254 | 2.1 of this License shall terminate.
255 |
256 | 5.3. In the event of termination under Sections 5.1 or 5.2 above, all
257 | end user license agreements (excluding distributors and resellers) which
258 | have been validly granted by You or Your distributors under this License
259 | prior to termination shall survive termination.
260 |
261 | ************************************************************************
262 | * *
263 | * 6. Disclaimer of Warranty *
264 | * ------------------------- *
265 | * *
266 | * Covered Software is provided under this License on an "as is" *
267 | * basis, without warranty of any kind, either expressed, implied, or *
268 | * statutory, including, without limitation, warranties that the *
269 | * Covered Software is free of defects, merchantable, fit for a *
270 | * particular purpose or non-infringing. The entire risk as to the *
271 | * quality and performance of the Covered Software is with You. *
272 | * Should any Covered Software prove defective in any respect, You *
273 | * (not any Contributor) assume the cost of any necessary servicing, *
274 | * repair, or correction. This disclaimer of warranty constitutes an *
275 | * essential part of this License. No use of any Covered Software is *
276 | * authorized under this License except under this disclaimer. *
277 | * *
278 | ************************************************************************
279 |
280 | ************************************************************************
281 | * *
282 | * 7. Limitation of Liability *
283 | * -------------------------- *
284 | * *
285 | * Under no circumstances and under no legal theory, whether tort *
286 | * (including negligence), contract, or otherwise, shall any *
287 | * Contributor, or anyone who distributes Covered Software as *
288 | * permitted above, be liable to You for any direct, indirect, *
289 | * special, incidental, or consequential damages of any character *
290 | * including, without limitation, damages for lost profits, loss of *
291 | * goodwill, work stoppage, computer failure or malfunction, or any *
292 | * and all other commercial damages or losses, even if such party *
293 | * shall have been informed of the possibility of such damages. This *
294 | * limitation of liability shall not apply to liability for death or *
295 | * personal injury resulting from such party's negligence to the *
296 | * extent applicable law prohibits such limitation. Some *
297 | * jurisdictions do not allow the exclusion or limitation of *
298 | * incidental or consequential damages, so this exclusion and *
299 | * limitation may not apply to You. *
300 | * *
301 | ************************************************************************
302 |
303 | 8. Litigation
304 | -------------
305 |
306 | Any litigation relating to this License may be brought only in the
307 | courts of a jurisdiction where the defendant maintains its principal
308 | place of business and such litigation shall be governed by laws of that
309 | jurisdiction, without reference to its conflict-of-law provisions.
310 | Nothing in this Section shall prevent a party's ability to bring
311 | cross-claims or counter-claims.
312 |
313 | 9. Miscellaneous
314 | ----------------
315 |
316 | This License represents the complete agreement concerning the subject
317 | matter hereof. If any provision of this License is held to be
318 | unenforceable, such provision shall be reformed only to the extent
319 | necessary to make it enforceable. Any law or regulation which provides
320 | that the language of a contract shall be construed against the drafter
321 | shall not be used to construe this License against a Contributor.
322 |
323 | 10. Versions of the License
324 | ---------------------------
325 |
326 | 10.1. New Versions
327 |
328 | Mozilla Foundation is the license steward. Except as provided in Section
329 | 10.3, no one other than the license steward has the right to modify or
330 | publish new versions of this License. Each version will be given a
331 | distinguishing version number.
332 |
333 | 10.2. Effect of New Versions
334 |
335 | You may distribute the Covered Software under the terms of the version
336 | of the License under which You originally received the Covered Software,
337 | or under the terms of any subsequent version published by the license
338 | steward.
339 |
340 | 10.3. Modified Versions
341 |
342 | If you create software not governed by this License, and you want to
343 | create a new license for such software, you may create and use a
344 | modified version of this License if you rename the license and remove
345 | any references to the name of the license steward (except to note that
346 | such modified license differs from this License).
347 |
348 | 10.4. Distributing Source Code Form that is Incompatible With Secondary
349 | Licenses
350 |
351 | If You choose to distribute Source Code Form that is Incompatible With
352 | Secondary Licenses under the terms of this version of the License, the
353 | notice described in Exhibit B of this License must be attached.
354 |
355 | Exhibit A - Source Code Form License Notice
356 | -------------------------------------------
357 |
358 | This Source Code Form is subject to the terms of the Mozilla Public
359 | License, v. 2.0. If a copy of the MPL was not distributed with this
360 | file, You can obtain one at http://mozilla.org/MPL/2.0/.
361 |
362 | If it is not possible or desirable to put the notice in a particular
363 | file, then You may include the notice in a location (such as a LICENSE
364 | file in a relevant directory) where a recipient would be likely to look
365 | for such a notice.
366 |
367 | You may add additional accurate notices of copyright ownership.
368 |
369 | Exhibit B - "Incompatible With Secondary Licenses" Notice
370 | ---------------------------------------------------------
371 |
372 | This Source Code Form is "Incompatible With Secondary Licenses", as
373 | defined by the Mozilla Public License, v. 2.0.
374 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Lordly·阅读
2 |
3 |
4 |

5 |
6 | Lordly·阅读
7 |
8 |
read.lordly.vip
9 |
10 | 支持「开源阅读」规则的手环在线阅读工具
11 |
12 |
13 | [](https://github.com/Lordly-Tech/LordlyRead/blob/main/LICENSE)
14 | [](https://wakatime.com/badge/user/7ef6dafe-00d7-45e3-b4c7-27b1f5d4d735/project/47bae092-8953-4da1-a089-e75e4ec13996)
15 | [](https://github.com/Lordly-Tech/LordlyRead/actions)
16 | [](https://github.com/Lordly-Tech/LordlyRead/commits)
17 | [](https://github.com/Lordly-Tech/LordlyRead/releases/latest)
18 | [](https://github.com/Lordly-Tech/LordlyRead/releases/latest)
19 | [](https://github.com/Lordly-Tech/LordlyRead/releases)
20 |
21 |
22 | > 新用户?请查看我们的[使用指南](https://read.lordly.vip/getting-started)。
23 |
24 | ## 主要功能
25 |
26 | 1. 自定义书源,自己设置规则,抓取网页数据,规则简单易懂,官网内有开发指南(WIP)。
27 | 2. (WIP)列表书架,网格书架自由切换。
28 | 3. 书源规则支持搜索及发现(WIP),所有找书看书功能全部自定义,找书更方便。
29 | 4. (WIP)支持替换净化,去除广告替换内容很方便。
30 | 5. (WIP)支持本地TXT、EPUB阅读,手动浏览,智能扫描。
31 | 6. 支持高度自定义阅读界面,切换颜色、行距、段距、加粗等。
32 | 7. (WIP)支持多种翻页模式,覆盖、仿真、滑动、滚动等。
33 | 8. 软件开源,持续优化,无广告。
34 |
35 | ## 开源许可
36 |
37 | 本项目开发过程中很大程度上参考了「[开源阅读](https://github.com/gedoor/legado)」的设计和规则。在此对「开源阅读」的开发者表示感谢。
38 |
39 | 但本项目在开发过程中并未直接使用或参考「开源阅读」的具体实现(u1s1,看着头疼 www),因此本项目并不是「开源阅读」的分支或衍生项目,不会跟进「开源阅读」的更新,也不受「开源阅读」`GPL-3` 许可证的约束。
40 |
41 | 经过综合考量,本项目最终使用 `MPL-2.0` 许可证开源,这意味着您可以自由使用、修改、分发本项目的代码,但是您的衍生项目(中使用到本项目的部分以及对本项目的更改)必须使用相同的许可证开源(具体规则以 `MPL-2.0` 协议约束为准)。
42 |
43 | ## 软件截图
44 |
45 | 


























46 |
47 | ## 免责声明(修改自 [https://gedoor.github.io/Disclaimer](https://gedoor.github.io/Disclaimer))
48 |
49 | 「Lordly·阅读」是一款提供网络文学搜索的工具,为广大网络文学爱好者提供一种方便、快捷舒适的试读体验。
50 |
51 | 当您搜索一本书的时,「Lordly·阅读」会将该书的书名以关键词的形式提交到各个第三方网络文学网站。各第三方网站返回的内与「Lordly·阅读」无关,「Lordly·阅读」对其概不负责,亦不承担任何法律责任。任何通过使用「Lordly·阅读」而链接到的第三方网页均系他人制作或供,您可能从第三方网页上获得其他服务,「Lordly·阅读」对其合法性概不负责,亦不承担任何法律责任。第三方搜索引擎结果据您提交的书名自动搜索获得并提供试读,不代表「Lordly·阅读」赞成或被搜索链接到的第三方网页上的内容或立场。您应该对用搜索引擎的结果自行承担风险。
52 |
53 | 「Lordly·阅读」不做任何形式的保证:不保证第三方搜索引擎的搜索结果满足您的要求,不保证搜索服务不中断,不保证搜索结的安全性、正确性、及时性、合法性。因网络状况、通讯线路、第三方网站等任何原因而导致您不能正常使用「Lordly·阅读」,读不承担任何法律责任。「Lordly·阅读」尊重并保护所有使用「Lordly·阅读」用户的个人隐私权,您注册的用户名、电子邮件地址等个人料,非经您亲自许可或根据相关法律、法规的强制性规定,「Lordly·阅读」不会主动地泄露给第三方。
54 |
55 | 「Lordly·阅读」致力于最大程度地减少网络文学阅读者在自行搜寻过程中的无意义的时间浪费,通过专业搜索展示不同网站中网文学的最新章节。阅读在为广大小说爱好者提供方便、快捷舒适的试读体验的同时,也使优秀网络文学得以迅速、更泛的传播,从而达到了在一定程度促进网络文学充分繁荣发展之目的。阅读鼓励广大小说爱好者通过阅读发现优秀网小说及其提供商,并建议阅读正版图书。
56 |
57 | 任何单位或个人认为通过阅读搜索链接到的第三方网页内容可能涉嫌侵犯其信息网络传播权,应该及时向阅读提出书权力通知,并提供身份证明、权属证明及详细侵权情况证明。阅读在收到上述法律文件后,将会依法尽快断开相关链内容。
58 |
59 | ## 联系我们
60 |
61 | ### QQ 交流群(扫描或点击下方二维码快捷加入)
62 |
63 | [](https://qm.qq.com/q/s94RXV7eHC)
64 |
65 | ### GitHub Discussions
66 |
67 | GitHub Discussions 地址:https://github.com/Lordly-Tech/LordlyRead/discussions
68 |
69 | ## ✨ Stargazers over time
70 |
71 | 感谢大家的支持!
72 |
73 | [](https://starchart.cc/Lordly-Tech/LordlyRead)
74 |
--------------------------------------------------------------------------------
/commitlint.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | extends: ["@commitlint/config-conventional"],
3 | rules: {
4 | "type-enum": [
5 | 2,
6 | "always",
7 | [
8 | "bug", // 此项特别针对bug号,用于向测试反馈bug列表的bug修改情况
9 | "feat", // 新功能(feature)
10 | "fix", // 修补bug
11 | "docs", // 文档(documentation)
12 | "style", // 格式(不影响代码运行的变动)
13 | "refactor", // 重构(即不是新增功能,也不是修改bug的代码变动)
14 | "test", // 增加测试
15 | "chore", // 构建过程或辅助工具的变动
16 | "revert", // feat(pencil): add ‘graphiteWidth’ option (撤销之前的commit)
17 | "merge" // 合并分支, 例如: merge(前端页面): feature-xxxx修改线程地址
18 | ]
19 | ]
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/docs/.vitepress/config.mts:
--------------------------------------------------------------------------------
1 | import {defineConfig} from "vitepress"
2 |
3 | // https://vitepress.dev/reference/site-config
4 | export default defineConfig({
5 | title: "Lordly·阅读",
6 | description: "支持「开源阅读」规则的手环在线阅读工具",
7 | head: [["link", {rel: "icon", href: "/logo.png", type: "image/png"}]],
8 | themeConfig: {
9 | // https://vitepress.dev/reference/default-theme-config
10 | nav: [
11 | {text: "首页", link: "/"},
12 | {text: "使用指南", link: "/getting-started"},
13 | {text: "团队", link: "/team"}
14 | ],
15 |
16 | sidebar: [
17 | {
18 | text: "使用指南",
19 | items: [
20 | {text: "简介&下载", link: "/getting-started"},
21 | {text: "基础教程", link: "/tutorial/basic"},
22 | {text: "概念介绍", link: "/tutorial/concepts"},
23 | {text: "主要功能及界面介绍", link: "/tutorial/functions"},
24 | {text: "设置及自定义", link: "/tutorial/setting"},
25 | {text: "主要社区 & 反馈渠道", link: "/tutorial/community"}
26 | ]
27 | }
28 | ],
29 |
30 | socialLinks: [{icon: "github", link: "https://github.com/Lordly-Tech/LordlyRead"}],
31 |
32 | footer: {
33 | message: "Released under the MPL-2.0 license",
34 | copyright: "Copyright © 2024-present, Jiwang Yihao (as part of the Lordly Tech Team)"
35 | },
36 |
37 | logo: "logo.png",
38 |
39 | editLink: {
40 | pattern: "https://github.com/Lordly-Tech/LordlyRead/edit/main/docs/:path",
41 | text: "在 GitHub 上编辑此页面"
42 | }
43 | },
44 |
45 | locales: {
46 | root: {
47 | label: "简体中文",
48 | lang: "zh-CN"
49 | },
50 | en: {
51 | label: "English",
52 | lang: "en",
53 |
54 | title: "Lordly·Read",
55 | description: "Supports Legado rule band reading tool",
56 |
57 | themeConfig: {
58 | nav: [
59 | {text: "Home", link: "/"},
60 | {text: "Getting Started", link: "/getting-started"}
61 | ],
62 | sidebar: [
63 | {
64 | text: "Getting Started"
65 | },
66 | {
67 | text: "Runtime API Examples"
68 | }
69 | ],
70 |
71 | logo: "logo.png"
72 | }
73 | }
74 | },
75 |
76 | lastUpdated: true,
77 | cleanUrls: true
78 | })
79 |
--------------------------------------------------------------------------------
/docs/.vitepress/theme/GLayout.vue:
--------------------------------------------------------------------------------
1 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
79 |
--------------------------------------------------------------------------------
/docs/.vitepress/theme/index.ts:
--------------------------------------------------------------------------------
1 | // https://vitepress.dev/guide/custom-theme
2 | import type {Theme} from "vitepress"
3 | import DefaultTheme from "vitepress/theme"
4 | import "./style.css"
5 | import "element-plus/dist/index.css"
6 | import GLayout from "./GLayout.vue"
7 |
8 | export default {
9 | extends: DefaultTheme,
10 | Layout: GLayout,
11 | enhanceApp({app, router, siteData}) {
12 | // ...
13 | }
14 | } satisfies Theme
15 |
--------------------------------------------------------------------------------
/docs/.vitepress/theme/style.css:
--------------------------------------------------------------------------------
1 | /**
2 | * Customize default theme styling by overriding CSS variables:
3 | * https://github.com/vuejs/vitepress/blob/main/src/client/theme-default/styles/vars.css
4 | */
5 |
6 | /**
7 | * Colors
8 | *
9 | * Each colors have exact same color scale system with 3 levels of solid
10 | * colors with different brightness, and 1 soft color.
11 | *
12 | * - `XXX-1`: The most solid color used mainly for colored text. It must
13 | * satisfy the contrast ratio against when used on top of `XXX-soft`.
14 | *
15 | * - `XXX-2`: The color used mainly for hover state of the button.
16 | *
17 | * - `XXX-3`: The color for solid background, such as bg color of the button.
18 | * It must satisfy the contrast ratio with pure white (#ffffff) text on
19 | * top of it.
20 | *
21 | * - `XXX-soft`: The color used for subtle background such as custom container
22 | * or badges. It must satisfy the contrast ratio when putting `XXX-1` colors
23 | * on top of it.
24 | *
25 | * The soft color must be semi transparent alpha channel. This is crucial
26 | * because it allows adding multiple "soft" colors on top of each other
27 | * to create a accent, such as when having inline code block inside
28 | * custom containers.
29 | *
30 | * - `default`: The color used purely for subtle indication without any
31 | * special meanings attched to it such as bg color for menu hover state.
32 | *
33 | * - `brand`: Used for primary brand colors, such as link text, button with
34 | * brand theme, etc.
35 | *
36 | * - `tip`: Used to indicate useful information. The default theme uses the
37 | * brand color for this by default.
38 | *
39 | * - `warning`: Used to indicate warning to the users. Used in custom
40 | * container, badges, etc.
41 | *
42 | * - `danger`: Used to show error, or dangerous message to the users. Used
43 | * in custom container, badges, etc.
44 | * -------------------------------------------------------------------------- */
45 |
46 | :root {
47 | --vp-c-default-1: var(--vp-c-gray-1);
48 | --vp-c-default-2: var(--vp-c-gray-2);
49 | --vp-c-default-3: var(--vp-c-gray-3);
50 | --vp-c-default-soft: var(--vp-c-gray-soft);
51 |
52 | --vp-c-brand-1: #5c74ac;
53 | --vp-c-brand-2: #5c74ac;
54 | --vp-c-brand-3: #5c74ac;
55 | --vp-c-brand-soft: var(--vp-c-indigo-soft);
56 |
57 | --vp-c-tip-1: var(--vp-c-brand-1);
58 | --vp-c-tip-2: var(--vp-c-brand-2);
59 | --vp-c-tip-3: var(--vp-c-brand-3);
60 | --vp-c-tip-soft: var(--vp-c-brand-soft);
61 |
62 | --vp-c-warning-1: var(--vp-c-yellow-1);
63 | --vp-c-warning-2: var(--vp-c-yellow-2);
64 | --vp-c-warning-3: var(--vp-c-yellow-3);
65 | --vp-c-warning-soft: var(--vp-c-yellow-soft);
66 |
67 | --vp-c-danger-1: var(--vp-c-red-1);
68 | --vp-c-danger-2: var(--vp-c-red-2);
69 | --vp-c-danger-3: var(--vp-c-red-3);
70 | --vp-c-danger-soft: var(--vp-c-red-soft);
71 | }
72 |
73 | /**
74 | * Component: Button
75 | * -------------------------------------------------------------------------- */
76 |
77 | :root {
78 | --vp-button-brand-border: transparent;
79 | --vp-button-brand-text: var(--vp-c-white);
80 | --vp-button-brand-bg: var(--vp-c-brand-3);
81 | --vp-button-brand-hover-border: transparent;
82 | --vp-button-brand-hover-text: var(--vp-c-white);
83 | --vp-button-brand-hover-bg: var(--vp-c-brand-2);
84 | --vp-button-brand-active-border: transparent;
85 | --vp-button-brand-active-text: var(--vp-c-white);
86 | --vp-button-brand-active-bg: var(--vp-c-brand-1);
87 | }
88 |
89 | /**
90 | * Component: Home
91 | * -------------------------------------------------------------------------- */
92 |
93 | :root {
94 | --vp-home-hero-name-color: transparent;
95 | --vp-home-hero-name-background: -webkit-linear-gradient(
96 | 120deg,
97 | #efc9f0 30%,
98 | #b1bff0
99 | );
100 |
101 | --vp-home-hero-image-background-image: linear-gradient(
102 | -45deg,
103 | #eda3f0 50%,
104 | #738ef0 50%
105 | );
106 | --vp-home-hero-image-filter: blur(44px);
107 | }
108 |
109 | @media (min-width: 640px) {
110 | :root {
111 | --vp-home-hero-image-filter: blur(56px);
112 | }
113 | }
114 |
115 | @media (min-width: 960px) {
116 | :root {
117 | --vp-home-hero-image-filter: blur(68px);
118 | }
119 | }
120 |
121 | /**
122 | * Component: Custom Block
123 | * -------------------------------------------------------------------------- */
124 |
125 | :root {
126 | --vp-custom-block-tip-border: transparent;
127 | --vp-custom-block-tip-text: var(--vp-c-text-1);
128 | --vp-custom-block-tip-bg: var(--vp-c-brand-soft);
129 | --vp-custom-block-tip-code-bg: var(--vp-c-brand-soft);
130 | }
131 |
132 | /**
133 | * Component: Algolia
134 | * -------------------------------------------------------------------------- */
135 |
136 | .DocSearch {
137 | --docsearch-primary-color: var(--vp-c-brand-1) !important;
138 | }
139 |
140 |
141 |
142 | .clip {
143 | filter: brightness(0.95);
144 | }
145 |
146 | .dark .clip {
147 | filter: none;
148 | }
--------------------------------------------------------------------------------
/docs/development/getting-started.md:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Lordly-Tech/LordlyRead/690aaccdc4cbf652d6ccf8f417f435ac1ff8ed60/docs/development/getting-started.md
--------------------------------------------------------------------------------
/docs/en/index.md:
--------------------------------------------------------------------------------
1 | ---
2 | # https://vitepress.dev/reference/default-theme-home-page
3 | layout: home
4 |
5 | hero:
6 | name: "Lordly·Read"
7 | text: "Supports Legado rule band reading tool"
8 | tagline: Open source project, welcome to PR or Issue
9 | actions:
10 | - theme: brand
11 | text: Getting Started
12 | link: /getting-started
13 | - theme: alt
14 | text: View on GitHub
15 | link: https://github.com/Lordly-Tech/LordlyRead
16 | image:
17 | src: /logo.png
18 | alt: Lordly·Read Logo
19 |
20 | features:
21 | - title: 📖Intelligent Source
22 | details: A rule-based Legado reader prioritizing compatibility and customization. It fetches and filters content, simplifying management and creating a personal library.
23 | - title: 🌈Visual Experience
24 | details: The interface design combines aesthetics and practicality with clean lines and harmonious colors. Attention to detail blends technology and art, offering an immersive reading experience.
25 | - title: ⚙️Customization
26 | details: The reading tool supports extensive customization, letting you create a unique environment for an enjoyable, tailored reading experience.
27 | ---
28 |
--------------------------------------------------------------------------------
/docs/getting-started.md:
--------------------------------------------------------------------------------
1 | # 简介&下载
2 |
3 | ::: warning
4 | Lordly·阅读为免费应用,其余一切收费或附带广告皆为盗版!请勿上当受骗!!!
5 | :::
6 |
7 | ## 主要功能
8 |
9 | 1. 自定义书源,自己设置规则,抓取网页数据,规则简单易懂,官网内有开发指南(WIP)。
10 | 2. (WIP)列表书架,网格书架自由切换。
11 | 3. 书源规则支持搜索及发现(WIP),所有找书看书功能全部自定义,找书更方便。
12 | 4. (WIP)支持替换净化,去除广告替换内容很方便。
13 | 5. (WIP)支持本地TXT、EPUB阅读,手动浏览,智能扫描。
14 | 6. 支持高度自定义阅读界面,切换颜色、行距、段距、加粗等。
15 | 7. (WIP)支持多种翻页模式,覆盖、仿真、滑动、滚动等。
16 | 8. 软件开源,持续优化,无广告。
17 |
18 | ## 软件截图
19 |
20 |
21 |
22 | ## 下载
23 |
24 | - [GitHub](https://github.com/Lordly-Tech/LordlyRead/releases)
25 | - More coming soon...
26 |
--------------------------------------------------------------------------------
/docs/index.md:
--------------------------------------------------------------------------------
1 | ---
2 | # https://vitepress.dev/reference/default-theme-home-page
3 | layout: home
4 |
5 | hero:
6 | name: "Lordly·阅读"
7 | text: "支持「开源阅读」规则的手环在线阅读工具"
8 | tagline: 本应用为开源项目,欢迎提交 PR 或 Issue
9 | actions:
10 | - theme: brand
11 | text: 开始了解
12 | link: /getting-started
13 | - theme: alt
14 | text: 在 GitHub 上查看
15 | link: https://github.com/Lordly-Tech/LordlyRead
16 | image:
17 | src: /logo.png
18 | alt: Lordly·阅读 Logo
19 |
20 | features:
21 | - title: 📖智能书源
22 | details: 兼容与特色并重的自定义书源功能,智能抓取和筛选所需内容,快速管理各类资源,轻松打造专属您的个性化阅读库。
23 | - title: 🌈视觉体验
24 | details: 界面设计注重美学与实用性的结合,简洁线条和柔和色彩搭配,对细节的极致追求,让您感受科技与艺术的完美融合。
25 | - title: ⚙️定制体验
26 | details: 阅读工具支持高度个性化定制功能,打造独一无二的阅读环境,满足不同审美需求,让每次阅读成为一种独特的享受。
27 | ---
28 |
29 | ## 图片展示
30 |
31 |
--------------------------------------------------------------------------------
/docs/parts/screenshot.md:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
--------------------------------------------------------------------------------
/docs/parts/wip.md:
--------------------------------------------------------------------------------
1 | ::: danger 注意
2 | 此页面尚未完工,欢迎您加入贡献。GitHub 地址:https://github.com/Lordly-Tech/LordlyRead
3 |
4 | 您也可以点击页面底部的「在 GitHub 上编辑此页面」前往 GitHub 查看对应文件。
5 | :::
--------------------------------------------------------------------------------
/docs/public/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Lordly-Tech/LordlyRead/690aaccdc4cbf652d6ccf8f417f435ac1ff8ed60/docs/public/logo.png
--------------------------------------------------------------------------------
/docs/public/logo.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Lordly-Tech/LordlyRead/690aaccdc4cbf652d6ccf8f417f435ac1ff8ed60/docs/public/logo.webp
--------------------------------------------------------------------------------
/docs/public/qq_qr.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Lordly-Tech/LordlyRead/690aaccdc4cbf652d6ccf8f417f435ac1ff8ed60/docs/public/qq_qr.png
--------------------------------------------------------------------------------
/docs/public/screenshot/1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Lordly-Tech/LordlyRead/690aaccdc4cbf652d6ccf8f417f435ac1ff8ed60/docs/public/screenshot/1.png
--------------------------------------------------------------------------------
/docs/public/screenshot/10.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Lordly-Tech/LordlyRead/690aaccdc4cbf652d6ccf8f417f435ac1ff8ed60/docs/public/screenshot/10.png
--------------------------------------------------------------------------------
/docs/public/screenshot/11.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Lordly-Tech/LordlyRead/690aaccdc4cbf652d6ccf8f417f435ac1ff8ed60/docs/public/screenshot/11.png
--------------------------------------------------------------------------------
/docs/public/screenshot/12.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Lordly-Tech/LordlyRead/690aaccdc4cbf652d6ccf8f417f435ac1ff8ed60/docs/public/screenshot/12.png
--------------------------------------------------------------------------------
/docs/public/screenshot/13.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Lordly-Tech/LordlyRead/690aaccdc4cbf652d6ccf8f417f435ac1ff8ed60/docs/public/screenshot/13.png
--------------------------------------------------------------------------------
/docs/public/screenshot/14.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Lordly-Tech/LordlyRead/690aaccdc4cbf652d6ccf8f417f435ac1ff8ed60/docs/public/screenshot/14.png
--------------------------------------------------------------------------------
/docs/public/screenshot/15.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Lordly-Tech/LordlyRead/690aaccdc4cbf652d6ccf8f417f435ac1ff8ed60/docs/public/screenshot/15.png
--------------------------------------------------------------------------------
/docs/public/screenshot/16.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Lordly-Tech/LordlyRead/690aaccdc4cbf652d6ccf8f417f435ac1ff8ed60/docs/public/screenshot/16.png
--------------------------------------------------------------------------------
/docs/public/screenshot/17.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Lordly-Tech/LordlyRead/690aaccdc4cbf652d6ccf8f417f435ac1ff8ed60/docs/public/screenshot/17.png
--------------------------------------------------------------------------------
/docs/public/screenshot/18.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Lordly-Tech/LordlyRead/690aaccdc4cbf652d6ccf8f417f435ac1ff8ed60/docs/public/screenshot/18.png
--------------------------------------------------------------------------------
/docs/public/screenshot/19.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Lordly-Tech/LordlyRead/690aaccdc4cbf652d6ccf8f417f435ac1ff8ed60/docs/public/screenshot/19.png
--------------------------------------------------------------------------------
/docs/public/screenshot/2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Lordly-Tech/LordlyRead/690aaccdc4cbf652d6ccf8f417f435ac1ff8ed60/docs/public/screenshot/2.png
--------------------------------------------------------------------------------
/docs/public/screenshot/20.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Lordly-Tech/LordlyRead/690aaccdc4cbf652d6ccf8f417f435ac1ff8ed60/docs/public/screenshot/20.png
--------------------------------------------------------------------------------
/docs/public/screenshot/21.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Lordly-Tech/LordlyRead/690aaccdc4cbf652d6ccf8f417f435ac1ff8ed60/docs/public/screenshot/21.png
--------------------------------------------------------------------------------
/docs/public/screenshot/22.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Lordly-Tech/LordlyRead/690aaccdc4cbf652d6ccf8f417f435ac1ff8ed60/docs/public/screenshot/22.png
--------------------------------------------------------------------------------
/docs/public/screenshot/23.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Lordly-Tech/LordlyRead/690aaccdc4cbf652d6ccf8f417f435ac1ff8ed60/docs/public/screenshot/23.png
--------------------------------------------------------------------------------
/docs/public/screenshot/24.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Lordly-Tech/LordlyRead/690aaccdc4cbf652d6ccf8f417f435ac1ff8ed60/docs/public/screenshot/24.png
--------------------------------------------------------------------------------
/docs/public/screenshot/25.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Lordly-Tech/LordlyRead/690aaccdc4cbf652d6ccf8f417f435ac1ff8ed60/docs/public/screenshot/25.png
--------------------------------------------------------------------------------
/docs/public/screenshot/26.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Lordly-Tech/LordlyRead/690aaccdc4cbf652d6ccf8f417f435ac1ff8ed60/docs/public/screenshot/26.png
--------------------------------------------------------------------------------
/docs/public/screenshot/27.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Lordly-Tech/LordlyRead/690aaccdc4cbf652d6ccf8f417f435ac1ff8ed60/docs/public/screenshot/27.png
--------------------------------------------------------------------------------
/docs/public/screenshot/28.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Lordly-Tech/LordlyRead/690aaccdc4cbf652d6ccf8f417f435ac1ff8ed60/docs/public/screenshot/28.png
--------------------------------------------------------------------------------
/docs/public/screenshot/3.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Lordly-Tech/LordlyRead/690aaccdc4cbf652d6ccf8f417f435ac1ff8ed60/docs/public/screenshot/3.png
--------------------------------------------------------------------------------
/docs/public/screenshot/4.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Lordly-Tech/LordlyRead/690aaccdc4cbf652d6ccf8f417f435ac1ff8ed60/docs/public/screenshot/4.png
--------------------------------------------------------------------------------
/docs/public/screenshot/5.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Lordly-Tech/LordlyRead/690aaccdc4cbf652d6ccf8f417f435ac1ff8ed60/docs/public/screenshot/5.png
--------------------------------------------------------------------------------
/docs/public/screenshot/6.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Lordly-Tech/LordlyRead/690aaccdc4cbf652d6ccf8f417f435ac1ff8ed60/docs/public/screenshot/6.png
--------------------------------------------------------------------------------
/docs/public/screenshot/7.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Lordly-Tech/LordlyRead/690aaccdc4cbf652d6ccf8f417f435ac1ff8ed60/docs/public/screenshot/7.png
--------------------------------------------------------------------------------
/docs/public/screenshot/8.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Lordly-Tech/LordlyRead/690aaccdc4cbf652d6ccf8f417f435ac1ff8ed60/docs/public/screenshot/8.png
--------------------------------------------------------------------------------
/docs/public/screenshot/9.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Lordly-Tech/LordlyRead/690aaccdc4cbf652d6ccf8f417f435ac1ff8ed60/docs/public/screenshot/9.png
--------------------------------------------------------------------------------
/docs/public/team/duchuanbo.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Lordly-Tech/LordlyRead/690aaccdc4cbf652d6ccf8f417f435ac1ff8ed60/docs/public/team/duchuanbo.jpg
--------------------------------------------------------------------------------
/docs/public/team/kongqi.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Lordly-Tech/LordlyRead/690aaccdc4cbf652d6ccf8f417f435ac1ff8ed60/docs/public/team/kongqi.jpg
--------------------------------------------------------------------------------
/docs/public/team/tiger.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Lordly-Tech/LordlyRead/690aaccdc4cbf652d6ccf8f417f435ac1ff8ed60/docs/public/team/tiger.jpg
--------------------------------------------------------------------------------
/docs/public/team/吉王义昊.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Lordly-Tech/LordlyRead/690aaccdc4cbf652d6ccf8f417f435ac1ff8ed60/docs/public/team/吉王义昊.webp
--------------------------------------------------------------------------------
/docs/team.md:
--------------------------------------------------------------------------------
1 | ---
2 | layout: home
3 | hero:
4 | name: 团队成员
5 | tagline: Lordly Team & Contributors
6 | ---
7 |
8 |
38 |
39 |
--------------------------------------------------------------------------------
/docs/tutorial/basic.md:
--------------------------------------------------------------------------------
1 | # 基础教程
2 |
3 |
4 |
5 | ::: warning
6 | 新人必看!
7 | :::
8 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/docs/tutorial/community.md:
--------------------------------------------------------------------------------
1 | # 主要社区 & 反馈渠道
2 |
3 | ## QQ 交流群(扫描或点击下方二维码快捷加入)
4 |
5 | [](https://qm.qq.com/q/s94RXV7eHC)
6 |
7 | ## GitHub(推荐的反馈渠道)
8 |
9 | GitHub 地址:https://github.com/Lordly-Tech/LordlyRead
10 |
11 | ::: tip
12 | 在 GitHub 中提 Issue 可以让开发者更快了解问题(当然也顺手点下 star 吧)!
13 |
14 | 如果希望参与贡献,也欢迎提交 PR!
15 | :::
16 |
--------------------------------------------------------------------------------
/docs/tutorial/concepts.md:
--------------------------------------------------------------------------------
1 | # 概念介绍
2 |
3 | ## 书源
4 | 书源,简单理解就是书籍资源的网络来源。众所周知,网络上存在许许多多或收费或免费的读书网站。然而,这些网站往往存在或这样或那样的问题,比如广告繁多、比如排版字体欠佳,对手机优化很差等情况。书源就是通过特定的规则,在网络上抓取书籍章节、书籍内容,并干净整齐地呈现在手机屏幕上,优化阅读体验。与「开源阅读」相同,「Lordly·阅读」本身也不提供书源,用户可以自行编写或导入书源规则。
5 |
6 | ::: tip
7 | 目前,「Lordly·阅读」对「开源阅读」书源的支持还比较有限,但是我们会持续优化,让用户更加方便地使用「开源阅读」书源。
8 |
9 | 小白用户?不妨试试「Lordly·阅读」开发者维护的[吉的书源集](https://github.com/jiwangyihao/source-j-legado)(书源集中主要包含轻小说和漫画,在「Lordly·阅读」前期开发中主要使用这些书源进行兼容性测试,相关书源也进行了一定调整。已确认可以在「Lordly·阅读」使用的书源会标注导入码,另,点个 star 吧 orz)。
10 |
11 | 书源作者?请参考[开发指南](../development/getting-started)来了解「Lordly·阅读」中书源规则与「开源阅读」的主要区别,也欢迎您提交 PR 或 Issue 来帮助我们完善「Lordly·阅读」对「开源阅读」书源的支持。
12 | :::
13 |
14 | ## 发现(WIP)
15 | 发现是书源的一部分。发现简单理解,就是把一些读书网站上的排行榜、书籍分类等信息抓取并呈现在手机屏幕上,方便用户书荒时找书。有的书源不带发现。
16 |
17 | ## 替换和净化规则(WIP)
18 | 网络上抓取的书籍正文里,可能会有一些广告词网址等类似的内容,比如“看小说请认准 xxx.com”等。替换和净化规则的作用就是替换或删除这些内容。让呈现在我们面前的书籍正文更加美观、整齐。
--------------------------------------------------------------------------------
/docs/tutorial/functions.md:
--------------------------------------------------------------------------------
1 | # 主要功能及界面介绍
2 |
3 |
4 |
5 | ## 主界面介绍
6 |
7 |
8 | ## 阅读界面介绍
9 |
10 |
11 | ## 导入书源
12 |
13 |
14 | ## 导入替换净化(WIP)
15 |
16 |
--------------------------------------------------------------------------------
/docs/tutorial/setting.md:
--------------------------------------------------------------------------------
1 | # 设置及自定义
2 |
3 |
4 |
5 |
6 | ## 主界面
7 |
8 |
9 | ## 阅读界面
10 |
11 |
12 | ## 阅读设置
13 |
14 |
15 | ## 其他设置
--------------------------------------------------------------------------------
/husky.sh:
--------------------------------------------------------------------------------
1 | npx husky install
2 | npx husky add .husky/pre-commit 'npx lint-staged'
3 | npx husky add .husky/commit-msg 'npx --no -- commitlint --edit ${1}'
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "lordly_read",
3 | "version": "1.0.0",
4 | "description": "",
5 | "engines": {
6 | "node": ">=8.10"
7 | },
8 | "scripts": {
9 | "start": "aiot server --watch --open-nuttx --enable-custom-component true --enable-protobuf true --enable-image-png8",
10 | "build": "aiot build --enable-custom-component --enable-image-png8 --enable-jsc",
11 | "release": "aiot release --enable-custom-component --enable-image-png8 --enable-jsc",
12 | "watch": "aiot watch --open-nuttx",
13 | "lint": "eslint --format codeframe --fix --ext .ux,.js src/",
14 | "docs:dev": "vitepress dev docs",
15 | "docs:build": "vitepress build docs",
16 | "docs:preview": "vitepress preview docs"
17 | },
18 | "lint-staged": {
19 | "*.{ux,js}": [
20 | "prettier --write",
21 | "eslint --format codeframe --fix",
22 | "git add"
23 | ],
24 | "*.{less,css}": [
25 | "prettier --write",
26 | "stylelint --fix --custom-syntax postcss-less",
27 | "git add"
28 | ]
29 | },
30 | "devDependencies": {
31 | "@aiot-toolkit/jsc": "^1.0.7",
32 | "@aiot-toolkit/velasim": "^0.1.26",
33 | "@commitlint/cli": "^19.5.0",
34 | "@commitlint/config-conventional": "^19.5.0",
35 | "aiot-toolkit": "1.1.4",
36 | "babel-eslint": "^10.1.0",
37 | "buffer": "^6.0.3",
38 | "element-plus": "^2.8.6",
39 | "eslint": "^6.8.0",
40 | "eslint-config-prettier": "^6.15.0",
41 | "eslint-import-resolver-node": "^0.3.9",
42 | "eslint-plugin-import": "^2.31.0",
43 | "eslint-plugin-prettier": "^3.4.1",
44 | "eslint-plugin-ux": "^0.0.4",
45 | "husky": "^8.0.1",
46 | "less": "^4.2.0",
47 | "less-loader": "^12.2.0",
48 | "lint-staged": "^15.2.10",
49 | "postcss-html": "^1.7.0",
50 | "postcss-less": "^6.0.0",
51 | "prettier": "^3.3.3",
52 | "stylelint": "^16.9.0",
53 | "stylelint-config-recess-order": "^5.1.1",
54 | "stylelint-config-standard": "^36.0.1",
55 | "stylelint-order": "^6.0.4",
56 | "ts-loader": "^9.5.1",
57 | "typescript": "^5.6.2",
58 | "ux-types": "^0.1.1",
59 | "vitepress": "^1.3.4",
60 | "vue": "^3.5.11"
61 | }
62 | }
63 |
--------------------------------------------------------------------------------
/quickapp.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | webpack: {
3 | module: {
4 | rules: [
5 | {
6 | test: /\.tsx?$/,
7 | use: [
8 | {
9 | loader: "ts-loader"
10 | }
11 | ]
12 | }
13 | ]
14 | },
15 | resolve: {
16 | alias: {
17 | vm: false
18 | }
19 | }
20 | },
21 | cli: {
22 | "enable-custom-component": true,
23 | "enable-jsc": true,
24 | "enable-protobuf": true
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/src/app.ux:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/common/css/page.css:
--------------------------------------------------------------------------------
1 | .page {
2 | color: #ffffff;
3 | flex-direction: column;
4 | justify-content: center;
5 | align-items: center;
6 | }
7 |
8 | .icon {
9 | width: 32px;
10 | height: 32px;
11 | }
12 |
13 | .badge {
14 | font-size: 16px;
15 | padding: 2px 8px;
16 | border-radius: 16px;
17 | background-color: #333;
18 | font-weight: bold;
19 | }
20 |
21 | .badge-btn {
22 | padding: 8px 0;
23 | }
24 |
25 | .vertical-separator {
26 | height: 1px;
27 | width: 100%;
28 | background-color: rgba(255,255,255,0.5)
29 | }
30 |
31 | .horizontal-separator {
32 | width: 1px;
33 | height: 100%;
34 | background-color: rgba(255,255,255,0.5)
35 | }
36 |
37 | .topbar {
38 | width: 100%;
39 | align-items: center;
40 | }
41 |
42 | .topbar-btn {
43 | padding: 16px 32px;
44 | }
45 |
46 | .title {
47 | flex-grow: 1;
48 | font-size: 24px;
49 | font-weight: bold;
50 | text-align: center;
51 | }
52 |
53 | .info {
54 | font-size: 16px;
55 | font-weight: bold;
56 | }
57 |
58 | .ellipsis {
59 | lines: 1;
60 | text-overflow: ellipsis;
61 | text-align: left;
62 | }
63 |
64 | .body {
65 | width: 100%;
66 | flex-direction: column;
67 | flex-grow: 1;
68 | }
69 |
70 | .body-swiper {
71 | width: 100%;
72 | flex-grow: 1;
73 | }
74 |
75 | .swiper-container {
76 | width: 100%;
77 | height: 100%;
78 | }
79 |
80 | .body-holder {
81 | width: 100%;
82 | height: 100%;
83 | flex-direction: column;
84 | align-items: center;
85 | justify-content: center;
86 | color: grey;
87 | }
88 |
89 | .body-container {
90 | width: 100%;
91 | height: 100%;
92 | flex-grow: 1;
93 | flex-direction: column;
94 | padding: 24px;
95 | padding-top: 0;
96 | }
97 |
98 | .tabbar {
99 | width: 100%;
100 | padding: 16px 24px;
101 | }
102 |
103 | .tab {
104 | flex-grow: 1;
105 | align-items: center;
106 | justify-content: center;
107 | }
108 |
109 | .btn-group-title {
110 | font-size: 24px;
111 | font-weight: bold;
112 | margin-bottom: 8px;
113 | }
114 |
115 | .card {
116 | padding: 12px 12px;
117 | border-radius: 8px;
118 | background-color: #161616;
119 | align-items: center;
120 | margin-bottom: 8px;
121 | }
122 |
123 | .card-btn-text {
124 | margin-left: 12px;
125 | font-size: 20px;
126 | }
127 |
128 | .card-btn-secondary {
129 | color: grey;
130 | font-size: 16px;
131 | }
132 |
133 | .choose-container {
134 | width: 100%;
135 | margin-top: 8px;
136 | }
137 |
138 | .choose-item {
139 | padding: 4px;
140 | font-size: 20px;
141 | text-align: center;
142 | border-radius: 8px;
143 | border: 1px solid rgba(255,255,255,0.5);
144 | background-color: transparent;
145 | color: white;
146 | }
147 |
148 | .choose-item-active {
149 | background-color: white;
150 | color: black;
151 | }
152 |
153 | .menu {
154 | position: absolute;
155 | top: 0;
156 | left: 0;
157 | width: 336px;
158 | height: 100%;
159 | background-color: rgba(0,0,0,0.9);
160 | justify-content: center;
161 | flex-direction: column;
162 | margin-left: 336px;
163 | }
164 |
165 | .cover-animation-helper {
166 | display: flex;
167 | position: absolute;
168 | top: 0;
169 | left: 0;
170 | width: 100%;
171 | height: 100%;
172 | background-color: black;
173 | margin-left: -336px;
174 | }
175 |
176 | .book-name {
177 | font-size: 24px;
178 | font-weight: bold;
179 | }
180 |
181 | .book-progress, .book-update, .book-hint {
182 | font-size: 24px;
183 | color: #999;
184 | }
185 |
186 | .book-cover {
187 | width: 64px;
188 | height: 96px;
189 | border-radius: 8px;
190 | margin-right: 8px;
191 | }
192 |
193 | .progress {
194 | color: white;
195 | layer-color: grey;
196 | stroke-width: 8px;
197 | }
198 |
199 | @keyframes In {
200 | 100% {
201 | transform: translateX(-336px);
202 | }
203 |
204 | 0% {
205 | transform: translateX(0%);
206 | }
207 | }
208 |
209 | .animation-in {
210 | animation-name: In;
211 | animation-duration: 200ms;
212 | animation-timing-function: ease-out;
213 | animation-delay: 50ms;
214 | margin-left: 336px;
215 | }
216 |
217 | @keyframes Out {
218 | 100% {
219 | transform: translateX(-100%);
220 | /* opacity: 1; */
221 | }
222 |
223 | 0% {
224 | transform: translateX(0%);
225 | /* opacity: 0; */
226 | }
227 | }
228 |
229 | .animation-out {
230 | animation-name: Out;
231 | animation-duration: 200ms;
232 | animation-timing-function: ease-in;
233 | animation-delay: 50ms;
234 | margin-left: 0;
235 | }
236 |
237 | @keyframes InBack {
238 | 100% {
239 | transform: translateX(336px);
240 | /* opacity: 1; */
241 | }
242 |
243 | 0% {
244 | transform: translateX(0%);
245 | /* opacity: 0; */
246 | }
247 | }
248 |
249 | .animation-in-back {
250 | animation-name: InBack;
251 | animation-duration: 200ms;
252 | animation-delay: 50ms;
253 | animation-timing-function: ease-out;
254 | margin-left: -336px;
255 | }
256 |
257 | @keyframes OutBack {
258 | 100% {
259 | transform: translateX(100%);
260 | /* opacity: 1; */
261 | }
262 |
263 | 0% {
264 | transform: translateX(0%);
265 | /* opacity: 0; */
266 | }
267 | }
268 |
269 | .animation-out-back {
270 | animation-name: OutBack;
271 | animation-duration: 200ms;
272 | animation-delay: 50ms;
273 | animation-timing-function: ease-in;
274 | margin-left: 0;
275 | }
276 |
277 | /* style reset classes */
278 | .col {
279 | flex-direction: column;
280 | }
281 |
282 | .start {
283 | align-items: flex-start;
284 | }
285 |
286 | .center {
287 | align-items: center;
288 | }
289 |
290 | .text-center {
291 | text-align: center;
292 | }
293 |
294 | .justify-start {
295 | justify-content: flex-start;
296 | }
297 |
298 | .justify-center {
299 | justify-content: center;
300 | }
301 |
302 | .space-between {
303 | justify-content: space-between;
304 | }
305 |
306 | .grow {
307 | flex-grow: 1;
308 | }
309 |
310 | .no-grow {
311 | flex-grow: 0;
312 | }
313 |
314 | .no-shrink, image, .badge {
315 | flex-shrink: 0;
316 | }
317 |
318 | .transparent {
319 | background-color: transparent;
320 | }
321 |
322 | .none {
323 | display: none;
324 | }
325 |
326 | /* margin and padding classes */
327 |
328 | .ma-xl {
329 | margin: 48px;
330 | }
331 |
332 | .ma-lg {
333 | margin: 32px;
334 | }
335 |
336 | .ma-md {
337 | margin: 24px;
338 | }
339 |
340 | .ma-sm {
341 | margin: 12px;
342 | }
343 |
344 | .ma-xs {
345 | margin: 8px;
346 | }
347 |
348 | .ma-0 {
349 | margin: 0;
350 | }
351 |
352 | .mx-xl {
353 | margin-left: 48px;
354 | margin-right: 48px;
355 | }
356 |
357 | .mx-lg {
358 | margin-left: 32px;
359 | margin-right: 32px;
360 | }
361 |
362 | .mx-md {
363 | margin-left: 24px;
364 | margin-right: 24px;
365 | }
366 |
367 | .mx-sm {
368 | margin-left: 12px;
369 | margin-right: 12px;
370 | }
371 |
372 | .mx-xs {
373 | margin-left: 8px;
374 | margin-right: 8px;
375 | }
376 |
377 | .mx-0 {
378 | margin-left: 0;
379 | margin-right: 0;
380 | }
381 |
382 | .my-xl {
383 | margin-top: 48px;
384 | margin-bottom: 48px;
385 | }
386 |
387 | .my-lg {
388 | margin-top: 32px;
389 | margin-bottom: 32px;
390 | }
391 |
392 | .my-md {
393 | margin-top: 24px;
394 | margin-bottom: 24px;
395 | }
396 |
397 | .my-sm {
398 | margin-top: 12px;
399 | margin-bottom: 12px;
400 | }
401 |
402 | .my-xs {
403 | margin-top: 8px;
404 | margin-bottom: 8px;
405 | }
406 |
407 | .my-0 {
408 | margin-top: 0;
409 | margin-bottom: 0;
410 | }
411 |
412 | .mt-xl {
413 | margin-top: 48px;
414 | }
415 |
416 | .mt-lg {
417 | margin-top: 32px;
418 | }
419 |
420 | .mt-md {
421 | margin-top: 24px;
422 | }
423 |
424 | .mt-sm {
425 | margin-top: 12px;
426 | }
427 |
428 | .mt-xs {
429 | margin-top: 8px;
430 | }
431 |
432 | .mt-0 {
433 | margin-top: 0;
434 | }
435 |
436 | .mb-xl {
437 | margin-bottom: 48px;
438 | }
439 |
440 | .mb-lg {
441 | margin-bottom: 32px;
442 | }
443 |
444 | .mb-md {
445 | margin-bottom: 24px;
446 | }
447 |
448 | .mb-sm {
449 | margin-bottom: 12px;
450 | }
451 |
452 | .mb-xs {
453 | margin-bottom: 8px;
454 | }
455 |
456 | .mb-0 {
457 | margin-bottom: 0;
458 | }
459 |
460 | .ml-xl {
461 | margin-left: 48px;
462 | }
463 |
464 | .ml-lg {
465 | margin-left: 32px;
466 | }
467 |
468 | .ml-md {
469 | margin-left: 24px;
470 | }
471 |
472 | .ml-sm {
473 | margin-left: 12px;
474 | }
475 |
476 | .ml-xs {
477 | margin-left: 8px;
478 | }
479 |
480 | .ml-0 {
481 | margin-left: 0;
482 | }
483 |
484 | .mr-xl {
485 | margin-right: 48px;
486 | }
487 |
488 | .mr-lg {
489 | margin-right: 32px;
490 | }
491 |
492 | .mr-md {
493 | margin-right: 24px;
494 | }
495 |
496 | .mr-sm {
497 | margin-right: 12px;
498 | }
499 |
500 | .mr-xs {
501 | margin-right: 8px;
502 | }
503 |
504 | .mr-0 {
505 | margin-right: 0;
506 | }
507 |
508 | .pa-xl {
509 | padding: 48px;
510 | }
511 |
512 | .pa-lg {
513 | padding: 32px;
514 | }
515 |
516 | .pa-md {
517 | padding: 24px;
518 | }
519 |
520 | .pa-sm {
521 | padding: 12px;
522 | }
523 |
524 | .pa-xs {
525 | padding: 8px;
526 | }
527 |
528 | .pa-0 {
529 | padding: 0;
530 | }
531 |
532 | .px-xl {
533 | padding-left: 48px;
534 | padding-right: 48px;
535 | }
536 |
537 | .px-lg {
538 | padding-left: 32px;
539 | padding-right: 32px;
540 | }
541 |
542 | .px-md {
543 | padding-left: 24px;
544 | padding-right: 24px;
545 | }
546 |
547 | .px-sm {
548 | padding-left: 12px;
549 | padding-right: 12px;
550 | }
551 |
552 | .px-xs {
553 | padding-left: 8px;
554 | padding-right: 8px;
555 | }
556 |
557 | .px-0 {
558 | padding-left: 0;
559 | padding-right: 0;
560 | }
561 |
562 | .py-xl {
563 | padding-top: 48px;
564 | padding-bottom: 48px;
565 | }
566 |
567 | .py-lg {
568 | padding-top: 32px;
569 | padding-bottom: 32px;
570 | }
571 |
572 | .py-md {
573 | padding-top: 24px;
574 | padding-bottom: 24px;
575 | }
576 |
577 | .py-sm {
578 | padding-top: 12px;
579 | padding-bottom: 12px;
580 | }
581 |
582 | .py-xs {
583 | padding-top: 8px;
584 | padding-bottom: 8px;
585 | }
586 |
587 | .py-0 {
588 | padding-top: 0;
589 | padding-bottom: 0;
590 | }
591 |
592 | .pt-xl {
593 | padding-top: 48px;
594 | }
595 |
596 | .pt-lg {
597 | padding-top: 32px;
598 | }
599 |
600 | .pt-md {
601 | padding-top: 24px;
602 | }
603 |
604 | .pt-sm {
605 | padding-top: 12px;
606 | }
607 |
608 | .pt-xs {
609 | padding-top: 8px;
610 | }
611 |
612 | .pt-0 {
613 | padding-top: 0;
614 | }
615 |
616 | .pb-xl {
617 | padding-bottom: 48px;
618 | }
619 |
620 | .pb-lg {
621 | padding-bottom: 32px;
622 | }
623 |
624 | .pb-md {
625 | padding-bottom: 24px;
626 | }
627 |
628 | .pb-sm {
629 | padding-bottom: 12px;
630 | }
631 |
632 | .pb-xs {
633 | padding-bottom: 8px;
634 | }
635 |
636 | .pb-0 {
637 | padding-bottom: 0;
638 | }
639 |
640 | .pl-lg {
641 | padding-left: 48px;
642 | }
643 |
644 | .pl-md {
645 | padding-left: 24px;
646 | }
647 |
648 | .pl-sm {
649 | padding-left: 12px;
650 | }
651 |
652 | .pl-xs {
653 | padding-left: 8px;
654 | }
655 |
656 | .pl-0 {
657 | padding-left: 0;
658 | }
659 |
660 | .pr-xl {
661 | padding-right: 48px;
662 | }
663 |
664 | .pr-lg {
665 | padding-right: 32px;
666 | }
667 |
668 | .pr-md {
669 | padding-right: 24px;
670 | }
671 |
672 | .pr-sm {
673 | padding-right: 12px;
674 | }
675 |
676 | .pr-xs {
677 | padding-right: 8px;
678 | }
679 |
680 | .pr-0 {
681 | padding-right: 0;
682 | }
683 |
684 | /* height classes */
685 |
686 | .h-100 {
687 | height: 100%;
688 | }
689 |
690 | .h-xl {
691 | height: 48px;
692 | }
693 |
694 | .h-lg {
695 | height: 32px;
696 | }
697 |
698 | .h-md {
699 | height: 24px;
700 | }
701 |
702 | .h-sm {
703 | height: 12px;
704 | }
705 |
706 | .h-xs {
707 | height: 8px;
708 | }
709 |
710 | .h-0 {
711 | height: 0;
712 | }
713 |
714 | /* width classes */
715 |
716 | .w-100 {
717 | width: 100%;
718 | }
719 |
720 | .w-xl {
721 | width: 48px;
722 | }
723 |
724 | .w-lg {
725 | width: 32px;
726 | }
727 |
728 | .w-md {
729 | width: 24px;
730 | }
731 |
732 | .w-sm {
733 | width: 12px;
734 | }
735 |
736 | .w-xs {
737 | width: 8px;
738 | }
739 |
740 | .w-0 {
741 | width: 0;
742 | }
743 |
--------------------------------------------------------------------------------
/src/common/icon/author.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Lordly-Tech/LordlyRead/690aaccdc4cbf652d6ccf8f417f435ac1ff8ed60/src/common/icon/author.png
--------------------------------------------------------------------------------
/src/common/icon/back.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Lordly-Tech/LordlyRead/690aaccdc4cbf652d6ccf8f417f435ac1ff8ed60/src/common/icon/back.png
--------------------------------------------------------------------------------
/src/common/icon/book-code.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Lordly-Tech/LordlyRead/690aaccdc4cbf652d6ccf8f417f435ac1ff8ed60/src/common/icon/book-code.png
--------------------------------------------------------------------------------
/src/common/icon/catalog.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Lordly-Tech/LordlyRead/690aaccdc4cbf652d6ccf8f417f435ac1ff8ed60/src/common/icon/catalog.png
--------------------------------------------------------------------------------
/src/common/icon/check-active.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Lordly-Tech/LordlyRead/690aaccdc4cbf652d6ccf8f417f435ac1ff8ed60/src/common/icon/check-active.png
--------------------------------------------------------------------------------
/src/common/icon/check.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Lordly-Tech/LordlyRead/690aaccdc4cbf652d6ccf8f417f435ac1ff8ed60/src/common/icon/check.png
--------------------------------------------------------------------------------
/src/common/icon/close.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Lordly-Tech/LordlyRead/690aaccdc4cbf652d6ccf8f417f435ac1ff8ed60/src/common/icon/close.png
--------------------------------------------------------------------------------
/src/common/icon/code.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Lordly-Tech/LordlyRead/690aaccdc4cbf652d6ccf8f417f435ac1ff8ed60/src/common/icon/code.png
--------------------------------------------------------------------------------
/src/common/icon/down.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Lordly-Tech/LordlyRead/690aaccdc4cbf652d6ccf8f417f435ac1ff8ed60/src/common/icon/down.png
--------------------------------------------------------------------------------
/src/common/icon/explore-active.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Lordly-Tech/LordlyRead/690aaccdc4cbf652d6ccf8f417f435ac1ff8ed60/src/common/icon/explore-active.png
--------------------------------------------------------------------------------
/src/common/icon/explore.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Lordly-Tech/LordlyRead/690aaccdc4cbf652d6ccf8f417f435ac1ff8ed60/src/common/icon/explore.png
--------------------------------------------------------------------------------
/src/common/icon/group.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Lordly-Tech/LordlyRead/690aaccdc4cbf652d6ccf8f417f435ac1ff8ed60/src/common/icon/group.png
--------------------------------------------------------------------------------
/src/common/icon/help.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Lordly-Tech/LordlyRead/690aaccdc4cbf652d6ccf8f417f435ac1ff8ed60/src/common/icon/help.png
--------------------------------------------------------------------------------
/src/common/icon/history.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Lordly-Tech/LordlyRead/690aaccdc4cbf652d6ccf8f417f435ac1ff8ed60/src/common/icon/history.png
--------------------------------------------------------------------------------
/src/common/icon/home-active.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Lordly-Tech/LordlyRead/690aaccdc4cbf652d6ccf8f417f435ac1ff8ed60/src/common/icon/home-active.png
--------------------------------------------------------------------------------
/src/common/icon/home.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Lordly-Tech/LordlyRead/690aaccdc4cbf652d6ccf8f417f435ac1ff8ed60/src/common/icon/home.png
--------------------------------------------------------------------------------
/src/common/icon/info.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Lordly-Tech/LordlyRead/690aaccdc4cbf652d6ccf8f417f435ac1ff8ed60/src/common/icon/info.png
--------------------------------------------------------------------------------
/src/common/icon/latest.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Lordly-Tech/LordlyRead/690aaccdc4cbf652d6ccf8f417f435ac1ff8ed60/src/common/icon/latest.png
--------------------------------------------------------------------------------
/src/common/icon/left-double.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Lordly-Tech/LordlyRead/690aaccdc4cbf652d6ccf8f417f435ac1ff8ed60/src/common/icon/left-double.png
--------------------------------------------------------------------------------
/src/common/icon/left.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Lordly-Tech/LordlyRead/690aaccdc4cbf652d6ccf8f417f435ac1ff8ed60/src/common/icon/left.png
--------------------------------------------------------------------------------
/src/common/icon/library.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Lordly-Tech/LordlyRead/690aaccdc4cbf652d6ccf8f417f435ac1ff8ed60/src/common/icon/library.png
--------------------------------------------------------------------------------
/src/common/icon/magic.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Lordly-Tech/LordlyRead/690aaccdc4cbf652d6ccf8f417f435ac1ff8ed60/src/common/icon/magic.png
--------------------------------------------------------------------------------
/src/common/icon/menu.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Lordly-Tech/LordlyRead/690aaccdc4cbf652d6ccf8f417f435ac1ff8ed60/src/common/icon/menu.png
--------------------------------------------------------------------------------
/src/common/icon/password.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Lordly-Tech/LordlyRead/690aaccdc4cbf652d6ccf8f417f435ac1ff8ed60/src/common/icon/password.png
--------------------------------------------------------------------------------
/src/common/icon/refresh.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Lordly-Tech/LordlyRead/690aaccdc4cbf652d6ccf8f417f435ac1ff8ed60/src/common/icon/refresh.png
--------------------------------------------------------------------------------
/src/common/icon/right-double.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Lordly-Tech/LordlyRead/690aaccdc4cbf652d6ccf8f417f435ac1ff8ed60/src/common/icon/right-double.png
--------------------------------------------------------------------------------
/src/common/icon/right.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Lordly-Tech/LordlyRead/690aaccdc4cbf652d6ccf8f417f435ac1ff8ed60/src/common/icon/right.png
--------------------------------------------------------------------------------
/src/common/icon/search.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Lordly-Tech/LordlyRead/690aaccdc4cbf652d6ccf8f417f435ac1ff8ed60/src/common/icon/search.png
--------------------------------------------------------------------------------
/src/common/icon/setting.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Lordly-Tech/LordlyRead/690aaccdc4cbf652d6ccf8f417f435ac1ff8ed60/src/common/icon/setting.png
--------------------------------------------------------------------------------
/src/common/icon/source.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Lordly-Tech/LordlyRead/690aaccdc4cbf652d6ccf8f417f435ac1ff8ed60/src/common/icon/source.png
--------------------------------------------------------------------------------
/src/common/icon/submit.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Lordly-Tech/LordlyRead/690aaccdc4cbf652d6ccf8f417f435ac1ff8ed60/src/common/icon/submit.png
--------------------------------------------------------------------------------
/src/common/icon/trash.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Lordly-Tech/LordlyRead/690aaccdc4cbf652d6ccf8f417f435ac1ff8ed60/src/common/icon/trash.png
--------------------------------------------------------------------------------
/src/common/icon/ui.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Lordly-Tech/LordlyRead/690aaccdc4cbf652d6ccf8f417f435ac1ff8ed60/src/common/icon/ui.png
--------------------------------------------------------------------------------
/src/common/icon/up.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Lordly-Tech/LordlyRead/690aaccdc4cbf652d6ccf8f417f435ac1ff8ed60/src/common/icon/up.png
--------------------------------------------------------------------------------
/src/common/icon/user-active.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Lordly-Tech/LordlyRead/690aaccdc4cbf652d6ccf8f417f435ac1ff8ed60/src/common/icon/user-active.png
--------------------------------------------------------------------------------
/src/common/icon/user.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Lordly-Tech/LordlyRead/690aaccdc4cbf652d6ccf8f417f435ac1ff8ed60/src/common/icon/user.png
--------------------------------------------------------------------------------
/src/common/icon/web.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Lordly-Tech/LordlyRead/690aaccdc4cbf652d6ccf8f417f435ac1ff8ed60/src/common/icon/web.png
--------------------------------------------------------------------------------
/src/common/image/bookcover.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Lordly-Tech/LordlyRead/690aaccdc4cbf652d6ccf8f417f435ac1ff8ed60/src/common/image/bookcover.png
--------------------------------------------------------------------------------
/src/common/image/cover.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Lordly-Tech/LordlyRead/690aaccdc4cbf652d6ccf8f417f435ac1ff8ed60/src/common/image/cover.jpg
--------------------------------------------------------------------------------
/src/common/image/qr-feed.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Lordly-Tech/LordlyRead/690aaccdc4cbf652d6ccf8f417f435ac1ff8ed60/src/common/image/qr-feed.png
--------------------------------------------------------------------------------
/src/common/image/qr-github.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Lordly-Tech/LordlyRead/690aaccdc4cbf652d6ccf8f417f435ac1ff8ed60/src/common/image/qr-github.png
--------------------------------------------------------------------------------
/src/common/image/qr-qq.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Lordly-Tech/LordlyRead/690aaccdc4cbf652d6ccf8f417f435ac1ff8ed60/src/common/image/qr-qq.png
--------------------------------------------------------------------------------
/src/common/logo/192-round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Lordly-Tech/LordlyRead/690aaccdc4cbf652d6ccf8f417f435ac1ff8ed60/src/common/logo/192-round.png
--------------------------------------------------------------------------------
/src/common/logo/96-round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Lordly-Tech/LordlyRead/690aaccdc4cbf652d6ccf8f417f435ac1ff8ed60/src/common/logo/96-round.png
--------------------------------------------------------------------------------
/src/config-watch.json:
--------------------------------------------------------------------------------
1 | {}
--------------------------------------------------------------------------------
/src/i18n/defaults.json:
--------------------------------------------------------------------------------
1 | {
2 | "index": {
3 | "title": "首页"
4 | }
5 | }
--------------------------------------------------------------------------------
/src/i18n/en.json:
--------------------------------------------------------------------------------
1 | {
2 | "index": {
3 | "title": "首页"
4 | }
5 | }
--------------------------------------------------------------------------------
/src/i18n/zh-CN.json:
--------------------------------------------------------------------------------
1 | {
2 | "index": {
3 | "title": "书架"
4 | }
5 | }
--------------------------------------------------------------------------------
/src/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "package": "vip.lordly.read",
3 | "name": "Lordly·阅读",
4 | "versionName": "1.1.1",
5 | "versionCode": 3,
6 | "minPlatformVersion": 1000,
7 | "minAPILevel": 2,
8 | "icon": "/common/logo/192-round.png",
9 | "deviceTypeList": ["watch"],
10 | "features": [
11 | {
12 | "name": "system.router"
13 | },
14 | {
15 | "name": "system.prompt"
16 | },
17 | {
18 | "name": "system.storage"
19 | },
20 | {
21 | "name": "system.fetch"
22 | },
23 | {
24 | "name": "system.device"
25 | },
26 | {
27 | "name": "system.request"
28 | },
29 | {
30 | "name": "system.file"
31 | },
32 | {
33 | "name": "system.brightness"
34 | }
35 | ],
36 | "config": {
37 | "logLevel": "log",
38 | "designWidth": 336
39 | },
40 | "router": {
41 | "entry": "pages/index",
42 | "pages": {
43 | "pages/index": {
44 | "component": "index",
45 | "launchMode": "singleTask"
46 | },
47 | "pages/about": {
48 | "component": "about",
49 | "launchMode": "singleTask"
50 | },
51 | "pages/setting": {
52 | "component": "setting",
53 | "launchMode": "singleTask"
54 | },
55 | "pages/source": {
56 | "component": "source",
57 | "launchMode": "singleTask"
58 | },
59 | "pages/detail": {
60 | "component": "detail",
61 | "launchMode": "singleTask"
62 | },
63 | "pages/content": {
64 | "component": "content",
65 | "launchMode": "singleTask"
66 | },
67 | "pages/search": {
68 | "component": "search",
69 | "launchMode": "singleTask"
70 | },
71 | "pages/searchResult": {
72 | "component": "searchResult",
73 | "launchMode": "singleTask"
74 | },
75 | "pages/catalog": {
76 | "component": "catalog",
77 | "launchMode": "singleTask"
78 | }
79 | }
80 | },
81 | "display": {
82 | "backgroundColor": "#000000"
83 | }
84 | }
85 |
--------------------------------------------------------------------------------
/src/pages/about/about.ux:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
关于
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 | {{ appInfo.name }}
18 | 版本 {{ appInfo.versionName }}
19 |
20 |
21 | 支持「开源阅读」规则的手环在线阅读工具
22 | 本应用为开源项目,欢迎提交 PR 或 Issue
23 | 开发者
24 | @吉王义昊
25 | @两广总督-tiger
26 | 官方Q群
27 | Lordly Wear 可穿戴设备交流群
28 |
29 | 赞助
30 | afdian@tiger14tiger
31 |
32 | 开源地址
33 | Lordly-Tech/LordlyRead
34 |
35 | 更多资源请访问
36 | bandbbs.cn
37 | 免责声明
38 | 本应用仅用于学习交流,请勿用于商业用途
39 | 本应用只是一个转码工具,不提供内容
40 |
41 |
42 |
43 |
44 |
45 |
46 |
60 |
61 |
--------------------------------------------------------------------------------
/src/pages/catalog/catalog.ux:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
目录
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 | {{ chapter.chapterName }}
17 |
18 |
20 | {{ chapter.chapterName }}
21 |
22 | {{ chapter.chapterInfo }}
23 |
24 |
25 |
26 |
27 |
33 |
34 |
35 |
36 |
37 |
38 |
119 |
120 |
--------------------------------------------------------------------------------
/src/pages/content/content.ux:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | {{ bookName }}
6 | {{ time }}
7 |
8 |
9 |
11 |
13 | {{ chapter.chapterName }}
14 |
15 |
16 |
18 |
20 | {{ item.content }}
21 |
22 |
23 |
24 |
160 |
182 |
183 |
184 |
185 |
186 |
187 |
589 |
590 |
--------------------------------------------------------------------------------
/src/pages/detail/detail.ux:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
详情
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 | {{ book.name }}
16 |
17 |
18 | {{ tag }}
19 |
20 |
21 |
22 |
23 | {{ book.author ?? "未知" }}
24 |
25 |
26 |
27 |
{{ book.bookSourceName ?? "未知" }}
28 |
29 | 换源
30 |
31 |
32 |
33 |
34 | {{ book.lastChapter ?? "未知" }}
35 |
36 |
37 |
38 |
{{ book.progress ?? "目录加载中……" }}
39 |
40 | 目录
41 |
42 |
43 |
44 |
45 | {{ text }}
46 |
47 |
48 |
49 | 开始阅读
50 |
51 |
52 | {{ book.inBookShelf ? "删除书籍" : "加入书架" }}
53 |
54 |
55 |
77 |
80 |
81 |
82 |
83 |
84 |
85 |
246 |
247 |
--------------------------------------------------------------------------------
/src/pages/index/index.ux:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
{{ $t("index.title") }}
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
探索
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
我的
26 |
27 |
28 |
29 |
30 |
31 |
32 |
34 |
35 |
36 | 这里……
37 | 空空如也呢~
38 |
39 |
40 |
41 |
42 |
43 |
44 |
{{ bookItem.name }}
45 |
46 | {{ bookItem.toc[bookItem.progress].chapterName }}
47 |
48 |
49 | {{ bookItem.lastChapter }}
50 | {{ bookItem.toc.length - 1 - bookItem.progress }}
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 | 这里……
60 | 空空如也呢~
61 |
62 |
63 |
64 |
65 |
66 |
67 | 书源管理
68 |
69 |
70 |
71 | 替换净化
72 |
73 | 设置
74 |
75 |
76 | 主界面
77 |
78 |
79 |
80 | 阅读界面
81 |
82 |
83 |
84 | 阅读设置
85 |
86 |
87 |
88 | 其他设置
89 |
90 | 其他
91 |
92 |
93 | 关于
94 |
95 |
96 |
97 |
98 |
99 |
100 |
101 |
102 |
103 |
104 |
105 |
106 |
107 |
108 |
109 |
110 |
111 |
112 |
113 |
114 |
224 |
225 |
--------------------------------------------------------------------------------
/src/pages/search/search.ux:
--------------------------------------------------------------------------------
1 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
搜索
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 | {{ searchText === "" ? "搜索书名、作者" : searchText }}
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 | 搜索
30 |
31 |
32 |
33 |
34 |
40 |
41 |
42 |
43 |
44 |
45 |
84 |
85 |
--------------------------------------------------------------------------------
/src/pages/searchResult/searchResult.ux:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
搜索
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
21 |
24 |
27 |
28 |
29 |
30 |
31 | 第{{ page - 1 }}页 | 下一页
32 |
33 |
34 |
44 |
45 |
46 |
47 |
48 |
49 |
106 |
107 |
--------------------------------------------------------------------------------
/src/pages/setting/setting.ux:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
设置
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 | {{ item.title }}
16 |
17 |
18 | {{ item.title }}
19 | {{ item.subtitle }}
20 |
21 |
22 |
23 |
24 |
25 |
{{ item.title }}
26 |
{{ item.subtitle }}
27 |
28 |
29 |
30 |
32 | {{ option.label }}
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
{{ item.title }}
41 |
{{ item.subtitle }}
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 | {{ item.value }}
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 | {{ item.title }}
68 | {{ item.subtitle }}
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
120 |
121 |
--------------------------------------------------------------------------------
/src/pages/source/source.ux:
--------------------------------------------------------------------------------
1 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
书源管理
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 | {{ item.bookSourceName }}
22 |
23 |
25 |
26 |
27 |
28 | 这里……
29 | 空空如也呢~
30 |
31 |
32 |
50 |
87 |
100 |
124 |
128 |
129 |
130 |
131 |
132 |
133 |
354 |
355 |
372 |
--------------------------------------------------------------------------------
/src/third-party/JSONPath/jsonpath.ts:
--------------------------------------------------------------------------------
1 | /* JSONPath 0.8.0 - XPath for JSON
2 | *
3 | * Copyright (c) 2007 Stefan Goessner (goessner.net)
4 | * Licensed under the MIT (MIT-LICENSE.txt) licence.
5 | */
6 | /* Modified by: jiwangyihao
7 | * Date: 2024-10-31
8 | * Description: Translated to TypeScript
9 | */
10 | export function jsonPath(
11 | obj: any,
12 | expr: string,
13 | arg?: {
14 | resultType: "VALUE" | "PATH"
15 | }
16 | ) {
17 | const $ = obj
18 | const P = {
19 | resultType: (arg && arg.resultType) || "VALUE",
20 | result: [],
21 | normalize: function (expr: string) {
22 | // noinspection SpellCheckingInspection
23 | const subx = []
24 | return expr
25 | .replace(/[\['](\??\(.*?\))[\]']/g, function (_$0, $1) {
26 | return "[#" + (subx.push($1) - 1) + "]"
27 | })
28 | .replace(/'?\.'?|\['?/g, ";")
29 | .replace(/;;;|;;/g, ";..;")
30 | .replace(/;$|'?]|'$/g, "")
31 | .replace(/#([0-9]+)/g, function (_$0, $1) {
32 | return subx[$1]
33 | })
34 | },
35 | asPath: function (path: string) {
36 | let x = path.split(";"),
37 | p = "$"
38 | for (let i = 1, n = x.length; i < n; i++)
39 | p += /^[0-9*]+$/.test(x[i]) ? "[" + x[i] + "]" : "['" + x[i] + "']"
40 | return p
41 | },
42 | store: function (p: string, v: any) {
43 | if (p) P.result[P.result.length] = P.resultType == "PATH" ? P.asPath(p) : v
44 | return !!p
45 | },
46 | trace: function (expr: string, val: any, path: string) {
47 | if (expr) {
48 | let x: string | string[] = expr.split(";"),
49 | loc = x.shift()
50 | x = x.join(";")
51 | if (val && val.hasOwnProperty(loc)) P.trace(x, val[loc], path + ";" + loc)
52 | else if (loc === "*")
53 | P.walk(loc, x, val, path, function (m, _l, x, v, p) {
54 | P.trace(m + ";" + x, v, p)
55 | })
56 | else if (loc === "..") {
57 | P.trace(x, val, path)
58 | P.walk(loc, x, val, path, function (m, _l, x, v, p) {
59 | typeof v[m] === "object" && P.trace("..;" + x, v[m], p + ";" + m)
60 | })
61 | } else if (/,/.test(loc)) {
62 | // [name1,name2,...]
63 | for (let s = loc.split(/'?,'?/), i = 0, n = s.length; i < n; i++)
64 | P.trace(s[i] + ";" + x, val, path)
65 | } else if (/^\(.*?\)$/.test(loc))
66 | // [(expr)]
67 | P.trace(P.eval(loc, val, path.substring(path.lastIndexOf(";") + 1)) + ";" + x, val, path)
68 | else if (/^\?\(.*?\)$/.test(loc))
69 | // [?(expr)]
70 | P.walk(loc, x, val, path, function (m, l, x, v, p) {
71 | if (P.eval(l.replace(/^\?\((.*?)\)$/, "$1"), v[m], m)) P.trace(m + ";" + x, v, p)
72 | })
73 | else if (/^(-?[0-9]*):(-?[0-9]*):?([0-9]*)$/.test(loc))
74 | // [start:end:step] phyton slice syntax
75 | P.slice(loc, x, val, path)
76 | } else P.store(path, val)
77 | },
78 | walk: function (
79 | loc: string,
80 | expr: string,
81 | val: any,
82 | path: string,
83 | f: (m: string, l: string, x: string, v: any, p: string) => void
84 | ) {
85 | if (val instanceof Array) {
86 | for (let i = 0, n = val.length; i < n; i++) if (i in val) f(String(i), loc, expr, val, path)
87 | } else if (typeof val === "object") {
88 | for (const m in val) if (val.hasOwnProperty(m)) f(m, loc, expr, val, path)
89 | }
90 | },
91 | slice: function (loc: string, expr: string, val: any, path: string) {
92 | if (val instanceof Array) {
93 | let len = val.length,
94 | start = 0,
95 | end = len,
96 | step = 1
97 | loc.replace(/^(-?[0-9]*):(-?[0-9]*):?(-?[0-9]*)$/g, function (_$0, $1, $2, $3) {
98 | start = parseInt($1 || start)
99 | end = parseInt($2 || end)
100 | step = parseInt($3 || step)
101 | return ""
102 | })
103 | start = start < 0 ? Math.max(0, start + len) : Math.min(len, start)
104 | end = end < 0 ? Math.max(0, end + len) : Math.min(len, end)
105 | for (let i = start; i < end; i += step) P.trace(i + ";" + expr, val, path)
106 | }
107 | },
108 | eval: function (x: string, _v: any, _vname: string) {
109 | try {
110 | return $ && _v && eval(x.replace(/@/g, "_v"))
111 | } catch (e) {
112 | throw new SyntaxError(
113 | "jsonPath: " + e.message + ": " + x.replace(/@/g, "_v").replace(/\^/g, "_a")
114 | )
115 | }
116 | }
117 | }
118 |
119 | if (expr && obj && (P.resultType == "VALUE" || P.resultType == "PATH")) {
120 | P.trace(P.normalize(expr).replace(/^\$;/, ""), obj, "$")
121 | return P.result
122 | }
123 | }
124 |
--------------------------------------------------------------------------------
/src/utils/book.ts:
--------------------------------------------------------------------------------
1 | import {source} from "./index"
2 |
3 | export interface BookTocChapter {
4 | chapterInfo?: string
5 | chapterName: string
6 | chapterUrl?: string
7 | isVolume?: boolean
8 | }
9 |
10 | export interface BookData {
11 | bookSourceUrl: string
12 | name: string
13 | author: string
14 | kind: string[]
15 | wordCount: string
16 | lastChapter: string
17 | coverUrl: string
18 | intro: string
19 | tocUrl: string
20 | bookUrl: string
21 | toc: BookTocChapter[]
22 | progress: number
23 | fProgress: number
24 | variable: string
25 | }
26 |
27 | export class Book {
28 | bookSourceUrl: string
29 | name: string
30 | author: string
31 | kind: string[]
32 | wordCount: string
33 | lastChapter: string
34 | coverUrl: string
35 | intro: string
36 | tocUrl: string
37 | bookUrl: string
38 | toc: BookTocChapter[] = []
39 | fProgress: number = 0
40 |
41 | variable: string
42 |
43 | constructor(data: Partial) {
44 | this.bookSourceUrl = data.bookSourceUrl
45 | this.name = data.name
46 | this.author = data.author
47 | this.kind = data.kind
48 | this.wordCount = data.wordCount
49 | this.lastChapter = data.lastChapter
50 | this.coverUrl = data.coverUrl
51 | this.intro = data.intro
52 | this.tocUrl = data.tocUrl
53 | this.bookUrl = data.bookUrl
54 | this.toc = data.toc ?? []
55 | this.progress = data.fProgress ?? data.progress ?? 0
56 | this.progress = Math.max(this.fProgress, 0)
57 | if (this.progress >= this.toc.length) this.progress = this.toc.length - 1
58 | this.variable = data.variable ?? ""
59 | }
60 |
61 | set progress(progress: number) {
62 | this.fProgress = progress
63 | }
64 |
65 | get progress() {
66 | return Math.max(Math.floor(this.fProgress), 0)
67 | }
68 |
69 | update(book: Book) {
70 | this.bookSourceUrl = book.bookSourceUrl
71 | this.name = book.name
72 | this.author = book.author
73 | this.kind = book.kind
74 | this.wordCount = book.wordCount
75 | this.lastChapter = book.lastChapter
76 | this.coverUrl = book.coverUrl
77 | this.intro = book.intro
78 | this.tocUrl = book.tocUrl
79 | this.bookUrl = book.bookUrl
80 | this.toc = book.toc
81 | }
82 |
83 | toData(): BookData {
84 | return {
85 | bookSourceUrl: this.bookSourceUrl,
86 | name: this.name,
87 | author: this.author,
88 | kind: this.kind,
89 | wordCount: this.wordCount,
90 | lastChapter: this.lastChapter,
91 | coverUrl: this.coverUrl,
92 | intro: this.intro,
93 | tocUrl: this.tocUrl,
94 | bookUrl: this.bookUrl,
95 | toc: this.toc,
96 | progress: this.progress,
97 | fProgress: this.fProgress,
98 | variable: this.variable
99 | }
100 | }
101 |
102 | getVariable(v?: string) {
103 | return this.variable ?? ""
104 | }
105 |
106 | async updateToc() {
107 | return await source.getSource(this.bookSourceUrl).loadToc(this)
108 | }
109 | }
110 |
--------------------------------------------------------------------------------
/src/utils/chapter.ts:
--------------------------------------------------------------------------------
1 | import {Book, BookData, BookTocChapter} from "./book"
2 | import {file, crypto} from "./tsimports"
3 | import {source} from "."
4 |
5 | export interface ChapterInfo extends BookTocChapter {}
6 |
7 | export class Chapter {
8 | info: ChapterInfo
9 | book: BookData
10 |
11 | constructor(info: ChapterInfo, book: BookData) {
12 | this.info = info
13 | this.book = book
14 | }
15 |
16 | getUri(type: string) {
17 | if (!this.info.chapterUrl) throw new Error("Chapter url not found")
18 | if (!this.book.bookUrl) throw new Error("Book url not found")
19 | return (
20 | `internal://files/lordly-read/${type}/chapter/` +
21 | crypto.hashDigest({
22 | data: this.book.bookUrl,
23 | algo: "MD5"
24 | }) +
25 | "-" +
26 | crypto.hashDigest({
27 | data: this.info.chapterUrl,
28 | algo: "MD5"
29 | })
30 | )
31 | }
32 |
33 | hasCached() {
34 | return new Promise((resolve, reject) => {
35 | const uri = this.getUri("cache")
36 | file.access({
37 | uri,
38 | success() {
39 | resolve(true)
40 | },
41 | fail() {
42 | resolve(false)
43 | }
44 | })
45 | })
46 | }
47 |
48 | hasDownloaded() {
49 | return new Promise((resolve, reject) => {
50 | const uri = this.getUri("download")
51 | file.access({
52 | uri,
53 | success() {
54 | resolve(true)
55 | },
56 | fail() {
57 | resolve(false)
58 | }
59 | })
60 | })
61 | }
62 |
63 | async getContent() {
64 | try {
65 | return await this.getCachedContent()
66 | } catch {
67 | try {
68 | return await this.getDownloadedContent()
69 | } catch {
70 | return await this.getOnlineContent()
71 | }
72 | }
73 | }
74 |
75 | getCachedContent() {
76 | return new Promise((resolve, reject) => {
77 | const uri = this.getUri("cache")
78 | file.readText({
79 | uri,
80 | success(data) {
81 | console.log("cache: " + uri)
82 | resolve(data.text)
83 | },
84 | fail(data, code) {
85 | console.log("cache-fail: ", data, code)
86 | reject(new Error("Chapter not found in cache"))
87 | }
88 | })
89 | })
90 | }
91 |
92 | getDownloadedContent() {
93 | return new Promise((resolve, reject) => {
94 | const uri = this.getUri("download")
95 | file.readText({
96 | uri,
97 | success(data) {
98 | // console.log("download: " + uri)
99 | resolve(data.text)
100 | console.log("download")
101 | },
102 | fail(data, code) {
103 | console.log("download-fail: ", data, code)
104 | reject(new Error("Chapter not found in downloads"))
105 | }
106 | })
107 | })
108 | }
109 |
110 | async getOnlineContent() {
111 | if (!this.info.chapterUrl) throw new Error("Chapter url not found")
112 | const chapterUrl = this.info.chapterUrl
113 | const bookSource = source.getSource(this.book.bookSourceUrl)
114 | const book = new Book(this.book)
115 | const content = await bookSource.loadContent(book, chapterUrl)
116 | await this.cacheContent(content)
117 | if (await this.hasDownloaded()) await this.downloadContent()
118 | return content
119 | }
120 |
121 | cacheContent(content: string) {
122 | return new Promise((resolve, reject) => {
123 | const uri = this.getUri("cache")
124 | file.writeText({
125 | uri,
126 | text: content,
127 | success() {
128 | console.log("cache: " + uri)
129 | resolve()
130 | },
131 | fail(data, code) {
132 | console.log("cache-fail: ", data, code)
133 | reject(new Error("Failed to cache chapter"))
134 | }
135 | })
136 | })
137 | }
138 |
139 | clearCache() {
140 | return new Promise((resolve, reject) => {
141 | const uri = this.getUri("cache")
142 | file.delete({
143 | uri,
144 | success() {
145 | resolve()
146 | },
147 | fail(data, code) {
148 | reject(new Error("Failed to clear cache"))
149 | }
150 | })
151 | })
152 | }
153 |
154 | downloadContent() {
155 | return new Promise(async (resolve, reject) => {
156 | const uri = this.getUri("download")
157 | const content = await this.getContent()
158 | await this.clearCache().catch(() => {})
159 | file.writeText({
160 | uri,
161 | text: content,
162 | success() {
163 | resolve(true)
164 | },
165 | fail(data, code) {
166 | reject(new Error("Failed to download chapter"))
167 | }
168 | })
169 | })
170 | }
171 | }
172 |
--------------------------------------------------------------------------------
/src/utils/cookie.ts:
--------------------------------------------------------------------------------
1 | import {helper} from "./index"
2 |
3 | export class Cookie {
4 | cookies: Record>
5 | getter: () => Promise
6 | setter: (data: string) => Promise
7 |
8 | constructor(options: {getter: () => Promise; setter: (data: string) => Promise}) {
9 | const {getter, setter} = options
10 | this.getter = getter
11 | this.setter = setter
12 | this.refresh().then()
13 | }
14 |
15 | async refresh() {
16 | if (this.cookies) await this.save()
17 | this.cookies = JSON.parse(await this.getter())
18 | }
19 |
20 | async save() {
21 | if (!this.cookies) console.error("在未初始化 Cookie 类前 save")
22 | await this.setter(JSON.stringify(this.cookies))
23 | }
24 |
25 | getKey(url: string, key: string) {
26 | url = helper.getDomain(url)
27 | return this.cookies[url]?.[key]
28 | }
29 |
30 | setKey(url: string, key: string, value: string) {
31 | url = helper.getDomain(url)
32 | if (!this.cookies.hasOwnProperty(url)) this.cookies[url] = {}
33 | this.cookies[url][key] = value
34 | }
35 |
36 | getUrl(url: string) {
37 | url = helper.getDomain(url)
38 | return helper.record2Map(this.cookies[url] ?? {})
39 | }
40 |
41 | setUrl(url: string, value: Map) {
42 | url = helper.getDomain(url)
43 | if (!this.cookies.hasOwnProperty(url)) this.cookies[url] = {}
44 | value.forEach((v, k) => {
45 | this.cookies[url][k] = v
46 | })
47 | }
48 |
49 | getCookieFromHeader(header: string) {
50 | const value = new Map()
51 | header.split(";")?.forEach((item) => {
52 | value.set(item.split("=")[0]?.trim(), item.split("=")[1]?.trim())
53 | })
54 | return value
55 | }
56 |
57 | setUrlByHeader(url: string, header: string) {
58 | this.setUrl(url, this.getCookieFromHeader(header))
59 | }
60 | }
61 |
--------------------------------------------------------------------------------
/src/utils/fetch.ts:
--------------------------------------------------------------------------------
1 | import {fetch as systemFetch, request, file} from "./tsimports"
2 | import {cookie, helper} from "."
3 |
4 | class Response {
5 | data: string
6 | cookie: string
7 | headers: any
8 |
9 | constructor(data: string, headers?: any) {
10 | this.data = data
11 | this.headers = headers
12 | }
13 |
14 | body() {
15 | return this.data
16 | }
17 |
18 | cookies() {
19 | return helper.json2Map(this.cookie ?? "{}")
20 | }
21 | }
22 |
23 | export function fetch(rawUrl: string, options?: any): Promise {
24 | return new Promise((resolve, reject) => {
25 | const urlOptions = JSON.parse(rawUrl.split(",")[1] ?? "{}")
26 | let url = rawUrl.split(",")[0] ?? rawUrl
27 |
28 | if (!/^http/i.test(url)) {
29 | if (options.baseUrl) url = `${options.baseUrl}/${url}`.replace(/([^:]\/)\/+/g, "$1")
30 | else {
31 | console.error("错误链接格式")
32 | reject("错误链接格式")
33 | }
34 | }
35 |
36 | const cookieList = []
37 |
38 | cookie.getUrl(url)?.forEach((v, k) => {
39 | cookieList.push(`${k}=${v}`)
40 | })
41 |
42 | const fullOptions = {
43 | ...options,
44 | ...urlOptions,
45 | header: {
46 | "User-Agent":
47 | "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36 Edg/131.0.0.0",
48 | "Cookie": cookieList.join(";"),
49 | ...options?.header,
50 | ...urlOptions?.header
51 | }
52 | }
53 |
54 | fullOptions.header = {
55 | ...fullOptions.sourceHeader,
56 | ...fullOptions.header
57 | }
58 |
59 | if (fullOptions.charset) {
60 | if (/^utf-?8$/gi.test(fullOptions.charset)) {
61 | fullOptions.charset = undefined
62 | url = encodeURI(decodeURIComponent(url))
63 | } else if (/^gbk$/gi.test(fullOptions.charset)) {
64 | fullOptions.charset = "gbk"
65 | } else {
66 | console.error("不支持的编码类型")
67 | reject("不支持的编码类型")
68 | }
69 | } else {
70 | url = encodeURI(decodeURIComponent(url))
71 | }
72 |
73 | if (fullOptions.charset) {
74 | // const GBK = require("../third-party/GBK.js/dist/gbk.min")
75 | // url = GBK.URI.encodeURI(url)
76 | // request.download({
77 | // url,
78 | // ...fullOptions,
79 | // header: "",
80 | // success(data) {
81 | // console.log(data)
82 | // request.onDownloadComplete({
83 | // token: data.token,
84 | // success(res) {
85 | // file.readArrayBuffer({
86 | // uri: res.uri,
87 | // success(buffer) {
88 | // const result = GBK.decode(buffer.buffer)
89 | // // const result = buffer
90 | // file.delete({
91 | // uri: res.uri,
92 | // success() {
93 | // resolve(new Response(result))
94 | // },
95 | // fail(...err) {
96 | // reject(err)
97 | // }
98 | // })
99 | // },
100 | // fail(...err) {
101 | // reject(err)
102 | // }
103 | // })
104 | // },
105 | // fail(...err) {
106 | // reject(err)
107 | // }
108 | // })
109 | // },
110 | // fail(...err) {
111 | // console.log(err)
112 | // reject(err)
113 | // }
114 | // })
115 | } else {
116 | systemFetch.fetch({
117 | url,
118 | ...fullOptions,
119 | success(res) {
120 | const response = new Response(res.data, res.headers)
121 | if (res.headers["Set-Cookie"]) {
122 | cookie.setUrlByHeader(url, res.headers["Set-Cookie"])
123 | response.cookie = helper.map2Json(cookie.getCookieFromHeader(res.headers["Set-Cookie"]))
124 | }
125 | resolve(response)
126 | },
127 | fail(err) {
128 | console.log(err)
129 | if ((fullOptions.retry ?? 0) > 0) {
130 | fetch(url, {...fullOptions, retry: fullOptions.retry - 1})
131 | .then(resolve)
132 | .catch(reject)
133 | } else {
134 | reject(err)
135 | }
136 | }
137 | })
138 | }
139 | })
140 | }
141 |
--------------------------------------------------------------------------------
/src/utils/jsExtension.ts:
--------------------------------------------------------------------------------
1 | import {fetch} from "./fetch"
2 | import {Source} from "./source"
3 | import {date} from "./index"
4 |
5 | export class JsExtension {
6 | state: {
7 | vars: Map
8 | }
9 | source: Source
10 | src: string
11 |
12 | constructor(state: any, source: Source) {
13 | this.state = state
14 | this.source = source
15 | }
16 |
17 | updateSrc(src: string) {
18 | this.src = src
19 | }
20 |
21 | get(key: string) {
22 | return this.state.vars.get(key)
23 | }
24 |
25 | put(key: string, value: any) {
26 | this.state.vars.set(key, value)
27 | return value
28 | }
29 |
30 | getString(rule: string) {
31 | return this.source.parseGetRule(rule, this.src)
32 | }
33 |
34 | async post(urlStr: string, body: string, headers: Record) {
35 | let data: Record | string = {}
36 | if (headers["Content-Type"] === "application/json") {
37 | data = JSON.parse(body)
38 | } else if (headers["Content-Type"] === "application/x-www-form-urlencoded") {
39 | body.split("&").forEach((v) => {
40 | const [key, value] = v.split("=")
41 | data[key] = value
42 | })
43 | }
44 |
45 | return await this.source.fetch(urlStr, {
46 | method: "POST",
47 | header: headers,
48 | data
49 | })
50 | }
51 |
52 | async ajax(urlStr: string) {
53 | return (await this.source.fetch(urlStr)).body()
54 | }
55 |
56 | timeFormat(time: number) {
57 | return date.format(new Date(time), "yyyy-MM-dd hh:mm:ss")
58 | }
59 | }
60 |
--------------------------------------------------------------------------------
/src/utils/source.ts:
--------------------------------------------------------------------------------
1 | import {JsExtension} from "./jsExtension"
2 | import {Cookie} from "./cookie"
3 | import {Book, BookData} from "./book"
4 | import {fetch} from "./fetch"
5 | import {helper} from "./index"
6 | import {jsonPath} from "../third-party/JSONPath/jsonpath"
7 |
8 | export interface SourceData {
9 | bookSourceComment: string
10 | bookSourceGroup: string
11 | bookSourceName: string
12 | bookSourceType: number
13 | bookSourceUrl: string
14 | concurrentRate: string
15 | customOrder: number
16 | enabled: boolean
17 | enabledCookieJar: boolean
18 | enabledExplore: boolean
19 | exploreUrl: string
20 | jsLib: string
21 | lastUpdateTime: number
22 | loginCheckJs: string
23 | loginUi: string
24 | loginUrl: string
25 | respondTime: number
26 | ruleBookInfo: RuleBookInfo
27 | ruleContent: RuleContent
28 | ruleExplore: RuleExplore
29 | ruleSearch: RuleSearch
30 | ruleToc: RuleToc
31 | searchUrl: string
32 | weight: number
33 | }
34 |
35 | export interface RuleBookInfo {
36 | author: string
37 | coverUrl: string
38 | init: string
39 | intro: string
40 | kind: string
41 | lastChapter: string
42 | name: string
43 | tocUrl: string
44 | wordCount: string
45 | }
46 |
47 | export interface RuleContent {
48 | content: string
49 | }
50 |
51 | export interface RuleExplore {
52 | author: string
53 | bookList: string
54 | bookUrl: string
55 | coverUrl: string
56 | intro: string
57 | kind: string
58 | name: string
59 | wordCount: string
60 | lastChapter: string
61 | }
62 |
63 | export interface RuleSearch extends RuleExplore {
64 | checkKeyWord: string
65 | }
66 |
67 | export interface RuleToc {
68 | chapterList: string
69 | chapterName: string
70 | chapterUrl: string
71 | isVolume: string
72 | updateTime: string
73 | }
74 |
75 | export interface SourceUi {
76 | bookSourceUrl: string
77 | bookSourceName: string
78 | enabled: boolean
79 | enabledExplore: boolean
80 | hasExplore: boolean
81 | hasLogin: boolean
82 | loginUi?: LoginUiComponent[]
83 | }
84 |
85 | export interface LoginUiComponent {
86 | type: string
87 | name: string
88 | value: string
89 | }
90 |
91 | export class Source {
92 | raw: SourceData
93 | cookie: Cookie
94 | java = new JsExtension(
95 | {
96 | vars: new Map()
97 | },
98 | this
99 | )
100 | loginInfoMap: string
101 | loginHeader: string
102 | variable: string
103 | jsonPathCache: Map = new Map()
104 |
105 | constructor(raw: SourceData, cookie: Cookie) {
106 | this.raw = raw
107 | this.cookie = cookie
108 | }
109 |
110 | get bookSourceUrl() {
111 | return this.raw.bookSourceUrl
112 | }
113 |
114 | get bookSourceName() {
115 | return this.raw.bookSourceName
116 | }
117 |
118 | get enabled() {
119 | return this.raw.enabled
120 | }
121 |
122 | set enabled(value: boolean) {
123 | this.raw.enabled = value
124 | }
125 |
126 | get enabledExplore() {
127 | return this.raw.enabledExplore
128 | }
129 |
130 | set enabledExplore(value: boolean) {
131 | this.raw.enabledExplore = value
132 | }
133 |
134 | get hasExplore() {
135 | return this.raw.ruleExplore !== undefined
136 | }
137 |
138 | get hasLogin() {
139 | return this.raw.loginUrl !== undefined
140 | }
141 |
142 | get loginUi() {
143 | try {
144 | const loginInfoMap = helper.json2Map(this.loginInfoMap ?? "{}")
145 | return JSON.parse(this.raw.loginUi)?.map((v: Partial) => {
146 | return {
147 | value: loginInfoMap.get(v.name) ?? "",
148 | ...v
149 | }
150 | }) as LoginUiComponent[]
151 | } catch (e) {
152 | return undefined
153 | }
154 | }
155 |
156 | get additionalData() {
157 | return {
158 | bookSourceUrl: this.bookSourceUrl,
159 | loginInfoMap: this.loginInfoMap,
160 | loginHeader: this.loginHeader,
161 | variable: this.variable
162 | }
163 | }
164 |
165 | set additionalData(value: {
166 | bookSourceUrl: string
167 | loginInfoMap: string
168 | loginHeader: string
169 | variable: string
170 | }) {
171 | this.loginInfoMap = value.loginInfoMap
172 | this.loginHeader = value.loginHeader
173 | this.variable = value.variable
174 | }
175 |
176 | get sourceHeader() {
177 | return JSON.parse(this.loginHeader ?? "{}")
178 | }
179 |
180 | // noinspection JSUnusedGlobalSymbols // used in eval
181 | getLoginInfoMap() {
182 | return helper.json2Map(this.loginInfoMap)
183 | }
184 |
185 | // noinspection JSUnusedGlobalSymbols // used in eval
186 | putLoginHeader(header: string) {
187 | this.loginHeader = header
188 | }
189 |
190 | getVariable() {
191 | return this.variable ?? ""
192 | }
193 |
194 | async fetch(url: string, options?: any) {
195 | return await fetch(url, {
196 | baseUrl: this.bookSourceUrl,
197 | sourceHeader: this.sourceHeader,
198 | ...options
199 | })
200 | }
201 |
202 | async executeJs(js: string, additional: Record, debug = false) {
203 | // noinspection JSUnusedLocalSymbols
204 | let {key, page, result, resolve, book, baseUrl} = {
205 | book: new Book({bookSourceUrl: this.bookSourceUrl}),
206 | baseUrl: this.bookSourceUrl,
207 | ...additional
208 | } as Record
209 | const java = this.java
210 | const cookie = this.cookie
211 | const source = this
212 |
213 | let resultResolve: (value: any) => void
214 | let resultReject: (value: any) => void
215 |
216 | const resultPromise = new Promise((resolve, reject) => {
217 | resultResolve = (res) => {
218 | if (debug) console.log(res)
219 | resolve(res)
220 | }
221 | resultReject = (e) => {
222 | if (debug) console.log(e)
223 | reject(e)
224 | }
225 | })
226 |
227 | // js = js.replace(/java\.(.*?)\(/gi, "await java.$1(")
228 |
229 | js = js
230 | .replace(/(?<=\s|\(|=|\n|\[)([.\w]+)\(/g, "await $1(") // 将函数调用全部转换为 await
231 | .replace(/^([.\w]+)\(/g, "await $1(") // 将函数调用全部转换为 await
232 | .replace(/\((\w+)\(([^()\n]*?)\)/g, "(await $1($2))") // 将函数调用全部转换为 await
233 | .replace(/function( await)? (\w+)\(/g, "async function $2(") // 将函数声明全部转换为 async function
234 | .replace(/new( await)? (\w+)\(/g, "new $2(") // 将类实例化中的 await 去掉
235 | .replace(/await (if|else if|catch|for|while)/g, "$1") // 去掉关键字前的 await
236 | .replace(/await (\w+)\((.*?)\)/g, "(await $1($2))") // 给 await 函数调用加括号
237 |
238 | js =
239 | "async function main() {\n" +
240 | (this.raw.jsLib ?? "")
241 | .replace(/(?<=\s|\(|=|\n)([.\w]+)\(/g, "await $1(") // 将函数调用全部转换为 await
242 | .replace(/^([.\w]+)\(/g, "await $1(") // 将函数调用全部转换为 await
243 | .replace(/function( await)? (\w+)\(/g, "async function $2(") // 将函数声明全部转换为 async function
244 | .replace(/new( await)? (\w+)\(/g, "new $2(") // 将类实例化中的 await 去掉
245 | .replace(/await (if|else if|catch|for|while)/g, "$1") // 去掉关键字前的 await
246 | .replace(/await (\w+)\((.*?)\)/g, "(await $1($2))") // 给 await 函数调用加括号
247 | .replace(/(const|let|var)\s*\{[\w\s,]+}\s*=\s*this\s*\n/g, "") + // 去掉 this 变量声明
248 | js.replace(/\n(.+)\s*$/i, "\nreturn $1").replace(/^(.+)$/i, "return $1") +
249 | "\n}\nmain().then(r=>resultResolve(r)).catch(e=>resultReject(e))"
250 |
251 | if (debug) console.log(js)
252 |
253 | try {
254 | global.runGC()
255 | eval(js)
256 | return await resultPromise
257 | } catch (e) {
258 | console.log(e)
259 | return result
260 | }
261 | }
262 |
263 | async parseGetRule(
264 | rule: string,
265 | result: string,
266 | maybeJs = false,
267 | isList = false,
268 | additional: Record = {},
269 | debug = false
270 | ) {
271 | let res: string | string[] = rule
272 |
273 | const additionalKeys = Object.keys(additional)
274 |
275 | if (additionalKeys.includes(rule)) {
276 | return [additional[rule]]
277 | } else if (/^\$\./.test(rule)) {
278 | // JsonPath
279 | if (debug) console.log(rule, result)
280 | global.runGC()
281 | const $ = JSON.parse(result)
282 | if (isList) {
283 | res = jsonPath($, rule.replace(/\[-1]/gi, "[-1:]"))
284 | } else {
285 | let path = this.jsonPathCache.get(rule)
286 | if (path === undefined) {
287 | path = jsonPath($, rule.replace(/\[-1]/gi, "[-1:]"), {
288 | resultType: "PATH"
289 | })
290 | if (path.length === 1) {
291 | this.jsonPathCache.set(rule, path)
292 | }
293 | }
294 | for (const v of path) {
295 | res = eval(v)
296 | }
297 | }
298 | } else if (maybeJs) {
299 | try {
300 | res = await this.executeJs(rule, {
301 | result,
302 | ...additional
303 | })
304 | } catch (e) {
305 | console.log(e)
306 | }
307 | }
308 |
309 | if (debug) console.log(rule, res)
310 |
311 | if (res instanceof Array) {
312 | return res
313 | } else {
314 | return [res]
315 | }
316 | }
317 |
318 | async parseBracketRule(
319 | rule: string,
320 | result: string,
321 | maybeJs = false,
322 | isList = false,
323 | additional?: Record,
324 | debug = false
325 | ) {
326 | const regexpRule = rule.match(/##.+(##.*)?/gi)?.[0]
327 | rule = rule.replace(/##.+(##.*)?/gi, "")
328 |
329 | let resultList = []
330 | for (const orPart of rule.split("||")) {
331 | if (debug) console.log(orPart)
332 | for (const andPart of orPart.split("&&")) {
333 | resultList.push(
334 | ...(await this.parseGetRule(andPart, result, maybeJs, isList, additional, debug))
335 | )
336 | }
337 | if (resultList.length > 0) break
338 | }
339 |
340 | resultList = resultList.flat(3).map((v) => (typeof v === "string" ? v : JSON.stringify(v)))
341 |
342 | if (regexpRule) {
343 | resultList = resultList.map((res) =>
344 | res.replace(
345 | new RegExp(regexpRule.split("##")[1] ?? "", "gi"),
346 | regexpRule.split("##")[2] ?? ""
347 | )
348 | )
349 | }
350 |
351 | return resultList
352 | }
353 |
354 | async parseRule(
355 | rule: string,
356 | result: string,
357 | isList = false,
358 | additional?: Record,
359 | debug = false
360 | ) {
361 | if (!rule) {
362 | return isList ? [] : ""
363 | }
364 |
365 | if (rule.match(/{{[\s\S]*?}}/gi)) {
366 | for (const v of rule.match(/{{[\s\S]*?}}/gi)) {
367 | const bracketRule = v.replace(/^{{|}}$/gi, "")
368 | rule = rule.replace(
369 | v,
370 | (await this.parseBracketRule(bracketRule, result, true, isList, additional, debug)).join(
371 | ", "
372 | )
373 | )
374 | }
375 | }
376 |
377 | let parts = rule
378 | .split(/(@js:[\s\S]*?$)|([\s\S]*?<\/js>)/gi)
379 | .filter((v) => !!v && !v?.match(/^\s*$/))
380 | .filter((v) => !v.match(/^undefined$/gi))
381 |
382 | let res = undefined
383 |
384 | if (debug) console.log(parts)
385 |
386 | for (const v of parts) {
387 | if (/^|^@js:|<\/js>$/gi.test(v)) {
388 | let js = v.replace(/^|^@js:|<\/js>$/gi, "")
389 | if (js.match(/{{[\s\S]*?}}/gi)) {
390 | for (const v of js.match(/{{[\s\S]*?}}/gi)) {
391 | const rule = v.replace(/^{{|}}$/gi, "")
392 | js = js.replace(
393 | v,
394 | (await this.parseBracketRule(rule, result, false, isList, additional, debug)).join(
395 | ", "
396 | )
397 | )
398 | }
399 | }
400 | if (debug) console.log(js)
401 | res = await this.executeJs(js, {result: res ?? result, ...additional}, debug)
402 | } else if (/^\s*##.+(##.*)?\s*$/.test(v)) {
403 | if (!isList && typeof res === "string") {
404 | res = res.replace(new RegExp(v.split("##")[1] ?? "", "gi"), v.split("##")[2] ?? "")
405 | }
406 | } else if (!v.match(/{{[\s\S]*?}}/gi)) {
407 | res = await this.parseBracketRule(v, result, false, isList, additional, debug)
408 | if (!isList) {
409 | res = res.join(", ")
410 | }
411 | } else {
412 | res ??= v
413 | }
414 | }
415 |
416 | if (!isList && typeof res === "string") {
417 | if (res.match(/{{[\s\S]*?}}/gi)) {
418 | for (const v of res.match(/{{[\s\S]*?}}/gi)) {
419 | const rule = v.replace(/^{{|}}$/gi, "")
420 | if (debug) console.log(v, rule)
421 | res = res.replace(
422 | v,
423 | await this.parseBracketRule(rule, result, true, isList, additional, debug)
424 | )
425 | }
426 | }
427 | }
428 |
429 | return res
430 | }
431 |
432 | async login(loginInfoMap: Map) {
433 | this.loginInfoMap = helper.map2Json(loginInfoMap)
434 |
435 | if (!this.hasLogin) {
436 | return {
437 | success: false,
438 | message: "未配置登录信息"
439 | }
440 | }
441 |
442 | if (!this.raw.loginUrl.match(/^|^@js:|<\/js>$/gi)) {
443 | console.error("暂不支持链接登录")
444 | }
445 |
446 | const js = this.raw.loginUrl
447 | .replace(/^|^@js:|<\/js>$/gi, "")
448 | .replace(/function login\(\) {/gi, "async function login() {")
449 |
450 | let resultResolve: (value: {success: boolean; message: string}) => void
451 |
452 | const result = new Promise<{
453 | success: boolean
454 | message: string
455 | }>((resolve) => {
456 | resultResolve = resolve
457 | })
458 |
459 | await this.executeJs(
460 | js +
461 | "\nlogin().then(r=>resolve({success:true, msg:''})).catch(e=>resolve({success:false, msg:e}))",
462 | {
463 | resolve: resultResolve
464 | }
465 | )
466 |
467 | return await result
468 | }
469 |
470 | async search(key: string, page: number) {
471 | let parts = this.raw.searchUrl
472 | // .replace("pageSize=20", "pageSize=10")
473 | .split(/(@js:[\s\S]*?$)|([\s\S]*?<\/js>)/gi)
474 | .filter((v) => !!v && !v?.match(/^\s*$/))
475 | .filter((v) => !v.match(/^undefined$/gi))
476 | let url = parts.shift()
477 |
478 | for (const v of parts) {
479 | const js = v.replace(/^|^@js:|<\/js>$/gi, "")
480 | url = await this.executeJs(js, {result: url})
481 | }
482 |
483 | if (url.match(/{{[\s\S]*?}}/gi)) {
484 | for (const v of url.match(/{{[\s\S]*?}}/gi)) {
485 | const rule = v.replace(/^{{|}}$/gi, "")
486 | url = url.replace(v, (await this.parseBracketRule(rule, url, true, false, {key, page}))[0])
487 | }
488 | }
489 |
490 | url.match(/{(key|page)}/gi)?.forEach((v) => {
491 | url = url.replace(v, (/key/gi.test(v) ? key : page) as string)
492 | })
493 |
494 | const response = (
495 | await this.fetch(url, {
496 | responseType: "text"
497 | })
498 | ).body()
499 |
500 | this.java.updateSrc(response)
501 |
502 | const resultList = []
503 |
504 | for (const v of await this.parseRule(this.raw.ruleSearch.bookList, response, true)) {
505 | this.java.updateSrc(v)
506 | try {
507 | resultList.push(
508 | new Book({
509 | bookSourceUrl: this.bookSourceUrl,
510 | name: await this.parseRule(this.raw.ruleSearch.name, v, false, {baseUrl: url}),
511 | author: await this.parseRule(this.raw.ruleSearch.author, v, false, {baseUrl: url}),
512 | kind: await this.parseRule(this.raw.ruleSearch.kind, v, true, {baseUrl: url}),
513 | coverUrl: await this.parseRule(this.raw.ruleSearch.coverUrl, v, false, {baseUrl: url}),
514 | intro: await this.parseRule(this.raw.ruleSearch.intro, v, false, {baseUrl: url}),
515 | wordCount: await this.parseRule(this.raw.ruleSearch.wordCount, v, false, {
516 | baseUrl: url
517 | }),
518 | bookUrl: await this.parseRule(this.raw.ruleSearch.bookUrl, v, false, {baseUrl: url}),
519 | lastChapter: await this.parseRule(this.raw.ruleSearch.lastChapter, v, false, {
520 | baseUrl: url
521 | })
522 | })
523 | )
524 | } catch (e) {
525 | console.log(e)
526 | }
527 | }
528 |
529 | return resultList
530 | }
531 |
532 | async detail(bookData: BookData) {
533 | const book = new Book(bookData)
534 | const response = await this.parseRule(
535 | this.raw.ruleBookInfo.init,
536 | (
537 | await this.fetch(bookData.bookUrl, {
538 | responseType: "text"
539 | })
540 | ).body(),
541 | false,
542 | {book}
543 | )
544 |
545 | this.java.updateSrc(response)
546 |
547 | book.name = helper.withDefault(
548 | await this.parseRule(this.raw.ruleBookInfo.name, response, false, {
549 | book,
550 | baseUrl: book.bookUrl
551 | }),
552 | book.name
553 | )
554 | book.author = helper.withDefault(
555 | await this.parseRule(this.raw.ruleBookInfo.author, response, false, {
556 | book,
557 | baseUrl: book.bookUrl
558 | }),
559 | book.author
560 | )
561 | book.kind = helper.withDefault(
562 | (
563 | await this.parseRule(this.raw.ruleBookInfo.kind, response, false, {
564 | book,
565 | baseUrl: book.bookUrl
566 | })
567 | )
568 | .split(",")
569 | .map((v: string) => v.trim())
570 | .filter((v: string) => !!v),
571 | book.kind
572 | )
573 | book.wordCount = helper.withDefault(
574 | await this.parseRule(this.raw.ruleBookInfo.wordCount, response, false, {
575 | book,
576 | baseUrl: book.bookUrl
577 | }),
578 | book.wordCount
579 | )
580 | book.lastChapter = helper.withDefault(
581 | await this.parseRule(this.raw.ruleBookInfo.lastChapter, response, false, {
582 | book,
583 | baseUrl: book.bookUrl
584 | }),
585 | book.lastChapter
586 | )
587 | book.coverUrl = helper.withDefault(
588 | await this.parseRule(this.raw.ruleBookInfo.coverUrl, response, false, {
589 | book,
590 | baseUrl: book.bookUrl
591 | }),
592 | book.coverUrl
593 | )
594 | book.intro = helper.withDefault(
595 | await this.parseRule(this.raw.ruleBookInfo.intro, response, false, {
596 | book,
597 | baseUrl: book.bookUrl
598 | }),
599 | book.intro
600 | )
601 | book.tocUrl = helper.withDefault(
602 | await this.parseRule(this.raw.ruleBookInfo.tocUrl, response, false, {
603 | book,
604 | baseUrl: book.bookUrl
605 | }),
606 | book.tocUrl
607 | )
608 |
609 | return book
610 | }
611 |
612 | async loadToc(book: Book) {
613 | const response = (
614 | await this.fetch(book.tocUrl ?? book.bookUrl, {
615 | responseType: "text"
616 | })
617 | ).body()
618 |
619 | this.java.updateSrc(response)
620 |
621 | book.toc = []
622 |
623 | for (const v of await this.parseRule(this.raw.ruleToc.chapterList, response, true)) {
624 | this.java.updateSrc(v)
625 | try {
626 | book.toc.push({
627 | chapterInfo: await this.parseRule(this.raw.ruleToc.updateTime, v, false, {
628 | baseUrl: book.bookUrl
629 | }),
630 | chapterName: await this.parseRule(this.raw.ruleToc.chapterName, v, false, {
631 | baseUrl: book.bookUrl
632 | }),
633 | chapterUrl: await this.parseRule(this.raw.ruleToc.chapterUrl, v, false, {
634 | baseUrl: book.bookUrl
635 | }),
636 | isVolume: await this.parseRule(this.raw.ruleToc.isVolume, v, false, {
637 | baseUrl: book.bookUrl
638 | })
639 | })
640 | } catch (e) {
641 | console.log(e)
642 | }
643 | }
644 |
645 | return book
646 | }
647 |
648 | async loadContent(book: Book, chapterUrl: string) {
649 | const response = (
650 | await this.fetch(chapterUrl, {
651 | responseType: "text"
652 | })
653 | ).body()
654 |
655 | const content = await this.parseRule(this.raw.ruleContent.content, response, false, {
656 | baseUrl: chapterUrl
657 | })
658 |
659 | return content
660 | .replace(/<(?!img\b)[^>]*>/gi, "") // 去掉除
以外的标签
661 | .replace(/&/gi, "&")
662 | .replace(/ /gi, " ")
663 | .split(/\n+/)
664 | .map((v: string) => v.trim())
665 | .filter((v: string) => !!v)
666 | .join("\n")
667 | }
668 | }
669 |
--------------------------------------------------------------------------------
/src/utils/tsimports.js:
--------------------------------------------------------------------------------
1 | import router from "@system.router"
2 | import storage from "@system.storage"
3 | import prompt from "@system.prompt"
4 | import fetch from "@system.fetch"
5 | import device from "@system.device"
6 | import request from "@system.request"
7 | import file from "@system.file"
8 | import crypto from "@system.crypto"
9 | import brightness from "@system.brightness"
10 |
11 | export {fetch, storage, device, router, prompt, request, file, crypto, brightness}
12 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "allowJs": true,
4 | "esModuleInterop": true,
5 | "module": "commonjs",
6 | "target": "es5",
7 | "outDir": "./dist"
8 | },
9 | "include": [
10 | "src/**/*"
11 | ],
12 | "exclude": [
13 | "src/**/*.ux"
14 | ]
15 | }
--------------------------------------------------------------------------------