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 scopeCopy = (config.scopeCopy)
|
||||||
var subjectLineBehavior = (config.subjectLineBehavior)
|
var subjectLineBehavior = (config.subjectLineBehavior)
|
||||||
var alwaysShowSubjectInput = (config.alwaysShowSubjectInput)
|
var alwaysShowSubjectInput = (config.alwaysShowSubjectInput)
|
||||||
|
var noAttachmentLinks = (config.noAttachmentLinks)
|
||||||
|
|
||||||
store.dispatch('setInstanceOption', { name: 'theme', value: theme })
|
store.dispatch('setInstanceOption', { name: 'theme', value: theme })
|
||||||
store.dispatch('setInstanceOption', { name: 'background', value: background })
|
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: 'scopeCopy', value: scopeCopy })
|
||||||
store.dispatch('setInstanceOption', { name: 'subjectLineBehavior', value: subjectLineBehavior })
|
store.dispatch('setInstanceOption', { name: 'subjectLineBehavior', value: subjectLineBehavior })
|
||||||
store.dispatch('setInstanceOption', { name: 'alwaysShowSubjectInput', value: alwaysShowSubjectInput })
|
store.dispatch('setInstanceOption', { name: 'alwaysShowSubjectInput', value: alwaysShowSubjectInput })
|
||||||
|
store.dispatch('setInstanceOption', { name: 'noAttachmentLinks', value: noAttachmentLinks })
|
||||||
|
|
||||||
if (chatDisabled) {
|
if (chatDisabled) {
|
||||||
store.dispatch('disableChat')
|
store.dispatch('disableChat')
|
||||||
}
|
}
|
||||||
|
|
|
@ -32,6 +32,8 @@ const PostStatusForm = {
|
||||||
},
|
},
|
||||||
mounted () {
|
mounted () {
|
||||||
this.resize(this.$refs.textarea)
|
this.resize(this.$refs.textarea)
|
||||||
|
const textLength = this.$refs.textarea.value.length
|
||||||
|
this.$refs.textarea.setSelectionRange(textLength, textLength)
|
||||||
|
|
||||||
if (this.replyTo) {
|
if (this.replyTo) {
|
||||||
this.$refs.textarea.focus()
|
this.$refs.textarea.focus()
|
||||||
|
@ -250,7 +252,8 @@ const PostStatusForm = {
|
||||||
}
|
}
|
||||||
this.$emit('posted')
|
this.$emit('posted')
|
||||||
let el = this.$el.querySelector('textarea')
|
let el = this.$el.querySelector('textarea')
|
||||||
el.style.height = '16px'
|
el.style.height = 'auto'
|
||||||
|
el.style.height = undefined
|
||||||
this.error = null
|
this.error = null
|
||||||
} else {
|
} else {
|
||||||
this.error = data.error
|
this.error = data.error
|
||||||
|
@ -298,13 +301,15 @@ const PostStatusForm = {
|
||||||
e.dataTransfer.dropEffect = 'copy'
|
e.dataTransfer.dropEffect = 'copy'
|
||||||
},
|
},
|
||||||
resize (e) {
|
resize (e) {
|
||||||
if (!e.target) { return }
|
const target = e.target || e
|
||||||
const vertPadding = Number(window.getComputedStyle(e.target)['padding-top'].substr(0, 1)) +
|
if (!(target instanceof window.Element)) { return }
|
||||||
Number(window.getComputedStyle(e.target)['padding-bottom'].substr(0, 1))
|
const vertPadding = Number(window.getComputedStyle(target)['padding-top'].substr(0, 1)) +
|
||||||
e.target.style.height = 'auto'
|
Number(window.getComputedStyle(target)['padding-bottom'].substr(0, 1))
|
||||||
e.target.style.height = `${e.target.scrollHeight - vertPadding}px`
|
// Auto is needed to make textbox shrink when removing lines
|
||||||
if (e.target.value === '') {
|
target.style.height = 'auto'
|
||||||
e.target.style.height = '16px'
|
target.style.height = `${target.scrollHeight - vertPadding}px`
|
||||||
|
if (target.value === '') {
|
||||||
|
target.style.height = null
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
clearError () {
|
clearError () {
|
||||||
|
|
|
@ -2,22 +2,25 @@
|
||||||
<div id="heading" class="profile-panel-background" :style="headingStyle">
|
<div id="heading" class="profile-panel-background" :style="headingStyle">
|
||||||
<div class="panel-heading text-center">
|
<div class="panel-heading text-center">
|
||||||
<div class='user-info'>
|
<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'>
|
<div class='container'>
|
||||||
<router-link :to="userProfileLink(user)">
|
<router-link :to="userProfileLink(user)">
|
||||||
<StillImage class="avatar" :class='{ "better-shadow": betterShadow }' :src="user.profile_image_url_original"/>
|
<StillImage class="avatar" :class='{ "better-shadow": betterShadow }' :src="user.profile_image_url_original"/>
|
||||||
</router-link>
|
</router-link>
|
||||||
<div class="name-and-screen-name">
|
<div class="name-and-screen-name">
|
||||||
<div :title="user.name" class='user-name' v-if="user.name_html" v-html="user.name_html"></div>
|
<div class="top-line">
|
||||||
<div :title="user.name" class='user-name' v-else>{{user.name}}</div>
|
<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)">
|
<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 class="handle">@{{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 v-if="!hideUserStatsLocal && !hideBio" class="dailyAvg">{{dailyAvg}} {{ $t('user_card.per_day') }}</span>
|
||||||
</router-link>
|
</router-link>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -25,7 +28,7 @@
|
||||||
<div v-if="user.follows_you && loggedIn && isOtherUser" class="following">
|
<div v-if="user.follows_you && loggedIn && isOtherUser" class="following">
|
||||||
{{ $t('user_card.follows_you') }}
|
{{ $t('user_card.follows_you') }}
|
||||||
</div>
|
</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 -->
|
<!-- 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="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"/>
|
<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;
|
border-bottom-right-radius: 0;
|
||||||
|
|
||||||
.panel-heading {
|
.panel-heading {
|
||||||
padding: 0.6em 0em;
|
padding: .6em 0;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
box-shadow: none;
|
box-shadow: none;
|
||||||
}
|
}
|
||||||
|
@ -158,10 +161,10 @@
|
||||||
.user-info {
|
.user-info {
|
||||||
color: $fallback--lightText;
|
color: $fallback--lightText;
|
||||||
color: var(--lightText, $fallback--lightText);
|
color: var(--lightText, $fallback--lightText);
|
||||||
padding: 0 16px;
|
padding: 0 26px;
|
||||||
|
|
||||||
.container {
|
.container {
|
||||||
padding: 16px 10px 6px 10px;
|
padding: 16px 0 6px;
|
||||||
display: flex;
|
display: flex;
|
||||||
max-height: 56px;
|
max-height: 56px;
|
||||||
|
|
||||||
|
@ -218,11 +221,15 @@
|
||||||
vertical-align: middle;
|
vertical-align: middle;
|
||||||
object-fit: contain
|
object-fit: contain
|
||||||
}
|
}
|
||||||
|
.top-line {
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.user-name{
|
.user-name{
|
||||||
text-overflow: ellipsis;
|
text-overflow: ellipsis;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
|
flex: 1 0 auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
.user-screen-name {
|
.user-screen-name {
|
||||||
|
@ -232,27 +239,73 @@
|
||||||
font-weight: light;
|
font-weight: light;
|
||||||
font-size: 15px;
|
font-size: 15px;
|
||||||
padding-right: 0.1em;
|
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 {
|
.user-meta {
|
||||||
margin-bottom: .4em;
|
margin-bottom: .15em;
|
||||||
|
display: flex;
|
||||||
|
align-items: baseline;
|
||||||
|
font-size: 14px;
|
||||||
|
line-height: 22px;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
|
||||||
.following {
|
.following {
|
||||||
font-size: 14px;
|
flex: 1 0 auto;
|
||||||
flex: 0 0 100%;
|
|
||||||
margin: 0;
|
margin: 0;
|
||||||
padding-left: 16px;
|
margin-bottom: .25em;
|
||||||
text-align: left;
|
text-align: left;
|
||||||
float: left;
|
|
||||||
}
|
|
||||||
.floater {
|
|
||||||
margin: 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
&::after {
|
.highlighter {
|
||||||
display: block;
|
flex: 0 1 auto;
|
||||||
content: '';
|
display: flex;
|
||||||
clear: both;
|
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 {
|
.user-interactions {
|
||||||
|
@ -260,8 +313,13 @@
|
||||||
flex-flow: row wrap;
|
flex-flow: row wrap;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
|
|
||||||
|
margin-right: -.75em;
|
||||||
|
|
||||||
div {
|
div {
|
||||||
flex: 1;
|
flex: 1 0 0;
|
||||||
|
margin-right: .75em;
|
||||||
|
margin-bottom: .6em;
|
||||||
|
white-space: nowrap;
|
||||||
}
|
}
|
||||||
|
|
||||||
.mute {
|
.mute {
|
||||||
|
@ -280,8 +338,9 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
button {
|
button {
|
||||||
width: 92%;
|
width: 100%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
|
margin: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.remote-button {
|
.remote-button {
|
||||||
|
@ -304,10 +363,11 @@
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
color: $fallback--lightText;
|
color: $fallback--lightText;
|
||||||
color: var(--lightText, $fallback--lightText);
|
color: var(--lightText, $fallback--lightText);
|
||||||
|
flex-wrap: wrap;
|
||||||
}
|
}
|
||||||
|
|
||||||
.user-count {
|
.user-count {
|
||||||
flex: 1;
|
flex: 1 0 auto;
|
||||||
padding: .5em 0 .5em 0;
|
padding: .5em 0 .5em 0;
|
||||||
margin: 0 .5em;
|
margin: 0 .5em;
|
||||||
|
|
||||||
|
@ -327,32 +387,5 @@
|
||||||
color: #CCC;
|
color: #CCC;
|
||||||
}
|
}
|
||||||
.floater {
|
.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>
|
</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'
|
import generateProfileLink from 'src/services/user_profile_link_generator/user_profile_link_generator'
|
||||||
|
|
||||||
function showWhoToFollow (panel, reply) {
|
function showWhoToFollow (panel, reply) {
|
||||||
var users = reply
|
panel.usersToFollow.forEach((toFollow, index) => {
|
||||||
var cn
|
let randIndex = Math.floor(Math.random() * reply.length)
|
||||||
var index
|
let user = reply[randIndex]
|
||||||
var step = 7
|
let img = user.avatar || '/images/avi.png'
|
||||||
cn = Math.floor(Math.random() * step)
|
let name = user.acct
|
||||||
for (index = 0; index < 3; index++) {
|
|
||||||
var user
|
toFollow.img = img
|
||||||
user = users[cn]
|
toFollow.name = name
|
||||||
var img
|
|
||||||
if (user.avatar) {
|
panel.$store.state.api.backendInteractor.externalProfile(name)
|
||||||
img = user.avatar
|
.then((externalUser) => {
|
||||||
} else {
|
if (!externalUser.error) {
|
||||||
img = '/images/avi.png'
|
panel.$store.commit('addNewUsers', [externalUser])
|
||||||
}
|
toFollow.id = externalUser.id
|
||||||
var name = user.acct
|
}
|
||||||
if (index === 0) {
|
})
|
||||||
panel.img1 = img
|
})
|
||||||
panel.name1 = name
|
|
||||||
panel.$store.state.api.backendInteractor.externalProfile(name)
|
|
||||||
.then((externalUser) => {
|
|
||||||
if (!externalUser.error) {
|
|
||||||
panel.$store.commit('addNewUsers', [externalUser])
|
|
||||||
panel.id1 = 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) {
|
function getWhoToFollow (panel) {
|
||||||
var credentials = panel.$store.state.users.currentUser.credentials
|
var credentials = panel.$store.state.users.currentUser.credentials
|
||||||
if (credentials) {
|
if (credentials) {
|
||||||
panel.name1 = 'Loading...'
|
panel.usersToFollow.forEach(toFollow => {
|
||||||
panel.name2 = 'Loading...'
|
toFollow.name = 'Loading...'
|
||||||
panel.name3 = 'Loading...'
|
})
|
||||||
apiService.suggestions({credentials: credentials})
|
apiService.suggestions({credentials: credentials})
|
||||||
.then((reply) => {
|
.then((reply) => {
|
||||||
showWhoToFollow(panel, reply)
|
showWhoToFollow(panel, reply)
|
||||||
|
@ -67,27 +36,24 @@ function getWhoToFollow (panel) {
|
||||||
|
|
||||||
const WhoToFollowPanel = {
|
const WhoToFollowPanel = {
|
||||||
data: () => ({
|
data: () => ({
|
||||||
img1: '/images/avi.png',
|
usersToFollow: new Array(3).fill().map(x => (
|
||||||
name1: '',
|
{
|
||||||
id1: 0,
|
img: '/images/avi.png',
|
||||||
img2: '/images/avi.png',
|
name: '',
|
||||||
name2: '',
|
id: 0
|
||||||
id2: 0,
|
}
|
||||||
img3: '/images/avi.png',
|
))
|
||||||
name3: '',
|
|
||||||
id3: 0
|
|
||||||
}),
|
}),
|
||||||
computed: {
|
computed: {
|
||||||
user: function () {
|
user: function () {
|
||||||
return this.$store.state.users.currentUser.screen_name
|
return this.$store.state.users.currentUser.screen_name
|
||||||
},
|
},
|
||||||
moreUrl: function () {
|
moreUrl: function () {
|
||||||
var host = window.location.hostname
|
const host = window.location.hostname
|
||||||
var user = this.user
|
const user = this.user
|
||||||
var suggestionsWeb = this.$store.state.instance.suggestionsWeb
|
const suggestionsWeb = this.$store.state.instance.suggestionsWeb
|
||||||
var url
|
const url = suggestionsWeb.replace(/{{host}}/g, encodeURIComponent(host))
|
||||||
url = suggestionsWeb.replace(/{{host}}/g, encodeURIComponent(host))
|
.replace(/{{user}}/g, encodeURIComponent(user))
|
||||||
url = url.replace(/{{user}}/g, encodeURIComponent(user))
|
|
||||||
return url
|
return url
|
||||||
},
|
},
|
||||||
suggestionsEnabled () {
|
suggestionsEnabled () {
|
||||||
|
|
|
@ -8,9 +8,12 @@
|
||||||
</div>
|
</div>
|
||||||
<div class="panel-body who-to-follow">
|
<div class="panel-body who-to-follow">
|
||||||
<p>
|
<p>
|
||||||
<img v-bind:src="img1"/> <router-link :to="userProfileLink(id1, name1)">{{ name1 }}</router-link><br>
|
<span v-for="user in usersToFollow">
|
||||||
<img v-bind:src="img2"/> <router-link :to="userProfileLink(id2, name2)">{{ name2 }}</router-link><br>
|
<img v-bind:src="user.img" />
|
||||||
<img v-bind:src="img3"/> <router-link :to="userProfileLink(id3, name3)">{{ name3 }}</router-link><br>
|
<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>
|
<img v-bind:src="$store.state.instance.logo"> <a v-bind:href="moreUrl" target="_blank">{{$t('who_to_follow.more')}}</a>
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
171
src/i18n/ja.json
171
src/i18n/ja.json
|
@ -29,13 +29,16 @@
|
||||||
"username": "ユーザーめい"
|
"username": "ユーザーめい"
|
||||||
},
|
},
|
||||||
"nav": {
|
"nav": {
|
||||||
|
"back": "もどる",
|
||||||
"chat": "ローカルチャット",
|
"chat": "ローカルチャット",
|
||||||
"friend_requests": "フォローリクエスト",
|
"friend_requests": "フォローリクエスト",
|
||||||
"mentions": "メンション",
|
"mentions": "メンション",
|
||||||
"dms": "ダイレクトメッセージ",
|
"dms": "ダイレクトメッセージ",
|
||||||
"public_tl": "パブリックタイムライン",
|
"public_tl": "パブリックタイムライン",
|
||||||
"timeline": "タイムライン",
|
"timeline": "タイムライン",
|
||||||
"twkn": "つながっているすべてのネットワーク"
|
"twkn": "つながっているすべてのネットワーク",
|
||||||
|
"user_search": "ユーザーをさがす",
|
||||||
|
"preferences": "せってい"
|
||||||
},
|
},
|
||||||
"notifications": {
|
"notifications": {
|
||||||
"broken_favorite": "ステータスがみつかりません。さがしています...",
|
"broken_favorite": "ステータスがみつかりません。さがしています...",
|
||||||
|
@ -70,7 +73,17 @@
|
||||||
"fullname": "スクリーンネーム",
|
"fullname": "スクリーンネーム",
|
||||||
"password_confirm": "パスワードのかくにん",
|
"password_confirm": "パスワードのかくにん",
|
||||||
"registration": "はじめる",
|
"registration": "はじめる",
|
||||||
"token": "しょうたいトークン"
|
"token": "しょうたいトークン",
|
||||||
|
"captcha": "CAPTCHA",
|
||||||
|
"new_captcha": "もじがよめないときは、がぞうをクリックすると、あたらしいがぞうになります",
|
||||||
|
"validations": {
|
||||||
|
"username_required": "なにかかいてください",
|
||||||
|
"fullname_required": "なにかかいてください",
|
||||||
|
"email_required": "なにかかいてください",
|
||||||
|
"password_required": "なにかかいてください",
|
||||||
|
"password_confirmation_required": "なにかかいてください",
|
||||||
|
"password_confirmation_match": "パスワードがちがいます"
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"settings": {
|
"settings": {
|
||||||
"attachmentRadius": "ファイル",
|
"attachmentRadius": "ファイル",
|
||||||
|
@ -90,6 +103,7 @@
|
||||||
"change_password_error": "パスワードをかえることが、できなかったかもしれません。",
|
"change_password_error": "パスワードをかえることが、できなかったかもしれません。",
|
||||||
"changed_password": "パスワードが、かわりました!",
|
"changed_password": "パスワードが、かわりました!",
|
||||||
"collapse_subject": "せつめいのあるとうこうをたたむ",
|
"collapse_subject": "せつめいのあるとうこうをたたむ",
|
||||||
|
"composing": "とうこう",
|
||||||
"confirm_new_password": "あたらしいパスワードのかくにん",
|
"confirm_new_password": "あたらしいパスワードのかくにん",
|
||||||
"current_avatar": "いまのアバター",
|
"current_avatar": "いまのアバター",
|
||||||
"current_password": "いまのパスワード",
|
"current_password": "いまのパスワード",
|
||||||
|
@ -113,17 +127,22 @@
|
||||||
"general": "ぜんぱん",
|
"general": "ぜんぱん",
|
||||||
"hide_attachments_in_convo": "スレッドのファイルをかくす",
|
"hide_attachments_in_convo": "スレッドのファイルをかくす",
|
||||||
"hide_attachments_in_tl": "タイムラインのファイルをかくす",
|
"hide_attachments_in_tl": "タイムラインのファイルをかくす",
|
||||||
|
"hide_isp": "インスタンススペシフィックパネルをかくす",
|
||||||
|
"preload_images": "がぞうをさきよみする",
|
||||||
"hide_post_stats": "とうこうのとうけいをかくす (れい: おきにいりのかず)",
|
"hide_post_stats": "とうこうのとうけいをかくす (れい: おきにいりのかず)",
|
||||||
"hide_user_stats": "ユーザーのとうけいをかくす (れい: フォロワーのかず)",
|
"hide_user_stats": "ユーザーのとうけいをかくす (れい: フォロワーのかず)",
|
||||||
"import_followers_from_a_csv_file": "CSVファイルからフォローをインポートする",
|
"import_followers_from_a_csv_file": "CSVファイルからフォローをインポートする",
|
||||||
"import_theme": "ロード",
|
"import_theme": "ロード",
|
||||||
"inputRadius": "インプットフィールド",
|
"inputRadius": "インプットフィールド",
|
||||||
|
"checkboxRadius": "チェックボックス",
|
||||||
"instance_default": "(デフォルト: {value})",
|
"instance_default": "(デフォルト: {value})",
|
||||||
|
"instance_default_simple": "(デフォルト)",
|
||||||
|
"interface": "インターフェース",
|
||||||
"interfaceLanguage": "インターフェースのことば",
|
"interfaceLanguage": "インターフェースのことば",
|
||||||
"invalid_theme_imported": "このファイルはPleromaのテーマではありません。テーマはへんこうされませんでした。",
|
"invalid_theme_imported": "このファイルはPleromaのテーマではありません。テーマはへんこうされませんでした。",
|
||||||
"limited_availability": "あなたのブラウザではできません",
|
"limited_availability": "あなたのブラウザではできません",
|
||||||
"links": "リンク",
|
"links": "リンク",
|
||||||
"lock_account_description": "あなたがみとめたひとだけ、あなたのアカウントをフォローできます",
|
"lock_account_description": "あなたがみとめたひとだけ、あなたのアカウントをフォローできる",
|
||||||
"loop_video": "ビデオをくりかえす",
|
"loop_video": "ビデオをくりかえす",
|
||||||
"loop_video_silent_only": "おとのないビデオだけくりかえす",
|
"loop_video_silent_only": "おとのないビデオだけくりかえす",
|
||||||
"name": "なまえ",
|
"name": "なまえ",
|
||||||
|
@ -135,6 +154,7 @@
|
||||||
"notification_visibility_mentions": "メンション",
|
"notification_visibility_mentions": "メンション",
|
||||||
"notification_visibility_repeats": "リピート",
|
"notification_visibility_repeats": "リピート",
|
||||||
"no_rich_text_description": "リッチテキストをつかわない",
|
"no_rich_text_description": "リッチテキストをつかわない",
|
||||||
|
"hide_network_description": "わたしがフォローしているひとと、わたしをフォローしているひとを、みせない",
|
||||||
"nsfw_clickthrough": "NSFWなファイルをかくす",
|
"nsfw_clickthrough": "NSFWなファイルをかくす",
|
||||||
"panelRadius": "パネル",
|
"panelRadius": "パネル",
|
||||||
"pause_on_unfocused": "タブにフォーカスがないときストリーミングをとめる",
|
"pause_on_unfocused": "タブにフォーカスがないときストリーミングをとめる",
|
||||||
|
@ -151,20 +171,139 @@
|
||||||
"saving_err": "せっていをセーブできませんでした",
|
"saving_err": "せっていをセーブできませんでした",
|
||||||
"saving_ok": "せっていをセーブしました",
|
"saving_ok": "せっていをセーブしました",
|
||||||
"security_tab": "セキュリティ",
|
"security_tab": "セキュリティ",
|
||||||
|
"scope_copy": "リプライするとき、こうかいはんいをコピーする (DMのこうかいはんいは、つねにコピーされます)",
|
||||||
"set_new_avatar": "あたらしいアバターをせっていする",
|
"set_new_avatar": "あたらしいアバターをせっていする",
|
||||||
"set_new_profile_background": "あたらしいプロフィールのバックグラウンドをせっていする",
|
"set_new_profile_background": "あたらしいプロフィールのバックグラウンドをせっていする",
|
||||||
"set_new_profile_banner": "あたらしいプロフィールバナーを設定する",
|
"set_new_profile_banner": "あたらしいプロフィールバナーを設定する",
|
||||||
"settings": "せってい",
|
"settings": "せってい",
|
||||||
|
"subject_input_always_show": "サブジェクトフィールドをいつでもひょうじする",
|
||||||
|
"subject_line_behavior": "リプライするときサブジェクトをコピーする",
|
||||||
|
"subject_line_email": "メールふう: \"re: サブジェクト\"",
|
||||||
|
"subject_line_mastodon": "マストドンふう: そのままコピー",
|
||||||
|
"subject_line_noop": "コピーしない",
|
||||||
"stop_gifs": "カーソルをかさねたとき、GIFをうごかす",
|
"stop_gifs": "カーソルをかさねたとき、GIFをうごかす",
|
||||||
"streaming": "うえまでスクロールしたとき、じどうてきにストリーミングする",
|
"streaming": "うえまでスクロールしたとき、じどうてきにストリーミングする",
|
||||||
"text": "もじ",
|
"text": "もじ",
|
||||||
"theme": "テーマ",
|
"theme": "テーマ",
|
||||||
"theme_help": "カラーテーマをカスタマイズできます",
|
"theme_help": "カラーテーマをカスタマイズできます",
|
||||||
|
"theme_help_v2_1": "チェックボックスをONにすると、コンポーネントごとに、いろと、とうめいどを、オーバーライドできます。「すべてクリア」ボタンをおすと、すべてのオーバーライドを、やめます。",
|
||||||
|
"theme_help_v2_2": "バックグラウンドとテキストのコントラストをあらわすアイコンがあります。マウスをホバーすると、くわしいせつめいがでます。とうめいないろをつかっているときは、もっともわるいばあいのコントラストがしめされます。",
|
||||||
"tooltipRadius": "ツールチップとアラート",
|
"tooltipRadius": "ツールチップとアラート",
|
||||||
"user_settings": "ユーザーせってい",
|
"user_settings": "ユーザーせってい",
|
||||||
"values": {
|
"values": {
|
||||||
"false": "いいえ",
|
"false": "いいえ",
|
||||||
"true": "はい"
|
"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": {
|
"timeline": {
|
||||||
|
@ -183,10 +322,15 @@
|
||||||
"blocked": "ブロックしています!",
|
"blocked": "ブロックしています!",
|
||||||
"deny": "おことわり",
|
"deny": "おことわり",
|
||||||
"follow": "フォロー",
|
"follow": "フォロー",
|
||||||
|
"follow_sent": "リクエストを、おくりました!",
|
||||||
|
"follow_progress": "リクエストしています…",
|
||||||
|
"follow_again": "ふたたびリクエストをおくりますか?",
|
||||||
|
"follow_unfollow": "フォローをやめる",
|
||||||
"followees": "フォロー",
|
"followees": "フォロー",
|
||||||
"followers": "フォロワー",
|
"followers": "フォロワー",
|
||||||
"following": "フォローしています!",
|
"following": "フォローしています!",
|
||||||
"follows_you": "フォローされました!",
|
"follows_you": "フォローされました!",
|
||||||
|
"its_you": "これはあなたです!",
|
||||||
"mute": "ミュート",
|
"mute": "ミュート",
|
||||||
"muted": "ミュートしています!",
|
"muted": "ミュートしています!",
|
||||||
"per_day": "/日",
|
"per_day": "/日",
|
||||||
|
@ -199,5 +343,26 @@
|
||||||
"who_to_follow": {
|
"who_to_follow": {
|
||||||
"more": "くわしく",
|
"more": "くわしく",
|
||||||
"who_to_follow": "おすすめユーザー"
|
"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 VueI18n from 'vue-i18n'
|
||||||
|
|
||||||
import createPersistedState from './lib/persisted_state.js'
|
import createPersistedState from './lib/persisted_state.js'
|
||||||
|
import pushNotifications from './lib/push_notifications_plugin.js'
|
||||||
|
|
||||||
import messages from './i18n/messages.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) => {
|
createPersistedState(persistedStateOptions).then((persistedState) => {
|
||||||
const store = new Vuex.Store({
|
const store = new Vuex.Store({
|
||||||
modules: {
|
modules: {
|
||||||
|
@ -88,7 +64,7 @@ createPersistedState(persistedStateOptions).then((persistedState) => {
|
||||||
chat: chatModule,
|
chat: chatModule,
|
||||||
oauth: oauthModule
|
oauth: oauthModule
|
||||||
},
|
},
|
||||||
plugins: [persistedState, registerPushNotifications],
|
plugins: [persistedState, pushNotifications],
|
||||||
strict: false // Socket modifies itself, let's ignore this for now.
|
strict: false // Socket modifies itself, let's ignore this for now.
|
||||||
// strict: process.env.NODE_ENV !== 'production'
|
// strict: process.env.NODE_ENV !== 'production'
|
||||||
})
|
})
|
||||||
|
|
|
@ -24,7 +24,7 @@ const defaultState = {
|
||||||
likes: true,
|
likes: true,
|
||||||
repeats: true
|
repeats: true
|
||||||
},
|
},
|
||||||
webPushNotifications: true,
|
webPushNotifications: false,
|
||||||
muteWords: [],
|
muteWords: [],
|
||||||
highlight: {},
|
highlight: {},
|
||||||
interfaceLanguage: browserLocale,
|
interfaceLanguage: browserLocale,
|
||||||
|
|
|
@ -27,6 +27,7 @@ const defaultState = {
|
||||||
loginMethod: 'password',
|
loginMethod: 'password',
|
||||||
nsfwCensorImage: undefined,
|
nsfwCensorImage: undefined,
|
||||||
vapidPublicKey: undefined,
|
vapidPublicKey: undefined,
|
||||||
|
noAttachmentLinks: false,
|
||||||
|
|
||||||
// Nasty stuff
|
// Nasty stuff
|
||||||
pleromaBackend: true,
|
pleromaBackend: true,
|
||||||
|
|
|
@ -27,6 +27,7 @@ export const defaultState = {
|
||||||
maxId: 0,
|
maxId: 0,
|
||||||
minId: Number.POSITIVE_INFINITY,
|
minId: Number.POSITIVE_INFINITY,
|
||||||
data: [],
|
data: [],
|
||||||
|
idStore: {},
|
||||||
error: false
|
error: false
|
||||||
},
|
},
|
||||||
favorites: new Set(),
|
favorites: new Set(),
|
||||||
|
@ -307,6 +308,7 @@ const addNewNotifications = (state, { dispatch, notifications, older, visibleNot
|
||||||
}
|
}
|
||||||
|
|
||||||
state.notifications.data.push(result)
|
state.notifications.data.push(result)
|
||||||
|
state.notifications.idStore[notification.id] = result
|
||||||
|
|
||||||
if ('Notification' in window && window.Notification.permission === 'granted') {
|
if ('Notification' in window && window.Notification.permission === 'granted') {
|
||||||
const title = action.user.name
|
const title = action.user.name
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import backendInteractorService from '../services/backend_interactor_service/backend_interactor_service.js'
|
import backendInteractorService from '../services/backend_interactor_service/backend_interactor_service.js'
|
||||||
import { compact, map, each, merge } from 'lodash'
|
import { compact, map, each, merge } from 'lodash'
|
||||||
import { set } from 'vue'
|
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 oauthApi from '../services/new_api/oauth'
|
||||||
import { humanizeErrors } from './errors'
|
import { humanizeErrors } from './errors'
|
||||||
|
|
||||||
|
@ -66,6 +66,9 @@ export const mutations = {
|
||||||
setUserForStatus (state, status) {
|
setUserForStatus (state, status) {
|
||||||
status.user = state.usersObject[status.user.id]
|
status.user = state.usersObject[status.user.id]
|
||||||
},
|
},
|
||||||
|
setUserForNotification (state, notification) {
|
||||||
|
notification.action.user = state.usersObject[notification.action.user.id]
|
||||||
|
},
|
||||||
setColor (state, { user: { id }, highlighted }) {
|
setColor (state, { user: { id }, highlighted }) {
|
||||||
const user = state.usersObject[id]
|
const user = state.usersObject[id]
|
||||||
set(user, 'highlight', highlighted)
|
set(user, 'highlight', highlighted)
|
||||||
|
@ -113,8 +116,14 @@ const users = {
|
||||||
const token = store.state.currentUser.credentials
|
const token = store.state.currentUser.credentials
|
||||||
const vapidPublicKey = store.rootState.instance.vapidPublicKey
|
const vapidPublicKey = store.rootState.instance.vapidPublicKey
|
||||||
const isEnabled = store.rootState.config.webPushNotifications
|
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 }) {
|
addNewStatuses (store, { statuses }) {
|
||||||
const users = map(statuses, 'user')
|
const users = map(statuses, 'user')
|
||||||
|
@ -131,6 +140,21 @@ const users = {
|
||||||
store.commit('setUserForStatus', status)
|
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) {
|
async signUp (store, userInfo) {
|
||||||
store.commit('signUpPending')
|
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 idsText = mediaIds.join(',')
|
||||||
const form = new FormData()
|
const form = new FormData()
|
||||||
|
|
||||||
form.append('status', status)
|
form.append('status', status)
|
||||||
form.append('source', 'Pleroma FE')
|
form.append('source', 'Pleroma FE')
|
||||||
|
if (noAttachmentLinks) form.append('no_attachment_links', noAttachmentLinks)
|
||||||
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)
|
if (sensitive) form.append('sensitive', sensitive)
|
||||||
|
|
|
@ -14,12 +14,12 @@ function isPushSupported () {
|
||||||
return 'serviceWorker' in navigator && 'PushManager' in window
|
return 'serviceWorker' in navigator && 'PushManager' in window
|
||||||
}
|
}
|
||||||
|
|
||||||
function registerServiceWorker () {
|
function getOrCreateServiceWorker () {
|
||||||
return runtime.register()
|
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 (!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'))
|
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)
|
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/', {
|
return window.fetch('/api/v1/push/subscription/', {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: {
|
headers: {
|
||||||
|
@ -41,29 +62,49 @@ function sendSubscriptionToBackEnd (subscription, token) {
|
||||||
subscription,
|
subscription,
|
||||||
data: {
|
data: {
|
||||||
alerts: {
|
alerts: {
|
||||||
follow: true,
|
follow: notificationVisibility.follows,
|
||||||
favourite: true,
|
favourite: notificationVisibility.likes,
|
||||||
mention: true,
|
mention: notificationVisibility.mentions,
|
||||||
reblog: true
|
reblog: notificationVisibility.repeats
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
}).then((response) => {
|
||||||
|
if (!response.ok) throw new Error('Bad status code from server.')
|
||||||
|
return response.json()
|
||||||
|
}).then((responseData) => {
|
||||||
|
if (!responseData.id) throw new Error('Bad response from server.')
|
||||||
|
return responseData
|
||||||
})
|
})
|
||||||
.then((response) => {
|
|
||||||
if (!response.ok) throw new Error('Bad status code from server.')
|
|
||||||
return response.json()
|
|
||||||
})
|
|
||||||
.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()) {
|
if (isPushSupported()) {
|
||||||
registerServiceWorker()
|
getOrCreateServiceWorker()
|
||||||
.then((registration) => subscribe(registration, isEnabled, vapidPublicKey))
|
.then((registration) => subscribePush(registration, isEnabled, vapidPublicKey))
|
||||||
.then((subscription) => sendSubscriptionToBackEnd(subscription, token))
|
.then((subscription) => sendSubscriptionToBackEnd(subscription, token, notificationVisibility))
|
||||||
.catch((e) => console.warn(`Failed to setup Web Push Notifications: ${e.message}`))
|
.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 postStatus = ({ store, status, spoilerText, visibility, sensitive, media = [], inReplyToStatusId = undefined, contentType = 'text/plain' }) => {
|
||||||
const mediaIds = map(media, 'id')
|
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) => data.json())
|
||||||
.then((data) => {
|
.then((data) => {
|
||||||
if (!data.error) {
|
if (!data.error) {
|
||||||
|
|
|
@ -16,5 +16,7 @@
|
||||||
"alwaysShowSubjectInput": true,
|
"alwaysShowSubjectInput": true,
|
||||||
"hidePostStats": false,
|
"hidePostStats": false,
|
||||||
"hideUserStats": false,
|
"hideUserStats": false,
|
||||||
"loginMethod": "password"
|
"loginMethod": "password",
|
||||||
|
"webPushNotifications": false,
|
||||||
|
"noAttachmentLinks": false
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue