Line endings fix

This commit is contained in:
anarch3 2022-11-20 18:33:25 -05:00
parent 5116dd2137
commit 544abd1ebd
25 changed files with 7378 additions and 7372 deletions

View file

@ -1,12 +1,12 @@
.env .env
var var
uploads uploads
# Node.js # Node.js
node_modules/ node_modules/
npm-debug.log* npm-debug.log*
# Mac OS X # Mac OS X
.DS_Store .DS_Store
Dockerfile Dockerfile

View file

@ -1,31 +1,31 @@
{ {
"env": { "env": {
"node": true, "node": true,
"commonjs": true, "commonjs": true,
"es2021": true "es2021": true
}, },
"extends": "eslint:recommended", "extends": "eslint:recommended",
"overrides": [ "overrides": [
], ],
"parserOptions": { "parserOptions": {
"ecmaVersion": "latest" "ecmaVersion": "latest"
}, },
"rules": { "rules": {
"indent": [ "indent": [
"error", "error",
"tab" "tab"
], ],
"linebreak-style": [ "linebreak-style": [
"error", "error",
"unix" "unix"
], ],
"quotes": [ "quotes": [
"error", "error",
"double" "double"
], ],
"semi": [ "semi": [
"error", "error",
"always" "always"
] ]
} }
} }

20
.gitignore vendored
View file

@ -1,10 +1,10 @@
.env .env
var var
uploads uploads
# Node.js # Node.js
node_modules/ node_modules/
npm-debug.log* npm-debug.log*
# Mac OS X # Mac OS X
.DS_Store .DS_Store

View file

@ -1,10 +1,10 @@
FROM node:16 FROM node:16
# Install dependencies # Install dependencies
COPY package*.json ./ COPY package*.json ./
RUN npm install RUN npm install
# Package app source # Package app source
COPY . . COPY . .
ENTRYPOINT ["./docker-entrypoint.sh"] ENTRYPOINT ["./docker-entrypoint.sh"]

46
LICENSE
View file

@ -1,24 +1,24 @@
This is free and unencumbered software released into the public domain. This is free and unencumbered software released into the public domain.
Anyone is free to copy, modify, publish, use, compile, sell, or Anyone is free to copy, modify, publish, use, compile, sell, or
distribute this software, either in source code form or as a compiled distribute this software, either in source code form or as a compiled
binary, for any purpose, commercial or non-commercial, and by any binary, for any purpose, commercial or non-commercial, and by any
means. means.
In jurisdictions that recognize copyright laws, the author or authors In jurisdictions that recognize copyright laws, the author or authors
of this software dedicate any and all copyright interest in the of this software dedicate any and all copyright interest in the
software to the public domain. We make this dedication for the benefit software to the public domain. We make this dedication for the benefit
of the public at large and to the detriment of our heirs and of the public at large and to the detriment of our heirs and
successors. We intend this dedication to be an overt act of successors. We intend this dedication to be an overt act of
relinquishment in perpetuity of all present and future rights to this relinquishment in perpetuity of all present and future rights to this
software under copyright law. software under copyright law.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
OTHER DEALINGS IN THE SOFTWARE. OTHER DEALINGS IN THE SOFTWARE.
For more information, please refer to <http://unlicense.org/> For more information, please refer to <http://unlicense.org/>

156
README.md
View file

@ -1,78 +1,78 @@
# Embedder # Embedder
A media host specialized in good looking embeds for services like Discord. No file size limits. No compression. A media host specialized in good looking embeds for services like Discord. No file size limits. No compression.
<img src="readmegif.gif"> <img src="readmegif.gif">
Upcoming Features: Upcoming Features:
* Guest user accounts * Guest user accounts
## Run ## Run
Source: Source:
```Bash ```Bash
EBPASS=changeme EBPASS=changeme
EBPORT=4000 EBPORT=4000
EBAPI_KEY=changeme #ShareX support EBAPI_KEY=changeme #ShareX support
$ npm install $ npm install
$ node db.js $ node db.js
$ npm start $ npm start
``` ```
Default username is admin with the password being whatever EBPASS is Default username is admin with the password being whatever EBPASS is
ShareX support is enabled at "/upload", requires auth with key, expire key is in days ShareX support is enabled at "/upload", requires auth with key, expire key is in days
JSON JSON
``` ```
{ {
"Version": "14.1.0", "Version": "14.1.0",
"Name": "embedder", "Name": "embedder",
"DestinationType": "ImageUploader, FileUploader", "DestinationType": "ImageUploader, FileUploader",
"RequestMethod": "POST", "RequestMethod": "POST",
"RequestURL": "http://localhost:3000/sharex", "RequestURL": "http://localhost:3000/sharex",
"Headers": { "Headers": {
"key": "changeme" "key": "changeme"
}, },
"Body": "MultipartFormData", "Body": "MultipartFormData",
"Arguments": { "Arguments": {
"fileupload": null, "fileupload": null,
"expire": null "expire": null
}, },
"FileFormName": "fileupload", "FileFormName": "fileupload",
"URL": null, "URL": null,
"ThumbnailURL": null, "ThumbnailURL": null,
"DeletionURL": null, "DeletionURL": null,
"ErrorMessage": null "ErrorMessage": null
} }
``` ```
Docker config Docker config
``` ```
docker run -d -p "4000:4000" -e EBPORT=4000 -e EBPASS=changeme -e EBAPI_KEY=changeme waveringana/embedder:latest docker run -d -p "4000:4000" -e EBPORT=4000 -e EBPASS=changeme -e EBAPI_KEY=changeme waveringana/embedder:latest
``` ```
Docker Compose Docker Compose
``` ```
version: '3.3' version: '3.3'
services: services:
embedder: embedder:
ports: ports:
- '4000:4000' - '4000:4000'
environment: environment:
- EBPORT=4000 - EBPORT=4000
- EBPASS=changeme - EBPASS=changeme
- EBAPI_KEY=changeme - EBAPI_KEY=changeme
volumes: volumes:
- embedderdb:/var/db - embedderdb:/var/db
- embedderuploads:/uploads - embedderuploads:/uploads
image: waveringana/embedder:latest image: waveringana/embedder:latest
network_mode: bridge network_mode: bridge
volumes: volumes:
embedderdb: embedderdb:
embedderuploads: embedderuploads:
``` ```
## License ## License
[The Unlicense](https://opensource.org/licenses/unlicense) [The Unlicense](https://opensource.org/licenses/unlicense)

168
app.js
View file

@ -1,82 +1,86 @@
require("dotenv").config(); require("dotenv").config();
let express = require("express"); let express = require("express");
let passport = require("passport"); let passport = require("passport");
let session = require("express-session"); let session = require("express-session");
let cookieParser = require("cookie-parser"); let cookieParser = require("cookie-parser");
let SQLiteStore = require("connect-sqlite3")(session); let SQLiteStore = require("connect-sqlite3")(session);
let fs = require("fs"); let fs = require("fs");
let path = require("path"); let path = require("path");
let authRouter = require("./routes/auth"); let authRouter = require("./routes/auth");
let indexRouter = require("./routes/index"); let indexRouter = require("./routes/index");
let db = require("./db"); let db = require("./db");
let app = express(); let app = express();
app.enable("trust proxy"); app.enable("trust proxy");
// view engine setup // view engine setup
app.set("views", path.join(__dirname, "views")); app.set("views", path.join(__dirname, "views"));
app.set("view engine", "ejs"); app.set("view engine", "ejs");
app.use(express.json()); app.use(express.json());
app.use(express.urlencoded({ app.use(express.urlencoded({
extended: false extended: false
})); }));
app.use(cookieParser()); app.use(cookieParser());
app.use(express.static(path.join(__dirname, "public"))); app.use(express.static(path.join(__dirname, "public")));
app.use(express.static(path.join(__dirname, "public"))); app.use(express.static(path.join(__dirname, "public")));
app.use(session({ app.use(session({
secret: process.env.EBSECRET || "pleasechangeme", secret: process.env.EBSECRET || "pleasechangeme",
resave: false, resave: false,
saveUninitialized: false, saveUninitialized: false,
store: new SQLiteStore({ store: new SQLiteStore({
db: "sessions.db", db: "sessions.db",
dir: "./var/db" dir: "./var/db"
}) })
})); }));
app.use(passport.authenticate("session")); app.use(passport.authenticate("session"));
app.use("/", indexRouter); app.use("/", indexRouter);
app.use("/", authRouter); app.use("/", authRouter);
app.use("/uploads", express.static("uploads")); app.use("/uploads", express.static("uploads"));
function prune () { function prune () {
console.log("Vacuuming database..."); db.all("SELECT * FROM media", (err, rows) => {
db.run("VACUUM"); console.log("Uploaded files: " + rows.length);
console.log(rows)
db.all("SELECT * FROM media WHERE expire < ?", [Date.now()], (err, rows) => { });
console.log("Expired rows: " + rows);
if (err) return console.error(err); console.log("Vacuuming database...");
rows.forEach((row) => { db.run("VACUUM");
console.log(`Deleting ${row.path}`);
fs.unlink(`uploads/${row.path}`, (err) => { db.all("SELECT * FROM media WHERE expire < ?", [Date.now()], (err, rows) => {
if (err) { console.log("Expired rows: " + rows);
if(err.errno == -4058) { if (err) return console.error(err);
console.log("File already deleted"); rows.forEach((row) => {
db.all("DELETE FROM media WHERE path = ?", [row.path], (err) => { console.log(`Deleting ${row.path}`);
if (err) return console.error(err); fs.unlink(`uploads/${row.path}`, (err) => {
}); if (err) {
} else { if(err.errno == -4058) {
console.error(err); console.log("File already deleted");
} db.all("DELETE FROM media WHERE path = ?", [row.path], (err) => {
} else { if (err) return console.error(err);
db.all("DELETE FROM media WHERE path = ?", [row.path], (err) => { });
if (err) return console.error(err); } else {
}); console.error(err);
} }
} else {
}); db.all("DELETE FROM media WHERE path = ?", [row.path], (err) => {
console.log(`Deleted ${row.path}`); if (err) return console.error(err);
}); });
}); }
} });
console.log(`Deleted ${row.path}`);
setInterval(prune, 1000 * 60 * 30); //prune every 30 minutes });
});
module.exports = app; }
setInterval(prune, 1000 * 60); //prune every minute
module.exports = app;

178
bin/www
View file

@ -1,89 +1,89 @@
#!/usr/bin/env node #!/usr/bin/env node
/** /**
* Module dependencies. * Module dependencies.
*/ */
var app = require('../app'); var app = require('../app');
var http = require('http'); var http = require('http');
/** /**
* Get port from environment and store in Express. * Get port from environment and store in Express.
*/ */
var port = normalizePort(process.env.EBPORT || '3000'); var port = normalizePort(process.env.EBPORT || '3000');
app.set('port', port); app.set('port', port);
/** /**
* Create HTTP server. * Create HTTP server.
*/ */
var server = http.createServer(app); var server = http.createServer(app);
/** /**
* Listen on provided port, on all network interfaces. * Listen on provided port, on all network interfaces.
*/ */
server.listen(port); server.listen(port);
server.on('error', onError); server.on('error', onError);
server.on('listening', onListening); server.on('listening', onListening);
/** /**
* Normalize a port into a number, string, or false. * Normalize a port into a number, string, or false.
*/ */
function normalizePort(val) { function normalizePort(val) {
var port = parseInt(val, 10); var port = parseInt(val, 10);
if (isNaN(port)) { if (isNaN(port)) {
// named pipe // named pipe
return val; return val;
} }
if (port >= 0) { if (port >= 0) {
// port number // port number
return port; return port;
} }
return false; return false;
} }
/** /**
* Event listener for HTTP server "error" event. * Event listener for HTTP server "error" event.
*/ */
function onError(error) { function onError(error) {
if (error.syscall !== 'listen') { if (error.syscall !== 'listen') {
throw error; throw error;
} }
var bind = typeof port === 'string' var bind = typeof port === 'string'
? 'Pipe ' + port ? 'Pipe ' + port
: 'Port ' + port; : 'Port ' + port;
// handle specific listen errors with friendly messages // handle specific listen errors with friendly messages
switch (error.code) { switch (error.code) {
case 'EACCES': case 'EACCES':
console.error(bind + ' requires elevated privileges'); console.error(bind + ' requires elevated privileges');
process.exit(1); process.exit(1);
break; break;
case 'EADDRINUSE': case 'EADDRINUSE':
console.error(bind + ' is already in use'); console.error(bind + ' is already in use');
process.exit(1); process.exit(1);
break; break;
default: default:
throw error; throw error;
} }
} }
/** /**
* Event listener for HTTP server "listening" event. * Event listener for HTTP server "listening" event.
*/ */
function onListening() { function onListening() {
var addr = server.address(); var addr = server.address();
var bind = typeof addr === 'string' var bind = typeof addr === 'string'
? 'pipe ' + addr ? 'pipe ' + addr
: 'port ' + addr.port; : 'port ' + addr.port;
console.log('Listening on ' + bind); console.log('Listening on ' + bind);
} }

66
db.js
View file

@ -1,33 +1,33 @@
const sqlite3 = require("sqlite3"); const sqlite3 = require("sqlite3");
const mkdirp = require("mkdirp"); const mkdirp = require("mkdirp");
const crypto = require("crypto"); const crypto = require("crypto");
mkdirp.sync("./var/db"); mkdirp.sync("./var/db");
let db = new sqlite3.Database("./var/db/media.db"); let db = new sqlite3.Database("./var/db/media.db");
db.serialize(function() { db.serialize(function() {
// create the database schema for the todos app // create the database schema for the todos app
db.run("CREATE TABLE IF NOT EXISTS users ( \ db.run("CREATE TABLE IF NOT EXISTS users ( \
id INTEGER PRIMARY KEY, \ id INTEGER PRIMARY KEY, \
username TEXT UNIQUE, \ username TEXT UNIQUE, \
hashed_password BLOB, \ hashed_password BLOB, \
salt BLOB \ salt BLOB \
)"); )");
db.run("CREATE TABLE IF NOT EXISTS media ( \ db.run("CREATE TABLE IF NOT EXISTS media ( \
id INTEGER PRIMARY KEY, \ id INTEGER PRIMARY KEY, \
path TEXT NOT NULL, \ path TEXT NOT NULL, \
expire INTEGER \ expire INTEGER \
)"); )");
// create an initial user (username: alice, password: letmein) // create an initial user (username: alice, password: letmein)
var salt = crypto.randomBytes(16); var salt = crypto.randomBytes(16);
db.run("INSERT OR IGNORE INTO users (username, hashed_password, salt) VALUES (?, ?, ?)", [ db.run("INSERT OR IGNORE INTO users (username, hashed_password, salt) VALUES (?, ?, ?)", [
"admin", "admin",
crypto.pbkdf2Sync(process.env.EBPASS || "changeme", salt, 310000, 32, "sha256"), crypto.pbkdf2Sync(process.env.EBPASS || "changeme", salt, 310000, 32, "sha256"),
salt salt
]); ]);
}); });
module.exports = db; module.exports = db;

24
docker-entrypoint.sh Executable file → Normal file
View file

@ -1,12 +1,12 @@
#!/bin/bash #!/bin/bash
# Exit build script on first failure. # Exit build script on first failure.
set -e set -e
# Exit on unset variable. # Exit on unset variable.
set -u set -u
set -x set -x
node db.js node db.js
npm start npm start

View file

@ -101,7 +101,6 @@ function handleUpload(req, res, next) {
for (let file in req.files) { for (let file in req.files) {
let currentdate = Date.now(); let currentdate = Date.now();
let expireDate = new Date(currentdate + (req.body.expire * 24 * 60 * 60 * 1000)); let expireDate = new Date(currentdate + (req.body.expire * 24 * 60 * 60 * 1000));
console.log(expireDate);
db.run("INSERT INTO media (path, expire) VALUES (?, ?)", [req.files[file].filename, expireDate], function (err) { db.run("INSERT INTO media (path, expire) VALUES (?, ?)", [req.files[file].filename, expireDate], function (err) {
if (err) { if (err) {
console.log(err); console.log(err);

11400
package-lock.json generated

File diff suppressed because it is too large Load diff

View file

@ -1,47 +1,47 @@
{ {
"name": "embedder", "name": "embedder",
"version": "0.0.0", "version": "0.0.0",
"private": true, "private": true,
"description": "Media host for quick embeds to sites like discord", "description": "Media host for quick embeds to sites like discord",
"keywords": [ "keywords": [
"example", "example",
"express", "express",
"passport", "passport",
"sqlite" "sqlite"
], ],
"author": { "author": {
"name": "Wavering Ana", "name": "Wavering Ana",
"email": "ana@nekomimi.pet", "email": "ana@nekomimi.pet",
"url": "https://l.nekomimi.pet/projects" "url": "https://l.nekomimi.pet/projects"
}, },
"homepage": "https://github.com/WaveringAna/embedder", "homepage": "https://github.com/WaveringAna/embedder",
"repository": { "repository": {
"type": "git", "type": "git",
"url": "git://github.com/WaveringAna/embedder.git" "url": "git://github.com/WaveringAna/embedder.git"
}, },
"license": "Unlicense", "license": "Unlicense",
"scripts": { "scripts": {
"start": "node ./bin/www" "start": "node ./bin/www"
}, },
"dependencies": { "dependencies": {
"@ffmpeg-installer/ffmpeg": "^1.1.0", "@ffmpeg-installer/ffmpeg": "^1.1.0",
"@ffprobe-installer/ffprobe": "^1.4.1", "@ffprobe-installer/ffprobe": "^1.4.1",
"connect-sqlite3": "^0.9.13", "connect-sqlite3": "^0.9.13",
"cookie-parser": "~1.4.4", "cookie-parser": "~1.4.4",
"dotenv": "^8.6.0", "dotenv": "^8.6.0",
"ejs": "^3.1.8", "ejs": "^3.1.8",
"express": "~4.16.1", "express": "~4.16.1",
"express-session": "^1.17.3", "express-session": "^1.17.3",
"fluent-ffmpeg": "^2.1.2", "fluent-ffmpeg": "^2.1.2",
"http-errors": "~1.6.3", "http-errors": "~1.6.3",
"mkdirp": "^1.0.4", "mkdirp": "^1.0.4",
"multer": "^1.4.5-lts.1", "multer": "^1.4.5-lts.1",
"passport": "^0.6.0", "passport": "^0.6.0",
"passport-local": "^1.0.0", "passport-local": "^1.0.0",
"probe-image-size": "^7.2.3", "probe-image-size": "^7.2.3",
"sqlite3": "^5.0.2" "sqlite3": "^5.0.2"
}, },
"devDependencies": { "devDependencies": {
"eslint": "^8.28.0" "eslint": "^8.28.0"
} }
} }

View file

@ -1,116 +1,116 @@
.nav { .nav {
position: absolute; position: absolute;
top: -130px; top: -130px;
right: 0; right: 0;
} }
.nav ul { .nav ul {
margin: 0; margin: 0;
list-style: none; list-style: none;
text-align: center; text-align: center;
} }
.nav li { .nav li {
display: inline-block; display: inline-block;
height: 40px; height: 40px;
margin-left: 12px; margin-left: 12px;
font-size: 14px; font-size: 14px;
font-weight: 400; font-weight: 400;
line-height: 40px; line-height: 40px;
} }
.nav a { .nav a {
display: block; display: block;
color: inherit; color: inherit;
text-decoration: none; text-decoration: none;
} }
.nav a:hover { .nav a:hover {
border-bottom: 1px solid #DB7676; border-bottom: 1px solid #DB7676;
} }
.nav button { .nav button {
height: 40px; height: 40px;
} }
.nav button:hover { .nav button:hover {
border-bottom: 1px solid #DB7676; border-bottom: 1px solid #DB7676;
cursor: pointer; cursor: pointer;
} }
/* background image by Cole Bemis <https://feathericons.com> */ /* background image by Cole Bemis <https://feathericons.com> */
.nav .user { .nav .user {
padding-left: 20px; padding-left: 20px;
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='18' height='18' viewBox='0 0 24 24' fill='none' stroke='currentColor' stroke-width='2' stroke-linecap='round' stroke-linejoin='round' class='feather feather-user'%3E%3Cpath d='M20 21v-2a4 4 0 0 0-4-4H8a4 4 0 0 0-4 4v2'%3E%3C/path%3E%3Ccircle cx='12' cy='7' r='4'%3E%3C/circle%3E%3C/svg%3E"); background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='18' height='18' viewBox='0 0 24 24' fill='none' stroke='currentColor' stroke-width='2' stroke-linecap='round' stroke-linejoin='round' class='feather feather-user'%3E%3Cpath d='M20 21v-2a4 4 0 0 0-4-4H8a4 4 0 0 0-4 4v2'%3E%3C/path%3E%3Ccircle cx='12' cy='7' r='4'%3E%3C/circle%3E%3C/svg%3E");
background-repeat: no-repeat; background-repeat: no-repeat;
background-position: center left; background-position: center left;
} }
/* background image by Cole Bemis <https://feathericons.com> */ /* background image by Cole Bemis <https://feathericons.com> */
.nav .logout { .nav .logout {
padding-left: 20px; padding-left: 20px;
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='18' height='18' viewBox='0 0 24 24' fill='none' stroke='currentColor' stroke-width='2' stroke-linecap='round' stroke-linejoin='round' class='feather feather-log-out'%3E%3Cpath d='M9 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h4'%3E%3C/path%3E%3Cpolyline points='16 17 21 12 16 7'%3E%3C/polyline%3E%3Cline x1='21' y1='12' x2='9' y2='12'%3E%3C/line%3E%3C/svg%3E%0A"); background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='18' height='18' viewBox='0 0 24 24' fill='none' stroke='currentColor' stroke-width='2' stroke-linecap='round' stroke-linejoin='round' class='feather feather-log-out'%3E%3Cpath d='M9 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h4'%3E%3C/path%3E%3Cpolyline points='16 17 21 12 16 7'%3E%3C/polyline%3E%3Cline x1='21' y1='12' x2='9' y2='12'%3E%3C/line%3E%3C/svg%3E%0A");
background-repeat: no-repeat; background-repeat: no-repeat;
background-position: center left; background-position: center left;
} }
#dropArea { #dropArea {
border: 2px dashed #ccc; border: 2px dashed #ccc;
border-radius: 20px; border-radius: 20px;
width: 100%; width: 100%;
font-family: sans-serif; font-family: sans-serif;
padding: 50px 0px 50px 0px; padding: 50px 0px 50px 0px;
} }
#dropArea.highlight { #dropArea.highlight {
border-color: purple; border-color: purple;
} }
.dragregion { .dragregion {
text-align: center; text-align: center;
} }
.image { .image {
width: 100%; width: 100%;
} }
div.nonmedia { div.nonmedia {
height: 100px; height: 100px;
width: 100%; width: 100%;
} }
div.nonmedia p { div.nonmedia p {
color: #444; color: #444;
font-size: 20px; font-size: 20px;
font-weight: 400; font-weight: 400;
line-height: 100px; line-height: 100px;
text-align: center; text-align: center;
} }
label { label {
text-align: center; text-align: center;
} }
.video { .video {
position: relative; position: relative;
} }
.video .overlay { .video .overlay {
position: absolute; position: absolute;
top: 0%; top: 0%;
left: 70%; left: 70%;
transform: translateY(0%) translateX(70%); transform: translateY(0%) translateX(70%);
z-index: 1; z-index: 1;
border-radius: 25px; border-radius: 25px;
border: 2px solid #73AD21; border: 2px solid #73AD21;
padding: 5px; padding: 5px;
background-color: #eee; background-color: #eee;
} }
.video .overlay a { .video .overlay a {
color: #73AD21; color: #73AD21;
font-size: 12px; font-size: 12px;
font-weight: 400; font-weight: 400;
line-height: 20px; line-height: 20px;
text-align: center; text-align: center;
} }

View file

@ -1,141 +1,141 @@
hr { hr {
margin: 20px 0; margin: 20px 0;
border: 0; border: 0;
border-top: 1px dashed #c5c5c5; border-top: 1px dashed #c5c5c5;
border-bottom: 1px dashed #f7f7f7; border-bottom: 1px dashed #f7f7f7;
} }
.learn a { .learn a {
font-weight: normal; font-weight: normal;
text-decoration: none; text-decoration: none;
color: #b83f45; color: #b83f45;
} }
.learn a:hover { .learn a:hover {
text-decoration: underline; text-decoration: underline;
color: #787e7e; color: #787e7e;
} }
.learn h3, .learn h3,
.learn h4, .learn h4,
.learn h5 { .learn h5 {
margin: 10px 0; margin: 10px 0;
font-weight: 500; font-weight: 500;
line-height: 1.2; line-height: 1.2;
color: #000; color: #000;
} }
.learn h3 { .learn h3 {
font-size: 24px; font-size: 24px;
} }
.learn h4 { .learn h4 {
font-size: 18px; font-size: 18px;
} }
.learn h5 { .learn h5 {
margin-bottom: 0; margin-bottom: 0;
font-size: 14px; font-size: 14px;
} }
.learn ul { .learn ul {
padding: 0; padding: 0;
margin: 0 0 30px 25px; margin: 0 0 30px 25px;
} }
.learn li { .learn li {
line-height: 20px; line-height: 20px;
} }
.learn p { .learn p {
font-size: 15px; font-size: 15px;
font-weight: 300; font-weight: 300;
line-height: 1.3; line-height: 1.3;
margin-top: 0; margin-top: 0;
margin-bottom: 0; margin-bottom: 0;
} }
#issue-count { #issue-count {
display: none; display: none;
} }
.quote { .quote {
border: none; border: none;
margin: 20px 0 60px 0; margin: 20px 0 60px 0;
} }
.quote p { .quote p {
font-style: italic; font-style: italic;
} }
.quote p:before { .quote p:before {
content: '“'; content: '“';
font-size: 50px; font-size: 50px;
opacity: .15; opacity: .15;
position: absolute; position: absolute;
top: -20px; top: -20px;
left: 3px; left: 3px;
} }
.quote p:after { .quote p:after {
content: '”'; content: '”';
font-size: 50px; font-size: 50px;
opacity: .15; opacity: .15;
position: absolute; position: absolute;
bottom: -42px; bottom: -42px;
right: 3px; right: 3px;
} }
.quote footer { .quote footer {
position: absolute; position: absolute;
bottom: -40px; bottom: -40px;
right: 0; right: 0;
} }
.quote footer img { .quote footer img {
border-radius: 3px; border-radius: 3px;
} }
.quote footer a { .quote footer a {
margin-left: 5px; margin-left: 5px;
vertical-align: middle; vertical-align: middle;
} }
.speech-bubble { .speech-bubble {
position: relative; position: relative;
padding: 10px; padding: 10px;
background: rgba(0, 0, 0, .04); background: rgba(0, 0, 0, .04);
border-radius: 5px; border-radius: 5px;
} }
.speech-bubble:after { .speech-bubble:after {
content: ''; content: '';
position: absolute; position: absolute;
top: 100%; top: 100%;
right: 30px; right: 30px;
border: 13px solid transparent; border: 13px solid transparent;
border-top-color: rgba(0, 0, 0, .04); border-top-color: rgba(0, 0, 0, .04);
} }
.learn-bar > .learn { .learn-bar > .learn {
position: absolute; position: absolute;
width: 272px; width: 272px;
top: 8px; top: 8px;
left: -300px; left: -300px;
padding: 10px; padding: 10px;
border-radius: 5px; border-radius: 5px;
background-color: rgba(255, 255, 255, .6); background-color: rgba(255, 255, 255, .6);
transition-property: left; transition-property: left;
transition-duration: 500ms; transition-duration: 500ms;
} }
@media (min-width: 899px) { @media (min-width: 899px) {
.learn-bar { .learn-bar {
width: auto; width: auto;
padding-left: 300px; padding-left: 300px;
} }
.learn-bar > .learn { .learn-bar > .learn {
left: 8px; left: 8px;
} }
} }

View file

@ -1,42 +1,42 @@
.todohome { .todohome {
margin: 130px 0 40px 0; margin: 130px 0 40px 0;
position: relative; position: relative;
} }
.todohome h1 { .todohome h1 {
position: absolute; position: absolute;
top: -140px; top: -140px;
width: 100%; width: 100%;
font-size: 80px; font-size: 80px;
font-weight: 200; font-weight: 200;
text-align: center; text-align: center;
color: #b83f45; color: #b83f45;
-webkit-text-rendering: optimizeLegibility; -webkit-text-rendering: optimizeLegibility;
-moz-text-rendering: optimizeLegibility; -moz-text-rendering: optimizeLegibility;
text-rendering: optimizeLegibility; text-rendering: optimizeLegibility;
} }
.todohome section { .todohome section {
padding-top: 1px; padding-top: 1px;
text-align: center; text-align: center;
} }
.todohome h2 { .todohome h2 {
padding-bottom: 48px; padding-bottom: 48px;
font-size: 28px; font-size: 28px;
font-weight: 300; font-weight: 300;
line-height: 1.5; line-height: 1.5;
} }
.todohome .button { .todohome .button {
padding: 13px 45px; padding: 13px 45px;
font-size: 16px; font-size: 16px;
font-weight: 500; font-weight: 500;
color: white; color: white;
border-radius: 5px; border-radius: 5px;
background: #d83f45; background: #d83f45;
} }
.todohome a.button { .todohome a.button {
text-decoration: none; text-decoration: none;
} }

View file

@ -1,393 +1,393 @@
html, html,
body { body {
margin: 0; margin: 0;
padding: 0; padding: 0;
} }
button { button {
margin: 0; margin: 0;
padding: 0; padding: 0;
border: 0; border: 0;
background: none; background: none;
font-size: 100%; font-size: 100%;
vertical-align: baseline; vertical-align: baseline;
font-family: inherit; font-family: inherit;
font-weight: inherit; font-weight: inherit;
color: inherit; color: inherit;
-webkit-appearance: none; -webkit-appearance: none;
appearance: none; appearance: none;
-webkit-font-smoothing: antialiased; -webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale; -moz-osx-font-smoothing: grayscale;
} }
body { body {
font: 14px 'Helvetica Neue', Helvetica, Arial, sans-serif; font: 14px 'Helvetica Neue', Helvetica, Arial, sans-serif;
line-height: 1.4em; line-height: 1.4em;
background: #111111; background: #111111;
color: #f5f5f5; color: #f5f5f5;
min-width: 230px; min-width: 230px;
max-width: 550px; max-width: 550px;
margin: 0 auto; margin: 0 auto;
-webkit-font-smoothing: antialiased; -webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale; -moz-osx-font-smoothing: grayscale;
font-weight: 300; font-weight: 300;
} }
.view .header { .view .header {
background: #121122; background: #121122;
} }
.hidden { .hidden {
display: none; display: none;
} }
.todoapp { .todoapp {
background: #121212; background: #121212;
margin: 130px 0 40px 0; margin: 130px 0 40px 0;
position: relative; position: relative;
box-shadow: 0 2px 4px 0 rgba(0, 0, 0, 0.2), box-shadow: 0 2px 4px 0 rgba(0, 0, 0, 0.2),
0 25px 50px 0 rgba(0, 0, 0, 0.1); 0 25px 50px 0 rgba(0, 0, 0, 0.1);
} }
.todoapp input::-webkit-input-placeholder { .todoapp input::-webkit-input-placeholder {
font-style: italic; font-style: italic;
font-weight: 400; font-weight: 400;
color: #f5f5f5; color: #f5f5f5;
text-align: center; text-align: center;
} }
.todoapp input::-moz-placeholder { .todoapp input::-moz-placeholder {
font-style: italic; font-style: italic;
font-weight: 400; font-weight: 400;
color: #f5f5f5; color: #f5f5f5;
text-align: center; text-align: center;
} }
.todoapp input::input-placeholder { .todoapp input::input-placeholder {
font-style: italic; font-style: italic;
font-weight: 400; font-weight: 400;
color: #f5f5f5; color: #f5f5f5;
text-align: center; text-align: center;
} }
.todoapp h1 { .todoapp h1 {
position: absolute; position: absolute;
top: -140px; top: -140px;
width: 100%; width: 100%;
font-size: 80px; font-size: 80px;
font-weight: 200; font-weight: 200;
text-align: center; text-align: center;
color: #b83f45; color: #b83f45;
-webkit-text-rendering: optimizeLegibility; -webkit-text-rendering: optimizeLegibility;
-moz-text-rendering: optimizeLegibility; -moz-text-rendering: optimizeLegibility;
text-rendering: optimizeLegibility; text-rendering: optimizeLegibility;
} }
.new-todo, .new-todo,
.edit { .edit {
position: relative; position: relative;
margin: 0; margin: 0;
width: 100%; width: 100%;
font-size: 24px; font-size: 24px;
font-family: inherit; font-family: inherit;
font-weight: inherit; font-weight: inherit;
line-height: 1.4em; line-height: 1.4em;
color: inherit; color: inherit;
padding: 6px; padding: 6px;
-webkit-font-smoothing: antialiased; -webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale; -moz-osx-font-smoothing: grayscale;
} }
.new-todo { .new-todo {
padding: 16px 16px 16px 60px; padding: 16px 16px 16px 60px;
height: 65px; height: 65px;
border: none; border: none;
background: rgba(0, 0, 0, 0.003); background: rgba(0, 0, 0, 0.003);
box-shadow: inset 0 -2px 1px rgba(0,0,0,0.03); box-shadow: inset 0 -2px 1px rgba(0,0,0,0.03);
} }
.main { .main {
position: relative; position: relative;
z-index: 2; z-index: 2;
} }
.toggle-all { .toggle-all {
width: 1px; width: 1px;
height: 1px; height: 1px;
border: none; /* Mobile Safari */ border: none; /* Mobile Safari */
opacity: 0; opacity: 0;
position: absolute; position: absolute;
right: 100%; right: 100%;
bottom: 100%; bottom: 100%;
} }
.toggle-all + label { .toggle-all + label {
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
width: 45px; width: 45px;
height: 65px; height: 65px;
font-size: 0; font-size: 0;
position: absolute; position: absolute;
top: -65px; top: -65px;
left: -0; left: -0;
} }
.toggle-all + label:before { .toggle-all + label:before {
content: ''; content: '';
display: inline-block; display: inline-block;
font-size: 22px; font-size: 22px;
color: #949494; color: #949494;
padding: 10px 27px 10px 27px; padding: 10px 27px 10px 27px;
-webkit-transform: rotate(90deg); -webkit-transform: rotate(90deg);
transform: rotate(90deg); transform: rotate(90deg);
} }
.toggle-all:checked + label:before { .toggle-all:checked + label:before {
color: #484848; color: #484848;
} }
.todo-list { .todo-list {
margin: 0; margin: 0;
padding: 0; padding: 0;
list-style: none; list-style: none;
} }
.todo-list li { .todo-list li {
position: relative; position: relative;
font-size: 24px; font-size: 24px;
} }
.todo-list li:last-child { .todo-list li:last-child {
border-bottom: none; border-bottom: none;
} }
.todo-list li.editing { .todo-list li.editing {
border-bottom: none; border-bottom: none;
padding: 0; padding: 0;
} }
.todo-list li.editing .edit { .todo-list li.editing .edit {
display: block; display: block;
width: calc(100% - 43px); width: calc(100% - 43px);
padding: 12px 16px; padding: 12px 16px;
margin: 0 0 0 43px; margin: 0 0 0 43px;
} }
.todo-list li.editing .view { .todo-list li.editing .view {
display: none; display: none;
} }
.todo-list li .toggle { .todo-list li .toggle {
text-align: center; text-align: center;
width: 40px; width: 40px;
/* auto, since non-WebKit browsers doesn't support input styling */ /* auto, since non-WebKit browsers doesn't support input styling */
height: auto; height: auto;
position: absolute; position: absolute;
top: 0; top: 0;
bottom: 0; bottom: 0;
margin: auto 0; margin: auto 0;
border: none; /* Mobile Safari */ border: none; /* Mobile Safari */
-webkit-appearance: none; -webkit-appearance: none;
appearance: none; appearance: none;
} }
.todo-list li .toggle { .todo-list li .toggle {
opacity: 0; opacity: 0;
} }
.todo-list li .toggle + label { .todo-list li .toggle + label {
/* /*
Firefox requires `#` to be escaped - https://bugzilla.mozilla.org/show_bug.cgi?id=922433 Firefox requires `#` to be escaped - https://bugzilla.mozilla.org/show_bug.cgi?id=922433
IE and Edge requires *everything* to be escaped to render, so we do that instead of just the `#` - https://developer.microsoft.com/en-us/microsoft-edge/platform/issues/7157459/ IE and Edge requires *everything* to be escaped to render, so we do that instead of just the `#` - https://developer.microsoft.com/en-us/microsoft-edge/platform/issues/7157459/
*/ */
background-image: url('data:image/svg+xml;utf8,%3Csvg%20xmlns%3D%22http%3A//www.w3.org/2000/svg%22%20width%3D%2240%22%20height%3D%2240%22%20viewBox%3D%22-10%20-18%20100%20135%22%3E%3Ccircle%20cx%3D%2250%22%20cy%3D%2250%22%20r%3D%2250%22%20fill%3D%22none%22%20stroke%3D%22%23949494%22%20stroke-width%3D%223%22/%3E%3C/svg%3E'); background-image: url('data:image/svg+xml;utf8,%3Csvg%20xmlns%3D%22http%3A//www.w3.org/2000/svg%22%20width%3D%2240%22%20height%3D%2240%22%20viewBox%3D%22-10%20-18%20100%20135%22%3E%3Ccircle%20cx%3D%2250%22%20cy%3D%2250%22%20r%3D%2250%22%20fill%3D%22none%22%20stroke%3D%22%23949494%22%20stroke-width%3D%223%22/%3E%3C/svg%3E');
background-repeat: no-repeat; background-repeat: no-repeat;
background-position: center left; background-position: center left;
} }
.todo-list li .toggle:checked + label { .todo-list li .toggle:checked + label {
background-image: url('data:image/svg+xml;utf8,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20width%3D%2240%22%20height%3D%2240%22%20viewBox%3D%22-10%20-18%20100%20135%22%3E%3Ccircle%20cx%3D%2250%22%20cy%3D%2250%22%20r%3D%2250%22%20fill%3D%22none%22%20stroke%3D%22%2359A193%22%20stroke-width%3D%223%22%2F%3E%3Cpath%20fill%3D%22%233EA390%22%20d%3D%22M72%2025L42%2071%2027%2056l-4%204%2020%2020%2034-52z%22%2F%3E%3C%2Fsvg%3E'); background-image: url('data:image/svg+xml;utf8,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20width%3D%2240%22%20height%3D%2240%22%20viewBox%3D%22-10%20-18%20100%20135%22%3E%3Ccircle%20cx%3D%2250%22%20cy%3D%2250%22%20r%3D%2250%22%20fill%3D%22none%22%20stroke%3D%22%2359A193%22%20stroke-width%3D%223%22%2F%3E%3Cpath%20fill%3D%22%233EA390%22%20d%3D%22M72%2025L42%2071%2027%2056l-4%204%2020%2020%2034-52z%22%2F%3E%3C%2Fsvg%3E');
} }
.todo-list li label { .todo-list li label {
word-break: break-all; word-break: break-all;
padding: 15px 15px 15px 60px; padding: 15px 15px 15px 60px;
display: block; display: block;
line-height: 1.2; line-height: 1.2;
transition: color 0.4s; transition: color 0.4s;
font-weight: 400; font-weight: 400;
color: #BB86FC; color: #BB86FC;
} }
.todo-list li.completed label { .todo-list li.completed label {
color: #949494; color: #949494;
text-decoration: line-through; text-decoration: line-through;
} }
.todo-list li .destroy { .todo-list li .destroy {
display: none; display: none;
position: absolute; position: absolute;
top: 0; top: 0;
right: 10px; right: 10px;
bottom: 0; bottom: 0;
width: 40px; width: 40px;
height: 40px; height: 40px;
margin: auto 0; margin: auto 0;
font-size: 30px; font-size: 30px;
color: #949494; color: #949494;
transition: color 0.2s ease-out; transition: color 0.2s ease-out;
} }
.todo-list li .destroy:hover, .todo-list li .destroy:hover,
.todo-list li .destroy:focus { .todo-list li .destroy:focus {
color: #C18585; color: #C18585;
} }
.todo-list li .destroy:after { .todo-list li .destroy:after {
content: '×'; content: '×';
display: block; display: block;
height: 100%; height: 100%;
line-height: 1.1; line-height: 1.1;
} }
.todo-list li:hover .destroy { .todo-list li:hover .destroy {
display: block; display: block;
} }
.todo-list li .edit { .todo-list li .edit {
display: none; display: none;
} }
.todo-list li.editing:last-child { .todo-list li.editing:last-child {
margin-bottom: -1px; margin-bottom: -1px;
} }
.footer { .footer {
padding: 10px 15px; padding: 10px 15px;
height: 20px; height: 20px;
text-align: center; text-align: center;
font-size: 15px; font-size: 15px;
border-top: 1px solid #e6e6e6; border-top: 1px solid #e6e6e6;
} }
.footer:before { .footer:before {
content: ''; content: '';
position: absolute; position: absolute;
right: 0; right: 0;
bottom: 0; bottom: 0;
left: 0; left: 0;
height: 50px; height: 50px;
overflow: hidden; overflow: hidden;
box-shadow: 0 1px 1px rgba(0, 0, 0, 0.2), box-shadow: 0 1px 1px rgba(0, 0, 0, 0.2),
0 8px 0 -3px #f6f6f6, 0 8px 0 -3px #f6f6f6,
0 9px 1px -3px rgba(0, 0, 0, 0.2), 0 9px 1px -3px rgba(0, 0, 0, 0.2),
0 16px 0 -6px #f6f6f6, 0 16px 0 -6px #f6f6f6,
0 17px 2px -6px rgba(0, 0, 0, 0.2); 0 17px 2px -6px rgba(0, 0, 0, 0.2);
} }
.todo-count { .todo-count {
float: left; float: left;
text-align: left; text-align: left;
} }
.todo-count strong { .todo-count strong {
font-weight: 300; font-weight: 300;
} }
.filters { .filters {
margin: 0; margin: 0;
padding: 0; padding: 0;
list-style: none; list-style: none;
position: absolute; position: absolute;
right: 0; right: 0;
left: 0; left: 0;
} }
.filters li { .filters li {
display: inline; display: inline;
} }
.filters li a { .filters li a {
color: inherit; color: inherit;
margin: 3px; margin: 3px;
padding: 3px 7px; padding: 3px 7px;
text-decoration: none; text-decoration: none;
border: 1px solid transparent; border: 1px solid transparent;
border-radius: 3px; border-radius: 3px;
} }
.filters li a:hover { .filters li a:hover {
border-color: #DB7676; border-color: #DB7676;
} }
.filters li a.selected { .filters li a.selected {
border-color: #CE4646; border-color: #CE4646;
} }
.clear-completed, .clear-completed,
html .clear-completed:active { html .clear-completed:active {
float: right; float: right;
position: relative; position: relative;
line-height: 19px; line-height: 19px;
text-decoration: none; text-decoration: none;
cursor: pointer; cursor: pointer;
} }
.clear-completed:hover { .clear-completed:hover {
text-decoration: underline; text-decoration: underline;
} }
.info { .info {
margin: 65px auto 0; margin: 65px auto 0;
color: #4d4d4d; color: #4d4d4d;
font-size: 11px; font-size: 11px;
text-shadow: 0 1px 0 rgba(255, 255, 255, 0.5); text-shadow: 0 1px 0 rgba(255, 255, 255, 0.5);
text-align: center; text-align: center;
} }
.info p { .info p {
line-height: 1; line-height: 1;
} }
.info a { .info a {
color: inherit; color: inherit;
text-decoration: none; text-decoration: none;
font-weight: 400; font-weight: 400;
} }
.info a:hover { .info a:hover {
text-decoration: underline; text-decoration: underline;
} }
/* /*
Hack to remove background from Mobile Safari. Hack to remove background from Mobile Safari.
Can't use it globally since it destroys checkboxes in Firefox Can't use it globally since it destroys checkboxes in Firefox
*/ */
@media screen and (-webkit-min-device-pixel-ratio:0) { @media screen and (-webkit-min-device-pixel-ratio:0) {
.toggle-all, .toggle-all,
.todo-list li .toggle { .todo-list li .toggle {
background: none; background: none;
} }
.todo-list li .toggle { .todo-list li .toggle {
height: 40px; height: 40px;
} }
} }
@media (max-width: 430px) { @media (max-width: 430px) {
.footer { .footer {
height: 50px; height: 50px;
} }
.filters { .filters {
bottom: 10px; bottom: 10px;
} }
} }
:focus, :focus,
.toggle:focus + label, .toggle:focus + label,
.toggle-all:focus + label { .toggle-all:focus + label {
box-shadow: 0 0 2px 2px #CF7D7D; box-shadow: 0 0 2px 2px #CF7D7D;
outline: 0; outline: 0;
} }

View file

@ -1,116 +1,116 @@
.prompt { .prompt {
max-width: 400px; max-width: 400px;
margin: 50px auto; margin: 50px auto;
padding: 25px; padding: 25px;
background: #11111; background: #11111;
border: 1px solid #e6e6e6; border: 1px solid #e6e6e6;
border-radius: 8px; border-radius: 8px;
} }
button { button {
display: block; display: block;
padding: 10px; padding: 10px;
width: 100%; width: 100%;
border-radius: 3px; border-radius: 3px;
background: #d83f45; background: #d83f45;
font-size: 14px; font-size: 14px;
font-weight: 700; font-weight: 700;
color: white; color: white;
cursor: pointer; cursor: pointer;
} }
a.button { a.button {
box-sizing: border-box; box-sizing: border-box;
display: block; display: block;
padding: 10px; padding: 10px;
width: 100%; width: 100%;
border-radius: 3px; border-radius: 3px;
background: #000; background: #000;
font-size: 14px; font-size: 14px;
font-weight: 700; font-weight: 700;
text-align: center; text-align: center;
text-decoration: none; text-decoration: none;
color: white; color: white;
} }
a.google { a.google {
background: #4787ed; background: #4787ed;
} }
a.facebook { a.facebook {
background: #4267b2; background: #4267b2;
} }
button:hover { button:hover {
background-color: #c83f45; background-color: #c83f45;
} }
h1 { h1 {
margin: 0 0 20px 0; margin: 0 0 20px 0;
padding: 0 0 5px 0; padding: 0 0 5px 0;
font-size: 24px; font-size: 24px;
font-weight: 500; font-weight: 500;
} }
h3 { h3 {
margin-top: 0; margin-top: 0;
font-size: 24px; font-size: 24px;
font-weight: 300; font-weight: 300;
text-align: center; text-align: center;
color: #b83f45; color: #b83f45;
} }
form section { form section {
margin: 0 0 20px 0; margin: 0 0 20px 0;
position: relative; /* for password toggle positioning */ position: relative; /* for password toggle positioning */
} }
label { label {
display: block; display: block;
margin: 0 0 3px 0; margin: 0 0 3px 0;
font-size: 14px; font-size: 14px;
font-weight: 500; font-weight: 500;
} }
input { input {
box-sizing: border-box; box-sizing: border-box;
width: 100%; width: 100%;
padding: 10px; padding: 10px;
font-size: 14px; font-size: 14px;
border: 1px solid #d9d9d9; border: 1px solid #d9d9d9;
border-radius: 5px; border-radius: 5px;
background-color:#200; background-color:#200;
color:#fff; color:#fff;
} }
input[type=email]:not(:focus):invalid, input[type=email]:not(:focus):invalid,
input[type=password]:not(:focus):invalid { input[type=password]:not(:focus):invalid {
color: red; color: red;
outline-color: red; outline-color: red;
} }
hr { hr {
border-top: 1px solid #d9d9d9; border-top: 1px solid #d9d9d9;
border-bottom: none; border-bottom: none;
} }
p.instructions { p.instructions {
font-weight: 400; font-weight: 400;
} }
p.help { p.help {
text-align: center; text-align: center;
font-weight: 400; font-weight: 400;
} }
/* background image by Cole Bemis <https://feathericons.com> */ /* background image by Cole Bemis <https://feathericons.com> */
.messages p { .messages p {
font-size: 14px; font-size: 14px;
font-weight: 400; font-weight: 400;
line-height: 1.3; line-height: 1.3;
color: #d83f45; color: #d83f45;
padding-left: 20px; padding-left: 20px;
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' viewBox='0 0 24 24' fill='none' stroke='%23d83f45' stroke-width='2' stroke-linecap='round' stroke-linejoin='round' class='feather feather-alert-circle'%3E%3Ccircle cx='12' cy='12' r='10'%3E%3C/circle%3E%3Cline x1='12' y1='8' x2='12' y2='12'%3E%3C/line%3E%3Cline x1='12' y1='16' x2='12.01' y2='16'%3E%3C/line%3E%3C/svg%3E"); background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' viewBox='0 0 24 24' fill='none' stroke='%23d83f45' stroke-width='2' stroke-linecap='round' stroke-linejoin='round' class='feather feather-alert-circle'%3E%3Ccircle cx='12' cy='12' r='10'%3E%3C/circle%3E%3Cline x1='12' y1='8' x2='12' y2='12'%3E%3C/line%3E%3Cline x1='12' y1='16' x2='12.01' y2='16'%3E%3C/line%3E%3C/svg%3E");
background-repeat: no-repeat; background-repeat: no-repeat;
background-position: center left; background-position: center left;
} }

View file

@ -1,143 +1,143 @@
/* eslint-env browser: true */ /* eslint-env browser: true */
function copyURI(evt) { function copyURI(evt) {
evt.preventDefault(); evt.preventDefault();
navigator.clipboard.writeText(absolutePath(evt.target.getAttribute("src"))).then(() => { navigator.clipboard.writeText(absolutePath(evt.target.getAttribute("src"))).then(() => {
/* clipboard successfully set */ /* clipboard successfully set */
console.log("copied"); console.log("copied");
}, () => { }, () => {
/* clipboard write failed */ /* clipboard write failed */
console.log("failed"); console.log("failed");
}); });
} }
function copyA(evt) { function copyA(evt) {
evt.preventDefault(); evt.preventDefault();
navigator.clipboard.writeText(absolutePath(evt.target.getAttribute("href"))).then(() => { navigator.clipboard.writeText(absolutePath(evt.target.getAttribute("href"))).then(() => {
console.log("copied"); console.log("copied");
}, () => { }, () => {
console.log("failed"); console.log("failed");
}); });
} }
function copyPath(evt) { function copyPath(evt) {
navigator.clipboard.writeText(absolutePath(evt)).then(() => { navigator.clipboard.writeText(absolutePath(evt)).then(() => {
console.log("copied"); console.log("copied");
}, () => { }, () => {
console.log("failed"); console.log("failed");
}); });
} }
function absolutePath (href) { function absolutePath (href) {
let link = document.createElement("a"); let link = document.createElement("a");
link.href = href; link.href = href;
return link.href; return link.href;
} }
function extension(string) { function extension(string) {
return string.slice((string.lastIndexOf(".") - 2 >>> 0) + 2); return string.slice((string.lastIndexOf(".") - 2 >>> 0) + 2);
} }
let dropArea = document.getElementById("dropArea"); let dropArea = document.getElementById("dropArea");
["dragenter", "dragover", "dragleave", "drop"].forEach(eventName => { ["dragenter", "dragover", "dragleave", "drop"].forEach(eventName => {
dropArea.addEventListener(eventName, preventDefaults, false); dropArea.addEventListener(eventName, preventDefaults, false);
}); });
function preventDefaults (e) { function preventDefaults (e) {
e.preventDefault(); e.preventDefault();
e.stopPropagation(); e.stopPropagation();
} }
["dragenter", "dragover"].forEach(eventName => { ["dragenter", "dragover"].forEach(eventName => {
dropArea.addEventListener(eventName, highlight, false); dropArea.addEventListener(eventName, highlight, false);
}) })
;["dragleave", "drop"].forEach(eventName => { ;["dragleave", "drop"].forEach(eventName => {
dropArea.addEventListener(eventName, unhighlight, false); dropArea.addEventListener(eventName, unhighlight, false);
}); });
function highlight(e) { function highlight(e) {
dropArea.classList.add("highlight"); dropArea.classList.add("highlight");
} }
function unhighlight(e) { function unhighlight(e) {
dropArea.classList.remove("highlight"); dropArea.classList.remove("highlight");
} }
dropArea.addEventListener("drop", handleDrop, false); dropArea.addEventListener("drop", handleDrop, false);
window.addEventListener("paste", handlePaste); window.addEventListener("paste", handlePaste);
function handleDrop(e) { function handleDrop(e) {
let dt = e.dataTransfer; let dt = e.dataTransfer;
let files = dt.files; let files = dt.files;
handleFiles(files); handleFiles(files);
} }
function handlePaste(e) { function handlePaste(e) {
// Get the data of clipboard // Get the data of clipboard
const clipboardItems = e.clipboardData.items; const clipboardItems = e.clipboardData.items;
const items = [].slice.call(clipboardItems).filter(function (item) { const items = [].slice.call(clipboardItems).filter(function (item) {
// Filter the image items only // Filter the image items only
return item.type.indexOf("image") !== -1; return item.type.indexOf("image") !== -1;
}); });
if (items.length === 0) { if (items.length === 0) {
return; return;
} }
const item = items[0]; const item = items[0];
// Get the blob of image // Get the blob of image
const blob = item.getAsFile(); const blob = item.getAsFile();
console.log(blob); console.log(blob);
uploadFile(blob); uploadFile(blob);
previewFile(blob); previewFile(blob);
} }
function handleFiles(files) { function handleFiles(files) {
files = [...files]; files = [...files];
files.forEach(uploadFile); files.forEach(uploadFile);
files.forEach(previewFile); files.forEach(previewFile);
} }
function previewFile(file) { function previewFile(file) {
let reader = new FileReader(); let reader = new FileReader();
reader.readAsDataURL(file); reader.readAsDataURL(file);
reader.onloadend = function() { reader.onloadend = function() {
let img = document.createElement("img"); let img = document.createElement("img");
img.src = reader.result; img.src = reader.result;
img.className = "image"; img.className = "image";
document.getElementById("gallery").appendChild(img); document.getElementById("gallery").appendChild(img);
console.log(document.getElementById("fileupload")); console.log(document.getElementById("fileupload"));
document.getElementById("fileupload").src = img.src; document.getElementById("fileupload").src = img.src;
}; };
} }
function uploadFile(file) { function uploadFile(file) {
let xhr = new XMLHttpRequest(); let xhr = new XMLHttpRequest();
let formData = new FormData(); let formData = new FormData();
let reader = new FileReader(); let reader = new FileReader();
xhr.open("POST", "/", true); xhr.open("POST", "/", true);
xhr.addEventListener("readystatechange", function(e) { xhr.addEventListener("readystatechange", function(e) {
if (xhr.readyState == 4 && xhr.status == 200) { if (xhr.readyState == 4 && xhr.status == 200) {
location.reload(); location.reload();
} }
else if (xhr.readyState == 4 && xhr.status != 200) { else if (xhr.readyState == 4 && xhr.status != 200) {
// Error. Inform the user // Error. Inform the user
} }
}); });
if (file == null || file == undefined) { if (file == null || file == undefined) {
//file = reader.readAsDataURL(document.getElementById("fileupload").files[0]); //file = reader.readAsDataURL(document.getElementById("fileupload").files[0]);
//file = reader.readAsDataURL(document.querySelector("#fileupload").files[0]); //file = reader.readAsDataURL(document.querySelector("#fileupload").files[0]);
file = document.querySelector("#fileupload").files[0]; file = document.querySelector("#fileupload").files[0];
} }
formData.append("fileupload", file); formData.append("fileupload", file);
formData.append("expire", document.getElementById("expire").value); formData.append("expire", document.getElementById("expire").value);
console.log(formData); console.log(formData);
xhr.send(formData); xhr.send(formData);
} }

View file

@ -1,68 +1,68 @@
let crypto = require("crypto"); let crypto = require("crypto");
let express = require("express"); let express = require("express");
let passport = require("passport"); let passport = require("passport");
let LocalStrategy = require("passport-local"); let LocalStrategy = require("passport-local");
let db = require("../db"); let db = require("../db");
let router = express.Router(); let router = express.Router();
passport.use(new LocalStrategy(function verify(username, password, cb) { passport.use(new LocalStrategy(function verify(username, password, cb) {
db.get("SELECT * FROM users WHERE username = ?", [username], function(err, row) { db.get("SELECT * FROM users WHERE username = ?", [username], function(err, row) {
if (err) { if (err) {
return cb(err); return cb(err);
} }
if (!row) { if (!row) {
return cb(null, false, { return cb(null, false, {
message: "Incorrect username or password." message: "Incorrect username or password."
}); });
} }
crypto.pbkdf2(password, row.salt, 310000, 32, "sha256", function(err, hashedPassword) { crypto.pbkdf2(password, row.salt, 310000, 32, "sha256", function(err, hashedPassword) {
if (err) { if (err) {
return cb(err); return cb(err);
} }
if (!crypto.timingSafeEqual(row.hashed_password, hashedPassword)) { if (!crypto.timingSafeEqual(row.hashed_password, hashedPassword)) {
return cb(null, false, { return cb(null, false, {
message: "Incorrect username or password." message: "Incorrect username or password."
}); });
} }
return cb(null, row); return cb(null, row);
}); });
}); });
})); }));
passport.serializeUser(function(user, cb) { passport.serializeUser(function(user, cb) {
process.nextTick(function() { process.nextTick(function() {
cb(null, { cb(null, {
id: user.id, id: user.id,
username: user.username username: user.username
}); });
}); });
}); });
passport.deserializeUser(function(user, cb) { passport.deserializeUser(function(user, cb) {
process.nextTick(function() { process.nextTick(function() {
return cb(null, user); return cb(null, user);
}); });
}); });
router.get("/login", function(req, res) { router.get("/login", function(req, res) {
res.render("login"); res.render("login");
}); });
router.post("/login/password", passport.authenticate("local", { router.post("/login/password", passport.authenticate("local", {
successRedirect: "/", successRedirect: "/",
failureRedirect: "/login" failureRedirect: "/login"
})); }));
router.post("/logout", function(req, res, next) { router.post("/logout", function(req, res, next) {
req.logout(function(err) { req.logout(function(err) {
if (err) { if (err) {
return next(err); return next(err);
} }
res.redirect("/"); res.redirect("/");
}); });
}); });
module.exports = router; module.exports = router;

View file

@ -1 +1 @@
<h1><%= error %></h1> <h1><%= error %></h1>

View file

@ -1,57 +1,57 @@
<% <%
function extension(str){ function extension(str){
let file = str.split('/').pop(); let file = str.split('/').pop();
return [file.substr(0,file.lastIndexOf('.')),file.substr(file.lastIndexOf('.'),file.length).toLowerCase()] return [file.substr(0,file.lastIndexOf('.')),file.substr(file.lastIndexOf('.'),file.length).toLowerCase()]
} }
%> %>
<!DOCTYPE html> <!DOCTYPE html>
<html> <html>
<head> <head>
<% if (extension(url)[1] == ".mp4" || extension(url)[1] == ".gif") { %> <% if (extension(url)[1] == ".mp4" || extension(url)[1] == ".gif") { %>
<meta name="twitter:image" content="<%= host %>/uploads/<%= extension(url)[0] %>.gif"> <meta name="twitter:image" content="<%= host %>/uploads/<%= extension(url)[0] %>.gif">
<meta name="twitter:card" content="player"></meta> <meta name="twitter:card" content="player"></meta>
<meta name="twitter:player" content="<%= host %>/uploads/<%= extension(url)[0] %>.mp4"> <meta name="twitter:player" content="<%= host %>/uploads/<%= extension(url)[0] %>.mp4">
<meta name="twitter:player:width" content="<%= width %>"></meta> <meta name="twitter:player:width" content="<%= width %>"></meta>
<meta name="twitter:player:height" content="<%= height %>"></meta> <meta name="twitter:player:height" content="<%= height %>"></meta>
<meta name="twitter:player:stream" content="<%= host %>/uploads/<%= extension(url)[0] %>.mp4"> <meta name="twitter:player:stream" content="<%= host %>/uploads/<%= extension(url)[0] %>.mp4">
<meta name="twitter:player:stream:content_type" content="text/mp4"></meta> <meta name="twitter:player:stream:content_type" content="text/mp4"></meta>
<link rel="alternate" type="application/json+oembed" href="<%= host %>/uploads/oembed-<%= extension(url)[0]+extension(url)[1] %>.json"></link> <link rel="alternate" type="application/json+oembed" href="<%= host %>/uploads/oembed-<%= extension(url)[0]+extension(url)[1] %>.json"></link>
<meta property="og:title" content="<%= extension(url)[0] %>.gif"></meta> <meta property="og:title" content="<%= extension(url)[0] %>.gif"></meta>
<meta property="og:url" content="<%= host %>/uploads/<%= extension(url)[0] %>.gif"></meta> <meta property="og:url" content="<%= host %>/uploads/<%= extension(url)[0] %>.gif"></meta>
<meta property="og:description" content="Click to view the GIF"></meta> <meta property="og:description" content="Click to view the GIF"></meta>
<meta property="og:type" content="video.other"></meta> <meta property="og:type" content="video.other"></meta>
<meta property="og:image" content="<%= host %>/uploads/<%= extension(url)[0] %>.gif"></meta> <meta property="og:image" content="<%= host %>/uploads/<%= extension(url)[0] %>.gif"></meta>
<meta property="og:image:type" content="image/gif"></meta> <meta property="og:image:type" content="image/gif"></meta>
<meta property="og:image:width" content="<%= width %>"></meta> <meta property="og:image:width" content="<%= width %>"></meta>
<meta property="og:image:height" content="<%= height %>"></meta> <meta property="og:image:height" content="<%= height %>"></meta>
<meta property="og:video" content="<%= host %>/uploads/<%= extension(url)[0] %>.mp4"></meta> <meta property="og:video" content="<%= host %>/uploads/<%= extension(url)[0] %>.mp4"></meta>
<meta property="og:video:secure_url" content="<%= host %>/uploads/<%= extension(url)[0] %>.mp4"></meta> <meta property="og:video:secure_url" content="<%= host %>/uploads/<%= extension(url)[0] %>.mp4"></meta>
<meta property="og:video:type" content="text/mp4"></meta> <meta property="og:video:type" content="text/mp4"></meta>
<meta property="og:video:width" content="<%= width %>"></meta> <meta property="og:video:width" content="<%= width %>"></meta>
<meta property="og:video:height" content="<%= height %>"></meta> <meta property="og:video:height" content="<%= height %>"></meta>
<% } else { %> <% } else { %>
<meta name="twitter:image" content="<%= host %>/uploads/<%= extension(url)[0] + extension(url)[1] %>"> <meta name="twitter:image" content="<%= host %>/uploads/<%= extension(url)[0] + extension(url)[1] %>">
<meta name="twitter:card" content="player"></meta> <meta name="twitter:card" content="player"></meta>
<meta name="twitter:player" content="<%= host %>/uploads/<%= extension(url)[0] + extension(url)[1] %>"> <meta name="twitter:player" content="<%= host %>/uploads/<%= extension(url)[0] + extension(url)[1] %>">
<meta name="twitter:player:width" content="<%= width %>"></meta> <meta name="twitter:player:width" content="<%= width %>"></meta>
<meta name="twitter:player:height" content="<%= height %>"></meta> <meta name="twitter:player:height" content="<%= height %>"></meta>
<link rel="alternate" type="application/json+oembed" href="<%= host %>/uploads/oembed-<%= extension(url)[0]+extension(url)[1] %>.json"></link> <link rel="alternate" type="application/json+oembed" href="<%= host %>/uploads/oembed-<%= extension(url)[0]+extension(url)[1] %>.json"></link>
<meta property="og:title" content="<%= extension(url)[0] + extension(url)[1] %>"></meta> <meta property="og:title" content="<%= extension(url)[0] + extension(url)[1] %>"></meta>
<meta property="og:url" content="<%= host %>/uploads/<%= extension(url)[0] + extension(url)[1] %>"></meta> <meta property="og:url" content="<%= host %>/uploads/<%= extension(url)[0] + extension(url)[1] %>"></meta>
<meta property="og:description" content="Click to view the Image"></meta> <meta property="og:description" content="Click to view the Image"></meta>
<meta property="og:type" content="video.other"></meta> <meta property="og:type" content="video.other"></meta>
<meta property="og:image" content="<%= host %>/uploads/<%= extension(url)[0] + extension(url)[1] %>"></meta> <meta property="og:image" content="<%= host %>/uploads/<%= extension(url)[0] + extension(url)[1] %>"></meta>
<meta property="og:image:type" content="image/<%= extension(url)[1].substr(1,extension(url)[1].length) %>"></meta> <meta property="og:image:type" content="image/<%= extension(url)[1].substr(1,extension(url)[1].length) %>"></meta>
<meta property="og:image:width" content="<%= width %>"></meta> <meta property="og:image:width" content="<%= width %>"></meta>
<meta property="og:image:height" content="<%= height %>"></meta> <meta property="og:image:height" content="<%= height %>"></meta>
<% } %> <% } %>
</head> </head>
<body> <body>
<% if (extension(url)[1] == ".mp4" || extension(url)[1] == ".gif") { %> <% if (extension(url)[1] == ".mp4" || extension(url)[1] == ".gif") { %>
<video autoplay loop muted playsinline class="image" width="100%"><source src="/uploads/<%= extension(url)[0] %>.mp4"></video> <video autoplay loop muted playsinline class="image" width="100%"><source src="/uploads/<%= extension(url)[0] %>.mp4"></video>
<% } else { %> <% } else { %>
<img src="/uploads/<%= extension(url)[0] + extension(url)[1] %>" class="image" width="100%"> <img src="/uploads/<%= extension(url)[0] + extension(url)[1] %>" class="image" width="100%">
<% } %> <% } %>
</body> </body>
</html> </html>

View file

@ -1,30 +1,30 @@
<!doctype html> <!doctype html>
<html lang="en"> <html lang="en">
<head> <head>
<meta charset="utf-8"> <meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1"> <meta name="viewport" content="width=device-width, initial-scale=1">
<title>Embedder</title> <title>Embedder</title>
<link rel="stylesheet" href="/css/base.css"> <link rel="stylesheet" href="/css/base.css">
<link rel="stylesheet" href="/css/index.css"> <link rel="stylesheet" href="/css/index.css">
<link rel="stylesheet" href="/css/home.css"> <link rel="stylesheet" href="/css/home.css">
<link rel="apple-touch-icon" sizes="180x180" href="/apple-touch-icon.png"> <link rel="apple-touch-icon" sizes="180x180" href="/apple-touch-icon.png">
<link rel="icon" type="image/png" sizes="32x32" href="/favicon-32x32.png"> <link rel="icon" type="image/png" sizes="32x32" href="/favicon-32x32.png">
<link rel="icon" type="image/png" sizes="16x16" href="/favicon-16x16.png"> <link rel="icon" type="image/png" sizes="16x16" href="/favicon-16x16.png">
<link rel="manifest" href="/site.webmanifest"> <link rel="manifest" href="/site.webmanifest">
</head> </head>
<body> <body>
<section class="todohome"> <section class="todohome">
<header> <header>
<h1>Embedder</h1> <h1>Embedder</h1>
</header> </header>
<section> <section>
<h2>A media host specialized in good looking embeds for services like Discord</h2> <h2>A media host specialized in good looking embeds for services like Discord</h2>
<a class="button" href="/login">Sign in</a> <a class="button" href="/login">Sign in</a>
</section> </section>
</section> </section>
<footer class="info"> <footer class="info">
<p><a href="https://l.nekomimi.pet/project">Created by Wavering Ana</a></p> <p><a href="https://l.nekomimi.pet/project">Created by Wavering Ana</a></p>
<p><a href="https://github.com/WaveringAna/Embedder">Github</a></p> <p><a href="https://github.com/WaveringAna/Embedder">Github</a></p>
</footer> </footer>
</body> </body>
</html> </html>

View file

@ -1,100 +1,103 @@
<!doctype html> <!doctype html>
<html lang="en"> <html lang="en">
<head> <head>
<meta charset="utf-8"> <meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1"> <meta name="viewport" content="width=device-width, initial-scale=1">
<title>Embedder</title> <title>Embedder</title>
<link rel="stylesheet" href="/css/base.css"> <link rel="stylesheet" href="/css/base.css">
<link rel="stylesheet" href="/css/index.css"> <link rel="stylesheet" href="/css/index.css">
<link rel="stylesheet" href="/css/app.css"> <link rel="stylesheet" href="/css/app.css">
<link rel="apple-touch-icon" sizes="180x180" href="/apple-touch-icon.png"> <link rel="apple-touch-icon" sizes="180x180" href="/apple-touch-icon.png">
<link rel="icon" type="image/png" sizes="32x32" href="/favicon-32x32.png"> <link rel="icon" type="image/png" sizes="32x32" href="/favicon-32x32.png">
<link rel="icon" type="image/png" sizes="16x16" href="/favicon-16x16.png"> <link rel="icon" type="image/png" sizes="16x16" href="/favicon-16x16.png">
<link rel="manifest" href="/site.webmanifest"> <link rel="manifest" href="/site.webmanifest">
<% <%
function extension(string) { function extension(string) {
return string.slice((string.lastIndexOf(".") - 2 >>> 0) + 2); return string.slice((string.lastIndexOf(".") - 2 >>> 0) + 2);
} }
%> %>
</head> </head>
<body> <body>
<section class="todoapp"> <section class="todoapp">
<nav class="nav"> <nav class="nav">
<ul> <ul>
<li class="user"><%= user.name || user.username %></li> <li class="user"><%= user.name || user.username %></li>
<li> <li>
<form action="/logout" method="post"> <form action="/logout" method="post">
<button class="logout" type="submit">Sign out</button> <button class="logout" type="submit">Sign out</button>
</form> </form>
</li> </li>
</ul> </ul>
</nav> </nav>
<header class="header"> <header class="header">
<h1>Embedder</h1> <h1>Embedder</h1>
<form action="/" method="post" encType="multipart/form-data"> <form action="/" method="post" encType="multipart/form-data">
<div id="dropArea"> <div id="dropArea">
<p class="dragregion">Upload a file, copy paste, or drag n' drop into the dashed region</p> <p class="dragregion">Upload a file, copy paste, or drag n' drop into the dashed region</p>
<div id="gallery"></div> <div id="gallery"></div>
<p class="dragregion"><input class="" type="file" id="fileupload" name="fileupload"><input type="button" value="Upload" id="submit" onclick="uploadFile()"></p> <p class="dragregion"><input class="" type="file" id="fileupload" name="fileupload"><input type="button" value="Upload" id="submit" onclick="uploadFile()"></p>
<br> <br>
<br> <br>
<p class="dragregion">Select file expiration date: <select name="expire" id="expire"> <p class="dragregion">Select file expiration date: <select name="expire" id="expire">
<option value="1">1 day</option> <option value="0.00347">5 minutes</option>
<option value="7">7 days</option> <option value="0.0417">1 hour</option>
<option value="14">14 days</option> <option value="0.25">6 hours</option>
<option value="30">30 days</option> <option value="1">1 day</option>
<option value="">never</option> <option value="7">7 days</option>
</select></p> <option value="14">14 days</option>
<p class="dragregion">Click the file to copy the url</p> <option value="30">30 days</option>
</div> <option value="">never</option>
</form> </select></p>
</header> <p class="dragregion">Click the file to copy the url</p>
<% if (Count > 0) { %> </div>
<section class="main"> </form>
<ul class="todo-list"> </header>
<% files.forEach(function(file) { %> <% if (Count > 0) { %>
<li> <section class="main">
<form action="<%= file.url %>" method="post"> <ul class="todo-list">
<div class="view"> <% files.forEach(function(file) { %>
<% if (extension(file.path) == ".mp4" || extension(file.path) == ".mov" || extension(file.path) == "webp") { %> <li>
<div class="video"> <form action="<%= file.url %>" method="post">
<video class="image" autoplay loop muted playsinline> <div class="view">
<source src="/uploads/<%= file.path %>"> <% if (extension(file.path) == ".mp4" || extension(file.path) == ".mov" || extension(file.path) == "webp") { %>
</video> <div class="video">
<div class="overlay"> <video class="image" autoplay loop muted playsinline>
<a href="/gifv/<%=file.path %>" onclick="copyA(event)">Copy as GIFv</a> <source src="/uploads/<%= file.path %>">
</div> </video>
</div> <div class="overlay">
<% } else if (extension(file.path) == ".gif") { %> <a href="/gifv/<%=file.path %>" onclick="copyA(event)">Copy as GIFv</a>
<div class="video"> </div>
<img class="image" src="/uploads/<%=file.path %>" width="100%" onclick="copyURI(event)"> </div>
<div class="overlay"> <% } else if (extension(file.path) == ".gif") { %>
<a href="/gifv/<%=file.path %>" onclick="copyA(event)">Copy as GIFv</a> <div class="video">
</div> <img class="image" src="/uploads/<%=file.path %>" width="100%" onclick="copyURI(event)">
</div> <div class="overlay">
<% } else if (extension(file.path) == ".jpg" || extension(file.path) == ".jpeg" || extension(file.path) == ".png" || extension(file.path) == ".gif" || extension(file.path) == ".webp" ) { %> <a href="/gifv/<%=file.path %>" onclick="copyA(event)">Copy as GIFv</a>
<img class="image" src="/uploads/<%=file.path %>" width="100%" onclick="copyURI(event)"> </div>
<% } else {%> <!-- non-media file --> </div>
<div class="nonmedia" onclick="copyPath('<%=file.path%>')"> <% } else if (extension(file.path) == ".jpg" || extension(file.path) == ".jpeg" || extension(file.path) == ".png" || extension(file.path) == ".gif" || extension(file.path) == ".webp" ) { %>
<p><%=extension(file.path)%> file</p> <img class="image" src="/uploads/<%=file.path %>" width="100%" onclick="copyURI(event)">
</div> <% } else {%> <!-- non-media file -->
<% } %> <div class="nonmedia" onclick="copyPath('<%=file.path%>')">
<label><%= file.path %></label> <p><%=extension(file.path)%> file</p>
<button class="destroy" form="delete-<%= file.path %>"></button> </div>
</div> <% } %>
</form> <label><%= file.path %></label>
<form name="delete-<%= file.path %>" id="delete-<%= file.path %>" action="<%= file.url %>/delete" method="post"> <button class="destroy" form="delete-<%= file.path %>"></button>
</form> </div>
</li> </form>
<% }); %> <form name="delete-<%= file.path %>" id="delete-<%= file.path %>" action="<%= file.url %>/delete" method="post">
</ul> </form>
</section> </li>
<% } %> <% }); %>
</section> </ul>
<footer class="info"> </section>
<p><a href="https://l.nekomimi.pet/project">Created by Wavering Ana</a></p> <% } %>
<p><a href="https://github.com/WaveringAna/Embedder">Github</a></p> </section>
</footer> <footer class="info">
<script src="/js/index.js"></script> <p><a href="https://l.nekomimi.pet/project">Created by Wavering Ana</a></p>
</body> <p><a href="https://github.com/WaveringAna/Embedder">Github</a></p>
</html> </footer>
<script src="/js/index.js"></script>
</body>
</html>

View file

@ -1,37 +1,37 @@
<!doctype html> <!doctype html>
<html lang="en"> <html lang="en">
<head> <head>
<meta charset="utf-8"> <meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1"> <meta name="viewport" content="width=device-width, initial-scale=1">
<title>Embedder</title> <title>Embedder</title>
<link rel="stylesheet" href="/css/base.css"> <link rel="stylesheet" href="/css/base.css">
<link rel="stylesheet" href="/css/index.css"> <link rel="stylesheet" href="/css/index.css">
<link rel="stylesheet" href="/css/login.css"> <link rel="stylesheet" href="/css/login.css">
<link rel="apple-touch-icon" sizes="180x180" href="/apple-touch-icon.png"> <link rel="apple-touch-icon" sizes="180x180" href="/apple-touch-icon.png">
<link rel="icon" type="image/png" sizes="32x32" href="/favicon-32x32.png"> <link rel="icon" type="image/png" sizes="32x32" href="/favicon-32x32.png">
<link rel="icon" type="image/png" sizes="16x16" href="/favicon-16x16.png"> <link rel="icon" type="image/png" sizes="16x16" href="/favicon-16x16.png">
<link rel="manifest" href="/site.webmanifest"> <link rel="manifest" href="/site.webmanifest">
</head> </head>
<body> <body>
<section class="prompt"> <section class="prompt">
<h3>Embedder</h3> <h3>Embedder</h3>
<h1>Sign in</h1> <h1>Sign in</h1>
<form action="/login/password" method="post"> <form action="/login/password" method="post">
<section> <section>
<label for="username">Username</label> <label for="username">Username</label>
<input id="username" name="username" type="text" autocomplete="username" required autofocus> <input id="username" name="username" type="text" autocomplete="username" required autofocus>
</section> </section>
<section> <section>
<label for="current-password">Password</label> <label for="current-password">Password</label>
<input id="current-password" name="password" type="password" autocomplete="current-password" required> <input id="current-password" name="password" type="password" autocomplete="current-password" required>
</section> </section>
<button type="submit">Sign in</button> <button type="submit">Sign in</button>
</form> </form>
<hr> <hr>
</section> </section>
<footer class="info"> <footer class="info">
<p><a href="https://l.nekomimi.pet/project">Created by Wavering Ana</a></p> <p><a href="https://l.nekomimi.pet/project">Created by Wavering Ana</a></p>
<p><a href="https://github.com/WaveringAna/Embedder">Github</a></p> <p><a href="https://github.com/WaveringAna/Embedder">Github</a></p>
</footer> </footer>
</body> </body>
</html> </html>