diff --git a/.eslintrc b/.eslintrc new file mode 100644 index 0000000000..d30cf2aa56 --- /dev/null +++ b/.eslintrc @@ -0,0 +1,17 @@ +{ + "parserOptions": { + "parser": "typescript-eslint-parser" + }, + "extends": [ + "eslint:recommended", + "plugin:vue/recommended" + ], + "rules": { + "vue/require-v-for-key": false, + "vue/max-attributes-per-line": false, + "vue/html-indent": false, + "vue/html-self-closing": false, + "vue/no-unused-vars": false, + "no-console": 0 + } +} diff --git a/package.json b/package.json index e87be0ab21..727c4af716 100644 --- a/package.json +++ b/package.json @@ -99,6 +99,8 @@ "diskusage": "0.2.4", "elasticsearch": "14.1.0", "escape-regexp": "0.0.1", + "eslint": "^4.18.0", + "eslint-plugin-vue": "^4.2.2", "eventemitter3": "3.0.0", "exif-js": "2.3.0", "express": "4.16.2", @@ -174,6 +176,7 @@ "ts-node": "4.1.0", "tslint": "5.9.1", "typescript": "2.7.1", + "typescript-eslint-parser": "^13.0.0", "uglify-es": "3.3.9", "uglifyjs-webpack-plugin": "1.1.8", "uuid": "3.2.1", diff --git a/src/web/app/desktop/-tags/home-widgets/timemachine.tag b/src/web/app/desktop/-tags/home-widgets/timemachine.tag deleted file mode 100644 index 43f59fe674..0000000000 --- a/src/web/app/desktop/-tags/home-widgets/timemachine.tag +++ /dev/null @@ -1,23 +0,0 @@ -<mk-timemachine-home-widget> - <mk-calendar-widget design={ data.design } warp={ warp }/> - <style lang="stylus" scoped> - :scope - display block - </style> - <script lang="typescript"> - this.data = { - design: 0 - }; - - this.mixin('widget'); - - this.warp = date => { - this.opts.tl.warp(date); - }; - - this.func = () => { - if (++this.data.design == 6) this.data.design = 0; - this.save(); - }; - </script> -</mk-timemachine-home-widget> diff --git a/src/web/app/desktop/views/components/calendar.vue b/src/web/app/desktop/views/components/calendar.vue index 3380774028..e548a82c57 100644 --- a/src/web/app/desktop/views/components/calendar.vue +++ b/src/web/app/desktop/views/components/calendar.vue @@ -7,16 +7,15 @@ </template> <div class="calendar"> + <template v-if="design == 0 || design == 2 || design == 4"> <div class="weekday" - v-if="design == 0 || design == 2 || design == 4" v-for="(day, i) in Array(7).fill(0)" - :key="i" :data-today="year == today.getFullYear() && month == today.getMonth() + 1 && today.getDay() == i" :data-is-donichi="i == 0 || i == 6" >{{ weekdayText[i] }}</div> - <div each={ day, i in Array(paddingDays).fill(0) }></div> - <div class="day" v-for="(day, i) in Array(days).fill(0)" - :key="i" + </template> + <div v-for="n in paddingDays"></div> + <div class="day" v-for="(day, i) in days" :data-today="isToday(i + 1)" :data-selected="isSelected(i + 1)" :data-is-out-of-range="isOutOfRange(i + 1)" diff --git a/src/web/app/desktop/views/components/home.vue b/src/web/app/desktop/views/components/home.vue index 3a04e13cb2..e815239d3f 100644 --- a/src/web/app/desktop/views/components/home.vue +++ b/src/web/app/desktop/views/components/home.vue @@ -37,43 +37,21 @@ </div> </div> <div class="main"> - <div class="left"> - <div ref="left" data-place="left"> - <template v-for="widget in leftWidgets"> + <div v-for="place in ['left', 'main', 'right']" :class="place" :ref="place" :data-place="place"> + <template v-if="place != 'main'"> + <template v-for="widget in widgets[place]"> <div class="customize-container" v-if="customize" :key="widget.id" @contextmenu.stop.prevent="onWidgetContextmenu(widget.id)"> - <component :is="'mkw-' + widget.name" :widget="widget" :ref="widget.id"/> + <component :is="`mkw-${widget.name}`" :widget="widget" :ref="widget.id"/> </div> <template v-else> - <component :is="'mkw-' + widget.name" :key="widget.id" :widget="widget" :ref="widget.id"/> + <component :is="`mkw-${widget.name}`" :key="widget.id" :widget="widget" :ref="widget.id" @chosen="warp"/> </template> </template> - </div> - </div> - <main ref="main"> - <div class="maintop" ref="maintop" data-place="main" v-if="customize"> - <template v-for="widget in centerWidgets"> - <div class="customize-container" v-if="customize" :key="widget.id" @contextmenu.stop.prevent="onWidgetContextmenu(widget.id)"> - <component :is="'mkw-' + widget.name" :widget="widget" :ref="widget.id"/> - </div> - <template v-else> - <component :is="'mkw-' + widget.name" :key="widget.id" :widget="widget" :ref="widget.id"/> - </template> - </template> - </div> - <mk-timeline ref="tl" @loaded="onTlLoaded" v-if="mode == 'timeline'"/> - <mk-mentions ref="tl" @loaded="onTlLoaded" v-if="mode == 'mentions'"/> - </main> - <div class="right"> - <div ref="right" data-place="right"> - <template v-for="widget in rightWidgets"> - <div class="customize-container" v-if="customize" :key="widget.id" @contextmenu.stop.prevent="onWidgetContextmenu(widget.id)"> - <component :is="'mkw-' + widget.name" :widget="widget" :ref="widget.id"/> - </div> - <template v-else> - <component :is="'mkw-' + widget.name" :key="widget.id" :widget="widget" :ref="widget.id"/> - </template> - </template> - </div> + </template> + <template v-else> + <mk-timeline ref="tl" @loaded="onTlLoaded" v-if="place == 'main' && mode == 'timeline'"/> + <mk-mentions @loaded="onTlLoaded" v-if="place == 'main' && mode == 'mentions'"/> + </template> </div> </div> </div> @@ -99,6 +77,85 @@ export default Vue.extend({ widgetAdderSelected: null }; }, + computed: { + leftWidgets(): any { + return (this as any).os.i.client_settings.home.filter(w => w.place == 'left'); + }, + rightWidgets(): any { + return (this as any).os.i.client_settings.home.filter(w => w.place == 'right'); + }, + widgets(): any { + return { + left: this.leftWidgets, + right: this.rightWidgets, + }; + }, + leftEl(): Element { + return (this.$refs.left as Element[])[0]; + }, + rightEl(): Element { + return (this.$refs.right as Element[])[0]; + } + }, + created() { + this.bakedHomeData = this.bakeHomeData(); + }, + mounted() { + (this as any).os.i.on('refreshed', this.onMeRefreshed); + + this.home = (this as any).os.i.client_settings.home; + + this.$nextTick(() => { + if (!this.customize) { + if (this.leftEl.children.length == 0) { + this.leftEl.parentNode.removeChild(this.leftEl); + } + if (this.rightEl.children.length == 0) { + this.rightEl.parentNode.removeChild(this.rightEl); + } + } + + if (this.customize) { + (this as any).apis.dialog({ + title: '%fa:info-circle%カスタマイズのヒント', + text: '<p>ホームのカスタマイズでは、ウィジェットを追加/削除したり、ドラッグ&ドロップして並べ替えたりすることができます。</p>' + + '<p>一部のウィジェットは、<strong><strong>右</strong>クリック</strong>することで表示を変更することができます。</p>' + + '<p>ウィジェットを削除するには、ヘッダーの<strong>「ゴミ箱」</strong>と書かれたエリアにウィジェットをドラッグ&ドロップします。</p>' + + '<p>カスタマイズを終了するには、右上の「完了」をクリックします。</p>', + actions: [{ + 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).widget.place = evt.to.getAttribute('data-place'); + }, + onSort: () => { + this.saveHome(); + } + }; + + new Sortable(this.leftEl, sortableOption); + new Sortable(this.rightEl, 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 as any).os.i.client_settings.home = (this as any).os.i.client_settings.home.filter(w => w.id != id); + this.saveHome(); + } + })); + } + }); + }, + beforeDestroy() { + (this as any).os.i.off('refreshed', this.onMeRefreshed); + }, methods: { bakeHomeData() { return JSON.stringify((this as any).os.i.client_settings.home); @@ -130,102 +187,27 @@ export default Vue.extend({ saveHome() { const data = []; - Array.from((this.$refs.left as Element).children).forEach(el => { + Array.from(this.leftEl.children).forEach(el => { const id = el.getAttribute('data-widget-id'); const widget = (this as any).os.i.client_settings.home.find(w => w.id == id); widget.place = 'left'; data.push(widget); }); - Array.from((this.$refs.right as Element).children).forEach(el => { + Array.from(this.rightEl.children).forEach(el => { const id = el.getAttribute('data-widget-id'); const widget = (this as any).os.i.client_settings.home.find(w => w.id == id); widget.place = 'right'; data.push(widget); }); - Array.from((this.$refs.maintop as Element).children).forEach(el => { - const id = el.getAttribute('data-widget-id'); - const widget = (this as any).os.i.client_settings.home.find(w => w.id == id); - widget.place = 'main'; - data.push(widget); - }); - (this as any).api('i/update_home', { home: data }); - } - }, - computed: { - leftWidgets(): any { - return (this as any).os.i.client_settings.home.filter(w => w.place == 'left'); }, - centerWidgets(): any { - return (this as any).os.i.client_settings.home.filter(w => w.place == 'center'); - }, - rightWidgets(): any { - return (this as any).os.i.client_settings.home.filter(w => w.place == 'right'); + warp(date) { + (this.$refs.tl as any)[0].warp(date); } - }, - created() { - this.bakedHomeData = this.bakeHomeData(); - }, - mounted() { - (this as any).os.i.on('refreshed', this.onMeRefreshed); - - this.home = (this as any).os.i.client_settings.home; - - if (!this.customize) { - if ((this.$refs.left as Element).children.length == 0) { - (this.$refs.left as Element).parentNode.removeChild((this.$refs.left as Element)); - } - if ((this.$refs.right as Element).children.length == 0) { - (this.$refs.right as Element).parentNode.removeChild((this.$refs.right as Element)); - } - } - - if (this.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 as any).os.i.client_settings.home = (this as any).os.i.client_settings.home.filter(w => w.id != id); - this.saveHome(); - } - })); - } - }, - beforeDestroy() { - (this as any).os.i.off('refreshed', this.onMeRefreshed); - - this.home.forEach(widget => { - widget.unmount(); - }); } }); </script> @@ -324,26 +306,16 @@ export default Vue.extend({ > * pointer-events none - > main + > .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 - > * - padding 16px 0 16px 0 - - > *:not(:last-child) - margin-bottom 16px + > *:not(:last-child) + margin-bottom 16px > .left padding-left 16px @@ -355,7 +327,7 @@ export default Vue.extend({ > *:not(main) display none - > main + > .main float none width 100% max-width 700px diff --git a/src/web/app/desktop/views/components/index.ts b/src/web/app/desktop/views/components/index.ts index 151ebf296b..9a27369547 100644 --- a/src/web/app/desktop/views/components/index.ts +++ b/src/web/app/desktop/views/components/index.ts @@ -33,6 +33,7 @@ import driveFolder from './drive-folder.vue'; import driveNavFolder from './drive-nav-folder.vue'; import postDetail from './post-detail.vue'; import settings from './settings.vue'; +import calendar from './calendar.vue'; import wNav from './widgets/nav.vue'; import wCalendar from './widgets/calendar.vue'; import wPhotoStream from './widgets/photo-stream.vue'; @@ -41,6 +42,7 @@ import wTips from './widgets/tips.vue'; import wDonation from './widgets/donation.vue'; import wNotifications from './widgets/notifications.vue'; import wBroadcast from './widgets/broadcast.vue'; +import wTimemachine from './widgets/timemachine.vue'; Vue.component('mk-ui', ui); Vue.component('mk-ui-header', uiHeader); @@ -75,6 +77,7 @@ Vue.component('mk-drive-folder', driveFolder); Vue.component('mk-drive-nav-folder', driveNavFolder); Vue.component('mk-post-detail', postDetail); Vue.component('mk-settings', settings); +Vue.component('mk-calendar', calendar); Vue.component('mkw-nav', wNav); Vue.component('mkw-calendar', wCalendar); Vue.component('mkw-photo-stream', wPhotoStream); @@ -83,3 +86,4 @@ Vue.component('mkw-tips', wTips); Vue.component('mkw-donation', wDonation); Vue.component('mkw-notifications', wNotifications); Vue.component('mkw-broadcast', wBroadcast); +Vue.component('mkw-timemachine', wTimemachine); diff --git a/src/web/app/desktop/views/components/timeline.vue b/src/web/app/desktop/views/components/timeline.vue index 3d792436e0..66d70a9578 100644 --- a/src/web/app/desktop/views/components/timeline.vue +++ b/src/web/app/desktop/views/components/timeline.vue @@ -13,19 +13,14 @@ import Vue from 'vue'; export default Vue.extend({ - props: { - date: { - type: Date, - required: false - } - }, data() { return { fetching: true, moreFetching: false, posts: [], connection: null, - connectionId: null + connectionId: null, + date: null }; }, computed: { @@ -60,7 +55,7 @@ export default Vue.extend({ this.fetching = true; (this as any).api('posts/timeline', { - until_date: this.date ? (this.date as any).getTime() : undefined + until_date: this.date ? this.date.getTime() : undefined }).then(posts => { this.fetching = false; this.posts = posts; @@ -93,6 +88,10 @@ export default Vue.extend({ (this.$refs.timeline as any).focus(); } } + }, + warp(date) { + this.date = date; + this.fetch(); } } }); diff --git a/src/web/app/desktop/views/components/widgets/timemachine.vue b/src/web/app/desktop/views/components/widgets/timemachine.vue new file mode 100644 index 0000000000..d484ce6d74 --- /dev/null +++ b/src/web/app/desktop/views/components/widgets/timemachine.vue @@ -0,0 +1,28 @@ +<template> +<div class="mkw-timemachine"> + <mk-calendar :design="props.design" @chosen="chosen"/> +</div> +</template> + +<script lang="ts"> +import define from '../../../../common/define-widget'; +export default define({ + name: 'timemachine', + props: { + design: 0 + } +}).extend({ + methods: { + chosen(date) { + this.$emit('chosen', date); + }, + func() { + if (this.props.design == 5) { + this.props.design = 0; + } else { + this.props.design++; + } + } + } +}); +</script>