diff options
Diffstat (limited to 'src/web/app/desktop/scripts')
21 files changed, 469 insertions, 501 deletions
diff --git a/src/web/app/desktop/scripts/autocomplete.js b/src/web/app/desktop/scripts/autocomplete.js new file mode 100644 index 0000000000..54985874d6 --- /dev/null +++ b/src/web/app/desktop/scripts/autocomplete.js @@ -0,0 +1,124 @@ +const getCaretCoordinates = require('textarea-caret'); +const riot = require('riot'); + +/** + * オートコンプリートを管理するクラス。 + */ +class Autocomplete { + + /** + * 対象のテキストエリアを与えてインスタンスを初期化します。 + */ + constructor(textarea) { + this.suggestion = null; + this.textarea = textarea; + } + + /** + * このインスタンスにあるテキストエリアの入力のキャプチャを開始します。 + */ + attach() { + this.textarea.addEventListener('input', this.onInput); + } + + /** + * このインスタンスにあるテキストエリアの入力のキャプチャを解除します。 + */ + detach() { + this.textarea.removeEventListener('input', this.onInput); + this.close(); + } + + /** + * [Private] テキスト入力時 + */ + onInput() { + this.close(); + + const caret = this.textarea.selectionStart; + const text = this.textarea.value.substr(0, caret); + + const mentionIndex = text.lastIndexOf('@'); + + if (mentionIndex == -1) return; + + const username = text.substr(mentionIndex + 1); + + if (!username.match(/^[a-zA-Z0-9-]+$/)) return; + + this.open('user', username); + } + + /** + * [Private] サジェストを提示します。 + */ + open(type, q) { + // 既に開いているサジェストは閉じる + this.close(); + + // サジェスト要素作成 + const suggestion = document.createElement('mk-autocomplete-suggestion'); + + // ~ サジェストを表示すべき位置を計算 ~ + + const caretPosition = getCaretCoordinates(this.textarea, this.textarea.selectionStart); + + const rect = this.textarea.getBoundingClientRect(); + + const x = rect.left + window.pageXOffset + caretPosition.left; + const y = rect.top + window.pageYOffset + caretPosition.top; + + suggestion.style.left = x + 'px'; + suggestion.style.top = y + 'px'; + + // 要素追加 + const el = document.body.appendChild(suggestion); + + // マウント + this.suggestion = riot.mount(el, { + textarea: this.textarea, + complete: this.complete, + close: this.close, + type: type, + q: q + })[0]; + } + + /** + * [Private] サジェストを閉じます。 + */ + close() { + if (this.suggestion == nul) return; + + this.suggestion.unmount(); + this.suggestion = null; + + this.textarea.focus(); + } + + /** + * [Private] オートコンプリートする + */ + complete(user) { + this.close(); + + const value = user.username; + + const caret = this.textarea.selectionStart; + const source = this.textarea.value; + + const before = source.substr(0, caret); + const trimedBefore = before.substring(0, before.lastIndexOf('@')); + const after = source.substr(caret); + + // 結果を挿入する + this.textarea.value = trimedBefore + '@' + value + ' ' + after; + + // キャレットを戻す + this.textarea.focus(); + const pos = caret + value.length; + this.textarea.setSelectionRange(pos, pos); + } +} + +module.exports = Autocomplete; diff --git a/src/web/app/desktop/scripts/autocomplete.ls b/src/web/app/desktop/scripts/autocomplete.ls deleted file mode 100644 index 391fb312e3..0000000000 --- a/src/web/app/desktop/scripts/autocomplete.ls +++ /dev/null @@ -1,108 +0,0 @@ -# Autocomplete -#================================ - -get-caret-coordinates = require 'textarea-caret' -riot = require 'riot' - -# オートコンプリートを管理するクラスです。 -class Autocomplete - - @textarea = null - @suggestion = null - - # 対象のテキストエリアを与えてインスタンスを初期化します。 - (textarea) ~> - @textarea = textarea - - # このインスタンスにあるテキストエリアの入力のキャプチャを開始します。 - attach: ~> - @textarea.add-event-listener \input @on-input - - # このインスタンスにあるテキストエリアの入力のキャプチャを解除します。 - detach: ~> - @textarea.remove-event-listener \input @on-input - @close! - - # テキスト入力時 - on-input: ~> - @close! - - caret = @textarea.selection-start - text = @textarea.value.substr 0 caret - - mention-index = text.last-index-of \@ - - if mention-index == -1 - return - - username = text.substr mention-index + 1 - - if not username.match /^[a-zA-Z0-9-]+$/ - return - - @open \user username - - # サジェストを提示します。 - open: (type, q) ~> - # 既に開いているサジェストは閉じる - @close! - - # サジェスト要素作成 - suggestion = document.create-element \mk-autocomplete-suggestion - - # ~ サジェストを表示すべき位置を計算 ~ - - caret-position = get-caret-coordinates @textarea, @textarea.selection-start - - rect = @textarea.get-bounding-client-rect! - - x = rect.left + window.page-x-offset + caret-position.left - y = rect.top + window.page-y-offset + caret-position.top - - suggestion.style.left = x + \px - suggestion.style.top = y + \px - - # 要素追加 - el = document.body.append-child suggestion - - # マウント - mounted = riot.mount el, do - textarea: @textarea - complete: @complete - close: @close - type: type - q: q - - @suggestion = mounted.0 - - # サジェストを閉じます。 - close: ~> - if !@suggestion? - return - - @suggestion.unmount! - @suggestion = null - - @textarea.focus! - - # オートコンプリートする - complete: (user) ~> - @close! - value = user.username - - caret = @textarea.selection-start - source = @textarea.value - - before = source.substr 0 caret - trimed-before = before.substring 0 before.last-index-of \@ - after = source.substr caret - - # 結果を挿入する - @textarea.value = trimed-before + \@ + value + ' ' + after - - # キャレットを戻す - @textarea.focus! - pos = caret + value.length - @textarea.set-selection-range pos, pos - -module.exports = Autocomplete diff --git a/src/web/app/desktop/scripts/dialog.js b/src/web/app/desktop/scripts/dialog.js new file mode 100644 index 0000000000..6fe7b6e8d7 --- /dev/null +++ b/src/web/app/desktop/scripts/dialog.js @@ -0,0 +1,16 @@ +const riot = require('riot'); + +module.exports = (title, text, buttons, canThrough, onThrough) => { + const dialog = document.body.appendChild(document.createElement('mk-dialog')); + const controller = riot.observable(); + riot.mount(dialog, { + controller: controller, + title: title, + text: text, + buttons: buttons, + canThrough: canThrough, + onThrough: onThrough + }); + controller.trigger('open'); + return controller; +}; diff --git a/src/web/app/desktop/scripts/dialog.ls b/src/web/app/desktop/scripts/dialog.ls deleted file mode 100644 index f3dd6cea1b..0000000000 --- a/src/web/app/desktop/scripts/dialog.ls +++ /dev/null @@ -1,17 +0,0 @@ -# Dialog -#================================ - -riot = require 'riot' - -module.exports = (title, text, buttons, can-through, on-through) ~> - dialog = document.body.append-child document.create-element \mk-dialog - controller = riot.observable! - riot.mount dialog, do - controller: controller - title: title - text: text - buttons: buttons - can-through: can-through - on-through: on-through - controller.trigger \open - return controller diff --git a/src/web/app/desktop/scripts/follow-scroll.ls b/src/web/app/desktop/scripts/follow-scroll.ls deleted file mode 100644 index 5072e9c583..0000000000 --- a/src/web/app/desktop/scripts/follow-scroll.ls +++ /dev/null @@ -1,56 +0,0 @@ -class Follower - (el) -> - @follower = el - @last-scroll-top = window.scroll-y - @initial-follower-top = @follower.get-bounding-client-rect!.top - @page-top = 48 - - follow: -> - window-height = window.inner-height - follower-height = @follower.offset-height - - scroll-top = window.scroll-y - scroll-bottom = scroll-top + window-height - - follower-top = @follower.get-bounding-client-rect!.top + scroll-top - follower-bottom = follower-top + follower-height - - height-delta = Math.abs window-height - follower-height - scroll-delta = @last-scroll-top - scroll-top - - is-scrolling-down = (scroll-top > @last-scroll-top) - is-window-larger = (window-height > follower-height) - - console.log @initial-follower-top - - if (is-window-larger && scroll-top > @initial-follower-top) || (!is-window-larger && scroll-top > @initial-follower-top + height-delta) - @follower.class-list.add \fixed - else if !is-scrolling-down && scroll-top + @page-top <= @initial-follower-top - @follower.class-list.remove \fixed - @follower.style.top = 0 - return - - drag-bottom-down = (follower-bottom <= scroll-bottom && is-scrolling-down) - drag-top-up = (follower-top >= scroll-top + @page-top && !is-scrolling-down) - - if drag-bottom-down - console.log \down - @follower.style.top = if is-window-larger then 0 else -height-delta + \px - else if drag-top-up - console.log \up - @follower.style.top = @page-top + \px - else if @follower.class-list.contains \fixed - console.log \- - current-top = parse-int @follower.style.top, 10 - - min-top = -height-delta - scrolled-top = current-top + scroll-delta - - is-page-at-bottom = (scroll-top + window-height >= document.body.offset-height) - new-top = if is-page-at-bottom then min-top else scrolled-top - - @follower.style.top = new-top + \px - - @last-scroll-top = scroll-top - -module.exports = Follower diff --git a/src/web/app/desktop/scripts/fuck-ad-block.js b/src/web/app/desktop/scripts/fuck-ad-block.js new file mode 100644 index 0000000000..38208d34c0 --- /dev/null +++ b/src/web/app/desktop/scripts/fuck-ad-block.js @@ -0,0 +1,18 @@ +require('fuckadblock'); +const dialog = require('./dialog'); + +module.exports = () => { + if (fuckAdBlock === undefined) { + adBlockDetected(); + } else { + fuckAdBlock.onDetected(adBlockDetected); + } +}; + +function adBlockDetected() { + dialog('<i class="fa fa-exclamation-triangle"></i>広告ブロッカーを無効にしてください', + '<strong>Misskeyは広告を掲載していません</strong>が、広告をブロックする機能が有効だと一部の機能が利用できなかったり、不具合が発生する場合があります。', + [{ + text: 'OK' + }]); +} diff --git a/src/web/app/desktop/scripts/fuck-ad-block.ls b/src/web/app/desktop/scripts/fuck-ad-block.ls deleted file mode 100644 index 7990f58f1f..0000000000 --- a/src/web/app/desktop/scripts/fuck-ad-block.ls +++ /dev/null @@ -1,19 +0,0 @@ -# FUCK AD BLOCK -#================================ - -require \fuckadblock -dialog = require './dialog.ls' - -module.exports = ~> - if fuck-ad-block == undefined - ad-block-detected! - else - fuck-ad-block.on-detected ad-block-detected - -function ad-block-detected - dialog do - '<i class="fa fa-exclamation-triangle"></i>広告ブロッカーを無効にしてください' - '<strong>Misskeyは広告を掲載していません</strong>が、広告をブロックする機能が有効だと一部の機能が利用できなかったり、不具合が発生する場合があります。' - [ - text: \OK - ] diff --git a/src/web/app/desktop/scripts/input-dialog.js b/src/web/app/desktop/scripts/input-dialog.js new file mode 100644 index 0000000000..ab9c57401f --- /dev/null +++ b/src/web/app/desktop/scripts/input-dialog.js @@ -0,0 +1,12 @@ +const riot = require('riot'); + +module.exports = (title, placeholder, defaultValue, onOk, onCancel) => { + const dialog = document.body.appendChild(document.createElement('mk-input-dialog')); + return riot.mount(dialog, { + title: title, + placeholder: placeholder, + 'default': defaultValue, + onOk: onOk, + onCancel: onCancel + }); +}; diff --git a/src/web/app/desktop/scripts/input-dialog.ls b/src/web/app/desktop/scripts/input-dialog.ls deleted file mode 100644 index f75b12dd01..0000000000 --- a/src/web/app/desktop/scripts/input-dialog.ls +++ /dev/null @@ -1,13 +0,0 @@ -# Input Dialog -#================================ - -riot = require 'riot' - -module.exports = (title, placeholder, default-value, on-ok, on-cancel) ~> - dialog = document.body.append-child document.create-element \mk-input-dialog - riot.mount dialog, do - title: title - placeholder: placeholder - default: default-value - on-ok: on-ok - on-cancel: on-cancel diff --git a/src/web/app/desktop/scripts/notify.js b/src/web/app/desktop/scripts/notify.js new file mode 100644 index 0000000000..048f22aea7 --- /dev/null +++ b/src/web/app/desktop/scripts/notify.js @@ -0,0 +1,8 @@ +const riot = require('riot'); + +module.exports = message => { + const notification = document.body.appendChild(document.createElement('mk-ui-notification')); + riot.mount(notification, { + message: message + }); +}; diff --git a/src/web/app/desktop/scripts/notify.ls b/src/web/app/desktop/scripts/notify.ls deleted file mode 100644 index 919bbc3dcf..0000000000 --- a/src/web/app/desktop/scripts/notify.ls +++ /dev/null @@ -1,6 +0,0 @@ -riot = require \riot - -module.exports = (message) ~> - notification = document.body.append-child document.create-element \mk-ui-notification - riot.mount notification, do - message: message diff --git a/src/web/app/desktop/scripts/open-window.js b/src/web/app/desktop/scripts/open-window.js new file mode 100644 index 0000000000..3f7cc424e0 --- /dev/null +++ b/src/web/app/desktop/scripts/open-window.js @@ -0,0 +1,8 @@ +const riot = require('riot'); + +riot.mixin('open-window', { + openWindow: (name, opts) => { + const window = document.body.appendChild(document.createElement(name)); + return riot.mount(window, opts)[0]; + } +}); diff --git a/src/web/app/desktop/scripts/open-window.ls b/src/web/app/desktop/scripts/open-window.ls deleted file mode 100644 index 4388272ecf..0000000000 --- a/src/web/app/desktop/scripts/open-window.ls +++ /dev/null @@ -1,8 +0,0 @@ -riot = require \riot - -function open(name, opts) - window = document.body.append-child document.create-element name - riot.mount window, opts - -riot.mixin \open-window do - open-window: open diff --git a/src/web/app/desktop/scripts/stream.js b/src/web/app/desktop/scripts/stream.js new file mode 100644 index 0000000000..ea1548ecfd --- /dev/null +++ b/src/web/app/desktop/scripts/stream.js @@ -0,0 +1,45 @@ +const stream = require('../../common/scripts/stream'); +const getPostSummary = require('../../common/scripts/get-post-summary'); +const riot = require('riot'); + +module.exports = me => { + const s = stream(me); + + s.event.on('drive_file_created', file => { + const n = new Notification('ファイルがアップロードされました', { + body: file.name, + icon: file.url + '?thumbnail&size=64' + }); + setTimeout(n.close.bind(n), 5000); + }); + + s.event.on('mention', post => { + const n = new Notification(post.user.name + "さんから:", { + body: getPostSummary(post), + icon: post.user.avatar_url + '?thumbnail&size=64' + }); + setTimeout(n.close.bind(n), 6000); + }); + + s.event.on('reply', post => { + const n = new Notification(post.user.name + "さんから返信:", { + body: getPostSummary(post), + icon: post.user.avatar_url + '?thumbnail&size=64' + }); + setTimeout(n.close.bind(n), 6000); + }); + + s.event.on('quote', post => { + const n = new Notification(post.user.name + "さんが引用:", { + body: getPostSummary(post), + icon: post.user.avatar_url + '?thumbnail&size=64' + }); + setTimeout(n.close.bind(n), 6000); + }); + + riot.mixin('stream', { + stream: s.event, + getStreamState: s.getState, + streamStateEv: s.stateEv + }); +}; diff --git a/src/web/app/desktop/scripts/stream.ls b/src/web/app/desktop/scripts/stream.ls deleted file mode 100644 index f84d6097a7..0000000000 --- a/src/web/app/desktop/scripts/stream.ls +++ /dev/null @@ -1,38 +0,0 @@ -# Stream -#================================ - -stream = require '../../common/scripts/stream.ls' -get-post-summary = require '../../common/scripts/get-post-summary.ls' -riot = require \riot - -module.exports = (me) ~> - s = stream me - - s.event.on \drive_file_created (file) ~> - n = new Notification 'ファイルがアップロードされました' do - body: file.name - icon: file.url + '?thumbnail&size=64' - set-timeout (n.close.bind n), 5000ms - - s.event.on \mention (post) ~> - n = new Notification "#{post.user.name}さんから:" do - body: get-post-summary post - icon: post.user.avatar_url + '?thumbnail&size=64' - set-timeout (n.close.bind n), 6000ms - - s.event.on \reply (post) ~> - n = new Notification "#{post.user.name}さんから返信:" do - body: get-post-summary post - icon: post.user.avatar_url + '?thumbnail&size=64' - set-timeout (n.close.bind n), 6000ms - - s.event.on \quote (post) ~> - n = new Notification "#{post.user.name}さんが引用:" do - body: get-post-summary post - icon: post.user.avatar_url + '?thumbnail&size=64' - set-timeout (n.close.bind n), 6000ms - - riot.mixin \stream do - stream: s.event - get-stream-state: s.get-state - stream-state-ev: s.state-ev diff --git a/src/web/app/desktop/scripts/update-avatar.js b/src/web/app/desktop/scripts/update-avatar.js new file mode 100644 index 0000000000..ad971f1b11 --- /dev/null +++ b/src/web/app/desktop/scripts/update-avatar.js @@ -0,0 +1,86 @@ +const riot = require('riot'); +const dialog = require('./dialog'); +const api = require('../../common/scripts/api'); + +module.exports = (I, cb, file = null) => { + const fileSelected = file => { + const cropper = riot.mount(document.body.appendChild(document.createElement('mk-crop-window')), { + file: file, + title: 'アバターとして表示する部分を選択', + aspectRatio: 1 / 1 + })[0]; + + cropper.on('cropped', blob => { + const data = new FormData(); + data.append('i', I.token); + data.append('file', blob, file.name + '.cropped.png'); + + api(I, 'drive/folders/find', { + name: 'アイコン' + }).then(iconFolder => { + if (iconFolder.length === 0) { + api(I, 'drive/folders/create', { + name: 'アイコン' + }).then(iconFolder => { + uplaod(data, iconFolder); + }); + } else { + uplaod(data, iconFolder[0]); + } + }); + }); + + cropper.on('skiped', () => { + set(file); + }); + }; + + const uplaod = (data, folder) => { + const progress = riot.mount(document.body.appendChild(document.createElement('mk-progress-dialog')), { + title: '新しいアバターをアップロードしています' + })[0]; + + if (folder) data.append('folder_id', folder.id); + + const xhr = new XMLHttpRequest(); + xhr.open('POST', CONFIG.api.url + '/drive/files/create', true); + xhr.onload = e => { + const file = JSON.parse(e.target.response); + progress.close(); + set(file); + }; + + xhr.upload.onprogress = e => { + if (e.lengthComputable) progress.updateProgress(e.loaded, e.total); + }; + + xhr.send(data); + }; + + const set = file => { + api(I, 'i/update', { + avatar_id: file.id + }).then(i => { + dialog('<i class="fa fa-info-circle"></i>アバターを更新しました', + '新しいアバターが反映されるまで時間がかかる場合があります。', + [{ + text: 'わかった' + }]); + + if (cb) cb(i); + }); + }; + + if (file) { + fileSelected(file); + } else { + const browser = riot.mount(document.body.appendChild(document.createElement('mk-select-file-from-drive-window')), { + multiple: false, + title: '<i class="fa fa-picture-o"></i>アバターにする画像を選択' + })[0]; + + browser.one('selected', file => { + fileSelected(file); + }); + } +}; diff --git a/src/web/app/desktop/scripts/update-avatar.ls b/src/web/app/desktop/scripts/update-avatar.ls deleted file mode 100644 index 351e54fe51..0000000000 --- a/src/web/app/desktop/scripts/update-avatar.ls +++ /dev/null @@ -1,81 +0,0 @@ -# Update Avatar -#================================ - -riot = require 'riot' -dialog = require './dialog.ls' -api = require '../../common/scripts/api' - -module.exports = (I, cb, file = null) ~> - - @file-selected = (file) ~> - cropper = document.body.append-child document.create-element \mk-crop-window - cropper = riot.mount cropper, do - file: file - title: 'アバターとして表示する部分を選択' - aspect-ratio: 1 / 1 - .0 - cropper.on \cropped (blob) ~> - data = new FormData! - data.append \i I.token - data.append \file blob, file.name + '.cropped.png' - api I, \drive/folders/find do - name: 'アイコン' - .then (icon-folder) ~> - if icon-folder.length == 0 - api I, \drive/folders/create do - name: 'アイコン' - .then (icon-folder) ~> - @uplaod data, icon-folder - else - @uplaod data, icon-folder.0 - cropper.on \skiped ~> - @set file - - @uplaod = (data, folder) ~> - - progress = document.body.append-child document.create-element \mk-progress-dialog - progress = riot.mount progress, do - title: '新しいアバターをアップロードしています' - .0 - - if folder? - data.append \folder_id folder.id - - xhr = new XMLHttpRequest! - xhr.open \POST CONFIG.api.url + \/drive/files/create true - xhr.onload = (e) ~> - file = JSON.parse e.target.response - progress.close! - @set file - - xhr.upload.onprogress = (e) ~> - if e.length-computable - progress.update-progress e.loaded, e.total - - xhr.send data - - @set = (file) ~> - api I, \i/update do - avatar_id: file.id - .then (i) ~> - dialog do - '<i class="fa fa-info-circle"></i>アバターを更新しました' - '新しいアバターが反映されるまで時間がかかる場合があります。' - [ - text: \わかった - ] - if cb? then cb i - .catch (err) ~> - console.error err - #@opts.ui.trigger \notification 'Error!' - - if file? - @file-selected file - else - browser = document.body.append-child document.create-element \mk-select-file-from-drive-window - browser = riot.mount browser, do - multiple: false - title: '<i class="fa fa-picture-o"></i>アバターにする画像を選択' - .0 - browser.one \selected (file) ~> - @file-selected file diff --git a/src/web/app/desktop/scripts/update-banner.js b/src/web/app/desktop/scripts/update-banner.js new file mode 100644 index 0000000000..9242adcea5 --- /dev/null +++ b/src/web/app/desktop/scripts/update-banner.js @@ -0,0 +1,86 @@ +const riot = require('riot'); +const dialog = require('./dialog'); +const api = require('../../common/scripts/api'); + +module.exports = (I, cb, file = null) => { + const fileSelected = file => { + const cropper = riot.mount(document.body.appendChild(document.createElement('mk-crop-window')), { + file: file, + title: 'バナーとして表示する部分を選択', + aspectRatio: 16 / 9 + })[0]; + + cropper.on('cropped', blob => { + const data = new FormData(); + data.append('i', I.token); + data.append('file', blob, file.name + '.cropped.png'); + + api(I, 'drive/folders/find', { + name: 'バナー' + }).then(iconFolder => { + if (iconFolder.length === 0) { + api(I, 'drive/folders/create', { + name: 'バナー' + }).then(iconFolder => { + uplaod(data, iconFolder); + }); + } else { + uplaod(data, iconFolder[0]); + } + }); + }); + + cropper.on('skiped', () => { + set(file); + }); + }; + + const uplaod = (data, folder) => { + const progress = riot.mount(document.body.appendChild(document.createElement('mk-progress-dialog')), { + title: '新しいバナーをアップロードしています' + })[0]; + + if (folder) data.append('folder_id', folder.id); + + const xhr = new XMLHttpRequest(); + xhr.open('POST', CONFIG.api.url + '/drive/files/create', true); + xhr.onload = e => { + const file = JSON.parse(e.target.response); + progress.close(); + set(file); + }; + + xhr.upload.onprogress = e => { + if (e.lengthComputable) progress.updateProgress(e.loaded, e.total); + }; + + xhr.send(data); + }; + + const set = file => { + api(I, 'i/update', { + banner_id: file.id + }).then(i => { + dialog('<i class="fa fa-info-circle"></i>バナーを更新しました', + '新しいバナーが反映されるまで時間がかかる場合があります。', + [{ + text: 'わかりました。' + }]); + + if (cb) cb(i); + }); + }; + + if (file) { + fileSelected(file); + } else { + const browser = riot.mount(document.body.appendChild(document.createElement('mk-select-file-from-drive-window')), { + multiple: false, + title: '<i class="fa fa-picture-o"></i>バナーにする画像を選択' + })[0]; + + browser.one('selected', file => { + fileSelected(file); + }); + } +}; diff --git a/src/web/app/desktop/scripts/update-banner.ls b/src/web/app/desktop/scripts/update-banner.ls deleted file mode 100644 index 2417b8ab2a..0000000000 --- a/src/web/app/desktop/scripts/update-banner.ls +++ /dev/null @@ -1,81 +0,0 @@ -# Update Banner -#================================ - -riot = require 'riot' -dialog = require './dialog.ls' -api = require '../../common/scripts/api' - -module.exports = (I, cb, file = null) ~> - - @file-selected = (file) ~> - cropper = document.body.append-child document.create-element \mk-crop-window - cropper = riot.mount cropper, do - file: file - title: 'バナーとして表示する部分を選択' - aspect-ratio: 16 / 9 - .0 - cropper.on \cropped (blob) ~> - data = new FormData! - data.append \i I.token - data.append \file blob, file.name + '.cropped.png' - api I, \drive/folders/find do - name: 'バナー' - .then (banner-folder) ~> - if banner-folder.length == 0 - api I, \drive/folders/create do - name: 'バナー' - .then (banner-folder) ~> - @uplaod data, banner-folder - else - @uplaod data, banner-folder.0 - cropper.on \skiped ~> - @set file - - @uplaod = (data, folder) ~> - - progress = document.body.append-child document.create-element \mk-progress-dialog - progress = riot.mount progress, do - title: '新しいバナーをアップロードしています' - .0 - - if folder? - data.append \folder_id folder.id - - xhr = new XMLHttpRequest! - xhr.open \POST CONFIG.api.url + \/drive/files/create true - xhr.onload = (e) ~> - file = JSON.parse e.target.response - progress.close! - @set file - - xhr.upload.onprogress = (e) ~> - if e.length-computable - progress.update-progress e.loaded, e.total - - xhr.send data - - @set = (file) ~> - api I, \i/update do - banner_id: file.id - .then (i) ~> - dialog do - '<i class="fa fa-info-circle"></i>バナーを更新しました' - '新しいバナーが反映されるまで時間がかかる場合があります。' - [ - text: \わかりました。 - ] - if cb? then cb i - .catch (err) ~> - console.error err - #@opts.ui.trigger \notification 'Error!' - - if file? - @file-selected file - else - browser = document.body.append-child document.create-element \mk-select-file-from-drive-window - browser = riot.mount browser, do - multiple: false - title: '<i class="fa fa-picture-o"></i>バナーにする画像を選択' - .0 - browser.one \selected (file) ~> - @file-selected file diff --git a/src/web/app/desktop/scripts/user-preview.js b/src/web/app/desktop/scripts/user-preview.js new file mode 100644 index 0000000000..8351f0e75a --- /dev/null +++ b/src/web/app/desktop/scripts/user-preview.js @@ -0,0 +1,66 @@ +const riot = require('riot'); + +riot.mixin('user-preview', { + init: () => { + const scan = () => { + this.root.querySelectorAll('[data-user-preview]:not([data-user-preview-attached])') + .forEach(attach.bind(this)); + }; + this.on('mount', scan); + this.on('updated', scan); + } +}); + +function attach(el) { + el.setAttribute('data-user-preview-attached', true); + + const user = el.getAttribute('data-user-preview'); + let tag = null; + let showTimer = null; + let hideTimer = null; + + el.addEventListener('mouseover', () => { + clearTimeout(showTimer); + clearTimeout(hideTimer); + showTimer = setTimeout(show, 500); + }); + + el.addEventListener('mouseleave', () => { + clearTimeout(showTimer); + clearTimeout(hideTimer); + hideTimer = setTimeout(close, 500); + }); + + this.on('unmount', () => { + clearTimeout(showTimer); + clearTimeout(hideTimer); + close(); + }); + + const show = () => { + if (tag) return; + const preview = document.createElement('mk-user-preview'); + const rect = el.getBoundingClientRect(); + const x = rect.left + el.offsetWidth + window.pageXOffset; + const y = rect.top + window.pageYOffset; + preview.style.top = y + 'px'; + preview.style.left = x + 'px'; + preview.addEventListener('mouseover', () => { + clearTimeout(hideTimer); + }); + preview.addEventListener('mouseleave', () => { + clearTimeout(showTimer); + hideTimer = setTimeout(close, 500); + }); + tag = riot.mount(document.body.appendChild(preview), { + user: user + })[0]; + }; + + const close = () => { + if (tag) { + tag.close(); + tag = null; + } + }; +} diff --git a/src/web/app/desktop/scripts/user-preview.ls b/src/web/app/desktop/scripts/user-preview.ls deleted file mode 100644 index 0c5a67aedb..0000000000 --- a/src/web/app/desktop/scripts/user-preview.ls +++ /dev/null @@ -1,74 +0,0 @@ -# User Preview -#================================ - -riot = require \riot - -riot.mixin \user-preview do - init: -> - @on \mount ~> - scan.call @ - @on \updated ~> - scan.call @ - - function scan - elems = @root.query-selector-all '[data-user-preview]:not([data-user-preview-attached])' - elems.for-each attach.bind @ - -function attach el - el.set-attribute \data-user-preview-attached true - user = el.get-attribute \data-user-preview - - tag = null - - show-timer = null - hide-timer = null - - el.add-event-listener \mouseover ~> - clear-timeout show-timer - clear-timeout hide-timer - show-timer := set-timeout ~> - show! - , 500ms - - el.add-event-listener \mouseleave ~> - clear-timeout show-timer - clear-timeout hide-timer - hide-timer := set-timeout ~> - close! - , 500ms - - @on \unmount ~> - clear-timeout show-timer - clear-timeout hide-timer - close! - - function show - if tag? - return - - preview = document.create-element \mk-user-preview - - rect = el.get-bounding-client-rect! - x = rect.left + el.offset-width + window.page-x-offset - y = rect.top + window.page-y-offset - - preview.style.top = y + \px - preview.style.left = x + \px - - preview.add-event-listener \mouseover ~> - clear-timeout hide-timer - - preview.add-event-listener \mouseleave ~> - clear-timeout show-timer - hide-timer := set-timeout ~> - close! - , 500ms - - tag := riot.mount (document.body.append-child preview), do - user: user - .0 - - function close - if tag? - tag.close! - tag := null |