mirror of
https://github.com/sourcegraph/sourcegraph.git
synced 2026-02-06 15:12:02 +00:00
Add after install page to browser extension (#14407)
This commit is contained in:
parent
2e2becfd20
commit
cf65dec202
@ -21,10 +21,11 @@ testdata
|
||||
doc/
|
||||
**/.cache
|
||||
**/__snapshots__
|
||||
**/*.html
|
||||
**/__fixtures__
|
||||
client/contrib/GH2SG.bookmarklet.js
|
||||
docker-images/grafana/jsonnet/*.json
|
||||
docker-images/grafana/grafonnet-lib/
|
||||
docker-images/grafana/config/provisioning/dashboards/sourcegraph/
|
||||
storybook-static/
|
||||
browser/code-intel-extensions/
|
||||
!/.storybook/**
|
||||
|
||||
7
client/branded/src/global-styles/background.scss
Normal file
7
client/branded/src/global-styles/background.scss
Normal file
@ -0,0 +1,7 @@
|
||||
// Extend Bootstrap's bg-* utilities with classes for our grey/blueish theme-aware background colors.
|
||||
|
||||
@for $number from 1 through 3 {
|
||||
.bg-#{$number} {
|
||||
background: var(--color-bg-#{$number});
|
||||
}
|
||||
}
|
||||
@ -118,6 +118,8 @@ $body-bg-color-dark: #04070e;
|
||||
--body-color: #{$body-color-light};
|
||||
--body-bg: #{$body-bg-color-light};
|
||||
--color-bg-1: #{$color-light-bg-1};
|
||||
--color-bg-2: #{$color-light-bg-2};
|
||||
--color-bg-3: #{$color-light-bg-3};
|
||||
--text-muted: #{$color-light-text-2};
|
||||
--link-color: #{$gray-13};
|
||||
--link-active-color: #{$gray-18};
|
||||
@ -132,6 +134,8 @@ $body-bg-color-dark: #04070e;
|
||||
--body-color: #{$body-color-dark};
|
||||
--body-bg: #{$body-bg-color-dark};
|
||||
--color-bg-1: #{$color-bg-1};
|
||||
--color-bg-2: #{$color-bg-2};
|
||||
--color-bg-3: #{$color-bg-3};
|
||||
--text-muted: #{$color-text-1};
|
||||
--link-color: #{$gray-07};
|
||||
--link-active-color: #{$gray-02};
|
||||
|
||||
@ -133,6 +133,7 @@ $code-bg: var(--body-bg);
|
||||
@import 'bootstrap/scss/tooltip';
|
||||
@import 'bootstrap/scss/transitions';
|
||||
@import '../../../shared/src/global-styles/icons';
|
||||
@import './background';
|
||||
@import './badge';
|
||||
@import './card';
|
||||
@import './dropdown';
|
||||
|
||||
@ -50,12 +50,6 @@
|
||||
}
|
||||
}
|
||||
|
||||
strong {
|
||||
.theme-dark & {
|
||||
color: $gray-11;
|
||||
}
|
||||
}
|
||||
|
||||
.search-keyword {
|
||||
color: #329af0;
|
||||
|
||||
|
||||
@ -29,8 +29,9 @@ const config: webpack.Configuration = {
|
||||
backgroundEntry,
|
||||
'../../src/browser-extension/scripts/backgroundPage.main.ts'
|
||||
),
|
||||
options: buildEntry(extensionEntry, optionsEntry, '../../src/browser-extension/scripts/optionsPage.main.tsx'),
|
||||
inject: buildEntry(extensionEntry, contentEntry, '../../src/browser-extension/scripts/contentPage.main.ts'),
|
||||
options: buildEntry(extensionEntry, optionsEntry, '../../src/browser-extension/scripts/optionsPage.main.tsx'),
|
||||
'after-install': path.resolve(__dirname, '../../src/browser-extension/scripts/afterInstallPage.main.tsx'),
|
||||
|
||||
// Common native integration entry point (Gitlab, Bitbucket)
|
||||
integration: buildEntry(pageEntry, '../../src/native-integration/integration.main.ts'),
|
||||
@ -39,7 +40,7 @@ const config: webpack.Configuration = {
|
||||
|
||||
// Styles
|
||||
style: path.join(__dirname, '../../src/app.scss'),
|
||||
'options-style': path.join(__dirname, '../../src/options.scss'),
|
||||
'branded-style': path.join(__dirname, '../../src/branded.scss'),
|
||||
},
|
||||
output: {
|
||||
path: path.join(__dirname, '../../build/dist/js'),
|
||||
|
||||
@ -43,6 +43,9 @@
|
||||
},
|
||||
{
|
||||
"path": "./build/dist/css/style.bundle.css"
|
||||
},
|
||||
{
|
||||
"path": "./build/dist/css/branded-style.bundle.css"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@ -62,16 +62,10 @@ export function copyAssets(): void {
|
||||
|
||||
function copyExtensionAssets(toDirectory: string): void {
|
||||
shelljs.mkdir('-p', `${toDirectory}/js`, `${toDirectory}/css`, `${toDirectory}/img`)
|
||||
shelljs.cp('build/dist/js/background.bundle.js', `${toDirectory}/js`)
|
||||
shelljs.cp('build/dist/js/inject.bundle.js', `${toDirectory}/js`)
|
||||
shelljs.cp('build/dist/js/options.bundle.js', `${toDirectory}/js`)
|
||||
shelljs.cp('build/dist/js/extensionHostWorker.bundle.js', `${toDirectory}/js`)
|
||||
shelljs.cp('build/dist/css/style.bundle.css', `${toDirectory}/css`)
|
||||
shelljs.cp('build/dist/css/options-style.bundle.css', `${toDirectory}/css`)
|
||||
shelljs.cp('build/dist/css/options-style.bundle.css', `${toDirectory}/css`)
|
||||
shelljs.cp('build/dist/js/*.bundle.js', `${toDirectory}/js`)
|
||||
shelljs.cp('build/dist/css/*.bundle.css', `${toDirectory}/css`)
|
||||
shelljs.cp('-R', 'build/dist/img/*', `${toDirectory}/img`)
|
||||
shelljs.cp('build/dist/background.html', toDirectory)
|
||||
shelljs.cp('build/dist/options.html', toDirectory)
|
||||
shelljs.cp('build/dist/*.html', toDirectory)
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
7
client/browser/src/branded.scss
Normal file
7
client/browser/src/branded.scss
Normal file
@ -0,0 +1,7 @@
|
||||
// CSS entry point for branded contexts (options menu and after-install page)
|
||||
// Bootstrap etc can be used freely here (in opposite to the content page styles in app.scss)
|
||||
|
||||
@import '../../branded/src/global-styles/index.scss';
|
||||
@import '../../branded/src/components/Toggle';
|
||||
@import './browser-extension/options-page/OptionsContainer';
|
||||
@import './browser-extension/after-install-page/AfterInstallPageContent';
|
||||
25
client/browser/src/browser-extension/ThemeWrapper.tsx
Normal file
25
client/browser/src/browser-extension/ThemeWrapper.tsx
Normal file
@ -0,0 +1,25 @@
|
||||
import React, { useEffect, useMemo, useState } from 'react'
|
||||
import { ThemeProps } from '../../../shared/src/theme'
|
||||
|
||||
/**
|
||||
* Wrapper for the browser extension that listens to changes of the OS theme.
|
||||
*/
|
||||
export function ThemeWrapper({
|
||||
children: Children,
|
||||
}: {
|
||||
children: (props: ThemeProps) => JSX.Element | null
|
||||
}): JSX.Element | null {
|
||||
const darkThemeMediaList = useMemo(() => window.matchMedia('(prefers-color-scheme: dark)'), [])
|
||||
const [isLightTheme, setIsLightTheme] = useState(!darkThemeMediaList.matches)
|
||||
|
||||
useEffect(() => {
|
||||
darkThemeMediaList.addListener(event => setIsLightTheme(!event.matches))
|
||||
}, [darkThemeMediaList])
|
||||
|
||||
useEffect(() => {
|
||||
document.body.classList.toggle('theme-light', isLightTheme)
|
||||
document.body.classList.toggle('theme-dark', !isLightTheme)
|
||||
}, [isLightTheme])
|
||||
|
||||
return <Children isLightTheme={isLightTheme} />
|
||||
}
|
||||
@ -0,0 +1,15 @@
|
||||
.after-install-page-content {
|
||||
&__sourcegraph-logo {
|
||||
width: 1.5rem;
|
||||
position: absolute;
|
||||
top: 0.5rem;
|
||||
left: 0.5rem;
|
||||
}
|
||||
&__code-host-logo {
|
||||
height: 2rem !important;
|
||||
width: 2rem !important;
|
||||
}
|
||||
&__code-host-titles {
|
||||
line-height: 2rem;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,9 @@
|
||||
import React from 'react'
|
||||
import { storiesOf } from '@storybook/react'
|
||||
import { AfterInstallPageContent } from './AfterInstallPageContent'
|
||||
import { BrandedStory } from '../../../../branded/src/components/BrandedStory'
|
||||
import brandedStyles from '../../branded.scss'
|
||||
|
||||
storiesOf('browser/AfterInstallPage', module).add('Default', () => (
|
||||
<BrandedStory styles={brandedStyles}>{() => <AfterInstallPageContent />}</BrandedStory>
|
||||
))
|
||||
@ -0,0 +1,170 @@
|
||||
import React from 'react'
|
||||
import LockIcon from 'mdi-react/LockIcon'
|
||||
import GithubIcon from 'mdi-react/GithubIcon'
|
||||
import GitlabIcon from 'mdi-react/GitlabIcon'
|
||||
import BitbucketIcon from 'mdi-react/BitbucketIcon'
|
||||
import CheckIcon from 'mdi-react/CheckIcon'
|
||||
import BookOpenPageVariantIcon from 'mdi-react/BookOpenPageVariantIcon'
|
||||
import { PhabricatorIcon } from '../../../../shared/src/components/icons'
|
||||
import { SourcegraphIcon } from '../../shared/components/SourcegraphIcon'
|
||||
|
||||
export const AfterInstallPageContent: React.FunctionComponent = () => (
|
||||
<div className="web-content after-install-page-content">
|
||||
<SourcegraphIcon className="after-install-page-content__sourcegraph-logo" />
|
||||
|
||||
<div className="container">
|
||||
<h1 className="mt-5">🎉 You’ve just installed the Sourcegraph browser extension!</h1>
|
||||
<p className="lead mb-0">We’ve gathered the most important information that will get your started:</p>
|
||||
</div>
|
||||
|
||||
<section className="border-bottom py-5">
|
||||
<div className="container">
|
||||
<h2 className="mb-4">How do I use the extension?</h2>
|
||||
<div className="row">
|
||||
<div className="col-sm-6">
|
||||
<h3>Code intelligence on your code host</h3>
|
||||
<p className="m-0">
|
||||
Sourcegraph browser extension adds code intelligence to files and diffs on GitHub, GitHub
|
||||
Enterprise, GitLab, Phabricator, and Bitbucket Server.
|
||||
</p>
|
||||
{/* Video placeholder */}
|
||||
</div>
|
||||
<div className="col-sm-6">
|
||||
<h3>Search shortcut in the URL location bar</h3>
|
||||
<p className="m-0">
|
||||
Type <code>src</code>
|
||||
<kbd>space</kbd> in the address bar of your browser to search for queries on Sourcegraph.
|
||||
</p>
|
||||
{/* Video placeholder */}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section className="border-bottom py-5">
|
||||
<div className="container">
|
||||
<h2 className="mb-4">Make it work on your codehost</h2>
|
||||
<div className="row">
|
||||
<div className="col-sm-6">
|
||||
<div className="bg-2 rounded p-3 mb-3">
|
||||
<h3 className="mb-3 after-install-page-content__code-host-titles">
|
||||
<GithubIcon className="icon-inline after-install-page-content__code-host-logo" />{' '}
|
||||
github.com
|
||||
</h3>
|
||||
<p className="m-0">
|
||||
<CheckIcon className="icon-inline" /> No action required. Your extension works here by
|
||||
default.
|
||||
</p>
|
||||
</div>
|
||||
<div className="bg-2 rounded p-3">
|
||||
<h3 className="d-flex flex-wrap after-install-page-content__code-host-titles">
|
||||
<div className="mr-5 mb-3">
|
||||
<GithubIcon className="icon-inline after-install-page-content__code-host-logo" />{' '}
|
||||
GitHub Enterprise
|
||||
</div>
|
||||
<div className="mr-5 mb-3">
|
||||
<GitlabIcon className="icon-inline after-install-page-content__code-host-logo" />{' '}
|
||||
GitLab
|
||||
</div>
|
||||
<div className="mr-5 mb-3">
|
||||
<BitbucketIcon className="icon-inline after-install-page-content__code-host-logo" />{' '}
|
||||
Bitbucket Server
|
||||
</div>
|
||||
<div className="mr-5 mb-3">
|
||||
<PhabricatorIcon className="icon-inline after-install-page-content__code-host-logo" />{' '}
|
||||
Phabricator
|
||||
</div>
|
||||
</h3>
|
||||
<p>Your extension needs explicit permissions to your code host:</p>
|
||||
<ol className="m-0">
|
||||
<li>Navigate to any page on your code host.</li>
|
||||
<li>
|
||||
Click the{' '}
|
||||
<q>
|
||||
<strong>Grant permissions</strong>
|
||||
</q>{' '}
|
||||
button.
|
||||
</li>
|
||||
<li>
|
||||
Click{' '}
|
||||
<q>
|
||||
<strong>Allow</strong>
|
||||
</q>{' '}
|
||||
in the permissions request popup.
|
||||
</li>
|
||||
</ol>
|
||||
</div>
|
||||
</div>
|
||||
<div className="col-sm-6">{/* Video placeholder */}</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section className="border-bottom py-5">
|
||||
<div className="container">
|
||||
<h2 className="mb-4">Make it work for private code</h2>
|
||||
<div className="row">
|
||||
<div className="col-sm-6">
|
||||
<p>By default, the browser extension works only for public code.</p>
|
||||
<div className="d-flex w-100 align-items-center">
|
||||
<div className="bg-3 rounded-circle p-2">
|
||||
<LockIcon className="icon-inline" />
|
||||
</div>
|
||||
<p className="flex-grow-1 m-0 ml-3">
|
||||
To use the browser extension with your private repositories, you need to set up a{' '}
|
||||
<strong>private Sourcegraph instance</strong> and connect the extension to it.
|
||||
</p>
|
||||
</div>
|
||||
<div className="bg-2 rounded p-3 mt-4">
|
||||
<p>Follow these instructions:</p>
|
||||
<ol className="m-0">
|
||||
<li>
|
||||
<strong>Install Sourcegraph</strong> (
|
||||
<a href="https://docs.sourcegraph.com/admin/install" target="_blank" rel="noopener">
|
||||
visit our docs for instructions
|
||||
</a>
|
||||
). Skip this step if you already have a private Sourcegraph instance.
|
||||
</li>
|
||||
<li>
|
||||
Click the Sourcegraph extension icon in the browser toolbar to{' '}
|
||||
<a href="./options.html" rel="noopener" target="_blank">
|
||||
open the settings page
|
||||
</a>
|
||||
.
|
||||
</li>
|
||||
<li>
|
||||
Enter the <strong>URL</strong> (including the protocol) of your Sourcegraph instance
|
||||
(such as <q>https://sourcegraph.example.com</q>).
|
||||
</li>
|
||||
<li>Make sure a green checkmark appears in the input field.</li>
|
||||
</ol>
|
||||
</div>
|
||||
</div>
|
||||
<div className="col-sm-6">{/* Video placeholder */}</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section className="py-5">
|
||||
<div className="container">
|
||||
<h2 className="mb-4">Additional resources</h2>
|
||||
<div className="d-flex w-100 align-items-center">
|
||||
<div className="bg-3 rounded-circle p-2">
|
||||
<BookOpenPageVariantIcon className="icon-inline" />
|
||||
</div>
|
||||
<p className="flex-grow-1 m-0 ml-3">
|
||||
Read the{' '}
|
||||
<a
|
||||
href="https://docs.sourcegraph.com/integration/browser_extension"
|
||||
rel="noopener"
|
||||
target="_blank"
|
||||
>
|
||||
Sourcegraph Documentation
|
||||
</a>{' '}
|
||||
to learn more about how we respect your privacy, troubleshooting and extension features.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
)
|
||||
@ -29,7 +29,7 @@
|
||||
},
|
||||
"optional_permissions": ["tabs", "http://*/*", "https://*/*"],
|
||||
"content_security_policy": "script-src 'self' blob:; object-src 'self'",
|
||||
"web_accessible_resources": ["img/*", "css/style.bundle.css", "css/options-style.bundle.css"],
|
||||
"web_accessible_resources": ["img/*", "css/style.bundle.css", "css/branded-style.bundle.css"],
|
||||
"omnibox": {
|
||||
"keyword": "src"
|
||||
},
|
||||
|
||||
@ -2,11 +2,11 @@ import * as React from 'react'
|
||||
import { action } from '@storybook/addon-actions'
|
||||
import { storiesOf } from '@storybook/react'
|
||||
import { OptionsMenu } from './OptionsMenu'
|
||||
import optionsStyles from '../../options.scss'
|
||||
import brandedStyles from '../../branded.scss'
|
||||
import { BrandedStory } from '../../../../branded/src/components/BrandedStory'
|
||||
|
||||
storiesOf('browser/Options/OptionsMenu', module)
|
||||
.addDecorator(story => <BrandedStory styles={optionsStyles}>{() => story()}</BrandedStory>)
|
||||
.addDecorator(story => <BrandedStory styles={brandedStyles}>{() => story()}</BrandedStory>)
|
||||
.add('Default', () => (
|
||||
<OptionsMenu
|
||||
version="0.0.0"
|
||||
|
||||
@ -3,7 +3,7 @@ import { action } from '@storybook/addon-actions'
|
||||
import { storiesOf } from '@storybook/react'
|
||||
import { ConnectionErrors, ServerUrlForm, ServerUrlFormProps } from './ServerUrlForm'
|
||||
import { BrandedStory } from '../../../../branded/src/components/BrandedStory'
|
||||
import optionsStyles from '../../options.scss'
|
||||
import brandedStyles from '../../branded.scss'
|
||||
|
||||
class Container extends React.Component<{}, { value: string; status: ServerUrlFormProps['status'] }> {
|
||||
public state = { value: 'https://sourcegraph.com', status: 'connected' as ServerUrlFormProps['status'] }
|
||||
@ -34,7 +34,7 @@ class Container extends React.Component<{}, { value: string; status: ServerUrlFo
|
||||
}
|
||||
|
||||
storiesOf('browser/Options/ServerUrlForm', module)
|
||||
.addDecorator(story => <BrandedStory styles={optionsStyles}>{() => story()}</BrandedStory>)
|
||||
.addDecorator(story => <BrandedStory styles={brandedStyles}>{() => story()}</BrandedStory>)
|
||||
.add('Interactive', () => <Container />)
|
||||
.add('Error Status', () => (
|
||||
<div style={{ maxWidth: 400, padding: '1rem' }}>
|
||||
|
||||
@ -0,0 +1,13 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta http-equiv="Content-type" content="text/html; charset=utf-8" />
|
||||
<title>Sourcegraph browser extension</title>
|
||||
<link href="./css/branded-style.bundle.css" rel="stylesheet" type="text/css" />
|
||||
</head>
|
||||
<body>
|
||||
<div id="root"></div>
|
||||
<noscript>You must enable JavaScript for the Sourcegraph browser extension to work.</noscript>
|
||||
<script type="text/javascript" src="./js/after-install.bundle.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
@ -3,7 +3,7 @@
|
||||
<head>
|
||||
<meta http-equiv="Content-type" content="text/html; charset=utf-8" />
|
||||
<title>Sourcegraph extension</title>
|
||||
<link href="./css/options-style.bundle.css" rel="stylesheet" type="text/css" />
|
||||
<link href="./css/branded-style.bundle.css" rel="stylesheet" type="text/css" />
|
||||
</head>
|
||||
<body>
|
||||
<script type="text/javascript" src="./js/options.bundle.js"></script>
|
||||
|
||||
@ -0,0 +1,11 @@
|
||||
// We want to polyfill first.
|
||||
import '../../shared/polyfills'
|
||||
|
||||
import React from 'react'
|
||||
import { render } from 'react-dom'
|
||||
import { AfterInstallPageContent } from '../after-install-page/AfterInstallPageContent'
|
||||
import { ThemeWrapper } from '../ThemeWrapper'
|
||||
|
||||
const AfterInstallPage: React.FunctionComponent = () => <ThemeWrapper>{() => <AfterInstallPageContent />}</ThemeWrapper>
|
||||
|
||||
render(<AfterInstallPage />, document.querySelector('#root'))
|
||||
@ -76,6 +76,15 @@ initializeOmniboxInterface(requestGraphQL)
|
||||
async function main(): Promise<void> {
|
||||
const subscriptions = new Subscription()
|
||||
|
||||
// Open installation page after the extension was installed
|
||||
browser.runtime.onInstalled.addListener(event => {
|
||||
if (event.reason === 'install') {
|
||||
browser.tabs.create({ url: browser.extension.getURL('after_install.html') }).catch(error => {
|
||||
console.error('Error opening after-install page:', error)
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
// Mirror the managed sourcegraphURL to sync storage
|
||||
subscriptions.add(
|
||||
observeStorageKey('managed', 'sourcegraphURL')
|
||||
|
||||
@ -1,17 +0,0 @@
|
||||
// Options page CSS entry point
|
||||
|
||||
@import '../../branded/src/global-styles/index.scss';
|
||||
@import '../../branded/src/components/Toggle';
|
||||
@import './browser-extension/options-page/OptionsContainer';
|
||||
|
||||
:root {
|
||||
// Use the same root size as the webapp to maintain consistency
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
body {
|
||||
// Use a smaller base size than the default 16px,
|
||||
// because an options menu should feel more like a
|
||||
// natural extension of the browser's UI than a webpage
|
||||
font-size: 14px;
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user