Merge remote-tracking branch 'origin/develop' into timed-user-mutes

This commit is contained in:
Henry Jameson 2025-07-09 15:57:23 +03:00
commit 385f921c41
72 changed files with 1336 additions and 851 deletions

View file

@ -11,6 +11,11 @@ const getSWMessagesAsText = async () => {
} }
const projectRoot = dirname(dirname(fileURLToPath(import.meta.url))) const projectRoot = dirname(dirname(fileURLToPath(import.meta.url)))
const swEnvName = 'virtual:pleroma-fe/service_worker_env'
const swEnvNameResolved = '\0' + swEnvName
const getDevSwEnv = () => `self.serviceWorkerOption = { assets: [] };`
const getProdSwEnv = ({ assets }) => `self.serviceWorkerOption = { assets: ${JSON.stringify(assets)} };`
export const devSwPlugin = ({ export const devSwPlugin = ({
swSrc, swSrc,
swDest, swDest,
@ -32,12 +37,16 @@ export const devSwPlugin = ({
const name = id.startsWith('/') ? id.slice(1) : id const name = id.startsWith('/') ? id.slice(1) : id
if (name === swDest) { if (name === swDest) {
return swFullSrc return swFullSrc
} else if (name === swEnvName) {
return swEnvNameResolved
} }
return null return null
}, },
async load (id) { async load (id) {
if (id === swFullSrc) { if (id === swFullSrc) {
return readFile(swFullSrc, 'utf-8') return readFile(swFullSrc, 'utf-8')
} else if (id === swEnvNameResolved) {
return getDevSwEnv()
} }
return null return null
}, },
@ -79,6 +88,21 @@ export const devSwPlugin = ({
contents: await getSWMessagesAsText() contents: await getSWMessagesAsText()
})) }))
} }
}, {
name: 'sw-env',
setup (b) {
b.onResolve(
{ filter: new RegExp('^' + swEnvName + '$') },
args => ({
path: args.path,
namespace: 'sw-env'
}))
b.onLoad(
{ filter: /.*/, namespace: 'sw-env' },
() => ({
contents: getDevSwEnv()
}))
}
}] }]
}) })
const text = res.outputFiles[0].text const text = res.outputFiles[0].text
@ -126,6 +150,30 @@ export const buildSwPlugin = ({
configFile: false configFile: false
} }
}, },
generateBundle: {
order: 'post',
sequential: true,
async handler (_, bundle) {
const assets = Object.keys(bundle)
.filter(name => !/\.map$/.test(name))
.map(name => '/' + name)
config.plugins.push({
name: 'build-sw-env-plugin',
resolveId (id) {
if (id === swEnvName) {
return swEnvNameResolved
}
return null
},
load (id) {
if (id === swEnvNameResolved) {
return getProdSwEnv({ assets })
}
return null
}
})
}
},
closeBundle: { closeBundle: {
order: 'post', order: 'post',
sequential: true, sequential: true,

View file

@ -0,0 +1 @@
Display counter for status action buttons when they are on the menu

View file

View file

@ -0,0 +1 @@
Added support for Akkoma and IceShrimp.NET backend

0
changelog.d/akkoma.skip Normal file
View file

View file

@ -0,0 +1,2 @@
Add arithmetic blend ISS function

View file

@ -0,0 +1 @@
Fix bookmark button alignment in the extra actions menu

1
changelog.d/csp.add Normal file
View file

@ -0,0 +1 @@
Compatibility with stricter CSP (Akkoma backend)

View file

View file

View file

@ -0,0 +1 @@
Cache assets and emojis with service worker

View file

@ -0,0 +1 @@
Unify show/hide content buttons

0
changelog.d/zoomlag.skip Normal file
View file

View file

@ -5,138 +5,16 @@
<meta name="viewport" content="width=device-width, initial-scale=1,user-scalable=no"> <meta name="viewport" content="width=device-width, initial-scale=1,user-scalable=no">
<link rel="preload" href="/static/config.json" as="fetch" crossorigin /> <link rel="preload" href="/static/config.json" as="fetch" crossorigin />
<link rel="preload" href="/api/pleroma/frontend_configurations" as="fetch" crossorigin /> <link rel="preload" href="/api/pleroma/frontend_configurations" as="fetch" crossorigin />
<link rel="preload" href="/nodeinfo/2.0.json" as="fetch" crossorigin />
<link rel="preload" href="/nodeinfo/2.1.json" as="fetch" crossorigin /> <link rel="preload" href="/nodeinfo/2.1.json" as="fetch" crossorigin />
<link rel="preload" href="/api/v1/instance" as="fetch" crossorigin /> <link rel="preload" href="/api/v1/instance" as="fetch" crossorigin />
<link rel="preload" href="/static/pleromatan_apology_fox_small.webp" as="image" /> <link rel="preload" href="/static/pleromatan_apology_fox_small.webp" as="image" />
<!-- putting styles here to avoid having to wait for styles to load up --> <!-- putting styles here to avoid having to wait for styles to load up -->
<style id="splashscreen"> <link rel="stylesheet" id="splashscreen" href="/static/splash.css" />
#splash { <link rel="stylesheet" id="custom-styles-holder" type="text/css" href="/static/empty.css" />
--scale: 1;
width: 100vw;
height: 100vh;
display: grid;
grid-template-rows: auto;
grid-template-columns: auto;
align-content: center;
align-items: center;
justify-content: center;
justify-items: center;
flex-direction: column;
background: #0f161e;
font-family: sans-serif;
color: #b9b9ba;
position: absolute;
z-index: 9999;
font-size: calc(1vw + 1vh + 1vmin);
}
#splash-credit {
position: absolute;
font-size: 14px;
bottom: 16px;
right: 16px;
}
#splash-container {
align-items: center;
}
#mascot-container {
display: flex;
align-items: flex-end;
justify-content: center;
perspective: 60em;
perspective-origin: 0 -15em;
transform-style: preserve-3d;
}
#mascot {
width: calc(10em * var(--scale));
height: calc(10em * var(--scale));
object-fit: contain;
object-position: bottom;
transform: translateZ(-2em);
}
#throbber {
display: grid;
width: calc(5em * 0.5 * var(--scale));
height: calc(8em * 0.5 * var(--scale));
margin-left: 4.1em;
z-index: 2;
grid-template-rows: repeat(8, 1fr);
grid-template-columns: repeat(5, 1fr);
grid-template-areas: "P P . L L"
"P P . L L"
"P P . L L"
"P P . L L"
"P P . . ."
"P P . . ."
"P P . E E"
"P P . E E";
--logoChunkSize: calc(2em * 0.5 * var(--scale))
}
.chunk {
background-color: #e2b188;
box-shadow: 0.01em 0.01em 0.1em 0 #e2b188;
}
#chunk-P {
grid-area: P;
border-top-left-radius: calc(var(--logoChunkSize) / 2);
}
#chunk-L {
grid-area: L;
border-bottom-right-radius: calc(var(--logoChunkSize) / 2);
}
#chunk-E {
grid-area: E;
border-bottom-right-radius: calc(var(--logoChunkSize) / 2);
}
#status {
margin-top: 1em;
line-height: 2;
width: 100%;
text-align: center;
}
#statusError {
display: none;
margin-top: 1em;
font-size: calc(1vw + 1vh + 1vmin);
line-height: 2;
width: 100%;
text-align: center;
}
#statusStack {
display: none;
margin-top: 1em;
font-size: calc((1vw + 1vh + 1vmin) / 2.5);
width: calc(100vw - 5em);
padding: 1em;
text-overflow: ellipsis;
overflow-x: hidden;
text-align: left;
line-height: 2;
}
@media (prefers-reduced-motion) {
#throbber {
animation: none !important;
}
}
</style>
<style id="pleroma-eager-styles" type="text/css"></style>
<style id="pleroma-lazy-styles" type="text/css"></style>
<!--server-generated-meta--> <!--server-generated-meta-->
</head> </head>
<body style="margin: 0; padding: 0"> <body>
<noscript>To use Pleroma, please enable JavaScript.</noscript> <noscript>To use Pleroma, please enable JavaScript.</noscript>
<div id="splash"> <div id="splash">
<!-- we are hiding entire graphic so no point showing credit --> <!-- we are hiding entire graphic so no point showing credit -->

View file

@ -23,7 +23,7 @@
"@fortawesome/free-regular-svg-icons": "6.7.2", "@fortawesome/free-regular-svg-icons": "6.7.2",
"@fortawesome/free-solid-svg-icons": "6.7.2", "@fortawesome/free-solid-svg-icons": "6.7.2",
"@fortawesome/vue-fontawesome": "3.0.8", "@fortawesome/vue-fontawesome": "3.0.8",
"@kazvmoe-infra/pinch-zoom-element": "1.2.0", "@kazvmoe-infra/pinch-zoom-element": "1.3.0",
"@kazvmoe-infra/unicode-emoji-json": "0.4.0", "@kazvmoe-infra/unicode-emoji-json": "0.4.0",
"@ruffle-rs/ruffle": "0.1.0-nightly.2025.1.13", "@ruffle-rs/ruffle": "0.1.0-nightly.2025.1.13",
"@vuelidate/core": "2.0.3", "@vuelidate/core": "2.0.3",
@ -47,7 +47,7 @@
"url": "0.11.4", "url": "0.11.4",
"utf8": "3.0.0", "utf8": "3.0.0",
"uuid": "11.1.0", "uuid": "11.1.0",
"vue": "3.5.13", "vue": "3.5.17",
"vue-i18n": "11", "vue-i18n": "11",
"vue-router": "4.5.1", "vue-router": "4.5.1",
"vue-virtual-scroller": "^2.0.0-beta.7", "vue-virtual-scroller": "^2.0.0-beta.7",
@ -66,7 +66,7 @@
"@vitest/ui": "^3.0.7", "@vitest/ui": "^3.0.7",
"@vue/babel-helper-vue-jsx-merge-props": "1.4.0", "@vue/babel-helper-vue-jsx-merge-props": "1.4.0",
"@vue/babel-plugin-jsx": "1.4.0", "@vue/babel-plugin-jsx": "1.4.0",
"@vue/compiler-sfc": "3.5.13", "@vue/compiler-sfc": "3.5.17",
"@vue/test-utils": "2.4.6", "@vue/test-utils": "2.4.6",
"autoprefixer": "10.4.21", "autoprefixer": "10.4.21",
"babel-plugin-lodash": "3.3.4", "babel-plugin-lodash": "3.3.4",
@ -90,17 +90,17 @@
"http-proxy-middleware": "3.0.5", "http-proxy-middleware": "3.0.5",
"iso-639-1": "3.1.5", "iso-639-1": "3.1.5",
"lodash": "4.17.21", "lodash": "4.17.21",
"msw": "2.7.6", "msw": "2.10.2",
"nightwatch": "3.12.1", "nightwatch": "3.12.1",
"playwright": "1.52.0", "playwright": "1.52.0",
"postcss": "8.5.3", "postcss": "8.5.3",
"postcss-html": "^1.5.0", "postcss-html": "^1.5.0",
"postcss-scss": "^4.0.6", "postcss-scss": "^4.0.6",
"sass": "1.87.0", "sass": "1.89.2",
"selenium-server": "3.141.59", "selenium-server": "3.141.59",
"semver": "7.7.1", "semver": "7.7.2",
"serve-static": "2.2.0", "serve-static": "2.2.0",
"shelljs": "0.9.2", "shelljs": "0.10.0",
"sinon": "20.0.0", "sinon": "20.0.0",
"sinon-chai": "4.0.0", "sinon-chai": "4.0.0",
"stylelint": "16.19.1", "stylelint": "16.19.1",

126
public/static/splash.css Normal file
View file

@ -0,0 +1,126 @@
body {
margin: 0;
padding: 0;
}
#splash {
--scale: 1;
width: 100vw;
height: 100vh;
display: grid;
grid-template-rows: auto;
grid-template-columns: auto;
align-content: center;
align-items: center;
justify-content: center;
justify-items: center;
flex-direction: column;
background: #0f161e;
font-family: sans-serif;
color: #b9b9ba;
position: absolute;
z-index: 9999;
font-size: calc(1vw + 1vh + 1vmin);
}
#splash-credit {
position: absolute;
font-size: 14px;
bottom: 16px;
right: 16px;
}
#splash-container {
align-items: center;
}
#mascot-container {
display: flex;
align-items: flex-end;
justify-content: center;
perspective: 60em;
perspective-origin: 0 -15em;
transform-style: preserve-3d;
}
#mascot {
width: calc(10em * var(--scale));
height: calc(10em * var(--scale));
object-fit: contain;
object-position: bottom;
transform: translateZ(-2em);
}
#throbber {
display: grid;
width: calc(5em * 0.5 * var(--scale));
height: calc(8em * 0.5 * var(--scale));
margin-left: 4.1em;
z-index: 2;
grid-template-rows: repeat(8, 1fr);
grid-template-columns: repeat(5, 1fr);
grid-template-areas: "P P . L L"
"P P . L L"
"P P . L L"
"P P . L L"
"P P . . ."
"P P . . ."
"P P . E E"
"P P . E E";
--logoChunkSize: calc(2em * 0.5 * var(--scale))
}
.chunk {
background-color: #e2b188;
box-shadow: 0.01em 0.01em 0.1em 0 #e2b188;
}
#chunk-P {
grid-area: P;
border-top-left-radius: calc(var(--logoChunkSize) / 2);
}
#chunk-L {
grid-area: L;
border-bottom-right-radius: calc(var(--logoChunkSize) / 2);
}
#chunk-E {
grid-area: E;
border-bottom-right-radius: calc(var(--logoChunkSize) / 2);
}
#status {
margin-top: 1em;
line-height: 2;
width: 100%;
text-align: center;
}
#statusError {
display: none;
margin-top: 1em;
font-size: calc(1vw + 1vh + 1vmin);
line-height: 2;
width: 100%;
text-align: center;
}
#statusStack {
display: none;
margin-top: 1em;
font-size: calc((1vw + 1vh + 1vmin) / 2.5);
width: calc(100vw - 5em);
padding: 1em;
text-overflow: ellipsis;
overflow-x: hidden;
text-align: left;
line-height: 2;
}
@media (prefers-reduced-motion) {
#throbber {
animation: none !important;
}
}

View file

@ -14,6 +14,7 @@ import EditStatusModal from './components/edit_status_modal/edit_status_modal.vu
import PostStatusModal from './components/post_status_modal/post_status_modal.vue' import PostStatusModal from './components/post_status_modal/post_status_modal.vue'
import StatusHistoryModal from './components/status_history_modal/status_history_modal.vue' import StatusHistoryModal from './components/status_history_modal/status_history_modal.vue'
import GlobalNoticeList from './components/global_notice_list/global_notice_list.vue' import GlobalNoticeList from './components/global_notice_list/global_notice_list.vue'
import { getOrCreateServiceWorker } from './services/sw/sw'
import { windowWidth, windowHeight } from './services/window_utils/window_utils' import { windowWidth, windowHeight } from './services/window_utils/window_utils'
import { mapGetters } from 'vuex' import { mapGetters } from 'vuex'
import { defineAsyncComponent } from 'vue' import { defineAsyncComponent } from 'vue'
@ -77,6 +78,7 @@ export default {
this.setThemeBodyClass() this.setThemeBodyClass()
this.removeSplash() this.removeSplash()
} }
getOrCreateServiceWorker()
}, },
unmounted () { unmounted () {
window.removeEventListener('resize', this.updateMobileState) window.removeEventListener('resize', this.updateMobileState)

View file

@ -2,6 +2,9 @@
/* stylelint-disable no-descending-specificity */ /* stylelint-disable no-descending-specificity */
@use "panel"; @use "panel";
@import '@fortawesome/fontawesome-svg-core/styles.css';
@import '@kazvmoe-infra/pinch-zoom-element/dist/pinch-zoom.css';
:root { :root {
--status-margin: 0.75em; --status-margin: 0.75em;
--post-line-height: 1.4; --post-line-height: 1.4;
@ -30,6 +33,7 @@ body {
font-family: sans-serif; font-family: sans-serif;
font-family: var(--font); font-family: var(--font);
margin: 0; margin: 0;
padding: 0;
color: var(--text); color: var(--text);
-webkit-font-smoothing: antialiased; -webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale; -moz-osx-font-smoothing: grayscale;

View file

@ -6,6 +6,8 @@ import VueVirtualScroller from 'vue-virtual-scroller'
import 'vue-virtual-scroller/dist/vue-virtual-scroller.css' import 'vue-virtual-scroller/dist/vue-virtual-scroller.css'
import { FontAwesomeIcon, FontAwesomeLayers } from '@fortawesome/vue-fontawesome' import { FontAwesomeIcon, FontAwesomeLayers } from '@fortawesome/vue-fontawesome'
import { config } from '@fortawesome/fontawesome-svg-core';
config.autoAddCss = false
import App from '../App.vue' import App from '../App.vue'
import routes from './routes' import routes from './routes'
@ -21,6 +23,7 @@ import { useOAuthStore } from 'src/stores/oauth'
import { useI18nStore } from 'src/stores/i18n' import { useI18nStore } from 'src/stores/i18n'
import { useInterfaceStore } from 'src/stores/interface' import { useInterfaceStore } from 'src/stores/interface'
import { useAnnouncementsStore } from 'src/stores/announcements' import { useAnnouncementsStore } from 'src/stores/announcements'
import { useAuthFlowStore } from 'src/stores/auth_flow'
let staticInitialResults = null let staticInitialResults = null
@ -63,10 +66,11 @@ const getInstanceConfig = async ({ store }) => {
const textlimit = data.max_toot_chars const textlimit = data.max_toot_chars
const vapidPublicKey = data.pleroma.vapid_public_key const vapidPublicKey = data.pleroma.vapid_public_key
store.dispatch('setInstanceOption', { name: 'pleromaExtensionsAvailable', value: data.pleroma })
store.dispatch('setInstanceOption', { name: 'textlimit', value: textlimit }) store.dispatch('setInstanceOption', { name: 'textlimit', value: textlimit })
store.dispatch('setInstanceOption', { name: 'accountApprovalRequired', value: data.approval_required }) store.dispatch('setInstanceOption', { name: 'accountApprovalRequired', value: data.approval_required })
store.dispatch('setInstanceOption', { name: 'birthdayRequired', value: !!data.pleroma.metadata.birthday_required }) store.dispatch('setInstanceOption', { name: 'birthdayRequired', value: !!data.pleroma?.metadata.birthday_required })
store.dispatch('setInstanceOption', { name: 'birthdayMinAge', value: data.pleroma.metadata.birthday_min_age || 0 }) store.dispatch('setInstanceOption', { name: 'birthdayMinAge', value: data.pleroma?.metadata.birthday_min_age || 0 })
if (vapidPublicKey) { if (vapidPublicKey) {
store.dispatch('setInstanceOption', { name: 'vapidPublicKey', value: vapidPublicKey }) store.dispatch('setInstanceOption', { name: 'vapidPublicKey', value: vapidPublicKey })
@ -78,6 +82,8 @@ const getInstanceConfig = async ({ store }) => {
console.error('Could not load instance config, potentially fatal') console.error('Could not load instance config, potentially fatal')
console.error(error) console.error(error)
} }
// We should check for scrobbles support here but it requires userId
// so instead we check for it where it's fetched (statuses.js)
} }
const getBackendProvidedConfig = async () => { const getBackendProvidedConfig = async () => {
@ -153,7 +159,7 @@ const setSettings = async ({ apiConfig, staticConfig, store }) => {
: config.logoMargin : config.logoMargin
}) })
copyInstanceOption('logoLeft') copyInstanceOption('logoLeft')
store.commit('authFlow/setInitialStrategy', config.loginMethod) useAuthFlowStore().setInitialStrategy(config.loginMethod)
copyInstanceOption('redirectRootNoLogin') copyInstanceOption('redirectRootNoLogin')
copyInstanceOption('redirectRootLogin') copyInstanceOption('redirectRootLogin')
@ -242,7 +248,8 @@ const resolveStaffAccounts = ({ store, accounts }) => {
const getNodeInfo = async ({ store }) => { const getNodeInfo = async ({ store }) => {
try { try {
const res = await preloadFetch('/nodeinfo/2.1.json') let res = await preloadFetch('/nodeinfo/2.1.json')
if (!res.ok) res = await preloadFetch('/nodeinfo/2.0.json')
if (res.ok) { if (res.ok) {
const data = await res.json() const data = await res.json()
const metadata = data.metadata const metadata = data.metadata
@ -254,7 +261,12 @@ const getNodeInfo = async ({ store }) => {
store.dispatch('setInstanceOption', { name: 'safeDM', value: features.includes('safe_dm_mentions') }) store.dispatch('setInstanceOption', { name: 'safeDM', value: features.includes('safe_dm_mentions') })
store.dispatch('setInstanceOption', { name: 'shoutAvailable', value: features.includes('chat') }) store.dispatch('setInstanceOption', { name: 'shoutAvailable', value: features.includes('chat') })
store.dispatch('setInstanceOption', { name: 'pleromaChatMessagesAvailable', value: features.includes('pleroma_chat_messages') }) store.dispatch('setInstanceOption', { name: 'pleromaChatMessagesAvailable', value: features.includes('pleroma_chat_messages') })
store.dispatch('setInstanceOption', { name: 'pleromaCustomEmojiReactionsAvailable', value: features.includes('pleroma_custom_emoji_reactions') }) store.dispatch('setInstanceOption', {
name: 'pleromaCustomEmojiReactionsAvailable',
value:
features.includes('pleroma_custom_emoji_reactions') ||
features.includes('custom_emoji_reactions')
})
store.dispatch('setInstanceOption', { name: 'pleromaBookmarkFoldersAvailable', value: features.includes('pleroma:bookmark_folders') }) store.dispatch('setInstanceOption', { name: 'pleromaBookmarkFoldersAvailable', value: features.includes('pleroma:bookmark_folders') })
store.dispatch('setInstanceOption', { name: 'gopherAvailable', value: features.includes('gopher') }) store.dispatch('setInstanceOption', { name: 'gopherAvailable', value: features.includes('gopher') })
store.dispatch('setInstanceOption', { name: 'pollsAvailable', value: features.includes('polls') }) store.dispatch('setInstanceOption', { name: 'pollsAvailable', value: features.includes('polls') })
@ -264,6 +276,7 @@ const getNodeInfo = async ({ store }) => {
store.dispatch('setInstanceOption', { name: 'quotingAvailable', value: features.includes('quote_posting') }) store.dispatch('setInstanceOption', { name: 'quotingAvailable', value: features.includes('quote_posting') })
store.dispatch('setInstanceOption', { name: 'groupActorAvailable', value: features.includes('pleroma:group_actors') }) store.dispatch('setInstanceOption', { name: 'groupActorAvailable', value: features.includes('pleroma:group_actors') })
store.dispatch('setInstanceOption', { name: 'blockExpiration', value: features.includes('pleroma:block_expiration') }) store.dispatch('setInstanceOption', { name: 'blockExpiration', value: features.includes('pleroma:block_expiration') })
store.dispatch('setInstanceOption', { name: 'localBubbleInstances', value: metadata.localBubbleInstances ?? [] })
const uploadLimits = metadata.uploadLimits const uploadLimits = metadata.uploadLimits
store.dispatch('setInstanceOption', { name: 'uploadlimit', value: parseInt(uploadLimits.general) }) store.dispatch('setInstanceOption', { name: 'uploadlimit', value: parseInt(uploadLimits.general) })
@ -282,7 +295,6 @@ const getNodeInfo = async ({ store }) => {
const software = data.software const software = data.software
store.dispatch('setInstanceOption', { name: 'backendVersion', value: software.version }) store.dispatch('setInstanceOption', { name: 'backendVersion', value: software.version })
store.dispatch('setInstanceOption', { name: 'backendRepository', value: software.repository }) store.dispatch('setInstanceOption', { name: 'backendRepository', value: software.repository })
store.dispatch('setInstanceOption', { name: 'pleromaBackend', value: software.name === 'pleroma' })
const priv = metadata.private const priv = metadata.private
store.dispatch('setInstanceOption', { name: 'private', value: priv }) store.dispatch('setInstanceOption', { name: 'private', value: priv })

View file

@ -1,4 +1,5 @@
import PublicTimeline from 'components/public_timeline/public_timeline.vue' import PublicTimeline from 'components/public_timeline/public_timeline.vue'
import BubbleTimeline from 'components/bubble_timeline/bubble_timeline.vue'
import PublicAndExternalTimeline from 'components/public_and_external_timeline/public_and_external_timeline.vue' import PublicAndExternalTimeline from 'components/public_and_external_timeline/public_and_external_timeline.vue'
import FriendsTimeline from 'components/friends_timeline/friends_timeline.vue' import FriendsTimeline from 'components/friends_timeline/friends_timeline.vue'
import TagTimeline from 'components/tag_timeline/tag_timeline.vue' import TagTimeline from 'components/tag_timeline/tag_timeline.vue'
@ -54,6 +55,7 @@ export default (store) => {
{ name: 'friends', path: '/main/friends', component: FriendsTimeline, beforeEnter: validateAuthenticatedRoute }, { name: 'friends', path: '/main/friends', component: FriendsTimeline, beforeEnter: validateAuthenticatedRoute },
{ name: 'tag-timeline', path: '/tag/:tag', component: TagTimeline }, { name: 'tag-timeline', path: '/tag/:tag', component: TagTimeline },
{ name: 'bookmarks', path: '/bookmarks', component: BookmarkTimeline }, { name: 'bookmarks', path: '/bookmarks', component: BookmarkTimeline },
{ name: 'bubble', path: '/bubble', component: BubbleTimeline },
{ name: 'conversation', path: '/notice/:id', component: ConversationPage, meta: { dontScroll: true } }, { name: 'conversation', path: '/notice/:id', component: ConversationPage, meta: { dontScroll: true } },
{ name: 'quotes', path: '/notice/:id/quotes', component: QuotesTimeline }, { name: 'quotes', path: '/notice/:id/quotes', component: QuotesTimeline },
{ {

View file

@ -2,7 +2,8 @@ import { h, resolveComponent } from 'vue'
import LoginForm from '../login_form/login_form.vue' import LoginForm from '../login_form/login_form.vue'
import MFARecoveryForm from '../mfa_form/recovery_form.vue' import MFARecoveryForm from '../mfa_form/recovery_form.vue'
import MFATOTPForm from '../mfa_form/totp_form.vue' import MFATOTPForm from '../mfa_form/totp_form.vue'
import { mapGetters } from 'vuex' import { mapState } from 'pinia'
import { useAuthFlowStore } from 'src/stores/auth_flow'
const AuthForm = { const AuthForm = {
name: 'AuthForm', name: 'AuthForm',
@ -15,7 +16,7 @@ const AuthForm = {
if (this.requiredRecovery) { return 'MFARecoveryForm' } if (this.requiredRecovery) { return 'MFARecoveryForm' }
return 'LoginForm' return 'LoginForm'
}, },
...mapGetters('authFlow', ['requiredTOTP', 'requiredRecovery']) ...mapState(useAuthFlowStore, ['requiredTOTP', 'requiredRecovery'])
}, },
components: { components: {
MFARecoveryForm, MFARecoveryForm,

View file

@ -0,0 +1,18 @@
import Timeline from '../timeline/timeline.vue'
const BubbleTimeline = {
components: {
Timeline
},
computed: {
timeline () { return this.$store.state.statuses.timelines.bubble }
},
created () {
this.$store.dispatch('startFetchingTimeline', { timeline: 'bubble' })
},
unmounted () {
this.$store.dispatch('stopFetchingTimeline', 'bubble')
}
}
export default BubbleTimeline

View file

@ -0,0 +1,9 @@
<template>
<Timeline
:title="$t('nav.bubble')"
:timeline="timeline"
:timeline-name="'bubble'"
/>
</template>
<script src="./bubble_timeline.js"></script>

View file

@ -26,7 +26,7 @@
class="textColor unstyled" class="textColor unstyled"
:class="{ disabled: !present || disabled }" :class="{ disabled: !present || disabled }"
type="text" type="text"
:value="modelValue || fallback" :value="modelValue ?? fallback"
:disabled="!present || disabled" :disabled="!present || disabled"
@input="updateValue($event.target.value)" @input="updateValue($event.target.value)"
> >

View file

@ -0,0 +1,82 @@
import Checkbox from 'src/components/checkbox/checkbox.vue'
import ColorInput from 'src/components/color_input/color_input.vue'
import genRandomSeed from 'src/services/random_seed/random_seed.service.js'
import { createStyleSheet, adoptStyleSheets } from 'src/services/style_setter/style_setter.js'
export default {
components: {
Checkbox,
ColorInput
},
props: [
'shadow',
'shadowControl',
'previewClass',
'previewStyle',
'previewCss',
'disabled',
'invalid',
'noColorControl'
],
emits: ['update:shadow'],
data () {
return {
colorOverride: undefined,
lightGrid: false,
zoom: 100,
randomSeed: genRandomSeed()
}
},
mounted () {
this.update()
},
computed: {
hideControls () {
return typeof this.shadow === 'string'
}
},
watch: {
previewCss () {
this.update()
},
previewStyle () {
this.update()
},
zoom () {
this.update()
}
},
methods: {
updateProperty (axis, value) {
this.$emit('update:shadow', { axis, value: Number(value) })
},
update () {
const sheet = createStyleSheet('style-component-preview', 90)
sheet.clear()
const result = [this.previewCss]
if (this.colorOverride) result.push(`--background: ${this.colorOverride}`)
const styleRule = [
'#component-preview-', this.randomSeed, ' {\n',
'.preview-block {\n',
`zoom: ${this.zoom / 100};`,
this.previewStyle,
'\n}',
'\n}'
].join('')
sheet.addRule(styleRule)
sheet.addRule([
'#component-preview-', this.randomSeed, ' {\n',
...result,
'\n}'
].join(''))
sheet.ready = true
adoptStyleSheets()
}
}
}

View file

@ -0,0 +1,151 @@
.ComponentPreview {
display: grid;
grid-template-columns: 1em 1fr 1fr 1em;
grid-template-rows: 2em 1fr 1fr 1fr 1em 2em max-content;
grid-template-areas:
"header header header header "
"preview preview preview y-slide"
"preview preview preview y-slide"
"preview preview preview y-slide"
"x-slide x-slide x-slide . "
"x-num x-num y-num y-num "
"assists assists assists assists";
grid-gap: 0.5em;
&:not(.-shadow-controls) {
grid-template-areas:
"header header header header "
"preview preview preview y-slide"
"preview preview preview y-slide"
"preview preview preview y-slide"
"assists assists assists assists";
grid-template-rows: 2em 1fr 1fr 1fr max-content;
}
.header {
grid-area: header;
place-self: baseline center;
line-height: 2;
}
.invalid-container {
position: absolute;
inset: 0;
display: grid;
place-items: center center;
background-color: rgb(100 0 0 / 50%);
.alert {
padding: 0.5em 1em;
}
}
.assists {
grid-area: assists;
display: grid;
grid-auto-flow: row;
grid-auto-rows: 2em;
grid-gap: 0.5em;
}
.input-light-grid {
justify-self: center;
}
.input-number {
min-width: 2em;
}
.x-shift-number {
grid-area: x-num;
justify-self: right;
}
.y-shift-number {
grid-area: y-num;
justify-self: left;
}
.x-shift-number,
.y-shift-number {
input {
max-width: 4em;
}
}
.x-shift-slider {
grid-area: x-slide;
height: auto;
align-self: start;
min-width: 10em;
}
.y-shift-slider {
grid-area: y-slide;
writing-mode: vertical-lr;
justify-self: left;
min-height: 10em;
}
.x-shift-slider,
.y-shift-slider {
padding: 0;
}
.preview-window {
--__grid-color1: rgb(102 102 102);
--__grid-color2: rgb(153 153 153);
--__grid-color1-disabled: rgb(102 102 102 / 20%);
--__grid-color2-disabled: rgb(153 153 153 / 20%);
&.-light-grid {
--__grid-color1: rgb(205 205 205);
--__grid-color2: rgb(255 255 255);
--__grid-color1-disabled: rgb(205 205 205 / 20%);
--__grid-color2-disabled: rgb(255 255 255 / 20%);
}
position: relative;
grid-area: preview;
aspect-ratio: 1;
display: flex;
align-items: center;
justify-content: center;
min-width: 10em;
min-height: 10em;
background-color: var(--__grid-color2);
background-image:
linear-gradient(45deg, var(--__grid-color1) 25%, transparent 25%),
linear-gradient(-45deg, var(--__grid-color1) 25%, transparent 25%),
linear-gradient(45deg, transparent 75%, var(--__grid-color1) 75%),
linear-gradient(-45deg, transparent 75%, var(--__grid-color1) 75%);
background-size: 20px 20px;
background-position: 0 0, 0 10px, 10px -10px, -10px 0;
border-radius: var(--roundness);
&.disabled {
background-color: var(--__grid-color2-disabled);
background-image:
linear-gradient(45deg, var(--__grid-color1-disabled) 25%, transparent 25%),
linear-gradient(-45deg, var(--__grid-color1-disabled) 25%, transparent 25%),
linear-gradient(45deg, transparent 75%, var(--__grid-color1-disabled) 75%),
linear-gradient(-45deg, transparent 75%, var(--__grid-color1-disabled) 75%);
}
.preview-block {
background: var(--background, var(--bg));
display: flex;
justify-content: center;
align-items: center;
min-width: 33%;
min-height: 33%;
max-width: 80%;
max-height: 80%;
border-width: 0;
border-style: solid;
border-color: var(--border);
border-radius: var(--roundness);
box-shadow: var(--shadow);
}
}
}

View file

@ -1,14 +1,9 @@
<template> <template>
<div <div
:id="'component-preview-' + randomSeed"
class="ComponentPreview" class="ComponentPreview"
:class="{ '-shadow-controls': shadowControl }" :class="{ '-shadow-controls': shadowControl }"
> >
<!-- eslint-disable vue/no-v-html vue/no-v-text-v-html-on-component -->
<component
:is="'style'"
v-html="previewCss"
/>
<!-- eslint-enable vue/no-v-html vue/no-v-text-v-html-on-component -->
<label <label
v-show="shadowControl" v-show="shadowControl"
role="heading" role="heading"
@ -74,7 +69,6 @@
<div <div
class="preview-block" class="preview-block"
:class="previewClass" :class="previewClass"
:style="style"
> >
{{ $t('settings.style.themes3.editor.test_string') }} {{ $t('settings.style.themes3.editor.test_string') }}
</div> </div>
@ -116,203 +110,5 @@
</div> </div>
</template> </template>
<script> <script src="./component_preview.js" />
import Checkbox from 'src/components/checkbox/checkbox.vue' <style src="./component_preview.scss" lang="scss" />
import ColorInput from 'src/components/color_input/color_input.vue'
export default {
components: {
Checkbox,
ColorInput
},
props: [
'shadow',
'shadowControl',
'previewClass',
'previewStyle',
'previewCss',
'disabled',
'invalid',
'noColorControl'
],
emits: ['update:shadow'],
data () {
return {
colorOverride: undefined,
lightGrid: false,
zoom: 100
}
},
computed: {
style () {
const result = [
this.previewStyle,
`zoom: ${this.zoom / 100}`
]
if (this.colorOverride) result.push(`--background: ${this.colorOverride}`)
return result
},
hideControls () {
return typeof this.shadow === 'string'
}
},
methods: {
updateProperty (axis, value) {
this.$emit('update:shadow', { axis, value: Number(value) })
}
}
}
</script>
<style lang="scss">
.ComponentPreview {
display: grid;
grid-template-columns: 1em 1fr 1fr 1em;
grid-template-rows: 2em 1fr 1fr 1fr 1em 2em max-content;
grid-template-areas:
"header header header header "
"preview preview preview y-slide"
"preview preview preview y-slide"
"preview preview preview y-slide"
"x-slide x-slide x-slide . "
"x-num x-num y-num y-num "
"assists assists assists assists";
grid-gap: 0.5em;
&:not(.-shadow-controls) {
grid-template-areas:
"header header header header "
"preview preview preview y-slide"
"preview preview preview y-slide"
"preview preview preview y-slide"
"assists assists assists assists";
grid-template-rows: 2em 1fr 1fr 1fr max-content;
}
.header {
grid-area: header;
place-self: baseline center;
line-height: 2;
}
.invalid-container {
position: absolute;
inset: 0;
display: grid;
place-items: center center;
background-color: rgb(100 0 0 / 50%);
.alert {
padding: 0.5em 1em;
}
}
.assists {
grid-area: assists;
display: grid;
grid-auto-flow: row;
grid-auto-rows: 2em;
grid-gap: 0.5em;
}
.input-light-grid {
justify-self: center;
}
.input-number {
min-width: 2em;
}
.x-shift-number {
grid-area: x-num;
justify-self: right;
}
.y-shift-number {
grid-area: y-num;
justify-self: left;
}
.x-shift-number,
.y-shift-number {
input {
max-width: 4em;
}
}
.x-shift-slider {
grid-area: x-slide;
height: auto;
align-self: start;
min-width: 10em;
}
.y-shift-slider {
grid-area: y-slide;
writing-mode: vertical-lr;
justify-self: left;
min-height: 10em;
}
.x-shift-slider,
.y-shift-slider {
padding: 0;
}
.preview-window {
--__grid-color1: rgb(102 102 102);
--__grid-color2: rgb(153 153 153);
--__grid-color1-disabled: rgb(102 102 102 / 20%);
--__grid-color2-disabled: rgb(153 153 153 / 20%);
&.-light-grid {
--__grid-color1: rgb(205 205 205);
--__grid-color2: rgb(255 255 255);
--__grid-color1-disabled: rgb(205 205 205 / 20%);
--__grid-color2-disabled: rgb(255 255 255 / 20%);
}
position: relative;
grid-area: preview;
aspect-ratio: 1;
display: flex;
align-items: center;
justify-content: center;
min-width: 10em;
min-height: 10em;
background-color: var(--__grid-color2);
background-image:
linear-gradient(45deg, var(--__grid-color1) 25%, transparent 25%),
linear-gradient(-45deg, var(--__grid-color1) 25%, transparent 25%),
linear-gradient(45deg, transparent 75%, var(--__grid-color1) 75%),
linear-gradient(-45deg, transparent 75%, var(--__grid-color1) 75%);
background-size: 20px 20px;
background-position: 0 0, 0 10px, 10px -10px, -10px 0;
border-radius: var(--roundness);
&.disabled {
background-color: var(--__grid-color2-disabled);
background-image:
linear-gradient(45deg, var(--__grid-color1-disabled) 25%, transparent 25%),
linear-gradient(-45deg, var(--__grid-color1-disabled) 25%, transparent 25%),
linear-gradient(45deg, transparent 75%, var(--__grid-color1-disabled) 75%),
linear-gradient(-45deg, transparent 75%, var(--__grid-color1-disabled) 75%);
}
.preview-block {
background: var(--background, var(--bg));
display: flex;
justify-content: center;
align-items: center;
min-width: 33%;
min-height: 33%;
max-width: 80%;
max-height: 80%;
border-width: 0;
border-style: solid;
border-color: var(--border);
border-radius: var(--roundness);
box-shadow: var(--shadow);
}
}
}
</style>

View file

@ -1,7 +1,8 @@
import { mapState, mapGetters, mapActions, mapMutations } from 'vuex' import { mapState } from 'vuex'
import { mapStores } from 'pinia' import { mapStores, mapActions, mapState as mapPiniaState } from 'pinia'
import oauthApi from '../../services/new_api/oauth.js' import oauthApi from '../../services/new_api/oauth.js'
import { useOAuthStore } from 'src/stores/oauth.js' import { useOAuthStore } from 'src/stores/oauth.js'
import { useAuthFlowStore } from 'src/stores/auth_flow.js'
import { library } from '@fortawesome/fontawesome-svg-core' import { library } from '@fortawesome/fontawesome-svg-core'
import { import {
faTimes faTimes
@ -25,13 +26,10 @@ const LoginForm = {
instance: state => state.instance, instance: state => state.instance,
loggingIn: state => state.users.loggingIn, loggingIn: state => state.users.loggingIn,
}), }),
...mapGetters( ...mapPiniaState(useAuthFlowStore, ['requiredPassword', 'requiredToken', 'requiredMFA'])
'authFlow', ['requiredPassword', 'requiredToken', 'requiredMFA']
)
}, },
methods: { methods: {
...mapMutations('authFlow', ['requireMFA']), ...mapActions(useAuthFlowStore, ['requireMFA', 'login']),
...mapActions({ login: 'authFlow/login' }),
submit () { submit () {
this.isTokenAuth ? this.submitToken() : this.submitPassword() this.isTokenAuth ? this.submitToken() : this.submitPassword()
}, },

View file

@ -1,7 +1,8 @@
import mfaApi from '../../services/new_api/mfa.js' import mfaApi from '../../services/new_api/mfa.js'
import { mapState, mapGetters, mapActions, mapMutations } from 'vuex' import { mapState } from 'vuex'
import { mapStores } from 'pinia' import { mapStores, mapActions, mapState as mapPiniaState } from 'pinia'
import { useOAuthStore } from 'src/stores/oauth.js' import { useOAuthStore } from 'src/stores/oauth.js'
import { useAuthFlowStore } from 'src/stores/auth_flow.js'
import { library } from '@fortawesome/fontawesome-svg-core' import { library } from '@fortawesome/fontawesome-svg-core'
import { import {
faTimes faTimes
@ -17,8 +18,8 @@ export default {
error: false error: false
}), }),
computed: { computed: {
...mapGetters({ ...mapPiniaState(useAuthFlowStore, {
authSettings: 'authFlow/settings' authSettings: store => store.settings
}), }),
...mapStores(useOAuthStore), ...mapStores(useOAuthStore),
...mapState({ ...mapState({
@ -26,8 +27,7 @@ export default {
}) })
}, },
methods: { methods: {
...mapMutations('authFlow', ['requireTOTP', 'abortMFA']), ...mapActions(useAuthFlowStore, ['requireTOTP', 'abortMFA', 'login']),
...mapActions({ login: 'authFlow/login' }),
clearError () { this.error = false }, clearError () { this.error = false },
focusOnCodeInput () { focusOnCodeInput () {

View file

@ -1,7 +1,8 @@
import mfaApi from '../../services/new_api/mfa.js' import mfaApi from '../../services/new_api/mfa.js'
import { mapState, mapGetters, mapActions, mapMutations } from 'vuex' import { mapState } from 'vuex'
import { mapStores } from 'pinia' import { mapStores, mapActions, mapState as mapPiniaState } from 'pinia'
import { useOAuthStore } from 'src/stores/oauth.js' import { useOAuthStore } from 'src/stores/oauth.js'
import { useAuthFlowStore } from 'src/stores/auth_flow.js'
import { library } from '@fortawesome/fontawesome-svg-core' import { library } from '@fortawesome/fontawesome-svg-core'
import { import {
faTimes faTimes
@ -17,8 +18,8 @@ export default {
error: false error: false
}), }),
computed: { computed: {
...mapGetters({ ...mapPiniaState(useAuthFlowStore, {
authSettings: 'authFlow/settings' authSettings: store => store.settings
}), }),
...mapStores(useOAuthStore), ...mapStores(useOAuthStore),
...mapState({ ...mapState({
@ -26,8 +27,7 @@ export default {
}) })
}, },
methods: { methods: {
...mapMutations('authFlow', ['requireRecovery', 'abortMFA']), ...mapActions(useAuthFlowStore, ['requireRecovery', 'abortMFA', 'login']),
...mapActions({ login: 'authFlow/login' }),
clearError () { this.error = false }, clearError () { this.error = false },
focusOnCodeInput () { focusOnCodeInput () {

View file

@ -15,6 +15,7 @@ import { library } from '@fortawesome/fontawesome-svg-core'
import { import {
faUsers, faUsers,
faGlobe, faGlobe,
faCity,
faBookmark, faBookmark,
faEnvelope, faEnvelope,
faChevronDown, faChevronDown,
@ -31,6 +32,7 @@ import {
library.add( library.add(
faUsers, faUsers,
faGlobe, faGlobe,
faCity,
faBookmark, faBookmark,
faEnvelope, faEnvelope,
faChevronDown, faChevronDown,
@ -108,12 +110,15 @@ const NavPanel = {
privateMode: state => state.instance.private, privateMode: state => state.instance.private,
federating: state => state.instance.federating, federating: state => state.instance.federating,
pleromaChatMessagesAvailable: state => state.instance.pleromaChatMessagesAvailable, pleromaChatMessagesAvailable: state => state.instance.pleromaChatMessagesAvailable,
bookmarkFolders: state => state.instance.pleromaBookmarkFoldersAvailable bookmarkFolders: state => state.instance.pleromaBookmarkFoldersAvailable,
bubbleTimeline: state => state.instance.localBubbleInstances.length > 0
}), }),
timelinesItems () { timelinesItems () {
return filterNavigation( return filterNavigation(
Object Object
.entries({ ...TIMELINES }) .entries({ ...TIMELINES })
// do not show in timeliens list since it's in a better place now
.filter(([key]) => key !== 'bookmarks')
.map(([k, v]) => ({ ...v, name: k })), .map(([k, v]) => ({ ...v, name: k })),
{ {
hasChats: this.pleromaChatMessagesAvailable, hasChats: this.pleromaChatMessagesAvailable,
@ -121,6 +126,7 @@ const NavPanel = {
isFederating: this.federating, isFederating: this.federating,
isPrivate: this.privateMode, isPrivate: this.privateMode,
currentUser: this.currentUser, currentUser: this.currentUser,
supportsBubbleTimeline: this.bubbleTimeline,
supportsBookmarkFolders: this.bookmarkFolders supportsBookmarkFolders: this.bookmarkFolders
} }
) )
@ -136,6 +142,7 @@ const NavPanel = {
isFederating: this.federating, isFederating: this.federating,
isPrivate: this.privateMode, isPrivate: this.privateMode,
currentUser: this.currentUser, currentUser: this.currentUser,
supportsBubbleTimeline: this.bubbleTimeline,
supportsBookmarkFolders: this.bookmarkFolders supportsBookmarkFolders: this.bookmarkFolders
} }
) )

View file

@ -1,4 +1,12 @@
export const filterNavigation = (list = [], { hasChats, hasAnnouncements, isFederating, isPrivate, currentUser, supportsBookmarkFolders }) => { export const filterNavigation = (list = [], {
hasChats,
hasAnnouncements,
isFederating,
isPrivate,
currentUser,
supportsBookmarkFolders,
supportsBubbleTimeline
}) => {
return list.filter(({ criteria, anon, anonRoute }) => { return list.filter(({ criteria, anon, anonRoute }) => {
const set = new Set(criteria || []) const set = new Set(criteria || [])
if (!isFederating && set.has('federating')) return false if (!isFederating && set.has('federating')) return false
@ -7,6 +15,8 @@ export const filterNavigation = (list = [], { hasChats, hasAnnouncements, isFede
if ((!currentUser || !currentUser.locked) && set.has('lockedUser')) return false if ((!currentUser || !currentUser.locked) && set.has('lockedUser')) return false
if (!hasChats && set.has('chats')) return false if (!hasChats && set.has('chats')) return false
if (!hasAnnouncements && set.has('announcements')) return false if (!hasAnnouncements && set.has('announcements')) return false
if (!supportsBubbleTimeline && set.has('supportsBubbleTimeline')) return false
if (!supportsBookmarkFolders && set.has('supportsBookmarkFolders')) return false
if (supportsBookmarkFolders && set.has('!supportsBookmarkFolders')) return false if (supportsBookmarkFolders && set.has('!supportsBookmarkFolders')) return false
return true return true
}) })
@ -19,11 +29,11 @@ export const getListEntries = store => store.allLists.map(list => ({
iconLetter: list.title[0] iconLetter: list.title[0]
})) }))
export const getBookmarkFolderEntries = store => store.allFolders.map(folder => ({ export const getBookmarkFolderEntries = store => store.allFolders ? store.allFolders.map(folder => ({
name: 'bookmark-folder-' + folder.id, name: 'bookmark-folder-' + folder.id,
routeObject: { name: 'bookmark-folder', params: { id: folder.id } }, routeObject: { name: 'bookmark-folder', params: { id: folder.id } },
labelRaw: folder.name, labelRaw: folder.name,
iconEmoji: folder.emoji, iconEmoji: folder.emoji,
iconEmojiUrl: folder.emoji_url, iconEmojiUrl: folder.emoji_url,
iconLetter: folder.name[0] iconLetter: folder.name[0]
})) })) : []

View file

@ -27,6 +27,13 @@ export const TIMELINES = {
label: 'nav.public_tl', label: 'nav.public_tl',
criteria: ['!private'] criteria: ['!private']
}, },
bubble: {
route: 'bubble',
anon: true,
icon: 'city',
label: 'nav.bubble',
criteria: ['!private', 'federating', 'supportsBubbleTimeline']
},
twkn: { twkn: {
route: 'public-external-timeline', route: 'public-external-timeline',
anon: true, anon: true,
@ -34,11 +41,11 @@ export const TIMELINES = {
label: 'nav.twkn', label: 'nav.twkn',
criteria: ['!private', 'federating'] criteria: ['!private', 'federating']
}, },
// bookmarks are still technically a timeline so we should show it in the dropdown
bookmarks: { bookmarks: {
route: 'bookmarks', route: 'bookmarks',
icon: 'bookmark', icon: 'bookmark',
label: 'nav.bookmarks', label: 'nav.bookmarks',
criteria: ['!supportsBookmarkFolders']
}, },
favorites: { favorites: {
routeObject: { name: 'user-profile', query: { tab: 'favorites' } }, routeObject: { name: 'user-profile', query: { tab: 'favorites' } },
@ -53,6 +60,15 @@ export const TIMELINES = {
} }
export const ROOT_ITEMS = { export const ROOT_ITEMS = {
bookmarks: {
route: 'bookmarks',
icon: 'bookmark',
label: 'nav.bookmarks',
// shows bookmarks entry in a better suited location
// hides it when bookmark folders are supported since
// we show custom component instead of it
criteria: ['!supportsBookmarkFolders']
},
interactions: { interactions: {
route: 'interactions', route: 'interactions',
icon: 'bell', icon: 'bell',

View file

@ -9,6 +9,7 @@ import { library } from '@fortawesome/fontawesome-svg-core'
import { import {
faUsers, faUsers,
faGlobe, faGlobe,
faCity,
faBookmark, faBookmark,
faEnvelope, faEnvelope,
faComments, faComments,
@ -25,6 +26,7 @@ import { useServerSideStorageStore } from 'src/stores/serverSideStorage'
library.add( library.add(
faUsers, faUsers,
faGlobe, faGlobe,
faCity,
faBookmark, faBookmark,
faEnvelope, faEnvelope,
faComments, faComments,
@ -65,7 +67,8 @@ const NavPanel = {
followRequestCount: state => state.api.followRequests.length, followRequestCount: state => state.api.followRequests.length,
privateMode: state => state.instance.private, privateMode: state => state.instance.private,
federating: state => state.instance.federating, federating: state => state.instance.federating,
pleromaChatMessagesAvailable: state => state.instance.pleromaChatMessagesAvailable pleromaChatMessagesAvailable: state => state.instance.pleromaChatMessagesAvailable,
bubbleTimeline: state => state.instance.localBubbleInstances.length > 0
}), }),
pinnedList () { pinnedList () {
if (!this.currentUser) { if (!this.currentUser) {
@ -79,7 +82,9 @@ const NavPanel = {
hasAnnouncements: this.supportsAnnouncements, hasAnnouncements: this.supportsAnnouncements,
isFederating: this.federating, isFederating: this.federating,
isPrivate: this.privateMode, isPrivate: this.privateMode,
currentUser: this.currentUser currentUser: this.currentUser,
supportsBubbleTimeline: this.bubbleTimeline,
supportsBookmarkFolders: this.bookmarks
}) })
} }
return filterNavigation( return filterNavigation(
@ -98,6 +103,8 @@ const NavPanel = {
{ {
hasChats: this.pleromaChatMessagesAvailable, hasChats: this.pleromaChatMessagesAvailable,
hasAnnouncements: this.supportsAnnouncements, hasAnnouncements: this.supportsAnnouncements,
supportsBubbleTimeline: this.bubbleTimeline,
supportsBookmarkFolders: this.bookmarks,
isFederating: this.federating, isFederating: this.federating,
isPrivate: this.privateMode, isPrivate: this.privateMode,
currentUser: this.currentUser currentUser: this.currentUser

View file

@ -60,11 +60,14 @@
} }
.extra-button { .extra-button {
border-left: 1px solid var(--icon); border-left: 1px solid;
border-image-source: linear-gradient(to bottom, transparent 0%, var(--icon) var(--__horizontal-gap) calc(100% - var(--__horizontal-gap)), transparent 100%);
border-image-slice: 1;
padding-left: calc(var(--__horizontal-gap) - 1px); padding-left: calc(var(--__horizontal-gap) - 1px);
border-right: var(--__horizontal-gap) solid transparent; padding-right: var(--__horizontal-gap);
border-top: var(--__horizontal-gap) solid transparent; padding-top: var(--__horizontal-gap);
border-bottom: var(--__horizontal-gap) solid transparent; padding-bottom: var(--__horizontal-gap);
max-width: fit-content;
} }
.main-button { .main-button {

View file

@ -12,10 +12,10 @@ import { newImporter } from 'src/services/export_import/export_import.js'
import { convertTheme2To3 } from 'src/services/theme_data/theme2_to_theme3.js' import { convertTheme2To3 } from 'src/services/theme_data/theme2_to_theme3.js'
import { init } from 'src/services/theme_data/theme_data_3.service.js' import { init } from 'src/services/theme_data/theme_data_3.service.js'
import { import {
getCssRules, getCssRules
getScopedVersion
} from 'src/services/theme_data/css_utils.js' } from 'src/services/theme_data/css_utils.js'
import { deserialize } from 'src/services/theme_data/iss_deserializer.js' import { deserialize } from 'src/services/theme_data/iss_deserializer.js'
import { createStyleSheet, adoptStyleSheets } from 'src/services/style_setter/style_setter.js'
import SharedComputedObject from '../helpers/shared_computed_object.js' import SharedComputedObject from '../helpers/shared_computed_object.js'
import ProfileSettingIndicator from '../helpers/profile_setting_indicator.vue' import ProfileSettingIndicator from '../helpers/profile_setting_indicator.vue'
@ -155,19 +155,23 @@ const AppearanceTab = {
})) }))
}) })
this.previewTheme('stock', 'v3')
if (window.IntersectionObserver) { if (window.IntersectionObserver) {
this.intersectionObserver = new IntersectionObserver((entries, observer) => { this.intersectionObserver = new IntersectionObserver((entries, observer) => {
entries.forEach(({ target, isIntersecting }) => { entries.forEach(({ target, isIntersecting }) => {
if (!isIntersecting) return if (!isIntersecting) return
const theme = this.availableStyles.find(x => x.key === target.dataset.themeKey) const theme = this.availableStyles.find(x => x.key === target.dataset.themeKey)
this.$nextTick(() => { this.$nextTick(() => {
if (theme) theme.ready = true if (theme) this.previewTheme(theme.key, theme.version, theme.data)
}) })
observer.unobserve(target) observer.unobserve(target)
}) })
}, { }, {
root: this.$refs.themeList root: this.$refs.themeList
}) })
} else {
this.availableStyles.forEach(theme => this.previewTheme(theme.key, theme.version, theme.data))
} }
}, },
updated () { updated () {
@ -391,7 +395,6 @@ const AppearanceTab = {
inputRuleset: [...input, paletteRule].filter(x => x), inputRuleset: [...input, paletteRule].filter(x => x),
ultimateBackgroundColor: '#000000', ultimateBackgroundColor: '#000000',
liteMode: true, liteMode: true,
debug: true,
onlyNormalState: true onlyNormalState: true
}) })
} }
@ -400,7 +403,6 @@ const AppearanceTab = {
inputRuleset: [], inputRuleset: [],
ultimateBackgroundColor: '#000000', ultimateBackgroundColor: '#000000',
liteMode: true, liteMode: true,
debug: true,
onlyNormalState: true onlyNormalState: true
}) })
} }
@ -409,10 +411,15 @@ const AppearanceTab = {
this.compilationCache[key] = theme3 this.compilationCache[key] = theme3
} }
return getScopedVersion(
getCssRules(theme3.eager), const sheet = createStyleSheet('appearance-tab-previews', 90)
'#theme-preview-' + key sheet.addRule([
).join('\n') '#theme-preview-', key, ' {\n',
getCssRules(theme3.eager).join('\n'),
'\n}'
].join(''))
sheet.ready = true
adoptStyleSheets()
} }
} }
} }

View file

@ -16,14 +16,6 @@
:disabled="switchInProgress" :disabled="switchInProgress"
@click="resetTheming" @click="resetTheming"
> >
<!-- eslint-disable vue/no-v-text-v-html-on-component -->
<!-- eslint-disable vue/no-v-html -->
<component
:is="'style'"
v-html="previewTheme('stock', 'v3')"
/>
<!-- eslint-enable vue/no-v-html -->
<!-- eslint-enable vue/no-v-text-v-html-on-component -->
<preview id="theme-preview-stock" /> <preview id="theme-preview-stock" />
<h4 class="theme-name"> <h4 class="theme-name">
{{ $t('settings.style.stock_theme_used') }} {{ $t('settings.style.stock_theme_used') }}
@ -61,16 +53,6 @@
:disabled="switchInProgress" :disabled="switchInProgress"
@click="style.version === 'v2' ? setTheme(style.key) : setStyle(style.key)" @click="style.version === 'v2' ? setTheme(style.key) : setStyle(style.key)"
> >
<!-- eslint-disable vue/no-v-text-v-html-on-component -->
<!-- eslint-disable vue/no-v-html -->
<div v-if="style.ready || noIntersectionObserver">
<component
:is="'style'"
v-html="previewTheme(style.key, style.version, style.data)"
/>
</div>
<!-- eslint-enable vue/no-v-html -->
<!-- eslint-enable vue/no-v-text-v-html-on-component -->
<preview :id="'theme-preview-' + style.key" /> <preview :id="'theme-preview-' + style.key" />
<h4 class="theme-name"> <h4 class="theme-name">
{{ style.name }} {{ style.name }}

View file

@ -8,6 +8,7 @@ import InterfaceLanguageSwitcher from 'src/components/interface_language_switche
import SharedComputedObject from '../helpers/shared_computed_object.js' import SharedComputedObject from '../helpers/shared_computed_object.js'
import ProfileSettingIndicator from '../helpers/profile_setting_indicator.vue' import ProfileSettingIndicator from '../helpers/profile_setting_indicator.vue'
import { clearCache, cacheKey, emojiCacheKey } from 'src/services/sw/sw.js'
import { library } from '@fortawesome/fontawesome-svg-core' import { library } from '@fortawesome/fontawesome-svg-core'
import { import {
faGlobe faGlobe
@ -98,6 +99,21 @@ const GeneralTab = {
methods: { methods: {
changeDefaultScope (value) { changeDefaultScope (value) {
this.$store.dispatch('setProfileOption', { name: 'defaultScope', value }) this.$store.dispatch('setProfileOption', { name: 'defaultScope', value })
},
clearCache (key) {
clearCache(key)
.then(() => {
this.$store.dispatch('settingsSaved', { success: true })
})
.catch(error => {
this.$store.dispatch('settingsSaved', { error })
})
},
clearAssetCache () {
this.clearCache(cacheKey)
},
clearEmojiCache () {
this.clearCache(emojiCacheKey)
} }
} }
} }

View file

@ -509,6 +509,29 @@
</li> </li>
</ul> </ul>
</div> </div>
<div
class="setting-item"
>
<h2>{{ $t('settings.cache') }}</h2>
<ul class="setting-list">
<li>
<button
class="btn button-default"
@click="clearAssetCache"
>
{{ $t('settings.clear_asset_cache') }}
</button>
</li>
<li>
<button
class="btn button-default"
@click="clearEmojiCache"
>
{{ $t('settings.clear_emoji_cache') }}
</button>
</li>
</ul>
</div>
</div> </div>
</template> </template>

View file

@ -41,8 +41,8 @@ const SecurityTab = {
user () { user () {
return this.$store.state.users.currentUser return this.$store.state.users.currentUser
}, },
pleromaBackend () { pleromaExtensionsAvailable () {
return this.$store.state.instance.pleromaBackend return this.$store.state.instance.pleromaExtensionsAvailable
}, },
oauthTokens () { oauthTokens () {
return useOAuthTokensStore().tokens.map(oauthToken => { return useOAuthTokensStore().tokens.map(oauthToken => {

View file

@ -1,4 +1,4 @@
import { ref, reactive, computed, watch, watchEffect, provide, getCurrentInstance } from 'vue' import { ref, reactive, computed, watch, provide, getCurrentInstance } from 'vue'
import { useInterfaceStore } from 'src/stores/interface' import { useInterfaceStore } from 'src/stores/interface'
import { get, set, unset, throttle } from 'lodash' import { get, set, unset, throttle } from 'lodash'
@ -19,11 +19,9 @@ import Preview from '../theme_tab/theme_preview.vue'
import VirtualDirectivesTab from './virtual_directives_tab.vue' import VirtualDirectivesTab from './virtual_directives_tab.vue'
import { createStyleSheet, adoptStyleSheets } from 'src/services/style_setter/style_setter.js'
import { init, findColor } from 'src/services/theme_data/theme_data_3.service.js' import { init, findColor } from 'src/services/theme_data/theme_data_3.service.js'
import { import { getCssRules } from 'src/services/theme_data/css_utils.js'
getCssRules,
getScopedVersion
} from 'src/services/theme_data/css_utils.js'
import { serialize } from 'src/services/theme_data/iss_serializer.js' import { serialize } from 'src/services/theme_data/iss_serializer.js'
import { deserializeShadow, deserialize } from 'src/services/theme_data/iss_deserializer.js' import { deserializeShadow, deserialize } from 'src/services/theme_data/iss_deserializer.js'
import { import {
@ -372,6 +370,9 @@ export default {
const path = getPath(component, directive) const path = getPath(component, directive)
usedRule = get(real, path) // get real usedRule = get(real, path) // get real
if (usedRule === '') {
return usedRule
}
if (!usedRule) { if (!usedRule) {
usedRule = get(fallback, path) usedRule = get(fallback, path)
} }
@ -379,7 +380,7 @@ export default {
return postProcess(usedRule) return postProcess(usedRule)
}, },
set (value) { set (value) {
if (value) { if (value != null) {
set(allEditedRules.value, getPath(component, directive), value) set(allEditedRules.value, getPath(component, directive), value)
} else { } else {
unset(allEditedRules.value, getPath(component, directive)) unset(allEditedRules.value, getPath(component, directive))
@ -667,7 +668,7 @@ export default {
}) })
exports.clearStyle = () => { exports.clearStyle = () => {
onImport(interfaceStore().styleDataUsed) onImport(interfaceStore.styleDataUsed)
} }
exports.exportStyle = () => { exports.exportStyle = () => {
@ -685,19 +686,26 @@ export default {
const overallPreviewRules = ref([]) const overallPreviewRules = ref([])
exports.overallPreviewRules = overallPreviewRules exports.overallPreviewRules = overallPreviewRules
const overallPreviewCssRules = ref([]) watch([overallPreviewRules], () => {
watchEffect(throttle(() => { let css = null
try { try {
overallPreviewCssRules.value = getScopedVersion( css = getCssRules(overallPreviewRules.value).map(r => r.replace('html', '&'))
getCssRules(overallPreviewRules.value),
'#edited-style-preview'
).join('\n')
} catch (e) { } catch (e) {
console.error(e) console.error(e)
return
} }
}, 500))
exports.overallPreviewCssRules = overallPreviewCssRules const sheet = createStyleSheet('style-tab-overall-preview', 90)
sheet.clear()
sheet.addRule([
'#edited-style-preview {\n',
css.join('\n'),
'\n}'
].join(''))
sheet.ready = true
adoptStyleSheets()
})
const updateOverallPreview = throttle(() => { const updateOverallPreview = throttle(() => {
try { try {
@ -721,12 +729,12 @@ export default {
console.error('Could not compile preview theme', e) console.error('Could not compile preview theme', e)
return null return null
} }
}, 5000) }, 1000)
// //
// Apart from "hover" we can't really show how component looks like in // Apart from "hover" we can't really show how component looks like in
// certain states, so we have to fake them. // certain states, so we have to fake them.
const simulatePseudoSelectors = (css, prefix) => css const simulatePseudoSelectors = (css, prefix) => css
.replace(prefix, '.component-preview .preview-block') .replace(prefix, '.preview-block')
.replace(':active', '.preview-active') .replace(':active', '.preview-active')
.replace(':hover', '.preview-hover') .replace(':hover', '.preview-hover')
.replace(':active', '.preview-active') .replace(':active', '.preview-active')

View file

@ -6,14 +6,6 @@
<div class="setting-item heading"> <div class="setting-item heading">
<h2> {{ $t('settings.style.themes3.editor.title') }} </h2> <h2> {{ $t('settings.style.themes3.editor.title') }} </h2>
<div class="meta-preview"> <div class="meta-preview">
<!-- eslint-disable vue/no-v-text-v-html-on-component -->
<!-- eslint-disable vue/no-v-html -->
<component
:is="'style'"
v-html="overallPreviewCssRules"
/>
<!-- eslint-enable vue/no-v-html -->
<!-- eslint-enable vue/no-v-text-v-html-on-component -->
<Preview id="edited-style-preview" /> <Preview id="edited-style-preview" />
<teleport <teleport
v-if="isActive" v-if="isActive"
@ -155,12 +147,6 @@
</ul> </ul>
</div> </div>
<div class="preview-container"> <div class="preview-container">
<!-- eslint-disable vue/no-v-html vue/no-v-text-v-html-on-component -->
<component
:is="'style'"
v-html="previewCss"
/>
<!-- eslint-enable vue/no-v-html vue/no-v-text-v-html-on-component -->
<ComponentPreview <ComponentPreview
class="component-preview" class="component-preview"
:show-text="componentHas('Text')" :show-text="componentHas('Text')"

View file

@ -31,6 +31,7 @@ import {
getCssRules, getCssRules,
getScopedVersion getScopedVersion
} from 'src/services/theme_data/css_utils.js' } from 'src/services/theme_data/css_utils.js'
import { createStyleSheet, adoptStyleSheets } from 'src/services/style_setter/style_setter.js'
import ColorInput from 'src/components/color_input/color_input.vue' import ColorInput from 'src/components/color_input/color_input.vue'
import RangeInput from 'src/components/range_input/range_input.vue' import RangeInput from 'src/components/range_input/range_input.vue'
@ -68,7 +69,6 @@ const colorConvert = (color) => {
export default { export default {
data () { data () {
return { return {
themeV3Preview: [],
themeImporter: newImporter({ themeImporter: newImporter({
validator: this.importValidator, validator: this.importValidator,
onImport: this.onImport, onImport: this.onImport,
@ -697,10 +697,16 @@ export default {
liteMode: true liteMode: true
}) })
this.themeV3Preview = getScopedVersion( const sheet = createStyleSheet('theme-tab-overall-preview', 90)
const rule = getScopedVersion(
getCssRules(theme3.eager), getCssRules(theme3.eager),
'#theme-preview' '&'
).join('\n') ).join('\n')
sheet.clear()
sheet.addRule('#theme-preview {\n' + rule + '\n}')
sheet.ready = true
adoptStyleSheets()
} }
}, },
watch: { watch: {

View file

@ -123,12 +123,6 @@
</div> </div>
</div> </div>
<!-- eslint-disable vue/no-v-html vue/no-v-text-v-html-on-component -->
<component
:is="'style'"
v-html="themeV3Preview"
/>
<!-- eslint-enable vue/no-v-html vue/no-v-text-v-html-on-component -->
<preview id="theme-preview" /> <preview id="theme-preview" />
<div> <div>

View file

@ -24,6 +24,7 @@ import {
faLock, faLock,
faLockOpen, faLockOpen,
faGlobe, faGlobe,
faIgloo,
faTimes, faTimes,
faRetweet, faRetweet,
faReply, faReply,
@ -43,6 +44,7 @@ import {
library.add( library.add(
faEnvelope, faEnvelope,
faGlobe, faGlobe,
faIgloo,
faLock, faLock,
faLockOpen, faLockOpen,
faTimes, faTimes,
@ -484,6 +486,8 @@ const Status = {
return 'lock-open' return 'lock-open'
case 'direct': case 'direct':
return 'envelope' return 'envelope'
case 'local':
return 'igloo'
default: default:
return 'globe' return 'globe'
} }

View file

@ -102,4 +102,20 @@
} }
} }
} }
&.-extra {
.action-counter {
justify-self: end;
margin-right: 1em;
}
.chevron-icon {
justify-self: end;
}
.extra-button {
justify-self: end;
justify-content: end;
}
}
} }

View file

@ -60,7 +60,7 @@
/> />
</component> </component>
<span <span
v-if="!extra && button.counter?.(funcArg) > 0" v-if="button.counter?.(funcArg) > 0"
class="action-counter" class="action-counter"
> >
{{ button.counter?.(funcArg) }} {{ button.counter?.(funcArg) }}

View file

@ -72,6 +72,23 @@ const StatusContent = {
hideTallStatus () { hideTallStatus () {
return this.mightHideBecauseTall && !this.showingTall return this.mightHideBecauseTall && !this.showingTall
}, },
shouldShowToggle () {
return this.mightHideBecauseSubject || this.mightHideBecauseTall
},
toggleButtonClasses () {
return {
'cw-status-hider': !this.showingMore && this.mightHideBecauseSubject,
'tall-status-hider': !this.showingMore && this.mightHideBecauseTall,
'status-unhider': this.showingMore,
}
},
toggleText () {
if (this.showingMore) {
return this.mightHideBecauseSubject ? this.$t('status.hide_content') : this.$t('general.show_less')
} else {
return this.mightHideBecauseSubject ? this.$t('status.show_content') : this.$t('general.show_more')
}
},
showingMore () { showingMore () {
return (this.mightHideBecauseTall && this.showingTall) || (this.mightHideBecauseSubject && this.expandingSubject) return (this.mightHideBecauseTall && this.showingTall) || (this.mightHideBecauseSubject && this.expandingSubject)
}, },

View file

@ -62,7 +62,6 @@
&.-tall-status { &.-tall-status {
position: relative; position: relative;
height: 16em; height: 16em;
overflow: hidden;
z-index: 1; z-index: 1;
.media-body { .media-body {
@ -82,6 +81,10 @@
mask-composite: exclude; mask-composite: exclude;
} }
} }
&.-expanded {
overflow: visible;
}
} }
& .tall-status-hider, & .tall-status-hider,
@ -95,6 +98,13 @@
text-align: center; text-align: center;
} }
.status-unhider {
margin-top: auto;
position: sticky;
bottom: 0;
padding-bottom: 1em;
}
.tall-status-hider { .tall-status-hider {
position: absolute; position: absolute;
height: 5em; height: 5em;
@ -118,6 +128,10 @@
} }
} }
.toggle-button {
padding: 0.5em;
}
&.-compact { &.-compact {
align-items: start; align-items: start;
flex-direction: row; flex-direction: row;
@ -166,11 +180,11 @@
line-height: inherit; line-height: inherit;
margin: 0; margin: 0;
border: none; border: none;
display: inline-block;
} }
.text-wrapper { .text-wrapper {
display: inline-block; display: inline-block;
width: 100%;
} }
} }
} }

View file

@ -31,17 +31,9 @@
</button> </button>
</div> </div>
<div <div
:class="{'-tall-status': hideTallStatus}"
class="text-wrapper" class="text-wrapper"
:class="{'-tall-status': hideTallStatus, '-expanded': showingMore}"
> >
<button
v-show="hideTallStatus"
class="button-unstyled -link tall-status-hider"
:class="{ '-focused': focused }"
@click.prevent="toggleShowMore"
>
{{ $t("general.show_more") }}
</button>
<RichContent <RichContent
v-if="!hideSubjectStatus && !(singleLine && status.summary_raw_html)" v-if="!hideSubjectStatus && !(singleLine && status.summary_raw_html)"
:class="{ '-single-line': singleLine }" :class="{ '-single-line': singleLine }"
@ -54,45 +46,45 @@
:attentions="status.attentions" :attentions="status.attentions"
@parse-ready="onParseReady" @parse-ready="onParseReady"
/> />
<div
<button v-show="shouldShowToggle"
v-show="hideSubjectStatus" :class="toggleButtonClasses"
class="button-unstyled -link cw-status-hider"
@click.prevent="toggleShowMore"
> >
{{ $t("status.show_content") }} <button
<FAIcon class="btn button-default toggle-button"
v-if="attachmentTypes.includes('image')" :class="{ '-focused': focused }"
icon="image" :aria-expanded="showingMore"
/> @click.prevent="toggleShowMore"
<FAIcon >
v-if="attachmentTypes.includes('video')" {{ toggleText }}
icon="video" <template v-if="!showingMore">
/> <FAIcon
<FAIcon v-if="attachmentTypes.includes('image')"
v-if="attachmentTypes.includes('audio')" icon="image"
icon="music" />
/> <FAIcon
<FAIcon v-if="attachmentTypes.includes('video')"
v-if="attachmentTypes.includes('unknown')" icon="video"
icon="file" />
/> <FAIcon
<FAIcon v-if="attachmentTypes.includes('audio')"
v-if="status.poll && status.poll.options" icon="music"
icon="poll-h" />
/> <FAIcon
<FAIcon v-if="attachmentTypes.includes('unknown')"
v-if="status.card" icon="file"
icon="link" />
/> <FAIcon
</button> v-if="status.poll && status.poll.options"
<button icon="poll-h"
v-show="showingMore && !fullContent" />
class="button-unstyled -link status-unhider" <FAIcon
@click.prevent="toggleShowMore" v-if="status.card"
> icon="link"
{{ tallStatus ? $t("general.show_less") : $t("status.hide_content") }} />
</button> </template>
</button>
</div>
</div> </div>
</div> </div>
<slot v-if="!hideSubjectStatus" /> <slot v-if="!hideSubjectStatus" />

View file

@ -24,7 +24,8 @@ export const timelineNames = (supportsBookmarkFolders) => {
dms: 'nav.dms', dms: 'nav.dms',
'public-timeline': 'nav.public_tl', 'public-timeline': 'nav.public_tl',
'public-external-timeline': 'nav.twkn', 'public-external-timeline': 'nav.twkn',
quotes: 'nav.quotes' quotes: 'nav.quotes',
bubble: 'nav.bubble'
} }
} }
@ -58,7 +59,8 @@ const TimelineMenu = {
currentUser: state => state.users.currentUser, currentUser: state => state.users.currentUser,
privateMode: state => state.instance.private, privateMode: state => state.instance.private,
federating: state => state.instance.federating, federating: state => state.instance.federating,
bookmarkFolders: state => state.instance.pleromaBookmarkFoldersAvailable bookmarkFolders: state => state.instance.pleromaBookmarkFoldersAvailable,
bubbleTimeline: state => state.instance.localBubbleInstances.length > 0
}), }),
timelinesList () { timelinesList () {
return filterNavigation( return filterNavigation(
@ -68,7 +70,8 @@ const TimelineMenu = {
isFederating: this.federating, isFederating: this.federating,
isPrivate: this.privateMode, isPrivate: this.privateMode,
currentUser: this.currentUser, currentUser: this.currentUser,
supportsBookmarkFolders: this.bookmarkFolders supportsBookmarkFolders: this.bookmarkFolders,
supportsBubbleTimeline: this.bubbleTimeline
} }
) )
} }

View file

@ -149,7 +149,10 @@ export default {
}, },
showModerationMenu () { showModerationMenu () {
const privileges = this.loggedIn.privileges const privileges = this.loggedIn.privileges
return this.loggedIn.role === 'admin' || privileges.includes('users_manage_activation_state') || privileges.includes('users_delete') || privileges.includes('users_manage_tags') return this.loggedIn.role === 'admin' ||
privileges.includes('users_manage_activation_state') ||
privileges.includes('users_delete') ||
privileges.includes('users_manage_tags')
}, },
hasNote () { hasNote () {
return this.relationship.note return this.relationship.note

View file

@ -81,7 +81,7 @@ const UserProfile = {
return this.isUs || !this.user.hide_followers return this.isUs || !this.user.hide_followers
}, },
favoritesTabVisible () { favoritesTabVisible () {
return this.isUs || !this.user.hide_favorites return this.isUs || (this.$store.state.instance.pleromaPublicFavouritesAvailable && !this.user.hide_favorites)
}, },
formattedBirthday () { formattedBirthday () {
const browserLocale = localeService.internalToBrowserLocale(this.$i18n.locale) const browserLocale = localeService.internalToBrowserLocale(this.$i18n.locale)

View file

@ -117,6 +117,7 @@
"flash_security": "Note that this can be potentially dangerous since Flash content is still arbitrary code.", "flash_security": "Note that this can be potentially dangerous since Flash content is still arbitrary code.",
"flash_fail": "Failed to load flash content, see console for details.", "flash_fail": "Failed to load flash content, see console for details.",
"scope_in_timeline": { "scope_in_timeline": {
"local": "Non-federated",
"direct": "Direct", "direct": "Direct",
"private": "Followers-only", "private": "Followers-only",
"public": "Public", "public": "Public",
@ -171,6 +172,7 @@
"interactions": "Interactions", "interactions": "Interactions",
"dms": "Direct messages", "dms": "Direct messages",
"public_tl": "Public timeline", "public_tl": "Public timeline",
"bubble": "Bubble timeline",
"timeline": "Timeline", "timeline": "Timeline",
"home_timeline": "Home timeline", "home_timeline": "Home timeline",
"twkn": "Known Network", "twkn": "Known Network",
@ -289,7 +291,8 @@
"text/plain": "Plain text", "text/plain": "Plain text",
"text/html": "HTML", "text/html": "HTML",
"text/markdown": "Markdown", "text/markdown": "Markdown",
"text/bbcode": "BBCode" "text/bbcode": "BBCode",
"text/x.misskeymarkdown": "MFM"
}, },
"content_type_selection": "Post format", "content_type_selection": "Post format",
"content_warning": "Subject (optional)", "content_warning": "Subject (optional)",
@ -1088,7 +1091,10 @@
"reset_value": "Reset", "reset_value": "Reset",
"reset_value_tooltip": "Reset draft", "reset_value_tooltip": "Reset draft",
"hard_reset_value": "Hard reset", "hard_reset_value": "Hard reset",
"hard_reset_value_tooltip": "Remove setting from storage, forcing use of default value" "hard_reset_value_tooltip": "Remove setting from storage, forcing use of default value",
"cache": "Cache",
"clear_asset_cache": "Clear asset cache",
"clear_emoji_cache": "Clear emoji cache"
}, },
"admin_dash": { "admin_dash": {
"window_title": "Administration", "window_title": "Administration",

View file

@ -211,6 +211,7 @@ const api = {
statusId = false, statusId = false,
bookmarkFolderId = false bookmarkFolderId = false
}) { }) {
if (timeline === 'favourites' && !store.rootState.instance.pleromaPublicFavouritesAvailable) return
if (store.state.fetchers[timeline]) return if (store.state.fetchers[timeline]) return
const fetcher = store.state.backendInteractor.startFetchingTimeline({ const fetcher = store.state.backendInteractor.startFetchingTimeline({
@ -281,6 +282,7 @@ const api = {
// Bookmark folders // Bookmark folders
startFetchingBookmarkFolders (store) { startFetchingBookmarkFolders (store) {
if (store.state.fetchers.bookmarkFolders) return if (store.state.fetchers.bookmarkFolders) return
if (!store.rootState.instance.pleromaBookmarkFoldersAvailable) return
const fetcher = store.state.backendInteractor.startFetchingBookmarkFolders({ store }) const fetcher = store.state.backendInteractor.startFetchingBookmarkFolders({ store })
store.commit('addFetcher', { fetcherName: 'bookmarkFolders', fetcher }) store.commit('addFetcher', { fetcherName: 'bookmarkFolders', fetcher })
}, },

View file

@ -1,86 +0,0 @@
import { useOAuthStore } from 'src/stores/oauth.js'
const PASSWORD_STRATEGY = 'password'
const TOKEN_STRATEGY = 'token'
// MFA strategies
const TOTP_STRATEGY = 'totp'
const RECOVERY_STRATEGY = 'recovery'
// initial state
const state = {
settings: {},
strategy: PASSWORD_STRATEGY,
initStrategy: PASSWORD_STRATEGY // default strategy from config
}
const resetState = (state) => {
state.strategy = state.initStrategy
state.settings = {}
}
// getters
const getters = {
settings: (state) => {
return state.settings
},
requiredPassword: (state) => {
return state.strategy === PASSWORD_STRATEGY
},
requiredToken: (state) => {
return state.strategy === TOKEN_STRATEGY
},
requiredTOTP: (state) => {
return state.strategy === TOTP_STRATEGY
},
requiredRecovery: (state) => {
return state.strategy === RECOVERY_STRATEGY
}
}
// mutations
const mutations = {
setInitialStrategy (state, strategy) {
if (strategy) {
state.initStrategy = strategy
state.strategy = strategy
}
},
requirePassword (state) {
state.strategy = PASSWORD_STRATEGY
},
requireToken (state) {
state.strategy = TOKEN_STRATEGY
},
requireMFA (state, { settings }) {
state.settings = settings
state.strategy = TOTP_STRATEGY // default strategy of MFA
},
requireRecovery (state) {
state.strategy = RECOVERY_STRATEGY
},
requireTOTP (state) {
state.strategy = TOTP_STRATEGY
},
abortMFA (state) {
resetState(state)
}
}
// actions
const actions = {
async login ({ state, dispatch }, { access_token: accessToken }) {
useOAuthStore().setToken(accessToken)
await dispatch('loginUser', accessToken, { root: true })
resetState(state)
}
}
export default {
namespaced: true,
state,
getters,
mutations,
actions
}

View file

@ -6,7 +6,6 @@ import api from './api.js'
import config from './config.js' import config from './config.js'
import profileConfig from './profileConfig.js' import profileConfig from './profileConfig.js'
import adminSettings from './adminSettings.js' import adminSettings from './adminSettings.js'
import authFlow from './auth_flow.js'
import drafts from './drafts.js' import drafts from './drafts.js'
import chats from './chats.js' import chats from './chats.js'
@ -19,7 +18,6 @@ export default {
config, config,
profileConfig, profileConfig,
adminSettings, adminSettings,
authFlow,
drafts, drafts,
chats chats
} }

View file

@ -143,7 +143,7 @@ const defaultState = {
emoji: {}, emoji: {},
emojiFetched: false, emojiFetched: false,
unicodeEmojiAnnotations: {}, unicodeEmojiAnnotations: {},
pleromaBackend: true, pleromaExtensionsAvailable: true,
postFormats: [], postFormats: [],
restrictedNicknames: [], restrictedNicknames: [],
safeDM: true, safeDM: true,
@ -156,6 +156,8 @@ const defaultState = {
pleromaChatMessagesAvailable: false, pleromaChatMessagesAvailable: false,
pleromaCustomEmojiReactionsAvailable: false, pleromaCustomEmojiReactionsAvailable: false,
pleromaBookmarkFoldersAvailable: false, pleromaBookmarkFoldersAvailable: false,
pleromaPublicFavouritesAvailable: true,
statusNotificationTypeAvailable: true,
gopherAvailable: false, gopherAvailable: false,
mediaProxyAvailable: false, mediaProxyAvailable: false,
suggestionsEnabled: false, suggestionsEnabled: false,
@ -163,6 +165,7 @@ const defaultState = {
quotingAvailable: false, quotingAvailable: false,
groupActorAvailable: false, groupActorAvailable: false,
blockExpiration: false, blockExpiration: false,
localBubbleInstances: [], // Akkoma
// Html stuff // Html stuff
instanceSpecificPanelContent: '', instanceSpecificPanelContent: '',
@ -341,7 +344,10 @@ const instance = {
async getCustomEmoji ({ commit, state }) { async getCustomEmoji ({ commit, state }) {
try { try {
const res = await window.fetch('/api/pleroma/emoji.json') let res = await window.fetch('/api/v1/pleroma/emoji')
if (!res.ok) {
res = await window.fetch('/api/pleroma/emoji.json')
}
if (res.ok) { if (res.ok) {
const result = await res.json() const result = await res.json()
const values = Array.isArray(result) ? Object.assign({}, ...result) : result const values = Array.isArray(result) ? Object.assign({}, ...result) : result

View file

@ -39,6 +39,7 @@ export const defaultState = () => ({
conversationsObject: {}, conversationsObject: {},
maxId: 0, maxId: 0,
favorites: new Set(), favorites: new Set(),
pleromaScrobblesAvailable: true, // not reported in nodeinfo
timelines: { timelines: {
mentions: emptyTl(), mentions: emptyTl(),
public: emptyTl(), public: emptyTl(),
@ -50,7 +51,8 @@ export const defaultState = () => ({
tag: emptyTl(), tag: emptyTl(),
dms: emptyTl(), dms: emptyTl(),
bookmarks: emptyTl(), bookmarks: emptyTl(),
list: emptyTl() list: emptyTl(),
bubble: emptyTl()
} }
}) })
@ -108,12 +110,21 @@ const sortTimeline = (timeline) => {
} }
const getLatestScrobble = (state, user) => { const getLatestScrobble = (state, user) => {
const scrobblesSupport = state.pleromaScrobblesAvailable
if (!scrobblesSupport) return
if (state.scrobblesNextFetch[user.id] && state.scrobblesNextFetch[user.id] > Date.now()) { if (state.scrobblesNextFetch[user.id] && state.scrobblesNextFetch[user.id] > Date.now()) {
return return
} }
state.scrobblesNextFetch[user.id] = Date.now() + 24 * 60 * 60 * 1000 state.scrobblesNextFetch[user.id] = Date.now() + 24 * 60 * 60 * 1000
if (!scrobblesSupport) return
apiService.fetchScrobbles({ accountId: user.id }).then((scrobbles) => { apiService.fetchScrobbles({ accountId: user.id }).then((scrobbles) => {
if (scrobbles?.error) {
state.pleromaScrobblesAvailable = false
return
}
if (scrobbles.length > 0) { if (scrobbles.length > 0) {
user.latestScrobble = scrobbles[0] user.latestScrobble = scrobbles[0]

View file

@ -607,6 +607,7 @@ const users = {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
const commit = store.commit const commit = store.commit
const dispatch = store.dispatch const dispatch = store.dispatch
const rootState = store.rootState
commit('beginLogin') commit('beginLogin')
store.rootState.api.backendInteractor.verifyCredentials(accessToken) store.rootState.api.backendInteractor.verifyCredentials(accessToken)
.then((data) => { .then((data) => {
@ -673,8 +674,10 @@ const users = {
// Start fetching notifications // Start fetching notifications
dispatch('startFetchingNotifications') dispatch('startFetchingNotifications')
// Start fetching chats if (rootState.instance.pleromaChatMessagesAvailable) {
dispatch('startFetchingChats') // Start fetching chats
dispatch('startFetchingChats')
}
} }
dispatch('startFetchingLists') dispatch('startFetchingLists')

View file

@ -15,7 +15,7 @@ const TAG_USER_URL = '/api/pleroma/admin/users/tag'
const PERMISSION_GROUP_URL = (screenName, right) => `/api/pleroma/admin/users/${screenName}/permission_group/${right}` const PERMISSION_GROUP_URL = (screenName, right) => `/api/pleroma/admin/users/${screenName}/permission_group/${right}`
const ACTIVATE_USER_URL = '/api/pleroma/admin/users/activate' const ACTIVATE_USER_URL = '/api/pleroma/admin/users/activate'
const DEACTIVATE_USER_URL = '/api/pleroma/admin/users/deactivate' const DEACTIVATE_USER_URL = '/api/pleroma/admin/users/deactivate'
const ADMIN_USERS_URL = '/api/pleroma/admin/users' const ADMIN_USERS_URL = '/api/v1/pleroma/admin/users'
const SUGGESTIONS_URL = '/api/v1/suggestions' const SUGGESTIONS_URL = '/api/v1/suggestions'
const NOTIFICATION_SETTINGS_URL = '/api/pleroma/notification_settings' const NOTIFICATION_SETTINGS_URL = '/api/pleroma/notification_settings'
const NOTIFICATION_READ_URL = '/api/v1/pleroma/notifications/read' const NOTIFICATION_READ_URL = '/api/v1/pleroma/notifications/read'
@ -61,6 +61,7 @@ const MASTODON_LIST_TIMELINE_URL = id => `/api/v1/timelines/list/${id}`
const MASTODON_LIST_ACCOUNTS_URL = id => `/api/v1/lists/${id}/accounts` const MASTODON_LIST_ACCOUNTS_URL = id => `/api/v1/lists/${id}/accounts`
const MASTODON_TAG_TIMELINE_URL = tag => `/api/v1/timelines/tag/${tag}` const MASTODON_TAG_TIMELINE_URL = tag => `/api/v1/timelines/tag/${tag}`
const MASTODON_BOOKMARK_TIMELINE_URL = '/api/v1/bookmarks' const MASTODON_BOOKMARK_TIMELINE_URL = '/api/v1/bookmarks'
const AKKOMA_BUBBLE_TIMELINE_URL = '/api/v1/timelines/bubble'
const MASTODON_USER_BLOCKS_URL = '/api/v1/blocks/' const MASTODON_USER_BLOCKS_URL = '/api/v1/blocks/'
const MASTODON_USER_MUTES_URL = '/api/v1/mutes/' const MASTODON_USER_MUTES_URL = '/api/v1/mutes/'
const MASTODON_BLOCK_USER_URL = id => `/api/v1/accounts/${id}/block` const MASTODON_BLOCK_USER_URL = id => `/api/v1/accounts/${id}/block`
@ -99,7 +100,7 @@ const PLEROMA_CHAT_URL = id => `/api/v1/pleroma/chats/by-account-id/${id}`
const PLEROMA_CHAT_MESSAGES_URL = id => `/api/v1/pleroma/chats/${id}/messages` const PLEROMA_CHAT_MESSAGES_URL = id => `/api/v1/pleroma/chats/${id}/messages`
const PLEROMA_CHAT_READ_URL = id => `/api/v1/pleroma/chats/${id}/read` const PLEROMA_CHAT_READ_URL = id => `/api/v1/pleroma/chats/${id}/read`
const PLEROMA_DELETE_CHAT_MESSAGE_URL = (chatId, messageId) => `/api/v1/pleroma/chats/${chatId}/messages/${messageId}` const PLEROMA_DELETE_CHAT_MESSAGE_URL = (chatId, messageId) => `/api/v1/pleroma/chats/${chatId}/messages/${messageId}`
const PLEROMA_ADMIN_REPORTS = '/api/pleroma/admin/reports' const PLEROMA_ADMIN_REPORTS = '/api/v1/pleroma/admin/reports'
const PLEROMA_BACKUP_URL = '/api/v1/pleroma/backups' const PLEROMA_BACKUP_URL = '/api/v1/pleroma/backups'
const PLEROMA_ANNOUNCEMENTS_URL = '/api/v1/pleroma/admin/announcements' const PLEROMA_ANNOUNCEMENTS_URL = '/api/v1/pleroma/admin/announcements'
const PLEROMA_POST_ANNOUNCEMENT_URL = '/api/v1/pleroma/admin/announcements' const PLEROMA_POST_ANNOUNCEMENT_URL = '/api/v1/pleroma/admin/announcements'
@ -111,10 +112,10 @@ const PLEROMA_USER_FAVORITES_TIMELINE_URL = id => `/api/v1/pleroma/accounts/${id
const PLEROMA_BOOKMARK_FOLDERS_URL = '/api/v1/pleroma/bookmark_folders' const PLEROMA_BOOKMARK_FOLDERS_URL = '/api/v1/pleroma/bookmark_folders'
const PLEROMA_BOOKMARK_FOLDER_URL = id => `/api/v1/pleroma/bookmark_folders/${id}` const PLEROMA_BOOKMARK_FOLDER_URL = id => `/api/v1/pleroma/bookmark_folders/${id}`
const PLEROMA_ADMIN_CONFIG_URL = '/api/pleroma/admin/config' const PLEROMA_ADMIN_CONFIG_URL = '/api/v1/pleroma/admin/config'
const PLEROMA_ADMIN_DESCRIPTIONS_URL = '/api/pleroma/admin/config/descriptions' const PLEROMA_ADMIN_DESCRIPTIONS_URL = '/api/v1/pleroma/admin/config/descriptions'
const PLEROMA_ADMIN_FRONTENDS_URL = '/api/pleroma/admin/frontends' const PLEROMA_ADMIN_FRONTENDS_URL = '/api/v1/pleroma/admin/frontends'
const PLEROMA_ADMIN_FRONTENDS_INSTALL_URL = '/api/pleroma/admin/frontends/install' const PLEROMA_ADMIN_FRONTENDS_INSTALL_URL = '/api/v1/pleroma/admin/frontends/install'
const PLEROMA_EMOJI_RELOAD_URL = '/api/pleroma/admin/reload_emoji' const PLEROMA_EMOJI_RELOAD_URL = '/api/pleroma/admin/reload_emoji'
const PLEROMA_EMOJI_IMPORT_FS_URL = '/api/pleroma/emoji/packs/import' const PLEROMA_EMOJI_IMPORT_FS_URL = '/api/pleroma/emoji/packs/import'
@ -714,7 +715,8 @@ const fetchTimeline = ({
publicFavorites: PLEROMA_USER_FAVORITES_TIMELINE_URL, publicFavorites: PLEROMA_USER_FAVORITES_TIMELINE_URL,
tag: MASTODON_TAG_TIMELINE_URL, tag: MASTODON_TAG_TIMELINE_URL,
bookmarks: MASTODON_BOOKMARK_TIMELINE_URL, bookmarks: MASTODON_BOOKMARK_TIMELINE_URL,
quotes: PLEROMA_STATUS_QUOTES_URL quotes: PLEROMA_STATUS_QUOTES_URL,
bubble: AKKOMA_BUBBLE_TIMELINE_URL
} }
const isNotifications = timeline === 'notifications' const isNotifications = timeline === 'notifications'
const params = [] const params = []
@ -764,7 +766,7 @@ const fetchTimeline = ({
if (replyVisibility !== 'all') { if (replyVisibility !== 'all') {
params.push(['reply_visibility', replyVisibility]) params.push(['reply_visibility', replyVisibility])
} }
if (includeTypes.length > 0) { if (includeTypes.size > 0) {
includeTypes.forEach(type => { includeTypes.forEach(type => {
params.push(['include_types[]', type]) params.push(['include_types[]', type])
}) })

View file

@ -95,6 +95,32 @@ export const getContrastRatioLayers = (text, layers, bedrock) => {
return getContrastRatio(alphaBlendLayers(bedrock, layers), text) return getContrastRatio(alphaBlendLayers(bedrock, layers), text)
} }
/**
* Blending of two solid colors with a user-defined operator: origin +- value
*
* @param {Object} origin - base color
* @param {Object} value - modification argument
* @param {string} operator - math operator to use
*/
export const arithmeticBlend = (origin, value, operator) => {
const func = (a, b) => {
switch (operator) {
case '+':
return Math.min(a + b, 255)
case '-':
return Math.max(a - b, 0)
default:
return a
}
}
return {
r: func(origin.r, value.r),
g: func(origin.g, value.g),
b: func(origin.b, value.b),
}
}
/** /**
* This performs alpha blending between solid background and semi-transparent foreground * This performs alpha blending between solid background and semi-transparent foreground
* *

View file

@ -91,6 +91,8 @@ export const parseUser = (data) => {
output.bot = data.bot output.bot = data.bot
output.privileges = []
if (data.pleroma) { if (data.pleroma) {
if (data.pleroma.settings_store) { if (data.pleroma.settings_store) {
output.storage = data.pleroma.settings_store['pleroma-fe'] output.storage = data.pleroma.settings_store['pleroma-fe']
@ -317,20 +319,18 @@ export const parseStatus = (data) => {
output.edited_at = data.edited_at output.edited_at = data.edited_at
const { pleroma } = data
if (data.pleroma) { if (data.pleroma) {
const { pleroma } = data
output.text = pleroma.content ? data.pleroma.content['text/plain'] : data.content output.text = pleroma.content ? data.pleroma.content['text/plain'] : data.content
output.summary = pleroma.spoiler_text ? data.pleroma.spoiler_text['text/plain'] : data.spoiler_text output.summary = pleroma.spoiler_text ? data.pleroma.spoiler_text['text/plain'] : data.spoiler_text
output.statusnet_conversation_id = data.pleroma.conversation_id output.statusnet_conversation_id = data.pleroma.conversation_id
output.is_local = pleroma.local output.is_local = pleroma.local
output.in_reply_to_screen_name = data.pleroma.in_reply_to_account_acct output.in_reply_to_screen_name = pleroma.in_reply_to_account_acct
output.thread_muted = pleroma.thread_muted output.thread_muted = pleroma.thread_muted
output.emoji_reactions = pleroma.emoji_reactions output.emoji_reactions = pleroma.emoji_reactions
output.parent_visible = pleroma.parent_visible === undefined ? true : pleroma.parent_visible output.parent_visible = pleroma.parent_visible === undefined ? true : pleroma.parent_visible
output.quote = pleroma.quote ? parseStatus(pleroma.quote) : undefined output.quote_visible = pleroma.quote_visible || true
output.quote_id = pleroma.quote_id ? pleroma.quote_id : (output.quote ? output.quote.id : undefined)
output.quote_url = pleroma.quote_url
output.quote_visible = pleroma.quote_visible
output.quotes_count = pleroma.quotes_count output.quotes_count = pleroma.quotes_count
output.bookmark_folder_id = pleroma.bookmark_folder output.bookmark_folder_id = pleroma.bookmark_folder
} else { } else {
@ -338,6 +338,12 @@ export const parseStatus = (data) => {
output.summary = data.spoiler_text output.summary = data.spoiler_text
} }
const quoteRaw = pleroma?.quote || data.quote
const quoteData = quoteRaw ? parseStatus(quoteRaw) : undefined
output.quote = quoteData
output.quote_id = data.quote?.id ?? data.quote_id ?? quoteData?.id ?? pleroma.quote_id
output.quote_url = data.quote?.url ?? quoteData?.url ?? pleroma.quote_url
output.in_reply_to_status_id = data.in_reply_to_id output.in_reply_to_status_id = data.in_reply_to_id
output.in_reply_to_user_id = data.in_reply_to_account_id output.in_reply_to_user_id = data.in_reply_to_account_id
output.replies_count = data.replies_count output.replies_count = data.replies_count

View file

@ -2,9 +2,13 @@ import { useInterfaceStore } from 'src/stores/interface.js'
import apiService from '../api/api.service.js' import apiService from '../api/api.service.js'
import { promiseInterval } from '../promise_interval/promise_interval.js' import { promiseInterval } from '../promise_interval/promise_interval.js'
const update = ({ store, notifications, older }) => {
store.dispatch('addNewNotifications', { notifications, older })
}
//
// For using include_types when fetching notifications. // For using include_types when fetching notifications.
// Note: chat_mention excluded as pleroma-fe polls them separately // Note: chat_mention excluded as pleroma-fe polls them separately
const mastoApiNotificationTypes = [ const mastoApiNotificationTypes = new Set([
'mention', 'mention',
'status', 'status',
'favourite', 'favourite',
@ -14,21 +18,22 @@ const mastoApiNotificationTypes = [
'move', 'move',
'poll', 'poll',
'pleroma:emoji_reaction', 'pleroma:emoji_reaction',
'pleroma:chat_mention', 'pleroma:report',
'pleroma:report' 'test'
] ])
const update = ({ store, notifications, older }) => {
store.dispatch('addNewNotifications', { notifications, older })
}
const fetchAndUpdate = ({ store, credentials, older = false, since }) => { const fetchAndUpdate = ({ store, credentials, older = false, since }) => {
const args = { credentials } const args = { credentials }
const { getters } = store const { getters } = store
const rootState = store.rootState || store.state const rootState = store.rootState || store.state
const timelineData = rootState.notifications const timelineData = rootState.notifications
const hideMutedPosts = getters.mergedConfig.hideMutedPosts const hideMutedPosts = getters.mergedConfig.hideMutedPosts
if (rootState.instance.pleromaChatMessagesAvailable) {
mastoApiNotificationTypes.add('pleroma:chat_mention')
}
args.includeTypes = mastoApiNotificationTypes args.includeTypes = mastoApiNotificationTypes
args.withMuted = !hideMutedPosts args.withMuted = !hideMutedPosts
@ -72,7 +77,17 @@ const fetchNotifications = ({ store, args, older }) => {
return apiService.fetchTimeline(args) return apiService.fetchTimeline(args)
.then((response) => { .then((response) => {
if (response.errors) { if (response.errors) {
throw new Error(`${response.status} ${response.statusText}`) if (response.status === 400 && response.statusText.includes('Invalid value for enum')) {
response
.statusText
.matchAll(/(\w+) - Invalid value for enum./g)
.toArray()
.map(x => x[1])
.forEach(x => mastoApiNotificationTypes.delete(x))
return fetchNotifications({ store, args, older })
} else {
throw new Error(`${response.status} ${response.statusText}`)
}
} }
const notifications = response.data const notifications = response.data
update({ store, notifications, older }) update({ store, notifications, older })

View file

@ -1,47 +1,76 @@
import { init, getEngineChecksum } from '../theme_data/theme_data_3.service.js' import { init, getEngineChecksum } from '../theme_data/theme_data_3.service.js'
import { getCssRules } from '../theme_data/css_utils.js' import { getCssRules } from '../theme_data/css_utils.js'
import { defaultState } from 'src/modules/default_config_state.js' import { defaultState } from 'src/modules/default_config_state.js'
import { chunk } from 'lodash' import { chunk, throttle } from 'lodash'
import localforage from 'localforage' import localforage from 'localforage'
// On platforms where this is not supported, it will return undefined // On platforms where this is not supported, it will return undefined
// Otherwise it will return an array // Otherwise it will return an array
const supportsAdoptedStyleSheets = !!document.adoptedStyleSheets const supportsAdoptedStyleSheets = !!document.adoptedStyleSheets
const createStyleSheet = (id) => { const stylesheets = {}
if (supportsAdoptedStyleSheets) {
return { export const createStyleSheet = (id, priority = 1000) => {
el: null, if (stylesheets[id]) return stylesheets[id]
sheet: new CSSStyleSheet(), const newStyleSheet = {
rules: [] rules: [],
ready: false,
priority,
clear () {
this.rules = []
},
addRule (rule) {
let newRule = rule
if (!CSS.supports?.('backdrop-filter', 'blur()')) {
newRule = newRule.replace(/backdrop-filter:[^;]+;/g, '') // Remove backdrop-filter
}
this.rules.push(
newRule
.replace(/var\(--shadowFilter\)[^;]*;/g, '') // Remove shadowFilter references
)
} }
} }
const el = document.getElementById(id) stylesheets[id] = newStyleSheet
// Clear all rules in it return newStyleSheet
for (let i = el.sheet.cssRules.length - 1; i >= 0; --i) {
el.sheet.deleteRule(i)
}
return {
el,
sheet: el.sheet,
rules: []
}
} }
const EAGER_STYLE_ID = 'pleroma-eager-styles'
const LAZY_STYLE_ID = 'pleroma-lazy-styles'
const adoptStyleSheets = (styles) => { export const adoptStyleSheets = throttle(() => {
if (supportsAdoptedStyleSheets) { if (supportsAdoptedStyleSheets) {
document.adoptedStyleSheets = styles.map(s => s.sheet) document.adoptedStyleSheets = Object
.values(stylesheets)
.filter(x => x.ready)
.sort((a, b) => a.priority - b.priority)
.map(sheet => {
const css = new CSSStyleSheet()
sheet.rules.forEach(r => css.insertRule(r))
return css
})
} else {
const holder = document.getElementById('custom-styles-holder')
for (let i = holder.sheet.cssRules.length - 1; i >= 0; --i) {
holder.sheet.deleteRule(i)
}
Object
.values(stylesheets)
.filter(x => x.ready)
.sort((a, b) => a.priority - b.priority)
.forEach(sheet => {
sheet.rules.forEach(r => holder.sheet.insertRule(r))
})
} }
// Some older browsers do not support document.adoptedStyleSheets. // Some older browsers do not support document.adoptedStyleSheets.
// In this case, we use the <style> elements. // In this case, we use the <style> elements.
// Since the <style> elements we need are already in the DOM, there // Since the <style> elements we need are already in the DOM, there
// is nothing to do here. // is nothing to do here.
} }, 500)
const EAGER_STYLE_ID = 'pleroma-eager-styles'
const LAZY_STYLE_ID = 'pleroma-lazy-styles'
export const generateTheme = (inputRuleset, callbacks, debug) => { export const generateTheme = (inputRuleset, callbacks, debug) => {
const { const {
@ -94,13 +123,17 @@ export const tryLoadCache = async () => {
if (!cache) return null if (!cache) return null
try { try {
if (cache.engineChecksum === getEngineChecksum()) { if (cache.engineChecksum === getEngineChecksum()) {
const eagerStyles = createStyleSheet(EAGER_STYLE_ID) const eagerStyles = createStyleSheet(EAGER_STYLE_ID, 10)
const lazyStyles = createStyleSheet(LAZY_STYLE_ID) const lazyStyles = createStyleSheet(LAZY_STYLE_ID, 20)
cache.data[0].forEach(rule => eagerStyles.sheet.insertRule(rule, 'index-max')) cache.data[0].forEach(rule => eagerStyles.addRule(rule))
cache.data[1].forEach(rule => lazyStyles.sheet.insertRule(rule, 'index-max')) cache.data[1].forEach(rule => lazyStyles.addRule(rule))
adoptStyleSheets([eagerStyles, lazyStyles]) eagerStyles.ready = true
lazyStyles.ready = true
// Don't do this, we need to wait until config adopts its styles first
//adoptStyleSheets()
console.info(`Loaded theme from cache`) console.info(`Loaded theme from cache`)
return true return true
@ -120,57 +153,29 @@ export const applyTheme = (
onFinish = () => {}, onFinish = () => {},
debug debug
) => { ) => {
const eagerStyles = createStyleSheet(EAGER_STYLE_ID) const eagerStyles = createStyleSheet(EAGER_STYLE_ID, 10)
const lazyStyles = createStyleSheet(LAZY_STYLE_ID) const lazyStyles = createStyleSheet(LAZY_STYLE_ID, 20)
const insertRule = (styles, rule) => {
try {
// Try to use modern syntax first
try {
styles.sheet.insertRule(rule, 'index-max')
styles.rules.push(rule)
} catch {
// Fallback for older browsers that don't support 'index-max'
styles.sheet.insertRule(rule, styles.sheet.cssRules.length)
styles.rules.push(rule)
}
} catch (e) {
console.warn('Can\'t insert rule due to lack of support', e, rule)
// Try to sanitize the rule for better compatibility
try {
// Remove any potentially problematic CSS features
let sanitizedRule = rule
.replace(/backdrop-filter:[^;]+;/g, '') // Remove backdrop-filter
.replace(/var\(--shadowFilter\)[^;]*;/g, '') // Remove shadowFilter references
if (sanitizedRule !== rule) {
styles.sheet.insertRule(sanitizedRule, styles.sheet.cssRules.length)
styles.rules.push(sanitizedRule)
}
} catch (e2) {
console.error('Failed to insert even sanitized rule', e2)
}
}
}
const { lazyProcessFunc } = generateTheme( const { lazyProcessFunc } = generateTheme(
input, input,
{ {
onNewRule (rule, isLazy) { onNewRule (rule, isLazy) {
if (isLazy) { if (isLazy) {
insertRule(lazyStyles, rule) lazyStyles.addRule(rule)
} else { } else {
insertRule(eagerStyles, rule) eagerStyles.addRule(rule)
} }
}, },
onEagerFinished () { onEagerFinished () {
adoptStyleSheets([eagerStyles]) eagerStyles.ready = true
adoptStyleSheets()
onEagerFinish() onEagerFinish()
console.info('Eager part of theme finished, waiting for lazy part to finish to store cache') console.info('Eager part of theme finished, waiting for lazy part to finish to store cache')
}, },
onLazyFinished () { onLazyFinished () {
adoptStyleSheets([eagerStyles, lazyStyles]) lazyStyles.ready = true
adoptStyleSheets()
const cache = { engineChecksum: getEngineChecksum(), data: [eagerStyles.rules, lazyStyles.rules] } const cache = { engineChecksum: getEngineChecksum(), data: [eagerStyles.rules, lazyStyles.rules] }
onFinish(cache) onFinish(cache)
localforage.setItem('pleromafe-theme-cache', cache) localforage.setItem('pleromafe-theme-cache', cache)
@ -234,28 +239,23 @@ export const applyConfig = (input) => {
return return
} }
const head = document.head
const rules = Object const rules = Object
.entries(config) .entries(config)
.filter(([, v]) => v) .filter(([, v]) => v)
.map(([k, v]) => `--${k}: ${v}`).join(';') .map(([k, v]) => `--${k}: ${v}`).join(';')
document.getElementById('style-config')?.remove() const styleSheet = createStyleSheet('theme-holder', 30)
const styleEl = document.createElement('style')
styleEl.id = 'style-config'
head.appendChild(styleEl)
const styleSheet = styleEl.sheet
styleSheet.toString() styleSheet.addRule(`:root { ${rules} }`)
styleSheet.insertRule(`:root { ${rules} }`, 'index-max')
// TODO find a way to make this not apply to theme previews // TODO find a way to make this not apply to theme previews
if (Object.prototype.hasOwnProperty.call(config, 'forcedRoundness')) { if (Object.prototype.hasOwnProperty.call(config, 'forcedRoundness')) {
styleSheet.insertRule(` *:not(.preview-block) { styleSheet.addRule(` *:not(.preview-block) {
--roundness: var(--forcedRoundness) !important; --roundness: var(--forcedRoundness) !important;
}`, 'index-max') }`)
} }
styleSheet.ready = true
adoptStyleSheets()
} }
export const getResourcesIndex = async (url, parser = JSON.parse) => { export const getResourcesIndex = async (url, parser = JSON.parse) => {

View file

@ -18,6 +18,7 @@ function isPushSupported () {
} }
function getOrCreateServiceWorker () { function getOrCreateServiceWorker () {
if (!isSWSupported()) return
const swType = process.env.HAS_MODULE_SERVICE_WORKER ? 'module' : 'classic' const swType = process.env.HAS_MODULE_SERVICE_WORKER ? 'module' : 'classic'
return navigator.serviceWorker.register('/sw-pleroma.js', { type: swType }) return navigator.serviceWorker.register('/sw-pleroma.js', { type: swType })
.catch((err) => console.error('Unable to get or create a service worker.', err)) .catch((err) => console.error('Unable to get or create a service worker.', err))
@ -146,3 +147,11 @@ export function unregisterPushNotifications (token) {
]).catch((e) => console.warn(`Failed to disable Web Push Notifications: ${e.message}`)) ]).catch((e) => console.warn(`Failed to disable Web Push Notifications: ${e.message}`))
} }
} }
export const shouldCache = process.env.NODE_ENV === 'production'
export const cacheKey = 'pleroma-fe'
export const emojiCacheKey = 'pleroma-fe-emoji'
export const clearCache = (key) => caches.delete(key)
export { getOrCreateServiceWorker }

View file

@ -1,8 +1,8 @@
import { convert, brightness } from 'chromatism' import { convert, brightness } from 'chromatism'
import { alphaBlend, getTextColor, relativeLuminance } from '../color_convert/color_convert.js' import { alphaBlend, arithmeticBlend, getTextColor, relativeLuminance } from '../color_convert/color_convert.js'
export const process = (text, functions, { findColor, findShadow }, { dynamicVars, staticVars }) => { export const process = (text, functions, { findColor, findShadow }, { dynamicVars, staticVars }) => {
const { funcName, argsString } = /\$(?<funcName>\w+)\((?<argsString>[#a-zA-Z0-9-,.'"\s]*)\)/.exec(text).groups const { funcName, argsString } = /\$(?<funcName>\w+)\((?<argsString>[#a-zA-Z0-9-+,.'"\s]*)\)/.exec(text).groups
const args = argsString.split(/ /g).map(a => a.trim()) const args = argsString.split(/ /g).map(a => a.trim())
const func = functions[funcName] const func = functions[funcName]
@ -81,6 +81,23 @@ export const colorFunctions = {
return alphaBlend(background, amount, foreground) return alphaBlend(background, amount, foreground)
} }
}, },
shift: {
argsNeeded: 2,
documentation: 'Arithmetic blend between two colors',
args: [
'origin: base color',
'value: shift value',
'operator: math operator to use (+ or -)'
],
exec: (args, { findColor }, { dynamicVars, staticVars }) => {
const [originArg, valueArg, operatorArg] = args
const origin = convert(findColor(originArg, { dynamicVars, staticVars })).rgb
const value = convert(findColor(valueArg, { dynamicVars, staticVars })).rgb
return arithmeticBlend(origin, value, operatorArg)
}
},
boost: { boost: {
argsNeeded: 2, argsNeeded: 2,
documentation: 'If given color is dark makes it darker, if color is light - makes it lighter', documentation: 'If given color is dark makes it darker, if color is light - makes it lighter',

View file

@ -54,7 +54,7 @@ const fetchAndUpdate = ({
args.bookmarkFolderId = bookmarkFolderId args.bookmarkFolderId = bookmarkFolderId
args.tag = tag args.tag = tag
args.withMuted = !hideMutedPosts args.withMuted = !hideMutedPosts
if (loggedIn && ['friends', 'public', 'publicAndExternal'].includes(timeline)) { if (loggedIn && ['friends', 'public', 'publicAndExternal', 'bubble'].includes(timeline)) {
args.replyVisibility = replyVisibility args.replyVisibility = replyVisibility
} }
@ -63,6 +63,10 @@ const fetchAndUpdate = ({
return apiService.fetchTimeline(args) return apiService.fetchTimeline(args)
.then(response => { .then(response => {
if (response.errors) { if (response.errors) {
if (timeline === 'favorites') {
rootState.instance.pleromaPublicFavouritesAvailable = false
return
}
throw new Error(`${response.status} ${response.statusText}`) throw new Error(`${response.status} ${response.statusText}`)
} }

69
src/stores/auth_flow.js Normal file
View file

@ -0,0 +1,69 @@
import { useOAuthStore } from 'src/stores/oauth.js'
import { defineStore } from 'pinia'
const PASSWORD_STRATEGY = 'password'
const TOKEN_STRATEGY = 'token'
// MFA strategies
const TOTP_STRATEGY = 'totp'
const RECOVERY_STRATEGY = 'recovery'
export const useAuthFlowStore = defineStore('authFlow', {
// initial state
state: () => ({
settings: {},
strategy: PASSWORD_STRATEGY,
initStrategy: PASSWORD_STRATEGY // default strategy from config
}),
// getters
getters: {
requiredPassword: (state) => {
return state.strategy === PASSWORD_STRATEGY
},
requiredToken: (state) => {
return state.strategy === TOKEN_STRATEGY
},
requiredTOTP: (state) => {
return state.strategy === TOTP_STRATEGY
},
requiredRecovery: (state) => {
return state.strategy === RECOVERY_STRATEGY
},
},
actions: {
setInitialStrategy (strategy) {
if (strategy) {
this.initStrategy = strategy
this.strategy = strategy
}
},
requirePassword () {
this.strategy = PASSWORD_STRATEGY
},
requireToken () {
this.strategy = TOKEN_STRATEGY
},
requireMFA ({ settings }) {
this.settings = settings
this.strategy = TOTP_STRATEGY // default strategy of MFA
},
requireRecovery () {
this.strategy = RECOVERY_STRATEGY
},
requireTOTP () {
this.strategy = TOTP_STRATEGY
},
abortMFA () {
this.resetState()
},
resetState () {
this.strategy = this.initStrategy
this.settings = {}
},
async login ({ access_token: accessToken }) {
useOAuthStore().setToken(accessToken)
await window.vuex.dispatch('loginUser', accessToken, { root: true })
this.resetState()
}
}
})

109
src/sw.js
View file

@ -1,8 +1,10 @@
/* eslint-env serviceworker */ /* eslint-env serviceworker */
import 'virtual:pleroma-fe/service_worker_env'
import { storage } from 'src/lib/storage.js' import { storage } from 'src/lib/storage.js'
import { parseNotification } from './services/entity_normalizer/entity_normalizer.service.js' import { parseNotification } from './services/entity_normalizer/entity_normalizer.service.js'
import { prepareNotificationObject } from './services/notification_utils/notification_utils.js' import { prepareNotificationObject } from './services/notification_utils/notification_utils.js'
import { shouldCache, cacheKey, emojiCacheKey } from './services/sw/sw.js'
import { createI18n } from 'vue-i18n' import { createI18n } from 'vue-i18n'
// Collects all messages for service workers // Collects all messages for service workers
// Needed because service workers cannot use dynamic imports // Needed because service workers cannot use dynamic imports
@ -85,6 +87,80 @@ const showPushNotification = async (event) => {
return Promise.resolve() return Promise.resolve()
} }
const cacheFiles = self.serviceWorkerOption.assets
const isEmoji = req => {
if (req.method !== 'GET') {
return false
}
const url = new URL(req.url)
return url.pathname.startsWith('/emoji/')
}
const isNotMedia = req => {
if (req.method !== 'GET') {
return false
}
const url = new URL(req.url)
return !url.pathname.startsWith('/media/')
}
const isAsset = req => {
const url = new URL(req.url)
return cacheFiles.includes(url.pathname)
}
const isSuccessful = (resp) => {
if (!resp.ok) {
return false
}
if ((new URL(resp.url)).pathname === '/index.html') {
// For index.html itself, there is no fallback possible.
return true
}
const type = resp.headers.get('Content-Type')
// Backend will revert to index.html if the file does not exist, so text/html for emojis and assets is a failure
return type && !type.includes('text/html')
}
self.addEventListener('install', async (event) => {
if (shouldCache) {
event.waitUntil((async () => {
// Do not preload i18n and emoji annotations to speed up loading
const shouldPreload = (route) => {
return !route.startsWith('/static/js/i18n/') && !route.startsWith('/static/js/emoji-annotations/')
}
const cache = await caches.open(cacheKey)
await Promise.allSettled(cacheFiles.filter(shouldPreload).map(async (route) => {
// https://developer.mozilla.org/en-US/docs/Web/API/Cache/add
// originally we used addAll() but it will raise a problem in one edge case:
// when the file for the route is not found, backend will return index.html with code 200
// but it's wrong, and it's cached, so we end up with a bad cache.
// this can happen when you refresh when you are in the process of upgrading
// the frontend.
const resp = await fetch(route)
if (isSuccessful(resp)) {
await cache.put(route, resp)
}
}))
})())
}
})
self.addEventListener('activate', async (event) => {
if (shouldCache) {
event.waitUntil((async () => {
const cache = await caches.open(cacheKey)
const keys = await cache.keys()
await Promise.all(
keys.filter(request => {
const url = new URL(request.url)
const shouldKeep = cacheFiles.includes(url.pathname)
return !shouldKeep
}).map(k => cache.delete(k))
)
})())
}
})
self.addEventListener('push', async (event) => { self.addEventListener('push', async (event) => {
if (event.data) { if (event.data) {
// Supposedly, we HAVE to return a promise inside waitUntil otherwise it will // Supposedly, we HAVE to return a promise inside waitUntil otherwise it will
@ -143,4 +219,35 @@ self.addEventListener('notificationclick', (event) => {
})) }))
}) })
console.log('sw here') self.addEventListener('fetch', (event) => {
// Do not mess up with remote things
const isSameOrigin = (new URL(event.request.url)).origin === self.location.origin
if (shouldCache && event.request.method === 'GET' && isSameOrigin && isNotMedia(event.request)) {
// this is a bit spammy
// console.debug('[Service worker] fetch:', event.request.url)
event.respondWith((async () => {
const r = await caches.match(event.request)
const isEmojiReq = isEmoji(event.request)
if (r && isSuccessful(r)) {
console.debug('[Service worker] already cached:', event.request.url)
return r
}
try {
const response = await fetch(event.request)
if (response.ok &&
isSuccessful(response) &&
(isEmojiReq || isAsset(event.request))) {
console.debug(`[Service worker] caching ${isEmojiReq ? 'emoji' : 'asset'}:`, event.request.url)
const cache = await caches.open(isEmojiReq ? emojiCacheKey : cacheKey)
await cache.put(event.request.clone(), response.clone())
}
return response
} catch (e) {
console.error('[Service worker] error when caching emoji:', e)
throw e
}
})())
}
})

View file

@ -172,7 +172,14 @@ export default defineConfig(async ({ mode, command }) => {
return 'static/js/[name].[hash].js' return 'static/js/[name].[hash].js'
} }
}, },
chunkFileNames () { chunkFileNames (chunkInfo) {
if (chunkInfo.facadeModuleId) {
if (chunkInfo.facadeModuleId.includes('node_modules/@kazvmoe-infra/unicode-emoji-json/annotations/')) {
return 'static/js/emoji-annotations/[name].[hash].js'
} else if (chunkInfo.facadeModuleId.includes('src/i18n/')) {
return 'static/js/i18n/[name].[hash].js'
}
}
return 'static/js/[name].[hash].js' return 'static/js/[name].[hash].js'
}, },
assetFileNames (assetInfo) { assetFileNames (assetInfo) {

334
yarn.lock
View file

@ -411,6 +411,13 @@
dependencies: dependencies:
"@babel/types" "^7.27.1" "@babel/types" "^7.27.1"
"@babel/parser@^7.27.5":
version "7.27.5"
resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.27.5.tgz#ed22f871f110aa285a6fd934a0efed621d118826"
integrity sha512-OsQd175SxWkGlzbny8J3K8TnnDD0N3lrIUtB92xwyRpzaenGZhxDvxN/JgU00U3CDZNj9tPuDJ5H0WS4Nt3vKg==
dependencies:
"@babel/types" "^7.27.3"
"@babel/plugin-bugfix-firefox-class-in-computed-class-key@^7.27.1": "@babel/plugin-bugfix-firefox-class-in-computed-class-key@^7.27.1":
version "7.27.1" version "7.27.1"
resolved "https://registry.yarnpkg.com/@babel/plugin-bugfix-firefox-class-in-computed-class-key/-/plugin-bugfix-firefox-class-in-computed-class-key-7.27.1.tgz#61dd8a8e61f7eb568268d1b5f129da3eee364bf9" resolved "https://registry.yarnpkg.com/@babel/plugin-bugfix-firefox-class-in-computed-class-key/-/plugin-bugfix-firefox-class-in-computed-class-key-7.27.1.tgz#61dd8a8e61f7eb568268d1b5f129da3eee364bf9"
@ -1061,6 +1068,14 @@
"@babel/helper-string-parser" "^7.27.1" "@babel/helper-string-parser" "^7.27.1"
"@babel/helper-validator-identifier" "^7.27.1" "@babel/helper-validator-identifier" "^7.27.1"
"@babel/types@^7.27.3":
version "7.27.6"
resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.27.6.tgz#a434ca7add514d4e646c80f7375c0aa2befc5535"
integrity sha512-ETyHEk2VHHvl9b9jZP5IHPavHYk57EhanlRRuae9XCpb/j5bDCbPPMOBfCWhnl/7EDJz0jEMCi/RhccCE8r1+Q==
dependencies:
"@babel/helper-string-parser" "^7.27.1"
"@babel/helper-validator-identifier" "^7.27.1"
"@bazel/runfiles@^6.3.1": "@bazel/runfiles@^6.3.1":
version "6.3.1" version "6.3.1"
resolved "https://registry.yarnpkg.com/@bazel/runfiles/-/runfiles-6.3.1.tgz#3f8824b2d82853377799d42354b4df78ab0ace0b" resolved "https://registry.yarnpkg.com/@bazel/runfiles/-/runfiles-6.3.1.tgz#3f8824b2d82853377799d42354b4df78ab0ace0b"
@ -1587,10 +1602,10 @@
"@jridgewell/resolve-uri" "^3.1.0" "@jridgewell/resolve-uri" "^3.1.0"
"@jridgewell/sourcemap-codec" "^1.4.14" "@jridgewell/sourcemap-codec" "^1.4.14"
"@kazvmoe-infra/pinch-zoom-element@1.2.0": "@kazvmoe-infra/pinch-zoom-element@1.3.0":
version "1.2.0" version "1.3.0"
resolved "https://registry.yarnpkg.com/@kazvmoe-infra/pinch-zoom-element/-/pinch-zoom-element-1.2.0.tgz#eb3ca34c53b4410c689d60aca02f4a497ce84aba" resolved "https://registry.yarnpkg.com/@kazvmoe-infra/pinch-zoom-element/-/pinch-zoom-element-1.3.0.tgz#a5e35ab190f93d016b8a83f69004fc69a8e6b774"
integrity sha512-HBrhH5O/Fsp2bB7EGTXzCsBAVcMjknSagKC5pBdGpKsF8meHISR0kjDIdw4YoE0S+0oNMwJ6ZUZyIBrdywxPPw== integrity sha512-YIx1ZsCLyFB/xhJVMc81yLNJO/ZYveYGws0033ZPPyrFLgLwrQrkx79lC1xBIcWlWnvPioTVdmOpnBeTv/0zNw==
dependencies: dependencies:
pointer-tracker "^2.0.3" pointer-tracker "^2.0.3"
@ -1622,10 +1637,10 @@
zod "^3.23.8" zod "^3.23.8"
zod-to-json-schema "^3.24.1" zod-to-json-schema "^3.24.1"
"@mswjs/interceptors@^0.37.0": "@mswjs/interceptors@^0.39.1":
version "0.37.6" version "0.39.2"
resolved "https://registry.yarnpkg.com/@mswjs/interceptors/-/interceptors-0.37.6.tgz#2635319b7a81934e1ef1b5593ef7910347e2b761" resolved "https://registry.yarnpkg.com/@mswjs/interceptors/-/interceptors-0.39.2.tgz#de9de0ab23f99d387c7904df7219a92157d1d666"
integrity sha512-wK+5pLK5XFmgtH3aQ2YVvA3HohS3xqV/OxuVOdNx9Wpnz7VE/fnC+e1A7ln6LFYeck7gOJ/dsZV6OLplOtAJ2w== integrity sha512-RuzCup9Ct91Y7V79xwCb146RaBRHZ7NBbrIUySumd1rpKqHL5OonaqrGIbug5hNwP/fRyxFMA6ISgw4FTtYFYg==
dependencies: dependencies:
"@open-draft/deferred-promise" "^2.2.0" "@open-draft/deferred-promise" "^2.2.0"
"@open-draft/logger" "^0.3.0" "@open-draft/logger" "^0.3.0"
@ -2309,6 +2324,17 @@
estree-walker "^2.0.2" estree-walker "^2.0.2"
source-map-js "^1.2.0" source-map-js "^1.2.0"
"@vue/compiler-core@3.5.17":
version "3.5.17"
resolved "https://registry.yarnpkg.com/@vue/compiler-core/-/compiler-core-3.5.17.tgz#23d291bd01b863da3ef2e26e7db84d8e01a9b4c5"
integrity sha512-Xe+AittLbAyV0pabcN7cP7/BenRBNcteM4aSDCtRvGw0d9OL+HG1u/XHLY/kt1q4fyMeZYXyIYrsHuPSiDPosA==
dependencies:
"@babel/parser" "^7.27.5"
"@vue/shared" "3.5.17"
entities "^4.5.0"
estree-walker "^2.0.2"
source-map-js "^1.2.1"
"@vue/compiler-dom@3.5.13": "@vue/compiler-dom@3.5.13":
version "3.5.13" version "3.5.13"
resolved "https://registry.yarnpkg.com/@vue/compiler-dom/-/compiler-dom-3.5.13.tgz#bb1b8758dbc542b3658dda973b98a1c9311a8a58" resolved "https://registry.yarnpkg.com/@vue/compiler-dom/-/compiler-dom-3.5.13.tgz#bb1b8758dbc542b3658dda973b98a1c9311a8a58"
@ -2317,7 +2343,30 @@
"@vue/compiler-core" "3.5.13" "@vue/compiler-core" "3.5.13"
"@vue/shared" "3.5.13" "@vue/shared" "3.5.13"
"@vue/compiler-sfc@3.5.13", "@vue/compiler-sfc@^3.5.13": "@vue/compiler-dom@3.5.17":
version "3.5.17"
resolved "https://registry.yarnpkg.com/@vue/compiler-dom/-/compiler-dom-3.5.17.tgz#7bc19a20e23b670243a64b47ce3a890239b870be"
integrity sha512-+2UgfLKoaNLhgfhV5Ihnk6wB4ljyW1/7wUIog2puUqajiC29Lp5R/IKDdkebh9jTbTogTbsgB+OY9cEWzG95JQ==
dependencies:
"@vue/compiler-core" "3.5.17"
"@vue/shared" "3.5.17"
"@vue/compiler-sfc@3.5.17":
version "3.5.17"
resolved "https://registry.yarnpkg.com/@vue/compiler-sfc/-/compiler-sfc-3.5.17.tgz#c518871276e26593612bdab36f3f5bcd053b13bf"
integrity sha512-rQQxbRJMgTqwRugtjw0cnyQv9cP4/4BxWfTdRBkqsTfLOHWykLzbOc3C4GGzAmdMDxhzU/1Ija5bTjMVrddqww==
dependencies:
"@babel/parser" "^7.27.5"
"@vue/compiler-core" "3.5.17"
"@vue/compiler-dom" "3.5.17"
"@vue/compiler-ssr" "3.5.17"
"@vue/shared" "3.5.17"
estree-walker "^2.0.2"
magic-string "^0.30.17"
postcss "^8.5.6"
source-map-js "^1.2.1"
"@vue/compiler-sfc@^3.5.13":
version "3.5.13" version "3.5.13"
resolved "https://registry.yarnpkg.com/@vue/compiler-sfc/-/compiler-sfc-3.5.13.tgz#461f8bd343b5c06fac4189c4fef8af32dea82b46" resolved "https://registry.yarnpkg.com/@vue/compiler-sfc/-/compiler-sfc-3.5.13.tgz#461f8bd343b5c06fac4189c4fef8af32dea82b46"
integrity sha512-6VdaljMpD82w6c2749Zhf5T9u5uLBWKnVue6XWxprDobftnletJ8+oel7sexFfM3qIxNmVE7LSFGTpv6obNyaQ== integrity sha512-6VdaljMpD82w6c2749Zhf5T9u5uLBWKnVue6XWxprDobftnletJ8+oel7sexFfM3qIxNmVE7LSFGTpv6obNyaQ==
@ -2340,6 +2389,14 @@
"@vue/compiler-dom" "3.5.13" "@vue/compiler-dom" "3.5.13"
"@vue/shared" "3.5.13" "@vue/shared" "3.5.13"
"@vue/compiler-ssr@3.5.17":
version "3.5.17"
resolved "https://registry.yarnpkg.com/@vue/compiler-ssr/-/compiler-ssr-3.5.17.tgz#14ba3b7bba6e0e1fd02002316263165a5d1046c7"
integrity sha512-hkDbA0Q20ZzGgpj5uZjb9rBzQtIHLS78mMilwrlpWk2Ep37DYntUz0PonQ6kr113vfOEdM+zTBuJDaceNIW0tQ==
dependencies:
"@vue/compiler-dom" "3.5.17"
"@vue/shared" "3.5.17"
"@vue/devtools-api@^6.0.0-beta.11", "@vue/devtools-api@^6.5.0", "@vue/devtools-api@^6.6.4": "@vue/devtools-api@^6.0.0-beta.11", "@vue/devtools-api@^6.5.0", "@vue/devtools-api@^6.6.4":
version "6.6.4" version "6.6.4"
resolved "https://registry.yarnpkg.com/@vue/devtools-api/-/devtools-api-6.6.4.tgz#cbe97fe0162b365edc1dba80e173f90492535343" resolved "https://registry.yarnpkg.com/@vue/devtools-api/-/devtools-api-6.6.4.tgz#cbe97fe0162b365edc1dba80e173f90492535343"
@ -2372,44 +2429,49 @@
dependencies: dependencies:
rfdc "^1.4.1" rfdc "^1.4.1"
"@vue/reactivity@3.5.13": "@vue/reactivity@3.5.17":
version "3.5.13" version "3.5.17"
resolved "https://registry.yarnpkg.com/@vue/reactivity/-/reactivity-3.5.13.tgz#b41ff2bb865e093899a22219f5b25f97b6fe155f" resolved "https://registry.yarnpkg.com/@vue/reactivity/-/reactivity-3.5.17.tgz#169b5dcf96c7f23788e5ed9745ec8a7227f2125e"
integrity sha512-NaCwtw8o48B9I6L1zl2p41OHo/2Z4wqYGGIK1Khu5T7yxrn+ATOixn/Udn2m+6kZKB/J7cuT9DbWWhRxqixACg== integrity sha512-l/rmw2STIscWi7SNJp708FK4Kofs97zc/5aEPQh4bOsReD/8ICuBcEmS7KGwDj5ODQLYWVN2lNibKJL1z5b+Lw==
dependencies: dependencies:
"@vue/shared" "3.5.13" "@vue/shared" "3.5.17"
"@vue/runtime-core@3.5.13": "@vue/runtime-core@3.5.17":
version "3.5.13" version "3.5.17"
resolved "https://registry.yarnpkg.com/@vue/runtime-core/-/runtime-core-3.5.13.tgz#1fafa4bf0b97af0ebdd9dbfe98cd630da363a455" resolved "https://registry.yarnpkg.com/@vue/runtime-core/-/runtime-core-3.5.17.tgz#b17bd41e13011e85e9b1025545292d43f5512730"
integrity sha512-Fj4YRQ3Az0WTZw1sFe+QDb0aXCerigEpw418pw1HBUKFtnQHWzwojaukAs2X/c9DQz4MQ4bsXTGlcpGxU/RCIw== integrity sha512-QQLXa20dHg1R0ri4bjKeGFKEkJA7MMBxrKo2G+gJikmumRS7PTD4BOU9FKrDQWMKowz7frJJGqBffYMgQYS96Q==
dependencies: dependencies:
"@vue/reactivity" "3.5.13" "@vue/reactivity" "3.5.17"
"@vue/shared" "3.5.13" "@vue/shared" "3.5.17"
"@vue/runtime-dom@3.5.13": "@vue/runtime-dom@3.5.17":
version "3.5.13" version "3.5.17"
resolved "https://registry.yarnpkg.com/@vue/runtime-dom/-/runtime-dom-3.5.13.tgz#610fc795de9246300e8ae8865930d534e1246215" resolved "https://registry.yarnpkg.com/@vue/runtime-dom/-/runtime-dom-3.5.17.tgz#8e325e29cd03097fe179032fc8df384a426fc83a"
integrity sha512-dLaj94s93NYLqjLiyFzVs9X6dWhTdAlEAciC3Moq7gzAc13VJUdCnjjRurNM6uTLFATRHexHCTu/Xp3eW6yoog== integrity sha512-8El0M60TcwZ1QMz4/os2MdlQECgGoVHPuLnQBU3m9h3gdNRW9xRmI8iLS4t/22OQlOE6aJvNNlBiCzPHur4H9g==
dependencies: dependencies:
"@vue/reactivity" "3.5.13" "@vue/reactivity" "3.5.17"
"@vue/runtime-core" "3.5.13" "@vue/runtime-core" "3.5.17"
"@vue/shared" "3.5.13" "@vue/shared" "3.5.17"
csstype "^3.1.3" csstype "^3.1.3"
"@vue/server-renderer@3.5.13": "@vue/server-renderer@3.5.17":
version "3.5.13" version "3.5.17"
resolved "https://registry.yarnpkg.com/@vue/server-renderer/-/server-renderer-3.5.13.tgz#429ead62ee51de789646c22efe908e489aad46f7" resolved "https://registry.yarnpkg.com/@vue/server-renderer/-/server-renderer-3.5.17.tgz#9b8fd6a40a3d55322509fafe78ac841ede649fbe"
integrity sha512-wAi4IRJV/2SAW3htkTlB+dHeRmpTiVIK1OGLWV1yeStVSebSQQOwGwIq0D3ZIoBj2C2qpgz5+vX9iEBkTdk5YA== integrity sha512-BOHhm8HalujY6lmC3DbqF6uXN/K00uWiEeF22LfEsm9Q93XeJ/plHTepGwf6tqFcF7GA5oGSSAAUock3VvzaCA==
dependencies: dependencies:
"@vue/compiler-ssr" "3.5.13" "@vue/compiler-ssr" "3.5.17"
"@vue/shared" "3.5.13" "@vue/shared" "3.5.17"
"@vue/shared@3.5.13", "@vue/shared@^3.5.13": "@vue/shared@3.5.13", "@vue/shared@^3.5.13":
version "3.5.13" version "3.5.13"
resolved "https://registry.yarnpkg.com/@vue/shared/-/shared-3.5.13.tgz#87b309a6379c22b926e696893237826f64339b6f" resolved "https://registry.yarnpkg.com/@vue/shared/-/shared-3.5.13.tgz#87b309a6379c22b926e696893237826f64339b6f"
integrity sha512-/hnE/qP5ZoGpol0a5mDi45bOd7t3tjYJBjsgCsivow7D48cJeV5l05RD82lPqi7gRiphZM37rnhW1l6ZoCNNnQ== integrity sha512-/hnE/qP5ZoGpol0a5mDi45bOd7t3tjYJBjsgCsivow7D48cJeV5l05RD82lPqi7gRiphZM37rnhW1l6ZoCNNnQ==
"@vue/shared@3.5.17":
version "3.5.17"
resolved "https://registry.yarnpkg.com/@vue/shared/-/shared-3.5.17.tgz#e8b3a41f0be76499882a89e8ed40d86a70fa4b70"
integrity sha512-CabR+UN630VnsJO/jHWYBC1YVXyMq94KKp6iF5MQgZJs5I8cmjw6oVMO1oDbtBkENSHSSn/UadWlW/OAgdmKrg==
"@vue/test-utils@2.4.6": "@vue/test-utils@2.4.6":
version "2.4.6" version "2.4.6"
resolved "https://registry.yarnpkg.com/@vue/test-utils/-/test-utils-2.4.6.tgz#7d534e70c4319d2a587d6a3b45a39e9695ade03c" resolved "https://registry.yarnpkg.com/@vue/test-utils/-/test-utils-2.4.6.tgz#7d534e70c4319d2a587d6a3b45a39e9695ade03c"
@ -3348,17 +3410,6 @@ cross-spawn@7.0.6, cross-spawn@^7.0.3, cross-spawn@^7.0.6:
shebang-command "^2.0.0" shebang-command "^2.0.0"
which "^2.0.1" which "^2.0.1"
cross-spawn@^6.0.0:
version "6.0.6"
resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-6.0.6.tgz#30d0efa0712ddb7eb5a76e1e8721bffafa6b5d57"
integrity sha512-VqCUuhcd1iB+dsv8gxPttb5iZh/D0iubSP21g36KXdEuf6I5JiioesUVjpCdHV9MZRUfVFlvwtIUyPfxo5trtw==
dependencies:
nice-try "^1.0.4"
path-key "^2.0.1"
semver "^5.5.0"
shebang-command "^1.2.0"
which "^1.2.9"
css-functions-list@^3.2.3: css-functions-list@^3.2.3:
version "3.2.3" version "3.2.3"
resolved "https://registry.yarnpkg.com/css-functions-list/-/css-functions-list-3.2.3.tgz#95652b0c24f0f59b291a9fc386041a19d4f40dbe" resolved "https://registry.yarnpkg.com/css-functions-list/-/css-functions-list-3.2.3.tgz#95652b0c24f0f59b291a9fc386041a19d4f40dbe"
@ -4199,18 +4250,20 @@ eventsource@^3.0.2:
dependencies: dependencies:
eventsource-parser "^3.0.1" eventsource-parser "^3.0.1"
execa@^1.0.0: execa@^5.1.1:
version "1.0.0" version "5.1.1"
resolved "https://registry.yarnpkg.com/execa/-/execa-1.0.0.tgz#c6236a5bb4df6d6f15e88e7f017798216749ddd8" resolved "https://registry.yarnpkg.com/execa/-/execa-5.1.1.tgz#f80ad9cbf4298f7bd1d4c9555c21e93741c411dd"
integrity sha512-adbxcyWV46qiHyvSp50TKt05tB4tK3HcmF7/nxfAdhnox83seTDbwnaqKO4sXRy7roHAIFqJP/Rw/AuEbX61LA== integrity sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==
dependencies: dependencies:
cross-spawn "^6.0.0" cross-spawn "^7.0.3"
get-stream "^4.0.0" get-stream "^6.0.0"
is-stream "^1.1.0" human-signals "^2.1.0"
npm-run-path "^2.0.0" is-stream "^2.0.0"
p-finally "^1.0.0" merge-stream "^2.0.0"
signal-exit "^3.0.0" npm-run-path "^4.0.1"
strip-eof "^1.0.0" onetime "^5.1.2"
signal-exit "^3.0.3"
strip-final-newline "^2.0.0"
expect-type@^1.2.1: expect-type@^1.2.1:
version "1.2.1" version "1.2.1"
@ -4556,13 +4609,6 @@ get-proto@^1.0.0, get-proto@^1.0.1:
dunder-proto "^1.0.1" dunder-proto "^1.0.1"
es-object-atoms "^1.0.0" es-object-atoms "^1.0.0"
get-stream@^4.0.0:
version "4.1.0"
resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-4.1.0.tgz#c1b255575f3dc21d59bfc79cd3d2b46b1c3a54b5"
integrity sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w==
dependencies:
pump "^3.0.0"
get-stream@^5.1.0: get-stream@^5.1.0:
version "5.2.0" version "5.2.0"
resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-5.2.0.tgz#4966a1795ee5ace65e706c4b7beb71257d6e22d3" resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-5.2.0.tgz#4966a1795ee5ace65e706c4b7beb71257d6e22d3"
@ -4570,6 +4616,11 @@ get-stream@^5.1.0:
dependencies: dependencies:
pump "^3.0.0" pump "^3.0.0"
get-stream@^6.0.0:
version "6.0.1"
resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-6.0.1.tgz#a262d8eef67aced57c2852ad6167526a43cbf7b7"
integrity sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==
get-symbol-description@^1.1.0: get-symbol-description@^1.1.0:
version "1.1.0" version "1.1.0"
resolved "https://registry.yarnpkg.com/get-symbol-description/-/get-symbol-description-1.1.0.tgz#7bdd54e0befe8ffc9f3b4e203220d9f1e881b6ee" resolved "https://registry.yarnpkg.com/get-symbol-description/-/get-symbol-description-1.1.0.tgz#7bdd54e0befe8ffc9f3b4e203220d9f1e881b6ee"
@ -4868,6 +4919,11 @@ https-proxy-agent@^7.0.5, https-proxy-agent@^7.0.6:
agent-base "^7.1.2" agent-base "^7.1.2"
debug "4" debug "4"
human-signals@^2.1.0:
version "2.1.0"
resolved "https://registry.yarnpkg.com/human-signals/-/human-signals-2.1.0.tgz#dc91fcba42e4d06e4abaed33b3e7a3c02f514ea0"
integrity sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==
iconv-lite@0.6.3, iconv-lite@^0.6.3: iconv-lite@0.6.3, iconv-lite@^0.6.3:
version "0.6.3" version "0.6.3"
resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.6.3.tgz#a52f80bf38da1952eb5c681790719871a1a72501" resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.6.3.tgz#a52f80bf38da1952eb5c681790719871a1a72501"
@ -4940,11 +4996,6 @@ internal-slot@^1.1.0:
hasown "^2.0.2" hasown "^2.0.2"
side-channel "^1.1.0" side-channel "^1.1.0"
interpret@^1.0.0:
version "1.4.0"
resolved "https://registry.yarnpkg.com/interpret/-/interpret-1.4.0.tgz#665ab8bc4da27a774a40584e812e3e0fa45b1a1e"
integrity sha512-agE4QfB2Lkp9uICn7BAqoscw4SZP9kTE2hxiFI3jBPmXJfdqiahTbUuKGsMoN2GtqL9AxhYioAcVvgsb1HvRbA==
ip-address@^9.0.5: ip-address@^9.0.5:
version "9.0.5" version "9.0.5"
resolved "https://registry.yarnpkg.com/ip-address/-/ip-address-9.0.5.tgz#117a960819b08780c3bd1f14ef3c1cc1d3f3ea5a" resolved "https://registry.yarnpkg.com/ip-address/-/ip-address-9.0.5.tgz#117a960819b08780c3bd1f14ef3c1cc1d3f3ea5a"
@ -5163,10 +5214,10 @@ is-shared-array-buffer@^1.0.2, is-shared-array-buffer@^1.0.4:
dependencies: dependencies:
call-bound "^1.0.3" call-bound "^1.0.3"
is-stream@^1.1.0: is-stream@^2.0.0:
version "1.1.0" version "2.0.1"
resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-1.1.0.tgz#12d4a3dd4e68e0b79ceb8dbc84173ae80d91ca44" resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-2.0.1.tgz#fac1e3d53b97ad5a9d0ae9cef2389f5810a5c077"
integrity sha512-uQPm8kcs47jx38atAcWTVxyltQYoPT68y9aWYdV6yWXSyW8mzSat0TL6CiWdZeCdF3KrAvpVtnHbTv4RN+rqdQ== integrity sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==
is-string@^1.0.7, is-string@^1.1.1: is-string@^1.0.7, is-string@^1.1.1:
version "1.1.1" version "1.1.1"
@ -5650,6 +5701,11 @@ merge-descriptors@^2.0.0:
resolved "https://registry.yarnpkg.com/merge-descriptors/-/merge-descriptors-2.0.0.tgz#ea922f660635a2249ee565e0449f951e6b603808" resolved "https://registry.yarnpkg.com/merge-descriptors/-/merge-descriptors-2.0.0.tgz#ea922f660635a2249ee565e0449f951e6b603808"
integrity sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g== integrity sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g==
merge-stream@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/merge-stream/-/merge-stream-2.0.0.tgz#52823629a14dd00c9770fb6ad47dc6310f2c1f60"
integrity sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==
merge2@^1.3.0, merge2@^1.4.1: merge2@^1.3.0, merge2@^1.4.1:
version "1.4.1" version "1.4.1"
resolved "https://registry.yarnpkg.com/merge2/-/merge2-1.4.1.tgz#4368892f885e907455a6fd7dc55c0c9d404990ae" resolved "https://registry.yarnpkg.com/merge2/-/merge2-1.4.1.tgz#4368892f885e907455a6fd7dc55c0c9d404990ae"
@ -5793,16 +5849,16 @@ ms@2.1.3, ms@^2.1.1, ms@^2.1.3:
resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.3.tgz#574c8138ce1d2b5861f0b44579dbadd60c6615b2" resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.3.tgz#574c8138ce1d2b5861f0b44579dbadd60c6615b2"
integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA== integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==
msw@2.7.6: msw@2.10.2:
version "2.7.6" version "2.10.2"
resolved "https://registry.yarnpkg.com/msw/-/msw-2.7.6.tgz#1471ce4311f4c173f287dced31dee211b6958deb" resolved "https://registry.yarnpkg.com/msw/-/msw-2.10.2.tgz#e7a56ed0b6865b00a30b4c4a5b59e5388fd48315"
integrity sha512-P+rwn43ktxN8ghcl8q+hSAUlEi0PbJpDhGmDkw4zeUnRj3hBCVynWD+dTu38yLYKCE9ZF1OYcvpy7CTBRcqkZA== integrity sha512-RCKM6IZseZQCWcSWlutdf590M8nVfRHG1ImwzOtwz8IYxgT4zhUO0rfTcTvDGiaFE0Rhcc+h43lcF3Jc9gFtwQ==
dependencies: dependencies:
"@bundled-es-modules/cookie" "^2.0.1" "@bundled-es-modules/cookie" "^2.0.1"
"@bundled-es-modules/statuses" "^1.0.1" "@bundled-es-modules/statuses" "^1.0.1"
"@bundled-es-modules/tough-cookie" "^0.1.6" "@bundled-es-modules/tough-cookie" "^0.1.6"
"@inquirer/confirm" "^5.0.0" "@inquirer/confirm" "^5.0.0"
"@mswjs/interceptors" "^0.37.0" "@mswjs/interceptors" "^0.39.1"
"@open-draft/deferred-promise" "^2.2.0" "@open-draft/deferred-promise" "^2.2.0"
"@open-draft/until" "^2.1.0" "@open-draft/until" "^2.1.0"
"@types/cookie" "^0.6.0" "@types/cookie" "^0.6.0"
@ -5822,7 +5878,7 @@ mute-stream@^2.0.0:
resolved "https://registry.yarnpkg.com/mute-stream/-/mute-stream-2.0.0.tgz#a5446fc0c512b71c83c44d908d5c7b7b4c493b2b" resolved "https://registry.yarnpkg.com/mute-stream/-/mute-stream-2.0.0.tgz#a5446fc0c512b71c83c44d908d5c7b7b4c493b2b"
integrity sha512-WWdIxpyjEn+FhQJQQv9aQAYlHoNVdzIzUySNV1gHUPDSdZJ3yZn7pAAbQcV7B56Mvu881q9FZV+0Vx2xC44VWA== integrity sha512-WWdIxpyjEn+FhQJQQv9aQAYlHoNVdzIzUySNV1gHUPDSdZJ3yZn7pAAbQcV7B56Mvu881q9FZV+0Vx2xC44VWA==
nanoid@^3.3.8: nanoid@^3.3.11, nanoid@^3.3.8:
version "3.3.11" version "3.3.11"
resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.11.tgz#4f4f112cefbe303202f2199838128936266d185b" resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.11.tgz#4f4f112cefbe303202f2199838128936266d185b"
integrity sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w== integrity sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==
@ -5842,11 +5898,6 @@ netmask@^2.0.2:
resolved "https://registry.yarnpkg.com/netmask/-/netmask-2.0.2.tgz#8b01a07644065d536383835823bc52004ebac5e7" resolved "https://registry.yarnpkg.com/netmask/-/netmask-2.0.2.tgz#8b01a07644065d536383835823bc52004ebac5e7"
integrity sha512-dBpDMdxv9Irdq66304OLfEmQ9tbNRFnFTuZiLo+bD+r332bBmMJ8GBLXklIXXgxd3+v9+KUnZaUR5PJMa75Gsg== integrity sha512-dBpDMdxv9Irdq66304OLfEmQ9tbNRFnFTuZiLo+bD+r332bBmMJ8GBLXklIXXgxd3+v9+KUnZaUR5PJMa75Gsg==
nice-try@^1.0.4:
version "1.0.5"
resolved "https://registry.yarnpkg.com/nice-try/-/nice-try-1.0.5.tgz#a3378a7696ce7d223e88fc9b764bd7ef1089e366"
integrity sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==
nightwatch-axe-verbose@^2.3.0: nightwatch-axe-verbose@^2.3.0:
version "2.3.1" version "2.3.1"
resolved "https://registry.yarnpkg.com/nightwatch-axe-verbose/-/nightwatch-axe-verbose-2.3.1.tgz#42cd226989cb5205b699db42d74b1b967587d099" resolved "https://registry.yarnpkg.com/nightwatch-axe-verbose/-/nightwatch-axe-verbose-2.3.1.tgz#42cd226989cb5205b699db42d74b1b967587d099"
@ -5921,12 +5972,12 @@ normalize-range@^0.1.2:
resolved "https://registry.yarnpkg.com/normalize-range/-/normalize-range-0.1.2.tgz#2d10c06bdfd312ea9777695a4d28439456b75942" resolved "https://registry.yarnpkg.com/normalize-range/-/normalize-range-0.1.2.tgz#2d10c06bdfd312ea9777695a4d28439456b75942"
integrity sha512-bdok/XvKII3nUpklnV6P2hxtMNrCboOjAcyBuQnWEhO665FwrSNRxU+AqpsyvO6LgGYPspN+lu5CLtw4jPRKNA== integrity sha512-bdok/XvKII3nUpklnV6P2hxtMNrCboOjAcyBuQnWEhO665FwrSNRxU+AqpsyvO6LgGYPspN+lu5CLtw4jPRKNA==
npm-run-path@^2.0.0: npm-run-path@^4.0.1:
version "2.0.2" version "4.0.1"
resolved "https://registry.yarnpkg.com/npm-run-path/-/npm-run-path-2.0.2.tgz#35a9232dfa35d7067b4cb2ddf2357b1871536c5f" resolved "https://registry.yarnpkg.com/npm-run-path/-/npm-run-path-4.0.1.tgz#b7ecd1e5ed53da8e37a55e1c2269e0b97ed748ea"
integrity sha512-lJxZYlT4DW/bRUtFh1MQIWqmLwQfAxnqWG4HhEdjMlkrJYnJn0Jrr2u3mgxqaWsdiBc76TYkTG/mhrnYTuzfHw== integrity sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==
dependencies: dependencies:
path-key "^2.0.0" path-key "^3.0.0"
nth-check@^2.1.1: nth-check@^2.1.1:
version "2.1.1" version "2.1.1"
@ -6018,7 +6069,7 @@ once@^1.3.0, once@^1.3.1, once@^1.4.0:
dependencies: dependencies:
wrappy "1" wrappy "1"
onetime@^5.1.0: onetime@^5.1.0, onetime@^5.1.2:
version "5.1.2" version "5.1.2"
resolved "https://registry.yarnpkg.com/onetime/-/onetime-5.1.2.tgz#d0e96ebb56b07476df1dd9c4806e5237985ca45e" resolved "https://registry.yarnpkg.com/onetime/-/onetime-5.1.2.tgz#d0e96ebb56b07476df1dd9c4806e5237985ca45e"
integrity sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg== integrity sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==
@ -6075,11 +6126,6 @@ own-keys@^1.0.1:
object-keys "^1.1.1" object-keys "^1.1.1"
safe-push-apply "^1.0.0" safe-push-apply "^1.0.0"
p-finally@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/p-finally/-/p-finally-1.0.0.tgz#3fbcfb15b899a44123b34b6dcc18b724336a2cae"
integrity sha512-LICb2p9CB7FS+0eR1oqWnHhp0FljGLZCWBE9aix0Uye9W8LTQPwMTYVGWQWIw9RdQiDg4+epXQODwIYJtSJaow==
p-limit@^2.0.0, p-limit@^2.2.0: p-limit@^2.0.0, p-limit@^2.2.0:
version "2.3.0" version "2.3.0"
resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-2.3.0.tgz#3dd33c647a214fdfffd835933eb086da0dc21db1" resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-2.3.0.tgz#3dd33c647a214fdfffd835933eb086da0dc21db1"
@ -6203,12 +6249,7 @@ path-is-absolute@^1.0.0:
resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f" resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f"
integrity sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg== integrity sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==
path-key@^2.0.0, path-key@^2.0.1: path-key@^3.0.0, path-key@^3.1.0:
version "2.0.1"
resolved "https://registry.yarnpkg.com/path-key/-/path-key-2.0.1.tgz#411cadb574c5a140d3a4b1910d40d80cc9f40b40"
integrity sha512-fEHGKCSmUSDPv4uoj8AlD+joPlq3peND+HRYyxFz4KPw4z926S/b8rIuFs2FYJg3BwsxJf6A9/3eIdLaYC+9Dw==
path-key@^3.1.0:
version "3.1.1" version "3.1.1"
resolved "https://registry.yarnpkg.com/path-key/-/path-key-3.1.1.tgz#581f6ade658cbba65a0d3380de7753295054f375" resolved "https://registry.yarnpkg.com/path-key/-/path-key-3.1.1.tgz#581f6ade658cbba65a0d3380de7753295054f375"
integrity sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q== integrity sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==
@ -6416,6 +6457,15 @@ postcss@8.5.3, postcss@^8.4.48, postcss@^8.5.0, postcss@^8.5.3:
picocolors "^1.1.1" picocolors "^1.1.1"
source-map-js "^1.2.1" source-map-js "^1.2.1"
postcss@^8.5.6:
version "8.5.6"
resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.5.6.tgz#2825006615a619b4f62a9e7426cc120b349a8f3c"
integrity sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==
dependencies:
nanoid "^3.3.11"
picocolors "^1.1.1"
source-map-js "^1.2.1"
prelude-ls@^1.2.1: prelude-ls@^1.2.1:
version "1.2.1" version "1.2.1"
resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.2.1.tgz#debc6489d7a6e6b0e7611888cec880337d316396" resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.2.1.tgz#debc6489d7a6e6b0e7611888cec880337d316396"
@ -6596,13 +6646,6 @@ readdirp@~3.6.0:
dependencies: dependencies:
picomatch "^2.2.1" picomatch "^2.2.1"
rechoir@^0.6.2:
version "0.6.2"
resolved "https://registry.yarnpkg.com/rechoir/-/rechoir-0.6.2.tgz#85204b54dba82d5742e28c96756ef43af50e3384"
integrity sha512-HFM8rkZ+i3zrV+4LQjwQ0W+ez98pApMGM3HUrN04j3CqzPOzl9nmP15Y8YXNm8QHGv/eacOVEjqhmWpkRV0NAw==
dependencies:
resolve "^1.1.6"
reflect.getprototypeof@^1.0.6, reflect.getprototypeof@^1.0.9: reflect.getprototypeof@^1.0.6, reflect.getprototypeof@^1.0.9:
version "1.0.10" version "1.0.10"
resolved "https://registry.yarnpkg.com/reflect.getprototypeof/-/reflect.getprototypeof-1.0.10.tgz#c629219e78a3316d8b604c765ef68996964e7bf9" resolved "https://registry.yarnpkg.com/reflect.getprototypeof/-/reflect.getprototypeof-1.0.10.tgz#c629219e78a3316d8b604c765ef68996964e7bf9"
@ -6705,7 +6748,7 @@ resolve-pkg-maps@^1.0.0:
resolved "https://registry.yarnpkg.com/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz#616b3dc2c57056b5588c31cdf4b3d64db133720f" resolved "https://registry.yarnpkg.com/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz#616b3dc2c57056b5588c31cdf4b3d64db133720f"
integrity sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw== integrity sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==
resolve@^1.1.6, resolve@^1.14.2, resolve@^1.22.4: resolve@^1.14.2, resolve@^1.22.4:
version "1.22.10" version "1.22.10"
resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.22.10.tgz#b663e83ffb09bbf2386944736baae803029b8b39" resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.22.10.tgz#b663e83ffb09bbf2386944736baae803029b8b39"
integrity sha512-NPRy+/ncIMeDlTAsuqwKIiferiawhefFJtkNSW0qZJEqMEb+qBt/77B/jGeeek+F0uOeN05CDa6HXbbIgtVX4w== integrity sha512-NPRy+/ncIMeDlTAsuqwKIiferiawhefFJtkNSW0qZJEqMEb+qBt/77B/jGeeek+F0uOeN05CDa6HXbbIgtVX4w==
@ -6832,10 +6875,10 @@ safe-regex-test@^1.1.0:
resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a" resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a"
integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg== integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==
sass@1.87.0: sass@1.89.2:
version "1.87.0" version "1.89.2"
resolved "https://registry.yarnpkg.com/sass/-/sass-1.87.0.tgz#8cceb36fa63fb48a8d5d7f2f4c13b49c524b723e" resolved "https://registry.yarnpkg.com/sass/-/sass-1.89.2.tgz#a771716aeae774e2b529f72c0ff2dfd46c9de10e"
integrity sha512-d0NoFH4v6SjEK7BoX810Jsrhj7IQSYHAHLi/iSpgqKc7LaIDshFRlSg5LOymf9FqQhxEHs2W5ZQXlvy0KD45Uw== integrity sha512-xCmtksBKd/jdJ9Bt9p7nPKiuqrlBMBuuGkQlkhZjjQk3Ty48lv93k5Dq6OPkKt4XwxDJ7tvlfrTa1MPA9bf+QA==
dependencies: dependencies:
chokidar "^4.0.0" chokidar "^4.0.0"
immutable "^5.0.2" immutable "^5.0.2"
@ -6872,12 +6915,12 @@ semver@7.5.4:
dependencies: dependencies:
lru-cache "^6.0.0" lru-cache "^6.0.0"
semver@7.7.1, semver@^7.3.5, semver@^7.5.3, semver@^7.5.4, semver@^7.6.3: semver@7.7.2:
version "7.7.1" version "7.7.2"
resolved "https://registry.yarnpkg.com/semver/-/semver-7.7.1.tgz#abd5098d82b18c6c81f6074ff2647fd3e7220c9f" resolved "https://registry.yarnpkg.com/semver/-/semver-7.7.2.tgz#67d99fdcd35cec21e6f8b87a7fd515a33f982b58"
integrity sha512-hlq8tAfn0m/61p4BVRcPzIGr6LKiMwo4VM6dGi6pt4qcRkmNzTcWq6eCEjEh+qXjkMDvPlOFFSGwQjoEa6gyMA== integrity sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==
semver@^5.5.0, semver@^5.6.0: semver@^5.6.0:
version "5.7.2" version "5.7.2"
resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.2.tgz#48d55db737c3287cd4835e17fa13feace1c41ef8" resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.2.tgz#48d55db737c3287cd4835e17fa13feace1c41ef8"
integrity sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g== integrity sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==
@ -6887,6 +6930,11 @@ semver@^6.3.1:
resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.1.tgz#556d2ef8689146e46dcea4bfdd095f3434dffcb4" resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.1.tgz#556d2ef8689146e46dcea4bfdd095f3434dffcb4"
integrity sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA== integrity sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==
semver@^7.3.5, semver@^7.5.3, semver@^7.5.4, semver@^7.6.3:
version "7.7.1"
resolved "https://registry.yarnpkg.com/semver/-/semver-7.7.1.tgz#abd5098d82b18c6c81f6074ff2647fd3e7220c9f"
integrity sha512-hlq8tAfn0m/61p4BVRcPzIGr6LKiMwo4VM6dGi6pt4qcRkmNzTcWq6eCEjEh+qXjkMDvPlOFFSGwQjoEa6gyMA==
send@^1.1.0, send@^1.2.0: send@^1.1.0, send@^1.2.0:
version "1.2.0" version "1.2.0"
resolved "https://registry.yarnpkg.com/send/-/send-1.2.0.tgz#32a7554fb777b831dfa828370f773a3808d37212" resolved "https://registry.yarnpkg.com/send/-/send-1.2.0.tgz#32a7554fb777b831dfa828370f773a3808d37212"
@ -6974,13 +7022,6 @@ shallow-clone@^3.0.0:
dependencies: dependencies:
kind-of "^6.0.2" kind-of "^6.0.2"
shebang-command@^1.2.0:
version "1.2.0"
resolved "https://registry.yarnpkg.com/shebang-command/-/shebang-command-1.2.0.tgz#44aac65b695b03398968c39f363fee5deafdf1ea"
integrity sha512-EV3L1+UQWGor21OmnvojK36mhg+TyIKDh3iFBKBohr5xeXIhNBcx8oWdgkTEEQ+BEFFYdLRuqMfd5L84N1V5Vg==
dependencies:
shebang-regex "^1.0.0"
shebang-command@^2.0.0: shebang-command@^2.0.0:
version "2.0.0" version "2.0.0"
resolved "https://registry.yarnpkg.com/shebang-command/-/shebang-command-2.0.0.tgz#ccd0af4f8835fbdc265b82461aaf0c36663f34ea" resolved "https://registry.yarnpkg.com/shebang-command/-/shebang-command-2.0.0.tgz#ccd0af4f8835fbdc265b82461aaf0c36663f34ea"
@ -6988,25 +7029,18 @@ shebang-command@^2.0.0:
dependencies: dependencies:
shebang-regex "^3.0.0" shebang-regex "^3.0.0"
shebang-regex@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-1.0.0.tgz#da42f49740c0b42db2ca9728571cb190c98efea3"
integrity sha512-wpoSFAxys6b2a2wHZ1XpDSgD7N9iVjg29Ph9uV/uaP9Ex/KXlkTZTeddxDPSYQpgvzKLGJke2UU0AzoGCjNIvQ==
shebang-regex@^3.0.0: shebang-regex@^3.0.0:
version "3.0.0" version "3.0.0"
resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-3.0.0.tgz#ae16f1644d873ecad843b0307b143362d4c42172" resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-3.0.0.tgz#ae16f1644d873ecad843b0307b143362d4c42172"
integrity sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A== integrity sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==
shelljs@0.9.2: shelljs@0.10.0:
version "0.9.2" version "0.10.0"
resolved "https://registry.yarnpkg.com/shelljs/-/shelljs-0.9.2.tgz#a8ac724434520cd7ae24d52071e37a18ac2bb183" resolved "https://registry.yarnpkg.com/shelljs/-/shelljs-0.10.0.tgz#e3bbae99b0f3f0cc5dce05b46a346fae2090e883"
integrity sha512-S3I64fEiKgTZzKCC46zT/Ib9meqofLrQVbpSswtjFfAVDW+AZ54WTnAM/3/yENoxz/V1Cy6u3kiiEbQ4DNphvw== integrity sha512-Jex+xw5Mg2qMZL3qnzXIfaxEtBaC4n7xifqaqtrZDdlheR70OGkydrPJWT0V1cA1k3nanC86x9FwAmQl6w3Klw==
dependencies: dependencies:
execa "^1.0.0" execa "^5.1.1"
fast-glob "^3.3.2" fast-glob "^3.3.2"
interpret "^1.0.0"
rechoir "^0.6.2"
side-channel-list@^1.0.0: side-channel-list@^1.0.0:
version "1.0.0" version "1.0.0"
@ -7053,7 +7087,7 @@ siginfo@^2.0.0:
resolved "https://registry.yarnpkg.com/siginfo/-/siginfo-2.0.0.tgz#32e76c70b79724e3bb567cb9d543eb858ccfaf30" resolved "https://registry.yarnpkg.com/siginfo/-/siginfo-2.0.0.tgz#32e76c70b79724e3bb567cb9d543eb858ccfaf30"
integrity sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g== integrity sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==
signal-exit@^3.0.0, signal-exit@^3.0.2: signal-exit@^3.0.2, signal-exit@^3.0.3:
version "3.0.7" version "3.0.7"
resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.7.tgz#a9a1767f8af84155114eaabd73f99273c8f59ad9" resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.7.tgz#a9a1767f8af84155114eaabd73f99273c8f59ad9"
integrity sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ== integrity sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==
@ -7293,10 +7327,10 @@ strip-bom@^3.0.0:
resolved "https://registry.yarnpkg.com/strip-bom/-/strip-bom-3.0.0.tgz#2334c18e9c759f7bdd56fdef7e9ae3d588e68ed3" resolved "https://registry.yarnpkg.com/strip-bom/-/strip-bom-3.0.0.tgz#2334c18e9c759f7bdd56fdef7e9ae3d588e68ed3"
integrity sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA== integrity sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==
strip-eof@^1.0.0: strip-final-newline@^2.0.0:
version "1.0.0" version "2.0.0"
resolved "https://registry.yarnpkg.com/strip-eof/-/strip-eof-1.0.0.tgz#bb43ff5598a6eb05d89b59fcd129c983313606bf" resolved "https://registry.yarnpkg.com/strip-final-newline/-/strip-final-newline-2.0.0.tgz#89b852fb2fcbe936f6f4b3187afb0a12c1ab58ad"
integrity sha512-7FCwGGmx8mD5xQd3RPUvnSpUXHM3BWuzjtpD4TXsfcZ9EL4azvVVUscFYwD9nx8Kh+uCBC00XBtAykoMHwTh8Q== integrity sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==
strip-json-comments@3.1.1, strip-json-comments@^3.1.1: strip-json-comments@3.1.1, strip-json-comments@^3.1.1:
version "3.1.1" version "3.1.1"
@ -7910,16 +7944,16 @@ vue-virtual-scroller@^2.0.0-beta.7:
vue-observe-visibility "^2.0.0-alpha.1" vue-observe-visibility "^2.0.0-alpha.1"
vue-resize "^2.0.0-alpha.1" vue-resize "^2.0.0-alpha.1"
vue@3.5.13: vue@3.5.17:
version "3.5.13" version "3.5.17"
resolved "https://registry.yarnpkg.com/vue/-/vue-3.5.13.tgz#9f760a1a982b09c0c04a867903fc339c9f29ec0a" resolved "https://registry.yarnpkg.com/vue/-/vue-3.5.17.tgz#ea8a6a45abb2b0620e7d479319ce8434b55650cf"
integrity sha512-wmeiSMxkZCSc+PM2w2VRsOYAZC8GdipNFRTsLSfodVqI9mbejKeXEGr8SckuLnrQPGe3oJN5c3K0vpoU9q/wCQ== integrity sha512-LbHV3xPN9BeljML+Xctq4lbz2lVHCR6DtbpTf5XIO6gugpXUN49j2QQPcMj086r9+AkJ0FfUT8xjulKKBkkr9g==
dependencies: dependencies:
"@vue/compiler-dom" "3.5.13" "@vue/compiler-dom" "3.5.17"
"@vue/compiler-sfc" "3.5.13" "@vue/compiler-sfc" "3.5.17"
"@vue/runtime-dom" "3.5.13" "@vue/runtime-dom" "3.5.17"
"@vue/server-renderer" "3.5.13" "@vue/server-renderer" "3.5.17"
"@vue/shared" "3.5.13" "@vue/shared" "3.5.17"
vuex@4.1.0: vuex@4.1.0:
version "4.1.0" version "4.1.0"
@ -8025,7 +8059,7 @@ which-typed-array@^1.1.13, which-typed-array@^1.1.16, which-typed-array@^1.1.18:
gopd "^1.2.0" gopd "^1.2.0"
has-tostringtag "^1.0.2" has-tostringtag "^1.0.2"
which@^1.2.9, which@^1.3.1: which@^1.3.1:
version "1.3.1" version "1.3.1"
resolved "https://registry.yarnpkg.com/which/-/which-1.3.1.tgz#a45043d54f5805316da8d62f9f50918d3da70b0a" resolved "https://registry.yarnpkg.com/which/-/which-1.3.1.tgz#a45043d54f5805316da8d62f9f50918d3da70b0a"
integrity sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ== integrity sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==