├── .github ├── ISSUE_TEMPLATE │ ├── config.yml │ ├── report_issue.yml │ └── request_feature.yml ├── assets │ ├── logo.png │ └── mihon-handbrake-profile.json ├── renovate.json └── workflows │ └── deploy.yml ├── .gitignore ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── netlify.toml └── website ├── .editorconfig ├── .markdownlint.json ├── .markdownlintignore ├── .npmrc ├── .nvmrc ├── .stylintrc ├── eslint.config.js ├── package.json ├── pnpm-lock.yaml ├── src ├── .vitepress │ ├── config.ts │ ├── config │ │ ├── constants.ts │ │ ├── headConfig.ts │ │ ├── hooks │ │ │ ├── generateFeed.ts │ │ │ ├── generateMeta.ts │ │ │ └── generateOgImages.ts │ │ ├── markdownConfig.ts │ │ ├── navigation │ │ │ ├── navbar.ts │ │ │ └── sidebar.ts │ │ ├── scripts │ │ │ └── languages.ts │ │ ├── shortcodes.ts │ │ └── themeConfig.ts │ ├── fonts │ │ ├── Inter-Bold.otf │ │ ├── Inter-Medium.otf │ │ ├── Inter-Regular.otf │ │ └── Inter-SemiBold.otf │ ├── theme │ │ ├── Layout.vue │ │ ├── components │ │ │ ├── AddRepoButton.vue │ │ │ ├── Changelog.vue │ │ │ ├── ChangelogsList.vue │ │ │ ├── Contributors.vue │ │ │ ├── CustomNavBarMenu.vue │ │ │ ├── CustomNavScreenMenu.vue │ │ │ ├── CustomSwitchAppearance.vue │ │ │ ├── DownloadButtons.vue │ │ │ ├── News.vue │ │ │ ├── OgImageTemplate.vue │ │ │ ├── ReleaseDate.vue │ │ │ └── RssLink.vue │ │ ├── data │ │ │ ├── changelogs.data.ts │ │ │ ├── news.data.ts │ │ │ └── release.data.ts │ │ ├── index.ts │ │ ├── plugin │ │ │ └── analytics.ts │ │ ├── queries │ │ │ └── useExtensionsRepositoryQuery.ts │ │ └── styles │ │ │ ├── base.styl │ │ │ ├── forks │ │ │ ├── komikku.styl │ │ │ ├── lint.styl │ │ │ ├── tachiyomi-az.styl │ │ │ ├── tachiyomi-j2k.styl │ │ │ ├── tachiyomi-sy.styl │ │ │ └── yokai.styl │ │ │ └── tree.styl │ └── vue-shim.d.ts ├── changelogs │ └── index.md ├── docs │ ├── contribute.md │ ├── faq │ │ ├── browse │ │ │ ├── extensions.md │ │ │ ├── index.md │ │ │ └── local-source.md │ │ ├── downloads.md │ │ ├── general.md │ │ ├── library.md │ │ ├── reader.md │ │ ├── settings.md │ │ ├── storage.md │ │ └── updates │ │ │ ├── smart.md │ │ │ └── upcoming.md │ └── guides │ │ ├── backups.md │ │ ├── categories.md │ │ ├── getting-started.md │ │ ├── local-source │ │ ├── advanced.md │ │ └── index.md │ │ ├── reader-settings.md │ │ ├── shizuku.md │ │ ├── source-migration.md │ │ ├── tracking.md │ │ └── troubleshooting │ │ ├── common-issues.md │ │ ├── diagnosis.md │ │ └── index.md ├── download │ └── index.md ├── forks │ ├── Komikku │ │ └── index.md │ ├── TachiyomiAZ │ │ └── index.md │ ├── TachiyomiJ2K │ │ └── index.md │ ├── TachiyomiSY │ │ └── index.md │ ├── Yokai │ │ └── index.md │ └── index.md ├── index.md ├── news │ └── index.md ├── privacy │ └── index.md ├── public │ ├── _headers │ ├── _redirects │ ├── android-chrome-192x192.png │ ├── android-chrome-512x512.png │ ├── apple-touch-icon.png │ ├── browserconfig.xml │ ├── docs │ │ ├── faq │ │ │ └── browse │ │ │ │ └── extensions │ │ │ │ ├── unknown-sources-A10.light.webm │ │ │ │ └── unknown-sources-A7.light.webm │ │ └── guides │ │ │ ├── backups │ │ │ ├── automatic_backups.dark.webp │ │ │ ├── automatic_backups.light.webp │ │ │ ├── backup.dark.webp │ │ │ └── backup.light.webp │ │ │ ├── reader-settings │ │ │ ├── animation-speed_fast.webp │ │ │ ├── animation-speed_normal.webp │ │ │ ├── animation-speed_off.webp │ │ │ ├── background-color_black.webp │ │ │ ├── background-color_gray.webp │ │ │ ├── background-color_white.webp │ │ │ ├── crop-borders_off.webp │ │ │ ├── crop-borders_on.webp │ │ │ ├── scale-type_fit-height.webp │ │ │ ├── scale-type_fit-screen.webp │ │ │ ├── scale-type_fit-width.webp │ │ │ ├── scale-type_original-size.webp │ │ │ ├── scale-type_smart-fit.webp │ │ │ ├── scale-type_stretch.webp │ │ │ ├── tap-zones_edge.webp │ │ │ ├── tap-zones_kindle-ish.webp │ │ │ ├── tap-zones_l-shaped.webp │ │ │ ├── tap-zones_right-and-left.webp │ │ │ ├── zoom-start-position_center.webp │ │ │ ├── zoom-start-position_left.webp │ │ │ └── zoom-start-position_right.webp │ │ │ └── troubleshooting │ │ │ └── dump-crash-logs.dark.webp │ ├── favicon-16x16.png │ ├── favicon-32x32.png │ ├── favicon.ico │ ├── forks │ │ ├── logo-az.webp │ │ ├── logo-j2k.webp │ │ ├── logo-komikku.webp │ │ ├── logo-sy.webp │ │ └── logo-yokai.webp │ ├── home │ │ ├── extra │ │ │ ├── screenshot.dark.webp │ │ │ └── screenshot.light.webp │ │ ├── phone.dark.webp │ │ └── phone.light.webp │ ├── img │ │ ├── folder.svg │ │ ├── jpeg.svg │ │ ├── logo-128px.png │ │ ├── logo-netlify.svg │ │ ├── logo.png │ │ ├── logo.webp │ │ ├── mihon-64px.png │ │ ├── mihon.png │ │ ├── mihon.svg │ │ ├── open-graph-background.png │ │ ├── open-graph-background.webp │ │ └── zip.svg │ ├── mstile-150x150.png │ ├── safari-pinned-tab.svg │ └── site.webmanifest └── sandbox │ └── index.md ├── stylelint.config.js ├── tsconfig.json └── tsconfig.node.json /.github/ISSUE_TEMPLATE/config.yml: -------------------------------------------------------------------------------- 1 | contact_links: 2 | - name: ⚠️ Application issue 3 | url: https://github.com/mihonapp/mihon/issues/new/choose 4 | about: Issues and requests about the app itself should be opened in the Mihon repository instead 5 | - name: 🖥️ Website 6 | url: https://mihon.app/docs/guides/getting-started 7 | about: Guides, troubleshooting, and answers to common questions 8 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/report_issue.yml: -------------------------------------------------------------------------------- 1 | name: 🐞 Website Issue report 2 | description: Report an issue with the mihon.app website 3 | labels: [Bug] 4 | body: 5 | 6 | - type: checkboxes 7 | id: acknowledgements 8 | attributes: 9 | label: Acknowledgements 10 | description: Read this carefully, we will close and ignore your issue if you skimmed through this. 11 | options: 12 | - label: I have searched the existing issues and this is a new ticket, **NOT** a duplicate or related to another open issue. 13 | required: true 14 | - label: I have written a short but informative title. 15 | required: true 16 | - label: I will fill out all of the requested information in this form. 17 | required: true 18 | 19 | - type: textarea 20 | id: reproduce-steps 21 | attributes: 22 | label: Steps to reproduce 23 | description: Provide an example of the issue. 24 | placeholder: | 25 | Example: 26 | 1. First step 27 | 2. Second step 28 | 3. Issue here 29 | validations: 30 | required: true 31 | 32 | - type: textarea 33 | id: expected-behavior 34 | attributes: 35 | label: Expected behavior 36 | description: Explain what you should expect to happen. 37 | placeholder: | 38 | Example: 39 | "This should happen..." 40 | validations: 41 | required: true 42 | 43 | - type: textarea 44 | id: actual-behavior 45 | attributes: 46 | label: Actual behavior 47 | description: Explain what actually happens. 48 | placeholder: | 49 | Example: 50 | "This happened instead..." 51 | validations: 52 | required: true 53 | 54 | - type: textarea 55 | id: other-details 56 | attributes: 57 | label: Other details 58 | placeholder: | 59 | Additional details and attachments. 60 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/request_feature.yml: -------------------------------------------------------------------------------- 1 | name: ⭐ Website Feature request 2 | description: Suggest a feature to improve the mihon.app website 3 | labels: [Feature request] 4 | body: 5 | 6 | - type: checkboxes 7 | id: acknowledgements 8 | attributes: 9 | label: Acknowledgements 10 | description: Read this carefully, we will close and ignore your issue if you skimmed through this. 11 | options: 12 | - label: I have searched the existing issues and this is a new ticket, **NOT** a duplicate or related to another open issue. 13 | required: true 14 | - label: I have written a short but informative title. 15 | required: true 16 | - label: I will fill out all of the requested information in this form. 17 | required: true 18 | 19 | - type: textarea 20 | id: feature-description 21 | attributes: 22 | label: Describe your suggested feature 23 | description: How can the website be improved? 24 | placeholder: | 25 | Example: 26 | "It should work like this..." 27 | validations: 28 | required: true 29 | 30 | - type: textarea 31 | id: other-details 32 | attributes: 33 | label: Other details 34 | placeholder: | 35 | Additional details and attachments. 36 | -------------------------------------------------------------------------------- /.github/assets/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mihonapp/website/bbbb8600e939dd156ed22e15ab79815da2a4f03a/.github/assets/logo.png -------------------------------------------------------------------------------- /.github/assets/mihon-handbrake-profile.json: -------------------------------------------------------------------------------- 1 | { 2 | "PresetList": [ 3 | { 4 | "AlignAVStart": false, 5 | "AudioCopyMask": [ 6 | "copy:aac", 7 | "copy:ac3", 8 | "copy:dtshd", 9 | "copy:dts", 10 | "copy:mp3", 11 | "copy:truehd", 12 | "copy:flac", 13 | "copy:eac3" 14 | ], 15 | "AudioEncoderFallback": "ac3", 16 | "AudioLanguageList": [], 17 | "AudioList": [ 18 | { 19 | "AudioBitrate": 128, 20 | "AudioCompressionLevel": 0.0, 21 | "AudioEncoder": "av_aac", 22 | "AudioMixdown": "stereo", 23 | "AudioNormalizeMixLevel": false, 24 | "AudioSamplerate": "auto", 25 | "AudioTrackQualityEnable": false, 26 | "AudioTrackQuality": -1.0, 27 | "AudioTrackGainSlider": 0.0, 28 | "AudioTrackDRCSlider": 0.0 29 | } 30 | ], 31 | "AudioSecondaryEncoderMode": true, 32 | "AudioTrackSelectionBehavior": "first", 33 | "ChapterMarkers": false, 34 | "ChildrenArray": [], 35 | "Default": false, 36 | "FileFormat": "av_webm", 37 | "Folder": false, 38 | "FolderOpen": false, 39 | "Mp4HttpOptimize": false, 40 | "Mp4iPodCompatible": false, 41 | "PictureAutoCrop": true, 42 | "PictureBottomCrop": 0, 43 | "PictureLeftCrop": 0, 44 | "PictureRightCrop": 0, 45 | "PictureTopCrop": 0, 46 | "PictureDARWidth": 512, 47 | "PictureDeblockPreset": "off", 48 | "PictureDeblockTune": "medium", 49 | "PictureDeblockCustom": "strength=strong:thresh=20:blocksize=8", 50 | "PictureDeinterlaceFilter": "decomb", 51 | "PictureCombDetectPreset": "default", 52 | "PictureCombDetectCustom": "", 53 | "PictureDeinterlacePreset": "default", 54 | "PictureDeinterlaceCustom": "", 55 | "PictureDenoiseCustom": "", 56 | "PictureDenoiseFilter": "off", 57 | "PictureDenoisePreset": "light", 58 | "PictureDenoiseTune": "none", 59 | "PictureSharpenCustom": "", 60 | "PictureSharpenFilter": "off", 61 | "PictureSharpenPreset": "medium", 62 | "PictureSharpenTune": "none", 63 | "PictureDetelecine": "off", 64 | "PictureDetelecineCustom": "", 65 | "PictureItuPAR": false, 66 | "PictureKeepRatio": true, 67 | "PictureLooseCrop": false, 68 | "PictureModulus": 2, 69 | "PicturePAR": "auto", 70 | "PicturePARWidth": 3452, 71 | "PicturePARHeight": 4369, 72 | "PictureWidth": 0, 73 | "PictureHeight": 0, 74 | "PictureForceHeight": 0, 75 | "PictureForceWidth": 0, 76 | "PresetName": "Mihon HandBrake Profile", 77 | "Type": 1, 78 | "UsesPictureFilters": true, 79 | "UsesPictureSettings": 0, 80 | "SubtitleAddCC": false, 81 | "SubtitleAddForeignAudioSearch": true, 82 | "SubtitleAddForeignAudioSubtitle": false, 83 | "SubtitleBurnBehavior": "foreign", 84 | "SubtitleBurnBDSub": false, 85 | "SubtitleBurnDVDSub": false, 86 | "SubtitleLanguageList": [], 87 | "SubtitleTrackSelectionBehavior": "none", 88 | "VideoAvgBitrate": 0, 89 | "VideoColorMatrixCode": 0, 90 | "VideoEncoder": "VP9", 91 | "VideoFramerate": "24", 92 | "VideoFramerateMode": "pfr", 93 | "VideoGrayScale": false, 94 | "VideoScaler": "swscale", 95 | "VideoPreset": "medium", 96 | "VideoTune": "", 97 | "VideoLevel": "auto", 98 | "VideoOptionExtra": "", 99 | "VideoQualityType": 2, 100 | "VideoQualitySlider": 21.0, 101 | "VideoQSVDecode": false, 102 | "VideoQSVAsyncDepth": 4, 103 | "VideoTwoPass": true, 104 | "VideoTurboTwoPass": false, 105 | "x264UseAdvancedOptions": false 106 | } 107 | ], 108 | "VersionMajor": 42, 109 | "VersionMicro": 0, 110 | "VersionMinor": 0 111 | } 112 | -------------------------------------------------------------------------------- /.github/renovate.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": [ 3 | "config:base" 4 | ], 5 | "labels": [ 6 | "Dependencies" 7 | ], 8 | "packageRules": [ 9 | { 10 | "groupName": "dependencies", 11 | "matchUpdateTypes": ["minor", "patch", "pin", "digest"], 12 | "automerge": true 13 | } 14 | ], 15 | "schedule": ["every sunday"] 16 | } 17 | -------------------------------------------------------------------------------- /.github/workflows/deploy.yml: -------------------------------------------------------------------------------- 1 | name: Deploy 2 | 3 | on: 4 | workflow_dispatch: {} 5 | push: 6 | branches: 7 | - main 8 | repository_dispatch: 9 | types: [app_release] 10 | 11 | env: 12 | VITE_BASE: / 13 | VITE_HOSTNAME: https://mihon.app 14 | 15 | jobs: 16 | deploy: 17 | runs-on: ubuntu-latest 18 | defaults: 19 | run: 20 | working-directory: "website" 21 | 22 | permissions: 23 | contents: read 24 | pages: write 25 | id-token: write 26 | 27 | environment: 28 | name: github-pages 29 | url: ${{ steps.deployment.outputs.page_url }} 30 | 31 | steps: 32 | - name: Checkout 33 | uses: actions/checkout@v4 34 | with: 35 | fetch-depth: 0 36 | 37 | - name: Install Node.js 38 | uses: actions/setup-node@v4 39 | with: 40 | node-version: 22 41 | 42 | - name: Install pnpm 43 | uses: pnpm/action-setup@v3 44 | with: 45 | version: 10 46 | run_install: false 47 | package_json_file: "website/package-lock.json" 48 | 49 | - name: Get pnpm store directory 50 | shell: bash 51 | run: | 52 | echo "STORE_PATH=$(pnpm store path --silent)" >> $GITHUB_ENV 53 | 54 | - uses: actions/cache@v4 55 | name: Setup pnpm cache 56 | with: 57 | path: ${{ env.STORE_PATH }} 58 | key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }} 59 | restore-keys: | 60 | ${{ runner.os }}-pnpm-store- 61 | 62 | - name: Install dependencies 63 | run: pnpm install 64 | 65 | - name: Build 66 | run: pnpm build 67 | 68 | - name: Configure pages 69 | uses: actions/configure-pages@v5 70 | 71 | - name: Upload pages artifact 72 | uses: actions/upload-pages-artifact@v3 73 | with: 74 | path: website/dist 75 | 76 | - name: Deploy 77 | id: deployment 78 | uses: actions/deploy-pages@v4 79 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | !.env.example 2 | *.code-workspace 3 | *.log 4 | *.tgz 5 | .cache 6 | .DS_Store 7 | .env 8 | .env.* 9 | .idea 10 | .temp 11 | .vite_opt_cache 12 | .vscode 13 | /coverage 14 | website/src/client/shared.ts 15 | website/src/node/shared.ts 16 | cache 17 | dist 18 | examples-temp 19 | node_modules 20 | pnpm-global 21 | website/src/.temp 22 | website/src/assets/style/sandbox.styl 23 | temp 24 | TODOs.md 25 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | In the interest of fostering an open and welcoming environment, we as 6 | contributors and maintainers pledge to making participation in our project and 7 | our community a harassment-free experience for everyone, regardless of age, body 8 | size, disability, ethnicity, sex characteristics, gender identity and expression, 9 | level of experience, education, socio-economic status, nationality, personal 10 | appearance, race, religion, or sexual identity and orientation. 11 | 12 | ## Our Standards 13 | 14 | Examples of behavior that contributes to creating a positive environment 15 | include: 16 | 17 | * Using welcoming and inclusive language 18 | * Being respectful of differing viewpoints and experiences 19 | * Gracefully accepting constructive criticism 20 | * Focusing on what is best for the community 21 | * Showing empathy towards other community members 22 | 23 | Examples of unacceptable behavior by participants include: 24 | 25 | * The use of sexualized language or imagery and unwelcome sexual attention or 26 | advances 27 | * Trolling, insulting/derogatory comments, and personal or political attacks 28 | * Public or private harassment 29 | * Publishing others' private information, such as a physical or electronic 30 | address, without explicit permission 31 | * Other conduct which could reasonably be considered inappropriate in a 32 | professional setting 33 | 34 | ## Our Responsibilities 35 | 36 | Project maintainers are responsible for clarifying the standards of acceptable 37 | behavior and are expected to take appropriate and fair corrective action in 38 | response to any instances of unacceptable behavior. 39 | 40 | Project maintainers have the right and responsibility to remove, edit, or 41 | reject comments, commits, code, wiki edits, issues, and other contributions 42 | that are not aligned to this Code of Conduct, or to ban temporarily or 43 | permanently any contributor for other behaviors that they deem inappropriate, 44 | threatening, offensive, or harmful. 45 | 46 | ## Scope 47 | 48 | This Code of Conduct applies both within project spaces and in public spaces 49 | when an individual is representing the project or its community. Examples of 50 | representing a project or community include using an official project e-mail 51 | address, posting via an official social media account, or acting as an appointed 52 | representative at an online or offline event. Representation of a project may be 53 | further defined and clarified by project maintainers. 54 | 55 | ## Enforcement 56 | 57 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 58 | reported by contacting the project team at the Mihon [Discord server](https://discord.gg/mihon). All 59 | complaints will be reviewed and investigated and will result in a response that 60 | is deemed necessary and appropriate to the circumstances. The project team is 61 | obligated to maintain confidentiality with regard to the reporter of an incident. 62 | Further details of specific enforcement policies may be posted separately. 63 | 64 | Project maintainers who do not follow or enforce the Code of Conduct in good 65 | faith may face temporary or permanent repercussions as determined by other 66 | members of the project's leadership. 67 | 68 | ## Attribution 69 | 70 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, 71 | 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) 72 | 73 | [homepage]: https://www.contributor-covenant.org 74 | 75 | For answers to common questions about this code of conduct, see 76 | [https://www.contributor-covenant.org/faq](https://www.contributor-covenant.org/faq) 77 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Mihon Website Contributing Guide 2 | 3 | Before submitting your contribution, please make sure to take a moment and read through the following guidelines: 4 | 5 | - [Code of Conduct](./CODE_OF_CONDUCT.md) 6 | - [Development Setup](#development-setup) 7 | - [Project Structure](#project-structure) 8 | 9 | ## Development Setup 10 | 11 | You will need [Node.js](http://nodejs.org) **version 22+**, and [pnpm](https://pnpm.io/installation) **version 10+**. 12 | 13 | After cloning the repo and entering the the directory, go to the `/website` folder and run: 14 | 15 | ``` bash 16 | # Installs any dependencies needed. 17 | $ pnpm install 18 | ``` 19 | 20 | To run the project now, run: 21 | 22 | ``` bash 23 | # This command start a local server you can access and edit live. 24 | $ pnpm dev 25 | ``` 26 | 27 | ### Commonly used PNPM scripts 28 | 29 | ``` bash 30 | # This command will generate a static site inside a dist directory in your project. 31 | $ pnpm build 32 | 33 | # Run this command to preview the built files in a local server. 34 | $ pnpm preview 35 | ``` 36 | 37 | **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. 38 | 39 | It is also recommended you lint your files before the PR. 40 | 41 | ## Project Structure 42 | 43 | - **`website`**: contains all the website related files. 44 | - **`src`**: contains all the markdown files used for the website. 45 | - **`.vitepress`**: 46 | - **`theme`**: contains custom theme files. 47 | - `config.ts`: main configuration file for VitePress. 48 | - **`public`**: files to be exposed publicly without any processing. 49 | - **[`dist`](https://vitepress.dev/guide/deploy)**: contains built files for distribution. 50 | Note this directory is only updated when a release happens or when you run the build command. 51 | Changes to this folder will not carry over with Git. 52 | - `package.json`: contains information about which plugins are installed in the project. 53 | 54 | ## Images and Videos guidelines 55 | 56 | ### Common 57 | 58 | - Use the Android Emulator 59 | 60 | - Use the default white theme 61 | 62 | - Preferably use local source or a self-hosted extension if your media contains series 63 | 64 | - Resize to have a width of 648px 65 | 66 | ### Images 67 | 68 | - Use `.webp` 69 | 70 | ### Videos 71 | 72 | - Use `.webm` format 73 | - Encode it with our [HandBrake profile](./.github/assets/mihon-handbrake-profile.json) 74 | 75 | - Remove audio track 76 | 77 | ## Credits 78 | 79 | Thank you to all the people who have already contributed! 80 | 81 | [![List of Contributors](https://contrib.rocks/image?repo=mihonapp/website 'List of Contributors')](https://github.com/mihonapp/website/graphs/contributors) 82 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |
2 | 3 | 4 | Mihon logo 5 | 6 | 7 | # Mihon [Website](#) 8 | 9 | ### Full-featured reader 10 | Discover and read manga, webtoons, comics, and more – easier than ever on your Android device. 11 | 12 | [![Discord server](https://img.shields.io/discord/1195734228319617024.svg?label=&labelColor=6A7EC2&color=7389D8&logo=discord&logoColor=FFFFFF)](https://discord.gg/mihon) 13 | [![GitHub downloads](https://img.shields.io/github/downloads/mihonapp/mihon/total?label=downloads&labelColor=27303D&color=0D1117&logo=github&logoColor=FFFFFF&style=flat)](https://github.com/mihonapp/mihon/releases) 14 | 15 | [![Netlify deployment](https://api.netlify.com/api/v1/badges/95d9e2f8-42ae-4e40-8c99-82b870c51e1a/deploy-status)](https://app.netlify.com/sites/mihonapp/deploys) 16 | 17 | ## Contributing 18 | 19 | [Code of conduct](./CODE_OF_CONDUCT.md) · [Contributing guide](./CONTRIBUTING.md) · [Project style guide](https://mihon.app/sandbox/style-guide/) 20 | 21 | Pull requests are welcome. For major changes, please open an issue first to discuss what you would like to change. 22 | 23 | If you got any questions, [join our Discord server](https://discord.gg/mihon). 24 | 25 | ### Repositories 26 | 27 | [![mihonapp/mihon - GitHub](https://github-readme-stats.vercel.app/api/pin/?username=mihonapp&repo=mihon&bg_color=161B22&text_color=c9d1d9&title_color=0877d2&icon_color=0877d2&border_radius=8&hide_border=true)](https://github.com/mihonapp/mihon/) 28 | 29 | ### Credits 30 | 31 | Thank you to all the people who have contributed! 32 | 33 | 34 | Mihon website contributors 35 | 36 | 37 | ### Disclaimer 38 | 39 | The developer(s) of this application does not have any affiliation with the content providers available, and this application hosts zero content. 40 | 41 | ### License 42 | 43 |
44 | Copyright © 2024 The Mihon Open Source Project
45 | 
46 | This Source Code Form is subject to the terms of the Mozilla Public
47 | License, v. 2.0. If a copy of the MPL was not distributed with this
48 | file, You can obtain one at http://mozilla.org/MPL/2.0/.
49 | 
50 | 51 |
52 | -------------------------------------------------------------------------------- /netlify.toml: -------------------------------------------------------------------------------- 1 | [build.environment] 2 | NODE_VERSION = "22" 3 | 4 | [build] 5 | base = "website" 6 | publish = "dist" 7 | 8 | [context.production] 9 | command = "VITE_HOSTNAME=$URL pnpm test" 10 | 11 | [context.deploy-preview] 12 | command = "VITE_HOSTNAME=$DEPLOY_PRIME_URL pnpm test" 13 | -------------------------------------------------------------------------------- /website/.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | end_of_line = lf 5 | indent_style = space 6 | indent_size = 2 7 | charset = utf-8 8 | trim_trailing_whitespace = true 9 | insert_final_newline = true 10 | -------------------------------------------------------------------------------- /website/.markdownlint.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "markdownlint/style/prettier" 3 | } 4 | -------------------------------------------------------------------------------- /website/.markdownlintignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | README.md 3 | -------------------------------------------------------------------------------- /website/.npmrc: -------------------------------------------------------------------------------- 1 | public-hoist-pattern[]=*eslint-plugin-* 2 | public-hoist-pattern[]=@typescript-eslint/eslint-plugin 3 | public-hoist-pattern[]=@antfu/* 4 | -------------------------------------------------------------------------------- /website/.nvmrc: -------------------------------------------------------------------------------- 1 | 22 2 | -------------------------------------------------------------------------------- /website/.stylintrc: -------------------------------------------------------------------------------- 1 | { 2 | "blocks": false, 3 | "brackets": "always", 4 | "colons": "always", 5 | "colors": "always", 6 | "commaSpace": "always", 7 | "commentSpace": "always", 8 | "cssLiteral": "never", 9 | "customProperties": [], 10 | "depthLimit": false, 11 | "duplicates": true, 12 | "efficient": "always", 13 | "exclude": [], 14 | "extendPref": "@extends", 15 | "globalDupe": true, 16 | "groupOutputByFile": true, 17 | "indentPref": false, 18 | "leadingZero": "always", 19 | "maxErrors": false, 20 | "maxWarnings": false, 21 | "mixed": true, 22 | "mixins": [], 23 | "namingConvention": false, 24 | "namingConventionStrict": false, 25 | "none": "always", 26 | "noImportant": false, 27 | "parenSpace": "never", 28 | "placeholders": "always", 29 | "prefixVarsWithDollar": "always", 30 | "quotePref": "double", 31 | "reporterOptions": { 32 | "columns": ["lineData", "severity", "description", "rule"], 33 | "columnSplitter": " ", 34 | "showHeaders": false, 35 | "truncate": true 36 | }, 37 | "semicolons": "never", 38 | "sortOrder": ["grouped", "alphabetical"], 39 | "stackedProperties": "never", 40 | "trailingWhitespace": "never", 41 | "universal": false, 42 | "valid": true, 43 | "zeroUnits": "never", 44 | "zIndexNormalize": false, 45 | "stylusSupremacy.selectorSeparator": ",\n", 46 | "stylusSupremacy.insertNewLineAroundBlocks": true 47 | } 48 | -------------------------------------------------------------------------------- /website/eslint.config.js: -------------------------------------------------------------------------------- 1 | import antfu from '@antfu/eslint-config' 2 | import { FlatCompat } from '@eslint/eslintrc' 3 | 4 | const compat = new FlatCompat() 5 | 6 | export default antfu({ 7 | ignores: [ 8 | '*.sh', 9 | '*.md', 10 | '*.woff', 11 | '*.ttf', 12 | '.vscode/**', 13 | '.idea/**', 14 | '.husky/**', 15 | '.local/**', 16 | 'dist/**', 17 | 'node_modules/**', 18 | '!docs/.vitepress/**', 19 | 'docs/.vitepress/cache/**', 20 | '.netlify/**', 21 | ], 22 | 23 | typescript: true, 24 | vue: true, 25 | 26 | ...compat.config({ 27 | rules: { 28 | 'comma-dangle': ['error', 'only-multiline'], 29 | 'quotes': 'off', 30 | 'no-tabs': 'off', 31 | 'arrow-parens': ['error', 'always'], 32 | '@typescript-eslint/quotes': [ 33 | 'error', 34 | 'double', 35 | { avoidEscape: true }, 36 | ], 37 | 'indent': 'off', 38 | 'semi': ['error', 'never'], 39 | '@typescript-eslint/indent': ['error', 'tab'], 40 | '@typescript-eslint/brace-style': ['error', '1tbs'], 41 | '@typescript-eslint/semi': ['error', 'never'], 42 | 'vue/no-extra-parens': 'off', 43 | 'vue/html-indent': ['error', 'tab'], 44 | 'curly': ['error', 'all'], 45 | 'brace-style': ['error', '1tbs'], 46 | 'no-console': 'off', 47 | 'no-debugger': 'off', 48 | 'vue/multi-word-component-names': 'off', 49 | 'vue/comment-directive': 'off', 50 | 'no-unused-vars': 'off', 51 | 'vue/no-parsing-error': [ 52 | 2, 53 | { 54 | 'x-invalid-end-tag': false, 55 | 'missing-semicolon-after-character-reference': false, 56 | }, 57 | ], 58 | 59 | /* --ECMAScript 6 ES6-- */ 60 | 'no-useless-escape': 'off', 61 | 'no-unused-expressions': [ 62 | 'error', 63 | { allowShortCircuit: true, allowTernary: true }, 64 | ], 65 | }, 66 | }), 67 | }) 68 | -------------------------------------------------------------------------------- /website/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "mihon-website", 3 | "type": "module", 4 | "version": "3.0.0", 5 | "private": true, 6 | "packageManager": "pnpm@10.2.1", 7 | "description": "Official website for the Mihon app.", 8 | "license": "MPL-2.0", 9 | "repository": { 10 | "type": "git", 11 | "url": "git+https://github.com/mihonapp/website.git" 12 | }, 13 | "bugs": { 14 | "url": "https://github.com/mihonapp/website/issues" 15 | }, 16 | "engines": { 17 | "node": ">=22", 18 | "pnpm": ">=10" 19 | }, 20 | "scripts": { 21 | "preinstall": "npx only-allow pnpm", 22 | "test": "pnpm lint && pnpm build", 23 | "dev": "vitepress dev src", 24 | "build": "vitepress build src", 25 | "preview": "vitepress preview src", 26 | "lint": "pnpm lint:es && pnpm lint:mdl && pnpm lint:style", 27 | "lint:fix": "pnpm lint:es:fix && pnpm lint:style:fix", 28 | "lint:es": "eslint . ", 29 | "lint:es:fix": "eslint . --fix", 30 | "lint:mdl": "markdownlint \"**/*.md\" \".github/**/*.md\" --enable sentences-per-line --disable MD025 MD033", 31 | "lint:style": "stylelint '**/*.{styl,vue}' 'src/.vitepress/**/*.{styl,vue}'", 32 | "lint:style:fix": "stylelint --fix '**/*.{styl,vue}' 'src/.vitepress/**/*.{styl,vue}'" 33 | }, 34 | "dependencies": { 35 | "@iconify-prerendered/vue-mdi": "0.28.1718880438", 36 | "@octokit/rest": "21.0.2", 37 | "@octokit/types": "13.6.1", 38 | "@tanstack/vue-query": "5.61.3", 39 | "@vueuse/core": "12.5.0", 40 | "axios": "1.7.7", 41 | "element-plus": "2.8.8", 42 | "lodash.groupby": "4.6.0", 43 | "markdown-it": "14.1.0", 44 | "markdown-it-shortcode-tag": "1.1.0", 45 | "moment": "2.30.1" 46 | }, 47 | "devDependencies": { 48 | "@antfu/eslint-config": "^2.26.0", 49 | "@eslint/eslintrc": "^3.1.0", 50 | "@mdit/plugin-attrs": "0.13.1", 51 | "@mdit/plugin-figure": "0.13.1", 52 | "@mdit/plugin-img-lazyload": "0.13.1", 53 | "@mdit/plugin-img-mark": "0.13.1", 54 | "@mdit/plugin-img-size": "0.13.1", 55 | "@mdit/plugin-include": "0.13.1", 56 | "@resvg/resvg-js": "2.6.2", 57 | "@types/gtag.js": "0.0.20", 58 | "@types/lodash.groupby": "4.6.9", 59 | "@types/markdown-it": "14.1.2", 60 | "@types/node": "22.9.3", 61 | "@typescript-eslint/eslint-plugin": "8.16.0", 62 | "@typescript-eslint/parser": "8.16.0", 63 | "eslint": "9.15.0", 64 | "eslint-config-standard": "17.1.0", 65 | "eslint-plugin-vue": "9.31.0", 66 | "feed": "4.2.2", 67 | "lint-staged": "15.2.10", 68 | "markdownlint": "0.36.1", 69 | "markdownlint-cli": "0.43.0", 70 | "sentences-per-line": "0.2.1", 71 | "stylelint": "16.10.0", 72 | "stylelint-stylus": "1.0.0", 73 | "stylus": "0.64.0", 74 | "unplugin-element-plus": "0.8.0", 75 | "vite-plugin-eslint": "1.8.1", 76 | "vitepress": "1.5.0", 77 | "vitepress-plugin-tabs": "0.5.0", 78 | "vue": "3.5.13", 79 | "vue-eslint-parser": "9.4.3", 80 | "x-satori": "0.2.0" 81 | }, 82 | "simple-git-hooks": { 83 | "pre-commit": "pnpm lint-staged" 84 | }, 85 | "lint-staged": { 86 | "*.{styl,vue}": "stylelint --fix", 87 | "*.{html,json}": "prettier --write" 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /website/src/.vitepress/config.ts: -------------------------------------------------------------------------------- 1 | import process from 'node:process' 2 | import { URL, fileURLToPath } from 'node:url' 3 | import { defineConfig, loadEnv } from 'vitepress' 4 | import ElementPlus from 'unplugin-element-plus/vite' 5 | 6 | import markdownConfig from './config/markdownConfig' 7 | 8 | // For use with loading Markdown plugins 9 | import themeConfig from './config/themeConfig' 10 | 11 | // Theme related config 12 | import headConfig from './config/headConfig' 13 | 14 | // Provides how to generate Meta head tags 15 | 16 | import generateMeta from './config/hooks/generateMeta' 17 | 18 | // Enhanced meta generation 19 | import generateFeed from './config/hooks/generateFeed' 20 | 21 | // Allows generation of RSS feed 22 | import generateOgImages from './config/hooks/generateOgImages' 23 | 24 | const title = 'Mihon' 25 | const description = 'Discover and read manga, webtoons, comics, and more – easier than ever on your Android device.' 26 | 27 | const env = loadEnv('', process.cwd()) 28 | const hostname: string = env.VITE_HOSTNAME || 'http://localhost:4173' 29 | 30 | export default defineConfig({ 31 | outDir: '../dist', 32 | lastUpdated: true, 33 | cleanUrls: true, 34 | title, 35 | description, 36 | sitemap: { 37 | hostname, 38 | }, 39 | head: headConfig, 40 | markdown: markdownConfig, 41 | themeConfig, 42 | transformHead: async context => generateMeta(context, hostname), 43 | buildEnd: async (context) => { 44 | generateFeed(context, hostname) 45 | generateOgImages(context) 46 | }, 47 | vite: { 48 | resolve: { 49 | alias: [ 50 | { 51 | // Used to show the release version on navbar. 52 | find: /^.*\/VPNavBarMenu\.vue$/, 53 | replacement: fileURLToPath( 54 | new URL('./theme/components/CustomNavBarMenu.vue', import.meta.url), 55 | ), 56 | }, 57 | { 58 | find: /^.*VPNavScreenMenu\.vue$/, 59 | replacement: fileURLToPath( 60 | new URL('./theme/components/CustomNavScreenMenu.vue', import.meta.url), 61 | ), 62 | }, 63 | { 64 | find: /^.*VPSwitchAppearance\.vue$/, 65 | replacement: fileURLToPath( 66 | new URL('./theme/components/CustomSwitchAppearance.vue', import.meta.url), 67 | ), 68 | }, 69 | ], 70 | }, 71 | plugins: [ElementPlus({})], 72 | ssr: { 73 | noExternal: ['element-plus'], 74 | }, 75 | }, 76 | }) 77 | -------------------------------------------------------------------------------- /website/src/.vitepress/config/constants.ts: -------------------------------------------------------------------------------- 1 | export const GITHUB_EXTENSION_JSON = 'https://raw.githubusercontent.com/mihonapp/extensions/repo/index.json' 2 | -------------------------------------------------------------------------------- /website/src/.vitepress/config/headConfig.ts: -------------------------------------------------------------------------------- 1 | import type { HeadConfig } from 'vitepress' 2 | 3 | const headConfig: HeadConfig[] = [ 4 | ['meta', { name: 'darkreader-lock' }], 5 | ['meta', { name: 'theme-color', content: '#0058A0' }], 6 | ['meta', { name: 'msapplication-TileColor', content: '#0058A0' }], 7 | ['meta', { name: 'viewport', content: 'width=device-width, initial-scale=1.0' }], 8 | ['meta', { name: 'referrer', content: 'no-referrer-when-downgrade' }], 9 | ['link', { rel: 'icon', type: 'image/x-icon', href: '/favicon.ico' }], 10 | ['link', { rel: 'icon', type: 'image/png', sizes: '32x32', href: '/favicon-32x32.png' }], 11 | ['link', { rel: 'icon', type: 'image/png', sizes: '16x16', href: '/favicon-16x16.png' }], 12 | ['link', { rel: 'manifest', href: '/site.webmanifest' }], 13 | ['link', { rel: 'mask-icon', href: '/safari-pinned-tab.svg', color: '#0058A0' }], 14 | ['link', { rel: 'apple-touch-icon', sizes: '180x180', href: '/apple-touch-icon.png' }], 15 | ['meta', { name: 'twitter:card', content: 'summary' }], 16 | ['meta', { name: 'twitter:site', content: '@mihonapp' }], 17 | ['meta', { name: 'twitter:creator', content: '@mihonapp' }], 18 | ['meta', { property: 'og:site_name', content: 'Mihon' }], 19 | ['meta', { property: 'og:description', content: 'Discover and read manga, webtoons, comics, and more – easier than ever on your Android device.' }], 20 | ['meta', { property: 'og:locale', content: 'en_US' }], 21 | ['meta', { property: 'og:type', content: 'website' }], 22 | ] 23 | 24 | export default headConfig 25 | -------------------------------------------------------------------------------- /website/src/.vitepress/config/hooks/generateFeed.ts: -------------------------------------------------------------------------------- 1 | import path from 'node:path' 2 | import { writeFileSync } from 'node:fs' 3 | import { Feed, type Item } from 'feed' 4 | import { type SiteConfig, createContentLoader } from 'vitepress' 5 | 6 | async function generateFeed(config: SiteConfig, hostname: string) { 7 | const feed = new Feed({ 8 | title: config.site.title, 9 | description: config.site.description, 10 | id: hostname, 11 | link: hostname, 12 | language: 'en', 13 | image: `${hostname}/img/logo.png`, 14 | favicon: `${hostname}/favicon.ico`, 15 | copyright: `Copyright © ${new Date().getFullYear()} Mihon App`, 16 | }) 17 | const json: Item[] = [] 18 | 19 | const posts = await createContentLoader('news/*.md', { 20 | excerpt: true, 21 | render: true, 22 | includeSrc: true, 23 | }).load() 24 | 25 | // Filter everything that"s not of type `article` (e.g. index.md) 26 | const filteredPosts = posts.filter(post => post.frontmatter.type === 'article') 27 | 28 | filteredPosts.sort((a, b) => +new Date(b.frontmatter.date as string) - +new Date(a.frontmatter.date as string)) 29 | 30 | for (const { url, frontmatter, html, src } of filteredPosts) { 31 | const fullUrl = `${hostname}${url}` 32 | 33 | // Strip `​` from `html` string 34 | const content = (html ?? '') 35 | .replace(/​/g, '') 36 | .replace(//g, ``) 37 | 38 | const markdown = (src ?? '') 39 | .replace(/^---.*---/s, '') 40 | .replace(/\]\((\/.*?)\)/g, `](${hostname}$1)`) 41 | .replace(/^# .*$/m, '') 42 | .trim() 43 | 44 | const post = { 45 | title: frontmatter.title, 46 | id: fullUrl, 47 | link: fullUrl, 48 | description: frontmatter.description, 49 | content, 50 | date: frontmatter.date, 51 | } satisfies Item 52 | 53 | feed.addItem(post) 54 | json.push({ ...post, content: markdown }) 55 | } 56 | 57 | writeFileSync(path.join(config.outDir, 'feed.rss'), feed.rss2()) 58 | writeFileSync(path.join(config.outDir, 'news.json'), JSON.stringify(json)) 59 | } 60 | 61 | export default generateFeed 62 | -------------------------------------------------------------------------------- /website/src/.vitepress/config/hooks/generateMeta.ts: -------------------------------------------------------------------------------- 1 | import type { HeadConfig, TransformContext } from 'vitepress' 2 | 3 | function generateMeta(context: TransformContext, hostname: string) { 4 | const head: HeadConfig[] = [] 5 | const { pageData } = context 6 | 7 | const url = `${hostname}/${pageData.relativePath.replace(/((^|\/)index)?\.md$/, '$2')}` 8 | 9 | const addMetaTag = (name: string, content: any) => { 10 | if (content !== undefined && content !== null) 11 | head.push(['meta', { name, content: String(content) }]) 12 | } 13 | 14 | const addPropertyTag = (property: string, content: any) => { 15 | if (content !== undefined && content !== null) 16 | head.push(['meta', { property, content: String(content) }]) 17 | } 18 | 19 | const addLinkTag = (rel: string, href: string, type?: string, title?: string) => { 20 | if (href) { 21 | const attributes: { rel: string, href: string, type?: string, title?: string } = { rel, href } 22 | if (type) 23 | attributes.type = type 24 | if (title) 25 | attributes.title = title 26 | head.push(['link', attributes]) 27 | } 28 | } 29 | 30 | head.push(['link', { rel: 'canonical', href: url }]) 31 | addPropertyTag('og:url', url) 32 | addMetaTag('twitter:url', url) 33 | addMetaTag('twitter:card', 'summary_large_image') 34 | 35 | if (pageData.frontmatter.theme) 36 | addMetaTag('theme-color', pageData.frontmatter.theme) 37 | 38 | if (pageData.frontmatter.type) 39 | addPropertyTag('og:type', pageData.frontmatter.type) 40 | 41 | if (pageData.frontmatter.customMetaTitle) { 42 | addPropertyTag('og:title', pageData.frontmatter.customMetaTitle) 43 | addMetaTag('twitter:title', pageData.frontmatter.customMetaTitle) 44 | addPropertyTag('og:site_name', '') 45 | } 46 | else { 47 | addPropertyTag('og:title', pageData.frontmatter.title) 48 | addMetaTag('twitter:title', pageData.frontmatter.title) 49 | } 50 | 51 | if (pageData.frontmatter.description) { 52 | addPropertyTag('og:description', pageData.frontmatter.description) 53 | addMetaTag('twitter:description', pageData.frontmatter.description) 54 | } 55 | 56 | if (pageData.frontmatter.image) { 57 | const imageUrl = `${hostname}/${pageData.frontmatter.image.replace(/^\//, '')}` 58 | addPropertyTag('og:image', imageUrl) 59 | addMetaTag('twitter:image', imageUrl) 60 | } 61 | else { 62 | const url = pageData.filePath.replace('index.md', '').replace('.md', '') 63 | const imageUrl = `${url}/__og_image__/og.png`.replace(/\/\//g, '/').replace(/^\//, '') 64 | addPropertyTag('og:image', `${hostname}/${imageUrl}`) 65 | addPropertyTag('og:image:width', '1200') 66 | addPropertyTag('og:image:height', '628') 67 | addPropertyTag('og:image:type', 'image/png') 68 | addPropertyTag('og:image:alt', pageData.frontmatter.title) 69 | addMetaTag('twitter:image', `${hostname}/${imageUrl}`) 70 | addMetaTag('twitter:image:width', '1200') 71 | addMetaTag('twitter:image:height', '628') 72 | addMetaTag('twitter:image:alt', pageData.frontmatter.title) 73 | } 74 | 75 | if (pageData.frontmatter.tag) 76 | addPropertyTag('article:tag', pageData.frontmatter.tag) 77 | 78 | if (pageData.frontmatter.date) 79 | addPropertyTag('article:published_time', pageData.frontmatter.date) 80 | 81 | if (pageData.lastUpdated && pageData.frontmatter.lastUpdated !== false) 82 | addPropertyTag('article:modified_time', new Date(pageData.lastUpdated).toISOString()) 83 | 84 | if (pageData.filePath === 'news/index.md') { 85 | addLinkTag('alternate', `${hostname}/feed.rss`, 'application/rss+xml', 'RSS feed for the news archive') 86 | addLinkTag('alternate', `${hostname}/news.json`, 'application/json', 'JSON of the news archive') 87 | } 88 | 89 | return head 90 | } 91 | 92 | export default generateMeta 93 | -------------------------------------------------------------------------------- /website/src/.vitepress/config/hooks/generateOgImages.ts: -------------------------------------------------------------------------------- 1 | import { mkdir, readFile, writeFile } from 'node:fs/promises' 2 | import { dirname, resolve } from 'node:path' 3 | import { fileURLToPath } from 'node:url' 4 | import { createContentLoader } from 'vitepress' 5 | import type { ContentData, SiteConfig } from 'vitepress' 6 | import { type SatoriOptions, satoriVue } from 'x-satori/vue' 7 | import { renderAsync } from '@resvg/resvg-js' 8 | 9 | const __dirname = dirname(fileURLToPath(import.meta.url)) 10 | const __fonts = resolve(__dirname, '../../fonts') 11 | 12 | async function generateOgImages(config: SiteConfig) { 13 | const pages = await createContentLoader('**/*.md', { excerpt: true }).load() 14 | const template = await readFile(resolve(__dirname, '../../theme/components/OgImageTemplate.vue'), 'utf-8') 15 | 16 | const fonts: SatoriOptions['fonts'] = [ 17 | { 18 | name: 'Inter', 19 | data: await readFile(resolve(__fonts, 'Inter-Regular.otf')), 20 | weight: 400, 21 | style: 'normal', 22 | }, 23 | { 24 | name: 'Inter', 25 | data: await readFile(resolve(__fonts, 'Inter-Medium.otf')), 26 | weight: 500, 27 | style: 'normal', 28 | }, 29 | { 30 | name: 'Inter', 31 | data: await readFile(resolve(__fonts, 'Inter-SemiBold.otf')), 32 | weight: 600, 33 | style: 'normal', 34 | }, 35 | { 36 | name: 'Inter', 37 | data: await readFile(resolve(__fonts, 'Inter-Bold.otf')), 38 | weight: 700, 39 | style: 'normal', 40 | }, 41 | ] 42 | 43 | const filteredPages = pages.filter(p => p.frontmatter.image === undefined) 44 | 45 | for (const page of filteredPages) { 46 | await generateImage({ 47 | page, 48 | template, 49 | outDir: config.outDir, 50 | fonts, 51 | }) 52 | } 53 | } 54 | 55 | export default generateOgImages 56 | 57 | interface GenerateImagesOptions { 58 | page: ContentData 59 | template: string 60 | outDir: string 61 | fonts: SatoriOptions['fonts'] 62 | } 63 | 64 | function getDir(url: string) { 65 | if (url.startsWith('/docs/faq/')) 66 | return 'FAQ' 67 | else if (url.startsWith('/docs/guides/')) 68 | return 'Guide' 69 | else if (url.startsWith('/news/') && url !== '/news/') 70 | return 'News' 71 | 72 | return undefined 73 | } 74 | 75 | async function generateImage({ page, template, outDir, fonts }: GenerateImagesOptions) { 76 | const { frontmatter, url } = page 77 | 78 | const options: SatoriOptions = { 79 | width: 1200, 80 | height: 628, 81 | fonts, 82 | props: { 83 | title: 84 | frontmatter.layout === 'home' 85 | ? frontmatter.hero.name ?? frontmatter.title 86 | : frontmatter.customMetaTitle ?? frontmatter.title, 87 | description: 88 | frontmatter.layout === 'home' 89 | ? frontmatter.hero.tagline ?? frontmatter.description 90 | : frontmatter.description, 91 | dir: getDir(url), 92 | }, 93 | } 94 | 95 | const svg = await satoriVue(options, template) 96 | 97 | const render = await renderAsync(svg, { 98 | fitTo: { 99 | mode: 'width', 100 | value: 1200, 101 | }, 102 | }) 103 | 104 | const outputFolder = resolve(outDir, url.substring(1), '__og_image__') 105 | const outputFile = resolve(outputFolder, 'og.png') 106 | 107 | await mkdir(outputFolder, { recursive: true }) 108 | 109 | return await writeFile(outputFile, render.asPng()) 110 | } 111 | -------------------------------------------------------------------------------- /website/src/.vitepress/config/markdownConfig.ts: -------------------------------------------------------------------------------- 1 | import type { MarkdownOptions } from 'vitepress' 2 | 3 | import { attrs } from '@mdit/plugin-attrs' 4 | import { figure } from '@mdit/plugin-figure' 5 | import { imgLazyload } from '@mdit/plugin-img-lazyload' 6 | import { imgMark } from '@mdit/plugin-img-mark' 7 | import { imgSize } from '@mdit/plugin-img-size' 8 | import { include } from '@mdit/plugin-include' 9 | import { tabsMarkdownPlugin } from 'vitepress-plugin-tabs' 10 | import shortcode_plugin from 'markdown-it-shortcode-tag' 11 | import shortcodes from './shortcodes' 12 | 13 | const markdownConfig: MarkdownOptions = { 14 | config: (md) => { 15 | md 16 | .use(attrs) 17 | .use(figure) 18 | .use(imgLazyload) 19 | .use(imgMark) 20 | .use(imgSize) 21 | .use(include, { 22 | currentPath: env => env.filePath, 23 | }) 24 | .use(tabsMarkdownPlugin) 25 | .use(shortcode_plugin, shortcodes) 26 | }, 27 | } 28 | 29 | export default markdownConfig 30 | -------------------------------------------------------------------------------- /website/src/.vitepress/config/navigation/navbar.ts: -------------------------------------------------------------------------------- 1 | import type { DefaultTheme } from 'vitepress' 2 | 3 | const nav: DefaultTheme.NavItem[] = [ 4 | { 5 | text: 'Get v{app_version}', 6 | activeMatch: '^/*?(download|changelogs)/*?$', 7 | items: [ 8 | { 9 | text: 'Download', 10 | link: '/download/', 11 | }, 12 | { 13 | text: 'Changelogs', 14 | link: '/changelogs/', 15 | }, 16 | ], 17 | }, 18 | { 19 | text: 'Docs', 20 | link: '/docs/guides/getting-started', 21 | activeMatch: '/docs/', 22 | }, 23 | { 24 | text: 'News', 25 | link: '/news/', 26 | activeMatch: '/news/', 27 | }, 28 | ] 29 | 30 | export default nav 31 | -------------------------------------------------------------------------------- /website/src/.vitepress/config/navigation/sidebar.ts: -------------------------------------------------------------------------------- 1 | import type { DefaultTheme } from 'vitepress' 2 | 3 | const sidebar: DefaultTheme.SidebarMulti = { 4 | '/download/': defaultSidebar(), 5 | '/extensions/': defaultSidebar(), 6 | '/docs/': defaultSidebar(), 7 | '/forks/': defaultSidebar(), 8 | '/changelogs/': defaultSidebar(), 9 | '/news/': defaultSidebar(), 10 | '/sandbox/': defaultSidebar(), 11 | } 12 | 13 | function defaultSidebar(): DefaultTheme.SidebarItem[] { 14 | return [ 15 | { 16 | items: [ 17 | { 18 | text: 'Download', 19 | link: '/download/', 20 | }, 21 | { 22 | text: 'Changelogs', 23 | link: '/changelogs/', 24 | }, 25 | { 26 | text: 'Forks', 27 | link: '/forks/', 28 | }, 29 | { 30 | text: 'Contribute', 31 | link: '/docs/contribute', 32 | }, 33 | ], 34 | }, 35 | { 36 | text: 'Frequently Asked Questions', 37 | items: [ 38 | { text: 'General', link: '/docs/faq/general' }, 39 | { 40 | text: 'Library', 41 | link: '/docs/faq/library', 42 | }, 43 | { 44 | text: 'Updates', 45 | collapsed: true, 46 | items: [ 47 | { text: 'Smart updates', link: '/docs/faq/updates/smart' }, 48 | { text: 'Upcoming', link: '/docs/faq/updates/upcoming' }, 49 | ], 50 | }, 51 | { 52 | text: 'Browse', 53 | link: '/docs/faq/browse/', 54 | collapsed: true, 55 | items: [ 56 | { text: 'Extensions', link: '/docs/faq/browse/extensions' }, 57 | { 58 | text: 'Local source', 59 | link: '/docs/faq/browse/local-source', 60 | }, 61 | ], 62 | }, 63 | { 64 | text: 'Downloads', 65 | link: '/docs/faq/downloads', 66 | }, 67 | { 68 | text: 'Reader', 69 | link: '/docs/faq/reader', 70 | }, 71 | { 72 | text: 'Settings', 73 | link: '/docs/faq/settings', 74 | }, 75 | { 76 | text: 'Storage', 77 | link: '/docs/faq/storage', 78 | }, 79 | ], 80 | }, 81 | { 82 | text: 'Guides', 83 | items: [ 84 | { 85 | text: 'Getting started', 86 | link: '/docs/guides/getting-started', 87 | }, 88 | { 89 | text: 'Troubleshooting', 90 | link: '/docs/guides/troubleshooting/', 91 | collapsed: true, 92 | items: [ 93 | { 94 | text: 'Common issues', 95 | link: '/docs/guides/troubleshooting/common-issues', 96 | }, 97 | { 98 | text: 'Diagnosis', 99 | link: '/docs/guides/troubleshooting/diagnosis', 100 | }, 101 | ], 102 | }, 103 | { 104 | text: 'Source migration', 105 | link: '/docs/guides/source-migration', 106 | }, 107 | { text: 'Backups', link: '/docs/guides/backups' }, 108 | { text: 'Tracking', link: '/docs/guides/tracking' }, 109 | { text: 'Categories', link: '/docs/guides/categories' }, 110 | { 111 | text: 'Local source', 112 | link: '/docs/guides/local-source/', 113 | collapsed: true, 114 | items: [ 115 | { 116 | text: 'Advanced editing', 117 | link: '/docs/guides/local-source/advanced', 118 | }, 119 | ], 120 | }, 121 | { 122 | text: 'Reader settings', 123 | link: '/docs/guides/reader-settings', 124 | }, 125 | { 126 | text: 'Shizuku', 127 | link: '/docs/guides/shizuku', 128 | }, 129 | ], 130 | }, 131 | ] 132 | } 133 | 134 | export default sidebar 135 | -------------------------------------------------------------------------------- /website/src/.vitepress/config/scripts/languages.ts: -------------------------------------------------------------------------------- 1 | export function simpleLangName(code: string) { 2 | if (code === 'all') 3 | return 'All' 4 | 5 | const namesInEnglish = new Intl.DisplayNames(['en'], { type: 'language' }) 6 | return capitalize(namesInEnglish.of(code)!, 'en') 7 | } 8 | 9 | export function langName(code: string) { 10 | if (code === 'all') 11 | return 'All' 12 | 13 | const namesInEnglish = new Intl.DisplayNames(['en'], { type: 'language' }) 14 | const namesInNative = new Intl.DisplayNames([code], { type: 'language' }) 15 | return `${capitalize(namesInEnglish.of(code)!, 'en')} - ${capitalize(namesInNative.of(code)!, code)}` 16 | } 17 | 18 | function capitalize(string: string, locale: string) { 19 | return string.charAt(0).toLocaleUpperCase(locale) + string.substring(1) 20 | } 21 | -------------------------------------------------------------------------------- /website/src/.vitepress/config/themeConfig.ts: -------------------------------------------------------------------------------- 1 | import type { DefaultTheme } from 'vitepress' 2 | 3 | import nav from './navigation/navbar' 4 | import sidebar from './navigation/sidebar' 5 | 6 | const themeConfig: DefaultTheme.Config = { 7 | logo: { 8 | src: '/img/logo-128px.png', 9 | width: 24, 10 | height: 24, 11 | }, 12 | 13 | nav, 14 | sidebar, 15 | 16 | outline: [2, 3], 17 | 18 | socialLinks: [ 19 | { 20 | icon: 'github', 21 | link: 'https://github.com/mihonapp/mihon', 22 | ariaLabel: 'Project GitHub', 23 | }, 24 | { 25 | icon: 'discord', 26 | link: 'https://discord.gg/mihon', 27 | ariaLabel: 'Discord Server', 28 | }, 29 | { 30 | icon: 'x', 31 | link: 'https://twitter.com/mihonapp', 32 | ariaLabel: 'X Page', 33 | }, 34 | { 35 | icon: 'facebook', 36 | link: 'https://facebook.com/mihonapp', 37 | ariaLabel: 'Facebook Page', 38 | }, 39 | { 40 | icon: { 41 | svg: '', 42 | }, 43 | link: 'https://reddit.com/r/mihonapp', 44 | ariaLabel: 'Support subreddit', 45 | }, 46 | // { icon: "instagram", link: "https://instagram.com/mihonapp", ariaLabel: "Instagram Page" }, 47 | ], 48 | 49 | footer: { 50 | message: 'Open-source Apache Licensed | Privacy policy', 51 | copyright: `Copyright © ${new Date().getFullYear()} Mihon App`, 52 | }, 53 | 54 | editLink: { 55 | pattern: 'https://github.com/mihonapp/website/edit/main/website/src/:path', 56 | text: 'Help us improve this page', 57 | }, 58 | 59 | lastUpdated: { 60 | text: 'Last updated', 61 | formatOptions: { 62 | forceLocale: true, 63 | dateStyle: 'long', 64 | timeStyle: 'short', 65 | }, 66 | }, 67 | 68 | search: { 69 | provider: 'algolia', 70 | options: { 71 | appId: 'IXX45N1P5C', 72 | apiKey: 'a7a819b0bd88bc7333c7f42d611ec04e', 73 | indexName: 'mihon', 74 | }, 75 | }, 76 | } 77 | 78 | export default themeConfig 79 | -------------------------------------------------------------------------------- /website/src/.vitepress/fonts/Inter-Bold.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mihonapp/website/bbbb8600e939dd156ed22e15ab79815da2a4f03a/website/src/.vitepress/fonts/Inter-Bold.otf -------------------------------------------------------------------------------- /website/src/.vitepress/fonts/Inter-Medium.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mihonapp/website/bbbb8600e939dd156ed22e15ab79815da2a4f03a/website/src/.vitepress/fonts/Inter-Medium.otf -------------------------------------------------------------------------------- /website/src/.vitepress/fonts/Inter-Regular.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mihonapp/website/bbbb8600e939dd156ed22e15ab79815da2a4f03a/website/src/.vitepress/fonts/Inter-Regular.otf -------------------------------------------------------------------------------- /website/src/.vitepress/fonts/Inter-SemiBold.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mihonapp/website/bbbb8600e939dd156ed22e15ab79815da2a4f03a/website/src/.vitepress/fonts/Inter-SemiBold.otf -------------------------------------------------------------------------------- /website/src/.vitepress/theme/Layout.vue: -------------------------------------------------------------------------------- 1 | 43 | 44 | 47 | 48 | 65 | -------------------------------------------------------------------------------- /website/src/.vitepress/theme/components/AddRepoButton.vue: -------------------------------------------------------------------------------- 1 | 19 | 20 | 70 | 71 | 157 | -------------------------------------------------------------------------------- /website/src/.vitepress/theme/components/Changelog.vue: -------------------------------------------------------------------------------- 1 | 20 | 21 | 43 | 44 | 117 | -------------------------------------------------------------------------------- /website/src/.vitepress/theme/components/ChangelogsList.vue: -------------------------------------------------------------------------------- 1 | 25 | 26 | 56 | 57 | 70 | -------------------------------------------------------------------------------- /website/src/.vitepress/theme/components/Contributors.vue: -------------------------------------------------------------------------------- 1 | 47 | 48 | 77 | 78 | 107 | -------------------------------------------------------------------------------- /website/src/.vitepress/theme/components/CustomNavBarMenu.vue: -------------------------------------------------------------------------------- 1 | 41 | 42 | 51 | 52 | 63 | -------------------------------------------------------------------------------- /website/src/.vitepress/theme/components/CustomNavScreenMenu.vue: -------------------------------------------------------------------------------- 1 | 40 | 41 | 56 | -------------------------------------------------------------------------------- /website/src/.vitepress/theme/components/CustomSwitchAppearance.vue: -------------------------------------------------------------------------------- 1 | 21 | 22 | 40 | 41 | 75 | -------------------------------------------------------------------------------- /website/src/.vitepress/theme/components/DownloadButtons.vue: -------------------------------------------------------------------------------- 1 | 36 | 37 | 88 | 89 | 175 | -------------------------------------------------------------------------------- /website/src/.vitepress/theme/components/News.vue: -------------------------------------------------------------------------------- 1 | 19 | 20 | 45 | 46 | 162 | -------------------------------------------------------------------------------- /website/src/.vitepress/theme/components/OgImageTemplate.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 33 | -------------------------------------------------------------------------------- /website/src/.vitepress/theme/components/ReleaseDate.vue: -------------------------------------------------------------------------------- 1 | 22 | 23 | 31 | -------------------------------------------------------------------------------- /website/src/.vitepress/theme/components/RssLink.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 11 | 12 | 28 | -------------------------------------------------------------------------------- /website/src/.vitepress/theme/data/changelogs.data.ts: -------------------------------------------------------------------------------- 1 | import { defineLoader } from 'vitepress' 2 | import { Octokit } from '@octokit/rest' 3 | import type { GetResponseDataTypeFromEndpointMethod } from '@octokit/types' 4 | 5 | const octokit = new Octokit() 6 | 7 | type GitHubReleaseList = GetResponseDataTypeFromEndpointMethod 8 | 9 | declare const data: GitHubReleaseList 10 | export { data } 11 | 12 | export default defineLoader({ 13 | async load(): Promise { 14 | const releases = await octokit.paginate(octokit.repos.listReleases, { 15 | owner: 'mihonapp', 16 | repo: 'mihon', 17 | per_page: 100, 18 | }) 19 | 20 | return releases 21 | }, 22 | }) 23 | -------------------------------------------------------------------------------- /website/src/.vitepress/theme/data/news.data.ts: -------------------------------------------------------------------------------- 1 | import { createContentLoader } from 'vitepress' 2 | 3 | export interface News { 4 | title: string 5 | description: string 6 | date: string 7 | url: string 8 | } 9 | 10 | declare const data: News[] 11 | export { data } 12 | 13 | export default createContentLoader('news/*.md', { 14 | excerpt: true, 15 | transform(articles) { 16 | return articles 17 | .filter(({ url }) => url !== '/news/') 18 | .map( 19 | ({ frontmatter, url }) => 20 | { 21 | title: frontmatter.title, 22 | description: frontmatter.description, 23 | date: frontmatter.date, 24 | url, 25 | }, 26 | ) 27 | .sort((a, b) => a.date.toString().localeCompare(b.date.toString())) 28 | }, 29 | }) 30 | -------------------------------------------------------------------------------- /website/src/.vitepress/theme/data/release.data.ts: -------------------------------------------------------------------------------- 1 | import { defineLoader } from 'vitepress' 2 | import { Octokit } from '@octokit/rest' 3 | import type { GetResponseDataTypeFromEndpointMethod } from '@octokit/types' 4 | 5 | const octokit = new Octokit() 6 | 7 | type GitHubRelease = GetResponseDataTypeFromEndpointMethod 8 | 9 | export interface AppRelease { 10 | stable: GitHubRelease 11 | beta: GitHubRelease 12 | } 13 | 14 | declare const data: AppRelease 15 | export { data } 16 | 17 | export default defineLoader({ 18 | async load(): Promise { 19 | const { data: stable } = await octokit.repos.getLatestRelease({ 20 | owner: 'mihonapp', 21 | repo: 'mihon', 22 | }) 23 | 24 | const { data: beta } = await octokit.repos.getLatestRelease({ 25 | owner: 'mihonapp', 26 | repo: 'mihon-preview', 27 | }) 28 | 29 | return { stable, beta } 30 | }, 31 | }) 32 | -------------------------------------------------------------------------------- /website/src/.vitepress/theme/index.ts: -------------------------------------------------------------------------------- 1 | // https://vitepress.dev/guide/custom-theme 2 | import DefaultTheme from 'vitepress/theme' 3 | 4 | // Import Stylus files 5 | import './styles/base.styl' 6 | 7 | // Import Global plugins 8 | import 'element-plus/theme-chalk/dark/css-vars.css' 9 | 10 | import { VueQueryPlugin } from '@tanstack/vue-query' 11 | 12 | import { enhanceAppWithTabs } from 'vitepress-plugin-tabs/client' 13 | 14 | // Import icon components 15 | import { IconBugReport, IconDownload, IconNewspaperVariant } from '@iconify-prerendered/vue-mdi' 16 | 17 | import analytics from './plugin/analytics' 18 | import Layout from './Layout.vue' 19 | 20 | export default { 21 | extends: DefaultTheme, 22 | enhanceApp({ app }) { 23 | app.use(VueQueryPlugin) 24 | enhanceAppWithTabs(app) 25 | app.component('IconDownload', IconDownload) 26 | app.component('IconNewspaperVariant', IconNewspaperVariant) 27 | app.component('IconBugReport', IconBugReport) 28 | analytics({ id: 'G-KN9GHR5EKT' }) 29 | }, 30 | Layout, 31 | } 32 | -------------------------------------------------------------------------------- /website/src/.vitepress/theme/plugin/analytics.ts: -------------------------------------------------------------------------------- 1 | // Code based on vitepress-plugin-google-analytics. 2 | // Customized as the plugin did not consider the script loading time. 3 | // https://github.com/ZhongxuYang/vitepress-plugin-google-analytics 4 | 5 | function mountGoogleAnalytics(id: string) { 6 | if (('dataLayer' in window && window.gtag) || window.location.hostname === 'localhost') 7 | return 8 | 9 | const analyticsScript = document.createElement('script') 10 | 11 | analyticsScript.addEventListener('load', () => { 12 | // @ts-expect-error Missing types 13 | window.dataLayer = window.dataLayer || [] 14 | function gtag(..._args: any[]) { 15 | // @ts-expect-error Missing types 16 | // eslint-disable-next-line prefer-rest-params 17 | window.dataLayer.push(arguments) 18 | } 19 | 20 | gtag('js', new Date()) 21 | gtag('config', id) 22 | 23 | window.gtag = gtag 24 | }) 25 | 26 | analyticsScript.src = `https://www.googletagmanager.com/gtag/js?id=${id}` 27 | 28 | document.body.appendChild(analyticsScript) 29 | } 30 | 31 | export default function ({ id }: { id: string }) { 32 | // eslint-disable-next-line node/prefer-global/process 33 | if (process.env.NODE_ENV === 'production' && id && typeof window !== 'undefined') 34 | mountGoogleAnalytics(id) 35 | } 36 | -------------------------------------------------------------------------------- /website/src/.vitepress/theme/queries/useExtensionsRepositoryQuery.ts: -------------------------------------------------------------------------------- 1 | import type { UseQueryOptions } from '@tanstack/vue-query' 2 | import { useQuery } from '@tanstack/vue-query' 3 | import axios from 'axios' 4 | import { GITHUB_EXTENSION_JSON } from '../../config/constants' 5 | 6 | export type ReleaseType = 'stable' | 'beta' 7 | 8 | export interface Extension { 9 | name: string 10 | pkg: string 11 | apk: string 12 | lang: string 13 | code: number 14 | version: string 15 | sources: Source[] 16 | } 17 | 18 | export interface Source { 19 | name: string 20 | lang: string 21 | id: string 22 | baseUrl: string 23 | versionId: number 24 | } 25 | 26 | type UseExtensionsRepositoryQueryOptions = 27 | UseQueryOptions 28 | 29 | export default function useExtensionsRepositoryQuery(options: UseExtensionsRepositoryQueryOptions = {}) { 30 | return useQuery({ 31 | queryKey: ['extensions'], 32 | queryFn: async () => { 33 | const { data } = await axios.get(GITHUB_EXTENSION_JSON) 34 | 35 | return data 36 | }, 37 | initialData: () => [], 38 | refetchOnWindowFocus: false, 39 | ...options, 40 | }) 41 | } 42 | -------------------------------------------------------------------------------- /website/src/.vitepress/theme/styles/forks/komikku.styl: -------------------------------------------------------------------------------- 1 | // Assign theme color 2 | $themeColor = #ff5555 3 | 4 | .page-komikku { 5 | .VPHero { 6 | h1 { 7 | .clip { 8 | --vp-home-hero-name-background: -webkit-linear-gradient(120deg, var(--vp-c-brand) 30%, var(--vp-c-brand-dark)) 9 | } 10 | } 11 | } 12 | 13 | .VPButton { 14 | &.brand { 15 | border-color: var(--vp-button-brand-border) 16 | color: var(--vp-button-brand-text) 17 | background-color: var(--vp-button-brand-bg) 18 | } 19 | } 20 | 21 | .image-bg { 22 | display: none 23 | } 24 | 25 | ::selection { 26 | background: alpha($themeColor, 0.2) 27 | } 28 | } 29 | 30 | 31 | /** 32 | * Colors 33 | * -------------------------------------------------------------------------- */ 34 | 35 | :root .page-komikku { 36 | --vp-c-brand: $themeColor 37 | --vp-c-brand-light: tint($themeColor, 20%) 38 | --vp-c-brand-lighter: tint($themeColor, 40%) 39 | --vp-c-brand-lightest: tint($themeColor, 60%) 40 | --vp-c-brand-dark: shade($themeColor, 25%) 41 | --vp-c-brand-darker: shade($themeColor, 50%) 42 | --vp-c-brand-darkest: shade($themeColor, 75%) 43 | --vp-c-brand-dimm: alpha($themeColor, 0.08) 44 | } 45 | 46 | /** 47 | * Component: Button 48 | * -------------------------------------------------------------------------- */ 49 | 50 | :root .page-komikku { 51 | --vp-button-brand-border: var(--vp-c-brand-light) 52 | --vp-button-brand-text: var(--vp-c-white) 53 | --vp-button-brand-bg: var(--vp-c-brand) 54 | --vp-button-brand-hover-border: var(--vp-c-brand-light) 55 | --vp-button-brand-hover-text: var(--vp-c-white) 56 | --vp-button-brand-hover-bg: var(--vp-c-brand-light) 57 | --vp-button-brand-active-border: var(--vp-c-brand-light) 58 | --vp-button-brand-active-text: var(--vp-c-white) 59 | --vp-button-brand-active-bg: var(--vp-button-brand-bg) 60 | } 61 | -------------------------------------------------------------------------------- /website/src/.vitepress/theme/styles/forks/lint.styl: -------------------------------------------------------------------------------- 1 | .extension-list { 2 | > div { 3 | &:not(:first-of-type) { 4 | .extensions-total { 5 | display: none 6 | } 7 | } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /website/src/.vitepress/theme/styles/forks/tachiyomi-az.styl: -------------------------------------------------------------------------------- 1 | // Assign theme color 2 | $themeColor = #ffcc4d 3 | 4 | .page-tachiyomi-az { 5 | .VPHero { 6 | h1 { 7 | .clip { 8 | --vp-home-hero-name-background: -webkit-linear-gradient(120deg, var(--vp-c-brand) 30%, var(--vp-c-brand-dark)) 9 | } 10 | } 11 | } 12 | 13 | .VPButton { 14 | &.brand { 15 | border-color: var(--vp-button-brand-border) 16 | color: var(--vp-button-brand-text) 17 | background-color: var(--vp-button-brand-bg) 18 | } 19 | } 20 | 21 | .image-bg { 22 | display: none 23 | } 24 | 25 | ::selection { 26 | background: alpha($themeColor, 0.2) 27 | } 28 | } 29 | 30 | 31 | /** 32 | * Colors 33 | * -------------------------------------------------------------------------- */ 34 | 35 | :root .page-tachiyomi-az { 36 | --vp-c-brand: $themeColor 37 | --vp-c-brand-light: tint($themeColor, 20%) 38 | --vp-c-brand-lighter: tint($themeColor, 40%) 39 | --vp-c-brand-lightest: tint($themeColor, 60%) 40 | --vp-c-brand-dark: shade($themeColor, 25%) 41 | --vp-c-brand-darker: shade($themeColor, 50%) 42 | --vp-c-brand-darkest: shade($themeColor, 75%) 43 | --vp-c-brand-dimm: alpha($themeColor, 0.08) 44 | } 45 | 46 | /** 47 | * Component: Button 48 | * -------------------------------------------------------------------------- */ 49 | 50 | :root .page-tachiyomi-az { 51 | --vp-button-brand-border: var(--vp-c-brand-light) 52 | --vp-button-brand-text: var(--vp-c-black) 53 | --vp-button-brand-bg: var(--vp-c-brand) 54 | --vp-button-brand-hover-border: var(--vp-c-brand-light) 55 | --vp-button-brand-hover-text: var(--vp-c-black) 56 | --vp-button-brand-hover-bg: var(--vp-c-brand-light) 57 | --vp-button-brand-active-border: var(--vp-c-brand-light) 58 | --vp-button-brand-active-text: var(--vp-c-black) 59 | --vp-button-brand-active-bg: var(--vp-button-brand-bg) 60 | } 61 | 62 | /** 63 | * Component: Dumb 64 | * -------------------------------------------------------------------------- */ 65 | @font-face { 66 | font-family: 'Comic Sans MS' 67 | src: url('//db.onlinewebfonts.com/t/7cc6719bd5f0310be3150ba33418e72e.eot') 68 | 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') 69 | } 70 | 71 | .azContainer { 72 | width: 100% 73 | overflow: hidden 74 | 75 | .azMarquee { 76 | display: inline-block 77 | overflow: hidden 78 | white-space: nowrap 79 | animation: marquee 10s linear infinite 80 | padding-left: 100% 81 | 82 | .azWiggleText { 83 | padding: 2em 84 | width: fit-content 85 | animation: wiggleAnimation 1s ease-out infinite 86 | 87 | &:hover { 88 | animation: barrelRollAnimation 0.6s 89 | } 90 | 91 | .azText { 92 | font-family: 'Comic Sans MS', 'Comic Sans', cursive 93 | font-size: 2em 94 | display: inline-block 95 | -webkit-text-stroke: 1px black 96 | text-shadow: -1px -1px 0 #000, 1px -1px 0 #000, -1px 1px 0 #000, 1px 1px 0 #000 97 | animation: rainbowTextColorAnimation 0.5s linear infinite, scaleXTextAnimation 2s infinite 98 | } 99 | } 100 | } 101 | } 102 | 103 | @keyframes marquee { 104 | 0% { 105 | transform: translate(0, 0) 106 | } 107 | 108 | 100% { 109 | transform: translate(-100%, 0) 110 | } 111 | } 112 | 113 | @keyframes barrelRollAnimation { 114 | from { 115 | transform: rotate(0) 116 | } 117 | 118 | to { 119 | transform: rotate(360deg) 120 | } 121 | } 122 | 123 | @keyframes wiggleAnimation { 124 | 0% { 125 | transform: rotate(0) 126 | } 127 | 128 | 25% { 129 | transform: rotate(-15deg) 130 | } 131 | 132 | 50% { 133 | transform: rotate(0) 134 | } 135 | 136 | 75% { 137 | transform: rotate(15deg) 138 | } 139 | 140 | 100% { 141 | transform: rotate(0) 142 | } 143 | } 144 | 145 | @keyframes scaleXTextAnimation { 146 | 0% { 147 | transform: scaleX(0.5) scaleY(0.5) 148 | } 149 | 150 | 50% { 151 | transform: scaleX(1) scaleY(1) 152 | } 153 | 154 | 100% { 155 | transform: scaleX(0.5) scaleY(0.5) 156 | } 157 | } 158 | 159 | @keyframes rainbowTextColorAnimation { 160 | 0% { 161 | color: hsl(0, 100%, 50%) 162 | } 163 | 164 | 10% { 165 | color: hsl(36, 100%, 50%) 166 | } 167 | 168 | 20% { 169 | color: hsl(72, 100%, 50%) 170 | } 171 | 172 | 30% { 173 | color: hsl(108, 100%, 50%) 174 | } 175 | 176 | 40% { 177 | color: hsl(144, 100%, 50%) 178 | } 179 | 180 | 50% { 181 | color: hsl(180, 100%, 50%) 182 | } 183 | 184 | 60% { 185 | color: hsl(216, 100%, 50%) 186 | } 187 | 188 | 70% { 189 | color: hsl(252, 100%, 50%) 190 | } 191 | 192 | 80% { 193 | color: hsl(288, 100%, 50%) 194 | } 195 | 196 | 90% { 197 | color: hsl(324, 100%, 50%) 198 | } 199 | 200 | 100% { 201 | color: hsl(360, 100%, 50%) 202 | } 203 | } 204 | -------------------------------------------------------------------------------- /website/src/.vitepress/theme/styles/forks/tachiyomi-j2k.styl: -------------------------------------------------------------------------------- 1 | // Assign theme color 2 | $themeColor = #0952af 3 | 4 | .page-tachiyomi-j2k { 5 | .VPHero { 6 | h1 { 7 | .clip { 8 | --vp-home-hero-name-background: -webkit-linear-gradient(120deg, var(--vp-c-brand) 30%, var(--vp-c-brand-dark)) 9 | } 10 | } 11 | } 12 | 13 | .VPButton { 14 | &.brand { 15 | border-color: var(--vp-button-brand-border) 16 | color: var(--vp-button-brand-text) 17 | background-color: var(--vp-button-brand-bg) 18 | } 19 | } 20 | 21 | .image-bg { 22 | display: none 23 | } 24 | 25 | ::selection { 26 | background: alpha($themeColor, 0.2) 27 | } 28 | } 29 | 30 | 31 | /** 32 | * Colors 33 | * -------------------------------------------------------------------------- */ 34 | 35 | :root .page-tachiyomi-j2k { 36 | --vp-c-brand: $themeColor 37 | --vp-c-brand-light: tint($themeColor, 20%) 38 | --vp-c-brand-lighter: tint($themeColor, 40%) 39 | --vp-c-brand-lightest: tint($themeColor, 60%) 40 | --vp-c-brand-dark: shade($themeColor, 25%) 41 | --vp-c-brand-darker: shade($themeColor, 50%) 42 | --vp-c-brand-darkest: shade($themeColor, 75%) 43 | --vp-c-brand-dimm: alpha($themeColor, 0.08) 44 | } 45 | 46 | /** 47 | * Component: Button 48 | * -------------------------------------------------------------------------- */ 49 | 50 | :root .page-tachiyomi-j2k { 51 | --vp-button-brand-border: var(--vp-c-brand-light) 52 | --vp-button-brand-text: var(--vp-c-white) 53 | --vp-button-brand-bg: var(--vp-c-brand) 54 | --vp-button-brand-hover-border: var(--vp-c-brand-light) 55 | --vp-button-brand-hover-text: var(--vp-c-white) 56 | --vp-button-brand-hover-bg: var(--vp-c-brand-light) 57 | --vp-button-brand-active-border: var(--vp-c-brand-light) 58 | --vp-button-brand-active-text: var(--vp-c-white) 59 | --vp-button-brand-active-bg: var(--vp-button-brand-bg) 60 | } 61 | -------------------------------------------------------------------------------- /website/src/.vitepress/theme/styles/forks/tachiyomi-sy.styl: -------------------------------------------------------------------------------- 1 | // Assign theme color 2 | $themeColor = #ce2828 3 | 4 | .page-tachiyomi-sy { 5 | .VPHero { 6 | h1 { 7 | .clip { 8 | --vp-home-hero-name-background: -webkit-linear-gradient(120deg, var(--vp-c-brand) 30%, var(--vp-c-brand-dark)) 9 | } 10 | } 11 | } 12 | 13 | .VPButton { 14 | &.brand { 15 | border-color: var(--vp-button-brand-border) 16 | color: var(--vp-button-brand-text) 17 | background-color: var(--vp-button-brand-bg) 18 | } 19 | } 20 | 21 | .image-bg { 22 | display: none 23 | } 24 | 25 | ::selection { 26 | background: alpha($themeColor, 0.2) 27 | } 28 | } 29 | 30 | 31 | /** 32 | * Colors 33 | * -------------------------------------------------------------------------- */ 34 | 35 | :root .page-tachiyomi-sy { 36 | --vp-c-brand: $themeColor 37 | --vp-c-brand-light: tint($themeColor, 20%) 38 | --vp-c-brand-lighter: tint($themeColor, 40%) 39 | --vp-c-brand-lightest: tint($themeColor, 60%) 40 | --vp-c-brand-dark: shade($themeColor, 25%) 41 | --vp-c-brand-darker: shade($themeColor, 50%) 42 | --vp-c-brand-darkest: shade($themeColor, 75%) 43 | --vp-c-brand-dimm: alpha($themeColor, 0.08) 44 | } 45 | 46 | /** 47 | * Component: Button 48 | * -------------------------------------------------------------------------- */ 49 | 50 | :root .page-tachiyomi-sy { 51 | --vp-button-brand-border: var(--vp-c-brand-light) 52 | --vp-button-brand-text: var(--vp-c-white) 53 | --vp-button-brand-bg: var(--vp-c-brand) 54 | --vp-button-brand-hover-border: var(--vp-c-brand-light) 55 | --vp-button-brand-hover-text: var(--vp-c-white) 56 | --vp-button-brand-hover-bg: var(--vp-c-brand-light) 57 | --vp-button-brand-active-border: var(--vp-c-brand-light) 58 | --vp-button-brand-active-text: var(--vp-c-white) 59 | --vp-button-brand-active-bg: var(--vp-button-brand-bg) 60 | } 61 | -------------------------------------------------------------------------------- /website/src/.vitepress/theme/styles/forks/yokai.styl: -------------------------------------------------------------------------------- 1 | // Assign theme color 2 | $themeColor = #30bbf8 3 | 4 | .page-yokai { 5 | .VPHero { 6 | h1 { 7 | .clip { 8 | --vp-home-hero-name-background: -webkit-linear-gradient(120deg, var(--vp-c-brand) 30%, var(--vp-c-brand-dark)) 9 | } 10 | } 11 | } 12 | 13 | .VPButton { 14 | &.brand { 15 | border-color: var(--vp-button-brand-border) 16 | color: var(--vp-button-brand-text) 17 | background-color: var(--vp-button-brand-bg) 18 | } 19 | } 20 | 21 | .image-bg { 22 | display: none 23 | } 24 | 25 | ::selection { 26 | background: alpha($themeColor, 0.2) 27 | } 28 | } 29 | 30 | 31 | /** 32 | * Colors 33 | * -------------------------------------------------------------------------- */ 34 | 35 | :root .page-yokai { 36 | --vp-c-brand: $themeColor 37 | --vp-c-brand-light: tint($themeColor, 20%) 38 | --vp-c-brand-lighter: tint($themeColor, 40%) 39 | --vp-c-brand-lightest: tint($themeColor, 60%) 40 | --vp-c-brand-dark: shade($themeColor, 25%) 41 | --vp-c-brand-darker: shade($themeColor, 50%) 42 | --vp-c-brand-darkest: shade($themeColor, 75%) 43 | --vp-c-brand-dimm: alpha($themeColor, 0.08) 44 | } 45 | 46 | /** 47 | * Component: Button 48 | * -------------------------------------------------------------------------- */ 49 | 50 | :root .page-yokai { 51 | --vp-button-brand-border: var(--vp-c-brand-light) 52 | --vp-button-brand-text: var(--vp-c-white) 53 | --vp-button-brand-bg: var(--vp-c-brand) 54 | --vp-button-brand-hover-border: var(--vp-c-brand-light) 55 | --vp-button-brand-hover-text: var(--vp-c-white) 56 | --vp-button-brand-hover-bg: var(--vp-c-brand-light) 57 | --vp-button-brand-active-border: var(--vp-c-brand-light) 58 | --vp-button-brand-active-text: var(--vp-c-white) 59 | --vp-button-brand-active-bg: var(--vp-button-brand-bg) 60 | } 61 | -------------------------------------------------------------------------------- /website/src/.vitepress/theme/styles/tree.styl: -------------------------------------------------------------------------------- 1 | .tree { 2 | border-radius: 8px 3 | margin: 16px 4 | padding: 16px 5 | color: var(--vp-c-text-1) 6 | // background-color: var(--vp-code-block-bg) 7 | font-family: var(--vp-font-family-mono) 8 | font-size: 0.85rem 9 | line-height: 1.5 10 | 11 | & > ul { 12 | margin: 0 13 | } 14 | 15 | li { 16 | & + li { 17 | margin-top: 0 18 | } 19 | } 20 | 21 | span { 22 | &.folder { 23 | &.root, 24 | &.main { 25 | color: var(--vp-c-brand-darker) 26 | } 27 | 28 | &.dynamic { 29 | color: var(--vp-c-brand-darker) 30 | } 31 | } 32 | 33 | &.file.zip { 34 | color: var(--vp-c-brand-darker) !important 35 | } 36 | 37 | &.file { 38 | .file-extension { 39 | color: var(--vp-c-text-2) 40 | } 41 | } 42 | } 43 | 44 | ul { 45 | padding-left: 5px 46 | list-style: none 47 | 48 | li { 49 | position: relative 50 | padding-left: 15px 51 | -webkit-box-sizing: border-box 52 | -moz-box-sizing: border-box 53 | box-sizing: border-box 54 | 55 | &:before { 56 | top: 15px 57 | left: 0 58 | width: 10px 59 | height: 1px 60 | margin: auto 61 | } 62 | 63 | &:after { 64 | top: 0 65 | bottom: 0 66 | left: 0 67 | width: 1px 68 | height: 100% 69 | } 70 | 71 | &:before, 72 | &:after { 73 | position: absolute 74 | content: '' 75 | background-color: var(--vp-c-text-3) 76 | } 77 | 78 | &:last-child { 79 | &:after { 80 | height: 15px 81 | } 82 | } 83 | } 84 | } 85 | 86 | &-icon { 87 | height: 12px 88 | width: 12px 89 | margin-right: 6px 90 | display: inline-block 91 | } 92 | } 93 | 94 | .dark { 95 | .tree { 96 | span { 97 | &.folder, 98 | &.file.zip { 99 | color: var(--vp-c-brand-lighter) !important 100 | } 101 | 102 | &.dynamic { 103 | color: var(--vp-c-brand-lightest) !important 104 | } 105 | } 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /website/src/.vitepress/vue-shim.d.ts: -------------------------------------------------------------------------------- 1 | declare module '*.vue' { 2 | import type { Component } from 'vue' 3 | 4 | const _default: Component 5 | export default _default 6 | } 7 | -------------------------------------------------------------------------------- /website/src/changelogs/index.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Changelogs 3 | description: Changelogs of all Mihon stable releases. 4 | lastUpdated: false 5 | editLink: false 6 | prev: false 7 | next: false 8 | --- 9 | 10 | 13 | 14 | # Changelogs 15 | 16 | Changelogs of all Mihon stable releases, which are also available [on GitHub](https://github.com/mihonapp/mihon/releases). Beta releases can be seen [on GitHub](https://github.com/mihonapp/mihon-preview/releases). 17 | 18 | 19 | -------------------------------------------------------------------------------- /website/src/docs/contribute.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Contribute 3 | description: Find out how to help build the app. 4 | --- 5 | 6 | # Contribute 7 | Find out how to help build the app. 8 | 9 | ## Code 10 | Know how to code and want to improve something or you generally want to support the creation of the app? 11 | 12 | [![mihonapp/mihon - GitHub](https://github-readme-stats.vercel.app/api/pin/?username=mihonapp&repo=mihon&bg_color=f6f6f7&title_color=0877d2&icon_color=0877d2&border_radius=8&hide_border=false#light "mihonapp/mihon - GitHub" =400x120)](https://github.com/mihonapp/mihon) 13 | 14 | [![mihonapp/website - GitHub](https://github-readme-stats.vercel.app/api/pin/?username=mihonapp&repo=website&bg_color=f6f6f7&title_color=0877d2&icon_color=0877d2&border_radius=8&hide_border=false#light "mihonapp/website - GitHub" =400x120)](https://github.com/mihonapp/website) 15 | 16 | [![mihonapp/mihon - GitHub](https://github-readme-stats.vercel.app/api/pin/?username=mihonapp&repo=mihon&bg_color=1a1a41&text_color=c9d1d9&title_color=0877d2&icon_color=0877d2&border_radius=8&hide_border=true#dark "mihonapp/mihon - GitHub" =400x120)](https://github.com/mihonapp/mihon) 17 | 18 | [![mihonapp/website - GitHub](https://github-readme-stats.vercel.app/api/pin/?username=mihonapp&repo=website&bg_color=1a1a41&text_color=c9d1d9&title_color=0877d2&icon_color=0877d2&border_radius=8&hide_border=true#dark "mihonapp/website - GitHub" =400x120)](https://github.com/mihonapp/website) 19 | 20 | ## Translation 21 | Find out how to help translate the app. 22 | 23 | [![Translation stats](https://hosted.weblate.org/widget/mihon/287x66-grey.png "Translation stats - Weblate" =287x66)](https://hosted.weblate.org/engage/mihon/) 24 | 25 | [![Translation progress](https://hosted.weblate.org/widget/mihon/horizontal-auto.svg "Translation progress - Weblate" =760x200)](https://hosted.weblate.org/engage/mihon/) 26 | 27 | Want to help translate the app to your language? 28 | You can easily help by utilizing a service we use called **Weblate**. 29 | 30 | > View translation project [here](https://hosted.weblate.org/engage/mihon/). 31 | 32 | ### Helpful links 33 | * [Translators guide](https://docs.weblate.org/en/latest/user/translating.html) 34 | * [Secondary languages](https://docs.weblate.org/en/latest/user/profile.html#secondary-languages) 35 | * [Subscriptions](https://docs.weblate.org/en/latest/user/profile.html#subscriptions) 36 | * [Glossary](https://docs.weblate.org/en/latest/user/translating.html#glossary) 37 | -------------------------------------------------------------------------------- /website/src/docs/faq/browse/extensions.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Extensions 3 | titleTemplate: Browse - Frequently Asked Questions 4 | description: Frequently Asked Questions about Extensions. 5 | --- 6 | 7 | # Extensions 8 | Frequently Asked Questions about Extensions. 9 | 10 | ## Where can I find repositories/extensions for Mihon? 11 | Mihon does not have, associate, or provide any repositories or extensions. 12 | 13 | ::: danger Caution 14 | Beware that any third-party repositories or extensions will have full access to the app and may contain malware. 15 | ::: 16 | 17 | ## What are some recommended extensions and sources? 18 | Mihon is bring-your-own-content and does not offer anything. 19 | 20 | ::: info Disclaimer 21 | **Mihon** isn't responsible for slow, down, missing chapters, or subpar image quality of sources as it doesn't host the content. 22 | ::: 23 | 24 | ## Enabling third-party installations 25 | ::::tabs 26 | == Android 8.0 and higher 27 | When prompted while installing your first extension, allow unknown apps installation from that source. For newer Androids, enable per-app in **Install unknown apps**. 28 | 29 | ::: details Video guide - Recorded on Android 10 30 | 33 | ::: 34 | 35 | ::: tip Still got questions? 36 | 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"). 37 | ::: 38 | == Android 7.1 and lower 39 | When prompted while installing your first extension, allow unknown apps installation from that source. For older Androids, enable globally in **Unknown sources**. 40 | 41 | ::: details Video guide - Recorded on Android 7 42 | 45 | ::: 46 | :::: 47 | 48 | ## How do I uninstall an extension? 49 | Uninstall extensions like regular apps: through device settings or in **Mihon**. 50 | 51 | ::: tip Uninstalling an extension 52 | In **Mihon**, uninstall an extension via