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
var
uploads
# Node.js
node_modules/
npm-debug.log*
# Mac OS X
.DS_Store
Dockerfile
.env
var
uploads
# Node.js
node_modules/
npm-debug.log*
# Mac OS X
.DS_Store
Dockerfile

View file

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

20
.gitignore vendored
View file

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

View file

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

46
LICENSE
View file

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

156
README.md
View file

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

168
app.js
View file

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

178
bin/www
View file

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

66
db.js
View file

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

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

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

View file

@ -101,7 +101,6 @@ function handleUpload(req, res, next) {
for (let file in req.files) {
let currentdate = Date.now();
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) {
if (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",
"version": "0.0.0",
"private": true,
"description": "Media host for quick embeds to sites like discord",
"keywords": [
"example",
"express",
"passport",
"sqlite"
],
"author": {
"name": "Wavering Ana",
"email": "ana@nekomimi.pet",
"url": "https://l.nekomimi.pet/projects"
},
"homepage": "https://github.com/WaveringAna/embedder",
"repository": {
"type": "git",
"url": "git://github.com/WaveringAna/embedder.git"
},
"license": "Unlicense",
"scripts": {
"start": "node ./bin/www"
},
"dependencies": {
"@ffmpeg-installer/ffmpeg": "^1.1.0",
"@ffprobe-installer/ffprobe": "^1.4.1",
"connect-sqlite3": "^0.9.13",
"cookie-parser": "~1.4.4",
"dotenv": "^8.6.0",
"ejs": "^3.1.8",
"express": "~4.16.1",
"express-session": "^1.17.3",
"fluent-ffmpeg": "^2.1.2",
"http-errors": "~1.6.3",
"mkdirp": "^1.0.4",
"multer": "^1.4.5-lts.1",
"passport": "^0.6.0",
"passport-local": "^1.0.0",
"probe-image-size": "^7.2.3",
"sqlite3": "^5.0.2"
},
"devDependencies": {
"eslint": "^8.28.0"
}
}
{
"name": "embedder",
"version": "0.0.0",
"private": true,
"description": "Media host for quick embeds to sites like discord",
"keywords": [
"example",
"express",
"passport",
"sqlite"
],
"author": {
"name": "Wavering Ana",
"email": "ana@nekomimi.pet",
"url": "https://l.nekomimi.pet/projects"
},
"homepage": "https://github.com/WaveringAna/embedder",
"repository": {
"type": "git",
"url": "git://github.com/WaveringAna/embedder.git"
},
"license": "Unlicense",
"scripts": {
"start": "node ./bin/www"
},
"dependencies": {
"@ffmpeg-installer/ffmpeg": "^1.1.0",
"@ffprobe-installer/ffprobe": "^1.4.1",
"connect-sqlite3": "^0.9.13",
"cookie-parser": "~1.4.4",
"dotenv": "^8.6.0",
"ejs": "^3.1.8",
"express": "~4.16.1",
"express-session": "^1.17.3",
"fluent-ffmpeg": "^2.1.2",
"http-errors": "~1.6.3",
"mkdirp": "^1.0.4",
"multer": "^1.4.5-lts.1",
"passport": "^0.6.0",
"passport-local": "^1.0.0",
"probe-image-size": "^7.2.3",
"sqlite3": "^5.0.2"
},
"devDependencies": {
"eslint": "^8.28.0"
}
}

View file

@ -1,116 +1,116 @@
.nav {
position: absolute;
top: -130px;
right: 0;
}
.nav ul {
margin: 0;
list-style: none;
text-align: center;
}
.nav li {
display: inline-block;
height: 40px;
margin-left: 12px;
font-size: 14px;
font-weight: 400;
line-height: 40px;
}
.nav a {
display: block;
color: inherit;
text-decoration: none;
}
.nav a:hover {
border-bottom: 1px solid #DB7676;
}
.nav button {
height: 40px;
}
.nav button:hover {
border-bottom: 1px solid #DB7676;
cursor: pointer;
}
/* background image by Cole Bemis <https://feathericons.com> */
.nav .user {
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-repeat: no-repeat;
background-position: center left;
}
/* background image by Cole Bemis <https://feathericons.com> */
.nav .logout {
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-repeat: no-repeat;
background-position: center left;
}
#dropArea {
border: 2px dashed #ccc;
border-radius: 20px;
width: 100%;
font-family: sans-serif;
padding: 50px 0px 50px 0px;
}
#dropArea.highlight {
border-color: purple;
}
.dragregion {
text-align: center;
}
.image {
width: 100%;
}
div.nonmedia {
height: 100px;
width: 100%;
}
div.nonmedia p {
color: #444;
font-size: 20px;
font-weight: 400;
line-height: 100px;
text-align: center;
}
label {
text-align: center;
}
.video {
position: relative;
}
.video .overlay {
position: absolute;
top: 0%;
left: 70%;
transform: translateY(0%) translateX(70%);
z-index: 1;
border-radius: 25px;
border: 2px solid #73AD21;
padding: 5px;
background-color: #eee;
}
.video .overlay a {
color: #73AD21;
font-size: 12px;
font-weight: 400;
line-height: 20px;
text-align: center;
}
.nav {
position: absolute;
top: -130px;
right: 0;
}
.nav ul {
margin: 0;
list-style: none;
text-align: center;
}
.nav li {
display: inline-block;
height: 40px;
margin-left: 12px;
font-size: 14px;
font-weight: 400;
line-height: 40px;
}
.nav a {
display: block;
color: inherit;
text-decoration: none;
}
.nav a:hover {
border-bottom: 1px solid #DB7676;
}
.nav button {
height: 40px;
}
.nav button:hover {
border-bottom: 1px solid #DB7676;
cursor: pointer;
}
/* background image by Cole Bemis <https://feathericons.com> */
.nav .user {
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-repeat: no-repeat;
background-position: center left;
}
/* background image by Cole Bemis <https://feathericons.com> */
.nav .logout {
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-repeat: no-repeat;
background-position: center left;
}
#dropArea {
border: 2px dashed #ccc;
border-radius: 20px;
width: 100%;
font-family: sans-serif;
padding: 50px 0px 50px 0px;
}
#dropArea.highlight {
border-color: purple;
}
.dragregion {
text-align: center;
}
.image {
width: 100%;
}
div.nonmedia {
height: 100px;
width: 100%;
}
div.nonmedia p {
color: #444;
font-size: 20px;
font-weight: 400;
line-height: 100px;
text-align: center;
}
label {
text-align: center;
}
.video {
position: relative;
}
.video .overlay {
position: absolute;
top: 0%;
left: 70%;
transform: translateY(0%) translateX(70%);
z-index: 1;
border-radius: 25px;
border: 2px solid #73AD21;
padding: 5px;
background-color: #eee;
}
.video .overlay a {
color: #73AD21;
font-size: 12px;
font-weight: 400;
line-height: 20px;
text-align: center;
}

View file

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

View file

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

View file

@ -1,393 +1,393 @@
html,
body {
margin: 0;
padding: 0;
}
button {
margin: 0;
padding: 0;
border: 0;
background: none;
font-size: 100%;
vertical-align: baseline;
font-family: inherit;
font-weight: inherit;
color: inherit;
-webkit-appearance: none;
appearance: none;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
body {
font: 14px 'Helvetica Neue', Helvetica, Arial, sans-serif;
line-height: 1.4em;
background: #111111;
color: #f5f5f5;
min-width: 230px;
max-width: 550px;
margin: 0 auto;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
font-weight: 300;
}
.view .header {
background: #121122;
}
.hidden {
display: none;
}
.todoapp {
background: #121212;
margin: 130px 0 40px 0;
position: relative;
box-shadow: 0 2px 4px 0 rgba(0, 0, 0, 0.2),
0 25px 50px 0 rgba(0, 0, 0, 0.1);
}
.todoapp input::-webkit-input-placeholder {
font-style: italic;
font-weight: 400;
color: #f5f5f5;
text-align: center;
}
.todoapp input::-moz-placeholder {
font-style: italic;
font-weight: 400;
color: #f5f5f5;
text-align: center;
}
.todoapp input::input-placeholder {
font-style: italic;
font-weight: 400;
color: #f5f5f5;
text-align: center;
}
.todoapp h1 {
position: absolute;
top: -140px;
width: 100%;
font-size: 80px;
font-weight: 200;
text-align: center;
color: #b83f45;
-webkit-text-rendering: optimizeLegibility;
-moz-text-rendering: optimizeLegibility;
text-rendering: optimizeLegibility;
}
.new-todo,
.edit {
position: relative;
margin: 0;
width: 100%;
font-size: 24px;
font-family: inherit;
font-weight: inherit;
line-height: 1.4em;
color: inherit;
padding: 6px;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
.new-todo {
padding: 16px 16px 16px 60px;
height: 65px;
border: none;
background: rgba(0, 0, 0, 0.003);
box-shadow: inset 0 -2px 1px rgba(0,0,0,0.03);
}
.main {
position: relative;
z-index: 2;
}
.toggle-all {
width: 1px;
height: 1px;
border: none; /* Mobile Safari */
opacity: 0;
position: absolute;
right: 100%;
bottom: 100%;
}
.toggle-all + label {
display: flex;
align-items: center;
justify-content: center;
width: 45px;
height: 65px;
font-size: 0;
position: absolute;
top: -65px;
left: -0;
}
.toggle-all + label:before {
content: '';
display: inline-block;
font-size: 22px;
color: #949494;
padding: 10px 27px 10px 27px;
-webkit-transform: rotate(90deg);
transform: rotate(90deg);
}
.toggle-all:checked + label:before {
color: #484848;
}
.todo-list {
margin: 0;
padding: 0;
list-style: none;
}
.todo-list li {
position: relative;
font-size: 24px;
}
.todo-list li:last-child {
border-bottom: none;
}
.todo-list li.editing {
border-bottom: none;
padding: 0;
}
.todo-list li.editing .edit {
display: block;
width: calc(100% - 43px);
padding: 12px 16px;
margin: 0 0 0 43px;
}
.todo-list li.editing .view {
display: none;
}
.todo-list li .toggle {
text-align: center;
width: 40px;
/* auto, since non-WebKit browsers doesn't support input styling */
height: auto;
position: absolute;
top: 0;
bottom: 0;
margin: auto 0;
border: none; /* Mobile Safari */
-webkit-appearance: none;
appearance: none;
}
.todo-list li .toggle {
opacity: 0;
}
.todo-list li .toggle + label {
/*
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/
*/
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-position: center left;
}
.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');
}
.todo-list li label {
word-break: break-all;
padding: 15px 15px 15px 60px;
display: block;
line-height: 1.2;
transition: color 0.4s;
font-weight: 400;
color: #BB86FC;
}
.todo-list li.completed label {
color: #949494;
text-decoration: line-through;
}
.todo-list li .destroy {
display: none;
position: absolute;
top: 0;
right: 10px;
bottom: 0;
width: 40px;
height: 40px;
margin: auto 0;
font-size: 30px;
color: #949494;
transition: color 0.2s ease-out;
}
.todo-list li .destroy:hover,
.todo-list li .destroy:focus {
color: #C18585;
}
.todo-list li .destroy:after {
content: '×';
display: block;
height: 100%;
line-height: 1.1;
}
.todo-list li:hover .destroy {
display: block;
}
.todo-list li .edit {
display: none;
}
.todo-list li.editing:last-child {
margin-bottom: -1px;
}
.footer {
padding: 10px 15px;
height: 20px;
text-align: center;
font-size: 15px;
border-top: 1px solid #e6e6e6;
}
.footer:before {
content: '';
position: absolute;
right: 0;
bottom: 0;
left: 0;
height: 50px;
overflow: hidden;
box-shadow: 0 1px 1px rgba(0, 0, 0, 0.2),
0 8px 0 -3px #f6f6f6,
0 9px 1px -3px rgba(0, 0, 0, 0.2),
0 16px 0 -6px #f6f6f6,
0 17px 2px -6px rgba(0, 0, 0, 0.2);
}
.todo-count {
float: left;
text-align: left;
}
.todo-count strong {
font-weight: 300;
}
.filters {
margin: 0;
padding: 0;
list-style: none;
position: absolute;
right: 0;
left: 0;
}
.filters li {
display: inline;
}
.filters li a {
color: inherit;
margin: 3px;
padding: 3px 7px;
text-decoration: none;
border: 1px solid transparent;
border-radius: 3px;
}
.filters li a:hover {
border-color: #DB7676;
}
.filters li a.selected {
border-color: #CE4646;
}
.clear-completed,
html .clear-completed:active {
float: right;
position: relative;
line-height: 19px;
text-decoration: none;
cursor: pointer;
}
.clear-completed:hover {
text-decoration: underline;
}
.info {
margin: 65px auto 0;
color: #4d4d4d;
font-size: 11px;
text-shadow: 0 1px 0 rgba(255, 255, 255, 0.5);
text-align: center;
}
.info p {
line-height: 1;
}
.info a {
color: inherit;
text-decoration: none;
font-weight: 400;
}
.info a:hover {
text-decoration: underline;
}
/*
Hack to remove background from Mobile Safari.
Can't use it globally since it destroys checkboxes in Firefox
*/
@media screen and (-webkit-min-device-pixel-ratio:0) {
.toggle-all,
.todo-list li .toggle {
background: none;
}
.todo-list li .toggle {
height: 40px;
}
}
@media (max-width: 430px) {
.footer {
height: 50px;
}
.filters {
bottom: 10px;
}
}
:focus,
.toggle:focus + label,
.toggle-all:focus + label {
box-shadow: 0 0 2px 2px #CF7D7D;
outline: 0;
}
html,
body {
margin: 0;
padding: 0;
}
button {
margin: 0;
padding: 0;
border: 0;
background: none;
font-size: 100%;
vertical-align: baseline;
font-family: inherit;
font-weight: inherit;
color: inherit;
-webkit-appearance: none;
appearance: none;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
body {
font: 14px 'Helvetica Neue', Helvetica, Arial, sans-serif;
line-height: 1.4em;
background: #111111;
color: #f5f5f5;
min-width: 230px;
max-width: 550px;
margin: 0 auto;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
font-weight: 300;
}
.view .header {
background: #121122;
}
.hidden {
display: none;
}
.todoapp {
background: #121212;
margin: 130px 0 40px 0;
position: relative;
box-shadow: 0 2px 4px 0 rgba(0, 0, 0, 0.2),
0 25px 50px 0 rgba(0, 0, 0, 0.1);
}
.todoapp input::-webkit-input-placeholder {
font-style: italic;
font-weight: 400;
color: #f5f5f5;
text-align: center;
}
.todoapp input::-moz-placeholder {
font-style: italic;
font-weight: 400;
color: #f5f5f5;
text-align: center;
}
.todoapp input::input-placeholder {
font-style: italic;
font-weight: 400;
color: #f5f5f5;
text-align: center;
}
.todoapp h1 {
position: absolute;
top: -140px;
width: 100%;
font-size: 80px;
font-weight: 200;
text-align: center;
color: #b83f45;
-webkit-text-rendering: optimizeLegibility;
-moz-text-rendering: optimizeLegibility;
text-rendering: optimizeLegibility;
}
.new-todo,
.edit {
position: relative;
margin: 0;
width: 100%;
font-size: 24px;
font-family: inherit;
font-weight: inherit;
line-height: 1.4em;
color: inherit;
padding: 6px;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
.new-todo {
padding: 16px 16px 16px 60px;
height: 65px;
border: none;
background: rgba(0, 0, 0, 0.003);
box-shadow: inset 0 -2px 1px rgba(0,0,0,0.03);
}
.main {
position: relative;
z-index: 2;
}
.toggle-all {
width: 1px;
height: 1px;
border: none; /* Mobile Safari */
opacity: 0;
position: absolute;
right: 100%;
bottom: 100%;
}
.toggle-all + label {
display: flex;
align-items: center;
justify-content: center;
width: 45px;
height: 65px;
font-size: 0;
position: absolute;
top: -65px;
left: -0;
}
.toggle-all + label:before {
content: '';
display: inline-block;
font-size: 22px;
color: #949494;
padding: 10px 27px 10px 27px;
-webkit-transform: rotate(90deg);
transform: rotate(90deg);
}
.toggle-all:checked + label:before {
color: #484848;
}
.todo-list {
margin: 0;
padding: 0;
list-style: none;
}
.todo-list li {
position: relative;
font-size: 24px;
}
.todo-list li:last-child {
border-bottom: none;
}
.todo-list li.editing {
border-bottom: none;
padding: 0;
}
.todo-list li.editing .edit {
display: block;
width: calc(100% - 43px);
padding: 12px 16px;
margin: 0 0 0 43px;
}
.todo-list li.editing .view {
display: none;
}
.todo-list li .toggle {
text-align: center;
width: 40px;
/* auto, since non-WebKit browsers doesn't support input styling */
height: auto;
position: absolute;
top: 0;
bottom: 0;
margin: auto 0;
border: none; /* Mobile Safari */
-webkit-appearance: none;
appearance: none;
}
.todo-list li .toggle {
opacity: 0;
}
.todo-list li .toggle + label {
/*
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/
*/
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-position: center left;
}
.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');
}
.todo-list li label {
word-break: break-all;
padding: 15px 15px 15px 60px;
display: block;
line-height: 1.2;
transition: color 0.4s;
font-weight: 400;
color: #BB86FC;
}
.todo-list li.completed label {
color: #949494;
text-decoration: line-through;
}
.todo-list li .destroy {
display: none;
position: absolute;
top: 0;
right: 10px;
bottom: 0;
width: 40px;
height: 40px;
margin: auto 0;
font-size: 30px;
color: #949494;
transition: color 0.2s ease-out;
}
.todo-list li .destroy:hover,
.todo-list li .destroy:focus {
color: #C18585;
}
.todo-list li .destroy:after {
content: '×';
display: block;
height: 100%;
line-height: 1.1;
}
.todo-list li:hover .destroy {
display: block;
}
.todo-list li .edit {
display: none;
}
.todo-list li.editing:last-child {
margin-bottom: -1px;
}
.footer {
padding: 10px 15px;
height: 20px;
text-align: center;
font-size: 15px;
border-top: 1px solid #e6e6e6;
}
.footer:before {
content: '';
position: absolute;
right: 0;
bottom: 0;
left: 0;
height: 50px;
overflow: hidden;
box-shadow: 0 1px 1px rgba(0, 0, 0, 0.2),
0 8px 0 -3px #f6f6f6,
0 9px 1px -3px rgba(0, 0, 0, 0.2),
0 16px 0 -6px #f6f6f6,
0 17px 2px -6px rgba(0, 0, 0, 0.2);
}
.todo-count {
float: left;
text-align: left;
}
.todo-count strong {
font-weight: 300;
}
.filters {
margin: 0;
padding: 0;
list-style: none;
position: absolute;
right: 0;
left: 0;
}
.filters li {
display: inline;
}
.filters li a {
color: inherit;
margin: 3px;
padding: 3px 7px;
text-decoration: none;
border: 1px solid transparent;
border-radius: 3px;
}
.filters li a:hover {
border-color: #DB7676;
}
.filters li a.selected {
border-color: #CE4646;
}
.clear-completed,
html .clear-completed:active {
float: right;
position: relative;
line-height: 19px;
text-decoration: none;
cursor: pointer;
}
.clear-completed:hover {
text-decoration: underline;
}
.info {
margin: 65px auto 0;
color: #4d4d4d;
font-size: 11px;
text-shadow: 0 1px 0 rgba(255, 255, 255, 0.5);
text-align: center;
}
.info p {
line-height: 1;
}
.info a {
color: inherit;
text-decoration: none;
font-weight: 400;
}
.info a:hover {
text-decoration: underline;
}
/*
Hack to remove background from Mobile Safari.
Can't use it globally since it destroys checkboxes in Firefox
*/
@media screen and (-webkit-min-device-pixel-ratio:0) {
.toggle-all,
.todo-list li .toggle {
background: none;
}
.todo-list li .toggle {
height: 40px;
}
}
@media (max-width: 430px) {
.footer {
height: 50px;
}
.filters {
bottom: 10px;
}
}
:focus,
.toggle:focus + label,
.toggle-all:focus + label {
box-shadow: 0 0 2px 2px #CF7D7D;
outline: 0;
}

View file

@ -1,116 +1,116 @@
.prompt {
max-width: 400px;
margin: 50px auto;
padding: 25px;
background: #11111;
border: 1px solid #e6e6e6;
border-radius: 8px;
}
button {
display: block;
padding: 10px;
width: 100%;
border-radius: 3px;
background: #d83f45;
font-size: 14px;
font-weight: 700;
color: white;
cursor: pointer;
}
a.button {
box-sizing: border-box;
display: block;
padding: 10px;
width: 100%;
border-radius: 3px;
background: #000;
font-size: 14px;
font-weight: 700;
text-align: center;
text-decoration: none;
color: white;
}
a.google {
background: #4787ed;
}
a.facebook {
background: #4267b2;
}
button:hover {
background-color: #c83f45;
}
h1 {
margin: 0 0 20px 0;
padding: 0 0 5px 0;
font-size: 24px;
font-weight: 500;
}
h3 {
margin-top: 0;
font-size: 24px;
font-weight: 300;
text-align: center;
color: #b83f45;
}
form section {
margin: 0 0 20px 0;
position: relative; /* for password toggle positioning */
}
label {
display: block;
margin: 0 0 3px 0;
font-size: 14px;
font-weight: 500;
}
input {
box-sizing: border-box;
width: 100%;
padding: 10px;
font-size: 14px;
border: 1px solid #d9d9d9;
border-radius: 5px;
background-color:#200;
color:#fff;
}
input[type=email]:not(:focus):invalid,
input[type=password]:not(:focus):invalid {
color: red;
outline-color: red;
}
hr {
border-top: 1px solid #d9d9d9;
border-bottom: none;
}
p.instructions {
font-weight: 400;
}
p.help {
text-align: center;
font-weight: 400;
}
/* background image by Cole Bemis <https://feathericons.com> */
.messages p {
font-size: 14px;
font-weight: 400;
line-height: 1.3;
color: #d83f45;
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-repeat: no-repeat;
background-position: center left;
}
.prompt {
max-width: 400px;
margin: 50px auto;
padding: 25px;
background: #11111;
border: 1px solid #e6e6e6;
border-radius: 8px;
}
button {
display: block;
padding: 10px;
width: 100%;
border-radius: 3px;
background: #d83f45;
font-size: 14px;
font-weight: 700;
color: white;
cursor: pointer;
}
a.button {
box-sizing: border-box;
display: block;
padding: 10px;
width: 100%;
border-radius: 3px;
background: #000;
font-size: 14px;
font-weight: 700;
text-align: center;
text-decoration: none;
color: white;
}
a.google {
background: #4787ed;
}
a.facebook {
background: #4267b2;
}
button:hover {
background-color: #c83f45;
}
h1 {
margin: 0 0 20px 0;
padding: 0 0 5px 0;
font-size: 24px;
font-weight: 500;
}
h3 {
margin-top: 0;
font-size: 24px;
font-weight: 300;
text-align: center;
color: #b83f45;
}
form section {
margin: 0 0 20px 0;
position: relative; /* for password toggle positioning */
}
label {
display: block;
margin: 0 0 3px 0;
font-size: 14px;
font-weight: 500;
}
input {
box-sizing: border-box;
width: 100%;
padding: 10px;
font-size: 14px;
border: 1px solid #d9d9d9;
border-radius: 5px;
background-color:#200;
color:#fff;
}
input[type=email]:not(:focus):invalid,
input[type=password]:not(:focus):invalid {
color: red;
outline-color: red;
}
hr {
border-top: 1px solid #d9d9d9;
border-bottom: none;
}
p.instructions {
font-weight: 400;
}
p.help {
text-align: center;
font-weight: 400;
}
/* background image by Cole Bemis <https://feathericons.com> */
.messages p {
font-size: 14px;
font-weight: 400;
line-height: 1.3;
color: #d83f45;
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-repeat: no-repeat;
background-position: center left;
}

View file

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

View file

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

View file

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

View file

@ -1,57 +1,57 @@
<%
function extension(str){
let file = str.split('/').pop();
return [file.substr(0,file.lastIndexOf('.')),file.substr(file.lastIndexOf('.'),file.length).toLowerCase()]
}
%>
<!DOCTYPE html>
<html>
<head>
<% if (extension(url)[1] == ".mp4" || extension(url)[1] == ".gif") { %>
<meta name="twitter:image" content="<%= host %>/uploads/<%= extension(url)[0] %>.gif">
<meta name="twitter:card" content="player"></meta>
<meta name="twitter:player" content="<%= host %>/uploads/<%= extension(url)[0] %>.mp4">
<meta name="twitter:player:width" content="<%= width %>"></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_type" content="text/mp4"></meta>
<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:url" content="<%= host %>/uploads/<%= extension(url)[0] %>.gif"></meta>
<meta property="og:description" content="Click to view the GIF"></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:type" content="image/gif"></meta>
<meta property="og:image:width" content="<%= width %>"></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:secure_url" content="<%= host %>/uploads/<%= extension(url)[0] %>.mp4"></meta>
<meta property="og:video:type" content="text/mp4"></meta>
<meta property="og:video:width" content="<%= width %>"></meta>
<meta property="og:video:height" content="<%= height %>"></meta>
<% } else { %>
<meta name="twitter:image" content="<%= host %>/uploads/<%= extension(url)[0] + extension(url)[1] %>">
<meta name="twitter:card" content="player"></meta>
<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:height" content="<%= height %>"></meta>
<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: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:type" content="video.other"></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:width" content="<%= width %>"></meta>
<meta property="og:image:height" content="<%= height %>"></meta>
<% } %>
</head>
<body>
<% 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>
<% } else { %>
<img src="/uploads/<%= extension(url)[0] + extension(url)[1] %>" class="image" width="100%">
<% } %>
</body>
</html>
<%
function extension(str){
let file = str.split('/').pop();
return [file.substr(0,file.lastIndexOf('.')),file.substr(file.lastIndexOf('.'),file.length).toLowerCase()]
}
%>
<!DOCTYPE html>
<html>
<head>
<% if (extension(url)[1] == ".mp4" || extension(url)[1] == ".gif") { %>
<meta name="twitter:image" content="<%= host %>/uploads/<%= extension(url)[0] %>.gif">
<meta name="twitter:card" content="player"></meta>
<meta name="twitter:player" content="<%= host %>/uploads/<%= extension(url)[0] %>.mp4">
<meta name="twitter:player:width" content="<%= width %>"></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_type" content="text/mp4"></meta>
<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:url" content="<%= host %>/uploads/<%= extension(url)[0] %>.gif"></meta>
<meta property="og:description" content="Click to view the GIF"></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:type" content="image/gif"></meta>
<meta property="og:image:width" content="<%= width %>"></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:secure_url" content="<%= host %>/uploads/<%= extension(url)[0] %>.mp4"></meta>
<meta property="og:video:type" content="text/mp4"></meta>
<meta property="og:video:width" content="<%= width %>"></meta>
<meta property="og:video:height" content="<%= height %>"></meta>
<% } else { %>
<meta name="twitter:image" content="<%= host %>/uploads/<%= extension(url)[0] + extension(url)[1] %>">
<meta name="twitter:card" content="player"></meta>
<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:height" content="<%= height %>"></meta>
<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: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:type" content="video.other"></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:width" content="<%= width %>"></meta>
<meta property="og:image:height" content="<%= height %>"></meta>
<% } %>
</head>
<body>
<% 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>
<% } else { %>
<img src="/uploads/<%= extension(url)[0] + extension(url)[1] %>" class="image" width="100%">
<% } %>
</body>
</html>

View file

@ -1,30 +1,30 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Embedder</title>
<link rel="stylesheet" href="/css/base.css">
<link rel="stylesheet" href="/css/index.css">
<link rel="stylesheet" href="/css/home.css">
<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="16x16" href="/favicon-16x16.png">
<link rel="manifest" href="/site.webmanifest">
</head>
<body>
<section class="todohome">
<header>
<h1>Embedder</h1>
</header>
<section>
<h2>A media host specialized in good looking embeds for services like Discord</h2>
<a class="button" href="/login">Sign in</a>
</section>
</section>
<footer class="info">
<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>
</footer>
</body>
</html>
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Embedder</title>
<link rel="stylesheet" href="/css/base.css">
<link rel="stylesheet" href="/css/index.css">
<link rel="stylesheet" href="/css/home.css">
<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="16x16" href="/favicon-16x16.png">
<link rel="manifest" href="/site.webmanifest">
</head>
<body>
<section class="todohome">
<header>
<h1>Embedder</h1>
</header>
<section>
<h2>A media host specialized in good looking embeds for services like Discord</h2>
<a class="button" href="/login">Sign in</a>
</section>
</section>
<footer class="info">
<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>
</footer>
</body>
</html>

View file

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

View file

@ -1,37 +1,37 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Embedder</title>
<link rel="stylesheet" href="/css/base.css">
<link rel="stylesheet" href="/css/index.css">
<link rel="stylesheet" href="/css/login.css">
<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="16x16" href="/favicon-16x16.png">
<link rel="manifest" href="/site.webmanifest">
</head>
<body>
<section class="prompt">
<h3>Embedder</h3>
<h1>Sign in</h1>
<form action="/login/password" method="post">
<section>
<label for="username">Username</label>
<input id="username" name="username" type="text" autocomplete="username" required autofocus>
</section>
<section>
<label for="current-password">Password</label>
<input id="current-password" name="password" type="password" autocomplete="current-password" required>
</section>
<button type="submit">Sign in</button>
</form>
<hr>
</section>
<footer class="info">
<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>
</footer>
</body>
</html>
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Embedder</title>
<link rel="stylesheet" href="/css/base.css">
<link rel="stylesheet" href="/css/index.css">
<link rel="stylesheet" href="/css/login.css">
<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="16x16" href="/favicon-16x16.png">
<link rel="manifest" href="/site.webmanifest">
</head>
<body>
<section class="prompt">
<h3>Embedder</h3>
<h1>Sign in</h1>
<form action="/login/password" method="post">
<section>
<label for="username">Username</label>
<input id="username" name="username" type="text" autocomplete="username" required autofocus>
</section>
<section>
<label for="current-password">Password</label>
<input id="current-password" name="password" type="password" autocomplete="current-password" required>
</section>
<button type="submit">Sign in</button>
</form>
<hr>
</section>
<footer class="info">
<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>
</footer>
</body>
</html>