mirror of
https://github.com/tachiyomiorg/website.git
synced 2024-10-31 23:15:05 +01:00
Merge remote-tracking branch 'origin/v3-vitepress'
This commit is contained in:
commit
437d408649
BIN
.github/assets/logo.png
vendored
Normal file
BIN
.github/assets/logo.png
vendored
Normal file
Binary file not shown.
After Width: | Height: | Size: 4.7 KiB |
24
.gitignore
vendored
Normal file
24
.gitignore
vendored
Normal file
@ -0,0 +1,24 @@
|
||||
!.env.example
|
||||
*.code-workspace
|
||||
*.log
|
||||
*.tgz
|
||||
.cache
|
||||
.DS_Store
|
||||
.env
|
||||
.env.*
|
||||
.idea
|
||||
.temp
|
||||
.vite_opt_cache
|
||||
.vscode
|
||||
/coverage
|
||||
website/src/client/shared.ts
|
||||
website/src/node/shared.ts
|
||||
cache
|
||||
dist
|
||||
examples-temp
|
||||
node_modules
|
||||
pnpm-global
|
||||
website/src/.temp
|
||||
website/src/assets/style/sandbox.styl
|
||||
temp
|
||||
TODOs.md
|
76
CODE_OF_CONDUCT.md
Normal file
76
CODE_OF_CONDUCT.md
Normal file
@ -0,0 +1,76 @@
|
||||
# Code of Conduct
|
||||
|
||||
## Our Pledge
|
||||
|
||||
In the interest of fostering an open and welcoming environment, we as
|
||||
contributors and maintainers pledge to making participation in our project and
|
||||
our community a harassment-free experience for everyone, regardless of age, body
|
||||
size, disability, ethnicity, sex characteristics, gender identity and expression,
|
||||
level of experience, education, socio-economic status, nationality, personal
|
||||
appearance, race, religion, or sexual identity and orientation.
|
||||
|
||||
## Our Standards
|
||||
|
||||
Examples of behavior that contributes to creating a positive environment
|
||||
include:
|
||||
|
||||
* Using welcoming and inclusive language
|
||||
* Being respectful of differing viewpoints and experiences
|
||||
* Gracefully accepting constructive criticism
|
||||
* Focusing on what is best for the community
|
||||
* Showing empathy towards other community members
|
||||
|
||||
Examples of unacceptable behavior by participants include:
|
||||
|
||||
* The use of sexualized language or imagery and unwelcome sexual attention or
|
||||
advances
|
||||
* Trolling, insulting/derogatory comments, and personal or political attacks
|
||||
* Public or private harassment
|
||||
* Publishing others' private information, such as a physical or electronic
|
||||
address, without explicit permission
|
||||
* Other conduct which could reasonably be considered inappropriate in a
|
||||
professional setting
|
||||
|
||||
## Our Responsibilities
|
||||
|
||||
Project maintainers are responsible for clarifying the standards of acceptable
|
||||
behavior and are expected to take appropriate and fair corrective action in
|
||||
response to any instances of unacceptable behavior.
|
||||
|
||||
Project maintainers have the right and responsibility to remove, edit, or
|
||||
reject comments, commits, code, wiki edits, issues, and other contributions
|
||||
that are not aligned to this Code of Conduct, or to ban temporarily or
|
||||
permanently any contributor for other behaviors that they deem inappropriate,
|
||||
threatening, offensive, or harmful.
|
||||
|
||||
## Scope
|
||||
|
||||
This Code of Conduct applies both within project spaces and in public spaces
|
||||
when an individual is representing the project or its community. Examples of
|
||||
representing a project or community include using an official project e-mail
|
||||
address, posting via an official social media account, or acting as an appointed
|
||||
representative at an online or offline event. Representation of a project may be
|
||||
further defined and clarified by project maintainers.
|
||||
|
||||
## Enforcement
|
||||
|
||||
Instances of abusive, harassing, or otherwise unacceptable behavior may be
|
||||
reported by contacting the project team at the Tachiyomi [Discord server](https://discord.gg/tachiyomi). All
|
||||
complaints will be reviewed and investigated and will result in a response that
|
||||
is deemed necessary and appropriate to the circumstances. The project team is
|
||||
obligated to maintain confidentiality with regard to the reporter of an incident.
|
||||
Further details of specific enforcement policies may be posted separately.
|
||||
|
||||
Project maintainers who do not follow or enforce the Code of Conduct in good
|
||||
faith may face temporary or permanent repercussions as determined by other
|
||||
members of the project's leadership.
|
||||
|
||||
## Attribution
|
||||
|
||||
This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,
|
||||
available at [https://www.contributor-covenant.org/version/1/4/code-of-conduct.html](https://www.contributor-covenant.org/version/1/4/code-of-conduct.html)
|
||||
|
||||
[homepage]: https://www.contributor-covenant.org
|
||||
|
||||
For answers to common questions about this code of conduct, see
|
||||
[https://www.contributor-covenant.org/faq](https://www.contributor-covenant.org/faq)
|
81
CONTRIBUTING.md
Normal file
81
CONTRIBUTING.md
Normal file
@ -0,0 +1,81 @@
|
||||
# Tachiyomi Website Contributing Guide
|
||||
|
||||
Before submitting your contribution, please make sure to take a moment and read through the following guidelines:
|
||||
|
||||
- [Code of Conduct](../CODE_OF_CONDUCT.md)
|
||||
- [Development Setup](#development-setup)
|
||||
- [Project Structure](#project-structure)
|
||||
|
||||
## Development Setup
|
||||
|
||||
You will need [Node.js](http://nodejs.org) **version 18+**, and [pnpm](https://pnpm.io/installation) **version 8+**.
|
||||
|
||||
After cloning the repo and entering the the directory, go to the `/website` folder and run:
|
||||
|
||||
``` bash
|
||||
# Installs any dependencies needed.
|
||||
$ pnpm install
|
||||
```
|
||||
|
||||
To run the project now, run:
|
||||
|
||||
``` bash
|
||||
# This command start a local server you can access and edit live.
|
||||
$ pnpm dev
|
||||
```
|
||||
|
||||
### Commonly used PNPM scripts
|
||||
|
||||
``` bash
|
||||
# This command will generate a static site inside a dist directory in your project.
|
||||
$ pnpm build
|
||||
|
||||
# Run this command to preview the built files in a local server.
|
||||
$ pnpm preview
|
||||
```
|
||||
|
||||
**Please make sure to have `pnpm test` pass successfully before submitting a PR.** Although the same tests will be run against your PR on the CI server, it is better to have it working locally.
|
||||
|
||||
It is also recommended you lint your files before the PR.
|
||||
|
||||
## Project Structure
|
||||
|
||||
- **`website`**: contains all the website related files.
|
||||
- **`src`**: contains all the markdown files used for the website.
|
||||
- **`.vitepress`**:
|
||||
- **`theme`**: contains custom theme files.
|
||||
- `config.ts`: main configuration file for VitePress.
|
||||
- **`public`**: files to be exposed publicly without any processing.
|
||||
- **[`dist`](https://vitepress.dev/guide/deploy)**: contains built files for distribution.
|
||||
Note this directory is only updated when a release happens or when you run the build command.
|
||||
Changes to this folder will not carry over with Git.
|
||||
- `package.json`: contains information about which plugins are installed in the project.
|
||||
|
||||
## Images and Videos guidelines
|
||||
|
||||
### Common
|
||||
|
||||
- Use the Android Emulator
|
||||
|
||||
- Use the default white theme
|
||||
|
||||
- Preferably use local source or a self-hosted extension if your media contains series
|
||||
|
||||
- Resize to have a width of 648px
|
||||
|
||||
### Images
|
||||
|
||||
- Use `.webp`
|
||||
|
||||
### Videos
|
||||
|
||||
- Use `.webm` format
|
||||
- Encode it with our [HandBreak profile](../.github/tachiyomi-handbrake-profile.json)
|
||||
|
||||
- Remove audio track
|
||||
|
||||
## Credits
|
||||
|
||||
Thank you to all the people who have already contributed!
|
||||
|
||||
[![List of Contributors](https://contrib.rocks/image?repo=tachiyomiorg/website 'List of Contributors')](https://github.com/tachiyomiorg/website/graphs/contributors)
|
373
LICENSE
Normal file
373
LICENSE
Normal file
@ -0,0 +1,373 @@
|
||||
Mozilla Public License Version 2.0
|
||||
==================================
|
||||
|
||||
1. Definitions
|
||||
--------------
|
||||
|
||||
1.1. "Contributor"
|
||||
means each individual or legal entity that creates, contributes to
|
||||
the creation of, or owns Covered Software.
|
||||
|
||||
1.2. "Contributor Version"
|
||||
means the combination of the Contributions of others (if any) used
|
||||
by a Contributor and that particular Contributor's Contribution.
|
||||
|
||||
1.3. "Contribution"
|
||||
means Covered Software of a particular Contributor.
|
||||
|
||||
1.4. "Covered Software"
|
||||
means Source Code Form to which the initial Contributor has attached
|
||||
the notice in Exhibit A, the Executable Form of such Source Code
|
||||
Form, and Modifications of such Source Code Form, in each case
|
||||
including portions thereof.
|
||||
|
||||
1.5. "Incompatible With Secondary Licenses"
|
||||
means
|
||||
|
||||
(a) that the initial Contributor has attached the notice described
|
||||
in Exhibit B to the Covered Software; or
|
||||
|
||||
(b) that the Covered Software was made available under the terms of
|
||||
version 1.1 or earlier of the License, but not also under the
|
||||
terms of a Secondary License.
|
||||
|
||||
1.6. "Executable Form"
|
||||
means any form of the work other than Source Code Form.
|
||||
|
||||
1.7. "Larger Work"
|
||||
means a work that combines Covered Software with other material, in
|
||||
a separate file or files, that is not Covered Software.
|
||||
|
||||
1.8. "License"
|
||||
means this document.
|
||||
|
||||
1.9. "Licensable"
|
||||
means having the right to grant, to the maximum extent possible,
|
||||
whether at the time of the initial grant or subsequently, any and
|
||||
all of the rights conveyed by this License.
|
||||
|
||||
1.10. "Modifications"
|
||||
means any of the following:
|
||||
|
||||
(a) any file in Source Code Form that results from an addition to,
|
||||
deletion from, or modification of the contents of Covered
|
||||
Software; or
|
||||
|
||||
(b) any new file in Source Code Form that contains any Covered
|
||||
Software.
|
||||
|
||||
1.11. "Patent Claims" of a Contributor
|
||||
means any patent claim(s), including without limitation, method,
|
||||
process, and apparatus claims, in any patent Licensable by such
|
||||
Contributor that would be infringed, but for the grant of the
|
||||
License, by the making, using, selling, offering for sale, having
|
||||
made, import, or transfer of either its Contributions or its
|
||||
Contributor Version.
|
||||
|
||||
1.12. "Secondary License"
|
||||
means either the GNU General Public License, Version 2.0, the GNU
|
||||
Lesser General Public License, Version 2.1, the GNU Affero General
|
||||
Public License, Version 3.0, or any later versions of those
|
||||
licenses.
|
||||
|
||||
1.13. "Source Code Form"
|
||||
means the form of the work preferred for making modifications.
|
||||
|
||||
1.14. "You" (or "Your")
|
||||
means an individual or a legal entity exercising rights under this
|
||||
License. For legal entities, "You" includes any entity that
|
||||
controls, is controlled by, or is under common control with You. For
|
||||
purposes of this definition, "control" means (a) the power, direct
|
||||
or indirect, to cause the direction or management of such entity,
|
||||
whether by contract or otherwise, or (b) ownership of more than
|
||||
fifty percent (50%) of the outstanding shares or beneficial
|
||||
ownership of such entity.
|
||||
|
||||
2. License Grants and Conditions
|
||||
--------------------------------
|
||||
|
||||
2.1. Grants
|
||||
|
||||
Each Contributor hereby grants You a world-wide, royalty-free,
|
||||
non-exclusive license:
|
||||
|
||||
(a) under intellectual property rights (other than patent or trademark)
|
||||
Licensable by such Contributor to use, reproduce, make available,
|
||||
modify, display, perform, distribute, and otherwise exploit its
|
||||
Contributions, either on an unmodified basis, with Modifications, or
|
||||
as part of a Larger Work; and
|
||||
|
||||
(b) under Patent Claims of such Contributor to make, use, sell, offer
|
||||
for sale, have made, import, and otherwise transfer either its
|
||||
Contributions or its Contributor Version.
|
||||
|
||||
2.2. Effective Date
|
||||
|
||||
The licenses granted in Section 2.1 with respect to any Contribution
|
||||
become effective for each Contribution on the date the Contributor first
|
||||
distributes such Contribution.
|
||||
|
||||
2.3. Limitations on Grant Scope
|
||||
|
||||
The licenses granted in this Section 2 are the only rights granted under
|
||||
this License. No additional rights or licenses will be implied from the
|
||||
distribution or licensing of Covered Software under this License.
|
||||
Notwithstanding Section 2.1(b) above, no patent license is granted by a
|
||||
Contributor:
|
||||
|
||||
(a) for any code that a Contributor has removed from Covered Software;
|
||||
or
|
||||
|
||||
(b) for infringements caused by: (i) Your and any other third party's
|
||||
modifications of Covered Software, or (ii) the combination of its
|
||||
Contributions with other software (except as part of its Contributor
|
||||
Version); or
|
||||
|
||||
(c) under Patent Claims infringed by Covered Software in the absence of
|
||||
its Contributions.
|
||||
|
||||
This License does not grant any rights in the trademarks, service marks,
|
||||
or logos of any Contributor (except as may be necessary to comply with
|
||||
the notice requirements in Section 3.4).
|
||||
|
||||
2.4. Subsequent Licenses
|
||||
|
||||
No Contributor makes additional grants as a result of Your choice to
|
||||
distribute the Covered Software under a subsequent version of this
|
||||
License (see Section 10.2) or under the terms of a Secondary License (if
|
||||
permitted under the terms of Section 3.3).
|
||||
|
||||
2.5. Representation
|
||||
|
||||
Each Contributor represents that the Contributor believes its
|
||||
Contributions are its original creation(s) or it has sufficient rights
|
||||
to grant the rights to its Contributions conveyed by this License.
|
||||
|
||||
2.6. Fair Use
|
||||
|
||||
This License is not intended to limit any rights You have under
|
||||
applicable copyright doctrines of fair use, fair dealing, or other
|
||||
equivalents.
|
||||
|
||||
2.7. Conditions
|
||||
|
||||
Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted
|
||||
in Section 2.1.
|
||||
|
||||
3. Responsibilities
|
||||
-------------------
|
||||
|
||||
3.1. Distribution of Source Form
|
||||
|
||||
All distribution of Covered Software in Source Code Form, including any
|
||||
Modifications that You create or to which You contribute, must be under
|
||||
the terms of this License. You must inform recipients that the Source
|
||||
Code Form of the Covered Software is governed by the terms of this
|
||||
License, and how they can obtain a copy of this License. You may not
|
||||
attempt to alter or restrict the recipients' rights in the Source Code
|
||||
Form.
|
||||
|
||||
3.2. Distribution of Executable Form
|
||||
|
||||
If You distribute Covered Software in Executable Form then:
|
||||
|
||||
(a) such Covered Software must also be made available in Source Code
|
||||
Form, as described in Section 3.1, and You must inform recipients of
|
||||
the Executable Form how they can obtain a copy of such Source Code
|
||||
Form by reasonable means in a timely manner, at a charge no more
|
||||
than the cost of distribution to the recipient; and
|
||||
|
||||
(b) You may distribute such Executable Form under the terms of this
|
||||
License, or sublicense it under different terms, provided that the
|
||||
license for the Executable Form does not attempt to limit or alter
|
||||
the recipients' rights in the Source Code Form under this License.
|
||||
|
||||
3.3. Distribution of a Larger Work
|
||||
|
||||
You may create and distribute a Larger Work under terms of Your choice,
|
||||
provided that You also comply with the requirements of this License for
|
||||
the Covered Software. If the Larger Work is a combination of Covered
|
||||
Software with a work governed by one or more Secondary Licenses, and the
|
||||
Covered Software is not Incompatible With Secondary Licenses, this
|
||||
License permits You to additionally distribute such Covered Software
|
||||
under the terms of such Secondary License(s), so that the recipient of
|
||||
the Larger Work may, at their option, further distribute the Covered
|
||||
Software under the terms of either this License or such Secondary
|
||||
License(s).
|
||||
|
||||
3.4. Notices
|
||||
|
||||
You may not remove or alter the substance of any license notices
|
||||
(including copyright notices, patent notices, disclaimers of warranty,
|
||||
or limitations of liability) contained within the Source Code Form of
|
||||
the Covered Software, except that You may alter any license notices to
|
||||
the extent required to remedy known factual inaccuracies.
|
||||
|
||||
3.5. Application of Additional Terms
|
||||
|
||||
You may choose to offer, and to charge a fee for, warranty, support,
|
||||
indemnity or liability obligations to one or more recipients of Covered
|
||||
Software. However, You may do so only on Your own behalf, and not on
|
||||
behalf of any Contributor. You must make it absolutely clear that any
|
||||
such warranty, support, indemnity, or liability obligation is offered by
|
||||
You alone, and You hereby agree to indemnify every Contributor for any
|
||||
liability incurred by such Contributor as a result of warranty, support,
|
||||
indemnity or liability terms You offer. You may include additional
|
||||
disclaimers of warranty and limitations of liability specific to any
|
||||
jurisdiction.
|
||||
|
||||
4. Inability to Comply Due to Statute or Regulation
|
||||
---------------------------------------------------
|
||||
|
||||
If it is impossible for You to comply with any of the terms of this
|
||||
License with respect to some or all of the Covered Software due to
|
||||
statute, judicial order, or regulation then You must: (a) comply with
|
||||
the terms of this License to the maximum extent possible; and (b)
|
||||
describe the limitations and the code they affect. Such description must
|
||||
be placed in a text file included with all distributions of the Covered
|
||||
Software under this License. Except to the extent prohibited by statute
|
||||
or regulation, such description must be sufficiently detailed for a
|
||||
recipient of ordinary skill to be able to understand it.
|
||||
|
||||
5. Termination
|
||||
--------------
|
||||
|
||||
5.1. The rights granted under this License will terminate automatically
|
||||
if You fail to comply with any of its terms. However, if You become
|
||||
compliant, then the rights granted under this License from a particular
|
||||
Contributor are reinstated (a) provisionally, unless and until such
|
||||
Contributor explicitly and finally terminates Your grants, and (b) on an
|
||||
ongoing basis, if such Contributor fails to notify You of the
|
||||
non-compliance by some reasonable means prior to 60 days after You have
|
||||
come back into compliance. Moreover, Your grants from a particular
|
||||
Contributor are reinstated on an ongoing basis if such Contributor
|
||||
notifies You of the non-compliance by some reasonable means, this is the
|
||||
first time You have received notice of non-compliance with this License
|
||||
from such Contributor, and You become compliant prior to 30 days after
|
||||
Your receipt of the notice.
|
||||
|
||||
5.2. If You initiate litigation against any entity by asserting a patent
|
||||
infringement claim (excluding declaratory judgment actions,
|
||||
counter-claims, and cross-claims) alleging that a Contributor Version
|
||||
directly or indirectly infringes any patent, then the rights granted to
|
||||
You by any and all Contributors for the Covered Software under Section
|
||||
2.1 of this License shall terminate.
|
||||
|
||||
5.3. In the event of termination under Sections 5.1 or 5.2 above, all
|
||||
end user license agreements (excluding distributors and resellers) which
|
||||
have been validly granted by You or Your distributors under this License
|
||||
prior to termination shall survive termination.
|
||||
|
||||
************************************************************************
|
||||
* *
|
||||
* 6. Disclaimer of Warranty *
|
||||
* ------------------------- *
|
||||
* *
|
||||
* Covered Software is provided under this License on an "as is" *
|
||||
* basis, without warranty of any kind, either expressed, implied, or *
|
||||
* statutory, including, without limitation, warranties that the *
|
||||
* Covered Software is free of defects, merchantable, fit for a *
|
||||
* particular purpose or non-infringing. The entire risk as to the *
|
||||
* quality and performance of the Covered Software is with You. *
|
||||
* Should any Covered Software prove defective in any respect, You *
|
||||
* (not any Contributor) assume the cost of any necessary servicing, *
|
||||
* repair, or correction. This disclaimer of warranty constitutes an *
|
||||
* essential part of this License. No use of any Covered Software is *
|
||||
* authorized under this License except under this disclaimer. *
|
||||
* *
|
||||
************************************************************************
|
||||
|
||||
************************************************************************
|
||||
* *
|
||||
* 7. Limitation of Liability *
|
||||
* -------------------------- *
|
||||
* *
|
||||
* Under no circumstances and under no legal theory, whether tort *
|
||||
* (including negligence), contract, or otherwise, shall any *
|
||||
* Contributor, or anyone who distributes Covered Software as *
|
||||
* permitted above, be liable to You for any direct, indirect, *
|
||||
* special, incidental, or consequential damages of any character *
|
||||
* including, without limitation, damages for lost profits, loss of *
|
||||
* goodwill, work stoppage, computer failure or malfunction, or any *
|
||||
* and all other commercial damages or losses, even if such party *
|
||||
* shall have been informed of the possibility of such damages. This *
|
||||
* limitation of liability shall not apply to liability for death or *
|
||||
* personal injury resulting from such party's negligence to the *
|
||||
* extent applicable law prohibits such limitation. Some *
|
||||
* jurisdictions do not allow the exclusion or limitation of *
|
||||
* incidental or consequential damages, so this exclusion and *
|
||||
* limitation may not apply to You. *
|
||||
* *
|
||||
************************************************************************
|
||||
|
||||
8. Litigation
|
||||
-------------
|
||||
|
||||
Any litigation relating to this License may be brought only in the
|
||||
courts of a jurisdiction where the defendant maintains its principal
|
||||
place of business and such litigation shall be governed by laws of that
|
||||
jurisdiction, without reference to its conflict-of-law provisions.
|
||||
Nothing in this Section shall prevent a party's ability to bring
|
||||
cross-claims or counter-claims.
|
||||
|
||||
9. Miscellaneous
|
||||
----------------
|
||||
|
||||
This License represents the complete agreement concerning the subject
|
||||
matter hereof. If any provision of this License is held to be
|
||||
unenforceable, such provision shall be reformed only to the extent
|
||||
necessary to make it enforceable. Any law or regulation which provides
|
||||
that the language of a contract shall be construed against the drafter
|
||||
shall not be used to construe this License against a Contributor.
|
||||
|
||||
10. Versions of the License
|
||||
---------------------------
|
||||
|
||||
10.1. New Versions
|
||||
|
||||
Mozilla Foundation is the license steward. Except as provided in Section
|
||||
10.3, no one other than the license steward has the right to modify or
|
||||
publish new versions of this License. Each version will be given a
|
||||
distinguishing version number.
|
||||
|
||||
10.2. Effect of New Versions
|
||||
|
||||
You may distribute the Covered Software under the terms of the version
|
||||
of the License under which You originally received the Covered Software,
|
||||
or under the terms of any subsequent version published by the license
|
||||
steward.
|
||||
|
||||
10.3. Modified Versions
|
||||
|
||||
If you create software not governed by this License, and you want to
|
||||
create a new license for such software, you may create and use a
|
||||
modified version of this License if you rename the license and remove
|
||||
any references to the name of the license steward (except to note that
|
||||
such modified license differs from this License).
|
||||
|
||||
10.4. Distributing Source Code Form that is Incompatible With Secondary
|
||||
Licenses
|
||||
|
||||
If You choose to distribute Source Code Form that is Incompatible With
|
||||
Secondary Licenses under the terms of this version of the License, the
|
||||
notice described in Exhibit B of this License must be attached.
|
||||
|
||||
Exhibit A - Source Code Form License Notice
|
||||
-------------------------------------------
|
||||
|
||||
This Source Code Form is subject to the terms of the Mozilla Public
|
||||
License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
If it is not possible or desirable to put the notice in a particular
|
||||
file, then You may include the notice in a location (such as a LICENSE
|
||||
file in a relevant directory) where a recipient would be likely to look
|
||||
for such a notice.
|
||||
|
||||
You may add additional accurate notices of copyright ownership.
|
||||
|
||||
Exhibit B - "Incompatible With Secondary Licenses" Notice
|
||||
---------------------------------------------------------
|
||||
|
||||
This Source Code Form is "Incompatible With Secondary Licenses", as
|
||||
defined by the Mozilla Public License, v. 2.0.
|
64
README.md
Normal file
64
README.md
Normal file
@ -0,0 +1,64 @@
|
||||
<p align="center">
|
||||
<br>
|
||||
<a href="https://tachiyomi.org">
|
||||
<img src="./.github/assets/logo.png" width="90"/>
|
||||
</a>
|
||||
</p>
|
||||
|
||||
<h1 align="center">Tachiyomi <a href="#">Website</a></h1>
|
||||
<h3 align="center">Full-featured reader</h3>
|
||||
<p align="center">Discover and read manga, webtoons, comics, and more – easier than ever on your Android device.</p>
|
||||
|
||||
<p align="center">
|
||||
<a title="Discord server" href="https://discord.gg/tachiyomi">
|
||||
<img src="https://img.shields.io/discord/349436576037732353.svg?label=&labelColor=6A7EC2&color=7389D8&logo=discord&logoColor=FFFFFF">
|
||||
</a>
|
||||
<a title="GitHub downloads" href="https://github.com/tachiyomiorg/tachiyomi/releases">
|
||||
<img src="https://img.shields.io/github/downloads/tachiyomiorg/tachiyomi/total?label=downloads&labelColor=27303D&color=0D1117&logo=github&logoColor=FFFFFF&style=flat">
|
||||
</a>
|
||||
<br>
|
||||
<a title="Netlify deployment" href="https://app.netlify.com/sites/tachiyomi-v3-vitepress/deploys">
|
||||
<img src="https://api.netlify.com/api/v1/badges/a2fcfa92-2d32-463c-9051-909f97e06f46/deploy-status">
|
||||
</a>
|
||||
<br>
|
||||
<br>
|
||||
</p>
|
||||
|
||||
<h3 align="center">Contributing</h3>
|
||||
|
||||
<p align="center">
|
||||
<a href="./CODE_OF_CONDUCT.md">Code of conduct</a>
|
||||
·
|
||||
<a href="./CONTRIBUTING.md">Contributing guide</a>
|
||||
·
|
||||
<a href="https://tachiyomi.org/sandbox/style-guide/">Project style guide</a>
|
||||
</p>
|
||||
|
||||
<p align="center">Pull requests are welcome. For major changes, please open an issue first to discuss what you would like to change.</p>
|
||||
<p align="center">If you got any questions, join our <a target="_blank" href="https://discord.gg/tachiyomi">Discord server</a>.</p>
|
||||
|
||||
<h3 align="center">Repositories</h3>
|
||||
|
||||
<div>
|
||||
<p align="center">
|
||||
<a href="https://github.com/tachiyomiorg/tachiyomi/">
|
||||
<img src="https://github-readme-stats.vercel.app/api/pin/?username=tachiyomiorg&repo=tachiyomi&bg_color=161B22&text_color=c9d1d9&title_color=818CF8&icon_color=818CF8&border_radius=8&hide_border=true" alt="Android App">
|
||||
</a>
|
||||
<a href="https://github.com/tachiyomiorg/tachiyomi-extensions/">
|
||||
<img src="https://github-readme-stats.vercel.app/api/pin/?username=tachiyomiorg&repo=tachiyomi-extensions&bg_color=161B22&text_color=c9d1d9&title_color=818CF8&icon_color=818CF8&border_radius=8&hide_border=true" alt="App Extensions">
|
||||
</a>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<h3 align="center">License</h3>
|
||||
|
||||
<pre align="center">Copyright © 2023 The Tachiyomi Open Source Project<br><br>This Source Code Form is subject to the terms of the Mozilla Public<br>License, v. 2.0. If a copy of the MPL was not distributed with this<br>file, You can obtain one at http://mozilla.org/MPL/2.0/.</pre>
|
||||
|
||||
<h3 align="center">Credits</h3>
|
||||
|
||||
<p align="center">Thank you to all the people who have already contributed!</p>
|
||||
<p align="right">
|
||||
<a href="https://github.com/tachiyomiorg/tachiyomi/graphs/contributors">
|
||||
<img src="https://contrib.rocks/image?repo=tachiyomiorg/website" width="800"/>
|
||||
</a>
|
||||
</p>
|
12
netlify.toml
Normal file
12
netlify.toml
Normal file
@ -0,0 +1,12 @@
|
||||
[build.environment]
|
||||
NODE_VERSION = "18"
|
||||
|
||||
[build]
|
||||
base = "website"
|
||||
publish = "dist"
|
||||
|
||||
[context.production]
|
||||
command = "VITE_HOSTNAME=$URL pnpm build"
|
||||
|
||||
[context.deploy-preview]
|
||||
command = "VITE_HOSTNAME=$DEPLOY_PRIME_URL pnpm build"
|
13
website/.editorconfig
Normal file
13
website/.editorconfig
Normal file
@ -0,0 +1,13 @@
|
||||
root = true
|
||||
|
||||
[*]
|
||||
end_of_line = lf
|
||||
indent_style = tab
|
||||
indent_size = 4
|
||||
charset = utf-8
|
||||
trim_trailing_whitespace = true
|
||||
insert_final_newline = true
|
||||
|
||||
[*.yml]
|
||||
indent_style = space
|
||||
indent_size = 2
|
12
website/.eslintignore
Normal file
12
website/.eslintignore
Normal file
@ -0,0 +1,12 @@
|
||||
*.sh
|
||||
*.md
|
||||
*.woff
|
||||
*.ttf
|
||||
.vscode
|
||||
.idea
|
||||
.husky
|
||||
.local
|
||||
dist
|
||||
node_modules
|
||||
!docs/.vitepress
|
||||
docs/.vitepress/cache
|
39
website/.eslintrc.cjs
Normal file
39
website/.eslintrc.cjs
Normal file
@ -0,0 +1,39 @@
|
||||
module.exports = {
|
||||
root: true,
|
||||
extends: ["@antfu"],
|
||||
rules: {
|
||||
"comma-dangle": ["error", "only-multiline"],
|
||||
"quotes": "off",
|
||||
"no-tabs": "off",
|
||||
"arrow-parens": ["error", "always"],
|
||||
"@typescript-eslint/quotes": ["error", "double", { avoidEscape: true }],
|
||||
"indent": "off",
|
||||
"semi": ["error", "never"],
|
||||
"@typescript-eslint/indent": ["error", "tab"],
|
||||
"@typescript-eslint/brace-style": ["error", "1tbs"],
|
||||
"@typescript-eslint/semi": ["error", "never"],
|
||||
"vue/no-extra-parens": "off",
|
||||
"vue/html-indent": ["error", "tab"],
|
||||
"curly": ["error", "all"],
|
||||
"brace-style": ["error", "1tbs"],
|
||||
"no-console": "off",
|
||||
"no-debugger": "off",
|
||||
"vue/multi-word-component-names": "off",
|
||||
"vue/comment-directive": "off",
|
||||
"no-unused-vars": "off",
|
||||
"vue/no-parsing-error": [
|
||||
2,
|
||||
{
|
||||
"x-invalid-end-tag": false,
|
||||
"missing-semicolon-after-character-reference": false,
|
||||
},
|
||||
],
|
||||
|
||||
/* --ECMAScript 6 ES6-- */
|
||||
"no-useless-escape": "off",
|
||||
"no-unused-expressions": [
|
||||
"error",
|
||||
{ allowShortCircuit: true, allowTernary: true },
|
||||
],
|
||||
},
|
||||
}
|
3
website/.markdownlint.json
Normal file
3
website/.markdownlint.json
Normal file
@ -0,0 +1,3 @@
|
||||
{
|
||||
"extends": "markdownlint/style/prettier"
|
||||
}
|
2
website/.markdownlintignore
Normal file
2
website/.markdownlintignore
Normal file
@ -0,0 +1,2 @@
|
||||
node_modules
|
||||
README.md
|
3
website/.npmrc
Normal file
3
website/.npmrc
Normal file
@ -0,0 +1,3 @@
|
||||
public-hoist-pattern[]=*eslint-plugin-*
|
||||
public-hoist-pattern[]=@typescript-eslint/eslint-plugin
|
||||
public-hoist-pattern[]=@antfu/*
|
25
website/.stylelintrc.cjs
Normal file
25
website/.stylelintrc.cjs
Normal file
@ -0,0 +1,25 @@
|
||||
module.exports = {
|
||||
plugins: ["stylelint-stylus"],
|
||||
rules: {
|
||||
"stylus/pythonic": "never",
|
||||
"stylus/declaration-colon": "always",
|
||||
"stylus/semicolon": "never",
|
||||
"stylus/single-line-comment-double-slash-space-after": "always",
|
||||
"stylus/property-no-unknown": null,
|
||||
"stylus/selector-type-no-unknown": null,
|
||||
"stylus/selector-list-comma": "always",
|
||||
"stylus/indentation": [
|
||||
"tab",
|
||||
{
|
||||
indentInsideParens: "twice",
|
||||
},
|
||||
],
|
||||
"rule-empty-line-before": [
|
||||
"always",
|
||||
{
|
||||
except: ["first-nested"],
|
||||
},
|
||||
],
|
||||
},
|
||||
extends: ["stylelint-stylus/standard"],
|
||||
}
|
47
website/.stylintrc
Normal file
47
website/.stylintrc
Normal file
@ -0,0 +1,47 @@
|
||||
{
|
||||
"blocks": false,
|
||||
"brackets": "always",
|
||||
"colons": "always",
|
||||
"colors": "always",
|
||||
"commaSpace": "always",
|
||||
"commentSpace": "always",
|
||||
"cssLiteral": "never",
|
||||
"customProperties": [],
|
||||
"depthLimit": false,
|
||||
"duplicates": true,
|
||||
"efficient": "always",
|
||||
"exclude": [],
|
||||
"extendPref": "@extends",
|
||||
"globalDupe": true,
|
||||
"groupOutputByFile": true,
|
||||
"indentPref": false,
|
||||
"leadingZero": "always",
|
||||
"maxErrors": false,
|
||||
"maxWarnings": false,
|
||||
"mixed": true,
|
||||
"mixins": [],
|
||||
"namingConvention": false,
|
||||
"namingConventionStrict": false,
|
||||
"none": "always",
|
||||
"noImportant": false,
|
||||
"parenSpace": "never",
|
||||
"placeholders": "always",
|
||||
"prefixVarsWithDollar": "always",
|
||||
"quotePref": "double",
|
||||
"reporterOptions": {
|
||||
"columns": ["lineData", "severity", "description", "rule"],
|
||||
"columnSplitter": " ",
|
||||
"showHeaders": false,
|
||||
"truncate": true
|
||||
},
|
||||
"semicolons": "never",
|
||||
"sortOrder": ["grouped", "alphabetical"],
|
||||
"stackedProperties": "never",
|
||||
"trailingWhitespace": "never",
|
||||
"universal": false,
|
||||
"valid": true,
|
||||
"zeroUnits": "never",
|
||||
"zIndexNormalize": false,
|
||||
"stylusSupremacy.selectorSeparator": ",\n",
|
||||
"stylusSupremacy.insertNewLineAroundBlocks": true
|
||||
}
|
87
website/package.json
Normal file
87
website/package.json
Normal file
@ -0,0 +1,87 @@
|
||||
{
|
||||
"name": "tachiyomi-website",
|
||||
"version": "3.0.0",
|
||||
"description": "Official website for the Tachiyomi app.",
|
||||
"license": "MPL-2.0",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"engines": {
|
||||
"node": ">=18",
|
||||
"pnpm": ">=8"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/tachiyomiorg/website.git"
|
||||
},
|
||||
"bugs": {
|
||||
"url": "https://github.com/tachiyomiorg/website/issues"
|
||||
},
|
||||
"scripts": {
|
||||
"preinstall": "npx only-allow pnpm",
|
||||
"test": "pnpm lint && pnpm build && pnpm preview",
|
||||
"dev": "vitepress dev src",
|
||||
"build": "vitepress build src",
|
||||
"preview": "vitepress preview src",
|
||||
"lint": "pnpm lint:es && pnpm lint:mdl && pnpm lint:style",
|
||||
"lint:fix": "pnpm lint:es:fix && pnpm lint:style:fix",
|
||||
"lint:es": "eslint . --ext .vue,.js,.ts,.cjs,.mjs,.jsx,.tsx",
|
||||
"lint:es:fix": "eslint . --ext .vue,.js,.ts,.cjs,.mjs,.jsx,.tsx --fix",
|
||||
"lint:mdl": "markdownlint \"**/*.md\" \".github/**/*.md\" --enable sentences-per-line --disable MD025 MD033",
|
||||
"lint:style": "stylelint \"**/*.{styl,vue}\" \"src/.vitepress/**/*.{styl,vue}\"",
|
||||
"lint:style:fix": "stylelint --fix \"**/*.{styl,vue}\" \"src/.vitepress/**/*.{styl,vue}\""
|
||||
},
|
||||
"dependencies": {
|
||||
"@iconify-prerendered/vue-mdi": "0.23.1689058119",
|
||||
"@octokit/rest": "20.0.1",
|
||||
"@octokit/types": "11.1.0",
|
||||
"@tanstack/vue-query": "4.35.2",
|
||||
"@vueuse/core": "10.4.1",
|
||||
"axios": "1.5.0",
|
||||
"element-plus": "2.3.12",
|
||||
"lodash.groupby": "4.6.0",
|
||||
"markdown-it": "13.0.1",
|
||||
"markdown-it-shortcode-tag": "1.1.0",
|
||||
"moment": "2.29.4"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@antfu/eslint-config": "0.41.0",
|
||||
"@mdit/plugin-attrs": "0.4.8",
|
||||
"@mdit/plugin-figure": "0.4.8",
|
||||
"@mdit/plugin-img-lazyload": "0.4.8",
|
||||
"@mdit/plugin-img-mark": "0.4.8",
|
||||
"@mdit/plugin-img-size": "0.4.8",
|
||||
"@mdit/plugin-include": "0.4.8",
|
||||
"@resvg/resvg-js": "2.4.1",
|
||||
"@types/gtag.js": "0.0.13",
|
||||
"@types/lodash.groupby": "4.6.7",
|
||||
"@types/markdown-it": "13.0.1",
|
||||
"@types/node": "20.6.0",
|
||||
"@typescript-eslint/eslint-plugin": "6.6.0",
|
||||
"@typescript-eslint/parser": "6.6.0",
|
||||
"eslint": "8.49.0",
|
||||
"eslint-config-standard": "17.1.0",
|
||||
"eslint-plugin-vue": "9.17.0",
|
||||
"feed": "4.2.2",
|
||||
"lint-staged": "14.0.1",
|
||||
"markdownlint": "0.31.0",
|
||||
"markdownlint-cli": "0.36.0",
|
||||
"sentences-per-line": "0.2.1",
|
||||
"stylelint": "15.10.3",
|
||||
"stylelint-stylus": "0.18.0",
|
||||
"stylus": "0.60.0",
|
||||
"unplugin-element-plus": "0.8.0",
|
||||
"vite-plugin-eslint": "1.8.1",
|
||||
"vitepress": "1.0.0-rc.11",
|
||||
"vitepress-plugin-tabs": "0.3.0",
|
||||
"vue": "3.3.4",
|
||||
"vue-eslint-parser": "9.3.1",
|
||||
"x-satori": "0.1.5"
|
||||
},
|
||||
"simple-git-hooks": {
|
||||
"pre-commit": "pnpm lint-staged"
|
||||
},
|
||||
"lint-staged": {
|
||||
"*.{styl,vue}": "stylelint --fix",
|
||||
"*.{html,json}": "prettier --write"
|
||||
}
|
||||
}
|
5730
website/pnpm-lock.yaml
Normal file
5730
website/pnpm-lock.yaml
Normal file
File diff suppressed because it is too large
Load Diff
72
website/src/.vitepress/config.ts
Normal file
72
website/src/.vitepress/config.ts
Normal file
@ -0,0 +1,72 @@
|
||||
import process from "node:process"
|
||||
import { URL, fileURLToPath } from "node:url"
|
||||
import { defineConfig, loadEnv } from "vitepress"
|
||||
import ElementPlus from "unplugin-element-plus/vite"
|
||||
|
||||
import markdownConfig from "./config/markdownConfig"
|
||||
|
||||
// For use with loading Markdown plugins
|
||||
import themeConfig from "./config/themeConfig"
|
||||
|
||||
// Theme related config
|
||||
import headConfig from "./config/headConfig" // Provides how to generate Meta head tags
|
||||
|
||||
import generateMeta from "./config/hooks/generateMeta"
|
||||
|
||||
// Enhanced meta generation
|
||||
import generateFeed from "./config/hooks/generateFeed" // Allows generation of RSS feed
|
||||
import generateOgImages from "./config/hooks/generateOgImages"
|
||||
|
||||
const title = "Tachiyomi"
|
||||
const description = "Discover and read manga, webtoons, comics, and more – easier than ever on your Android device."
|
||||
|
||||
const env = loadEnv("", process.cwd())
|
||||
const hostname: string = env.VITE_HOSTNAME || "http://localhost:4173"
|
||||
|
||||
export default defineConfig({
|
||||
outDir: "../dist",
|
||||
lastUpdated: true,
|
||||
cleanUrls: true,
|
||||
title,
|
||||
description,
|
||||
sitemap: {
|
||||
hostname,
|
||||
},
|
||||
head: headConfig,
|
||||
markdown: markdownConfig,
|
||||
themeConfig,
|
||||
transformHead: async (context) => generateMeta(context, hostname),
|
||||
buildEnd: async (context) => {
|
||||
generateFeed(context, hostname)
|
||||
generateOgImages(context)
|
||||
},
|
||||
vite: {
|
||||
resolve: {
|
||||
alias: [
|
||||
{
|
||||
// Used to show the release version on navbar.
|
||||
find: /^.*\/VPNavBarMenu\.vue$/,
|
||||
replacement: fileURLToPath(
|
||||
new URL("./theme/components/CustomNavBarMenu.vue", import.meta.url),
|
||||
),
|
||||
},
|
||||
{
|
||||
find: /^.*VPNavScreenMenu\.vue$/,
|
||||
replacement: fileURLToPath(
|
||||
new URL("./theme/components/CustomNavScreenMenu.vue", import.meta.url),
|
||||
),
|
||||
},
|
||||
{
|
||||
find: /^.*VPSwitchAppearance\.vue$/,
|
||||
replacement: fileURLToPath(
|
||||
new URL("./theme/components/CustomSwitchAppearance.vue", import.meta.url),
|
||||
),
|
||||
},
|
||||
],
|
||||
},
|
||||
plugins: [ElementPlus({})],
|
||||
ssr: {
|
||||
noExternal: ["element-plus"],
|
||||
},
|
||||
},
|
||||
})
|
6
website/src/.vitepress/config/constants.ts
Normal file
6
website/src/.vitepress/config/constants.ts
Normal file
@ -0,0 +1,6 @@
|
||||
export const GITHUB_EXTENSION_JSON
|
||||
= "https://raw.githubusercontent.com/tachiyomiorg/tachiyomi-extensions/repo/index.json"
|
||||
export const GITHUB_STABLE_API = "https://api.github.com/repos/tachiyomiorg/tachiyomi/releases/latest"
|
||||
export const GITHUB_STABLE_RELEASE = "https://github.com/tachiyomiorg/tachiyomi/releases/latest"
|
||||
export const GITHUB_PREVIEW_API = "https://api.github.com/repos/tachiyomiorg/android-app-preview/releases/latest"
|
||||
export const GITHUB_PREVIEW_RELEASE = "https://github.com/tachiyomiorg/android-app-preview/releases/latest"
|
59
website/src/.vitepress/config/headConfig.ts
Normal file
59
website/src/.vitepress/config/headConfig.ts
Normal file
@ -0,0 +1,59 @@
|
||||
import type { HeadConfig } from "vitepress"
|
||||
|
||||
const headConfig: HeadConfig[] = [
|
||||
["meta", { name: "darkreader-lock" }],
|
||||
|
||||
["meta", { name: "theme-color", content: "#818CF8" }],
|
||||
["meta", { name: "msapplication-TileColor", content: "#818CF8" }],
|
||||
|
||||
["meta", { name: "viewport", content: "width=device-width, initial-scale=1.0" }],
|
||||
["meta", { name: "referrer", content: "no-referrer-when-downgrade" }],
|
||||
|
||||
["link", { rel: "icon", type: "image/x-icon", href: "/favicon.ico" }],
|
||||
[
|
||||
"link",
|
||||
{
|
||||
rel: "icon",
|
||||
type: "image/png",
|
||||
sizes: "32x32",
|
||||
href: "/favicon-32x32.png",
|
||||
},
|
||||
],
|
||||
[
|
||||
"link",
|
||||
{
|
||||
rel: "icon",
|
||||
type: "image/png",
|
||||
sizes: "16x16",
|
||||
href: "/favicon-16x16.png",
|
||||
},
|
||||
],
|
||||
["link", { rel: "manifest", href: "/site.webmanifest" }],
|
||||
["link", { rel: "mask-icon", href: "/safari-pinned-tab.svg", color: "#818CF8" }],
|
||||
[
|
||||
"link",
|
||||
{
|
||||
rel: "apple-touch-icon",
|
||||
type: "image/x-icon",
|
||||
sizes: "180x180",
|
||||
href: "/favicon.ico",
|
||||
},
|
||||
],
|
||||
|
||||
["meta", { name: "twitter:card", content: "summary" }],
|
||||
["meta", { name: "twitter:site", content: "@tachiyomiorg" }],
|
||||
["meta", { name: "twitter:creator", content: "@tachiyomiorg" }],
|
||||
|
||||
["meta", { property: "og:site_name", content: "Tachiyomi" }],
|
||||
[
|
||||
"meta",
|
||||
{
|
||||
property: "og:description",
|
||||
content: "Discover and read manga, webtoons, comics, and more – easier than ever on your Android device.",
|
||||
},
|
||||
],
|
||||
["meta", { property: "og:locale", content: "en_US" }],
|
||||
["meta", { property: "og:type", content: "website" }],
|
||||
]
|
||||
|
||||
export default headConfig
|
61
website/src/.vitepress/config/hooks/generateFeed.ts
Normal file
61
website/src/.vitepress/config/hooks/generateFeed.ts
Normal file
@ -0,0 +1,61 @@
|
||||
import path from "node:path"
|
||||
import { writeFileSync } from "node:fs"
|
||||
import { Feed, type Item } from "feed"
|
||||
import { type SiteConfig, createContentLoader } from "vitepress"
|
||||
|
||||
async function generateFeed(config: SiteConfig, hostname: string) {
|
||||
const feed = new Feed({
|
||||
title: config.site.title,
|
||||
description: config.site.description,
|
||||
id: hostname,
|
||||
link: hostname,
|
||||
language: "en",
|
||||
image: `${hostname}/img/logo.png`,
|
||||
favicon: `${hostname}/favicon.ico`,
|
||||
copyright: `Copyright © 2015 - ${new Date().getFullYear()} Javier Tomás`,
|
||||
})
|
||||
const json: Item[] = []
|
||||
|
||||
const posts = await createContentLoader("news/*.md", {
|
||||
excerpt: true,
|
||||
render: true,
|
||||
includeSrc: true,
|
||||
}).load()
|
||||
|
||||
// Filter everything that"s not of type `article` (e.g. index.md)
|
||||
const filteredPosts = posts.filter((post) => post.frontmatter.type === "article")
|
||||
|
||||
filteredPosts.sort((a, b) => +new Date(b.frontmatter.date as string) - +new Date(a.frontmatter.date as string))
|
||||
|
||||
for (const { url, frontmatter, html, src } of filteredPosts) {
|
||||
const fullUrl = `${hostname}${url}`
|
||||
|
||||
// Strip `​` from `html` string
|
||||
const content = (html ?? "")
|
||||
.replace(/​/g, "")
|
||||
.replace(/<a href="(\/.*?)">/g, `<a href="${hostname}$1">`)
|
||||
|
||||
const markdown = (src ?? "")
|
||||
.replace(/^---.*---/s, "")
|
||||
.replace(/]\((\/.*?)\)/g, `](${hostname}$1)`)
|
||||
.replace(/^# .*$/m, "")
|
||||
.trim()
|
||||
|
||||
const post = {
|
||||
title: frontmatter.title,
|
||||
id: fullUrl,
|
||||
link: fullUrl,
|
||||
description: frontmatter.description,
|
||||
content,
|
||||
date: frontmatter.date,
|
||||
} satisfies Item
|
||||
|
||||
feed.addItem(post)
|
||||
json.push({ ...post, content: markdown })
|
||||
}
|
||||
|
||||
writeFileSync(path.join(config.outDir, "feed.rss"), feed.rss2())
|
||||
writeFileSync(path.join(config.outDir, "news.json"), JSON.stringify(json))
|
||||
}
|
||||
|
||||
export default generateFeed
|
131
website/src/.vitepress/config/hooks/generateMeta.ts
Normal file
131
website/src/.vitepress/config/hooks/generateMeta.ts
Normal file
@ -0,0 +1,131 @@
|
||||
import type { HeadConfig, TransformContext } from "vitepress"
|
||||
|
||||
function generateMeta(context: TransformContext, hostname: string) {
|
||||
const head: HeadConfig[] = []
|
||||
const { pageData } = context
|
||||
|
||||
const url = `${hostname}/${pageData.relativePath.replace(/((^|\/)index)?\.md$/, "$2")}`
|
||||
|
||||
head.push(["link", { rel: "canonical", href: url }])
|
||||
head.push(["meta", { property: "og:url", content: url }])
|
||||
head.push(["meta", { name: "twitter:url", content: url }])
|
||||
head.push(["meta", { name: "twitter:card", content: "summary_large_image" }])
|
||||
|
||||
if (pageData.frontmatter.theme) {
|
||||
head.push(["meta", { name: "theme-color", content: pageData.frontmatter.theme }])
|
||||
}
|
||||
if (pageData.frontmatter.type) {
|
||||
head.push(["meta", { property: "og:type", content: pageData.frontmatter.type }])
|
||||
}
|
||||
if (pageData.frontmatter.customMetaTitle) {
|
||||
head.push([
|
||||
"meta",
|
||||
{
|
||||
property: "og:title",
|
||||
content: pageData.frontmatter.customMetaTitle,
|
||||
},
|
||||
])
|
||||
head.push([
|
||||
"meta",
|
||||
{
|
||||
name: "twitter:title",
|
||||
content: pageData.frontmatter.customMetaTitle,
|
||||
},
|
||||
])
|
||||
head.push(["meta", { property: "og:site_name", content: "" }])
|
||||
} else {
|
||||
head.push(["meta", { property: "og:title", content: pageData.frontmatter.title }])
|
||||
head.push(["meta", { name: "twitter:title", content: pageData.frontmatter.title }])
|
||||
}
|
||||
if (pageData.frontmatter.description) {
|
||||
head.push([
|
||||
"meta",
|
||||
{
|
||||
property: "og:description",
|
||||
content: pageData.frontmatter.description,
|
||||
},
|
||||
])
|
||||
head.push([
|
||||
"meta",
|
||||
{
|
||||
name: "twitter:description",
|
||||
content: pageData.frontmatter.description,
|
||||
},
|
||||
])
|
||||
}
|
||||
if (pageData.frontmatter.image) {
|
||||
head.push([
|
||||
"meta",
|
||||
{
|
||||
property: "og:image",
|
||||
content: `${hostname}/${pageData.frontmatter.image.replace(/^\//, "")}`,
|
||||
},
|
||||
])
|
||||
head.push([
|
||||
"meta",
|
||||
{
|
||||
name: "twitter:image",
|
||||
content: `${hostname}/${pageData.frontmatter.image.replace(/^\//, "")}`,
|
||||
},
|
||||
])
|
||||
} else {
|
||||
const url = pageData.filePath.replace("index.md", "").replace(".md", "")
|
||||
const imageUrl = `${url}/__og_image__/og.png`.replace(/\/\//g, "/").replace(/^\//, "")
|
||||
|
||||
head.push(["meta", { property: "og:image", content: `${hostname}/${imageUrl}` }])
|
||||
head.push(["meta", { property: "og:image:width", content: "1200" }])
|
||||
head.push(["meta", { property: "og:image:height", content: "628" }])
|
||||
head.push(["meta", { property: "og:image:type", content: "image/png" }])
|
||||
head.push(["meta", { property: "og:image:alt", content: pageData.frontmatter.title }])
|
||||
head.push(["meta", { name: "twitter:image", content: `${hostname}/${imageUrl}` }])
|
||||
head.push(["meta", { name: "twitter:image:width", content: "1200" }])
|
||||
head.push(["meta", { name: "twitter:image:height", content: "628" }])
|
||||
head.push(["meta", { name: "twitter:image:alt", content: pageData.frontmatter.title }])
|
||||
}
|
||||
if (pageData.frontmatter.tag) {
|
||||
head.push(["meta", { property: "article:tag", content: pageData.frontmatter.tag }])
|
||||
}
|
||||
if (pageData.frontmatter.date) {
|
||||
head.push([
|
||||
"meta",
|
||||
{
|
||||
property: "article:published_time",
|
||||
content: pageData.frontmatter.date,
|
||||
},
|
||||
])
|
||||
}
|
||||
if (pageData.lastUpdated && pageData.frontmatter.lastUpdated !== false) {
|
||||
head.push([
|
||||
"meta",
|
||||
{
|
||||
property: "article:modified_time",
|
||||
content: new Date(pageData.lastUpdated).toISOString(),
|
||||
},
|
||||
])
|
||||
}
|
||||
|
||||
if (pageData.filePath === "news/index.md") {
|
||||
head.push([
|
||||
"link",
|
||||
{
|
||||
rel: "alternate",
|
||||
type: "application/rss+xml",
|
||||
title: "RSS feed for the news archive",
|
||||
href: `${hostname}/feed.rss`,
|
||||
},
|
||||
])
|
||||
head.push([
|
||||
"link",
|
||||
{
|
||||
rel: "alternate",
|
||||
type: "application/json",
|
||||
title: "JSON of the news archive",
|
||||
href: `${hostname}/news.json`,
|
||||
},
|
||||
])
|
||||
}
|
||||
|
||||
return head
|
||||
}
|
||||
|
||||
export default generateMeta
|
111
website/src/.vitepress/config/hooks/generateOgImages.ts
Normal file
111
website/src/.vitepress/config/hooks/generateOgImages.ts
Normal file
@ -0,0 +1,111 @@
|
||||
import { mkdir, readFile, writeFile } from "node:fs/promises"
|
||||
import { dirname, resolve } from "node:path"
|
||||
import { fileURLToPath } from "node:url"
|
||||
import { createContentLoader } from "vitepress"
|
||||
import type { ContentData, SiteConfig } from "vitepress"
|
||||
import { type SatoriOptions, satoriVue } from "x-satori/vue"
|
||||
import { renderAsync } from "@resvg/resvg-js"
|
||||
|
||||
const __dirname = dirname(fileURLToPath(import.meta.url))
|
||||
const __fonts = resolve(__dirname, "../../fonts")
|
||||
|
||||
async function generateOgImages(config: SiteConfig) {
|
||||
const pages = await createContentLoader("**/*.md", { excerpt: true }).load()
|
||||
const template = await readFile(resolve(__dirname, "../../theme/components/OgImageTemplate.vue"), "utf-8")
|
||||
|
||||
const fonts: SatoriOptions["fonts"] = [
|
||||
{
|
||||
name: "Inter",
|
||||
data: await readFile(resolve(__fonts, "Inter-Regular.otf")),
|
||||
weight: 400,
|
||||
style: "normal",
|
||||
},
|
||||
{
|
||||
name: "Inter",
|
||||
data: await readFile(resolve(__fonts, "Inter-Medium.otf")),
|
||||
weight: 500,
|
||||
style: "normal",
|
||||
},
|
||||
{
|
||||
name: "Inter",
|
||||
data: await readFile(resolve(__fonts, "Inter-SemiBold.otf")),
|
||||
weight: 600,
|
||||
style: "normal",
|
||||
},
|
||||
{
|
||||
name: "Inter",
|
||||
data: await readFile(resolve(__fonts, "Inter-Bold.otf")),
|
||||
weight: 700,
|
||||
style: "normal",
|
||||
},
|
||||
]
|
||||
|
||||
const filteredPages = pages.filter((p) => p.frontmatter.image === undefined)
|
||||
|
||||
for (const page of filteredPages) {
|
||||
await generateImage({
|
||||
page,
|
||||
template,
|
||||
outDir: config.outDir,
|
||||
fonts,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
export default generateOgImages
|
||||
|
||||
interface GenerateImagesOptions {
|
||||
page: ContentData
|
||||
template: string
|
||||
outDir: string
|
||||
fonts: SatoriOptions["fonts"]
|
||||
}
|
||||
|
||||
function getDir(url: string) {
|
||||
if (url.startsWith("/docs/faq/")) {
|
||||
return "FAQ"
|
||||
} else if (url.startsWith("/docs/guides/")) {
|
||||
return "Guide"
|
||||
} else if (url.startsWith("/news/") && url !== "/news/") {
|
||||
return "News"
|
||||
}
|
||||
|
||||
return undefined
|
||||
}
|
||||
|
||||
async function generateImage({ page, template, outDir, fonts }: GenerateImagesOptions) {
|
||||
const { frontmatter, url } = page
|
||||
|
||||
const options: SatoriOptions = {
|
||||
width: 1200,
|
||||
height: 628,
|
||||
fonts,
|
||||
props: {
|
||||
title:
|
||||
frontmatter.layout === "home"
|
||||
? frontmatter.hero.name ?? frontmatter.title
|
||||
: frontmatter.customMetaTitle ?? frontmatter.title,
|
||||
description:
|
||||
frontmatter.layout === "home"
|
||||
? frontmatter.hero.tagline ?? frontmatter.description
|
||||
: frontmatter.description,
|
||||
dir: getDir(url),
|
||||
},
|
||||
}
|
||||
|
||||
const svg = await satoriVue(options, template)
|
||||
|
||||
const render = await renderAsync(svg, {
|
||||
fitTo: {
|
||||
mode: "width",
|
||||
value: 1200,
|
||||
},
|
||||
})
|
||||
|
||||
const outputFolder = resolve(outDir, url.substring(1), "__og_image__")
|
||||
const outputFile = resolve(outputFolder, "og.png")
|
||||
|
||||
await mkdir(outputFolder, { recursive: true })
|
||||
|
||||
return await writeFile(outputFile, render.asPng())
|
||||
}
|
29
website/src/.vitepress/config/markdownConfig.ts
Normal file
29
website/src/.vitepress/config/markdownConfig.ts
Normal file
@ -0,0 +1,29 @@
|
||||
import type { MarkdownOptions } from "vitepress"
|
||||
|
||||
import { attrs } from "@mdit/plugin-attrs"
|
||||
import { figure } from "@mdit/plugin-figure"
|
||||
import { imgLazyload } from "@mdit/plugin-img-lazyload"
|
||||
import { imgMark } from "@mdit/plugin-img-mark"
|
||||
import { imgSize } from "@mdit/plugin-img-size"
|
||||
import { include } from "@mdit/plugin-include"
|
||||
import { tabsMarkdownPlugin } from "vitepress-plugin-tabs"
|
||||
import shortcode_plugin from "markdown-it-shortcode-tag"
|
||||
import shortcodes from "./shortcodes"
|
||||
|
||||
const markdownConfig: MarkdownOptions = {
|
||||
config: (md) => {
|
||||
md
|
||||
.use(attrs)
|
||||
.use(figure)
|
||||
.use(imgLazyload)
|
||||
.use(imgMark)
|
||||
.use(imgSize)
|
||||
.use(include, {
|
||||
currentPath: (env) => env.filePath,
|
||||
})
|
||||
.use(tabsMarkdownPlugin)
|
||||
.use(shortcode_plugin, shortcodes)
|
||||
},
|
||||
}
|
||||
|
||||
export default markdownConfig
|
30
website/src/.vitepress/config/navigation/navbar.ts
Normal file
30
website/src/.vitepress/config/navigation/navbar.ts
Normal file
@ -0,0 +1,30 @@
|
||||
import type { DefaultTheme } from "vitepress"
|
||||
|
||||
const nav: DefaultTheme.NavItem[] = [
|
||||
{
|
||||
text: "Get v{app_version}",
|
||||
activeMatch: "^/*?(download|changelogs)/*?$",
|
||||
items: [
|
||||
{
|
||||
text: "Download",
|
||||
link: "/download/",
|
||||
},
|
||||
{
|
||||
text: "Changelogs",
|
||||
link: "/changelogs/",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
text: "Docs",
|
||||
link: "/docs/guides/getting-started",
|
||||
activeMatch: "/docs/",
|
||||
},
|
||||
{
|
||||
text: "News",
|
||||
link: "/news/",
|
||||
activeMatch: "/news/",
|
||||
},
|
||||
]
|
||||
|
||||
export default nav
|
130
website/src/.vitepress/config/navigation/sidebar.ts
Normal file
130
website/src/.vitepress/config/navigation/sidebar.ts
Normal file
@ -0,0 +1,130 @@
|
||||
import type { DefaultTheme } from "vitepress"
|
||||
|
||||
const sidebar: DefaultTheme.SidebarMulti = {
|
||||
"/download/": defaultSidebar(),
|
||||
"/extensions/": defaultSidebar(),
|
||||
"/docs/": defaultSidebar(),
|
||||
"/forks/": defaultSidebar(),
|
||||
"/changelogs/": defaultSidebar(),
|
||||
"/news/": defaultSidebar(),
|
||||
"/sandbox/": defaultSidebar(),
|
||||
}
|
||||
|
||||
function defaultSidebar(): DefaultTheme.SidebarItem[] {
|
||||
return [
|
||||
{
|
||||
items: [
|
||||
{
|
||||
text: "Download",
|
||||
link: "/download/",
|
||||
},
|
||||
{
|
||||
text: "Extensions",
|
||||
link: "/extensions/",
|
||||
},
|
||||
{
|
||||
text: "Changelogs",
|
||||
link: "/changelogs/",
|
||||
},
|
||||
{
|
||||
text: "Forks",
|
||||
link: "/forks/",
|
||||
},
|
||||
{
|
||||
text: "Contribute",
|
||||
link: "/docs/contribute",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
text: "Frequently Asked Questions",
|
||||
items: [
|
||||
{ text: "General", link: "/docs/faq/general" },
|
||||
{
|
||||
text: "Library",
|
||||
link: "/docs/faq/library",
|
||||
},
|
||||
{
|
||||
text: "Browse",
|
||||
link: "/docs/faq/browse/",
|
||||
collapsed: true,
|
||||
items: [
|
||||
{ text: "Extensions", link: "/docs/faq/browse/extensions" },
|
||||
{
|
||||
text: "Local source",
|
||||
link: "/docs/faq/browse/local-source",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
text: "Downloads",
|
||||
link: "/docs/faq/downloads",
|
||||
},
|
||||
{
|
||||
text: "Reader",
|
||||
link: "/docs/faq/reader",
|
||||
},
|
||||
{
|
||||
text: "Settings",
|
||||
link: "/docs/faq/settings",
|
||||
},
|
||||
{
|
||||
text: "Android 11+",
|
||||
link: "/docs/faq/android-11+",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
text: "Guides",
|
||||
items: [
|
||||
{
|
||||
text: "Getting started",
|
||||
link: "/docs/guides/getting-started",
|
||||
},
|
||||
{
|
||||
text: "Troubleshooting",
|
||||
link: "/docs/guides/troubleshooting/",
|
||||
collapsed: true,
|
||||
items: [
|
||||
{
|
||||
text: "Common issues",
|
||||
link: "/docs/guides/troubleshooting/common-issues",
|
||||
},
|
||||
{
|
||||
text: "Diagnosis",
|
||||
link: "/docs/guides/troubleshooting/diagnosis",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
text: "Source migration",
|
||||
link: "/docs/guides/source-migration",
|
||||
},
|
||||
{ text: "Backups", link: "/docs/guides/backups" },
|
||||
{ text: "Tracking", link: "/docs/guides/tracking" },
|
||||
{ text: "Categories", link: "/docs/guides/categories" },
|
||||
{
|
||||
text: "Local source",
|
||||
link: "/docs/guides/local-source/",
|
||||
collapsed: true,
|
||||
items: [
|
||||
{
|
||||
text: "Advanced editing",
|
||||
link: "/docs/guides/local-source/advanced",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
text: "Reader settings",
|
||||
link: "/docs/guides/reader-settings",
|
||||
},
|
||||
{
|
||||
text: "Shizuku",
|
||||
link: "/docs/guides/shizuku",
|
||||
},
|
||||
],
|
||||
},
|
||||
]
|
||||
}
|
||||
|
||||
export default sidebar
|
21
website/src/.vitepress/config/scripts/languages.ts
Normal file
21
website/src/.vitepress/config/scripts/languages.ts
Normal file
@ -0,0 +1,21 @@
|
||||
export function simpleLangName(code: string) {
|
||||
if (code === "all") {
|
||||
return "All"
|
||||
}
|
||||
const namesInEnglish = new Intl.DisplayNames(["en"], { type: "language" })
|
||||
return capitalize(namesInEnglish.of(code)!, "en")
|
||||
}
|
||||
|
||||
export function langName(code: string) {
|
||||
if (code === "all") {
|
||||
return "All"
|
||||
}
|
||||
|
||||
const namesInEnglish = new Intl.DisplayNames(["en"], { type: "language" })
|
||||
const namesInNative = new Intl.DisplayNames([code], { type: "language" })
|
||||
return `${capitalize(namesInEnglish.of(code)!, "en")} - ${capitalize(namesInNative.of(code)!, code)}`
|
||||
}
|
||||
|
||||
function capitalize(string: string, locale: string) {
|
||||
return string.charAt(0).toLocaleUpperCase(locale) + string.substring(1)
|
||||
}
|
91
website/src/.vitepress/config/shortcodes.ts
Normal file
91
website/src/.vitepress/config/shortcodes.ts
Normal file
@ -0,0 +1,91 @@
|
||||
const iconMappings = {
|
||||
alertDecagramOutline: '<svg originalIcon="alertDecagramOutline" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M23,12L20.56,14.78L20.9,18.46L17.29,19.28L15.4,22.46L12,21L8.6,22.47L6.71,19.29L3.1,18.47L3.44,14.78L1,12L3.44,9.21L3.1,5.53L6.71,4.72L8.6,1.54L12,3L15.4,1.54L17.29,4.72L20.9,5.54L20.56,9.22L23,12M20.33,12L18.5,9.89L18.74,7.1L16,6.5L14.58,4.07L12,5.18L9.42,4.07L8,6.5L5.26,7.09L5.5,9.88L3.67,12L5.5,14.1L5.26,16.9L8,17.5L9.42,19.93L12,18.81L14.58,19.92L16,17.5L18.74,16.89L18.5,14.1L20.33,12M11,15H13V17H11V15M11,7H13V13H11V7" /></svg>',
|
||||
backupRestore: '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M12,3A9,9 0 0,0 3,12H0L4,16L8,12H5A7,7 0 0,1 12,5A7,7 0 0,1 19,12A7,7 0 0,1 12,19C10.5,19 9.09,18.5 7.94,17.7L6.5,19.14C8.04,20.3 9.94,21 12,21A9,9 0 0,0 21,12A9,9 0 0,0 12,3M14,12A2,2 0 0,0 12,10A2,2 0 0,0 10,12A2,2 0 0,0 12,14A2,2 0 0,0 14,12Z" /></svg>',
|
||||
bookmarkBoxOutline: '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M4 20H18V22H4C2.9 22 2 21.1 2 20V6H4V20M22 4V16C22 17.1 21.1 18 20 18H8C6.9 18 6 17.1 6 16V4C6 2.9 6.9 2 8 2H20C21.1 2 22 2.9 22 4M20 4H8V16H20V4M18 6H13V13L15.5 11.5L18 13V6Z" /></svg>',
|
||||
bookOpenOutline: '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M21,4H3A2,2 0 0,0 1,6V19A2,2 0 0,0 3,21H21A2,2 0 0,0 23,19V6A2,2 0 0,0 21,4M3,19V6H11V19H3M21,19H13V6H21V19M14,9.5H20V11H14V9.5M14,12H20V13.5H14V12M14,14.5H20V16H14V14.5Z" /></svg>',
|
||||
cloudOffOutline: '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M19.8 22.6L17.15 20H6.5Q4.2 20 2.6 18.4T1 14.5Q1 12.58 2.19 11.08 3.38 9.57 5.25 9.15 5.33 8.95 5.4 8.76 5.5 8.57 5.55 8.35L1.4 4.2L2.8 2.8L21.2 21.2M6.5 18H15.15L7.1 9.95Q7.05 10.23 7.03 10.5 7 10.73 7 11H6.5Q5.05 11 4.03 12.03 3 13.05 3 14.5 3 15.95 4.03 17 5.05 18 6.5 18M11.13 14M21.6 18.75L20.15 17.35Q20.58 17 20.79 16.54 21 16.08 21 15.5 21 14.45 20.27 13.73 19.55 13 18.5 13H17V11Q17 8.93 15.54 7.46 14.08 6 12 6 11.33 6 10.7 6.16 10.07 6.33 9.5 6.68L8.05 5.23Q8.93 4.63 9.91 4.31 10.9 4 12 4 14.93 4 16.96 6.04 19 8.07 19 11 20.73 11.2 21.86 12.5 23 13.78 23 15.5 23 16.5 22.63 17.31 22.25 18.15 21.6 18.75M14.83 12.03Z" /></svg>',
|
||||
codeTags: '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M14.6,16.6L19.2,12L14.6,7.4L16,6L22,12L16,18L14.6,16.6M9.4,16.6L4.8,12L9.4,7.4L8,6L2,12L8,18L9.4,16.6Z" /></svg>',
|
||||
cog: '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M12,15.5A3.5,3.5 0 0,1 8.5,12A3.5,3.5 0 0,1 12,8.5A3.5,3.5 0 0,1 15.5,12A3.5,3.5 0 0,1 12,15.5M19.43,12.97C19.47,12.65 19.5,12.33 19.5,12C19.5,11.67 19.47,11.34 19.43,11L21.54,9.37C21.73,9.22 21.78,8.95 21.66,8.73L19.66,5.27C19.54,5.05 19.27,4.96 19.05,5.05L16.56,6.05C16.04,5.66 15.5,5.32 14.87,5.07L14.5,2.42C14.46,2.18 14.25,2 14,2H10C9.75,2 9.54,2.18 9.5,2.42L9.13,5.07C8.5,5.32 7.96,5.66 7.44,6.05L4.95,5.05C4.73,4.96 4.46,5.05 4.34,5.27L2.34,8.73C2.21,8.95 2.27,9.22 2.46,9.37L4.57,11C4.53,11.34 4.5,11.67 4.5,12C4.5,12.33 4.53,12.65 4.57,12.97L2.46,14.63C2.27,14.78 2.21,15.05 2.34,15.27L4.34,18.73C4.46,18.95 4.73,19.03 4.95,18.95L7.44,17.94C7.96,18.34 8.5,18.68 9.13,18.93L9.5,21.58C9.54,21.82 9.75,22 10,22H14C14.25,22 14.46,21.82 14.5,21.58L14.87,18.93C15.5,18.67 16.04,18.34 16.56,17.94L19.05,18.95C19.27,19.03 19.54,18.95 19.66,18.73L21.66,15.27C21.78,15.05 21.73,14.78 21.54,14.63L19.43,12.97Z" /></svg>',
|
||||
compassOutline: '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M7,17L10.2,10.2L17,7L13.8,13.8L7,17M12,11.1A0.9,0.9 0 0,0 11.1,12A0.9,0.9 0 0,0 12,12.9A0.9,0.9 0 0,0 12.9,12A0.9,0.9 0 0,0 12,11.1M12,2A10,10 0 0,1 22,12A10,10 0 0,1 12,22A10,10 0 0,1 2,12A10,10 0 0,1 12,2M12,4A8,8 0 0,0 4,12A8,8 0 0,0 12,20A8,8 0 0,0 20,12A8,8 0 0,0 12,4Z" /></svg>',
|
||||
dotsHorizontal: '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M16,12A2,2 0 0,1 18,10A2,2 0 0,1 20,12A2,2 0 0,1 18,14A2,2 0 0,1 16,12M10,12A2,2 0 0,1 12,10A2,2 0 0,1 14,12A2,2 0 0,1 12,14A2,2 0 0,1 10,12M4,12A2,2 0 0,1 6,10A2,2 0 0,1 8,12A2,2 0 0,1 6,14A2,2 0 0,1 4,12Z" /></svg>',
|
||||
downloadOutline: '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M13,5V11H14.17L12,13.17L9.83,11H11V5H13M15,3H9V9H5L12,16L19,9H15V3M19,18H5V20H19V18Z" /></svg>',
|
||||
glasses: '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M3,10C2.76,10 2.55,10.09 2.41,10.25C2.27,10.4 2.21,10.62 2.24,10.86L2.74,13.85C2.82,14.5 3.4,15 4,15H7C7.64,15 8.36,14.44 8.5,13.82L9.56,10.63C9.6,10.5 9.57,10.31 9.5,10.19C9.39,10.07 9.22,10 9,10H3M7,17H4C2.38,17 0.96,15.74 0.76,14.14L0.26,11.15C0.15,10.3 0.39,9.5 0.91,8.92C1.43,8.34 2.19,8 3,8H9C9.83,8 10.58,8.35 11.06,8.96C11.17,9.11 11.27,9.27 11.35,9.45C11.78,9.36 12.22,9.36 12.64,9.45C12.72,9.27 12.82,9.11 12.94,8.96C13.41,8.35 14.16,8 15,8H21C21.81,8 22.57,8.34 23.09,8.92C23.6,9.5 23.84,10.3 23.74,11.11L23.23,14.18C23.04,15.74 21.61,17 20,17H17C15.44,17 13.92,15.81 13.54,14.3L12.64,11.59C12.26,11.31 11.73,11.31 11.35,11.59L10.43,14.37C10.07,15.82 8.56,17 7,17M15,10C14.78,10 14.61,10.07 14.5,10.19C14.42,10.31 14.4,10.5 14.45,10.7L15.46,13.75C15.64,14.44 16.36,15 17,15H20C20.59,15 21.18,14.5 21.25,13.89L21.76,10.82C21.79,10.62 21.73,10.4 21.59,10.25C21.45,10.09 21.24,10 21,10H15Z" /></svg>',
|
||||
helpCircleOutline: '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M11,18H13V16H11V18M12,2A10,10 0 0,0 2,12A10,10 0 0,0 12,22A10,10 0 0,0 22,12A10,10 0 0,0 12,2M12,20C7.59,20 4,16.41 4,12C4,7.59 7.59,4 12,4C16.41,4 20,7.59 20,12C20,16.41 16.41,20 12,20M12,6A4,4 0 0,0 8,10H10A2,2 0 0,1 12,8A2,2 0 0,1 14,10C14,12 11,11.75 11,15H13C13,12.75 16,12.5 16,10A4,4 0 0,0 12,6Z" /></svg>',
|
||||
history: '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M13.5,8H12V13L16.28,15.54L17,14.33L13.5,12.25V8M13,3A9,9 0 0,0 4,12H1L4.96,16.03L9,12H6A7,7 0 0,1 13,5A7,7 0 0,1 20,12A7,7 0 0,1 13,19C11.07,19 9.32,18.21 8.06,16.94L6.64,18.36C8.27,20 10.5,21 13,21A9,9 0 0,0 22,12A9,9 0 0,0 13,3" /></svg>',
|
||||
informationOutline: '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M11,9H13V7H11M12,20C7.59,20 4,16.41 4,12C4,7.59 7.59,4 12,4C16.41,4 20,7.59 20,12C20,16.41 16.41,20 12,20M12,2A10,10 0 0,0 2,12A10,10 0 0,0 12,22A10,10 0 0,0 22,12A10,10 0 0,0 12,2M11,17H13V11H11V17Z" /></svg>',
|
||||
labelOutline: '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M16,17H5V7H16L19.55,12M17.63,5.84C17.27,5.33 16.67,5 16,5H5A2,2 0 0,0 3,7V17A2,2 0 0,0 5,19H16C16.67,19 17.27,18.66 17.63,18.15L22,12L17.63,5.84Z" /></svg>',
|
||||
paletteOutline: '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M12,22A10,10 0 0,1 2,12A10,10 0 0,1 12,2C17.5,2 22,6 22,11A6,6 0 0,1 16,17H14.2C13.9,17 13.7,17.2 13.7,17.5C13.7,17.6 13.8,17.7 13.8,17.8C14.2,18.3 14.4,18.9 14.4,19.5C14.5,20.9 13.4,22 12,22M12,4A8,8 0 0,0 4,12A8,8 0 0,0 12,20C12.3,20 12.5,19.8 12.5,19.5C12.5,19.3 12.4,19.2 12.4,19.1C12,18.6 11.8,18.1 11.8,17.5C11.8,16.1 12.9,15 14.3,15H16A4,4 0 0,0 20,11C20,7.1 16.4,4 12,4M6.5,10C7.3,10 8,10.7 8,11.5C8,12.3 7.3,13 6.5,13C5.7,13 5,12.3 5,11.5C5,10.7 5.7,10 6.5,10M9.5,6C10.3,6 11,6.7 11,7.5C11,8.3 10.3,9 9.5,9C8.7,9 8,8.3 8,7.5C8,6.7 8.7,6 9.5,6M14.5,6C15.3,6 16,6.7 16,7.5C16,8.3 15.3,9 14.5,9C13.7,9 13,8.3 13,7.5C13,6.7 13.7,6 14.5,6M17.5,10C18.3,10 19,10.7 19,11.5C19,12.3 18.3,13 17.5,13C16.7,13 16,12.3 16,11.5C16,10.7 16.7,10 17.5,10Z" /></svg>',
|
||||
queryStats: '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 -960 960 960"><path d="m105-233-65-47 200-320 120 140 160-260 109 163q-23 1-43.5 5.5T545-539l-22-33-152 247-121-141-145 233ZM863-40 738-165q-20 14-44.5 21t-50.5 7q-75 0-127.5-52.5T463-317q0-75 52.5-127.5T643-497q75 0 127.5 52.5T823-317q0 26-7 50.5T795-221L920-97l-57 57ZM643-217q42 0 71-29t29-71q0-42-29-71t-71-29q-42 0-71 29t-29 71q0 42 29 71t71 29Zm89-320q-19-8-39.5-13t-42.5-6l205-324 65 47-188 296Z"/></svg>',
|
||||
security: '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M12,12H19C18.47,16.11 15.72,19.78 12,20.92V12H5V6.3L12,3.19M12,1L3,5V11C3,16.55 6.84,21.73 12,23C17.16,21.73 21,16.55 21,11V5L12,1Z" /></svg>',
|
||||
sync: '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M12,18A6,6 0 0,1 6,12C6,11 6.25,10.03 6.7,9.2L5.24,7.74C4.46,8.97 4,10.43 4,12A8,8 0 0,0 12,20V23L16,19L12,15M12,4V1L8,5L12,9V6A6,6 0 0,1 18,12C18,13 17.75,13.97 17.3,14.8L18.76,16.26C19.54,15.03 20,13.57 20,12A8,8 0 0,0 12,4Z" /></svg>',
|
||||
}
|
||||
|
||||
interface Navigation {
|
||||
name: string
|
||||
icon?: string
|
||||
dependsOn?: string
|
||||
}
|
||||
|
||||
const navigationMappings: Record<string, Navigation> = {
|
||||
// Main menus
|
||||
"main_library": { name: "Library", icon: iconMappings.bookmarkBoxOutline },
|
||||
"main_updates": { name: "Updates", icon: iconMappings.alertDecagramOutline },
|
||||
"main_history": { name: "History", icon: iconMappings.history },
|
||||
"main_browse": { name: "Browse", icon: iconMappings.compassOutline },
|
||||
"main_more": { name: "More", icon: iconMappings.dotsHorizontal },
|
||||
|
||||
// Browse menu
|
||||
"sources": { name: "Sources", dependsOn: "main_browse" },
|
||||
"extensions": { name: "Extensions", dependsOn: "main_browse" },
|
||||
"migrate": { name: "Migrate", dependsOn: "main_browse" },
|
||||
|
||||
// More menu
|
||||
"downloaded-only": { name: "Downloaded only", icon: iconMappings.cloudOffOutline, dependsOn: "main_more" },
|
||||
"incognito-mode": { name: "Incognito mode", icon: iconMappings.glasses, dependsOn: "main_more" },
|
||||
"download-queue": { name: "Download queue", icon: iconMappings.downloadOutline, dependsOn: "main_more" },
|
||||
"categories": { name: "Categories", icon: iconMappings.labelOutline, dependsOn: "main_more" },
|
||||
"statistics": { name: "Statistics", icon: iconMappings.queryStats, dependsOn: "main_more" },
|
||||
"backup-and-restore": { name: "Backup and restore", icon: iconMappings.backupRestore, dependsOn: "main_more" },
|
||||
"settings": { name: "Settings", icon: iconMappings.cog, dependsOn: "main_more" },
|
||||
"about": { name: "About", icon: iconMappings.informationOutline, dependsOn: "main_more" },
|
||||
"help": { name: "Help", icon: iconMappings.helpCircleOutline, dependsOn: "main_more" },
|
||||
|
||||
// Settings submenu
|
||||
"appearance": { name: "Appearance", icon: iconMappings.paletteOutline, dependsOn: "settings" },
|
||||
"library": { name: "Library", icon: iconMappings.bookmarkBoxOutline, dependsOn: "settings" },
|
||||
"downloads": { name: "Downloads", icon: iconMappings.downloadOutline, dependsOn: "settings" },
|
||||
"tracking": { name: "Tracking", icon: iconMappings.sync, dependsOn: "settings" },
|
||||
"browse": { name: "Browse", icon: iconMappings.compassOutline, dependsOn: "settings" },
|
||||
"security-and-privacy": { name: "Security and privacy", icon: iconMappings.security, dependsOn: "settings" },
|
||||
"reader": { name: "Reader", icon: iconMappings.bookOpenOutline, dependsOn: "settings" },
|
||||
"advanced": { name: "Advanced", icon: iconMappings.codeTags, dependsOn: "settings" },
|
||||
}
|
||||
|
||||
function generateNavigationHtml(navKey: string) {
|
||||
const navData = navigationMappings[navKey]
|
||||
|
||||
if (!navData) {
|
||||
return "<strong style='color:var(--vp-c-danger-1)'>Unsupported Navigation!</strong>"
|
||||
}
|
||||
|
||||
const { name, icon, dependsOn } = navData
|
||||
|
||||
const iconHtml = icon ?? ""
|
||||
let html = `<span class='shortcode navigation ${navKey}'>${iconHtml}<span class="name">${name}</span></span>`
|
||||
|
||||
if (dependsOn) {
|
||||
html = `${generateNavigationHtml(dependsOn)} -> ${html}`
|
||||
}
|
||||
|
||||
return html
|
||||
}
|
||||
|
||||
const shortcodes = {
|
||||
nav: {
|
||||
render({ to }) {
|
||||
return generateNavigationHtml(to)
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
export default shortcodes
|
72
website/src/.vitepress/config/themeConfig.ts
Normal file
72
website/src/.vitepress/config/themeConfig.ts
Normal file
@ -0,0 +1,72 @@
|
||||
import type { DefaultTheme } from "vitepress"
|
||||
|
||||
import nav from "./navigation/navbar"
|
||||
import sidebar from "./navigation/sidebar"
|
||||
|
||||
const themeConfig: DefaultTheme.Config = {
|
||||
logo: {
|
||||
src: "/img/logo-128px.png",
|
||||
width: 24,
|
||||
height: 24,
|
||||
},
|
||||
|
||||
nav,
|
||||
sidebar,
|
||||
|
||||
outline: [2, 3],
|
||||
|
||||
socialLinks: [
|
||||
{
|
||||
icon: "github",
|
||||
link: "https://github.com/tachiyomiorg/tachiyomi",
|
||||
ariaLabel: "Project GitHub",
|
||||
},
|
||||
{
|
||||
icon: "discord",
|
||||
link: "https://discord.gg/tachiyomi",
|
||||
ariaLabel: "Discord Server",
|
||||
},
|
||||
{
|
||||
icon: "twitter",
|
||||
link: "https://twitter.com/tachiyomiorg",
|
||||
ariaLabel: "Twitter Page",
|
||||
},
|
||||
{
|
||||
icon: "facebook",
|
||||
link: "https://facebook.com/tachiyomiorg",
|
||||
ariaLabel: "Facebook Page",
|
||||
},
|
||||
{
|
||||
icon: {
|
||||
svg: '<svg viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path d="M12 0A12 12 0 0 0 0 12a12 12 0 0 0 12 12 12 12 0 0 0 12-12A12 12 0 0 0 12 0zm5.01 4.744c.688 0 1.25.561 1.25 1.249a1.25 1.25 0 0 1-2.498.056l-2.597-.547-.8 3.747c1.824.07 3.48.632 4.674 1.488.308-.309.73-.491 1.207-.491.968 0 1.754.786 1.754 1.754 0 .716-.435 1.333-1.01 1.614a3.111 3.111 0 0 1 .042.52c0 2.694-3.13 4.87-7.004 4.87-3.874 0-7.004-2.176-7.004-4.87 0-.183.015-.366.043-.534A1.748 1.748 0 0 1 4.028 12c0-.968.786-1.754 1.754-1.754.463 0 .898.196 1.207.49 1.207-.883 2.878-1.43 4.744-1.487l.885-4.182a.342.342 0 0 1 .14-.197.35.35 0 0 1 .238-.042l2.906.617a1.214 1.214 0 0 1 1.108-.701zM9.25 12C8.561 12 8 12.562 8 13.25c0 .687.561 1.248 1.25 1.248.687 0 1.248-.561 1.248-1.249 0-.688-.561-1.249-1.249-1.249zm5.5 0c-.687 0-1.248.561-1.248 1.25 0 .687.561 1.248 1.249 1.248.688 0 1.249-.561 1.249-1.249 0-.687-.562-1.249-1.25-1.249zm-5.466 3.99a.327.327 0 0 0-.231.094.33.33 0 0 0 0 .463c.842.842 2.484.913 2.961.913.477 0 2.105-.056 2.961-.913a.361.361 0 0 0 .029-.463.33.33 0 0 0-.464 0c-.547.533-1.684.73-2.512.73-.828 0-1.979-.196-2.512-.73a.326.326 0 0 0-.232-.095z"/></svg>',
|
||||
},
|
||||
link: "https://reddit.com/r/Tachiyomi",
|
||||
ariaLabel: "Support subreddit",
|
||||
},
|
||||
// { icon: "instagram", link: "https://instagram.com/tachiyomiorg", ariaLabel: "Instagram Page" },
|
||||
],
|
||||
|
||||
footer: {
|
||||
message: "<a href=\"https://www.apache.org/licenses/LICENSE-2.0\" target=\"_blank\">Open-source Apache Licensed</a> <span class=\"divider\">|</span> <a href=\"/privacy/\">Privacy policy</a> <span class=\"divider\">|</span> Powered by <a target=\"_blank\" href=\"https://www.netlify.com/\">Netlify <img src=\"/img/logo-netlify.svg\" alt=\"Netlify Logo\" height=\"12px\" width=\"12px\" style=\"display: inline-block\"></a>",
|
||||
copyright: `Copyright © 2015 - ${new Date().getFullYear()} Javier Tomás`,
|
||||
},
|
||||
|
||||
editLink: {
|
||||
pattern: "https://github.com/xhenos/kodo/edit/v3-vitepress/website/src/:path",
|
||||
text: "Help us improve this page",
|
||||
},
|
||||
|
||||
lastUpdated: {
|
||||
text: "Last updated",
|
||||
formatOptions: {
|
||||
dateStyle: "short",
|
||||
timeStyle: "short",
|
||||
},
|
||||
},
|
||||
|
||||
search: {
|
||||
provider: "local",
|
||||
},
|
||||
}
|
||||
|
||||
export default themeConfig
|
BIN
website/src/.vitepress/fonts/Inter-Bold.otf
Normal file
BIN
website/src/.vitepress/fonts/Inter-Bold.otf
Normal file
Binary file not shown.
BIN
website/src/.vitepress/fonts/Inter-Medium.otf
Normal file
BIN
website/src/.vitepress/fonts/Inter-Medium.otf
Normal file
Binary file not shown.
BIN
website/src/.vitepress/fonts/Inter-Regular.otf
Normal file
BIN
website/src/.vitepress/fonts/Inter-Regular.otf
Normal file
Binary file not shown.
BIN
website/src/.vitepress/fonts/Inter-SemiBold.otf
Normal file
BIN
website/src/.vitepress/fonts/Inter-SemiBold.otf
Normal file
Binary file not shown.
64
website/src/.vitepress/theme/Layout.vue
Normal file
64
website/src/.vitepress/theme/Layout.vue
Normal file
@ -0,0 +1,64 @@
|
||||
<script setup lang="ts">
|
||||
import { useData } from "vitepress"
|
||||
import DefaultTheme from "vitepress/theme"
|
||||
import { nextTick, provide } from "vue"
|
||||
|
||||
const { isDark } = useData()
|
||||
|
||||
function shouldEnableTransitions() {
|
||||
return "startViewTransition" in document
|
||||
&& window.matchMedia("(prefers-reduced-motion: no-preference)").matches
|
||||
}
|
||||
|
||||
provide("toggle-appearance", async ({ clientX: x, clientY: y }: MouseEvent) => {
|
||||
if (!shouldEnableTransitions()) {
|
||||
isDark.value = !isDark.value
|
||||
return
|
||||
}
|
||||
|
||||
const clipPath = [
|
||||
`circle(0px at ${x}px ${y}px)`,
|
||||
`circle(${Math.hypot(
|
||||
Math.max(x, innerWidth - x),
|
||||
Math.max(y, innerHeight - y),
|
||||
)}px at ${x}px ${y}px)`,
|
||||
]
|
||||
|
||||
// @ts-expect-error Missing types as its experimental
|
||||
await document.startViewTransition(async () => {
|
||||
isDark.value = !isDark.value
|
||||
await nextTick()
|
||||
}).ready
|
||||
|
||||
document.documentElement.animate(
|
||||
{ clipPath: isDark.value ? clipPath.reverse() : clipPath },
|
||||
{
|
||||
duration: 300,
|
||||
easing: "ease-in",
|
||||
pseudoElement: `::view-transition-${isDark.value ? "old" : "new"}(root)`,
|
||||
},
|
||||
)
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<DefaultTheme.Layout />
|
||||
</template>
|
||||
|
||||
<style lang="stylus">
|
||||
::view-transition-old(root),
|
||||
::view-transition-new(root) {
|
||||
animation: none
|
||||
mix-blend-mode: normal
|
||||
}
|
||||
|
||||
::view-transition-old(root),
|
||||
.dark::view-transition-new(root) {
|
||||
z-index: 1
|
||||
}
|
||||
|
||||
::view-transition-new(root),
|
||||
.dark::view-transition-old(root) {
|
||||
z-index: 9999
|
||||
}
|
||||
</style>
|
116
website/src/.vitepress/theme/components/Changelog.vue
Normal file
116
website/src/.vitepress/theme/components/Changelog.vue
Normal file
@ -0,0 +1,116 @@
|
||||
<script setup lang="ts">
|
||||
import { computed, toRefs } from "vue"
|
||||
import MarkdownIt from "markdown-it"
|
||||
import { type AppRelease, data as release } from "../data/release.data"
|
||||
import Contributors from "./Contributors.vue"
|
||||
|
||||
const props = defineProps<{ type: keyof AppRelease }>()
|
||||
const { type } = toRefs(props)
|
||||
|
||||
const md = new MarkdownIt()
|
||||
|
||||
const changelog = computed(() => {
|
||||
const flavoredString = (release[type.value].body ?? "")
|
||||
.replace(/(?<=\(|(, ))@(.*?)(?=\)|(, ))/g, "[@$2](https://github.com/$2)")
|
||||
.replace("https://github.com/tachiyomiorg/tachiyomi/releases", "/changelogs/")
|
||||
|
||||
return md.render(flavoredString)
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="changelog">
|
||||
<header>
|
||||
<IconNewspaperVariant />
|
||||
<h2>Changelog</h2>
|
||||
</header>
|
||||
<div v-html="changelog" />
|
||||
<Contributors
|
||||
:body="release[type].body!"
|
||||
:author="release[type].author.login"
|
||||
:tag="release[type].tag_name"
|
||||
/>
|
||||
</div>
|
||||
<div class="fullChangelog">
|
||||
<p>
|
||||
View the full release
|
||||
<a href="https://github.com/tachiyomiorg/tachiyomi/releases/latest" target="_blank" rel="noopener">
|
||||
here
|
||||
</a>
|
||||
</p>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="stylus">
|
||||
.changelog {
|
||||
display: block
|
||||
border: 1px solid var(--vp-c-bg-soft)
|
||||
border-radius: 12px
|
||||
background-color: var(--vp-c-bg-soft)
|
||||
transition: border-color 0.25s, background-color 0.25s
|
||||
padding: 24px
|
||||
height: 100%
|
||||
margin: 1.5em auto 0.5em
|
||||
|
||||
header {
|
||||
display: flex
|
||||
justify-content: center
|
||||
align-items: baseline
|
||||
margin: 0 0 1rem
|
||||
}
|
||||
|
||||
svg {
|
||||
font-size: 1.2em
|
||||
margin-right: 0.5rem
|
||||
vertical-align: middle
|
||||
}
|
||||
|
||||
h2 {
|
||||
font-size: 1.5rem
|
||||
margin: 0
|
||||
padding: 0
|
||||
color: var(--vp-c-text-1)
|
||||
border: none
|
||||
}
|
||||
|
||||
div > p {
|
||||
margin: 0 0 1rem
|
||||
color: var(--vp-c-text-2)
|
||||
font-size: 0.9rem
|
||||
}
|
||||
|
||||
table {
|
||||
border-radius: 8px
|
||||
border-collapse: collapse
|
||||
border: 1px solid var(--vp-c-divider)
|
||||
|
||||
tr,
|
||||
th,
|
||||
td {
|
||||
border: none
|
||||
width: 100%
|
||||
}
|
||||
|
||||
tbody tr {
|
||||
border-top: 1px solid var(--vp-c-divider)
|
||||
}
|
||||
|
||||
tr > td {
|
||||
&:first-child {
|
||||
color: var(--vp-c-text-2)
|
||||
}
|
||||
|
||||
&:last-child {
|
||||
font-family: var(--vp-font-family-mono)
|
||||
font-size: var(--vp-code-font-size)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.fullChangelog {
|
||||
margin: 0 0 1rem
|
||||
color: var(--vp-c-text-2)
|
||||
font-size: 0.9rem
|
||||
}
|
||||
</style>
|
69
website/src/.vitepress/theme/components/ChangelogsList.vue
Normal file
69
website/src/.vitepress/theme/components/ChangelogsList.vue
Normal file
@ -0,0 +1,69 @@
|
||||
<script setup lang="ts">
|
||||
import MarkdownIt from "markdown-it"
|
||||
import { data as changelogs } from "../data/changelogs.data"
|
||||
import Contributors from "./Contributors.vue"
|
||||
|
||||
const md = new MarkdownIt()
|
||||
|
||||
function renderMarkdown(string: string | null | undefined) {
|
||||
const body = string ?? "No changelog provided."
|
||||
const flavoredString = body
|
||||
.split(/---\r\n\r\n### Checksums|---\r\n\r\nMD5/)[0]
|
||||
.replace(/(?<=\(|(, ))@(.*?)(?=\)|(, ))/g, "[@$2](https://github.com/$2)")
|
||||
.replace(/#(\d+)/g, "[#$1](https://github.com/tachiyomiorg/tachiyomi/issues/$1)")
|
||||
.replace(/^Check out the .*past release notes.* if you're.*$/m, "")
|
||||
.replace(/https\:\/\/github.com\/tachiyomiorg\/tachiyomi\/releases\/tag\/(.*?)/g, "#$1")
|
||||
.trim()
|
||||
|
||||
return md.render(flavoredString)
|
||||
}
|
||||
|
||||
const dateFormatter = new Intl.DateTimeFormat("en", {
|
||||
dateStyle: "medium",
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div
|
||||
v-for="(release, index) of changelogs"
|
||||
:key="release.tag_name"
|
||||
>
|
||||
<h2 :id="index === 0 ? 'latest' : release.tag_name">
|
||||
<a
|
||||
:href="release.html_url"
|
||||
target="_blank"
|
||||
>
|
||||
{{ release.tag_name.substring(1) }}
|
||||
</a>
|
||||
<Badge v-if="index === 0" type="tip" text="Latest" />
|
||||
<a
|
||||
class="header-anchor"
|
||||
:href="index === 0 ? '#latest' : `#${release.tag_name}`"
|
||||
:aria-label="`Permalink to "${release.tag_name}"`"
|
||||
/>
|
||||
</h2>
|
||||
<time :datetime="release.published_at!">
|
||||
{{ dateFormatter.format(new Date(release.published_at!)) }}
|
||||
</time>
|
||||
<div v-html="renderMarkdown(release.body)" />
|
||||
<Contributors
|
||||
:body="release.body!"
|
||||
:author="release.author.login"
|
||||
:tag="release.tag_name"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="stylus" scoped>
|
||||
h2 {
|
||||
margin-bottom: 0
|
||||
display: flex
|
||||
align-items: center
|
||||
gap: 0.5rem
|
||||
}
|
||||
|
||||
time {
|
||||
font-size: 0.875rem
|
||||
color: var(--vp-c-text-2)
|
||||
}
|
||||
</style>
|
108
website/src/.vitepress/theme/components/Contributors.vue
Normal file
108
website/src/.vitepress/theme/components/Contributors.vue
Normal file
@ -0,0 +1,108 @@
|
||||
<script setup lang="ts">
|
||||
import { computed, ref, toRefs } from "vue"
|
||||
|
||||
const props = defineProps<{ body: string; author: string; tag: string }>()
|
||||
const { body, author, tag } = toRefs(props)
|
||||
|
||||
function isHigherThan(tagName: string, reference: string) {
|
||||
return reference.localeCompare(tagName, undefined, { numeric: true, sensitivity: "base" }) >= 0
|
||||
}
|
||||
|
||||
const notMentioned = computed(() => {
|
||||
return isHigherThan("v0.8.5", tag.value) ? ["arkon"] : []
|
||||
})
|
||||
|
||||
const nonExistent = ref<string[]>([])
|
||||
|
||||
const contributors = computed(() => {
|
||||
const list = [...body.value.matchAll(/(?<=\(|(, ))@(.*?)(?=\)|(, ))/g)]
|
||||
.map((match) => match[2])
|
||||
const uncredited = author.value.includes("[bot]")
|
||||
? notMentioned.value
|
||||
: [author.value, ...notMentioned.value]
|
||||
|
||||
return [...new Set([...uncredited, ...list])].filter((user) => !nonExistent.value.includes(user))
|
||||
})
|
||||
|
||||
const listFormatter = new Intl.ListFormat("en", {
|
||||
style: "long",
|
||||
type: "conjunction",
|
||||
})
|
||||
|
||||
const contributorsText = computed(() => {
|
||||
if (contributors.value.length <= 3) {
|
||||
return listFormatter.format(contributors.value)
|
||||
}
|
||||
|
||||
return listFormatter.format([
|
||||
...contributors.value.slice(0, 2),
|
||||
`${contributors.value.length - 2} other contributors`,
|
||||
])
|
||||
})
|
||||
|
||||
function addToNonExistent(user: string) {
|
||||
if (!nonExistent.value.includes(user)) {
|
||||
nonExistent.value.push(user)
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div v-if="contributors.length > 0" class="contributors">
|
||||
<h3>Contributors</h3>
|
||||
<ul>
|
||||
<li
|
||||
v-for="contributor of contributors"
|
||||
:key="contributor"
|
||||
>
|
||||
<a
|
||||
:href="`https://github.com/${contributor}`"
|
||||
target="_blank"
|
||||
:title="`${contributor} profile on GitHub`"
|
||||
:aria-label="`${contributor} profile on GitHub`"
|
||||
>
|
||||
<img
|
||||
:src="`https://github.com/${contributor}.png?size=32`"
|
||||
:alt="`@${contributor} profile picture`"
|
||||
loading="lazy"
|
||||
class="avatar"
|
||||
@error="addToNonExistent(contributor)"
|
||||
>
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
<div class="names">
|
||||
{{ contributorsText }}
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="stylus" scoped>
|
||||
.contributors {
|
||||
ul {
|
||||
display: flex
|
||||
align-items: center
|
||||
flex-wrap: wrap
|
||||
gap: 0.5rem
|
||||
list-style-type: none
|
||||
padding-left: 0
|
||||
|
||||
li + li {
|
||||
margin-top: 0
|
||||
}
|
||||
}
|
||||
|
||||
.avatar {
|
||||
width: 32px
|
||||
height: 32px
|
||||
border-radius: 50%
|
||||
box-shadow: var(--vp-shadow-1)
|
||||
border: 1px solid var(--vp-c-divider)
|
||||
}
|
||||
|
||||
.names {
|
||||
font-size: 0.875rem
|
||||
color: var(--vp-c-text-2)
|
||||
}
|
||||
}
|
||||
</style>
|
64
website/src/.vitepress/theme/components/CustomNavBarMenu.vue
Normal file
64
website/src/.vitepress/theme/components/CustomNavBarMenu.vue
Normal file
@ -0,0 +1,64 @@
|
||||
<script setup lang="ts">
|
||||
import { computed, onMounted, ref } from "vue"
|
||||
import { useData } from "vitepress"
|
||||
import type { DefaultTheme } from "vitepress/theme"
|
||||
|
||||
import VPNavBarMenuLink from "vitepress/dist/client/theme-default/components/VPNavBarMenuLink.vue"
|
||||
import VPNavBarMenuGroup from "vitepress/dist/client/theme-default/components/VPNavBarMenuGroup.vue"
|
||||
|
||||
import { data as release } from "../data/release.data"
|
||||
|
||||
const { theme } = useData<DefaultTheme.Config>()
|
||||
|
||||
// Used to avoid hydration issues.
|
||||
const replace = ref(false)
|
||||
|
||||
onMounted(() => {
|
||||
replace.value = true
|
||||
})
|
||||
|
||||
/**
|
||||
* Workaround to use the release data directly while the sidebar
|
||||
* and navbar doesn't support using the VitePress data loading.
|
||||
*/
|
||||
const nav = computed(() => {
|
||||
if (!replace.value) {
|
||||
return theme.value.nav
|
||||
}
|
||||
|
||||
return theme.value.nav?.map((item) => {
|
||||
if (!item.text.includes("{app_version}")) {
|
||||
return item
|
||||
}
|
||||
|
||||
const appVersion = release.stable.tag_name.substring(1)
|
||||
|
||||
return {
|
||||
...item,
|
||||
text: item.text.replace("{app_version}", appVersion),
|
||||
} satisfies DefaultTheme.NavItem
|
||||
})
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<nav v-if="nav" aria-labelledby="main-nav-aria-label" class="VPNavBarMenu">
|
||||
<span id="main-nav-aria-label" class="visually-hidden">Main navigation</span>
|
||||
<template v-for="item in nav" :key="item.text">
|
||||
<VPNavBarMenuLink v-if="'link' in item" :item="item" />
|
||||
<VPNavBarMenuGroup v-else :item="item" />
|
||||
</template>
|
||||
</nav>
|
||||
</template>
|
||||
|
||||
<style lang="stylus" scoped>
|
||||
.VPNavBarMenu {
|
||||
display: none
|
||||
}
|
||||
|
||||
@media (min-width 768px) {
|
||||
.VPNavBarMenu {
|
||||
display: flex
|
||||
}
|
||||
}
|
||||
</style>
|
@ -0,0 +1,57 @@
|
||||
<script setup lang="ts">
|
||||
import { computed, onMounted, ref } from "vue"
|
||||
import { type DefaultTheme, useData } from "vitepress"
|
||||
|
||||
import VPNavScreenMenuLink from "vitepress/dist/client/theme-default/components/VPNavScreenMenuLink.vue"
|
||||
import VPNavScreenMenuGroup from "vitepress/dist/client/theme-default/components/VPNavScreenMenuGroup.vue"
|
||||
|
||||
import { data as release } from "../data/release.data"
|
||||
|
||||
const { theme } = useData<DefaultTheme.Config>()
|
||||
|
||||
// Used to avoid hydration issues.
|
||||
const replace = ref(false)
|
||||
|
||||
onMounted(() => {
|
||||
replace.value = true
|
||||
})
|
||||
|
||||
/**
|
||||
* Workaround to use the release data directly while the sidebar
|
||||
* and navbar doesn't support using the VitePress data loading.
|
||||
*/
|
||||
const nav = computed(() => {
|
||||
if (!replace.value) {
|
||||
return theme.value.nav
|
||||
}
|
||||
|
||||
return theme.value.nav?.map((item) => {
|
||||
if (!item.text.includes("{app_version}")) {
|
||||
return item
|
||||
}
|
||||
|
||||
const appVersion = release.stable.tag_name.substring(1)
|
||||
|
||||
return {
|
||||
...item,
|
||||
text: item.text.replace("{app_version}", appVersion),
|
||||
} satisfies DefaultTheme.NavItem
|
||||
})
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<nav v-if="nav" class="VPNavScreenMenu">
|
||||
<template v-for="item in nav" :key="item.text">
|
||||
<VPNavScreenMenuLink
|
||||
v-if="'link' in item"
|
||||
:item="item"
|
||||
/>
|
||||
<VPNavScreenMenuGroup
|
||||
v-else
|
||||
:text="item.text || ''"
|
||||
:items="item.items"
|
||||
/>
|
||||
</template>
|
||||
</nav>
|
||||
</template>
|
@ -0,0 +1,74 @@
|
||||
<script setup lang="ts">
|
||||
import { inject, onMounted, ref } from "vue"
|
||||
import { useData } from "vitepress"
|
||||
|
||||
import VPIconMoon from "vitepress/dist/client/theme-default/components/icons/VPIconMoon.vue"
|
||||
import VPIconSun from "vitepress/dist/client/theme-default/components/icons/VPIconSun.vue"
|
||||
|
||||
const { isDark } = useData()
|
||||
|
||||
const toggleAppearance = inject("toggle-appearance", () => {
|
||||
isDark.value = !isDark.value
|
||||
})
|
||||
|
||||
const supportsViewTransition = ref(false)
|
||||
|
||||
onMounted(() => {
|
||||
supportsViewTransition.value = "startViewTransition" in document
|
||||
&& window.matchMedia("(prefers-reduced-motion: no-preference)").matches
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<button
|
||||
type="button"
|
||||
role="switch"
|
||||
title="Toggle dark mode"
|
||||
class="CustomSwitchAppearance"
|
||||
:aria-checked="isDark"
|
||||
:data-view-transition="supportsViewTransition"
|
||||
@click="toggleAppearance"
|
||||
>
|
||||
<ClientOnly>
|
||||
<Transition name="fade" mode="out-in">
|
||||
<VPIconSun v-if="!isDark" class="sun" />
|
||||
<VPIconMoon v-else class="moon" />
|
||||
</Transition>
|
||||
</ClientOnly>
|
||||
</button>
|
||||
</template>
|
||||
|
||||
<style lang="stylus" scoped>
|
||||
.CustomSwitchAppearance {
|
||||
display: flex
|
||||
justify-content: center
|
||||
align-items: center
|
||||
width: 36px
|
||||
height: 36px
|
||||
color: var(--vp-c-text-2)
|
||||
transition: color 0.5s
|
||||
|
||||
&:hover {
|
||||
color: var(--vp-c-text-1)
|
||||
transition: color 0.25s
|
||||
}
|
||||
|
||||
& > :deep(svg) {
|
||||
width: 20px
|
||||
height: 20px
|
||||
fill: currentColor
|
||||
}
|
||||
|
||||
&[data-view-transition="false"] {
|
||||
.fade-enter-active,
|
||||
.fade-leave-active {
|
||||
transition: opacity 0.1s ease
|
||||
}
|
||||
|
||||
.fade-enter-from,
|
||||
.fade-leave-to {
|
||||
opacity: 0
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
174
website/src/.vitepress/theme/components/DownloadButtons.vue
Normal file
174
website/src/.vitepress/theme/components/DownloadButtons.vue
Normal file
@ -0,0 +1,174 @@
|
||||
<script setup lang="ts">
|
||||
/// <reference types="@types/gtag.js" />
|
||||
|
||||
import { computed, onMounted, ref } from "vue"
|
||||
import { data as release } from "../data/release.data"
|
||||
|
||||
const downloadInformation = computed(() => ({
|
||||
preview: {
|
||||
tagName: release.preview.tag_name ?? "r0000",
|
||||
asset: (release.preview.assets ?? [])
|
||||
.find((a) => /^tachiyomi-r\d{4,}.apk/.test(a.name)),
|
||||
},
|
||||
stable: {
|
||||
tagName: release.stable.tag_name ?? "v0.00.0",
|
||||
asset: (release.stable.assets ?? [])
|
||||
.find((a) => /^tachiyomi-v\d+\.\d+\.\d+.apk/.test(a.name)),
|
||||
},
|
||||
}))
|
||||
|
||||
const isAndroid = ref(true)
|
||||
|
||||
onMounted(() => {
|
||||
isAndroid.value = !!navigator.userAgent.match(/android/i)
|
||||
})
|
||||
|
||||
function handleAnalytics(type: "preview" | "stable") {
|
||||
window.gtag?.("event", "Download", {
|
||||
event_category: "App",
|
||||
event_label: type === "stable" ? "Stable" : "Preview",
|
||||
version: type === "stable"
|
||||
? release.stable.tag_name
|
||||
: release.preview.tag_name,
|
||||
})
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<div v-if="!isAndroid" class="custom-block danger">
|
||||
<p class="custom-block-title">
|
||||
Unsupported operating system
|
||||
</p>
|
||||
<p>
|
||||
<strong>Tachiyomi</strong> is an <strong>Android app</strong> only.
|
||||
Use an <strong>Android device</strong> to download and install the app.
|
||||
</p>
|
||||
</div>
|
||||
<div v-if="!isAndroid" class="custom-block warning">
|
||||
<p class="custom-block-title">
|
||||
Caution
|
||||
</p>
|
||||
<p>
|
||||
Any app for any operating systems other than Android called
|
||||
<strong>Tachiyomi</strong> is not affiliated with this project.
|
||||
</p>
|
||||
<blockquote>
|
||||
For more information, read the
|
||||
<a href="/docs/faq/general">General FAQ</a>.
|
||||
</blockquote>
|
||||
</div>
|
||||
<div class="download-buttons">
|
||||
<a
|
||||
class="download-button primary"
|
||||
:download="downloadInformation.stable.asset?.name"
|
||||
:href="downloadInformation.stable.asset?.browser_download_url"
|
||||
@click="handleAnalytics('stable')"
|
||||
>
|
||||
<IconDownload />
|
||||
<span class="text">Stable</span>
|
||||
<span class="version">{{ downloadInformation.stable.tagName }}</span>
|
||||
</a>
|
||||
<a
|
||||
class="download-button secondary"
|
||||
:download="downloadInformation.preview.asset?.name"
|
||||
:href="downloadInformation.preview.asset?.browser_download_url"
|
||||
@click="handleAnalytics('preview')"
|
||||
>
|
||||
<IconBugReport />
|
||||
<span class="text">Preview</span>
|
||||
<span class="version">{{ downloadInformation.preview.tagName }}</span>
|
||||
</a>
|
||||
</div>
|
||||
<span class="version-disclaimer">
|
||||
Requires <strong>Android 6.0</strong> or higher.
|
||||
</span>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="stylus">
|
||||
.download-buttons {
|
||||
display: flex
|
||||
gap: 0.75em
|
||||
justify-content: center
|
||||
align-items: center
|
||||
margin: 0.75em auto
|
||||
}
|
||||
|
||||
.download-button {
|
||||
display: inline-block
|
||||
border: 1px solid transparent
|
||||
text-align: center
|
||||
font-weight: 600
|
||||
white-space: nowrap
|
||||
transition: color 0.25s, border-color 0.25s, background-color 0.25s
|
||||
cursor: pointer
|
||||
transition: all 0.3s ease
|
||||
border-radius: 20px
|
||||
padding: 0 20px
|
||||
line-height: 38px
|
||||
font-size: 14px
|
||||
|
||||
&:hover {
|
||||
text-decoration: none !important
|
||||
}
|
||||
|
||||
&.primary {
|
||||
border-color: var(--vp-button-brand-border)
|
||||
color: var(--vp-button-brand-text)
|
||||
background-color: var(--vp-button-brand-bg)
|
||||
|
||||
&:hover {
|
||||
border-color: var(--vp-button-brand-hover-border)
|
||||
color: var(--vp-button-brand-hover-text)
|
||||
background-color: var(--vp-button-brand-hover-bg)
|
||||
}
|
||||
|
||||
&:active {
|
||||
border-color: var(--vp-button-brand-active-border)
|
||||
color: var(--vp-button-brand-active-text)
|
||||
background-color: var(--vp-button-brand-active-bg)
|
||||
}
|
||||
}
|
||||
|
||||
&.secondary {
|
||||
border-color: var(--vp-button-alt-border)
|
||||
color: var(--vp-button-alt-text)
|
||||
background-color: var(--vp-button-alt-bg)
|
||||
|
||||
&:hover {
|
||||
border-color: var(--vp-button-alt-hover-border)
|
||||
color: var(--vp-button-alt-hover-text)
|
||||
background-color: var(--vp-button-alt-hover-bg)
|
||||
}
|
||||
|
||||
&:active {
|
||||
border-color: var(--vp-button-alt-active-border)
|
||||
color: var(--vp-button-alt-active-text)
|
||||
background-color: var(--vp-button-alt-active-bg)
|
||||
}
|
||||
}
|
||||
|
||||
svg {
|
||||
display: inline-block
|
||||
vertical-align: middle
|
||||
margin-right: 0.5em
|
||||
font-size: 1.25em
|
||||
}
|
||||
|
||||
.text {
|
||||
margin-right: 10px
|
||||
}
|
||||
|
||||
.version {
|
||||
font-size: 0.8em
|
||||
}
|
||||
}
|
||||
|
||||
.version-disclaimer {
|
||||
display: block
|
||||
text-align: center
|
||||
margin: 0.75em auto
|
||||
font-size: 0.75rem
|
||||
}
|
||||
</style>
|
@ -0,0 +1,104 @@
|
||||
<script setup lang="ts">
|
||||
import { computed, toRefs } from "vue"
|
||||
import { useMediaQuery } from "@vueuse/core"
|
||||
|
||||
import {
|
||||
ElForm,
|
||||
ElFormItem,
|
||||
ElInput,
|
||||
ElOption,
|
||||
ElRadio,
|
||||
ElRadioGroup,
|
||||
ElSelect,
|
||||
} from "element-plus"
|
||||
|
||||
import { langName, simpleLangName } from "../../../config/scripts/languages"
|
||||
import type { Extension } from "../../queries/useExtensionsRepositoryQuery"
|
||||
|
||||
export type Nsfw = "Show all" | "NSFW" | "SFW"
|
||||
export type Sort = "Ascending" | "Descending"
|
||||
|
||||
const props = defineProps<{
|
||||
extensions: Extension[][]
|
||||
search: string
|
||||
lang: string[]
|
||||
nsfw: Nsfw
|
||||
sort: Sort
|
||||
}>()
|
||||
|
||||
defineEmits<{
|
||||
(e: "update:search", search: string): void
|
||||
(e: "update:lang", lang: string[]): void
|
||||
(e: "update:nsfw", nsfw: Nsfw): void
|
||||
(e: "update:sort", sort: Sort): void
|
||||
}>()
|
||||
|
||||
const { extensions } = toRefs(props)
|
||||
|
||||
const isSmallScreen = useMediaQuery("(max-width: 767px)")
|
||||
const labelPosition = computed(() => isSmallScreen.value ? "top" : "right")
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<ClientOnly>
|
||||
<div class="filters-list">
|
||||
<ElForm label-width="120px" :label-position="labelPosition">
|
||||
<ElFormItem label="Search:">
|
||||
<ElInput
|
||||
:model-value="search"
|
||||
placeholder="Search extensions by name or ID..."
|
||||
clearable
|
||||
@update:model-value="$emit('update:search', $event)"
|
||||
/>
|
||||
</ElFormItem>
|
||||
<ElFormItem label="Languages:">
|
||||
<ElSelect
|
||||
:model-value="lang"
|
||||
placeholder="Show specific languages..."
|
||||
multiple
|
||||
clearable
|
||||
@update:model-value="$emit('update:lang', $event)"
|
||||
>
|
||||
<ElOption
|
||||
v-for="[group] in extensions"
|
||||
:key="group.lang"
|
||||
:label="group.lang === 'en' ? simpleLangName(group.lang) : langName(group.lang)"
|
||||
:value="group.lang"
|
||||
/>
|
||||
</ElSelect>
|
||||
</ElFormItem>
|
||||
<ElFormItem label="Sort by:">
|
||||
<ElRadioGroup
|
||||
:model-value="sort"
|
||||
@update:model-value="$emit('update:sort', $event)"
|
||||
>
|
||||
<ElRadio label="Ascending" />
|
||||
<ElRadio label="Descending" />
|
||||
</ElRadioGroup>
|
||||
</ElFormItem>
|
||||
<ElFormItem label="Display mode:">
|
||||
<ElRadioGroup
|
||||
:model-value="nsfw"
|
||||
@update:model-value="$emit('update:nsfw', $event)"
|
||||
>
|
||||
<ElRadio label="NSFW" />
|
||||
<ElRadio label="SFW" />
|
||||
<ElRadio label="Show all" />
|
||||
</ElRadioGroup>
|
||||
</ElFormItem>
|
||||
</ElForm>
|
||||
</div>
|
||||
</ClientOnly>
|
||||
</template>
|
||||
|
||||
<style lang="stylus">
|
||||
.filters-list {
|
||||
display: flex
|
||||
flex-direction: column
|
||||
row-gap: 1rem
|
||||
}
|
||||
|
||||
.el-select {
|
||||
width: 100%
|
||||
}
|
||||
</style>
|
@ -0,0 +1,51 @@
|
||||
<script setup lang="ts">
|
||||
import { computed, toRefs } from "vue"
|
||||
import { langName, simpleLangName } from "../../../config/scripts/languages"
|
||||
import type { Extension } from "../../queries/useExtensionsRepositoryQuery"
|
||||
import ExtensionItem from "./ExtensionItem.vue"
|
||||
|
||||
const props = defineProps<{ list: Extension[]; totalCount: number }>()
|
||||
const { list } = toRefs(props)
|
||||
|
||||
const groupName = computed(() => {
|
||||
const firstItem = list.value[0]
|
||||
|
||||
return firstItem.lang === "en"
|
||||
? simpleLangName(firstItem.lang)
|
||||
: langName(firstItem.lang)
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="extension-group">
|
||||
<h2>
|
||||
<span>{{ groupName }}</span>
|
||||
|
||||
<span class="extensions-total">
|
||||
Total:
|
||||
<span class="extensions-total-sum">
|
||||
{{ totalCount }}
|
||||
</span>
|
||||
</span>
|
||||
</h2>
|
||||
|
||||
<ExtensionItem
|
||||
v-for="extension in list"
|
||||
:id="extension.pkg.replace('eu.kanade.tachiyomi.extension.', '')"
|
||||
:key="extension.apk"
|
||||
:item="extension"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="stylus">
|
||||
.extension-group h2 {
|
||||
display: flex
|
||||
align-items: center
|
||||
justify-content: space-between
|
||||
|
||||
.extensions-total-sum {
|
||||
color: var(--vp-c-brand)
|
||||
}
|
||||
}
|
||||
</style>
|
@ -0,0 +1,172 @@
|
||||
<script setup lang="ts">
|
||||
/// <reference types="@types/gtag.js" />
|
||||
|
||||
import { computed, toRefs } from "vue"
|
||||
import type { Extension } from "../../queries/useExtensionsRepositoryQuery"
|
||||
|
||||
const props = defineProps<{ item: Extension }>()
|
||||
const { item } = toRefs(props)
|
||||
|
||||
const pkgId = computed(() => {
|
||||
return item.value.pkg.replace("eu.kanade.tachiyomi.extension.", "")
|
||||
})
|
||||
|
||||
const pkgName = computed(() => item.value.name.split(": ")[1])
|
||||
const pkgIsNsfw = computed(() => item.value.nsfw === 1)
|
||||
|
||||
const iconUrl = computed(() => {
|
||||
return `https://raw.githubusercontent.com/tachiyomiorg/tachiyomi-extensions/repo/icon/${item.value.pkg}.png`
|
||||
})
|
||||
|
||||
const apkUrl = computed(() => {
|
||||
return `https://raw.githubusercontent.com/tachiyomiorg/tachiyomi-extensions/repo/apk/${item.value.apk}`
|
||||
})
|
||||
|
||||
function handleAnalytics() {
|
||||
window.gtag?.("event", "Download", {
|
||||
event_category: "Extension",
|
||||
event_label: pkgName.value,
|
||||
version: item.value.version,
|
||||
})
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="extension">
|
||||
<a :href="`#${pkgId}`" class="anchor" aria-hidden="true" @click.stop>#</a>
|
||||
<img class="extension-icon" :src="iconUrl" loading="lazy" width="42" height="42">
|
||||
<div class="extension-text">
|
||||
<div class="upper">
|
||||
{{ pkgName }}
|
||||
</div>
|
||||
<div class="lower">
|
||||
{{ pkgId }}
|
||||
</div>
|
||||
</div>
|
||||
<Badge v-if="pkgIsNsfw" type="danger" :text="item.version" title="This extension contains NSFW entries." />
|
||||
<Badge v-else type="info" :text="item.version" title="This extension is free from NSFW entries." />
|
||||
<a
|
||||
:href="apkUrl"
|
||||
class="extension-download"
|
||||
title="Download APK"
|
||||
:download="item.apk"
|
||||
@click="handleAnalytics"
|
||||
>
|
||||
↓
|
||||
</a>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="stylus">
|
||||
.extension {
|
||||
position: relative
|
||||
align-items: center
|
||||
display: flex
|
||||
width: calc(100% + 1em)
|
||||
padding: 0.5em
|
||||
margin: 0.8em -0.5em
|
||||
border-radius: 8px
|
||||
gap: 0.675rem
|
||||
|
||||
&:hover {
|
||||
background-color: var(--vp-c-bg-soft)
|
||||
}
|
||||
|
||||
&:target {
|
||||
background-color: var(--vp-c-brand-soft)
|
||||
border-radius: 8px
|
||||
transition: 500ms background-color
|
||||
}
|
||||
|
||||
.anchor {
|
||||
position: absolute
|
||||
left: 0
|
||||
margin-left: -1em
|
||||
font-size: 1.4em
|
||||
opacity: 0
|
||||
}
|
||||
|
||||
&:hover .anchor {
|
||||
opacity: 1
|
||||
}
|
||||
|
||||
.extension-icon {
|
||||
flex-shrink: 0
|
||||
margin-left: -4px
|
||||
}
|
||||
|
||||
.extension-text {
|
||||
flex-grow: 1
|
||||
min-width: 0
|
||||
|
||||
.upper {
|
||||
font-weight: 600
|
||||
overflow: hidden
|
||||
text-overflow: ellipsis
|
||||
white-space: nowrap
|
||||
|
||||
.badge {
|
||||
font-weight: 400
|
||||
}
|
||||
}
|
||||
|
||||
.lower {
|
||||
margin-top: 0.25rem
|
||||
color: #6c757d
|
||||
font-family: var(--vp-font-family-mono)
|
||||
font-size: 0.9rem
|
||||
overflow: hidden
|
||||
text-overflow: ellipsis
|
||||
white-space: nowrap
|
||||
line-height: 16px
|
||||
}
|
||||
}
|
||||
|
||||
.extension-download {
|
||||
padding: 0.4em
|
||||
font-weight: 700
|
||||
font-size: 1.4em
|
||||
border-radius: 4px
|
||||
flex-shrink: 0
|
||||
margin-right: -0.4em
|
||||
|
||||
.material-icons {
|
||||
color: white
|
||||
max-width: 18px
|
||||
}
|
||||
|
||||
&:hover {
|
||||
.VPBadge {
|
||||
background-color: var(--vp-c-brand-darker)
|
||||
text-decoration: none
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width 767px) {
|
||||
padding: 0.4em 0
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width 767px) {
|
||||
.anchor {
|
||||
display: none
|
||||
}
|
||||
|
||||
.extension {
|
||||
border: 1px solid var(--vp-c-divider)
|
||||
border-radius: 8px
|
||||
padding: 0.5em
|
||||
margin: 0.8em 0
|
||||
width: 100%
|
||||
|
||||
.extension-icon {
|
||||
margin-left: 0
|
||||
}
|
||||
|
||||
.extension-download {
|
||||
margin-right: 0
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
@ -0,0 +1,36 @@
|
||||
<script setup lang="ts">
|
||||
import { computed, toRefs } from "vue"
|
||||
import type { Extension } from "../../queries/useExtensionsRepositoryQuery"
|
||||
import ExtensionGroup from "./ExtensionGroup.vue"
|
||||
|
||||
const props = defineProps<{ extensions: Extension[][] }>()
|
||||
const { extensions } = toRefs(props)
|
||||
|
||||
const totalCount = computed(() => {
|
||||
return extensions.value.reduce((sum, item) => sum + item.length, 0)
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="extension-list">
|
||||
<ExtensionGroup
|
||||
v-for="group in extensions"
|
||||
:key="group[0].lang"
|
||||
:list="group"
|
||||
:class="group[0].lang"
|
||||
:total-count="totalCount"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="stylus">
|
||||
.extension-list {
|
||||
> div {
|
||||
&:not(:first-of-type) {
|
||||
.extensions-total {
|
||||
display: none
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
@ -0,0 +1,125 @@
|
||||
<script setup lang="ts">
|
||||
import groupBy from "lodash.groupby"
|
||||
import { ElLoading } from "element-plus"
|
||||
|
||||
import { computed, nextTick, onMounted, reactive, ref, watch } from "vue"
|
||||
import { simpleLangName } from "../../../config/scripts/languages"
|
||||
import useExtensionsRepositoryQuery from "../../queries/useExtensionsRepositoryQuery"
|
||||
import type { Extension } from "../../queries/useExtensionsRepositoryQuery"
|
||||
import ExtensionFilters from "./ExtensionFilters.vue"
|
||||
import ExtensionList from "./ExtensionList.vue"
|
||||
import type { Nsfw, Sort } from "./ExtensionFilters.vue"
|
||||
|
||||
const { data: extensions, isLoading } = useExtensionsRepositoryQuery({
|
||||
select: (response) => {
|
||||
const values: Extension[][] = Object.values(groupBy(response, "lang"))
|
||||
values.sort(languageComparator)
|
||||
|
||||
return values
|
||||
},
|
||||
})
|
||||
|
||||
const filters = reactive({
|
||||
search: "",
|
||||
lang: [] as string[],
|
||||
nsfw: "Show all" as Nsfw,
|
||||
sort: "Ascending" as Sort,
|
||||
})
|
||||
|
||||
function languageComparator(a: Extension[], b: Extension[]) {
|
||||
const langA = simpleLangName(a[0].lang)
|
||||
const langB = simpleLangName(b[0].lang)
|
||||
if (langA === "All" && langB === "English") {
|
||||
return -1
|
||||
}
|
||||
if (langA === "English" && langB === "All") {
|
||||
return 1
|
||||
}
|
||||
if (langA === "English") {
|
||||
return -1
|
||||
}
|
||||
if (langB === "English") {
|
||||
return 1
|
||||
}
|
||||
if (langA < langB) {
|
||||
return -1
|
||||
}
|
||||
if (langA > langB) {
|
||||
return 1
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
const filteredExtensions = computed(() => {
|
||||
const filtered: Extension[][] = []
|
||||
|
||||
for (const group of (extensions.value ?? [])) {
|
||||
let filteredGroup = filters.lang.length
|
||||
? (filters.lang.includes(group[0].lang) ? group : [])
|
||||
: group
|
||||
|
||||
if (filters.search) {
|
||||
filteredGroup = filteredGroup.filter(
|
||||
(ext) =>
|
||||
ext.name.toLowerCase().includes(filters.search.toLowerCase())
|
||||
|| ext.sources.some((source) => source.id.includes(filters.search)),
|
||||
)
|
||||
}
|
||||
filteredGroup = filteredGroup.filter((ext) =>
|
||||
filters.nsfw === "Show all" ? true : ext.nsfw === (filters.nsfw === "NSFW" ? 1 : 0),
|
||||
)
|
||||
|
||||
if (filters.sort && filters.sort === "Descending") {
|
||||
filteredGroup = filteredGroup.reverse()
|
||||
}
|
||||
if (filteredGroup.length) {
|
||||
filtered.push(filteredGroup)
|
||||
}
|
||||
}
|
||||
|
||||
return filtered
|
||||
})
|
||||
|
||||
const loadingInstance = ref<ReturnType<typeof ElLoading["service"]>>()
|
||||
|
||||
onMounted(() => {
|
||||
loadingInstance.value = ElLoading.service({
|
||||
target: ".extensions",
|
||||
fullscreen: false,
|
||||
background: "transparent",
|
||||
})
|
||||
})
|
||||
|
||||
watch(extensions, async () => {
|
||||
if (window.location.hash) {
|
||||
await nextTick()
|
||||
document.getElementById(window.location.hash.substring(1))
|
||||
?.scrollIntoView({ behavior: "smooth" })
|
||||
}
|
||||
})
|
||||
|
||||
watch([isLoading, loadingInstance], async ([newIsLoading]) => {
|
||||
if (!newIsLoading) {
|
||||
loadingInstance.value?.close()
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<ExtensionFilters
|
||||
v-model:search="filters.search"
|
||||
v-model:lang="filters.lang"
|
||||
v-model:nsfw="filters.nsfw"
|
||||
v-model:sort="filters.sort"
|
||||
:extensions="extensions ?? []"
|
||||
/>
|
||||
<div class="extensions">
|
||||
<ExtensionList v-if="!isLoading" :extensions="filteredExtensions" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="stylus" scoped>
|
||||
.extensions {
|
||||
min-height: 200px
|
||||
}
|
||||
</style>
|
152
website/src/.vitepress/theme/components/News.vue
Normal file
152
website/src/.vitepress/theme/components/News.vue
Normal file
@ -0,0 +1,152 @@
|
||||
<script setup lang="ts">
|
||||
import { IconChevronRight } from "@iconify-prerendered/vue-mdi"
|
||||
import { data as newsList } from "../data/news.data"
|
||||
|
||||
const dateFormatter = new Intl.DateTimeFormat("en", {
|
||||
dateStyle: "medium",
|
||||
timeZone: "UTC",
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<article
|
||||
v-for="news of newsList"
|
||||
:key="news.url"
|
||||
class="news"
|
||||
>
|
||||
<div>
|
||||
<h3>
|
||||
<a :href="news.url">
|
||||
<span class="hover" />
|
||||
<span class="title">{{ news.title }}</span>
|
||||
</a>
|
||||
<div class="background" />
|
||||
</h3>
|
||||
<time :datetime="news.date">
|
||||
{{ dateFormatter.format(new Date(news.date)) }}
|
||||
</time>
|
||||
</div>
|
||||
<p>{{ news.description }}</p>
|
||||
<div class="readPrompt" aria-hidden="true">
|
||||
<span>Read article</span>
|
||||
<IconChevronRight />
|
||||
</div>
|
||||
</article>
|
||||
</template>
|
||||
|
||||
<style lang="stylus" scoped>
|
||||
.news {
|
||||
display: flex
|
||||
flex-direction: column
|
||||
gap: 0.5rem
|
||||
position: relative
|
||||
|
||||
&:first-of-type {
|
||||
margin-top: 3rem
|
||||
}
|
||||
|
||||
& + .news {
|
||||
margin-top: 3rem
|
||||
}
|
||||
|
||||
time {
|
||||
font-size: 0.875rem
|
||||
line-height: 1.25rem
|
||||
color: var(--vp-c-text-2)
|
||||
z-index: 10
|
||||
}
|
||||
|
||||
h3,
|
||||
p {
|
||||
margin: 0
|
||||
}
|
||||
|
||||
h3 {
|
||||
position: unset
|
||||
font-size: 1.125rem
|
||||
line-height: 1.75rem
|
||||
letter-spacing: -0.025em
|
||||
}
|
||||
|
||||
h3 a {
|
||||
font-weight: 600
|
||||
color: var(--vp-c-text-1)
|
||||
|
||||
&:hover {
|
||||
color: var(--vp-c-text-1)
|
||||
text-decoration: none
|
||||
}
|
||||
|
||||
&:focus {
|
||||
outline: none
|
||||
}
|
||||
}
|
||||
|
||||
.title,
|
||||
.readPrompt,
|
||||
p,
|
||||
time {
|
||||
position: relative
|
||||
}
|
||||
|
||||
.title {
|
||||
z-index: 10
|
||||
}
|
||||
|
||||
.readPrompt {
|
||||
font-weight: 500
|
||||
font-size: 0.875rem
|
||||
line-height: 1.25rem
|
||||
color: var(--vp-c-brand-1)
|
||||
display: flex
|
||||
align-items: center
|
||||
margin-top: 0.25rem
|
||||
|
||||
svg {
|
||||
margin-bottom: -2px
|
||||
margin-left: 0.25rem
|
||||
width: 1.25rem
|
||||
height: 1.25rem
|
||||
}
|
||||
}
|
||||
|
||||
.hover,
|
||||
.background {
|
||||
position: absolute
|
||||
z-index: 20
|
||||
bottom: -1rem
|
||||
top: -1rem
|
||||
left: -1rem
|
||||
right: -1rem
|
||||
border-radius: 12px
|
||||
}
|
||||
|
||||
.background {
|
||||
background-color: var(--vp-c-bg-soft)
|
||||
transform: scale(0.95)
|
||||
opacity: 0
|
||||
z-index: 0
|
||||
transition: opacity 0.15s cubic-bezier(0.4, 0, 0.2, 1), transform 0.15s cubic-bezier(0.4, 0, 0.2, 1)
|
||||
}
|
||||
|
||||
&:hover .background,
|
||||
&:focus-within .background {
|
||||
opacity: 1
|
||||
transform: scale(1)
|
||||
}
|
||||
|
||||
h3: a
|
||||
|
||||
:focus-visible + .background {
|
||||
outline: 2px solid var(--vp-c-brand-2)
|
||||
}
|
||||
|
||||
:focus-visible + .background {
|
||||
outline: 2px solid var(--vp-c-brand-2)
|
||||
}
|
||||
|
||||
:focus-visible + .background {
|
||||
outline: 2px solid var(--vp-c-brand-2)
|
||||
}
|
||||
}
|
||||
</style>
|
32
website/src/.vitepress/theme/components/OgImageTemplate.vue
Normal file
32
website/src/.vitepress/theme/components/OgImageTemplate.vue
Normal file
@ -0,0 +1,32 @@
|
||||
<script setup lang="ts">
|
||||
defineProps<{ title: string; description?: string; dir?: string }>()
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div
|
||||
tw="w-full h-full bg-white flex flex-col"
|
||||
style="background:linear-gradient(0deg, rgba(255, 255, 255, 0.92), rgba(231,230,255,0.86)), url(https://tachiyomi.org/img/open-graph-background.png)"
|
||||
>
|
||||
<div tw="p-10 w-full min-h-0 grow flex flex-col items-center justify-between">
|
||||
<div tw="w-full flex justify-between items-center text-4xl font-medium">
|
||||
<div tw="flex items-center">
|
||||
<svg width="64" height="64" viewBox="0 0 288 288" preserveAspectRatio="xMidYMid meet" fill="#8995FF"><path d="M141.2 36.7l-18.3.3.8 9.8c.4 5.3.8 11.9.8 14.7v5h-41-41l.3 17.9.3 17.9 6.7-.7c15.2-1.4 101.5-1.8 146.2-.7l48 1.6c1.3.4 1.5-2 1.5-17.8V66.5l-38.3.4c-21 .2-39.8 0-41.8-.3l-3.5-.7.3-15c.3-12.8.1-14.9-1.2-14.7-.8.1-9.7.4-19.8.5zm-56.7 76.9c-12.8 5-16 6.7-15.8 8.1.1 1 2.2 7.3 4.7 14 5.8 15.3 12.4 38.4 16.6 57.8l3.5 15.3c.2.2 8.3-2.5 18.2-6l17.9-6.3-1.2-6c-3.5-16.8-24.8-83.6-26.7-83.4-.7.1-8.4 3-17.2 6.5zm93.6 10.1c-10.4 41.7-22.9 83.2-27.1 90.1l-1.9 3.1-17.3.3c-20 .3-91.3-.9-94.3-1.6-1.9-.5-2 0-2 18v18.6l5-.6c2.8-.3 51.8-.9 109-1.2l104-.7v-17.6-17.6l-8.5.7c-4.7.3-19.5.7-32.9.7-19.1.1-24.2-.2-23.8-1.2.3-.6 2.2-5 4.2-9.7 6.5-15.2 29.6-86.3 28.4-87.4-.4-.4-32.6-9.4-36.5-10.3-2-.4-2.4.7-6.3 16.4z" /></svg>
|
||||
<div tw="text-slate-900 ml-2 mt-1 font-semibold">
|
||||
Tachiyomi
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="dir" tw="flex items-center text-slate-600">
|
||||
<div v-if="dir" tw="text-4xl font-semibold mr-2" v-html="dir" />
|
||||
<svg v-if="dir === 'FAQ'" width="48" height="48" viewBox="0 -960 960 960" fill="currentColor" xmlns="http://www.w3.org/2000/svg"><path d="M120-55.5q-15.5 0-26.5-11T82.5-93q0-15.5 11-26.5t26.5-11h720q15.5 0 26.5 11t11 26.5q0 15.5-11 26.5t-26.5 11H120ZM207-170q-15.5 0-26.5-11t-11-26.5v-193q-32.5-54-50.75-114.25T100.5-639.5q0-59 15.25-117.5t36.75-116q7.5-20 24.5-32.25t38-12.25q26.5 0 44.75 18.5T276-854.5l-5.5 92q-3 48.5 11 93t41.75 78Q351-558 391-537.75t89 20.25q59.5 0 116.5 11.5T697-471.5q43.5 23 68.25 58.5T790-326v118.5q0 15.5-11 26.5t-26.5 11h-355v-32q0-35 24-60.25T480-287.5h120q15.5 0 26.5-11t11-26.5q0-15.5-11-26.5t-26.5-11H480q-66 0-111.75 47T322.5-202v32H207Zm273-392.5q-65.5 0-111.5-46t-46-111.5q0-65.5 46-111.5t111.5-46q65.5 0 111.5 46t46 111.5q0 65.5-46 111.5t-111.5 46Z" /></svg>
|
||||
<svg v-else-if="dir === 'Guide'" height="48" width="48" viewBox="0 -960 960 960" fill="currentColor" xmlns="http://www.w3.org/2000/svg"><path d="M831-327.5V-558L516-386.5q-17 9-36 9t-36-9l-329.5-180q-10-5.5-14.5-14.25t-4.5-18.75q0-10 4.5-18.75t14.5-14.25l330-179.5q8.5-5 17.25-7t18.25-2q9.5 0 18.25 2t17.25 7l371 202q9 5 14.25 13.75T906-577v249.5q0 15.5-11 26.5t-26.5 11q-15.5 0-26.5-11t-11-26.5Zm-387 173-197-107q-18.5-10-28.75-27.75T208-328v-149.5L444-349q17 9 36 9t36-9l236-128.5V-328q0 21-10.25 38.75T713-261.5l-197 107q-8.5 5-17.5 7t-18.5 2q-9.5 0-18.5-2t-17.5-7Z" /></svg>
|
||||
<svg v-else-if="dir === 'News'" height="48" width="48" viewBox="0 -960 960 960" fill="currentColor" xmlns="http://www.w3.org/2000/svg"><path d="M157.5-122.5q-31 0-53-22t-22-53v-614q0-6.5 5.5-9t10.5 2.5l35.5 35.5q5.5 5.5 13 5.25t13-5.75l40-40q5.5-5.5 13-5.75t13 5.25l41 41q5.5 5.5 13 5.5t13-5.5l41-41q5.5-5.5 13-5.25t13 5.75l40 40q5.5 5.5 13 5.75t13-5.25l41-41q5.5-5.5 13-5.5t13 5.5l41 41q5.5 5.5 13 5.25t13-5.75l40-40q5.5-5.5 13-5.75t13 5.25l41 41q5.5 5.5 13 5.5t13-5.5l41-41q5.5-5.5 13-5.25t13 5.75l40 40q5.5 5.5 13 5.75t13-5.25l35.5-35.5q5-5 10.5-2.5t5.5 9v614q0 31-22 53t-53 22h-645Zm0-75h285v-245h-285v245Zm360 0h285v-85h-285v85Zm0-160h285v-85h-285v85Zm-360-160h645v-125h-645v125Z" /></svg>
|
||||
</div>
|
||||
</div>
|
||||
<div tw="w-full pr-56 flex flex-col items-start justify-end">
|
||||
<div tw="text-6xl font-bold text-slate-900" v-html="title" />
|
||||
<div v-if="description" tw="mt-2 text-4xl text-slate-600" v-html="description" />
|
||||
</div>
|
||||
</div>
|
||||
<div tw="shrink-0 h-2 w-full flex" style="background-color: #8995ff;" />
|
||||
</div>
|
||||
</template>
|
30
website/src/.vitepress/theme/components/ReleaseDate.vue
Normal file
30
website/src/.vitepress/theme/components/ReleaseDate.vue
Normal file
@ -0,0 +1,30 @@
|
||||
<script setup lang="ts">
|
||||
import { computed, onMounted, ref, toRefs } from "vue"
|
||||
import moment from "moment"
|
||||
import { type AppRelease, data as release } from "../data/release.data"
|
||||
|
||||
const props = defineProps<{ type: keyof AppRelease }>()
|
||||
const { type } = toRefs(props)
|
||||
|
||||
const momentInfo = computed(() => ({
|
||||
relative: moment(release[type.value].published_at).fromNow(),
|
||||
exact: moment(release[type.value].published_at).format("dddd, MMMM Do YYYY [at] HH:mm"),
|
||||
iso: release[type.value].published_at ?? undefined,
|
||||
}))
|
||||
|
||||
// Mimic the <ClientOnly /> behavior to show custom text while rendering.
|
||||
const show = ref(false)
|
||||
|
||||
onMounted(() => {
|
||||
show.value = true
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<time v-if="show" :datetime="momentInfo.iso" :title="momentInfo.exact">
|
||||
{{ momentInfo.relative }}
|
||||
</time>
|
||||
<time v-else :datetime="momentInfo.iso">
|
||||
{{ momentInfo.exact }}
|
||||
</time>
|
||||
</template>
|
27
website/src/.vitepress/theme/components/RssLink.vue
Normal file
27
website/src/.vitepress/theme/components/RssLink.vue
Normal file
@ -0,0 +1,27 @@
|
||||
<script setup lang="ts">
|
||||
import { IconRssBox } from "@iconify-prerendered/vue-mdi"
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<a href="/feed.rss" class="rss" title="RSS feed for the news archive">
|
||||
<IconRssBox />
|
||||
<span>RSS feed</span>
|
||||
</a>
|
||||
</template>
|
||||
|
||||
<style lang="stylus" scoped>
|
||||
.rss {
|
||||
& > * {
|
||||
vertical-align: middle
|
||||
position: relative
|
||||
bottom: 1px
|
||||
}
|
||||
|
||||
svg {
|
||||
width: 1em
|
||||
height: 1em
|
||||
display: inline-block
|
||||
margin-right: 4px
|
||||
}
|
||||
}
|
||||
</style>
|
22
website/src/.vitepress/theme/data/changelogs.data.ts
Normal file
22
website/src/.vitepress/theme/data/changelogs.data.ts
Normal file
@ -0,0 +1,22 @@
|
||||
import { defineLoader } from "vitepress"
|
||||
import { Octokit } from "@octokit/rest"
|
||||
import type { GetResponseDataTypeFromEndpointMethod } from "@octokit/types"
|
||||
|
||||
const octokit = new Octokit()
|
||||
|
||||
type GitHubReleaseList = GetResponseDataTypeFromEndpointMethod<typeof octokit.repos.listReleases>
|
||||
|
||||
declare const data: GitHubReleaseList
|
||||
export { data }
|
||||
|
||||
export default defineLoader({
|
||||
async load(): Promise<GitHubReleaseList> {
|
||||
const releases = await octokit.paginate(octokit.repos.listReleases, {
|
||||
owner: "tachiyomiorg",
|
||||
repo: "tachiyomi",
|
||||
per_page: 100,
|
||||
})
|
||||
|
||||
return releases
|
||||
},
|
||||
})
|
29
website/src/.vitepress/theme/data/news.data.ts
Normal file
29
website/src/.vitepress/theme/data/news.data.ts
Normal file
@ -0,0 +1,29 @@
|
||||
import { createContentLoader } from "vitepress"
|
||||
|
||||
export interface News {
|
||||
title: string
|
||||
description: string
|
||||
date: string
|
||||
url: string
|
||||
}
|
||||
|
||||
declare const data: News[]
|
||||
export { data }
|
||||
|
||||
export default createContentLoader("news/*.md", {
|
||||
excerpt: true,
|
||||
transform(articles) {
|
||||
return articles
|
||||
.filter(({ url }) => url !== "/news/")
|
||||
.map(
|
||||
({ frontmatter, url }) =>
|
||||
<News>{
|
||||
title: frontmatter.title,
|
||||
description: frontmatter.description,
|
||||
date: frontmatter.date,
|
||||
url,
|
||||
},
|
||||
)
|
||||
.sort((a, b) => b.date.localeCompare(a.date))
|
||||
},
|
||||
})
|
31
website/src/.vitepress/theme/data/release.data.ts
Normal file
31
website/src/.vitepress/theme/data/release.data.ts
Normal file
@ -0,0 +1,31 @@
|
||||
import { defineLoader } from "vitepress"
|
||||
import { Octokit } from "@octokit/rest"
|
||||
import type { GetResponseDataTypeFromEndpointMethod } from "@octokit/types"
|
||||
|
||||
const octokit = new Octokit()
|
||||
|
||||
type GitHubRelease = GetResponseDataTypeFromEndpointMethod<typeof octokit.repos.getLatestRelease>
|
||||
|
||||
export interface AppRelease {
|
||||
stable: GitHubRelease
|
||||
preview: GitHubRelease
|
||||
}
|
||||
|
||||
declare const data: AppRelease
|
||||
export { data }
|
||||
|
||||
export default defineLoader({
|
||||
async load(): Promise<AppRelease> {
|
||||
const { data: stable } = await octokit.repos.getLatestRelease({
|
||||
owner: "tachiyomiorg",
|
||||
repo: "tachiyomi",
|
||||
})
|
||||
|
||||
const { data: preview } = await octokit.repos.getLatestRelease({
|
||||
owner: "tachiyomiorg",
|
||||
repo: "tachiyomi-preview",
|
||||
})
|
||||
|
||||
return { stable, preview }
|
||||
},
|
||||
})
|
31
website/src/.vitepress/theme/index.ts
Normal file
31
website/src/.vitepress/theme/index.ts
Normal file
@ -0,0 +1,31 @@
|
||||
// https://vitepress.dev/guide/custom-theme
|
||||
import DefaultTheme from "vitepress/theme"
|
||||
|
||||
// Import Stylus files
|
||||
import "./styles/base.styl"
|
||||
|
||||
// Import Global plugins
|
||||
import "element-plus/theme-chalk/dark/css-vars.css"
|
||||
|
||||
import { VueQueryPlugin } from "@tanstack/vue-query"
|
||||
|
||||
import { enhanceAppWithTabs } from "vitepress-plugin-tabs/client"
|
||||
|
||||
// Import icon components
|
||||
import { IconBugReport, IconDownload, IconNewspaperVariant } from "@iconify-prerendered/vue-mdi"
|
||||
|
||||
import analytics from "./plugin/analytics"
|
||||
import Layout from "./Layout.vue"
|
||||
|
||||
export default {
|
||||
extends: DefaultTheme,
|
||||
enhanceApp({ app }) {
|
||||
app.use(VueQueryPlugin)
|
||||
enhanceAppWithTabs(app)
|
||||
app.component("IconDownload", IconDownload)
|
||||
app.component("IconNewspaperVariant", IconNewspaperVariant)
|
||||
app.component("IconBugReport", IconBugReport)
|
||||
analytics({ id: "G-2CBXXM1Y86" })
|
||||
},
|
||||
Layout,
|
||||
}
|
37
website/src/.vitepress/theme/plugin/analytics.ts
Normal file
37
website/src/.vitepress/theme/plugin/analytics.ts
Normal file
@ -0,0 +1,37 @@
|
||||
// Code based on vitepress-plugin-google-analytics.
|
||||
// Customized as the plugin did not consider the script loading time.
|
||||
// https://github.com/ZhongxuYang/vitepress-plugin-google-analytics
|
||||
|
||||
function mountGoogleAnalytics(id: string) {
|
||||
if (("dataLayer" in window && window.gtag) || window.location.hostname === "localhost") {
|
||||
return
|
||||
}
|
||||
|
||||
const analyticsScript = document.createElement("script")
|
||||
|
||||
analyticsScript.addEventListener("load", () => {
|
||||
// @ts-expect-error Missing types
|
||||
window.dataLayer = window.dataLayer || []
|
||||
function gtag(..._args: any[]) {
|
||||
// @ts-expect-error Missing types
|
||||
// eslint-disable-next-line prefer-rest-params
|
||||
window.dataLayer.push(arguments)
|
||||
}
|
||||
|
||||
gtag("js", new Date())
|
||||
gtag("config", id)
|
||||
|
||||
window.gtag = gtag
|
||||
})
|
||||
|
||||
analyticsScript.src = `https://www.googletagmanager.com/gtag/js?id=${id}`
|
||||
|
||||
document.body.appendChild(analyticsScript)
|
||||
}
|
||||
|
||||
export default function ({ id }: { id: string }) {
|
||||
// eslint-disable-next-line n/prefer-global/process
|
||||
if (process.env.NODE_ENV === "production" && id && typeof window !== "undefined") {
|
||||
mountGoogleAnalytics(id)
|
||||
}
|
||||
}
|
@ -0,0 +1,45 @@
|
||||
import type { UseQueryOptions } from "@tanstack/vue-query"
|
||||
import { useQuery } from "@tanstack/vue-query"
|
||||
import axios from "axios"
|
||||
import { GITHUB_EXTENSION_JSON } from "../../config/constants"
|
||||
|
||||
export type ReleaseType = "stable" | "preview"
|
||||
|
||||
export interface Extension {
|
||||
name: string
|
||||
pkg: string
|
||||
apk: string
|
||||
lang: string
|
||||
code: number
|
||||
version: string
|
||||
nsfw: number
|
||||
hasReadme: number
|
||||
hasChangelog: number
|
||||
sources: Source[]
|
||||
}
|
||||
|
||||
export interface Source {
|
||||
name: string
|
||||
lang: string
|
||||
id: string
|
||||
baseUrl: string
|
||||
versionId: number
|
||||
hasCloudflare: string
|
||||
}
|
||||
|
||||
type UseExtensionsRepositoryQueryOptions<S = Extension[]> =
|
||||
UseQueryOptions<Extension[], Error, S>
|
||||
|
||||
export default function useExtensionsRepositoryQuery<S = Extension[]>(options: UseExtensionsRepositoryQueryOptions<S> = {}) {
|
||||
return useQuery<Extension[], Error, S>({
|
||||
queryKey: ["extensions"],
|
||||
queryFn: async () => {
|
||||
const { data } = await axios.get<Extension[]>(GITHUB_EXTENSION_JSON)
|
||||
|
||||
return data
|
||||
},
|
||||
initialData: () => [],
|
||||
refetchOnWindowFocus: false,
|
||||
...options,
|
||||
})
|
||||
}
|
278
website/src/.vitepress/theme/styles/base.styl
Normal file
278
website/src/.vitepress/theme/styles/base.styl
Normal file
@ -0,0 +1,278 @@
|
||||
/**
|
||||
* Customize default theme styling by overriding CSS variables:
|
||||
* https://github.com/vuejs/vitepress/blob/main/src/client/theme-default/styles/vars.css
|
||||
*/
|
||||
/**
|
||||
* Colors
|
||||
* -------------------------------------------------------------------------- */
|
||||
|
||||
// Assign theme color
|
||||
$themeColor = #818cf8
|
||||
|
||||
:root {
|
||||
--vp-c-brand: $themeColor
|
||||
--vp-c-brand-light: tint($themeColor, 20%)
|
||||
--vp-c-brand-lighter: tint($themeColor, 40%)
|
||||
--vp-c-brand-lightest: tint($themeColor, 60%)
|
||||
--vp-c-brand-dark: shade($themeColor, 25%)
|
||||
--vp-c-brand-darker: shade($themeColor, 50%)
|
||||
--vp-c-brand-darkest: shade($themeColor, 75%)
|
||||
--vp-c-brand-dimm: alpha($themeColor, 0.08)
|
||||
}
|
||||
|
||||
/**
|
||||
* Dark/Light Theme Overrides
|
||||
* -------------------------------------------------------------------------- */
|
||||
|
||||
html:not(.dark) {
|
||||
img[data-mode="darkmode-only"] {
|
||||
display: none !important
|
||||
|
||||
& + figcaption {
|
||||
display: none !important
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.dark {
|
||||
img[data-mode="lightmode-only"] {
|
||||
display: none !important
|
||||
|
||||
& + figcaption {
|
||||
display: none !important
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Component: Button
|
||||
* -------------------------------------------------------------------------- */
|
||||
|
||||
:root {
|
||||
--vp-button-brand-bg: var(--vp-c-brand-darker)
|
||||
}
|
||||
|
||||
/**
|
||||
* Component: Home
|
||||
* -------------------------------------------------------------------------- */
|
||||
|
||||
:root {
|
||||
--vp-home-hero-name-color: transparent
|
||||
--vp-home-hero-name-background: -webkit-linear-gradient(120deg, var(--vp-c-brand) 30%, var(--vp-c-brand-dark))
|
||||
--vp-home-hero-image-background-image: linear-gradient(-45deg, var(--vp-c-brand-light) 50%, var(--vp-c-brand-lighter) 50%)
|
||||
--vp-home-hero-image-filter: blur(40px)
|
||||
}
|
||||
|
||||
.dark {
|
||||
--vp-home-hero-image-background-image: linear-gradient(-45deg, var(--vp-c-brand-darker) 25%, var(--vp-c-brand-darkest) 25%)
|
||||
}
|
||||
|
||||
@media (min-width 640px) {
|
||||
:root {
|
||||
--vp-home-hero-image-filter: blur(56px)
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width 960px) {
|
||||
:root {
|
||||
--vp-home-hero-image-filter: blur(72px)
|
||||
}
|
||||
}
|
||||
|
||||
.VPFooter {
|
||||
.divider {
|
||||
color: var(--vp-c-divider)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Component: Custom Block
|
||||
* -------------------------------------------------------------------------- */
|
||||
|
||||
:root {
|
||||
--vp-custom-block-tip-border: var(--vp-c-brand)
|
||||
--vp-custom-block-tip-text: var(--vp-c-brand-darker)
|
||||
--vp-custom-block-tip-bg: var(--vp-c-brand-dimm)
|
||||
}
|
||||
|
||||
.dark {
|
||||
--vp-custom-block-tip-border: var(--vp-c-brand)
|
||||
--vp-custom-block-tip-text: var(--vp-c-brand-lightest)
|
||||
--vp-custom-block-tip-bg: var(--vp-c-brand-dimm)
|
||||
--vp-custom-block-info-bg: #212127
|
||||
}
|
||||
|
||||
.plugin-tabs {
|
||||
&--content {
|
||||
padding: 2rem !important
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Component: Algolia
|
||||
* -------------------------------------------------------------------------- */
|
||||
|
||||
.DocSearch {
|
||||
--docsearch-primary-color: var(--vp-c-brand) !important
|
||||
}
|
||||
|
||||
.VPImage.image-src {
|
||||
border-radius: 8px
|
||||
}
|
||||
|
||||
/**
|
||||
* Component: LocalSearch
|
||||
*/
|
||||
|
||||
.VPLocalSearchBox {
|
||||
--vp-local-search-highlight-bg: var(--vp-c-brand-soft)
|
||||
--vp-local-search-highlight-text: var(--vp-c-brand-dark)
|
||||
}
|
||||
|
||||
.dark .VPLocalSearchBox {
|
||||
--vp-local-search-highlight-text: var(--vp-c-brand-lightest)
|
||||
}
|
||||
|
||||
/**
|
||||
* Component: Image Figure
|
||||
* -------------------------------------------------------------------------- */
|
||||
|
||||
// Shitty method to give elevation to image - TO BE REPLACED
|
||||
|
||||
main figure {
|
||||
margin: 2rem 0
|
||||
transition: transform var(--vp-tt)
|
||||
|
||||
img {
|
||||
margin: 0 auto
|
||||
border-radius: 12px
|
||||
box-shadow: 2px 2px 8px 4px var(--vp-c-bg-alt)
|
||||
}
|
||||
|
||||
figcaption {
|
||||
text-align: center
|
||||
margin-top: 1.25rem
|
||||
font-size: 0.875rem
|
||||
color: var(--vp-c-text-2)
|
||||
}
|
||||
|
||||
& > a .external-link-icon {
|
||||
display: none
|
||||
}
|
||||
}
|
||||
|
||||
main :where(h1, h2, h3, h4, h5, h6) + figure {
|
||||
margin-top: 1.5rem
|
||||
}
|
||||
|
||||
.custom-block {
|
||||
figure figcaption {
|
||||
color: inherit
|
||||
}
|
||||
|
||||
&.tip figure img {
|
||||
--vp-c-bg-alt: var(--vp-custom-block-tip-bg)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Component: Links
|
||||
* -------------------------------------------------------------------------- */
|
||||
|
||||
@media print {
|
||||
figure:has(img)>a[href^="http://"]:after,
|
||||
figure:has(img)>a[href^="https://"]:after {
|
||||
content: ""
|
||||
}
|
||||
}
|
||||
|
||||
.vp-doc a {
|
||||
text-decoration: none
|
||||
|
||||
&:hover {
|
||||
text-decoration: underline
|
||||
text-underline-offset: 2px
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Component: Shortcodes
|
||||
* -------------------------------------------------------------------------- */
|
||||
|
||||
.navigation {
|
||||
color: var(--vp-c-green-2)
|
||||
font-weight: 600
|
||||
|
||||
&:hover {
|
||||
color: var(--vp-c-green-1)
|
||||
cursor: default
|
||||
}
|
||||
|
||||
svg,
|
||||
span.name {
|
||||
vertical-align: middle // Align both SVG and <span> vertically
|
||||
position: relative
|
||||
bottom: 1px
|
||||
}
|
||||
|
||||
svg {
|
||||
fill: currentColor
|
||||
height: 1em
|
||||
width: 1em
|
||||
display: inline-block
|
||||
margin-right: 4px
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Component: Element Plus
|
||||
*/
|
||||
|
||||
body {
|
||||
--el-color-primary: var(--vp-c-brand-1)
|
||||
}
|
||||
|
||||
/**
|
||||
* Component: Appearance Switch
|
||||
*/
|
||||
|
||||
.menu + .appearance::before {
|
||||
margin-right: 8px !important
|
||||
}
|
||||
|
||||
.appearance + .social-links::before {
|
||||
margin-left: 8px !important
|
||||
}
|
||||
|
||||
.VPMenu .CustomSwitchAppearance {
|
||||
margin-right: -8px
|
||||
}
|
||||
|
||||
/**
|
||||
* Component: Forks page
|
||||
*/
|
||||
|
||||
.forks .VPFeatures {
|
||||
padding: 2rem 0 !important
|
||||
|
||||
.VPLink:hover {
|
||||
text-decoration: none
|
||||
}
|
||||
|
||||
.title {
|
||||
line-height: 24px
|
||||
font-size: 16px
|
||||
font-weight: 600
|
||||
margin: 0
|
||||
border-top: none
|
||||
padding-top: 0
|
||||
letter-spacing: 0
|
||||
color: var(--vp-c-text-1)
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width 640px) {
|
||||
.forks .item.grid-4 {
|
||||
width: 50% !important
|
||||
}
|
||||
}
|
9
website/src/.vitepress/theme/styles/forks/lint.styl
Normal file
9
website/src/.vitepress/theme/styles/forks/lint.styl
Normal file
@ -0,0 +1,9 @@
|
||||
.extension-list {
|
||||
> div {
|
||||
&:not(:first-of-type) {
|
||||
.extensions-total {
|
||||
display: none
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
60
website/src/.vitepress/theme/styles/forks/neko.styl
Normal file
60
website/src/.vitepress/theme/styles/forks/neko.styl
Normal file
@ -0,0 +1,60 @@
|
||||
// Assign theme color
|
||||
$themeColor = #ff6884
|
||||
|
||||
.page-neko {
|
||||
.VPHero {
|
||||
h1 {
|
||||
.clip {
|
||||
--vp-home-hero-name-background: -webkit-linear-gradient(120deg, var(--vp-c-brand) 30%, var(--vp-c-brand-dark))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.VPButton {
|
||||
&.brand {
|
||||
border-color: var(--vp-button-brand-border)
|
||||
color: var(--vp-button-brand-text)
|
||||
background-color: var(--vp-button-brand-bg)
|
||||
}
|
||||
}
|
||||
|
||||
.image-bg {
|
||||
display: none
|
||||
}
|
||||
|
||||
::selection {
|
||||
background: alpha($themeColor, 0.2)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Colors
|
||||
* -------------------------------------------------------------------------- */
|
||||
|
||||
:root .page-neko {
|
||||
--vp-c-brand: $themeColor
|
||||
--vp-c-brand-light: tint($themeColor, 20%)
|
||||
--vp-c-brand-lighter: tint($themeColor, 40%)
|
||||
--vp-c-brand-lightest: tint($themeColor, 60%)
|
||||
--vp-c-brand-dark: shade($themeColor, 25%)
|
||||
--vp-c-brand-darker: shade($themeColor, 50%)
|
||||
--vp-c-brand-darkest: shade($themeColor, 75%)
|
||||
--vp-c-brand-dimm: alpha($themeColor, 0.08)
|
||||
}
|
||||
|
||||
/**
|
||||
* Component: Button
|
||||
* -------------------------------------------------------------------------- */
|
||||
|
||||
:root .page-neko {
|
||||
--vp-button-brand-border: var(--vp-c-brand-light)
|
||||
--vp-button-brand-text: var(--vp-c-black)
|
||||
--vp-button-brand-bg: var(--vp-c-brand)
|
||||
--vp-button-brand-hover-border: var(--vp-c-brand-light)
|
||||
--vp-button-brand-hover-text: var(--vp-c-black)
|
||||
--vp-button-brand-hover-bg: var(--vp-c-brand-light)
|
||||
--vp-button-brand-active-border: var(--vp-c-brand-light)
|
||||
--vp-button-brand-active-text: var(--vp-c-black)
|
||||
--vp-button-brand-active-bg: var(--vp-button-brand-bg)
|
||||
}
|
203
website/src/.vitepress/theme/styles/forks/tachiyomi-az.styl
Normal file
203
website/src/.vitepress/theme/styles/forks/tachiyomi-az.styl
Normal file
@ -0,0 +1,203 @@
|
||||
// Assign theme color
|
||||
$themeColor = #ffcc4d
|
||||
|
||||
.page-tachiyomi-az {
|
||||
.VPHero {
|
||||
h1 {
|
||||
.clip {
|
||||
--vp-home-hero-name-background: -webkit-linear-gradient(120deg, var(--vp-c-brand) 30%, var(--vp-c-brand-dark))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.VPButton {
|
||||
&.brand {
|
||||
border-color: var(--vp-button-brand-border)
|
||||
color: var(--vp-button-brand-text)
|
||||
background-color: var(--vp-button-brand-bg)
|
||||
}
|
||||
}
|
||||
|
||||
.image-bg {
|
||||
display: none
|
||||
}
|
||||
|
||||
::selection {
|
||||
background: alpha($themeColor, 0.2)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Colors
|
||||
* -------------------------------------------------------------------------- */
|
||||
|
||||
:root .page-tachiyomi-az {
|
||||
--vp-c-brand: $themeColor
|
||||
--vp-c-brand-light: tint($themeColor, 20%)
|
||||
--vp-c-brand-lighter: tint($themeColor, 40%)
|
||||
--vp-c-brand-lightest: tint($themeColor, 60%)
|
||||
--vp-c-brand-dark: shade($themeColor, 25%)
|
||||
--vp-c-brand-darker: shade($themeColor, 50%)
|
||||
--vp-c-brand-darkest: shade($themeColor, 75%)
|
||||
--vp-c-brand-dimm: alpha($themeColor, 0.08)
|
||||
}
|
||||
|
||||
/**
|
||||
* Component: Button
|
||||
* -------------------------------------------------------------------------- */
|
||||
|
||||
:root .page-tachiyomi-az {
|
||||
--vp-button-brand-border: var(--vp-c-brand-light)
|
||||
--vp-button-brand-text: var(--vp-c-black)
|
||||
--vp-button-brand-bg: var(--vp-c-brand)
|
||||
--vp-button-brand-hover-border: var(--vp-c-brand-light)
|
||||
--vp-button-brand-hover-text: var(--vp-c-black)
|
||||
--vp-button-brand-hover-bg: var(--vp-c-brand-light)
|
||||
--vp-button-brand-active-border: var(--vp-c-brand-light)
|
||||
--vp-button-brand-active-text: var(--vp-c-black)
|
||||
--vp-button-brand-active-bg: var(--vp-button-brand-bg)
|
||||
}
|
||||
|
||||
/**
|
||||
* Component: Dumb
|
||||
* -------------------------------------------------------------------------- */
|
||||
@font-face {
|
||||
font-family: 'Comic Sans MS'
|
||||
src: url('//db.onlinewebfonts.com/t/7cc6719bd5f0310be3150ba33418e72e.eot')
|
||||
src: url('//db.onlinewebfonts.com/t/7cc6719bd5f0310be3150ba33418e72e.eot?#iefix') format('embedded-opentype'), url('//db.onlinewebfonts.com/t/7cc6719bd5f0310be3150ba33418e72e.woff2') format('woff2'), url('//db.onlinewebfonts.com/t/7cc6719bd5f0310be3150ba33418e72e.woff') format('woff'), url('//db.onlinewebfonts.com/t/7cc6719bd5f0310be3150ba33418e72e.ttf') format('truetype'), url('//db.onlinewebfonts.com/t/7cc6719bd5f0310be3150ba33418e72e.svg#Comic Sans MS') format('svg')
|
||||
}
|
||||
|
||||
.azContainer {
|
||||
width: 100%
|
||||
overflow: hidden
|
||||
|
||||
.azMarquee {
|
||||
display: inline-block
|
||||
overflow: hidden
|
||||
white-space: nowrap
|
||||
animation: marquee 10s linear infinite
|
||||
padding-left: 100%
|
||||
|
||||
.azWiggleText {
|
||||
padding: 2em
|
||||
width: fit-content
|
||||
animation: wiggleAnimation 1s ease-out infinite
|
||||
|
||||
&:hover {
|
||||
animation: barrelRollAnimation 0.6s
|
||||
}
|
||||
|
||||
.azText {
|
||||
font-family: 'Comic Sans MS', 'Comic Sans', cursive
|
||||
font-size: 2em
|
||||
display: inline-block
|
||||
-webkit-text-stroke: 1px black
|
||||
text-shadow: -1px -1px 0 #000, 1px -1px 0 #000, -1px 1px 0 #000, 1px 1px 0 #000
|
||||
animation: rainbowTextColorAnimation 0.5s linear infinite, scaleXTextAnimation 2s infinite
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes marquee {
|
||||
0% {
|
||||
transform: translate(0, 0)
|
||||
}
|
||||
|
||||
100% {
|
||||
transform: translate(-100%, 0)
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes barrelRollAnimation {
|
||||
from {
|
||||
transform: rotate(0)
|
||||
}
|
||||
|
||||
to {
|
||||
transform: rotate(360deg)
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes wiggleAnimation {
|
||||
0% {
|
||||
transform: rotate(0)
|
||||
}
|
||||
|
||||
25% {
|
||||
transform: rotate(-15deg)
|
||||
}
|
||||
|
||||
50% {
|
||||
transform: rotate(0)
|
||||
}
|
||||
|
||||
75% {
|
||||
transform: rotate(15deg)
|
||||
}
|
||||
|
||||
100% {
|
||||
transform: rotate(0)
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes scaleXTextAnimation {
|
||||
0% {
|
||||
transform: scaleX(0.5) scaleY(0.5)
|
||||
}
|
||||
|
||||
50% {
|
||||
transform: scaleX(1) scaleY(1)
|
||||
}
|
||||
|
||||
100% {
|
||||
transform: scaleX(0.5) scaleY(0.5)
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes rainbowTextColorAnimation {
|
||||
0% {
|
||||
color: hsl(0, 100%, 50%)
|
||||
}
|
||||
|
||||
10% {
|
||||
color: hsl(36, 100%, 50%)
|
||||
}
|
||||
|
||||
20% {
|
||||
color: hsl(72, 100%, 50%)
|
||||
}
|
||||
|
||||
30% {
|
||||
color: hsl(108, 100%, 50%)
|
||||
}
|
||||
|
||||
40% {
|
||||
color: hsl(144, 100%, 50%)
|
||||
}
|
||||
|
||||
50% {
|
||||
color: hsl(180, 100%, 50%)
|
||||
}
|
||||
|
||||
60% {
|
||||
color: hsl(216, 100%, 50%)
|
||||
}
|
||||
|
||||
70% {
|
||||
color: hsl(252, 100%, 50%)
|
||||
}
|
||||
|
||||
80% {
|
||||
color: hsl(288, 100%, 50%)
|
||||
}
|
||||
|
||||
90% {
|
||||
color: hsl(324, 100%, 50%)
|
||||
}
|
||||
|
||||
100% {
|
||||
color: hsl(360, 100%, 50%)
|
||||
}
|
||||
}
|
60
website/src/.vitepress/theme/styles/forks/tachiyomi-j2k.styl
Normal file
60
website/src/.vitepress/theme/styles/forks/tachiyomi-j2k.styl
Normal file
@ -0,0 +1,60 @@
|
||||
// Assign theme color
|
||||
$themeColor = #0952af
|
||||
|
||||
.page-tachiyomi-j2k {
|
||||
.VPHero {
|
||||
h1 {
|
||||
.clip {
|
||||
--vp-home-hero-name-background: -webkit-linear-gradient(120deg, var(--vp-c-brand) 30%, var(--vp-c-brand-dark))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.VPButton {
|
||||
&.brand {
|
||||
border-color: var(--vp-button-brand-border)
|
||||
color: var(--vp-button-brand-text)
|
||||
background-color: var(--vp-button-brand-bg)
|
||||
}
|
||||
}
|
||||
|
||||
.image-bg {
|
||||
display: none
|
||||
}
|
||||
|
||||
::selection {
|
||||
background: alpha($themeColor, 0.2)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Colors
|
||||
* -------------------------------------------------------------------------- */
|
||||
|
||||
:root .page-tachiyomi-j2k {
|
||||
--vp-c-brand: $themeColor
|
||||
--vp-c-brand-light: tint($themeColor, 20%)
|
||||
--vp-c-brand-lighter: tint($themeColor, 40%)
|
||||
--vp-c-brand-lightest: tint($themeColor, 60%)
|
||||
--vp-c-brand-dark: shade($themeColor, 25%)
|
||||
--vp-c-brand-darker: shade($themeColor, 50%)
|
||||
--vp-c-brand-darkest: shade($themeColor, 75%)
|
||||
--vp-c-brand-dimm: alpha($themeColor, 0.08)
|
||||
}
|
||||
|
||||
/**
|
||||
* Component: Button
|
||||
* -------------------------------------------------------------------------- */
|
||||
|
||||
:root .page-tachiyomi-j2k {
|
||||
--vp-button-brand-border: var(--vp-c-brand-light)
|
||||
--vp-button-brand-text: var(--vp-c-white)
|
||||
--vp-button-brand-bg: var(--vp-c-brand)
|
||||
--vp-button-brand-hover-border: var(--vp-c-brand-light)
|
||||
--vp-button-brand-hover-text: var(--vp-c-white)
|
||||
--vp-button-brand-hover-bg: var(--vp-c-brand-light)
|
||||
--vp-button-brand-active-border: var(--vp-c-brand-light)
|
||||
--vp-button-brand-active-text: var(--vp-c-white)
|
||||
--vp-button-brand-active-bg: var(--vp-button-brand-bg)
|
||||
}
|
60
website/src/.vitepress/theme/styles/forks/tachiyomi-sy.styl
Normal file
60
website/src/.vitepress/theme/styles/forks/tachiyomi-sy.styl
Normal file
@ -0,0 +1,60 @@
|
||||
// Assign theme color
|
||||
$themeColor = #ce2828
|
||||
|
||||
.page-tachiyomi-sy {
|
||||
.VPHero {
|
||||
h1 {
|
||||
.clip {
|
||||
--vp-home-hero-name-background: -webkit-linear-gradient(120deg, var(--vp-c-brand) 30%, var(--vp-c-brand-dark))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.VPButton {
|
||||
&.brand {
|
||||
border-color: var(--vp-button-brand-border)
|
||||
color: var(--vp-button-brand-text)
|
||||
background-color: var(--vp-button-brand-bg)
|
||||
}
|
||||
}
|
||||
|
||||
.image-bg {
|
||||
display: none
|
||||
}
|
||||
|
||||
::selection {
|
||||
background: alpha($themeColor, 0.2)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Colors
|
||||
* -------------------------------------------------------------------------- */
|
||||
|
||||
:root .page-tachiyomi-sy {
|
||||
--vp-c-brand: $themeColor
|
||||
--vp-c-brand-light: tint($themeColor, 20%)
|
||||
--vp-c-brand-lighter: tint($themeColor, 40%)
|
||||
--vp-c-brand-lightest: tint($themeColor, 60%)
|
||||
--vp-c-brand-dark: shade($themeColor, 25%)
|
||||
--vp-c-brand-darker: shade($themeColor, 50%)
|
||||
--vp-c-brand-darkest: shade($themeColor, 75%)
|
||||
--vp-c-brand-dimm: alpha($themeColor, 0.08)
|
||||
}
|
||||
|
||||
/**
|
||||
* Component: Button
|
||||
* -------------------------------------------------------------------------- */
|
||||
|
||||
:root .page-tachiyomi-sy {
|
||||
--vp-button-brand-border: var(--vp-c-brand-light)
|
||||
--vp-button-brand-text: var(--vp-c-white)
|
||||
--vp-button-brand-bg: var(--vp-c-brand)
|
||||
--vp-button-brand-hover-border: var(--vp-c-brand-light)
|
||||
--vp-button-brand-hover-text: var(--vp-c-white)
|
||||
--vp-button-brand-hover-bg: var(--vp-c-brand-light)
|
||||
--vp-button-brand-active-border: var(--vp-c-brand-light)
|
||||
--vp-button-brand-active-text: var(--vp-c-white)
|
||||
--vp-button-brand-active-bg: var(--vp-button-brand-bg)
|
||||
}
|
99
website/src/.vitepress/theme/styles/tree.styl
Normal file
99
website/src/.vitepress/theme/styles/tree.styl
Normal file
@ -0,0 +1,99 @@
|
||||
.tree {
|
||||
border-radius: 8px
|
||||
margin: 16px
|
||||
padding: 16px
|
||||
color: var(--vp-c-text-1)
|
||||
// background-color: var(--vp-code-block-bg)
|
||||
font-family: var(--vp-font-family-mono)
|
||||
font-size: 0.85rem
|
||||
line-height: 1.5
|
||||
|
||||
& > ul {
|
||||
margin: 0
|
||||
}
|
||||
|
||||
li {
|
||||
& + li {
|
||||
margin-top: 0
|
||||
}
|
||||
}
|
||||
|
||||
span {
|
||||
&.folder {
|
||||
&.root,
|
||||
&.main {
|
||||
color: var(--vp-c-brand-darker)
|
||||
}
|
||||
}
|
||||
|
||||
&.file.zip {
|
||||
color: var(--vp-c-brand-darker) !important
|
||||
}
|
||||
|
||||
&.file {
|
||||
.file-extension {
|
||||
color: var(--vp-c-text-2)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ul {
|
||||
padding-left: 5px
|
||||
list-style: none
|
||||
|
||||
li {
|
||||
position: relative
|
||||
padding-left: 15px
|
||||
-webkit-box-sizing: border-box
|
||||
-moz-box-sizing: border-box
|
||||
box-sizing: border-box
|
||||
|
||||
&:before {
|
||||
top: 15px
|
||||
left: 0
|
||||
width: 10px
|
||||
height: 1px
|
||||
margin: auto
|
||||
}
|
||||
|
||||
&:after {
|
||||
top: 0
|
||||
bottom: 0
|
||||
left: 0
|
||||
width: 1px
|
||||
height: 100%
|
||||
}
|
||||
|
||||
&:before,
|
||||
&:after {
|
||||
position: absolute
|
||||
content: ''
|
||||
background-color: var(--vp-c-text-3)
|
||||
}
|
||||
|
||||
&:last-child {
|
||||
&:after {
|
||||
height: 15px
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&-icon {
|
||||
height: 12px
|
||||
width: 12px
|
||||
margin-right: 6px
|
||||
display: inline-block
|
||||
}
|
||||
}
|
||||
|
||||
.dark {
|
||||
.tree {
|
||||
span {
|
||||
&.folder,
|
||||
&.file.zip {
|
||||
color: var(--vp-c-brand-lighter) !important
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
5
website/src/.vitepress/vue-shim.d.ts
vendored
Normal file
5
website/src/.vitepress/vue-shim.d.ts
vendored
Normal file
@ -0,0 +1,5 @@
|
||||
declare module "*.vue" {
|
||||
import { Component } from "vue";
|
||||
const _default: Component;
|
||||
export default _default;
|
||||
}
|
18
website/src/changelogs/index.md
Normal file
18
website/src/changelogs/index.md
Normal file
@ -0,0 +1,18 @@
|
||||
---
|
||||
title: Changelogs
|
||||
description: Changelogs of all Tachiyomi stable releases.
|
||||
lastUpdated: false
|
||||
editLink: false
|
||||
prev: false
|
||||
next: false
|
||||
---
|
||||
|
||||
<script setup>
|
||||
import ChangelogsList from "@theme/components/ChangelogsList.vue";
|
||||
</script>
|
||||
|
||||
# Changelogs
|
||||
|
||||
Changelogs of all Tachiyomi stable releases, which are also available [on GitHub](https://github.com/tachiyomiorg/tachiyomi/releases). Preview releases can be seen [on GitHub](https://github.com/tachiyomiorg/tachiyomi-preview/releases).
|
||||
|
||||
<ChangelogsList />
|
30
website/src/docs/contribute.md
Normal file
30
website/src/docs/contribute.md
Normal file
@ -0,0 +1,30 @@
|
||||
---
|
||||
title: Contribute
|
||||
description: Find out how to help translate or build the app and extensions.
|
||||
---
|
||||
|
||||
# Contribute
|
||||
Find out how to help translate or build the app and extensions.
|
||||
|
||||
## Code
|
||||
Know how to code and want to improve something or you generally want to support the creation of the app?
|
||||
|
||||
[![tachiyomiorg/tachiyomi - GitHub](https://gh-card.dev/repos/tachiyomiorg/tachiyomi.svg)](https://github.com/tachiyomiorg/tachiyomi)
|
||||
|
||||
[![tachiyomiorg/tachiyomi-extensions - GitHub](https://gh-card.dev/repos/tachiyomiorg/tachiyomi-extensions.svg)](https://github.com/tachiyomiorg/tachiyomi-extensions)
|
||||
|
||||
[![tachiyomiorg/website - GitHub](https://gh-card.dev/repos/tachiyomiorg/website.svg)](https://github.com/tachiyomiorg/website)
|
||||
|
||||
## Translation
|
||||
![Graph of Weblate Translations](https://hosted.weblate.org/widgets/tachiyomi/-/strings/open-graph.png)
|
||||
|
||||
Want to help translate the app to your language? You can easily help by utilizing a service we use called **Weblate**.
|
||||
|
||||
### Helpful links
|
||||
* [Translators guide](https://docs.weblate.org/en/latest/user/translating.html)
|
||||
* [Secondary languages](https://docs.weblate.org/en/latest/user/profile.html#secondary-languages)
|
||||
* [Subscriptions](https://docs.weblate.org/en/latest/user/profile.html#subscriptions)
|
||||
* [Glossary](https://docs.weblate.org/en/latest/user/translating.html#glossary)
|
||||
|
||||
## Donation
|
||||
If you're unable to contribute code nor translations but still wish to help, then you can choose to contribute directly to the projects founder [Inorichi](https://github.com/inorichi/) using [Ko-fi](https://ko-fi.com/inorichi).
|
33
website/src/docs/faq/android-11+.md
Normal file
33
website/src/docs/faq/android-11+.md
Normal file
@ -0,0 +1,33 @@
|
||||
---
|
||||
title: Android 11+
|
||||
titleTemplate: Frequently Asked Questions
|
||||
description: Understanding Android 11 Changes.
|
||||
---
|
||||
|
||||
# Android 11+
|
||||
Understanding **Android 11** Changes.
|
||||
|
||||
## Android 11 modifications
|
||||
**Android 11** introduced changes related to [Scoped Storage](https://developer.android.com/about/versions/11/privacy/storage), which was initially introduced in **Android 5.0 Lollipop**.
|
||||
|
||||
However, the enforcement of **Scoped Storage** was initiated by **Google** with **Android 11**.
|
||||
|
||||
Different smartphone manufacturers implement **Scoped Storage** with varying levels of success, resulting in varying user experiences.
|
||||
Some encounter minimal issues, while others using different phone brands face numerous challenges.
|
||||
|
||||
## Implications for Tachiyomi
|
||||
**Scoped Storage**'s introduction affects various storage-related functions in **Tachiyomi**.
|
||||
These functions may become slower due to **Scoped Storage**'s inherent latency, as discussed in detail [here](https://www.xda-developers.com/android-q-storage-access-framework-scoped-storage/).
|
||||
|
||||
This impact encompasses tasks like deleting chapters, library loading times, accessing folders outside data directories for downloads and reading, and more.
|
||||
|
||||
## Enhancing performance
|
||||
For potential performance improvements, consider utilizing the following ADB command if you are comfortable with it.
|
||||
|
||||
```bash
|
||||
adb shell cmd appops set eu.kanade.tachiyomi android:legacy_storage allow
|
||||
```
|
||||
|
||||
This command grants general storage access to the app, enabling **Tachiyomi** to use the conventional storage access interface.
|
||||
|
||||
If using **Tachiyomi Preview**, or a fork, replace `eu.kanade.tachiyomi` with the corresponding package name.
|
72
website/src/docs/faq/browse/extensions.md
Normal file
72
website/src/docs/faq/browse/extensions.md
Normal file
@ -0,0 +1,72 @@
|
||||
---
|
||||
title: Extensions
|
||||
titleTemplate: Browse - Frequently Asked Questions
|
||||
description: Frequently Asked Questions about Extensions.
|
||||
---
|
||||
|
||||
# Extensions
|
||||
Frequently Asked Questions about Extensions.
|
||||
|
||||
## What are some recommended extensions and sources?
|
||||
**Tachiyomi** does not endorse or recommend any source, and there is no best extension.
|
||||
|
||||
Instead, we encourage users to spend some time trying out a few sources themselves and discover what sources work best for them.
|
||||
What works well for somebody else might not work well for you.
|
||||
|
||||
::: info Disclaimer
|
||||
**Tachiyomi** isn't responsible for slow, down, missing chapters, or subpar image quality of sources as it doesn't host content.
|
||||
:::
|
||||
|
||||
## What is a scanlator source?
|
||||
Non-officially licensed series are translated by scanlators, often found on their websites or [MangaDex](https://mangadex.org/).
|
||||
Compare with official sources like [MANGA Plus By SHUEISHA](https://mangaplus.shueisha.co.jp) or [VIZ Shonen Jump](https://www.viz.com/shonenjump).
|
||||
Learn more in this [Wikipedia article](https://en.wikipedia.org/wiki/Scanlation).
|
||||
|
||||
## How do I request new extensions?
|
||||
[Open an issue](https://github.com/tachiyomiorg/tachiyomi-extensions/issues) on **GitHub** if not already there.
|
||||
Check the removed [extensions list](https://github.com/tachiyomiorg/tachiyomi-extensions/blob/master/REMOVED_SOURCES.md) first.
|
||||
|
||||
You can find the list of extensions to download [here](/extensions/)
|
||||
|
||||
## Enabling third-party installations
|
||||
::::tabs
|
||||
== Android 8.0 and higher
|
||||
When prompted while installing your first extension, allow unknown apps installation from that source. For newer Androids, enable per-app in **Install unknown apps**.
|
||||
|
||||
::: details Video guide - Recorded on Android 10
|
||||
<video controls muted preload="metadata">
|
||||
<source src="/docs/faq/browse/extensions/unknown-sources-A10.light.webm" type="video/webm">
|
||||
</video>
|
||||
:::
|
||||
|
||||
::: tip Still got questions?
|
||||
If you need more help regarding this, read [this post](https://nerdschalk.com/how-to-allow-apps-installation-from-unknown-sources-on-android-9-pie/ "nerdschalk.com | How to allow apps installation from unknown sources on Android 9 Pie").
|
||||
:::
|
||||
== Android 7.1 and lower
|
||||
When prompted while installing your first extension, allow unknown apps installation from that source. For older Androids, enable globally in **Unknown sources**.
|
||||
|
||||
::: details Video guide - Recorded on Android 7
|
||||
<video controls muted preload="metadata">
|
||||
<source src="/docs/faq/browse/extensions/unknown-sources-A7.light.webm">
|
||||
</video>
|
||||
:::
|
||||
::::
|
||||
|
||||
## How do I uninstall an extension?
|
||||
Uninstall extensions like regular apps: through device settings or in **Tachiyomi**.
|
||||
|
||||
::: tip Uninstalling an extension
|
||||
In **Tachiyomi**, uninstall an extension via <nav to="extensions">, then tap **Uninstall** on the chosen extension.
|
||||
:::
|
||||
|
||||
## Why was an extension removed?
|
||||
Extensions can be removed due to several reasons:
|
||||
|
||||
* Frequent website changes.
|
||||
* Image scrambling.
|
||||
* Scanlator removal request.
|
||||
* Paywall implementation.
|
||||
* Reverse engineering needs.
|
||||
* Site shutdown.
|
||||
|
||||
Find the list of removed extensions [here](https://github.com/tachiyomiorg/tachiyomi-extensions/blob/master/REMOVED_SOURCES.md), excluding offline sites.
|
37
website/src/docs/faq/browse/index.md
Normal file
37
website/src/docs/faq/browse/index.md
Normal file
@ -0,0 +1,37 @@
|
||||
---
|
||||
title: Browse
|
||||
titleTemplate: Frequently Asked Questions
|
||||
description: Frequently Asked Questions about Browse.
|
||||
---
|
||||
|
||||
# Browse
|
||||
Frequently Asked Questions about Browse.
|
||||
|
||||
## Why can't I see installed sources?
|
||||
|
||||
### If the extension language differs from your device's primary language
|
||||
Enable the source's language at <nav to="sources">, tap on **Filter**, then turn on the language of the desired source.
|
||||
|
||||
### If it's an NSFW extension
|
||||
Navigate to <nav to="browse"> and check the **Show in sources and extensions list** option.
|
||||
|
||||
## How can I locate a specific series?
|
||||
At times, locating a source containing the series you want to read can be challenging.
|
||||
|
||||
Here are strategies to help you find it:
|
||||
|
||||
1. Search for the series on [Google](https://google.com/) or a database like [MangaUpdates](https://www.mangaupdates.com/).
|
||||
1. Consider alternate spellings or title variations for the series.
|
||||
- Retry searching your sources using the alternate titles you discovered.
|
||||
1. If you still can't find the series on any source:
|
||||
- Search for an extension for the website you found in *step 1* within the [extensions list](/extensions/).
|
||||
|
||||
### If you find the series on a specific scanlator or aggregator with an extension
|
||||
Download the relevant extension, locate it under <nav to="sources">, and proceed to search for the series there.
|
||||
|
||||
### If you find the series on a specific scanlator or aggregator without an extension
|
||||
Check if a request has been made to add the site as an extension [here](https://github.com/tachiyomiorg/tachiyomi-extensions/issues) and confirm it's not on the [list of extensions that won't be added back](https://github.com/tachiyomiorg/tachiyomi-extensions/blob/master/REMOVED_SOURCES.md).
|
||||
|
||||
If it's not requested and not on the exclusion list, you can [submit a request here](https://github.com/tachiyomiorg/tachiyomi-extensions/issues/new/choose).
|
||||
|
||||
For scanlators or aggregators without an online reader, you can download the series from their website and set it up as a local series.
|
29
website/src/docs/faq/browse/local-source.md
Normal file
29
website/src/docs/faq/browse/local-source.md
Normal file
@ -0,0 +1,29 @@
|
||||
---
|
||||
title: Local source
|
||||
titleTemplate: Browse - Frequently Asked Questions
|
||||
description: Frequently Asked Questions about the Local source.
|
||||
---
|
||||
|
||||
# Local source
|
||||
Frequently Asked Questions about the Local source.
|
||||
|
||||
## How can I import my downloaded series?
|
||||
For a step-by-step process, we recommend referring to [this guide](/docs/guides/local-source/).
|
||||
|
||||
## What should I do if I can't find the Tachiyomi folder?
|
||||
In case the **Tachiyomi** folder is not visible, create one using a file manager.
|
||||
|
||||
## How can I resolve empty/blank covers?
|
||||
Occasionally, cover images for local series might not appear.
|
||||
|
||||
1. Confirm that you've organized the folder structure correctly.
|
||||
* To verify, access the series with the missing cover and see if you can read chapters within the app.
|
||||
* If not, follow the [provided guide](/docs/guides/local-source/#folder-structure) first.
|
||||
1. Capture a screenshot of the read chapters, then remove the series from your library.
|
||||
1. Navigate to <nav to="advanced"> and tap **Clear database**.
|
||||
* This action will only affect series not in your library.
|
||||
2. Return to <nav to="sources">, go to **Local source** and locate the series.
|
||||
* The cover issue should now be resolved.
|
||||
* Re-add the series to your library, mark your read chapters, and re-enable tracking if necessary.
|
||||
|
||||
Related GitHub Issue: [#932](https://github.com/tachiyomiorg/tachiyomi/issues/932)
|
44
website/src/docs/faq/downloads.md
Normal file
44
website/src/docs/faq/downloads.md
Normal file
@ -0,0 +1,44 @@
|
||||
---
|
||||
title: Downloads
|
||||
titleTemplate: Frequently Asked Questions
|
||||
description: Frequently Asked Question about Downloads.
|
||||
---
|
||||
|
||||
# Downloads
|
||||
Frequently Asked Question about Downloads.
|
||||
|
||||
## How do I download multiple chapters or series at the same time?
|
||||
The app does not support parallel downloads from a single source to prevent potential IP bans due to excessive requests.
|
||||
While this might impact speed, it's preferable over rendering a source inaccessible.
|
||||
|
||||
Note that Tachiyomi will download from up to five different sources in parallel.
|
||||
|
||||
## Why did my downloads stop midway?
|
||||
Downloads stopping midway may be related to network connection issues or source problems.
|
||||
**Tachiyomi** will provide notifications regarding encountered errors during download attempts.
|
||||
|
||||
## Why can't I see my downloads?
|
||||
Downloads might not be detected due to multiple factors:
|
||||
|
||||
* Inaccessibility of the download location.
|
||||
> Ensure the SD card is properly detected if in use.
|
||||
* Source name changes.
|
||||
> Rename the source's folder to match the new name.
|
||||
* Series title modified by the source.
|
||||
> Adjust the folder title to the updated name.
|
||||
|
||||
## How do I manage what's downloading?
|
||||
Navigate to <nav to="download-queue"> to interact with queued downloads.
|
||||
|
||||
Cancel all items by clicking the **Overflow** button beside a series chapter or the top right corner.
|
||||
|
||||
To reorder the queue, long-press and drag the `=` icon next to a queue item.
|
||||
|
||||
## Can I use both internal storage and external SD card storage?
|
||||
No, you must choose a single location. Internal storage performs better than external SD cards.
|
||||
|
||||
## Why does my device photo gallery contain series pages?
|
||||
**Tachiyomi** typically prevents series pages in downloads from appearing in your device's photo gallery by default through a `.nomedia` file.
|
||||
However, in some cases, this might not function as intended.
|
||||
|
||||
A quick solution is to create the `.nomedia` file yourself, name it as such, and place it in your downloads folder. If the issue pertains to local source, put the `.nomedia` file in the respective local folder.
|
75
website/src/docs/faq/general.md
Normal file
75
website/src/docs/faq/general.md
Normal file
@ -0,0 +1,75 @@
|
||||
---
|
||||
title: General
|
||||
titleTemplate: Frequently Asked Questions
|
||||
description: Frequently Asked Questions
|
||||
---
|
||||
|
||||
# General
|
||||
Frequently Asked Questions
|
||||
|
||||
## Why isn't Tachiyomi on the Google Play Store?
|
||||
**Tachiyomi** won't be on the **Google Play Store**.
|
||||
|
||||
APK-based extensions conflict with [Google Play's content policy](https://play.google.com/about/developer-content-policy/).
|
||||
**Google** might take down the app due to certain content, which the developers wishes to avoid.
|
||||
|
||||
To report **Tachiyomi** copycats on the **Google Play Store**, fill out [this form](https://support.google.com/googleplay/android-developer/contact/takedown) following the steps below.
|
||||
|
||||
:::details Steps to report Tachiyomi copycats
|
||||
1. Go to the page -> three dots menu -> Flag as inappropriate -> Other objection.
|
||||
1. After choosing "Other objection", you may choose to put down any or all of the following:
|
||||
|
||||
The app infringes on the Google Play Developer Policy by
|
||||
|
||||
Encouraging Infringement of Copyright
|
||||
|
||||
> We don’t allow apps that induce or encourage copyright infringement. Before you publish your app, look for ways your app may be encouraging copyright infringement and get legal advice if necessary.
|
||||
|
||||
Trademark Infringement
|
||||
|
||||
> We don’t allow apps that infringe on others’ trademarks. A trademark is a word, symbol, or combination that identifies the source of a good or service. Once acquired, a trademark gives the owner exclusive rights to the trademark usage with respect to certain goods or services.
|
||||
>
|
||||
> Trademark infringement is an improper or unauthorized use of an identical or similar trademark in a way that is likely to cause confusion as to the source of that product. If your app uses another party’s trademarks in a way that is likely to cause confusion, your app may be suspended.
|
||||
|
||||
Misleading Claims, e.g. "MangaKa - Best Manga Reader"
|
||||
|
||||
> We don’t allow apps that contain false or misleading information or claims, including in the description, title, icon, and screenshots. Here are some examples of common violations: Apps that misrepresent or do not accurately and clearly describe their functionality:
|
||||
> - Developer or app names that misrepresent their current status or performance on Play. (E.g. “Editor’s Choice,” “Number 1 App,” “Top Paid”)...
|
||||
> - Apps that are improperly categorized.
|
||||
:::
|
||||
|
||||
## Is Tachiyomi available for iOS/iPadOS?
|
||||
There is no iOS or iPadOS version and neither are there plans for one.
|
||||
Porting is difficult due to the separate codebases of iOS and Android apps.
|
||||
|
||||
Any app proclaiming to be "**Tachiyomi for iOS**" is not by us and should be treated as a scam.
|
||||
|
||||
## What is Tachiyomi Preview?
|
||||
**Tachiyomi Preview** is a regularly updated beta version of the app.
|
||||
It showcases potential upcoming features, but it's more prone to bugs and crashes.
|
||||
|
||||
Ideal for users seeking the latest **Tachiyomi** experience, it's essential to [enable auto-backup](/docs/guides/backups#enabling-automatic-backups) to prevent library loss due to issues.
|
||||
|
||||
## How do I update from the F-Droid builds?
|
||||
**Tachiyomi** on **F-Droid** lacks official support, updates aren't guaranteed.
|
||||
|
||||
To migrate to official builds:
|
||||
|
||||
1. Create a backup.
|
||||
1. Uninstall the **F-Droid** version (discard app data if possible).
|
||||
1. Download/install **Tachiyomi**.
|
||||
1. Restore your backup.
|
||||
|
||||
Hosting an **F-Droid** repo isn't planned, as **Tachiyomi** manages updates independently, rendering **F-Droid** usage redundant.
|
||||
Refer to [this GitHub comment](https://github.com/tachiyomiorg/tachiyomi/issues/6736#issuecomment-1059608058) for details.
|
||||
|
||||
## Can I read light novels?
|
||||
**Tachiyomi** can't read light novels; it's an image parser, not a text parser.
|
||||
|
||||
## Can I stream anime?
|
||||
**Tachiyomi** isn't designed for anime streaming.
|
||||
|
||||
Projects using the **Tachiyomi** name for anime streaming aren't affiliated with the main project.
|
||||
|
||||
## What's a fork?
|
||||
Forks are alternate **Tachiyomi** versions with distinct features. Get more details [here](/forks/).
|
75
website/src/docs/faq/library.md
Normal file
75
website/src/docs/faq/library.md
Normal file
@ -0,0 +1,75 @@
|
||||
---
|
||||
title: Library
|
||||
titleTemplate: Frequently Asked Questions
|
||||
description: Frequently Asked Questions about the Library.
|
||||
---
|
||||
|
||||
# Library
|
||||
Frequently Asked Questions about the Library.
|
||||
|
||||
## Why is Global Update skipping entries?
|
||||
The app's default behavior is to skip updates for entries that meet the following criteria:
|
||||
|
||||
* Have unread chapters
|
||||
* Haven't been initiated
|
||||
* Carry a "**Completed**" status
|
||||
|
||||
This strategy prevents unnecessary load which may lead to sources implementing measures against **Tachiyomi**.
|
||||
To manage entries with infrequent or no updates, consider using categories and excluding them from updates.
|
||||
|
||||
We recommend using the default settings and prioritizing unread chapters for reading.
|
||||
|
||||
If you wish to disable the notification about skipped items, you can do so at <nav to="advanced"> and then **Manage notifications** (doing so requires Android 8 or above).
|
||||
|
||||
## Why am I warned about large bulk updates and downloads?
|
||||
Excessive server load may trigger anti-**Tachiyomi** measures from sources. See the previous question for more context. Long-running update checks and downloads may also impact your device's battery life.
|
||||
|
||||
To mitigate these concerns:
|
||||
|
||||
* Use categories to segment your library ("Reading", "Plan to Read", "Completed", etc.).
|
||||
* Update only the Reading category by navigating to <nav to="library">, then tap **Categories** under **Global update**.
|
||||
* If the warning persists, create a new category for infrequently updated entries (like monthly series or those on hiatus) and set global updates to target the more frequently updated reading category.
|
||||
|
||||
## Why aren't library updates working?
|
||||
Some Android skins (e.g., **MIUI**) aggressively save battery, potentially shutting down apps in the background.
|
||||
|
||||
Whitelist **Tachiyomi** from your battery saver by going to <nav to="advanced"> and tapping **Disable battery optimization**.
|
||||
|
||||
If unsuccessful, refer to [Don't Kill My App](https://dontkillmyapp.com/) for how to disable specific battery-saving options for your device.
|
||||
|
||||
## How can I see the number of downloaded chapters?
|
||||
Badges can be enabled by navigating to <nav to="main_library">, then going to **Filter** and clicking the **Display** tab.
|
||||
Then, at the bottom, select **Download badges**.
|
||||
|
||||
## Can I sync between multiple devices?
|
||||
**Tachiyomi** can't sync between devices.
|
||||
Use its [backup and restore](/docs/guides/backups) features, including [auto backups](/docs/guides/backups#enabling-automatic-backups), for series database and content migration to another device.
|
||||
|
||||
## How can I ignore duplicate chapters?
|
||||
Dealing with series translated by multiple groups that result in duplicate chapter releases?
|
||||
|
||||
Bookmark or mark as read the undesired chapters, then open the **Filter** menu, ensure you're on the **Filter** tab, then double-tap **Bookmarked** or single-tap **Unread**.
|
||||
|
||||
This hides bookmarked or read chapters, enabling you to skip them as you read.
|
||||
Ensure [Skip filtered chapters](/docs/guides/reader-settings#skip-filtered-chapters) is enabled at <nav to="reader"> under the section **Reading**.
|
||||
|
||||
Alternatively, migrate to a source without duplicates.
|
||||
Refer to the [migration guide](/docs/guides/source-migration) for detailed instructions.
|
||||
|
||||
## Why are some cover thumbnails corrupted or blank?
|
||||
If cover thumbnails appear corrupted, blank, or broken, it's likely due to an incomplete download. Fix this by refreshing the covers in settings.
|
||||
|
||||
Refresh your covers at <nav to="advanced"> then tap **Refresh library covers**.
|
||||
|
||||
## Why have some chapters been marked as unread?
|
||||
If certain series chapters are marked as unread without your interaction, it could be due to changed URLs.
|
||||
**Tachiyomi** detects these changes and interprets the chapters as new.
|
||||
|
||||
## How do I pause reading history?
|
||||
Disable **Incognito Mode** through <nav to="incognito-mode">.
|
||||
|
||||
## How do I only read downloaded chapters?
|
||||
Enable **Download only** via <nav to="downloaded-only">.
|
||||
|
||||
## Why can't I disable the Downloaded filter?
|
||||
Disable **Download only** via <nav to="downloaded-only">.
|
18
website/src/docs/faq/reader.md
Normal file
18
website/src/docs/faq/reader.md
Normal file
@ -0,0 +1,18 @@
|
||||
---
|
||||
title: Reader
|
||||
titleTemplate: Frequently Asked Questions
|
||||
description: Frequently Asked Questions about the Reader.
|
||||
---
|
||||
|
||||
# Reader
|
||||
Frequently Asked Questions about the Reader.
|
||||
|
||||
## Why didn't the page load?
|
||||
Besides network-related problems, **Tachiyomi** may occasionally fail to recognize certain images.
|
||||
To address this, simply exit and re-enter the reader, often resolving the issue.
|
||||
|
||||
## Can I see two pages at once?
|
||||
Not currently. Creating an effective dual-page reader that accommodates scanlator page inconsistencies and other complexities poses challenges. This feature may be added in the future.
|
||||
|
||||
## What do all the settings do?
|
||||
For detailed instructions, please consult the guides section on the website [here](/docs/guides/reader-settings).
|
24
website/src/docs/faq/settings.md
Normal file
24
website/src/docs/faq/settings.md
Normal file
@ -0,0 +1,24 @@
|
||||
---
|
||||
title: Settings
|
||||
titleTemplate: Frequently Asked Questions
|
||||
description: Frequently Asked Questions about various app settings.
|
||||
---
|
||||
|
||||
# Settings
|
||||
Frequently Asked Questions about various app settings.
|
||||
|
||||
## Why is taking screenshots blocked?
|
||||
Turn off **Secure Screen** in <nav to="security-and-privacy">.
|
||||
|
||||
## What is DNS over HTTPS?
|
||||
**DNS over HTTPS (DoH)** (in <nav to="advanced">) offers secure DNS resolution through HTTPS, preventing attacks.
|
||||
Learn more [here](https://www.cloudflare.com/learning/dns/dns-over-tls/). This may help bypass some basic website blocking.
|
||||
|
||||
## What are the different installers?
|
||||
Three installer options are available (in <nav to="advanced">):
|
||||
|
||||
* **Legacy**: A fallback installer if the standard **PackageInstaller** doesn't work.
|
||||
> This is the default for **MIUI** (i.e. Xiaomi devices).
|
||||
* **PackageInstaller**: The primary installer option with additional features dependent on the Android version.
|
||||
> For instance, it can bypass user prompts when updating extensions on **Android 12**.
|
||||
* **Shizuku**: Refer to the [Shizuku guide](/docs/guides/shizuku) for details on **Shizuku**'s functionality.
|
112
website/src/docs/guides/backups.md
Normal file
112
website/src/docs/guides/backups.md
Normal file
@ -0,0 +1,112 @@
|
||||
---
|
||||
title: Backups
|
||||
titleTemplate: Guides
|
||||
description: Backups helps you prevent losing your library if something happens.
|
||||
---
|
||||
|
||||
# Backups
|
||||
|
||||
Backups in **Tachiyomi** are compatible between different versions of the app.
|
||||
|
||||
::: tip How to create a backup
|
||||
1. Go to <nav to="backup-and-restore">.
|
||||
1. Select **Create backup** and choose a location to save it.
|
||||
|
||||
![Backup and Restore](/docs/guides/backups/backup.light.webp#light =414x215)
|
||||
|
||||
![Backup and Restore](/docs/guides/backups/backup.dark.webp#dark =414x215)
|
||||
:::
|
||||
|
||||
## General backup details
|
||||
|
||||
### What's included in a backup?
|
||||
|
||||
Backups store the following information:
|
||||
|
||||
- **Titles**
|
||||
- **Categories**
|
||||
- **Read chapters** for titles in Library
|
||||
- **Tracking settings**
|
||||
- **Reading history**
|
||||
- **Series information**
|
||||
- Author, Artist, Date Added to Library, Selected Viewer, Read Duration, etc.
|
||||
- **Extensions** used
|
||||
|
||||
### What's NOT included in a backup?
|
||||
|
||||
Backups do NOT store:
|
||||
|
||||
- **Reading history** of titles NOT in library
|
||||
- **Settings** including app settings and extension-specific settings
|
||||
- **Downloaded** chapters including [local source](/docs/guides/local-source/) chapters
|
||||
|
||||
## Restoring a backup
|
||||
|
||||
Restoring a backup can be done through the Backup and Restore settings.
|
||||
|
||||
To ensure a smooth restoration process, remember to:
|
||||
|
||||
1. Log into the Tracking services you previously used.
|
||||
1. Download any extensions you've used in your backup.
|
||||
|
||||
Before starting to import the selected backup, the app will remind the user of these.
|
||||
|
||||
### Transferring downloads to a new installation
|
||||
|
||||
You can transfer downloaded series chapters from one version of **Tachiyomi** to another
|
||||
by correctly specifying the Download Location.
|
||||
|
||||
## Suggestions for backups
|
||||
|
||||
### Enabling automatic backups
|
||||
|
||||
It is highly recommended to enable automatic backups to ensure you can recover in case of any issues.
|
||||
|
||||
::: tip How to enable automatic backups
|
||||
1. Go to <nav to="backup-and-restore">.
|
||||
1. Set a **backup frequency** to schedule automatic backups.
|
||||
This way, you can recover from catastrophic failures.
|
||||
|
||||
![Automatic Backups](/docs/guides/backups/automatic_backups.light.webp#light =414x402)
|
||||
|
||||
![Automatic Backups](/docs/guides/backups/automatic_backups.dark.webp#dark =414x402)
|
||||
:::
|
||||
|
||||
### Syncing backups with external cloud services
|
||||
|
||||
Cross device sync in **Tachiyomi** is not planned in the future, but users can use
|
||||
[Autosync for Google Drive](https://play.google.com/store/apps/details?id=com.ttxapps.drivesync)
|
||||
in order to sync backup files to Drive automatically with the following steps:
|
||||
|
||||
1. Install the app from the link above
|
||||
|
||||
2. Enable [Automatic Backups](/docs/guides/backups#enabling-automatic-backups) and set it to your desired frequency and storage location.
|
||||
|
||||
3. Download the latest backup from Google Drive and restore to whatever device you have
|
||||
|
||||
Users who are familiar with [FolderSync](https://play.google.com/store/apps/details?id=dk.tacit.android.foldersync.lite)
|
||||
or [Tasker](https://play.google.com/store/apps/details?id=net.dinglisch.android.taskerm) can setup auto sync of their backups similarly
|
||||
|
||||
## Additional information for forks
|
||||
|
||||
::: warning
|
||||
This section explores some extra details regarding [forks](/forks/).
|
||||
:::
|
||||
|
||||
All [Endorsed Tachiyomi forks](/forks/) support the `.proto.gz` format to backup/restore your library
|
||||
|
||||
In addition, some forks have specific limitations regarding backup restoration:
|
||||
|
||||
- [Neko](/forks/Neko/) can only restore [MangaDex](/extensions/#all.mangadex) entries in a backup.
|
||||
> Entries from other sources will not transfer.
|
||||
|
||||
> Migrate everything to MangaDex if you require to.
|
||||
- All forks have fork-specific settings and changes that might be saved in Backups.
|
||||
Such settings are not restored in original Tachiyomi and will get lost when creating a new backup.
|
||||
> For Example: [TachiyomiSY](/forks/TachiyomiSY/) has the option to backup/restore saved searches.
|
||||
|
||||
These will **NOT** be restored to original Tachiyomi or its forks.
|
||||
- Only [TachiyomiAZ](/forks/TachiyomiAZ/) supports creating/restoring legacy `.json` backups and current `.proto.gz` backups.
|
||||
> Users are recommended to update their `.json` backups to use the improved and efficient `.proto.gz` backups.
|
||||
|
||||
Be aware of these limitations when dealing with backups in different **Tachiyomi** forks.
|
44
website/src/docs/guides/categories.md
Normal file
44
website/src/docs/guides/categories.md
Normal file
@ -0,0 +1,44 @@
|
||||
---
|
||||
title: Categories
|
||||
titleTemplate: Guides
|
||||
description: Organize your favorite series effortlessly with categories that declutter and structure your library.
|
||||
---
|
||||
|
||||
# Categories
|
||||
|
||||
Organize your favorite series effortlessly with categories that declutter and structure your library.
|
||||
|
||||
To manage your categories, navigate to <nav to="categories">.
|
||||
|
||||
- You can name and sort categories as you prefer (e.g., by `Genre`, `Reading Status`).
|
||||
- Add series to multiple categories and control update options through Library settings, even auto-download chapters from chosen categories.
|
||||
> If you've enabled **Download new chapters** in the Downloads settings.
|
||||
|
||||
|
||||
## Content
|
||||
|
||||
Categories would be useless without any content in them.
|
||||
Below are some tips for using them.
|
||||
|
||||
:::: tabs
|
||||
== Add entries
|
||||
### Add series to a category
|
||||
|
||||
1. Long press the series you want to add.
|
||||
1. Press the **Set categories** button.
|
||||
1. Select which category or categories you want it in and press **OK**.
|
||||
|
||||
::: tip
|
||||
You can also add multiple series to a category by selecting them when you see the **Set categories** button.
|
||||
:::
|
||||
== Remove entries
|
||||
### Remove series from a category
|
||||
|
||||
1. Long press series that you want to remove.
|
||||
1. Press the **Set categories** button.
|
||||
1. Deselect the category or categories you want to remove it from and press **OK**.
|
||||
|
||||
::: tip
|
||||
You can also remove multiple series from a category by selecting them when you see the **Set categories** button.
|
||||
:::
|
||||
::::
|
78
website/src/docs/guides/getting-started.md
Normal file
78
website/src/docs/guides/getting-started.md
Normal file
@ -0,0 +1,78 @@
|
||||
---
|
||||
title: Getting started
|
||||
titleTemplate: Guides
|
||||
description: Essential information to help you get set up with Tachiyomi.
|
||||
---
|
||||
|
||||
<script setup lang="ts">
|
||||
import { data as release } from "@theme/data/release.data"
|
||||
</script>
|
||||
|
||||
# Getting started
|
||||
|
||||
Essential information to help you get set up with Tachiyomi.
|
||||
|
||||
## Installation guide
|
||||
|
||||
### 1. Downloading Tachiyomi
|
||||
|
||||
1. Visit our [download](/download/) page to get the latest version of **Tachiyomi**.
|
||||
1. After the download is complete, open the `tachiyomi-{{ release.stable.tag_name }}.apk` file.
|
||||
1. Proceed with the installation process.
|
||||
|
||||
### 2. Adding sources
|
||||
|
||||
Once **Tachiyomi** is installed on your device, you can install extensions to access a wide range of sources.
|
||||
|
||||
1. Open the **Tachiyomi** app.
|
||||
1. Navigate to the "**Browse**" section.
|
||||
1. Tap on the "**Extensions**" tab.
|
||||
1. Look for the extension you want to use and press the "**INSTALL**" button next to it.
|
||||
1. Proceed with the installation process.
|
||||
|
||||
![Installing extensions](/docs/guides/getting-started/installing-extensions.light.webp#light =414x245)
|
||||
|
||||
![Installing extensions](/docs/guides/getting-started/installing-extensions.dark.webp#dark =414x245)
|
||||
|
||||
::: tip INSTALL PERMISSION
|
||||
Depending on your device settings, you might need to grant **Tachiyomi** permission to install unknown apps.
|
||||
|
||||
![Security warning](/docs/guides/getting-started/security-warning.webp =546x165)
|
||||
|
||||
> Relevant guide: [Enabling Third-Party Installations](/docs/faq/browse/extensions#enabling-third-party-installations)
|
||||
:::
|
||||
|
||||
### 3. Adding series to your library
|
||||
|
||||
After installing the desired extension, you'll find it in the **Sources** tab.
|
||||
|
||||
Here's how you can add series to your library:
|
||||
|
||||
1. Select the source you'd like to browse.
|
||||
1. You can use the **Popular**/**Latest** listings to browse, or you can search for the series name.
|
||||
1. Once you've found the series that you want to add, tap on it for more details.
|
||||
1. Press the "**Add to library**" button, and the series will be added to your Library, ready to be read!
|
||||
|
||||
## Additional setup
|
||||
|
||||
### Series search options
|
||||
|
||||
If you want to search for series across all your sources, you can use the Global Search feature.
|
||||
|
||||
Follow these steps:
|
||||
|
||||
1. Go to the "**Browse**" section.
|
||||
1. Ensure you're on the "**Sources**" tab located at the top-right corner.
|
||||
1. Use the Search icon in the toolbar to find series from all available sources.
|
||||
|
||||
### Trouble finding a specific series?
|
||||
|
||||
If you encounter difficulties while searching for a specific series, consider the following points:
|
||||
|
||||
* Double-check your spelling and try again, as some sources might use **Japanese romanized** titles instead of **English** ones.
|
||||
> Example: **Boku no Hero Academia** instead of **My Hero Academia**.
|
||||
|
||||
* Some sources may use different spellings or wordings for titles.
|
||||
> Example: **Bungo Stray Dogs** instead of **Bungou Stray Dogs**
|
||||
|
||||
> Example: **3-gatsu no Lion** instead of **Sangatsu no Lion**.
|
49
website/src/docs/guides/local-source/advanced.md
Normal file
49
website/src/docs/guides/local-source/advanced.md
Normal file
@ -0,0 +1,49 @@
|
||||
---
|
||||
title: Advanced editing
|
||||
titleTemplate: Local source - Guides
|
||||
description: Advanced local series metadata editing for enhanced library organization.
|
||||
---
|
||||
|
||||
# Advanced editing
|
||||
Advanced local series metadata editing for enhanced library organization.
|
||||
|
||||
## Editing local series details
|
||||
|
||||
It is possible to add details to local series.
|
||||
Like series from other sources, you add information about the series such as the `author`, `artist`, `description`, and `genre` tags.
|
||||
|
||||
To import details along with your local series, you have to create a JSON file.
|
||||
It can be named anything but it must be placed within the **Series** folder.
|
||||
A standard file name is `details.json`.
|
||||
This file will contain the extended details about the series in the `JSON` format.
|
||||
You can see the example below on how to build the file.
|
||||
Once the file is there, the app should load the data when you first open the series or you can pull down to refresh the details.
|
||||
|
||||
You can copy the following example and edit the details as needed:
|
||||
|
||||
```json
|
||||
{
|
||||
"title": "Example Title",
|
||||
"author": "Example Author",
|
||||
"artist": "Example Artist",
|
||||
"description": "Example Description",
|
||||
"genre": ["genre 1", "genre 2", "etc"],
|
||||
"status": "0",
|
||||
"_status values": ["0 = Unknown", "1 = Ongoing", "2 = Completed", "3 = Licensed", "4 = Publishing finished", "5 = Cancelled", "6 = On hiatus"]
|
||||
}
|
||||
```
|
||||
::: tip
|
||||
If you don't want to manually create the `details.json` file, you can alternatively use [this tool.](https://tachi-local.netlify.app/?utm\_source=tachi-website\&utm\_medium=referral\&utm\_campaign=tachi-website)
|
||||
:::
|
||||
|
||||
## Using a custom cover image
|
||||
|
||||
It is also possible to use a custom image as a cover for each local series.
|
||||
|
||||
To do this, you only need to place the image file, that needs to be named `cover.jpg`, in the root of the series folder.
|
||||
The app will then use your custom image in the local source listing.
|
||||
|
||||
|
||||
<style scoped>
|
||||
@import "../../../.vitepress/theme/styles/tree.styl"
|
||||
</style>
|
204
website/src/docs/guides/local-source/index.md
Normal file
204
website/src/docs/guides/local-source/index.md
Normal file
@ -0,0 +1,204 @@
|
||||
---
|
||||
title: Local source
|
||||
titleTemplate: Guides
|
||||
description: For users who would like to download and organize their own media.
|
||||
---
|
||||
|
||||
# Local source
|
||||
|
||||
If you like to download and organize your media, then you want to know how to manage your own series in Tachiyomi.
|
||||
|
||||
::: warning
|
||||
This page explores some advanced features.
|
||||
:::
|
||||
|
||||
## Creating local series
|
||||
|
||||
1. Create a folder named `local` in the `/Tachiyomi/` folder.
|
||||
|
||||
> The `/Tachiyomi/` folder is located in the root of phone's **internal storage** or **external SD card** and it's **not related** to the `eu.kanade.tachiyomi/` folder or the download location in the settings.
|
||||
1. Place correctly structured series inside `/Tachiyomi/local/`.
|
||||
|
||||
> If adding series in folders it is recommended to add a file named `.nomedia` to the local folder so images do not show up in the gallery.
|
||||
1. You should now be able to access the series in <nav to="sources"> under **Local source**.
|
||||
|
||||
If you add more chapters then you'll have to manually refresh the chapter list (by pulling down the list).
|
||||
|
||||
Supported chapter formats are folders with pictures inside (such as `.jpg`, `.png`, etc), `ZIP`/`CBZ`, `RAR`/`CBR` and `EPUB`.
|
||||
But expect better performance with directories and `ZIP`/`CBZ`.
|
||||
|
||||
Remember to give the app storage permissions on **Android 6** and newer.
|
||||
|
||||
### Folder structure
|
||||
|
||||
Tachiyomi requires a specific folder structure for local series to be correctly processed.
|
||||
Local series will be read from the `Tachiyomi/local` folder.
|
||||
Each series must have a `Series` folder and a `Chapter` folder.
|
||||
Images will then go into the chapter folder.
|
||||
See below for more information on archive files.
|
||||
You can refer to the following example:
|
||||
|
||||
#### Example {#example-storages}
|
||||
|
||||
:::tabs
|
||||
== Device Storage
|
||||
<div class="tree">
|
||||
<ul>
|
||||
<img src="/img/folder.svg" class="tree-icon icon-folder">
|
||||
<span class="folder root">/sdcard/Tachiyomi/local</span>
|
||||
<li>
|
||||
<img src="/img/folder.svg" class="tree-icon icon-folder">
|
||||
<span class="folder main">[the series title]</span>
|
||||
<ul>
|
||||
<li>
|
||||
<img src="/img/jpeg.svg" class="tree-icon icon-jpeg">
|
||||
<span class="file jpg">cover<span class="file-extension">.jpg</span></span>
|
||||
</li>
|
||||
<li>
|
||||
<img src="/img/folder.svg" class="tree-icon icon-folder">
|
||||
<span class="folder">chapter_1</span>
|
||||
<ul>
|
||||
<li><span class="file">image_1<span class="file-extension">.ext</span></span></li>
|
||||
<li><span class="file">image_n<span class="file-extension">.ext</span></span></li>
|
||||
</ul>
|
||||
</li>
|
||||
<li>
|
||||
<img src="/img/folder.svg" class="tree-icon icon-folder">
|
||||
<span class="folder">chapter_2</span>
|
||||
<ul>
|
||||
<li><span class="file">image_1<span class="file-extension">.ext</span></span></li>
|
||||
<li><span class="file">image_n<span class="file-extension">.ext</span></span></li>
|
||||
</ul>
|
||||
</li>
|
||||
<li>
|
||||
<img src="/img/folder.svg" class="tree-icon icon-folder">
|
||||
<span class="folder">chapter_n</span>
|
||||
<ul>
|
||||
<li><span class="file">image_1<span class="file-extension">.ext</span></span></li>
|
||||
<li><span class="file">image_n<span class="file-extension">.ext</span></span></li>
|
||||
</ul>
|
||||
</li>
|
||||
</ul>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
== SD Card
|
||||
<div class="tree">
|
||||
<ul>
|
||||
<img src="/img/folder.svg" class="tree-icon icon-folder">
|
||||
<span class="folder root">/storage/18F5-2C11/Tachiyomi/local</span>
|
||||
<li>
|
||||
<img src="/img/folder.svg" class="tree-icon icon-folder">
|
||||
<span class="folder main">[the series title]</span>
|
||||
<ul>
|
||||
<li>
|
||||
<img src="/img/jpeg.svg" class="tree-icon icon-jpeg">
|
||||
<span class="file jpg">cover<span class="file-extension">.jpg</span></span>
|
||||
</li>
|
||||
<li>
|
||||
<img src="/img/folder.svg" class="tree-icon icon-folder">
|
||||
<span class="folder">chapter_1</span>
|
||||
<ul>
|
||||
<li><span class="file">image_1<span class="file-extension">.ext</span></span></li>
|
||||
<li><span class="file">image_n<span class="file-extension">.ext</span></span></li>
|
||||
</ul>
|
||||
</li>
|
||||
<li>
|
||||
<img src="/img/folder.svg" class="tree-icon icon-folder">
|
||||
<span class="folder">chapter_2</span>
|
||||
<ul>
|
||||
<li><span class="file">image_1<span class="file-extension">.ext</span></span></li>
|
||||
<li><span class="file">image_n<span class="file-extension">.ext</span></span></li>
|
||||
</ul>
|
||||
</li>
|
||||
<li>
|
||||
<img src="/img/folder.svg" class="tree-icon icon-folder">
|
||||
<span class="folder">chapter_n</span>
|
||||
<ul>
|
||||
<li><span class="file">image_1<span class="file-extension">.ext</span></span></li>
|
||||
<li><span class="file">image_n<span class="file-extension">.ext</span></span></li>
|
||||
</ul>
|
||||
</li>
|
||||
</ul>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
:::
|
||||
|
||||
Tachiyomi will see four chapters in a single series.
|
||||
The path to the folder with images must contain both the series title and the chapter name (as seen above).
|
||||
|
||||
### Archive files
|
||||
|
||||
Archive files such as `ZIP`/`CBZ` are supported but the folder structure inside is not.
|
||||
Any folders inside the archive file are ignored.
|
||||
You must place the archive inside the `Series` folder where the name will become the `Chapter` title.
|
||||
All images inside the archive regardless of folder structure will become pages for that chapter.
|
||||
|
||||
#### Example {#example-archives}
|
||||
|
||||
:::tabs
|
||||
== .ZIP
|
||||
<div class="tree">
|
||||
<ul>
|
||||
<img src="/img/folder.svg" class="tree-icon icon-folder">
|
||||
<span class="folder root">/sdcard/Tachiyomi/local</span>
|
||||
<li>
|
||||
<img src="/img/folder.svg" class="tree-icon icon-folder">
|
||||
<span class="folder main">[the series title]</span>
|
||||
<ul>
|
||||
<li>
|
||||
<img src="/img/jpeg.svg" class="tree-icon icon-jpeg">
|
||||
<span class="file jpg">cover<span class="file-extension">.jpg</span></span>
|
||||
</li>
|
||||
<li>
|
||||
<img src="/img/zip.svg" class="tree-icon icon-zip">
|
||||
<span class="file zip">chapter_1<span class="file-extension">.zip</span></span>
|
||||
<ul>
|
||||
<li>
|
||||
<img src="/img/jpeg.svg" class="tree-icon icon-jpeg">
|
||||
<span class="file jpg">image_1<span class="file-extension">.jpg</span></span>
|
||||
</li>
|
||||
<li>
|
||||
<img src="/img/jpeg.svg" class="tree-icon icon-jpeg">
|
||||
<span class="file jpg">image_n<span class="file-extension">.jpg</span></span>
|
||||
</li>
|
||||
</ul>
|
||||
</li>
|
||||
<li>
|
||||
<img src="/img/zip.svg" class="tree-icon icon-zip">
|
||||
<span class="file zip">chapter_2<span class="file-extension">.zip</span></span>
|
||||
<ul>
|
||||
<li>
|
||||
<img src="/img/jpeg.svg" class="tree-icon icon-jpeg">
|
||||
<span class="file jpg">image_1<span class="file-extension">.jpg</span></span>
|
||||
</li>
|
||||
<li>
|
||||
<img src="/img/jpeg.svg" class="tree-icon icon-jpeg">
|
||||
<span class="file jpg">image_n<span class="file-extension">.jpg</span></span>
|
||||
</li>
|
||||
</ul>
|
||||
</li>
|
||||
<li>
|
||||
<img src="/img/zip.svg" class="tree-icon icon-zip">
|
||||
<span class="file zip">chapter_n<span class="file-extension">.zip</span></span>
|
||||
<ul>
|
||||
<li>
|
||||
<img src="/img/jpeg.svg" class="tree-icon icon-jpeg">
|
||||
<span class="file jpg">image_1<span class="file-extension">.jpg</span></span>
|
||||
</li>
|
||||
<li>
|
||||
<img src="/img/jpeg.svg" class="tree-icon icon-jpeg">
|
||||
<span class="file jpg">image_n<span class="file-extension">.jpg</span></span>
|
||||
</li>
|
||||
</ul>
|
||||
</li>
|
||||
</ul>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
:::
|
||||
|
||||
<style scoped>
|
||||
@import "../../../.vitepress/theme/styles/tree.styl"
|
||||
</style>
|
296
website/src/docs/guides/reader-settings.md
Normal file
296
website/src/docs/guides/reader-settings.md
Normal file
@ -0,0 +1,296 @@
|
||||
---
|
||||
title: Reader settings
|
||||
titleTemplate: Guides
|
||||
description: This section relates to the reading experience in the app and navigating the reader.
|
||||
---
|
||||
|
||||
# Reader settings
|
||||
|
||||
This section relates to the reading experience in the app and navigating the reader.
|
||||
|
||||
## Options
|
||||
|
||||
### Default reading mode <Badge type="info" text="Paged (right to left)" />
|
||||
|
||||
This setting sets the reader's default direction when you open a series.
|
||||
|
||||
::: tabs
|
||||
== Paged (RTL)
|
||||
Right-to-left, the default way of reading manga.
|
||||
|
||||
- Swipe right for next page.
|
||||
- Swipe left for previous page.
|
||||
== Paged (LTR)
|
||||
Left-to-right, the default way of reading comics.
|
||||
|
||||
- Swipe left for next page.
|
||||
- Swipe right for previous page.
|
||||
== Paged (Vertical)
|
||||
- Swipe up for next page.
|
||||
- Swipe down for previous.
|
||||
|
||||
== Long strip
|
||||
Default way of reading webtoons.
|
||||
== Long strip with gaps
|
||||
Long strip but with a little space between pages.
|
||||
:::
|
||||
|
||||
::: tip
|
||||
You can change viewer for different series by going to the series, opening a chapter, tapping the middle of the screen, pressing the gear icon, and selecting a different viewer in **Viewer for this series**.
|
||||
:::
|
||||
|
||||
### Double tap animation speed <Badge type="info" text="Normal" />
|
||||
Double tap animation speed changes the speed in which the zoom happens when double tapping.
|
||||
|
||||
::: tabs
|
||||
== No animation
|
||||
TBA
|
||||
== Normal
|
||||
TBA
|
||||
== Fast
|
||||
TBA
|
||||
:::
|
||||
|
||||
### Show reading mode <Badge type="info" text="On" />
|
||||
Briefly show current mode when reader is opened
|
||||
|
||||
### Show tap zones overlay <Badge type="info" text="Off" />
|
||||
TBA
|
||||
|
||||
### 32-bit color <Badge type="info" text="Off" />
|
||||
This setting decodes images in `ARGB888` format to allow the reader to display more colors.
|
||||
|
||||
::: warning
|
||||
This setting will only show up on devices running **Android 8.0** or higher.
|
||||
:::
|
||||
|
||||
### Animate page transitions <Badge type="info" text="On" />
|
||||
This setting applies a smooth transition when tapping to change page.
|
||||
|
||||
## Display
|
||||
|
||||
### Default rotation type <Badge type="info" text="Free" />
|
||||
|
||||
This allows you to control how the screen is going to be oriented.
|
||||
|
||||
::: tabs
|
||||
== Free
|
||||
TBA
|
||||
== Portrait
|
||||
TBA
|
||||
== Landscape
|
||||
TBA
|
||||
== Locked portrait
|
||||
TBA
|
||||
== Locked landscape
|
||||
TBA
|
||||
== Reverse portrait
|
||||
TBA
|
||||
:::
|
||||
|
||||
### Background color <Badge type="info" text="Black" />
|
||||
|
||||
::: tabs
|
||||
== Black
|
||||
![Black](/docs/guides/reader-settings/background-color_black.webp =512x788)
|
||||
== Gray
|
||||
![Gray](/docs/guides/reader-settings/background-color_gray.webp =512x788)
|
||||
== White
|
||||
![White](/docs/guides/reader-settings/background-color_white.webp =512x788)
|
||||
== Automatic
|
||||
Automatically sets the color based on the content of your page.
|
||||
:::
|
||||
|
||||
### Fullscreen <Badge type="info" text="On" />
|
||||
TBA
|
||||
|
||||
### Show content in cutout area <Badge type="info" text="On" />
|
||||
TBA
|
||||
|
||||
### Keep screen on <Badge type="info" text="On" />
|
||||
TBA
|
||||
|
||||
### Show page number <Badge type="info" text="On" />
|
||||
TBA
|
||||
|
||||
## Reading
|
||||
|
||||
### Skip chapters marked read <Badge type="info" text="Off" />
|
||||
TBA
|
||||
|
||||
### Skip filtered chapters <Badge type="info" text="On" />
|
||||
TBA
|
||||
|
||||
### Skip duplicate chapters <Badge type="info" text="Off" />
|
||||
TBA
|
||||
|
||||
### Always show chapter transition <Badge type="info" text="On" />
|
||||
TBA
|
||||
|
||||
## Pages
|
||||
|
||||
### Tap zones <Badge type="info" text="Default" /> {#tap-zones-pages}
|
||||
|
||||
::: tabs
|
||||
== Default
|
||||
![Right and Left](/docs/guides/reader-settings/tap-zones_right-and-left.webp =247x600)
|
||||
== L shaped
|
||||
![L shaped](/docs/guides/reader-settings/tap-zones_l-shaped.webp =247x600)
|
||||
== Kindle-ish
|
||||
![Kindle-ish](/docs/guides/reader-settings/tap-zones_kindle-ish.webp =247x600)
|
||||
== Edge
|
||||
![Edge](/docs/guides/reader-settings/tap-zones_edge.webp =247x600)
|
||||
== Right and Left
|
||||
![Right and Left](/docs/guides/reader-settings/tap-zones_right-and-left.webp =247x600)
|
||||
== Disabled
|
||||
No tap zones to assist with navigation will be active.
|
||||
:::
|
||||
|
||||
### Invert tap zones <Badge type="info" text="None" /> {#invert-tap-zones-pages}
|
||||
|
||||
::: tabs
|
||||
== None
|
||||
Keeps the default zap zones.
|
||||
== Horizontal
|
||||
Changes so that the tap zones are flipped horizontally.
|
||||
== Vertical
|
||||
Changes so that the tap zones are flipped vertically.
|
||||
== Both
|
||||
Changes so that the tap zones are flipped horizontally and vertically.
|
||||
:::
|
||||
|
||||
### Scale type <Badge type="info" text="Fit screen" />
|
||||
|
||||
::: tabs
|
||||
== Fit screen
|
||||
![Fit screen](/docs/guides/reader-settings/scale-type_fit-screen.webp =512x788)
|
||||
== Stretch
|
||||
![Stretch](/docs/guides/reader-settings/scale-type_stretch.webp =512x788)
|
||||
== Fit width
|
||||
![Fit width](/docs/guides/reader-settings/scale-type_fit-width.webp =512x788)
|
||||
== Fit height
|
||||
![Fit height](/docs/guides/reader-settings/scale-type_fit-height.webp =512x788)
|
||||
== Original size
|
||||
![Original size](/docs/guides/reader-settings/scale-type_original-size.webp =512x788)
|
||||
== Smart fit
|
||||
![Smart fit](/docs/guides/reader-settings/scale-type_smart-fit.webp =512x788)
|
||||
:::
|
||||
|
||||
### Zoom start position <Badge type="info" text="Automatic" />
|
||||
|
||||
::: tabs
|
||||
== Automatic
|
||||
![Center (TBA Default Image)](/docs/guides/reader-settings/zoom-start-position_center.webp =512x788)
|
||||
== Left
|
||||
![Left](/docs/guides/reader-settings/zoom-start-position_left.webp =512x788)
|
||||
== Right
|
||||
![Right](/docs/guides/reader-settings/zoom-start-position_right.webp =512x788)
|
||||
== Center
|
||||
![Center](/docs/guides/reader-settings/zoom-start-position_center.webp =512x788)
|
||||
:::
|
||||
|
||||
### Crop borders <Badge type="info" text="Off" /> {#crop-borders-pages}
|
||||
|
||||
:::tabs
|
||||
== Off
|
||||
![Not cropping borders](/docs/guides/reader-settings/crop-borders_off.webp =512x788)
|
||||
== On
|
||||
![Cropping borders](/docs/guides/reader-settings/crop-borders_on.webp =512x788)
|
||||
:::
|
||||
|
||||
### Automatically zoom into wide images <Badge type="info" text="On" />
|
||||
TBA
|
||||
|
||||
### Pan wide images <Badge type="info" text="On" />
|
||||
TBA
|
||||
|
||||
### Split wide pages <Badge type="info" text="Off" /> {#split-wide-pages}
|
||||
TBA
|
||||
|
||||
### Rotate wide pages to fit <Badge type="info" text="Off" />
|
||||
TBA
|
||||
|
||||
## Long strip
|
||||
|
||||
### Tap zones <Badge type="info" text="Default" /> {#tap-zones-longstrip}
|
||||
|
||||
::: tabs
|
||||
== Default
|
||||
![Right and Left](/docs/guides/reader-settings/tap-zones_right-and-left.webp =247x600)
|
||||
== L shaped
|
||||
![L shaped](/docs/guides/reader-settings/tap-zones_l-shaped.webp =247x600)
|
||||
== Kindle-ish
|
||||
![Kindle-ish](/docs/guides/reader-settings/tap-zones_kindle-ish.webp =247x600)
|
||||
== Edge
|
||||
![Edge](/docs/guides/reader-settings/tap-zones_edge.webp =247x600)
|
||||
== Right and Left
|
||||
![Right and Left](/docs/guides/reader-settings/tap-zones_right-and-left.webp =247x600)
|
||||
== Disabled
|
||||
No tap zones to assist with navigation will be active.
|
||||
:::
|
||||
|
||||
### Invert tap zones <Badge type="info" text="None" /> {#invert-tap-zones-longstrip}
|
||||
|
||||
::: tabs
|
||||
== None
|
||||
TBA
|
||||
== Horizontal
|
||||
TBA
|
||||
== Vertical
|
||||
TBA
|
||||
== Both
|
||||
TBA
|
||||
:::
|
||||
|
||||
### Side padding <Badge type="info" text="None" />
|
||||
|
||||
::: tabs
|
||||
== None
|
||||
TBA
|
||||
== 5%
|
||||
TBA
|
||||
== 10%
|
||||
TBA
|
||||
== 15%
|
||||
TBA
|
||||
== 20%
|
||||
TBA
|
||||
== 25%
|
||||
TBA
|
||||
:::
|
||||
|
||||
### Sensitivity for hiding menu on scroll <Badge type="info" text="Low" />
|
||||
|
||||
::: tabs
|
||||
== Highest
|
||||
TBA
|
||||
== High
|
||||
TBA
|
||||
== Low
|
||||
TBA
|
||||
== Lowest
|
||||
TBA
|
||||
:::
|
||||
|
||||
### Crop borders <Badge type="info" text="Off" /> {#crop-borders-longstrip}
|
||||
TBA
|
||||
|
||||
### Split wide pages <Badge type="info" text="Off" /> {#split-wide-longstrip}
|
||||
TBA
|
||||
|
||||
### Double tap to zoom <Badge type="info" text="On" />
|
||||
TBA
|
||||
|
||||
## Navigation
|
||||
TBA
|
||||
|
||||
### Volume keys <Badge type="info" text="Off" />
|
||||
TBA
|
||||
|
||||
## Actions
|
||||
|
||||
### Show on long tap <Badge type="info" text="On" />
|
||||
TBA
|
||||
|
||||
### Save pages into seperate folders <Badge type="info" text="Off" />
|
||||
TBA
|
17
website/src/docs/guides/shizuku.md
Normal file
17
website/src/docs/guides/shizuku.md
Normal file
@ -0,0 +1,17 @@
|
||||
---
|
||||
title: Shizuku
|
||||
titleTemplate: Guides
|
||||
description: Using Shizuku with Tachiyomi.
|
||||
---
|
||||
|
||||
# Shizuku
|
||||
**Shizuku** is an alternative method for installing and updating extensions in **Tachiyomi**.
|
||||
|
||||
It taps into system APIs to directly install packages without requiring user interaction.
|
||||
|
||||
To get a detailed understanding of **Shizuku**, its purpose, and how it operates, refer to the information provided [here](https://shizuku.rikka.app/introduction/).
|
||||
|
||||
## Setting up Shizuku
|
||||
To set up **Shizuku**, follow the instructions [here](https://shizuku.rikka.app/guide/setup/).
|
||||
|
||||
After configuring it, enable **Shizuku** within **Tachiyomi** by navigating to <nav to="advanced"> then changing the **Installer** setting.
|
100
website/src/docs/guides/source-migration.md
Normal file
100
website/src/docs/guides/source-migration.md
Normal file
@ -0,0 +1,100 @@
|
||||
---
|
||||
title: Source migration
|
||||
titleTemplate: Guides
|
||||
description: Migration is the process of moving series between sources without losing progress.
|
||||
---
|
||||
|
||||
# Source migration
|
||||
Migration is the process of moving series between sources without losing progress. This is most often used when a source is no longer accessible or another source is more up-to-date.
|
||||
|
||||
::: warning
|
||||
Always make sure to have a backup in case anything unexpected occurs.
|
||||
:::
|
||||
|
||||
::: danger
|
||||
Downloaded chapter(s) do not transfer with migrations.
|
||||
|
||||
Migrations with downloaded chapter(s) may leave the download behind.
|
||||
You will need to remove these manually with a file manager.
|
||||
:::
|
||||
|
||||
## Migration guide
|
||||
|
||||
::::tabs
|
||||
==Tachiyomi
|
||||
### Migrating from Series
|
||||
|
||||
1. Tap into the **Series** you would like to migrate.
|
||||
1. Go to Overflow and tap Migrate.
|
||||
|
||||
> **Tachiyomi** will do a global search of all the sources you have installed and enabled.
|
||||
1. Select the **Source** that you'd like to migrate _to_ by tapping the **Series** thumbnail.
|
||||
1. Choose which data you want to transfer over, and you're done.
|
||||
|
||||
### Migrating from Source
|
||||
|
||||
1. Tap into Browse on the bottom navbar.
|
||||
1. Press the Migrate tab at the top next to Extensions.
|
||||
1. Select the **Source** that you'd like to migrate _from_.
|
||||
1. Tap the **Series** you'd like to migrate _from_ the **Source**.
|
||||
|
||||
> **Tachiyomi** will do a global search of all the sources you have installed and enabled.
|
||||
1. Select the **Source** you'd like to migrate _to_ by tapping the **Series** thumbnail.
|
||||
1. Choose which data you want to transfer over, and you're done.
|
||||
|
||||
==TachiyomiJ2K
|
||||
### Migrating multiple Series
|
||||
|
||||
1. Tap **Settings** -> **Sources** -> **Source migration**.
|
||||
1. Select the **Source** you'd like to migrate _from_ and select **All**.
|
||||
1. Select the **Sources** that you'd like to migrate _to_ and search by and tap the arrow at the bottom right.
|
||||
1. Choose which data you want to transfer over.
|
||||
1. Wait until all your **Series** is found and hit the done at the top and you're done.
|
||||
|
||||
> If a series is not found, or is wrong you can manually search it by pressing Overflow -> **Search manually**.
|
||||
|
||||
### Migrating a single Series
|
||||
|
||||
1. Tap into a **Series** in your Library.
|
||||
1. Tap **Overflow** -> **Migrate**.
|
||||
1. Select the **Sources** you'd like to search and migrate _to_ and hit the arrow at the bottom right.
|
||||
1. Wait until it is found and select _done_ in the top right and you're done.
|
||||
|
||||
> If a series is not found, or is wrong you can manually search it by pressing Overflow -> **Search manually**
|
||||
|
||||
==TachiyomiSY
|
||||
### Migrating from Library
|
||||
|
||||
1. Tap into **Library**.
|
||||
1. Tap **Overflow** -> **Source migration**.
|
||||
1. Select the **Source** you'd like to migrate _from_ and select **All**.
|
||||
1. Select the **Sources** that you'd like to migrate _to_ and search by and tap the arrow labeled Migrate at the bottom right.
|
||||
1. Choose which data you want to transfer over.
|
||||
1. Wait until all your **Series** is found and hit the done at the top and you're done.
|
||||
|
||||
> If a series is not found, or is wrong you can manually search it by pressing **Overflow** -> **Search manually**.
|
||||
|
||||
### Migrating from Source {#migrating-from-source-sy}
|
||||
|
||||
1. Tap into Browse on the bottom navbar.
|
||||
1. Press the Migrate tab at the top next to Extensions.
|
||||
1. Select the **Source** that you'd like to migrate _from_.
|
||||
1. Select the **Source** you'd like to migrate _from_ and select **All**.
|
||||
1. Select the **Sources** that you'd like to migrate _to_ and search by and tap the arrow labeled Migrate at the bottom right.
|
||||
1. Choose which data you want to transfer over.
|
||||
1. Wait until all your **Series** is found and hit the done at the top and you're done.
|
||||
|
||||
> If a series is not found, or is wrong you can manually search it by pressing **Overflow** -> **Search manually**.
|
||||
|
||||
==TachiyomiAZ
|
||||
### Instructions
|
||||
|
||||
1. Tap into **Library**.
|
||||
1. Tap **Overflow** -> **Source migration**.
|
||||
1. Select the **Source** you'd like to migrate _from_ and select **All**.
|
||||
1. Select the **Sources** that you'd like to migrate _to_ and search by and tap the arrow at the bottom right.
|
||||
1. Choose which data you want to transfer over.
|
||||
1. Wait until all your **Series** is found and hit the done at the top and you're done.
|
||||
|
||||
> If a series is not found, or is wrong you can manually search it by pressing **Overflow** -> **Search manually**.
|
||||
::::
|
73
website/src/docs/guides/tracking.md
Normal file
73
website/src/docs/guides/tracking.md
Normal file
@ -0,0 +1,73 @@
|
||||
---
|
||||
title: Tracking
|
||||
titleTemplate: Guides
|
||||
description: Tracking helps track your library with different online services.
|
||||
---
|
||||
|
||||
# Tracking
|
||||
Tracking helps you automatically send read chapters to supported trackers, so you can keep track of what and when you read it online.
|
||||
|
||||
## Services
|
||||
|
||||
[MyAnimeList](https://myanimelist.net/), [AniList](https://anilist.co/), [MangaUpdates](https://www.mangaupdates.com/), [Shikimori](https://shikimori.one/), [Bangumi](https://bangumi.tv/), and [Kitsu](https://kitsu.io/).
|
||||
|
||||
- Tracking is manual for each entry.
|
||||
- Reading the last page of a chapter marks it as read.
|
||||
- Set chapters by tapping or dragging the ticker.
|
||||
- Offline progress syncs when online.
|
||||
- Start date is auto-set.
|
||||
- Completion status auto-updates.
|
||||
- Tracking is one-way: **Tachiyomi -> Tracker**.
|
||||
|
||||
## Enhanced services
|
||||
|
||||
::::tabs
|
||||
==Komga
|
||||
- No separate login required.
|
||||
- Automatic tracking.
|
||||
- Works with **Komga** sources only.
|
||||
- Two-way sync for local chapters.
|
||||
- Manually read chapter syncs with delay.
|
||||
- Auto-track on library add: <nav to="tracking">.
|
||||
|
||||
> Learn how to set it up on the [Komga](https://komga.org/) website.
|
||||
==Kavita
|
||||
TBA
|
||||
|
||||
> Learn how to set it up on the [Kavita](https://www.kavitareader.com/) website.
|
||||
==Suwayomi
|
||||
TBA
|
||||
|
||||
> Learn how to set it up on the [Suwayomi](https://suwayomi.org/) website.
|
||||
::::
|
||||
|
||||
## General questions
|
||||
|
||||
### How do I login into trackers?
|
||||
1. Go to <nav to="tracking">.
|
||||
1. Tap the desired tracker to begin login.
|
||||
|
||||
### How do I set up tracking for each series?
|
||||
1. Open the series.
|
||||
1. Tap **Tracking**.
|
||||
1. Tap **Add tracking** for the desired service.
|
||||
|
||||
::: tip
|
||||
You can also change the search query if there is no match.
|
||||
:::
|
||||
|
||||
### How do I log in with Kitsu?
|
||||
To log in with Kitsu, you need to use your email address as your username.
|
||||
|
||||
### Can't find a series on MyAnimeList?
|
||||
If you cannot find a series by name, you can look it up on MyAnimeList and then search for it in **Tachiyomi** using the following format: `id:<id from series URL>`.
|
||||
|
||||
You can also search for a series on your MAL profile list by searching in the following format: `my:<series name>`.
|
||||
|
||||
::: warning For your information
|
||||
Related GitHub issue: [#65](https://github.com/tachiyomiorg/tachiyomi/issues/65)
|
||||
:::
|
||||
|
||||
### Finding tracked/intracked series in your library
|
||||
Go to <nav to="main_library">, then **Filter (top right) -> Filter tab** then **Toggle Tracked**.
|
||||
If you are logged into more than one tracker, toggle the tracker you want to include or exclude.
|
110
website/src/docs/guides/troubleshooting/common-issues.md
Normal file
110
website/src/docs/guides/troubleshooting/common-issues.md
Normal file
@ -0,0 +1,110 @@
|
||||
---
|
||||
title: Common issues
|
||||
titleTemplate: Troubleshooting - Guides
|
||||
description: Facing issues with a source or the app? Here's how to tackle common challenges.
|
||||
---
|
||||
|
||||
# Common issues
|
||||
|
||||
Facing issues with a source or the app?
|
||||
Here's how to tackle common challenges.
|
||||
|
||||
## Basic issues
|
||||
|
||||
### `Cannot Access SD Card`
|
||||
* Temporarily switch download location from SD card, then revert and restart the app.
|
||||
* Long filenames trigger this. Android file manager doesn't support **>255** characters.
|
||||
* If known, shorten the file/folder name via computer when SD card is connected.
|
||||
* Else, delete **Tachiyomi** downloads folder on SD card.
|
||||
|
||||
### Storage issues with Android 11+
|
||||
See [this](/docs/faq/android-11+) section of the FAQ to learn how Scoped Storage affects **Tachiyomi** in **Android 11+** and how to fix it.
|
||||
|
||||
### Slow loading
|
||||
Sources being slow could stem from site slowness, your internet, or source-imposed rate limits/IP bans.
|
||||
|
||||
### Reading is laggy
|
||||
* Caused by oversized images in chapters.
|
||||
* For **32-bit color** users, try disabling in <nav to="reader">.
|
||||
* Free up RAM.
|
||||
* Use sources with smaller images.
|
||||
|
||||
### App not installed
|
||||
Refer to "[Unable to install the app or extensions](/docs/guides/troubleshooting/#app-or-extension-installation-issues)" section.
|
||||
|
||||
## Advanced errors
|
||||
|
||||
### `Java.lang Exception: Failed to bypass Cloudflare`
|
||||
This error indicates the selected source is protected by **Cloudflare**.
|
||||
|
||||
Consult the [Cloudflare guide](/docs/guides/troubleshooting/#cloudflare) for solutions.
|
||||
If issues persist, the source might have high **Cloudflare** protection.
|
||||
|
||||
### `Unable to resolve host` / `Connection failed` / `Failed to connect to` / `timeout` / `connection reset`
|
||||
These errors indicate connection issues. Possible causes include:
|
||||
|
||||
* Weak internet connection.
|
||||
* App lacks internet access.
|
||||
* Your ISP has blocked the site.
|
||||
* The site is down.
|
||||
|
||||
Try these solutions:
|
||||
|
||||
* Enable **DNS over HTTPS** under <nav to="advanced">.
|
||||
* Change network (Wi-Fi, mobile data, VPN).
|
||||
* Reboot router.
|
||||
|
||||
### `java.security.cert.CertPathValidatorException` / `Chain validation failed`
|
||||
Validation issue with source's certificate.
|
||||
|
||||
Try these solutions:
|
||||
|
||||
* Check expired certificate, use SSL checker.
|
||||
* Set correct device date and time.
|
||||
* In <nav to="advanced">, try **Clear cache** and **Clear cookies**.
|
||||
* Change network (Wi-Fi, mobile data, VPN).
|
||||
* Reboot device.
|
||||
|
||||
### `Attempt to invoke virtual method 'com.hippo.unifile...'`
|
||||
|
||||
Storage-related error causes:
|
||||
|
||||
* Storage full, check device/SD Card.
|
||||
* Grant **Tachiyomi** SD card access in Android settings.
|
||||
* Download folder access issues, validate paths.
|
||||
* Corrupted or inaccessible writing drive, verify using a file manager.
|
||||
|
||||
## HTTP errors
|
||||
Encountering HTTP errors? Here's what they mean and how to address them.
|
||||
|
||||
### `HTTP Error: 403` - Forbidden
|
||||
Possible reasons for this error:
|
||||
* The selected source has Cloudflare protection. Check the [Cloudflare guide](/docs/guides/troubleshooting/#cloudflare) for solutions.
|
||||
* The source might be down, removed the series, or banned your IP.
|
||||
> Open WebView to confirm.
|
||||
|
||||
### `HTTP Error: 404` - Not Found
|
||||
This error likely indicates a down source or removed series.
|
||||
* Use **WebView** to verify.
|
||||
> Consider switching to a different source for the series.
|
||||
|
||||
### `HTTP Error: 429` - Too Many Requests
|
||||
This error suggests the source temporarily banned your IP due to fast downloads/reads.
|
||||
|
||||
[Report the issue](https://github.com/tachiyomiorg/tachiyomi-extensions/issues/new/choose) to add rate limits and prevent future IP bans.
|
||||
|
||||
### `HTTP Error: 5xx`
|
||||
Errors like `500`, `502`, etc., indicate server-side issues on the source's end.
|
||||
|
||||
[Check the source in WebView](/docs/guides/troubleshooting/#accessing-websites-via-webview) to confirm if it's down.
|
||||
|
||||
### `HTTP Error: 1006`
|
||||
This error means a temporary IP ban by the source.
|
||||
|
||||
### `HTTP Error: 1020`
|
||||
This error points to violating a firewall rule set by the site owner.
|
||||
The owner might raise Cloudflare protection or block IPs from outside their country.
|
||||
|
||||
::: warning
|
||||
For unlisted errors or if instructions don't help, refer to [Diagnosis](/docs/guides/troubleshooting/diagnosis).
|
||||
:::
|
65
website/src/docs/guides/troubleshooting/diagnosis.md
Normal file
65
website/src/docs/guides/troubleshooting/diagnosis.md
Normal file
@ -0,0 +1,65 @@
|
||||
---
|
||||
title: Diagnosis
|
||||
titleTemplate: Troubleshooting - Guides
|
||||
description: Facing issues with a source or the app? Follow these steps to troubleshoot and find solutions.
|
||||
---
|
||||
|
||||
# Diagnosis
|
||||
|
||||
Facing issues with a source or the app?
|
||||
Follow these steps to troubleshoot and find solutions.
|
||||
|
||||
## Primary diagnosis
|
||||
|
||||
1. **Update Extensions**: Check <nav to="extensions"> for updates, no pending updates should be present.
|
||||
1. **Update App**: Go to <nav to="about"> and tap **Check for updates**.
|
||||
1. **Manual Series Refresh**: Drag down to manually refresh problematic series.
|
||||
1. **Test Other Series**: Try different series from the same source.
|
||||
1. **Update WebView**: Ensure your WebView is current.
|
||||
1. **Public WebView**: Attempt opening series in public WebView. Wait for CAPTCHA or Cloudflare protection if needed.
|
||||
1. **Change Connection**: Switch networks (Wi-Fi, mobile data, VPN) and confirm IP change.
|
||||
1. **Collaborative Check**: Get others to replicate the error.
|
||||
1. **Source Status**: Verify the source's status in a browser.
|
||||
1. **Retry Button**: Look for a retry button on the series page.
|
||||
1. **Advanced Settings**: Under <nav to="advanced">, try these options:
|
||||
- Clear Cache
|
||||
- Clear Cookies
|
||||
- Clear Database
|
||||
- DNS over HTTPS
|
||||
1. **Download Issues**: Delete the queue and retry downloads.
|
||||
1. **Restart Tachiyomi**: Force close and reopen the app.
|
||||
|
||||
If any of these solutions help, go to [Personalized Issue](#personalized-issue).
|
||||
If it is not just you, go to [Widespread Issue](#widespread-issue).
|
||||
|
||||
If none of these solutions help, try asking in our [Discord server](https://discord.gg/tachiyomi).
|
||||
|
||||
Check [#status-updates](https://discord.com/channels/349436576037732353/738862409284059239) first to see if your issue is known.
|
||||
State your app version and the source, series, and chapter with the problem if it is not listed.
|
||||
|
||||
::: tip An extension update may fix your issue
|
||||
Wait or check for an extension update if you have not already.
|
||||
There are no ETAs for updates.
|
||||
:::
|
||||
|
||||
## Personalized issue
|
||||
If you're the only one facing a problem, you might be encountering [Cloudflare](/docs/guides/troubleshooting/#cloudflare) protection, an IP ban, or other countermeasures set by website owners against programs like **Tachiyomi**.
|
||||
|
||||
**To minimize future issues:**
|
||||
- Avoid using downloads with the source.
|
||||
- Reduce the number of series in your library from that source.
|
||||
|
||||
::: warning
|
||||
These are general guidelines as each site has its specific undisclosed limits and triggers.
|
||||
:::
|
||||
|
||||
## Widespread issue
|
||||
When everyone experiences a problem, it could be with the extension or app:
|
||||
|
||||
1. Check open issues [for the app](https://github.com/tachiyomiorg/tachiyomi/issues) and/or [**extensions**](https://github.com/tachiyomiorg/tachiyomi-extensions/issues).
|
||||
1. Check closed issues ([app](https://github.com/tachiyomiorg/tachiyomi/issues?q=is%3Aissue+is%3Aclosed)/[extensions](https://github.com/tachiyomiorg/tachiyomi-extensions/issues?q=is%3Aissue+is%3Aclosed)) in case it's resolved but not yet released.
|
||||
1. If not found, create a new issue.
|
||||
|
||||
::: warning
|
||||
If the site itself is problematic, patience is the only solution until it becomes functional again.
|
||||
:::
|
123
website/src/docs/guides/troubleshooting/index.md
Normal file
123
website/src/docs/guides/troubleshooting/index.md
Normal file
@ -0,0 +1,123 @@
|
||||
---
|
||||
title: Troubleshooting
|
||||
titleTemplate: Guides
|
||||
description: Facing source or app issues? Here's how to troubleshoot.
|
||||
---
|
||||
|
||||
# Troubleshooting
|
||||
|
||||
Facing source or app issues?
|
||||
Here's how to troubleshoot.
|
||||
|
||||
## WebView
|
||||
|
||||
### Accessing websites via WebView
|
||||
|
||||
::: tabs
|
||||
== From Browse
|
||||
1. Open **Browse** from the bottom navbar.
|
||||
1. Tap the desired source.
|
||||
1. Tap the **WebView** icon in the top toolbar.
|
||||
1. Complete a **CAPTCHA** if one is shown.
|
||||
1. Close by tapping `X` at the top-left.
|
||||
== From a Series
|
||||
1. Open a series.
|
||||
1. Tap the **WebView** icon button.
|
||||
1. Complete a **CAPTCHA** if one is shown.
|
||||
1. Close by tapping `X` at the top-left.
|
||||
:::
|
||||
|
||||
Repeat if needed.
|
||||
Alternatively, try opening the website in your browser using the **Overflow** icon in the WebView screen and solve any **CAPTCHA** there.
|
||||
|
||||
![Open WebView](/docs/guides/troubleshooting/open-webview.dark.webp =1079x520)
|
||||
|
||||
### Clearing cookies and WebView data
|
||||
This resets your WebView to a clean state, including any login states.
|
||||
|
||||
1. Navigate to <nav to="advanced">.
|
||||
1. Tap **Clear cookies**.
|
||||
1. Tap **Clear WebView data**.
|
||||
|
||||
### WebView update
|
||||
To update WebView, you need to find what WebView implementation is used on your device.
|
||||
|
||||
Typical default implementation depends on the Android version as follows:
|
||||
|
||||
::: tabs
|
||||
== Android 10 and above
|
||||
[Android System WebView](https://play.google.com/store/apps/details?id=com.google.android.webview)
|
||||
== Android 7 - 9
|
||||
[Google Chrome](https://play.google.com/store/apps/details?id=com.android.chrome)
|
||||
== Android 6 and below
|
||||
[Android System WebView](https://play.google.com/store/apps/details?id=com.google.android.webview)
|
||||
:::
|
||||
|
||||
::: tip **Android 7** and above
|
||||
Newer Android users can check/change WebView in [Developer Options](https://developer.android.com/studio/debug/dev-options).
|
||||
:::
|
||||
|
||||
::: warning Caution with Non-Standard WebView
|
||||
Using non-standard WebView (like Firefox) might cause **Tachiyomi** to malfunction or crash.
|
||||
|
||||
It's best to use the standard [Android System WebView](https://play.google.com/store/apps/details?id=com.google.android.webview) or [Google Chrome](https://play.google.com/store/apps/details?id=com.android.chrome).
|
||||
:::
|
||||
|
||||
## Cloudflare
|
||||
|
||||
**Cloudflare**, an anti-bot mechanism, is used by some sources.
|
||||
Some sources intentionally have higher **Cloudflare** protection to deter apps like **Tachiyomi**.
|
||||
|
||||
### Dealing with Cloudflare looping
|
||||
Certain sources may employ more advanced **Cloudflare** protection, leading to WebView continuously reloading when bypassing using the above solution.
|
||||
If this occurs, try [Accessing the Website via WebView](#accessing-websites-via-webview).
|
||||
|
||||
### Changing your user agent
|
||||
A user agent string shares requester information with websites, potentially affecting **Cloudflare**'s bot detection.
|
||||
While some sources have specific user agent strings, most rely on the app's default.
|
||||
|
||||
::: info Changing your user agent
|
||||
1. Navigate to <nav to="advanced">.
|
||||
1. Modify **Default user agent string** to another value. You may need to experiment to find one that works.
|
||||
> [Here's a reference](https://www.whatismybrowser.com/guides/the-latest-user-agent/).
|
||||
1. Restart the app and retry source access.
|
||||
:::
|
||||
|
||||
::: tip Did these methods not work?
|
||||
Wait for the source to lower its protection or switch to different sources.
|
||||
:::
|
||||
|
||||
## General
|
||||
|
||||
### Obtaining crash/error logs
|
||||
For crash investigations, navigate to <nav to="advanced"> and tap **Dump crash logs**.
|
||||
|
||||
![Dump crashlogs](/docs/guides/troubleshooting/dump-crash-logs.dark.webp =512x386)
|
||||
|
||||
### Obtaining more logs
|
||||
To diagnose abnormal app behavior, record device logs using a [Logcat Reader](https://play.google.com/store/apps/details?id=com.dp.logcatapp).
|
||||
|
||||
### App or extension installation issues
|
||||
Encountering problems while trying to install app or extension `.apk` files?
|
||||
Follow these steps:
|
||||
|
||||
1. Install [Split APK Installer](https://play.google.com/store/apps/details?id=com.aefyr.sai) from the Google Play Store.
|
||||
1. Try installing your `.apk` from Split APK Installer.
|
||||
|
||||
**Split APK Installer** helps show better error messages or may even successfully install your `.apk` without issue.
|
||||
Common errors include:
|
||||
|
||||
::: details `INSTALL_FAILED_UPDATE_INCOMPATIBLE: Package eu.kanade.tachiyomi signatures do not match the previously installed version; ignoring!`
|
||||
Seeing this error while installing the `.apk` over an existing **F-Droid** build indicates a mismatch in signatures.
|
||||
Data backup, uninstall, and fresh installation are required.
|
||||
:::
|
||||
|
||||
::: details `DISPLAY_NAME column is null`
|
||||
Seeing this error points to a corrupted `.apk`.
|
||||
Try redownloading the `.apk`.
|
||||
:::
|
||||
|
||||
::: details `INSTALL_FAILED_NO_MATCHING_ABIS`
|
||||
Seeing this error suggests the `.apk` is incompatible with your CPU architecture.
|
||||
Obtain the appropriate version or a universal `.apk` (i.e. the option with largest file size on GitHub).
|
||||
:::
|
24
website/src/download/index.md
Normal file
24
website/src/download/index.md
Normal file
@ -0,0 +1,24 @@
|
||||
---
|
||||
title: Download
|
||||
description: Download page that allows users to access and install the latest version of the app.
|
||||
lastUpdated: false
|
||||
editLink: false
|
||||
prev: false
|
||||
next: false
|
||||
---
|
||||
|
||||
<script setup>
|
||||
import DownloadButtons from "@theme/components/DownloadButtons.vue";
|
||||
import ReleaseDate from "@theme/components/ReleaseDate.vue";
|
||||
import Changelog from "@theme/components/Changelog.vue";
|
||||
</script>
|
||||
|
||||
# Download
|
||||
|
||||
The latest stable version of **Tachiyomi** was released **<ReleaseDate type="stable" />** and the latest preview version was released **<ReleaseDate type="preview" />**.
|
||||
|
||||
Preview releases are intended for testing upcoming changes and may not be a stable experience.
|
||||
|
||||
<DownloadButtons />
|
||||
|
||||
<Changelog type="stable" />
|
18
website/src/extensions/index.md
Normal file
18
website/src/extensions/index.md
Normal file
@ -0,0 +1,18 @@
|
||||
---
|
||||
title: Extensions
|
||||
description: Browse and download sources for Tachiyomi.
|
||||
lastUpdated: false
|
||||
editLink: false
|
||||
prev: false
|
||||
next: false
|
||||
---
|
||||
|
||||
# Extensions
|
||||
|
||||
Extensions can also be installed directly from the app.
|
||||
|
||||
<ExtensionsWrapper/>
|
||||
|
||||
<script setup>
|
||||
import ExtensionsWrapper from '@theme/components/Extensions/ExtensionsWrapper.vue'
|
||||
</script>
|
64
website/src/forks/Neko/index.md
Normal file
64
website/src/forks/Neko/index.md
Normal file
@ -0,0 +1,64 @@
|
||||
---
|
||||
title: Neko
|
||||
titleTemplate: false
|
||||
description: Features specific to MangaDex with the featureset of TachiyomiJ2K
|
||||
|
||||
layout: home
|
||||
pageClass: page-neko
|
||||
|
||||
hero:
|
||||
name: Neko
|
||||
text: For MangaDex
|
||||
tagline: Features specific to MangaDex with the featureset of TachiyomiJ2K
|
||||
image: /forks/logo-neko.webp
|
||||
actions:
|
||||
- theme: brand
|
||||
text: Download
|
||||
link: https://github.com/CarlosEsco/Neko/releases/latest
|
||||
- theme: alt
|
||||
text: GitHub
|
||||
link: https://github.com/CarlosEsco/Neko
|
||||
|
||||
customMetaTitle: Neko
|
||||
|
||||
features:
|
||||
- title: MangaDex
|
||||
details: Native login with support for MDList as a tracker, and syncing the MangaDex follows list.
|
||||
icon: <img src="/img/logo-mangadex.svg" alt="MangaDex Logo" height="32" width="32">
|
||||
- title: Similiar Series
|
||||
details: Recommendation system
|
||||
icon: 📚
|
||||
- title: Merging missing chapters
|
||||
details: If anything is missing on MangaDex you can attempt to merge them with another source.
|
||||
icon: ❔
|
||||
|
||||
theme: "#FD6684"
|
||||
image: /forks/logo-neko.webp
|
||||
imageSize: small
|
||||
---
|
||||
|
||||
<br><VPTeamMembers size="small" :members="members" />
|
||||
|
||||
<script setup>
|
||||
import "@theme/styles/forks/neko.styl"
|
||||
import { VPTeamMembers } from "vitepress/theme"
|
||||
|
||||
const members = [
|
||||
{
|
||||
avatar: "https://www.github.com/CarlosEsco.png",
|
||||
name: "CarlosEsco",
|
||||
title: "Creator",
|
||||
links: [
|
||||
{ icon: "github", link: "https://github.com/CarlosEsco" }
|
||||
]
|
||||
},
|
||||
{
|
||||
avatar: "https://www.github.com/Jays2Kings.png",
|
||||
name: "Jays2Kings",
|
||||
title: "Fork base",
|
||||
links: [
|
||||
{ icon: "github", link: "https://github.com/Jays2Kings" }
|
||||
]
|
||||
}
|
||||
]
|
||||
</script>
|
82
website/src/forks/TachiyomiAZ/index.md
Normal file
82
website/src/forks/TachiyomiAZ/index.md
Normal file
@ -0,0 +1,82 @@
|
||||
---
|
||||
title: TachiyomiAZ
|
||||
titleTemplate: false
|
||||
description: Hentai-focused with legacy features
|
||||
|
||||
layout: home
|
||||
pageClass: page-tachiyomi-az
|
||||
|
||||
hero:
|
||||
name: TachiyomiAZ
|
||||
text: Hentai-focused
|
||||
tagline: Recommendation system, a hamburger menu, and loads of hentai
|
||||
image: /forks/logo-az.webp
|
||||
actions:
|
||||
- theme: brand
|
||||
text: Download
|
||||
link: https://github.com/az4521/TachiyomiAZ/releases/latest
|
||||
- theme: alt
|
||||
text: GitHub
|
||||
link: https://github.com/az4521/tachiyomiAZ
|
||||
|
||||
customMetaTitle: TachiyomiAZ
|
||||
|
||||
features:
|
||||
- title: Hentai-focused
|
||||
details: Adds several features to enhance your Hentai experience.
|
||||
icon: 🔞
|
||||
- title: Series recommendations
|
||||
details: Get recommendations from MyAnimeList and AniDB.
|
||||
icon: 📚
|
||||
- title: Legacy design
|
||||
details: Keeps the old design of Tachiyomi with hamburger menu.
|
||||
icon: 👵
|
||||
|
||||
theme: "#FFCC4D"
|
||||
image: /forks/logo-az.webp
|
||||
imageSize: small
|
||||
---
|
||||
|
||||
<br><VPTeamMembers size="small" :members="members" />
|
||||
|
||||
<script setup>
|
||||
import "@theme/styles/forks/tachiyomi-az.styl"
|
||||
import { VPTeamMembers } from "vitepress/theme"
|
||||
|
||||
const members = [
|
||||
{
|
||||
avatar: "https://www.github.com/az4521.png",
|
||||
name: "az4521",
|
||||
title: "Creator",
|
||||
links: [
|
||||
{ icon: "github", link: "https://github.com/az4521" }
|
||||
]
|
||||
},
|
||||
{
|
||||
avatar: "https://www.github.com/jobobby04.png",
|
||||
name: "jobobby04",
|
||||
title: "Former Maintainer",
|
||||
links: [
|
||||
{ icon: "github", link: "https://github.com/jobobby04" }
|
||||
]
|
||||
},
|
||||
{
|
||||
avatar: "https://www.github.com/NerdNumber9.png",
|
||||
name: "NerdNumber9",
|
||||
title: "Original EH Fork",
|
||||
links: [
|
||||
{ icon: "github", link: "https://github.com/NerdNumber9" }
|
||||
]
|
||||
}
|
||||
]
|
||||
</script>
|
||||
|
||||
<br>
|
||||
|
||||
<div class="azContainer">
|
||||
<div class="azMarquee">
|
||||
<div class="azWiggleText">
|
||||
<span class="azText"><i>"The BEST fork"</i> - az4521</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
56
website/src/forks/TachiyomiJ2K/index.md
Normal file
56
website/src/forks/TachiyomiJ2K/index.md
Normal file
@ -0,0 +1,56 @@
|
||||
---
|
||||
title: TachiyomiJ2K
|
||||
titleTemplate: false
|
||||
description: New design approach along with several other enhancements
|
||||
|
||||
layout: home
|
||||
pageClass: page-tachiyomi-j2k
|
||||
|
||||
hero:
|
||||
name: TachiyomiJ2K
|
||||
text: Redesigned
|
||||
tagline: New design approach along with several other enhancements
|
||||
image: /forks/logo-j2k.webp
|
||||
actions:
|
||||
- theme: brand
|
||||
text: Download
|
||||
link: https://github.com/Jays2Kings/tachiyomiJ2K/releases/latest
|
||||
- theme: alt
|
||||
text: GitHub
|
||||
link: https://github.com/Jays2Kings/tachiyomiJ2K
|
||||
|
||||
customMetaTitle: TachiyomiJ2K
|
||||
|
||||
features:
|
||||
- title: Redesigned UI
|
||||
details: Entirely different design from regular Tachiyomi, with exciting new features.
|
||||
icon: 👑
|
||||
- title: Double-page for Tablets
|
||||
details: Combine 2 pages while reading into a single one for a better tablet experience.
|
||||
icon: 📖
|
||||
- title: Dynamic categories
|
||||
details: Group your library automatically by the tags, tracking status, source, and more.
|
||||
icon: 🔖
|
||||
|
||||
theme: "#0952AF"
|
||||
image: /forks/logo-j2k.webp
|
||||
imageSize: small
|
||||
---
|
||||
|
||||
<br><VPTeamMembers size="small" :members="members" />
|
||||
|
||||
<script setup>
|
||||
import "@theme/styles/forks/tachiyomi-j2k.styl"
|
||||
import { VPTeamMembers } from "vitepress/theme"
|
||||
|
||||
const members = [
|
||||
{
|
||||
avatar: "https://www.github.com/Jays2Kings.png",
|
||||
name: "Jays2Kings",
|
||||
title: "Creator",
|
||||
links: [
|
||||
{ icon: "github", link: "https://github.com/Jays2Kings" }
|
||||
]
|
||||
}
|
||||
]
|
||||
</script>
|
72
website/src/forks/TachiyomiSY/index.md
Normal file
72
website/src/forks/TachiyomiSY/index.md
Normal file
@ -0,0 +1,72 @@
|
||||
---
|
||||
title: TachiyomiSY
|
||||
titleTemplate: false
|
||||
description: Keeping up-to-date with Tachiyomi while also adding exclusive features
|
||||
|
||||
layout: home
|
||||
pageClass: page-tachiyomi-sy
|
||||
|
||||
hero:
|
||||
name: TachiyomiSY
|
||||
text: Hentai-focused
|
||||
tagline: Keeping up-to-date with Tachiyomi while also adding exclusive features
|
||||
image: /forks/logo-sy.webp
|
||||
actions:
|
||||
- theme: brand
|
||||
text: Download
|
||||
link: https://github.com/jobobby04/TachiyomiSY/releases/latest
|
||||
- theme: alt
|
||||
text: GitHub
|
||||
link: https://github.com/jobobby04/TachiyomiSY
|
||||
|
||||
customMetaTitle: TachiyomiSY
|
||||
|
||||
features:
|
||||
- title: Hentai-focused
|
||||
details: Adds several features to enhance your Hentai experience.
|
||||
icon: 🔞
|
||||
- title: Series recommendations
|
||||
details: Get recommendations from MyAnimeList and AniDB.
|
||||
icon: 📚
|
||||
- title: Autoscroll
|
||||
details: Allows you to read without any extra interaction.
|
||||
icon: 📜
|
||||
|
||||
theme: "#CE2828"
|
||||
image: /forks/logo-sy.webp
|
||||
imageSize: small
|
||||
---
|
||||
|
||||
<br><VPTeamMembers size="small" :members="members" />
|
||||
|
||||
<script setup>
|
||||
import "@theme/styles/forks/tachiyomi-sy.styl"
|
||||
import { VPTeamMembers } from "vitepress/theme"
|
||||
|
||||
const members = [
|
||||
{
|
||||
avatar: "https://www.github.com/jobobby04.png",
|
||||
name: "jobobby04",
|
||||
title: "Creator",
|
||||
links: [
|
||||
{ icon: "github", link: "https://github.com/jobobby04" }
|
||||
]
|
||||
},
|
||||
{
|
||||
avatar: "https://www.github.com/she11sh0cked.png",
|
||||
name: "she11sh0cked",
|
||||
title: "Extra tracking, filter, recommendations, and more",
|
||||
links: [
|
||||
{ icon: "github", link: "https://github.com/she11sh0cked" }
|
||||
]
|
||||
},
|
||||
{
|
||||
avatar: "https://www.github.com/az4521.png",
|
||||
name: "az4521",
|
||||
title: "Base recommendations and AZ",
|
||||
links: [
|
||||
{ icon: "github", link: "https://github.com/az4521" }
|
||||
]
|
||||
}
|
||||
]
|
||||
</script>
|
37
website/src/forks/index.md
Normal file
37
website/src/forks/index.md
Normal file
@ -0,0 +1,37 @@
|
||||
---
|
||||
title: Endorsed Forks
|
||||
description: Forks are alternative versions of Tachiyomi with exclusive features.
|
||||
lastUpdated: false
|
||||
editLink: false
|
||||
prev: false
|
||||
next: false
|
||||
pageClass: forks
|
||||
|
||||
features:
|
||||
- title: Neko
|
||||
details: Features specific to MangaDex with the feature set of TachiyomiJ2K
|
||||
icon: <img src="/forks/logo-neko.webp" alt="Neko Logo" height="32" width="32">
|
||||
link: /forks/Neko/
|
||||
- title: TachiyomiJ2K
|
||||
details: New design approach along with several other enhancements
|
||||
icon: <img src="/forks/logo-j2k.webp" alt="TachiyomiJ2K Logo" height="32" width="32">
|
||||
link: /forks/TachiyomiJ2K/
|
||||
- title: TachiyomiSY
|
||||
details: Keeping up-to-date with Tachiyomi while also adding exclusive features
|
||||
icon: <img src="/forks/logo-sy.webp" alt="TachiyomiSY Logo" height="32" width="32">
|
||||
link: /forks/TachiyomiSY/
|
||||
- title: TachiyomiAZ
|
||||
details: Keeps the old design of Tachiyomi with hamburger menu.
|
||||
icon: <img src="/forks/logo-az.webp" alt="TachiyomiAZ Logo" height="32" width="32">
|
||||
link: /forks/TachiyomiAZ/
|
||||
---
|
||||
|
||||
<script setup>
|
||||
import { VPHomeFeatures } from "vitepress/theme"
|
||||
</script>
|
||||
|
||||
# Endorsed Forks
|
||||
|
||||
Forks are alternative versions of Tachiyomi with exclusive features.
|
||||
|
||||
<VPHomeFeatures />
|
37
website/src/index.md
Normal file
37
website/src/index.md
Normal file
@ -0,0 +1,37 @@
|
||||
---
|
||||
title: Home
|
||||
layout: home
|
||||
|
||||
hero:
|
||||
name: Tachiyomi
|
||||
text: Full-featured reader
|
||||
tagline: Discover and read manga, webtoons, comics, and more – easier than ever on your Android device.
|
||||
image:
|
||||
alt: Tachiyomi Library Tab Dark
|
||||
light: /home/phone.light.webp
|
||||
dark: /home/phone.dark.webp
|
||||
actions:
|
||||
- theme: brand
|
||||
text: Get Started
|
||||
link: /docs/guides/getting-started
|
||||
- theme: alt
|
||||
text: Download
|
||||
link: /download/
|
||||
|
||||
customMetaTitle: Tachiyomi
|
||||
|
||||
features:
|
||||
- title: Tracking
|
||||
details: Automatically keep track of your series with MyAnimeList, AniList, Kitsu, and more.
|
||||
icon: <svg height="24" width="24" viewBox="0 -960 960 960" fill="var(--vp-c-green-2)" xmlns="http://www.w3.org/2000/svg"><path d="M160-160v-80h110l-16-14q-52-46-73-105t-21-119q0-111 66.5-197.5T400-790v84q-72 26-116 88.5T240-478q0 45 17 87.5t53 78.5l10 10v-98h80v240H160Zm400-10v-84q72-26 116-88.5T720-482q0-45-17-87.5T650-648l-10-10v98h-80v-240h240v80H690l16 14q49 49 71.5 106.5T800-482q0 111-66.5 197.5T560-170Z"/></svg>
|
||||
link: /docs/guides/tracking
|
||||
linkText: Setup tracking
|
||||
- title: Extensions
|
||||
details: Online and offline reading from over a thousand sources.
|
||||
icon: <svg height="24" width="24" viewBox="0 -960 960 960" fill="var(--vp-c-yellow-2)" xmlns="http://www.w3.org/2000/svg"><path d="M352-120H200q-33 0-56.5-23.5T120-200v-152q48 0 84-30.5t36-77.5q0-47-36-77.5T120-568v-152q0-33 23.5-56.5T200-800h160q0-42 29-71t71-29q42 0 71 29t29 71h160q33 0 56.5 23.5T800-720v160q42 0 71 29t29 71q0 42-29 71t-71 29v160q0 33-23.5 56.5T720-120H568q0-50-31.5-85T460-240q-45 0-76.5 35T352-120Zm-152-80h85q24-66 77-93t98-27q45 0 98 27t77 93h85v-240h80q8 0 14-6t6-14q0-8-6-14t-14-6h-80v-240H480v-80q0-8-6-14t-14-6q-8 0-14 6t-6 14v80H200v88q54 20 87 67t33 105q0 57-33 104t-87 68v88Zm310-310Z"/></svg>
|
||||
link: /extensions/
|
||||
linkText: Find extensions
|
||||
- title: Customization
|
||||
details: Make it yours with multiple reading modes, custom color filters, and many other settings.
|
||||
icon: <svg height="24" width="24" viewBox="0 -960 960 960" fill="var(--vp-c-indigo-2)" xmlns="http://www.w3.org/2000/svg"><path d="M440-120v-240h80v80h320v80H520v80h-80Zm-320-80v-80h240v80H120Zm160-160v-80H120v-80h160v-80h80v240h-80Zm160-80v-80h400v80H440Zm160-160v-240h80v80h160v80H680v80h-80Zm-480-80v-80h400v80H120Z"/></svg>
|
||||
---
|
26
website/src/news/2023-09-09-updated-website.md
Normal file
26
website/src/news/2023-09-09-updated-website.md
Normal file
@ -0,0 +1,26 @@
|
||||
---
|
||||
type: article
|
||||
title: Updated website
|
||||
description: We've got a fresh new website we hope will be even easier to use
|
||||
date: 2023-09-09
|
||||
---
|
||||
|
||||
# Updated website
|
||||
|
||||
We've launched a fully rewritten website with improved usability for both users
|
||||
and contributors. Some improvements include the [download](/download/) page,
|
||||
which now doesn't require any new additional requests, and a new [changelogs](/changelogs/)
|
||||
page to easily see historical app release notes.
|
||||
|
||||
This page is part of the new [news](/news/) section, which will be used in occasions
|
||||
like this to keep you informed of any interesting updates.
|
||||
|
||||
Under the hood, we're now using [VitePress](https://vitepress.dev/) with
|
||||
[Vue.js](https://vuejs.org/) v3, replacing our previous usage of
|
||||
[VuePress](https://vuepress.vuejs.org/).
|
||||
|
||||
As we're still in the process of rewritting and updating part of the
|
||||
documentation and guides, there may still be some errors or outdated content.
|
||||
If you find any, please report by opening an issue in the [repository for this website](https://github.com/tachiyomiorg/website/issues/new/choose) on GitHub.
|
||||
|
||||
*The Tachiyomi Team*.
|
21
website/src/news/index.md
Normal file
21
website/src/news/index.md
Normal file
@ -0,0 +1,21 @@
|
||||
---
|
||||
title: News
|
||||
description: Collection of news and announcements about Tachiyomi.
|
||||
lastUpdated: false
|
||||
editLink: false
|
||||
prev: false
|
||||
next: false
|
||||
---
|
||||
|
||||
<script setup>
|
||||
import News from "@theme/components/News.vue";
|
||||
import RssLink from "@theme/components/RssLink.vue";
|
||||
</script>
|
||||
|
||||
# News
|
||||
|
||||
Collection of news and announcements about Tachiyomi.
|
||||
|
||||
Also available as <RssLink />.
|
||||
|
||||
<News />
|
60
website/src/privacy/index.md
Normal file
60
website/src/privacy/index.md
Normal file
@ -0,0 +1,60 @@
|
||||
---
|
||||
title: Privacy policy
|
||||
description: Privacy Policy that explains how Tachiyomi collects, uses, and protects users' personal information.
|
||||
lastUpdated: false
|
||||
editLink: false
|
||||
---
|
||||
|
||||
# Privacy policy
|
||||
|
||||
Tachiyomi is an Open Source app.
|
||||
This SERVICE is provided at no cost and is intended for use as is.
|
||||
|
||||
This page details our policies with the collection, use, and disclosure of Personal Information if anyone decided to use our Service.
|
||||
|
||||
If you choose to use our Service, then you agree to the collection and use of information in relation to this policy.
|
||||
The Personal Information that we collect is used for providing and improving the Service.
|
||||
We will not use or share your information with anyone except as described in this Privacy Policy.
|
||||
|
||||
## Information Collection and Use
|
||||
|
||||
For a better experience, while using our Service, we may require you to provide us with certain personally identifiable information.
|
||||
The information that we request will be retained by us and used as described in this privacy policy.
|
||||
|
||||
Links to the privacy policy of third-party service providers used by the app:
|
||||
* [Google Analytics for Firebase](https://firebase.google.com/policies/analytics)
|
||||
* [Sentry](https://sentry.io/privacy/)
|
||||
|
||||
### Log Data
|
||||
|
||||
In a case of an error in the app, the Service automatically collects data and information called Log Data.
|
||||
This Log Data may include information including your device name, operating system version, the configuration of the app when utilizing our Service, the time and date of your use of the Service, and other statistics.
|
||||
|
||||
This can be disabled within the app.
|
||||
|
||||
### Analytics Data
|
||||
|
||||
The Service is integrated with [Firebase](https://firebase.google.com/) to collect anonymized analytics data about Service usage.
|
||||
|
||||
For more information, you can refer to [How Google uses data when you use our partners' sites or apps](https://google.com/policies/privacy/partners/).
|
||||
|
||||
## External Links
|
||||
|
||||
This Service contains links to other sites.
|
||||
If you click on a third-party link, you will be directed to that site.
|
||||
Note that these external sites are not operated by us.
|
||||
Therefore, we strongly advise you to review the Privacy Policy of these websites.
|
||||
We have no control over and assume no responsibility for the content, privacy policies, or practices of any third-party sites or services.
|
||||
|
||||
This includes the use of external tracking services (e.g. MyAnimeList).
|
||||
|
||||
## Changes to This Privacy Policy
|
||||
|
||||
We may periodically update our Privacy Policy.
|
||||
Thus, you are advised to review this page periodically for any changes.
|
||||
|
||||
The current iteration of this policy is effective as of December 15, 2021.
|
||||
|
||||
## Contact Us
|
||||
|
||||
If you have any questions or suggestions about this Privacy Policy, do not hesitate to reach out to us on [our Discord server](https://discord.gg/tachiyomi).
|
8
website/src/public/_headers
Normal file
8
website/src/public/_headers
Normal file
@ -0,0 +1,8 @@
|
||||
# Cache headers for media files for the Google recommended of 6 months minimum.
|
||||
*.webp
|
||||
*.webm
|
||||
*.png
|
||||
*.jpg
|
||||
*.svg
|
||||
*.ico
|
||||
Cache-Control: public, max-age=15552000
|
12
website/src/public/_redirects
Normal file
12
website/src/public/_redirects
Normal file
@ -0,0 +1,12 @@
|
||||
# Requested URL -> Redirected URL -> HTTP Code
|
||||
/help/ / 301
|
||||
/help/guides/getting-started/ /docs/guides/getting-started 301
|
||||
/help/guides/troubleshooting/ /docs/guides/troubleshooting/ 301
|
||||
/help/guides/source-migration/ /docs/guides/source-migration 301
|
||||
/help/guides/backups/ /docs/guides/backups 301
|
||||
/help/guides/tracking/ /docs/guides/tracking 301
|
||||
/help/guides/categories/ /docs/guides/categories 301
|
||||
/help/guides/local-manga/ /docs/guides/local-source/ 301
|
||||
/help/guides/reader-settings/ /docs/guides/reader-settings 301
|
||||
/help/contribution/ /docs/contribute 301
|
||||
/help/faq/ /docs/faq/general 301
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user