Compare commits
168 Commits
e179c9f36e
...
205f31e3dc
Author | SHA1 | Date |
---|---|---|
Vivian Lim | 205f31e3dc | |
Vivian Lim | 267cf6143e | |
Vivian Lim | bc860b74f6 | |
kaniini | 7887e42fca | |
Henry | 9989ad29df | |
Artik Banana | 9b95c4c7b5 | |
Henry | c94b9796ae | |
Henry Jameson | 89f9b3a468 | |
Henry | 4bff6f12ed | |
Hakaba Hitoyo | 207c188c16 | |
Henry | dda0effd65 | |
Henry Jameson | aea00310c6 | |
Henry | 8455936cd6 | |
kaniini | ed6abeacad | |
kaniini | 369fd648f6 | |
kaniini | eb93034a40 | |
kaniini | 3f72b611c1 | |
Hakaba Hitoyo | 3a17252929 | |
Henry Jameson | 5726be6830 | |
Artik Banana | 78e12bc812 | |
Henry | 2eab0cb115 | |
Henry | 385484566a | |
Artik Banana | 5e1f0d9416 | |
hakabahitoyo | 8b94ea28ec | |
hakabahitoyo | dbf24e1fbf | |
Hakaba Hitoyo | 49ab19c342 | |
Hakaba Hitoyo | 65115bfc7f | |
Henry Jameson | dc0b47e8bd | |
Henry | c348a3ec11 | |
Henry Jameson | f9eb9e0b70 | |
Henry Jameson | 6e64324d37 | |
Henry Jameson | d4f9d21857 | |
Henry Jameson | 08b044c365 | |
Henry Jameson | a6047e0ad5 | |
Henry Jameson | 36101abc22 | |
Dingdreher | 900aaefb98 | |
hakabahitoyo | fa477dd41f | |
hakabahitoyo | 38b683b30c | |
Hakaba Hitoyo | a99869146f | |
Hakaba Hitoyo | 049e2397b1 | |
hakabahitoyo | 07ef43c7b5 | |
Hakaba Hitoyo | 52fe01e4d8 | |
Shpuld Shpludson | 20b0ffc0b9 | |
Shpuld Shpludson | a8ae2a5b11 | |
Henry Jameson | d6240c25cf | |
Henry Jameson | 17f30190e0 | |
shpuld | 77e8933b2e | |
shpuld | 384c80e238 | |
shpuld | f07e6b271c | |
kaniini | 33b13d3775 | |
William Pitcock | f9bfe2ea37 | |
kaniini | 958acbab8d | |
kaniini | 48391a45ba | |
meireikei | 2e37e8cf39 | |
kaniini | e71b0411aa | |
kaniini | 84aa1ecdcd | |
William Pitcock | cabcb5c81b | |
William Pitcock | 9a43ba73e6 | |
Henry Jameson | 8c07e63f77 | |
Henry Jameson | 46d8b55d44 | |
Henry Jameson | e99534ef71 | |
William Pitcock | 20a67e6809 | |
William Pitcock | 38e3c2493d | |
Henry Jameson | 63fdad8703 | |
Henry Jameson | 1461a52ade | |
Henry Jameson | 065d1c7f49 | |
Henry Jameson | 3b9cb1384a | |
Henry Jameson | fa4c4c9122 | |
Henry Jameson | fa1116249d | |
Henry Jameson | 42584b1a34 | |
Henry Jameson | 4589466917 | |
Henry Jameson | 507d5bc444 | |
Henry Jameson | 1246463f96 | |
Henry Jameson | fb7f65481e | |
Henry Jameson | e58221fb87 | |
Henry Jameson | b0e0686c7f | |
Henry Jameson | 66a22762c2 | |
Henry Jameson | da362b2b88 | |
Henry Jameson | 330288b4cd | |
Henry Jameson | c3b27ab4c2 | |
Henry Jameson | cff4177bf3 | |
Henry Jameson | b48a3210a3 | |
Henry Jameson | 8e560676f1 | |
Henry Jameson | 68d15f665e | |
Henry Jameson | 226849b26e | |
Henry Jameson | fd604dfd2a | |
Henry Jameson | eacbd9b500 | |
Henry Jameson | b4cc1e020b | |
Ekaterina Vaartis | d2640d4bb5 | |
Ekaterina Vaartis | 14c1704ea1 | |
kaniini | 2dd99c7dd9 | |
kaniini | 257da5c740 | |
scarlett | 74a6df8a55 | |
scarlett | b68ebf3056 | |
hakabahitoyo | 7fc4506d29 | |
kaniini | bc4f09b775 | |
kaniini | 256aa25a11 | |
scarlett | 54ac0dfefd | |
kaniini | a7c6007d54 | |
kaniini | 5bb5ef43ef | |
scarlett | 52ce86ed57 | |
kaniini | 9e111f14fd | |
Hakaba Hitoyo | c6913e3909 | |
Hakaba Hitoyo | 8b9e973a55 | |
Hakaba Hitoyo | a3cc78115c | |
Hakaba Hitoyo | a81c3b1324 | |
scarlett | a7811e7bd9 | |
scarlett | d50440d802 | |
Ekaterina Vaartis | c1e4bfa90f | |
William Pitcock | 30a6b7be5b | |
kaniini | 673f0fca3f | |
kaniini | fe906cc3f0 | |
kaniini | 55650ff7ea | |
scarlett | da96294866 | |
scarlett | 60b115320f | |
scarlett | 50b3bd22e6 | |
scarlett | 296ab54301 | |
kaniini | 14db3f279d | |
kaniini | 0429963e63 | |
kaniini | 71576947ed | |
kaniini | b0568ca5c3 | |
dtluna | 81c04fac17 | |
tsukada-ecsec | dfc5f170c6 | |
Henry Jameson | 13acdc4a00 | |
tsukada-ecsec | 0647c1bb72 | |
tsukada-ecsec | 54166c3ad3 | |
tsukada-ecsec | 41256045f2 | |
tsukada-ecsec | bebd9c5ec8 | |
scarlett | 2596f22814 | |
Henry Jameson | a196c3551a | |
Henry Jameson | b97db4912d | |
Henry Jameson | f9b0a95969 | |
Henry Jameson | 3ccea3442e | |
Henry Jameson | 35b912bce4 | |
Henry Jameson | 9e78c64d5e | |
Henry Jameson | fa66385c5b | |
Henry Jameson | 612aa56c8b | |
Henry Jameson | 0b6f9c62a1 | |
Henry Jameson | 23a1000298 | |
Henry Jameson | cc473df314 | |
Henry Jameson | 3afe65352b | |
Henry Jameson | 6454837ea4 | |
Henry Jameson | decc209fdc | |
Henry Jameson | 693eb4b717 | |
Henry Jameson | e8f7491003 | |
Henry Jameson | ef04a78634 | |
Sebastian Huebner | 45478d4bed | |
Sebastian Huebner | 2b61be5271 | |
Sebastian Huebner | 1dddce5624 | |
Henry Jameson | ef515056b5 | |
Henry Jameson | d085cc8584 | |
Henry Jameson | 63650aec29 | |
Hakaba Hitoyo | d1b3d7e90f | |
Hakaba Hitoyo | e2dae87772 | |
Hakaba Hitoyo | b7d1bb39e0 | |
Hakaba Hitoyo | 23cfec4332 | |
Hakaba Hitoyo | 6cc1083287 | |
Hakaba Hitoyo | f8834ba1fb | |
Hakaba Hitoyo | 3398303c9b | |
hakabahitoyo | 5e47c59615 | |
Hakaba Hitoyo | 19e310fc67 | |
Hakaba Hitoyo | 5900bccff3 | |
Hakaba Hitoyo | bcd499c372 | |
Sebastian Huebner | 687d80ed79 | |
lambda | af47d51cd1 | |
pizzaiolo | 9b86fc4dcd | |
pizzaiolo | c8cdfda01c | |
pizzaiolo | 7187a8f2dc |
4
.babelrc
4
.babelrc
|
@ -1,5 +1,5 @@
|
|||
{
|
||||
"presets": ["es2015", "stage-2"],
|
||||
"plugins": ["transform-runtime", "lodash"],
|
||||
"presets": ["es2015", "stage-2", "env"],
|
||||
"plugins": ["transform-runtime", "lodash", "transform-vue-jsx"],
|
||||
"comments": false
|
||||
}
|
||||
|
|
|
@ -54,7 +54,7 @@ module.exports = {
|
|||
loader: 'vue'
|
||||
},
|
||||
{
|
||||
test: /\.js$/,
|
||||
test: /\.jsx?$/,
|
||||
loader: 'babel',
|
||||
include: projectRoot,
|
||||
exclude: /node_modules\/(?!tributejs)/
|
||||
|
|
|
@ -37,8 +37,12 @@
|
|||
"autoprefixer": "^6.4.0",
|
||||
"babel-core": "^6.0.0",
|
||||
"babel-eslint": "^7.0.0",
|
||||
"babel-helper-vue-jsx-merge-props": "^2.0.3",
|
||||
"babel-loader": "^6.0.0",
|
||||
"babel-plugin-syntax-jsx": "^6.18.0",
|
||||
"babel-plugin-transform-runtime": "^6.0.0",
|
||||
"babel-plugin-transform-vue-jsx": "3",
|
||||
"babel-preset-env": "^1.7.0",
|
||||
"babel-preset-es2015": "^6.0.0",
|
||||
"babel-preset-stage-2": "^6.0.0",
|
||||
"babel-register": "^6.0.0",
|
||||
|
@ -63,6 +67,7 @@
|
|||
"html-webpack-plugin": "^2.8.1",
|
||||
"http-proxy-middleware": "^0.17.2",
|
||||
"inject-loader": "^2.0.1",
|
||||
"iso-639-1": "^2.0.3",
|
||||
"isparta-loader": "^2.0.0",
|
||||
"json-loader": "^0.5.4",
|
||||
"karma": "^1.3.0",
|
||||
|
|
43
src/App.js
43
src/App.js
|
@ -2,8 +2,9 @@ import UserPanel from './components/user_panel/user_panel.vue'
|
|||
import NavPanel from './components/nav_panel/nav_panel.vue'
|
||||
import Notifications from './components/notifications/notifications.vue'
|
||||
import UserFinder from './components/user_finder/user_finder.vue'
|
||||
import WhoToFollowPanel from './components/who_to_follow_panel/who_to_follow_panel.vue'
|
||||
import InstanceSpecificPanel from './components/instance_specific_panel/instance_specific_panel.vue'
|
||||
import FeaturesPanel from './components/features_panel/features_panel.vue'
|
||||
import WhoToFollowPanel from './components/who_to_follow_panel/who_to_follow_panel.vue'
|
||||
import ChatPanel from './components/chat_panel/chat_panel.vue'
|
||||
|
||||
export default {
|
||||
|
@ -13,23 +14,55 @@ export default {
|
|||
NavPanel,
|
||||
Notifications,
|
||||
UserFinder,
|
||||
WhoToFollowPanel,
|
||||
InstanceSpecificPanel,
|
||||
FeaturesPanel,
|
||||
WhoToFollowPanel,
|
||||
ChatPanel
|
||||
},
|
||||
data: () => ({
|
||||
mobileActivePanel: 'timeline'
|
||||
mobileActivePanel: 'timeline',
|
||||
supportsMask: window.CSS && window.CSS.supports && (
|
||||
window.CSS.supports('mask-size', 'contain') ||
|
||||
window.CSS.supports('-webkit-mask-size', 'contain') ||
|
||||
window.CSS.supports('-moz-mask-size', 'contain') ||
|
||||
window.CSS.supports('-ms-mask-size', 'contain') ||
|
||||
window.CSS.supports('-o-mask-size', 'contain')
|
||||
)
|
||||
}),
|
||||
created () {
|
||||
// Load the locale from the storage
|
||||
this.$i18n.locale = this.$store.state.config.interfaceLanguage
|
||||
},
|
||||
computed: {
|
||||
currentUser () { return this.$store.state.users.currentUser },
|
||||
background () {
|
||||
return this.currentUser.background_image || this.$store.state.config.background
|
||||
},
|
||||
logoStyle () { return { 'background-image': `url(${this.$store.state.config.logo})` } },
|
||||
enableMask () { return this.supportsMask && this.$store.state.config.logoMask },
|
||||
logoStyle () {
|
||||
return {
|
||||
'visibility': this.enableMask ? 'hidden' : 'visible'
|
||||
}
|
||||
},
|
||||
logoMaskStyle () {
|
||||
return this.enableMask ? {
|
||||
'mask-image': `url(${this.$store.state.config.logo})`
|
||||
} : {
|
||||
'background-color': this.enableMask ? '' : 'transparent'
|
||||
}
|
||||
},
|
||||
logoBgStyle () {
|
||||
return Object.assign({
|
||||
'margin': `${this.$store.state.config.logoMargin} 0`
|
||||
}, this.enableMask ? {} : {
|
||||
'background-color': this.enableMask ? '' : 'transparent'
|
||||
})
|
||||
},
|
||||
logo () { return this.$store.state.config.logo },
|
||||
style () { return { 'background-image': `url(${this.background})` } },
|
||||
sitename () { return this.$store.state.config.name },
|
||||
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 }
|
||||
},
|
||||
methods: {
|
||||
|
|
91
src/App.scss
91
src/App.scss
|
@ -48,7 +48,7 @@ a {
|
|||
color: var(--link, $fallback--link);
|
||||
}
|
||||
|
||||
button{
|
||||
button {
|
||||
user-select: none;
|
||||
color: $fallback--fg;
|
||||
color: var(--fg, $fallback--fg);
|
||||
|
@ -64,10 +64,19 @@ button{
|
|||
font-size: 14px;
|
||||
font-family: sans-serif;
|
||||
|
||||
&::-moz-focus-inner {
|
||||
border: none;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
box-shadow: 0px 0px 4px rgba(255, 255, 255, 0.3);
|
||||
}
|
||||
|
||||
&:active {
|
||||
border-bottom: 1px solid rgba(255, 255, 255, 0.2);
|
||||
border-top: 1px solid rgba(0, 0, 0, 0.2);
|
||||
}
|
||||
|
||||
&:disabled {
|
||||
cursor: not-allowed;
|
||||
opacity: 0.5;
|
||||
|
@ -105,6 +114,7 @@ input, textarea, .select {
|
|||
position: relative;
|
||||
height: 29px;
|
||||
line-height: 16px;
|
||||
hyphens: none;
|
||||
|
||||
.icon-down-open {
|
||||
position: absolute;
|
||||
|
@ -226,6 +236,40 @@ nav {
|
|||
position: fixed;
|
||||
height: 50px;
|
||||
|
||||
.logo {
|
||||
display: flex;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
|
||||
align-items: stretch;
|
||||
justify-content: center;
|
||||
flex: 0 0 auto;
|
||||
z-index: -1;
|
||||
|
||||
.mask {
|
||||
mask-repeat: no-repeat;
|
||||
mask-position: center;
|
||||
mask-size: contain;
|
||||
background-color: $fallback--fg;
|
||||
background-color: var(--fg, $fallback--fg);
|
||||
position: absolute;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
}
|
||||
|
||||
img {
|
||||
height: 100%;
|
||||
object-fit: contain;
|
||||
display: block;
|
||||
flex: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.inner-nav {
|
||||
padding-left: 20px;
|
||||
padding-right: 20px;
|
||||
|
@ -234,9 +278,6 @@ nav {
|
|||
flex-basis: 970px;
|
||||
margin: auto;
|
||||
height: 50px;
|
||||
background-repeat: no-repeat;
|
||||
background-position: center;
|
||||
background-size: auto 80%;
|
||||
|
||||
a i {
|
||||
color: $fallback--link;
|
||||
|
@ -282,15 +323,42 @@ main-router {
|
|||
}
|
||||
|
||||
.panel-heading {
|
||||
display: flex;
|
||||
border-radius: $fallback--panelRadius $fallback--panelRadius 0 0;
|
||||
border-radius: var(--panelRadius, $fallback--panelRadius) var(--panelRadius, $fallback--panelRadius) 0 0;
|
||||
background-size: cover;
|
||||
padding: 0.6em 1.0em;
|
||||
padding: .6em .6em;
|
||||
text-align: left;
|
||||
font-size: 1.3em;
|
||||
line-height: 24px;
|
||||
line-height: 28px;
|
||||
background-color: $fallback--btn;
|
||||
background-color: var(--btn, $fallback--btn);
|
||||
align-items: baseline;
|
||||
|
||||
.title {
|
||||
flex: 1 0 auto;
|
||||
font-size: 1.3em;
|
||||
}
|
||||
|
||||
.alert {
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
overflow-x: hidden;
|
||||
}
|
||||
|
||||
button {
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
button, .alert {
|
||||
// height: 100%;
|
||||
line-height: 21px;
|
||||
min-height: 0;
|
||||
box-sizing: border-box;
|
||||
margin: 0;
|
||||
margin-left: .25em;
|
||||
min-width: 1px;
|
||||
align-self: stretch;
|
||||
}
|
||||
}
|
||||
|
||||
.panel-heading.stub {
|
||||
|
@ -451,6 +519,14 @@ nav {
|
|||
color: $fallback--lightFg;
|
||||
color: var(--lightFg, $fallback--lightFg);
|
||||
}
|
||||
|
||||
.text-format {
|
||||
float: right;
|
||||
}
|
||||
|
||||
div {
|
||||
padding-top: 5px;
|
||||
}
|
||||
}
|
||||
|
||||
.visibility-notice {
|
||||
|
@ -460,4 +536,3 @@ nav {
|
|||
border-radius: $fallback--inputRadius;
|
||||
border-radius: var(--inputRadius, $fallback--inputRadius);
|
||||
}
|
||||
|
||||
|
|
|
@ -1,7 +1,11 @@
|
|||
<template>
|
||||
<div id="app" v-bind:style="style">
|
||||
<nav class='container' @click="scrollToTop()" id="nav">
|
||||
<div class='inner-nav' :style="logoStyle">
|
||||
<div class='logo' :style='logoBgStyle'>
|
||||
<div class='mask' :style='logoMaskStyle'></div>
|
||||
<img :src='logo' :style='logoStyle'>
|
||||
</div>
|
||||
<div class='inner-nav'>
|
||||
<div class='item'>
|
||||
<router-link :to="{ name: 'root'}">{{sitename}}</router-link>
|
||||
</div>
|
||||
|
@ -24,7 +28,8 @@
|
|||
<user-panel></user-panel>
|
||||
<nav-panel></nav-panel>
|
||||
<instance-specific-panel v-if="showInstanceSpecificPanel"></instance-specific-panel>
|
||||
<who-to-follow-panel v-if="currentUser && showWhoToFollowPanel"></who-to-follow-panel>
|
||||
<features-panel v-if="!currentUser"></features-panel>
|
||||
<who-to-follow-panel v-if="currentUser && suggestionsEnabled"></who-to-follow-panel>
|
||||
<notifications v-if="currentUser"></notifications>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -16,7 +16,7 @@ const Attachment = {
|
|||
loopVideo: this.$store.state.config.loopVideo,
|
||||
showHidden: false,
|
||||
loading: false,
|
||||
img: this.type === 'image' && document.createElement('img')
|
||||
img: fileTypeService.fileType(this.attachment.mimetype) === 'image' && document.createElement('img')
|
||||
}
|
||||
},
|
||||
components: {
|
||||
|
|
|
@ -3,14 +3,14 @@
|
|||
<a class="placeholder" v-if="type !== 'html'" target="_blank" :href="attachment.url">[{{nsfw ? "NSFW/" : ""}}{{type.toUpperCase()}}]</a>
|
||||
</div>
|
||||
<div v-else class="attachment" :class="{[type]: true, loading, 'small-attachment': isSmall, 'fullwidth': fullwidth, 'nsfw-placeholder': hidden}" v-show="!isEmpty">
|
||||
<a class="image-attachment" v-if="hidden" @click.prevent="toggleHidden()">
|
||||
<a class="image-attachment-placeholder" v-if="hidden" @click.prevent="toggleHidden()">
|
||||
<img :key="nsfwImage" :src="nsfwImage"/>
|
||||
</a>
|
||||
<div class="hider" v-if="nsfw && hideNsfwLocal && !hidden">
|
||||
<a href="#" @click.prevent="toggleHidden()">Hide</a>
|
||||
</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"/>
|
||||
</a>
|
||||
|
||||
|
@ -51,6 +51,10 @@
|
|||
|
||||
.nsfw-placeholder {
|
||||
cursor: pointer;
|
||||
|
||||
&.loading {
|
||||
cursor: progress;
|
||||
}
|
||||
}
|
||||
|
||||
.small-attachment {
|
||||
|
@ -61,6 +65,7 @@
|
|||
}
|
||||
|
||||
.attachment {
|
||||
position: relative;
|
||||
flex: 1 0 30%;
|
||||
margin: 0.5em 0.7em 0.6em 0.0em;
|
||||
align-self: flex-start;
|
||||
|
@ -88,10 +93,6 @@
|
|||
display: flex;
|
||||
}
|
||||
|
||||
&.loading {
|
||||
cursor: progress;
|
||||
}
|
||||
|
||||
.hider {
|
||||
position: absolute;
|
||||
margin: 10px;
|
||||
|
@ -179,5 +180,9 @@
|
|||
image-orientation: from-image;
|
||||
}
|
||||
}
|
||||
|
||||
.image-attachment-placeholder {
|
||||
// currently, no style is applied to image placeholders.
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
<template>
|
||||
<div class="timeline panel panel-default">
|
||||
<div class="panel-heading conversation-heading">
|
||||
{{ $t('timeline.conversation') }}
|
||||
<span v-if="collapsable" style="float:right;">
|
||||
<small><a href="#" @click.prevent="$emit('toggleExpanded')">{{ $t('timeline.collapse') }}</a></small>
|
||||
<span class="title"> {{ $t('timeline.conversation') }} </span>
|
||||
<span v-if="collapsable">
|
||||
<a href="#" @click.prevent="$emit('toggleExpanded')">{{ $t('timeline.collapse') }}</a>
|
||||
</span>
|
||||
</div>
|
||||
<div class="panel-body">
|
||||
|
|
|
@ -0,0 +1,14 @@
|
|||
const FeaturesPanel = {
|
||||
computed: {
|
||||
chat: function () {
|
||||
return this.$store.state.config.chatAvailable && (!this.$store.state.chatDisabled)
|
||||
},
|
||||
gopher: function () { return this.$store.state.config.gopherAvailable },
|
||||
whoToFollow: function () { return this.$store.state.config.suggestionsEnabled },
|
||||
mediaProxy: function () { return this.$store.state.config.mediaProxyAvailable },
|
||||
scopeOptions: function () { return this.$store.state.config.scopeOptionsEnabled },
|
||||
textlimit: function () { return this.$store.state.config.textlimit }
|
||||
}
|
||||
}
|
||||
|
||||
export default FeaturesPanel
|
|
@ -0,0 +1,29 @@
|
|||
<template>
|
||||
<div class="features-panel">
|
||||
<div class="panel panel-default base01-background">
|
||||
<div class="panel-heading timeline-heading base02-background base04">
|
||||
<div class="title">
|
||||
{{$t('features_panel.title')}}
|
||||
</div>
|
||||
</div>
|
||||
<div class="panel-body features-panel">
|
||||
<ul>
|
||||
<li v-if="chat">{{$t('features_panel.chat')}}</li>
|
||||
<li v-if="gopher">{{$t('features_panel.gopher')}}</li>
|
||||
<li v-if="whoToFollow">{{$t('features_panel.who_to_follow')}}</li>
|
||||
<li v-if="mediaProxy">{{$t('features_panel.media_proxy')}}</li>
|
||||
<li v-if="scopeOptions">{{$t('features_panel.scope_options')}}</li>
|
||||
<li>{{$t('features_panel.text_limit')}} = {{textlimit}}</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script src="./features_panel.js" ></script>
|
||||
|
||||
<style lang="scss">
|
||||
.features-panel li {
|
||||
line-height: 24px;
|
||||
}
|
||||
</style>
|
|
@ -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>
|
|
@ -6,8 +6,10 @@ const mediaUpload = {
|
|||
const input = this.$el.querySelector('input')
|
||||
|
||||
input.addEventListener('change', ({target}) => {
|
||||
const file = target.files[0]
|
||||
this.uploadFile(file)
|
||||
for (var i = 0; i < target.files.length; i++) {
|
||||
let file = target.files[i]
|
||||
this.uploadFile(file)
|
||||
}
|
||||
})
|
||||
},
|
||||
data () {
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
<label class="btn btn-default">
|
||||
<i class="icon-spin4 animate-spin" v-if="uploading"></i>
|
||||
<i class="icon-upload" v-if="!uploading"></i>
|
||||
<input type=file style="position: fixed; top: -100em"></input>
|
||||
<input type="file" style="position: fixed; top: -100em" multiple="true"></input>
|
||||
</label>
|
||||
</div>
|
||||
</template>
|
||||
|
|
|
@ -12,7 +12,7 @@
|
|||
<div class="name-and-action">
|
||||
<span class="username" v-if="!!notification.action.user.name_html" :title="'@'+notification.action.user.screen_name" v-html="notification.action.user.name_html"></span>
|
||||
<span class="username" v-else :title="'@'+notification.action.user.screen_name">{{ notification.action.user.name }}</span>
|
||||
<span v-if="notification.type === 'favorite'">
|
||||
<span v-if="notification.type === 'like'">
|
||||
<i class="fa icon-star lit"></i>
|
||||
<small>{{$t('notifications.favorited_you')}}</small>
|
||||
</span>
|
||||
|
@ -25,12 +25,17 @@
|
|||
<small>{{$t('notifications.followed_you')}}</small>
|
||||
</span>
|
||||
</div>
|
||||
<small class="timeago"><router-link :to="{ name: 'conversation', params: { id: notification.status.id } }"><timeago :since="notification.action.created_at" :auto-update="240"></timeago></router-link></small>
|
||||
<small class="timeago"><router-link v-if="notification.status" :to="{ name: 'conversation', params: { id: notification.status.id } }"><timeago :since="notification.action.created_at" :auto-update="240"></timeago></router-link></small>
|
||||
</span>
|
||||
<div class="follow-text" v-if="notification.type === 'follow'">
|
||||
<router-link :to="{ name: 'user-profile', params: { id: notification.action.user.id } }">@{{notification.action.user.screen_name}}</router-link>
|
||||
</div>
|
||||
<status v-else class="faint" :compact="true" :statusoid="notification.status" :noHeading="true"></status>
|
||||
<template v-else>
|
||||
<status v-if="notification.status" class="faint" :compact="true" :statusoid="notification.status" :noHeading="true"></status>
|
||||
<div class="broken-favorite" v-else>
|
||||
{{$t('notifications.broken_favorite')}}
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
|
|
@ -1,25 +1,38 @@
|
|||
import Notification from '../notification/notification.vue'
|
||||
import notificationsFetcher from '../../services/notifications_fetcher/notifications_fetcher.service.js'
|
||||
|
||||
import { sortBy, take, filter } from 'lodash'
|
||||
import { sortBy, filter } from 'lodash'
|
||||
|
||||
const Notifications = {
|
||||
data () {
|
||||
return {
|
||||
visibleNotificationCount: 20
|
||||
}
|
||||
created () {
|
||||
const store = this.$store
|
||||
const credentials = store.state.users.currentUser.credentials
|
||||
|
||||
notificationsFetcher.startFetching({ store, credentials })
|
||||
},
|
||||
computed: {
|
||||
visibleTypes () {
|
||||
return [
|
||||
this.$store.state.config.notificationVisibility.likes && 'like',
|
||||
this.$store.state.config.notificationVisibility.mentions && 'mention',
|
||||
this.$store.state.config.notificationVisibility.repeats && 'repeat',
|
||||
this.$store.state.config.notificationVisibility.follows && 'follow'
|
||||
].filter(_ => _)
|
||||
},
|
||||
notifications () {
|
||||
return this.$store.state.statuses.notifications
|
||||
return this.$store.state.statuses.notifications.data
|
||||
},
|
||||
error () {
|
||||
return this.$store.state.statuses.notifications.error
|
||||
},
|
||||
unseenNotifications () {
|
||||
return filter(this.notifications, ({seen}) => !seen)
|
||||
return filter(this.visibleNotifications, ({seen}) => !seen)
|
||||
},
|
||||
visibleNotifications () {
|
||||
// Don't know why, but sortBy([seen, -action.id]) doesn't work.
|
||||
let sortedNotifications = sortBy(this.notifications, ({action}) => -action.id)
|
||||
sortedNotifications = sortBy(sortedNotifications, 'seen')
|
||||
return take(sortedNotifications, this.visibleNotificationCount)
|
||||
return sortedNotifications.filter((notification) => this.visibleTypes.includes(notification.type))
|
||||
},
|
||||
unseenCount () {
|
||||
return this.unseenNotifications.length
|
||||
|
@ -40,6 +53,15 @@ const Notifications = {
|
|||
methods: {
|
||||
markAsSeen () {
|
||||
this.$store.commit('markNotificationsAsSeen', this.visibleNotifications)
|
||||
},
|
||||
fetchOlderNotifications () {
|
||||
const store = this.$store
|
||||
const credentials = store.state.users.currentUser.credentials
|
||||
notificationsFetcher.fetchAndUpdate({
|
||||
store,
|
||||
credentials,
|
||||
older: true
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,44 +4,26 @@
|
|||
// a bit of a hack to allow scrolling below notifications
|
||||
padding-bottom: 15em;
|
||||
|
||||
.panel {
|
||||
background: $fallback--bg;
|
||||
background: var(--bg, $fallback--bg)
|
||||
}
|
||||
|
||||
.panel-body {
|
||||
border-color: $fallback--border;
|
||||
border-color: var(--border, $fallback--border)
|
||||
}
|
||||
|
||||
.panel-heading {
|
||||
// force the text to stay centered, while keeping
|
||||
// the button in the right side of the panel heading
|
||||
position: relative;
|
||||
background: $fallback--btn;
|
||||
background: var(--btn, $fallback--btn);
|
||||
color: $fallback--fg;
|
||||
color: var(--fg, $fallback--fg);
|
||||
.read-button {
|
||||
position: absolute;
|
||||
right: 0.7em;
|
||||
height: 1.8em;
|
||||
line-height: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
.unseen-count {
|
||||
display: inline-block;
|
||||
background-color: $fallback--cRed;
|
||||
background-color: var(--cRed, $fallback--cRed);
|
||||
text-shadow: 0px 0px 3px rgba(0, 0, 0, 0.5);
|
||||
min-width: 1.3em;
|
||||
border-radius: 1.3em;
|
||||
margin: 0 0.2em 0 -0.4em;
|
||||
border-radius: 99px;
|
||||
min-width: 22px;
|
||||
max-width: 22px;
|
||||
min-height: 22px;
|
||||
max-height: 22px;
|
||||
color: white;
|
||||
font-size: 0.9em;
|
||||
font-size: 15px;
|
||||
line-height: 22px;
|
||||
text-align: center;
|
||||
line-height: 1.3em;
|
||||
vertical-align: middle
|
||||
}
|
||||
|
||||
.loadmore-error {
|
||||
color: $fallback--fg;
|
||||
color: var(--fg, $fallback--fg);
|
||||
}
|
||||
|
||||
.unseen {
|
||||
|
@ -54,7 +36,18 @@
|
|||
box-sizing: border-box;
|
||||
display: flex;
|
||||
border-bottom: 1px solid;
|
||||
border-bottom-color: inherit;
|
||||
border-color: $fallback--border;
|
||||
border-color: var(--border, $fallback--border);
|
||||
|
||||
.broken-favorite {
|
||||
border-radius: $fallback--tooltipRadius;
|
||||
border-radius: var(--tooltipRadius, $fallback--tooltipRadius);
|
||||
color: $fallback--faint;
|
||||
color: var(--faint, $fallback--faint);
|
||||
background-color: $fallback--cAlertRed;
|
||||
background-color: var(--cAlertRed, $fallback--cAlertRed);
|
||||
padding: 2px .5em
|
||||
}
|
||||
|
||||
.avatar-compact {
|
||||
width: 32px;
|
||||
|
@ -69,7 +62,7 @@
|
|||
}
|
||||
}
|
||||
|
||||
&:hover .animated.avatar {
|
||||
&:hover .animated.avatar-compact {
|
||||
canvas {
|
||||
display: none;
|
||||
}
|
||||
|
@ -145,6 +138,13 @@
|
|||
max-width: 100%;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
|
||||
img {
|
||||
width: 14px;
|
||||
height: 14px;
|
||||
vertical-align: middle;
|
||||
object-fit: contain
|
||||
}
|
||||
}
|
||||
.timeago {
|
||||
float: right;
|
||||
|
@ -194,15 +194,4 @@
|
|||
margin-bottom: 0.3em;
|
||||
}
|
||||
}
|
||||
|
||||
// ugly as heck
|
||||
&:last-child {
|
||||
border-bottom: none;
|
||||
border-radius: 0 0 $fallback--panelRadius $fallback--panelRadius;
|
||||
border-radius: 0 0 var(--panelRadius, $fallback--panelRadius) var(--panelRadius, $fallback--panelRadius);
|
||||
.status-el {
|
||||
border-radius: 0 0 $fallback--panelRadius $fallback--panelRadius;
|
||||
border-radius: 0 0 var(--panelRadius, $fallback--panelRadius) var(--panelRadius, $fallback--panelRadius);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,8 +2,13 @@
|
|||
<div class="notifications">
|
||||
<div class="panel panel-default">
|
||||
<div class="panel-heading">
|
||||
<span class="unseen-count" v-if="unseenCount">{{unseenCount}}</span>
|
||||
{{$t('notifications.notifications')}}
|
||||
<div class="title">
|
||||
{{$t('notifications.notifications')}}
|
||||
<span class="unseen-count" v-if="unseenCount">{{unseenCount}}</span>
|
||||
</div>
|
||||
<div @click.prevent class="loadmore-error alert error" v-if="error">
|
||||
{{$t('timeline.error_fetching')}}
|
||||
</div>
|
||||
<button v-if="unseenCount" @click.prevent="markAsSeen" class="read-button">{{$t('notifications.read')}}</button>
|
||||
</div>
|
||||
<div class="panel-body">
|
||||
|
@ -11,6 +16,12 @@
|
|||
<notification :notification="notification"></notification>
|
||||
</div>
|
||||
</div>
|
||||
<div class="panel-footer">
|
||||
<a href="#" v-on:click.prevent='fetchOlderNotifications()' v-if="!notifications.loading">
|
||||
<div class="new-status-notification text-center panel-footer">{{$t('notifications.load_older')}}</div>
|
||||
</a>
|
||||
<div class="new-status-notification text-center panel-footer" v-else>...</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
|
|
@ -24,7 +24,8 @@ const PostStatusForm = {
|
|||
'replyTo',
|
||||
'repliedUser',
|
||||
'attentions',
|
||||
'messageScope'
|
||||
'messageScope',
|
||||
'subject'
|
||||
],
|
||||
components: {
|
||||
MediaUpload
|
||||
|
@ -52,7 +53,10 @@ const PostStatusForm = {
|
|||
posting: false,
|
||||
highlighted: 0,
|
||||
newStatus: {
|
||||
spoilerText: this.subject,
|
||||
status: statusText,
|
||||
contentType: 'text/plain',
|
||||
nsfw: false,
|
||||
files: [],
|
||||
visibility: this.messageScope || this.$store.state.users.currentUser.default_scope
|
||||
},
|
||||
|
@ -71,8 +75,11 @@ const PostStatusForm = {
|
|||
candidates () {
|
||||
const firstchar = this.textAtCaret.charAt(0)
|
||||
if (firstchar === '@') {
|
||||
const matchedUsers = filter(this.users, (user) => (String(user.name + user.screen_name)).toUpperCase()
|
||||
.match(this.textAtCaret.slice(1).toUpperCase()))
|
||||
const query = this.textAtCaret.slice(1).toUpperCase()
|
||||
const matchedUsers = filter(this.users, (user) => {
|
||||
return user.screen_name.toUpperCase().startsWith(query) ||
|
||||
user.name && user.name.toUpperCase().startsWith(query)
|
||||
})
|
||||
if (matchedUsers.length <= 0) {
|
||||
return false
|
||||
}
|
||||
|
@ -86,7 +93,7 @@ const PostStatusForm = {
|
|||
}))
|
||||
} else if (firstchar === ':') {
|
||||
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) {
|
||||
return false
|
||||
}
|
||||
|
@ -135,6 +142,9 @@ const PostStatusForm = {
|
|||
},
|
||||
scopeOptionsEnabled () {
|
||||
return this.$store.state.config.scopeOptionsEnabled
|
||||
},
|
||||
formattingOptionsEnabled () {
|
||||
return this.$store.state.config.formattingOptionsEnabled
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
|
@ -204,15 +214,18 @@ const PostStatusForm = {
|
|||
status: newStatus.status,
|
||||
spoilerText: newStatus.spoilerText || null,
|
||||
visibility: newStatus.visibility,
|
||||
sensitive: newStatus.nsfw,
|
||||
media: newStatus.files,
|
||||
store: this.$store,
|
||||
inReplyToStatusId: this.replyTo
|
||||
inReplyToStatusId: this.replyTo,
|
||||
contentType: newStatus.contentType
|
||||
}).then((data) => {
|
||||
if (!data.error) {
|
||||
this.newStatus = {
|
||||
status: '',
|
||||
files: [],
|
||||
visibility: newStatus.visibility
|
||||
visibility: newStatus.visibility,
|
||||
contentType: newStatus.contentType
|
||||
}
|
||||
this.$emit('posted')
|
||||
let el = this.$el.querySelector('textarea')
|
||||
|
|
|
@ -32,11 +32,24 @@
|
|||
@input="resize"
|
||||
@paste="paste">
|
||||
</textarea>
|
||||
<div v-if="scopeOptionsEnabled" class="visibility-tray">
|
||||
<i v-on:click="changeVis('direct')" class="icon-mail-alt" :class="vis.direct" :title="$t('post_status.scope.direct')"></i>
|
||||
<i v-on:click="changeVis('private')" class="icon-lock" :class="vis.private" :title="$t('post_status.scope.private')"></i>
|
||||
<i v-on:click="changeVis('unlisted')" class="icon-lock-open-alt" :class="vis.unlisted" :title="$t('post_status.scope.unlisted')"></i>
|
||||
<i v-on:click="changeVis('public')" class="icon-globe" :class="vis.public" :title="$t('post_status.scope.public')"></i>
|
||||
<div class="visibility-tray">
|
||||
<span class="text-format" v-if="formattingOptionsEnabled">
|
||||
<label for="post-content-type" class="select">
|
||||
<select id="post-content-type" v-model="newStatus.contentType" class="form-control">
|
||||
<option value="text/plain">{{$t('post_status.content_type.plain_text')}}</option>
|
||||
<option value="text/html">HTML</option>
|
||||
<option value="text/markdown">Markdown</option>
|
||||
</select>
|
||||
<i class="icon-down-open"></i>
|
||||
</label>
|
||||
</span>
|
||||
|
||||
<div v-if="scopeOptionsEnabled">
|
||||
<i v-on:click="changeVis('direct')" class="icon-mail-alt" :class="vis.direct" :title="$t('post_status.scope.direct')"></i>
|
||||
<i v-on:click="changeVis('private')" class="icon-lock" :class="vis.private" :title="$t('post_status.scope.private')"></i>
|
||||
<i v-on:click="changeVis('unlisted')" class="icon-lock-open-alt" :class="vis.unlisted" :title="$t('post_status.scope.unlisted')"></i>
|
||||
<i v-on:click="changeVis('public')" class="icon-globe" :class="vis.public" :title="$t('post_status.scope.public')"></i>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div style="position:relative;" v-if="candidates">
|
||||
|
@ -75,6 +88,10 @@
|
|||
</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">{{$t('post_status.attachments_sensitive')}}</label>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</template>
|
||||
|
|
|
@ -1,7 +1,12 @@
|
|||
<template>
|
||||
<div v-if="loggedIn && visibility !== 'private' && visibility !== 'direct'">
|
||||
<i :class='classes' class='icon-retweet rt-active' v-on:click.prevent='retweet()'></i>
|
||||
<span v-if='status.repeat_num > 0'>{{status.repeat_num}}</span>
|
||||
<div v-if="loggedIn">
|
||||
<template v-if="visibility !== 'private' && visibility !== 'direct'">
|
||||
<i :class='classes' class='icon-retweet rt-active' v-on:click.prevent='retweet()'></i>
|
||||
<span v-if='status.repeat_num > 0'>{{status.repeat_num}}</span>
|
||||
</template>
|
||||
<template v-else>
|
||||
<i :class='classes' class='icon-lock' :title="$t('timeline.no_retweet_hint')"></i>
|
||||
</template>
|
||||
</div>
|
||||
<div v-else-if="!loggedIn">
|
||||
<i :class='classes' class='icon-retweet'></i>
|
||||
|
|
|
@ -1,22 +1,30 @@
|
|||
/* eslint-env browser */
|
||||
import TabSwitcher from '../tab_switcher/tab_switcher.jsx'
|
||||
import StyleSwitcher from '../style_switcher/style_switcher.vue'
|
||||
import InterfaceLanguageSwitcher from '../interface_language_switcher/interface_language_switcher.vue'
|
||||
import { filter, trim } from 'lodash'
|
||||
|
||||
const settings = {
|
||||
data () {
|
||||
const config = this.$store.state.config
|
||||
|
||||
return {
|
||||
hideAttachmentsLocal: this.$store.state.config.hideAttachments,
|
||||
hideAttachmentsInConvLocal: this.$store.state.config.hideAttachmentsInConv,
|
||||
hideNsfwLocal: this.$store.state.config.hideNsfw,
|
||||
loopVideoLocal: this.$store.state.config.loopVideo,
|
||||
loopVideoSilentOnlyLocal: this.$store.state.config.loopVideoSilentOnly,
|
||||
muteWordsString: this.$store.state.config.muteWords.join('\n'),
|
||||
autoLoadLocal: this.$store.state.config.autoLoad,
|
||||
streamingLocal: this.$store.state.config.streaming,
|
||||
pauseOnUnfocusedLocal: this.$store.state.config.pauseOnUnfocused,
|
||||
hoverPreviewLocal: this.$store.state.config.hoverPreview,
|
||||
collapseMessageWithSubjectLocal: this.$store.state.config.collapseMessageWithSubject,
|
||||
stopGifs: this.$store.state.config.stopGifs,
|
||||
hideAttachmentsLocal: config.hideAttachments,
|
||||
hideAttachmentsInConvLocal: config.hideAttachmentsInConv,
|
||||
hideNsfwLocal: config.hideNsfw,
|
||||
notificationVisibilityLocal: config.notificationVisibility,
|
||||
replyVisibilityLocal: config.replyVisibility,
|
||||
loopVideoLocal: config.loopVideo,
|
||||
loopVideoSilentOnlyLocal: config.loopVideoSilentOnly,
|
||||
muteWordsString: config.muteWords.join('\n'),
|
||||
autoLoadLocal: config.autoLoad,
|
||||
streamingLocal: config.streaming,
|
||||
pauseOnUnfocusedLocal: config.pauseOnUnfocused,
|
||||
hoverPreviewLocal: config.hoverPreview,
|
||||
collapseMessageWithSubjectLocal: typeof config.collapseMessageWithSubject === 'undefined'
|
||||
? config.defaultCollapseMessageWithSubject
|
||||
: config.collapseMessageWithSubject,
|
||||
stopGifs: config.stopGifs,
|
||||
loopSilentAvailable:
|
||||
// Firefox
|
||||
Object.getOwnPropertyDescriptor(HTMLVideoElement.prototype, 'mozHasAudio') ||
|
||||
|
@ -27,7 +35,9 @@ const settings = {
|
|||
}
|
||||
},
|
||||
components: {
|
||||
StyleSwitcher
|
||||
TabSwitcher,
|
||||
StyleSwitcher,
|
||||
InterfaceLanguageSwitcher
|
||||
},
|
||||
computed: {
|
||||
user () {
|
||||
|
@ -44,6 +54,21 @@ const settings = {
|
|||
hideNsfwLocal (value) {
|
||||
this.$store.dispatch('setOption', { name: 'hideNsfw', value })
|
||||
},
|
||||
'notificationVisibilityLocal.likes' (value) {
|
||||
this.$store.dispatch('setOption', { name: 'notificationVisibility', value: this.$store.state.config.notificationVisibility })
|
||||
},
|
||||
'notificationVisibilityLocal.follows' (value) {
|
||||
this.$store.dispatch('setOption', { name: 'notificationVisibility', value: this.$store.state.config.notificationVisibility })
|
||||
},
|
||||
'notificationVisibilityLocal.repeats' (value) {
|
||||
this.$store.dispatch('setOption', { name: 'notificationVisibility', value: this.$store.state.config.notificationVisibility })
|
||||
},
|
||||
'notificationVisibilityLocal.mentions' (value) {
|
||||
this.$store.dispatch('setOption', { name: 'notificationVisibility', value: this.$store.state.config.notificationVisibility })
|
||||
},
|
||||
replyVisibilityLocal (value) {
|
||||
this.$store.dispatch('setOption', { name: 'replyVisibility', value })
|
||||
},
|
||||
loopVideoLocal (value) {
|
||||
this.$store.dispatch('setOption', { name: 'loopVideo', value })
|
||||
},
|
||||
|
|
|
@ -4,76 +4,132 @@
|
|||
{{$t('settings.settings')}}
|
||||
</div>
|
||||
<div class="panel-body">
|
||||
<div class="setting-item">
|
||||
<h2>{{$t('settings.theme')}}</h2>
|
||||
<style-switcher></style-switcher>
|
||||
</div>
|
||||
<div class="setting-item">
|
||||
<h2>{{$t('settings.filtering')}}</h2>
|
||||
<p>{{$t('settings.filtering_explanation')}}</p>
|
||||
<textarea id="muteWords" v-model="muteWordsString"></textarea>
|
||||
</div>
|
||||
<div class="setting-item">
|
||||
<h2>{{$t('nav.timeline')}}</h2>
|
||||
<ul class="setting-list">
|
||||
<li>
|
||||
<input type="checkbox" id="collapseMessageWithSubject" v-model="collapseMessageWithSubjectLocal">
|
||||
<label for="collapseMessageWithSubject">{{$t('settings.collapse_subject')}}</label>
|
||||
</li>
|
||||
<li>
|
||||
<input type="checkbox" id="streaming" v-model="streamingLocal">
|
||||
<label for="streaming">{{$t('settings.streaming')}}</label>
|
||||
<ul class="setting-list suboptions" :class="[{disabled: !streamingLocal}]">
|
||||
<tab-switcher>
|
||||
<div :label="$t('settings.general')" >
|
||||
<div class="setting-item">
|
||||
<h2>{{ $t('settings.interfaceLanguage') }}</h2>
|
||||
<interface-language-switcher />
|
||||
</div>
|
||||
<div class="setting-item">
|
||||
<h2>{{$t('nav.timeline')}}</h2>
|
||||
<ul class="setting-list">
|
||||
<li>
|
||||
<input :disabled="!streamingLocal" type="checkbox" id="pauseOnUnfocused" v-model="pauseOnUnfocusedLocal">
|
||||
<label for="pauseOnUnfocused">{{$t('settings.pause_on_unfocused')}}</label>
|
||||
<input type="checkbox" id="collapseMessageWithSubject" v-model="collapseMessageWithSubjectLocal">
|
||||
<label for="collapseMessageWithSubject">{{$t('settings.collapse_subject')}}</label>
|
||||
</li>
|
||||
<li>
|
||||
<input type="checkbox" id="streaming" v-model="streamingLocal">
|
||||
<label for="streaming">{{$t('settings.streaming')}}</label>
|
||||
<ul class="setting-list suboptions" :class="[{disabled: !streamingLocal}]">
|
||||
<li>
|
||||
<input :disabled="!streamingLocal" type="checkbox" id="pauseOnUnfocused" v-model="pauseOnUnfocusedLocal">
|
||||
<label for="pauseOnUnfocused">{{$t('settings.pause_on_unfocused')}}</label>
|
||||
</li>
|
||||
</ul>
|
||||
</li>
|
||||
<li>
|
||||
<input type="checkbox" id="autoload" v-model="autoLoadLocal">
|
||||
<label for="autoload">{{$t('settings.autoload')}}</label>
|
||||
</li>
|
||||
<li>
|
||||
<input type="checkbox" id="hoverPreview" v-model="hoverPreviewLocal">
|
||||
<label for="hoverPreview">{{$t('settings.reply_link_preview')}}</label>
|
||||
</li>
|
||||
</ul>
|
||||
</li>
|
||||
<li>
|
||||
<input type="checkbox" id="autoload" v-model="autoLoadLocal">
|
||||
<label for="autoload">{{$t('settings.autoload')}}</label>
|
||||
</li>
|
||||
<li>
|
||||
<input type="checkbox" id="hoverPreview" v-model="hoverPreviewLocal">
|
||||
<label for="hoverPreview">{{$t('settings.reply_link_preview')}}</label>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="setting-item">
|
||||
<h2>{{$t('settings.attachments')}}</h2>
|
||||
<ul class="setting-list">
|
||||
<li>
|
||||
<input type="checkbox" id="hideAttachments" v-model="hideAttachmentsLocal">
|
||||
<label for="hideAttachments">{{$t('settings.hide_attachments_in_tl')}}</label>
|
||||
</li>
|
||||
<li>
|
||||
<input type="checkbox" id="hideAttachmentsInConv" v-model="hideAttachmentsInConvLocal">
|
||||
<label for="hideAttachmentsInConv">{{$t('settings.hide_attachments_in_convo')}}</label>
|
||||
</li>
|
||||
<li>
|
||||
<input type="checkbox" id="hideNsfw" v-model="hideNsfwLocal">
|
||||
<label for="hideNsfw">{{$t('settings.nsfw_clickthrough')}}</label>
|
||||
</li>
|
||||
<li>
|
||||
<input type="checkbox" id="stopGifs" v-model="stopGifs">
|
||||
<label for="stopGifs">{{$t('settings.stop_gifs')}}</label>
|
||||
</li>
|
||||
<li>
|
||||
<input type="checkbox" id="loopVideo" v-model="loopVideoLocal">
|
||||
<label for="loopVideo">{{$t('settings.loop_video')}}</label>
|
||||
<ul class="setting-list suboptions" :class="[{disabled: !streamingLocal}]">
|
||||
</div>
|
||||
<div class="setting-item">
|
||||
<h2>{{$t('settings.attachments')}}</h2>
|
||||
<ul class="setting-list">
|
||||
<li>
|
||||
<input :disabled="!loopVideoLocal || !loopSilentAvailable" type="checkbox" id="loopVideoSilentOnly" v-model="loopVideoSilentOnlyLocal">
|
||||
<label for="loopVideoSilentOnly">{{$t('settings.loop_video_silent_only')}}</label>
|
||||
<div v-if="!loopSilentAvailable" class="unavailable">
|
||||
<i class="icon-globe"/>! {{$t('settings.limited_availability')}}
|
||||
</div>
|
||||
<input type="checkbox" id="hideAttachments" v-model="hideAttachmentsLocal">
|
||||
<label for="hideAttachments">{{$t('settings.hide_attachments_in_tl')}}</label>
|
||||
</li>
|
||||
<li>
|
||||
<input type="checkbox" id="hideAttachmentsInConv" v-model="hideAttachmentsInConvLocal">
|
||||
<label for="hideAttachmentsInConv">{{$t('settings.hide_attachments_in_convo')}}</label>
|
||||
</li>
|
||||
<li>
|
||||
<input type="checkbox" id="hideNsfw" v-model="hideNsfwLocal">
|
||||
<label for="hideNsfw">{{$t('settings.nsfw_clickthrough')}}</label>
|
||||
</li>
|
||||
<li>
|
||||
<input type="checkbox" id="stopGifs" v-model="stopGifs">
|
||||
<label for="stopGifs">{{$t('settings.stop_gifs')}}</label>
|
||||
</li>
|
||||
<li>
|
||||
<input type="checkbox" id="loopVideo" v-model="loopVideoLocal">
|
||||
<label for="loopVideo">{{$t('settings.loop_video')}}</label>
|
||||
<ul class="setting-list suboptions" :class="[{disabled: !streamingLocal}]">
|
||||
<li>
|
||||
<input :disabled="!loopVideoLocal || !loopSilentAvailable" type="checkbox" id="loopVideoSilentOnly" v-model="loopVideoSilentOnlyLocal">
|
||||
<label for="loopVideoSilentOnly">{{$t('settings.loop_video_silent_only')}}</label>
|
||||
<div v-if="!loopSilentAvailable" class="unavailable">
|
||||
<i class="icon-globe"/>! {{$t('settings.limited_availability')}}
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
</li>
|
||||
</ul>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div :label="$t('settings.theme')" >
|
||||
<div class="setting-item">
|
||||
<style-switcher></style-switcher>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div :label="$t('settings.filtering')" >
|
||||
<div class="setting-item">
|
||||
<div class="select-multiple">
|
||||
<span class="label">{{$t('settings.notification_visibility')}}</span>
|
||||
<ul class="option-list">
|
||||
<li>
|
||||
<input type="checkbox" id="notification-visibility-likes" v-model="notificationVisibilityLocal.likes">
|
||||
<label for="notification-visibility-likes">
|
||||
{{$t('settings.notification_visibility_likes')}}
|
||||
</label>
|
||||
</li>
|
||||
<li>
|
||||
<input type="checkbox" id="notification-visibility-repeats" v-model="notificationVisibilityLocal.repeats">
|
||||
<label for="notification-visibility-repeats">
|
||||
{{$t('settings.notification_visibility_repeats')}}
|
||||
</label>
|
||||
</li>
|
||||
<li>
|
||||
<input type="checkbox" id="notification-visibility-follows" v-model="notificationVisibilityLocal.follows">
|
||||
<label for="notification-visibility-follows">
|
||||
{{$t('settings.notification_visibility_follows')}}
|
||||
</label>
|
||||
</li>
|
||||
<li>
|
||||
<input type="checkbox" id="notification-visibility-mentions" v-model="notificationVisibilityLocal.mentions">
|
||||
<label for="notification-visibility-mentions">
|
||||
{{$t('settings.notification_visibility_mentions')}}
|
||||
</label>
|
||||
</li>
|
||||
</ul>
|
||||
</label>
|
||||
</div>
|
||||
<div>
|
||||
{{$t('settings.replies_in_timeline')}}
|
||||
<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>
|
||||
</div>
|
||||
</div>
|
||||
<div class="setting-item">
|
||||
<p>{{$t('settings.filtering_explanation')}}</p>
|
||||
<textarea id="muteWords" v-model="muteWordsString"></textarea>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</tab-switcher>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
@ -89,6 +145,23 @@
|
|||
margin: 1em 1em 1.4em;
|
||||
padding-bottom: 1.4em;
|
||||
|
||||
> div {
|
||||
margin-bottom: .5em;
|
||||
&:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
}
|
||||
|
||||
&:last-child {
|
||||
border-bottom: none;
|
||||
padding-bottom: 0;
|
||||
margin-bottom: 1em;
|
||||
}
|
||||
|
||||
select {
|
||||
min-width: 10em;
|
||||
}
|
||||
|
||||
|
||||
textarea {
|
||||
width: 100%;
|
||||
|
@ -116,12 +189,24 @@
|
|||
}
|
||||
|
||||
.btn {
|
||||
margin-top: 1em;
|
||||
min-height: 28px;
|
||||
}
|
||||
|
||||
.submit {
|
||||
margin-top: 1em;
|
||||
min-height: 30px;
|
||||
width: 10em;
|
||||
}
|
||||
}
|
||||
.setting-list {
|
||||
.select-multiple {
|
||||
display: flex;
|
||||
.option-list {
|
||||
margin: 0;
|
||||
padding-left: .5em;
|
||||
}
|
||||
}
|
||||
.setting-list,
|
||||
.option-list{
|
||||
list-style-type: none;
|
||||
padding-left: 2em;
|
||||
li {
|
||||
|
|
|
@ -83,7 +83,6 @@ const Status = {
|
|||
return hits
|
||||
},
|
||||
muted () { return !this.unmuted && (this.status.user.muted || this.muteWordHits.length > 0) },
|
||||
isReply () { return !!this.status.in_reply_to_status_id },
|
||||
isFocused () {
|
||||
// retweet or root of an expanded conversation
|
||||
if (this.focused) {
|
||||
|
@ -105,6 +104,48 @@ const Status = {
|
|||
const lengthScore = this.status.statusnet_html.split(/<p|<br/).length + this.status.text.length / 80
|
||||
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 () {
|
||||
if (this.tallStatus && !this.$store.state.config.collapseMessageWithSubject) {
|
||||
return false
|
||||
|
@ -123,6 +164,21 @@ const Status = {
|
|||
showingMore () {
|
||||
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 () {
|
||||
if ((this.$store.state.config.hideAttachments && !this.inConversation) ||
|
||||
(this.$store.state.config.hideAttachmentsInConv && this.inConversation)) {
|
||||
|
@ -226,6 +282,11 @@ const Status = {
|
|||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
filters: {
|
||||
capitalize: function (str) {
|
||||
return str.charAt(0).toUpperCase() + str.slice(1)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
<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">
|
||||
<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>
|
||||
|
@ -58,11 +58,15 @@
|
|||
<timeago :since="status.created_at" :auto-update="60"></timeago>
|
||||
</router-link>
|
||||
<div class="visibility-icon" v-if="status.visibility">
|
||||
<i :class="visibilityIcon(status.visibility)"></i>
|
||||
<i :class="visibilityIcon(status.visibility)" :title="status.visibility | capitalize"></i>
|
||||
</div>
|
||||
<a :href="status.external_url" target="_blank" v-if="!status.is_local" class="source_url"><i class="icon-link-ext-alt"></i></a>
|
||||
<a :href="status.external_url" target="_blank" v-if="!status.is_local" class="source_url" title="Source">
|
||||
<i class="icon-link-ext-alt"></i>
|
||||
</a>
|
||||
<template v-if="expandable">
|
||||
<a href="#" @click.prevent="toggleExpanded"><i class="icon-plus-squared"></i></a>
|
||||
<a href="#" @click.prevent="toggleExpanded" title="Expand">
|
||||
<i class="icon-plus-squared"></i>
|
||||
</a>
|
||||
</template>
|
||||
<a href="#" @click.prevent="toggleMute" v-if="unmuted"><i class="icon-eye-off"></i></a>
|
||||
</div>
|
||||
|
@ -83,8 +87,8 @@
|
|||
<a v-if="showingMore" href="#" class="status-unhider" @click.prevent="toggleShowMore">Show less</a>
|
||||
</div>
|
||||
|
||||
<div v-if='status.attachments' 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">
|
||||
<div v-if='status.attachments && !hideSubjectStatus' class='attachments media-body'>
|
||||
<attachment :size="attachmentSize" :status-id="status.id" :nsfw="nsfwClickthrough" :attachment="attachment" v-for="attachment in status.attachments" :key="attachment.id">
|
||||
</attachment>
|
||||
</div>
|
||||
|
||||
|
@ -102,7 +106,7 @@
|
|||
</div>
|
||||
<div class="container" v-if="replying">
|
||||
<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>
|
||||
</template>
|
||||
</div>
|
||||
|
@ -331,11 +335,35 @@
|
|||
font-style: italic;
|
||||
}
|
||||
|
||||
pre {
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
p {
|
||||
margin: 0;
|
||||
margin-top: 0.2em;
|
||||
margin-bottom: 0.5em;
|
||||
}
|
||||
|
||||
h1 {
|
||||
font-size: 1.1em;
|
||||
line-height: 1.2em;
|
||||
margin: 1.4em 0;
|
||||
}
|
||||
|
||||
h2 {
|
||||
font-size: 1.1em;
|
||||
margin: 1.0em 0;
|
||||
}
|
||||
|
||||
h3 {
|
||||
font-size: 1em;
|
||||
margin: 1.2em 0;
|
||||
}
|
||||
|
||||
h4 {
|
||||
margin: 1.1em 0;
|
||||
}
|
||||
}
|
||||
|
||||
.retweet-info {
|
||||
|
|
|
@ -18,7 +18,11 @@ const StillImage = {
|
|||
onLoad () {
|
||||
const canvas = this.$refs.canvas
|
||||
if (!canvas) return
|
||||
canvas.getContext('2d').drawImage(this.$refs.src, 1, 1, canvas.width, canvas.height)
|
||||
const width = this.$refs.src.naturalWidth
|
||||
const height = this.$refs.src.naturalHeight
|
||||
canvas.width = width
|
||||
canvas.height = height
|
||||
canvas.getContext('2d').drawImage(this.$refs.src, 0, 0, width, height)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -23,6 +23,7 @@
|
|||
img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: contain;
|
||||
}
|
||||
|
||||
&.animated {
|
||||
|
@ -60,6 +61,7 @@
|
|||
right: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: contain;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -1,102 +1,30 @@
|
|||
<template>
|
||||
<div>
|
||||
<div>{{$t('settings.presets')}}
|
||||
<div>
|
||||
<div class="presets-container">
|
||||
<div>
|
||||
{{$t('settings.presets')}}
|
||||
<label for="style-switcher" class='select'>
|
||||
<select id="style-switcher" v-model="selected" class="style-switcher">
|
||||
<option v-for="style in availableStyles" :value="style" :style="{
|
||||
backgroundColor: style[1],
|
||||
color: style[3]
|
||||
}">{{style[0]}}</option>
|
||||
<option v-for="style in availableStyles"
|
||||
:value="style"
|
||||
:style="{
|
||||
backgroundColor: style[1],
|
||||
color: style[3]
|
||||
}">
|
||||
{{style[0]}}
|
||||
</option>
|
||||
</select>
|
||||
<i class="icon-down-open"/>
|
||||
</label>
|
||||
</div>
|
||||
<div>
|
||||
<div class="import-export">
|
||||
<button class="btn" @click="exportCurrentTheme">{{ $t('settings.export_theme') }}</button>
|
||||
<button class="btn" @click="importTheme">{{ $t('settings.import_theme') }}</button>
|
||||
<p v-if="invalidThemeImported" class="import-warning">{{ $t('settings.invalid_theme_imported') }}</p>
|
||||
</div>
|
||||
<div class="color-container">
|
||||
<p>{{$t('settings.theme_help')}}</p>
|
||||
<div class="color-item">
|
||||
<label for="bgcolor" class="theme-color-lb">{{$t('settings.background')}}</label>
|
||||
<input id="bgcolor" class="theme-color-cl" type="color" v-model="bgColorLocal">
|
||||
<input id="bgcolor-t" class="theme-color-in" type="text" v-model="bgColorLocal">
|
||||
</div>
|
||||
<div class="color-item">
|
||||
<label for="fgcolor" class="theme-color-lb">{{$t('settings.foreground')}}</label>
|
||||
<input id="fgcolor" class="theme-color-cl" type="color" v-model="btnColorLocal">
|
||||
<input id="fgcolor-t" class="theme-color-in" type="text" v-model="btnColorLocal">
|
||||
</div>
|
||||
<div class="color-item">
|
||||
<label for="textcolor" class="theme-color-lb">{{$t('settings.text')}}</label>
|
||||
<input id="textcolor" class="theme-color-cl" type="color" v-model="textColorLocal">
|
||||
<input id="textcolor-t" class="theme-color-in" type="text" v-model="textColorLocal">
|
||||
</div>
|
||||
<div class="color-item">
|
||||
<label for="linkcolor" class="theme-color-lb">{{$t('settings.links')}}</label>
|
||||
<input id="linkcolor" class="theme-color-cl" type="color" v-model="linkColorLocal">
|
||||
<input id="linkcolor-t" class="theme-color-in" type="text" v-model="linkColorLocal">
|
||||
</div>
|
||||
<div class="color-item">
|
||||
<label for="redcolor" class="theme-color-lb">{{$t('settings.cRed')}}</label>
|
||||
<input id="redcolor" class="theme-color-cl" type="color" v-model="redColorLocal">
|
||||
<input id="redcolor-t" class="theme-color-in" type="text" v-model="redColorLocal">
|
||||
</div>
|
||||
<div class="color-item">
|
||||
<label for="bluecolor" class="theme-color-lb">{{$t('settings.cBlue')}}</label>
|
||||
<input id="bluecolor" class="theme-color-cl" type="color" v-model="blueColorLocal">
|
||||
<input id="bluecolor-t" class="theme-color-in" type="text" v-model="blueColorLocal">
|
||||
</div>
|
||||
<div class="color-item">
|
||||
<label for="greencolor" class="theme-color-lb">{{$t('settings.cGreen')}}</label>
|
||||
<input id="greencolor" class="theme-color-cl" type="color" v-model="greenColorLocal">
|
||||
<input id="greencolor-t" class="theme-color-in" type="green" v-model="greenColorLocal">
|
||||
</div>
|
||||
<div class="color-item">
|
||||
<label for="orangecolor" class="theme-color-lb">{{$t('settings.cOrange')}}</label>
|
||||
<input id="orangecolor" class="theme-color-cl" type="color" v-model="orangeColorLocal">
|
||||
<input id="orangecolor-t" class="theme-color-in" type="text" v-model="orangeColorLocal">
|
||||
</div>
|
||||
</div>
|
||||
<div class="radius-container">
|
||||
<p>{{$t('settings.radii_help')}}</p>
|
||||
<div class="radius-item">
|
||||
<label for="btnradius" class="theme-radius-lb">{{$t('settings.btnRadius')}}</label>
|
||||
<input id="btnradius" class="theme-radius-rn" type="range" v-model="btnRadiusLocal" max="16">
|
||||
<input id="btnradius-t" class="theme-radius-in" type="text" v-model="btnRadiusLocal">
|
||||
</div>
|
||||
<div class="radius-item">
|
||||
<label for="inputradius" class="theme-radius-lb">{{$t('settings.inputRadius')}}</label>
|
||||
<input id="inputradius" class="theme-radius-rn" type="range" v-model="inputRadiusLocal" max="16">
|
||||
<input id="inputradius-t" class="theme-radius-in" type="text" v-model="inputRadiusLocal">
|
||||
</div>
|
||||
<div class="radius-item">
|
||||
<label for="panelradius" class="theme-radius-lb">{{$t('settings.panelRadius')}}</label>
|
||||
<input id="panelradius" class="theme-radius-rn" type="range" v-model="panelRadiusLocal" max="50">
|
||||
<input id="panelradius-t" class="theme-radius-in" type="text" v-model="panelRadiusLocal">
|
||||
</div>
|
||||
<div class="radius-item">
|
||||
<label for="avatarradius" class="theme-radius-lb">{{$t('settings.avatarRadius')}}</label>
|
||||
<input id="avatarradius" class="theme-radius-rn" type="range" v-model="avatarRadiusLocal" max="28">
|
||||
<input id="avatarradius-t" class="theme-radius-in" type="green" v-model="avatarRadiusLocal">
|
||||
</div>
|
||||
<div class="radius-item">
|
||||
<label for="avataraltradius" class="theme-radius-lb">{{$t('settings.avatarAltRadius')}}</label>
|
||||
<input id="avataraltradius" class="theme-radius-rn" type="range" v-model="avatarAltRadiusLocal" max="28">
|
||||
<input id="avataraltradius-t" class="theme-radius-in" type="text" v-model="avatarAltRadiusLocal">
|
||||
</div>
|
||||
<div class="radius-item">
|
||||
<label for="attachmentradius" class="theme-radius-lb">{{$t('settings.attachmentRadius')}}</label>
|
||||
<input id="attachmentrradius" class="theme-radius-rn" type="range" v-model="attachmentRadiusLocal" max="50">
|
||||
<input id="attachmentradius-t" class="theme-radius-in" type="text" v-model="attachmentRadiusLocal">
|
||||
</div>
|
||||
<div class="radius-item">
|
||||
<label for="tooltipradius" class="theme-radius-lb">{{$t('settings.tooltipRadius')}}</label>
|
||||
<input id="tooltipradius" class="theme-radius-rn" type="range" v-model="tooltipRadiusLocal" max="20">
|
||||
<input id="tooltipradius-t" class="theme-radius-in" type="text" v-model="tooltipRadiusLocal">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="preview-container">
|
||||
<div :style="{
|
||||
'--btnRadius': btnRadiusLocal + 'px',
|
||||
'--inputRadius': inputRadiusLocal + 'px',
|
||||
|
@ -127,8 +55,95 @@
|
|||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<button class="btn" @click="setCustomTheme">{{$t('general.apply')}}</button>
|
||||
</div>
|
||||
|
||||
<div class="color-container">
|
||||
<p>{{$t('settings.theme_help')}}</p>
|
||||
<div class="color-item">
|
||||
<label for="bgcolor" class="theme-color-lb">{{$t('settings.background')}}</label>
|
||||
<input id="bgcolor" class="theme-color-cl" type="color" v-model="bgColorLocal">
|
||||
<input id="bgcolor-t" class="theme-color-in" type="text" v-model="bgColorLocal">
|
||||
</div>
|
||||
<div class="color-item">
|
||||
<label for="fgcolor" class="theme-color-lb">{{$t('settings.foreground')}}</label>
|
||||
<input id="fgcolor" class="theme-color-cl" type="color" v-model="btnColorLocal">
|
||||
<input id="fgcolor-t" class="theme-color-in" type="text" v-model="btnColorLocal">
|
||||
</div>
|
||||
<div class="color-item">
|
||||
<label for="textcolor" class="theme-color-lb">{{$t('settings.text')}}</label>
|
||||
<input id="textcolor" class="theme-color-cl" type="color" v-model="textColorLocal">
|
||||
<input id="textcolor-t" class="theme-color-in" type="text" v-model="textColorLocal">
|
||||
</div>
|
||||
<div class="color-item">
|
||||
<label for="linkcolor" class="theme-color-lb">{{$t('settings.links')}}</label>
|
||||
<input id="linkcolor" class="theme-color-cl" type="color" v-model="linkColorLocal">
|
||||
<input id="linkcolor-t" class="theme-color-in" type="text" v-model="linkColorLocal">
|
||||
</div>
|
||||
<div class="color-item">
|
||||
<label for="redcolor" class="theme-color-lb">{{$t('settings.cRed')}}</label>
|
||||
<input id="redcolor" class="theme-color-cl" type="color" v-model="redColorLocal">
|
||||
<input id="redcolor-t" class="theme-color-in" type="text" v-model="redColorLocal">
|
||||
</div>
|
||||
<div class="color-item">
|
||||
<label for="bluecolor" class="theme-color-lb">{{$t('settings.cBlue')}}</label>
|
||||
<input id="bluecolor" class="theme-color-cl" type="color" v-model="blueColorLocal">
|
||||
<input id="bluecolor-t" class="theme-color-in" type="text" v-model="blueColorLocal">
|
||||
</div>
|
||||
<div class="color-item">
|
||||
<label for="greencolor" class="theme-color-lb">{{$t('settings.cGreen')}}</label>
|
||||
<input id="greencolor" class="theme-color-cl" type="color" v-model="greenColorLocal">
|
||||
<input id="greencolor-t" class="theme-color-in" type="green" v-model="greenColorLocal">
|
||||
</div>
|
||||
<div class="color-item">
|
||||
<label for="orangecolor" class="theme-color-lb">{{$t('settings.cOrange')}}</label>
|
||||
<input id="orangecolor" class="theme-color-cl" type="color" v-model="orangeColorLocal">
|
||||
<input id="orangecolor-t" class="theme-color-in" type="text" v-model="orangeColorLocal">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="radius-container">
|
||||
<p>{{$t('settings.radii_help')}}</p>
|
||||
<div class="radius-item">
|
||||
<label for="btnradius" class="theme-radius-lb">{{$t('settings.btnRadius')}}</label>
|
||||
<input id="btnradius" class="theme-radius-rn" type="range" v-model="btnRadiusLocal" max="16">
|
||||
<input id="btnradius-t" class="theme-radius-in" type="text" v-model="btnRadiusLocal">
|
||||
</div>
|
||||
<div class="radius-item">
|
||||
<label for="inputradius" class="theme-radius-lb">{{$t('settings.inputRadius')}}</label>
|
||||
<input id="inputradius" class="theme-radius-rn" type="range" v-model="inputRadiusLocal" max="16">
|
||||
<input id="inputradius-t" class="theme-radius-in" type="text" v-model="inputRadiusLocal">
|
||||
</div>
|
||||
<div class="radius-item">
|
||||
<label for="panelradius" class="theme-radius-lb">{{$t('settings.panelRadius')}}</label>
|
||||
<input id="panelradius" class="theme-radius-rn" type="range" v-model="panelRadiusLocal" max="50">
|
||||
<input id="panelradius-t" class="theme-radius-in" type="text" v-model="panelRadiusLocal">
|
||||
</div>
|
||||
<div class="radius-item">
|
||||
<label for="avatarradius" class="theme-radius-lb">{{$t('settings.avatarRadius')}}</label>
|
||||
<input id="avatarradius" class="theme-radius-rn" type="range" v-model="avatarRadiusLocal" max="28">
|
||||
<input id="avatarradius-t" class="theme-radius-in" type="green" v-model="avatarRadiusLocal">
|
||||
</div>
|
||||
<div class="radius-item">
|
||||
<label for="avataraltradius" class="theme-radius-lb">{{$t('settings.avatarAltRadius')}}</label>
|
||||
<input id="avataraltradius" class="theme-radius-rn" type="range" v-model="avatarAltRadiusLocal" max="28">
|
||||
<input id="avataraltradius-t" class="theme-radius-in" type="text" v-model="avatarAltRadiusLocal">
|
||||
</div>
|
||||
<div class="radius-item">
|
||||
<label for="attachmentradius" class="theme-radius-lb">{{$t('settings.attachmentRadius')}}</label>
|
||||
<input id="attachmentrradius" class="theme-radius-rn" type="range" v-model="attachmentRadiusLocal" max="50">
|
||||
<input id="attachmentradius-t" class="theme-radius-in" type="text" v-model="attachmentRadiusLocal">
|
||||
</div>
|
||||
<div class="radius-item">
|
||||
<label for="tooltipradius" class="theme-radius-lb">{{$t('settings.tooltipRadius')}}</label>
|
||||
<input id="tooltipradius" class="theme-radius-rn" type="range" v-model="tooltipRadiusLocal" max="20">
|
||||
<input id="tooltipradius-t" class="theme-radius-in" type="text" v-model="tooltipRadiusLocal">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="apply-container">
|
||||
<button class="btn submit" @click="setCustomTheme">{{$t('general.apply')}}</button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script src="./style_switcher.js"></script>
|
||||
|
@ -144,15 +159,19 @@
|
|||
color: var(--cRed, $fallback--cRed);
|
||||
}
|
||||
|
||||
.apply-container,
|
||||
.radius-container,
|
||||
.color-container {
|
||||
.color-container,
|
||||
.presets-container {
|
||||
display: flex;
|
||||
|
||||
p {
|
||||
flex: 2 0 100%;
|
||||
margin-top: 2em;
|
||||
margin-bottom: .5em;
|
||||
}
|
||||
}
|
||||
|
||||
.radius-container {
|
||||
flex-direction: column;
|
||||
}
|
||||
|
@ -162,6 +181,36 @@
|
|||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.presets-container {
|
||||
justify-content: center;
|
||||
.import-export {
|
||||
display: flex;
|
||||
|
||||
.btn {
|
||||
margin-left: .5em;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.preview-container {
|
||||
border-top: 1px dashed;
|
||||
border-bottom: 1px dashed;
|
||||
border-color: $fallback--border;
|
||||
border-color: var(--border, $fallback--border);
|
||||
margin: 1em -1em 0;
|
||||
padding: 1em;
|
||||
|
||||
.btn {
|
||||
margin-top: 1em;
|
||||
min-height: 30px;
|
||||
width: 10em;
|
||||
}
|
||||
}
|
||||
|
||||
.apply-container {
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.radius-item,
|
||||
.color-item {
|
||||
min-width: 20em;
|
||||
|
@ -229,6 +278,7 @@
|
|||
flex: 0;
|
||||
min-width: 2em;
|
||||
cursor: pointer;
|
||||
max-height: 29px;
|
||||
}
|
||||
|
||||
.theme-preview-content {
|
||||
|
|
|
@ -0,0 +1,44 @@
|
|||
import Vue from 'vue'
|
||||
|
||||
import './tab_switcher.scss'
|
||||
|
||||
export default Vue.component('tab-switcher', {
|
||||
name: 'TabSwitcher',
|
||||
data () {
|
||||
return {
|
||||
active: 0
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
activateTab(index) {
|
||||
return () => this.active = index;
|
||||
}
|
||||
},
|
||||
render(h) {
|
||||
const tabs = this.$slots.default
|
||||
.filter(slot => slot.data)
|
||||
.map((slot, index) => {
|
||||
const classes = ['tab']
|
||||
|
||||
if (index === this.active) {
|
||||
classes.push('active')
|
||||
}
|
||||
return (<button onClick={this.activateTab(index)} class={ classes.join(' ') }>{slot.data.attrs.label}</button>)
|
||||
});
|
||||
const contents = (
|
||||
<div>
|
||||
{this.$slots.default.filter(slot => slot.data)[this.active]}
|
||||
</div>
|
||||
);
|
||||
return (
|
||||
<div class="tab-switcher">
|
||||
<div class="tabs">
|
||||
{tabs}
|
||||
</div>
|
||||
<div class="contents">
|
||||
{contents}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
})
|
|
@ -0,0 +1,43 @@
|
|||
@import '../../_variables.scss';
|
||||
|
||||
.tab-switcher {
|
||||
.tabs {
|
||||
display: flex;
|
||||
position: relative;
|
||||
justify-content: center;
|
||||
width: 100%;
|
||||
overflow: hidden;
|
||||
padding-top: 5px;
|
||||
|
||||
&::after, &::before {
|
||||
display: block;
|
||||
content: '';
|
||||
flex: 1 1 auto;
|
||||
}
|
||||
|
||||
.tab, &::after, &::before {
|
||||
border-bottom: 1px solid;
|
||||
border-bottom-color: $fallback--btn;
|
||||
border-bottom-color: var(--btn, $fallback--btn);
|
||||
}
|
||||
|
||||
.tab {
|
||||
border-bottom-left-radius: 0;
|
||||
border-bottom-right-radius: 0;
|
||||
padding: .3em 1em;
|
||||
|
||||
&:not(.active) {
|
||||
border-bottom: 1px solid;
|
||||
border-bottom-color: $fallback--btn;
|
||||
border-bottom-color: var(--btn, $fallback--btn);
|
||||
z-index: 4;
|
||||
}
|
||||
|
||||
&.active {
|
||||
background: transparent;
|
||||
border-bottom: none;
|
||||
z-index: 5;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -4,12 +4,12 @@
|
|||
<div class="title">
|
||||
{{title}}
|
||||
</div>
|
||||
<button @click.prevent="showNewStatuses" class="loadmore-button" v-if="timeline.newStatusCount > 0 && !timelineError">
|
||||
{{$t('timeline.show_new')}}{{newStatusCountStr}}
|
||||
</button>
|
||||
<div @click.prevent class="loadmore-error alert error" v-if="timelineError">
|
||||
{{$t('timeline.error_fetching')}}
|
||||
</div>
|
||||
<button @click.prevent="showNewStatuses" class="loadmore-button" v-if="timeline.newStatusCount > 0 && !timelineError">
|
||||
{{$t('timeline.show_new')}}{{newStatusCountStr}}
|
||||
</button>
|
||||
<div @click.prevent class="loadmore-text" v-if="!timeline.newStatusCount > 0 && !timelineError">
|
||||
{{$t('timeline.up_to_date')}}
|
||||
</div>
|
||||
|
@ -57,36 +57,7 @@
|
|||
@import '../../_variables.scss';
|
||||
|
||||
.timeline {
|
||||
.timeline-heading {
|
||||
position: relative;
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.title {
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
max-width: 70%;
|
||||
}
|
||||
|
||||
.loadmore-button {
|
||||
position: absolute;
|
||||
right: 0.6em;
|
||||
font-size: 14px;
|
||||
|
||||
min-width: 6em;
|
||||
height: 1.8em;
|
||||
line-height: 100%;
|
||||
}
|
||||
|
||||
.loadmore-text {
|
||||
position: absolute;
|
||||
right: 0.6em;
|
||||
font-size: 14px;
|
||||
min-width: 6em;
|
||||
font-family: sans-serif;
|
||||
text-align: center;
|
||||
padding: 0 0.5em 0 0.5em;
|
||||
opacity: 0.8;
|
||||
background-color: transparent;
|
||||
color: $fallback--faint;
|
||||
|
@ -94,14 +65,6 @@
|
|||
}
|
||||
|
||||
.loadmore-error {
|
||||
position: absolute;
|
||||
right: 0.6em;
|
||||
font-size: 14px;
|
||||
min-width: 6em;
|
||||
font-family: sans-serif;
|
||||
text-align: center;
|
||||
padding: 0 0.25em 0 0.25em;
|
||||
margin: 0;
|
||||
color: $fallback--fg;
|
||||
color: var(--fg, $fallback--fg);
|
||||
}
|
||||
|
|
|
@ -73,12 +73,14 @@
|
|||
border-radius: var(--panelRadius, $fallback--panelRadius);
|
||||
border-style: solid;
|
||||
border-color: $fallback--border;
|
||||
border-color: var(--border, $fallback--border);
|
||||
border-color: var(--border, $fallback--border);
|
||||
border-width: 1px;
|
||||
overflow: hidden;
|
||||
|
||||
.panel-heading {
|
||||
background: transparent;
|
||||
flex-direction: column;
|
||||
align-items: stretch;
|
||||
}
|
||||
|
||||
p {
|
||||
|
|
|
@ -105,8 +105,8 @@
|
|||
<span>{{user.followers_count}}</span>
|
||||
</div>
|
||||
</div>
|
||||
<p v-if="!hideBio && user.description_html" v-html="user.description_html"></p>
|
||||
<p v-else-if="!hideBio">{{ user.description }}</p>
|
||||
<p v-if="!hideBio && user.description_html" class="profile-bio" v-html="user.description_html"></p>
|
||||
<p v-else-if="!hideBio" class="profile-bio">{{ user.description }}</p>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
@ -130,7 +130,11 @@
|
|||
.profile-panel-body {
|
||||
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), 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 {
|
||||
|
|
|
@ -14,8 +14,10 @@
|
|||
|
||||
<style lang="scss">
|
||||
.user-panel {
|
||||
.profile-panel-background .panel-heading {
|
||||
background: transparent;
|
||||
}
|
||||
.profile-panel-background .panel-heading {
|
||||
background: transparent;
|
||||
flex-direction: column;
|
||||
align-items: stretch;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -17,6 +17,8 @@
|
|||
padding-bottom: 10px;
|
||||
.panel-heading {
|
||||
background: transparent;
|
||||
flex-direction: column;
|
||||
align-items: stretch;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
import TabSwitcher from '../tab_switcher/tab_switcher.jsx'
|
||||
import StyleSwitcher from '../style_switcher/style_switcher.vue'
|
||||
|
||||
const UserSettings = {
|
||||
|
@ -23,7 +24,8 @@ const UserSettings = {
|
|||
}
|
||||
},
|
||||
components: {
|
||||
StyleSwitcher
|
||||
StyleSwitcher,
|
||||
TabSwitcher
|
||||
},
|
||||
computed: {
|
||||
user () {
|
||||
|
|
|
@ -4,126 +4,131 @@
|
|||
{{$t('settings.user_settings')}}
|
||||
</div>
|
||||
<div class="panel-body profile-edit">
|
||||
<div class="tab-switcher">
|
||||
<button class="btn btn-default" @click="activateTab('profile')">{{$t('settings.profile_tab')}}</button>
|
||||
<button class="btn btn-default" @click="activateTab('security')">{{$t('settings.security_tab')}}</button>
|
||||
<button class="btn btn-default" @click="activateTab('data_import_export')" v-if="pleromaBackend">{{$t('settings.data_import_export_tab')}}</button>
|
||||
</div>
|
||||
<div class="setting-item" v-if="activeTab == 'profile'">
|
||||
<h2>{{$t('settings.name_bio')}}</h2>
|
||||
<p>{{$t('settings.name')}}</p>
|
||||
<input class='name-changer' id='username' v-model="newname"></input>
|
||||
<p>{{$t('settings.bio')}}</p>
|
||||
<textarea class="bio" v-model="newbio"></textarea>
|
||||
<p>
|
||||
<input type="checkbox" v-model="newlocked" id="account-locked">
|
||||
<label for="account-locked">{{$t('settings.lock_account_description')}}</label>
|
||||
</p>
|
||||
<div v-if="scopeOptionsEnabled">
|
||||
<label for="default-vis">{{$t('settings.default_vis')}}</label>
|
||||
<div id="default-vis" class="visibility-tray">
|
||||
<i v-on:click="changeVis('direct')" class="icon-mail-alt" :class="vis.direct"></i>
|
||||
<i v-on:click="changeVis('private')" class="icon-lock" :class="vis.private"></i>
|
||||
<i v-on:click="changeVis('unlisted')" class="icon-lock-open-alt" :class="vis.unlisted"></i>
|
||||
<i v-on:click="changeVis('public')" class="icon-globe" :class="vis.public"></i>
|
||||
<tab-switcher>
|
||||
<div :label="$t('settings.profile_tab')">
|
||||
<div class="setting-item" >
|
||||
<h2>{{$t('settings.name_bio')}}</h2>
|
||||
<p>{{$t('settings.name')}}</p>
|
||||
<input class='name-changer' id='username' v-model="newname"></input>
|
||||
<p>{{$t('settings.bio')}}</p>
|
||||
<textarea class="bio" v-model="newbio"></textarea>
|
||||
<p>
|
||||
<input type="checkbox" v-model="newlocked" id="account-locked">
|
||||
<label for="account-locked">{{$t('settings.lock_account_description')}}</label>
|
||||
</p>
|
||||
<div v-if="scopeOptionsEnabled">
|
||||
<label for="default-vis">{{$t('settings.default_vis')}}</label>
|
||||
<div id="default-vis" class="visibility-tray">
|
||||
<i v-on:click="changeVis('direct')" class="icon-mail-alt" :class="vis.direct"></i>
|
||||
<i v-on:click="changeVis('private')" class="icon-lock" :class="vis.private"></i>
|
||||
<i v-on:click="changeVis('unlisted')" class="icon-lock-open-alt" :class="vis.unlisted"></i>
|
||||
<i v-on:click="changeVis('public')" class="icon-globe" :class="vis.public"></i>
|
||||
</div>
|
||||
</div>
|
||||
<button :disabled='newname.length <= 0' class="btn btn-default" @click="updateProfile">{{$t('general.submit')}}</button>
|
||||
</div>
|
||||
<div class="setting-item">
|
||||
<h2>{{$t('settings.avatar')}}</h2>
|
||||
<p>{{$t('settings.current_avatar')}}</p>
|
||||
<img :src="user.profile_image_url_original" class="old-avatar"></img>
|
||||
<p>{{$t('settings.set_new_avatar')}}</p>
|
||||
<img class="new-avatar" v-bind:src="previews[0]" v-if="previews[0]">
|
||||
</img>
|
||||
<div>
|
||||
<input type="file" @change="uploadFile(0, $event)" ></input>
|
||||
</div>
|
||||
<i class="icon-spin4 animate-spin" v-if="uploading[0]"></i>
|
||||
<button class="btn btn-default" v-else-if="previews[0]" @click="submitAvatar">{{$t('general.submit')}}</button>
|
||||
</div>
|
||||
<div class="setting-item">
|
||||
<h2>{{$t('settings.profile_banner')}}</h2>
|
||||
<p>{{$t('settings.current_profile_banner')}}</p>
|
||||
<img :src="user.cover_photo" class="banner"></img>
|
||||
<p>{{$t('settings.set_new_profile_banner')}}</p>
|
||||
<img class="banner" v-bind:src="previews[1]" v-if="previews[1]">
|
||||
</img>
|
||||
<div>
|
||||
<input type="file" @change="uploadFile(1, $event)" ></input>
|
||||
</div>
|
||||
<i class=" icon-spin4 animate-spin uploading" v-if="uploading[1]"></i>
|
||||
<button class="btn btn-default" v-else-if="previews[1]" @click="submitBanner">{{$t('general.submit')}}</button>
|
||||
</div>
|
||||
<div class="setting-item">
|
||||
<h2>{{$t('settings.profile_background')}}</h2>
|
||||
<p>{{$t('settings.set_new_profile_background')}}</p>
|
||||
<img class="bg" v-bind:src="previews[2]" v-if="previews[2]">
|
||||
</img>
|
||||
<div>
|
||||
<input type="file" @change="uploadFile(2, $event)" ></input>
|
||||
</div>
|
||||
<i class=" icon-spin4 animate-spin uploading" v-if="uploading[2]"></i>
|
||||
<button class="btn btn-default" v-else-if="previews[2]" @click="submitBg">{{$t('general.submit')}}</button>
|
||||
</div>
|
||||
</div>
|
||||
<button :disabled='newname.length <= 0' class="btn btn-default" @click="updateProfile">{{$t('general.submit')}}</button>
|
||||
</div>
|
||||
<div class="setting-item" v-if="activeTab == 'profile'">
|
||||
<h2>{{$t('settings.avatar')}}</h2>
|
||||
<p>{{$t('settings.current_avatar')}}</p>
|
||||
<img :src="user.profile_image_url_original" class="old-avatar"></img>
|
||||
<p>{{$t('settings.set_new_avatar')}}</p>
|
||||
<img class="new-avatar" v-bind:src="previews[0]" v-if="previews[0]">
|
||||
</img>
|
||||
<div>
|
||||
<input type="file" @change="uploadFile(0, $event)" ></input>
|
||||
|
||||
<div :label="$t('settings.security_tab')">
|
||||
<div class="setting-item">
|
||||
<h2>{{$t('settings.change_password')}}</h2>
|
||||
<div>
|
||||
<p>{{$t('settings.current_password')}}</p>
|
||||
<input type="password" v-model="changePasswordInputs[0]">
|
||||
</div>
|
||||
<div>
|
||||
<p>{{$t('settings.new_password')}}</p>
|
||||
<input type="password" v-model="changePasswordInputs[1]">
|
||||
</div>
|
||||
<div>
|
||||
<p>{{$t('settings.confirm_new_password')}}</p>
|
||||
<input type="password" v-model="changePasswordInputs[2]">
|
||||
</div>
|
||||
<button class="btn btn-default" @click="changePassword">{{$t('general.submit')}}</button>
|
||||
<p v-if="changedPassword">{{$t('settings.changed_password')}}</p>
|
||||
<p v-else-if="changePasswordError !== false">{{$t('settings.change_password_error')}}</p>
|
||||
<p v-if="changePasswordError">{{changePasswordError}}</p>
|
||||
</div>
|
||||
|
||||
<div class="setting-item">
|
||||
<h2>{{$t('settings.delete_account')}}</h2>
|
||||
<p v-if="!deletingAccount">{{$t('settings.delete_account_description')}}</p>
|
||||
<div v-if="deletingAccount">
|
||||
<p>{{$t('settings.delete_account_instructions')}}</p>
|
||||
<p>{{$t('login.password')}}</p>
|
||||
<input type="password" v-model="deleteAccountConfirmPasswordInput">
|
||||
<button class="btn btn-default" @click="deleteAccount">{{$t('settings.delete_account')}}</button>
|
||||
</div>
|
||||
<p v-if="deleteAccountError !== false">{{$t('settings.delete_account_error')}}</p>
|
||||
<p v-if="deleteAccountError">{{deleteAccountError}}</p>
|
||||
<button class="btn btn-default" v-if="!deletingAccount" @click="confirmDelete">{{$t('general.submit')}}</button>
|
||||
</div>
|
||||
</div>
|
||||
<i class="icon-spin4 animate-spin" v-if="uploading[0]"></i>
|
||||
<button class="btn btn-default" v-else-if="previews[0]" @click="submitAvatar">{{$t('general.submit')}}</button>
|
||||
</div>
|
||||
<div class="setting-item" v-if="activeTab == 'profile'">
|
||||
<h2>{{$t('settings.profile_banner')}}</h2>
|
||||
<p>{{$t('settings.current_profile_banner')}}</p>
|
||||
<img :src="user.cover_photo" class="banner"></img>
|
||||
<p>{{$t('settings.set_new_profile_banner')}}</p>
|
||||
<img class="banner" v-bind:src="previews[1]" v-if="previews[1]">
|
||||
</img>
|
||||
<div>
|
||||
<input type="file" @change="uploadFile(1, $event)" ></input>
|
||||
|
||||
<div :label="$t('settings.data_import_export_tab')" v-if="pleromaBackend">
|
||||
<div class="setting-item">
|
||||
<h2>{{$t('settings.follow_import')}}</h2>
|
||||
<p>{{$t('settings.import_followers_from_a_csv_file')}}</p>
|
||||
<form v-model="followImportForm">
|
||||
<input type="file" ref="followlist" v-on:change="followListChange"></input>
|
||||
</form>
|
||||
<i class=" icon-spin4 animate-spin uploading" v-if="uploading[3]"></i>
|
||||
<button class="btn btn-default" v-else @click="importFollows">{{$t('general.submit')}}</button>
|
||||
<div v-if="followsImported">
|
||||
<i class="icon-cross" @click="dismissImported"></i>
|
||||
<p>{{$t('settings.follows_imported')}}</p>
|
||||
</div>
|
||||
<div v-else-if="followImportError">
|
||||
<i class="icon-cross" @click="dismissImported"></i>
|
||||
<p>{{$t('settings.follow_import_error')}}</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="setting-item" v-if="enableFollowsExport">
|
||||
<h2>{{$t('settings.follow_export')}}</h2>
|
||||
<button class="btn btn-default" @click="exportFollows">{{$t('settings.follow_export_button')}}</button>
|
||||
</div>
|
||||
<div class="setting-item" v-else>
|
||||
<h2>{{$t('settings.follow_export_processing')}}</h2>
|
||||
</div>
|
||||
</div>
|
||||
<i class=" icon-spin4 animate-spin uploading" v-if="uploading[1]"></i>
|
||||
<button class="btn btn-default" v-else-if="previews[1]" @click="submitBanner">{{$t('general.submit')}}</button>
|
||||
</div>
|
||||
<div class="setting-item" v-if="activeTab == 'profile'">
|
||||
<h2>{{$t('settings.profile_background')}}</h2>
|
||||
<p>{{$t('settings.set_new_profile_background')}}</p>
|
||||
<img class="bg" v-bind:src="previews[2]" v-if="previews[2]">
|
||||
</img>
|
||||
<div>
|
||||
<input type="file" @change="uploadFile(2, $event)" ></input>
|
||||
</div>
|
||||
<i class=" icon-spin4 animate-spin uploading" v-if="uploading[2]"></i>
|
||||
<button class="btn btn-default" v-else-if="previews[2]" @click="submitBg">{{$t('general.submit')}}</button>
|
||||
</div>
|
||||
<div class="setting-item" v-if="activeTab == 'security'">
|
||||
<h2>{{$t('settings.change_password')}}</h2>
|
||||
<div>
|
||||
<p>{{$t('settings.current_password')}}</p>
|
||||
<input type="password" v-model="changePasswordInputs[0]">
|
||||
</div>
|
||||
<div>
|
||||
<p>{{$t('settings.new_password')}}</p>
|
||||
<input type="password" v-model="changePasswordInputs[1]">
|
||||
</div>
|
||||
<div>
|
||||
<p>{{$t('settings.confirm_new_password')}}</p>
|
||||
<input type="password" v-model="changePasswordInputs[2]">
|
||||
</div>
|
||||
<button class="btn btn-default" @click="changePassword">{{$t('general.submit')}}</button>
|
||||
<p v-if="changedPassword">{{$t('settings.changed_password')}}</p>
|
||||
<p v-else-if="changePasswordError !== false">{{$t('settings.change_password_error')}}</p>
|
||||
<p v-if="changePasswordError">{{changePasswordError}}</p>
|
||||
</div>
|
||||
<div class="setting-item" v-if="pleromaBackend && activeTab == 'data_import_export'">
|
||||
<h2>{{$t('settings.follow_import')}}</h2>
|
||||
<p>{{$t('settings.import_followers_from_a_csv_file')}}</p>
|
||||
<form v-model="followImportForm">
|
||||
<input type="file" ref="followlist" v-on:change="followListChange"></input>
|
||||
</form>
|
||||
<i class=" icon-spin4 animate-spin uploading" v-if="uploading[3]"></i>
|
||||
<button class="btn btn-default" v-else @click="importFollows">{{$t('general.submit')}}</button>
|
||||
<div v-if="followsImported">
|
||||
<i class="icon-cross" @click="dismissImported"></i>
|
||||
<p>{{$t('settings.follows_imported')}}</p>
|
||||
</div>
|
||||
<div v-else-if="followImportError">
|
||||
<i class="icon-cross" @click="dismissImported"></i>
|
||||
<p>{{$t('settings.follow_import_error')}}</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="setting-item" v-if="enableFollowsExport && activeTab == 'data_import_export'">
|
||||
<h2>{{$t('settings.follow_export')}}</h2>
|
||||
<button class="btn btn-default" @click="exportFollows">{{$t('settings.follow_export_button')}}</button>
|
||||
</div>
|
||||
<div class="setting-item" v-else-if="activeTab == 'data_import_export'">
|
||||
<h2>{{$t('settings.follow_export_processing')}}</h2>
|
||||
</div>
|
||||
<hr>
|
||||
<div class="setting-item" v-if="activeTab == 'security'">
|
||||
<h2>{{$t('settings.delete_account')}}</h2>
|
||||
<p v-if="!deletingAccount">{{$t('settings.delete_account_description')}}</p>
|
||||
<div v-if="deletingAccount">
|
||||
<p>{{$t('settings.delete_account_instructions')}}</p>
|
||||
<p>{{$t('login.password')}}</p>
|
||||
<input type="password" v-model="deleteAccountConfirmPasswordInput">
|
||||
<button class="btn btn-default" @click="deleteAccount">{{$t('settings.delete_account')}}</button>
|
||||
</div>
|
||||
<p v-if="deleteAccountError !== false">{{$t('settings.delete_account_error')}}</p>
|
||||
<p v-if="deleteAccountError">{{deleteAccountError}}</p>
|
||||
<button class="btn btn-default" v-if="!deletingAccount" @click="confirmDelete">{{$t('general.submit')}}</button>
|
||||
</div>
|
||||
</tab-switcher>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
@ -151,13 +156,4 @@
|
|||
margin: 0.25em;
|
||||
}
|
||||
}
|
||||
|
||||
.tab-switcher {
|
||||
margin: 7px 7px;
|
||||
display: inline-block;
|
||||
|
||||
button {
|
||||
height: 30px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -1,18 +1,21 @@
|
|||
function showWhoToFollow (panel, reply, aHost, aUser) {
|
||||
var users = reply.ids
|
||||
import apiService from '../../services/api/api.service.js'
|
||||
|
||||
function showWhoToFollow (panel, reply) {
|
||||
var users = reply
|
||||
var cn
|
||||
var index = 0
|
||||
var random = Math.floor(Math.random() * 10)
|
||||
for (cn = random; cn < users.length; cn = cn + 10) {
|
||||
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.icon) {
|
||||
img = user.icon
|
||||
if (user.avatar) {
|
||||
img = user.avatar
|
||||
} else {
|
||||
img = '/images/avi.png'
|
||||
}
|
||||
var name = user.to_id
|
||||
var name = user.acct
|
||||
if (index === 0) {
|
||||
panel.img1 = img
|
||||
panel.name1 = name
|
||||
|
@ -44,35 +47,20 @@ function showWhoToFollow (panel, reply, aHost, aUser) {
|
|||
}
|
||||
})
|
||||
}
|
||||
index = index + 1
|
||||
if (index > 2) {
|
||||
break
|
||||
}
|
||||
cn = (cn + step) % users.length
|
||||
}
|
||||
}
|
||||
|
||||
function getWhoToFollow (panel) {
|
||||
var user = panel.$store.state.users.currentUser.screen_name
|
||||
if (user) {
|
||||
var credentials = panel.$store.state.users.currentUser.credentials
|
||||
if (credentials) {
|
||||
panel.name1 = 'Loading...'
|
||||
panel.name2 = 'Loading...'
|
||||
panel.name3 = 'Loading...'
|
||||
var host = window.location.hostname
|
||||
var whoToFollowProvider = panel.$store.state.config.whoToFollowProvider
|
||||
var url
|
||||
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)
|
||||
})
|
||||
apiService.suggestions({credentials: credentials})
|
||||
.then((reply) => {
|
||||
showWhoToFollow(panel, reply)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -95,26 +83,26 @@ const WhoToFollowPanel = {
|
|||
moreUrl: function () {
|
||||
var host = window.location.hostname
|
||||
var user = this.user
|
||||
var whoToFollowLink = this.$store.state.config.whoToFollowLink
|
||||
var suggestionsWeb = this.$store.state.config.suggestionsWeb
|
||||
var url
|
||||
url = whoToFollowLink.replace(/{{host}}/g, encodeURIComponent(host))
|
||||
url = suggestionsWeb.replace(/{{host}}/g, encodeURIComponent(host))
|
||||
url = url.replace(/{{user}}/g, encodeURIComponent(user))
|
||||
return url
|
||||
},
|
||||
showWhoToFollowPanel () {
|
||||
return this.$store.state.config.showWhoToFollowPanel
|
||||
suggestionsEnabled () {
|
||||
return this.$store.state.config.suggestionsEnabled
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
user: function (user, oldUser) {
|
||||
if (this.showWhoToFollowPanel) {
|
||||
if (this.suggestionsEnabled) {
|
||||
getWhoToFollow(this)
|
||||
}
|
||||
}
|
||||
},
|
||||
mounted:
|
||||
function () {
|
||||
if (this.showWhoToFollowPanel) {
|
||||
if (this.suggestionsEnabled) {
|
||||
getWhoToFollow(this)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
<div class="panel panel-default base01-background">
|
||||
<div class="panel-heading timeline-heading base02-background base04">
|
||||
<div class="title">
|
||||
Who to follow
|
||||
{{$t('who_to_follow.who_to_follow')}}
|
||||
</div>
|
||||
</div>
|
||||
<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="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="$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>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -7,7 +7,8 @@ const de = {
|
|||
timeline: 'Zeitleiste',
|
||||
mentions: 'Erwähnungen',
|
||||
public_tl: 'Lokale Zeitleiste',
|
||||
twkn: 'Das gesamte Netzwerk'
|
||||
twkn: 'Das gesamte Netzwerk',
|
||||
friend_requests: 'Followanfragen'
|
||||
},
|
||||
user_card: {
|
||||
follows_you: 'Folgt dir!',
|
||||
|
@ -18,10 +19,12 @@ const de = {
|
|||
statuses: 'Beiträge',
|
||||
mute: 'Stummschalten',
|
||||
muted: 'Stummgeschaltet',
|
||||
followers: 'Folgende',
|
||||
followers: 'Followers',
|
||||
followees: 'Folgt',
|
||||
per_day: 'pro Tag',
|
||||
remote_follow: 'Remote Follow'
|
||||
remote_follow: 'Folgen',
|
||||
approve: 'Genehmigen',
|
||||
deny: 'Ablehnen'
|
||||
},
|
||||
timeline: {
|
||||
show_new: 'Zeige Neuere',
|
||||
|
@ -39,17 +42,17 @@ const de = {
|
|||
bio: 'Bio',
|
||||
avatar: 'Avatar',
|
||||
current_avatar: 'Dein derzeitiger Avatar',
|
||||
set_new_avatar: 'Setze neuen Avatar',
|
||||
set_new_avatar: 'Setze einen neuen Avatar',
|
||||
profile_banner: 'Profil Banner',
|
||||
current_profile_banner: 'Dein derzeitiger Profil Banner',
|
||||
set_new_profile_banner: 'Setze neuen Profil Banner',
|
||||
current_profile_banner: 'Der derzeitige Banner deines Profils',
|
||||
set_new_profile_banner: 'Setze einen neuen Banner für dein Profil',
|
||||
profile_background: 'Profil Hintergrund',
|
||||
set_new_profile_background: 'Setze neuen Profil Hintergrund',
|
||||
set_new_profile_background: 'Setze einen neuen Hintergrund für dein Profil',
|
||||
settings: 'Einstellungen',
|
||||
theme: 'Farbschema',
|
||||
presets: 'Voreinstellungen',
|
||||
export_theme: 'Aktuelles Theme exportieren',
|
||||
import_theme: 'Gespeichertes Theme laden',
|
||||
export_theme: 'Farbschema speichern',
|
||||
import_theme: 'Farbschema laden',
|
||||
invalid_theme_imported: 'Die ausgewählte Datei ist kein unterstütztes Pleroma-Theme. Keine Änderungen wurden vorgenommen.',
|
||||
theme_help: 'Benutze HTML Farbcodes (#rrggbb) um dein Farbschema anzupassen',
|
||||
radii_help: 'Kantenrundung (in Pixel) der Oberfläche anpassen',
|
||||
|
@ -78,15 +81,15 @@ const de = {
|
|||
autoload: 'Aktiviere automatisches Laden von älteren Beiträgen beim scrollen',
|
||||
streaming: 'Aktiviere automatisches Laden (Streaming) von neuen Beiträgen',
|
||||
reply_link_preview: 'Aktiviere reply-link Vorschau bei Maus-Hover',
|
||||
follow_import: 'Folgeliste importieren',
|
||||
import_followers_from_a_csv_file: 'Importiere Kontakte, denen du folgen möchtest, aus einer CSV-Datei',
|
||||
follows_imported: 'Folgeliste importiert! Die Bearbeitung kann eine Zeit lang dauern.',
|
||||
follow_import_error: 'Fehler beim importieren der Folgeliste',
|
||||
follow_import: 'Followers importieren',
|
||||
import_followers_from_a_csv_file: 'Importiere Follower, denen du folgen möchtest, aus einer CSV-Datei',
|
||||
follows_imported: 'Followers importiert! Die Bearbeitung kann eine Zeit lang dauern.',
|
||||
follow_import_error: 'Fehler beim importieren der Follower',
|
||||
delete_account: 'Account löschen',
|
||||
delete_account_description: 'Lösche deinen Account und alle deine Nachrichten dauerhaft.',
|
||||
delete_account_instructions: 'Tippe dein Passwort unten in das Feld ein um die Löschung deines Accounts zu bestätigen.',
|
||||
delete_account_description: 'Lösche deinen Account und alle deine Nachrichten unwiderruflich.',
|
||||
delete_account_instructions: 'Tippe dein Passwort unten in das Feld ein, um die Löschung deines Accounts zu bestätigen.',
|
||||
delete_account_error: 'Es ist ein Fehler beim löschen deines Accounts aufgetreten. Tritt dies weiterhin auf, wende dich an den Administrator der Instanz.',
|
||||
follow_export: 'Folgeliste exportieren',
|
||||
follow_export: 'Follower exportieren',
|
||||
follow_export_processing: 'In Bearbeitung. Die Liste steht gleich zum herunterladen bereit.',
|
||||
follow_export_button: 'Liste (.csv) erstellen',
|
||||
change_password: 'Passwort ändern',
|
||||
|
@ -94,7 +97,8 @@ const de = {
|
|||
new_password: 'Neues Passwort',
|
||||
confirm_new_password: 'Neues Passwort bestätigen',
|
||||
changed_password: 'Passwort erfolgreich geändert!',
|
||||
change_password_error: 'Es gab ein Problem bei der Änderung des Passworts.'
|
||||
change_password_error: 'Es gab ein Problem bei der Änderung des Passworts.',
|
||||
lock_account_description: 'Sperre deinen Account, um neue Follower zu genehmigen oder abzulehnen'
|
||||
},
|
||||
notifications: {
|
||||
notifications: 'Benachrichtigungen',
|
||||
|
@ -116,7 +120,8 @@ const de = {
|
|||
fullname: 'Angezeigter Name',
|
||||
email: 'Email',
|
||||
bio: 'Bio',
|
||||
password_confirm: 'Passwort bestätigen'
|
||||
password_confirm: 'Passwort bestätigen',
|
||||
token: 'Einladungsschlüssel'
|
||||
},
|
||||
post_status: {
|
||||
posting: 'Veröffentlichen',
|
||||
|
@ -127,7 +132,7 @@ const de = {
|
|||
scope: {
|
||||
public: 'Öffentlich - Beitrag an öffentliche Zeitleisten',
|
||||
unlisted: 'Nicht gelistet - Nicht in öffentlichen Zeitleisten anzeigen',
|
||||
private: 'Nur Folgende - Beitrag nur an Folgende',
|
||||
private: 'Nur Follower - Beitrag nur für Follower sichtbar',
|
||||
direct: 'Direkt - Beitrag nur an erwähnte Profile'
|
||||
}
|
||||
},
|
||||
|
@ -273,9 +278,11 @@ const en = {
|
|||
load_older: 'Load older statuses',
|
||||
conversation: 'Conversation',
|
||||
collapse: 'Collapse',
|
||||
repeated: 'repeated'
|
||||
repeated: 'repeated',
|
||||
no_retweet_hint: 'Post is marked as followers-only or direct and cannot be repeated'
|
||||
},
|
||||
settings: {
|
||||
general: 'General',
|
||||
user_settings: 'User Settings',
|
||||
name_bio: 'Name & Bio',
|
||||
name: 'Name',
|
||||
|
@ -291,8 +298,8 @@ const en = {
|
|||
settings: 'Settings',
|
||||
theme: 'Theme',
|
||||
presets: 'Presets',
|
||||
export_theme: 'Export current theme',
|
||||
import_theme: 'Load saved theme',
|
||||
export_theme: 'Save preset',
|
||||
import_theme: 'Load preset',
|
||||
theme_help: 'Use hex color codes (#rrggbb) to customize your color theme.',
|
||||
invalid_theme_imported: 'The selected file is not a supported Pleroma theme. No changes to your theme were made.',
|
||||
radii_help: 'Set up interface edge rounding (in pixels)',
|
||||
|
@ -325,6 +332,15 @@ const en = {
|
|||
loop_video: 'Loop videos',
|
||||
loop_video_silent_only: 'Loop only videos without sound (i.e. Mastodon\'s "gifs")',
|
||||
reply_link_preview: 'Enable reply-link preview on mouse hover',
|
||||
replies_in_timeline: 'Replies in timeline',
|
||||
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',
|
||||
notification_visibility: 'Types of notifications to show',
|
||||
notification_visibility_likes: 'Likes',
|
||||
notification_visibility_mentions: 'Mentions',
|
||||
notification_visibility_repeats: 'Repeats',
|
||||
notification_visibility_follows: 'Follows',
|
||||
follow_import: 'Follow import',
|
||||
import_followers_from_a_csv_file: 'Import follows from a csv file',
|
||||
follows_imported: 'Follows imported! Processing them will take a while.',
|
||||
|
@ -347,14 +363,17 @@ const en = {
|
|||
default_vis: 'Default visibility scope',
|
||||
profile_tab: 'Profile',
|
||||
security_tab: 'Security',
|
||||
data_import_export_tab: 'Data Import / Export'
|
||||
data_import_export_tab: 'Data Import / Export',
|
||||
interfaceLanguage: 'Interface language'
|
||||
},
|
||||
notifications: {
|
||||
notifications: 'Notifications',
|
||||
read: 'Read!',
|
||||
followed_you: 'followed you',
|
||||
favorited_you: 'favorited your status',
|
||||
repeated_you: 'repeated your status'
|
||||
repeated_you: 'repeated your status',
|
||||
broken_favorite: 'Unknown status, searching for it...',
|
||||
load_older: 'Load older notifications'
|
||||
},
|
||||
login: {
|
||||
login: 'Log in',
|
||||
|
@ -379,11 +398,15 @@ 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_link: 'locked',
|
||||
direct_warning: 'This post will only be visible to all the mentioned users.',
|
||||
attachments_sensitive: 'Mark attachments as sensitive',
|
||||
scope: {
|
||||
public: 'Public - Post to public timelines',
|
||||
unlisted: 'Unlisted - Do not post to public timelines',
|
||||
private: 'Followers-only - Post to followers only',
|
||||
direct: 'Direct - Post to mentioned users only'
|
||||
},
|
||||
content_type: {
|
||||
plain_text: 'Plain text'
|
||||
}
|
||||
},
|
||||
finder: {
|
||||
|
@ -396,19 +419,32 @@ const en = {
|
|||
},
|
||||
user_profile: {
|
||||
timeline_title: 'User Timeline'
|
||||
},
|
||||
who_to_follow: {
|
||||
who_to_follow: 'Who to follow',
|
||||
more: 'More'
|
||||
},
|
||||
features_panel: {
|
||||
title: 'Features',
|
||||
chat: 'Chat',
|
||||
gopher: 'Gopher',
|
||||
who_to_follow: 'Who to follow',
|
||||
media_proxy: 'Media proxy',
|
||||
scope_options: 'Scope options',
|
||||
text_limit: 'Text limit'
|
||||
}
|
||||
}
|
||||
|
||||
const eo = {
|
||||
chat: {
|
||||
title: 'Babilo'
|
||||
title: 'Babilejo'
|
||||
},
|
||||
nav: {
|
||||
chat: 'Loka babilo',
|
||||
timeline: 'Tempovido',
|
||||
chat: 'Loka babilejo',
|
||||
timeline: 'Tempolinio',
|
||||
mentions: 'Mencioj',
|
||||
public_tl: 'Publika tempovido',
|
||||
twkn: 'Tuta konata reto'
|
||||
public_tl: 'Publika tempolinio',
|
||||
twkn: 'La tuta konata reto'
|
||||
},
|
||||
user_card: {
|
||||
follows_you: 'Abonas vin!',
|
||||
|
@ -418,26 +454,26 @@ const eo = {
|
|||
block: 'Bari',
|
||||
statuses: 'Statoj',
|
||||
mute: 'Silentigi',
|
||||
muted: 'Silentigita',
|
||||
muted: 'Silentigitaj',
|
||||
followers: 'Abonantoj',
|
||||
followees: 'Abonatoj',
|
||||
per_day: 'tage',
|
||||
remote_follow: 'Fora abono'
|
||||
remote_follow: 'Fore aboni'
|
||||
},
|
||||
timeline: {
|
||||
show_new: 'Montri novajn',
|
||||
error_fetching: 'Eraro ĝisdatigante',
|
||||
error_fetching: 'Eraro dum ĝisdatigo',
|
||||
up_to_date: 'Ĝisdata',
|
||||
load_older: 'Enlegi pli malnovajn statojn',
|
||||
load_older: 'Montri pli malnovajn statojn',
|
||||
conversation: 'Interparolo',
|
||||
collapse: 'Maletendi',
|
||||
repeated: 'ripetata'
|
||||
},
|
||||
settings: {
|
||||
user_settings: 'Uzulaj agordoj',
|
||||
name_bio: 'Nomo kaj prio',
|
||||
user_settings: 'Uzantaj agordoj',
|
||||
name_bio: 'Nomo kaj priskribo',
|
||||
name: 'Nomo',
|
||||
bio: 'Prio',
|
||||
bio: 'Priskribo',
|
||||
avatar: 'Profilbildo',
|
||||
current_avatar: 'Via nuna profilbildo',
|
||||
set_new_avatar: 'Agordi novan profilbildon',
|
||||
|
@ -447,9 +483,9 @@ const eo = {
|
|||
profile_background: 'Profila fono',
|
||||
set_new_profile_background: 'Agordi novan profilan fonon',
|
||||
settings: 'Agordoj',
|
||||
theme: 'Haŭto',
|
||||
presets: 'Antaŭmetaĵoj',
|
||||
theme_help: 'Uzu deksesumajn kolorkodojn (#rrvvbb) por adapti vian koloran haŭton.',
|
||||
theme: 'Etoso',
|
||||
presets: 'Antaŭagordoj',
|
||||
theme_help: 'Uzu deksesumajn kolorkodojn (#rrvvbb) por adapti vian koloran etoson.',
|
||||
radii_help: 'Agordi fasadan rondigon de randoj (rastrumere)',
|
||||
background: 'Fono',
|
||||
foreground: 'Malfono',
|
||||
|
@ -457,65 +493,65 @@ const eo = {
|
|||
links: 'Ligiloj',
|
||||
cBlue: 'Blua (Respondo, abono)',
|
||||
cRed: 'Ruĝa (Nuligo)',
|
||||
cOrange: 'Orange (Ŝato)',
|
||||
cOrange: 'Oranĝa (Ŝato)',
|
||||
cGreen: 'Verda (Kunhavigo)',
|
||||
btnRadius: 'Butonoj',
|
||||
panelRadius: 'Paneloj',
|
||||
avatarRadius: 'Profilbildoj',
|
||||
avatarAltRadius: 'Profilbildoj (Sciigoj)',
|
||||
avatarAltRadius: 'Profilbildoj (sciigoj)',
|
||||
tooltipRadius: 'Ŝpruchelpiloj/avertoj',
|
||||
attachmentRadius: 'Kunsendaĵoj',
|
||||
filtering: 'Filtrado',
|
||||
filtering_explanation: 'Ĉiuj statoj kun tiuj ĉi vortoj silentiĝos, po unu linie',
|
||||
attachments: 'Kunsendaĵoj',
|
||||
hide_attachments_in_tl: 'Kaŝi kunsendaĵojn en tempovido',
|
||||
hide_attachments_in_tl: 'Kaŝi kunsendaĵojn en tempolinio',
|
||||
hide_attachments_in_convo: 'Kaŝi kunsendaĵojn en interparoloj',
|
||||
nsfw_clickthrough: 'Ŝalti traklakan kaŝon de konsternaj kunsendaĵoj',
|
||||
stop_gifs: 'Movi GIF-bildojn dum ŝvebo',
|
||||
autoload: 'Ŝalti memfaran enlegadon ĉe subo de paĝo',
|
||||
streaming: 'Ŝalti memfaran fluigon de novaj afiŝoj ĉe supro de paĝo',
|
||||
autoload: 'Ŝalti memfaran ŝarĝadon ĉe subo de paĝo',
|
||||
streaming: 'Ŝalti memfaran fluigon de novaj afiŝoj ĉe la supro de la paĝo',
|
||||
reply_link_preview: 'Ŝalti respond-ligilan antaŭvidon dum ŝvebo',
|
||||
follow_import: 'Abona enporto',
|
||||
import_followers_from_a_csv_file: 'Enporti abonojn de CSV-dosiero',
|
||||
import_followers_from_a_csv_file: 'Enporti abonojn el CSV-dosiero',
|
||||
follows_imported: 'Abonoj enportiĝis! Traktado daŭros iom.',
|
||||
follow_import_error: 'Eraro enportante abonojn'
|
||||
},
|
||||
notifications: {
|
||||
notifications: 'Sciigoj',
|
||||
read: 'Legita!',
|
||||
read: 'Legite!',
|
||||
followed_you: 'ekabonis vin',
|
||||
favorited_you: 'ŝatis vian staton',
|
||||
repeated_you: 'ripetis vian staton'
|
||||
},
|
||||
login: {
|
||||
login: 'Saluti',
|
||||
login: 'Ensaluti',
|
||||
username: 'Salutnomo',
|
||||
placeholder: 'ekz. zero_cool',
|
||||
password: 'Pasvorto',
|
||||
register: 'Registriĝi',
|
||||
logout: 'Adiaŭi'
|
||||
logout: 'Elsaluti'
|
||||
},
|
||||
registration: {
|
||||
registration: 'Registriĝo',
|
||||
fullname: 'Vidiga nomo',
|
||||
email: 'Retpoŝtadreso',
|
||||
bio: 'Prio',
|
||||
bio: 'Priskribo',
|
||||
password_confirm: 'Konfirmo de pasvorto'
|
||||
},
|
||||
post_status: {
|
||||
posting: 'Afiŝanta',
|
||||
default: 'Ĵus alvenis la universalan kongreson!'
|
||||
posting: 'Afiŝante',
|
||||
default: 'Ĵus alvenis al la Universala Kongreso!'
|
||||
},
|
||||
finder: {
|
||||
find_user: 'Trovi uzulon',
|
||||
error_fetching_user: 'Eraro alportante uzulon'
|
||||
find_user: 'Trovi uzanton',
|
||||
error_fetching_user: 'Eraro alportante uzanton'
|
||||
},
|
||||
general: {
|
||||
submit: 'Sendi',
|
||||
apply: 'Apliki'
|
||||
},
|
||||
user_profile: {
|
||||
timeline_title: 'Uzula tempovido'
|
||||
timeline_title: 'Uzanta tempolinio'
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -779,115 +815,156 @@ const ja = {
|
|||
chat: 'ローカルチャット',
|
||||
timeline: 'タイムライン',
|
||||
mentions: 'メンション',
|
||||
public_tl: '公開タイムライン',
|
||||
twkn: '接続しているすべてのネットワーク'
|
||||
public_tl: 'パブリックタイムライン',
|
||||
twkn: 'つながっているすべてのネットワーク',
|
||||
friend_requests: 'Follow Requests'
|
||||
},
|
||||
user_card: {
|
||||
follows_you: 'フォローされました!',
|
||||
following: 'フォロー中!',
|
||||
following: 'フォローしています!',
|
||||
follow: 'フォロー',
|
||||
blocked: 'ブロック済み!',
|
||||
blocked: 'ブロックしています!',
|
||||
block: 'ブロック',
|
||||
statuses: '投稿',
|
||||
statuses: 'ステータス',
|
||||
mute: 'ミュート',
|
||||
muted: 'ミュート済み',
|
||||
muted: 'ミュートしています!',
|
||||
followers: 'フォロワー',
|
||||
followees: 'フォロー',
|
||||
per_day: '/日',
|
||||
remote_follow: 'リモートフォロー'
|
||||
remote_follow: 'リモートフォロー',
|
||||
approve: 'Approve',
|
||||
deny: 'Deny'
|
||||
},
|
||||
timeline: {
|
||||
show_new: '更新',
|
||||
error_fetching: '更新の取得中にエラーが発生しました。',
|
||||
up_to_date: '最新',
|
||||
load_older: '古い投稿を読み込む',
|
||||
conversation: '会話',
|
||||
collapse: '折り畳む',
|
||||
show_new: 'よみこみ',
|
||||
error_fetching: 'よみこみがエラーになりました。',
|
||||
up_to_date: 'さいしん',
|
||||
load_older: 'ふるいステータス',
|
||||
conversation: 'スレッド',
|
||||
collapse: 'たたむ',
|
||||
repeated: 'リピート'
|
||||
},
|
||||
settings: {
|
||||
user_settings: 'ユーザー設定',
|
||||
name_bio: '名前とプロフィール',
|
||||
name: '名前',
|
||||
user_settings: 'ユーザーせってい',
|
||||
name_bio: 'なまえとプロフィール',
|
||||
name: 'なまえ',
|
||||
bio: 'プロフィール',
|
||||
avatar: 'アバター',
|
||||
current_avatar: 'あなたの現在のアバター',
|
||||
set_new_avatar: '新しいアバターを設定する',
|
||||
current_avatar: 'いまのアバター',
|
||||
set_new_avatar: 'あたらしいアバターをせっていする',
|
||||
profile_banner: 'プロフィールバナー',
|
||||
current_profile_banner: '現在のプロフィールバナー',
|
||||
set_new_profile_banner: '新しいプロフィールバナーを設定する',
|
||||
profile_background: 'プロフィールの背景',
|
||||
set_new_profile_background: '新しいプロフィールの背景を設定する',
|
||||
settings: '設定',
|
||||
current_profile_banner: 'いまのプロフィールバナー',
|
||||
set_new_profile_banner: 'あたらしいプロフィールバナーを設定する',
|
||||
profile_background: 'プロフィールのバックグラウンド',
|
||||
set_new_profile_background: 'あたらしいプロフィールのバックグラウンドをせっていする',
|
||||
settings: 'せってい',
|
||||
theme: 'テーマ',
|
||||
presets: 'プリセット',
|
||||
theme_help: '16進数カラーコード (#aabbcc) を使用してカラーテーマをカスタマイズ出来ます。',
|
||||
radii_help: 'インターフェースの縁の丸さを設定する。',
|
||||
background: '背景',
|
||||
foreground: '前景',
|
||||
text: '文字',
|
||||
theme_help: 'カラーテーマをカスタマイズできます。',
|
||||
radii_help: 'インターフェースのまるさをせっていする。',
|
||||
background: 'バックグラウンド',
|
||||
foreground: 'フォアグラウンド',
|
||||
text: 'もじ',
|
||||
links: 'リンク',
|
||||
cBlue: '青 (返信, フォロー)',
|
||||
cRed: '赤 (キャンセル)',
|
||||
cOrange: 'オレンジ (お気に入り)',
|
||||
cGreen: '緑 (リツイート)',
|
||||
cBlue: 'あお (リプライ, フォロー)',
|
||||
cRed: 'あか (キャンセル)',
|
||||
cOrange: 'オレンジ (おきにいり)',
|
||||
cGreen: 'みどり (リピート)',
|
||||
btnRadius: 'ボタン',
|
||||
inputRadius: 'Input fields',
|
||||
panelRadius: 'パネル',
|
||||
avatarRadius: 'アバター',
|
||||
avatarAltRadius: 'アバター (通知)',
|
||||
avatarAltRadius: 'アバター (つうち)',
|
||||
tooltipRadius: 'ツールチップ/アラート',
|
||||
attachmentRadius: 'ファイル',
|
||||
filtering: 'フィルタリング',
|
||||
filtering_explanation: 'これらの単語を含むすべてのものがミュートされます。1行に1つの単語を入力してください。',
|
||||
filtering_explanation: 'これらのことばをふくむすべてのものがミュートされます。1行に1つのことばをかいてください。',
|
||||
attachments: 'ファイル',
|
||||
hide_attachments_in_tl: 'タイムラインのファイルを隠す。',
|
||||
hide_attachments_in_convo: '会話の中のファイルを隠す。',
|
||||
nsfw_clickthrough: 'NSFWファイルの非表示を有効にする。',
|
||||
stop_gifs: 'カーソルを重ねた時にGIFを再生する。',
|
||||
autoload: '下にスクロールした時に自動で読み込むようにする。',
|
||||
streaming: '上までスクロールした時に自動でストリーミングされるようにする。',
|
||||
reply_link_preview: 'マウスカーソルを重ねた時に返信のプレビューを表示するようにする。',
|
||||
hide_attachments_in_tl: 'タイムラインのファイルをかくす。',
|
||||
hide_attachments_in_convo: 'スレッドのファイルをかくす。',
|
||||
nsfw_clickthrough: 'NSFWなファイルをかくす。',
|
||||
stop_gifs: 'カーソルをかさねたとき、GIFをうごかす。',
|
||||
autoload: 'したにスクロールしたとき、じどうてきによみこむ。',
|
||||
streaming: 'うえまでスクロールしたとき、じどうてきにストリーミングする。',
|
||||
reply_link_preview: 'カーソルをかさねたとき、リプライのプレビューをみる。',
|
||||
follow_import: 'フォローインポート',
|
||||
import_followers_from_a_csv_file: 'CSVファイルからフォローをインポートする。',
|
||||
follows_imported: 'フォローがインポートされました!処理に少し時間がかかるかもしれません。',
|
||||
follow_import_error: 'フォロワーのインポート中にエラーが発生しました。'
|
||||
follows_imported: 'フォローがインポートされました! すこしじかんがかかるかもしれません。',
|
||||
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: '通知',
|
||||
read: '読んだ!',
|
||||
notifications: 'つうち',
|
||||
read: 'よんだ!',
|
||||
followed_you: 'フォローされました',
|
||||
favorited_you: 'あなたの投稿がお気に入りされました',
|
||||
repeated_you: 'あなたの投稿がリピートされました'
|
||||
favorited_you: 'あなたのステータスがおきにいりされました',
|
||||
repeated_you: 'あなたのステータスがリピートされました'
|
||||
},
|
||||
login: {
|
||||
login: 'ログイン',
|
||||
username: 'ユーザー名',
|
||||
placeholder: '例えば zero_cool',
|
||||
username: 'ユーザーめい',
|
||||
placeholder: 'れい: zero_cool',
|
||||
password: 'パスワード',
|
||||
register: '登録',
|
||||
register: 'はじめる',
|
||||
logout: 'ログアウト'
|
||||
},
|
||||
registration: {
|
||||
registration: '登録',
|
||||
fullname: '表示名',
|
||||
registration: 'はじめる',
|
||||
fullname: 'スクリーンネーム',
|
||||
email: 'Eメール',
|
||||
bio: 'プロフィール',
|
||||
password_confirm: 'パスワードの確認'
|
||||
password_confirm: 'パスワードのかくにん'
|
||||
},
|
||||
post_status: {
|
||||
posting: '投稿',
|
||||
default: 'ちょうどL.A.に着陸しました。'
|
||||
posting: 'とうこう',
|
||||
content_warning: 'せつめい (かかなくてもよい)',
|
||||
default: 'はねだくうこうに、つきました。',
|
||||
account_not_locked_warning: 'あなたのアカウントは {0} ではありません。あなたをフォローすれば、だれでも、フォロワーげんていのステータスをよむことができます。',
|
||||
account_not_locked_warning_link: 'ロックされたアカウント',
|
||||
direct_warning: 'このステータスは、メンションされたユーザーだけが、よむことができます。',
|
||||
scope: {
|
||||
public: 'パブリック - パブリックタイムラインにとどきます。',
|
||||
unlisted: 'アンリステッド - パブリックタイムラインにとどきません。',
|
||||
private: 'フォロワーげんてい - フォロワーのみにとどきます。',
|
||||
direct: 'ダイレクト - メンションされたユーザーのみにとどきます。'
|
||||
}
|
||||
},
|
||||
finder: {
|
||||
find_user: 'ユーザー検索',
|
||||
error_fetching_user: 'ユーザー検索でエラーが発生しました'
|
||||
find_user: 'ユーザーをさがす',
|
||||
error_fetching_user: 'ユーザーけんさくがエラーになりました。'
|
||||
},
|
||||
general: {
|
||||
submit: '送信',
|
||||
apply: '適用'
|
||||
submit: 'そうしん',
|
||||
apply: 'てきよう'
|
||||
},
|
||||
user_profile: {
|
||||
timeline_title: 'ユーザータイムライン'
|
||||
},
|
||||
who_to_follow: {
|
||||
who_to_follow: 'おすすめユーザー',
|
||||
more: 'くわしく'
|
||||
},
|
||||
features_panel: {
|
||||
title: 'ゆうこうなきのう',
|
||||
chat: 'チャット',
|
||||
gopher: 'Gopher',
|
||||
who_to_follow: 'おすすめユーザー',
|
||||
media_proxy: 'メディアプロクシ',
|
||||
scope_options: 'こうかいはんい',
|
||||
text_limit: 'もじのかず'
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1099,8 +1176,8 @@ const oc = {
|
|||
twkn: 'Lo malhum conegut'
|
||||
},
|
||||
user_card: {
|
||||
follows_you: 'Vos sèc !',
|
||||
following: 'Seguit !',
|
||||
follows_you: 'Vos sèc!',
|
||||
following: 'Seguit!',
|
||||
follow: 'Seguir',
|
||||
blocked: 'Blocat',
|
||||
block: 'Blocar',
|
||||
|
@ -1145,10 +1222,10 @@ const oc = {
|
|||
links: 'Ligams',
|
||||
cBlue: 'Blau (Respondre, seguir)',
|
||||
cRed: 'Roge (Anullar)',
|
||||
cOrange: 'Irange (Metre en favorit)',
|
||||
cOrange: 'Irange (Aimar)',
|
||||
cGreen: 'Verd (Repartajar)',
|
||||
inputRadius: 'Camps tèxte',
|
||||
btnRadius: 'Botons',
|
||||
inputRadius: 'Camps tèxte',
|
||||
panelRadius: 'Panèls',
|
||||
avatarRadius: 'Avatars',
|
||||
avatarAltRadius: 'Avatars (Notificacions)',
|
||||
|
@ -1167,12 +1244,25 @@ const oc = {
|
|||
follow_import: 'Importar los abonaments',
|
||||
import_followers_from_a_csv_file: 'Importar los seguidors d’un fichièr csv',
|
||||
follows_imported: 'Seguidors importats. Lo tractament pòt trigar una estona.',
|
||||
follow_import_error: 'Error en important los seguidors'
|
||||
follow_import_error: 'Error en important los seguidors',
|
||||
delete_account: 'Suprimir lo compte',
|
||||
delete_account_description: 'Suprimir vòstre compte e los messatges per sempre.',
|
||||
delete_account_instructions: 'Picatz vòstre senhal dins lo camp tèxte çai-jos per confirmar la supression del compte.',
|
||||
delete_account_error: 'Una error s’es producha en suprimir lo compte. S’aquò ten d’arribar mercés de contactar vòstre administrador d’instància.',
|
||||
follow_export: 'Exportar los abonaments',
|
||||
follow_export_processing: 'Tractament, vos demandarem lèu de telecargar lo fichièr',
|
||||
follow_export_button: 'Exportar vòstres abonaments dins un fichièr csv',
|
||||
change_password: 'Cambiar lo senhal',
|
||||
current_password: 'Senhal actual',
|
||||
new_password: 'Nòu senhal',
|
||||
confirm_new_password: 'Confirmatz lo nòu senhal',
|
||||
changed_password: 'Senhal corrèctament cambiat',
|
||||
change_password_error: 'Una error s’es producha en cambiant lo senhal.'
|
||||
},
|
||||
notifications: {
|
||||
notifications: 'Notficacions',
|
||||
read: 'Legit !',
|
||||
followed_you: 'vos sèc',
|
||||
read: 'Legit!',
|
||||
followed_you: 'vos a seguit',
|
||||
favorited_you: 'a aimat vòstre estatut',
|
||||
repeated_you: 'a repetit your vòstre estatut'
|
||||
},
|
||||
|
@ -1193,6 +1283,7 @@ const oc = {
|
|||
},
|
||||
post_status: {
|
||||
posting: 'Mandadís',
|
||||
content_warning: 'Avís de contengut (opcional)',
|
||||
default: 'Escrivètz aquí vòstre estatut.'
|
||||
},
|
||||
finder: {
|
||||
|
@ -1448,7 +1539,7 @@ const pt = {
|
|||
title: 'Chat'
|
||||
},
|
||||
nav: {
|
||||
chat: 'Chat Local',
|
||||
chat: 'Chat local',
|
||||
timeline: 'Linha do tempo',
|
||||
mentions: 'Menções',
|
||||
public_tl: 'Linha do tempo pública',
|
||||
|
@ -1492,16 +1583,28 @@ const pt = {
|
|||
theme: 'Tema',
|
||||
presets: 'Predefinições',
|
||||
theme_help: 'Use cores em código hexadecimal (#rrggbb) para personalizar seu esquema de cores.',
|
||||
radii_help: 'Arredondar arestas da interface (em píxeis)',
|
||||
background: 'Plano de Fundo',
|
||||
foreground: 'Primeiro Plano',
|
||||
text: 'Texto',
|
||||
links: 'Links',
|
||||
cBlue: 'Azul (Responder, seguir)',
|
||||
cRed: 'Vermelho (Cancelar)',
|
||||
cOrange: 'Laranja (Favoritar)',
|
||||
cGreen: 'Verde (Repetir)',
|
||||
btnRadius: 'Botões',
|
||||
panelRadius: 'Paineis',
|
||||
avatarRadius: 'Avatares',
|
||||
avatarAltRadius: 'Avatares (Notificações)',
|
||||
tooltipRadius: 'Dicass/alertas',
|
||||
attachmentRadius: 'Anexos',
|
||||
filtering: 'Filtragem',
|
||||
filtering_explanation: 'Todas as postagens contendo estas palavras serão silenciadas, uma por linha.',
|
||||
attachments: 'Anexos',
|
||||
hide_attachments_in_tl: 'Ocultar anexos na linha do tempo.',
|
||||
hide_attachments_in_convo: 'Ocultar anexos em conversas',
|
||||
nsfw_clickthrough: 'Habilitar clique para ocultar anexos NSFW',
|
||||
stop_gifs: 'Reproduzir GIFs ao passar o cursor em cima',
|
||||
autoload: 'Habilitar carregamento automático quando a rolagem chegar ao fim.',
|
||||
streaming: 'Habilitar o fluxo automático de postagens quando ao topo da página',
|
||||
reply_link_preview: 'Habilitar a pré-visualização de link de respostas ao passar o mouse.',
|
||||
|
@ -1512,8 +1615,10 @@ const pt = {
|
|||
},
|
||||
notifications: {
|
||||
notifications: 'Notificações',
|
||||
read: 'Ler!',
|
||||
followed_you: 'seguiu você'
|
||||
read: 'Lido!',
|
||||
followed_you: 'seguiu você',
|
||||
favorited_you: 'favoritou sua postagem',
|
||||
repeated_you: 'repetiu sua postagem'
|
||||
},
|
||||
login: {
|
||||
login: 'Entrar',
|
||||
|
@ -1532,7 +1637,7 @@ const pt = {
|
|||
},
|
||||
post_status: {
|
||||
posting: 'Publicando',
|
||||
default: 'Acabo de aterrizar em L.A.'
|
||||
default: 'Acabei de chegar no Rio!'
|
||||
},
|
||||
finder: {
|
||||
find_user: 'Buscar usuário',
|
||||
|
@ -1541,6 +1646,9 @@ const pt = {
|
|||
general: {
|
||||
submit: 'Enviar',
|
||||
apply: 'Aplicar'
|
||||
},
|
||||
user_profile: {
|
||||
timeline_title: 'Linha do tempo do usuário'
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1576,9 +1684,11 @@ const ru = {
|
|||
load_older: 'Загрузить старые статусы',
|
||||
conversation: 'Разговор',
|
||||
collapse: 'Свернуть',
|
||||
repeated: 'повторил(а)'
|
||||
repeated: 'повторил(а)',
|
||||
no_retweet_hint: 'Пост помечен как "только для подписчиков" или "личное" и поэтому не может быть повторён'
|
||||
},
|
||||
settings: {
|
||||
general: 'Общие',
|
||||
user_settings: 'Настройки пользователя',
|
||||
name_bio: 'Имя и описание',
|
||||
name: 'Имя',
|
||||
|
@ -1593,9 +1703,11 @@ const ru = {
|
|||
set_new_profile_background: 'Загрузить новый фон профиля',
|
||||
settings: 'Настройки',
|
||||
theme: 'Тема',
|
||||
export_theme: 'Сохранить Тему',
|
||||
import_theme: 'Загрузить Тему',
|
||||
presets: 'Пресеты',
|
||||
theme_help: 'Используйте шестнадцатеричные коды цветов (#rrggbb) для настройки темы.',
|
||||
radii_help: 'Округление краёв элементов интерфейса (в пикселях)',
|
||||
radii_help: 'Скругление углов элементов интерфейса (в пикселях)',
|
||||
background: 'Фон',
|
||||
foreground: 'Передний план',
|
||||
text: 'Текст',
|
||||
|
@ -1624,6 +1736,15 @@ const ru = {
|
|||
loop_video: 'Зациливать видео',
|
||||
loop_video_silent_only: 'Зацикливать только беззвучные видео (т.е. "гифки" с Mastodon)',
|
||||
reply_link_preview: 'Включить предварительный просмотр ответа при наведении мыши',
|
||||
replies_in_timeline: 'Ответы в ленте',
|
||||
reply_visibility_all: 'Показывать все ответы',
|
||||
reply_visibility_following: 'Показывать только ответы мне и тех на кого я подписан',
|
||||
reply_visibility_self: 'Показывать только ответы мне',
|
||||
notification_visibility: 'Показывать уведомления',
|
||||
notification_visibility_likes: 'Лайки',
|
||||
notification_visibility_mentions: 'Упоминания',
|
||||
notification_visibility_repeats: 'Повторы',
|
||||
notification_visibility_follows: 'Подписки',
|
||||
follow_import: 'Импортировать читаемых',
|
||||
import_followers_from_a_csv_file: 'Импортировать читаемых из файла .csv',
|
||||
follows_imported: 'Список читаемых импортирован. Обработка займёт некоторое время..',
|
||||
|
@ -1641,14 +1762,22 @@ const ru = {
|
|||
confirm_new_password: 'Подтверждение нового пароля',
|
||||
changed_password: 'Пароль изменён успешно.',
|
||||
change_password_error: 'Произошла ошибка при попытке изменить пароль.',
|
||||
limited_availability: 'Не доступно в вашем браузере'
|
||||
lock_account_description: 'Аккаунт доступен только подтверждённым подписчикам',
|
||||
limited_availability: 'Не доступно в вашем браузере',
|
||||
profile_tab: 'Профиль',
|
||||
security_tab: 'Безопасность',
|
||||
data_import_export_tab: 'Импорт / Экспорт данных',
|
||||
collapse_subject: 'Сворачивать посты с темой',
|
||||
interfaceLanguage: 'Язык интерфейса'
|
||||
},
|
||||
notifications: {
|
||||
notifications: 'Уведомления',
|
||||
read: 'Прочесть',
|
||||
followed_you: 'начал(а) читать вас',
|
||||
favorited_you: 'нравится ваш статус',
|
||||
repeated_you: 'повторил(а) ваш статус'
|
||||
repeated_you: 'повторил(а) ваш статус',
|
||||
broken_favorite: 'Неизвестный статус, ищем...',
|
||||
load_older: 'Загрузить старые уведомления'
|
||||
},
|
||||
login: {
|
||||
login: 'Войти',
|
||||
|
@ -1668,7 +1797,18 @@ const ru = {
|
|||
},
|
||||
post_status: {
|
||||
posting: 'Отправляется',
|
||||
default: 'Что нового?'
|
||||
content_warning: 'Тема (не обязательно)',
|
||||
default: 'Что нового?',
|
||||
account_not_locked_warning: 'Ваш аккаунт не {0}. Кто угодно может зафоловить вас чтобы прочитать посты только для подписчиков',
|
||||
account_not_locked_warning_link: 'залочен',
|
||||
direct_warning: 'Этот пост будет видет только упомянутым пользователям',
|
||||
attachments_sensitive: 'Вложения содержат чувствительный контент',
|
||||
scope: {
|
||||
public: 'Публичный - этот пост виден всем',
|
||||
unlisted: 'Непубличный - этот пост не виден на публичных лентах',
|
||||
private: 'Для подписчиков - этот пост видят только подписчики',
|
||||
direct: 'Личное - этот пост видят только те кто в нём упомянут'
|
||||
}
|
||||
},
|
||||
finder: {
|
||||
find_user: 'Найти пользователя',
|
||||
|
@ -1806,8 +1946,18 @@ const he = {
|
|||
chat: {
|
||||
title: 'צ\'אט'
|
||||
},
|
||||
features_panel: {
|
||||
chat: 'צ\'אט',
|
||||
gopher: 'גופר',
|
||||
media_proxy: 'מדיה פרוקסי',
|
||||
scope_options: 'אפשרויות טווח',
|
||||
text_limit: 'מגבלת טקסט',
|
||||
title: 'מאפיינים',
|
||||
who_to_follow: 'אחרי מי לעקוב'
|
||||
},
|
||||
nav: {
|
||||
chat: 'צ\'אט מקומי',
|
||||
friend_requests: 'בקשות עקיבה',
|
||||
timeline: 'ציר הזמן',
|
||||
mentions: 'אזכורים',
|
||||
public_tl: 'ציר הזמן הציבורי',
|
||||
|
@ -1825,7 +1975,9 @@ const he = {
|
|||
followers: 'עוקבים',
|
||||
followees: 'נעקבים',
|
||||
per_day: 'ליום',
|
||||
remote_follow: 'עקיבה מרחוק'
|
||||
remote_follow: 'עקיבה מרחוק',
|
||||
approve: 'אשר',
|
||||
deny: 'דחה'
|
||||
},
|
||||
timeline: {
|
||||
show_new: 'הראה חדש',
|
||||
|
@ -1834,7 +1986,8 @@ const he = {
|
|||
load_older: 'טען סטטוסים חדשים',
|
||||
conversation: 'שיחה',
|
||||
collapse: 'מוטט',
|
||||
repeated: 'חזר'
|
||||
repeated: 'חזר',
|
||||
no_retweet_hint: 'ההודעה מסומנת כ"לעוקבים-בלבד" ולא ניתן לחזור עליה'
|
||||
},
|
||||
settings: {
|
||||
user_settings: 'הגדרות משתמש',
|
||||
|
@ -1852,7 +2005,10 @@ const he = {
|
|||
settings: 'הגדרות',
|
||||
theme: 'תמה',
|
||||
presets: 'ערכים קבועים מראש',
|
||||
export_theme: 'שמור ערכים',
|
||||
import_theme: 'טען ערכים',
|
||||
theme_help: 'השתמש בקודי צבע הקס (#אדום-אדום-ירוק-ירוק-כחול-כחול) על מנת להתאים אישית את תמת הצבע שלך.',
|
||||
invalid_theme_imported: 'הקובץ הנבחר אינו תמה הנתמכת ע"י פלרומה. שום שינויים לא נעשו לתמה שלך.',
|
||||
radii_help: 'קבע מראש עיגול פינות לממשק (בפיקסלים)',
|
||||
background: 'רקע',
|
||||
foreground: 'חזית',
|
||||
|
@ -1875,10 +2031,23 @@ const he = {
|
|||
hide_attachments_in_tl: 'החבא צירופים בציר הזמן',
|
||||
hide_attachments_in_convo: 'החבא צירופים בשיחות',
|
||||
nsfw_clickthrough: 'החל החבאת צירופים לא בטוחים לצפיה בעת עבודה בעזרת לחיצת עכבר',
|
||||
collapse_subject: 'מזער הודעות עם נושאים',
|
||||
stop_gifs: 'נגן-בעת-ריחוף GIFs',
|
||||
autoload: 'החל טעינה אוטומטית בגלילה לתחתית הדף',
|
||||
streaming: 'החל זרימת הודעות אוטומטית בעת גלילה למעלה הדף',
|
||||
pause_on_unfocused: 'השהה זרימת הודעות כשהחלון לא בפוקוס',
|
||||
loop_video: 'נגן סרטונים ללא הפסקה',
|
||||
loop_video_silent_only: 'נגן רק סרטונים חסרי קול ללא הפסקה',
|
||||
reply_link_preview: 'החל תצוגה מקדימה של לינק-תגובה בעת ריחוף עם העכבר',
|
||||
replies_in_timeline: 'תגובות בציר הזמן',
|
||||
reply_visibility_all: 'הראה את כל התגובות',
|
||||
reply_visibility_following: 'הראה תגובות שמופנות אליי או לעקובים שלי בלבד',
|
||||
reply_visibility_self: 'הראה תגובות שמופנות אליי בלבד',
|
||||
notification_visibility: 'סוג ההתראות שתרצו לראות',
|
||||
notification_visibility_likes: 'לייקים',
|
||||
notification_visibility_mentions: 'אזכורים',
|
||||
notification_visibility_repeats: 'חזרות',
|
||||
notification_visibility_follows: 'עקיבות',
|
||||
follow_import: 'יבוא עקיבות',
|
||||
import_followers_from_a_csv_file: 'ייבא את הנעקבים שלך מקובץ csv',
|
||||
follows_imported: 'נעקבים יובאו! ייקח זמן מה לעבד אותם.',
|
||||
|
@ -1895,9 +2064,18 @@ const he = {
|
|||
new_password: 'סיסמה חדשה',
|
||||
confirm_new_password: 'אשר סיסמה',
|
||||
changed_password: 'סיסמה שונתה בהצלחה!',
|
||||
change_password_error: 'הייתה בעיה בשינוי סיסמתך.'
|
||||
change_password_error: 'הייתה בעיה בשינוי סיסמתך.',
|
||||
lock_account_description: 'הגבל את המשתמש לעוקבים מאושרים בלבד',
|
||||
limited_availability: 'לא זמין בדפדפן שלך',
|
||||
default_vis: 'ברירת מחדל לטווח הנראות',
|
||||
profile_tab: 'פרופיל',
|
||||
security_tab: 'ביטחון',
|
||||
data_import_export_tab: 'ייבוא או ייצוא מידע',
|
||||
interfaceLanguage: 'שפת הממשק'
|
||||
},
|
||||
notifications: {
|
||||
broken_favorite: 'סטאטוס לא ידוע, מחפש...',
|
||||
load_older: 'טען התראות ישנות',
|
||||
notifications: 'התראות',
|
||||
read: 'קרא!',
|
||||
followed_you: 'עקב אחריך!',
|
||||
|
@ -1917,9 +2095,24 @@ const he = {
|
|||
fullname: 'שם תצוגה',
|
||||
email: 'אימייל',
|
||||
bio: 'אודות',
|
||||
password_confirm: 'אישור סיסמה'
|
||||
password_confirm: 'אישור סיסמה',
|
||||
token: 'טוקן הזמנה'
|
||||
},
|
||||
post_status: {
|
||||
account_not_locked_warning: 'המשתמש שלך אינו {0}. כל אחד יכול לעקוב אחריך ולראות את ההודעות לעוקבים-בלבד שלך.',
|
||||
account_not_locked_warning_link: 'נעול',
|
||||
attachments_sensitive: 'סמן מסמכים מצורפים כלא בטוחים לצפייה',
|
||||
content_type: {
|
||||
plain_text: 'טקסט פשוט'
|
||||
},
|
||||
content_warning: 'נושא (נתון לבחירה)',
|
||||
direct_warning: 'הודעה זו תהיה זמינה רק לאנשים המוזכרים.',
|
||||
scope: {
|
||||
direct: 'ישיר - שלח לאנשים המוזכרים בלבד',
|
||||
private: 'עוקבים-בלבד - שלח לעוקבים בלבד',
|
||||
public: 'ציבורי - שלח לציר הזמן הציבורי',
|
||||
unlisted: 'מחוץ לרשימה - אל תשלח לציר הזמן הציבורי'
|
||||
},
|
||||
posting: 'מפרסם',
|
||||
default: 'הרגע נחת ב-ל.א.'
|
||||
},
|
||||
|
@ -1933,6 +2126,10 @@ const he = {
|
|||
},
|
||||
user_profile: {
|
||||
timeline_title: 'ציר זמן המשתמש'
|
||||
},
|
||||
who_to_follow: {
|
||||
who_to_follow: 'אחרי מי לעקוב',
|
||||
more: 'עוד'
|
||||
}
|
||||
}
|
||||
|
||||
|
|
143
src/main.js
143
src/main.js
|
@ -49,6 +49,8 @@ const persistedStateOptions = {
|
|||
'config.hideAttachments',
|
||||
'config.hideAttachmentsInConv',
|
||||
'config.hideNsfw',
|
||||
'config.replyVisibility',
|
||||
'config.notificationVisibility',
|
||||
'config.autoLoad',
|
||||
'config.hoverPreview',
|
||||
'config.streaming',
|
||||
|
@ -59,7 +61,9 @@ const persistedStateOptions = {
|
|||
'config.loopVideoSilentOnly',
|
||||
'config.pauseOnUnfocused',
|
||||
'config.stopGifs',
|
||||
'users.lastLoginName'
|
||||
'config.interfaceLanguage',
|
||||
'users.lastLoginName',
|
||||
'statuses.notifications.maxSavedId'
|
||||
]
|
||||
}
|
||||
|
||||
|
@ -77,6 +81,7 @@ const store = new Vuex.Store({
|
|||
})
|
||||
|
||||
const i18n = new VueI18n({
|
||||
// By default, use the browser locale, we will update it if neccessary
|
||||
locale: currentLocale,
|
||||
fallbackLocale: 'en',
|
||||
messages
|
||||
|
@ -91,65 +96,81 @@ window.fetch('/api/statusnet/config.json')
|
|||
store.dispatch('setOption', { name: 'registrationOpen', value: (registrationClosed === '0') })
|
||||
store.dispatch('setOption', { name: 'textlimit', value: parseInt(textlimit) })
|
||||
store.dispatch('setOption', { name: 'server', value: server })
|
||||
})
|
||||
|
||||
window.fetch('/static/config.json')
|
||||
.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')
|
||||
}
|
||||
var apiConfig = data.site.pleromafe
|
||||
|
||||
const routes = [
|
||||
{ name: 'root',
|
||||
path: '/',
|
||||
redirect: to => {
|
||||
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 }
|
||||
]
|
||||
window.fetch('/static/config.json')
|
||||
.then((res) => res.json())
|
||||
.then((data) => {
|
||||
var staticConfig = data
|
||||
// This takes static config and overrides properties that are present in apiConfig
|
||||
var config = Object.assign({}, staticConfig, apiConfig)
|
||||
|
||||
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 }
|
||||
var theme = (config.theme)
|
||||
var background = (config.background)
|
||||
var logo = (config.logo)
|
||||
var logoMask = (typeof config.logoMask === 'undefined' ? true : config.logoMask)
|
||||
var logoMargin = (typeof config.logoMargin === 'undefined' ? 0 : config.logoMargin)
|
||||
var redirectRootNoLogin = (config.redirectRootNoLogin)
|
||||
var redirectRootLogin = (config.redirectRootLogin)
|
||||
var chatDisabled = (config.chatDisabled)
|
||||
var showInstanceSpecificPanel = (config.showInstanceSpecificPanel)
|
||||
var scopeOptionsEnabled = (config.scopeOptionsEnabled)
|
||||
var formattingOptionsEnabled = (config.formattingOptionsEnabled)
|
||||
var defaultCollapseMessageWithSubject = (config.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: 'logoMask', value: logoMask })
|
||||
store.dispatch('setOption', { name: 'logoMargin', value: logoMargin })
|
||||
store.dispatch('setOption', { name: 'showInstanceSpecificPanel', value: showInstanceSpecificPanel })
|
||||
store.dispatch('setOption', { name: 'scopeOptionsEnabled', value: scopeOptionsEnabled })
|
||||
store.dispatch('setOption', { name: 'formattingOptionsEnabled', value: formattingOptionsEnabled })
|
||||
store.dispatch('setOption', { name: 'defaultCollapseMessageWithSubject', value: defaultCollapseMessageWithSubject })
|
||||
if (chatDisabled) {
|
||||
store.dispatch('disableChat')
|
||||
}
|
||||
})
|
||||
|
||||
/* eslint-disable no-new */
|
||||
new Vue({
|
||||
router,
|
||||
store,
|
||||
i18n,
|
||||
el: '#app',
|
||||
render: h => h(App)
|
||||
const routes = [
|
||||
{ name: 'root',
|
||||
path: '/',
|
||||
redirect: to => {
|
||||
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({
|
||||
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 +213,15 @@ window.fetch('/instance/panel.html')
|
|||
store.dispatch('setOption', { name: 'instanceSpecificPanelContent', value: html })
|
||||
})
|
||||
|
||||
window.fetch('/nodeinfo/2.0.json')
|
||||
.then((res) => res.json())
|
||||
.then((data) => {
|
||||
const metadata = data.metadata
|
||||
store.dispatch('setOption', { name: 'mediaProxyAvailable', value: data.metadata.mediaProxy })
|
||||
store.dispatch('setOption', { name: 'chatAvailable', value: data.metadata.chat })
|
||||
store.dispatch('setOption', { name: 'gopherAvailable', value: data.metadata.gopher })
|
||||
|
||||
const suggestions = metadata.suggestions
|
||||
store.dispatch('setOption', { name: 'suggestionsEnabled', value: suggestions.enabled })
|
||||
store.dispatch('setOption', { name: 'suggestionsWeb', value: suggestions.web })
|
||||
})
|
||||
|
|
|
@ -46,6 +46,9 @@ const api = {
|
|||
store.commit('addFetcher', {timeline, fetcher})
|
||||
}
|
||||
},
|
||||
fetchOldPost (store, { postId }) {
|
||||
store.state.backendInteractor.fetchOldPost({ store, postId })
|
||||
},
|
||||
stopFetching (store, timeline) {
|
||||
const fetcher = store.state.fetchers[timeline]
|
||||
window.clearInterval(fetcher)
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
import { set, delete as del } from 'vue'
|
||||
import StyleSetter from '../services/style_setter/style_setter.js'
|
||||
|
||||
const browserLocale = (window.navigator.language || 'en').split('-')[0]
|
||||
|
||||
const defaultState = {
|
||||
name: 'Pleroma FE',
|
||||
colors: {},
|
||||
|
@ -15,8 +17,16 @@ const defaultState = {
|
|||
hoverPreview: true,
|
||||
pauseOnUnfocused: true,
|
||||
stopGifs: false,
|
||||
replyVisibility: 'all',
|
||||
notificationVisibility: {
|
||||
follows: true,
|
||||
mentions: true,
|
||||
likes: true,
|
||||
repeats: true
|
||||
},
|
||||
muteWords: [],
|
||||
highlight: {}
|
||||
highlight: {},
|
||||
interfaceLanguage: browserLocale
|
||||
}
|
||||
|
||||
const config = {
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import { includes, remove, slice, sortBy, toInteger, each, find, flatten, maxBy, minBy, merge, last, isArray } from 'lodash'
|
||||
import { set } from 'vue'
|
||||
import apiService from '../services/api/api.service.js'
|
||||
// import parse from '../services/status_parser/status_parser.js'
|
||||
|
||||
|
@ -22,13 +23,22 @@ export const defaultState = {
|
|||
allStatuses: [],
|
||||
allStatusesObject: {},
|
||||
maxId: 0,
|
||||
notifications: [],
|
||||
notifications: {
|
||||
desktopNotificationSilence: true,
|
||||
maxId: 0,
|
||||
maxSavedId: 0,
|
||||
minId: Number.POSITIVE_INFINITY,
|
||||
data: [],
|
||||
error: false,
|
||||
brokenFavorites: {}
|
||||
},
|
||||
favorites: new Set(),
|
||||
error: false,
|
||||
timelines: {
|
||||
mentions: emptyTl(),
|
||||
public: emptyTl(),
|
||||
user: emptyTl(),
|
||||
own: emptyTl(),
|
||||
publicAndExternal: emptyTl(),
|
||||
friends: emptyTl(),
|
||||
tag: emptyTl()
|
||||
|
@ -58,6 +68,15 @@ export const prepareStatus = (status) => {
|
|||
return status
|
||||
}
|
||||
|
||||
const visibleNotificationTypes = (rootState) => {
|
||||
return [
|
||||
rootState.config.notificationVisibility.likes && 'like',
|
||||
rootState.config.notificationVisibility.mentions && 'mention',
|
||||
rootState.config.notificationVisibility.repeats && 'repeat',
|
||||
rootState.config.notificationVisibility.follows && 'follow'
|
||||
].filter(_ => _)
|
||||
}
|
||||
|
||||
export const statusType = (status) => {
|
||||
if (status.is_post_verb) {
|
||||
return 'status'
|
||||
|
@ -76,8 +95,7 @@ export const statusType = (status) => {
|
|||
return 'deletion'
|
||||
}
|
||||
|
||||
// TODO change to status.activity_type === 'follow' when gs supports it
|
||||
if (status.text.match(/started following/)) {
|
||||
if (status.text.match(/started following/) || status.activity_type === 'follow') {
|
||||
return 'follow'
|
||||
}
|
||||
|
||||
|
@ -134,11 +152,13 @@ const addNewStatuses = (state, { statuses, showImmediately = false, timeline, us
|
|||
const result = mergeOrAdd(allStatuses, allStatusesObject, status)
|
||||
status = result.item
|
||||
|
||||
if (result.new) {
|
||||
if (statusType(status) === 'retweet' && status.retweeted_status.user.id === user.id) {
|
||||
addNotification({ type: 'repeat', status: status, action: status })
|
||||
}
|
||||
const brokenFavorites = state.notifications.brokenFavorites[status.id] || []
|
||||
brokenFavorites.forEach((fav) => {
|
||||
fav.status = status
|
||||
})
|
||||
delete state.notifications.brokenFavorites[status.id]
|
||||
|
||||
if (result.new) {
|
||||
// We are mentioned in a post
|
||||
if (statusType(status) === 'status' && find(status.attentions, { id: user.id })) {
|
||||
const mentions = state.timelines.mentions
|
||||
|
@ -150,10 +170,6 @@ const addNewStatuses = (state, { statuses, showImmediately = false, timeline, us
|
|||
|
||||
sortTimeline(mentions)
|
||||
}
|
||||
// Don't add notification for self-mention
|
||||
if (status.user.id !== user.id) {
|
||||
addNotification({ type: 'mention', status, action: status })
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -176,45 +192,14 @@ const addNewStatuses = (state, { statuses, showImmediately = false, timeline, us
|
|||
return status
|
||||
}
|
||||
|
||||
const addNotification = ({type, status, action}) => {
|
||||
// Only add a new notification if we don't have one for the same action
|
||||
if (!find(state.notifications, (oldNotification) => oldNotification.action.id === action.id)) {
|
||||
state.notifications.push({ type, status, action, seen: false })
|
||||
|
||||
if ('Notification' in window && window.Notification.permission === 'granted') {
|
||||
const title = action.user.name
|
||||
const result = {}
|
||||
result.icon = action.user.profile_image_url
|
||||
result.body = action.text // there's a problem that it doesn't put a space before links tho
|
||||
|
||||
// Shows first attached non-nsfw image, if any. Should add configuration for this somehow...
|
||||
if (action.attachments && action.attachments.length > 0 && !action.nsfw &&
|
||||
action.attachments[0].mimetype.startsWith('image/')) {
|
||||
result.image = action.attachments[0].url
|
||||
}
|
||||
|
||||
let notification = new window.Notification(title, result)
|
||||
|
||||
// Chrome is known for not closing notifications automatically
|
||||
// according to MDN, anyway.
|
||||
setTimeout(notification.close.bind(notification), 5000)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const favoriteStatus = (favorite) => {
|
||||
const favoriteStatus = (favorite, counter) => {
|
||||
const status = find(allStatuses, { id: toInteger(favorite.in_reply_to_status_id) })
|
||||
if (status) {
|
||||
status.fave_num += 1
|
||||
|
||||
// This is our favorite, so the relevant bit.
|
||||
if (favorite.user.id === user.id) {
|
||||
status.favorited = true
|
||||
}
|
||||
|
||||
// Add a notification if the user's status is favorited
|
||||
if (status.user.id === user.id) {
|
||||
addNotification({type: 'favorite', status, action: favorite})
|
||||
} else {
|
||||
status.fave_num += 1
|
||||
}
|
||||
}
|
||||
return status
|
||||
|
@ -248,18 +233,12 @@ const addNewStatuses = (state, { statuses, showImmediately = false, timeline, us
|
|||
},
|
||||
'favorite': (favorite) => {
|
||||
// Only update if this is a new favorite.
|
||||
// Ignore our own favorites because we get info about likes as response to like request
|
||||
if (!state.favorites.has(favorite.id)) {
|
||||
state.favorites.add(favorite.id)
|
||||
favoriteStatus(favorite)
|
||||
}
|
||||
},
|
||||
'follow': (status) => {
|
||||
let re = new RegExp(`started following ${user.name} \\(${user.statusnet_profile_url}\\)`)
|
||||
let repleroma = new RegExp(`started following ${user.screen_name}$`)
|
||||
if (status.text.match(re) || status.text.match(repleroma)) {
|
||||
addNotification({ type: 'follow', status: status, action: status })
|
||||
}
|
||||
},
|
||||
'deletion': (deletion) => {
|
||||
const uri = deletion.uri
|
||||
|
||||
|
@ -269,7 +248,7 @@ const addNewStatuses = (state, { statuses, showImmediately = false, timeline, us
|
|||
return
|
||||
}
|
||||
|
||||
remove(state.notifications, ({action: {id}}) => id === status.id)
|
||||
remove(state.notifications.data, ({action: {id}}) => id === status.id)
|
||||
|
||||
remove(allStatuses, { uri })
|
||||
if (timeline) {
|
||||
|
@ -298,8 +277,69 @@ const addNewStatuses = (state, { statuses, showImmediately = false, timeline, us
|
|||
}
|
||||
}
|
||||
|
||||
const addNewNotifications = (state, { dispatch, notifications, older, visibleNotificationTypes }) => {
|
||||
const allStatuses = state.allStatuses
|
||||
const allStatusesObject = state.allStatusesObject
|
||||
each(notifications, (notification) => {
|
||||
const result = mergeOrAdd(allStatuses, allStatusesObject, notification.notice)
|
||||
const action = result.item
|
||||
// Only add a new notification if we don't have one for the same action
|
||||
if (!find(state.notifications.data, (oldNotification) => oldNotification.action.id === action.id)) {
|
||||
state.notifications.maxId = Math.max(notification.id, state.notifications.maxId)
|
||||
state.notifications.minId = Math.min(notification.id, state.notifications.minId)
|
||||
|
||||
const fresh = !older && !notification.is_seen && notification.id > state.notifications.maxSavedId
|
||||
const status = notification.ntype === 'like'
|
||||
? find(allStatuses, { id: action.in_reply_to_status_id })
|
||||
: action
|
||||
|
||||
const result = {
|
||||
type: notification.ntype,
|
||||
status,
|
||||
action,
|
||||
// Always assume older notifications as seen
|
||||
seen: !fresh
|
||||
}
|
||||
|
||||
if (notification.ntype === 'like' && !status) {
|
||||
let broken = state.notifications.brokenFavorites[action.in_reply_to_status_id]
|
||||
if (broken) {
|
||||
broken.push(result)
|
||||
} else {
|
||||
dispatch('fetchOldPost', { postId: action.in_reply_to_status_id })
|
||||
broken = [ result ]
|
||||
state.notifications.brokenFavorites[action.in_reply_to_status_id] = broken
|
||||
}
|
||||
}
|
||||
|
||||
state.notifications.data.push(result)
|
||||
|
||||
if ('Notification' in window && window.Notification.permission === 'granted') {
|
||||
const title = action.user.name
|
||||
const result = {}
|
||||
result.icon = action.user.profile_image_url
|
||||
result.body = action.text // there's a problem that it doesn't put a space before links tho
|
||||
|
||||
// Shows first attached non-nsfw image, if any. Should add configuration for this somehow...
|
||||
if (action.attachments && action.attachments.length > 0 && !action.nsfw &&
|
||||
action.attachments[0].mimetype.startsWith('image/')) {
|
||||
result.image = action.attachments[0].url
|
||||
}
|
||||
|
||||
if (fresh && !state.notifications.desktopNotificationSilence && visibleNotificationTypes.includes(notification.ntype)) {
|
||||
let notification = new window.Notification(title, result)
|
||||
// Chrome is known for not closing notifications automatically
|
||||
// according to MDN, anyway.
|
||||
setTimeout(notification.close.bind(notification), 5000)
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
export const mutations = {
|
||||
addNewStatuses,
|
||||
addNewNotifications,
|
||||
showNewStatuses (state, { timeline }) {
|
||||
const oldTimeline = (state.timelines[timeline])
|
||||
|
||||
|
@ -316,6 +356,11 @@ export const mutations = {
|
|||
const newStatus = state.allStatusesObject[status.id]
|
||||
newStatus.favorited = value
|
||||
},
|
||||
setFavoritedConfirm (state, { status }) {
|
||||
const newStatus = state.allStatusesObject[status.id]
|
||||
newStatus.favorited = status.favorited
|
||||
newStatus.fave_num = status.fave_num
|
||||
},
|
||||
setRetweeted (state, { status, value }) {
|
||||
const newStatus = state.allStatusesObject[status.id]
|
||||
newStatus.repeated = value
|
||||
|
@ -334,6 +379,12 @@ export const mutations = {
|
|||
setError (state, { value }) {
|
||||
state.error = value
|
||||
},
|
||||
setNotificationsError (state, { value }) {
|
||||
state.notifications.error = value
|
||||
},
|
||||
setNotificationsSilence (state, { value }) {
|
||||
state.notifications.desktopNotificationSilence = value
|
||||
},
|
||||
setProfileView (state, { v }) {
|
||||
// load followers / friends only when needed
|
||||
state.timelines['user'].viewing = v
|
||||
|
@ -345,6 +396,7 @@ export const mutations = {
|
|||
state.timelines['user'].followers = followers
|
||||
},
|
||||
markNotificationsAsSeen (state, notifications) {
|
||||
set(state.notifications, 'maxSavedId', state.notifications.maxId)
|
||||
each(notifications, (notification) => {
|
||||
notification.seen = true
|
||||
})
|
||||
|
@ -360,9 +412,18 @@ const statuses = {
|
|||
addNewStatuses ({ rootState, commit }, { statuses, showImmediately = false, timeline = false, noIdUpdate = false }) {
|
||||
commit('addNewStatuses', { statuses, showImmediately, timeline, noIdUpdate, user: rootState.users.currentUser })
|
||||
},
|
||||
addNewNotifications ({ rootState, commit, dispatch }, { notifications, older }) {
|
||||
commit('addNewNotifications', { visibleNotificationTypes: visibleNotificationTypes(rootState), dispatch, notifications, older })
|
||||
},
|
||||
setError ({ rootState, commit }, { value }) {
|
||||
commit('setError', { value })
|
||||
},
|
||||
setNotificationsError ({ rootState, commit }, { value }) {
|
||||
commit('setNotificationsError', { value })
|
||||
},
|
||||
setNotificationsSilence ({ rootState, commit }, { value }) {
|
||||
commit('setNotificationsSilence', { value })
|
||||
},
|
||||
addFriends ({ rootState, commit }, { friends }) {
|
||||
commit('addFriends', { friends })
|
||||
},
|
||||
|
@ -377,11 +438,31 @@ const statuses = {
|
|||
// Optimistic favoriting...
|
||||
commit('setFavorited', { status, value: true })
|
||||
apiService.favorite({ id: status.id, credentials: rootState.users.currentUser.credentials })
|
||||
.then(response => {
|
||||
if (response.ok) {
|
||||
return response.json()
|
||||
} else {
|
||||
return {}
|
||||
}
|
||||
})
|
||||
.then(status => {
|
||||
commit('setFavoritedConfirm', { status })
|
||||
})
|
||||
},
|
||||
unfavorite ({ rootState, commit }, status) {
|
||||
// Optimistic favoriting...
|
||||
commit('setFavorited', { status, value: false })
|
||||
apiService.unfavorite({ id: status.id, credentials: rootState.users.currentUser.credentials })
|
||||
.then(response => {
|
||||
if (response.ok) {
|
||||
return response.json()
|
||||
} else {
|
||||
return {}
|
||||
}
|
||||
})
|
||||
.then(status => {
|
||||
commit('setFavoritedConfirm', { status })
|
||||
})
|
||||
},
|
||||
retweet ({ rootState, commit }, status) {
|
||||
// Optimistic retweeting...
|
||||
|
|
|
@ -107,6 +107,8 @@ const users = {
|
|||
|
||||
// Start getting fresh tweets.
|
||||
store.dispatch('startFetching', 'friends')
|
||||
// Start getting our own posts, only really needed for mitigating broken favorites
|
||||
store.dispatch('startFetching', ['own', user.id])
|
||||
|
||||
// Get user mutes and follower info
|
||||
store.rootState.api.backendInteractor.fetchMutes().then((mutedUsers) => {
|
||||
|
@ -119,7 +121,7 @@ const users = {
|
|||
}
|
||||
|
||||
// Fetch our friends
|
||||
store.rootState.api.backendInteractor.fetchFriends()
|
||||
store.rootState.api.backendInteractor.fetchFriends({id: user.id})
|
||||
.then((friends) => commit('addNewUsers', friends))
|
||||
})
|
||||
} else {
|
||||
|
|
|
@ -27,6 +27,7 @@ const BANNER_UPDATE_URL = '/api/account/update_profile_banner.json'
|
|||
const PROFILE_UPDATE_URL = '/api/account/update_profile.json'
|
||||
const EXTERNAL_PROFILE_URL = '/api/externalprofile/show.json'
|
||||
const QVITTER_USER_TIMELINE_URL = '/api/qvitter/statuses/user_timeline.json'
|
||||
const QVITTER_USER_NOTIFICATIONS_URL = '/api/qvitter/statuses/notifications.json'
|
||||
const BLOCKING_URL = '/api/blocks/create.json'
|
||||
const UNBLOCKING_URL = '/api/blocks/destroy.json'
|
||||
const USER_URL = '/api/users/show.json'
|
||||
|
@ -36,6 +37,7 @@ const CHANGE_PASSWORD_URL = '/api/pleroma/change_password'
|
|||
const FOLLOW_REQUESTS_URL = '/api/pleroma/friend_requests'
|
||||
const APPROVE_USER_URL = '/api/pleroma/friendships/approve'
|
||||
const DENY_USER_URL = '/api/pleroma/friendships/deny'
|
||||
const SUGGESTIONS_URL = '/api/v1/suggestions'
|
||||
|
||||
import { each, map } from 'lodash'
|
||||
import 'whatwg-fetch'
|
||||
|
@ -302,8 +304,12 @@ const fetchTimeline = ({timeline, credentials, since = false, until = false, use
|
|||
public: PUBLIC_TIMELINE_URL,
|
||||
friends: FRIENDS_TIMELINE_URL,
|
||||
mentions: MENTIONS_URL,
|
||||
notifications: QVITTER_USER_NOTIFICATIONS_URL,
|
||||
'publicAndExternal': PUBLIC_AND_EXTERNAL_TIMELINE_URL,
|
||||
user: QVITTER_USER_TIMELINE_URL,
|
||||
// separate timeline for own posts, so it won't break due to user timeline bugs
|
||||
// really needed only for broken favorites
|
||||
own: QVITTER_USER_TIMELINE_URL,
|
||||
tag: TAG_TIMELINE_URL
|
||||
}
|
||||
|
||||
|
@ -367,7 +373,7 @@ const unretweet = ({ id, credentials }) => {
|
|||
})
|
||||
}
|
||||
|
||||
const postStatus = ({credentials, status, spoilerText, visibility, mediaIds, inReplyToStatusId}) => {
|
||||
const postStatus = ({credentials, status, spoilerText, visibility, sensitive, mediaIds, inReplyToStatusId, contentType}) => {
|
||||
const idsText = mediaIds.join(',')
|
||||
const form = new FormData()
|
||||
|
||||
|
@ -375,6 +381,8 @@ const postStatus = ({credentials, status, spoilerText, visibility, mediaIds, inR
|
|||
form.append('source', 'Pleroma FE')
|
||||
if (spoilerText) form.append('spoiler_text', spoilerText)
|
||||
if (visibility) form.append('visibility', visibility)
|
||||
if (sensitive) form.append('sensitive', sensitive)
|
||||
if (contentType) form.append('content_type', contentType)
|
||||
form.append('media_ids', idsText)
|
||||
if (inReplyToStatusId) {
|
||||
form.append('in_reply_to_status_id', inReplyToStatusId)
|
||||
|
@ -449,6 +457,12 @@ const fetchMutes = ({credentials}) => {
|
|||
}).then((data) => data.json())
|
||||
}
|
||||
|
||||
const suggestions = ({credentials}) => {
|
||||
return fetch(SUGGESTIONS_URL, {
|
||||
headers: authHeaders(credentials)
|
||||
}).then((data) => data.json())
|
||||
}
|
||||
|
||||
const apiService = {
|
||||
verifyCredentials,
|
||||
fetchTimeline,
|
||||
|
@ -482,7 +496,8 @@ const apiService = {
|
|||
changePassword,
|
||||
fetchFollowRequests,
|
||||
approveUser,
|
||||
denyUser
|
||||
denyUser,
|
||||
suggestions
|
||||
}
|
||||
|
||||
export default apiService
|
||||
|
|
|
@ -54,6 +54,16 @@ const backendInteractorService = (credentials) => {
|
|||
return timelineFetcherService.startFetching({timeline, store, credentials, userId})
|
||||
}
|
||||
|
||||
const fetchOldPost = ({store, postId}) => {
|
||||
return timelineFetcherService.fetchAndUpdate({
|
||||
store,
|
||||
credentials,
|
||||
timeline: 'own',
|
||||
older: true,
|
||||
until: postId + 1
|
||||
})
|
||||
}
|
||||
|
||||
const setUserMute = ({id, muted = true}) => {
|
||||
return apiService.setUserMute({id, muted, credentials})
|
||||
}
|
||||
|
@ -86,6 +96,7 @@ const backendInteractorService = (credentials) => {
|
|||
fetchAllFollowing,
|
||||
verifyCredentials: apiService.verifyCredentials,
|
||||
startFetching,
|
||||
fetchOldPost,
|
||||
setUserMute,
|
||||
fetchMutes,
|
||||
register,
|
||||
|
|
|
@ -0,0 +1,46 @@
|
|||
import apiService from '../api/api.service.js'
|
||||
|
||||
const update = ({store, notifications, older}) => {
|
||||
store.dispatch('setNotificationsError', { value: false })
|
||||
|
||||
store.dispatch('addNewNotifications', { notifications, older })
|
||||
}
|
||||
|
||||
const fetchAndUpdate = ({store, credentials, older = false}) => {
|
||||
const args = { credentials }
|
||||
const rootState = store.rootState || store.state
|
||||
const timelineData = rootState.statuses.notifications
|
||||
|
||||
if (older) {
|
||||
if (timelineData.minId !== Number.POSITIVE_INFINITY) {
|
||||
args['until'] = timelineData.minId
|
||||
}
|
||||
} else {
|
||||
args['since'] = timelineData.maxId
|
||||
}
|
||||
|
||||
args['timeline'] = 'notifications'
|
||||
|
||||
return apiService.fetchTimeline(args)
|
||||
.then((notifications) => {
|
||||
update({store, notifications, older})
|
||||
}, () => store.dispatch('setNotificationsError', { value: true }))
|
||||
.catch(() => store.dispatch('setNotificationsError', { value: true }))
|
||||
}
|
||||
|
||||
const startFetching = ({credentials, store}) => {
|
||||
fetchAndUpdate({ credentials, store })
|
||||
const boundFetchAndUpdate = () => fetchAndUpdate({ credentials, store })
|
||||
// Initially there's set flag to silence all desktop notifications so
|
||||
// that there won't spam of them when user just opened up the FE we
|
||||
// reset that flag after a while to show new notifications once again.
|
||||
setTimeout(() => store.dispatch('setNotificationsSilence', false), 10000)
|
||||
return setInterval(boundFetchAndUpdate, 10000)
|
||||
}
|
||||
|
||||
const notificationsFetcher = {
|
||||
fetchAndUpdate,
|
||||
startFetching
|
||||
}
|
||||
|
||||
export default notificationsFetcher
|
|
@ -1,10 +1,10 @@
|
|||
import { map } from 'lodash'
|
||||
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, contentType = 'text/plain' }) => {
|
||||
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, contentType})
|
||||
.then((data) => data.json())
|
||||
.then((data) => {
|
||||
if (!data.error) {
|
||||
|
|
|
@ -14,13 +14,13 @@ const update = ({store, statuses, timeline, showImmediately}) => {
|
|||
})
|
||||
}
|
||||
|
||||
const fetchAndUpdate = ({store, credentials, timeline = 'friends', older = false, showImmediately = false, userId = false, tag = false}) => {
|
||||
const fetchAndUpdate = ({store, credentials, timeline = 'friends', older = false, showImmediately = false, userId = false, tag = false, until}) => {
|
||||
const args = { timeline, credentials }
|
||||
const rootState = store.rootState || store.state
|
||||
const timelineData = rootState.statuses.timelines[camelCase(timeline)]
|
||||
|
||||
if (older) {
|
||||
args['until'] = timelineData.minVisibleId
|
||||
args['until'] = until || timelineData.minVisibleId
|
||||
} else {
|
||||
args['since'] = timelineData.maxId
|
||||
}
|
||||
|
|
|
@ -2,15 +2,13 @@
|
|||
"theme": "fairyfloss",
|
||||
"background": "/static/windows_xp_bliss.jpg",
|
||||
"logo": "/static/logo.png",
|
||||
"logoMask": true,
|
||||
"logoMargin": ".1em",
|
||||
"redirectRootNoLogin": "/main/all",
|
||||
"redirectRootLogin": "/main/friends",
|
||||
"chatDisabled": false,
|
||||
"showWhoToFollowPanel": false,
|
||||
"whoToFollowProvider": "https://vinayaka.distsn.org/cgi-bin/vinayaka-user-match-osa-api.cgi?{{host}}+{{user}}",
|
||||
"whoToFollowProviderDummy2": "https://followlink.osa-p.net/api/get_recommend.json?acct=@{{user}}@{{host}}",
|
||||
"whoToFollowLink": "https://vinayaka.distsn.org/?{{host}}+{{user}}",
|
||||
"whoToFollowLinkDummy2": "https://followlink.osa-p.net/recommend.html",
|
||||
"chatDisabled": true,
|
||||
"showInstanceSpecificPanel": true,
|
||||
"scopeOptionsEnabled": true,
|
||||
"formattingOptionsEnabled": true,
|
||||
"collapseMessageWithSubject": true
|
||||
}
|
||||
|
|
|
@ -247,7 +247,7 @@ describe('The Statuses module', () => {
|
|||
in_reply_to_status_id: '1', // The API uses strings here...
|
||||
uri: 'tag:shitposter.club,2016-08-21:fave:3895:note:773501:2016-08-21T16:52:15+00:00',
|
||||
text: 'a favorited something by b',
|
||||
user: {}
|
||||
user: { id: 99 }
|
||||
}
|
||||
|
||||
mutations.addNewStatuses(state, { statuses: [status], showImmediately: true, timeline: 'public' })
|
||||
|
@ -264,7 +264,7 @@ describe('The Statuses module', () => {
|
|||
expect(state.timelines.public.visibleStatuses[0].fave_num).to.eql(1)
|
||||
expect(state.timelines.public.maxId).to.eq(favorite.id)
|
||||
|
||||
// If something is favorited by the current user, it also sets the 'favorited' property
|
||||
// If something is favorited by the current user, it also sets the 'favorited' property but does not increment counter to avoid over-counting. Counter is incremented (updated, really) via response to the favorite request.
|
||||
const user = {
|
||||
id: 1
|
||||
}
|
||||
|
@ -281,45 +281,11 @@ describe('The Statuses module', () => {
|
|||
mutations.addNewStatuses(state, { statuses: [ownFavorite], showImmediately: true, timeline: 'public', user })
|
||||
|
||||
expect(state.timelines.public.visibleStatuses.length).to.eql(1)
|
||||
expect(state.timelines.public.visibleStatuses[0].fave_num).to.eql(2)
|
||||
expect(state.timelines.public.visibleStatuses[0].fave_num).to.eql(1)
|
||||
expect(state.timelines.public.visibleStatuses[0].favorited).to.eql(true)
|
||||
})
|
||||
|
||||
describe('notifications', () => {
|
||||
it('adds a notfications for retweets if you are the retweetet', () => {
|
||||
const user = { id: 1 }
|
||||
const state = cloneDeep(defaultState)
|
||||
const status = makeMockStatus({id: 1})
|
||||
status.user = user
|
||||
const retweet = makeMockStatus({id: 2, is_post_verb: false})
|
||||
retweet.retweeted_status = status
|
||||
|
||||
mutations.addNewStatuses(state, { statuses: [retweet], user })
|
||||
|
||||
expect(state.notifications.length).to.eql(1)
|
||||
expect(state.notifications[0].status).to.eql(retweet)
|
||||
expect(state.notifications[0].action).to.eql(retweet)
|
||||
expect(state.notifications[0].type).to.eql('repeat')
|
||||
})
|
||||
|
||||
it('adds a notification when you are mentioned', () => {
|
||||
const user = { id: 1 }
|
||||
const state = cloneDeep(defaultState)
|
||||
const status = makeMockStatus({id: 1})
|
||||
const mentionedStatus = makeMockStatus({id: 2})
|
||||
mentionedStatus.attentions = [user]
|
||||
|
||||
mutations.addNewStatuses(state, { statuses: [status], user })
|
||||
|
||||
expect(state.notifications.length).to.eql(0)
|
||||
|
||||
mutations.addNewStatuses(state, { statuses: [mentionedStatus], user })
|
||||
expect(state.notifications.length).to.eql(1)
|
||||
expect(state.notifications[0].status).to.eql(mentionedStatus)
|
||||
expect(state.notifications[0].action).to.eql(mentionedStatus)
|
||||
expect(state.notifications[0].type).to.eql('mention')
|
||||
})
|
||||
|
||||
it('removes a notification when the notice gets removed', () => {
|
||||
const user = { id: 1 }
|
||||
const state = cloneDeep(defaultState)
|
||||
|
@ -335,92 +301,39 @@ describe('The Statuses module', () => {
|
|||
deletion.uri = 'xxx'
|
||||
|
||||
mutations.addNewStatuses(state, { statuses: [status, otherStatus], user })
|
||||
mutations.addNewNotifications(
|
||||
state,
|
||||
{
|
||||
notifications: [{
|
||||
ntype: 'mention',
|
||||
status: otherStatus,
|
||||
notice: otherStatus,
|
||||
is_seen: false
|
||||
}]
|
||||
})
|
||||
|
||||
expect(state.notifications.length).to.eql(1)
|
||||
expect(state.notifications.data.length).to.eql(1)
|
||||
mutations.addNewNotifications(
|
||||
state,
|
||||
{
|
||||
notifications: [{
|
||||
ntype: 'mention',
|
||||
status: mentionedStatus,
|
||||
notice: mentionedStatus,
|
||||
is_seen: false
|
||||
}]
|
||||
})
|
||||
|
||||
mutations.addNewStatuses(state, { statuses: [mentionedStatus], user })
|
||||
expect(state.allStatuses.length).to.eql(3)
|
||||
expect(state.notifications.length).to.eql(2)
|
||||
expect(state.notifications[1].status).to.eql(mentionedStatus)
|
||||
expect(state.notifications[1].action).to.eql(mentionedStatus)
|
||||
expect(state.notifications[1].type).to.eql('mention')
|
||||
expect(state.notifications.data.length).to.eql(2)
|
||||
expect(state.notifications.data[1].status).to.eql(mentionedStatus)
|
||||
expect(state.notifications.data[1].action).to.eql(mentionedStatus)
|
||||
expect(state.notifications.data[1].type).to.eql('mention')
|
||||
|
||||
mutations.addNewStatuses(state, { statuses: [deletion], user })
|
||||
expect(state.allStatuses.length).to.eql(2)
|
||||
expect(state.notifications.length).to.eql(1)
|
||||
})
|
||||
|
||||
it('adds the message to mentions when you are mentioned', () => {
|
||||
const user = { id: 1 }
|
||||
const state = cloneDeep(defaultState)
|
||||
const status = makeMockStatus({id: 1})
|
||||
const mentionedStatus = makeMockStatus({id: 2})
|
||||
mentionedStatus.attentions = [user]
|
||||
|
||||
mutations.addNewStatuses(state, { statuses: [status], user })
|
||||
|
||||
expect(state.timelines.mentions.statuses).to.have.length(0)
|
||||
|
||||
mutations.addNewStatuses(state, { statuses: [mentionedStatus], user })
|
||||
expect(state.timelines.mentions.statuses).to.have.length(1)
|
||||
expect(state.timelines.mentions.statuses).to.eql([mentionedStatus])
|
||||
})
|
||||
|
||||
it('adds a notfication when one of the user\'s status is favorited', () => {
|
||||
const state = cloneDeep(defaultState)
|
||||
const status = makeMockStatus({id: 1})
|
||||
const user = {id: 1}
|
||||
status.user = user
|
||||
|
||||
const favorite = {
|
||||
id: 2,
|
||||
is_post_verb: false,
|
||||
in_reply_to_status_id: '1', // The API uses strings here...
|
||||
uri: 'tag:shitposter.club,2016-08-21:fave:3895:note:773501:2016-08-21T16:52:15+00:00',
|
||||
text: 'a favorited something by b',
|
||||
user: {}
|
||||
}
|
||||
|
||||
mutations.addNewStatuses(state, { statuses: [status], showImmediately: true, timeline: 'public', user })
|
||||
mutations.addNewStatuses(state, { statuses: [favorite], showImmediately: true, timeline: 'public', user })
|
||||
|
||||
expect(state.notifications).to.have.length(1)
|
||||
})
|
||||
|
||||
it('adds a notification when the user is followed', () => {
|
||||
const state = cloneDeep(defaultState)
|
||||
const user = {id: 1, screen_name: 'b'}
|
||||
const follower = {id: 2, screen_name: 'a'}
|
||||
|
||||
const follow = {
|
||||
id: 3,
|
||||
is_post_verb: false,
|
||||
activity_type: 'follow',
|
||||
text: 'a started following b',
|
||||
user: follower
|
||||
}
|
||||
|
||||
mutations.addNewStatuses(state, { statuses: [follow], showImmediately: true, timeline: 'public', user })
|
||||
|
||||
expect(state.notifications).to.have.length(1)
|
||||
})
|
||||
|
||||
it('does not add a notification when an other user is followed', () => {
|
||||
const state = cloneDeep(defaultState)
|
||||
const user = {id: 1, screen_name: 'b'}
|
||||
const follower = {id: 2, screen_name: 'a'}
|
||||
|
||||
const follow = {
|
||||
id: 3,
|
||||
is_post_verb: false,
|
||||
activity_type: 'follow',
|
||||
text: 'a started following b@shitposter.club',
|
||||
user: follower
|
||||
}
|
||||
|
||||
mutations.addNewStatuses(state, { statuses: [follow], showImmediately: true, timeline: 'public', user })
|
||||
|
||||
expect(state.notifications).to.have.length(0)
|
||||
expect(state.notifications.data.length).to.eql(1)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
|
115
yarn.lock
115
yarn.lock
|
@ -434,6 +434,10 @@ babel-helper-replace-supers@^6.24.1:
|
|||
babel-traverse "^6.24.1"
|
||||
babel-types "^6.24.1"
|
||||
|
||||
babel-helper-vue-jsx-merge-props@^2.0.3:
|
||||
version "2.0.3"
|
||||
resolved "https://registry.yarnpkg.com/babel-helper-vue-jsx-merge-props/-/babel-helper-vue-jsx-merge-props-2.0.3.tgz#22aebd3b33902328e513293a8e4992b384f9f1b6"
|
||||
|
||||
babel-helpers@^6.24.1:
|
||||
version "6.24.1"
|
||||
resolved "https://registry.yarnpkg.com/babel-helpers/-/babel-helpers-6.24.1.tgz#3471de9caec388e5c850e597e58a26ddf37602b2"
|
||||
|
@ -500,6 +504,10 @@ babel-plugin-syntax-exponentiation-operator@^6.8.0:
|
|||
version "6.13.0"
|
||||
resolved "https://registry.yarnpkg.com/babel-plugin-syntax-exponentiation-operator/-/babel-plugin-syntax-exponentiation-operator-6.13.0.tgz#9ee7e8337290da95288201a6a57f4170317830de"
|
||||
|
||||
babel-plugin-syntax-jsx@^6.18.0:
|
||||
version "6.18.0"
|
||||
resolved "https://registry.yarnpkg.com/babel-plugin-syntax-jsx/-/babel-plugin-syntax-jsx-6.18.0.tgz#0af32a9a6e13ca7a3fd5069e62d7b0f58d0d8946"
|
||||
|
||||
babel-plugin-syntax-object-rest-spread@^6.8.0:
|
||||
version "6.13.0"
|
||||
resolved "https://registry.yarnpkg.com/babel-plugin-syntax-object-rest-spread/-/babel-plugin-syntax-object-rest-spread-6.13.0.tgz#fd6536f2bce13836ffa3a5458c4903a597bb3bf5"
|
||||
|
@ -516,7 +524,7 @@ babel-plugin-transform-async-generator-functions@^6.24.1:
|
|||
babel-plugin-syntax-async-generators "^6.5.0"
|
||||
babel-runtime "^6.22.0"
|
||||
|
||||
babel-plugin-transform-async-to-generator@^6.24.1:
|
||||
babel-plugin-transform-async-to-generator@^6.22.0, babel-plugin-transform-async-to-generator@^6.24.1:
|
||||
version "6.24.1"
|
||||
resolved "https://registry.yarnpkg.com/babel-plugin-transform-async-to-generator/-/babel-plugin-transform-async-to-generator-6.24.1.tgz#6536e378aff6cb1d5517ac0e40eb3e9fc8d08761"
|
||||
dependencies:
|
||||
|
@ -555,7 +563,7 @@ babel-plugin-transform-es2015-block-scoped-functions@^6.22.0:
|
|||
dependencies:
|
||||
babel-runtime "^6.22.0"
|
||||
|
||||
babel-plugin-transform-es2015-block-scoping@^6.24.1:
|
||||
babel-plugin-transform-es2015-block-scoping@^6.23.0, babel-plugin-transform-es2015-block-scoping@^6.24.1:
|
||||
version "6.26.0"
|
||||
resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-block-scoping/-/babel-plugin-transform-es2015-block-scoping-6.26.0.tgz#d70f5299c1308d05c12f463813b0a09e73b1895f"
|
||||
dependencies:
|
||||
|
@ -565,7 +573,7 @@ babel-plugin-transform-es2015-block-scoping@^6.24.1:
|
|||
babel-types "^6.26.0"
|
||||
lodash "^4.17.4"
|
||||
|
||||
babel-plugin-transform-es2015-classes@^6.24.1:
|
||||
babel-plugin-transform-es2015-classes@^6.23.0, babel-plugin-transform-es2015-classes@^6.24.1:
|
||||
version "6.24.1"
|
||||
resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-classes/-/babel-plugin-transform-es2015-classes-6.24.1.tgz#5a4c58a50c9c9461e564b4b2a3bfabc97a2584db"
|
||||
dependencies:
|
||||
|
@ -579,33 +587,33 @@ babel-plugin-transform-es2015-classes@^6.24.1:
|
|||
babel-traverse "^6.24.1"
|
||||
babel-types "^6.24.1"
|
||||
|
||||
babel-plugin-transform-es2015-computed-properties@^6.24.1:
|
||||
babel-plugin-transform-es2015-computed-properties@^6.22.0, babel-plugin-transform-es2015-computed-properties@^6.24.1:
|
||||
version "6.24.1"
|
||||
resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-computed-properties/-/babel-plugin-transform-es2015-computed-properties-6.24.1.tgz#6fe2a8d16895d5634f4cd999b6d3480a308159b3"
|
||||
dependencies:
|
||||
babel-runtime "^6.22.0"
|
||||
babel-template "^6.24.1"
|
||||
|
||||
babel-plugin-transform-es2015-destructuring@^6.22.0:
|
||||
babel-plugin-transform-es2015-destructuring@^6.22.0, babel-plugin-transform-es2015-destructuring@^6.23.0:
|
||||
version "6.23.0"
|
||||
resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-destructuring/-/babel-plugin-transform-es2015-destructuring-6.23.0.tgz#997bb1f1ab967f682d2b0876fe358d60e765c56d"
|
||||
dependencies:
|
||||
babel-runtime "^6.22.0"
|
||||
|
||||
babel-plugin-transform-es2015-duplicate-keys@^6.24.1:
|
||||
babel-plugin-transform-es2015-duplicate-keys@^6.22.0, babel-plugin-transform-es2015-duplicate-keys@^6.24.1:
|
||||
version "6.24.1"
|
||||
resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-duplicate-keys/-/babel-plugin-transform-es2015-duplicate-keys-6.24.1.tgz#73eb3d310ca969e3ef9ec91c53741a6f1576423e"
|
||||
dependencies:
|
||||
babel-runtime "^6.22.0"
|
||||
babel-types "^6.24.1"
|
||||
|
||||
babel-plugin-transform-es2015-for-of@^6.22.0:
|
||||
babel-plugin-transform-es2015-for-of@^6.22.0, babel-plugin-transform-es2015-for-of@^6.23.0:
|
||||
version "6.23.0"
|
||||
resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-for-of/-/babel-plugin-transform-es2015-for-of-6.23.0.tgz#f47c95b2b613df1d3ecc2fdb7573623c75248691"
|
||||
dependencies:
|
||||
babel-runtime "^6.22.0"
|
||||
|
||||
babel-plugin-transform-es2015-function-name@^6.24.1:
|
||||
babel-plugin-transform-es2015-function-name@^6.22.0, babel-plugin-transform-es2015-function-name@^6.24.1:
|
||||
version "6.24.1"
|
||||
resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-function-name/-/babel-plugin-transform-es2015-function-name-6.24.1.tgz#834c89853bc36b1af0f3a4c5dbaa94fd8eacaa8b"
|
||||
dependencies:
|
||||
|
@ -619,7 +627,7 @@ babel-plugin-transform-es2015-literals@^6.22.0:
|
|||
dependencies:
|
||||
babel-runtime "^6.22.0"
|
||||
|
||||
babel-plugin-transform-es2015-modules-amd@^6.24.1:
|
||||
babel-plugin-transform-es2015-modules-amd@^6.22.0, babel-plugin-transform-es2015-modules-amd@^6.24.1:
|
||||
version "6.24.1"
|
||||
resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-modules-amd/-/babel-plugin-transform-es2015-modules-amd-6.24.1.tgz#3b3e54017239842d6d19c3011c4bd2f00a00d154"
|
||||
dependencies:
|
||||
|
@ -627,6 +635,15 @@ babel-plugin-transform-es2015-modules-amd@^6.24.1:
|
|||
babel-runtime "^6.22.0"
|
||||
babel-template "^6.24.1"
|
||||
|
||||
babel-plugin-transform-es2015-modules-commonjs@^6.23.0:
|
||||
version "6.26.2"
|
||||
resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-modules-commonjs/-/babel-plugin-transform-es2015-modules-commonjs-6.26.2.tgz#58a793863a9e7ca870bdc5a881117ffac27db6f3"
|
||||
dependencies:
|
||||
babel-plugin-transform-strict-mode "^6.24.1"
|
||||
babel-runtime "^6.26.0"
|
||||
babel-template "^6.26.0"
|
||||
babel-types "^6.26.0"
|
||||
|
||||
babel-plugin-transform-es2015-modules-commonjs@^6.24.1:
|
||||
version "6.26.0"
|
||||
resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-modules-commonjs/-/babel-plugin-transform-es2015-modules-commonjs-6.26.0.tgz#0d8394029b7dc6abe1a97ef181e00758dd2e5d8a"
|
||||
|
@ -636,7 +653,7 @@ babel-plugin-transform-es2015-modules-commonjs@^6.24.1:
|
|||
babel-template "^6.26.0"
|
||||
babel-types "^6.26.0"
|
||||
|
||||
babel-plugin-transform-es2015-modules-systemjs@^6.24.1:
|
||||
babel-plugin-transform-es2015-modules-systemjs@^6.23.0, babel-plugin-transform-es2015-modules-systemjs@^6.24.1:
|
||||
version "6.24.1"
|
||||
resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-modules-systemjs/-/babel-plugin-transform-es2015-modules-systemjs-6.24.1.tgz#ff89a142b9119a906195f5f106ecf305d9407d23"
|
||||
dependencies:
|
||||
|
@ -644,7 +661,7 @@ babel-plugin-transform-es2015-modules-systemjs@^6.24.1:
|
|||
babel-runtime "^6.22.0"
|
||||
babel-template "^6.24.1"
|
||||
|
||||
babel-plugin-transform-es2015-modules-umd@^6.24.1:
|
||||
babel-plugin-transform-es2015-modules-umd@^6.23.0, babel-plugin-transform-es2015-modules-umd@^6.24.1:
|
||||
version "6.24.1"
|
||||
resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-modules-umd/-/babel-plugin-transform-es2015-modules-umd-6.24.1.tgz#ac997e6285cd18ed6176adb607d602344ad38468"
|
||||
dependencies:
|
||||
|
@ -652,14 +669,14 @@ babel-plugin-transform-es2015-modules-umd@^6.24.1:
|
|||
babel-runtime "^6.22.0"
|
||||
babel-template "^6.24.1"
|
||||
|
||||
babel-plugin-transform-es2015-object-super@^6.24.1:
|
||||
babel-plugin-transform-es2015-object-super@^6.22.0, babel-plugin-transform-es2015-object-super@^6.24.1:
|
||||
version "6.24.1"
|
||||
resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-object-super/-/babel-plugin-transform-es2015-object-super-6.24.1.tgz#24cef69ae21cb83a7f8603dad021f572eb278f8d"
|
||||
dependencies:
|
||||
babel-helper-replace-supers "^6.24.1"
|
||||
babel-runtime "^6.22.0"
|
||||
|
||||
babel-plugin-transform-es2015-parameters@^6.24.1:
|
||||
babel-plugin-transform-es2015-parameters@^6.23.0, babel-plugin-transform-es2015-parameters@^6.24.1:
|
||||
version "6.24.1"
|
||||
resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-parameters/-/babel-plugin-transform-es2015-parameters-6.24.1.tgz#57ac351ab49caf14a97cd13b09f66fdf0a625f2b"
|
||||
dependencies:
|
||||
|
@ -670,7 +687,7 @@ babel-plugin-transform-es2015-parameters@^6.24.1:
|
|||
babel-traverse "^6.24.1"
|
||||
babel-types "^6.24.1"
|
||||
|
||||
babel-plugin-transform-es2015-shorthand-properties@^6.24.1:
|
||||
babel-plugin-transform-es2015-shorthand-properties@^6.22.0, babel-plugin-transform-es2015-shorthand-properties@^6.24.1:
|
||||
version "6.24.1"
|
||||
resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-shorthand-properties/-/babel-plugin-transform-es2015-shorthand-properties-6.24.1.tgz#24f875d6721c87661bbd99a4622e51f14de38aa0"
|
||||
dependencies:
|
||||
|
@ -683,7 +700,7 @@ babel-plugin-transform-es2015-spread@^6.22.0:
|
|||
dependencies:
|
||||
babel-runtime "^6.22.0"
|
||||
|
||||
babel-plugin-transform-es2015-sticky-regex@^6.24.1:
|
||||
babel-plugin-transform-es2015-sticky-regex@^6.22.0, babel-plugin-transform-es2015-sticky-regex@^6.24.1:
|
||||
version "6.24.1"
|
||||
resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-sticky-regex/-/babel-plugin-transform-es2015-sticky-regex-6.24.1.tgz#00c1cdb1aca71112cdf0cf6126c2ed6b457ccdbc"
|
||||
dependencies:
|
||||
|
@ -697,13 +714,13 @@ babel-plugin-transform-es2015-template-literals@^6.22.0:
|
|||
dependencies:
|
||||
babel-runtime "^6.22.0"
|
||||
|
||||
babel-plugin-transform-es2015-typeof-symbol@^6.22.0:
|
||||
babel-plugin-transform-es2015-typeof-symbol@^6.22.0, babel-plugin-transform-es2015-typeof-symbol@^6.23.0:
|
||||
version "6.23.0"
|
||||
resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-typeof-symbol/-/babel-plugin-transform-es2015-typeof-symbol-6.23.0.tgz#dec09f1cddff94b52ac73d505c84df59dcceb372"
|
||||
dependencies:
|
||||
babel-runtime "^6.22.0"
|
||||
|
||||
babel-plugin-transform-es2015-unicode-regex@^6.24.1:
|
||||
babel-plugin-transform-es2015-unicode-regex@^6.22.0, babel-plugin-transform-es2015-unicode-regex@^6.24.1:
|
||||
version "6.24.1"
|
||||
resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-unicode-regex/-/babel-plugin-transform-es2015-unicode-regex-6.24.1.tgz#d38b12f42ea7323f729387f18a7c5ae1faeb35e9"
|
||||
dependencies:
|
||||
|
@ -711,7 +728,7 @@ babel-plugin-transform-es2015-unicode-regex@^6.24.1:
|
|||
babel-runtime "^6.22.0"
|
||||
regexpu-core "^2.0.0"
|
||||
|
||||
babel-plugin-transform-exponentiation-operator@^6.24.1:
|
||||
babel-plugin-transform-exponentiation-operator@^6.22.0, babel-plugin-transform-exponentiation-operator@^6.24.1:
|
||||
version "6.24.1"
|
||||
resolved "https://registry.yarnpkg.com/babel-plugin-transform-exponentiation-operator/-/babel-plugin-transform-exponentiation-operator-6.24.1.tgz#2ab0c9c7f3098fa48907772bb813fe41e8de3a0e"
|
||||
dependencies:
|
||||
|
@ -726,7 +743,7 @@ babel-plugin-transform-object-rest-spread@^6.22.0:
|
|||
babel-plugin-syntax-object-rest-spread "^6.8.0"
|
||||
babel-runtime "^6.26.0"
|
||||
|
||||
babel-plugin-transform-regenerator@^6.24.1:
|
||||
babel-plugin-transform-regenerator@^6.22.0, babel-plugin-transform-regenerator@^6.24.1:
|
||||
version "6.26.0"
|
||||
resolved "https://registry.yarnpkg.com/babel-plugin-transform-regenerator/-/babel-plugin-transform-regenerator-6.26.0.tgz#e0703696fbde27f0a3efcacf8b4dca2f7b3a8f2f"
|
||||
dependencies:
|
||||
|
@ -745,6 +762,47 @@ babel-plugin-transform-strict-mode@^6.24.1:
|
|||
babel-runtime "^6.22.0"
|
||||
babel-types "^6.24.1"
|
||||
|
||||
babel-plugin-transform-vue-jsx@3:
|
||||
version "3.7.0"
|
||||
resolved "https://registry.yarnpkg.com/babel-plugin-transform-vue-jsx/-/babel-plugin-transform-vue-jsx-3.7.0.tgz#d40492e6692a36b594f7e9a1928f43e969740960"
|
||||
dependencies:
|
||||
esutils "^2.0.2"
|
||||
|
||||
babel-preset-env@^1.7.0:
|
||||
version "1.7.0"
|
||||
resolved "https://registry.yarnpkg.com/babel-preset-env/-/babel-preset-env-1.7.0.tgz#dea79fa4ebeb883cd35dab07e260c1c9c04df77a"
|
||||
dependencies:
|
||||
babel-plugin-check-es2015-constants "^6.22.0"
|
||||
babel-plugin-syntax-trailing-function-commas "^6.22.0"
|
||||
babel-plugin-transform-async-to-generator "^6.22.0"
|
||||
babel-plugin-transform-es2015-arrow-functions "^6.22.0"
|
||||
babel-plugin-transform-es2015-block-scoped-functions "^6.22.0"
|
||||
babel-plugin-transform-es2015-block-scoping "^6.23.0"
|
||||
babel-plugin-transform-es2015-classes "^6.23.0"
|
||||
babel-plugin-transform-es2015-computed-properties "^6.22.0"
|
||||
babel-plugin-transform-es2015-destructuring "^6.23.0"
|
||||
babel-plugin-transform-es2015-duplicate-keys "^6.22.0"
|
||||
babel-plugin-transform-es2015-for-of "^6.23.0"
|
||||
babel-plugin-transform-es2015-function-name "^6.22.0"
|
||||
babel-plugin-transform-es2015-literals "^6.22.0"
|
||||
babel-plugin-transform-es2015-modules-amd "^6.22.0"
|
||||
babel-plugin-transform-es2015-modules-commonjs "^6.23.0"
|
||||
babel-plugin-transform-es2015-modules-systemjs "^6.23.0"
|
||||
babel-plugin-transform-es2015-modules-umd "^6.23.0"
|
||||
babel-plugin-transform-es2015-object-super "^6.22.0"
|
||||
babel-plugin-transform-es2015-parameters "^6.23.0"
|
||||
babel-plugin-transform-es2015-shorthand-properties "^6.22.0"
|
||||
babel-plugin-transform-es2015-spread "^6.22.0"
|
||||
babel-plugin-transform-es2015-sticky-regex "^6.22.0"
|
||||
babel-plugin-transform-es2015-template-literals "^6.22.0"
|
||||
babel-plugin-transform-es2015-typeof-symbol "^6.23.0"
|
||||
babel-plugin-transform-es2015-unicode-regex "^6.22.0"
|
||||
babel-plugin-transform-exponentiation-operator "^6.22.0"
|
||||
babel-plugin-transform-regenerator "^6.22.0"
|
||||
browserslist "^3.2.6"
|
||||
invariant "^2.2.2"
|
||||
semver "^5.3.0"
|
||||
|
||||
babel-preset-es2015@^6.0.0:
|
||||
version "6.24.1"
|
||||
resolved "https://registry.yarnpkg.com/babel-preset-es2015/-/babel-preset-es2015-6.24.1.tgz#d44050d6bc2c9feea702aaf38d727a0210538939"
|
||||
|
@ -996,6 +1054,13 @@ browserslist@^1.3.6, browserslist@^1.5.2, browserslist@^1.7.6:
|
|||
caniuse-db "^1.0.30000639"
|
||||
electron-to-chromium "^1.2.7"
|
||||
|
||||
browserslist@^3.2.6:
|
||||
version "3.2.8"
|
||||
resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-3.2.8.tgz#b0005361d6471f0f5952797a76fc985f1f978fc6"
|
||||
dependencies:
|
||||
caniuse-lite "^1.0.30000844"
|
||||
electron-to-chromium "^1.3.47"
|
||||
|
||||
buffer@^4.9.0:
|
||||
version "4.9.1"
|
||||
resolved "https://registry.yarnpkg.com/buffer/-/buffer-4.9.1.tgz#6d1bb601b07a4efced97094132093027c95bc298"
|
||||
|
@ -1069,6 +1134,10 @@ caniuse-db@^1.0.30000529, caniuse-db@^1.0.30000634, caniuse-db@^1.0.30000639:
|
|||
version "1.0.30000801"
|
||||
resolved "https://registry.yarnpkg.com/caniuse-db/-/caniuse-db-1.0.30000801.tgz#a1d49def94c4e5aca5ccf1d58812e4668fac19d4"
|
||||
|
||||
caniuse-lite@^1.0.30000844:
|
||||
version "1.0.30000878"
|
||||
resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30000878.tgz#c644c39588dd42d3498e952234c372e5a40a4123"
|
||||
|
||||
caseless@~0.12.0:
|
||||
version "0.12.0"
|
||||
resolved "https://registry.yarnpkg.com/caseless/-/caseless-0.12.0.tgz#1b681c21ff84033c826543090689420d187151dc"
|
||||
|
@ -1789,6 +1858,10 @@ electron-to-chromium@^1.2.7:
|
|||
version "1.3.32"
|
||||
resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.32.tgz#11d0684c0840e003c4be8928f8ac5f35dbc2b4e6"
|
||||
|
||||
electron-to-chromium@^1.3.47:
|
||||
version "1.3.61"
|
||||
resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.61.tgz#a8ac295b28d0f03d85e37326fd16b6b6b17a1795"
|
||||
|
||||
emojis-list@^2.0.0:
|
||||
version "2.1.0"
|
||||
resolved "https://registry.yarnpkg.com/emojis-list/-/emojis-list-2.1.0.tgz#4daa4d9db00f9819880c79fa457ae5b09a1fd389"
|
||||
|
@ -3081,6 +3154,10 @@ isexe@^2.0.0:
|
|||
version "2.0.0"
|
||||
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:
|
||||
version "2.1.0"
|
||||
resolved "https://registry.yarnpkg.com/isobject/-/isobject-2.1.0.tgz#f065561096a3f1da2ef46272f815c840d87e0c89"
|
||||
|
|
Loading…
Reference in New Issue