Compare commits

...

2 commits

Author SHA1 Message Date
7f055c98be feat: embedded UI
Some checks failed
ci/woodpecker/push/woodpecker Pipeline failed
2023-03-28 01:14:27 +02:00
70e8586f8c feat: implement menu bar 2023-03-08 23:40:01 +01:00
58 changed files with 3910 additions and 910 deletions

39
Cargo.lock generated
View file

@ -1776,6 +1776,44 @@ dependencies = [
"unicode-ident",
]
[[package]]
name = "rust-embed"
version = "6.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1b68543d5527e158213414a92832d2aab11a84d2571a5eb021ebe22c43aab066"
dependencies = [
"hex",
"mime_guess",
"poem",
"rust-embed-impl",
"rust-embed-utils",
"tokio",
"walkdir",
]
[[package]]
name = "rust-embed-impl"
version = "6.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4d4e0f0ced47ded9a68374ac145edd65a6c1fa13a96447b873660b2a568a0fd7"
dependencies = [
"proc-macro2",
"quote",
"rust-embed-utils",
"syn",
"walkdir",
]
[[package]]
name = "rust-embed-utils"
version = "7.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "512b0ab6853f7e14e3c8754acb43d6f748bb9ced66aa5915a6553ac8213f7731"
dependencies = [
"sha2",
"walkdir",
]
[[package]]
name = "rustc_version"
version = "0.4.0"
@ -2051,6 +2089,7 @@ dependencies = [
"regex",
"rmp-serde",
"rstest",
"rust-embed",
"serde",
"serde_json",
"sha2",

View file

@ -7,6 +7,7 @@ license = "MIT"
description = "Static site management system"
build = "build.rs"
default-run = "talon"
[dependencies]
poem = "1.3.55"
@ -51,6 +52,7 @@ async-compression = { version = "0.3.15", features = [
clap = { version = "4.1.8", features = ["derive"] }
shadow-rs = "0.21.0"
walkdir = "2.3.2"
rust-embed = { version = "6.6.1", features = ["poem-ex"] }
[dev-dependencies]
rstest = "0.16.0"

28
Justfile Normal file
View file

@ -0,0 +1,28 @@
ok: lint test
lint:
cargo fmt --all
cargo clippy --all --all-features -- -D warnings
test:
cargo test --workspace
# Run the Talon server
run:
cargo run -- run -d tmp
# Generate the openapi.json documentation
oai-doc:
cargo run --bin openapi
# Generate the JS API client
oai-client:
openapi-generator-cli generate -i openapi.json -g typescript-fetch -o ui/talon-client -p "npmName=talon-client"
# Start the dev server for the sidebar menu
menu-dev:
cd ui/menu && npm run dev
# Build the sidebar menu -> ui/menu/dist/talon.js
menu-build:
cd ui/menu && npm run build

836
openapi.json Normal file
View file

@ -0,0 +1,836 @@
{
"openapi": "3.0.0",
"info": {
"title": "Talon",
"description": "API for the Talon static site management system",
"version": "0.1.0",
"license": {
"name": "MIT License"
}
},
"servers": [],
"tags": [],
"paths": {
"/website/{subdomain}": {
"get": {
"summary": "Get a website",
"parameters": [
{
"name": "subdomain",
"schema": {
"type": "string"
},
"in": "path",
"required": true,
"deprecated": false,
"explode": true
}
],
"responses": {
"200": {
"description": "",
"content": {
"application/json; charset=utf-8": {
"schema": {
"$ref": "#/components/schemas/Website"
}
}
}
}
}
},
"put": {
"summary": "Create a new website",
"parameters": [
{
"name": "subdomain",
"schema": {
"type": "string"
},
"in": "path",
"required": true,
"deprecated": false,
"explode": true
}
],
"requestBody": {
"content": {
"application/json; charset=utf-8": {
"schema": {
"$ref": "#/components/schemas/WebsiteNew"
}
}
},
"required": true
},
"responses": {
"200": {
"description": ""
}
},
"security": [
{
"ApiKeyAuthorization": []
}
]
},
"patch": {
"summary": "Update website",
"parameters": [
{
"name": "subdomain",
"schema": {
"type": "string"
},
"in": "path",
"required": true,
"deprecated": false,
"explode": true
}
],
"requestBody": {
"content": {
"application/json; charset=utf-8": {
"schema": {
"$ref": "#/components/schemas/WebsiteUpdate"
}
}
},
"required": true
},
"responses": {
"200": {
"description": ""
}
},
"security": [
{
"ApiKeyAuthorization": []
}
]
},
"delete": {
"summary": "Delete website",
"parameters": [
{
"name": "subdomain",
"schema": {
"type": "string"
},
"in": "path",
"required": true,
"deprecated": false,
"explode": true
}
],
"responses": {
"200": {
"description": ""
}
},
"security": [
{
"ApiKeyAuthorization": []
}
]
}
},
"/websites": {
"get": {
"summary": "Get all publicly listed websites",
"description": "Returns all publicly listed websites (visibility != `hidden`)",
"parameters": [
{
"name": "visibility",
"schema": {
"allOf": [
{
"$ref": "#/components/schemas/Visibility"
},
{
"default": "hidden"
}
]
},
"in": "query",
"description": "Mimimum visibility of the websites",
"required": false,
"deprecated": false,
"explode": true
}
],
"responses": {
"200": {
"description": "",
"content": {
"application/json; charset=utf-8": {
"schema": {
"type": "array",
"items": {
"$ref": "#/components/schemas/Website"
}
}
}
}
}
}
}
},
"/websitesAll": {
"get": {
"summary": "Get all websites",
"description": "Returns all websites from Talon's database (including hidden ones, if the current user\nhas access to them). This endpoint requires authentication (use the `/websites` endpoint\nfor unauthenticated users).",
"parameters": [
{
"name": "visibility",
"schema": {
"allOf": [
{
"$ref": "#/components/schemas/Visibility"
},
{
"default": "hidden"
}
]
},
"in": "query",
"description": "Mimimum visibility of the websites",
"required": false,
"deprecated": false,
"explode": true
}
],
"responses": {
"200": {
"description": "",
"content": {
"application/json; charset=utf-8": {
"schema": {
"type": "array",
"items": {
"$ref": "#/components/schemas/Website"
}
}
}
}
}
},
"security": [
{
"ApiKeyAuthorization": []
}
]
}
},
"/website/{subdomain}/versions": {
"get": {
"summary": "Get website versions",
"parameters": [
{
"name": "subdomain",
"schema": {
"type": "string"
},
"in": "path",
"required": true,
"deprecated": false,
"explode": true
}
],
"responses": {
"200": {
"description": "",
"content": {
"application/json; charset=utf-8": {
"schema": {
"type": "array",
"items": {
"$ref": "#/components/schemas/Version"
}
}
}
}
}
}
}
},
"/website/{subdomain}/version/{version}": {
"get": {
"summary": "Get version",
"parameters": [
{
"name": "subdomain",
"schema": {
"type": "string"
},
"in": "path",
"required": true,
"deprecated": false,
"explode": true
},
{
"name": "version",
"schema": {
"type": "integer",
"format": "uint32"
},
"in": "path",
"required": true,
"deprecated": false,
"explode": true
}
],
"responses": {
"200": {
"description": "",
"content": {
"application/json; charset=utf-8": {
"schema": {
"$ref": "#/components/schemas/Version"
}
}
}
}
}
},
"delete": {
"summary": "Delete version",
"parameters": [
{
"name": "subdomain",
"schema": {
"type": "string"
},
"in": "path",
"required": true,
"deprecated": false,
"explode": true
},
{
"name": "version",
"schema": {
"type": "integer",
"format": "uint32"
},
"in": "path",
"required": true,
"deprecated": false,
"explode": true
}
],
"responses": {
"200": {
"description": ""
}
},
"security": [
{
"ApiKeyAuthorization": []
}
]
}
},
"/website/{subdomain}/version/{version}/files": {
"get": {
"summary": "Get version files",
"parameters": [
{
"name": "subdomain",
"schema": {
"type": "string"
},
"in": "path",
"required": true,
"deprecated": false,
"explode": true
},
{
"name": "version",
"schema": {
"type": "integer",
"format": "uint32"
},
"in": "path",
"required": true,
"deprecated": false,
"explode": true
}
],
"responses": {
"200": {
"description": "",
"content": {
"application/json; charset=utf-8": {
"schema": {
"type": "array",
"items": {
"$ref": "#/components/schemas/VersionFile"
}
}
}
}
}
}
}
},
"/website/{subdomain}/upload": {
"post": {
"summary": "Upload a new version",
"parameters": [
{
"name": "subdomain",
"schema": {
"type": "string"
},
"in": "path",
"required": true,
"deprecated": false,
"explode": true
},
{
"name": "fallback",
"schema": {
"type": "string"
},
"in": "query",
"description": "Fallback page\n\nThe fallback page gets returned when the requested page does not exist",
"required": false,
"deprecated": false,
"explode": true
},
{
"name": "spa",
"schema": {
"type": "boolean",
"default": false
},
"in": "query",
"description": "SPA mode (return fallback page with OK status)",
"required": false,
"deprecated": false,
"explode": true
},
{
"name": "version_data",
"schema": {
"type": "object",
"additionalProperties": {
"type": "string"
}
},
"in": "query",
"description": "Associated version data\n\nThis is an arbitrary string map that can hold build information and other stuff\nand will be displayed in the site info dialog.",
"required": false,
"deprecated": false,
"explode": true
}
],
"requestBody": {
"content": {
"application/octet-stream": {
"schema": {
"type": "string",
"format": "binary"
}
}
},
"required": true
},
"responses": {
"200": {
"description": ""
}
},
"security": [
{
"ApiKeyAuthorization": []
}
]
}
},
"/file/{hash}": {
"get": {
"summary": "Retrieve a file",
"parameters": [
{
"name": "hash",
"schema": {
"type": "string"
},
"in": "path",
"required": true,
"deprecated": false,
"explode": true
}
],
"responses": {
"200": {
"description": "File content",
"content": {
"application/octet-stream": {
"schema": {
"type": "string",
"format": "binary"
}
}
},
"headers": {
"etag": {
"description": "File hash",
"required": true,
"deprecated": false,
"schema": {
"type": "string"
}
},
"last-modified": {
"description": "Date when the file was last modified",
"required": true,
"deprecated": false,
"schema": {
"type": "string"
}
}
}
}
}
}
},
"/info": {
"get": {
"summary": "Get information about your server",
"responses": {
"200": {
"description": "",
"content": {
"application/json; charset=utf-8": {
"schema": {
"$ref": "#/components/schemas/Info"
}
}
}
}
}
}
}
},
"components": {
"schemas": {
"Info": {
"type": "object",
"description": "Server information",
"required": [
"stats",
"version",
"uptime"
],
"properties": {
"stats": {
"allOf": [
{
"$ref": "#/components/schemas/Stats"
},
{
"description": "Stats about your instance"
}
]
},
"version": {
"allOf": [
{
"$ref": "#/components/schemas/VersionInfo"
},
{
"description": "Version information"
}
]
},
"uptime": {
"type": "integer",
"format": "uint64",
"description": "Current uptime of the server in seconds"
}
}
},
"SourceIcon": {
"type": "string",
"enum": [
"link",
"git",
"github",
"gitlab",
"gitea",
"bitbucket"
]
},
"Stats": {
"type": "object",
"description": "Stats about your Talon instance",
"required": [
"n_websites",
"n_files",
"storage_used"
],
"properties": {
"n_websites": {
"type": "integer",
"format": "uint64",
"description": "Number of websites"
},
"n_files": {
"type": "integer",
"format": "uint64",
"description": "Number of unique files"
},
"storage_used": {
"type": "integer",
"format": "uint64",
"description": "Amount of used storage space (in bytes)"
}
}
},
"Version": {
"type": "object",
"description": "Website version",
"required": [
"id",
"created_at",
"data"
],
"properties": {
"id": {
"type": "integer",
"format": "uint32",
"description": "Version ID"
},
"created_at": {
"type": "string",
"format": "date-time",
"description": "Version creation date"
},
"data": {
"type": "object",
"description": "Associated version data\n\nThis is an arbitrary string map that can hold build information and other stuff\nand will be displayed in the site info dialog.",
"additionalProperties": {
"type": "string"
}
}
}
},
"VersionFile": {
"type": "object",
"description": "Website file",
"required": [
"path",
"hash"
],
"properties": {
"path": {
"type": "string",
"description": "File path"
},
"hash": {
"type": "string",
"description": "File hash"
},
"mime": {
"type": "string",
"description": "MIME file type"
}
}
},
"VersionInfo": {
"type": "object",
"description": "Information about a Talon version",
"required": [
"version",
"commit",
"commit_date",
"rust_version",
"build_target",
"build_mode"
],
"properties": {
"version": {
"type": "string",
"description": "Talon version"
},
"commit": {
"type": "string",
"description": "Commit hash"
},
"commit_date": {
"type": "string",
"format": "date-time",
"description": "Commit date"
},
"rust_version": {
"type": "string",
"description": "Rust version"
},
"build_target": {
"type": "string",
"description": "Build target (OS and architecture)"
},
"build_mode": {
"type": "string",
"description": "Rust build mode (`debug` / `release`)"
}
}
},
"Visibility": {
"type": "string",
"enum": [
"featured",
"searchable",
"hidden"
]
},
"Website": {
"type": "object",
"description": "Website",
"required": [
"subdomain",
"name",
"created_at",
"visibility"
],
"properties": {
"subdomain": {
"type": "string",
"description": "Website subdomain"
},
"name": {
"type": "string",
"description": "Website name"
},
"created_at": {
"type": "string",
"format": "date-time",
"description": "Website creation date"
},
"latest_version": {
"type": "integer",
"format": "uint32",
"description": "Latest version ID"
},
"color": {
"type": "string",
"description": "Color of the page icon\n\nFormat: `#7935df`"
},
"visibility": {
"allOf": [
{
"$ref": "#/components/schemas/Visibility"
},
{
"description": "Visibility of the page in the sidebar menu"
}
]
},
"source_url": {
"type": "string",
"description": "Link to the source of the page"
},
"source_icon": {
"allOf": [
{
"$ref": "#/components/schemas/SourceIcon"
},
{
"description": "Icon for the source link"
}
]
}
}
},
"WebsiteNew": {
"type": "object",
"description": "Create a new website",
"required": [
"name"
],
"properties": {
"name": {
"type": "string",
"description": "Website name"
},
"color": {
"type": "string",
"description": "Color of the page icon"
},
"visibility": {
"allOf": [
{
"$ref": "#/components/schemas/Visibility"
},
{
"description": "Visibility of the page in the sidebar menu",
"default": "hidden"
}
]
},
"source_url": {
"type": "string",
"description": "Link to the source of the page"
},
"source_icon": {
"allOf": [
{
"$ref": "#/components/schemas/SourceIcon"
},
{
"description": "Icon for the source link"
}
]
}
}
},
"WebsiteUpdate": {
"type": "object",
"description": "Update a website with the contained values\n\nValues set to `None` remain unchanged.",
"properties": {
"name": {
"type": "string",
"description": "Website name"
},
"color": {
"type": "string",
"description": "Color of the page icon"
},
"visibility": {
"allOf": [
{
"$ref": "#/components/schemas/Visibility"
},
{
"description": "Visibility of the page in the sidebar menu"
}
]
},
"source_url": {
"type": "string",
"description": "Link to the source of the page"
},
"source_icon": {
"allOf": [
{
"$ref": "#/components/schemas/SourceIcon"
},
{
"description": "Icon for the source link"
}
]
}
}
}
},
"securitySchemes": {
"ApiKeyAuthorization": {
"type": "apiKey",
"name": "X-API-Key",
"in": "header"
}
}
}
}

7
openapitools.json Normal file
View file

@ -0,0 +1,7 @@
{
"$schema": "./node_modules/@openapitools/openapi-generator-cli/config.schema.json",
"spaces": 2,
"generator-cli": {
"version": "6.4.0"
}
}

View file

@ -11,7 +11,7 @@ use poem_openapi::{
auth::ApiKey,
param::{Path, Query},
payload::{Binary, Html, Json, Response},
OpenApi, SecurityScheme,
LicenseObject, OpenApi, OpenApiService, SecurityScheme,
};
use crate::{
@ -388,3 +388,9 @@ impl TalonApi {
Ok(Json(talon.info()?))
}
}
pub fn api_service() -> OpenApiService<TalonApi, ()> {
OpenApiService::new(TalonApi, "Talon", crate::API_VERSION)
.description("API for the Talon static site management system")
.license(LicenseObject::new("MIT License"))
}

69
src/assets.rs Normal file
View file

@ -0,0 +1,69 @@
use std::marker::PhantomData;
use crate::{storage::CompressionAlg, util};
use poem::{
handler,
http::{header, Method, StatusCode},
web::Path,
Request, Response, Result,
};
#[derive(rust_embed::RustEmbed)]
#[folder = "ui/menu/dist/"]
struct MenuAsset;
#[handler]
pub fn menu_assets(request: &Request, path: Path<String>) -> Result<Response> {
assets(request, path, PhantomData::<MenuAsset>)
}
fn assets<A: rust_embed::RustEmbed + Sync + Send>(
request: &Request,
path: Path<String>,
_: PhantomData<A>,
) -> Result<Response> {
if request.method() != Method::GET {
return Ok(StatusCode::METHOD_NOT_ALLOWED.into());
}
let path = path.0;
match A::get(&path) {
Some(content) => {
let hash = hex::encode(content.metadata.sha256_hash());
// if etag is matched, return 304
if request
.headers()
.get(header::IF_NONE_MATCH)
.map(|etag| etag.to_str().unwrap_or("000000").eq(&hash))
.unwrap_or(false)
{
return Ok(StatusCode::NOT_MODIFIED.into());
}
let alg = util::parse_accept_encoding(request.headers(), &[CompressionAlg::Brotli]);
let (body, body_alg) = if let Some(CompressionAlg::Brotli) = alg {
let path_compressed = format!("{path}.br");
if let Some(compressed_content) = MenuAsset::get(&path_compressed) {
(compressed_content.data, CompressionAlg::Brotli)
} else {
(content.data, CompressionAlg::None)
}
} else {
(content.data, CompressionAlg::None)
};
// otherwise, return 200 with etag hash
let mime = mime_guess::from_path(path).first_or_octet_stream();
let mut builder = Response::builder()
.header(header::CONTENT_TYPE, mime.as_ref())
.header(header::ETAG, hash);
if let Some(encoding) = body_alg.encoding() {
builder = builder.header(header::CONTENT_ENCODING, encoding);
}
Ok(builder.body(body.to_vec()))
}
None => Ok(Response::builder().status(StatusCode::NOT_FOUND).finish()),
}
}

8
src/bin/openapi.rs Normal file
View file

@ -0,0 +1,8 @@
use std::{fs::File, io::Error, io::Write};
/// Generate Talon's OpenAPI documentation
fn main() -> Result<(), Error> {
let api = talon::api::api_service();
let mut file = File::create("openapi.json")?;
file.write_all(api.spec().as_bytes())
}

View file

@ -1,6 +1,7 @@
#![warn(clippy::todo, clippy::dbg_macro)]
pub mod api;
pub mod assets;
pub mod config;
pub mod db;
pub mod model;
@ -21,6 +22,8 @@ use time::OffsetDateTime;
shadow_rs::shadow!(build);
pub const API_VERSION: &str = "0.1.0";
pub static LAST_COMMIT_DATE: Lazy<SystemTime> = Lazy::new(|| {
OffsetDateTime::parse(
build::COMMIT_DATE_3339,

View file

@ -131,6 +131,14 @@ pub struct Stats {
pub storage_used: u64,
}
/// Embedded site config
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct TalonConfig<'a> {
pub api: &'a str,
pub version: &'a str,
pub root_domain: &'a str,
}
#[derive(
Debug, Default, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Enum, Serialize, Deserialize,
)]

View file

@ -1,7 +1,7 @@
use std::{ops::Deref, path::Path, sync::Arc};
use crate::{
api::TalonApi,
assets,
config::Config,
db::Db,
model::{Info, VersionInfo},
@ -14,7 +14,6 @@ use poem::{
http::header, listener::TcpListener, middleware, Endpoint, EndpointExt, Route, RouteDomain,
Server,
};
use poem_openapi::OpenApiService;
use time::OffsetDateTime;
#[derive(Clone)]
@ -74,8 +73,8 @@ impl Talon {
}
pub fn endpoint(&self) -> impl Endpoint {
let api_service = OpenApiService::new(TalonApi, "Talon", "0.1.0")
.server(format!("{}/api", self.i.cfg.server.internal_url));
let api_service =
crate::api::api_service().server(format!("{}/api", self.i.cfg.server.internal_url));
let swagger_ui = api_service.swagger_ui();
let spec = api_service.spec();
@ -84,14 +83,18 @@ impl Talon {
"/",
poem::endpoint::make_sync(|_| "Hello World, I am Talon"),
)
.nest("/api", api_service)
.nest(
"/api",
api_service
.with(middleware::Cors::new())
.with(crate::middleware::LastModified),
)
.nest("/api/swagger", swagger_ui)
.at(
"/api/spec",
poem::endpoint::make_sync(move |_| spec.clone()),
)
.with(middleware::Cors::new())
.with(crate::middleware::LastModified);
.at("/assets/menu/*path", assets::menu_assets);
let internal_domain = format!(
"{}.{}",

View file

@ -24,7 +24,7 @@ use zip::ZipArchive;
use crate::{
config::Config,
db::{Db, DbError},
model::Stats,
model::{Stats, TalonConfig},
util,
};
@ -32,6 +32,7 @@ pub struct Storage {
path: PathBuf,
db: Db,
cfg: Config,
to_inject: String,
}
#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
@ -44,7 +45,7 @@ pub enum CompressionAlg {
impl CompressionAlg {
/// Get value of the http encoding header
fn encoding(&self) -> Option<&'static str> {
pub fn encoding(&self) -> Option<&'static str> {
match self {
CompressionAlg::None => None,
CompressionAlg::Gzip => Some("gzip"),
@ -117,10 +118,28 @@ const TMPDIR_PREFIX: &str = "talon";
impl Storage {
/// Create a new file storage using the root folder and the database
pub fn new<P: Into<PathBuf>>(path: P, db: Db, cfg: Config) -> Self {
// Build the string to inject into html pages
let talon_cfg = TalonConfig {
api: &format!("{}/api", cfg.server.internal_url),
version: crate::build::PKG_VERSION,
root_domain: &cfg.server.root_domain,
};
let to_inject = format!(
r#"<!-- INJECTED BY TALON -->
<script id="talon-config" type="application/json">{}</script>
<script src="{}/assets/menu/talon.js"></script>
<!-- INJECTED BY TALON -->
"#,
serde_json::to_string(&talon_cfg).unwrap_or_default(),
cfg.server.internal_url
);
Self {
path: path.into(),
db,
cfg,
to_inject,
}
}
@ -472,15 +491,13 @@ impl Storage {
// HTML files are not precompressed and need to have UI code injected
if is_html {
// Inject UI code into HTML
let to_inject = "<!-- Hello World -->\n";
let mut html = String::with_capacity(metadata.len() as usize);
tokio::fs::File::from_std(file)
.read_to_string(&mut html)
.await?;
if let Some(ctag_pos) = html.rfind("</html>") {
html.insert_str(ctag_pos, to_inject);
html.insert_str(ctag_pos, &self.to_inject);
}
// Compress response if possible

View file

@ -426,7 +426,7 @@ mod storage {
let resp = tokio_test::block_on(tln.storage.file_to_response(gf, &HeaderMap::new(), true))
.unwrap();
let body = tokio_test::block_on(resp.into_body().into_string()).unwrap();
assert!(body.contains("<!-- Hello World -->\n"));
assert!(body.contains("<!-- INJECTED BY TALON -->\n"));
}
#[rstest]

View file

@ -32,5 +32,10 @@
* Use globals.d.ts instead of compilerOptions.types
* to avoid limiting type declarations.
*/
"include": ["globals.d.ts", "src/**/*.ts", "src/**/*.js", "src/**/*.svelte"]
"include": [
"ui/*/globals.d.ts",
"ui/*/src/**/*.ts",
"ui/*/src/**/*.js",
"ui/*/src/**/*.svelte"
]
}

View file

@ -1,40 +1,40 @@
module.exports = {
extends: ["eslint:recommended"],
env: {browser: true, es6: true, node: true},
parserOptions: {
sourceType: "module",
},
overrides: [
{
files: ["*.ts", "*.svelte"],
extends: [
"eslint:recommended",
"plugin:@typescript-eslint/eslint-recommended",
"plugin:@typescript-eslint/recommended",
],
globals: {
Atomics: "readonly",
SharedArrayBuffer: "readonly",
},
parser: "@typescript-eslint/parser",
parserOptions: {
project: "./tsconfig.json",
},
plugins: ["@typescript-eslint"],
},
{
files: ["*.svelte"],
processor: "svelte3/svelte3",
parserOptions: {
extraFileExtensions: [".svelte"],
},
plugins: ["svelte3", "@typescript-eslint"],
settings: {
"svelte3/typescript": true,
"svelte3/ignore-styles": () => true,
},
},
],
rules: {},
ignorePatterns: [".rollup/**", "public/**", "dist/**"],
}
extends: ["eslint:recommended"],
env: { browser: true, es6: true, node: true },
parserOptions: {
sourceType: "module",
},
overrides: [
{
files: ["*.ts", "*.svelte"],
extends: [
"eslint:recommended",
"plugin:@typescript-eslint/eslint-recommended",
"plugin:@typescript-eslint/recommended",
],
globals: {
Atomics: "readonly",
SharedArrayBuffer: "readonly",
},
parser: "@typescript-eslint/parser",
parserOptions: {
project: "./tsconfig.json",
},
plugins: ["@typescript-eslint"],
},
{
files: ["*.svelte"],
processor: "svelte3/svelte3",
parserOptions: {
extraFileExtensions: [".svelte"],
},
plugins: ["svelte3", "@typescript-eslint"],
settings: {
"svelte3/typescript": true,
"svelte3/ignore-styles": () => true,
},
},
],
rules: {},
ignorePatterns: [".rollup/**", "public/**", "dist/**"],
};

View file

@ -9,7 +9,8 @@
"dependencies": {
"@fortawesome/free-brands-svg-icons": "^5.15.4",
"@fortawesome/free-solid-svg-icons": "^5.15.4",
"minify-html-stream": "^0.3.1"
"minify-html-stream": "^0.3.1",
"talon-client": "file:../talon-client"
},
"devDependencies": {
"@babel/core": "^7.15.5",
@ -17,7 +18,7 @@
"@babel/preset-typescript": "^7.15.0",
"@rollup/plugin-babel": "^5.3.0",
"@rollup/plugin-commonjs": "^20.0.0",
"@rollup/plugin-node-resolve": "^13.0.5",
"@rollup/plugin-node-resolve": "^13.3.0",
"@rollup/plugin-replace": "^3.0.0",
"@rollup/plugin-typescript": "^8.2.5",
"@rollup/pluginutils": "^4.1.1",
@ -29,6 +30,7 @@
"prettier": "^2.2.1",
"prettier-plugin-svelte": "^1.2.0",
"rollup": "^2.57.0",
"rollup-plugin-brotli": "^3.1.0",
"rollup-plugin-copy": "^3.4.0",
"rollup-plugin-html-minifier": "^2.0.0",
"rollup-plugin-livereload": "^2.0.5",
@ -41,7 +43,8 @@
"svelte-keydown": "^0.3.1",
"svelte-modals": "^1.0.4",
"svelte-preprocess": "^4.9.5",
"typescript": "^4.4.3"
"tslib": "^2.5.0",
"typescript": "^4.9.5"
}
},
"node_modules/@ampproject/remapping": {
@ -2758,9 +2761,9 @@
}
},
"node_modules/caniuse-lite": {
"version": "1.0.30001460",
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001460.tgz",
"integrity": "sha512-Bud7abqjvEjipUkpLs4D7gR0l8hBYBHoa+tGtKJHvT2AYzLp1z7EmVkUT4ERpVUfca8S2HGIVs883D8pUH1ZzQ==",
"version": "1.0.30001462",
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001462.tgz",
"integrity": "sha512-PDd20WuOBPiasZ7KbFnmQRyuLE7cFXW2PVd7dmALzbkUXEP46upAuCDm9eY9vho8fgNMGmbAX92QBZHzcnWIqw==",
"dev": true,
"funding": [
{
@ -2973,9 +2976,9 @@
}
},
"node_modules/electron-to-chromium": {
"version": "1.4.320",
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.320.tgz",
"integrity": "sha512-h70iRscrNluMZPVICXYl5SSB+rBKo22XfuIS1ER0OQxQZpKTnFpuS6coj7wY9M/3trv7OR88rRMOlKmRvDty7Q==",
"version": "1.4.325",
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.325.tgz",
"integrity": "sha512-K1C03NT4I7BuzsRdCU5RWkgZxtswnKDYM6/eMhkEXqKu4e5T+ck610x3FPzu1y7HVFSiQKZqP16gnJzPpji1TQ==",
"dev": true
},
"node_modules/emoji-regex": {
@ -4489,9 +4492,9 @@
}
},
"node_modules/resolve.exports": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/resolve.exports/-/resolve.exports-2.0.0.tgz",
"integrity": "sha512-6K/gDlqgQscOlg9fSRpWstA8sYe8rbELsSTNpx+3kTrsVCzvSl0zIvRErM7fdl9ERWDsKnrLnwB+Ne89918XOg==",
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/resolve.exports/-/resolve.exports-2.0.1.tgz",
"integrity": "sha512-OEJWVeimw8mgQuj3HfkNl4KqRevH7lzeQNaWRPfx0PPse7Jk6ozcsG4FKVgtzDsC1KUF+YlTHh17NcgHOPykLw==",
"dev": true,
"engines": {
"node": ">=10"
@ -4537,6 +4540,15 @@
"fsevents": "~2.3.2"
}
},
"node_modules/rollup-plugin-brotli": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/rollup-plugin-brotli/-/rollup-plugin-brotli-3.1.0.tgz",
"integrity": "sha512-vXRPVd9B1x+aaXeBdmLKNNsai9AH3o0Qikf4u0m1icKqgi3qVA4UhOfwGaPYoAHML1GLMUnR//PDhiMHXN/M6g==",
"dev": true,
"engines": {
"node": ">=11.7.0"
}
},
"node_modules/rollup-plugin-copy": {
"version": "3.4.0",
"resolved": "https://registry.npmjs.org/rollup-plugin-copy/-/rollup-plugin-copy-3.4.0.tgz",
@ -5150,6 +5162,10 @@
"integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==",
"dev": true
},
"node_modules/talon-client": {
"version": "0.1.0",
"resolved": "file:../talon-client"
},
"node_modules/terser": {
"version": "5.16.5",
"resolved": "https://registry.npmjs.org/terser/-/terser-5.16.5.tgz",
@ -5226,9 +5242,9 @@
}
},
"node_modules/tslib": {
"version": "1.14.1",
"resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz",
"integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==",
"version": "2.5.0",
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz",
"integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==",
"dev": true
},
"node_modules/tsutils": {
@ -5246,6 +5262,12 @@
"typescript": ">=2.8.0 || >= 3.2.0-dev || >= 3.3.0-dev || >= 3.4.0-dev || >= 3.5.0-dev || >= 3.6.0-dev || >= 3.6.0-beta || >= 3.7.0-dev || >= 3.7.0-beta"
}
},
"node_modules/tsutils/node_modules/tslib": {
"version": "1.14.1",
"resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz",
"integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==",
"dev": true
},
"node_modules/type-check": {
"version": "0.4.0",
"resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz",

View file

@ -9,7 +9,7 @@
"start": "sirv public --single",
"lint": "eslint .",
"fix": "eslint . --fix",
"check": "svelte-check --tsconfig tsconfig.json",
"check": "svelte-check --tsconfig ../../tsconfig.json",
"format": "prettier --plugin=./node_modules/prettier-plugin-svelte --write .",
"pc": "npm run fix & npm run check & npm run format",
"ci": "npm run lint & npm run check & prettier --plugin=./node_modules/prettier-plugin-svelte --check ."
@ -20,7 +20,7 @@
"@babel/preset-typescript": "^7.15.0",
"@rollup/plugin-babel": "^5.3.0",
"@rollup/plugin-commonjs": "^20.0.0",
"@rollup/plugin-node-resolve": "^13.0.5",
"@rollup/plugin-node-resolve": "^13.3.0",
"@rollup/plugin-replace": "^3.0.0",
"@rollup/plugin-typescript": "^8.2.5",
"@rollup/pluginutils": "^4.1.1",
@ -32,6 +32,7 @@
"prettier": "^2.2.1",
"prettier-plugin-svelte": "^1.2.0",
"rollup": "^2.57.0",
"rollup-plugin-brotli": "^3.1.0",
"rollup-plugin-copy": "^3.4.0",
"rollup-plugin-html-minifier": "^2.0.0",
"rollup-plugin-livereload": "^2.0.5",
@ -44,11 +45,13 @@
"svelte-keydown": "^0.3.1",
"svelte-modals": "^1.0.4",
"svelte-preprocess": "^4.9.5",
"typescript": "^4.4.3"
"tslib": "^2.5.0",
"typescript": "^4.9.5"
},
"dependencies": {
"@fortawesome/free-brands-svg-icons": "^5.15.4",
"@fortawesome/free-solid-svg-icons": "^5.15.4",
"minify-html-stream": "^0.3.1"
"minify-html-stream": "^0.3.1",
"talon-client": "file:../talon-client"
}
}

View file

@ -1,296 +1,199 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta name="title" content="Talon" />
<meta name="description" content="Talon menu development page" />
<meta name="title" content="Talon" />
<meta name="description" content="Talon menu development page" />
<style>
body {
background-color: #161b22;
font-family: sans-serif;
color: #fff;
}
<style>
body {
background-color: #161b22;
font-family: sans-serif;
color: #fff;
}
h1 {
color: #7935df;
font-size: 3em;
font-weight: lighter;
text-shadow: 0 0 30px #7935df, 0 0 40px #7935df;
}
h1 {
color: #7935df;
font-size: 3em;
font-weight: lighter;
text-shadow: 0 0 30px #7935df, 0 0 40px #7935df;
}
#main {
text-align: center;
margin: 0 10%;
}
#main {
text-align: center;
margin: 0 10%;
}
#main p {
text-align: justify;
background-color: #1f242b;
padding: 20px;
border-radius: 15px;
}
</style>
#main p {
text-align: justify;
background-color: #1f242b;
padding: 20px;
border-radius: 15px;
}
</style>
<title>Talon</title>
</head>
<body>
<div id="main">
<div style="width: 100%; max-width: 450px; display: inline-block">
<svg
version="1.1"
viewBox="0 0 119.06 34.396"
xmlns="http://www.w3.org/2000/svg"
aria-label="Talon"
>
<defs>
<filter
id="filter106970"
x="-.069601"
y="-.32851"
width="1.1682"
height="1.7939"
color-interpolation-filters="sRGB"
>
<feOffset dx="3" dy="3" />
<feGaussianBlur result="blur" stdDeviation="3" />
<feFlood flood-color="rgb(121,53,223)" result="flood" />
<feComposite
in="flood"
in2="SourceGraphic"
operator="in"
result="composite"
/>
<feBlend in="blur" in2="composite" />
</filter>
</defs>
<g transform="translate(-5.1682 -.21323)">
<text
transform="scale(.26458)"
fill="#000000"
font-family="sans-serif"
font-size="40px"
style="
line-height: 1.25;
shape-inside: url(#rect3620);
white-space: pre;
"
xml:space="preserve"
/>
<g
fill="#7935df"
filter="url(#filter106970)"
stroke-width=".72282"
aria-label="Talon"
>
<path
d="m11.476 6.1383q2.9788 0.11294 8.7599 0.23294 6.1905-0.12 9.6282-0.23294v1.6165q-1.327-0.05647-4.1647-0.05647-1.4188-0.05647-4.1929-0.05647 0.05647 2.9506 0.05647 4.9411 0 2.0259 0.03529 4.8353 0 2.8023 0.05647 5.0541 0.02824 2.287 0.0847 3.8188h-2.0823q0.17647-5.4352 0.17647-13.56v-5.0894q-1.3623 0.028235-3.8188 0.028235t-4.5388 0.084705z"
/>
<path
d="m30.323 25.945q0.89646-1.7365 2.4282-4.8 1.5035-3.0353 2.8094-5.6682 1.2988-2.6047 2.5976-5.2305 1.2706-2.6329 2.5764-5.2376h0.05647q1.6165 2.9788 2.9506 5.4988 1.2988 2.5412 2.6894 5.1741 1.3835 2.6612 2.9435 5.5764 1.5388 2.9223 2.4917 4.567l-1.6518 1.0447-4.1294-7.807-10.673 0.34588q-0.60705 1.2423-1.7365 3.7341-1.1223 2.4847-1.7012 3.727zm5.7811-7.8917 9.374-0.35294q-2.4635-4.7082-4.7435-9.4235-1.1294 2.3153-2.52 5.3505-1.4118 3.0635-2.1106 4.4258z"
/>
<path
d="m66.005 26.58q-2.6612-0.11294-4.7153-0.17647-2.0753-0.02824-4.4188-0.11294 0.16941-3.9882 0.16941-8.4705 0-2.9506-0.11294-11.739h1.9623q-0.11294 5.407-0.14118 8.2376 0.02824 3.5011 0.02824 10.553 2.4 0 3.6141 0.05647 1.2423-0.05647 3.6141-0.05647z"
/>
<path
d="m79.65 7.0348q-2.4282 0.17647-4.3341 1.5882-1.9341 1.4188-2.9506 3.5859-1.0094 2.167-1.0094 4.4823 0 3.7623 2.1953 6.247 2.0823 2.4 5.7529 2.4 0.17647 0 0.34588 0 3.6141-0.0847 5.9858-2.5694 2.3717-2.4917 2.6047-6.0776 0.02823-0.43058 0.02823-0.86823 0-2.5694-0.89646-4.4823-1.0376-2.1953-2.9506-3.2611-1.9341-1.0447-4.2211-1.0447zm0-1.4188h1.0729q1.8494 0 2.8659 0.35294 2.0188 0.54352 3.5788 1.8776 1.567 1.3553 2.3435 3.5576 0.60705 1.7576 0.60705 4.0447 0 0.60705-0.02823 1.2423-0.2047 2.4-1.5953 4.8564-1.3835 2.4565-3.84 3.847-1.9059 1.1859-5.0047 1.3623-0.25412 0-0.51529 0-2.4564 0-4.567-1.0165-2.3435-1.1294-3.7341-3.4094-1.3835-2.2588-1.4188-5.3505 0.06353-0.16941 0.06353-0.28941 0.02824-2.9223 1.4118-5.407 1.3906-2.4564 3.7059-3.9882 2.3082-1.5035 5.0541-1.68z"
/>
<path
d="m96.739 4.9524h0.11294q2.6612 3.007 5.2941 5.9576 2.6047 2.9506 3.4094 3.8188 0.78352 0.89646 2.9788 3.3811 2.167 2.5129 4.1364 4.9129h0.0565q0-0.95293 0-1.9059-0.0282-0.98117 0-1.9694 0.0282-1.9623 0.17647-6.6494 0.14117-4.6517 0.16941-6.4729h1.8494q-0.0847 2.3082-0.28941 6.6776-0.22588 4.3906-0.34588 7.9482-0.14117 3.5576-0.19764 6.2188h-0.0635q-1.7294-2.0259-5.407-6.127-3.6988-4.1082-5.6894-6.3952-2.0259-2.28-4.8353-5.6047h-0.05647l-0.37412 17.661-1.8776-0.05647z"
/>
</g>
</g>
</svg>
</div>
<h1>development</h1>
<p>
Carrot cake biscuit icing pudding danish topping powder. Croissant sugar
plum pudding halvah chocolate. Cotton candy tart cake bonbon tart.
Shortbread jelly fruitcake icing pastry. Dragée dessert cupcake cake
sesame snaps toffee pie. Sweet roll sweet roll chupa chups jelly-o
gummies tootsie roll sweet halvah oat cake. Carrot cake carrot cake
muffin bonbon sesame snaps brownie. Bonbon candy macaroon fruitcake
candy canes. Cake pudding danish liquorice cupcake jelly-o ice cream.
Liquorice lollipop danish tootsie roll toffee. Gingerbread chocolate
candy canes donut lemon drops apple pie danish bear claw. Caramels cake
jelly jelly sweet chocolate bar gingerbread icing. Cake soufflé lollipop
pudding marshmallow candy canes tootsie roll danish.
</p>
<p>
Carrot cake biscuit icing pudding danish topping powder. Croissant sugar
plum pudding halvah chocolate. Cotton candy tart cake bonbon tart.
Shortbread jelly fruitcake icing pastry. Dragée dessert cupcake cake
sesame snaps toffee pie. Sweet roll sweet roll chupa chups jelly-o
gummies tootsie roll sweet halvah oat cake. Carrot cake carrot cake
muffin bonbon sesame snaps brownie. Bonbon candy macaroon fruitcake
candy canes. Cake pudding danish liquorice cupcake jelly-o ice cream.
Liquorice lollipop danish tootsie roll toffee. Gingerbread chocolate
candy canes donut lemon drops apple pie danish bear claw. Caramels cake
jelly jelly sweet chocolate bar gingerbread icing. Cake soufflé lollipop
pudding marshmallow candy canes tootsie roll danish.
</p>
<p>
Carrot cake biscuit icing pudding danish topping powder. Croissant sugar
plum pudding halvah chocolate. Cotton candy tart cake bonbon tart.
Shortbread jelly fruitcake icing pastry. Dragée dessert cupcake cake
sesame snaps toffee pie. Sweet roll sweet roll chupa chups jelly-o
gummies tootsie roll sweet halvah oat cake. Carrot cake carrot cake
muffin bonbon sesame snaps brownie. Bonbon candy macaroon fruitcake
candy canes. Cake pudding danish liquorice cupcake jelly-o ice cream.
Liquorice lollipop danish tootsie roll toffee. Gingerbread chocolate
candy canes donut lemon drops apple pie danish bear claw. Caramels cake
jelly jelly sweet chocolate bar gingerbread icing. Cake soufflé lollipop
pudding marshmallow candy canes tootsie roll danish.
</p>
<p>
Carrot cake biscuit icing pudding danish topping powder. Croissant sugar
plum pudding halvah chocolate. Cotton candy tart cake bonbon tart.
Shortbread jelly fruitcake icing pastry. Dragée dessert cupcake cake
sesame snaps toffee pie. Sweet roll sweet roll chupa chups jelly-o
gummies tootsie roll sweet halvah oat cake. Carrot cake carrot cake
muffin bonbon sesame snaps brownie. Bonbon candy macaroon fruitcake
candy canes. Cake pudding danish liquorice cupcake jelly-o ice cream.
Liquorice lollipop danish tootsie roll toffee. Gingerbread chocolate
candy canes donut lemon drops apple pie danish bear claw. Caramels cake
jelly jelly sweet chocolate bar gingerbread icing. Cake soufflé lollipop
pudding marshmallow candy canes tootsie roll danish.
</p>
<p>
Carrot cake biscuit icing pudding danish topping powder. Croissant sugar
plum pudding halvah chocolate. Cotton candy tart cake bonbon tart.
Shortbread jelly fruitcake icing pastry. Dragée dessert cupcake cake
sesame snaps toffee pie. Sweet roll sweet roll chupa chups jelly-o
gummies tootsie roll sweet halvah oat cake. Carrot cake carrot cake
muffin bonbon sesame snaps brownie. Bonbon candy macaroon fruitcake
candy canes. Cake pudding danish liquorice cupcake jelly-o ice cream.
Liquorice lollipop danish tootsie roll toffee. Gingerbread chocolate
candy canes donut lemon drops apple pie danish bear claw. Caramels cake
jelly jelly sweet chocolate bar gingerbread icing. Cake soufflé lollipop
pudding marshmallow candy canes tootsie roll danish.
</p>
<p>
Carrot cake biscuit icing pudding danish topping powder. Croissant sugar
plum pudding halvah chocolate. Cotton candy tart cake bonbon tart.
Shortbread jelly fruitcake icing pastry. Dragée dessert cupcake cake
sesame snaps toffee pie. Sweet roll sweet roll chupa chups jelly-o
gummies tootsie roll sweet halvah oat cake. Carrot cake carrot cake
muffin bonbon sesame snaps brownie. Bonbon candy macaroon fruitcake
candy canes. Cake pudding danish liquorice cupcake jelly-o ice cream.
Liquorice lollipop danish tootsie roll toffee. Gingerbread chocolate
candy canes donut lemon drops apple pie danish bear claw. Caramels cake
jelly jelly sweet chocolate bar gingerbread icing. Cake soufflé lollipop
pudding marshmallow candy canes tootsie roll danish.
</p>
<p>
Carrot cake biscuit icing pudding danish topping powder. Croissant sugar
plum pudding halvah chocolate. Cotton candy tart cake bonbon tart.
Shortbread jelly fruitcake icing pastry. Dragée dessert cupcake cake
sesame snaps toffee pie. Sweet roll sweet roll chupa chups jelly-o
gummies tootsie roll sweet halvah oat cake. Carrot cake carrot cake
muffin bonbon sesame snaps brownie. Bonbon candy macaroon fruitcake
candy canes. Cake pudding danish liquorice cupcake jelly-o ice cream.
Liquorice lollipop danish tootsie roll toffee. Gingerbread chocolate
candy canes donut lemon drops apple pie danish bear claw. Caramels cake
jelly jelly sweet chocolate bar gingerbread icing. Cake soufflé lollipop
pudding marshmallow candy canes tootsie roll danish.
</p>
</div>
</body>
<title>Talon</title>
</head>
<body>
<div id="main">
<div style="width: 100%; max-width: 450px; display: inline-block">
<svg
version="1.1"
viewBox="0 0 119.06 34.396"
xmlns="http://www.w3.org/2000/svg"
aria-label="Talon"
>
<defs>
<filter
id="filter106970"
x="-.069601"
y="-.32851"
width="1.1682"
height="1.7939"
color-interpolation-filters="sRGB"
>
<feOffset dx="3" dy="3" />
<feGaussianBlur result="blur" stdDeviation="3" />
<feFlood flood-color="rgb(121,53,223)" result="flood" />
<feComposite
in="flood"
in2="SourceGraphic"
operator="in"
result="composite"
/>
<feBlend in="blur" in2="composite" />
</filter>
</defs>
<g transform="translate(-5.1682 -.21323)">
<text
transform="scale(.26458)"
fill="#000000"
font-family="sans-serif"
font-size="40px"
style="line-height: 1.25; shape-inside: url(#rect3620); white-space: pre"
xml:space="preserve"
/>
<g
fill="#7935df"
filter="url(#filter106970)"
stroke-width=".72282"
aria-label="Talon"
>
<path
d="m11.476 6.1383q2.9788 0.11294 8.7599 0.23294 6.1905-0.12 9.6282-0.23294v1.6165q-1.327-0.05647-4.1647-0.05647-1.4188-0.05647-4.1929-0.05647 0.05647 2.9506 0.05647 4.9411 0 2.0259 0.03529 4.8353 0 2.8023 0.05647 5.0541 0.02824 2.287 0.0847 3.8188h-2.0823q0.17647-5.4352 0.17647-13.56v-5.0894q-1.3623 0.028235-3.8188 0.028235t-4.5388 0.084705z"
/>
<path
d="m30.323 25.945q0.89646-1.7365 2.4282-4.8 1.5035-3.0353 2.8094-5.6682 1.2988-2.6047 2.5976-5.2305 1.2706-2.6329 2.5764-5.2376h0.05647q1.6165 2.9788 2.9506 5.4988 1.2988 2.5412 2.6894 5.1741 1.3835 2.6612 2.9435 5.5764 1.5388 2.9223 2.4917 4.567l-1.6518 1.0447-4.1294-7.807-10.673 0.34588q-0.60705 1.2423-1.7365 3.7341-1.1223 2.4847-1.7012 3.727zm5.7811-7.8917 9.374-0.35294q-2.4635-4.7082-4.7435-9.4235-1.1294 2.3153-2.52 5.3505-1.4118 3.0635-2.1106 4.4258z"
/>
<path
d="m66.005 26.58q-2.6612-0.11294-4.7153-0.17647-2.0753-0.02824-4.4188-0.11294 0.16941-3.9882 0.16941-8.4705 0-2.9506-0.11294-11.739h1.9623q-0.11294 5.407-0.14118 8.2376 0.02824 3.5011 0.02824 10.553 2.4 0 3.6141 0.05647 1.2423-0.05647 3.6141-0.05647z"
/>
<path
d="m79.65 7.0348q-2.4282 0.17647-4.3341 1.5882-1.9341 1.4188-2.9506 3.5859-1.0094 2.167-1.0094 4.4823 0 3.7623 2.1953 6.247 2.0823 2.4 5.7529 2.4 0.17647 0 0.34588 0 3.6141-0.0847 5.9858-2.5694 2.3717-2.4917 2.6047-6.0776 0.02823-0.43058 0.02823-0.86823 0-2.5694-0.89646-4.4823-1.0376-2.1953-2.9506-3.2611-1.9341-1.0447-4.2211-1.0447zm0-1.4188h1.0729q1.8494 0 2.8659 0.35294 2.0188 0.54352 3.5788 1.8776 1.567 1.3553 2.3435 3.5576 0.60705 1.7576 0.60705 4.0447 0 0.60705-0.02823 1.2423-0.2047 2.4-1.5953 4.8564-1.3835 2.4565-3.84 3.847-1.9059 1.1859-5.0047 1.3623-0.25412 0-0.51529 0-2.4564 0-4.567-1.0165-2.3435-1.1294-3.7341-3.4094-1.3835-2.2588-1.4188-5.3505 0.06353-0.16941 0.06353-0.28941 0.02824-2.9223 1.4118-5.407 1.3906-2.4564 3.7059-3.9882 2.3082-1.5035 5.0541-1.68z"
/>
<path
d="m96.739 4.9524h0.11294q2.6612 3.007 5.2941 5.9576 2.6047 2.9506 3.4094 3.8188 0.78352 0.89646 2.9788 3.3811 2.167 2.5129 4.1364 4.9129h0.0565q0-0.95293 0-1.9059-0.0282-0.98117 0-1.9694 0.0282-1.9623 0.17647-6.6494 0.14117-4.6517 0.16941-6.4729h1.8494q-0.0847 2.3082-0.28941 6.6776-0.22588 4.3906-0.34588 7.9482-0.14117 3.5576-0.19764 6.2188h-0.0635q-1.7294-2.0259-5.407-6.127-3.6988-4.1082-5.6894-6.3952-2.0259-2.28-4.8353-5.6047h-0.05647l-0.37412 17.661-1.8776-0.05647z"
/>
</g>
</g>
</svg>
</div>
<h1>development</h1>
<p>
Carrot cake biscuit icing pudding danish topping powder. Croissant sugar plum
pudding halvah chocolate. Cotton candy tart cake bonbon tart. Shortbread jelly
fruitcake icing pastry. Dragée dessert cupcake cake sesame snaps toffee pie.
Sweet roll sweet roll chupa chups jelly-o gummies tootsie roll sweet halvah oat
cake. Carrot cake carrot cake muffin bonbon sesame snaps brownie. Bonbon candy
macaroon fruitcake candy canes. Cake pudding danish liquorice cupcake jelly-o
ice cream. Liquorice lollipop danish tootsie roll toffee. Gingerbread chocolate
candy canes donut lemon drops apple pie danish bear claw. Caramels cake jelly
jelly sweet chocolate bar gingerbread icing. Cake soufflé lollipop pudding
marshmallow candy canes tootsie roll danish.
</p>
<p>
Carrot cake biscuit icing pudding danish topping powder. Croissant sugar plum
pudding halvah chocolate. Cotton candy tart cake bonbon tart. Shortbread jelly
fruitcake icing pastry. Dragée dessert cupcake cake sesame snaps toffee pie.
Sweet roll sweet roll chupa chups jelly-o gummies tootsie roll sweet halvah oat
cake. Carrot cake carrot cake muffin bonbon sesame snaps brownie. Bonbon candy
macaroon fruitcake candy canes. Cake pudding danish liquorice cupcake jelly-o
ice cream. Liquorice lollipop danish tootsie roll toffee. Gingerbread chocolate
candy canes donut lemon drops apple pie danish bear claw. Caramels cake jelly
jelly sweet chocolate bar gingerbread icing. Cake soufflé lollipop pudding
marshmallow candy canes tootsie roll danish.
</p>
<p>
Carrot cake biscuit icing pudding danish topping powder. Croissant sugar plum
pudding halvah chocolate. Cotton candy tart cake bonbon tart. Shortbread jelly
fruitcake icing pastry. Dragée dessert cupcake cake sesame snaps toffee pie.
Sweet roll sweet roll chupa chups jelly-o gummies tootsie roll sweet halvah oat
cake. Carrot cake carrot cake muffin bonbon sesame snaps brownie. Bonbon candy
macaroon fruitcake candy canes. Cake pudding danish liquorice cupcake jelly-o
ice cream. Liquorice lollipop danish tootsie roll toffee. Gingerbread chocolate
candy canes donut lemon drops apple pie danish bear claw. Caramels cake jelly
jelly sweet chocolate bar gingerbread icing. Cake soufflé lollipop pudding
marshmallow candy canes tootsie roll danish.
</p>
<p>
Carrot cake biscuit icing pudding danish topping powder. Croissant sugar plum
pudding halvah chocolate. Cotton candy tart cake bonbon tart. Shortbread jelly
fruitcake icing pastry. Dragée dessert cupcake cake sesame snaps toffee pie.
Sweet roll sweet roll chupa chups jelly-o gummies tootsie roll sweet halvah oat
cake. Carrot cake carrot cake muffin bonbon sesame snaps brownie. Bonbon candy
macaroon fruitcake candy canes. Cake pudding danish liquorice cupcake jelly-o
ice cream. Liquorice lollipop danish tootsie roll toffee. Gingerbread chocolate
candy canes donut lemon drops apple pie danish bear claw. Caramels cake jelly
jelly sweet chocolate bar gingerbread icing. Cake soufflé lollipop pudding
marshmallow candy canes tootsie roll danish.
</p>
<p>
Carrot cake biscuit icing pudding danish topping powder. Croissant sugar plum
pudding halvah chocolate. Cotton candy tart cake bonbon tart. Shortbread jelly
fruitcake icing pastry. Dragée dessert cupcake cake sesame snaps toffee pie.
Sweet roll sweet roll chupa chups jelly-o gummies tootsie roll sweet halvah oat
cake. Carrot cake carrot cake muffin bonbon sesame snaps brownie. Bonbon candy
macaroon fruitcake candy canes. Cake pudding danish liquorice cupcake jelly-o
ice cream. Liquorice lollipop danish tootsie roll toffee. Gingerbread chocolate
candy canes donut lemon drops apple pie danish bear claw. Caramels cake jelly
jelly sweet chocolate bar gingerbread icing. Cake soufflé lollipop pudding
marshmallow candy canes tootsie roll danish.
</p>
<p>
Carrot cake biscuit icing pudding danish topping powder. Croissant sugar plum
pudding halvah chocolate. Cotton candy tart cake bonbon tart. Shortbread jelly
fruitcake icing pastry. Dragée dessert cupcake cake sesame snaps toffee pie.
Sweet roll sweet roll chupa chups jelly-o gummies tootsie roll sweet halvah oat
cake. Carrot cake carrot cake muffin bonbon sesame snaps brownie. Bonbon candy
macaroon fruitcake candy canes. Cake pudding danish liquorice cupcake jelly-o
ice cream. Liquorice lollipop danish tootsie roll toffee. Gingerbread chocolate
candy canes donut lemon drops apple pie danish bear claw. Caramels cake jelly
jelly sweet chocolate bar gingerbread icing. Cake soufflé lollipop pudding
marshmallow candy canes tootsie roll danish.
</p>
<p>
Carrot cake biscuit icing pudding danish topping powder. Croissant sugar plum
pudding halvah chocolate. Cotton candy tart cake bonbon tart. Shortbread jelly
fruitcake icing pastry. Dragée dessert cupcake cake sesame snaps toffee pie.
Sweet roll sweet roll chupa chups jelly-o gummies tootsie roll sweet halvah oat
cake. Carrot cake carrot cake muffin bonbon sesame snaps brownie. Bonbon candy
macaroon fruitcake candy canes. Cake pudding danish liquorice cupcake jelly-o
ice cream. Liquorice lollipop danish tootsie roll toffee. Gingerbread chocolate
candy canes donut lemon drops apple pie danish bear claw. Caramels cake jelly
jelly sweet chocolate bar gingerbread icing. Cake soufflé lollipop pudding
marshmallow candy canes tootsie roll danish.
</p>
</div>
</body>
<script id="talon-data" type="application/json">
{
"root_path": "/",
"current_page": "2",
"current_version": "6",
"versions": {
"3": {
"date": "2021-06-15T11:12:21+00:00",
"name": "0.1.0",
"user": "ThetaDev",
"tags": {
"commit": "ec55eba5ae45640c3d225c2471920fd3c9a36489"
}
},
"4": {
"date": "2021-06-20T16:48:21+00:00",
"name": "0.1.1",
"user": "ThetaDev",
"tags": {
"commit": "ec55eba5ae45640c3d225c2471920fd3c9a36489"
}
},
"5": {
"date": "2021-06-22T12:08:21+00:00",
"name": "0.1.2",
"user": "ThetaDev",
"tags": {
"commit": "ec55eba5ae45640c3d225c2471920fd3c9a36489"
}
},
"6": {
"date": "2021-07-02T16:12:40+00:00",
"name": "0.1.3",
"user": "ThetaDev",
"tags": {
"commit": "bbc7342580b48433481857bfe95e58784a508275"
}
}
},
"pages": {
"1": {
"name": "ThetaDev",
"path": "",
"color": "#1f91ee",
"visibility": "featured",
"source": {
"url": "https://github.com/Theta-Dev",
"type": "github"
}
},
"2": {
"name": "Talon",
"path": "Talon",
"color": "#4b228a",
"visibility": "featured",
"source": {
"url": "https://github.com/Theta-Dev/Talon",
"type": "github"
}
},
"3": {
"name": "Spotify-Gender-Ex",
"path": "Spotify-Gender-Ex",
"color": "#1DB954",
"image": "https://raw.githubusercontent.com/Theta-Dev/Spotify-Gender-Ex/master/assets/logo_square.svg",
"visibility": "featured",
"source": {
"url": "https://github.com/Theta-Dev/Spotify-Gender-Ex",
"type": "github"
}
},
"4": {
"name": "A1",
"path": "tests/a1",
"color": "#ff0000",
"visibility": "searchable"
},
"5": {
"name": "B1",
"path": "tests/b1",
"color": "#00ff00",
"visibility": "searchable"
},
"6": {
"name": "C1",
"path": "tests/c1",
"color": "#0000ff",
"visibility": "searchable"
}
}
}
</script>
<script src="./talon.js"></script>
<script id="talon-config" type="application/json">
{
"api": "http://talon.localhost:3000/api",
"version": "0.1.0",
"root_domain": "localhost:3000"
}
</script>
<script src="./talon.js"></script>
</html>

View file

@ -13,6 +13,7 @@ import sveltePreprocess from "svelte-preprocess"
import typescript from "@rollup/plugin-typescript"
import replace from "@rollup/plugin-replace"
import babel from "@rollup/plugin-babel"
import brotli from "rollup-plugin-brotli"
import css from ".rollup/css-only"
import {serve} from ".rollup/serve"
@ -148,6 +149,8 @@ export default {
],
babelHelpers: "bundled",
}),
brotli(),
],
watch: {
chokidar: true,

View file

@ -1,16 +1,32 @@
<script lang="ts">
import {closeModal, Modals} from "svelte-modals"
import { onMount } from "svelte";
import { closeModal, Modals } from "svelte-modals";
import type { Website } from "ui/talon-client/src";
import Menu from "./components/Menu.svelte"
import {currentPage, isPresent} from "./util/talonData"
import Menu from "./components/Menu.svelte";
import { currentWebsiteStore, fetchWebsites } from "./util/api";
let currentWebsite: Website;
currentWebsiteStore.subscribe((ws) => {
console.log("current ws changed", ws);
currentWebsite = ws;
});
onMount(fetchWebsites);
let color: String;
$: {
if (currentWebsite && currentWebsite.color) {
color = currentWebsite.color;
} else {
color = "#7935df";
}
}
</script>
<style lang="sass">
// Default theme
.wrapper
--talon-color: #7935df
.backdrop
position: fixed
top: 0
@ -20,12 +36,13 @@
background: rgba(0, 0, 0, 0.6)
</style>
<div class="wrapper" style={`--talon-color: ${currentPage.color}`}>
{#if isPresent}
<Menu />
{/if}
<div class="wrapper" style={`--talon-color: ${color}`}>
{#if currentWebsite}
<Menu />
{/if}
<Modals>
<div class="backdrop" slot="backdrop" on:click={closeModal} />
</Modals>
<Modals>
<!-- svelte-ignore a11y-click-events-have-key-events -->
<div class="backdrop" slot="backdrop" on:click={closeModal} />
</Modals>
</div>

View file

@ -1,9 +1,9 @@
<script lang="ts">
export let imageSrc: string
export let size = 32
export let scale = 1
export let alt = "??"
export let color = "#4b228a"
export let imageSrc: string;
export let size = 32;
export let scale = 1;
export let alt = "??";
export let color = "#4b228a";
</script>
@ -19,13 +19,13 @@
</style>
<span class="icon" style="width: {size}px; height: {size}px;">
{#if imageSrc}
<span
style="width: {size * scale}px; height: {size * scale}px; background-image: url('{imageSrc}')" />
{:else}
<span
style="width: {size * scale}px; height: {size * scale}px; font-size: {size * scale * 0.55}px; background: {color}">
{alt}
</span>
{/if}
{#if imageSrc}
<span
style="width: {size * scale}px; height: {size * scale}px; background-image: url('{imageSrc}')" />
{:else}
<span
style="width: {size * scale}px; height: {size * scale}px; font-size: {size * scale * 0.55}px; background: {color}">
{alt}
</span>
{/if}
</span>

View file

@ -1,63 +1,38 @@
<script lang="ts">
import {fly} from "svelte/transition"
import {closeModal} from "svelte-modals"
import Keydown from "svelte-keydown"
import { fly } from "svelte/transition";
import { closeModal } from "svelte-modals";
import Keydown from "svelte-keydown";
import type {TalonVersion} from "../util/types"
import PageIcon from "./PageIcon.svelte"
import Icon from "./Icon.svelte"
import {formatDate} from "../util/functions"
import InlineIcon from "./InlineIcon.svelte"
import Tag from "./Tag.svelte"
import {
currentPage,
currentVersion,
currentVersionId,
rootPath,
versions,
} from "../util/talonData"
import PageIcon from "./PageIcon.svelte";
import Icon from "./Icon.svelte";
import { formatDate, getWebsiteVersionUrl } from "../util/functions";
import InlineIcon from "./InlineIcon.svelte";
import Tag from "./Tag.svelte";
import type { Version, Website } from "talon-client";
import { client, currentWebsiteStore } from "../util/api";
import { talonConfig } from "../util/talonData";
export let isOpen: boolean
let currentWebsite: Website;
currentWebsiteStore.subscribe((ws) => {
currentWebsite = ws;
});
function getVersionName(versionId: string, version: TalonVersion): string {
return version.name ? version.name : "#" + versionId
}
function getVersionUrl(versionId: string, version: TalonVersion): string {
return (
rootPath +
(currentPage && version.name
? currentPage.path + "@" + version.name
: "&v/" + versionId)
)
}
let versionName: string
$: versionName = getVersionName(currentVersionId, currentVersion)
let versionUrl: string
$: versionUrl = getVersionUrl(currentVersionId, currentVersion)
let uploadDate: string
$: uploadDate = formatDate(currentVersion.date)
let pageTags: [string, string][]
$: pageTags = currentVersion.tags
? Object.entries(currentVersion.tags).map(([key, val]) => [
key.replace(/^\w/, (c) => c.toUpperCase()),
val,
])
: []
let history: [string, string, string][]
$: history = Object.entries(versions)
.filter((e) => e[0] !== currentVersionId)
.map(([key, version]) => [
formatDate(version.date),
getVersionName(key, version),
getVersionUrl(key, version),
])
export let isOpen: boolean;
$: {
if (isOpen && currentWebsite) {
client
.websiteSubdomainVersionsGet({ subdomain: currentWebsite.subdomain })
.then((v) => {
versions = v;
if (v && v.length > 0) {
currentVersion = v[v.length - 1];
}
});
}
}
let versions: Version[] = [];
let currentVersion: Version = null;
</script>
<style lang="sass">
@ -143,65 +118,58 @@
<Keydown paused={!isOpen} on:Escape={closeModal} />
{#if isOpen}
<div class="modal" role="dialog" transition:fly={{y: 50}} on:introstart on:outroend>
<div>
<div class="tag">
<PageIcon page={currentPage} size={60} scale={0.8} />
<span>{currentPage ? currentPage.name : 'v' + currentVersionId}</span>
</div>
{#if isOpen && currentWebsite}
<div class="modal" role="dialog" transition:fly={{ y: 50 }} on:introstart on:outroend>
<div>
<div class="tag">
<PageIcon website={currentWebsite} size={60} scale={0.8} />
<span>{currentWebsite.name}</span>
</div>
{#if !currentPage}
<p>
This is a dangling version, i.e. it does not belong to a page.
Assign it to a page or it will be purged within 24 hours.
</p>
{/if}
{#if currentVersion}
<p class="dhead">
<InlineIcon iconName="question" />
Current version #{currentVersion.id}
</p>
<Tag key="Upload date" value={formatDate(currentVersion.createdAt)} />
<!--<Tag key="Uploaded by" value={currentVersion.user} />-->
<p class="dhead">
<InlineIcon iconName="question" />
Current version
</p>
{#each Object.entries(currentVersion.data) as [key, value]}
<Tag {key} {value} />
{/each}
<Tag key="Version" value={versionName} href={versionUrl} />
<Tag key="Upload date" value={uploadDate} />
<Tag key="Uploaded by" value={currentVersion.user} />
{#if versions && versions.length}
<p class="dhead">
<InlineIcon iconName="history" />
History
</p>
{#each pageTags as [key, value]}
<Tag {key} {value} />
{/each}
{#each versions as version}
<p class="smalltag">
<a href={getWebsiteVersionUrl(currentWebsite.subdomain, version.id)}>
<span>#{version.id}</span>
<span>{formatDate(version.createdAt)}</span>
</a>
</p>
{/each}
{/if}
{/if}
{#if history.length}
<p class="dhead">
<InlineIcon iconName="history" />
History
</p>
<p class="dhead" />
{#each history as [date, name, url]}
<p class="smalltag">
<a href={url}> <span>{name}</span> <span>{date}</span> </a>
</p>
{/each}
{/if}
<p class="dhead" />
<div>
This site is powered by
<a
href="https://github.com/Theta-Dev/Talon/tree/__VERSION__"
target="_blank"
referrerpolicy="no-referrer">Talon __VERSION__</a>, a static site
management system created by
<a
href="https://thetadev.de"
target="_blank"
referrerpolicy="no-referrer">ThetaDev</a>
</div>
<p><a href={rootPath + '&credits'} target="_blank">View licenses</a></p>
<button on:click={closeModal}>
<Icon iconName="close" size={40} scale={0.6} transparent={true} />
</button>
</div>
</div>
<div>
Powered by
<a
href="https://code.thetadev.de/ThetaDev/Talon/src/tag/{talonConfig.version}"
target="_blank"
rel="noreferrer"
referrerpolicy="no-referrer">Talon
{talonConfig.version}</a>
</div>
<!--<p><a href="" target="_blank">View licenses</a></p>-->
<button on:click={closeModal}>
<Icon iconName="close" size={40} scale={0.6} transparent={true} />
</button>
</div>
</div>
{/if}

View file

@ -1,87 +1,97 @@
<script lang="ts">
import {openModal} from "svelte-modals"
import { openModal } from "svelte-modals";
import Icon from "./Icon.svelte"
import MenuItem from "./MenuItem.svelte"
import MenuItemPage from "./MenuItemPage.svelte"
import InfoModal from "./InfoModal.svelte"
import FloatingButton from "./FloatingButton.svelte"
import Icon from "./Icon.svelte";
import MenuItem from "./MenuItem.svelte";
import MenuItemPage from "./MenuItemPage.svelte";
import InfoModal from "./InfoModal.svelte";
import FloatingButton from "./FloatingButton.svelte";
import type {Focusable, TalonPage} from "../util/types"
import {TalonVisibility} from "../util/types"
import PageIcon from "./PageIcon.svelte"
import MenuItemInput from "./MenuItemInput.svelte"
import {currentPage, currentPageId, pages, rootPath} from "../util/talonData"
import type { Focusable } from "../util/types";
import PageIcon from "./PageIcon.svelte";
import MenuItemInput from "./MenuItemInput.svelte";
import { currentWebsiteStore, websitesStore } from "../util/api";
import { Visibility, Website } from "talon-client";
import { getWebsiteUrl } from "../util/functions";
function showSidebar(): void {
sidebarShown = true
}
let currentWebsite: Website;
let websites: Website[];
function hideSidebar(): void {
sidebarShown = false
}
currentWebsiteStore.subscribe((ws) => {
currentWebsite = ws;
});
websitesStore.subscribe((ws) => {
websites = ws;
});
function isMobile(): boolean {
return window.innerWidth < 768
}
function showSidebar(): void {
sidebarShown = true;
}
function openSearch(): void {
searchOpen = true
searchInput.focus()
}
function hideSidebar(): void {
sidebarShown = false;
}
function closeSearch() {
searchOpen = false
searchInput.blur()
function isMobile(): boolean {
return window.innerWidth < 768;
}
if (displayedPages.length === 0) searchText = ""
}
function openSearch(): void {
searchOpen = true;
searchInput.focus();
}
function clearSearch() {
searchText = ""
closeSearch()
}
function closeSearch() {
searchOpen = false;
searchInput.blur();
function searchKeypress(e: KeyboardEvent) {
switch (e.key) {
case "Enter":
if (!searchText) {
closeSearch()
} else if (displayedPages.length) {
window.location.href = rootPath + displayedPages[0].path
} else {
closeSearch()
}
break
case "Escape":
clearSearch()
break
}
}
if (displayedWebsites.length === 0) searchText = "";
}
function openInfo() {
openModal(InfoModal)
}
function clearSearch() {
searchText = "";
closeSearch();
}
let sidebarShown = !isMobile()
let searchInput: Focusable
let searchOpen = false
let searchText = ""
function searchKeypress(e: KeyboardEvent) {
switch (e.key) {
case "Enter":
if (!searchText) {
closeSearch();
} else if (displayedWebsites.length) {
window.location.href = getWebsiteUrl(displayedWebsites[0].subdomain);
} else {
closeSearch();
}
break;
case "Escape":
clearSearch();
break;
}
}
let displayedPages: TalonPage[]
$: displayedPages = Object.entries(pages)
.filter(([id, page]) => {
if (id === currentPageId) return false
function openInfo() {
openModal(InfoModal);
}
if (searchText) {
return (
page.visibility !== TalonVisibility.HIDDEN &&
page.name.toLowerCase().includes(searchText.toLowerCase())
)
}
return page.visibility === TalonVisibility.FEATURED
})
.map(([, page]) => page)
let sidebarShown = !isMobile();
let searchInput: Focusable;
let searchOpen = false;
let searchText = "";
let displayedWebsites: Website[];
$: displayedWebsites = websites.filter((ws) => {
if (ws.subdomain == currentWebsite.subdomain) return false;
if (searchText) {
return (
ws.visibility !== Visibility.Hidden &&
ws.name.toLowerCase().includes(searchText.toLowerCase())
);
}
return ws.visibility === Visibility.Featured;
});
</script>
@ -118,40 +128,37 @@
</style>
<nav class:hide={!sidebarShown}>
<div>
<MenuItemInput
active={searchOpen || Boolean(searchText).valueOf()}
on:click={openSearch}
on:focusout={closeSearch}
on:keyup={searchKeypress}
bind:input={searchInput}
bind:text={searchText} />
</div>
<div>
{#each displayedPages as page, i}
<MenuItemPage
{page}
{rootPath}
active={searchOpen && searchText && i === 0} />
{/each}
</div>
<div>
{#if currentPage && currentPage.source}
<MenuItem
text="View source"
link={currentPage.source.url}
newTab={true}
privacy={true}>
<Icon iconName={currentPage.source.type} size={40} scale={0.6} />
</MenuItem>
{/if}
<MenuItem text="Info" on:click={openInfo}>
<PageIcon page={currentPage} />
</MenuItem>
<MenuItem text="Hide sidebar" on:click={hideSidebar}>
<Icon iconName="arrowRight" size={40} scale={0.6} />
</MenuItem>
</div>
<div>
<MenuItemInput
active={searchOpen || Boolean(searchText).valueOf()}
on:click={openSearch}
on:focusout={closeSearch}
on:keyup={searchKeypress}
bind:input={searchInput}
bind:text={searchText} />
</div>
<div>
{#each displayedWebsites as website, i}
<MenuItemPage {website} active={searchOpen && searchText && i === 0} />
{/each}
</div>
<div>
{#if currentWebsite && currentWebsite.sourceUrl}
<MenuItem
text="View source"
link={currentWebsite.sourceUrl}
newTab={true}
privacy={true}>
<Icon iconName={currentWebsite.sourceIcon} size={40} scale={0.6} />
</MenuItem>
{/if}
<MenuItem text="Info" on:click={openInfo}>
<PageIcon website={currentWebsite} />
</MenuItem>
<MenuItem text="Hide sidebar" on:click={hideSidebar}>
<Icon iconName="arrowRight" size={40} scale={0.6} />
</MenuItem>
</div>
</nav>
<FloatingButton hide={sidebarShown} on:click={showSidebar} />

View file

@ -1,22 +1,22 @@
<script lang="ts">
import type {TalonPage} from "../util/types"
import PageIcon from "./PageIcon.svelte"
import MenuItem from "./MenuItem.svelte"
import PageIcon from "./PageIcon.svelte";
import MenuItem from "./MenuItem.svelte";
import type { Website } from "talon-client";
import { getWebsiteUrl } from "../util/functions";
export let page: TalonPage
export let rootPath = "/"
export let active = false
export let website: Website;
export let active = false;
const MAX_TEXT_LEN = 20
const MAX_TEXT_LEN = 20;
let text: string
$: text =
page.name.length > MAX_TEXT_LEN
? page.name.substr(0, 20).trim() + "..."
: page.name
let text: string;
$: text =
website.name.length > MAX_TEXT_LEN
? website.name.substring(0, 20).trim() + "..."
: website.name;
</script>
<MenuItem {active} {text} link={rootPath + page.path}>
<PageIcon {page} />
<MenuItem {active} {text} link={getWebsiteUrl(website.subdomain)}>
<PageIcon {website} />
</MenuItem>

View file

@ -1,19 +1,19 @@
<script lang="ts">
import ImageIcon from "./ImageIcon.svelte"
import Icon from "./Icon.svelte"
import type {TalonPage} from "../util/types"
import type { Website } from "ui/apiclient";
export let page: TalonPage
export let website: Website
export let size = 40
export let scale = 0.8
</script>
{#if page}
{#if website}
<ImageIcon
imageSrc={page.image}
color={page.color}
alt={page.name.substr(0, 2)}
imageSrc=""
color={website.color}
alt={website.name.substring(0, 2)}
{size}
{scale} />
{:else}

View file

@ -1,4 +1,4 @@
const sidebar = document.createElement("talon-sidebar")
document.body.append(sidebar)
const sidebar = document.createElement("talon-sidebar");
document.body.append(sidebar);
export {default as default} from "./App.svelte"
export { default as default } from "./App.svelte";

18
ui/menu/src/util/api.ts Normal file
View file

@ -0,0 +1,18 @@
import {Writable, writable} from "svelte/store"
import {Configuration, DefaultApi, Website} from "talon-client"
import {getSubdomain} from "./functions"
import { talonConfig } from "./talonData"
export const websitesStore: Writable<Website[]> = writable([])
export const currentWebsiteStore: Writable<Website> = writable(null)
export const client = new DefaultApi(new Configuration({basePath: talonConfig.api}));
export function fetchWebsites(): void {
client.websitesGet().then((ws) => {
websitesStore.set(ws)
const subdomain = getSubdomain()
currentWebsiteStore.set(ws.find((w) => w.subdomain == subdomain))
})
}

View file

@ -1,5 +1,41 @@
function formatDate(dateString: string): string {
return new Date(dateString).toLocaleString(navigator.language)
import {talonConfig} from "./talonData"
export function formatDate(date: Date): string {
return date.toLocaleString(navigator.language)
}
export {formatDate}
export function getSubdomain(): string {
const hn = window.location.hostname
const rd_noport = talonConfig.root_domain.split(":", 1)[0]
if (hn.endsWith("." + rd_noport)) {
const subdomain = hn.substring(0, hn.length - rd_noport.length - 1).split("--", 1)[0]
if (subdomain === "x") {
return "-"
} else {
return subdomain
}
}
return "-"
}
export function getWebsiteUrl(subdomain: string): string {
const proto = window.location.protocol
if (subdomain === "-") {
return `${proto}//${talonConfig.root_domain}`
} else {
return `${proto}//${subdomain}.${talonConfig.root_domain}`
}
}
export function getWebsiteVersionUrl(subdomain: string, version: number): string {
const proto = window.location.protocol
const v = `--v${version}`
if (subdomain === "-") {
return `${proto}//x${v}.${talonConfig.root_domain}`
} else {
return `${proto}//${subdomain}${v}.${talonConfig.root_domain}`
}
}

View file

@ -1,54 +1,33 @@
import type {TalonData, TalonPage, TalonVersion} from "./types"
import {TalonVisibility} from "./types"
import type {TalonConfig} from "./types"
const talonData: TalonData = JSON.parse(
document.getElementById("talon-data").textContent
) as TalonData
export const talonConfig: TalonConfig = JSON.parse(
document.getElementById("talon-config").textContent
) as TalonConfig
const isTalonData = (obj: TalonData) =>
"root_path" in obj &&
"current_page" in obj &&
"current_version" in obj &&
"versions" in obj &&
"pages" in obj
// const rootPath = talonData.root_path
const isTalonVersion = (obj: TalonVersion) => "date" in obj && "user" in obj
// const currentVersion = talonData.versions[talonData.current_version]
// const currentVersionId = talonData.current_version
const rootPath = talonData.root_path
// const getCurrentPage = () => {
// if (talonData.current_page) {
// return talonData.pages[talonData.current_page]
// } else {
// return {
// name: "#" + talonData.current_version,
// path: "&v/" + talonData.current_version,
// color: "#7935df",
// visibility: TalonVisibility.HIDDEN,
// image: undefined,
// source: undefined,
// }
// }
// }
const currentVersion = talonData.versions[talonData.current_version]
const currentVersionId = talonData.current_version
// const currentPage: TalonPage = getCurrentPage()
// const currentPageId = talonData.current_page
const getCurrentPage = () => {
if (talonData.current_page) {
return talonData.pages[talonData.current_page]
} else {
return {
name: "#" + talonData.current_version,
path: "&v/" + talonData.current_version,
color: "#7935df",
visibility: TalonVisibility.HIDDEN,
image: undefined,
source: undefined,
}
}
}
// const isPresent = isTalonData(talonData) && isTalonVersion(currentVersion)
const currentPage: TalonPage = getCurrentPage()
const currentPageId = talonData.current_page
const isPresent = isTalonData(talonData) && isTalonVersion(currentVersion)
const versions = talonData.versions
const pages = talonData.pages
export {
rootPath,
isPresent,
currentVersion,
currentVersionId,
currentPage,
currentPageId,
versions,
pages,
}
// const versions = talonData.versions
// const pages = talonData.pages

View file

@ -1,54 +1,15 @@
export interface TalonData {
root_path: string
current_page: string | null
current_version: string
versions: {[key: string]: TalonVersion}
pages: {[key: string]: TalonPage}
export interface TalonConfig {
api: string;
version: string;
root_domain: string;
}
export interface TalonPage {
name: string
path: string
color: string
visibility: TalonVisibility
image: string | undefined
source: TalonLink | undefined
}
export enum TalonVisibility {
FEATURED = "featured",
SEARCHABLE = "searchable",
HIDDEN = "hidden",
}
export interface TalonLink {
url: string
type: TalonLinkType
}
export enum TalonLinkType {
LINK = "link",
GIT = "git",
GITHUB = "github",
GITLAB = "gitlab",
GITEA = "gitea",
BITBUCKET = "bitbucket",
}
export interface TalonVersion {
date: string
name: string | undefined
user: string
tags: {[key: string]: string} | undefined
}
export interface Focusable {
focus(): void
blur(): void
focus(): void;
blur(): void;
}
export interface SvelteActionRes {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
update?: (parameters: any) => void
destroy?: () => void
// eslint-disable-next-line @typescript-eslint/no-explicit-any
update?: (parameters: any) => void;
destroy?: () => void;
}

View file

@ -1,90 +0,0 @@
{
"root_path": "/",
"current_page": null,
"current_version": "6",
"versions": {
"3": {
"date": "2021-06-15T11:12:21+00:00",
"name": "0.1.0",
"user": "ThetaDev",
"tags": {
"commit": "ec55eba5ae45640c3d225c2471920fd3c9a36489"
}
},
"4": {
"date": "2021-06-20T16:48:21+00:00",
"name": "0.1.1",
"user": "ThetaDev",
"tags": {
"commit": "ec55eba5ae45640c3d225c2471920fd3c9a36489"
}
},
"5": {
"date": "2021-06-22T12:08:21+00:00",
"name": "0.1.2",
"user": "ThetaDev",
"tags": {
"commit": "ec55eba5ae45640c3d225c2471920fd3c9a36489"
}
},
"6": {
"date": "2021-07-02T16:12:40+00:00",
"name": "0.1.3",
"user": "ThetaDev",
"tags": {
"commit": "bbc7342580b48433481857bfe95e58784a508275"
}
}
},
"pages": {
"1": {
"name": "ThetaDev",
"path": "",
"color": "#1f91ee",
"visibility": "featured",
"source": {
"url": "https://github.com/Theta-Dev",
"type": "github"
}
},
"2": {
"name": "Talon",
"path": "Talon",
"color": "#4b228a",
"visibility": "featured",
"source": {
"url": "https://github.com/Theta-Dev/Talon",
"type": "github"
}
},
"3": {
"name": "Spotify-Gender-Ex",
"path": "Spotify-Gender-Ex",
"color": "#1DB954",
"image": "https://raw.githubusercontent.com/Theta-Dev/Spotify-Gender-Ex/master/assets/logo_square.svg",
"visibility": "featured",
"source": {
"url": "https://github.com/Theta-Dev/Spotify-Gender-Ex",
"type": "github"
}
},
"4": {
"name": "A1",
"path": "tests/a1",
"color": "#ff0000",
"visibility": "searchable"
},
"5": {
"name": "B1",
"path": "tests/b1",
"color": "#00ff00",
"visibility": "searchable"
},
"6": {
"name": "C1",
"path": "tests/c1",
"color": "#0000ff",
"visibility": "searchable"
}
}
}

View file

@ -1,99 +0,0 @@
{
"$schema": "http://json-schema.org/draft-07/schema#",
"title": "Talon menu data",
"definitions": {
"Version": {
"type": "object",
"properties": {
"date": {
"type": "string",
"format": "date-time"
},
"name": {
"type": "string"
},
"user": {
"type": "string"
},
"tags": {
"type": "object",
"additionalProperties": {
"type": "string"
}
}
},
"required": ["date", "user"]
},
"Link": {
"type": "object",
"properties": {
"url": {
"type": "string"
},
"type": {
"type": "string",
"enum": ["link", "git", "github", "gitlab", "gitea", "bitbucket"]
}
},
"required": ["url", "type"]
},
"Page": {
"type": "object",
"properties": {
"name": {
"type": "string",
"maxLength": 100
},
"path": {
"type": "string"
},
"color": {
"type": "string",
"format": "regex",
"pattern": "^#[A-z\\d]{6}$"
},
"image": {
"type": "string",
"format": "uri"
},
"visibility": {
"type": "string",
"enum": ["featured", "searchable", "hidden"]
},
"source": {
"$ref": "#/definitions/Link"
}
},
"required": ["name", "path", "color", "visibility"]
}
},
"oneOf": [
{
"type": "object",
"properties": {
"root_path": {
"type": "string"
},
"current_page": {
"type": ["string", "null"]
},
"current_version": {
"type": "string"
},
"versions": {
"type": "object",
"additionalProperties": {
"$ref": "#/definitions/Version"
}
},
"pages": {
"type": "object",
"additionalProperties": {
"$ref": "#/definitions/Page"
}
}
},
"required": ["root_path", "current_page", "current_version", "versions", "pages"]
}
]
}

4
ui/talon-client/.gitignore vendored Normal file
View file

@ -0,0 +1,4 @@
wwwroot/*.js
node_modules
typings
dist

View file

@ -0,0 +1 @@
README.md

View file

@ -0,0 +1,23 @@
# OpenAPI Generator Ignore
# Generated by openapi-generator https://github.com/openapitools/openapi-generator
# Use this file to prevent files from being overwritten by the generator.
# The patterns follow closely to .gitignore or .dockerignore.
# As an example, the C# client generator defines ApiClient.cs.
# You can make changes and tell OpenAPI Generator to ignore just this file by uncommenting the following line:
#ApiClient.cs
# You can match any string of characters against a directory, file or extension with a single asterisk (*):
#foo/*/qux
# The above matches foo/bar/qux and foo/baz/qux, but not foo/bar/baz/qux
# You can recursively match patterns against a directory, file or extension with a double asterisk (**):
#foo/**/qux
# This matches foo/bar/qux, foo/baz/qux, and foo/bar/baz/qux
# You can also negate patterns with an exclamation (!).
# For example, you can ignore all files in a docs folder with the file extension .md:
#docs/*.md
# Then explicitly reverse the ignore rule for a single file:
#!docs/README.md

View file

@ -0,0 +1,23 @@
.gitignore
.npmignore
.openapi-generator-ignore
README.md
package.json
src/apis/DefaultApi.ts
src/apis/index.ts
src/index.ts
src/models/Info.ts
src/models/InfoStats.ts
src/models/InfoVersion.ts
src/models/SourceIcon.ts
src/models/Stats.ts
src/models/Version.ts
src/models/VersionFile.ts
src/models/VersionInfo.ts
src/models/Visibility.ts
src/models/Website.ts
src/models/WebsiteNew.ts
src/models/WebsiteUpdate.ts
src/models/index.ts
src/runtime.ts
tsconfig.json

View file

@ -0,0 +1 @@
6.4.0

45
ui/talon-client/README.md Normal file
View file

@ -0,0 +1,45 @@
## talon-client@0.1.0
This generator creates TypeScript/JavaScript client that utilizes [Fetch API](https://fetch.spec.whatwg.org/). The generated Node module can be used in the following environments:
Environment
* Node.js
* Webpack
* Browserify
Language level
* ES5 - you must have a Promises/A+ library installed
* ES6
Module system
* CommonJS
* ES6 module system
It can be used in both TypeScript and JavaScript. In TypeScript, the definition should be automatically resolved via `package.json`. ([Reference](http://www.typescriptlang.org/docs/handbook/typings-for-npm-packages.html))
### Building
To build and compile the typescript sources to javascript use:
```
npm install
npm run build
```
### Publishing
First build the package then run ```npm publish```
### Consuming
navigate to the folder of your consuming project and run one of the following commands.
_published:_
```
npm install talon-client@0.1.0 --save
```
_unPublished (not recommended):_
```
npm install PATH_TO_GENERATED_PACKAGE --save

28
ui/talon-client/package-lock.json generated Normal file
View file

@ -0,0 +1,28 @@
{
"name": "talon-client",
"version": "0.1.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "talon-client",
"version": "0.1.0",
"devDependencies": {
"typescript": "^4.0"
}
},
"node_modules/typescript": {
"version": "4.9.5",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-4.9.5.tgz",
"integrity": "sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g==",
"dev": true,
"bin": {
"tsc": "bin/tsc",
"tsserver": "bin/tsserver"
},
"engines": {
"node": ">=4.2.0"
}
}
}
}

View file

@ -0,0 +1,19 @@
{
"name": "talon-client",
"version": "0.1.0",
"description": "OpenAPI client for talon-client",
"author": "OpenAPI-Generator",
"repository": {
"type": "git",
"url": "https://github.com/GIT_USER_ID/GIT_REPO_ID.git"
},
"main": "./dist/index.js",
"typings": "./dist/index.d.ts",
"scripts": {
"build": "tsc",
"prepare": "npm run build"
},
"devDependencies": {
"typescript": "^4.0"
}
}

View file

@ -0,0 +1,559 @@
/* tslint:disable */
/* eslint-disable */
/**
* Talon
* API for the Talon static site management system
*
* The version of the OpenAPI document: 0.1.0
*
*
* NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
* https://openapi-generator.tech
* Do not edit the class manually.
*/
import * as runtime from '../runtime';
import type {
Info,
Version,
VersionFile,
Visibility,
Website,
WebsiteNew,
WebsiteUpdate,
} from '../models';
import {
InfoFromJSON,
InfoToJSON,
VersionFromJSON,
VersionToJSON,
VersionFileFromJSON,
VersionFileToJSON,
VisibilityFromJSON,
VisibilityToJSON,
WebsiteFromJSON,
WebsiteToJSON,
WebsiteNewFromJSON,
WebsiteNewToJSON,
WebsiteUpdateFromJSON,
WebsiteUpdateToJSON,
} from '../models';
export interface FileHashGetRequest {
hash: string;
}
export interface WebsiteSubdomainDeleteRequest {
subdomain: string;
}
export interface WebsiteSubdomainGetRequest {
subdomain: string;
}
export interface WebsiteSubdomainPatchRequest {
subdomain: string;
websiteUpdate: WebsiteUpdate;
}
export interface WebsiteSubdomainPutRequest {
subdomain: string;
websiteNew: WebsiteNew;
}
export interface WebsiteSubdomainUploadPostRequest {
subdomain: string;
body: Blob;
fallback?: string;
spa?: boolean;
versionData?: { [key: string]: string; };
}
export interface WebsiteSubdomainVersionVersionDeleteRequest {
subdomain: string;
version: number;
}
export interface WebsiteSubdomainVersionVersionFilesGetRequest {
subdomain: string;
version: number;
}
export interface WebsiteSubdomainVersionVersionGetRequest {
subdomain: string;
version: number;
}
export interface WebsiteSubdomainVersionsGetRequest {
subdomain: string;
}
export interface WebsitesAllGetRequest {
visibility?: Visibility;
}
export interface WebsitesGetRequest {
visibility?: Visibility;
}
/**
*
*/
export class DefaultApi extends runtime.BaseAPI {
/**
* Retrieve a file
*/
async fileHashGetRaw(requestParameters: FileHashGetRequest, initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise<runtime.ApiResponse<Blob>> {
if (requestParameters.hash === null || requestParameters.hash === undefined) {
throw new runtime.RequiredError('hash','Required parameter requestParameters.hash was null or undefined when calling fileHashGet.');
}
const queryParameters: any = {};
const headerParameters: runtime.HTTPHeaders = {};
const response = await this.request({
path: `/file/{hash}`.replace(`{${"hash"}}`, encodeURIComponent(String(requestParameters.hash))),
method: 'GET',
headers: headerParameters,
query: queryParameters,
}, initOverrides);
return new runtime.BlobApiResponse(response);
}
/**
* Retrieve a file
*/
async fileHashGet(requestParameters: FileHashGetRequest, initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise<Blob> {
const response = await this.fileHashGetRaw(requestParameters, initOverrides);
return await response.value();
}
/**
* Get information about your server
*/
async infoGetRaw(initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise<runtime.ApiResponse<Info>> {
const queryParameters: any = {};
const headerParameters: runtime.HTTPHeaders = {};
const response = await this.request({
path: `/info`,
method: 'GET',
headers: headerParameters,
query: queryParameters,
}, initOverrides);
return new runtime.JSONApiResponse(response, (jsonValue) => InfoFromJSON(jsonValue));
}
/**
* Get information about your server
*/
async infoGet(initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise<Info> {
const response = await this.infoGetRaw(initOverrides);
return await response.value();
}
/**
* Delete website
*/
async websiteSubdomainDeleteRaw(requestParameters: WebsiteSubdomainDeleteRequest, initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise<runtime.ApiResponse<void>> {
if (requestParameters.subdomain === null || requestParameters.subdomain === undefined) {
throw new runtime.RequiredError('subdomain','Required parameter requestParameters.subdomain was null or undefined when calling websiteSubdomainDelete.');
}
const queryParameters: any = {};
const headerParameters: runtime.HTTPHeaders = {};
if (this.configuration && this.configuration.apiKey) {
headerParameters["X-API-Key"] = this.configuration.apiKey("X-API-Key"); // ApiKeyAuthorization authentication
}
const response = await this.request({
path: `/website/{subdomain}`.replace(`{${"subdomain"}}`, encodeURIComponent(String(requestParameters.subdomain))),
method: 'DELETE',
headers: headerParameters,
query: queryParameters,
}, initOverrides);
return new runtime.VoidApiResponse(response);
}
/**
* Delete website
*/
async websiteSubdomainDelete(requestParameters: WebsiteSubdomainDeleteRequest, initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise<void> {
await this.websiteSubdomainDeleteRaw(requestParameters, initOverrides);
}
/**
* Get a website
*/
async websiteSubdomainGetRaw(requestParameters: WebsiteSubdomainGetRequest, initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise<runtime.ApiResponse<Website>> {
if (requestParameters.subdomain === null || requestParameters.subdomain === undefined) {
throw new runtime.RequiredError('subdomain','Required parameter requestParameters.subdomain was null or undefined when calling websiteSubdomainGet.');
}
const queryParameters: any = {};
const headerParameters: runtime.HTTPHeaders = {};
const response = await this.request({
path: `/website/{subdomain}`.replace(`{${"subdomain"}}`, encodeURIComponent(String(requestParameters.subdomain))),
method: 'GET',
headers: headerParameters,
query: queryParameters,
}, initOverrides);
return new runtime.JSONApiResponse(response, (jsonValue) => WebsiteFromJSON(jsonValue));
}
/**
* Get a website
*/
async websiteSubdomainGet(requestParameters: WebsiteSubdomainGetRequest, initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise<Website> {
const response = await this.websiteSubdomainGetRaw(requestParameters, initOverrides);
return await response.value();
}
/**
* Update website
*/
async websiteSubdomainPatchRaw(requestParameters: WebsiteSubdomainPatchRequest, initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise<runtime.ApiResponse<void>> {
if (requestParameters.subdomain === null || requestParameters.subdomain === undefined) {
throw new runtime.RequiredError('subdomain','Required parameter requestParameters.subdomain was null or undefined when calling websiteSubdomainPatch.');
}
if (requestParameters.websiteUpdate === null || requestParameters.websiteUpdate === undefined) {
throw new runtime.RequiredError('websiteUpdate','Required parameter requestParameters.websiteUpdate was null or undefined when calling websiteSubdomainPatch.');
}
const queryParameters: any = {};
const headerParameters: runtime.HTTPHeaders = {};
headerParameters['Content-Type'] = 'application/json; charset=utf-8';
if (this.configuration && this.configuration.apiKey) {
headerParameters["X-API-Key"] = this.configuration.apiKey("X-API-Key"); // ApiKeyAuthorization authentication
}
const response = await this.request({
path: `/website/{subdomain}`.replace(`{${"subdomain"}}`, encodeURIComponent(String(requestParameters.subdomain))),
method: 'PATCH',
headers: headerParameters,
query: queryParameters,
body: WebsiteUpdateToJSON(requestParameters.websiteUpdate),
}, initOverrides);
return new runtime.VoidApiResponse(response);
}
/**
* Update website
*/
async websiteSubdomainPatch(requestParameters: WebsiteSubdomainPatchRequest, initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise<void> {
await this.websiteSubdomainPatchRaw(requestParameters, initOverrides);
}
/**
* Create a new website
*/
async websiteSubdomainPutRaw(requestParameters: WebsiteSubdomainPutRequest, initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise<runtime.ApiResponse<void>> {
if (requestParameters.subdomain === null || requestParameters.subdomain === undefined) {
throw new runtime.RequiredError('subdomain','Required parameter requestParameters.subdomain was null or undefined when calling websiteSubdomainPut.');
}
if (requestParameters.websiteNew === null || requestParameters.websiteNew === undefined) {
throw new runtime.RequiredError('websiteNew','Required parameter requestParameters.websiteNew was null or undefined when calling websiteSubdomainPut.');
}
const queryParameters: any = {};
const headerParameters: runtime.HTTPHeaders = {};
headerParameters['Content-Type'] = 'application/json; charset=utf-8';
if (this.configuration && this.configuration.apiKey) {
headerParameters["X-API-Key"] = this.configuration.apiKey("X-API-Key"); // ApiKeyAuthorization authentication
}
const response = await this.request({
path: `/website/{subdomain}`.replace(`{${"subdomain"}}`, encodeURIComponent(String(requestParameters.subdomain))),
method: 'PUT',
headers: headerParameters,
query: queryParameters,
body: WebsiteNewToJSON(requestParameters.websiteNew),
}, initOverrides);
return new runtime.VoidApiResponse(response);
}
/**
* Create a new website
*/
async websiteSubdomainPut(requestParameters: WebsiteSubdomainPutRequest, initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise<void> {
await this.websiteSubdomainPutRaw(requestParameters, initOverrides);
}
/**
* Upload a new version
*/
async websiteSubdomainUploadPostRaw(requestParameters: WebsiteSubdomainUploadPostRequest, initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise<runtime.ApiResponse<void>> {
if (requestParameters.subdomain === null || requestParameters.subdomain === undefined) {
throw new runtime.RequiredError('subdomain','Required parameter requestParameters.subdomain was null or undefined when calling websiteSubdomainUploadPost.');
}
if (requestParameters.body === null || requestParameters.body === undefined) {
throw new runtime.RequiredError('body','Required parameter requestParameters.body was null or undefined when calling websiteSubdomainUploadPost.');
}
const queryParameters: any = {};
if (requestParameters.fallback !== undefined) {
queryParameters['fallback'] = requestParameters.fallback;
}
if (requestParameters.spa !== undefined) {
queryParameters['spa'] = requestParameters.spa;
}
if (requestParameters.versionData !== undefined) {
queryParameters['version_data'] = requestParameters.versionData;
}
const headerParameters: runtime.HTTPHeaders = {};
headerParameters['Content-Type'] = 'application/octet-stream';
if (this.configuration && this.configuration.apiKey) {
headerParameters["X-API-Key"] = this.configuration.apiKey("X-API-Key"); // ApiKeyAuthorization authentication
}
const response = await this.request({
path: `/website/{subdomain}/upload`.replace(`{${"subdomain"}}`, encodeURIComponent(String(requestParameters.subdomain))),
method: 'POST',
headers: headerParameters,
query: queryParameters,
body: requestParameters.body as any,
}, initOverrides);
return new runtime.VoidApiResponse(response);
}
/**
* Upload a new version
*/
async websiteSubdomainUploadPost(requestParameters: WebsiteSubdomainUploadPostRequest, initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise<void> {
await this.websiteSubdomainUploadPostRaw(requestParameters, initOverrides);
}
/**
* Delete version
*/
async websiteSubdomainVersionVersionDeleteRaw(requestParameters: WebsiteSubdomainVersionVersionDeleteRequest, initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise<runtime.ApiResponse<void>> {
if (requestParameters.subdomain === null || requestParameters.subdomain === undefined) {
throw new runtime.RequiredError('subdomain','Required parameter requestParameters.subdomain was null or undefined when calling websiteSubdomainVersionVersionDelete.');
}
if (requestParameters.version === null || requestParameters.version === undefined) {
throw new runtime.RequiredError('version','Required parameter requestParameters.version was null or undefined when calling websiteSubdomainVersionVersionDelete.');
}
const queryParameters: any = {};
const headerParameters: runtime.HTTPHeaders = {};
if (this.configuration && this.configuration.apiKey) {
headerParameters["X-API-Key"] = this.configuration.apiKey("X-API-Key"); // ApiKeyAuthorization authentication
}
const response = await this.request({
path: `/website/{subdomain}/version/{version}`.replace(`{${"subdomain"}}`, encodeURIComponent(String(requestParameters.subdomain))).replace(`{${"version"}}`, encodeURIComponent(String(requestParameters.version))),
method: 'DELETE',
headers: headerParameters,
query: queryParameters,
}, initOverrides);
return new runtime.VoidApiResponse(response);
}
/**
* Delete version
*/
async websiteSubdomainVersionVersionDelete(requestParameters: WebsiteSubdomainVersionVersionDeleteRequest, initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise<void> {
await this.websiteSubdomainVersionVersionDeleteRaw(requestParameters, initOverrides);
}
/**
* Get version files
*/
async websiteSubdomainVersionVersionFilesGetRaw(requestParameters: WebsiteSubdomainVersionVersionFilesGetRequest, initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise<runtime.ApiResponse<Array<VersionFile>>> {
if (requestParameters.subdomain === null || requestParameters.subdomain === undefined) {
throw new runtime.RequiredError('subdomain','Required parameter requestParameters.subdomain was null or undefined when calling websiteSubdomainVersionVersionFilesGet.');
}
if (requestParameters.version === null || requestParameters.version === undefined) {
throw new runtime.RequiredError('version','Required parameter requestParameters.version was null or undefined when calling websiteSubdomainVersionVersionFilesGet.');
}
const queryParameters: any = {};
const headerParameters: runtime.HTTPHeaders = {};
const response = await this.request({
path: `/website/{subdomain}/version/{version}/files`.replace(`{${"subdomain"}}`, encodeURIComponent(String(requestParameters.subdomain))).replace(`{${"version"}}`, encodeURIComponent(String(requestParameters.version))),
method: 'GET',
headers: headerParameters,
query: queryParameters,
}, initOverrides);
return new runtime.JSONApiResponse(response, (jsonValue) => jsonValue.map(VersionFileFromJSON));
}
/**
* Get version files
*/
async websiteSubdomainVersionVersionFilesGet(requestParameters: WebsiteSubdomainVersionVersionFilesGetRequest, initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise<Array<VersionFile>> {
const response = await this.websiteSubdomainVersionVersionFilesGetRaw(requestParameters, initOverrides);
return await response.value();
}
/**
* Get version
*/
async websiteSubdomainVersionVersionGetRaw(requestParameters: WebsiteSubdomainVersionVersionGetRequest, initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise<runtime.ApiResponse<Version>> {
if (requestParameters.subdomain === null || requestParameters.subdomain === undefined) {
throw new runtime.RequiredError('subdomain','Required parameter requestParameters.subdomain was null or undefined when calling websiteSubdomainVersionVersionGet.');
}
if (requestParameters.version === null || requestParameters.version === undefined) {
throw new runtime.RequiredError('version','Required parameter requestParameters.version was null or undefined when calling websiteSubdomainVersionVersionGet.');
}
const queryParameters: any = {};
const headerParameters: runtime.HTTPHeaders = {};
const response = await this.request({
path: `/website/{subdomain}/version/{version}`.replace(`{${"subdomain"}}`, encodeURIComponent(String(requestParameters.subdomain))).replace(`{${"version"}}`, encodeURIComponent(String(requestParameters.version))),
method: 'GET',
headers: headerParameters,
query: queryParameters,
}, initOverrides);
return new runtime.JSONApiResponse(response, (jsonValue) => VersionFromJSON(jsonValue));
}
/**
* Get version
*/
async websiteSubdomainVersionVersionGet(requestParameters: WebsiteSubdomainVersionVersionGetRequest, initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise<Version> {
const response = await this.websiteSubdomainVersionVersionGetRaw(requestParameters, initOverrides);
return await response.value();
}
/**
* Get website versions
*/
async websiteSubdomainVersionsGetRaw(requestParameters: WebsiteSubdomainVersionsGetRequest, initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise<runtime.ApiResponse<Array<Version>>> {
if (requestParameters.subdomain === null || requestParameters.subdomain === undefined) {
throw new runtime.RequiredError('subdomain','Required parameter requestParameters.subdomain was null or undefined when calling websiteSubdomainVersionsGet.');
}
const queryParameters: any = {};
const headerParameters: runtime.HTTPHeaders = {};
const response = await this.request({
path: `/website/{subdomain}/versions`.replace(`{${"subdomain"}}`, encodeURIComponent(String(requestParameters.subdomain))),
method: 'GET',
headers: headerParameters,
query: queryParameters,
}, initOverrides);
return new runtime.JSONApiResponse(response, (jsonValue) => jsonValue.map(VersionFromJSON));
}
/**
* Get website versions
*/
async websiteSubdomainVersionsGet(requestParameters: WebsiteSubdomainVersionsGetRequest, initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise<Array<Version>> {
const response = await this.websiteSubdomainVersionsGetRaw(requestParameters, initOverrides);
return await response.value();
}
/**
* Returns all websites from Talon\'s database (including hidden ones, if the current user has access to them). This endpoint requires authentication (use the `/websites` endpoint for unauthenticated users).
* Get all websites
*/
async websitesAllGetRaw(requestParameters: WebsitesAllGetRequest, initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise<runtime.ApiResponse<Array<Website>>> {
const queryParameters: any = {};
if (requestParameters.visibility !== undefined) {
queryParameters['visibility'] = requestParameters.visibility;
}
const headerParameters: runtime.HTTPHeaders = {};
if (this.configuration && this.configuration.apiKey) {
headerParameters["X-API-Key"] = this.configuration.apiKey("X-API-Key"); // ApiKeyAuthorization authentication
}
const response = await this.request({
path: `/websitesAll`,
method: 'GET',
headers: headerParameters,
query: queryParameters,
}, initOverrides);
return new runtime.JSONApiResponse(response, (jsonValue) => jsonValue.map(WebsiteFromJSON));
}
/**
* Returns all websites from Talon\'s database (including hidden ones, if the current user has access to them). This endpoint requires authentication (use the `/websites` endpoint for unauthenticated users).
* Get all websites
*/
async websitesAllGet(requestParameters: WebsitesAllGetRequest = {}, initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise<Array<Website>> {
const response = await this.websitesAllGetRaw(requestParameters, initOverrides);
return await response.value();
}
/**
* Returns all publicly listed websites (visibility != `hidden`)
* Get all publicly listed websites
*/
async websitesGetRaw(requestParameters: WebsitesGetRequest, initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise<runtime.ApiResponse<Array<Website>>> {
const queryParameters: any = {};
if (requestParameters.visibility !== undefined) {
queryParameters['visibility'] = requestParameters.visibility;
}
const headerParameters: runtime.HTTPHeaders = {};
const response = await this.request({
path: `/websites`,
method: 'GET',
headers: headerParameters,
query: queryParameters,
}, initOverrides);
return new runtime.JSONApiResponse(response, (jsonValue) => jsonValue.map(WebsiteFromJSON));
}
/**
* Returns all publicly listed websites (visibility != `hidden`)
* Get all publicly listed websites
*/
async websitesGet(requestParameters: WebsitesGetRequest = {}, initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise<Array<Website>> {
const response = await this.websitesGetRaw(requestParameters, initOverrides);
return await response.value();
}
}

View file

@ -0,0 +1,3 @@
/* tslint:disable */
/* eslint-disable */
export * from './DefaultApi';

View file

@ -0,0 +1,5 @@
/* tslint:disable */
/* eslint-disable */
export * from './runtime';
export * from './apis';
export * from './models';

View file

@ -0,0 +1,96 @@
/* tslint:disable */
/* eslint-disable */
/**
* Talon
* API for the Talon static site management system
*
* The version of the OpenAPI document: 0.1.0
*
*
* NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
* https://openapi-generator.tech
* Do not edit the class manually.
*/
import { exists, mapValues } from '../runtime';
import type { InfoStats } from './InfoStats';
import {
InfoStatsFromJSON,
InfoStatsFromJSONTyped,
InfoStatsToJSON,
} from './InfoStats';
import type { InfoVersion } from './InfoVersion';
import {
InfoVersionFromJSON,
InfoVersionFromJSONTyped,
InfoVersionToJSON,
} from './InfoVersion';
/**
* Server information
* @export
* @interface Info
*/
export interface Info {
/**
*
* @type {InfoStats}
* @memberof Info
*/
stats: InfoStats;
/**
*
* @type {InfoVersion}
* @memberof Info
*/
version: InfoVersion;
/**
* Current uptime of the server in seconds
* @type {number}
* @memberof Info
*/
uptime: number;
}
/**
* Check if a given object implements the Info interface.
*/
export function instanceOfInfo(value: object): boolean {
let isInstance = true;
isInstance = isInstance && "stats" in value;
isInstance = isInstance && "version" in value;
isInstance = isInstance && "uptime" in value;
return isInstance;
}
export function InfoFromJSON(json: any): Info {
return InfoFromJSONTyped(json, false);
}
export function InfoFromJSONTyped(json: any, ignoreDiscriminator: boolean): Info {
if ((json === undefined) || (json === null)) {
return json;
}
return {
'stats': InfoStatsFromJSON(json['stats']),
'version': InfoVersionFromJSON(json['version']),
'uptime': json['uptime'],
};
}
export function InfoToJSON(value?: Info | null): any {
if (value === undefined) {
return undefined;
}
if (value === null) {
return null;
}
return {
'stats': InfoStatsToJSON(value.stats),
'version': InfoVersionToJSON(value.version),
'uptime': value.uptime,
};
}

View file

@ -0,0 +1,83 @@
/* tslint:disable */
/* eslint-disable */
/**
* Talon
* API for the Talon static site management system
*
* The version of the OpenAPI document: 0.1.0
*
*
* NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
* https://openapi-generator.tech
* Do not edit the class manually.
*/
import { exists, mapValues } from '../runtime';
/**
*
* @export
* @interface InfoStats
*/
export interface InfoStats {
/**
* Number of websites
* @type {number}
* @memberof InfoStats
*/
nWebsites: number;
/**
* Number of unique files
* @type {number}
* @memberof InfoStats
*/
nFiles: number;
/**
* Amount of used storage space (in bytes)
* @type {number}
* @memberof InfoStats
*/
storageUsed: number;
}
/**
* Check if a given object implements the InfoStats interface.
*/
export function instanceOfInfoStats(value: object): boolean {
let isInstance = true;
isInstance = isInstance && "nWebsites" in value;
isInstance = isInstance && "nFiles" in value;
isInstance = isInstance && "storageUsed" in value;
return isInstance;
}
export function InfoStatsFromJSON(json: any): InfoStats {
return InfoStatsFromJSONTyped(json, false);
}
export function InfoStatsFromJSONTyped(json: any, ignoreDiscriminator: boolean): InfoStats {
if ((json === undefined) || (json === null)) {
return json;
}
return {
'nWebsites': json['n_websites'],
'nFiles': json['n_files'],
'storageUsed': json['storage_used'],
};
}
export function InfoStatsToJSON(value?: InfoStats | null): any {
if (value === undefined) {
return undefined;
}
if (value === null) {
return null;
}
return {
'n_websites': value.nWebsites,
'n_files': value.nFiles,
'storage_used': value.storageUsed,
};
}

View file

@ -0,0 +1,110 @@
/* tslint:disable */
/* eslint-disable */
/**
* Talon
* API for the Talon static site management system
*
* The version of the OpenAPI document: 0.1.0
*
*
* NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
* https://openapi-generator.tech
* Do not edit the class manually.
*/
import { exists, mapValues } from '../runtime';
/**
*
* @export
* @interface InfoVersion
*/
export interface InfoVersion {
/**
* Talon version
* @type {string}
* @memberof InfoVersion
*/
version: string;
/**
* Commit hash
* @type {string}
* @memberof InfoVersion
*/
commit: string;
/**
* Commit date
* @type {Date}
* @memberof InfoVersion
*/
commitDate: Date;
/**
* Rust version
* @type {string}
* @memberof InfoVersion
*/
rustVersion: string;
/**
* Build target (OS and architecture)
* @type {string}
* @memberof InfoVersion
*/
buildTarget: string;
/**
* Rust build mode (`debug` / `release`)
* @type {string}
* @memberof InfoVersion
*/
buildMode: string;
}
/**
* Check if a given object implements the InfoVersion interface.
*/
export function instanceOfInfoVersion(value: object): boolean {
let isInstance = true;
isInstance = isInstance && "version" in value;
isInstance = isInstance && "commit" in value;
isInstance = isInstance && "commitDate" in value;
isInstance = isInstance && "rustVersion" in value;
isInstance = isInstance && "buildTarget" in value;
isInstance = isInstance && "buildMode" in value;
return isInstance;
}
export function InfoVersionFromJSON(json: any): InfoVersion {
return InfoVersionFromJSONTyped(json, false);
}
export function InfoVersionFromJSONTyped(json: any, ignoreDiscriminator: boolean): InfoVersion {
if ((json === undefined) || (json === null)) {
return json;
}
return {
'version': json['version'],
'commit': json['commit'],
'commitDate': (new Date(json['commit_date'])),
'rustVersion': json['rust_version'],
'buildTarget': json['build_target'],
'buildMode': json['build_mode'],
};
}
export function InfoVersionToJSON(value?: InfoVersion | null): any {
if (value === undefined) {
return undefined;
}
if (value === null) {
return null;
}
return {
'version': value.version,
'commit': value.commit,
'commit_date': (value.commitDate.toISOString()),
'rust_version': value.rustVersion,
'build_target': value.buildTarget,
'build_mode': value.buildMode,
};
}

View file

@ -0,0 +1,41 @@
/* tslint:disable */
/* eslint-disable */
/**
* Talon
* API for the Talon static site management system
*
* The version of the OpenAPI document: 0.1.0
*
*
* NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
* https://openapi-generator.tech
* Do not edit the class manually.
*/
/**
*
* @export
*/
export const SourceIcon = {
Link: 'link',
Git: 'git',
Github: 'github',
Gitlab: 'gitlab',
Gitea: 'gitea',
Bitbucket: 'bitbucket'
} as const;
export type SourceIcon = typeof SourceIcon[keyof typeof SourceIcon];
export function SourceIconFromJSON(json: any): SourceIcon {
return SourceIconFromJSONTyped(json, false);
}
export function SourceIconFromJSONTyped(json: any, ignoreDiscriminator: boolean): SourceIcon {
return json as SourceIcon;
}
export function SourceIconToJSON(value?: SourceIcon | null): any {
return value as any;
}

View file

@ -0,0 +1,83 @@
/* tslint:disable */
/* eslint-disable */
/**
* Talon
* API for the Talon static site management system
*
* The version of the OpenAPI document: 0.1.0
*
*
* NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
* https://openapi-generator.tech
* Do not edit the class manually.
*/
import { exists, mapValues } from '../runtime';
/**
* Stats about your Talon instance
* @export
* @interface Stats
*/
export interface Stats {
/**
* Number of websites
* @type {number}
* @memberof Stats
*/
nWebsites: number;
/**
* Number of unique files
* @type {number}
* @memberof Stats
*/
nFiles: number;
/**
* Amount of used storage space (in bytes)
* @type {number}
* @memberof Stats
*/
storageUsed: number;
}
/**
* Check if a given object implements the Stats interface.
*/
export function instanceOfStats(value: object): boolean {
let isInstance = true;
isInstance = isInstance && "nWebsites" in value;
isInstance = isInstance && "nFiles" in value;
isInstance = isInstance && "storageUsed" in value;
return isInstance;
}
export function StatsFromJSON(json: any): Stats {
return StatsFromJSONTyped(json, false);
}
export function StatsFromJSONTyped(json: any, ignoreDiscriminator: boolean): Stats {
if ((json === undefined) || (json === null)) {
return json;
}
return {
'nWebsites': json['n_websites'],
'nFiles': json['n_files'],
'storageUsed': json['storage_used'],
};
}
export function StatsToJSON(value?: Stats | null): any {
if (value === undefined) {
return undefined;
}
if (value === null) {
return null;
}
return {
'n_websites': value.nWebsites,
'n_files': value.nFiles,
'storage_used': value.storageUsed,
};
}

View file

@ -0,0 +1,86 @@
/* tslint:disable */
/* eslint-disable */
/**
* Talon
* API for the Talon static site management system
*
* The version of the OpenAPI document: 0.1.0
*
*
* NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
* https://openapi-generator.tech
* Do not edit the class manually.
*/
import { exists, mapValues } from '../runtime';
/**
* Website version
* @export
* @interface Version
*/
export interface Version {
/**
* Version ID
* @type {number}
* @memberof Version
*/
id: number;
/**
* Version creation date
* @type {Date}
* @memberof Version
*/
createdAt: Date;
/**
* Associated version data
*
* This is an arbitrary string map that can hold build information and other stuff
* and will be displayed in the site info dialog.
* @type {{ [key: string]: string; }}
* @memberof Version
*/
data: { [key: string]: string; };
}
/**
* Check if a given object implements the Version interface.
*/
export function instanceOfVersion(value: object): boolean {
let isInstance = true;
isInstance = isInstance && "id" in value;
isInstance = isInstance && "createdAt" in value;
isInstance = isInstance && "data" in value;
return isInstance;
}
export function VersionFromJSON(json: any): Version {
return VersionFromJSONTyped(json, false);
}
export function VersionFromJSONTyped(json: any, ignoreDiscriminator: boolean): Version {
if ((json === undefined) || (json === null)) {
return json;
}
return {
'id': json['id'],
'createdAt': (new Date(json['created_at'])),
'data': json['data'],
};
}
export function VersionToJSON(value?: Version | null): any {
if (value === undefined) {
return undefined;
}
if (value === null) {
return null;
}
return {
'id': value.id,
'created_at': (value.createdAt.toISOString()),
'data': value.data,
};
}

View file

@ -0,0 +1,82 @@
/* tslint:disable */
/* eslint-disable */
/**
* Talon
* API for the Talon static site management system
*
* The version of the OpenAPI document: 0.1.0
*
*
* NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
* https://openapi-generator.tech
* Do not edit the class manually.
*/
import { exists, mapValues } from '../runtime';
/**
* Website file
* @export
* @interface VersionFile
*/
export interface VersionFile {
/**
* File path
* @type {string}
* @memberof VersionFile
*/
path: string;
/**
* File hash
* @type {string}
* @memberof VersionFile
*/
hash: string;
/**
* MIME file type
* @type {string}
* @memberof VersionFile
*/
mime?: string;
}
/**
* Check if a given object implements the VersionFile interface.
*/
export function instanceOfVersionFile(value: object): boolean {
let isInstance = true;
isInstance = isInstance && "path" in value;
isInstance = isInstance && "hash" in value;
return isInstance;
}
export function VersionFileFromJSON(json: any): VersionFile {
return VersionFileFromJSONTyped(json, false);
}
export function VersionFileFromJSONTyped(json: any, ignoreDiscriminator: boolean): VersionFile {
if ((json === undefined) || (json === null)) {
return json;
}
return {
'path': json['path'],
'hash': json['hash'],
'mime': !exists(json, 'mime') ? undefined : json['mime'],
};
}
export function VersionFileToJSON(value?: VersionFile | null): any {
if (value === undefined) {
return undefined;
}
if (value === null) {
return null;
}
return {
'path': value.path,
'hash': value.hash,
'mime': value.mime,
};
}

View file

@ -0,0 +1,110 @@
/* tslint:disable */
/* eslint-disable */
/**
* Talon
* API for the Talon static site management system
*
* The version of the OpenAPI document: 0.1.0
*
*
* NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
* https://openapi-generator.tech
* Do not edit the class manually.
*/
import { exists, mapValues } from '../runtime';
/**
* Information about a Talon version
* @export
* @interface VersionInfo
*/
export interface VersionInfo {
/**
* Talon version
* @type {string}
* @memberof VersionInfo
*/
version: string;
/**
* Commit hash
* @type {string}
* @memberof VersionInfo
*/
commit: string;
/**
* Commit date
* @type {Date}
* @memberof VersionInfo
*/
commitDate: Date;
/**
* Rust version
* @type {string}
* @memberof VersionInfo
*/
rustVersion: string;
/**
* Build target (OS and architecture)
* @type {string}
* @memberof VersionInfo
*/
buildTarget: string;
/**
* Rust build mode (`debug` / `release`)
* @type {string}
* @memberof VersionInfo
*/
buildMode: string;
}
/**
* Check if a given object implements the VersionInfo interface.
*/
export function instanceOfVersionInfo(value: object): boolean {
let isInstance = true;
isInstance = isInstance && "version" in value;
isInstance = isInstance && "commit" in value;
isInstance = isInstance && "commitDate" in value;
isInstance = isInstance && "rustVersion" in value;
isInstance = isInstance && "buildTarget" in value;
isInstance = isInstance && "buildMode" in value;
return isInstance;
}
export function VersionInfoFromJSON(json: any): VersionInfo {
return VersionInfoFromJSONTyped(json, false);
}
export function VersionInfoFromJSONTyped(json: any, ignoreDiscriminator: boolean): VersionInfo {
if ((json === undefined) || (json === null)) {
return json;
}
return {
'version': json['version'],
'commit': json['commit'],
'commitDate': (new Date(json['commit_date'])),
'rustVersion': json['rust_version'],
'buildTarget': json['build_target'],
'buildMode': json['build_mode'],
};
}
export function VersionInfoToJSON(value?: VersionInfo | null): any {
if (value === undefined) {
return undefined;
}
if (value === null) {
return null;
}
return {
'version': value.version,
'commit': value.commit,
'commit_date': (value.commitDate.toISOString()),
'rust_version': value.rustVersion,
'build_target': value.buildTarget,
'build_mode': value.buildMode,
};
}

View file

@ -0,0 +1,38 @@
/* tslint:disable */
/* eslint-disable */
/**
* Talon
* API for the Talon static site management system
*
* The version of the OpenAPI document: 0.1.0
*
*
* NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
* https://openapi-generator.tech
* Do not edit the class manually.
*/
/**
*
* @export
*/
export const Visibility = {
Featured: 'featured',
Searchable: 'searchable',
Hidden: 'hidden'
} as const;
export type Visibility = typeof Visibility[keyof typeof Visibility];
export function VisibilityFromJSON(json: any): Visibility {
return VisibilityFromJSONTyped(json, false);
}
export function VisibilityFromJSONTyped(json: any, ignoreDiscriminator: boolean): Visibility {
return json as Visibility;
}
export function VisibilityToJSON(value?: Visibility | null): any {
return value as any;
}

View file

@ -0,0 +1,139 @@
/* tslint:disable */
/* eslint-disable */
/**
* Talon
* API for the Talon static site management system
*
* The version of the OpenAPI document: 0.1.0
*
*
* NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
* https://openapi-generator.tech
* Do not edit the class manually.
*/
import { exists, mapValues } from '../runtime';
import type { SourceIcon } from './SourceIcon';
import {
SourceIconFromJSON,
SourceIconFromJSONTyped,
SourceIconToJSON,
} from './SourceIcon';
import type { Visibility } from './Visibility';
import {
VisibilityFromJSON,
VisibilityFromJSONTyped,
VisibilityToJSON,
} from './Visibility';
/**
* Website
* @export
* @interface Website
*/
export interface Website {
/**
* Website subdomain
* @type {string}
* @memberof Website
*/
subdomain: string;
/**
* Website name
* @type {string}
* @memberof Website
*/
name: string;
/**
* Website creation date
* @type {Date}
* @memberof Website
*/
createdAt: Date;
/**
* Latest version ID
* @type {number}
* @memberof Website
*/
latestVersion?: number;
/**
* Color of the page icon
*
* Format: `#7935df`
* @type {string}
* @memberof Website
*/
color?: string;
/**
*
* @type {Visibility}
* @memberof Website
*/
visibility: Visibility;
/**
* Link to the source of the page
* @type {string}
* @memberof Website
*/
sourceUrl?: string;
/**
*
* @type {SourceIcon}
* @memberof Website
*/
sourceIcon?: SourceIcon;
}
/**
* Check if a given object implements the Website interface.
*/
export function instanceOfWebsite(value: object): boolean {
let isInstance = true;
isInstance = isInstance && "subdomain" in value;
isInstance = isInstance && "name" in value;
isInstance = isInstance && "createdAt" in value;
isInstance = isInstance && "visibility" in value;
return isInstance;
}
export function WebsiteFromJSON(json: any): Website {
return WebsiteFromJSONTyped(json, false);
}
export function WebsiteFromJSONTyped(json: any, ignoreDiscriminator: boolean): Website {
if ((json === undefined) || (json === null)) {
return json;
}
return {
'subdomain': json['subdomain'],
'name': json['name'],
'createdAt': (new Date(json['created_at'])),
'latestVersion': !exists(json, 'latest_version') ? undefined : json['latest_version'],
'color': !exists(json, 'color') ? undefined : json['color'],
'visibility': VisibilityFromJSON(json['visibility']),
'sourceUrl': !exists(json, 'source_url') ? undefined : json['source_url'],
'sourceIcon': !exists(json, 'source_icon') ? undefined : SourceIconFromJSON(json['source_icon']),
};
}
export function WebsiteToJSON(value?: Website | null): any {
if (value === undefined) {
return undefined;
}
if (value === null) {
return null;
}
return {
'subdomain': value.subdomain,
'name': value.name,
'created_at': (value.createdAt.toISOString()),
'latest_version': value.latestVersion,
'color': value.color,
'visibility': VisibilityToJSON(value.visibility),
'source_url': value.sourceUrl,
'source_icon': SourceIconToJSON(value.sourceIcon),
};
}

View file

@ -0,0 +1,110 @@
/* tslint:disable */
/* eslint-disable */
/**
* Talon
* API for the Talon static site management system
*
* The version of the OpenAPI document: 0.1.0
*
*
* NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
* https://openapi-generator.tech
* Do not edit the class manually.
*/
import { exists, mapValues } from '../runtime';
import type { SourceIcon } from './SourceIcon';
import {
SourceIconFromJSON,
SourceIconFromJSONTyped,
SourceIconToJSON,
} from './SourceIcon';
import type { Visibility } from './Visibility';
import {
VisibilityFromJSON,
VisibilityFromJSONTyped,
VisibilityToJSON,
} from './Visibility';
/**
* Create a new website
* @export
* @interface WebsiteNew
*/
export interface WebsiteNew {
/**
* Website name
* @type {string}
* @memberof WebsiteNew
*/
name: string;
/**
* Color of the page icon
* @type {string}
* @memberof WebsiteNew
*/
color?: string;
/**
*
* @type {Visibility}
* @memberof WebsiteNew
*/
visibility?: Visibility;
/**
* Link to the source of the page
* @type {string}
* @memberof WebsiteNew
*/
sourceUrl?: string;
/**
*
* @type {SourceIcon}
* @memberof WebsiteNew
*/
sourceIcon?: SourceIcon;
}
/**
* Check if a given object implements the WebsiteNew interface.
*/
export function instanceOfWebsiteNew(value: object): boolean {
let isInstance = true;
isInstance = isInstance && "name" in value;
return isInstance;
}
export function WebsiteNewFromJSON(json: any): WebsiteNew {
return WebsiteNewFromJSONTyped(json, false);
}
export function WebsiteNewFromJSONTyped(json: any, ignoreDiscriminator: boolean): WebsiteNew {
if ((json === undefined) || (json === null)) {
return json;
}
return {
'name': json['name'],
'color': !exists(json, 'color') ? undefined : json['color'],
'visibility': !exists(json, 'visibility') ? undefined : VisibilityFromJSON(json['visibility']),
'sourceUrl': !exists(json, 'source_url') ? undefined : json['source_url'],
'sourceIcon': !exists(json, 'source_icon') ? undefined : SourceIconFromJSON(json['source_icon']),
};
}
export function WebsiteNewToJSON(value?: WebsiteNew | null): any {
if (value === undefined) {
return undefined;
}
if (value === null) {
return null;
}
return {
'name': value.name,
'color': value.color,
'visibility': VisibilityToJSON(value.visibility),
'source_url': value.sourceUrl,
'source_icon': SourceIconToJSON(value.sourceIcon),
};
}

View file

@ -0,0 +1,111 @@
/* tslint:disable */
/* eslint-disable */
/**
* Talon
* API for the Talon static site management system
*
* The version of the OpenAPI document: 0.1.0
*
*
* NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
* https://openapi-generator.tech
* Do not edit the class manually.
*/
import { exists, mapValues } from '../runtime';
import type { SourceIcon } from './SourceIcon';
import {
SourceIconFromJSON,
SourceIconFromJSONTyped,
SourceIconToJSON,
} from './SourceIcon';
import type { Visibility } from './Visibility';
import {
VisibilityFromJSON,
VisibilityFromJSONTyped,
VisibilityToJSON,
} from './Visibility';
/**
* Update a website with the contained values
*
* Values set to `None` remain unchanged.
* @export
* @interface WebsiteUpdate
*/
export interface WebsiteUpdate {
/**
* Website name
* @type {string}
* @memberof WebsiteUpdate
*/
name?: string;
/**
* Color of the page icon
* @type {string}
* @memberof WebsiteUpdate
*/
color?: string;
/**
*
* @type {Visibility}
* @memberof WebsiteUpdate
*/
visibility?: Visibility;
/**
* Link to the source of the page
* @type {string}
* @memberof WebsiteUpdate
*/
sourceUrl?: string;
/**
*
* @type {SourceIcon}
* @memberof WebsiteUpdate
*/
sourceIcon?: SourceIcon;
}
/**
* Check if a given object implements the WebsiteUpdate interface.
*/
export function instanceOfWebsiteUpdate(value: object): boolean {
let isInstance = true;
return isInstance;
}
export function WebsiteUpdateFromJSON(json: any): WebsiteUpdate {
return WebsiteUpdateFromJSONTyped(json, false);
}
export function WebsiteUpdateFromJSONTyped(json: any, ignoreDiscriminator: boolean): WebsiteUpdate {
if ((json === undefined) || (json === null)) {
return json;
}
return {
'name': !exists(json, 'name') ? undefined : json['name'],
'color': !exists(json, 'color') ? undefined : json['color'],
'visibility': !exists(json, 'visibility') ? undefined : VisibilityFromJSON(json['visibility']),
'sourceUrl': !exists(json, 'source_url') ? undefined : json['source_url'],
'sourceIcon': !exists(json, 'source_icon') ? undefined : SourceIconFromJSON(json['source_icon']),
};
}
export function WebsiteUpdateToJSON(value?: WebsiteUpdate | null): any {
if (value === undefined) {
return undefined;
}
if (value === null) {
return null;
}
return {
'name': value.name,
'color': value.color,
'visibility': VisibilityToJSON(value.visibility),
'source_url': value.sourceUrl,
'source_icon': SourceIconToJSON(value.sourceIcon),
};
}

View file

@ -0,0 +1,14 @@
/* tslint:disable */
/* eslint-disable */
export * from './Info';
export * from './InfoStats';
export * from './InfoVersion';
export * from './SourceIcon';
export * from './Stats';
export * from './Version';
export * from './VersionFile';
export * from './VersionInfo';
export * from './Visibility';
export * from './Website';
export * from './WebsiteNew';
export * from './WebsiteUpdate';

View file

@ -0,0 +1,407 @@
/* tslint:disable */
/* eslint-disable */
/**
* Talon
* API for the Talon static site management system
*
* The version of the OpenAPI document: 0.1.0
*
*
* NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
* https://openapi-generator.tech
* Do not edit the class manually.
*/
export const BASE_PATH = "http://localhost".replace(/\/+$/, "");
export interface ConfigurationParameters {
basePath?: string; // override base path
fetchApi?: FetchAPI; // override for fetch implementation
middleware?: Middleware[]; // middleware to apply before/after fetch requests
queryParamsStringify?: (params: HTTPQuery) => string; // stringify function for query strings
username?: string; // parameter for basic security
password?: string; // parameter for basic security
apiKey?: string | ((name: string) => string); // parameter for apiKey security
accessToken?: string | Promise<string> | ((name?: string, scopes?: string[]) => string | Promise<string>); // parameter for oauth2 security
headers?: HTTPHeaders; //header params we want to use on every request
credentials?: RequestCredentials; //value for the credentials param we want to use on each request
}
export class Configuration {
constructor(private configuration: ConfigurationParameters = {}) {}
set config(configuration: Configuration) {
this.configuration = configuration;
}
get basePath(): string {
return this.configuration.basePath != null ? this.configuration.basePath : BASE_PATH;
}
get fetchApi(): FetchAPI | undefined {
return this.configuration.fetchApi;
}
get middleware(): Middleware[] {
return this.configuration.middleware || [];
}
get queryParamsStringify(): (params: HTTPQuery) => string {
return this.configuration.queryParamsStringify || querystring;
}
get username(): string | undefined {
return this.configuration.username;
}
get password(): string | undefined {
return this.configuration.password;
}
get apiKey(): ((name: string) => string) | undefined {
const apiKey = this.configuration.apiKey;
if (apiKey) {
return typeof apiKey === 'function' ? apiKey : () => apiKey;
}
return undefined;
}
get accessToken(): ((name?: string, scopes?: string[]) => string | Promise<string>) | undefined {
const accessToken = this.configuration.accessToken;
if (accessToken) {
return typeof accessToken === 'function' ? accessToken : async () => accessToken;
}
return undefined;
}
get headers(): HTTPHeaders | undefined {
return this.configuration.headers;
}
get credentials(): RequestCredentials | undefined {
return this.configuration.credentials;
}
}
export const DefaultConfig = new Configuration();
/**
* This is the base class for all generated API classes.
*/
export class BaseAPI {
private middleware: Middleware[];
constructor(protected configuration = DefaultConfig) {
this.middleware = configuration.middleware;
}
withMiddleware<T extends BaseAPI>(this: T, ...middlewares: Middleware[]) {
const next = this.clone<T>();
next.middleware = next.middleware.concat(...middlewares);
return next;
}
withPreMiddleware<T extends BaseAPI>(this: T, ...preMiddlewares: Array<Middleware['pre']>) {
const middlewares = preMiddlewares.map((pre) => ({ pre }));
return this.withMiddleware<T>(...middlewares);
}
withPostMiddleware<T extends BaseAPI>(this: T, ...postMiddlewares: Array<Middleware['post']>) {
const middlewares = postMiddlewares.map((post) => ({ post }));
return this.withMiddleware<T>(...middlewares);
}
protected async request(context: RequestOpts, initOverrides?: RequestInit | InitOverrideFunction): Promise<Response> {
const { url, init } = await this.createFetchParams(context, initOverrides);
const response = await this.fetchApi(url, init);
if (response && (response.status >= 200 && response.status < 300)) {
return response;
}
throw new ResponseError(response, 'Response returned an error code');
}
private async createFetchParams(context: RequestOpts, initOverrides?: RequestInit | InitOverrideFunction) {
let url = this.configuration.basePath + context.path;
if (context.query !== undefined && Object.keys(context.query).length !== 0) {
// only add the querystring to the URL if there are query parameters.
// this is done to avoid urls ending with a "?" character which buggy webservers
// do not handle correctly sometimes.
url += '?' + this.configuration.queryParamsStringify(context.query);
}
const headers = Object.assign({}, this.configuration.headers, context.headers);
Object.keys(headers).forEach(key => headers[key] === undefined ? delete headers[key] : {});
const initOverrideFn =
typeof initOverrides === "function"
? initOverrides
: async () => initOverrides;
const initParams = {
method: context.method,
headers,
body: context.body,
credentials: this.configuration.credentials,
};
const overriddenInit: RequestInit = {
...initParams,
...(await initOverrideFn({
init: initParams,
context,
}))
};
const init: RequestInit = {
...overriddenInit,
body:
isFormData(overriddenInit.body) ||
overriddenInit.body instanceof URLSearchParams ||
isBlob(overriddenInit.body)
? overriddenInit.body
: JSON.stringify(overriddenInit.body),
};
return { url, init };
}
private fetchApi = async (url: string, init: RequestInit) => {
let fetchParams = { url, init };
for (const middleware of this.middleware) {
if (middleware.pre) {
fetchParams = await middleware.pre({
fetch: this.fetchApi,
...fetchParams,
}) || fetchParams;
}
}
let response: Response | undefined = undefined;
try {
response = await (this.configuration.fetchApi || fetch)(fetchParams.url, fetchParams.init);
} catch (e) {
for (const middleware of this.middleware) {
if (middleware.onError) {
response = await middleware.onError({
fetch: this.fetchApi,
url: fetchParams.url,
init: fetchParams.init,
error: e,
response: response ? response.clone() : undefined,
}) || response;
}
}
if (response === undefined) {
if (e instanceof Error) {
throw new FetchError(e, 'The request failed and the interceptors did not return an alternative response');
} else {
throw e;
}
}
}
for (const middleware of this.middleware) {
if (middleware.post) {
response = await middleware.post({
fetch: this.fetchApi,
url: fetchParams.url,
init: fetchParams.init,
response: response.clone(),
}) || response;
}
}
return response;
}
/**
* Create a shallow clone of `this` by constructing a new instance
* and then shallow cloning data members.
*/
private clone<T extends BaseAPI>(this: T): T {
const constructor = this.constructor as any;
const next = new constructor(this.configuration);
next.middleware = this.middleware.slice();
return next;
}
};
function isBlob(value: any): value is Blob {
return typeof Blob !== 'undefined' && value instanceof Blob;
}
function isFormData(value: any): value is FormData {
return typeof FormData !== "undefined" && value instanceof FormData;
}
export class ResponseError extends Error {
override name: "ResponseError" = "ResponseError";
constructor(public response: Response, msg?: string) {
super(msg);
}
}
export class FetchError extends Error {
override name: "FetchError" = "FetchError";
constructor(public cause: Error, msg?: string) {
super(msg);
}
}
export class RequiredError extends Error {
override name: "RequiredError" = "RequiredError";
constructor(public field: string, msg?: string) {
super(msg);
}
}
export const COLLECTION_FORMATS = {
csv: ",",
ssv: " ",
tsv: "\t",
pipes: "|",
};
export type FetchAPI = WindowOrWorkerGlobalScope['fetch'];
export type Json = any;
export type HTTPMethod = 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE' | 'OPTIONS' | 'HEAD';
export type HTTPHeaders = { [key: string]: string };
export type HTTPQuery = { [key: string]: string | number | null | boolean | Array<string | number | null | boolean> | Set<string | number | null | boolean> | HTTPQuery };
export type HTTPBody = Json | FormData | URLSearchParams;
export type HTTPRequestInit = { headers?: HTTPHeaders; method: HTTPMethod; credentials?: RequestCredentials; body?: HTTPBody };
export type ModelPropertyNaming = 'camelCase' | 'snake_case' | 'PascalCase' | 'original';
export type InitOverrideFunction = (requestContext: { init: HTTPRequestInit, context: RequestOpts }) => Promise<RequestInit>
export interface FetchParams {
url: string;
init: RequestInit;
}
export interface RequestOpts {
path: string;
method: HTTPMethod;
headers: HTTPHeaders;
query?: HTTPQuery;
body?: HTTPBody;
}
export function exists(json: any, key: string) {
const value = json[key];
return value !== null && value !== undefined;
}
export function querystring(params: HTTPQuery, prefix: string = ''): string {
return Object.keys(params)
.map(key => querystringSingleKey(key, params[key], prefix))
.filter(part => part.length > 0)
.join('&');
}
function querystringSingleKey(key: string, value: string | number | null | undefined | boolean | Array<string | number | null | boolean> | Set<string | number | null | boolean> | HTTPQuery, keyPrefix: string = ''): string {
const fullKey = keyPrefix + (keyPrefix.length ? `[${key}]` : key);
if (value instanceof Array) {
const multiValue = value.map(singleValue => encodeURIComponent(String(singleValue)))
.join(`&${encodeURIComponent(fullKey)}=`);
return `${encodeURIComponent(fullKey)}=${multiValue}`;
}
if (value instanceof Set) {
const valueAsArray = Array.from(value);
return querystringSingleKey(key, valueAsArray, keyPrefix);
}
if (value instanceof Date) {
return `${encodeURIComponent(fullKey)}=${encodeURIComponent(value.toISOString())}`;
}
if (value instanceof Object) {
return querystring(value as HTTPQuery, fullKey);
}
return `${encodeURIComponent(fullKey)}=${encodeURIComponent(String(value))}`;
}
export function mapValues(data: any, fn: (item: any) => any) {
return Object.keys(data).reduce(
(acc, key) => ({ ...acc, [key]: fn(data[key]) }),
{}
);
}
export function canConsumeForm(consumes: Consume[]): boolean {
for (const consume of consumes) {
if ('multipart/form-data' === consume.contentType) {
return true;
}
}
return false;
}
export interface Consume {
contentType: string;
}
export interface RequestContext {
fetch: FetchAPI;
url: string;
init: RequestInit;
}
export interface ResponseContext {
fetch: FetchAPI;
url: string;
init: RequestInit;
response: Response;
}
export interface ErrorContext {
fetch: FetchAPI;
url: string;
init: RequestInit;
error: unknown;
response?: Response;
}
export interface Middleware {
pre?(context: RequestContext): Promise<FetchParams | void>;
post?(context: ResponseContext): Promise<Response | void>;
onError?(context: ErrorContext): Promise<Response | void>;
}
export interface ApiResponse<T> {
raw: Response;
value(): Promise<T>;
}
export interface ResponseTransformer<T> {
(json: any): T;
}
export class JSONApiResponse<T> {
constructor(public raw: Response, private transformer: ResponseTransformer<T> = (jsonValue: any) => jsonValue) {}
async value(): Promise<T> {
return this.transformer(await this.raw.json());
}
}
export class VoidApiResponse {
constructor(public raw: Response) {}
async value(): Promise<void> {
return undefined;
}
}
export class BlobApiResponse {
constructor(public raw: Response) {}
async value(): Promise<Blob> {
return await this.raw.blob();
};
}
export class TextApiResponse {
constructor(public raw: Response) {}
async value(): Promise<string> {
return await this.raw.text();
};
}

View file

@ -0,0 +1,20 @@
{
"compilerOptions": {
"declaration": true,
"target": "es5",
"module": "commonjs",
"moduleResolution": "node",
"outDir": "dist",
"lib": [
"es6",
"dom"
],
"typeRoots": [
"node_modules/@types"
]
},
"exclude": [
"dist",
"node_modules"
]
}