diff --git a/routes/_components/DynamicPageBanner.html b/routes/_components/DynamicPageBanner.html new file mode 100644 index 0000000..bed32f2 --- /dev/null +++ b/routes/_components/DynamicPageBanner.html @@ -0,0 +1,44 @@ +
+

{{title}}

+ +
+ + \ No newline at end of file diff --git a/routes/_components/NavItem.html b/routes/_components/NavItem.html index 821fc3e..02c781c 100644 --- a/routes/_components/NavItem.html +++ b/routes/_components/NavItem.html @@ -50,6 +50,9 @@ font-size: 16px; color: var(--nav-text-color); padding-left: 10px; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; } @media (max-width: 767px) { diff --git a/routes/_components/Status.html b/routes/_components/Status.html index 060cb7e..e4cf223 100644 --- a/routes/_components/Status.html +++ b/routes/_components/Status.html @@ -32,8 +32,8 @@ {{/if}} {{#if !status.spoiler_text || spoilerShown}} -
- {{{hydratedContent}}} +
+ {{{emojifiedContent}}}
{{/if}} {{#if originalMediaAttachments && originalMediaAttachments.length}} @@ -295,6 +295,9 @@ const relativeFormat = new IntlRelativeFormat('en-US'); export default { + oncreate() { + this.hashtagifyContent() + }, components: { Avatar, Media, @@ -311,15 +314,9 @@ originalStatus: (status) => status.reblog ? status.reblog : status, originalAccount: (originalStatus) => originalStatus.account, originalMediaAttachments: (originalStatus) => originalStatus.media_attachments, - hydratedContent: (originalStatus) => { + emojifiedContent: (originalStatus) => { let status = originalStatus let content = status.content - if (status.tags && status.tags.length) { - for (let tag of status.tags) { - let {name, url} = tag - content = replaceAll(content, url, `/tags/${name}`) - } - } if (status.emojis && status.emojis.length) { for (let emoji of status.emojis) { let { shortcode, url } = emoji @@ -337,11 +334,34 @@ methods: { onClickSpoilerButton() { this.set({spoilerShown: !this.get('spoilerShown')}) + this.hashtagifyContent() this.fire('recalculateHeight') }, onClickSensitiveMediaButton() { this.set({sensitiveShown: !this.get('sensitiveShown')}) this.fire('recalculateHeight') + }, + hashtagifyContent() { + if (!this.refs.contentNode) { + return + } + let status = this.get('originalStatus') + mark('hydrateHashtags') + if (status.tags && status.tags.length) { + let anchorTags = Array.from(this.refs.contentNode.querySelectorAll( + 'a[class~=hashtag][href^=http]')) + for (let tag of status.tags) { + let { name } = tag + for (let anchorTag of anchorTags) { + if (anchorTag.getAttribute('href').endsWith(`/tags/${name}`)) { + anchorTag.setAttribute('href', `/tags/${name}`) + anchorTag.removeAttribute('target') + anchorTag.removeAttribute('rel') + } + } + } + } + stop('hydrateHashtags') } } } diff --git a/routes/_components/Timeline.html b/routes/_components/Timeline.html index 76d3118..2744716 100644 --- a/routes/_components/Timeline.html +++ b/routes/_components/Timeline.html @@ -38,6 +38,22 @@ this.fire('initialized') })) }) + this.observe('statuses', statuses => { + let cachedAccountNames = this.store.get('cachedAccountNames') || {} + for (let status of statuses) { + cachedAccountNames[status.account.id] = { + username: status.account.username, + acct: status.account.acct + } + if (status.reblog) { + cachedAccountNames[status.reblog.account.id] = { + username: status.reblog.account.username, + acct: status.reblog.account.acct + } + } + } + this.store.set({'cachedAccountNames': cachedAccountNames}) + }) }, data: () => ({ StatusListItem: StatusListItem, @@ -58,6 +74,9 @@ } else if (timeline.startsWith('tag/')) { let tag = timeline.split('/').slice(-1)[0] return `#${tag} timeline for ${$currentInstance}` + } else if (timeline.startsWith('account/')) { + let account = timeline.split('/').slice(-1)[0] + return `Account #${account} on ${$currentInstance}` } } }, diff --git a/routes/_utils/database/database.js b/routes/_utils/database/database.js index 99725c2..4003043 100644 --- a/routes/_utils/database/database.js +++ b/routes/_utils/database/database.js @@ -1,6 +1,8 @@ +/* import worker from 'workerize-loader!./databaseCore' -const database = process.browser && worker() +export const database = process.browser && worker() +*/ -export { - database -} \ No newline at end of file +import * as dbCore from './databaseCore' + +export { dbCore as database } \ No newline at end of file diff --git a/routes/_utils/mastodon/timelines.js b/routes/_utils/mastodon/timelines.js index f37ca1c..0e30737 100644 --- a/routes/_utils/mastodon/timelines.js +++ b/routes/_utils/mastodon/timelines.js @@ -1,24 +1,29 @@ import { get, paramsString } from '../ajax' import { basename } from './utils' -function getTimelineUrlName(timeline) { +function getTimelineUrlPath(timeline) { switch (timeline) { case 'local': case 'federated': - return 'public' + return 'timelines/public' case 'home': - return 'home' - default: - return 'tag' + return 'timelines/home' + } + if (timeline.startsWith('tag/')) { + return 'timelines/tag' + } else if (timeline.startsWith('account/')) { + return 'accounts' } } export function getTimeline(instanceName, accessToken, timeline, maxId, since) { - let timelineUrlName = getTimelineUrlName(timeline) - let url = `${basename(instanceName)}/api/v1/timelines/${timelineUrlName}` + let timelineUrlName = getTimelineUrlPath(timeline) + let url = `${basename(instanceName)}/api/v1/${timelineUrlName}` if (timeline.startsWith('tag/')) { url += '/' + timeline.split('/').slice(-1)[0] + } else if (timeline.startsWith('account/')) { + url += '/' + timeline.split('/').slice(-1)[0] +'/statuses' } let params = {} diff --git a/routes/_utils/mastodon/user.js b/routes/_utils/mastodon/user.js index 056f862..ee14ede 100644 --- a/routes/_utils/mastodon/user.js +++ b/routes/_utils/mastodon/user.js @@ -6,4 +6,11 @@ export function getVerifyCredentials(instanceName, accessToken) { return get(url, { 'Authorization': `Bearer ${accessToken}` }) +} + +export function getAccount(instanceName, accessToken, accountId) { + let url = `${basename(instanceName)}/api/v1/accounts/${accountId}` + return get(url, { + 'Authorization': `Bearer ${accessToken}` + }) } \ No newline at end of file diff --git a/routes/_utils/store.js b/routes/_utils/store.js index acf988c..5114569 100644 --- a/routes/_utils/store.js +++ b/routes/_utils/store.js @@ -1,5 +1,9 @@ import { Store } from 'svelte/store.js' +const DONT_STORE_THESE_KEYS = [ + 'cachedAccountNames' +] + const LS = process.browser && localStorage class LocalStorageStore extends Store { @@ -18,7 +22,8 @@ class LocalStorageStore extends Store { this.set(newState) this.onchange((state, changed) => { Object.keys(changed).forEach(change => { - if (!this._computed[change]) { // TODO: better way to ignore computed values? + if (!DONT_STORE_THESE_KEYS.includes(change) && + !this._computed[change]) { // TODO: better way to ignore computed values? this.lastChanged[change] = true } }) @@ -74,6 +79,12 @@ store.compute( }, loggedInInstances[currentInstance]) }) +store.compute( + 'accessToken', + ['currentInstanceData'], + (currentInstanceData) => currentInstanceData.access_token +) + store.compute( 'currentTheme', ['currentInstance', 'instanceThemes'], diff --git a/routes/accounts/[accountId].html b/routes/accounts/[accountId].html new file mode 100644 index 0000000..dc6816f --- /dev/null +++ b/routes/accounts/[accountId].html @@ -0,0 +1,63 @@ +<:Head> + {{'Pinafore – ' + (cachedProfileName || profileName || '')}} + + + + {{#if $isUserLoggedIn}} + + + {{else}} + + +

Profile

+ +

A user timeline will appear here when logged in.

+
+
+ {{/if}} +
+ \ No newline at end of file diff --git a/routes/tags/[tagName].html b/routes/tags/[tagName].html index e29203e..96bfa61 100644 --- a/routes/tags/[tagName].html +++ b/routes/tags/[tagName].html @@ -8,10 +8,7 @@ dynamicLabel="{{'#' + params.tagName}}" dynamicIcon="#fa-hashtag" > {{#if $isUserLoggedIn}} -
-

{{'#' + params.tagName}}

- -
+ {{else}} @@ -23,37 +20,13 @@ {{/if}} - \ No newline at end of file