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 @@
Simply use the section and subsection commands, as in this example document! With Overleaf, all the formatting and numbering is handled automatically according to the template you've chosen. If you're using the Visual Editor, you can also create new section and subsections via the buttons in the editor toolbar.

View File

@@ -0,0 +1,8 @@
@article{greenwade93,
author = "George D. Greenwade",
title = "The {C}omprehensive {T}ex {A}rchive {N}etwork ({CTAN})",
year = "1993",
journal = "TUGBoat",
volume = "14",
number = "3",
pages = "342--351"}

View File

@@ -0,0 +1 @@
module.exports = {}

Binary file not shown.

Binary file not shown.

View File

@@ -0,0 +1,21 @@
log This is pdfTeX, Version 3.14159265-2.6-1.40.21 (TeX Live 2020) (preloaded format=pdflatex 2020.9.10) 8 FEB 2022 16:27
entering extended mode
\write18 enabled.
%&-line parsing enabled.
**main.tex
(./main.tex
LaTeX2e <2020-02-02> patch level 5
LaTeX Warning: Reference `intorduction' on page 1 undefined on input line 11.
LaTeX Warning: Reference `section1' on page 1 undefined on input line 13.
[1
{/usr/local/texlive/2020/texmf-var/fonts/map/pdftex/updmap/pdftex.map}] (/compi
le/output.aux)
LaTeX Warning: There were undefined references.
)

View File

@@ -0,0 +1,10 @@
Package rerunfilecheck Info: File `output.out' has not changed.
(rerunfilecheck) Checksum: 339DB29951BB30436898BC39909EA4FA;11265.
Package rerunfilecheck Warning: File `output.brf' has changed.
(rerunfilecheck) Rerun to get bibliographical references right.
Package rerunfilecheck Info: Checksums for `output.brf':
(rerunfilecheck) Before: D41D8CD98F00B204E9800998ECF8427E;0
(rerunfilecheck) After: DF3260FAD3828D54C5E4E9337E97F7AF;4841.
)

View File

@@ -0,0 +1 @@
This is BibTeX, Version 4.0

View File

@@ -0,0 +1,19 @@
The LaTeX compiler output
* With a lot of details
Wrapped in an HTML <pre> element with
preformatted text which is to be presented exactly
as written in the HTML file
(whitespace included™)
The text is typically rendered using a non-proportional ("monospace") font.
LaTeX Font Info: External font `cmex10' loaded for size
(Font) <7> on input line 18.
LaTeX Font Info: External font `cmex10' loaded for size
(Font) <5> on input line 18.
! Undefined control sequence.
<recently read> \Zlpha
main.tex, line 23

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

View File

@@ -0,0 +1,12 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width,initial-scale=1.0">
<title>Components App</title>
</head>
<body>
<div data-cy-root></div>
</body>
</html>

View File

@@ -0,0 +1,12 @@
import 'cypress-plugin-tab'
import { resetMeta } from './ct/window' // needs to be before i18n
import localesPromise from '@/i18n'
import './shared/commands'
import './shared/exceptions'
import './ct/commands'
import './ct/codemirror'
import '../../test/frontend/helpers/bootstrap-5'
beforeEach(function () {
cy.wrap(localesPromise).then(resetMeta)
})

View File

@@ -0,0 +1,4 @@
import { EditorView } from '@codemirror/view'
// @ts-ignore (disable EditContext-based editing until stable)
EditorView.EDIT_CONTEXT = false

View File

@@ -0,0 +1,14 @@
import { mount } from 'cypress/react'
// eslint-disable-next-line no-unused-vars,@typescript-eslint/no-namespace
declare global {
// eslint-disable-next-line @typescript-eslint/no-namespace,no-unused-vars
namespace Cypress {
// eslint-disable-next-line no-unused-vars
interface Chainable {
mount: typeof mount
}
}
}
Cypress.Commands.add('mount', mount)

View File

@@ -0,0 +1,16 @@
export function resetMeta() {
window.metaAttributesCache = new Map()
window.metaAttributesCache.set('ol-i18n', { currentLangCode: 'en' })
window.metaAttributesCache.set('ol-ExposedSettings', {
appName: 'Overleaf',
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}}',
hasLinkedProjectFileFeature: true,
hasLinkedProjectOutputFileFeature: true,
hasLinkUrlFeature: true,
})
}
// Populate meta for top-level access in modules on import
resetMeta()

View File

@@ -0,0 +1,243 @@
import { v4 as uuid } from 'uuid'
const outputFiles = () => {
const build = uuid()
return [
{
path: 'output.pdf',
build,
url: `/build/${build}/output.pdf`,
type: 'pdf',
},
{
path: 'output.bbl',
build,
url: `/build/${build}/output.bbl`,
type: 'bbl',
},
{
path: 'output.bib',
build,
url: `/build/${build}/output.bib`,
type: 'bib',
},
{
path: 'example.txt',
build,
url: `/build/${build}/example.txt`,
type: 'txt',
},
{
path: 'output.log',
build,
url: `/build/${build}/output.log`,
type: 'log',
},
{
path: 'output.blg',
build,
url: `/build/${build}/output.blg`,
type: 'blg',
},
]
}
const compileFromCacheResponse = () => {
return {
fromCache: true,
status: 'success',
clsiServerId: 'foo',
compileGroup: 'priority',
pdfDownloadDomain: 'https://clsi.test-overleaf.com',
outputFiles: outputFiles(),
options: {
rootResourcePath: 'main.tex',
imageName: 'texlive-full:2024.1',
compiler: 'pdflatex',
stopOnFirstError: false,
draft: false,
},
}
}
export const interceptCompileFromCacheRequest = ({
times,
promise,
}: {
times: number
promise: Promise<void>
}) => {
return cy.intercept(
{ path: '/project/*/output/cached/output.overleaf.json', times },
async req => {
await promise
req.reply({ body: compileFromCacheResponse() })
}
)
}
export const interceptCompileRequest = ({ times = 1 } = {}) => {
return cy.intercept(
{ method: 'POST', pathname: '/project/*/compile', times },
{
body: {
status: 'success',
clsiServerId: 'foo',
compileGroup: 'priority',
pdfDownloadDomain: 'https://clsi.test-overleaf.com',
outputFiles: outputFiles(),
},
}
)
}
export const interceptCompile = ({
prefix = 'compile',
times = 1,
cached = false,
regular = true,
outputPDFFixture = 'output.pdf',
} = {}) => {
if (cached) {
cy.intercept(
{ path: '/project/*/output/cached/output.overleaf.json', times },
{ body: compileFromCacheResponse() }
).as(`${prefix}-cached`)
} else {
cy.intercept(
{ pathname: '/project/*/output/cached/output.overleaf.json', times },
{ statusCode: 404 }
).as(`${prefix}-cached`)
}
if (regular) {
interceptCompileRequest({ times }).as(`${prefix}`)
} else {
cy.intercept(
{ method: 'POST', pathname: '/project/*/compile', times },
{
body: {
status: 'unavailable',
clsiServerId: 'foo',
compileGroup: 'priority',
pdfDownloadDomain: 'https://clsi.test-overleaf.com',
outputFiles: [],
},
}
).as(`${prefix}`)
}
cy.intercept(
{ pathname: '/build/*/output.pdf', times },
{ fixture: `build/${outputPDFFixture},null` }
).as(`${prefix}-pdf`)
cy.intercept(
{ pathname: '/build/*/output.log', times },
{ fixture: 'build/output.log' }
).as(`${prefix}-log`)
cy.intercept(
{ pathname: '/build/*/output.blg', times },
{ fixture: 'build/output.blg' }
).as(`${prefix}-blg`)
}
export const waitForCompile = ({
prefix = 'compile',
pdf = false,
cached = false,
regular = true,
} = {}) => {
if (cached) {
cy.wait(`@${prefix}-cached`)
}
if (regular) {
cy.wait(`@${prefix}`)
}
return waitForCompileOutput({ prefix, pdf, cached })
}
export const waitForCompileOutput = ({
prefix = 'compile',
pdf = false,
cached = false,
} = {}) => {
cy.wait(`@${prefix}-log`)
.its('request.query.clsiserverid')
.should('eq', cached ? 'cache' : 'foo') // straight from cache if cached
cy.wait(`@${prefix}-blg`)
.its('request.query.clsiserverid')
.should('eq', cached ? 'cache' : 'foo') // straight from cache if cached
if (pdf) {
cy.wait(`@${prefix}-pdf`)
.its('request.query.clsiserverid')
.should('eq', 'foo') // always from VM first
}
return cy.wrap(null)
}
export const interceptDeferredCompile = (beforeResponse?: () => void) => {
const { promise, resolve } = Promise.withResolvers<void>()
cy.intercept(
{ method: 'POST', url: '/project/*/compile*', times: 1 },
req => {
if (beforeResponse) {
beforeResponse()
}
// only reply once the Promise is resolved
promise.then(() => {
req.reply({
body: {
status: 'success',
clsiServerId: 'foo',
compileGroup: 'priority',
pdfDownloadDomain: 'https://clsi.test-overleaf.com',
outputFiles: [
{
path: 'output.pdf',
build: '123',
url: '/build/123/output.pdf',
type: 'pdf',
},
{
path: 'output.log',
build: '123',
url: '/build/123/output.log',
type: 'log',
},
{
path: 'output.blg',
build: '123',
url: '/build/123/output.blg',
type: 'log',
},
],
},
})
})
return promise
}
).as('compile')
cy.intercept(
{ pathname: '/build/*/output.pdf', times: 1 },
{ fixture: 'build/output.pdf,null' }
).as(`compile-pdf`)
cy.intercept(
{ pathname: '/build/*/output.log', times: 1 },
{ fixture: 'build/output.log' }
).as(`compile-log`)
cy.intercept(
{ pathname: '/build/*/output.blg', times: 1 },
{ fixture: 'build/output.blg' }
).as(`compile-blg`)
return cy.wrap(resolve)
}

View File

@@ -0,0 +1,5 @@
export const interceptEvents = () => {
cy.intercept('POST', '/event/*', {
statusCode: 204,
}).as('createEvent')
}

View File

@@ -0,0 +1,56 @@
import '@testing-library/cypress/add-commands'
import {
interceptCompile,
interceptCompileFromCacheRequest,
waitForCompile,
interceptDeferredCompile,
interceptCompileRequest,
} from './compile'
import { interceptEvents } from './events'
import { interceptAsync } from './intercept-async'
import { interceptFileUpload } from './upload'
import { interceptProjectListing } from './project-list'
import { interceptLinkedFile } from './linked-file'
import { interceptMathJax } from './mathjax'
import { interceptMetadata } from './metadata'
import { interceptTutorials } from './tutorials'
// eslint-disable-next-line no-unused-vars,@typescript-eslint/no-namespace
declare global {
// eslint-disable-next-line @typescript-eslint/no-namespace,no-unused-vars
namespace Cypress {
// eslint-disable-next-line no-unused-vars
interface Chainable {
interceptAsync: typeof interceptAsync
interceptCompile: typeof interceptCompile
interceptCompileRequest: typeof interceptCompileRequest
interceptCompileFromCacheRequest: typeof interceptCompileFromCacheRequest
interceptEvents: typeof interceptEvents
interceptMetadata: typeof interceptMetadata
waitForCompile: typeof waitForCompile
interceptDeferredCompile: typeof interceptDeferredCompile
interceptFileUpload: typeof interceptFileUpload
interceptProjectListing: typeof interceptProjectListing
interceptLinkedFile: typeof interceptLinkedFile
interceptMathJax: typeof interceptMathJax
interceptTutorials: typeof interceptTutorials
}
}
}
Cypress.Commands.add('interceptAsync', interceptAsync)
Cypress.Commands.add('interceptCompile', interceptCompile)
Cypress.Commands.add('interceptCompileRequest', interceptCompileRequest)
Cypress.Commands.add(
'interceptCompileFromCacheRequest',
interceptCompileFromCacheRequest
)
Cypress.Commands.add('interceptEvents', interceptEvents)
Cypress.Commands.add('interceptMetadata', interceptMetadata)
Cypress.Commands.add('waitForCompile', waitForCompile)
Cypress.Commands.add('interceptDeferredCompile', interceptDeferredCompile)
Cypress.Commands.add('interceptFileUpload', interceptFileUpload)
Cypress.Commands.add('interceptProjectListing', interceptProjectListing)
Cypress.Commands.add('interceptLinkedFile', interceptLinkedFile)
Cypress.Commands.add('interceptMathJax', interceptMathJax)
Cypress.Commands.add('interceptTutorials', interceptTutorials)

View File

@@ -0,0 +1,19 @@
import { RouteHandler, RouteMatcher } from 'cypress/types/net-stubbing'
export const interceptAsync = (route: RouteMatcher, alias: string) => {
const deferred: { resolve: (value: RouteHandler) => void } = {
resolve: () => {
console.error('This should never be called')
},
}
const promise = new Promise<RouteHandler>(resolve => {
deferred.resolve = resolve
})
cy.intercept(route, req => {
return promise.then(response => req.reply(response))
}).as(alias)
return cy.wrap(deferred)
}

View File

@@ -0,0 +1,12 @@
import { HttpRequestInterceptor } from 'cypress/types/net-stubbing'
export const interceptLinkedFile = () => {
cy.intercept(
{ method: 'POST', url: '/project/*/linked_file' },
cy
.spy((req: Parameters<HttpRequestInterceptor>[0]) => {
req.reply({ statusCode: 200, body: { success: true } })
})
.as('linked-file-request')
)
}

View File

@@ -0,0 +1,32 @@
const MATHJAX_STUB = `
window.MathJax = {
startup: {
promise: Promise.resolve()
},
svgStylesheet: () => document.createElement("STYLE"),
typesetPromise: (elements) => {
for (const element of elements) {
// This will keep math delimeters around the text
element.classList.add('MathJax')
}
return Promise.resolve()
},
tex2svgPromise: (content) => {
const text = document.createElement('SPAN')
text.classList.add('MathJax')
text.innerText = content
return Promise.resolve(text)
},
getMetricsFor: () => ({}),
texReset: () => {},
}
`
export const interceptMathJax = () => {
// NOTE: this is just a URL to be intercepted with the stub, not the real (versioned) MathJax URL
const url = '/js/libs/mathjax/es5/tex-svg-full.js'
cy.window().then(win => {
win.metaAttributesCache.set('ol-mathJaxPath', url)
})
cy.intercept('GET', url, MATHJAX_STUB).as('mathjax-load-request')
}

View File

@@ -0,0 +1,3 @@
export const interceptMetadata = () => {
cy.intercept('POST', '/project/*/doc/*/metadata', {})
}

View File

@@ -0,0 +1,22 @@
export const interceptProjectListing = () => {
cy.intercept('GET', '/user/projects', {
projects: [
{
_id: 'fake-project-1',
accessLevel: 'owner',
name: 'My first project',
},
{
_id: 'fake-project-2',
accessLevel: 'owner',
name: 'My second project',
},
],
})
cy.intercept('GET', '/project/*/entities', {
entities: [
{ path: '/frog.jpg', type: 'file' },
{ path: 'figures/unicorn.png', type: 'file' },
],
})
}

View File

@@ -0,0 +1,5 @@
export const interceptTutorials = () => {
cy.intercept('POST', '/tutorial/**', {
statusCode: 204,
}).as('completeTutorial')
}

View File

@@ -0,0 +1,18 @@
import { HttpRequestInterceptor } from 'cypress/types/net-stubbing'
export const interceptFileUpload = () => {
cy.intercept(
{ method: 'POST', url: /\/project\/.*\/upload/ },
cy
.spy((req: Parameters<HttpRequestInterceptor>[0]) => {
const folderMatch = req.url.match(
/project\/.*\/upload\?folder_id=[a-f0-9]{24}/
)
if (!folderMatch) {
req.reply({ statusCode: 500, body: { success: false } })
}
req.reply({ statusCode: 200, body: { success: true } })
})
.as('uploadRequest')
)
}

View File

@@ -0,0 +1,6 @@
Cypress.on('uncaught:exception', err => {
// don't fail the test for ResizeObserver error messages
if (err.message.includes('ResizeObserver')) {
return false
}
})

View File

@@ -0,0 +1,52 @@
import { merge } from 'webpack-merge'
import path from 'path'
import webpack from 'webpack'
import HtmlWebpackPlugin from 'html-webpack-plugin'
import devConfig from '../../webpack.config.dev'
const buildConfig = () => {
const webpackConfig = merge(devConfig, {
output: {
workerPublicPath: '/__cypress/src/',
},
devServer: {
static: path.join(__dirname, '../../public'),
port: 3200,
},
stats: 'none',
plugins: [
new webpack.EnvironmentPlugin({
CYPRESS: true,
}),
new HtmlWebpackPlugin({
template: path.resolve('./component-index.html'),
}),
],
} as any)
delete webpackConfig.devServer.client
webpackConfig.entry = {}
const addWorker = (name: string, importPath: string) => {
webpackConfig.entry[name] = require.resolve(importPath)
}
// add entrypoint under '/' for latex-linter worker
addWorker(
'latex-linter-worker',
'../../frontend/js/features/source-editor/languages/latex/linter/latex-linter.worker'
)
// add entrypoint under '/' for hunspell worker
addWorker(
'hunspell-worker',
'../../frontend/js/features/source-editor/hunspell/hunspell.worker'
)
// add entrypoints under '/' for pdfjs workers
addWorker('pdfjs-dist', 'pdfjs-dist/build/pdf.worker.mjs')
return webpackConfig
}
export const webpackConfig = buildConfig()