init
This commit is contained in:
commit
5ae824b63a
36 changed files with 3290 additions and 0 deletions
128
src/build.ts
Normal file
128
src/build.ts
Normal file
|
@ -0,0 +1,128 @@
|
|||
import { env } from "bun";
|
||||
import { SveltePlugin } from "bun-plugin-svelte";
|
||||
import Tail from "bun-plugin-tailwind";
|
||||
import path from "path";
|
||||
|
||||
const development = env.NODE_ENV !== "production";
|
||||
|
||||
const publicPath = development
|
||||
? "http://localhost:3000/html/"
|
||||
: "https://userscripts.skaarup.dev/html/";
|
||||
|
||||
function replaceUrl(text: string): string {
|
||||
if (!development) return text;
|
||||
|
||||
return text.replaceAll(
|
||||
"https://userscripts.skaarup.dev",
|
||||
"http://localhost:3000",
|
||||
);
|
||||
}
|
||||
|
||||
const tagStart = `// ==UserScript==\n`;
|
||||
const tagEnd = `// ==/UserScript==\n`;
|
||||
const tagEndLen = tagEnd.length;
|
||||
|
||||
const globScanOptions: Bun.GlobScanOptions = {
|
||||
absolute: true,
|
||||
};
|
||||
|
||||
const userscripts = new Bun.Glob("./src/userscripts/*/*.user.ts");
|
||||
for await (const absPath of userscripts.scan(globScanOptions)) {
|
||||
const parsed = path.parse(absPath);
|
||||
console.log(`Building ${parsed.base}...`);
|
||||
|
||||
const file = Bun.file(absPath);
|
||||
const fileText = await file.text();
|
||||
|
||||
const bannerStart = fileText.indexOf(tagStart);
|
||||
const bannerEnd = fileText.indexOf(tagEnd) + tagEndLen;
|
||||
|
||||
let banner = fileText.slice(bannerStart, bannerEnd);
|
||||
|
||||
banner = replaceUrl(banner);
|
||||
|
||||
await Bun.build({
|
||||
entrypoints: [absPath],
|
||||
banner,
|
||||
outdir: `./build`,
|
||||
splitting: false,
|
||||
minify: false,
|
||||
env: "PUBLIC_*",
|
||||
sourcemap: "linked",
|
||||
packages: "bundle",
|
||||
target: "browser", // Also supports "bun" or "node" for server-side components
|
||||
plugins: [
|
||||
Tail,
|
||||
SveltePlugin({
|
||||
development: false, // Enable dev features, set to false for production
|
||||
}),
|
||||
],
|
||||
publicPath,
|
||||
});
|
||||
|
||||
await swapHtmlImports(fileText, `./build/${parsed.name}.js`);
|
||||
}
|
||||
|
||||
const styles = new Bun.Glob("./src/userscripts/*/*.user.css");
|
||||
for await (const absPath of styles.scan(globScanOptions)) {
|
||||
const name = path.parse(absPath).base;
|
||||
|
||||
console.log(`Building ${name}...`);
|
||||
|
||||
const outFile = `./build/${name}`;
|
||||
|
||||
await Bun.$`bunx @tailwindcss/cli --optimize -i ${absPath} -o ${outFile}`.quiet();
|
||||
|
||||
const file = Bun.file(outFile);
|
||||
|
||||
let text = await file.text();
|
||||
|
||||
text = replaceUrl(text);
|
||||
|
||||
file.writer().write(text);
|
||||
}
|
||||
|
||||
async function swapHtmlImports(fileText: string, filePath: string) {
|
||||
const imports: string[] = [];
|
||||
|
||||
const lines = fileText.split("\n");
|
||||
for (const line of lines) {
|
||||
if (
|
||||
!line.startsWith("import ") ||
|
||||
(!line.endsWith('.html";') && !line.endsWith(".html';"))
|
||||
)
|
||||
continue;
|
||||
|
||||
// Extract the import path between the quotes
|
||||
const match = line.match(/['"]([^'"]+)['"]/);
|
||||
if (!match) continue;
|
||||
|
||||
const importPath = match[1];
|
||||
// Check if the import path is a .html file
|
||||
if (!importPath) continue;
|
||||
imports.push(path.parse(importPath).name);
|
||||
}
|
||||
|
||||
const outFile = Bun.file(filePath);
|
||||
const text = await outFile.text();
|
||||
|
||||
const startIdx = text.indexOf(tagStart);
|
||||
const endIdx = text.indexOf(tagEnd);
|
||||
const banner = text.slice(startIdx, endIdx);
|
||||
|
||||
let js = text.slice(endIdx);
|
||||
|
||||
const htmlFiles = new Bun.Glob("./build/*.html");
|
||||
for await (const filePath of htmlFiles.scan(globScanOptions)) {
|
||||
const parsed = path.parse(filePath);
|
||||
const fileName = parsed.base;
|
||||
if (!imports.some((x) => fileName.startsWith(x))) continue;
|
||||
|
||||
const file = Bun.file(filePath);
|
||||
const html = await file.text();
|
||||
|
||||
js = js.replace(`${publicPath}${fileName}`, btoa(html));
|
||||
}
|
||||
|
||||
outFile.writer().write(`${banner}${js}`);
|
||||
}
|
28
src/index.css
Normal file
28
src/index.css
Normal file
|
@ -0,0 +1,28 @@
|
|||
@import 'tailwindcss';
|
||||
|
||||
@theme {
|
||||
--breakout-size: calc((var(--breakpoint-xl) - var(--breakpoint-lg)) / 2);
|
||||
--ultrawide-val: minmax(calc(var(--spacing) * 4), 1fr);
|
||||
--breakout-val: minmax(0, var(--breakout-size));
|
||||
--content-val: min(100% - calc(var(--spacing) * 8), var(--breakpoint-lg));
|
||||
}
|
||||
|
||||
@layer base {
|
||||
* {
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.content {
|
||||
grid-template-columns:
|
||||
[ultrawide-start] var(--ultrawide-val)
|
||||
[breakout-start] var(--breakout-val)
|
||||
[content-start] var(--content-val)
|
||||
[content-end] var(--breakout-val)
|
||||
[breakout-end] var(--ultrawide-val)
|
||||
[ultrawide-end];
|
||||
}
|
||||
|
||||
.content > * {
|
||||
grid-column: content;
|
||||
}
|
||||
}
|
86
src/index.html
Normal file
86
src/index.html
Normal file
File diff suppressed because one or more lines are too long
0
src/index.ts
Normal file
0
src/index.ts
Normal file
113
src/server.ts
Normal file
113
src/server.ts
Normal file
|
@ -0,0 +1,113 @@
|
|||
import { env } from "bun";
|
||||
import homepage from "./index.html";
|
||||
import robotsTxt from "./static/robots.txt";
|
||||
import sitemapTxt from "./static/sitemap.txt";
|
||||
// @ts-ignore ts2307
|
||||
import icon from "./static/favicon.png" with { type: "file" };
|
||||
import path from "path";
|
||||
|
||||
const favicon = await Bun.file(icon).bytes();
|
||||
|
||||
let htmlFiles: Set<string>;
|
||||
let scriptFiles: Set<string>;
|
||||
let cssFiles: Set<string>;
|
||||
|
||||
const development = env.NODE_ENV !== "production";
|
||||
|
||||
const globScanOptions: Bun.GlobScanOptions = {
|
||||
absolute: true,
|
||||
};
|
||||
|
||||
async function load() {
|
||||
await Bun.$`bun run src/build.ts`;
|
||||
const tempHtml: Set<string> = new Set();
|
||||
const userHtml = new Bun.Glob("./build/*.html");
|
||||
for await (const filePath of userHtml.scan(globScanOptions)) {
|
||||
tempHtml.add(path.parse(filePath).base);
|
||||
}
|
||||
htmlFiles = tempHtml;
|
||||
|
||||
const tempScripts: Set<string> = new Set();
|
||||
const userJs = new Bun.Glob("./build/*.user.js");
|
||||
for await (const filePath of userJs.scan(globScanOptions)) {
|
||||
tempScripts.add(path.parse(filePath).base);
|
||||
}
|
||||
scriptFiles = tempScripts;
|
||||
|
||||
const tempStyles: Set<string> = new Set();
|
||||
const css = new Bun.Glob("./build/*.user.css");
|
||||
for await (const filePath of css.scan(globScanOptions)) {
|
||||
tempStyles.add(path.parse(filePath).base);
|
||||
}
|
||||
cssFiles = tempStyles;
|
||||
}
|
||||
await load();
|
||||
|
||||
Bun.serve({
|
||||
routes: {
|
||||
"/": homepage,
|
||||
"/robots.txt": new Response(robotsTxt, {
|
||||
headers: { "Content-Type": "text/plain" },
|
||||
}),
|
||||
"/sitemap.txt": new Response(sitemapTxt, {
|
||||
headers: { "Content-Type": "text/plain" },
|
||||
}),
|
||||
"/favicon.ico": new Response(favicon, {
|
||||
headers: { "Content-Type": "image/png" },
|
||||
}),
|
||||
"/health": new Response("OK"),
|
||||
"/html/:html": async (req) => {
|
||||
const html = req.params.html.toLocaleLowerCase();
|
||||
|
||||
if (!htmlFiles.has(html)) {
|
||||
return new Response("Not found", { status: 404 });
|
||||
}
|
||||
|
||||
const file = Bun.file(`./build/${html}`);
|
||||
|
||||
return new Response(file, {
|
||||
headers: { "Content-Type": "text/css" },
|
||||
});
|
||||
},
|
||||
"/scripts/:script": async (req) => {
|
||||
const script = req.params.script.toLocaleLowerCase();
|
||||
|
||||
if (!scriptFiles.has(script)) {
|
||||
return new Response("Not found", { status: 404 });
|
||||
}
|
||||
|
||||
const file = Bun.file(`./build/${script}`);
|
||||
|
||||
return new Response(file, {
|
||||
headers: { "Content-Type": "application/javascript" },
|
||||
});
|
||||
},
|
||||
"/styles/:style": async (req) => {
|
||||
const style = req.params.style.toLocaleLowerCase();
|
||||
|
||||
if (!cssFiles.has(style)) {
|
||||
return new Response("Not found", { status: 404 });
|
||||
}
|
||||
|
||||
const file = Bun.file(`./build/${style}`);
|
||||
|
||||
return new Response(file, {
|
||||
headers: { "Content-Type": "text/css" },
|
||||
});
|
||||
},
|
||||
},
|
||||
development,
|
||||
// async fetch(req, server) {
|
||||
// return new Response("Not found", { status: 404 });
|
||||
// },
|
||||
});
|
||||
|
||||
if (development) {
|
||||
setInterval(async () => {
|
||||
try {
|
||||
await load();
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
}
|
||||
}, 2500);
|
||||
}
|
1
src/static/favicon.base64.txt
Normal file
1
src/static/favicon.base64.txt
Normal file
File diff suppressed because one or more lines are too long
BIN
src/static/favicon.png
Normal file
BIN
src/static/favicon.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 5.2 KiB |
10
src/static/manifest.json
Normal file
10
src/static/manifest.json
Normal file
|
@ -0,0 +1,10 @@
|
|||
{
|
||||
"name": "Userscripts by skaarup.dev",
|
||||
"short_name": "Userscripts",
|
||||
"display": "browser",
|
||||
"background_color": "#141141",
|
||||
"theme_color": "#371D85",
|
||||
"description": "Userscripts",
|
||||
"icons": [],
|
||||
"related_applications": []
|
||||
}
|
4
src/static/robots.txt
Normal file
4
src/static/robots.txt
Normal file
|
@ -0,0 +1,4 @@
|
|||
User-agent: *
|
||||
Allow: /
|
||||
|
||||
Sitemap: https://userscripts.skaarup.dev/sitemap.txt
|
1
src/static/sitemap.txt
Normal file
1
src/static/sitemap.txt
Normal file
|
@ -0,0 +1 @@
|
|||
https://userscripts.skaarup.dev/
|
7
src/types/configuration-ui.d.ts
vendored
Normal file
7
src/types/configuration-ui.d.ts
vendored
Normal file
|
@ -0,0 +1,7 @@
|
|||
/* eslint-disable @typescript-eslint/no-unused-vars */
|
||||
namespace configuration_ui {
|
||||
type globalsType = {
|
||||
uiInitialized: boolean;
|
||||
configurationWindowOpen: boolean;
|
||||
};
|
||||
}
|
76
src/types/greasemonkey.d.ts
vendored
Normal file
76
src/types/greasemonkey.d.ts
vendored
Normal file
|
@ -0,0 +1,76 @@
|
|||
// Greasemonkey api
|
||||
// https://wiki.greasespot.net/Greasemonkey_Manual:API
|
||||
|
||||
type gm_responseObjectType = {
|
||||
readyState: number;
|
||||
responseHeaders: string;
|
||||
responseText: string;
|
||||
status: number;
|
||||
statusText: string;
|
||||
context?: unknown;
|
||||
lengthComputable?: boolean;
|
||||
loaded?: number;
|
||||
total?: number;
|
||||
};
|
||||
|
||||
type gm_detailType = {
|
||||
binary?: boolean;
|
||||
context?: unknown;
|
||||
data?: string;
|
||||
headers?: { [key: string]: string };
|
||||
method?: string;
|
||||
overrideMimeType?: string;
|
||||
password?: string;
|
||||
responseType?: string;
|
||||
synchronous?: boolean;
|
||||
timeout?: number;
|
||||
upload?: {
|
||||
onabort?: (event: gm_responseObjectType) => void;
|
||||
onerror?: (event: gm_responseObjectType) => void;
|
||||
onload?: (event: gm_responseObjectType) => void;
|
||||
onprogress?: (event: gm_responseObjectType) => void;
|
||||
};
|
||||
url: string;
|
||||
user?: string;
|
||||
onabort?: (event: gm_responseObjectType) => void;
|
||||
onerror?: (event: gm_responseObjectType) => void;
|
||||
onload?: (event: gm_responseObjectType) => void;
|
||||
onprogress?: (event: gm_responseObjectType) => void;
|
||||
onreadystatechange?: (event: gm_responseObjectType) => void;
|
||||
ontimeout?: (event: gm_responseObjectType) => void;
|
||||
};
|
||||
|
||||
type gm_optionType = {
|
||||
text: string;
|
||||
title?: string;
|
||||
image?: string;
|
||||
onclick?: () => void;
|
||||
ondone?: () => void;
|
||||
};
|
||||
|
||||
type gm_resourceType = {
|
||||
[key: string]: {
|
||||
name: string;
|
||||
mimetype: string;
|
||||
url: string;
|
||||
};
|
||||
};
|
||||
|
||||
type gm_scriptType = {
|
||||
description: string;
|
||||
excludes: string[];
|
||||
includes: string[];
|
||||
matches: string[];
|
||||
name: string;
|
||||
namespace: string;
|
||||
resources: gm_resourceType;
|
||||
"run-at": string;
|
||||
version: string;
|
||||
};
|
||||
|
||||
type gm_infoType = {
|
||||
script: gm_scriptType;
|
||||
scriptMetaStr: string;
|
||||
scriptHandler: string;
|
||||
version: string;
|
||||
};
|
28
src/types/manga-reading.d.ts
vendored
Normal file
28
src/types/manga-reading.d.ts
vendored
Normal file
|
@ -0,0 +1,28 @@
|
|||
/* eslint-disable @typescript-eslint/no-unused-vars */
|
||||
namespace manga_reading {
|
||||
type At = "neither" | "chapter" | "manga";
|
||||
|
||||
type globalsType = {
|
||||
nextUrl: string;
|
||||
prevUrl: string;
|
||||
currentTitle: string;
|
||||
uiInitialized: boolean;
|
||||
at: At;
|
||||
titleList: string[];
|
||||
ptApi: {
|
||||
url: string;
|
||||
bearerToken: string;
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
type shortcutType = "Global" | "ConfigOpen" | "ConfigClosed";
|
||||
|
||||
type shortcutCallback = (event: KeyboardEvent) => Promise<boolean>;
|
||||
|
||||
type shortcut = {
|
||||
name: string;
|
||||
callback: shortcutCallback;
|
||||
};
|
||||
|
||||
type registeredShortcut = { shortcutType: shortcutType; shortcut: shortcut };
|
291
src/types/violentmonkey.d.ts
vendored
Normal file
291
src/types/violentmonkey.d.ts
vendored
Normal file
|
@ -0,0 +1,291 @@
|
|||
type vm_responseObjectTypeUnknown = vm_responseObjectType<unknown>;
|
||||
type vm_responseObjectType<T> = {
|
||||
status: number;
|
||||
statusText: string;
|
||||
readyState: number;
|
||||
responseHeaders: string;
|
||||
response: string | Blob | ArrayBuffer | Document | object | null;
|
||||
responseText: string | undefined;
|
||||
responseXML: Document | null;
|
||||
lengthComputable: boolean;
|
||||
loaded: number;
|
||||
total: number;
|
||||
finalUrl: string;
|
||||
context: T;
|
||||
};
|
||||
|
||||
type vm_detailType<T> = {
|
||||
url: string;
|
||||
method?: string;
|
||||
user?: string;
|
||||
password?: string;
|
||||
overrideMimeType?: string;
|
||||
headers?: { [key: string]: string };
|
||||
responseType?: string;
|
||||
timeout?: number;
|
||||
data?:
|
||||
| string
|
||||
| ArrayBuffer
|
||||
| Blob
|
||||
| DataView
|
||||
| FormData
|
||||
| ReadableStream
|
||||
| TypedArray
|
||||
| URLSearchParams;
|
||||
binary?: boolean;
|
||||
context?: T;
|
||||
anonymous?: boolean;
|
||||
onabort?: (resp: vm_responseObjectType<T>) => void;
|
||||
onerror?: (resp: vm_responseObjectType<T>) => void;
|
||||
onload?: (resp: vm_responseObjectType<T>) => void;
|
||||
onloadend?: (resp: vm_responseObjectType<T>) => void;
|
||||
onloadstart?: (resp: vm_responseObjectType<T>) => void;
|
||||
onprogress?: (resp: vm_responseObjectType<T>) => void;
|
||||
onreadystatechange?: (resp: vm_responseObjectType<T>) => void;
|
||||
ontimeout?: (resp: vm_responseObjectType<T>) => void;
|
||||
};
|
||||
|
||||
type vm_optionType = {
|
||||
text: string;
|
||||
title?: string;
|
||||
image?: string;
|
||||
onclick?: () => void;
|
||||
ondone?: () => void;
|
||||
};
|
||||
|
||||
type vm_resourceType = {
|
||||
name: string;
|
||||
url: string;
|
||||
};
|
||||
|
||||
type vm_scriptType = {
|
||||
description: string;
|
||||
excludes: Array<string>;
|
||||
includes: Array<string>;
|
||||
matches: Array<string>;
|
||||
name: string;
|
||||
namespace: string;
|
||||
resources: Array<vm_resourceType>;
|
||||
"run-at": string;
|
||||
version: string;
|
||||
};
|
||||
|
||||
type vm_platformType = {
|
||||
arch: string;
|
||||
browserName: string;
|
||||
browserVersion: string;
|
||||
fullVersionList: Array<{
|
||||
brand: string;
|
||||
version: string;
|
||||
}>;
|
||||
mobile: boolean;
|
||||
os: string;
|
||||
};
|
||||
|
||||
type vm_infoType = {
|
||||
uuid: string;
|
||||
scriptMetaStr: string;
|
||||
scriptWillUpdate: boolean;
|
||||
scriptHandler: string;
|
||||
version: string;
|
||||
isIncognito: boolean;
|
||||
platform: vm_platformType;
|
||||
userAgent: string;
|
||||
userAgentData: {
|
||||
brands: Array<{ brand: string; version: string }>;
|
||||
mobile: boolean;
|
||||
platform: string;
|
||||
getHighEntropyValues: (
|
||||
hints: string[],
|
||||
) => Promise<{ [key: string]: unknown }>;
|
||||
};
|
||||
script: vm_scriptType;
|
||||
injectInto: string;
|
||||
};
|
||||
|
||||
type openInNewTabOptionsType = {
|
||||
active?: boolean;
|
||||
container?: number;
|
||||
insert?: boolean;
|
||||
pinned?: boolean;
|
||||
};
|
||||
|
||||
type openInNewTabReturnType = {
|
||||
onclose: () => void;
|
||||
closed: boolean;
|
||||
close: () => void;
|
||||
};
|
||||
|
||||
declare namespace GM {
|
||||
function addStyle(css: string): void;
|
||||
function addElement(
|
||||
tagName: string,
|
||||
attributes?: { [key: string]: unknown },
|
||||
): HTMLElement;
|
||||
function addElement(
|
||||
parent: Node | Element | ShadowRoot,
|
||||
tagName: string,
|
||||
attributes?: { [key: string]: unknown },
|
||||
): HTMLElement;
|
||||
function registerMenuCommand(
|
||||
caption: string,
|
||||
func: (event: MouseEvent | KeyboardEvent) => void,
|
||||
options?: { id?: string; title?: string; autoClose?: boolean },
|
||||
): string;
|
||||
function deleteValue(key: string): Promise<void>;
|
||||
function deleteValues(keys: string[]): Promise<void>;
|
||||
function download(options: {
|
||||
url: string;
|
||||
name: string;
|
||||
headers?: object;
|
||||
timeout?: number;
|
||||
context?: unknown;
|
||||
user?: string;
|
||||
password?: string;
|
||||
anonymous?: boolean;
|
||||
onabort?: () => void;
|
||||
onerror?: () => void;
|
||||
onload?: () => void;
|
||||
onloadend?: () => void;
|
||||
onloadstart?: () => void;
|
||||
onprogress?: () => void;
|
||||
onreadystatechange?: () => void;
|
||||
ontimeout?: () => void;
|
||||
}): Promise<void>;
|
||||
function download(url: string, name: string): Promise<void>;
|
||||
function getResourceUrl(name: string, isBlobUrl?: boolean): Promise<string>;
|
||||
function getValue<T>(
|
||||
key: string,
|
||||
defaultValue?: string | number | boolean,
|
||||
): Promise<T>;
|
||||
function getValue(
|
||||
key: string,
|
||||
defaultValue?: string | number | boolean,
|
||||
): Promise<string>;
|
||||
function getValues<T>(keys: string[]): Promise<{ [key: string]: T }>;
|
||||
function getValues<T>(obj: {
|
||||
[key: string]: T;
|
||||
}): Promise<{ [key: string]: T }>;
|
||||
const info: vm_infoType;
|
||||
function listValues(): Promise<string[]>;
|
||||
function notification(options: {
|
||||
text: string;
|
||||
title?: string;
|
||||
image?: string;
|
||||
silent?: boolean; // false
|
||||
tag?: string;
|
||||
zombieTimeout?: number;
|
||||
zombieUrl?: string;
|
||||
onclick?: () => void;
|
||||
ondone?: () => void;
|
||||
}): Promise<() => void>;
|
||||
function notification(
|
||||
text: string,
|
||||
title?: string,
|
||||
image?: string,
|
||||
onclick?: () => void,
|
||||
): promise<() => void>;
|
||||
function openInTab(
|
||||
url: string,
|
||||
options?: {
|
||||
active?: boolean; // true
|
||||
container?: number; // 0 = default (main) container 1, 2, etc. = internal container index
|
||||
insert?: boolean; // true
|
||||
pinned?: boolean; // false
|
||||
},
|
||||
): {
|
||||
onclose?: () => void;
|
||||
closed: boolean;
|
||||
close: () => void;
|
||||
};
|
||||
function openInTab(
|
||||
url: string,
|
||||
openInBackground: boolean,
|
||||
): {
|
||||
onclose?: () => void;
|
||||
closed: boolean;
|
||||
close: () => void;
|
||||
};
|
||||
function setClipboard(data: string, type: string): void;
|
||||
function setValue(key: string, value: unknown): Promise<void>;
|
||||
function setValues(obj: { [key: string]: unknown }): Promise<void>;
|
||||
|
||||
// function xmlHttpRequest<T>(details: vm_detailType<T>): { abort: () => void };
|
||||
function xmlHttpRequest<T>(
|
||||
details: vm_detailType<T>,
|
||||
): Promise<vm_responseObjectType<T>>;
|
||||
}
|
||||
|
||||
function GM_getValue<t>(key: string, defaultValue?: t): t;
|
||||
function GM_getValue<t>(
|
||||
key: string,
|
||||
defaultValue?: string | number | boolean,
|
||||
): t;
|
||||
function GM_getValue(
|
||||
key: string,
|
||||
defaultValue?: string | number | boolean,
|
||||
): string;
|
||||
|
||||
function GM_setValue<t>(key: string, value: t): void;
|
||||
function GM_setValue(key: string, value: string | number | boolean): void;
|
||||
|
||||
function GM_deleteValue(key: string): void;
|
||||
|
||||
function GM_listValues(): string[];
|
||||
|
||||
function GM_addValueChangeListener(
|
||||
name,
|
||||
callback: (name, oldValue, newValue, remote) => void,
|
||||
): string;
|
||||
|
||||
function GM_removeValueChangeListener(listenerId: string): void;
|
||||
|
||||
function GM_getResourceText(name: string): string;
|
||||
|
||||
function GM_getResourceURL(name: string): string;
|
||||
function GM_getResourceURL(name: string, isBlobUrl: boolean): string;
|
||||
|
||||
function GM_addElement(
|
||||
tagName: string,
|
||||
attributes?: { [key: string]: string },
|
||||
): HTMLElement;
|
||||
function GM_addElement(
|
||||
parentNode: Node | Element | ShadowRoot,
|
||||
tagName: string,
|
||||
attributes?: { [key: string]: string },
|
||||
): HTMLElement;
|
||||
|
||||
function GM_addStyle(css: string): HTMLStyleElement;
|
||||
|
||||
function GM_openInTab(
|
||||
url: string,
|
||||
options?: openInNewTabOptionsType,
|
||||
): openInNewTabReturnType;
|
||||
function GM_openInTab(
|
||||
url: string,
|
||||
openInBackground?: boolean,
|
||||
): openInNewTabReturnType;
|
||||
|
||||
function GM_registerMenuCommand(
|
||||
caption: string,
|
||||
onClick: (e: MouseEvent | KeyboardEvent) => void,
|
||||
): void;
|
||||
|
||||
function GM_unregisterMenuCommand(caption: string): void;
|
||||
|
||||
function GM_notification(option: vm_optionType): vm_notificationReturnType;
|
||||
function GM_notification(
|
||||
text: string,
|
||||
title?: string,
|
||||
image?: string,
|
||||
onclick?: () => void,
|
||||
): vm_notificationReturnType;
|
||||
|
||||
function GM_setClipboard(data: string, type: string): void;
|
||||
|
||||
function GM_xmlhttpRequest(
|
||||
request: vm_xmlhttpRequestType,
|
||||
): Promise<vm_xmlhttpRequestReturnType>;
|
||||
|
||||
function GM_download(url: string, name?: string): void;
|
||||
function GM_download(options: vm_downloadOptionsType): void;
|
168
src/userscripts/manga-reading/manga-reading-overrides.user.css
Normal file
168
src/userscripts/manga-reading/manga-reading-overrides.user.css
Normal file
|
@ -0,0 +1,168 @@
|
|||
/* overrides */
|
||||
body {
|
||||
border-color: currentcolor;
|
||||
background: rgba(0, 0, 0, 0) url("/images/icons/black_thread.webp") !important;
|
||||
border-top: 0px !important;
|
||||
}
|
||||
|
||||
body > div,
|
||||
body > header,
|
||||
body > footer {
|
||||
isolation: isolate;
|
||||
}
|
||||
|
||||
body::before {
|
||||
inset: 0;
|
||||
content: "";
|
||||
position: fixed;
|
||||
z-index: 0; /* Ensure this is below the main content */
|
||||
pointer-events: none;
|
||||
backdrop-filter: contrast(1.2) brightness(0.75);
|
||||
}
|
||||
|
||||
body .top-logo img,
|
||||
body .logo_chapter img {
|
||||
filter: invert() hue-rotate(180deg) brightness(0.75);
|
||||
}
|
||||
|
||||
body > div::last-of-type {
|
||||
padding-bottom: 120px;
|
||||
}
|
||||
|
||||
/* overrides chapter */
|
||||
.container-chapter-reader {
|
||||
margin-bottom: 60px;
|
||||
}
|
||||
|
||||
.container-chapter-reader > img {
|
||||
min-width: 630px;
|
||||
}
|
||||
|
||||
.overrides-header-container,
|
||||
.overrides-footer-container {
|
||||
background-color: rgba(0, 0, 0, 0.5);
|
||||
padding-bottom: 60px;
|
||||
}
|
||||
|
||||
.logo_chapter {
|
||||
background: none;
|
||||
}
|
||||
|
||||
.overrides-header-container > div,
|
||||
.overrides-footer-container > div {
|
||||
background: none;
|
||||
}
|
||||
|
||||
/* overrides manga */
|
||||
body > div.body-site {
|
||||
padding-bottom: 120px;
|
||||
}
|
||||
|
||||
div#panel-story-info-description {
|
||||
max-height: none !important;
|
||||
}
|
||||
|
||||
div#panel-description-linear,
|
||||
span#panel-story-info-description-show-more,
|
||||
span#panel-story-info-description-show-less {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
/* overrides neither chapter nor manga */
|
||||
div.truyen-list > div.list-truyen-item-wrap > .genres-item-info {
|
||||
display: flex;
|
||||
flex-flow: column nowrap;
|
||||
gap: 4px;
|
||||
box-sizing: border-box;
|
||||
padding: 4px;
|
||||
/*background-color: rgb(24, 26, 27, 0.4);*/
|
||||
float: none;
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
||||
div.truyen-list > div.breadcrumb {
|
||||
grid-column: span 2;
|
||||
}
|
||||
|
||||
div.truyen-list {
|
||||
display: grid;
|
||||
grid-gap: 12px;
|
||||
grid-template-columns: calc(50% - 6px) calc(50% - 6px);
|
||||
}
|
||||
|
||||
div.truyen-list > div.list-truyen-item-wrap * {
|
||||
float: unset !important;
|
||||
}
|
||||
|
||||
div.truyen-list > div.list-truyen-item-wrap {
|
||||
backdrop-filter: blur(3px);
|
||||
margin: 0;
|
||||
width: auto;
|
||||
background-color: rgb(24, 26, 27, 0.2);
|
||||
background: none;
|
||||
transition: background-color linear 0.15s;
|
||||
padding: 4px;
|
||||
display: flex;
|
||||
gap: 4px;
|
||||
}
|
||||
|
||||
div.truyen-list > div.list-truyen-item-wrap.mr-genre-all-active-item {
|
||||
background-color: rgb(4, 126, 123, 0.3);
|
||||
transition: background-color linear 0s;
|
||||
}
|
||||
|
||||
div.truyen-list > div.list-truyen-item-wrap > .genres-item-img {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
div.truyen-list > div.list-truyen-item-wrap {
|
||||
display: grid;
|
||||
grid-template:
|
||||
"img title" auto
|
||||
"img link" auto
|
||||
"img description" 1fr / 123px 1fr;
|
||||
}
|
||||
|
||||
div.truyen-list > div.list-truyen-item-wrap > .list-story-item {
|
||||
grid-area: img;
|
||||
}
|
||||
div.truyen-list > div.list-truyen-item-wrap > h3 {
|
||||
grid-area: title;
|
||||
}
|
||||
div.truyen-list > div.list-truyen-item-wrap > a.list-story-item-wrap-chapter {
|
||||
grid-area: link;
|
||||
}
|
||||
div.truyen-list > div.list-truyen-item-wrap > p {
|
||||
grid-area: description;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
width: 100%;
|
||||
height: fit-content;
|
||||
}
|
||||
|
||||
div.truyen-list
|
||||
> div.list-truyen-item-wrap
|
||||
> .genres-item-info
|
||||
> .genres-item-chap,
|
||||
div.truyen-list
|
||||
> div.list-truyen-item-wrap
|
||||
> .genres-item-info
|
||||
> .genres-item-view-time,
|
||||
div.truyen-list > div.list-truyen-item-wrap > .genres-item-info > h3 {
|
||||
min-height: 22px;
|
||||
height: 22px;
|
||||
}
|
||||
|
||||
div.truyen-list
|
||||
> div.list-truyen-item-wrap
|
||||
> .genres-item-info
|
||||
> .genres-item-description {
|
||||
height: auto;
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
||||
div.truyen-list > div.list-truyen-item-wrap > div,
|
||||
div.truyen-list > div.list-truyen-item-wrap > br,
|
||||
div.truyen-list > div.list-truyen-item-wrap > a.read-more {
|
||||
display: none;
|
||||
}
|
6
src/userscripts/manga-reading/manga-reading.user.css
Normal file
6
src/userscripts/manga-reading/manga-reading.user.css
Normal file
|
@ -0,0 +1,6 @@
|
|||
@import "tailwindcss";
|
||||
@source "templates/manga-reading-config.html";
|
||||
@source "templates/manga-reading-notification-button.html";
|
||||
@source "templates/manga-reading-notification-container.html";
|
||||
@source "templates/manga-reading-notification.html";
|
||||
@source "templates/manga-reading-lib-frame.html";
|
1535
src/userscripts/manga-reading/manga-reading.user.ts
Normal file
1535
src/userscripts/manga-reading/manga-reading.user.ts
Normal file
File diff suppressed because one or more lines are too long
|
@ -0,0 +1,73 @@
|
|||
<div class="flex flex-col gap-2" data-mr-config="">
|
||||
<p class="border-b border-solid border-b-indigo-400/50 text-sm mt-2">
|
||||
Current Title:
|
||||
<span data-mr-title-current=""></span>
|
||||
</p>
|
||||
<div
|
||||
class="flex flex-col gap-0.5 border-b border-solid border-b-indigo-400/50 pb-1 text-sm"
|
||||
>
|
||||
<label for="ptApiUrl"> Progress Tracker Api Url: </label>
|
||||
<input
|
||||
type="text"
|
||||
class="border-none bg-transparent pb-2 font-mono text-xs outline-4 outline-transparent focus:outline-indigo-500/80"
|
||||
id="ptApiUrl"
|
||||
data-mr-pt-api-url=""
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
class="flex flex-col gap-0.5 border-b border-solid border-b-indigo-400/50 pb-1 text-sm"
|
||||
>
|
||||
<label for="ptApiBearerToken"> Progress Tracker Api BEARER TOKEN: </label>
|
||||
<input
|
||||
type="text"
|
||||
class="border-none bg-transparent pb-2 font-mono text-xs outline-4 outline-transparent focus:outline-indigo-500/80"
|
||||
id="ptApiBearerToken"
|
||||
data-mr-pt-api-bearer-token=""
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
class="flex flex-col gap-0.5 border-b border-solid border-b-indigo-400/50 pb-1 text-sm"
|
||||
>
|
||||
<label for="titleList"> Titles to remove gaps from: </label>
|
||||
<textarea
|
||||
class="min-h-24 resize-y border-none bg-transparent pb-2 font-mono text-xs outline-4 outline-transparent focus:outline-indigo-500/80"
|
||||
id="titleList"
|
||||
rows="12"
|
||||
data-mr-title-list=""
|
||||
></textarea>
|
||||
</div>
|
||||
<div
|
||||
class="flex justify-between border-b border-solid border-b-indigo-400/50 pb-1"
|
||||
>
|
||||
<div class="flex justify-end gap-2">
|
||||
<button
|
||||
class="cursor-pointer border-none bg-indigo-800 px-1 text-sm text-gray-200 outline outline-4 outline-transparent transition-all hover:bg-indigo-600 hover:outline-indigo-500/80 disabled:cursor-default disabled:bg-indigo-900/70"
|
||||
disabled=""
|
||||
data-mr-title-current-add=""
|
||||
>
|
||||
Add
|
||||
</button>
|
||||
<button
|
||||
class="cursor-pointer border-none bg-indigo-800 px-1 text-sm text-gray-200 outline outline-4 outline-transparent transition-all hover:bg-indigo-600 hover:outline-indigo-500/80 disabled:cursor-default disabled:bg-indigo-900/70"
|
||||
disabled=""
|
||||
data-mr-title-current-remove=""
|
||||
>
|
||||
Remove
|
||||
</button>
|
||||
</div>
|
||||
<div class="flex justify-end gap-2">
|
||||
<button
|
||||
class="cursor-pointer border-none bg-indigo-800 px-1 text-sm text-gray-200 outline outline-4 outline-transparent transition-all hover:bg-indigo-600 hover:outline-indigo-500/80 disabled:cursor-default disabled:bg-indigo-900/70"
|
||||
data-mr-title-list-reset=""
|
||||
>
|
||||
Reset
|
||||
</button>
|
||||
<button
|
||||
class="cursor-pointer border-none bg-indigo-800 px-1 text-sm text-gray-200 outline outline-4 outline-transparent transition-all hover:bg-indigo-600 hover:outline-indigo-500/80 disabled:cursor-default disabled:bg-indigo-900/70"
|
||||
data-mr-title-list-save=""
|
||||
>
|
||||
Save
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
|
@ -0,0 +1,38 @@
|
|||
<div
|
||||
class="mr-lib-frame fixed inset-0 isolate z-[999999] flex items-center justify-center p-4 font-sans"
|
||||
>
|
||||
<div
|
||||
class="absolute inset-0 -z-10 overscroll-none bg-black/5 backdrop-blur-sm"
|
||||
data-mr-backdrop=""
|
||||
></div>
|
||||
<div
|
||||
class="z-10 flex max-h-[90dvh] w-full max-w-prose flex-col bg-gray-950/90 p-2 text-gray-200 shadow-gray-950 backdrop-blur-sm"
|
||||
>
|
||||
<header class="border-b border-solid border-b-indigo-400/50">
|
||||
<h1 class="text-base">Manga Reading - Configuration UI</h1>
|
||||
</header>
|
||||
<main class="flex flex-col gap-2 overflow-y-auto">
|
||||
<div class="flex flex-col gap-2" data-mr-sub-config-target=""></div>
|
||||
</main>
|
||||
<footer class="flex items-end justify-between pt-1">
|
||||
<a
|
||||
class="text-xs text-indigo-400 hover:text-indigo-300"
|
||||
href="https://userscripts.skaarup.dev"
|
||||
target="_blank"
|
||||
referrerpolicy="no-referrer"
|
||||
>
|
||||
userscripts.skaarup.dev
|
||||
</a>
|
||||
<div class="mr-buttons-container nws">
|
||||
<div class="flex justify-end gap-2">
|
||||
<button
|
||||
class="cursor-pointer border-none bg-indigo-800 px-1 text-sm text-gray-200 outline outline-4 outline-transparent transition-all hover:bg-indigo-600 hover:outline-indigo-500/80 disabled:cursor-default disabled:bg-indigo-900/70"
|
||||
data-mr-close=""
|
||||
>
|
||||
Close
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</footer>
|
||||
</div>
|
||||
</div>
|
|
@ -0,0 +1,4 @@
|
|||
<button
|
||||
data-button=""
|
||||
class="flex h-6 shrink-0 cursor-pointer items-center rounded-sm border-none bg-indigo-700 px-2 text-sm text-gray-100 outline-none transition-[box-shadow] duration-400 hover:bg-indigo-500 focus-visible:shadow-sm disabled:cursor-not-allowed"
|
||||
></button>
|
|
@ -0,0 +1,5 @@
|
|||
<ol
|
||||
tabindex="-1"
|
||||
class="pointer-events-none fixed right-[var(--offset)] bottom-[var(--offset)] z-[var(--z-index)] m-0 box-border flex w-80 list-none flex-col gap-2 p-0 font-sans outline-none"
|
||||
data-mr-notification-container=""
|
||||
></ol>
|
|
@ -0,0 +1,12 @@
|
|||
<li
|
||||
aria-live="polite"
|
||||
aria-atomic="true"
|
||||
role="status"
|
||||
tabindex="0"
|
||||
class="pointer-events-auto box-border flex w-80 touch-none items-center gap-1.5 text-wrap rounded-sm border border-solid border-gray-100 bg-black p-4 shadow outline-none transition-[box-shadow] duration-400 focus-visible:shadow-sm"
|
||||
>
|
||||
<div data-content="" class="flex flex-col gap-0.5">
|
||||
<div data-title="" class="font-medium leading-normal text-gray-100"></div>
|
||||
<div data-description="" class="font-normal leading-snug text-gray-300"></div>
|
||||
</div>
|
||||
</li>
|
Loading…
Add table
Add a link
Reference in a new issue