mirror of
https://github.com/sourcegraph/sourcegraph.git
synced 2026-02-06 15:51:43 +00:00
sveltekit: React integration (#48269)
This PR adds various helpers to integrate existing React routes into the SvelteKit prototype. The idea is that we create pages which handle a complete route "namespace" (e.g. `/notebooks/*`) delegate navigation within this namespace to the React component / React router. In order to properly handle "cross framework links" (i.e. a link rendered with SvelteKit pointing to a React page and vice versa), and to make navigation state synchronization more robust in general I've introduced a custom React Router router which integrates with SvelteKit's router API. This PR adds support for the `/notebooks` and `/insights` routes but I could only really test Notebooks because insights isn't available on sourcegraph.com (which the prototype proxies to). ## Test plan Run the prototype and navigate to, from ans withing `/notebooks`.
This commit is contained in:
parent
ef37053587
commit
dad459f92b
@ -1,13 +1,13 @@
|
||||
export function isChrome(): boolean {
|
||||
return !!window.navigator.userAgent.match(/chrome|chromium|crios/i)
|
||||
return typeof window !== 'undefined' && !!window.navigator.userAgent.match(/chrome|chromium|crios/i)
|
||||
}
|
||||
|
||||
export function isSafari(): boolean {
|
||||
return !!window.navigator.userAgent.match(/safari/i) && !isChrome()
|
||||
return typeof window !== 'undefined' && !!window.navigator.userAgent.match(/safari/i) && !isChrome()
|
||||
}
|
||||
|
||||
export function isFirefox(): boolean {
|
||||
return window.navigator.userAgent.includes('Firefox')
|
||||
return typeof window !== 'undefined' && window.navigator.userAgent.includes('Firefox')
|
||||
}
|
||||
|
||||
export function getBrowserName(): 'chrome' | 'safari' | 'firefox' | 'other' {
|
||||
@ -19,5 +19,5 @@ export function getBrowserName(): 'chrome' | 'safari' | 'firefox' | 'other' {
|
||||
* is accessed only when the function is called
|
||||
*/
|
||||
export function isMacPlatform(): boolean {
|
||||
return window.navigator.platform.includes('Mac')
|
||||
return typeof window !== 'undefined' && window.navigator.platform.includes('Mac')
|
||||
}
|
||||
|
||||
@ -12,3 +12,5 @@ package.json
|
||||
pnpm-lock.yaml
|
||||
package-lock.json
|
||||
yarn.lock
|
||||
|
||||
tsconfig.json
|
||||
|
||||
@ -17,21 +17,22 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"@playwright/test": "1.25.0",
|
||||
"@sveltejs/adapter-auto": "1.0.0-next.91",
|
||||
"@sveltejs/adapter-auto": "^2.0.0",
|
||||
"@sveltejs/adapter-static": "^2.0.0",
|
||||
"@sveltejs/kit": "^1.3.2",
|
||||
"@sveltejs/kit": "^1.5.6",
|
||||
"@types/cookie": "^0.5.1",
|
||||
"@types/prismjs": "^1.26.0",
|
||||
"eslint-plugin-svelte3": "^4.0.0",
|
||||
"prettier-plugin-svelte": "^2.9.0",
|
||||
"svelte": "^3.55.1",
|
||||
"svelte-check": "^3.0.3",
|
||||
"svelte-check": "^3.0.4",
|
||||
"tslib": "2.1.0",
|
||||
"vite": "^4.0.4"
|
||||
"vite": "^4.1.4"
|
||||
},
|
||||
"type": "module",
|
||||
"dependencies": {
|
||||
"@popperjs/core": "^2.11.6",
|
||||
"@remix-run/router": "^1.3.2",
|
||||
"@sourcegraph/branded": "workspace:*",
|
||||
"@sourcegraph/common": "workspace:*",
|
||||
"@sourcegraph/http-client": "workspace:*",
|
||||
|
||||
38
client/web-sveltekit/src/lib/ReactComponent.svelte
Normal file
38
client/web-sveltekit/src/lib/ReactComponent.svelte
Normal file
@ -0,0 +1,38 @@
|
||||
<script lang="ts">
|
||||
import React from 'react'
|
||||
import { createRoot, type Root } from 'react-dom/client'
|
||||
import { onDestroy, onMount } from 'svelte'
|
||||
import { ReactAdapter } from './react-interop'
|
||||
|
||||
type ComponentProps = $$Generic<{}>
|
||||
|
||||
export let component: React.FunctionComponent<ComponentProps>
|
||||
export let props: ComponentProps
|
||||
export let route: string
|
||||
|
||||
let container: HTMLDivElement
|
||||
let root: Root | null = null
|
||||
|
||||
function renderComponent(
|
||||
root: Root | null,
|
||||
component: React.FunctionComponent<ComponentProps>,
|
||||
props: ComponentProps,
|
||||
route: string
|
||||
) {
|
||||
root?.render(
|
||||
React.createElement(
|
||||
ReactAdapter,
|
||||
{
|
||||
route,
|
||||
},
|
||||
React.createElement(component, props)
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
onMount(() => (root = createRoot(container)))
|
||||
onDestroy(() => root?.unmount())
|
||||
$: renderComponent(root, component, props, route)
|
||||
</script>
|
||||
|
||||
<div bind:this={container} />
|
||||
@ -8,6 +8,8 @@ import { eventLogger, type EventLogger } from '@sourcegraph/web/src/tracking/eve
|
||||
|
||||
import { PUBLIC_ENABLE_EVENT_LOGGER } from '$env/static/public'
|
||||
|
||||
export { eventLogger }
|
||||
|
||||
/**
|
||||
* Can only be called during component initialization. It logs a view event when
|
||||
* the component is mounted (and event logging is enabled).
|
||||
|
||||
195
client/web-sveltekit/src/lib/react-interop.tsx
Normal file
195
client/web-sveltekit/src/lib/react-interop.tsx
Normal file
@ -0,0 +1,195 @@
|
||||
import React, { useEffect, useMemo, type FC, type PropsWithChildren } from 'react'
|
||||
|
||||
import { createRouter, type History, Action, type Location, type Router } from '@remix-run/router'
|
||||
import type { Navigation } from '@sveltejs/kit'
|
||||
import { RouterProvider, UNSAFE_enhanceManualRouteObjects, type RouteObject } from 'react-router-dom'
|
||||
import { RouterLink, setLinkComponent } from 'wildcard/src'
|
||||
|
||||
import { WildcardThemeContext, type WildcardTheme } from './wildcard'
|
||||
|
||||
import { goto } from '$app/navigation'
|
||||
import { navigating } from '$app/stores'
|
||||
|
||||
setLinkComponent(RouterLink)
|
||||
|
||||
const WILDCARD_THEME: WildcardTheme = {
|
||||
isBranded: true,
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a minimal context for rendering React components inside Svelte, including a
|
||||
* custom React Router router to integrate with SvelteKit.
|
||||
*/
|
||||
export const ReactAdapter: FC<PropsWithChildren<{ route: string }>> = ({ route, children }) => {
|
||||
const router = useMemo(
|
||||
() =>
|
||||
createSvelteKitRouter([
|
||||
{
|
||||
path: route,
|
||||
// React.Suspense seems necessary to render the components without error
|
||||
element: <React.Suspense fallback={true}>{children}</React.Suspense>,
|
||||
},
|
||||
]),
|
||||
[route, children]
|
||||
)
|
||||
|
||||
// Dispose is marked as INTERNAL but without calling it the listeners created by React
|
||||
// Router are not removed. It doesn't look like React Router expects to be unmounted
|
||||
// during the lifetime of the application.
|
||||
useEffect(() => () => router.dispose(), [router])
|
||||
|
||||
return (
|
||||
<WildcardThemeContext.Provider value={WILDCARD_THEME}>
|
||||
<RouterProvider router={router} />
|
||||
</WildcardThemeContext.Provider>
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Custom router that synchronizes between the SvelteKit routing and React router.
|
||||
*/
|
||||
function createSvelteKitRouter(routes: RouteObject[]): Router {
|
||||
return createRouter({
|
||||
routes: UNSAFE_enhanceManualRouteObjects(routes),
|
||||
history: createSvelteKitHistory(),
|
||||
}).initialize()
|
||||
}
|
||||
|
||||
/**
|
||||
* Custom history that synchronizes between the SvelteKit routing and React router.
|
||||
* This is a "best effort" implementation because the API used here is not very well
|
||||
* documented or doesn't seem to be intended for this use case.
|
||||
*
|
||||
* Caveat: Using the browser back/forward buttons to navigate between hash targets on the
|
||||
* same page doesn't seem to scroll the target into view. It's not clear yet why that is.
|
||||
*/
|
||||
function createSvelteKitHistory(): History {
|
||||
let action: Action = Action.Push
|
||||
|
||||
const history: History = {
|
||||
get action() {
|
||||
return action
|
||||
},
|
||||
|
||||
get location() {
|
||||
return createLocation(window.location)
|
||||
},
|
||||
|
||||
createURL,
|
||||
|
||||
createHref(to) {
|
||||
return typeof to === 'string' ? to : toPath(createURL(to))
|
||||
},
|
||||
|
||||
go(delta) {
|
||||
window.history.go(delta)
|
||||
},
|
||||
|
||||
push(to, state) {
|
||||
action = Action.Push
|
||||
// Without this, links that are outside of the React component (i.e. Svelte)
|
||||
// pointing to a path handle by the React component causes duplicate entries
|
||||
// See below for more information.
|
||||
// This is safe to do because the browser does the same when clicking on a
|
||||
// link that navigates to the curren page.
|
||||
if (createURL(to).href !== window.location.href) {
|
||||
goto(createURL(to), { state: state ?? undefined })
|
||||
// Make eslint happy
|
||||
.catch(() => {})
|
||||
}
|
||||
},
|
||||
|
||||
replace(to, state) {
|
||||
action = Action.Replace
|
||||
goto(createURL(to), { state: state ?? undefined, replaceState: true })
|
||||
// Make eslint happy
|
||||
.catch(() => {})
|
||||
},
|
||||
|
||||
encodeLocation(to) {
|
||||
const url = createURL(to)
|
||||
return {
|
||||
hash: url.hash,
|
||||
search: url.search,
|
||||
pathname: url.pathname,
|
||||
}
|
||||
},
|
||||
|
||||
listen(listener) {
|
||||
let prevState: Navigation | null = null
|
||||
return navigating.subscribe(state => {
|
||||
// Events are emitted when navigation *starts*. That means the browser URL hasn't updated yet
|
||||
// I don't know whether that's relevant for React Router or not, but in order to make the
|
||||
// equality check in the `push` method possible we need to wait to call `listener` until the
|
||||
// navigation completed.
|
||||
// This is done by storing the emitted value in `prevState` and wait until the next event, which
|
||||
// will emit `null`.
|
||||
// NOTE: SvelteKit does not emit events when the back/forward buttons are used to navigate between
|
||||
// "hashes" on the same page. SvelteKit instead lets the browser handle these natively. However
|
||||
// I noticed that at least on Notebook pages this won't scroll the target into view.
|
||||
if (!state && prevState) {
|
||||
switch (prevState.type) {
|
||||
case 'popstate':
|
||||
action = Action.Pop
|
||||
if (prevState.to) {
|
||||
listener({
|
||||
action,
|
||||
location: createLocation(prevState.to.url),
|
||||
delta: prevState.delta ?? 0,
|
||||
})
|
||||
}
|
||||
break
|
||||
case 'link':
|
||||
// This is a special case for SvelteKit. In a normal browser context it seems that `listen`
|
||||
// should only handle popstate events. Listening to the SvelteKit 'link' event seems
|
||||
// necessary to properly handle SvelteKit links which point to paths handled by this React
|
||||
// component (it neither works without it nor with the default browser router).
|
||||
// However, React Router doesn't seem to expect that `listener` can be called for "push" events
|
||||
// and will subequently call `history.push`. In order to prevent a double history entry
|
||||
// we are checking whether the target URL is the same as the current URL and do not call
|
||||
// back to SvelteKit again if that's the case.
|
||||
action = Action.Push
|
||||
if (prevState.to) {
|
||||
listener({ action, location: createLocation(prevState.to.url), delta: 1 })
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
prevState = state
|
||||
})
|
||||
},
|
||||
}
|
||||
|
||||
return history
|
||||
}
|
||||
|
||||
function createURL(path: string | { pathname?: string; search?: string; hash?: string }): URL {
|
||||
if (typeof path === 'string') {
|
||||
return new URL(path, window.location.href)
|
||||
}
|
||||
const url = new URL(window.location.href)
|
||||
if (path.pathname !== undefined) {
|
||||
url.pathname = path.pathname
|
||||
}
|
||||
if (path.search !== undefined) {
|
||||
url.search = path.search
|
||||
}
|
||||
if (path.hash !== undefined) {
|
||||
url.hash = path.hash
|
||||
}
|
||||
return url
|
||||
}
|
||||
|
||||
function createLocation(target: URL | typeof window['location']): Location {
|
||||
return {
|
||||
pathname: target.pathname,
|
||||
search: target.search,
|
||||
hash: target.hash,
|
||||
state: window.history.state?.usr ?? null,
|
||||
key: window.history.state?.key ?? 'default',
|
||||
}
|
||||
}
|
||||
|
||||
function toPath(location: { pathname: string; search: string; hash: string }): string {
|
||||
return location.pathname + location.search + location.hash
|
||||
}
|
||||
@ -35,7 +35,6 @@ export { filterExists } from '@sourcegraph/shared/src/search/query/validate'
|
||||
export { FilterType } from '@sourcegraph/shared/src/search/query/filters'
|
||||
export { getGlobalSearchContextFilter } from '@sourcegraph/shared/src/search/query/query'
|
||||
export { omitFilter } from '@sourcegraph/shared/src/search/query/transformer'
|
||||
export { observeSystemIsLightTheme } from '@sourcegraph/shared/src/theme'
|
||||
export type { PlatformContext } from '@sourcegraph/shared/src/platform/context'
|
||||
export { type SettingsCascade, type SettingsSubject, gqlToCascade } from '@sourcegraph/shared/src/settings/settings'
|
||||
export { fetchStreamSuggestions } from '@sourcegraph/shared/src/search/suggestions'
|
||||
@ -43,6 +42,7 @@ export { QueryChangeSource, type QueryState } from '@sourcegraph/shared/src/sear
|
||||
export { migrateLocalStorageToTemporarySettings } from '@sourcegraph/shared/src/settings/temporary/migrateLocalStorageToTemporarySettings'
|
||||
export type { TemporarySettings } from '@sourcegraph/shared/src/settings/temporary/TemporarySettings'
|
||||
export { SyntaxKind } from '@sourcegraph/shared/src/codeintel/scip'
|
||||
export { type FetchFileParameters, fetchHighlightedFileLineRanges } from '@sourcegraph/shared/src/backend/file'
|
||||
|
||||
// Copies of non-reusable code
|
||||
|
||||
|
||||
@ -16,6 +16,12 @@ export { replaceRevisionInURL } from '@sourcegraph/web/src/util/url'
|
||||
export type { ResolvedRevision } from '@sourcegraph/web/src/repo/backend'
|
||||
export { syntaxHighlight } from '@sourcegraph/web/src/repo/blob/codemirror/highlight'
|
||||
export { defaultSearchModeFromSettings } from '@sourcegraph/web/src/util/settings'
|
||||
export { setExperimentalFeaturesFromSettings } from '@sourcegraph/web/src/stores/experimentalFeatures'
|
||||
export { GlobalNotebooksArea, type GlobalNotebooksAreaProps } from '@sourcegraph/web/src/notebooks/GlobalNotebooksArea'
|
||||
export {
|
||||
CodeInsightsRouter,
|
||||
type CodeInsightsRouterProps,
|
||||
} from '@sourcegraph/web/src/enterprise/insights/CodeInsightsRouter'
|
||||
|
||||
// Copy of non-reusable code
|
||||
|
||||
|
||||
@ -1,2 +1,6 @@
|
||||
// We want to limit the number of imported modules as much as possible
|
||||
/* eslint-disable no-restricted-imports */
|
||||
|
||||
export { default as Button } from './Button.svelte'
|
||||
export { default as ButtonGroup } from './ButtonGroup.svelte'
|
||||
export { WildcardThemeContext, type WildcardTheme } from '@sourcegraph/wildcard/src/hooks/useWildcardTheme'
|
||||
|
||||
@ -0,0 +1,19 @@
|
||||
<script lang="ts">
|
||||
import { CodeInsightsRouter, type CodeInsightsRouterProps } from '$lib/web'
|
||||
import { PUBLIC_DOTCOM } from '$env/static/public'
|
||||
import type { PageData } from './$types'
|
||||
import ReactComponent from '$lib/ReactComponent.svelte'
|
||||
import { eventLogger } from '$lib/logger'
|
||||
|
||||
export let data: PageData
|
||||
|
||||
const isSourcegraphDotCom = !!PUBLIC_DOTCOM
|
||||
|
||||
$: props = {
|
||||
telemetryService: eventLogger,
|
||||
isSourcegraphDotCom,
|
||||
authenticatedUser: data.user,
|
||||
} satisfies CodeInsightsRouterProps
|
||||
</script>
|
||||
|
||||
<ReactComponent route="/insights/*" component={CodeInsightsRouter} {props} />
|
||||
@ -0,0 +1,46 @@
|
||||
<!--
|
||||
This page is rendered by using the corresponding React component.
|
||||
|
||||
CAVEAT: Using back/forward buttons to navigate between headings inside a
|
||||
Notebook (has navigation) doesn't scroll the target into view.
|
||||
We need to investigate if that's a fundamental problem with
|
||||
SvelteKit<->ReactRouter of if Notebooks are doing something that prevents
|
||||
the default browser behavior to take place.
|
||||
-->
|
||||
<script lang="ts">
|
||||
import { PUBLIC_DOTCOM } from '$env/static/public'
|
||||
import { GlobalNotebooksArea, type GlobalNotebooksAreaProps } from '$lib/web'
|
||||
import type { PageData } from './$types'
|
||||
import {
|
||||
aggregateStreamingSearch,
|
||||
fetchHighlightedFileLineRanges as _fetchHighlightedFileLineRanges,
|
||||
type FetchFileParameters,
|
||||
} from '$lib/shared'
|
||||
import type { Observable } from 'rxjs'
|
||||
import ReactComponent from '$lib/ReactComponent.svelte'
|
||||
import { eventLogger } from '$lib/logger'
|
||||
|
||||
export let data: PageData
|
||||
|
||||
const isSourcegraphDotCom = !!PUBLIC_DOTCOM
|
||||
|
||||
function fetchHighlightedFileLineRanges(parameters: FetchFileParameters, force?: boolean): Observable<string[][]> {
|
||||
return _fetchHighlightedFileLineRanges({ ...parameters, platformContext: data.platformContext }, force)
|
||||
}
|
||||
|
||||
$: props = {
|
||||
fetchHighlightedFileLineRanges,
|
||||
telemetryService: eventLogger,
|
||||
isSourcegraphDotCom,
|
||||
// FIXME: Terrible hack to avoid having to create a complete context object
|
||||
platformContext: data.platformContext as any,
|
||||
authenticatedUser: data.user,
|
||||
notebooksEnabled: true,
|
||||
globbing: false,
|
||||
settingsCascade: data.settings,
|
||||
searchContextsEnabled: false,
|
||||
streamSearch: aggregateStreamingSearch,
|
||||
} satisfies GlobalNotebooksAreaProps
|
||||
</script>
|
||||
|
||||
<ReactComponent route="/notebooks/*" component={GlobalNotebooksArea} {props} />
|
||||
@ -1,24 +1,39 @@
|
||||
<script lang="ts">
|
||||
import { onMount, setContext } from 'svelte'
|
||||
import { readable, writable } from 'svelte/store'
|
||||
import { readable, writable, type Readable } from 'svelte/store'
|
||||
|
||||
import { browser } from '$app/environment'
|
||||
import { KEY, type SourcegraphContext } from '$lib/stores'
|
||||
import { isErrorLike } from '$lib/common'
|
||||
import { observeSystemIsLightTheme, TemporarySettingsStorage } from '$lib/shared'
|
||||
import { readableObservable } from '$lib/utils'
|
||||
import { TemporarySettingsStorage } from '$lib/shared'
|
||||
import { createTemporarySettingsStorage } from '$lib/temporarySettings'
|
||||
|
||||
import Header from './Header.svelte'
|
||||
import './styles.scss'
|
||||
import type { LayoutData } from './$types'
|
||||
import type { LayoutData, Snapshot } from './$types'
|
||||
import { setExperimentalFeaturesFromSettings } from '$lib/web'
|
||||
import { beforeNavigate } from '$app/navigation'
|
||||
|
||||
export let data: LayoutData
|
||||
|
||||
function createLightThemeStore(): Readable<boolean> {
|
||||
if (browser) {
|
||||
const matchMedia = window.matchMedia('(prefers-color-scheme: dark)')
|
||||
return readable(!matchMedia.matches, set => {
|
||||
const listener = (event: MediaQueryListEventMap['change']) => {
|
||||
set(!event.matches)
|
||||
}
|
||||
matchMedia.addEventListener('change', listener)
|
||||
return () => matchMedia.removeEventListener('change', listener)
|
||||
})
|
||||
}
|
||||
return readable(true)
|
||||
}
|
||||
|
||||
const user = writable(data.user ?? null)
|
||||
const settings = writable(data.settings)
|
||||
const settings = writable(isErrorLike(data.settings) ? null : data.settings.final)
|
||||
const platformContext = writable(data.platformContext)
|
||||
const isLightTheme = browser ? readableObservable(observeSystemIsLightTheme(window).observable) : readable(true)
|
||||
const isLightTheme = createLightThemeStore()
|
||||
// It's OK to set the temporary storage during initialization time because
|
||||
// sign-in/out currently performs a full page refresh
|
||||
const temporarySettingsStorage = createTemporarySettingsStorage(
|
||||
@ -43,13 +58,44 @@
|
||||
})
|
||||
|
||||
$: $user = data.user ?? null
|
||||
$: $settings = data.settings
|
||||
$: $settings = isErrorLike(data.settings) ? null : data.settings.final
|
||||
$: $platformContext = data.platformContext
|
||||
// Sync React stores
|
||||
$: setExperimentalFeaturesFromSettings(data.settings)
|
||||
|
||||
$: if (browser) {
|
||||
document.documentElement.classList.toggle('theme-light', $isLightTheme)
|
||||
document.documentElement.classList.toggle('theme-dark', !$isLightTheme)
|
||||
}
|
||||
|
||||
let main: HTMLElement | null = null
|
||||
let scrollTop = 0
|
||||
beforeNavigate(() => {
|
||||
// It looks like `snapshot.capture` is called "too late", i.e. after the
|
||||
// content has been updated. beforeNavigate is used to capture the correct
|
||||
// scroll offset
|
||||
scrollTop = main?.scrollTop ?? 0
|
||||
})
|
||||
export const snapshot: Snapshot<{ x: number }> = {
|
||||
capture() {
|
||||
return { x: scrollTop }
|
||||
},
|
||||
restore(value) {
|
||||
restoreScrollPosition(value.x)
|
||||
},
|
||||
}
|
||||
|
||||
function restoreScrollPosition(y: number) {
|
||||
const start = Date.now()
|
||||
requestAnimationFrame(function scroll() {
|
||||
if (main) {
|
||||
main.scrollTo(0, y)
|
||||
}
|
||||
if ((!main || main.scrollTop !== y) && Date.now() - start < 3000) {
|
||||
requestAnimationFrame(scroll)
|
||||
}
|
||||
})
|
||||
}
|
||||
</script>
|
||||
|
||||
<svelte:head>
|
||||
@ -60,12 +106,12 @@
|
||||
<div class="app">
|
||||
<Header authenticatedUser={$user} />
|
||||
|
||||
<main>
|
||||
<main bind:this={main}>
|
||||
<slot />
|
||||
</main>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
<style lang="scss">
|
||||
.app {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
@ -78,6 +124,6 @@
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
box-sizing: border-box;
|
||||
overflow: hidden;
|
||||
overflow: auto;
|
||||
}
|
||||
</style>
|
||||
|
||||
@ -1,10 +1,9 @@
|
||||
import { from } from 'rxjs'
|
||||
import { map, switchMap, take } from 'rxjs/operators'
|
||||
import { switchMap, take } from 'rxjs/operators'
|
||||
|
||||
import type { LayoutLoad } from './$types'
|
||||
|
||||
import { browser } from '$app/environment'
|
||||
import { isErrorLike } from '$lib/common'
|
||||
import { createPlatformContext } from '$lib/context'
|
||||
import type { CurrentAuthStateResult } from '$lib/graphql/shared'
|
||||
import { getDocumentNode } from '$lib/http-client'
|
||||
@ -39,7 +38,6 @@ export const load: LayoutLoad = () => {
|
||||
settings: from(platformContext)
|
||||
.pipe(
|
||||
switchMap(platformContext => platformContext.settings),
|
||||
map(settingsOrError => (isErrorLike(settingsOrError.final) ? null : settingsOrError.final)),
|
||||
take(1)
|
||||
)
|
||||
.toPromise(),
|
||||
|
||||
@ -17,6 +17,8 @@
|
||||
<nav class="ml-2">
|
||||
<ul>
|
||||
<HeaderNavLink href="/search" svgIconPath={mdiMagnify}>Code search</HeaderNavLink>
|
||||
<HeaderNavLink href="/notebooks" svgIconPath={mdiBookOutline}>Notebooks</HeaderNavLink>
|
||||
<HeaderNavLink href="/insights" svgIconPath={mdiChartBar}>Insights</HeaderNavLink>
|
||||
</ul>
|
||||
</nav>
|
||||
<div class="user">
|
||||
|
||||
@ -33,6 +33,12 @@ const config = {
|
||||
'@apollo/client': '../../node_modules/@apollo/client/index.js',
|
||||
lodash: './node_modules/lodash-es',
|
||||
},
|
||||
typescript: {
|
||||
config: config => {
|
||||
config.extends = '../../../tsconfig.base.json'
|
||||
config.include = [...(config.include ?? []), '../src/**/*.tsx']
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
@ -9,6 +9,7 @@
|
||||
"skipLibCheck": true,
|
||||
"sourceMap": true,
|
||||
"strict": true,
|
||||
"jsx": "react",
|
||||
},
|
||||
// Path aliases are handled by https://kit.svelte.dev/docs/configuration#alias
|
||||
//
|
||||
|
||||
@ -23,6 +23,12 @@ const config: UserConfig = {
|
||||
},
|
||||
},
|
||||
},
|
||||
optimizeDeps: {
|
||||
exclude: [
|
||||
// Without addings this Vite throws an error
|
||||
'linguist-languages',
|
||||
],
|
||||
},
|
||||
}
|
||||
|
||||
export default config
|
||||
|
||||
@ -26,7 +26,7 @@ export interface GlobalNotebooksAreaProps
|
||||
SettingsCascadeProps,
|
||||
NotebookProps,
|
||||
SearchStreamingProps,
|
||||
SearchContextProps {
|
||||
Pick<SearchContextProps, 'searchContextsEnabled'> {
|
||||
authenticatedUser: AuthenticatedUser | null
|
||||
isSourcegraphDotCom: boolean
|
||||
fetchHighlightedFileLineRanges: (parameters: FetchFileParameters, force?: boolean) => Observable<string[][]>
|
||||
|
||||
@ -1141,15 +1141,16 @@ importers:
|
||||
specifiers:
|
||||
'@playwright/test': 1.25.0
|
||||
'@popperjs/core': ^2.11.6
|
||||
'@remix-run/router': ^1.3.2
|
||||
'@sourcegraph/branded': workspace:*
|
||||
'@sourcegraph/common': workspace:*
|
||||
'@sourcegraph/http-client': workspace:*
|
||||
'@sourcegraph/shared': workspace:*
|
||||
'@sourcegraph/web': workspace:*
|
||||
'@sourcegraph/wildcard': workspace:*
|
||||
'@sveltejs/adapter-auto': 1.0.0-next.91
|
||||
'@sveltejs/adapter-auto': ^2.0.0
|
||||
'@sveltejs/adapter-static': ^2.0.0
|
||||
'@sveltejs/kit': ^1.3.2
|
||||
'@sveltejs/kit': ^1.5.6
|
||||
'@types/cookie': ^0.5.1
|
||||
'@types/prismjs': ^1.26.0
|
||||
eslint-plugin-svelte3: ^4.0.0
|
||||
@ -1157,11 +1158,12 @@ importers:
|
||||
prettier-plugin-svelte: ^2.9.0
|
||||
prismjs: ^1.29.0
|
||||
svelte: ^3.55.1
|
||||
svelte-check: ^3.0.3
|
||||
svelte-check: ^3.0.4
|
||||
tslib: 2.1.0
|
||||
vite: ^4.0.4
|
||||
vite: ^4.1.4
|
||||
dependencies:
|
||||
'@popperjs/core': 2.11.6
|
||||
'@remix-run/router': 1.3.2
|
||||
'@sourcegraph/branded': link:../branded
|
||||
'@sourcegraph/common': link:../common
|
||||
'@sourcegraph/http-client': link:../http-client
|
||||
@ -1172,17 +1174,17 @@ importers:
|
||||
prismjs: 1.29.0
|
||||
devDependencies:
|
||||
'@playwright/test': 1.25.0
|
||||
'@sveltejs/adapter-auto': 1.0.0-next.91_@sveltejs+kit@1.5.6
|
||||
'@sveltejs/adapter-auto': 2.0.0_@sveltejs+kit@1.5.6
|
||||
'@sveltejs/adapter-static': 2.0.1_@sveltejs+kit@1.5.6
|
||||
'@sveltejs/kit': 1.5.6_svelte@3.55.1+vite@4.1.1
|
||||
'@sveltejs/kit': 1.5.6_svelte@3.55.1+vite@4.1.4
|
||||
'@types/cookie': 0.5.1
|
||||
'@types/prismjs': 1.26.0
|
||||
eslint-plugin-svelte3: 4.0.0_dbthnr4b2bdkhyiebwn7su3hnq
|
||||
prettier-plugin-svelte: 2.9.0_jrsxveqmsx2uadbqiuq74wlc4u
|
||||
svelte: 3.55.1
|
||||
svelte-check: 3.0.3_svelte@3.55.1
|
||||
svelte-check: 3.0.4_svelte@3.55.1
|
||||
tslib: 2.1.0
|
||||
vite: 4.1.1
|
||||
vite: 4.1.4
|
||||
|
||||
client/wildcard:
|
||||
specifiers:
|
||||
@ -9913,12 +9915,12 @@ packages:
|
||||
sucrase: 3.29.0
|
||||
dev: true
|
||||
|
||||
/@sveltejs/adapter-auto/1.0.0-next.91_@sveltejs+kit@1.5.6:
|
||||
resolution: {integrity: sha512-U57tQdzTfFINim8tzZSARC9ztWPzwOoHwNOpGdb2o6XrD0mEQwU9DsII7dBblvzg+xCnmd0pw7PDtXz5c5t96w==}
|
||||
/@sveltejs/adapter-auto/2.0.0_@sveltejs+kit@1.5.6:
|
||||
resolution: {integrity: sha512-b+gkHFZgD771kgV3aO4avHFd7y1zhmMYy9i6xOK7m/rwmwaRO8gnF5zBc0Rgca80B2PMU1bKNxyBTHA14OzUAQ==}
|
||||
peerDependencies:
|
||||
'@sveltejs/kit': ^1.0.0-next.587
|
||||
'@sveltejs/kit': ^1.0.0
|
||||
dependencies:
|
||||
'@sveltejs/kit': 1.5.6_svelte@3.55.1+vite@4.1.1
|
||||
'@sveltejs/kit': 1.5.6_svelte@3.55.1+vite@4.1.4
|
||||
import-meta-resolve: 2.2.1
|
||||
dev: true
|
||||
|
||||
@ -9927,10 +9929,10 @@ packages:
|
||||
peerDependencies:
|
||||
'@sveltejs/kit': ^1.5.0
|
||||
dependencies:
|
||||
'@sveltejs/kit': 1.5.6_svelte@3.55.1+vite@4.1.1
|
||||
'@sveltejs/kit': 1.5.6_svelte@3.55.1+vite@4.1.4
|
||||
dev: true
|
||||
|
||||
/@sveltejs/kit/1.5.6_svelte@3.55.1+vite@4.1.1:
|
||||
/@sveltejs/kit/1.5.6_svelte@3.55.1+vite@4.1.4:
|
||||
resolution: {integrity: sha512-CHVeQpbcwSIUekF6WwevmExKXyfHnzM5nlCEUv13lGgfJ0zKA0ny5YwIe40vccrm5w5Caec7jMTt9Ih3McJr5g==}
|
||||
engines: {node: ^16.14 || >=18}
|
||||
hasBin: true
|
||||
@ -9939,7 +9941,7 @@ packages:
|
||||
svelte: ^3.54.0
|
||||
vite: ^4.0.0
|
||||
dependencies:
|
||||
'@sveltejs/vite-plugin-svelte': 2.0.2_svelte@3.55.1+vite@4.1.1
|
||||
'@sveltejs/vite-plugin-svelte': 2.0.2_svelte@3.55.1+vite@4.1.4
|
||||
'@types/cookie': 0.5.1
|
||||
cookie: 0.5.0
|
||||
devalue: 4.3.0
|
||||
@ -9953,12 +9955,12 @@ packages:
|
||||
svelte: 3.55.1
|
||||
tiny-glob: 0.2.9
|
||||
undici: 5.18.0
|
||||
vite: 4.1.1
|
||||
vite: 4.1.4
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
dev: true
|
||||
|
||||
/@sveltejs/vite-plugin-svelte/2.0.2_svelte@3.55.1+vite@4.1.1:
|
||||
/@sveltejs/vite-plugin-svelte/2.0.2_svelte@3.55.1+vite@4.1.4:
|
||||
resolution: {integrity: sha512-xCEan0/NNpQuL0l5aS42FjwQ6wwskdxC3pW1OeFtEKNZwRg7Evro9lac9HesGP6TdFsTv2xMes5ASQVKbCacxg==}
|
||||
engines: {node: ^14.18.0 || >= 16}
|
||||
peerDependencies:
|
||||
@ -9971,8 +9973,8 @@ packages:
|
||||
magic-string: 0.27.0
|
||||
svelte: 3.55.1
|
||||
svelte-hmr: 0.15.1_svelte@3.55.1
|
||||
vite: 4.1.1
|
||||
vitefu: 0.2.4_vite@4.1.1
|
||||
vite: 4.1.4
|
||||
vitefu: 0.2.4_vite@4.1.4
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
dev: true
|
||||
@ -31082,8 +31084,8 @@ packages:
|
||||
react: 18.1.0
|
||||
dev: false
|
||||
|
||||
/svelte-check/3.0.3_svelte@3.55.1:
|
||||
resolution: {integrity: sha512-ByBFXo3bfHRGIsYEasHkdMhLkNleVfszX/Ns1oip58tPJlKdo5Ssr8kgVIuo5oq00hss8AIcdesuy0Xt0BcTvg==}
|
||||
/svelte-check/3.0.4_svelte@3.55.1:
|
||||
resolution: {integrity: sha512-feIyBAA5cSIxq4vq6mwGvGQTHy/wBVQbs5b+/VvE21WN8X7nonAuSqwvZv0UDBowzRka3Rh4gmLPH8rPePz3/w==}
|
||||
hasBin: true
|
||||
peerDependencies:
|
||||
svelte: ^3.55.0
|
||||
@ -33023,8 +33025,8 @@ packages:
|
||||
replace-ext: 1.0.0
|
||||
dev: true
|
||||
|
||||
/vite/4.1.1:
|
||||
resolution: {integrity: sha512-LM9WWea8vsxhr782r9ntg+bhSFS06FJgCvvB0+8hf8UWtvaiDagKYWXndjfX6kGl74keHJUcpzrQliDXZlF5yg==}
|
||||
/vite/4.1.4:
|
||||
resolution: {integrity: sha512-3knk/HsbSTKEin43zHu7jTwYWv81f8kgAL99G5NWBcA1LKvtvcVAC4JjBH1arBunO9kQka+1oGbrMKOjk4ZrBg==}
|
||||
engines: {node: ^14.18.0 || >=16.0.0}
|
||||
hasBin: true
|
||||
peerDependencies:
|
||||
@ -33056,7 +33058,7 @@ packages:
|
||||
fsevents: 2.3.2
|
||||
dev: true
|
||||
|
||||
/vitefu/0.2.4_vite@4.1.1:
|
||||
/vitefu/0.2.4_vite@4.1.4:
|
||||
resolution: {integrity: sha512-fanAXjSaf9xXtOOeno8wZXIhgia+CZury481LsDaV++lSvcU2R9Ch2bPh3PYFyoHW+w9LqAeYRISVQjUIew14g==}
|
||||
peerDependencies:
|
||||
vite: ^3.0.0 || ^4.0.0
|
||||
@ -33064,7 +33066,7 @@ packages:
|
||||
vite:
|
||||
optional: true
|
||||
dependencies:
|
||||
vite: 4.1.1
|
||||
vite: 4.1.4
|
||||
dev: true
|
||||
|
||||
/vlq/1.0.1:
|
||||
|
||||
Loading…
Reference in New Issue
Block a user