Add svelte as a view engine to swaf #33
@ -10,9 +10,11 @@ module.exports = {
|
|||||||
parserOptions: {
|
parserOptions: {
|
||||||
tsconfigRootDir: __dirname,
|
tsconfigRootDir: __dirname,
|
||||||
project: [
|
project: [
|
||||||
'./tsconfig.json',
|
|
||||||
'./tsconfig.test.json',
|
'./tsconfig.test.json',
|
||||||
'./tsconfig.frontend.json',
|
'./src/tsconfig.json',
|
||||||
|
'./src/common/tsconfig.json',
|
||||||
|
'./src/assets/ts/tsconfig.json',
|
||||||
|
'./src/assets/views/tsconfig.json',
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
extends: [
|
extends: [
|
||||||
|
4
.gitignore
vendored
4
.gitignore
vendored
@ -1,8 +1,10 @@
|
|||||||
.idea
|
.idea
|
||||||
node_modules
|
node_modules
|
||||||
build
|
|
||||||
dist
|
dist
|
||||||
|
intermediates
|
||||||
public
|
public
|
||||||
yarn-error.log
|
yarn-error.log
|
||||||
|
|
||||||
config/local.*
|
config/local.*
|
||||||
|
|
||||||
|
tsconfig.tsbuildinfo
|
||||||
|
@ -1,112 +0,0 @@
|
|||||||
{% extends 'layouts/base.njk' %}
|
|
||||||
{% import 'macros.njk' as macros %}
|
|
||||||
|
|
||||||
{% set title = 'Authentication / Registration' %}
|
|
||||||
{% set decription = 'Join ' + app.name + ' and share your files!' %}
|
|
||||||
{% set h1 = 'Authentication and registration' %}
|
|
||||||
|
|
||||||
{% block body %}
|
|
||||||
<div class="container">
|
|
||||||
{% set queryStr = '' %}
|
|
||||||
{% set previousUrl = getPreviousUrl() %}
|
|
||||||
{% if query.redirect_uri | length %}
|
|
||||||
{% set queryStr = '?' + querystring.stringify({redirect_uri: query.redirect_uri}) %}
|
|
||||||
{% elif previousUrl | length %}
|
|
||||||
{% set queryStr = '?' + querystring.stringify({redirect_uri: previousUrl}) %}
|
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
<section class="panel">
|
|
||||||
<h2><i data-feather="log-in"></i> Log in</h2>
|
|
||||||
|
|
||||||
{{ setFormPrefix('login-') }}
|
|
||||||
<form action="{{ route('login') + queryStr }}" method="POST" id="login-form">
|
|
||||||
{{ macros.field(_locals, 'text', 'identifier', query.identifier or '', 'Your email address or username', null, 'required') }}
|
|
||||||
|
|
||||||
{{ macros.field(_locals, 'password', 'password', null, 'Your password', 'Do not fill to log in via magic link.') }}
|
|
||||||
|
|
||||||
{{ macros.field(_locals, 'checkbox', 'persist_session', null, 'Stay logged in on this computer.') }}
|
|
||||||
|
|
||||||
<button type="submit">Authenticate</button>
|
|
||||||
|
|
||||||
{{ macros.csrf(getCsrfToken) }}
|
|
||||||
</form>
|
|
||||||
</section>
|
|
||||||
|
|
||||||
<section class="panel">
|
|
||||||
<h2><i data-feather="user-plus"></i> Register</h2>
|
|
||||||
|
|
||||||
{{ setFormPrefix('register-') }}
|
|
||||||
<form action="{{ route('register') + queryStr }}" method="POST" id="register-form">
|
|
||||||
<input type="hidden" name="auth_method" value="magic_link">
|
|
||||||
{{ macros.csrf(getCsrfToken) }}
|
|
||||||
|
|
||||||
{% if has_username %}
|
|
||||||
{{ macros.field(_locals, 'text', 'name', null, 'Choose your username', 'This cannot be changed later.', 'pattern="[0-9a-z_-]+" required') }}
|
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
<div id="register-magic_link_method_fields">
|
|
||||||
{{ macros.field(_locals, 'email', 'identifier', null, 'Your email address', null, 'required') }}
|
|
||||||
<a href="javascript: void(0);">Use password instead</a>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div id="register-password_method_fields" class="hidden">
|
|
||||||
{{ macros.field(_locals, 'password', 'password', null, 'Choose a password', null, 'required disabled') }}
|
|
||||||
{{ macros.field(_locals, 'password', 'password_confirmation', null, 'Confirm your password', null, 'required disabled') }}
|
|
||||||
<a href="javascript: void(0);">Use email address instead</a>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{{ macros.field(_locals, 'checkbox', 'terms', null, 'I accept the <a href="/terms-of-services" target="_blank">Terms Of Services</a>.' | safe, null, 'required') }}
|
|
||||||
|
|
||||||
<button type="submit" class="primary">Register</button>
|
|
||||||
</form>
|
|
||||||
</section>
|
|
||||||
</div>
|
|
||||||
{% endblock %}
|
|
||||||
|
|
||||||
{% block scripts %}
|
|
||||||
<script>
|
|
||||||
// Register form dynamics
|
|
||||||
document.addEventListener('DOMContentLoaded', () => {
|
|
||||||
const form = document.getElementById('register-form');
|
|
||||||
const authMethodField = form.querySelector('input[name=auth_method]');
|
|
||||||
const usernameField = form.querySelector('input[name=name]');
|
|
||||||
const magicLinkFields = document.getElementById('register-magic_link_method_fields');
|
|
||||||
const passwordFields = document.getElementById('register-password_method_fields');
|
|
||||||
|
|
||||||
let switchToPassword;
|
|
||||||
magicLinkFields.querySelector('a').addEventListener('click', switchToPassword = () => {
|
|
||||||
authMethodField.value = 'password';
|
|
||||||
usernameField.name = 'identifier';
|
|
||||||
|
|
||||||
magicLinkFields.classList.add('hidden');
|
|
||||||
magicLinkFields.querySelectorAll('input').forEach(el => {
|
|
||||||
el.disabled = true;
|
|
||||||
});
|
|
||||||
|
|
||||||
passwordFields.classList.remove('hidden');
|
|
||||||
passwordFields.querySelectorAll('input').forEach(el => {
|
|
||||||
el.disabled = false;
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
passwordFields.querySelector('a').addEventListener('click', () => {
|
|
||||||
authMethodField.value = 'magic_link';
|
|
||||||
usernameField.name = 'name';
|
|
||||||
|
|
||||||
magicLinkFields.classList.remove('hidden');
|
|
||||||
magicLinkFields.querySelectorAll('input').forEach(el => {
|
|
||||||
el.disabled = false;
|
|
||||||
});
|
|
||||||
|
|
||||||
passwordFields.classList.add('hidden');
|
|
||||||
passwordFields.querySelectorAll('input').forEach(el => {
|
|
||||||
el.disabled = true;
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
if (`{{ _locals.previousFormData()['auth_method'] | default('') }}` === 'password') {
|
|
||||||
switchToPassword();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
{% endblock %}
|
|
@ -1,185 +0,0 @@
|
|||||||
{% macro message(type, content, raw=false, discreet=false) %}
|
|
||||||
<div class="message{{ ' message-discreet' if discreet }}" data-type="{{ type }}">
|
|
||||||
<i class="icon"></i>
|
|
||||||
<span class="content">
|
|
||||||
{{ content|safe if raw else content }}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
{% endmacro %}
|
|
||||||
|
|
||||||
{% macro messages(flash) %}
|
|
||||||
{% set flashed = flash() %}
|
|
||||||
{% set display = 0 %}
|
|
||||||
|
|
||||||
{% for type, bag in flashed %}
|
|
||||||
{% if bag|length %}
|
|
||||||
{% set display = 1 %}
|
|
||||||
{% endif %}
|
|
||||||
{% endfor %}
|
|
||||||
|
|
||||||
{% if display %}
|
|
||||||
<div class="messages">
|
|
||||||
{% for type, bag in flashed %}
|
|
||||||
{% for content in bag %}
|
|
||||||
{{ message(type, content) }}
|
|
||||||
{% endfor %}
|
|
||||||
{% endfor %}
|
|
||||||
</div>
|
|
||||||
{% endif %}
|
|
||||||
{% endmacro %}
|
|
||||||
|
|
||||||
{% macro csrf(getCsrfToken) %}
|
|
||||||
<input type="hidden" name="csrf" value="{{ getCsrfToken() }}">
|
|
||||||
{% endmacro %}
|
|
||||||
|
|
||||||
{% macro field(_locals, type, name, value, placeholder, hint, validation_attributes='', extraData='', icon=null) %}
|
|
||||||
{% set validation = _locals.validation() %}
|
|
||||||
{% set validation = validation[name] if validation[name] or null %}
|
|
||||||
{% set previousFormData = _locals.previousFormData() %}
|
|
||||||
{% set value = previousFormData[name] or value or validation.value or '' %}
|
|
||||||
{% set prefix = _locals.getFormPrefix() | default('') %}
|
|
||||||
|
|
||||||
{% if type == 'hidden' %}
|
|
||||||
{% if validation %}
|
|
||||||
{{ message('error', validation.message) }}
|
|
||||||
{% endif %}
|
|
||||||
<input type="hidden" name="{{ name }}" value="{{ value }}">
|
|
||||||
{% else %}
|
|
||||||
<div class="form-field{{ ' inline' if type == 'checkbox' }}">
|
|
||||||
<div class="control">
|
|
||||||
{% if icon != null %}
|
|
||||||
{% if icon.startsWith('fa') %}
|
|
||||||
<i class="{{ icon }} feather icon"></i>
|
|
||||||
{% else %}
|
|
||||||
<i data-feather="{{ icon }}" class="icon"></i>
|
|
||||||
{% endif %}
|
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
{% if type == 'duration' %}
|
|
||||||
<div class="input-group">
|
|
||||||
{% for f in extraData %}
|
|
||||||
<div class="time-input">
|
|
||||||
{% if previousFormData[name] %}
|
|
||||||
{% set v = value[f] %}
|
|
||||||
{% else %}
|
|
||||||
{% set v = (value % 60) if f == 's' else (((value - value % 60) / 60 % 60) if f == 'm' else ((value - value % 3600) / 3600 if f == 'h')) %}
|
|
||||||
{% endif %}
|
|
||||||
<input type="number" name="{{ name }}[{{ f }}]" id="field-{{ prefix }}{{ name }}-{{ f }}"
|
|
||||||
value="{{ v }}"
|
|
||||||
min="0" {{ 'max=60' if (f == 's' or f == 'm') }}
|
|
||||||
{{ validation_attributes }}>
|
|
||||||
<label for="field-{{ prefix }}{{ name }}-{{ f }}">{{ f }}</label>
|
|
||||||
</div>
|
|
||||||
{% endfor %}
|
|
||||||
</div>
|
|
||||||
{% elseif type == 'select' %}
|
|
||||||
<select name="{{ name }}" id="field-{{ prefix }}{{ name }}" {{ validation_attributes|safe }}>
|
|
||||||
{% for option in extraData %}
|
|
||||||
<option value="{% if option.display === undefined or option.value !== undefined %}{{ option.value | default(option) }}{% endif %}"
|
|
||||||
{{ 'selected' if value == (option.value | default(option)) }}>{{ option.display | default(option) }}</option>
|
|
||||||
{% endfor %}
|
|
||||||
</select>
|
|
||||||
<i data-feather="chevron-down"></i>
|
|
||||||
{% elseif type == 'textarea' %}
|
|
||||||
<textarea name="{{ name }}" id="field-{{ prefix }}{{ name }}"
|
|
||||||
{{ validation_attributes|safe }} value="{{ value }}">{{ value }}</textarea>
|
|
||||||
{% else %}
|
|
||||||
<input type="{{ type }}" name="{{ name }}" id="field-{{ prefix }}{{ name }}"
|
|
||||||
{% if type != 'checkbox' %} value="{{ value }}" {% endif %}
|
|
||||||
{{ 'checked' if (type == 'checkbox' and value == 'on') }}
|
|
||||||
{{ validation_attributes|safe }}>
|
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
<label for="field-{{ prefix }}{{ name }}{{ '-' + extraData[0] if type == 'duration' }}">{{ placeholder }}</label>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{{ fieldError(_locals, name) }}
|
|
||||||
{% if hint %}
|
|
||||||
<div class="hint"><i data-feather="info"></i> {{ hint }}</div>
|
|
||||||
{% endif %}
|
|
||||||
</div>
|
|
||||||
{% endif %}
|
|
||||||
{% endmacro %}
|
|
||||||
|
|
||||||
{% macro fieldError(_locals, name) %}
|
|
||||||
{% set validation = _locals.validation() %}
|
|
||||||
{% set validation = validation[name] if validation[name] or null %}
|
|
||||||
{% if validation %}
|
|
||||||
<div class="error"><i data-feather="x-circle"></i> {{ validation.message }}</div>
|
|
||||||
{% endif %}
|
|
||||||
{% endmacro %}
|
|
||||||
|
|
||||||
{% macro websocket(websocketUrl, listener, reconnectOnClose = 1, checkFunction = 0) %}
|
|
||||||
<script>
|
|
||||||
document.addEventListener('DOMContentLoaded', () => {
|
|
||||||
{% if checkFunction %}
|
|
||||||
if (!{{ checkFunction }}()) return;
|
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
const run = () => {
|
|
||||||
const websocket = new WebSocket('{{ websocketUrl }}');
|
|
||||||
websocket.onopen = (e) => {
|
|
||||||
console.debug('Websocket connected');
|
|
||||||
};
|
|
||||||
websocket.onmessage = (e) => {
|
|
||||||
{{ listener }}(websocket, e);
|
|
||||||
};
|
|
||||||
websocket.onerror = (e) => {
|
|
||||||
console.error('Websocket error', e);
|
|
||||||
};
|
|
||||||
websocket.onclose = (e) => {
|
|
||||||
console.debug('Websocket closed', e.code, e.reason);
|
|
||||||
|
|
||||||
{% if reconnectOnClose %}
|
|
||||||
setTimeout(run, 1000);
|
|
||||||
{% endif %}
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
run();
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
{% endmacro %}
|
|
||||||
|
|
||||||
{% macro paginate(pagination, routeName, contextSize) %}
|
|
||||||
{% if pagination.hasPrevious() or pagination.hasNext() %}
|
|
||||||
<nav class="pagination">
|
|
||||||
<ul>
|
|
||||||
{% if pagination.hasPrevious() %}
|
|
||||||
<li><a href="{{ route(routeName, {page: pagination.page - 1}) }}"><i data-feather="chevron-left"></i> Previous</a></li>
|
|
||||||
{% for i in pagination.previousPages(contextSize) %}
|
|
||||||
{% if i == -1 %}
|
|
||||||
<li class="ellipsis">...</li>
|
|
||||||
{% else %}
|
|
||||||
<li><a href="{{ route(routeName, {page: i}) }}">{{ i }}</a></li>
|
|
||||||
{% endif %}
|
|
||||||
{% endfor %}
|
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
<li class="active"><span>{{ pagination.page }}</span></li>
|
|
||||||
|
|
||||||
{% if pagination.hasNext() %}
|
|
||||||
{% for i in pagination.nextPages(contextSize) %}
|
|
||||||
{% if i == -1 %}
|
|
||||||
<li class="ellipsis">...</li>
|
|
||||||
{% else %}
|
|
||||||
<li><a href="{{ route(routeName, {page: i}) }}">{{ i }}</a></li>
|
|
||||||
{% endif %}
|
|
||||||
{% endfor %}
|
|
||||||
<li><a href="{{ route(routeName, {page: pagination.page + 1}) }}">Next <i data-feather="chevron-right"></i></a></li>
|
|
||||||
{% endif %}
|
|
||||||
</ul>
|
|
||||||
</nav>
|
|
||||||
{% endif %}
|
|
||||||
{% endmacro %}
|
|
||||||
|
|
||||||
{% macro breadcrumb(currentPageTitle, pages=[]) %}
|
|
||||||
<nav aria-label="breadcrumb">
|
|
||||||
<ol class="breadcrumb">
|
|
||||||
{% for page in pages %}
|
|
||||||
<li><a href="{{ page.link }}">{{ page.title }}</a></li>
|
|
||||||
{% endfor %}
|
|
||||||
<li class="active" aria-current="page">{{ currentPageTitle }}</li>
|
|
||||||
</ol>
|
|
||||||
</nav>
|
|
||||||
{% endmacro %}
|
|
@ -1,57 +0,0 @@
|
|||||||
{% extends 'layouts/base.njk' %}
|
|
||||||
|
|
||||||
{% set h1 = 'Authentication lobby' %}
|
|
||||||
{% set title = app.name + ' ' + h1 %}
|
|
||||||
|
|
||||||
{% block body %}
|
|
||||||
<div class="container">
|
|
||||||
<div class="panel">
|
|
||||||
{{ macros.message('success', 'We sent a link to ' + email + '. To authenticate, open it from any device.') }}
|
|
||||||
{{ macros.message('info', 'This link will be valid for <span id="countdown"></span> and can only be used once.', true, true) }}
|
|
||||||
|
|
||||||
<p class="center">Waiting for you to open the link...</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{% endblock %}
|
|
||||||
|
|
||||||
{% block scripts %}
|
|
||||||
<script>
|
|
||||||
const validUntil = {{ validUntil }}.0;
|
|
||||||
|
|
||||||
function isValid() {
|
|
||||||
return new Date().getTime() < validUntil;
|
|
||||||
}
|
|
||||||
|
|
||||||
function websocketListen(websocket, e) {
|
|
||||||
if (e.data === 'refresh') {
|
|
||||||
window.location.reload();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
document.addEventListener('DOMContentLoaded', () => {
|
|
||||||
const countdown = document.getElementById('countdown');
|
|
||||||
|
|
||||||
if (!isValid()) return;
|
|
||||||
|
|
||||||
function animateCountdown() {
|
|
||||||
requestAnimationFrame(() => {
|
|
||||||
let diff = Math.max(0, validUntil - new Date().getTime());
|
|
||||||
if (diff === 0) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
diff /= 1000;
|
|
||||||
const seconds = Math.floor(diff % 60).toFixed(0);
|
|
||||||
const minutes = Math.floor((diff - seconds) / 60).toFixed(0);
|
|
||||||
|
|
||||||
countdown.innerText = `${minutes < 10 ? '0' : ''}${minutes}:${seconds < 10 ? '0' : ''}${seconds}`;
|
|
||||||
animateCountdown();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
animateCountdown();
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
|
|
||||||
{{ macros.websocket(websocketUrl, 'websocketListen', 1, 'isValid') }}
|
|
||||||
{% endblock %}
|
|
@ -17,10 +17,10 @@
|
|||||||
"test": "jest --verbose --runInBand",
|
"test": "jest --verbose --runInBand",
|
||||||
"clean": "node scripts/clean.js",
|
"clean": "node scripts/clean.js",
|
||||||
"prepare-sources": "node scripts/prepare-sources.js",
|
"prepare-sources": "node scripts/prepare-sources.js",
|
||||||
"compile": "yarn clean && tsc",
|
"compile": "yarn clean && yarn prepare-sources && tsc --build",
|
||||||
"build": "yarn prepare-sources && yarn compile && node . pre-compile-views && node scripts/dist.js",
|
"build": "yarn compile && node . pre-compile-views && node scripts/dist.js",
|
||||||
"build-production": "NODE_ENV=production yarn build",
|
"build-production": "NODE_ENV=production yarn build",
|
||||||
"dev": "yarn compile && yarn prepare-sources && concurrently -k -n \"Maildev,Typescript,ViewPreCompile,Node\" -p \"[{name}]\" -c \"yellow,blue,red,green\" \"maildev\" \"tsc --watch\" \"nodemon -i public -i build -- pre-compile-views --watch\" \"nodemon -i public -i build\"",
|
"dev": "yarn compile && concurrently -k -n \"Maildev,Typescript,ViewPreCompile,Node\" -p \"[{name}]\" -c \"yellow,blue,red,green\" \"maildev\" \"tsc --build --watch\" \"nodemon -i public -i intermediates -- pre-compile-views --watch\" \"nodemon -i public -i intermediates\"",
|
||||||
"lint": "eslint .",
|
"lint": "eslint .",
|
||||||
"release": "yarn build && yarn lint && yarn test && cd dist && yarn publish"
|
"release": "yarn build && yarn lint && yarn test && cd dist && yarn publish"
|
||||||
},
|
},
|
||||||
|
18
scripts/_functions.js
Normal file
18
scripts/_functions.js
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
import fs from "fs";
|
||||||
|
import path from "path";
|
||||||
|
|
||||||
|
export function copyRecursively(file, destination) {
|
||||||
|
const target = path.join(destination, path.basename(file));
|
||||||
|
if (fs.statSync(file).isDirectory()) {
|
||||||
|
console.log('mkdir', target);
|
||||||
|
|
||||||
|
fs.mkdirSync(target, {recursive: true});
|
||||||
|
fs.readdirSync(file).forEach(f => {
|
||||||
|
copyRecursively(path.join(file, f), target);
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
console.log('> cp ', target);
|
||||||
|
|
||||||
|
fs.copyFileSync(file, target);
|
||||||
|
}
|
||||||
|
}
|
@ -1,7 +1,7 @@
|
|||||||
import fs from "fs";
|
import fs from "fs";
|
||||||
|
|
||||||
[
|
[
|
||||||
'build',
|
'intermediates',
|
||||||
'dist',
|
'dist',
|
||||||
'public',
|
'public',
|
||||||
].forEach(file => {
|
].forEach(file => {
|
||||||
|
@ -1,28 +1,12 @@
|
|||||||
import fs from "fs";
|
import fs from "fs";
|
||||||
import path from "path";
|
import path from "path";
|
||||||
|
import {copyRecursively} from "./_functions.js";
|
||||||
function copyRecursively(file, destination) {
|
|
||||||
const target = path.join(destination, path.basename(file));
|
|
||||||
if (fs.statSync(file).isDirectory()) {
|
|
||||||
console.log('mkdir', target);
|
|
||||||
|
|
||||||
fs.mkdirSync(target, {recursive: true});
|
|
||||||
fs.readdirSync(file).forEach(f => {
|
|
||||||
copyRecursively(path.join(file, f), target);
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
console.log('> cp ', target);
|
|
||||||
|
|
||||||
fs.copyFileSync(file, target);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
[
|
[
|
||||||
'yarn.lock',
|
'yarn.lock',
|
||||||
'README.md',
|
'README.md',
|
||||||
'config/',
|
'config/',
|
||||||
'assets/',
|
|
||||||
].forEach(file => {
|
].forEach(file => {
|
||||||
copyRecursively(file, 'dist');
|
copyRecursively(file, 'dist');
|
||||||
});
|
});
|
||||||
|
@ -1,9 +1,24 @@
|
|||||||
import fs from "fs";
|
import fs from "fs";
|
||||||
|
import path from "path";
|
||||||
|
import {copyRecursively} from "./_functions.js";
|
||||||
|
|
||||||
// These folders must exist for nodemon not to loop indefinitely.
|
// These folders must exist for nodemon not to loop indefinitely.
|
||||||
[
|
[
|
||||||
'build',
|
|
||||||
'public',
|
'public',
|
||||||
|
'dist',
|
||||||
|
'intermediates',
|
||||||
|
'intermediates/assets',
|
||||||
].forEach(dir => {
|
].forEach(dir => {
|
||||||
if (!fs.existsSync(dir)) fs.mkdirSync(dir);
|
if (!fs.existsSync(dir)) fs.mkdirSync(dir);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Symlink to build/common
|
||||||
|
const symlink = path.resolve('intermediates/common');
|
||||||
|
if (!fs.existsSync(symlink)) {
|
||||||
|
fs.symlinkSync(path.resolve('dist/common'), symlink);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Copy all source files
|
||||||
|
fs.readdirSync('src').forEach(file => {
|
||||||
|
copyRecursively(path.join('src', file), 'dist');
|
||||||
|
});
|
||||||
|
@ -84,7 +84,7 @@ export default class TestApp extends Application {
|
|||||||
this.use(new MaintenanceComponent());
|
this.use(new MaintenanceComponent());
|
||||||
|
|
||||||
// Dynamic views and routes
|
// Dynamic views and routes
|
||||||
const intermediateDirectory = 'build';
|
const intermediateDirectory = 'intermediates/assets';
|
||||||
this.use(new FrontendToolsComponent(
|
this.use(new FrontendToolsComponent(
|
||||||
new AssetCompiler(intermediateDirectory, 'public'),
|
new AssetCompiler(intermediateDirectory, 'public'),
|
||||||
new CopyAssetPreCompiler(intermediateDirectory, '', 'json', ['test/assets'], false),
|
new CopyAssetPreCompiler(intermediateDirectory, '', 'json', ['test/assets'], false),
|
||||||
@ -96,7 +96,7 @@ export default class TestApp extends Application {
|
|||||||
|
|
||||||
// Services
|
// Services
|
||||||
this.use(new MysqlComponent());
|
this.use(new MysqlComponent());
|
||||||
this.use(new MailComponent(new MailViewEngine('build', 'test/assets')));
|
this.use(new MailComponent(new MailViewEngine('intermediates/assets', 'test/assets')));
|
||||||
|
|
||||||
// Session
|
// Session
|
||||||
this.use(new RedisComponent());
|
this.use(new RedisComponent());
|
||||||
|
@ -23,11 +23,11 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
{% if has_name_component %}
|
{% if has_name_component %}
|
||||||
{% include './name_panel.njk' %}
|
{% include 'views/auth/account/name_panel.njk' %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
{% if has_password_component %}
|
{% if has_password_component %}
|
||||||
{% include './password_panel.njk' %}
|
{% include 'views/auth/account/password_panel.njk' %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
<section class="panel">
|
<section class="panel">
|
@ -1,5 +1,5 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import {locals} from "../../ts/stores";
|
import {locals} from "../../ts/stores.js";
|
||||||
|
|
||||||
const previousUrl = $locals.getPreviousUrl();
|
const previousUrl = $locals.getPreviousUrl();
|
||||||
|
|
@ -9,13 +9,11 @@
|
|||||||
import * as stores from '/js/stores.js';
|
import * as stores from '/js/stores.js';
|
||||||
const localStore = stores[Object.keys(stores)[0]].locals;
|
const localStore = stores[Object.keys(stores)[0]].locals;
|
||||||
|
|
||||||
const locals = %locals%;
|
const localMap = %locals%;
|
||||||
localStore.set((key, args) => {
|
localStore.set((key, args) => {
|
||||||
const localKey = args ?
|
return localMap[args ?
|
||||||
`'${key}', \`${args}\``
|
`'${key}', \`${args}\``
|
||||||
: `'${key}'`;
|
: `'${key}'`];
|
||||||
// console.debug(localKey + '=' + locals[localKey])
|
|
||||||
return locals[localKey];
|
|
||||||
});
|
});
|
||||||
|
|
||||||
new View({
|
new View({
|
15
src/assets/views/tsconfig.json
Normal file
15
src/assets/views/tsconfig.json
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
{
|
||||||
|
"extends": "@tsconfig/svelte/tsconfig.json",
|
||||||
|
"compilerOptions": {
|
||||||
|
"outDir": "public/js",
|
||||||
|
"rootDir": "../../../intermediates/assets",
|
||||||
|
},
|
||||||
|
"include": [
|
||||||
|
"src/assets/ts/**/*"
|
||||||
|
],
|
||||||
|
"references": [
|
||||||
|
{
|
||||||
|
"path": "../../common"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
@ -1,13 +1,13 @@
|
|||||||
import config from "config";
|
import config from "config";
|
||||||
import {Request, Response} from "express";
|
import {Request, Response} from "express";
|
||||||
|
|
||||||
|
import Time from "../common/Time.js";
|
||||||
import Controller from "../Controller.js";
|
import Controller from "../Controller.js";
|
||||||
import ModelFactory from "../db/ModelFactory.js";
|
import ModelFactory from "../db/ModelFactory.js";
|
||||||
import Validator, {EMAIL_REGEX, InvalidFormatValidationError} from "../db/Validator.js";
|
import Validator, {EMAIL_REGEX, InvalidFormatValidationError} from "../db/Validator.js";
|
||||||
import {BadRequestError, ForbiddenHttpError, NotFoundHttpError} from "../HttpError.js";
|
import {BadRequestError, ForbiddenHttpError, NotFoundHttpError} from "../HttpError.js";
|
||||||
import MailTemplate from "../mail/MailTemplate.js";
|
import MailTemplate from "../mail/MailTemplate.js";
|
||||||
import {ADD_EMAIL_MAIL_TEMPLATE, REMOVE_PASSWORD_MAIL_TEMPLATE} from "../Mails.js";
|
import {ADD_EMAIL_MAIL_TEMPLATE, REMOVE_PASSWORD_MAIL_TEMPLATE} from "../Mails.js";
|
||||||
import Time from "../Time.js";
|
|
||||||
import {RequireAuthMiddleware} from "./AuthComponent.js";
|
import {RequireAuthMiddleware} from "./AuthComponent.js";
|
||||||
import AuthMagicLinkActionType from "./magic_link/AuthMagicLinkActionType.js";
|
import AuthMagicLinkActionType from "./magic_link/AuthMagicLinkActionType.js";
|
||||||
import MagicLinkController from "./magic_link/MagicLinkController.js";
|
import MagicLinkController from "./magic_link/MagicLinkController.js";
|
||||||
|
3
src/common/package.json
Normal file
3
src/common/package.json
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
{
|
||||||
|
"type": "commonjs"
|
||||||
|
}
|
20
src/common/tsconfig.json
Normal file
20
src/common/tsconfig.json
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
{
|
||||||
|
"extends": "../../tsconfig.json",
|
||||||
|
"compilerOptions": {
|
||||||
|
"composite": true,
|
||||||
|
|
||||||
|
"module": "CommonJS",
|
||||||
|
|
||||||
|
"baseUrl": "../../dist/common",
|
||||||
|
"rootDir": "./",
|
||||||
|
"sourceRoot": "./",
|
||||||
|
"outDir": "../../dist/common",
|
||||||
|
|
||||||
|
"typeRoots": [
|
||||||
|
"src/types"
|
||||||
|
],
|
||||||
|
},
|
||||||
|
"include": [
|
||||||
|
"./**/*"
|
||||||
|
]
|
||||||
|
}
|
@ -34,7 +34,7 @@ export default abstract class AssetPreCompiler {
|
|||||||
) {
|
) {
|
||||||
this.assetPaths = [
|
this.assetPaths = [
|
||||||
...additionalFallbackAssetPaths,
|
...additionalFallbackAssetPaths,
|
||||||
'assets',
|
'src/assets',
|
||||||
'node_modules/swaf/assets',
|
'node_modules/swaf/assets',
|
||||||
].map(p => path.resolve(p, assetType))
|
].map(p => path.resolve(p, assetType))
|
||||||
.filter(dir => existsSync(dir));
|
.filter(dir => existsSync(dir));
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import "../../node_modules/svelte/register.js";
|
import "./register_svelte/register_svelte.js";
|
||||||
|
|
||||||
import clearModule from "clear-module";
|
import clearModule from "clear-module";
|
||||||
import config from "config";
|
import config from "config";
|
||||||
@ -176,7 +176,7 @@ export default class SvelteViewEngine extends ViewEngine {
|
|||||||
replacedBackendCalls.code,
|
replacedBackendCalls.code,
|
||||||
sveltePreprocess({
|
sveltePreprocess({
|
||||||
typescript: {
|
typescript: {
|
||||||
tsconfigFile: 'tsconfig.svelte.json',
|
tsconfigFile: 'src/assets/views/tsconfig.json',
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
{
|
{
|
||||||
@ -276,7 +276,7 @@ export default class SvelteViewEngine extends ViewEngine {
|
|||||||
});
|
});
|
||||||
|
|
||||||
// Load locals into locals store
|
// Load locals into locals store
|
||||||
const localsModulePath = "../../build/ts/stores.js";
|
const localsModulePath = "../../intermediates/assets/ts/stores.js";
|
||||||
const localsModule = await import(localsModulePath);
|
const localsModule = await import(localsModulePath);
|
||||||
const locals = this.getGlobals().get();
|
const locals = this.getGlobals().get();
|
||||||
const localMap = this.compileBackendCalls(backendCalls, locals, true);
|
const localMap = this.compileBackendCalls(backendCalls, locals, true);
|
||||||
|
@ -12,7 +12,7 @@ export default class TypeScriptPreCompiler extends CopyAssetPreCompiler {
|
|||||||
|
|
||||||
this.onPreCompile(async _watch => {
|
this.onPreCompile(async _watch => {
|
||||||
logger.info('Building ts assets...');
|
logger.info('Building ts assets...');
|
||||||
await child_process.execSync(`yarn tsc -b tsconfig.frontend.json`, {stdio: [process.stdin, process.stdout, process.stderr]});
|
await child_process.execSync(`yarn tsc -b src/assets/ts/tsconfig.json`, {stdio: [process.stdin, process.stdout, process.stderr]});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
3
src/frontend/register_svelte/package.json
Normal file
3
src/frontend/register_svelte/package.json
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
{
|
||||||
|
"type": "commonjs"
|
||||||
|
}
|
1
src/frontend/register_svelte/register_svelte.js
Normal file
1
src/frontend/register_svelte/register_svelte.js
Normal file
@ -0,0 +1 @@
|
|||||||
|
require("svelte/register");
|
27
src/tsconfig.json
Normal file
27
src/tsconfig.json
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
{
|
||||||
|
"extends": "../tsconfig.json",
|
||||||
|
"compilerOptions": {
|
||||||
|
"composite": true,
|
||||||
|
|
||||||
|
"baseUrl": "../dist",
|
||||||
|
"rootDir": "./",
|
||||||
|
"sourceRoot": "./",
|
||||||
|
"outDir": "../dist",
|
||||||
|
|
||||||
|
"typeRoots": [
|
||||||
|
"src/types"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"include": [
|
||||||
|
"./**/*"
|
||||||
|
],
|
||||||
|
"exclude": [
|
||||||
|
"./assets/**/*",
|
||||||
|
"./common/**/*"
|
||||||
|
],
|
||||||
|
"references": [
|
||||||
|
{
|
||||||
|
"path": "./common"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
@ -1,18 +0,0 @@
|
|||||||
{
|
|
||||||
"extends": "./tsconfig.json",
|
|
||||||
"compilerOptions": {
|
|
||||||
"target": "ESNext",
|
|
||||||
"module": "CommonJS",
|
|
||||||
"baseUrl": "build",
|
|
||||||
"rootDir": "build/ts-source",
|
|
||||||
"sourceRoot": "build/ts-source",
|
|
||||||
"outDir": "build/ts",
|
|
||||||
"declaration": false,
|
|
||||||
|
|
||||||
"typeRoots": [],
|
|
||||||
"resolveJsonModule": false
|
|
||||||
},
|
|
||||||
"include": [
|
|
||||||
"build/ts-source/**/*"
|
|
||||||
]
|
|
||||||
}
|
|
@ -7,12 +7,11 @@
|
|||||||
|
|
||||||
"strict": true,
|
"strict": true,
|
||||||
"allowSyntheticDefaultImports": true,
|
"allowSyntheticDefaultImports": true,
|
||||||
|
"strictNullChecks": true,
|
||||||
|
|
||||||
"moduleResolution": "Node",
|
"moduleResolution": "Node",
|
||||||
"esModuleInterop": true,
|
"esModuleInterop": true,
|
||||||
"baseUrl": "dist",
|
"baseUrl": "dist",
|
||||||
"rootDir": "src",
|
|
||||||
"sourceRoot": "src",
|
|
||||||
"inlineSourceMap": true,
|
"inlineSourceMap": true,
|
||||||
"inlineSources": true,
|
"inlineSources": true,
|
||||||
"outDir": "dist",
|
"outDir": "dist",
|
||||||
@ -25,9 +24,19 @@
|
|||||||
"es2020"
|
"es2020"
|
||||||
],
|
],
|
||||||
"resolveJsonModule": true,
|
"resolveJsonModule": true,
|
||||||
"skipLibCheck": true
|
"skipLibCheck": true,
|
||||||
|
"allowJs": true,
|
||||||
},
|
},
|
||||||
"include": [
|
"include": [],
|
||||||
"src/**/*"
|
"references": [
|
||||||
|
{
|
||||||
|
"path": "src",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "src/assets/ts",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "src/assets/views",
|
||||||
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
@ -1,10 +0,0 @@
|
|||||||
{
|
|
||||||
"extends": "@tsconfig/svelte/tsconfig.json",
|
|
||||||
"compilerOptions": {
|
|
||||||
"outDir": "public/js",
|
|
||||||
"rootDir": "build",
|
|
||||||
},
|
|
||||||
"include": [
|
|
||||||
"assets/ts/**/*"
|
|
||||||
],
|
|
||||||
}
|
|
Loading…
Reference in New Issue
Block a user