From b3f42e62af698a67c2250533c437569559f1fdf9 Mon Sep 17 00:00:00 2001 From: syuilo Date: Thu, 29 Dec 2016 07:49:51 +0900 Subject: Initial commit :four_leaf_clover: --- src/web/app/common/mixins.ls | 40 +++ src/web/app/common/pages/about/base.pug | 13 + src/web/app/common/pages/about/pages/staff.pug | 13 + src/web/app/common/scripts/api.ls | 67 ++++ src/web/app/common/scripts/bytes-to-size.js | 6 + src/web/app/common/scripts/check-for-update.ls | 9 + src/web/app/common/scripts/date-stringify.ls | 14 + .../common/scripts/generate-default-userdata.ls | 27 ++ src/web/app/common/scripts/get-post-summary.ls | 26 ++ src/web/app/common/scripts/i.ls | 16 + src/web/app/common/scripts/is-promise.ls | 1 + src/web/app/common/scripts/loading.ls | 16 + src/web/app/common/scripts/log.ls | 18 ++ src/web/app/common/scripts/messaging-stream.ls | 34 ++ src/web/app/common/scripts/signout.ls | 4 + src/web/app/common/scripts/stream.ls | 42 +++ src/web/app/common/scripts/text-compiler.js | 30 ++ src/web/app/common/scripts/uuid.js | 12 + src/web/app/common/tags.ls | 16 + src/web/app/common/tags/copyright.tag | 5 + src/web/app/common/tags/core-error.tag | 63 ++++ src/web/app/common/tags/ellipsis.tag | 25 ++ src/web/app/common/tags/file-type-icon.tag | 9 + src/web/app/common/tags/forkit.tag | 37 +++ src/web/app/common/tags/introduction.tag | 22 ++ src/web/app/common/tags/number.tag | 15 + src/web/app/common/tags/raw.tag | 7 + src/web/app/common/tags/ripple-string.tag | 24 ++ src/web/app/common/tags/signin.tag | 136 ++++++++ src/web/app/common/tags/signup.tag | 352 +++++++++++++++++++++ src/web/app/common/tags/special-message.tag | 24 ++ src/web/app/common/tags/time.tag | 43 +++ src/web/app/common/tags/uploader.tag | 201 ++++++++++++ src/web/app/common/tags/url-preview.tag | 105 ++++++ src/web/app/common/tags/url.tag | 50 +++ 35 files changed, 1522 insertions(+) create mode 100644 src/web/app/common/mixins.ls create mode 100644 src/web/app/common/pages/about/base.pug create mode 100644 src/web/app/common/pages/about/pages/staff.pug create mode 100644 src/web/app/common/scripts/api.ls create mode 100644 src/web/app/common/scripts/bytes-to-size.js create mode 100644 src/web/app/common/scripts/check-for-update.ls create mode 100644 src/web/app/common/scripts/date-stringify.ls create mode 100644 src/web/app/common/scripts/generate-default-userdata.ls create mode 100644 src/web/app/common/scripts/get-post-summary.ls create mode 100644 src/web/app/common/scripts/i.ls create mode 100644 src/web/app/common/scripts/is-promise.ls create mode 100644 src/web/app/common/scripts/loading.ls create mode 100644 src/web/app/common/scripts/log.ls create mode 100644 src/web/app/common/scripts/messaging-stream.ls create mode 100644 src/web/app/common/scripts/signout.ls create mode 100644 src/web/app/common/scripts/stream.ls create mode 100644 src/web/app/common/scripts/text-compiler.js create mode 100644 src/web/app/common/scripts/uuid.js create mode 100644 src/web/app/common/tags.ls create mode 100644 src/web/app/common/tags/copyright.tag create mode 100644 src/web/app/common/tags/core-error.tag create mode 100644 src/web/app/common/tags/ellipsis.tag create mode 100644 src/web/app/common/tags/file-type-icon.tag create mode 100644 src/web/app/common/tags/forkit.tag create mode 100644 src/web/app/common/tags/introduction.tag create mode 100644 src/web/app/common/tags/number.tag create mode 100644 src/web/app/common/tags/raw.tag create mode 100644 src/web/app/common/tags/ripple-string.tag create mode 100644 src/web/app/common/tags/signin.tag create mode 100644 src/web/app/common/tags/signup.tag create mode 100644 src/web/app/common/tags/special-message.tag create mode 100644 src/web/app/common/tags/time.tag create mode 100644 src/web/app/common/tags/uploader.tag create mode 100644 src/web/app/common/tags/url-preview.tag create mode 100644 src/web/app/common/tags/url.tag (limited to 'src/web/app/common') diff --git a/src/web/app/common/mixins.ls b/src/web/app/common/mixins.ls new file mode 100644 index 0000000000..1320cacd1e --- /dev/null +++ b/src/web/app/common/mixins.ls @@ -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' diff --git a/src/web/app/common/pages/about/base.pug b/src/web/app/common/pages/about/base.pug new file mode 100644 index 0000000000..0bac19ee2b --- /dev/null +++ b/src/web/app/common/pages/about/base.pug @@ -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 diff --git a/src/web/app/common/pages/about/pages/staff.pug b/src/web/app/common/pages/about/pages/staff.pug new file mode 100644 index 0000000000..dfdf015a3d --- /dev/null +++ b/src/web/app/common/pages/about/pages/staff.pug @@ -0,0 +1,13 @@ +extends ../base + +block title + | スタッフ | Misskey + +block header + | スタッフ + +block content + div.members + div.member + p しゅいろ + p 統括、設計、グラフィックデザイン、プログラム \ No newline at end of file diff --git a/src/web/app/common/scripts/api.ls b/src/web/app/common/scripts/api.ls new file mode 100644 index 0000000000..0656a56168 --- /dev/null +++ b/src/web/app/common/scripts/api.ls @@ -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 diff --git a/src/web/app/common/scripts/bytes-to-size.js b/src/web/app/common/scripts/bytes-to-size.js new file mode 100644 index 0000000000..717f9ad507 --- /dev/null +++ b/src/web/app/common/scripts/bytes-to-size.js @@ -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]; +} diff --git a/src/web/app/common/scripts/check-for-update.ls b/src/web/app/common/scripts/check-for-update.ls new file mode 100644 index 0000000000..48e250a4c7 --- /dev/null +++ b/src/web/app/common/scripts/check-for-update.ls @@ -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 diff --git a/src/web/app/common/scripts/date-stringify.ls b/src/web/app/common/scripts/date-stringify.ls new file mode 100644 index 0000000000..9aa8b3e6c5 --- /dev/null +++ b/src/web/app/common/scripts/date-stringify.ls @@ -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 diff --git a/src/web/app/common/scripts/generate-default-userdata.ls b/src/web/app/common/scripts/generate-default-userdata.ls new file mode 100644 index 0000000000..de03e96151 --- /dev/null +++ b/src/web/app/common/scripts/generate-default-userdata.ls @@ -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 diff --git a/src/web/app/common/scripts/get-post-summary.ls b/src/web/app/common/scripts/get-post-summary.ls new file mode 100644 index 0000000000..0150d53004 --- /dev/null +++ b/src/web/app/common/scripts/get-post-summary.ls @@ -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 diff --git a/src/web/app/common/scripts/i.ls b/src/web/app/common/scripts/i.ls new file mode 100644 index 0000000000..5f3c016f8a --- /dev/null +++ b/src/web/app/common/scripts/i.ls @@ -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 diff --git a/src/web/app/common/scripts/is-promise.ls b/src/web/app/common/scripts/is-promise.ls new file mode 100644 index 0000000000..e3c7adff85 --- /dev/null +++ b/src/web/app/common/scripts/is-promise.ls @@ -0,0 +1 @@ +module.exports = (x) -> typeof x.then == \function diff --git a/src/web/app/common/scripts/loading.ls b/src/web/app/common/scripts/loading.ls new file mode 100644 index 0000000000..ed791b21ac --- /dev/null +++ b/src/web/app/common/scripts/loading.ls @@ -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 diff --git a/src/web/app/common/scripts/log.ls b/src/web/app/common/scripts/log.ls new file mode 100644 index 0000000000..6e1e3735d8 --- /dev/null +++ b/src/web/app/common/scripts/log.ls @@ -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 diff --git a/src/web/app/common/scripts/messaging-stream.ls b/src/web/app/common/scripts/messaging-stream.ls new file mode 100644 index 0000000000..298285dc93 --- /dev/null +++ b/src/web/app/common/scripts/messaging-stream.ls @@ -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 diff --git a/src/web/app/common/scripts/signout.ls b/src/web/app/common/scripts/signout.ls new file mode 100644 index 0000000000..a647922678 --- /dev/null +++ b/src/web/app/common/scripts/signout.ls @@ -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 = \/ diff --git a/src/web/app/common/scripts/stream.ls b/src/web/app/common/scripts/stream.ls new file mode 100644 index 0000000000..534048248f --- /dev/null +++ b/src/web/app/common/scripts/stream.ls @@ -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 + } diff --git a/src/web/app/common/scripts/text-compiler.js b/src/web/app/common/scripts/text-compiler.js new file mode 100644 index 0000000000..9915e3335f --- /dev/null +++ b/src/web/app/common/scripts/text-compiler.js @@ -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(/' : ' '); + } else { + return token.content + .replace(/(\r\n|\n|\r)/g, canBreak ? '
' : ' '); + } + case 'bold': + return '' + token.bold + ''; + case 'link': + return ''; + case 'mention': + return '' + token.content + ''; + case 'hashtag': // TODO + return '' + token.content + ''; + } + }).join(''); +} diff --git a/src/web/app/common/scripts/uuid.js b/src/web/app/common/scripts/uuid.js new file mode 100644 index 0000000000..6161190d63 --- /dev/null +++ b/src/web/app/common/scripts/uuid.js @@ -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; +} diff --git a/src/web/app/common/tags.ls b/src/web/app/common/tags.ls new file mode 100644 index 0000000000..fe71a7bb37 --- /dev/null +++ b/src/web/app/common/tags.ls @@ -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' diff --git a/src/web/app/common/tags/copyright.tag b/src/web/app/common/tags/copyright.tag new file mode 100644 index 0000000000..74acae4df7 --- /dev/null +++ b/src/web/app/common/tags/copyright.tag @@ -0,0 +1,5 @@ +mk-copyright + span (c) syuilo 2014-2016 + +style. + display block diff --git a/src/web/app/common/tags/core-error.tag b/src/web/app/common/tags/core-error.tag new file mode 100644 index 0000000000..19ef68bea6 --- /dev/null +++ b/src/web/app/common/tags/core-error.tag @@ -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! diff --git a/src/web/app/common/tags/ellipsis.tag b/src/web/app/common/tags/ellipsis.tag new file mode 100644 index 0000000000..47eca62acd --- /dev/null +++ b/src/web/app/common/tags/ellipsis.tag @@ -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 diff --git a/src/web/app/common/tags/file-type-icon.tag b/src/web/app/common/tags/file-type-icon.tag new file mode 100644 index 0000000000..68b8f95ad7 --- /dev/null +++ b/src/web/app/common/tags/file-type-icon.tag @@ -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 diff --git a/src/web/app/common/tags/forkit.tag b/src/web/app/common/tags/forkit.tag new file mode 100644 index 0000000000..7205fbe76b --- /dev/null +++ b/src/web/app/common/tags/forkit.tag @@ -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) diff --git a/src/web/app/common/tags/introduction.tag b/src/web/app/common/tags/introduction.tag new file mode 100644 index 0000000000..962f195cca --- /dev/null +++ b/src/web/app/common/tags/introduction.tag @@ -0,0 +1,22 @@ +mk-introduction + article + h1 Misskeyとは? +

Misskeyみすきーは、syuiloが2014年くらいからオープンソースで開発・運営を行っている、ミニブログベースのSNSです。

+

Twitter, Facebook, LINE, Google+ などをパクって参考にしています。

+

無料で誰でも利用でき、広告なども一切ありません。

+

もっと知りたい方はこちら

+ +style. + display block + + h1 + margin 0 + text-align center + font-size 1.2em + + p + margin 16px 0 + + &:last-child + margin 0 + text-align center diff --git a/src/web/app/common/tags/number.tag b/src/web/app/common/tags/number.tag new file mode 100644 index 0000000000..589c747b35 --- /dev/null +++ b/src/web/app/common/tags/number.tag @@ -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! diff --git a/src/web/app/common/tags/raw.tag b/src/web/app/common/tags/raw.tag new file mode 100644 index 0000000000..131826e597 --- /dev/null +++ b/src/web/app/common/tags/raw.tag @@ -0,0 +1,7 @@ +mk-raw + +style. + display inline + +script. + @root.innerHTML = @opts.content diff --git a/src/web/app/common/tags/ripple-string.tag b/src/web/app/common/tags/ripple-string.tag new file mode 100644 index 0000000000..3be6903369 --- /dev/null +++ b/src/web/app/common/tags/ripple-string.tag @@ -0,0 +1,24 @@ +mk-ripple-string + + +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 diff --git a/src/web/app/common/tags/signin.tag b/src/web/app/common/tags/signin.tag new file mode 100644 index 0000000000..6f4013b1cb --- /dev/null +++ b/src/web/app/common/tags/signin.tag @@ -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 diff --git a/src/web/app/common/tags/signup.tag b/src/web/app/common/tags/signup.tag new file mode 100644 index 0000000000..730f00fb4e --- /dev/null +++ b/src/web/app/common/tags/signup.tag @@ -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 diff --git a/src/web/app/common/tags/special-message.tag b/src/web/app/common/tags/special-message.tag new file mode 100644 index 0000000000..5a6d5787ea --- /dev/null +++ b/src/web/app/common/tags/special-message.tag @@ -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 diff --git a/src/web/app/common/tags/time.tag b/src/web/app/common/tags/time.tag new file mode 100644 index 0000000000..56c3b8ecc3 --- /dev/null +++ b/src/web/app/common/tags/time.tag @@ -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! diff --git a/src/web/app/common/tags/uploader.tag b/src/web/app/common/tags/uploader.tag new file mode 100644 index 0000000000..6d4e9b6363 --- /dev/null +++ b/src/web/app/common/tags/uploader.tag @@ -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 diff --git a/src/web/app/common/tags/url-preview.tag b/src/web/app/common/tags/url-preview.tag new file mode 100644 index 0000000000..605d26bc67 --- /dev/null +++ b/src/web/app/common/tags/url-preview.tag @@ -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! diff --git a/src/web/app/common/tags/url.tag b/src/web/app/common/tags/url.tag new file mode 100644 index 0000000000..18892e8108 --- /dev/null +++ b/src/web/app/common/tags/url.tag @@ -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! -- cgit v1.2.3-freya