From 015bf78dd9b17a1eac11119e38d8e83be8d791ae Mon Sep 17 00:00:00 2001 From: shpuld <shpuld@gmail.com> Date: Sun, 26 Feb 2017 22:36:54 +0200 Subject: [PATCH 01/24] Heading text floats left, initial attempt at load more in the right side of the heading. --- src/App.scss | 4 ++-- src/components/timeline/timeline.vue | 12 +++++++++--- .../user_card_content/user_card_content.vue | 5 +++++ 3 files changed, 16 insertions(+), 5 deletions(-) diff --git a/src/App.scss b/src/App.scss index 0945c76b..8a1942c6 100644 --- a/src/App.scss +++ b/src/App.scss @@ -109,8 +109,8 @@ main-router { .panel-heading { border-radius: 10px 10px 0 0; background-size: cover; - padding: 0.6em 0; - text-align: center; + padding: 0.6em 1.0em; + text-align: left; font-size: 1.3em; line-height: 24px; } diff --git a/src/components/timeline/timeline.vue b/src/components/timeline/timeline.vue index 45886f6c..078d954c 100644 --- a/src/components/timeline/timeline.vue +++ b/src/components/timeline/timeline.vue @@ -1,7 +1,7 @@ <template> <div class="timeline"> <a href="#" v-on:click.prevent='showNewStatuses()' v-if="timeline.newStatusCount > 0"> - <div class="base01-background base05-border new-status-notification"> + <div class="base00-background base05-border new-status-notification"> <p class="text-center" > {{timeline.newStatusCount}} new statuses </p> @@ -22,12 +22,18 @@ <style lang="scss"> .new-status-notification { border-style: solid; - border-width: 1px 0 1px 0; + float: right; + border-width: 1px 1px 0px 1px; + border-radius: 5px 5px 0 0; font-size: 1.1em; + margin-top: -2.08em; + margin-right: 0.8em; + max-width: 200px; + max-height: 2em; p { margin: 0px; - padding: 10px; + padding: 0.6em; } } </style> diff --git a/src/components/user_card_content/user_card_content.vue b/src/components/user_card_content/user_card_content.vue index 178100b8..aa9a15e1 100644 --- a/src/components/user_card_content/user_card_content.vue +++ b/src/components/user_card_content/user_card_content.vue @@ -106,6 +106,11 @@ .profile-panel-background { background-size: cover; border-radius: 10px; + + .panel-heading { + padding: 0.6em 0em; + text-align: center; + } } .profile-panel-body { From 22e8258a56ab0828231bc0e510b52dd39eebb5c7 Mon Sep 17 00:00:00 2001 From: wakarimasen <wakarimasen@airmail.cc> Date: Sun, 5 Mar 2017 11:56:28 +0100 Subject: [PATCH 02/24] Highlight current notice in conversation-page, add backlinks --- src/components/status/status.js | 8 +++++++- src/components/status/status.vue | 9 ++++++++- src/main.js | 5 ++++- 3 files changed, 19 insertions(+), 3 deletions(-) diff --git a/src/components/status/status.js b/src/components/status/status.js index 22292ffa..9550c19f 100644 --- a/src/components/status/status.js +++ b/src/components/status/status.js @@ -4,6 +4,7 @@ import RetweetButton from '../retweet_button/retweet_button.vue' import DeleteButton from '../delete_button/delete_button.vue' import PostStatusForm from '../post_status_form/post_status_form.vue' import UserCardContent from '../user_card_content/user_card_content.vue' +import { toInteger } from 'lodash' const Status = { props: [ @@ -30,7 +31,12 @@ const Status = { loggedIn () { return !!this.$store.state.users.currentUser }, - muted () { return !this.unmuted && this.status.user.muted } + muted () { return !this.unmuted && this.status.user.muted }, + focused () { + const id = toInteger(this.$route.params.id) + return (this.statusoid.id == id) + }, + isReply () { return !!this.statusoid.in_reply_to_status_id } }, components: { Attachment, diff --git a/src/components/status/status.vue b/src/components/status/status.vue index 585bf621..6476e1e5 100644 --- a/src/components/status/status.vue +++ b/src/components/status/status.vue @@ -1,5 +1,5 @@ <template> - <div class="status-el base00-background" v-if="!status.deleted" v-bind:class="{ 'expanded-status': !expandable }"> + <div class="status-el base00-background" v-if="!status.deleted" v-bind:class="[{ 'expanded-status': !expandable }, { 'base01-background': focused }]"> <template v-if="muted"> <div class="media status container muted"> <small><router-link :to="{ name: 'user-profile', params: { id: status.user.id } }">{{status.user.screen_name}}</router-link></small> @@ -34,6 +34,13 @@ {{status.in_reply_to_screen_name}} </router-link> </small> + <template v-if="isReply"> + <small> + <router-link :to="{ name: 'conversation', params: { id: status.in_reply_to_status_id } }"> + <i class="icon-reply"></i> + </router-link> + </small> + </template> - <small> <router-link :to="{ name: 'conversation', params: { id: status.id } }"> diff --git a/src/main.js b/src/main.js index fa0a872f..30929f0b 100644 --- a/src/main.js +++ b/src/main.js @@ -53,7 +53,7 @@ const routes = [ { path: '/main/all', component: PublicAndExternalTimeline }, { path: '/main/public', component: PublicTimeline }, { path: '/main/friends', component: FriendsTimeline }, - { name: 'conversation', path: '/notice/:id', component: ConversationPage }, + { name: 'conversation', path: '/notice/:id', component: ConversationPage, meta: { dontScroll: true } }, { name: 'user-profile', path: '/users/:id', component: UserProfile }, { name: 'mentions', path: '/:username/mentions', component: Mentions }, { name: 'settings', path: '/settings', component: Settings } @@ -63,6 +63,9 @@ const router = new VueRouter({ mode: 'history', routes, scrollBehavior: (to, from, savedPosition) => { + if (to.matched.some(m => m.meta.dontScroll)) { + return false + } return savedPosition || { x: 0, y: 0 } } }) From 289326855caa5e763439f89f2829c430056e79c3 Mon Sep 17 00:00:00 2001 From: wakarimasen <wakarimasen@airmail.cc> Date: Sun, 5 Mar 2017 16:31:01 +0100 Subject: [PATCH 03/24] Add focused prop to status --- src/components/conversation/conversation.js | 3 +++ src/components/conversation/conversation.vue | 2 +- src/components/status/status.js | 7 ++----- .../status_or_conversation/status_or_conversation.vue | 2 +- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/components/conversation/conversation.js b/src/components/conversation/conversation.js index ecc76e71..524737fd 100644 --- a/src/components/conversation/conversation.js +++ b/src/components/conversation/conversation.js @@ -47,6 +47,9 @@ const conversation = { .then((status) => this.$store.dispatch('addNewStatuses', { statuses: [status] })) .then(() => this.fetchConversation()) } + }, + focused: function(id) { + return (id == this.statusoid.id) } } } diff --git a/src/components/conversation/conversation.vue b/src/components/conversation/conversation.vue index 9675e69f..8803dd57 100644 --- a/src/components/conversation/conversation.vue +++ b/src/components/conversation/conversation.vue @@ -8,7 +8,7 @@ </div> <div class="panel-body"> <div class="timeline"> - <status v-for="status in conversation" :key="status.id" v-bind:statusoid="status":expandable='false'></status> + <status v-for="status in conversation" :key="status.id" v-bind:statusoid="status":expandable='false':focused="focused(status.id)"></status> </div> </div> </div> diff --git a/src/components/status/status.js b/src/components/status/status.js index 9550c19f..f1682e68 100644 --- a/src/components/status/status.js +++ b/src/components/status/status.js @@ -9,7 +9,8 @@ import { toInteger } from 'lodash' const Status = { props: [ 'statusoid', - 'expandable' + 'expandable', + 'focused' ], data: () => ({ replying: false, @@ -32,10 +33,6 @@ const Status = { return !!this.$store.state.users.currentUser }, muted () { return !this.unmuted && this.status.user.muted }, - focused () { - const id = toInteger(this.$route.params.id) - return (this.statusoid.id == id) - }, isReply () { return !!this.statusoid.in_reply_to_status_id } }, components: { diff --git a/src/components/status_or_conversation/status_or_conversation.vue b/src/components/status_or_conversation/status_or_conversation.vue index 4fabfab2..4aaaf2ff 100644 --- a/src/components/status_or_conversation/status_or_conversation.vue +++ b/src/components/status_or_conversation/status_or_conversation.vue @@ -1,7 +1,7 @@ <template> <div> <conversation v-if="expanded" @toggleExpanded="toggleExpanded" :collapsable="true" :statusoid="statusoid"></conversation> - <status v-if="!expanded" @toggleExpanded="toggleExpanded" :expandable="true" :statusoid="statusoid"></status> + <status v-if="!expanded" @toggleExpanded="toggleExpanded" :expandable="true" :statusoid="statusoid" :focused="false"></status> </div> </template> From 42ae57dace4089141e2f31351119dc80d8e7cc4d Mon Sep 17 00:00:00 2001 From: wakarimasen <wakarimasen@airmail.cc> Date: Sun, 5 Mar 2017 16:49:45 +0100 Subject: [PATCH 04/24] Remove unused import --- src/components/status/status.js | 1 - 1 file changed, 1 deletion(-) diff --git a/src/components/status/status.js b/src/components/status/status.js index f1682e68..101a0e14 100644 --- a/src/components/status/status.js +++ b/src/components/status/status.js @@ -4,7 +4,6 @@ import RetweetButton from '../retweet_button/retweet_button.vue' import DeleteButton from '../delete_button/delete_button.vue' import PostStatusForm from '../post_status_form/post_status_form.vue' import UserCardContent from '../user_card_content/user_card_content.vue' -import { toInteger } from 'lodash' const Status = { props: [ From 51160f0fdb068f0c9a1133fe66286467b1f4fd78 Mon Sep 17 00:00:00 2001 From: Roger Braun <roger@rogerbraun.net> Date: Sun, 5 Mar 2017 20:36:22 +0100 Subject: [PATCH 05/24] Style fixes. --- src/components/conversation/conversation.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/conversation/conversation.js b/src/components/conversation/conversation.js index 524737fd..a598b521 100644 --- a/src/components/conversation/conversation.js +++ b/src/components/conversation/conversation.js @@ -48,8 +48,8 @@ const conversation = { .then(() => this.fetchConversation()) } }, - focused: function(id) { - return (id == this.statusoid.id) + focused: function (id) { + return (id === this.statusoid.id) } } } From 9d56721533d46cbd88db8e64edb8d6fddf61298e Mon Sep 17 00:00:00 2001 From: wakarimasen <wakarimasen@airmail.cc> Date: Mon, 6 Mar 2017 16:21:11 +0100 Subject: [PATCH 06/24] Highlight original notice when expanding retweets --- src/components/conversation/conversation.js | 6 +++++- src/components/status/status.js | 2 +- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/src/components/conversation/conversation.js b/src/components/conversation/conversation.js index a598b521..769893d3 100644 --- a/src/components/conversation/conversation.js +++ b/src/components/conversation/conversation.js @@ -49,7 +49,11 @@ const conversation = { } }, focused: function (id) { - return (id === this.statusoid.id) + if (!!this.statusoid.retweeted_status) { + return (id === this.statusoid.retweeted_status.id) + } else { + return (id === this.statusoid.id) + } } } } diff --git a/src/components/status/status.js b/src/components/status/status.js index 101a0e14..bc9d6e6c 100644 --- a/src/components/status/status.js +++ b/src/components/status/status.js @@ -32,7 +32,7 @@ const Status = { return !!this.$store.state.users.currentUser }, muted () { return !this.unmuted && this.status.user.muted }, - isReply () { return !!this.statusoid.in_reply_to_status_id } + isReply () { return !!this.status.in_reply_to_status_id } }, components: { Attachment, From 0bb1ec30d0ccba0fded1c6555d75297a6d9c83d2 Mon Sep 17 00:00:00 2001 From: Roger Braun <roger@rogerbraun.net> Date: Mon, 6 Mar 2017 19:55:08 +0100 Subject: [PATCH 07/24] Temporarily remove persistence. --- src/main.js | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/src/main.js b/src/main.js index 30929f0b..b448510e 100644 --- a/src/main.js +++ b/src/main.js @@ -17,8 +17,6 @@ import configModule from './modules/config.js' import VueTimeago from 'vue-timeago' -import createPersistedState from './lib/persisted_state.js' - Vue.use(Vuex) Vue.use(VueRouter) Vue.use(VueTimeago, { @@ -28,15 +26,6 @@ Vue.use(VueTimeago, { } }) -const persistedStateOptions = { - paths: [ - 'config.hideAttachments', - 'config.hideNsfw', - 'statuses.notifications', - 'users.users' - ] -} - const store = new Vuex.Store({ modules: { statuses: statusesModule, @@ -44,7 +33,6 @@ const store = new Vuex.Store({ api: apiModule, config: configModule }, - plugins: [createPersistedState(persistedStateOptions)], strict: process.env.NODE_ENV !== 'production' }) From 990047725a4f6c7bbda1e7519a605b1ae1ece51e Mon Sep 17 00:00:00 2001 From: Roger Braun <roger@rogerbraun.net> Date: Mon, 6 Mar 2017 19:57:00 +0100 Subject: [PATCH 08/24] Revert "Temporarily remove persistence." This reverts commit 0bb1ec30d0ccba0fded1c6555d75297a6d9c83d2. --- src/main.js | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/src/main.js b/src/main.js index b448510e..30929f0b 100644 --- a/src/main.js +++ b/src/main.js @@ -17,6 +17,8 @@ import configModule from './modules/config.js' import VueTimeago from 'vue-timeago' +import createPersistedState from './lib/persisted_state.js' + Vue.use(Vuex) Vue.use(VueRouter) Vue.use(VueTimeago, { @@ -26,6 +28,15 @@ Vue.use(VueTimeago, { } }) +const persistedStateOptions = { + paths: [ + 'config.hideAttachments', + 'config.hideNsfw', + 'statuses.notifications', + 'users.users' + ] +} + const store = new Vuex.Store({ modules: { statuses: statusesModule, @@ -33,6 +44,7 @@ const store = new Vuex.Store({ api: apiModule, config: configModule }, + plugins: [createPersistedState(persistedStateOptions)], strict: process.env.NODE_ENV !== 'production' }) From a4ebf44f242fd877506dcdadf035840168e16364 Mon Sep 17 00:00:00 2001 From: shpuld <shpuld@gmail.com> Date: Mon, 6 Mar 2017 23:51:39 +0200 Subject: [PATCH 09/24] Fix overlapping styles in timeline and notifications that screwed up user-card. --- src/components/notifications/notifications.scss | 2 +- src/components/notifications/notifications.vue | 2 +- src/components/timeline/timeline.vue | 17 +++++++++-------- 3 files changed, 11 insertions(+), 10 deletions(-) diff --git a/src/components/notifications/notifications.scss b/src/components/notifications/notifications.scss index 1378e730..8e3e0721 100644 --- a/src/components/notifications/notifications.scss +++ b/src/components/notifications/notifications.scss @@ -6,7 +6,7 @@ // force the text to stay centered, while keeping // the button in the right side of the panel heading position: relative; - button { + .read-button { position: absolute; padding: 0.1em 0.3em 0.25em 0.3em; right: 0.7em; diff --git a/src/components/notifications/notifications.vue b/src/components/notifications/notifications.vue index 180b86a1..e0dcb12d 100644 --- a/src/components/notifications/notifications.vue +++ b/src/components/notifications/notifications.vue @@ -3,7 +3,7 @@ <div class="panel panel-default base00-background"> <div class="panel-heading base01-background base04"> Notifications ({{unseenCount}}) - <button @click.prevent="markAsSeen" class="base06 base02-background">Read!</button> + <button @click.prevent="markAsSeen" class="base06 base02-background read-button">Read!</button> </div> <div class="panel-body"> <div v-for="notification in visibleNotifications" class="notification" :class='{"unseen": !notification.seen}'> diff --git a/src/components/timeline/timeline.vue b/src/components/timeline/timeline.vue index 8a302e18..11280a08 100644 --- a/src/components/timeline/timeline.vue +++ b/src/components/timeline/timeline.vue @@ -1,13 +1,13 @@ <template> <div class="timeline panel panel-default"> - <div class="panel-heading base01-background base04"> + <div class="panel-heading timeline-heading base01-background base04"> <div class="title"> {{title}} </div> - <button @click.prevent="showNewStatuses" class="base06 base02-background" v-if="timeline.newStatusCount > 0"> + <button @click.prevent="showNewStatuses" class="base06 base02-background loadmore-button" v-if="timeline.newStatusCount > 0"> Show new ({{timeline.newStatusCount}}) </button> - <button @click.prevent class="base04 base01-background no-press" v-if="!timeline.newStatusCount > 0"> + <button @click.prevent class="base04 base01-background no-press loadmore-button" v-if="!timeline.newStatusCount > 0"> Up-to-date </button> </div> @@ -26,17 +26,18 @@ <style lang="scss"> - .timeline .panel-heading { - position: relative; - display: flex; - + .timeline { + .timeline-heading { + position: relative; + display: flex; + } .title { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; max-width: 70%; } - button { + .loadmore-button { position: absolute; right: 0.6em; padding: 0.1em 0.3em 0.25em 0.3em; From 5143ae7f72c7535872188d9887dc61628add09fb Mon Sep 17 00:00:00 2001 From: Roger Braun <roger@rogerbraun.net> Date: Tue, 7 Mar 2017 08:43:48 +0100 Subject: [PATCH 10/24] Switch to localforage to use indexeddb by default. --- package.json | 2 +- src/lib/persisted_state.js | 47 ++++------------ yarn.lock | 112 +++++++++++++++++++++++++++++++++---- 3 files changed, 114 insertions(+), 47 deletions(-) diff --git a/package.json b/package.json index e7aa7241..74706389 100644 --- a/package.json +++ b/package.json @@ -17,7 +17,7 @@ "babel-plugin-lodash": "^3.2.11", "diff": "^3.0.1", "karma-mocha-reporter": "^2.2.1", - "lz-string": "^1.4.4", + "localforage": "^1.5.0", "node-sass": "^3.10.1", "object-path": "^0.11.3", "sanitize-html": "^1.13.0", diff --git a/src/lib/persisted_state.js b/src/lib/persisted_state.js index 6a17ca99..a518cb51 100644 --- a/src/lib/persisted_state.js +++ b/src/lib/persisted_state.js @@ -1,7 +1,7 @@ import merge from 'lodash.merge' import objectPath from 'object-path' +import localforage from 'localforage' import { throttle } from 'lodash' -import lzstring from 'lz-string' const defaultReducer = (state, paths) => ( paths.length === 0 ? state : paths.reduce((substate, path) => { @@ -11,32 +11,11 @@ const defaultReducer = (state, paths) => ( ) const defaultStorage = (() => { - const hasLocalStorage = typeof window !== 'undefined' && window.localStorage - if (hasLocalStorage) { - return window.localStorage - } - - class InternalStorage { - setItem (key, item) { - this[key] = item - return item - } - getItem (key) { - return this[key] - } - removeItem (key) { - delete this[key] - } - clear () { - Object.keys(this).forEach(key => delete this[key]) - } - } - - return new InternalStorage() + return localforage })() const defaultSetState = (key, state, storage) => { - return storage.setItem(key, lzstring.compressToUTF16(JSON.stringify(state))) + return storage.setItem(key, state) } export default function createPersistedState ({ @@ -44,12 +23,7 @@ export default function createPersistedState ({ paths = [], getState = (key, storage) => { let value = storage.getItem(key) - try { - value = lzstring.decompressFromUTF16(value) // inflate(value, { to: 'string' }) - } catch (e) { - console.log("Couldn't inflate value... Maybe upgrading") - } - return value && value !== 'undefined' ? JSON.parse(value) : undefined + return value }, setState = throttle(defaultSetState, 60000), reducer = defaultReducer, @@ -57,12 +31,13 @@ export default function createPersistedState ({ subscriber = store => handler => store.subscribe(handler) } = {}) { return store => { - const savedState = getState(key, storage) - if (typeof savedState === 'object') { - store.replaceState( - merge({}, store.state, savedState) - ) - } + getState(key, storage).then((savedState) => { + if (typeof savedState === 'object') { + store.replaceState( + merge({}, store.state, savedState) + ) + } + }) subscriber(store)((mutation, state) => { try { diff --git a/yarn.lock b/yarn.lock index 92f734bc..677c9690 100644 --- a/yarn.lock +++ b/yarn.lock @@ -23,6 +23,10 @@ acorn@4.0.4: version "4.0.4" resolved "https://registry.yarnpkg.com/acorn/-/acorn-4.0.4.tgz#17a8d6a7a6c4ef538b814ec9abac2779293bf30a" +acorn@^1.0.3: + version "1.2.2" + resolved "https://registry.yarnpkg.com/acorn/-/acorn-1.2.2.tgz#c8ce27de0acc76d896d2b1fad3df588d9e82f014" + acorn@^3.0.0, acorn@^3.0.4, acorn@^3.1.0: version "3.3.0" resolved "https://registry.yarnpkg.com/acorn/-/acorn-3.3.0.tgz#45e37fb39e8da3f25baee3ff5369e2bb5f22017a" @@ -193,11 +197,11 @@ ast-traverse@~0.1.1: version "0.1.1" resolved "https://registry.yarnpkg.com/ast-traverse/-/ast-traverse-0.1.1.tgz#69cf2b8386f19dcda1bb1e05d68fe359d8897de6" -ast-types@0.8.12: +ast-types@0.8.12, ast-types@0.x.x: version "0.8.12" resolved "https://registry.yarnpkg.com/ast-types/-/ast-types-0.8.12.tgz#a0d90e4351bb887716c83fd637ebf818af4adfcc" -ast-types@0.9.5, ast-types@0.x.x: +ast-types@0.9.5: version "0.9.5" resolved "https://registry.yarnpkg.com/ast-types/-/ast-types-0.9.5.tgz#1a660a09945dbceb1f9c9cbb715002617424e04a" @@ -836,6 +840,10 @@ balanced-match@^0.4.1, balanced-match@^0.4.2: version "0.4.2" resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-0.4.2.tgz#cb3f3e3c732dc0f01ee70b403f302e61d7709838" +base62@0.1.1: + version "0.1.1" + resolved "https://registry.yarnpkg.com/base62/-/base62-0.1.1.tgz#7b4174c2f94449753b11c2651c083da841a7b084" + base64-arraybuffer@0.1.5: version "0.1.5" resolved "https://registry.yarnpkg.com/base64-arraybuffer/-/base64-arraybuffer-0.1.5.tgz#73926771923b5a19747ad666aa5cd4bf9c6e9ce8" @@ -1815,6 +1823,14 @@ error-ex@^1.2.0: dependencies: is-arrayish "^0.2.1" +es3ify@^0.1.3: + version "0.1.4" + resolved "https://registry.yarnpkg.com/es3ify/-/es3ify-0.1.4.tgz#ad9fa5df1ae34f3f31e1211b5818b2d51078dfd1" + dependencies: + esprima-fb "~3001.0001.0000-dev-harmony-fb" + jstransform "~3.0.0" + through "~2.3.4" + es5-ext@^0.10.7, es5-ext@^0.10.8, es5-ext@~0.10.11, es5-ext@~0.10.2, es5-ext@~0.10.7: version "0.10.12" resolved "https://registry.yarnpkg.com/es5-ext/-/es5-ext-0.10.12.tgz#aa84641d4db76b62abba5e45fd805ecbab140047" @@ -1978,6 +1994,10 @@ eslint@^3.7.1: text-table "~0.2.0" user-home "^2.0.0" +esmangle-evaluator@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/esmangle-evaluator/-/esmangle-evaluator-1.0.1.tgz#620d866ef4861b3311f75766d52a8572bb3c6336" + espree@^3.4.0: version "3.4.0" resolved "https://registry.yarnpkg.com/espree/-/espree-3.4.0.tgz#41656fa5628e042878025ef467e78f125cb86e1d" @@ -1989,6 +2009,10 @@ esprima-fb@~15001.1001.0-dev-harmony-fb: version "15001.1001.0-dev-harmony-fb" resolved "https://registry.yarnpkg.com/esprima-fb/-/esprima-fb-15001.1001.0-dev-harmony-fb.tgz#43beb57ec26e8cf237d3dd8b33e42533577f2659" +esprima-fb@~3001.0001.0000-dev-harmony-fb, esprima-fb@~3001.1.0-dev-harmony-fb: + version "3001.1.0-dev-harmony-fb" + resolved "https://registry.yarnpkg.com/esprima-fb/-/esprima-fb-3001.0001.0000-dev-harmony-fb.tgz#b77d37abcd38ea0b77426bb8bc2922ce6b426411" + esprima@2.7.x, esprima@^2.1.0, esprima@^2.6.0, esprima@^2.7.1: version "2.7.3" resolved "https://registry.yarnpkg.com/esprima/-/esprima-2.7.3.tgz#96e3b70d5779f6ad49cd032673d1c312767ba581" @@ -2136,6 +2160,15 @@ extsprintf@1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/extsprintf/-/extsprintf-1.0.2.tgz#e1080e0658e300b06294990cc70e1502235fd550" +falafel@^1.0.1: + version "1.2.0" + resolved "https://registry.yarnpkg.com/falafel/-/falafel-1.2.0.tgz#c18d24ef5091174a497f318cd24b026a25cddab4" + dependencies: + acorn "^1.0.3" + foreach "^2.0.5" + isarray "0.0.1" + object-keys "^1.0.6" + fast-levenshtein@~2.0.4: version "2.0.6" resolved "https://registry.yarnpkg.com/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz#3d8a5c66883a16a30ca8643e851f19baa7797917" @@ -2248,6 +2281,10 @@ for-own@^0.1.4: dependencies: for-in "^0.1.5" +foreach@^2.0.5: + version "2.0.5" + resolved "https://registry.yarnpkg.com/foreach/-/foreach-2.0.5.tgz#0bee005018aeb260d0a3af3ae658dd0136ec1b99" + forever-agent@~0.6.1: version "0.6.1" resolved "https://registry.yarnpkg.com/forever-agent/-/forever-agent-0.6.1.tgz#fbc71f0c41adeb37f96c577ad1ed42d8fdacca91" @@ -2683,6 +2720,10 @@ ignore@^3.2.0: version "3.2.2" resolved "https://registry.yarnpkg.com/ignore/-/ignore-3.2.2.tgz#1c51e1ef53bab6ddc15db4d9ac4ec139eceb3410" +immediate@~3.0.5: + version "3.0.6" + resolved "https://registry.yarnpkg.com/immediate/-/immediate-3.0.6.tgz#9db1dbd0faf8de6fbe0f5dd5e56bb606280de69b" + imurmurhash@^0.1.4: version "0.1.4" resolved "https://registry.yarnpkg.com/imurmurhash/-/imurmurhash-0.1.4.tgz#9218b9b2b928a238b13dc4fb6b6d576f231453ea" @@ -2730,6 +2771,13 @@ inject-loader@^2.0.1: dependencies: loader-utils "^0.2.3" +inline-process-browser@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/inline-process-browser/-/inline-process-browser-1.0.0.tgz#46a61b153dd3c9b1624b1a00626edb4f7f414f22" + dependencies: + falafel "^1.0.1" + through2 "^0.6.5" + inquirer@^0.12.0: version "0.12.0" resolved "https://registry.yarnpkg.com/inquirer/-/inquirer-0.12.0.tgz#1ef2bfd63504df0bc75785fff8c2c41df12f077e" @@ -3092,6 +3140,14 @@ jsprim@^1.2.2: json-schema "0.2.3" verror "1.3.6" +jstransform@~3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/jstransform/-/jstransform-3.0.0.tgz#a2591ab6cee8d97bf3be830dbfa2313b87cd640b" + dependencies: + base62 "0.1.1" + esprima-fb "~3001.1.0-dev-harmony-fb" + source-map "0.1.31" + karma-coverage@^1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/karma-coverage/-/karma-coverage-1.1.1.tgz#5aff8b39cf6994dc22de4c84362c76001b637cf6" @@ -3214,6 +3270,15 @@ levn@^0.3.0, levn@~0.3.0: prelude-ls "~1.1.2" type-check "~0.3.2" +lie@3.0.2: + version "3.0.2" + resolved "https://registry.yarnpkg.com/lie/-/lie-3.0.2.tgz#ffda21d7bba26f377cad865d3649b2fc8ce39fea" + dependencies: + es3ify "^0.1.3" + immediate "~3.0.5" + inline-process-browser "^1.0.0" + unreachable-branch-transform "^0.3.0" + load-json-file@^1.0.0: version "1.1.0" resolved "https://registry.yarnpkg.com/load-json-file/-/load-json-file-1.1.0.tgz#956905708d58b4bab4c2261b04f59f31c99374c0" @@ -3233,6 +3298,12 @@ loader-utils@0.2.x, loader-utils@^0.2.10, loader-utils@^0.2.11, loader-utils@^0. json5 "^0.5.0" object-assign "^4.0.1" +localforage@^1.5.0: + version "1.5.0" + resolved "https://registry.yarnpkg.com/localforage/-/localforage-1.5.0.tgz#6b994e19b56611fa85df3992df397ac4ab66e815" + dependencies: + lie "3.0.2" + lodash._arraycopy@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/lodash._arraycopy/-/lodash._arraycopy-3.0.0.tgz#76e7b7c1f1fb92547374878a562ed06a3e50f6e1" @@ -3549,10 +3620,6 @@ lru-cache@~2.6.5: version "2.6.5" resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-2.6.5.tgz#e56d6354148ede8d7707b58d143220fd08df0fd5" -lz-string@^1.4.4: - version "1.4.4" - resolved "https://registry.yarnpkg.com/lz-string/-/lz-string-1.4.4.tgz#c0d8eaf36059f705796e1e344811cf4c498d3a26" - macaddress@^0.2.8: version "0.2.8" resolved "https://registry.yarnpkg.com/macaddress/-/macaddress-0.2.8.tgz#5904dc537c39ec6dbefeae902327135fa8511f12" @@ -3936,6 +4003,10 @@ object-hash@^1.1.4: version "1.1.5" resolved "https://registry.yarnpkg.com/object-hash/-/object-hash-1.1.5.tgz#bdd844e030d0861b692ca175c6cab6868ec233d7" +object-keys@^1.0.6: + version "1.0.11" + resolved "https://registry.yarnpkg.com/object-keys/-/object-keys-1.0.11.tgz#c54601778ad560f1142ce0e01bcca8b56d13426d" + object-path@^0.11.3: version "0.11.3" resolved "https://registry.yarnpkg.com/object-path/-/object-path-0.11.3.tgz#3e21a42ad07234d815429ae9e15c1c5f38050554" @@ -4597,7 +4668,7 @@ read-pkg@^1.0.0: normalize-package-data "^2.3.2" path-type "^1.0.0" -readable-stream@1.0, readable-stream@~1.0.2: +readable-stream@1.0, "readable-stream@>=1.0.33-1 <1.1.0-0", readable-stream@~1.0.2: version "1.0.34" resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-1.0.34.tgz#125820e34bc842d2f2aaafafe4c2916ee32c157c" dependencies: @@ -4667,7 +4738,7 @@ readline2@^1.0.1: is-fullwidth-code-point "^1.0.0" mute-stream "0.0.5" -recast@0.10.33: +recast@0.10.33, recast@^0.10.1: version "0.10.33" resolved "https://registry.yarnpkg.com/recast/-/recast-0.10.33.tgz#942808f7aa016f1fa7142c461d7e5704aaa8d697" dependencies: @@ -5143,6 +5214,12 @@ source-map-support@^0.4.2: dependencies: source-map "^0.5.3" +source-map@0.1.31: + version "0.1.31" + resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.1.31.tgz#9f704d0d69d9e138a81badf6ebb4fde33d151c61" + dependencies: + amdefine ">=0.0.4" + source-map@0.5.x, source-map@^0.5.0, source-map@^0.5.1, source-map@^0.5.3, source-map@^0.5.6, source-map@~0.5.0, source-map@~0.5.1, source-map@~0.5.3: version "0.5.6" resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.5.6.tgz#75ce38f52bf0733c5a7f0c118d81334a2bb5f412" @@ -5374,7 +5451,14 @@ throttleit@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/throttleit/-/throttleit-1.0.0.tgz#9e785836daf46743145a5984b6268d828528ac6c" -through@^2.3.6, through@~2.3.8: +through2@^0.6.2, through2@^0.6.5: + version "0.6.5" + resolved "https://registry.yarnpkg.com/through2/-/through2-0.6.5.tgz#41ab9c67b29d57209071410e1d7a7a968cd3ad48" + dependencies: + readable-stream ">=1.0.33-1 <1.1.0-0" + xtend ">=4.0.0 <4.1.0-0" + +through@^2.3.6, through@~2.3.4, through@~2.3.8: version "2.3.8" resolved "https://registry.yarnpkg.com/through/-/through-2.3.8.tgz#0dd4c9ffaabc357960b1b724115d7e0e86a2e1f5" @@ -5516,6 +5600,14 @@ unpipe@1.0.0, unpipe@~1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/unpipe/-/unpipe-1.0.0.tgz#b2bf4ee8514aae6165b4817829d21b2ef49904ec" +unreachable-branch-transform@^0.3.0: + version "0.3.0" + resolved "https://registry.yarnpkg.com/unreachable-branch-transform/-/unreachable-branch-transform-0.3.0.tgz#d99cc4c6e746d264928845b611db54b0f3474caa" + dependencies: + esmangle-evaluator "^1.0.0" + recast "^0.10.1" + through2 "^0.6.2" + upper-case@^1.1.1: version "1.1.3" resolved "https://registry.yarnpkg.com/upper-case/-/upper-case-1.1.3.tgz#f6b4501c2ec4cdd26ba78be7222961de77621598" @@ -5812,7 +5904,7 @@ xregexp@2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/xregexp/-/xregexp-2.0.0.tgz#52a63e56ca0b84a7f3a5f3d61872f126ad7a5943" -xtend@^4.0.0: +"xtend@>=4.0.0 <4.1.0-0", xtend@^4.0.0: version "4.0.1" resolved "https://registry.yarnpkg.com/xtend/-/xtend-4.0.1.tgz#a5c6d532be656e23db820efb943a1f04998d63af" From 58fdf9e70d56e23fe6c6cd73e31532ebde4caac1 Mon Sep 17 00:00:00 2001 From: shpuld <shpuld@gmail.com> Date: Tue, 7 Mar 2017 15:55:00 +0200 Subject: [PATCH 11/24] Put the number of notifications inside a red circle (kinda like qvitter and everyone else does), make the red notification line slightly less transparent. --- src/components/notifications/notifications.scss | 15 ++++++++++++++- src/components/notifications/notifications.vue | 3 ++- 2 files changed, 16 insertions(+), 2 deletions(-) diff --git a/src/components/notifications/notifications.scss b/src/components/notifications/notifications.scss index 8e3e0721..6ad7ec1e 100644 --- a/src/components/notifications/notifications.scss +++ b/src/components/notifications/notifications.scss @@ -13,6 +13,19 @@ } } + .unseen-count { + display: inline-block; + background-color: rgba(255, 16, 8, 0.8); + text-shadow: 0px 0px 3px rgba(0, 0, 0, 0.5); + min-width: 1.3em; + border-radius: 1.3em; + margin: 0 0.2em 0 -0.4em; + color: white; + font-size: 0.9em; + text-align: center; + line-height: 1.3em; + } + .notification { // Will have to use pixels here to ensure consistent distance with // pad alone and pad + border, browsers bad at rounding this with em, @@ -64,7 +77,7 @@ } .unseen { - border-left: 4px solid rgba(255, 48, 16, 0.65); + border-left: 4px solid rgba(255, 16, 8, 0.75); padding-left: 6px; } } diff --git a/src/components/notifications/notifications.vue b/src/components/notifications/notifications.vue index e0dcb12d..91f6cfdc 100644 --- a/src/components/notifications/notifications.vue +++ b/src/components/notifications/notifications.vue @@ -2,7 +2,8 @@ <div class="notifications"> <div class="panel panel-default base00-background"> <div class="panel-heading base01-background base04"> - Notifications ({{unseenCount}}) + <span class="unseen-count" v-if="unseenCount">{{unseenCount}}</span> + Notifications <button @click.prevent="markAsSeen" class="base06 base02-background read-button">Read!</button> </div> <div class="panel-body"> From ae570ea0ff10853d96465c59bbc6ab9e086786b2 Mon Sep 17 00:00:00 2001 From: shpuld <shpuld@gmail.com> Date: Tue, 7 Mar 2017 16:00:45 +0200 Subject: [PATCH 12/24] Put conversation 'Collapse' back on the same line with the rest of the heading.' --- src/components/conversation/conversation.vue | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/components/conversation/conversation.vue b/src/components/conversation/conversation.vue index 5f395f46..33a43e15 100644 --- a/src/components/conversation/conversation.vue +++ b/src/components/conversation/conversation.vue @@ -1,10 +1,10 @@ <template> <div class="timeline panel panel-default base00-background"> - <div class="panel-heading base01-background base04" style="justify-content:space-between;"> + <div class="panel-heading base01-background base04"> Conversation - <div v-if="collapsable"> + <span v-if="collapsable" style="float:right;"> <small><a href="#" @click.prevent="$emit('toggleExpanded')">Collapse</a></small> - </div> + </span> </div> <div class="panel-body"> <div class="timeline"> From 086dd832d3d2c8103e7d6f99d72d27bea97ce7b0 Mon Sep 17 00:00:00 2001 From: wakarimasen <wakarimasen@airmail.cc> Date: Tue, 7 Mar 2017 17:27:12 +0100 Subject: [PATCH 13/24] Visual feedback on failure to fetch new statuses --- src/components/timeline/timeline.vue | 10 ++++++++-- src/modules/statuses.js | 18 ++++++++++++++---- .../timeline_fetcher.service.js | 11 +++++++++++ 3 files changed, 33 insertions(+), 6 deletions(-) diff --git a/src/components/timeline/timeline.vue b/src/components/timeline/timeline.vue index 11280a08..ac074f3c 100644 --- a/src/components/timeline/timeline.vue +++ b/src/components/timeline/timeline.vue @@ -4,10 +4,13 @@ <div class="title"> {{title}} </div> - <button @click.prevent="showNewStatuses" class="base06 base02-background loadmore-button" v-if="timeline.newStatusCount > 0"> + <button @click.prevent="showNewStatuses" class="base06 base02-background loadmore-button" v-if="timeline.newStatusCount > 0 && !timeline.error"> Show new ({{timeline.newStatusCount}}) </button> - <button @click.prevent class="base04 base01-background no-press loadmore-button" v-if="!timeline.newStatusCount > 0"> + <button @click.prevent class="base06 error no-press loadmore-button" v-if="timeline.error"> + Error fetching updates + </button> + <button @click.prevent class="base04 base01-background no-press loadmore-button" v-if="!timeline.newStatusCount > 0 && !timeline.error"> Up-to-date </button> </div> @@ -43,6 +46,9 @@ padding: 0.1em 0.3em 0.25em 0.3em; min-width: 6em; } + .error { + background-color: rgba(255, 48, 16, 0.65); + } .no-press { opacity: 0.8; cursor: default; diff --git a/src/modules/statuses.js b/src/modules/statuses.js index 491d0024..b19109b2 100644 --- a/src/modules/statuses.js +++ b/src/modules/statuses.js @@ -15,7 +15,8 @@ export const defaultState = { newStatusCount: 0, maxId: 0, minVisibleId: 0, - loading: false + loading: false, + error: false }, public: { statuses: [], @@ -24,7 +25,8 @@ export const defaultState = { newStatusCount: 0, maxId: 0, minVisibleId: 0, - loading: false + loading: false, + error: false }, publicAndExternal: { statuses: [], @@ -33,7 +35,8 @@ export const defaultState = { newStatusCount: 0, maxId: 0, minVisibleId: 0, - loading: false + loading: false, + error: false }, friends: { statuses: [], @@ -42,7 +45,8 @@ export const defaultState = { newStatusCount: 0, maxId: 0, minVisibleId: 0, - loading: false + loading: false, + error: false } } } @@ -280,6 +284,9 @@ export const mutations = { const newStatus = find(state.allStatuses, { id }) newStatus.nsfw = nsfw }, + setError (state, { timeline, value }) { + state.timelines[timeline].error = value + }, markNotificationsAsSeen (state, notifications) { each(notifications, (notification) => { notification.seen = true @@ -293,6 +300,9 @@ const statuses = { addNewStatuses ({ rootState, commit }, { statuses, showImmediately = false, timeline = false, noIdUpdate = false }) { commit('addNewStatuses', { statuses, showImmediately, timeline, noIdUpdate, user: rootState.users.currentUser }) }, + setError ({ rootState, commit }, { timeline, value }) { + commit('setError', { timeline, value }) + }, deleteStatus ({ rootState, commit }, status) { commit('setDeleted', { status }) apiService.deleteStatus({ id: status.id, credentials: rootState.users.currentUser.credentials }) diff --git a/src/services/timeline_fetcher/timeline_fetcher.service.js b/src/services/timeline_fetcher/timeline_fetcher.service.js index 37bbcd82..40f568c3 100644 --- a/src/services/timeline_fetcher/timeline_fetcher.service.js +++ b/src/services/timeline_fetcher/timeline_fetcher.service.js @@ -12,6 +12,15 @@ const update = ({store, statuses, timeline, showImmediately}) => { }) } +const setError = ({store, timeline, value}) => { + const ccTimeline = camelCase(timeline) + + store.dispatch('setError', { + timeline: ccTimeline, + value + }) +} + const fetchAndUpdate = ({store, credentials, timeline = 'friends', older = false, showImmediately = false}) => { const args = { timeline, credentials } const rootState = store.rootState || store.state @@ -25,6 +34,8 @@ const fetchAndUpdate = ({store, credentials, timeline = 'friends', older = false return apiService.fetchTimeline(args) .then((statuses) => update({store, statuses, timeline, showImmediately})) + .then(() => setError({store, timeline, value: false})) + .catch(() => setError({store, timeline, value: true})) } const startFetching = ({ timeline = 'friends', credentials, store }) => { From 7d522583780cae5746112f204b3f8c604d5a87bd Mon Sep 17 00:00:00 2001 From: wakarimasen <wakarimasen@airmail.cc> Date: Tue, 7 Mar 2017 21:38:55 +0100 Subject: [PATCH 14/24] Move rejection handler --- src/services/timeline_fetcher/timeline_fetcher.service.js | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/services/timeline_fetcher/timeline_fetcher.service.js b/src/services/timeline_fetcher/timeline_fetcher.service.js index 40f568c3..e684a170 100644 --- a/src/services/timeline_fetcher/timeline_fetcher.service.js +++ b/src/services/timeline_fetcher/timeline_fetcher.service.js @@ -5,6 +5,8 @@ import apiService from '../api/api.service.js' const update = ({store, statuses, timeline, showImmediately}) => { const ccTimeline = camelCase(timeline) + setError({store, timeline, value: false}) + store.dispatch('addNewStatuses', { timeline: ccTimeline, statuses, @@ -33,9 +35,8 @@ const fetchAndUpdate = ({store, credentials, timeline = 'friends', older = false } return apiService.fetchTimeline(args) - .then((statuses) => update({store, statuses, timeline, showImmediately})) - .then(() => setError({store, timeline, value: false})) - .catch(() => setError({store, timeline, value: true})) + .then((statuses) => update({store, statuses, timeline, showImmediately}), + () => setError({store, timeline, value: true})) } const startFetching = ({ timeline = 'friends', credentials, store }) => { From ccbfc64bfcaf5efd598af6987e304291e23def1c Mon Sep 17 00:00:00 2001 From: Roger Braun <roger@rogerbraun.net> Date: Wed, 8 Mar 2017 16:54:06 +0100 Subject: [PATCH 15/24] Don't redirect after login. --- src/components/login_form/login_form.js | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/components/login_form/login_form.js b/src/components/login_form/login_form.js index b55f770f..827c704c 100644 --- a/src/components/login_form/login_form.js +++ b/src/components/login_form/login_form.js @@ -7,9 +7,7 @@ const LoginForm = { }, methods: { submit () { - this.$store.dispatch('loginUser', this.user).then(() => { - this.$router.push('/main/friends') - }) + this.$store.dispatch('loginUser', this.user) } } } From bde12418433f3ae9d2ad3781343f334caf58cc95 Mon Sep 17 00:00:00 2001 From: Roger Braun <roger@rogerbraun.net> Date: Wed, 8 Mar 2017 17:58:49 +0100 Subject: [PATCH 16/24] eslint fixes. --- src/components/conversation/conversation.js | 2 +- test/unit/specs/modules/statuses.spec.js | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/src/components/conversation/conversation.js b/src/components/conversation/conversation.js index 769893d3..281b0183 100644 --- a/src/components/conversation/conversation.js +++ b/src/components/conversation/conversation.js @@ -49,7 +49,7 @@ const conversation = { } }, focused: function (id) { - if (!!this.statusoid.retweeted_status) { + if (this.statusoid.retweeted_status) { return (id === this.statusoid.retweeted_status.id) } else { return (id === this.statusoid.id) diff --git a/test/unit/specs/modules/statuses.spec.js b/test/unit/specs/modules/statuses.spec.js index b1581e03..891423ca 100644 --- a/test/unit/specs/modules/statuses.spec.js +++ b/test/unit/specs/modules/statuses.spec.js @@ -1,6 +1,7 @@ import { cloneDeep } from 'lodash' import { defaultState, mutations, findMaxId, prepareStatus, statusType } from '../../../../src/modules/statuses.js' +// eslint-disable-next-line camelcase const makeMockStatus = ({id, text, is_post_verb = true}) => { return { id, From 480a1ba253d49d4df36fbc64192e1260fc51a181 Mon Sep 17 00:00:00 2001 From: Roger Braun <roger@rogerbraun.net> Date: Wed, 8 Mar 2017 17:59:12 +0100 Subject: [PATCH 17/24] Use cache to quickly access users. --- src/lib/persisted_state.js | 9 ++++++++- src/modules/users.js | 10 ++++++---- 2 files changed, 14 insertions(+), 5 deletions(-) diff --git a/src/lib/persisted_state.js b/src/lib/persisted_state.js index a518cb51..a47ad7d5 100644 --- a/src/lib/persisted_state.js +++ b/src/lib/persisted_state.js @@ -1,7 +1,7 @@ import merge from 'lodash.merge' import objectPath from 'object-path' import localforage from 'localforage' -import { throttle } from 'lodash' +import { throttle, each } from 'lodash' const defaultReducer = (state, paths) => ( paths.length === 0 ? state : paths.reduce((substate, path) => { @@ -33,6 +33,13 @@ export default function createPersistedState ({ return store => { getState(key, storage).then((savedState) => { if (typeof savedState === 'object') { + // build user cache + const usersState = savedState.users || {} + usersState.usersObject = {} + const users = usersState.users || [] + each(users, (user) => { usersState.usersObject[user.id] = user }) + savedState.users = usersState + store.replaceState( merge({}, store.state, savedState) ) diff --git a/src/modules/users.js b/src/modules/users.js index 31731880..dc910c91 100644 --- a/src/modules/users.js +++ b/src/modules/users.js @@ -3,9 +3,9 @@ import { compact, map, each, find, merge } from 'lodash' import { set } from 'vue' // TODO: Unify with mergeOrAdd in statuses.js -export const mergeOrAdd = (arr, item) => { +export const mergeOrAdd = (arr, obj, item) => { if (!item) { return false } - const oldItem = find(arr, {id: item.id}) + const oldItem = obj[item.id] if (oldItem) { // We already have this, so only merge the new info. merge(oldItem, item) @@ -13,6 +13,7 @@ export const mergeOrAdd = (arr, item) => { } else { // This is a new item, prepare it arr.push(item) + obj[item.id] = item return {item, new: true} } } @@ -32,7 +33,7 @@ export const mutations = { state.loggingIn = false }, addNewUsers (state, users) { - each(users, (user) => mergeOrAdd(state.users, user)) + each(users, (user) => mergeOrAdd(state.users, state.usersObject, user)) }, setUserForStatus (state, status) { status.user = find(state.users, status.user) @@ -42,7 +43,8 @@ export const mutations = { export const defaultState = { currentUser: false, loggingIn: false, - users: [] + users: [], + usersObject: {} } const users = { From 5699872bb5e05b5dc576c75cac2c79b7ece3f2ac Mon Sep 17 00:00:00 2001 From: Roger Braun <roger@rogerbraun.net> Date: Wed, 8 Mar 2017 18:04:21 +0100 Subject: [PATCH 18/24] Use user cache in users module. --- src/modules/users.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/modules/users.js b/src/modules/users.js index dc910c91..9367ec68 100644 --- a/src/modules/users.js +++ b/src/modules/users.js @@ -1,5 +1,5 @@ import backendInteractorService from '../services/backend_interactor_service/backend_interactor_service.js' -import { compact, map, each, find, merge } from 'lodash' +import { compact, map, each, merge } from 'lodash' import { set } from 'vue' // TODO: Unify with mergeOrAdd in statuses.js @@ -20,7 +20,7 @@ export const mergeOrAdd = (arr, obj, item) => { export const mutations = { setMuted (state, { user: {id}, muted }) { - const user = find(state.users, {id}) + const user = state.usersObject[id] set(user, 'muted', muted) }, setCurrentUser (state, user) { @@ -36,7 +36,7 @@ export const mutations = { each(users, (user) => mergeOrAdd(state.users, state.usersObject, user)) }, setUserForStatus (state, status) { - status.user = find(state.users, status.user) + status.user = state.usersObject[status.user.id] } } From a6b6fe95c0fe2aa60ebbfca87fde47e629035c49 Mon Sep 17 00:00:00 2001 From: wakarimasen <wakarimasen@airmail.cc> Date: Wed, 8 Mar 2017 18:28:41 +0100 Subject: [PATCH 19/24] Show visual feedback on login error, redirect on success --- src/components/login_form/login_form.js | 8 ++- src/components/login_form/login_form.vue | 9 ++++ src/modules/users.js | 68 +++++++++++++----------- 3 files changed, 53 insertions(+), 32 deletions(-) diff --git a/src/components/login_form/login_form.js b/src/components/login_form/login_form.js index 827c704c..2ad5b0b5 100644 --- a/src/components/login_form/login_form.js +++ b/src/components/login_form/login_form.js @@ -1,13 +1,17 @@ const LoginForm = { data: () => ({ - user: {} + user: {}, + authError: false }), computed: { loggingIn () { return this.$store.state.users.loggingIn } }, methods: { submit () { - this.$store.dispatch('loginUser', this.user) + this.$store.dispatch('loginUser', this.user).then( + () => { this.$router.push('/main/friends')}, + () => { this.authError = true } + ) } } } diff --git a/src/components/login_form/login_form.vue b/src/components/login_form/login_form.vue index c0273bae..279469ee 100644 --- a/src/components/login_form/login_form.vue +++ b/src/components/login_form/login_form.vue @@ -17,6 +17,9 @@ <div class='form-group'> <button :disabled="loggingIn" type='submit' class='btn btn-default base05 base01-background'>Submit</button> </div> + <div v-if="authError" class='form-group'> + <button disabled='true' class='btn btn-default base05 error'>Error logging in, try again</button> + </div> </form> </div> </div> @@ -39,6 +42,12 @@ margin-top: 1.0em; min-height: 28px; } + + .error { + margin-top: 0em; + margin-bottom: 0em; + background-color: rgba(255, 48, 16, 0.65); + } } </style> diff --git a/src/modules/users.js b/src/modules/users.js index 31731880..a5274480 100644 --- a/src/modules/users.js +++ b/src/modules/users.js @@ -65,40 +65,48 @@ const users = { }) }, loginUser (store, userCredentials) { - const commit = store.commit - commit('beginLogin') - store.rootState.api.backendInteractor.verifyCredentials(userCredentials) - .then((response) => { - if (response.ok) { - response.json() - .then((user) => { - user.credentials = userCredentials - commit('setCurrentUser', user) - commit('addNewUsers', [user]) + return new Promise((resolve, reject) => { + const commit = store.commit + commit('beginLogin') + store.rootState.api.backendInteractor.verifyCredentials(userCredentials) + .then((response) => { + if (response.ok) { + response.json() + .then((user) => { + user.credentials = userCredentials + commit('setCurrentUser', user) + commit('addNewUsers', [user]) - // Set our new backend interactor - commit('setBackendInteractor', backendInteractorService(userCredentials)) + // Set our new backend interactor + commit('setBackendInteractor', backendInteractorService(userCredentials)) - // Start getting fresh tweets. - store.dispatch('startFetching', 'friends') + // Start getting fresh tweets. + store.dispatch('startFetching', 'friends') - // Get user mutes and follower info - store.rootState.api.backendInteractor.fetchMutes().then((mutedUsers) => { - each(mutedUsers, (user) => { user.muted = true }) - store.commit('addNewUsers', mutedUsers) + // Get user mutes and follower info + store.rootState.api.backendInteractor.fetchMutes().then((mutedUsers) => { + each(mutedUsers, (user) => { user.muted = true }) + store.commit('addNewUsers', mutedUsers) + }) + + // Fetch our friends + store.rootState.api.backendInteractor.fetchFriends() + .then((friends) => commit('addNewUsers', friends)) }) - - // Fetch our friends - store.rootState.api.backendInteractor.fetchFriends() - .then((friends) => commit('addNewUsers', friends)) - }) - } - commit('endLogin') - }) - .catch((error) => { - console.log(error) - commit('endLogin') - }) + } else { + // Authentication failed + commit('endLogin') + reject() + } + commit('endLogin') + resolve() + }) + .catch((error) => { + console.log(error) + commit('endLogin') + reject() + }) + }) } } } From c0e8111d642ca9f85fbb4091f2ac9e86f4238a58 Mon Sep 17 00:00:00 2001 From: wakarimasen <wakarimasen@airmail.cc> Date: Wed, 8 Mar 2017 19:08:01 +0100 Subject: [PATCH 20/24] Clear username and password field on failed login --- src/components/login_form/login_form.js | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/components/login_form/login_form.js b/src/components/login_form/login_form.js index 2ad5b0b5..e489f381 100644 --- a/src/components/login_form/login_form.js +++ b/src/components/login_form/login_form.js @@ -10,7 +10,11 @@ const LoginForm = { submit () { this.$store.dispatch('loginUser', this.user).then( () => { this.$router.push('/main/friends')}, - () => { this.authError = true } + () => { + this.authError = true + this.user.username = '' + this.user.password = '' + } ) } } From ccc460bb5ed1c8b7338f8a26bdb3029c74b26024 Mon Sep 17 00:00:00 2001 From: wakarimasen <wakarimasen@airmail.cc> Date: Wed, 8 Mar 2017 19:22:56 +0100 Subject: [PATCH 21/24] Give more specific reason for failed login --- src/components/login_form/login_form.js | 4 ++-- src/components/login_form/login_form.vue | 2 +- src/modules/users.js | 8 ++++++-- 3 files changed, 9 insertions(+), 5 deletions(-) diff --git a/src/components/login_form/login_form.js b/src/components/login_form/login_form.js index e489f381..bc801397 100644 --- a/src/components/login_form/login_form.js +++ b/src/components/login_form/login_form.js @@ -10,8 +10,8 @@ const LoginForm = { submit () { this.$store.dispatch('loginUser', this.user).then( () => { this.$router.push('/main/friends')}, - () => { - this.authError = true + (error) => { + this.authError = error this.user.username = '' this.user.password = '' } diff --git a/src/components/login_form/login_form.vue b/src/components/login_form/login_form.vue index 279469ee..8a32e064 100644 --- a/src/components/login_form/login_form.vue +++ b/src/components/login_form/login_form.vue @@ -18,7 +18,7 @@ <button :disabled="loggingIn" type='submit' class='btn btn-default base05 base01-background'>Submit</button> </div> <div v-if="authError" class='form-group'> - <button disabled='true' class='btn btn-default base05 error'>Error logging in, try again</button> + <button disabled='true' class='btn btn-default base05 error'>{{authError}}</button> </div> </form> </div> diff --git a/src/modules/users.js b/src/modules/users.js index a5274480..482c3b14 100644 --- a/src/modules/users.js +++ b/src/modules/users.js @@ -96,7 +96,11 @@ const users = { } else { // Authentication failed commit('endLogin') - reject() + if (response.status === 401) { + reject('Wrong username or password') + } else { + reject('An error occured, please try again') + } } commit('endLogin') resolve() @@ -104,7 +108,7 @@ const users = { .catch((error) => { console.log(error) commit('endLogin') - reject() + reject('Failed to connect to server, try again') }) }) } From 0810b2d51a6f0fbbfe1604f6d1954cde8ed08290 Mon Sep 17 00:00:00 2001 From: wakarimasen <wakarimasen@airmail.cc> Date: Wed, 8 Mar 2017 19:31:39 +0100 Subject: [PATCH 22/24] Fix typo --- src/modules/users.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/modules/users.js b/src/modules/users.js index 482c3b14..51643bd1 100644 --- a/src/modules/users.js +++ b/src/modules/users.js @@ -99,7 +99,7 @@ const users = { if (response.status === 401) { reject('Wrong username or password') } else { - reject('An error occured, please try again') + reject('An error occurred, please try again') } } commit('endLogin') From 0d39ed809b17dc7a3304f9a659c3d5e99d960f26 Mon Sep 17 00:00:00 2001 From: Roger Braun <roger@rogerbraun.net> Date: Wed, 8 Mar 2017 21:04:48 +0100 Subject: [PATCH 23/24] Add caching system to statuses. --- src/modules/statuses.js | 34 ++++++++++++++++++++++++---------- 1 file changed, 24 insertions(+), 10 deletions(-) diff --git a/src/modules/statuses.js b/src/modules/statuses.js index b19109b2..e4528520 100644 --- a/src/modules/statuses.js +++ b/src/modules/statuses.js @@ -4,14 +4,17 @@ import apiService from '../services/api/api.service.js' export const defaultState = { allStatuses: [], + allStatusesObject: {}, maxId: 0, notifications: [], favorites: new Set(), timelines: { mentions: { statuses: [], + statusesObject: {}, faves: [], visibleStatuses: [], + visibleStatusesObject: {}, newStatusCount: 0, maxId: 0, minVisibleId: 0, @@ -20,8 +23,10 @@ export const defaultState = { }, public: { statuses: [], + statusesObject: {}, faves: [], visibleStatuses: [], + visibleStatusesObject: {}, newStatusCount: 0, maxId: 0, minVisibleId: 0, @@ -30,8 +35,10 @@ export const defaultState = { }, publicAndExternal: { statuses: [], + statusesObject: {}, faves: [], visibleStatuses: [], + visibleStatusesObject: {}, newStatusCount: 0, maxId: 0, minVisibleId: 0, @@ -40,8 +47,10 @@ export const defaultState = { }, friends: { statuses: [], + statusesObject: {}, faves: [], visibleStatuses: [], + visibleStatusesObject: {}, newStatusCount: 0, maxId: 0, minVisibleId: 0, @@ -91,8 +100,9 @@ export const findMaxId = (...args) => { return (maxBy(flatten(args), 'id') || {}).id } -const mergeOrAdd = (arr, item) => { - const oldItem = find(arr, {id: item.id}) +const mergeOrAdd = (arr, obj, item) => { + const oldItem = obj[item.id] + if (oldItem) { // We already have this, so only merge the new info. merge(oldItem, item) @@ -103,6 +113,7 @@ const mergeOrAdd = (arr, item) => { // This is a new item, prepare it prepareStatus(item) arr.push(item) + obj[item.id] = item return {item, new: true} } } @@ -122,6 +133,7 @@ const addNewStatuses = (state, { statuses, showImmediately = false, timeline, us } const allStatuses = state.allStatuses + const allStatusesObject = state.allStatusesObject const timelineObject = state.timelines[timeline] // Set the maxId to the new id if it's larger. @@ -131,7 +143,7 @@ const addNewStatuses = (state, { statuses, showImmediately = false, timeline, us } const addStatus = (status, showImmediately, addToTimeline = true) => { - const result = mergeOrAdd(allStatuses, status) + const result = mergeOrAdd(allStatuses, allStatusesObject, status) status = result.item if (result.new) { @@ -147,7 +159,7 @@ const addNewStatuses = (state, { statuses, showImmediately = false, timeline, us // Add the mention to the mentions timeline if (timelineObject !== mentions) { - mergeOrAdd(mentions.statuses, status) + mergeOrAdd(mentions.statuses, mentions.statusesObject, status) mentions.newStatusCount += 1 sortTimeline(mentions) @@ -161,13 +173,13 @@ const addNewStatuses = (state, { statuses, showImmediately = false, timeline, us let resultForCurrentTimeline // Some statuses should only be added to the global status repository. if (timeline && addToTimeline) { - resultForCurrentTimeline = mergeOrAdd(timelineObject.statuses, status) + resultForCurrentTimeline = mergeOrAdd(timelineObject.statuses, timelineObject.statusesObject, status) } if (timeline && showImmediately) { // Add it directly to the visibleStatuses, don't change // newStatusCount - mergeOrAdd(timelineObject.visibleStatuses, status) + mergeOrAdd(timelineObject.visibleStatuses, timelineObject.visibleStatusesObject, status) } else if (timeline && addToTimeline && resultForCurrentTimeline.new) { // Just change newStatuscount timelineObject.newStatusCount += 1 @@ -264,24 +276,26 @@ export const mutations = { oldTimeline.newStatusCount = 0 oldTimeline.visibleStatuses = slice(oldTimeline.statuses, 0, 50) + oldTimeline.visibleStatusesObject = {} + each(oldTimeline.visibleStatuses, (status) => { oldTimeline.visibleStatusesObject[status.id] = status }) }, setFavorited (state, { status, value }) { - const newStatus = find(state.allStatuses, status) + const newStatus = state.allStatusesObject[status.id] newStatus.favorited = value }, setRetweeted (state, { status, value }) { - const newStatus = find(state.allStatuses, status) + const newStatus = state.allStatusesObject[status.id] newStatus.repeated = value }, setDeleted (state, { status }) { - const newStatus = find(state.allStatuses, status) + const newStatus = state.allStatusesObject[status.id] newStatus.deleted = true }, setLoading (state, { timeline, value }) { state.timelines[timeline].loading = value }, setNsfw (state, { id, nsfw }) { - const newStatus = find(state.allStatuses, { id }) + const newStatus = state.allStatusesObject[id] newStatus.nsfw = nsfw }, setError (state, { timeline, value }) { From 9511691c9467e26c3237b4a2c936e8a757b3e515 Mon Sep 17 00:00:00 2001 From: shpuld <shpuld@gmail.com> Date: Thu, 9 Mar 2017 02:21:23 +0200 Subject: [PATCH 24/24] Make the error into a div instead of a button to get rid of the hover effects. --- src/components/login_form/login_form.vue | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/components/login_form/login_form.vue b/src/components/login_form/login_form.vue index 8a32e064..b2fa5341 100644 --- a/src/components/login_form/login_form.vue +++ b/src/components/login_form/login_form.vue @@ -18,7 +18,7 @@ <button :disabled="loggingIn" type='submit' class='btn btn-default base05 base01-background'>Submit</button> </div> <div v-if="authError" class='form-group'> - <button disabled='true' class='btn btn-default base05 error'>{{authError}}</button> + <div class='error base05'>{{authError}}</div> </div> </form> </div> @@ -44,9 +44,11 @@ } .error { - margin-top: 0em; - margin-bottom: 0em; + border-radius: 5px; + text-align: center; background-color: rgba(255, 48, 16, 0.65); + min-height: 28px; + line-height: 28px; } }