first commit
This commit is contained in:
129
services/web/.storybook/main.ts
Normal file
129
services/web/.storybook/main.ts
Normal file
@@ -0,0 +1,129 @@
|
||||
import type { StorybookConfig } from '@storybook/react-webpack5'
|
||||
import path from 'node:path'
|
||||
import MiniCssExtractPlugin from 'mini-css-extract-plugin'
|
||||
|
||||
const rootDir = path.resolve(__dirname, '..')
|
||||
|
||||
// NOTE: must be set before webpack config is imported
|
||||
process.env.OVERLEAF_CONFIG = path.join(rootDir, 'config/settings.webpack.js')
|
||||
|
||||
function getAbsolutePath(value: string): any {
|
||||
return path.dirname(require.resolve(path.join(value, 'package.json')))
|
||||
}
|
||||
|
||||
const config: StorybookConfig = {
|
||||
core: {
|
||||
disableTelemetry: true,
|
||||
},
|
||||
staticDirs: [path.join(rootDir, 'public')],
|
||||
stories: [
|
||||
path.join(rootDir, 'frontend/stories/**/*.stories.{js,jsx,ts,tsx}'),
|
||||
path.join(rootDir, 'modules/**/stories/**/*.stories.{js,jsx,ts,tsx}'),
|
||||
],
|
||||
addons: [
|
||||
getAbsolutePath('@storybook/addon-links'),
|
||||
getAbsolutePath('@storybook/addon-essentials'),
|
||||
getAbsolutePath('@storybook/addon-interactions'),
|
||||
getAbsolutePath('@storybook/addon-a11y'),
|
||||
getAbsolutePath('@storybook/addon-webpack5-compiler-babel'),
|
||||
{
|
||||
name: getAbsolutePath('@storybook/addon-styling-webpack'),
|
||||
options: {
|
||||
rules: [
|
||||
{
|
||||
test: /\.css$/,
|
||||
use: [
|
||||
{ loader: MiniCssExtractPlugin.loader },
|
||||
{ loader: 'css-loader' },
|
||||
],
|
||||
},
|
||||
{
|
||||
test: /\.less$/,
|
||||
use: [
|
||||
{ loader: MiniCssExtractPlugin.loader },
|
||||
{ loader: 'css-loader' },
|
||||
{ loader: 'less-loader' },
|
||||
],
|
||||
},
|
||||
{
|
||||
// Pass Sass files through sass-loader/css-loader/mini-css-extract-
|
||||
// plugin (note: run in reverse order)
|
||||
test: /\.s[ac]ss$/,
|
||||
use: [
|
||||
// Allows the CSS to be extracted to a separate .css file
|
||||
{ loader: MiniCssExtractPlugin.loader },
|
||||
// Resolves any CSS dependencies (e.g. url())
|
||||
{ loader: 'css-loader' },
|
||||
// Resolve relative paths sensibly in SASS
|
||||
{ loader: 'resolve-url-loader' },
|
||||
{
|
||||
// Runs autoprefixer on CSS via postcss
|
||||
loader: 'postcss-loader',
|
||||
options: {
|
||||
postcssOptions: {
|
||||
plugins: ['autoprefixer'],
|
||||
},
|
||||
},
|
||||
},
|
||||
// Compiles Sass to CSS
|
||||
{
|
||||
loader: 'sass-loader',
|
||||
options: { sourceMap: true }, // sourceMap: true is required for resolve-url-loader
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
plugins: [new MiniCssExtractPlugin()],
|
||||
},
|
||||
},
|
||||
],
|
||||
framework: {
|
||||
name: getAbsolutePath('@storybook/react-webpack5'),
|
||||
options: {},
|
||||
},
|
||||
docs: {
|
||||
autodocs: 'tag',
|
||||
},
|
||||
babel: (options: Record<string, any>) => {
|
||||
return {
|
||||
...options,
|
||||
plugins: [
|
||||
// ensure that TSX files are transformed before other plugins run
|
||||
['@babel/plugin-transform-typescript', { isTSX: true }],
|
||||
...(options.plugins ?? []),
|
||||
],
|
||||
}
|
||||
},
|
||||
webpackFinal: storybookConfig => {
|
||||
return {
|
||||
...storybookConfig,
|
||||
resolve: {
|
||||
...storybookConfig.resolve,
|
||||
fallback: {
|
||||
...storybookConfig.resolve?.fallback,
|
||||
fs: false,
|
||||
os: false,
|
||||
module: false,
|
||||
tty: require.resolve('tty-browserify'),
|
||||
},
|
||||
extensions: ['.js', '.jsx', '.mjs', '.ts', '.tsx', '.json'],
|
||||
alias: {
|
||||
...storybookConfig.resolve?.alias,
|
||||
// custom prefixes for import paths
|
||||
'@': path.join(rootDir, 'frontend/js/'),
|
||||
},
|
||||
},
|
||||
module: {
|
||||
...storybookConfig.module,
|
||||
rules: (storybookConfig.module?.rules ?? []).concat({
|
||||
test: /\.wasm$/,
|
||||
type: 'asset/resource',
|
||||
generator: {
|
||||
filename: 'js/[name]-[contenthash][ext]',
|
||||
},
|
||||
}),
|
||||
},
|
||||
}
|
||||
},
|
||||
}
|
||||
export default config
|
3
services/web/.storybook/manager.css
Normal file
3
services/web/.storybook/manager.css
Normal file
@@ -0,0 +1,3 @@
|
||||
.sidebar-container a[title='Overleaf'] {
|
||||
max-width: 100px;
|
||||
}
|
15
services/web/.storybook/manager.ts
Normal file
15
services/web/.storybook/manager.ts
Normal file
@@ -0,0 +1,15 @@
|
||||
import { addons } from '@storybook/manager-api'
|
||||
import { create } from '@storybook/theming/create'
|
||||
|
||||
import './manager.css'
|
||||
|
||||
import brandImage from '../public/img/ol-brand/overleaf.svg'
|
||||
|
||||
const theme = create({
|
||||
base: 'light',
|
||||
brandTitle: 'Overleaf',
|
||||
brandUrl: 'https://www.overleaf.com',
|
||||
brandImage,
|
||||
})
|
||||
|
||||
addons.setConfig({ theme })
|
173
services/web/.storybook/preview.tsx
Normal file
173
services/web/.storybook/preview.tsx
Normal file
@@ -0,0 +1,173 @@
|
||||
import type { Preview } from '@storybook/react'
|
||||
|
||||
// Storybook does not (currently) support async loading of "stories". Therefore
|
||||
// the strategy in frontend/js/i18n.ts does not work (because we cannot wait on
|
||||
// the promise to resolve).
|
||||
// Therefore we have to use the synchronous method for configuring
|
||||
// react-i18next. Because this, we can only hard-code a single language.
|
||||
import i18n from 'i18next'
|
||||
import { initReactI18next } from 'react-i18next'
|
||||
// @ts-ignore
|
||||
import en from '../../../services/web/locales/en.json'
|
||||
|
||||
function resetMeta() {
|
||||
window.metaAttributesCache = new Map()
|
||||
window.metaAttributesCache.set('ol-i18n', { currentLangCode: 'en' })
|
||||
window.metaAttributesCache.set('ol-ExposedSettings', {
|
||||
adminEmail: 'placeholder@example.com',
|
||||
appName: 'Overleaf',
|
||||
cookieDomain: '.overleaf.stories',
|
||||
dropboxAppName: 'Overleaf-Stories',
|
||||
emailConfirmationDisabled: false,
|
||||
enableSubscriptions: true,
|
||||
hasAffiliationsFeature: false,
|
||||
hasLinkUrlFeature: true,
|
||||
hasLinkedProjectFileFeature: true,
|
||||
hasLinkedProjectOutputFileFeature: true,
|
||||
hasSamlFeature: true,
|
||||
ieeeBrandId: 15,
|
||||
isOverleaf: true,
|
||||
labsEnabled: true,
|
||||
maxEntitiesPerProject: 10,
|
||||
maxUploadSize: 5 * 1024 * 1024,
|
||||
recaptchaDisabled: {
|
||||
invite: true,
|
||||
login: true,
|
||||
passwordReset: true,
|
||||
register: true,
|
||||
addEmail: true,
|
||||
},
|
||||
sentryAllowedOriginRegex: '',
|
||||
siteUrl: 'http://localhost',
|
||||
templateLinks: [],
|
||||
textExtensions: [
|
||||
'tex',
|
||||
'latex',
|
||||
'sty',
|
||||
'cls',
|
||||
'bst',
|
||||
'bib',
|
||||
'bibtex',
|
||||
'txt',
|
||||
'tikz',
|
||||
'mtx',
|
||||
'rtex',
|
||||
'md',
|
||||
'asy',
|
||||
'lbx',
|
||||
'bbx',
|
||||
'cbx',
|
||||
'm',
|
||||
'lco',
|
||||
'dtx',
|
||||
'ins',
|
||||
'ist',
|
||||
'def',
|
||||
'clo',
|
||||
'ldf',
|
||||
'rmd',
|
||||
'lua',
|
||||
'gv',
|
||||
'mf',
|
||||
'lhs',
|
||||
'mk',
|
||||
'xmpdata',
|
||||
'cfg',
|
||||
'rnw',
|
||||
'ltx',
|
||||
'inc',
|
||||
],
|
||||
editableFilenames: ['latexmkrc', '.latexmkrc', 'makefile', 'gnumakefile'],
|
||||
validRootDocExtensions: ['tex', 'Rtex', 'ltx', 'Rnw'],
|
||||
fileIgnorePattern:
|
||||
'**/{{__MACOSX,.git,.texpadtmp,.R}{,/**},.!(latexmkrc),*.{dvi,aux,log,toc,out,pdfsync,synctex,synctex(busy),fdb_latexmk,fls,nlo,ind,glo,gls,glg,bbl,blg,doc,docx,gz,swp}}',
|
||||
projectUploadTimeout: 12000,
|
||||
})
|
||||
}
|
||||
|
||||
i18n.use(initReactI18next).init({
|
||||
lng: 'en',
|
||||
|
||||
// still using the v3 plural suffixes
|
||||
compatibilityJSON: 'v3',
|
||||
|
||||
resources: {
|
||||
en: { translation: en },
|
||||
},
|
||||
|
||||
react: {
|
||||
useSuspense: false,
|
||||
transSupportBasicHtmlNodes: false,
|
||||
},
|
||||
|
||||
interpolation: {
|
||||
prefix: '__',
|
||||
suffix: '__',
|
||||
unescapeSuffix: 'HTML',
|
||||
skipOnVariables: true,
|
||||
escapeValue: false,
|
||||
defaultVariables: {
|
||||
appName: 'Overleaf',
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
const preview: Preview = {
|
||||
parameters: {
|
||||
// Automatically mark prop-types like onClick, onToggle, etc as Storybook
|
||||
// "actions", so that they are logged in the Actions pane at the bottom of the
|
||||
// viewer
|
||||
actions: { argTypesRegex: '^on.*' },
|
||||
docs: {
|
||||
// render stories in iframes, to isolate modals
|
||||
inlineStories: false,
|
||||
},
|
||||
},
|
||||
globalTypes: {
|
||||
theme: {
|
||||
name: 'Theme',
|
||||
description: 'Editor theme',
|
||||
defaultValue: 'main-',
|
||||
toolbar: {
|
||||
icon: 'circlehollow',
|
||||
items: [
|
||||
{ value: 'main-', title: 'Default' },
|
||||
{ value: 'main-light-', title: 'Light' },
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
loaders: [
|
||||
async () => {
|
||||
return {
|
||||
mainStyle: await import(
|
||||
// @ts-ignore
|
||||
`!!to-string-loader!css-loader!resolve-url-loader!sass-loader!../../../services/web/frontend/stylesheets/bootstrap-5/main-style.scss`
|
||||
),
|
||||
}
|
||||
},
|
||||
],
|
||||
decorators: [
|
||||
(Story, context) => {
|
||||
const { mainStyle } = context.loaded
|
||||
|
||||
resetMeta()
|
||||
|
||||
return (
|
||||
<div
|
||||
data-theme={
|
||||
context.globals.theme === 'main-light-' ? 'light' : 'default'
|
||||
}
|
||||
>
|
||||
{mainStyle && <style>{mainStyle.default}</style>}
|
||||
<Story {...context} />
|
||||
</div>
|
||||
)
|
||||
},
|
||||
],
|
||||
}
|
||||
|
||||
export default preview
|
||||
|
||||
// Populate meta for top-level access in modules on import
|
||||
resetMeta()
|
34
services/web/.storybook/utils/with-split-tests.tsx
Normal file
34
services/web/.storybook/utils/with-split-tests.tsx
Normal file
@@ -0,0 +1,34 @@
|
||||
import type { Meta } from '@storybook/react'
|
||||
import _ from 'lodash'
|
||||
import { SplitTestContext } from '../../frontend/js/shared/context/split-test-context'
|
||||
|
||||
export const splitTestsArgTypes = {
|
||||
// to be able to use this utility, you need to add the argTypes for each split test in this object
|
||||
// Check the original implementation for an example: https://github.com/overleaf/internal/pull/17809
|
||||
}
|
||||
|
||||
export const withSplitTests = (
|
||||
story: Meta,
|
||||
splitTests: (keyof typeof splitTestsArgTypes)[] = []
|
||||
): Meta => {
|
||||
return {
|
||||
...story,
|
||||
argTypes: { ...story.argTypes, ..._.pick(splitTestsArgTypes, splitTests) },
|
||||
decorators: [
|
||||
(Story, { args }) => {
|
||||
const splitTestVariants = _.pick(args, splitTests)
|
||||
const value = { splitTestVariants, splitTestInfo: {} }
|
||||
return (
|
||||
<SplitTestContext.Provider value={value}>
|
||||
<Story />
|
||||
</SplitTestContext.Provider>
|
||||
)
|
||||
},
|
||||
...(story.decorators
|
||||
? Array.isArray(story.decorators)
|
||||
? story.decorators
|
||||
: [story.decorators]
|
||||
: []),
|
||||
],
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user