Merge pull request #193 from syuilo/no-tag-ls

No tag ls
This commit is contained in:
syuilo⭐️ 2017-02-22 02:18:57 +09:00 committed by GitHub
commit 82a28f4c05
178 changed files with 5281 additions and 4794 deletions

View File

@ -96,7 +96,6 @@
"gulp-babel": "6.1.2",
"gulp-cssnano": "2.1.2",
"gulp-imagemin": "3.1.1",
"gulp-livescript": "3.0.1",
"gulp-pug": "3.2.0",
"gulp-rename": "1.2.2",
"gulp-replace": "0.5.4",
@ -108,7 +107,6 @@
"is-root": "1.0.0",
"is-url": "1.2.2",
"js-yaml": "3.8.1",
"livescript": "1.5.0",
"mime-types": "2.1.14",
"mocha": "3.2.0",
"mongodb": "2.2.24",

View File

@ -106,21 +106,25 @@
</style>
<script>
@mixin \api
this.mixin('api');
@session = @opts.session
@app = @session.app
this.session = this.opts.session;
this.app = this.session.app;
@cancel = ~>
@api \auth/deny do
token: @session.token
.then ~>
@trigger \denied
this.cancel = () => {
this.api('auth/deny', {
token: this.session.token
}).then(() => {
this.trigger('denied');
});
};
@accept = ~>
@api \auth/accept do
token: @session.token
.then ~>
@trigger \accepted
this.accept = () => {
this.api('auth/accept', {
token: this.session.token
}).then(() => {
this.trigger('accepted');
});
};
</script>
</mk-form>

View File

@ -88,50 +88,60 @@
</style>
<script>
@mixin \i
@mixin \api
this.mixin('i');
this.mixin('api');
@state = null
@fetching = true
this.state = null;
this.fetching = true;
@token = window.location.href.split \/ .pop!
this.token = window.location.href.split('/').pop();
@on \mount ~>
if not @SIGNIN then return
this.on('mount', () => {
if (!this.SIGNIN) return;
# Fetch session
@api \auth/session/show do
token: @token
.then (session) ~>
@session = session
@fetching = false
// Fetch session
this.api('auth/session/show', {
token: this.token
}).then(session => {
this.session = session;
this.fetching = false;
# 既に連携していた場合
if @session.app.is_authorized
@api \auth/accept do
token: @session.token
.then ~>
@accepted!
else
@state = \waiting
@update!
// 既に連携していた場合
if (this.session.app.is_authorized) {
this.api('auth/accept', {
token: this.session.token
}).then(() => {
this.accepted();
});
} else {
this.update({
state: 'waiting'
});
@refs.form.on \denied ~>
@state = \denied
@update!
this.refs.form.on('denied', () => {
this.update({
state: 'denied'
});
});
@refs.form.on \accepted @accepted
this.refs.form.on('accepted', this.accepted);
}
}).catch(error => {
this.update({
fetching: false,
state: 'fetch-session-error'
});
});
});
.catch (error) ~>
@fetching = false
@state = \fetch-session-error
@update!
this.accepted = () => {
this.update({
state: 'accepted'
});
@accepted = ~>
@state = \accepted
@update!
if @session.app.callback_url
location.href = @session.app.callback_url + '?token=' + @session.token
if (this.session.app.callback_url) {
location.href = this.session.app.callback_url + '?token=' + this.session.token;
}
};
</script>
</mk-index>

View File

@ -27,13 +27,16 @@ riot.mixin({
// ↓ iOS待ちPolyfill (SEE: http://caniuse.com/#feat=fetch)
require('whatwg-fetch');
// ↓ NodeList、HTMLCollectionで forEach を使えるようにする
// ↓ NodeList、HTMLCollection、FileListで forEach を使えるようにする
if (NodeList.prototype.forEach === undefined) {
NodeList.prototype.forEach = Array.prototype.forEach;
}
if (HTMLCollection.prototype.forEach === undefined) {
HTMLCollection.prototype.forEach = Array.prototype.forEach;
}
if (FileList.prototype.forEach === undefined) {
FileList.prototype.forEach = Array.prototype.forEach;
}
// ↓ iOSでプライベートモードだとlocalStorageが使えないので既存のメソッドを上書きする
try {

View File

@ -0,0 +1,8 @@
module.exports = function(parent, child) {
let node = child.parentNode;
while (node) {
if (node == parent) return true;
node = node.parentNode;
}
return false;
}

View File

@ -21,6 +21,6 @@
text-decoration underline
</style>
<script>
@mixin \i
this.mixin('i');
</script>
</mk-api-info>

View File

@ -17,18 +17,17 @@
</style>
<script>
@mixin \api
this.mixin('api');
@apps = []
@fetching = true
this.apps = [];
this.fetching = true;
@on \mount ~>
@api \i/authorized_apps
.then (apps) ~>
@apps = apps
@fetching = false
@update!
.catch (err) ~>
console.error err
this.on('mount', () => {
this.api('i/authorized_apps').then(apps => {
this.apps = apps;
this.fetching = false;
this.update();
});
});
</script>
</mk-authorized-apps>

View File

@ -1,11 +1,7 @@
<mk-copyright><span>(c) syuilo 2014-2017</span>
<mk-copyright>
<span>(c) syuilo 2014-2017</span>
<style>
:scope
display block
</style>
</mk-copyright>

View File

@ -1,8 +1,7 @@
<mk-core-error>
<!--i: i.fa.fa-times-circle--><img src="/_/resources/error.jpg" alt=""/>
<h1>
<mk-ripple-string>サーバーに接続できません</mk-ripple-string>
</h1>
<!--i: i.fa.fa-times-circle-->
<img src="/_/resources/error.jpg" alt=""/>
<h1>サーバーに接続できません</h1>
<p class="text">インターネット回線に問題があるか、サーバーがダウンまたはメンテナンスしている可能性があります。しばらくしてから<a onclick={ retry }>再度お試し</a>ください。</p>
<p class="thanks">いつもMisskeyをご利用いただきありがとうございます。</p>
<style>
@ -57,8 +56,9 @@
</style>
<script>
@retry = ~>
@unmount!
@opts.retry!
this.retry = () => {
this.unmount();
this.opts.retry();
}
</script>
</mk-core-error>

View File

@ -20,10 +20,5 @@
opacity 1
40%
opacity 0
</style>
</mk-ellipsis>

View File

@ -5,6 +5,6 @@
display inline
</style>
<script>
@kind = @opts.type.split \/ .0
this.kind = this.opts.type.split('/')[0];
</script>
</mk-file-type-icon>

View File

@ -1,7 +1,6 @@
require('./core-error.tag');
require('./url.tag');
require('./url-preview.tag');
require('./ripple-string.tag');
require('./time.tag');
require('./file-type-icon.tag');
require('./uploader.tag');
@ -24,3 +23,4 @@ require('./messaging/room.tag');
require('./messaging/message.tag');
require('./messaging/index.tag');
require('./messaging/form.tag');
require('./stream-indicator.tag');

View File

@ -21,9 +21,5 @@
margin 0
text-align center
</style>
</mk-introduction>

View File

@ -2,9 +2,15 @@
<textarea ref="text" onkeypress={ onkeypress } onpaste={ onpaste } placeholder="ここにメッセージを入力"></textarea>
<div class="files"></div>
<mk-uploader ref="uploader"></mk-uploader>
<button class="send" onclick={ send } disabled={ sending } title="メッセージを送信"><i class="fa fa-paper-plane" if={ !sending }></i><i class="fa fa-spinner fa-spin" if={ sending }></i></button>
<button class="attach-from-local" type="button" title="PCから画像を添付する"><i class="fa fa-upload"></i></button>
<button class="attach-from-drive" type="button" title="アルバムから画像を添付する"><i class="fa fa-folder-open"></i></button>
<button class="send" onclick={ send } disabled={ sending } title="メッセージを送信">
<i class="fa fa-paper-plane" if={ !sending }></i><i class="fa fa-spinner fa-spin" if={ sending }></i>
</button>
<button class="attach-from-local" type="button" title="PCから画像を添付する">
<i class="fa fa-upload"></i>
</button>
<button class="attach-from-drive" type="button" title="アルバムから画像を添付する">
<i class="fa fa-folder-open"></i>
</button>
<input name="file" type="file" accept="image/*"/>
<style>
:scope
@ -111,49 +117,60 @@
</style>
<script>
@mixin \api
this.mixin('api');
@onpaste = (e) ~>
data = e.clipboard-data
items = data.items
for i from 0 to items.length - 1
item = items[i]
switch (item.kind)
| \file =>
@upload item.get-as-file!
this.onpaste = (e) => {
const data = e.clipboardData;
const items = data.items;
for (let i = 0; i < items.length; i++) {
const item = items[i];
if (item.kind == 'file') {
this.upload(item.getAsFile());
}
}
};
@onkeypress = (e) ~>
if (e.which == 10 || e.which == 13) && e.ctrl-key
@send!
this.onkeypress = (e) => {
if ((e.which == 10 || e.which == 13) && e.ctrlKey) {
this.send();
}
};
@select-file = ~>
@refs.file.click!
this.selectFile = () => {
this.refs.file.click();
};
@select-file-from-drive = ~>
browser = document.body.append-child document.create-element \mk-select-file-from-drive-window
event = riot.observable!
riot.mount browser, do
multiple: true
this.selectFileFromDrive = () => {
const browser = document.body.appendChild(document.createElement('mk-select-file-from-drive-window'));
const event = riot.observable();
riot.mount(browser, {
multiple: true,
event: event
event.one \selected (files) ~>
files.for-each @add-file
});
event.one('selected', files => {
files.forEach(this.addFile);
});
};
@send = ~>
@sending = true
@api \messaging/messages/create do
user_id: @opts.user.id
text: @refs.text.value
.then (message) ~>
@clear!
.catch (err) ~>
console.error err
.then ~>
@sending = false
@update!
this.send = () => {
this.sending = true;
this.api('messaging/messages/create', {
user_id: this.opts.user.id,
text: this.refs.text.value
}).then(message => {
this.clear();
}).catch(err => {
console.error(err);
}).then(() => {
this.sending = false;
this.update();
});
};
@clear = ~>
@refs.text.value = ''
@files = []
@update!
this.clear = () => {
this.refs.text.value = '';
this.files = [];
this.update();
};
</script>
</mk-messaging-form>

View File

@ -286,72 +286,88 @@
</style>
<script>
@mixin \i
@mixin \api
this.mixin('i');
this.mixin('api');
@search-result = []
this.searchResult = [];
@on \mount ~>
@api \messaging/history
.then (history) ~>
@is-loading = false
history.for-each (message) ~>
message.is_me = message.user_id == @I.id
message._click = ~>
if message.is_me
@trigger \navigate-user message.recipient
else
@trigger \navigate-user message.user
@history = history
@update!
.catch (err) ~>
console.error err
this.on('mount', () => {
this.api('messaging/history').then(history => {
this.isLoading = false;
history.forEach(message => {
message.is_me = message.user_id == this.I.id
message._click = () => {
this.trigger('navigate-user', message.is_me ? message.recipient : message.user);
};
});
this.history = history;
this.update();
});
});
@search = ~>
q = @refs.search.value
if q == ''
@search-result = []
else
@api \users/search do
query: q
max: 5
.then (users) ~>
users.for-each (user) ~>
user._click = ~>
@trigger \navigate-user user
@search-result = []
@search-result = users
@update!
.catch (err) ~>
console.error err
this.search = () => {
const q = this.refs.search.value;
if (q == '') {
this.searchResult = [];
return;
}
this.api('users/search', {
query: q,
max: 5
}).then(users => {
users.forEach(user => {
user._click = () => {
this.trigger('navigate-user', user);
this.searchResult = [];
};
});
this.update({
searchResult: users
});
});
};
@on-search-keydown = (e) ~>
key = e.which
switch (key)
| 9, 40 => # Key[TAB] or Key[↓]
e.prevent-default!
e.stop-propagation!
@refs.search-result.child-nodes[0].focus!
this.onSearchKeydown = e => {
switch (e.which) {
case 9: // [TAB]
case 40: // [↓]
e.preventDefault();
e.stopPropagation();
this.refs.searchResult.childNodes[0].focus();
break;
}
};
@on-search-result-keydown = (i, e) ~>
key = e.which
switch (key)
| 10, 13 => # Key[ENTER]
e.prevent-default!
e.stop-propagation!
@search-result[i]._click!
| 27 => # Key[ESC]
e.prevent-default!
e.stop-propagation!
@refs.search.focus!
| 38 => # Key[↑]
e.prevent-default!
e.stop-propagation!
(@refs.search-result.child-nodes[i].previous-element-sibling || @refs.search-result.child-nodes[@search-result.length - 1]).focus!
| 9, 40 => # Key[TAB] or Key[↓]
e.prevent-default!
e.stop-propagation!
(@refs.search-result.child-nodes[i].next-element-sibling || @refs.search-result.child-nodes[0]).focus!
this.onSearchResultKeydown = (i, e) => {
const cancel = () => {
e.preventDefault();
e.stopPropagation();
};
switch (true) {
case e.which == 10: // [ENTER]
case e.which == 13: // [ENTER]
cancel();
this.searchResult[i]._click();
break;
case e.which == 27: // [ESC]
cancel();
this.refs.search.focus();
break;
case e.which == 9 && e.shiftKey: // [TAB] + [Shift]
case e.which == 38: // [↑]
cancel();
(this.refs.searchResult.childNodes[i].previousElementSibling || this.refs.searchResult.childNodes[this.searchResult.length - 1]).focus();
break;
case e.which == 9: // [TAB]
case e.which == 40: // [↓]
cancel();
(this.refs.searchResult.childNodes[i].nextElementSibling || this.refs.searchResult.childNodes[0]).focus();
break;
}
};
</script>
</mk-messaging>

View File

@ -203,28 +203,32 @@
</style>
<script>
@mixin \i
@mixin \text
this.mixin('i');
this.mixin('text');
@message = @opts.message
@message.is_me = @message.user.id == @I.id
this.message = this.opts.message;
this.message.is_me = this.message.user.id == this.I.id;
@on \mount ~>
if @message.text?
tokens = @analyze @message.text
this.on('mount', () => {
if (this.message.text) {
const tokens = this.analyze(this.message.text);
@refs.text.innerHTML = @compile tokens
this.refs.text.innerHTML = this.compile(tokens);
@refs.text.children.for-each (e) ~>
if e.tag-name == \MK-URL
riot.mount e
this.refs.text.children.forEach(e => {
if (e.tagName == 'MK-URL') riot.mount(e);
});
# URLをプレビュー
// URLをプレビュー
tokens
.filter (t) -> t.type == \link
.map (t) ~>
@preview = @refs.text.append-child document.create-element \mk-url-preview
riot.mount @preview, do
.filter(t => t.type == 'link')
.map(t => {
const el = this.refs.text.appendChild(document.createElement('mk-url-preview'));
riot.mount(el, {
url: t.content
});
});
}
});
</script>
</mk-messaging-message>

View File

@ -124,101 +124,117 @@
</style>
<script>
@mixin \i
@mixin \api
@mixin \messaging-stream
this.mixin('i');
this.mixin('api');
this.mixin('messaging-stream');
@user = @opts.user
@init = true
@sending = false
@messages = []
this.user = this.opts.user;
this.init = true;
this.sending = false;
this.messages = [];
@connection = new @MessagingStreamConnection @I, @user.id
this.connection = new this.MessagingStreamConnection(this.I, this.user.id);
@on \mount ~>
@connection.event.on \message @on-message
@connection.event.on \read @on-read
this.on('mount', () => {
this.connection.event.on('message', this.onMessage);
this.connection.event.on('read', this.onRead);
document.add-event-listener \visibilitychange @on-visibilitychange
document.addEventListener('visibilitychange', this.onVisibilitychange);
@api \messaging/messages do
user_id: @user.id
.then (messages) ~>
@init = false
@messages = messages.reverse!
@update!
@scroll-to-bottom!
.catch (err) ~>
console.error err
this.api('messaging/messages', {
user_id: this.user.id
}).then(messages => {
this.init = false;
this.messages = messages.reverse();
this.update();
this.scrollToBottom();
});
});
@on \unmount ~>
@connection.event.off \message @on-message
@connection.event.off \read @on-read
@connection.close!
this.on('unmount', () => {
this.connection.event.off('message', this.onMessage);
this.connection.event.off('read', this.onRead);
this.connection.close();
document.remove-event-listener \visibilitychange @on-visibilitychange
document.removeEventListener('visibilitychange', this.onVisibilitychange);
});
@on \update ~>
@messages.for-each (message) ~>
date = (new Date message.created_at).get-date!
month = (new Date message.created_at).get-month! + 1
message._date = date
message._datetext = month + '月 ' + date + '日'
this.on('update', () => {
this.messages.forEach(message => {
const date = (new Date(message.created_at)).getDate();
const month = (new Date(message.created_at)).getMonth() + 1;
message._date = date;
message._datetext = month + '月 ' + date + '日';
});
});
@on-message = (message) ~>
is-bottom = @is-bottom!
this.onMessage = (message) => {
const isbottom = this.isBottom();
@messages.push message
if message.user_id != @I.id and not document.hidden
@connection.socket.send JSON.stringify do
type: \read
this.messages.push(message);
if (message.user_id != this.I.id && !document.hidden) {
this.connection.socket.send(JSON.stringify({
type: 'read',
id: message.id
@update!
}));
}
this.update();
if is-bottom
# Scroll to bottom
@scroll-to-bottom!
else if message.user_id != @I.id
# Notify
@notify '新しいメッセージがあります'
if (isBottom) {
// Scroll to bottom
this.scrollToBottom();
} else if (message.user_id != this.I.id) {
// Notify
this.notify('新しいメッセージがあります');
}
};
@on-read = (ids) ~>
if not Array.isArray ids then ids = [ids]
ids.for-each (id) ~>
if (@messages.some (x) ~> x.id == id)
exist = (@messages.map (x) -> x.id).index-of id
@messages[exist].is_read = true
@update!
this.onRead = ids => {
if (!Array.isArray(ids)) ids = [ids];
ids.forEach(id => {
if (this.messages.some(x => x.id == id)) {
const exist = this.messages.map(x => x.id).indexOf(id);
this.messages[exist].is_read = true;
this.update();
}
});
};
@is-bottom = ~>
current = @root.scroll-top + @root.offset-height
max = @root.scroll-height
current > (max - 32)
this.isBottom = () => {
const current = this.root.scrollTop + this.root.offsetHeight;
const max = this.root.scrollHeight;
return current > (max - 32);
};
@scroll-to-bottom = ~>
@root.scroll-top = @root.scroll-height
this.scrollToBottom = () => {
this.root.scrollTop = this.root.scrollHeight;
};
@notify = (message) ~>
n = document.create-element \p
n.inner-HTML = '<i class="fa fa-arrow-circle-down"></i>' + message
n.onclick = ~>
@scroll-to-bottom!
n.parent-node.remove-child n
@refs.notifications.append-child n
this.notify = message => {
const n = document.createElement('p');
n.innerHTML = '<i class="fa fa-arrow-circle-down"></i>' + message;
n.onclick = () => {
this.scrollToBottom();
n.parentNode.removeChild(n);
};
this.refs.notifications.appendChild(n);
set-timeout ~>
n.style.opacity = 0
set-timeout ~>
n.parent-node.remove-child n
, 1000ms
, 4000ms
setTimeout(() => {
n.style.opacity = 0;
setTimeout(() => n.parentNode.removeChild(n), 1000);
}, 4000);
};
@on-visibilitychange = ~>
if document.hidden then return
@messages.for-each (message) ~>
if message.user_id != @I.id and not message.is_read
@connection.socket.send JSON.stringify do
type: \read
this.onVisibilitychange = () => {
if (document.hidden) return;
this.messages.forEach(message => {
if (message.user_id !== this.I.id && !message.is_read) {
this.connection.socket.send(JSON.stringify({
type: 'read',
id: message.id
}));
}
});
};
</script>
</mk-messaging-room>

View File

@ -2,17 +2,17 @@
<style>
:scope
display inline
</style>
<script>
@on \mount ~>
# バグ? https://github.com/riot/riot/issues/2103
#value = @opts.value
value = @opts.riot-value
max = @opts.max
this.on('mount', () => {
// https://github.com/riot/riot/issues/2103
//value = this.opts.value
let value = this.opts.riotValue;
const max = this.opts.max;
if max? then if value > max then value = max
if (max != null && value > max) value = max;
@root.innerHTML = value.to-locale-string!
this.root.innerHTML = value.toLocaleString();
});
</script>
</mk-number>

View File

@ -86,26 +86,31 @@
</style>
<script>
@choices = ['', '']
this.choices = ['', ''];
@oninput = (i, e) ~>
@choices[i] = e.target.value
this.oninput = (i, e) => {
this.choices[i] = e.target.value;
}
@add = ~>
@choices.push ''
@update!
@refs.choices.child-nodes[@choices.length - 1].child-nodes[0].focus!
this.add = () => {
this.choices.push('');
this.update();
this.refs.choices.childNodes[this.choices.length - 1].childNodes[0].focus();
}
@remove = (i) ~>
@choices = @choices.filter((_, _i) -> _i != i)
@update!
this.remove = (i) => {
this.choices = this.choices.filter((_, _i) => _i != i);
this.update();
}
@destroy = ~>
@opts.ondestroy!
this.destroy = () => {
this.opts.ondestroy();
}
@get = ~>
this.get = () => {
return {
choices: @choices.filter (choice) -> choice != ''
choices: this.choices.filter(choice => choice != '')
}
}
</script>
</mk-poll-editor>

View File

@ -68,32 +68,37 @@
</style>
<script>
@mixin \api
this.mixin('api');
@post = @opts.post
@poll = @post.poll
@total = @poll.choices.reduce ((a, b) -> a + b.votes), 0
@is-voted = @poll.choices.some (c) -> c.is_voted
@result = @is-voted
this.post = this.opts.post;
this.poll = this.post.poll;
this.total = this.poll.choices.reduce((a, b) => a + b.votes, 0);
this.isVoted = this.poll.choices.some(c => c.is_voted);
this.result = this.isVoted;
@toggle-result = ~>
@result = !@result
this.toggleResult = () => {
this.result = !this.result;
}
@vote = (id) ~>
if (@poll.choices.some (c) -> c.is_voted) then return
@api \posts/polls/vote do
post_id: @post.id
this.vote = (id) => {
if (this.poll.choices.some(c => c.is_voted)) return;
this.api('posts/polls/vote', {
post_id: this.post.id,
choice: id
.then ~>
@poll.choices.for-each (c) ->
if c.id == id
c.votes++
c.is_voted = true
@update do
poll: @poll
is-voted: true
result: true
total: @total + 1
}).then(() => {
this.poll.choices.forEach(c => {
if (c.id == id) {
c.votes++;
c.is_voted = true;
}
});
this.update({
poll: this.poll,
isVoted: true,
result: true,
total: this.total + 1
});
});
}
</script>
</mk-poll>

View File

@ -4,5 +4,5 @@
display inline
</style>
<script>@root.innerHTML = @opts.content</script>
<script>this.root.innerHTML = this.opts.content</script>
</mk-raw>

View File

@ -1,26 +0,0 @@
<mk-ripple-string><yield/>
<style>
:scope
display inline
> span
animation ripple-string 5s infinite ease-in-out both
@keyframes ripple-string
0%, 50%, 100%
opacity 1
25%
opacity 0.5
</style>
<script>
@on \mount ~>
text = @root.innerHTML
@root.innerHTML = ''
(text.split '').for-each (c, i) ~>
ce = document.create-element \span
ce.innerHTML = c
ce.style.animation-delay = (i / 10) + 's'
@root.append-child ce
</script>
</mk-ripple-string>

View File

@ -48,28 +48,30 @@
</style>
<script>
@mixin \api
@mixin \stream
this.mixin('api');
this.mixin('stream');
@history = []
@fetching = true
this.history = [];
this.fetching = true;
@on \mount ~>
@api \i/signin_history
.then (history) ~>
@history = history
@fetching = false
@update!
.catch (err) ~>
console.error err
this.on('mount', () => {
this.api('i/signin_history').then(history => {
this.update({
fetching: false,
history: history
});
});
@stream.on \signin @on-signin
this.stream.on('signin', this.onSignin);
});
@on \unmount ~>
@stream.off \signin @on-signin
this.on('unmount', () => {
this.stream.off('signin', this.onSignin);
});
@on-signin = (signin) ~>
@history.unshift signin
@update!
this.onSignin = signin => {
this.history.unshift(signin);
this.update();
};
</script>
</mk-signin-history>

View File

@ -97,42 +97,50 @@
</style>
<script>
@mixin \api
this.mixin('api');
@user = null
@signing = false
this.user = null;
this.signing = false;
@oninput = ~>
@api \users/show do
username: @refs.username.value
.then (user) ~>
@user = user
@trigger \user user
@update!
this.oninput = () => {
this.api('users/show', {
username: this.refs.username.value
}).then(user => {
this.user = user;
this.trigger('user', user);
this.update();
});
};
@onsubmit = (e) ~>
e.prevent-default!
this.onsubmit = e => {
e.preventDefault();
if @refs.username.value == ''
@refs.username.focus!
return false
if @refs.password.value == ''
@refs.password.focus!
return false
if (this.refs.username.value == '') {
this.refs.username.focus();
return false;
}
if (this.refs.password.value == '') {
this.refs.password.focus();
return false;
}
@signing = true
@update!
this.update({
signing: true
});
@api \signin do
username: @refs.username.value
password: @refs.password.value
.then ~>
location.reload!
.catch ~>
alert 'something happened'
@signing = false
@update!
this.api('signin', {
username: this.refs.username.value,
password: this.refs.password.value
}).then(() => {
location.reload();
}).catch(() => {
alert('something happened');
this.update({
signing: false
});
});
false
return false;
};
</script>
</mk-signin>

View File

@ -174,120 +174,126 @@
</style>
<script>
@mixin \api
@mixin \get-password-strength
this.mixin('api');
this.mixin('get-password-strength');
@username-state = null
@password-strength = ''
@password-retype-state = null
@recaptchaed = false
this.usernameState = null;
this.passwordStrength = '';
this.passwordRetypeState = null;
this.recaptchaed = false;
window.on-recaptchaed = ~>
@recaptchaed = true
@update!
window.onEecaptchaed = () => {
this.recaptchaed = true;
this.update();
};
window.on-recaptcha-expired = ~>
@recaptchaed = false
@update!
window.onRecaptchaExpired = () => {
this.recaptchaed = false;
this.update();
};
@on \mount ~>
head = (document.get-elements-by-tag-name \head).0
script = document.create-element \script
..set-attribute \src \https://www.google.com/recaptcha/api.js
head.append-child script
this.on('mount', () => {
const head = document.getElementsByTagName('head')[0];
const script = document.createElement('script');
script.setAttribute('src', 'https://www.google.com/recaptcha/api.js');
head.appendChild(script);
});
@on-change-username = ~>
username = @refs.username.value
this.onChangeUsername = () => {
const username = this.refs.username.value;
if username == ''
@username-state = null
@update!
return
if (username == '') {
this.update({
usernameState: null
});
return;
}
err = switch
| not username.match /^[a-zA-Z0-9\-]+$/ => \invalid-format
| username.length < 3chars => \min-range
| username.length > 20chars => \max-range
| _ => null
const err =
!username.match(/^[a-zA-Z0-9\-]+$/) ? 'invalid-format' :
username.length < 3 ? 'min-range' :
username.length > 20 ? 'max-range' :
null;
if err?
@username-state = err
@update!
else
@username-state = \wait
@update!
if (err) {
this.update({
usernameState: err
});
return;
}
@api \username/available do
username: username
.then (result) ~>
if result.available
@username-state = \ok
else
@username-state = \unavailable
@update!
.catch (err) ~>
@username-state = \error
@update!
this.update({
usernameState: 'wait'
});
@on-change-password = ~>
password = @refs.password.value
if password == ''
@password-strength = ''
return
strength = @get-password-strength password
if strength > 0.3
@password-strength = \medium
if strength > 0.7
@password-strength = \high
else
@password-strength = \low
@update!
@refs.password-metar.style.width = (strength * 100) + \%
@on-change-password-retype = ~>
password = @refs.password.value
retyped-password = @refs.password-retype.value
if retyped-password == ''
@password-retype-state = null
return
if password == retyped-password
@password-retype-state = \match
else
@password-retype-state = \not-match
@onsubmit = (e) ~>
e.prevent-default!
username = @refs.username.value
password = @refs.password.value
locker = document.body.append-child document.create-element \mk-locker
@api \signup do
this.api('username/available', {
username: username
password: password
'g-recaptcha-response': grecaptcha.get-response!
.then ~>
@api \signin do
username: username
}).then(result => {
this.update({
usernameState: result.available ? 'ok' : 'unavailable'
});
}).catch(err => {
this.update({
usernameState: 'error'
});
});
};
this.onChangePassword = () => {
const password = this.refs.password.value;
if (password == '') {
this.passwordStrength = '';
return;
}
const strength = this.getPasswordStrength(password);
this.passwordStrength = strength > 0.7 ? 'high' : strength > 0.3 ? 'medium' : 'low';
this.update();
this.refs.passwordMetar.style.width = `${strength * 100}%`;
};
this.onChangePasswordRetype = () => {
const password = this.refs.password.value;
const retypedPassword = this.refs.passwordRetype.value;
if (retypedPassword == '') {
this.passwordRetypeState = null;
return;
}
this.passwordRetypeState = password == retypedPassword ? 'match' : 'not-match';
};
this.onsubmit = e => {
e.preventDefault();
const username = this.refs.username.value;
const password = this.refs.password.value;
const locker = document.body.appendChild(document.createElement('mk-locker'));
this.api('signup', {
username: username,
password: password,
'g-recaptcha-response': grecaptcha.getResponse()
}).then(() => {
this.api('signin', {
username: username,
password: password
.then ~>
}).then(() => {
location.href = CONFIG.url
.catch ~>
alert '何らかの原因によりアカウントの作成に失敗しました。再度お試しください。'
});
}).catch(() => {
alert('何らかの原因によりアカウントの作成に失敗しました。再度お試しください。');
grecaptcha.reset!
@recaptchaed = false
grecaptcha.reset();
this.recaptchaed = false;
locker.parent-node.remove-child locker
locker.parentNode.removeChild(locker);
});
false
return false;
};
</script>
</mk-signup>

View File

@ -20,8 +20,8 @@
</style>
<script>
now = new Date!
@d = now.get-date!
@m = now.get-month! + 1
const now = new Date();
this.d = now.getDate();
this.m = now.getMonth() + 1;
</script>
</mk-special-message>

View File

@ -0,0 +1,65 @@
<mk-stream-indicator>
<p if={ state == 'initializing' }>
<i class="fa fa-spinner fa-spin"></i>
<span>接続中<mk-ellipsis></mk-ellipsis></span>
</p>
<p if={ state == 'reconnecting' }>
<i class="fa fa-spinner fa-spin"></i>
<span>切断されました 接続中<mk-ellipsis></mk-ellipsis></span>
</p>
<p if={ state == 'connected' }>
<i class="fa fa-check"></i>
<span>接続完了</span>
</p>
<style>
:scope
display block
pointer-events none
position fixed
z-index 16384
bottom 8px
right 8px
margin 0
padding 6px 12px
font-size 0.9em
color #fff
background rgba(0, 0, 0, 0.8)
> p
display block
margin 0
> i
margin-right 0.25em
</style>
<script>
this.mixin('stream');
this.on('before-mount', () => {
this.state = this.getStreamState();
if (this.state == 'connected') {
this.root.style.opacity = 0;
}
});
this.streamStateEv.on('connected', () => {
this.state = this.getStreamState();
this.update();
setTimeout(() => {
Velocity(this.root, {
opacity: 0
}, 200, 'linear');
}, 1000);
});
this.streamStateEv.on('closed', () => {
this.state = this.getStreamState();
this.update();
Velocity(this.root, {
opacity: 1
}, 0);
});
</script>
</mk-stream-indicator>

View File

@ -1,41 +1,50 @@
<mk-time>
<time datetime={ opts.time }><span if={ mode == 'relative' }>{ relative }</span><span if={ mode == 'absolute' }>{ absolute }</span><span if={ mode == 'detail' }>{ absolute } ({ relative })</span></time>
<time datetime={ opts.time }>
<span if={ mode == 'relative' }>{ relative }</span>
<span if={ mode == 'absolute' }>{ absolute }</span>
<span if={ mode == 'detail' }>{ absolute } ({ relative })</span>
</time>
<script>
@time = new Date @opts.time
@mode = @opts.mode || \relative
@tickid = null
this.time = new Date(this.opts.time);
this.mode = this.opts.mode || 'relative';
this.tickid = null;
@absolute =
@time.get-full-year! + \年 +
@time.get-month! + 1 + \月 +
@time.get-date! + \日 +
this.absolute =
this.time.getFullYear() + '年' +
this.time.getMonth() + 1 + '月' +
this.time.getDate() + '日' +
' ' +
@time.get-hours! + \時 +
@time.get-minutes! + \分
this.time.getHours() + '時' +
this.time.getMinutes() + '分';
@on \mount ~>
if @mode == \relative or @mode == \detail
@tick!
@tickid = set-interval @tick, 1000ms
this.on('mount', () => {
if (this.mode == 'relative' || this.mode == 'detail') {
this.tick();
this.tickid = setInterval(this.tick, 1000);
}
});
@on \unmount ~>
if @mode == \relative or @mode == \detail
clear-interval @tickid
this.on('unmount', () => {
if (this.mode === 'relative' || this.mode === 'detail') {
clearInterval(this.tickid);
}
});
@tick = ~>
now = new Date!
ago = (now - @time) / 1000ms
@relative = switch
| ago >= 31536000s => ~~(ago / 31536000s) + '年前'
| ago >= 2592000s => ~~(ago / 2592000s) + 'ヶ月前'
| ago >= 604800s => ~~(ago / 604800s) + '週間前'
| ago >= 86400s => ~~(ago / 86400s) + '日前'
| ago >= 3600s => ~~(ago / 3600s) + '時間前'
| ago >= 60s => ~~(ago / 60s) + '分前'
| ago >= 10s => ~~(ago % 60s) + '秒前'
| ago >= 0s => 'たった今'
| ago < 0s => '未来'
| _ => 'なぞのじかん'
@update!
this.tick = () => {
const now = new Date();
const ago = (now - this.time) / 1000/*ms*/;
this.relative =
ago >= 31536000 ? ~~(ago / 31536000) + '年前' :
ago >= 2592000 ? ~~(ago / 2592000) + 'ヶ月前' :
ago >= 604800 ? ~~(ago / 604800) + '週間前' :
ago >= 86400 ? ~~(ago / 86400) + '日前' :
ago >= 3600 ? ~~(ago / 3600) + '時間前' :
ago >= 60 ? ~~(ago / 60) + '分前' :
ago >= 10 ? ~~(ago % 60) + '秒前' :
ago >= 0 ? 'たった今' :
ago < 0 ? '未来' :
'なぞのじかん';
this.update();
};
</script>
</mk-time>

View File

@ -24,6 +24,6 @@
color #8899a6
</style>
<script>
@mixin \i
this.mixin('i');
</script>
</mk-twitter-setting>

View File

@ -140,56 +140,59 @@
</style>
<script>
@mixin \i
this.mixin('i');
@uploads = []
this.uploads = [];
this.upload = (file, folder) => {
const id = Math.random();
@upload = (file, folder) ~>
id = Math.random!
ctx =
id: id
name: file.name || \untitled
const ctx = {
id: id,
name: file.name || 'untitled',
progress: undefined
};
@uploads.push ctx
@trigger \change-uploads @uploads
@update!
this.uploads.push(ctx);
this.trigger('change-uploads', this.uploads);
this.update();
reader = new FileReader!
reader.onload = (e) ~>
ctx.img = e.target.result
@update!
reader.read-as-data-URL file
const reader = new FileReader();
reader.onload = e => {
ctx.img = e.target.result;
this.update();
};
reader.readAsDataURL(file);
data = new FormData!
data.append \i @I.token
data.append \file file
const data = new FormData();
data.append('i', this.I.token);
data.append('file', file);
if folder?
data.append \folder_id folder
if (folder) data.append('folder_id', folder);
xhr = new XMLHttpRequest!
xhr.open \POST CONFIG.apiUrl + '/drive/files/create' true
xhr.onload = (e) ~>
drive-file = JSON.parse e.target.response
const xhr = new XMLHttpRequest();
xhr.open('POST', CONFIG.apiUrl + '/drive/files/create', true);
xhr.onload = e => {
const driveFile = JSON.parse(e.target.response);
@trigger \uploaded drive-file
this.trigger('uploaded', driveFile);
@uploads = @uploads.filter (x) -> x.id != id
@trigger \change-uploads @uploads
this.uploads = this.uploads.filter(x => x.id != id);
this.trigger('change-uploads', this.uploads);
@update!
this.update();
};
xhr.upload.onprogress = (e) ~>
if e.length-computable
if ctx.progress == undefined
ctx.progress = {}
ctx.progress.max = e.total
ctx.progress.value = e.loaded
@update!
xhr.upload.onprogress = e => {
if (e.lengthComputable) {
if (ctx.progress == undefined) ctx.progress = {};
ctx.progress.max = e.total;
ctx.progress.value = e.loaded;
this.update();
}
};
xhr.send data
xhr.send(data);
};
</script>
</mk-uploader>

View File

@ -91,22 +91,24 @@
</style>
<script>
@mixin \api
this.mixin('api');
@url = @opts.url
@loading = true
this.url = this.opts.url;
this.loading = true;
@on \mount ~>
fetch CONFIG.url + '/api:url?url=' + @url
.then (res) ~>
info <~ res.json!.then
@title = info.title
@description = info.description
@thumbnail = info.thumbnail
@icon = info.icon
@sitename = info.sitename
this.on('mount', () => {
fetch(CONFIG.url + '/api:url?url=' + this.url).then(res => {
res.json().then(info => {
this.title = info.title;
this.description = info.description;
this.thumbnail = info.thumbnail;
this.icon = info.icon;
this.sitename = info.sitename;
@loading = false
@update!
this.loading = false;
this.update();
});
});
});
</script>
</mk-url-preview>

View File

@ -30,19 +30,20 @@
</style>
<script>
@url = @opts.href
this.url = this.opts.href;
@on \before-mount ~>
parser = document.create-element \a
parser.href = @url
this.on('before-mount', () => {
parser = document.createElement('a');
parser.href = this.url;
@schema = parser.protocol
@hostname = parser.hostname
@port = parser.port
@pathname = parser.pathname
@query = parser.search
@hash = parser.hash
this.schema = parser.protocol;
this.hostname = parser.hostname;
this.port = parser.port;
this.pathname = parser.pathname;
this.query = parser.search;
this.hash = parser.hash;
@update!
this.update();
});
</script>
</mk-url>

View File

@ -6,100 +6,90 @@
display block
width 256px
height 256px
</style>
<script>
@on \mount ~>
@draw!
@clock = set-interval @draw, 1000ms
const Vec2 = function(x, y) {
this.x = x;
this.y = y;
};
@on \unmount ~>
clear-interval @clock
this.on('mount', () => {
this.draw()
this.clock = setInterval(this.draw, 1000);
});
@draw = ~>
now = new Date!
s = now.get-seconds!
m = now.get-minutes!
h = now.get-hours!
this.on('unmount', () => {
clearInterval(this.clock);
});
vec2 = (x, y) ->
@x = x
@y = y
this.draw = () => {
const now = new Date();
const s = now.getSeconds();
const m = now.getMinutes();
const h = now.getHours();
ctx = @refs.canvas.get-context \2d
canv-w = @refs.canvas.width
canv-h = @refs.canvas.height
ctx.clear-rect 0, 0, canv-w, canv-h
const ctx = this.refs.canvas.getContext('2d');
const canvW = this.refs.canvas.width;
const canvH = this.refs.canvas.height;
ctx.clearRect(0, 0, canvW, canvH);
# 背景
center = (Math.min (canv-w / 2), (canv-h / 2))
line-start = center * 0.90
line-end-short = center * 0.87
line-end-long = center * 0.84
for i from 0 to 59 by 1
angle = Math.PI * i / 30
uv = new vec2 (Math.sin angle), (-Math.cos angle)
ctx.begin-path!
ctx.line-width = 1
ctx.move-to do
(canv-w / 2) + uv.x * line-start
(canv-h / 2) + uv.y * line-start
if i % 5 == 0
ctx.stroke-style = 'rgba(255, 255, 255, 0.2)'
ctx.line-to do
(canv-w / 2) + uv.x * line-end-long
(canv-h / 2) + uv.y * line-end-long
else
ctx.stroke-style = 'rgba(255, 255, 255, 0.1)'
ctx.line-to do
(canv-w / 2) + uv.x * line-end-short
(canv-h / 2) + uv.y * line-end-short
ctx.stroke!
{ // 背景
const center = Math.min((canvW / 2), (canvH / 2));
const lineStart = center * 0.90;
const shortLineEnd = center * 0.87;
const longLineEnd = center * 0.84;
for (let i = 0; i < 60; i++) {
const angle = Math.PI * i / 30;
const uv = new Vec2(Math.sin(angle), -Math.cos(angle));
ctx.beginPath();
ctx.lineWidth = 1;
ctx.moveTo((canvW / 2) + uv.x * lineStart, (canvH / 2) + uv.y * lineStart);
if (i % 5 == 0) {
ctx.strokeStyle = 'rgba(255, 255, 255, 0.2)';
ctx.lineTo((canvW / 2) + uv.x * longLineEnd, (canvH / 2) + uv.y * longLineEnd);
} else {
ctx.strokeStyle = 'rgba(255, 255, 255, 0.1)';
ctx.lineTo((canvW / 2) + uv.x * shortLineEnd, (canvH / 2) + uv.y * shortLineEnd);
}
ctx.stroke();
}
}
# 分
angle = Math.PI * (m + s / 60) / 30
length = (Math.min canv-w, canv-h) / 2.6
uv = new vec2 (Math.sin angle), (-Math.cos angle)
ctx.begin-path!
ctx.stroke-style = \#ffffff
ctx.line-width = 2
ctx.move-to do
(canv-w / 2) - uv.x * length / 5
(canv-h / 2) - uv.y * length / 5
ctx.line-to do
(canv-w / 2) + uv.x * length
(canv-h / 2) + uv.y * length
ctx.stroke!
{ // 分
const angle = Math.PI * (m + s / 60) / 30;
const length = Math.min(canvW, canvH) / 2.6;
const uv = new Vec2(Math.sin(angle), -Math.cos(angle));
ctx.beginPath();
ctx.strokeStyle = '#ffffff';
ctx.lineWidth = 2;
ctx.moveTo(canvW / 2 - uv.x * length / 5, canvH / 2 - uv.y * length / 5);
ctx.lineTo(canvW / 2 + uv.x * length, canvH / 2 + uv.y * length);
ctx.stroke();
}
# 時
angle = Math.PI * (h % 12 + m / 60) / 6
length = (Math.min canv-w, canv-h) / 4
uv = new vec2 (Math.sin angle), (-Math.cos angle)
ctx.begin-path!
#ctx.stroke-style = \#ffffff
ctx.stroke-style = CONFIG.theme-color
ctx.line-width = 2
ctx.move-to do
(canv-w / 2) - uv.x * length / 5
(canv-h / 2) - uv.y * length / 5
ctx.line-to do
(canv-w / 2) + uv.x * length
(canv-h / 2) + uv.y * length
ctx.stroke!
{ // 時
const angle = Math.PI * (h % 12 + m / 60) / 6;
const length = Math.min(canvW, canvH) / 4;
const uv = new Vec2(Math.sin(angle), -Math.cos(angle));
ctx.beginPath();
ctx.strokeStyle = CONFIG.themeColor;
ctx.lineWidth = 2;
ctx.moveTo(canvW / 2 - uv.x * length / 5, canvH / 2 - uv.y * length / 5);
ctx.lineTo(canvW / 2 + uv.x * length, canvH / 2 + uv.y * length);
ctx.stroke();
}
# 秒
angle = Math.PI * s / 30
length = (Math.min canv-w, canv-h) / 2.6
uv = new vec2 (Math.sin angle), (-Math.cos angle)
ctx.begin-path!
ctx.stroke-style = 'rgba(255, 255, 255, 0.5)'
ctx.line-width = 1
ctx.move-to do
(canv-w / 2) - uv.x * length / 5
(canv-h / 2) - uv.y * length / 5
ctx.line-to do
(canv-w / 2) + uv.x * length
(canv-h / 2) + uv.y * length
ctx.stroke!
{ // 秒
const angle = Math.PI * s / 30;
const length = Math.min(canvW, canvH) / 2.6;
const uv = new Vec2(Math.sin(angle), -Math.cos(angle));
ctx.beginPath();
ctx.strokeStyle = 'rgba(255, 255, 255, 0.5)';
ctx.lineWidth = 1;
ctx.moveTo(canvW / 2 - uv.x * length / 5, canvH / 2 - uv.y * length / 5);
ctx.lineTo(canvW / 2 + uv.x * length, canvH / 2 + uv.y * length);
ctx.stroke();
}
};
</script>
</mk-analog-clock>

View File

@ -80,108 +80,118 @@
</style>
<script>
@mixin \api
const contains = require('../../common/scripts/contains');
@q = @opts.q
@textarea = @opts.textarea
@loading = true
@users = []
@select = -1
this.mixin('api');
@on \mount ~>
@textarea.add-event-listener \keydown @on-keydown
this.q = this.opts.q;
this.textarea = this.opts.textarea;
this.fetching = true;
this.users = [];
this.select = -1;
all = document.query-selector-all 'body *'
Array.prototype.for-each.call all, (el) ~>
el.add-event-listener \mousedown @mousedown
this.on('mount', () => {
this.textarea.addEventListener('keydown', this.onKeydown);
@api \users/search_by_username do
query: @q
limit: 30users
.then (users) ~>
@users = users
@loading = false
@update!
.catch (err) ~>
console.error err
document.querySelectorAll('body *').forEach(el => {
el.addEventListener('mousedown', this.mousedown);
});
@on \unmount ~>
@textarea.remove-event-listener \keydown @on-keydown
this.api('users/search_by_username', {
query: this.q,
limit: 30
}).then(users => {
this.update({
fetching: false,
users: users
});
});
});
all = document.query-selector-all 'body *'
Array.prototype.for-each.call all, (el) ~>
el.remove-event-listener \mousedown @mousedown
this.on('unmount', () => {
this.textarea.removeEventListener('keydown', this.onKeydown);
@mousedown = (e) ~>
if (!contains @root, e.target) and (@root != e.target)
@close!
document.querySelectorAll('body *').forEach(el => {
el.removeEventListener('mousedown', this.mousedown);
});
});
@on-click = (e) ~>
@complete e.item
this.mousedown = e => {
if (!contains(this.root, e.target) && (this.root != e.target)) this.close();
};
@on-keydown = (e) ~>
key = e.which
switch (key)
| 10, 13 => # Key[ENTER]
if @select != -1
e.prevent-default!
e.stop-propagation!
@complete @users[@select]
else
@close!
| 27 => # Key[ESC]
e.prevent-default!
e.stop-propagation!
@close!
| 38 => # Key[↑]
if @select != -1
e.prevent-default!
e.stop-propagation!
@select-prev!
else
@close!
| 9, 40 => # Key[TAB] or Key[↓]
e.prevent-default!
e.stop-propagation!
@select-next!
| _ =>
@close!
this.onClick = e => {
this.complete(e.item);
};
@select-next = ~>
@select++
this.onKeydown = e => {
const cancel = () => {
e.preventDefault();
e.stopPropagation();
};
if @select >= @users.length
@select = 0
switch (e.which) {
case 10: // [ENTER]
case 13: // [ENTER]
if (this.select !== -1) {
cancel();
this.complete(this.users[this.select]);
} else {
this.close();
}
break;
@apply-select!
case 27: // [ESC]
cancel();
this.close();
break;
@select-prev = ~>
@select--
case 38: // [↑]
if (this.select !== -1) {
cancel();
this.selectPrev();
} else {
this.close();
}
break;
if @select < 0
@select = @users.length - 1
case 9: // [TAB]
case 40: // [↓]
cancel();
this.selectNext();
break;
@apply-select!
default:
this.close();
}
};
@apply-select = ~>
@refs.users.children.for-each (el) ~>
el.remove-attribute \data-selected
this.selectNext = () => {
if (++this.select >= this.users.length) this.select = 0;
this.applySelect();
};
@refs.users.children[@select].set-attribute \data-selected \true
@refs.users.children[@select].focus!
this.selectPrev = () => {
if (--this.select < 0) this.select = this.users.length - 1;
this.applySelect();
};
@complete = (user) ~>
@opts.complete user
this.applySelect = () => {
this.refs.users.children.forEach(el => {
el.removeAttribute('data-selected');
});
@close = ~>
@opts.close!
this.refs.users.children[this.select].setAttribute('data-selected', 'true');
this.refs.users.children[this.select].focus();
};
this.complete = user => {
this.opts.complete(user);
};
this.close = () => {
this.opts.close();
};
function contains(parent, child)
node = child.parent-node
while node?
if node == parent
return true
node = node.parent-node
return false
</script>
</mk-autocomplete-suggestion>

View File

@ -70,58 +70,74 @@
</style>
<script>
@mixin \api
@mixin \is-promise
@mixin \stream
this.mixin('api');
this.mixin('is-promise');
this.mixin('stream');
@user = null
@user-promise = if @is-promise @opts.user then @opts.user else Promise.resolve @opts.user
@init = true
@wait = false
this.user = null;
this.userPromise = this.isPromise(this.opts.user)
? this.opts.user
: Promise.resolve(this.opts.user);
this.init = true;
this.wait = false;
@on \mount ~>
@user-promise.then (user) ~>
@user = user
@init = false
@update!
@stream.on \follow @on-stream-follow
@stream.on \unfollow @on-stream-unfollow
this.on('mount', () => {
this.userPromise.then(user => {
this.update({
init: false,
user: user
});
this.stream.on('follow', this.onStreamFollow);
this.stream.on('unfollow', this.onStreamUnfollow);
});
});
@on \unmount ~>
@stream.off \follow @on-stream-follow
@stream.off \unfollow @on-stream-unfollow
this.on('unmount', () => {
this.stream.off('follow', this.onStreamFollow);
this.stream.off('unfollow', this.onStreamUnfollow);
});
@on-stream-follow = (user) ~>
if user.id == @user.id
@user = user
@update!
this.onStreamFollow = user => {
if (user.id == this.user.id) {
this.update({
user: user
});
}
};
@on-stream-unfollow = (user) ~>
if user.id == @user.id
@user = user
@update!
this.onStreamUnfollow = user => {
if (user.id == this.user.id) {
this.update({
user: user
});
}
};
@onclick = ~>
@wait = true
if @user.is_following
@api \following/delete do
user_id: @user.id
.then ~>
@user.is_following = false
.catch (err) ->
console.error err
.then ~>
@wait = false
@update!
else
@api \following/create do
user_id: @user.id
.then ~>
@user.is_following = true
.catch (err) ->
console.error err
.then ~>
@wait = false
@update!
this.onclick = () => {
this.wait = true;
if (this.user.is_following) {
this.api('following/delete', {
user_id: this.user.id
}).then(() => {
this.user.is_following = false;
}).catch(err => {
console.error(err);
}).then(() => {
this.wait = false;
this.update();
});
} else {
this.api('following/create', {
user_id: this.user.id
}).then(() => {
this.user.is_following = true;
}).catch(err => {
console.error(err);
}).then(() => {
this.wait = false;
this.update();
});
}
};
</script>
</mk-big-follow-button>

View File

@ -1,4 +1,5 @@
<mk-contextmenu><yield />
<mk-contextmenu>
<yield />
<style>
:scope
$width = 240px
@ -94,46 +95,45 @@
</style>
<script>
@root.add-event-listener \contextmenu (e) ~>
e.prevent-default!
const contains = require('../../common/scripts/contains');
@mousedown = (e) ~>
e.prevent-default!
if (!contains @root, e.target) and (@root != e.target)
@close!
return false
this.root.addEventListener('contextmenu', e => {
e.preventDefault();
});
@open = (pos) ~>
all = document.query-selector-all 'body *'
Array.prototype.for-each.call all, (el) ~>
el.add-event-listener \mousedown @mousedown
@root.style.display = \block
@root.style.left = pos.x + \px
@root.style.top = pos.y + \px
this.mousedown = e => {
e.preventDefault();
if (!contains(this.root, e.target) && (this.root != e.target)) this.close();
return false;
};
Velocity @root, \finish true
Velocity @root, { opacity: 0 } 0ms
Velocity @root, {
this.open = pos => {
document.querySelectorAll('body *').forEach(el => {
el.addEventListener('mousedown', this.mousedown);
});
this.root.style.display = 'block';
this.root.style.left = pos.x + 'px';
this.root.style.top = pos.y + 'px';
Velocity(this.root, 'finish', true);
Velocity(this.root, { opacity: 0 }, 0);
Velocity(this.root, {
opacity: 1
} {
queue: false
duration: 100ms
easing: \linear
}
}, {
queue: false,
duration: 100,
easing: 'linear'
});
};
@close = ~>
all = document.query-selector-all 'body *'
Array.prototype.for-each.call all, (el) ~>
el.remove-event-listener \mousedown @mousedown
@trigger \closed
@unmount!
this.close = () => {
document.querySelectorAll('body *').forEach(el => {
el.removeEventListener('mousedown', this.mousedown);
});
function contains(parent, child)
node = child.parent-node
while (node != null)
if (node == parent)
return true
node = node.parent-node
return false
this.trigger('closed');
this.unmount();
};
</script>
</mk-contextmenu>

View File

@ -158,31 +158,37 @@
</style>
<script>
@mixin \cropper
this.mixin('cropper');
@image = @opts.file
@title = @opts.title
@aspect-ratio = @opts.aspect-ratio
@cropper = null
this.image = this.opts.file;
this.title = this.opts.title;
this.aspectRatio = this.opts.aspectRatio;
this.cropper = null;
@on \mount ~>
@img = @refs.window.refs.img
@cropper = new @Cropper @img, do
aspect-ratio: @aspect-ratio
highlight: no
view-mode: 1
this.on('mount', () => {
this.img = this.refs.window.refs.img;
this.cropper = new this.Cropper(this.img, {
aspectRatio: this.aspectRatio,
highlight: false,
viewMode: 1
});
});
@ok = ~>
@cropper.get-cropped-canvas!.to-blob (blob) ~>
@trigger \cropped blob
@refs.window.close!
this.ok = () => {
this.cropper.getCroppedCanvas().toBlob(blob => {
this.trigger('cropped', blob);
this.refs.window.close();
});
};
@skip = ~>
@trigger \skiped
@refs.window.close!
this.skip = () => {
this.trigger('skiped');
this.refs.window.close();
};
@cancel = ~>
@trigger \canceled
@refs.window.close!
this.cancel = () => {
this.trigger('canceled');
this.refs.window.close();
};
</script>
</mk-crop-window>

View File

@ -79,69 +79,72 @@
</style>
<script>
@can-through = if opts.can-through? then opts.can-through else true
@opts.buttons.for-each (button) ~>
button._onclick = ~>
if button.onclick?
button.onclick!
@close!
this.canThrough = opts.canThrough != null ? opts.canThrough : true;
this.opts.buttons.forEach(button => {
button._onclick = () => {
if (button.onclick) button.onclick();
this.close();
};
});
@on \mount ~>
@refs.header.innerHTML = @opts.title
@refs.body.innerHTML = @opts.text
this.on('mount', () => {
this.refs.header.innerHTML = this.opts.title;
this.refs.body.innerHTML = this.opts.text;
@refs.bg.style.pointer-events = \auto
Velocity @refs.bg, \finish true
Velocity @refs.bg, {
this.refs.bg.style.pointerEvents = 'auto';
Velocity(this.refs.bg, 'finish', true);
Velocity(this.refs.bg, {
opacity: 1
} {
queue: false
duration: 100ms
easing: \linear
}
}, {
queue: false,
duration: 100,
easing: 'linear'
});
Velocity @refs.main, {
opacity: 0
Velocity(this.refs.main, {
opacity: 0,
scale: 1.2
} {
}, {
duration: 0
}
Velocity @refs.main, {
opacity: 1
});
Velocity(this.refs.main, {
opacity: 1,
scale: 1
} {
duration: 300ms
}, {
duration: 300,
easing: [ 0, 0.5, 0.5, 1 ]
}
});
});
@close = ~>
@refs.bg.style.pointer-events = \none
Velocity @refs.bg, \finish true
Velocity @refs.bg, {
this.close = () => {
this.refs.bg.style.pointerEvents = 'none';
Velocity(this.refs.bg, 'finish', true);
Velocity(this.refs.bg, {
opacity: 0
} {
queue: false
duration: 300ms
easing: \linear
}
}, {
queue: false,
duration: 300,
easing: 'linear'
});
@refs.main.style.pointer-events = \none
Velocity @refs.main, \finish true
Velocity @refs.main, {
opacity: 0
this.refs.main.style.pointerEvents = 'none';
Velocity(this.refs.main, 'finish', true);
Velocity(this.refs.main, {
opacity: 0,
scale: 0.8
} {
queue: false
duration: 300ms
easing: [ 0.5, -0.5, 1, 0.5 ]
complete: ~>
@unmount!
}
}, {
queue: false,
duration: 300,
easing: [ 0.5, -0.5, 1, 0.5 ],
complete: () => this.unmount()
});
};
@bg-click = ~>
if @can-through
if @opts.on-through?
@opts.on-through!
@close!
this.bgClick = () => {
if (this.canThrough) {
if (this.opts.onThrough) this.opts.onThrough();
this.close();
}
};
</script>
</mk-dialog>

View File

@ -47,21 +47,22 @@
</style>
<script>
@mixin \api
@mixin \i
this.mixin('api');
this.mixin('i');
@close = (e) ~>
e.prevent-default!
e.stop-propagation!
this.close = e => {
e.preventDefault();
e.stopPropagation();
@I.data.no_donation = true
@I.update!
@api \i/appdata/set do
data: JSON.stringify do
no_donation: @I.data.no_donation
this.I.data.no_donation = true;
this.I.update();
this.api('i/appdata/set', {
data: JSON.stringify({
no_donation: this.I.data.no_donation
})
});
@unmount!
@parent.parent.set-root-layout!
this.unmount();
};
</script>
</mk-donation>

View File

@ -13,26 +13,32 @@
</ul>
</mk-contextmenu>
<script>
@browser = @opts.browser
this.browser = this.opts.browser;
@on \mount ~>
@refs.ctx.on \closed ~>
@trigger \closed
@unmount!
this.on('mount', () => {
this.refs.ctx.on('closed', () => {
this.trigger('closed');
this.unmount();
});
});
@open = (pos) ~>
@refs.ctx.open pos
this.open = pos => {
this.refs.ctx.open(pos);
};
@create-folder = ~>
@browser.create-folder!
@refs.ctx.close!
this.createFolder = () => {
this.browser.createFolder();
this.refs.ctx.close();
};
@upload = ~>
@browser.select-local-file!
@refs.ctx.close!
this.upload = () => {
this.browser.selectLocalFile();
this.refs.ctx.close();
};
@url-upload = ~>
@browser.url-upload!
@refs.ctx.close!
this.urlUpload = () => {
this.browser.urlUpload();
this.refs.ctx.close();
};
</script>
</mk-drive-browser-base-contextmenu>

View File

@ -28,19 +28,24 @@
</style>
<script>
@mixin \api
this.mixin('api');
@folder = if @opts.folder? then @opts.folder else null
this.folder = this.opts.folder ? this.opts.folder : null;
@on \mount ~>
@refs.window.on \closed ~>
@unmount!
this.on('mount', () => {
this.refs.window.on('closed', () => {
this.unmount();
});
@api \drive .then (info) ~>
@update do
this.api('drive').then(info => {
this.update({
usage: info.usage / info.capacity * 100
});
});
});
@close = ~>
@refs.window.close!
this.close = () => {
this.refs.window.close();
};
</script>
</mk-drive-browser-window>

View File

@ -8,7 +8,7 @@
</div>
<input class="search" type="search" placeholder="&#xf002; 検索"/>
</nav>
<div class="main { uploading: uploads.length > 0, loading: loading }" ref="main" onmousedown={ onmousedown } ondragover={ ondragover } ondragenter={ ondragenter } ondragleave={ ondragleave } ondrop={ ondrop } oncontextmenu={ oncontextmenu }>
<div class="main { uploading: uploads.length > 0, fetching: fetching }" ref="main" onmousedown={ onmousedown } ondragover={ ondragover } ondragenter={ ondragenter } ondragleave={ ondragleave } ondrop={ ondrop } oncontextmenu={ oncontextmenu }>
<div class="selection" ref="selection"></div>
<div class="contents" ref="contents">
<div class="folders" ref="foldersContainer" if={ folders.length > 0 }>
@ -23,13 +23,13 @@
</virtual>
<button if={ moreFiles }>もっと読み込む</button>
</div>
<div class="empty" if={ files.length == 0 && folders.length == 0 && !loading }>
<div class="empty" if={ files.length == 0 && folders.length == 0 && !fetching }>
<p if={ draghover }>ドロップですか?いいですよ、ボクはカワイイですからね</p>
<p if={ !draghover && folder == null }><strong>ドライブには何もありません。</strong><br/>右クリックして「ファイルをアップロード」を選んだり、ファイルをドラッグ&ドロップすることでもアップロードできます。</p>
<p if={ !draghover && folder != null }>このフォルダーは空です</p>
</div>
</div>
<div class="loading" if={ loading }>
<div class="loading" if={ fetching }>
<div class="spinner">
<div class="dot1"></div>
<div class="dot2"></div>
@ -133,7 +133,7 @@
&, *
user-select none
&.loading
&.fetching
cursor wait !important
*
@ -184,7 +184,7 @@
> p
margin 0
> .loading
> .fetching
.spinner
margin 100px auto
width 40px
@ -238,418 +238,439 @@
</style>
<script>
@mixin \api
@mixin \dialog
@mixin \input-dialog
@mixin \stream
@files = []
@folders = []
@hierarchy-folders = []
@uploads = []
# 現在の階層(フォルダ)
# * null でルートを表す
@folder = null
@multiple = if @opts.multiple? then @opts.multiple else false
# ドロップされようとしているか
@draghover = false
# 自信の所有するアイテムがドラッグをスタートさせたか
# (自分自身の階層にドロップできないようにするためのフラグ)
@is-drag-source = false
@on \mount ~>
@refs.uploader.on \uploaded (file) ~>
@add-file file, true
@refs.uploader.on \change-uploads (uploads) ~>
@uploads = uploads
@update!
@stream.on \drive_file_created @on-stream-drive-file-created
@stream.on \drive_file_updated @on-stream-drive-file-updated
@stream.on \drive_folder_created @on-stream-drive-folder-created
@stream.on \drive_folder_updated @on-stream-drive-folder-updated
# Riotのバグでnullを渡しても""になる
# https://github.com/riot/riot/issues/2080
#if @opts.folder?
if @opts.folder? and @opts.folder != ''
@move @opts.folder
else
@load!
@on \unmount ~>
@stream.off \drive_file_created @on-stream-drive-file-created
@stream.off \drive_file_updated @on-stream-drive-file-updated
@stream.off \drive_folder_created @on-stream-drive-folder-created
@stream.off \drive_folder_updated @on-stream-drive-folder-updated
@on-stream-drive-file-created = (file) ~>
@add-file file, true
@on-stream-drive-file-updated = (file) ~>
current = if @folder? then @folder.id else null
if current != file.folder_id
@remove-file file
else
@add-file file, true
@on-stream-drive-folder-created = (folder) ~>
@add-folder folder, true
@on-stream-drive-folder-updated = (folder) ~>
current = if @folder? then @folder.id else null
if current != folder.parent_id
@remove-folder folder
else
@add-folder folder, true
@onmousedown = (e) ~>
if (contains @refs.folders-container, e.target) or (contains @refs.files-container, e.target)
return true
rect = @refs.main.get-bounding-client-rect!
left = e.page-x + @refs.main.scroll-left - rect.left - window.page-x-offset
top = e.page-y + @refs.main.scroll-top - rect.top - window.page-y-offset
move = (e) ~>
@refs.selection.style.display = \block
cursor-x = e.page-x + @refs.main.scroll-left - rect.left - window.page-x-offset
cursor-y = e.page-y + @refs.main.scroll-top - rect.top - window.page-y-offset
w = cursor-x - left
h = cursor-y - top
if w > 0
@refs.selection.style.width = w + \px
@refs.selection.style.left = left + \px
else
@refs.selection.style.width = -w + \px
@refs.selection.style.left = cursor-x + \px
if h > 0
@refs.selection.style.height = h + \px
@refs.selection.style.top = top + \px
else
@refs.selection.style.height = -h + \px
@refs.selection.style.top = cursor-y + \px
up = (e) ~>
document.document-element.remove-event-listener \mousemove move
document.document-element.remove-event-listener \mouseup up
@refs.selection.style.display = \none
document.document-element.add-event-listener \mousemove move
document.document-element.add-event-listener \mouseup up
@path-oncontextmenu = (e) ~>
e.prevent-default!
e.stop-immediate-propagation!
return false
@ondragover = (e) ~>
e.prevent-default!
e.stop-propagation!
# ドラッグ元が自分自身の所有するアイテムかどうか
if !@is-drag-source
# ドラッグされてきたものがファイルだったら
if e.data-transfer.effect-allowed == \all
e.data-transfer.drop-effect = \copy
else
e.data-transfer.drop-effect = \move
@draghover = true
else
# 自分自身にはドロップさせない
e.data-transfer.drop-effect = \none
return false
@ondragenter = (e) ~>
e.prevent-default!
if !@is-drag-source
@draghover = true
@ondragleave = (e) ~>
@draghover = false
@ondrop = (e) ~>
e.prevent-default!
e.stop-propagation!
@draghover = false
# ドロップされてきたものがファイルだったら
if e.data-transfer.files.length > 0
Array.prototype.for-each.call e.data-transfer.files, (file) ~>
@upload file, @folder
return false
# データ取得
data = e.data-transfer.get-data 'text'
if !data?
return false
# パース
obj = JSON.parse data
# (ドライブの)ファイルだったら
if obj.type == \file
file = obj.id
if (@files.some (f) ~> f.id == file)
return false
@remove-file file
@api \drive/files/update do
file_id: file
folder_id: if @folder? then @folder.id else null
.then ~>
# something
.catch (err, text-status) ~>
console.error err
# (ドライブの)フォルダーだったら
else if obj.type == \folder
folder = obj.id
# 移動先が自分自身ならreject
if @folder? and folder == @folder.id
return false
if (@folders.some (f) ~> f.id == folder)
return false
@remove-folder folder
@api \drive/folders/update do
folder_id: folder
parent_id: if @folder? then @folder.id else null
.then ~>
# something
.catch (err) ~>
if err == 'detected-circular-definition'
@dialog do
'<i class="fa fa-exclamation-triangle"></i>操作を完了できません'
'移動先のフォルダーは、移動するフォルダーのサブフォルダーです。'
[
text: \OK
]
return false
@oncontextmenu = (e) ~>
e.prevent-default!
e.stop-immediate-propagation!
ctx = document.body.append-child document.create-element \mk-drive-browser-base-contextmenu
ctx = riot.mount ctx, do
browser: @
ctx = ctx.0
ctx.open do
x: e.page-x - window.page-x-offset
y: e.page-y - window.page-y-offset
return false
@select-local-file = ~>
@refs.file-input.click!
@url-upload = ~>
url <~ @input-dialog do
'URLアップロード'
'アップロードしたいファイルのURL'
null
if url? and url != ''
@api \drive/files/upload_from_url do
url: url
folder_id: if @folder? then @folder.id else undefined
@dialog do
'<i class="fa fa-check"></i>アップロードをリクエストしました'
'アップロードが完了するまで時間がかかる場合があります。'
[
text: \OK
]
@create-folder = ~>
name <~ @input-dialog do
'フォルダー作成'
'フォルダー名'
null
@api \drive/folders/create do
name: name
folder_id: if @folder? then @folder.id else undefined
.then (folder) ~>
@add-folder folder, true
@update!
.catch (err) ~>
console.error err
@change-file-input = ~>
files = @refs.file-input.files
for i from 0 to files.length - 1
file = files.item i
@upload file, @folder
@upload = (file, folder) ~>
if folder? and typeof folder == \object
folder = folder.id
@refs.uploader.upload file, folder
@get-selection = ~>
@files.filter (file) -> file._selected
@new-window = (folder-id) ~>
browser = document.body.append-child document.create-element \mk-drive-browser-window
riot.mount browser, do
folder: folder-id
@move = (target-folder) ~>
if target-folder? and typeof target-folder == \object
target-folder = target-folder.id
if target-folder == null
@go-root!
return
@loading = true
@update!
@api \drive/folders/show do
folder_id: target-folder
.then (folder) ~>
@folder = folder
@hierarchy-folders = []
x = (f) ~>
@hierarchy-folders.unshift f
if f.parent?
x f.parent
if folder.parent?
x folder.parent
@update!
@load!
.catch (err, text-status) ->
console.error err
@add-folder = (folder, unshift = false) ~>
current = if @folder? then @folder.id else null
if current != folder.parent_id
return
if (@folders.some (f) ~> f.id == folder.id)
exist = (@folders.map (f) -> f.id).index-of folder.id
@folders[exist] = folder
@update!
return
if unshift
@folders.unshift folder
else
@folders.push folder
@update!
@add-file = (file, unshift = false) ~>
current = if @folder? then @folder.id else null
if current != file.folder_id
return
if (@files.some (f) ~> f.id == file.id)
exist = (@files.map (f) -> f.id).index-of file.id
@files[exist] = file
@update!
return
if unshift
@files.unshift file
else
@files.push file
@update!
@remove-folder = (folder) ~>
if typeof folder == \object
folder = folder.id
@folders = @folders.filter (f) -> f.id != folder
@update!
@remove-file = (file) ~>
if typeof file == \object
file = file.id
@files = @files.filter (f) -> f.id != file
@update!
@go-root = ~>
if @folder != null
@folder = null
@hierarchy-folders = []
@update!
@load!
@load = ~>
@folders = []
@files = []
@more-folders = false
@more-files = false
@loading = true
@update!
load-folders = null
load-files = null
folders-max = 30
files-max = 30
# フォルダ一覧取得
@api \drive/folders do
folder_id: if @folder? then @folder.id else null
limit: folders-max + 1
.then (folders) ~>
if folders.length == folders-max + 1
@more-folders = true
folders.pop!
load-folders := folders
complete!
.catch (err, text-status) ~>
console.error err
# ファイル一覧取得
@api \drive/files do
folder_id: if @folder? then @folder.id else null
limit: files-max + 1
.then (files) ~>
if files.length == files-max + 1
@more-files = true
files.pop!
load-files := files
complete!
.catch (err, text-status) ~>
console.error err
flag = false
complete = ~>
if flag
load-folders.for-each (folder) ~>
@add-folder folder
load-files.for-each (file) ~>
@add-file file
@loading = false
@update!
else
flag := true
function contains(parent, child)
node = child.parent-node
while node?
if node == parent
return true
node = node.parent-node
return false
const contains = require('../../../common/scripts/contains');
this.mixin('api');
this.mixin('dialog');
this.mixin('input-dialog');
this.mixin('stream');
this.files = [];
this.folders = [];
this.hierarchyFolders = [];
this.uploads = [];
// 現在の階層(フォルダ)
// * null でルートを表す
this.folder = null;
this.multiple = this.opts.multiple != null ? this.opts.multiple : false;
// ドロップされようとしているか
this.draghover = false;
// 自信の所有するアイテムがドラッグをスタートさせたか
// (自分自身の階層にドロップできないようにするためのフラグ)
this.isDragSource = false;
this.on('mount', () => {
this.refs.uploader.on('uploaded', file => {
this.addFile(file, true);
});
this.refs.uploader.on('change-uploads', uploads => {
this.update({
uploads: uploads
});
});
this.stream.on('drive_file_created', this.onStreamDriveFileCreated);
this.stream.on('drive_file_updated', this.onStreamDriveFileUpdated);
this.stream.on('drive_folder_created', this.onStreamDriveFolderCreated);
this.stream.on('drive_folder_updated', this.onStreamDriveFolderUpdated);
// Riotのバグでnullを渡しても""になる
// https://github.com/riot/riot/issues/2080
//if (this.opts.folder)
if (this.opts.folder && this.opts.folder != '') {
this.move(this.opts.folder);
} else {
this.load();
}
});
this.on('unmount', () => {
this.stream.off('drive_file_created', this.onStreamDriveFileCreated);
this.stream.off('drive_file_updated', this.onStreamDriveFileUpdated);
this.stream.off('drive_folder_created', this.onStreamDriveFolderCreated);
this.stream.off('drive_folder_updated', this.onStreamDriveFolderUpdated);
});
this.onStreamDriveFileCreated = file => {
this.addFile(file, true);
};
this.onStreamDriveFileUpdated = file => {
const current = this.folder ? this.folder.id : null;
if (current != file.folder_id) {
this.removeFile(file);
} else {
this.addFile(file, true);
}
};
this.onStreamDriveFolderCreated = folder => {
this.addFolder(folder, true);
};
this.onStreamDriveFolderUpdated = folder => {
const current = this.folder ? this.folder.id : null;
if (current != folder.parent_id) {
this.removeFolder(folder);
} else {
this.addFolder(folder, true);
}
};
this.onmousedown = e => {
if (contains(this.refs.foldersContainer, e.target) || contains(this.refs.filesContainer, e.target)) return true;
const rect = this.refs.main.getBoundingClientRect();
const left = e.pageX + this.refs.main.scrollLeft - rect.left - window.pageXOffset
const top = e.pageY + this.refs.main.scrollTop - rect.top - window.pageYOffset
const move = e => {
this.refs.selection.style.display = 'block';
const cursorX = e.pageX + this.refs.main.scrollLeft - rect.left - window.pageXOffset;
const cursorY = e.pageY + this.refs.main.scrollTop - rect.top - window.pageYOffset;
const w = cursorX - left;
const h = cursorY - top;
if (w > 0) {
this.refs.selection.style.width = w + 'px';
this.refs.selection.style.left = left + 'px';
} else {
this.refs.selection.style.width = -w + 'px';
this.refs.selection.style.left = cursorX + 'px';
}
if (h > 0) {
this.refs.selection.style.height = h + 'px';
this.refs.selection.style.top = top + 'px';
} else {
this.refs.selection.style.height = -h + 'px';
this.refs.selection.style.top = cursorY + 'px';
}
};
up = e => {
document.documentElement.removeEventListener('mousemove', move);
document.documentElement.removeEventListener('mouseup', up);
this.refs.selection.style.display = 'none';
};
document.documentElement.addEventListener('mousemove', move);
document.documentElement.addEventListener('mouseup', up);
};
this.pathOncontextmenu = e => {
e.preventDefault();
e.stopImmediatePropagation();
return false;
};
this.ondragover = e => {
e.preventDefault();
e.stopPropagation();
// ドラッグ元が自分自身の所有するアイテムかどうか
if (!this.isDragSource) {
// ドラッグされてきたものがファイルだったら
e.dataTransfer.dropEffect = e.dataTransfer.effectAllowed == 'all' ? 'copy' : 'move';
this.draghover = true;
} else {
// 自分自身にはドロップさせない
e.dataTransfer.dropEffect = 'none';
return false;
}
};
this.ondragenter = e => {
e.preventDefault();
if (!this.isDragSource) this.draghover = true;
};
this.ondragleave = e => {
this.draghover = false;
};
this.ondrop = e => {
e.preventDefault();
e.stopPropagation();
this.draghover = false;
// ドロップされてきたものがファイルだったら
if (e.dataTransfer.files.length > 0) {
e.dataTransfer.files.forEach(file => {
this.upload(file, this.folder);
});
return false;
}
// データ取得
const data = e.dataTransfer.getData('text');
if (data == null) return false;
// パース
// TODO: JSONじゃなかったら中断
const obj = JSON.parse(data);
// (ドライブの)ファイルだったら
if (obj.type == 'file') {
const file = obj.id;
if (this.files.some(f => f.id == file)) return false;
this.removeFile(file);
this.api('drive/files/update', {
file_id: file,
folder_id: this.folder ? this.folder.id : null
});
// (ドライブの)フォルダーだったら
} else if (obj.type == 'folder') {
const folder = obj.id;
// 移動先が自分自身ならreject
if (this.folder && folder == this.folder.id) return false;
if (this.folders.some(f => f.id == folder)) return false;
this.removeFolder(folder);
this.api('drive/folders/update', {
folder_id: folder,
parent_id: this.folder ? this.folder.id : null
}).then(() => {
// something
}).catch(err => {
switch (err) {
case 'detected-circular-definition':
this.dialog('<i class="fa fa-exclamation-triangle"></i>操作を完了できません',
'移動先のフォルダーは、移動するフォルダーのサブフォルダーです。', [{
text: 'OK'
}]);
break;
default:
alert('不明なエラー' + err);
}
});
}
return false;
};
this.oncontextmenu = e => {
e.preventDefault();
e.stopImmediatePropagation();
const ctx = riot.mount(document.body.appendChild(document.createElement('mk-drive-browser-base-contextmenu')), {
browser: this
})[0];
ctx.open({
x: e.pageX - window.pageXOffset,
y: e.pageY - window.pageYOffset
});
return false;
};
this.selectLocalFile = () => {
this.refs.fileInput.click();
};
this.urlUpload = () => {
this.inputDialog('URLアップロード', 'アップロードしたいファイルのURL', null, url => {
this.api('drive/files/upload_from_url', {
url: url,
folder_id: this.folder ? this.folder.id : undefined
});
this.dialog('<i class="fa fa-check"></i>アップロードをリクエストしました',
'アップロードが完了するまで時間がかかる場合があります。', [{
text: 'OK'
}]);
});
};
this.createFolder = () => {
this.inputDialog('フォルダー作成', 'フォルダー名', null, name => {
this.api('drive/folders/create', {
name: name,
folder_id: this.folder ? this.folder.id : undefined
}).then(folder => {
this.addFolder(folder, true);
this.update();
});
});
};
this.changeFileInput = () => {
this.refs.fileInput.files.forEach(file => {
this.upload(file, this.folder);
});
};
this.upload = (file, folder) => {
if (folder && typeof folder == 'object') folder = folder.id;
this.refs.uploader.upload(file, folder);
};
this.getSelection = () => {
this.files.filter(file => file._selected);
};
this.newWindow = folderId => {
riot.mount(document.body.appendChild(document.createElement('mk-drive-browser-window')), {
folder: folderId
});
};
this.move = target => {
if (target == null) {
this.goRoot();
return;
} else if (typeof target == 'object') {
target = target.id;
}
this.update({
fetching: true
});
this.api('drive/folders/show', {
folder_id: target
}).then(folder => {
this.folder = folder;
this.hierarchyFolders = [];
const dive = folder => {
this.hierarchyFolders.unshift(folder);
if (folder.parent) dive(folder.parent);
};
if (folder.parent) dive(folder.parent);
this.update();
this.load();
});
};
this.addFolder = (folder, unshift = false) => {
const current = this.folder ? this.folder.id : null;
if (current != folder.parent_id) return;
if (this.folders.some(f => f.id == folder.id)) {
const exist = this.folders.map(f => f.id).indexOf(folder.id);
this.folders[exist] = folder;
this.update();
return;
}
if (unshift) {
this.folders.unshift(folder);
} else {
this.folders.push(folder);
}
this.update();
};
this.addFile = (file, unshift = false) => {
const current = this.folder ? this.folder.id : null;
if (current != file.folder_id) return;
if (this.files.some(f => f.id == file.id)) {
const exist = this.files.map(f => f.id).indexOf(file.id);
this.files[exist] = file;
this.update();
return;
}
if (unshift) {
this.files.unshift(file);
} else {
this.files.push(file);
}
this.update();
};
this.removeFolder = folder => {
if (typeof folder == 'object') folder = folder.id;
this.folders = this.folders.filter(f => f.id != folder);
this.update();
};
this.removeFile = file => {
if (typeof file == 'object') file = file.id;
this.files = this.files.filter(f => f.id != file);
this.update();
};
this.goRoot = () => {
// 既にrootにいるなら何もしない
if (this.folder == null) return;
this.update({
folder: null,
hierarchyFolders: []
});
this.load();
};
this.load = () => {
this.update({
folders: [],
files: [],
moreFolders: false,
moreFiles: false,
fetching: true
});
let fetchedFolders = null;
let fetchedFiles = null;
const foldersMax = 30;
const filesMax = 30;
// フォルダ一覧取得
this.api('drive/folders', {
folder_id: this.folder ? this.folder.id : null,
limit: foldersMax + 1
}).then(folders => {
if (folders.length == foldersMax + 1) {
this.moreFolders = true;
folders.pop();
}
fetchedFolders = folders;
complete();
});
// ファイル一覧取得
this.api('drive/files', {
folder_id: this.folder ? this.folder.id : null,
limit: filesMax + 1
}).then(files => {
if (files.length == filesMax + 1) {
this.moreFiles = true;
files.pop();
}
fetchedFiles = files;
complete();
});
let flag = false;
const complete = () => {
if (flag) {
fetchedFolders.forEach(this.addFolder);
fetchedFiles.forEach(this.addFile);
this.update({
fetching: false
});
} else {
flag = true;
}
};
};
</script>
</mk-drive-browser>

View File

@ -22,9 +22,6 @@
<li onclick={ parent.setBanner }>
<p>バナーに設定</p>
</li>
<li onclick={ parent.setWallpaper }>
<p>壁紙に設定</p>
</li>
</ul>
</li>
<li class="has-child">
@ -38,60 +35,58 @@
</ul>
</mk-contextmenu>
<script>
@mixin \api
@mixin \i
@mixin \update-avatar
@mixin \update-banner
@mixin \update-wallpaper
@mixin \input-dialog
@mixin \NotImplementedException
this.mixin('api');
this.mixin('i');
this.mixin('update-avatar');
this.mixin('update-banner');
this.mixin('input-dialog');
this.mixin('NotImplementedException');
@browser = @opts.browser
@file = @opts.file
this.browser = this.opts.browser;
this.file = this.opts.file;
@on \mount ~>
@refs.ctx.on \closed ~>
@trigger \closed
@unmount!
this.on('mount', () => {
this.refs.ctx.on('closed', () => {
this.trigger('closed');
this.unmount();
});
});
@open = (pos) ~>
@refs.ctx.open pos
this.open = pos => {
this.refs.ctx.open(pos);
};
@rename = ~>
@refs.ctx.close!
this.rename = () => {
this.refs.ctx.close();
name <~ @input-dialog do
'ファイル名の変更'
'新しいファイル名を入力してください'
@file.name
this.inputDialog('ファイル名の変更', '新しいファイル名を入力してください', this.file.name, name => {
this.api('drive/files/update', {
file_id: this.file.id,
name: name
})
});
};
@api \drive/files/update do
file_id: @file.id
name: name
.then ~>
# something
.catch (err) ~>
console.error err
this.copyUrl = () => {
this.NotImplementedException();
};
@copy-url = ~>
@NotImplementedException!
this.download = () => {
this.refs.ctx.close();
};
@download = ~>
@refs.ctx.close!
this.setAvatar = () => {
this.refs.ctx.close();
this.updateAvatar(this.I, null, this.file);
};
@set-avatar = ~>
@refs.ctx.close!
@update-avatar @I, null, @file
this.setBanner = () => {
this.refs.ctx.close();
this.updateBanner(this.I, null, this.file);
};
@set-banner = ~>
@refs.ctx.close!
@update-banner @I, null, @file
@set-wallpaper = ~>
@refs.ctx.close!
@update-wallpaper @I, null, @file
@add-app = ~>
@NotImplementedException!
this.addApp = () => {
this.NotImplementedException();
};
</script>
</mk-drive-browser-file-contextmenu>

View File

@ -144,66 +144,76 @@
</style>
<script>
@bytes-to-size = require '../../../common/scripts/bytes-to-size.js'
this.bytesToSize = require('../../../common/scripts/bytes-to-size');
@mixin \i
this.mixin('i');
@file = @opts.file
@browser = @parent
this.file = this.opts.file;
this.browser = this.parent;
@title = @file.name + '\n' + @file.type + ' ' + (@bytes-to-size @file.datasize)
this.title = `${this.file.name}\n${this.file.type} ${this.bytesToSize(this.file.datasize)}`;
@is-contextmenu-showing = false
this.isContextmenuShowing = false;
@onclick = ~>
if @browser.multiple
if @file._selected?
@file._selected = !@file._selected
else
@file._selected = true
@browser.trigger \change-selection @browser.get-selection!
else
if @file._selected
@browser.trigger \selected @file
else
@browser.files.for-each (file) ~>
file._selected = false
@file._selected = true
@browser.trigger \change-selection @file
this.onclick = () => {
if (this.browser.multiple) {
if (this.file._selected != null) {
this.file._selected = !this.file._selected;
} else {
this.file._selected = true;
}
this.browser.trigger('change-selection', this.browser.getSelection());
} else {
if (this.file._selected) {
this.browser.trigger('selected', this.file);
} else {
this.browser.files.forEach(file => file._selected = false);
this.file._selected = true;
this.browser.trigger('change-selection', this.file);
}
}
};
@oncontextmenu = (e) ~>
e.prevent-default!
e.stop-immediate-propagation!
this.oncontextmenu = e => {
e.preventDefault();
e.stopImmediatePropagation();
@is-contextmenu-showing = true
@update!
ctx = document.body.append-child document.create-element \mk-drive-browser-file-contextmenu
ctx = riot.mount ctx, do
browser: @browser
file: @file
ctx = ctx.0
ctx.open do
x: e.page-x - window.page-x-offset
y: e.page-y - window.page-y-offset
ctx.on \closed ~>
@is-contextmenu-showing = false
@update!
return false
this.update({
isContextmenuShowing: true
});
const ctx = riot.mount(document.body.appendChild(document.createElement('mk-drive-browser-file-contextmenu')), {
browser: this.browser,
file: this.file
})[0];
ctx.open({
x: e.pageX - window.pageXOffset,
y: e.pageY - window.pageYOffset
});
ctx.on('closed', () => {
this.update({
isContextmenuShowing: false
});
});
return false;
};
@ondragstart = (e) ~>
e.data-transfer.effect-allowed = \move
e.data-transfer.set-data 'text' JSON.stringify do
type: \file
id: @file.id
file: @file
@is-dragging = true
this.ondragstart = e => {
e.dataTransfer.effectAllowed = 'move';
e.dataTransfer.setData('text', JSON.stringify({
type: 'file',
id: this.file.id,
file: this.file
}));
this.isDragging = true;
# 親ブラウザに対して、ドラッグが開始されたフラグを立てる
# (=あなたの子供が、ドラッグを開始しましたよ)
@browser.is-drag-source = true
// 親ブラウザに対して、ドラッグが開始されたフラグを立てる
// (=あなたの子供が、ドラッグを開始しましたよ)
this.browser.isDragSource = true;
};
@ondragend = (e) ~>
@is-dragging = false
@browser.is-drag-source = false
this.ondragend = e => {
this.isDragging = false;
this.browser.isDragSource = false;
};
</script>
</mk-drive-browser-file>

View File

@ -18,49 +18,45 @@
</ul>
</mk-contextmenu>
<script>
@mixin \api
@mixin \input-dialog
this.mixin('api');
this.mixin('input-dialog');
@browser = @opts.browser
@folder = @opts.folder
this.browser = this.opts.browser;
this.folder = this.opts.folder;
@open = (pos) ~>
@refs.ctx.open pos
this.open = pos => {
this.refs.ctx.open(pos);
@refs.ctx.on \closed ~>
@trigger \closed
@unmount!
this.refs.ctx.on('closed', () => {
this.trigger('closed');
this.unmount();
});
};
@move = ~>
@browser.move @folder.id
@refs.ctx.close!
this.move = () => {
this.browser.move(this.folder.id);
this.refs.ctx.close();
};
@new-window = ~>
@browser.new-window @folder.id
@refs.ctx.close!
this.newWindow = () => {
this.browser.newWindow(this.folder.id);
this.refs.ctx.close();
};
@create-folder = ~>
@browser.create-folder!
@refs.ctx.close!
this.createFolder = () => {
this.browser.createFolder();
this.refs.ctx.close();
};
@upload = ~>
@browser.select-lcoal-file!
@refs.ctx.close!
this.rename = () => {
this.refs.ctx.close();
@rename = ~>
@refs.ctx.close!
name <~ @input-dialog do
'フォルダ名の変更'
'新しいフォルダ名を入力してください'
@folder.name
@api \drive/folders/update do
folder_id: @folder.id
name: name
.then ~>
# something
.catch (err) ~>
console.error err
this.inputialog('フォルダ名の変更', '新しいフォルダ名を入力してください', this.folder.name, name => {
this.api('drive/folders/update', {
folder_id: this.folder.id,
name: name
});
});
};
</script>
</mk-drive-browser-folder-contextmenu>

View File

@ -50,135 +50,152 @@
</style>
<script>
@mixin \api
@mixin \dialog
this.mixin('api');
this.mixin('dialog');
@folder = @opts.folder
@browser = @parent
this.folder = this.opts.folder;
this.browser = this.parent;
@title = @folder.name
@hover = false
@draghover = false
@is-contextmenu-showing = false
this.title = this.folder.name;
this.hover = false;
this.draghover = false;
this.isContextmenuShowing = false;
@onclick = ~>
@browser.move @folder
this.onclick = () => {
this.browser.move(this.folder);
};
@onmouseover = ~>
@hover = true
this.onmouseover = () => {
this.hover = true;
};
@onmouseout = ~>
@hover = false
this.onmouseout = () => {
this.hover = false
};
@ondragover = (e) ~>
e.prevent-default!
e.stop-propagation!
this.ondragover = e => {
e.preventDefault();
e.stopPropagation();
# 自分自身がドラッグされていない場合
if !@is-dragging
# ドラッグされてきたものがファイルだったら
if e.data-transfer.effect-allowed == \all
e.data-transfer.drop-effect = \copy
else
e.data-transfer.drop-effect = \move
else
# 自分自身にはドロップさせない
e.data-transfer.drop-effect = \none
return false
// 自分自身がドラッグされていない場合
if (!this.isDragging) {
// ドラッグされてきたものがファイルだったら
if (e.dataTransfer.effectAllowed === 'all') {
e.dataTransfer.dropEffect = 'copy';
} else {
e.dataTransfer.dropEffect = 'move';
}
} else {
// 自分自身にはドロップさせない
e.dataTransfer.dropEffect = 'none';
}
return false;
};
@ondragenter = ~>
if !@is-dragging
@draghover = true
this.ondragenter = () => {
if (!this.isDragging) this.draghover = true;
};
@ondragleave = ~>
@draghover = false
this.ondragleave = () => {
this.draghover = false;
};
@ondrop = (e) ~>
e.stop-propagation!
@draghover = false
this.ondrop = e => {
e.stopPropagation();
this.draghover = false;
# ファイルだったら
if e.data-transfer.files.length > 0
Array.prototype.for-each.call e.data-transfer.files, (file) ~>
@browser.upload file, @folder
return false
// ファイルだったら
if (e.dataTransfer.files.length > 0) {
e.dataTransfer.files.forEach(file => {
this.browser.upload(file, this.folder);
});
return false;
};
# データ取得
data = e.data-transfer.get-data 'text'
if !data?
return false
// データ取得
const data = e.dataTransfer.getData('text');
if (data == null) return false;
# パース
obj = JSON.parse data
// パース
// TODO: Validate JSON
const obj = JSON.parse(data);
# (ドライブの)ファイルだったら
if obj.type == \file
file = obj.id
@browser.remove-file file
@api \drive/files/update do
file_id: file
folder_id: @folder.id
.then ~>
# something
.catch (err, text-status) ~>
console.error err
// (ドライブの)ファイルだったら
if (obj.type == 'file') {
const file = obj.id;
this.browser.removeFile(file);
this.api('drive/files/update', {
file_id: file,
folder_id: this.folder.id
});
// (ドライブの)フォルダーだったら
} else if (obj.type == 'folder') {
const folder = obj.id;
// 移動先が自分自身ならreject
if (folder == this.folder.id) return false;
this.browser.removeFolder(folder);
this.api('drive/folders/update', {
folder_id: folder,
parent_id: this.folder.id
}).then(() => {
// something
}).catch(err => {
switch (err) {
case 'detected-circular-definition':
this.dialog('<i class="fa fa-exclamation-triangle"></i>操作を完了できません',
'移動先のフォルダーは、移動するフォルダーのサブフォルダーです。', [{
text: 'OK'
}]);
break;
default:
alert('不明なエラー' + err);
}
});
}
# (ドライブの)フォルダーだったら
else if obj.type == \folder
folder = obj.id
# 移動先が自分自身ならreject
if folder == @folder.id
return false
@browser.remove-folder folder
@api \drive/folders/update do
folder_id: folder
parent_id: @folder.id
.then ~>
# something
.catch (err) ~>
if err == 'detected-circular-definition'
@dialog do
'<i class="fa fa-exclamation-triangle"></i>操作を完了できません'
'移動先のフォルダーは、移動するフォルダーのサブフォルダーです。'
[
text: \OK
]
return false;
};
return false
this.ondragstart = e => {
e.dataTransfer.effectAllowed = 'move';
e.dataTransfer.setData('text', JSON.stringify({
type: 'folder',
id: this.folder.id
}));
this.isDragging = true;
@ondragstart = (e) ~>
e.data-transfer.effect-allowed = \move
e.data-transfer.set-data 'text' JSON.stringify do
type: \folder
id: @folder.id
@is-dragging = true
// 親ブラウザに対して、ドラッグが開始されたフラグを立てる
// (=あなたの子供が、ドラッグを開始しましたよ)
this.browser.isDragSource = true;
};
# 親ブラウザに対して、ドラッグが開始されたフラグを立てる
# (=あなたの子供が、ドラッグを開始しましたよ)
@browser.is-drag-source = true
this.ondragend = e => {
this.isDragging = false;
this.browser.isDragSource = false;
};
@ondragend = (e) ~>
@is-dragging = false
@browser.is-drag-source = false
this.oncontextmenu = e => {
e.preventDefault();
e.stopImmediatePropagation();
@oncontextmenu = (e) ~>
e.prevent-default!
e.stop-immediate-propagation!
this.update({
isContextmenuShowing: true
});
const ctx = riot.mount(document.body.appendChild(document.createElement('mk-drive-browser-folder-contextmenu')), {
browser: this.browser,
folder: this.folder
})[0];
ctx.open({
x: e.pageX - window.pageXOffset,
y: e.pageY - window.pageYOffset
});
ctx.on('closed', () => {
this.update({
isContextmenuShowing: false
});
});
@is-contextmenu-showing = true
@update!
ctx = document.body.append-child document.create-element \mk-drive-browser-folder-contextmenu
ctx = riot.mount ctx, do
browser: @browser
folder: @folder
ctx = ctx.0
ctx.open do
x: e.page-x - window.page-x-offset
y: e.page-y - window.page-y-offset
ctx.on \closed ~>
@is-contextmenu-showing = false
@update!
return false
return false;
};
</script>
</mk-drive-browser-folder>

View File

@ -6,92 +6,93 @@
</style>
<script>
@mixin \api
this.mixin('api');
# Riotのバグでnullを渡しても""になる
# https://github.com/riot/riot/issues/2080
#@folder = @opts.folder
@folder = if @opts.folder? and @opts.folder != '' then @opts.folder else null
@browser = @parent
// Riotのバグでnullを渡しても""になる
// https://github.com/riot/riot/issues/2080
//this.folder = this.opts.folder
this.folder = this.opts.folder && this.opts.folder != '' ? this.opts.folder : null;
this.browser = this.parent;
@hover = false
this.hover = false;
@onclick = ~>
@browser.move @folder
this.onclick = () => {
this.browser.move(this.folder);
};
@onmouseover = ~>
@hover = true
this.onmouseover = () => {
this.hover = true
};
@onmouseout = ~>
@hover = false
this.onmouseout = () => {
this.hover = false
};
@ondragover = (e) ~>
e.prevent-default!
e.stop-propagation!
this.ondragover = e => {
e.preventDefault();
e.stopPropagation();
# このフォルダがルートかつカレントディレクトリならドロップ禁止
if @folder == null and @browser.folder == null
e.data-transfer.drop-effect = \none
# ドラッグされてきたものがファイルだったら
else if e.data-transfer.effect-allowed == \all
e.data-transfer.drop-effect = \copy
else
e.data-transfer.drop-effect = \move
return false
// このフォルダがルートかつカレントディレクトリならドロップ禁止
if (this.folder == null && this.browser.folder == null) {
e.dataTransfer.dropEffect = 'none';
// ドラッグされてきたものがファイルだったら
} else if (e.dataTransfer.effectAllowed == 'all') {
e.dataTransfer.dropEffect = 'copy';
} else {
e.dataTransfer.dropEffect = 'move';
}
return false;
};
@ondragenter = ~>
if @folder != null or @browser.folder != null
@draghover = true
this.ondragenter = () => {
if (this.folder || this.browser.folder) this.draghover = true;
};
@ondragleave = ~>
if @folder != null or @browser.folder != null
@draghover = false
this.ondragleave = () => {
if (this.folder || this.browser.folder) this.draghover = false;
};
@ondrop = (e) ~>
e.stop-propagation!
@draghover = false
this.ondrop = e => {
e.stopPropagation();
this.draghover = false;
# ファイルだったら
if e.data-transfer.files.length > 0
Array.prototype.for-each.call e.data-transfer.files, (file) ~>
@browser.upload file, @folder
return false
// ファイルだったら
if (e.dataTransfer.files.length > 0) {
e.dataTransfer.files.forEach(file => {
this.browser.upload(file, this.folder);
});
return false;
};
# データ取得
data = e.data-transfer.get-data 'text'
if !data?
return false
// データ取得
const data = e.dataTransfer.getData('text');
if (data == null) return false;
# パース
obj = JSON.parse data
// パース
// TODO: Validate JSON
const obj = JSON.parse(data);
# (ドライブの)ファイルだったら
if obj.type == \file
file = obj.id
@browser.remove-file file
@api \drive/files/update do
file_id: file
folder_id: if @folder? then @folder.id else null
.then ~>
# something
.catch (err, text-status) ~>
console.error err
// (ドライブの)ファイルだったら
if (obj.type == 'file') {
const file = obj.id;
this.browser.removeFile(file);
this.api('drive/files/update', {
file_id: file,
folder_id: this.folder ? this.folder.id : null
});
// (ドライブの)フォルダーだったら
} else if (obj.type == 'folder') {
const folder = obj.id;
// 移動先が自分自身ならreject
if (this.folder && folder == this.folder.id) return false;
this.browser.removeFolder(folder);
this.api('drive/folders/update', {
folder_id: folder,
parent_id: this.folder ? this.folder.id : null
});
}
# (ドライブの)フォルダーだったら
else if obj.type == \folder
folder = obj.id
# 移動先が自分自身ならreject
if @folder? and folder == @folder.id
return false
@browser.remove-folder folder
@api \drive/folders/update do
folder_id: folder
parent_id: if @folder? then @folder.id else null
.then ~>
# something
.catch (err, text-status) ~>
console.error err
return false
return false;
};
</script>
</mk-drive-browser-nav-folder>

View File

@ -33,9 +33,5 @@
40%
transform scale(1)
</style>
</mk-ellipsis-icon>

View File

@ -67,58 +67,74 @@
</style>
<script>
@mixin \api
@mixin \is-promise
@mixin \stream
this.mixin('api');
this.mixin('is-promise');
this.mixin('stream');
@user = null
@user-promise = if @is-promise @opts.user then @opts.user else Promise.resolve @opts.user
@init = true
@wait = false
this.user = null;
this.userPromise = this.isPromise(this.opts.user)
? this.opts.user
: Promise.resolve(this.opts.user);
this.init = true;
this.wait = false;
@on \mount ~>
@user-promise.then (user) ~>
@user = user
@init = false
@update!
@stream.on \follow @on-stream-follow
@stream.on \unfollow @on-stream-unfollow
this.on('mount', () => {
this.userPromise.then(user => {
this.update({
init: false,
user: user
});
this.stream.on('follow', this.onStreamFollow);
this.stream.on('unfollow', this.onStreamUnfollow);
});
});
@on \unmount ~>
@stream.off \follow @on-stream-follow
@stream.off \unfollow @on-stream-unfollow
this.on('unmount', () => {
this.stream.off('follow', this.onStreamFollow);
this.stream.off('unfollow', this.onStreamUnfollow);
});
@on-stream-follow = (user) ~>
if user.id == @user.id
@user = user
@update!
this.onStreamFollow = user => {
if (user.id == this.user.id) {
this.update({
user: user
});
}
};
@on-stream-unfollow = (user) ~>
if user.id == @user.id
@user = user
@update!
this.onStreamUnfollow = user => {
if (user.id == this.user.id) {
this.update({
user: user
});
}
};
@onclick = ~>
@wait = true
if @user.is_following
@api \following/delete do
user_id: @user.id
.then ~>
@user.is_following = false
.catch (err) ->
console.error err
.then ~>
@wait = false
@update!
else
@api \following/create do
user_id: @user.id
.then ~>
@user.is_following = true
.catch (err) ->
console.error err
.then ~>
@wait = false
@update!
this.onclick = () => {
this.wait = true;
if (this.user.is_following) {
this.api('following/delete', {
user_id: this.user.id
}).then(() => {
this.user.is_following = false;
}).catch(err => {
console.error(err);
}).then(() => {
this.wait = false;
this.update();
});
} else {
this.api('following/create', {
user_id: this.user.id
}).then(() => {
this.user.is_following = true;
}).catch(err => {
console.error(err);
}).then(() => {
this.wait = false;
this.update();
});
}
};
</script>
</mk-follow-button>

View File

@ -1,6 +1,6 @@
<mk-following-setuper>
<p class="title">気になるユーザーをフォロー:</p>
<div class="users" if={ !loading && users.length > 0 }>
<div class="users" if={ !fetching && users.length > 0 }>
<div class="user" each={ users }><a class="avatar-anchor" href={ CONFIG.url + '/' + username }><img class="avatar" src={ avatar_url + '?thumbnail&size=42' } alt="" data-user-preview={ id }/></a>
<div class="body"><a class="name" href={ CONFIG.url + '/' + username } target="_blank" data-user-preview={ id }>{ name }</a>
<p class="username">@{ username }</p>
@ -8,8 +8,8 @@
<mk-follow-button user={ this }></mk-follow-button>
</div>
</div>
<p class="empty" if={ !loading && users.length == 0 }>おすすめのユーザーは見つかりませんでした。</p>
<p class="loading" if={ loading }><i class="fa fa-spinner fa-pulse fa-fw"></i>読み込んでいます
<p class="empty" if={ !fetching && users.length == 0 }>おすすめのユーザーは見つかりませんでした。</p>
<p class="fetching" if={ fetching }><i class="fa fa-spinner fa-pulse fa-fw"></i>読み込んでいます
<mk-ellipsis></mk-ellipsis>
</p><a class="refresh" onclick={ refresh }>もっと見る</a>
<button class="close" onclick={ close } title="閉じる"><i class="fa fa-times"></i></button>
@ -81,7 +81,7 @@
text-align center
color #aaa
> .loading
> .fetching
margin 0
padding 16px
text-align center
@ -123,41 +123,49 @@
</style>
<script>
@mixin \api
@mixin \user-preview
this.mixin('api');
this.mixin('user-preview');
@users = null
@loading = true
this.users = null;
this.fetching = true;
@limit = 6users
@page = 0
this.limit = 6;
this.page = 0;
@on \mount ~>
@load!
this.on('mount', () => {
this.fetch();
});
@load = ~>
@loading = true
@users = null
@update!
this.fetch = () => {
this.update({
fetching: true,
users: null
});
@api \users/recommendation do
limit: @limit
offset: @limit * @page
.then (users) ~>
@loading = false
@users = users
@update!
.catch (err, text-status) ->
console.error err
this.api('users/recommendation', {
limit: this.limit,
offset: this.limit * this.page
}).then(users => {
this.fetching = false
this.users = users
this.update({
fetching: false,
users: users
});
});
};
@refresh = ~>
if @users.length < @limit
@page = 0
else
@page++
@load!
this.refresh = () => {
if (this.users.length < this.limit) {
this.page = 0;
} else {
this.page++;
}
this.fetch();
};
@close = ~>
@unmount!
this.close = () => {
this.unmount();
};
</script>
</mk-following-setuper>

View File

@ -1,14 +0,0 @@
<mk-go-top>
<button class="hidden" title="一番上へ"><i class="fa fa-angle-up"></i></button>
<script>
window.add-event-listener \load @on-scroll
window.add-event-listener \scroll @on-scroll
window.add-event-listener \resize @on-scroll
@on-scroll = ~>
if $ window .scroll-top! > 500px
@remove-class \hidden
else
@add-class \hidden
</script>
</mk-go-top>

View File

@ -106,43 +106,45 @@
</style>
<script>
@draw = ~>
now = new Date!
nd = now.get-date!
nm = now.get-month!
ny = now.get-full-year!
this.draw = () => {
const now = new Date();
const nd = now.getDate();
const nm = now.getMonth();
const ny = now.getFullYear();
@year = ny
@month = nm + 1
@day = nd
@week-day = [\日 \月 \火 \水 \木 \金 \土][now.get-day!]
this.year = ny;
this.month = nm + 1;
this.day = nd;
this.weekDay = ['日', '月', '火', '水', '木', '金', '土'][now.getDay()];
@day-numer = (now - (new Date ny, nm, nd))
@day-denom = 1000ms * 60s * 60m * 24h
@month-numer = (now - (new Date ny, nm, 1))
@month-denom = (new Date ny, nm + 1, 1) - (new Date ny, nm, 1)
@year-numer = (now - (new Date ny, 0, 1))
@year-denom = (new Date ny + 1, 0, 1) - (new Date ny, 0, 1)
this.dayNumer = now - new Date(ny, nm, nd);
this.dayDenom = 1000/*ms*/ * 60/*s*/ * 60/*m*/ * 24/*h*/;
this.monthNumer = now - new Date(ny, nm, 1);
this.monthDenom = new Date(ny, nm + 1, 1) - new Date(ny, nm, 1);
this.yearNumer = now - new Date(ny, 0, 1);
this.yearDenom = new Date(ny + 1, 0, 1) - new Date(ny, 0, 1);
@day-p = @day-numer / @day-denom * 100
@month-p = @month-numer / @month-denom * 100
@year-p = @year-numer / @year-denom * 100
this.dayP = this.dayNumer / this.dayDenom * 100;
this.monthP = this.monthNumer / this.monthDenom * 100;
this.yearP = this.yearNumer / this.yearDenom * 100;
@is-holiday =
(now.get-day! == 0 or now.get-day! == 6)
this.isHoliday = now.getDay() == 0 || now.getDay() == 6;
@special =
| nm == 0 and nd == 1 => \on-new-years-day
| _ => false
this.special =
nm == 0 && nd == 1 ? 'on-new-years-day' :
false;
@update!
this.update();
};
@draw!
this.draw();
@on \mount ~>
@clock = set-interval @draw, 1000ms
this.on('mount', () => {
this.clock = setInterval(this.draw, 1000);
});
@on \unmount ~>
clear-interval @clock
this.on('unmount', () => {
clearInterval(this.clock);
});
</script>
</mk-calendar-home-widget>

View File

@ -32,5 +32,5 @@
color #999
</style>
<script>@mixin \user-preview</script>
<script>this.mixin('user-preview');</script>
</mk-donation-home-widget>

View File

@ -46,67 +46,73 @@
</style>
<script>
@mixin \i
@mixin \api
this.mixin('i');
this.mixin('api');
@is-loading = true
@is-empty = false
@more-loading = false
@mode = \all
this.isLoading = true;
this.isEmpty = false;
this.moreLoading = false;
this.mode = 'all';
@on \mount ~>
document.add-event-listener \keydown @on-document-keydown
window.add-event-listener \scroll @on-scroll
this.on('mount', () => {
document.addEventListener('keydown', this.onDocumentKeydown);
window.addEventListener('scroll', this.onScroll);
@fetch ~>
@trigger \loaded
this.fetch(() => this.trigger('loaded'));
});
@on \unmount ~>
document.remove-event-listener \keydown @on-document-keydown
window.remove-event-listener \scroll @on-scroll
this.on('unmount', () => {
document.removeEventListener('keydown', this.onDocumentKeydown);
window.removeEventListener('scroll', this.onScroll);
});
@on-document-keydown = (e) ~>
tag = e.target.tag-name.to-lower-case!
if tag != \input and tag != \textarea
if e.which == 84 # t
@refs.timeline.focus!
this.onDocumentKeydown = e => {
if (e.target.tagName != 'INPUT' && tag != 'TEXTAREA') {
if (e.which == 84) { // t
this.refs.timeline.focus();
}
}
};
@fetch = (cb) ~>
@api \posts/mentions do
following: @mode == \following
.then (posts) ~>
@is-loading = false
@is-empty = posts.length == 0
@update!
@refs.timeline.set-posts posts
if cb? then cb!
.catch (err) ~>
console.error err
if cb? then cb!
this.fetch = cb => {
this.api('posts/mentions', {
following: this.mode == 'following'
}).then(posts => {
this.update({
isLoading: false,
isEmpty: posts.length == 0
});
this.refs.timeline.setPosts(posts);
if (cb) cb();
});
};
@more = ~>
if @more-loading or @is-loading or @refs.timeline.posts.length == 0
return
@more-loading = true
@update!
@api \posts/mentions do
following: @mode == \following
max_id: @refs.timeline.tail!.id
.then (posts) ~>
@more-loading = false
@update!
@refs.timeline.prepend-posts posts
.catch (err) ~>
console.error err
this.more = () => {
if (this.moreLoading || this.isLoading || this.refs.timeline.posts.length == 0) return;
this.update({
moreLoading: true
});
this.api('posts/mentions', {
following: this.mode == 'following',
max_id: this.refs.timeline.tail().id
}).then(posts => {
this.update({
moreLoading: false
});
this.refs.timeline.prependPosts(posts);
});
};
@on-scroll = ~>
current = window.scroll-y + window.inner-height
if current > document.body.offset-height - 8
@more!
this.onScroll = () => {
const current = window.scrollY + window.innerHeight;
if (current > document.body.offsetHeight - 8) this.more();
};
@set-mode = (mode) ~>
@update do
this.setMode = mode => {
this.update({
mode: mode
@fetch!
});
this.fetch();
};
</script>
</mk-mentions-home-widget>

View File

@ -13,9 +13,5 @@
i
color #ccc
</style>
</mk-nav-home-widget>

View File

@ -43,8 +43,9 @@
</style>
<script>
@settings = ~>
w = riot.mount document.body.append-child document.create-element \mk-settings-window .0
w.switch \notification
this.settings = () => {
const w = riot.mount(document.body.appendChild(document.createElement('mk-settings-window')))[0];
w.switch('notification');
};
</script>
</mk-notifications-home-widget>

View File

@ -57,31 +57,36 @@
</style>
<script>
@mixin \api
@mixin \stream
this.mixin('api');
this.mixin('stream');
@images = []
@initializing = true
this.images = [];
this.initializing = true;
@on \mount ~>
@stream.on \drive_file_created @on-stream-drive-file-created
this.on('mount', () => {
this.stream.on('drive_file_created', this.onStreamDriveFileCreated);
@api \drive/stream do
type: 'image/*'
limit: 9images
.then (images) ~>
@initializing = false
@images = images
@update!
this.api('drive/stream', {
type: 'image/*',
limit: 9
}).then(images => {
this.update({
initializing: false,
images: images
});
});
});
@on \unmount ~>
@stream.off \drive_file_created @on-stream-drive-file-created
this.on('unmount', () => {
this.stream.off('drive_file_created', this.onStreamDriveFileCreated);
});
@on-stream-drive-file-created = (file) ~>
if /^image\/.+$/.test file.type
@images.unshift file
if @images.length > 9
@images.pop!
@update!
this.onStreamDriveFileCreated = file => {
if (/^image\/.+$/.test(file.type)) {
this.images.unshift(file);
if (this.images.length > 9) this.images.pop();
this.update();
}
};
</script>
</mk-photo-stream-home-widget>

View File

@ -41,15 +41,17 @@
</style>
<script>
@mixin \i
@mixin \user-preview
@mixin \update-avatar
@mixin \update-banner
this.mixin('i');
this.mixin('user-preview');
this.mixin('update-avatar');
this.mixin('update-banner');
@set-avatar = ~>
@update-avatar @I
this.setAvatar = () => {
this.updateAvatar(this.I);
};
@set-banner = ~>
@update-banner @I
this.setBanner = () => {
this.updateBanner(this.I);
};
</script>
</mk-profile-home-widget>

View File

@ -64,31 +64,35 @@
</style>
<script>
@mixin \api
@mixin \NotImplementedException
this.mixin('api');
this.mixin('NotImplementedException');
@url = 'http://news.yahoo.co.jp/pickup/rss.xml'
@items = []
@initializing = true
this.url = 'http://news.yahoo.co.jp/pickup/rss.xml';
this.items = [];
this.initializing = true;
@on \mount ~>
@fetch!
@clock = set-interval @fetch, 60000ms
this.on('mount', () => {
this.fetch();
this.clock = setInterval(this.fetch, 60000);
});
@on \unmount ~>
clear-interval @clock
this.on('unmount', () => {
clearInterval(this.clock);
});
@fetch = ~>
@api CONFIG.url + '/api:rss' do
url: @url
.then (feed) ~>
@items = feed.rss.channel.item
@initializing = false
@update!
.catch (err) ->
console.error err
this.fetch = () => {
this.api(CONFIG.url + '/api:rss', {
url: this.url
}).then(feed => {
this.update({
initializing: false,
items: feed.rss.channel.item
});
});
};
@settings = ~>
@NotImplementedException!
this.settings = () => {
this.NotImplementedException();
};
</script>
</mk-rss-reader-home-widget>

View File

@ -32,80 +32,87 @@
</style>
<script>
@mixin \i
@mixin \api
@mixin \stream
this.mixin('i');
this.mixin('api');
this.mixin('stream');
@is-loading = true
@is-empty = false
@more-loading = false
@no-following = @I.following_count == 0
this.isLoading = true;
this.isEmpty = false;
this.moreLoading = false;
this.noFollowing = this.I.following_count == 0;
@on \mount ~>
@stream.on \post @on-stream-post
@stream.on \follow @on-stream-follow
@stream.on \unfollow @on-stream-unfollow
this.on('mount', () => {
this.stream.on('post', this.onStreamPost);
this.stream.on('follow', this.onStreamFollow);
this.stream.on('unfollow', this.onStreamUnfollow);
document.add-event-listener \keydown @on-document-keydown
window.add-event-listener \scroll @on-scroll
document.addEventListener('keydown', this.onDocumentKeydown);
window.addEventListener('scroll', this.onScroll);
@load ~>
@trigger \loaded
this.load(() => this.trigger('loaded'));
});
@on \unmount ~>
@stream.off \post @on-stream-post
@stream.off \follow @on-stream-follow
@stream.off \unfollow @on-stream-unfollow
this.on('unmount', () => {
this.stream.off('post', this.onStreamPost);
this.stream.off('follow', this.onStreamFollow);
this.stream.off('unfollow', this.onStreamUnfollow);
document.remove-event-listener \keydown @on-document-keydown
window.remove-event-listener \scroll @on-scroll
document.removeEventListener('keydown', this.onDocumentKeydown);
window.removeEventListener('scroll', this.onScroll);
});
@on-document-keydown = (e) ~>
tag = e.target.tag-name.to-lower-case!
if tag != \input and tag != \textarea
if e.which == 84 # t
@refs.timeline.focus!
this.onDocumentKeydown = e => {
if (e.target.tagName != 'INPUT' && e.target.tagName != 'TEXTAREA') {
if (e.which == 84) { // t
this.refs.timeline.focus();
}
}
};
@load = (cb) ~>
@api \posts/timeline
.then (posts) ~>
@is-loading = false
@is-empty = posts.length == 0
@update!
@refs.timeline.set-posts posts
if cb? then cb!
.catch (err) ~>
console.error err
if cb? then cb!
this.load = (cb) => {
this.api('posts/timeline').then(posts => {
this.update({
isLoading: false,
isEmpty: posts.length == 0
});
this.refs.timeline.setPosts(posts);
if (cb) cb();
});
};
@more = ~>
if @more-loading or @is-loading or @refs.timeline.posts.length == 0
return
@more-loading = true
@update!
@api \posts/timeline do
max_id: @refs.timeline.tail!.id
.then (posts) ~>
@more-loading = false
@update!
@refs.timeline.prepend-posts posts
.catch (err) ~>
console.error err
this.more = () => {
if (this.moreLoading || this.isLoading || this.refs.timeline.posts.length == 0) return;
this.update({
moreLoading: true
});
this.api('posts/timeline', {
max_id: this.refs.timeline.tail().id
}).then(posts => {
this.update({
moreLoading: false
});
this.refs.timeline.prependPosts(posts);
});
};
@on-stream-post = (post) ~>
@is-empty = false
@update!
@refs.timeline.add-post post
this.onStreamPost = post => {
this.update({
isEmpty: false
});
this.refs.timeline.addPost(post);
};
@on-stream-follow = ~>
@load!
this.onStreamFollow = () => {
this.load();
};
@on-stream-unfollow = ~>
@load!
this.onStreamUnfollow = () => {
this.load();
};
@on-scroll = ~>
current = window.scroll-y + window.inner-height
if current > document.body.offset-height - 8
@more!
this.onScroll = () => {
const current = window.scrollY + window.innerHeight;
if (current > document.body.offsetHeight - 8) this.more();
};
</script>
</mk-timeline-home-widget>

View File

@ -29,43 +29,46 @@
</style>
<script>
@tips = [
'<kbd>t</kbd>でタイムラインにフォーカスできます'
'<kbd>p</kbd>または<kbd>n</kbd>で投稿フォームを開きます'
'投稿フォームにはファイルをドラッグ&ドロップできます'
'投稿フォームにクリップボードにある画像データをペーストできます'
'ドライブにファイルをドラッグ&ドロップしてアップロードできます'
'ドライブでファイルをドラッグしてフォルダ移動できます'
'ドライブでフォルダをドラッグしてフォルダ移動できます'
'ホームをカスタマイズできます(準備中)'
this.tips = [
'<kbd>t</kbd>でタイムラインにフォーカスできます',
'<kbd>p</kbd>または<kbd>n</kbd>で投稿フォームを開きます',
'投稿フォームにはファイルをドラッグ&ドロップできます',
'投稿フォームにクリップボードにある画像データをペーストできます',
'ドライブにファイルをドラッグ&ドロップしてアップロードできます',
'ドライブでファイルをドラッグしてフォルダ移動できます',
'ドライブでフォルダをドラッグしてフォルダ移動できます',
'ホームをカスタマイズできます(準備中)',
'MisskeyはMIT Licenseです'
]
@on \mount ~>
@set!
@clock = set-interval @change, 20000ms
this.on('mount', () => {
this.set();
this.clock = setInterval(this.change, 20000);
});
@on \unmount ~>
clear-interval @clock
this.on('unmount', () => {
clearInterval(this.clock);
});
@set = ~>
@refs.text.innerHTML = @tips[Math.floor Math.random! * @tips.length]
@update!
this.set = () => {
this.refs.text.innerHTML = this.tips[Math.floor(Math.random() * this.tips.length)];
};
@change = ~>
Velocity @refs.tip, {
this.change = () => {
Velocity(this.refs.tip, {
opacity: 0
} {
duration: 500ms
easing: \linear
complete: @set
}
}, {
duration: 500,
easing: 'linear',
complete: this.set
});
Velocity @refs.tip, {
Velocity(this.refs.tip, {
opacity: 1
} {
duration: 500ms
easing: \linear
}
}, {
duration: 500,
easing: 'linear'
});
};
</script>
</mk-tips-home-widget>

View File

@ -109,44 +109,42 @@
</style>
<script>
@mixin \api
@mixin \user-preview
this.mixin('api');
this.mixin('user-preview');
@users = null
@loading = true
this.users = null;
this.loading = true;
@limit = 3users
@page = 0
this.limit = 3;
this.page = 0;
@on \mount ~>
@fetch!
@clock = set-interval ~>
if @users.length < @limit
@fetch true
, 60000ms
this.on('mount', () => {
this.fetch();
});
@on \unmount ~>
clear-interval @clock
this.fetch = () => {
this.update({
loading: true,
users: null
});
this.api('users/recommendation', {
limit: this.limit,
offset: this.limit * this.page
}).then(users => {
this.update({
loading: false,
users: users
});
});
};
@fetch = (quiet = false) ~>
@loading = true
@users = null
if not quiet then @update!
@api \users/recommendation do
limit: @limit
offset: @limit * @page
.then (users) ~>
@loading = false
@users = users
@update!
.catch (err, text-status) ->
console.error err
@refresh = ~>
if @users.length < @limit
@page = 0
else
@page++
@fetch!
this.refresh = () => {
if (this.users.length < this.limit) {
this.page = 0;
} else {
this.page++;
}
this.fetch();
};
</script>
</mk-user-recommendation-home-widget>

View File

@ -58,33 +58,40 @@
</style>
<script>
@mixin \i
@mode = @opts.mode || \timeline
this.mixin('i');
# https://github.com/riot/riot/issues/2080
if @mode == '' then @mode = \timeline
this.mode = this.opts.mode || 'timeline';
// https://github.com/riot/riot/issues/2080
if (this.mode == '') this.mode = 'timeline';
@home = []
this.home = [];
@on \mount ~>
@refs.tl.on \loaded ~>
@trigger \loaded
this.on('mount', () => {
this.refs.tl.on('loaded', () => {
this.trigger('loaded');
});
@I.data.home.for-each (widget) ~>
try
el = document.create-element \mk- + widget.name + \-home-widget
switch widget.place
| \left => @refs.left.append-child el
| \right => @refs.right.append-child el
@home.push (riot.mount el, do
id: widget.id
this.I.data.home.forEach(widget => {
try {
const el = document.createElement(`mk-${widget.name}-home-widget`);
switch (widget.place) {
case 'left': this.refs.left.appendChild(el); break;
case 'right': this.refs.right.appendChild(el); break;
}
this.home.push(riot.mount(el, {
id: widget.id,
data: widget.data
.0)
catch e
# noop
})[0]);
} catch (e) {
// noop
}
});
});
@on \unmount ~>
@home.for-each (widget) ~>
widget.unmount!
this.on('unmount', () => {
this.home.forEach(widget => {
widget.unmount();
});
});
</script>
</mk-home>

View File

@ -35,41 +35,26 @@
</style>
<script>
@image = @opts.image
this.image = this.opts.image;
@on \mount ~>
Velocity @root, {
this.on('mount', () => {
Velocity(this.root, {
opacity: 1
} {
duration: 100ms
easing: \linear
}
}, {
duration: 100,
easing: 'linear'
});
});
#Velocity @img, {
# scale: 1
# opacity: 1
#} {
# duration: 200ms
# easing: \ease-out
#}
@close = ~>
Velocity @root, {
this.close = () => {
Velocity(this.root, {
opacity: 0
} {
duration: 100ms
easing: \linear
complete: ~> @unmount!
}
}, {
duration: 100,
easing: 'linear',
complete: () => this.unmount()
});
};
#Velocity @img, {
# scale: 0.9
# opacity: 0
#} {
# duration: 200ms
# easing: \ease-in
# complete: ~>
# @unmount!
#}
</script>
</mk-image-dialog>

View File

@ -26,20 +26,22 @@
</style>
<script>
@images = @opts.images
@image = @images.0
this.images = this.opts.images;
this.image = this.images[0];
@mousemove = (e) ~>
rect = @refs.view.get-bounding-client-rect!
mouse-x = e.client-x - rect.left
mouse-y = e.client-y - rect.top
xp = mouse-x / @refs.view.offset-width * 100
yp = mouse-y / @refs.view.offset-height * 100
@refs.view.style.background-position = xp + '% ' + yp + '%'
this.mousemove = e => {
const rect = this.refs.view.getBoundingClientRect();
const mouseX = e.clientX - rect.left;
const mouseY = e.clientY - rect.top;
const xp = mouseX / this.refs.view.offsetWidth * 100;
const yp = mouseY / this.refs.view.offsetHeight * 100;
this.refs.view.style.backgroundPosition = xp + '% ' + yp + '%';
};
@click = ~>
dialog = document.body.append-child document.create-element \mk-image-dialog
riot.mount dialog, do
image: @image
this.click = () => {
riot.mount(document.body.appendChild(document.createElement('mk-image-dialog')), {
image: this.image
});
};
</script>
</mk-images-viewer>

View File

@ -16,7 +16,6 @@ require('./crop-window.tag');
require('./settings.tag');
require('./settings-window.tag');
require('./analog-clock.tag');
require('./go-top.tag');
require('./ui-header.tag');
require('./ui-header-account.tag');
require('./ui-header-notifications.tag');
@ -42,7 +41,6 @@ require('./home-widgets/notifications.tag');
require('./home-widgets/rss-reader.tag');
require('./home-widgets/photo-stream.tag');
require('./home-widgets/broadcast.tag');
require('./stream-indicator.tag');
require('./timeline.tag');
require('./messaging/window.tag');
require('./messaging/room-window.tag');

View File

@ -1,13 +1,17 @@
<mk-input-dialog>
<mk-window ref="window" is-modal={ true } width={ '500px' }><yield to="header"><i class="fa fa-i-cursor"></i>{ parent.title }</yield>
<yield to="content">
<div class="body">
<input ref="text" oninput={ parent.update } onkeydown={ parent.onKeydown } placeholder={ parent.placeholder }/>
</div>
<div class="action">
<button class="cancel" onclick={ parent.cancel }>キャンセル</button>
<button class="ok" disabled={ !parent.allowEmpty && refs.text.value.length == 0 } onclick={ parent.ok }>決定</button>
</div></yield>
<mk-window ref="window" is-modal={ true } width={ '500px' }>
<yield to="header">
<i class="fa fa-i-cursor"></i>{ parent.title }
</yield>
<yield to="content">
<div class="body">
<input ref="text" oninput={ parent.update } onkeydown={ parent.onKeydown } placeholder={ parent.placeholder }/>
</div>
<div class="action">
<button class="cancel" onclick={ parent.cancel }>キャンセル</button>
<button class="ok" disabled={ !parent.allowEmpty && refs.text.value.length == 0 } onclick={ parent.ok }>決定</button>
</div>
</yield>
</mk-window>
<style>
:scope
@ -116,42 +120,48 @@
</style>
<script>
@done = false
this.done = false;
@title = @opts.title
@placeholder = @opts.placeholder
@default = @opts.default
@allow-empty = if @opts.allow-empty? then @opts.allow-empty else true
this.title = this.opts.title;
this.placeholder = this.opts.placeholder;
this.default = this.opts.default;
this.allowEmpty = this.opts.allowEmpty != null ? this.opts.allowEmpty : true;
@on \mount ~>
@text = @refs.window.refs.text
if @default?
@text.value = @default
@text.focus!
this.on('mount', () => {
this.text = this.refs.window.refs.text;
if (this.default) this.text.value = this.default;
this.text.focus();
@refs.window.on \closing ~>
if @done
@opts.on-ok @text.value
else
if @opts.on-cancel?
@opts.on-cancel!
this.refs.window.on('closing', () => {
if (this.done) {
this.opts.onOk(this.text.value);
} else {
if (this.opts.onCancel) this.opts.onCancel();
}
});
@refs.window.on \closed ~>
@unmount!
this.refs.window.on('closed', () => {
this.unmount();
});
});
@cancel = ~>
@done = false
@refs.window.close!
this.cancel = () => {
this.done = false;
this.refs.window.close();
};
@ok = ~>
if not @allow-empty and @text.value == '' then return
@done = true
@refs.window.close!
this.ok = () => {
if (!this.allowEmpty && this.text.value == '') return;
this.done = true;
this.refs.window.close();
};
@on-keydown = (e) ~>
if e.which == 13 # Enter
e.prevent-default!
e.stop-propagation!
@ok!
this.onKeydown = e => {
if (e.which == 13) { // Enter
e.preventDefault();
e.stopPropagation();
this.ok();
}
};
</script>
</mk-input-dialog>

View File

@ -93,5 +93,5 @@
right 16px
</style>
<script>@user = @opts.user</script>
<script>this.user = this.opts.user</script>
</mk-list-user>

View File

@ -19,10 +19,12 @@
</style>
<script>
@user = @opts.user
this.user = this.opts.user;
@on \mount ~>
@refs.window.on \closed ~>
@unmount!
this.on('mount', () => {
this.refs.window.on('closed', () => {
this.unmount();
});
});
</script>
</mk-messaging-room-window>

View File

@ -19,13 +19,16 @@
</style>
<script>
@on \mount ~>
@refs.window.on \closed ~>
@unmount!
this.on('mount', () => {
this.refs.window.on('closed', () => {
this.unmount();
});
@refs.window.refs.index.on \navigate-user (user) ~>
w = document.body.append-child document.create-element \mk-messaging-room-window
riot.mount w, do
this.refs.window.refs.index.on('navigate-user', user => {
riot.mount(document.body.appendChild(document.createElement('mk-messaging-room-window')), {
user: user
});
});
});
</script>
</mk-messaging-window>

View File

@ -177,37 +177,41 @@
</style>
<script>
@mixin \api
@mixin \stream
@mixin \user-preview
@mixin \get-post-summary
this.mixin('api');
this.mixin('stream');
this.mixin('user-preview');
this.mixin('get-post-summary');
@notifications = []
@loading = true
this.notifications = [];
this.loading = true;
@on \mount ~>
@api \i/notifications
.then (notifications) ~>
@notifications = notifications
@loading = false
@update!
.catch (err, text-status) ->
console.error err
this.on('mount', () => {
this.api('i/notifications').then(notifications => {
this.update({
loading: false,
notifications: notifications
});
});
@stream.on \notification @on-notification
this.stream.on('notification', this.onNotification);
});
@on \unmount ~>
@stream.off \notification @on-notification
this.on('unmount', () => {
this.stream.off('notification', this.onNotification);
});
@on-notification = (notification) ~>
@notifications.unshift notification
@update!
this.onNotification = notification => {
this.notifications.unshift(notification);
this.update();
};
@on \update ~>
@notifications.for-each (notification) ~>
date = (new Date notification.created_at).get-date!
month = (new Date notification.created_at).get-month! + 1
notification._date = date
notification._datetext = month + '月 ' + date + '日'
this.on('update', () => {
this.notifications.forEach(notification => {
const date = new Date(notification.created_at).getDate();
const month = new Date(notification.created_at).getMonth() + 1;
notification._date = date;
notification._datetext = `${month}月 ${date}日`;
});
});
</script>
</mk-notifications>

View File

@ -63,18 +63,24 @@
</style>
<script>
@mode = \signin
this.mode = 'signin';
@signup = ~>
@mode = \signup
@update!
this.signup = () => {
this.update({
mode: 'signup'
});
};
@signin = ~>
@mode = \signin
@update!
this.signin = () => {
this.update({
mode: 'signin'
});
};
@introduction = ~>
@mode = \introduction
@update!
this.introduction = () => {
this.update({
mode: 'introduction'
});
};
</script>
</mk-entrance>

View File

@ -119,12 +119,16 @@
</style>
<script>
@on \mount ~>
@refs.signin.on \user (user) ~>
@update do
this.on('mount', () => {
this.refs.signin.on('user', user => {
this.update({
user: user
});
});
});
@introduction = ~>
@parent.introduction!
this.introduction = () => {
this.parent.introduction();
};
</script>
</mk-entrance-signin>

View File

@ -43,9 +43,5 @@
> i
padding 14px
</style>
</mk-entrance-signup>

View File

@ -5,43 +5,45 @@
<style>
:scope
display block
</style>
<script>
@mixin \i
@mixin \api
@mixin \ui-progress
@mixin \stream
@mixin \get-post-summary
this.mixin('i');
this.mixin('api');
this.mixin('ui-progress');
this.mixin('stream');
this.mixin('get-post-summary');
@unread-count = 0
this.unreadCount = 0;
@page = switch @opts.mode
| \timelie => \home
| \mentions => \mentions
| _ => \home
this.page = this.opts.mode || 'timeline';
@on \mount ~>
@refs.ui.refs.home.on \loaded ~>
@Progress.done!
this.on('mount', () => {
this.refs.ui.refs.home.on('loaded', () => {
this.Progress.done();
});
document.title = 'Misskey';
this.Progress.start();
this.stream.on('post', this.onStreamPost);
document.addEventListener('visibilitychange', this.windowOnVisibilitychange, false);
});
document.title = 'Misskey'
@Progress.start!
@stream.on \post @on-stream-post
document.add-event-listener \visibilitychange @window-on-visibilitychange, false
this.on('unmount', () => {
this.stream.off('post', this.onStreamPost);
document.removeEventListener('visibilitychange', this.windowOnVisibilitychange);
});
@on \unmount ~>
@stream.off \post @on-stream-post
document.remove-event-listener \visibilitychange @window-on-visibilitychange
this.onStreamPost = post => {
if (document.hidden && post.user_id != this.I.id) {
this.unreadCount++;
document.title = `(${this.unreadCount}) ${this.getPostSummary(post)}`;
}
};
@on-stream-post = (post) ~>
if document.hidden and post.user_id !== @I.id
@unread-count++
document.title = '(' + @unread-count + ') ' + @get-post-summary post
@window-on-visibilitychange = ~>
if !document.hidden
@unread-count = 0
document.title = 'Misskey'
this.windowOnVisibilitychange = () => {
if (!document.hidden) {
this.unreadCount = 0;
document.title = 'Misskey';
}
};
</script>
</mk-home-page>

View File

@ -1,54 +1,11 @@
<mk-not-found>
<mk-ui>
<main>
<h1>Not Found</h1><img src="/_/resources/rogge.jpg" alt=""/>
<div class="mask"></div>
<h1>Not Found</h1>
</main>
</mk-ui>
<style>
:scope
display block
main
display block
width 600px
margin 32px auto
> img
display block
width 600px
height 459px
pointer-events none
user-select none
border-radius 16px
box-shadow 0 0 16px rgba(0, 0, 0, 0.1)
> h1
display block
margin 0
padding 0
position absolute
top 260px
left 225px
transform rotate(-12deg)
z-index 2
color #444
font-size 24px
line-height 20px
> .mask
position absolute
top 262px
left 217px
width 126px
height 18px
transform rotate(-12deg)
background #D6D5DA
border-radius 2px 6px 7px 6px
</style>
</mk-not-found>

View File

@ -16,17 +16,20 @@
</style>
<script>
@mixin \ui-progress
this.mixin('ui-progress');
@post = @opts.post
this.post = this.opts.post;
@on \mount ~>
@Progress.start!
this.on('mount', () => {
this.Progress.start();
@refs.ui.refs.detail.on \post-fetched ~>
@Progress.set 0.5
this.refs.ui.refs.detail.on('post-fetched', () => {
this.Progress.set(0.5);
});
@refs.ui.refs.detail.on \loaded ~>
@Progress.done!
this.refs.ui.refs.detail.on('loaded', () => {
this.Progress.done();
});
});
</script>
</mk-post-page>

View File

@ -5,15 +5,16 @@
<style>
:scope
display block
</style>
<script>
@mixin \ui-progress
this.mixin('ui-progress');
@on \mount ~>
@Progress.start!
this.on('mount', () => {
this.Progress.start();
@refs.ui.refs.search.on \loaded ~>
@Progress.done!
this.refs.ui.refs.search.on('loaded', () => {
this.Progress.done();
});
});
</script>
</mk-search-page>

View File

@ -5,21 +5,23 @@
<style>
:scope
display block
</style>
<script>
@mixin \ui-progress
this.mixin('ui-progress');
@user = @opts.user
this.user = this.opts.user;
@on \mount ~>
@Progress.start!
this.on('mount', () => {
this.Progress.start();
@refs.ui.refs.user.on \user-fetched (user) ~>
@Progress.set 0.5
this.refs.ui.refs.user.on('user-fetched', user => {
this.Progress.set(0.5);
document.title = user.name + ' | Misskey'
});
@refs.ui.refs.user.on \loaded ~>
@Progress.done!
this.refs.ui.refs.user.on('loaded', () => {
this.Progress.done();
});
});
</script>
</mk-user-page>

View File

@ -103,38 +103,45 @@
</style>
<script>
@mixin \api
@mixin \text
@mixin \date-stringify
@mixin \user-preview
this.mixin('api');
this.mixin('text');
this.mixin('date-stringify');
this.mixin('user-preview');
@post = @opts.post
this.post = this.opts.post;
@url = CONFIG.url + '/' + @post.user.username + '/' + @post.id
this.url = CONFIG.url + '/' + this.post.user.username + '/' + this.post.id;
@title = @date-stringify @post.created_at
this.title = this.dateStringify(this.post.created_at);
@on \mount ~>
if @post.text?
tokens = @analyze @post.text
@refs.text.innerHTML = @compile tokens
this.on('mount', () => {
if (this.p.text) {
const tokens = this.analyze(this.p.text);
@refs.text.children.for-each (e) ~>
if e.tag-name == \MK-URL
riot.mount e
this.refs.text.innerHTML = this.refs.text.innerHTML.replace('<p class="dummy"></p>', this.compile(tokens));
@like = ~>
if @post.is_liked
@api \posts/likes/delete do
post_id: @post.id
.then ~>
@post.is_liked = false
@update!
else
@api \posts/likes/create do
post_id: @post.id
.then ~>
@post.is_liked = true
@update!
this.refs.text.children.forEach(e => {
if (e.tagName == 'MK-URL') riot.mount(e);
});
}
});
this.like = () => {
if (this.post.is_liked) {
this.api('posts/likes/delete', {
post_id: this.post.id
}).then(() => {
this.post.is_liked = false;
this.update();
});
} else {
this.api('posts/likes/create', {
post_id: this.post.id
}).then(() => {
this.post.is_liked = true;
this.update();
});
}
};
</script>
</mk-post-detail-sub>

View File

@ -329,108 +329,126 @@
</style>
<script>
@mixin \api
@mixin \text
@mixin \user-preview
@mixin \date-stringify
@mixin \NotImplementedException
this.mixin('api');
this.mixin('text');
this.mixin('user-preview');
this.mixin('date-stringify');
this.mixin('NotImplementedException');
@fetching = true
@loading-context = false
@content = null
@post = null
this.fetching = true;
this.loadingContext = false;
this.content = null;
this.post = null;
@on \mount ~>
this.on('mount', () => {
this.api('posts/show', {
post_id: this.opts.post
}).then(post => {
const isRepost = post.repost != null;
const p = isRepost ? post.repost : post;
this.update({
fetching: false,
post: post,
isRepost: isRepost,
p: p,
title: this.dateStringify(p.created_at)
});
@api \posts/show do
post_id: @opts.post
.then (post) ~>
@fetching = false
@post = post
@trigger \loaded
this.trigger('loaded');
@is-repost = @post.repost?
@p = if @is-repost then @post.repost else @post
if (this.p.text) {
const tokens = this.analyze(this.p.text);
@title = @date-stringify @p.created_at
this.refs.text.innerHTML = this.compile(tokens);
@update!
this.refs.text.children.forEach(e => {
if (e.tagName == 'MK-URL') riot.mount(e);
});
if @p.text?
tokens = @analyze @p.text
@refs.text.innerHTML = @compile tokens
@refs.text.children.for-each (e) ~>
if e.tag-name == \MK-URL
riot.mount e
# URLをプレビュー
// URLをプレビュー
tokens
.filter (t) -> t.type == \link
.map (t) ~>
@preview = @refs.text.append-child document.create-element \mk-url-preview
riot.mount @preview, do
url: t.content
.filter(t => t.type == 'link')
.map(t => {
riot.mount(this.refs.text.appendChild(document.createElement('mk-url-preview')), {
url: t.content
});
});
}
# Get likes
@api \posts/likes do
post_id: @p.id
// Get likes
this.api('posts/likes', {
post_id: this.p.id,
limit: 8
.then (likes) ~>
@likes = likes
@update!
}).then(likes => {
this.update({
likes: likes
});
});
# Get reposts
@api \posts/reposts do
post_id: @p.id
// Get reposts
this.api('posts/reposts', {
post_id: this.p.id,
limit: 8
.then (reposts) ~>
@reposts = reposts
@update!
}).then(reposts => {
this.update({
reposts: reposts
});
});
# Get replies
@api \posts/replies do
post_id: @p.id
// Get replies
this.api('posts/replies', {
post_id: this.p.id,
limit: 8
.then (replies) ~>
@replies = replies
@update!
}).then(replies => {
this.update({
replies: replies
});
});
});
});
@update!
this.reply = () => {
riot.mount(document.body.appendChild(document.createElement('mk-post-form-window')), {
reply: this.p
});
};
@reply = ~>
form = document.body.append-child document.create-element \mk-post-form-window
riot.mount form, do
reply: @p
this.repost = () => {
riot.mount(document.body.appendChild(document.createElement('mk-repost-form-window')), {
post: this.p
});
};
@repost = ~>
form = document.body.append-child document.create-element \mk-repost-form-window
riot.mount form, do
post: @p
this.like = () => {
if (this.p.is_liked) {
this.api('posts/likes/delete', {
post_id: this.p.id
}).then(() => {
this.p.is_liked = false;
this.update();
});
} else {
this.api('posts/likes/create', {
post_id: this.p.id
}).then(() => {
this.p.is_liked = true;
this.update();
});
}
};
@like = ~>
if @p.is_liked
@api \posts/likes/delete do
post_id: @p.id
.then ~>
@p.is_liked = false
@update!
else
@api \posts/likes/create do
post_id: @p.id
.then ~>
@p.is_liked = true
@update!
this.loadContext = () => {
this.loadingContext = true;
@load-context = ~>
@loading-context = true
# Get context
@api \posts/context do
post_id: @p.reply_to_id
.then (context) ~>
@context = context.reverse!
@loading-context = false
@update!
// Fetch context
this.api('posts/context', {
post_id: this.p.reply_to_id
}).then(context => {
this.update({
loadContext: false,
content: context.reverse()
});
});
};
</script>
</mk-post-detail>

View File

@ -32,24 +32,31 @@
</style>
<script>
@uploading-files = []
@files = []
this.uploadingFiles = [];
this.files = [];
@on \mount ~>
@refs.window.refs.form.focus!
this.on('mount', () => {
this.refs.window.refs.form.focus();
@refs.window.on \closed ~>
@unmount!
this.refs.window.on('closed', () => {
this.unmount();
});
@refs.window.refs.form.on \post ~>
@refs.window.close!
this.refs.window.refs.form.on('post', () => {
this.refs.window.close();
});
@refs.window.refs.form.on \change-uploading-files (files) ~>
@uploading-files = files
@update!
this.refs.window.refs.form.on('change-uploading-files', files => {
this.update({
uploadingFiles: files
});
});
@refs.window.refs.form.on \change-files (files) ~>
@files = files
@update!
this.refs.window.refs.form.on('change-files', files => {
this.update({
files: files
});
});
});
</script>
</mk-post-form-window>

View File

@ -305,161 +305,160 @@
</style>
<script>
get-cat = require '../../common/scripts/get-cat'
const getCat = require('../../common/scripts/get-cat');
@mixin \api
@mixin \notify
@mixin \autocomplete
this.mixin('api');
this.mixin('notify');
this.mixin('autocomplete');
@wait = false
@uploadings = []
@files = []
@autocomplete = null
@poll = false
this.wait = false;
this.uploadings = [];
this.files = [];
this.autocomplete = null;
this.poll = false;
@in-reply-to-post = @opts.reply
this.inReplyToPost = this.opts.reply;
# https://github.com/riot/riot/issues/2080
if @in-reply-to-post == '' then @in-reply-to-post = null
// https://github.com/riot/riot/issues/2080
if (this.inReplyToPost == '') this.inReplyToPost = null;
@on \mount ~>
@refs.uploader.on \uploaded (file) ~>
@add-file file
this.on('mount', () => {
this.refs.uploader.on('uploaded', file => {
this.addFile(file);
});
@refs.uploader.on \change-uploads (uploads) ~>
@trigger \change-uploading-files uploads
this.refs.uploader.on('change-uploads', uploads => {
this.trigger('change-uploading-files', uploads);
});
@autocomplete = new @Autocomplete @refs.text
@autocomplete.attach!
this.autocomplete = new this.Autocomplete(this.refs.text);
this.autocomplete.attach();
});
@on \unmount ~>
@autocomplete.detach!
this.on('unmount', () => {
this.autocomplete.detach();
});
@focus = ~>
@refs.text.focus!
this.focus = () => {
this.refs.text.focus();
};
@clear = ~>
@refs.text.value = ''
@files = []
@trigger \change-files
@update!
this.clear = () => {
this.refs.text.value = '';
this.files = [];
this.trigger('change-files');
this.update();
};
@ondragover = (e) ~>
e.stop-propagation!
@draghover = true
# ドラッグされてきたものがファイルだったら
if e.data-transfer.effect-allowed == \all
e.data-transfer.drop-effect = \copy
else
e.data-transfer.drop-effect = \move
return false
this.ondragover = e => {
e.stopPropagation();
this.draghover = true;
e.dataTransfer.dropEffect = e.dataTransfer.effectAllowed == 'all' ? 'copy' : 'move';
return false;
};
@ondragenter = (e) ~>
@draghover = true
this.ondragenter = e => {
this.draghover = true;
};
@ondragleave = (e) ~>
@draghover = false
this.ondragleave = e => {
this.draghover = false;
};
@ondrop = (e) ~>
e.prevent-default!
e.stop-propagation!
@draghover = false
this.ondrop = e => {
e.preventDefault();
e.stopPropagation();
this.draghover = false;
# ファイルだったら
if e.data-transfer.files.length > 0
Array.prototype.for-each.call e.data-transfer.files, (file) ~>
@upload file
return false
// ファイルだったら
if (e.dataTransfer.files.length > 0) {
e.dataTransfer.files.forEach(this.upload);
}
# データ取得
data = e.data-transfer.get-data 'text'
if !data?
return false
return false;
};
try
# パース
obj = JSON.parse data
this.onkeydown = e => {
if ((e.which == 10 || e.which == 13) && (e.ctrlKey || e.metaKey)) this.post();
};
# (ドライブの)ファイルだったら
if obj.type == \file
@add-file obj.file
catch
# ignore
this.onpaste = e => {
e.clipboardData.items.forEach(item => {
if (item.kind == 'file') {
this.upload(item.getAsFile());
}
});
};
return false
this.selectFile = () => {
this.refs.file.click();
};
@onkeydown = (e) ~>
if (e.which == 10 || e.which == 13) && (e.ctrl-key || e.meta-key)
@post!
@onpaste = (e) ~>
data = e.clipboard-data
items = data.items
for i from 0 to items.length - 1
item = items[i]
switch (item.kind)
| \file =>
@upload item.get-as-file!
@select-file = ~>
@refs.file.click!
@select-file-from-drive = ~>
browser = document.body.append-child document.create-element \mk-select-file-from-drive-window
i = riot.mount browser, do
this.selectFileFromDrive = () => {
const i = riot.mount(document.body.appendChild(document.createElement('mk-select-file-from-drive-window')), {
multiple: true
i[0].one \selected (files) ~>
files.for-each @add-file
})[0];
i.one('selected', files => {
files.forEach(this.addFile);
});
};
@change-file = ~>
files = @refs.file.files
for i from 0 to files.length - 1
file = files.item i
@upload file
this.changeFile = () => {
this.refs.file.files.forEach(this.upload);
};
@upload = (file) ~>
@refs.uploader.upload file
this.upload = file => {
this.refs.uploader.upload(file);
};
@add-file = (file) ~>
file._remove = ~>
@files = @files.filter (x) -> x.id != file.id
@trigger \change-files @files
@update!
this.addFile = file => {
file._remove = () => {
this.files = this.files.filter(x => x.id != file.id);
this.trigger('change-files', this.files);
this.update();
};
@files.push file
@trigger \change-files @files
@update!
this.files.push(file);
this.trigger('change-files', this.files);
this.update();
};
@add-poll = ~>
@poll = true
this.addPoll = () => {
this.poll = true;
};
@on-poll-destroyed = ~>
@update do
this.onPollDestroyed = () => {
this.update({
poll: false
});
};
@post = (e) ~>
@wait = true
this.post = e => {
this.wait = true;
files = if @files? and @files.length > 0
then @files.map (f) -> f.id
else undefined
const files = this.files && this.files.length > 0
? this.files.map(f => f.id)
: undefined;
@api \posts/create do
text: @refs.text.value
media_ids: files
reply_to_id: if @in-reply-to-post? then @in-reply-to-post.id else undefined
poll: if @poll then @refs.poll.get! else undefined
.then (data) ~>
@trigger \post
@notify if @in-reply-to-post? then '返信しました!' else '投稿しました!'
.catch (err) ~>
console.error err
@notify '投稿できませんでした'
.then ~>
@wait = false
@update!
this.api('posts/create', {
text: this.refs.text.value,
media_ids: files,
reply_to_id: this.inReplyToPost ? this.inReplyToPost.id : undefined,
poll: this.poll ? this.refs.poll.get() : undefined
}).then(data => {
this.trigger('post');
this.notify(this.inReplyToPost ? '返信しました!' : '投稿しました!');
}).catch(err => {
this.notify('投稿できませんでした');
}).then(() => {
this.update({
wait: false
});
});
};
@cat = ~>
@refs.text.value = @refs.text.value + get-cat!
this.cat = () => {
this.refs.text.value += getCat();
};
</script>
</mk-post-form>

View File

@ -83,11 +83,11 @@
</style>
<script>
@mixin \date-stringify
@mixin \user-preview
this.mixin('date-stringify');
this.mixin('user-preview');
@post = @opts.post
this.post = this.opts.post;
@title = @date-stringify @post.created_at
this.title = this.dateStringify(this.post.created_at);
</script>
</mk-post-preview>

View File

@ -75,20 +75,25 @@
</style>
<script>
@title = @opts.title
@value = parse-int @opts.value, 10
@max = parse-int @opts.max, 10
this.title = this.opts.title;
this.value = parseInt(this.opts.value, 10);
this.max = parseInt(this.opts.max, 10);
@on \mount ~>
@refs.window.on \closed ~>
@unmount!
this.on('mount', () => {
this.refs.window.on('closed', () => {
this.unmount();
});
});
@update-progress = (value, max) ~>
@value = parse-int value, 10
@max = parse-int max, 10
@update!
this.updateProgress = (value, max) => {
this.update({
value: parseInt(value, 10),
max: parseInt(max, 10)
});
};
@close = ~>
@refs.window.close!
this.close = () => {
this.refs.window.close();
};
</script>
</mk-progress-dialog>

View File

@ -12,25 +12,32 @@
</style>
<script>
@on-document-keydown = (e) ~>
tag = e.target.tag-name.to-lower-case!
if tag != \input and tag != \textarea
if e.which == 27 # Esc
@refs.window.close!
this.onDocumentKeydown = e => {
if (e.target.tagName != 'INPUT' && e.target.tagName != 'TEXTAREA') {
if (e.which == 27) { // Esc
this.refs.window.close();
}
}
};
@on \mount ~>
@refs.window.refs.form.on \cancel ~>
@refs.window.close!
this.on('mount', () => {
this.refs.window.refs.form.on('cancel', () => {
this.refs.window.close();
});
@refs.window.refs.form.on \posted ~>
@refs.window.close!
this.refs.window.refs.form.on('posted', () => {
this.refs.window.close();
});
document.add-event-listener \keydown @on-document-keydown
document.addEventListener('keydown', this.onDocumentKeydown);
@refs.window.on \closed ~>
@unmount!
this.refs.window.on('closed', () => {
this.unmount();
});
});
@on \unmount ~>
document.remove-event-listener \keydown @on-document-keydown
this.on('unmount', () => {
document.removeEventListener('keydown', this.onDocumentKeydown);
});
</script>
</mk-repost-form-window>

View File

@ -114,31 +114,35 @@
</style>
<script>
@mixin \api
@mixin \notify
this.mixin('api');
this.mixin('notify');
@wait = false
@quote = false
this.wait = false;
this.quote = false;
@cancel = ~>
@trigger \cancel
this.cancel = () => {
this.trigger('cancel');
};
@ok = ~>
@wait = true
@api \posts/create do
repost_id: @opts.post.id
text: if @quote then @refs.text.value else undefined
.then (data) ~>
@trigger \posted
@notify 'Repostしました'
.catch (err) ~>
console.error err
@notify 'Repostできませんでした'
.then ~>
@wait = false
@update!
this.ok = () => {
this.wait = true;
this.api('posts/create', {
repost_id: this.opts.post.id,
text: this.quote ? this.refs.text.value : undefined
}).then(data => {
this.trigger('posted');
this.notify('Repostしました');
}).catch(err => {
this.notify('Repostできませんでした');
}).then(() => {
this.update({
wait: false
});
});
};
@onquote = ~>
@quote = true
this.onquote = () => {
this.quote = true;
};
</script>
</mk-repost-form>

View File

@ -28,59 +28,64 @@
</style>
<script>
@mixin \api
@mixin \get-post-summary
this.mixin('api');
this.mixin('get-post-summary');
@query = @opts.query
@is-loading = true
@is-empty = false
@more-loading = false
@page = 0
this.query = this.opts.query;
this.isLoading = true;
this.isEmpty = false;
this.moreLoading = false;
this.page = 0;
@on \mount ~>
document.add-event-listener \keydown @on-document-keydown
window.add-event-listener \scroll @on-scroll
this.on('mount', () => {
document.addEventListener('keydown', this.onDocumentKeydown);
window.addEventListener('scroll', this.onScroll);
@api \posts/search do
query: @query
.then (posts) ~>
@is-loading = false
@is-empty = posts.length == 0
@update!
@refs.timeline.set-posts posts
@trigger \loaded
.catch (err) ~>
console.error err
this.api('posts/search', {
query: this.query
}).then(posts => {
this.update({
isLoading: false,
isEmpty: posts.length == 0
});
this.refs.timeline.setPosts(posts);
this.trigger('loaded');
});
});
@on \unmount ~>
document.remove-event-listener \keydown @on-document-keydown
window.remove-event-listener \scroll @on-scroll
this.on('unmount', () => {
document.removeEventListener('keydown', this.onDocumentKeydown);
window.removeEventListener('scroll', this.onScroll);
});
@on-document-keydown = (e) ~>
tag = e.target.tag-name.to-lower-case!
if tag != \input and tag != \textarea
if e.which == 84 # t
@refs.timeline.focus!
this.onDocumentKeydown = e => {
if (e.target.tagName != 'INPUT' && e.target.tagName != 'TEXTAREA') {
if (e.which == 84) { // t
this.refs.timeline.focus();
}
}
};
@more = ~>
if @more-loading or @is-loading or @timeline.posts.length == 0
return
@more-loading = true
@update!
@api \posts/search do
query: @query
page: @page + 1
.then (posts) ~>
@more-loading = false
@page++
@update!
@refs.timeline.prepend-posts posts
.catch (err) ~>
console.error err
this.more = () => {
if (this.moreLoading || this.isLoading || this.timeline.posts.length == 0) return;
this.update({
moreLoading: true
});
this.api('posts/search', {
query: this.query,
page: this.page + 1
}).then(posts => {
this.update({
moreLoading: false,
page: page + 1
});
this.refs.timeline.prependPosts(posts);
});
};
@on-scroll = ~>
current = window.scroll-y + window.inner-height
if current > document.body.offset-height - 16 # 遊び
@more!
this.onScroll = () => {
const current = window.scrollY + window.innerHeight;
if (current > document.body.offsetHeight - 16) this.more();
};
</script>
</mk-search-posts>

View File

@ -23,10 +23,12 @@
</style>
<script>
@query = @opts.query
this.query = this.opts.query;
@on \mount ~>
@refs.posts.on \loaded ~>
@trigger \loaded
this.on('mount', () => {
this.refs.posts.on('loaded', () => {
this.trigger('loaded');
});
});
</script>
</mk-search>

View File

@ -131,31 +131,38 @@
</style>
<script>
@file = []
this.file = [];
@multiple = if @opts.multiple? then @opts.multiple else false
@title = @opts.title || '<i class="fa fa-file-o"></i>ファイルを選択'
this.multiple = this.opts.multiple != null ? this.opts.multiple : false;
this.title = this.opts.title || '<i class="fa fa-file-o"></i>ファイルを選択';
@on \mount ~>
@refs.window.refs.browser.on \selected (file) ~>
@file = file
@ok!
this.on('mount', () => {
this.refs.window.refs.browser.on('selected', file => {
this.file = file;
this.ok();
});
@refs.window.refs.browser.on \change-selection (files) ~>
@file = files
@update!
this.refs.window.refs.browser.on('change-selection', files => {
this.file = files;
this.update();
});
@refs.window.on \closed ~>
@unmount!
this.refs.window.on('closed', () => {
this.unmount();
});
});
@close = ~>
@refs.window.close!
this.close = () => {
this.refs.window.close();
};
@upload = ~>
@refs.window.refs.browser.select-local-file!
this.upload = () => {
this.refs.window.refs.browser.selectLocalFile();
};
@ok = ~>
@trigger \selected @file
@refs.window.close!
this.ok = () => {
this.trigger('selected', this.file);
this.refs.window.close();
};
</script>
</mk-select-file-from-drive-window>

View File

@ -31,15 +31,17 @@
</style>
<script>
@mixin \i
@mixin \update-avatar
this.mixin('i');
this.mixin('update-avatar');
@set = ~>
@update-avatar @I
this.set = () => {
this.updateAvatar(this.I);
};
@close = (e) ~>
e.prevent-default!
e.stop-propagation!
@unmount!
this.close = e => {
e.preventDefault();
e.stopPropagation();
this.unmount();
};
</script>
</mk-set-avatar-suggestion>

View File

@ -31,15 +31,17 @@
</style>
<script>
@mixin \i
@mixin \update-banner
this.mixin('i');
this.mixin('update-banner');
@set = ~>
@update-banner @I
this.set = () => {
this.updateBanner(this.I);
};
@close = (e) ~>
e.prevent-default!
e.stop-propagation!
@unmount!
this.close = e => {
e.preventDefault();
e.stopPropagation();
this.unmount();
};
</script>
</mk-set-banner-suggestion>

View File

@ -15,11 +15,14 @@
</style>
<script>
@on \mount ~>
@refs.window.on \closed ~>
@unmount!
this.on('mount', () => {
this.refs.window.on('closed', () => {
this.unmount();
});
});
@close = ~>
@refs.window.close!
this.close = () => {
this.refs.window.close();
};
</script>
</mk-settings-window>

View File

@ -47,11 +47,6 @@
<p>読み込みを高速化する</p>
<p>API通信時に新鮮なユーザー情報をキャッシュすることでフェッチのオーバーヘッドを無くします。(実験的)</p>
</label>
<label class="checkbox">
<input type="checkbox" checked={ I.data.debug } onclick={ updateDebug }/>
<p>開発者モード</p>
<p>デバッグ等の開発者モードを有効にします。</p>
</label>
<label class="checkbox">
<input type="checkbox" checked={ I.data.nya } onclick={ updateNya }/>
<p><i>な</i>を<i>にゃ</i>に変換する</p>
@ -198,46 +193,49 @@
</style>
<script>
@mixin \i
@mixin \api
@mixin \dialog
@mixin \update-avatar
this.mixin('i');
this.mixin('api');
this.mixin('notify');
this.mixin('dialog');
this.mixin('update-avatar');
@page = \account
this.page = 'account';
@set-page = (page) ~>
@page = page
this.setPage = page => {
this.page = page;
};
@avatar = ~>
@update-avatar @I
this.avatar = () => {
this.updateAvatar(this.I);
};
@update-account = ~>
@api \i/update do
name: @refs.account-name.value
location: @refs.account-location.value
bio: @refs.account-bio.value
birthday: @refs.account-birthday.value
.then (i) ~>
alert \ok
.catch (err) ~>
console.error err
this.updateAccount = () => {
this.api('i/update', {
name: this.refs.accountName.value,
location: this.refs.accountLocation.value,
bio: this.refs.accountBio.value,
birthday: this.refs.accountBirthday.value
}).then(() => {
this.notify('プロフィールを更新しました');
});
};
@update-cache = ~>
@I.data.cache = !@I.data.cache
@api \i/appdata/set do
data: JSON.stringify do
cache: @I.data.cache
this.updateCache = () => {
this.I.data.cache = !this.I.data.cache;
this.api('i/appdata/set', {
data: JSON.stringify({
cache: this.I.data.cache
})
});
};
@update-debug = ~>
@I.data.debug = !@I.data.debug
@api \i/appdata/set do
data: JSON.stringify do
debug: @I.data.debug
@update-nya = ~>
@I.data.nya = !@I.data.nya
@api \i/appdata/set do
data: JSON.stringify do
nya: @I.data.nya
this.updateNya = () => {
this.I.data.nya = !this.I.data.nya;
this.api('i/appdata/set', {
data: JSON.stringify({
nya: this.I.data.nya
})
});
};
</script>
</mk-settings>

View File

@ -1,54 +0,0 @@
<mk-stream-indicator>
<p if={ state == 'initializing' }><i class="fa fa-spinner fa-spin"></i><span>接続中
<mk-ellipsis></mk-ellipsis></span></p>
<p if={ state == 'reconnecting' }><i class="fa fa-spinner fa-spin"></i><span>切断されました 接続中
<mk-ellipsis></mk-ellipsis></span></p>
<p if={ state == 'connected' }><i class="fa fa-check"></i><span>接続完了</span></p>
<style>
:scope
display block
pointer-events none
position fixed
z-index 16384
bottom 8px
right 8px
margin 0
padding 6px 12px
font-size 0.9em
color #fff
background rgba(0, 0, 0, 0.8)
> p
display block
margin 0
> i
margin-right 0.25em
</style>
<script>
@mixin \stream
@on \before-mount ~>
@state = @get-stream-state!
if @state == \connected
@root.style.opacity = 0
@stream-state-ev.on \connected ~>
@state = @get-stream-state!
@update!
set-timeout ~>
Velocity @root, {
opacity: 0
} 200ms \linear
, 1000ms
@stream-state-ev.on \closed ~>
@state = @get-stream-state!
@update!
Velocity @root, {
opacity: 1
} 0ms
</script>
</mk-stream-indicator>

View File

@ -1,5 +1,11 @@
<mk-sub-post-content>
<div class="body"><a class="reply" if={ post.reply_to_id }><i class="fa fa-reply"></i></a><span ref="text"></span><a class="quote" if={ post.repost_id } href={ '/post:' + post.repost_id }>RP: ...</a></div>
<div class="body">
<a class="reply" if={ post.reply_to_id }>
<i class="fa fa-reply"></i>
</a>
<span ref="text"></span>
<a class="quote" if={ post.repost_id } href={ '/post:' + post.repost_id }>RP: ...</a>
</div>
<details if={ post.media }>
<summary>({ post.media.length }つのメディア)</summary>
<mk-images-viewer images={ post.media }></mk-images-viewer>
@ -28,18 +34,20 @@
</style>
<script>
@mixin \text
@mixin \user-preview
this.mixin('text');
this.mixin('user-preview');
@post = @opts.post
this.post = this.opts.post;
@on \mount ~>
if @post.text?
tokens = @analyze @post.text
@refs.text.innerHTML = @compile tokens, false
this.on('mount', () => {
if (this.post.text) {
const tokens = this.analyze(this.post.text);
this.refs.text.innerHTML = this.compile(tokens, false);
@refs.text.children.for-each (e) ~>
if e.tag-name == \MK-URL
riot.mount e
this.refs.text.children.forEach(e => {
if (e.tagName == 'MK-URL') riot.mount(e);
});
}
});
</script>
</mk-sub-post-content>

View File

@ -8,15 +8,6 @@
</div>
</div>
</article>
<script>
@mixin \date-stringify
@mixin \user-preview
@post = @opts.post
@title = @date-stringify @post.created_at
</script>
<style>
:scope
display block
@ -97,4 +88,11 @@
font-size 80%
</style>
<script>
this.mixin('date-stringify');
this.mixin('user-preview');
this.post = this.opts.post;
this.title = this.dateStringify(this.post.created_at);
</script>
</mk-timeline-post-sub>

View File

@ -55,8 +55,13 @@
<button class={ liked: p.is_liked } onclick={ like } title="善哉"><i class="fa fa-thumbs-o-up"></i>
<p class="count" if={ p.likes_count > 0 }>{ p.likes_count }</p>
</button>
<button onclick={ NotImplementedException }><i class="fa fa-ellipsis-h"></i></button>
<button onclick={ toggleDetail } title="詳細"><i class="fa fa-caret-down" if={ !isDetailOpened }></i><i class="fa fa-caret-up" if={ isDetailOpened }></i></button>
<button onclick={ NotImplementedException }>
<i class="fa fa-ellipsis-h"></i>
</button>
<button onclick={ toggleDetail } title="詳細">
<i class="fa fa-caret-down" if={ !isDetailOpened }></i>
<i class="fa fa-caret-up" if={ isDetailOpened }></i>
</button>
</footer>
</div>
</article>
@ -312,96 +317,124 @@
</style>
<script>
@mixin \api
@mixin \text
@mixin \date-stringify
@mixin \user-preview
@mixin \NotImplementedException
this.mixin('api');
this.mixin('text');
this.mixin('date-stringify');
this.mixin('user-preview');
this.mixin('NotImplementedException');
@post = @opts.post
@is-repost = @post.repost? and !@post.text?
@p = if @is-repost then @post.repost else @post
this.post = this.opts.post;
this.isRepost = this.post.repost && this.post.text == null;
this.p = this.isRepost ? this.post.repost : this.post;
@title = @date-stringify @p.created_at
this.title = this.dateStringify(this.p.created_at);
@url = CONFIG.url + '/' + @p.user.username + '/' + @p.id
@is-detail-opened = false
this.url = CONFIG.url + '/' + this.p.user.username + '/' + this.p.id;
this.isDetailOpened = false;
@on \mount ~>
if @p.text?
tokens = if @p._highlight?
then @analyze @p._highlight
else @analyze @p.text
this.on('mount', () => {
if (this.p.text) {
const tokens = this.analyze(this.p.text);
@refs.text.innerHTML = @refs.text.innerHTML.replace '<p class="dummy"></p>' if @p._highlight?
then @compile tokens, true, false
else @compile tokens
this.refs.text.innerHTML = this.refs.text.innerHTML.replace('<p class="dummy"></p>', this.compile(tokens));
@refs.text.children.for-each (e) ~>
if e.tag-name == \MK-URL
riot.mount e
this.refs.text.children.forEach(e => {
if (e.tagName == 'MK-URL') riot.mount(e);
});
# URLをプレビュー
// URLをプレビュー
tokens
.filter (t) -> t.type == \link
.map (t) ~>
@preview = @refs.text.append-child document.create-element \mk-url-preview
riot.mount @preview, do
url: t.content
.filter(t => t.type == 'link')
.map(t => {
riot.mount(this.refs.text.appendChild(document.createElement('mk-url-preview')), {
url: t.content
});
});
}
});
@reply = ~>
form = document.body.append-child document.create-element \mk-post-form-window
riot.mount form, do
reply: @p
this.reply = () => {
riot.mount(document.body.appendChild(document.createElement('mk-post-form-window')), {
reply: this.p
});
};
@repost = ~>
form = document.body.append-child document.create-element \mk-repost-form-window
riot.mount form, do
post: @p
this.repost = () => {
riot.mount(document.body.appendChild(document.createElement('mk-repost-form-window')), {
post: this.p
});
};
@like = ~>
if @p.is_liked
@api \posts/likes/delete do
post_id: @p.id
.then ~>
@p.is_liked = false
@update!
else
@api \posts/likes/create do
post_id: @p.id
.then ~>
@p.is_liked = true
@update!
this.like = () => {
if (this.p.is_liked) {
this.api('posts/likes/delete', {
post_id: this.p.id
}).then(() => {
this.p.is_liked = false;
this.update();
});
} else {
this.api('posts/likes/create', {
post_id: this.p.id
}).then(() => {
this.p.is_liked = true;
this.update();
});
}
};
@toggle-detail = ~>
@is-detail-opened = !@is-detail-opened
@update!
this.toggleDetail = () => {
this.update({
isDetailOpened: !this.isDetailOpened
});
};
@on-key-down = (e) ~>
should-be-cancel = true
switch
| e.which == 38 or e.which == 74 or (e.which == 9 and e.shift-key) => # ↑, j or Shift+Tab
focus @root, (e) -> e.previous-element-sibling
| e.which == 40 or e.which == 75 or e.which == 9 => # ↓, k or Tab
focus @root, (e) -> e.next-element-sibling
| e.which == 81 or e.which == 69 => # q or e
@repost!
| e.which == 70 or e.which == 76 => # f or l
@like!
| e.which == 82 => # r
@reply!
| _ =>
should-be-cancel = false
this.onKeyDown = e => {
let shouldBeCancel = true;
if should-be-cancel
e.prevent-default!
switch (true) {
case e.which == 38: // [↑]
case e.which == 74: // [j]
case e.which == 9 && e.shiftKey: // [Shift] + [Tab]
focus(this.root, e => e.previousElementSibling);
break;
function focus(el, fn)
target = fn el
if target?
if target.has-attribute \tabindex
target.focus!
else
focus target, fn
case e.which == 40: // [↓]
case e.which == 75: // [k]
case e.which == 9: // [Tab]
focus(this.root, e => e.nextElementSibling);
break;
case e.which == 81: // [q]
case e.which == 69: // [e]
this.repost();
break;
case e.which == 70: // [f]
case e.which == 76: // [l]
this.like();
break;
case e.which == 82: // [r]
this.reply();
break;
default:
shouldBeCancel = false;
}
if (shouldBeCancel) e.preventDefault();
};
function focus(el, fn) {
const target = fn(el);
if (target) {
if (target.hasAttribute('tabindex')) {
target.focus();
} else {
focus(target, fn);
}
}
}
</script>
</mk-timeline-post>

View File

@ -3,7 +3,9 @@
<mk-timeline-post post={ post }></mk-timeline-post>
<p class="date" if={ i != posts.length - 1 && post._date != posts[i + 1]._date }><span><i class="fa fa-angle-up"></i>{ post._datetext }</span><span><i class="fa fa-angle-down"></i>{ posts[i + 1]._datetext }</span></p>
</virtual>
<footer data-yield="footer"><yield from="footer"/></footer>
<footer data-yield="footer">
<yield from="footer"/>
</footer>
<style>
:scope
display block
@ -44,36 +46,47 @@
</style>
<script>
@posts = []
this.posts = [];
@set-posts = (posts) ~>
@posts = posts
@update!
this.on('update', () => {
this.posts.forEach(post => {
const date = new Date(post.created_at).getDate();
const month = new Date(post.created_at).getMonth() + 1;
post._date = date;
post._datetext = `${month}月 ${date}日`;
});
});
@prepend-posts = (posts) ~>
posts.for-each (post) ~>
@posts.push post
@update!
this.setPosts = posts => {
this.update({
posts: posts
});
};
@add-post = (post) ~>
@posts.unshift post
@update!
this.prependPosts = posts => {
posts.forEach(post => {
this.posts.push(post);
this.update();
});
}
@clear = ~>
@posts = []
@update!
this.addPost = post => {
this.posts.unshift(post);
this.update();
};
@focus = ~>
@root.children.0.focus!
this.tail = () => {
return this.posts[this.posts.length - 1];
};
@on \update ~>
@posts.for-each (post) ~>
date = (new Date post.created_at).get-date!
month = (new Date post.created_at).get-month! + 1
post._date = date
post._datetext = month + '月 ' + date + '日'
this.clear = () => {
this.posts = [];
this.update();
};
this.focus = () => {
this.root.children[0].focus();
};
@tail = ~>
@posts[@posts.length - 1]
</script>
</mk-timeline>

View File

@ -159,54 +159,54 @@
</style>
<script>
@mixin \i
@mixin \signout
const contains = require('../../common/scripts/contains');
@is-open = false
this.mixin('i');
this.mixin('signout');
@on \before-unmount ~>
@close!
this.isOpen = false;
@toggle = ~>
if @is-open
@close!
else
@open!
this.on('before-unmount', () => {
this.close();
});
@open = ~>
@is-open = true
@update!
all = document.query-selector-all 'body *'
Array.prototype.for-each.call all, (el) ~>
el.add-event-listener \mousedown @mousedown
this.toggle = () => {
this.isOpen ? this.close() : this.open();
};
@close = ~>
@is-open = false
@update!
all = document.query-selector-all 'body *'
Array.prototype.for-each.call all, (el) ~>
el.remove-event-listener \mousedown @mousedown
this.open = () => {
this.update({
isOpen: true
});
document.querySelectorAll('body *').forEach(el => {
el.addEventListener('mousedown', this.mousedown);
});
};
@mousedown = (e) ~>
e.prevent-default!
if (!contains @root, e.target) and (@root != e.target)
@close!
return false
this.close = () => {
this.update({
isOpen: false
});
document.querySelectorAll('body *').forEach(el => {
el.removeEventListener('mousedown', this.mousedown);
});
};
@drive = ~>
@close!
riot.mount document.body.append-child document.create-element \mk-drive-browser-window
this.mousedown = e => {
e.preventDefault();
if (!contains(this.root, e.target) && this.root != e.target) this.close();
return false;
};
@settings = ~>
@close!
riot.mount document.body.append-child document.create-element \mk-settings-window
this.drive = () => {
this.close();
riot.mount(document.body.appendChild(document.createElement('mk-drive-browser-window')));
};
this.settings = () => {
this.close();
riot.mount(document.body.appendChild(document.createElement('mk-settings-window')));
};
function contains(parent, child)
node = child.parent-node
while node?
if node == parent
return true
node = node.parent-node
return false
</script>
</mk-ui-header-account>

Some files were not shown because too many files have changed in this diff Show More