diff --git a/.gitignore b/.gitignore index 7626c60..570ee35 100644 --- a/.gitignore +++ b/.gitignore @@ -3,3 +3,5 @@ node_modules public dist yarn-error.log + +src/package.json diff --git a/assets/sass/_vars.scss b/assets/sass/_vars.scss index 31b7580..478fca3 100644 --- a/assets/sass/_vars.scss +++ b/assets/sass/_vars.scss @@ -6,9 +6,10 @@ $secondaryForeground: $primaryForeground; $backgroundColor: darken($primary, 4%); $defaultTextColor: #ffffff; -$headerBackground: darken($primary, 7.5%); -$footerBackground: lighten($headerBackground, 1%); -$panelBackground: lighten($headerBackground, 1%); +$headerBackground: transparent; +$headerContainer: true; +$footerBackground: transparent; +$panelBackground: darken($backgroundColor, 3.2%); $inputBackground: darken($panelBackground, 4%); $info: #4499ff; @@ -29,3 +30,4 @@ $errorColor: desaturate($errorText, 50%); // Responsivity $mobileThreshold: 632px; +$desktopThreshold: 940px; diff --git a/assets/sass/layout.scss b/assets/sass/layout.scss index 5a00bff..7a677e0 100644 --- a/assets/sass/layout.scss +++ b/assets/sass/layout.scss @@ -1,5 +1,6 @@ @import "vars"; @import 'fonts'; +@import "responsivity_tools"; * { box-sizing: border-box; @@ -69,21 +70,31 @@ body { body > header { z-index: 50; display: flex; - flex-direction: row; + flex-direction: row-reverse; justify-content: space-between; + align-items: center; $headerHeight: 64px; height: $headerHeight; line-height: $headerHeight; - background-color: $headerBackground; + background: $headerBackground; + + @if $headerContainer { + @include container; + } + + @media (max-width: $mobileThreshold) { + padding: 0; + } .logo { display: flex; flex-direction: row; + align-items: center; - padding: 0 24px 0 16px; - font-size: 32px; + padding: 0 16px 0 8px; + font-size: 24px; color: $defaultTextColor; &:hover { @@ -91,34 +102,48 @@ body > header { } img { - width: $headerHeight; - height: $headerHeight; - margin-right: 16px; + width: initial; + height: calc(#{$headerHeight} - 16px); + margin-right: 8px; + flex-shrink: 0; } } nav { - ul { + > ul { + position: fixed; + z-index: -1; + top: 0; + left: 0; + height: 100%; + transform: translateX(-100%); + transition: transform ease-out 150ms; + display: flex; - flex-direction: row; + flex-direction: column; margin: 0; - padding: 0; + padding: $headerHeight 8px 8px; font-size: 20px; + background: $panelBackground; + li { position: relative; list-style: none; + margin-top: 8px; a, button { position: relative; - height: 64px; margin: 0; - padding: 0 24px; display: flex; flex-direction: row; align-items: center; + height: auto; + padding: 8px; + + border-radius: 3px; &:hover, &:active { &:not(button) { @@ -127,13 +152,40 @@ body > header { } .feather { - --icon-size: 24px; + --icon-size: 16px; + } + + .tip { + position: static; + visibility: visible; + opacity: 1; + display: block; + height: auto; + margin-left: 8px; + padding: 0 0 0 4px; + transform: none; + + font-size: 16px; + line-height: 16px; + + color: inherit; + text-transform: uppercase; + font-weight: inherit; + background: transparent; + } + + &:hover { + .tip { + visibility: visible; + opacity: 1; + transition: opacity ease-out 100ms; + transition-delay: 150ms; + } } } button { - margin: 8px; - padding: 24px; + margin: 0; height: 32px; .feather { @@ -158,114 +210,117 @@ body > header { } .dropdown { - position: absolute; - z-index: -1; - top: 100%; - right: 0; - - white-space: nowrap; - background: $headerBackground; - border-radius: 0 0 3px 3px; - - a { - padding: 0 8px; - } - } - - &:hover .dropdown { + position: initial; display: block; + padding-left: 0; } } + + > li:not(:first-child) { + border-top: 1px solid transparentize($defaultTextColor, 0.8); + padding-top: 8px; + } + + &.open { + transform: translateX(0%); + box-shadow: 0 0 5px darken($panelBackground, 20%); + } } #menu-button { - display: none; - } - } + position: fixed; + top: 0; + left: 0; + display: block; + margin: 0; + padding: 0 16px; + line-height: $headerHeight; - @media (max-width: $mobileThreshold) { - flex-direction: row-reverse; + cursor: pointer; + background: transparent; + border-radius: 0; - .logo { - padding: 0 16px 0 8px; - font-size: 24px; - - img { - margin-right: 8px; + .feather { + --icon-size: 28px; + margin: 0 8px; } } - nav { - #menu-button { - display: block; - margin: 0; - padding: 0 16px; - line-height: $headerHeight; - - cursor: pointer; - background: transparent; - border-radius: 0; - - .feather { - --icon-size: 28px; - margin: 0 8px; - } - } - - > ul { - flex-direction: column; - position: absolute; - z-index: 10; - left: 0; - transform: translateX(-100%); - transition: transform ease-out 150ms; - - background-color: $headerBackground; - - &.open { - transform: translateX(0%); - } - - li { - a, button { - .tip { - display: block; - margin-left: 8px; - text-transform: inherit; - font-weight: inherit; - } - } - - .dropdown { - position: initial; - display: block; - padding-left: 32px; - } - } - } + hr { + border: 0; + border-bottom: 1px solid $defaultTextColor; + opacity: 0.2; } } @media (min-width: $mobileThreshold) { - nav ul li { - a, button, .button { - @include tip; + flex-direction: row; + + nav { + #menu-button { + display: none; } - &:last-child { - a, button, .button { - .tip { - left: unset; - right: 4px; - transform: none; + ul { + position: static; + flex-direction: row; + transform: none; + padding: 0; + background: transparent; + + li { + margin-top: 0; + margin-left: 8px; + + &:last-child { + a, button, .button { + .tip { + left: unset; + right: 4px; + transform: none; + } + } } + + .dropdown { + position: absolute; + z-index: -1; + top: 100%; + right: 0; + display: none; + padding: 8px; + + white-space: nowrap; + background: $panelBackground; + border-radius: 0 0 3px 3px; + + box-shadow: 0 2px 2px transparentize(darken($panelBackground, 20%), 0.75); + border-top: 4px solid lighten($panelBackground, 5%); + + li { + margin-left: 0; + + &:not(:first-child) { + margin-top: 8px; + } + } + } + + &:hover .dropdown { + display: block; + } + } + + > li:not(:first-child) { + border-top: 0; + padding-top: 0; } } } } } -footer { +body > footer { padding: 8px; margin-top: 8px; text-align: center; @@ -338,7 +393,7 @@ a { text-decoration: none; &:hover { - color: lighten($secondary, 10%); + color: lighten($secondary, 30%); } .feather.feather-external-link { @@ -584,6 +639,10 @@ button, .button { &.warning { background-color: $warningColor; + + &:hover { + background-color: lighten($warningColor, 10%); + } } &.error, &.danger { @@ -603,6 +662,35 @@ button, .button { } } + +// --- +// --- Tables +// --- +td.actions { + display: flex; + flex-direction: row; + justify-content: center; + align-items: center; + + form { + padding: 0; + display: inline; + } + + button, .button { + margin: 0; + padding: 8px; + + .feather { + margin-right: 0; + } + } + + > *:not(:first-child) { + margin-left: 8px; + } +} + .data-table { width: 100%; text-align: left; @@ -629,6 +717,10 @@ button, .button { } } + +// --- +// --- Breadcrumb widget +// --- .breadcrumb { list-style: none; display: flex; @@ -642,6 +734,7 @@ button, .button { } } + // --- // --- Layout helpers // --- @@ -649,24 +742,6 @@ button, .button { text-align: center; } -@mixin container { - width: $mobileThreshold; - padding: 0 16px; - - @media (min-width: $mobileThreshold) { - margin: 0 auto; - } - - @media (max-width: $mobileThreshold) { - width: 100%; - padding: 0 8px; - } -} - -.container { - @include container; -} - .panel { position: relative; margin: 16px 0 48px; @@ -688,10 +763,14 @@ button, .button { } .sub-panel { - margin: 32px -18px; + margin: 32px 0; padding: 1px 16px; border: 2px solid lighten($panelBackground, 4%); border-radius: 5px; + + form > & { + margin: 32px -18px; + } } @@ -715,6 +794,10 @@ button, .button { stroke-linejoin: miter; fill: none; vertical-align: middle; + + h1 > &, h2 > &, h3 > & { + --icon-size: 24px; + } } // --- diff --git a/assets/sass/responsivity_tools.scss b/assets/sass/responsivity_tools.scss new file mode 100644 index 0000000..0e3d232 --- /dev/null +++ b/assets/sass/responsivity_tools.scss @@ -0,0 +1,19 @@ +@import "vars"; + +@mixin container { + width: 100%; + padding: 0 8px; + + @media (min-width: $mobileThreshold) { + margin: 0 auto; + padding: 0 16px; + } + + @media (min-width: $desktopThreshold) { + width: $desktopThreshold; + } +} + +.container { + @include container; +} diff --git a/package.json b/package.json index 497a19e..5788c8f 100644 --- a/package.json +++ b/package.json @@ -4,23 +4,23 @@ "description": "A simple redirection to serve a gitea instance's repositories' latest release files as an http file server. (302 redirections)", "repository": "https://eternae.ink/arisu/update.eternae.ink", "author": "Alice Gaudon ", - "main": "dist/src/main.js", + "main": "dist/main.js", "license": "MIT", "scripts": { - "dist-webpack": "webpack --mode production", + "test": "jest --verbose --runInBand", "clean": "(test ! -d dist || rm -r dist)", + "prepareSources": "cp package.json src/", "compile": "yarn clean && tsc", - "build": "yarn compile && yarn dist-webpack", - "lint": "eslint . --ext .js,.jsx,.ts,.tsx", - "dev": "concurrently -k -n \"Typescript,Node,Webpack,Maildev\" -p \"[{name}]\" -c \"blue,green,red,yellow\" \"tsc --watch\" \"nodemon\" \"webpack --watch --mode development\" \"maildev\"", - "start": "yarn build && node dist/src/main.js", - "test": "jest --verbose --runInBand" + "build": "yarn prepareSources && yarn compile && webpack --mode production", + "dev": "yarn prepareSources && concurrently -k -n \"Typescript,Node,Webpack,Maildev\" -p \"[{name}]\" -c \"blue,green,red,yellow\" \"tsc --watch\" \"nodemon\" \"webpack --watch --mode development\" \"maildev\"", + "start": "yarn build && node", + "lint": "eslint . --ext .js,.jsx,.ts,.tsx" }, "devDependencies": { "@babel/core": "^7.9.0", "@babel/preset-env": "^7.9.5", "@fortawesome/fontawesome-free": "^5.14.0", - "@types/config": "^0.0.36", + "@types/config": "^0.0.38", "@types/express": "^4.17.6", "@types/express-session": "^1.17.0", "@types/feather-icons": "^4.7.0", @@ -47,6 +47,7 @@ "imagemin-svgo": "^8.0.0", "img-loader": "^3.0.1", "jest": "^26.1.0", + "maildev": "^1.1.0", "mini-css-extract-plugin": "^1.2.1", "node-sass": "^5.0.0", "nodemon": "^2.0.3", @@ -63,6 +64,6 @@ "express": "^4.17.1", "mime": "^2.4.6", "send-ranges": "^4.0.0", - "swaf": "^0.22.5" + "swaf": "^0.23.0" } } diff --git a/src/App.ts b/src/App.ts index 4225328..ec81ee3 100644 --- a/src/App.ts +++ b/src/App.ts @@ -4,7 +4,7 @@ import ExpressAppComponent from "swaf/components/ExpressAppComponent"; import LogRequestsComponent from "swaf/components/LogRequestsComponent"; import GiteaRepoLatestReleaseController from "./controllers/GiteaRepoLatestReleaseController"; import NunjucksComponent from "swaf/components/NunjucksComponent"; -import packageJson = require('../package.json'); +import packageJson = require('./package.json'); export default class App extends Application { public constructor( diff --git a/src/controllers/HomeController.ts b/src/controllers/HomeController.ts new file mode 100644 index 0000000..b5e1740 --- /dev/null +++ b/src/controllers/HomeController.ts @@ -0,0 +1,25 @@ +import Controller from "swaf/Controller"; +import {Request, Response} from "express"; + +export default class HomeController extends Controller { + public routes(): void { + this.get('/', this.getHome, 'home'); + this.get('/about', this.getAbout, 'about'); + this.get('/back', this.goBack, 'about'); + } + + protected async getHome(req: Request, res: Response): Promise { + res.render('home'); + } + + protected async getAbout(req: Request, res: Response): Promise { + res.render('about'); + } + + /** + * This is to test and assert that swaf extended types are available + */ + protected async goBack(req: Request, res: Response): Promise { + res.redirect(req.getPreviousUrl() || Controller.route('home')); + } +} diff --git a/src/main.ts b/src/main.ts index 0033726..babc7df 100644 --- a/src/main.ts +++ b/src/main.ts @@ -2,19 +2,19 @@ import {delimiter} from "path"; // Load config from specified path or default + swaf/config (default defaults) process.env['NODE_CONFIG_DIR'] = - __dirname + '/../../node_modules/swaf/config/' + __dirname + '/../node_modules/swaf/config/' + delimiter - + (process.env['NODE_CONFIG_DIR'] || __dirname + '/../../config/'); + + (process.env['NODE_CONFIG_DIR'] || __dirname + '/../config/'); -import {log} from "swaf/Logger"; +import {logger} from "swaf/Logger"; import App from "./App"; import config from "config"; (async () => { - log.debug('Config path:', process.env['NODE_CONFIG_DIR']); + logger.debug('Config path:', process.env['NODE_CONFIG_DIR']); const app = new App(config.get('listen_addr'), config.get('port')); await app.start(); })().catch(err => { - log.error(err); + logger.error(err); }); diff --git a/tsconfig.frontend.json b/tsconfig.frontend.json index 4b5a3f5..ad74720 100644 --- a/tsconfig.frontend.json +++ b/tsconfig.frontend.json @@ -2,6 +2,7 @@ "extends": "./tsconfig.json", "compilerOptions": { "outDir": "public/js", + "rootDir": "./assets", "target": "ES6", "strict": true, "lib": [ diff --git a/tsconfig.json b/tsconfig.json index 1207e21..0b19cec 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -3,6 +3,7 @@ "module": "CommonJS", "esModuleInterop": true, "outDir": "dist", + "rootDir": "./src", "target": "ES6", "strict": true, "lib": [