Merge branch 'develop' into 'iss-149/profile-fields-setting'
# Conflicts: # src/components/settings_modal/tabs/profile_tab.vue
This commit is contained in:
commit
ea0a12f604
10
CHANGELOG.md
10
CHANGELOG.md
@ -4,28 +4,38 @@ All notable changes to this project will be documented in this file.
|
||||
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
|
||||
## [Unreleased]
|
||||
### Changed
|
||||
- Greentext now has separate color slot for it
|
||||
- Removed the use of with_move parameters when fetching notifications
|
||||
- Push notifications now are the same as normal notfication, and are localized.
|
||||
|
||||
### Fixed
|
||||
- Weird bug related to post being sent seemingly after pasting with keyboard (hopefully)
|
||||
- Multiple issues with muted statuses/notifications
|
||||
|
||||
## [Unreleased patch]
|
||||
### Add
|
||||
- Added private notifications option for push notifications
|
||||
- 'Copy link' button for statuses (in the ellipsis menu)
|
||||
- Autocomplete domains from list of known instances
|
||||
- 'Bot' settings option and badge
|
||||
|
||||
### Changed
|
||||
- Registration page no longer requires email if the server is configured not to require it
|
||||
- Change heart to thumbs up in reaction picker
|
||||
- Close the media modal on navigation events
|
||||
- Add colons to the emoji alt text, to make them copyable
|
||||
- Add better visual indication for drag-and-drop for files
|
||||
|
||||
### Fixed
|
||||
- Custom Emoji will display in poll options now.
|
||||
- Status ellipsis menu closes properly when selecting certain options
|
||||
- Cropped images look correct in Chrome
|
||||
- Newlines in the muted words settings work again
|
||||
- Clicking on non-latin hashtags won't open a new window
|
||||
- Uploading and drag-dropping multiple files works correctly now.
|
||||
- Subject field now appears disabled when posting
|
||||
- Fix status ellipsis menu being cut off in notifications column
|
||||
- Fixed autocomplete sometimes not returning the right user when there's already some results
|
||||
|
||||
## [2.0.3] - 2020-05-02
|
||||
### Fixed
|
||||
|
@ -22,12 +22,9 @@
|
||||
"cropperjs": "^1.4.3",
|
||||
"diff": "^3.0.1",
|
||||
"escape-html": "^1.0.3",
|
||||
"karma-mocha-reporter": "^2.2.1",
|
||||
"localforage": "^1.5.0",
|
||||
"object-path": "^0.11.3",
|
||||
"phoenix": "^1.3.0",
|
||||
"portal-vue": "^2.1.4",
|
||||
"sanitize-html": "^1.13.0",
|
||||
"v-click-outside": "^2.1.1",
|
||||
"vue": "^2.6.11",
|
||||
"vue-chat-scroll": "^1.2.1",
|
||||
@ -35,10 +32,10 @@
|
||||
"vue-router": "^3.0.1",
|
||||
"vue-template-compiler": "^2.6.11",
|
||||
"vuelidate": "^0.7.4",
|
||||
"vuex": "^3.0.1",
|
||||
"whatwg-fetch": "^2.0.3"
|
||||
"vuex": "^3.0.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"karma-mocha-reporter": "^2.2.1",
|
||||
"@babel/core": "^7.7.5",
|
||||
"@babel/plugin-transform-runtime": "^7.7.6",
|
||||
"@babel/preset-env": "^7.7.6",
|
||||
|
@ -3,6 +3,7 @@
|
||||
<Popover
|
||||
trigger="click"
|
||||
placement="bottom"
|
||||
:bound-to="{ x: 'container' }"
|
||||
>
|
||||
<div
|
||||
slot="content"
|
||||
|
@ -5,9 +5,20 @@ const DomainMuteCard = {
|
||||
components: {
|
||||
ProgressButton
|
||||
},
|
||||
computed: {
|
||||
user () {
|
||||
return this.$store.state.users.currentUser
|
||||
},
|
||||
muted () {
|
||||
return this.user.domainMutes.includes(this.domain)
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
unmuteDomain () {
|
||||
return this.$store.dispatch('unmuteDomain', this.domain)
|
||||
},
|
||||
muteDomain () {
|
||||
return this.$store.dispatch('muteDomain', this.domain)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -4,6 +4,7 @@
|
||||
{{ domain }}
|
||||
</div>
|
||||
<ProgressButton
|
||||
v-if="muted"
|
||||
:click="unmuteDomain"
|
||||
class="btn btn-default"
|
||||
>
|
||||
@ -12,6 +13,16 @@
|
||||
{{ $t('domain_mute_card.unmute_progress') }}
|
||||
</template>
|
||||
</ProgressButton>
|
||||
<ProgressButton
|
||||
v-else
|
||||
:click="muteDomain"
|
||||
class="btn btn-default"
|
||||
>
|
||||
{{ $t('domain_mute_card.mute') }}
|
||||
<template slot="progress">
|
||||
{{ $t('domain_mute_card.mute_progress') }}
|
||||
</template>
|
||||
</ProgressButton>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@ -34,5 +45,9 @@
|
||||
button {
|
||||
width: 10em;
|
||||
}
|
||||
|
||||
.autosuggest-results & {
|
||||
padding-left: 1em;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
@ -13,7 +13,7 @@ import { debounce } from 'lodash'
|
||||
|
||||
const debounceUserSearch = debounce((data, input) => {
|
||||
data.updateUsersList(input)
|
||||
}, 500, { leading: true, trailing: false })
|
||||
}, 500)
|
||||
|
||||
export default data => input => {
|
||||
const firstChar = input[0]
|
||||
@ -97,8 +97,8 @@ export const suggestUsers = data => input => {
|
||||
replacement: '@' + screen_name + ' '
|
||||
}))
|
||||
|
||||
// BE search users if there are no matches
|
||||
if (newUsers.length === 0 && data.updateUsersList) {
|
||||
// BE search users to get more comprehensive results
|
||||
if (data.updateUsersList) {
|
||||
debounceUserSearch(data, noPrefix)
|
||||
}
|
||||
return newUsers
|
||||
|
@ -3,6 +3,7 @@
|
||||
trigger="click"
|
||||
placement="top"
|
||||
class="extra-button-popover"
|
||||
:bound-to="{ x: 'container' }"
|
||||
>
|
||||
<div
|
||||
slot="content"
|
||||
|
@ -78,6 +78,7 @@
|
||||
video,
|
||||
canvas {
|
||||
object-fit: contain;
|
||||
height: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -45,20 +45,6 @@ const mediaUpload = {
|
||||
this.$emit('all-uploaded')
|
||||
}
|
||||
},
|
||||
fileDrop (e) {
|
||||
if (e.dataTransfer.files.length > 0) {
|
||||
e.preventDefault() // allow dropping text like before
|
||||
this.multiUpload(e.dataTransfer.files)
|
||||
}
|
||||
},
|
||||
fileDrag (e) {
|
||||
let types = e.dataTransfer.types
|
||||
if (types.contains('Files')) {
|
||||
e.dataTransfer.dropEffect = 'copy'
|
||||
} else {
|
||||
e.dataTransfer.dropEffect = 'none'
|
||||
}
|
||||
},
|
||||
clearFile () {
|
||||
this.uploadReady = false
|
||||
this.$nextTick(() => {
|
||||
|
@ -1,10 +1,5 @@
|
||||
<template>
|
||||
<div
|
||||
class="media-upload"
|
||||
@drop.prevent
|
||||
@dragover.prevent="fileDrag"
|
||||
@drop="fileDrop"
|
||||
>
|
||||
<div class="media-upload">
|
||||
<label
|
||||
class="label"
|
||||
:title="$t('tool_tip.media_upload')"
|
||||
|
@ -54,25 +54,20 @@
|
||||
flex-wrap: nowrap;
|
||||
padding: 0.6em;
|
||||
min-width: 0;
|
||||
|
||||
.avatar-container {
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
}
|
||||
.status-el {
|
||||
.status {
|
||||
padding: 0.25em 0;
|
||||
color: $fallback--faint;
|
||||
color: var(--faint, $fallback--faint);
|
||||
a {
|
||||
color: var(--faintLink);
|
||||
}
|
||||
.status-content a {
|
||||
color: var(--postFaintLink);
|
||||
}
|
||||
|
||||
.status-body {
|
||||
color: $fallback--faint;
|
||||
color: var(--faint, $fallback--faint);
|
||||
a {
|
||||
color: var(--faintLink);
|
||||
}
|
||||
padding: 0;
|
||||
.media-body {
|
||||
margin: 0;
|
||||
.status-content a {
|
||||
color: var(--postFaintLink);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -17,7 +17,7 @@
|
||||
<span class="result-percentage">
|
||||
{{ percentageForOption(option.votes_count) }}%
|
||||
</span>
|
||||
<span>{{ option.title }}</span>
|
||||
<span v-html="option.title_html"></span>
|
||||
</div>
|
||||
<div
|
||||
class="result-fill"
|
||||
|
@ -1,4 +1,3 @@
|
||||
|
||||
const Popover = {
|
||||
name: 'Popover',
|
||||
props: {
|
||||
@ -10,6 +9,9 @@ const Popover = {
|
||||
// 'container' for using offsetParent as boundaries for either axis
|
||||
// or 'viewport'
|
||||
boundTo: Object,
|
||||
// Takes a selector to use as a replacement for the parent container
|
||||
// for getting boundaries for x an y axis
|
||||
boundToSelector: String,
|
||||
// Takes a top/bottom/left/right object, how much space to leave
|
||||
// between boundary and popover element
|
||||
margin: Object,
|
||||
@ -27,6 +29,10 @@ const Popover = {
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
containerBoundingClientRect () {
|
||||
const container = this.boundToSelector ? this.$el.closest(this.boundToSelector) : this.$el.offsetParent
|
||||
return container.getBoundingClientRect()
|
||||
},
|
||||
updateStyles () {
|
||||
if (this.hidden) {
|
||||
this.styles = {
|
||||
@ -45,7 +51,8 @@ const Popover = {
|
||||
// Minor optimization, don't call a slow reflow call if we don't have to
|
||||
const parentBounds = this.boundTo &&
|
||||
(this.boundTo.x === 'container' || this.boundTo.y === 'container') &&
|
||||
this.$el.offsetParent.getBoundingClientRect()
|
||||
this.containerBoundingClientRect()
|
||||
|
||||
const margin = this.margin || {}
|
||||
|
||||
// What are the screen bounds for the popover? Viewport vs container
|
||||
|
@ -82,7 +82,9 @@ const PostStatusForm = {
|
||||
contentType
|
||||
},
|
||||
caret: 0,
|
||||
pollFormVisible: false
|
||||
pollFormVisible: false,
|
||||
showDropIcon: 'hide',
|
||||
dropStopTimeout: null
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
@ -248,13 +250,27 @@ const PostStatusForm = {
|
||||
}
|
||||
},
|
||||
fileDrop (e) {
|
||||
if (e.dataTransfer.files.length > 0) {
|
||||
if (e.dataTransfer && e.dataTransfer.types.includes('Files')) {
|
||||
e.preventDefault() // allow dropping text like before
|
||||
this.dropFiles = e.dataTransfer.files
|
||||
clearTimeout(this.dropStopTimeout)
|
||||
this.showDropIcon = 'hide'
|
||||
}
|
||||
},
|
||||
fileDragStop (e) {
|
||||
// The false-setting is done with delay because just using leave-events
|
||||
// directly caused unwanted flickering, this is not perfect either but
|
||||
// much less noticable.
|
||||
clearTimeout(this.dropStopTimeout)
|
||||
this.showDropIcon = 'fade'
|
||||
this.dropStopTimeout = setTimeout(() => (this.showDropIcon = 'hide'), 500)
|
||||
},
|
||||
fileDrag (e) {
|
||||
e.dataTransfer.dropEffect = 'copy'
|
||||
if (e.dataTransfer && e.dataTransfer.types.includes('Files')) {
|
||||
clearTimeout(this.dropStopTimeout)
|
||||
this.showDropIcon = 'show'
|
||||
}
|
||||
},
|
||||
onEmojiInputInput (e) {
|
||||
this.$nextTick(() => {
|
||||
|
@ -6,7 +6,15 @@
|
||||
<form
|
||||
autocomplete="off"
|
||||
@submit.prevent="postStatus(newStatus)"
|
||||
@dragover.prevent="fileDrag"
|
||||
>
|
||||
<div
|
||||
v-show="showDropIcon !== 'hide'"
|
||||
:style="{ animation: showDropIcon === 'show' ? 'fade-in 0.25s' : 'fade-out 0.5s' }"
|
||||
class="drop-indicator icon-upload"
|
||||
@dragleave="fileDragStop"
|
||||
@drop.stop="fileDrop"
|
||||
/>
|
||||
<div class="form-group">
|
||||
<i18n
|
||||
v-if="!$store.state.users.currentUser.locked && newStatus.visibility == 'private'"
|
||||
@ -73,6 +81,7 @@
|
||||
v-model="newStatus.spoilerText"
|
||||
type="text"
|
||||
:placeholder="$t('post_status.content_warning')"
|
||||
:disabled="posting"
|
||||
class="form-post-subject"
|
||||
>
|
||||
</EmojiInput>
|
||||
@ -96,9 +105,7 @@
|
||||
:disabled="posting"
|
||||
class="form-post-body"
|
||||
@keydown.meta.enter="postStatus(newStatus)"
|
||||
@keyup.ctrl.enter="postStatus(newStatus)"
|
||||
@drop="fileDrop"
|
||||
@dragover.prevent="fileDrag"
|
||||
@keydown.ctrl.enter="postStatus(newStatus)"
|
||||
@input="resize"
|
||||
@compositionupdate="resize"
|
||||
@paste="paste"
|
||||
@ -447,7 +454,8 @@
|
||||
form {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
padding: 0.6em;
|
||||
margin: 0.6em;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.form-group {
|
||||
@ -505,5 +513,35 @@
|
||||
cursor: pointer;
|
||||
z-index: 4;
|
||||
}
|
||||
|
||||
@keyframes fade-in {
|
||||
from { opacity: 0; }
|
||||
to { opacity: 0.6; }
|
||||
}
|
||||
|
||||
@keyframes fade-out {
|
||||
from { opacity: 0.6; }
|
||||
to { opacity: 0; }
|
||||
}
|
||||
|
||||
.drop-indicator {
|
||||
position: absolute;
|
||||
z-index: 1;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
font-size: 5em;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
opacity: 0.6;
|
||||
color: $fallback--text;
|
||||
color: var(--text, $fallback--text);
|
||||
background-color: $fallback--bg;
|
||||
background-color: var(--bg, $fallback--bg);
|
||||
border-radius: $fallback--tooltipRadius;
|
||||
border-radius: var(--tooltipRadius, $fallback--tooltipRadius);
|
||||
border: 2px dashed $fallback--text;
|
||||
border: 2px dashed var(--text, $fallback--text);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
@ -13,6 +13,9 @@ const PostStatusModal = {
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
isLoggedIn () {
|
||||
return !!this.$store.state.users.currentUser
|
||||
},
|
||||
modalActivated () {
|
||||
return this.$store.state.postStatus.modalActivated
|
||||
},
|
||||
|
@ -1,5 +1,6 @@
|
||||
<template>
|
||||
<Modal
|
||||
v-if="isLoggedIn && !resettingForm"
|
||||
:is-open="modalActivated"
|
||||
class="post-form-modal-view"
|
||||
@backdropClicked="closeModal"
|
||||
|
@ -32,12 +32,12 @@ const DomainMuteList = withSubscription({
|
||||
const MutesAndBlocks = {
|
||||
data () {
|
||||
return {
|
||||
activeTab: 'profile',
|
||||
newDomainToMute: ''
|
||||
activeTab: 'profile'
|
||||
}
|
||||
},
|
||||
created () {
|
||||
this.$store.dispatch('fetchTokens')
|
||||
this.$store.dispatch('getKnownDomains')
|
||||
},
|
||||
components: {
|
||||
TabSwitcher,
|
||||
@ -51,6 +51,14 @@ const MutesAndBlocks = {
|
||||
Autosuggest,
|
||||
Checkbox
|
||||
},
|
||||
computed: {
|
||||
knownDomains () {
|
||||
return this.$store.state.instance.knownDomains
|
||||
},
|
||||
user () {
|
||||
return this.$store.state.users.currentUser
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
importFollows (file) {
|
||||
return this.$store.state.api.backendInteractor.importFollows({ file })
|
||||
@ -86,13 +94,13 @@ const MutesAndBlocks = {
|
||||
filterUnblockedUsers (userIds) {
|
||||
return reject(userIds, (userId) => {
|
||||
const relationship = this.$store.getters.relationship(this.userId)
|
||||
return relationship.blocking || userId === this.$store.state.users.currentUser.id
|
||||
return relationship.blocking || userId === this.user.id
|
||||
})
|
||||
},
|
||||
filterUnMutedUsers (userIds) {
|
||||
return reject(userIds, (userId) => {
|
||||
const relationship = this.$store.getters.relationship(this.userId)
|
||||
return relationship.muting || userId === this.$store.state.users.currentUser.id
|
||||
return relationship.muting || userId === this.user.id
|
||||
})
|
||||
},
|
||||
queryUserIds (query) {
|
||||
@ -111,12 +119,16 @@ const MutesAndBlocks = {
|
||||
unmuteUsers (ids) {
|
||||
return this.$store.dispatch('unmuteUsers', ids)
|
||||
},
|
||||
filterUnMutedDomains (urls) {
|
||||
return urls.filter(url => !this.user.domainMutes.includes(url))
|
||||
},
|
||||
queryKnownDomains (query) {
|
||||
return new Promise((resolve, reject) => {
|
||||
resolve(this.knownDomains.filter(url => url.toLowerCase().includes(query)))
|
||||
})
|
||||
},
|
||||
unmuteDomains (domains) {
|
||||
return this.$store.dispatch('unmuteDomains', domains)
|
||||
},
|
||||
muteDomain () {
|
||||
return this.$store.dispatch('muteDomain', this.newDomainToMute)
|
||||
.then(() => { this.newDomainToMute = '' })
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -119,21 +119,16 @@
|
||||
|
||||
<div :label="$t('settings.domain_mutes')">
|
||||
<div class="domain-mute-form">
|
||||
<input
|
||||
v-model="newDomainToMute"
|
||||
<Autosuggest
|
||||
:filter="filterUnMutedDomains"
|
||||
:query="queryKnownDomains"
|
||||
:placeholder="$t('settings.type_domains_to_mute')"
|
||||
type="text"
|
||||
@keyup.enter="muteDomain"
|
||||
>
|
||||
<ProgressButton
|
||||
class="btn btn-default domain-mute-button"
|
||||
:click="muteDomain"
|
||||
>
|
||||
{{ $t('domain_mute_card.mute') }}
|
||||
<template slot="progress">
|
||||
{{ $t('domain_mute_card.mute_progress') }}
|
||||
</template>
|
||||
</ProgressButton>
|
||||
<DomainMuteCard
|
||||
slot-scope="row"
|
||||
:domain="row.item"
|
||||
/>
|
||||
</Autosuggest>
|
||||
</div>
|
||||
<DomainMuteList
|
||||
:refresh="true"
|
||||
|
@ -25,6 +25,7 @@ const ProfileTab = {
|
||||
showRole: this.$store.state.users.currentUser.show_role,
|
||||
role: this.$store.state.users.currentUser.role,
|
||||
discoverable: this.$store.state.users.currentUser.discoverable,
|
||||
bot: this.$store.state.users.currentUser.bot,
|
||||
allowFollowingMove: this.$store.state.users.currentUser.allow_following_move,
|
||||
pickAvatarBtnVisible: true,
|
||||
bannerUploading: false,
|
||||
@ -94,6 +95,7 @@ const ProfileTab = {
|
||||
hide_follows: this.hideFollows,
|
||||
hide_followers: this.hideFollowers,
|
||||
discoverable: this.discoverable,
|
||||
bot: this.bot,
|
||||
allow_following_move: this.allowFollowingMove,
|
||||
hide_follows_count: this.hideFollowsCount,
|
||||
hide_followers_count: this.hideFollowersCount,
|
||||
|
@ -143,6 +143,11 @@
|
||||
{{ $t("settings.profile_fields.add_field") }}
|
||||
</a>
|
||||
</div>
|
||||
<p>
|
||||
<Checkbox v-model="bot">
|
||||
{{ $t('settings.bot') }}
|
||||
</Checkbox>
|
||||
</p>
|
||||
<button
|
||||
:disabled="newName && newName.length === 0"
|
||||
class="btn btn-default"
|
||||
|
@ -256,6 +256,13 @@
|
||||
:label="$t('settings.links')"
|
||||
/>
|
||||
<ContrastRatio :contrast="previewContrast.postLink" />
|
||||
<ColorInput
|
||||
v-model="postGreentextColorLocal"
|
||||
name="postGreentextColor"
|
||||
:fallback="previewTheme.colors.cGreen"
|
||||
:label="$t('settings.greentext')"
|
||||
/>
|
||||
<ContrastRatio :contrast="previewContrast.postGreentext" />
|
||||
<h4>{{ $t('settings.style.advanced_colors.alert') }}</h4>
|
||||
<ColorInput
|
||||
v-model="alertErrorColorLocal"
|
||||
|
@ -418,7 +418,7 @@ $status-margin: 0.75em;
|
||||
max-width: 85%;
|
||||
font-weight: bold;
|
||||
|
||||
img {
|
||||
img.emoji {
|
||||
width: 14px;
|
||||
height: 14px;
|
||||
vertical-align: middle;
|
||||
|
@ -164,23 +164,23 @@ $status-margin: 0.75em;
|
||||
word-break: break-all;
|
||||
}
|
||||
|
||||
img, video {
|
||||
max-width: 100%;
|
||||
max-height: 400px;
|
||||
vertical-align: middle;
|
||||
object-fit: contain;
|
||||
|
||||
&.emoji {
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
}
|
||||
}
|
||||
|
||||
.status-content {
|
||||
font-family: var(--postFont, sans-serif);
|
||||
line-height: 1.4em;
|
||||
white-space: pre-wrap;
|
||||
|
||||
img, video {
|
||||
max-width: 100%;
|
||||
max-height: 400px;
|
||||
vertical-align: middle;
|
||||
object-fit: contain;
|
||||
|
||||
&.emoji {
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
}
|
||||
}
|
||||
|
||||
blockquote {
|
||||
margin: 0.2em 0 0.2em 2em;
|
||||
font-style: italic;
|
||||
@ -226,7 +226,7 @@ $status-margin: 0.75em;
|
||||
|
||||
.greentext {
|
||||
color: $fallback--cGreen;
|
||||
color: var(--cGreen, $fallback--cGreen);
|
||||
color: var(--postGreentext, $fallback--cGreen);
|
||||
}
|
||||
|
||||
.timeline :not(.panel-disabled) > {
|
||||
|
@ -23,13 +23,6 @@
|
||||
|
||||
<style lang="scss">
|
||||
@import '../../_variables.scss';
|
||||
.contain-fit {
|
||||
.still-image {
|
||||
img {
|
||||
height: 100%;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.still-image {
|
||||
position: relative;
|
||||
@ -38,6 +31,7 @@
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
&:hover canvas {
|
||||
display: none;
|
||||
@ -45,8 +39,8 @@
|
||||
|
||||
img {
|
||||
width: 100%;
|
||||
min-height: 100%;
|
||||
object-fit: contain;
|
||||
align-self: center;
|
||||
}
|
||||
|
||||
&.animated {
|
||||
|
@ -70,10 +70,20 @@
|
||||
>
|
||||
@{{ user.screen_name }}
|
||||
</router-link>
|
||||
<span
|
||||
v-if="!hideBio && !!visibleRole"
|
||||
class="alert staff"
|
||||
>{{ visibleRole }}</span>
|
||||
<template v-if="!hideBio">
|
||||
<span
|
||||
v-if="!!visibleRole"
|
||||
class="alert user-role"
|
||||
>
|
||||
{{ visibleRole }}
|
||||
</span>
|
||||
<span
|
||||
v-if="user.bot"
|
||||
class="alert user-role"
|
||||
>
|
||||
bot
|
||||
</span>
|
||||
</template>
|
||||
<span v-if="user.locked"><i class="icon icon-lock" /></span>
|
||||
<span
|
||||
v-if="!mergedConfig.hideUserStats && !hideBio"
|
||||
@ -458,7 +468,7 @@
|
||||
color: var(--text, $fallback--text);
|
||||
}
|
||||
|
||||
.staff {
|
||||
.user-role {
|
||||
flex: none;
|
||||
text-transform: capitalize;
|
||||
color: $fallback--text;
|
||||
|
@ -124,6 +124,14 @@ const UserProfile = {
|
||||
onTabSwitch (tab) {
|
||||
this.tab = tab
|
||||
this.$router.replace({ query: { tab } })
|
||||
},
|
||||
linkClicked ({ target }) {
|
||||
if (target.tagName === 'SPAN') {
|
||||
target = target.parentNode
|
||||
}
|
||||
if (target.tagName === 'A') {
|
||||
window.open(target.href, '_blank')
|
||||
}
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
|
@ -11,6 +11,31 @@
|
||||
:allow-zooming-avatar="true"
|
||||
rounded="top"
|
||||
/>
|
||||
<div
|
||||
v-if="user.fields_html && user.fields_html.length > 0"
|
||||
class="user-profile-fields"
|
||||
>
|
||||
<dl
|
||||
v-for="(field, index) in user.fields_html"
|
||||
:key="index"
|
||||
class="user-profile-field"
|
||||
>
|
||||
<!-- eslint-disable vue/no-v-html -->
|
||||
<dt
|
||||
:title="user.fields_text[index].name"
|
||||
class="user-profile-field-name"
|
||||
@click.prevent="linkClicked"
|
||||
v-html="field.name"
|
||||
/>
|
||||
<dd
|
||||
:title="user.fields_text[index].value"
|
||||
class="user-profile-field-value"
|
||||
@click.prevent="linkClicked"
|
||||
v-html="field.value"
|
||||
/>
|
||||
<!-- eslint-enable vue/no-v-html -->
|
||||
</dl>
|
||||
</div>
|
||||
<tab-switcher
|
||||
:active-tab="tab"
|
||||
:render-only-focused="true"
|
||||
@ -108,11 +133,60 @@
|
||||
<script src="./user_profile.js"></script>
|
||||
|
||||
<style lang="scss">
|
||||
@import '../../_variables.scss';
|
||||
|
||||
.user-profile {
|
||||
flex: 2;
|
||||
flex-basis: 500px;
|
||||
|
||||
.user-profile-fields {
|
||||
margin: 0 0.5em;
|
||||
img {
|
||||
object-fit: contain;
|
||||
vertical-align: middle;
|
||||
max-width: 100%;
|
||||
max-height: 400px;
|
||||
|
||||
&.emoji {
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
}
|
||||
}
|
||||
|
||||
.user-profile-field {
|
||||
display: flex;
|
||||
margin: 0.25em auto;
|
||||
max-width: 32em;
|
||||
border: 1px solid var(--border, $fallback--border);
|
||||
border-radius: $fallback--inputRadius;
|
||||
border-radius: var(--inputRadius, $fallback--inputRadius);
|
||||
|
||||
.user-profile-field-name {
|
||||
flex: 0 1 30%;
|
||||
font-weight: 500;
|
||||
text-align: right;
|
||||
color: var(--lightText);
|
||||
min-width: 120px;
|
||||
border-right: 1px solid var(--border, $fallback--border);
|
||||
}
|
||||
|
||||
.user-profile-field-value {
|
||||
flex: 1 1 70%;
|
||||
color: var(--text);
|
||||
margin: 0 0 0 0.25em;
|
||||
}
|
||||
|
||||
.user-profile-field-name, .user-profile-field-value {
|
||||
line-height: 18px;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
padding: 0.5em 1.5em;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.userlist-placeholder {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
|
@ -266,6 +266,7 @@
|
||||
"block_import_error": "Error importing blocks",
|
||||
"blocks_imported": "Blocks imported! Processing them will take a while.",
|
||||
"blocks_tab": "Blocks",
|
||||
"bot": "This is a bot account",
|
||||
"btnRadius": "Buttons",
|
||||
"cBlue": "Blue (Reply, follow)",
|
||||
"cGreen": "Green (Retweet)",
|
||||
@ -407,7 +408,7 @@
|
||||
"theme_help_v2_1": "You can also override certain component's colors and opacity by toggling the checkbox, use \"Clear all\" button to clear all overrides.",
|
||||
"theme_help_v2_2": "Icons underneath some entries are background/text contrast indicators, hover over for detailed info. Please keep in mind that when using transparency contrast indicators show the worst possible case.",
|
||||
"tooltipRadius": "Tooltips/alerts",
|
||||
"type_domains_to_mute": "Type in domains to mute",
|
||||
"type_domains_to_mute": "Search domains to mute",
|
||||
"upload_a_photo": "Upload a photo",
|
||||
"user_settings": "User Settings",
|
||||
"values": {
|
||||
|
@ -12,7 +12,12 @@
|
||||
"disable": "Disabilita",
|
||||
"enable": "Abilita",
|
||||
"confirm": "Conferma",
|
||||
"verify": "Verifica"
|
||||
"verify": "Verifica",
|
||||
"peek": "Anteprima",
|
||||
"close": "Chiudi",
|
||||
"retry": "Riprova",
|
||||
"error_retry": "Per favore, riprova",
|
||||
"loading": "Carico…"
|
||||
},
|
||||
"nav": {
|
||||
"mentions": "Menzioni",
|
||||
@ -212,7 +217,63 @@
|
||||
},
|
||||
"common": {
|
||||
"opacity": "Opacità",
|
||||
"color": "Colore"
|
||||
"color": "Colore",
|
||||
"contrast": {
|
||||
"context": {
|
||||
"text": "per il testo",
|
||||
"18pt": "per il testo grande (oltre 17pt)"
|
||||
},
|
||||
"level": {
|
||||
"bad": "non soddisfa le linee guida di alcun livello",
|
||||
"aaa": "soddisfa le linee guida di livello AAA (ottimo)",
|
||||
"aa": "soddisfa le linee guida di livello AA (sufficiente)"
|
||||
},
|
||||
"hint": "Il rapporto di contrasto è {ratio}, e {level} {context}"
|
||||
}
|
||||
},
|
||||
"advanced_colors": {
|
||||
"badge": "Sfondo medaglie",
|
||||
"post": "Messaggi / Biografie",
|
||||
"alert_neutral": "Neutro",
|
||||
"alert_warning": "Attenzione",
|
||||
"alert_error": "Errore",
|
||||
"alert": "Sfondo degli avvertimenti",
|
||||
"_tab_label": "Avanzate",
|
||||
"tabs": "Etichette",
|
||||
"disabled": "Disabilitato",
|
||||
"selectedMenu": "Voce menù selezionata",
|
||||
"selectedPost": "Messaggio selezionato",
|
||||
"pressed": "Premuto",
|
||||
"highlight": "Elementi evidenziati",
|
||||
"icons": "Icone",
|
||||
"poll": "Grafico sondaggi",
|
||||
"underlay": "Sottostante",
|
||||
"faint_text": "Testo sbiadito",
|
||||
"inputs": "Campi d'immissione",
|
||||
"buttons": "Pulsanti",
|
||||
"borders": "Bordi",
|
||||
"top_bar": "Barra superiore",
|
||||
"panel_header": "Titolo pannello",
|
||||
"badge_notification": "Notifica",
|
||||
"popover": "Suggerimenti, menù, sbalzi"
|
||||
},
|
||||
"common_colors": {
|
||||
"rgbo": "Icone, accenti, medaglie",
|
||||
"foreground_hint": "Seleziona l'etichetta \"Avanzate\" per controlli più fini",
|
||||
"main": "Colori comuni",
|
||||
"_tab_label": "Comuni"
|
||||
},
|
||||
"shadows": {
|
||||
"inset": "Includi",
|
||||
"spread": "Spandi",
|
||||
"blur": "Sfoca",
|
||||
"shadow_id": "Ombra numero {value}",
|
||||
"override": "Sostituisci",
|
||||
"component": "Componente",
|
||||
"_tab_label": "Luci ed ombre"
|
||||
},
|
||||
"radii": {
|
||||
"_tab_label": "Raggio"
|
||||
}
|
||||
},
|
||||
"enable_web_push_notifications": "Abilita notifiche web push",
|
||||
@ -229,7 +290,7 @@
|
||||
"notifications": "Notifiche",
|
||||
"greentext": "Frecce da meme",
|
||||
"upload_a_photo": "Carica un'immagine",
|
||||
"type_domains_to_mute": "Inserisci domini da zittire",
|
||||
"type_domains_to_mute": "Cerca domini da zittire",
|
||||
"theme_help_v2_2": "Le icone dietro alcuni elementi sono indicatori del contrasto fra testo e sfondo, passaci sopra col puntatore per ulteriori informazioni. Se si usano delle trasparenze, questi indicatori mostrano il peggior caso possibile.",
|
||||
"theme_help_v2_1": "Puoi anche forzare colore ed opacità di alcuni elementi selezionando la casella. Usa il pulsante \"Azzera\" per azzerare tutte le forzature.",
|
||||
"useStreamingApiWarning": "(Sconsigliato, sperimentale, può saltare messaggi)",
|
||||
@ -273,7 +334,8 @@
|
||||
"accent": "Accento",
|
||||
"emoji_reactions_on_timeline": "Mostra emoji di reazione sulle sequenze",
|
||||
"pad_emoji": "Affianca spazi agli emoji inseriti tramite selettore",
|
||||
"notification_blocks": "Bloccando un utente non riceverai più le sue notifiche né lo seguirai più."
|
||||
"notification_blocks": "Bloccando un utente non riceverai più le sue notifiche né lo seguirai più.",
|
||||
"mutes_and_blocks": "Zittiti e bloccati"
|
||||
},
|
||||
"timeline": {
|
||||
"error_fetching": "Errore nell'aggiornamento",
|
||||
|
@ -130,6 +130,7 @@
|
||||
"background": "Фон",
|
||||
"bio": "Описание",
|
||||
"btnRadius": "Кнопки",
|
||||
"bot": "Это аккаунт бота",
|
||||
"cBlue": "Ответить, читать",
|
||||
"cGreen": "Повторить",
|
||||
"cOrange": "Нравится",
|
||||
@ -456,9 +457,9 @@
|
||||
},
|
||||
"domain_mute_card": {
|
||||
"mute": "Игнорировать",
|
||||
"mute_progress": "В процессе...",
|
||||
"mute_progress": "В процессе…",
|
||||
"unmute": "Прекратить игнорирование",
|
||||
"unmute_progress": "В процессе..."
|
||||
"unmute_progress": "В процессе…"
|
||||
},
|
||||
"exporter": {
|
||||
"export": "Экспорт",
|
||||
|
35
src/i18n/service_worker_messages.js
Normal file
35
src/i18n/service_worker_messages.js
Normal file
@ -0,0 +1,35 @@
|
||||
/* eslint-disable import/no-webpack-loader-syntax */
|
||||
// This module exports only the notification part of the i18n,
|
||||
// which is useful for the service worker
|
||||
|
||||
const messages = {
|
||||
ar: require('../lib/notification-i18n-loader.js!./ar.json'),
|
||||
ca: require('../lib/notification-i18n-loader.js!./ca.json'),
|
||||
cs: require('../lib/notification-i18n-loader.js!./cs.json'),
|
||||
de: require('../lib/notification-i18n-loader.js!./de.json'),
|
||||
eo: require('../lib/notification-i18n-loader.js!./eo.json'),
|
||||
es: require('../lib/notification-i18n-loader.js!./es.json'),
|
||||
et: require('../lib/notification-i18n-loader.js!./et.json'),
|
||||
eu: require('../lib/notification-i18n-loader.js!./eu.json'),
|
||||
fi: require('../lib/notification-i18n-loader.js!./fi.json'),
|
||||
fr: require('../lib/notification-i18n-loader.js!./fr.json'),
|
||||
ga: require('../lib/notification-i18n-loader.js!./ga.json'),
|
||||
he: require('../lib/notification-i18n-loader.js!./he.json'),
|
||||
hu: require('../lib/notification-i18n-loader.js!./hu.json'),
|
||||
it: require('../lib/notification-i18n-loader.js!./it.json'),
|
||||
ja: require('../lib/notification-i18n-loader.js!./ja_pedantic.json'),
|
||||
ja_easy: require('../lib/notification-i18n-loader.js!./ja_easy.json'),
|
||||
ko: require('../lib/notification-i18n-loader.js!./ko.json'),
|
||||
nb: require('../lib/notification-i18n-loader.js!./nb.json'),
|
||||
nl: require('../lib/notification-i18n-loader.js!./nl.json'),
|
||||
oc: require('../lib/notification-i18n-loader.js!./oc.json'),
|
||||
pl: require('../lib/notification-i18n-loader.js!./pl.json'),
|
||||
pt: require('../lib/notification-i18n-loader.js!./pt.json'),
|
||||
ro: require('../lib/notification-i18n-loader.js!./ro.json'),
|
||||
ru: require('../lib/notification-i18n-loader.js!./ru.json'),
|
||||
te: require('../lib/notification-i18n-loader.js!./te.json'),
|
||||
zh: require('../lib/notification-i18n-loader.js!./zh.json'),
|
||||
en: require('../lib/notification-i18n-loader.js!./en.json')
|
||||
}
|
||||
|
||||
export default messages
|
12
src/lib/notification-i18n-loader.js
Normal file
12
src/lib/notification-i18n-loader.js
Normal file
@ -0,0 +1,12 @@
|
||||
// This somewhat mysterious module will load a json string
|
||||
// and then extract only the 'notifications' part. This is
|
||||
// meant to be used to load the partial i18n we need for
|
||||
// the service worker.
|
||||
module.exports = function (source) {
|
||||
var object = JSON.parse(source)
|
||||
var smol = {
|
||||
notifications: object.notifications || {}
|
||||
}
|
||||
|
||||
return JSON.stringify(smol)
|
||||
}
|
@ -1,13 +1,12 @@
|
||||
import merge from 'lodash.merge'
|
||||
import objectPath from 'object-path'
|
||||
import localforage from 'localforage'
|
||||
import { each } from 'lodash'
|
||||
import { each, get, set } from 'lodash'
|
||||
|
||||
let loaded = false
|
||||
|
||||
const defaultReducer = (state, paths) => (
|
||||
paths.length === 0 ? state : paths.reduce((substate, path) => {
|
||||
objectPath.set(substate, path, objectPath.get(state, path))
|
||||
set(substate, path, get(state, path))
|
||||
return substate
|
||||
}, {})
|
||||
)
|
||||
|
@ -1,6 +1,7 @@
|
||||
import { set } from 'vue'
|
||||
import { getPreset, applyTheme } from '../services/style_setter/style_setter.js'
|
||||
import { CURRENT_VERSION } from '../services/theme_data/theme_data.service.js'
|
||||
import apiService from '../services/api/api.service.js'
|
||||
import { instanceDefaultProperties } from './config.js'
|
||||
|
||||
const defaultState = {
|
||||
@ -48,6 +49,7 @@ const defaultState = {
|
||||
postFormats: [],
|
||||
restrictedNicknames: [],
|
||||
safeDM: true,
|
||||
knownDomains: [],
|
||||
|
||||
// Feature-set, apparently, not everything here is reported...
|
||||
chatAvailable: false,
|
||||
@ -80,6 +82,9 @@ const instance = {
|
||||
if (typeof value !== 'undefined') {
|
||||
set(state, name, value)
|
||||
}
|
||||
},
|
||||
setKnownDomains (state, domains) {
|
||||
state.knownDomains = domains
|
||||
}
|
||||
},
|
||||
getters: {
|
||||
@ -182,6 +187,18 @@ const instance = {
|
||||
state.emojiFetched = true
|
||||
dispatch('getStaticEmoji')
|
||||
}
|
||||
},
|
||||
|
||||
async getKnownDomains ({ commit, rootState }) {
|
||||
try {
|
||||
const result = await apiService.fetchKnownDomains({
|
||||
credentials: rootState.users.currentUser.credentials
|
||||
})
|
||||
commit('setKnownDomains', result)
|
||||
} catch (e) {
|
||||
console.warn("Can't load known domains")
|
||||
console.warn(e)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -13,7 +13,7 @@ import {
|
||||
omitBy
|
||||
} from 'lodash'
|
||||
import { set } from 'vue'
|
||||
import { isStatusNotification } from '../services/notification_utils/notification_utils.js'
|
||||
import { isStatusNotification, prepareNotificationObject } from '../services/notification_utils/notification_utils.js'
|
||||
import apiService from '../services/api/api.service.js'
|
||||
import { muteWordHits } from '../services/status_parser/status_parser.js'
|
||||
|
||||
@ -344,42 +344,7 @@ const addNewNotifications = (state, { dispatch, notifications, older, visibleNot
|
||||
state.notifications.idStore[notification.id] = notification
|
||||
|
||||
if ('Notification' in window && window.Notification.permission === 'granted') {
|
||||
const notifObj = {}
|
||||
const status = notification.status
|
||||
const title = notification.from_profile.name
|
||||
notifObj.icon = notification.from_profile.profile_image_url
|
||||
let i18nString
|
||||
switch (notification.type) {
|
||||
case 'like':
|
||||
i18nString = 'favorited_you'
|
||||
break
|
||||
case 'repeat':
|
||||
i18nString = 'repeated_you'
|
||||
break
|
||||
case 'follow':
|
||||
i18nString = 'followed_you'
|
||||
break
|
||||
case 'move':
|
||||
i18nString = 'migrated_to'
|
||||
break
|
||||
case 'follow_request':
|
||||
i18nString = 'follow_request'
|
||||
break
|
||||
}
|
||||
|
||||
if (notification.type === 'pleroma:emoji_reaction') {
|
||||
notifObj.body = rootGetters.i18n.t('notifications.reacted_with', [notification.emoji])
|
||||
} else if (i18nString) {
|
||||
notifObj.body = rootGetters.i18n.t('notifications.' + i18nString)
|
||||
} else if (isStatusNotification(notification.type)) {
|
||||
notifObj.body = notification.status.text
|
||||
}
|
||||
|
||||
// Shows first attached non-nsfw image, if any. Should add configuration for this somehow...
|
||||
if (status && status.attachments && status.attachments.length > 0 && !status.nsfw &&
|
||||
status.attachments[0].mimetype.startsWith('image/')) {
|
||||
notifObj.image = status.attachments[0].url
|
||||
}
|
||||
const notifObj = prepareNotificationObject(notification, rootGetters.i18n)
|
||||
|
||||
const reasonsToMuteNotif = (
|
||||
notification.seen ||
|
||||
@ -393,7 +358,7 @@ const addNewNotifications = (state, { dispatch, notifications, older, visibleNot
|
||||
)
|
||||
)
|
||||
if (!reasonsToMuteNotif) {
|
||||
let desktopNotification = new window.Notification(title, notifObj)
|
||||
let desktopNotification = new window.Notification(notifObj.title, notifObj)
|
||||
// Chrome is known for not closing notifications automatically
|
||||
// according to MDN, anyway.
|
||||
setTimeout(desktopNotification.close.bind(desktopNotification), 5000)
|
||||
|
@ -435,10 +435,10 @@ const users = {
|
||||
store.commit('setUserForNotification', notification)
|
||||
})
|
||||
},
|
||||
searchUsers (store, { query }) {
|
||||
return store.rootState.api.backendInteractor.searchUsers({ query })
|
||||
searchUsers ({ rootState, commit }, { query }) {
|
||||
return rootState.api.backendInteractor.searchUsers({ query })
|
||||
.then((users) => {
|
||||
store.commit('addNewUsers', users)
|
||||
commit('addNewUsers', users)
|
||||
return users
|
||||
})
|
||||
},
|
||||
|
@ -1,6 +1,5 @@
|
||||
import { each, map, concat, last, get } from 'lodash'
|
||||
import { parseStatus, parseUser, parseNotification, parseAttachment } from '../entity_normalizer/entity_normalizer.service.js'
|
||||
import 'whatwg-fetch'
|
||||
import { RegistrationError, StatusCodeError } from '../errors/errors'
|
||||
|
||||
/* eslint-env browser */
|
||||
@ -75,6 +74,7 @@ const MASTODON_SEARCH_2 = `/api/v2/search`
|
||||
const MASTODON_USER_SEARCH_URL = '/api/v1/accounts/search'
|
||||
const MASTODON_DOMAIN_BLOCKS_URL = '/api/v1/domain_blocks'
|
||||
const MASTODON_STREAMING = '/api/v1/streaming'
|
||||
const MASTODON_KNOWN_DOMAIN_LIST_URL = '/api/v1/instance/peers'
|
||||
const PLEROMA_EMOJI_REACTIONS_URL = id => `/api/v1/pleroma/statuses/${id}/reactions`
|
||||
const PLEROMA_EMOJI_REACT_URL = (id, emoji) => `/api/v1/pleroma/statuses/${id}/reactions/${emoji}`
|
||||
const PLEROMA_EMOJI_UNREACT_URL = (id, emoji) => `/api/v1/pleroma/statuses/${id}/reactions/${emoji}`
|
||||
@ -995,6 +995,10 @@ const search2 = ({ credentials, q, resolve, limit, offset, following }) => {
|
||||
})
|
||||
}
|
||||
|
||||
const fetchKnownDomains = ({ credentials }) => {
|
||||
return promisedRequest({ url: MASTODON_KNOWN_DOMAIN_LIST_URL, credentials })
|
||||
}
|
||||
|
||||
const fetchDomainMutes = ({ credentials }) => {
|
||||
return promisedRequest({ url: MASTODON_DOMAIN_BLOCKS_URL, credentials })
|
||||
}
|
||||
@ -1193,6 +1197,7 @@ const apiService = {
|
||||
updateNotificationSettings,
|
||||
search2,
|
||||
searchUsers,
|
||||
fetchKnownDomains,
|
||||
fetchDomainMutes,
|
||||
muteDomain,
|
||||
unmuteDomain
|
||||
|
@ -56,6 +56,12 @@ export const parseUser = (data) => {
|
||||
value: addEmojis(field.value, data.emojis)
|
||||
}
|
||||
})
|
||||
output.fields_text = data.fields.map(field => {
|
||||
return {
|
||||
name: unescape(field.name.replace(/<[^>]*>/g, '')),
|
||||
value: unescape(field.value.replace(/<[^>]*>/g, ''))
|
||||
}
|
||||
})
|
||||
|
||||
// Utilize avatar_static for gif avatars?
|
||||
output.profile_image_url = data.avatar
|
||||
@ -258,6 +264,12 @@ export const parseStatus = (data) => {
|
||||
output.summary_html = addEmojis(escape(data.spoiler_text), data.emojis)
|
||||
output.external_url = data.url
|
||||
output.poll = data.poll
|
||||
if (output.poll) {
|
||||
output.poll.options = (output.poll.options || []).map(field => ({
|
||||
...field,
|
||||
title_html: addEmojis(field.title, data.emojis)
|
||||
}))
|
||||
}
|
||||
output.pinned = data.pinned
|
||||
output.muted = data.muted
|
||||
} else {
|
||||
|
@ -43,3 +43,47 @@ export const filteredNotificationsFromStore = (store, types) => {
|
||||
|
||||
export const unseenNotificationsFromStore = store =>
|
||||
filter(filteredNotificationsFromStore(store), ({ seen }) => !seen)
|
||||
|
||||
export const prepareNotificationObject = (notification, i18n) => {
|
||||
const notifObj = {
|
||||
tag: notification.id
|
||||
}
|
||||
const status = notification.status
|
||||
const title = notification.from_profile.name
|
||||
notifObj.title = title
|
||||
notifObj.icon = notification.from_profile.profile_image_url
|
||||
let i18nString
|
||||
switch (notification.type) {
|
||||
case 'like':
|
||||
i18nString = 'favorited_you'
|
||||
break
|
||||
case 'repeat':
|
||||
i18nString = 'repeated_you'
|
||||
break
|
||||
case 'follow':
|
||||
i18nString = 'followed_you'
|
||||
break
|
||||
case 'move':
|
||||
i18nString = 'migrated_to'
|
||||
break
|
||||
case 'follow_request':
|
||||
i18nString = 'follow_request'
|
||||
break
|
||||
}
|
||||
|
||||
if (notification.type === 'pleroma:emoji_reaction') {
|
||||
notifObj.body = i18n.t('notifications.reacted_with', [notification.emoji])
|
||||
} else if (i18nString) {
|
||||
notifObj.body = i18n.t('notifications.' + i18nString)
|
||||
} else if (isStatusNotification(notification.type)) {
|
||||
notifObj.body = notification.status.text
|
||||
}
|
||||
|
||||
// Shows first attached non-nsfw image, if any. Should add configuration for this somehow...
|
||||
if (status && status.attachments && status.attachments.length > 0 && !status.nsfw &&
|
||||
status.attachments[0].mimetype.startsWith('image/')) {
|
||||
notifObj.image = status.attachments[0].url
|
||||
}
|
||||
|
||||
return notifObj
|
||||
}
|
||||
|
@ -1,17 +1,4 @@
|
||||
import { filter } from 'lodash'
|
||||
import sanitize from 'sanitize-html'
|
||||
|
||||
export const removeAttachmentLinks = (html) => {
|
||||
return sanitize(html, {
|
||||
allowedTags: false,
|
||||
allowedAttributes: false,
|
||||
exclusiveFilter: ({ tag, attribs }) => tag === 'a' && typeof attribs.class === 'string' && attribs.class.match(/attachment/)
|
||||
})
|
||||
}
|
||||
|
||||
export const parse = (html) => {
|
||||
return removeAttachmentLinks(html)
|
||||
}
|
||||
|
||||
export const muteWordHits = (status, muteWords) => {
|
||||
const statusText = status.text.toLowerCase()
|
||||
@ -22,5 +9,3 @@ export const muteWordHits = (status, muteWords) => {
|
||||
|
||||
return hits
|
||||
}
|
||||
|
||||
export default parse
|
||||
|
@ -356,6 +356,12 @@ export const SLOT_INHERITANCE = {
|
||||
textColor: 'preserve'
|
||||
},
|
||||
|
||||
postGreentext: {
|
||||
depends: ['cGreen'],
|
||||
layer: 'bg',
|
||||
textColor: 'preserve'
|
||||
},
|
||||
|
||||
border: {
|
||||
depends: ['fg'],
|
||||
opacity: 'border',
|
||||
|
47
src/sw.js
47
src/sw.js
@ -1,6 +1,19 @@
|
||||
/* eslint-env serviceworker */
|
||||
|
||||
import localForage from 'localforage'
|
||||
import { parseNotification } from './services/entity_normalizer/entity_normalizer.service.js'
|
||||
import { prepareNotificationObject } from './services/notification_utils/notification_utils.js'
|
||||
import Vue from 'vue'
|
||||
import VueI18n from 'vue-i18n'
|
||||
import messages from './i18n/service_worker_messages.js'
|
||||
|
||||
Vue.use(VueI18n)
|
||||
const i18n = new VueI18n({
|
||||
// By default, use the browser locale, we will update it if neccessary
|
||||
locale: 'en',
|
||||
fallbackLocale: 'en',
|
||||
messages
|
||||
})
|
||||
|
||||
function isEnabled () {
|
||||
return localForage.getItem('vuex-lz')
|
||||
@ -12,15 +25,33 @@ function getWindowClients () {
|
||||
.then((clientList) => clientList.filter(({ type }) => type === 'window'))
|
||||
}
|
||||
|
||||
self.addEventListener('push', (event) => {
|
||||
if (event.data) {
|
||||
event.waitUntil(isEnabled().then((isEnabled) => {
|
||||
return isEnabled && getWindowClients().then((list) => {
|
||||
const data = event.data.json()
|
||||
const setLocale = async () => {
|
||||
const state = await localForage.getItem('vuex-lz')
|
||||
const locale = state.config.interfaceLanguage || 'en'
|
||||
i18n.locale = locale
|
||||
}
|
||||
|
||||
if (list.length === 0) return self.registration.showNotification(data.title, data)
|
||||
})
|
||||
}))
|
||||
const maybeShowNotification = async (event) => {
|
||||
const enabled = await isEnabled()
|
||||
const activeClients = await getWindowClients()
|
||||
await setLocale()
|
||||
if (enabled && (activeClients.length === 0)) {
|
||||
const data = event.data.json()
|
||||
|
||||
const url = `${self.registration.scope}api/v1/notifications/${data.notification_id}`
|
||||
const notification = await fetch(url, { headers: { Authorization: 'Bearer ' + data.access_token } })
|
||||
const notificationJson = await notification.json()
|
||||
const parsedNotification = parseNotification(notificationJson)
|
||||
|
||||
const res = prepareNotificationObject(parsedNotification, i18n)
|
||||
|
||||
self.registration.showNotification(res.title, res)
|
||||
}
|
||||
}
|
||||
|
||||
self.addEventListener('push', async (event) => {
|
||||
if (event.data) {
|
||||
event.waitUntil(maybeShowNotification(event))
|
||||
}
|
||||
})
|
||||
|
||||
|
@ -1,4 +1,9 @@
|
||||
<h4>Terms of Service</h4>
|
||||
|
||||
<p>This is a placeholder ToS. Edit <code>"/static/terms-of-service.html"</code> to make it fit the needs of your instance.</p>
|
||||
<p>This is the default placeholder ToS. You should copy it over to your static folder and edit it to fit the needs of your instance.</p>
|
||||
|
||||
<p>To do so, place a file at <code>"/instance/static/terms-of-service.html"</code> in your
|
||||
Pleroma install containing the real ToS for your instance.</p>
|
||||
<p>See the <a href='https://docs.pleroma.social/backend/configuration/static_dir/'>Pleroma documentation</a> for more information.</p>
|
||||
<br>
|
||||
<img src="/static/logo.png" style="display: block; margin: auto; max-width: 100%; height: 50px; object-fit: contain;" />
|
||||
|
@ -286,7 +286,9 @@
|
||||
"cGreen": "#008000",
|
||||
"cOrange": "#808000",
|
||||
"highlight": "--accent",
|
||||
"selectedPost": "--bg,-10"
|
||||
"selectedPost": "--bg,-10",
|
||||
"selectedMenu": "--accent",
|
||||
"selectedMenuPopover": "--accent"
|
||||
},
|
||||
"radii": {
|
||||
"btn": "0",
|
||||
|
@ -277,7 +277,9 @@
|
||||
"cGreen": "#008000",
|
||||
"cOrange": "#808000",
|
||||
"highlight": "--accent",
|
||||
"selectedPost": "--bg,-10"
|
||||
"selectedPost": "--bg,-10",
|
||||
"selectedMenu": "--accent",
|
||||
"selectedMenuPopover": "--accent"
|
||||
},
|
||||
"radii": {
|
||||
"btn": "0",
|
||||
|
@ -259,7 +259,9 @@
|
||||
"cGreen": "#669966",
|
||||
"cOrange": "#cc6633",
|
||||
"highlight": "--accent",
|
||||
"selectedPost": "--bg,-10"
|
||||
"selectedPost": "--bg,-10",
|
||||
"selectedMenu": "--accent",
|
||||
"selectedMenuPopover": "--accent"
|
||||
},
|
||||
"radii": {
|
||||
"btn": "0",
|
||||
|
@ -290,6 +290,19 @@ describe('API Entities normalizer', () => {
|
||||
expect(field).to.have.property('value').that.contains('<img')
|
||||
})
|
||||
|
||||
it('removes html tags from user profile fields', () => {
|
||||
const user = makeMockUserMasto({ emojis: makeMockEmojiMasto(), fields: [{ name: 'user', value: '<a rel="me" href="https://example.com/@user">@user</a>' }] })
|
||||
|
||||
const parsedUser = parseUser(user)
|
||||
|
||||
expect(parsedUser).to.have.property('fields_text').to.be.an('array')
|
||||
|
||||
const field = parsedUser.fields_text[0]
|
||||
|
||||
expect(field).to.have.property('name').that.equal('user')
|
||||
expect(field).to.have.property('value').that.equal('@user')
|
||||
})
|
||||
|
||||
it('adds hide_follows and hide_followers user settings', () => {
|
||||
const user = makeMockUserMasto({ pleroma: { hide_followers: true, hide_follows: false, hide_followers_count: false, hide_follows_count: true } })
|
||||
|
||||
|
@ -1,17 +0,0 @@
|
||||
import { removeAttachmentLinks } from '../../../../../src/services/status_parser/status_parser.js'
|
||||
|
||||
const example = '<div class="status-content">@<a href="https://sealion.club/user/4" class="h-card mention" title="dewoo">dwmatiz</a> <a href="https://social.heldscal.la/file/3deb764ada10ce64a61b7a070b75dac45f86d2d5bf213bf18873da71d8714d86.png" title="https://social.heldscal.la/file/3deb764ada10ce64a61b7a070b75dac45f86d2d5bf213bf18873da71d8714d86.png" class="attachment" id="attachment-159853" rel="nofollow external">https://social.heldscal.la/attachment/159853</a></div>'
|
||||
|
||||
describe('statusParser.removeAttachmentLinks', () => {
|
||||
const exampleWithoutAttachmentLinks = '<div class="status-content">@<a href="https://sealion.club/user/4" class="h-card mention" title="dewoo">dwmatiz</a> </div>'
|
||||
|
||||
it('removes attachment links', () => {
|
||||
const parsed = removeAttachmentLinks(example)
|
||||
expect(parsed).to.eql(exampleWithoutAttachmentLinks)
|
||||
})
|
||||
|
||||
it('works when the class is empty', () => {
|
||||
const parsed = removeAttachmentLinks('<a></a>')
|
||||
expect(parsed).to.eql('<a></a>')
|
||||
})
|
||||
})
|
100
yarn.lock
100
yarn.lock
@ -1062,7 +1062,7 @@ array-union@^1.0.1:
|
||||
dependencies:
|
||||
array-uniq "^1.0.1"
|
||||
|
||||
array-uniq@^1.0.1, array-uniq@^1.0.2:
|
||||
array-uniq@^1.0.1:
|
||||
version "1.0.3"
|
||||
resolved "https://registry.yarnpkg.com/array-uniq/-/array-uniq-1.0.3.tgz#af6ac877a25cc7f74e058894753858dfdb24fdb6"
|
||||
|
||||
@ -2545,7 +2545,7 @@ domain-browser@^1.1.1:
|
||||
version "1.2.0"
|
||||
resolved "https://registry.yarnpkg.com/domain-browser/-/domain-browser-1.2.0.tgz#3d31f50191a6749dd1375a7f522e823d42e54eda"
|
||||
|
||||
domelementtype@1, domelementtype@^1.3.0:
|
||||
domelementtype@1:
|
||||
version "1.3.1"
|
||||
resolved "https://registry.yarnpkg.com/domelementtype/-/domelementtype-1.3.1.tgz#d048c44b37b0d10a7f2a3d5fee3f4333d790481f"
|
||||
|
||||
@ -2559,12 +2559,6 @@ domhandler@2.1:
|
||||
dependencies:
|
||||
domelementtype "1"
|
||||
|
||||
domhandler@^2.3.0:
|
||||
version "2.4.2"
|
||||
resolved "https://registry.yarnpkg.com/domhandler/-/domhandler-2.4.2.tgz#8805097e933d65e85546f726d60f5eb88b44f803"
|
||||
dependencies:
|
||||
domelementtype "1"
|
||||
|
||||
domutils@1.1:
|
||||
version "1.1.6"
|
||||
resolved "https://registry.yarnpkg.com/domutils/-/domutils-1.1.6.tgz#bddc3de099b9a2efacc51c623f28f416ecc57485"
|
||||
@ -2578,13 +2572,6 @@ domutils@1.5.1:
|
||||
dom-serializer "0"
|
||||
domelementtype "1"
|
||||
|
||||
domutils@^1.5.1:
|
||||
version "1.7.0"
|
||||
resolved "https://registry.yarnpkg.com/domutils/-/domutils-1.7.0.tgz#56ea341e834e06e6748af7a1cb25da67ea9f8c2a"
|
||||
dependencies:
|
||||
dom-serializer "0"
|
||||
domelementtype "1"
|
||||
|
||||
duplexer2@~0.1.4:
|
||||
version "0.1.4"
|
||||
resolved "https://registry.yarnpkg.com/duplexer2/-/duplexer2-0.1.4.tgz#8b12dab878c0d69e3e7891051662a32fc6bddcc1"
|
||||
@ -2711,7 +2698,7 @@ ent@~2.2.0:
|
||||
version "2.2.0"
|
||||
resolved "https://registry.yarnpkg.com/ent/-/ent-2.2.0.tgz#e964219325a21d05f44466a2f686ed6ce5f5dd1d"
|
||||
|
||||
entities@^1.1.1, entities@~1.1.1:
|
||||
entities@~1.1.1:
|
||||
version "1.1.2"
|
||||
resolved "https://registry.yarnpkg.com/entities/-/entities-1.1.2.tgz#bdfa735299664dfafd34529ed4f8522a275fea56"
|
||||
|
||||
@ -3762,17 +3749,6 @@ html-webpack-plugin@^3.0.0, html-webpack-plugin@^3.2.0:
|
||||
toposort "^1.0.0"
|
||||
util.promisify "1.0.0"
|
||||
|
||||
htmlparser2@^3.10.0:
|
||||
version "3.10.0"
|
||||
resolved "https://registry.yarnpkg.com/htmlparser2/-/htmlparser2-3.10.0.tgz#5f5e422dcf6119c0d983ed36260ce9ded0bee464"
|
||||
dependencies:
|
||||
domelementtype "^1.3.0"
|
||||
domhandler "^2.3.0"
|
||||
domutils "^1.5.1"
|
||||
entities "^1.1.1"
|
||||
inherits "^2.0.1"
|
||||
readable-stream "^3.0.6"
|
||||
|
||||
htmlparser2@~3.3.0:
|
||||
version "3.3.0"
|
||||
resolved "https://registry.yarnpkg.com/htmlparser2/-/htmlparser2-3.3.0.tgz#cc70d05a59f6542e43f0e685c982e14c924a9efe"
|
||||
@ -4757,10 +4733,6 @@ lodash.clone@3.0.3:
|
||||
lodash._bindcallback "^3.0.0"
|
||||
lodash._isiterateecall "^3.0.0"
|
||||
|
||||
lodash.clonedeep@^4.5.0:
|
||||
version "4.5.0"
|
||||
resolved "https://registry.yarnpkg.com/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz#e23f3f9c4f8fbdde872529c1071857a086e5ccef"
|
||||
|
||||
lodash.create@3.1.1:
|
||||
version "3.1.1"
|
||||
resolved "https://registry.yarnpkg.com/lodash.create/-/lodash.create-3.1.1.tgz#d7f2849f0dbda7e04682bb8cd72ab022461debe7"
|
||||
@ -4780,10 +4752,6 @@ lodash.defaultsdeep@4.3.2:
|
||||
lodash.mergewith "^4.0.0"
|
||||
lodash.rest "^4.0.0"
|
||||
|
||||
lodash.escaperegexp@^4.1.2:
|
||||
version "4.1.2"
|
||||
resolved "https://registry.yarnpkg.com/lodash.escaperegexp/-/lodash.escaperegexp-4.1.2.tgz#64762c48618082518ac3df4ccf5d5886dae20347"
|
||||
|
||||
lodash.find@^3.2.1:
|
||||
version "3.2.1"
|
||||
resolved "https://registry.yarnpkg.com/lodash.find/-/lodash.find-3.2.1.tgz#046e319f3ace912ac6c9246c7f683c5ec07b36ad"
|
||||
@ -4815,14 +4783,10 @@ lodash.isplainobject@^3.0.0, lodash.isplainobject@^3.2.0:
|
||||
lodash.isarguments "^3.0.0"
|
||||
lodash.keysin "^3.0.0"
|
||||
|
||||
lodash.isplainobject@^4.0.0, lodash.isplainobject@^4.0.6:
|
||||
lodash.isplainobject@^4.0.0:
|
||||
version "4.0.6"
|
||||
resolved "https://registry.yarnpkg.com/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz#7c526a52d89b45c45cc690b88163be0497f550cb"
|
||||
|
||||
lodash.isstring@^4.0.1:
|
||||
version "4.0.1"
|
||||
resolved "https://registry.yarnpkg.com/lodash.isstring/-/lodash.isstring-4.0.1.tgz#d527dfb5456eca7cc9bb95d5daeaf88ba54a5451"
|
||||
|
||||
lodash.istypedarray@^3.0.0:
|
||||
version "3.0.6"
|
||||
resolved "https://registry.yarnpkg.com/lodash.istypedarray/-/lodash.istypedarray-3.0.6.tgz#c9a477498607501d8e8494d283b87c39281cef62"
|
||||
@ -4871,7 +4835,7 @@ lodash.merge@^3.3.2:
|
||||
lodash.keysin "^3.0.0"
|
||||
lodash.toplainobject "^3.0.0"
|
||||
|
||||
lodash.mergewith@^4.0.0, lodash.mergewith@^4.6.1:
|
||||
lodash.mergewith@^4.0.0:
|
||||
version "4.6.1"
|
||||
resolved "https://registry.yarnpkg.com/lodash.mergewith/-/lodash.mergewith-4.6.1.tgz#639057e726c3afbdb3e7d42741caa8d6e4335927"
|
||||
|
||||
@ -5538,10 +5502,6 @@ object-keys@^1.0.11, object-keys@^1.0.12:
|
||||
version "1.1.1"
|
||||
resolved "https://registry.yarnpkg.com/object-keys/-/object-keys-1.1.1.tgz#1c47f272df277f3b1daf061677d9c82e2322c60e"
|
||||
|
||||
object-path@^0.11.3:
|
||||
version "0.11.4"
|
||||
resolved "https://registry.yarnpkg.com/object-path/-/object-path-0.11.4.tgz#370ae752fbf37de3ea70a861c23bba8915691949"
|
||||
|
||||
object-visit@^1.0.0:
|
||||
version "1.0.1"
|
||||
resolved "https://registry.yarnpkg.com/object-visit/-/object-visit-1.0.1.tgz#f79c4493af0c5377b59fe39d395e41042dd045bb"
|
||||
@ -6245,14 +6205,6 @@ postcss@^7.0.0:
|
||||
source-map "^0.6.1"
|
||||
supports-color "^6.1.0"
|
||||
|
||||
postcss@^7.0.5:
|
||||
version "7.0.8"
|
||||
resolved "https://registry.yarnpkg.com/postcss/-/postcss-7.0.8.tgz#2a3c5f2bdd00240cd0d0901fd998347c93d36696"
|
||||
dependencies:
|
||||
chalk "^2.4.2"
|
||||
source-map "^0.6.1"
|
||||
supports-color "^6.0.0"
|
||||
|
||||
prelude-ls@~1.1.2:
|
||||
version "1.1.2"
|
||||
resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.1.2.tgz#21932a549f5e52ffd9a827f570e04be62a97da54"
|
||||
@ -6521,14 +6473,6 @@ readable-stream@1.1.x:
|
||||
isarray "0.0.1"
|
||||
string_decoder "~0.10.x"
|
||||
|
||||
readable-stream@^3.0.6:
|
||||
version "3.1.1"
|
||||
resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-3.1.1.tgz#ed6bbc6c5ba58b090039ff18ce670515795aeb06"
|
||||
dependencies:
|
||||
inherits "^2.0.3"
|
||||
string_decoder "^1.1.1"
|
||||
util-deprecate "^1.0.1"
|
||||
|
||||
readdirp@^2.2.1:
|
||||
version "2.2.1"
|
||||
resolved "https://registry.yarnpkg.com/readdirp/-/readdirp-2.2.1.tgz#0e87622a3325aa33e892285caf8b4e846529a525"
|
||||
@ -6839,21 +6783,6 @@ samsam@1.x, samsam@^1.1.3:
|
||||
version "1.3.0"
|
||||
resolved "https://registry.yarnpkg.com/samsam/-/samsam-1.3.0.tgz#8d1d9350e25622da30de3e44ba692b5221ab7c50"
|
||||
|
||||
sanitize-html@^1.13.0:
|
||||
version "1.20.0"
|
||||
resolved "https://registry.yarnpkg.com/sanitize-html/-/sanitize-html-1.20.0.tgz#9a602beb1c9faf960fb31f9890f61911cc4d9156"
|
||||
dependencies:
|
||||
chalk "^2.4.1"
|
||||
htmlparser2 "^3.10.0"
|
||||
lodash.clonedeep "^4.5.0"
|
||||
lodash.escaperegexp "^4.1.2"
|
||||
lodash.isplainobject "^4.0.6"
|
||||
lodash.isstring "^4.0.1"
|
||||
lodash.mergewith "^4.6.1"
|
||||
postcss "^7.0.5"
|
||||
srcset "^1.0.0"
|
||||
xtend "^4.0.1"
|
||||
|
||||
"sass-loader@git://github.com/webpack-contrib/sass-loader":
|
||||
version "7.1.0"
|
||||
resolved "git://github.com/webpack-contrib/sass-loader#e279f2a129eee0bd0b624b5acd498f23a81ee35e"
|
||||
@ -7225,13 +7154,6 @@ sprintf-js@~1.0.2:
|
||||
version "1.0.3"
|
||||
resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.0.3.tgz#04e6926f662895354f3dd015203633b857297e2c"
|
||||
|
||||
srcset@^1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/srcset/-/srcset-1.0.0.tgz#a5669de12b42f3b1d5e83ed03c71046fc48f41ef"
|
||||
dependencies:
|
||||
array-uniq "^1.0.2"
|
||||
number-is-nan "^1.0.0"
|
||||
|
||||
sshpk@^1.7.0:
|
||||
version "1.16.0"
|
||||
resolved "https://registry.yarnpkg.com/sshpk/-/sshpk-1.16.0.tgz#1d4963a2fbffe58050aa9084ca20be81741c07de"
|
||||
@ -7331,7 +7253,7 @@ string-width@^3.0.0:
|
||||
is-fullwidth-code-point "^2.0.0"
|
||||
strip-ansi "^5.1.0"
|
||||
|
||||
string_decoder@^1.0.0, string_decoder@^1.1.1:
|
||||
string_decoder@^1.0.0:
|
||||
version "1.2.0"
|
||||
resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.2.0.tgz#fe86e738b19544afe70469243b2a1ee9240eae8d"
|
||||
dependencies:
|
||||
@ -7415,7 +7337,7 @@ supports-color@^5.3.0, supports-color@^5.4.0:
|
||||
dependencies:
|
||||
has-flag "^3.0.0"
|
||||
|
||||
supports-color@^6.0.0, supports-color@^6.1.0:
|
||||
supports-color@^6.1.0:
|
||||
version "6.1.0"
|
||||
resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-6.1.0.tgz#0764abc69c63d5ac842dd4867e8d025e880df8f3"
|
||||
dependencies:
|
||||
@ -7780,7 +7702,7 @@ useragent@2.3.0:
|
||||
lru-cache "4.1.x"
|
||||
tmp "0.0.x"
|
||||
|
||||
util-deprecate@^1.0.1, util-deprecate@~1.0.1:
|
||||
util-deprecate@~1.0.1:
|
||||
version "1.0.2"
|
||||
resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf"
|
||||
|
||||
@ -8015,10 +7937,6 @@ webpack@^4.0.0:
|
||||
watchpack "^1.5.0"
|
||||
webpack-sources "^1.3.0"
|
||||
|
||||
whatwg-fetch@^2.0.3:
|
||||
version "2.0.4"
|
||||
resolved "https://registry.yarnpkg.com/whatwg-fetch/-/whatwg-fetch-2.0.4.tgz#dde6a5df315f9d39991aa17621853d720b85566f"
|
||||
|
||||
whet.extend@~0.9.9:
|
||||
version "0.9.9"
|
||||
resolved "https://registry.yarnpkg.com/whet.extend/-/whet.extend-0.9.9.tgz#f877d5bf648c97e5aa542fadc16d6a259b9c11a1"
|
||||
@ -8090,7 +8008,7 @@ xregexp@2.0.0:
|
||||
version "2.0.0"
|
||||
resolved "https://registry.yarnpkg.com/xregexp/-/xregexp-2.0.0.tgz#52a63e56ca0b84a7f3a5f3d61872f126ad7a5943"
|
||||
|
||||
xtend@^4.0.0, xtend@^4.0.1, xtend@~4.0.1:
|
||||
xtend@^4.0.0, xtend@~4.0.1:
|
||||
version "4.0.1"
|
||||
resolved "https://registry.yarnpkg.com/xtend/-/xtend-4.0.1.tgz#a5c6d532be656e23db820efb943a1f04998d63af"
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user