From d1aba96a2909654e9869edd18b43c5f863719e99 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E3=81=93=E3=81=B4=E3=81=AA=E3=81=9F=E3=81=BF=E3=81=BD?= <syuilotan@yahoo.co.jp> Date: Fri, 9 Feb 2018 09:46:23 +0900 Subject: [PATCH] wip --- src/web/app/desktop/tags/home.tag | 388 ----------------------------- src/web/app/desktop/tags/home.vue | 392 ++++++++++++++++++++++++++++++ 2 files changed, 392 insertions(+), 388 deletions(-) delete mode 100644 src/web/app/desktop/tags/home.tag create mode 100644 src/web/app/desktop/tags/home.vue diff --git a/src/web/app/desktop/tags/home.tag b/src/web/app/desktop/tags/home.tag deleted file mode 100644 index 827622930d..0000000000 --- a/src/web/app/desktop/tags/home.tag +++ /dev/null @@ -1,388 +0,0 @@ -<mk-home data-customize={ opts.customize }> - <div class="customize" v-if="opts.customize"> - <a href="/">%fa:check%完了</a> - <div> - <div class="adder"> - <p>ウィジェットを追加:</p> - <select ref="widgetSelector"> - <option value="profile">プロフィール</option> - <option value="calendar">カレンダー</option> - <option value="timemachine">カレンダー(タイムマシン)</option> - <option value="activity">アクティビティ</option> - <option value="rss-reader">RSSリーダー</option> - <option value="trends">トレンド</option> - <option value="photo-stream">フォトストリーム</option> - <option value="slideshow">スライドショー</option> - <option value="version">バージョン</option> - <option value="broadcast">ブロードキャスト</option> - <option value="notifications">通知</option> - <option value="user-recommendation">おすすめユーザー</option> - <option value="recommended-polls">投票</option> - <option value="post-form">投稿フォーム</option> - <option value="messaging">メッセージ</option> - <option value="channel">チャンネル</option> - <option value="access-log">アクセスログ</option> - <option value="server">サーバー情報</option> - <option value="donation">寄付のお願い</option> - <option value="nav">ナビゲーション</option> - <option value="tips">ヒント</option> - </select> - <button @click="addWidget">追加</button> - </div> - <div class="trash"> - <div ref="trash"></div> - <p>ゴミ箱</p> - </div> - </div> - </div> - <div class="main"> - <div class="left"> - <div ref="left" data-place="left"></div> - </div> - <main ref="main"> - <div class="maintop" ref="maintop" data-place="main" v-if="opts.customize"></div> - <mk-timeline-home-widget ref="tl" v-if="mode == 'timeline'"/> - <mk-mentions-home-widget ref="tl" v-if="mode == 'mentions'"/> - </main> - <div class="right"> - <div ref="right" data-place="right"></div> - </div> - </div> - <style lang="stylus" scoped> - :scope - display block - - &[data-customize] - padding-top 48px - background-image url('/assets/desktop/grid.svg') - - > .main > main > *:not(.maintop) - cursor not-allowed - - > * - pointer-events none - - &:not([data-customize]) - > .main > *:empty - display none - - > .customize - position fixed - z-index 1000 - top 0 - left 0 - width 100% - height 48px - background #f7f7f7 - box-shadow 0 1px 1px rgba(0, 0, 0, 0.075) - - > a - display block - position absolute - z-index 1001 - top 0 - right 0 - padding 0 16px - line-height 48px - text-decoration none - color $theme-color-foreground - background $theme-color - transition background 0.1s ease - - &:hover - background lighten($theme-color, 10%) - - &:active - background darken($theme-color, 10%) - transition background 0s ease - - > [data-fa] - margin-right 8px - - > div - display flex - margin 0 auto - max-width 1200px - 32px - - > div - width 50% - - &.adder - > p - display inline - line-height 48px - - &.trash - border-left solid 1px #ddd - - > div - width 100% - height 100% - - > p - position absolute - top 0 - left 0 - width 100% - line-height 48px - margin 0 - text-align center - pointer-events none - - > .main - display flex - justify-content center - margin 0 auto - max-width 1200px - - > * - .customize-container - cursor move - - > * - pointer-events none - - > main - padding 16px - width calc(100% - 275px * 2) - - > *:not(.maintop):not(:last-child) - > .maintop > *:not(:last-child) - margin-bottom 16px - - > .maintop - min-height 64px - margin-bottom 16px - - > *:not(main) - width 275px - - > * - padding 16px 0 16px 0 - - > *:not(:last-child) - margin-bottom 16px - - > .left - padding-left 16px - - > .right - padding-right 16px - - @media (max-width 1100px) - > *:not(main) - display none - - > main - float none - width 100% - max-width 700px - margin 0 auto - - </style> - <script lang="typescript"> - import uuid from 'uuid'; - import Sortable from 'sortablejs'; - import dialog from '../scripts/dialog'; - import ScrollFollower from '../scripts/scroll-follower'; - - this.mixin('i'); - this.mixin('api'); - - this.mode = this.opts.mode || 'timeline'; - - this.home = []; - - this.bakeHomeData = () => JSON.stringify(this.I.client_settings.home); - this.bakedHomeData = this.bakeHomeData(); - - this.on('mount', () => { - this.$refs.tl.on('loaded', () => { - this.trigger('loaded'); - }); - - this.I.on('refreshed', this.onMeRefreshed); - - this.I.client_settings.home.forEach(widget => { - try { - this.setWidget(widget); - } catch (e) { - // noop - } - }); - - if (!this.opts.customize) { - if (this.$refs.left.children.length == 0) { - this.$refs.left.parentNode.removeChild(this.$refs.left); - } - if (this.$refs.right.children.length == 0) { - this.$refs.right.parentNode.removeChild(this.$refs.right); - } - } - - if (this.opts.customize) { - dialog('%fa:info-circle%カスタマイズのヒント', - '<p>ホームのカスタマイズでは、ウィジェットを追加/削除したり、ドラッグ&ドロップして並べ替えたりすることができます。</p>' + - '<p>一部のウィジェットは、<strong><strong>右</strong>クリック</strong>することで表示を変更することができます。</p>' + - '<p>ウィジェットを削除するには、ヘッダーの<strong>「ゴミ箱」</strong>と書かれたエリアにウィジェットをドラッグ&ドロップします。</p>' + - '<p>カスタマイズを終了するには、右上の「完了」をクリックします。</p>', - [{ - text: 'Got it!' - }]); - - const sortableOption = { - group: 'kyoppie', - animation: 150, - onMove: evt => { - const id = evt.dragged.getAttribute('data-widget-id'); - this.home.find(tag => tag.id == id).update({ place: evt.to.getAttribute('data-place') }); - }, - onSort: () => { - this.saveHome(); - } - }; - - new Sortable(this.$refs.left, sortableOption); - new Sortable(this.$refs.right, sortableOption); - new Sortable(this.$refs.maintop, sortableOption); - new Sortable(this.$refs.trash, Object.assign({}, sortableOption, { - onAdd: evt => { - const el = evt.item; - const id = el.getAttribute('data-widget-id'); - el.parentNode.removeChild(el); - this.I.client_settings.home = this.I.client_settings.home.filter(w => w.id != id); - this.saveHome(); - } - })); - } - - if (!this.opts.customize) { - this.scrollFollowerLeft = this.$refs.left.parentNode ? new ScrollFollower(this.$refs.left, this.root.getBoundingClientRect().top) : null; - this.scrollFollowerRight = this.$refs.right.parentNode ? new ScrollFollower(this.$refs.right, this.root.getBoundingClientRect().top) : null; - } - }); - - this.on('unmount', () => { - this.I.off('refreshed', this.onMeRefreshed); - - this.home.forEach(widget => { - widget.unmount(); - }); - - if (!this.opts.customize) { - if (this.scrollFollowerLeft) this.scrollFollowerLeft.dispose(); - if (this.scrollFollowerRight) this.scrollFollowerRight.dispose(); - } - }); - - this.onMeRefreshed = () => { - if (this.bakedHomeData != this.bakeHomeData()) { - alert('別の場所でホームが編集されました。ページを再度読み込みすると編集が反映されます。'); - } - }; - - this.setWidget = (widget, prepend = false) => { - const el = document.createElement(`mk-${widget.name}-home-widget`); - - let actualEl; - - if (this.opts.customize) { - const container = document.createElement('div'); - container.classList.add('customize-container'); - container.setAttribute('data-widget-id', widget.id); - container.appendChild(el); - actualEl = container; - } else { - actualEl = el; - } - - switch (widget.place) { - case 'left': - if (prepend) { - this.$refs.left.insertBefore(actualEl, this.$refs.left.firstChild); - } else { - this.$refs.left.appendChild(actualEl); - } - break; - case 'right': - if (prepend) { - this.$refs.right.insertBefore(actualEl, this.$refs.right.firstChild); - } else { - this.$refs.right.appendChild(actualEl); - } - break; - case 'main': - if (this.opts.customize) { - this.$refs.maintop.appendChild(actualEl); - } else { - this.$refs.main.insertBefore(actualEl, this.$refs.tl.root); - } - break; - } - - const tag = riot.mount(el, { - id: widget.id, - data: widget.data, - place: widget.place, - tl: this.$refs.tl - })[0]; - - this.home.push(tag); - - if (this.opts.customize) { - actualEl.oncontextmenu = e => { - e.preventDefault(); - e.stopImmediatePropagation(); - if (tag.func) tag.func(); - return false; - }; - } - }; - - this.addWidget = () => { - const widget = { - name: this.$refs.widgetSelector.options[this.$refs.widgetSelector.selectedIndex].value, - id: uuid(), - place: 'left', - data: {} - }; - - this.I.client_settings.home.unshift(widget); - - this.setWidget(widget, true); - - this.saveHome(); - }; - - this.saveHome = () => { - const data = []; - - Array.from(this.$refs.left.children).forEach(el => { - const id = el.getAttribute('data-widget-id'); - const widget = this.I.client_settings.home.find(w => w.id == id); - widget.place = 'left'; - data.push(widget); - }); - - Array.from(this.$refs.right.children).forEach(el => { - const id = el.getAttribute('data-widget-id'); - const widget = this.I.client_settings.home.find(w => w.id == id); - widget.place = 'right'; - data.push(widget); - }); - - Array.from(this.$refs.maintop.children).forEach(el => { - const id = el.getAttribute('data-widget-id'); - const widget = this.I.client_settings.home.find(w => w.id == id); - widget.place = 'main'; - data.push(widget); - }); - - this.api('i/update_home', { - home: data - }).then(() => { - this.I.update(); - }); - }; - </script> -</mk-home> diff --git a/src/web/app/desktop/tags/home.vue b/src/web/app/desktop/tags/home.vue new file mode 100644 index 0000000000..ee12200ba3 --- /dev/null +++ b/src/web/app/desktop/tags/home.vue @@ -0,0 +1,392 @@ +<template> + <div :data-customize="customize"> + <div class="customize" v-if="customize"> + <a href="/">%fa:check%完了</a> + <div> + <div class="adder"> + <p>ウィジェットを追加:</p> + <select ref="widgetSelector"> + <option value="profile">プロフィール</option> + <option value="calendar">カレンダー</option> + <option value="timemachine">カレンダー(タイムマシン)</option> + <option value="activity">アクティビティ</option> + <option value="rss-reader">RSSリーダー</option> + <option value="trends">トレンド</option> + <option value="photo-stream">フォトストリーム</option> + <option value="slideshow">スライドショー</option> + <option value="version">バージョン</option> + <option value="broadcast">ブロードキャスト</option> + <option value="notifications">通知</option> + <option value="user-recommendation">おすすめユーザー</option> + <option value="recommended-polls">投票</option> + <option value="post-form">投稿フォーム</option> + <option value="messaging">メッセージ</option> + <option value="channel">チャンネル</option> + <option value="access-log">アクセスログ</option> + <option value="server">サーバー情報</option> + <option value="donation">寄付のお願い</option> + <option value="nav">ナビゲーション</option> + <option value="tips">ヒント</option> + </select> + <button @click="addWidget">追加</button> + </div> + <div class="trash"> + <div ref="trash"></div> + <p>ゴミ箱</p> + </div> + </div> + </div> + <div class="main"> + <div class="left"> + <div ref="left" data-place="left"></div> + </div> + <main ref="main"> + <div class="maintop" ref="maintop" data-place="main" v-if="customize"></div> + <mk-timeline-home-widget ref="tl" v-if="mode == 'timeline'"/> + <mk-mentions-home-widget ref="tl" v-if="mode == 'mentions'"/> + </main> + <div class="right"> + <div ref="right" data-place="right"></div> + </div> + </div> + </div> +</template> + +<style lang="stylus" scoped> + :scope + display block + + &[data-customize] + padding-top 48px + background-image url('/assets/desktop/grid.svg') + + > .main > main > *:not(.maintop) + cursor not-allowed + + > * + pointer-events none + + &:not([data-customize]) + > .main > *:empty + display none + + > .customize + position fixed + z-index 1000 + top 0 + left 0 + width 100% + height 48px + background #f7f7f7 + box-shadow 0 1px 1px rgba(0, 0, 0, 0.075) + + > a + display block + position absolute + z-index 1001 + top 0 + right 0 + padding 0 16px + line-height 48px + text-decoration none + color $theme-color-foreground + background $theme-color + transition background 0.1s ease + + &:hover + background lighten($theme-color, 10%) + + &:active + background darken($theme-color, 10%) + transition background 0s ease + + > [data-fa] + margin-right 8px + + > div + display flex + margin 0 auto + max-width 1200px - 32px + + > div + width 50% + + &.adder + > p + display inline + line-height 48px + + &.trash + border-left solid 1px #ddd + + > div + width 100% + height 100% + + > p + position absolute + top 0 + left 0 + width 100% + line-height 48px + margin 0 + text-align center + pointer-events none + + > .main + display flex + justify-content center + margin 0 auto + max-width 1200px + + > * + .customize-container + cursor move + + > * + pointer-events none + + > main + padding 16px + width calc(100% - 275px * 2) + + > *:not(.maintop):not(:last-child) + > .maintop > *:not(:last-child) + margin-bottom 16px + + > .maintop + min-height 64px + margin-bottom 16px + + > *:not(main) + width 275px + + > * + padding 16px 0 16px 0 + + > *:not(:last-child) + margin-bottom 16px + + > .left + padding-left 16px + + > .right + padding-right 16px + + @media (max-width 1100px) + > *:not(main) + display none + + > main + float none + width 100% + max-width 700px + margin 0 auto + +</style> + +<script lang="typescript"> + import uuid from 'uuid'; + import Sortable from 'sortablejs'; + import dialog from '../scripts/dialog'; + import ScrollFollower from '../scripts/scroll-follower'; + + this.mixin('i'); + this.mixin('api'); + + this.mode = this.opts.mode || 'timeline'; + + this.home = []; + + this.bakeHomeData = () => JSON.stringify(this.I.client_settings.home); + this.bakedHomeData = this.bakeHomeData(); + + this.on('mount', () => { + this.$refs.tl.on('loaded', () => { + this.trigger('loaded'); + }); + + this.I.on('refreshed', this.onMeRefreshed); + + this.I.client_settings.home.forEach(widget => { + try { + this.setWidget(widget); + } catch (e) { + // noop + } + }); + + if (!this.opts.customize) { + if (this.$refs.left.children.length == 0) { + this.$refs.left.parentNode.removeChild(this.$refs.left); + } + if (this.$refs.right.children.length == 0) { + this.$refs.right.parentNode.removeChild(this.$refs.right); + } + } + + if (this.opts.customize) { + dialog('%fa:info-circle%カスタマイズのヒント', + '<p>ホームのカスタマイズでは、ウィジェットを追加/削除したり、ドラッグ&ドロップして並べ替えたりすることができます。</p>' + + '<p>一部のウィジェットは、<strong><strong>右</strong>クリック</strong>することで表示を変更することができます。</p>' + + '<p>ウィジェットを削除するには、ヘッダーの<strong>「ゴミ箱」</strong>と書かれたエリアにウィジェットをドラッグ&ドロップします。</p>' + + '<p>カスタマイズを終了するには、右上の「完了」をクリックします。</p>', + [{ + text: 'Got it!' + }]); + + const sortableOption = { + group: 'kyoppie', + animation: 150, + onMove: evt => { + const id = evt.dragged.getAttribute('data-widget-id'); + this.home.find(tag => tag.id == id).update({ place: evt.to.getAttribute('data-place') }); + }, + onSort: () => { + this.saveHome(); + } + }; + + new Sortable(this.$refs.left, sortableOption); + new Sortable(this.$refs.right, sortableOption); + new Sortable(this.$refs.maintop, sortableOption); + new Sortable(this.$refs.trash, Object.assign({}, sortableOption, { + onAdd: evt => { + const el = evt.item; + const id = el.getAttribute('data-widget-id'); + el.parentNode.removeChild(el); + this.I.client_settings.home = this.I.client_settings.home.filter(w => w.id != id); + this.saveHome(); + } + })); + } + + if (!this.opts.customize) { + this.scrollFollowerLeft = this.$refs.left.parentNode ? new ScrollFollower(this.$refs.left, this.root.getBoundingClientRect().top) : null; + this.scrollFollowerRight = this.$refs.right.parentNode ? new ScrollFollower(this.$refs.right, this.root.getBoundingClientRect().top) : null; + } + }); + + this.on('unmount', () => { + this.I.off('refreshed', this.onMeRefreshed); + + this.home.forEach(widget => { + widget.unmount(); + }); + + if (!this.opts.customize) { + if (this.scrollFollowerLeft) this.scrollFollowerLeft.dispose(); + if (this.scrollFollowerRight) this.scrollFollowerRight.dispose(); + } + }); + + this.onMeRefreshed = () => { + if (this.bakedHomeData != this.bakeHomeData()) { + alert('別の場所でホームが編集されました。ページを再度読み込みすると編集が反映されます。'); + } + }; + + this.setWidget = (widget, prepend = false) => { + const el = document.createElement(`mk-${widget.name}-home-widget`); + + let actualEl; + + if (this.opts.customize) { + const container = document.createElement('div'); + container.classList.add('customize-container'); + container.setAttribute('data-widget-id', widget.id); + container.appendChild(el); + actualEl = container; + } else { + actualEl = el; + } + + switch (widget.place) { + case 'left': + if (prepend) { + this.$refs.left.insertBefore(actualEl, this.$refs.left.firstChild); + } else { + this.$refs.left.appendChild(actualEl); + } + break; + case 'right': + if (prepend) { + this.$refs.right.insertBefore(actualEl, this.$refs.right.firstChild); + } else { + this.$refs.right.appendChild(actualEl); + } + break; + case 'main': + if (this.opts.customize) { + this.$refs.maintop.appendChild(actualEl); + } else { + this.$refs.main.insertBefore(actualEl, this.$refs.tl.root); + } + break; + } + + const tag = riot.mount(el, { + id: widget.id, + data: widget.data, + place: widget.place, + tl: this.$refs.tl + })[0]; + + this.home.push(tag); + + if (this.opts.customize) { + actualEl.oncontextmenu = e => { + e.preventDefault(); + e.stopImmediatePropagation(); + if (tag.func) tag.func(); + return false; + }; + } + }; + + this.addWidget = () => { + const widget = { + name: this.$refs.widgetSelector.options[this.$refs.widgetSelector.selectedIndex].value, + id: uuid(), + place: 'left', + data: {} + }; + + this.I.client_settings.home.unshift(widget); + + this.setWidget(widget, true); + + this.saveHome(); + }; + + this.saveHome = () => { + const data = []; + + Array.from(this.$refs.left.children).forEach(el => { + const id = el.getAttribute('data-widget-id'); + const widget = this.I.client_settings.home.find(w => w.id == id); + widget.place = 'left'; + data.push(widget); + }); + + Array.from(this.$refs.right.children).forEach(el => { + const id = el.getAttribute('data-widget-id'); + const widget = this.I.client_settings.home.find(w => w.id == id); + widget.place = 'right'; + data.push(widget); + }); + + Array.from(this.$refs.maintop.children).forEach(el => { + const id = el.getAttribute('data-widget-id'); + const widget = this.I.client_settings.home.find(w => w.id == id); + widget.place = 'main'; + data.push(widget); + }); + + this.api('i/update_home', { + home: data + }).then(() => { + this.I.update(); + }); + }; +</script>