Commit 821a2bcc authored by feld's avatar feld

Merge branch 'develop' into feature/lockdown-timelines-private-mode

parents 43d4d17b 051b9703
......@@ -24,7 +24,7 @@ test:
- apt install firefox-esr -y --no-install-recommends
- firefox --version
- yarn
- npm run unit
- yarn unit
build:
stage: build
......
## 2017-02-20
- Overall CSS styling fixes
- Current theme is displayed in theme selector
- Theme selector is moved to the settings page
- Oembed attachments will now display correctly
- Styling changes to the user info cards
- Notification count in title
- Better Notification handling (persistance, mark as read)
- Post statuses with ctrl+enter
- Links in statuses open in a new tab
- Optimized mobile view
- Fix crash on persistance failure
- Compress persisted state
- Sync mutes with backend (SEE NOTE BELOW)
Pleroma will now try to get the current mutes from the backend. Sadly, a bug in
Qvitter will not allow getting the mutes from the endpoint, because it will
ignore HTTP Basic authentication. Mutes will still persist in Pleroma through
localstorage, but the mutes from Qvitter won't be picked up if the call fails.
The patch for Qvitter:
--- a/actions/apiqvittermutes.php
+++ b/actions/apiqvittermutes.php
@@ -74,7 +74,7 @@ class ApiQvitterMutesAction extends ApiPrivateAuthAction
{
parent::handle();
- $this->target = Profile::current();
+ $this->target = $this->scoped;
if(!$this->target instanceof Profile) {
$this->clientError(_('You have to be logged in to view your mutes.'), 403);
......@@ -3,6 +3,7 @@ var config = require('../config')
var utils = require('./utils')
var projectRoot = path.resolve(__dirname, '../')
var ServiceWorkerWebpackPlugin = require('serviceworker-webpack-plugin')
var FontelloPlugin = require("fontello-webpack-plugin")
var env = process.env.NODE_ENV
// check env & config/index.js to decide weither to enable CSS Sourcemaps for the
......@@ -11,6 +12,8 @@ var cssSourceMapDev = (env === 'development' && config.dev.cssSourceMap)
var cssSourceMapProd = (env === 'production' && config.build.productionSourceMap)
var useCssSourceMap = cssSourceMapDev || cssSourceMapProd
var now = Date.now()
module.exports = {
entry: {
app: './src/main.js'
......@@ -90,6 +93,14 @@ module.exports = {
new ServiceWorkerWebpackPlugin({
entry: path.join(__dirname, '..', 'src/sw.js'),
filename: 'sw-pleroma.js'
}),
new FontelloPlugin({
config: require('../static/fontello.json'),
name: 'fontello',
output: {
css: 'static/[name].' + now + '.css', // [hash] is not supported. Use the current timestamp instead for versioning.
font: 'static/font/[name].' + now + '.[ext]'
}
})
]
}
......@@ -77,6 +77,9 @@ Use custom image for NSFW'd images
### `showFeaturesPanel`
Show panel showcasing instance features/settings to logged-out visitors
### `hideSitename`
Hide instance name in header
## Indirect configuration
Some features are configured depending on how backend is configured. In general the approach is "if backend allows it there's no need to hide it, if backend doesn't allow it there's no need to show it.
......
......@@ -6,8 +6,6 @@
<title>Pleroma</title>
<!--server-generated-meta-->
<link rel="icon" type="image/png" href="/favicon.png">
<link rel="stylesheet" href="/static/font/css/fontello.css">
<link rel="stylesheet" href="/static/font/css/animation.css">
</head>
<body class="hidden">
<noscript>To use Pleroma, please enable JavaScript.</noscript>
......
......@@ -90,6 +90,7 @@ export default {
},
sitename () { return this.$store.state.instance.name },
chat () { return this.$store.state.chat.channel.state === 'joined' },
hideSitename () { return this.$store.state.instance.hideSitename },
suggestionsEnabled () { return this.$store.state.instance.suggestionsEnabled },
showInstanceSpecificPanel () {
return this.$store.state.instance.showInstanceSpecificPanel &&
......
......@@ -855,3 +855,31 @@ nav {
.btn.btn-default {
min-height: 28px;
}
.animate-spin {
animation: spin 2s infinite linear;
display: inline-block;
}
@keyframes spin {
0% {
transform: rotate(0deg);
}
100% {
transform: rotate(359deg);
}
}
.new-status-notification {
position:relative;
margin-top: -1px;
font-size: 1.1em;
border-width: 1px 0 0 0;
border-style: solid;
border-color: var(--border, $fallback--border);
padding: 10px;
z-index: 1;
background-color: $fallback--fg;
background-color: var(--panel, $fallback--fg);
}
......@@ -31,6 +31,7 @@
</div>
<div class="item">
<router-link
v-if="!hideSitename"
class="site-name"
:to="{ name: 'root' }"
active-class="home"
......
......@@ -108,6 +108,7 @@ const setSettings = async ({ apiConfig, staticConfig, store }) => {
copyInstanceOption('alwaysShowSubjectInput')
copyInstanceOption('noAttachmentLinks')
copyInstanceOption('showFeaturesPanel')
copyInstanceOption('hideSitename')
return store.dispatch('setTheme', config['theme'])
}
......
......@@ -25,9 +25,6 @@ const AccountActions = {
},
reportUser () {
this.$store.dispatch('openUserReportingModal', this.user.id)
},
mentionUser () {
this.$store.dispatch('openPostStatusModal', { replyTo: true, repliedUser: this.user })
}
}
}
......
......@@ -9,17 +9,7 @@
>
<div slot="popover">
<div class="dropdown-menu">
<button
class="btn btn-default btn-block dropdown-item"
@click="mentionUser"
>
{{ $t('user_card.mention') }}
</button>
<template v-if="user.following">
<div
role="separator"
class="dropdown-divider"
/>
<button
v-if="user.showing_reblogs"
class="btn btn-default dropdown-item"
......@@ -34,11 +24,11 @@
>
{{ $t('user_card.show_repeats') }}
</button>
<div
role="separator"
class="dropdown-divider"
/>
</template>
<div
role="separator"
class="dropdown-divider"
/>
<button
v-if="user.statusnet_blocking"
class="btn btn-default btn-block dropdown-item"
......
......@@ -43,7 +43,8 @@ const conversation = {
'collapsable',
'isPage',
'pinnedStatusIdsObject',
'inProfile'
'inProfile',
'profileUserId'
],
created () {
if (this.isPage) {
......
......@@ -27,6 +27,7 @@
:highlight="getHighlight()"
:replies="getReplies(status.id)"
:in-profile="inProfile"
:profile-user-id="profileUserId"
class="status-fadein panel-body"
@goto="setHighlight"
@toggleExpanded="toggleExpanded"
......
......@@ -51,8 +51,8 @@ export default {
methods: {
getLanguageName (code) {
const specialLanguageNames = {
'ja': 'Japanese (やさしいにほんご)',
'ja_pedantic': 'Japanese (日本語)',
'ja': 'Japanese (日本語)',
'ja_easy': 'Japanese (やさしいにほんご)',
'zh': 'Chinese (简体中文)'
}
return specialLanguageNames[code] || ISO6391.getName(code)
......
......@@ -10,13 +10,13 @@
:src="currentMedia.url"
@touchstart.stop="mediaTouchStart"
@touchmove.stop="mediaTouchMove"
@click="hide"
>
<VideoAttachment
v-if="type === 'video'"
class="modal-image"
:attachment="currentMedia"
:controls="true"
@click.stop.native=""
/>
<button
v-if="canNavigate"
......
......@@ -29,6 +29,7 @@ const MobileNav = {
unseenNotificationsCount () {
return this.unseenNotifications.length
},
hideSitename () { return this.$store.state.instance.hideSitename },
sitename () { return this.$store.state.instance.name }
},
methods: {
......
......@@ -17,6 +17,7 @@
<i class="button-icon icon-menu" />
</a>
<router-link
v-if="!hideSitename"
class="site-name"
:to="{ name: 'root' }"
active-class="home"
......
import { mapState } from 'vuex'
import { get } from 'lodash'
const MRFTransparencyPanel = {
computed: mapState({
federationPolicy: state => state.instance.federationPolicy,
mrfPolicies: state => state.instance.federationPolicy.mrf_policies,
acceptInstances: state => state.instance.federationPolicy.mrf_simple.accept,
rejectInstances: state => state.instance.federationPolicy.mrf_simple.reject,
quarantineInstances: state => state.instance.federationPolicy.quarantined_instances,
ftlRemovalInstances: state => state.instance.federationPolicy.mrf_simple.federated_timeline_removal,
mediaNsfwInstances: state => state.instance.federationPolicy.mrf_simple.media_nsfw,
mediaRemovalInstances: state => state.instance.federationPolicy.mrf_simple.media_removal
})
computed: {
...mapState({
federationPolicy: state => get(state, 'instance.federationPolicy'),
mrfPolicies: state => get(state, 'instance.federationPolicy.mrf_policies', []),
quarantineInstances: state => get(state, 'instance.federationPolicy.quarantined_instances', []),
acceptInstances: state => get(state, 'instance.federationPolicy.mrf_simple.accept', []),
rejectInstances: state => get(state, 'instance.federationPolicy.mrf_simple.reject', []),
ftlRemovalInstances: state => get(state, 'instance.federationPolicy.mrf_simple.federated_timeline_removal', []),
mediaNsfwInstances: state => get(state, 'instance.federationPolicy.mrf_simple.media_nsfw', []),
mediaRemovalInstances: state => get(state, 'instance.federationPolicy.mrf_simple.media_removal', [])
}),
hasInstanceSpecificPolicies () {
return this.quarantineInstances.length ||
this.acceptInstances.length ||
this.rejectInstances.length ||
this.ftlRemovalInstances.length ||
this.mediaNsfwInstances.length ||
this.mediaRemovalInstances.length
}
}
}
export default MRFTransparencyPanel
......@@ -22,7 +22,9 @@
/>
</ul>
<h2>{{ $t("about.mrf_policy_simple") }}</h2>
<h2 v-if="hasInstanceSpecificPolicies">
{{ $t("about.mrf_policy_simple") }}
</h2>
<div v-if="acceptInstances.length">
<h4>{{ $t("about.mrf_policy_simple_accept") }}</h4>
......
......@@ -4,10 +4,7 @@ import { mapState } from 'vuex'
const NavPanel = {
created () {
if (this.currentUser && this.currentUser.locked) {
const store = this.$store
const credentials = store.state.users.currentUser.credentials
followRequestFetcher.startFetching({ store, credentials })
this.$store.dispatch('startFetchingFollowRequest')
}
},
computed: mapState({
......
......@@ -4,22 +4,22 @@
<ul>
<li v-if="currentUser">
<router-link :to="{ name: 'friends' }">
{{ $t("nav.timeline") }}
<i class="button-icon icon-home-2" /> {{ $t("nav.timeline") }}
</router-link>
</li>
<li v-if="currentUser">
<router-link :to="{ name: 'interactions', params: { username: currentUser.screen_name } }">
{{ $t("nav.interactions") }}
<i class="button-icon icon-bell-alt" /> {{ $t("nav.interactions") }}
</router-link>
</li>
<li v-if="currentUser">
<router-link :to="{ name: 'dms', params: { username: currentUser.screen_name } }">
{{ $t("nav.dms") }}
<i class="button-icon icon-mail-alt" /> {{ $t("nav.dms") }}
</router-link>
</li>
<li v-if="currentUser && currentUser.locked">
<router-link :to="{ name: 'friend-requests' }">
{{ $t("nav.friend_requests") }}
<i class="button-icon icon-user-plus" /> {{ $t("nav.friend_requests") }}
<span
v-if="followRequestCount > 0"
class="badge follow-request-count"
......@@ -30,17 +30,17 @@
</li>
<li v-if="currentUser || !privateMode">
<router-link :to="{ name: 'public-timeline' }">
{{ $t("nav.public_tl") }}
<i class="button-icon icon-users" /> {{ $t("nav.public_tl") }}
</router-link>
</li>
<li v-if="(currentUser || !privateMode) && federating">
<router-link :to="{ name: 'public-external-timeline' }">
{{ $t("nav.twkn") }}
<i class="button-icon icon-globe" /> {{ $t("nav.twkn") }}
</router-link>
</li>
<li>
<router-link :to="{ name: 'about' }">
{{ $t("nav.about") }}
<i class="button-icon icon-info-circled" /> {{ $t("nav.about") }}
</router-link>
</li>
</ul>
......@@ -113,4 +113,8 @@
}
}
}
.nav-panel .button-icon:before {
width: 1.1em;
}
</style>
......@@ -169,9 +169,7 @@ const PostStatusForm = {
if (this.submitDisabled) { return }
if (this.newStatus.status === '') {
if (this.newStatus.files.length > 0) {
this.newStatus.status = '\u200b' // hack
} else {
if (this.newStatus.files.length === 0) {
this.error = 'Cannot post an empty status with no files'
return
}
......
......@@ -172,7 +172,7 @@
for="captcha-label"
>{{ $t('captcha') }}</label>
<template v-if="captcha.type == 'kocaptcha'">
<template v-if="['kocaptcha', 'native'].includes(captcha.type)">
<img
:src="captcha.url"
@click="setCaptcha"
......
......@@ -270,6 +270,17 @@
</li>
</ul>
</div>
<div class="setting-item">
<h2>{{ $t('settings.fun') }}</h2>
<ul class="setting-list">
<li>
<Checkbox v-model="greentext">
{{ $t('settings.greentext') }} {{ $t('settings.instance_default', { value: greentextLocalizedValue }) }}
</Checkbox>
</li>
</ul>
</div>
</div>
<div :label="$t('settings.theme')">
......
......@@ -10,6 +10,10 @@ const SideDrawer = {
}),
created () {
this.closeGesture = GestureService.swipeGesture(GestureService.DIRECTION_LEFT, this.toggleDrawer)
if (this.currentUser && this.currentUser.locked) {
this.$store.dispatch('startFetchingFollowRequest')
}
},
components: { UserCard },
computed: {
......@@ -29,6 +33,9 @@ const SideDrawer = {
logo () {
return this.$store.state.instance.logo
},
hideSitename () {
return this.$store.state.instance.hideSitename
},
sitename () {
return this.$store.state.instance.name
},
......
......@@ -27,7 +27,7 @@
class="side-drawer-logo-wrapper"
>
<img :src="logo">
<span>{{ sitename }}</span>
<span v-if="!hideSitename">{{ sitename }}</span>
</div>
</div>
<ul>
......@@ -36,7 +36,7 @@
@click="toggleDrawer"
>
<router-link :to="{ name: 'login' }">
{{ $t("login.login") }}
<i class="button-icon icon-login" /> {{ $t("login.login") }}
</router-link>
</li>
<li
......@@ -44,7 +44,7 @@
@click="toggleDrawer"
>
<router-link :to="{ name: 'dms', params: { username: currentUser.screen_name } }">
{{ $t("nav.dms") }}
<i class="button-icon icon-mail-alt" /> {{ $t("nav.dms") }}
</router-link>
</li>
<li
......@@ -52,7 +52,7 @@
@click="toggleDrawer"
>
<router-link :to="{ name: 'interactions', params: { username: currentUser.screen_name } }">
{{ $t("nav.interactions") }}
<i class="button-icon icon-bell-alt" /> {{ $t("nav.interactions") }}
</router-link>
</li>
</ul>
......@@ -62,7 +62,7 @@
@click="toggleDrawer"
>
<router-link :to="{ name: 'friends' }">
{{ $t("nav.timeline") }}
<i class="button-icon icon-home-2" /> {{ $t("nav.timeline") }}
</router-link>
</li>
<li
......@@ -70,7 +70,7 @@
@click="toggleDrawer"
>
<router-link to="/friend-requests">
{{ $t("nav.friend_requests") }}
<i class="button-icon icon-user-plus" /> {{ $t("nav.friend_requests") }}
<span
v-if="followRequestCount > 0"
class="badge follow-request-count"
......@@ -81,12 +81,12 @@
</li>
<li @click="toggleDrawer" v-if="currentUser || !privateMode">
<router-link to="/main/public">
{{ $t("nav.public_tl") }}
<i class="button-icon icon-users" /> {{ $t("nav.public_tl") }}
</router-link>
</li>
<li @click="toggleDrawer" v-if="(currentUser || !privateMode) && federating">
<router-link to="/main/all">
{{ $t("nav.twkn") }}
<i class="button-icon icon-globe" /> {{ $t("nav.twkn") }}
</router-link>
</li>
<li
......@@ -94,14 +94,14 @@
@click="toggleDrawer"
>
<router-link :to="{ name: 'chat' }">
{{ $t("nav.chat") }}
<i class="button-icon icon-chat" /> {{ $t("nav.chat") }}
</router-link>
</li>
</ul>
<ul>
<li @click="toggleDrawer" v-if="currentUser || !privateMode">
<router-link :to="{ name: 'search' }">
{{ $t("nav.search") }}
<i class="button-icon icon-search" /> {{ $t("nav.search") }}
</router-link>
</li>
<li
......@@ -109,17 +109,17 @@
@click="toggleDrawer"
>
<router-link :to="{ name: 'who-to-follow' }">
{{ $t("nav.who_to_follow") }}
<i class="button-icon icon-user-plus" /> {{ $t("nav.who_to_follow") }}
</router-link>
</li>
<li @click="toggleDrawer">
<router-link :to="{ name: 'settings' }">
{{ $t("settings.settings") }}
<i class="button-icon icon-cog" /> {{ $t("settings.settings") }}
</router-link>
</li>
<li @click="toggleDrawer">
<router-link :to="{ name: 'about'}">
{{ $t("nav.about") }}
<i class="button-icon icon-info-circled" /> {{ $t("nav.about") }}
</router-link>
</li>
<li
......@@ -130,7 +130,7 @@
href="/pleroma/admin/#/login-pleroma"
target="_blank"
>
{{ $t("nav.administration") }}
<i class="button-icon icon-gauge" /> {{ $t("nav.administration") }}
</a>
</li>
<li
......@@ -141,7 +141,7 @@
href="#"
@click="doLogout"
>
{{ $t("login.logout") }}
<i class="button-icon icon-logout" /> {{ $t("login.logout") }}
</a>
</li>
</ul>
......@@ -215,6 +215,10 @@
box-shadow: var(--panelShadow);
background-color: $fallback--bg;
background-color: var(--bg, $fallback--bg);
.button-icon:before {
width: 1.1em;
}
}
.side-drawer-logo-wrapper {
......
......@@ -13,10 +13,11 @@ import Timeago from '../timeago/timeago.vue'
import StatusPopover from '../status_popover/status_popover.vue'
import generateProfileLink from 'src/services/user_profile_link_generator/user_profile_link_generator'
import fileType from 'src/services/file_type/file_type.service'
import { processHtml } from 'src/services/tiny_post_html_processor/tiny_post_html_processor.service.js'
import { highlightClass, highlightStyle } from '../../services/user_highlighter/user_highlighter.js'
import { mentionMatchesUrl, extractTagFromUrl } from 'src/services/matcher/matcher.service.js'
import { filter, unescape, uniqBy } from 'lodash'
import { mapGetters } from 'vuex'
import { mapGetters, mapState } from 'vuex'
const Status = {
name: 'Status',
......@@ -32,7 +33,8 @@ const Status = {
'noHeading',
'inlineExpanded',
'showPinned',
'inProfile'
'inProfile',
'profileUserId'
],
data () {
return {
......@@ -42,8 +44,8 @@ const Status = {
showingTall: this.inConversation && this.focused,
showingLongSubject: false,
error: null,
expandingSubject: !this.$store.getters.mergedConfig.collapseMessageWithSubject,
betterShadow: this.$store.state.interface.browserSupport.cssFilter
// not as computed because it sets the initial state which will be changed later
expandingSubject: !this.$store.getters.mergedConfig.collapseMessageWithSubject
}
},