Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
109 changes: 108 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,113 @@
# Chytanka

This project was generated with [Angular CLI](https://github.com/angular/angular-cli) version 17.0.0.
**Chytanka** is a versatile and user-friendly PWA for reading manga, comics, and other visual stories. Whether you prefer to read from popular online platforms, your own server, or local files, Chytanka is here to enhance your reading experience.

## Features

### 🖥️ **Read Episodes Online**

Chytanka supports opening episodes from the following platforms:

- [x] [Blankary](https://blankary.com)
- [x] [Comick](https://comick.io)
- [x] [Imgur](https://imgur.com)
- [x] [Mangadex](https://mangadex.org)
- [x] [Nhentai](https://nhentai.net)
- [x] [Pixiv](https://pixiv.net)
- [x] [Reddit](https://reddit.com)
- [x] [Telegra.ph](https://telegra.ph)
- [x] [Yande.re Pool](https://yande.re/pool)
- [x] [Zenko](https://zenko.online)

### 🌐 **Custom JSON API**

Chytanka can open episodes from any custom JSON API returning the following format:

```json
{
"title": "Title of the episode",
"nsfw": false,
"images": [
{
"src": "full-link-to-image-1"
},
{
"src": "full-link-to-image-2"
},
{
"src": "full-link-to-image-n"
}
]
}
```

### 📚 **Create and Share Readlists**

Compile a readlist using [Chytanka Readlist Creator](https://chytanka.ink/list):

1. Paste supported links into the input field.
2. Edit titles (optional; automatic retrieval supported).
3. Generate and publish a JSON readlist on Rentry, Gist, or your server.
4. Use the generated link to start reading with your custom readlist.

### 📂 **Open Local Files**

Chytanka supports opening the following file formats from your device:

- [x] ZIP/CBZ
- [x] PDF
- [x] MOBI
- [ ] DJVU
- [ ] RAR/CBR

### 📖 **Three Reading Modes**

1. **Vertical**: Perfect for webtoons.
2. **Horizontal (RTL)**: Best for manga.
3. **Horizontal (LTR)**: Ideal for comics.

### 🌓 **Blue Light Filter**

Read comfortably at night with Chytanka's built-in blue light filter.

### 📱 **Responsive Viewing**

- In horizontal mode with landscape orientation: view two pages side by side.
- In portrait orientation: view one page at a time.

### 🖥️ **Fullscreen Mode**

Immerse yourself in reading with a fullscreen option.

### 🕒 **Viewing History**

- [x] Tracks history of supported links.
- [ ] File history support is planned.

### ⌨️ **Keyboard Shortcuts**

#### On the Start Page:

- `F1` — Open FAQ
- `F2` — Open Settings
- `Ctrl+H` — Open History
- `Ctrl+O` — Open File

#### While Reading:

- `A`, `D`, `ArrowLeft`, `ArrowRight` — Navigate pages in horizontal mode
- `W`, `S`, `ArrowUp`, `ArrowDown` — Navigate pages in vertical mode
- `Ctrl+O` — Open File
- `Ctrl+E` — Share (copy link or embed code)
- `F` — Toggle Fullscreen

### 🔞 **NSFW Content Warning**

If supported by the API, Chytanka warns users about NSFW content.

### 🖇️ **Embed Chytanka on Your Website**

Embed Chytanka using an iframe and interact with it via `postMessage`. Learn more in the [Embedding Guide](https://github.com/chytanka/chytanka.github.io/wiki/Embedding-Chytanka-on-Your-Website).

## Development server

Expand Down
4 changes: 3 additions & 1 deletion angular.json
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@
"build": {
"builder": "@angular-devkit/build-angular:application",
"options": {
"allowedCommonJsDependencies": ["jszip"],
"outputPath": "dist/chytanka",
"index": "src/index.html",
"browser": "src/main.ts",
Expand All @@ -53,7 +54,8 @@
"assets": [
"src/favicon.ico",
"src/assets",
"src/.well-known",
"src/robots.txt",
"src/sitemap.xml",
"src/manifest.webmanifest",
"src/manifest-uk.webmanifest",
"src/CNAME"
Expand Down
25 changes: 25 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "chytanka",
"version": "0.1.25",
"version": "0.14.28",
"scripts": {
"ng": "ng",
"start": "ng serve",
Expand Down Expand Up @@ -29,6 +29,7 @@
"jszip": "^3.10.1",
"ngx-highlightjs": "^12.0.0",
"pdfjs-dist": "^4.6.82",
"readiverse": "^0.1.0",
"rxjs": "~7.8.0",
"tslib": "^2.3.0",
"zone.js": "~0.14.10"
Expand Down
26 changes: 26 additions & 0 deletions scripts/generate-site-module.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
#!/bin/bash

# Default directory if not provided
DEFAULT_DIR="@site-modules"

# Check if the module name is provided
if [ -z "$1" ]; then
echo "Usage: $0 <module-name> [dir]"
exit 1
fi

MODULE_NAME=$1
DIR=${2:-$DEFAULT_DIR} # Use second argument or default to @site-modules

echo "Creating module $MODULE_NAME in directory $DIR..."

# Generate the module with routing
ng g m "$DIR/$MODULE_NAME" --routing

# Generate the shell component
ng g c "$DIR/$MODULE_NAME/$MODULE_NAME-shell" --no-standalone

# Generate the service for data-access
ng g s "$DIR/$MODULE_NAME/data-access/$MODULE_NAME"

echo "Module $MODULE_NAME successfully created in $DIR."
1 change: 0 additions & 1 deletion src/.well-known/atproto-did

This file was deleted.

1 change: 0 additions & 1 deletion src/CNAME

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { LangService } from "../../../shared/data-access/lang.service";
import { HistoryService } from "../../../history/data-access/history.service";
import { ViewerService } from "../../../shared/data-access";
import { PlaylistItem, PlaylistService, isPlaylist } from "../../../playlist/data-access/playlist.service";
import { MetaTagsService } from "../../../shared/data-access/meta-tags.service";

export abstract class ReadBaseComponent {
protected refresh$: BehaviorSubject<null> = new BehaviorSubject<null>(null);
Expand Down Expand Up @@ -38,7 +39,7 @@ export abstract class ReadBaseComponent {
})
}

private title: Title = inject(Title)
meta = inject(MetaTagsService)
protected route: ActivatedRoute = inject(ActivatedRoute)
public lang: LangService = inject(LangService)

Expand Down Expand Up @@ -82,11 +83,29 @@ export abstract class ReadBaseComponent {
})
}

protected tapSetTitle(): MonoTypeOperatorFunction<CompositionEpisode> {
return tap(async (episode: CompositionEpisode) => {
if (episode) {
this.title.setTitle(`${episode.title} | Chytanka`);
}
// protected tapSetTitle(): MonoTypeOperatorFunction<CompositionEpisode> {
// return tap(async (episode: CompositionEpisode) => {
// if (episode) {
// this.title.setTitle(`${episode.title} | Chytanka`);
// }
// })
// }

protected tapSetMetaTags(copyrights = ''): MonoTypeOperatorFunction<any> {
return tap((v) => {

const t = v?.title;
this.meta.setOg();
this.meta.setTwiter()
this.meta.setTitle(`Читати ${t} онлайн в Читанці`)
this.meta.setDesc(`Читати ${t} онлайн в Читанці`)
this.meta.setImage(v.images[0].src, t, '', copyrights)
// this.meta.setOgUrl(`https://chtnk.online/${v.id}/${MangadexHelper.getAlias(v.attributes)}`)

// if (isNSFW(v.attributes)) {
// this.meta.setAdult()
// }

})
}

Expand All @@ -113,14 +132,14 @@ export abstract class ReadBaseComponent {
return tap(async (episode: CompositionEpisode) => {

if (episode) {

this.currentPlItem.set({
id: post_id,
site: site
})
this.cdr.detectChanges()
}

})
}
}
17 changes: 17 additions & 0 deletions src/app/@site-modules/blankary/blankary-routing.module.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';
import { BlankaryShellComponent } from './blankary-shell/blankary-shell.component';

const routes: Routes = [
{ path: '', redirectTo: '/', pathMatch: 'full' },
{
path: ':id',
component: BlankaryShellComponent
}
];

@NgModule({
imports: [RouterModule.forChild(routes)],
exports: [RouterModule]
})
export class BlankaryRoutingModule { }
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
<app-common-read [episode$]="episode$" [error$]="error$" [loading$]="loading$" (refreshData)="refreshData()"
[playlist]="playlistService.playlist()" [playlistLink]="playlistLink()" [currentPlaylistItem]="currentPlItem()">

<p>{{lang.ph().imagesVia}}<a href="https://blankary.com" target="_blank" rel="noopener noreferrer">Blankary</a>
API.
{{lang.ph().thanks}}<br>{{lang.ph().detalisCopy}}</p>

</app-common-read>
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import { Component, inject, OnDestroy } from '@angular/core';
import { switchMap, of, MonoTypeOperatorFunction, tap } from 'rxjs';
import { BLANKARY_PATH } from '../../../app-routing.module';
import { ReadBaseComponent } from '../../@common-read';
import { Base64 } from '../../../shared/utils';
import { BlankaryService } from '../data-access/blankary.service';
import { MetaTagsService } from '../../../shared/data-access/meta-tags.service';

@Component({
selector: 'app-blankary-shell',
templateUrl: './blankary-shell.component.html',
styleUrl: './blankary-shell.component.scss'
})
export class BlankaryShellComponent extends ReadBaseComponent implements OnDestroy {
blankary = inject(BlankaryService)


override episode$ = this.combineParamMapAndRefresh()
.pipe(this.tapStartLoading(),
switchMap(([params]) => {
const pathParam = params?.get('id');

if (!pathParam) return of(null);

const path = (Base64.isBase64(pathParam)) ? Base64.fromBase64(pathParam) : pathParam;

return (this.blankary.getComposition(path)).pipe(
this.catchError(),
this.tapSetMetaTags(),
this.tapSaveToHistory(BLANKARY_PATH, path),
this.tapSaveToCurrentPlaylistItem(BLANKARY_PATH, path),
this.finalizeLoading());
})
);

constructor() {
super()
}

ngOnDestroy(): void {
this.plObserv?.unsubscribe();
}



}
19 changes: 19 additions & 0 deletions src/app/@site-modules/blankary/blankary.module.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';

import { BlankaryRoutingModule } from './blankary-routing.module';
import { BlankaryShellComponent } from './blankary-shell/blankary-shell.component';
import { CommonReadModule } from '../@common-read';


@NgModule({
declarations: [
BlankaryShellComponent
],
imports: [
CommonModule,
BlankaryRoutingModule,
CommonReadModule
]
})
export class BlankaryModule { }
Loading
Loading