aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorShpuld Shpludson <shp@cock.li>2020-06-27 07:19:49 +0000
committerShpuld Shpludson <shp@cock.li>2020-06-27 07:19:49 +0000
commitea0a12f6049241cb9e8fa7cd5bc6dc8b9852b57c (patch)
tree36863abf8ac320756bc16906b11a7892ea7da0dc /src
parentbad3dacfac1ef3dd2c0f55b53fb78f4bf410a01e (diff)
parentbbb91d8ae3f1c3d5374de7610e723e63121e8222 (diff)
Merge branch 'develop' into 'iss-149/profile-fields-setting'
# Conflicts: # src/components/settings_modal/tabs/profile_tab.vue
Diffstat (limited to 'src')
-rw-r--r--src/components/account_actions/account_actions.vue1
-rw-r--r--src/components/domain_mute_card/domain_mute_card.js11
-rw-r--r--src/components/domain_mute_card/domain_mute_card.vue15
-rw-r--r--src/components/emoji_input/suggestor.js6
-rw-r--r--src/components/extra_buttons/extra_buttons.vue1
-rw-r--r--src/components/gallery/gallery.vue1
-rw-r--r--src/components/media_upload/media_upload.js14
-rw-r--r--src/components/media_upload/media_upload.vue7
-rw-r--r--src/components/notifications/notifications.scss23
-rw-r--r--src/components/poll/poll.vue2
-rw-r--r--src/components/popover/popover.js11
-rw-r--r--src/components/post_status_form/post_status_form.js20
-rw-r--r--src/components/post_status_form/post_status_form.vue46
-rw-r--r--src/components/post_status_modal/post_status_modal.js3
-rw-r--r--src/components/post_status_modal/post_status_modal.vue1
-rw-r--r--src/components/settings_modal/tabs/mutes_and_blocks_tab.js28
-rw-r--r--src/components/settings_modal/tabs/mutes_and_blocks_tab.vue21
-rw-r--r--src/components/settings_modal/tabs/profile_tab.js2
-rw-r--r--src/components/settings_modal/tabs/profile_tab.vue5
-rw-r--r--src/components/settings_modal/tabs/theme_tab/theme_tab.vue7
-rw-r--r--src/components/status/status.vue2
-rw-r--r--src/components/status_content/status_content.vue26
-rw-r--r--src/components/still-image/still-image.vue10
-rw-r--r--src/components/user_card/user_card.vue20
-rw-r--r--src/components/user_profile/user_profile.js8
-rw-r--r--src/components/user_profile/user_profile.vue74
-rw-r--r--src/i18n/en.json3
-rw-r--r--src/i18n/it.json70
-rw-r--r--src/i18n/ru.json5
-rw-r--r--src/i18n/service_worker_messages.js35
-rw-r--r--src/lib/notification-i18n-loader.js12
-rw-r--r--src/lib/persisted_state.js5
-rw-r--r--src/modules/instance.js17
-rw-r--r--src/modules/statuses.js41
-rw-r--r--src/modules/users.js6
-rw-r--r--src/services/api/api.service.js7
-rw-r--r--src/services/entity_normalizer/entity_normalizer.service.js12
-rw-r--r--src/services/notification_utils/notification_utils.js44
-rw-r--r--src/services/status_parser/status_parser.js15
-rw-r--r--src/services/theme_data/pleromafe.js6
-rw-r--r--src/sw.js47
41 files changed, 521 insertions, 169 deletions
diff --git a/src/components/account_actions/account_actions.vue b/src/components/account_actions/account_actions.vue
index 744b77d5..029e7096 100644
--- a/src/components/account_actions/account_actions.vue
+++ b/src/components/account_actions/account_actions.vue
@@ -3,6 +3,7 @@
<Popover
trigger="click"
placement="bottom"
+ :bound-to="{ x: 'container' }"
>
<div
slot="content"
diff --git a/src/components/domain_mute_card/domain_mute_card.js b/src/components/domain_mute_card/domain_mute_card.js
index c8e838ba..f234dcb0 100644
--- a/src/components/domain_mute_card/domain_mute_card.js
+++ b/src/components/domain_mute_card/domain_mute_card.js
@@ -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)
}
}
}
diff --git a/src/components/domain_mute_card/domain_mute_card.vue b/src/components/domain_mute_card/domain_mute_card.vue
index 567d81c5..97aee243 100644
--- a/src/components/domain_mute_card/domain_mute_card.vue
+++ b/src/components/domain_mute_card/domain_mute_card.vue
@@ -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>
diff --git a/src/components/emoji_input/suggestor.js b/src/components/emoji_input/suggestor.js
index 15a71eff..8330345b 100644
--- a/src/components/emoji_input/suggestor.js
+++ b/src/components/emoji_input/suggestor.js
@@ -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
diff --git a/src/components/extra_buttons/extra_buttons.vue b/src/components/extra_buttons/extra_buttons.vue
index bca93ea7..68db6fd8 100644
--- a/src/components/extra_buttons/extra_buttons.vue
+++ b/src/components/extra_buttons/extra_buttons.vue
@@ -3,6 +3,7 @@
trigger="click"
placement="top"
class="extra-button-popover"
+ :bound-to="{ x: 'container' }"
>
<div
slot="content"
diff --git a/src/components/gallery/gallery.vue b/src/components/gallery/gallery.vue
index 7abc2161..1ffa7b3c 100644
--- a/src/components/gallery/gallery.vue
+++ b/src/components/gallery/gallery.vue
@@ -78,6 +78,7 @@
video,
canvas {
object-fit: contain;
+ height: 100%;
}
}
diff --git a/src/components/media_upload/media_upload.js b/src/components/media_upload/media_upload.js
index 5849b065..fbb2d03d 100644
--- a/src/components/media_upload/media_upload.js
+++ b/src/components/media_upload/media_upload.js
@@ -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(() => {
diff --git a/src/components/media_upload/media_upload.vue b/src/components/media_upload/media_upload.vue
index 0fc305ac..5e31730b 100644
--- a/src/components/media_upload/media_upload.vue
+++ b/src/components/media_upload/media_upload.vue
@@ -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')"
diff --git a/src/components/notifications/notifications.scss b/src/components/notifications/notifications.scss
index b675af5a..20797cf9 100644
--- a/src/components/notifications/notifications.scss
+++ b/src/components/notifications/notifications.scss
@@ -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);
}
}
}
diff --git a/src/components/poll/poll.vue b/src/components/poll/poll.vue
index 56e91cca..adbb0555 100644
--- a/src/components/poll/poll.vue
+++ b/src/components/poll/poll.vue
@@ -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"
diff --git a/src/components/popover/popover.js b/src/components/popover/popover.js
index 5881d266..a40a9195 100644
--- a/src/components/popover/popover.js
+++ b/src/components/popover/popover.js
@@ -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
diff --git a/src/components/post_status_form/post_status_form.js b/src/components/post_status_form/post_status_form.js
index 6164caa0..9027566f 100644
--- a/src/components/post_status_form/post_status_form.js
+++ b/src/components/post_status_form/post_status_form.js
@@ -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(() => {
diff --git a/src/components/post_status_form/post_status_form.vue b/src/components/post_status_form/post_status_form.vue
index 5629ceac..e3d8d087 100644
--- a/src/components/post_status_form/post_status_form.vue
+++ b/src/components/post_status_form/post_status_form.vue
@@ -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>
diff --git a/src/components/post_status_modal/post_status_modal.js b/src/components/post_status_modal/post_status_modal.js
index be945400..b44354db 100644
--- a/src/components/post_status_modal/post_status_modal.js
+++ b/src/components/post_status_modal/post_status_modal.js
@@ -13,6 +13,9 @@ const PostStatusModal = {
}
},
computed: {
+ isLoggedIn () {
+ return !!this.$store.state.users.currentUser
+ },
modalActivated () {
return this.$store.state.postStatus.modalActivated
},
diff --git a/src/components/post_status_modal/post_status_modal.vue b/src/components/post_status_modal/post_status_modal.vue
index 07c58f74..dbcd321e 100644
--- a/src/components/post_status_modal/post_status_modal.vue
+++ b/src/components/post_status_modal/post_status_modal.vue
@@ -1,5 +1,6 @@
<template>
<Modal
+ v-if="isLoggedIn && !resettingForm"
:is-open="modalActivated"
class="post-form-modal-view"
@backdropClicked="closeModal"
diff --git a/src/components/settings_modal/tabs/mutes_and_blocks_tab.js b/src/components/settings_modal/tabs/mutes_and_blocks_tab.js
index b0043dbb..40a87b81 100644
--- a/src/components/settings_modal/tabs/mutes_and_blocks_tab.js
+++ b/src/components/settings_modal/tabs/mutes_and_blocks_tab.js
@@ -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 = '' })
}
}
}
diff --git a/src/components/settings_modal/tabs/mutes_and_blocks_tab.vue b/src/components/settings_modal/tabs/mutes_and_blocks_tab.vue
index 6884b7be..5a1cf2c0 100644
--- a/src/components/settings_modal/tabs/mutes_and_blocks_tab.vue
+++ b/src/components/settings_modal/tabs/mutes_and_blocks_tab.vue
@@ -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"
diff --git a/src/components/settings_modal/tabs/profile_tab.js b/src/components/settings_modal/tabs/profile_tab.js
index 0874e0c8..e6db802d 100644
--- a/src/components/settings_modal/tabs/profile_tab.js
+++ b/src/components/settings_modal/tabs/profile_tab.js
@@ -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,
diff --git a/src/components/settings_modal/tabs/profile_tab.vue b/src/components/settings_modal/tabs/profile_tab.vue
index 20a68f7d..0f9210a6 100644
--- a/src/components/settings_modal/tabs/profile_tab.vue
+++ b/src/components/settings_modal/tabs/profile_tab.vue
@@ -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"
diff --git a/src/components/settings_modal/tabs/theme_tab/theme_tab.vue b/src/components/settings_modal/tabs/theme_tab/theme_tab.vue
index fcfad23b..d14f854c 100644
--- a/src/components/settings_modal/tabs/theme_tab/theme_tab.vue
+++ b/src/components/settings_modal/tabs/theme_tab/theme_tab.vue
@@ -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"
diff --git a/src/components/status/status.vue b/src/components/status/status.vue
index 336f912a..7ec29b28 100644
--- a/src/components/status/status.vue
+++ b/src/components/status/status.vue
@@ -418,7 +418,7 @@ $status-margin: 0.75em;
max-width: 85%;
font-weight: bold;
- img {
+ img.emoji {
width: 14px;
height: 14px;
vertical-align: middle;
diff --git a/src/components/status_content/status_content.vue b/src/components/status_content/status_content.vue
index 8c2e8749..efc2485e 100644
--- a/src/components/status_content/status_content.vue
+++ b/src/components/status_content/status_content.vue
@@ -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) > {
diff --git a/src/components/still-image/still-image.vue b/src/components/still-image/still-image.vue
index 08af26f6..f2ddeb7b 100644
--- a/src/components/still-image/still-image.vue
+++ b/src/components/still-image/still-image.vue
@@ -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 {
diff --git a/src/components/user_card/user_card.vue b/src/components/user_card/user_card.vue
index c4a5ce9d..9529d7f6 100644
--- a/src/components/user_card/user_card.vue
+++ b/src/components/user_card/user_card.vue
@@ -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;
diff --git a/src/components/user_profile/user_profile.js b/src/components/user_profile/user_profile.js
index 95760bf8..201727d4 100644
--- a/src/components/user_profile/user_profile.js
+++ b/src/components/user_profile/user_profile.js
@@ -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: {
diff --git a/src/components/user_profile/user_profile.vue b/src/components/user_profile/user_profile.vue
index 1871d46c..361a3b5c 100644
--- a/src/components/user_profile/user_profile.vue
+++ b/src/components/user_profile/user_profile.vue
@@ -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;
diff --git a/src/i18n/en.json b/src/i18n/en.json
index ca49514e..2840904f 100644
--- a/src/i18n/en.json
+++ b/src/i18n/en.json
@@ -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": {
diff --git a/src/i18n/it.json b/src/i18n/it.json
index 360c72aa..6c8be351 100644
--- a/src/i18n/it.json
+++ b/src/i18n/it.json
@@ -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",
diff --git a/src/i18n/ru.json b/src/i18n/ru.json
index 69b22618..aa78db26 100644
--- a/src/i18n/ru.json
+++ b/src/i18n/ru.json
@@ -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": "Экспорт",
diff --git a/src/i18n/service_worker_messages.js b/src/i18n/service_worker_messages.js
new file mode 100644
index 00000000..270ed043
--- /dev/null
+++ b/src/i18n/service_worker_messages.js
@@ -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
diff --git a/src/lib/notification-i18n-loader.js b/src/lib/notification-i18n-loader.js
new file mode 100644
index 00000000..71f9156a
--- /dev/null
+++ b/src/lib/notification-i18n-loader.js
@@ -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)
+}
diff --git a/src/lib/persisted_state.js b/src/lib/persisted_state.js
index cad7ea25..8ecb66a8 100644
--- a/src/lib/persisted_state.js
+++ b/src/lib/persisted_state.js
@@ -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
}, {})
)
diff --git a/src/modules/instance.js b/src/modules/instance.js
index da82eb01..ec5f4e54 100644
--- a/src/modules/instance.js
+++ b/src/modules/instance.js
@@ -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)
+ }
}
}
}
diff --git a/src/modules/statuses.js b/src/modules/statuses.js
index 9a2e0df1..073b15f1 100644
--- a/src/modules/statuses.js
+++ b/src/modules/statuses.js
@@ -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)
diff --git a/src/modules/users.js b/src/modules/users.js
index fca01a56..68d02931 100644
--- a/src/modules/users.js
+++ b/src/modules/users.js
@@ -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
})
},
diff --git a/src/services/api/api.service.js b/src/services/api/api.service.js
index 9c7530a2..dfffc291 100644
--- a/src/services/api/api.service.js
+++ b/src/services/api/api.service.js
@@ -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
diff --git a/src/services/entity_normalizer/entity_normalizer.service.js b/src/services/entity_normalizer/entity_normalizer.service.js
index c7ed65a4..3bdb92f3 100644
--- a/src/services/entity_normalizer/entity_normalizer.service.js
+++ b/src/services/entity_normalizer/entity_normalizer.service.js
@@ -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 {
diff --git a/src/services/notification_utils/notification_utils.js b/src/services/notification_utils/notification_utils.js
index eb479227..5cc19215 100644
--- a/src/services/notification_utils/notification_utils.js
+++ b/src/services/notification_utils/notification_utils.js
@@ -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
+}
diff --git a/src/services/status_parser/status_parser.js b/src/services/status_parser/status_parser.js
index 3d517e3c..ed0f6d57 100644
--- a/src/services/status_parser/status_parser.js
+++ b/src/services/status_parser/status_parser.js
@@ -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
diff --git a/src/services/theme_data/pleromafe.js b/src/services/theme_data/pleromafe.js
index 0c1fe543..b577cfab 100644
--- a/src/services/theme_data/pleromafe.js
+++ b/src/services/theme_data/pleromafe.js
@@ -356,6 +356,12 @@ export const SLOT_INHERITANCE = {
textColor: 'preserve'
},
+ postGreentext: {
+ depends: ['cGreen'],
+ layer: 'bg',
+ textColor: 'preserve'
+ },
+
border: {
depends: ['fg'],
opacity: 'border',
diff --git a/src/sw.js b/src/sw.js
index 6cecb3f3..f5e34dd6 100644
--- a/src/sw.js
+++ b/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
+}
+
+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)
- if (list.length === 0) return self.registration.showNotification(data.title, data)
- })
- }))
+ const res = prepareNotificationObject(parsedNotification, i18n)
+
+ self.registration.showNotification(res.title, res)
+ }
+}
+
+self.addEventListener('push', async (event) => {
+ if (event.data) {
+ event.waitUntil(maybeShowNotification(event))
}
})