Page Menu
Home
Phorge
Search
Configure Global Search
Log In
Files
F85832693
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Mute Notifications
Flag For Later
Award Token
Authored By
Unknown
Size
16 KB
Referenced Files
None
Subscribers
None
View Options
diff --git a/src/components/settings/settings.js b/src/components/settings/settings.js
index 3d373283f3..7aa4bbc456 100644
--- a/src/components/settings/settings.js
+++ b/src/components/settings/settings.js
@@ -1,27 +1,31 @@
import StyleSwitcher from '../style_switcher/style_switcher.vue'
const settings = {
data () {
return {
hideAttachmentsLocal: this.$store.state.config.hideAttachments,
hideAttachmentsInConvLocal: this.$store.state.config.hideAttachmentsInConv,
- hideNsfwLocal: this.$store.state.config.hideNsfw
+ hideNsfwLocal: this.$store.state.config.hideNsfw,
+ muteWordsString: this.$store.state.config.muteWords.join('\n')
}
},
components: {
StyleSwitcher
},
watch: {
hideAttachmentsLocal (value) {
this.$store.dispatch('setOption', { name: 'hideAttachments', value })
},
hideAttachmentsInConvLocal (value) {
this.$store.dispatch('setOption', { name: 'hideAttachmentsInConv', value })
},
hideNsfwLocal (value) {
this.$store.dispatch('setOption', { name: 'hideNsfw', value })
+ },
+ muteWordsString (value) {
+ this.$store.dispatch('setOption', { name: 'muteWords', value: value.split('\n') })
}
}
}
export default settings
diff --git a/src/components/settings/settings.vue b/src/components/settings/settings.vue
index 478d761a9f..33d46e7e3b 100644
--- a/src/components/settings/settings.vue
+++ b/src/components/settings/settings.vue
@@ -1,42 +1,51 @@
<template>
<div class="settings panel panel-default base00-background">
<div class="panel-heading base01-background base04">
Settings
</div>
<div class="panel-body">
<div class="setting-item">
<h2>Theme</h2>
<style-switcher></style-switcher>
</div>
+ <div class="setting-item">
+ <h2>Filtering</h2>
+ <p>All notices containing these words will be muted, one per line</p>
+ <textarea id="muteWords" v-model="muteWordsString"></textarea>
+ </div>
<div class="setting-item">
<h2>Attachments</h2>
<ul class="setting-list">
<li>
<input type="checkbox" id="hideAttachments" v-model="hideAttachmentsLocal">
<label for="hideAttachments">Hide attachments in timeline</label>
</li>
<li>
<input type="checkbox" id="hideAttachmentsInConv" v-model="hideAttachmentsInConvLocal">
<label for="hideAttachmentsInConv">Hide attachments in conversations</label>
</li>
<li>
<input type="checkbox" id="hideNsfw" v-model="hideNsfwLocal">
<label for="hideNsfw">Enable clickthrough NSFW attachment hiding</label>
</li>
</ul>
</div>
</div>
</div>
</template>
<script src="./settings.js">
</script>
-<style>
+<style lang="scss">
.setting-item {
margin: 1em 1em 1.4em;
+ textarea {
+ width: 100%;
+ height: 100px;
+ }
}
.setting-list {
list-style-type: none;
}
</style>
diff --git a/src/components/status/status.js b/src/components/status/status.js
index 87fff8792d..bb026fe1f6 100644
--- a/src/components/status/status.js
+++ b/src/components/status/status.js
@@ -1,78 +1,90 @@
import Attachment from '../attachment/attachment.vue'
import FavoriteButton from '../favorite_button/favorite_button.vue'
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 { filter } from 'lodash'
const Status = {
props: [
'statusoid',
'expandable',
'inConversation',
'focused'
],
data: () => ({
replying: false,
expanded: false,
unmuted: false,
userExpanded: false
}),
computed: {
+ muteWords () {
+ return this.$store.state.config.muteWords
+ },
hideAttachments () {
return (this.$store.state.config.hideAttachments && !this.inConversation) ||
(this.$store.state.config.hideAttachmentsInConv && this.inConversation)
},
retweet () { return !!this.statusoid.retweeted_status },
retweeter () { return this.statusoid.user.name },
status () {
if (this.retweet) {
return this.statusoid.retweeted_status
} else {
return this.statusoid
}
},
loggedIn () {
return !!this.$store.state.users.currentUser
},
- muted () { return !this.unmuted && this.status.user.muted },
+ muteWordHits () {
+ const statusText = this.status.text.toLowerCase()
+ const hits = filter(this.muteWords, (muteWord) => {
+ return statusText.includes(muteWord.toLowerCase())
+ })
+
+ return hits
+ },
+ muted () { return !this.unmuted && (this.status.user.muted || this.muteWordHits.length > 0) },
isReply () { return !!this.status.in_reply_to_status_id },
borderColor () {
return {
borderBottomColor: this.$store.state.config.colors['base02']
}
}
},
components: {
Attachment,
FavoriteButton,
RetweetButton,
DeleteButton,
PostStatusForm,
UserCardContent
},
methods: {
linkClicked ({target}) {
if (target.tagName === 'SPAN') {
target = target.parentNode
}
if (target.tagName === 'A') {
window.open(target.href, '_blank')
}
},
toggleReplying () {
this.replying = !this.replying
},
toggleExpanded () {
this.$emit('toggleExpanded')
},
toggleMute () {
this.unmuted = !this.unmuted
},
toggleUserExpanded () {
this.userExpanded = !this.userExpanded
}
}
}
export default Status
diff --git a/src/components/status/status.vue b/src/components/status/status.vue
index 993ce79691..642f114320 100644
--- a/src/components/status/status.vue
+++ b/src/components/status/status.vue
@@ -1,194 +1,199 @@
<template>
<div class="status-el base00-background base03-border" v-if="!status.deleted" v-bind:class="[{ 'base01-background': focused }, { 'status-conversation': inConversation }]" >
<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>
+ <small class="muteWords">{{muteWordHits.join(', ')}}</small>
<a href="#" class="unmute" @click.prevent="toggleMute"><i class="icon-eye-off"></i></a>
</div>
</template>
<template v-if="!muted">
<div v-if="retweet" class="media container retweet-info">
<div class="media-left">
<i class='fa icon-retweet retweeted'></i>
</div>
<div class="media-body">
Retweeted by {{retweeter}}
</div>
</div>
<div class="media status container">
<div class="media-left">
<a :href="status.user.statusnet_profile_url">
<img @click.prevent="toggleUserExpanded" class='avatar' :src="status.user.profile_image_url_original">
</a>
</div>
<div class="media-body">
<div class="base05 base05=border usercard" v-if="userExpanded">
<user-card-content :user="status.user"></user-card-content>
</div>
<div class="user-content">
<h4 class="media-heading">
{{status.user.name}}
<small><router-link :to="{ name: 'user-profile', params: { id: status.user.id } }">{{status.user.screen_name}}</router-link></small>
<small v-if="status.in_reply_to_screen_name"> >
<router-link :to="{ name: 'user-profile', params: { id: status.in_reply_to_user_id } }">
{{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 } }">
<timeago :since="status.created_at" :auto-update="60"></timeago>
</router-link>
</small>
<template v-if="expandable">
-
<small>
<a href="#" @click.prevent="toggleExpanded" ><i class="icon-plus-squared"></i></a>
</small>
- <small v-if="status.user.muted">
+ <small v-if="unmuted">
<a href="#" @click.prevent="toggleMute" ><i class="icon-eye-off"></i></a>
</small>
</template>
<small v-if="!status.is_local" class="source_url">
<a :href="status.external_url" target="_blank" ><i class="icon-binoculars"></i></a>
</small>
</h4>
<div @click.prevent="linkClicked" class="status-content" v-html="status.statusnet_html"></div>
<div v-if='status.attachments' class='attachments'>
<attachment v-if="!hideAttachments" :status-id="status.id" :nsfw="status.nsfw" :attachment="attachment" v-for="attachment in status.attachments">
</attachment>
</div>
</div>
<div v-if="loggedIn">
<div class='status-actions'>
<div>
<a href="#" v-on:click.prevent="toggleReplying">
<i class="fa icon-reply" :class="{'icon-reply-active': replying}"></i>
</a>
</div>
<retweet-button :status=status></retweet-button>
<favorite-button :status=status></favorite-button>
<delete-button :status=status></delete-button>
</div>
</div>
</div>
</div>
<div class="status base00-background container" v-if="replying">
<div class="reply-left"/>
<post-status-form class="reply-body" :reply-to="status.id" :attentions="status.attentions" :repliedUser="status.user" v-on:posted="toggleReplying"/>
</div>
</template>
</div>
</template>
<script src="./status.js" ></script>
<style lang="scss">
@import '../../_variables.scss';
.status-el {
hyphens: auto;
overflow-wrap: break-word;
word-wrap: break-word;
word-break: break-word;
border-left-width: 0px;
.user-content {
min-height: 52px;
padding-top: 1px;
}
.source_url {
float: right;
}
.greentext {
color: green;
}
a {
display: inline-block;
word-break: break-all;
}
.status-content {
margin: 3px 15px 4px 0;
}
p {
margin: 0;
margin-top: 0.2em;
margin-bottom: 0.5em;
}
}
.status-conversation {
border-left-style: solid;
}
.status-actions {
padding-top: 5px;
}
.icon-reply:hover {
color: $blue;
}
.icon-reply-active {
color: $blue;
}
.status .avatar {
width: 48px;
}
.status.compact .avatar {
width: 32px;
}
.status {
padding: 0.65em 0.7em 0.8em 0.8em;
border-bottom: 1px solid;
border-bottom-color: inherit;
border-left: 4px rgba(255, 48, 16, 0.65);
border-left-style: inherit;
}
.muted {
padding: 0.1em 0.7em 0.1em 0.8em;
button {
margin-left: auto;
}
+
+ .muteWords {
+ margin-left: 10px;
+ }
}
a.unmute {
display: block;
margin-left: auto;
}
.usercard {
border-style: solid;
border-width: 1px;
border-radius: 10px;
margin-bottom: 1em;
margin-top: 0.2em;
}
.reply-left {
flex: 0;
min-width: 48px;
}
.reply-body {
flex: 1;
}
</style>
diff --git a/src/main.js b/src/main.js
index ab0fd6c08b..969ca8dc0c 100644
--- a/src/main.js
+++ b/src/main.js
@@ -1,89 +1,90 @@
import Vue from 'vue'
import VueRouter from 'vue-router'
import Vuex from 'vuex'
import App from './App.vue'
import PublicTimeline from './components/public_timeline/public_timeline.vue'
import PublicAndExternalTimeline from './components/public_and_external_timeline/public_and_external_timeline.vue'
import FriendsTimeline from './components/friends_timeline/friends_timeline.vue'
import ConversationPage from './components/conversation-page/conversation-page.vue'
import Mentions from './components/mentions/mentions.vue'
import UserProfile from './components/user_profile/user_profile.vue'
import Settings from './components/settings/settings.vue'
import statusesModule from './modules/statuses.js'
import usersModule from './modules/users.js'
import apiModule from './modules/api.js'
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, {
locale: 'en-US',
locales: {
'en-US': require('vue-timeago/locales/en-US.json')
}
})
const persistedStateOptions = {
paths: [
'config.hideAttachments',
'config.hideAttachmentsInConv',
'config.hideNsfw',
+ 'config.muteWords',
'statuses.notifications',
'users.users'
]
}
const store = new Vuex.Store({
modules: {
statuses: statusesModule,
users: usersModule,
api: apiModule,
config: configModule
},
plugins: [createPersistedState(persistedStateOptions)],
strict: process.env.NODE_ENV !== 'production'
})
const routes = [
{ name: 'root', path: '/', redirect: '/main/all' },
{ path: '/main/all', component: PublicAndExternalTimeline },
{ path: '/main/public', component: PublicTimeline },
{ path: '/main/friends', component: FriendsTimeline },
{ 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 }
]
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 }
}
})
/* eslint-disable no-new */
new Vue({
router,
store,
el: '#app',
render: h => h(App)
})
window.fetch('/static/config.json')
.then((res) => res.json())
.then(({name, theme, background, logo}) => {
store.dispatch('setOption', { name: 'name', value: name })
store.dispatch('setOption', { name: 'theme', value: theme })
store.dispatch('setOption', { name: 'background', value: background })
store.dispatch('setOption', { name: 'logo', value: logo })
})
diff --git a/src/modules/config.js b/src/modules/config.js
index 05b4ab3bbe..f59dc6f0d7 100644
--- a/src/modules/config.js
+++ b/src/modules/config.js
@@ -1,37 +1,38 @@
import { set } from 'vue'
import StyleSetter from '../services/style_setter/style_setter.js'
const defaultState = {
name: 'Pleroma FE',
colors: {},
hideAttachments: false,
hideAttachmentsInConv: false,
- hideNsfw: true
+ hideNsfw: true,
+ muteWords: []
}
const config = {
state: defaultState,
mutations: {
setOption (state, { name, value }) {
set(state, name, value)
}
},
actions: {
setPageTitle ({state}, option = '') {
document.title = `${option} ${state.name}`
},
setOption ({ commit, dispatch }, { name, value }) {
commit('setOption', {name, value})
switch (name) {
case 'name':
dispatch('setPageTitle')
break
case 'theme':
const fullPath = `/static/css/${value}`
StyleSetter.setStyle(fullPath, commit)
}
}
}
}
export default config
File Metadata
Details
Attached
Mime Type
text/x-diff
Expires
Mon, Nov 2, 8:59 AM (2 h, 36 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
1804024
Default Alt Text
(16 KB)
Attached To
Mode
rPUFE pleroma-fe-upstream
Attached
Detach File
Event Timeline