refac: stale test entries, failing web tests, Postgre backup bug, Hardcoded MySQL table count, Stale Postgre connection in test, own-scope visibility filter, GetAll ignoring its error, JSON tags in use-case types, 31 copies of DESKTOP_QUERY, ProfileView endpoint paths

This commit is contained in:
Florian Sylvain committed 2026-10-28 17:54:10 +01:00
1 parent efecdee200
commit 5d6f378fce
87 files changed
+696 -373

No files matched your search

+2 -4
View File
@@ -1,10 +1,10 @@
<script setup lang="ts">
import { t } from '@/i18n'
import { useMediaQuery } from '@vueuse/core'
import { computed } from 'vue'
import ApiSearchPanel from '@/components/ApiSearchPanel.vue'
import type { SearchEntry, SearchIndex } from '@/lib/apiSearch'
import { useIsDesktop } from '@/composables/useIsDesktop'
defineProps<{
index: SearchIndex
@@ -12,10 +12,8 @@ defineProps<{
const emit = defineEmits<{ select: [entry: SearchEntry] }>()
const DESKTOP_QUERY = '(min-width: 64rem)'
const open = defineModel<boolean>('open', { required: true })
const isDesktop = useMediaQuery(DESKTOP_QUERY)
const isDesktop = useIsDesktop('64rem')
const contentClass = computed(() =>
isDesktop.value
+2 -4
View File
@@ -2,11 +2,9 @@
import { t, formatNumber } from '@/i18n'
import { computed } from 'vue'
import { useMediaQuery } from '@vueuse/core'
import { pageItems, rangeFor } from '@/lib/pagination'
import PageSizeSelect from '@/components/PageSizeSelect.vue'
const DESKTOP_QUERY = '(min-width: 48rem)'
import { useIsDesktop } from '@/composables/useIsDesktop'
const props = defineProps<{
page: number
@@ -23,7 +21,7 @@ const emit = defineEmits<{
'update:size': [size: number]
}>()
const isDesktop = useMediaQuery(DESKTOP_QUERY)
const isDesktop = useIsDesktop()
const busy = computed(() => props.loadingPage !== null)
const atStart = computed(() => busy.value || props.page <= 1)
+2 -3
View File
@@ -2,13 +2,12 @@
import { t } from '@/i18n'
import { computed, nextTick, ref, useId } from 'vue'
import { useMediaQuery } from '@vueuse/core'
import AuthorAvatar from '@/components/AuthorAvatar.vue'
import type { Byline } from '@/composables/useArticles'
import { moveItem } from '@/lib/arrayMove'
import { insertionTarget } from '@/lib/articleMeta'
import { useIsDesktop } from '@/composables/useIsDesktop'
const DESKTOP_QUERY = '(min-width: 64rem)'
const DESKTOP_AVATAR = 22
const MOBILE_AVATAR = 28
@@ -19,7 +18,7 @@ const props = withDefaults(defineProps<{ authors: Byline[]; hintHidden?: boolean
const emit = defineEmits<{ 'update:authors': [authors: Byline[]] }>()
const labelId = useId()
const isDesktop = useMediaQuery(DESKTOP_QUERY)
const isDesktop = useIsDesktop('64rem')
const list = ref<HTMLElement | null>(null)
const working = ref<Byline[] | null>(null)
const grabbed = ref<number | null>(null)
+2 -3
View File
@@ -2,13 +2,12 @@
import { t } from '@/i18n'
import { ref } from 'vue'
import { useMediaQuery } from '@vueuse/core'
import ColumnPickerList from '@/components/ColumnPickerList.vue'
import type { BuiltinField } from '@/lib/contentTypes'
import { COLUMN_CAP } from '@/lib/entryColumns'
import { BOTTOM_SHEET_UI } from '@/lib/sheetUi'
import { useIsDesktop } from '@/composables/useIsDesktop'
const DESKTOP_QUERY = '(min-width: 48rem)'
const FOCUS_RING =
'focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-primary'
const TRIGGER = `h-11 w-11 justify-center rounded-none border border-accented bg-default p-0 text-highlighted ring-0 md:h-8 md:w-auto md:px-3 ${FOCUS_RING}`
@@ -17,7 +16,7 @@ defineProps<{ fields: BuiltinField[]; keys: string[]; atCap: boolean; disabled?:
const emit = defineEmits<{ toggle: [key: string, on: boolean]; reset: [] }>()
const isDesktop = useMediaQuery(DESKTOP_QUERY)
const isDesktop = useIsDesktop()
const open = ref(false)
</script>
+2 -4
View File
@@ -1,13 +1,11 @@
<script setup lang="ts">
import { t } from '@/i18n'
import { useMediaQuery } from '@vueuse/core'
import ColumnPicker from '@/components/ColumnPicker.vue'
import SortSelect from '@/components/SortSelect.vue'
import type { BuiltinField } from '@/lib/contentTypes'
import type { EntrySortField } from '@/lib/entryListQuery'
const DESKTOP_QUERY = '(min-width: 48rem)'
import { useIsDesktop } from '@/composables/useIsDesktop'
defineProps<{
sort: string
@@ -25,7 +23,7 @@ const emit = defineEmits<{
reset: []
}>()
const isDesktop = useMediaQuery(DESKTOP_QUERY)
const isDesktop = useIsDesktop()
</script>
<template>
+2 -3
View File
@@ -2,11 +2,10 @@
import { t } from '@/i18n'
import { ref } from 'vue'
import { useMediaQuery } from '@vueuse/core'
import DirectoryRowMenu from '@/components/DirectoryRowMenu.vue'
import type { DirectoryMenuItem } from '@/lib/directoryMenu'
import { useIsDesktop } from '@/composables/useIsDesktop'
const DESKTOP_QUERY = '(min-width: 48rem)'
const DEFAULT_SKELETON_ROWS = 3
const props = withDefaults(
@@ -31,7 +30,7 @@ const props = withDefaults(
const emit = defineEmits<{ open: [item: T] }>()
const isDesktop = useMediaQuery(DESKTOP_QUERY)
const isDesktop = useIsDesktop()
const openMenuId = ref<number | null>(null)
function setMenuOpen(item: T, isOpen: boolean) {
+2 -3
View File
@@ -2,12 +2,11 @@
import { t } from '@/i18n'
import { computed, nextTick, ref, useId } from 'vue'
import { useMediaQuery } from '@vueuse/core'
import { useLocalizedOverlay } from '@/i18n/presentation'
import ConfirmDialog from '@/components/ConfirmDialog.vue'
import type { DirectoryNotice } from '@/lib/directoryTypes'
import { useIsDesktop } from '@/composables/useIsDesktop'
const DESKTOP_QUERY = '(min-width: 48rem)'
const AUTOFOCUS_SELECTOR = '[data-autofocus]'
const INVALID_SELECTOR = '[aria-invalid="true"]'
@@ -38,7 +37,7 @@ const emit = defineEmits<{ close: []; submit: [] }>()
const open = defineModel<boolean>('open', { default: true })
const overlay = useLocalizedOverlay()
const isDesktop = useMediaQuery(DESKTOP_QUERY)
const isDesktop = useIsDesktop()
const formId = useId()
const form = ref<{ $el: HTMLElement } | null>(null)
const side = computed(() => (isDesktop.value ? 'right' : 'bottom'))
+3 -3
View File
@@ -2,9 +2,9 @@
import { t, formatNumber } from '@/i18n'
import { computed } from 'vue'
import { useElementBounding, useMediaQuery } from '@vueuse/core'
import { useElementBounding } from '@vueuse/core'
import { useIsDesktop } from '@/composables/useIsDesktop'
const DESKTOP_QUERY = '(min-width: 48rem)'
const CONTENT_SELECTOR = '#dashboard-content'
defineProps<{
@@ -18,7 +18,7 @@ const emit = defineEmits<{
clear: []
}>()
const isDesktop = useMediaQuery(DESKTOP_QUERY)
const isDesktop = useIsDesktop()
const content = useElementBounding(() => document.querySelector<HTMLElement>(CONTENT_SELECTOR))
const centre = computed(() => `${content.left.value + content.width.value / 2}px`)
</script>
+2 -3
View File
@@ -2,7 +2,6 @@
import { t } from '@/i18n'
import { computed, onBeforeUnmount, ref, watch } from 'vue'
import { useMediaQuery } from '@vueuse/core'
import MediaArticleStatusBadge from '@/components/MediaArticleStatusBadge.vue'
import MediaThumb from '@/components/MediaThumb.vue'
import MediaVariantsTab from '@/components/variants/MediaVariantsTab.vue'
@@ -19,8 +18,8 @@ import { usageSplitOf } from '@/lib/mediaUsage'
import { PANEL_TAB_TRIGGER } from '@/lib/tabStyles'
import { useCanManageVariants } from '@/composables/useCanManageVariants'
import { usePermissionsStore } from '@/stores/permissions'
import { useIsDesktop } from '@/composables/useIsDesktop'
const DESKTOP_QUERY = '(min-width: 48rem)'
const ACTION_BUTTON =
'h-11 justify-center border border-accented bg-default px-3 text-sm ring-0 max-md:flex-1 md:h-8'
@@ -41,7 +40,7 @@ const emit = defineEmits<{
}>()
const open = defineModel<boolean>('open', { default: false })
const isDesktop = useMediaQuery(DESKTOP_QUERY)
const isDesktop = useIsDesktop()
const canManageVariants = useCanManageVariants()
const permissions = usePermissionsStore()
const canRename = computed(() => permissions.can('media.upload'))
+2 -3
View File
@@ -2,10 +2,10 @@
import { t } from '@/i18n'
import { computed, nextTick, ref, useId } from 'vue'
import { useMediaQuery } from '@vueuse/core'
import DirectoryField from '@/components/DirectoryField.vue'
import ImageUploader from '@/components/ImageUploader.vue'
import type { MediaItem } from '@/composables/useMedia'
import { useIsDesktop } from '@/composables/useIsDesktop'
import {
imageButtonName,
imageMeta,
@@ -15,7 +15,6 @@ import {
type MediaSummary,
} from '@/lib/mediaField'
const DESKTOP_QUERY = '(min-width: 48rem)'
const BUTTON_CLASS = 'h-11 gap-1.5 px-3 text-sm text-highlighted disabled:opacity-40 md:h-8'
const OUTLINE_CLASS = `${BUTTON_CLASS} border border-accented bg-default ring-0`
const ROW_BUTTON_CLASS = `${BUTTON_CLASS} max-md:flex-1`
@@ -46,7 +45,7 @@ const labelId = `${id}-label`
const helpId = `${id}-help`
const errorId = `${id}-error`
const isDesktop = useMediaQuery(DESKTOP_QUERY)
const isDesktop = useIsDesktop()
const pickerOpen = ref(false)
const hovered = ref(false)
const announcement = ref('')
+2 -3
View File
@@ -2,7 +2,6 @@
import { t } from '@/i18n'
import { computed, nextTick, onBeforeUnmount, ref, watch } from 'vue'
import { useMediaQuery } from '@vueuse/core'
import MediaThumb from '@/components/MediaThumb.vue'
import VariantPickerField from '@/components/variants/VariantPickerField.vue'
import type { LibraryStatus } from '@/composables/useLibraryList'
@@ -10,9 +9,9 @@ import type { ImageVariant } from '@/composables/useImageVariants'
import type { MediaItem } from '@/composables/useMedia'
import type { FieldPick } from '@/lib/imageChoice'
import { accessibleName, formatDimensions, formatSize } from '@/lib/mediaFormat'
import { useIsDesktop } from '@/composables/useIsDesktop'
const SEARCH_DEBOUNCE_MS = 120
const DESKTOP_QUERY = '(min-width: 48rem)'
const FIRST_LOAD_SKELETONS_DESKTOP = 8
const FIRST_LOAD_SKELETONS_MOBILE = 9
const COLUMNS_DESKTOP = 4
@@ -62,7 +61,7 @@ const tileAspect = computed(() =>
const alt = defineModel<string>('alt', { default: '' })
const variant = defineModel<string>('variant', { default: '' })
const isDesktop = useMediaQuery(DESKTOP_QUERY)
const isDesktop = useIsDesktop()
const grid = ref<HTMLElement | null>(null)
const searchText = ref(props.query)
let debounceTimer: ReturnType<typeof setTimeout> | null = null
+2 -3
View File
@@ -2,7 +2,6 @@
import { t, formatNumber } from '@/i18n'
import { computed, onBeforeUnmount, ref, watch } from 'vue'
import { useMediaQuery } from '@vueuse/core'
import MediaVariantsFilter from '@/components/MediaVariantsFilter.vue'
import type { MediaCounts } from '@/composables/useMedia'
import type {
@@ -13,9 +12,9 @@ import type {
MediaView,
} from '@/lib/mediaQuery'
import { UNDERLINE_TAB_TRIGGER } from '@/lib/tabStyles'
import { useIsDesktop } from '@/composables/useIsDesktop'
const SEARCH_DEBOUNCE_MS = 120
const DESKTOP_QUERY = '(min-width: 48rem)'
const SORT_LABELS: Record<MediaSort, string> = {
get newest() {
@@ -71,7 +70,7 @@ const emit = defineEmits<{
purge: []
}>()
const isDesktop = useMediaQuery(DESKTOP_QUERY)
const isDesktop = useIsDesktop()
const searchText = ref(props.controls.q)
let debounceTimer: ReturnType<typeof setTimeout> | null = null
+3 -3
View File
@@ -1,10 +1,10 @@
<script setup lang="ts">
import { computed, inject, ref, watch } from 'vue'
import { useEventListener, useMediaQuery } from '@vueuse/core'
import { useEventListener } from '@vueuse/core'
import { MEDIA_USAGE_KEY } from '@/lib/mediaUsageContext'
import { tooltipLines } from '@/lib/mediaUsage'
import { useIsDesktop } from '@/composables/useIsDesktop'
const DESKTOP_QUERY = '(min-width: 48rem)'
const TOOLTIP_UI = {
content:
'h-auto flex-col items-start gap-0 bg-inverted px-2 py-1.5 text-xs/4.5 text-inverted shadow-(--shadow-popover) ring-0',
@@ -17,7 +17,7 @@ const props = defineProps<{
}>()
const usage = inject(MEDIA_USAGE_KEY, null)
const isDesktop = useMediaQuery(DESKTOP_QUERY)
const isDesktop = useIsDesktop()
const hovered = ref(false)
const dismissed = ref(false)
+2 -4
View File
@@ -2,7 +2,6 @@
import { t } from '@/i18n'
import { computed, nextTick, ref } from 'vue'
import { useMediaQuery } from '@vueuse/core'
import ApiPreview from '@/components/ApiPreview.vue'
import ApiPreviewSheet from '@/components/ApiPreviewSheet.vue'
import BuilderActions from '@/components/BuilderActions.vue'
@@ -22,12 +21,11 @@ import { useUnsavedChangesGuard } from '@/composables/useUnsavedChangesGuard'
import { sampleJson } from '@/lib/schemaBuilderSample'
import { entriesNotice } from '@/lib/schemaEntries'
import { formatClock } from '@/lib/updateFormat'
const DESKTOP_QUERY = '(min-width: 48rem)'
import { useIsDesktop } from '@/composables/useIsDesktop'
const props = defineProps<{ edit?: EditSource }>()
const isDesktop = useMediaQuery(DESKTOP_QUERY)
const isDesktop = useIsDesktop()
const builder = useSchemaBuilder(props.edit)
const { form } = builder
const remove = props.edit ? useSchemaDelete(() => props.edit!) : undefined
+3 -3
View File
@@ -3,7 +3,7 @@ import AppSwitch from '@/components/AppSwitch.vue'
import { t } from '@/i18n'
import { computed, nextTick, ref } from 'vue'
import { createReusableTemplate, useMediaQuery } from '@vueuse/core'
import { createReusableTemplate } from '@vueuse/core'
import DirectoryField from '@/components/DirectoryField.vue'
import DirectoryPanel from '@/components/DirectoryPanel.vue'
import FallbackChip from '@/components/FallbackChip.vue'
@@ -34,8 +34,8 @@ import {
type SeoPreviewInput,
} from '@/lib/seoPreview'
import type { SeoFocus, SeoSuggestion, SuggestionCode, SuggestionField } from '@/lib/seoTypes'
import { useIsDesktop } from '@/composables/useIsDesktop'
const DESKTOP_QUERY = '(min-width: 48rem)'
const PANEL_FIELDS: SuggestionField[] = [
'seo_title',
'seo_description',
@@ -79,7 +79,7 @@ const [DefineFields, Fields] = createReusableTemplate()
const [DefinePreviews, Previews] = createReusableTemplate()
const [DefinePreviewButton, PreviewButton] = createReusableTemplate()
const isDesktop = useMediaQuery(DESKTOP_QUERY)
const isDesktop = useIsDesktop()
const layout = ref<HTMLElement | null>(null)
const focusTarget = ref<SeoFocus | null>(props.focus)
const device = ref<PreviewDevice>('desktop')
+2 -3
View File
@@ -2,12 +2,11 @@
import { locale, t } from '@/i18n'
import { computed, ref } from 'vue'
import { useMediaQuery } from '@vueuse/core'
import { colorModeOptions, useAccountMenu } from '@/composables/useAccountMenu'
import { chooseLanguage, languageOptions } from '@/composables/useLanguage'
import { useAccountMenuRadios } from '@/composables/useRadioMenuRoles'
import { useIsDesktop } from '@/composables/useIsDesktop'
const DESKTOP_QUERY = '(min-width: 64rem)'
const ITEM_CLASS = 'h-8 items-center gap-2.5 px-2 py-0 text-sm font-normal text-highlighted'
const CHOICE_CLASS = 'h-8 items-center gap-2.5 px-2 py-0 text-base font-normal text-highlighted'
const SECTION_LABEL_CLASS =
@@ -27,7 +26,7 @@ const emit = defineEmits<{
}>()
const { colorMode, name, initials, onProfile, goToProfile } = useAccountMenu(() => props)
const isDesktop = useMediaQuery(DESKTOP_QUERY)
const isDesktop = useIsDesktop('64rem')
const open = ref(false)
useAccountMenuRadios(open)
const chip = ref<{ $el: HTMLElement } | null>(null)
+2 -4
View File
@@ -1,17 +1,15 @@
<script setup lang="ts">
import { t } from '@/i18n'
import { computed, ref } from 'vue'
import { useMediaQuery } from '@vueuse/core'
import ProfileCard from '@/components/ProfileCard.vue'
import RoleBadge from '@/components/RoleBadge.vue'
import RoleSummaryDialog from '@/components/RoleSummaryDialog.vue'
import { buildHeldRolePreview } from '@/lib/rolePreview'
import { usePermissionsStore } from '@/stores/permissions'
const DESKTOP_QUERY = '(min-width: 48rem)'
import { useIsDesktop } from '@/composables/useIsDesktop'
const permissions = usePermissionsStore()
const isDesktop = useMediaQuery(DESKTOP_QUERY)
const isDesktop = useIsDesktop()
const summaryOpen = ref(false)
const model = computed(() => buildHeldRolePreview(permissions.grants))
@@ -2,7 +2,6 @@
import { t } from '@/i18n'
import { computed, nextTick, ref } from 'vue'
import { useMediaQuery } from '@vueuse/core'
import { useLocalizedOverlay } from '@/i18n/presentation'
import ConfirmDialog from '@/components/ConfirmDialog.vue'
import VariantFormFields from '@/components/variants/VariantFormFields.vue'
@@ -19,8 +18,8 @@ import {
type VariantFormValues,
} from '@/lib/variants/variantForm'
import { useImageVariantsStore } from '@/stores/imageVariants'
import { useIsDesktop } from '@/composables/useIsDesktop'
const DESKTOP_QUERY = '(min-width: 40rem)'
const CONFLICT = 409
const UNPROCESSABLE = 422
const TOAST_STATUSES = [400, 403, 404]
@@ -39,7 +38,7 @@ const emit = defineEmits<{
const store = useImageVariantsStore()
const overlay = useLocalizedOverlay()
const { announceFailure } = useVariantToasts()
const isDesktop = useMediaQuery(DESKTOP_QUERY)
const isDesktop = useIsDesktop('40rem')
const form = useVariantForm(props.mode, props.initial)
const side = computed(() => (isDesktop.value ? 'right' : 'bottom'))
+1 -1
View File
@@ -216,7 +216,7 @@ describe('getMe maps the account fields', () => {
await expect(useApi().getMe()).resolves.toEqual({
id: 1,
verified: true,
access: ownerAccess(),
access: { ...ownerAccess(), roleAssignedBy: null },
username: 'marta',
email: 'marta@renew.example',
displayName: 'Marta Alves',
+2 -3
View File
@@ -1,6 +1,5 @@
import { computed } from 'vue'
import { useRouter } from 'vue-router'
import { useMediaQuery } from '@vueuse/core'
import { useContentTypeDetail } from '@/composables/useContentTypeDetail'
import { useEntryColumns } from '@/composables/useEntryColumns'
import { useEntryList } from '@/composables/useEntryList'
@@ -8,8 +7,8 @@ import { resolveContentListState } from '@/lib/contentListState'
import { entryCaption } from '@/lib/entryListQuery'
import { fieldCountLabel } from '@/lib/schemaRows'
import { HOME_PATH, entryPath, schemaPath } from '@/lib/routePaths'
import { useIsDesktop } from '@/composables/useIsDesktop'
const DESKTOP_QUERY = '(min-width: 48rem)'
const DESKTOP_SKELETON_ROWS = 8
const MOBILE_SKELETON_ROWS = 6
@@ -19,7 +18,7 @@ export function useContentListPage(
canEdit: () => boolean,
) {
const router = useRouter()
const isDesktop = useMediaQuery(DESKTOP_QUERY)
const isDesktop = useIsDesktop()
const list = useEntryList(typeKey)
const columns = useEntryColumns(typeKey, () => detail.type.value?.fields ?? [])
+2 -3
View File
@@ -1,6 +1,5 @@
import { computed } from 'vue'
import { useRouter } from 'vue-router'
import { useMediaQuery } from '@vueuse/core'
import { t } from '@/i18n'
import { useLocalizedToast } from '@/i18n/presentation'
import { useEntryDraft } from '@/composables/useEntryDraft'
@@ -12,8 +11,8 @@ import { entryTitle } from '@/lib/entryForm'
import type { EntryTarget } from '@/lib/entryEditorState'
import { formatEntryStamp, formatSavedClock } from '@/lib/entryStamp'
import { contentPath, entryPath } from '@/lib/routePaths'
import { useIsDesktop } from '@/composables/useIsDesktop'
const DESKTOP_QUERY = '(min-width: 48rem)'
const SAVED_TOAST_ID = 'entry-saved'
const MISSING_NAME = '–'
@@ -26,7 +25,7 @@ export function useEntryEditorPage(
) {
const router = useRouter()
const toast = useLocalizedToast()
const isDesktop = useMediaQuery(DESKTOP_QUERY)
const isDesktop = useIsDesktop()
const editor = useEntryDraft(typeKey, target)
const { detail, load, form } = editor
const names = useEntryNames(() => load.entry.value)
+7
View File
@@ -0,0 +1,7 @@
import { useMediaQuery } from '@vueuse/core'
export type DesktopBreakpoint = '40rem' | '48rem' | '64rem'
export function useIsDesktop(minWidth: DesktopBreakpoint = '48rem') {
return useMediaQuery(`(min-width: ${minWidth})`)
}
+25
View File
@@ -0,0 +1,25 @@
import { useApi } from '@/composables/useApi'
const JSON_HEADERS = { 'Content-Type': 'application/json' }
export function useProfile() {
const { request } = useApi()
function updateDisplayName(displayName: string) {
return request<{ display_name?: string | null }>('/v1/user/me', {
method: 'PATCH',
headers: JSON_HEADERS,
body: JSON.stringify({ display_name: displayName }),
})
}
function sendPasswordReset(email: string) {
return request('/v1/auth/password-reset', {
method: 'POST',
headers: JSON_HEADERS,
body: JSON.stringify({ email }),
})
}
return { updateDisplayName, sendPasswordReset }
}
+8 -3
View File
@@ -42,7 +42,11 @@ beforeEach(() => {
setViewportWidth(DESKTOP_WIDTH)
setActivePinia(createPinia())
useSessionStore().status = 'ready'
request.mockReset().mockResolvedValue([])
request
.mockReset()
.mockImplementation(async (path: string) =>
path === '/v1/image-variants' ? { items: [] } : [],
)
vi.mocked(useApi).mockReturnValue({
request,
getMe: vi.fn().mockResolvedValue(null),
@@ -50,10 +54,11 @@ beforeEach(() => {
})
afterEach(async () => {
vi.unstubAllGlobals()
vi.restoreAllMocks()
wrapper?.unmount()
wrapper = undefined
await flushPromises()
vi.unstubAllGlobals()
vi.restoreAllMocks()
document.body.innerHTML = ''
localStorage.clear()
initializeLocale()
+2 -4
View File
@@ -3,7 +3,6 @@ import { t } from '@/i18n'
import { computed, nextTick, ref, watch } from 'vue'
import { useRouter } from 'vue-router'
import { useMediaQuery } from '@vueuse/core'
import ActivityCardList from '@/components/ActivityCardList.vue'
import ActivityChips from '@/components/ActivityChips.vue'
import ActivityDetailsPanel from '@/components/ActivityDetailsPanel.vue'
@@ -21,12 +20,11 @@ import { useActivityPage } from '@/composables/useActivityPage'
import { useSessionStore } from '@/stores/session'
import type { ActivityFilters } from '@/lib/activity'
import type { StripKey } from '@/lib/activitySummary'
const DESKTOP_QUERY = '(min-width: 48rem)'
import { useIsDesktop } from '@/composables/useIsDesktop'
const router = useRouter()
const session = useSessionStore()
const isDesktop = useMediaQuery(DESKTOP_QUERY)
const isDesktop = useIsDesktop()
const activity = useActivityPage()
const {
filters,
+2 -3
View File
@@ -2,7 +2,6 @@
import { t } from '@/i18n'
import { computed, nextTick, onBeforeUnmount, onMounted, ref, watch } from 'vue'
import { useMediaQuery } from '@vueuse/core'
import { useLocalizedOverlay, useLocalizedToast } from '@/i18n/presentation'
import ApiKeyCardList from '@/components/ApiKeyCardList.vue'
import ApiKeyReveal from '@/components/ApiKeyReveal.vue'
@@ -16,8 +15,8 @@ import { toastsAboveBulkBar } from '@/composables/useToastOffset'
import { displayPrefix } from '@/lib/apiKeys'
import { useSessionStore } from '@/stores/session'
import { usePermissionsStore } from '@/stores/permissions'
import { useIsDesktop } from '@/composables/useIsDesktop'
const DESKTOP_QUERY = '(min-width: 48rem)'
const AUTH_HANDLED_STATUSES = [401, 403]
const NOT_FOUND_STATUS = 404
const REVOKE_FAILED_COPY = "Couldn't revoke the key. Try again in a moment."
@@ -30,7 +29,7 @@ const overlay = useLocalizedOverlay()
const session = useSessionStore()
const permissions = usePermissionsStore()
const { listApiKeys, revokeApiKey } = useApiKeys()
const isDesktop = useMediaQuery(DESKTOP_QUERY)
const isDesktop = useIsDesktop()
const loadState = ref<LoadState>('loading')
const keys = ref<ApiKey[]>([])
+2 -3
View File
@@ -3,7 +3,6 @@ import { t, formatNumber } from '@/i18n'
import { computed, ref, watch } from 'vue'
import { useRoute } from 'vue-router'
import { useMediaQuery } from '@vueuse/core'
import { useLocalizedOverlay } from '@/i18n/presentation'
import AuthorAvatar from '@/components/AuthorAvatar.vue'
import AuthorFormPanel from '@/components/AuthorFormPanel.vue'
@@ -31,8 +30,8 @@ import { classifyFailure } from '@/lib/directoryErrors'
import type { DirectoryMenuItem } from '@/lib/directoryMenu'
import { useSessionStore } from '@/stores/session'
import { usePermissionsStore } from '@/stores/permissions'
import { useIsDesktop } from '@/composables/useIsDesktop'
const DESKTOP_QUERY = '(min-width: 48rem)'
const LIST_COLUMNS = 'minmax(0,1.4fr) minmax(0,1fr) 110px 100px 44px'
const INITIAL_SKELETON_ROWS = 6
const NOBODY = 'none'
@@ -49,7 +48,7 @@ const permissions = usePermissionsStore()
const overlay = useLocalizedOverlay()
const toasts = useDirectoryToasts()
const { rememberOpener, restoreFocus, leavePanel, followEditLink } = useDirectoryView()
const isDesktop = useMediaQuery(DESKTOP_QUERY)
const isDesktop = useIsDesktop()
const { listAuthors, listAuthorPage, listAllAuthors, deleteAuthor } = useAuthors()
const removal = useDirectoryDelete<Author>()
+2 -3
View File
@@ -2,7 +2,6 @@
import { formatNumber, t } from '@/i18n'
import { computed, nextTick, onBeforeUnmount, ref, watch } from 'vue'
import { useMediaQuery } from '@vueuse/core'
import { useLocalizedOverlay, useLocalizedToast } from '@/i18n/presentation'
import ConfirmDialog from '@/components/ConfirmDialog.vue'
import DashboardShell from '@/components/DashboardShell.vue'
@@ -27,8 +26,8 @@ import { formatVariantSize } from '@/lib/variants/variantFormat'
import { useSessionStore } from '@/stores/session'
import { usePermissionsStore } from '@/stores/permissions'
import { useVariantCacheStore } from '@/stores/variantCache'
import { useIsDesktop } from '@/composables/useIsDesktop'
const DESKTOP_QUERY = '(min-width: 40rem)'
const SKELETON_DELAY_MS = 150
const NOT_FOUND = 404
const CONFIRM_WORD = 'purge'
@@ -39,7 +38,7 @@ const store = useVariantCacheStore()
const overlay = useLocalizedOverlay()
const toast = useLocalizedToast()
const { announceFailure, announceSuccess } = useVariantToasts()
const isDesktop = useMediaQuery(DESKTOP_QUERY)
const isDesktop = useIsDesktop('40rem')
const showSkeleton = ref(false)
const purgeUnusedButton = ref<{ $el: HTMLElement } | null>(null)
+3 -3
View File
@@ -3,7 +3,7 @@ import { t } from '@/i18n'
import { computed, nextTick, onBeforeUnmount, provide, ref, watch } from 'vue'
import { useRoute, type LocationQuery } from 'vue-router'
import { useEventListener, useMediaQuery, useOnline } from '@vueuse/core'
import { useEventListener, useOnline } from '@vueuse/core'
import { useLocalizedToast } from '@/i18n/presentation'
import AppPaginator from '@/components/AppPaginator.vue'
import BulkDeleteMediaDialog from '@/components/BulkDeleteMediaDialog.vue'
@@ -51,8 +51,8 @@ import { pageCountFor } from '@/lib/pagination'
import { purgedToast } from '@/lib/mediaPurge'
import { RATE_LIMITED_TEXT, RATE_LIMITED_TITLE, UPLOAD_ACCEPT } from '@/lib/mediaUploadRows'
import { usePermissionsStore } from '@/stores/permissions'
import { useIsDesktop } from '@/composables/useIsDesktop'
const DESKTOP_QUERY = '(min-width: 48rem)'
const MEDIA_PATH = '/media'
const TOAST_DURATION_MS = 5000
const PERSISTENT_TOAST = 0
@@ -84,7 +84,7 @@ const permissions = usePermissionsStore()
const media = useMedia()
const selection = useMediaSelection(media.items)
const upload = useMediaUpload({ onAdded: refreshInBackground })
const isDesktop = useMediaQuery(DESKTOP_QUERY)
const isDesktop = useIsDesktop()
const online = useOnline()
function differsOnlyByPage(query: MediaControls, current: MediaControls) {
+5 -12
View File
@@ -10,7 +10,8 @@ import ConfirmDialog from '@/components/ConfirmDialog.vue'
import DashboardShell from '@/components/DashboardShell.vue'
import ProfileCard from '@/components/ProfileCard.vue'
import YourRoleCard from '@/components/YourRoleCard.vue'
import { ApiError, authRedirectPending, useApi } from '@/composables/useApi'
import { ApiError, authRedirectPending } from '@/composables/useApi'
import { useProfile } from '@/composables/useProfile'
import { useRetryAfter } from '@/composables/useRetryAfter'
import { usePermissionsStore } from '@/stores/permissions'
import { useSessionStore, type Session } from '@/stores/session'
@@ -36,7 +37,7 @@ const session = useSessionStore()
const permissions = usePermissionsStore()
const toast = useLocalizedToast()
const overlay = useLocalizedOverlay()
const { request } = useApi()
const { updateDisplayName, sendPasswordReset } = useProfile()
const retryAfter = useRetryAfter()
const account = ref<Session | null>(session.session)
@@ -189,11 +190,7 @@ async function onSave() {
saveFailed.value = false
serverLimitError.value = false
try {
const updated = await request<{ display_name?: string | null }>('/v1/user/me', {
method: 'PATCH',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ display_name: draft.value }),
})
const updated = await updateDisplayName(draft.value)
const displayName = updated.display_name ?? ''
session.setDisplayName(displayName)
field.value = displayName
@@ -223,11 +220,7 @@ async function onSendReset() {
}
resetState.value = 'sending'
try {
await request('/v1/auth/password-reset', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ email: email.value }),
})
await sendPasswordReset(email.value)
resetState.value = 'sent'
} catch (error) {
if (error instanceof ApiError && error.status === 429) {
+2 -3
View File
@@ -1,7 +1,6 @@
<script setup lang="ts">
import { t } from '@/i18n'
import { computed, onMounted, ref } from 'vue'
import { useMediaQuery } from '@vueuse/core'
import DashboardShell from '@/components/DashboardShell.vue'
import PermissionGrid from '@/components/PermissionGrid.vue'
import PermissionGroup from '@/components/PermissionGroup.vue'
@@ -14,12 +13,12 @@ import { useLocalizedOverlay } from '@/i18n/presentation'
import { useRoleEditor } from '@/composables/useRoleEditor'
import type { CallerScope } from '@/lib/grantLocks'
import { usePermissionsStore } from '@/stores/permissions'
import { useIsDesktop } from '@/composables/useIsDesktop'
const DESKTOP_QUERY = '(min-width: 64rem)'
const CONTENT_GROUP = 'content'
const permissions = usePermissionsStore()
const isDesktop = useMediaQuery(DESKTOP_QUERY)
const isDesktop = useIsDesktop('64rem')
const editor = useRoleEditor()
const overlay = useLocalizedOverlay()
+2 -4
View File
@@ -2,7 +2,6 @@
import { t } from '@/i18n'
import { computed, onMounted, ref } from 'vue'
import { useRouter } from 'vue-router'
import { useMediaQuery } from '@vueuse/core'
import { useLocalizedToast } from '@/i18n/presentation'
import DashboardShell from '@/components/DashboardShell.vue'
import DeleteRoleDialog from '@/components/DeleteRoleDialog.vue'
@@ -14,14 +13,13 @@ import { useRoles } from '@/composables/useRoles'
import { canCover } from '@/lib/grantLocks'
import type { RoleItem } from '@/lib/roles'
import { usePermissionsStore } from '@/stores/permissions'
const DESKTOP_QUERY = '(min-width: 48rem)'
import { useIsDesktop } from '@/composables/useIsDesktop'
const router = useRouter()
const toast = useLocalizedToast()
const permissions = usePermissionsStore()
const { listRoles } = useRoles()
const isDesktop = useMediaQuery(DESKTOP_QUERY)
const isDesktop = useIsDesktop()
const skeleton = useDelayedSkeleton()
const loadState = ref<LoadState>('loading')
+2 -3
View File
@@ -4,7 +4,6 @@ import { t } from '@/i18n'
import { computed } from 'vue'
import { useRouter } from 'vue-router'
import { usePermissionsStore } from '@/stores/permissions'
import { useMediaQuery } from '@vueuse/core'
import DashboardShell from '@/components/DashboardShell.vue'
import DirectoryAddButton from '@/components/DirectoryAddButton.vue'
import DirectoryLoadError from '@/components/DirectoryLoadError.vue'
@@ -13,13 +12,13 @@ import NoAccessState from '@/components/NoAccessState.vue'
import SchemaTable from '@/components/SchemaTable.vue'
import { useSchemaGate } from '@/composables/useSchemaGate'
import { builtinRow, customRow, schemaSummary } from '@/lib/schemaRows'
import { useIsDesktop } from '@/composables/useIsDesktop'
const DESKTOP_QUERY = '(min-width: 48rem)'
const NEW_TYPE_PATH = '/schema/new'
const router = useRouter()
const permissions = usePermissionsStore()
const isDesktop = useMediaQuery(DESKTOP_QUERY)
const isDesktop = useIsDesktop()
const {
sessionReady,
+2 -3
View File
@@ -3,7 +3,6 @@ import { t, formatNumber } from '@/i18n'
import { computed, ref, watch } from 'vue'
import { useRoute } from 'vue-router'
import { useMediaQuery } from '@vueuse/core'
import { useLocalizedOverlay } from '@/i18n/presentation'
import ConfirmDialog from '@/components/ConfirmDialog.vue'
import DashboardShell from '@/components/DashboardShell.vue'
@@ -29,8 +28,8 @@ import { classifyFailure } from '@/lib/directoryErrors'
import type { DirectoryMenuItem } from '@/lib/directoryMenu'
import { useSessionStore } from '@/stores/session'
import { usePermissionsStore } from '@/stores/permissions'
import { useIsDesktop } from '@/composables/useIsDesktop'
const DESKTOP_QUERY = '(min-width: 48rem)'
const LIST_COLUMNS = 'minmax(0,1.1fr) minmax(0,1.5fr) 110px 44px'
const INITIAL_SKELETON_ROWS = 3
@@ -42,7 +41,7 @@ const permissions = usePermissionsStore()
const overlay = useLocalizedOverlay()
const toasts = useDirectoryToasts()
const { rememberOpener, restoreFocus, leavePanel, followEditLink } = useDirectoryView()
const isDesktop = useMediaQuery(DESKTOP_QUERY)
const isDesktop = useIsDesktop()
const { listAllSites, listSitePage, setDefaultSite, deleteSite } = useSites()
const removal = useDirectoryDelete<Site>()
+2 -3
View File
@@ -2,7 +2,6 @@
import { t } from '@/i18n'
import { computed, nextTick, onBeforeUnmount, onMounted, ref } from 'vue'
import { useMediaQuery } from '@vueuse/core'
import ChangeRoleDialog from '@/components/ChangeRoleDialog.vue'
import DashboardShell from '@/components/DashboardShell.vue'
import InviteUserModal from '@/components/InviteUserModal.vue'
@@ -15,16 +14,16 @@ import type { AcceptedUser, UserListItem } from '@/lib/users'
import type { UserMenuContext } from '@/lib/userMenu'
import { useSessionStore } from '@/stores/session'
import { usePermissionsStore } from '@/stores/permissions'
import { useIsDesktop } from '@/composables/useIsDesktop'
const SKELETON_DELAY_MS = 150
const DESKTOP_QUERY = '(min-width: 48rem)'
type LoadState = 'loading' | 'ready' | 'error'
const session = useSessionStore()
const permissions = usePermissionsStore()
const { listUsers } = useUsers()
const isDesktop = useMediaQuery(DESKTOP_QUERY)
const isDesktop = useIsDesktop()
const loadState = ref<LoadState>('loading')
const items = ref<UserListItem[]>([])
+2 -3
View File
@@ -2,7 +2,6 @@
import { formatNumber, t } from '@/i18n'
import { computed, nextTick, onBeforeUnmount, ref, watch } from 'vue'
import { useMediaQuery } from '@vueuse/core'
import { useLocalizedOverlay } from '@/i18n/presentation'
import ConfirmDialog from '@/components/ConfirmDialog.vue'
import DashboardShell from '@/components/DashboardShell.vue'
@@ -30,8 +29,8 @@ import { formatVariantSize } from '@/lib/variants/variantFormat'
import { useImageVariantsStore } from '@/stores/imageVariants'
import { useSessionStore } from '@/stores/session'
import { usePermissionsStore } from '@/stores/permissions'
import { useIsDesktop } from '@/composables/useIsDesktop'
const DESKTOP_QUERY = '(min-width: 40rem)'
const SKELETON_DELAY_MS = 150
const NOT_FOUND = 404
@@ -46,7 +45,7 @@ const permissions = usePermissionsStore()
const store = useImageVariantsStore()
const overlay = useLocalizedOverlay()
const { announceFailure, announceSuccess } = useVariantToasts()
const isDesktop = useMediaQuery(DESKTOP_QUERY)
const isDesktop = useIsDesktop('40rem')
const showSkeleton = ref(false)
const formTarget = ref<FormTarget | null>(null)
@@ -85,7 +85,7 @@ describe('Single relation, empty, focused search', () => {
await typeSearch('venue', 'rue ')
await settleSearch(vi)
expect(searches()).toEqual(['/v1/content-type/venues/entry?q=rue&page=1&size=10'])
expect(searches()).toEqual(['/v1/content-type/venues/entry?q=rue&page=1&size=25'])
expect(optionLabels()).toEqual([
'Rue Verte hall Lyon · 120 seats',
'Rue Verte yard Lyon · outdoor',
@@ -103,7 +103,7 @@ describe('Single relation, empty, focused search', () => {
await openList('venue', vi)
expect(searches()).toEqual(['/v1/content-type/venues/entry?page=1&size=10'])
expect(searches()).toEqual(['/v1/content-type/venues/entry?page=1&size=25'])
expect(optionLabels()).toHaveLength(4)
})
@@ -82,7 +82,7 @@ describe('No match', () => {
await flushPromises()
await settleSearch(vi)
expect(relationInput('venue').value).toBe('')
expect(searches().at(-1)).toBe('/v1/content-type/venues/entry?page=1&size=10')
expect(searches().at(-1)).toBe('/v1/content-type/venues/entry?page=1&size=25')
})
it('says the type has no entries at all', async () => {
@@ -123,7 +123,7 @@ describe('Search failures', () => {
await flushPromises()
expect(calls.slice(before)).toEqual([
'/v1/content-type/venues/entry?q=rue&page=1&size=10',
'/v1/content-type/venues/entry?q=rue&page=1&size=25',
])
expect(optionLabels()).toHaveLength(3)
},
@@ -175,7 +175,7 @@ describe('Load more', () => {
button('Load more')!.click()
await flushPromises()
expect(searches().at(-1)).toBe('/v1/content-type/venues/entry?page=2&size=10')
expect(searches().at(-1)).toBe('/v1/content-type/venues/entry?page=2&size=25')
expect(optionLabels()).toHaveLength(12)
expect(button('Load more')).toBeUndefined()
})
@@ -652,8 +652,7 @@ describe('Errors', () => {
'The expiry must be a date in the future.',
'You must be logged in to access this resource.',
'This action needs a real session.',
'Only owners and admins can do this.',
'Only the owner can do this.',
'missing_permission',
'You must verify your account to access this resource.',
'Article not found',
'Image not found',
@@ -697,8 +696,7 @@ describe('Errors table rows of the custom content types round', () => {
const order = [
'This action needs a real session.',
'Only owners and admins can do this.',
'Only the owner can do this.',
'missing_permission',
'You must verify your account to access this resource.',
].map((message) => text.indexOf(message))
expect(order.every((position) => position > -1)).toBe(true)
@@ -909,7 +907,7 @@ describe('Expanded cards of the custom content types round', () => {
[
'POST /v1/content-type/',
['key', 'name', 'icon', 'fields'],
['400', '401', 'Only owners and admins can do this.', 'content_type_key_taken'],
['400', '401', '403', 'content_type_key_taken'],
],
[
'PUT /v1/content-type/{key}',
@@ -1134,7 +1132,7 @@ describe('Authentication copy of the custom content types round', () => {
"Requests act as the user who created the key, with that user's role (owner or admin).",
)
expect(auth).toContain(
'Owner and admin routes answer 403 to a key whose creator is neither.',
'Routes that need a permission answer 403 "missing_permission" to a key when the role of its creator does not hold it.',
)
})
})
@@ -187,7 +187,7 @@ describe('Search and sharing group', () => {
const wrapper = await mountIntegration()
const forbidden = ERROR_ROWS.find((row) => row.status === 403)!
expect(forbidden.messages).toHaveLength(4)
expect(forbidden.messages).toHaveLength(3)
expect(forbidden.note).toBe(NOTE)
const notes = wrapper.findAll('[data-testid="error-note"]').map((item) => item.text())
expect(notes.filter((item) => item === NOTE)).toHaveLength(2)