Merge remote-tracking branch 'upstream/develop' into shigusegubu
* upstream/develop: (37 commits) Match users using startsWith instead of match. Match emoji using startsWith instead of match. remove-unused-settings Preserve subject in replies. Don't use nsfw clickthrough if the post is collapsed by default. correct /static/config.json decoding save /api/statusnet/config.json connection rename apiStatusnetConfigSitePleromafe to apiConfig fix typo Add a checkbox for marking a post's attachments as NSFW When a post with a subject is collapsed, hide its attachments. Make interface language configurable from settings attachment: add support for rendering alt text on images Don't hide replies when inConversation. Fix indentation Remove old implementation of isReply. Add settings for changing the visibility of replies in the timeline. Update Russian translations update fixed error not displaying for 500 error. ...
This commit is contained in:
commit
16ec650b71
23 changed files with 392 additions and 168 deletions
|
|
@ -63,6 +63,7 @@
|
||||||
"html-webpack-plugin": "^2.8.1",
|
"html-webpack-plugin": "^2.8.1",
|
||||||
"http-proxy-middleware": "^0.17.2",
|
"http-proxy-middleware": "^0.17.2",
|
||||||
"inject-loader": "^2.0.1",
|
"inject-loader": "^2.0.1",
|
||||||
|
"iso-639-1": "^2.0.3",
|
||||||
"isparta-loader": "^2.0.0",
|
"isparta-loader": "^2.0.0",
|
||||||
"json-loader": "^0.5.4",
|
"json-loader": "^0.5.4",
|
||||||
"karma": "^1.3.0",
|
"karma": "^1.3.0",
|
||||||
|
|
|
||||||
|
|
@ -20,6 +20,10 @@ export default {
|
||||||
data: () => ({
|
data: () => ({
|
||||||
mobileActivePanel: 'timeline'
|
mobileActivePanel: 'timeline'
|
||||||
}),
|
}),
|
||||||
|
created () {
|
||||||
|
// Load the locale from the storage
|
||||||
|
this.$i18n.locale = this.$store.state.config.interfaceLanguage
|
||||||
|
},
|
||||||
computed: {
|
computed: {
|
||||||
currentUser () { return this.$store.state.users.currentUser },
|
currentUser () { return this.$store.state.users.currentUser },
|
||||||
background () {
|
background () {
|
||||||
|
|
@ -29,7 +33,7 @@ export default {
|
||||||
style () { return { 'background-image': `url(${this.background})` } },
|
style () { return { 'background-image': `url(${this.background})` } },
|
||||||
sitename () { return this.$store.state.config.name },
|
sitename () { return this.$store.state.config.name },
|
||||||
chat () { return this.$store.state.chat.channel.state === 'joined' },
|
chat () { return this.$store.state.chat.channel.state === 'joined' },
|
||||||
showWhoToFollowPanel () { return this.$store.state.config.showWhoToFollowPanel },
|
suggestionsEnabled () { return this.$store.state.config.suggestionsEnabled },
|
||||||
showInstanceSpecificPanel () { return this.$store.state.config.showInstanceSpecificPanel }
|
showInstanceSpecificPanel () { return this.$store.state.config.showInstanceSpecificPanel }
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
|
|
||||||
|
|
@ -63,8 +63,6 @@ button{
|
||||||
box-shadow: 0px 0px 2px black;
|
box-shadow: 0px 0px 2px black;
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
font-family: sans-serif;
|
font-family: sans-serif;
|
||||||
min-width: 10em;
|
|
||||||
min-height: 2em;
|
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
box-shadow: 0px 0px 4px rgba(255, 255, 255, 0.3);
|
box-shadow: 0px 0px 4px rgba(255, 255, 255, 0.3);
|
||||||
|
|
|
||||||
|
|
@ -24,7 +24,7 @@
|
||||||
<user-panel></user-panel>
|
<user-panel></user-panel>
|
||||||
<nav-panel></nav-panel>
|
<nav-panel></nav-panel>
|
||||||
<instance-specific-panel v-if="showInstanceSpecificPanel"></instance-specific-panel>
|
<instance-specific-panel v-if="showInstanceSpecificPanel"></instance-specific-panel>
|
||||||
<who-to-follow-panel v-if="currentUser && showWhoToFollowPanel"></who-to-follow-panel>
|
<who-to-follow-panel v-if="currentUser && suggestionsEnabled"></who-to-follow-panel>
|
||||||
<notifications v-if="currentUser"></notifications>
|
<notifications v-if="currentUser"></notifications>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,7 @@
|
||||||
<a href="#" @click.prevent="toggleHidden()">Hide</a>
|
<a href="#" @click.prevent="toggleHidden()">Hide</a>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<a v-if="type === 'image' && !hidden" class="image-attachment" :href="attachment.url" target="_blank">
|
<a v-if="type === 'image' && !hidden" class="image-attachment" :href="attachment.url" target="_blank" :title="attachment.description">
|
||||||
<StillImage :class="{'small': isSmall}" referrerpolicy="no-referrer" :mimetype="attachment.mimetype" :src="attachment.large_thumb_url || attachment.url"/>
|
<StillImage :class="{'small': isSmall}" referrerpolicy="no-referrer" :mimetype="attachment.mimetype" :src="attachment.large_thumb_url || attachment.url"/>
|
||||||
</a>
|
</a>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,38 @@
|
||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<label for="interface-language-switcher" class='select'>
|
||||||
|
<select id="interface-language-switcher" v-model="language">
|
||||||
|
<option v-for="(langCode, i) in languageCodes" :value="langCode">
|
||||||
|
{{ languageNames[i] }}
|
||||||
|
</option>
|
||||||
|
</select>
|
||||||
|
<i class="icon-down-open"/>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import languagesObject from '../../i18n/messages'
|
||||||
|
import ISO6391 from 'iso-639-1'
|
||||||
|
import _ from 'lodash'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
computed: {
|
||||||
|
languageCodes () {
|
||||||
|
return Object.keys(languagesObject)
|
||||||
|
},
|
||||||
|
|
||||||
|
languageNames () {
|
||||||
|
return _.map(this.languageCodes, ISO6391.getName)
|
||||||
|
},
|
||||||
|
|
||||||
|
language: {
|
||||||
|
get: function () { return this.$store.state.config.interfaceLanguage },
|
||||||
|
set: function (val) {
|
||||||
|
this.$store.dispatch('setOption', { name: 'interfaceLanguage', value: val })
|
||||||
|
this.$i18n.locale = val
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
@ -34,6 +34,11 @@
|
||||||
@import '../../_variables.scss';
|
@import '../../_variables.scss';
|
||||||
|
|
||||||
.login-form {
|
.login-form {
|
||||||
|
.btn {
|
||||||
|
min-height: 28px;
|
||||||
|
width: 10em;
|
||||||
|
}
|
||||||
|
|
||||||
.error {
|
.error {
|
||||||
text-align: center;
|
text-align: center;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -24,7 +24,8 @@ const PostStatusForm = {
|
||||||
'replyTo',
|
'replyTo',
|
||||||
'repliedUser',
|
'repliedUser',
|
||||||
'attentions',
|
'attentions',
|
||||||
'messageScope'
|
'messageScope',
|
||||||
|
'subject'
|
||||||
],
|
],
|
||||||
components: {
|
components: {
|
||||||
MediaUpload
|
MediaUpload
|
||||||
|
|
@ -52,7 +53,9 @@ const PostStatusForm = {
|
||||||
posting: false,
|
posting: false,
|
||||||
highlighted: 0,
|
highlighted: 0,
|
||||||
newStatus: {
|
newStatus: {
|
||||||
|
spoilerText: this.subject,
|
||||||
status: statusText,
|
status: statusText,
|
||||||
|
nsfw: false,
|
||||||
files: [],
|
files: [],
|
||||||
visibility: this.messageScope || this.$store.state.users.currentUser.default_scope
|
visibility: this.messageScope || this.$store.state.users.currentUser.default_scope
|
||||||
},
|
},
|
||||||
|
|
@ -72,7 +75,7 @@ const PostStatusForm = {
|
||||||
const firstchar = this.textAtCaret.charAt(0)
|
const firstchar = this.textAtCaret.charAt(0)
|
||||||
if (firstchar === '@') {
|
if (firstchar === '@') {
|
||||||
const matchedUsers = filter(this.users, (user) => (String(user.name + user.screen_name)).toUpperCase()
|
const matchedUsers = filter(this.users, (user) => (String(user.name + user.screen_name)).toUpperCase()
|
||||||
.match(this.textAtCaret.slice(1).toUpperCase()))
|
.startsWith(this.textAtCaret.slice(1).toUpperCase()))
|
||||||
if (matchedUsers.length <= 0) {
|
if (matchedUsers.length <= 0) {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
@ -86,7 +89,7 @@ const PostStatusForm = {
|
||||||
}))
|
}))
|
||||||
} else if (firstchar === ':') {
|
} else if (firstchar === ':') {
|
||||||
if (this.textAtCaret === ':') { return }
|
if (this.textAtCaret === ':') { return }
|
||||||
const matchedEmoji = filter(this.emoji.concat(this.customEmoji), (emoji) => emoji.shortcode.match(this.textAtCaret.slice(1)))
|
const matchedEmoji = filter(this.emoji.concat(this.customEmoji), (emoji) => emoji.shortcode.startsWith(this.textAtCaret.slice(1)))
|
||||||
if (matchedEmoji.length <= 0) {
|
if (matchedEmoji.length <= 0) {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
@ -204,6 +207,7 @@ const PostStatusForm = {
|
||||||
status: newStatus.status,
|
status: newStatus.status,
|
||||||
spoilerText: newStatus.spoilerText || null,
|
spoilerText: newStatus.spoilerText || null,
|
||||||
visibility: newStatus.visibility,
|
visibility: newStatus.visibility,
|
||||||
|
sensitive: newStatus.nsfw,
|
||||||
media: newStatus.files,
|
media: newStatus.files,
|
||||||
store: this.$store,
|
store: this.$store,
|
||||||
inReplyToStatusId: this.replyTo
|
inReplyToStatusId: this.replyTo
|
||||||
|
|
|
||||||
|
|
@ -75,6 +75,11 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="upload_settings" v-if="newStatus.files.length > 0">
|
||||||
|
<input type="checkbox" id="filesSensitive" v-model="newStatus.nsfw">
|
||||||
|
<label for="filesSensitive" v-if="newStatus.nsfw">{{$t('post_status.attachments_sensitive')}}</label>
|
||||||
|
<label for="filesSensitive" v-else v-html="$t('post_status.attachments_not_sensitive')"></label>
|
||||||
|
</div>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
@ -107,6 +112,10 @@
|
||||||
padding: 0.5em;
|
padding: 0.5em;
|
||||||
height: 32px;
|
height: 32px;
|
||||||
|
|
||||||
|
button {
|
||||||
|
width: 10em;
|
||||||
|
}
|
||||||
|
|
||||||
p {
|
p {
|
||||||
margin: 0.35em;
|
margin: 0.35em;
|
||||||
padding: 0.35em;
|
padding: 0.35em;
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
/* eslint-env browser */
|
/* eslint-env browser */
|
||||||
import StyleSwitcher from '../style_switcher/style_switcher.vue'
|
import StyleSwitcher from '../style_switcher/style_switcher.vue'
|
||||||
|
import InterfaceLanguageSwitcher from '../interface_language_switcher/interface_language_switcher.vue'
|
||||||
import { filter, trim } from 'lodash'
|
import { filter, trim } from 'lodash'
|
||||||
|
|
||||||
const settings = {
|
const settings = {
|
||||||
|
|
@ -8,6 +9,7 @@ const settings = {
|
||||||
hideAttachmentsLocal: this.$store.state.config.hideAttachments,
|
hideAttachmentsLocal: this.$store.state.config.hideAttachments,
|
||||||
hideAttachmentsInConvLocal: this.$store.state.config.hideAttachmentsInConv,
|
hideAttachmentsInConvLocal: this.$store.state.config.hideAttachmentsInConv,
|
||||||
hideNsfwLocal: this.$store.state.config.hideNsfw,
|
hideNsfwLocal: this.$store.state.config.hideNsfw,
|
||||||
|
replyVisibilityLocal: this.$store.state.config.replyVisibility,
|
||||||
loopVideoLocal: this.$store.state.config.loopVideo,
|
loopVideoLocal: this.$store.state.config.loopVideo,
|
||||||
loopVideoSilentOnlyLocal: this.$store.state.config.loopVideoSilentOnly,
|
loopVideoSilentOnlyLocal: this.$store.state.config.loopVideoSilentOnly,
|
||||||
muteWordsString: this.$store.state.config.muteWords.join('\n'),
|
muteWordsString: this.$store.state.config.muteWords.join('\n'),
|
||||||
|
|
@ -27,7 +29,8 @@ const settings = {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
components: {
|
components: {
|
||||||
StyleSwitcher
|
StyleSwitcher,
|
||||||
|
InterfaceLanguageSwitcher
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
user () {
|
user () {
|
||||||
|
|
@ -44,6 +47,9 @@ const settings = {
|
||||||
hideNsfwLocal (value) {
|
hideNsfwLocal (value) {
|
||||||
this.$store.dispatch('setOption', { name: 'hideNsfw', value })
|
this.$store.dispatch('setOption', { name: 'hideNsfw', value })
|
||||||
},
|
},
|
||||||
|
replyVisibilityLocal (value) {
|
||||||
|
this.$store.dispatch('setOption', { name: 'replyVisibility', value })
|
||||||
|
},
|
||||||
loopVideoLocal (value) {
|
loopVideoLocal (value) {
|
||||||
this.$store.dispatch('setOption', { name: 'loopVideo', value })
|
this.$store.dispatch('setOption', { name: 'loopVideo', value })
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -38,6 +38,16 @@
|
||||||
<input type="checkbox" id="hoverPreview" v-model="hoverPreviewLocal">
|
<input type="checkbox" id="hoverPreview" v-model="hoverPreviewLocal">
|
||||||
<label for="hoverPreview">{{$t('settings.reply_link_preview')}}</label>
|
<label for="hoverPreview">{{$t('settings.reply_link_preview')}}</label>
|
||||||
</li>
|
</li>
|
||||||
|
<li>
|
||||||
|
<label for="replyVisibility" class="select">
|
||||||
|
<select id="replyVisibility" v-model="replyVisibilityLocal">
|
||||||
|
<option value="all" selected>{{$t('settings.reply_visibility_all')}}</option>
|
||||||
|
<option value="following">{{$t('settings.reply_visibility_following')}}</option>
|
||||||
|
<option value="self">{{$t('settings.reply_visibility_self')}}</option>
|
||||||
|
</select>
|
||||||
|
<i class="icon-down-open"/>
|
||||||
|
</label>
|
||||||
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
<div class="setting-item">
|
<div class="setting-item">
|
||||||
|
|
@ -74,6 +84,10 @@
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="setting-item">
|
||||||
|
<h2>{{ $t('settings.interfaceLanguage') }}</h2>
|
||||||
|
<interface-language-switcher />
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
@ -117,6 +131,8 @@
|
||||||
|
|
||||||
.btn {
|
.btn {
|
||||||
margin-top: 1em;
|
margin-top: 1em;
|
||||||
|
min-height: 28px;
|
||||||
|
width: 10em;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.setting-list {
|
.setting-list {
|
||||||
|
|
|
||||||
|
|
@ -83,7 +83,6 @@ const Status = {
|
||||||
return hits
|
return hits
|
||||||
},
|
},
|
||||||
muted () { return !this.unmuted && (this.status.user.muted || this.muteWordHits.length > 0) },
|
muted () { return !this.unmuted && (this.status.user.muted || this.muteWordHits.length > 0) },
|
||||||
isReply () { return !!this.status.in_reply_to_status_id },
|
|
||||||
isFocused () {
|
isFocused () {
|
||||||
// retweet or root of an expanded conversation
|
// retweet or root of an expanded conversation
|
||||||
if (this.focused) {
|
if (this.focused) {
|
||||||
|
|
@ -105,6 +104,48 @@ const Status = {
|
||||||
const lengthScore = this.status.statusnet_html.split(/<p|<br/).length + this.status.text.length / 80
|
const lengthScore = this.status.statusnet_html.split(/<p|<br/).length + this.status.text.length / 80
|
||||||
return lengthScore > 20
|
return lengthScore > 20
|
||||||
},
|
},
|
||||||
|
isReply () {
|
||||||
|
if (this.status.in_reply_to_status_id) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
// For private replies where we can't see the OP, in_reply_to_status_id will be null.
|
||||||
|
// So instead, check that the post starts with a @mention.
|
||||||
|
if (this.status.visibility === 'private') {
|
||||||
|
var textBody = this.status.text
|
||||||
|
if (this.status.summary !== null) {
|
||||||
|
textBody = textBody.substring(this.status.summary.length, textBody.length)
|
||||||
|
}
|
||||||
|
return textBody.startsWith('@')
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
},
|
||||||
|
hideReply () {
|
||||||
|
if (this.$store.state.config.replyVisibility === 'all') {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if (this.inlineExpanded || this.expanded || this.inConversation || !this.isReply) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if (this.status.user.id === this.$store.state.users.currentUser.id) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if (this.status.activity_type === 'repeat') {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
var checkFollowing = this.$store.state.config.replyVisibility === 'following'
|
||||||
|
for (var i = 0; i < this.status.attentions.length; ++i) {
|
||||||
|
if (this.status.user.id === this.status.attentions[i].id) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if (checkFollowing && this.status.attentions[i].following) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if (this.status.attentions[i].id === this.$store.state.users.currentUser.id) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return this.status.attentions.length > 0
|
||||||
|
},
|
||||||
hideSubjectStatus () {
|
hideSubjectStatus () {
|
||||||
if (this.tallStatus && !this.$store.state.config.collapseMessageWithSubject) {
|
if (this.tallStatus && !this.$store.state.config.collapseMessageWithSubject) {
|
||||||
return false
|
return false
|
||||||
|
|
@ -123,6 +164,21 @@ const Status = {
|
||||||
showingMore () {
|
showingMore () {
|
||||||
return this.showingTall || (this.status.summary && this.expandingSubject)
|
return this.showingTall || (this.status.summary && this.expandingSubject)
|
||||||
},
|
},
|
||||||
|
nsfwClickthrough () {
|
||||||
|
if (!this.status.nsfw) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if (this.status.summary && this.$store.state.config.collapseMessageWithSubject) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
},
|
||||||
|
replySubject () {
|
||||||
|
if (this.status.summary && !this.status.summary.match(/^re[: ]/i)) {
|
||||||
|
return 're: '.concat(this.status.summary)
|
||||||
|
}
|
||||||
|
return this.status.summary
|
||||||
|
},
|
||||||
attachmentSize () {
|
attachmentSize () {
|
||||||
if ((this.$store.state.config.hideAttachments && !this.inConversation) ||
|
if ((this.$store.state.config.hideAttachments && !this.inConversation) ||
|
||||||
(this.$store.state.config.hideAttachmentsInConv && this.inConversation)) {
|
(this.$store.state.config.hideAttachmentsInConv && this.inConversation)) {
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
<template>
|
<template>
|
||||||
<div class="status-el" :class="[{ 'status-el_focused': isFocused }, { 'status-conversation': inlineExpanded }]">
|
<div class="status-el" v-if="!hideReply" :class="[{ 'status-el_focused': isFocused }, { 'status-conversation': inlineExpanded }]">
|
||||||
<template v-if="muted && !noReplyLinks">
|
<template v-if="muted && !noReplyLinks">
|
||||||
<div class="media status container 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><router-link :to="{ name: 'user-profile', params: { id: status.user.id } }">{{status.user.screen_name}}</router-link></small>
|
||||||
|
|
@ -83,8 +83,8 @@
|
||||||
<a v-if="showingMore" href="#" class="status-unhider" @click.prevent="toggleShowMore">Show less</a>
|
<a v-if="showingMore" href="#" class="status-unhider" @click.prevent="toggleShowMore">Show less</a>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div v-if='status.attachments' class='attachments media-body'>
|
<div v-if='status.attachments && !hideSubjectStatus' class='attachments media-body'>
|
||||||
<attachment :size="attachmentSize" :status-id="status.id" :nsfw="status.nsfw" :attachment="attachment" v-for="attachment in status.attachments" :key="attachment.id">
|
<attachment :size="attachmentSize" :status-id="status.id" :nsfw="nsfwClickthrough" :attachment="attachment" v-for="attachment in status.attachments" :key="attachment.id">
|
||||||
</attachment>
|
</attachment>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
@ -102,7 +102,7 @@
|
||||||
</div>
|
</div>
|
||||||
<div class="container" v-if="replying">
|
<div class="container" v-if="replying">
|
||||||
<div class="reply-left"/>
|
<div class="reply-left"/>
|
||||||
<post-status-form class="reply-body" :reply-to="status.id" :attentions="status.attentions" :repliedUser="status.user" :message-scope="status.visibility" v-on:posted="toggleReplying"/>
|
<post-status-form class="reply-body" :reply-to="status.id" :attentions="status.attentions" :repliedUser="status.user" :message-scope="status.visibility" :subject="replySubject" v-on:posted="toggleReplying"/>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -105,8 +105,8 @@
|
||||||
<span>{{user.followers_count}}</span>
|
<span>{{user.followers_count}}</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<p v-if="!hideBio && user.description_html" v-html="user.description_html"></p>
|
<p v-if="!hideBio && user.description_html" class="profile-bio" v-html="user.description_html"></p>
|
||||||
<p v-else-if="!hideBio">{{ user.description }}</p>
|
<p v-else-if="!hideBio" class="profile-bio">{{ user.description }}</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
@ -130,7 +130,11 @@
|
||||||
.profile-panel-body {
|
.profile-panel-body {
|
||||||
word-wrap: break-word;
|
word-wrap: break-word;
|
||||||
background: linear-gradient(to bottom, rgba(0, 0, 0, 0), $fallback--bg 80%);
|
background: linear-gradient(to bottom, rgba(0, 0, 0, 0), $fallback--bg 80%);
|
||||||
background: linear-gradient(to bottom, rgba(0, 0, 0, 0), var(--bg, $fallback--bg) 80%)
|
background: linear-gradient(to bottom, rgba(0, 0, 0, 0), var(--bg, $fallback--bg) 80%);
|
||||||
|
|
||||||
|
.profile-bio {
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.user-info {
|
.user-info {
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,7 @@
|
||||||
function showWhoToFollow (panel, reply, aHost, aUser) {
|
import apiService from '../../services/api/api.service.js'
|
||||||
var users = reply.ids
|
|
||||||
|
function showWhoToFollow (panel, reply) {
|
||||||
|
var users = reply
|
||||||
var cn
|
var cn
|
||||||
var index = 0
|
var index = 0
|
||||||
var random = Math.floor(Math.random() * 10)
|
var random = Math.floor(Math.random() * 10)
|
||||||
|
|
@ -7,12 +9,12 @@ function showWhoToFollow (panel, reply, aHost, aUser) {
|
||||||
var user
|
var user
|
||||||
user = users[cn]
|
user = users[cn]
|
||||||
var img
|
var img
|
||||||
if (user.icon) {
|
if (user.avatar) {
|
||||||
img = user.icon
|
img = user.avatar
|
||||||
} else {
|
} else {
|
||||||
img = '/images/avi.png'
|
img = '/images/avi.png'
|
||||||
}
|
}
|
||||||
var name = user.to_id
|
var name = user.acct
|
||||||
if (index === 0) {
|
if (index === 0) {
|
||||||
panel.img1 = img
|
panel.img1 = img
|
||||||
panel.name1 = name
|
panel.name1 = name
|
||||||
|
|
@ -52,27 +54,15 @@ function showWhoToFollow (panel, reply, aHost, aUser) {
|
||||||
}
|
}
|
||||||
|
|
||||||
function getWhoToFollow (panel) {
|
function getWhoToFollow (panel) {
|
||||||
var user = panel.$store.state.users.currentUser.screen_name
|
var credentials = panel.$store.state.users.currentUser.credentials
|
||||||
if (user) {
|
if (credentials) {
|
||||||
panel.name1 = 'Loading...'
|
panel.name1 = 'Loading...'
|
||||||
panel.name2 = 'Loading...'
|
panel.name2 = 'Loading...'
|
||||||
panel.name3 = 'Loading...'
|
panel.name3 = 'Loading...'
|
||||||
var host = window.location.hostname
|
apiService.suggestions({credentials: credentials})
|
||||||
var whoToFollowProvider = panel.$store.state.config.whoToFollowProvider
|
.then((reply) => {
|
||||||
var url
|
showWhoToFollow(panel, reply)
|
||||||
url = whoToFollowProvider.replace(/{{host}}/g, encodeURIComponent(host))
|
})
|
||||||
url = url.replace(/{{user}}/g, encodeURIComponent(user))
|
|
||||||
window.fetch(url, {mode: 'cors'}).then(function (response) {
|
|
||||||
if (response.ok) {
|
|
||||||
return response.json()
|
|
||||||
} else {
|
|
||||||
panel.name1 = ''
|
|
||||||
panel.name2 = ''
|
|
||||||
panel.name3 = ''
|
|
||||||
}
|
|
||||||
}).then(function (reply) {
|
|
||||||
showWhoToFollow(panel, reply, host, user)
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -95,26 +85,26 @@ const WhoToFollowPanel = {
|
||||||
moreUrl: function () {
|
moreUrl: function () {
|
||||||
var host = window.location.hostname
|
var host = window.location.hostname
|
||||||
var user = this.user
|
var user = this.user
|
||||||
var whoToFollowLink = this.$store.state.config.whoToFollowLink
|
var suggestionsWeb = this.$store.state.config.suggestionsWeb
|
||||||
var url
|
var url
|
||||||
url = whoToFollowLink.replace(/{{host}}/g, encodeURIComponent(host))
|
url = suggestionsWeb.replace(/{{host}}/g, encodeURIComponent(host))
|
||||||
url = url.replace(/{{user}}/g, encodeURIComponent(user))
|
url = url.replace(/{{user}}/g, encodeURIComponent(user))
|
||||||
return url
|
return url
|
||||||
},
|
},
|
||||||
showWhoToFollowPanel () {
|
suggestionsEnabled () {
|
||||||
return this.$store.state.config.showWhoToFollowPanel
|
return this.$store.state.config.suggestionsEnabled
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
watch: {
|
watch: {
|
||||||
user: function (user, oldUser) {
|
user: function (user, oldUser) {
|
||||||
if (this.showWhoToFollowPanel) {
|
if (this.suggestionsEnabled) {
|
||||||
getWhoToFollow(this)
|
getWhoToFollow(this)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
mounted:
|
mounted:
|
||||||
function () {
|
function () {
|
||||||
if (this.showWhoToFollowPanel) {
|
if (this.suggestionsEnabled) {
|
||||||
getWhoToFollow(this)
|
getWhoToFollow(this)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,7 @@
|
||||||
<div class="panel panel-default base01-background">
|
<div class="panel panel-default base01-background">
|
||||||
<div class="panel-heading timeline-heading base02-background base04">
|
<div class="panel-heading timeline-heading base02-background base04">
|
||||||
<div class="title">
|
<div class="title">
|
||||||
Who to follow
|
{{$t('who_to_follow.who_to_follow')}}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="panel-body who-to-follow">
|
<div class="panel-body who-to-follow">
|
||||||
|
|
@ -11,7 +11,7 @@
|
||||||
<img v-bind:src="img1"/> <router-link :to="{ name: 'user-profile', params: { id: id1 } }">{{ name1 }}</router-link><br>
|
<img v-bind:src="img1"/> <router-link :to="{ name: 'user-profile', params: { id: id1 } }">{{ name1 }}</router-link><br>
|
||||||
<img v-bind:src="img2"/> <router-link :to="{ name: 'user-profile', params: { id: id2 } }">{{ name2 }}</router-link><br>
|
<img v-bind:src="img2"/> <router-link :to="{ name: 'user-profile', params: { id: id2 } }">{{ name2 }}</router-link><br>
|
||||||
<img v-bind:src="img3"/> <router-link :to="{ name: 'user-profile', params: { id: id3 } }">{{ name3 }}</router-link><br>
|
<img v-bind:src="img3"/> <router-link :to="{ name: 'user-profile', params: { id: id3 } }">{{ name3 }}</router-link><br>
|
||||||
<img v-bind:src="$store.state.config.logo"> <a v-bind:href="moreUrl" target="_blank">More</a>
|
<img v-bind:src="$store.state.config.logo"> <a v-bind:href="moreUrl" target="_blank">{{$t('who_to_follow.more')}}</a>
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -325,6 +325,9 @@ const en = {
|
||||||
loop_video: 'Loop videos',
|
loop_video: 'Loop videos',
|
||||||
loop_video_silent_only: 'Loop only videos without sound (i.e. Mastodon\'s "gifs")',
|
loop_video_silent_only: 'Loop only videos without sound (i.e. Mastodon\'s "gifs")',
|
||||||
reply_link_preview: 'Enable reply-link preview on mouse hover',
|
reply_link_preview: 'Enable reply-link preview on mouse hover',
|
||||||
|
reply_visibility_all: 'Show all replies',
|
||||||
|
reply_visibility_following: 'Only show replies directed at me or users I\'m following',
|
||||||
|
reply_visibility_self: 'Only show replies directed at me',
|
||||||
follow_import: 'Follow import',
|
follow_import: 'Follow import',
|
||||||
import_followers_from_a_csv_file: 'Import follows from a csv file',
|
import_followers_from_a_csv_file: 'Import follows from a csv file',
|
||||||
follows_imported: 'Follows imported! Processing them will take a while.',
|
follows_imported: 'Follows imported! Processing them will take a while.',
|
||||||
|
|
@ -347,7 +350,8 @@ const en = {
|
||||||
default_vis: 'Default visibility scope',
|
default_vis: 'Default visibility scope',
|
||||||
profile_tab: 'Profile',
|
profile_tab: 'Profile',
|
||||||
security_tab: 'Security',
|
security_tab: 'Security',
|
||||||
data_import_export_tab: 'Data Import / Export'
|
data_import_export_tab: 'Data Import / Export',
|
||||||
|
interfaceLanguage: 'Interface language'
|
||||||
},
|
},
|
||||||
notifications: {
|
notifications: {
|
||||||
notifications: 'Notifications',
|
notifications: 'Notifications',
|
||||||
|
|
@ -381,6 +385,8 @@ const en = {
|
||||||
account_not_locked_warning: 'Your account is not {0}. Anyone can follow you to view your follower-only posts.',
|
account_not_locked_warning: 'Your account is not {0}. Anyone can follow you to view your follower-only posts.',
|
||||||
account_not_locked_warning_link: 'locked',
|
account_not_locked_warning_link: 'locked',
|
||||||
direct_warning: 'This post will only be visible to all the mentioned users.',
|
direct_warning: 'This post will only be visible to all the mentioned users.',
|
||||||
|
attachments_sensitive: 'Attachments marked sensitive',
|
||||||
|
attachments_not_sensitive: 'Attachments <strong>not</strong> marked sensitive',
|
||||||
scope: {
|
scope: {
|
||||||
public: 'Public - Post to public timelines',
|
public: 'Public - Post to public timelines',
|
||||||
unlisted: 'Unlisted - Do not post to public timelines',
|
unlisted: 'Unlisted - Do not post to public timelines',
|
||||||
|
|
@ -398,6 +404,10 @@ const en = {
|
||||||
},
|
},
|
||||||
user_profile: {
|
user_profile: {
|
||||||
timeline_title: 'User Timeline'
|
timeline_title: 'User Timeline'
|
||||||
|
},
|
||||||
|
who_to_follow: {
|
||||||
|
who_to_follow: 'Who to follow',
|
||||||
|
more: 'More'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -781,115 +791,147 @@ const ja = {
|
||||||
chat: 'ローカルチャット',
|
chat: 'ローカルチャット',
|
||||||
timeline: 'タイムライン',
|
timeline: 'タイムライン',
|
||||||
mentions: 'メンション',
|
mentions: 'メンション',
|
||||||
public_tl: '公開タイムライン',
|
public_tl: 'パブリックタイムライン',
|
||||||
twkn: '接続しているすべてのネットワーク'
|
twkn: 'つながっているすべてのネットワーク',
|
||||||
|
friend_requests: 'Follow Requests'
|
||||||
},
|
},
|
||||||
user_card: {
|
user_card: {
|
||||||
follows_you: 'フォローされました!',
|
follows_you: 'フォローされました!',
|
||||||
following: 'フォロー中!',
|
following: 'フォローしています!',
|
||||||
follow: 'フォロー',
|
follow: 'フォロー',
|
||||||
blocked: 'ブロック済み!',
|
blocked: 'ブロックしています!',
|
||||||
block: 'ブロック',
|
block: 'ブロック',
|
||||||
statuses: '投稿',
|
statuses: 'ステータス',
|
||||||
mute: 'ミュート',
|
mute: 'ミュート',
|
||||||
muted: 'ミュート済み',
|
muted: 'ミュートしています!',
|
||||||
followers: 'フォロワー',
|
followers: 'フォロワー',
|
||||||
followees: 'フォロー',
|
followees: 'フォロー',
|
||||||
per_day: '/日',
|
per_day: '/日',
|
||||||
remote_follow: 'リモートフォロー'
|
remote_follow: 'リモートフォロー',
|
||||||
|
approve: 'Approve',
|
||||||
|
deny: 'Deny'
|
||||||
},
|
},
|
||||||
timeline: {
|
timeline: {
|
||||||
show_new: '更新',
|
show_new: 'よみこみ',
|
||||||
error_fetching: '更新の取得中にエラーが発生しました。',
|
error_fetching: 'よみこみがエラーになりました。',
|
||||||
up_to_date: '最新',
|
up_to_date: 'さいしん',
|
||||||
load_older: '古い投稿を読み込む',
|
load_older: 'ふるいステータス',
|
||||||
conversation: '会話',
|
conversation: 'スレッド',
|
||||||
collapse: '折り畳む',
|
collapse: 'たたむ',
|
||||||
repeated: 'リピート'
|
repeated: 'リピート'
|
||||||
},
|
},
|
||||||
settings: {
|
settings: {
|
||||||
user_settings: 'ユーザー設定',
|
user_settings: 'ユーザーせってい',
|
||||||
name_bio: '名前とプロフィール',
|
name_bio: 'なまえとプロフィール',
|
||||||
name: '名前',
|
name: 'なまえ',
|
||||||
bio: 'プロフィール',
|
bio: 'プロフィール',
|
||||||
avatar: 'アバター',
|
avatar: 'アバター',
|
||||||
current_avatar: 'あなたの現在のアバター',
|
current_avatar: 'いまのアバター',
|
||||||
set_new_avatar: '新しいアバターを設定する',
|
set_new_avatar: 'あたらしいアバターをせっていする',
|
||||||
profile_banner: 'プロフィールバナー',
|
profile_banner: 'プロフィールバナー',
|
||||||
current_profile_banner: '現在のプロフィールバナー',
|
current_profile_banner: 'いまのプロフィールバナー',
|
||||||
set_new_profile_banner: '新しいプロフィールバナーを設定する',
|
set_new_profile_banner: 'あたらしいプロフィールバナーを設定する',
|
||||||
profile_background: 'プロフィールの背景',
|
profile_background: 'プロフィールのバックグラウンド',
|
||||||
set_new_profile_background: '新しいプロフィールの背景を設定する',
|
set_new_profile_background: 'あたらしいプロフィールのバックグラウンドをせっていする',
|
||||||
settings: '設定',
|
settings: 'せってい',
|
||||||
theme: 'テーマ',
|
theme: 'テーマ',
|
||||||
presets: 'プリセット',
|
presets: 'プリセット',
|
||||||
theme_help: '16進数カラーコード (#aabbcc) を使用してカラーテーマをカスタマイズ出来ます。',
|
theme_help: 'カラーテーマをカスタマイズできます。',
|
||||||
radii_help: 'インターフェースの縁の丸さを設定する。',
|
radii_help: 'インターフェースのまるさをせっていする。',
|
||||||
background: '背景',
|
background: 'バックグラウンド',
|
||||||
foreground: '前景',
|
foreground: 'フォアグラウンド',
|
||||||
text: '文字',
|
text: 'もじ',
|
||||||
links: 'リンク',
|
links: 'リンク',
|
||||||
cBlue: '青 (返信, フォロー)',
|
cBlue: 'あお (リプライ, フォロー)',
|
||||||
cRed: '赤 (キャンセル)',
|
cRed: 'あか (キャンセル)',
|
||||||
cOrange: 'オレンジ (お気に入り)',
|
cOrange: 'オレンジ (おきにいり)',
|
||||||
cGreen: '緑 (リツイート)',
|
cGreen: 'みどり (リピート)',
|
||||||
btnRadius: 'ボタン',
|
btnRadius: 'ボタン',
|
||||||
|
inputRadius: 'Input fields',
|
||||||
panelRadius: 'パネル',
|
panelRadius: 'パネル',
|
||||||
avatarRadius: 'アバター',
|
avatarRadius: 'アバター',
|
||||||
avatarAltRadius: 'アバター (通知)',
|
avatarAltRadius: 'アバター (つうち)',
|
||||||
tooltipRadius: 'ツールチップ/アラート',
|
tooltipRadius: 'ツールチップ/アラート',
|
||||||
attachmentRadius: 'ファイル',
|
attachmentRadius: 'ファイル',
|
||||||
filtering: 'フィルタリング',
|
filtering: 'フィルタリング',
|
||||||
filtering_explanation: 'これらの単語を含むすべてのものがミュートされます。1行に1つの単語を入力してください。',
|
filtering_explanation: 'これらのことばをふくむすべてのものがミュートされます。1行に1つのことばをかいてください。',
|
||||||
attachments: 'ファイル',
|
attachments: 'ファイル',
|
||||||
hide_attachments_in_tl: 'タイムラインのファイルを隠す。',
|
hide_attachments_in_tl: 'タイムラインのファイルをかくす。',
|
||||||
hide_attachments_in_convo: '会話の中のファイルを隠す。',
|
hide_attachments_in_convo: 'スレッドのファイルをかくす。',
|
||||||
nsfw_clickthrough: 'NSFWファイルの非表示を有効にする。',
|
nsfw_clickthrough: 'NSFWなファイルをかくす。',
|
||||||
stop_gifs: 'カーソルを重ねた時にGIFを再生する。',
|
stop_gifs: 'カーソルをかさねたとき、GIFをうごかす。',
|
||||||
autoload: '下にスクロールした時に自動で読み込むようにする。',
|
autoload: 'したにスクロールしたとき、じどうてきによみこむ。',
|
||||||
streaming: '上までスクロールした時に自動でストリーミングされるようにする。',
|
streaming: 'うえまでスクロールしたとき、じどうてきにストリーミングする。',
|
||||||
reply_link_preview: 'マウスカーソルを重ねた時に返信のプレビューを表示するようにする。',
|
reply_link_preview: 'カーソルをかさねたとき、リプライのプレビューをみる。',
|
||||||
follow_import: 'フォローインポート',
|
follow_import: 'フォローインポート',
|
||||||
import_followers_from_a_csv_file: 'CSVファイルからフォローをインポートする。',
|
import_followers_from_a_csv_file: 'CSVファイルからフォローをインポートする。',
|
||||||
follows_imported: 'フォローがインポートされました!処理に少し時間がかかるかもしれません。',
|
follows_imported: 'フォローがインポートされました! すこしじかんがかかるかもしれません。',
|
||||||
follow_import_error: 'フォロワーのインポート中にエラーが発生しました。'
|
follow_import_error: 'フォローのインポートがエラーになりました。',
|
||||||
|
delete_account: 'アカウントをけす',
|
||||||
|
delete_account_description: 'あなたのアカウントとメッセージが、きえます。',
|
||||||
|
delete_account_instructions: 'ほんとうにアカウントをけしてもいいなら、パスワードをかいてください。',
|
||||||
|
delete_account_error: 'アカウントをけすことが、できなかったかもしれません。インスタンスのかんりしゃに、れんらくしてください。',
|
||||||
|
follow_export: 'フォローのエクスポート',
|
||||||
|
follow_export_processing: 'おまちください。まもなくファイルをダウンロードできます。',
|
||||||
|
follow_export_button: 'エクスポート',
|
||||||
|
change_password: 'パスワードをかえる',
|
||||||
|
current_password: 'いまのパスワード',
|
||||||
|
new_password: 'あたらしいパスワード',
|
||||||
|
confirm_new_password: 'あたらしいパスワードのかくにん',
|
||||||
|
changed_password: 'パスワードが、かわりました!',
|
||||||
|
change_password_error: 'パスワードをかえることが、できなかったかもしれません。',
|
||||||
|
lock_account_description: 'あなたがみとめたひとだけ、あなたのアカウントをフォローできます。'
|
||||||
},
|
},
|
||||||
notifications: {
|
notifications: {
|
||||||
notifications: '通知',
|
notifications: 'つうち',
|
||||||
read: '読んだ!',
|
read: 'よんだ!',
|
||||||
followed_you: 'フォローされました',
|
followed_you: 'フォローされました',
|
||||||
favorited_you: 'あなたの投稿がお気に入りされました',
|
favorited_you: 'あなたのステータスがおきにいりされました',
|
||||||
repeated_you: 'あなたの投稿がリピートされました'
|
repeated_you: 'あなたのステータスがリピートされました'
|
||||||
},
|
},
|
||||||
login: {
|
login: {
|
||||||
login: 'ログイン',
|
login: 'ログイン',
|
||||||
username: 'ユーザー名',
|
username: 'ユーザーめい',
|
||||||
placeholder: '例えば lain',
|
placeholder: 'れい: lain',
|
||||||
password: 'パスワード',
|
password: 'パスワード',
|
||||||
register: '登録',
|
register: 'はじめる',
|
||||||
logout: 'ログアウト'
|
logout: 'ログアウト'
|
||||||
},
|
},
|
||||||
registration: {
|
registration: {
|
||||||
registration: '登録',
|
registration: 'はじめる',
|
||||||
fullname: '表示名',
|
fullname: 'スクリーンネーム',
|
||||||
email: 'Eメール',
|
email: 'Eメール',
|
||||||
bio: 'プロフィール',
|
bio: 'プロフィール',
|
||||||
password_confirm: 'パスワードの確認'
|
password_confirm: 'パスワードのかくにん'
|
||||||
},
|
},
|
||||||
post_status: {
|
post_status: {
|
||||||
posting: '投稿',
|
posting: 'とうこう',
|
||||||
default: 'ちょうどL.A.に着陸しました。'
|
content_warning: 'せつめい (かかなくてもよい)',
|
||||||
|
default: 'はねだくうこうに、つきました。',
|
||||||
|
account_not_locked_warning: 'あなたのアカウントは {0} ではありません。あなたをフォローすれば、だれでも、フォロワーげんていのステータスをよむことができます。',
|
||||||
|
account_not_locked_warning_link: 'ロックされたアカウント',
|
||||||
|
direct_warning: 'このステータスは、メンションされたユーザーだけが、よむことができます。',
|
||||||
|
scope: {
|
||||||
|
public: 'パブリック - パブリックタイムラインにとどきます。',
|
||||||
|
unlisted: 'アンリステッド - パブリックタイムラインにとどきません。',
|
||||||
|
private: 'フォロワーげんてい - フォロワーのみにとどきます。',
|
||||||
|
direct: 'ダイレクト - メンションされたユーザーのみにとどきます。'
|
||||||
|
}
|
||||||
},
|
},
|
||||||
finder: {
|
finder: {
|
||||||
find_user: 'ユーザー検索',
|
find_user: 'ユーザーをさがす',
|
||||||
error_fetching_user: 'ユーザー検索でエラーが発生しました'
|
error_fetching_user: 'ユーザーけんさくがエラーになりました。'
|
||||||
},
|
},
|
||||||
general: {
|
general: {
|
||||||
submit: '送信',
|
submit: 'そうしん',
|
||||||
apply: '適用'
|
apply: 'てきよう'
|
||||||
},
|
},
|
||||||
user_profile: {
|
user_profile: {
|
||||||
timeline_title: 'ユーザータイムライン'
|
timeline_title: 'ユーザータイムライン'
|
||||||
|
},
|
||||||
|
who_to_follow: {
|
||||||
|
who_to_follow: 'おすすめユーザー',
|
||||||
|
more: 'くわしく'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1595,6 +1637,8 @@ const ru = {
|
||||||
set_new_profile_background: 'Загрузить новый фон профиля',
|
set_new_profile_background: 'Загрузить новый фон профиля',
|
||||||
settings: 'Настройки',
|
settings: 'Настройки',
|
||||||
theme: 'Тема',
|
theme: 'Тема',
|
||||||
|
export_theme: 'Экспортировать текущую тему',
|
||||||
|
import_theme: 'Загрузить сохранённую тему',
|
||||||
presets: 'Пресеты',
|
presets: 'Пресеты',
|
||||||
theme_help: 'Используйте шестнадцатеричные коды цветов (#rrggbb) для настройки темы.',
|
theme_help: 'Используйте шестнадцатеричные коды цветов (#rrggbb) для настройки темы.',
|
||||||
radii_help: 'Округление краёв элементов интерфейса (в пикселях)',
|
radii_help: 'Округление краёв элементов интерфейса (в пикселях)',
|
||||||
|
|
@ -1643,7 +1687,13 @@ const ru = {
|
||||||
confirm_new_password: 'Подтверждение нового пароля',
|
confirm_new_password: 'Подтверждение нового пароля',
|
||||||
changed_password: 'Пароль изменён успешно.',
|
changed_password: 'Пароль изменён успешно.',
|
||||||
change_password_error: 'Произошла ошибка при попытке изменить пароль.',
|
change_password_error: 'Произошла ошибка при попытке изменить пароль.',
|
||||||
limited_availability: 'Не доступно в вашем браузере'
|
lock_account_description: 'Аккаунт доступен только подтверждённым подписчикам',
|
||||||
|
limited_availability: 'Не доступно в вашем браузере',
|
||||||
|
profile_tab: 'Профиль',
|
||||||
|
security_tab: 'Безопасность',
|
||||||
|
data_import_export_tab: 'Импорт / Экспорт данных',
|
||||||
|
collapse_subject: 'Сворачивать посты с темой',
|
||||||
|
interfaceLanguage: 'Язык интерфейса'
|
||||||
},
|
},
|
||||||
notifications: {
|
notifications: {
|
||||||
notifications: 'Уведомления',
|
notifications: 'Уведомления',
|
||||||
|
|
|
||||||
133
src/main.js
133
src/main.js
|
|
@ -49,6 +49,7 @@ const persistedStateOptions = {
|
||||||
'config.hideAttachments',
|
'config.hideAttachments',
|
||||||
'config.hideAttachmentsInConv',
|
'config.hideAttachmentsInConv',
|
||||||
'config.hideNsfw',
|
'config.hideNsfw',
|
||||||
|
'config.replyVisibility',
|
||||||
'config.autoLoad',
|
'config.autoLoad',
|
||||||
'config.hoverPreview',
|
'config.hoverPreview',
|
||||||
'config.streaming',
|
'config.streaming',
|
||||||
|
|
@ -59,6 +60,7 @@ const persistedStateOptions = {
|
||||||
'config.loopVideoSilentOnly',
|
'config.loopVideoSilentOnly',
|
||||||
'config.pauseOnUnfocused',
|
'config.pauseOnUnfocused',
|
||||||
'config.stopGifs',
|
'config.stopGifs',
|
||||||
|
'config.interfaceLanguage',
|
||||||
'users.lastLoginName',
|
'users.lastLoginName',
|
||||||
'statuses.notifications.maxSavedId'
|
'statuses.notifications.maxSavedId'
|
||||||
]
|
]
|
||||||
|
|
@ -78,6 +80,7 @@ const store = new Vuex.Store({
|
||||||
})
|
})
|
||||||
|
|
||||||
const i18n = new VueI18n({
|
const i18n = new VueI18n({
|
||||||
|
// By default, use the browser locale, we will update it if neccessary
|
||||||
locale: currentLocale,
|
locale: currentLocale,
|
||||||
fallbackLocale: 'en',
|
fallbackLocale: 'en',
|
||||||
messages
|
messages
|
||||||
|
|
@ -92,65 +95,79 @@ window.fetch('/api/statusnet/config.json')
|
||||||
store.dispatch('setOption', { name: 'registrationOpen', value: (registrationClosed === '0') })
|
store.dispatch('setOption', { name: 'registrationOpen', value: (registrationClosed === '0') })
|
||||||
store.dispatch('setOption', { name: 'textlimit', value: parseInt(textlimit) })
|
store.dispatch('setOption', { name: 'textlimit', value: parseInt(textlimit) })
|
||||||
store.dispatch('setOption', { name: 'server', value: server })
|
store.dispatch('setOption', { name: 'server', value: server })
|
||||||
})
|
|
||||||
|
|
||||||
window.fetch('/static/config.json')
|
var apiConfig = data.site.pleromafe
|
||||||
.then((res) => res.json())
|
|
||||||
.then((data) => {
|
|
||||||
const {theme, background, logo, showWhoToFollowPanel, whoToFollowProvider, whoToFollowLink, showInstanceSpecificPanel, scopeOptionsEnabled, collapseMessageWithSubject} = data
|
|
||||||
store.dispatch('setOption', { name: 'theme', value: theme })
|
|
||||||
store.dispatch('setOption', { name: 'background', value: background })
|
|
||||||
store.dispatch('setOption', { name: 'logo', value: logo })
|
|
||||||
store.dispatch('setOption', { name: 'showWhoToFollowPanel', value: showWhoToFollowPanel })
|
|
||||||
store.dispatch('setOption', { name: 'whoToFollowProvider', value: whoToFollowProvider })
|
|
||||||
store.dispatch('setOption', { name: 'whoToFollowLink', value: whoToFollowLink })
|
|
||||||
store.dispatch('setOption', { name: 'showInstanceSpecificPanel', value: showInstanceSpecificPanel })
|
|
||||||
store.dispatch('setOption', { name: 'scopeOptionsEnabled', value: scopeOptionsEnabled })
|
|
||||||
store.dispatch('setOption', { name: 'collapseMessageWithSubject', value: collapseMessageWithSubject })
|
|
||||||
if (data['chatDisabled']) {
|
|
||||||
store.dispatch('disableChat')
|
|
||||||
}
|
|
||||||
|
|
||||||
const routes = [
|
window.fetch('/static/config.json')
|
||||||
{ name: 'root',
|
.then((res) => res.json())
|
||||||
path: '/',
|
.then((data) => {
|
||||||
redirect: to => {
|
var staticConfig = data
|
||||||
var redirectRootLogin = data['redirectRootLogin']
|
|
||||||
var redirectRootNoLogin = data['redirectRootNoLogin']
|
|
||||||
return (store.state.users.currentUser ? redirectRootLogin : redirectRootNoLogin) || '/main/all'
|
|
||||||
}},
|
|
||||||
{ path: '/main/all', component: PublicAndExternalTimeline },
|
|
||||||
{ path: '/main/public', component: PublicTimeline },
|
|
||||||
{ path: '/main/friends', component: FriendsTimeline },
|
|
||||||
{ path: '/tag/:tag', component: TagTimeline },
|
|
||||||
{ 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 },
|
|
||||||
{ name: 'registration', path: '/registration', component: Registration },
|
|
||||||
{ name: 'registration', path: '/registration/:token', component: Registration },
|
|
||||||
{ name: 'friend-requests', path: '/friend-requests', component: FollowRequests },
|
|
||||||
{ name: 'user-settings', path: '/user-settings', component: UserSettings }
|
|
||||||
]
|
|
||||||
|
|
||||||
const router = new VueRouter({
|
var theme = (apiConfig.theme || staticConfig.theme)
|
||||||
mode: 'history',
|
var background = (apiConfig.background || staticConfig.background)
|
||||||
routes,
|
var logo = (apiConfig.logo || staticConfig.logo)
|
||||||
scrollBehavior: (to, from, savedPosition) => {
|
var redirectRootNoLogin = (apiConfig.redirectRootNoLogin || staticConfig.redirectRootNoLogin)
|
||||||
if (to.matched.some(m => m.meta.dontScroll)) {
|
var redirectRootLogin = (apiConfig.redirectRootLogin || staticConfig.redirectRootLogin)
|
||||||
return false
|
var chatDisabled = (apiConfig.chatDisabled || staticConfig.chatDisabled)
|
||||||
}
|
var showWhoToFollowPanel = (apiConfig.showWhoToFollowPanel || staticConfig.showWhoToFollowPanel)
|
||||||
return savedPosition || { x: 0, y: 0 }
|
var whoToFollowProvider = (apiConfig.whoToFollowProvider || staticConfig.whoToFollowProvider)
|
||||||
|
var whoToFollowLink = (apiConfig.whoToFollowLink || staticConfig.whoToFollowLink)
|
||||||
|
var showInstanceSpecificPanel = (apiConfig.showInstanceSpecificPanel || staticConfig.showInstanceSpecificPanel)
|
||||||
|
var scopeOptionsEnabled = (apiConfig.scopeOptionsEnabled || staticConfig.scopeOptionsEnabled)
|
||||||
|
var collapseMessageWithSubject = (apiConfig.collapseMessageWithSubject || staticConfig.collapseMessageWithSubject)
|
||||||
|
|
||||||
|
store.dispatch('setOption', { name: 'theme', value: theme })
|
||||||
|
store.dispatch('setOption', { name: 'background', value: background })
|
||||||
|
store.dispatch('setOption', { name: 'logo', value: logo })
|
||||||
|
store.dispatch('setOption', { name: 'showWhoToFollowPanel', value: showWhoToFollowPanel })
|
||||||
|
store.dispatch('setOption', { name: 'whoToFollowProvider', value: whoToFollowProvider })
|
||||||
|
store.dispatch('setOption', { name: 'whoToFollowLink', value: whoToFollowLink })
|
||||||
|
store.dispatch('setOption', { name: 'showInstanceSpecificPanel', value: showInstanceSpecificPanel })
|
||||||
|
store.dispatch('setOption', { name: 'scopeOptionsEnabled', value: scopeOptionsEnabled })
|
||||||
|
store.dispatch('setOption', { name: 'collapseMessageWithSubject', value: collapseMessageWithSubject })
|
||||||
|
if (chatDisabled) {
|
||||||
|
store.dispatch('disableChat')
|
||||||
}
|
}
|
||||||
})
|
|
||||||
|
|
||||||
/* eslint-disable no-new */
|
const routes = [
|
||||||
new Vue({
|
{ name: 'root',
|
||||||
router,
|
path: '/',
|
||||||
store,
|
redirect: to => {
|
||||||
i18n,
|
return (store.state.users.currentUser ? redirectRootLogin : redirectRootNoLogin) || '/main/all'
|
||||||
el: '#app',
|
}},
|
||||||
render: h => h(App)
|
{ path: '/main/all', component: PublicAndExternalTimeline },
|
||||||
|
{ path: '/main/public', component: PublicTimeline },
|
||||||
|
{ path: '/main/friends', component: FriendsTimeline },
|
||||||
|
{ path: '/tag/:tag', component: TagTimeline },
|
||||||
|
{ 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 },
|
||||||
|
{ name: 'registration', path: '/registration', component: Registration },
|
||||||
|
{ name: 'registration', path: '/registration/:token', component: Registration },
|
||||||
|
{ name: 'friend-requests', path: '/friend-requests', component: FollowRequests },
|
||||||
|
{ name: 'user-settings', path: '/user-settings', component: UserSettings }
|
||||||
|
]
|
||||||
|
|
||||||
|
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,
|
||||||
|
i18n,
|
||||||
|
el: '#app',
|
||||||
|
render: h => h(App)
|
||||||
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
@ -192,3 +209,11 @@ window.fetch('/instance/panel.html')
|
||||||
.then((html) => {
|
.then((html) => {
|
||||||
store.dispatch('setOption', { name: 'instanceSpecificPanelContent', value: html })
|
store.dispatch('setOption', { name: 'instanceSpecificPanelContent', value: html })
|
||||||
})
|
})
|
||||||
|
|
||||||
|
window.fetch('/nodeinfo/2.0.json')
|
||||||
|
.then((res) => res.json())
|
||||||
|
.then((data) => {
|
||||||
|
const suggestions = data.metadata.suggestions
|
||||||
|
store.dispatch('setOption', { name: 'suggestionsEnabled', value: suggestions.enabled })
|
||||||
|
store.dispatch('setOption', { name: 'suggestionsWeb', value: suggestions.web })
|
||||||
|
})
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,8 @@
|
||||||
import { set, delete as del } from 'vue'
|
import { set, delete as del } from 'vue'
|
||||||
import StyleSetter from '../services/style_setter/style_setter.js'
|
import StyleSetter from '../services/style_setter/style_setter.js'
|
||||||
|
|
||||||
|
const browserLocale = (window.navigator.language || 'en').split('-')[0]
|
||||||
|
|
||||||
const defaultState = {
|
const defaultState = {
|
||||||
name: 'Pleroma FE',
|
name: 'Pleroma FE',
|
||||||
colors: {},
|
colors: {},
|
||||||
|
|
@ -15,8 +17,10 @@ const defaultState = {
|
||||||
hoverPreview: true,
|
hoverPreview: true,
|
||||||
pauseOnUnfocused: true,
|
pauseOnUnfocused: true,
|
||||||
stopGifs: false,
|
stopGifs: false,
|
||||||
|
replyVisibility: 'all',
|
||||||
muteWords: [],
|
muteWords: [],
|
||||||
highlight: {}
|
highlight: {},
|
||||||
|
interfaceLanguage: browserLocale
|
||||||
}
|
}
|
||||||
|
|
||||||
const config = {
|
const config = {
|
||||||
|
|
|
||||||
|
|
@ -37,6 +37,7 @@ const CHANGE_PASSWORD_URL = '/api/pleroma/change_password'
|
||||||
const FOLLOW_REQUESTS_URL = '/api/pleroma/friend_requests'
|
const FOLLOW_REQUESTS_URL = '/api/pleroma/friend_requests'
|
||||||
const APPROVE_USER_URL = '/api/pleroma/friendships/approve'
|
const APPROVE_USER_URL = '/api/pleroma/friendships/approve'
|
||||||
const DENY_USER_URL = '/api/pleroma/friendships/deny'
|
const DENY_USER_URL = '/api/pleroma/friendships/deny'
|
||||||
|
const SUGGESTIONS_URL = '/api/v1/suggestions'
|
||||||
|
|
||||||
import { each, map } from 'lodash'
|
import { each, map } from 'lodash'
|
||||||
import 'whatwg-fetch'
|
import 'whatwg-fetch'
|
||||||
|
|
@ -372,7 +373,7 @@ const unretweet = ({ id, credentials }) => {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
const postStatus = ({credentials, status, spoilerText, visibility, mediaIds, inReplyToStatusId}) => {
|
const postStatus = ({credentials, status, spoilerText, visibility, sensitive, mediaIds, inReplyToStatusId}) => {
|
||||||
const idsText = mediaIds.join(',')
|
const idsText = mediaIds.join(',')
|
||||||
const form = new FormData()
|
const form = new FormData()
|
||||||
|
|
||||||
|
|
@ -380,6 +381,7 @@ const postStatus = ({credentials, status, spoilerText, visibility, mediaIds, inR
|
||||||
form.append('source', 'Pleroma FE')
|
form.append('source', 'Pleroma FE')
|
||||||
if (spoilerText) form.append('spoiler_text', spoilerText)
|
if (spoilerText) form.append('spoiler_text', spoilerText)
|
||||||
if (visibility) form.append('visibility', visibility)
|
if (visibility) form.append('visibility', visibility)
|
||||||
|
if (sensitive) form.append('sensitive', sensitive)
|
||||||
form.append('media_ids', idsText)
|
form.append('media_ids', idsText)
|
||||||
if (inReplyToStatusId) {
|
if (inReplyToStatusId) {
|
||||||
form.append('in_reply_to_status_id', inReplyToStatusId)
|
form.append('in_reply_to_status_id', inReplyToStatusId)
|
||||||
|
|
@ -454,6 +456,12 @@ const fetchMutes = ({credentials}) => {
|
||||||
}).then((data) => data.json())
|
}).then((data) => data.json())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const suggestions = ({credentials}) => {
|
||||||
|
return fetch(SUGGESTIONS_URL, {
|
||||||
|
headers: authHeaders(credentials)
|
||||||
|
}).then((data) => data.json())
|
||||||
|
}
|
||||||
|
|
||||||
const apiService = {
|
const apiService = {
|
||||||
verifyCredentials,
|
verifyCredentials,
|
||||||
fetchTimeline,
|
fetchTimeline,
|
||||||
|
|
@ -487,7 +495,8 @@ const apiService = {
|
||||||
changePassword,
|
changePassword,
|
||||||
fetchFollowRequests,
|
fetchFollowRequests,
|
||||||
approveUser,
|
approveUser,
|
||||||
denyUser
|
denyUser,
|
||||||
|
suggestions
|
||||||
}
|
}
|
||||||
|
|
||||||
export default apiService
|
export default apiService
|
||||||
|
|
|
||||||
|
|
@ -25,6 +25,7 @@ const fetchAndUpdate = ({store, credentials, older = false}) => {
|
||||||
.then((notifications) => {
|
.then((notifications) => {
|
||||||
update({store, notifications, older})
|
update({store, notifications, older})
|
||||||
}, () => store.dispatch('setNotificationsError', { value: true }))
|
}, () => store.dispatch('setNotificationsError', { value: true }))
|
||||||
|
.catch(() => store.dispatch('setNotificationsError', { value: true }))
|
||||||
}
|
}
|
||||||
|
|
||||||
const startFetching = ({credentials, store}) => {
|
const startFetching = ({credentials, store}) => {
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,10 @@
|
||||||
import { map } from 'lodash'
|
import { map } from 'lodash'
|
||||||
import apiService from '../api/api.service.js'
|
import apiService from '../api/api.service.js'
|
||||||
|
|
||||||
const postStatus = ({ store, status, spoilerText, visibility, media = [], inReplyToStatusId = undefined }) => {
|
const postStatus = ({ store, status, spoilerText, visibility, sensitive, media = [], inReplyToStatusId = undefined }) => {
|
||||||
const mediaIds = map(media, 'id')
|
const mediaIds = map(media, 'id')
|
||||||
|
|
||||||
return apiService.postStatus({credentials: store.state.users.currentUser.credentials, status, spoilerText, visibility, mediaIds, inReplyToStatusId})
|
return apiService.postStatus({credentials: store.state.users.currentUser.credentials, status, spoilerText, visibility, sensitive, mediaIds, inReplyToStatusId})
|
||||||
.then((data) => data.json())
|
.then((data) => data.json())
|
||||||
.then((data) => {
|
.then((data) => {
|
||||||
if (!data.error) {
|
if (!data.error) {
|
||||||
|
|
|
||||||
|
|
@ -3081,6 +3081,10 @@ isexe@^2.0.0:
|
||||||
version "2.0.0"
|
version "2.0.0"
|
||||||
resolved "https://registry.yarnpkg.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10"
|
resolved "https://registry.yarnpkg.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10"
|
||||||
|
|
||||||
|
iso-639-1@^2.0.3:
|
||||||
|
version "2.0.3"
|
||||||
|
resolved "https://registry.yarnpkg.com/iso-639-1/-/iso-639-1-2.0.3.tgz#72dd3448ac5629c271628c5ac566369428d6ccd0"
|
||||||
|
|
||||||
isobject@^2.0.0:
|
isobject@^2.0.0:
|
||||||
version "2.1.0"
|
version "2.1.0"
|
||||||
resolved "https://registry.yarnpkg.com/isobject/-/isobject-2.1.0.tgz#f065561096a3f1da2ef46272f815c840d87e0c89"
|
resolved "https://registry.yarnpkg.com/isobject/-/isobject-2.1.0.tgz#f065561096a3f1da2ef46272f815c840d87e0c89"
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue