aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/App.js1
-rw-r--r--src/App.scss28
-rw-r--r--src/App.vue1
-rw-r--r--src/boot/after_store.js1
-rw-r--r--src/components/account_actions/account_actions.js3
-rw-r--r--src/components/account_actions/account_actions.vue18
-rw-r--r--src/components/conversation/conversation.js3
-rw-r--r--src/components/conversation/conversation.vue1
-rw-r--r--src/components/interface_language_switcher/interface_language_switcher.vue4
-rw-r--r--src/components/media_modal/media_modal.vue2
-rw-r--r--src/components/mobile_nav/mobile_nav.js1
-rw-r--r--src/components/mobile_nav/mobile_nav.vue1
-rw-r--r--src/components/mrf_transparency_panel/mrf_transparency_panel.js31
-rw-r--r--src/components/mrf_transparency_panel/mrf_transparency_panel.vue4
-rw-r--r--src/components/nav_panel/nav_panel.js5
-rw-r--r--src/components/nav_panel/nav_panel.vue18
-rw-r--r--src/components/post_status_form/post_status_form.js4
-rw-r--r--src/components/registration/registration.vue2
-rw-r--r--src/components/settings/settings.vue11
-rw-r--r--src/components/side_drawer/side_drawer.js7
-rw-r--r--src/components/side_drawer/side_drawer.vue34
-rw-r--r--src/components/status/status.js60
-rw-r--r--src/components/status/status.vue5
-rw-r--r--src/components/timeline/timeline.vue15
-rw-r--r--src/components/user_card/user_card.js9
-rw-r--r--src/components/user_card/user_card.vue12
-rw-r--r--src/components/user_settings/user_settings.vue2
-rw-r--r--src/i18n/en.json3
-rw-r--r--src/i18n/ja_easy.json (renamed from src/i18n/ja.json)0
-rw-r--r--src/i18n/messages.js4
-rw-r--r--src/i18n/ru.json2
-rw-r--r--src/i18n/zh.json2
-rw-r--r--src/modules/api.js7
-rw-r--r--src/modules/config.js1
-rw-r--r--src/modules/instance.js2
-rw-r--r--src/modules/users.js1
-rw-r--r--src/services/api/api.service.js2
-rw-r--r--src/services/backend_interactor_service/backend_interactor_service.js8
-rw-r--r--src/services/entity_normalizer/entity_normalizer.service.js9
-rw-r--r--src/services/tiny_post_html_processor/tiny_post_html_processor.service.js94
40 files changed, 320 insertions, 98 deletions
diff --git a/src/App.js b/src/App.js
index e2b0e6db..61b5eec1 100644
--- a/src/App.js
+++ b/src/App.js
@@ -90,6 +90,7 @@ export default {
},
sitename () { return this.$store.state.instance.name },
chat () { return this.$store.state.chat.channel.state === 'joined' },
+ hideSitename () { return this.$store.state.instance.hideSitename },
suggestionsEnabled () { return this.$store.state.instance.suggestionsEnabled },
showInstanceSpecificPanel () {
return this.$store.state.instance.showInstanceSpecificPanel &&
diff --git a/src/App.scss b/src/App.scss
index 310962b8..754ca62e 100644
--- a/src/App.scss
+++ b/src/App.scss
@@ -855,3 +855,31 @@ nav {
.btn.btn-default {
min-height: 28px;
}
+
+.animate-spin {
+ animation: spin 2s infinite linear;
+ display: inline-block;
+}
+
+@keyframes spin {
+ 0% {
+ transform: rotate(0deg);
+ }
+
+ 100% {
+ transform: rotate(359deg);
+ }
+}
+
+.new-status-notification {
+ position:relative;
+ margin-top: -1px;
+ font-size: 1.1em;
+ border-width: 1px 0 0 0;
+ border-style: solid;
+ border-color: var(--border, $fallback--border);
+ padding: 10px;
+ z-index: 1;
+ background-color: $fallback--fg;
+ background-color: var(--panel, $fallback--fg);
+}
diff --git a/src/App.vue b/src/App.vue
index 1f244b56..d455e9ed 100644
--- a/src/App.vue
+++ b/src/App.vue
@@ -31,6 +31,7 @@
</div>
<div class="item">
<router-link
+ v-if="!hideSitename"
class="site-name"
:to="{ name: 'root' }"
active-class="home"
diff --git a/src/boot/after_store.js b/src/boot/after_store.js
index cbe0c330..f169d7ba 100644
--- a/src/boot/after_store.js
+++ b/src/boot/after_store.js
@@ -108,6 +108,7 @@ const setSettings = async ({ apiConfig, staticConfig, store }) => {
copyInstanceOption('alwaysShowSubjectInput')
copyInstanceOption('noAttachmentLinks')
copyInstanceOption('showFeaturesPanel')
+ copyInstanceOption('hideSitename')
return store.dispatch('setTheme', config['theme'])
}
diff --git a/src/components/account_actions/account_actions.js b/src/components/account_actions/account_actions.js
index 204d506a..d2153680 100644
--- a/src/components/account_actions/account_actions.js
+++ b/src/components/account_actions/account_actions.js
@@ -25,9 +25,6 @@ const AccountActions = {
},
reportUser () {
this.$store.dispatch('openUserReportingModal', this.user.id)
- },
- mentionUser () {
- this.$store.dispatch('openPostStatusModal', { replyTo: true, repliedUser: this.user })
}
}
}
diff --git a/src/components/account_actions/account_actions.vue b/src/components/account_actions/account_actions.vue
index 046cba93..d3235be1 100644
--- a/src/components/account_actions/account_actions.vue
+++ b/src/components/account_actions/account_actions.vue
@@ -9,17 +9,7 @@
>
<div slot="popover">
<div class="dropdown-menu">
- <button
- class="btn btn-default btn-block dropdown-item"
- @click="mentionUser"
- >
- {{ $t('user_card.mention') }}
- </button>
<template v-if="user.following">
- <div
- role="separator"
- class="dropdown-divider"
- />
<button
v-if="user.showing_reblogs"
class="btn btn-default dropdown-item"
@@ -34,11 +24,11 @@
>
{{ $t('user_card.show_repeats') }}
</button>
+ <div
+ role="separator"
+ class="dropdown-divider"
+ />
</template>
- <div
- role="separator"
- class="dropdown-divider"
- />
<button
v-if="user.statusnet_blocking"
class="btn btn-default btn-block dropdown-item"
diff --git a/src/components/conversation/conversation.js b/src/components/conversation/conversation.js
index 72ee9c39..08283fff 100644
--- a/src/components/conversation/conversation.js
+++ b/src/components/conversation/conversation.js
@@ -43,7 +43,8 @@ const conversation = {
'collapsable',
'isPage',
'pinnedStatusIdsObject',
- 'inProfile'
+ 'inProfile',
+ 'profileUserId'
],
created () {
if (this.isPage) {
diff --git a/src/components/conversation/conversation.vue b/src/components/conversation/conversation.vue
index 0f1de55f..2e48240a 100644
--- a/src/components/conversation/conversation.vue
+++ b/src/components/conversation/conversation.vue
@@ -27,6 +27,7 @@
:highlight="getHighlight()"
:replies="getReplies(status.id)"
:in-profile="inProfile"
+ :profile-user-id="profileUserId"
class="status-fadein panel-body"
@goto="setHighlight"
@toggleExpanded="toggleExpanded"
diff --git a/src/components/interface_language_switcher/interface_language_switcher.vue b/src/components/interface_language_switcher/interface_language_switcher.vue
index 1ca22001..f5ace0cc 100644
--- a/src/components/interface_language_switcher/interface_language_switcher.vue
+++ b/src/components/interface_language_switcher/interface_language_switcher.vue
@@ -51,8 +51,8 @@ export default {
methods: {
getLanguageName (code) {
const specialLanguageNames = {
- 'ja': 'Japanese (やさしいにほんご)',
- 'ja_pedantic': 'Japanese (日本語)',
+ 'ja': 'Japanese (日本語)',
+ 'ja_easy': 'Japanese (やさしいにほんご)',
'zh': 'Chinese (简体中文)'
}
return specialLanguageNames[code] || ISO6391.getName(code)
diff --git a/src/components/media_modal/media_modal.vue b/src/components/media_modal/media_modal.vue
index 49e3143e..80d2a8b9 100644
--- a/src/components/media_modal/media_modal.vue
+++ b/src/components/media_modal/media_modal.vue
@@ -10,13 +10,13 @@
:src="currentMedia.url"
@touchstart.stop="mediaTouchStart"
@touchmove.stop="mediaTouchMove"
+ @click="hide"
>
<VideoAttachment
v-if="type === 'video'"
class="modal-image"
:attachment="currentMedia"
:controls="true"
- @click.stop.native=""
/>
<button
v-if="canNavigate"
diff --git a/src/components/mobile_nav/mobile_nav.js b/src/components/mobile_nav/mobile_nav.js
index 5a90c31f..c1166a0c 100644
--- a/src/components/mobile_nav/mobile_nav.js
+++ b/src/components/mobile_nav/mobile_nav.js
@@ -29,6 +29,7 @@ const MobileNav = {
unseenNotificationsCount () {
return this.unseenNotifications.length
},
+ hideSitename () { return this.$store.state.instance.hideSitename },
sitename () { return this.$store.state.instance.name }
},
methods: {
diff --git a/src/components/mobile_nav/mobile_nav.vue b/src/components/mobile_nav/mobile_nav.vue
index d1c24e56..51f1d636 100644
--- a/src/components/mobile_nav/mobile_nav.vue
+++ b/src/components/mobile_nav/mobile_nav.vue
@@ -17,6 +17,7 @@
<i class="button-icon icon-menu" />
</a>
<router-link
+ v-if="!hideSitename"
class="site-name"
:to="{ name: 'root' }"
active-class="home"
diff --git a/src/components/mrf_transparency_panel/mrf_transparency_panel.js b/src/components/mrf_transparency_panel/mrf_transparency_panel.js
index 20f8a08a..6a1baec8 100644
--- a/src/components/mrf_transparency_panel/mrf_transparency_panel.js
+++ b/src/components/mrf_transparency_panel/mrf_transparency_panel.js
@@ -1,16 +1,27 @@
import { mapState } from 'vuex'
+import { get } from 'lodash'
const MRFTransparencyPanel = {
- computed: mapState({
- federationPolicy: state => state.instance.federationPolicy,
- mrfPolicies: state => state.instance.federationPolicy.mrf_policies,
- acceptInstances: state => state.instance.federationPolicy.mrf_simple.accept,
- rejectInstances: state => state.instance.federationPolicy.mrf_simple.reject,
- quarantineInstances: state => state.instance.federationPolicy.quarantined_instances,
- ftlRemovalInstances: state => state.instance.federationPolicy.mrf_simple.federated_timeline_removal,
- mediaNsfwInstances: state => state.instance.federationPolicy.mrf_simple.media_nsfw,
- mediaRemovalInstances: state => state.instance.federationPolicy.mrf_simple.media_removal
- })
+ computed: {
+ ...mapState({
+ federationPolicy: state => get(state, 'instance.federationPolicy'),
+ mrfPolicies: state => get(state, 'instance.federationPolicy.mrf_policies', []),
+ quarantineInstances: state => get(state, 'instance.federationPolicy.quarantined_instances', []),
+ acceptInstances: state => get(state, 'instance.federationPolicy.mrf_simple.accept', []),
+ rejectInstances: state => get(state, 'instance.federationPolicy.mrf_simple.reject', []),
+ ftlRemovalInstances: state => get(state, 'instance.federationPolicy.mrf_simple.federated_timeline_removal', []),
+ mediaNsfwInstances: state => get(state, 'instance.federationPolicy.mrf_simple.media_nsfw', []),
+ mediaRemovalInstances: state => get(state, 'instance.federationPolicy.mrf_simple.media_removal', [])
+ }),
+ hasInstanceSpecificPolicies () {
+ return this.quarantineInstances.length ||
+ this.acceptInstances.length ||
+ this.rejectInstances.length ||
+ this.ftlRemovalInstances.length ||
+ this.mediaNsfwInstances.length ||
+ this.mediaRemovalInstances.length
+ }
+ }
}
export default MRFTransparencyPanel
diff --git a/src/components/mrf_transparency_panel/mrf_transparency_panel.vue b/src/components/mrf_transparency_panel/mrf_transparency_panel.vue
index 2640d68c..d6495dc6 100644
--- a/src/components/mrf_transparency_panel/mrf_transparency_panel.vue
+++ b/src/components/mrf_transparency_panel/mrf_transparency_panel.vue
@@ -22,7 +22,9 @@
/>
</ul>
- <h2>{{ $t("about.mrf_policy_simple") }}</h2>
+ <h2 v-if="hasInstanceSpecificPolicies">
+ {{ $t("about.mrf_policy_simple") }}
+ </h2>
<div v-if="acceptInstances.length">
<h4>{{ $t("about.mrf_policy_simple_accept") }}</h4>
diff --git a/src/components/nav_panel/nav_panel.js b/src/components/nav_panel/nav_panel.js
index a6426d13..dbbfb096 100644
--- a/src/components/nav_panel/nav_panel.js
+++ b/src/components/nav_panel/nav_panel.js
@@ -4,10 +4,7 @@ import { mapState } from 'vuex'
const NavPanel = {
created () {
if (this.currentUser && this.currentUser.locked) {
- const store = this.$store
- const credentials = store.state.users.currentUser.credentials
-
- followRequestFetcher.startFetching({ store, credentials })
+ this.$store.dispatch('startFetchingFollowRequest')
}
},
computed: mapState({
diff --git a/src/components/nav_panel/nav_panel.vue b/src/components/nav_panel/nav_panel.vue
index d85c28bd..97c36711 100644
--- a/src/components/nav_panel/nav_panel.vue
+++ b/src/components/nav_panel/nav_panel.vue
@@ -4,22 +4,22 @@
<ul>
<li v-if="currentUser">
<router-link :to="{ name: 'friends' }">
- {{ $t("nav.timeline") }}
+ <i class="button-icon icon-home-2" /> {{ $t("nav.timeline") }}
</router-link>
</li>
<li v-if="currentUser">
<router-link :to="{ name: 'interactions', params: { username: currentUser.screen_name } }">
- {{ $t("nav.interactions") }}
+ <i class="button-icon icon-bell-alt" /> {{ $t("nav.interactions") }}
</router-link>
</li>
<li v-if="currentUser">
<router-link :to="{ name: 'dms', params: { username: currentUser.screen_name } }">
- {{ $t("nav.dms") }}
+ <i class="button-icon icon-mail-alt" /> {{ $t("nav.dms") }}
</router-link>
</li>
<li v-if="currentUser && currentUser.locked">
<router-link :to="{ name: 'friend-requests' }">
- {{ $t("nav.friend_requests") }}
+ <i class="button-icon icon-user-plus" /> {{ $t("nav.friend_requests") }}
<span
v-if="followRequestCount > 0"
class="badge follow-request-count"
@@ -30,17 +30,17 @@
</li>
<li v-if="currentUser || !privateMode">
<router-link :to="{ name: 'public-timeline' }">
- {{ $t("nav.public_tl") }}
+ <i class="button-icon icon-users" /> {{ $t("nav.public_tl") }}
</router-link>
</li>
<li v-if="(currentUser || !privateMode) && federating">
<router-link :to="{ name: 'public-external-timeline' }">
- {{ $t("nav.twkn") }}
+ <i class="button-icon icon-globe" /> {{ $t("nav.twkn") }}
</router-link>
</li>
<li>
<router-link :to="{ name: 'about' }">
- {{ $t("nav.about") }}
+ <i class="button-icon icon-info-circled" /> {{ $t("nav.about") }}
</router-link>
</li>
</ul>
@@ -113,4 +113,8 @@
}
}
}
+
+.nav-panel .button-icon:before {
+ width: 1.1em;
+}
</style>
diff --git a/src/components/post_status_form/post_status_form.js b/src/components/post_status_form/post_status_form.js
index af6299e4..74067fef 100644
--- a/src/components/post_status_form/post_status_form.js
+++ b/src/components/post_status_form/post_status_form.js
@@ -169,9 +169,7 @@ const PostStatusForm = {
if (this.submitDisabled) { return }
if (this.newStatus.status === '') {
- if (this.newStatus.files.length > 0) {
- this.newStatus.status = '\u200b' // hack
- } else {
+ if (this.newStatus.files.length === 0) {
this.error = 'Cannot post an empty status with no files'
return
}
diff --git a/src/components/registration/registration.vue b/src/components/registration/registration.vue
index 5bb06a4f..222b67a8 100644
--- a/src/components/registration/registration.vue
+++ b/src/components/registration/registration.vue
@@ -172,7 +172,7 @@
for="captcha-label"
>{{ $t('captcha') }}</label>
- <template v-if="captcha.type == 'kocaptcha'">
+ <template v-if="['kocaptcha', 'native'].includes(captcha.type)">
<img
:src="captcha.url"
@click="setCaptcha"
diff --git a/src/components/settings/settings.vue b/src/components/settings/settings.vue
index a83489d2..c4021137 100644
--- a/src/components/settings/settings.vue
+++ b/src/components/settings/settings.vue
@@ -270,6 +270,17 @@
</li>
</ul>
</div>
+
+ <div class="setting-item">
+ <h2>{{ $t('settings.fun') }}</h2>
+ <ul class="setting-list">
+ <li>
+ <Checkbox v-model="greentext">
+ {{ $t('settings.greentext') }} {{ $t('settings.instance_default', { value: greentextLocalizedValue }) }}
+ </Checkbox>
+ </li>
+ </ul>
+ </div>
</div>
<div :label="$t('settings.theme')">
diff --git a/src/components/side_drawer/side_drawer.js b/src/components/side_drawer/side_drawer.js
index 2725d43a..22cb1a55 100644
--- a/src/components/side_drawer/side_drawer.js
+++ b/src/components/side_drawer/side_drawer.js
@@ -10,6 +10,10 @@ const SideDrawer = {
}),
created () {
this.closeGesture = GestureService.swipeGesture(GestureService.DIRECTION_LEFT, this.toggleDrawer)
+
+ if (this.currentUser && this.currentUser.locked) {
+ this.$store.dispatch('startFetchingFollowRequest')
+ }
},
components: { UserCard },
computed: {
@@ -29,6 +33,9 @@ const SideDrawer = {
logo () {
return this.$store.state.instance.logo
},
+ hideSitename () {
+ return this.$store.state.instance.hideSitename
+ },
sitename () {
return this.$store.state.instance.name
},
diff --git a/src/components/side_drawer/side_drawer.vue b/src/components/side_drawer/side_drawer.vue
index be18a5d7..1454e0d8 100644
--- a/src/components/side_drawer/side_drawer.vue
+++ b/src/components/side_drawer/side_drawer.vue
@@ -27,7 +27,7 @@
class="side-drawer-logo-wrapper"
>
<img :src="logo">
- <span>{{ sitename }}</span>
+ <span v-if="!hideSitename">{{ sitename }}</span>
</div>
</div>
<ul>
@@ -36,7 +36,7 @@
@click="toggleDrawer"
>
<router-link :to="{ name: 'login' }">
- {{ $t("login.login") }}
+ <i class="button-icon icon-login" /> {{ $t("login.login") }}
</router-link>
</li>
<li
@@ -44,7 +44,7 @@
@click="toggleDrawer"
>
<router-link :to="{ name: 'dms', params: { username: currentUser.screen_name } }">
- {{ $t("nav.dms") }}
+ <i class="button-icon icon-mail-alt" /> {{ $t("nav.dms") }}
</router-link>
</li>
<li
@@ -52,7 +52,7 @@
@click="toggleDrawer"
>
<router-link :to="{ name: 'interactions', params: { username: currentUser.screen_name } }">
- {{ $t("nav.interactions") }}
+ <i class="button-icon icon-bell-alt" /> {{ $t("nav.interactions") }}
</router-link>
</li>
</ul>
@@ -62,7 +62,7 @@
@click="toggleDrawer"
>
<router-link :to="{ name: 'friends' }">
- {{ $t("nav.timeline") }}
+ <i class="button-icon icon-home-2" /> {{ $t("nav.timeline") }}
</router-link>
</li>
<li
@@ -70,7 +70,7 @@
@click="toggleDrawer"
>
<router-link to="/friend-requests">
- {{ $t("nav.friend_requests") }}
+ <i class="button-icon icon-user-plus" /> {{ $t("nav.friend_requests") }}
<span
v-if="followRequestCount > 0"
class="badge follow-request-count"
@@ -81,12 +81,12 @@
</li>
<li @click="toggleDrawer" v-if="currentUser || !privateMode">
<router-link to="/main/public">
- {{ $t("nav.public_tl") }}
+ <i class="button-icon icon-users" /> {{ $t("nav.public_tl") }}
</router-link>
</li>
<li @click="toggleDrawer" v-if="(currentUser || !privateMode) && federating">
<router-link to="/main/all">
- {{ $t("nav.twkn") }}
+ <i class="button-icon icon-globe" /> {{ $t("nav.twkn") }}
</router-link>
</li>
<li
@@ -94,14 +94,14 @@
@click="toggleDrawer"
>
<router-link :to="{ name: 'chat' }">
- {{ $t("nav.chat") }}
+ <i class="button-icon icon-chat" /> {{ $t("nav.chat") }}
</router-link>
</li>
</ul>
<ul>
<li @click="toggleDrawer" v-if="currentUser || !privateMode">
<router-link :to="{ name: 'search' }">
- {{ $t("nav.search") }}
+ <i class="button-icon icon-search" /> {{ $t("nav.search") }}
</router-link>
</li>
<li
@@ -109,17 +109,17 @@
@click="toggleDrawer"
>
<router-link :to="{ name: 'who-to-follow' }">
- {{ $t("nav.who_to_follow") }}
+ <i class="button-icon icon-user-plus" /> {{ $t("nav.who_to_follow") }}
</router-link>
</li>
<li @click="toggleDrawer">
<router-link :to="{ name: 'settings' }">
- {{ $t("settings.settings") }}
+ <i class="button-icon icon-cog" /> {{ $t("settings.settings") }}
</router-link>
</li>
<li @click="toggleDrawer">
<router-link :to="{ name: 'about'}">
- {{ $t("nav.about") }}
+ <i class="button-icon icon-info-circled" /> {{ $t("nav.about") }}
</router-link>
</li>
<li
@@ -130,7 +130,7 @@
href="/pleroma/admin/#/login-pleroma"
target="_blank"
>
- {{ $t("nav.administration") }}
+ <i class="button-icon icon-gauge" /> {{ $t("nav.administration") }}
</a>
</li>
<li
@@ -141,7 +141,7 @@
href="#"
@click="doLogout"
>
- {{ $t("login.logout") }}
+ <i class="button-icon icon-logout" /> {{ $t("login.logout") }}
</a>
</li>
</ul>
@@ -215,6 +215,10 @@
box-shadow: var(--panelShadow);
background-color: $fallback--bg;
background-color: var(--bg, $fallback--bg);
+
+ .button-icon:before {
+ width: 1.1em;
+ }
}
.side-drawer-logo-wrapper {
diff --git a/src/components/status/status.js b/src/components/status/status.js
index 4fbd5ac3..c49e729c 100644
--- a/src/components/status/status.js
+++ b/src/components/status/status.js
@@ -13,10 +13,11 @@ import Timeago from '../timeago/timeago.vue'
import StatusPopover from '../status_popover/status_popover.vue'
import generateProfileLink from 'src/services/user_profile_link_generator/user_profile_link_generator'
import fileType from 'src/services/file_type/file_type.service'
+import { processHtml } from 'src/services/tiny_post_html_processor/tiny_post_html_processor.service.js'
import { highlightClass, highlightStyle } from '../../services/user_highlighter/user_highlighter.js'
import { mentionMatchesUrl, extractTagFromUrl } from 'src/services/matcher/matcher.service.js'
import { filter, unescape, uniqBy } from 'lodash'
-import { mapGetters } from 'vuex'
+import { mapGetters, mapState } from 'vuex'
const Status = {
name: 'Status',
@@ -32,7 +33,8 @@ const Status = {
'noHeading',
'inlineExpanded',
'showPinned',
- 'inProfile'
+ 'inProfile',
+ 'profileUserId'
],
data () {
return {
@@ -42,8 +44,8 @@ const Status = {
showingTall: this.inConversation && this.focused,
showingLongSubject: false,
error: null,
- expandingSubject: !this.$store.getters.mergedConfig.collapseMessageWithSubject,
- betterShadow: this.$store.state.interface.browserSupport.cssFilter
+ // not as computed because it sets the initial state which will be changed later
+ expandingSubject: !this.$store.getters.mergedConfig.collapseMessageWithSubject
}
},
computed: {
@@ -103,7 +105,7 @@ const Status = {
return this.$store.state.statuses.allStatusesObject[this.status.id]
},
loggedIn () {
- return !!this.$store.state.users.currentUser
+ return !!this.currentUser
},
muteWordHits () {
const statusText = this.status.text.toLowerCase()
@@ -114,7 +116,7 @@ const Status = {
return hits
},
- muted () { return !this.unmuted && ((!this.inProfile && this.status.user.muted) || (!this.inConversation && this.status.thread_muted) || this.muteWordHits.length > 0) },
+ muted () { return !this.unmuted && ((!(this.inProfile && this.status.user.id === this.profileUserId) && this.status.user.muted) || (!this.inConversation && this.status.thread_muted) || this.muteWordHits.length > 0) },
hideFilteredStatuses () {
return this.mergedConfig.hideFilteredStatuses
},
@@ -163,7 +165,7 @@ const Status = {
if (this.inConversation || !this.isReply) {
return false
}
- if (this.status.user.id === this.$store.state.users.currentUser.id) {
+ if (this.status.user.id === this.currentUser.id) {
return false
}
if (this.status.type === 'retweet') {
@@ -178,7 +180,7 @@ const Status = {
if (checkFollowing && taggedUser && taggedUser.following) {
return false
}
- if (this.status.attentions[i].id === this.$store.state.users.currentUser.id) {
+ if (this.status.attentions[i].id === this.currentUser.id) {
return false
}
}
@@ -255,11 +257,41 @@ const Status = {
maxThumbnails () {
return this.mergedConfig.maxThumbnails
},
+ postBodyHtml () {
+ const html = this.status.statusnet_html
+
+ if (this.mergedConfig.greentext) {
+ try {
+ if (html.includes('&gt;')) {
+ // This checks if post has '>' at the beginning, excluding mentions so that @mention >impying works
+ return processHtml(html, (string) => {
+ if (string.includes('&gt;') &&
+ string
+ .replace(/<[^>]+?>/gi, '') // remove all tags
+ .replace(/@\w+/gi, '') // remove mentions (even failed ones)
+ .trim()
+ .startsWith('&gt;')) {
+ return `<span class='greentext'>${string}</span>`
+ } else {
+ return string
+ }
+ })
+ } else {
+ return html
+ }
+ } catch (e) {
+ console.err('Failed to process status html', e)
+ return html
+ }
+ } else {
+ return html
+ }
+ },
contentHtml () {
if (!this.status.summary_html) {
- return this.status.statusnet_html
+ return this.postBodyHtml
}
- return this.status.summary_html + '<br />' + this.status.statusnet_html
+ return this.status.summary_html + '<br />' + this.postBodyHtml
},
combinedFavsAndRepeatsUsers () {
// Use the status from the global status repository since favs and repeats are saved in it
@@ -270,7 +302,7 @@ const Status = {
return uniqBy(combinedUsers, 'id')
},
ownStatus () {
- return this.status.user.id === this.$store.state.users.currentUser.id
+ return this.status.user.id === this.currentUser.id
},
tags () {
return this.status.tags.filter(tagObj => tagObj.hasOwnProperty('name')).map(tagObj => tagObj.name).join(' ')
@@ -278,7 +310,11 @@ const Status = {
hidePostStats () {
return this.mergedConfig.hidePostStats
},
- ...mapGetters(['mergedConfig'])
+ ...mapGetters(['mergedConfig']),
+ ...mapState({
+ betterShadow: state => state.interface.browserSupport.cssFilter,
+ currentUser: state => state.users.currentUser
+ })
},
components: {
Attachment,
diff --git a/src/components/status/status.vue b/src/components/status/status.vue
index 65778b2e..d291e762 100644
--- a/src/components/status/status.vue
+++ b/src/components/status/status.vue
@@ -606,7 +606,7 @@ $status-margin: 0.75em;
height: 100%;
mask: linear-gradient(to top, white, transparent) bottom/100% 70px no-repeat,
linear-gradient(to top, white, white);
- // Autoprefixed seem to ignore this one, and also syntax is different
+ /* Autoprefixed seem to ignore this one, and also syntax is different */
-webkit-mask-composite: xor;
mask-composite: exclude;
}
@@ -752,7 +752,8 @@ $status-margin: 0.75em;
}
.greentext {
- color: green;
+ color: $fallback--cGreen;
+ color: var(--cGreen, $fallback--cGreen);
}
.status-conversation {
diff --git a/src/components/timeline/timeline.vue b/src/components/timeline/timeline.vue
index f1d3903a..a6fba452 100644
--- a/src/components/timeline/timeline.vue
+++ b/src/components/timeline/timeline.vue
@@ -37,6 +37,7 @@
:collapsable="true"
:pinned-status-ids-object="pinnedStatusIdsObject"
:in-profile="inProfile"
+ :profile-user-id="userId"
/>
</template>
<template v-for="status in timeline.visibleStatuses">
@@ -47,6 +48,7 @@
:status-id="status.id"
:collapsable="true"
:in-profile="inProfile"
+ :profile-user-id="userId"
/>
</template>
</div>
@@ -91,17 +93,4 @@
opacity: 1;
}
}
-
-.new-status-notification {
- position:relative;
- margin-top: -1px;
- font-size: 1.1em;
- border-width: 1px 0 0 0;
- border-style: solid;
- border-color: var(--border, $fallback--border);
- padding: 10px;
- z-index: 1;
- background-color: $fallback--fg;
- background-color: var(--panel, $fallback--fg);
-}
</style>
diff --git a/src/components/user_card/user_card.js b/src/components/user_card/user_card.js
index cc8a1ed6..2f649910 100644
--- a/src/components/user_card/user_card.js
+++ b/src/components/user_card/user_card.js
@@ -93,6 +93,12 @@ export default {
const roleTitle = rights.admin ? 'admin' : 'moderator'
return validRole && roleTitle
},
+ hideFollowsCount () {
+ return this.isOtherUser && this.user.hide_follows_count
+ },
+ hideFollowersCount () {
+ return this.isOtherUser && this.user.hide_followers_count
+ },
...mapGetters(['mergedConfig'])
},
components: {
@@ -143,6 +149,9 @@ export default {
}
this.$store.dispatch('setMedia', [attachment])
this.$store.dispatch('setCurrent', attachment)
+ },
+ mentionUser () {
+ this.$store.dispatch('openPostStatusModal', { replyTo: true, repliedUser: this.user })
}
}
}
diff --git a/src/components/user_card/user_card.vue b/src/components/user_card/user_card.vue
index 6f3c958e..93d55fff 100644
--- a/src/components/user_card/user_card.vue
+++ b/src/components/user_card/user_card.vue
@@ -175,6 +175,14 @@
{{ $t('user_card.mute') }}
</button>
</div>
+ <div>
+ <button
+ class="btn btn-default btn-block"
+ @click="mentionUser"
+ >
+ {{ $t('user_card.mention') }}
+ </button>
+ </div>
<ModerationTools
v-if="loggedIn.role === &quot;admin&quot;"
:user="user"
@@ -208,14 +216,14 @@
@click.prevent="setProfileView('friends')"
>
<h5>{{ $t('user_card.followees') }}</h5>
- <span>{{ user.friends_count }}</span>
+ <span>{{ hideFollowsCount ? $t('user_card.hidden') : user.friends_count }}</span>
</div>
<div
class="user-count"
@click.prevent="setProfileView('followers')"
>
<h5>{{ $t('user_card.followers') }}</h5>
- <span>{{ user.followers_count }}</span>
+ <span>{{ hideFollowersCount ? $t('user_card.hidden') : user.followers_count }}</span>
</div>
</div>
<!-- eslint-disable vue/no-v-html -->
diff --git a/src/components/user_settings/user_settings.vue b/src/components/user_settings/user_settings.vue
index 8c18cf49..3f1982a6 100644
--- a/src/components/user_settings/user_settings.vue
+++ b/src/components/user_settings/user_settings.vue
@@ -104,7 +104,7 @@
{{ $t('settings.hide_followers_count_description') }}
</Checkbox>
</p>
- <p>
+ <p v-if="role === 'admin' || role === 'moderator'">
<Checkbox v-model="showRole">
<template v-if="role === 'admin'">
{{ $t('settings.show_admin_badge') }}
diff --git a/src/i18n/en.json b/src/i18n/en.json
index ead333c1..85146ef5 100644
--- a/src/i18n/en.json
+++ b/src/i18n/en.json
@@ -370,6 +370,8 @@
"false": "no",
"true": "yes"
},
+ "fun": "Fun",
+ "greentext": "Meme arrows",
"notifications": "Notifications",
"notification_setting": "Receive notifications from:",
"notification_setting_follows": "Users you follow",
@@ -569,6 +571,7 @@
"followers": "Followers",
"following": "Following!",
"follows_you": "Follows you!",
+ "hidden": "Hidden",
"its_you": "It's you!",
"media": "Media",
"mention": "Mention",
diff --git a/src/i18n/ja.json b/src/i18n/ja_easy.json
index 592a7257..592a7257 100644
--- a/src/i18n/ja.json
+++ b/src/i18n/ja_easy.json
diff --git a/src/i18n/messages.js b/src/i18n/messages.js
index 89c8a8c8..c56ae205 100644
--- a/src/i18n/messages.js
+++ b/src/i18n/messages.js
@@ -23,8 +23,8 @@ const messages = {
he: require('./he.json'),
hu: require('./hu.json'),
it: require('./it.json'),
- ja: require('./ja.json'),
- ja_pedantic: require('./ja_pedantic.json'),
+ ja: require('./ja_pedantic.json'),
+ ja_easy: require('./ja_easy.json'),
ko: require('./ko.json'),
nb: require('./nb.json'),
nl: require('./nl.json'),
diff --git a/src/i18n/ru.json b/src/i18n/ru.json
index f8bcd996..19e10f1e 100644
--- a/src/i18n/ru.json
+++ b/src/i18n/ru.json
@@ -174,6 +174,8 @@
"name_bio": "Имя и описание",
"new_email": "Новый email",
"new_password": "Новый пароль",
+ "fun": "Потешное",
+ "greentext": "Мемные стрелочки",
"notification_visibility": "Показывать уведомления",
"notification_visibility_follows": "Подписки",
"notification_visibility_likes": "Лайки",
diff --git a/src/i18n/zh.json b/src/i18n/zh.json
index 80c4e0d8..8d9462ab 100644
--- a/src/i18n/zh.json
+++ b/src/i18n/zh.json
@@ -111,7 +111,7 @@
},
"interactions": {
"favs_repeats": "转发和收藏",
- "follows": "新的关注着",
+ "follows": "新的关注者",
"load_older": "加载更早的互动"
},
"post_status": {
diff --git a/src/modules/api.js b/src/modules/api.js
index eb6a7980..1293e3c8 100644
--- a/src/modules/api.js
+++ b/src/modules/api.js
@@ -43,6 +43,13 @@ const api = {
const fetcher = store.state.backendInteractor.startFetchingNotifications({ store })
store.commit('addFetcher', { fetcherName: 'notifications', fetcher })
},
+ startFetchingFollowRequest (store) {
+ // Don't start fetching if we already are.
+ if (store.state.fetchers['followRequest']) return
+
+ const fetcher = store.state.backendInteractor.startFetchingFollowRequest({ store })
+ store.commit('addFetcher', { fetcherName: 'followRequest', fetcher })
+ },
stopFetching (store, fetcherName) {
const fetcher = store.state.fetchers[fetcherName]
window.clearInterval(fetcher)
diff --git a/src/modules/config.js b/src/modules/config.js
index d4819ee8..329b4091 100644
--- a/src/modules/config.js
+++ b/src/modules/config.js
@@ -45,6 +45,7 @@ export const defaultState = {
playVideosInModal: false,
useOneClickNsfw: false,
useContainFit: false,
+ greentext: undefined, // instance default
hidePostStats: undefined, // instance default
hideUserStats: undefined // instance default
}
diff --git a/src/modules/instance.js b/src/modules/instance.js
index 7b0e0da4..625323b9 100644
--- a/src/modules/instance.js
+++ b/src/modules/instance.js
@@ -27,11 +27,13 @@ const defaultState = {
scopeCopy: true,
subjectLineBehavior: 'email',
postContentType: 'text/plain',
+ hideSitename: false,
nsfwCensorImage: undefined,
vapidPublicKey: undefined,
noAttachmentLinks: false,
showFeaturesPanel: true,
minimalScopesMode: false,
+ greentext: false,
// Nasty stuff
pleromaBackend: true,
diff --git a/src/modules/users.js b/src/modules/users.js
index 1c9ff5e8..14b2d8b5 100644
--- a/src/modules/users.js
+++ b/src/modules/users.js
@@ -434,6 +434,7 @@ const users = {
store.dispatch('stopFetching', 'friends')
store.commit('setBackendInteractor', backendInteractorService(store.getters.getToken()))
store.dispatch('stopFetching', 'notifications')
+ store.dispatch('stopFetching', 'followRequest')
store.commit('clearNotifications')
store.commit('resetStatuses')
})
diff --git a/src/services/api/api.service.js b/src/services/api/api.service.js
index 8f5eb416..68be0d50 100644
--- a/src/services/api/api.service.js
+++ b/src/services/api/api.service.js
@@ -22,7 +22,7 @@ const MFA_BACKUP_CODES_URL = '/api/pleroma/accounts/mfa/backup_codes'
const MFA_SETUP_OTP_URL = '/api/pleroma/accounts/mfa/setup/totp'
const MFA_CONFIRM_OTP_URL = '/api/pleroma/accounts/mfa/confirm/totp'
-const MFA_DISABLE_OTP_URL = '/api/pleroma/account/mfa/totp'
+const MFA_DISABLE_OTP_URL = '/api/pleroma/accounts/mfa/totp'
const MASTODON_LOGIN_URL = '/api/v1/accounts/verify_credentials'
const MASTODON_REGISTRATION_URL = '/api/v1/accounts'
diff --git a/src/services/backend_interactor_service/backend_interactor_service.js b/src/services/backend_interactor_service/backend_interactor_service.js
index d6617276..c16bd1f1 100644
--- a/src/services/backend_interactor_service/backend_interactor_service.js
+++ b/src/services/backend_interactor_service/backend_interactor_service.js
@@ -1,6 +1,7 @@
import apiService from '../api/api.service.js'
import timelineFetcherService from '../timeline_fetcher/timeline_fetcher.service.js'
import notificationsFetcher from '../notifications_fetcher/notifications_fetcher.service.js'
+import followRequestFetcher from '../../services/follow_request_fetcher/follow_request_fetcher.service'
const backendInteractorService = credentials => {
const fetchStatus = ({ id }) => {
@@ -63,6 +64,10 @@ const backendInteractorService = credentials => {
return notificationsFetcher.startFetching({ store, credentials })
}
+ const startFetchingFollowRequest = ({ store }) => {
+ return followRequestFetcher.startFetching({ store, credentials })
+ }
+
// eslint-disable-next-line camelcase
const tagUser = ({ screen_name }, tag) => {
return apiService.tagUser({ screen_name, tag, credentials })
@@ -111,7 +116,6 @@ const backendInteractorService = credentials => {
const subscribeUser = (id) => apiService.subscribeUser({ credentials, id })
const unsubscribeUser = (id) => apiService.unsubscribeUser({ credentials, id })
const fetchBlocks = () => apiService.fetchBlocks({ credentials })
- const fetchFollowRequests = () => apiService.fetchFollowRequests({ credentials })
const fetchOAuthTokens = () => apiService.fetchOAuthTokens({ credentials })
const revokeOAuthToken = (id) => apiService.revokeOAuthToken({ id, credentials })
const fetchPinnedStatuses = (id) => apiService.fetchPinnedStatuses({ credentials, id })
@@ -168,6 +172,7 @@ const backendInteractorService = credentials => {
verifyCredentials: apiService.verifyCredentials,
startFetchingTimeline,
startFetchingNotifications,
+ startFetchingFollowRequest,
fetchMutes,
muteUser,
unmuteUser,
@@ -203,7 +208,6 @@ const backendInteractorService = credentials => {
mfaSetupOTP,
mfaConfirmOTP,
mfaDisableOTP,
- fetchFollowRequests,
approveUser,
denyUser,
vote,
diff --git a/src/services/entity_normalizer/entity_normalizer.service.js b/src/services/entity_normalizer/entity_normalizer.service.js
index 5f45660d..ca79df6f 100644
--- a/src/services/entity_normalizer/entity_normalizer.service.js
+++ b/src/services/entity_normalizer/entity_normalizer.service.js
@@ -46,6 +46,14 @@ export const parseUser = (data) => {
output.description = data.note
output.description_html = addEmojis(data.note, data.emojis)
+ output.fields = data.fields
+ output.fields_html = data.fields.map(field => {
+ return {
+ name: addEmojis(field.name, data.emojis),
+ value: addEmojis(field.value, data.emojis)
+ }
+ })
+
// Utilize avatar_static for gif avatars?
output.profile_image_url = data.avatar
output.profile_image_url_original = data.avatar
@@ -95,6 +103,7 @@ export const parseUser = (data) => {
if (data.source) {
output.description = data.source.note
output.default_scope = data.source.privacy
+ output.fields = data.source.fields
if (data.source.pleroma) {
output.no_rich_text = data.source.pleroma.no_rich_text
output.show_role = data.source.pleroma.show_role
diff --git a/src/services/tiny_post_html_processor/tiny_post_html_processor.service.js b/src/services/tiny_post_html_processor/tiny_post_html_processor.service.js
new file mode 100644
index 00000000..de6f20ef
--- /dev/null
+++ b/src/services/tiny_post_html_processor/tiny_post_html_processor.service.js
@@ -0,0 +1,94 @@
+/**
+ * This is a tiny purpose-built HTML parser/processor. This basically detects any type of visual newline and
+ * allows it to be processed, useful for greentexting, mostly
+ *
+ * known issue: doesn't handle CDATA so nested CDATA might not work well
+ *
+ * @param {Object} input - input data
+ * @param {(string) => string} processor - function that will be called on every line
+ * @return {string} processed html
+ */
+export const processHtml = (html, processor) => {
+ const handledTags = new Set(['p', 'br', 'div'])
+ const openCloseTags = new Set(['p', 'div'])
+
+ let buffer = '' // Current output buffer
+ const level = [] // How deep we are in tags and which tags were there
+ let textBuffer = '' // Current line content
+ let tagBuffer = null // Current tag buffer, if null = we are not currently reading a tag
+
+ // Extracts tag name from tag, i.e. <span a="b"> => span
+ const getTagName = (tag) => {
+ const result = /(?:<\/(\w+)>|<(\w+)\s?[^/]*?\/?>)/gi.exec(tag)
+ return result && (result[1] || result[2])
+ }
+
+ const flush = () => { // Processes current line buffer, adds it to output buffer and clears line buffer
+ if (textBuffer.trim().length > 0) {
+ buffer += processor(textBuffer)
+ } else {
+ buffer += textBuffer
+ }
+ textBuffer = ''
+ }
+
+ const handleBr = (tag) => { // handles single newlines/linebreaks/selfclosing
+ flush()
+ buffer += tag
+ }
+
+ const handleOpen = (tag) => { // handles opening tags
+ flush()
+ buffer += tag
+ level.push(tag)
+ }
+
+ const handleClose = (tag) => { // handles closing tags
+ flush()
+ buffer += tag
+ if (level[level.length - 1] === tag) {
+ level.pop()
+ }
+ }
+
+ for (let i = 0; i < html.length; i++) {
+ const char = html[i]
+ if (char === '<' && tagBuffer === null) {
+ tagBuffer = char
+ } else if (char !== '>' && tagBuffer !== null) {
+ tagBuffer += char
+ } else if (char === '>' && tagBuffer !== null) {
+ tagBuffer += char
+ const tagFull = tagBuffer
+ tagBuffer = null
+ const tagName = getTagName(tagFull)
+ if (handledTags.has(tagName)) {
+ if (tagName === 'br') {
+ handleBr(tagFull)
+ } else if (openCloseTags.has(tagName)) {
+ if (tagFull[1] === '/') {
+ handleClose(tagFull)
+ } else if (tagFull[tagFull.length - 2] === '/') {
+ // self-closing
+ handleBr(tagFull)
+ } else {
+ handleOpen(tagFull)
+ }
+ }
+ } else {
+ textBuffer += tagFull
+ }
+ } else if (char === '\n') {
+ handleBr(char)
+ } else {
+ textBuffer += char
+ }
+ }
+ if (tagBuffer) {
+ textBuffer += tagBuffer
+ }
+
+ flush()
+
+ return buffer
+}