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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -2,17 +2,17 @@
<style> <style>
:scope :scope
display inline display inline
</style> </style>
<script> <script>
@on \mount ~> this.on('mount', () => {
# バグ? https://github.com/riot/riot/issues/2103 // https://github.com/riot/riot/issues/2103
#value = @opts.value //value = this.opts.value
value = @opts.riot-value let value = this.opts.riotValue;
max = @opts.max 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> </script>
</mk-number> </mk-number>

View File

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

View File

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

View File

@ -4,5 +4,5 @@
display inline display inline
</style> </style>
<script>@root.innerHTML = @opts.content</script> <script>this.root.innerHTML = this.opts.content</script>
</mk-raw> </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> </style>
<script> <script>
@mixin \api this.mixin('api');
@mixin \stream this.mixin('stream');
@history = [] this.history = [];
@fetching = true this.fetching = true;
@on \mount ~> this.on('mount', () => {
@api \i/signin_history this.api('i/signin_history').then(history => {
.then (history) ~> this.update({
@history = history fetching: false,
@fetching = false history: history
@update! });
.catch (err) ~> });
console.error err
@stream.on \signin @on-signin this.stream.on('signin', this.onSignin);
});
@on \unmount ~> this.on('unmount', () => {
@stream.off \signin @on-signin this.stream.off('signin', this.onSignin);
});
@on-signin = (signin) ~> this.onSignin = signin => {
@history.unshift signin this.history.unshift(signin);
@update! this.update();
};
</script> </script>
</mk-signin-history> </mk-signin-history>

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -28,19 +28,24 @@
</style> </style>
<script> <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 ~> this.on('mount', () => {
@refs.window.on \closed ~> this.refs.window.on('closed', () => {
@unmount! this.unmount();
});
@api \drive .then (info) ~> this.api('drive').then(info => {
@update do this.update({
usage: info.usage / info.capacity * 100 usage: info.usage / info.capacity * 100
});
});
});
@close = ~> this.close = () => {
@refs.window.close! this.refs.window.close();
};
</script> </script>
</mk-drive-browser-window> </mk-drive-browser-window>

View File

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

View File

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

View File

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

View File

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

View File

@ -50,135 +50,152 @@
</style> </style>
<script> <script>
@mixin \api this.mixin('api');
@mixin \dialog this.mixin('dialog');
@folder = @opts.folder this.folder = this.opts.folder;
@browser = @parent this.browser = this.parent;
@title = @folder.name this.title = this.folder.name;
@hover = false this.hover = false;
@draghover = false this.draghover = false;
@is-contextmenu-showing = false this.isContextmenuShowing = false;
@onclick = ~> this.onclick = () => {
@browser.move @folder this.browser.move(this.folder);
};
@onmouseover = ~> this.onmouseover = () => {
@hover = true this.hover = true;
};
@onmouseout = ~> this.onmouseout = () => {
@hover = false this.hover = false
};
@ondragover = (e) ~> this.ondragover = e => {
e.prevent-default! e.preventDefault();
e.stop-propagation! e.stopPropagation();
# 自分自身がドラッグされていない場合 // 自分自身がドラッグされていない場合
if !@is-dragging if (!this.isDragging) {
# ドラッグされてきたものがファイルだったら // ドラッグされてきたものがファイルだったら
if e.data-transfer.effect-allowed == \all if (e.dataTransfer.effectAllowed === 'all') {
e.data-transfer.drop-effect = \copy e.dataTransfer.dropEffect = 'copy';
else } else {
e.data-transfer.drop-effect = \move e.dataTransfer.dropEffect = 'move';
else }
# 自分自身にはドロップさせない } else {
e.data-transfer.drop-effect = \none // 自分自身にはドロップさせない
return false e.dataTransfer.dropEffect = 'none';
}
return false;
};
@ondragenter = ~> this.ondragenter = () => {
if !@is-dragging if (!this.isDragging) this.draghover = true;
@draghover = true };
@ondragleave = ~> this.ondragleave = () => {
@draghover = false this.draghover = false;
};
@ondrop = (e) ~> this.ondrop = e => {
e.stop-propagation! e.stopPropagation();
@draghover = false this.draghover = false;
# ファイルだったら // ファイルだったら
if e.data-transfer.files.length > 0 if (e.dataTransfer.files.length > 0) {
Array.prototype.for-each.call e.data-transfer.files, (file) ~> e.dataTransfer.files.forEach(file => {
@browser.upload file, @folder this.browser.upload(file, this.folder);
return false });
return false;
};
# データ取得 // データ取得
data = e.data-transfer.get-data 'text' const data = e.dataTransfer.getData('text');
if !data? if (data == null) return false;
return false
# パース // パース
obj = JSON.parse data // TODO: Validate JSON
const obj = JSON.parse(data);
# (ドライブの)ファイルだったら // (ドライブの)ファイルだったら
if obj.type == \file if (obj.type == 'file') {
file = obj.id const file = obj.id;
@browser.remove-file file this.browser.removeFile(file);
@api \drive/files/update do this.api('drive/files/update', {
file_id: file file_id: file,
folder_id: @folder.id folder_id: this.folder.id
.then ~> });
# something // (ドライブの)フォルダーだったら
.catch (err, text-status) ~> } else if (obj.type == 'folder') {
console.error err 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);
}
});
}
# (ドライブの)フォルダーだったら return false;
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 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 this.browser.isDragSource = true;
type: \folder };
id: @folder.id
@is-dragging = true
# 親ブラウザに対して、ドラッグが開始されたフラグを立てる this.ondragend = e => {
# (=あなたの子供が、ドラッグを開始しましたよ) this.isDragging = false;
@browser.is-drag-source = true this.browser.isDragSource = false;
};
@ondragend = (e) ~> this.oncontextmenu = e => {
@is-dragging = false e.preventDefault();
@browser.is-drag-source = false e.stopImmediatePropagation();
@oncontextmenu = (e) ~> this.update({
e.prevent-default! isContextmenuShowing: true
e.stop-immediate-propagation! });
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 return false;
@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
</script> </script>
</mk-drive-browser-folder> </mk-drive-browser-folder>

View File

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

View File

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

View File

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

View File

@ -1,6 +1,6 @@
<mk-following-setuper> <mk-following-setuper>
<p class="title">気になるユーザーをフォロー:</p> <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="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> <div class="body"><a class="name" href={ CONFIG.url + '/' + username } target="_blank" data-user-preview={ id }>{ name }</a>
<p class="username">@{ username }</p> <p class="username">@{ username }</p>
@ -8,8 +8,8 @@
<mk-follow-button user={ this }></mk-follow-button> <mk-follow-button user={ this }></mk-follow-button>
</div> </div>
</div> </div>
<p class="empty" if={ !loading && users.length == 0 }>おすすめのユーザーは見つかりませんでした。</p> <p class="empty" if={ !fetching && users.length == 0 }>おすすめのユーザーは見つかりませんでした。</p>
<p class="loading" if={ loading }><i class="fa fa-spinner fa-pulse fa-fw"></i>読み込んでいます <p class="fetching" if={ fetching }><i class="fa fa-spinner fa-pulse fa-fw"></i>読み込んでいます
<mk-ellipsis></mk-ellipsis> <mk-ellipsis></mk-ellipsis>
</p><a class="refresh" onclick={ refresh }>もっと見る</a> </p><a class="refresh" onclick={ refresh }>もっと見る</a>
<button class="close" onclick={ close } title="閉じる"><i class="fa fa-times"></i></button> <button class="close" onclick={ close } title="閉じる"><i class="fa fa-times"></i></button>
@ -81,7 +81,7 @@
text-align center text-align center
color #aaa color #aaa
> .loading > .fetching
margin 0 margin 0
padding 16px padding 16px
text-align center text-align center
@ -123,41 +123,49 @@
</style> </style>
<script> <script>
@mixin \api this.mixin('api');
@mixin \user-preview this.mixin('user-preview');
@users = null this.users = null;
@loading = true this.fetching = true;
@limit = 6users this.limit = 6;
@page = 0 this.page = 0;
@on \mount ~> this.on('mount', () => {
@load! this.fetch();
});
@load = ~> this.fetch = () => {
@loading = true this.update({
@users = null fetching: true,
@update! users: null
});
@api \users/recommendation do this.api('users/recommendation', {
limit: @limit limit: this.limit,
offset: @limit * @page offset: this.limit * this.page
.then (users) ~> }).then(users => {
@loading = false this.fetching = false
@users = users this.users = users
@update! this.update({
.catch (err, text-status) -> fetching: false,
console.error err users: users
});
});
};
@refresh = ~> this.refresh = () => {
if @users.length < @limit if (this.users.length < this.limit) {
@page = 0 this.page = 0;
else } else {
@page++ this.page++;
@load! }
this.fetch();
};
@close = ~> this.close = () => {
@unmount! this.unmount();
};
</script> </script>
</mk-following-setuper> </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> </style>
<script> <script>
@draw = ~> this.draw = () => {
now = new Date! const now = new Date();
nd = now.get-date! const nd = now.getDate();
nm = now.get-month! const nm = now.getMonth();
ny = now.get-full-year! const ny = now.getFullYear();
@year = ny this.year = ny;
@month = nm + 1 this.month = nm + 1;
@day = nd this.day = nd;
@week-day = [\日 \月 \火 \水 \木 \金 \土][now.get-day!] this.weekDay = ['日', '月', '火', '水', '木', '金', '土'][now.getDay()];
@day-numer = (now - (new Date ny, nm, nd)) this.dayNumer = now - new Date(ny, nm, nd);
@day-denom = 1000ms * 60s * 60m * 24h this.dayDenom = 1000/*ms*/ * 60/*s*/ * 60/*m*/ * 24/*h*/;
@month-numer = (now - (new Date ny, nm, 1)) this.monthNumer = now - new Date(ny, nm, 1);
@month-denom = (new Date ny, nm + 1, 1) - (new Date ny, nm, 1) this.monthDenom = new Date(ny, nm + 1, 1) - new Date(ny, nm, 1);
@year-numer = (now - (new Date ny, 0, 1)) this.yearNumer = now - new Date(ny, 0, 1);
@year-denom = (new Date ny + 1, 0, 1) - (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 this.dayP = this.dayNumer / this.dayDenom * 100;
@month-p = @month-numer / @month-denom * 100 this.monthP = this.monthNumer / this.monthDenom * 100;
@year-p = @year-numer / @year-denom * 100 this.yearP = this.yearNumer / this.yearDenom * 100;
@is-holiday = this.isHoliday = now.getDay() == 0 || now.getDay() == 6;
(now.get-day! == 0 or now.get-day! == 6)
@special = this.special =
| nm == 0 and nd == 1 => \on-new-years-day nm == 0 && nd == 1 ? 'on-new-years-day' :
| _ => false false;
@update! this.update();
};
@draw! this.draw();
@on \mount ~> this.on('mount', () => {
@clock = set-interval @draw, 1000ms this.clock = setInterval(this.draw, 1000);
});
@on \unmount ~> this.on('unmount', () => {
clear-interval @clock clearInterval(this.clock);
});
</script> </script>
</mk-calendar-home-widget> </mk-calendar-home-widget>

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,54 +1,11 @@
<mk-not-found> <mk-not-found>
<mk-ui> <mk-ui>
<main> <main>
<h1>Not Found</h1><img src="/_/resources/rogge.jpg" alt=""/> <h1>Not Found</h1>
<div class="mask"></div>
</main> </main>
</mk-ui> </mk-ui>
<style> <style>
:scope :scope
display block 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> </style>
</mk-not-found> </mk-not-found>

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -83,11 +83,11 @@
</style> </style>
<script> <script>
@mixin \date-stringify this.mixin('date-stringify');
@mixin \user-preview 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> </script>
</mk-post-preview> </mk-post-preview>

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -47,11 +47,6 @@
<p>読み込みを高速化する</p> <p>読み込みを高速化する</p>
<p>API通信時に新鮮なユーザー情報をキャッシュすることでフェッチのオーバーヘッドを無くします。(実験的)</p> <p>API通信時に新鮮なユーザー情報をキャッシュすることでフェッチのオーバーヘッドを無くします。(実験的)</p>
</label> </label>
<label class="checkbox">
<input type="checkbox" checked={ I.data.debug } onclick={ updateDebug }/>
<p>開発者モード</p>
<p>デバッグ等の開発者モードを有効にします。</p>
</label>
<label class="checkbox"> <label class="checkbox">
<input type="checkbox" checked={ I.data.nya } onclick={ updateNya }/> <input type="checkbox" checked={ I.data.nya } onclick={ updateNya }/>
<p><i>な</i>を<i>にゃ</i>に変換する</p> <p><i>な</i>を<i>にゃ</i>に変換する</p>
@ -198,46 +193,49 @@
</style> </style>
<script> <script>
@mixin \i this.mixin('i');
@mixin \api this.mixin('api');
@mixin \dialog this.mixin('notify');
@mixin \update-avatar this.mixin('dialog');
this.mixin('update-avatar');
@page = \account this.page = 'account';
@set-page = (page) ~> this.setPage = page => {
@page = page this.page = page;
};
@avatar = ~> this.avatar = () => {
@update-avatar @I this.updateAvatar(this.I);
};
@update-account = ~> this.updateAccount = () => {
@api \i/update do this.api('i/update', {
name: @refs.account-name.value name: this.refs.accountName.value,
location: @refs.account-location.value location: this.refs.accountLocation.value,
bio: @refs.account-bio.value bio: this.refs.accountBio.value,
birthday: @refs.account-birthday.value birthday: this.refs.accountBirthday.value
.then (i) ~> }).then(() => {
alert \ok this.notify('プロフィールを更新しました');
.catch (err) ~> });
console.error err };
@update-cache = ~> this.updateCache = () => {
@I.data.cache = !@I.data.cache this.I.data.cache = !this.I.data.cache;
@api \i/appdata/set do this.api('i/appdata/set', {
data: JSON.stringify do data: JSON.stringify({
cache: @I.data.cache cache: this.I.data.cache
})
});
};
@update-debug = ~> this.updateNya = () => {
@I.data.debug = !@I.data.debug this.I.data.nya = !this.I.data.nya;
@api \i/appdata/set do this.api('i/appdata/set', {
data: JSON.stringify do data: JSON.stringify({
debug: @I.data.debug nya: this.I.data.nya
})
@update-nya = ~> });
@I.data.nya = !@I.data.nya };
@api \i/appdata/set do
data: JSON.stringify do
nya: @I.data.nya
</script> </script>
</mk-settings> </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> <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 }> <details if={ post.media }>
<summary>({ post.media.length }つのメディア)</summary> <summary>({ post.media.length }つのメディア)</summary>
<mk-images-viewer images={ post.media }></mk-images-viewer> <mk-images-viewer images={ post.media }></mk-images-viewer>
@ -28,18 +34,20 @@
</style> </style>
<script> <script>
@mixin \text this.mixin('text');
@mixin \user-preview this.mixin('user-preview');
@post = @opts.post this.post = this.opts.post;
@on \mount ~> this.on('mount', () => {
if @post.text? if (this.post.text) {
tokens = @analyze @post.text const tokens = this.analyze(this.post.text);
@refs.text.innerHTML = @compile tokens, false this.refs.text.innerHTML = this.compile(tokens, false);
@refs.text.children.for-each (e) ~> this.refs.text.children.forEach(e => {
if e.tag-name == \MK-URL if (e.tagName == 'MK-URL') riot.mount(e);
riot.mount e });
}
});
</script> </script>
</mk-sub-post-content> </mk-sub-post-content>

View File

@ -8,15 +8,6 @@
</div> </div>
</div> </div>
</article> </article>
<script>
@mixin \date-stringify
@mixin \user-preview
@post = @opts.post
@title = @date-stringify @post.created_at
</script>
<style> <style>
:scope :scope
display block display block
@ -97,4 +88,11 @@
font-size 80% font-size 80%
</style> </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> </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> <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> <p class="count" if={ p.likes_count > 0 }>{ p.likes_count }</p>
</button> </button>
<button onclick={ NotImplementedException }><i class="fa fa-ellipsis-h"></i></button> <button onclick={ NotImplementedException }>
<button onclick={ toggleDetail } title="詳細"><i class="fa fa-caret-down" if={ !isDetailOpened }></i><i class="fa fa-caret-up" if={ isDetailOpened }></i></button> <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> </footer>
</div> </div>
</article> </article>
@ -312,96 +317,124 @@
</style> </style>
<script> <script>
@mixin \api this.mixin('api');
@mixin \text this.mixin('text');
@mixin \date-stringify this.mixin('date-stringify');
@mixin \user-preview this.mixin('user-preview');
@mixin \NotImplementedException this.mixin('NotImplementedException');
@post = @opts.post this.post = this.opts.post;
@is-repost = @post.repost? and !@post.text? this.isRepost = this.post.repost && this.post.text == null;
@p = if @is-repost then @post.repost else @post 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 this.url = CONFIG.url + '/' + this.p.user.username + '/' + this.p.id;
@is-detail-opened = false this.isDetailOpened = false;
@on \mount ~> this.on('mount', () => {
if @p.text? if (this.p.text) {
tokens = if @p._highlight? const tokens = this.analyze(this.p.text);
then @analyze @p._highlight
else @analyze @p.text
@refs.text.innerHTML = @refs.text.innerHTML.replace '<p class="dummy"></p>' if @p._highlight? this.refs.text.innerHTML = this.refs.text.innerHTML.replace('<p class="dummy"></p>', this.compile(tokens));
then @compile tokens, true, false
else @compile tokens
@refs.text.children.for-each (e) ~> this.refs.text.children.forEach(e => {
if e.tag-name == \MK-URL if (e.tagName == 'MK-URL') riot.mount(e);
riot.mount e });
# URLをプレビュー // URLをプレビュー
tokens tokens
.filter (t) -> t.type == \link .filter(t => t.type == 'link')
.map (t) ~> .map(t => {
@preview = @refs.text.append-child document.create-element \mk-url-preview riot.mount(this.refs.text.appendChild(document.createElement('mk-url-preview')), {
riot.mount @preview, do
url: t.content url: t.content
});
});
}
});
@reply = ~> this.reply = () => {
form = document.body.append-child document.create-element \mk-post-form-window riot.mount(document.body.appendChild(document.createElement('mk-post-form-window')), {
riot.mount form, do reply: this.p
reply: @p });
};
@repost = ~> this.repost = () => {
form = document.body.append-child document.create-element \mk-repost-form-window riot.mount(document.body.appendChild(document.createElement('mk-repost-form-window')), {
riot.mount form, do post: this.p
post: @p });
};
@like = ~> this.like = () => {
if @p.is_liked if (this.p.is_liked) {
@api \posts/likes/delete do this.api('posts/likes/delete', {
post_id: @p.id post_id: this.p.id
.then ~> }).then(() => {
@p.is_liked = false this.p.is_liked = false;
@update! this.update();
else });
@api \posts/likes/create do } else {
post_id: @p.id this.api('posts/likes/create', {
.then ~> post_id: this.p.id
@p.is_liked = true }).then(() => {
@update! this.p.is_liked = true;
this.update();
});
}
};
@toggle-detail = ~> this.toggleDetail = () => {
@is-detail-opened = !@is-detail-opened this.update({
@update! isDetailOpened: !this.isDetailOpened
});
};
@on-key-down = (e) ~> this.onKeyDown = e => {
should-be-cancel = true let shouldBeCancel = 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
if should-be-cancel switch (true) {
e.prevent-default! 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) case e.which == 40: // [↓]
target = fn el case e.which == 75: // [k]
if target? case e.which == 9: // [Tab]
if target.has-attribute \tabindex focus(this.root, e => e.nextElementSibling);
target.focus! break;
else
focus target, fn 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> </script>
</mk-timeline-post> </mk-timeline-post>

View File

@ -3,7 +3,9 @@
<mk-timeline-post post={ post }></mk-timeline-post> <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> <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> </virtual>
<footer data-yield="footer"><yield from="footer"/></footer> <footer data-yield="footer">
<yield from="footer"/>
</footer>
<style> <style>
:scope :scope
display block display block
@ -44,36 +46,47 @@
</style> </style>
<script> <script>
@posts = [] this.posts = [];
@set-posts = (posts) ~> this.on('update', () => {
@posts = posts this.posts.forEach(post => {
@update! 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) ~> this.setPosts = posts => {
posts.for-each (post) ~> this.update({
@posts.push post posts: posts
@update! });
};
@add-post = (post) ~> this.prependPosts = posts => {
@posts.unshift post posts.forEach(post => {
@update! this.posts.push(post);
this.update();
});
}
@clear = ~> this.addPost = post => {
@posts = [] this.posts.unshift(post);
@update! this.update();
};
@focus = ~> this.tail = () => {
@root.children.0.focus! return this.posts[this.posts.length - 1];
};
@on \update ~> this.clear = () => {
@posts.for-each (post) ~> this.posts = [];
date = (new Date post.created_at).get-date! this.update();
month = (new Date post.created_at).get-month! + 1 };
post._date = date
post._datetext = month + '月 ' + date + '日' this.focus = () => {
this.root.children[0].focus();
};
@tail = ~>
@posts[@posts.length - 1]
</script> </script>
</mk-timeline> </mk-timeline>

View File

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

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