1
1
mirror of https://github.com/kokonect-link/cherrypick synced 2024-11-01 15:45:58 +09:00

Merge pull request #179 from syuilo/no-ls

No ls
This commit is contained in:
syuilo⭐️ 2017-02-18 18:12:04 +09:00 committed by GitHub
commit 5305f3dde2
64 changed files with 1097 additions and 1105 deletions

View File

@ -17,7 +17,6 @@ import * as es from 'event-stream';
import stylus = require('gulp-stylus'); import stylus = require('gulp-stylus');
import cssnano = require('gulp-cssnano'); import cssnano = require('gulp-cssnano');
import * as uglify from 'gulp-uglify'; import * as uglify from 'gulp-uglify';
import ls = require('browserify-livescript');
import riotify = require('riotify'); import riotify = require('riotify');
import transformify = require('syuilo-transformify'); import transformify = require('syuilo-transformify');
import pug = require('gulp-pug'); import pug = require('gulp-pug');
@ -169,7 +168,6 @@ gulp.task('build:client:scripts', () => new Promise(async (ok) => {
browserify({ browserify({
entries: [entry] entries: [entry]
}) })
.transform(ls)
.transform(transformify((source, file) => { .transform(transformify((source, file) => {
return source return source
.replace(/VERSION/g, `'${commit ? commit.hash : 'null'}'`) .replace(/VERSION/g, `'${commit ? commit.hash : 'null'}'`)

View File

@ -73,7 +73,6 @@
"bcryptjs": "2.4.3", "bcryptjs": "2.4.3",
"body-parser": "1.16.1", "body-parser": "1.16.1",
"browserify": "14.1.0", "browserify": "14.1.0",
"browserify-livescript": "0.2.3",
"chai": "3.5.0", "chai": "3.5.0",
"chai-http": "3.0.0", "chai-http": "3.0.0",
"chalk": "1.1.3", "chalk": "1.1.3",

View File

@ -5,10 +5,10 @@
const riot = require('riot'); const riot = require('riot');
require('velocity-animate'); require('velocity-animate');
const api = require('./common/scripts/api'); const api = require('./common/scripts/api');
const signout = require('./common/scripts/signout.ls'); const signout = require('./common/scripts/signout');
const generateDefaultUserdata = require('./common/scripts/generate-default-userdata.ls'); const generateDefaultUserdata = require('./common/scripts/generate-default-userdata');
const mixins = require('./common/mixins.ls'); const mixins = require('./common/mixins');
const checkForUpdate = require('./common/scripts/check-for-update.ls'); const checkForUpdate = require('./common/scripts/check-for-update');
require('./common/tags'); require('./common/tags');
/** /**

View File

@ -0,0 +1,48 @@
const riot = require('riot');
module.exports = me => {
const i = me ? me.token : null;
require('./scripts/i')(me);
riot.mixin('api', {
api: require('./scripts/api').bind(null, i)
});
riot.mixin('cropper', {
Cropper: require('cropperjs')
});
riot.mixin('signout', {
signout: require('./scripts/signout')
});
riot.mixin('messaging-stream', {
MessagingStreamConnection: require('./scripts/messaging-stream')
});
riot.mixin('is-promise', {
isPromise: require('./scripts/is-promise')
});
riot.mixin('get-post-summary', {
getPostSummary: require('./scripts/get-post-summary')
});
riot.mixin('date-stringify', {
dateStringify: require('./scripts/date-stringify')
});
riot.mixin('text', {
analyze: require('../../../common/text/index'),
compile: require('./scripts/text-compiler')
});
riot.mixin('get-password-strength', {
getPasswordStrength: require('syuilo-password-strength')
});
riot.mixin('ui-progress', {
Progress: require('./scripts/loading')
});
};

View File

@ -1,37 +0,0 @@
riot = require \riot
module.exports = (me) ~>
i = if me? then me.token else null
(require './scripts/i.ls') me
riot.mixin \api do
api: (require './scripts/api').bind null i
riot.mixin \cropper do
Cropper: require \cropperjs
riot.mixin \signout do
signout: require './scripts/signout.ls'
riot.mixin \messaging-stream do
MessagingStreamConnection: require './scripts/messaging-stream.ls'
riot.mixin \is-promise do
is-promise: require './scripts/is-promise.ls'
riot.mixin \get-post-summary do
get-post-summary: require './scripts/get-post-summary.ls'
riot.mixin \date-stringify do
date-stringify: require './scripts/date-stringify.ls'
riot.mixin \text do
analyze: require '../../../common/text/index.js'
compile: require './scripts/text-compiler.js'
riot.mixin \get-password-strength do
get-password-strength: require 'syuilo-password-strength'
riot.mixin \ui-progress do
Progress: require './scripts/loading.ls'

View File

@ -0,0 +1,11 @@
module.exports = () => {
fetch('/api:meta').then(res => {
res.json().then(meta => {
if (meta.commit.hash !== VERSION) {
if (window.confirm('新しいMisskeyのバージョンがあります。更新しますか\r\n(このメッセージが繰り返し表示される場合は、サーバーにデータがまだ届いていない可能性があるので、少し時間を置いてから再度お試しください)')) {
location.reload(true);
}
}
});
});
};

View File

@ -1,9 +0,0 @@
module.exports = ->
fetch \/api:meta
.then (res) ~>
meta <~ res.json!.then
if meta.commit.hash != VERSION
if window.confirm '新しいMisskeyのバージョンがあります。更新しますか\r\n(このメッセージが繰り返し表示される場合は、サーバーにデータがまだ届いていない可能性があるので、少し時間を置いてから再度お試しください)'
location.reload true
.catch ~>
# ignore

View File

@ -0,0 +1,13 @@
module.exports = date => {
if (typeof date == 'string') date = new Date(date);
return (
date.getFullYear() + '年' +
date.getMonth() + 1 + '月' +
date.getDate() + '日' +
' ' +
date.getHours() + '時' +
date.getMinutes() + '分' +
' ' +
`(${['日', '月', '火', '水', '木', '金', '土'][date.getDay()]})`
);
};

View File

@ -1,14 +0,0 @@
module.exports = (date) ->
if typeof date == \string then date = new Date date
text =
date.get-full-year! + \年 +
date.get-month! + 1 + \月 +
date.get-date! + \日 +
' ' +
date.get-hours! + \時 +
date.get-minutes! + \分 +
' ' +
"(#{[\日 \月 \火 \水 \木 \金 \土][date.get-day!]})"
return text

View File

@ -0,0 +1,47 @@
const uuid = require('./uuid.js');
const home = {
left: [
'profile',
'calendar',
'rss-reader',
'photo-stream'
],
right: [
'broadcast',
'notifications',
'user-recommendation',
'donation',
'nav',
'tips'
]
};
module.exports = () => {
const homeData = [];
home.left.forEach(widget => {
homeData.push({
name: widget,
id: uuid(),
place: 'left'
});
});
home.right.forEach(widget => {
homeData.push({
name: widget,
id: uuid(),
place: 'right'
});
});
const data = {
cache: true,
debug: false,
nya: true,
home: homeData
};
return data;
};

View File

@ -1,28 +0,0 @@
uuid = require './uuid.js'
home =
left: [ \profile \calendar \rss-reader \photo-stream ]
right: [ \broadcast \notifications \user-recommendation \donation \nav \tips ]
module.exports = ~>
home-data = []
home.left.for-each (widget) ~>
home-data.push do
name: widget
id: uuid!
place: \left
home.right.for-each (widget) ~>
home-data.push do
name: widget
id: uuid!
place: \right
data =
cache: true
debug: false
nya: true
home: home-data
return data

View File

@ -0,0 +1,37 @@
const getPostSummary = post => {
let = post.text ? post.text : '';
// メディアが添付されているとき
if (post.media) {
summary += ` (${post.media.length}つのメディア)`;
}
// 投票が添付されているとき
if (post.poll) {
summary += ' (投票)';
}
// 返信のとき
if (post.reply_to_id) {
if (post.reply_to) {
replySummary = getPostSummary(post.reply_to);
summary += ` RE: ${replySummary}`;
} else {
summary += ' RE: ...';
}
}
// Repostのとき
if (post.repost_id) {
if (post.repost) {
repostSummary = getPostSummary(post.repost);
summary += ` RP: ${repostSummary}`;
} else {
summary += ' RP: ...';
}
}
return summary.trim();
};
module.exports = getPostSummary;

View File

@ -1,30 +0,0 @@
get-post-summary = (post) ~>
summary = if post.text? then post.text else ''
# メディアが添付されているとき
if post.media?
summary += " (#{post.media.length}つのメディア)"
# 投票が添付されているとき
if post.poll?
summary += " (投票)"
# 返信のとき
if post.reply_to_id?
if post.reply_to?
reply-summary = get-post-summary post.reply_to
summary += " RE: #{reply-summary}"
else
summary += " RE: ..."
# Repostのとき
if post.repost_id?
if post.repost?
repost-summary = get-post-summary post.repost
summary += " RP: #{repost-summary}"
else
summary += " RP: ..."
return summary.trim!
module.exports = get-post-summary

View File

@ -0,0 +1,20 @@
const riot = require('riot');
module.exports = me => {
riot.mixin('i', {
init: () => {
this.I = me;
this.SIGNIN = me != null;
if (this.SIGNIN) {
this.on('mount', () => {
me.on('updated', this.update);
});
this.on('unmount', () => {
me.off('updated', this.update);
});
}
},
me: me
});
};

View File

@ -1,13 +0,0 @@
riot = require \riot
module.exports = (me) ->
riot.mixin \i do
init: ->
@I = me
@SIGNIN = me?
if @SIGNIN
@on \mount ~> me.on \updated @update
@on \unmount ~> me.off \updated @update
me: me

View File

@ -0,0 +1 @@
module.exports = x => typeof x.then == 'function';

View File

@ -1 +0,0 @@
module.exports = (x) -> typeof x.then == \function

View File

@ -0,0 +1,21 @@
const NProgress = require('nprogress');
NProgress.configure({
trickleSpeed: 500,
showSpinner: false
});
const root = document.getElementsByTagName('html')[0];
module.exports = {
start: () => {
root.classList.add('progress');
NProgress.start();
},
done: () => {
root.classList.remove('progress');
NProgress.done();
},
set: val => {
NProgress.set(val);
}
};

View File

@ -1,16 +0,0 @@
NProgress = require \nprogress
NProgress.configure do
trickle-speed: 500ms
show-spinner: false
root = document.get-elements-by-tag-name \html .0
module.exports =
start: ~>
root.class-list.add \progress
NProgress.start!
done: ~>
root.class-list.remove \progress
NProgress.done!
set: (val) ~>
NProgress.set val

View File

@ -1,18 +0,0 @@
riot = require \riot
logs = []
ev = riot.observable!
function log(msg)
logs.push do
date: new Date!
message: msg
ev.trigger \log
riot.mixin \log do
logs: logs
log: log
log-event: ev
module.exports = log

View File

@ -0,0 +1,36 @@
const ReconnectingWebSocket = require('reconnecting-websocket');
const riot = require('riot');
class Connection {
constructor(me, otherparty) {
this.event = riot.observable();
this.me = me;
const host = CONFIG.api.url.replace('http', 'ws');
this.socket = new ReconnectingWebSocket(`${host}/messaging?i=${me.token}&otherparty=${otherparty}`);
this.socket.addEventListener('open', this.onOpen);
this.socket.addEventListener('message', this.onMessage);
}
onOpen() {
this.socket.send(JSON.stringify({
i: this.me.token
}));
}
onMessage(message) {
try {
const message = JSON.parse(message.data);
if (message.type) this.event.trigger(message.type, message.body);
} catch(e) {
// noop
}
}
close() {
this.socket.removeEventListener('open', this.onOpen);
this.socket.removeEventListener('message', this.onMessage);
}
}
module.exports = Connection;

View File

@ -1,34 +0,0 @@
# Stream
#================================
ReconnectingWebSocket = require 'reconnecting-websocket'
riot = require 'riot'
class Connection
(me, otherparty) ~>
@event = riot.observable!
@me = me
host = CONFIG.api.url.replace \http \ws
@socket = new ReconnectingWebSocket "#{host}/messaging?i=#{me.token}&otherparty=#{otherparty}"
@socket.add-event-listener \open @on-open
@socket.add-event-listener \message @on-message
on-open: ~>
@socket.send JSON.stringify do
i: @me.token
on-message: (message) ~>
try
message = JSON.parse message.data
if message.type?
@event.trigger message.type, message.body
catch
# ignore
close: ~>
@socket.remove-event-listener \open @on-open
@socket.remove-event-listener \message @on-message
@socket.close!
module.exports = Connection

View File

@ -0,0 +1,5 @@
module.exports = () => {
localStorage.removeItem('me');
document.cookie = `i=; domain=.${CONFIG.host}; expires=Thu, 01 Jan 1970 00:00:01 GMT;`;
location.href = '/';
};

View File

@ -1,4 +0,0 @@
module.exports = ->
local-storage.remove-item \me
document.cookie = "i=; domain=.#{CONFIG.host}; expires=Thu, 01 Jan 1970 00:00:01 GMT;"
location.href = \/

View File

@ -0,0 +1,39 @@
const ReconnectingWebSocket = require('reconnecting-websocket');
const riot = require('riot');
module.exports = me => {
let state = 'initializing';
const stateEv = riot.observable();
const event = riot.observable();
const host = CONFIG.api.url.replace('http', 'ws');
const socket = new ReconnectingWebSocket(`${host}?i=${me.token}`);
socket.onopen = () => {
state = 'connected';
stateEv.trigger('connected');
};
socket.onclose = () => {
state = 'reconnecting';
stateEv.trigger('closed');
};
socket.onmessage = message => {
try {
const message = JSON.parse(message.data);
if (message.type) {
event.trigger(message.type, message.body);
}
} catch (e) {
// noop
}
};
event.on('i_updated', me.update);
return {
stateEv: stateEv,
getState: () => state,
event: event
};
};

View File

@ -1,39 +0,0 @@
# Stream
#================================
ReconnectingWebSocket = require \reconnecting-websocket
riot = require \riot
module.exports = (me) ~>
state = \initializing
state-ev = riot.observable!
event = riot.observable!
host = CONFIG.api.url.replace \http \ws
socket = new ReconnectingWebSocket "#{host}?i=#{me.token}"
socket.onopen = ~>
state := \connected
state-ev.trigger \connected
socket.onclose = ~>
state := \reconnecting
state-ev.trigger \closed
socket.onmessage = (message) ~>
try
message = JSON.parse message.data
if message.type?
event.trigger message.type, message.body
catch
# ignore
get-state = ~> state
event.on \i_updated me.update
{
state-ev
get-state
event
}

View File

@ -0,0 +1,42 @@
const riot = require('riot');
module.exports = me => {
if (me) require('./scripts/stream')(me);
require('./scripts/user-preview');
require('./scripts/open-window');
riot.mixin('notify', {
notify: require('./scripts/notify')
});
const dialog = require('./scripts/dialog');
riot.mixin('dialog', {
dialog: dialog
});
riot.mixin('NotImplementedException', {
NotImplementedException: () => {
return dialog('<i class="fa fa-exclamation-triangle"></i>Not implemented yet', '要求された操作は実装されていません。<br>→<a href="https://github.com/syuilo/misskey" target="_blank">Misskeyの開発に参加する</a>', [{
text: 'OK'
}]);
}
});
riot.mixin('input-dialog', {
inputDialog: require('./scripts/input-dialog')
});
riot.mixin('update-avatar', {
updateAvatar: require('./scripts/update-avatar')
});
riot.mixin('update-banner', {
updateBanner: require('./scripts/update-banner')
});
riot.mixin('autocomplete', {
Autocomplete: require('./scripts/autocomplete')
});
};

View File

@ -1,41 +0,0 @@
riot = require \riot
module.exports = (me) ~>
if me?
(require './scripts/stream.ls') me
require './scripts/user-preview.ls'
require './scripts/open-window.ls'
riot.mixin \notify do
notify: require './scripts/notify.ls'
dialog = require './scripts/dialog.ls'
riot.mixin \dialog do
dialog: dialog
riot.mixin \NotImplementedException do
NotImplementedException: ~>
dialog do
'<i class="fa fa-exclamation-triangle"></i>Not implemented yet'
'要求された操作は実装されていません。<br>→<a href="https://github.com/syuilo/misskey" target="_blank">Misskeyの開発に参加する</a>'
[
text: \OK
]
riot.mixin \input-dialog do
input-dialog: require './scripts/input-dialog.ls'
riot.mixin \update-avatar do
update-avatar: require './scripts/update-avatar.ls'
riot.mixin \update-banner do
update-banner: require './scripts/update-banner.ls'
riot.mixin \autocomplete do
Autocomplete: require './scripts/autocomplete.ls'
riot.mixin \follow-scroll do
Follower: require './scripts/follow-scroll.ls'

View File

@ -0,0 +1,74 @@
/**
* Desktop App Router
*/
const riot = require('riot');
const route = require('page');
let page = null;
module.exports = me => {
route('/', index);
route('/i>mentions', mentions);
route('/post::post', post);
route('/search::query', search);
route('/:user', user.bind(null, 'home'));
route('/:user/graphs', user.bind(null, 'graphs'));
route('/:user/:post', post);
route('*', notFound);
function index() {
me ? home() : entrance();
}
function home() {
mount(document.createElement('mk-home-page'));
}
function entrance() {
mount(document.createElement('mk-entrance'));
document.documentElement.setAttribute('data-page', 'entrance');
}
function mentions() {
const el = document.createElement('mk-home-page');
el.setAttribute('mode', 'mentions');
mount(el);
}
function search(ctx) {
const el = document.createElement('mk-search-page');
el.setAttribute('query', ctx.params.query);
mount(el);
}
function user(page, ctx) {
const el = document.createElement('mk-user-page');
el.setAttribute('user', ctx.params.user);
el.setAttribute('page', page);
mount(el);
}
function post(ctx) {
const el = document.createElement('mk-post-page');
el.setAttribute('post', ctx.params.post);
mount(el);
}
function notFound() {
mount(document.createElement('mk-not-found'));
}
riot.mixin('page', {
page: route
});
// EXEC
route();
};
function mount(content) {
document.documentElement.removeAttribute('data-page');
if (page) page.unmount();
const body = document.getElementById('app');
page = riot.mount(body.appendChild(content))[0];
}

View File

@ -1,77 +0,0 @@
# Router
#================================
riot = require \riot
route = require \page
page = null
module.exports = (me) ~>
# Routing
#--------------------------------
route \/ index
route \/i>mentions mentions
route \/post::post post
route \/search::query search
route \/:user user.bind null \home
route \/:user/graphs user.bind null \graphs
route \/:user/:post post
route \* not-found
# Handlers
#--------------------------------
function index
if me? then home! else entrance!
function home
mount document.create-element \mk-home-page
function entrance
mount document.create-element \mk-entrance
document.document-element.set-attribute \data-page \entrance
function mentions
document.create-element \mk-home-page
..set-attribute \mode \mentions
.. |> mount
function search ctx
document.create-element \mk-search-page
..set-attribute \query ctx.params.query
.. |> mount
function user page, ctx
document.create-element \mk-user-page
..set-attribute \user ctx.params.user
..set-attribute \page page
.. |> mount
function post ctx
document.create-element \mk-post-page
..set-attribute \post ctx.params.post
.. |> mount
function not-found
mount document.create-element \mk-not-found
# Register mixin
#--------------------------------
riot.mixin \page do
page: route
# Exec
#--------------------------------
route!
# Mount
#================================
function mount content
document.document-element.remove-attribute \data-page
if page? then page.unmount!
body = document.get-element-by-id \app
page := riot.mount body.append-child content .0

View File

@ -5,10 +5,10 @@
require('chart.js'); require('chart.js');
require('./tags'); require('./tags');
const riot = require('riot'); const riot = require('riot');
const boot = require('../boot.js'); const boot = require('../boot');
const mixins = require('./mixins.ls'); const mixins = require('./mixins');
const route = require('./router.ls'); const route = require('./router');
const fuckAdBlock = require('./scripts/fuck-ad-block.ls'); const fuckAdBlock = require('./scripts/fuck-ad-block');
/** /**
* Boot * Boot

View File

@ -0,0 +1,124 @@
const getCaretCoordinates = require('textarea-caret');
const riot = require('riot');
/**
* オートコンプリートを管理するクラス
*/
class Autocomplete {
/**
* 対象のテキストエリアを与えてインスタンスを初期化します
*/
constructor(textarea) {
this.suggestion = null;
this.textarea = textarea;
}
/**
* このインスタンスにあるテキストエリアの入力のキャプチャを開始します
*/
attach() {
this.textarea.addEventListener('input', this.onInput);
}
/**
* このインスタンスにあるテキストエリアの入力のキャプチャを解除します
*/
detach() {
this.textarea.removeEventListener('input', this.onInput);
this.close();
}
/**
* [Private] テキスト入力時
*/
onInput() {
this.close();
const caret = this.textarea.selectionStart;
const text = this.textarea.value.substr(0, caret);
const mentionIndex = text.lastIndexOf('@');
if (mentionIndex == -1) return;
const username = text.substr(mentionIndex + 1);
if (!username.match(/^[a-zA-Z0-9-]+$/)) return;
this.open('user', username);
}
/**
* [Private] サジェストを提示します
*/
open(type, q) {
// 既に開いているサジェストは閉じる
this.close();
// サジェスト要素作成
const suggestion = document.createElement('mk-autocomplete-suggestion');
// ~ サジェストを表示すべき位置を計算 ~
const caretPosition = getCaretCoordinates(this.textarea, this.textarea.selectionStart);
const rect = this.textarea.getBoundingClientRect();
const x = rect.left + window.pageXOffset + caretPosition.left;
const y = rect.top + window.pageYOffset + caretPosition.top;
suggestion.style.left = x + 'px';
suggestion.style.top = y + 'px';
// 要素追加
const el = document.body.appendChild(suggestion);
// マウント
this.suggestion = riot.mount(el, {
textarea: this.textarea,
complete: this.complete,
close: this.close,
type: type,
q: q
})[0];
}
/**
* [Private] サジェストを閉じます
*/
close() {
if (this.suggestion == nul) return;
this.suggestion.unmount();
this.suggestion = null;
this.textarea.focus();
}
/**
* [Private] オートコンプリートする
*/
complete(user) {
this.close();
const value = user.username;
const caret = this.textarea.selectionStart;
const source = this.textarea.value;
const before = source.substr(0, caret);
const trimedBefore = before.substring(0, before.lastIndexOf('@'));
const after = source.substr(caret);
// 結果を挿入する
this.textarea.value = trimedBefore + '@' + value + ' ' + after;
// キャレットを戻す
this.textarea.focus();
const pos = caret + value.length;
this.textarea.setSelectionRange(pos, pos);
}
}
module.exports = Autocomplete;

View File

@ -1,108 +0,0 @@
# Autocomplete
#================================
get-caret-coordinates = require 'textarea-caret'
riot = require 'riot'
# オートコンプリートを管理するクラスです。
class Autocomplete
@textarea = null
@suggestion = null
# 対象のテキストエリアを与えてインスタンスを初期化します。
(textarea) ~>
@textarea = textarea
# このインスタンスにあるテキストエリアの入力のキャプチャを開始します。
attach: ~>
@textarea.add-event-listener \input @on-input
# このインスタンスにあるテキストエリアの入力のキャプチャを解除します。
detach: ~>
@textarea.remove-event-listener \input @on-input
@close!
# テキスト入力時
on-input: ~>
@close!
caret = @textarea.selection-start
text = @textarea.value.substr 0 caret
mention-index = text.last-index-of \@
if mention-index == -1
return
username = text.substr mention-index + 1
if not username.match /^[a-zA-Z0-9-]+$/
return
@open \user username
# サジェストを提示します。
open: (type, q) ~>
# 既に開いているサジェストは閉じる
@close!
# サジェスト要素作成
suggestion = document.create-element \mk-autocomplete-suggestion
# ~ サジェストを表示すべき位置を計算 ~
caret-position = get-caret-coordinates @textarea, @textarea.selection-start
rect = @textarea.get-bounding-client-rect!
x = rect.left + window.page-x-offset + caret-position.left
y = rect.top + window.page-y-offset + caret-position.top
suggestion.style.left = x + \px
suggestion.style.top = y + \px
# 要素追加
el = document.body.append-child suggestion
# マウント
mounted = riot.mount el, do
textarea: @textarea
complete: @complete
close: @close
type: type
q: q
@suggestion = mounted.0
# サジェストを閉じます。
close: ~>
if !@suggestion?
return
@suggestion.unmount!
@suggestion = null
@textarea.focus!
# オートコンプリートする
complete: (user) ~>
@close!
value = user.username
caret = @textarea.selection-start
source = @textarea.value
before = source.substr 0 caret
trimed-before = before.substring 0 before.last-index-of \@
after = source.substr caret
# 結果を挿入する
@textarea.value = trimed-before + \@ + value + ' ' + after
# キャレットを戻す
@textarea.focus!
pos = caret + value.length
@textarea.set-selection-range pos, pos
module.exports = Autocomplete

View File

@ -0,0 +1,16 @@
const riot = require('riot');
module.exports = (title, text, buttons, canThrough, onThrough) => {
const dialog = document.body.appendChild(document.createElement('mk-dialog'));
const controller = riot.observable();
riot.mount(dialog, {
controller: controller,
title: title,
text: text,
buttons: buttons,
canThrough: canThrough,
onThrough: onThrough
});
controller.trigger('open');
return controller;
};

View File

@ -1,17 +0,0 @@
# Dialog
#================================
riot = require 'riot'
module.exports = (title, text, buttons, can-through, on-through) ~>
dialog = document.body.append-child document.create-element \mk-dialog
controller = riot.observable!
riot.mount dialog, do
controller: controller
title: title
text: text
buttons: buttons
can-through: can-through
on-through: on-through
controller.trigger \open
return controller

View File

@ -1,56 +0,0 @@
class Follower
(el) ->
@follower = el
@last-scroll-top = window.scroll-y
@initial-follower-top = @follower.get-bounding-client-rect!.top
@page-top = 48
follow: ->
window-height = window.inner-height
follower-height = @follower.offset-height
scroll-top = window.scroll-y
scroll-bottom = scroll-top + window-height
follower-top = @follower.get-bounding-client-rect!.top + scroll-top
follower-bottom = follower-top + follower-height
height-delta = Math.abs window-height - follower-height
scroll-delta = @last-scroll-top - scroll-top
is-scrolling-down = (scroll-top > @last-scroll-top)
is-window-larger = (window-height > follower-height)
console.log @initial-follower-top
if (is-window-larger && scroll-top > @initial-follower-top) || (!is-window-larger && scroll-top > @initial-follower-top + height-delta)
@follower.class-list.add \fixed
else if !is-scrolling-down && scroll-top + @page-top <= @initial-follower-top
@follower.class-list.remove \fixed
@follower.style.top = 0
return
drag-bottom-down = (follower-bottom <= scroll-bottom && is-scrolling-down)
drag-top-up = (follower-top >= scroll-top + @page-top && !is-scrolling-down)
if drag-bottom-down
console.log \down
@follower.style.top = if is-window-larger then 0 else -height-delta + \px
else if drag-top-up
console.log \up
@follower.style.top = @page-top + \px
else if @follower.class-list.contains \fixed
console.log \-
current-top = parse-int @follower.style.top, 10
min-top = -height-delta
scrolled-top = current-top + scroll-delta
is-page-at-bottom = (scroll-top + window-height >= document.body.offset-height)
new-top = if is-page-at-bottom then min-top else scrolled-top
@follower.style.top = new-top + \px
@last-scroll-top = scroll-top
module.exports = Follower

View File

@ -0,0 +1,18 @@
require('fuckadblock');
const dialog = require('./dialog');
module.exports = () => {
if (fuckAdBlock === undefined) {
adBlockDetected();
} else {
fuckAdBlock.onDetected(adBlockDetected);
}
};
function adBlockDetected() {
dialog('<i class="fa fa-exclamation-triangle"></i>広告ブロッカーを無効にしてください',
'<strong>Misskeyは広告を掲載していません</strong>が、広告をブロックする機能が有効だと一部の機能が利用できなかったり、不具合が発生する場合があります。',
[{
text: 'OK'
}]);
}

View File

@ -1,19 +0,0 @@
# FUCK AD BLOCK
#================================
require \fuckadblock
dialog = require './dialog.ls'
module.exports = ~>
if fuck-ad-block == undefined
ad-block-detected!
else
fuck-ad-block.on-detected ad-block-detected
function ad-block-detected
dialog do
'<i class="fa fa-exclamation-triangle"></i>広告ブロッカーを無効にしてください'
'<strong>Misskeyは広告を掲載していません</strong>が、広告をブロックする機能が有効だと一部の機能が利用できなかったり、不具合が発生する場合があります。'
[
text: \OK
]

View File

@ -0,0 +1,12 @@
const riot = require('riot');
module.exports = (title, placeholder, defaultValue, onOk, onCancel) => {
const dialog = document.body.appendChild(document.createElement('mk-input-dialog'));
return riot.mount(dialog, {
title: title,
placeholder: placeholder,
'default': defaultValue,
onOk: onOk,
onCancel: onCancel
});
};

View File

@ -1,13 +0,0 @@
# Input Dialog
#================================
riot = require 'riot'
module.exports = (title, placeholder, default-value, on-ok, on-cancel) ~>
dialog = document.body.append-child document.create-element \mk-input-dialog
riot.mount dialog, do
title: title
placeholder: placeholder
default: default-value
on-ok: on-ok
on-cancel: on-cancel

View File

@ -0,0 +1,8 @@
const riot = require('riot');
module.exports = message => {
const notification = document.body.appendChild(document.createElement('mk-ui-notification'));
riot.mount(notification, {
message: message
});
};

View File

@ -1,6 +0,0 @@
riot = require \riot
module.exports = (message) ~>
notification = document.body.append-child document.create-element \mk-ui-notification
riot.mount notification, do
message: message

View File

@ -0,0 +1,8 @@
const riot = require('riot');
riot.mixin('open-window', {
openWindow: (name, opts) => {
const window = document.body.appendChild(document.createElement(name));
return riot.mount(window, opts)[0];
}
});

View File

@ -1,8 +0,0 @@
riot = require \riot
function open(name, opts)
window = document.body.append-child document.create-element name
riot.mount window, opts
riot.mixin \open-window do
open-window: open

View File

@ -0,0 +1,45 @@
const stream = require('../../common/scripts/stream');
const getPostSummary = require('../../common/scripts/get-post-summary');
const riot = require('riot');
module.exports = me => {
const s = stream(me);
s.event.on('drive_file_created', file => {
const n = new Notification('ファイルがアップロードされました', {
body: file.name,
icon: file.url + '?thumbnail&size=64'
});
setTimeout(n.close.bind(n), 5000);
});
s.event.on('mention', post => {
const n = new Notification(post.user.name + "さんから:", {
body: getPostSummary(post),
icon: post.user.avatar_url + '?thumbnail&size=64'
});
setTimeout(n.close.bind(n), 6000);
});
s.event.on('reply', post => {
const n = new Notification(post.user.name + "さんから返信:", {
body: getPostSummary(post),
icon: post.user.avatar_url + '?thumbnail&size=64'
});
setTimeout(n.close.bind(n), 6000);
});
s.event.on('quote', post => {
const n = new Notification(post.user.name + "さんが引用:", {
body: getPostSummary(post),
icon: post.user.avatar_url + '?thumbnail&size=64'
});
setTimeout(n.close.bind(n), 6000);
});
riot.mixin('stream', {
stream: s.event,
getStreamState: s.getState,
streamStateEv: s.stateEv
});
};

View File

@ -1,38 +0,0 @@
# Stream
#================================
stream = require '../../common/scripts/stream.ls'
get-post-summary = require '../../common/scripts/get-post-summary.ls'
riot = require \riot
module.exports = (me) ~>
s = stream me
s.event.on \drive_file_created (file) ~>
n = new Notification 'ファイルがアップロードされました' do
body: file.name
icon: file.url + '?thumbnail&size=64'
set-timeout (n.close.bind n), 5000ms
s.event.on \mention (post) ~>
n = new Notification "#{post.user.name}さんから:" do
body: get-post-summary post
icon: post.user.avatar_url + '?thumbnail&size=64'
set-timeout (n.close.bind n), 6000ms
s.event.on \reply (post) ~>
n = new Notification "#{post.user.name}さんから返信:" do
body: get-post-summary post
icon: post.user.avatar_url + '?thumbnail&size=64'
set-timeout (n.close.bind n), 6000ms
s.event.on \quote (post) ~>
n = new Notification "#{post.user.name}さんが引用:" do
body: get-post-summary post
icon: post.user.avatar_url + '?thumbnail&size=64'
set-timeout (n.close.bind n), 6000ms
riot.mixin \stream do
stream: s.event
get-stream-state: s.get-state
stream-state-ev: s.state-ev

View File

@ -0,0 +1,86 @@
const riot = require('riot');
const dialog = require('./dialog');
const api = require('../../common/scripts/api');
module.exports = (I, cb, file = null) => {
const fileSelected = file => {
const cropper = riot.mount(document.body.appendChild(document.createElement('mk-crop-window')), {
file: file,
title: 'アバターとして表示する部分を選択',
aspectRatio: 1 / 1
})[0];
cropper.on('cropped', blob => {
const data = new FormData();
data.append('i', I.token);
data.append('file', blob, file.name + '.cropped.png');
api(I, 'drive/folders/find', {
name: 'アイコン'
}).then(iconFolder => {
if (iconFolder.length === 0) {
api(I, 'drive/folders/create', {
name: 'アイコン'
}).then(iconFolder => {
uplaod(data, iconFolder);
});
} else {
uplaod(data, iconFolder[0]);
}
});
});
cropper.on('skiped', () => {
set(file);
});
};
const uplaod = (data, folder) => {
const progress = riot.mount(document.body.appendChild(document.createElement('mk-progress-dialog')), {
title: '新しいアバターをアップロードしています'
})[0];
if (folder) data.append('folder_id', folder.id);
const xhr = new XMLHttpRequest();
xhr.open('POST', CONFIG.api.url + '/drive/files/create', true);
xhr.onload = e => {
const file = JSON.parse(e.target.response);
progress.close();
set(file);
};
xhr.upload.onprogress = e => {
if (e.lengthComputable) progress.updateProgress(e.loaded, e.total);
};
xhr.send(data);
};
const set = file => {
api(I, 'i/update', {
avatar_id: file.id
}).then(i => {
dialog('<i class="fa fa-info-circle"></i>アバターを更新しました',
'新しいアバターが反映されるまで時間がかかる場合があります。',
[{
text: 'わかった'
}]);
if (cb) cb(i);
});
};
if (file) {
fileSelected(file);
} else {
const browser = riot.mount(document.body.appendChild(document.createElement('mk-select-file-from-drive-window')), {
multiple: false,
title: '<i class="fa fa-picture-o"></i>アバターにする画像を選択'
})[0];
browser.one('selected', file => {
fileSelected(file);
});
}
};

View File

@ -1,81 +0,0 @@
# Update Avatar
#================================
riot = require 'riot'
dialog = require './dialog.ls'
api = require '../../common/scripts/api'
module.exports = (I, cb, file = null) ~>
@file-selected = (file) ~>
cropper = document.body.append-child document.create-element \mk-crop-window
cropper = riot.mount cropper, do
file: file
title: 'アバターとして表示する部分を選択'
aspect-ratio: 1 / 1
.0
cropper.on \cropped (blob) ~>
data = new FormData!
data.append \i I.token
data.append \file blob, file.name + '.cropped.png'
api I, \drive/folders/find do
name: 'アイコン'
.then (icon-folder) ~>
if icon-folder.length == 0
api I, \drive/folders/create do
name: 'アイコン'
.then (icon-folder) ~>
@uplaod data, icon-folder
else
@uplaod data, icon-folder.0
cropper.on \skiped ~>
@set file
@uplaod = (data, folder) ~>
progress = document.body.append-child document.create-element \mk-progress-dialog
progress = riot.mount progress, do
title: '新しいアバターをアップロードしています'
.0
if folder?
data.append \folder_id folder.id
xhr = new XMLHttpRequest!
xhr.open \POST CONFIG.api.url + \/drive/files/create true
xhr.onload = (e) ~>
file = JSON.parse e.target.response
progress.close!
@set file
xhr.upload.onprogress = (e) ~>
if e.length-computable
progress.update-progress e.loaded, e.total
xhr.send data
@set = (file) ~>
api I, \i/update do
avatar_id: file.id
.then (i) ~>
dialog do
'<i class="fa fa-info-circle"></i>アバターを更新しました'
'新しいアバターが反映されるまで時間がかかる場合があります。'
[
text: \わかった
]
if cb? then cb i
.catch (err) ~>
console.error err
#@opts.ui.trigger \notification 'Error!'
if file?
@file-selected file
else
browser = document.body.append-child document.create-element \mk-select-file-from-drive-window
browser = riot.mount browser, do
multiple: false
title: '<i class="fa fa-picture-o"></i>アバターにする画像を選択'
.0
browser.one \selected (file) ~>
@file-selected file

View File

@ -0,0 +1,86 @@
const riot = require('riot');
const dialog = require('./dialog');
const api = require('../../common/scripts/api');
module.exports = (I, cb, file = null) => {
const fileSelected = file => {
const cropper = riot.mount(document.body.appendChild(document.createElement('mk-crop-window')), {
file: file,
title: 'バナーとして表示する部分を選択',
aspectRatio: 16 / 9
})[0];
cropper.on('cropped', blob => {
const data = new FormData();
data.append('i', I.token);
data.append('file', blob, file.name + '.cropped.png');
api(I, 'drive/folders/find', {
name: 'バナー'
}).then(iconFolder => {
if (iconFolder.length === 0) {
api(I, 'drive/folders/create', {
name: 'バナー'
}).then(iconFolder => {
uplaod(data, iconFolder);
});
} else {
uplaod(data, iconFolder[0]);
}
});
});
cropper.on('skiped', () => {
set(file);
});
};
const uplaod = (data, folder) => {
const progress = riot.mount(document.body.appendChild(document.createElement('mk-progress-dialog')), {
title: '新しいバナーをアップロードしています'
})[0];
if (folder) data.append('folder_id', folder.id);
const xhr = new XMLHttpRequest();
xhr.open('POST', CONFIG.api.url + '/drive/files/create', true);
xhr.onload = e => {
const file = JSON.parse(e.target.response);
progress.close();
set(file);
};
xhr.upload.onprogress = e => {
if (e.lengthComputable) progress.updateProgress(e.loaded, e.total);
};
xhr.send(data);
};
const set = file => {
api(I, 'i/update', {
banner_id: file.id
}).then(i => {
dialog('<i class="fa fa-info-circle"></i>バナーを更新しました',
'新しいバナーが反映されるまで時間がかかる場合があります。',
[{
text: 'わかりました。'
}]);
if (cb) cb(i);
});
};
if (file) {
fileSelected(file);
} else {
const browser = riot.mount(document.body.appendChild(document.createElement('mk-select-file-from-drive-window')), {
multiple: false,
title: '<i class="fa fa-picture-o"></i>バナーにする画像を選択'
})[0];
browser.one('selected', file => {
fileSelected(file);
});
}
};

View File

@ -1,81 +0,0 @@
# Update Banner
#================================
riot = require 'riot'
dialog = require './dialog.ls'
api = require '../../common/scripts/api'
module.exports = (I, cb, file = null) ~>
@file-selected = (file) ~>
cropper = document.body.append-child document.create-element \mk-crop-window
cropper = riot.mount cropper, do
file: file
title: 'バナーとして表示する部分を選択'
aspect-ratio: 16 / 9
.0
cropper.on \cropped (blob) ~>
data = new FormData!
data.append \i I.token
data.append \file blob, file.name + '.cropped.png'
api I, \drive/folders/find do
name: 'バナー'
.then (banner-folder) ~>
if banner-folder.length == 0
api I, \drive/folders/create do
name: 'バナー'
.then (banner-folder) ~>
@uplaod data, banner-folder
else
@uplaod data, banner-folder.0
cropper.on \skiped ~>
@set file
@uplaod = (data, folder) ~>
progress = document.body.append-child document.create-element \mk-progress-dialog
progress = riot.mount progress, do
title: '新しいバナーをアップロードしています'
.0
if folder?
data.append \folder_id folder.id
xhr = new XMLHttpRequest!
xhr.open \POST CONFIG.api.url + \/drive/files/create true
xhr.onload = (e) ~>
file = JSON.parse e.target.response
progress.close!
@set file
xhr.upload.onprogress = (e) ~>
if e.length-computable
progress.update-progress e.loaded, e.total
xhr.send data
@set = (file) ~>
api I, \i/update do
banner_id: file.id
.then (i) ~>
dialog do
'<i class="fa fa-info-circle"></i>バナーを更新しました'
'新しいバナーが反映されるまで時間がかかる場合があります。'
[
text: \わかりました。
]
if cb? then cb i
.catch (err) ~>
console.error err
#@opts.ui.trigger \notification 'Error!'
if file?
@file-selected file
else
browser = document.body.append-child document.create-element \mk-select-file-from-drive-window
browser = riot.mount browser, do
multiple: false
title: '<i class="fa fa-picture-o"></i>バナーにする画像を選択'
.0
browser.one \selected (file) ~>
@file-selected file

View File

@ -0,0 +1,66 @@
const riot = require('riot');
riot.mixin('user-preview', {
init: () => {
const scan = () => {
this.root.querySelectorAll('[data-user-preview]:not([data-user-preview-attached])')
.forEach(attach.bind(this));
};
this.on('mount', scan);
this.on('updated', scan);
}
});
function attach(el) {
el.setAttribute('data-user-preview-attached', true);
const user = el.getAttribute('data-user-preview');
let tag = null;
let showTimer = null;
let hideTimer = null;
el.addEventListener('mouseover', () => {
clearTimeout(showTimer);
clearTimeout(hideTimer);
showTimer = setTimeout(show, 500);
});
el.addEventListener('mouseleave', () => {
clearTimeout(showTimer);
clearTimeout(hideTimer);
hideTimer = setTimeout(close, 500);
});
this.on('unmount', () => {
clearTimeout(showTimer);
clearTimeout(hideTimer);
close();
});
const show = () => {
if (tag) return;
const preview = document.createElement('mk-user-preview');
const rect = el.getBoundingClientRect();
const x = rect.left + el.offsetWidth + window.pageXOffset;
const y = rect.top + window.pageYOffset;
preview.style.top = y + 'px';
preview.style.left = x + 'px';
preview.addEventListener('mouseover', () => {
clearTimeout(hideTimer);
});
preview.addEventListener('mouseleave', () => {
clearTimeout(showTimer);
hideTimer = setTimeout(close, 500);
});
tag = riot.mount(document.body.appendChild(preview), {
user: user
})[0];
};
const close = () => {
if (tag) {
tag.close();
tag = null;
}
};
}

View File

@ -1,74 +0,0 @@
# User Preview
#================================
riot = require \riot
riot.mixin \user-preview do
init: ->
@on \mount ~>
scan.call @
@on \updated ~>
scan.call @
function scan
elems = @root.query-selector-all '[data-user-preview]:not([data-user-preview-attached])'
elems.for-each attach.bind @
function attach el
el.set-attribute \data-user-preview-attached true
user = el.get-attribute \data-user-preview
tag = null
show-timer = null
hide-timer = null
el.add-event-listener \mouseover ~>
clear-timeout show-timer
clear-timeout hide-timer
show-timer := set-timeout ~>
show!
, 500ms
el.add-event-listener \mouseleave ~>
clear-timeout show-timer
clear-timeout hide-timer
hide-timer := set-timeout ~>
close!
, 500ms
@on \unmount ~>
clear-timeout show-timer
clear-timeout hide-timer
close!
function show
if tag?
return
preview = document.create-element \mk-user-preview
rect = el.get-bounding-client-rect!
x = rect.left + el.offset-width + window.page-x-offset
y = rect.top + window.page-y-offset
preview.style.top = y + \px
preview.style.left = x + \px
preview.add-event-listener \mouseover ~>
clear-timeout hide-timer
preview.add-event-listener \mouseleave ~>
clear-timeout show-timer
hide-timer := set-timeout ~>
close!
, 500ms
tag := riot.mount (document.body.append-child preview), do
user: user
.0
function close
if tag?
tag.close!
tag := null

42
src/web/app/dev/router.js Normal file
View File

@ -0,0 +1,42 @@
const route = require('page');
let page = null;
module.exports = me => {
route('/', index);
route('/apps', apps);
route('/app/new', newApp);
route('/app/:app', app);
route('*', notFound);
function index() {
mount(document.createElement('mk-index'));
}
function apps() {
mount(document.createElement('mk-apps-page'));
}
function newApp() {
mount(document.createElement('mk-new-app-page'));
}
function app(ctx) {
const el = document.createElement('mk-app-page');
el.setAttribute('app', ctx.params.app);
mount(el);
}
function notFound() {
mount(document.createElement('mk-not-found'));
}
// EXEC
route();
};
const riot = require('riot');
function mount(content) {
if (page) page.unmount();
const body = document.getElementById('app');
page = riot.mount(body.appendChild(content))[0];
}

View File

@ -1,51 +0,0 @@
# Router
#================================
route = require \page
page = null
module.exports = (me) ~>
# Routing
#--------------------------------
route \/ index
route \/apps apps
route \/app/new new-app
route \/app/:app app
route \* not-found
# Handlers
#--------------------------------
function index
mount document.create-element \mk-index
function apps
mount document.create-element \mk-apps-page
function new-app
mount document.create-element \mk-new-app-page
function app ctx
document.create-element \mk-app-page
..set-attribute \app ctx.params.app
.. |> mount
function not-found
mount document.create-element \mk-not-found
# Exec
#--------------------------------
route!
# Mount
#================================
riot = require \riot
function mount content
if page? then page.unmount!
body = document.get-element-by-id \app
page := riot.mount body.append-child content .0

View File

@ -3,8 +3,8 @@
*/ */
require('./tags'); require('./tags');
const boot = require('../boot.js'); const boot = require('../boot');
const route = require('./router.ls'); const route = require('./router');
/** /**
* Boot * Boot

View File

@ -0,0 +1,25 @@
const riot = require('riot');
module.exports = me => {
if (me) {
require('./scripts/stream')(me);
}
require('./scripts/ui');
riot.mixin('open-post-form', {
openPostForm: opts => {
const app = document.getElementById('app');
app.style.display = 'none';
function recover() {
app.style.display = 'block';
}
const form = riot.mount(document.body.appendChild(document.createElement('mk-post-form')), opts)[0];
form
.on('cancel', recover)
.on('post', recover);
}
});
};

View File

@ -1,19 +0,0 @@
riot = require \riot
module.exports = (me) ~>
if me?
(require './scripts/stream.ls') me
require './scripts/ui.ls'
riot.mixin \open-post-form do
open-post-form: (opts) ->
app = document.get-element-by-id \app
app.style.display = \none
form = document.body.append-child document.create-element \mk-post-form
form = riot.mount form, opts .0
form.on \cancel recover
form.on \post recover
function recover
app.style.display = \block

View File

@ -0,0 +1,136 @@
/**
* Mobile App Router
*/
const riot = require('riot');
const route = require('page');
let page = null;
module.exports = me => {
route('/', index);
route('/i/notifications', notifications);
route('/i/messaging', messaging);
route('/i/messaging/:username', messaging);
route('/i/drive', drive);
route('/i/drive/folder/:folder', drive);
route('/i/drive/file/:file', drive);
route('/i/settings', settings);
route('/i/settings/signin-history', settingsSignin);
route('/i/settings/api', settingsApi);
route('/i/settings/twitter', settingsTwitter);
route('/i/settings/authorized-apps', settingsAuthorizedApps);
route('/post/new', newPost);
route('/post::post', post);
route('/search::query', search);
route('/:user', user.bind(null, 'posts'));
route('/:user/graphs', user.bind(null, 'graphs'));
route('/:user/followers', userFollowers);
route('/:user/following', userFollowing);
route('/:user/:post', post);
route('*', notFound);
function index() {
me ? home() : entrance();
}
function home() {
mount(document.createElement('mk-home-page'));
}
function entrance() {
mount(document.createElement('mk-entrance'));
}
function notifications() {
mount(document.createElement('mk-notifications-page'));
}
function messaging(ctx) {
if (ctx.params.username) {
const el = document.createElement('mk-messaging-room-page');
el.setAttribute('username', ctx.params.username);
mount(el);
} else {
mount(document.createElement('mk-messaging-page'));
}
}
function newPost() {
mount(document.createElement('mk-new-post-page'));
}
function settings() {
mount(document.createElement('mk-settings-page'));
}
function settingsSignin() {
mount(document.createElement('mk-signin-history-page'));
}
function settingsApi() {
mount(document.createElement('mk-api-info-page'));
}
function settingsTwitter() {
mount(document.createElement('mk-twitter-setting-page'));
}
function settingsAuthorizedApps() {
mount(document.createElement('mk-authorized-apps-page'));
}
function search(ctx) {
const el = document.createElement('mk-search-page');
el.setAttribute('query', ctx.params.query);
mount(el);
}
function user(page, ctx) {
const el = document.createElement('mk-user-page');
el.setAttribute('user', ctx.params.user);
el.setAttribute('page', page);
mount(el);
}
function userFollowing(ctx) {
const el = document.createElement('mk-user-following-page');
el.setAttribute('user', ctx.params.user);
mount(el);
}
function userFollowers(ctx) {
const el = document.createElement('mk-user-followers-page');
el.setAttribute('user', ctx.params.user);
mount(el);
}
function post(ctx) {
const el = document.createElement('mk-post-page');
el.setAttribute('post', ctx.params.post);
mount(el);
}
function drive(ctx) {
const el = document.createElement('mk-drive-page');
if (ctx.params.folder) el.setAttribute('folder', ctx.params.folder);
if (ctx.params.file) el.setAttribute('file', ctx.params.file);
mount(el);
}
function notFound() {
mount(document.createElement('mk-not-found'));
}
riot.mixin('page', {
page: route
});
// EXEC
route();
};
function mount(content) {
if (page) page.unmount();
const body = document.getElementById('app');
page = riot.mount(body.appendChild(content))[0];
}

View File

@ -1,138 +0,0 @@
# Router
#================================
riot = require \riot
route = require \page
page = null
module.exports = (me) ~>
# Routing
#--------------------------------
route \/ index
route \/i/notifications notifications
route \/i/messaging messaging
route \/i/messaging/:username messaging
route \/i/drive drive
route \/i/drive/folder/:folder drive
route \/i/drive/file/:file drive
route \/i/settings settings
route \/i/settings/signin-history settings-signin
route \/i/settings/api settings-api
route \/i/settings/twitter settings-twitter
route \/i/settings/authorized-apps settings-authorized-apps
route \/post/new new-post
route \/post::post post
route \/search::query search
route \/:user user.bind null \posts
route \/:user/graphs user.bind null \graphs
route \/:user/followers user-followers
route \/:user/following user-following
route \/:user/:post post
route \* not-found
# Handlers
#--------------------------------
# /
function index
if me? then home! else entrance!
# ホーム
function home
mount document.create-element \mk-home-page
# 玄関
function entrance
mount document.create-element \mk-entrance
# 通知
function notifications
mount document.create-element \mk-notifications-page
# メッセージ
function messaging ctx
if ctx.params.username
p = document.create-element \mk-messaging-room-page
p.set-attribute \username ctx.params.username
mount p
else
mount document.create-element \mk-messaging-page
# 新規投稿
function new-post
mount document.create-element \mk-new-post-page
# 設定
function settings
mount document.create-element \mk-settings-page
function settings-signin
mount document.create-element \mk-signin-history-page
function settings-api
mount document.create-element \mk-api-info-page
function settings-twitter
mount document.create-element \mk-twitter-setting-page
function settings-authorized-apps
mount document.create-element \mk-authorized-apps-page
# 検索
function search ctx
document.create-element \mk-search-page
..set-attribute \query ctx.params.query
.. |> mount
# ユーザー
function user page, ctx
document.create-element \mk-user-page
..set-attribute \user ctx.params.user
..set-attribute \page page
.. |> mount
# フォロー一覧
function user-following ctx
document.create-element \mk-user-following-page
..set-attribute \user ctx.params.user
.. |> mount
# フォロワー一覧
function user-followers ctx
document.create-element \mk-user-followers-page
..set-attribute \user ctx.params.user
.. |> mount
# 投稿詳細ページ
function post ctx
document.create-element \mk-post-page
..set-attribute \post ctx.params.post
.. |> mount
# ドライブ
function drive ctx
p = document.create-element \mk-drive-page
if ctx.params.folder then p.set-attribute \folder ctx.params.folder
if ctx.params.file then p.set-attribute \file ctx.params.file
mount p
# not found
function not-found
mount document.create-element \mk-not-found
# Register mixin
#--------------------------------
riot.mixin \page do
page: route
# Exec
#--------------------------------
route!
# Mount
#================================
function mount content
if page? then page.unmount!
body = document.get-element-by-id \app
page := riot.mount body.append-child content .0

View File

@ -3,9 +3,9 @@
*/ */
require('./tags'); require('./tags');
const boot = require('../boot.js'); const boot = require('../boot');
const mixins = require('./mixins.ls'); const mixins = require('./mixins');
const route = require('./router.ls'); const route = require('./router');
/** /**
* Boot * Boot

View File

@ -0,0 +1,11 @@
const stream = require('../../common/scripts/stream');
const riot = require('riot');
module.exports = me => {
const s = stream(me);
riot.mixin('stream', {
stream: s.event,
getStreamState: s.getState,
streamStateEv: s.stateEv
});
};

View File

@ -1,13 +0,0 @@
# Stream
#================================
stream = require '../../common/scripts/stream.ls'
riot = require \riot
module.exports = (me) ~>
s = stream me
riot.mixin \stream do
stream: s.event
get-stream-state: s.get-state
stream-state-ev: s.state-ev

View File

@ -0,0 +1,7 @@
const riot = require('riot');
const ui = riot.observable();
riot.mixin('ui', {
ui: ui
});

View File

@ -1,6 +0,0 @@
riot = require \riot
ui = riot.observable!
riot.mixin \ui do
ui: ui