wip
BIN
packages/frontend/assets/api-doc.png
Normal file
After Width: | Height: | Size: 5.4 KiB |
BIN
packages/frontend/assets/apple-touch-icon.png
Normal file
After Width: | Height: | Size: 9.6 KiB |
BIN
packages/frontend/assets/emoji-unknown.png
Normal file
After Width: | Height: | Size: 3.4 KiB |
BIN
packages/frontend/assets/favicon.ico
Normal file
After Width: | Height: | Size: 88 KiB |
BIN
packages/frontend/assets/favicon.png
Normal file
After Width: | Height: | Size: 9.1 KiB |
BIN
packages/frontend/assets/icons/192.png
Normal file
After Width: | Height: | Size: 7.5 KiB |
BIN
packages/frontend/assets/icons/512.png
Normal file
After Width: | Height: | Size: 20 KiB |
1
packages/frontend/assets/locales/ar-SA.13.6.1.json
Normal file
1
packages/frontend/assets/locales/cs-CZ.13.6.1.json
Normal file
1
packages/frontend/assets/locales/da-DK.13.6.1.json
Normal file
1
packages/frontend/assets/locales/de-DE.13.6.1.json
Normal file
1
packages/frontend/assets/locales/en-US.13.6.1.json
Normal file
1
packages/frontend/assets/locales/es-ES.13.6.1.json
Normal file
1
packages/frontend/assets/locales/fr-FR.13.6.1.json
Normal file
1
packages/frontend/assets/locales/id-ID.13.6.1.json
Normal file
1
packages/frontend/assets/locales/it-IT.13.6.1.json
Normal file
1
packages/frontend/assets/locales/ja-JP.13.6.1.json
Normal file
1
packages/frontend/assets/locales/ja-KS.13.6.1.json
Normal file
1
packages/frontend/assets/locales/kab-KAB.13.6.1.json
Normal file
1
packages/frontend/assets/locales/kn-IN.13.6.1.json
Normal file
1
packages/frontend/assets/locales/ko-KR.13.6.1.json
Normal file
1
packages/frontend/assets/locales/nl-NL.13.6.1.json
Normal file
1
packages/frontend/assets/locales/no-NO.13.6.1.json
Normal file
1
packages/frontend/assets/locales/pl-PL.13.6.1.json
Normal file
1
packages/frontend/assets/locales/pt-PT.13.6.1.json
Normal file
1
packages/frontend/assets/locales/ru-RU.13.6.1.json
Normal file
1
packages/frontend/assets/locales/sk-SK.13.6.1.json
Normal file
1
packages/frontend/assets/locales/th-TH.13.6.1.json
Normal file
1
packages/frontend/assets/locales/ug-CN.13.6.1.json
Normal file
1
packages/frontend/assets/locales/uk-UA.13.6.1.json
Normal file
1
packages/frontend/assets/locales/vi-VN.13.6.1.json
Normal file
1
packages/frontend/assets/locales/zh-CN.13.6.1.json
Normal file
1
packages/frontend/assets/locales/zh-TW.13.6.1.json
Normal file
BIN
packages/frontend/assets/mi-white.png
Normal file
After Width: | Height: | Size: 18 KiB |
24
packages/frontend/assets/redoc.html
Normal file
@ -0,0 +1,24 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>Misskey API</title>
|
||||
<!-- needed for adaptive design -->
|
||||
<meta charset="utf-8"/>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<link href="https://fonts.googleapis.com/css?family=Montserrat:300,400,700|Roboto:300,400,700" rel="stylesheet">
|
||||
|
||||
<!--
|
||||
ReDoc doesn't change outer page styles
|
||||
-->
|
||||
<style>
|
||||
body {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<redoc spec-url="/api.json" expand-responses="200" expand-single-schema-field="true"></redoc>
|
||||
<script src="https://cdn.jsdelivr.net/npm/redoc@2.0.0-rc.50/bundles/redoc.standalone.js" integrity="sha256-WJbngBWN9vp6vkEuzeoSj5tE5saW9Hfj6/SinkzhL2s=" crossorigin="anonymous"></script>
|
||||
</body>
|
||||
</html>
|
4
packages/frontend/assets/robots.txt
Normal file
@ -0,0 +1,4 @@
|
||||
user-agent: *
|
||||
allow: /
|
||||
|
||||
# todo: sitemap
|
BIN
packages/frontend/assets/splash.png
Normal file
After Width: | Height: | Size: 23 KiB |
24
packages/frontend/assets/tabler-badges/LICENSE
Normal file
@ -0,0 +1,24 @@
|
||||
Tabler Icons
|
||||
https://github.com/tabler/tabler-icons/blob/master/LICENSE
|
||||
====
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2020-2022 Paweł Kuna
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
BIN
packages/frontend/assets/tabler-badges/antenna.png
Normal file
After Width: | Height: | Size: 516 B |
BIN
packages/frontend/assets/tabler-badges/arrow-back-up.png
Normal file
After Width: | Height: | Size: 952 B |
BIN
packages/frontend/assets/tabler-badges/at.png
Normal file
After Width: | Height: | Size: 2.8 KiB |
BIN
packages/frontend/assets/tabler-badges/chart-arrows.png
Normal file
After Width: | Height: | Size: 829 B |
BIN
packages/frontend/assets/tabler-badges/circle-check.png
Normal file
After Width: | Height: | Size: 2.3 KiB |
BIN
packages/frontend/assets/tabler-badges/messages.png
Normal file
After Width: | Height: | Size: 1.0 KiB |
BIN
packages/frontend/assets/tabler-badges/null.png
Normal file
After Width: | Height: | Size: 174 B |
BIN
packages/frontend/assets/tabler-badges/plus.png
Normal file
After Width: | Height: | Size: 414 B |
BIN
packages/frontend/assets/tabler-badges/quote.png
Normal file
After Width: | Height: | Size: 1011 B |
BIN
packages/frontend/assets/tabler-badges/repeat.png
Normal file
After Width: | Height: | Size: 1.2 KiB |
BIN
packages/frontend/assets/tabler-badges/user-plus.png
Normal file
After Width: | Height: | Size: 1.4 KiB |
BIN
packages/frontend/assets/tabler-badges/users.png
Normal file
After Width: | Height: | Size: 1.9 KiB |
BIN
packages/frontend/assets/user-unknown.png
Normal file
After Width: | Height: | Size: 3.4 KiB |
309
packages/frontend/boot.js
Normal file
@ -0,0 +1,309 @@
|
||||
/**
|
||||
* BOOT LOADER
|
||||
* サーバーからレスポンスされるHTMLに埋め込まれるスクリプトで、以下の役割を持ちます。
|
||||
* - 翻訳ファイルをフェッチする。
|
||||
* - バージョンに基づいて適切なメインスクリプトを読み込む。
|
||||
* - キャッシュされたコンパイル済みテーマを適用する。
|
||||
* - クライアントの設定値に基づいて対応するHTMLクラス等を設定する。
|
||||
* テーマをこの段階で設定するのは、メインスクリプトが読み込まれる間もテーマを適用したいためです。
|
||||
* 注: webpackは介さないため、このファイルではrequireやimportは使えません。
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
// ブロックの中に入れないと、定義した変数がブラウザのグローバルスコープに登録されてしまい邪魔なので
|
||||
(async () => {
|
||||
window.onerror = (e) => {
|
||||
console.error(e);
|
||||
renderError('SOMETHING_HAPPENED', e);
|
||||
};
|
||||
window.onunhandledrejection = (e) => {
|
||||
console.error(e);
|
||||
renderError('SOMETHING_HAPPENED_IN_PROMISE', e);
|
||||
};
|
||||
|
||||
let forceError = localStorage.getItem('forceError');
|
||||
if (forceError != null) {
|
||||
renderError('FORCED_ERROR', 'This error is forced by having forceError in local storage.')
|
||||
}
|
||||
|
||||
//#region Detect language & fetch translations
|
||||
if (!localStorage.hasOwnProperty('locale')) {
|
||||
// const supportedLangs = LANGS;
|
||||
const supportedLangs = ['en-US', 'ja-JP'];
|
||||
let lang = localStorage.getItem('lang');
|
||||
if (lang == null || !supportedLangs.includes(lang)) {
|
||||
if (supportedLangs.includes(navigator.language)) {
|
||||
lang = navigator.language;
|
||||
} else {
|
||||
lang = supportedLangs.find(x => x.split('-')[0] === navigator.language);
|
||||
|
||||
// Fallback
|
||||
if (lang == null) lang = 'en-US';
|
||||
}
|
||||
}
|
||||
|
||||
// const metaRes = await window.fetch('/api/meta', {
|
||||
// method: 'POST',
|
||||
// body: JSON.stringify({}),
|
||||
// credentials: 'omit',
|
||||
// cache: 'no-cache',
|
||||
// headers: {
|
||||
// 'Content-Type': 'application/json',
|
||||
// },
|
||||
// });
|
||||
// if (metaRes.status !== 200) {
|
||||
// renderError('META_FETCH');
|
||||
// return;
|
||||
// }
|
||||
// const meta = await metaRes.json();
|
||||
const meta = { "version": "13.6.1-simkey", "uri": "https://kokt.club", "langs": [], "repositoryUrl": "https://github.com/misskey-dev/misskey", "feedbackUrl": "https://github.com/misskey-dev/misskey/issues/new", "disableRegistration": false, "emailRequiredForSignup": false, "enableHcaptcha": false, "hcaptchaSiteKey": null, "enableRecaptcha": true, "recaptchaSiteKey": "6LcSVvkhAAAAAHfnEeJhfI_XJLnfn8-gPHqZ4K5z", "enableTurnstile": false, "turnstileSiteKey": null, "swPublickey": "BJO5qBTaw1t5O448MwtH4jikC9JG0V682uVftWHPNU05-PE5-aAuj14zBLPPpgJP_ifpndg0cVn0Ka3oE453ZkA", "themeColor": null, "mascotImageUrl": "/assets/ai.png", "bannerUrl": "https://s3.kokt.club/files/a952f48b-7720-4b00-9d15-f044150db378.webp", "errorImageUrl": "https://xn--931a.moe/aiart/yubitun.png", "iconUrl": "https://s3.kokt.club/files/e2149c00-311a-4348-a548-0877831b4b08.png", "backgroundImageUrl": "https://s3.kokt.club/files/a952f48b-7720-4b00-9d15-f044150db378.webp", "logoImageUrl": null, "maxNoteTextLength": 8192, "defaultLightTheme": null, "defaultDarkTheme": null, "ads": [], "enableEmail": true, "enableTwitterIntegration": false, "enableGithubIntegration": false, "enableDiscordIntegration": false, "enableServiceWorker": true, "translatorAvailable": true, "policies": { "gtlAvailable": true, "ltlAvailable": true, "canPublicNote": true, "canInvite": false, "canManageCustomEmojis": false, "canHideAds": false, "driveCapacityMb": 20480, "pinLimit": 5, "antennaLimit": 10, "wordMuteLimit": 200, "webhookLimit": 10, "clipLimit": 20, "noteEachClipsLimit": 2000, "userListLimit": 15, "userEachUserListsLimit": 100, "rateLimitFactor": 1 } }
|
||||
const v = meta.version;
|
||||
if (v == null) {
|
||||
renderError('META_FETCH_V');
|
||||
return;
|
||||
}
|
||||
const localRes = await window.fetch(`/assets/locales/${lang}.${v}.json`);
|
||||
// const localRes = await window.fetch(`/assets/locales/${lang}.json`);
|
||||
if (localRes.status === 200) {
|
||||
localStorage.setItem('lang', lang);
|
||||
localStorage.setItem('locale', await localRes.text());
|
||||
localStorage.setItem('localeVersion', v);
|
||||
} else {
|
||||
renderError('LOCALE_FETCH');
|
||||
return;
|
||||
}
|
||||
}
|
||||
//#endregion
|
||||
|
||||
//#region Script
|
||||
function importAppScript() {
|
||||
import(`/${CLIENT_ENTRY}`)
|
||||
.catch(async e => {
|
||||
console.error(e);
|
||||
renderError('APP_IMPORT', e);
|
||||
});
|
||||
}
|
||||
|
||||
// タイミングによっては、この時点でDOMの構築が済んでいる場合とそうでない場合とがある
|
||||
if (document.readyState !== 'loading') {
|
||||
importAppScript();
|
||||
} else {
|
||||
window.addEventListener('DOMContentLoaded', () => {
|
||||
importAppScript();
|
||||
});
|
||||
}
|
||||
//#endregion
|
||||
|
||||
//#region Theme
|
||||
const theme = localStorage.getItem('theme');
|
||||
if (theme) {
|
||||
for (const [k, v] of Object.entries(JSON.parse(theme))) {
|
||||
document.documentElement.style.setProperty(`--${k}`, v.toString());
|
||||
|
||||
// HTMLの theme-color 適用
|
||||
if (k === 'htmlThemeColor') {
|
||||
for (const tag of document.head.children) {
|
||||
if (tag.tagName === 'META' && tag.getAttribute('name') === 'theme-color') {
|
||||
tag.setAttribute('content', v);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
const colorSchema = localStorage.getItem('colorSchema');
|
||||
if (colorSchema) {
|
||||
document.documentElement.style.setProperty('color-schema', colorSchema);
|
||||
}
|
||||
//#endregion
|
||||
|
||||
const fontSize = localStorage.getItem('fontSize');
|
||||
if (fontSize) {
|
||||
document.documentElement.classList.add('f-' + fontSize);
|
||||
}
|
||||
|
||||
const useSystemFont = localStorage.getItem('useSystemFont');
|
||||
if (useSystemFont) {
|
||||
document.documentElement.classList.add('useSystemFont');
|
||||
}
|
||||
|
||||
const wallpaper = localStorage.getItem('wallpaper');
|
||||
if (wallpaper) {
|
||||
document.documentElement.style.backgroundImage = `url(${wallpaper})`;
|
||||
}
|
||||
|
||||
const customCss = localStorage.getItem('customCss');
|
||||
if (customCss && customCss.length > 0) {
|
||||
const style = document.createElement('style');
|
||||
style.innerHTML = customCss;
|
||||
document.head.appendChild(style);
|
||||
}
|
||||
|
||||
async function addStyle(styleText) {
|
||||
let css = document.createElement('style');
|
||||
css.appendChild(document.createTextNode(styleText));
|
||||
document.head.appendChild(css);
|
||||
}
|
||||
|
||||
function renderError(code, details) {
|
||||
let errorsElement = document.getElementById('errors');
|
||||
|
||||
if (!errorsElement) {
|
||||
document.body.innerHTML = `
|
||||
<svg class="icon-warning" xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-alert-triangle" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round">
|
||||
<path stroke="none" d="M0 0h24v24H0z" fill="none"></path>
|
||||
<path d="M12 9v2m0 4v.01"></path>
|
||||
<path d="M5 19h14a2 2 0 0 0 1.84 -2.75l-7.1 -12.25a2 2 0 0 0 -3.5 0l-7.1 12.25a2 2 0 0 0 1.75 2.75"></path>
|
||||
</svg>
|
||||
<h1>An error has occurred!</h1>
|
||||
<button class="button-big" onclick="location.reload();">
|
||||
<span class="button-label-big">Refresh</span>
|
||||
</button>
|
||||
<p class="dont-worry">Don't worry, it's (probably) not your fault.</p>
|
||||
<p>If the problem persists after refreshing, please contact your instance's administrator.<br>You may also try the following options:</p>
|
||||
<p>Update your os and browser.</p>
|
||||
<p>Disable an adblocker.</p>
|
||||
<a href="/flush">
|
||||
<button class="button-small">
|
||||
<span class="button-label-small">Clear preferences and cache</span>
|
||||
</button>
|
||||
</a>
|
||||
<br>
|
||||
<a href="/cli">
|
||||
<button class="button-small">
|
||||
<span class="button-label-small">Start the simple client</span>
|
||||
</button>
|
||||
</a>
|
||||
<br>
|
||||
<a href="/bios">
|
||||
<button class="button-small">
|
||||
<span class="button-label-small">Start the repair tool</span>
|
||||
</button>
|
||||
</a>
|
||||
<br>
|
||||
<div id="errors"></div>
|
||||
`;
|
||||
errorsElement = document.getElementById('errors');
|
||||
}
|
||||
const detailsElement = document.createElement('details');
|
||||
detailsElement.innerHTML = `
|
||||
<br>
|
||||
<summary>
|
||||
<code>ERROR CODE: ${code}</code>
|
||||
</summary>
|
||||
<code>${JSON.stringify(details)}</code>`;
|
||||
errorsElement.appendChild(detailsElement);
|
||||
addStyle(`
|
||||
* {
|
||||
font-family: BIZ UDGothic, Roboto, HelveticaNeue, Arial, sans-serif;
|
||||
}
|
||||
|
||||
#misskey_app,
|
||||
#splash {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
body,
|
||||
html {
|
||||
background-color: #222;
|
||||
color: #dfddcc;
|
||||
justify-content: center;
|
||||
margin: auto;
|
||||
padding: 10px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
button {
|
||||
border-radius: 999px;
|
||||
padding: 0px 12px 0px 12px;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.button-big {
|
||||
background: linear-gradient(90deg, rgb(134, 179, 0), rgb(74, 179, 0));
|
||||
line-height: 50px;
|
||||
}
|
||||
|
||||
.button-big:hover {
|
||||
background: rgb(153, 204, 0);
|
||||
}
|
||||
|
||||
.button-small {
|
||||
background: #444;
|
||||
line-height: 40px;
|
||||
}
|
||||
|
||||
.button-small:hover {
|
||||
background: #555;
|
||||
}
|
||||
|
||||
.button-label-big {
|
||||
color: #222;
|
||||
font-weight: bold;
|
||||
font-size: 20px;
|
||||
padding: 12px;
|
||||
}
|
||||
|
||||
.button-label-small {
|
||||
color: rgb(153, 204, 0);
|
||||
font-size: 16px;
|
||||
padding: 12px;
|
||||
}
|
||||
|
||||
a {
|
||||
color: rgb(134, 179, 0);
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
p,
|
||||
li {
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
.dont-worry,
|
||||
#msg {
|
||||
font-size: 18px;
|
||||
}
|
||||
|
||||
.icon-warning {
|
||||
color: #dec340;
|
||||
height: 4rem;
|
||||
padding-top: 2rem;
|
||||
}
|
||||
|
||||
h1 {
|
||||
font-size: 32px;
|
||||
}
|
||||
|
||||
code {
|
||||
font-family: Fira, FiraCode, monospace;
|
||||
}
|
||||
|
||||
details {
|
||||
background: #333;
|
||||
margin-bottom: 2rem;
|
||||
padding: 0.5rem 1rem;
|
||||
width: 40rem;
|
||||
border-radius: 10px;
|
||||
justify-content: center;
|
||||
margin: auto;
|
||||
}
|
||||
|
||||
summary {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
summary > * {
|
||||
display: inline;
|
||||
}
|
||||
|
||||
@media screen and (max-width: 500px) {
|
||||
details {
|
||||
width: 50%;
|
||||
}
|
||||
`)
|
||||
}
|
||||
})();
|
164
packages/frontend/config.ts
Normal file
@ -0,0 +1,164 @@
|
||||
/**
|
||||
* Config loader
|
||||
*/
|
||||
|
||||
import * as fs from 'node:fs';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
import { dirname } from 'node:path';
|
||||
import * as yaml from 'js-yaml';
|
||||
|
||||
/**
|
||||
* ユーザーが設定する必要のある情報
|
||||
*/
|
||||
export type Source = {
|
||||
repository_url?: string;
|
||||
feedback_url?: string;
|
||||
url: string;
|
||||
port: number;
|
||||
disableHsts?: boolean;
|
||||
db: {
|
||||
host: string;
|
||||
port: number;
|
||||
db: string;
|
||||
user: string;
|
||||
pass: string;
|
||||
disableCache?: boolean;
|
||||
extra?: { [x: string]: string };
|
||||
};
|
||||
redis: {
|
||||
host: string;
|
||||
port: number;
|
||||
family?: number;
|
||||
pass: string;
|
||||
db?: number;
|
||||
prefix?: string;
|
||||
};
|
||||
elasticsearch: {
|
||||
host: string;
|
||||
port: number;
|
||||
ssl?: boolean;
|
||||
user?: string;
|
||||
pass?: string;
|
||||
index?: string;
|
||||
};
|
||||
|
||||
proxy?: string;
|
||||
proxySmtp?: string;
|
||||
proxyBypassHosts?: string[];
|
||||
|
||||
allowedPrivateNetworks?: string[];
|
||||
|
||||
maxFileSize?: number;
|
||||
|
||||
accesslog?: string;
|
||||
|
||||
clusterLimit?: number;
|
||||
|
||||
id: string;
|
||||
|
||||
outgoingAddressFamily?: 'ipv4' | 'ipv6' | 'dual';
|
||||
|
||||
deliverJobConcurrency?: number;
|
||||
inboxJobConcurrency?: number;
|
||||
deliverJobPerSec?: number;
|
||||
inboxJobPerSec?: number;
|
||||
deliverJobMaxAttempts?: number;
|
||||
inboxJobMaxAttempts?: number;
|
||||
|
||||
mediaProxy?: string;
|
||||
proxyRemoteFiles?: boolean;
|
||||
videoThumbnailGenerator?: string;
|
||||
|
||||
signToActivityPubGet?: boolean;
|
||||
};
|
||||
|
||||
/**
|
||||
* Misskeyが自動的に(ユーザーが設定した情報から推論して)設定する情報
|
||||
*/
|
||||
export type Mixin = {
|
||||
version: string;
|
||||
host: string;
|
||||
hostname: string;
|
||||
scheme: string;
|
||||
wsScheme: string;
|
||||
apiUrl: string;
|
||||
wsUrl: string;
|
||||
authUrl: string;
|
||||
driveUrl: string;
|
||||
userAgent: string;
|
||||
clientEntry: string;
|
||||
clientManifestExists: boolean;
|
||||
mediaProxy: string;
|
||||
externalMediaProxyEnabled: boolean;
|
||||
videoThumbnailGenerator: string | null;
|
||||
};
|
||||
|
||||
export type Config = Source & Mixin;
|
||||
|
||||
const _filename = fileURLToPath(import.meta.url);
|
||||
const _dirname = dirname(_filename);
|
||||
|
||||
/**
|
||||
* Path of configuration directory
|
||||
*/
|
||||
const dir = `${_dirname}/../../../.config`;
|
||||
|
||||
/**
|
||||
* Path of configuration file
|
||||
*/
|
||||
const path = process.env.NODE_ENV === 'test'
|
||||
? `${dir}/test.yml`
|
||||
: `${dir}/default.yml`;
|
||||
|
||||
export function loadConfig() {
|
||||
const meta = JSON.parse(fs.readFileSync(`${_dirname}/../../../built/meta.json`, 'utf-8'));
|
||||
const clientManifestExists = fs.existsSync(_dirname + '/../../../built/_vite_/manifest.json');
|
||||
const clientManifest = clientManifestExists ?
|
||||
JSON.parse(fs.readFileSync(`${_dirname}/../../../built/_vite_/manifest.json`, 'utf-8'))
|
||||
: { 'src/init.ts': { file: 'src/init.ts' } };
|
||||
const config = yaml.load(fs.readFileSync(path, 'utf-8')) as Source;
|
||||
|
||||
const mixin = {} as Mixin;
|
||||
|
||||
const url = tryCreateUrl(config.url);
|
||||
|
||||
config.url = url.origin;
|
||||
|
||||
config.port = config.port ?? parseInt(process.env.PORT ?? '', 10);
|
||||
|
||||
mixin.version = meta.version;
|
||||
mixin.host = url.host;
|
||||
mixin.hostname = url.hostname;
|
||||
mixin.scheme = url.protocol.replace(/:$/, '');
|
||||
mixin.wsScheme = mixin.scheme.replace('http', 'ws');
|
||||
mixin.wsUrl = `${mixin.wsScheme}://${mixin.host}`;
|
||||
mixin.apiUrl = `${mixin.scheme}://${mixin.host}/api`;
|
||||
mixin.authUrl = `${mixin.scheme}://${mixin.host}/auth`;
|
||||
mixin.driveUrl = `${mixin.scheme}://${mixin.host}/files`;
|
||||
mixin.userAgent = `Misskey/${meta.version} (${config.url})`;
|
||||
mixin.clientEntry = clientManifest['src/init.ts'];
|
||||
mixin.clientManifestExists = clientManifestExists;
|
||||
|
||||
const externalMediaProxy = config.mediaProxy ?
|
||||
config.mediaProxy.endsWith('/') ? config.mediaProxy.substring(0, config.mediaProxy.length - 1) : config.mediaProxy
|
||||
: null;
|
||||
const internalMediaProxy = `${mixin.scheme}://${mixin.host}/proxy`;
|
||||
mixin.mediaProxy = externalMediaProxy ?? internalMediaProxy;
|
||||
mixin.externalMediaProxyEnabled = externalMediaProxy !== null && externalMediaProxy !== internalMediaProxy;
|
||||
|
||||
mixin.videoThumbnailGenerator = config.videoThumbnailGenerator ?
|
||||
config.videoThumbnailGenerator.endsWith('/') ? config.videoThumbnailGenerator.substring(0, config.videoThumbnailGenerator.length - 1) : config.videoThumbnailGenerator
|
||||
: null;
|
||||
|
||||
if (!config.redis.prefix) config.redis.prefix = mixin.host;
|
||||
|
||||
return Object.assign(config, mixin);
|
||||
}
|
||||
|
||||
function tryCreateUrl(url: string) {
|
||||
try {
|
||||
return new URL(url);
|
||||
} catch (e) {
|
||||
throw `url="${url}" is not a valid URL.`;
|
||||
}
|
||||
}
|
66
packages/frontend/const.ts
Normal file
@ -0,0 +1,66 @@
|
||||
export const MAX_NOTE_TEXT_LENGTH = 3000;
|
||||
|
||||
export const USER_ONLINE_THRESHOLD = 1000 * 60 * 10; // 10min
|
||||
export const USER_ACTIVE_THRESHOLD = 1000 * 60 * 60 * 24 * 3; // 3days
|
||||
|
||||
//#region hard limits
|
||||
// If you change DB_* values, you must also change the DB schema.
|
||||
|
||||
/**
|
||||
* Maximum note text length that can be stored in DB.
|
||||
* Surrogate pairs count as one
|
||||
*/
|
||||
export const DB_MAX_NOTE_TEXT_LENGTH = 8192;
|
||||
|
||||
/**
|
||||
* Maximum image description length that can be stored in DB.
|
||||
* Surrogate pairs count as one
|
||||
*/
|
||||
export const DB_MAX_IMAGE_COMMENT_LENGTH = 512;
|
||||
//#endregion
|
||||
|
||||
// ブラウザで直接表示することを許可するファイルの種類のリスト
|
||||
// ここに含まれないものは application/octet-stream としてレスポンスされる
|
||||
// SVGはXSSを生むので許可しない
|
||||
export const FILE_TYPE_BROWSERSAFE = [
|
||||
// Images
|
||||
'image/png',
|
||||
'image/gif',
|
||||
'image/jpeg',
|
||||
'image/webp',
|
||||
'image/avif',
|
||||
'image/apng',
|
||||
'image/bmp',
|
||||
'image/tiff',
|
||||
'image/x-icon',
|
||||
|
||||
// OggS
|
||||
'audio/opus',
|
||||
'video/ogg',
|
||||
'audio/ogg',
|
||||
'application/ogg',
|
||||
|
||||
// ISO/IEC base media file format
|
||||
'video/quicktime',
|
||||
'video/mp4',
|
||||
'audio/mp4',
|
||||
'video/x-m4v',
|
||||
'audio/x-m4a',
|
||||
'video/3gpp',
|
||||
'video/3gpp2',
|
||||
|
||||
'video/mpeg',
|
||||
'audio/mpeg',
|
||||
|
||||
'video/webm',
|
||||
'audio/webm',
|
||||
|
||||
'audio/aac',
|
||||
'audio/x-flac',
|
||||
'audio/vnd.wave',
|
||||
];
|
||||
/*
|
||||
https://github.com/sindresorhus/file-type/blob/main/supported.js
|
||||
https://github.com/sindresorhus/file-type/blob/main/core.js
|
||||
https://developer.mozilla.org/en-US/docs/Web/Media/Formats/Containers
|
||||
*/
|
57
packages/frontend/index copy.html
Normal file
@ -0,0 +1,57 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="application-name" content="Misskey">
|
||||
<meta name="referrer" content="origin">
|
||||
<meta name="theme-color" content="#86b300">
|
||||
<meta name="theme-color-orig" content="#86b300">
|
||||
<meta property="twitter:card" content="summary">
|
||||
<meta property="og:site_name" content="Misskey">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<link rel="icon" href="/assets/favicon.ico">
|
||||
<link rel="apple-touch-icon" href="/assets/apple-touch-icon.png">
|
||||
<link rel="manifest" href="/manifest.json">
|
||||
<link rel="search" type="application/opensearchdescription+xml" title="Misskey"
|
||||
href="https://xn--931a.moe/opensearch.xml">
|
||||
<link rel="prefetch" href="https://xn--931a.moe/assets/info.jpg">
|
||||
<link rel="prefetch" href="https://xn--931a.moe/assets/not-found.jpg">
|
||||
<link rel="prefetch" href="https://xn--931a.moe/assets/error.jpg">
|
||||
<link rel="stylesheet" href="/assets/tabler-icons/tabler-icons.min.css?v2.2.0">
|
||||
<!-- <link rel="modulepreload" href="/vite/client"> -->
|
||||
<title>Misskey</title>
|
||||
<meta name="description" content="✨🌎✨ A interplanetary communication platform ✨🚀✨">
|
||||
<meta property='og:title' content="Misskey">
|
||||
<meta property='og:description' content="✨🌎✨ A interplanetary communication platform ✨🚀✨">
|
||||
<meta property='og:image' content="img">
|
||||
<link rel="stylesheet" href="./style.css">
|
||||
<script src="./boot.js"></script>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<noscript>
|
||||
<p>
|
||||
JavaScriptを有効にしてください<br>
|
||||
Please turn on your JavaScript
|
||||
</p>
|
||||
</noscript>
|
||||
<div id="splash">
|
||||
<img src="/static-assets/splash.png">
|
||||
<div id="splashSpinner">
|
||||
<svg class="spinner bg" viewBox="0 0 152 152" xmlns="http://www.w3.org/2000/svg">
|
||||
<g transform="matrix(1,0,0,1,12,12)">
|
||||
<circle cx="64" cy="64" r="64" style="fill:none;stroke:currentColor;stroke-width:24px;" />
|
||||
</g>
|
||||
</svg>
|
||||
<svg class="spinner fg" viewBox="0 0 152 152" xmlns="http://www.w3.org/2000/svg">
|
||||
<g transform="matrix(1,0,0,1,12,12)">
|
||||
<path d="M128,64C128,28.654 99.346,0 64,0C99.346,0 128,28.654 128,64Z"
|
||||
style="fill:none;stroke:currentColor;stroke-width:24px;" />
|
||||
</g>
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
|
||||
</html>
|
60
packages/frontend/index.html
Normal file
@ -0,0 +1,60 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="application-name" content="Misskey">
|
||||
<meta name="referrer" content="origin">
|
||||
<meta name="theme-color" content="#86b300">
|
||||
<meta name="theme-color-orig" content="#86b300">
|
||||
<meta property="twitter:card" content="summary">
|
||||
<meta property="og:site_name" content="Misskey">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<link rel="icon" href="/assets/favicon.ico">
|
||||
<link rel="apple-touch-icon" href="/assets/apple-touch-icon.png">
|
||||
<link rel="manifest" href="/manifest.json">
|
||||
<link rel="search" type="application/opensearchdescription+xml" title="Misskey"
|
||||
href="https://xn--931a.moe/opensearch.xml">
|
||||
<link rel="prefetch" href="https://xn--931a.moe/assets/info.jpg">
|
||||
<link rel="prefetch" href="https://xn--931a.moe/assets/not-found.jpg">
|
||||
<link rel="prefetch" href="https://xn--931a.moe/assets/error.jpg">
|
||||
<!-- <link rel="stylesheet" href="/assets/tabler-icons/tabler-icons.min.css?v2.2.0"> -->
|
||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@tabler/icons@latest/iconfont/tabler-icons.min.css" />
|
||||
<!-- <link rel="modulepreload" href="/vite/client"> -->
|
||||
<title>Misskey</title>
|
||||
<meta name="description" content="✨🌎✨ A interplanetary communication platform ✨🚀✨">
|
||||
<meta property='og:title' content="Misskey">
|
||||
<meta property='og:description' content="✨🌎✨ A interplanetary communication platform ✨🚀✨">
|
||||
<meta property='og:image' content="img">
|
||||
<link rel="stylesheet" href="./style.css">
|
||||
<!-- <script src="./boot.js"></script> -->
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<noscript>
|
||||
<p>
|
||||
JavaScriptを有効にしてください<br>
|
||||
Please turn on your JavaScript
|
||||
</p>
|
||||
</noscript>
|
||||
<div id="splash">
|
||||
<img src="/static-assets/splash.png">
|
||||
<div id="splashSpinner">
|
||||
<svg class="spinner bg" viewBox="0 0 152 152" xmlns="http://www.w3.org/2000/svg">
|
||||
<g transform="matrix(1,0,0,1,12,12)">
|
||||
<circle cx="64" cy="64" r="64" style="fill:none;stroke:currentColor;stroke-width:24px;" />
|
||||
</g>
|
||||
</svg>
|
||||
<svg class="spinner fg" viewBox="0 0 152 152" xmlns="http://www.w3.org/2000/svg">
|
||||
<g transform="matrix(1,0,0,1,12,12)">
|
||||
<path d="M128,64C128,28.654 99.346,0 64,0C99.346,0 128,28.654 128,64Z"
|
||||
style="fill:none;stroke:currentColor;stroke-width:24px;" />
|
||||
</g>
|
||||
</svg>
|
||||
</div>
|
||||
<div id="app"></div>
|
||||
<script type="module" src="./src/init.ts"></script>
|
||||
</div>
|
||||
</body>
|
||||
|
||||
</html>
|
28
packages/frontend/manifest.json
Normal file
@ -0,0 +1,28 @@
|
||||
{
|
||||
"short_name": "Misskey",
|
||||
"name": "Misskey",
|
||||
"start_url": "/",
|
||||
"display": "standalone",
|
||||
"background_color": "#313a42",
|
||||
"theme_color": "#86b300",
|
||||
"icons": [
|
||||
{
|
||||
"src": "/static-assets/icons/192.png",
|
||||
"sizes": "192x192",
|
||||
"type": "image/png"
|
||||
},
|
||||
{
|
||||
"src": "/static-assets/icons/512.png",
|
||||
"sizes": "512x512",
|
||||
"type": "image/png"
|
||||
}
|
||||
],
|
||||
"share_target": {
|
||||
"action": "/share/",
|
||||
"params": {
|
||||
"title": "title",
|
||||
"text": "text",
|
||||
"url": "url"
|
||||
}
|
||||
}
|
||||
}
|
@ -173,18 +173,18 @@ if ($i && $i.token) {
|
||||
}
|
||||
//#endregion
|
||||
|
||||
const fetchInstanceMetaPromise = fetchInstance();
|
||||
// const fetchInstanceMetaPromise = fetchInstance();
|
||||
|
||||
fetchInstanceMetaPromise.then(() => {
|
||||
miLocalStorage.setItem('v', instance.version);
|
||||
// fetchInstanceMetaPromise.then(() => {
|
||||
// miLocalStorage.setItem('v', instance.version);
|
||||
|
||||
// Init service worker
|
||||
initializeSw();
|
||||
});
|
||||
// // Init service worker
|
||||
// initializeSw();
|
||||
// });
|
||||
|
||||
try {
|
||||
await fetchCustomEmojis();
|
||||
} catch (err) {}
|
||||
// try {
|
||||
// await fetchCustomEmojis();
|
||||
// } catch (err) {}
|
||||
|
||||
const app = createApp(
|
||||
window.location.search === '?zen' ? defineAsyncComponent(() => import('@/ui/zen.vue')) :
|
||||
|
@ -68,9 +68,11 @@ export default defineComponent({
|
||||
},
|
||||
|
||||
created() {
|
||||
os.api('meta', { detail: true }).then(meta => {
|
||||
this.meta = meta;
|
||||
});
|
||||
// os.api('meta', { detail: true }).then(meta => {
|
||||
// this.meta = meta;
|
||||
// });
|
||||
this.meta = { 'version': '13.6.1-simkey', 'uri': 'https://kokt.club', 'langs': [], 'repositoryUrl': 'https://github.com/misskey-dev/misskey', 'feedbackUrl': 'https://github.com/misskey-dev/misskey/issues/new', 'disableRegistration': false, 'emailRequiredForSignup': false, 'enableHcaptcha': false, 'hcaptchaSiteKey': null, 'enableRecaptcha': true, 'recaptchaSiteKey': '6LcSVvkhAAAAAHfnEeJhfI_XJLnfn8-gPHqZ4K5z', 'enableTurnstile': false, 'turnstileSiteKey': null, 'swPublickey': 'BJO5qBTaw1t5O448MwtH4jikC9JG0V682uVftWHPNU05-PE5-aAuj14zBLPPpgJP_ifpndg0cVn0Ka3oE453ZkA', 'themeColor': null, 'mascotImageUrl': '/assets/ai.png', 'bannerUrl': 'https://s3.kokt.club/files/a952f48b-7720-4b00-9d15-f044150db378.webp', 'errorImageUrl': 'https://xn--931a.moe/aiart/yubitun.png', 'iconUrl': 'https://s3.kokt.club/files/e2149c00-311a-4348-a548-0877831b4b08.png', 'backgroundImageUrl': 'https://s3.kokt.club/files/a952f48b-7720-4b00-9d15-f044150db378.webp', 'logoImageUrl': null, 'maxNoteTextLength': 8192, 'defaultLightTheme': null, 'defaultDarkTheme': null, 'ads': [], 'enableEmail': true, 'enableTwitterIntegration': false, 'enableGithubIntegration': false, 'enableDiscordIntegration': false, 'enableServiceWorker': true, 'translatorAvailable': true, 'policies': { 'gtlAvailable': true, 'ltlAvailable': true, 'canPublicNote': true, 'canInvite': false, 'canManageCustomEmojis': false, 'canHideAds': false, 'driveCapacityMb': 20480, 'pinLimit': 5, 'antennaLimit': 10, 'wordMuteLimit': 200, 'webhookLimit': 10, 'clipLimit': 20, 'noteEachClipsLimit': 2000, 'userListLimit': 15, 'userEachUserListsLimit': 100, 'rateLimitFactor': 1 } };
|
||||
|
||||
|
||||
os.api('stats').then(stats => {
|
||||
this.stats = stats;
|
||||
|
@ -1,23 +1,22 @@
|
||||
<template>
|
||||
<div v-if="meta">
|
||||
<XSetup v-if="meta.requireSetup"/>
|
||||
<XEntrance v-else/>
|
||||
<div>
|
||||
<XEntrance/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { computed } from 'vue';
|
||||
import XSetup from './welcome.setup.vue';
|
||||
import XEntrance from './welcome.entrance.a.vue';
|
||||
import { instanceName } from '@/config';
|
||||
import * as os from '@/os';
|
||||
import { definePageMetadata } from '@/scripts/page-metadata';
|
||||
|
||||
let meta = $ref(null);
|
||||
// let meta = $ref(null);
|
||||
const meta = { 'version': '13.6.1-simkey', 'uri': 'https://kokt.club', 'langs': [], 'repositoryUrl': 'https://github.com/misskey-dev/misskey', 'feedbackUrl': 'https://github.com/misskey-dev/misskey/issues/new', 'disableRegistration': false, 'emailRequiredForSignup': false, 'enableHcaptcha': false, 'hcaptchaSiteKey': null, 'enableRecaptcha': true, 'recaptchaSiteKey': '6LcSVvkhAAAAAHfnEeJhfI_XJLnfn8-gPHqZ4K5z', 'enableTurnstile': false, 'turnstileSiteKey': null, 'swPublickey': 'BJO5qBTaw1t5O448MwtH4jikC9JG0V682uVftWHPNU05-PE5-aAuj14zBLPPpgJP_ifpndg0cVn0Ka3oE453ZkA', 'themeColor': null, 'mascotImageUrl': '/assets/ai.png', 'bannerUrl': 'https://s3.kokt.club/files/a952f48b-7720-4b00-9d15-f044150db378.webp', 'errorImageUrl': 'https://xn--931a.moe/aiart/yubitun.png', 'iconUrl': 'https://s3.kokt.club/files/e2149c00-311a-4348-a548-0877831b4b08.png', 'backgroundImageUrl': 'https://s3.kokt.club/files/a952f48b-7720-4b00-9d15-f044150db378.webp', 'logoImageUrl': null, 'maxNoteTextLength': 8192, 'defaultLightTheme': null, 'defaultDarkTheme': null, 'ads': [], 'enableEmail': true, 'enableTwitterIntegration': false, 'enableGithubIntegration': false, 'enableDiscordIntegration': false, 'enableServiceWorker': true, 'translatorAvailable': true, 'policies': { 'gtlAvailable': true, 'ltlAvailable': true, 'canPublicNote': true, 'canInvite': false, 'canManageCustomEmojis': false, 'canHideAds': false, 'driveCapacityMb': 20480, 'pinLimit': 5, 'antennaLimit': 10, 'wordMuteLimit': 200, 'webhookLimit': 10, 'clipLimit': 20, 'noteEachClipsLimit': 2000, 'userListLimit': 15, 'userEachUserListsLimit': 100, 'rateLimitFactor': 1 } };
|
||||
|
||||
os.api('meta', { detail: true }).then(res => {
|
||||
meta = res;
|
||||
});
|
||||
// os.api('meta', { detail: true }).then(res => {
|
||||
// meta = res;
|
||||
// });
|
||||
|
||||
const headerActions = $computed(() => []);
|
||||
|
||||
|
@ -6,7 +6,7 @@ export function char2twemojiFilePath(char: string): string {
|
||||
if (!codes.includes('200d')) codes = codes.filter(x => x !== 'fe0f');
|
||||
codes = codes.filter(x => x && x.length);
|
||||
const fileName = codes.join('-');
|
||||
return `${twemojiSvgBase}/${fileName}.svg`;
|
||||
return `https://simkey.net/${twemojiSvgBase}/${fileName}.svg`;
|
||||
}
|
||||
|
||||
export function char2fluentEmojiFilePath(char: string): string {
|
||||
|
70
packages/frontend/style.css
Normal file
@ -0,0 +1,70 @@
|
||||
html {
|
||||
background-color: var(--bg);
|
||||
color: var(--fg);
|
||||
}
|
||||
|
||||
#splash {
|
||||
position: fixed;
|
||||
z-index: 10000;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100vw;
|
||||
height: 100vh;
|
||||
cursor: wait;
|
||||
background-color: var(--bg);
|
||||
opacity: 1;
|
||||
transition: opacity 0.5s ease;
|
||||
}
|
||||
|
||||
#splashIcon {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
margin: auto;
|
||||
width: 64px;
|
||||
height: 64px;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
#splashSpinner {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
margin: auto;
|
||||
display: inline-block;
|
||||
width: 28px;
|
||||
height: 28px;
|
||||
transform: translateY(70px);
|
||||
color: var(--accent);
|
||||
}
|
||||
#splashSpinner > .spinner {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 28px;
|
||||
height: 28px;
|
||||
fill-rule: evenodd;
|
||||
clip-rule: evenodd;
|
||||
stroke-linecap: round;
|
||||
stroke-linejoin: round;
|
||||
stroke-miterlimit: 1.5;
|
||||
}
|
||||
#splashSpinner > .spinner.bg {
|
||||
opacity: 0.275;
|
||||
}
|
||||
#splashSpinner > .spinner.fg {
|
||||
animation: splashSpinner 0.5s linear infinite;
|
||||
}
|
||||
|
||||
@keyframes splashSpinner {
|
||||
0% {
|
||||
transform: rotate(0deg);
|
||||
}
|
||||
100% {
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
@ -39,7 +39,7 @@ function toBase62(n: number): string {
|
||||
|
||||
export default defineConfig(({ command, mode }) => {
|
||||
return {
|
||||
base: '/vite/',
|
||||
// base: '/vite/',
|
||||
|
||||
plugins: [
|
||||
pluginVue({
|
||||
@ -53,7 +53,8 @@ export default defineConfig(({ command, mode }) => {
|
||||
alias: {
|
||||
'@/': __dirname + '/src/',
|
||||
'/client-assets/': __dirname + '/assets/',
|
||||
'/static-assets/': __dirname + '/../backend/assets/',
|
||||
// '/static-assets/': __dirname + '/../backend/assets/',
|
||||
'/static-assets/': __dirname + '/assets/',
|
||||
'/fluent-emojis/': __dirname + '/../../fluent-emojis/dist/',
|
||||
'/fluent-emoji/': __dirname + '/../../fluent-emojis/dist/',
|
||||
},
|
||||
@ -104,7 +105,8 @@ export default defineConfig(({ command, mode }) => {
|
||||
},
|
||||
},
|
||||
cssCodeSplit: true,
|
||||
outDir: __dirname + '/../../built/_vite_',
|
||||
// outDir: __dirname + '/../../built/_vite_',
|
||||
outDir: __dirname + '/built/',
|
||||
assetsDir: '.',
|
||||
emptyOutDir: false,
|
||||
sourcemap: process.env.NODE_ENV === 'development',
|
||||
|