Initial commit 🍀
This commit is contained in:
commit
b3f42e62af
405 changed files with 31017 additions and 0 deletions
40
src/web/app/common/mixins.ls
Normal file
40
src/web/app/common/mixins.ls
Normal 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'
|
||||
13
src/web/app/common/pages/about/base.pug
Normal file
13
src/web/app/common/pages/about/base.pug
Normal 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
|
||||
13
src/web/app/common/pages/about/pages/staff.pug
Normal file
13
src/web/app/common/pages/about/pages/staff.pug
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
extends ../base
|
||||
|
||||
block title
|
||||
| スタッフ | Misskey
|
||||
|
||||
block header
|
||||
| スタッフ
|
||||
|
||||
block content
|
||||
div.members
|
||||
div.member
|
||||
p しゅいろ
|
||||
p 統括、設計、グラフィックデザイン、プログラム
|
||||
67
src/web/app/common/scripts/api.ls
Normal file
67
src/web/app/common/scripts/api.ls
Normal 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
|
||||
6
src/web/app/common/scripts/bytes-to-size.js
Normal file
6
src/web/app/common/scripts/bytes-to-size.js
Normal 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];
|
||||
}
|
||||
9
src/web/app/common/scripts/check-for-update.ls
Normal file
9
src/web/app/common/scripts/check-for-update.ls
Normal 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
|
||||
14
src/web/app/common/scripts/date-stringify.ls
Normal file
14
src/web/app/common/scripts/date-stringify.ls
Normal 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
|
||||
27
src/web/app/common/scripts/generate-default-userdata.ls
Normal file
27
src/web/app/common/scripts/generate-default-userdata.ls
Normal 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
|
||||
26
src/web/app/common/scripts/get-post-summary.ls
Normal file
26
src/web/app/common/scripts/get-post-summary.ls
Normal 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
|
||||
16
src/web/app/common/scripts/i.ls
Normal file
16
src/web/app/common/scripts/i.ls
Normal 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
|
||||
1
src/web/app/common/scripts/is-promise.ls
Normal file
1
src/web/app/common/scripts/is-promise.ls
Normal file
|
|
@ -0,0 +1 @@
|
|||
module.exports = (x) -> typeof x.then == \function
|
||||
16
src/web/app/common/scripts/loading.ls
Normal file
16
src/web/app/common/scripts/loading.ls
Normal 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
|
||||
18
src/web/app/common/scripts/log.ls
Normal file
18
src/web/app/common/scripts/log.ls
Normal 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
|
||||
34
src/web/app/common/scripts/messaging-stream.ls
Normal file
34
src/web/app/common/scripts/messaging-stream.ls
Normal 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
|
||||
4
src/web/app/common/scripts/signout.ls
Normal file
4
src/web/app/common/scripts/signout.ls
Normal 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 = \/
|
||||
42
src/web/app/common/scripts/stream.ls
Normal file
42
src/web/app/common/scripts/stream.ls
Normal 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
|
||||
}
|
||||
30
src/web/app/common/scripts/text-compiler.js
Normal file
30
src/web/app/common/scripts/text-compiler.js
Normal 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, '>')
|
||||
.replace(/</g, '<')
|
||||
.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('');
|
||||
}
|
||||
12
src/web/app/common/scripts/uuid.js
Normal file
12
src/web/app/common/scripts/uuid.js
Normal 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;
|
||||
}
|
||||
16
src/web/app/common/tags.ls
Normal file
16
src/web/app/common/tags.ls
Normal 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'
|
||||
5
src/web/app/common/tags/copyright.tag
Normal file
5
src/web/app/common/tags/copyright.tag
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
mk-copyright
|
||||
span (c) syuilo 2014-2016
|
||||
|
||||
style.
|
||||
display block
|
||||
63
src/web/app/common/tags/core-error.tag
Normal file
63
src/web/app/common/tags/core-error.tag
Normal 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!
|
||||
25
src/web/app/common/tags/ellipsis.tag
Normal file
25
src/web/app/common/tags/ellipsis.tag
Normal 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
|
||||
9
src/web/app/common/tags/file-type-icon.tag
Normal file
9
src/web/app/common/tags/file-type-icon.tag
Normal 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
|
||||
37
src/web/app/common/tags/forkit.tag
Normal file
37
src/web/app/common/tags/forkit.tag
Normal 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)
|
||||
22
src/web/app/common/tags/introduction.tag
Normal file
22
src/web/app/common/tags/introduction.tag
Normal 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
|
||||
15
src/web/app/common/tags/number.tag
Normal file
15
src/web/app/common/tags/number.tag
Normal 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!
|
||||
7
src/web/app/common/tags/raw.tag
Normal file
7
src/web/app/common/tags/raw.tag
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
mk-raw
|
||||
|
||||
style.
|
||||
display inline
|
||||
|
||||
script.
|
||||
@root.innerHTML = @opts.content
|
||||
24
src/web/app/common/tags/ripple-string.tag
Normal file
24
src/web/app/common/tags/ripple-string.tag
Normal 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
|
||||
136
src/web/app/common/tags/signin.tag
Normal file
136
src/web/app/common/tags/signin.tag
Normal 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
|
||||
352
src/web/app/common/tags/signup.tag
Normal file
352
src/web/app/common/tags/signup.tag
Normal 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
|
||||
24
src/web/app/common/tags/special-message.tag
Normal file
24
src/web/app/common/tags/special-message.tag
Normal 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
|
||||
43
src/web/app/common/tags/time.tag
Normal file
43
src/web/app/common/tags/time.tag
Normal 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!
|
||||
201
src/web/app/common/tags/uploader.tag
Normal file
201
src/web/app/common/tags/uploader.tag
Normal 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
|
||||
105
src/web/app/common/tags/url-preview.tag
Normal file
105
src/web/app/common/tags/url-preview.tag
Normal 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!
|
||||
50
src/web/app/common/tags/url.tag
Normal file
50
src/web/app/common/tags/url.tag
Normal 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!
|
||||
Loading…
Add table
Add a link
Reference in a new issue