mirror of
https://github.com/styppo/hamstr.git
synced 2024-10-18 05:23:28 +00:00
* Add following feed
* Improve feed performance
This commit is contained in:
parent
7bdc8bd867
commit
171f53b5a1
193
src/components/Feed/Feed.vue
Normal file
193
src/components/Feed/Feed.vue
Normal file
@ -0,0 +1,193 @@
|
|||||||
|
<template>
|
||||||
|
<div class="feed">
|
||||||
|
<div class="load-more-container" :class="{'more-available': numUnreads}">
|
||||||
|
<AsyncLoadButton
|
||||||
|
v-if="numUnreads"
|
||||||
|
:load-fn="loadNewer"
|
||||||
|
:label="`Load ${numUnreads} unread`"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<Thread v-for="thread in visible" :key="thread[0].id" :thread="thread" class="full-width" />
|
||||||
|
|
||||||
|
<ListPlaceholder :count="visible.length" :loading="loading" />
|
||||||
|
|
||||||
|
<AsyncLoadButton
|
||||||
|
v-if="visible.length"
|
||||||
|
:load-fn="loadOlder"
|
||||||
|
autoload
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import AsyncLoadButton from 'components/AsyncLoadButton.vue'
|
||||||
|
import Thread from 'components/Post/Thread.vue'
|
||||||
|
import ListPlaceholder from 'components/ListPlaceholder.vue'
|
||||||
|
import {useAppStore} from 'stores/App'
|
||||||
|
import {useNostrStore} from 'src/nostr/NostrStore'
|
||||||
|
import DateUtils from 'src/utils/DateUtils'
|
||||||
|
import Bots from 'src/utils/bots'
|
||||||
|
|
||||||
|
const feedOrder = (a, b) => b[0].createdAt - a[0].createdAt
|
||||||
|
|
||||||
|
const MAX_ITEMS_VISIBLE = 25
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'Feed',
|
||||||
|
components: {
|
||||||
|
ListPlaceholder,
|
||||||
|
Thread,
|
||||||
|
AsyncLoadButton
|
||||||
|
},
|
||||||
|
props: {
|
||||||
|
feed: {
|
||||||
|
type: Object,
|
||||||
|
required: true,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
setup() {
|
||||||
|
return {
|
||||||
|
app: useAppStore(),
|
||||||
|
nostr: useNostrStore(),
|
||||||
|
}
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
visible: [],
|
||||||
|
newer: [],
|
||||||
|
older: [],
|
||||||
|
stream: null,
|
||||||
|
loading: true,
|
||||||
|
recentlyLoaded: true,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
numUnreads() {
|
||||||
|
if (this.recentlyLoaded) return 0
|
||||||
|
return this.newer.length
|
||||||
|
},
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
init() {
|
||||||
|
const filters = typeof this.feed.filters === 'function'
|
||||||
|
? this.feed.filters()
|
||||||
|
: this.feed.filters
|
||||||
|
this.stream = this.nostr.stream(
|
||||||
|
filters,
|
||||||
|
{subId: `feed:${this.feed.name}`}
|
||||||
|
)
|
||||||
|
this.stream.on('init', notes => {
|
||||||
|
const items = notes
|
||||||
|
.filter(note => this.filterNote(note, this.feed.hideBots))
|
||||||
|
.map(note => [note]) // TODO Single element thread
|
||||||
|
items.sort(feedOrder)
|
||||||
|
this.visible = items.slice(0, filters.limit)
|
||||||
|
this.loading = false
|
||||||
|
|
||||||
|
// Wait a bit before showing the first unreads
|
||||||
|
setTimeout(() => this.recentlyLoaded = false, 5000)
|
||||||
|
})
|
||||||
|
this.stream.on('update', note => {
|
||||||
|
if (this.filterNote(note, this.feed.hideBots)) {
|
||||||
|
this.newer.push([note]) // TODO Single element thread
|
||||||
|
}
|
||||||
|
})
|
||||||
|
},
|
||||||
|
reload() {
|
||||||
|
if (!this.stream) return
|
||||||
|
this.loading = true
|
||||||
|
this.stream.close()
|
||||||
|
this.init()
|
||||||
|
},
|
||||||
|
loadNewer() {
|
||||||
|
// TODO Deduplicate feed items
|
||||||
|
this.newer.sort(feedOrder)
|
||||||
|
const items = this.newer.concat(this.visible)
|
||||||
|
if (items.length > MAX_ITEMS_VISIBLE) {
|
||||||
|
const older = items.splice(MAX_ITEMS_VISIBLE)
|
||||||
|
this.older = older.concat(this.older)
|
||||||
|
}
|
||||||
|
//items.sort(feedOrder)
|
||||||
|
|
||||||
|
this.visible = items
|
||||||
|
this.newer = []
|
||||||
|
|
||||||
|
// Wait a bit before showing unreads again
|
||||||
|
this.recentlyLoaded = true
|
||||||
|
setTimeout(() => this.recentlyLoaded = false, 5000)
|
||||||
|
|
||||||
|
return true
|
||||||
|
},
|
||||||
|
async loadOlder() {
|
||||||
|
const feedFilters = typeof this.feed.filters === 'function'
|
||||||
|
? this.feed.filters()
|
||||||
|
: this.feed.filters
|
||||||
|
|
||||||
|
const until = this.visible[this.visible.length - 1]?.[0]?.createdAt || DateUtils.now()
|
||||||
|
const filters = Object.assign({}, feedFilters, {until})
|
||||||
|
|
||||||
|
if (this.older.length >= filters.limit) {
|
||||||
|
const chunk = this.older.splice(0, filters.limit)
|
||||||
|
this.visible = this.visible.concat(chunk)
|
||||||
|
return chunk
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove any residual older items
|
||||||
|
this.older = []
|
||||||
|
|
||||||
|
const older = await this.nostr.fetch(filters, {subId: `feed:${this.feed.name}-older`})
|
||||||
|
const items = older
|
||||||
|
.filter(note => note.createdAt <= until)
|
||||||
|
.filter(note => this.filterNote(note, this.feed.hideBots))
|
||||||
|
.map(note => [note]) // TODO Single element thread
|
||||||
|
.sort(feedOrder)
|
||||||
|
|
||||||
|
// TODO Deduplicate feed items
|
||||||
|
this.visible = this.visible.concat(items)
|
||||||
|
|
||||||
|
return items
|
||||||
|
},
|
||||||
|
filterNote(note, hideBots) {
|
||||||
|
if (note.isReaction()) return false
|
||||||
|
if (note.isRepostOrTag()) return false
|
||||||
|
if (hideBots && note.relatedPubkeys().some(Bots.isBot)) return false
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
mounted() {
|
||||||
|
this.init()
|
||||||
|
},
|
||||||
|
unmounted() {
|
||||||
|
if (this.stream) this.stream.close()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
@import "assets/theme/colors.scss";
|
||||||
|
@import "assets/variables.scss";
|
||||||
|
|
||||||
|
.feed {
|
||||||
|
.load-more-container {
|
||||||
|
border-top: $border-dark;
|
||||||
|
border-bottom: $border-dark;
|
||||||
|
min-height: 6px;
|
||||||
|
}
|
||||||
|
> .async-load-button:last-child {
|
||||||
|
border-bottom: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media screen and (max-width: $phone) {
|
||||||
|
.feed {
|
||||||
|
.load-more-container {
|
||||||
|
border: 0;
|
||||||
|
min-height: 0;
|
||||||
|
&.more-available {
|
||||||
|
border-bottom: $border-dark;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
@ -108,6 +108,7 @@ export default defineComponent({
|
|||||||
}
|
}
|
||||||
.addon {
|
.addon {
|
||||||
flex-grow: 1;
|
flex-grow: 1;
|
||||||
|
text-align: right;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -66,7 +66,7 @@ export default defineComponent({
|
|||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
cachedPages: ['Feed', 'Notifications', 'Messages', 'Inbox', 'Settings'],
|
cachedPages: ['FeedPage', 'Notifications', 'Messages', 'Inbox', 'Settings'],
|
||||||
mobileMenuOpen: false,
|
mobileMenuOpen: false,
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -6,7 +6,7 @@ import FetchQueue from 'src/nostr/FetchQueue'
|
|||||||
import {NoteOrder, useNoteStore} from 'src/nostr/store/NoteStore'
|
import {NoteOrder, useNoteStore} from 'src/nostr/store/NoteStore'
|
||||||
import {useProfileStore} from 'src/nostr/store/ProfileStore'
|
import {useProfileStore} from 'src/nostr/store/ProfileStore'
|
||||||
import {useContactStore} from 'src/nostr/store/ContactStore'
|
import {useContactStore} from 'src/nostr/store/ContactStore'
|
||||||
import {useSettingsStore} from 'stores/Settings'
|
import {useSettingsStore, RELAYS} from 'stores/Settings'
|
||||||
import {useStatStore} from 'src/nostr/store/StatStore'
|
import {useStatStore} from 'src/nostr/store/StatStore'
|
||||||
import {Observable} from 'src/nostr/utils'
|
import {Observable} from 'src/nostr/utils'
|
||||||
import {CloseAfter} from 'src/nostr/Relay'
|
import {CloseAfter} from 'src/nostr/Relay'
|
||||||
@ -61,7 +61,8 @@ export const useNostrStore = defineStore('nostr', {
|
|||||||
actions: {
|
actions: {
|
||||||
init() {
|
init() {
|
||||||
const settings = useSettingsStore()
|
const settings = useSettingsStore()
|
||||||
this.client = markRaw(new NostrClient(settings.relays))
|
// FIXME Use relays from settings
|
||||||
|
this.client = markRaw(new NostrClient(RELAYS))
|
||||||
this.client.connect()
|
this.client.connect()
|
||||||
|
|
||||||
this.profileQueue = profileQueue(this.client)
|
this.profileQueue = profileQueue(this.client)
|
||||||
@ -172,8 +173,8 @@ export const useNostrStore = defineStore('nostr', {
|
|||||||
{
|
{
|
||||||
kinds: [EventKind.NOTE],
|
kinds: [EventKind.NOTE],
|
||||||
authors: [pubkey],
|
authors: [pubkey],
|
||||||
},
|
limit,
|
||||||
limit
|
}
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
|
|
||||||
@ -191,14 +192,13 @@ export const useNostrStore = defineStore('nostr', {
|
|||||||
return followers
|
return followers
|
||||||
},
|
},
|
||||||
|
|
||||||
fetchFollowers(pubkey, opts = {}) {
|
fetchFollowers(pubkey, limit = 500) {
|
||||||
const limit = opts.limit || 500
|
|
||||||
return this.fetch(
|
return this.fetch(
|
||||||
{
|
{
|
||||||
kinds: [EventKind.CONTACT],
|
kinds: [EventKind.CONTACT],
|
||||||
'#p': [pubkey],
|
'#p': [pubkey],
|
||||||
|
limit,
|
||||||
},
|
},
|
||||||
limit
|
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
|
|
||||||
@ -231,8 +231,8 @@ export const useNostrStore = defineStore('nostr', {
|
|||||||
{
|
{
|
||||||
kinds: [EventKind.REACTION],
|
kinds: [EventKind.REACTION],
|
||||||
authors: [pubkey],
|
authors: [pubkey],
|
||||||
},
|
limit,
|
||||||
limit
|
}
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -2,26 +2,18 @@
|
|||||||
<q-page>
|
<q-page>
|
||||||
<PageHeader logo class="page-header">
|
<PageHeader logo class="page-header">
|
||||||
<template #addon>
|
<template #addon>
|
||||||
<div class="addon-menu">
|
<q-btn-toggle
|
||||||
<div class="addon-menu-icon">
|
v-if="availableFeeds.length > 1"
|
||||||
<q-icon name="more_vert" size="sm" />
|
v-model="activeFeed"
|
||||||
</div>
|
rounded
|
||||||
<q-menu target=".addon-menu-icon" anchor="top left" self="top right" class="addon-menu-popup">
|
toggle-color="primary"
|
||||||
<div>
|
size="sm"
|
||||||
<div
|
class="feed-selector"
|
||||||
v-for="feed in availableFeeds"
|
:options="[
|
||||||
:key="feed"
|
{value: 'global', icon: 'public'},
|
||||||
@click="switchFeed(feed)"
|
{value: 'following', icon: 'group'},
|
||||||
class="popup-header"
|
]"
|
||||||
v-close-popup>
|
/>
|
||||||
<p>{{ feed }}</p>
|
|
||||||
<div v-if="feed === selectedFeed" class="more">
|
|
||||||
<BaseIcon icon="tick" />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</q-menu>
|
|
||||||
</div>
|
|
||||||
</template>
|
</template>
|
||||||
</PageHeader>
|
</PageHeader>
|
||||||
|
|
||||||
@ -29,27 +21,11 @@
|
|||||||
<PostEditor />
|
<PostEditor />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="feed">
|
<q-tab-panels v-model="activeFeed" keep-alive animated>
|
||||||
<div class="load-more-container" :class="{'more-available': numUnreads}">
|
<q-tab-panel v-for="feed in availableFeeds" :key="feed" :name="feed">
|
||||||
<AsyncLoadButton
|
<Feed :feed="feedDef(feed)" :ref="feed" />
|
||||||
v-if="numUnreads"
|
</q-tab-panel>
|
||||||
:load-fn="loadNewer"
|
</q-tab-panels>
|
||||||
:label="`Load ${numUnreads} unreads`"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<template v-for="(thread, i) in feedItems">
|
|
||||||
<Thread v-if="true || defer(i)" :key="thread[0].id" :thread="thread" class="full-width" />
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<ListPlaceholder :count="feedItems?.length" :loading="loading" />
|
|
||||||
|
|
||||||
<AsyncLoadButton
|
|
||||||
v-if="feedItems?.length"
|
|
||||||
:load-fn="loadOlder"
|
|
||||||
autoload
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</q-page>
|
</q-page>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@ -57,16 +33,12 @@
|
|||||||
import {defineComponent} from 'vue'
|
import {defineComponent} from 'vue'
|
||||||
import PageHeader from 'components/PageHeader.vue'
|
import PageHeader from 'components/PageHeader.vue'
|
||||||
import PostEditor from 'components/CreatePost/PostEditor.vue'
|
import PostEditor from 'components/CreatePost/PostEditor.vue'
|
||||||
import Thread from 'components/Post/Thread.vue'
|
import Feed from 'components/Feed/Feed.vue'
|
||||||
import BaseIcon from 'components/BaseIcon/index.vue'
|
|
||||||
import AsyncLoadButton from 'components/AsyncLoadButton.vue'
|
|
||||||
import ListPlaceholder from 'components/ListPlaceholder.vue'
|
|
||||||
import {useAppStore} from 'stores/App'
|
import {useAppStore} from 'stores/App'
|
||||||
import {useNostrStore} from 'src/nostr/NostrStore'
|
import {useNostrStore} from 'src/nostr/NostrStore'
|
||||||
import Defer from 'src/utils/Defer'
|
|
||||||
import {EventKind} from 'src/nostr/model/Event'
|
import {EventKind} from 'src/nostr/model/Event'
|
||||||
import DateUtils from 'src/utils/DateUtils'
|
|
||||||
import Bots from 'src/utils/bots'
|
const ZERO_PUBKEY = '0000000000000000000000000000000000000000000000000000000000000000'
|
||||||
|
|
||||||
const Feeds = {
|
const Feeds = {
|
||||||
global: {
|
global: {
|
||||||
@ -75,24 +47,33 @@ const Feeds = {
|
|||||||
kinds: [EventKind.NOTE], // TODO Deletions
|
kinds: [EventKind.NOTE], // TODO Deletions
|
||||||
limit: 20,
|
limit: 20,
|
||||||
},
|
},
|
||||||
|
hideBots: true,
|
||||||
|
},
|
||||||
|
following: {
|
||||||
|
name: 'following',
|
||||||
|
filters: () => {
|
||||||
|
const app = useAppStore()
|
||||||
|
const nostr = useNostrStore()
|
||||||
|
const contacts = nostr.getContacts(app.myPubkey)
|
||||||
|
let authors = contacts?.map(contact => contact.pubkey)
|
||||||
|
if (!authors || !authors.length) authors = [ZERO_PUBKEY]
|
||||||
|
return {
|
||||||
|
authors,
|
||||||
|
kinds: [EventKind.NOTE],
|
||||||
|
limit: 50,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
hideBots: false,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
const feedOrder = (a, b) => b[0].createdAt - a[0].createdAt
|
|
||||||
|
|
||||||
const MAX_ITEMS_VISIBLE = 50
|
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
name: 'Feed',
|
name: 'FeedPage',
|
||||||
components: {
|
components: {
|
||||||
PageHeader,
|
PageHeader,
|
||||||
PostEditor,
|
PostEditor,
|
||||||
Thread,
|
Feed,
|
||||||
BaseIcon,
|
|
||||||
AsyncLoadButton,
|
|
||||||
ListPlaceholder,
|
|
||||||
},
|
},
|
||||||
mixins: [Defer(2000)],
|
|
||||||
setup() {
|
setup() {
|
||||||
return {
|
return {
|
||||||
app: useAppStore(),
|
app: useAppStore(),
|
||||||
@ -102,120 +83,31 @@ export default defineComponent({
|
|||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
feeds: {},
|
feeds: {},
|
||||||
availableFeeds: ['global'],
|
activeFeed: 'global',
|
||||||
selectedFeed: 'global',
|
|
||||||
loading: true,
|
|
||||||
recentlyLoaded: true,
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
activeFeed() {
|
availableFeeds() {
|
||||||
return this.feeds[this.selectedFeed]
|
const feeds = ['global']
|
||||||
|
if (this.app.isSignedIn) feeds.push('following')
|
||||||
|
return feeds
|
||||||
},
|
},
|
||||||
feedItems() {
|
contacts() {
|
||||||
return this.activeFeed?.visible
|
if (!this.app.isSignedIn) return
|
||||||
},
|
return this.nostr.getContacts(this.app.myPubkey)
|
||||||
numUnreads() {
|
|
||||||
if (this.recentlyLoaded) return 0
|
|
||||||
return this.activeFeed?.newer.length
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
initFeed(feedId) {
|
feedDef(feed) {
|
||||||
if (this.feeds[feedId]) return
|
return Feeds[feed]
|
||||||
|
|
||||||
const filters = Feeds[feedId].filters
|
|
||||||
const stream = this.nostr.stream(
|
|
||||||
filters,
|
|
||||||
{subId: `feed:${feedId}`}
|
|
||||||
)
|
|
||||||
stream.on('init', notes => {
|
|
||||||
const items = notes
|
|
||||||
.filter(this.filterNote.bind(this))
|
|
||||||
.map(note => [note]) // TODO Single element thread
|
|
||||||
items.sort(feedOrder)
|
|
||||||
this.feeds[feedId].visible = items.slice(0, filters.limit)
|
|
||||||
this.loading = false
|
|
||||||
|
|
||||||
// Wait a bit before showing the first unreads
|
|
||||||
setTimeout(() => this.recentlyLoaded = false, 5000)
|
|
||||||
})
|
|
||||||
stream.on('update', note => {
|
|
||||||
if (this.filterNote(note)) {
|
|
||||||
this.feeds[feedId].newer.push([note]) // TODO Single element thread
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
this.feeds[feedId] = {
|
|
||||||
visible: [],
|
|
||||||
newer: [],
|
|
||||||
older: [],
|
|
||||||
stream,
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
switchFeed(feedId) {
|
|
||||||
this.initFeed(feedId)
|
|
||||||
this.selectedFeed = feedId
|
|
||||||
},
|
},
|
||||||
loadNewer() {
|
watch: {
|
||||||
// TODO Deduplicate feed items
|
contacts() {
|
||||||
this.activeFeed.newer.sort(feedOrder)
|
console.log('following', this.$refs.following)
|
||||||
const items = this.activeFeed.newer.concat(this.feedItems)
|
this.$refs.following?.[0]?.reload()
|
||||||
if (items.length > MAX_ITEMS_VISIBLE) {
|
|
||||||
const older = items.splice(MAX_ITEMS_VISIBLE)
|
|
||||||
this.activeFeed.older = older.concat(this.activeFeed.older)
|
|
||||||
}
|
|
||||||
//items.sort(feedOrder)
|
|
||||||
|
|
||||||
this.activeFeed.visible = items
|
|
||||||
this.activeFeed.newer = []
|
|
||||||
|
|
||||||
// Wait a bit before showing unreads again
|
|
||||||
this.recentlyLoaded = true
|
|
||||||
setTimeout(() => this.recentlyLoaded = false, 5000)
|
|
||||||
|
|
||||||
return true
|
|
||||||
},
|
},
|
||||||
async loadOlder() {
|
|
||||||
const until = this.feedItems[this.feedItems.length - 1]?.[0]?.createdAt || DateUtils.now()
|
|
||||||
const filters = Object.assign({}, Feeds[this.selectedFeed].filters, {until})
|
|
||||||
|
|
||||||
if (this.activeFeed.older.length >= filters.limit) {
|
|
||||||
const chunk = this.activeFeed.older.splice(0, filters.limit)
|
|
||||||
this.activeFeed.visible = this.feedItems.concat(chunk)
|
|
||||||
return chunk
|
|
||||||
}
|
|
||||||
|
|
||||||
// Remove any residual older items
|
|
||||||
this.activeFeed.older = []
|
|
||||||
|
|
||||||
const older = await this.nostr.fetch(filters, {subId: `feed:${this.selectedFeed}-older`})
|
|
||||||
const items = older
|
|
||||||
.filter(note => note.createdAt <= until)
|
|
||||||
.filter(this.filterNote.bind(this))
|
|
||||||
.map(note => [note]) // TODO Single element thread
|
|
||||||
.sort(feedOrder)
|
|
||||||
|
|
||||||
// TODO Deduplicate feed items
|
|
||||||
this.activeFeed.visible = this.feedItems.concat(items)
|
|
||||||
|
|
||||||
return older
|
|
||||||
},
|
},
|
||||||
filterNote(note) {
|
|
||||||
if (note.isReaction()) return false
|
|
||||||
if (note.isRepostOrTag()) return false
|
|
||||||
if (note.relatedPubkeys().some(Bots.isBot)) return false
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
},
|
|
||||||
mounted() {
|
|
||||||
this.initFeed(this.selectedFeed)
|
|
||||||
},
|
|
||||||
unmounted() {
|
|
||||||
for (const feed of Object.values(this.feeds)) {
|
|
||||||
feed.stream.close()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
@ -223,23 +115,9 @@ export default defineComponent({
|
|||||||
@import "assets/theme/colors.scss";
|
@import "assets/theme/colors.scss";
|
||||||
@import "assets/variables.scss";
|
@import "assets/variables.scss";
|
||||||
|
|
||||||
.feed {
|
.feed-selector {
|
||||||
.load-more-container {
|
background-color: rgba($color: $color-dark-gray, $alpha: 0.2);
|
||||||
border-top: $border-dark;
|
//box-shadow: none;
|
||||||
border-bottom: $border-dark;
|
|
||||||
min-height: 6px;
|
|
||||||
}
|
|
||||||
> .async-load-button:last-child {
|
|
||||||
border-bottom: 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.addon-menu {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: row-reverse;
|
|
||||||
&-icon {
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@media screen and (max-width: $phone) {
|
@media screen and (max-width: $phone) {
|
||||||
@ -249,50 +127,27 @@ export default defineComponent({
|
|||||||
.post-editor {
|
.post-editor {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
.feed {
|
|
||||||
.load-more-container {
|
|
||||||
border: 0;
|
|
||||||
min-height: 0;
|
|
||||||
&.more-available {
|
|
||||||
border-bottom: $border-dark;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
<style lang="scss">
|
<style lang="scss">
|
||||||
@import "assets/theme/colors.scss";
|
@import "assets/theme/colors.scss";
|
||||||
|
|
||||||
.addon-menu-popup {
|
.q-tab-panels {
|
||||||
min-width: 150px;
|
background: unset;
|
||||||
border-radius: 1rem;
|
}
|
||||||
padding: 10px;
|
.q-tab-panel {
|
||||||
background-color: $color-bg;
|
padding: 0;
|
||||||
box-shadow: $shadow-white;
|
}
|
||||||
.popup-header {
|
.q-panel.scroll {
|
||||||
display: flex;
|
overflow: inherit;
|
||||||
width: 100%;
|
}
|
||||||
padding: 8px;
|
|
||||||
cursor: pointer;
|
.feed-selector {
|
||||||
border-radius: .5rem;
|
button:first-child {
|
||||||
p {
|
padding: 4px 10px 4px 12px;
|
||||||
margin: 0;
|
|
||||||
flex-grow: 1;
|
|
||||||
font-size: 1.1em;
|
|
||||||
font-weight: bold;
|
|
||||||
text-transform: capitalize;
|
|
||||||
}
|
|
||||||
&:hover {
|
|
||||||
background-color: rgba($color: $color-dark-gray, $alpha: 0.3);
|
|
||||||
}
|
|
||||||
.more {
|
|
||||||
width: 1.5rem;
|
|
||||||
height: 1.5rem;
|
|
||||||
svg {
|
|
||||||
fill: $color-primary;
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
button:last-child {
|
||||||
|
padding: 4px 12px 4px 10px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
Loading…
Reference in New Issue
Block a user