Initial commit 🍀

This commit is contained in:
syuilo 2016-12-29 07:49:51 +09:00
commit b3f42e62af
405 changed files with 31017 additions and 0 deletions

View file

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

View file

@ -0,0 +1,13 @@
extends ../../../base
block head
link(rel='stylesheet', href='/_/resources/common/pages/about/style.css')
script(src='/_/resources/common/pages/about/script.js', async, defer)
block body
article
header
h1
block header
div.body
block content

View file

@ -0,0 +1,13 @@
extends ../base
block title
| スタッフ | Misskey
block header
| スタッフ
block content
div.members
div.member
p しゅいろ
p 統括、設計、グラフィックデザイン、プログラム

View file

@ -0,0 +1,67 @@
riot = require \riot
spinner = null
pending = 0
net = riot.observable!
riot.mixin \net do
net: net
log = (riot.mixin \log).log
module.exports = (i, endpoint, data) ->
pending++
if i? and typeof i == \object then i = i.token
body = []
# append user token when signed in
if i? then body.push "i=#i"
for k, v of data
if v != undefined
v = encodeURIComponent v
body.push "#k=#v"
opts =
method: \POST
headers:
'Content-Type': 'application/x-www-form-urlencoded; charset=utf-8'
body: body.join \&
if endpoint == \signin
opts.credentials = \include
ep = if (endpoint.index-of '://') > -1
then endpoint
else "#{CONFIG.api.url}/#{endpoint}"
if pending == 1
spinner := document.create-element \div
..set-attribute \id \wait
document.body.append-child spinner
new Promise (resolve, reject) ->
timer = set-timeout ->
net.trigger \detected-slow-network
, 5000ms
log "API: #{ep}"
fetch ep, opts
.then (res) ->
pending--
clear-timeout timer
if pending == 0
spinner.parent-node.remove-child spinner
if res.status == 200
res.json!.then resolve
else if res.status == 204
resolve!
else
res.json!.then (err) ->
reject err.error
.catch reject

View file

@ -0,0 +1,6 @@
module.exports = function(bytes) {
var sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB'];
if (bytes == 0) return '0Byte';
var i = parseInt(Math.floor(Math.log(bytes) / Math.log(1024)));
return Math.round(bytes / Math.pow(1024, i), 2) + sizes[i];
}

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -0,0 +1,30 @@
module.exports = function(tokens, canBreak, escape) {
if (canBreak == null) {
canBreak = true;
}
if (escape == null) {
escape = true;
}
return tokens.map(function(token) {
switch (token.type) {
case 'text':
if (escape) {
return token.content
.replace(/>/g, '&gt;')
.replace(/</g, '&lt;')
.replace(/(\r\n|\n|\r)/g, canBreak ? '<br>' : ' ');
} else {
return token.content
.replace(/(\r\n|\n|\r)/g, canBreak ? '<br>' : ' ');
}
case 'bold':
return '<strong>' + token.bold + '</strong>';
case 'link':
return '<mk-url href="' + token.content + '" target="_blank"></mk-url>';
case 'mention':
return '<a href="' + CONFIG.url + '/' + token.username + '" target="_blank" data-user-preview="' + token.content + '">' + token.content + '</a>';
case 'hashtag': // TODO
return '<a>' + token.content + '</a>';
}
}).join('');
}

View file

@ -0,0 +1,12 @@
module.exports = function () {
var uuid = '', i, random;
for (i = 0; i < 32; i++) {
random = Math.random() * 16 | 0;
if (i == 8 || i == 12 || i == 16 || i == 20) {
uuid += '-'
}
uuid += (i == 12 ? 4 : (i == 16 ? (random & 3 | 8) : random)).toString(16);
}
return uuid;
}

View file

@ -0,0 +1,16 @@
require './tags/core-error.tag'
require './tags/url.tag'
require './tags/url-preview.tag'
require './tags/ripple-string.tag'
require './tags/time.tag'
require './tags/file-type-icon.tag'
require './tags/uploader.tag'
require './tags/ellipsis.tag'
require './tags/raw.tag'
require './tags/number.tag'
require './tags/special-message.tag'
require './tags/signin.tag'
require './tags/signup.tag'
require './tags/forkit.tag'
require './tags/introduction.tag'
require './tags/copyright.tag'

View file

@ -0,0 +1,5 @@
mk-copyright
span (c) syuilo 2014-2016
style.
display block

View file

@ -0,0 +1,63 @@
mk-core-error
//i: i.fa.fa-times-circle
img(src='/_/resources/error.jpg', alt='')
h1: mk-ripple-string サーバーに接続できません
p.text
| インターネット回線に問題があるか、サーバーがダウンまたはメンテナンスしている可能性があります。しばらくしてから
a(onclick={ retry }) 再度お試し
| ください。
p.thanks いつもMisskeyをご利用いただきありがとうございます。
style.
position fixed
z-index 16385
top 0
left 0
width 100%
height 100%
text-align center
background #f8f8f8
> i
display block
margin-top 64px
font-size 5em
color #6998a0
> img
display block
height 200px
margin 64px auto 0 auto
pointer-events none
-ms-user-select none
-moz-user-select none
-webkit-user-select none
user-select none
> h1
display block
margin 32px auto 16px auto
font-size 1.5em
color #555
> .text
display block
margin 0 auto
max-width 600px
font-size 1em
color #666
> .thanks
display block
margin 32px auto 0 auto
padding 32px 0 32px 0
max-width 600px
font-size 0.9em
font-style oblique
color #aaa
border-top solid 1px #eee
script.
@retry = ~>
@unmount!
@opts.retry!

View file

@ -0,0 +1,25 @@
mk-ellipsis
span .
span .
span .
style.
display inline
> span
animation ellipsis 1.4s infinite ease-in-out both
&:nth-child(1)
animation-delay 0s
&:nth-child(2)
animation-delay 0.16s
&:nth-child(3)
animation-delay 0.32s
@keyframes ellipsis
0%, 80%, 100%
opacity 1
40%
opacity 0

View file

@ -0,0 +1,9 @@
mk-file-type-icon
i.fa.fa-file-image-o(if={ kind == 'image' })
style.
display inline
script.
@file = @opts.file
@kind = @file.type.split \/ .0

View file

@ -0,0 +1,37 @@
mk-forkit
a(href='https://github.com/syuilo/misskey', target='_blank', title='View source on Github', aria-label='View source on Github')
svg(width='80', height='80', viewBox='0 0 250 250', aria-hidden)
path(d='M0,0 L115,115 L130,115 L142,142 L250,250 L250,0 Z')
path.octo-arm(d='M128.3,109.0 C113.8,99.7 119.0,89.6 119.0,89.6 C122.0,82.7 120.5,78.6 120.5,78.6 C119.2,72.0 123.4,76.3 123.4,76.3 C127.3,80.9 125.5,87.3 125.5,87.3 C122.9,97.6 130.6,101.9 134.4,103.2', fill='currentColor')
path(d='M115.0,115.0 C114.9,115.1 118.7,116.5 119.8,115.4 L133.7,101.6 C136.9,99.2 139.9,98.4 142.2,98.6 C133.8,88.0 127.5,74.4 143.8,58.0 C148.5,53.4 154.0,51.2 159.7,51.0 C160.3,49.4 163.2,43.6 171.4,40.1 C171.4,40.1 176.1,42.5 178.8,56.2 C183.1,58.6 187.2,61.8 190.9,65.4 C194.5,69.0 197.7,73.2 200.1,77.6 C213.8,80.2 216.3,84.9 216.3,84.9 C212.7,93.1 206.9,96.0 205.4,96.6 C205.1,102.4 203.0,107.8 198.3,112.5 C181.9,128.9 168.3,122.5 157.7,114.1 C157.9,116.9 156.7,120.9 152.7,124.9 L141.0,136.5 C139.8,137.7 141.6,141.9 141.8,141.8 Z', fill='currentColor')
style.
display block
position absolute
top 0
right 0
> a
display block
> svg
display block
//fill #151513
//color #fff
fill $theme-color
color $theme-color-foreground
.octo-arm
transform-origin 130px 106px
&:hover
.octo-arm
animation octocat-wave 560ms ease-in-out
@keyframes octocat-wave
0%, 100%
transform rotate(0)
20%, 60%
transform rotate(-25deg)
40%, 80%
transform rotate(10deg)

View file

@ -0,0 +1,22 @@
mk-introduction
article
h1 Misskeyとは
<p><ruby>Misskey<rt>みすきー</rt></ruby>は、<a href="http://syuilo.com" target="_blank">syuilo</a>が2014年くらいから<a href="https://github.com/syuilo" target="_blank">オープンソースで</a>開発・運営を行っている、ミニブログベースのSNSです。</p>
<p>Twitter, Facebook, LINE, Google+ などを<del>パクって</del><i>参考にして</i>います。</p>
<p>無料で誰でも利用でき、広告なども一切ありません。</p>
<p><a href={ CONFIG.urls.about } target="_blank">もっと知りたい方はこちら</a></p>
style.
display block
h1
margin 0
text-align center
font-size 1.2em
p
margin 16px 0
&:last-child
margin 0
text-align center

View file

@ -0,0 +1,15 @@
mk-number
style.
display inline
script.
@on \mount ~>
# バグ? https://github.com/riot/riot/issues/2103
#value = @opts.value
value = @opts.riot-value
max = @opts.max
if max? then if value > max then value = max
@root.innerHTML = value.to-locale-string!

View file

@ -0,0 +1,7 @@
mk-raw
style.
display inline
script.
@root.innerHTML = @opts.content

View file

@ -0,0 +1,24 @@
mk-ripple-string
<yield/>
style.
display inline
> span
animation ripple-string 5s infinite ease-in-out both
@keyframes ripple-string
0%, 50%, 100%
opacity 1
25%
opacity 0.5
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

View file

@ -0,0 +1,136 @@
mk-signin
form(onsubmit={ onsubmit }, class={ signing: signing })
label.user-name
input@username(
type='text'
pattern='^[a-zA-Z0-9\-]+$'
placeholder='ユーザー名'
autofocus
required
oninput={ oninput })
i.fa.fa-at
label.password
input@password(
type='password'
placeholder='パスワード'
required)
i.fa.fa-lock
button(type='submit', disabled={ signing }) { signing ? 'やっています...' : 'サインイン' }
style.
display block
> form
display block
z-index 2
&.signing
&, *
cursor wait !important
label
display block
margin 12px 0
i
display block
pointer-events none
position absolute
bottom 0
top 0
left 0
z-index 1
margin auto
padding 0 16px
height 1em
color #898786
input[type=text]
input[type=password]
user-select text
display inline-block
cursor auto
padding 0 0 0 38px
margin 0
width 100%
line-height 44px
font-size 1em
color rgba(0, 0, 0, 0.7)
background #fff
outline none
border solid 1px #eee
border-radius 4px
&:hover
background rgba(255, 255, 255, 0.7)
border-color #ddd
& + i
color #797776
&:focus
background #fff
border-color #ccc
& + i
color #797776
[type=submit]
cursor pointer
padding 16px
margin -6px 0 0 0
width 100%
font-size 1.2em
color rgba(0, 0, 0, 0.5)
outline none
border none
border-radius 0
background transparent
transition all .5s ease
&:hover
color $theme-color
transition all .2s ease
&:focus
color $theme-color
transition all .2s ease
&:active
color darken($theme-color, 30%)
transition all .2s ease
&:disabled
opacity 0.7
script.
@mixin \api
@user = null
@signing = false
@oninput = ~>
@api \users/show do
username: @refs.username.value
.then (user) ~>
@user = user
@trigger \user user
@update!
@onsubmit = (e) ~>
e.prevent-default!
@signing = true
@update!
@api \signin do
username: @refs.username.value
password: @refs.password.value
.then ~>
location.reload!
.catch ~>
alert 'something happened'
@signing = false
@update!
false

View file

@ -0,0 +1,352 @@
mk-signup
form(onsubmit={ onsubmit }, autocomplete='off')
label.username
p.caption
i.fa.fa-at
| ユーザー名
input@username(
type='text'
pattern='^[a-zA-Z0-9\-]{3,20}$'
placeholder='a~z、A~Z、0~9、-'
autocomplete='off'
required
onkeyup={ on-change-username })
p.profile-page-url-preview(if={ refs.username.value != '' && username-state != 'invalid-format' && username-state != 'min-range' && username-state != 'max-range' }) { CONFIG.url + '/' + refs.username.value }
p.info(if={ username-state == 'wait' }, style='color:#999')
i.fa.fa-fw.fa-spinner.fa-pulse
| 確認しています...
p.info(if={ username-state == 'ok' }, style='color:#3CB7B5')
i.fa.fa-fw.fa-check
| 利用できます
p.info(if={ username-state == 'unavailable' }, style='color:#FF1161')
i.fa.fa-fw.fa-exclamation-triangle
| 既に利用されています
p.info(if={ username-state == 'error' }, style='color:#FF1161')
i.fa.fa-fw.fa-exclamation-triangle
| 通信エラー
p.info(if={ username-state == 'invalid-format' }, style='color:#FF1161')
i.fa.fa-fw.fa-exclamation-triangle
| a~z、A~Z、0~9、-(ハイフン)が使えます
p.info(if={ username-state == 'min-range' }, style='color:#FF1161')
i.fa.fa-fw.fa-exclamation-triangle
| 3文字以上でお願いします
p.info(if={ username-state == 'max-range' }, style='color:#FF1161')
i.fa.fa-fw.fa-exclamation-triangle
| 20文字以内でお願いします
label.password
p.caption
i.fa.fa-lock
| パスワード
input@password(
type='password'
placeholder='8文字以上を推奨します'
autocomplete='off'
required
onkeyup={ on-change-password })
div.meter(if={ password-strength != '' }, data-strength={ password-strength })
div.value@password-metar
p.info(if={ password-strength == 'low' }, style='color:#FF1161')
i.fa.fa-fw.fa-exclamation-triangle
| 弱いパスワード
p.info(if={ password-strength == 'medium' }, style='color:#3CB7B5')
i.fa.fa-fw.fa-check
| まあまあのパスワード
p.info(if={ password-strength == 'high' }, style='color:#3CB7B5')
i.fa.fa-fw.fa-check
| 強いパスワード
label.retype-password
p.caption
i.fa.fa-lock
| パスワード(再入力)
input@password-retype(
type='password'
placeholder='確認のため再入力してください'
autocomplete='off'
required
onkeyup={ on-change-password-retype })
p.info(if={ password-retype-state == 'match' }, style='color:#3CB7B5')
i.fa.fa-fw.fa-check
| 確認されました
p.info(if={ password-retype-state == 'not-match' }, style='color:#FF1161')
i.fa.fa-fw.fa-exclamation-triangle
| 一致していません
label.recaptcha
p.caption
i.fa.fa-toggle-on(if={ recaptchaed })
i.fa.fa-toggle-off(if={ !recaptchaed })
| 認証
div.g-recaptcha(
data-callback='onRecaptchaed'
data-expired-callback='onRecaptchaExpired'
data-sitekey={ CONFIG.recaptcha.site-key })
label.agree-tou
input(
name='agree-tou',
type='checkbox',
autocomplete='off',
required)
p
a() 利用規約
| に同意する
button(onclick={ onsubmit })
| アカウント作成
style.
display block
min-width 302px
overflow hidden
> form
label
display block
margin 16px 0
> .caption
margin 0 0 4px 0
color #828888
font-size 0.95em
> i
margin-right 0.25em
color #96adac
> .info
display block
margin 4px 0
font-size 0.8em
> i
margin-right 0.3em
&.username
.profile-page-url-preview
display block
margin 4px 8px 0 4px
font-size 0.8em
color #888
&:empty
display none
&:not(:empty) + .info
margin-top 0
&.password
.meter
display block
margin-top 8px
width 100%
height 8px
&[data-strength='']
display none
&[data-strength='low']
> .value
background #d73612
&[data-strength='medium']
> .value
background #d7ca12
&[data-strength='high']
> .value
background #61bb22
> .value
display block
width 0%
height 100%
background transparent
border-radius 4px
transition all 0.1s ease
[type=text], [type=password]
user-select text
display inline-block
cursor auto
padding 0 12px
margin 0
width 100%
line-height 44px
font-size 1em
color #333 !important
background #fff !important
outline none
border solid 1px rgba(0, 0, 0, 0.1)
border-radius 4px
box-shadow 0 0 0 114514px #fff inset
transition all .3s ease
&:hover
border-color rgba(0, 0, 0, 0.2)
transition all .1s ease
&:focus
color $theme-color !important
border-color $theme-color
box-shadow 0 0 0 1024px #fff inset, 0 0 0 4px rgba($theme-color, 10%)
transition all 0s ease
&:disabled
opacity 0.5
.agree-tou
padding 4px
border-radius 4px
&:hover
background #f4f4f4
&:active
background #eee
&, *
cursor pointer
p
display inline
color #555
button
margin 0 0 32px 0
padding 16px
width 100%
font-size 1em
color #fff
background $theme-color
border-radius 3px
&:hover
background lighten($theme-color, 5%)
&:active
background darken($theme-color, 5%)
script.
@mixin \api
@mixin \get-password-strength
@username-state = null
@password-strength = ''
@password-retype-state = null
@recaptchaed = false
window.on-recaptchaed = ~>
@recaptchaed = true
@update!
window.on-recaptcha-expired = ~>
@recaptchaed = false
@update!
@on \mount ~>
head = (document.get-elements-by-tag-name \head).0
script = document.create-element \script
..set-attribute \src \https://www.google.com/recaptcha/api.js
head.append-child script
@on-change-username = ~>
username = @refs.username.value
if username == ''
@username-state = null
@update!
return
err = switch
| not username.match /^[a-zA-Z0-9\-]+$/ => \invalid-format
| username.length < 3chars => \min-range
| username.length > 20chars => \max-range
| _ => null
if err?
@username-state = err
@update!
else
@username-state = \wait
@update!
@api \username/available do
username: username
.then (result) ~>
if result.available
@username-state = \ok
else
@username-state = \unavailable
@update!
.catch (err) ~>
@username-state = \error
@update!
@on-change-password = ~>
password = @refs.password.value
if password == ''
@password-strength = ''
return
strength = @get-password-strength password
if strength > 0.3
@password-strength = \medium
if strength > 0.7
@password-strength = \high
else
@password-strength = \low
@update!
@refs.password-metar.style.width = (strength * 100) + \%
@on-change-password-retype = ~>
password = @refs.password.value
retyped-password = @refs.password-retype.value
if retyped-password == ''
@password-retype-state = null
return
if password == retyped-password
@password-retype-state = \match
else
@password-retype-state = \not-match
@onsubmit = (e) ~>
e.prevent-default!
username = @refs.username.value
password = @refs.password.value
locker = document.body.append-child document.create-element \mk-locker
@api \signup do
username: username
password: password
'g-recaptcha-response': grecaptcha.get-response!
.then ~>
@api \signin do
username: username
password: password
.then ~>
location.href = CONFIG.url
.catch ~>
alert '何らかの原因によりアカウントの作成に失敗しました。再度お試しください。'
grecaptcha.reset!
@recaptchaed = false
locker.parent-node.remove-child locker
false

View file

@ -0,0 +1,24 @@
mk-special-message
p(if={ m == 1 && d == 1 }) Happy New Year!
p(if={ m == 12 && d == 25 }) Merry Christmas!
style.
display block
&:empty
display none
> p
margin 0
padding 4px
text-align center
font-size 14px
font-weight bold
text-transform uppercase
color #fff
background #ff1036
script.
now = new Date!
@d = now.get-date!
@m = now.get-month! + 1

View file

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

View file

@ -0,0 +1,201 @@
mk-uploader
ol(if={ uploads.length > 0 })
li(each={ uploads })
div.img(style='background-image: url({ img })')
p.name
i.fa.fa-spinner.fa-pulse
| { name }
p.status
span.initing(if={ progress == undefined })
| 待機中
mk-ellipsis
span.kb(if={ progress != undefined })
| { String(Math.floor(progress.value / 1024)).replace(/(\d)(?=(\d\d\d)+(?!\d))/g, '$1,') }
i KB
= ' / '
| { String(Math.floor(progress.max / 1024)).replace(/(\d)(?=(\d\d\d)+(?!\d))/g, '$1,') }
i KB
span.percentage(if={ progress != undefined }) { Math.floor((progress.value / progress.max) * 100) }
progress(if={ progress != undefined && progress.value != progress.max }, value={ progress.value }, max={ progress.max })
div.progress.initing(if={ progress == undefined })
div.progress.waiting(if={ progress != undefined && progress.value == progress.max })
style.
display block
overflow auto
&:empty
display none
> ol
display block
margin 0
padding 0
list-style none
> li
display block
margin 8px 0 0 0
padding 0
height 36px
box-shadow 0 -1px 0 rgba($theme-color, 0.1)
border-top solid 8px transparent
&:first-child
margin 0
box-shadow none
border-top none
> .img
display block
position absolute
top 0
left 0
width 36px
height 36px
background-size cover
background-position center center
> .name
display block
position absolute
top 0
left 44px
margin 0
padding 0
max-width 256px
font-size 0.8em
color rgba($theme-color, 0.7)
white-space nowrap
text-overflow ellipsis
overflow hidden
> i
margin-right 4px
> .status
display block
position absolute
top 0
right 0
margin 0
padding 0
font-size 0.8em
> .initing
color rgba($theme-color, 0.5)
> .kb
color rgba($theme-color, 0.5)
> .percentage
display inline-block
width 48px
text-align right
color rgba($theme-color, 0.7)
&:after
content '%'
> progress
display block
position absolute
bottom 0
right 0
margin 0
width calc(100% - 44px)
height 8px
background transparent
border none
border-radius 4px
overflow hidden
&::-webkit-progress-value
background $theme-color
&::-webkit-progress-bar
background rgba($theme-color, 0.1)
> .progress
display block
position absolute
bottom 0
right 0
margin 0
width calc(100% - 44px)
height 8px
border none
border-radius 4px
background linear-gradient(
45deg,
lighten($theme-color, 30%) 25%,
$theme-color 25%,
$theme-color 50%,
lighten($theme-color, 30%) 50%,
lighten($theme-color, 30%) 75%,
$theme-color 75%,
$theme-color
)
background-size 32px 32px
animation bg 1.5s linear infinite
&.initing
opacity 0.3
@keyframes bg
from {background-position: 0 0;}
to {background-position: -64px 32px;}
script.
@mixin \i
@uploads = []
@upload = (file, folder) ~>
id = Math.random!
ctx =
id: id
name: file.name || \untitled
progress: undefined
@uploads.push ctx
@trigger \change-uploads @uploads
@update!
reader = new FileReader!
reader.onload = (e) ~>
ctx.img = e.target.result
@update!
reader.read-as-data-URL file
data = new FormData!
data.append \i @I.token
data.append \file file
if folder?
data.append \folder_id folder
xhr = new XMLHttpRequest!
xhr.open \POST CONFIG.api.url + '/drive/files/create' true
xhr.onload = (e) ~>
drive-file = JSON.parse e.target.response
@trigger \uploaded drive-file
@uploads = @uploads.filter (x) -> x.id != id
@trigger \change-uploads @uploads
@update!
xhr.upload.onprogress = (e) ~>
if e.length-computable
if ctx.progress == undefined
ctx.progress = {}
ctx.progress.max = e.total
ctx.progress.value = e.loaded
@update!
xhr.send data

View file

@ -0,0 +1,105 @@
mk-url-preview
a(href={ url }, target='_blank', title={ url }, if={ !loading })
div.thumbnail(if={ thumbnail }, style={ 'background-image: url(' + thumbnail + ')' })
article
header: h1 { title }
p { description }
footer
img.icon(if={ icon }, src={ icon })
p { sitename }
style.
display block
font-size 16px
> a
display block
border solid 1px #eee
border-radius 4px
overflow hidden
&:hover
text-decoration none
border-color #ddd
> article > header > h1
text-decoration underline
> .thumbnail
position absolute
width 100px
height 100%
background-position center
background-size cover
& + article
left 100px
width calc(100% - 100px)
> article
padding 16px
> header
margin-bottom 8px
> h1
margin 0
font-size 1em
color #555
> p
margin 0
color #777
font-size 0.8em
> footer
margin-top 8px
> img
display inline-block
width 16px
heigth 16px
margin-right 4px
vertical-align bottom
> p
display inline-block
margin 0
color #666
font-size 0.8em
line-height 16px
@media (max-width 500px)
font-size 8px
> a
border none
> .thumbnail
width 70px
& + article
left 70px
width calc(100% - 70px)
> article
padding 8px
script.
@mixin \api
@url = @opts.url
@loading = true
@on \mount ~>
fetch CONFIG.url + '/api:url?url=' + @url
.then (res) ~>
info <~ res.json!.then
@title = info.title
@description = info.description
@thumbnail = info.thumbnail
@icon = info.icon
@sitename = info.sitename
@loading = false
@update!

View file

@ -0,0 +1,50 @@
mk-url
a(href={ url }, target={ opts.target })
span.schema { schema }//
span.hostname { hostname }
span.port(if={ port != '' }) :{ port }
span.pathname(if={ pathname != '' }) { pathname }
span.query { query }
span.hash { hash }
style.
> a
&:after
content "\f14c"
display inline-block
padding-left 2px
font-family FontAwesome
font-size .9em
font-weight 400
font-style normal
> .schema
opacity 0.5
> .hostname
font-weight bold
> .pathname
opacity 0.8
> .query
opacity 0.5
> .hash
font-style italic
script.
@url = @opts.href
@on \before-mount ~>
parser = document.create-element \a
parser.href = @url
@schema = parser.protocol
@hostname = parser.hostname
@port = parser.port
@pathname = parser.pathname
@query = parser.search
@hash = parser.hash
@update!