forked from AkkomaGang/akkoma-fe
fix conflict in user card content
This commit is contained in:
commit
650655709d
16 changed files with 432 additions and 188 deletions
|
@ -72,6 +72,7 @@ const afterStoreSetup = ({ store, i18n }) => {
|
|||
var scopeCopy = (config.scopeCopy)
|
||||
var subjectLineBehavior = (config.subjectLineBehavior)
|
||||
var alwaysShowSubjectInput = (config.alwaysShowSubjectInput)
|
||||
var noAttachmentLinks = (config.noAttachmentLinks)
|
||||
|
||||
store.dispatch('setInstanceOption', { name: 'theme', value: theme })
|
||||
store.dispatch('setInstanceOption', { name: 'background', value: background })
|
||||
|
@ -90,6 +91,8 @@ const afterStoreSetup = ({ store, i18n }) => {
|
|||
store.dispatch('setInstanceOption', { name: 'scopeCopy', value: scopeCopy })
|
||||
store.dispatch('setInstanceOption', { name: 'subjectLineBehavior', value: subjectLineBehavior })
|
||||
store.dispatch('setInstanceOption', { name: 'alwaysShowSubjectInput', value: alwaysShowSubjectInput })
|
||||
store.dispatch('setInstanceOption', { name: 'noAttachmentLinks', value: noAttachmentLinks })
|
||||
|
||||
if (chatDisabled) {
|
||||
store.dispatch('disableChat')
|
||||
}
|
||||
|
|
|
@ -32,6 +32,8 @@ const PostStatusForm = {
|
|||
},
|
||||
mounted () {
|
||||
this.resize(this.$refs.textarea)
|
||||
const textLength = this.$refs.textarea.value.length
|
||||
this.$refs.textarea.setSelectionRange(textLength, textLength)
|
||||
|
||||
if (this.replyTo) {
|
||||
this.$refs.textarea.focus()
|
||||
|
@ -250,7 +252,8 @@ const PostStatusForm = {
|
|||
}
|
||||
this.$emit('posted')
|
||||
let el = this.$el.querySelector('textarea')
|
||||
el.style.height = '16px'
|
||||
el.style.height = 'auto'
|
||||
el.style.height = undefined
|
||||
this.error = null
|
||||
} else {
|
||||
this.error = data.error
|
||||
|
@ -298,13 +301,15 @@ const PostStatusForm = {
|
|||
e.dataTransfer.dropEffect = 'copy'
|
||||
},
|
||||
resize (e) {
|
||||
if (!e.target) { return }
|
||||
const vertPadding = Number(window.getComputedStyle(e.target)['padding-top'].substr(0, 1)) +
|
||||
Number(window.getComputedStyle(e.target)['padding-bottom'].substr(0, 1))
|
||||
e.target.style.height = 'auto'
|
||||
e.target.style.height = `${e.target.scrollHeight - vertPadding}px`
|
||||
if (e.target.value === '') {
|
||||
e.target.style.height = '16px'
|
||||
const target = e.target || e
|
||||
if (!(target instanceof window.Element)) { return }
|
||||
const vertPadding = Number(window.getComputedStyle(target)['padding-top'].substr(0, 1)) +
|
||||
Number(window.getComputedStyle(target)['padding-bottom'].substr(0, 1))
|
||||
// Auto is needed to make textbox shrink when removing lines
|
||||
target.style.height = 'auto'
|
||||
target.style.height = `${target.scrollHeight - vertPadding}px`
|
||||
if (target.value === '') {
|
||||
target.style.height = null
|
||||
}
|
||||
},
|
||||
clearError () {
|
||||
|
|
|
@ -2,22 +2,25 @@
|
|||
<div id="heading" class="profile-panel-background" :style="headingStyle">
|
||||
<div class="panel-heading text-center">
|
||||
<div class='user-info'>
|
||||
<router-link :to="{ name: 'user-settings' }" style="float: right; margin-top:16px;" v-if="!isOtherUser">
|
||||
<i class="button-icon icon-cog usersettings" :title="$t('tool_tip.user_settings')"></i>
|
||||
</router-link>
|
||||
<a :href="user.statusnet_profile_url" target="_blank" class="floater" v-if="isOtherUser">
|
||||
<i class="icon-link-ext usersettings"></i>
|
||||
</a>
|
||||
<div class='container'>
|
||||
<router-link :to="userProfileLink(user)">
|
||||
<StillImage class="avatar" :class='{ "better-shadow": betterShadow }' :src="user.profile_image_url_original"/>
|
||||
</router-link>
|
||||
<div class="name-and-screen-name">
|
||||
<div class="top-line">
|
||||
<div :title="user.name" class='user-name' v-if="user.name_html" v-html="user.name_html"></div>
|
||||
<div :title="user.name" class='user-name' v-else>{{user.name}}</div>
|
||||
<router-link :to="{ name: 'user-settings' }" v-if="!isOtherUser">
|
||||
<i class="button-icon icon-cog usersettings" :title="$t('tool_tip.user_settings')"></i>
|
||||
</router-link>
|
||||
<a :href="user.statusnet_profile_url" target="_blank" v-if="isOtherUser">
|
||||
<i class="icon-link-ext usersettings"></i>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<router-link class='user-screen-name' :to="userProfileLink(user)">
|
||||
<span>@{{user.screen_name}}</span><span v-if="user.locked"><i class="icon icon-lock"></i></span>
|
||||
<span v-if="!hideUserStatsLocal" class="dailyAvg">{{dailyAvg}} {{ $t('user_card.per_day') }}</span>
|
||||
<span class="handle">@{{user.screen_name}}</span><span v-if="user.locked"><i class="icon icon-lock"></i></span>
|
||||
<span v-if="!hideUserStatsLocal && !hideBio" class="dailyAvg">{{dailyAvg}} {{ $t('user_card.per_day') }}</span>
|
||||
</router-link>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -25,7 +28,7 @@
|
|||
<div v-if="user.follows_you && loggedIn && isOtherUser" class="following">
|
||||
{{ $t('user_card.follows_you') }}
|
||||
</div>
|
||||
<div class="floater" v-if="isOtherUser && (loggedIn || !switcher)">
|
||||
<div class="highlighter" v-if="isOtherUser && (loggedIn || !switcher)">
|
||||
<!-- id's need to be unique, otherwise vue confuses which user-card checkbox belongs to -->
|
||||
<input class="userHighlightText" type="text" :id="'userHighlightColorTx'+user.id" v-if="userHighlightType !== 'disabled'" v-model="userHighlightColor"/>
|
||||
<input class="userHighlightCl" type="color" :id="'userHighlightColor'+user.id" v-if="userHighlightType !== 'disabled'" v-model="userHighlightColor"/>
|
||||
|
@ -139,7 +142,7 @@
|
|||
border-bottom-right-radius: 0;
|
||||
|
||||
.panel-heading {
|
||||
padding: 0.6em 0em;
|
||||
padding: .6em 0;
|
||||
text-align: center;
|
||||
box-shadow: none;
|
||||
}
|
||||
|
@ -158,10 +161,10 @@
|
|||
.user-info {
|
||||
color: $fallback--lightText;
|
||||
color: var(--lightText, $fallback--lightText);
|
||||
padding: 0 16px;
|
||||
padding: 0 26px;
|
||||
|
||||
.container {
|
||||
padding: 16px 10px 6px 10px;
|
||||
padding: 16px 0 6px;
|
||||
display: flex;
|
||||
max-height: 56px;
|
||||
|
||||
|
@ -218,11 +221,15 @@
|
|||
vertical-align: middle;
|
||||
object-fit: contain
|
||||
}
|
||||
.top-line {
|
||||
display: flex;
|
||||
}
|
||||
}
|
||||
|
||||
.user-name{
|
||||
text-overflow: ellipsis;
|
||||
overflow: hidden;
|
||||
flex: 1 0 auto;
|
||||
}
|
||||
|
||||
.user-screen-name {
|
||||
|
@ -232,27 +239,73 @@
|
|||
font-weight: light;
|
||||
font-size: 15px;
|
||||
padding-right: 0.1em;
|
||||
width: 100%;
|
||||
display: flex;
|
||||
|
||||
.dailyAvg {
|
||||
min-width: 1px;
|
||||
flex: 0 0 auto;
|
||||
}
|
||||
|
||||
.handle {
|
||||
min-width: 1px;
|
||||
flex: 0 1 auto;
|
||||
text-overflow: ellipsis;
|
||||
overflow: hidden;
|
||||
}
|
||||
}
|
||||
|
||||
.user-meta {
|
||||
margin-bottom: .4em;
|
||||
margin-bottom: .15em;
|
||||
display: flex;
|
||||
align-items: baseline;
|
||||
font-size: 14px;
|
||||
line-height: 22px;
|
||||
flex-wrap: wrap;
|
||||
|
||||
.following {
|
||||
font-size: 14px;
|
||||
flex: 0 0 100%;
|
||||
flex: 1 0 auto;
|
||||
margin: 0;
|
||||
padding-left: 16px;
|
||||
margin-bottom: .25em;
|
||||
text-align: left;
|
||||
float: left;
|
||||
}
|
||||
.floater {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
&::after {
|
||||
display: block;
|
||||
content: '';
|
||||
clear: both;
|
||||
.highlighter {
|
||||
flex: 0 1 auto;
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
margin-right: -.5em;
|
||||
align-self: start;
|
||||
|
||||
.userHighlightCl {
|
||||
padding: 2px 10px;
|
||||
flex: 1 0 auto;
|
||||
}
|
||||
|
||||
.userHighlightSel,
|
||||
.userHighlightSel.select {
|
||||
padding-top: 0;
|
||||
padding-bottom: 0;
|
||||
flex: 1 0 auto;
|
||||
}
|
||||
.userHighlightSel.select i {
|
||||
line-height: 22px;
|
||||
}
|
||||
|
||||
.userHighlightText {
|
||||
width: 70px;
|
||||
flex: 1 0 auto;
|
||||
}
|
||||
|
||||
.userHighlightCl,
|
||||
.userHighlightText,
|
||||
.userHighlightSel,
|
||||
.userHighlightSel.select {
|
||||
height: 22px;
|
||||
vertical-align: top;
|
||||
margin-right: .5em;
|
||||
margin-bottom: .25em;
|
||||
}
|
||||
}
|
||||
}
|
||||
.user-interactions {
|
||||
|
@ -260,8 +313,13 @@
|
|||
flex-flow: row wrap;
|
||||
justify-content: space-between;
|
||||
|
||||
margin-right: -.75em;
|
||||
|
||||
div {
|
||||
flex: 1;
|
||||
flex: 1 0 0;
|
||||
margin-right: .75em;
|
||||
margin-bottom: .6em;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.mute {
|
||||
|
@ -280,8 +338,9 @@
|
|||
}
|
||||
|
||||
button {
|
||||
width: 92%;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.remote-button {
|
||||
|
@ -304,10 +363,11 @@
|
|||
justify-content: space-between;
|
||||
color: $fallback--lightText;
|
||||
color: var(--lightText, $fallback--lightText);
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.user-count {
|
||||
flex: 1;
|
||||
flex: 1 0 auto;
|
||||
padding: .5em 0 .5em 0;
|
||||
margin: 0 .5em;
|
||||
|
||||
|
@ -327,32 +387,5 @@
|
|||
color: #CCC;
|
||||
}
|
||||
.floater {
|
||||
float: right;
|
||||
margin-top: 16px;
|
||||
|
||||
.userHighlightCl {
|
||||
padding: 2px 10px;
|
||||
}
|
||||
.userHighlightSel,
|
||||
.userHighlightSel.select {
|
||||
padding-top: 0;
|
||||
padding-bottom: 0;
|
||||
}
|
||||
.userHighlightSel.select i {
|
||||
line-height: 22px;
|
||||
}
|
||||
|
||||
.userHighlightText {
|
||||
width: 70px;
|
||||
}
|
||||
|
||||
.userHighlightCl,
|
||||
.userHighlightText,
|
||||
.userHighlightSel,
|
||||
.userHighlightSel.select {
|
||||
height: 22px;
|
||||
vertical-align: top;
|
||||
margin-right: 0
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -2,62 +2,31 @@ import apiService from '../../services/api/api.service.js'
|
|||
import generateProfileLink from 'src/services/user_profile_link_generator/user_profile_link_generator'
|
||||
|
||||
function showWhoToFollow (panel, reply) {
|
||||
var users = reply
|
||||
var cn
|
||||
var index
|
||||
var step = 7
|
||||
cn = Math.floor(Math.random() * step)
|
||||
for (index = 0; index < 3; index++) {
|
||||
var user
|
||||
user = users[cn]
|
||||
var img
|
||||
if (user.avatar) {
|
||||
img = user.avatar
|
||||
} else {
|
||||
img = '/images/avi.png'
|
||||
}
|
||||
var name = user.acct
|
||||
if (index === 0) {
|
||||
panel.img1 = img
|
||||
panel.name1 = name
|
||||
panel.usersToFollow.forEach((toFollow, index) => {
|
||||
let randIndex = Math.floor(Math.random() * reply.length)
|
||||
let user = reply[randIndex]
|
||||
let img = user.avatar || '/images/avi.png'
|
||||
let name = user.acct
|
||||
|
||||
toFollow.img = img
|
||||
toFollow.name = name
|
||||
|
||||
panel.$store.state.api.backendInteractor.externalProfile(name)
|
||||
.then((externalUser) => {
|
||||
if (!externalUser.error) {
|
||||
panel.$store.commit('addNewUsers', [externalUser])
|
||||
panel.id1 = externalUser.id
|
||||
toFollow.id = externalUser.id
|
||||
}
|
||||
})
|
||||
} else if (index === 1) {
|
||||
panel.img2 = img
|
||||
panel.name2 = name
|
||||
panel.$store.state.api.backendInteractor.externalProfile(name)
|
||||
.then((externalUser) => {
|
||||
if (!externalUser.error) {
|
||||
panel.$store.commit('addNewUsers', [externalUser])
|
||||
panel.id2 = externalUser.id
|
||||
}
|
||||
})
|
||||
} else if (index === 2) {
|
||||
panel.img3 = img
|
||||
panel.name3 = name
|
||||
panel.$store.state.api.backendInteractor.externalProfile(name)
|
||||
.then((externalUser) => {
|
||||
if (!externalUser.error) {
|
||||
panel.$store.commit('addNewUsers', [externalUser])
|
||||
panel.id3 = externalUser.id
|
||||
}
|
||||
})
|
||||
}
|
||||
cn = (cn + step) % users.length
|
||||
}
|
||||
}
|
||||
|
||||
function getWhoToFollow (panel) {
|
||||
var credentials = panel.$store.state.users.currentUser.credentials
|
||||
if (credentials) {
|
||||
panel.name1 = 'Loading...'
|
||||
panel.name2 = 'Loading...'
|
||||
panel.name3 = 'Loading...'
|
||||
panel.usersToFollow.forEach(toFollow => {
|
||||
toFollow.name = 'Loading...'
|
||||
})
|
||||
apiService.suggestions({credentials: credentials})
|
||||
.then((reply) => {
|
||||
showWhoToFollow(panel, reply)
|
||||
|
@ -67,27 +36,24 @@ function getWhoToFollow (panel) {
|
|||
|
||||
const WhoToFollowPanel = {
|
||||
data: () => ({
|
||||
img1: '/images/avi.png',
|
||||
name1: '',
|
||||
id1: 0,
|
||||
img2: '/images/avi.png',
|
||||
name2: '',
|
||||
id2: 0,
|
||||
img3: '/images/avi.png',
|
||||
name3: '',
|
||||
id3: 0
|
||||
usersToFollow: new Array(3).fill().map(x => (
|
||||
{
|
||||
img: '/images/avi.png',
|
||||
name: '',
|
||||
id: 0
|
||||
}
|
||||
))
|
||||
}),
|
||||
computed: {
|
||||
user: function () {
|
||||
return this.$store.state.users.currentUser.screen_name
|
||||
},
|
||||
moreUrl: function () {
|
||||
var host = window.location.hostname
|
||||
var user = this.user
|
||||
var suggestionsWeb = this.$store.state.instance.suggestionsWeb
|
||||
var url
|
||||
url = suggestionsWeb.replace(/{{host}}/g, encodeURIComponent(host))
|
||||
url = url.replace(/{{user}}/g, encodeURIComponent(user))
|
||||
const host = window.location.hostname
|
||||
const user = this.user
|
||||
const suggestionsWeb = this.$store.state.instance.suggestionsWeb
|
||||
const url = suggestionsWeb.replace(/{{host}}/g, encodeURIComponent(host))
|
||||
.replace(/{{user}}/g, encodeURIComponent(user))
|
||||
return url
|
||||
},
|
||||
suggestionsEnabled () {
|
||||
|
|
|
@ -8,9 +8,12 @@
|
|||
</div>
|
||||
<div class="panel-body who-to-follow">
|
||||
<p>
|
||||
<img v-bind:src="img1"/> <router-link :to="userProfileLink(id1, name1)">{{ name1 }}</router-link><br>
|
||||
<img v-bind:src="img2"/> <router-link :to="userProfileLink(id2, name2)">{{ name2 }}</router-link><br>
|
||||
<img v-bind:src="img3"/> <router-link :to="userProfileLink(id3, name3)">{{ name3 }}</router-link><br>
|
||||
<span v-for="user in usersToFollow">
|
||||
<img v-bind:src="user.img" />
|
||||
<router-link v-bind:to="userProfileLink(user.id, user.name)">
|
||||
{{user.name}}
|
||||
</router-link><br />
|
||||
</span>
|
||||
<img v-bind:src="$store.state.instance.logo"> <a v-bind:href="moreUrl" target="_blank">{{$t('who_to_follow.more')}}</a>
|
||||
</p>
|
||||
</div>
|
||||
|
|
171
src/i18n/ja.json
171
src/i18n/ja.json
|
@ -29,13 +29,16 @@
|
|||
"username": "ユーザーめい"
|
||||
},
|
||||
"nav": {
|
||||
"back": "もどる",
|
||||
"chat": "ローカルチャット",
|
||||
"friend_requests": "フォローリクエスト",
|
||||
"mentions": "メンション",
|
||||
"dms": "ダイレクトメッセージ",
|
||||
"public_tl": "パブリックタイムライン",
|
||||
"timeline": "タイムライン",
|
||||
"twkn": "つながっているすべてのネットワーク"
|
||||
"twkn": "つながっているすべてのネットワーク",
|
||||
"user_search": "ユーザーをさがす",
|
||||
"preferences": "せってい"
|
||||
},
|
||||
"notifications": {
|
||||
"broken_favorite": "ステータスがみつかりません。さがしています...",
|
||||
|
@ -70,7 +73,17 @@
|
|||
"fullname": "スクリーンネーム",
|
||||
"password_confirm": "パスワードのかくにん",
|
||||
"registration": "はじめる",
|
||||
"token": "しょうたいトークン"
|
||||
"token": "しょうたいトークン",
|
||||
"captcha": "CAPTCHA",
|
||||
"new_captcha": "もじがよめないときは、がぞうをクリックすると、あたらしいがぞうになります",
|
||||
"validations": {
|
||||
"username_required": "なにかかいてください",
|
||||
"fullname_required": "なにかかいてください",
|
||||
"email_required": "なにかかいてください",
|
||||
"password_required": "なにかかいてください",
|
||||
"password_confirmation_required": "なにかかいてください",
|
||||
"password_confirmation_match": "パスワードがちがいます"
|
||||
}
|
||||
},
|
||||
"settings": {
|
||||
"attachmentRadius": "ファイル",
|
||||
|
@ -90,6 +103,7 @@
|
|||
"change_password_error": "パスワードをかえることが、できなかったかもしれません。",
|
||||
"changed_password": "パスワードが、かわりました!",
|
||||
"collapse_subject": "せつめいのあるとうこうをたたむ",
|
||||
"composing": "とうこう",
|
||||
"confirm_new_password": "あたらしいパスワードのかくにん",
|
||||
"current_avatar": "いまのアバター",
|
||||
"current_password": "いまのパスワード",
|
||||
|
@ -113,17 +127,22 @@
|
|||
"general": "ぜんぱん",
|
||||
"hide_attachments_in_convo": "スレッドのファイルをかくす",
|
||||
"hide_attachments_in_tl": "タイムラインのファイルをかくす",
|
||||
"hide_isp": "インスタンススペシフィックパネルをかくす",
|
||||
"preload_images": "がぞうをさきよみする",
|
||||
"hide_post_stats": "とうこうのとうけいをかくす (れい: おきにいりのかず)",
|
||||
"hide_user_stats": "ユーザーのとうけいをかくす (れい: フォロワーのかず)",
|
||||
"import_followers_from_a_csv_file": "CSVファイルからフォローをインポートする",
|
||||
"import_theme": "ロード",
|
||||
"inputRadius": "インプットフィールド",
|
||||
"checkboxRadius": "チェックボックス",
|
||||
"instance_default": "(デフォルト: {value})",
|
||||
"instance_default_simple": "(デフォルト)",
|
||||
"interface": "インターフェース",
|
||||
"interfaceLanguage": "インターフェースのことば",
|
||||
"invalid_theme_imported": "このファイルはPleromaのテーマではありません。テーマはへんこうされませんでした。",
|
||||
"limited_availability": "あなたのブラウザではできません",
|
||||
"links": "リンク",
|
||||
"lock_account_description": "あなたがみとめたひとだけ、あなたのアカウントをフォローできます",
|
||||
"lock_account_description": "あなたがみとめたひとだけ、あなたのアカウントをフォローできる",
|
||||
"loop_video": "ビデオをくりかえす",
|
||||
"loop_video_silent_only": "おとのないビデオだけくりかえす",
|
||||
"name": "なまえ",
|
||||
|
@ -135,6 +154,7 @@
|
|||
"notification_visibility_mentions": "メンション",
|
||||
"notification_visibility_repeats": "リピート",
|
||||
"no_rich_text_description": "リッチテキストをつかわない",
|
||||
"hide_network_description": "わたしがフォローしているひとと、わたしをフォローしているひとを、みせない",
|
||||
"nsfw_clickthrough": "NSFWなファイルをかくす",
|
||||
"panelRadius": "パネル",
|
||||
"pause_on_unfocused": "タブにフォーカスがないときストリーミングをとめる",
|
||||
|
@ -151,20 +171,139 @@
|
|||
"saving_err": "せっていをセーブできませんでした",
|
||||
"saving_ok": "せっていをセーブしました",
|
||||
"security_tab": "セキュリティ",
|
||||
"scope_copy": "リプライするとき、こうかいはんいをコピーする (DMのこうかいはんいは、つねにコピーされます)",
|
||||
"set_new_avatar": "あたらしいアバターをせっていする",
|
||||
"set_new_profile_background": "あたらしいプロフィールのバックグラウンドをせっていする",
|
||||
"set_new_profile_banner": "あたらしいプロフィールバナーを設定する",
|
||||
"settings": "せってい",
|
||||
"subject_input_always_show": "サブジェクトフィールドをいつでもひょうじする",
|
||||
"subject_line_behavior": "リプライするときサブジェクトをコピーする",
|
||||
"subject_line_email": "メールふう: \"re: サブジェクト\"",
|
||||
"subject_line_mastodon": "マストドンふう: そのままコピー",
|
||||
"subject_line_noop": "コピーしない",
|
||||
"stop_gifs": "カーソルをかさねたとき、GIFをうごかす",
|
||||
"streaming": "うえまでスクロールしたとき、じどうてきにストリーミングする",
|
||||
"text": "もじ",
|
||||
"theme": "テーマ",
|
||||
"theme_help": "カラーテーマをカスタマイズできます",
|
||||
"theme_help_v2_1": "チェックボックスをONにすると、コンポーネントごとに、いろと、とうめいどを、オーバーライドできます。「すべてクリア」ボタンをおすと、すべてのオーバーライドを、やめます。",
|
||||
"theme_help_v2_2": "バックグラウンドとテキストのコントラストをあらわすアイコンがあります。マウスをホバーすると、くわしいせつめいがでます。とうめいないろをつかっているときは、もっともわるいばあいのコントラストがしめされます。",
|
||||
"tooltipRadius": "ツールチップとアラート",
|
||||
"user_settings": "ユーザーせってい",
|
||||
"values": {
|
||||
"false": "いいえ",
|
||||
"true": "はい"
|
||||
},
|
||||
"notifications": "つうち",
|
||||
"enable_web_push_notifications": "ウェブプッシュつうちをゆるす",
|
||||
"style": {
|
||||
"switcher": {
|
||||
"keep_color": "いろをのこす",
|
||||
"keep_shadows": "かげをのこす",
|
||||
"keep_opacity": "とうめいどをのこす",
|
||||
"keep_roundness": "まるさをのこす",
|
||||
"keep_fonts": "フォントをのこす",
|
||||
"save_load_hint": "「のこす」オプションをONにすると、テーマをえらんだときとロードしたとき、いまのせっていをのこします。また、テーマをエクスポートするとき、これらのオプションをストアします。すべてのチェックボックスをOFFにすると、テーマをエクスポートしたとき、すべてのせっていをセーブします。",
|
||||
"reset": "リセット",
|
||||
"clear_all": "すべてクリア",
|
||||
"clear_opacity": "とうめいどをクリア"
|
||||
},
|
||||
"common": {
|
||||
"color": "いろ",
|
||||
"opacity": "とうめいど",
|
||||
"contrast": {
|
||||
"hint": "コントラストは {ratio} です。{level}。({context})",
|
||||
"level": {
|
||||
"aa": "AAレベルガイドライン (ミニマル) をみたします",
|
||||
"aaa": "AAAレベルガイドライン (レコメンデッド) をみたします。",
|
||||
"bad": "ガイドラインをみたしません。"
|
||||
},
|
||||
"context": {
|
||||
"18pt": "おおきい (18ポイントいじょう) テキスト",
|
||||
"text": "テキスト"
|
||||
}
|
||||
}
|
||||
},
|
||||
"common_colors": {
|
||||
"_tab_label": "きょうつう",
|
||||
"main": "きょうつうのいろ",
|
||||
"foreground_hint": "「くわしく」タブで、もっとこまかくせっていできます",
|
||||
"rgbo": "アイコンとアクセントとバッジ"
|
||||
},
|
||||
"advanced_colors": {
|
||||
"_tab_label": "くわしく",
|
||||
"alert": "アラートのバックグラウンド",
|
||||
"alert_error": "エラー",
|
||||
"badge": "バッジのバックグラウンド",
|
||||
"badge_notification": "つうち",
|
||||
"panel_header": "パネルヘッダー",
|
||||
"top_bar": "トップバー",
|
||||
"borders": "さかいめ",
|
||||
"buttons": "ボタン",
|
||||
"inputs": "インプットフィールド",
|
||||
"faint_text": "うすいテキスト"
|
||||
},
|
||||
"radii": {
|
||||
"_tab_label": "まるさ"
|
||||
},
|
||||
"shadows": {
|
||||
"_tab_label": "ひかりとかげ",
|
||||
"component": "コンポーネント",
|
||||
"override": "オーバーライド",
|
||||
"shadow_id": "かげ #{value}",
|
||||
"blur": "ぼかし",
|
||||
"spread": "ひろがり",
|
||||
"inset": "うちがわ",
|
||||
"hint": "かげのせっていでは、いろのあたいとして --variable をつかうことができます。これはCSS3へんすうです。ただし、とうめいどのせっていは、きかなくなります。",
|
||||
"filter_hint": {
|
||||
"always_drop_shadow": "ブラウザーがサポートしていれば、つねに {0} がつかわれます。",
|
||||
"drop_shadow_syntax": "{0} は、{1} パラメーターと {2} キーワードをサポートしていません。",
|
||||
"avatar_inset": "うちがわのかげと、そとがわのかげを、いっしょにつかうと、とうめいなアバターが、へんなみためになります。",
|
||||
"spread_zero": "ひろがりが 0 よりもおおきなかげは、0 とおなじです。",
|
||||
"inset_classic": "うちがわのかげは {0} をつかいます。"
|
||||
},
|
||||
"components": {
|
||||
"panel": "パネル",
|
||||
"panelHeader": "パネルヘッダー",
|
||||
"topBar": "トップバー",
|
||||
"avatar": "ユーザーアバター (プロフィール)",
|
||||
"avatarStatus": "ユーザーアバター (とうこう)",
|
||||
"popup": "ポップアップとツールチップ",
|
||||
"button": "ボタン",
|
||||
"buttonHover": "ボタン (ホバー)",
|
||||
"buttonPressed": "ボタン (おされているとき)",
|
||||
"buttonPressedHover": "ボタン (ホバー、かつ、おされているとき)",
|
||||
"input": "インプットフィールド"
|
||||
}
|
||||
},
|
||||
"fonts": {
|
||||
"_tab_label": "フォント",
|
||||
"help": "「カスタム」をえらんだときは、システムにあるフォントのなまえを、ただしくにゅうりょくしてください。",
|
||||
"components": {
|
||||
"interface": "インターフェース",
|
||||
"input": "インプットフィールド",
|
||||
"post": "とうこう",
|
||||
"postCode": "モノスペース (とうこうがリッチテキストであるとき)"
|
||||
},
|
||||
"family": "フォントめい",
|
||||
"size": "おおきさ (px)",
|
||||
"weight": "ふとさ",
|
||||
"custom": "カスタム"
|
||||
},
|
||||
"preview": {
|
||||
"header": "プレビュー",
|
||||
"content": "ほんぶん",
|
||||
"error": "エラーのれい",
|
||||
"button": "ボタン",
|
||||
"text": "これは{0}と{1}のれいです。",
|
||||
"mono": "monospace",
|
||||
"input": "はねだくうこうに、つきました。",
|
||||
"faint_link": "とてもたすけになるマニュアル",
|
||||
"fine_print": "わたしたちの{0}を、よまないでください!",
|
||||
"header_faint": "エラーではありません",
|
||||
"checkbox": "りようきやくを、よみました",
|
||||
"link": "ハイパーリンク"
|
||||
}
|
||||
}
|
||||
},
|
||||
"timeline": {
|
||||
|
@ -183,10 +322,15 @@
|
|||
"blocked": "ブロックしています!",
|
||||
"deny": "おことわり",
|
||||
"follow": "フォロー",
|
||||
"follow_sent": "リクエストを、おくりました!",
|
||||
"follow_progress": "リクエストしています…",
|
||||
"follow_again": "ふたたびリクエストをおくりますか?",
|
||||
"follow_unfollow": "フォローをやめる",
|
||||
"followees": "フォロー",
|
||||
"followers": "フォロワー",
|
||||
"following": "フォローしています!",
|
||||
"follows_you": "フォローされました!",
|
||||
"its_you": "これはあなたです!",
|
||||
"mute": "ミュート",
|
||||
"muted": "ミュートしています!",
|
||||
"per_day": "/日",
|
||||
|
@ -199,5 +343,26 @@
|
|||
"who_to_follow": {
|
||||
"more": "くわしく",
|
||||
"who_to_follow": "おすすめユーザー"
|
||||
},
|
||||
"tool_tip": {
|
||||
"media_upload": "メディアをアップロード",
|
||||
"repeat": "リピート",
|
||||
"reply": "リプライ",
|
||||
"favorite": "おきにいり",
|
||||
"user_settings": "ユーザーせってい"
|
||||
},
|
||||
"upload":{
|
||||
"error": {
|
||||
"base": "アップロードにしっぱいしました。",
|
||||
"file_too_big": "ファイルがおおきすぎます [{filesize} {filesizeunit} / {allowedsize} {allowedsizeunit}]",
|
||||
"default": "しばらくしてから、ためしてください"
|
||||
},
|
||||
"file_size_units": {
|
||||
"B": "B",
|
||||
"KiB": "KiB",
|
||||
"MiB": "MiB",
|
||||
"GiB": "GiB",
|
||||
"TiB": "TiB"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
22
src/lib/push_notifications_plugin.js
Normal file
22
src/lib/push_notifications_plugin.js
Normal file
|
@ -0,0 +1,22 @@
|
|||
export default (store) => {
|
||||
store.subscribe((mutation, state) => {
|
||||
const vapidPublicKey = state.instance.vapidPublicKey
|
||||
const webPushNotification = state.config.webPushNotifications
|
||||
const permission = state.interface.notificationPermission === 'granted'
|
||||
const user = state.users.currentUser
|
||||
|
||||
const isUserMutation = mutation.type === 'setCurrentUser'
|
||||
const isVapidMutation = mutation.type === 'setInstanceOption' && mutation.payload.name === 'vapidPublicKey'
|
||||
const isPermMutation = mutation.type === 'setNotificationPermission' && mutation.payload === 'granted'
|
||||
const isUserConfigMutation = mutation.type === 'setOption' && mutation.payload.name === 'webPushNotifications'
|
||||
const isVisibilityMutation = mutation.type === 'setOption' && mutation.payload.name === 'notificationVisibility'
|
||||
|
||||
if (isUserMutation || isVapidMutation || isPermMutation || isUserConfigMutation || isVisibilityMutation) {
|
||||
if (user && vapidPublicKey && permission && webPushNotification) {
|
||||
return store.dispatch('registerPushNotifications')
|
||||
} else if (isUserConfigMutation && !webPushNotification) {
|
||||
return store.dispatch('unregisterPushNotifications')
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
28
src/main.js
28
src/main.js
|
@ -15,6 +15,7 @@ import VueTimeago from 'vue-timeago'
|
|||
import VueI18n from 'vue-i18n'
|
||||
|
||||
import createPersistedState from './lib/persisted_state.js'
|
||||
import pushNotifications from './lib/push_notifications_plugin.js'
|
||||
|
||||
import messages from './i18n/messages.js'
|
||||
|
||||
|
@ -51,31 +52,6 @@ const persistedStateOptions = {
|
|||
]
|
||||
}
|
||||
|
||||
const registerPushNotifications = store => {
|
||||
store.subscribe((mutation, state) => {
|
||||
const vapidPublicKey = state.instance.vapidPublicKey
|
||||
const permission = state.interface.notificationPermission === 'granted'
|
||||
const isUserMutation = mutation.type === 'setCurrentUser'
|
||||
|
||||
if (isUserMutation && vapidPublicKey && permission) {
|
||||
return store.dispatch('registerPushNotifications')
|
||||
}
|
||||
|
||||
const user = state.users.currentUser
|
||||
const isVapidMutation = mutation.type === 'setInstanceOption' && mutation.payload.name === 'vapidPublicKey'
|
||||
|
||||
if (isVapidMutation && user && permission) {
|
||||
return store.dispatch('registerPushNotifications')
|
||||
}
|
||||
|
||||
const isPermMutation = mutation.type === 'setNotificationPermission' && mutation.payload === 'granted'
|
||||
|
||||
if (isPermMutation && user && vapidPublicKey) {
|
||||
return store.dispatch('registerPushNotifications')
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
createPersistedState(persistedStateOptions).then((persistedState) => {
|
||||
const store = new Vuex.Store({
|
||||
modules: {
|
||||
|
@ -88,7 +64,7 @@ createPersistedState(persistedStateOptions).then((persistedState) => {
|
|||
chat: chatModule,
|
||||
oauth: oauthModule
|
||||
},
|
||||
plugins: [persistedState, registerPushNotifications],
|
||||
plugins: [persistedState, pushNotifications],
|
||||
strict: false // Socket modifies itself, let's ignore this for now.
|
||||
// strict: process.env.NODE_ENV !== 'production'
|
||||
})
|
||||
|
|
|
@ -24,7 +24,7 @@ const defaultState = {
|
|||
likes: true,
|
||||
repeats: true
|
||||
},
|
||||
webPushNotifications: true,
|
||||
webPushNotifications: false,
|
||||
muteWords: [],
|
||||
highlight: {},
|
||||
interfaceLanguage: browserLocale,
|
||||
|
|
|
@ -27,6 +27,7 @@ const defaultState = {
|
|||
loginMethod: 'password',
|
||||
nsfwCensorImage: undefined,
|
||||
vapidPublicKey: undefined,
|
||||
noAttachmentLinks: false,
|
||||
|
||||
// Nasty stuff
|
||||
pleromaBackend: true,
|
||||
|
|
|
@ -27,6 +27,7 @@ export const defaultState = {
|
|||
maxId: 0,
|
||||
minId: Number.POSITIVE_INFINITY,
|
||||
data: [],
|
||||
idStore: {},
|
||||
error: false
|
||||
},
|
||||
favorites: new Set(),
|
||||
|
@ -307,6 +308,7 @@ const addNewNotifications = (state, { dispatch, notifications, older, visibleNot
|
|||
}
|
||||
|
||||
state.notifications.data.push(result)
|
||||
state.notifications.idStore[notification.id] = result
|
||||
|
||||
if ('Notification' in window && window.Notification.permission === 'granted') {
|
||||
const title = action.user.name
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import backendInteractorService from '../services/backend_interactor_service/backend_interactor_service.js'
|
||||
import { compact, map, each, merge } from 'lodash'
|
||||
import { set } from 'vue'
|
||||
import registerPushNotifications from '../services/push/push.js'
|
||||
import { registerPushNotifications, unregisterPushNotifications } from '../services/push/push.js'
|
||||
import oauthApi from '../services/new_api/oauth'
|
||||
import { humanizeErrors } from './errors'
|
||||
|
||||
|
@ -66,6 +66,9 @@ export const mutations = {
|
|||
setUserForStatus (state, status) {
|
||||
status.user = state.usersObject[status.user.id]
|
||||
},
|
||||
setUserForNotification (state, notification) {
|
||||
notification.action.user = state.usersObject[notification.action.user.id]
|
||||
},
|
||||
setColor (state, { user: { id }, highlighted }) {
|
||||
const user = state.usersObject[id]
|
||||
set(user, 'highlight', highlighted)
|
||||
|
@ -113,8 +116,14 @@ const users = {
|
|||
const token = store.state.currentUser.credentials
|
||||
const vapidPublicKey = store.rootState.instance.vapidPublicKey
|
||||
const isEnabled = store.rootState.config.webPushNotifications
|
||||
const notificationVisibility = store.rootState.config.notificationVisibility
|
||||
|
||||
registerPushNotifications(isEnabled, vapidPublicKey, token)
|
||||
registerPushNotifications(isEnabled, vapidPublicKey, token, notificationVisibility)
|
||||
},
|
||||
unregisterPushNotifications (store) {
|
||||
const token = store.state.currentUser.credentials
|
||||
|
||||
unregisterPushNotifications(token)
|
||||
},
|
||||
addNewStatuses (store, { statuses }) {
|
||||
const users = map(statuses, 'user')
|
||||
|
@ -131,6 +140,21 @@ const users = {
|
|||
store.commit('setUserForStatus', status)
|
||||
})
|
||||
},
|
||||
addNewNotifications (store, { notifications }) {
|
||||
const users = compact(map(notifications, 'from_profile'))
|
||||
const notificationIds = compact(notifications.map(_ => String(_.id)))
|
||||
store.commit('addNewUsers', users)
|
||||
|
||||
const notificationsObject = store.rootState.statuses.notifications.idStore
|
||||
const relevantNotifications = Object.entries(notificationsObject)
|
||||
.filter(([k, val]) => notificationIds.includes(k))
|
||||
.map(([k, val]) => val)
|
||||
|
||||
// Reconnect users to notifications
|
||||
each(relevantNotifications, (notification) => {
|
||||
store.commit('setUserForNotification', notification)
|
||||
})
|
||||
},
|
||||
async signUp (store, userInfo) {
|
||||
store.commit('signUpPending')
|
||||
|
||||
|
|
|
@ -370,12 +370,13 @@ const unretweet = ({ id, credentials }) => {
|
|||
})
|
||||
}
|
||||
|
||||
const postStatus = ({credentials, status, spoilerText, visibility, sensitive, mediaIds, inReplyToStatusId, contentType}) => {
|
||||
const postStatus = ({credentials, status, spoilerText, visibility, sensitive, mediaIds, inReplyToStatusId, contentType, noAttachmentLinks}) => {
|
||||
const idsText = mediaIds.join(',')
|
||||
const form = new FormData()
|
||||
|
||||
form.append('status', status)
|
||||
form.append('source', 'Pleroma FE')
|
||||
if (noAttachmentLinks) form.append('no_attachment_links', noAttachmentLinks)
|
||||
if (spoilerText) form.append('spoiler_text', spoilerText)
|
||||
if (visibility) form.append('visibility', visibility)
|
||||
if (sensitive) form.append('sensitive', sensitive)
|
||||
|
|
|
@ -14,12 +14,12 @@ function isPushSupported () {
|
|||
return 'serviceWorker' in navigator && 'PushManager' in window
|
||||
}
|
||||
|
||||
function registerServiceWorker () {
|
||||
function getOrCreateServiceWorker () {
|
||||
return runtime.register()
|
||||
.catch((err) => console.error('Unable to register service worker.', err))
|
||||
.catch((err) => console.error('Unable to get or create a service worker.', err))
|
||||
}
|
||||
|
||||
function subscribe (registration, isEnabled, vapidPublicKey) {
|
||||
function subscribePush (registration, isEnabled, vapidPublicKey) {
|
||||
if (!isEnabled) return Promise.reject(new Error('Web Push is disabled in config'))
|
||||
if (!vapidPublicKey) return Promise.reject(new Error('VAPID public key is not found'))
|
||||
|
||||
|
@ -30,7 +30,28 @@ function subscribe (registration, isEnabled, vapidPublicKey) {
|
|||
return registration.pushManager.subscribe(subscribeOptions)
|
||||
}
|
||||
|
||||
function sendSubscriptionToBackEnd (subscription, token) {
|
||||
function unsubscribePush (registration) {
|
||||
return registration.pushManager.getSubscription()
|
||||
.then((subscribtion) => {
|
||||
if (subscribtion === null) { return }
|
||||
return subscribtion.unsubscribe()
|
||||
})
|
||||
}
|
||||
|
||||
function deleteSubscriptionFromBackEnd (token) {
|
||||
return window.fetch('/api/v1/push/subscription/', {
|
||||
method: 'DELETE',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'Authorization': `Bearer ${token}`
|
||||
}
|
||||
}).then((response) => {
|
||||
if (!response.ok) throw new Error('Bad status code from server.')
|
||||
return response
|
||||
})
|
||||
}
|
||||
|
||||
function sendSubscriptionToBackEnd (subscription, token, notificationVisibility) {
|
||||
return window.fetch('/api/v1/push/subscription/', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
|
@ -41,29 +62,49 @@ function sendSubscriptionToBackEnd (subscription, token) {
|
|||
subscription,
|
||||
data: {
|
||||
alerts: {
|
||||
follow: true,
|
||||
favourite: true,
|
||||
mention: true,
|
||||
reblog: true
|
||||
follow: notificationVisibility.follows,
|
||||
favourite: notificationVisibility.likes,
|
||||
mention: notificationVisibility.mentions,
|
||||
reblog: notificationVisibility.repeats
|
||||
}
|
||||
}
|
||||
})
|
||||
})
|
||||
.then((response) => {
|
||||
}).then((response) => {
|
||||
if (!response.ok) throw new Error('Bad status code from server.')
|
||||
return response.json()
|
||||
})
|
||||
.then((responseData) => {
|
||||
}).then((responseData) => {
|
||||
if (!responseData.id) throw new Error('Bad response from server.')
|
||||
return responseData
|
||||
})
|
||||
}
|
||||
|
||||
export default function registerPushNotifications (isEnabled, vapidPublicKey, token) {
|
||||
export function registerPushNotifications (isEnabled, vapidPublicKey, token, notificationVisibility) {
|
||||
if (isPushSupported()) {
|
||||
registerServiceWorker()
|
||||
.then((registration) => subscribe(registration, isEnabled, vapidPublicKey))
|
||||
.then((subscription) => sendSubscriptionToBackEnd(subscription, token))
|
||||
getOrCreateServiceWorker()
|
||||
.then((registration) => subscribePush(registration, isEnabled, vapidPublicKey))
|
||||
.then((subscription) => sendSubscriptionToBackEnd(subscription, token, notificationVisibility))
|
||||
.catch((e) => console.warn(`Failed to setup Web Push Notifications: ${e.message}`))
|
||||
}
|
||||
}
|
||||
|
||||
export function unregisterPushNotifications (token) {
|
||||
if (isPushSupported()) {
|
||||
Promise.all([
|
||||
deleteSubscriptionFromBackEnd(token),
|
||||
getOrCreateServiceWorker()
|
||||
.then((registration) => {
|
||||
return unsubscribePush(registration).then((result) => [registration, result])
|
||||
})
|
||||
.then(([registration, unsubResult]) => {
|
||||
if (!unsubResult) {
|
||||
console.warn('Push subscription cancellation wasn\'t successful, killing SW anyway...')
|
||||
}
|
||||
return registration.unregister().then((result) => {
|
||||
if (!result) {
|
||||
console.warn('Failed to kill SW')
|
||||
}
|
||||
})
|
||||
})
|
||||
]).catch((e) => console.warn(`Failed to disable Web Push Notifications: ${e.message}`))
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,7 +4,7 @@ import apiService from '../api/api.service.js'
|
|||
const postStatus = ({ store, status, spoilerText, visibility, sensitive, media = [], inReplyToStatusId = undefined, contentType = 'text/plain' }) => {
|
||||
const mediaIds = map(media, 'id')
|
||||
|
||||
return apiService.postStatus({credentials: store.state.users.currentUser.credentials, status, spoilerText, visibility, sensitive, mediaIds, inReplyToStatusId, contentType})
|
||||
return apiService.postStatus({credentials: store.state.users.currentUser.credentials, status, spoilerText, visibility, sensitive, mediaIds, inReplyToStatusId, contentType, noAttachmentLinks: store.state.instance.noAttachmentLinks})
|
||||
.then((data) => data.json())
|
||||
.then((data) => {
|
||||
if (!data.error) {
|
||||
|
|
|
@ -16,5 +16,7 @@
|
|||
"alwaysShowSubjectInput": true,
|
||||
"hidePostStats": false,
|
||||
"hideUserStats": false,
|
||||
"loginMethod": "password"
|
||||
"loginMethod": "password",
|
||||
"webPushNotifications": false,
|
||||
"noAttachmentLinks": false
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue