Commit ff37dbbd authored by Haelwenn's avatar Haelwenn
Browse files

Merge branch 'develop' into 'simplePolicy_reasons_for_instance_specific_policies'

# Conflicts:
#   src/i18n/nl.json
parents ab620e2c 425919a0
Pipeline #36865 passed with stages
in 8 minutes and 42 seconds
......@@ -8,7 +8,12 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
- Added a quick settings to timeline header for easier access
- Added option to mark posts as sensitive by default
- Added quick filters for notifications
- Implemented user option to change sidebar position to the right side
- Implemented user option to hide floating shout panel
- Implemented "edit profile" button if viewing own profile which opens profile settings
### Fixed
- Fixed follow request count showing in the wrong location in mobile view
## [2.3.0] - 2021-03-01
### Fixed
......
......@@ -3,6 +3,7 @@ Contributors of this project.
- Constance Variable (lambadalambda@social.heldscal.la): Code
- Coco Snuss (cocosnuss@social.heldscal.la): Code
- wakarimasen (wakarimasen@shitposter.club): NSFW hiding image
- eris (eris@disqordia.space): Code
- dtluna (dtluna@social.heldscal.la): Code
- sonyam (sonyam@social.heldscal.la): Background images
- hakui (hakui@freezepeach.xyz): CSS and styling
......
......@@ -21,6 +21,7 @@ var compiler = webpack(webpackConfig)
var devMiddleware = require('webpack-dev-middleware')(compiler, {
publicPath: webpackConfig.output.publicPath,
writeToDisk: true,
stats: {
colors: true,
chunks: false
......
......@@ -3,6 +3,7 @@ var config = require('../config')
var utils = require('./utils')
var projectRoot = path.resolve(__dirname, '../')
var ServiceWorkerWebpackPlugin = require('serviceworker-webpack-plugin')
var CopyPlugin = require('copy-webpack-plugin');
var env = process.env.NODE_ENV
// check env & config/index.js to decide weither to enable CSS Sourcemaps for the
......@@ -93,6 +94,19 @@ module.exports = {
new ServiceWorkerWebpackPlugin({
entry: path.join(__dirname, '..', 'src/sw.js'),
filename: 'sw-pleroma.js'
}),
// This copies Ruffle's WASM to a directory so that JS side can access it
new CopyPlugin({
patterns: [
{
from: "node_modules/ruffle-mirror/*",
to: "static/ruffle",
flatten: true
},
],
options: {
concurrency: 100,
},
})
]
}
......@@ -4,7 +4,7 @@ import Notifications from './components/notifications/notifications.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'
import ShoutPanel from './components/shout_panel/shout_panel.vue'
import SettingsModal from './components/settings_modal/settings_modal.vue'
import MediaModal from './components/media_modal/media_modal.vue'
import SideDrawer from './components/side_drawer/side_drawer.vue'
......@@ -26,7 +26,7 @@ export default {
InstanceSpecificPanel,
FeaturesPanel,
WhoToFollowPanel,
ChatPanel,
ShoutPanel,
MediaModal,
SideDrawer,
MobilePostStatusButton,
......@@ -65,7 +65,7 @@ export default {
}
}
},
chat () { return this.$store.state.chat.channel.state === 'joined' },
shout () { return this.$store.state.shout.channel.state === 'joined' },
suggestionsEnabled () { return this.$store.state.instance.suggestionsEnabled },
showInstanceSpecificPanel () {
return this.$store.state.instance.showInstanceSpecificPanel &&
......@@ -73,11 +73,14 @@ export default {
this.$store.state.instance.instanceSpecificPanelContent
},
showFeaturesPanel () { return this.$store.state.instance.showFeaturesPanel },
hideShoutbox () {
return this.$store.getters.mergedConfig.hideShoutbox
},
isMobileLayout () { return this.$store.state.interface.mobileLayout },
privateMode () { return this.$store.state.instance.private },
sidebarAlign () {
return {
'order': this.$store.state.instance.sidebarRight ? 99 : 0
'order': this.$store.getters.mergedConfig.sidebarRight ? 99 : 0
}
},
...mapGetters(['mergedConfig'])
......
......@@ -187,7 +187,7 @@ a {
}
}
input, textarea, .select, .input {
input, textarea, .input {
&.unstyled {
border-radius: 0;
......@@ -217,47 +217,11 @@ input, textarea, .select, .input {
hyphens: none;
padding: 8px .5em;
&.select {
padding: 0;
}
&:disabled, &[disabled=disabled] {
&:disabled, &[disabled=disabled], &.disabled {
cursor: not-allowed;
opacity: 0.5;
}
.select-down-icon {
position: absolute;
top: 0;
bottom: 0;
right: 5px;
height: 100%;
color: $fallback--text;
color: var(--inputText, $fallback--text);
line-height: 28px;
z-index: 0;
pointer-events: none;
}
select {
-webkit-appearance: none;
-moz-appearance: none;
appearance: none;
background: transparent;
border: none;
color: $fallback--text;
color: var(--inputText, --text, $fallback--text);
margin: 0;
padding: 0 2em 0 .2em;
font-family: sans-serif;
font-family: var(--inputFont, sans-serif);
font-size: 14px;
width: 100%;
z-index: 1;
height: 28px;
line-height: 16px;
}
&[type=range] {
background: none;
border: none;
......@@ -547,9 +511,21 @@ main-router {
border-radius: var(--panelRadius, $fallback--panelRadius);
}
.panel-footer {
/* TODO Should remove timeline-footer from here when we refactor panels into
* separate component and utilize slots
*/
.panel-footer, .timeline-footer {
display: flex;
border-radius: 0 0 $fallback--panelRadius $fallback--panelRadius;
border-radius: 0 0 var(--panelRadius, $fallback--panelRadius) var(--panelRadius, $fallback--panelRadius);
flex: none;
padding: 0.6em 0.6em;
text-align: left;
line-height: 28px;
align-items: baseline;
border-width: 1px 0 0 0;
border-style: solid;
border-color: var(--border, $fallback--border);
.faint {
color: $fallback--faint;
......@@ -818,13 +794,6 @@ nav {
}
}
.select-multiple {
display: flex;
.option-list {
margin: 0;
padding-left: .5em;
}
}
.setting-list,
.option-list{
list-style-type: none;
......@@ -871,16 +840,10 @@ nav {
}
.new-status-notification {
position:relative;
margin-top: -1px;
position: relative;
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);
flex: 1;
}
.chat-layout {
......
......@@ -49,10 +49,10 @@
</div>
<media-modal />
</div>
<chat-panel
v-if="currentUser && chat"
<shout-panel
v-if="currentUser && shout && !hideShoutbox"
:floating="true"
class="floating-chat mobile-hidden"
class="floating-shout mobile-hidden"
/>
<MobilePostStatusButton />
<UserReportingModal />
......
......@@ -240,7 +240,7 @@ const getNodeInfo = async ({ store }) => {
store.dispatch('setInstanceOption', { name: 'registrationOpen', value: data.openRegistrations })
store.dispatch('setInstanceOption', { name: 'mediaProxyAvailable', value: features.includes('media_proxy') })
store.dispatch('setInstanceOption', { name: 'safeDM', value: features.includes('safe_dm_mentions') })
store.dispatch('setInstanceOption', { name: 'chatAvailable', value: features.includes('chat') })
store.dispatch('setInstanceOption', { name: 'shoutAvailable', value: features.includes('chat') })
store.dispatch('setInstanceOption', { name: 'pleromaChatMessagesAvailable', value: features.includes('pleroma_chat_messages') })
store.dispatch('setInstanceOption', { name: 'gopherAvailable', value: features.includes('gopher') })
store.dispatch('setInstanceOption', { name: 'pollsAvailable', value: features.includes('polls') })
......
......@@ -16,7 +16,7 @@ import FollowRequests from 'components/follow_requests/follow_requests.vue'
import OAuthCallback from 'components/oauth_callback/oauth_callback.vue'
import Notifications from 'components/notifications/notifications.vue'
import AuthForm from 'components/auth_form/auth_form.js'
import ChatPanel from 'components/chat_panel/chat_panel.vue'
import ShoutPanel from 'components/shout_panel/shout_panel.vue'
import WhoToFollow from 'components/who_to_follow/who_to_follow.vue'
import About from 'components/about/about.vue'
import RemoteUserResolver from 'components/remote_user_resolver/remote_user_resolver.vue'
......@@ -64,7 +64,7 @@ export default (store) => {
{ name: 'friend-requests', path: '/friend-requests', component: FollowRequests, beforeEnter: validateAuthenticatedRoute },
{ name: 'notifications', path: '/:username/notifications', component: Notifications, beforeEnter: validateAuthenticatedRoute },
{ name: 'login', path: '/login', component: AuthForm },
{ name: 'chat-panel', path: '/chat-panel', component: ChatPanel, props: () => ({ floating: false }) },
{ name: 'shout-panel', path: '/shout-panel', component: ShoutPanel, props: () => ({ floating: false }) },
{ name: 'oauth-callback', path: '/oauth-callback', component: OAuthCallback, props: (route) => ({ code: route.query.code }) },
{ name: 'search', path: '/search', component: Search, props: (route) => ({ query: route.query.query }) },
{ name: 'who-to-follow', path: '/who-to-follow', component: WhoToFollow, beforeEnter: validateAuthenticatedRoute },
......
......@@ -6,10 +6,7 @@
:bound-to="{ x: 'container' }"
remove-padding
>
<div
slot="content"
class="account-tools-popover"
>
<template v-slot:content>
<div class="dropdown-menu">
<template v-if="relationship.following">
<button
......@@ -59,16 +56,15 @@
{{ $t('user_card.message') }}
</button>
</div>
</div>
<div
slot="trigger"
class="ellipsis-button"
>
<FAIcon
class="icon"
icon="ellipsis-v"
/>
</div>
</template>
<template v-slot:trigger>
<button class="button-unstyled ellipsis-button">
<FAIcon
class="icon"
icon="ellipsis-v"
/>
</button>
</template>
</Popover>
</div>
</template>
......@@ -83,7 +79,6 @@
}
.ellipsis-button {
cursor: pointer;
width: 2.5em;
margin: -0.5em 0;
padding: 0.5em 0;
......
import StillImage from '../still-image/still-image.vue'
import Flash from '../flash/flash.vue'
import VideoAttachment from '../video_attachment/video_attachment.vue'
import nsfwImage from '../../assets/nsfw.png'
import fileTypeService from '../../services/file_type/file_type.service.js'
......@@ -43,6 +44,7 @@ const Attachment = {
}
},
components: {
Flash,
StillImage,
VideoAttachment
},
......
......@@ -117,6 +117,11 @@
<!-- eslint-enable vue/no-v-html -->
</div>
</div>
<Flash
v-if="type === 'flash'"
:src="attachment.large_thumb_url || attachment.url"
/>
</div>
</template>
......@@ -172,6 +177,7 @@
}
.non-gallery.attachment {
&.flash,
&.video {
flex: 1 0 40%;
}
......
......@@ -23,10 +23,7 @@
class="timeline"
>
<List :items="sortedChatList">
<template
slot="item"
slot-scope="{item}"
>
<template v-slot:item="{item}">
<ChatListItem
:key="item.id"
:compact="false"
......
......@@ -50,7 +50,7 @@
@show="menuOpened = true"
@close="menuOpened = false"
>
<div slot="content">
<template v-slot:content>
<div class="dropdown-menu">
<button
class="button-default dropdown-item dropdown-item-icon"
......@@ -59,26 +59,28 @@
<FAIcon icon="times" /> {{ $t("chats.delete") }}
</button>
</div>
</div>
<button
slot="trigger"
class="button-default menu-icon"
:title="$t('chats.more')"
>
<FAIcon icon="ellipsis-h" />
</button>
</template>
<template v-slot:trigger>
<button
class="button-default menu-icon"
:title="$t('chats.more')"
>
<FAIcon icon="ellipsis-h" />
</button>
</template>
</Popover>
</div>
<StatusContent
:status="messageForStatusContent"
:full-content="true"
>
<span
slot="footer"
class="created-at"
>
{{ createdAt }}
</span>
<template v-slot:footer>
<span
class="created-at"
>
{{ createdAt }}
</span>
</template>
</StatusContent>
</div>
</div>
......
......@@ -9,7 +9,7 @@
class="btn button-default"
>
{{ $t('domain_mute_card.unmute') }}
<template slot="progress">
<template v-slot:progress>
{{ $t('domain_mute_card.unmute_progress') }}
</template>
</ProgressButton>
......@@ -19,7 +19,7 @@
class="btn button-default"
>
{{ $t('domain_mute_card.mute') }}
<template slot="progress">
<template v-slot:progress>
{{ $t('domain_mute_card.mute_progress') }}
</template>
</ProgressButton>
......
......@@ -57,6 +57,7 @@ const EmojiInput = {
required: true,
type: Function
},
// TODO VUE3: change to modelValue, change 'input' event to 'input'
value: {
/**
* Used for v-model
......@@ -143,32 +144,31 @@ const EmojiInput = {
}
},
mounted () {
const slots = this.$slots.default
if (!slots || slots.length === 0) return
const input = slots.find(slot => ['input', 'textarea'].includes(slot.tag))
const { root } = this.$refs
const input = root.querySelector('.emoji-input > input') || root.querySelector('.emoji-input > textarea')
if (!input) return
this.input = input
this.resize()
input.elm.addEventListener('blur', this.onBlur)
input.elm.addEventListener('focus', this.onFocus)
input.elm.addEventListener('paste', this.onPaste)
input.elm.addEventListener('keyup', this.onKeyUp)
input.elm.addEventListener('keydown', this.onKeyDown)
input.elm.addEventListener('click', this.onClickInput)
input.elm.addEventListener('transitionend', this.onTransition)
input.elm.addEventListener('input', this.onInput)
input.addEventListener('blur', this.onBlur)
input.addEventListener('focus', this.onFocus)
input.addEventListener('paste', this.onPaste)
input.addEventListener('keyup', this.onKeyUp)
input.addEventListener('keydown', this.onKeyDown)
input.addEventListener('click', this.onClickInput)
input.addEventListener('transitionend', this.onTransition)
input.addEventListener('input', this.onInput)
},
unmounted () {
const { input } = this
if (input) {
input.elm.removeEventListener('blur', this.onBlur)
input.elm.removeEventListener('focus', this.onFocus)
input.elm.removeEventListener('paste', this.onPaste)
input.elm.removeEventListener('keyup', this.onKeyUp)
input.elm.removeEventListener('keydown', this.onKeyDown)
input.elm.removeEventListener('click', this.onClickInput)
input.elm.removeEventListener('transitionend', this.onTransition)
input.elm.removeEventListener('input', this.onInput)
input.removeEventListener('blur', this.onBlur)
input.removeEventListener('focus', this.onFocus)
input.removeEventListener('paste', this.onPaste)
input.removeEventListener('keyup', this.onKeyUp)
input.removeEventListener('keydown', this.onKeyDown)
input.removeEventListener('click', this.onClickInput)
input.removeEventListener('transitionend', this.onTransition)
input.removeEventListener('input', this.onInput)
}
},
watch: {
......@@ -216,7 +216,7 @@ const EmojiInput = {
}, 0)
},
togglePicker () {
this.input.elm.focus()
this.input.focus()
this.showPicker = !this.showPicker
if (this.showPicker) {
this.scrollIntoView()
......@@ -262,13 +262,13 @@ const EmojiInput = {
this.$emit('input', newValue)
const position = this.caret + (insertion + spaceAfter + spaceBefore).length
if (!keepOpen) {
this.input.elm.focus()
this.input.focus()
}
this.$nextTick(function () {
// Re-focus inputbox after clicking suggestion
// Set selection right after the replacement instead of the very end
this.input.elm.setSelectionRange(position, position)
this.input.setSelectionRange(position, position)
this.caret = position
})
},
......@@ -285,9 +285,9 @@ const EmojiInput = {
this.$nextTick(function () {
// Re-focus inputbox after clicking suggestion
this.input.elm.focus()
this.input.focus()
// Set selection right after the replacement instead of the very end
this.input.elm.setSelectionRange(position, position)
this.input.setSelectionRange(position, position)
this.caret = position
})
e.preventDefault()
......@@ -349,7 +349,7 @@ const EmojiInput = {
}
this.$nextTick(() => {
const { offsetHeight } = this.input.elm
const { offsetHeight } = this.input
const { picker } = this.$refs
const pickerBottom = picker.$el.getBoundingClientRect().bottom
if (pickerBottom > window.innerHeight) {
......@@ -414,8 +414,8 @@ const EmojiInput = {
// Scroll the input element to the position of the cursor
this.$nextTick(() => {
this.input.elm.blur()
this.input.elm.focus()
this.input.blur()
this.input.focus()
})
}
// Disable suggestions hotkeys if suggestions are hidden
......@@ -444,7 +444,7 @@ const EmojiInput = {
// de-focuses the element (i.e. default browser behavior)
if (key === 'Escape') {
if (!this.temporarilyHideSuggestions) {
this.input.elm.focus()
this.input.focus()
}
}
......@@ -480,7 +480,7 @@ const EmojiInput = {
if (!panel) return
const picker = this.$refs.picker.$el
const panelBody = this.$refs['panel-body']
const { offsetHeight, offsetTop } = this.input.elm
const { offsetHeight, offsetTop } = this.input
const offsetBottom = offsetTop + offsetHeight
this.setPlacement(panelBody, panel, offsetBottom)
......@@ -494,7 +494,7 @@ const EmojiInput = {
if (this.placement === 'top' || (this.placement === 'auto' && this.overflowsBottom(container))) {
target.style.top = 'auto'
target.style.bottom = this.input.elm.offsetHeight + 'px'
target.style.bottom = this.input.offsetHeight + 'px'
}
},
overflowsBottom (el) {
......
<template>
<div
ref="root"
v-click-outside="onClickOutside"
class="emoji-input"
:class="{ 'with-picker': !hideEmojiButton }"
......
<template>
<div class="import-export-container">
<slot name="before" />
<button
class="btn button-default"
@click="exportData"
>
{{ exportLabel }}