first commit

This commit is contained in:
2025-04-24 13:11:28 +08:00
commit ff9c54d5e4
5960 changed files with 834111 additions and 0 deletions

View 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

View File

@@ -0,0 +1,3 @@
.sidebar-container a[title='Overleaf'] {
max-width: 100px;
}

View 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 })

View 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()

View 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]
: []),
],
}
}