feat: Adds a basic conversations timeline (#1137)

fixes #639
This commit is contained in:
Steve Genoud 2019-04-02 12:05:27 +02:00 committed by Nolan Lawson
parent 37a50dd8ea
commit 622dbde258
11 changed files with 89 additions and 7 deletions

View file

@ -16,6 +16,14 @@ function processMessage (instanceName, timelineName, message) {
case 'notification': case 'notification':
addStatusOrNotification(instanceName, 'notifications', JSON.parse(payload)) addStatusOrNotification(instanceName, 'notifications', JSON.parse(payload))
break break
case 'conversation':
// This is a hack in order to mostly fit the conversation model into
// a timeline of statuses. To have a clean implementation we would need to
// reproduce what is done for statuses for the conversation.
//
// It will add new DMs as new conversations intead of updating existing threads
addStatusOrNotification(instanceName, timelineName, JSON.parse(payload).last_status)
break
} }
stop('processMessage') stop('processMessage')
} }
@ -24,7 +32,12 @@ export function createStream (streamingApi, instanceName, accessToken,
timelineName, onOpenStream) { timelineName, onOpenStream) {
return new TimelineStream(streamingApi, accessToken, timelineName, { return new TimelineStream(streamingApi, accessToken, timelineName, {
onMessage (msg) { onMessage (msg) {
if (msg.event !== 'update' && msg.event !== 'delete' && msg.event !== 'notification') { if (
msg.event !== 'update' &&
msg.event !== 'delete' &&
msg.event !== 'notification' &&
msg.event !== 'conversation'
) {
console.error("don't know how to handle event", msg) console.error("don't know how to handle event", msg)
return return
} }

View file

@ -12,6 +12,8 @@ function getStreamName (timeline) {
return 'user' return 'user'
case 'notifications': case 'notifications':
return 'user:notification' return 'user:notification'
case 'conversations':
return 'direct'
} }
if (timeline.startsWith('tag/')) { if (timeline.startsWith('tag/')) {
return 'hashtag' return 'hashtag'

View file

@ -12,6 +12,8 @@ function getTimelineUrlPath (timeline) {
return 'notifications' return 'notifications'
case 'favorites': case 'favorites':
return 'favourites' return 'favourites'
case 'conversations':
return 'conversations'
} }
if (timeline.startsWith('tag/')) { if (timeline.startsWith('tag/')) {
return 'timelines/tag' return 'timelines/tag'
@ -61,5 +63,8 @@ export function getTimeline (instanceName, accessToken, timeline, maxId, since,
url += '?' + paramsString(params) url += '?' + paramsString(params)
return get(url, auth(accessToken), { timeout: DEFAULT_TIMEOUT }) const timelineRequest = get(url, auth(accessToken), { timeout: DEFAULT_TIMEOUT })
if (timeline !== 'conversations') return timelineRequest
return timelineRequest.then(items => items.map(item => item.last_status))
} }

View file

@ -4,6 +4,7 @@
<Shortcut key="g h" on:pressed="goto('/')"/> <Shortcut key="g h" on:pressed="goto('/')"/>
<Shortcut key="g n" on:pressed="goto('/notifications')"/> <Shortcut key="g n" on:pressed="goto('/notifications')"/>
<Shortcut key="g c" on:pressed="goto('/community')"/> <Shortcut key="g c" on:pressed="goto('/community')"/>
<Shortcut key="g d" on:pressed="goto('/conversations')"/>
<Shortcut key="s" on:pressed="goto('/search')"/> <Shortcut key="s" on:pressed="goto('/search')"/>
<Shortcut key="h|?" on:pressed="showShortcutHelpDialog()"/> <Shortcut key="h|?" on:pressed="showShortcutHelpDialog()"/>
<Shortcut key="c" on:pressed="showComposeDialog()"/> <Shortcut key="c" on:pressed="showComposeDialog()"/>

View file

@ -13,6 +13,7 @@
<li><kbd>g</kbd> + <kbd>l</kbd> to go to the local timeline</li> <li><kbd>g</kbd> + <kbd>l</kbd> to go to the local timeline</li>
<li><kbd>g</kbd> + <kbd>t</kbd> to go to the federated timeline</li> <li><kbd>g</kbd> + <kbd>t</kbd> to go to the federated timeline</li>
<li><kbd>g</kbd> + <kbd>c</kbd> to go to the community page</li> <li><kbd>g</kbd> + <kbd>c</kbd> to go to the community page</li>
<li><kbd>g</kbd> + <kbd>d</kbd> to go to the conversations page</li>
<li><kbd>h</kbd> or <kbd>?</kbd> to toggle the help dialog</li> <li><kbd>h</kbd> or <kbd>?</kbd> to toggle the help dialog</li>
<li><kbd>Backspace</kbd> to go back, close dialogs</li> <li><kbd>Backspace</kbd> to go back, close dialogs</li>
</ul> </ul>

View file

@ -73,10 +73,6 @@
border-bottom: 1px solid var(--main-border); border-bottom: 1px solid var(--main-border);
} }
.status-article.status-direct {
background-color: var(--status-direct-background);
}
.status-article:focus { .status-article:focus {
outline: none; /* focus is on the parent instead */ outline: none; /* focus is on the parent instead */
} }
@ -288,7 +284,6 @@
className: ({ visibility, timelineType, isStatusInOwnThread, $underlineLinks, $disableTapOnStatus }) => (classname( className: ({ visibility, timelineType, isStatusInOwnThread, $underlineLinks, $disableTapOnStatus }) => (classname(
'status-article', 'status-article',
'shortcut-list-item', 'shortcut-list-item',
visibility === 'direct' && 'status-direct',
timelineType !== 'search' && 'status-in-timeline', timelineType !== 'search' && 'status-in-timeline',
isStatusInOwnThread && 'status-in-own-thread', isStatusInOwnThread && 'status-in-own-thread',
$underlineLinks && 'underline-links', $underlineLinks && 'underline-links',

View file

@ -21,6 +21,11 @@
icon="#fa-star" icon="#fa-star"
pinnable="true" pinnable="true"
/> />
<PageListItem href="/conversations"
label="Conversations"
icon="#fa-envelope"
pinnable="true"
/>
</PageList> </PageList>
{#if $lists.length} {#if $lists.length}

View file

@ -0,0 +1,32 @@
{#if $isUserLoggedIn}
<TimelinePage timeline="conversations">
{#if $pinnedPage !== '/conversations'}
<DynamicPageBanner title="Conversations" icon="#fa-envelope"/>
{/if}
</TimelinePage>
{:else}
<HiddenFromSSR>
<FreeTextLayout>
<h1>Conversations</h1>
<p>Your conversations will appear here when logged in.</p>
</FreeTextLayout>
</HiddenFromSSR>
{/if}
<script>
import TimelinePage from '../_components/TimelinePage.html'
import FreeTextLayout from '../_components/FreeTextLayout.html'
import { store } from '../_store/store.js'
import HiddenFromSSR from '../_components/HiddenFromSSR'
import DynamicPageBanner from '../_components/DynamicPageBanner.html'
export default {
store: () => store,
components: {
TimelinePage,
FreeTextLayout,
HiddenFromSSR,
DynamicPageBanner
}
}
</script>

View file

@ -24,6 +24,13 @@ export function navComputations (store) {
svg: '#fa-globe', svg: '#fa-globe',
label: 'Federated' label: 'Federated'
} }
} else if (pinnedPage === '/conversations') {
pinnedPageObject = {
name: 'conversations',
href: '/conversations',
svg: '#fa-envelope',
label: 'Conversations'
}
} else if (pinnedPage === '/favorites') { } else if (pinnedPage === '/favorites') {
pinnedPageObject = { pinnedPageObject = {
name: 'favorites', name: 'favorites',

View file

@ -26,6 +26,7 @@ export function timelineObservers () {
!( !(
timeline !== 'local' && timeline !== 'local' &&
timeline !== 'federated' && timeline !== 'federated' &&
timeline !== 'conversations' &&
!timeline.startsWith('list/') && !timeline.startsWith('list/') &&
!timeline.startsWith('tag/') !timeline.startsWith('tag/')
) )

View file

@ -0,0 +1,20 @@
<Title name="Favorites" />
<LazyPage {pageComponent} {params} />
<script>
import Title from './_components/Title.html'
import LazyPage from './_components/LazyPage.html'
import pageComponent from './_pages/conversations.html'
export default {
components: {
Title,
LazyPage
},
data: () => ({
pageComponent
})
}
</script>