diff options
| author | aura <nw@moneybot.cc> | 2026-02-19 19:57:10 +0100 |
|---|---|---|
| committer | aura <nw@moneybot.cc> | 2026-02-19 19:57:10 +0100 |
| commit | bedf43af45d97a10a6f62b4f1bb21cd66fda1d71 (patch) | |
| tree | 6501bb95977a574c188bef6a228ff7500b243f3a /public/src | |
| parent | b5fca421c8f5f0f8f26d1392ef48635196887fa3 (diff) | |
empty moneyjsx site
Diffstat (limited to 'public/src')
| -rw-r--r-- | public/src/api.tsx | 39 | ||||
| -rw-r--r-- | public/src/components.tsx | 257 | ||||
| -rw-r--r-- | public/src/header.tsx | 10 | ||||
| -rw-r--r-- | public/src/home.tsx | 11 | ||||
| -rw-r--r-- | public/src/index-page.tsx | 10 | ||||
| -rw-r--r-- | public/src/index.html | 33 | ||||
| -rw-r--r-- | public/src/jsx.tsx | 338 | ||||
| -rw-r--r-- | public/src/tsconfig.json | 121 | ||||
| -rw-r--r-- | public/src/user.tsx | 6 | ||||
| -rw-r--r-- | public/src/util.tsx | 60 |
10 files changed, 885 insertions, 0 deletions
diff --git a/public/src/api.tsx b/public/src/api.tsx new file mode 100644 index 0000000..fa941ec --- /dev/null +++ b/public/src/api.tsx @@ -0,0 +1,39 @@ +export const url = "https://forum.networkheaven.net/api/"; + +export interface ReqParams { + method: string, + body?: string, +} + +export async function post( endpoint: string, body: Object ) { + return await req( endpoint, { + method: "POST", + body: JSON.stringify( body ), + } ); +} + +export async function req( endpoint: string, params: ReqParams ) { + const res = await fetch( `${url}/${endpoint}`, { + method: params.method, + headers: { + "Content-Type": "application/json", + }, + body: params.body, + } ); + + if( !res.ok ) { + let json = null; + try { + json = await res.json(); + } catch( e: any ) { + throw new Error( "error contacting server" ); + } + + throw new Error( json.msg ); + } + + const json = await res.json(); + if( json.status != 'ok' ) + throw new Error( json.msg ); + return json; +} diff --git a/public/src/components.tsx b/public/src/components.tsx new file mode 100644 index 0000000..689179a --- /dev/null +++ b/public/src/components.tsx @@ -0,0 +1,257 @@ +import $ from "jquery"; +import * as JSX from "./jsx"; +import { Header } from "./header"; + +const popup_stack = []; +window.onclick = ( e: Event ) => { + if( !e.target ) return; + + if( popup_stack.length > 0 ) { + const last = popup_stack[popup_stack.length - 1]; + if( last.el[0] == e.target || last.el.find( e.target ).length != 0 ) return; + e.preventDefault(); + e.stopPropagation(); + if( last.fn ) + last.fn(); + + popup_stack.pop(); + last.el.remove(); + } +} + +/** + * appends an element to the DOM and saves it in the popup stack to be removed. +**/ +export function addPopup( element: any ) { + document.body.appendChild( element[0] ); + setTimeout( () => popup_stack.push( { el: element, fn: null } ) ); +} + +/** + * closes the topmost popup +**/ +export function closePopup() { + if( popup_stack.length > 0 ) { + const last = popup_stack[popup_stack.length - 1]; + if( last.fn ) + last.fn(); + + popup_stack.pop(); + setTimeout( () => last.el.remove() ); + } +} + +/** + * sets a callback that will get executed once the current popup is closed + **/ +export function onPopupClosed( fn: Function ) { + setTimeout( () => { + if( popup_stack.length > 0 ) { + popup_stack[popup_stack.length - 1].fn = fn; + } + } ); +} + +/* + * accepts "folded" prop +**/ +export function RolldownListItem( props: any ) { + const ret = <div class="rolldown"> + <div class="rolldown-collapsed-container"> + <div class="rolldown-icon-container">></div> + <div class="rolldown-collapsed">{props.folded}</div> + </div> + <div class="rolldown-expanded-container"> + <div class="rolldown-expanded">{props.children}</div> + </div> + </div>; + + ret.onclick = () => { + $( ret ).toggleClass( "active" ); + $( ret ).find( ".rolldown-expanded-container" ).toggleClass( 'active' ); + }; + + return ret; +} + +export function Spinner( props: any ) { + let spinner_steps = [ + '/', + '-', + '\\', + '|', + '/', + '-', + '\\', + '|' + ]; + + const id = props.id || ''; + + let el = $( <div class="spinner" id={ id } style={ props.style || '' } /> ); + let i = 0; + let loop = () => { + el.text( spinner_steps[i++] ); + if( i > spinner_steps.length ) + i = 0; + + if( el[0].isConnected ) + setTimeout( loop, 100 ); + }; + + setTimeout( loop, 100 ); + return el[0]; +} + +/* + * accepts title prop +**/ +export function RolldownList( props: any ) { + return <GroupBox title={props.title} style={props.style} innerStyle="margin: 0px; width: 100%"> + <div class="rolldownlist"> + { props.children } + </div> + </GroupBox> +} + +/* + * accepts title prop and optional innerStyle +**/ +export function GroupBox( props: any ) { + return <div class="groupbox" id={props.id || ''} style={props.style || ''}> + <span class="grouptitle"> + {props.title} + </span> + <span class="groupbody" style={props.innerStyle || ''}> + {props.children} + </span> + </div> +} + +export function Page( props: any ) { + return <> + <Header /> + <div id="page-main"> + <div id="page-main-content"> + {props.children} + </div> + </div> + + <BackgroundToggle /> + </> +} + +export function DropdownItem( props: any ) { + return <div class="dropdown-inner" style={ props.style || "" } onclick={ props.onclick || null }> + {props.children} + </div> +} + +/** + * supports innerStyle for styling the actual dropdown picker + **/ +export function Dropdown( props: any ) { + const children = props.children; + let title = props.title; + let inline = props.inline; + let onchange = props.onchange; + let classes = props.class || ''; + let style = props.style || ""; + let id = props.id || ''; + let innerStyle = props.innerStyle || ""; + + const showItems = ( e: Event ) => { + e.preventDefault(); + const target = $( e.target as HTMLElement ); + + const newDropdown = $( <div class="dropdown-wrapper" style={ innerStyle }> + { children.map( ( child: HTMLElement ) => { + if( !child.onclick ) child.onclick = onchange; return child; + } ) } + </div> ); + target.parent().append( newDropdown[0] ); + setTimeout( () => popup_stack.push( { el: newDropdown, fn: null } ) ); + } + + if( inline ) { + return <button class={ 'dropdown' + ' ' + classes } style={ style } onclick={ showItems } id={ id }> + { title } + </button> + } + else { + return <> + <label>{ title }</label> + <button class={ 'dropdown' + ' ' + classes } style={ style } onclick={ showItems } id={ id }> + </button> + </> + } +} + +export function OkPopup( props: any ) { + const children = props.children; + const classes = props.class || ''; + const style = props.style || ''; + const id = props.id || ''; + + const onclick = ( e: Event ) => { + e.preventDefault(); + e.stopPropagation(); + if( props.onclick ) + props.onclick(); + + closePopup(); + } + + return <div class={ "popup-msg " + classes } id={ id } style={ style }> + { children } + <div style="display: flex; justify-content: center"> + <button onclick={ onclick }>Ok</button> + </div> + </div> +} + +export function OkCancelPopup( props: any ) { + const children = props.children; + const classes = props.class || ''; + const style = props.style || ''; + const id = props.id || ''; + + const onclick = ( e: Event ) => { + e.preventDefault(); + e.stopPropagation(); + if( props.onclick ) + props.onclick(); + + closePopup(); + } + + const oncancel = ( e: Event ) => { + e.preventDefault(); + e.stopPropagation(); + + closePopup(); + } + + return <div class={ "popup-msg " + classes } id={ id } style={ style }> + { children } + <div style="display: flex; justify-content: center"> + <button onclick={ onclick } style="margin-right: 10px">Ok</button> + <button onclick={ oncancel }>Cancel</button> + </div> + </div> +} + +export function BackgroundToggle() { + const toggleBackground = () => { + const main = $( "#page-main" ); + if( main.css( "display" ) == "none" ) { + main.css( "display", "" ); + } else { + main.css( "display", "none" ); + } + } + + return <div id="background-toggle" onclick={ toggleBackground }> + show background + </div> +} diff --git a/public/src/header.tsx b/public/src/header.tsx new file mode 100644 index 0000000..731241d --- /dev/null +++ b/public/src/header.tsx @@ -0,0 +1,10 @@ +import $ from 'jquery'; +import * as JSX from './jsx'; + +export function Header( props: any ) { + return <div class="border-wrapper" style="z-index: 1"> + <div class="header"> + forum.networkheaven.net + </div> + </div> +} diff --git a/public/src/home.tsx b/public/src/home.tsx new file mode 100644 index 0000000..eb7a674 --- /dev/null +++ b/public/src/home.tsx @@ -0,0 +1,11 @@ +import $ from "jquery"; +import * as JSX from "./jsx"; +import { Page } from "./components"; + +export default function Home() { + return <Page> + <div class="page-title"> + <h3 style="font-family: JPN24; width: max-content" class="gradient">NETWORKHEAVEN</h3> + </div> + </Page>; +} diff --git a/public/src/index-page.tsx b/public/src/index-page.tsx new file mode 100644 index 0000000..dabaf05 --- /dev/null +++ b/public/src/index-page.tsx @@ -0,0 +1,10 @@ +import * as JSX from "./jsx"; +import Home from "./home"; + +JSX.setDefaultTitle( "forum.networkheaven.net" ); +JSX.addRoute( "/", () => <Home /> ); + +document.head.appendChild( <link rel="shortcut icon" href="/static/icon.ico" /> ); + +const url = new URL( window.location.href ); +JSX.navigateParams( url.pathname, url.searchParams.entries() ); diff --git a/public/src/index.html b/public/src/index.html new file mode 100644 index 0000000..215dd8f --- /dev/null +++ b/public/src/index.html @@ -0,0 +1,33 @@ +<!DOCTYPE html> +<html lang="en"> + <head> + <meta charset="UTF-8"> + <meta name="description" content="networkheaven forum"> + <meta name="viewport" content="user-scalable=yes"> + <title>forum.networkheaven.net</title> + <link rel="stylesheet" href="/static/main.css"> + <link href="/static/highlight.css" rel="preload" as="style" onload="this.rel='stylesheet'"> + <link rel="icon" href="data:image/png;base64,iVBORw0KGgo="> + <noscript> + <link rel="shortcut icon" href="/static/icon.ico" type="image"> + </noscript> + </head> + <body> + <div id="moneyjsx-root"> + <div id="homepage" style="padding-top: 0px"> + <noscript> + + <!--- for browsers with noscript !---> + <div id="ascii-art"> + + </div> + <h3 style="margin-top: 5px;">forum.networkheaven.net</h3> + <div> + you need javascript to use this website + </div> + </noscript> + </div> + </div> + </body> +</html> + diff --git a/public/src/jsx.tsx b/public/src/jsx.tsx new file mode 100644 index 0000000..db46f15 --- /dev/null +++ b/public/src/jsx.tsx @@ -0,0 +1,338 @@ +import $ from 'jquery'; +const assetAttributeNames = new Set( ['data', 'srcset', 'src', 'href'] ); + +let curRoute = null; +export interface Route { + path: string, + component: Function, + wildcard: boolean +}; + +const routes: Route[] = []; +let err404page = "/"; +let rootId = "moneyjsx-root"; +let defaultTitle = ""; +let onprenavigate: Function = () => {}; +let onpostnavigate: Function = () => {}; + +function routeForPath( route: string ) : Function | null { + if( !routes[route] ) { + for( let key of Object.keys( routes ) ) { + const r = routes[key]; + + if( r.wildcard ) { + if( route.slice( 0, r.path.length ) == r.path ) { + return r.component; + } + } + } + + return null; + } else return routes[route].component; +} + +/** + * sets the id of the element to be replaced by the navigator + **/ +export function setRootId( rootId: string ) { + rootId = rootId; +} + +export function setDefaultTitle( title: string ) { + defaultTitle = title; +} + +/** + * adds a route component to the routes list + * the component function must return either a jquery or a DOM element + **/ +export function addRoute( name: string, component: Function ) { + let path = name; + let wildcard = false; + if( path[path.length - 1] === "*" ) { + path = path.substring( 0, path.length - 1 ); + wildcard = true; + } + + routes[path] = { path, component, wildcard }; +} + +/** + * sets the route for a 404 page + **/ +export function set404Route( name: string ) { + err404page = name; +} + +/** + * sets the callback that will get called when a route changes + **/ +export function onPreNavigate( callback: Function ) { + onprenavigate = callback; +} + +/** + * sets the callback that will get called when a route changes + **/ +export function onPostNavigate( callback: Function ) { + onpostnavigate = callback; +} + + +/** + * replaces the root element with the route component + **/ +export function navigate( route: string ) { + let url = new URL( window.location.href ); + let cb = routeForPath( route ); + url.pathname = route; + if( !cb ) + return navigate( err404page ); + + if( curRoute != cb ) + window.history.pushState( {}, null, url.href ); + + onprenavigate(); + setTitle(); + const el = $( cb() ); + curRoute = cb; + + $( `#${rootId}` ).children().remove(); + $( `#${rootId}` ).append( el ); + onpostnavigate(); +} + +/** + * navigate with params. see: navigate + **/ +export function navigateParams( route: string, params: any ) { + let url = new URL( window.location.href ); + let uparams = new URLSearchParams( params ); + url.pathname = route; + url.search = uparams.toString(); + let cb = routeForPath( route ); + if( !cb ) + return navigate( err404page ); + + if( curRoute != cb ) + window.history.pushState( {}, null, url.href ); + + onprenavigate(); + setTitle(); + const el = $( cb() ); + curRoute = cb; + + $( `#${rootId}` ).children().remove(); + $( `#${rootId}` ).append( el ); + onpostnavigate(); +} + +/** + * convenience function to pass to href elements + **/ +export function href( e: Event ) { + const el = $( e.target ); + if( el.is( 'a' ) ) { + e.preventDefault(); + navigate( el.attr( 'href' ) ); + } +} + +/** + * wrapper for history.pushState + **/ +export function pushParams( params: any ) { + const url = new URL( window.location.href ); + url.search = new URLSearchParams( params ).toString(); + + window.history.pushState( {}, null, url.href ); +} + +/** + * navigates without adding a history entry + * useful for e.g. re-rendering a page after waiting for a data callback +**/ +export function navigateParamsSilent( route: string, params: any ) { + let url = new URL( window.location.href ); + let uparams = new URLSearchParams( params ); + url.pathname = route; + url.search = uparams.toString(); + let cb = routeForPath( route ); + if( !cb ) + return navigateSilent( err404page ); + + onprenavigate(); + setTitle(); + const el = $( cb() ); + curRoute = cb; + + $( `#${rootId}` ).children().remove(); + $( `#${rootId}` ).append( el ); + onpostnavigate(); +} + +/** + * see: navigateParamsSilent + **/ +export function navigateSilent( route: string ) { + let url = new URL( window.location.href ); + url.pathname = route; + let cb = routeForPath( route ); + if( !cb ) + return navigateSilent( err404page ); + + onprenavigate(); + setTitle(); + const el = $( cb() ); + curRoute = cb; + + $( `#${rootId}` ).children().remove(); + $( `#${rootId}` ).append( el ); + onpostnavigate(); +} + +/** + * action when the back button is pressed +**/ +export function onPopState() { + let url = new URL( window.location.href ); + let uparams = new URLSearchParams( url.searchParams ); + url.search = uparams.toString(); + let cb = routeForPath( url.pathname ); + if( !cb ) + return navigateSilent( err404page ); + + if( cb == curRoute ) + return; + + onprenavigate(); + setTitle(); + const el = $( cb() ); + curRoute = cb; + + $( `#${rootId}` ).children().remove(); + $( `#${rootId}` ).append( el ); + onpostnavigate(); +} + +/** + * navigates to the parent directory from the current page +**/ +export function goUpDirectory() { + const url = new URL( window.location.href ); + if( url.pathname.endsWith( "/" ) ) + url.pathname = url.pathname.slice( 0, -1 ); + let idx = url.pathname.lastIndexOf( "/" ); + if( idx === -1 ) + return; + url.pathname = url.pathname.slice( 0, url.pathname.lastIndexOf( "/" ) ); + navigate( url.pathname ); +} + +export function getRoutes() : Route[] { + return routes; +} + + +// internal stuff below + +const originalAppendChild = Element.prototype.appendChild; +Element.prototype.appendChild = function( child: any ) { + if( Array.isArray( child ) ) { + for( const childArrayMember of child ) + this.appendChild( childArrayMember ); + + return child; + } + else if( typeof child === 'string' ) { + return originalAppendChild.call( this, document.createTextNode( child ) ); + } + else if( child ) { + return originalAppendChild.call( this, child ); + } +}; + +export function createElement( tag: any, props: any, ...children: any ) { + props = props || {}; + + if( typeof tag === "function" ) { + props.children = children; + return tag( props ); + } + + if( tag === 'raw-content' ) { + const dummy = document.createElement( 'div' ); + dummy.innerHTML = props.content; + return [...dummy.children]; + } + + const element = document.createElement( tag ); + + for( const [name, value] of Object.entries( props ) ) { + if( name.startsWith( 'on' ) ) { + const lowercaseName = name.toLowerCase(); + + if( lowercaseName in window ) { + element.addEventListener( lowercaseName.substring( 2 ), value ); + continue; + } + } + + if( name == 'ref' ) { + ( value as any ).current = element; + continue; + } + + if( value === false ) + continue; + + if( value === true ) { + element.setAttribute( name, '' ); + continue; + } + + if( assetAttributeNames.has( name ) ) { + if( typeof value === 'string' ) { + element.setAttribute( name, value ); + } + continue; + } + + element.setAttribute( name, value ); + }; + + for( const child of children ) + element.appendChild( child ); + + return element; +} + +export function createFragment( props: any ) { + return props.children; +} + +function setTitle() { + document.title = defaultTitle; +} + +document.addEventListener( "click", e => { + let a = ( e.target ) as HTMLAnchorElement; + if( !a ) + return; + if( a.origin !== location.origin ) + return; + + e.preventDefault(); + if( !a.onclick ) { + if( routeForPath( a.pathname ) ) + return navigate( a.pathname ); + + const hasExt = /\.[a-zA-Z0-9]{1,8}$/.test( a.href ); + if( hasExt ) + location.href = a.href; + else + navigate( err404page ); + } +} ); + +window.onpopstate = onPopState; diff --git a/public/src/tsconfig.json b/public/src/tsconfig.json new file mode 100644 index 0000000..b964594 --- /dev/null +++ b/public/src/tsconfig.json @@ -0,0 +1,121 @@ +{ + "compilerOptions": { + /* Visit https://aka.ms/tsconfig to read more about this file */ + + /* Projects */ + // "incremental": true, /* Save .tsbuildinfo files to allow for incremental compilation of projects. */ + // "composite": true, /* Enable constraints that allow a TypeScript project to be used with project references. */ + // "tsBuildInfoFile": "./.tsbuildinfo", /* Specify the path to .tsbuildinfo incremental compilation file. */ + // "disableSourceOfProjectReferenceRedirect": true, /* Disable preferring source files instead of declaration files when referencing composite projects. */ + // "disableSolutionSearching": true, /* Opt a project out of multi-project reference checking when editing. */ + // "disableReferencedProjectLoad": true, /* Reduce the number of projects loaded automatically by TypeScript. */ + "moduleResolution": "node", + /* Language and Environment */ + "target": "es2020", /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */ + "lib": [ + "ES2017", + "DOM", + "DOM.Iterable", + "ScriptHost" + ], + // "lib": [], /* Specify a set of bundled library declaration files that describe the target runtime environment. */ + "jsx": "react", /* Specify what JSX code is generated. */ + // "experimentalDecorators": true, /* Enable experimental support for legacy experimental decorators. */ + // "emitDecoratorMetadata": true, /* Emit design-type metadata for decorated declarations in source files. */ + "jsxFactory": "JSX.createElement", + "jsxFragmentFactory": "JSX.createFragment", + // "jsxImportSource": "", /* Specify module specifier used to import the JSX factory functions when using 'jsx: react-jsx*'. */ + // "reactNamespace": "", /* Specify the object invoked for 'createElement'. This only applies when targeting 'react' JSX emit. */ + // "noLib": true, /* Disable including any library files, including the default lib.d.ts. */ + // "useDefineForClassFields": true, /* Emit ECMAScript-standard-compliant class fields. */ + // "moduleDetection": "auto", /* Control what method is used to detect module-format JS files. */ + + /* Modules */ + "module": "es2020", /* Specify what module code is generated. */ + // "rootDir": "./", /* Specify the root folder within your source files. */ + // "moduleResolution": "node10", /* Specify how TypeScript looks up a file from a given module specifier. */ + "baseUrl": ".", + "paths": { + "*": ["types/*"] + }, /* Specify a set of entries that re-map imports to additional lookup locations. */ + // "rootDirs": [], /* Allow multiple folders to be treated as one when resolving modules. */ + "typeRoots": [ + "./node_modules/@types", + "./types" + ], /* Specify multiple folders that act like './node_modules/@types'. */ + // "types": [], /* Specify type package names to be included without being referenced in a source file. */ + // "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */ + // "moduleSuffixes": [], /* List of file name suffixes to search when resolving a module. */ + // "allowImportingTsExtensions": true, /* Allow imports to include TypeScript file extensions. Requires '--moduleResolution bundler' and either '--noEmit' or '--emitDeclarationOnly' to be set. */ + // "resolvePackageJsonExports": true, /* Use the package.json 'exports' field when resolving package imports. */ + // "resolvePackageJsonImports": true, /* Use the package.json 'imports' field when resolving imports. */ + // "customConditions": [], /* Conditions to set in addition to the resolver-specific defaults when resolving imports. */ + // "noUncheckedSideEffectImports": true, /* Check side effect imports. */ + // "resolveJsonModule": true, /* Enable importing .json files. */ + // "allowArbitraryExtensions": true, /* Enable importing files with any extension, provided a declaration file is present. */ + // "noResolve": true, /* Disallow 'import's, 'require's or '<reference>'s from expanding the number of files TypeScript should add to a project. */ + + /* JavaScript Support */ + "allowJs": true, /* Allow JavaScript files to be a part of your program. Use the 'checkJS' option to get errors from these files. */ + "checkJs": true, /* Enable error reporting in type-checked JavaScript files. */ + // "maxNodeModuleJsDepth": 1, /* Specify the maximum folder depth used for checking JavaScript files from 'node_modules'. Only applicable with 'allowJs'. */ + + /* Emit */ + // "declaration": true, /* Generate .d.ts files from TypeScript and JavaScript files in your project. */ + // "declarationMap": true, /* Create sourcemaps for d.ts files. */ + // "emitDeclarationOnly": true, /* Only output d.ts files and not JavaScript files. */ + "sourceMap": true, /* Create source map files for emitted JavaScript files. */ + // "inlineSourceMap": true, /* Include sourcemap files inside the emitted JavaScript. */ + // "noEmit": true, /* Disable emitting files from a compilation. */ + // "outFile": "./", /* Specify a file that bundles all outputs into one JavaScript file. If 'declaration' is true, also designates a file that bundles all .d.ts output. */ + // "outDir": "./", /* Specify an output folder for all emitted files. */ + // "removeComments": true, /* Disable emitting comments. */ + // "importHelpers": true, /* Allow importing helper functions from tslib once per project, instead of including them per-file. */ + // "downlevelIteration": true, /* Emit more compliant, but verbose and less performant JavaScript for iteration. */ + // "sourceRoot": "", /* Specify the root path for debuggers to find the reference source code. */ + // "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */ + // "inlineSources": true, /* Include source code in the sourcemaps inside the emitted JavaScript. */ + // "emitBOM": true, /* Emit a UTF-8 Byte Order Mark (BOM) in the beginning of output files. */ + // "newLine": "crlf", /* Set the newline character for emitting files. */ + // "stripInternal": true, /* Disable emitting declarations that have '@internal' in their JSDoc comments. */ + // "noEmitHelpers": true, /* Disable generating custom helper functions like '__extends' in compiled output. */ + // "noEmitOnError": true, /* Disable emitting files if any type checking errors are reported. */ + // "preserveConstEnums": true, /* Disable erasing 'const enum' declarations in generated code. */ + // "declarationDir": "./", /* Specify the output directory for generated declaration files. */ + + /* Interop Constraints */ + // "isolatedModules": true, /* Ensure that each file can be safely transpiled without relying on other imports. */ + // "verbatimModuleSyntax": true, /* Do not transform or elide any imports or exports not marked as type-only, ensuring they are written in the output file's format based on the 'module' setting. */ + // "isolatedDeclarations": true, /* Require sufficient annotation on exports so other tools can trivially generate declaration files. */ + // "allowSyntheticDefaultImports": true, /* Allow 'import x from y' when a module doesn't have a default export. */ + "esModuleInterop": true, /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables 'allowSyntheticDefaultImports' for type compatibility. */ + // "preserveSymlinks": true, /* Disable resolving symlinks to their realpath. This correlates to the same flag in node. */ + "forceConsistentCasingInFileNames": true, /* Ensure that casing is correct in imports. */ + + /* Type Checking */ + "strict": true, /* Enable all strict type-checking options. */ + "noImplicitAny": false, /* Enable error reporting for expressions and declarations with an implied 'any' type. */ + "strictNullChecks": false, /* When type checking, take into account 'null' and 'undefined'. */ + "strictFunctionTypes": false, /* When assigning functions, check to ensure parameters and the return values are subtype-compatible. */ + "strictBindCallApply": false, /* Check that the arguments for 'bind', 'call', and 'apply' methods match the original function. */ + "strictPropertyInitialization": false, /* Check for class properties that are declared but not set in the constructor. */ + "strictBuiltinIteratorReturn": false, /* Built-in iterators are instantiated with a 'TReturn' type of 'undefined' instead of 'any'. */ + "noImplicitThis": false, /* Enable error reporting when 'this' is given the type 'any'. */ + // "useUnknownInCatchVariables": true, /* Default catch clause variables as 'unknown' instead of 'any'. */ + // "alwaysStrict": true, /* Ensure 'use strict' is always emitted. */ + // "noUnusedLocals": true, /* Enable error reporting when local variables aren't read. */ + // "noUnusedParameters": true, /* Raise an error when a function parameter isn't read. */ + // "exactOptionalPropertyTypes": true, /* Interpret optional property types as written, rather than adding 'undefined'. */ + // "noImplicitReturns": true, /* Enable error reporting for codepaths that do not explicitly return in a function. */ + // "noFallthroughCasesInSwitch": true, /* Enable error reporting for fallthrough cases in switch statements. */ + // "noUncheckedIndexedAccess": true, /* Add 'undefined' to a type when accessed using an index. */ + // "noImplicitOverride": true, /* Ensure overriding members in derived classes are marked with an override modifier. */ + // "noPropertyAccessFromIndexSignature": true, /* Enforces using indexed accessors for keys declared using an indexed type. */ + // "allowUnusedLabels": true, /* Disable error reporting for unused labels. */ + // "allowUnreachableCode": true, /* Disable error reporting for unreachable code. */ + + /* Completeness */ + // "skipDefaultLibCheck": true, /* Skip type checking .d.ts files that are included with TypeScript. */ + "skipLibCheck": true /* Skip type checking all .d.ts files. */ + } +} diff --git a/public/src/user.tsx b/public/src/user.tsx new file mode 100644 index 0000000..d909d9e --- /dev/null +++ b/public/src/user.tsx @@ -0,0 +1,6 @@ +import $ from 'jquery'; + +import * as JSX from './jsx'; +import * as api from './api'; + + diff --git a/public/src/util.tsx b/public/src/util.tsx new file mode 100644 index 0000000..f083d9f --- /dev/null +++ b/public/src/util.tsx @@ -0,0 +1,60 @@ +export function escapeHtml( html: string ) { + const entityMap = { + '&': '&', + '<': '<', + '>': '>', + '"': '"', + "'": ''', + '/': '/', + '`': '`', + '=': '=' + }; + + return String( html ).replace( /[&<>"'`=\/]/g, ( s ) => { + return entityMap[s]; + } ); +} + +export function parseJWT( token: string ) : any { + const parts = token.split( '.' ); + let encoded = parts[1]; + encoded = encoded.replace(/-/g, '+').replace(/_/g, '/'); + const pad = encoded.length % 4; + if( pad === 1 ) + throw new Error( 'what the fuck' ); + if( pad > 1 ) + encoded += new Array( 5 - pad ).join( '=' ); + + const payload = JSON.parse( atob( encoded ) ); + return payload; +} + +export function sizeHumanReadable( size: number, short: boolean = false ) { + if( size < 1024 ) + return size + (short? 'B' : ' B'); + else if( size < 1024 * 1024 ) + return ( size / 1024 ).toFixed( short? 1: 2 ) + (short? 'K' : ' KB'); + else if( size < 1024 * 1024 * 1024 ) + return ( size / 1024 / 1024 ).toFixed( short? 1 : 2 ) + (short? 'M' : ' MB'); + else + return ( size / 1024 / 1024 / 1024 ).toFixed( short? 1 : 2 ) + (short? 'G':' GB'); +} + +export function monthToNumber( month: string ) { + const months = [ + '', + 'jan', + 'feb', + 'mar', + 'apr', + 'may', + 'jun', + 'jul', + 'aug', + 'sep', + 'oct', + 'nov', + 'dec' + ]; + return months.indexOf( month.toLowerCase() ); +}; |
