fix: replace any types in frontend components
- use Leaflet types (L.Map, L.Marker, L.LeafletEvent) for map components - use Post and Project types from Prisma for form components - use JSONContent type for editor instances - use Snippet type for Svelte 5 render functions - use EditorView type for TipTap/ProseMirror views - use proper type guards for error handling - add editor interface types for save/clear methods
This commit is contained in:
parent
8ec4c582c1
commit
9c746d51c0
12 changed files with 50 additions and 42 deletions
|
|
@ -19,9 +19,9 @@
|
||||||
}: Props = $props()
|
}: Props = $props()
|
||||||
|
|
||||||
let mapContainer: HTMLDivElement
|
let mapContainer: HTMLDivElement
|
||||||
let map: any
|
let map: L.Map | null = null
|
||||||
let marker: any
|
let marker: L.Marker | null = null
|
||||||
let leaflet: any
|
let leaflet: typeof L | null = null
|
||||||
|
|
||||||
// Load Leaflet dynamically
|
// Load Leaflet dynamically
|
||||||
async function loadLeaflet() {
|
async function loadLeaflet() {
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,9 @@
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
|
import type { Snippet } from 'svelte'
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
left?: any
|
left?: Snippet
|
||||||
right?: any
|
right?: Snippet
|
||||||
}
|
}
|
||||||
|
|
||||||
let { left, right }: Props = $props()
|
let { left, right }: Props = $props()
|
||||||
|
|
|
||||||
|
|
@ -37,8 +37,8 @@
|
||||||
let isSaving = $state(false)
|
let isSaving = $state(false)
|
||||||
let validationErrors = $state<Record<string, string>>({})
|
let validationErrors = $state<Record<string, string>>({})
|
||||||
let showBulkAlbumModal = $state(false)
|
let showBulkAlbumModal = $state(false)
|
||||||
let albumMedia = $state<any[]>([])
|
let albumMedia = $state<Array<{ media: Media; displayOrder: number }>>([])
|
||||||
let editorInstance = $state<any>()
|
let editorInstance = $state<{ save: () => Promise<JSONContent>; clear: () => void } | undefined>()
|
||||||
let activeTab = $state('metadata')
|
let activeTab = $state('metadata')
|
||||||
let pendingMediaIds = $state<number[]>([]) // Photos to add after album creation
|
let pendingMediaIds = $state<number[]>([]) // Photos to add after album creation
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,8 @@
|
||||||
import { makeDraftKey, saveDraft, loadDraft, clearDraft, timeAgo } from '$lib/admin/draftStore'
|
import { makeDraftKey, saveDraft, loadDraft, clearDraft, timeAgo } from '$lib/admin/draftStore'
|
||||||
import { createAutoSaveStore } from '$lib/admin/autoSave.svelte'
|
import { createAutoSaveStore } from '$lib/admin/autoSave.svelte'
|
||||||
import AutoSaveStatus from './AutoSaveStatus.svelte'
|
import AutoSaveStatus from './AutoSaveStatus.svelte'
|
||||||
import type { JSONContent } from '@tiptap/core'
|
import type { JSONContent, Editor as TipTapEditor } from '@tiptap/core'
|
||||||
|
import type { Post } from '@prisma/client'
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
postId?: number
|
postId?: number
|
||||||
|
|
@ -43,7 +44,7 @@
|
||||||
let tagInput = $state('')
|
let tagInput = $state('')
|
||||||
|
|
||||||
// Ref to the editor component
|
// Ref to the editor component
|
||||||
let editorRef: any
|
let editorRef: { save: () => Promise<JSONContent> } | undefined
|
||||||
|
|
||||||
// Draft backup
|
// Draft backup
|
||||||
const draftKey = $derived(makeDraftKey('post', postId ?? 'new'))
|
const draftKey = $derived(makeDraftKey('post', postId ?? 'new'))
|
||||||
|
|
@ -80,8 +81,8 @@ let autoSave = mode === 'edit' && postId
|
||||||
if (!response.ok) throw new Error('Failed to save')
|
if (!response.ok) throw new Error('Failed to save')
|
||||||
return await response.json()
|
return await response.json()
|
||||||
},
|
},
|
||||||
onSaved: (saved: any, { prime }) => {
|
onSaved: (saved: Post, { prime }) => {
|
||||||
updatedAt = saved.updatedAt
|
updatedAt = saved.updatedAt.toISOString()
|
||||||
prime(buildPayload())
|
prime(buildPayload())
|
||||||
if (draftKey) clearDraft(draftKey)
|
if (draftKey) clearDraft(draftKey)
|
||||||
}
|
}
|
||||||
|
|
@ -144,7 +145,7 @@ $effect(() => {
|
||||||
|
|
||||||
// Show restore prompt if a draft exists
|
// Show restore prompt if a draft exists
|
||||||
$effect(() => {
|
$effect(() => {
|
||||||
const draft = loadDraft<any>(draftKey)
|
const draft = loadDraft<ReturnType<typeof buildPayload>>(draftKey)
|
||||||
if (draft) {
|
if (draft) {
|
||||||
showDraftPrompt = true
|
showDraftPrompt = true
|
||||||
draftTimestamp = draft.ts
|
draftTimestamp = draft.ts
|
||||||
|
|
@ -152,7 +153,7 @@ $effect(() => {
|
||||||
})
|
})
|
||||||
|
|
||||||
function restoreDraft() {
|
function restoreDraft() {
|
||||||
const draft = loadDraft<any>(draftKey)
|
const draft = loadDraft<ReturnType<typeof buildPayload>>(draftKey)
|
||||||
if (!draft) return
|
if (!draft) return
|
||||||
const p = draft.payload
|
const p = draft.payload
|
||||||
title = p.title ?? title
|
title = p.title ?? title
|
||||||
|
|
|
||||||
|
|
@ -1,16 +1,18 @@
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
|
import type { Snippet } from 'svelte'
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
label: string
|
label: string
|
||||||
name?: string
|
name?: string
|
||||||
type?: string
|
type?: string
|
||||||
value?: any
|
value?: string | number
|
||||||
placeholder?: string
|
placeholder?: string
|
||||||
required?: boolean
|
required?: boolean
|
||||||
error?: string
|
error?: string
|
||||||
helpText?: string
|
helpText?: string
|
||||||
disabled?: boolean
|
disabled?: boolean
|
||||||
onchange?: (e: Event) => void
|
onchange?: (e: Event) => void
|
||||||
children?: any
|
children?: Snippet
|
||||||
}
|
}
|
||||||
|
|
||||||
let {
|
let {
|
||||||
|
|
|
||||||
|
|
@ -42,7 +42,7 @@
|
||||||
content: [{ type: 'paragraph' }]
|
content: [{ type: 'paragraph' }]
|
||||||
}
|
}
|
||||||
let characterCount = 0
|
let characterCount = 0
|
||||||
let editorInstance: any
|
let editorInstance: { save: () => Promise<JSONContent>; clear: () => void } | undefined
|
||||||
|
|
||||||
// Essay metadata
|
// Essay metadata
|
||||||
let essayTitle = ''
|
let essayTitle = ''
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,9 @@
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import GenericMetadataPopover, { type MetadataConfig } from './GenericMetadataPopover.svelte'
|
import GenericMetadataPopover, { type MetadataConfig } from './GenericMetadataPopover.svelte'
|
||||||
|
import type { Post } from '@prisma/client'
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
post: any
|
post: Post
|
||||||
postType: 'post' | 'essay'
|
postType: 'post' | 'essay'
|
||||||
slug: string
|
slug: string
|
||||||
tags: string[]
|
tags: string[]
|
||||||
|
|
@ -12,7 +13,7 @@
|
||||||
onRemoveTag: (tag: string) => void
|
onRemoveTag: (tag: string) => void
|
||||||
onDelete: () => void
|
onDelete: () => void
|
||||||
onClose?: () => void
|
onClose?: () => void
|
||||||
onFieldUpdate?: (key: string, value: any) => void
|
onFieldUpdate?: (key: string, value: unknown) => void
|
||||||
}
|
}
|
||||||
|
|
||||||
let {
|
let {
|
||||||
|
|
@ -29,11 +30,11 @@
|
||||||
onFieldUpdate
|
onFieldUpdate
|
||||||
}: Props = $props()
|
}: Props = $props()
|
||||||
|
|
||||||
function handleFieldUpdate(key: string, value: any) {
|
function handleFieldUpdate(key: string, value: unknown) {
|
||||||
if (key === 'slug') {
|
if (key === 'slug' && typeof value === 'string') {
|
||||||
slug = value
|
slug = value
|
||||||
onFieldUpdate?.(key, value)
|
onFieldUpdate?.(key, value)
|
||||||
} else if (key === 'tagInput') {
|
} else if (key === 'tagInput' && typeof value === 'string') {
|
||||||
tagInput = value
|
tagInput = value
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -17,6 +17,7 @@
|
||||||
import { useFormGuards } from '$lib/admin/useFormGuards.svelte'
|
import { useFormGuards } from '$lib/admin/useFormGuards.svelte'
|
||||||
import { makeDraftKey, saveDraft, clearDraft } from '$lib/admin/draftStore'
|
import { makeDraftKey, saveDraft, clearDraft } from '$lib/admin/draftStore'
|
||||||
import type { ProjectFormData } from '$lib/types/project'
|
import type { ProjectFormData } from '$lib/types/project'
|
||||||
|
import type { JSONContent } from '@tiptap/core'
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
project?: Project | null
|
project?: Project | null
|
||||||
|
|
@ -37,7 +38,7 @@
|
||||||
let successMessage = $state<string | null>(null)
|
let successMessage = $state<string | null>(null)
|
||||||
|
|
||||||
// Ref to the editor component
|
// Ref to the editor component
|
||||||
let editorRef: any
|
let editorRef: { save: () => Promise<JSONContent> } | undefined
|
||||||
|
|
||||||
// Draft key for autosave fallback
|
// Draft key for autosave fallback
|
||||||
const draftKey = $derived(mode === 'edit' && project ? makeDraftKey('project', project.id) : null)
|
const draftKey = $derived(mode === 'edit' && project ? makeDraftKey('project', project.id) : null)
|
||||||
|
|
@ -50,7 +51,7 @@
|
||||||
save: async (payload, { signal }) => {
|
save: async (payload, { signal }) => {
|
||||||
return await api.put(`/api/projects/${project?.id}`, payload, { signal })
|
return await api.put(`/api/projects/${project?.id}`, payload, { signal })
|
||||||
},
|
},
|
||||||
onSaved: (savedProject: any, { prime }) => {
|
onSaved: (savedProject: Project, { prime }) => {
|
||||||
project = savedProject
|
project = savedProject
|
||||||
formStore.populateFromProject(savedProject)
|
formStore.populateFromProject(savedProject)
|
||||||
prime(formStore.buildPayload())
|
prime(formStore.buildPayload())
|
||||||
|
|
@ -112,7 +113,7 @@
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
function handleEditorChange(content: any) {
|
function handleEditorChange(content: JSONContent) {
|
||||||
formStore.setField('caseStudyContent', content)
|
formStore.setField('caseStudyContent', content)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -158,7 +159,7 @@
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
toast.dismiss(loadingToastId)
|
toast.dismiss(loadingToastId)
|
||||||
if ((err as any)?.status === 409) {
|
if (err && typeof err === 'object' && 'status' in err && (err as { status: number }).status === 409) {
|
||||||
toast.error('This project has changed in another tab. Please reload.')
|
toast.error('This project has changed in another tab. Please reload.')
|
||||||
} else {
|
} else {
|
||||||
toast.error(`Failed to ${mode === 'edit' ? 'save' : 'create'} project`)
|
toast.error(`Failed to ${mode === 'edit' ? 'save' : 'create'} project`)
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
import type { Editor } from '@tiptap/core'
|
import type { Editor, EditorView } from '@tiptap/core'
|
||||||
import type { ComposerMediaHandler } from './ComposerMediaHandler.svelte'
|
import type { ComposerMediaHandler } from './ComposerMediaHandler.svelte'
|
||||||
import { focusEditor } from '$lib/components/edra/utils'
|
import { focusEditor } from '$lib/components/edra/utils'
|
||||||
|
|
||||||
|
|
@ -12,7 +12,7 @@ export interface UseComposerEventsOptions {
|
||||||
|
|
||||||
export function useComposerEvents(options: UseComposerEventsOptions) {
|
export function useComposerEvents(options: UseComposerEventsOptions) {
|
||||||
// Handle paste events
|
// Handle paste events
|
||||||
function handlePaste(view: any, event: ClipboardEvent): boolean {
|
function handlePaste(view: EditorView, event: ClipboardEvent): boolean {
|
||||||
const clipboardData = event.clipboardData
|
const clipboardData = event.clipboardData
|
||||||
if (!clipboardData) return false
|
if (!clipboardData) return false
|
||||||
|
|
||||||
|
|
@ -30,7 +30,7 @@ export function useComposerEvents(options: UseComposerEventsOptions) {
|
||||||
event.preventDefault()
|
event.preventDefault()
|
||||||
|
|
||||||
// Use editor commands to insert HTML content
|
// Use editor commands to insert HTML content
|
||||||
const editorInstance = (view as any).editor
|
const editorInstance = options.editor
|
||||||
if (editorInstance) {
|
if (editorInstance) {
|
||||||
editorInstance
|
editorInstance
|
||||||
.chain()
|
.chain()
|
||||||
|
|
@ -66,7 +66,7 @@ export function useComposerEvents(options: UseComposerEventsOptions) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Handle drag and drop for images
|
// Handle drag and drop for images
|
||||||
function handleDrop(view: any, event: DragEvent): boolean {
|
function handleDrop(view: EditorView, event: DragEvent): boolean {
|
||||||
if (!options.features.imageUpload || !options.mediaHandler) return false
|
if (!options.features.imageUpload || !options.mediaHandler) return false
|
||||||
|
|
||||||
const files = event.dataTransfer?.files
|
const files = event.dataTransfer?.files
|
||||||
|
|
|
||||||
|
|
@ -189,7 +189,7 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
// Block manipulation functions
|
// Block manipulation functions
|
||||||
function convertBlockType(type: string, attrs?: any) {
|
function convertBlockType(type: string, attrs?: Record<string, unknown>) {
|
||||||
console.log('convertBlockType called:', type, attrs)
|
console.log('convertBlockType called:', type, attrs)
|
||||||
// Use menuNode which was captured when menu was opened
|
// Use menuNode which was captured when menu was opened
|
||||||
const nodeToConvert = menuNode || currentNode
|
const nodeToConvert = menuNode || currentNode
|
||||||
|
|
@ -486,10 +486,11 @@
|
||||||
// Find the existing drag handle created by the plugin and add click listener
|
// Find the existing drag handle created by the plugin and add click listener
|
||||||
const checkForDragHandle = setInterval(() => {
|
const checkForDragHandle = setInterval(() => {
|
||||||
const existingDragHandle = document.querySelector('.drag-handle')
|
const existingDragHandle = document.querySelector('.drag-handle')
|
||||||
if (existingDragHandle && !(existingDragHandle as any).__menuListener) {
|
const element = existingDragHandle as HTMLElement & { __menuListener?: boolean }
|
||||||
|
if (existingDragHandle && !element.__menuListener) {
|
||||||
console.log('Found drag handle, adding click listener')
|
console.log('Found drag handle, adding click listener')
|
||||||
existingDragHandle.addEventListener('click', handleMenuClick)
|
existingDragHandle.addEventListener('click', handleMenuClick)
|
||||||
;(existingDragHandle as any).__menuListener = true
|
element.__menuListener = true
|
||||||
|
|
||||||
// Update our reference to use the existing drag handle
|
// Update our reference to use the existing drag handle
|
||||||
dragHandleContainer = existingDragHandle as HTMLElement
|
dragHandleContainer = existingDragHandle as HTMLElement
|
||||||
|
|
|
||||||
|
|
@ -7,9 +7,9 @@
|
||||||
let { node, updateAttributes }: Props = $props()
|
let { node, updateAttributes }: Props = $props()
|
||||||
|
|
||||||
let mapContainer: HTMLDivElement
|
let mapContainer: HTMLDivElement
|
||||||
let map: any
|
let map: L.Map | null = null
|
||||||
let marker: any
|
let marker: L.Marker | null = null
|
||||||
let leaflet: any
|
let leaflet: typeof L | null = null
|
||||||
let isEditing = $state(false)
|
let isEditing = $state(false)
|
||||||
|
|
||||||
// Extract attributes
|
// Extract attributes
|
||||||
|
|
|
||||||
|
|
@ -19,9 +19,9 @@
|
||||||
// Map picker state
|
// Map picker state
|
||||||
let showMapPicker = $state(false)
|
let showMapPicker = $state(false)
|
||||||
let mapContainer: HTMLDivElement
|
let mapContainer: HTMLDivElement
|
||||||
let pickerMap: any
|
let pickerMap: L.Map | null = null
|
||||||
let pickerMarker: any
|
let pickerMarker: L.Marker | null = null
|
||||||
let leaflet: any
|
let leaflet: typeof L | null = null
|
||||||
|
|
||||||
// Load Leaflet for map picker
|
// Load Leaflet for map picker
|
||||||
async function loadLeaflet() {
|
async function loadLeaflet() {
|
||||||
|
|
@ -77,15 +77,15 @@
|
||||||
.addTo(pickerMap)
|
.addTo(pickerMap)
|
||||||
|
|
||||||
// Update coordinates on marker drag
|
// Update coordinates on marker drag
|
||||||
pickerMarker.on('dragend', (e: any) => {
|
pickerMarker.on('dragend', (e: L.LeafletEvent) => {
|
||||||
const position = e.target.getLatLng()
|
const position = (e.target as L.Marker).getLatLng()
|
||||||
latitude = position.lat.toFixed(6)
|
latitude = position.lat.toFixed(6)
|
||||||
longitude = position.lng.toFixed(6)
|
longitude = position.lng.toFixed(6)
|
||||||
})
|
})
|
||||||
|
|
||||||
// Update marker on map click
|
// Update marker on map click
|
||||||
pickerMap.on('click', (e: any) => {
|
pickerMap.on('click', (e: L.LeafletMouseEvent) => {
|
||||||
pickerMarker.setLatLng(e.latlng)
|
pickerMarker!.setLatLng(e.latlng)
|
||||||
latitude = e.latlng.lat.toFixed(6)
|
latitude = e.latlng.lat.toFixed(6)
|
||||||
longitude = e.latlng.lng.toFixed(6)
|
longitude = e.latlng.lng.toFixed(6)
|
||||||
})
|
})
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue