なんかもうめっちゃ変えた

Closes #940
This commit is contained in:
syuilo 2017-11-23 05:43:00 +09:00
parent 1809800980
commit 3f8ebac466
46 changed files with 244 additions and 225 deletions

View File

@ -22,5 +22,5 @@ elasticsearch:
port: 9200 port: 9200
pass: '' pass: ''
recaptcha: recaptcha:
siteKey: hima site_key: hima
secretKey: saku secret_key: saku

View File

@ -22,5 +22,5 @@ elasticsearch:
port: 9200 port: 9200
pass: '' pass: ''
recaptcha: recaptcha:
siteKey: hima site_key: hima
secretKey: saku secret_key: saku

55
docs/config.md Normal file
View File

@ -0,0 +1,55 @@
``` yaml
# サーバーのメンテナ情報
maintainer:
# メンテナの名前
name:
# メンテナの連絡先(URLかmailto形式のURL)
url:
# プライマリURL
url:
# セカンダリURL
secondary_url:
# 待受ポート
port:
# TLSの設定
https:
# TLSを有効にするか否か
enable: false
key: null
cert: null
ca: null
# MongoDBの設定
mongodb:
host: localhost
port: 27017
db: misskey
user:
pass:
# Redisの設定
redis:
host: localhost
port: 6379
pass:
# reCAPTCHAの設定
recaptcha:
site_key:
secret_key:
# ServiceWrokerの設定
sw:
# VAPIDの公開鍵
public_key:
# VAPIDの秘密鍵
private_key:
```

View File

@ -36,6 +36,15 @@ Note that Misskey uses following subdomains:
Misskey requires reCAPTCHA tokens. Misskey requires reCAPTCHA tokens.
Please visit https://www.google.com/recaptcha/intro/ and generate keys. Please visit https://www.google.com/recaptcha/intro/ and generate keys.
*(optional)* Generating VAPID keys
----------------------------------------------------------------
If you want to enable ServiceWroker, you need to generate VAPID keys:
``` shell
npm install web-push -g
web-push generate-vapid-keys
```
*3.* Install dependencies *3.* Install dependencies
---------------------------------------------------------------- ----------------------------------------------------------------
Please install and setup these softwares: Please install and setup these softwares:
@ -51,24 +60,6 @@ Please install and setup these softwares:
*4.* Install Misskey *4.* Install Misskey
---------------------------------------------------------------- ----------------------------------------------------------------
There is **two ways** to install Misskey:
### WAY 1) Using built code (recommended)
We have the official release of Misskey.
The built code is automatically pushed to https://github.com/syuilo/misskey/tree/release after the CI test succeeds.
1. `git clone -b release git://github.com/syuilo/misskey.git`
2. `cd misskey`
3. `npm install`
#### Update
1. `git fetch`
2. `git reset --hard origin/release`
3. `npm install`
### WAY 2) Using source code
If you want to build Misskey manually, you can do it via the
`build` command after download the source code of Misskey and install dependencies:
1. `git clone -b master git://github.com/syuilo/misskey.git` 1. `git clone -b master git://github.com/syuilo/misskey.git`
2. `cd misskey` 2. `cd misskey`

View File

@ -37,6 +37,15 @@ Misskeyは以下のサブドメインを使います:
MisskeyはreCAPTCHAトークンを必要とします。 MisskeyはreCAPTCHAトークンを必要とします。
https://www.google.com/recaptcha/intro/ にアクセスしてトークンを生成してください。 https://www.google.com/recaptcha/intro/ にアクセスしてトークンを生成してください。
*(オプション)* VAPIDキーペアの生成
----------------------------------------------------------------
ServiceWorkerを有効にする場合、VAPIDキーペアを生成する必要があります:
``` shell
npm install web-push -g
web-push generate-vapid-keys
```
*3.* 依存関係をインストールする *3.* 依存関係をインストールする
---------------------------------------------------------------- ----------------------------------------------------------------
これらのソフトウェアをインストール・設定してください: これらのソフトウェアをインストール・設定してください:
@ -52,26 +61,6 @@ https://www.google.com/recaptcha/intro/ にアクセスしてトークンを生
*4.* Misskeyのインストール *4.* Misskeyのインストール
---------------------------------------------------------------- ----------------------------------------------------------------
Misskeyをインストールするには**2つの方法**があります:
### 方法 1) ビルドされたコードを利用する (推奨)
Misskeyには公式のリリースがあります。
ビルドされたコードはCIテストに合格した後、自動で https://github.com/syuilo/misskey/tree/release にpushされています。
1. `git clone -b release git://github.com/syuilo/misskey.git`
2. `cd misskey`
3. `npm install`
#### アップデートするには:
1. `git fetch`
2. `git reset --hard origin/release`
3. `npm install`
### 方法 2) ソースコードを利用する
> 注: この方法では正しくビルド・動作できることは保証されません。
Misskeyを手動でビルドしたい場合は、Misskeyのソースコードと依存関係をインストールした後、
`build`コマンドを用いることができます:
1. `git clone -b master git://github.com/syuilo/misskey.git` 1. `git clone -b master git://github.com/syuilo/misskey.git`
2. `cd misskey` 2. `cd misskey`

View File

@ -4,7 +4,11 @@ import Subscription from '../models/sw-subscription';
import config from '../../conf'; import config from '../../conf';
if (config.sw) { if (config.sw) {
push.setGCMAPIKey(config.sw.gcm_api_key); // アプリケーションの連絡先と、サーバーサイドの鍵ペアの情報を登録
push.setVapidDetails(
config.maintainer.url,
config.sw.public_key,
config.sw.private_key);
} }
export default async function(userId: mongo.ObjectID | string, type, body?) { export default async function(userId: mongo.ObjectID | string, type, body?) {

View File

@ -9,7 +9,7 @@ import generateUserToken from '../common/generate-native-user-token';
import config from '../../conf'; import config from '../../conf';
recaptcha.init({ recaptcha.init({
secret_key: config.recaptcha.secretKey secret_key: config.recaptcha.secret_key
}); });
const home = { const home = {

View File

@ -3,7 +3,6 @@
*/ */
import * as fs from 'fs'; import * as fs from 'fs';
import * as URL from 'url';
import * as yaml from 'js-yaml'; import * as yaml from 'js-yaml';
import isUrl = require('is-url'); import isUrl = require('is-url');
@ -23,7 +22,19 @@ export const path = process.env.NODE_ENV == 'test'
* *
*/ */
type Source = { type Source = {
maintainer: string; /**
*
*/
maintainer: {
/**
*
*/
name: string;
/**
* (URLかmailto形式のURL)
*/
url: string;
};
url: string; url: string;
secondary_url: string; secondary_url: string;
port: number; port: number;
@ -52,8 +63,8 @@ type Source = {
pass: string; pass: string;
}; };
recaptcha: { recaptcha: {
siteKey: string; site_key: string;
secretKey: string; secret_key: string;
}; };
accesslog?: string; accesslog?: string;
accesses?: { accesses?: {
@ -80,8 +91,8 @@ type Source = {
* Service Worker * Service Worker
*/ */
sw?: { sw?: {
gcm_sender_id: string; public_key: string;
gcm_api_key: string; private_key: string;
}; };
}; };
@ -114,14 +125,6 @@ export default function load() {
if (!isUrl(config.url)) urlError(config.url); if (!isUrl(config.url)) urlError(config.url);
if (!isUrl(config.secondary_url)) urlError(config.secondary_url); if (!isUrl(config.secondary_url)) urlError(config.secondary_url);
const url = URL.parse(config.url);
const head = url.host.split('.')[0];
if (head != 'misskey' && head != 'localhost') {
console.error(`プライマリドメインは、必ず「misskey」ドメインで始まっていなければなりません(現在の設定では「${head}」で始まっています)。例えば「https://misskey.xyz」「http://misskey.my.app.example.com」などが正しいプライマリURLです。`);
process.exit();
}
config.url = normalizeUrl(config.url); config.url = normalizeUrl(config.url);
config.secondary_url = normalizeUrl(config.secondary_url); config.secondary_url = normalizeUrl(config.secondary_url);

View File

@ -26,11 +26,11 @@
<hr> <hr>
<mk-channel-form if={ SIGNIN } channel={ channel } ref="form"/> <mk-channel-form if={ SIGNIN } channel={ channel } ref="form"/>
<div if={ !SIGNIN }> <div if={ !SIGNIN }>
<p>参加するには<a href={ CONFIG.url }>ログインまたは新規登録</a>してください</p> <p>参加するには<a href={ _URL_ }>ログインまたは新規登録</a>してください</p>
</div> </div>
<hr> <hr>
<footer> <footer>
<small><a href={ CONFIG.url }>Misskey</a> ver { version } (葵 aoi)</small> <small><a href={ _URL_ }>Misskey</a> ver { _VERSION_ } (葵 aoi)</small>
</footer> </footer>
</main> </main>
<style> <style>
@ -66,7 +66,6 @@
this.channel = null; this.channel = null;
this.posts = null; this.posts = null;
this.connection = new ChannelStream(this.id); this.connection = new ChannelStream(this.id);
this.version = VERSION;
this.unreadCount = 0; this.unreadCount = 0;
this.on('mount', () => { this.on('mount', () => {
@ -166,7 +165,7 @@
<mk-channel-post> <mk-channel-post>
<header> <header>
<a class="index" onclick={ reply }>{ post.index }:</a> <a class="index" onclick={ reply }>{ post.index }:</a>
<a class="name" href={ CONFIG.url + '/' + post.user.username }><b>{ post.user.name }</b></a> <a class="name" href={ _URL_ + '/' + post.user.username }><b>{ post.user.name }</b></a>
<mk-time time={ post.created_at }/> <mk-time time={ post.created_at }/>
<mk-time time={ post.created_at } mode="detail"/> <mk-time time={ post.created_at } mode="detail"/>
<span>ID:<i>{ post.user.username }</i></span> <span>ID:<i>{ post.user.username }</i></span>
@ -284,8 +283,6 @@
</style> </style>
<script> <script>
import CONFIG from '../../common/scripts/config';
this.mixin('api'); this.mixin('api');
this.channel = this.opts.channel; this.channel = this.opts.channel;
@ -357,7 +354,7 @@
}); });
}; };
window.open(CONFIG.url + '/selectdrive?multiple=true', window.open(_URL_ + '/selectdrive?multiple=true',
'drive_window', 'drive_window',
'height=500,width=800'); 'height=500,width=800');
}; };
@ -390,7 +387,7 @@
</mk-twitter-button> </mk-twitter-button>
<mk-line-button> <mk-line-button>
<div class="line-it-button" data-lang="ja" data-type="share-a" data-url={ CONFIG.chUrl } style="display: none;"></div> <div class="line-it-button" data-lang="ja" data-type="share-a" data-url={ _CH_URL_ } style="display: none;"></div>
<script> <script>
this.on('mount', () => { this.on('mount', () => {
const head = document.getElementsByTagName('head')[0]; const head = document.getElementsByTagName('head')[0];

View File

@ -1,10 +1,10 @@
<mk-header> <mk-header>
<div> <div>
<a href={ CONFIG.chUrl }>Index</a> | <a href={ CONFIG.url }>Misskey</a> <a href={ _CH_URL_ }>Index</a> | <a href={ _URL_ }>Misskey</a>
</div> </div>
<div> <div>
<a if={ !SIGNIN } href={ CONFIG.url }>ログイン(新規登録)</a> <a if={ !SIGNIN } href={ _URL_ }>ログイン(新規登録)</a>
<a if={ SIGNIN } href={ CONFIG.url + '/' + I.username }>{ I.username }</a> <a if={ SIGNIN } href={ _URL_ + '/' + I.username }>{ I.username }</a>
</div> </div>
<style> <style>
:scope :scope

View File

@ -3,11 +3,12 @@ import * as riot from 'riot';
import signout from './scripts/signout'; import signout from './scripts/signout';
import Progress from './scripts/loading'; import Progress from './scripts/loading';
import HomeStreamManager from './scripts/streaming/home-stream-manager'; import HomeStreamManager from './scripts/streaming/home-stream-manager';
import CONFIG from './scripts/config';
import api from './scripts/api'; import api from './scripts/api';
declare var VERSION: string; declare const _VERSION_: string;
declare var LANG: string; declare const _LANG_: string;
declare const _API_URL_: string;
declare const _SW_PUBLICKEY_: string;
/** /**
* Misskey Operating System * Misskey Operating System
@ -113,7 +114,7 @@ export default class MiOS extends EventEmitter {
} }
// Fetch user // Fetch user
fetch(`${CONFIG.apiUrl}/i`, { fetch(`${_API_URL_}/i`, {
method: 'POST', method: 'POST',
body: JSON.stringify({ body: JSON.stringify({
i: token i: token
@ -229,10 +230,15 @@ export default class MiOS extends EventEmitter {
this.swRegistration = registration; this.swRegistration = registration;
// Options of pushManager.subscribe // Options of pushManager.subscribe
// SEE: https://developer.mozilla.org/en-US/docs/Web/API/PushManager/subscribe#Parameters
const opts = { const opts = {
// A boolean indicating that the returned push subscription // A boolean indicating that the returned push subscription
// will only be used for messages whose effect is made visible to the user. // will only be used for messages whose effect is made visible to the user.
userVisibleOnly: true userVisibleOnly: true,
// A public key your push server will use to send
// messages to client apps via a push server.
applicationServerKey: urlBase64ToUint8Array(_SW_PUBLICKEY_)
}; };
// Subscribe push notification // Subscribe push notification
@ -257,7 +263,7 @@ export default class MiOS extends EventEmitter {
}); });
// The path of service worker script // The path of service worker script
const sw = `/sw.${VERSION}.${LANG}.js`; const sw = `/sw.${_VERSION_}.${_LANG_}.js`;
// Register service worker // Register service worker
navigator.serviceWorker.register(sw).then(registration => { navigator.serviceWorker.register(sw).then(registration => {
@ -310,3 +316,22 @@ export default class MiOS extends EventEmitter {
}); });
} }
} }
/**
* Convert the URL safe base64 string to a Uint8Array
* @param base64String base64 string
*/
function urlBase64ToUint8Array(base64String: string): Uint8Array {
const padding = '='.repeat((4 - base64String.length % 4) % 4);
const base64 = (base64String + padding)
.replace(/\-/g, '+')
.replace(/_/g, '/');
const rawData = window.atob(base64);
const outputArray = new Uint8Array(rawData.length);
for (let i = 0; i < rawData.length; ++i) {
outputArray[i] = rawData.charCodeAt(i);
}
return outputArray;
}

View File

@ -2,7 +2,7 @@
* API Request * API Request
*/ */
import CONFIG from './config'; declare const _API_URL_: string;
let spinner = null; let spinner = null;
let pending = 0; let pending = 0;
@ -26,7 +26,7 @@ export default (i, endpoint, data = {}): Promise<{ [x: string]: any }> => {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
// Send request // Send request
fetch(endpoint.indexOf('://') > -1 ? endpoint : `${CONFIG.apiUrl}/${endpoint}`, { fetch(endpoint.indexOf('://') > -1 ? endpoint : `${_API_URL_}/${endpoint}`, {
method: 'POST', method: 'POST',
body: JSON.stringify(data), body: JSON.stringify(data),
credentials: endpoint === 'signin' ? 'include' : 'omit' credentials: endpoint === 'signin' ? 'include' : 'omit'

View File

@ -1,12 +1,12 @@
import MiOS from '../mios'; import MiOS from '../mios';
declare var VERSION: string; declare const _VERSION_: string;
export default async function(mios: MiOS) { export default async function(mios: MiOS) {
const meta = await mios.getMeta(); const meta = await mios.getMeta();
if (meta.version != VERSION) { if (meta.version != _VERSION_) {
localStorage.setItem('should-refresh', 'true'); localStorage.setItem('should-refresh', 'true');
alert('%i18n:common.update-available%'.replace('{newer}', meta.version).replace('{current}', VERSION)); alert('%i18n:common.update-available%'.replace('{newer}', meta.version).replace('{current}', _VERSION_));
} }
} }

View File

@ -1,27 +0,0 @@
const _url = new URL(location.href);
const isRoot = _url.host == 'localhost'
? true
: _url.host.split('.')[0] == 'misskey';
const host = isRoot ? _url.host : _url.host.substring(_url.host.indexOf('.') + 1, _url.host.length);
const scheme = _url.protocol;
const url = `${scheme}//${host}`;
const apiUrl = `${scheme}//api.${host}`;
const chUrl = `${scheme}//ch.${host}`;
const devUrl = `${scheme}//dev.${host}`;
const aboutUrl = `${scheme}//about.${host}`;
const statsUrl = `${scheme}//stats.${host}`;
const statusUrl = `${scheme}//status.${host}`;
export default {
host,
scheme,
url,
apiUrl,
chUrl,
devUrl,
aboutUrl,
statsUrl,
statusUrl
};

View File

@ -1,7 +1,7 @@
import CONFIG from './config'; declare const _HOST_: string;
export default () => { export default () => {
localStorage.removeItem('me'); localStorage.removeItem('me');
document.cookie = `i=; domain=.${CONFIG.host}; expires=Thu, 01 Jan 1970 00:00:01 GMT;`; document.cookie = `i=; domain=.${_HOST_}; expires=Thu, 01 Jan 1970 00:00:01 GMT;`;
location.href = '/'; location.href = '/';
}; };

View File

@ -1,6 +1,7 @@
declare const _API_URL_: string;
import { EventEmitter } from 'eventemitter3'; import { EventEmitter } from 'eventemitter3';
import * as ReconnectingWebsocket from 'reconnecting-websocket'; import * as ReconnectingWebsocket from 'reconnecting-websocket';
import CONFIG from '../config';
/** /**
* Misskey stream connection * Misskey stream connection
@ -24,7 +25,7 @@ export default class Connection extends EventEmitter {
this.state = 'initializing'; this.state = 'initializing';
this.buffer = []; this.buffer = [];
const host = CONFIG.apiUrl.replace('http', 'ws'); const host = _API_URL_.replace('http', 'ws');
const query = params const query = params
? Object.keys(params) ? Object.keys(params)
.map(k => encodeURIComponent(k) + '=' + encodeURIComponent(params[k])) .map(k => encodeURIComponent(k) + '=' + encodeURIComponent(params[k]))

View File

@ -1,6 +1,7 @@
declare const _URL_: string;
import * as riot from 'riot'; import * as riot from 'riot';
import * as pictograph from 'pictograph'; import * as pictograph from 'pictograph';
import CONFIG from './config';
const escape = text => const escape = text =>
text text
@ -26,7 +27,7 @@ export default (tokens, shouldBreak) => {
case 'link': case 'link':
return `<a class="link" href="${escape(token.url)}" target="_blank" title="${escape(token.url)}">${escape(token.title)}</a>`; return `<a class="link" href="${escape(token.url)}" target="_blank" title="${escape(token.url)}">${escape(token.title)}</a>`;
case 'mention': case 'mention':
return `<a href="${CONFIG.url + '/' + escape(token.username)}" target="_blank" data-user-preview="${token.content}" ${me && me.username == token.username ? 'data-is-me' : ''}>${token.content}</a>`; return `<a href="${_URL_ + '/' + escape(token.username)}" target="_blank" data-user-preview="${token.content}" ${me && me.username == token.username ? 'data-is-me' : ''}>${token.content}</a>`;
case 'hashtag': // TODO case 'hashtag': // TODO
return `<a>${escape(token.content)}</a>`; return `<a>${escape(token.content)}</a>`;
case 'code': case 'code':

View File

@ -170,8 +170,6 @@
</style> </style>
<script> <script>
import CONFIG from '../../common/scripts/config';
this.on('mount', () => { this.on('mount', () => {
this.update({ this.update({
network: navigator.onLine network: navigator.onLine
@ -193,7 +191,7 @@
}); });
// Check misskey server is available // Check misskey server is available
fetch(`${CONFIG.apiUrl}/meta`).then(() => { fetch(`${_API_URL_}/meta`).then(() => {
this.update({ this.update({
end: true, end: true,
server: true server: true

View File

@ -3,7 +3,7 @@
<h1>Misskeyとは</h1> <h1>Misskeyとは</h1>
<p><ruby>Misskey<rt>みすきー</rt></ruby>は、<a href="http://syuilo.com" target="_blank">syuilo</a>が2014年くらいから<a href="https://github.com/syuilo/misskey" target="_blank">オープンソースで</a>開発・運営を行っている、ミニブログベースのSNSです。</p> <p><ruby>Misskey<rt>みすきー</rt></ruby>は、<a href="http://syuilo.com" target="_blank">syuilo</a>が2014年くらいから<a href="https://github.com/syuilo/misskey" target="_blank">オープンソースで</a>開発・運営を行っている、ミニブログベースのSNSです。</p>
<p>無料で誰でも利用でき、広告も掲載していません。</p> <p>無料で誰でも利用でき、広告も掲載していません。</p>
<p><a href={ CONFIG.aboutUrl } target="_blank">もっと知りたい方はこちら</a></p> <p><a href={ _ABOUT_URL_ } target="_blank">もっと知りたい方はこちら</a></p>
</article> </article>
<style> <style>
:scope :scope

View File

@ -1,5 +1,5 @@
<mk-nav-links> <mk-nav-links>
<a href={ CONFIG.aboutUrl }>%i18n:common.tags.mk-nav-links.about%</a><i></i><a href={ CONFIG.statsUrl }>%i18n:common.tags.mk-nav-links.stats%</a><i></i><a href={ CONFIG.statusUrl }>%i18n:common.tags.mk-nav-links.status%</a><i></i><a href="http://zawazawa.jp/misskey/">%i18n:common.tags.mk-nav-links.wiki%</a><i></i><a href="https://github.com/syuilo/misskey/blob/master/DONORS.md">%i18n:common.tags.mk-nav-links.donors%</a><i></i><a href="https://github.com/syuilo/misskey">%i18n:common.tags.mk-nav-links.repository%</a><i></i><a href={ CONFIG.devUrl }>%i18n:common.tags.mk-nav-links.develop%</a><i></i><a href="https://twitter.com/misskey_xyz" target="_blank">Follow us on <i class="fa fa-twitter"></i></a> <a href={ _ABOUT_URL_ }>%i18n:common.tags.mk-nav-links.about%</a><i></i><a href={ _STATS_URL_ }>%i18n:common.tags.mk-nav-links.stats%</a><i></i><a href={ _STATUS_URL_ }>%i18n:common.tags.mk-nav-links.status%</a><i></i><a href="http://zawazawa.jp/misskey/">%i18n:common.tags.mk-nav-links.wiki%</a><i></i><a href="https://github.com/syuilo/misskey/blob/master/DONORS.md">%i18n:common.tags.mk-nav-links.donors%</a><i></i><a href="https://github.com/syuilo/misskey">%i18n:common.tags.mk-nav-links.repository%</a><i></i><a href={ _DEV_URL_ }>%i18n:common.tags.mk-nav-links.develop%</a><i></i><a href="https://twitter.com/misskey_xyz" target="_blank">Follow us on <i class="fa fa-twitter"></i></a>
<style> <style>
:scope :scope
display inline display inline

View File

@ -3,7 +3,7 @@
<label class="username"> <label class="username">
<p class="caption"><i class="fa fa-at"></i>%i18n:common.tags.mk-signup.username%</p> <p class="caption"><i class="fa fa-at"></i>%i18n:common.tags.mk-signup.username%</p>
<input ref="username" type="text" pattern="^[a-zA-Z0-9-]{3,20}$" placeholder="a~z、A~Z、0~9、-" autocomplete="off" required="required" onkeyup={ onChangeUsername }/> <input ref="username" type="text" pattern="^[a-zA-Z0-9-]{3,20}$" placeholder="a~z、A~Z、0~9、-" autocomplete="off" required="required" onkeyup={ onChangeUsername }/>
<p class="profile-page-url-preview" if={ refs.username.value != '' && username-state != 'invalidFormat' && username-state != 'minRange' && username-state != 'maxRange' }>{ CONFIG.url + '/' + refs.username.value }</p> <p class="profile-page-url-preview" if={ refs.username.value != '' && username-state != 'invalidFormat' && username-state != 'minRange' && username-state != 'maxRange' }>{ _URL_ + '/' + refs.username.value }</p>
<p class="info" if={ usernameState == 'wait' } style="color:#999"><i class="fa fa-fw fa-spinner fa-pulse"></i>%i18n:common.tags.mk-signup.checking%</p> <p class="info" if={ usernameState == 'wait' } style="color:#999"><i class="fa fa-fw fa-spinner fa-pulse"></i>%i18n:common.tags.mk-signup.checking%</p>
<p class="info" if={ usernameState == 'ok' } style="color:#3CB7B5"><i class="fa fa-fw fa-check"></i>%i18n:common.tags.mk-signup.available%</p> <p class="info" if={ usernameState == 'ok' } style="color:#3CB7B5"><i class="fa fa-fw fa-check"></i>%i18n:common.tags.mk-signup.available%</p>
<p class="info" if={ usernameState == 'unavailable' } style="color:#FF1161"><i class="fa fa-fw fa-exclamation-triangle"></i>%i18n:common.tags.mk-signup.unavailable%</p> <p class="info" if={ usernameState == 'unavailable' } style="color:#FF1161"><i class="fa fa-fw fa-exclamation-triangle"></i>%i18n:common.tags.mk-signup.unavailable%</p>
@ -30,7 +30,7 @@
</label> </label>
<label class="recaptcha"> <label class="recaptcha">
<p class="caption"><i class="fa fa-toggle-on" if={ recaptchaed }></i><i class="fa fa-toggle-off" if={ !recaptchaed }></i>%i18n:common.tags.mk-signup.recaptcha%</p> <p class="caption"><i class="fa fa-toggle-on" if={ recaptchaed }></i><i class="fa fa-toggle-off" if={ !recaptchaed }></i>%i18n:common.tags.mk-signup.recaptcha%</p>
<div if={ recaptcha } class="g-recaptcha" data-callback="onRecaptchaed" data-expired-callback="onRecaptchaExpired" data-sitekey={ recaptcha.siteKey }></div> <div if={ recaptcha } class="g-recaptcha" data-callback="onRecaptchaed" data-expired-callback="onRecaptchaExpired" data-sitekey={ recaptcha.site_key }></div>
</label> </label>
<label class="agree-tou"> <label class="agree-tou">
<input name="agree-tou" type="checkbox" autocomplete="off" required="required"/> <input name="agree-tou" type="checkbox" autocomplete="off" required="required"/>
@ -193,11 +193,9 @@
}; };
this.on('mount', () => { this.on('mount', () => {
fetch('/config.json').then(res => {
res.json().then(conf => {
this.update({ this.update({
recaptcha: { recaptcha: {
siteKey: conf.recaptcha.siteKey site_key: _RECAPTCHA_SITEKEY_
} }
}); });
@ -206,8 +204,6 @@
script.setAttribute('src', 'https://www.google.com/recaptcha/api.js'); script.setAttribute('src', 'https://www.google.com/recaptcha/api.js');
head.appendChild(script); head.appendChild(script);
}); });
});
});
this.onChangeUsername = () => { this.onChangeUsername = () => {
const username = this.refs.username.value; const username = this.refs.username.value;

View File

@ -1,10 +1,10 @@
<mk-twitter-setting> <mk-twitter-setting>
<p>%i18n:common.tags.mk-twitter-setting.description%<a href={ CONFIG.aboutUrl + '/link-to-twitter' } target="_blank">%i18n:common.tags.mk-twitter-setting.detail%</a></p> <p>%i18n:common.tags.mk-twitter-setting.description%<a href={ _ABOUT_URL_ + '/link-to-twitter' } target="_blank">%i18n:common.tags.mk-twitter-setting.detail%</a></p>
<p class="account" if={ I.twitter } title={ 'Twitter ID: ' + I.twitter.user_id }>%i18n:common.tags.mk-twitter-setting.connected-to%: <a href={ 'https://twitter.com/' + I.twitter.screen_name } target="_blank">@{ I.twitter.screen_name }</a></p> <p class="account" if={ I.twitter } title={ 'Twitter ID: ' + I.twitter.user_id }>%i18n:common.tags.mk-twitter-setting.connected-to%: <a href={ 'https://twitter.com/' + I.twitter.screen_name } target="_blank">@{ I.twitter.screen_name }</a></p>
<p> <p>
<a href={ CONFIG.apiUrl + '/connect/twitter' } target="_blank" onclick={ connect }>{ I.twitter ? '%i18n:common.tags.mk-twitter-setting.reconnect%' : '%i18n:common.tags.mk-twitter-setting.connect%' }</a> <a href={ _API_URL_ + '/connect/twitter' } target="_blank" onclick={ connect }>{ I.twitter ? '%i18n:common.tags.mk-twitter-setting.reconnect%' : '%i18n:common.tags.mk-twitter-setting.connect%' }</a>
<span if={ I.twitter }> or </span> <span if={ I.twitter }> or </span>
<a href={ CONFIG.apiUrl + '/disconnect/twitter' } target="_blank" if={ I.twitter } onclick={ disconnect }>%i18n:common.tags.mk-twitter-setting.disconnect%</a> <a href={ _API_URL_ + '/disconnect/twitter' } target="_blank" if={ I.twitter } onclick={ disconnect }>%i18n:common.tags.mk-twitter-setting.disconnect%</a>
</p> </p>
<p class="id" if={ I.twitter }>Twitter ID: { I.twitter.user_id }</p> <p class="id" if={ I.twitter }>Twitter ID: { I.twitter.user_id }</p>
<style> <style>
@ -25,8 +25,6 @@
color #8899a6 color #8899a6
</style> </style>
<script> <script>
import CONFIG from '../scripts/config';
this.mixin('i'); this.mixin('i');
this.form = null; this.form = null;
@ -47,7 +45,7 @@
this.connect = e => { this.connect = e => {
e.preventDefault(); e.preventDefault();
this.form = window.open(CONFIG.apiUrl + '/connect/twitter', this.form = window.open(_API_URL_ + '/connect/twitter',
'twitter_connect_window', 'twitter_connect_window',
'height=570,width=520'); 'height=570,width=520');
return false; return false;
@ -55,7 +53,7 @@
this.disconnect = e => { this.disconnect = e => {
e.preventDefault(); e.preventDefault();
window.open(CONFIG.apiUrl + '/disconnect/twitter', window.open(_API_URL_ + '/disconnect/twitter',
'twitter_disconnect_window', 'twitter_disconnect_window',
'height=570,width=520'); 'height=570,width=520');
return false; return false;

View File

@ -172,7 +172,7 @@
if (folder) data.append('folder_id', folder); if (folder) data.append('folder_id', folder);
const xhr = new XMLHttpRequest(); const xhr = new XMLHttpRequest();
xhr.open('POST', this.CONFIG.apiUrl + '/drive/files/create', true); xhr.open('POST', _API_URL_ + '/drive/files/create', true);
xhr.onload = e => { xhr.onload = e => {
const driveFile = JSON.parse(e.target.response); const driveFile = JSON.parse(e.target.response);

View File

@ -1,7 +1,7 @@
require('fuckadblock'); require('fuckadblock');
import dialog from './dialog'; import dialog from './dialog';
declare var fuckAdBlock: any; declare const fuckAdBlock: any;
export default () => { export default () => {
if (fuckAdBlock === undefined) { if (fuckAdBlock === undefined) {

View File

@ -1,5 +1,6 @@
declare const _API_URL_: string;
import * as riot from 'riot'; import * as riot from 'riot';
import CONFIG from '../../common/scripts/config';
import dialog from './dialog'; import dialog from './dialog';
import api from '../../common/scripts/api'; import api from '../../common/scripts/api';
@ -44,7 +45,7 @@ export default (I, cb, file = null) => {
if (folder) data.append('folder_id', folder.id); if (folder) data.append('folder_id', folder.id);
const xhr = new XMLHttpRequest(); const xhr = new XMLHttpRequest();
xhr.open('POST', CONFIG.apiUrl + '/drive/files/create', true); xhr.open('POST', _API_URL_ + '/drive/files/create', true);
xhr.onload = e => { xhr.onload = e => {
const file = JSON.parse((e.target as any).response); const file = JSON.parse((e.target as any).response);
progress.close(); progress.close();

View File

@ -1,5 +1,6 @@
declare const _API_URL_: string;
import * as riot from 'riot'; import * as riot from 'riot';
import CONFIG from '../../common/scripts/config';
import dialog from './dialog'; import dialog from './dialog';
import api from '../../common/scripts/api'; import api from '../../common/scripts/api';
@ -44,7 +45,7 @@ export default (I, cb, file = null) => {
if (folder) data.append('folder_id', folder.id); if (folder) data.append('folder_id', folder.id);
const xhr = new XMLHttpRequest(); const xhr = new XMLHttpRequest();
xhr.open('POST', CONFIG.apiUrl + '/drive/files/create', true); xhr.open('POST', _API_URL_ + '/drive/files/create', true);
xhr.onload = e => { xhr.onload = e => {
const file = JSON.parse((e.target as any).response); const file = JSON.parse((e.target as any).response);
progress.close(); progress.close();

View File

@ -72,7 +72,7 @@
const length = Math.min(canvW, canvH) / 4; const length = Math.min(canvW, canvH) / 4;
const uv = new Vec2(Math.sin(angle), -Math.cos(angle)); const uv = new Vec2(Math.sin(angle), -Math.cos(angle));
ctx.beginPath(); ctx.beginPath();
ctx.strokeStyle = THEME_COLOR; ctx.strokeStyle = _THEME_COLOR_;
ctx.lineWidth = 2; ctx.lineWidth = 2;
ctx.moveTo(canvW / 2 - uv.x * length / 5, canvH / 2 - uv.y * length / 5); ctx.moveTo(canvW / 2 - uv.x * length / 5, canvH / 2 - uv.y * length / 5);
ctx.lineTo(canvW / 2 + uv.x * length, canvH / 2 + uv.y * length); ctx.lineTo(canvW / 2 + uv.x * length, canvH / 2 + uv.y * length);

View File

@ -28,8 +28,6 @@
</style> </style>
<script> <script>
import CONFIG from '../../../common/scripts/config';
this.mixin('api'); this.mixin('api');
this.folder = this.opts.folder ? this.opts.folder : null; this.folder = this.opts.folder ? this.opts.folder : null;
@ -37,9 +35,9 @@
this.popout = () => { this.popout = () => {
const folder = this.refs.window.refs.browser.folder; const folder = this.refs.window.refs.browser.folder;
if (folder) { if (folder) {
return `${CONFIG.url}/i/drive/folder/${folder.id}`; return `${_URL_}/i/drive/folder/${folder.id}`;
} else { } else {
return `${CONFIG.url}/i/drive`; return `${_URL_}/i/drive`;
} }
}; };

View File

@ -114,8 +114,8 @@
let broadcasts = []; let broadcasts = [];
if (meta.broadcasts) { if (meta.broadcasts) {
meta.broadcasts.forEach(broadcast => { meta.broadcasts.forEach(broadcast => {
if (broadcast[LANG]) { if (broadcast[_LANG_]) {
broadcasts.push(broadcast[LANG]); broadcasts.push(broadcast[_LANG_]);
} }
}); });
} }

View File

@ -193,7 +193,7 @@
<mk-channel-post> <mk-channel-post>
<header> <header>
<a class="index" onclick={ reply }>{ post.index }:</a> <a class="index" onclick={ reply }>{ post.index }:</a>
<a class="name" href={ CONFIG.url + '/' + post.user.username }><b>{ post.user.name }</b></a> <a class="name" href={ _URL_ + '/' + post.user.username }><b>{ post.user.name }</b></a>
<span>ID:<i>{ post.user.username }</i></span> <span>ID:<i>{ post.user.username }</i></span>
</header> </header>
<div> <div>

View File

@ -1,5 +1,5 @@
<mk-version-home-widget> <mk-version-home-widget>
<p>ver { version } (葵 aoi)</p> <p>ver { _VERSION_ } (葵 aoi)</p>
<style> <style>
:scope :scope
display block display block
@ -16,7 +16,5 @@
</style> </style>
<script> <script>
this.mixin('widget'); this.mixin('widget');
this.version = VERSION;
</script> </script>
</mk-version-home-widget> </mk-version-home-widget>

View File

@ -19,11 +19,9 @@
</style> </style>
<script> <script>
import CONFIG from '../../../common/scripts/config';
this.user = this.opts.user; this.user = this.opts.user;
this.popout = `${CONFIG.url}/i/messaging/${this.user.username}`; this.popout = `${_URL_}/i/messaging/${this.user.username}`;
this.on('mount', () => { this.on('mount', () => {
this.refs.window.on('closed', () => { this.refs.window.on('closed', () => {

View File

@ -150,7 +150,7 @@
</mk-entrance> </mk-entrance>
<mk-entrance-signin> <mk-entrance-signin>
<a class="help" href={ CONFIG.aboutUrl + '/help' } title="お困りですか?"><i class="fa fa-question"></i></a> <a class="help" href={ _ABOUT_URL_ + '/help' } title="お困りですか?"><i class="fa fa-question"></i></a>
<div class="form"> <div class="form">
<h1><img if={ user } src={ user.avatar_url + '?thumbnail&size=32' }/> <h1><img if={ user } src={ user.avatar_url + '?thumbnail&size=32' }/>
<p>{ user ? user.name : 'アカウント' }</p> <p>{ user ? user.name : 'アカウント' }</p>

View File

@ -112,7 +112,7 @@
</header> </header>
<div class="body"> <div class="body">
<div class="text" ref="text"> <div class="text" ref="text">
<p class="channel" if={ p.channel != null }><a href={ CONFIG.chUrl + '/' + p.channel.id } target="_blank">{ p.channel.title }</a>:</p> <p class="channel" if={ p.channel != null }><a href={ _CH_URL_ + '/' + p.channel.id } target="_blank">{ p.channel.title }</a>:</p>
<a class="reply" if={ p.reply }> <a class="reply" if={ p.reply }>
<i class="fa fa-reply"></i> <i class="fa fa-reply"></i>
</a> </a>

View File

@ -379,7 +379,7 @@
<ul> <ul>
<virtual if={ SIGNIN }> <virtual if={ SIGNIN }>
<li class="home { active: page == 'home' }"> <li class="home { active: page == 'home' }">
<a href={ CONFIG.url }> <a href={ _URL_ }>
<i class="fa fa-home"></i> <i class="fa fa-home"></i>
<p>%i18n:desktop.tags.mk-ui-header-nav.home%</p> <p>%i18n:desktop.tags.mk-ui-header-nav.home%</p>
</a> </a>
@ -393,7 +393,7 @@
</li> </li>
</virtual> </virtual>
<li class="ch"> <li class="ch">
<a href={ CONFIG.chUrl } target="_blank"> <a href={ _CH_URL_ } target="_blank">
<i class="fa fa-television"></i> <i class="fa fa-television"></i>
<p>%i18n:desktop.tags.mk-ui-header-nav.ch%</p> <p>%i18n:desktop.tags.mk-ui-header-nav.ch%</p>
</a> </a>

View File

@ -2,13 +2,12 @@
* App initializer * App initializer
*/ */
declare var VERSION: string; declare const _VERSION_: string;
declare var LANG: string; declare const _LANG_: string;
declare const _HOST_: string;
import * as riot from 'riot';
import checkForUpdate from './common/scripts/check-for-update'; import checkForUpdate from './common/scripts/check-for-update';
import mixin from './common/mixins'; import mixin from './common/mixins';
import CONFIG from './common/scripts/config';
import MiOS from './common/mios'; import MiOS from './common/mios';
require('./common/tags'); require('./common/tags');
@ -16,15 +15,15 @@ require('./common/tags');
* APP ENTRY POINT! * APP ENTRY POINT!
*/ */
console.info(`Misskey v${VERSION} (葵 aoi)`); console.info(`Misskey v${_VERSION_} (葵 aoi)`);
if (CONFIG.host != 'localhost') { if (_HOST_ != 'localhost') {
document.domain = CONFIG.host; document.domain = _HOST_;
} }
{ // Set lang attr { // Set lang attr
const html = document.documentElement; const html = document.documentElement;
html.setAttribute('lang', LANG); html.setAttribute('lang', _LANG_);
} }
{ // Set description meta tag { // Set description meta tag
@ -35,9 +34,6 @@ if (CONFIG.host != 'localhost') {
head.appendChild(meta); head.appendChild(meta);
} }
// Set global configuration
(riot as any).mixin({ CONFIG });
// iOSでプライベートモードだとlocalStorageが使えないので既存のメソッドを上書きする // iOSでプライベートモードだとlocalStorageが使えないので既存のメソッドを上書きする
try { try {
localStorage.setItem('kyoppie', 'yuppie'); localStorage.setItem('kyoppie', 'yuppie');
@ -94,7 +90,7 @@ function panic(e) {
+ '<hr>' + '<hr>'
+ `<p>エラーコード: ${e.toString()}</p>` + `<p>エラーコード: ${e.toString()}</p>`
+ `<p>ブラウザ バージョン: ${navigator.userAgent}</p>` + `<p>ブラウザ バージョン: ${navigator.userAgent}</p>`
+ `<p>クライアント バージョン: ${VERSION}</p>` + `<p>クライアント バージョン: ${_VERSION_}</p>`
+ '<hr>' + '<hr>'
+ '<p>問題が解決しない場合は、上記の情報をお書き添えの上 syuilotan@yahoo.co.jp までご連絡ください。</p>' + '<p>問題が解決しない場合は、上記の情報をお書き添えの上 syuilotan@yahoo.co.jp までご連絡ください。</p>'
+ '<p>Thank you for using Misskey.</p>' + '<p>Thank you for using Misskey.</p>'

View File

@ -29,7 +29,7 @@
<ul> <ul>
<li><a onclick={ signout }><i class="fa fa-power-off"></i>%i18n:mobile.tags.mk-settings-page.signout%</a></li> <li><a onclick={ signout }><i class="fa fa-power-off"></i>%i18n:mobile.tags.mk-settings-page.signout%</a></li>
</ul> </ul>
<p><small>ver { version } (葵 aoi)</small></p> <p><small>ver { _VERSION_ } (葵 aoi)</small></p>
<style> <style>
:scope :scope
display block display block
@ -97,7 +97,5 @@
this.signout = signout; this.signout = signout;
this.mixin('i'); this.mixin('i');
this.version = VERSION;
</script> </script>
</mk-settings> </mk-settings>

View File

@ -164,7 +164,7 @@
</header> </header>
<div class="body"> <div class="body">
<div class="text" ref="text"> <div class="text" ref="text">
<p class="channel" if={ p.channel != null }><a href={ CONFIG.chUrl + '/' + p.channel.id } target="_blank">{ p.channel.title }</a>:</p> <p class="channel" if={ p.channel != null }><a href={ _CH_URL_ + '/' + p.channel.id } target="_blank">{ p.channel.title }</a>:</p>
<a class="reply" if={ p.reply }> <a class="reply" if={ p.reply }>
<i class="fa fa-reply"></i> <i class="fa fa-reply"></i>
</a> </a>

View File

@ -239,7 +239,7 @@
<li><a href="/i/messaging"><i class="fa fa-comments-o"></i>%i18n:mobile.tags.mk-ui-nav.messaging%<i class="i fa fa-circle" if={ hasUnreadMessagingMessages }></i><i class="fa fa-angle-right"></i></a></li> <li><a href="/i/messaging"><i class="fa fa-comments-o"></i>%i18n:mobile.tags.mk-ui-nav.messaging%<i class="i fa fa-circle" if={ hasUnreadMessagingMessages }></i><i class="fa fa-angle-right"></i></a></li>
</ul> </ul>
<ul> <ul>
<li><a href={ CONFIG.chUrl } target="_blank"><i class="fa fa-television"></i>%i18n:mobile.tags.mk-ui-nav.ch%<i class="fa fa-angle-right"></i></a></li> <li><a href={ _CH_URL_ } target="_blank"><i class="fa fa-television"></i>%i18n:mobile.tags.mk-ui-nav.ch%<i class="fa fa-angle-right"></i></a></li>
<li><a href="/i/drive"><i class="fa fa-cloud"></i>%i18n:mobile.tags.mk-ui-nav.drive%<i class="fa fa-angle-right"></i></a></li> <li><a href="/i/drive"><i class="fa fa-cloud"></i>%i18n:mobile.tags.mk-ui-nav.drive%<i class="fa fa-angle-right"></i></a></li>
</ul> </ul>
<ul> <ul>
@ -249,7 +249,7 @@
<li><a href="/i/settings"><i class="fa fa-cog"></i>%i18n:mobile.tags.mk-ui-nav.settings%<i class="fa fa-angle-right"></i></a></li> <li><a href="/i/settings"><i class="fa fa-cog"></i>%i18n:mobile.tags.mk-ui-nav.settings%<i class="fa fa-angle-right"></i></a></li>
</ul> </ul>
</div> </div>
<a href={ CONFIG.aboutUrl }><p class="about">%i18n:mobile.tags.mk-ui-nav.about%</p></a> <a href={ _ABOUT_URL_ }><p class="about">%i18n:mobile.tags.mk-ui-nav.about%</p></a>
</div> </div>
<style> <style>
:scope :scope

View File

@ -4,7 +4,7 @@
<mk-users stats={ stats }/> <mk-users stats={ stats }/>
<mk-posts stats={ stats }/> <mk-posts stats={ stats }/>
</main> </main>
<footer><a href={ CONFIG.url }>{ CONFIG.host }</a></footer> <footer><a href={ _URL_ }>{ _HOST_ }</a></footer>
<style> <style>
:scope :scope
display block display block

View File

@ -5,7 +5,7 @@
<mk-cpu-usage connection={ connection }/> <mk-cpu-usage connection={ connection }/>
<mk-mem-usage connection={ connection }/> <mk-mem-usage connection={ connection }/>
</main> </main>
<footer><a href={ CONFIG.url }>{ CONFIG.host }</a></footer> <footer><a href={ _URL_ }>{ _HOST_ }</a></footer>
<style> <style>
:scope :scope
display block display block

View File

@ -11,8 +11,6 @@ import * as bodyParser from 'body-parser';
import * as favicon from 'serve-favicon'; import * as favicon from 'serve-favicon';
import * as compression from 'compression'; import * as compression from 'compression';
import config from '../conf';
/** /**
* Init app * Init app
*/ */
@ -50,29 +48,7 @@ app.get(/^\/sw\.(.+?)\.js$/, (req, res) => res.sendFile(`${__dirname}/assets/sw.
/** /**
* Manifest * Manifest
*/ */
app.get('/manifest.json', (req, res) => { app.get('/manifest.json', (req, res) => res.sendFile(`${__dirname}/assets/manifest.json`));
const manifest = require((`${__dirname}/assets/manifest.json`));
// Service Worker
if (config.sw) {
manifest['gcm_sender_id'] = config.sw.gcm_sender_id;
}
res.send(manifest);
});
/**
* Serve config
*/
app.get('/config.json', (req, res) => {
const conf = {
recaptcha: {
siteKey: config.recaptcha.siteKey
}
};
res.send(conf);
});
/** /**
* Common API * Common API

View File

@ -0,0 +1,36 @@
/**
* Replace consts
*/
const StringReplacePlugin = require('string-replace-webpack-plugin');
import version from '../../../src/version';
const constants = require('../../../src/const.json');
import config from '../../../src/conf';
export default lang => {
// 置換の誤爆を防ぐため文字数の多い順に並べてください
const consts = {
_RECAPTCHA_SITEKEY_: JSON.stringify(config.recaptcha.site_key),
_SW_PUBLICKEY_: JSON.stringify(config.sw.public_key),
_THEME_COLOR_: JSON.stringify(constants.themeColor),
_VERSION_: JSON.stringify(version),
_API_URL_: JSON.stringify(config.api_url),
_LANG_: JSON.stringify(lang),
_HOST_: JSON.stringify(config.host),
_URL_: JSON.stringify(config.url),
};
const replacements = Object.keys(consts).map(key => ({
pattern: new RegExp(key, 'g'), replacement: () => consts[key]
}));
return {
enforce: 'post',
test: /\.(tag|js|ts)$/,
exclude: /node_modules/,
loader: StringReplacePlugin.replace({
replacements: replacements
})
};
};

View File

@ -1,4 +1,5 @@
import i18n from './i18n'; import i18n from './i18n';
import consts from './consts';
import base64 from './base64'; import base64 from './base64';
import themeColor from './theme-color'; import themeColor from './theme-color';
import tag from './tag'; import tag from './tag';
@ -7,6 +8,7 @@ import typescript from './typescript';
export default (lang, locale) => [ export default (lang, locale) => [
i18n(lang, locale), i18n(lang, locale),
consts(lang),
base64(), base64(),
themeColor(), themeColor(),
tag(), tag(),

View File

@ -1,14 +0,0 @@
/**
* Constant Replacer
*/
import * as webpack from 'webpack';
import version from '../../src/version';
const constants = require('../../src/const.json');
export default lang => new webpack.DefinePlugin({
VERSION: JSON.stringify(version),
LANG: JSON.stringify(lang),
THEME_COLOR: JSON.stringify(constants.themeColor)
});

View File

@ -1,6 +1,5 @@
const StringReplacePlugin = require('string-replace-webpack-plugin'); const StringReplacePlugin = require('string-replace-webpack-plugin');
import constant from './const';
import hoist from './hoist'; import hoist from './hoist';
//import minify from './minify'; //import minify from './minify';
import banner from './banner'; import banner from './banner';
@ -10,7 +9,6 @@ const isProduction = env === 'production';
export default (version, lang) => { export default (version, lang) => {
const plugins = [ const plugins = [
constant(lang),
new StringReplacePlugin(), new StringReplacePlugin(),
hoist() hoist()
]; ];