forked from FoundKeyGang/FoundKey
commit
5305f3dde2
64 changed files with 1097 additions and 1105 deletions
|
@ -17,7 +17,6 @@ import * as es from 'event-stream';
|
|||
import stylus = require('gulp-stylus');
|
||||
import cssnano = require('gulp-cssnano');
|
||||
import * as uglify from 'gulp-uglify';
|
||||
import ls = require('browserify-livescript');
|
||||
import riotify = require('riotify');
|
||||
import transformify = require('syuilo-transformify');
|
||||
import pug = require('gulp-pug');
|
||||
|
@ -169,7 +168,6 @@ gulp.task('build:client:scripts', () => new Promise(async (ok) => {
|
|||
browserify({
|
||||
entries: [entry]
|
||||
})
|
||||
.transform(ls)
|
||||
.transform(transformify((source, file) => {
|
||||
return source
|
||||
.replace(/VERSION/g, `'${commit ? commit.hash : 'null'}'`)
|
||||
|
|
|
@ -73,7 +73,6 @@
|
|||
"bcryptjs": "2.4.3",
|
||||
"body-parser": "1.16.1",
|
||||
"browserify": "14.1.0",
|
||||
"browserify-livescript": "0.2.3",
|
||||
"chai": "3.5.0",
|
||||
"chai-http": "3.0.0",
|
||||
"chalk": "1.1.3",
|
||||
|
|
|
@ -5,10 +5,10 @@
|
|||
const riot = require('riot');
|
||||
require('velocity-animate');
|
||||
const api = require('./common/scripts/api');
|
||||
const signout = require('./common/scripts/signout.ls');
|
||||
const generateDefaultUserdata = require('./common/scripts/generate-default-userdata.ls');
|
||||
const mixins = require('./common/mixins.ls');
|
||||
const checkForUpdate = require('./common/scripts/check-for-update.ls');
|
||||
const signout = require('./common/scripts/signout');
|
||||
const generateDefaultUserdata = require('./common/scripts/generate-default-userdata');
|
||||
const mixins = require('./common/mixins');
|
||||
const checkForUpdate = require('./common/scripts/check-for-update');
|
||||
require('./common/tags');
|
||||
|
||||
/**
|
||||
|
|
48
src/web/app/common/mixins.js
Normal file
48
src/web/app/common/mixins.js
Normal 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')
|
||||
});
|
||||
};
|
|
@ -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'
|
11
src/web/app/common/scripts/check-for-update.js
Normal file
11
src/web/app/common/scripts/check-for-update.js
Normal 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);
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
};
|
|
@ -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
|
13
src/web/app/common/scripts/date-stringify.js
Normal file
13
src/web/app/common/scripts/date-stringify.js
Normal 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()]})`
|
||||
);
|
||||
};
|
|
@ -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
|
47
src/web/app/common/scripts/generate-default-userdata.js
Normal file
47
src/web/app/common/scripts/generate-default-userdata.js
Normal 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;
|
||||
};
|
|
@ -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
|
37
src/web/app/common/scripts/get-post-summary.js
Normal file
37
src/web/app/common/scripts/get-post-summary.js
Normal 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;
|
|
@ -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
|
20
src/web/app/common/scripts/i.js
Normal file
20
src/web/app/common/scripts/i.js
Normal 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
|
||||
});
|
||||
};
|
|
@ -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
|
1
src/web/app/common/scripts/is-promise.js
Normal file
1
src/web/app/common/scripts/is-promise.js
Normal file
|
@ -0,0 +1 @@
|
|||
module.exports = x => typeof x.then == 'function';
|
|
@ -1 +0,0 @@
|
|||
module.exports = (x) -> typeof x.then == \function
|
21
src/web/app/common/scripts/loading.js
Normal file
21
src/web/app/common/scripts/loading.js
Normal 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);
|
||||
}
|
||||
};
|
|
@ -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
|
|
@ -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
|
36
src/web/app/common/scripts/messaging-stream.js
Normal file
36
src/web/app/common/scripts/messaging-stream.js
Normal 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;
|
|
@ -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
|
5
src/web/app/common/scripts/signout.js
Normal file
5
src/web/app/common/scripts/signout.js
Normal 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 = '/';
|
||||
};
|
|
@ -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 = \/
|
39
src/web/app/common/scripts/stream.js
Normal file
39
src/web/app/common/scripts/stream.js
Normal 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
|
||||
};
|
||||
};
|
|
@ -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
|
||||
}
|
42
src/web/app/desktop/mixins.js
Normal file
42
src/web/app/desktop/mixins.js
Normal 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')
|
||||
});
|
||||
};
|
|
@ -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'
|
74
src/web/app/desktop/router.js
Normal file
74
src/web/app/desktop/router.js
Normal 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];
|
||||
}
|
|
@ -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
|
|
@ -5,10 +5,10 @@
|
|||
require('chart.js');
|
||||
require('./tags');
|
||||
const riot = require('riot');
|
||||
const boot = require('../boot.js');
|
||||
const mixins = require('./mixins.ls');
|
||||
const route = require('./router.ls');
|
||||
const fuckAdBlock = require('./scripts/fuck-ad-block.ls');
|
||||
const boot = require('../boot');
|
||||
const mixins = require('./mixins');
|
||||
const route = require('./router');
|
||||
const fuckAdBlock = require('./scripts/fuck-ad-block');
|
||||
|
||||
/**
|
||||
* Boot
|
||||
|
|
124
src/web/app/desktop/scripts/autocomplete.js
Normal file
124
src/web/app/desktop/scripts/autocomplete.js
Normal 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;
|
|
@ -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
|
16
src/web/app/desktop/scripts/dialog.js
Normal file
16
src/web/app/desktop/scripts/dialog.js
Normal 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;
|
||||
};
|
|
@ -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
|
|
@ -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
|
18
src/web/app/desktop/scripts/fuck-ad-block.js
Normal file
18
src/web/app/desktop/scripts/fuck-ad-block.js
Normal 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'
|
||||
}]);
|
||||
}
|
|
@ -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
|
||||
]
|
12
src/web/app/desktop/scripts/input-dialog.js
Normal file
12
src/web/app/desktop/scripts/input-dialog.js
Normal 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
|
||||
});
|
||||
};
|
|
@ -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
|
8
src/web/app/desktop/scripts/notify.js
Normal file
8
src/web/app/desktop/scripts/notify.js
Normal 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
|
||||
});
|
||||
};
|
|
@ -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
|
8
src/web/app/desktop/scripts/open-window.js
Normal file
8
src/web/app/desktop/scripts/open-window.js
Normal 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];
|
||||
}
|
||||
});
|
|
@ -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
|
45
src/web/app/desktop/scripts/stream.js
Normal file
45
src/web/app/desktop/scripts/stream.js
Normal 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
|
||||
});
|
||||
};
|
|
@ -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
|
86
src/web/app/desktop/scripts/update-avatar.js
Normal file
86
src/web/app/desktop/scripts/update-avatar.js
Normal 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);
|
||||
});
|
||||
}
|
||||
};
|
|
@ -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
|
86
src/web/app/desktop/scripts/update-banner.js
Normal file
86
src/web/app/desktop/scripts/update-banner.js
Normal 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);
|
||||
});
|
||||
}
|
||||
};
|
|
@ -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
|
66
src/web/app/desktop/scripts/user-preview.js
Normal file
66
src/web/app/desktop/scripts/user-preview.js
Normal 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;
|
||||
}
|
||||
};
|
||||
}
|
|
@ -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
42
src/web/app/dev/router.js
Normal 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];
|
||||
}
|
|
@ -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
|
|
@ -3,8 +3,8 @@
|
|||
*/
|
||||
|
||||
require('./tags');
|
||||
const boot = require('../boot.js');
|
||||
const route = require('./router.ls');
|
||||
const boot = require('../boot');
|
||||
const route = require('./router');
|
||||
|
||||
/**
|
||||
* Boot
|
||||
|
|
25
src/web/app/mobile/mixins.js
Normal file
25
src/web/app/mobile/mixins.js
Normal 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);
|
||||
}
|
||||
});
|
||||
};
|
|
@ -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
|
136
src/web/app/mobile/router.js
Normal file
136
src/web/app/mobile/router.js
Normal 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];
|
||||
}
|
|
@ -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
|
|
@ -3,9 +3,9 @@
|
|||
*/
|
||||
|
||||
require('./tags');
|
||||
const boot = require('../boot.js');
|
||||
const mixins = require('./mixins.ls');
|
||||
const route = require('./router.ls');
|
||||
const boot = require('../boot');
|
||||
const mixins = require('./mixins');
|
||||
const route = require('./router');
|
||||
|
||||
/**
|
||||
* Boot
|
||||
|
|
11
src/web/app/mobile/scripts/stream.js
Normal file
11
src/web/app/mobile/scripts/stream.js
Normal 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
|
||||
});
|
||||
};
|
|
@ -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
|
7
src/web/app/mobile/scripts/ui.js
Normal file
7
src/web/app/mobile/scripts/ui.js
Normal file
|
@ -0,0 +1,7 @@
|
|||
const riot = require('riot');
|
||||
|
||||
const ui = riot.observable();
|
||||
|
||||
riot.mixin('ui', {
|
||||
ui: ui
|
||||
});
|
|
@ -1,6 +0,0 @@
|
|||
riot = require \riot
|
||||
|
||||
ui = riot.observable!
|
||||
|
||||
riot.mixin \ui do
|
||||
ui: ui
|
Loading…
Reference in a new issue