diff options
Diffstat (limited to 'src')
28 files changed, 699 insertions, 118 deletions
@@ -2,6 +2,7 @@ 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 ChatPanel from './components/chat_panel/chat_panel.vue' @@ -12,8 +13,9 @@ export default { NavPanel, Notifications, UserFinder, - ChatPanel, - InstanceSpecificPanel + WhoToFollowPanel, + InstanceSpecificPanel, + ChatPanel }, data: () => ({ mobileActivePanel: 'timeline' @@ -27,6 +29,7 @@ export default { 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 }, showInstanceSpecificPanel () { return this.$store.state.config.showInstanceSpecificPanel } }, methods: { diff --git a/src/App.scss b/src/App.scss index a8601220..f830a33b 100644 --- a/src/App.scss +++ b/src/App.scss @@ -88,13 +88,13 @@ label.select { input, textarea, .select { border: none; - border-radius: $fallback--btnRadius; - border-radius: var(--btnRadius, $fallback--btnRadius); + border-radius: $fallback--inputRadius; + border-radius: var(--inputRadius, $fallback--inputRadius); border-bottom: 1px solid rgba(255, 255, 255, 0.2); border-top: 1px solid rgba(0, 0, 0, 0.2); box-shadow: 0px 0px 2px black inset; - background-color: $fallback--lightBg; - background-color: var(--lightBg, $fallback--lightBg); + background-color: $fallback--input; + background-color: var(--input, $fallback--input); color: $fallback--lightFg; color: var(--lightFg, $fallback--lightFg); font-family: sans-serif; @@ -154,8 +154,8 @@ input, textarea, .select { border-top: 1px solid rgba(0, 0, 0, 0.2); box-shadow: 0px 0px 2px black inset; margin-right: .5em; - background-color: $fallback--btn; - background-color: var(--btn, $fallback--btn); + background-color: $fallback--input; + background-color: var(--input, $fallback--input); vertical-align: top; text-align: center; line-height: 1.1em; diff --git a/src/App.vue b/src/App.vue index a8d17fa7..923d411b 100644 --- a/src/App.vue +++ b/src/App.vue @@ -24,6 +24,7 @@ <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> <notifications v-if="currentUser"></notifications> </div> </div> diff --git a/src/_variables.scss b/src/_variables.scss index d90a1d48..b5222a6a 100644 --- a/src/_variables.scss +++ b/src/_variables.scss @@ -4,7 +4,8 @@ $darkened-background: whitesmoke; $fallback--bg: #121a24; $fallback--btn: #182230; -$fallback--faint: #999; +$fallback--input: #182230; +$fallback--faint: rgba(185, 185, 186, .5); $fallback--fg: #b9b9ba; $fallback--link: #d8a070; $fallback--icon: #666; @@ -21,6 +22,7 @@ $fallback--cAlertRed: rgba(211,16,20,.5); $fallback--panelRadius: 10px; $fallback--checkBoxRadius: 2px; $fallback--btnRadius: 4px; +$fallback--inputRadius: 4px; $fallback--tooltipRadius: 5px; $fallback--avatarRadius: 4px; $fallback--avatarAltRadius: 10px; diff --git a/src/assets/nsfw.png b/src/assets/nsfw.png Binary files differindex bb6556b4..42749033 100644 --- a/src/assets/nsfw.png +++ b/src/assets/nsfw.png diff --git a/src/components/attachment/attachment.vue b/src/components/attachment/attachment.vue index b2f63668..c48fb16b 100644 --- a/src/components/attachment/attachment.vue +++ b/src/components/attachment/attachment.vue @@ -96,6 +96,9 @@ background: rgba(230,230,230,0.6); font-weight: bold; z-index: 4; + line-height: 1; + border-radius: $fallback--tooltipRadius; + border-radius: var(--tooltipRadius, $fallback--tooltipRadius); } .small { diff --git a/src/components/conversation/conversation.vue b/src/components/conversation/conversation.vue index 308e5e7d..bfcd3fe7 100644 --- a/src/components/conversation/conversation.vue +++ b/src/components/conversation/conversation.vue @@ -3,7 +3,7 @@ <div class="panel-heading conversation-heading"> {{ $t('timeline.conversation') }} <span v-if="collapsable" style="float:right;"> - <small><a href="#" @click.prevent="$emit('toggleExpanded')">Collapse</a></small> + <small><a href="#" @click.prevent="$emit('toggleExpanded')">{{ $t('timeline.collapse') }}</a></small> </span> </div> <div class="panel-body"> diff --git a/src/components/nav_panel/nav_panel.vue b/src/components/nav_panel/nav_panel.vue index 6f949afb..2e1a6c7a 100644 --- a/src/components/nav_panel/nav_panel.vue +++ b/src/components/nav_panel/nav_panel.vue @@ -45,8 +45,6 @@ border-bottom: 1px solid; border-color: $fallback--border; border-color: var(--border, $fallback--border); - background-color: $fallback--bg; - background-color: var(--bg, $fallback--bg); padding: 0; &:first-child a { diff --git a/src/components/notifications/notifications.scss b/src/components/notifications/notifications.scss index 9cbb1226..008530b4 100644 --- a/src/components/notifications/notifications.scss +++ b/src/components/notifications/notifications.scss @@ -98,7 +98,7 @@ .status { padding: 0.25em 0; color: $fallback--faint; - color: var($fallback--faint, --faint); + color: var(--faint, $fallback--faint); } padding: 0; .media-body { diff --git a/src/components/post_status_form/post_status_form.js b/src/components/post_status_form/post_status_form.js index 6bcf1c66..0597d652 100644 --- a/src/components/post_status_form/post_status_form.js +++ b/src/components/post_status_form/post_status_form.js @@ -48,12 +48,21 @@ const PostStatusForm = { highlighted: 0, newStatus: { status: statusText, - files: [] + files: [], + visibility: 'public' }, caret: 0 } }, computed: { + vis () { + return { + public: { selected: this.newStatus.visibility === 'public' }, + unlisted: { selected: this.newStatus.visibility === 'unlisted' }, + private: { selected: this.newStatus.visibility === 'private' }, + direct: { selected: this.newStatus.visibility === 'direct' } + } + }, candidates () { const firstchar = this.textAtCaret.charAt(0) if (firstchar === '@') { @@ -118,6 +127,9 @@ const PostStatusForm = { }, isOverLengthLimit () { return this.hasStatusLengthLimit && (this.statusLength > this.statusLengthLimit) + }, + scopeOptionsEnabled () { + return this.$store.state.config.scopeOptionsEnabled } }, methods: { @@ -185,6 +197,8 @@ const PostStatusForm = { this.posting = true statusPoster.postStatus({ status: newStatus.status, + spoilerText: newStatus.spoilerText || null, + visibility: newStatus.visibility, media: newStatus.files, store: this.$store, inReplyToStatusId: this.replyTo @@ -192,7 +206,8 @@ const PostStatusForm = { if (!data.error) { this.newStatus = { status: '', - files: [] + files: [], + visibility: newStatus.visibility } this.$emit('posted') let el = this.$el.querySelector('textarea') @@ -239,18 +254,20 @@ const PostStatusForm = { e.dataTransfer.dropEffect = 'copy' }, resize (e) { - const target = e.target || e - target.style.height = 'auto' - const heightPx = target.scrollHeight - 10 - if (heightPx > 54) { - target.style.height = `${target.scrollHeight - 10}px` - } - if (target.value === '') { - target.style.height = '16px' + if (!e.target) { return } + const vertPadding = Number(window.getComputedStyle(e.target)['padding-top'].substr(0, 1)) + + Number(window.getComputedStyle(e.target)['padding-bottom'].substr(0, 1)) + e.target.style.height = 'auto' + e.target.style.height = `${e.target.scrollHeight - vertPadding}px` + if (e.target.value === '') { + e.target.style.height = '16px' } }, clearError () { this.error = null + }, + changeVis (visibility) { + this.newStatus.visibility = visibility } } } diff --git a/src/components/post_status_form/post_status_form.vue b/src/components/post_status_form/post_status_form.vue index 88627e3a..802d51ed 100644 --- a/src/components/post_status_form/post_status_form.vue +++ b/src/components/post_status_form/post_status_form.vue @@ -2,6 +2,12 @@ <div class="post-status-form"> <form @submit.prevent="postStatus(newStatus)"> <div class="form-group" > + <input + v-if="scopeOptionsEnabled" + type="text" + :placeholder="$t('post_status.content_warning')" + v-model="newStatus.spoilerText" + class="form-cw"> <textarea ref="textarea" @click="setCaret" @@ -18,16 +24,17 @@ @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"></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> <div style="position:relative;" v-if="candidates"> <div class="autocomplete-panel"> <div v-for="candidate in candidates" @click="replace(candidate.utf || (candidate.screen_name + ' '))"> - <div v-if="candidate.highlighted" class="autocomplete"> - <span v-if="candidate.img"><img :src="candidate.img"></span> - <span v-else>{{candidate.utf}}</span> - <span>{{candidate.screen_name}}<small>{{candidate.name}}</small></span> - </div> - <div v-else class="autocomplete"> + <div class="autocomplete" :class="{ highlighted: candidate.highlighted }"> <span v-if="candidate.img"><img :src="candidate.img"></img></span> <span v-else>{{candidate.utf}}</span> <span>{{candidate.screen_name}}<small>{{candidate.name}}</small></span> @@ -84,6 +91,17 @@ } } +.post-status-form .visibility-tray { + font-size: 1.2em; + padding: 3px; + cursor: pointer; + + .selected { + color: $fallback--lightFg; + color: var(--lightFg, $fallback--lightFg); + } +} + .post-status-form, .login { .form-bottom { display: flex; @@ -135,10 +153,6 @@ cursor: not-allowed; } - .icon-cancel { - cursor: pointer; - } - form { display: flex; flex-direction: column; @@ -152,7 +166,15 @@ line-height:24px; } - form textarea { + form textarea.form-cw { + line-height:16px; + resize: none; + overflow: hidden; + transition: min-height 200ms 100ms; + min-height: 1px; + } + + form textarea.form-control { line-height:16px; resize: none; overflow: hidden; @@ -161,7 +183,7 @@ box-sizing: content-box; } - form textarea:focus { + form textarea.form-control:focus { min-height: 48px; } @@ -186,8 +208,8 @@ z-index: 1; box-shadow: 1px 2px 4px rgba(0, 0, 0, 0.5); min-width: 75%; - background: $fallback--btn; - background: var(--btn, $fallback--btn); + background: $fallback--bg; + background: var(--bg, $fallback--bg); color: $fallback--lightFg; color: var(--lightFg, $fallback--lightFg); } @@ -216,6 +238,11 @@ color: $fallback--faint; color: var(--faint, $fallback--faint); } + + &.highlighted { + background-color: $fallback--btn; + background-color: var(--btn, $fallback--btn); + } } } </style> diff --git a/src/components/status/status.js b/src/components/status/status.js index 73f4a7aa..87ef90d8 100644 --- a/src/components/status/status.js +++ b/src/components/status/status.js @@ -104,6 +104,18 @@ const Status = { StillImage }, methods: { + visibilityIcon (visibility) { + switch (visibility) { + case 'private': + return 'icon-lock' + case 'unlisted': + return 'icon-lock-open-alt' + case 'direct': + return 'icon-mail-alt' + default: + return 'icon-globe' + } + }, linkClicked ({target}) { if (target.tagName === 'SPAN') { target = target.parentNode diff --git a/src/components/status/status.vue b/src/components/status/status.vue index f1163fd9..ace141cd 100644 --- a/src/components/status/status.vue +++ b/src/components/status/status.vue @@ -55,6 +55,7 @@ <router-link class="timeago" :to="{ name: 'conversation', params: { id: status.id } }"> <timeago :since="status.created_at" :auto-update="60"></timeago> </router-link> + <span v-if="status.visibility"><i :class="visibilityIcon(status.visibility)"></i> </span> <a :href="status.external_url" target="_blank" v-if="!status.is_local" class="source_url"><i class="icon-link-ext"></i></a> <template v-if="expandable"> <a href="#" @click.prevent="toggleExpanded"><i class="icon-plus-squared"></i></a> @@ -165,8 +166,6 @@ border-left-width: 0px; line-height: 18px; min-width: 0; - background-color: $fallback--bg; - background-color: var(--bg, $fallback--bg); border-color: $fallback--border; border-color: var(--border, $fallback--border); @@ -189,6 +188,10 @@ margin: 0 0 0.25em 0.8em; } + .usercard { + margin-bottom: .7em + } + .media-heading { flex-wrap: nowrap; } diff --git a/src/components/style_switcher/style_switcher.js b/src/components/style_switcher/style_switcher.js index 08bc7113..6f4845c4 100644 --- a/src/components/style_switcher/style_switcher.js +++ b/src/components/style_switcher/style_switcher.js @@ -14,6 +14,7 @@ export default { greenColorLocal: '', orangeColorLocal: '', btnRadiusLocal: '', + inputRadiusLocal: '', panelRadiusLocal: '', avatarRadiusLocal: '', avatarAltRadiusLocal: '', @@ -42,6 +43,7 @@ export default { this.orangeColorLocal = rgbstr2hex(this.$store.state.config.colors.cOrange) this.btnRadiusLocal = this.$store.state.config.radii.btnRadius || 4 + this.inputRadiusLocal = this.$store.state.config.radii.inputRadius || 4 this.panelRadiusLocal = this.$store.state.config.radii.panelRadius || 10 this.avatarRadiusLocal = this.$store.state.config.radii.avatarRadius || 5 this.avatarAltRadiusLocal = this.$store.state.config.radii.avatarAltRadius || 50 @@ -85,6 +87,7 @@ export default { cGreen: greenRgb, cOrange: orangeRgb, btnRadius: this.btnRadiusLocal, + inputRadius: this.inputRadiusLocal, panelRadius: this.panelRadiusLocal, avatarRadius: this.avatarRadiusLocal, avatarAltRadius: this.avatarAltRadiusLocal, diff --git a/src/components/style_switcher/style_switcher.vue b/src/components/style_switcher/style_switcher.vue index 9c39b245..7acba1dc 100644 --- a/src/components/style_switcher/style_switcher.vue +++ b/src/components/style_switcher/style_switcher.vue @@ -59,6 +59,11 @@ <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"> @@ -86,6 +91,7 @@ </div> <div :style="{ '--btnRadius': btnRadiusLocal + 'px', + '--inputRadius': inputRadiusLocal + 'px', '--panelRadius': panelRadiusLocal + 'px', '--avatarRadius': avatarRadiusLocal + 'px', '--avatarAltRadius': avatarAltRadiusLocal + 'px', diff --git a/src/components/user_card_content/user_card_content.js b/src/components/user_card_content/user_card_content.js index 1e8c91de..4d4266cb 100644 --- a/src/components/user_card_content/user_card_content.js +++ b/src/components/user_card_content/user_card_content.js @@ -2,16 +2,24 @@ import StillImage from '../still-image/still-image.vue' import { hex2rgb } from '../../services/color_convert/color_convert.js' export default { - props: [ 'user', 'switcher', 'hideBio' ], + props: [ 'user', 'switcher', 'selected', 'hideBio' ], computed: { headingStyle () { const color = this.$store.state.config.colors.bg if (color) { const rgb = hex2rgb(color) + const tintColor = `rgba(${Math.floor(rgb.r)}, ${Math.floor(rgb.g)}, ${Math.floor(rgb.b)}, .5)` console.log(rgb) + console.log([ + `url(${this.user.cover_photo})`, + `linear-gradient(to bottom, ${tintColor}, ${tintColor})` + ].join(', ')) return { - backgroundColor: `rgb(${Math.floor(rgb[0] * 0.53)}, ${Math.floor(rgb[1] * 0.56)}, ${Math.floor(rgb[2] * 0.59)})`, - backgroundImage: `url(${this.user.cover_photo})` + backgroundColor: `rgb(${Math.floor(rgb.r * 0.53)}, ${Math.floor(rgb.g * 0.56)}, ${Math.floor(rgb.b * 0.59)})`, + backgroundImage: [ + `linear-gradient(to bottom, ${tintColor}, ${tintColor})`, + `url(${this.user.cover_photo})` + ].join(', ') } } }, @@ -61,8 +69,10 @@ export default { store.state.api.backendInteractor.setUserMute(this.user) }, setProfileView (v) { - const store = this.$store - store.commit('setProfileView', { v }) + if (this.switcher) { + const store = this.$store + store.commit('setProfileView', { v }) + } } } } diff --git a/src/components/user_card_content/user_card_content.vue b/src/components/user_card_content/user_card_content.vue index ca8428ca..c120df9a 100644 --- a/src/components/user_card_content/user_card_content.vue +++ b/src/components/user_card_content/user_card_content.vue @@ -14,8 +14,9 @@ </router-link> <div class="name-and-screen-name"> <div :title="user.name" class='user-name'>{{user.name}}</div> - <router-link :to="{ name: 'user-profile', params: { id: user.id } }"> - <div class='user-screen-name'>@{{user.screen_name}}</div> + <router-link class='user-screen-name':to="{ name: 'user-profile', params: { id: user.id } }"> + <span>@{{user.screen_name}}</span> + <span class="dailyAvg">{{dailyAvg}} {{ $t('user_card.per_day') }}</span> </router-link> </div> </div> @@ -73,20 +74,17 @@ </div> </div> <div class="panel-body profile-panel-body"> - <div class="user-counts"> - <div class="user-count"> - <a href="#" v-on:click.prevent="setProfileView('statuses')" v-if="switcher"><h5>{{ $t('user_card.statuses') }}</h5></a> - <h5 v-else>{{ $t('user_card.statuses') }}</h5> - <span>{{user.statuses_count}} <br><span class="dailyAvg">{{dailyAvg}} {{ $t('user_card.per_day') }}</span></span> + <div class="user-counts" :class="{clickable: switcher}"> + <div class="user-count" v-on:click.prevent="setProfileView('statuses')" :class="{selected: selected === 'statuses'}"> + <h5>{{ $t('user_card.statuses') }}</h5> + <span>{{user.statuses_count}} <br></span> </div> - <div class="user-count"> - <a href="#" v-on:click.prevent="setProfileView('friends')" v-if="switcher"><h5>{{ $t('user_card.followees') }}</h5></a> - <h5 v-else>{{ $t('user_card.followees') }}</h5> + <div class="user-count" v-on:click.prevent="setProfileView('friends')" :class="{selected: selected === 'friends'}"> + <h5>{{ $t('user_card.followees') }}</h5> <span>{{user.friends_count}}</span> </div> - <div class="user-count"> - <a href="#" v-on:click.prevent="setProfileView('followers')" v-if="switcher"><h5>{{ $t('user_card.followers') }}</h5></a> - <h5 v-else>{{ $t('user_card.followers') }}</h5> + <div class="user-count" v-on:click.prevent="setProfileView('followers')" :class="{selected: selected === 'followers'}"> + <h5>{{ $t('user_card.followers') }}</h5> <span>{{user.followers_count}}</span> </div> </div> @@ -112,20 +110,18 @@ } .profile-panel-body { - top: -0em; - padding-top: 4em; 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%) } .user-info { - color: white; - padding: 0 16px 16px 16px; - margin-bottom: -4em; + color: $fallback--lightFg; + color: var(--lightFg, $fallback--lightFg); + padding: 0 16px; .container { - padding: 16px 10px 4px 10px; + padding: 16px 10px 6px 10px; display: flex; max-height: 56px; overflow: hidden; @@ -154,10 +150,9 @@ } } - text-shadow: 0px 1px 1.5px rgba(0, 0, 0, 1.0); - .usersettings { - color: #fff; + color: $fallback--lightFg; + color: var(--lightFg, $fallback--lightFg); opacity: .8; } @@ -171,14 +166,15 @@ } .user-name{ - color: white; text-overflow: ellipsis; overflow: hidden; } .user-screen-name { - color: white; - font-weight: lighter; + color: $fallback--lightFg; + color: var(--lightFg, $fallback--lightFg); + display: inline-block; + font-weight: light; font-size: 15px; padding-right: 0.1em; } @@ -191,14 +187,11 @@ div { flex: 1; } - margin-top: 0.7em; - margin-bottom: -1.0em; .following { - color: white; font-size: 14px; flex: 0 0 100%; - margin: -0.7em 0.0em 0.3em 0.0em; + margin: 0 0 .4em 0; padding-left: 16px; text-align: left; } @@ -238,12 +231,37 @@ .user-counts { display: flex; line-height:16px; - padding: 1em 1.5em 0em 1em; + padding: .5em 1.5em 0em 1.5em; text-align: center; + justify-content: space-between; + color: $fallback--lightFg; + color: var(--lightFg, $fallback--lightFg); + + &.clickable { + .user-count { + cursor: pointer; + + &:hover:not(.selected) { + transition: border-bottom 100ms; + border-bottom: 3px solid $fallback--link; + border-bottom: 3px solid var(--link, $fallback--link); + } + } + } } .user-count { flex: 1; + padding: .5em 0 .5em 0; + margin: 0 .5em; + + &.selected { + transition: none; + border-bottom: 5px solid $fallback--link; + border-bottom: 5px solid var(--link, $fallback--link); + border-radius: $fallback--btnRadius; + border-radius: var(--btnRadius, $fallback--btnRadius); + } h5 { font-size:1em; @@ -256,7 +274,8 @@ } .dailyAvg { - font-size: 0.8em; - opacity: 0.5; + margin-left: 1em; + font-size: 0.7em; + color: #CCC; } </style> diff --git a/src/components/user_profile/user_profile.vue b/src/components/user_profile/user_profile.vue index 838a43ab..f8502907 100644 --- a/src/components/user_profile/user_profile.vue +++ b/src/components/user_profile/user_profile.vue @@ -1,7 +1,7 @@ <template> <div> <div v-if="user" class="user-profile panel panel-default"> - <user-card-content :user="user" :switcher="true"></user-card-content> + <user-card-content :user="user" :switcher="true" :selected="timeline.viewing"></user-card-content> </div> <Timeline :title="$t('user_profile.timeline_title')" :timeline="timeline" :timeline-name="'user'" :user-id="userId"/> </div> diff --git a/src/components/user_settings/user_settings.js b/src/components/user_settings/user_settings.js index 25ee1f35..b6026e18 100644 --- a/src/components/user_settings/user_settings.js +++ b/src/components/user_settings/user_settings.js @@ -8,8 +8,15 @@ const UserSettings = { followList: null, followImportError: false, followsImported: false, + enableFollowsExport: true, uploading: [ false, false, false, false ], - previews: [ null, null, null ] + previews: [ null, null, null ], + deletingAccount: false, + deleteAccountConfirmPasswordInput: '', + deleteAccountError: false, + changePasswordInputs: [ '', '', '' ], + changedPassword: false, + changePasswordError: false } }, components: { @@ -137,6 +144,37 @@ const UserSettings = { this.uploading[3] = false }) }, + /* This function takes an Array of Users + * and outputs a file with all the addresses for the user to download + */ + exportPeople (users, filename) { + // Get all the friends addresses + var UserAddresses = users.map(function (user) { + // check is it's a local user + if (user && user.is_local) { + // append the instance address + // eslint-disable-next-line no-undef + user.screen_name += '@' + location.hostname + } + return user.screen_name + }).join('\n') + // Make the user download the file + var fileToDownload = document.createElement('a') + fileToDownload.setAttribute('href', 'data:text/plain;charset=utf-8,' + encodeURIComponent(UserAddresses)) + fileToDownload.setAttribute('download', filename) + fileToDownload.style.display = 'none' + document.body.appendChild(fileToDownload) + fileToDownload.click() + document.body.removeChild(fileToDownload) + }, + exportFollows () { + this.enableFollowsExport = false + this.$store.state.api.backendInteractor + .fetchFriends({id: this.$store.state.users.currentUser.id}) + .then((friendList) => { + this.exportPeople(friendList, 'friends.csv') + }) + }, followListChange () { // eslint-disable-next-line no-undef let formData = new FormData() @@ -146,6 +184,37 @@ const UserSettings = { dismissImported () { this.followsImported = false this.followImportError = false + }, + confirmDelete () { + this.deletingAccount = true + }, + deleteAccount () { + this.$store.state.api.backendInteractor.deleteAccount({password: this.deleteAccountConfirmPasswordInput}) + .then((res) => { + if (res.status === 'success') { + this.$store.dispatch('logout') + this.$router.push('/main/all') + } else { + this.deleteAccountError = res.error + } + }) + }, + changePassword () { + const params = { + password: this.changePasswordInputs[0], + newPassword: this.changePasswordInputs[1], + newPasswordConfirmation: this.changePasswordInputs[2] + } + this.$store.state.api.backendInteractor.changePassword(params) + .then((res) => { + if (res.status === 'success') { + this.changedPassword = true + this.changePasswordError = false + } else { + this.changedPassword = false + this.changePasswordError = res.error + } + }) } } } diff --git a/src/components/user_settings/user_settings.vue b/src/components/user_settings/user_settings.vue index ed1864cc..fbf3f651 100644 --- a/src/components/user_settings/user_settings.vue +++ b/src/components/user_settings/user_settings.vue @@ -49,6 +49,25 @@ <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"> + <h3>{{$t('settings.change_password')}}</h3> + <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"> <h3>{{$t('settings.follow_import')}}</h3> <p>{{$t('settings.import_followers_from_a_csv_file')}}</p> @@ -62,10 +81,31 @@ <p>{{$t('settings.follows_imported')}}</p> </div> <div v-else-if="followImportError"> - <i class="icon-cross" @click="dismissImported"</i> + <i class="icon-cross" @click="dismissImported"></i> <p>{{$t('settings.follow_import_error')}}</p> </div> </div> + <div class="setting-item" v-if="enableFollowsExport"> + <h3>{{$t('settings.follow_export')}}</h3> + <button class="btn btn-default" @click="exportFollows">{{$t('settings.follow_export_button')}}</button> + </div> + <div class="setting-item" v-else> + <h3>{{$t('settings.follow_export_processing')}}</h3> + </div> + <hr> + <div class="setting-item"> + <h3>{{$t('settings.delete_account')}}</h3> + <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> </div> </template> diff --git a/src/components/who_to_follow_panel/who_to_follow_panel.js b/src/components/who_to_follow_panel/who_to_follow_panel.js new file mode 100644 index 00000000..51b9f469 --- /dev/null +++ b/src/components/who_to_follow_panel/who_to_follow_panel.js @@ -0,0 +1,123 @@ +function showWhoToFollow (panel, reply, aHost, aUser) { + var users = reply.ids + var cn + var index = 0 + var random = Math.floor(Math.random() * 10) + for (cn = random; cn < users.length; cn = cn + 10) { + var user + user = users[cn] + var img + if (user.icon) { + img = user.icon + } else { + img = '/images/avi.png' + } + var name = user.to_id + if (index === 0) { + panel.img1 = img + panel.name1 = name + panel.$store.state.api.backendInteractor.externalProfile(name) + .then((externalUser) => { + if (!externalUser.error) { + panel.$store.commit('addNewUsers', [externalUser]) + panel.id1 = externalUser.id + } + }) + } else if (index === 1) { + panel.img2 = img + panel.name2 = name + panel.$store.state.api.backendInteractor.externalProfile(name) + .then((externalUser) => { + if (!externalUser.error) { + panel.$store.commit('addNewUsers', [externalUser]) + panel.id2 = externalUser.id + } + }) + } else if (index === 2) { + panel.img3 = img + panel.name3 = name + panel.$store.state.api.backendInteractor.externalProfile(name) + .then((externalUser) => { + if (!externalUser.error) { + panel.$store.commit('addNewUsers', [externalUser]) + panel.id3 = externalUser.id + } + }) + } + index = index + 1 + if (index > 2) { + break + } + } +} + +function getWhoToFollow (panel) { + var user = panel.$store.state.users.currentUser.screen_name + if (user) { + 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) + }) + } +} + +const WhoToFollowPanel = { + data: () => ({ + img1: '/images/avi.png', + name1: '', + id1: 0, + img2: '/images/avi.png', + name2: '', + id2: 0, + img3: '/images/avi.png', + name3: '', + id3: 0 + }), + computed: { + user: function () { + return this.$store.state.users.currentUser.screen_name + }, + moreUrl: function () { + var host = window.location.hostname + var user = this.user + var whoToFollowLink = this.$store.state.config.whoToFollowLink + var url + url = whoToFollowLink.replace(/{{host}}/g, encodeURIComponent(host)) + url = url.replace(/{{user}}/g, encodeURIComponent(user)) + return url + }, + showWhoToFollowPanel () { + return this.$store.state.config.showWhoToFollowPanel + } + }, + watch: { + user: function (user, oldUser) { + if (this.showWhoToFollowPanel) { + getWhoToFollow(this) + } + } + }, + mounted: + function () { + if (this.showWhoToFollowPanel) { + getWhoToFollow(this) + } + } +} + +export default WhoToFollowPanel diff --git a/src/components/who_to_follow_panel/who_to_follow_panel.vue b/src/components/who_to_follow_panel/who_to_follow_panel.vue new file mode 100644 index 00000000..5af6d0d5 --- /dev/null +++ b/src/components/who_to_follow_panel/who_to_follow_panel.vue @@ -0,0 +1,37 @@ +<template> + <div class="who-to-follow-panel"> + <div class="panel panel-default base01-background"> + <div class="panel-heading timeline-heading base02-background base04"> + <div class="title"> + Who to follow + </div> + </div> + <div class="panel-body who-to-follow"> + <p> + <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> + </p> + </div> + </div> + </div> +</template> + +<script src="./who_to_follow_panel.js" ></script> + +<style lang="scss"> + .who-to-follow * { + vertical-align: middle; + } + .who-to-follow img { + width: 32px; + height: 32px; + } + .who-to-follow p { + line-height: 40px; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + } +</style> diff --git a/src/i18n/messages.js b/src/i18n/messages.js index d1e62155..0b161192 100644 --- a/src/i18n/messages.js +++ b/src/i18n/messages.js @@ -270,6 +270,7 @@ const en = { cOrange: 'Orange (Favorite)', cGreen: 'Green (Retweet)', btnRadius: 'Buttons', + inputRadius: 'Input fields', panelRadius: 'Panels', avatarRadius: 'Avatars', avatarAltRadius: 'Avatars (Notifications)', @@ -288,7 +289,20 @@ const en = { 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.', - follow_import_error: 'Error importing followers' + follow_import_error: 'Error importing followers', + delete_account: 'Delete Account', + delete_account_description: 'Permanently delete your account and all your messages.', + delete_account_instructions: 'Type your password in the input below to confirm account deletion.', + delete_account_error: 'There was an issue deleting your account. If this persists please contact your instance administrator.', + follow_export: 'Follow export', + follow_export_processing: 'Processing, you\'ll soon be asked to download your file', + follow_export_button: 'Export your follows to a csv file', + change_password: 'Change Password', + current_password: 'Current password', + new_password: 'New password', + confirm_new_password: 'Confirm new password', + changed_password: 'Password changed successfully!', + change_password_error: 'There was an issue changing your password.' }, notifications: { notifications: 'Notifications', @@ -313,6 +327,7 @@ const en = { }, post_status: { posting: 'Posting', + content_warning: 'Content warning (optional)', default: 'Just landed in L.A.' }, finder: { @@ -830,8 +845,8 @@ const fr = { blocked: 'Bloqué', block: 'Bloquer', statuses: 'Statuts', - mute: 'Mettre en muet', - muted: 'Mis en muet', + mute: 'Masquer', + muted: 'Masqué', followers: 'Vous suivent', followees: 'Suivis', per_day: 'par jour', @@ -839,7 +854,7 @@ const fr = { }, timeline: { show_new: 'Afficher plus', - error_fetching: 'Erreur en cherchant des mises à jours', + error_fetching: 'Erreur en cherchant les mises à jour', up_to_date: 'À jour', load_older: 'Afficher plus', conversation: 'Conversation', @@ -850,32 +865,32 @@ const fr = { user_settings: 'Paramètres utilisateur', name_bio: 'Nom & Bio', name: 'Nom', - bio: 'Bioraphie', + bio: 'Biographie', avatar: 'Avatar', - current_avatar: 'Votre avatar', + current_avatar: 'Avatar actuel', set_new_avatar: 'Changer d\'avatar', - profile_banner: 'Bannière du profil', - current_profile_banner: 'Bannière du profil', + profile_banner: 'Bannière de profil', + current_profile_banner: 'Bannière de profil actuelle', set_new_profile_banner: 'Changer de bannière', profile_background: 'Image de fond', set_new_profile_background: 'Changer d\'image de fond', settings: 'Paramètres', theme: 'Thème', filtering: 'Filtre', - filtering_explanation: 'Tout les statuts contenant ces mots vont être cachés, un mot par ligne.', + filtering_explanation: 'Tout les statuts contenant ces mots seront masqués. Un mot par ligne.', attachments: 'Pièces jointes', - hide_attachments_in_tl: 'Cacher les pièces jointes dans le journal', - hide_attachments_in_convo: 'Cacher les pièces jointes dans les conversations', - nsfw_clickthrough: 'Activer le clic pour afficher les images marquées comme contenu adulte ou sensible', - autoload: 'Activer le chargement automatique une fois le bas de la page atteint', - reply_link_preview: 'Activer un aperçu d\'une réponse sur passage de la souris', + hide_attachments_in_tl: 'Masquer les pièces jointes dans le journal', + hide_attachments_in_convo: 'Masquer les pièces jointes dans les conversations', + nsfw_clickthrough: 'Masquer les images marquées comme contenu adulte ou sensible', + autoload: 'Charger la suite automatiquement une fois le bas de la page atteint', + reply_link_preview: 'Afficher un aperçu lors du survol de liens vers une réponse', presets: 'Thèmes prédéfinis', - theme_help: 'Utilisez les codes de couleur hexadécimaux (#aabbcc) pour customiser les couleurs de votre thème.', + theme_help: 'Spécifiez des codes couleur hexadécimaux (#aabbcc) pour personnaliser les couleurs du thème', background: 'Arrière plan', foreground: 'Premier plan', text: 'Texte', links: 'Liens', - streaming: 'Active le défilement automatique de nouveaux statuts lorsqu\'on est au haut de la page', + streaming: 'Charger automatiquement les nouveaux statuts lorsque vous êtes au haut de la page', follow_import: 'Importer ses abonnements', import_followers_from_a_csv_file: 'Importer ses abonnements depuis un fichier csv', follows_imported: 'Abonnements importés ! Le traitement peut prendre un moment.', @@ -886,33 +901,34 @@ const fr = { cGreen: 'Vert (Partager)', btnRadius: 'Boutons', panelRadius: 'Fenêtres', + inputRadius: 'Champs de texte', avatarRadius: 'Avatars', avatarAltRadius: 'Avatars (Notifications)', tooltipRadius: 'Info-bulles/alertes ', attachmentRadius: 'Pièces jointes', - radii_help: 'Mettre en place l\'arondissement des coins de l\'interface (en pixels)', - stop_gifs: 'Passer la souris sur un GIF pour l\'animer' + radii_help: 'Vous pouvez ici choisir le niveau d\'arrondi des angles de l\'interface (en pixels)', + stop_gifs: 'N\'animer les GIFS que lors du survol du curseur de la souris' }, notifications: { notifications: 'Notifications', read: 'Lu !', - followed_you: 'vous a suivi', + followed_you: 'a commencé à vous suivre', favorited_you: 'a aimé votre statut', repeated_you: 'a partagé votre statut' }, login: { login: 'Connexion', - username: 'Nom d\'utilisateur', + username: 'Identifiant', password: 'Mot de passe', register: 'S\'inscrire', logout: 'Déconnexion' }, registration: { registration: 'Inscription', - fullname: 'Nom affiché', + fullname: 'Pseudonyme', email: 'Adresse email', bio: 'Biographie', - password_confirm: 'Confirmez le mot de passe' + password_confirm: 'Confirmation du mot de passe' }, post_status: { posting: 'Envoi en cours', @@ -920,7 +936,7 @@ const fr = { }, finder: { find_user: 'Chercher un utilisateur', - error_fetching_user: 'Une erreur est survenue lors de la recherche de l\'utilisateur' + error_fetching_user: 'Erreur lors de la recherche de l\'utilisateur' }, general: { submit: 'Envoyer', @@ -1017,7 +1033,7 @@ const oc = { timeline: { show_new: 'Ne veire mai', error_fetching: 'Error en cercant de mesas a jorn', - up_to_date: 'Actualizat', + up_to_date: 'A jorn', load_older: 'Ne veire mai', conversation: 'Conversacion', collapse: 'Tampar', @@ -1049,6 +1065,7 @@ const oc = { cRed: 'Roge (Anullar)', cOrange: 'Irange (Metre en favorit)', cGreen: 'Verd (Repartajar)', + inputRadius: 'Camps tèxte', btnRadius: 'Botons', panelRadius: 'Panèls', avatarRadius: 'Avatars', @@ -1104,7 +1121,7 @@ const oc = { apply: 'Aplicar' }, user_profile: { - timeline_title: 'Flux a l’utilizaire' + timeline_title: 'Flux utilizaire' } } @@ -1169,13 +1186,14 @@ const pl = { cOrange: 'Pomarańczowy (ulubione)', cGreen: 'Zielony (powtórzenia)', btnRadius: 'Przyciski', + inputRadius: 'Pola tekstowe', panelRadius: 'Panele', avatarRadius: 'Awatary', avatarAltRadius: 'Awatary (powiadomienia)', tooltipRadius: 'Etykiety/alerty', attachmentRadius: 'Załączniki', filtering: 'Filtrowanie', - filtering_explanation: 'Wszystkie statusy zawierające te słowa będą wyciszone. Jedno słowo na linijkę', + filtering_explanation: 'Wszystkie statusy zawierające te słowa będą wyciszone. Jedno słowo na linijkę.', attachments: 'Załączniki', hide_attachments_in_tl: 'Ukryj załączniki w osi czasu', hide_attachments_in_convo: 'Ukryj załączniki w rozmowach', @@ -1187,7 +1205,20 @@ const pl = { follow_import: 'Import obserwowanych', import_followers_from_a_csv_file: 'Importuj obserwowanych z pliku CSV', follows_imported: 'Obserwowani zaimportowani! Przetwarzanie może trochę potrwać.', - follow_import_error: 'Błąd przy importowaniu obserwowanych' + follow_import_error: 'Błąd przy importowaniu obserwowanych', + delete_account: 'Usuń konto', + delete_account_description: 'Trwale usuń konto i wszystkie posty.', + delete_account_instructions: 'Wprowadź swoje hasło w poniższe pole aby potwierdzić usunięcie konta.', + delete_account_error: 'Wystąpił problem z usuwaniem twojego konta. Jeżeli problem powtarza się, poinformuj administratora swojej instancji.', + follow_export: 'Eksport obserwowanych', + follow_export_processing: 'Przetwarzanie, wkrótce twój plik zacznie się ściągać.', + follow_export_button: 'Eksportuj swoją listę obserwowanych do pliku CSV', + change_password: 'Zmień hasło', + current_password: 'Obecne hasło', + new_password: 'Nowe hasło', + confirm_new_password: 'Potwierdź nowe hasło', + changed_password: 'Hasło zmienione poprawnie!', + change_password_error: 'Podczas zmiany hasła wystąpił problem.' }, notifications: { notifications: 'Powiadomienia', @@ -1505,6 +1536,7 @@ const ru = { cOrange: 'Нравится', cGreen: 'Повторить', btnRadius: 'Кнопки', + inputRadius: 'Поля ввода', panelRadius: 'Панели', avatarRadius: 'Аватары', avatarAltRadius: 'Аватары в уведомлениях', @@ -1681,6 +1713,139 @@ const nb = { } } +const he = { + chat: { + title: 'צ\'אט' + }, + nav: { + chat: 'צ\'אט מקומי', + timeline: 'ציר הזמן', + mentions: 'אזכורים', + public_tl: 'ציר הזמן הציבורי', + twkn: 'כל הרשת הידועה' + }, + user_card: { + follows_you: 'עוקב אחריך!', + following: 'עוקב!', + follow: 'עקוב', + blocked: 'חסום!', + block: 'חסימה', + statuses: 'סטטוסים', + mute: 'השתק', + muted: 'מושתק', + followers: 'עוקבים', + followees: 'נעקבים', + per_day: 'ליום', + remote_follow: 'עקיבה מרחוק' + }, + timeline: { + show_new: 'הראה חדש', + error_fetching: 'שגיאה בהבאת הודעות', + up_to_date: 'עדכני', + load_older: 'טען סטטוסים חדשים', + conversation: 'שיחה', + collapse: 'מוטט', + repeated: 'חזר' + }, + settings: { + user_settings: 'הגדרות משתמש', + name_bio: 'שם ואודות', + name: 'שם', + bio: 'אודות', + avatar: 'תמונת פרופיל', + current_avatar: 'תמונת הפרופיל הנוכחית שלך', + set_new_avatar: 'קבע תמונת פרופיל חדשה', + profile_banner: 'כרזת הפרופיל', + current_profile_banner: 'כרזת הפרופיל הנוכחית שלך', + set_new_profile_banner: 'קבע כרזת פרופיל חדשה', + profile_background: 'רקע הפרופיל', + set_new_profile_background: 'קבע רקע פרופיל חדש', + settings: 'הגדרות', + theme: 'תמה', + presets: 'ערכים קבועים מראש', + theme_help: 'השתמש בקודי צבע הקס (#אדום-אדום-ירוק-ירוק-כחול-כחול) על מנת להתאים אישית את תמת הצבע שלך.', + radii_help: 'קבע מראש עיגול פינות לממשק (בפיקסלים)', + background: 'רקע', + foreground: 'חזית', + text: 'טקסט', + links: 'לינקים', + cBlue: 'כחול (תגובה, עקיבה)', + cRed: 'אדום (ביטול)', + cOrange: 'כתום (לייק)', + cGreen: 'ירוק (חזרה)', + btnRadius: 'כפתורים', + inputRadius: 'שדות קלט', + panelRadius: 'פאנלים', + avatarRadius: 'תמונות פרופיל', + avatarAltRadius: 'תמונות פרופיל (התראות)', + tooltipRadius: 'טולטיפ \\ התראות', + attachmentRadius: 'צירופים', + filtering: 'סינון', + filtering_explanation: 'כל הסטטוסים הכוללים את המילים הללו יושתקו, אחד לשורה', + attachments: 'צירופים', + hide_attachments_in_tl: 'החבא צירופים בציר הזמן', + hide_attachments_in_convo: 'החבא צירופים בשיחות', + nsfw_clickthrough: 'החל החבאת צירופים לא בטוחים לצפיה בעת עבודה בעזרת לחיצת עכבר', + stop_gifs: 'נגן-בעת-ריחוף GIFs', + autoload: 'החל טעינה אוטומטית בגלילה לתחתית הדף', + streaming: 'החל זרימת הודעות אוטומטית בעת גלילה למעלה הדף', + reply_link_preview: 'החל תצוגה מקדימה של לינק-תגובה בעת ריחוף עם העכבר', + follow_import: 'יבוא עקיבות', + import_followers_from_a_csv_file: 'ייבא את הנעקבים שלך מקובץ csv', + follows_imported: 'נעקבים יובאו! ייקח זמן מה לעבד אותם.', + follow_import_error: 'שגיאה בייבוא נעקבים.', + delete_account: 'מחק משתמש', + delete_account_description: 'מחק לצמיתות את המשתמש שלך ואת כל הודעותיך.', + delete_account_instructions: 'הכנס את סיסמתך בקלט למטה על מנת לאשר מחיקת משתמש.', + delete_account_error: 'הייתה בעיה במחיקת המשתמש. אם זה ממשיך, אנא עדכן את מנהל השרת שלך.', + follow_export: 'יצוא עקיבות', + follow_export_processing: 'טוען. בקרוב תתבקש להוריד את הקובץ את הקובץ שלך', + follow_export_button: 'ייצא את הנעקבים שלך לקובץ csv', + change_password: 'שנה סיסמה', + current_password: 'סיסמה נוכחית', + new_password: 'סיסמה חדשה', + confirm_new_password: 'אשר סיסמה', + changed_password: 'סיסמה שונתה בהצלחה!', + change_password_error: 'הייתה בעיה בשינוי סיסמתך.' + }, + notifications: { + notifications: 'התראות', + read: 'קרא!', + followed_you: 'עקב אחריך!', + favorited_you: 'אהב את הסטטוס שלך', + repeated_you: 'חזר על הסטטוס שלך' + }, + login: { + login: 'התחבר', + username: 'שם המשתמש', + password: 'סיסמה', + register: 'הירשם', + logout: 'התנתק' + }, + registration: { + registration: 'הרשמה', + fullname: 'שם תצוגה', + email: 'אימייל', + bio: 'אודות', + password_confirm: 'אישור סיסמה' + }, + post_status: { + posting: 'מפרסם', + default: 'הרגע נחת ב-ל.א.' + }, + finder: { + find_user: 'מציאת משתמש', + error_fetching_user: 'שגיאה במציאת משתמש' + }, + general: { + submit: 'שלח', + apply: 'החל' + }, + user_profile: { + timeline_title: 'ציר זמן המשתמש' + } +} + const messages = { de, fi, @@ -1697,7 +1862,8 @@ const messages = { es, pt, ru, - nb + nb, + he } export default messages diff --git a/src/main.js b/src/main.js index 7ca34adf..0c964dcc 100644 --- a/src/main.js +++ b/src/main.js @@ -88,11 +88,15 @@ window.fetch('/api/statusnet/config.json') window.fetch('/static/config.json') .then((res) => res.json()) .then((data) => { - const {theme, background, logo, showInstanceSpecificPanel} = data + const {theme, background, logo, showWhoToFollowPanel, whoToFollowProvider, whoToFollowLink, showInstanceSpecificPanel, scopeOptionsEnabled} = 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 }) if (data['chatDisabled']) { store.dispatch('disableChat') } diff --git a/src/services/api/api.service.js b/src/services/api/api.service.js index f14bfd6d..0d91851b 100644 --- a/src/services/api/api.service.js +++ b/src/services/api/api.service.js @@ -30,6 +30,8 @@ const BLOCKING_URL = '/api/blocks/create.json' const UNBLOCKING_URL = '/api/blocks/destroy.json' const USER_URL = '/api/users/show.json' const FOLLOW_IMPORT_URL = '/api/pleroma/follow_import' +const DELETE_ACCOUNT_URL = '/api/pleroma/delete_account' +const CHANGE_PASSWORD_URL = '/api/pleroma/change_password' import { each, map } from 'lodash' import 'whatwg-fetch' @@ -329,12 +331,14 @@ const retweet = ({ id, credentials }) => { }) } -const postStatus = ({credentials, status, mediaIds, inReplyToStatusId}) => { +const postStatus = ({credentials, status, spoilerText, visibility, mediaIds, inReplyToStatusId}) => { const idsText = mediaIds.join(',') const form = new FormData() form.append('status', status) form.append('source', 'Pleroma FE') + if (spoilerText) form.append('spoiler_text', spoilerText) + if (visibility) form.append('visibility', visibility) form.append('media_ids', idsText) if (inReplyToStatusId) { form.append('in_reply_to_status_id', inReplyToStatusId) @@ -373,6 +377,34 @@ const followImport = ({params, credentials}) => { .then((response) => response.ok) } +const deleteAccount = ({credentials, password}) => { + const form = new FormData() + + form.append('password', password) + + return fetch(DELETE_ACCOUNT_URL, { + body: form, + method: 'POST', + headers: authHeaders(credentials) + }) + .then((response) => response.json()) +} + +const changePassword = ({credentials, password, newPassword, newPasswordConfirmation}) => { + const form = new FormData() + + form.append('password', password) + form.append('new_password', newPassword) + form.append('new_password_confirmation', newPasswordConfirmation) + + return fetch(CHANGE_PASSWORD_URL, { + body: form, + method: 'POST', + headers: authHeaders(credentials) + }) + .then((response) => response.json()) +} + const fetchMutes = ({credentials}) => { const url = '/api/qvitter/mutes.json' @@ -408,7 +440,9 @@ const apiService = { updateProfile, updateBanner, externalProfile, - followImport + followImport, + deleteAccount, + changePassword } export default apiService diff --git a/src/services/backend_interactor_service/backend_interactor_service.js b/src/services/backend_interactor_service/backend_interactor_service.js index 52b8286b..14173558 100644 --- a/src/services/backend_interactor_service/backend_interactor_service.js +++ b/src/services/backend_interactor_service/backend_interactor_service.js @@ -61,6 +61,9 @@ const backendInteractorService = (credentials) => { const externalProfile = (profileUrl) => apiService.externalProfile({profileUrl, credentials}) const followImport = ({params}) => apiService.followImport({params, credentials}) + const deleteAccount = ({password}) => apiService.deleteAccount({credentials, password}) + const changePassword = ({password, newPassword, newPasswordConfirmation}) => apiService.changePassword({credentials, password, newPassword, newPasswordConfirmation}) + const backendInteractorServiceInstance = { fetchStatus, fetchConversation, @@ -82,7 +85,9 @@ const backendInteractorService = (credentials) => { updateBanner, updateProfile, externalProfile, - followImport + followImport, + deleteAccount, + changePassword } return backendInteractorServiceInstance diff --git a/src/services/status_poster/status_poster.service.js b/src/services/status_poster/status_poster.service.js index 001ff8a5..3381e9e2 100644 --- a/src/services/status_poster/status_poster.service.js +++ b/src/services/status_poster/status_poster.service.js @@ -1,10 +1,10 @@ import { map } from 'lodash' import apiService from '../api/api.service.js' -const postStatus = ({ store, status, media = [], inReplyToStatusId = undefined }) => { +const postStatus = ({ store, status, spoilerText, visibility, media = [], inReplyToStatusId = undefined }) => { const mediaIds = map(media, 'id') - return apiService.postStatus({credentials: store.state.users.currentUser.credentials, status, mediaIds, inReplyToStatusId}) + return apiService.postStatus({credentials: store.state.users.currentUser.credentials, status, spoilerText, visibility, mediaIds, inReplyToStatusId}) .then((data) => data.json()) .then((data) => { if (!data.error) { diff --git a/src/services/style_setter/style_setter.js b/src/services/style_setter/style_setter.js index 9dc4a3e1..493d444e 100644 --- a/src/services/style_setter/style_setter.js +++ b/src/services/style_setter/style_setter.js @@ -71,13 +71,11 @@ const setColors = (col, commit) => { colors.bg = rgb2hex(col.bg.r, col.bg.g, col.bg.b) // background colors.lightBg = rgb2hex((col.bg.r + col.fg.r) / 2, (col.bg.g + col.fg.g) / 2, (col.bg.b + col.fg.b) / 2) // hilighted bg colors.btn = rgb2hex(col.fg.r, col.fg.g, col.fg.b) // panels & buttons + colors.input = `rgba(${col.fg.r}, ${col.fg.g}, ${col.fg.b}, .5)` colors.border = rgb2hex(col.fg.r - mod, col.fg.g - mod, col.fg.b - mod) // borders - colors.faint = rgb2hex( - col.text.r * 0.45 + col.fg.r * 0.55, - col.text.g * 0.45 + col.fg.g * 0.55, - col.text.b * 0.45 + col.fg.b * 0.55) // faint text + colors.faint = `rgba(${col.text.r}, ${col.text.g}, ${col.text.b}, .5)` colors.fg = rgb2hex(col.text.r, col.text.g, col.text.b) // text - colors.lightFg = rgb2hex(col.text.r - mod, col.text.g - mod, col.text.b - mod) // strong text + colors.lightFg = rgb2hex(col.text.r - mod * 5, col.text.g - mod * 5, col.text.b - mod * 5) // strong text colors['base07'] = rgb2hex(col.text.r - mod * 2, col.text.g - mod * 2, col.text.b - mod * 2) @@ -92,6 +90,7 @@ const setColors = (col, commit) => { colors.cAlertRed = col.cRed && `rgba(${col.cRed.r}, ${col.cRed.g}, ${col.cRed.b}, .5)` radii.btnRadius = col.btnRadius + radii.inputRadius = col.inputRadius radii.panelRadius = col.panelRadius radii.avatarRadius = col.avatarRadius radii.avatarAltRadius = col.avatarAltRadius |
