Merge branch 'chat-refactor' into shigusegubu-themes3
This commit is contained in:
commit
e4429c57cf
20 changed files with 147 additions and 135 deletions
|
|
@ -3,7 +3,11 @@ 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/).
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
|
||||||
|
|
||||||
## 2.11
|
## 2.11.1
|
||||||
|
### Fixed
|
||||||
|
- User note not working
|
||||||
|
|
||||||
|
## 2.11.0
|
||||||
### Added
|
### Added
|
||||||
- Initial MFM rendering support
|
- Initial MFM rendering support
|
||||||
- Button to remove all drafts
|
- Button to remove all drafts
|
||||||
|
|
|
||||||
4
changelog.d/2-11-2.fix
Normal file
4
changelog.d/2-11-2.fix
Normal file
|
|
@ -0,0 +1,4 @@
|
||||||
|
Fixed crashing on Firefox 153
|
||||||
|
Fixed serif-fonts being used by default in some cases
|
||||||
|
Fixed font select component showing and using objects instead of strings
|
||||||
|
Fixed generic ('sans-serif', 'monospace' etc) fonts not working
|
||||||
1
changelog.d/extra-notifications-style.fix
Normal file
1
changelog.d/extra-notifications-style.fix
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
Fix style of extra notifications
|
||||||
1
changelog.d/mfa-fixes.fix
Normal file
1
changelog.d/mfa-fixes.fix
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
Fix MFA login and recovery code authentication.
|
||||||
1
changelog.d/no-crash-on-bad-style.fix
Normal file
1
changelog.d/no-crash-on-bad-style.fix
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
Do not crash even on css rule insertion failure
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "pleroma_fe",
|
"name": "pleroma_fe",
|
||||||
"version": "2.11.0",
|
"version": "2.11.1",
|
||||||
"description": "Pleroma frontend, the default frontend of Pleroma social network server",
|
"description": "Pleroma frontend, the default frontend of Pleroma social network server",
|
||||||
"author": "Pleroma contributors <https://git.pleroma.social/pleroma/pleroma-fe/src/CONTRIBUTORS.md>",
|
"author": "Pleroma contributors <https://git.pleroma.social/pleroma/pleroma-fe/src/CONTRIBUTORS.md>",
|
||||||
"private": false,
|
"private": false,
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,3 @@
|
||||||
import { defineAsyncComponent } from 'vue'
|
|
||||||
|
|
||||||
import AuthForm from 'src/components/auth_form/auth_form.js'
|
import AuthForm from 'src/components/auth_form/auth_form.js'
|
||||||
import BookmarkTimeline from 'src/components/bookmark_timeline/bookmark_timeline.vue'
|
import BookmarkTimeline from 'src/components/bookmark_timeline/bookmark_timeline.vue'
|
||||||
import BubbleTimeline from 'src/components/bubble_timeline/bubble_timeline.vue'
|
import BubbleTimeline from 'src/components/bubble_timeline/bubble_timeline.vue'
|
||||||
|
|
@ -68,9 +66,7 @@ export default (store) => {
|
||||||
{
|
{
|
||||||
name: 'conversation2',
|
name: 'conversation2',
|
||||||
path: '/conversation/:statusId',
|
path: '/conversation/:statusId',
|
||||||
component: defineAsyncComponent(
|
component: () => import('src/components/chat_view/chat_view.vue'),
|
||||||
() => import('src/components/chat_view/chat_view.vue'),
|
|
||||||
),
|
|
||||||
props: true,
|
props: true,
|
||||||
meta: { dontScroll: true },
|
meta: { dontScroll: true },
|
||||||
beforeEnter: validateAuthenticatedRoute,
|
beforeEnter: validateAuthenticatedRoute,
|
||||||
|
|
@ -91,23 +87,18 @@ export default (store) => {
|
||||||
{
|
{
|
||||||
name: 'external-user-profile',
|
name: 'external-user-profile',
|
||||||
path: '/users/$:id',
|
path: '/users/$:id',
|
||||||
component: defineAsyncComponent(
|
component: () => import('src/components/user_profile/user_profile.vue'),
|
||||||
() => import('src/components/user_profile/user_profile.vue'),
|
|
||||||
),
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'user-profile-admin-view',
|
name: 'user-profile-admin-view',
|
||||||
path: '/users/$:id/admin_view',
|
path: '/users/$:id/admin_view',
|
||||||
component: defineAsyncComponent(
|
component: () =>
|
||||||
() => import('src/components/user_profile/user_profile_admin_view.vue'),
|
import('src/components/user_profile/user_profile_admin_view.vue'),
|
||||||
),
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'interactions',
|
name: 'interactions',
|
||||||
path: '/users/:username/interactions',
|
path: '/users/:username/interactions',
|
||||||
component: defineAsyncComponent(
|
component: () => import('src/components/interactions/interactions.vue'),
|
||||||
() => import('src/components/interactions/interactions.vue'),
|
|
||||||
),
|
|
||||||
beforeEnter: validateAuthenticatedRoute,
|
beforeEnter: validateAuthenticatedRoute,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|
@ -119,39 +110,31 @@ export default (store) => {
|
||||||
{
|
{
|
||||||
name: 'registration',
|
name: 'registration',
|
||||||
path: '/registration',
|
path: '/registration',
|
||||||
component: defineAsyncComponent(
|
component: () => import('src/components/registration/registration.vue'),
|
||||||
() => import('src/components/registration/registration.vue'),
|
|
||||||
),
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'password-reset',
|
name: 'password-reset',
|
||||||
path: '/password-reset',
|
path: '/password-reset',
|
||||||
component: defineAsyncComponent(
|
component: () =>
|
||||||
() => import('src/components/password_reset/password_reset.vue'),
|
import('src/components/password_reset/password_reset.vue'),
|
||||||
),
|
|
||||||
props: true,
|
props: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'registration-token',
|
name: 'registration-token',
|
||||||
path: '/registration/:token',
|
path: '/registration/:token',
|
||||||
component: defineAsyncComponent(
|
component: () => import('src/components/registration/registration.vue'),
|
||||||
() => import('src/components/registration/registration.vue'),
|
|
||||||
),
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'friend-requests',
|
name: 'friend-requests',
|
||||||
path: '/friend-requests',
|
path: '/friend-requests',
|
||||||
component: defineAsyncComponent(
|
component: () =>
|
||||||
() => import('src/components/follow_requests/follow_requests.vue'),
|
import('src/components/follow_requests/follow_requests.vue'),
|
||||||
),
|
|
||||||
beforeEnter: validateAuthenticatedRoute,
|
beforeEnter: validateAuthenticatedRoute,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'notifications',
|
name: 'notifications',
|
||||||
path: '/:username/notifications',
|
path: '/:username/notifications',
|
||||||
component: defineAsyncComponent(
|
component: () => import('src/components/notifications/notifications.vue'),
|
||||||
() => import('src/components/notifications/notifications.vue'),
|
|
||||||
),
|
|
||||||
props: () => ({ disableTeleport: true }),
|
props: () => ({ disableTeleport: true }),
|
||||||
beforeEnter: validateAuthenticatedRoute,
|
beforeEnter: validateAuthenticatedRoute,
|
||||||
},
|
},
|
||||||
|
|
@ -163,98 +146,74 @@ export default (store) => {
|
||||||
{
|
{
|
||||||
name: 'shout-panel',
|
name: 'shout-panel',
|
||||||
path: '/shout-panel',
|
path: '/shout-panel',
|
||||||
component: defineAsyncComponent(
|
component: () => import('src/components/shout_panel/shout_panel.vue'),
|
||||||
() => import('src/components/shout_panel/shout_panel.vue'),
|
|
||||||
),
|
|
||||||
props: () => ({ floating: false }),
|
props: () => ({ floating: false }),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'oauth-callback',
|
name: 'oauth-callback',
|
||||||
path: '/oauth-callback',
|
path: '/oauth-callback',
|
||||||
component: defineAsyncComponent(
|
component: () =>
|
||||||
() => import('src/components/oauth_callback/oauth_callback.vue'),
|
import('src/components/oauth_callback/oauth_callback.vue'),
|
||||||
),
|
|
||||||
props: (route) => ({ code: route.query.code }),
|
props: (route) => ({ code: route.query.code }),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'search',
|
name: 'search',
|
||||||
path: '/search',
|
path: '/search',
|
||||||
component: defineAsyncComponent(
|
component: () => import('src/components/search/search.vue'),
|
||||||
() => import('src/components/search/search.vue'),
|
|
||||||
),
|
|
||||||
props: (route) => ({ query: route.query.query }),
|
props: (route) => ({ query: route.query.query }),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'who-to-follow',
|
name: 'who-to-follow',
|
||||||
path: '/who-to-follow',
|
path: '/who-to-follow',
|
||||||
component: defineAsyncComponent(
|
component: () => import('src/components/who_to_follow/who_to_follow.vue'),
|
||||||
() => import('src/components/who_to_follow/who_to_follow.vue'),
|
|
||||||
),
|
|
||||||
beforeEnter: validateAuthenticatedRoute,
|
beforeEnter: validateAuthenticatedRoute,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'about',
|
name: 'about',
|
||||||
path: '/about',
|
path: '/about',
|
||||||
component: defineAsyncComponent(
|
component: () => import('src/components/about/about.vue'),
|
||||||
() => import('src/components/about/about.vue'),
|
|
||||||
),
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'announcements',
|
name: 'announcements',
|
||||||
path: '/announcements',
|
path: '/announcements',
|
||||||
component: defineAsyncComponent(
|
component: () =>
|
||||||
() =>
|
|
||||||
import('src/components/announcements_page/announcements_page.vue'),
|
import('src/components/announcements_page/announcements_page.vue'),
|
||||||
),
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'drafts',
|
name: 'drafts',
|
||||||
path: '/drafts',
|
path: '/drafts',
|
||||||
component: defineAsyncComponent(
|
component: () => import('src/components/drafts/drafts.vue'),
|
||||||
() => import('src/components/drafts/drafts.vue'),
|
|
||||||
),
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'user-profile',
|
name: 'user-profile',
|
||||||
path: '/users/:name',
|
path: '/users/:name',
|
||||||
component: defineAsyncComponent(
|
component: () => import('src/components/user_profile/user_profile.vue'),
|
||||||
() => import('src/components/user_profile/user_profile.vue'),
|
|
||||||
),
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'legacy-user-profile',
|
name: 'legacy-user-profile',
|
||||||
path: '/:name',
|
path: '/:name',
|
||||||
component: defineAsyncComponent(
|
component: () => import('src/components/user_profile/user_profile.vue'),
|
||||||
() => import('src/components/user_profile/user_profile.vue'),
|
|
||||||
),
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'lists',
|
name: 'lists',
|
||||||
path: '/lists',
|
path: '/lists',
|
||||||
component: defineAsyncComponent(
|
component: () => import('src/components/lists/lists.vue'),
|
||||||
() => import('src/components/lists/lists.vue'),
|
|
||||||
),
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'lists-timeline',
|
name: 'lists-timeline',
|
||||||
path: '/lists/:id',
|
path: '/lists/:id',
|
||||||
component: defineAsyncComponent(
|
component: () =>
|
||||||
() => import('src/components/lists_timeline/lists_timeline.vue'),
|
import('src/components/lists_timeline/lists_timeline.vue'),
|
||||||
),
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'lists-edit',
|
name: 'lists-edit',
|
||||||
path: '/lists/:id/edit',
|
path: '/lists/:id/edit',
|
||||||
component: defineAsyncComponent(
|
component: () => import('src/components/lists_edit/lists_edit.vue'),
|
||||||
() => import('src/components/lists_edit/lists_edit.vue'),
|
|
||||||
),
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'lists-new',
|
name: 'lists-new',
|
||||||
path: '/lists/new',
|
path: '/lists/new',
|
||||||
component: defineAsyncComponent(
|
component: () => import('src/components/lists_edit/lists_edit.vue'),
|
||||||
() => import('src/components/lists_edit/lists_edit.vue'),
|
|
||||||
),
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'edit-navigation',
|
name: 'edit-navigation',
|
||||||
|
|
@ -266,19 +225,14 @@ export default (store) => {
|
||||||
{
|
{
|
||||||
name: 'bookmark-folders',
|
name: 'bookmark-folders',
|
||||||
path: '/bookmark_folders',
|
path: '/bookmark_folders',
|
||||||
component: defineAsyncComponent(
|
component: () =>
|
||||||
() => import('src/components/bookmark_folders/bookmark_folders.vue'),
|
import('src/components/bookmark_folders/bookmark_folders.vue'),
|
||||||
),
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'bookmark-folder-new',
|
name: 'bookmark-folder-new',
|
||||||
path: '/bookmarks/new-folder',
|
path: '/bookmarks/new-folder',
|
||||||
component: defineAsyncComponent(
|
component: () =>
|
||||||
() =>
|
import('src/components/bookmark_folder_edit/bookmark_folder_edit.vue'),
|
||||||
import(
|
|
||||||
'src/components/bookmark_folder_edit/bookmark_folder_edit.vue'
|
|
||||||
),
|
|
||||||
),
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'bookmark-folder',
|
name: 'bookmark-folder',
|
||||||
|
|
@ -288,12 +242,8 @@ export default (store) => {
|
||||||
{
|
{
|
||||||
name: 'bookmark-folder-edit',
|
name: 'bookmark-folder-edit',
|
||||||
path: '/bookmarks/:id/edit',
|
path: '/bookmarks/:id/edit',
|
||||||
component: defineAsyncComponent(
|
component: () =>
|
||||||
() =>
|
import('src/components/bookmark_folder_edit/bookmark_folder_edit.vue'),
|
||||||
import(
|
|
||||||
'src/components/bookmark_folder_edit/bookmark_folder_edit.vue'
|
|
||||||
),
|
|
||||||
),
|
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
@ -301,19 +251,16 @@ export default (store) => {
|
||||||
routes = routes.concat([
|
routes = routes.concat([
|
||||||
{
|
{
|
||||||
name: 'chat',
|
name: 'chat',
|
||||||
path: '/users/:username/chats/:recipient_id',
|
path: '/users/:username/chats/:chatUserId',
|
||||||
component: defineAsyncComponent(
|
component: () => import('src/components/chat_view/chat_view.vue'),
|
||||||
() => import('src/components/chat_view/chat_view.vue'),
|
|
||||||
),
|
|
||||||
meta: { dontScroll: false },
|
meta: { dontScroll: false },
|
||||||
|
props: true,
|
||||||
beforeEnter: validateAuthenticatedRoute,
|
beforeEnter: validateAuthenticatedRoute,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'chats',
|
name: 'chats',
|
||||||
path: '/users/:username/chats',
|
path: '/users/:username/chats',
|
||||||
component: defineAsyncComponent(
|
component: () => import('src/components/chat_list/chat_list.vue'),
|
||||||
() => import('src/components/chat_list/chat_list.vue'),
|
|
||||||
),
|
|
||||||
meta: { dontScroll: false },
|
meta: { dontScroll: false },
|
||||||
beforeEnter: validateAuthenticatedRoute,
|
beforeEnter: validateAuthenticatedRoute,
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -76,9 +76,7 @@ const ChatMessage = {
|
||||||
return !!this.message.user
|
return !!this.message.user
|
||||||
},
|
},
|
||||||
authorId() {
|
authorId() {
|
||||||
return this.isStatus
|
return this.isStatus ? this.message.user.id : this.message.account_id
|
||||||
? this.message.user.id
|
|
||||||
: this.message.account_id
|
|
||||||
},
|
},
|
||||||
author() {
|
author() {
|
||||||
return this.$store.getters.findUser(this.authorId)
|
return this.$store.getters.findUser(this.authorId)
|
||||||
|
|
|
||||||
|
|
@ -27,8 +27,8 @@ import {
|
||||||
readChat,
|
readChat,
|
||||||
sendChatMessage,
|
sendChatMessage,
|
||||||
} from 'src/api/chats.js'
|
} from 'src/api/chats.js'
|
||||||
import { WSConnectionStatus } from 'src/api/websocket.js'
|
|
||||||
import { fetchConversation, fetchStatus } from 'src/api/public.js'
|
import { fetchConversation, fetchStatus } from 'src/api/public.js'
|
||||||
|
import { WSConnectionStatus } from 'src/api/websocket.js'
|
||||||
|
|
||||||
import { library } from '@fortawesome/fontawesome-svg-core'
|
import { library } from '@fortawesome/fontawesome-svg-core'
|
||||||
import { faChevronDown, faChevronLeft } from '@fortawesome/free-solid-svg-icons'
|
import { faChevronDown, faChevronLeft } from '@fortawesome/free-solid-svg-icons'
|
||||||
|
|
@ -53,7 +53,14 @@ const Chat = {
|
||||||
PostStatusForm,
|
PostStatusForm,
|
||||||
},
|
},
|
||||||
props: {
|
props: {
|
||||||
statusId: String,
|
statusId: {
|
||||||
|
type: String,
|
||||||
|
default: null,
|
||||||
|
},
|
||||||
|
chatUserId: {
|
||||||
|
type: String,
|
||||||
|
default: null,
|
||||||
|
},
|
||||||
testMode: Boolean,
|
testMode: Boolean,
|
||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
|
|
@ -180,11 +187,20 @@ const Chat = {
|
||||||
},
|
},
|
||||||
$route: async function (newVal) {
|
$route: async function (newVal) {
|
||||||
if (this.messagesIndex[newVal.params.statusId]) {
|
if (this.messagesIndex[newVal.params.statusId]) {
|
||||||
const focused = document.getElementById(`chatmessage-${this.$route.params.statusId}`)
|
const focused = document.getElementById(
|
||||||
|
`chatmessage-${this.$route.params.statusId}`,
|
||||||
|
)
|
||||||
if (focused?.getBoundingClientRect == null) return
|
if (focused?.getBoundingClientRect == null) return
|
||||||
const bottomBoundary = window.innerHeight - this.$refs.footer.clientHeight
|
const bottomBoundary =
|
||||||
const topBoundary = this.$refs.header.clientHeight + document.getElementById('nav').clientHeight
|
window.innerHeight - this.$refs.footer.clientHeight
|
||||||
const margin = Number(window.getComputedStyle(this.$refs.messageList.$el).gap.replace('px',''))
|
const topBoundary =
|
||||||
|
this.$refs.header.clientHeight +
|
||||||
|
document.getElementById('nav').clientHeight
|
||||||
|
const margin = Number(
|
||||||
|
window
|
||||||
|
.getComputedStyle(this.$refs.messageList.$el)
|
||||||
|
.gap.replace('px', ''),
|
||||||
|
)
|
||||||
|
|
||||||
const rect = focused.getBoundingClientRect()
|
const rect = focused.getBoundingClientRect()
|
||||||
const scrollAmount = (() => {
|
const scrollAmount = (() => {
|
||||||
|
|
@ -293,7 +309,9 @@ const Chat = {
|
||||||
if (this.isConversation) {
|
if (this.isConversation) {
|
||||||
const [
|
const [
|
||||||
{ data: status },
|
{ data: status },
|
||||||
{ data: { ancestors, descendants } }
|
{
|
||||||
|
data: { ancestors, descendants },
|
||||||
|
},
|
||||||
] = await Promise.all([
|
] = await Promise.all([
|
||||||
fetchStatus({
|
fetchStatus({
|
||||||
id: this.statusId,
|
id: this.statusId,
|
||||||
|
|
@ -302,7 +320,7 @@ const Chat = {
|
||||||
fetchConversation({
|
fetchConversation({
|
||||||
id: this.statusId,
|
id: this.statusId,
|
||||||
credentials: useOAuthStore().token,
|
credentials: useOAuthStore().token,
|
||||||
})
|
}),
|
||||||
])
|
])
|
||||||
messages = [...ancestors, status, ...descendants]
|
messages = [...ancestors, status, ...descendants]
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -337,17 +355,21 @@ const Chat = {
|
||||||
// full height of the scrollable container.
|
// full height of the scrollable container.
|
||||||
// If this is the case, we want to fetch the messages until the scrollable container
|
// If this is the case, we want to fetch the messages until the scrollable container
|
||||||
// is fully populated so that the user has the ability to scroll up and load the history.
|
// is fully populated so that the user has the ability to scroll up and load the history.
|
||||||
if (!isScrollable() && messages.length > 0) {
|
//
|
||||||
|
// Conversation fetching doesn't support pagination and spews out everything at once
|
||||||
|
// so we both can't and don't need to fetch previous posts
|
||||||
|
if (!this.isConversation && !isScrollable() && messages.length > 0) {
|
||||||
this.fetchChat({
|
this.fetchChat({
|
||||||
maxId: this.minId,
|
maxId: this.minId,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
async startFetching() {
|
async startFetching() {
|
||||||
|
console.log(this.statusId, this.chatUserId)
|
||||||
if (!this.isConversation) {
|
if (!this.isConversation) {
|
||||||
try {
|
try {
|
||||||
const { data } = await getOrCreateChat({
|
const { data } = await getOrCreateChat({
|
||||||
accountId: this.recipientId,
|
accountId: this.chatUserId,
|
||||||
credentials: useOAuthStore().token,
|
credentials: useOAuthStore().token,
|
||||||
})
|
})
|
||||||
this.$store.commit('addNewUsers', [data.account])
|
this.$store.commit('addNewUsers', [data.account])
|
||||||
|
|
@ -378,7 +400,7 @@ const Chat = {
|
||||||
const message = newMessages[i]
|
const message = newMessages[i]
|
||||||
|
|
||||||
// Sanity check
|
// Sanity check
|
||||||
if (!this.isConversation && (message.chat_id !== this.chat.id)) {
|
if (!this.isConversation && message.chat_id !== this.chat.id) {
|
||||||
console.warn(
|
console.warn(
|
||||||
`Chat message doesn't belong to current chat (id: ${this.chat.id})!!`,
|
`Chat message doesn't belong to current chat (id: ${this.chat.id})!!`,
|
||||||
message,
|
message,
|
||||||
|
|
@ -516,7 +538,10 @@ const Chat = {
|
||||||
// Event handlers
|
// Event handlers
|
||||||
onPosted(data) {
|
onPosted(data) {
|
||||||
this.explicitReplyStatus = null
|
this.explicitReplyStatus = null
|
||||||
this.$router.push({ name: 'conversation2', params: { statusId: data.id } })
|
this.$router.push({
|
||||||
|
name: 'conversation2',
|
||||||
|
params: { statusId: data.id },
|
||||||
|
})
|
||||||
},
|
},
|
||||||
handleVisibilityChange() {
|
handleVisibilityChange() {
|
||||||
this.$nextTick(() => {
|
this.$nextTick(() => {
|
||||||
|
|
|
||||||
|
|
@ -67,7 +67,10 @@
|
||||||
</div>
|
</div>
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="auto-reply-to-section">
|
<div
|
||||||
|
v-if="isConversation"
|
||||||
|
class="auto-reply-to-section"
|
||||||
|
>
|
||||||
<div class="reply-to-text">
|
<div class="reply-to-text">
|
||||||
{{ explicitReplyStatus ? $t('status.reply_to_selected') : $t('status.reply_to_last') }}
|
{{ explicitReplyStatus ? $t('status.reply_to_selected') : $t('status.reply_to_last') }}
|
||||||
<button
|
<button
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
<template>
|
<template>
|
||||||
<div class="ExtraNotifications">
|
<div class="ExtraNotifications panel-body">
|
||||||
<div
|
<div
|
||||||
v-if="shouldShowChats"
|
v-if="shouldShowChats"
|
||||||
class="notification unseen"
|
class="notification unseen"
|
||||||
|
|
@ -87,6 +87,11 @@
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
align-items: stretch;
|
align-items: stretch;
|
||||||
|
|
||||||
|
&.panel-body::before {
|
||||||
|
content: '';
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
.notification {
|
.notification {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
border-bottom: 1px solid;
|
border-bottom: 1px solid;
|
||||||
|
|
|
||||||
|
|
@ -63,8 +63,8 @@ const LoginForm = {
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
.catch((error) => {
|
.catch((error) => {
|
||||||
if (error.errorData?.error === 'mfa_required') {
|
if (error.errorData === 'mfa_required') {
|
||||||
this.requireMFA({ settings: error })
|
this.requireMFA({ settings: error.error })
|
||||||
} else if (error.identifier === 'password_reset_required') {
|
} else if (error.identifier === 'password_reset_required') {
|
||||||
this.$router.push({
|
this.$router.push({
|
||||||
name: 'password-reset',
|
name: 'password-reset',
|
||||||
|
|
|
||||||
|
|
@ -44,7 +44,7 @@ export default {
|
||||||
}
|
}
|
||||||
|
|
||||||
verifyRecoveryCode(data)
|
verifyRecoveryCode(data)
|
||||||
.then((result) => {
|
.then(({ data: result }) => {
|
||||||
this.login(result).then(() => {
|
this.login(result).then(() => {
|
||||||
this.$router.push({ name: 'friends' })
|
this.$router.push({ name: 'friends' })
|
||||||
})
|
})
|
||||||
|
|
|
||||||
|
|
@ -690,6 +690,7 @@ const PostStatusForm = {
|
||||||
.then((data) => {
|
.then((data) => {
|
||||||
this.abandonDraft()
|
this.abandonDraft()
|
||||||
this.clearStatus()
|
this.clearStatus()
|
||||||
|
this.updateIdempotencyKey()
|
||||||
this.$emit('posted', data)
|
this.$emit('posted', data)
|
||||||
})
|
})
|
||||||
.catch((error) => {
|
.catch((error) => {
|
||||||
|
|
@ -1007,6 +1008,7 @@ const PostStatusForm = {
|
||||||
this.saveable = false
|
this.saveable = false
|
||||||
if (!this.shouldAutoSaveDraft) {
|
if (!this.shouldAutoSaveDraft) {
|
||||||
this.clearStatus()
|
this.clearStatus()
|
||||||
|
this.updateIdempotencyKey()
|
||||||
this.$emit('draft-done')
|
this.$emit('draft-done')
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
@ -1016,6 +1018,7 @@ const PostStatusForm = {
|
||||||
this.saveable = false
|
this.saveable = false
|
||||||
if (!this.shouldAutoSaveDraft) {
|
if (!this.shouldAutoSaveDraft) {
|
||||||
this.clearStatus()
|
this.clearStatus()
|
||||||
|
this.updateIdempotencyKey()
|
||||||
this.$emit('draft-done')
|
this.$emit('draft-done')
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
|
||||||
|
|
@ -82,13 +82,13 @@ const ScopeSelector = {
|
||||||
changeVis(scope) {
|
changeVis(scope) {
|
||||||
this.currentScope = scope
|
this.currentScope = scope
|
||||||
this.$emit('change', scope)
|
this.$emit('change', scope)
|
||||||
}
|
},
|
||||||
},
|
},
|
||||||
watch: {
|
watch: {
|
||||||
originalScope(newVal) {
|
originalScope(newVal) {
|
||||||
this.currentScope = newVal
|
this.currentScope = newVal
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
export default ScopeSelector
|
export default ScopeSelector
|
||||||
|
|
|
||||||
|
|
@ -68,7 +68,7 @@
|
||||||
:model-value="mergedConfig.fontInterface"
|
:model-value="mergedConfig.fontInterface"
|
||||||
name="ui"
|
name="ui"
|
||||||
:label="$t('settings.style.fonts.components_inline.interface')"
|
:label="$t('settings.style.fonts.components_inline.interface')"
|
||||||
:fallback="{ family: 'sans-serif' }"
|
fallback="sans-serif"
|
||||||
no-inherit="1"
|
no-inherit="1"
|
||||||
@update:model-value="v => updateFont('fontInterface', v)"
|
@update:model-value="v => updateFont('fontInterface', v)"
|
||||||
/>
|
/>
|
||||||
|
|
@ -77,7 +77,7 @@
|
||||||
<FontControl
|
<FontControl
|
||||||
:model-value="mergedConfig.fontInput"
|
:model-value="mergedConfig.fontInput"
|
||||||
name="input"
|
name="input"
|
||||||
:fallback="{ family: 'inherit' }"
|
fallback="inherit"
|
||||||
:label="$t('settings.style.fonts.components_inline.input')"
|
:label="$t('settings.style.fonts.components_inline.input')"
|
||||||
@update:model-value="v => updateFont('fontInput', v)"
|
@update:model-value="v => updateFont('fontInput', v)"
|
||||||
/>
|
/>
|
||||||
|
|
|
||||||
|
|
@ -58,7 +58,7 @@
|
||||||
<FontControl
|
<FontControl
|
||||||
:model-value="mergedConfig.fontPosts"
|
:model-value="mergedConfig.fontPosts"
|
||||||
name="post"
|
name="post"
|
||||||
:fallback="{ family: 'inherit' }"
|
fallback="inherit"
|
||||||
:label="$t('settings.style.fonts.components.post')"
|
:label="$t('settings.style.fonts.components.post')"
|
||||||
@update:model-value="v => updateFont('fontPosts', v)"
|
@update:model-value="v => updateFont('fontPosts', v)"
|
||||||
/>
|
/>
|
||||||
|
|
@ -67,7 +67,7 @@
|
||||||
<FontControl
|
<FontControl
|
||||||
:model-value="mergedConfig.fontMonospace"
|
:model-value="mergedConfig.fontMonospace"
|
||||||
name="postCode"
|
name="postCode"
|
||||||
:fallback="{ family: 'monospace' }"
|
fallback="monospace"
|
||||||
:label="$t('settings.style.fonts.components.monospace')"
|
:label="$t('settings.style.fonts.components.monospace')"
|
||||||
@update:model-value="v => updateFont('fontMonospace', v)"
|
@update:model-value="v => updateFont('fontMonospace', v)"
|
||||||
/>
|
/>
|
||||||
|
|
|
||||||
|
|
@ -17,21 +17,21 @@ import {
|
||||||
faCheck,
|
faCheck,
|
||||||
faChevronDown,
|
faChevronDown,
|
||||||
faChevronRight,
|
faChevronRight,
|
||||||
|
faComments,
|
||||||
faExternalLinkAlt,
|
faExternalLinkAlt,
|
||||||
faEye,
|
faEye,
|
||||||
faEyeSlash,
|
faEyeSlash,
|
||||||
faHistory,
|
faHistory,
|
||||||
faMinus,
|
faMinus,
|
||||||
|
faPencil,
|
||||||
faPlus,
|
faPlus,
|
||||||
faReply,
|
faReply,
|
||||||
faRetweet,
|
faRetweet,
|
||||||
faShareAlt,
|
faShareAlt,
|
||||||
faStar,
|
faStar,
|
||||||
faThumbtack,
|
faThumbtack,
|
||||||
faPencil,
|
|
||||||
faTimes,
|
faTimes,
|
||||||
faWrench,
|
faWrench,
|
||||||
faComments,
|
|
||||||
} from '@fortawesome/free-solid-svg-icons'
|
} from '@fortawesome/free-solid-svg-icons'
|
||||||
|
|
||||||
library.add(
|
library.add(
|
||||||
|
|
|
||||||
|
|
@ -32,16 +32,17 @@ export const createStyleSheet = (id, priority = 1000) => {
|
||||||
newRule = newRule.replace(/backdrop-filter:[^;]+;/g, '') // Remove backdrop-filter
|
newRule = newRule.replace(/backdrop-filter:[^;]+;/g, '') // Remove backdrop-filter
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (newRule.startsWith('::-webkit')) {
|
||||||
|
if (typeof CSS.supports !== 'function') return
|
||||||
// firefox doesn't like invalid selectors
|
// firefox doesn't like invalid selectors
|
||||||
if (
|
const fullWebkitScrollbarSupport =
|
||||||
!CSS.supports?.('selector(::-webkit-scrollbar)') &&
|
CSS.supports('selector(::-webkit-scrollbar)') &&
|
||||||
!CSS.supports?.('selector(::-webkit-scrollbar-button)') &&
|
CSS.supports('selector(::-webkit-scrollbar-button)') &&
|
||||||
!CSS.supports?.('selector(::-webkit-resizer)') &&
|
CSS.supports('selector(::-webkit-resizer)') &&
|
||||||
!CSS.supports?.('selector(::-webkit-scrollbar-thumb)') &&
|
CSS.supports('selector(::-webkit-scrollbar-thumb)')
|
||||||
newRule.startsWith('::-webkit')
|
if (!fullWebkitScrollbarSupport) return
|
||||||
) {
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
this.rules.push(
|
this.rules.push(
|
||||||
newRule.replace(/var\(--shadowFilter\)[^;]*;/g, ''), // Remove shadowFilter references
|
newRule.replace(/var\(--shadowFilter\)[^;]*;/g, ''), // Remove shadowFilter references
|
||||||
)
|
)
|
||||||
|
|
@ -59,7 +60,13 @@ export const adoptStyleSheets = throttle(() => {
|
||||||
.sort((a, b) => a.priority - b.priority)
|
.sort((a, b) => a.priority - b.priority)
|
||||||
.map((sheet) => {
|
.map((sheet) => {
|
||||||
const css = new CSSStyleSheet()
|
const css = new CSSStyleSheet()
|
||||||
sheet.rules.forEach((r) => css.insertRule(r))
|
sheet.rules.forEach((r) => {
|
||||||
|
try {
|
||||||
|
css.insertRule(r)
|
||||||
|
} catch (e) {
|
||||||
|
console.warn('Error inserting rule:', e, r)
|
||||||
|
}
|
||||||
|
})
|
||||||
return css
|
return css
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
|
|
|
||||||
|
|
@ -17,6 +17,16 @@ import {
|
||||||
} from 'src/services/theme_data/theme_data.service.js'
|
} from 'src/services/theme_data/theme_data.service.js'
|
||||||
import { convertTheme2To3 } from 'src/services/theme_data/theme2_to_theme3.js'
|
import { convertTheme2To3 } from 'src/services/theme_data/theme2_to_theme3.js'
|
||||||
|
|
||||||
|
const GENERIC_FONT_NAMES = new Set([
|
||||||
|
'serif',
|
||||||
|
'sans-serif',
|
||||||
|
'system-ui',
|
||||||
|
'cursive',
|
||||||
|
'fantasy',
|
||||||
|
'math',
|
||||||
|
'monospace',
|
||||||
|
])
|
||||||
|
|
||||||
export const useInterfaceStore = defineStore('interface', {
|
export const useInterfaceStore = defineStore('interface', {
|
||||||
state: () => ({
|
state: () => ({
|
||||||
localFonts: null,
|
localFonts: null,
|
||||||
|
|
@ -246,7 +256,7 @@ export const useInterfaceStore = defineStore('interface', {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
setFontsList(value) {
|
setFontsList(value) {
|
||||||
this.localFonts = [...new Set(value.map((font) => font)).values()]
|
this.localFonts = [...new Set(value.map(({ family }) => family)).values()]
|
||||||
},
|
},
|
||||||
queryLocalFonts() {
|
queryLocalFonts() {
|
||||||
if (this.localFonts !== null) return
|
if (this.localFonts !== null) return
|
||||||
|
|
@ -706,11 +716,14 @@ export const useInterfaceStore = defineStore('interface', {
|
||||||
Object.entries(fontMap).forEach(([font, component]) => {
|
Object.entries(fontMap).forEach(([font, component]) => {
|
||||||
const family = mergedConfig[`font${font}`]
|
const family = mergedConfig[`font${font}`]
|
||||||
const variable = font === 'Monospace' ? '--monoFont' : '--font'
|
const variable = font === 'Monospace' ? '--monoFont' : '--font'
|
||||||
if (family) {
|
if (typeof family === 'string') {
|
||||||
|
const familyString = GENERIC_FONT_NAMES.has(family)
|
||||||
|
? family
|
||||||
|
: `"${family}"`
|
||||||
hacks.push({
|
hacks.push({
|
||||||
component,
|
component,
|
||||||
directives: {
|
directives: {
|
||||||
[variable]: `generic | "${family}"`,
|
[variable]: `generic | ${familyString}`,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue