mirror of
https://github.com/h3poteto/whalebird-desktop
synced 2025-02-09 16:28:45 +01:00
Merge pull request #2428 from h3poteto/iss-2258
refs #2258 Apply filter for each timelines
This commit is contained in:
commit
6b9b38e643
@ -84,7 +84,8 @@ const state = (): TimelineSpaceState => {
|
||||
local: true,
|
||||
public: true
|
||||
},
|
||||
sns: 'mastodon'
|
||||
sns: 'mastodon',
|
||||
filters: []
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -16,7 +16,8 @@ describe('TimelineSpace', () => {
|
||||
local: unreadSettings.Local.default,
|
||||
public: unreadSettings.Public.default
|
||||
},
|
||||
sns: 'mastodon'
|
||||
sns: 'mastodon',
|
||||
filters: []
|
||||
}
|
||||
})
|
||||
|
||||
|
106
spec/renderer/unit/utils/filter.spec.ts
Normal file
106
spec/renderer/unit/utils/filter.spec.ts
Normal file
@ -0,0 +1,106 @@
|
||||
import filtered from '@/utils/filter'
|
||||
import { Entity } from 'megalodon'
|
||||
|
||||
describe('filter', () => {
|
||||
describe('whole word is enabled', () => {
|
||||
describe('Only asci', () => {
|
||||
const filters = [
|
||||
{
|
||||
id: '1',
|
||||
phrase: 'Fedi',
|
||||
context: ['home'],
|
||||
expires_at: null,
|
||||
irreversible: false,
|
||||
whole_word: true
|
||||
} as Entity.Filter
|
||||
]
|
||||
it('should not be matched', () => {
|
||||
const status =
|
||||
'Pleroma is social networking software compatible with other Fediverse software such as Mastodon, Misskey, Pixelfed and many others.'
|
||||
const res = filtered(status, filters)
|
||||
expect(res).toBeFalsy()
|
||||
})
|
||||
it('should be matched', () => {
|
||||
const status =
|
||||
'Pleroma is social networking software compatible with other Fedi software such as Mastodon, Misskey, Pixelfed and many others.'
|
||||
const res = filtered(status, filters)
|
||||
expect(res).toBeTruthy()
|
||||
})
|
||||
})
|
||||
describe('With Japanese', () => {
|
||||
const filters = [
|
||||
{
|
||||
id: '1',
|
||||
phrase: 'ミニブログ',
|
||||
context: ['home'],
|
||||
expires_at: null,
|
||||
irreversible: false,
|
||||
whole_word: true
|
||||
} as Entity.Filter
|
||||
]
|
||||
it('should be matched', () => {
|
||||
const status =
|
||||
'マストドン (Mastodon) はミニブログサービスを提供するためのフリーソフトウェア、またはこれが提供する連合型のソーシャルネットワークサービスである'
|
||||
const res = filtered(status, filters)
|
||||
expect(res).toBeTruthy()
|
||||
})
|
||||
it('should not be matched', () => {
|
||||
const status =
|
||||
'「脱中央集権型」 (decentralized) のマストドンのサーバーはだれでも自由に運用する事が可能であり、利用者は通常このサーバーの一つを選んで所属するが、異なるサーバーに属する利用者間のコミュニケーションも容易である'
|
||||
const res = filtered(status, filters)
|
||||
expect(res).toBeFalsy()
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('whole word is disabled', () => {
|
||||
describe('Only asci', () => {
|
||||
const filters = [
|
||||
{
|
||||
id: '1',
|
||||
phrase: 'Fedi',
|
||||
context: ['home'],
|
||||
expires_at: null,
|
||||
irreversible: false,
|
||||
whole_word: false
|
||||
} as Entity.Filter
|
||||
]
|
||||
it('should be matched', () => {
|
||||
const status =
|
||||
'Pleroma is social networking software compatible with other Fediverse software such as Mastodon, Misskey, Pixelfed and many others.'
|
||||
const res = filtered(status, filters)
|
||||
expect(res).toBeTruthy()
|
||||
})
|
||||
it('should be matched', () => {
|
||||
const status =
|
||||
'Pleroma is social networking software compatible with other Fedi software such as Mastodon, Misskey, Pixelfed and many others.'
|
||||
const res = filtered(status, filters)
|
||||
expect(res).toBeTruthy()
|
||||
})
|
||||
})
|
||||
describe('With Japanese', () => {
|
||||
const filters = [
|
||||
{
|
||||
id: '1',
|
||||
phrase: 'ミニブログ',
|
||||
context: ['home'],
|
||||
expires_at: null,
|
||||
irreversible: false,
|
||||
whole_word: true
|
||||
} as Entity.Filter
|
||||
]
|
||||
it('should be matched', () => {
|
||||
const status =
|
||||
'マストドン (Mastodon) はミニブログサービスを提供するためのフリーソフトウェア、またはこれが提供する連合型のソーシャルネットワークサービスである'
|
||||
const res = filtered(status, filters)
|
||||
expect(res).toBeTruthy()
|
||||
})
|
||||
it('should not be matched', () => {
|
||||
const status =
|
||||
'「脱中央集権型」 (decentralized) のマストドンのサーバーはだれでも自由に運用する事が可能であり、利用者は通常このサーバーの一つを選んで所属するが、異なるサーバーに属する利用者間のコミュニケーションも容易である'
|
||||
const res = filtered(status, filters)
|
||||
expect(res).toBeFalsy()
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
@ -9,6 +9,7 @@
|
||||
:message="item"
|
||||
:focused="item.uri + item.id === focusedId"
|
||||
:overlaid="modalOpened"
|
||||
:filters="filters"
|
||||
v-on:update="updateToot"
|
||||
v-on:delete="deleteToot"
|
||||
@focusNext="focusNext"
|
||||
@ -44,18 +45,21 @@ export default {
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
...mapState('TimelineSpace/Contents/Home', {
|
||||
timeline: state => state.timeline,
|
||||
lazyLoading: state => state.lazyLoading,
|
||||
heading: state => state.heading,
|
||||
unread: state => state.unreadTimeline,
|
||||
showReblogs: state => state.showReblogs,
|
||||
showReplies: state => state.showReplies
|
||||
}),
|
||||
...mapState({
|
||||
openSideBar: state => state.TimelineSpace.Contents.SideBar.openSideBar,
|
||||
backgroundColor: state => state.App.theme.background_color,
|
||||
startReload: state => state.TimelineSpace.HeaderMenu.reload,
|
||||
timeline: state => state.TimelineSpace.Contents.Home.timeline,
|
||||
lazyLoading: state => state.TimelineSpace.Contents.Home.lazyLoading,
|
||||
heading: state => state.TimelineSpace.Contents.Home.heading,
|
||||
unread: state => state.TimelineSpace.Contents.Home.unreadTimeline,
|
||||
showReblogs: state => state.TimelineSpace.Contents.Home.showReblogs,
|
||||
showReplies: state => state.TimelineSpace.Contents.Home.showReplies
|
||||
openSideBar: state => state.TimelineSpace.Contents.SideBar.openSideBar,
|
||||
startReload: state => state.TimelineSpace.HeaderMenu.reload
|
||||
}),
|
||||
...mapGetters('TimelineSpace/Modals', ['modalOpened']),
|
||||
...mapGetters('TimelineSpace/Contents/Home', ['filters']),
|
||||
shortcutEnabled: function () {
|
||||
if (this.modalOpened) {
|
||||
return false
|
||||
|
@ -9,6 +9,7 @@
|
||||
:message="item"
|
||||
:focused="item.id === focusedId"
|
||||
:overlaid="modalOpened"
|
||||
:filters="filters"
|
||||
v-on:update="updateToot"
|
||||
@focusNext="focusNext"
|
||||
@focusPrev="focusPrev"
|
||||
@ -51,7 +52,7 @@ export default {
|
||||
heading: state => state.TimelineSpace.Contents.Notifications.heading,
|
||||
unread: state => state.TimelineSpace.Contents.Notifications.unreadNotifications
|
||||
}),
|
||||
...mapGetters('TimelineSpace/Contents/Notifications', ['handledNotifications']),
|
||||
...mapGetters('TimelineSpace/Contents/Notifications', ['handledNotifications', 'filters']),
|
||||
...mapGetters('TimelineSpace/Modals', ['modalOpened']),
|
||||
shortcutEnabled: function () {
|
||||
if (this.modalOpened) {
|
||||
|
@ -9,6 +9,7 @@
|
||||
:message="item"
|
||||
:focused="item.uri + item.id === focusedId"
|
||||
:overlaid="modalOpened"
|
||||
:filters="filters"
|
||||
v-on:update="updateToot"
|
||||
v-on:delete="deleteToot"
|
||||
@focusNext="focusNext"
|
||||
@ -56,6 +57,7 @@ export default {
|
||||
startReload: state => state.TimelineSpace.HeaderMenu.reload,
|
||||
unreadNotification: state => state.TimelineSpace.unreadNotification
|
||||
}),
|
||||
...mapGetters('TimelineSpace/Contents/Public', ['filters']),
|
||||
...mapGetters('TimelineSpace/Modals', ['modalOpened']),
|
||||
shortcutEnabled: function () {
|
||||
if (this.modalOpened) {
|
||||
|
@ -1,14 +1,17 @@
|
||||
<template>
|
||||
<div class="tabs" id="sidebar_tabs">
|
||||
<el-tabs v-model="activeName" @tab-click="handleClick" stretch>
|
||||
<el-tab-pane label="Posts" name="posts"><Posts :account="account" :buffer="buffer" /></el-tab-pane>
|
||||
<el-tab-pane label="Posts and replies" name="posts_and_replies"><PostsAndReplies :account="account" :buffer="buffer" /></el-tab-pane>
|
||||
<el-tab-pane label="Media" name="media"><Media :account="account" :buffer="buffer" /></el-tab-pane>
|
||||
<el-tab-pane label="Posts" name="posts"><Posts :account="account" :buffer="buffer" :filters="filters" /></el-tab-pane>
|
||||
<el-tab-pane label="Posts and replies" name="posts_and_replies"
|
||||
><PostsAndReplies :account="account" :buffer="buffer" :filters="filters"
|
||||
/></el-tab-pane>
|
||||
<el-tab-pane label="Media" name="media"><Media :account="account" :buffer="buffer" :filters="filters" /></el-tab-pane>
|
||||
</el-tabs>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { mapGetters } from 'vuex'
|
||||
import Posts from './Timeline/Posts'
|
||||
import PostsAndReplies from './Timeline/PostsAndReplies'
|
||||
import Media from './Timeline/Media'
|
||||
@ -28,6 +31,9 @@ export default {
|
||||
buffer: 200
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
...mapGetters('TimelineSpace/Contents/SideBar/AccountProfile/Timeline', ['filters'])
|
||||
},
|
||||
mounted() {
|
||||
const timeline = document.getElementById('sidebar_tabs')
|
||||
if (timeline !== undefined && timeline !== null) {
|
||||
|
@ -8,6 +8,7 @@
|
||||
:key="item.id"
|
||||
:focused="item.uri + item.id === focusedId"
|
||||
:overlaid="modalOpened"
|
||||
:filters="filters"
|
||||
v-on:update="updateToot"
|
||||
v-on:delete="deleteToot"
|
||||
@focusNext="focusNext"
|
||||
@ -30,7 +31,7 @@ import { Event } from '~/src/renderer/components/event'
|
||||
|
||||
export default {
|
||||
name: 'media',
|
||||
props: ['account', 'buffer'],
|
||||
props: ['account', 'buffer', 'filters'],
|
||||
components: { Toot },
|
||||
data() {
|
||||
return {
|
||||
|
@ -7,6 +7,7 @@
|
||||
:focused="message.uri + message.id === focusedId"
|
||||
:pinned="true"
|
||||
:overlaid="modalOpened"
|
||||
:filters="filters"
|
||||
v-on:update="updatePinnedToot"
|
||||
v-on:delete="deleteToot"
|
||||
@focusNext="focusNext"
|
||||
@ -24,6 +25,7 @@
|
||||
:key="item.id"
|
||||
:focused="item.uri + item.id === focusedId"
|
||||
:overlaid="modalOpened"
|
||||
:filters="filters"
|
||||
v-on:update="updateToot"
|
||||
v-on:delete="deleteToot"
|
||||
@focusNext="focusNext"
|
||||
@ -46,7 +48,7 @@ import { Event } from '~/src/renderer/components/event'
|
||||
|
||||
export default {
|
||||
name: 'posts',
|
||||
props: ['account', 'buffer'],
|
||||
props: ['account', 'buffer', 'filters'],
|
||||
components: { Toot },
|
||||
data() {
|
||||
return {
|
||||
|
@ -8,6 +8,7 @@
|
||||
:key="item.id"
|
||||
:focused="item.uri + item.id === focusedId"
|
||||
:overlaid="modalOpened"
|
||||
:filters="filters"
|
||||
v-on:update="updateToot"
|
||||
v-on:delete="deleteToot"
|
||||
@focusNext="focusNext"
|
||||
@ -30,7 +31,7 @@ import { Event } from '~/src/renderer/components/event'
|
||||
|
||||
export default {
|
||||
name: 'posts-and-replies',
|
||||
props: ['account', 'buffer'],
|
||||
props: ['account', 'buffer', 'filters'],
|
||||
components: { Toot },
|
||||
data() {
|
||||
return {
|
||||
|
@ -5,6 +5,7 @@
|
||||
:message="message"
|
||||
:focused="message.uri + message.id === focusedId"
|
||||
:overlaid="modalOpened"
|
||||
:filters="filters"
|
||||
v-on:update="updateAncestorsToot"
|
||||
v-on:delete="deleteAncestorsToot"
|
||||
@focusNext="focusNext"
|
||||
@ -19,6 +20,7 @@
|
||||
:message="message"
|
||||
:focused="message.uri + message.id === focusedId"
|
||||
:overlaid="modalOpened"
|
||||
:filters="filters"
|
||||
:detailed="true"
|
||||
v-on:update="updateToot"
|
||||
v-on:delete="deleteToot"
|
||||
@ -34,6 +36,7 @@
|
||||
:message="message"
|
||||
:focused="message.uri + message.id === focusedId"
|
||||
:overlaid="modalOpened"
|
||||
:filters="filters"
|
||||
v-on:update="updateDescendantsToot"
|
||||
v-on:delete="deleteDescendantsToot"
|
||||
@focusNext="focusNext"
|
||||
@ -66,6 +69,7 @@ export default {
|
||||
descendants: state => state.descendants,
|
||||
timeline: state => state.ancestors.concat([state.message]).concat(state.descendants)
|
||||
}),
|
||||
...mapGetters('TimelineSpace/Contents/SideBar/TootDetail', ['filters']),
|
||||
...mapGetters('TimelineSpace/Modals', ['modalOpened'])
|
||||
},
|
||||
created() {
|
||||
@ -74,13 +78,13 @@ export default {
|
||||
mounted() {
|
||||
Event.$on('focus-sidebar', () => {
|
||||
this.focusedId = 0
|
||||
this.$nextTick(function() {
|
||||
this.$nextTick(function () {
|
||||
this.focusedId = this.timeline[0].uri + this.timeline[0].id
|
||||
})
|
||||
})
|
||||
},
|
||||
watch: {
|
||||
message: function() {
|
||||
message: function () {
|
||||
this.load()
|
||||
}
|
||||
},
|
||||
|
@ -3,7 +3,7 @@
|
||||
<favourite
|
||||
v-if="message.type === 'favourite'"
|
||||
:message="message"
|
||||
:filter="filter"
|
||||
:filters="filters"
|
||||
:focused="focused"
|
||||
:overlaid="overlaid"
|
||||
@focusNext="$emit('focusNext')"
|
||||
@ -37,7 +37,7 @@
|
||||
<mention
|
||||
v-else-if="message.type === 'mention'"
|
||||
:message="message"
|
||||
:filter="filter"
|
||||
:filters="filters"
|
||||
:focused="focused"
|
||||
:overlaid="overlaid"
|
||||
v-on:update="updateToot"
|
||||
@ -51,7 +51,7 @@
|
||||
<quote
|
||||
v-else-if="message.type === 'reblog' && message.status.quote"
|
||||
:message="message"
|
||||
:filter="filter"
|
||||
:filters="filters"
|
||||
:focused="focused"
|
||||
:overlaid="overlaid"
|
||||
@focusNext="$emit('focusNext')"
|
||||
@ -63,7 +63,7 @@
|
||||
<reblog
|
||||
v-else-if="message.type === 'reblog' && !message.status.quote"
|
||||
:message="message"
|
||||
:filter="filter"
|
||||
:filters="filters"
|
||||
:focused="focused"
|
||||
:overlaid="overlaid"
|
||||
@focusNext="$emit('focusNext')"
|
||||
@ -75,7 +75,7 @@
|
||||
<reaction
|
||||
v-else-if="message.type === 'emoji_reaction'"
|
||||
:message="message"
|
||||
:filter="filter"
|
||||
:filters="filters"
|
||||
:focused="focused"
|
||||
:overlaid="overlaid"
|
||||
@focusNext="$emit('focusNext')"
|
||||
@ -87,7 +87,7 @@
|
||||
<status
|
||||
v-else-if="message.type === 'status'"
|
||||
:message="message"
|
||||
:filter="filter"
|
||||
:filters="filters"
|
||||
:focused="focused"
|
||||
:overlaid="overlaid"
|
||||
@focusNext="$emit('focusNext')"
|
||||
@ -99,7 +99,7 @@
|
||||
<PollVote
|
||||
v-else-if="message.type === 'poll_vote'"
|
||||
:message="message"
|
||||
:filter="filter"
|
||||
:filters="filters"
|
||||
:focused="focused"
|
||||
:overlaid="overlaid"
|
||||
@focusNext="$emit('focusNext')"
|
||||
@ -111,7 +111,7 @@
|
||||
<PollExpired
|
||||
v-else-if="message.type === 'poll_expired'"
|
||||
:message="message"
|
||||
:filter="filter"
|
||||
:filters="filters"
|
||||
:focused="focused"
|
||||
:overlaid="overlaid"
|
||||
@focusNext="$emit('focusNext')"
|
||||
@ -142,9 +142,9 @@ export default {
|
||||
type: Object,
|
||||
default: {}
|
||||
},
|
||||
filter: {
|
||||
type: String,
|
||||
default: ''
|
||||
filters: {
|
||||
type: Array,
|
||||
default: []
|
||||
},
|
||||
focused: {
|
||||
type: Boolean,
|
||||
|
@ -112,6 +112,7 @@ import emojify from '~/src/renderer/utils/emojify'
|
||||
import TimeFormat from '~/src/constants/timeFormat'
|
||||
import FailoverImg from '~/src/renderer/components/atoms/FailoverImg'
|
||||
import LinkPreview from '~/src/renderer/components/molecules/Toot/LinkPreview'
|
||||
import Filtered from '@/utils/filter'
|
||||
|
||||
export default {
|
||||
name: 'favourite',
|
||||
@ -124,9 +125,9 @@ export default {
|
||||
type: Object,
|
||||
default: {}
|
||||
},
|
||||
filter: {
|
||||
type: String,
|
||||
default: ''
|
||||
filters: {
|
||||
type: Array,
|
||||
default: []
|
||||
},
|
||||
focused: {
|
||||
type: Boolean,
|
||||
@ -233,7 +234,7 @@ export default {
|
||||
return message.media_attachments
|
||||
},
|
||||
filtered(message) {
|
||||
return this.filter.length > 0 && message.status.content.search(this.filter) >= 0
|
||||
return Filtered(message.status.content, this.filters)
|
||||
},
|
||||
spoilered(message) {
|
||||
return message.spoiler_text.length > 0
|
||||
|
@ -2,7 +2,7 @@
|
||||
<div class="mention">
|
||||
<toot
|
||||
:message="message.status"
|
||||
:filter="filter"
|
||||
:filters="filters"
|
||||
:focused="focused"
|
||||
:overlaid="overlaid"
|
||||
v-on:update="updateToot"
|
||||
@ -11,7 +11,7 @@
|
||||
@focusPrev="$emit('focusPrev')"
|
||||
@focusRight="$emit('focusRight')"
|
||||
@selectToot="$emit('select')"
|
||||
>
|
||||
>
|
||||
</toot>
|
||||
</div>
|
||||
</template>
|
||||
@ -26,9 +26,9 @@ export default {
|
||||
type: Object,
|
||||
default: {}
|
||||
},
|
||||
filter: {
|
||||
type: String,
|
||||
default: ''
|
||||
filters: {
|
||||
type: Array,
|
||||
default: []
|
||||
},
|
||||
focused: {
|
||||
type: Boolean,
|
||||
@ -41,10 +41,10 @@ export default {
|
||||
},
|
||||
components: { Toot },
|
||||
methods: {
|
||||
updateToot (message) {
|
||||
updateToot(message) {
|
||||
return this.$emit('update', message)
|
||||
},
|
||||
deleteToot (message) {
|
||||
deleteToot(message) {
|
||||
return this.$emit('delete', message)
|
||||
}
|
||||
}
|
||||
|
@ -112,6 +112,7 @@ import emojify from '~/src/renderer/utils/emojify'
|
||||
import TimeFormat from '~/src/constants/timeFormat'
|
||||
import FailoverImg from '~/src/renderer/components/atoms/FailoverImg'
|
||||
import LinkPreview from '~/src/renderer/components/molecules/Toot/LinkPreview'
|
||||
import Filtered from '@/utils/filter'
|
||||
|
||||
export default {
|
||||
name: 'poll-expired',
|
||||
@ -124,9 +125,9 @@ export default {
|
||||
type: Object,
|
||||
default: {}
|
||||
},
|
||||
filter: {
|
||||
type: String,
|
||||
default: ''
|
||||
filters: {
|
||||
type: Array,
|
||||
default: []
|
||||
},
|
||||
focused: {
|
||||
type: Boolean,
|
||||
@ -233,7 +234,7 @@ export default {
|
||||
return message.media_attachments
|
||||
},
|
||||
filtered(message) {
|
||||
return this.filter.length > 0 && message.status.content.search(this.filter) >= 0
|
||||
return Filtered(message.status.content, this.filters)
|
||||
},
|
||||
spoilered(message) {
|
||||
return message.spoiler_text.length > 0
|
||||
|
@ -112,6 +112,7 @@ import emojify from '~/src/renderer/utils/emojify'
|
||||
import TimeFormat from '~/src/constants/timeFormat'
|
||||
import FailoverImg from '~/src/renderer/components/atoms/FailoverImg'
|
||||
import LinkPreview from '~/src/renderer/components/molecules/Toot/LinkPreview'
|
||||
import Filtered from '@/utils/filter'
|
||||
|
||||
export default {
|
||||
name: 'poll-vote',
|
||||
@ -124,9 +125,9 @@ export default {
|
||||
type: Object,
|
||||
default: {}
|
||||
},
|
||||
filter: {
|
||||
type: String,
|
||||
default: ''
|
||||
filters: {
|
||||
type: Array,
|
||||
default: []
|
||||
},
|
||||
focused: {
|
||||
type: Boolean,
|
||||
@ -233,7 +234,7 @@ export default {
|
||||
return message.media_attachments
|
||||
},
|
||||
filtered(message) {
|
||||
return this.filter.length > 0 && message.status.content.search(this.filter) >= 0
|
||||
return Filtered(message.status.content, this.filters)
|
||||
},
|
||||
spoilered(message) {
|
||||
return message.spoiler_text.length > 0
|
||||
|
@ -9,9 +9,7 @@
|
||||
role="article"
|
||||
aria-label="quoted toot"
|
||||
>
|
||||
<div v-show="filtered(message)" class="filtered">
|
||||
Filtered
|
||||
</div>
|
||||
<div v-show="filtered(message)" class="filtered">Filtered</div>
|
||||
<div v-show="!filtered(message)" class="quoted">
|
||||
<div class="action">
|
||||
<div class="action-mark">
|
||||
@ -125,6 +123,7 @@ import TimeFormat from '~/src/constants/timeFormat'
|
||||
import FailoverImg from '~/src/renderer/components/atoms/FailoverImg'
|
||||
import LinkPreview from '~/src/renderer/components/molecules/Toot/LinkPreview'
|
||||
import Quote from '@/components/molecules/Toot/Quote'
|
||||
import Filtered from '@/utils/filter'
|
||||
|
||||
export default {
|
||||
name: 'quote',
|
||||
@ -138,9 +137,9 @@ export default {
|
||||
type: Object,
|
||||
default: {}
|
||||
},
|
||||
filter: {
|
||||
type: String,
|
||||
default: ''
|
||||
filters: {
|
||||
type: Array,
|
||||
default: []
|
||||
},
|
||||
focused: {
|
||||
type: Boolean,
|
||||
@ -245,7 +244,7 @@ export default {
|
||||
return message.media_attachments
|
||||
},
|
||||
filtered(message) {
|
||||
return this.filter.length > 0 && message.status.content.search(this.filter) >= 0
|
||||
return Filtered(message.status.content, this.filters)
|
||||
},
|
||||
spoilered(message) {
|
||||
return message.spoiler_text.length > 0
|
||||
|
@ -9,9 +9,7 @@
|
||||
role="article"
|
||||
aria-label="reacted toot"
|
||||
>
|
||||
<div v-show="filtered(message)" class="filtered">
|
||||
Filtered
|
||||
</div>
|
||||
<div v-show="filtered(message)" class="filtered">Filtered</div>
|
||||
<div v-show="!filtered(message)" class="favourite">
|
||||
<div class="action">
|
||||
<div class="action-mark">
|
||||
@ -114,6 +112,7 @@ import emojify from '~/src/renderer/utils/emojify'
|
||||
import TimeFormat from '~/src/constants/timeFormat'
|
||||
import FailoverImg from '~/src/renderer/components/atoms/FailoverImg'
|
||||
import LinkPreview from '~/src/renderer/components/molecules/Toot/LinkPreview'
|
||||
import Filtered from '@/utils/filter'
|
||||
|
||||
export default {
|
||||
name: 'reaction',
|
||||
@ -126,9 +125,9 @@ export default {
|
||||
type: Object,
|
||||
default: {}
|
||||
},
|
||||
filter: {
|
||||
type: String,
|
||||
default: ''
|
||||
filters: {
|
||||
type: Array,
|
||||
default: []
|
||||
},
|
||||
focused: {
|
||||
type: Boolean,
|
||||
@ -235,7 +234,7 @@ export default {
|
||||
return message.media_attachments
|
||||
},
|
||||
filtered(message) {
|
||||
return this.filter.length > 0 && message.status.content.search(this.filter) >= 0
|
||||
return Filtered(message.status.content, this.filters)
|
||||
},
|
||||
spoilered(message) {
|
||||
return message.spoiler_text.length > 0
|
||||
|
@ -9,9 +9,7 @@
|
||||
role="article"
|
||||
aria-label="reblogged toot"
|
||||
>
|
||||
<div v-show="filtered(message)" class="filtered">
|
||||
Filtered
|
||||
</div>
|
||||
<div v-show="filtered(message)" class="filtered">Filtered</div>
|
||||
<div v-show="!filtered(message)" class="reblog">
|
||||
<div class="action">
|
||||
<div class="action-mark">
|
||||
@ -116,6 +114,7 @@ import emojify from '~/src/renderer/utils/emojify'
|
||||
import TimeFormat from '~/src/constants/timeFormat'
|
||||
import FailoverImg from '~/src/renderer/components/atoms/FailoverImg'
|
||||
import LinkPreview from '~/src/renderer/components/molecules/Toot/LinkPreview'
|
||||
import Filtered from '@/utils/filter'
|
||||
|
||||
export default {
|
||||
name: 'reblog',
|
||||
@ -128,9 +127,9 @@ export default {
|
||||
type: Object,
|
||||
default: {}
|
||||
},
|
||||
filter: {
|
||||
type: String,
|
||||
default: ''
|
||||
filters: {
|
||||
type: Array,
|
||||
default: []
|
||||
},
|
||||
focused: {
|
||||
type: Boolean,
|
||||
@ -235,7 +234,7 @@ export default {
|
||||
return message.media_attachments
|
||||
},
|
||||
filtered(message) {
|
||||
return this.filter.length > 0 && message.status.content.search(this.filter) >= 0
|
||||
return Filtered(message.status.content, this.filters)
|
||||
},
|
||||
spoilered(message) {
|
||||
return message.spoiler_text.length > 0
|
||||
|
@ -15,7 +15,7 @@
|
||||
<div class="clearfix"></div>
|
||||
<toot
|
||||
:message="message.status"
|
||||
:filter="filter"
|
||||
:filters="filters"
|
||||
:focused="focused"
|
||||
:overlaid="overlaid"
|
||||
v-on:update="updateToot"
|
||||
@ -42,9 +42,9 @@ export default {
|
||||
type: Object,
|
||||
default: {}
|
||||
},
|
||||
filter: {
|
||||
type: String,
|
||||
default: ''
|
||||
filters: {
|
||||
type: Array,
|
||||
default: []
|
||||
},
|
||||
focused: {
|
||||
type: Boolean,
|
||||
|
@ -269,6 +269,7 @@ import LinkPreview from '~/src/renderer/components/molecules/Toot/LinkPreview'
|
||||
import Quote from '@/components/molecules/Toot/Quote'
|
||||
import { setInterval, clearInterval } from 'timers'
|
||||
import QuoteSupported from '@/utils/quoteSupported'
|
||||
import Filtered from '@/utils/filter'
|
||||
|
||||
export default {
|
||||
name: 'toot',
|
||||
@ -297,9 +298,9 @@ export default {
|
||||
type: Object,
|
||||
default: {}
|
||||
},
|
||||
filter: {
|
||||
type: String,
|
||||
default: ''
|
||||
filters: {
|
||||
type: Array,
|
||||
default: []
|
||||
},
|
||||
focused: {
|
||||
type: Boolean,
|
||||
@ -389,7 +390,7 @@ export default {
|
||||
return !this.sensitive || this.showAttachments
|
||||
},
|
||||
filtered: function () {
|
||||
return this.filter.length > 0 && this.originalMessage.content.search(this.filter) >= 0
|
||||
return Filtered(this.originalMessage.content, this.filters)
|
||||
},
|
||||
locked: function () {
|
||||
return this.message.visibility === 'private'
|
||||
|
@ -22,6 +22,7 @@ export type TimelineSpaceState = {
|
||||
tootMax: number
|
||||
unreadNotification: UnreadNotification
|
||||
sns: 'mastodon' | 'pleroma' | 'misskey'
|
||||
filters: Array<Entity.Filter>
|
||||
}
|
||||
|
||||
export const blankAccount: LocalAccount = {
|
||||
@ -49,7 +50,8 @@ const state = (): TimelineSpaceState => ({
|
||||
local: unreadSettings.Local.default,
|
||||
public: unreadSettings.Public.default
|
||||
},
|
||||
sns: 'mastodon'
|
||||
sns: 'mastodon',
|
||||
filters: []
|
||||
})
|
||||
|
||||
export const MUTATION_TYPES = {
|
||||
@ -59,7 +61,8 @@ export const MUTATION_TYPES = {
|
||||
UPDATE_EMOJIS: 'updateEmojis',
|
||||
UPDATE_TOOT_MAX: 'updateTootMax',
|
||||
UPDATE_UNREAD_NOTIFICATION: 'updateUnreadNotification',
|
||||
CHANGE_SNS: 'changeSNS'
|
||||
CHANGE_SNS: 'changeSNS',
|
||||
UPDATE_FILTERS: 'updateFilters'
|
||||
}
|
||||
|
||||
const mutations: MutationTree<TimelineSpaceState> = {
|
||||
@ -87,6 +90,9 @@ const mutations: MutationTree<TimelineSpaceState> = {
|
||||
},
|
||||
[MUTATION_TYPES.CHANGE_SNS]: (state, sns: 'mastodon' | 'pleroma' | 'misskey') => {
|
||||
state.sns = sns
|
||||
},
|
||||
[MUTATION_TYPES.UPDATE_FILTERS]: (state, filters: Array<Entity.Filter>) => {
|
||||
state.filters = filters
|
||||
}
|
||||
}
|
||||
|
||||
@ -104,6 +110,7 @@ const actions: ActionTree<TimelineSpaceState, RootState> = {
|
||||
dispatch('TimelineSpace/SideMenu/fetchFollowRequests', account, { root: true })
|
||||
dispatch('TimelineSpace/SideMenu/confirmTimelines', account, { root: true })
|
||||
await dispatch('loadUnreadNotification', accountId)
|
||||
await dispatch('fetchFilters')
|
||||
commit(MUTATION_TYPES.CHANGE_LOADING, false)
|
||||
await dispatch('fetchContentsTimelines').catch(_ => {
|
||||
throw new TimelineFetchError()
|
||||
@ -175,6 +182,15 @@ const actions: ActionTree<TimelineSpaceState, RootState> = {
|
||||
commit(MUTATION_TYPES.UPDATE_EMOJIS, res.data)
|
||||
return res.data
|
||||
},
|
||||
/**
|
||||
* fetchFilters
|
||||
*/
|
||||
fetchFilters: async ({ commit, state, rootState }): Promise<Array<Entity.Filter>> => {
|
||||
const client = generator(state.sns, state.account.baseURL, state.account.accessToken, rootState.App.userAgent)
|
||||
const res = await client.getFilters()
|
||||
commit(MUTATION_TYPES.UPDATE_FILTERS, res.data)
|
||||
return res.data
|
||||
},
|
||||
/**
|
||||
* fetchInstance
|
||||
*/
|
||||
|
@ -1,5 +1,5 @@
|
||||
import generator, { Entity } from 'megalodon'
|
||||
import { Module, MutationTree, ActionTree } from 'vuex'
|
||||
import generator, { Entity, FilterContext } from 'megalodon'
|
||||
import { Module, MutationTree, ActionTree, GetterTree } from 'vuex'
|
||||
import { RootState } from '@/store'
|
||||
|
||||
export type HomeState = {
|
||||
@ -138,11 +138,18 @@ const actions: ActionTree<HomeState, RootState> = {
|
||||
}
|
||||
}
|
||||
|
||||
const getters: GetterTree<HomeState, RootState> = {
|
||||
filters: (_state, _getters, rootState) => {
|
||||
return rootState.TimelineSpace.filters.filter(f => f.context.includes(FilterContext.Home) && !f.irreversible)
|
||||
}
|
||||
}
|
||||
|
||||
const Home: Module<HomeState, RootState> = {
|
||||
namespaced: true,
|
||||
state: state,
|
||||
mutations: mutations,
|
||||
actions: actions
|
||||
actions: actions,
|
||||
getters: getters
|
||||
}
|
||||
|
||||
export default Home
|
||||
|
@ -1,4 +1,4 @@
|
||||
import generator, { Entity, NotificationType } from 'megalodon'
|
||||
import generator, { Entity, FilterContext, NotificationType } from 'megalodon'
|
||||
import { Module, MutationTree, ActionTree, GetterTree } from 'vuex'
|
||||
import { RootState } from '@/store'
|
||||
import { MyWindow } from '~/src/types/global'
|
||||
@ -156,6 +156,9 @@ const getters: GetterTree<NotificationsState, RootState> = {
|
||||
return false
|
||||
}
|
||||
})
|
||||
},
|
||||
filters: (_state, _getters, rootState) => {
|
||||
return rootState.TimelineSpace.filters.filter(f => f.context.includes(FilterContext.Notifications) && !f.irreversible)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,5 +1,5 @@
|
||||
import generator, { Entity } from 'megalodon'
|
||||
import { Module, MutationTree, ActionTree } from 'vuex'
|
||||
import generator, { Entity, FilterContext } from 'megalodon'
|
||||
import { Module, MutationTree, ActionTree, GetterTree } from 'vuex'
|
||||
import { RootState } from '@/store'
|
||||
|
||||
export type PublicState = {
|
||||
@ -131,11 +131,18 @@ const actions: ActionTree<PublicState, RootState> = {
|
||||
}
|
||||
}
|
||||
|
||||
const getters: GetterTree<PublicState, RootState> = {
|
||||
filters: (_state, _getters, rootState) => {
|
||||
return rootState.TimelineSpace.filters.filter(f => f.context.includes(FilterContext.Public) && !f.irreversible)
|
||||
}
|
||||
}
|
||||
|
||||
const Public: Module<PublicState, RootState> = {
|
||||
namespaced: true,
|
||||
state: state,
|
||||
mutations: mutations,
|
||||
actions: actions
|
||||
actions: actions,
|
||||
getters: getters
|
||||
}
|
||||
|
||||
export default Public
|
||||
|
@ -1,9 +1,10 @@
|
||||
import { Module } from 'vuex'
|
||||
import { GetterTree, Module } from 'vuex'
|
||||
import { RootState } from '@/store'
|
||||
|
||||
import Posts, { PostsState } from './Timeline/Posts'
|
||||
import PostsAndReplies, { PostsAndRepliesState } from './Timeline/PostsAndReplies'
|
||||
import Media, { MediaState } from './Timeline/Media'
|
||||
import { FilterContext } from 'megalodon'
|
||||
|
||||
export type TimelineState = {}
|
||||
|
||||
@ -17,6 +18,12 @@ export type TimelineModuleState = TimelineModule & TimelineState
|
||||
|
||||
const state = (): TimelineState => ({})
|
||||
|
||||
const getters: GetterTree<TimelineState, RootState> = {
|
||||
filters: (_state, _getters, rootState) => {
|
||||
return rootState.TimelineSpace.filters.filter(f => f.context.includes(FilterContext.Account) && !f.irreversible)
|
||||
}
|
||||
}
|
||||
|
||||
const Timeline: Module<TimelineState, RootState> = {
|
||||
namespaced: true,
|
||||
modules: {
|
||||
@ -24,7 +31,8 @@ const Timeline: Module<TimelineState, RootState> = {
|
||||
PostsAndReplies,
|
||||
Media
|
||||
},
|
||||
state: state
|
||||
state: state,
|
||||
getters: getters
|
||||
}
|
||||
|
||||
export default Timeline
|
||||
|
@ -1,5 +1,5 @@
|
||||
import generator, { Entity } from 'megalodon'
|
||||
import { Module, MutationTree, ActionTree } from 'vuex'
|
||||
import generator, { Entity, FilterContext } from 'megalodon'
|
||||
import { Module, MutationTree, ActionTree, GetterTree } from 'vuex'
|
||||
import { RootState } from '@/store'
|
||||
|
||||
export type TootDetailState = {
|
||||
@ -136,11 +136,18 @@ const actions: ActionTree<TootDetailState, RootState> = {
|
||||
}
|
||||
}
|
||||
|
||||
const getters: GetterTree<TootDetailState, RootState> = {
|
||||
filters: (_state, _getters, rootState) => {
|
||||
return rootState.TimelineSpace.filters.filter(f => f.context.includes(FilterContext.Thread) && !f.irreversible)
|
||||
}
|
||||
}
|
||||
|
||||
const TootDetail: Module<TootDetailState, RootState> = {
|
||||
namespaced: true,
|
||||
state: state,
|
||||
mutations: mutations,
|
||||
actions: actions
|
||||
actions: actions,
|
||||
getters: getters
|
||||
}
|
||||
|
||||
export default TootDetail
|
||||
|
38
src/renderer/utils/filter.ts
Normal file
38
src/renderer/utils/filter.ts
Normal file
@ -0,0 +1,38 @@
|
||||
import { Entity } from 'megalodon'
|
||||
|
||||
// refs: https://github.com/tootsuite/mastodon/blob/c3aef491d66aec743a3a53e934a494f653745b61/app/javascript/mastodon/selectors/index.js#L43
|
||||
|
||||
const filtered = (status: string, filters: Array<Entity.Filter>): boolean => {
|
||||
if (filters.length === 0) {
|
||||
return false
|
||||
}
|
||||
|
||||
const regexp = filterRegexp(filters)
|
||||
return status.match(regexp) !== null
|
||||
}
|
||||
|
||||
const filterRegexp = (filters: Array<Entity.Filter>): RegExp => {
|
||||
return new RegExp(
|
||||
filters
|
||||
.map(f => {
|
||||
let exp = escapeRegExp(f.phrase)
|
||||
|
||||
if (f.whole_word) {
|
||||
if (/^[\w]/.test(exp)) {
|
||||
exp = `\\b${exp}`
|
||||
}
|
||||
|
||||
if (/[\w]$/.test(exp)) {
|
||||
exp = `${exp}\\b`
|
||||
}
|
||||
}
|
||||
return exp
|
||||
})
|
||||
.join('|'),
|
||||
'i'
|
||||
)
|
||||
}
|
||||
|
||||
const escapeRegExp = (str: string) => str.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')
|
||||
|
||||
export default filtered
|
Loading…
x
Reference in New Issue
Block a user