Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: List builder #13

Merged
merged 1 commit into from
Jul 5, 2024
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
24 changes: 24 additions & 0 deletions package-lock.json

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

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
"@angular/router": "^17.1.1",
"@angular/service-worker": "^17.1.1",
"dexie": "^4.0.4",
"ngx-highlightjs": "^12.0.0",
"rxjs": "~7.8.0",
"tslib": "^2.3.0",
"zone.js": "~0.14.2"
Expand Down
5 changes: 5 additions & 0 deletions src/app/app-routing.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,12 +22,17 @@ export const TELEGRAPH_PATH = `telegraph`;
export const IMGUR_PATH = `imgur`;
export const REDDIT_PATH = `reddit`;
export const READ_PATH = `read`;
export const LIST_PATH = `list`;

const routes: Routes = [
{
path: '',
loadChildren: () => import('./link-parser/link-parser.module').then(m => m.LinkParserModule)
},
{
path: LIST_PATH,
loadChildren: () => import('./list/list.module').then(m => m.ListModule)
},
{
path: IMGUR_PATH,
loadChildren: () => import('./imgur/imgur.module').then(m => m.ImgurModule)
Expand Down
2 changes: 1 addition & 1 deletion src/app/link-parser/data-access/link-parser.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ export class LinkParserService {
const p = this.parsers[i];

const res = p.parse(link);

if (res != null) { return res; }
}

Expand Down
2 changes: 1 addition & 1 deletion src/app/link-parser/utils/imgur-link-parser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,6 @@ import { LinkParser } from "./link-parser";
export class ImgurLinkParser extends LinkParser {
// override regex = /imgur\.com\/(?:a|gallery)\/(\w+)/;
// override regex = /imgur\.com\/(?:a|gallery|t\/manga|t\/webtoon|t\/comics?)\/(\w+)/;
override regex = /imgur\.com\/(?:a|gallery|t\/manga|t\/webtoon|t\/comics?)\/(?:[\w-]+-)?(\w+)/;
override regex = /(?:https:\/\/)imgur\.com\/(?:a|gallery|t\/manga|t\/webtoon|t\/comics?)\/(?:[\w-]+-)?(\w+)/;
override site = 'imgur'
};
3 changes: 2 additions & 1 deletion src/app/link-parser/utils/json-link-parser.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { LinkParser } from "./link-parser";

export class JsonLinkParser extends LinkParser {
override regex = /((?:https?:\/\/)?(?:www\.)?[-a-zA-Z0-9@:%._\+~#=]{1,256}\.[a-zA-Z0-9()]{1,6}\b[-a-zA-Z0-9()@:%_\+.~#?&//=]*)/;
// override regex = /((?:https?:\/\/)?(?:www\.)?[-a-zA-Z0-9@:%._\+~#=]{1,256}\.[a-zA-Z0-9()]{1,6}\b[-a-zA-Z0-9()@:%_\+.~#?&//=]*)/;
protected override regex: RegExp = /((?:https?:\/\/)?(?:www\.)?[-a-zA-Z0-9@:%._\+~#=]{1,256}\.[a-zA-Z0-9()]{1,6}\b[іІїЇа-яА-Я-a-zA-Z0-9()@:%_\+.~#?&//=]*)/;
override site = 'read'
};
24 changes: 24 additions & 0 deletions src/app/link-parser/utils/link-parser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,33 @@ export abstract class LinkParser {
protected regex: RegExp = / /;
protected site: string = ''

public getRegex = () => this.regex;

public parse(link: string): LinkParseResult | null {
const match = link.match(this.regex);

return match ? { site: this.site, id: match[1] } : null;
}

public parseAll(link: string): LinkParseResult[] {

const reg = (!this.regex.global) ?
new RegExp(this.regex.source, this.regex.flags + 'g') :
this.regex;



const matches = link.matchAll(reg);
const results: LinkParseResult[] = [];

for (const match of matches) {
if (match && match[1]) {
console.log(match);

results.push({ site: this.site, id: match[1] });
}
}

return results;
}
}
2 changes: 1 addition & 1 deletion src/app/link-parser/utils/mangadex-link-parser.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { LinkParser } from "./link-parser";

export class MangadexLinkParser extends LinkParser {
override regex = /mangadex\.org\/chapter\/([a-f\d-]+)/;
override regex = /(?:https:\/\/)mangadex\.org\/chapter\/([a-f\d-]+)/;
override site = 'mangadex';
};
2 changes: 1 addition & 1 deletion src/app/link-parser/utils/reddit-link-parser.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { LinkParser } from "./link-parser";

export class RedditLinkParser extends LinkParser {
override regex = /reddit\.com\/[ur]\/\w+(?:\/comments\/)([a-zA-Z0-9]+)(?=[\/?]|$)/;
override regex = /(?:https:\/\/)reddit\.com\/[ur]\/\w+(?:\/comments\/)([a-zA-Z0-9]+)(?=[\/?]|$)/;
// override regex = /reddit\.com\/[ur]\/\w+(?:(?:\/comments\/)|(?:\/s\/))([a-zA-Z0-9]+)(?=[\/?]|$)/;
override site = 'reddit';
};
2 changes: 1 addition & 1 deletion src/app/link-parser/utils/telegraph-link-parser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,6 @@ import { LinkParser } from "./link-parser";

export class TelegraphLinkParser extends LinkParser {
// override regex = /telegra\.ph\/([\w\-_іїґ]+)/;
override regex = /telegra\.ph\/([\p{L}\p{N}\p{M}\-_%]+)/u;
override regex = /(?:https:\/\/)telegra\.ph\/([\p{L}\p{N}\p{M}\-_%]+)/u;
override site = 'telegraph'
};
13 changes: 13 additions & 0 deletions src/app/list/list-routing.module.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';
import { ListShellComponent } from './list-shell/list-shell.component';

const routes: Routes = [
{ path: '', component: ListShellComponent }
];

@NgModule({
imports: [RouterModule.forChild(routes)],
exports: [RouterModule]
})
export class ListRoutingModule { }
54 changes: 54 additions & 0 deletions src/app/list/list-shell/list-shell.component.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
<main>
<section>
<h4>Вставте підтримувані посилання у поле нижче 🔗⬇️</h4>
<textarea class="input" style="width: 100%; height: 8lh; box-shadow: var(--shadow-elevation-medium);"
placeholder="Вставте сюди підтримувані посилання на епізод через кому пробіл, чи кожне посилання з нового рядка:

https://telegra.ph/YAk-kozaki-kulіsh-varili-06-28
https://telegra.ph/YAk-kozaki-kulіsh-varili-ch2-07-03" [ngModel]="inputValue()"
(ngModelChange)="inputValue.set($event);"></textarea>
</section>
@if(listValue() && listValue().length > 0){
<section>
<h4>Відредагуйте назви (за бажанням) ✏️🔢📝</h4>
<div style="display: grid; gap: 1ch">
@for (item of outputValue(); track $index) {
<article
style="box-shadow: var(--shadow-elevation-low); border: 1px solid #8884; padding: .5ch; border-radius: .5ch;">
<div class="input-group">
<input style="width: 100%;" placeholder="Введіть назву епізоду (не обов'язково)" type="text"
[(ngModel)]="item.title"> <button class="button"
(click)=" (setAutoTitle(item.link, item))">🤖🏷️</button>
</div> <br>

<label [for]="'nsfw-'+$index">🔞</label>
<input type="checkbox" [id]="'nsfw-'+$index" [(ngModel)]="item.nsfw">
<small><b>{{item.link}}</b></small>
</article>
}
</div>

</section>

<section>
<h4>Скопіюйте JSON код, та опублікуйте його на <a href="//rentry.co" target="_blank"
rel="noopener noreferrer">Rentry</a> чи <a href="//gist.github.com/" target="_blank"
rel="noopener noreferrer">Gist</a> 📋💻🌐</h4>
<div class="input-group">
<textarea style="width: 100%; height: 8lh; resize: unset;" readonly>{{outputValue() | json}}</textarea>
<button class="button small" (click)="copy()">Copy</button>
</div>
</section>

<section>
<h4>Вставте посилання на RAW JSON-файл 🔗📄</h4>
<div class="input-group" style="gap: 1ch">
<input type="text" [ngModel]="rawJsonLink()" (ngModelChange)="rawJsonLink.set($event);">
@if(rawJsonLink()){<a style="text-align: center;" class="button large primary" target="__blank"
[routerLink]="['/', parsedFirstLink()?.site, parsedFirstLink()?.id]"
[queryParams]="{lang: lang.lang(), list: rawJsonLink()}">
{{getItem(firstLink())?.title != ''? getItem(firstLink())?.title :
lang.ph().untitled }}</a>}
</div>
</section>}
</main>
43 changes: 43 additions & 0 deletions src/app/list/list-shell/list-shell.component.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
:host {
display: block;
padding: 0 1ch;
}
main {
margin: 0 auto;
padding: 1ch 0 2ch;
max-width: 80ch;
display: grid;
// grid-template-rows: auto 1fr auto;
// height: 100dvh;
}

.input-group {
box-shadow: var(--shadow-elevation-medium);
}

textarea {
padding: 1ch;

display: block;
font: inherit;
padding: 1.5ch 2ch;
background: transparent;
font-family: 'Courier New', Courier, monospace;
color: inherit;
width: 100%;
transition: all var(--t) cubic-bezier(0.075, 0.82, 0.165, 1);
border-radius: .5ch;
border: 2px solid #16649640;

// background-color: #00274190;
// @media (prefers-color-scheme: light) {
// background-color: #eceff2a0;
// }

&:focus {
border-color: #166496;
outline: unset;
}
}


114 changes: 114 additions & 0 deletions src/app/list/list-shell/list-shell.component.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
import { Component, EffectCleanupRegisterFn, WritableSignal, computed, effect, inject, signal } from '@angular/core';
import { LinkParserService } from '../../link-parser/data-access/link-parser.service';
import { ImgurLinkParser, JsonLinkParser, LinkParser, MangadexLinkParser, RedditLinkParser, TelegraphLinkParser } from '../../link-parser/utils';
import { DomManipulationService } from '../../shared/data-access';
import { LangService } from '../../shared/data-access/lang.service';


@Component({
selector: 'app-list-shell',
templateUrl: './list-shell.component.html',
styleUrls: [
'./list-shell.component.scss',
'/src/app/shared/ui/@styles/input-group.scss'
]
})
export class ListShellComponent {
public inputValue: WritableSignal<string> = signal<string>('')

// signal<string>(`https://telegra.ph/YAk-kozaki-kulіsh-varili-06-28
// https://telegra.ph/YAk-kozaki-kulіsh-varili-ch2-07-03`);

listValue = computed(() => this.parseAllUrls(this.inputValue()))

outputValue = computed(() => this.listValue().map((i: string) => {
const saved = this.getItem(i);

const res = {
link: i,
title: saved?.title ?? '',
nsfw: saved?.nsfw ?? false
}

if (!saved) this.outputValueCopy.push(res)

if (saved ?? saved?.link == res.link) {
saved.title = res.title
saved.snfw = res.nsfw
}
return res
}))

outputValueCopy: any[] = [];

rawJsonLink = signal('');

public parser: LinkParserService = inject(LinkParserService);
public domMan: DomManipulationService = inject(DomManipulationService);
public lang: LangService = inject(LangService);

urlParser: LinkParser[] = [new JsonLinkParser];
await: any;
parseAllUrls(text: string) {
const matches: any = [];
this.urlParser.forEach(regex => {

const origReg = regex.getRegex();
const reg = (!origReg.global) ? new RegExp(origReg.source, origReg.flags + 'g') : origReg;

let match;
while ((match = reg.exec(text)) !== null) {
matches.push(match[0]);
}
});

return matches;
}

getItem(link: string) {
for (let i = 0; i < this.outputValueCopy.length; i++) {
const item = this.outputValueCopy[i];
if (item.link == link) return item;
}
}

constructor() {
this.initParser();
}

firstLink = computed(() => this.listValue()[0] ?? '');
parsedFirstLink = computed(() => this.parser.parse(this.firstLink() ?? ''))

initParser() {
this.parser.parsers = [];
this.parser.parsers.push(new ImgurLinkParser)
this.parser.parsers.push(new MangadexLinkParser)
this.parser.parsers.push(new TelegraphLinkParser)
this.parser.parsers.push(new RedditLinkParser)
this.parser.parsers.push(new JsonLinkParser)
}

copy() {
this.domMan.copyToClipboard(JSON.stringify(this.outputValue()))
}


async fetchTitle(url: string) {
const apiUrl = `https://api.microlink.io?url=${encodeURIComponent(url)}`;
try {
const response = await fetch(apiUrl);
const data = await response.json();

return data.data.title;
} catch (error) {
console.error('Error fetching title:', error);
return ''
}
}

async setAutoTitle(url: string, field: any) {
const t = await this.fetchTitle(url);
field.title = t
}

}
Loading
Loading