Compare commits
80 commits
nfp_moe_v2
...
master
Author | SHA1 | Date | |
---|---|---|---|
852f37dc8d | |||
14a3bc3123 | |||
ab2e7b93c4 | |||
2e7b3be8d5 | |||
533e279b0b | |||
e75719682e | |||
c55f0c9a02 | |||
0b686a462f | |||
329a3e267c | |||
ab9ed32196 | |||
3a6996bfbb | |||
bdeeff3794 | |||
fad7acd5f7 | |||
2d7101d666 | |||
16b87aabcf | |||
857a087410 | |||
ec7ade938f | |||
638e6cc435 | |||
531c7acefe | |||
a5c7e53802 | |||
d1730974dc | |||
4216e036e4 | |||
825cd7ef2d | |||
5baf1823f4 | |||
285ad3dc64 | |||
9e56095773 | |||
de0a8b6f00 | |||
86394efb1f | |||
3dbcc18da8 | |||
f6d87c647f | |||
e5a1432443 | |||
324623d717 | |||
aad55ae9f0 | |||
70d14b24aa | |||
5e31e785df | |||
d9ca9bcc52 | |||
6f39e97977 | |||
972dde2c74 | |||
ddb66d9836 | |||
7f4dce5920 | |||
3b1a1b7bf6 | |||
840f23908e | |||
31f2ecc09b | |||
aa324cf0f1 | |||
9e642afef6 | |||
82b5e1bb5a | |||
ba3161dd62 | |||
c613aa126a | |||
1ff7b3aabe | |||
401df6902c | |||
098b7e5f14 | |||
cbdba450b5 | |||
60f21e0279 | |||
53c527342f | |||
726a6a8f25 | |||
23580dea3f | |||
97b54d23c4 | |||
260f2b6c7e | |||
203965027b | |||
6faf71ba18 | |||
8badc915cd | |||
3e7e787002 | |||
02c1f699fa | |||
3ac90b5eb6 | |||
17e266e89a | |||
3a60332bdf | |||
f3cb6837cb | |||
b8ed6a1c99 | |||
a13ac57bc8 | |||
3385058fc9 | |||
6ae87b9c16 | |||
5c07b00f2d | |||
89ea760ce3 | |||
e737909b45 | |||
bcc4dfe745 | |||
2b1e2d695a | |||
9b227ead62 | |||
fda0b7e498 | |||
8d0ae21374 | |||
1c0995f4da |
79
.gitea/workflows/deploy.yaml
Normal file
|
@ -0,0 +1,79 @@
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- master
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
deploy:
|
||||||
|
runs-on: alpine
|
||||||
|
steps:
|
||||||
|
- name: Check out repository code
|
||||||
|
uses: actions/checkout@v3
|
||||||
|
- name: Check for new release
|
||||||
|
run: |
|
||||||
|
chmod +x ./7zas
|
||||||
|
echo ""
|
||||||
|
echo "Checking following projects:"
|
||||||
|
for f in *; do
|
||||||
|
[ -d "$f" ] && [ ! -L "$f" ] && [ ! "$f" = "base" ] && echo " * $f";
|
||||||
|
done
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
|
||||||
|
for f in *; do
|
||||||
|
[ ! -d "$f" ] || [ -L "$f" ] || [ "$f" = "base" ] && continue;
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "------------------------------------"
|
||||||
|
echo ""
|
||||||
|
echo "checking $f";
|
||||||
|
cd $f
|
||||||
|
|
||||||
|
CURR_VER="$(cat package.json | jq -r .name)_v$(cat package.json | jq -r .version)"
|
||||||
|
CURR_NAME="$(cat package.json | jq -r .name) v$(cat package.json | jq -r .version)"
|
||||||
|
|
||||||
|
echo "Checking https://git.nfp.is/api/v1/repos/${{ gitea.repository }}/releases for name ${CURR_NAME}"
|
||||||
|
|
||||||
|
if curl -s -X GET -H "Authorization: token ${{ secrets.deploytoken }}" https://git.nfp.is/api/v1/repos/${{ gitea.repository }}/releases | grep -o "\"name\"\:\"${CURR_NAME}\"" > /dev/null; then
|
||||||
|
echo "Skipping ${{ gitea.job }} since $CURR_NAME already exists";
|
||||||
|
cd ..
|
||||||
|
continue;
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "New release ${CURR_VER} found, running npm install..."
|
||||||
|
|
||||||
|
mv package.json fuck-you-npm-package.json
|
||||||
|
mv build-package.json package.json
|
||||||
|
npm install && npm run build
|
||||||
|
|
||||||
|
mv package.json build-package.json
|
||||||
|
mv fuck-you-npm-package.json package.json
|
||||||
|
|
||||||
|
../7zas a -xr!*.xcf -mx9 "${CURR_VER}_build-sc.7z" package.json index.mjs api base public
|
||||||
|
|
||||||
|
echo "Creating ${CURR_VER} release on gitea"
|
||||||
|
RELEASE_RESULT=$(curl \
|
||||||
|
-X POST \
|
||||||
|
-H "Authorization: token ${{ secrets.deploytoken }}" \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
https://git.nfp.is/api/v1/repos/${{ gitea.repository }}/releases \
|
||||||
|
-d "{\"tag_name\":\"${CURR_VER}\",\"name\":\"${CURR_NAME}\",\"body\":\"Automatic release from Appveyor from ${{ gitea.sha }} :\n\n${{ gitea.event.head_commit.message }}\"}")
|
||||||
|
|
||||||
|
RELEASE_ID=$(echo $RELEASE_RESULT | jq -r .id)
|
||||||
|
echo "Adding ${CURR_VER}_build-sc.7z to release ${RELEASE_ID}"
|
||||||
|
|
||||||
|
curl \
|
||||||
|
-X POST \
|
||||||
|
-H "Authorization: token ${{ secrets.deploytoken }}" \
|
||||||
|
-F "attachment=@${CURR_VER}_build-sc.7z" \
|
||||||
|
https://git.nfp.is/api/v1/repos/${{ gitea.repository }}/releases/$RELEASE_ID/assets
|
||||||
|
|
||||||
|
MAN_PORT=$(cat package.json | jq -r .port)
|
||||||
|
MAN_NAME=$(cat package.json | jq -r .name)
|
||||||
|
|
||||||
|
echo "Deplying to production"
|
||||||
|
echo "curl -X POST http://192.168.93.51:$MAN_PORT/update/$MAN_NAME"
|
||||||
|
curl -X POST http://192.168.93.51:$MAN_PORT/update/$MAN_NAME
|
||||||
|
|
||||||
|
cd ..
|
||||||
|
done
|
113
appveyor.yml
|
@ -1,113 +0,0 @@
|
||||||
# version format
|
|
||||||
version: '{build}'
|
|
||||||
deploy: on
|
|
||||||
|
|
||||||
# branches to build
|
|
||||||
branches:
|
|
||||||
# whitelist
|
|
||||||
only:
|
|
||||||
- master
|
|
||||||
|
|
||||||
# Do not build on tags (GitHub, Bitbucket, GitLab, Gitea)
|
|
||||||
skip_tags: true
|
|
||||||
|
|
||||||
# Maximum number of concurrent jobs for the project
|
|
||||||
max_jobs: 1
|
|
||||||
clone_depth: 1
|
|
||||||
|
|
||||||
# Build worker image (VM template)
|
|
||||||
build_cloud: Docker
|
|
||||||
|
|
||||||
environment:
|
|
||||||
docker_image: node:16-alpine
|
|
||||||
npm_config_cache: /appveyor/projects/cache
|
|
||||||
|
|
||||||
build_script:
|
|
||||||
- sh: |
|
|
||||||
chown -R node:node /appveyor/projects
|
|
||||||
chmod -R 777 /appveyor/projects
|
|
||||||
|
|
||||||
apk add curl jq
|
|
||||||
if [ $? -ne 0 ]; then
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
echo "Finished installling curl and jq"
|
|
||||||
|
|
||||||
SUCCESS=1
|
|
||||||
|
|
||||||
for f in *; do
|
|
||||||
[ ! -d "$f" ] || [ -L "$f" ] || [ "$f" = "base" ] && continue;
|
|
||||||
echo "checking $f";
|
|
||||||
cd $f
|
|
||||||
CURR_VER="$(cat package.json | jq -r .name)_v$(cat package.json | jq -r .version)"
|
|
||||||
CURR_NAME="$(cat package.json | jq -r .name) v$(cat package.json | jq -r .version)"
|
|
||||||
MAN_PORT=$(cat package.json | jq -r .port)
|
|
||||||
MAN_NAME=$(cat package.json | jq -r .name)
|
|
||||||
echo "Checking https://git.nfp.is/api/v1/repos/$APPVEYOR_REPO_NAME/releases for name ${CURR_NAME}"
|
|
||||||
curl -s -X GET -H "Authorization: token $deploytoken" https://git.nfp.is/api/v1/repos/$APPVEYOR_REPO_NAME/releases | grep -o "\"name\"\:\"${CURR_NAME}\"" > /dev/null
|
|
||||||
|
|
||||||
if [ $? -eq 0 ] ; then
|
|
||||||
echo "Skipping $f since $CURR_NAME already exists";
|
|
||||||
continue;
|
|
||||||
fi
|
|
||||||
|
|
||||||
rm base
|
|
||||||
cp -Rf ../base ./base
|
|
||||||
|
|
||||||
mv package.json fuck-you-npm-package.json
|
|
||||||
mv build-package.json package.json
|
|
||||||
|
|
||||||
npm install && npm run build
|
|
||||||
CHECK=$?
|
|
||||||
if [ $CHECK -ne 0 ]; then
|
|
||||||
echo "Command failed with error code $CHECK"
|
|
||||||
SUCCESS=0;
|
|
||||||
continue;
|
|
||||||
fi;
|
|
||||||
|
|
||||||
mv package.json build-package.json
|
|
||||||
mv fuck-you-npm-package.json package.json
|
|
||||||
|
|
||||||
../7zas a -xr!*.xcf -mx9 "${CURR_VER}_build-sc.7z" package.json index.mjs api base public
|
|
||||||
echo "Creating release on gitea"
|
|
||||||
RELEASE_RESULT=$(curl \
|
|
||||||
-X POST \
|
|
||||||
-H "Authorization: token $deploytoken" \
|
|
||||||
-H "Content-Type: application/json" \
|
|
||||||
https://git.nfp.is/api/v1/repos/$APPVEYOR_REPO_NAME/releases \
|
|
||||||
-d "{\"tag_name\":\"${CURR_VER}\",\"name\":\"${CURR_NAME}\",\"body\":\"Automatic release from Appveyor from ${APPVEYOR_REPO_COMMIT} :\n\n${APPVEYOR_REPO_COMMIT_MESSAGE}\"}")
|
|
||||||
CHECK=$?
|
|
||||||
if [ $CHECK -ne 0 ]; then
|
|
||||||
echo "Command failed with error code $CHECK"
|
|
||||||
SUCCESS=0;
|
|
||||||
continue;
|
|
||||||
fi;
|
|
||||||
RELEASE_ID=$(echo $RELEASE_RESULT | jq -r .id)
|
|
||||||
echo "Adding ${CURR_VER}_build-sc.7z to release ${RELEASE_ID}"
|
|
||||||
curl \
|
|
||||||
-X POST \
|
|
||||||
-H "Authorization: token $deploytoken" \
|
|
||||||
-F "attachment=@${CURR_VER}_build-sc.7z" \
|
|
||||||
https://git.nfp.is/api/v1/repos/$APPVEYOR_REPO_NAME/releases/$RELEASE_ID/assets
|
|
||||||
CHECK=$?
|
|
||||||
if [ $CHECK -ne 0 ]; then
|
|
||||||
echo "Command failed with error code $CHECK"
|
|
||||||
SUCCESS=0;
|
|
||||||
continue;
|
|
||||||
fi;
|
|
||||||
|
|
||||||
echo "Deplying to production"
|
|
||||||
echo "curl -X POST http://192.168.93.50:$MAN_PORT/update/$MAN_NAME"
|
|
||||||
curl -X POST http://192.168.93.50:$MAN_PORT/update/$MAN_NAME
|
|
||||||
cd ..
|
|
||||||
done
|
|
||||||
|
|
||||||
if [ $SUCCESS -eq 0 ]; then
|
|
||||||
echo "One or more jobs failed to build"
|
|
||||||
exit 1;
|
|
||||||
fi;
|
|
||||||
|
|
||||||
# on build failure
|
|
||||||
on_failure:
|
|
||||||
- sh: echo on_failure
|
|
|
@ -8,11 +8,14 @@ export default class ArticleRoutes {
|
||||||
uploadMedia: uploadMedia,
|
uploadMedia: uploadMedia,
|
||||||
uploadFile: uploadFile,
|
uploadFile: uploadFile,
|
||||||
deleteFile: deleteFile,
|
deleteFile: deleteFile,
|
||||||
|
requireAuth: opts.requireAuth,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
register(server) {
|
register(server) {
|
||||||
server.flaska.get('/api/articles/:path', this.getArticle.bind(this))
|
if (!this.requireAuth) {
|
||||||
|
server.flaska.get('/api/articles/:path', this.getArticle.bind(this))
|
||||||
|
}
|
||||||
server.flaska.get('/api/auth/articles', server.authenticate(), this.auth_getAllArticles.bind(this))
|
server.flaska.get('/api/auth/articles', server.authenticate(), this.auth_getAllArticles.bind(this))
|
||||||
server.flaska.get('/api/auth/articles/:id', server.authenticate(), this.auth_getSingleArticle.bind(this))
|
server.flaska.get('/api/auth/articles/:id', server.authenticate(), this.auth_getSingleArticle.bind(this))
|
||||||
server.flaska.put('/api/auth/articles/:id', [
|
server.flaska.put('/api/auth/articles/:id', [
|
||||||
|
@ -74,6 +77,7 @@ export default class ArticleRoutes {
|
||||||
params = params.concat(mediaToDatabase(banner, body.remove_banner === 'true'))
|
params = params.concat(mediaToDatabase(banner, body.remove_banner === 'true'))
|
||||||
params = params.concat(mediaToDatabase(media, body.remove_media === 'true'))
|
params = params.concat(mediaToDatabase(media, body.remove_media === 'true'))
|
||||||
}
|
}
|
||||||
|
|
||||||
let res = await ctx.db.safeCallProc('article_auth_get_update_create', params)
|
let res = await ctx.db.safeCallProc('article_auth_get_update_create', params)
|
||||||
|
|
||||||
ctx.body = this.private_getUpdateArticle_resOutput(res)
|
ctx.body = this.private_getUpdateArticle_resOutput(res)
|
||||||
|
@ -111,6 +115,10 @@ export default class ArticleRoutes {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (ctx.req.body.media && ctx.req.body.media.filename && ctx.req.body.media.type && ctx.req.body.media.path && ctx.req.body.media.size) {
|
||||||
|
newMedia = ctx.req.body.media
|
||||||
|
}
|
||||||
|
|
||||||
await Promise.all(promises)
|
await Promise.all(promises)
|
||||||
|
|
||||||
return this.private_getUpdateArticle(ctx, ctx.req.body, newBanner, newMedia)
|
return this.private_getUpdateArticle(ctx, ctx.req.body, newBanner, newMedia)
|
||||||
|
|
|
@ -18,8 +18,8 @@ export default class AuthenticationRoutes {
|
||||||
/** GET: /api/authentication/login */
|
/** GET: /api/authentication/login */
|
||||||
async login(ctx) {
|
async login(ctx) {
|
||||||
let res = await ctx.db.safeCallProc('auth_login', [
|
let res = await ctx.db.safeCallProc('auth_login', [
|
||||||
ctx.req.body.email,
|
ctx.req.body.email || '',
|
||||||
ctx.req.body.password,
|
ctx.req.body.password || '',
|
||||||
])
|
])
|
||||||
|
|
||||||
let out = res.results[0][0]
|
let out = res.results[0][0]
|
||||||
|
|
|
@ -53,6 +53,7 @@ nconf.defaults({
|
||||||
"iss": "dev",
|
"iss": "dev",
|
||||||
"path": "https://media.nfp.is/media/resize",
|
"path": "https://media.nfp.is/media/resize",
|
||||||
"filePath": "https://media.nfp.is/media",
|
"filePath": "https://media.nfp.is/media",
|
||||||
|
"directFilePath": "https://media.nfp.is/media/noprefix",
|
||||||
"removePath": "https://media.nfp.is/media/",
|
"removePath": "https://media.nfp.is/media/",
|
||||||
"preview": {
|
"preview": {
|
||||||
"out": "base64",
|
"out": "base64",
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
import MSSQL from 'msnodesqlv8'
|
import MSSQL from 'msnodesqlv8'
|
||||||
import { HttpError } from 'flaska'
|
import { HttpError } from 'flaska'
|
||||||
|
import { HttpErrorInternal } from './error.mjs'
|
||||||
|
|
||||||
export function initPool(core, config) {
|
export function initPool(core, config) {
|
||||||
let pool = new MSSQL.Pool(config)
|
let pool = new MSSQL.Pool(config)
|
||||||
|
@ -32,7 +33,10 @@ export function initPool(core, config) {
|
||||||
if (err.lineNumber && err.procName) {
|
if (err.lineNumber && err.procName) {
|
||||||
message = `Error at ${err.procName}:${err.lineNumber} => ${message}`
|
message = `Error at ${err.procName}:${err.lineNumber} => ${message}`
|
||||||
}
|
}
|
||||||
throw new HttpError(500, message)
|
throw new HttpErrorInternal(message, err, !err.lineNumber ? {
|
||||||
|
name,
|
||||||
|
params
|
||||||
|
} : null)
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
promises: pool.promises,
|
promises: pool.promises,
|
||||||
|
|
15
base/error.mjs
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
import { HttpError } from 'flaska'
|
||||||
|
|
||||||
|
export class HttpErrorInternal extends HttpError {
|
||||||
|
constructor(message, inner, extra) {
|
||||||
|
super(500, message);
|
||||||
|
|
||||||
|
Error.captureStackTrace(this, HttpError);
|
||||||
|
|
||||||
|
let proto = Object.getPrototypeOf(this);
|
||||||
|
proto.name = 'HttpErrorInternal';
|
||||||
|
|
||||||
|
this.inner = inner
|
||||||
|
this.extra = extra
|
||||||
|
}
|
||||||
|
}
|
|
@ -77,6 +77,7 @@ export default class Client {
|
||||||
}
|
}
|
||||||
if (!options.getRaw && output.status && typeof(output.status) === 'number') {
|
if (!options.getRaw && output.status && typeof(output.status) === 'number') {
|
||||||
let err = new Error(`Request failed [${output.status}]: ${output.message}`)
|
let err = new Error(`Request failed [${output.status}]: ${output.message}`)
|
||||||
|
err.url = path
|
||||||
err.body = output
|
err.body = output
|
||||||
return reject(err)
|
return reject(err)
|
||||||
}
|
}
|
||||||
|
@ -97,6 +98,10 @@ export default class Client {
|
||||||
}
|
}
|
||||||
|
|
||||||
createJwt(body, secret) {
|
createJwt(body, secret) {
|
||||||
|
return Client.createJwt(body, secret)
|
||||||
|
}
|
||||||
|
|
||||||
|
static createJwt(body, secret) {
|
||||||
let header = {
|
let header = {
|
||||||
typ: 'JWT',
|
typ: 'JWT',
|
||||||
alg: 'HS256',
|
alg: 'HS256',
|
||||||
|
@ -130,6 +135,10 @@ export default class Client {
|
||||||
return str;
|
return str;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get(url) {
|
||||||
|
return this.customRequest('GET', url, null)
|
||||||
|
}
|
||||||
|
|
||||||
post(url, body) {
|
post(url, body) {
|
||||||
let parsed = JSON.stringify(body)
|
let parsed = JSON.stringify(body)
|
||||||
return this.customRequest('POST', url, parsed, {
|
return this.customRequest('POST', url, parsed, {
|
||||||
|
|
|
@ -9,44 +9,56 @@ export function uploadMedia(file) {
|
||||||
|
|
||||||
let out = {
|
let out = {
|
||||||
sizes: {
|
sizes: {
|
||||||
small: {},
|
small: media.small ? {} : null,
|
||||||
medium: {},
|
medium: media.medium ? {} : null,
|
||||||
large: {},
|
large: media.large ? {} : null,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let body = {}
|
||||||
|
|
||||||
|
if (media.preview) {
|
||||||
|
body.preview = media.preview
|
||||||
|
}
|
||||||
|
if (media.small?.avif) {
|
||||||
|
body.small = media.small.avif
|
||||||
|
}
|
||||||
|
if (media.medium?.avif) {
|
||||||
|
body.medium = media.medium.avif
|
||||||
|
}
|
||||||
|
if (media.large?.avif) {
|
||||||
|
body.large = media.large.avif
|
||||||
|
}
|
||||||
|
|
||||||
return client.upload(media.path + '?token=' + token, { file: {
|
return client.upload(media.path + '?token=' + token, { file: {
|
||||||
file: file.path,
|
file: file.path,
|
||||||
filename: file.name,
|
filename: file.name,
|
||||||
} }, 'POST', {
|
} }, 'POST', body).then(res => {
|
||||||
preview: media.preview,
|
|
||||||
small: media.small.avif,
|
|
||||||
medium: media.medium.avif,
|
|
||||||
large: media.large.avif,
|
|
||||||
/*
|
|
||||||
|
|
||||||
small: media.small.jpeg,
|
|
||||||
medium: media.medium.avif,
|
|
||||||
large_jpeg: media.large.jpeg,*/
|
|
||||||
}).then(res => {
|
|
||||||
out.filename = res.filename
|
out.filename = res.filename
|
||||||
out.path = res.path
|
out.path = res.path
|
||||||
out.preview = res.preview
|
out.preview = res.preview
|
||||||
out.sizes.small.avif = res.small
|
if (out.sizes.small) { out.sizes.small.avif = res.small }
|
||||||
out.sizes.medium.avif = res.medium
|
if (out.sizes.medium) { out.sizes.medium.avif = res.medium }
|
||||||
out.sizes.large.avif = res.large
|
if (out.sizes.large) { out.sizes.large.avif = res.large }
|
||||||
out.size = file.size
|
out.size = file.size
|
||||||
out.type = file.type
|
out.type = file.type
|
||||||
|
|
||||||
return client.post(media.path + '/' + out.filename + '?token=' + token, {
|
let body = {}
|
||||||
small: media.small.jpeg,
|
if (media.small?.jpeg) {
|
||||||
medium: media.medium.jpeg,
|
body.small = media.small.jpeg
|
||||||
large: media.large.jpeg,
|
}
|
||||||
})
|
if (media.medium?.jpeg) {
|
||||||
|
body.medium = media.medium.jpeg
|
||||||
|
}
|
||||||
|
if (media.large?.jpeg) {
|
||||||
|
body.large = media.large.jpeg
|
||||||
|
}
|
||||||
|
|
||||||
|
return client.post(media.path + '/' + out.filename + '?token=' + token, body)
|
||||||
.then(res => {
|
.then(res => {
|
||||||
out.sizes.small.jpeg = res.small
|
if (out.sizes.small) { out.sizes.small.jpeg = res.small }
|
||||||
out.sizes.medium.jpeg = res.medium
|
if (out.sizes.medium) { out.sizes.medium.jpeg = res.medium }
|
||||||
out.sizes.large.jpeg = res.large
|
if (out.sizes.large) { out.sizes.large.jpeg = res.large }
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
.then(() => {
|
.then(() => {
|
||||||
|
|
|
@ -5,9 +5,9 @@ export function mediaToDatabase(media, removeFlag) {
|
||||||
media.type,
|
media.type,
|
||||||
media.path,
|
media.path,
|
||||||
media.size,
|
media.size,
|
||||||
media.preview.base64,
|
media.preview?.base64 || null,
|
||||||
media.sizes.small.avif.path.replace(/_small\.avif$/, ''),
|
media.sizes?.small?.avif?.path?.replace(/_small\.avif$/, '') || null,
|
||||||
JSON.stringify(media.sizes),
|
JSON.stringify(media.sizes || {}),
|
||||||
removeFlag ? 1 : 0,
|
removeFlag ? 1 : 0,
|
||||||
]
|
]
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -1,9 +1,9 @@
|
||||||
{
|
{
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"dot": "^2.0.0-beta.1",
|
"dot": "^2.0.0-beta.1",
|
||||||
"flaska": "^1.3.1",
|
"flaska": "^1.3.2",
|
||||||
"formidable": "^1.2.6",
|
"formidable": "^1.2.6",
|
||||||
"msnodesqlv8": "^2.4.7",
|
"msnodesqlv8": "^2.7.0",
|
||||||
"nconf-lite": "^2.0.0"
|
"nconf-lite": "^2.0.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -22,8 +22,11 @@ export default class ServeHandler {
|
||||||
}
|
}
|
||||||
|
|
||||||
let indexFile = fsSync.readFileSync(path.join(this.root, 'index.html'))
|
let indexFile = fsSync.readFileSync(path.join(this.root, 'index.html'))
|
||||||
this.template = dot.template(indexFile.toString(), { argName: ['headerDescription', 'headerImage', 'headerTitle', 'headerUrl', 'payloadData', 'payloadTree', 'version', 'nonce', 'type', 'banner'] })
|
this.loadTemplate(indexFile)
|
||||||
// console.log(indexFile.toString())
|
}
|
||||||
|
|
||||||
|
loadTemplate(indexFile) {
|
||||||
|
this.template = dot.template(indexFile.toString(), { argName: ['headerDescription', 'headerImage', 'headerTitle', 'headerUrl', 'payloadData', 'payloadTree', 'version', 'nonce', 'type', 'banner', 'media', 'in_debug'] })
|
||||||
}
|
}
|
||||||
|
|
||||||
register(server) {
|
register(server) {
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
import { Flaska, QueryHandler, JsonHandler, FormidableHandler } from 'flaska'
|
import { Flaska, QueryHandler, JsonHandler, FormidableHandler, HttpError } from 'flaska'
|
||||||
import formidable from 'formidable'
|
import formidable from 'formidable'
|
||||||
|
|
||||||
import { initPool } from './db.mjs'
|
|
||||||
import config from './config.mjs'
|
import config from './config.mjs'
|
||||||
import PageRoutes from './page/routes.mjs'
|
import PageRoutes from './page/routes.mjs'
|
||||||
import ArticleRoutes from './article/routes.mjs'
|
import ArticleRoutes from './article/routes.mjs'
|
||||||
|
@ -15,6 +14,7 @@ export default class Server {
|
||||||
this.http = http
|
this.http = http
|
||||||
this.port = port
|
this.port = port
|
||||||
this.core = core
|
this.core = core
|
||||||
|
this.pool = null
|
||||||
|
|
||||||
this.flaskaOptions = {
|
this.flaskaOptions = {
|
||||||
appendHeaders: {
|
appendHeaders: {
|
||||||
|
@ -43,18 +43,36 @@ export default class Server {
|
||||||
// Create our server
|
// Create our server
|
||||||
this.flaska = new Flaska(this.flaskaOptions, this.http)
|
this.flaska = new Flaska(this.flaskaOptions, this.http)
|
||||||
|
|
||||||
// Create our database pool
|
|
||||||
let pool = this.runCreateDatabase()
|
|
||||||
|
|
||||||
// configure our server
|
// configure our server
|
||||||
if (config.get('NODE_ENV') === 'development') {
|
if (config.get('NODE_ENV') === 'development') {
|
||||||
this.flaska.devMode()
|
this.flaska.devMode()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.flaska.onerror((err, ctx) => {
|
||||||
|
if (err instanceof HttpError && err.status !== 500) {
|
||||||
|
ctx.status = err.status
|
||||||
|
ctx.log.warn(err.message)
|
||||||
|
} else {
|
||||||
|
ctx.log.error(err.inner || err)
|
||||||
|
if (err.extra) {
|
||||||
|
ctx.log.error({ extra: err.extra }, 'Database parameters')
|
||||||
|
}
|
||||||
|
ctx.status = 500
|
||||||
|
}
|
||||||
|
ctx.body = {
|
||||||
|
status: ctx.status,
|
||||||
|
message: err.message,
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
this.flaska.before(function(ctx) {
|
this.flaska.before(function(ctx) {
|
||||||
ctx.state.started = new Date().getTime()
|
ctx.state.started = new Date().getTime()
|
||||||
ctx.db = pool
|
ctx.req.ip = ctx.req.headers['x-forwarded-for'] || ctx.req.connection.remoteAddress
|
||||||
})
|
ctx.log = ctx.log.child({
|
||||||
|
id: Math.random().toString(36).substring(2, 14),
|
||||||
|
})
|
||||||
|
ctx.db = this.pool
|
||||||
|
}.bind(this))
|
||||||
this.flaska.before(QueryHandler())
|
this.flaska.before(QueryHandler())
|
||||||
|
|
||||||
let healthChecks = 0
|
let healthChecks = 0
|
||||||
|
@ -77,7 +95,7 @@ export default class Server {
|
||||||
level = 'error'
|
level = 'error'
|
||||||
}
|
}
|
||||||
|
|
||||||
if (ctx.url === '/health') {
|
if (ctx.url === '/health' || ctx.url === '/api/health') {
|
||||||
healthChecks++
|
healthChecks++
|
||||||
if (healthChecks >= healthCollectLimit) {
|
if (healthChecks >= healthCollectLimit) {
|
||||||
ctx.log[level]({
|
ctx.log[level]({
|
||||||
|
@ -92,6 +110,7 @@ export default class Server {
|
||||||
ctx.log[level]({
|
ctx.log[level]({
|
||||||
duration: requestTime,
|
duration: requestTime,
|
||||||
status: ctx.status,
|
status: ctx.status,
|
||||||
|
ip: ctx.req.ip,
|
||||||
}, (ctx.aborted ? '[ABORT]' : '<--') + ` ${status}${ctx.method} ${ctx.url}`)
|
}, (ctx.aborted ? '[ABORT]' : '<--') + ` ${status}${ctx.method} ${ctx.url}`)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -104,7 +123,9 @@ export default class Server {
|
||||||
}
|
}
|
||||||
|
|
||||||
runCreateDatabase() {
|
runCreateDatabase() {
|
||||||
return initPool(this.core, config.get('mssql'))
|
return import('./db.mjs').then(db => {
|
||||||
|
this.pool = db.initPool(this.core, config.get('mssql'))
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
runStartListen() {
|
runStartListen() {
|
||||||
|
@ -114,9 +135,13 @@ export default class Server {
|
||||||
}
|
}
|
||||||
|
|
||||||
run() {
|
run() {
|
||||||
this.runCreateServer()
|
return Promise.all([
|
||||||
this.runRegisterRoutes()
|
this.runCreateServer(),
|
||||||
|
this.runCreateDatabase(),
|
||||||
return this.runStartListen()
|
]).then(() => {
|
||||||
|
return this.runRegisterRoutes()
|
||||||
|
}).then(() => {
|
||||||
|
return this.runStartListen()
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,7 +6,7 @@ export default class StaticRoutes {
|
||||||
}
|
}
|
||||||
|
|
||||||
register(server) {
|
register(server) {
|
||||||
server.flaska.get('/health', this.health.bind(this))
|
server.flaska.get('/api/health', this.health.bind(this))
|
||||||
}
|
}
|
||||||
|
|
||||||
health(ctx) {
|
health(ctx) {
|
||||||
|
|
|
@ -23,11 +23,11 @@ export function decode(base64StringUrlSafe) {
|
||||||
export function parseMediaAndBanner(item) {
|
export function parseMediaAndBanner(item) {
|
||||||
if (item.banner_path) {
|
if (item.banner_path) {
|
||||||
item.banner_path = 'https://cdn.nfp.is' + item.banner_path
|
item.banner_path = 'https://cdn.nfp.is' + item.banner_path
|
||||||
item.banner_alt_prefix = 'https://cdn.nfp.is' + item.banner_alt_prefix
|
item.banner_alt_prefix = 'https://cdn.nfp.is' + (item.banner_alt_prefix || '')
|
||||||
}
|
}
|
||||||
if (item.media_path) {
|
if (item.media_path) {
|
||||||
item.media_path = 'https://cdn.nfp.is' + item.media_path
|
item.media_path = 'https://cdn.nfp.is' + item.media_path
|
||||||
item.media_alt_prefix = 'https://cdn.nfp.is' + item.media_alt_prefix
|
item.media_alt_prefix = 'https://cdn.nfp.is' + (item.media_alt_prefix || '')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
1
discord_embed/.npmrc
Normal file
|
@ -0,0 +1 @@
|
||||||
|
package-lock=false
|
85
discord_embed/api/id.mjs
Normal file
|
@ -0,0 +1,85 @@
|
||||||
|
/**
|
||||||
|
* Javascript AlphabeticID class
|
||||||
|
* (based on a script by Kevin van Zonneveld <kevin@vanzonneveld.net>)
|
||||||
|
*
|
||||||
|
* Author: Even Simon <even.simon@gmail.com>
|
||||||
|
*
|
||||||
|
* Description: Translates a numeric identifier into a short string and backwords.
|
||||||
|
*
|
||||||
|
* Usage:
|
||||||
|
* var str = AlphabeticID.encode(9007199254740989); // str = 'fE2XnNGpF'
|
||||||
|
* var id = AlphabeticID.decode('fE2XnNGpF'); // id = 9007199254740989;
|
||||||
|
**/
|
||||||
|
|
||||||
|
const AlphabeticID = {
|
||||||
|
index:'WzPmtXQhwGnOiB8Vvu9fC1SL2l4F7MrUNc0RbD6dAayI3YTosejpZxJH5KqgEk', // abcdefghijklmnopqrstuvwxyz0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ
|
||||||
|
|
||||||
|
/**
|
||||||
|
* [@function](https://twitter.com/function) AlphabeticID.encode
|
||||||
|
* [@description](https://twitter.com/description) Encode a number into short string
|
||||||
|
* [@param](https://twitter.com/param) integer
|
||||||
|
* [@return](https://twitter.com/return) string
|
||||||
|
**/
|
||||||
|
encode:function(_number){
|
||||||
|
if('undefined' == typeof _number){
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
else if('number' != typeof(_number)){
|
||||||
|
throw new Error('Wrong parameter type');
|
||||||
|
}
|
||||||
|
|
||||||
|
var ret = '';
|
||||||
|
|
||||||
|
for(var i=Math.floor(Math.log(parseInt(_number))/Math.log(AlphabeticID.index.length));i>=0;i--){
|
||||||
|
ret = ret + AlphabeticID.index.substr((Math.floor(parseInt(_number) / AlphabeticID.bcpow(AlphabeticID.index.length, i)) % AlphabeticID.index.length),1);
|
||||||
|
}
|
||||||
|
|
||||||
|
return reverse(ret);
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* [@function](https://twitter.com/function) AlphabeticID.decode
|
||||||
|
* [@description](https://twitter.com/description) Decode a short string and return number
|
||||||
|
* [@param](https://twitter.com/param) string
|
||||||
|
* [@return](https://twitter.com/return) integer
|
||||||
|
**/
|
||||||
|
decode:function(_string){
|
||||||
|
if('undefined' == typeof _string){
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
else if('string' != typeof _string){
|
||||||
|
throw new Error('Wrong parameter type');
|
||||||
|
}
|
||||||
|
|
||||||
|
var str = reverse(_string);
|
||||||
|
var ret = 0;
|
||||||
|
|
||||||
|
for(var i=0;i<=(str.length - 1);i++){
|
||||||
|
ret = ret + AlphabeticID.index.indexOf(str.substr(i,1)) * (AlphabeticID.bcpow(AlphabeticID.index.length, (str.length - 1) - i));
|
||||||
|
}
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* [@function](https://twitter.com/function) AlphabeticID.bcpow
|
||||||
|
* [@description](https://twitter.com/description) Raise _a to the power _b
|
||||||
|
* [@param](https://twitter.com/param) float _a
|
||||||
|
* [@param](https://twitter.com/param) integer _b
|
||||||
|
* [@return](https://twitter.com/return) string
|
||||||
|
**/
|
||||||
|
bcpow:function(_a, _b){
|
||||||
|
return Math.floor(Math.pow(parseFloat(_a), parseInt(_b)));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* [@function](https://twitter.com/function) String.reverse
|
||||||
|
* [@description](https://twitter.com/description) Reverse a string
|
||||||
|
* [@return](https://twitter.com/return) string
|
||||||
|
**/
|
||||||
|
function reverse(str){
|
||||||
|
return str.split('').reverse().join('');
|
||||||
|
};
|
||||||
|
|
||||||
|
export default AlphabeticID
|
156
discord_embed/api/post.mjs
Normal file
|
@ -0,0 +1,156 @@
|
||||||
|
import { uploadMedia, deleteFile } from '../base/media/upload.mjs'
|
||||||
|
import config from '../base/config.mjs'
|
||||||
|
import AlphabeticID from './id.mjs'
|
||||||
|
|
||||||
|
export default class IndexPost {
|
||||||
|
constructor(opts = {}) {
|
||||||
|
Object.assign(this, {
|
||||||
|
frontend: opts.frontend,
|
||||||
|
uploadMedia: uploadMedia,
|
||||||
|
deleteFile: deleteFile,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
register(server) {
|
||||||
|
this.serve = server.routes.serve
|
||||||
|
server.flaska.post('/', [
|
||||||
|
server.formidable({ maxFileSize: 8 * 1024 * 1024, }),
|
||||||
|
], this.createNewLink.bind(this))
|
||||||
|
server.flaska.get('/video/:id', this.videoRedirect.bind(this))
|
||||||
|
}
|
||||||
|
|
||||||
|
async videoRedirect(ctx) {
|
||||||
|
try {
|
||||||
|
let id = AlphabeticID.decode(ctx.params.id.slice(0,-5))
|
||||||
|
let videoLink = null
|
||||||
|
if (id) {
|
||||||
|
let res = await ctx.db.safeCallProc('discord_embed.link_get', [id - 3843])
|
||||||
|
if (res.first.length) {
|
||||||
|
videoLink = res.first[0].video_link
|
||||||
|
} else {
|
||||||
|
ctx.status = 404
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (videoLink) {
|
||||||
|
ctx.status = 302
|
||||||
|
ctx.headers['Location'] = videoLink
|
||||||
|
ctx.type = 'text/html; charset=utf-8'
|
||||||
|
ctx.body = `
|
||||||
|
Redirecting
|
||||||
|
<a href="${videoLink}">Click here if it doesn't redirect</a>
|
||||||
|
`
|
||||||
|
return
|
||||||
|
} else {
|
||||||
|
ctx.status = 404
|
||||||
|
ctx.state.error = 'Video not found.'
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
ctx.log.error(err, 'Unable to fetch resource ' + ctx.url.slice(1))
|
||||||
|
ctx.state.error = 'Unknown error while fetching link.'
|
||||||
|
}
|
||||||
|
return this.serve.serveIndex(ctx)
|
||||||
|
}
|
||||||
|
|
||||||
|
hasErrors(ctx, hasMedia) {
|
||||||
|
if (!ctx.req.body.video) {
|
||||||
|
return 'Missing video link'
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!ctx.req.body.video.startsWith('http')) {
|
||||||
|
return 'Video link has to be a valid full url'
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ctx.req.body.image) {
|
||||||
|
if (!ctx.req.body.image.startsWith('http')) {
|
||||||
|
return 'Image link has to be a valid full url'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async getLink(ctx) {
|
||||||
|
|
||||||
|
return this.serve.serveIndex(ctx)
|
||||||
|
}
|
||||||
|
|
||||||
|
/** POST: / */
|
||||||
|
async createNewLink(ctx) {
|
||||||
|
ctx.state.video = ctx.req.body.video
|
||||||
|
ctx.state.image = ctx.req.body.image || 'https://cdn.nfp.is/av1/empty.png'
|
||||||
|
|
||||||
|
let rateLimited = false
|
||||||
|
let redisKey = 'ratelimit_' + ctx.req.ip.replace(/:/g, '-')
|
||||||
|
|
||||||
|
try {
|
||||||
|
let val = (await ctx.redis.get(redisKey))
|
||||||
|
val = val && Number(val) || 0
|
||||||
|
if (val > 3) {
|
||||||
|
rateLimited = true
|
||||||
|
} else if (val > 2) {
|
||||||
|
await ctx.redis.setex(redisKey, 60 * 15, val + 1)
|
||||||
|
rateLimited = true
|
||||||
|
} else {
|
||||||
|
await ctx.redis.setex(redisKey, 15, val + 1)
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
ctx.log.error(err, 'Error checking rate limit for ' + redisKey)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (rateLimited) {
|
||||||
|
ctx.state.error = 'You have reached rate limit. Please wait at least 15 minutes.'
|
||||||
|
return this.serve.serveIndex(ctx)
|
||||||
|
}
|
||||||
|
|
||||||
|
let hasMedia = ctx.req.files.media && ctx.req.files.media.size
|
||||||
|
let redirect = ''
|
||||||
|
let error = this.hasErrors(ctx, hasMedia)
|
||||||
|
|
||||||
|
if (!error && hasMedia) {
|
||||||
|
try {
|
||||||
|
let temp = await this.uploadMedia(ctx.req.files.media)
|
||||||
|
ctx.state.image = ctx.req.body.image = 'https://cdn.nfp.is' + temp.sizes.small.jpeg.path
|
||||||
|
|
||||||
|
await this.deleteFile(temp.filename).catch(err => {
|
||||||
|
ctx.log.error(err, 'Error removing ' + temp.filename)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
catch (err) {
|
||||||
|
ctx.log.error(err)
|
||||||
|
error = 'Unable to upload file: ' + err.message
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!error) {
|
||||||
|
redirect = `${this.frontend}/?v=${ctx.state.video}&i=${ctx.state.image}`
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!error) {
|
||||||
|
try {
|
||||||
|
let params = [
|
||||||
|
ctx.state.video,
|
||||||
|
ctx.state.image,
|
||||||
|
ctx.req.ip,
|
||||||
|
]
|
||||||
|
let res = await ctx.db.safeCallProc('discord_embed.link_add', params)
|
||||||
|
let id = AlphabeticID.encode(res.first[0].id + 3843)
|
||||||
|
redirect = `${this.frontend}/${id}`
|
||||||
|
}
|
||||||
|
catch (err) {
|
||||||
|
ctx.log.error(err)
|
||||||
|
error = 'Error while generating shortened link.'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (redirect && !error) {
|
||||||
|
ctx.status = 302
|
||||||
|
ctx.headers['Location'] = redirect
|
||||||
|
ctx.type = 'text/html; charset=utf-8'
|
||||||
|
ctx.body = `
|
||||||
|
Redirecting
|
||||||
|
<a href="${redirect}">Click here if it doesn't redirect</a>
|
||||||
|
`
|
||||||
|
}
|
||||||
|
ctx.state.error = error
|
||||||
|
return this.serve.serveIndex(ctx)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// https://litter.catbox.moe/cnl6hy.mp4
|
116
discord_embed/api/serve.mjs
Normal file
|
@ -0,0 +1,116 @@
|
||||||
|
import path from 'path'
|
||||||
|
import { HttpError } from 'flaska'
|
||||||
|
import Parent from '../base/serve.mjs'
|
||||||
|
import fs from 'fs/promises'
|
||||||
|
import fsSync from 'fs'
|
||||||
|
import dot from 'dot'
|
||||||
|
import config from '../base/config.mjs'
|
||||||
|
import AlphabeticID from './id.mjs'
|
||||||
|
|
||||||
|
export default class ServeHandler extends Parent {
|
||||||
|
loadTemplate(indexFile) {
|
||||||
|
this.template = dot.template(indexFile.toString(), { argName: [
|
||||||
|
'imageLink',
|
||||||
|
'videoLink',
|
||||||
|
'error',
|
||||||
|
'siteUrl',
|
||||||
|
'siteUrlBase',
|
||||||
|
'version',
|
||||||
|
'nonce',
|
||||||
|
'in_debug',
|
||||||
|
'inputVideo',
|
||||||
|
'inputImage'
|
||||||
|
], strip: false })
|
||||||
|
}
|
||||||
|
|
||||||
|
register(server) {
|
||||||
|
super.register(server)
|
||||||
|
server.flaska.onerror(this.serveError.bind(this))
|
||||||
|
}
|
||||||
|
|
||||||
|
serveError(err, ctx) {
|
||||||
|
ctx.log.error(err)
|
||||||
|
|
||||||
|
if (err instanceof HttpError) {
|
||||||
|
ctx.status = err.status
|
||||||
|
ctx.state.error = err.message
|
||||||
|
} else {
|
||||||
|
ctx.status = 500
|
||||||
|
ctx.state.error = 'Unknown error occured'
|
||||||
|
}
|
||||||
|
|
||||||
|
let videoLink = ctx.query.get('v') || ''
|
||||||
|
let imageLink = ctx.query.get('i') || ''
|
||||||
|
|
||||||
|
ctx.body = this.template({
|
||||||
|
videoLink: videoLink,
|
||||||
|
imageLink: imageLink,
|
||||||
|
error: ctx.state.error || '',
|
||||||
|
inputVideo: ctx.state.video || videoLink || '',
|
||||||
|
inputImage: ctx.state.image || imageLink || '',
|
||||||
|
siteUrl: this.frontend + ctx.url,
|
||||||
|
siteUrlBase: this.frontend + '/',
|
||||||
|
version: this.version,
|
||||||
|
nonce: ctx.state.nonce,
|
||||||
|
in_debug: config.get('NODE_ENV') === 'development' && false,
|
||||||
|
})
|
||||||
|
ctx.type = 'text/html; charset=utf-8'
|
||||||
|
}
|
||||||
|
|
||||||
|
async serveIndex(ctx) {
|
||||||
|
if (config.get('NODE_ENV') === 'development') {
|
||||||
|
let indexFile = await fs.readFile(path.join(this.root, 'index.html'))
|
||||||
|
this.loadTemplate(indexFile)
|
||||||
|
}
|
||||||
|
|
||||||
|
let videoLink = ctx.query.get('v') || ''
|
||||||
|
let imageLink = ctx.query.get('i') || (videoLink ? 'https://cdn.nfp.is/av1/empty.png' : '')
|
||||||
|
|
||||||
|
if (!ctx.state.error) {
|
||||||
|
if (ctx.url.match(/^\/[a-zA-Z0-9][a-zA-Z0-9][a-zA-Z0-9]+$/) && ctx.url.length < 7) {
|
||||||
|
try {
|
||||||
|
let id = AlphabeticID.decode(ctx.url.slice(1))
|
||||||
|
if (id) {
|
||||||
|
let res = await ctx.db.safeCallProc('discord_embed.link_get', [id - 3843])
|
||||||
|
if (res.first.length) {
|
||||||
|
videoLink = ctx.state.video = res.first[0].video_link
|
||||||
|
if (!ctx.state.video.startsWith('https://cdn.discordapp.com')
|
||||||
|
&& !ctx.state.video.includes('catbox.')
|
||||||
|
&& ctx.state.video.includes('?')) {
|
||||||
|
videoLink = this.frontend + '/video/' + ctx.url.slice(1) + '.webm'
|
||||||
|
}
|
||||||
|
imageLink = res.first[0].image_link
|
||||||
|
} else {
|
||||||
|
ctx.status = 404
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
ctx.log.error(err, 'Unable to fetch resource ' + ctx.url.slice(1))
|
||||||
|
ctx.state.error = 'Unknown error while fetching link.'
|
||||||
|
}
|
||||||
|
} else if (ctx.url !== '/') {
|
||||||
|
ctx.status = 404
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (videoLink.startsWith('https://cdn.discordapp.com')) {
|
||||||
|
videoLink = videoLink.replace('https://cdn.discordapp.com', 'https://discordproxy.nfp.is')
|
||||||
|
}
|
||||||
|
|
||||||
|
let payload = {
|
||||||
|
videoLink: videoLink,
|
||||||
|
imageLink: imageLink,
|
||||||
|
error: ctx.state.error || '',
|
||||||
|
inputVideo: ctx.state.video || videoLink || '',
|
||||||
|
inputImage: ctx.state.image || imageLink || '',
|
||||||
|
siteUrl: this.frontend + ctx.url,
|
||||||
|
siteUrlBase: this.frontend + '/',
|
||||||
|
version: this.version,
|
||||||
|
nonce: ctx.state.nonce,
|
||||||
|
in_debug: config.get('NODE_ENV') === 'development' && false,
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx.body = this.template(payload)
|
||||||
|
ctx.type = 'text/html; charset=utf-8'
|
||||||
|
}
|
||||||
|
}
|
40
discord_embed/api/server.mjs
Normal file
|
@ -0,0 +1,40 @@
|
||||||
|
import Redis from 'ioredis'
|
||||||
|
import config from '../base/config.mjs'
|
||||||
|
import Parent from '../base/server.mjs'
|
||||||
|
import StaticRoutes from '../base/static_routes.mjs'
|
||||||
|
import IndexPost from './post.mjs'
|
||||||
|
import ServeHandler from './serve.mjs'
|
||||||
|
|
||||||
|
export default class Server extends Parent {
|
||||||
|
init() {
|
||||||
|
super.init()
|
||||||
|
let localUtil = new this.core.sc.Util(import.meta.url)
|
||||||
|
|
||||||
|
this.flaskaOptions.appendHeaders['Content-Security-Policy'] = `default-src 'self'; script-src 'self' 'nonce-0d1valZOnjp8ZpR6vBd4dg=='; style-src 'self' 'unsafe-inline'; img-src * data: blob:; media-src *; font-src 'self' data:; object-src 'none'; frame-ancestors 'none'`
|
||||||
|
this.flaskaOptions.appendHeaders['Cross-Origin-Embedder-Policy'] = 'unsafe-none'
|
||||||
|
this.redis = new Redis(config.get('redis'))
|
||||||
|
this.redis.on('error', (err) => {
|
||||||
|
this.core.log.error(err)
|
||||||
|
})
|
||||||
|
this.routes = {
|
||||||
|
static: new StaticRoutes(),
|
||||||
|
post: new IndexPost({
|
||||||
|
frontend: config.get('frontend:url'),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
this.routes.serve = new ServeHandler({
|
||||||
|
root: localUtil.getPathFromRoot('../public'),
|
||||||
|
version: this.core.app.running,
|
||||||
|
frontend: config.get('frontend:url'),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
runCreateServer() {
|
||||||
|
super.runCreateServer()
|
||||||
|
|
||||||
|
|
||||||
|
this.flaska.before((ctx) => {
|
||||||
|
ctx.redis = this.redis
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
1
discord_embed/base
Symbolic link
|
@ -0,0 +1 @@
|
||||||
|
../base
|
8
discord_embed/build-package.json
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
{
|
||||||
|
"scripts": {
|
||||||
|
"build": "echo done;"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"service-core": "^3.0.0-beta.17"
|
||||||
|
}
|
||||||
|
}
|
38
discord_embed/index.mjs
Normal file
|
@ -0,0 +1,38 @@
|
||||||
|
import fs from 'fs'
|
||||||
|
import { pathToFileURL } from 'url'
|
||||||
|
import config from './base/config.mjs'
|
||||||
|
|
||||||
|
export function start(http, port, ctx) {
|
||||||
|
config.sources[1].store = ctx.config
|
||||||
|
|
||||||
|
return import('./api/server.mjs')
|
||||||
|
.then(function(module) {
|
||||||
|
let server = new module.default(http, port, ctx)
|
||||||
|
return server.run()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
if (import.meta.url === pathToFileURL(process.argv[1]).href) {
|
||||||
|
import('service-core').then(core => {
|
||||||
|
const port = 4120
|
||||||
|
|
||||||
|
var core = new core.ServiceCore('nfp_moe', import.meta.url, port, '')
|
||||||
|
|
||||||
|
let config = {
|
||||||
|
frontend: {
|
||||||
|
url: 'http://localhost:' + port
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
config = JSON.parse(fs.readFileSync('./config.json'))
|
||||||
|
} catch {}
|
||||||
|
|
||||||
|
config.port = port
|
||||||
|
|
||||||
|
core.setConfig(config)
|
||||||
|
core.init({ start }).then(function() {
|
||||||
|
return core.run()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
49
discord_embed/package.json
Normal file
|
@ -0,0 +1,49 @@
|
||||||
|
{
|
||||||
|
"name": "discord_embed",
|
||||||
|
"version": "1.0.26",
|
||||||
|
"port": 4120,
|
||||||
|
"description": "AV1 discord server embed helper",
|
||||||
|
"main": "index.js",
|
||||||
|
"directories": {
|
||||||
|
"test": "test"
|
||||||
|
},
|
||||||
|
"scripts": {
|
||||||
|
"start": "node index.mjs",
|
||||||
|
"dev:server": "node index.mjs | bunyan",
|
||||||
|
"dev": "npm-watch dev:server"
|
||||||
|
},
|
||||||
|
"watch": {
|
||||||
|
"dev:server": {
|
||||||
|
"patterns": [
|
||||||
|
"api/*",
|
||||||
|
"base/*",
|
||||||
|
"../base/*"
|
||||||
|
],
|
||||||
|
"extensions": "js,mjs",
|
||||||
|
"quiet": true,
|
||||||
|
"inherit": true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"repository": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://git.nfp.is/nfp/nfp_sites.git"
|
||||||
|
},
|
||||||
|
"author": "Jonatan Nilsson",
|
||||||
|
"license": "WTFPL",
|
||||||
|
"bugs": {
|
||||||
|
"url": "https://git.nfp.is/nfp/nfp_sites/issues"
|
||||||
|
},
|
||||||
|
"homepage": "https://git.nfp.is/nfp/nfp_sites",
|
||||||
|
"dependencies": {
|
||||||
|
"bunyan-lite": "^1.2.1",
|
||||||
|
"dot": "^2.0.0-beta.1",
|
||||||
|
"flaska": "^1.3.4",
|
||||||
|
"formidable": "^1.2.6",
|
||||||
|
"ioredis": "^5.2.3",
|
||||||
|
"msnodesqlv8": "^4.1.1",
|
||||||
|
"nconf-lite": "^2.0.0"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"service-core": "^3.0.0-beta.17"
|
||||||
|
}
|
||||||
|
}
|
0
discord_embed/public/app.css
Normal file
BIN
discord_embed/public/favicon.png
Normal file
After Width: | Height: | Size: 3 KiB |
BIN
discord_embed/public/heart.png
Normal file
After Width: | Height: | Size: 393 KiB |
330
discord_embed/public/index.html
Normal file
|
@ -0,0 +1,330 @@
|
||||||
|
<!doctype html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<title>Discord Embedder from AV1 server 1.0.25</title>
|
||||||
|
<base href="/">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
|
{{ if (imageLink) { }}
|
||||||
|
<meta property="og:image" content="{{=imageLink}}">
|
||||||
|
<meta property="og:type" content="video.other">
|
||||||
|
<meta property="og:video:url" content="{{=videoLink}}">
|
||||||
|
<meta property="og:video:width" content="1280">
|
||||||
|
<meta property="og:video:height" content="720">
|
||||||
|
{{ } else { }}
|
||||||
|
<meta property="og:type" content="website" />
|
||||||
|
<meta property="og:url" content="{{=siteUrl}}" />
|
||||||
|
<meta property="og:image" content="/heart.png" />
|
||||||
|
<meta property="og:description" content="Simple site to help with embedding of AV1 videos and large/external videos into Discord." />
|
||||||
|
<meta property="og:title" content="Discord Embedder Helper Website" />
|
||||||
|
{{ } }}
|
||||||
|
<link rel="icon" type="image/png" href="/favicon.png">
|
||||||
|
<style>
|
||||||
|
|
||||||
|
:root {
|
||||||
|
--content-max-width: 1280px;
|
||||||
|
--bg: black;
|
||||||
|
--bg-content-alt: #333;
|
||||||
|
--color: #d7dadc;
|
||||||
|
--link: #bb4d00;
|
||||||
|
--button-border: 1px solid #f57c00;
|
||||||
|
--button-bg: #ffad42;
|
||||||
|
--button-fg: #000;
|
||||||
|
--error: red;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Box sizing rules */
|
||||||
|
*, *::before, *::after { box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Remove default margin */
|
||||||
|
body, h1, h2, h3, h4, p, figure, blockquote, dl, dd {
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
min-height: 100vh;
|
||||||
|
text-rendering: optimizeSpeed;
|
||||||
|
line-height: 1.5;
|
||||||
|
font-size: 16px;
|
||||||
|
padding: 1rem 0.5rem 3rem;
|
||||||
|
font-family: sans-serif;
|
||||||
|
background: var(--bg);
|
||||||
|
color: var(--color);
|
||||||
|
}
|
||||||
|
|
||||||
|
input, button, textarea, select {
|
||||||
|
font: inherit;
|
||||||
|
}
|
||||||
|
|
||||||
|
h1 {
|
||||||
|
font-size: 1.88rem;
|
||||||
|
}
|
||||||
|
h2 {
|
||||||
|
font-size: 1.66rem;
|
||||||
|
}
|
||||||
|
h3 {
|
||||||
|
font-size: 1.44rem;
|
||||||
|
}
|
||||||
|
h4 {
|
||||||
|
font-size: 1.22rem;
|
||||||
|
}
|
||||||
|
h5 {
|
||||||
|
font-size: 1.0rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
a, a:visited, button {
|
||||||
|
text-decoration: none;
|
||||||
|
border: none;
|
||||||
|
padding: 0;
|
||||||
|
margin: 0;
|
||||||
|
font-weight: bold;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
input[type=text] {
|
||||||
|
border: 1px solid var(--color);
|
||||||
|
background: var(--bg);
|
||||||
|
color: var(--color);
|
||||||
|
border-radius: 0;
|
||||||
|
padding: 0.25rem;
|
||||||
|
line-height: 1rem;
|
||||||
|
outline: none;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
label {
|
||||||
|
font-size: 0.75rem;
|
||||||
|
font-weight: 500;
|
||||||
|
margin: 0.5rem 0;
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
input[type=text]:hover,
|
||||||
|
input[type=text]:active,
|
||||||
|
input[type=text]:focus {
|
||||||
|
border-color: var(--link);
|
||||||
|
}
|
||||||
|
|
||||||
|
input[type=text]:focus {
|
||||||
|
outline: 1px solid var(--link);
|
||||||
|
}
|
||||||
|
|
||||||
|
button,
|
||||||
|
input[type=submit] {
|
||||||
|
border: var(--button-border);
|
||||||
|
background: var(--button-bg);
|
||||||
|
color: var(--button-fg);
|
||||||
|
padding: 0.25rem 1rem;
|
||||||
|
margin: 0rem 0 2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.row {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.row-item {
|
||||||
|
flex: 2 0 200px;
|
||||||
|
align-self: flex-end;
|
||||||
|
}
|
||||||
|
|
||||||
|
.row-item input {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.row-inbetween {
|
||||||
|
align-self: flex-end;
|
||||||
|
padding: 0.25rem 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.error {
|
||||||
|
color: var(--error);
|
||||||
|
font-size: 0.8rem;
|
||||||
|
padding: 1rem 1rem 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.center {
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
video {
|
||||||
|
max-width: calc(100vw - 2rem);
|
||||||
|
max-height: calc(100vh - 140px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.inside {
|
||||||
|
width: 100%;
|
||||||
|
max-width: var(--content-max-width);
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: stretch;
|
||||||
|
margin: 0 auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
h1 {
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
p {
|
||||||
|
margin-bottom: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.warning {
|
||||||
|
font-size: 0.9rem;
|
||||||
|
color: #ffe69c;
|
||||||
|
margin-bottom: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.alert {
|
||||||
|
font-size: 0.9rem;
|
||||||
|
margin: 0.5rem 0 1rem;
|
||||||
|
color: hsl(353.7, 70.5%, 87%);
|
||||||
|
}
|
||||||
|
|
||||||
|
.info {
|
||||||
|
font-size: 0.9rem;
|
||||||
|
color: #a3cfbb;
|
||||||
|
margin-bottom: 0.5rem;
|
||||||
|
}
|
||||||
|
.info a, .info a:visited, .info a:hover {
|
||||||
|
color: inherit;
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
||||||
|
|
||||||
|
.optional {
|
||||||
|
margin: 0 -0.5rem 0.5rem;
|
||||||
|
padding: 0rem 0.5rem 1rem;
|
||||||
|
background: var(--bg-content-alt);
|
||||||
|
}
|
||||||
|
|
||||||
|
#testing {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: stretch;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hidden {
|
||||||
|
display: none !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
pre {
|
||||||
|
padding: 0.5rem !important;
|
||||||
|
white-space: break-spaces;
|
||||||
|
word-break: break-all;
|
||||||
|
}
|
||||||
|
|
||||||
|
#mainplayer {
|
||||||
|
max-width: calc(100vw - 1rem);
|
||||||
|
max-height: 95vh;
|
||||||
|
}
|
||||||
|
|
||||||
|
#previewVideo {
|
||||||
|
border: 1px solid white;
|
||||||
|
max-width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
{{ if (videoLink && imageLink) { }}
|
||||||
|
<p>Your link is:</p>
|
||||||
|
<pre>{{=siteUrl}}</pre>
|
||||||
|
<div class="center">
|
||||||
|
<video id=mainplayer" crossorigin controls poster="{{=imageLink}}">
|
||||||
|
<source src="{{=videoLink}}">
|
||||||
|
</video>
|
||||||
|
</div>
|
||||||
|
{{ } }}
|
||||||
|
<form action="/" method="post" enctype="multipart/form-data" class="inside">
|
||||||
|
<h1>Embed {{ if (videoLink && imageLink) { }} another {{ } }} video for discord:</h1>
|
||||||
|
<p>Use this tool to generate links that forces discord to try and play a video or movie directly inside discord. By default, discord will not show video playback for video links that are too large or not in proper format. This tool forces discord to at least try.</p>
|
||||||
|
|
||||||
|
<p class="warning">Only sites and filesharing sites that have direct link to the video will work and play in discord. Youtube has it's own player and will not work. Many video sites will have their own player and will not work.</p>
|
||||||
|
|
||||||
|
<p class="info">Video upload sites that work are sites like <a href="https://catbox.moe/" target="_blank">catbox.moe</a> and other sites that allow <b>direct link to video file</b> will work.</p>
|
||||||
|
|
||||||
|
{{ if (error) { }}<p class="error">{{=error}}</p>{{ } }}
|
||||||
|
<p> </p>
|
||||||
|
<label>Link to video file you want to play in discord*</label>
|
||||||
|
<input id="inputVideo" type="text" name="video" value="{{=inputVideo}}">
|
||||||
|
<p class="alert">
|
||||||
|
<b>.mkv</b> video files will <b>not work</b>! <b>HEVC/h.265</b> will also <b>not work</b>! File hosting sites like <b>mediafire/google drive/dropbox</b> will also probably <b>not work</b>!<br>
|
||||||
|
</p>
|
||||||
|
<div class="row optional">
|
||||||
|
<div class="row-item">
|
||||||
|
<label>Optional: Link to image to show before discord user presses play</label>
|
||||||
|
<input id="inputImage" type="text" name="image" value="{{=inputImage}}">
|
||||||
|
</div>
|
||||||
|
<span class="row-inbetween">or</span>
|
||||||
|
<div class="row-item">
|
||||||
|
<label>Optional: Upload image to show before discord user presses play</label>
|
||||||
|
<input type="file" name="media" type="image">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div id="testing" class="hidden">
|
||||||
|
<p>Test area (does the video load and play? If not, it might not work on discord)</p>
|
||||||
|
|
||||||
|
<div class="center">
|
||||||
|
<video class="hidden" id="previewVideo" crossorigin controls poster="" preload="none">
|
||||||
|
<source src="">
|
||||||
|
</video>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<pre class="optional" id="generateurl">{{=siteUrlBase}}?v=<video link></pre>
|
||||||
|
|
||||||
|
<input type="submit" value="Generate shorter url">
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
<script type="text/javascript" nonce="{{=nonce}}">
|
||||||
|
var defaultPoster = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAABQAAAALQAQAAAADnBuD7AAAAh0lEQVR42u3BMQEAAADCIPuntsROYAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACkDsTfAAFMFnd/AAAAAElFTkSuQmCC';
|
||||||
|
var baseSite = '{{=siteUrlBase}}';
|
||||||
|
var existingVideoUrl = '{{=inputVideo}}';
|
||||||
|
var testing = document.getElementById('testing');
|
||||||
|
var generateurl = document.getElementById('generateurl');
|
||||||
|
var inputVideo = document.getElementById('inputVideo');
|
||||||
|
var inputImage = document.getElementById('inputImage');
|
||||||
|
var previewVideo = document.getElementById('previewVideo');
|
||||||
|
var isExample = true;
|
||||||
|
previewVideo.poster = defaultPoster;
|
||||||
|
|
||||||
|
function checkChange() {
|
||||||
|
var currentIsExample = true;
|
||||||
|
if (inputVideo.value) {
|
||||||
|
currentIsExample = false;
|
||||||
|
}
|
||||||
|
if (isExample && currentIsExample) { return; }
|
||||||
|
isExample = currentIsExample;
|
||||||
|
if (isExample) {
|
||||||
|
generateurl.innerText = baseSite + '?v=<video link>';
|
||||||
|
previewVideo.classList.add('hidden')
|
||||||
|
} else {
|
||||||
|
generateurl.innerText = baseSite + '?v=' + encodeURIComponent(inputVideo.value) + (inputImage.value ? '&i=' + encodeURIComponent(inputImage.value) : '');
|
||||||
|
|
||||||
|
previewVideo.pause();
|
||||||
|
if (inputVideo.value !== existingVideoUrl) {
|
||||||
|
previewVideo.setAttribute('poster', inputImage.value || defaultPoster);
|
||||||
|
previewVideo.children[0].setAttribute('src', inputVideo.value);
|
||||||
|
previewVideo.classList.remove('hidden');
|
||||||
|
testing.classList.remove('hidden');
|
||||||
|
} else {
|
||||||
|
previewVideo.removeAttribute('poster');
|
||||||
|
previewVideo.children[0].removeAttribute('src');
|
||||||
|
previewVideo.classList.add('hidden')
|
||||||
|
testing.classList.add('hidden');
|
||||||
|
}
|
||||||
|
previewVideo.load();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
inputVideo.addEventListener('change', checkChange);
|
||||||
|
inputImage.addEventListener('change', checkChange);
|
||||||
|
inputVideo.addEventListener('keyup', checkChange);
|
||||||
|
inputImage.addEventListener('keyup', checkChange);
|
||||||
|
checkChange()
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
1
filadelfia_archive/.npmrc
Normal file
|
@ -0,0 +1 @@
|
||||||
|
package-lock=false
|
52
filadelfia_archive/api/article/routes.mjs
Normal file
|
@ -0,0 +1,52 @@
|
||||||
|
import config from '../../base/config.mjs'
|
||||||
|
import Client from '../../base/media/client.mjs'
|
||||||
|
import OriginalArticleRoutes from '../../base/article/routes.mjs'
|
||||||
|
import { deleteFile, uploadFile } from '../../base/media/upload.mjs'
|
||||||
|
import { parseVideos, parseVideo } from './util.mjs'
|
||||||
|
import { RankLevels } from '../../base/authentication/security.mjs'
|
||||||
|
|
||||||
|
export default class ArticleRoutes extends OriginalArticleRoutes {
|
||||||
|
constructor(opts = {}) {
|
||||||
|
opts.requireAuth = true
|
||||||
|
super(opts)
|
||||||
|
|
||||||
|
Object.assign(this, {
|
||||||
|
uploadFile: uploadFile,
|
||||||
|
deleteFile: deleteFile,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
register(server) {
|
||||||
|
server.flaska.get('/api/articles', this.getVideos.bind(this))
|
||||||
|
server.flaska.get('/api/articles/:path', this.getArticle.bind(this))
|
||||||
|
server.flaska.put('/api/auth/articles/:id', [server.authenticate(), server.jsonHandler()], this.updateCreateArticle.bind(this))
|
||||||
|
server.flaska.get('/api/auth/uploadToken', server.authenticate(RankLevels.Admin), this.getUploadToken.bind(this))
|
||||||
|
server.flaska.delete('/api/auth/articles/:id', server.authenticate(RankLevels.Admin), this.auth_removeSingleArticle.bind(this))
|
||||||
|
}
|
||||||
|
|
||||||
|
/** GET: /api/auth/articles */
|
||||||
|
async getVideos(ctx) {
|
||||||
|
let res = await ctx.db.safeCallProc('filadelfia_archive.article_get_all', [])
|
||||||
|
|
||||||
|
ctx.body = {
|
||||||
|
videos: parseVideos(res.results[0]),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** GET: /api/auth/uploadToken */
|
||||||
|
async getUploadToken(ctx) {
|
||||||
|
const media = config.get('media')
|
||||||
|
|
||||||
|
ctx.body = {
|
||||||
|
token: Client.createJwt({ iss: media.iss }, media.secret),
|
||||||
|
path: config.get('media:directFilePath'),
|
||||||
|
resize: config.get('media:path'),
|
||||||
|
delete: config.get('media:removePath'),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** PUT: /api/auth/articles/:id */
|
||||||
|
async updateCreateArticle(ctx) {
|
||||||
|
return this.private_getUpdateArticle(ctx, ctx.req.body, ctx.req.body.banner, ctx.req.body.media)
|
||||||
|
}
|
||||||
|
}
|
19
filadelfia_archive/api/article/util.mjs
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
import { parseMediaAndBanner } from "../../base/util.mjs"
|
||||||
|
|
||||||
|
export function parseVideos(videos) {
|
||||||
|
for (let i = 0; i < videos.length; i++) {
|
||||||
|
parseVideo(videos[i])
|
||||||
|
}
|
||||||
|
return videos
|
||||||
|
}
|
||||||
|
|
||||||
|
export function parseVideo(video) {
|
||||||
|
if (!video) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
parseMediaAndBanner(video)
|
||||||
|
video.metadata = JSON.parse(video.metadata || '{}')
|
||||||
|
delete video.banner_alt_prefix
|
||||||
|
delete video.media_alt_prefix
|
||||||
|
return video
|
||||||
|
}
|
24
filadelfia_archive/api/serve.mjs
Normal file
|
@ -0,0 +1,24 @@
|
||||||
|
import fs from 'fs/promises'
|
||||||
|
import dot from 'dot'
|
||||||
|
import Parent from '../base/serve.mjs'
|
||||||
|
import path from 'path'
|
||||||
|
|
||||||
|
export default class ServeHandler extends Parent {
|
||||||
|
loadTemplate(indexFile) {
|
||||||
|
this.template = dot.template(indexFile.toString(), { argName: [
|
||||||
|
'siteUrl',
|
||||||
|
] })
|
||||||
|
}
|
||||||
|
|
||||||
|
async serveIndex(ctx) {
|
||||||
|
if (process.env.NODE_ENV !== 'production') {
|
||||||
|
let indexFile = await fs.readFile(path.join(this.root, 'index.html'))
|
||||||
|
this.loadTemplate(indexFile)
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx.body = this.template({
|
||||||
|
siteUrl: this.frontend + ctx.url,
|
||||||
|
})
|
||||||
|
ctx.type = 'text/html; charset=utf-8'
|
||||||
|
}
|
||||||
|
}
|
26
filadelfia_archive/api/server.mjs
Normal file
|
@ -0,0 +1,26 @@
|
||||||
|
import config from '../base/config.mjs'
|
||||||
|
import Parent from '../base/server.mjs'
|
||||||
|
import StaticRoutes from '../base/static_routes.mjs'
|
||||||
|
import ServeHandler from './serve.mjs'
|
||||||
|
import AuthenticationRoutes from '../base/authentication/routes.mjs'
|
||||||
|
import ArticleRoutes from './article/routes.mjs'
|
||||||
|
|
||||||
|
export default class Server extends Parent {
|
||||||
|
init() {
|
||||||
|
super.init()
|
||||||
|
let localUtil = new this.core.sc.Util(import.meta.url)
|
||||||
|
|
||||||
|
this.flaskaOptions.appendHeaders['Content-Security-Policy'] = `default-src 'self'; style-src 'self' 'unsafe-inline'; img-src * data: blob:; font-src 'self' data:; object-src 'none'; frame-ancestors 'none'; connect-src 'self' https://media.nfp.is/; media-src 'self' https://cdn.nfp.is/`,
|
||||||
|
this.flaskaOptions.nonce = []
|
||||||
|
this.routes = {
|
||||||
|
static: new StaticRoutes(),
|
||||||
|
auth: new AuthenticationRoutes(),
|
||||||
|
article: new ArticleRoutes(),
|
||||||
|
}
|
||||||
|
this.routes.serve = new ServeHandler({
|
||||||
|
root: localUtil.getPathFromRoot('../public'),
|
||||||
|
version: this.core.app.running,
|
||||||
|
frontend: config.get('frontend:url'),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
284
filadelfia_archive/app/api.js
Normal file
|
@ -0,0 +1,284 @@
|
||||||
|
const Authentication = require('./authentication')
|
||||||
|
const lang = require('./lang')
|
||||||
|
|
||||||
|
function safeParseReponse(str, status, url) {
|
||||||
|
if (status === 0) {
|
||||||
|
return new Error(lang.api_down)
|
||||||
|
}
|
||||||
|
if (str.slice(0, 9) === '<!doctype') {
|
||||||
|
if (status === 500) {
|
||||||
|
return new Error('Server is temporarily down, try again later.')
|
||||||
|
}
|
||||||
|
return new Error('Expected JSON but got HTML (' + status + ': ' + url.split('?')[0] + ')')
|
||||||
|
}
|
||||||
|
if (!str) {
|
||||||
|
return {}
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
return JSON.parse(str)
|
||||||
|
} catch (err) {
|
||||||
|
return new Error('Unexpected non-JSON response: ' + err.message)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let requests = new Set()
|
||||||
|
let requestIndex = 0
|
||||||
|
|
||||||
|
function clearLoading(request) {
|
||||||
|
requests.delete(request)
|
||||||
|
if (!requests.size) {
|
||||||
|
api.loading = false
|
||||||
|
window.requestAnimationFrame(m.redraw.bind(m))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const api = {
|
||||||
|
loading: false,
|
||||||
|
sendRequest: function(options, isPagination) {
|
||||||
|
let request = requestIndex++
|
||||||
|
requests.add(request)
|
||||||
|
api.loading = true
|
||||||
|
let token = Authentication.getToken()
|
||||||
|
let pagination = isPagination
|
||||||
|
|
||||||
|
if (token) {
|
||||||
|
options.headers = options.headers || {}
|
||||||
|
options.headers['Authorization'] = 'Bearer ' + token
|
||||||
|
}
|
||||||
|
|
||||||
|
options.extract = function(xhr) {
|
||||||
|
let out = safeParseReponse(xhr.responseText, xhr.status, this.url)
|
||||||
|
|
||||||
|
if (out instanceof Error) {
|
||||||
|
throw out
|
||||||
|
}
|
||||||
|
if (xhr.status >= 300) {
|
||||||
|
if (out.message) {
|
||||||
|
throw out
|
||||||
|
}
|
||||||
|
console.error('Got error ' + xhr.status + ' but no error message:', out)
|
||||||
|
throw new Error('Unknown or empty response from server.')
|
||||||
|
}
|
||||||
|
if (pagination) {
|
||||||
|
let headers = {}
|
||||||
|
|
||||||
|
xhr.getAllResponseHeaders().split('\r\n').forEach(function(item) {
|
||||||
|
let splitted = item.split(': ')
|
||||||
|
headers[splitted[0]] = splitted[1]
|
||||||
|
})
|
||||||
|
|
||||||
|
out = {
|
||||||
|
headers: headers || {},
|
||||||
|
data: out,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|
||||||
|
return m.request(options)
|
||||||
|
.then(function(res) {
|
||||||
|
clearLoading(request)
|
||||||
|
return res
|
||||||
|
})
|
||||||
|
.catch(function (error) {
|
||||||
|
clearLoading(request)
|
||||||
|
window.requestAnimationFrame(m.redraw.bind(m))
|
||||||
|
if (error.status === 403) {
|
||||||
|
Authentication.clearToken()
|
||||||
|
m.route.set('/', { redirect: m.route.get() })
|
||||||
|
}
|
||||||
|
if (error.response && error.response.status) {
|
||||||
|
return Promise.reject(error.response)
|
||||||
|
}
|
||||||
|
return Promise.reject(error)
|
||||||
|
})
|
||||||
|
},
|
||||||
|
|
||||||
|
uploadBanner: function(bannerFile, token, reporter) {
|
||||||
|
let request = requestIndex++
|
||||||
|
requests.add(request)
|
||||||
|
api.loading = true
|
||||||
|
|
||||||
|
var report = reporter || function() {}
|
||||||
|
var data = new FormData()
|
||||||
|
data.append('file', bannerFile)
|
||||||
|
/*data.append('preview', JSON.stringify({
|
||||||
|
"out": "base64",
|
||||||
|
"format": "avif",
|
||||||
|
"resize": {
|
||||||
|
"width": 360,
|
||||||
|
"height": 203,
|
||||||
|
"fit": "cover",
|
||||||
|
"withoutEnlargement": true,
|
||||||
|
"kernel": "mitchell"
|
||||||
|
},
|
||||||
|
"avif": {
|
||||||
|
"quality": 40,
|
||||||
|
"effort": 9
|
||||||
|
}
|
||||||
|
}))*/
|
||||||
|
data.append('medium', JSON.stringify({
|
||||||
|
"format": "avif",
|
||||||
|
"resize": {
|
||||||
|
"width": 1280,
|
||||||
|
"height": 720,
|
||||||
|
"fit": "cover",
|
||||||
|
"withoutEnlargement": true,
|
||||||
|
"kernel": "mitchell"
|
||||||
|
},
|
||||||
|
"avif": {
|
||||||
|
"quality": 75,
|
||||||
|
"effort": 3
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
|
||||||
|
report(lang.api_banner_upload)
|
||||||
|
|
||||||
|
return api.sendRequest({
|
||||||
|
method: 'POST',
|
||||||
|
url: token.resize + '?token=' + token.token,
|
||||||
|
body: data,
|
||||||
|
})
|
||||||
|
.then(async (banner) => {
|
||||||
|
let preview = null
|
||||||
|
let quality = 60
|
||||||
|
while (!preview && quality > 10) {
|
||||||
|
report(lang.format(lang.api_banner_generate, quality))
|
||||||
|
let check = await api.sendRequest({
|
||||||
|
method: 'POST',
|
||||||
|
url: token.resize + '/' + banner.filename + '?token=' + token.token,
|
||||||
|
body: {
|
||||||
|
"preview": {
|
||||||
|
"out": "base64",
|
||||||
|
"format": "avif",
|
||||||
|
"resize": {
|
||||||
|
"width": 360,
|
||||||
|
"height": 203,
|
||||||
|
"fit": "cover",
|
||||||
|
"withoutEnlargement": true,
|
||||||
|
"kernel": "mitchell"
|
||||||
|
},
|
||||||
|
"avif": {
|
||||||
|
"quality": quality,
|
||||||
|
"effort": 9
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
if (check.preview.base64.length < 8000) {
|
||||||
|
preview = check.preview.base64
|
||||||
|
} else {
|
||||||
|
quality -= 5
|
||||||
|
}
|
||||||
|
}
|
||||||
|
report(null)
|
||||||
|
|
||||||
|
api.sendRequest({
|
||||||
|
method: 'DELETE',
|
||||||
|
url: token.delete + banner.filename + '?token=' + token.token,
|
||||||
|
}).catch(err => console.error(err))
|
||||||
|
|
||||||
|
return {
|
||||||
|
file: bannerFile,
|
||||||
|
size: bannerFile.size,
|
||||||
|
medium: {
|
||||||
|
filename: banner.medium.filename,
|
||||||
|
path: banner.medium.path,
|
||||||
|
},
|
||||||
|
preview: {
|
||||||
|
base64: preview,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.then(
|
||||||
|
function(res) {
|
||||||
|
clearLoading(request)
|
||||||
|
return res
|
||||||
|
},
|
||||||
|
function(err) {
|
||||||
|
clearLoading(request)
|
||||||
|
return Promise.reject(err)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
},
|
||||||
|
|
||||||
|
prettyFormatBytes: function(bytes) {
|
||||||
|
if (bytes < 1024) {
|
||||||
|
return `${bytes} B`
|
||||||
|
}
|
||||||
|
if (bytes < 1024 * 1024) {
|
||||||
|
return `${(bytes / 1024).toFixed(1)} KB`
|
||||||
|
}
|
||||||
|
if (bytes < 1024 * 1024 * 1024) {
|
||||||
|
return `${(bytes / 1024 / 1024).toFixed(1)} MB`
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
uploadFileProgress: function(options, file, reporter) {
|
||||||
|
let request = requestIndex++
|
||||||
|
requests.add(request)
|
||||||
|
api.loading = true
|
||||||
|
|
||||||
|
return new Promise(function(res, rej) {
|
||||||
|
let report = reporter || function() {}
|
||||||
|
let formdata = new FormData()
|
||||||
|
|
||||||
|
formdata.append('file', file)
|
||||||
|
|
||||||
|
let request = new XMLHttpRequest()
|
||||||
|
|
||||||
|
let finished = false
|
||||||
|
let lastMarker = new Date()
|
||||||
|
let lastMarkerLoaded = 0
|
||||||
|
let lastMarkerSpeed = '...'
|
||||||
|
|
||||||
|
request.abortRequest = function() {
|
||||||
|
finished = true
|
||||||
|
request.abort()
|
||||||
|
}
|
||||||
|
|
||||||
|
request.upload.addEventListener('progress', function (e) {
|
||||||
|
let check = new Date()
|
||||||
|
if (check - lastMarker >= 1000) {
|
||||||
|
let loaded = e.loaded - lastMarkerLoaded
|
||||||
|
lastMarkerSpeed = api.prettyFormatBytes(loaded / ((check - lastMarker) / 1000))
|
||||||
|
lastMarker = check
|
||||||
|
lastMarkerLoaded = e.loaded
|
||||||
|
}
|
||||||
|
report(request, Math.min(e.loaded / file.size * 100, 100), lastMarkerSpeed)
|
||||||
|
})
|
||||||
|
request.addEventListener('abort', function(e) {
|
||||||
|
finished = true
|
||||||
|
window.requestAnimationFrame(m.redraw.bind(m))
|
||||||
|
rej()
|
||||||
|
})
|
||||||
|
|
||||||
|
request.addEventListener('readystatechange', function(e) {
|
||||||
|
if (finished) return
|
||||||
|
if (request.readyState !== 4) return
|
||||||
|
|
||||||
|
finished = true
|
||||||
|
let out = safeParseReponse(request.responseText, request.status, options.url)
|
||||||
|
if (out instanceof Error || request.status >= 300) {
|
||||||
|
return rej(out)
|
||||||
|
}
|
||||||
|
return res(out)
|
||||||
|
})
|
||||||
|
|
||||||
|
request.open(options.method || 'POST', options.url)
|
||||||
|
request.send(formdata)
|
||||||
|
|
||||||
|
report(request, 0)
|
||||||
|
})
|
||||||
|
.then(function(res) {
|
||||||
|
clearLoading(request)
|
||||||
|
return res
|
||||||
|
}, function(err) {
|
||||||
|
clearLoading(request)
|
||||||
|
return Promise.reject(err)
|
||||||
|
})
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = api
|
64
filadelfia_archive/app/authentication.js
Normal file
|
@ -0,0 +1,64 @@
|
||||||
|
const m = require('mithril')
|
||||||
|
const storageName = 'nfp_sites_filadelfia_web_logintoken'
|
||||||
|
|
||||||
|
const Authentication = {
|
||||||
|
currentUser: null,
|
||||||
|
isAdmin: false,
|
||||||
|
loadingListeners: [],
|
||||||
|
authListeners: [],
|
||||||
|
|
||||||
|
updateToken: function(token) {
|
||||||
|
if (!token) return Authentication.clearToken()
|
||||||
|
localStorage.setItem(storageName, token)
|
||||||
|
Authentication.currentUser = JSON.parse(atob(token.split('.')[1]))
|
||||||
|
|
||||||
|
if (Authentication.authListeners.length) {
|
||||||
|
Authentication.authListeners.forEach(function(x) { x(Authentication.currentUser) })
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
clearToken: function() {
|
||||||
|
Authentication.currentUser = null
|
||||||
|
localStorage.removeItem(storageName)
|
||||||
|
Authentication.isAdmin = false
|
||||||
|
},
|
||||||
|
|
||||||
|
addEvent: function(event) {
|
||||||
|
Authentication.authListeners.push(event)
|
||||||
|
},
|
||||||
|
|
||||||
|
setAdmin: function(item) {
|
||||||
|
Authentication.isAdmin = item
|
||||||
|
},
|
||||||
|
|
||||||
|
getToken: function() {
|
||||||
|
return localStorage.getItem(storageName)
|
||||||
|
},
|
||||||
|
|
||||||
|
getTokenDecoded: function() {
|
||||||
|
let token = Authentication.getToken()
|
||||||
|
var base64Url = token.split('.')[1];
|
||||||
|
var base64 = base64Url.replace(/-/g, '+').replace(/_/g, '/');
|
||||||
|
var jsonPayload = decodeURIComponent(window.atob(base64).split('').map(function(c) {
|
||||||
|
return '%' + ('00' + c.charCodeAt(0).toString(16)).slice(-2);
|
||||||
|
}).join(''));
|
||||||
|
|
||||||
|
return JSON.parse(jsonPayload);
|
||||||
|
},
|
||||||
|
|
||||||
|
requiresLogin: function() {
|
||||||
|
if (Authentication.currentUser) return
|
||||||
|
m.route.set('/login')
|
||||||
|
},
|
||||||
|
|
||||||
|
requiresNotLogin: function() {
|
||||||
|
if (!Authentication.currentUser) return
|
||||||
|
m.route.set('/')
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
Authentication.updateToken(localStorage.getItem(storageName))
|
||||||
|
|
||||||
|
window.Authentication = Authentication
|
||||||
|
|
||||||
|
module.exports = Authentication
|
72
filadelfia_archive/app/header.js
Normal file
|
@ -0,0 +1,72 @@
|
||||||
|
const m = require('mithril')
|
||||||
|
const videos = require('./videos')
|
||||||
|
const Authentication = require('./authentication')
|
||||||
|
const lang = require('./lang')
|
||||||
|
|
||||||
|
const Menu = {
|
||||||
|
oninit: function(vnode) {
|
||||||
|
this.currentActive = 'home'
|
||||||
|
this.loading = false
|
||||||
|
if (!videos.Tree.length) {
|
||||||
|
videos.refreshTree()
|
||||||
|
}
|
||||||
|
this.onbeforeupdate()
|
||||||
|
},
|
||||||
|
|
||||||
|
onbeforeupdate: function() {
|
||||||
|
videos.calculateActiveBranches()
|
||||||
|
let currentPath = m.route.get()
|
||||||
|
},
|
||||||
|
|
||||||
|
logOut: function() {
|
||||||
|
Authentication.clearToken()
|
||||||
|
m.route.set('/')
|
||||||
|
},
|
||||||
|
|
||||||
|
view: function() {
|
||||||
|
let tree = videos.Tree
|
||||||
|
let last = videos.Tree[videos.Tree.length - 1]
|
||||||
|
let hasId = m.route.param('id')
|
||||||
|
|
||||||
|
return [
|
||||||
|
m('nav', [
|
||||||
|
m('h4', m(m.route.Link, { href: '/' }, lang.header_title /* Filadelfia archival center */)),
|
||||||
|
m('a.link.changelang', { onclick: lang.langtoggle }, lang.lang_current),
|
||||||
|
Authentication.currentUser?.rank >= 100
|
||||||
|
? m(m.route.Link, { class: 'upload', href: '/upload' }, lang.upload_goto) // Upload
|
||||||
|
: null,
|
||||||
|
Authentication.currentUser
|
||||||
|
? m(m.route.Link, { class: 'link', href: '/logout' }, lang.logout)
|
||||||
|
: m(m.route.Link, { class: 'upload', href: '/login' }, lang.login_submit) // Upload,
|
||||||
|
// m('button.logout', { onclick: this.logOut }, lang.header_logout), // Log out
|
||||||
|
]),
|
||||||
|
videos.error
|
||||||
|
? m('div.error', { onclick: videos.refreshTree }, [
|
||||||
|
videos.error, m('br'), 'Click here to try again'
|
||||||
|
])
|
||||||
|
: null,
|
||||||
|
!videos.error
|
||||||
|
? [
|
||||||
|
m('.nav', m('.inner', tree.map(year => {
|
||||||
|
return m(m.route.Link, {
|
||||||
|
class: [videos.year === year ? 'active' : '',
|
||||||
|
!year.videos.length ? 'empty' : ''].join(' '),
|
||||||
|
href: ['', (videos.year !== year && year !== last) || hasId ? year.title : '' ].filter(x => x).join('/') || '/',
|
||||||
|
}, year.title)
|
||||||
|
}))),
|
||||||
|
videos.year
|
||||||
|
? m('.nav', m('.inner', videos.year.branches.map(month => {
|
||||||
|
return m(m.route.Link, {
|
||||||
|
class: [videos.month === month ? 'active' : '',
|
||||||
|
!month.videos.length ? 'empty' : ''].join(' '),
|
||||||
|
href: ['', videos.year.title, videos.month !== month || hasId ? month.title : null ].filter(x => x).join('/'),
|
||||||
|
}, lang.months[month.title])
|
||||||
|
})))
|
||||||
|
: null,
|
||||||
|
]
|
||||||
|
: null,
|
||||||
|
]
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = Menu
|
68
filadelfia_archive/app/holdbutton.js
Normal file
|
@ -0,0 +1,68 @@
|
||||||
|
const m = require('mithril')
|
||||||
|
|
||||||
|
const HoldButton = {
|
||||||
|
oninit: function(vnode) {
|
||||||
|
this.timer = null
|
||||||
|
this.holding = false
|
||||||
|
this.windowBlur = () => {
|
||||||
|
this.timerStop()
|
||||||
|
m.redraw()
|
||||||
|
}
|
||||||
|
window.addEventListener('blur', this.windowBlur)
|
||||||
|
},
|
||||||
|
|
||||||
|
onremove: function(vnode) {
|
||||||
|
this.timerStop()
|
||||||
|
window.removeEventListener('blur', this.windowBlur)
|
||||||
|
},
|
||||||
|
|
||||||
|
keydown(vnode, e) {
|
||||||
|
if (e.key === " " || e.key === "Enter" || e.key === "Spacebar") {
|
||||||
|
this.timerStart(vnode)
|
||||||
|
m.redraw()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
keyup(e) {
|
||||||
|
if (e.key === " " || e.key === "Enter" || e.key === "Spacebar") {
|
||||||
|
this.timerStop()
|
||||||
|
m.redraw()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
timerStart(vnode) {
|
||||||
|
if (this.timer) return
|
||||||
|
|
||||||
|
this.timer = setTimeout(this.timerConfirmed.bind(this), 2000, vnode)
|
||||||
|
this.holding = true
|
||||||
|
},
|
||||||
|
|
||||||
|
timerStop() {
|
||||||
|
clearTimeout(this.timer)
|
||||||
|
this.timer = null
|
||||||
|
this.holding = false
|
||||||
|
},
|
||||||
|
|
||||||
|
timerConfirmed(vnode) {
|
||||||
|
this.timerStop()
|
||||||
|
vnode.attrs.onclick()
|
||||||
|
m.redraw()
|
||||||
|
},
|
||||||
|
|
||||||
|
view: function(vnode) {
|
||||||
|
return m('button.holdbutton', {
|
||||||
|
style: '--hold-bg: var(--error-bg); --hold-color: var(--error); --hold-fill: var(--error);',
|
||||||
|
hidden: vnode.attrs.hidden,
|
||||||
|
class: (vnode.attrs.class || '')
|
||||||
|
+ (this.holding ? ' holdbutton-active' : ''),
|
||||||
|
onpointerdown: this.timerStart.bind(this, vnode),
|
||||||
|
onpointerup: this.timerStop.bind(this),
|
||||||
|
onpointerleave: this.timerStop.bind(this),
|
||||||
|
onkeydown: this.keydown.bind(this, vnode),
|
||||||
|
onkeyup: this.keyup.bind(this),
|
||||||
|
onclick: e => { return false },
|
||||||
|
}, m('div.inner', vnode.attrs.text))
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = HoldButton
|
62
filadelfia_archive/app/index.js
Normal file
|
@ -0,0 +1,62 @@
|
||||||
|
const m = require('mithril')
|
||||||
|
const Authentication = require('./authentication')
|
||||||
|
const Header = require('./header')
|
||||||
|
const Login = require('./page_login')
|
||||||
|
const Logout = require('./page_logout')
|
||||||
|
const Browse = require('./page_browse')
|
||||||
|
const Upload = require('./page_upload')
|
||||||
|
const Article = require('./page_article')
|
||||||
|
window.m = m
|
||||||
|
|
||||||
|
let css = [
|
||||||
|
'/assets/app.css?v=2',
|
||||||
|
'/assets/tempus-dominus.css',
|
||||||
|
'/assets/fontawesome.css',
|
||||||
|
]
|
||||||
|
|
||||||
|
for (let item of css) {
|
||||||
|
var fileref = document.createElement("link");
|
||||||
|
fileref.setAttribute("rel", "stylesheet");
|
||||||
|
fileref.setAttribute("type", "text/css");
|
||||||
|
fileref.setAttribute("href", item);
|
||||||
|
document.head.appendChild(fileref)
|
||||||
|
}
|
||||||
|
|
||||||
|
m.route.setOrig = m.route.set
|
||||||
|
m.route.set = function(path, data, options){
|
||||||
|
m.route.setOrig(path, data, options)
|
||||||
|
window.scrollTo(0, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
m.route.linkOrig = m.route.link
|
||||||
|
m.route.link = function(vnode){
|
||||||
|
m.route.linkOrig(vnode)
|
||||||
|
window.scrollTo(0, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
m.route.prefix = ''
|
||||||
|
|
||||||
|
const allRoutes = {
|
||||||
|
'/': Browse,
|
||||||
|
'/login': Login,
|
||||||
|
'/logout': Logout,
|
||||||
|
'/upload': Upload,
|
||||||
|
'/:year': Browse,
|
||||||
|
'/:year/:month': Browse,
|
||||||
|
'/:year/:month/:path': Article,
|
||||||
|
}
|
||||||
|
|
||||||
|
// Wait until we finish checking avif support, some views render immediately and will ask for this immediately before the callback gets called.
|
||||||
|
|
||||||
|
/*
|
||||||
|
* imgsupport.js from leechy/imgsupport
|
||||||
|
*/
|
||||||
|
const AVIF = new Image();
|
||||||
|
AVIF.onload = AVIF.onerror = function () {
|
||||||
|
window.supportsavif = (AVIF.height === 2)
|
||||||
|
document.body.className = document.body.className + ' ' + (window.supportsavif ? 'avifsupport' : 'jpegonly')
|
||||||
|
|
||||||
|
m.mount(document.getElementById('header'), Header)
|
||||||
|
m.route(document.getElementById('main'), '/', allRoutes)
|
||||||
|
}
|
||||||
|
AVIF.src = 'data:image/avif;base64,AAAAIGZ0eXBhdmlmAAAAAGF2aWZtaWYxbWlhZk1BMUIAAADybWV0YQAAAAAAAAAoaGRscgAAAAAAAAAAcGljdAAAAAAAAAAAAAAAAGxpYmF2aWYAAAAADnBpdG0AAAAAAAEAAAAeaWxvYwAAAABEAAABAAEAAAABAAABGgAAABcAAAAoaWluZgAAAAAAAQAAABppbmZlAgAAAAABAABhdjAxQ29sb3IAAAAAamlwcnAAAABLaXBjbwAAABRpc3BlAAAAAAAAAAIAAAACAAAAEHBpeGkAAAAAAwgICAAAAAxhdjFDgQAMAAAAABNjb2xybmNseAABAA0ABoAAAAAXaXBtYQAAAAAAAAABAAEEAQKDBAAAAB9tZGF0EgAKCBgAPkgIaDQgMgkf8AAAQAAAr3A=';
|
143
filadelfia_archive/app/input.js
Normal file
|
@ -0,0 +1,143 @@
|
||||||
|
const m = require('mithril')
|
||||||
|
const api = require('./api')
|
||||||
|
const tempus = require('@eonasdan/tempus-dominus')
|
||||||
|
|
||||||
|
const tempusLocalization = {
|
||||||
|
locale: 'is',
|
||||||
|
startOfTheWeek: 0,
|
||||||
|
hourCycle: 'h23',
|
||||||
|
dateFormats: {
|
||||||
|
LTS: 'H:mm:ss',
|
||||||
|
LT: 'H:mm',
|
||||||
|
L: 'dd.MM.yyyy',
|
||||||
|
LL: 'd [de] MMMM [de] yyyy',
|
||||||
|
LLL: 'd [de] MMMM [de] yyyy H:mm',
|
||||||
|
LLLL: 'dddd, d [de] MMMM [de] yyyy H:mm',
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
const Input = {
|
||||||
|
oninit: function(vnode) {
|
||||||
|
this.tempus = null
|
||||||
|
this.subscription = null
|
||||||
|
this.input = null
|
||||||
|
this.preview = null
|
||||||
|
},
|
||||||
|
|
||||||
|
onremove: function(vnode) {
|
||||||
|
if (this.subscription) this.subscription.unsubscribe()
|
||||||
|
if (this.tempus) {
|
||||||
|
this.tempus.dispose()
|
||||||
|
this.tempus = null
|
||||||
|
}
|
||||||
|
if (this.preview) {
|
||||||
|
this.preview.clear()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
onupdate: function(vnode) {
|
||||||
|
if (this.tempus && vnode.attrs.form[vnode.attrs.formKey]) {
|
||||||
|
if (vnode.attrs.form[vnode.attrs.formKey].getTime() !== this.tempus.viewDate?.getTime()) {
|
||||||
|
this.tempus.dates.setValue(new tempus.DateTime(vnode.attrs.form[vnode.attrs.formKey]))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
imageChanged: function(vnode, e) {
|
||||||
|
let file = e.currentTarget.files?.[0] || null
|
||||||
|
this.updateValue(vnode, file)
|
||||||
|
if (this.preview) {
|
||||||
|
this.preview.clear()
|
||||||
|
this.preview = null
|
||||||
|
}
|
||||||
|
if (!file) return
|
||||||
|
|
||||||
|
if (file.type.startsWith('image')) {
|
||||||
|
this.preview = {
|
||||||
|
file: file,
|
||||||
|
preview: URL.createObjectURL(file),
|
||||||
|
clear: function() {
|
||||||
|
URL.revokeObjectURL(this.preview)
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
updateValue: function(vnode, value) {
|
||||||
|
vnode.attrs.form[vnode.attrs.formKey] = value
|
||||||
|
if (typeof(vnode.attrs.oninput) === 'function') {
|
||||||
|
vnode.attrs.oninput(vnode.attrs.form[vnode.attrs.formKey])
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
},
|
||||||
|
|
||||||
|
getInput: function(vnode) {
|
||||||
|
switch (vnode.attrs.utility) {
|
||||||
|
case 'file':
|
||||||
|
return m('div.form-row', [
|
||||||
|
m('input', {
|
||||||
|
type: 'text',
|
||||||
|
disabled: api.loading,
|
||||||
|
value: vnode.attrs.form[vnode.attrs.formKey]?.name || '',
|
||||||
|
}),
|
||||||
|
m('button.fal', { class: vnode.attrs.button || 'file' }),
|
||||||
|
m('input.cover', {
|
||||||
|
type: 'file',
|
||||||
|
accept: vnode.attrs.accept,
|
||||||
|
disabled: api.loading,
|
||||||
|
oninput: (e) => this.updateValue(vnode, e.currentTarget.files?.[0] || null),
|
||||||
|
}),
|
||||||
|
])
|
||||||
|
case 'datetime':
|
||||||
|
return m('div.form-row', [
|
||||||
|
m('input', {
|
||||||
|
type: 'text',
|
||||||
|
disabled: api.loading,
|
||||||
|
oncreate: (e) => {
|
||||||
|
this.tempus = new tempus.TempusDominus(e.dom, {
|
||||||
|
localization: tempusLocalization,
|
||||||
|
})
|
||||||
|
this.tempus.dates.setValue(new tempus.DateTime(vnode.attrs.form[vnode.attrs.formKey]))
|
||||||
|
this.subscription = this.tempus.subscribe(tempus.Namespace.events.change, (e) => {
|
||||||
|
this.updateValue(vnode, e.date)
|
||||||
|
});
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
m('button.fal.fa-calendar', {
|
||||||
|
onclick: () => { this.tempus.toggle(); return false },
|
||||||
|
})
|
||||||
|
])
|
||||||
|
case 'image':
|
||||||
|
let imageLink = this.preview && this.preview.preview || vnode.attrs.form[vnode.attrs.formKey]
|
||||||
|
|
||||||
|
return m('div.form-row.image-banner', {
|
||||||
|
style: {
|
||||||
|
'background-image': typeof imageLink === 'string' ? 'url("' + (imageLink) + '")' : null,
|
||||||
|
},
|
||||||
|
}, [
|
||||||
|
m('input.cover', {
|
||||||
|
type: 'file',
|
||||||
|
accept: vnode.attrs.accept,
|
||||||
|
disabled: api.loading,
|
||||||
|
onchange: this.imageChanged.bind(this, vnode),
|
||||||
|
}),
|
||||||
|
])
|
||||||
|
default:
|
||||||
|
return m('input', {
|
||||||
|
disabled: api.loading,
|
||||||
|
type: vnode.attrs.type || 'text',
|
||||||
|
value: vnode.attrs.form[vnode.attrs.formKey],
|
||||||
|
oninput: (e) => this.updateValue(vnode, e.currentTarget.value),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
view: function(vnode) {
|
||||||
|
return [
|
||||||
|
vnode.attrs.label ? m('label', vnode.attrs.label) : null,
|
||||||
|
this.getInput(vnode),
|
||||||
|
]
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = Input
|
156
filadelfia_archive/app/lang.js
Normal file
|
@ -0,0 +1,156 @@
|
||||||
|
const out = {
|
||||||
|
currentlang: 'en',
|
||||||
|
}
|
||||||
|
|
||||||
|
const i18n = {
|
||||||
|
lang_change_long: ['Skipta yfir á íslensku',
|
||||||
|
'Change to english'],
|
||||||
|
lang_current: ['en',
|
||||||
|
'is'],
|
||||||
|
header_title: ['Fíladelfia archival center',
|
||||||
|
'Fíladelfia myndhvelfing'],
|
||||||
|
header_logout: ['Log out',
|
||||||
|
'Skrá út'],
|
||||||
|
title: ['Title',
|
||||||
|
'Titill'],
|
||||||
|
date: ['Date',
|
||||||
|
'Dagsetning'],
|
||||||
|
language: ['EN',
|
||||||
|
'IS'],
|
||||||
|
upload_goto: ['Upload',
|
||||||
|
'Upphlaða'],
|
||||||
|
login_error: ['Error while logging in: {0}',
|
||||||
|
'Villa við innskráningu: {0}'],
|
||||||
|
login_error_auth: ['Unknown error from server. Try again later.',
|
||||||
|
'Óþekkt villa frá vefþjóni. Reyndu aftur seinna.'],
|
||||||
|
login_missing_email: ['Email is missing',
|
||||||
|
'Email eða nafn vantar'],
|
||||||
|
login_missing_password:['Password is missing',
|
||||||
|
'Lykilorð vantar'],
|
||||||
|
login_email: ['Email or name',
|
||||||
|
'Email eða nafn'],
|
||||||
|
login_password: ['Password',
|
||||||
|
'Lykilorð'],
|
||||||
|
login_submit: ['Log in',
|
||||||
|
'Skrá inn'],
|
||||||
|
logout: ['Log out',
|
||||||
|
'Skrá út'],
|
||||||
|
upload_missing_title: ['Title is missing',
|
||||||
|
'Titill vantar'],
|
||||||
|
upload_missing_date: ['Date is missing',
|
||||||
|
'Dagsetning vantar'],
|
||||||
|
upload_missing_file: ['Video file missing',
|
||||||
|
'Myndaskrá vantar'],
|
||||||
|
upload_missing_banner: ['Poster image missing',
|
||||||
|
'Mynd vantar'],
|
||||||
|
upload_error: ['Error while uploading: {0}',
|
||||||
|
'Villa við að hlaða upp myndefni: {0}'],
|
||||||
|
unsplash: ['Photo by {0} on {1}',
|
||||||
|
'Mynd eftir {0} frá {1}'],
|
||||||
|
api_down: ['No internet or browser blocked the request.',
|
||||||
|
'Ekkert net eða vafri blockaði fyrirspurn.'],
|
||||||
|
edit: ['Edit',
|
||||||
|
'Breyta'],
|
||||||
|
delete: ['Delete',
|
||||||
|
'Eyða'],
|
||||||
|
article_speaker: ['Speaker',
|
||||||
|
'Ræðumaður'],
|
||||||
|
delete_error: ['Error while deleting: {0}',
|
||||||
|
'Villa við að eyða efni: {0}'],
|
||||||
|
article_error: ['Error while saving: {0}',
|
||||||
|
'Villa við að vista: {0}'],
|
||||||
|
api_banner_upload: ['Uploading banner image',
|
||||||
|
'Er að senda mynd'],
|
||||||
|
api_banner_generate:['Generating preview, testing quality {0}%',
|
||||||
|
'Bý til forsíðumynd, prufa {0}% gæði'],
|
||||||
|
months: {
|
||||||
|
'1': ['January',
|
||||||
|
'Janúar'],
|
||||||
|
'2': ['February',
|
||||||
|
'Febrúar'],
|
||||||
|
'3': ['March',
|
||||||
|
'Mars'],
|
||||||
|
'4': ['April',
|
||||||
|
'Apríl'],
|
||||||
|
'5': ['May',
|
||||||
|
'Maí'],
|
||||||
|
'6': ['June',
|
||||||
|
'Júní'],
|
||||||
|
'7': ['July',
|
||||||
|
'Júlí'],
|
||||||
|
'8': ['August',
|
||||||
|
'Ágúst'],
|
||||||
|
'9': ['September',
|
||||||
|
'September'],
|
||||||
|
'10': ['Oktober',
|
||||||
|
'Október'],
|
||||||
|
'11': ['November',
|
||||||
|
'Nóvember'],
|
||||||
|
'12': ['December',
|
||||||
|
'Desember'],
|
||||||
|
},
|
||||||
|
}
|
||||||
|
const langs = {
|
||||||
|
'en': 0,
|
||||||
|
'is': 1,
|
||||||
|
}
|
||||||
|
|
||||||
|
const regexNumber = new RegExp('^\\d+$')
|
||||||
|
|
||||||
|
out.printdate = function(date) {
|
||||||
|
let day = date.getDate().toString()
|
||||||
|
if (out.currentlang === 'en') {
|
||||||
|
let last = day[day.length - 1]
|
||||||
|
if (last === '1') {
|
||||||
|
day += 'st'
|
||||||
|
} else if (last === '2') {
|
||||||
|
day += 'nd'
|
||||||
|
} else if (last === '3') {
|
||||||
|
day += 'rd'
|
||||||
|
} else {
|
||||||
|
day += 'th'
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
day += '.'
|
||||||
|
}
|
||||||
|
return `${day} ${out.months[date.getMonth() + 1]} ${date.getFullYear()}, ${date.getHours().toString().padStart(2, '0')}:${date.getMinutes().toString().padStart(2, '0')}`
|
||||||
|
}
|
||||||
|
|
||||||
|
out.langset = function(lang) {
|
||||||
|
out.currentlang = lang
|
||||||
|
let index = langs[lang]
|
||||||
|
|
||||||
|
for (let key of Object.keys(i18n)) {
|
||||||
|
if (!Array.isArray(i18n[key])) {
|
||||||
|
out[key] = {}
|
||||||
|
for (let subKey of Object.keys(i18n[key])) {
|
||||||
|
out[key][subKey] = i18n[key][subKey][index]
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
out[key] = i18n[key][index]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
out.langtoggle = function() {
|
||||||
|
out.langset(out.currentlang === 'en' ? 'is' : 'en')
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
out.format = function(str, ...args) {
|
||||||
|
return out.mformat(str, ...args).join('')
|
||||||
|
}
|
||||||
|
|
||||||
|
out.mformat = function(str, ...args) {
|
||||||
|
let split = (str || '').split(/\{|\}/)
|
||||||
|
return split.map(function(item) {
|
||||||
|
if (regexNumber.test(item)) {
|
||||||
|
return args[Number(item)] || item
|
||||||
|
}
|
||||||
|
return item
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
out.langset('is')
|
||||||
|
|
||||||
|
module.exports = out
|
265
filadelfia_archive/app/page_article.js
Normal file
|
@ -0,0 +1,265 @@
|
||||||
|
const m = require('mithril')
|
||||||
|
const api = require('./api')
|
||||||
|
const Authentication = require('./authentication')
|
||||||
|
const Input = require('./input')
|
||||||
|
const lang = require('./lang')
|
||||||
|
const videos = require('./videos')
|
||||||
|
const HoldButton = require('./holdbutton')
|
||||||
|
|
||||||
|
const Article = {
|
||||||
|
oninit: function(vnode) {
|
||||||
|
this.error = ''
|
||||||
|
this.path = ''
|
||||||
|
this.data = null
|
||||||
|
this.editing = false
|
||||||
|
this.cacheImage = null
|
||||||
|
this.form = {
|
||||||
|
title: 'Sunnudagssamkoma',
|
||||||
|
date: new Date(),
|
||||||
|
banner: null,
|
||||||
|
metadata: {
|
||||||
|
speaker: '',
|
||||||
|
},
|
||||||
|
}
|
||||||
|
this.onbeforeupdate(vnode)
|
||||||
|
},
|
||||||
|
|
||||||
|
onbeforeupdate: function(vnode) {
|
||||||
|
let path = m.route.param('year').padStart(4, '0')
|
||||||
|
+ '-' + m.route.param('month').padStart(2, '0')
|
||||||
|
+ '-' + m.route.param('path')
|
||||||
|
if (this.path === path) return
|
||||||
|
|
||||||
|
this.fetchArticle(vnode, path)
|
||||||
|
},
|
||||||
|
|
||||||
|
fetchArticle: function(vnode, path) {
|
||||||
|
this.error = ''
|
||||||
|
this.data = null
|
||||||
|
this.path = path
|
||||||
|
this.cacheImage = null
|
||||||
|
this.editing = false
|
||||||
|
|
||||||
|
api.sendRequest({
|
||||||
|
method: 'GET',
|
||||||
|
url: '/api/articles/' + this.path,
|
||||||
|
})
|
||||||
|
.then((result) => {
|
||||||
|
this.data = result.article
|
||||||
|
this.gotArticle(vnode)
|
||||||
|
}, (err) => {
|
||||||
|
this.error = err.message
|
||||||
|
})
|
||||||
|
},
|
||||||
|
|
||||||
|
gotArticle: function(vnode) {
|
||||||
|
if (!this.data) {
|
||||||
|
return this.error = 'Article not found'
|
||||||
|
}
|
||||||
|
this.form.title = this.data.name
|
||||||
|
this.form.date = new Date(this.data.publish_at)
|
||||||
|
this.form.banner = this.data.banner_path
|
||||||
|
this.form.metadata.speaker = this.data.content.speaker
|
||||||
|
},
|
||||||
|
|
||||||
|
updatevideo: function(vnode, e) {
|
||||||
|
this.error = ''
|
||||||
|
|
||||||
|
if (!this.form.title) this.error = lang.upload_missing_title // Title is missing
|
||||||
|
if (!this.form.date) this.error = lang.upload_missing_date // Date is missing
|
||||||
|
if (this.error) return false
|
||||||
|
|
||||||
|
let promise = Promise.resolve()
|
||||||
|
|
||||||
|
if (Authentication.currentUser && (typeof(this.form.banner) !== 'string' && this.cacheImage?.file !== this.form.banner)) {
|
||||||
|
promise = api.sendRequest({
|
||||||
|
method: 'GET',
|
||||||
|
url: '/api/auth/uploadToken',
|
||||||
|
})
|
||||||
|
.then(res => {
|
||||||
|
return api.uploadBanner(this.form.banner, res)
|
||||||
|
.then(imageData => {
|
||||||
|
this.cacheImage = imageData
|
||||||
|
|
||||||
|
if (this.data.banner_path) {
|
||||||
|
api.sendRequest({
|
||||||
|
method: 'DELETE',
|
||||||
|
url: res.delete + this.data.banner_path.slice(this.data.banner_path.lastIndexOf('/') + 1) + '?token=' + res.token,
|
||||||
|
}).catch(err => console.error(err))
|
||||||
|
}
|
||||||
|
|
||||||
|
return res
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
promise.then(() => {
|
||||||
|
return api.sendRequest({
|
||||||
|
method: 'PUT',
|
||||||
|
url: '/api/auth/articles/' + this.data.id,
|
||||||
|
body: {
|
||||||
|
name: this.form.title,
|
||||||
|
page_id: 'null',
|
||||||
|
path: this.form.date.toISOString().replace('T', '_').replace(/:/g, '').split('.')[0],
|
||||||
|
content: JSON.stringify(this.form.metadata),
|
||||||
|
publish_at: this.form.date,
|
||||||
|
admin_id: Authentication.getTokenDecoded().user_id,
|
||||||
|
is_featured: false,
|
||||||
|
media: null,
|
||||||
|
banner: this.cacheImage ? {
|
||||||
|
filename: this.cacheImage.medium.filename,
|
||||||
|
path: this.cacheImage.medium.path,
|
||||||
|
type: 'image/avif',
|
||||||
|
size: this.cacheImage.size,
|
||||||
|
preview: {
|
||||||
|
base64: this.cacheImage.preview.base64,
|
||||||
|
},
|
||||||
|
} : null,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.then(res => {
|
||||||
|
this.fetchArticle(vnode, this.path)
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
console.error(error)
|
||||||
|
this.error = lang.format(lang.article_error, error.message) // Error while saving:
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
return false
|
||||||
|
},
|
||||||
|
|
||||||
|
deletevideo: function(vnode) {
|
||||||
|
api.sendRequest({
|
||||||
|
method: 'GET',
|
||||||
|
url: '/api/auth/uploadToken',
|
||||||
|
body: this.form,
|
||||||
|
})
|
||||||
|
.then(res => {
|
||||||
|
return Promise.all([
|
||||||
|
this.data.banner_path ? api.sendRequest({
|
||||||
|
method: 'DELETE',
|
||||||
|
url: res.delete + this.data.banner_path.slice(this.data.banner_path.lastIndexOf('/') + 1) + '?token=' + res.token,
|
||||||
|
}).catch(err => console.error(err)) : Promise.resolve(),
|
||||||
|
this.data.media_path ? api.sendRequest({
|
||||||
|
method: 'DELETE',
|
||||||
|
url: res.delete + this.data.media_path.slice(this.data.media_path.lastIndexOf('/') + 1) + '?token=' + res.token,
|
||||||
|
}).catch(err => console.error(err)) : Promise.resolve(),
|
||||||
|
])
|
||||||
|
})
|
||||||
|
.then(() => {
|
||||||
|
return api.sendRequest({
|
||||||
|
method: 'DELETE',
|
||||||
|
url: '/api/auth/articles/' + this.data.id,
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.then(res => {
|
||||||
|
videos.removeArticle(this.data.id)
|
||||||
|
m.route.set('/')
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
if (!error) return
|
||||||
|
|
||||||
|
this.error = lang.format(lang.delete_error, error.message) // Error while uploading:
|
||||||
|
})
|
||||||
|
},
|
||||||
|
|
||||||
|
view: function(vnode) {
|
||||||
|
return [
|
||||||
|
api.loading && !this.data ? m('div.loading-spinner') : null,
|
||||||
|
this.data ? [
|
||||||
|
this.data.media_path
|
||||||
|
? [
|
||||||
|
m('.player', [
|
||||||
|
m('video', {
|
||||||
|
crossorigin: '',
|
||||||
|
controls: true,
|
||||||
|
preload: 'none',
|
||||||
|
poster: this.data.banner_path || '/assets/placeholder.avif',
|
||||||
|
}, [
|
||||||
|
m('source', {
|
||||||
|
src: this.data.media_path
|
||||||
|
})
|
||||||
|
]),
|
||||||
|
]),
|
||||||
|
]
|
||||||
|
: null,
|
||||||
|
this.editing
|
||||||
|
? m('form.article', {
|
||||||
|
onsubmit: this.updatevideo.bind(this, vnode),
|
||||||
|
}, [
|
||||||
|
m('div.form-row', [
|
||||||
|
m('div.form-columns', [
|
||||||
|
m(Input, {
|
||||||
|
label: '',
|
||||||
|
type: 'file',
|
||||||
|
accept: 'image/*',
|
||||||
|
utility: 'image',
|
||||||
|
form: this.form,
|
||||||
|
formKey: 'banner',
|
||||||
|
}),
|
||||||
|
]),
|
||||||
|
m('div.form-columns.article-name', [
|
||||||
|
m(Input, {
|
||||||
|
label: 'Title',
|
||||||
|
form: this.form,
|
||||||
|
formKey: 'title',
|
||||||
|
}),
|
||||||
|
m(Input, {
|
||||||
|
label: 'Date (dd.mm.yyyy)',
|
||||||
|
type: 'text',
|
||||||
|
utility: 'datetime',
|
||||||
|
form: this.form,
|
||||||
|
formKey: 'date',
|
||||||
|
}),
|
||||||
|
]),
|
||||||
|
]),
|
||||||
|
m('p.separator', 'Optional'),
|
||||||
|
m(Input, {
|
||||||
|
label: 'Speaker',
|
||||||
|
form: this.form.metadata,
|
||||||
|
formKey: 'speaker',
|
||||||
|
}),
|
||||||
|
this.error ? m('div.full-error', this.error) : null,
|
||||||
|
m('div.row', [
|
||||||
|
m('input.spinner', {
|
||||||
|
hidden: api.loading,
|
||||||
|
type: 'submit',
|
||||||
|
value: lang.edit,
|
||||||
|
}),
|
||||||
|
m('div.filler', {
|
||||||
|
hidden: api.loading,
|
||||||
|
}),
|
||||||
|
api.loading ? m('div.loading-spinner') : null,
|
||||||
|
m(HoldButton, {
|
||||||
|
class: 'button spinner',
|
||||||
|
onclick: () => this.deletevideo(vnode),
|
||||||
|
hidden: api.loading,
|
||||||
|
text: lang.delete,
|
||||||
|
}),
|
||||||
|
]),
|
||||||
|
])
|
||||||
|
: m('div.article', [
|
||||||
|
m('h1', this.data.name),
|
||||||
|
m('p', [
|
||||||
|
lang.printdate(this.form.date),
|
||||||
|
]),
|
||||||
|
m('div.table', [
|
||||||
|
m('div.table-row', [
|
||||||
|
m('div.table-item', lang.article_speaker),
|
||||||
|
m('div.table-item', this.data.content.speaker || '...'),
|
||||||
|
]),
|
||||||
|
]),
|
||||||
|
Authentication.currentUser?.rank >= 10
|
||||||
|
? m('button.button', { onclick: () => this.editing = true },lang.edit)
|
||||||
|
: null,
|
||||||
|
]),
|
||||||
|
] : [
|
||||||
|
this.error ? m('div.full-error', this.error) : null,
|
||||||
|
],
|
||||||
|
]
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = Article
|
62
filadelfia_archive/app/page_browse.js
Normal file
|
@ -0,0 +1,62 @@
|
||||||
|
const m = require('mithril')
|
||||||
|
const api = require('./api')
|
||||||
|
const Authentication = require('./authentication')
|
||||||
|
const videos = require('./videos')
|
||||||
|
const lang = require('./lang')
|
||||||
|
|
||||||
|
const Browse = {
|
||||||
|
oninit: function(vnode) {
|
||||||
|
},
|
||||||
|
|
||||||
|
mArticles: function(vnode, articles) {
|
||||||
|
return articles.map(article => {
|
||||||
|
return m(m.route.Link, {
|
||||||
|
href: ['', article.publish_at.getFullYear(), article.publish_at.getMonth() + 1, article.path_short].join('/'),
|
||||||
|
style: article.avif_preview ? `background-image: url('${article.avif_preview}')` : null,
|
||||||
|
}, [
|
||||||
|
m('span', lang.printdate(article.publish_at)),
|
||||||
|
m('span', article.name),
|
||||||
|
])
|
||||||
|
})
|
||||||
|
},
|
||||||
|
|
||||||
|
mMonth: function(vnode, year) {
|
||||||
|
return year.branches.map(month => {
|
||||||
|
return [
|
||||||
|
m('.gallery-month', lang.months[month.title]),
|
||||||
|
m('.group', this.mArticles(vnode, month.videos))
|
||||||
|
]
|
||||||
|
})
|
||||||
|
},
|
||||||
|
|
||||||
|
view: function(vnode) {
|
||||||
|
let articles = videos.month?.videos || videos.year?.videos || videos.Articles
|
||||||
|
|
||||||
|
return [
|
||||||
|
api.loading ? m('div.loading-spinner') : null,
|
||||||
|
videos.error
|
||||||
|
? m('div.full-error', { onclick: videos.refreshTree }, [
|
||||||
|
videos.error, m('br'), 'Click here to try again'
|
||||||
|
])
|
||||||
|
: null,
|
||||||
|
m('.gallery', [
|
||||||
|
videos.month
|
||||||
|
? m('.group', this.mArticles(vnode, articles))
|
||||||
|
: null,
|
||||||
|
videos.year && !videos.month
|
||||||
|
? this.mMonth(vnode, videos.year)
|
||||||
|
: null,
|
||||||
|
!videos.year
|
||||||
|
? videos.Tree.slice(-1).map(year => {
|
||||||
|
return [
|
||||||
|
m('.gallery-year', year.title),
|
||||||
|
this.mMonth(vnode, year),
|
||||||
|
]
|
||||||
|
})
|
||||||
|
: null
|
||||||
|
]),
|
||||||
|
]
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = Browse
|
86
filadelfia_archive/app/page_login.js
Normal file
|
@ -0,0 +1,86 @@
|
||||||
|
const m = require('mithril')
|
||||||
|
const Authentication = require('./authentication')
|
||||||
|
const api = require('./api')
|
||||||
|
const Input = require('./input')
|
||||||
|
const lang = require('./lang')
|
||||||
|
const videos = require('./videos')
|
||||||
|
|
||||||
|
const Login = {
|
||||||
|
oninit: function(vnode) {
|
||||||
|
this.redirect = vnode.attrs.redirect || ''
|
||||||
|
Authentication.requiresNotLogin()
|
||||||
|
|
||||||
|
this.error = ''
|
||||||
|
this.form = {
|
||||||
|
email: '',
|
||||||
|
password: '',
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
loginuser: function(vnode, e) {
|
||||||
|
e.preventDefault()
|
||||||
|
this.error = ''
|
||||||
|
|
||||||
|
if (!this.form.email) this.error = lang.login_missing_email // Email is missing
|
||||||
|
if (!this.form.password) this.error = lang.login_missing_password // Password is missing
|
||||||
|
|
||||||
|
if (this.error) return false
|
||||||
|
|
||||||
|
api.sendRequest({
|
||||||
|
method: 'POST',
|
||||||
|
url: '/api/authentication/login',
|
||||||
|
body: this.form,
|
||||||
|
})
|
||||||
|
.then((result) => {
|
||||||
|
if (!result.token) return Promise.reject(new Error(lang.login_error_auth)) // Unknown error from server. Try again later
|
||||||
|
Authentication.updateToken(result.token)
|
||||||
|
m.route.set(this.redirect || '/')
|
||||||
|
videos.refreshTree()
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
this.error = lang.format(lang.login_error, error.message) // Error while logging in:
|
||||||
|
this.form.password = ''
|
||||||
|
})
|
||||||
|
|
||||||
|
return false
|
||||||
|
},
|
||||||
|
|
||||||
|
view: function(vnode) {
|
||||||
|
return [
|
||||||
|
m('div.page.page-login', [
|
||||||
|
m('div.modal', [
|
||||||
|
m('form', {
|
||||||
|
onsubmit: this.loginuser.bind(this, vnode),
|
||||||
|
}, [
|
||||||
|
m('h3', lang.header_title /* Filadelfia archival center */),
|
||||||
|
this.error ? m('p.error', this.error) : null,
|
||||||
|
m(Input, {
|
||||||
|
label: lang.login_email, // Email or name
|
||||||
|
form: this.form,
|
||||||
|
formKey: 'email',
|
||||||
|
}),
|
||||||
|
m(Input, {
|
||||||
|
label: lang.login_password, // Password
|
||||||
|
type: 'password',
|
||||||
|
form: this.form,
|
||||||
|
formKey: 'password',
|
||||||
|
}),
|
||||||
|
m('input.spinner', {
|
||||||
|
hidden: api.loading,
|
||||||
|
type: 'submit',
|
||||||
|
value: lang.login_submit, // Log in
|
||||||
|
}),
|
||||||
|
api.loading ? m('div.loading-spinner') : null,
|
||||||
|
]),
|
||||||
|
]),
|
||||||
|
m('footer', lang.mformat(
|
||||||
|
lang.unsplash, // Photo by X on Y
|
||||||
|
m('a', { href: 'https://unsplash.com/@franhotchin?utm_content=creditCopyText&utm_medium=referral&utm_source=unsplash', target: '_blank' }, 'Francesca Hotchin'),
|
||||||
|
m('a', { href: 'https://unsplash.com/photos/landscape-photo-of-mountain-covered-with-snow-FN-cedy6NHA?utm_content=creditCopyText&utm_medium=referral&utm_source=unsplash', target: '_blank' }, 'Unsplash'),
|
||||||
|
)),
|
||||||
|
]),
|
||||||
|
]
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = Login
|
15
filadelfia_archive/app/page_logout.js
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
const m = require('mithril')
|
||||||
|
const Authentication = require('./authentication')
|
||||||
|
|
||||||
|
const Logout = {
|
||||||
|
oninit: function(vnode) {
|
||||||
|
Authentication.clearToken()
|
||||||
|
m.route.set(vnode.attrs.redirect || '/')
|
||||||
|
},
|
||||||
|
|
||||||
|
view: function(vnode) {
|
||||||
|
return []
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = Logout
|
227
filadelfia_archive/app/page_upload.js
Normal file
|
@ -0,0 +1,227 @@
|
||||||
|
const m = require('mithril')
|
||||||
|
const Authentication = require('./authentication')
|
||||||
|
const api = require('./api')
|
||||||
|
const Input = require('./input')
|
||||||
|
const lang = require('./lang')
|
||||||
|
const videos = require('./videos')
|
||||||
|
|
||||||
|
const Upload = {
|
||||||
|
oninit: function(vnode) {
|
||||||
|
Authentication.requiresLogin()
|
||||||
|
this.error = ''
|
||||||
|
let d = new Date()
|
||||||
|
d.setDate(d.getDate() - d.getDay())
|
||||||
|
d.setHours(11)
|
||||||
|
d.setMinutes(0)
|
||||||
|
d.setSeconds(0)
|
||||||
|
d.setMilliseconds(0)
|
||||||
|
|
||||||
|
this.cacheVideo = null
|
||||||
|
this.cacheImage = null
|
||||||
|
this.uploading = null
|
||||||
|
this.bannerStatus = null
|
||||||
|
this.form = {
|
||||||
|
title: 'Sunnudagssamkoma',
|
||||||
|
date: d,
|
||||||
|
file: null,
|
||||||
|
banner: null,
|
||||||
|
metadata: {
|
||||||
|
speaker: '',
|
||||||
|
},
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
uploadvideo: function(vnode, e) {
|
||||||
|
this.error = ''
|
||||||
|
|
||||||
|
if (!this.form.title) this.error = lang.upload_missing_title // Title is missing
|
||||||
|
if (!this.form.date) this.error = lang.upload_missing_date // Date is missing
|
||||||
|
if (!this.form.file) this.error = lang.upload_missing_file // Video file missing
|
||||||
|
if (!this.form.banner) this.error = lang.upload_missing_banner // Poster image missing
|
||||||
|
|
||||||
|
if (this.error) return false
|
||||||
|
|
||||||
|
api.sendRequest({
|
||||||
|
method: 'GET',
|
||||||
|
url: '/api/auth/uploadToken',
|
||||||
|
})
|
||||||
|
.then(res => {
|
||||||
|
if (this.cacheImage?.file === this.form.banner) {
|
||||||
|
return this.cacheImage
|
||||||
|
}
|
||||||
|
|
||||||
|
return api.uploadBanner(this.form.banner, res, (status) => {
|
||||||
|
this.bannerStatus = status
|
||||||
|
m.redraw()
|
||||||
|
})
|
||||||
|
.then(imageData => {
|
||||||
|
this.cacheImage = imageData
|
||||||
|
return res
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.then(res => {
|
||||||
|
if (this.cacheVideo?.file === this.form.file) {
|
||||||
|
return this.cacheVideo
|
||||||
|
}
|
||||||
|
|
||||||
|
return api.uploadFileProgress({
|
||||||
|
url: res.path + '?token=' + res.token,
|
||||||
|
}, this.form.file, (xhr, progress, perSecond) => {
|
||||||
|
this.uploading = {
|
||||||
|
progress,
|
||||||
|
xhr,
|
||||||
|
perSecond
|
||||||
|
}
|
||||||
|
m.redraw()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.then(res => {
|
||||||
|
this.cacheVideo = {
|
||||||
|
file: this.form.file,
|
||||||
|
filename: res.filename,
|
||||||
|
path: res.path,
|
||||||
|
}
|
||||||
|
this.uploading = null
|
||||||
|
|
||||||
|
return api.sendRequest({
|
||||||
|
method: 'PUT',
|
||||||
|
url: '/api/auth/articles/0',
|
||||||
|
body: {
|
||||||
|
name: this.form.title,
|
||||||
|
page_id: 'null',
|
||||||
|
path: this.form.date.toISOString().replace('T', '_').replace(/:/g, '').split('.')[0],
|
||||||
|
content: JSON.stringify(this.form.metadata),
|
||||||
|
publish_at: this.form.date,
|
||||||
|
admin_id: Authentication.getTokenDecoded().user_id,
|
||||||
|
is_featured: false,
|
||||||
|
media: {
|
||||||
|
filename: res.filename,
|
||||||
|
path: res.path,
|
||||||
|
type: this.form.file.type,
|
||||||
|
size: this.form.file.size,
|
||||||
|
},
|
||||||
|
banner: {
|
||||||
|
filename: this.cacheImage.medium.filename,
|
||||||
|
path: this.cacheImage.medium.path,
|
||||||
|
type: 'image/avif',
|
||||||
|
size: this.cacheImage.size,
|
||||||
|
preview: {
|
||||||
|
base64: this.cacheImage.preview.base64,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.then(res => {
|
||||||
|
videos.refreshTree()
|
||||||
|
m.route.set('/')
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
this.bannerStatus = null
|
||||||
|
this.uploading = null
|
||||||
|
if (!error) return
|
||||||
|
|
||||||
|
this.error = lang.format(lang.upload_error, error.message) // Error while uploading:
|
||||||
|
})
|
||||||
|
|
||||||
|
return false
|
||||||
|
},
|
||||||
|
|
||||||
|
cancelUpload(e) {
|
||||||
|
e.stopPropagation()
|
||||||
|
this.uploading.xhr.abortRequest()
|
||||||
|
this.uploading = null
|
||||||
|
return false
|
||||||
|
},
|
||||||
|
|
||||||
|
filechanged(file) {
|
||||||
|
if (!file || !file.name) return
|
||||||
|
|
||||||
|
let matches = /^(\d{4})-(\d\d)-(\d\d)_(\d\d)-(\d\d)/.exec(file.name)
|
||||||
|
if (!matches) return
|
||||||
|
|
||||||
|
var date = new Date(matches.slice(1, 4).join('-') + 'T' + matches.slice(4,6).join(':') + ':00')
|
||||||
|
if (isNaN(date.getTime())) return
|
||||||
|
|
||||||
|
if (date.getMinutes() >= 30 || date.getHours() === 10) {
|
||||||
|
date.setHours(date.getHours() + 1)
|
||||||
|
}
|
||||||
|
date.setMinutes(0)
|
||||||
|
this.form.date = date
|
||||||
|
},
|
||||||
|
|
||||||
|
view: function(vnode) {
|
||||||
|
return [
|
||||||
|
m('div.page.page-upload', [
|
||||||
|
m('div.modal', [
|
||||||
|
m('form', {
|
||||||
|
onsubmit: this.uploadvideo.bind(this, vnode),
|
||||||
|
}, [
|
||||||
|
m('h3', 'Upload new video'),
|
||||||
|
this.error ? m('p.error', this.error) : null,
|
||||||
|
m(Input, {
|
||||||
|
label: 'Title',
|
||||||
|
form: this.form,
|
||||||
|
formKey: 'title',
|
||||||
|
}),
|
||||||
|
m(Input, {
|
||||||
|
label: 'Date (dd.mm.yyyy)',
|
||||||
|
type: 'text',
|
||||||
|
utility: 'datetime',
|
||||||
|
form: this.form,
|
||||||
|
formKey: 'date',
|
||||||
|
}),
|
||||||
|
m(Input, {
|
||||||
|
label: 'Video',
|
||||||
|
type: 'file',
|
||||||
|
accept: '.webm',
|
||||||
|
utility: 'file',
|
||||||
|
button: 'fa-video',
|
||||||
|
form: this.form,
|
||||||
|
formKey: 'file',
|
||||||
|
oninput: (file) => this.filechanged(file),
|
||||||
|
}),
|
||||||
|
m(Input, {
|
||||||
|
label: 'Mynd',
|
||||||
|
type: 'file',
|
||||||
|
accept: 'image/*',
|
||||||
|
utility: 'image',
|
||||||
|
form: this.form,
|
||||||
|
formKey: 'banner',
|
||||||
|
}),
|
||||||
|
m('p.separator', 'Optional'),
|
||||||
|
m(Input, {
|
||||||
|
label: 'Speaker',
|
||||||
|
form: this.form.metadata,
|
||||||
|
formKey: 'speaker',
|
||||||
|
}),
|
||||||
|
m('input.spinner', {
|
||||||
|
hidden: api.loading,
|
||||||
|
type: 'submit',
|
||||||
|
value: 'Begin upload',
|
||||||
|
}),
|
||||||
|
api.loading ? m('div.loading-spinner') : null,
|
||||||
|
this.bannerStatus ? [
|
||||||
|
m('p', this.bannerStatus),
|
||||||
|
m('.loading-bar', { style: `--progress: 0%` }),
|
||||||
|
] : null,
|
||||||
|
this.uploading ? [
|
||||||
|
m('p', `${Math.floor(this.uploading.progress)}% (${this.uploading.perSecond}/s)`),
|
||||||
|
m('.loading-bar', { style: `--progress: ${this.uploading.progress}%` }),
|
||||||
|
m('button.button.button-alert', {
|
||||||
|
onclick: this.cancelUpload.bind(this),
|
||||||
|
}, 'Cancel upload'),
|
||||||
|
] : null,
|
||||||
|
]),
|
||||||
|
]),
|
||||||
|
m('footer', lang.mformat(
|
||||||
|
lang.unsplash, // Photo by X on Y
|
||||||
|
m('a', { href: 'https://unsplash.com/@franhotchin?utm_content=creditCopyText&utm_medium=referral&utm_source=unsplash', target: '_blank' }, 'Francesca Hotchin'),
|
||||||
|
m('a', { href: 'https://unsplash.com/photos/landscape-photo-of-mountain-covered-with-snow-FN-cedy6NHA?utm_content=creditCopyText&utm_medium=referral&utm_source=unsplash', target: '_blank' }, 'Unsplash'),
|
||||||
|
)),
|
||||||
|
]),
|
||||||
|
]
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = Upload
|
120
filadelfia_archive/app/videos.js
Normal file
|
@ -0,0 +1,120 @@
|
||||||
|
const m = require('mithril')
|
||||||
|
const api = require('./api')
|
||||||
|
|
||||||
|
const Tree = []
|
||||||
|
const Articles = []
|
||||||
|
|
||||||
|
exports.Tree = Tree
|
||||||
|
exports.Articles = Articles
|
||||||
|
|
||||||
|
exports.loading = false
|
||||||
|
exports.error = ''
|
||||||
|
exports.year = null
|
||||||
|
exports.month = null
|
||||||
|
|
||||||
|
const matcher = /\/(\d+)(\/\d+)?/
|
||||||
|
|
||||||
|
function calculateActiveBranches() {
|
||||||
|
let path = matcher.exec(m.route.get())
|
||||||
|
if (path && path[1] !== exports.year?.title) {
|
||||||
|
for (let year of Tree) {
|
||||||
|
if (year.title === path[1]) {
|
||||||
|
exports.year = year
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if (!path && m.route.get() === '/') {
|
||||||
|
exports.year = Tree[Tree.length - 1]
|
||||||
|
} else if (!path) {
|
||||||
|
exports.year = null
|
||||||
|
}
|
||||||
|
if (path && exports.year && path[2]) {
|
||||||
|
exports.month = exports.year.branches[Number(path[2].slice(1)) - 1] || null
|
||||||
|
} else if (!path?.[2]) {
|
||||||
|
exports.month = null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function rebuildTree() {
|
||||||
|
Tree.splice(0, Tree.length)
|
||||||
|
|
||||||
|
if (!Articles.length) return
|
||||||
|
|
||||||
|
let startYear = Articles[0].publish_at
|
||||||
|
let target = new Date()
|
||||||
|
let articleIndex = 0
|
||||||
|
|
||||||
|
for (let year = startYear.getFullYear(); year <= target.getFullYear(); year++) {
|
||||||
|
let branchYear = {
|
||||||
|
title: year.toString(),
|
||||||
|
type: 'year',
|
||||||
|
branches: [],
|
||||||
|
videos: []
|
||||||
|
}
|
||||||
|
Tree.push(branchYear)
|
||||||
|
let lastMonth = year === target.getFullYear() ? target.getMonth() + 1 : 12
|
||||||
|
|
||||||
|
for (let month = 1; month <= lastMonth; month++) {
|
||||||
|
let branchMonth = {
|
||||||
|
title: month.toString(),
|
||||||
|
type: 'month',
|
||||||
|
branches: [],
|
||||||
|
videos: []
|
||||||
|
}
|
||||||
|
branchYear.branches.push(branchMonth)
|
||||||
|
|
||||||
|
let start = new Date(year, month - 1)
|
||||||
|
let end = new Date(year, month)
|
||||||
|
|
||||||
|
for (; Articles[articleIndex] && Articles[articleIndex].publish_at >= start && Articles[articleIndex].publish_at < end; articleIndex++) {
|
||||||
|
branchYear.videos.push(Articles[articleIndex])
|
||||||
|
branchMonth.videos.push(Articles[articleIndex])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function removeArticle(id) {
|
||||||
|
let index = Articles.findIndex(article => article.id === id)
|
||||||
|
if (index >= 0) {
|
||||||
|
Articles.splice(index, 1)
|
||||||
|
rebuildTree()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function refreshTree() {
|
||||||
|
exports.error = ''
|
||||||
|
|
||||||
|
if (exports.loading) return Promise.resolve()
|
||||||
|
|
||||||
|
exports.loading = true
|
||||||
|
|
||||||
|
m.redraw()
|
||||||
|
|
||||||
|
return api.sendRequest({
|
||||||
|
method: 'GET',
|
||||||
|
url: '/api/articles',
|
||||||
|
})
|
||||||
|
.then(result => {
|
||||||
|
result.videos.forEach(video => {
|
||||||
|
video.publish_at = new Date(video.publish_at)
|
||||||
|
video.path_short = video.path.split('-')[2]
|
||||||
|
})
|
||||||
|
|
||||||
|
Articles.splice(0, Articles.length)
|
||||||
|
Articles.push.apply(Articles, result.videos)
|
||||||
|
|
||||||
|
rebuildTree()
|
||||||
|
}, err => {
|
||||||
|
exports.error = 'Error fetching videos: ' + err.message
|
||||||
|
})
|
||||||
|
.then(() => {
|
||||||
|
exports.loading = false
|
||||||
|
m.redraw()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
exports.removeArticle = removeArticle
|
||||||
|
exports.rebuildTree = rebuildTree
|
||||||
|
exports.refreshTree = refreshTree
|
||||||
|
exports.calculateActiveBranches = calculateActiveBranches
|
1
filadelfia_archive/base
Symbolic link
|
@ -0,0 +1 @@
|
||||||
|
../base
|
13
filadelfia_archive/build-package.json
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
{
|
||||||
|
"scripts": {
|
||||||
|
"build": "esbuild app/index.js --bundle --outfile=public/assets/app.js"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"@eonasdan/tempus-dominus": "^6.7.19",
|
||||||
|
"@popperjs/core": "^2.11.8",
|
||||||
|
"eltro": "^1.4.4",
|
||||||
|
"esbuild": "^0.19.5",
|
||||||
|
"mithril": "^2.2.2",
|
||||||
|
"service-core": "^3.0.0-beta.17"
|
||||||
|
}
|
||||||
|
}
|
38
filadelfia_archive/index.mjs
Normal file
|
@ -0,0 +1,38 @@
|
||||||
|
import fs from 'fs'
|
||||||
|
import { pathToFileURL } from 'url'
|
||||||
|
import config from './base/config.mjs'
|
||||||
|
|
||||||
|
export function start(http, port, ctx) {
|
||||||
|
config.sources[1].store = ctx.config
|
||||||
|
|
||||||
|
return import('./api/server.mjs')
|
||||||
|
.then(function(module) {
|
||||||
|
let server = new module.default(http, port, ctx)
|
||||||
|
return server.run()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
if (import.meta.url === pathToFileURL(process.argv[1]).href) {
|
||||||
|
import('service-core').then(core => {
|
||||||
|
const port = 4130
|
||||||
|
|
||||||
|
var core = new core.ServiceCore('filadelfia_web', import.meta.url, port, '')
|
||||||
|
|
||||||
|
let config = {
|
||||||
|
frontend: {
|
||||||
|
url: 'http://localhost:' + port
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
config = JSON.parse(fs.readFileSync('./config.json'))
|
||||||
|
} catch {}
|
||||||
|
|
||||||
|
config.port = port
|
||||||
|
|
||||||
|
core.setConfig(config)
|
||||||
|
core.init({ start }).then(function() {
|
||||||
|
return core.run()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
66
filadelfia_archive/package.json
Normal file
|
@ -0,0 +1,66 @@
|
||||||
|
{
|
||||||
|
"name": "filadelfia_archive",
|
||||||
|
"version": "1.0.8",
|
||||||
|
"port": 4130,
|
||||||
|
"description": "Filadelfia archive",
|
||||||
|
"main": "index.js",
|
||||||
|
"directories": {
|
||||||
|
"test": "test"
|
||||||
|
},
|
||||||
|
"scripts": {
|
||||||
|
"start": "node index.mjs",
|
||||||
|
"test": "echo \"Error: no test specified\" && exit 1",
|
||||||
|
"build:prod": "asbundle app/index.js public/assets/app.js",
|
||||||
|
"build": "esbuild app/index.js --bundle --outfile=public/assets/app.js",
|
||||||
|
"dev:build": "eltro --watch build --npm build",
|
||||||
|
"dev:server": "eltro --watch server --npm server",
|
||||||
|
"dev:build:old": "npm-watch build",
|
||||||
|
"dev:server:old": "npm-watch server",
|
||||||
|
"server": "node index.mjs | bunyan"
|
||||||
|
},
|
||||||
|
"watch": {
|
||||||
|
"server": {
|
||||||
|
"patterns": [
|
||||||
|
"api",
|
||||||
|
"base",
|
||||||
|
"../base"
|
||||||
|
],
|
||||||
|
"extensions": "js,mjs",
|
||||||
|
"quiet": true,
|
||||||
|
"inherit": true
|
||||||
|
},
|
||||||
|
"build": {
|
||||||
|
"patterns": [
|
||||||
|
"app"
|
||||||
|
],
|
||||||
|
"extensions": "js",
|
||||||
|
"quiet": true,
|
||||||
|
"inherit": true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"repository": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://git.nfp.is/nfp/nfp_sites.git"
|
||||||
|
},
|
||||||
|
"author": "Jonatan Nilsson",
|
||||||
|
"license": "WTFPL",
|
||||||
|
"bugs": {
|
||||||
|
"url": "https://git.nfp.is/nfp/nfp_sites/issues"
|
||||||
|
},
|
||||||
|
"homepage": "https://git.nfp.is/nfp/nfp_sites",
|
||||||
|
"dependencies": {
|
||||||
|
"dot": "^2.0.0-beta.1",
|
||||||
|
"flaska": "^1.3.2",
|
||||||
|
"formidable": "^1.2.6",
|
||||||
|
"msnodesqlv8": "^4.1.1",
|
||||||
|
"nconf-lite": "^2.0.0"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@eonasdan/tempus-dominus": "^6.7.19",
|
||||||
|
"@popperjs/core": "^2.11.8",
|
||||||
|
"eltro": "^1.4.4",
|
||||||
|
"esbuild": "^0.19.5",
|
||||||
|
"mithril": "^2.2.2",
|
||||||
|
"service-core": "^3.0.0-beta.17"
|
||||||
|
}
|
||||||
|
}
|
13
filadelfia_archive/public/assets/app.css
Normal file
BIN
filadelfia_archive/public/assets/apple-touch-icon.png
Normal file
After Width: | Height: | Size: 13 KiB |
BIN
filadelfia_archive/public/assets/bg.avif
Normal file
BIN
filadelfia_archive/public/assets/fa-light-300.woff2
Normal file
BIN
filadelfia_archive/public/assets/favicon.png
Normal file
After Width: | Height: | Size: 3.1 KiB |
20
filadelfia_archive/public/assets/fontawesome.css
vendored
Normal file
101
filadelfia_archive/public/assets/logo.svg
Normal file
|
@ -0,0 +1,101 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||||
|
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||||
|
|
||||||
|
<svg
|
||||||
|
width="26.656599mm"
|
||||||
|
height="26.65659mm"
|
||||||
|
viewBox="0 0 26.656599 26.65659"
|
||||||
|
version="1.1"
|
||||||
|
id="svg1"
|
||||||
|
inkscape:version="1.3.1 (91b66b0783, 2023-11-16)"
|
||||||
|
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||||
|
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
xmlns:svg="http://www.w3.org/2000/svg">
|
||||||
|
<sodipodi:namedview
|
||||||
|
id="namedview1"
|
||||||
|
pagecolor="#ffffff"
|
||||||
|
bordercolor="#111111"
|
||||||
|
borderopacity="1"
|
||||||
|
inkscape:showpageshadow="0"
|
||||||
|
inkscape:pageopacity="0"
|
||||||
|
inkscape:pagecheckerboard="1"
|
||||||
|
inkscape:deskcolor="#d1d1d1"
|
||||||
|
inkscape:document-units="mm"
|
||||||
|
showguides="true"
|
||||||
|
showgrid="false"
|
||||||
|
inkscape:zoom="4.2083894"
|
||||||
|
inkscape:cx="-36.712382"
|
||||||
|
inkscape:cy="66.890197"
|
||||||
|
inkscape:window-width="2560"
|
||||||
|
inkscape:window-height="1377"
|
||||||
|
inkscape:window-x="1912"
|
||||||
|
inkscape:window-y="-8"
|
||||||
|
inkscape:window-maximized="1"
|
||||||
|
inkscape:current-layer="layer1">
|
||||||
|
<sodipodi:guide
|
||||||
|
position="21.496619,297"
|
||||||
|
orientation="0,-1"
|
||||||
|
id="guide1"
|
||||||
|
inkscape:locked="false" />
|
||||||
|
<inkscape:grid
|
||||||
|
id="grid2"
|
||||||
|
units="mm"
|
||||||
|
originx="-90.44421"
|
||||||
|
originy="-127.62701"
|
||||||
|
spacingx="0.99999998"
|
||||||
|
spacingy="1"
|
||||||
|
empcolor="#0099e5"
|
||||||
|
empopacity="0.30196078"
|
||||||
|
color="#0099e5"
|
||||||
|
opacity="0.14901961"
|
||||||
|
empspacing="5"
|
||||||
|
dotted="false"
|
||||||
|
gridanglex="30"
|
||||||
|
gridanglez="30"
|
||||||
|
visible="false" />
|
||||||
|
<sodipodi:guide
|
||||||
|
position="-2.3731349e-07,282.22545"
|
||||||
|
orientation="1,0"
|
||||||
|
id="guide2"
|
||||||
|
inkscape:locked="false" />
|
||||||
|
<sodipodi:guide
|
||||||
|
position="26.656599,279.39628"
|
||||||
|
orientation="1,0"
|
||||||
|
id="guide3"
|
||||||
|
inkscape:locked="false" />
|
||||||
|
<sodipodi:guide
|
||||||
|
position="8.8910958,270.34341"
|
||||||
|
orientation="0,-1"
|
||||||
|
id="guide4"
|
||||||
|
inkscape:locked="false" />
|
||||||
|
</sodipodi:namedview>
|
||||||
|
<defs
|
||||||
|
id="defs1" />
|
||||||
|
<g
|
||||||
|
inkscape:label="Layer 1"
|
||||||
|
inkscape:groupmode="layer"
|
||||||
|
id="layer1"
|
||||||
|
transform="translate(-90.444211,-127.62701)">
|
||||||
|
<path
|
||||||
|
id="path1"
|
||||||
|
d="M 90.444211,154.2836 H 117.10081 V 127.62701 H 90.444211 Z"
|
||||||
|
style="fill:#18597d;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.159661" />
|
||||||
|
<path
|
||||||
|
id="path224"
|
||||||
|
d="m 103.77216,127.62701 c 7.36141,0 13.32865,5.96724 13.32865,13.3283 0,7.36106 -5.96724,13.32829 -13.32865,13.32829 -7.360713,0 -13.327947,-5.96723 -13.327947,-13.32829 0,-7.36106 5.967234,-13.3283 13.327947,-13.3283"
|
||||||
|
style="fill:#18597d;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.352778" />
|
||||||
|
<path
|
||||||
|
id="path225"
|
||||||
|
d="m 103.77216,128.3629 c -6.954663,0 -12.592405,5.63774 -12.592405,12.5924 0,6.95466 5.637742,12.5924 12.592405,12.5924 6.95501,0 12.59275,-5.63774 12.59275,-12.5924 0,-6.95466 -5.63774,-12.5924 -12.59275,-12.5924"
|
||||||
|
style="fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.352778" />
|
||||||
|
<path
|
||||||
|
id="path226"
|
||||||
|
d="m 103.74393,129.68123 c 6.19478,0 11.21586,5.02179 11.21586,11.21622 0,6.19477 -5.02108,11.21621 -11.21586,11.21621 -6.19442,0 -11.216219,-5.02144 -11.216219,-11.21621 0,-6.19443 5.021799,-11.21622 11.216219,-11.21622"
|
||||||
|
style="fill:#18597d;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.352778" />
|
||||||
|
<path
|
||||||
|
id="path227"
|
||||||
|
d="m 102.98229,131.02708 v 5.04931 h 3.34469 c 0.56162,-1.17581 2.25918,-2.76684 3.26989,-3.64631 l -0.45332,1.06045 c 0.0794,-0.067 -0.70238,1.29046 0.009,2.81799 0.63217,1.35713 1.07032,2.09867 1.2894,3.73097 0.35278,-0.94015 0.56691,-1.62736 1.04034,-2.17628 0.27552,-0.31856 0.31538,-0.93698 0.27023,-1.63971 0.38488,0.42192 0.93556,2.09126 0.82726,3.4364 -0.0536,0.67134 0.10936,2.83175 -2.17523,3.46358 -0.36794,1.33879 -1.24283,2.57704 -2.68993,3.37961 -1.78646,0.9906 -2.58057,0.20249 -3.81987,2.15512 -0.0596,-0.33691 -0.0677,-0.71932 -0.0773,-0.99554 -0.12065,-0.1584 -0.20885,-0.26988 -0.26494,-0.57397 -0.0603,-0.16052 0.001,-0.51082 0.26423,-0.57256 -0.0127,-1.36984 1.36384,-1.15641 1.60585,-1.95474 0.52422,-1.24848 -1.57445,-1.39877 -2.44052,-2.12513 v 6.70877 c 0.30127,0.17286 0.58526,0.44062 0.84772,0.85478 1.29082,-2.03412 3.09175,-0.54151 4.95265,-1.57339 l 1.83021,0.63077 -0.1337,0.1076 c -2.52412,1.23119 -4.95406,-0.67381 -6.70772,2.0902 -1.78223,-2.80846 -4.264368,-0.79586 -6.832603,-2.15229 l 1.937463,-0.67628 c 0.670632,0.37183 1.33315,0.41593 1.96744,0.41522 v -10.6306 h -5.036613 c -0.20179,0.0473 -0.21449,-2.18899 0,-2.13713 l 5.036613,0.002 -0.001,-5.04931 c -0.0483,-0.21449 2.18969,-0.19614 2.1396,0 z m 3.31576,7.18397 h -1.01177 c 0.004,0.32773 10e-4,0.62194 10e-4,0.85901 0,0.78317 1.59526,1.75789 2.0387,1.95827 0.14958,-1.2125 -0.12029,-1.60549 -0.8128,-2.50049 -0.0832,-0.10689 -0.15451,-0.21167 -0.21519,-0.31679"
|
||||||
|
style="fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.352778" />
|
||||||
|
</g>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 5 KiB |
1
filadelfia_archive/public/assets/logo_nobg.svg
Normal file
|
@ -0,0 +1 @@
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="100.749" height="100.749" viewBox="0 0 26.657 26.657"><path d="M103.772 127.627c7.362 0 13.329 5.967 13.329 13.328s-5.967 13.329-13.329 13.329c-7.36 0-13.328-5.968-13.328-13.329 0-7.36 5.967-13.328 13.328-13.328" style="fill:#18597d;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:.352778" transform="translate(-90.444 -127.627)"/><path d="M103.772 128.363c-6.955 0-12.592 5.638-12.592 12.592 0 6.955 5.637 12.593 12.592 12.593 6.955 0 12.593-5.638 12.593-12.593 0-6.954-5.638-12.592-12.593-12.592" style="fill:#fff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:.352778" transform="translate(-90.444 -127.627)"/><path d="M103.744 129.681c6.195 0 11.216 5.022 11.216 11.216 0 6.195-5.021 11.217-11.216 11.217-6.194 0-11.216-5.022-11.216-11.217 0-6.194 5.022-11.216 11.216-11.216" style="fill:#18597d;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:.352778" transform="translate(-90.444 -127.627)"/><path d="M102.982 131.027v5.05h3.345c.562-1.176 2.26-2.767 3.27-3.647l-.453 1.06c.079-.066-.703 1.291.009 2.819.632 1.357 1.07 2.098 1.289 3.73.353-.94.567-1.627 1.04-2.176.276-.318.316-.937.27-1.64.385.422.936 2.092.828 3.437-.054.671.11 2.832-2.175 3.463-.368 1.34-1.243 2.578-2.69 3.38-1.787.99-2.581.203-3.82 2.155-.06-.337-.068-.72-.078-.995-.12-.159-.208-.27-.264-.574-.06-.16 0-.511.264-.573-.013-1.37 1.364-1.156 1.606-1.955.524-1.248-1.575-1.398-2.44-2.125v6.709c.3.173.584.44.847.855 1.29-2.034 3.092-.542 4.952-1.574l1.83.631-.133.108c-2.524 1.231-4.954-.674-6.708 2.09-1.782-2.808-4.264-.796-6.832-2.152l1.937-.677c.67.372 1.333.416 1.968.416v-10.63h-5.037c-.202.046-.215-2.19 0-2.138l5.037.002-.001-5.05c-.049-.214 2.19-.196 2.14 0zm3.316 7.184h-1.012c.004.328.001.622.001.86 0 .782 1.596 1.757 2.039 1.957.15-1.212-.12-1.605-.813-2.5a2.774 2.774 0 0 1-.215-.317" style="fill:#fff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:.352778" transform="translate(-90.444 -127.627)"/></svg>
|
After Width: | Height: | Size: 1.9 KiB |
BIN
filadelfia_archive/public/assets/placeholder.avif
Normal file
700
filadelfia_archive/public/assets/tempus-dominus.css
Normal file
|
@ -0,0 +1,700 @@
|
||||||
|
.visually-hidden, .tempus-dominus-widget [data-action]::after {
|
||||||
|
position: absolute !important;
|
||||||
|
width: 1px !important;
|
||||||
|
height: 1px !important;
|
||||||
|
padding: 0 !important;
|
||||||
|
margin: -1px !important;
|
||||||
|
overflow: hidden !important;
|
||||||
|
clip: rect(0, 0, 0, 0) !important;
|
||||||
|
white-space: nowrap !important;
|
||||||
|
border: 0 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tempus-dominus-widget {
|
||||||
|
list-style: none;
|
||||||
|
padding: 4px;
|
||||||
|
width: 19rem;
|
||||||
|
border-radius: 4px;
|
||||||
|
display: none;
|
||||||
|
z-index: 9999;
|
||||||
|
box-shadow: 0 2px 4px -1px rgba(0, 0, 0, 0.2), 0 4px 5px 0 rgba(0, 0, 0, 0.14), 0 1px 10px 0 rgba(0, 0, 0, 0.12);
|
||||||
|
}
|
||||||
|
.tempus-dominus-widget.calendarWeeks {
|
||||||
|
width: 21rem;
|
||||||
|
}
|
||||||
|
.tempus-dominus-widget.calendarWeeks .date-container-days {
|
||||||
|
grid-auto-columns: 12.5%;
|
||||||
|
grid-template-areas: "a a a a a a a a";
|
||||||
|
}
|
||||||
|
.tempus-dominus-widget [data-action] {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
.tempus-dominus-widget [data-action]::after {
|
||||||
|
content: attr(title);
|
||||||
|
}
|
||||||
|
.tempus-dominus-widget [data-action].disabled, .tempus-dominus-widget [data-action].disabled:hover {
|
||||||
|
background: none;
|
||||||
|
cursor: not-allowed;
|
||||||
|
}
|
||||||
|
.tempus-dominus-widget .arrow {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
.tempus-dominus-widget.show {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
.tempus-dominus-widget.show.date-container {
|
||||||
|
min-height: 315px;
|
||||||
|
}
|
||||||
|
.tempus-dominus-widget.show.time-container {
|
||||||
|
min-height: 217px;
|
||||||
|
}
|
||||||
|
.tempus-dominus-widget .td-collapse:not(.show) {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
.tempus-dominus-widget .td-collapsing {
|
||||||
|
height: 0;
|
||||||
|
overflow: hidden;
|
||||||
|
transition: height 0.35s ease;
|
||||||
|
}
|
||||||
|
@media (min-width: 576px) {
|
||||||
|
.tempus-dominus-widget.timepicker-sbs {
|
||||||
|
width: 38em;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@media (min-width: 768px) {
|
||||||
|
.tempus-dominus-widget.timepicker-sbs {
|
||||||
|
width: 38em;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@media (min-width: 992px) {
|
||||||
|
.tempus-dominus-widget.timepicker-sbs {
|
||||||
|
width: 38em;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.tempus-dominus-widget.timepicker-sbs .td-row {
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
.tempus-dominus-widget.timepicker-sbs .td-row .td-half {
|
||||||
|
flex: 0 0 auto;
|
||||||
|
width: 50%;
|
||||||
|
}
|
||||||
|
.tempus-dominus-widget div[data-action]:active {
|
||||||
|
box-shadow: none;
|
||||||
|
}
|
||||||
|
.tempus-dominus-widget .timepicker-hour,
|
||||||
|
.tempus-dominus-widget .timepicker-minute,
|
||||||
|
.tempus-dominus-widget .timepicker-second {
|
||||||
|
width: 54px;
|
||||||
|
font-weight: bold;
|
||||||
|
font-size: 1.2em;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
.tempus-dominus-widget button[data-action] {
|
||||||
|
padding: 6px;
|
||||||
|
}
|
||||||
|
.tempus-dominus-widget .toggleMeridiem {
|
||||||
|
text-align: center;
|
||||||
|
height: 38px;
|
||||||
|
}
|
||||||
|
.tempus-dominus-widget .calendar-header {
|
||||||
|
display: grid;
|
||||||
|
grid-template-areas: "a a a";
|
||||||
|
margin-bottom: 10px;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
.tempus-dominus-widget .calendar-header .next {
|
||||||
|
text-align: right;
|
||||||
|
padding-right: 10px;
|
||||||
|
}
|
||||||
|
.tempus-dominus-widget .calendar-header .previous {
|
||||||
|
text-align: left;
|
||||||
|
padding-left: 10px;
|
||||||
|
}
|
||||||
|
.tempus-dominus-widget .calendar-header .picker-switch {
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
.tempus-dominus-widget .toolbar {
|
||||||
|
display: grid;
|
||||||
|
grid-auto-flow: column;
|
||||||
|
grid-auto-rows: 40px;
|
||||||
|
}
|
||||||
|
.tempus-dominus-widget .toolbar div {
|
||||||
|
border-radius: 999px;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
box-sizing: border-box;
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
.tempus-dominus-widget .date-container-days {
|
||||||
|
display: grid;
|
||||||
|
grid-template-areas: "a a a a a a a";
|
||||||
|
grid-auto-rows: 40px;
|
||||||
|
grid-auto-columns: 14.2857142857%;
|
||||||
|
}
|
||||||
|
.tempus-dominus-widget .date-container-days .range-in {
|
||||||
|
background-color: #01419e !important;
|
||||||
|
border: none;
|
||||||
|
border-radius: 0 !important;
|
||||||
|
box-shadow: -5px 0 0 #01419e, 5px 0 0 #01419e;
|
||||||
|
}
|
||||||
|
.tempus-dominus-widget .date-container-days .range-end {
|
||||||
|
border-radius: 0 50px 50px 0 !important;
|
||||||
|
}
|
||||||
|
.tempus-dominus-widget .date-container-days .range-start {
|
||||||
|
border-radius: 50px 0 0 50px !important;
|
||||||
|
}
|
||||||
|
.tempus-dominus-widget .date-container-days .dow {
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
.tempus-dominus-widget .date-container-days .cw {
|
||||||
|
width: 90%;
|
||||||
|
height: 90%;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
display: flex;
|
||||||
|
font-size: 0.8em;
|
||||||
|
line-height: 20px;
|
||||||
|
cursor: default;
|
||||||
|
}
|
||||||
|
.tempus-dominus-widget .date-container-decades,
|
||||||
|
.tempus-dominus-widget .date-container-years,
|
||||||
|
.tempus-dominus-widget .date-container-months {
|
||||||
|
display: grid;
|
||||||
|
grid-template-areas: "a a a";
|
||||||
|
grid-auto-rows: calc((19rem - 8px) / 7);
|
||||||
|
}
|
||||||
|
.tempus-dominus-widget .time-container-hour,
|
||||||
|
.tempus-dominus-widget .time-container-minute,
|
||||||
|
.tempus-dominus-widget .time-container-second {
|
||||||
|
display: grid;
|
||||||
|
grid-template-areas: "a a a a";
|
||||||
|
grid-auto-rows: calc((19rem - 8px) / 7);
|
||||||
|
}
|
||||||
|
.tempus-dominus-widget .time-container-clock {
|
||||||
|
display: grid;
|
||||||
|
grid-auto-rows: calc((19rem - 8px) / 7);
|
||||||
|
}
|
||||||
|
.tempus-dominus-widget .time-container-clock .no-highlight {
|
||||||
|
width: 90%;
|
||||||
|
height: 90%;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
.tempus-dominus-widget .date-container-decades div:not(.no-highlight),
|
||||||
|
.tempus-dominus-widget .date-container-years div:not(.no-highlight),
|
||||||
|
.tempus-dominus-widget .date-container-months div:not(.no-highlight),
|
||||||
|
.tempus-dominus-widget .date-container-days div:not(.no-highlight),
|
||||||
|
.tempus-dominus-widget .time-container-clock div:not(.no-highlight),
|
||||||
|
.tempus-dominus-widget .time-container-hour div:not(.no-highlight),
|
||||||
|
.tempus-dominus-widget .time-container-minute div:not(.no-highlight),
|
||||||
|
.tempus-dominus-widget .time-container-second div:not(.no-highlight) {
|
||||||
|
width: 90%;
|
||||||
|
height: 90%;
|
||||||
|
border-radius: 999px;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
box-sizing: border-box;
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
.tempus-dominus-widget .date-container-decades div:not(.no-highlight).disabled, .tempus-dominus-widget .date-container-decades div:not(.no-highlight).disabled:hover,
|
||||||
|
.tempus-dominus-widget .date-container-years div:not(.no-highlight).disabled,
|
||||||
|
.tempus-dominus-widget .date-container-years div:not(.no-highlight).disabled:hover,
|
||||||
|
.tempus-dominus-widget .date-container-months div:not(.no-highlight).disabled,
|
||||||
|
.tempus-dominus-widget .date-container-months div:not(.no-highlight).disabled:hover,
|
||||||
|
.tempus-dominus-widget .date-container-days div:not(.no-highlight).disabled,
|
||||||
|
.tempus-dominus-widget .date-container-days div:not(.no-highlight).disabled:hover,
|
||||||
|
.tempus-dominus-widget .time-container-clock div:not(.no-highlight).disabled,
|
||||||
|
.tempus-dominus-widget .time-container-clock div:not(.no-highlight).disabled:hover,
|
||||||
|
.tempus-dominus-widget .time-container-hour div:not(.no-highlight).disabled,
|
||||||
|
.tempus-dominus-widget .time-container-hour div:not(.no-highlight).disabled:hover,
|
||||||
|
.tempus-dominus-widget .time-container-minute div:not(.no-highlight).disabled,
|
||||||
|
.tempus-dominus-widget .time-container-minute div:not(.no-highlight).disabled:hover,
|
||||||
|
.tempus-dominus-widget .time-container-second div:not(.no-highlight).disabled,
|
||||||
|
.tempus-dominus-widget .time-container-second div:not(.no-highlight).disabled:hover {
|
||||||
|
background: none;
|
||||||
|
cursor: not-allowed;
|
||||||
|
}
|
||||||
|
.tempus-dominus-widget .date-container-decades div:not(.no-highlight).today,
|
||||||
|
.tempus-dominus-widget .date-container-years div:not(.no-highlight).today,
|
||||||
|
.tempus-dominus-widget .date-container-months div:not(.no-highlight).today,
|
||||||
|
.tempus-dominus-widget .date-container-days div:not(.no-highlight).today,
|
||||||
|
.tempus-dominus-widget .time-container-clock div:not(.no-highlight).today,
|
||||||
|
.tempus-dominus-widget .time-container-hour div:not(.no-highlight).today,
|
||||||
|
.tempus-dominus-widget .time-container-minute div:not(.no-highlight).today,
|
||||||
|
.tempus-dominus-widget .time-container-second div:not(.no-highlight).today {
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
.tempus-dominus-widget .date-container-decades div:not(.no-highlight).today:before,
|
||||||
|
.tempus-dominus-widget .date-container-years div:not(.no-highlight).today:before,
|
||||||
|
.tempus-dominus-widget .date-container-months div:not(.no-highlight).today:before,
|
||||||
|
.tempus-dominus-widget .date-container-days div:not(.no-highlight).today:before,
|
||||||
|
.tempus-dominus-widget .time-container-clock div:not(.no-highlight).today:before,
|
||||||
|
.tempus-dominus-widget .time-container-hour div:not(.no-highlight).today:before,
|
||||||
|
.tempus-dominus-widget .time-container-minute div:not(.no-highlight).today:before,
|
||||||
|
.tempus-dominus-widget .time-container-second div:not(.no-highlight).today:before {
|
||||||
|
content: "";
|
||||||
|
display: inline-block;
|
||||||
|
border: solid transparent;
|
||||||
|
border-width: 0 0 7px 7px;
|
||||||
|
position: absolute;
|
||||||
|
bottom: 6px;
|
||||||
|
right: 6px;
|
||||||
|
}
|
||||||
|
.tempus-dominus-widget .time-container {
|
||||||
|
margin-bottom: 0.5rem;
|
||||||
|
}
|
||||||
|
.tempus-dominus-widget button {
|
||||||
|
display: inline-block;
|
||||||
|
font-weight: 400;
|
||||||
|
line-height: 1.5;
|
||||||
|
text-align: center;
|
||||||
|
text-decoration: none;
|
||||||
|
vertical-align: middle;
|
||||||
|
cursor: pointer;
|
||||||
|
-webkit-user-select: none;
|
||||||
|
-moz-user-select: none;
|
||||||
|
user-select: none;
|
||||||
|
padding: 0.375rem 0.75rem;
|
||||||
|
font-size: 1rem;
|
||||||
|
border-radius: 0.25rem;
|
||||||
|
transition: color 0.15s ease-in-out, background-color 0.15s ease-in-out, border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out;
|
||||||
|
}
|
||||||
|
.tempus-dominus-widget.tempus-dominus-widget-readonly table td.day,
|
||||||
|
.tempus-dominus-widget.tempus-dominus-widget-readonly table td.hour,
|
||||||
|
.tempus-dominus-widget.tempus-dominus-widget-readonly table td.minute,
|
||||||
|
.tempus-dominus-widget.tempus-dominus-widget-readonly table td.second,
|
||||||
|
.tempus-dominus-widget.tempus-dominus-widget-readonly table td [data-action=incrementHours],
|
||||||
|
.tempus-dominus-widget.tempus-dominus-widget-readonly table td [data-action=incrementMinutes],
|
||||||
|
.tempus-dominus-widget.tempus-dominus-widget-readonly table td [data-action=incrementSeconds],
|
||||||
|
.tempus-dominus-widget.tempus-dominus-widget-readonly table td [data-action=decrementHours],
|
||||||
|
.tempus-dominus-widget.tempus-dominus-widget-readonly table td [data-action=decrementMinutes],
|
||||||
|
.tempus-dominus-widget.tempus-dominus-widget-readonly table td [data-action=decrementSeconds],
|
||||||
|
.tempus-dominus-widget.tempus-dominus-widget-readonly table td [data-action=showHours],
|
||||||
|
.tempus-dominus-widget.tempus-dominus-widget-readonly table td [data-action=showMinutes],
|
||||||
|
.tempus-dominus-widget.tempus-dominus-widget-readonly table td [data-action=showSeconds],
|
||||||
|
.tempus-dominus-widget.tempus-dominus-widget-readonly table td [data-action=togglePeriod] {
|
||||||
|
pointer-events: none;
|
||||||
|
cursor: default;
|
||||||
|
}
|
||||||
|
.tempus-dominus-widget.tempus-dominus-widget-readonly table td.day:hover,
|
||||||
|
.tempus-dominus-widget.tempus-dominus-widget-readonly table td.hour:hover,
|
||||||
|
.tempus-dominus-widget.tempus-dominus-widget-readonly table td.minute:hover,
|
||||||
|
.tempus-dominus-widget.tempus-dominus-widget-readonly table td.second:hover,
|
||||||
|
.tempus-dominus-widget.tempus-dominus-widget-readonly table td [data-action=incrementHours]:hover,
|
||||||
|
.tempus-dominus-widget.tempus-dominus-widget-readonly table td [data-action=incrementMinutes]:hover,
|
||||||
|
.tempus-dominus-widget.tempus-dominus-widget-readonly table td [data-action=incrementSeconds]:hover,
|
||||||
|
.tempus-dominus-widget.tempus-dominus-widget-readonly table td [data-action=decrementHours]:hover,
|
||||||
|
.tempus-dominus-widget.tempus-dominus-widget-readonly table td [data-action=decrementMinutes]:hover,
|
||||||
|
.tempus-dominus-widget.tempus-dominus-widget-readonly table td [data-action=decrementSeconds]:hover,
|
||||||
|
.tempus-dominus-widget.tempus-dominus-widget-readonly table td [data-action=showHours]:hover,
|
||||||
|
.tempus-dominus-widget.tempus-dominus-widget-readonly table td [data-action=showMinutes]:hover,
|
||||||
|
.tempus-dominus-widget.tempus-dominus-widget-readonly table td [data-action=showSeconds]:hover,
|
||||||
|
.tempus-dominus-widget.tempus-dominus-widget-readonly table td [data-action=togglePeriod]:hover {
|
||||||
|
background: none;
|
||||||
|
}
|
||||||
|
.tempus-dominus-widget.light {
|
||||||
|
color: #000;
|
||||||
|
background-color: #fff;
|
||||||
|
}
|
||||||
|
.tempus-dominus-widget.light [data-action].disabled, .tempus-dominus-widget.light [data-action].disabled:hover {
|
||||||
|
color: #6c757d;
|
||||||
|
}
|
||||||
|
.tempus-dominus-widget.light .toolbar div:hover {
|
||||||
|
background: #e9ecef;
|
||||||
|
}
|
||||||
|
.tempus-dominus-widget.light .date-container-days .dow {
|
||||||
|
color: rgba(0, 0, 0, 0.5);
|
||||||
|
}
|
||||||
|
.tempus-dominus-widget.light .date-container-days .cw {
|
||||||
|
color: rgba(0, 0, 0, 0.38);
|
||||||
|
}
|
||||||
|
.tempus-dominus-widget.light .date-container-decades div:not(.no-highlight):hover,
|
||||||
|
.tempus-dominus-widget.light .date-container-years div:not(.no-highlight):hover,
|
||||||
|
.tempus-dominus-widget.light .date-container-months div:not(.no-highlight):hover,
|
||||||
|
.tempus-dominus-widget.light .date-container-days div:not(.no-highlight):hover,
|
||||||
|
.tempus-dominus-widget.light .time-container-clock div:not(.no-highlight):hover,
|
||||||
|
.tempus-dominus-widget.light .time-container-hour div:not(.no-highlight):hover,
|
||||||
|
.tempus-dominus-widget.light .time-container-minute div:not(.no-highlight):hover,
|
||||||
|
.tempus-dominus-widget.light .time-container-second div:not(.no-highlight):hover {
|
||||||
|
background: #e9ecef;
|
||||||
|
}
|
||||||
|
.tempus-dominus-widget.light .date-container-decades div:not(.no-highlight).active,
|
||||||
|
.tempus-dominus-widget.light .date-container-years div:not(.no-highlight).active,
|
||||||
|
.tempus-dominus-widget.light .date-container-months div:not(.no-highlight).active,
|
||||||
|
.tempus-dominus-widget.light .date-container-days div:not(.no-highlight).active,
|
||||||
|
.tempus-dominus-widget.light .date-container-days div.range-in:not(.no-highlight),
|
||||||
|
.tempus-dominus-widget.light .date-container-days div.range-end:not(.no-highlight),
|
||||||
|
.tempus-dominus-widget.light .date-container-days div.range-start:not(.no-highlight),
|
||||||
|
.tempus-dominus-widget.light .time-container-clock div:not(.no-highlight).active,
|
||||||
|
.tempus-dominus-widget.light .time-container-hour div:not(.no-highlight).active,
|
||||||
|
.tempus-dominus-widget.light .time-container-minute div:not(.no-highlight).active,
|
||||||
|
.tempus-dominus-widget.light .time-container-second div:not(.no-highlight).active {
|
||||||
|
background-color: #0d6efd;
|
||||||
|
color: #fff;
|
||||||
|
text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.25);
|
||||||
|
}
|
||||||
|
.tempus-dominus-widget.light .date-container-decades div:not(.no-highlight).active.old, .tempus-dominus-widget.light .date-container-decades .date-container-days div.range-in:not(.no-highlight).old, .tempus-dominus-widget.light .date-container-days .date-container-decades div.range-in:not(.no-highlight).old, .tempus-dominus-widget.light .date-container-decades .date-container-days div.range-end:not(.no-highlight).old, .tempus-dominus-widget.light .date-container-days .date-container-decades div.range-end:not(.no-highlight).old, .tempus-dominus-widget.light .date-container-decades .date-container-days div.range-start:not(.no-highlight).old, .tempus-dominus-widget.light .date-container-days .date-container-decades div.range-start:not(.no-highlight).old, .tempus-dominus-widget.light .date-container-decades div:not(.no-highlight).active.new, .tempus-dominus-widget.light .date-container-decades .date-container-days div.range-in:not(.no-highlight).new, .tempus-dominus-widget.light .date-container-days .date-container-decades div.range-in:not(.no-highlight).new, .tempus-dominus-widget.light .date-container-decades .date-container-days div.range-end:not(.no-highlight).new, .tempus-dominus-widget.light .date-container-days .date-container-decades div.range-end:not(.no-highlight).new, .tempus-dominus-widget.light .date-container-decades .date-container-days div.range-start:not(.no-highlight).new, .tempus-dominus-widget.light .date-container-days .date-container-decades div.range-start:not(.no-highlight).new,
|
||||||
|
.tempus-dominus-widget.light .date-container-years div:not(.no-highlight).active.old,
|
||||||
|
.tempus-dominus-widget.light .date-container-years .date-container-days div.range-in:not(.no-highlight).old,
|
||||||
|
.tempus-dominus-widget.light .date-container-days .date-container-years div.range-in:not(.no-highlight).old,
|
||||||
|
.tempus-dominus-widget.light .date-container-years .date-container-days div.range-end:not(.no-highlight).old,
|
||||||
|
.tempus-dominus-widget.light .date-container-days .date-container-years div.range-end:not(.no-highlight).old,
|
||||||
|
.tempus-dominus-widget.light .date-container-years .date-container-days div.range-start:not(.no-highlight).old,
|
||||||
|
.tempus-dominus-widget.light .date-container-days .date-container-years div.range-start:not(.no-highlight).old,
|
||||||
|
.tempus-dominus-widget.light .date-container-years div:not(.no-highlight).active.new,
|
||||||
|
.tempus-dominus-widget.light .date-container-years .date-container-days div.range-in:not(.no-highlight).new,
|
||||||
|
.tempus-dominus-widget.light .date-container-days .date-container-years div.range-in:not(.no-highlight).new,
|
||||||
|
.tempus-dominus-widget.light .date-container-years .date-container-days div.range-end:not(.no-highlight).new,
|
||||||
|
.tempus-dominus-widget.light .date-container-days .date-container-years div.range-end:not(.no-highlight).new,
|
||||||
|
.tempus-dominus-widget.light .date-container-years .date-container-days div.range-start:not(.no-highlight).new,
|
||||||
|
.tempus-dominus-widget.light .date-container-days .date-container-years div.range-start:not(.no-highlight).new,
|
||||||
|
.tempus-dominus-widget.light .date-container-months div:not(.no-highlight).active.old,
|
||||||
|
.tempus-dominus-widget.light .date-container-months .date-container-days div.range-in:not(.no-highlight).old,
|
||||||
|
.tempus-dominus-widget.light .date-container-days .date-container-months div.range-in:not(.no-highlight).old,
|
||||||
|
.tempus-dominus-widget.light .date-container-months .date-container-days div.range-end:not(.no-highlight).old,
|
||||||
|
.tempus-dominus-widget.light .date-container-days .date-container-months div.range-end:not(.no-highlight).old,
|
||||||
|
.tempus-dominus-widget.light .date-container-months .date-container-days div.range-start:not(.no-highlight).old,
|
||||||
|
.tempus-dominus-widget.light .date-container-days .date-container-months div.range-start:not(.no-highlight).old,
|
||||||
|
.tempus-dominus-widget.light .date-container-months div:not(.no-highlight).active.new,
|
||||||
|
.tempus-dominus-widget.light .date-container-months .date-container-days div.range-in:not(.no-highlight).new,
|
||||||
|
.tempus-dominus-widget.light .date-container-days .date-container-months div.range-in:not(.no-highlight).new,
|
||||||
|
.tempus-dominus-widget.light .date-container-months .date-container-days div.range-end:not(.no-highlight).new,
|
||||||
|
.tempus-dominus-widget.light .date-container-days .date-container-months div.range-end:not(.no-highlight).new,
|
||||||
|
.tempus-dominus-widget.light .date-container-months .date-container-days div.range-start:not(.no-highlight).new,
|
||||||
|
.tempus-dominus-widget.light .date-container-days .date-container-months div.range-start:not(.no-highlight).new,
|
||||||
|
.tempus-dominus-widget.light .date-container-days div:not(.no-highlight).active.old,
|
||||||
|
.tempus-dominus-widget.light .date-container-days div.range-in:not(.no-highlight).old,
|
||||||
|
.tempus-dominus-widget.light .date-container-days div.range-end:not(.no-highlight).old,
|
||||||
|
.tempus-dominus-widget.light .date-container-days div.range-start:not(.no-highlight).old,
|
||||||
|
.tempus-dominus-widget.light .date-container-days div:not(.no-highlight).active.new,
|
||||||
|
.tempus-dominus-widget.light .date-container-days div.range-in:not(.no-highlight).new,
|
||||||
|
.tempus-dominus-widget.light .date-container-days div.range-end:not(.no-highlight).new,
|
||||||
|
.tempus-dominus-widget.light .date-container-days div.range-start:not(.no-highlight).new,
|
||||||
|
.tempus-dominus-widget.light .time-container-clock div:not(.no-highlight).active.old,
|
||||||
|
.tempus-dominus-widget.light .time-container-clock .date-container-days div.range-in:not(.no-highlight).old,
|
||||||
|
.tempus-dominus-widget.light .date-container-days .time-container-clock div.range-in:not(.no-highlight).old,
|
||||||
|
.tempus-dominus-widget.light .time-container-clock .date-container-days div.range-end:not(.no-highlight).old,
|
||||||
|
.tempus-dominus-widget.light .date-container-days .time-container-clock div.range-end:not(.no-highlight).old,
|
||||||
|
.tempus-dominus-widget.light .time-container-clock .date-container-days div.range-start:not(.no-highlight).old,
|
||||||
|
.tempus-dominus-widget.light .date-container-days .time-container-clock div.range-start:not(.no-highlight).old,
|
||||||
|
.tempus-dominus-widget.light .time-container-clock div:not(.no-highlight).active.new,
|
||||||
|
.tempus-dominus-widget.light .time-container-clock .date-container-days div.range-in:not(.no-highlight).new,
|
||||||
|
.tempus-dominus-widget.light .date-container-days .time-container-clock div.range-in:not(.no-highlight).new,
|
||||||
|
.tempus-dominus-widget.light .time-container-clock .date-container-days div.range-end:not(.no-highlight).new,
|
||||||
|
.tempus-dominus-widget.light .date-container-days .time-container-clock div.range-end:not(.no-highlight).new,
|
||||||
|
.tempus-dominus-widget.light .time-container-clock .date-container-days div.range-start:not(.no-highlight).new,
|
||||||
|
.tempus-dominus-widget.light .date-container-days .time-container-clock div.range-start:not(.no-highlight).new,
|
||||||
|
.tempus-dominus-widget.light .time-container-hour div:not(.no-highlight).active.old,
|
||||||
|
.tempus-dominus-widget.light .time-container-hour .date-container-days div.range-in:not(.no-highlight).old,
|
||||||
|
.tempus-dominus-widget.light .date-container-days .time-container-hour div.range-in:not(.no-highlight).old,
|
||||||
|
.tempus-dominus-widget.light .time-container-hour .date-container-days div.range-end:not(.no-highlight).old,
|
||||||
|
.tempus-dominus-widget.light .date-container-days .time-container-hour div.range-end:not(.no-highlight).old,
|
||||||
|
.tempus-dominus-widget.light .time-container-hour .date-container-days div.range-start:not(.no-highlight).old,
|
||||||
|
.tempus-dominus-widget.light .date-container-days .time-container-hour div.range-start:not(.no-highlight).old,
|
||||||
|
.tempus-dominus-widget.light .time-container-hour div:not(.no-highlight).active.new,
|
||||||
|
.tempus-dominus-widget.light .time-container-hour .date-container-days div.range-in:not(.no-highlight).new,
|
||||||
|
.tempus-dominus-widget.light .date-container-days .time-container-hour div.range-in:not(.no-highlight).new,
|
||||||
|
.tempus-dominus-widget.light .time-container-hour .date-container-days div.range-end:not(.no-highlight).new,
|
||||||
|
.tempus-dominus-widget.light .date-container-days .time-container-hour div.range-end:not(.no-highlight).new,
|
||||||
|
.tempus-dominus-widget.light .time-container-hour .date-container-days div.range-start:not(.no-highlight).new,
|
||||||
|
.tempus-dominus-widget.light .date-container-days .time-container-hour div.range-start:not(.no-highlight).new,
|
||||||
|
.tempus-dominus-widget.light .time-container-minute div:not(.no-highlight).active.old,
|
||||||
|
.tempus-dominus-widget.light .time-container-minute .date-container-days div.range-in:not(.no-highlight).old,
|
||||||
|
.tempus-dominus-widget.light .date-container-days .time-container-minute div.range-in:not(.no-highlight).old,
|
||||||
|
.tempus-dominus-widget.light .time-container-minute .date-container-days div.range-end:not(.no-highlight).old,
|
||||||
|
.tempus-dominus-widget.light .date-container-days .time-container-minute div.range-end:not(.no-highlight).old,
|
||||||
|
.tempus-dominus-widget.light .time-container-minute .date-container-days div.range-start:not(.no-highlight).old,
|
||||||
|
.tempus-dominus-widget.light .date-container-days .time-container-minute div.range-start:not(.no-highlight).old,
|
||||||
|
.tempus-dominus-widget.light .time-container-minute div:not(.no-highlight).active.new,
|
||||||
|
.tempus-dominus-widget.light .time-container-minute .date-container-days div.range-in:not(.no-highlight).new,
|
||||||
|
.tempus-dominus-widget.light .date-container-days .time-container-minute div.range-in:not(.no-highlight).new,
|
||||||
|
.tempus-dominus-widget.light .time-container-minute .date-container-days div.range-end:not(.no-highlight).new,
|
||||||
|
.tempus-dominus-widget.light .date-container-days .time-container-minute div.range-end:not(.no-highlight).new,
|
||||||
|
.tempus-dominus-widget.light .time-container-minute .date-container-days div.range-start:not(.no-highlight).new,
|
||||||
|
.tempus-dominus-widget.light .date-container-days .time-container-minute div.range-start:not(.no-highlight).new,
|
||||||
|
.tempus-dominus-widget.light .time-container-second div:not(.no-highlight).active.old,
|
||||||
|
.tempus-dominus-widget.light .time-container-second .date-container-days div.range-in:not(.no-highlight).old,
|
||||||
|
.tempus-dominus-widget.light .date-container-days .time-container-second div.range-in:not(.no-highlight).old,
|
||||||
|
.tempus-dominus-widget.light .time-container-second .date-container-days div.range-end:not(.no-highlight).old,
|
||||||
|
.tempus-dominus-widget.light .date-container-days .time-container-second div.range-end:not(.no-highlight).old,
|
||||||
|
.tempus-dominus-widget.light .time-container-second .date-container-days div.range-start:not(.no-highlight).old,
|
||||||
|
.tempus-dominus-widget.light .date-container-days .time-container-second div.range-start:not(.no-highlight).old,
|
||||||
|
.tempus-dominus-widget.light .time-container-second div:not(.no-highlight).active.new,
|
||||||
|
.tempus-dominus-widget.light .time-container-second .date-container-days div.range-in:not(.no-highlight).new,
|
||||||
|
.tempus-dominus-widget.light .date-container-days .time-container-second div.range-in:not(.no-highlight).new,
|
||||||
|
.tempus-dominus-widget.light .time-container-second .date-container-days div.range-end:not(.no-highlight).new,
|
||||||
|
.tempus-dominus-widget.light .date-container-days .time-container-second div.range-end:not(.no-highlight).new,
|
||||||
|
.tempus-dominus-widget.light .time-container-second .date-container-days div.range-start:not(.no-highlight).new,
|
||||||
|
.tempus-dominus-widget.light .date-container-days .time-container-second div.range-start:not(.no-highlight).new {
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
.tempus-dominus-widget.light .date-container-decades div:not(.no-highlight).active.today:before,
|
||||||
|
.tempus-dominus-widget.light .date-container-years div:not(.no-highlight).active.today:before,
|
||||||
|
.tempus-dominus-widget.light .date-container-months div:not(.no-highlight).active.today:before,
|
||||||
|
.tempus-dominus-widget.light .date-container-days div:not(.no-highlight).active.today:before,
|
||||||
|
.tempus-dominus-widget.light .date-container-days div.range-in:not(.no-highlight).today:before,
|
||||||
|
.tempus-dominus-widget.light .date-container-days div.range-end:not(.no-highlight).today:before,
|
||||||
|
.tempus-dominus-widget.light .date-container-days div.range-start:not(.no-highlight).today:before,
|
||||||
|
.tempus-dominus-widget.light .time-container-clock div:not(.no-highlight).active.today:before,
|
||||||
|
.tempus-dominus-widget.light .time-container-hour div:not(.no-highlight).active.today:before,
|
||||||
|
.tempus-dominus-widget.light .time-container-minute div:not(.no-highlight).active.today:before,
|
||||||
|
.tempus-dominus-widget.light .time-container-second div:not(.no-highlight).active.today:before {
|
||||||
|
border-bottom-color: #fff;
|
||||||
|
}
|
||||||
|
.tempus-dominus-widget.light .date-container-decades div:not(.no-highlight).old, .tempus-dominus-widget.light .date-container-decades div:not(.no-highlight).new,
|
||||||
|
.tempus-dominus-widget.light .date-container-years div:not(.no-highlight).old,
|
||||||
|
.tempus-dominus-widget.light .date-container-years div:not(.no-highlight).new,
|
||||||
|
.tempus-dominus-widget.light .date-container-months div:not(.no-highlight).old,
|
||||||
|
.tempus-dominus-widget.light .date-container-months div:not(.no-highlight).new,
|
||||||
|
.tempus-dominus-widget.light .date-container-days div:not(.no-highlight).old,
|
||||||
|
.tempus-dominus-widget.light .date-container-days div:not(.no-highlight).new,
|
||||||
|
.tempus-dominus-widget.light .time-container-clock div:not(.no-highlight).old,
|
||||||
|
.tempus-dominus-widget.light .time-container-clock div:not(.no-highlight).new,
|
||||||
|
.tempus-dominus-widget.light .time-container-hour div:not(.no-highlight).old,
|
||||||
|
.tempus-dominus-widget.light .time-container-hour div:not(.no-highlight).new,
|
||||||
|
.tempus-dominus-widget.light .time-container-minute div:not(.no-highlight).old,
|
||||||
|
.tempus-dominus-widget.light .time-container-minute div:not(.no-highlight).new,
|
||||||
|
.tempus-dominus-widget.light .time-container-second div:not(.no-highlight).old,
|
||||||
|
.tempus-dominus-widget.light .time-container-second div:not(.no-highlight).new {
|
||||||
|
color: rgba(0, 0, 0, 0.38);
|
||||||
|
}
|
||||||
|
.tempus-dominus-widget.light .date-container-decades div:not(.no-highlight).disabled, .tempus-dominus-widget.light .date-container-decades div:not(.no-highlight).disabled:hover,
|
||||||
|
.tempus-dominus-widget.light .date-container-years div:not(.no-highlight).disabled,
|
||||||
|
.tempus-dominus-widget.light .date-container-years div:not(.no-highlight).disabled:hover,
|
||||||
|
.tempus-dominus-widget.light .date-container-months div:not(.no-highlight).disabled,
|
||||||
|
.tempus-dominus-widget.light .date-container-months div:not(.no-highlight).disabled:hover,
|
||||||
|
.tempus-dominus-widget.light .date-container-days div:not(.no-highlight).disabled,
|
||||||
|
.tempus-dominus-widget.light .date-container-days div:not(.no-highlight).disabled:hover,
|
||||||
|
.tempus-dominus-widget.light .time-container-clock div:not(.no-highlight).disabled,
|
||||||
|
.tempus-dominus-widget.light .time-container-clock div:not(.no-highlight).disabled:hover,
|
||||||
|
.tempus-dominus-widget.light .time-container-hour div:not(.no-highlight).disabled,
|
||||||
|
.tempus-dominus-widget.light .time-container-hour div:not(.no-highlight).disabled:hover,
|
||||||
|
.tempus-dominus-widget.light .time-container-minute div:not(.no-highlight).disabled,
|
||||||
|
.tempus-dominus-widget.light .time-container-minute div:not(.no-highlight).disabled:hover,
|
||||||
|
.tempus-dominus-widget.light .time-container-second div:not(.no-highlight).disabled,
|
||||||
|
.tempus-dominus-widget.light .time-container-second div:not(.no-highlight).disabled:hover {
|
||||||
|
color: #6c757d;
|
||||||
|
}
|
||||||
|
.tempus-dominus-widget.light .date-container-decades div:not(.no-highlight).today:before,
|
||||||
|
.tempus-dominus-widget.light .date-container-years div:not(.no-highlight).today:before,
|
||||||
|
.tempus-dominus-widget.light .date-container-months div:not(.no-highlight).today:before,
|
||||||
|
.tempus-dominus-widget.light .date-container-days div:not(.no-highlight).today:before,
|
||||||
|
.tempus-dominus-widget.light .time-container-clock div:not(.no-highlight).today:before,
|
||||||
|
.tempus-dominus-widget.light .time-container-hour div:not(.no-highlight).today:before,
|
||||||
|
.tempus-dominus-widget.light .time-container-minute div:not(.no-highlight).today:before,
|
||||||
|
.tempus-dominus-widget.light .time-container-second div:not(.no-highlight).today:before {
|
||||||
|
border-bottom-color: #0d6efd;
|
||||||
|
border-top-color: rgba(0, 0, 0, 0.2);
|
||||||
|
}
|
||||||
|
.tempus-dominus-widget.light button {
|
||||||
|
color: #fff;
|
||||||
|
background-color: #0d6efd;
|
||||||
|
border-color: #0d6efd;
|
||||||
|
}
|
||||||
|
.tempus-dominus-widget.dark {
|
||||||
|
color: #e3e3e3;
|
||||||
|
background-color: #1b1b1b;
|
||||||
|
}
|
||||||
|
.tempus-dominus-widget.dark [data-action].disabled, .tempus-dominus-widget.dark [data-action].disabled:hover {
|
||||||
|
color: #6c757d;
|
||||||
|
}
|
||||||
|
.tempus-dominus-widget.dark .toolbar div:hover {
|
||||||
|
background: rgb(35, 38, 39);
|
||||||
|
}
|
||||||
|
.tempus-dominus-widget.dark .date-container-days .dow {
|
||||||
|
color: rgba(232, 230, 227, 0.5);
|
||||||
|
}
|
||||||
|
.tempus-dominus-widget.dark .date-container-days .range-in {
|
||||||
|
background-color: #0071c7 !important;
|
||||||
|
box-shadow: -5px 0 0 #0071c7, 5px 0 0 #0071c7;
|
||||||
|
}
|
||||||
|
.tempus-dominus-widget.dark .date-container-days .cw {
|
||||||
|
color: rgba(232, 230, 227, 0.38);
|
||||||
|
}
|
||||||
|
.tempus-dominus-widget.dark .date-container-decades div:not(.no-highlight):hover,
|
||||||
|
.tempus-dominus-widget.dark .date-container-years div:not(.no-highlight):hover,
|
||||||
|
.tempus-dominus-widget.dark .date-container-months div:not(.no-highlight):hover,
|
||||||
|
.tempus-dominus-widget.dark .date-container-days div:not(.no-highlight):hover,
|
||||||
|
.tempus-dominus-widget.dark .time-container-clock div:not(.no-highlight):hover,
|
||||||
|
.tempus-dominus-widget.dark .time-container-hour div:not(.no-highlight):hover,
|
||||||
|
.tempus-dominus-widget.dark .time-container-minute div:not(.no-highlight):hover,
|
||||||
|
.tempus-dominus-widget.dark .time-container-second div:not(.no-highlight):hover {
|
||||||
|
background: rgb(35, 38, 39);
|
||||||
|
}
|
||||||
|
.tempus-dominus-widget.dark .date-container-decades div:not(.no-highlight).active,
|
||||||
|
.tempus-dominus-widget.dark .date-container-years div:not(.no-highlight).active,
|
||||||
|
.tempus-dominus-widget.dark .date-container-months div:not(.no-highlight).active,
|
||||||
|
.tempus-dominus-widget.dark .date-container-days div:not(.no-highlight).active,
|
||||||
|
.tempus-dominus-widget.dark .date-container-days div.range-in:not(.no-highlight),
|
||||||
|
.tempus-dominus-widget.dark .date-container-days div.range-end:not(.no-highlight),
|
||||||
|
.tempus-dominus-widget.dark .date-container-days div.range-start:not(.no-highlight),
|
||||||
|
.tempus-dominus-widget.dark .time-container-clock div:not(.no-highlight).active,
|
||||||
|
.tempus-dominus-widget.dark .time-container-hour div:not(.no-highlight).active,
|
||||||
|
.tempus-dominus-widget.dark .time-container-minute div:not(.no-highlight).active,
|
||||||
|
.tempus-dominus-widget.dark .time-container-second div:not(.no-highlight).active {
|
||||||
|
background-color: #4db2ff;
|
||||||
|
color: #fff;
|
||||||
|
text-shadow: 0 -1px 0 rgba(232, 230, 227, 0.25);
|
||||||
|
}
|
||||||
|
.tempus-dominus-widget.dark .date-container-decades div:not(.no-highlight).active.old, .tempus-dominus-widget.dark .date-container-decades .date-container-days div.range-in:not(.no-highlight).old, .tempus-dominus-widget.dark .date-container-days .date-container-decades div.range-in:not(.no-highlight).old, .tempus-dominus-widget.dark .date-container-decades .date-container-days div.range-end:not(.no-highlight).old, .tempus-dominus-widget.dark .date-container-days .date-container-decades div.range-end:not(.no-highlight).old, .tempus-dominus-widget.dark .date-container-decades .date-container-days div.range-start:not(.no-highlight).old, .tempus-dominus-widget.dark .date-container-days .date-container-decades div.range-start:not(.no-highlight).old, .tempus-dominus-widget.dark .date-container-decades div:not(.no-highlight).active.new, .tempus-dominus-widget.dark .date-container-decades .date-container-days div.range-in:not(.no-highlight).new, .tempus-dominus-widget.dark .date-container-days .date-container-decades div.range-in:not(.no-highlight).new, .tempus-dominus-widget.dark .date-container-decades .date-container-days div.range-end:not(.no-highlight).new, .tempus-dominus-widget.dark .date-container-days .date-container-decades div.range-end:not(.no-highlight).new, .tempus-dominus-widget.dark .date-container-decades .date-container-days div.range-start:not(.no-highlight).new, .tempus-dominus-widget.dark .date-container-days .date-container-decades div.range-start:not(.no-highlight).new,
|
||||||
|
.tempus-dominus-widget.dark .date-container-years div:not(.no-highlight).active.old,
|
||||||
|
.tempus-dominus-widget.dark .date-container-years .date-container-days div.range-in:not(.no-highlight).old,
|
||||||
|
.tempus-dominus-widget.dark .date-container-days .date-container-years div.range-in:not(.no-highlight).old,
|
||||||
|
.tempus-dominus-widget.dark .date-container-years .date-container-days div.range-end:not(.no-highlight).old,
|
||||||
|
.tempus-dominus-widget.dark .date-container-days .date-container-years div.range-end:not(.no-highlight).old,
|
||||||
|
.tempus-dominus-widget.dark .date-container-years .date-container-days div.range-start:not(.no-highlight).old,
|
||||||
|
.tempus-dominus-widget.dark .date-container-days .date-container-years div.range-start:not(.no-highlight).old,
|
||||||
|
.tempus-dominus-widget.dark .date-container-years div:not(.no-highlight).active.new,
|
||||||
|
.tempus-dominus-widget.dark .date-container-years .date-container-days div.range-in:not(.no-highlight).new,
|
||||||
|
.tempus-dominus-widget.dark .date-container-days .date-container-years div.range-in:not(.no-highlight).new,
|
||||||
|
.tempus-dominus-widget.dark .date-container-years .date-container-days div.range-end:not(.no-highlight).new,
|
||||||
|
.tempus-dominus-widget.dark .date-container-days .date-container-years div.range-end:not(.no-highlight).new,
|
||||||
|
.tempus-dominus-widget.dark .date-container-years .date-container-days div.range-start:not(.no-highlight).new,
|
||||||
|
.tempus-dominus-widget.dark .date-container-days .date-container-years div.range-start:not(.no-highlight).new,
|
||||||
|
.tempus-dominus-widget.dark .date-container-months div:not(.no-highlight).active.old,
|
||||||
|
.tempus-dominus-widget.dark .date-container-months .date-container-days div.range-in:not(.no-highlight).old,
|
||||||
|
.tempus-dominus-widget.dark .date-container-days .date-container-months div.range-in:not(.no-highlight).old,
|
||||||
|
.tempus-dominus-widget.dark .date-container-months .date-container-days div.range-end:not(.no-highlight).old,
|
||||||
|
.tempus-dominus-widget.dark .date-container-days .date-container-months div.range-end:not(.no-highlight).old,
|
||||||
|
.tempus-dominus-widget.dark .date-container-months .date-container-days div.range-start:not(.no-highlight).old,
|
||||||
|
.tempus-dominus-widget.dark .date-container-days .date-container-months div.range-start:not(.no-highlight).old,
|
||||||
|
.tempus-dominus-widget.dark .date-container-months div:not(.no-highlight).active.new,
|
||||||
|
.tempus-dominus-widget.dark .date-container-months .date-container-days div.range-in:not(.no-highlight).new,
|
||||||
|
.tempus-dominus-widget.dark .date-container-days .date-container-months div.range-in:not(.no-highlight).new,
|
||||||
|
.tempus-dominus-widget.dark .date-container-months .date-container-days div.range-end:not(.no-highlight).new,
|
||||||
|
.tempus-dominus-widget.dark .date-container-days .date-container-months div.range-end:not(.no-highlight).new,
|
||||||
|
.tempus-dominus-widget.dark .date-container-months .date-container-days div.range-start:not(.no-highlight).new,
|
||||||
|
.tempus-dominus-widget.dark .date-container-days .date-container-months div.range-start:not(.no-highlight).new,
|
||||||
|
.tempus-dominus-widget.dark .date-container-days div:not(.no-highlight).active.old,
|
||||||
|
.tempus-dominus-widget.dark .date-container-days div.range-in:not(.no-highlight).old,
|
||||||
|
.tempus-dominus-widget.dark .date-container-days div.range-end:not(.no-highlight).old,
|
||||||
|
.tempus-dominus-widget.dark .date-container-days div.range-start:not(.no-highlight).old,
|
||||||
|
.tempus-dominus-widget.dark .date-container-days div:not(.no-highlight).active.new,
|
||||||
|
.tempus-dominus-widget.dark .date-container-days div.range-in:not(.no-highlight).new,
|
||||||
|
.tempus-dominus-widget.dark .date-container-days div.range-end:not(.no-highlight).new,
|
||||||
|
.tempus-dominus-widget.dark .date-container-days div.range-start:not(.no-highlight).new,
|
||||||
|
.tempus-dominus-widget.dark .time-container-clock div:not(.no-highlight).active.old,
|
||||||
|
.tempus-dominus-widget.dark .time-container-clock .date-container-days div.range-in:not(.no-highlight).old,
|
||||||
|
.tempus-dominus-widget.dark .date-container-days .time-container-clock div.range-in:not(.no-highlight).old,
|
||||||
|
.tempus-dominus-widget.dark .time-container-clock .date-container-days div.range-end:not(.no-highlight).old,
|
||||||
|
.tempus-dominus-widget.dark .date-container-days .time-container-clock div.range-end:not(.no-highlight).old,
|
||||||
|
.tempus-dominus-widget.dark .time-container-clock .date-container-days div.range-start:not(.no-highlight).old,
|
||||||
|
.tempus-dominus-widget.dark .date-container-days .time-container-clock div.range-start:not(.no-highlight).old,
|
||||||
|
.tempus-dominus-widget.dark .time-container-clock div:not(.no-highlight).active.new,
|
||||||
|
.tempus-dominus-widget.dark .time-container-clock .date-container-days div.range-in:not(.no-highlight).new,
|
||||||
|
.tempus-dominus-widget.dark .date-container-days .time-container-clock div.range-in:not(.no-highlight).new,
|
||||||
|
.tempus-dominus-widget.dark .time-container-clock .date-container-days div.range-end:not(.no-highlight).new,
|
||||||
|
.tempus-dominus-widget.dark .date-container-days .time-container-clock div.range-end:not(.no-highlight).new,
|
||||||
|
.tempus-dominus-widget.dark .time-container-clock .date-container-days div.range-start:not(.no-highlight).new,
|
||||||
|
.tempus-dominus-widget.dark .date-container-days .time-container-clock div.range-start:not(.no-highlight).new,
|
||||||
|
.tempus-dominus-widget.dark .time-container-hour div:not(.no-highlight).active.old,
|
||||||
|
.tempus-dominus-widget.dark .time-container-hour .date-container-days div.range-in:not(.no-highlight).old,
|
||||||
|
.tempus-dominus-widget.dark .date-container-days .time-container-hour div.range-in:not(.no-highlight).old,
|
||||||
|
.tempus-dominus-widget.dark .time-container-hour .date-container-days div.range-end:not(.no-highlight).old,
|
||||||
|
.tempus-dominus-widget.dark .date-container-days .time-container-hour div.range-end:not(.no-highlight).old,
|
||||||
|
.tempus-dominus-widget.dark .time-container-hour .date-container-days div.range-start:not(.no-highlight).old,
|
||||||
|
.tempus-dominus-widget.dark .date-container-days .time-container-hour div.range-start:not(.no-highlight).old,
|
||||||
|
.tempus-dominus-widget.dark .time-container-hour div:not(.no-highlight).active.new,
|
||||||
|
.tempus-dominus-widget.dark .time-container-hour .date-container-days div.range-in:not(.no-highlight).new,
|
||||||
|
.tempus-dominus-widget.dark .date-container-days .time-container-hour div.range-in:not(.no-highlight).new,
|
||||||
|
.tempus-dominus-widget.dark .time-container-hour .date-container-days div.range-end:not(.no-highlight).new,
|
||||||
|
.tempus-dominus-widget.dark .date-container-days .time-container-hour div.range-end:not(.no-highlight).new,
|
||||||
|
.tempus-dominus-widget.dark .time-container-hour .date-container-days div.range-start:not(.no-highlight).new,
|
||||||
|
.tempus-dominus-widget.dark .date-container-days .time-container-hour div.range-start:not(.no-highlight).new,
|
||||||
|
.tempus-dominus-widget.dark .time-container-minute div:not(.no-highlight).active.old,
|
||||||
|
.tempus-dominus-widget.dark .time-container-minute .date-container-days div.range-in:not(.no-highlight).old,
|
||||||
|
.tempus-dominus-widget.dark .date-container-days .time-container-minute div.range-in:not(.no-highlight).old,
|
||||||
|
.tempus-dominus-widget.dark .time-container-minute .date-container-days div.range-end:not(.no-highlight).old,
|
||||||
|
.tempus-dominus-widget.dark .date-container-days .time-container-minute div.range-end:not(.no-highlight).old,
|
||||||
|
.tempus-dominus-widget.dark .time-container-minute .date-container-days div.range-start:not(.no-highlight).old,
|
||||||
|
.tempus-dominus-widget.dark .date-container-days .time-container-minute div.range-start:not(.no-highlight).old,
|
||||||
|
.tempus-dominus-widget.dark .time-container-minute div:not(.no-highlight).active.new,
|
||||||
|
.tempus-dominus-widget.dark .time-container-minute .date-container-days div.range-in:not(.no-highlight).new,
|
||||||
|
.tempus-dominus-widget.dark .date-container-days .time-container-minute div.range-in:not(.no-highlight).new,
|
||||||
|
.tempus-dominus-widget.dark .time-container-minute .date-container-days div.range-end:not(.no-highlight).new,
|
||||||
|
.tempus-dominus-widget.dark .date-container-days .time-container-minute div.range-end:not(.no-highlight).new,
|
||||||
|
.tempus-dominus-widget.dark .time-container-minute .date-container-days div.range-start:not(.no-highlight).new,
|
||||||
|
.tempus-dominus-widget.dark .date-container-days .time-container-minute div.range-start:not(.no-highlight).new,
|
||||||
|
.tempus-dominus-widget.dark .time-container-second div:not(.no-highlight).active.old,
|
||||||
|
.tempus-dominus-widget.dark .time-container-second .date-container-days div.range-in:not(.no-highlight).old,
|
||||||
|
.tempus-dominus-widget.dark .date-container-days .time-container-second div.range-in:not(.no-highlight).old,
|
||||||
|
.tempus-dominus-widget.dark .time-container-second .date-container-days div.range-end:not(.no-highlight).old,
|
||||||
|
.tempus-dominus-widget.dark .date-container-days .time-container-second div.range-end:not(.no-highlight).old,
|
||||||
|
.tempus-dominus-widget.dark .time-container-second .date-container-days div.range-start:not(.no-highlight).old,
|
||||||
|
.tempus-dominus-widget.dark .date-container-days .time-container-second div.range-start:not(.no-highlight).old,
|
||||||
|
.tempus-dominus-widget.dark .time-container-second div:not(.no-highlight).active.new,
|
||||||
|
.tempus-dominus-widget.dark .time-container-second .date-container-days div.range-in:not(.no-highlight).new,
|
||||||
|
.tempus-dominus-widget.dark .date-container-days .time-container-second div.range-in:not(.no-highlight).new,
|
||||||
|
.tempus-dominus-widget.dark .time-container-second .date-container-days div.range-end:not(.no-highlight).new,
|
||||||
|
.tempus-dominus-widget.dark .date-container-days .time-container-second div.range-end:not(.no-highlight).new,
|
||||||
|
.tempus-dominus-widget.dark .time-container-second .date-container-days div.range-start:not(.no-highlight).new,
|
||||||
|
.tempus-dominus-widget.dark .date-container-days .time-container-second div.range-start:not(.no-highlight).new {
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
.tempus-dominus-widget.dark .date-container-decades div:not(.no-highlight).active.today:before,
|
||||||
|
.tempus-dominus-widget.dark .date-container-years div:not(.no-highlight).active.today:before,
|
||||||
|
.tempus-dominus-widget.dark .date-container-months div:not(.no-highlight).active.today:before,
|
||||||
|
.tempus-dominus-widget.dark .date-container-days div:not(.no-highlight).active.today:before,
|
||||||
|
.tempus-dominus-widget.dark .date-container-days div.range-in:not(.no-highlight).today:before,
|
||||||
|
.tempus-dominus-widget.dark .date-container-days div.range-end:not(.no-highlight).today:before,
|
||||||
|
.tempus-dominus-widget.dark .date-container-days div.range-start:not(.no-highlight).today:before,
|
||||||
|
.tempus-dominus-widget.dark .time-container-clock div:not(.no-highlight).active.today:before,
|
||||||
|
.tempus-dominus-widget.dark .time-container-hour div:not(.no-highlight).active.today:before,
|
||||||
|
.tempus-dominus-widget.dark .time-container-minute div:not(.no-highlight).active.today:before,
|
||||||
|
.tempus-dominus-widget.dark .time-container-second div:not(.no-highlight).active.today:before {
|
||||||
|
border-bottom-color: #1b1b1b;
|
||||||
|
}
|
||||||
|
.tempus-dominus-widget.dark .date-container-decades div:not(.no-highlight).old, .tempus-dominus-widget.dark .date-container-decades div:not(.no-highlight).new,
|
||||||
|
.tempus-dominus-widget.dark .date-container-years div:not(.no-highlight).old,
|
||||||
|
.tempus-dominus-widget.dark .date-container-years div:not(.no-highlight).new,
|
||||||
|
.tempus-dominus-widget.dark .date-container-months div:not(.no-highlight).old,
|
||||||
|
.tempus-dominus-widget.dark .date-container-months div:not(.no-highlight).new,
|
||||||
|
.tempus-dominus-widget.dark .date-container-days div:not(.no-highlight).old,
|
||||||
|
.tempus-dominus-widget.dark .date-container-days div:not(.no-highlight).new,
|
||||||
|
.tempus-dominus-widget.dark .time-container-clock div:not(.no-highlight).old,
|
||||||
|
.tempus-dominus-widget.dark .time-container-clock div:not(.no-highlight).new,
|
||||||
|
.tempus-dominus-widget.dark .time-container-hour div:not(.no-highlight).old,
|
||||||
|
.tempus-dominus-widget.dark .time-container-hour div:not(.no-highlight).new,
|
||||||
|
.tempus-dominus-widget.dark .time-container-minute div:not(.no-highlight).old,
|
||||||
|
.tempus-dominus-widget.dark .time-container-minute div:not(.no-highlight).new,
|
||||||
|
.tempus-dominus-widget.dark .time-container-second div:not(.no-highlight).old,
|
||||||
|
.tempus-dominus-widget.dark .time-container-second div:not(.no-highlight).new {
|
||||||
|
color: rgba(232, 230, 227, 0.38);
|
||||||
|
}
|
||||||
|
.tempus-dominus-widget.dark .date-container-decades div:not(.no-highlight).disabled, .tempus-dominus-widget.dark .date-container-decades div:not(.no-highlight).disabled:hover,
|
||||||
|
.tempus-dominus-widget.dark .date-container-years div:not(.no-highlight).disabled,
|
||||||
|
.tempus-dominus-widget.dark .date-container-years div:not(.no-highlight).disabled:hover,
|
||||||
|
.tempus-dominus-widget.dark .date-container-months div:not(.no-highlight).disabled,
|
||||||
|
.tempus-dominus-widget.dark .date-container-months div:not(.no-highlight).disabled:hover,
|
||||||
|
.tempus-dominus-widget.dark .date-container-days div:not(.no-highlight).disabled,
|
||||||
|
.tempus-dominus-widget.dark .date-container-days div:not(.no-highlight).disabled:hover,
|
||||||
|
.tempus-dominus-widget.dark .time-container-clock div:not(.no-highlight).disabled,
|
||||||
|
.tempus-dominus-widget.dark .time-container-clock div:not(.no-highlight).disabled:hover,
|
||||||
|
.tempus-dominus-widget.dark .time-container-hour div:not(.no-highlight).disabled,
|
||||||
|
.tempus-dominus-widget.dark .time-container-hour div:not(.no-highlight).disabled:hover,
|
||||||
|
.tempus-dominus-widget.dark .time-container-minute div:not(.no-highlight).disabled,
|
||||||
|
.tempus-dominus-widget.dark .time-container-minute div:not(.no-highlight).disabled:hover,
|
||||||
|
.tempus-dominus-widget.dark .time-container-second div:not(.no-highlight).disabled,
|
||||||
|
.tempus-dominus-widget.dark .time-container-second div:not(.no-highlight).disabled:hover {
|
||||||
|
color: #6c757d;
|
||||||
|
}
|
||||||
|
.tempus-dominus-widget.dark .date-container-decades div:not(.no-highlight).today:before,
|
||||||
|
.tempus-dominus-widget.dark .date-container-years div:not(.no-highlight).today:before,
|
||||||
|
.tempus-dominus-widget.dark .date-container-months div:not(.no-highlight).today:before,
|
||||||
|
.tempus-dominus-widget.dark .date-container-days div:not(.no-highlight).today:before,
|
||||||
|
.tempus-dominus-widget.dark .time-container-clock div:not(.no-highlight).today:before,
|
||||||
|
.tempus-dominus-widget.dark .time-container-hour div:not(.no-highlight).today:before,
|
||||||
|
.tempus-dominus-widget.dark .time-container-minute div:not(.no-highlight).today:before,
|
||||||
|
.tempus-dominus-widget.dark .time-container-second div:not(.no-highlight).today:before {
|
||||||
|
border-bottom-color: #4db2ff;
|
||||||
|
border-top-color: rgba(232, 230, 227, 0.2);
|
||||||
|
}
|
||||||
|
.tempus-dominus-widget.dark button {
|
||||||
|
color: #fff;
|
||||||
|
background-color: #4db2ff;
|
||||||
|
border-color: #4db2ff;
|
||||||
|
}
|
||||||
|
/*# sourceMappingURL=tempus-dominus.css.map */
|
BIN
filadelfia_archive/public/favicon.ico
Normal file
After Width: | Height: | Size: 15 KiB |
213
filadelfia_archive/public/index.html
Normal file
|
@ -0,0 +1,213 @@
|
||||||
|
<!doctype html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<title>Filadelfia myndhvelfing</title>
|
||||||
|
<base href="/">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
|
<link rel="apple-touch-icon" sizes="180x180" href="/assets/apple-touch-icon.png">
|
||||||
|
<link rel="icon" type="image/png" href="/assets/favicon.png">
|
||||||
|
<style>
|
||||||
|
|
||||||
|
[hidden] { display: none !important; }
|
||||||
|
|
||||||
|
:root { --bg: #fff; --bg-component: #f3f7ff; --bg-component-half: #f3f7ff77; --bg-component-alt: #ffd99c; --color: #031131; --color-alt: #7a9ad3; --main: #18597d; --main-fg: #fff; --error: red; --error-bg: hsl(0, 75%, 80%); } /* Box sizing rules */
|
||||||
|
*, *::before, *::after { box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Remove default margin */
|
||||||
|
body, h1, h2, h3, h4, p, figure, blockquote, dl, dd { margin: 0; }
|
||||||
|
|
||||||
|
body { min-height: 100vh; text-rendering: optimizeSpeed; line-height: 1.5; font-size: 16px; font-family: 'Inter var', Helvetica, Arial, sans-serif; font-variation-settings: "slnt" 0; font-feature-settings: "case", "frac", "tnum", "ss02", "calt", "ccmp", "kern"; background: var(--bg); color: var(--color); display: flex; flex-direction: column; }
|
||||||
|
|
||||||
|
.italic { font-variation-settings: "slnt" 10deg; }
|
||||||
|
|
||||||
|
input, button, textarea, select { font: inherit; }
|
||||||
|
|
||||||
|
h1 { font-size: 1.88rem; }
|
||||||
|
h2 { font-size: 1.66rem; }
|
||||||
|
h3 { font-size: 1.44rem; }
|
||||||
|
h4 { font-size: 1.22rem; }
|
||||||
|
h5 { font-size: 1.0rem; }
|
||||||
|
|
||||||
|
a, a:visited, button { text-decoration: underline; border: none; padding: 0; margin: 0; font-weight: bold; cursor: pointer; color: var(--main); background: transparent; }
|
||||||
|
h1 { margin-bottom: 1rem; }
|
||||||
|
#main { flex: 2 1 auto; display: flex; flex-direction: column; }
|
||||||
|
.page { flex: 2 1 auto; display: flex; flex-direction: column; }
|
||||||
|
.modal { flex: 2 1 auto; display: flex; flex-direction: column; justify-content: center; align-items: center; }
|
||||||
|
.modal h3 { text-align: center; margin-bottom: 1rem; }
|
||||||
|
.error { color: var(--error); }
|
||||||
|
.modal form { background: var(--bg-component); border-radius: 20px; width: 100%; max-width: 500px; margin: 2rem; padding: 1rem; display: flex; flex-direction: column; }
|
||||||
|
.loading-spinner { display: inline-block; width: 80px; height: 80px; margin-top: 0.5rem; align-self: center; }
|
||||||
|
.loading-spinner:after { content: " "; display: block; width: 64px; height: 64px; margin: 8px; border-radius: 50%; border: 6px solid var(--main); border-color: var(--main) transparent var(--main) transparent; animation: loading-spinner 1.2s linear infinite; }
|
||||||
|
@keyframes loading-spinner { 0% { transform: rotate(0deg); } 100% { transform: rotate(360deg); } }
|
||||||
|
|
||||||
|
/* Common components */
|
||||||
|
|
||||||
|
.row { display: flex; }
|
||||||
|
.column { display: flex; flex-direction: column; }
|
||||||
|
.filler { flex-grow: 2; }
|
||||||
|
|
||||||
|
input[type=text],
|
||||||
|
input[type=password],
|
||||||
|
input[type=datetime] { border: 1px solid var(--main); background: #fff; color: var(--color); border-radius: 0; padding: 0.25rem; line-height: 1rem; outline: none; width: 100%; }
|
||||||
|
|
||||||
|
input[type=text]:disabled,
|
||||||
|
input[type=password]:disabled,
|
||||||
|
input[type=datetime]:disabled { background: var(--bg-component); border-color: var(--color-alt); color: var(--color-alt); }
|
||||||
|
|
||||||
|
.form-row input:disabled + button { border-color: var(--color-alt); }
|
||||||
|
.form-row { display: flex; position: relative; flex-wrap: wrap; }
|
||||||
|
.form-row input { flex: 2 1 auto; }
|
||||||
|
.form-row > input { width: auto; }
|
||||||
|
.form-row .form-column { display: flex; flex-direction: column; justify-content: space-around; }
|
||||||
|
.form-row button { min-width: 30px; text-align: center; border: 1px solid var(--main); border-left: none; background: var(--bg-component); text-decoration: none; }
|
||||||
|
.form-row.image-banner { background-size: cover; background-color: white; border: 2px dashed var(--main); aspect-ratio: 16 / 9; align-self: center; width: 100%; max-width: 360px; }
|
||||||
|
.form-row .cover { position: absolute; top: 0; left: 0; width: 100%; height: 100%; opacity: 0; cursor: pointer; }
|
||||||
|
|
||||||
|
input[type=text]:focus,
|
||||||
|
input[type=password]:focus,
|
||||||
|
input[type=datetime]:focus { outline: 1px solid var(--main); }
|
||||||
|
.button, input[type=submit] { background: var(--main); color:var(--main-fg); border-radius: 10px; padding: 0.25rem 1rem; border: none; margin: 1rem 0 2rem; align-self: center; cursor: pointer; text-decoration: none; }
|
||||||
|
|
||||||
|
.button.spinner, input[type=submit].spinner { height: 2rem; margin-top: 2rem; margin-bottom: 1.5rem; }
|
||||||
|
.button-alert { background: var(--error-bg); color: var(--color); }
|
||||||
|
|
||||||
|
.loading-bar { border: 1px solid var(--main); background: var(--bg-component); }
|
||||||
|
.loading-bar::after { height: 1rem; background: var(--main); min-width: 1px; content: ''; display: block; width: var(--progress); transition: width 3s; }
|
||||||
|
|
||||||
|
form p, label { font-size: 0.75rem; font-weight: 500; margin: 0.75rem 0 0.5rem 0; display: block; }
|
||||||
|
form p.separator { color: var(--color-alt); margin-top: 1.5rem; padding-bottom: 0.5rem; border-bottom: 1px solid var(--color-alt); }
|
||||||
|
|
||||||
|
/* Nav */
|
||||||
|
|
||||||
|
#header { background: var(--bg-component); }
|
||||||
|
|
||||||
|
#header nav { display: flex; }
|
||||||
|
#header nav,
|
||||||
|
#header .nav { text-align: center; padding: 0.5rem 1rem 0.5rem 0; justify-content: flex-end; flex-wrap: wrap; }
|
||||||
|
|
||||||
|
#header nav a,
|
||||||
|
#header nav button { margin-left: 1rem; }
|
||||||
|
|
||||||
|
#header h4 { flex: 2 1 auto; margin: -0.5rem; }
|
||||||
|
|
||||||
|
#header h4 a,
|
||||||
|
#header h4 a:visited { background: url('/assets/logo_nobg.svg') left center no-repeat; background-size: auto calc(3rem - 4px); height: 3rem; display: inline-block; padding-left: 3rem; line-height: 3rem; }
|
||||||
|
|
||||||
|
#header .link { font-size: 1.0rem; line-height: 2rem; font-weight: normal; padding: 0 0.5rem; }
|
||||||
|
#header .changelang { font-size: 1.2rem; }
|
||||||
|
#header .logout,
|
||||||
|
#header .upload { padding: 0.25rem 1.5rem; border-radius: 2rem; text-decoration: none; }
|
||||||
|
|
||||||
|
#header .logout { background: var(--bg-component-alt); color: var(--color); }
|
||||||
|
#header .upload { background: var(--main); color: var(--main-fg); }
|
||||||
|
#header .nav { overflow-x: hidden; padding: 0.75rem 1rem 0.25rem; min-height: 4rem; align-items: flex-start; border-bottom: 1px solid #0001; }
|
||||||
|
#header .nav:hover { overflow-x: auto; }
|
||||||
|
#header .nav .inner { padding-top: 0.5rem; }
|
||||||
|
#header .nav a { margin: 0 0.25rem; padding: 0.25rem 1.5rem; border-radius: 3rem; }
|
||||||
|
#header .nav a.empty { opacity: 0.5; }
|
||||||
|
#header .nav a.active { background: var(--bg-component-alt); color: var(--color); text-decoration: none; }
|
||||||
|
#header .error { background: var(--error-bg); color: var(--color); font-size: 0.8rem; text-align: center; padding: 0.25rem; cursor: pointer; }
|
||||||
|
|
||||||
|
/* Main */
|
||||||
|
|
||||||
|
.full-error { background: var(--error-bg); color: var(--color); font-size: 0.8rem; text-align: center; padding: 0.25rem; cursor: pointer; flex: 2 1 auto; display: flex; justify-content: center; align-items: center; }
|
||||||
|
|
||||||
|
footer { text-align: center; padding: 1rem; }
|
||||||
|
|
||||||
|
footer a { font-size: 0.8rem; }
|
||||||
|
|
||||||
|
/* login */
|
||||||
|
/* upload */
|
||||||
|
|
||||||
|
.page-login,
|
||||||
|
.page-upload { background-image: url('./assets/bg.avif'); background-repeat: no-repeat; background-position: center; background-size: cover; }
|
||||||
|
.page-login .modal form,
|
||||||
|
.page-upload .modal form { backdrop-filter: blur(10px); background: var(--bg-component-half); }
|
||||||
|
|
||||||
|
/* browse */
|
||||||
|
|
||||||
|
.gallery { margin: 1rem; display: flex; flex-direction: column; }
|
||||||
|
.gallery-year { margin: 1rem; padding: 0 0 1rem; border-bottom: 1px solid var(--main); text-align: center; font-size: 2rem; }
|
||||||
|
.gallery-month { margin: 0rem 1rem 1rem; font-size: 1.2rem; border-bottom: 1px solid #0003; }
|
||||||
|
.gallery .group { display: flex; flex-wrap: wrap; }
|
||||||
|
.gallery .group a { width: calc(50vw - 4rem); max-width: 320px; aspect-ratio: 16 / 9; display: flex; flex-direction: column; justify-content: flex-end; background: url('./assets/placeholder.avif') center no-repeat; background-size: cover; margin: 0 1rem 1rem; text-align: center; border: 1px solid var(--main); }
|
||||||
|
|
||||||
|
.gallery .group a span { align-self: stretch; text-align: center; background: #fffb; }
|
||||||
|
|
||||||
|
/* Player */
|
||||||
|
|
||||||
|
.player { background: black; text-align: center; margin-bottom: 1rem; }
|
||||||
|
.player video { margin: 0 auto; width: 1280px; max-width: 100%; aspect-ratio: 16 / 9; }
|
||||||
|
|
||||||
|
/* article */
|
||||||
|
|
||||||
|
.article { width: 100%; max-width: 1280px; padding: 0.5rem; align-self: center; margin-bottom: 5rem; }
|
||||||
|
.article .full-error { margin-top: 1rem; }
|
||||||
|
.article-name { flex: 2 1 auto; margin-left: 1rem; }
|
||||||
|
.article h1 { margin: 0; }
|
||||||
|
.article h1,
|
||||||
|
.article p { padding: 0 0.5rem 0.5rem; }
|
||||||
|
|
||||||
|
/* table */
|
||||||
|
|
||||||
|
.table { display: grid; column-gap: 0; row-gap: 0; grid-template-columns: minmax(150px, 1.33fr) minmax(150px, 2.33fr); }
|
||||||
|
.table-row { display: contents; }
|
||||||
|
.table-row:nth-child(odd) .table-item { background: #f8f6ff; }
|
||||||
|
.table-item { padding: 0.5rem; }
|
||||||
|
|
||||||
|
/* holdbutton */
|
||||||
|
.holdbutton {
|
||||||
|
--hold-bg: var(--bg-component);
|
||||||
|
--hold-color: var(--main);
|
||||||
|
--hold-fill: var(--main);
|
||||||
|
--hold-fill-fg: white;
|
||||||
|
display: inline-block;
|
||||||
|
background: var(--hold-bg);
|
||||||
|
position: relative;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
.holdbutton .inner {
|
||||||
|
padding: 0.25rem 1rem;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
text-align: center;
|
||||||
|
justify-content: center;
|
||||||
|
height: 100%;
|
||||||
|
background: var(--hold-color);
|
||||||
|
background-clip: text;
|
||||||
|
color: transparent;
|
||||||
|
}
|
||||||
|
.holdbutton.holdbutton-active {
|
||||||
|
background: linear-gradient( var(--hold-fill) , var(--hold-fill)) var(--hold-bg) no-repeat 0 0;
|
||||||
|
background-size: 0 100%;
|
||||||
|
animation: stripes 2s linear 1 forwards;
|
||||||
|
}
|
||||||
|
.holdbutton.holdbutton-active div.inner {
|
||||||
|
background: linear-gradient( var(--hold-fill-fg), var(--hold-fill-fg)) var(--hold-color) no-repeat 0 0;
|
||||||
|
background-size: 0 100%;
|
||||||
|
animation: stripes 2s linear 1 forwards;
|
||||||
|
background-clip: text;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes stripes { to { background-size: 100% 100%; } }
|
||||||
|
|
||||||
|
@media (pointer:coarse) {
|
||||||
|
#header .nav { overflow-x: scroll; }
|
||||||
|
}
|
||||||
|
@media screen and (max-width: 700px){
|
||||||
|
.gallery, .gallery-year { margin: 0.25rem; }
|
||||||
|
.gallery-month { margin: 0rem 0.25rem 0.25rem; }
|
||||||
|
.gallery .group a { margin: 0 0.25rem 0.25rem; width: calc(50vw - 1rem); font-size: min(3vw, 1rem); }
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div id="header"></div>
|
||||||
|
<main id="main"></main>
|
||||||
|
<script type="text/javascript" src="/assets/app.js?v=2"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
BIN
filadelfia_archive/rawassets/bg.png
Normal file
After Width: | Height: | Size: 1.4 MiB |
BIN
filadelfia_archive/rawassets/bg_admin.jpg
Normal file
After Width: | Height: | Size: 435 KiB |
1
heimaerbest/.npmrc
Normal file
|
@ -0,0 +1 @@
|
||||||
|
package-lock=false
|
29
heimaerbest/api/serve.mjs
Normal file
|
@ -0,0 +1,29 @@
|
||||||
|
import path from 'path'
|
||||||
|
import striptags from 'striptags'
|
||||||
|
import Parent from '../base/serve.mjs'
|
||||||
|
import fs from 'fs/promises'
|
||||||
|
import dot from 'dot'
|
||||||
|
import config from '../base/config.mjs'
|
||||||
|
|
||||||
|
export default class ServeHandler extends Parent {
|
||||||
|
loadTemplate(indexFile) {
|
||||||
|
this.template = dot.template(indexFile.toString(), { argName: ['headerDescription', 'headerImage', 'headerTitle', 'headerUrl', 'payloadData', 'payloadTree', 'version', 'nonce', 'type', 'banner', 'media', 'in_debug'] })
|
||||||
|
}
|
||||||
|
|
||||||
|
async serveIndex(ctx) {
|
||||||
|
if (config.get('NODE_ENV') === 'development') {
|
||||||
|
let indexFile = await fs.readFile(path.join(this.root, 'index.html'))
|
||||||
|
this.loadTemplate(indexFile)
|
||||||
|
}
|
||||||
|
|
||||||
|
let payload = {
|
||||||
|
in_debug: config.get('NODE_ENV') === 'development' && false,
|
||||||
|
nonce: ctx.state.nonce,
|
||||||
|
headerUrl: '/',
|
||||||
|
headerTitle: 'Eignavakt',
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx.body = this.template(payload)
|
||||||
|
ctx.type = 'text/html; charset=utf-8'
|
||||||
|
}
|
||||||
|
}
|
18
heimaerbest/api/server.mjs
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
import config from '../base/config.mjs'
|
||||||
|
import Parent from '../base/server.mjs'
|
||||||
|
import ServeHandler from './serve.mjs'
|
||||||
|
|
||||||
|
export default class Server extends Parent {
|
||||||
|
init() {
|
||||||
|
super.init()
|
||||||
|
let localUtil = new this.core.sc.Util(import.meta.url)
|
||||||
|
|
||||||
|
this.routes.serve = new ServeHandler({
|
||||||
|
pageRoutes: this.routes.page,
|
||||||
|
articleRoutes: this.routes.article,
|
||||||
|
root: localUtil.getPathFromRoot('../public'),
|
||||||
|
version: this.core.app.running,
|
||||||
|
frontend: config.get('frontend:url'),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
131156
heimaerbest/app/Stadfangaskra.csv
Normal file
42
heimaerbest/app/authentication.js
Normal file
|
@ -0,0 +1,42 @@
|
||||||
|
const storageName = 'heimaerbest_sites_logintoken'
|
||||||
|
|
||||||
|
const Authentication = {
|
||||||
|
currentUser: null,
|
||||||
|
isAdmin: false,
|
||||||
|
loadingListeners: [],
|
||||||
|
authListeners: [],
|
||||||
|
|
||||||
|
updateToken: function(token) {
|
||||||
|
if (!token) return Authentication.clearToken()
|
||||||
|
localStorage.setItem(storageName, token)
|
||||||
|
Authentication.currentUser = JSON.parse(atob(token.split('.')[1]))
|
||||||
|
|
||||||
|
if (Authentication.authListeners.length) {
|
||||||
|
Authentication.authListeners.forEach(function(x) { x(Authentication.currentUser) })
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
clearToken: function() {
|
||||||
|
Authentication.currentUser = null
|
||||||
|
localStorage.removeItem(storageName)
|
||||||
|
Authentication.isAdmin = false
|
||||||
|
},
|
||||||
|
|
||||||
|
addEvent: function(event) {
|
||||||
|
Authentication.authListeners.push(event)
|
||||||
|
},
|
||||||
|
|
||||||
|
setAdmin: function(item) {
|
||||||
|
Authentication.isAdmin = item
|
||||||
|
},
|
||||||
|
|
||||||
|
getToken: function() {
|
||||||
|
return localStorage.getItem(storageName)
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
Authentication.updateToken(localStorage.getItem(storageName))
|
||||||
|
|
||||||
|
window.Authentication = Authentication
|
||||||
|
|
||||||
|
module.exports = Authentication
|
144
heimaerbest/app/combobox.js
Normal file
|
@ -0,0 +1,144 @@
|
||||||
|
const m = require('mithril')
|
||||||
|
const util = require('./util')
|
||||||
|
|
||||||
|
let activeBox = null
|
||||||
|
let boxIndex = 1
|
||||||
|
|
||||||
|
document.body.addEventListener('click', function() {
|
||||||
|
activeBox = null
|
||||||
|
m.redraw()
|
||||||
|
})
|
||||||
|
|
||||||
|
const Combobox = {
|
||||||
|
oninit: function(vnode) {
|
||||||
|
this.filtered = []
|
||||||
|
this.open = false
|
||||||
|
this.input = null
|
||||||
|
this.id = boxIndex++
|
||||||
|
this.onbeforeupdate(vnode)
|
||||||
|
this.focus = this.onFocus.bind(this, vnode)
|
||||||
|
},
|
||||||
|
|
||||||
|
onbeforeupdate: function(vnode) {
|
||||||
|
if (!vnode.attrs.value) {
|
||||||
|
this.filtered = vnode.attrs.items || []
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
let val = vnode.attrs.value.toLocaleLowerCase()
|
||||||
|
this.filtered = vnode.attrs.items.filter(item => {
|
||||||
|
return item.toLocaleLowerCase().indexOf(val) >= 0
|
||||||
|
})
|
||||||
|
},
|
||||||
|
|
||||||
|
onInput: function(vnode, e) {
|
||||||
|
this.smartOpen(vnode)
|
||||||
|
if (vnode.attrs.oninput) {
|
||||||
|
vnode.attrs.oninput(e)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
onDone: function(vnode) {
|
||||||
|
if (vnode.attrs.ondone) {
|
||||||
|
vnode.attrs.ondone()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
onFocus: function(vnode) {
|
||||||
|
this.input.focus()
|
||||||
|
},
|
||||||
|
|
||||||
|
selectText: function(vnode, text) {
|
||||||
|
this.input.value = text
|
||||||
|
this.onInput(vnode, { target: this.input })
|
||||||
|
activeBox = null
|
||||||
|
this.onDone(vnode)
|
||||||
|
return false
|
||||||
|
},
|
||||||
|
|
||||||
|
onKeyPress: function(vnode, e) {
|
||||||
|
if (e.key === 'ArrowDown') {
|
||||||
|
if (e.target.dataset.type === 'input' && e.target.nextElementSibling && e.target.nextElementSibling.childNodes) {
|
||||||
|
e.target.nextElementSibling.childNodes[0].focus()
|
||||||
|
} else if (e.target.dataset.type === 'item') {
|
||||||
|
if (e.target.nextElementSibling) {
|
||||||
|
e.target.nextElementSibling.focus()
|
||||||
|
} else {
|
||||||
|
this.input.focus()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if (e.key === 'ArrowUp') {
|
||||||
|
if (e.target.dataset.type === 'input' && e.target.nextElementSibling && e.target.nextElementSibling.lastChild) {
|
||||||
|
e.target.nextElementSibling.scrollTop = e.target.nextElementSibling.scrollHeight
|
||||||
|
e.target.nextElementSibling.lastChild.focus()
|
||||||
|
} else if (e.target.dataset.type === 'item') {
|
||||||
|
if (e.target.previousElementSibling) {
|
||||||
|
e.target.previousElementSibling.focus()
|
||||||
|
} else {
|
||||||
|
this.input.focus()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if (e.key === 'Enter') {
|
||||||
|
let newVal = ''
|
||||||
|
if (e.target.dataset.type === 'input' && this.filtered.length) {
|
||||||
|
if (e.target.value && e.target.value !== this.filtered[0]) {
|
||||||
|
return this.selectText(vnode, this.filtered[0])
|
||||||
|
}
|
||||||
|
this.onDone(vnode)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if (e.target.dataset.type === 'item') {
|
||||||
|
return this.selectText(vnode, e.target.dataset.value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
smartOpen: function(vnode) {
|
||||||
|
if (this.input.value && this.input.value === this.filtered[0]) {
|
||||||
|
activeBox = null
|
||||||
|
} else {
|
||||||
|
activeBox = this.id
|
||||||
|
}
|
||||||
|
m.redraw()
|
||||||
|
},
|
||||||
|
|
||||||
|
view: function(vnode) {
|
||||||
|
closeBox = false
|
||||||
|
|
||||||
|
return m('div.form-item.combobox', {
|
||||||
|
class: vnode.attrs.class,
|
||||||
|
onclick: util.cancelPropagation,
|
||||||
|
}, [
|
||||||
|
m('label', vnode.attrs.label),
|
||||||
|
m('input', {
|
||||||
|
'data-type': 'input',
|
||||||
|
oncreate: (e) => { this.input = e.dom },
|
||||||
|
onkeydown: (e) => this.onKeyPress(vnode, e),
|
||||||
|
type: vnode.attrs.type || 'text',
|
||||||
|
value: vnode.attrs.value,
|
||||||
|
onfocus: () => this.smartOpen(vnode),
|
||||||
|
placeholder: vnode.attrs.placeholder || '',
|
||||||
|
oninput: this.onInput.bind(this, vnode),
|
||||||
|
}),
|
||||||
|
activeBox === this.id
|
||||||
|
? m('div.combobox-list', [
|
||||||
|
this.filtered.slice(0, 50).map(item => {
|
||||||
|
return m('div.combobox-list-item', {
|
||||||
|
'data-type': 'item',
|
||||||
|
'data-value': item,
|
||||||
|
onkeydown: (e) => this.onKeyPress(vnode, e),
|
||||||
|
onclick: (e) => this.selectText(vnode, item),
|
||||||
|
tabindex: '0',
|
||||||
|
}, item)
|
||||||
|
})
|
||||||
|
])
|
||||||
|
: null,
|
||||||
|
])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = Combobox
|
272
heimaerbest/app/consts.js
Normal file
46
heimaerbest/app/footer.js
Normal file
|
@ -0,0 +1,46 @@
|
||||||
|
const m = require('mithril')
|
||||||
|
|
||||||
|
const Footer = {
|
||||||
|
view: function() {
|
||||||
|
return [
|
||||||
|
m('div.house_4'),
|
||||||
|
m('div.content', [
|
||||||
|
m('div.top', [
|
||||||
|
m('img.logo', { src: '/assets/img/logo_white.svg' }),
|
||||||
|
m('div.filler'),
|
||||||
|
m('a.facebook', { href: '#' }),
|
||||||
|
]),
|
||||||
|
m('div.split', [
|
||||||
|
m('div.split-part', [
|
||||||
|
m('p', 'Eignavakt.is'),
|
||||||
|
m('p', 'Sundagarðar 2'),
|
||||||
|
m('p', '104 Reykjavík'),
|
||||||
|
]),
|
||||||
|
m('div.split-part', [
|
||||||
|
m('a', { href: '#' }, 'Heim'),
|
||||||
|
m('a', { href: '#' }, 'Góð ráð'),
|
||||||
|
m('a', { href: '#' }, 'Privacy policy'),
|
||||||
|
]),
|
||||||
|
m('form', [
|
||||||
|
m('div.form-group', [
|
||||||
|
m('h5', 'Skrá mig í fréttabréf'),
|
||||||
|
]),
|
||||||
|
m('div.form-group', [
|
||||||
|
m('div.form-item', [
|
||||||
|
m('label', 'Netfang*'),
|
||||||
|
m('input', { type: 'text', placeholder: 'jon@jonson.is' }),
|
||||||
|
]),
|
||||||
|
]),
|
||||||
|
m('div.form-group', [
|
||||||
|
m('div.form-item', [
|
||||||
|
m('button.button-inactive', { disabled: true }, 'Skrá mig')
|
||||||
|
]),
|
||||||
|
]),
|
||||||
|
]),
|
||||||
|
]),
|
||||||
|
]),
|
||||||
|
]
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = Footer
|
57
heimaerbest/app/header.js
Normal file
|
@ -0,0 +1,57 @@
|
||||||
|
const m = require('mithril')
|
||||||
|
const Authentication = require('./authentication')
|
||||||
|
|
||||||
|
const Header = {
|
||||||
|
oninit: function(vnode) {
|
||||||
|
this.currentActive = 'home'
|
||||||
|
this.error = ''
|
||||||
|
this.loading = false
|
||||||
|
this.onbeforeupdate()
|
||||||
|
},
|
||||||
|
|
||||||
|
onbeforeupdate: function() {
|
||||||
|
let currentPath = m.route.get()
|
||||||
|
|
||||||
|
if (!currentPath || currentPath === '/' || currentPath.startsWith('/#')) this.currentActive = 'home'
|
||||||
|
else if (currentPath === '/login') this.currentActive = 'login'
|
||||||
|
},
|
||||||
|
|
||||||
|
logOut: function() {
|
||||||
|
Authentication.clearToken()
|
||||||
|
m.route.set('/')
|
||||||
|
},
|
||||||
|
|
||||||
|
scrollToView: function(e, name) {
|
||||||
|
if (this.currentActive === 'home') {
|
||||||
|
var el = document.getElementById(name)
|
||||||
|
if (el) {
|
||||||
|
el.scrollIntoView({ behavior: 'smooth' })
|
||||||
|
e.preventDefault()
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
view: function() {
|
||||||
|
return [
|
||||||
|
m(m.route.Link,
|
||||||
|
{ href: '/', class: 'title' },
|
||||||
|
[
|
||||||
|
m('img.logo', { src: '/assets/img/logo.svg' }),
|
||||||
|
]
|
||||||
|
),
|
||||||
|
m('div.filler'),
|
||||||
|
m('div.links', [
|
||||||
|
m(m.route.Link, { href: '/#Heim' }, 'Heim'),
|
||||||
|
m(m.route.Link, { href: '/#GoodAdvice' }, 'Góð ráð'),
|
||||||
|
m(m.route.Link, {
|
||||||
|
class: 'button-active',
|
||||||
|
href: '/#subscribe',
|
||||||
|
onclick: (e) => { this.scrollToView(e, 'subscribe') },
|
||||||
|
}, 'Subscribe'),
|
||||||
|
]),
|
||||||
|
]
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = Header
|
41
heimaerbest/app/index.js
Normal file
|
@ -0,0 +1,41 @@
|
||||||
|
const m = require('mithril')
|
||||||
|
const Header = require('./header')
|
||||||
|
const Footer = require('./footer')
|
||||||
|
const Frontpage = require('./site_frontpage')
|
||||||
|
const NotFound = require('./site_404')
|
||||||
|
window.m = m
|
||||||
|
|
||||||
|
m.route.setOrig = m.route.set
|
||||||
|
m.route.set = function(path, data, options){
|
||||||
|
m.route.setOrig(path, data, options)
|
||||||
|
window.scrollTo(0, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
m.route.linkOrig = m.route.link
|
||||||
|
m.route.link = function(vnode){
|
||||||
|
m.route.linkOrig(vnode)
|
||||||
|
window.scrollTo(0, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
m.route.prefix = ''
|
||||||
|
|
||||||
|
const allRoutes = {
|
||||||
|
'/': Frontpage, // Frontpage
|
||||||
|
'/:404...': NotFound,
|
||||||
|
}
|
||||||
|
|
||||||
|
// Wait until we finish checking avif support, some views render immediately and will ask for this immediately before the callback gets called.
|
||||||
|
|
||||||
|
/*
|
||||||
|
* imgsupport.js from leechy/imgsupport
|
||||||
|
*/
|
||||||
|
const AVIF = new Image();
|
||||||
|
AVIF.onload = AVIF.onerror = function () {
|
||||||
|
window.supportsavif = (AVIF.height === 2)
|
||||||
|
document.body.className = document.body.className + ' ' + (window.supportsavif ? 'avifsupport' : 'jpegonly')
|
||||||
|
|
||||||
|
m.mount(document.getElementById('header'), Header)
|
||||||
|
m.route(document.getElementById('main'), '/', allRoutes)
|
||||||
|
m.mount(document.getElementById('footer'), Footer)
|
||||||
|
}
|
||||||
|
AVIF.src = 'data:image/avif;base64,AAAAIGZ0eXBhdmlmAAAAAGF2aWZtaWYxbWlhZk1BMUIAAADybWV0YQAAAAAAAAAoaGRscgAAAAAAAAAAcGljdAAAAAAAAAAAAAAAAGxpYmF2aWYAAAAADnBpdG0AAAAAAAEAAAAeaWxvYwAAAABEAAABAAEAAAABAAABGgAAABcAAAAoaWluZgAAAAAAAQAAABppbmZlAgAAAAABAABhdjAxQ29sb3IAAAAAamlwcnAAAABLaXBjbwAAABRpc3BlAAAAAAAAAAIAAAACAAAAEHBpeGkAAAAAAwgICAAAAAxhdjFDgQAMAAAAABNjb2xybmNseAABAA0ABoAAAAAXaXBtYQAAAAAAAAABAAEEAQKDBAAAAB9tZGF0EgAKCBgAPkgIaDQgMgkf8AAAQAAAr3A=';
|
13
heimaerbest/app/site_404.js
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
const m = require('mithril')
|
||||||
|
|
||||||
|
const NotFound = {
|
||||||
|
oninit: function(vnode) {
|
||||||
|
this.error = ''
|
||||||
|
},
|
||||||
|
|
||||||
|
view: function(vnode) {
|
||||||
|
return m('div', 'Not found')
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = NotFound
|
253
heimaerbest/app/site_frontpage.js
Normal file
|
@ -0,0 +1,253 @@
|
||||||
|
const m = require('mithril')
|
||||||
|
|
||||||
|
const Combobox = require('./combobox')
|
||||||
|
const Constants = require('./consts')
|
||||||
|
|
||||||
|
const Frontpage = {
|
||||||
|
oninit: function(vnode) {
|
||||||
|
this.error = ''
|
||||||
|
this.showAddLocation = true
|
||||||
|
this.form = {
|
||||||
|
city: '',
|
||||||
|
zip: '',
|
||||||
|
street_name: '',
|
||||||
|
locations: [
|
||||||
|
// 'Hverfisgata, 101 - Reykjavík',
|
||||||
|
],
|
||||||
|
type: [ true, false, false, false, false ],
|
||||||
|
size: [ true, false, false, false, false ],
|
||||||
|
rooms: [ true, false, false, false, false ],
|
||||||
|
}
|
||||||
|
this.values = {
|
||||||
|
type: [ 'Alveg sama', 'Einbýli', 'Fjölbýli', 'Rað/Parhús', 'Hæð/Íbuð' ],
|
||||||
|
size: [ 'Alveg sama', '0 - 50fm', '50 - 80fm', '80 - 120fm', '120fm +'],
|
||||||
|
rooms: [ 'Alveg sama', 'Stúdíó', '2 - 3 herb.', '3 - 4 herb.', '5 + herb.' ],
|
||||||
|
}
|
||||||
|
this.inputs = {
|
||||||
|
zip: null,
|
||||||
|
street: null,
|
||||||
|
}
|
||||||
|
this.cities = Object.keys(Constants.Locations)
|
||||||
|
this.zips = Object.keys(Constants.Streets)
|
||||||
|
this.streets = []
|
||||||
|
},
|
||||||
|
|
||||||
|
onFormUpdate: function(vnode, key, index, event) {
|
||||||
|
if (['city', 'zip', 'street_name'].includes(key)) {
|
||||||
|
this.form[key] = event.target.value
|
||||||
|
if (key === 'city') {
|
||||||
|
this.zips = Constants.Locations[this.form.city] || []
|
||||||
|
} else if (key === 'zip') {
|
||||||
|
this.streets = Constants.Streets[this.form.zip] || []
|
||||||
|
}
|
||||||
|
} else if (key === 'type' || key === 'size' || key === 'rooms') {
|
||||||
|
if (index > 0) {
|
||||||
|
this.form[key][0] = false
|
||||||
|
this.form[key][index] = true
|
||||||
|
} else {
|
||||||
|
for (let i = 1; i < this.form[key].length; i++) {
|
||||||
|
this.form[key][i] = false
|
||||||
|
}
|
||||||
|
this.form[key][0] = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
console.log(key, event.target.value)
|
||||||
|
},
|
||||||
|
|
||||||
|
onLocationAdd: function(vnode, event) {
|
||||||
|
if (!this.form.city) return false
|
||||||
|
this.showAddLocation = false
|
||||||
|
let entry = this.form.city
|
||||||
|
if (this.form.zip) {
|
||||||
|
entry = this.form.zip + ' - ' + this.form.city
|
||||||
|
}
|
||||||
|
if (this.form.street_name) {
|
||||||
|
if (entry !== this.form.city) {
|
||||||
|
entry = ', ' + entry
|
||||||
|
} else {
|
||||||
|
entry = ' - ' + entry
|
||||||
|
}
|
||||||
|
entry = this.form.street_name + entry
|
||||||
|
}
|
||||||
|
this.form.city = ''
|
||||||
|
this.form.zip = ''
|
||||||
|
this.form.street_name = ''
|
||||||
|
this.form.locations.push(entry)
|
||||||
|
this.cities = Object.keys(Constants.Locations)
|
||||||
|
this.zips = Object.keys(Constants.Streets)
|
||||||
|
this.streets = []
|
||||||
|
return false
|
||||||
|
},
|
||||||
|
|
||||||
|
removeLocationIndex: function(index) {
|
||||||
|
this.form.locations.splice(index, 1)
|
||||||
|
if (!this.form.locations.length) {
|
||||||
|
this.showAddLocation = true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
},
|
||||||
|
|
||||||
|
view: function(vnode) {
|
||||||
|
return [
|
||||||
|
m('section.title', [
|
||||||
|
m('div.under', [
|
||||||
|
m('div.text', [
|
||||||
|
m('h1', 'Ekki missa af draumaeigninni þinni'),
|
||||||
|
m('p', 'Skráðu þig í Eignavaktina sem sendir þér tölvupóst um leið og draumaeignina þín kemur á markaðinn'),
|
||||||
|
]),
|
||||||
|
m('div.filler'),
|
||||||
|
m('div.house1'),
|
||||||
|
]),
|
||||||
|
m('div.form#subscribe', [
|
||||||
|
m('h5', 'What are you looking for?'),
|
||||||
|
m('form.form-list-vertical', { hidden: !this.form.locations.length }, [
|
||||||
|
m('div.form-item.no-margin', [
|
||||||
|
m('label', 'Location'),
|
||||||
|
]),
|
||||||
|
this.form.locations.map((location, i) => {
|
||||||
|
return m('div.form-item', [
|
||||||
|
m('div.fake-input', [
|
||||||
|
m('p', location),
|
||||||
|
m('button.remove-item', { onclick: (e) => this.removeLocationIndex(i) })
|
||||||
|
])
|
||||||
|
])
|
||||||
|
}),
|
||||||
|
]),
|
||||||
|
m('form.form-group', {
|
||||||
|
hidden: !this.showAddLocation,
|
||||||
|
onsubmit: (e) => this.onLocationAdd(e),
|
||||||
|
}, [
|
||||||
|
m(Combobox, {
|
||||||
|
label: 'City*',
|
||||||
|
items: this.cities,
|
||||||
|
value: this.form.city,
|
||||||
|
placeholder: 'Reykjavík',
|
||||||
|
oninput: (e) => this.onFormUpdate(vnode, 'city', null, e),
|
||||||
|
ondone: () => { this.inputs.zip.state.focus() },
|
||||||
|
}),
|
||||||
|
m(Combobox, {
|
||||||
|
label: 'Postal code (optional)',
|
||||||
|
items: this.zips,
|
||||||
|
value: this.form.zip,
|
||||||
|
placeholder: '000',
|
||||||
|
oninput: (e) => this.onFormUpdate(vnode, 'zip', null, e),
|
||||||
|
oncreate: (e) => { this.inputs.zip = e },
|
||||||
|
ondone: () => { this.inputs.street.state.focus() },
|
||||||
|
}),
|
||||||
|
m(Combobox, {
|
||||||
|
class: 'form-fill',
|
||||||
|
label: 'Street name (optional)',
|
||||||
|
items: this.streets,
|
||||||
|
value: this.form.street_name,
|
||||||
|
placeholder: 'Enter your dream street adress',
|
||||||
|
oninput: (e) => this.onFormUpdate(vnode, 'street_name', null, e),
|
||||||
|
oncreate: (e) => { this.inputs.street = e },
|
||||||
|
ondone: () => { this.onLocationAdd(vnode) },
|
||||||
|
}),
|
||||||
|
m('div.form-item.form-small.form-no-label', [
|
||||||
|
m('input', {
|
||||||
|
class: this.form.city ? 'button-active' : 'button-outline',
|
||||||
|
type: 'submit',
|
||||||
|
value: 'Add location',
|
||||||
|
}),
|
||||||
|
]),
|
||||||
|
]
|
||||||
|
),
|
||||||
|
m('div.form-group', { hidden: this.showAddLocation }, [
|
||||||
|
m('div.form-item', [
|
||||||
|
m('button.button-flat', {
|
||||||
|
onclick: () => { this.showAddLocation = !this.showAddLocation }
|
||||||
|
}, [
|
||||||
|
m('i.ic-plus'),
|
||||||
|
m('span', 'Add more locations')
|
||||||
|
]),
|
||||||
|
]),
|
||||||
|
]),
|
||||||
|
m('div.form-group', [
|
||||||
|
m('div.form-item', [
|
||||||
|
m('label', 'Tegund'),
|
||||||
|
m('div.form-list', [
|
||||||
|
this.values.type.map((type, i) => {
|
||||||
|
return m('label.checkbox', [
|
||||||
|
m('input', {
|
||||||
|
type: 'checkbox',
|
||||||
|
checked: this.form.type[i],
|
||||||
|
oninput: (e) => this.onFormUpdate(vnode, 'type', i, e),
|
||||||
|
}),
|
||||||
|
m('div.button-checkbox', type),
|
||||||
|
])
|
||||||
|
}),
|
||||||
|
]),
|
||||||
|
]),
|
||||||
|
]),
|
||||||
|
m('div.form-group', [
|
||||||
|
m('div.form-item', [
|
||||||
|
m('label', 'Size'),
|
||||||
|
m('div.form-list', [
|
||||||
|
this.values.size.map((size, i) => {
|
||||||
|
return m('label.checkbox', [
|
||||||
|
m('input', {
|
||||||
|
type: 'checkbox',
|
||||||
|
checked: this.form.size[i],
|
||||||
|
oninput: (e) => this.onFormUpdate(vnode, 'size', i, e),
|
||||||
|
}),
|
||||||
|
m('div.button-checkbox', size),
|
||||||
|
])
|
||||||
|
}),
|
||||||
|
]),
|
||||||
|
]),
|
||||||
|
]),
|
||||||
|
m('div.form-group', [
|
||||||
|
m('div.form-item', [
|
||||||
|
m('label', 'Rooms'),
|
||||||
|
m('div.form-list', [
|
||||||
|
this.values.rooms.map((rooms, i) => {
|
||||||
|
return m('label.checkbox', [
|
||||||
|
m('input', {
|
||||||
|
type: 'checkbox',
|
||||||
|
checked: this.form.rooms[i],
|
||||||
|
oninput: (e) => this.onFormUpdate(vnode, 'rooms', i, e),
|
||||||
|
}),
|
||||||
|
m('div.button-checkbox', rooms),
|
||||||
|
])
|
||||||
|
}),
|
||||||
|
]),
|
||||||
|
]),
|
||||||
|
]),
|
||||||
|
|
||||||
|
|
||||||
|
m('div.form-group', [
|
||||||
|
m('div.form-small.form-no-label', [
|
||||||
|
m('button.button-inactive', { disabled: true }, 'Continue')
|
||||||
|
]),
|
||||||
|
]),
|
||||||
|
]),
|
||||||
|
]),
|
||||||
|
m('section.tips', [
|
||||||
|
m('div.image.house2'),
|
||||||
|
m('div.space'),
|
||||||
|
m('div.content', [
|
||||||
|
m('h2', 'Góð ráð þegar kemur að kaupa fasteign'),
|
||||||
|
m('p', 'Að undirbúa eignina þína fyrir sölusýningu eða opið hús er lykil atriði og það mun ekki bara hjálpa til við sölu heldur getur það hækkað endanlegt verð eignarinnar umtalsvert.'),
|
||||||
|
m('p', 'Það eru smáatriðin sem skipta máli þegar kemur að því að sýna eignina. Taktu til, hugaðu að lýsingu og gerðu kósí.'),
|
||||||
|
m('p', m.trust(' ')),
|
||||||
|
m('div.checkmark', 'Lagaðu til og hafðu heimilið aðlaðandi'),
|
||||||
|
m('div.checkmark', 'Falleg lýsing skiptir máli'),
|
||||||
|
m('div.checkmark', 'Kveiktu á ilmkertum eða bakaðu smákökur til að fá fram heimilislega lykt'),
|
||||||
|
m('div.checkmark', 'Mikilvægustu herbergin eru stofan, eldhúsið og hjónaherbergið'),
|
||||||
|
]),
|
||||||
|
]),
|
||||||
|
m('section.pricemat', [
|
||||||
|
m('div.content', [
|
||||||
|
m('a.link', { href: 'https://verdmat.is', target: '_blank' }, 'verdmat.is'),
|
||||||
|
m('h2', 'Want to know how much your property is worth?'),
|
||||||
|
m('p', 'Við erum með allar upplýsingar um stærð eignarinnar, fasteignamat, brunabótamat, byggingarefni, byggingarár og fleira á skrá hjá okkur. '),
|
||||||
|
m('a.button-white', { href: 'https://verdmat.is', target: '_blank' }, 'Visit verðmat.is')
|
||||||
|
]),
|
||||||
|
m('div.image.house3'),
|
||||||
|
]),
|
||||||
|
]
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = Frontpage
|
0
heimaerbest/app/temp.txt
Normal file
4
heimaerbest/app/util.js
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
export function cancelPropagation(event) {
|
||||||
|
event.stopPropagation()
|
||||||
|
return false
|
||||||
|
}
|
195
heimaerbest/app/zipcode.txt
Normal file
|
@ -0,0 +1,195 @@
|
||||||
|
101 Reykjavík Reykjavík (Miðborg) Þéttbýli Hagatorgi 1
|
||||||
|
102 Reykjavík Reykjavík (Vatnsmýri og Skerjafjörður) Þéttbýli Hagatorgi 1
|
||||||
|
103 Reykjavík Reykjavík (Háaleitis- og Bústaðahverfi) Þéttbýli Síðumúla 3-5, 108 Reykjavík
|
||||||
|
104 Reykjavík Reykjavík (Laugardalur) Þéttbýli Síðumúla 3-5, 108 Reykjavík
|
||||||
|
105 Reykjavík Reykjavík (Hlíðar) Þéttbýli Síðumúla 3-5, 108 Reykjavík
|
||||||
|
107 Reykjavík Reykjavík (Vesturbær) Þéttbýli Hagatorgi 1
|
||||||
|
108 Reykjavík Reykjavík (Múlar) Þéttbýli Síðumúla 3-5, 108 Reykjavík
|
||||||
|
109 Reykjavík Reykjavík (Breiðholt) Þéttbýli Þönglabakka 4
|
||||||
|
110 Reykjavík Reykjavík (Árbær) Þéttbýli Höfðabakka 9, 110 Reykjavík
|
||||||
|
111 Reykjavík Reykjavík (Breiðholt) Þéttbýli Þönglabakka 4, 109 Reykjavík
|
||||||
|
112 Reykjavík Reykjavík (Grafarvogur) Þéttbýli Höfðabakka 9, 110 Reykjavík
|
||||||
|
113 Reykjavík Reykjavík (Grafarholt og Úlfarsárdalur) Þéttbýli Höfðabakka 9, 110 Reykjavík
|
||||||
|
116 Reykjavík Reykjavík (Grundarhverfi) Þéttbýli Háholti 14, 270 Mosfellsbæ
|
||||||
|
121 Reykjavík Reykjavík, pósthólf Pósthólf Pósthússtræti 5, 101 Reykjavík
|
||||||
|
123 Reykjavík Reykjavík, pósthólf Pósthólf Síðumúla 3-5, 108 Reykjavík
|
||||||
|
124 Reykjavík Reykjavík, pósthólf Pósthólf Síðumúla 3-5, 108 Reykjavík
|
||||||
|
125 Reykjavík Reykjavík, pósthólf Pósthólf Síðumúla 3-5, 108 Reykjavík
|
||||||
|
127 Reykjavík Reykjavík, pósthólf Pósthólf Eiðistorgi 15, 170 Seltjarnarnesi
|
||||||
|
128 Reykjavík Reykjavík, pósthólf Pósthólf Síðumúli 3-5, 108 Reykjavík
|
||||||
|
129 Reykjavík Reykjavík, pósthólf Pósthólf Þönglabakka 4, 109 Reykjavík
|
||||||
|
130 Reykjavík Reykjavík, pósthólf Pósthólf Höfðabakka 9, 110 Reykjavík
|
||||||
|
132 Reykjavík Reykjavík, pósthólf Pósthólf Hverafold 1-3, 112 Reykjavík
|
||||||
|
150 Reykjavík Annað Opinberar stofnanir, eins og ráðuneyti og ríkisstofnanir.
|
||||||
|
155 Reykjavík Annað Einkafyrirtæki, eins og viðskiptabankar.
|
||||||
|
161 Reykjavík Reykjavík, dreifbýli (ofan Elliðavatns) Dreifbýli Höfðabakka 9, 110 Reykjavík
|
||||||
|
162 Reykjavík - Dreifbýli Kjalarnes, dreifbýli Dreifbýli Höfðabakka 9, 110 Reykjavík
|
||||||
|
170 Seltjarnarnesi Seltjarnarnes Þéttbýli Hagatorg 1
|
||||||
|
172 Seltjarnarnesi Seltjarnarnes, pósthólf Pósthólf Hagatorg 1
|
||||||
|
200 Kópavogi Kópavogur (Miðbær) Þéttbýli Dalvegi 18, 201 Kópavogi
|
||||||
|
201 Kópavogi Kópavogur (Smárar, Lindir, Salir) Þéttbýli Dalvegi 18
|
||||||
|
202 Kópavogi Kópavogur, pósthólf Pósthólf Dalvegi 18, 201 Kópavogi
|
||||||
|
203 Kópavogi Kópavogur (Hvörf, Kórar) Þéttbýli Dalvegi 18, 201 Kópavogi
|
||||||
|
206 Kópavogi Kópavogur, dreifbýli Dreifbýli Dalvegi 18
|
||||||
|
210 Garðabæ Garðabær Þéttbýli Fjarðargötu 13-15, 220 Hafnarfirði
|
||||||
|
212 Garðabæ Garðabær, pósthólf Pósthólf Fjarðargötu 13-15, 220 Hafnarfirði
|
||||||
|
220 Hafnarfirði Hafnarfjörður (Miðbær) Þéttbýli Fjarðargötu 13-15
|
||||||
|
221 Hafnarfirði Hafnarfjörður (Vellir) Þéttbýli Fjarðargötu 13-15, 220 Hafnarfirði
|
||||||
|
222 Hafnarfirði Hafnarfjörður, pósthólf Pósthólf Fjarðargötu 13-15, 220 Hafnarfirði
|
||||||
|
225 Garðabæ Garðabær (Álftanes) Þéttbýli Fjarðargötu 13-15, 220 Hafnarfirði
|
||||||
|
270 Mosfellsbæ Mosfellsbær Þéttbýli Höfðabakka 9, 110 Reykjavík
|
||||||
|
271 Mosfellsbæ Mosfellssveit, dreifbýli Dreifbýli Höfðabakka 9, 110 Reykjavík
|
||||||
|
276 Mosfellsbæ Hvalfjörður og Kjós, dreifbýli Dreifbýli Höfðabakka 9, 110 Reykjavík
|
||||||
|
190 Vogum Vogar Þéttbýli Hafnargötu 89, 230 Reykjanesbæ
|
||||||
|
191 Vogum Vatnsleysuströnd, dreifbýli Dreifbýli Hafnargötu 89, 230 Reykjanesbæ
|
||||||
|
230 Reykjanesbæ Reykjanesbær (Keflavík) Þéttbýli Hafnargötu 89, 230 Reykjanesbæ
|
||||||
|
232 Reykjanesbæ Reykjanesbær, pósthólf Pósthólf Hafnargötu 89, 230 Reykjanesbæ
|
||||||
|
233 Reykjanesbæ Reykjanesbær (Hafnir) Þéttbýli Hafnargötu 89, 230 Reykjanesbæ
|
||||||
|
235 Reykjanesbæ Keflavíkurflugvöllur Þéttbýli Hafnargötu 89, 230 Reykjanesbæ
|
||||||
|
240 Grindavík Grindavík Þéttbýli Víkurbraut 25
|
||||||
|
241 Grindavík Grindavík, dreifbýli Dreifbýli Víkurbraut 25, 240 Grindavík
|
||||||
|
245 Suðurnesjabæ Sandgerði Þéttbýli Suðurgötu 2-4
|
||||||
|
246 Suðurnesjabæ Sandgerði, dreifbýli Dreifbýli Suðurgötu 2-4, 245 Sandgerði
|
||||||
|
250 Suðurnesjabæ Garður Þéttbýli Garðbraut 69
|
||||||
|
251 Suðurnesjabæ Garður, dreifbýli Dreifbýli Garðbraut 69, 250 Garði
|
||||||
|
260 Reykjanesbæ Reykjanesbær (Njarðvík) Þéttbýli Hafnargötu 89, 230 Reykjanesbæ
|
||||||
|
262 Reykjanesbæ Reykjanesbær (Ásbrú) Þéttbýli Hafnargötu 89, 230 Reykjanesbæ
|
||||||
|
300 Akranesi Akranes Þéttbýli Smiðjuvöllum 30
|
||||||
|
301 Akranesi Akranes, dreifbýli Dreifbýli Smiðjuvöllum 30, 300 Akranesi
|
||||||
|
302 Akranesi Akranes, pósthólf Pósthólf Smiðjuvöllum 30, 300 Akranesi
|
||||||
|
310 Borgarnesi Borgarnes Þéttbýli Borgarbraut 12
|
||||||
|
311 Borgarnesi Borgarnes, dreifbýli Dreifbýli Borgarbraut 12, 310 Borgarnesi
|
||||||
|
320 Reykholti í Borgarfirði Reykholt í Borgarfirði, dreifbýli Dreifbýli Borgarbraut 12, 310 Borgarnesi
|
||||||
|
340 Stykkishólmi Stykkishólmur Þéttbýli Aðalgötu 31
|
||||||
|
341 Stykkishólmi Stykkishólmur, dreifbýli Dreifbýli Aðalgötu 31, 340 Stykkilshólmi
|
||||||
|
342 Stykkishólmi Eyja og Miklaholtshreppur Dreifbýli Aðalgötu 31, 340 Stykkilshólmi
|
||||||
|
345 Flatey á Breiðafirði Flatey á Breiðafirði Dreifbýli Aðalgötu 31, 340 Stykkishólmi
|
||||||
|
350 Grundarfirði Grundarfjörður Þéttbýli Grundargötu 50
|
||||||
|
351 Grundarfirði Grundarfjörður, dreifbýli Dreifbýli Grundargötu 50, 350 Grundarfirði
|
||||||
|
355 Ólafsvík Ólafsvík Þéttbýli Bæjartúni 5
|
||||||
|
356 Snæfellsbæ Snæfellsbær, dreifbýli Dreifbýli Bæjartúni 5, 355 Ólafsvík
|
||||||
|
360 Hellissandi Hellissandur Þéttbýli Bæjartúni 5, 355 Ólafsvík
|
||||||
|
370 Búðardal Búðardalur Þéttbýli Miðbraut 13
|
||||||
|
371 Búðardal Búðardalur, dreifbýli Dreifbýli Miðbraut 13, 370 Búðardal
|
||||||
|
380 Reykhólahreppi Reykhólar Þéttbýli Miðbraut 13, 370 Búðardal
|
||||||
|
381 Reykhólahreppi Reykhólahreppur, dreifbýli Dreifbýli Miðbraut 13, 370 Búðardal
|
||||||
|
400 Ísafirði Ísafjörður Þéttbýli Hafnarstræti 9-13
|
||||||
|
401 Ísafirði Ísafjarðardjúp, dreifbýli (frá Ögri til Laugarholts) Dreifbýli Hafnarstræti 9-13, 400 Ísafirði
|
||||||
|
410 Hnífsdal Hnífsdalur Þéttbýli Hafnarstræti 9-13, 400 Ísafirði
|
||||||
|
415 Bolungarvík Bolungarvík Þéttbýli Aðalstræti 14
|
||||||
|
416 Bolungarvík Bolungarvík, dreifbýli Dreifbýli Aðalstræti 14, 415 Bolungarvík
|
||||||
|
420 Súðavík Súðavík Þéttbýli Grundarstræti 3-5
|
||||||
|
421 Súðavík Súðavík, dreifbýli Dreifbýli Grundarstræti 3-5, Súðavík
|
||||||
|
425 Flateyri Flateyri Þéttbýli Hafnarstræti 9-13, Ísafirði
|
||||||
|
426 Flateyri Flateyri, dreifbýli Dreifbýli Hafnarstræti 9-13, Ísafirði
|
||||||
|
430 Suðureyri Suðureyri Þéttbýli Hafnarstræti 9-13, Ísafirði
|
||||||
|
431 Suðureyri Súgandafjörður, dreifbýli Dreifbýli Hafnarstræti 9-13, Ísafirði
|
||||||
|
450 Patreksfirði Patreksfjörður Þéttbýli Bjarkargötu 4
|
||||||
|
451 Patreksfirði Patreksfjörður, dreifbýli Dreifbýli Bjarkargötu 4, Patreksfirði
|
||||||
|
460 Tálknafirði Tálknafjörður Þéttbýli Bjarkargötu 4, Patreksfirði
|
||||||
|
461 Tálknafirði Tálknafjörður, dreifbýli Dreifbýli Bjarkargötu 4, Patreksfirði
|
||||||
|
465 Bíldudal Bíldudalur Þéttbýli Bjarkargötu 4, Patreksfirði
|
||||||
|
466 Bíldudal Bíldudalur, dreifbýli Dreifbýli Bjarkargötu 4, Patreksfirði
|
||||||
|
470 Þingeyri Þingeyri Þéttbýli Hafnarstræti 9-13, Ísafirði
|
||||||
|
471 Þingeyri Dýrafjörður, dreifbýli Dreifbýli Hafnarstræti 9-13, Ísafirði
|
||||||
|
500 Stað Staður Dreifbýli Lækjargötu 2, 530 Hvammstanga
|
||||||
|
510 Hólmavík Hólmavík Þéttbýli Hafnarbraut 19
|
||||||
|
511 Hólmavík Hólmavík, dreifbýli Dreifbýli Hafnarbraut 19, 510 Hólmavík
|
||||||
|
512 Hólmavík Ísafjarðardjúp, dreifbýli (nær Hólmavík) Dreifbýli Hafnarbraut 19, 510 Hólmavík
|
||||||
|
520 Drangsnesi Drangsnes Þéttbýli Hafnarbraut 19, 510 Hólmavík
|
||||||
|
524 Árneshreppi Árneshreppur Dreifbýli Hafnarbraut 19, 510 Hólmavík
|
||||||
|
530 Hvammstanga Hvammstangi Þéttbýli Lækjargötu 2
|
||||||
|
531 Hvammstanga Hvammstangi, dreifbýli Dreifbýli Lækjargötu 2, Hvammstanga
|
||||||
|
540 Blönduósi Blönduós Þéttbýli Hnjúkabyggð 32
|
||||||
|
541 Blönduósi Blönduós, dreifbýli Dreifbýli Hnjúkabyggð 32, Blönduósi
|
||||||
|
545 Skagaströnd Skagaströnd Þéttbýli Höfða
|
||||||
|
546 Skagaströnd Skagaströnd, dreifbýli Dreifbýli Hnjúkabyggð 32, Blönduósi
|
||||||
|
550 Sauðárkróki Sauðárkrókur Þéttbýli Kirkjutorgi 5
|
||||||
|
551 Sauðárkróki Sauðárkrókur, dreifbýli Dreifbýli Kirkjutorgi 5, Sauðárkróki
|
||||||
|
560 Varmahlíð Varmahlíð Þéttbýli Kirkjutorgi 5, Sauðárkróki
|
||||||
|
561 Varmahlíð Varmahlíð, dreifbýli Dreifbýli Kirkjutorgi 5, Sauðárkróki
|
||||||
|
565 Hofsósi Hofsós Þéttbýli Kirkjutorgi 5, Sauðárkróki
|
||||||
|
566 Hofsósi Hofsós, dreifbýli Dreifbýli Kirkjutorgi 5, Sauðárkróki
|
||||||
|
570 Fljótum Fljót Dreifbýli Kirkjutorgi 5, Sauðárkróki
|
||||||
|
580 Siglufirði Siglufjörður Þéttbýli Aðalgötu 24
|
||||||
|
581 Siglufirði Siglufjörður, dreifbýli Dreifbýli Aðalgötu 24, Siglufirði
|
||||||
|
600 Akureyri Akureyri Þéttbýli Strandgötu 3
|
||||||
|
601 Akureyri Akureyri, dreifbýli Dreifbýli Strandgötu 3, 600 Akureyri
|
||||||
|
602 Akureyri Akureyri, pósthólf Pósthólf Strandgötu 3, 600 Akureyri
|
||||||
|
603 Akureyri Akureyri Þéttbýli Norðurtanga 3, 600 Akureyri
|
||||||
|
604 Akureyri Akureyri, dreifbýli (Hörgársveit) Dreifbýli Norðurtanga 3
|
||||||
|
605 Akureyri Akureyri, dreifbýli (Eyjafjarðarsveit) Dreifbýli Norðurtanga 3
|
||||||
|
606 Akureyri Akureyri, dreifbýli (Svalbarðsströnd) Dreifbýli Norðurtanga 3
|
||||||
|
607 Akureyri Akureyri, dreifbýli (Þingeyjarsveit) Dreifbýli Norðurtanga 3
|
||||||
|
610 Grenivík Grenivík Þéttbýli Túngötu 3
|
||||||
|
611 Grímsey Grímsey Þéttbýli Vallargata 9
|
||||||
|
616 Grenivík Grenivík, dreifbýli Dreifbýli Túngötu 3, 610 Grenivík
|
||||||
|
620 Dalvík Dalvík Þéttbýli Hafnarbraut 26
|
||||||
|
621 Dalvík Dalvík, dreifbýli Dreifbýli Hafnarbraut 26, 620 Dalvík
|
||||||
|
625 Ólafsfirði Ólafsfjörður Þéttbýli Aðalgötu 14
|
||||||
|
626 Ólafsfirði Ólafsfjörður, dreifbýli Dreifbýli Aðalgötu 14, 625 Ólafsfirði
|
||||||
|
630 Hrísey Hrísey Þéttbýli Norðurvegi 6-8
|
||||||
|
640 Húsavík Húsavík Þéttbýli Garðarsbraut 70
|
||||||
|
641 Húsavík Húsavík, dreifbýli Dreifbýli Garðarsbraut 70, 640 Húsavík
|
||||||
|
645 Fosshóli Fosshóll, dreifbýli Dreifbýli Garðarsbraut 70, 640 Húsavík
|
||||||
|
650 Laugum Laugar Þéttbýli Kjarna
|
||||||
|
660 Mývatni Mývatn Dreifbýli Helluhrauni 3
|
||||||
|
670 Kópaskeri Kópasker Þéttbýli Bakkagötu 2
|
||||||
|
671 Kópaskeri Kópasker, dreifbýli Dreifbýli Bakkagötu 2, 670 Kópaskeri
|
||||||
|
675 Raufarhöfn Raufarhöfn Þéttbýli Aðalbraut 19
|
||||||
|
676 Raufarhöfn Raufarhöfn, dreifbýli Dreifbýli Aðalbraut 19, 675 Raufarhöfn
|
||||||
|
680 Þórshöfn Þórshöfn Þéttbýli Fjarðarvegi 5
|
||||||
|
681 Þórshöfn Þórshöfn, dreifbýli Dreifbýli Fjarðarvegi 5, 680 Þórshöfn
|
||||||
|
685 Bakkafirði Bakkafjörður Þéttbýli Fjarðarvegi 5, 680 Þórshöfn
|
||||||
|
686 Bakkafirði Bakkafjörður, dreifbýli Dreifbýli Fjarðarvegi 5, 680 Þórshöfn
|
||||||
|
690 Vopnafirði Vopnafjörður Þéttbýli Kolbeinsgötu 10
|
||||||
|
691 Vopnafirði Vopnafjörður, dreifbýli Dreifbýli Kolbeinsgötu 10, 690 Vopnafirði
|
||||||
|
700 Egilsstöðum Egilsstaðir Þéttbýli Kaupvangi 6
|
||||||
|
701 Egilsstöðum Egilsstaðir, dreifbýli Dreifbýli Kaupvangi 6, 700 Egilsstöðum
|
||||||
|
710 Seyðisfirði Seyðisfjörður Þéttbýli Hafnargötu 4
|
||||||
|
711 Seyðisfirði Seyðisfjörður, dreifbýli Dreifbýli Hafnargötu 6, 710 Seyðisfirði
|
||||||
|
715 Mjóafirði Mjóifjörður, dreifbýli Dreifbýli Brekku
|
||||||
|
720 Borgarfirði (eystri) Bakkagerði Þéttbýli Kaupvangi 6, 700 Egilsstöðum
|
||||||
|
721 Borgarfirði (eystri) Borgarfjörður eystri Dreifbýli Kaupvangi 6, 700 Egilsstöðum
|
||||||
|
730 Reyðarfirði Reyðarfjörður Þéttbýli Búðareyri 35
|
||||||
|
731 Reyðarfirði Reyðarfjörður, dreifbýli Dreifbýli Búðareyri 35, 720 Reyðarfirði
|
||||||
|
735 Eskifirði Eskifjörður Þéttbýli Strandgötu 55
|
||||||
|
736 Eskifirði Eskifjörður, dreifbýli Dreifbýli Strandgötu 55, 735 Eskifirði
|
||||||
|
740 Neskaupstað Neskaupstaður Þéttbýli Miðstræti 26
|
||||||
|
741 Neskaupstað Neskaupstaður, dreifbýli Dreifbýli Miðstræti 26, 740 Neskaupstað
|
||||||
|
750 Fáskrúðsfirði Fáskrúðsfjörður Þéttbýli Skólavegi 59
|
||||||
|
751 Fáskrúðsfirði Fáskrúðsfjörður, dreifbýli Dreifbýli Skólavegi 59, 750, Fáskrúðsfirði
|
||||||
|
755 Stöðvarfirði Stöðvarfjörður Þéttbýli Búðareyri 35, 720 Reiðarfirði
|
||||||
|
756 Stöðvarfirði Stöðvarfjörður, dreifbýli Dreifbýli Búðareyri 35, 720 Reiðarfirði
|
||||||
|
760 Breiðdalsvík Breiðdalsvík Þéttbýli Selnesi 38
|
||||||
|
761 Breiðdalsvík Breiðdalsvík, dreifbýli Dreifbýli Selnesi 38, 760 Breiðdalsvík
|
||||||
|
765 Djúpavogi Djúpivogur Þéttbýli Kambi 1
|
||||||
|
766 Djúpavogi Djúpivogur, dreifbýli Dreifbýli Kambi 1, 765 Djúpavog
|
||||||
|
780 Höfn í Hornafirði Höfn Þéttbýli Hafnarbraut 21
|
||||||
|
781 Höfn í Hornafirði Höfn, dreifbýli Dreifbýli Hafnarbraut 21, 780 Höfn
|
||||||
|
785 Öræfum Öræfi, dreifbýli Dreifbýli Hafnarbraut 21, 780 Höfn
|
||||||
|
800 Selfossi Selfoss Þéttbýli Larsenstræti 1
|
||||||
|
801 Selfossi Selfoss, dreifbyli (Árborg) Dreifbýli Larsenstræti 1, 800 Selfossi
|
||||||
|
802 Selfossi Selfoss, pósthólf Pósthólf Larsenstræti 1, 800 Selfossi
|
||||||
|
803 Selfossi Selfoss, dreifbýli (Flóahreppur) Dreifbýli Larsenstræti 1, 800 Selfossi
|
||||||
|
804 Selfossi Selfoss, dreifbýli (Skeiða- og Gnúpverjahreppur) Dreifbýli Larsenstræti 1, 800 Selfossi
|
||||||
|
805 Selfossi Selfoss, dreifbýli (Grímsnes- og Grafningshreppur) Dreifbýli Larsenstræti 1, 800 Selfossi
|
||||||
|
806 Selfossi Selfoss (Bláskógabyggð) Dreifbýli Larsenstræti 1, 800 Selfossi
|
||||||
|
810 Hveragerði Hveragerði Þéttbýli Sunnumörk 2-4
|
||||||
|
815 Þorlákshöfn Þorlákshöfn Þéttbýli Hafnarberg 1
|
||||||
|
816 Ölfusi Ölfus, dreifbýli Dreifbýli Larsenstræti 1, 800 Selfossi
|
||||||
|
820 Eyrarbakka Eyrarbakki Þéttbýli Larsenstræti 1, 800 Selfossi
|
||||||
|
825 Stokkseyri Stokkseyri Þéttbýli Larsenstræti 1, 800 Selfossi
|
||||||
|
840 Laugarvatni Laugarvatn Þéttbýli Larsenstræti 1, 800 Selfossi
|
||||||
|
845 Flúðum Flúðir Þéttbýli Larsenstræti 1, 800 Selfossi
|
||||||
|
846 Flúðum Flúðir, dreifbýli Dreifbýli Larsenstræti 1, 800 Selfossi
|
||||||
|
850 Hellu Hella Þéttbýli Þrúðvangi 10
|
||||||
|
851 Hellu Hella, dreifbyli Dreifbýli Þrúðvangi 10, 850 Hella
|
||||||
|
860 Hvolsvelli Hvolsvöllur Þéttbýli Austurvegi 2
|
||||||
|
861 Hvolsvelli Hvolsvöllur, dreifbýli Dreifbýli Austurvegi 2, 860 Hvolsvelli
|
||||||
|
870 Vík Vík Þéttbýli Austurvegi 2, 860 Hvolsvelli
|
||||||
|
871 Vík Vík, dreifbýli Dreifbýli Austurvegi 2, 860 Hvolsvelli
|
||||||
|
880 Kirkjubæjarklaustri Kirkjubæjarklaustur Þéttbýli Austurvegi 2, 860 Hvolsvelli
|
||||||
|
881 Kirkjubæjarklaustri Kirkjubæjarklaustur, dreifbýli Dreifbýli Austurvegi 2, 860 Hvolsvelli
|
||||||
|
900 Vestmannaeyjum Vestmannaeyjar Þéttbýli Vestmannabraut 22
|
||||||
|
902 Vestmannaeyjum Vestmannaeyjar, pósthólf Pósthólf Vestmannabraut 22, 900 Vestmannaeyjar
|
62
heimaerbest/autofetch.mjs
Normal file
|
@ -0,0 +1,62 @@
|
||||||
|
import Client from './base/media/client.mjs'
|
||||||
|
|
||||||
|
const client = new Client()
|
||||||
|
|
||||||
|
function fetch(page = 1) {
|
||||||
|
return client.get(`http://fasteignir.visir.is/api/search?onpage=100&page=${page}`)
|
||||||
|
.then(data => {
|
||||||
|
return data.map(listing => {
|
||||||
|
if (listing.bathrooms === null) {
|
||||||
|
listing.bathrooms = 0
|
||||||
|
listing.invalid = true
|
||||||
|
}
|
||||||
|
listing.rooms = Number(listing.rooms)
|
||||||
|
listing.bedrooms = Number(listing.bedrooms)
|
||||||
|
listing.bathrooms = Number(listing.bathrooms)
|
||||||
|
listing.zip.zip = Number(listing.zip.zip)
|
||||||
|
listing.price = Number(listing.price)
|
||||||
|
listing.size = listing.size.split(',')[0]
|
||||||
|
|
||||||
|
return listing
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
async function parseAll() {
|
||||||
|
let page = 1
|
||||||
|
let data = []
|
||||||
|
|
||||||
|
while (page <= 10) {
|
||||||
|
console.log(`Fetching page ${page}`)
|
||||||
|
let add = await fetch(page)
|
||||||
|
if (add.length === 0) {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
page += 1
|
||||||
|
data = data.concat(add)
|
||||||
|
|
||||||
|
}
|
||||||
|
console.log(`finished, got total ${data.length} listings`)
|
||||||
|
return data
|
||||||
|
}
|
||||||
|
|
||||||
|
parseAll().then((data) => {
|
||||||
|
for (let i = 0; i < data.length && i < 100; i++) {
|
||||||
|
let listing = data[i]
|
||||||
|
if (listing.invalid || listing.rooms === 0 || listing.zip.zip >= 950 || listing.bathrooms === 0 || listing.price === 0) {
|
||||||
|
let prefix = ''
|
||||||
|
if (listing.invalid && listing.rooms > 0) {
|
||||||
|
prefix += '[Invalid] '
|
||||||
|
}
|
||||||
|
if (listing.bathrooms === 0 && listing.rooms > 0) {
|
||||||
|
prefix += '[No bath] '
|
||||||
|
}
|
||||||
|
if (listing.price === 0) {
|
||||||
|
prefix += '[Tilboð] '
|
||||||
|
}
|
||||||
|
console.log(`${prefix}http://fasteignir.visir.is/property/${listing.id} : ${listing.street_name} ${listing.street_number}, ${listing.zip.zip} (${listing.sale_or_rent}). ${listing.rooms} rooms, ${listing.bedrooms} bedrooms, ${listing.bathrooms} baths. ${listing.size} m2 = ${listing.price} kr.`)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
// http://fasteignir.visir.is/api/search?onpage=1000&page=1&zip=103
|
1
heimaerbest/base
Symbolic link
|
@ -0,0 +1 @@
|
||||||
|
../base
|
8
heimaerbest/build-package.json
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
{
|
||||||
|
"scripts": {
|
||||||
|
"build": "echo done;"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"service-core": "^3.0.0-beta.17"
|
||||||
|
}
|
||||||
|
}
|
|
@ -2,11 +2,11 @@ import fs from 'fs'
|
||||||
import { ServiceCore } from 'service-core'
|
import { ServiceCore } from 'service-core'
|
||||||
import * as index from './index.mjs'
|
import * as index from './index.mjs'
|
||||||
|
|
||||||
var core = new ServiceCore('nfp_moe', import.meta.url, 4030, '')
|
var core = new ServiceCore('nfp_moe', import.meta.url, 4210, '')
|
||||||
|
|
||||||
let config = {
|
let config = {
|
||||||
frontend: {
|
frontend: {
|
||||||
url: 'http://localhost:4030'
|
url: 'http://localhost:4210'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -14,7 +14,7 @@ try {
|
||||||
config = JSON.parse(fs.readFileSync('./config.json'))
|
config = JSON.parse(fs.readFileSync('./config.json'))
|
||||||
} catch {}
|
} catch {}
|
||||||
|
|
||||||
config.port = 4030
|
config.port = 4210
|
||||||
|
|
||||||
core.setConfig(config)
|
core.setConfig(config)
|
||||||
core.init(index).then(function() {
|
core.init(index).then(function() {
|
11
heimaerbest/index.mjs
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
import config from './base/config.mjs'
|
||||||
|
|
||||||
|
export function start(http, port, ctx) {
|
||||||
|
config.sources[1].store = ctx.config
|
||||||
|
|
||||||
|
return import('./api/server.mjs')
|
||||||
|
.then(function(module) {
|
||||||
|
let server = new module.default(http, port, ctx)
|
||||||
|
return server.run()
|
||||||
|
})
|
||||||
|
}
|
62
heimaerbest/package.json
Normal file
|
@ -0,0 +1,62 @@
|
||||||
|
{
|
||||||
|
"name": "heimaerbest",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"port": 4210,
|
||||||
|
"description": "NFP Moe website",
|
||||||
|
"main": "index.js",
|
||||||
|
"directories": {
|
||||||
|
"test": "test"
|
||||||
|
},
|
||||||
|
"scripts": {
|
||||||
|
"start": "node index.mjs",
|
||||||
|
"test": "echo \"Error: no test specified\" && exit 1",
|
||||||
|
"build:prod": "asbundle app/index.js public/assets/app.js",
|
||||||
|
"build": "asbundle app/index.js public/assets/app.js",
|
||||||
|
"dev:build": "npm-watch build",
|
||||||
|
"dev:server": "node dev.mjs | bunyan",
|
||||||
|
"dev": "npm-watch dev:server"
|
||||||
|
},
|
||||||
|
"watch": {
|
||||||
|
"dev:server": {
|
||||||
|
"patterns": [
|
||||||
|
"api/*",
|
||||||
|
"base/*",
|
||||||
|
"../base/*"
|
||||||
|
],
|
||||||
|
"extensions": "js,mjs",
|
||||||
|
"quiet": true,
|
||||||
|
"inherit": true
|
||||||
|
},
|
||||||
|
"build": {
|
||||||
|
"patterns": [
|
||||||
|
"app/*"
|
||||||
|
],
|
||||||
|
"extensions": "js,mjs,scss",
|
||||||
|
"quiet": true,
|
||||||
|
"inherit": true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"repository": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://github.com/nfp-projects/nfp_moe.git"
|
||||||
|
},
|
||||||
|
"author": "Jonatan Nilsson",
|
||||||
|
"license": "WTFPL",
|
||||||
|
"bugs": {
|
||||||
|
"url": "https://github.com/nfp-projects/nfp_moe/issues"
|
||||||
|
},
|
||||||
|
"homepage": "https://github.com/nfp-projects/nfp_moe",
|
||||||
|
"dependencies": {
|
||||||
|
"dot": "^2.0.0-beta.1",
|
||||||
|
"flaska": "^1.3.0",
|
||||||
|
"formidable": "^1.2.6",
|
||||||
|
"msnodesqlv8": "^2.7.0",
|
||||||
|
"nconf-lite": "^2.0.0",
|
||||||
|
"striptags": "^3.2.0"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"asbundle": "^2.6.1",
|
||||||
|
"mithril": "^2.2.2",
|
||||||
|
"service-core": "^3.0.0-beta.17"
|
||||||
|
}
|
||||||
|
}
|
23
heimaerbest/public/assets/app.css
Normal file
4
heimaerbest/public/assets/img/checkmark.svg
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
<svg width="52" height="52" viewBox="0 0 52 52" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<circle cx="26" cy="26" r="26" fill="#EF6C10"/>
|
||||||
|
<path transform="translate(14,17)" d="M0 9L2.4 6.5L8 11L21.6 0.5L24 3L8 18L0 9Z" fill="white"/>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 247 B |
BIN
heimaerbest/public/assets/img/favicon.png
Normal file
After Width: | Height: | Size: 2 KiB |
BIN
heimaerbest/public/assets/img/house_1.avif
Normal file
BIN
heimaerbest/public/assets/img/house_1.png
Normal file
After Width: | Height: | Size: 689 KiB |
BIN
heimaerbest/public/assets/img/house_2.png
Normal file
After Width: | Height: | Size: 799 KiB |
BIN
heimaerbest/public/assets/img/house_3.png
Normal file
After Width: | Height: | Size: 326 KiB |
BIN
heimaerbest/public/assets/img/house__4.png
Normal file
After Width: | Height: | Size: 220 KiB |
12
heimaerbest/public/assets/img/logo.svg
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
<svg width="522" height="123" viewBox="0 0 522 123" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path d="M75.6929 0H25.2379L0.747803 42.9944V82.328L25.335 122.421H75.5377L100.125 82.328V42.9944L75.6929 0ZM84.5226 39.6647H55.4139C54.7333 39.664 54.0648 39.4837 53.4755 39.1419C52.8862 38.8001 52.3971 38.3089 52.0568 37.7174L37.4053 11.9753C37.2403 11.6807 37.1545 11.3481 37.1564 11.0101C37.1583 10.6721 37.2478 10.3404 37.4162 10.0477C37.5846 9.75494 37.826 9.51124 38.1167 9.34052C38.4074 9.1698 38.7374 9.07795 39.0742 9.07401H68.1829C68.8668 9.07128 69.5392 9.24991 70.1322 9.59186C70.7252 9.9338 71.2175 10.4269 71.5594 11.0212L86.2109 36.7633C86.3914 37.0608 86.4893 37.4014 86.494 37.7497C86.4987 38.0979 86.4101 38.4411 86.2377 38.7434C86.0652 39.0457 85.8152 39.296 85.5135 39.4683C85.2118 39.6407 84.8697 39.7286 84.5226 39.7231V39.6647ZM60.9834 85.6772H85.2406C85.4822 85.6787 85.719 85.7448 85.9266 85.8687C86.1343 85.9926 86.3053 86.1698 86.422 86.3821C86.5386 86.5944 86.5968 86.834 86.5904 87.0764C86.584 87.3187 86.5132 87.555 86.3855 87.7608L74.3151 107.447C74.1933 107.644 74.0233 107.807 73.8212 107.92C73.6192 108.033 73.3918 108.092 73.1606 108.092C72.9293 108.092 72.7017 108.033 72.4996 107.92C72.2976 107.807 72.1278 107.644 72.006 107.447L59.916 87.7608C59.7911 87.5605 59.7203 87.3312 59.7104 87.0952C59.7004 86.8592 59.7517 86.6246 59.8594 86.4145C59.967 86.2045 60.1273 86.026 60.3244 85.8968C60.5214 85.7675 60.7484 85.6919 60.9834 85.6772ZM40.3356 39.762H15.3798C15.154 39.7567 14.9333 39.6941 14.7381 39.5801C14.5429 39.4661 14.3796 39.3044 14.2634 39.1101C14.1472 38.9158 14.0818 38.6951 14.0734 38.4686C14.065 38.2422 14.114 38.0172 14.2155 37.8148L26.6739 15.9087C26.7921 15.698 26.964 15.5227 27.1721 15.4006C27.3801 15.2786 27.6169 15.2143 27.8578 15.2143C28.0988 15.2143 28.3354 15.2786 28.5434 15.4006C28.7514 15.5227 28.9234 15.698 29.0416 15.9087L41.4999 37.8148C41.5928 38.0157 41.6348 38.2365 41.6222 38.4575C41.6095 38.6786 41.5427 38.8931 41.4274 39.082C41.3122 39.2709 41.1522 39.4282 40.9617 39.5401C40.7712 39.6519 40.5562 39.7148 40.3356 39.7231V39.762ZM45.9439 50.7832V74.656C45.9439 75.1725 45.7396 75.6677 45.3756 76.0329C45.0117 76.3981 44.518 76.6032 44.0033 76.6032H11.7315C11.2169 76.6032 10.7232 76.3981 10.3592 76.0329C9.99532 75.6677 9.79096 75.1725 9.79096 74.656V50.7443C9.79096 50.2279 9.99532 49.7326 10.3592 49.3674C10.7232 49.0022 11.2169 48.7971 11.7315 48.7971H44.0033C44.518 48.7971 45.0117 49.0022 45.3756 49.3674C45.7396 49.7326 45.9439 50.2279 45.9439 50.7443V50.7832ZM54.9871 74.656V50.7443C54.9871 50.2279 55.1914 49.7326 55.5554 49.3674C55.9193 49.0022 56.413 48.7971 56.9277 48.7971H89.1218C89.6364 48.7971 90.1301 49.0022 90.494 49.3674C90.858 49.7326 91.0623 50.2279 91.0623 50.7443V74.6171C91.0623 75.1335 90.858 75.6288 90.494 75.994C90.1301 76.3591 89.6364 76.5643 89.1218 76.5643H56.85C56.3489 76.5442 55.875 76.3302 55.5276 75.9672C55.1803 75.6041 54.9867 75.1203 54.9871 74.6171V74.656ZM16.8353 85.6772H45.7693C46.4433 85.6847 47.1036 85.8682 47.6855 86.2096C48.2674 86.5511 48.7507 87.0387 49.0876 87.6245L63.1568 110.582C63.3356 110.877 63.4331 111.214 63.4394 111.559C63.4458 111.904 63.3609 112.244 63.1931 112.545C63.0253 112.846 62.7807 113.097 62.4845 113.272C62.1884 113.447 61.8512 113.54 61.5074 113.542H32.5733C31.8993 113.534 31.2388 113.351 30.6569 113.009C30.075 112.668 29.592 112.18 29.255 111.595L15.1858 88.637C15.007 88.3424 14.9093 88.0053 14.903 87.6604C14.8966 87.3155 14.9818 86.9751 15.1496 86.674C15.3174 86.373 15.5619 86.122 15.8581 85.9469C16.1543 85.7717 16.4915 85.6787 16.8353 85.6772Z" fill="#EF6C10"/>
|
||||||
|
<path d="M182.485 60.1604V63.6044H149.305C150.145 70.9964 153.785 74.6924 160.225 74.6924C162.689 74.6924 164.705 74.1604 166.273 73.0964C167.897 71.9764 168.961 70.4644 169.465 68.5604H181.225V70.2404C180.161 74.3284 177.725 77.6044 173.917 80.0684C170.109 82.5324 165.545 83.7644 160.225 83.7644C153.225 83.7644 147.625 81.6364 143.425 77.3804C139.225 73.0684 137.125 67.3284 137.125 60.1604C137.125 53.2164 139.225 47.6444 143.425 43.4444C147.681 39.1884 153.281 37.0604 160.225 37.0604C166.889 37.0604 172.265 39.1884 176.353 43.4444C180.441 47.7004 182.485 53.2724 182.485 60.1604ZM149.641 54.8684H170.389C169.941 52.1244 168.821 49.9964 167.029 48.4844C165.293 46.9723 163.025 46.2164 160.225 46.2164C154.457 46.2164 150.929 49.1003 149.641 54.8684Z" fill="black"/>
|
||||||
|
<path d="M188.959 31.1804V19.7563H200.887V31.1804H188.959ZM188.959 82.7564V38.0684H200.887V82.7564H188.959Z" fill="black"/>
|
||||||
|
<path d="M231.542 102.664C224.934 102.664 219.614 101.124 215.582 98.0444C211.55 94.9644 209.534 90.8764 209.534 85.7803H221.462C221.462 88.2443 222.33 90.1204 224.066 91.4084C225.858 92.7524 228.35 93.4244 231.542 93.4244C238.262 93.4244 241.622 90.3444 241.622 84.1843V81.9164L241.958 76.2044H241.454C240.222 78.3884 238.402 80.0964 235.994 81.3284C233.586 82.5044 230.842 83.0924 227.762 83.0924C221.602 83.0924 216.674 80.9924 212.978 76.7924C209.282 72.5364 207.434 66.8524 207.434 59.7404C207.434 52.9644 209.338 47.4764 213.146 43.2764C217.01 39.0204 222.05 36.8924 228.266 36.8924C234.874 36.8924 239.466 39.4964 242.042 44.7044H242.546V38.0684H253.55V83.4284C253.55 89.5323 251.618 94.2644 247.754 97.6244C243.946 100.984 238.542 102.664 231.542 102.664ZM230.534 73.0124C234.006 73.0124 236.778 71.8084 238.85 69.4004C240.978 66.9924 242.042 63.7724 242.042 59.7404C242.042 55.9324 241.006 52.8524 238.934 50.5004C236.918 48.1484 234.23 46.9724 230.87 46.9724C227.286 46.9724 224.486 48.0924 222.47 50.3324C220.51 52.5723 219.53 55.7084 219.53 59.7404C219.53 64.0524 220.482 67.3563 222.386 69.6524C224.29 71.8924 227.006 73.0124 230.534 73.0124Z" fill="black"/>
|
||||||
|
<path d="M262.219 82.7564V38.0684H273.307V44.7044H273.727C277.031 39.4964 281.987 36.8924 288.595 36.8924C293.915 36.8924 298.003 38.5444 300.859 41.8484C303.771 45.1523 305.227 49.8004 305.227 55.7924V82.7564H293.299V56.2124C293.299 49.9404 290.611 46.8043 285.235 46.8043C281.707 46.8043 278.963 48.0364 277.003 50.5004C275.099 52.9084 274.147 56.2684 274.147 60.5804V82.7564H262.219Z" fill="black"/>
|
||||||
|
<path d="M327.008 83.7644C321.8 83.7644 317.768 82.7004 314.912 80.5724C312.056 78.3884 310.628 75.3363 310.628 71.4164C310.628 67.0484 311.972 63.7164 314.66 61.4204C317.348 59.1244 321.632 57.6404 327.512 56.9684C333.28 56.3524 336.976 55.7364 338.6 55.1203C340.224 54.5044 341.036 53.2724 341.036 51.4244C341.036 47.8964 338.376 46.1324 333.056 46.1324C330.312 46.1324 328.268 46.6364 326.924 47.6444C325.58 48.5964 324.908 50.1364 324.908 52.2644H312.98C312.98 47.3923 314.716 43.6404 318.188 41.0084C321.66 38.3764 326.588 37.0604 332.972 37.0604C339.3 37.0604 344.2 38.3484 347.672 40.9244C351.2 43.5004 352.964 47.1124 352.964 51.7604V74.9444C352.964 76.1763 353.048 77.3244 353.216 78.3884C353.384 79.4524 353.58 80.1804 353.804 80.5724L354.056 81.2444V82.7564H343.388C342.436 81.5244 341.96 79.7324 341.96 77.3804V77.2124H341.456C340.28 79.2844 338.432 80.9084 335.912 82.0844C333.392 83.2044 330.424 83.7644 327.008 83.7644ZM328.94 75.4484C332.524 75.4484 335.436 74.3284 337.676 72.0884C339.916 69.8484 341.036 66.9084 341.036 63.2684V61.7564H340.532C339.02 63.2684 335.66 64.2764 330.452 64.7803C327.652 65.1164 325.664 65.7324 324.488 66.6284C323.312 67.4684 322.724 68.8404 322.724 70.7444C322.724 73.8804 324.796 75.4484 328.94 75.4484Z" fill="black"/>
|
||||||
|
<path d="M370.455 82.7564L353.571 39.5804V38.0684H366.423L376.503 66.7124H376.671L387.003 38.0684H399.603V39.5804L382.551 82.7564H370.455Z" fill="black"/>
|
||||||
|
<path d="M414.542 83.7644C409.334 83.7644 405.302 82.7004 402.446 80.5724C399.59 78.3884 398.162 75.3363 398.162 71.4164C398.162 67.0484 399.506 63.7164 402.194 61.4204C404.882 59.1244 409.166 57.6404 415.046 56.9684C420.814 56.3524 424.51 55.7364 426.134 55.1203C427.758 54.5044 428.57 53.2724 428.57 51.4244C428.57 47.8964 425.91 46.1324 420.59 46.1324C417.846 46.1324 415.802 46.6364 414.458 47.6444C413.114 48.5964 412.442 50.1364 412.442 52.2644H400.514C400.514 47.3923 402.25 43.6404 405.722 41.0084C409.194 38.3764 414.122 37.0604 420.506 37.0604C426.834 37.0604 431.734 38.3484 435.206 40.9244C438.734 43.5004 440.498 47.1124 440.498 51.7604V74.9444C440.498 76.1763 440.582 77.3244 440.75 78.3884C440.918 79.4524 441.114 80.1804 441.338 80.5724L441.59 81.2444V82.7564H430.922C429.97 81.5244 429.494 79.7324 429.494 77.3804V77.2124H428.99C427.814 79.2844 425.966 80.9084 423.446 82.0844C420.926 83.2044 417.958 83.7644 414.542 83.7644ZM416.474 75.4484C420.058 75.4484 422.97 74.3284 425.21 72.0884C427.45 69.8484 428.57 66.9084 428.57 63.2684V61.7564H428.066C426.554 63.2684 423.194 64.2764 417.986 64.7803C415.186 65.1164 413.198 65.7324 412.022 66.6284C410.846 67.4684 410.258 68.8404 410.258 70.7444C410.258 73.8804 412.33 75.4484 416.474 75.4484Z" fill="black"/>
|
||||||
|
<path d="M475.489 55.7924L491.701 81.2444V82.7564H478.849L467.593 64.0244L460.453 71.7523V82.7564H448.525V19.7563H460.453V57.6404L476.497 38.0684H489.937V39.5804L475.489 55.7924Z" fill="black"/>
|
||||||
|
<path d="M521.252 48.3164H509.408V67.1324C509.408 69.6524 509.828 71.3604 510.668 72.2564C511.564 73.0964 513.3 73.5164 515.876 73.5164H521.252V83.0084C519.572 83.6244 517.192 83.9324 514.112 83.9324C508.512 83.9324 504.34 82.5604 501.596 79.8164C498.852 77.0724 497.48 72.9004 497.48 67.3004V48.3164H490.256V38.0684H497.48V27.1483H509.408V38.0684H521.252V48.3164Z" fill="black"/>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 9.2 KiB |
12
heimaerbest/public/assets/img/logo_white.svg
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
<svg width="522" height="123" viewBox="0 0 522 123" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path d="M75.6929 0H25.2379L0.747803 42.9944V82.328L25.335 122.421H75.5377L100.125 82.328V42.9944L75.6929 0ZM84.5226 39.6647H55.4139C54.7333 39.664 54.0648 39.4837 53.4755 39.1419C52.8862 38.8001 52.3971 38.3089 52.0568 37.7174L37.4053 11.9753C37.2403 11.6807 37.1545 11.3481 37.1564 11.0101C37.1583 10.6721 37.2478 10.3404 37.4162 10.0477C37.5846 9.75494 37.826 9.51124 38.1167 9.34052C38.4074 9.1698 38.7374 9.07795 39.0742 9.07401H68.1829C68.8668 9.07128 69.5392 9.24991 70.1322 9.59186C70.7252 9.9338 71.2175 10.4269 71.5594 11.0212L86.2109 36.7633C86.3914 37.0608 86.4893 37.4014 86.494 37.7497C86.4987 38.0979 86.4101 38.4411 86.2377 38.7434C86.0652 39.0457 85.8152 39.296 85.5135 39.4683C85.2118 39.6407 84.8697 39.7286 84.5226 39.7231V39.6647ZM60.9834 85.6772H85.2406C85.4822 85.6787 85.719 85.7448 85.9266 85.8687C86.1343 85.9926 86.3053 86.1698 86.422 86.3821C86.5386 86.5944 86.5968 86.834 86.5904 87.0764C86.584 87.3187 86.5132 87.555 86.3855 87.7608L74.3151 107.447C74.1933 107.644 74.0233 107.807 73.8212 107.92C73.6192 108.033 73.3918 108.092 73.1606 108.092C72.9293 108.092 72.7017 108.033 72.4996 107.92C72.2976 107.807 72.1278 107.644 72.006 107.447L59.916 87.7608C59.7911 87.5605 59.7203 87.3312 59.7104 87.0952C59.7004 86.8592 59.7517 86.6246 59.8594 86.4145C59.967 86.2045 60.1273 86.026 60.3244 85.8968C60.5214 85.7675 60.7484 85.6919 60.9834 85.6772ZM40.3356 39.762H15.3798C15.154 39.7567 14.9333 39.6941 14.7381 39.5801C14.5429 39.4661 14.3796 39.3044 14.2634 39.1101C14.1472 38.9158 14.0818 38.6951 14.0734 38.4686C14.065 38.2422 14.114 38.0172 14.2155 37.8148L26.6739 15.9087C26.7921 15.698 26.964 15.5227 27.1721 15.4006C27.3801 15.2786 27.6169 15.2143 27.8578 15.2143C28.0988 15.2143 28.3354 15.2786 28.5434 15.4006C28.7514 15.5227 28.9234 15.698 29.0416 15.9087L41.4999 37.8148C41.5928 38.0157 41.6348 38.2365 41.6222 38.4575C41.6095 38.6786 41.5427 38.8931 41.4274 39.082C41.3122 39.2709 41.1522 39.4282 40.9617 39.5401C40.7712 39.6519 40.5562 39.7148 40.3356 39.7231V39.762ZM45.9439 50.7832V74.656C45.9439 75.1725 45.7396 75.6677 45.3756 76.0329C45.0117 76.3981 44.518 76.6032 44.0033 76.6032H11.7315C11.2169 76.6032 10.7232 76.3981 10.3592 76.0329C9.99532 75.6677 9.79096 75.1725 9.79096 74.656V50.7443C9.79096 50.2279 9.99532 49.7326 10.3592 49.3674C10.7232 49.0022 11.2169 48.7971 11.7315 48.7971H44.0033C44.518 48.7971 45.0117 49.0022 45.3756 49.3674C45.7396 49.7326 45.9439 50.2279 45.9439 50.7443V50.7832ZM54.9871 74.656V50.7443C54.9871 50.2279 55.1914 49.7326 55.5554 49.3674C55.9193 49.0022 56.413 48.7971 56.9277 48.7971H89.1218C89.6364 48.7971 90.1301 49.0022 90.494 49.3674C90.858 49.7326 91.0623 50.2279 91.0623 50.7443V74.6171C91.0623 75.1335 90.858 75.6288 90.494 75.994C90.1301 76.3591 89.6364 76.5643 89.1218 76.5643H56.85C56.3489 76.5442 55.875 76.3302 55.5276 75.9672C55.1803 75.6041 54.9867 75.1203 54.9871 74.6171V74.656ZM16.8353 85.6772H45.7693C46.4433 85.6847 47.1036 85.8682 47.6855 86.2096C48.2674 86.5511 48.7507 87.0387 49.0876 87.6245L63.1568 110.582C63.3356 110.877 63.4331 111.214 63.4394 111.559C63.4458 111.904 63.3609 112.244 63.1931 112.545C63.0253 112.846 62.7807 113.097 62.4845 113.272C62.1884 113.447 61.8512 113.54 61.5074 113.542H32.5733C31.8993 113.534 31.2388 113.351 30.6569 113.009C30.075 112.668 29.592 112.18 29.255 111.595L15.1858 88.637C15.007 88.3424 14.9093 88.0053 14.903 87.6604C14.8966 87.3155 14.9818 86.9751 15.1496 86.674C15.3174 86.373 15.5619 86.122 15.8581 85.9469C16.1543 85.7717 16.4915 85.6787 16.8353 85.6772Z" fill="white"/>
|
||||||
|
<path d="M182.485 60.1604V63.6044H149.305C150.145 70.9964 153.785 74.6924 160.225 74.6924C162.689 74.6924 164.705 74.1604 166.273 73.0964C167.897 71.9764 168.961 70.4644 169.465 68.5604H181.225V70.2404C180.161 74.3284 177.725 77.6044 173.917 80.0684C170.109 82.5324 165.545 83.7644 160.225 83.7644C153.225 83.7644 147.625 81.6364 143.425 77.3804C139.225 73.0684 137.125 67.3284 137.125 60.1604C137.125 53.2164 139.225 47.6444 143.425 43.4444C147.681 39.1884 153.281 37.0604 160.225 37.0604C166.889 37.0604 172.265 39.1884 176.353 43.4444C180.441 47.7004 182.485 53.2724 182.485 60.1604ZM149.641 54.8684H170.389C169.941 52.1244 168.821 49.9964 167.029 48.4844C165.293 46.9723 163.025 46.2164 160.225 46.2164C154.457 46.2164 150.929 49.1003 149.641 54.8684Z" fill="white"/>
|
||||||
|
<path d="M188.959 31.1804V19.7563H200.887V31.1804H188.959ZM188.959 82.7564V38.0684H200.887V82.7564H188.959Z" fill="white"/>
|
||||||
|
<path d="M231.542 102.664C224.934 102.664 219.614 101.124 215.582 98.0444C211.55 94.9644 209.534 90.8764 209.534 85.7803H221.462C221.462 88.2443 222.33 90.1204 224.066 91.4084C225.858 92.7524 228.35 93.4244 231.542 93.4244C238.262 93.4244 241.622 90.3444 241.622 84.1843V81.9164L241.958 76.2044H241.454C240.222 78.3884 238.402 80.0964 235.994 81.3284C233.586 82.5044 230.842 83.0924 227.762 83.0924C221.602 83.0924 216.674 80.9924 212.978 76.7924C209.282 72.5364 207.434 66.8524 207.434 59.7404C207.434 52.9644 209.338 47.4764 213.146 43.2764C217.01 39.0204 222.05 36.8924 228.266 36.8924C234.874 36.8924 239.466 39.4964 242.042 44.7044H242.546V38.0684H253.55V83.4284C253.55 89.5323 251.618 94.2644 247.754 97.6244C243.946 100.984 238.542 102.664 231.542 102.664ZM230.534 73.0124C234.006 73.0124 236.778 71.8084 238.85 69.4004C240.978 66.9924 242.042 63.7724 242.042 59.7404C242.042 55.9324 241.006 52.8524 238.934 50.5004C236.918 48.1484 234.23 46.9724 230.87 46.9724C227.286 46.9724 224.486 48.0924 222.47 50.3324C220.51 52.5723 219.53 55.7084 219.53 59.7404C219.53 64.0524 220.482 67.3563 222.386 69.6524C224.29 71.8924 227.006 73.0124 230.534 73.0124Z" fill="white"/>
|
||||||
|
<path d="M262.219 82.7564V38.0684H273.307V44.7044H273.727C277.031 39.4964 281.987 36.8924 288.595 36.8924C293.915 36.8924 298.003 38.5444 300.859 41.8484C303.771 45.1523 305.227 49.8004 305.227 55.7924V82.7564H293.299V56.2124C293.299 49.9404 290.611 46.8043 285.235 46.8043C281.707 46.8043 278.963 48.0364 277.003 50.5004C275.099 52.9084 274.147 56.2684 274.147 60.5804V82.7564H262.219Z" fill="white"/>
|
||||||
|
<path d="M327.008 83.7644C321.8 83.7644 317.768 82.7004 314.912 80.5724C312.056 78.3884 310.628 75.3363 310.628 71.4164C310.628 67.0484 311.972 63.7164 314.66 61.4204C317.348 59.1244 321.632 57.6404 327.512 56.9684C333.28 56.3524 336.976 55.7364 338.6 55.1203C340.224 54.5044 341.036 53.2724 341.036 51.4244C341.036 47.8964 338.376 46.1324 333.056 46.1324C330.312 46.1324 328.268 46.6364 326.924 47.6444C325.58 48.5964 324.908 50.1364 324.908 52.2644H312.98C312.98 47.3923 314.716 43.6404 318.188 41.0084C321.66 38.3764 326.588 37.0604 332.972 37.0604C339.3 37.0604 344.2 38.3484 347.672 40.9244C351.2 43.5004 352.964 47.1124 352.964 51.7604V74.9444C352.964 76.1763 353.048 77.3244 353.216 78.3884C353.384 79.4524 353.58 80.1804 353.804 80.5724L354.056 81.2444V82.7564H343.388C342.436 81.5244 341.96 79.7324 341.96 77.3804V77.2124H341.456C340.28 79.2844 338.432 80.9084 335.912 82.0844C333.392 83.2044 330.424 83.7644 327.008 83.7644ZM328.94 75.4484C332.524 75.4484 335.436 74.3284 337.676 72.0884C339.916 69.8484 341.036 66.9084 341.036 63.2684V61.7564H340.532C339.02 63.2684 335.66 64.2764 330.452 64.7803C327.652 65.1164 325.664 65.7324 324.488 66.6284C323.312 67.4684 322.724 68.8404 322.724 70.7444C322.724 73.8804 324.796 75.4484 328.94 75.4484Z" fill="white"/>
|
||||||
|
<path d="M370.455 82.7564L353.571 39.5804V38.0684H366.423L376.503 66.7124H376.671L387.003 38.0684H399.603V39.5804L382.551 82.7564H370.455Z" fill="white"/>
|
||||||
|
<path d="M414.542 83.7644C409.334 83.7644 405.302 82.7004 402.446 80.5724C399.59 78.3884 398.162 75.3363 398.162 71.4164C398.162 67.0484 399.506 63.7164 402.194 61.4204C404.882 59.1244 409.166 57.6404 415.046 56.9684C420.814 56.3524 424.51 55.7364 426.134 55.1203C427.758 54.5044 428.57 53.2724 428.57 51.4244C428.57 47.8964 425.91 46.1324 420.59 46.1324C417.846 46.1324 415.802 46.6364 414.458 47.6444C413.114 48.5964 412.442 50.1364 412.442 52.2644H400.514C400.514 47.3923 402.25 43.6404 405.722 41.0084C409.194 38.3764 414.122 37.0604 420.506 37.0604C426.834 37.0604 431.734 38.3484 435.206 40.9244C438.734 43.5004 440.498 47.1124 440.498 51.7604V74.9444C440.498 76.1763 440.582 77.3244 440.75 78.3884C440.918 79.4524 441.114 80.1804 441.338 80.5724L441.59 81.2444V82.7564H430.922C429.97 81.5244 429.494 79.7324 429.494 77.3804V77.2124H428.99C427.814 79.2844 425.966 80.9084 423.446 82.0844C420.926 83.2044 417.958 83.7644 414.542 83.7644ZM416.474 75.4484C420.058 75.4484 422.97 74.3284 425.21 72.0884C427.45 69.8484 428.57 66.9084 428.57 63.2684V61.7564H428.066C426.554 63.2684 423.194 64.2764 417.986 64.7803C415.186 65.1164 413.198 65.7324 412.022 66.6284C410.846 67.4684 410.258 68.8404 410.258 70.7444C410.258 73.8804 412.33 75.4484 416.474 75.4484Z" fill="white"/>
|
||||||
|
<path d="M475.489 55.7924L491.701 81.2444V82.7564H478.849L467.593 64.0244L460.453 71.7523V82.7564H448.525V19.7563H460.453V57.6404L476.497 38.0684H489.937V39.5804L475.489 55.7924Z" fill="white"/>
|
||||||
|
<path d="M521.252 48.3164H509.408V67.1324C509.408 69.6524 509.828 71.3604 510.668 72.2564C511.564 73.0964 513.3 73.5164 515.876 73.5164H521.252V83.0084C519.572 83.6244 517.192 83.9324 514.112 83.9324C508.512 83.9324 504.34 82.5604 501.596 79.8164C498.852 77.0724 497.48 72.9004 497.48 67.3004V48.3164H490.256V38.0684H497.48V27.1483H509.408V38.0684H521.252V48.3164Z" fill="white"/>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 9.2 KiB |
3
heimaerbest/public/assets/img/orange_circle_plus.svg
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
<svg width="24" height="25" viewBox="0 0 24 25" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path fill-rule="evenodd" clip-rule="evenodd" d="M24 12.9412C24 19.5686 18.6274 24.9412 12 24.9412C5.37259 24.9412 0 19.5686 0 12.9412C0 6.31375 5.37259 0.941162 12 0.941162C18.6274 0.941162 24 6.31375 24 12.9412ZM12 6.51259C12.7101 6.51259 13.2857 7.08823 13.2857 7.79831V11.6554H17.1429C17.8529 11.6554 18.4286 12.2311 18.4286 12.9412C18.4286 13.6512 17.8529 14.2269 17.1429 14.2269H13.2857V18.084C13.2857 18.7941 12.7101 19.3697 12 19.3697C11.2899 19.3697 10.7143 18.7941 10.7143 18.084V14.2269H6.85714C6.14707 14.2269 5.57143 13.6512 5.57143 12.9412C5.57143 12.2311 6.14707 11.6554 6.85714 11.6554H10.7143V7.79831C10.7143 7.08823 11.2899 6.51259 12 6.51259Z" fill="#EF6C10"/>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 783 B |
3
heimaerbest/public/assets/img/remove.svg
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
<svg width="25" height="25" viewBox="0 0 25 25" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path fill-rule="evenodd" clip-rule="evenodd" d="M20.5146 21.4558C15.8283 26.1421 8.23031 26.1421 3.54402 21.4558C-1.14227 16.7696 -1.14228 9.17158 3.54402 4.48528C8.2303 -0.201006 15.8283 -0.201006 20.5146 4.48528C25.2009 9.17158 25.2009 16.7696 20.5146 21.4558ZM16.575 8.42488C17.0771 8.92697 17.0771 9.74105 16.575 10.2432L13.8476 12.9706L16.575 15.698C17.0771 16.2001 17.0771 17.0142 16.575 17.5162C16.0729 18.0183 15.2588 18.0183 14.7567 17.5162L12.0293 14.7888L9.30189 17.5162C8.7998 18.0183 7.98571 18.0183 7.48361 17.5162C6.98151 17.0142 6.98153 16.2001 7.48361 15.698L10.211 12.9706L7.48361 10.2432C6.98151 9.74105 6.98151 8.92697 7.48361 8.42488C7.98571 7.92278 8.79979 7.92278 9.30189 8.42488L12.0293 11.1523L14.7567 8.42488C15.2588 7.92278 16.0729 7.92278 16.575 8.42488Z" fill="#EF6C10"/>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 905 B |
627
heimaerbest/public/index.html
Normal file
|
@ -0,0 +1,627 @@
|
||||||
|
<!doctype html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<title>{{=headerTitle}}</title>
|
||||||
|
<base href="/">
|
||||||
|
<meta name="description" content="{{=headerDescription}}">
|
||||||
|
<meta name="twitter:card" value="summary">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
|
<meta property="og:type" content="website" />
|
||||||
|
<meta property="og:url" content="{{=headerUrl}}" />
|
||||||
|
<meta property="og:image" content="{{=headerImage}}" />
|
||||||
|
<meta property="og:description" content="{{=headerDescription}}" />
|
||||||
|
<meta property="og:title" content="{{=headerTitle}}" />
|
||||||
|
|
||||||
|
<link rel="icon" type="image/png" href="/assets/img/favicon.png">
|
||||||
|
<link rel="preconnect" href="https://eignavakt.is" />
|
||||||
|
<style>
|
||||||
|
/*
|
||||||
|
===================== Variables =====================
|
||||||
|
*/
|
||||||
|
:root {
|
||||||
|
--bg: #E5E5E5;
|
||||||
|
--content-max-width: 1280px;
|
||||||
|
--accent-bg: #EF6C10;
|
||||||
|
--accent-fg: white;
|
||||||
|
--main-bg: white;
|
||||||
|
--main-fg: black;
|
||||||
|
--main-fg-light: #919191;
|
||||||
|
--main-accent: #EF6C10;
|
||||||
|
--main-accent-fg: white;
|
||||||
|
--menu-bg: var(--bg);
|
||||||
|
--menu-fg: black;
|
||||||
|
--form-border: 1px solid #e4e4e4;
|
||||||
|
--form-inactive-bg: #C4C4C4;
|
||||||
|
--form-inactive-fg: white;
|
||||||
|
--form-radius: 4px;
|
||||||
|
--form-input-padding: 0.5rem 0.5rem;
|
||||||
|
--form-button-padding: 0.5rem 2rem;
|
||||||
|
--section-blue-bg: #5465FF;
|
||||||
|
--section-blue-fg: white;
|
||||||
|
--section-radius: 25px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nightmode {
|
||||||
|
--content-max-width: 1280px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
===================== Reset =====================
|
||||||
|
*/
|
||||||
|
|
||||||
|
/* Box sizing rules */
|
||||||
|
*, *::before, *::after { box-sizing: border-box; }
|
||||||
|
|
||||||
|
/* Remove default margin */
|
||||||
|
body, h1, h2, h3, h4, p, figure, blockquote, dl, dd {
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
[hidden] { display: none !important; }
|
||||||
|
|
||||||
|
body {
|
||||||
|
min-height: 100vh;
|
||||||
|
text-rendering: optimizeSpeed;
|
||||||
|
line-height: 1.5;
|
||||||
|
font-size: 16px;
|
||||||
|
font-family: 'Inter var', 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
|
||||||
|
font-variation-settings: "slnt" 0;
|
||||||
|
font-feature-settings: "case", "frac", "tnum", "ss02", "calt", "ccmp", "kern";
|
||||||
|
background: var(--bg);
|
||||||
|
color: var(--main-fg);
|
||||||
|
width: 100%;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
padding: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.italic { font-variation-settings: "slnt" 10deg; }
|
||||||
|
|
||||||
|
a {
|
||||||
|
text-decoration-skip-ink: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
img {
|
||||||
|
max-width: 100%;
|
||||||
|
margin: 0 auto;
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
input, button, textarea, select {
|
||||||
|
font: inherit;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (prefers-reduced-motion: reduce) {
|
||||||
|
*, *::before, *::after {
|
||||||
|
animation-play-state: paused !important;
|
||||||
|
transition: none !important;
|
||||||
|
scroll-behavior: auto !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
h1, h2, h3, h4, h5 {
|
||||||
|
line-height: 120%;
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
h1 {
|
||||||
|
font-size: 2.488rem;
|
||||||
|
}
|
||||||
|
h2 {
|
||||||
|
font-size: 2.074rem;
|
||||||
|
}
|
||||||
|
h3 {
|
||||||
|
font-size: 1.728rem;
|
||||||
|
}
|
||||||
|
h4 {
|
||||||
|
font-size: 1.44rem;
|
||||||
|
}
|
||||||
|
h5 {
|
||||||
|
font-size: 1.0rem;
|
||||||
|
font-weight: 700;
|
||||||
|
margin: 1rem 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
a, a:visited, button {
|
||||||
|
text-decoration: none;
|
||||||
|
border: none;
|
||||||
|
padding: 0;
|
||||||
|
margin: 0;
|
||||||
|
cursor: pointer;
|
||||||
|
background: transparent;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.filler {
|
||||||
|
flex-grow: 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
header, main, footer {
|
||||||
|
width: 100%;
|
||||||
|
max-width: var(--content-max-width);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
.button-flat,
|
||||||
|
.button-outline,
|
||||||
|
.button-inactive,
|
||||||
|
.button-active,
|
||||||
|
.button-white {
|
||||||
|
border-radius: var(--form-radius);
|
||||||
|
padding: 0.9rem 2rem;
|
||||||
|
display: inline-block;
|
||||||
|
align-self: flex-start;
|
||||||
|
border: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.button-flat {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
color: #909090;
|
||||||
|
background: transparent;
|
||||||
|
padding: 0.9rem 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.button-outline {
|
||||||
|
background: transparent;
|
||||||
|
border: var(--form-border);
|
||||||
|
color: var(--main-fg-light);
|
||||||
|
}
|
||||||
|
|
||||||
|
.button-active {
|
||||||
|
background: var(--main-accent);
|
||||||
|
color: var(--main-accent-fg);
|
||||||
|
}
|
||||||
|
|
||||||
|
.button-inactive {
|
||||||
|
background: var(--form-inactive-bg);
|
||||||
|
color: var(--form-inactive-fg);
|
||||||
|
}
|
||||||
|
|
||||||
|
.button-white {
|
||||||
|
background: var(--section-blue-fg);
|
||||||
|
color: var(--section-blue-bg);
|
||||||
|
}
|
||||||
|
|
||||||
|
.button-flat i,
|
||||||
|
.button-outline i,
|
||||||
|
.button-inactive i,
|
||||||
|
.button-active i,
|
||||||
|
.button-white i {
|
||||||
|
margin-right: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ---------------- header ---------------- */
|
||||||
|
|
||||||
|
header {
|
||||||
|
display: flex;
|
||||||
|
min-height: 127px;
|
||||||
|
align-items: center;
|
||||||
|
background: var(--menu-bg);
|
||||||
|
color: var(--menu-fg);
|
||||||
|
flex-wrap: wrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
header .logo {
|
||||||
|
height: 45px;
|
||||||
|
width: auto;
|
||||||
|
margin-right: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
header a {
|
||||||
|
color: var(--menu-fg);
|
||||||
|
font-weight: 350;
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
header .links {
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
|
||||||
|
header .button {
|
||||||
|
background: var(--accent-bg);
|
||||||
|
color: var(--accent-fg);
|
||||||
|
}
|
||||||
|
|
||||||
|
header .links a {
|
||||||
|
padding: 1rem;
|
||||||
|
}
|
||||||
|
header .links a.button {
|
||||||
|
padding: 1rem 2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ---------------- main ---------------- */
|
||||||
|
/* ---------------- top ---------------- */
|
||||||
|
|
||||||
|
section.title {
|
||||||
|
margin: 0 0 5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
section.title .under {
|
||||||
|
display: flex;
|
||||||
|
border-radius: var(--section-radius);
|
||||||
|
background: var(--accent-bg);
|
||||||
|
color: var(--accent-fg);
|
||||||
|
}
|
||||||
|
|
||||||
|
section.title .under .text {
|
||||||
|
padding: 3rem 3rem 14rem;
|
||||||
|
max-width: 580px;
|
||||||
|
}
|
||||||
|
|
||||||
|
section.title .under .text p {
|
||||||
|
padding: 1rem 0 2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
section.title .under .house1 {
|
||||||
|
flex: 2 1 670px;
|
||||||
|
background: url('/assets/img/house_1.png') right bottom no-repeat;
|
||||||
|
background-size: contain;
|
||||||
|
border-radius: 0 0 var(--section-radius) 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
section.title .form {
|
||||||
|
border-radius: var(--section-radius);
|
||||||
|
background: var(--main-bg);
|
||||||
|
color: var(--main-fg);
|
||||||
|
padding: 0.5rem 1rem 3rem 2rem;
|
||||||
|
margin: -12rem 1rem 0 3rem;
|
||||||
|
max-width: 650px;
|
||||||
|
position: relative;
|
||||||
|
z-index: 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
section.title .remove-item {
|
||||||
|
height: 2.5rem;
|
||||||
|
width: 2.5rem;
|
||||||
|
background: url('/assets/img/remove.svg') center no-repeat;
|
||||||
|
background-size: 1.5rem auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
section.title .form h5 {
|
||||||
|
color: var(--main-accent);
|
||||||
|
margin: 2rem 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ---------------- tips ---------------- */
|
||||||
|
|
||||||
|
section.tips {
|
||||||
|
display: flex;
|
||||||
|
margin: 3rem 3rem 6rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
section.tips .image {
|
||||||
|
flex: 1 1 100px;
|
||||||
|
background: url('/assets/img/house_2.png') center no-repeat;
|
||||||
|
background-size: cover;
|
||||||
|
border-radius: var(--section-radius);
|
||||||
|
}
|
||||||
|
|
||||||
|
section.tips .content {
|
||||||
|
flex: 1 1 100px;
|
||||||
|
padding: 3rem 0rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
section.tips .space {
|
||||||
|
flex: 0 0 100px;
|
||||||
|
}
|
||||||
|
|
||||||
|
section.tips h2 {
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
section.tips p {
|
||||||
|
font-size: 0.8rem;
|
||||||
|
font-weight: 350;
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
section.tips .checkmark {
|
||||||
|
background: url('/assets/img/checkmark.svg') left top no-repeat;
|
||||||
|
background-size: auto 52px;
|
||||||
|
min-height: 52px;
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
padding: 0.8rem 0 0 5rem;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* ---------------- pricemat ---------------- */
|
||||||
|
|
||||||
|
section.pricemat {
|
||||||
|
display: flex;
|
||||||
|
margin: 3rem 3rem 6rem;
|
||||||
|
background: var(--section-blue-bg);
|
||||||
|
color: var(--section-blue-fg);
|
||||||
|
border-radius: var(--section-radius);
|
||||||
|
}
|
||||||
|
|
||||||
|
section.pricemat .image {
|
||||||
|
margin-top: 2rem;
|
||||||
|
flex: 1 1 100px;
|
||||||
|
background: url('/assets/img/house_3.png') center top no-repeat;
|
||||||
|
background-size: cover;
|
||||||
|
border-radius: 0 0 var(--section-radius) 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
section.pricemat a.link {
|
||||||
|
font-weight: 800;
|
||||||
|
font-size: 0.6rem;
|
||||||
|
text-transform: uppercase;
|
||||||
|
color: var(--section-blue-fg);
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
section.pricemat .content {
|
||||||
|
flex: 1 1 100px;
|
||||||
|
padding: 3rem 0rem 3rem 3rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
section.pricemat .space {
|
||||||
|
flex: 0 0 50px;
|
||||||
|
}
|
||||||
|
|
||||||
|
section.pricemat h2 {
|
||||||
|
margin-bottom: 2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
section.pricemat p {
|
||||||
|
font-size: 0.8rem;
|
||||||
|
font-weight: 350;
|
||||||
|
margin-bottom: 2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
section.pricemat .button-white {
|
||||||
|
font-size: 0.8rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ---------------- footer ---------------- */
|
||||||
|
|
||||||
|
footer {
|
||||||
|
display: flex;
|
||||||
|
padding: 3rem 3rem 0 0;
|
||||||
|
margin: 1rem 1rem 3rem;
|
||||||
|
background: var(--accent-bg);
|
||||||
|
color: var(--accent-fg);
|
||||||
|
border-radius: var(--section-radius);
|
||||||
|
}
|
||||||
|
|
||||||
|
footer .house_4 {
|
||||||
|
flex: 0 1 300px;
|
||||||
|
background: url('/assets/img/house__4.png') center top no-repeat;
|
||||||
|
background-size: cover;
|
||||||
|
border-radius: 0 0 0 var(--section-radius);
|
||||||
|
}
|
||||||
|
|
||||||
|
footer .content {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
margin-left: 2rem;
|
||||||
|
flex: 2 1 auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
footer .top {
|
||||||
|
display: flex;
|
||||||
|
align-self: stretch;
|
||||||
|
}
|
||||||
|
|
||||||
|
footer .logo {
|
||||||
|
height: 45px;
|
||||||
|
width: auto;
|
||||||
|
margin-right: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
footer .middle {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
footer .split {
|
||||||
|
margin: 3rem 0 2rem;
|
||||||
|
display: flex;
|
||||||
|
padding-left: 3rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
footer .split-part {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
footer .split-part {
|
||||||
|
flex: 1 1 100px;
|
||||||
|
}
|
||||||
|
|
||||||
|
footer form {
|
||||||
|
flex: 1 1 300px;
|
||||||
|
background: var(--main-bg);
|
||||||
|
color: var(--main-fg);
|
||||||
|
border-radius: 8px;
|
||||||
|
padding: 1rem;
|
||||||
|
margin-bottom: 3rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
footer .split-part a,
|
||||||
|
footer .split-part a:visited,
|
||||||
|
footer .split-part p {
|
||||||
|
color: var(--accent-fg);
|
||||||
|
margin-bottom: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
footer .split-part a {
|
||||||
|
font-weight: 700;
|
||||||
|
}
|
||||||
|
|
||||||
|
footer button {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ---------------- form ---------------- */
|
||||||
|
|
||||||
|
.form-group {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-item {
|
||||||
|
flex: 2 1 calc(45% - 1rem);
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
margin-right: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-item.no-margin {
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-fill {
|
||||||
|
flex: 2 1 60%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-no-label {
|
||||||
|
padding-top: 1.7rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-small {
|
||||||
|
flex: 0 0 auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-item label {
|
||||||
|
font-size: 0.8rem;
|
||||||
|
margin-bottom: 0.5rem;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-list {
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-list-vertical {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
label.checkbox {
|
||||||
|
display: block;
|
||||||
|
position: relative;
|
||||||
|
margin-right: 1rem;
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
label.checkbox input[type=checkbox] {
|
||||||
|
position: absolute;
|
||||||
|
opacity: 0;
|
||||||
|
cursor: pointer;
|
||||||
|
height: 0;
|
||||||
|
width: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
label.checkbox .button-checkbox {
|
||||||
|
border-radius: var(--form-radius);
|
||||||
|
padding: var(--form-input-padding);
|
||||||
|
border: var(--form-border);
|
||||||
|
color: var(--main-fg-light);
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
label.checkbox input[type=checkbox]:checked ~ .button-checkbox {
|
||||||
|
background: var(--main-accent);
|
||||||
|
border-color: var(--main-accent);
|
||||||
|
color: var(--main-accent-fg);
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-item input[type=text],
|
||||||
|
.form-item input[type=password] {
|
||||||
|
border-radius: var(--form-radius);
|
||||||
|
border: var(--form-border);
|
||||||
|
padding: 0.9rem 0.5rem;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-item .fake-input {
|
||||||
|
border-radius: var(--form-radius);
|
||||||
|
border: none;
|
||||||
|
padding: 0.5rem 0.5rem;
|
||||||
|
width: 100%;
|
||||||
|
background: rgba(237, 101, 3, 0.1);
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-item .fake-input p {
|
||||||
|
padding: 0.5rem;
|
||||||
|
line-height: 1.5rem;
|
||||||
|
font-size: 0.8rem;
|
||||||
|
flex: 2 1 auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ---------------- icons ---------------- */
|
||||||
|
|
||||||
|
i {
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
|
||||||
|
i.ic-plus {
|
||||||
|
width: 1.5rem;
|
||||||
|
height: 1.5rem;
|
||||||
|
background: url('/assets/img/orange_circle_plus.svg') center no-repeat;
|
||||||
|
background-size: contain;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ---------------- icons ---------------- */
|
||||||
|
|
||||||
|
.combobox {
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.combobox-list {
|
||||||
|
position: absolute;
|
||||||
|
top: 100%;
|
||||||
|
left: 0;
|
||||||
|
width: 100%;
|
||||||
|
border-radius: var(--form-radius);
|
||||||
|
border: var(--form-border);
|
||||||
|
background: var(--main-bg);
|
||||||
|
z-index: 5;
|
||||||
|
max-height: 530px;
|
||||||
|
overflow-y: scroll;
|
||||||
|
box-shadow: 0px 3px 10px #0004;
|
||||||
|
}
|
||||||
|
|
||||||
|
.combobox-list-item {
|
||||||
|
padding: 0.9rem 0.5rem;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.combobox-list-item:hover,
|
||||||
|
.combobox-list-item:active,
|
||||||
|
.combobox-list-item:focus {
|
||||||
|
background: var(--main-accent);
|
||||||
|
color: var(--main-accent-fg);
|
||||||
|
}
|
||||||
|
|
||||||
|
</style>
|
||||||
|
<link id="headstyle" rel="Stylesheet" href="/assets/app.css?v=2" type="text/css" media="none" />
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<script type="text/javascript" nonce="{{=nonce}}">
|
||||||
|
document.getElementById('headstyle').addEventListener('load', function() {
|
||||||
|
window.styleloaded = true;
|
||||||
|
if(this.media!='all') { this.media='all'}
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
<header id="header">
|
||||||
|
|
||||||
|
</header>
|
||||||
|
<main id="main">
|
||||||
|
|
||||||
|
</main>
|
||||||
|
<footer id="footer">
|
||||||
|
|
||||||
|
</footer>
|
||||||
|
<script type="text/javascript" src="/assets/app.js?v=2"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
1
nfp_is/.npmrc
Normal file
|
@ -0,0 +1 @@
|
||||||
|
package-lock=false
|
20
nfp_is/api/serve.mjs
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
import path from 'path'
|
||||||
|
import Parent from '../base/serve.mjs'
|
||||||
|
import fs from 'fs/promises'
|
||||||
|
import config from '../base/config.mjs'
|
||||||
|
|
||||||
|
export default class ServeHandler extends Parent {
|
||||||
|
loadTemplate(indexFile) {
|
||||||
|
this.template = indexFile.toString()
|
||||||
|
}
|
||||||
|
|
||||||
|
async serveIndex(ctx) {
|
||||||
|
if (config.get('NODE_ENV') === 'development') {
|
||||||
|
let indexFile = await fs.readFile(path.join(this.root, 'index.html'))
|
||||||
|
this.loadTemplate(indexFile)
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx.body = this.template
|
||||||
|
ctx.type = 'text/html; charset=utf-8'
|
||||||
|
}
|
||||||
|
}
|