Switch the base framework from Create React App to Next.JS

Signed-off-by: Tilman Vatteroth <git@tilmanvatteroth.de>
This commit is contained in:
Renovate Bot 2021-12-25 15:44:24 +00:00 committed by Tilman Vatteroth
parent a979b6ffdd
commit 77a60c6c48
361 changed files with 5130 additions and 9605 deletions

1
.env.development Normal file
View file

@ -0,0 +1 @@
NEXT_PUBLIC_USE_MOCK_API=true

View file

@ -1 +1 @@
INLINE_RUNTIME_CHUNK=false
NEXT_PUBLIC_USE_MOCK_API=false

1
.env.test Normal file
View file

@ -0,0 +1 @@
NEXT_PUBLIC_USE_MOCK_API=true

44
.eslintrc.json Normal file
View file

@ -0,0 +1,44 @@
{
"root": true,
"parserOptions": {
"tsconfigRootDir": "",
"project": [
"./tsconfig.json"
]
},
"rules": {
"no-use-before-define": "off",
"no-debugger": "warn",
"default-param-last": "off",
"@typescript-eslint/consistent-type-imports": [
"error",
{
"prefer": "type-imports",
"disallowTypeAnnotations": false
}
]
},
"plugins": [
"@typescript-eslint",
"testing-library"
],
"extends": [
"next/core-web-vitals",
"eslint:recommended",
"plugin:@typescript-eslint/eslint-recommended",
"plugin:@typescript-eslint/recommended",
"plugin:@typescript-eslint/recommended-requiring-type-checking",
"plugin:import/recommended",
"plugin:import/typescript",
"prettier"
],
"overrides": [
{
"files": [
"**/__tests__/**/*.[jt]s?(x)",
"**/?(*.)+(spec|test).[jt]s?(x)"
],
"extends": ["plugin:testing-library/react"]
}
]
}

View file

@ -15,7 +15,7 @@ jobs:
runs-on: ubuntu-latest
strategy:
matrix:
node: [ '12', '14', '16' ]
node: [ '14', '16' ]
name: Test and build with NodeJS ${{ matrix.node }}
steps:
- name: Checkout repository
@ -38,6 +38,6 @@ jobs:
- name: Install dependencies
run: yarn install --frozen-lockfile --prefer-offline
- name: Test Project
run: yarn test
run: yarn test:ci
- name: Build project
run: yarn build:mock

View file

@ -22,7 +22,7 @@ jobs:
uses: actions/cache@v2.1.7
id: build-cache
with:
path: build
path: .next
key: build-${{ github.sha }}
- name: Get yarn cache directory path
@ -56,8 +56,8 @@ jobs:
- uses: actions/upload-artifact@master
with:
name: build
path: build
name: next-build
path: .next
end2end:
name: Perform E2E Test in ${{ matrix.browser }}
@ -79,13 +79,13 @@ jobs:
- name: Download built frontend
uses: actions/download-artifact@master
with:
name: build
path: build
name: next-build
path: .next
- uses: cypress-io/github-action@v2
with:
browser: ${{ matrix.browser }}
start: 'yarn serve:build'
start: 'yarn start:ci'
parallel: true
record: true
group: "UI - ${{ matrix.browser }}"

31
.gitignore vendored
View file

@ -15,23 +15,36 @@
# downloaded files during tests with cypress
/cypress/downloads
# next.js
/.next/
/out/
# production
/build
# ide
.idea
!.idea/dictionaries/hedgedoc.xml
!.idea/copyright
!.idea/prettier.xml
# misc
.DS_Store
*.pem
# debug
npm-debug.log*
yarn-debug.log*
yarn-error.log*
# local env files
.env.local
.env.development.local
.env.test.local
.env.production.local
npm-debug.log*
yarn-debug.log*
yarn-error.log*
# vercel
.vercel
.eslintcache
# typescript
*.tsbuildinfo
# IDE
.idea
!.idea/dictionaries/hedgedoc.xml
!.idea/copyright
!.idea/prettier.xml

11
.prettierrc.json Normal file
View file

@ -0,0 +1,11 @@
{
"parser": "typescript",
"singleQuote": true,
"jsxSingleQuote": true,
"semi": false,
"tabWidth": 2,
"trailingComma": "none",
"bracketSpacing": true,
"bracketSameLine": true,
"arrowParens": "always"
}

View file

@ -1,3 +1,3 @@
SPDX-FileCopyrightText: 2021 The HedgeDoc developers (see AUTHORS file)
SPDX-License-Identifier: LicenseRef-HedgeDoc-Icon-Usage-Guidelines
SPDX-License-Identifier: CC0-1.0

View file

@ -13,11 +13,12 @@ SPDX-License-Identifier: CC-BY-SA-4.0
[![Language grade: JavaScript](https://img.shields.io/lgtm/grade/javascript/g/hedgedoc/react-client.svg?logo=lgtm&logoWidth=18)](https://lgtm.com/projects/g/hedgedoc/react-client/context:javascript)
[![Total alerts](https://img.shields.io/lgtm/alerts/g/hedgedoc/react-client.svg?logo=lgtm&logoWidth=18)](https://lgtm.com/projects/g/hedgedoc/react-client/alerts/)
This is the new, improved and better looking frontend for HedgeDoc 2.0.
Our goal is to recreate the current frontend in react and to improve it.
This is the new, improved and better looking frontend for HedgeDoc 2.0. Our goal is to recreate the current frontend in
react and to improve it.
## Preparation
You need at least Node 12 (we recommend Node 16) and [yarn](https://yarnpkg.com/).
You need at least Node 14 (we recommend Node 16) and [yarn](https://yarnpkg.com/).
## Development mode
@ -25,13 +26,14 @@ You need at least Node 12 (we recommend Node 16) and [yarn](https://yarnpkg.com/
2. Go inside the repo (e.g. `cd hedgedoc-react-client`)
3. Run `yarn install`
4. Either run
- `yarn start` - Calls only mocked version of the api. Doesn't need a HedgeDoc backend.
- `yarn start:for-real-backend` - Expects [a HedgeDoc backend server](https://github.com/hedgedoc/hedgedoc/tree/develop) running under [http://localhost:3000](http://localhost:3000))
- `yarn dev` - Calls only mocked version of the api. Doesn't need a HedgeDoc backend.
- `yarn start:for-real-backend` -
Expects [a HedgeDoc backend server](https://github.com/hedgedoc/hedgedoc/tree/develop) running
under [http://localhost:3000](http://localhost:3000))
This should run the app in the development mode and open [http://localhost:3001](http://localhost:3001) in your browser.
The page will reload if you make edits.
You will also see any lint errors in the console.
The page will reload if you make edits. You will also see any lint errors in the console.
### Tests
@ -45,25 +47,28 @@ Unit testing is done via jest.
We use [cypress](https://cypress.io) for e2e tests.
1. Start the frontend with `yarn start:test` in dev test mode or build a test build with `yarn build:test` which you can serve with `yarn serve:build`
1. Start the frontend with `yarn dev:test` in dev test mode or build a test build with `yarn build:test` which you can
serve with `yarn serve:build`
Don't use the regular start/build command, or the tests will fail!
2. Run `yarn cy:open` to open the cypress test loader
3. Choose your browser and test
4. Let the tests run
### Bundle analysis
You can inspect the generated production-bundle files to look for optimization issues.
1. Run `yarn analyze`
2. Open the generated `build/report.html` in your favourite browser
2. Open the generated `.next/server/analyze/server.html` in your favourite browser
## Production mode
1. Clone this repo (e.g. `git clone https://github.com/hedgedoc/react-client.git hedgedoc-react-client`)
2. Go inside the repo (e.g. `cd hedgedoc-react-client`)
3. Run `yarn install`
4. Run `yarn build:production`
4. Run `yarn build`
This will build the app in production mode and save it into the `build` folder.
The production build is optimized for best performance, minimized
and the filenames include a hash value of the content. Don't edit them by hand!
This will build the app in production mode and save it into the `.next` folder. The production build is optimized for
best performance, minimized and the filenames include a hash value of the content. Don't edit them by hand!
You can run the production build using the built-in server with `yarn start`.

View file

@ -1,10 +0,0 @@
/*
* SPDX-FileCopyrightText: 2021 The HedgeDoc developers (see AUTHORS file)
*
* SPDX-License-Identifier: AGPL-3.0-only
*/
if (!process.env.REACT_APP_BACKEND_BASE_URL) {
console.error("==============\nREACT_APP_BACKEND_BASE_URL not set.\n Use this task only if you want to create a production build with a real backend. Otherwise use build:mock\n==============");
process.exit(1);
}

View file

@ -1,31 +0,0 @@
/*
* SPDX-FileCopyrightText: 2021 The HedgeDoc developers (see AUTHORS file)
*
* SPDX-License-Identifier: AGPL-3.0-only
*/
const CopyPlugin = require('copy-webpack-plugin');
const { when } = require('@craco/craco');
const { BundleAnalyzerPlugin } = require('webpack-bundle-analyzer');
module.exports = {
webpack: {
plugins: {
add: [
new CopyPlugin({
patterns: [
{ from: 'node_modules/@hpcc-js/wasm/dist/graphvizlib.wasm', to: 'static/js' },
{ from: 'node_modules/@hpcc-js/wasm/dist/expatlib.wasm', to: 'static/js' },
{ from: 'node_modules/emoji-picker-element-data/en/emojibase/data.json', to: 'static/js/emoji-data.json' }
],
}),
...when(Boolean(process.env.ANALYZE), () => [
new BundleAnalyzerPlugin({
analyzerMode: "static",
generateStatsFile: true
})
], [])
]
}
}
}

View file

@ -18,7 +18,7 @@ describe('Autocompletion works for', () => {
cy.get('.CodeMirror-hints').should('not.exist')
cy.get('.CodeMirror-code > div:nth-of-type(1) > .CodeMirror-line').contains('```abnf')
cy.get('.CodeMirror-code > div:nth-of-type(3) > .CodeMirror-line').contains('```')
cy.getMarkdownBody().findById('highlighted-code-block').should('exist')
cy.getMarkdownBody().findByCypressId('highlighted-code-block').should('exist')
})
it('via doubleclick', () => {
cy.setCodemirrorContent('```')
@ -26,7 +26,7 @@ describe('Autocompletion works for', () => {
cy.get('.CodeMirror-hints').should('not.exist')
cy.get('.CodeMirror-code > div:nth-of-type(1) > .CodeMirror-line').contains('```abnf')
cy.get('.CodeMirror-code > div:nth-of-type(3) > .CodeMirror-line').contains('```')
cy.getMarkdownBody().findById('highlighted-code-block').should('exist')
cy.getMarkdownBody().findByCypressId('highlighted-code-block').should('exist')
})
})
@ -109,14 +109,14 @@ describe('Autocompletion works for', () => {
cy.get('@codeinput').type('{enter}')
cy.get('.CodeMirror-hints').should('not.exist')
cy.get('.CodeMirror-activeline').contains('![image alt](https:// "title")')
cy.getMarkdownBody().find('.image-drop').should('exist')
cy.getMarkdownBody().findByCypressId('image-placeholder-image-drop').should('exist')
})
it('via doubleclick', () => {
cy.setCodemirrorContent('!')
cy.get('.CodeMirror-hints > li').first().dblclick()
cy.get('.CodeMirror-hints').should('not.exist')
cy.get('.CodeMirror-activeline').contains('![image alt](https:// "title")')
cy.getMarkdownBody().find('.image-drop').should('exist')
cy.getMarkdownBody().findByCypressId('image-placeholder-image-drop').should('exist')
})
})

View file

@ -9,10 +9,14 @@ describe('Diagram codeblock ', () => {
cy.visitTestEditor()
})
it('renders markmap', () => {
cy.setCodemirrorContent('```markmap\n- pro\n- contra\n```')
cy.getMarkdownBody().findById('markmap').children().should('be.visible')
})
/*
TODO: Readd test after fixing https://github.com/hedgedoc/react-client/issues/1709
it('renders markmap', () => {
cy.setCodemirrorContent('```markmap\n- pro\n- contra\n```')
cy.getMarkdownBody().findByCypressId('markmap').children().should('be.visible')
})
*/
it('renders vega-lite', () => {
cy.setCodemirrorContent(
@ -23,27 +27,27 @@ describe('Diagram codeblock ', () => {
it('renders graphviz', () => {
cy.setCodemirrorContent('```graphviz\ngraph {\na -- b\n}\n```')
cy.getMarkdownBody().findById('graphviz').children().should('be.visible')
cy.getMarkdownBody().findByCypressId('graphviz').children().should('be.visible')
})
it('renders mermaid', () => {
cy.setCodemirrorContent('```mermaid\ngraph TD;\n A-->B;\n```')
cy.getMarkdownBody().find('.mermaid').children().should('be.visible')
cy.getMarkdownBody().findByCypressId('mermaid-frame').children().should('be.visible')
})
it('renders flowcharts', () => {
cy.setCodemirrorContent('```flow\nst=>start: Start\ne=>end: End\nst->e\n```')
cy.getMarkdownBody().findById('flowchart').children().should('be.visible')
cy.getMarkdownBody().findByCypressId('flowchart').children().should('be.visible')
})
it('renders abc scores', () => {
cy.setCodemirrorContent('```abc\nM:4/4\nK:G\n|:GABc dedB:|\n```')
cy.getMarkdownBody().findById('abcjs').children().should('be.visible')
cy.getMarkdownBody().findByCypressId('abcjs').children().should('be.visible')
})
it('renders csv as table', () => {
cy.setCodemirrorContent('```csv delimiter=; header\na;b;c;d\n1;2;3;4\n```')
cy.getMarkdownBody().findById('csv-html-table').first().should('be.visible')
cy.getMarkdownBody().findByCypressId('csv-html-table').first().should('be.visible')
})
it('renders plantuml', () => {

View file

@ -10,7 +10,7 @@ const title = 'This is a test title'
describe('Document Title', () => {
beforeEach(() => {
cy.visitTestEditor()
cy.getById('view-mode-both').should('exist')
cy.getByCypressId('view-mode-both').should('exist')
})
describe('title should be yaml metadata title', () => {

View file

@ -7,18 +7,18 @@
describe('Editor mode from URL parameter is used', () => {
it('mode view', () => {
cy.visitTestEditor('view')
cy.get('.splitter.left').should('not.be.visible')
cy.get('.splitter.right').should('be.visible')
cy.getByCypressId('splitter-left').should('not.be.visible')
cy.getByCypressId('splitter-right').should('be.visible')
})
it('mode both', () => {
cy.visitTestEditor('both')
cy.get('.splitter.left').should('be.visible')
cy.get('.splitter.separator').should('exist')
cy.get('.splitter.right').should('be.visible')
cy.getByCypressId('splitter-left').should('be.visible')
cy.getByCypressId('splitter-separator').should('exist')
cy.getByCypressId('splitter-right').should('be.visible')
})
it('mode edit', () => {
cy.visitTestEditor('edit')
cy.get('.splitter.left').should('be.visible')
cy.get('.splitter.right').should('not.be.visible')
cy.getByCypressId('splitter-left').should('be.visible')
cy.getByCypressId('splitter-right').should('not.be.visible')
})
})

View file

@ -14,8 +14,8 @@ describe('Export', () => {
})
it('Markdown', () => {
cy.getById('menu-export').click()
cy.getById('menu-export-markdown').click()
cy.getByCypressId('menu-export').click()
cy.getByCypressId('menu-export-markdown').click()
cy.get('a[download]')
.then(
(anchor) =>

View file

@ -40,8 +40,8 @@ describe('File upload', () => {
)
})
it('via button', () => {
cy.getById('editor-toolbar-upload-image-button').should('be.visible')
cy.getById('editor-toolbar-upload-image-input').attachFixture({
cy.getByCypressId('editor-toolbar-upload-image-button').should('be.visible')
cy.getByCypressId('editor-toolbar-upload-image-input').attachFixture({
filePath: 'demo.png',
mimeType: 'image/png'
})
@ -86,8 +86,8 @@ describe('File upload', () => {
statusCode: 400
}
)
cy.getById('editor-toolbar-upload-image-button').should('be.visible')
cy.getById('editor-toolbar-upload-image-input').attachFixture({
cy.getByCypressId('editor-toolbar-upload-image-button').should('be.visible')
cy.getByCypressId('editor-toolbar-upload-image-input').attachFixture({
filePath: 'demo.png',
mimeType: 'image/png'
})

View file

@ -10,7 +10,7 @@ describe('Help Dialog', () => {
})
it('ToDo-List', () => {
cy.getById('editor-help-button').click()
cy.getByCypressId('editor-help-button').click()
cy.get('input[type="checkbox"]').should('exist').should('not.be.checked')
})
})

View file

@ -5,7 +5,7 @@
*/
const findHljsCodeBlock = () => {
return cy.getMarkdownBody().find('.code-highlighter > code.hljs').should('be.visible')
return cy.getMarkdownBody().findByCypressId('code-highlighter').should('be.visible')
}
describe('Code', () => {
@ -16,17 +16,18 @@ describe('Code', () => {
describe('with just the language', () => {
it("doesn't show a gutter", () => {
cy.setCodemirrorContent('```javascript \nlet x = 0\n```')
findHljsCodeBlock().should('not.have.class', 'showGutter')
findHljsCodeBlock().should('have.attr', 'data-cypress-showgutter', 'false')
findHljsCodeBlock().find('.linenumber').should('not.be.visible')
findHljsCodeBlock().findByCypressId('linenumber').should('not.be.visible')
})
describe('and line wrapping', () => {
it("doesn't show a gutter", () => {
cy.setCodemirrorContent('```javascript! \nlet x = 0\n```')
findHljsCodeBlock().should('not.have.class', 'showGutter').should('have.class', 'wrapLines')
findHljsCodeBlock().should('have.attr', 'data-cypress-showgutter', 'false')
findHljsCodeBlock().should('have.attr', 'data-cypress-wrapLines', 'true')
findHljsCodeBlock().find('.linenumber').should('not.be.visible')
findHljsCodeBlock().findByCypressId('linenumber').should('not.be.visible')
})
})
})
@ -34,17 +35,18 @@ describe('Code', () => {
describe('with the language and show gutter', () => {
it('shows the correct line number', () => {
cy.setCodemirrorContent('```javascript= \nlet x = 0\n```')
findHljsCodeBlock().should('have.class', 'showGutter')
findHljsCodeBlock().should('have.attr', 'data-cypress-showgutter', 'true')
findHljsCodeBlock().find('.linenumber').should('be.visible').text().should('eq', '1')
findHljsCodeBlock().findByCypressId('linenumber').should('be.visible').text().should('eq', '1')
})
describe('and line wrapping', () => {
it('shows the correct line number', () => {
cy.setCodemirrorContent('```javascript=! \nlet x = 0\n```')
findHljsCodeBlock().should('have.class', 'showGutter').should('have.class', 'wrapLines')
findHljsCodeBlock().should('have.attr', 'data-cypress-showgutter', 'true')
findHljsCodeBlock().should('have.attr', 'data-cypress-wrapLines', 'true')
findHljsCodeBlock().find('.linenumber').should('be.visible').text().should('eq', '1')
findHljsCodeBlock().findByCypressId('linenumber').should('be.visible').text().should('eq', '1')
})
})
})
@ -52,45 +54,40 @@ describe('Code', () => {
describe('with the language, show gutter with a start number', () => {
it('shows the correct line number', () => {
cy.setCodemirrorContent('```javascript=100 \nlet x = 0\n```')
findHljsCodeBlock().should('have.class', 'showGutter')
findHljsCodeBlock().should('have.attr', 'data-cypress-showgutter', 'true')
findHljsCodeBlock().find('.linenumber').should('be.visible').text().should('eq', '100')
findHljsCodeBlock().findByCypressId('linenumber').should('be.visible').text().should('eq', '100')
})
it('shows the correct line number and continues in another codeblock', () => {
cy.setCodemirrorContent('```javascript=100 \nlet x = 0\nlet y = 1\n```\n\n```javascript=+\nlet y = 2\n```\n')
findHljsCodeBlock()
.should('have.class', 'showGutter')
.first()
.find('.linenumber')
.first()
.should('be.visible')
.text()
.should('eq', '100')
findHljsCodeBlock().first().find('.linenumber').last().should('be.visible').text().should('eq', '101')
findHljsCodeBlock().last().find('.linenumber').first().should('be.visible').text().should('eq', '102')
findHljsCodeBlock().should('have.attr', 'data-cypress-showgutter', 'true')
findHljsCodeBlock().first().findByCypressId('linenumber').first().should('be.visible').text().should('eq', '100')
findHljsCodeBlock().first().findByCypressId('linenumber').last().should('be.visible').text().should('eq', '101')
findHljsCodeBlock().last().findByCypressId('linenumber').first().should('be.visible').text().should('eq', '102')
})
describe('and line wrapping', () => {
it('shows the correct line number', () => {
cy.setCodemirrorContent('```javascript=100! \nlet x = 0\n```')
findHljsCodeBlock().should('have.class', 'showGutter').should('have.class', 'wrapLines')
findHljsCodeBlock().find('.linenumber').should('be.visible').text().should('eq', '100')
findHljsCodeBlock().should('have.attr', 'data-cypress-showgutter', 'true')
findHljsCodeBlock().should('have.attr', 'data-cypress-wrapLines', 'true')
findHljsCodeBlock().findByCypressId('linenumber').should('be.visible').text().should('eq', '100')
})
it('shows the correct line number and continues in another codeblock', () => {
cy.setCodemirrorContent('```javascript=100! \nlet x = 0\nlet y = 1\n```\n\n```javascript=+\nlet y = 2\n```\n')
findHljsCodeBlock().should('have.attr', 'data-cypress-showgutter', 'true')
findHljsCodeBlock().should('have.attr', 'data-cypress-wrapLines', 'true')
findHljsCodeBlock()
.should('have.class', 'showGutter')
.should('have.class', 'wrapLines')
.first()
.find('.linenumber')
.findByCypressId('linenumber')
.first()
.should('be.visible')
.text()
.should('eq', '100')
findHljsCodeBlock().first().find('.linenumber').last().should('be.visible').text().should('eq', '101')
findHljsCodeBlock().last().find('.linenumber').first().should('be.visible').text().should('eq', '102')
findHljsCodeBlock().first().findByCypressId('linenumber').last().should('be.visible').text().should('eq', '101')
findHljsCodeBlock().last().findByCypressId('linenumber').first().should('be.visible').text().should('eq', '102')
})
})
})
@ -98,7 +95,7 @@ describe('Code', () => {
it('has a working copy button', () => {
cy.setCodemirrorContent('```javascript \nlet x = 0\n```')
cy.getById('documentIframe').then((element: JQuery<HTMLElement>) => {
cy.getByCypressId('documentIframe').then((element: JQuery<HTMLElement>) => {
const frame = element.get(0) as HTMLIFrameElement
if (frame === null || frame.contentWindow === null) {
return cy.wrap(null)
@ -107,7 +104,7 @@ describe('Code', () => {
cy.spy(frame.contentWindow.navigator.clipboard, 'writeText').as('copy')
})
cy.getIframeBody().findById('copy-code-button').click()
cy.getIframeBody().findByCypressId('copy-code-button').click()
cy.get('@copy').should('be.calledWithExactly', 'let x = 0\n')
})

View file

@ -11,12 +11,12 @@ describe('History', () => {
})
it('Cards', () => {
cy.getById('history-card').should('be.visible')
cy.getByCypressId('history-card').should('be.visible')
})
it('Table', () => {
cy.getById('history-mode-table').click()
cy.getById('history-table').should('be.visible')
cy.getByCypressId('history-mode-table').click()
cy.getByCypressId('history-table').should('be.visible')
})
})
@ -39,13 +39,13 @@ describe('History', () => {
})
it('in table view', () => {
cy.getById('history-mode-table').click()
cy.getById('history-table').should('be.visible')
cy.getById('history-entry-title').contains('Features')
cy.getByCypressId('history-mode-table').click()
cy.getByCypressId('history-table').should('be.visible')
cy.getByCypressId('history-entry-title').contains('Features')
})
it('in cards view', () => {
cy.getById('history-entry-title').contains('Features')
cy.getByCypressId('history-entry-title').contains('Features')
})
})
describe('is untitled when not empty', () => {
@ -66,13 +66,13 @@ describe('History', () => {
})
it('in table view', () => {
cy.getById('history-mode-table').click()
cy.getById('history-table').should('be.visible')
cy.getById('history-entry-title').contains('Untitled')
cy.getByCypressId('history-mode-table').click()
cy.getByCypressId('history-table').should('be.visible')
cy.getByCypressId('history-entry-title').contains('Untitled')
})
it('in cards view', () => {
cy.getById('history-entry-title').contains('Untitled')
cy.getByCypressId('history-entry-title').contains('Untitled')
})
})
})
@ -90,17 +90,17 @@ describe('History', () => {
})
it('Cards', () => {
cy.getById('history-card').should('be.visible')
cy.getById('history-entry-pin-button').first().as('pin-button')
cy.get('@pin-button').should('have.class', 'pinned').click()
cy.get('@pin-button').should('not.have.class', 'pinned')
cy.getByCypressId('history-card').should('be.visible')
cy.getByCypressId('history-entry-pin-button').first().as('pin-button')
cy.get('@pin-button').should('have.attr', 'data-cypress-pinned', 'true').click()
cy.get('@pin-button').should('have.attr', 'data-cypress-pinned', 'false')
})
it('Table', () => {
cy.getById('history-mode-table').click()
cy.getById('history-entry-pin-button').first().as('pin-button')
cy.get('@pin-button').should('have.class', 'pinned').click()
cy.get('@pin-button').should('not.have.class', 'pinned')
cy.getByCypressId('history-mode-table').click()
cy.getByCypressId('history-entry-pin-button').first().as('pin-button')
cy.get('@pin-button').should('have.attr', 'data-cypress-pinned', 'true').click()
cy.get('@pin-button').should('have.attr', 'data-cypress-pinned', 'false')
})
})
@ -112,15 +112,15 @@ describe('History', () => {
})
it('Cards', () => {
cy.getById('history-card').should('be.visible')
cy.getById('history-entry-pin-button').first().click()
cy.getById('notification-toast').should('be.visible')
cy.getByCypressId('history-card').should('be.visible')
cy.getByCypressId('history-entry-pin-button').first().click()
cy.getByCypressId('notification-toast').should('be.visible')
})
it('Table', () => {
cy.getById('history-mode-table').click()
cy.getById('history-entry-pin-button').first().click()
cy.getById('notification-toast').should('be.visible')
cy.getByCypressId('history-mode-table').click()
cy.getByCypressId('history-entry-pin-button').first().click()
cy.getByCypressId('notification-toast').should('be.visible')
})
})
})
@ -136,37 +136,37 @@ describe('History', () => {
})
it('works with valid file', () => {
cy.getById('import-history-file-button').should('be.visible')
cy.getById('import-history-file-input').attachFixture({
cy.getByCypressId('import-history-file-button').should('be.visible')
cy.getByCypressId('import-history-file-input').attachFixture({
filePath: 'history.json',
mimeType: 'application/json'
})
cy.getById('history-entry-title').should('have.length', 1).contains('cy-Test')
cy.getByCypressId('history-entry-title').should('have.length', 1).contains('cy-Test')
})
it('fails on invalid file', () => {
cy.getById('import-history-file-button').should('be.visible')
cy.getById('import-history-file-input').attachFixture({
cy.getByCypressId('import-history-file-button').should('be.visible')
cy.getByCypressId('import-history-file-input').attachFixture({
filePath: 'invalid-history.txt',
mimeType: 'text/plain'
})
cy.getById('notification-toast').should('be.visible')
cy.getByCypressId('notification-toast').should('be.visible')
})
it('works when selecting two files with the same name', () => {
cy.getById('import-history-file-button').should('be.visible')
cy.getById('import-history-file-input').attachFixture({
cy.getByCypressId('import-history-file-button').should('be.visible')
cy.getByCypressId('import-history-file-input').attachFixture({
filePath: 'history.json',
mimeType: 'application/json'
})
cy.getById('history-entry-title').should('have.length', 1).contains('cy-Test')
cy.getById('import-history-file-button').should('be.visible')
cy.getById('import-history-file-input').attachFixture({
cy.getByCypressId('history-entry-title').should('have.length', 1).contains('cy-Test')
cy.getByCypressId('import-history-file-button').should('be.visible')
cy.getByCypressId('import-history-file-input').attachFixture({
filePath: 'history-2.json',
fileName: 'history.json',
mimeType: 'application/json'
})
cy.getById('history-entry-title').should('have.length', 2).contains('cy-Test2')
cy.getByCypressId('history-entry-title').should('have.length', 2).contains('cy-Test2')
})
})
})

View file

@ -11,7 +11,7 @@ describe('Iframe capsule', () => {
it('shows a clickable click shield instead of the iframe', () => {
cy.setCodemirrorContent('<iframe src="https://example.org"></iframe>')
cy.getMarkdownBody().findById('iframe-capsule-click-shield').should('exist').click()
cy.getMarkdownBody().findByCypressId('iframe-capsule-click-shield').should('exist').click()
cy.getMarkdownBody().find('iframe').should('exist').should('have.attr', 'src', 'https://example.org')
})
})

View file

@ -10,9 +10,9 @@ describe('Import markdown file', () => {
})
it('import on blank note', () => {
cy.getById('menu-import').click()
cy.getById('menu-import-markdown-button').should('be.visible')
cy.getById('menu-import-markdown-input').attachFixture({
cy.getByCypressId('menu-import').click()
cy.getByCypressId('menu-import-markdown-button').should('be.visible')
cy.getByCypressId('menu-import-markdown-input').attachFixture({
filePath: 'import.md',
mimeType: 'text/markdown'
})
@ -25,9 +25,9 @@ describe('Import markdown file', () => {
it('import on note with content', () => {
cy.setCodemirrorContent('test\nabc')
cy.getById('menu-import').click()
cy.getById('menu-import-markdown-button').should('be.visible')
cy.getById('menu-import-markdown-input').attachFixture({
cy.getByCypressId('menu-import').click()
cy.getByCypressId('menu-import-markdown-button').should('be.visible')
cy.getByCypressId('menu-import-markdown-input').attachFixture({
filePath: 'import.md',
mimeType: 'text/markdown'
})

View file

@ -22,39 +22,39 @@ describe('Intro page', () => {
})
cy.visit('/')
cy.getById('documentIframe').should('not.exist')
cy.getByCypressId('documentIframe').should('not.exist')
})
})
describe('features button', () => {
it('is hidden when logged in', () => {
cy.getById('features-button').should('not.exist')
cy.getByCypressId('features-button').should('not.exist')
})
it('is visible when logged out', () => {
cy.logout()
cy.getById('features-button').should('exist')
cy.getByCypressId('features-button').should('exist')
})
})
describe('sign in button', () => {
it('is hidden when logged in', () => {
cy.getById('sign-in-button').should('not.exist')
cy.getByCypressId('sign-in-button').should('not.exist')
})
it('is visible when logged out', () => {
cy.logout()
cy.getById('sign-in-button').should('exist')
cy.getByCypressId('sign-in-button').should('exist')
})
})
describe('version dialog', () => {
it('can be opened and closed', () => {
cy.getById('version-modal').should('not.exist')
cy.getById('show-version-modal').click()
cy.getById('version-modal').should('be.visible')
cy.getById('version-modal').find('.modal-header .close').click()
cy.getById('version-modal').should('not.exist')
cy.getByCypressId('version-modal').should('not.exist')
cy.getByCypressId('show-version-modal').click()
cy.getByCypressId('version-modal').should('be.visible')
cy.getByCypressId('version-modal').find('.modal-header .close').click()
cy.getByCypressId('version-modal').should('not.exist')
})
})
})

View file

@ -12,7 +12,7 @@ describe('Languages', () => {
})
it('all languages are available', () => {
cy.getById('language-picker').find('option').as('languages')
cy.getByCypressId('language-picker').find('option').as('languages')
cy.get('@languages').should('have.length', 28)
languages.forEach((language) => {
cy.get('@languages').contains(language)
@ -20,9 +20,9 @@ describe('Languages', () => {
})
it('language changes affect the UI', () => {
cy.getById('language-picker').select('English')
cy.getById('new-note-button').find('span').contains('New note')
cy.getById('language-picker').select('Deutsch')
cy.getById('new-note-button').find('span').contains('Neue Notiz')
cy.getByCypressId('language-picker').select('English')
cy.getByCypressId('new-note-button').find('span').contains('New note')
cy.getByCypressId('language-picker').select('Deutsch')
cy.getByCypressId('new-note-button').find('span').contains('Neue Notiz')
})
})

View file

@ -12,9 +12,9 @@ describe('Links Intro', () => {
})
it('History', () => {
cy.getById('navLinkHistory').click()
cy.getByCypressId('navLinkHistory').click()
cy.url().should('include', '/history')
cy.getById('navLinkIntro').click()
cy.getByCypressId('navLinkIntro').click()
cy.url().should('include', '/intro')
})
@ -24,29 +24,29 @@ describe('Links Intro', () => {
})
it('New guest note', () => {
cy.getById('new-guest-note-button').click()
cy.getByCypressId('new-guest-note-button').click()
cy.url().should('include', '/new')
})
})
describe('Menu Buttons logged in', () => {
it('New note', () => {
cy.getById('new-note-button').click()
cy.getByCypressId('new-note-button').click()
cy.url().should('include', '/new')
})
describe('User Menu', () => {
beforeEach(() => {
cy.getById('user-dropdown').click()
cy.getByCypressId('user-dropdown').click()
})
it('Features', () => {
cy.getById('user-dropdown-features-button').click()
cy.getByCypressId('user-dropdown-features-button').click()
cy.url().should('include', '/features')
})
it('Profile', () => {
cy.getById('user-dropdown-profile-button').click()
cy.getByCypressId('user-dropdown-profile-button').click()
cy.url().should('include', '/profile')
})
})

View file

@ -13,14 +13,14 @@ describe('Link gets replaced with embedding: ', () => {
it('GitHub Gist', () => {
cy.setCodemirrorContent('https://gist.github.com/schacon/1')
cy.getMarkdownBody().findById('click-shield-gist').find('.preview-background').parent().click()
cy.getMarkdownBody().findById('gh-gist').should('be.visible')
cy.getMarkdownBody().findByCypressId('click-shield-gist').find('.preview-background').parent().click()
cy.getMarkdownBody().findByCypressId('gh-gist').should('be.visible')
})
it('YouTube', () => {
cy.setCodemirrorContent('https://www.youtube.com/watch?v=YE7VzlLtp-4')
cy.getMarkdownBody()
.findById('click-shield-youtube')
.findByCypressId('click-shield-youtube')
.find('.preview-background')
.should('have.attr', 'src', 'https://i.ytimg.com/vi/YE7VzlLtp-4/maxresdefault.jpg')
.parent()
@ -46,7 +46,7 @@ describe('Link gets replaced with embedding: ', () => {
)
cy.setCodemirrorContent('https://vimeo.com/23237102')
cy.getMarkdownBody()
.findById('click-shield-vimeo')
.findByCypressId('click-shield-vimeo')
.find('.preview-background')
.should('have.attr', 'src', 'https://i.vimeocdn.com/video/503631401_640.jpg')
.parent()
@ -57,7 +57,7 @@ describe('Link gets replaced with embedding: ', () => {
it('Asciinema', () => {
cy.setCodemirrorContent('https://asciinema.org/a/117928')
cy.getMarkdownBody()
.findById('click-shield-asciinema')
.findByCypressId('click-shield-asciinema')
.find('.preview-background')
.should('have.attr', 'src', 'https://asciinema.org/a/117928.png')
.parent()

View file

@ -14,21 +14,21 @@ describe('The status bar text length info', () => {
})
it('shows the maximal length of the document as number of available characters in the tooltip', () => {
cy.getById('remainingCharacters').attribute('title').should('contain', ' 200 ')
cy.getByCypressId('remainingCharacters').attribute('title').should('contain', ' 200 ')
})
it('color is set to "warning" on <= 100 characters remaining', () => {
cy.setCodemirrorContent(warningTestContent)
cy.getById('remainingCharacters').should('have.class', 'text-warning')
cy.getByCypressId('remainingCharacters').should('have.class', 'text-warning')
})
it('color is set to danger on <= 0 characters remaining', () => {
cy.setCodemirrorContent(dangerTestContent)
cy.getById('remainingCharacters').should('have.class', 'text-danger')
cy.getByCypressId('remainingCharacters').should('have.class', 'text-danger')
})
it('opens a modal', () => {
cy.setCodemirrorContent(tooMuchTestContent)
cy.getById('limitReachedModal').should('be.visible')
cy.getByCypressId('limitReachedModal').should('be.visible')
})
})

View file

@ -29,63 +29,63 @@ describe('Motd', () => {
it('shows the correct alert Motd text', () => {
mockExistingMotd()
cy.visit('/')
cy.getById('motd').contains(motdMockContent)
cy.getByCypressId('motd').contains(motdMockContent)
})
it('can be dismissed', () => {
mockExistingMotd()
cy.visit('/')
cy.getById('motd').contains(motdMockContent)
cy.getById('motd-dismiss')
cy.getByCypressId('motd').contains(motdMockContent)
cy.getByCypressId('motd-dismiss')
.click()
.then(() => {
expect(localStorage.getItem(MOTD_LOCAL_STORAGE_KEY)).to.equal(MOCK_LAST_MODIFIED)
})
cy.getById('motd').should('not.exist')
cy.getByCypressId('motd').should('not.exist')
})
it("won't show again after dismiss and reload", () => {
mockExistingMotd()
cy.visit('/')
cy.getById('motd').contains(motdMockContent)
cy.getById('motd-dismiss')
cy.getByCypressId('motd').contains(motdMockContent)
cy.getByCypressId('motd-dismiss')
.click()
.then(() => {
expect(localStorage.getItem(MOTD_LOCAL_STORAGE_KEY)).to.equal(MOCK_LAST_MODIFIED)
})
cy.getById('motd').should('not.exist')
cy.getByCypressId('motd').should('not.exist')
cy.reload()
cy.get('main').should('exist')
cy.getById('motd').should('not.exist')
cy.getByCypressId('motd').should('not.exist')
})
it('will show again after reload without dismiss', () => {
mockExistingMotd()
cy.visit('/')
cy.getById('motd').contains(motdMockContent)
cy.getByCypressId('motd').contains(motdMockContent)
cy.reload()
cy.get('main').should('exist')
cy.getById('motd').contains(motdMockContent)
cy.getByCypressId('motd').contains(motdMockContent)
})
it("won't show again after dismiss and page navigation", () => {
mockExistingMotd()
cy.visit('/')
cy.getById('motd').contains(motdMockContent)
cy.getById('motd-dismiss')
cy.getByCypressId('motd').contains(motdMockContent)
cy.getByCypressId('motd-dismiss')
.click()
.then(() => {
expect(localStorage.getItem(MOTD_LOCAL_STORAGE_KEY)).to.equal(MOCK_LAST_MODIFIED)
})
cy.getById('motd').should('not.exist')
cy.getById('navLinkHistory').click()
cy.getByCypressId('motd').should('not.exist')
cy.getByCypressId('navLinkHistory').click()
cy.get('main').should('exist')
cy.getById('motd').should('not.exist')
cy.getByCypressId('motd').should('not.exist')
})
it("won't show if no file exists", () => {
cy.visit('/')
cy.get('main').should('exist')
cy.getById('motd').should('not.exist')
cy.getByCypressId('motd').should('not.exist')
})
})

View file

@ -53,22 +53,22 @@ describe('profile page', () => {
describe('access tokens', () => {
it('list existing tokens', () => {
cy.getById('access-token-label').contains('cypress-App')
cy.getByCypressId('access-token-label').contains('cypress-App')
})
it('delete token', () => {
cy.getById('access-token-delete-button').click()
cy.getById('access-token-modal-delete').as('deletion-modal')
cy.getByCypressId('access-token-delete-button').click()
cy.getByCypressId('access-token-modal-delete').as('deletion-modal')
cy.get('@deletion-modal').should('be.visible').find('.modal-footer .btn-danger').click()
cy.get('@deletion-modal').should('not.exist')
})
it('add token', () => {
cy.getById('access-token-add-button').should('be.disabled')
cy.getById('access-token-add-input-label').type('cypress')
cy.getById('access-token-modal-add').should('not.exist')
cy.getById('access-token-add-button').should('not.be.disabled').click()
cy.getById('access-token-modal-add')
cy.getByCypressId('access-token-add-button').should('be.disabled')
cy.getByCypressId('access-token-add-input-label').type('cypress')
cy.getByCypressId('access-token-modal-add').should('not.exist')
cy.getByCypressId('access-token-add-button').should('not.be.disabled').click()
cy.getByCypressId('access-token-modal-add')
.should('be.visible')
.find('input[readonly]')
.should('have.value', 'c-y-p-r-e-s-s')

View file

@ -35,7 +35,7 @@ describe('Short code gets replaced or rendered: ', () => {
describe('youtube', () => {
it('renders click-shield', () => {
cy.setCodemirrorContent(`{%youtube YE7VzlLtp-4 %}`)
cy.getMarkdownBody().findById('click-shield-youtube')
cy.getMarkdownBody().findByCypressId('click-shield-youtube')
})
})
})

View file

@ -34,7 +34,7 @@ const initLoggedOutTestWithCustomAuthProviders = (
describe('When logged-in, ', () => {
it('sign-in button is hidden', () => {
cy.visit('/')
cy.getById('sign-in-button').should('not.exist')
cy.getByCypressId('sign-in-button').should('not.exist')
})
})
@ -42,7 +42,7 @@ describe('When logged-out ', () => {
describe('and no auth-provider is enabled, ', () => {
it('sign-in button is hidden', () => {
initLoggedOutTestWithCustomAuthProviders(cy, {})
cy.getById('sign-in-button').should('not.exist')
cy.getByCypressId('sign-in-button').should('not.exist')
})
})
@ -51,14 +51,14 @@ describe('When logged-out ', () => {
initLoggedOutTestWithCustomAuthProviders(cy, {
local: true
})
cy.getById('sign-in-button').should('be.visible').should('have.attr', 'href', '/login')
cy.getByCypressId('sign-in-button').should('be.visible').should('have.attr', 'href', '/login')
})
it('sign-in button points to login route: ldap', () => {
initLoggedOutTestWithCustomAuthProviders(cy, {
ldap: true
})
cy.getById('sign-in-button').should('be.visible').should('have.attr', 'href', '/login')
cy.getByCypressId('sign-in-button').should('be.visible').should('have.attr', 'href', '/login')
})
})
@ -67,7 +67,7 @@ describe('When logged-out ', () => {
initLoggedOutTestWithCustomAuthProviders(cy, {
saml: true
})
cy.getById('sign-in-button')
cy.getByCypressId('sign-in-button')
.should('be.visible')
// The absolute URL is used because it is defined as API base URL absolute.
.should('have.attr', 'href', '/mock-backend/auth/saml')
@ -80,7 +80,7 @@ describe('When logged-out ', () => {
saml: true,
github: true
})
cy.getById('sign-in-button').should('be.visible').should('have.attr', 'href', '/login')
cy.getByCypressId('sign-in-button').should('be.visible').should('have.attr', 'href', '/login')
})
})
@ -90,7 +90,7 @@ describe('When logged-out ', () => {
saml: true,
local: true
})
cy.getById('sign-in-button').should('be.visible').should('have.attr', 'href', '/login')
cy.getByCypressId('sign-in-button').should('be.visible').should('have.attr', 'href', '/login')
})
})
})

View file

@ -10,33 +10,33 @@ describe('Split view', () => {
})
it('can show both panes', () => {
cy.getById('view-mode-both').click()
cy.get('.splitter.left').should('be.visible')
cy.get('.splitter.right').should('be.visible')
cy.getByCypressId('view-mode-both').click()
cy.getByCypressId('splitter-left').should('be.visible')
cy.getByCypressId('splitter-right').should('be.visible')
})
it('can show only preview pane', () => {
cy.getById('view-mode-preview').click()
cy.get('.splitter.left').should('be.not.visible')
cy.get('.splitter.right').should('be.visible')
cy.getByCypressId('view-mode-preview').click()
cy.getByCypressId('splitter-left').should('be.not.visible')
cy.getByCypressId('splitter-right').should('be.visible')
})
it('can show only editor pane', () => {
cy.getById('view-mode-editor').click()
cy.get('.splitter.left').should('be.visible')
cy.get('.splitter.right').should('be.not.visible')
cy.getByCypressId('view-mode-editor').click()
cy.getByCypressId('splitter-left').should('be.visible')
cy.getByCypressId('splitter-right').should('be.not.visible')
})
it('can change the split by dragging', () => {
cy.get('.splitter.left').then((leftPanebefore) => {
cy.getByCypressId('splitter-left').then((leftPanebefore) => {
const widthBefore = leftPanebefore.outerWidth()
cy.getById('view-mode-both').click()
cy.get('.split-divider').should('be.visible').trigger('mousedown', { buttons: 1 })
cy.getByCypressId('view-mode-both').click()
cy.getByCypressId('split-divider').should('be.visible').trigger('mousedown', { buttons: 1 })
cy.document().trigger('mousemove', { buttons: 1, pageX: 0, pageY: 0 })
cy.get('.split-divider').trigger('mouseup')
cy.getByCypressId('split-divider').trigger('mouseup')
cy.get('.splitter.left').should('not.eq', widthBefore)
cy.getByCypressId('splitter-left').should('not.eq', widthBefore)
})
})
})

View file

@ -26,17 +26,17 @@ describe('Toolbar Buttons', () => {
})
it('should format as bold', () => {
cy.getById('format-bold').click()
cy.getByCypressId('format-bold').click()
cy.get('.CodeMirror-activeline > .CodeMirror-line > span').should('have.text', `**${testText}**`)
})
it('should format as italic', () => {
cy.getById('format-italic').click()
cy.getByCypressId('format-italic').click()
cy.get('.CodeMirror-activeline > .CodeMirror-line > span').should('have.text', `*${testText}*`)
})
it('should format as underline', () => {
cy.getById('format-underline').click()
cy.getByCypressId('format-underline').click()
cy.get('.CodeMirror-activeline > .CodeMirror-line > span').should('have.text', `++${testText}++`)
})
@ -46,17 +46,17 @@ describe('Toolbar Buttons', () => {
})
it('should format as subscript', () => {
cy.getById('format-subscript').click()
cy.getByCypressId('format-subscript').click()
cy.get('.CodeMirror-activeline > .CodeMirror-line > span').should('have.text', `~${testText}~`)
})
it('should format as superscript', () => {
cy.getById('format-superscript').click()
cy.getByCypressId('format-superscript').click()
cy.get('.CodeMirror-activeline > .CodeMirror-line > span').should('have.text', `^${testText}^`)
})
it('should format the line as code block', () => {
cy.getById('format-code-block').click()
cy.getByCypressId('format-code-block').click()
cy.get('.CodeMirror-code > div:nth-of-type(1) > .CodeMirror-line > span > span').should('have.text', '```')
cy.get('.CodeMirror-code > div:nth-of-type(2) > .CodeMirror-line > span span').should('have.text', testText)
cy.get('.CodeMirror-code > div.CodeMirror-activeline > .CodeMirror-line > span span').should(
@ -66,65 +66,65 @@ describe('Toolbar Buttons', () => {
})
it('should format links', () => {
cy.getById('format-link').click()
cy.getByCypressId('format-link').click()
cy.get('.CodeMirror-activeline > .CodeMirror-line > span').should('have.text', `[${testText}](https://)`)
})
it('should format as image', () => {
cy.getById('format-image').click()
cy.getByCypressId('format-image').click()
cy.get('.CodeMirror-activeline > .CodeMirror-line > span').should('have.text', `![${testText}](https://)`)
})
})
it('should format line as heading', () => {
cy.getById('format-heading').click()
cy.getByCypressId('format-heading').click()
cy.get('.CodeMirror-activeline > .CodeMirror-line > span').should('have.text', `# ${testText}`)
cy.get('.fa-header').click()
cy.get('.CodeMirror-activeline > .CodeMirror-line > span').should('have.text', `## ${testText}`)
})
it('should format the line as code', () => {
cy.getById('format-code-block').click()
cy.getByCypressId('format-code-block').click()
cy.get('.CodeMirror-code > div:nth-of-type(1) > .CodeMirror-line > span > span').should('have.text', '```')
cy.get('.CodeMirror-code > div:nth-of-type(2) > .CodeMirror-line > span span').should('have.text', testText)
cy.get('.CodeMirror-code > div.CodeMirror-activeline > .CodeMirror-line > span span').should('have.text', '```')
})
it('should add a quote', () => {
cy.getById('format-block-quote').click()
cy.getByCypressId('format-block-quote').click()
cy.get('.CodeMirror-activeline > .CodeMirror-line > span').should('have.text', `> ${testText}`)
cy.getById('format-block-quote').click()
cy.getByCypressId('format-block-quote').click()
cy.get('.CodeMirror-activeline > .CodeMirror-line > span').should('have.text', `> > ${testText}`)
})
it('should format as unordered list', () => {
cy.getById('format-unordered-list').click()
cy.getByCypressId('format-unordered-list').click()
cy.get('.CodeMirror-activeline > .CodeMirror-line > span').should('have.text', `- ${testText}`)
cy.getById('format-unordered-list').click()
cy.getByCypressId('format-unordered-list').click()
cy.get('.CodeMirror-activeline > .CodeMirror-line > span').should('have.text', `- - ${testText}`)
})
it('should format as ordered list', () => {
cy.getById('format-ordered-list').click()
cy.getByCypressId('format-ordered-list').click()
cy.get('.CodeMirror-activeline > .CodeMirror-line > span').should('have.text', `1. ${testText}`)
cy.getById('format-ordered-list').click()
cy.getByCypressId('format-ordered-list').click()
cy.get('.CodeMirror-activeline > .CodeMirror-line > span').should('have.text', `1. 1. ${testText}`)
})
it('should format as check list', () => {
cy.getById('format-check-list').click()
cy.getByCypressId('format-check-list').click()
cy.get('.CodeMirror-activeline > .CodeMirror-line > span').should('have.text', `- [ ] ${testText}`)
cy.getById('format-check-list').click()
cy.getByCypressId('format-check-list').click()
cy.get('.CodeMirror-activeline > .CodeMirror-line > span').should('have.text', `- [ ] - [ ] ${testText}`)
})
it('should insert links', () => {
cy.getById('format-link').click()
cy.getByCypressId('format-link').click()
cy.get('.CodeMirror-activeline > .CodeMirror-line > span').should('have.text', `${testText}[](https://)`)
})
it('should insert an empty image link', () => {
cy.getById('format-image').click()
cy.getByCypressId('format-image').click()
cy.get('.CodeMirror-activeline > .CodeMirror-line > span').should('have.text', `${testText}![](https://)`)
})
})
@ -137,30 +137,30 @@ describe('Toolbar Buttons', () => {
})
it('should format as link', () => {
cy.getById('format-link').click()
cy.getByCypressId('format-link').click()
cy.get('.CodeMirror-activeline > .CodeMirror-line > span').should('have.text', `[](${testLink})`)
})
it('should format as image', () => {
cy.getById('format-image').click()
cy.getByCypressId('format-image').click()
cy.get('.CodeMirror-activeline > .CodeMirror-line > span').should('have.text', `![](${testLink})`)
})
})
describe('for no text', () => {
it('should add an empty code block', () => {
cy.getById('format-code-block').click()
cy.getByCypressId('format-code-block').click()
cy.get('.CodeMirror-code > div:nth-of-type(1) > .CodeMirror-line > span > span').should('have.text', '```')
cy.get('.CodeMirror-code > div.CodeMirror-activeline > .CodeMirror-line > span span').should('have.text', '```')
})
it('should insert lines', () => {
cy.getById('format-add-line').click()
cy.getByCypressId('format-add-line').click()
cy.get('.CodeMirror-code > div:nth-of-type(2) > .CodeMirror-line > span span').should('have.text', '----')
})
it('should add a collapsable block', () => {
cy.getById('format-collapsable-block').click()
cy.getByCypressId('format-collapsable-block').click()
cy.get('.CodeMirror-code > div:nth-of-type(2) > .CodeMirror-line > span span').should(
'have.text',
':::spoiler Toggle label'
@ -168,36 +168,34 @@ describe('Toolbar Buttons', () => {
})
it('should add a comment', () => {
cy.getById('format-add-comment').click()
cy.getByCypressId('format-add-comment').click()
cy.get('.CodeMirror-code > div:nth-of-type(2) > .CodeMirror-line > span span').should('have.text', '> []')
})
})
describe('for new tables', () => {
beforeEach(() => {
cy.getById('table-size-picker-popover').should('not.exist')
cy.getById('table-size-picker-button').last().click()
cy.getById('table-size-picker-popover').should('be.visible')
cy.getByCypressId('table-size-picker-popover').should('not.exist')
cy.getByCypressId('table-size-picker-button').last().click()
cy.getByCypressId('table-size-picker-popover').should('be.visible')
})
it('should select table size', () => {
cy.getById('table-size-picker-popover')
.find('.table-container > .table-cell:nth-of-type(25)')
cy.getByCypressId('table-size-picker-popover')
.find('[data-cypress-col=5][data-cypress-row=3]')
.trigger('mouseover')
cy.getById('table-size-picker-popover')
.find('.table-container > .table-cell[data-cypress-selected="true"]')
.should('have.length', 15)
cy.getById('table-size-picker-popover').find('.popover-header').contains('5x3')
cy.getById('table-size-picker-popover').find('.table-container > .table-cell:nth-of-type(25)').click()
cy.getByCypressId('table-size-picker-popover').find('[data-cypress-selected="true"]').should('have.length', 15)
cy.getByCypressId('table-size-picker-popover').find('.popover-header').contains('5x3')
cy.getByCypressId('table-size-picker-popover').find('[data-cypress-col=5][data-cypress-row=3]').click()
})
it('should open a custom table size in the modal', () => {
cy.getById('custom-table-size-modal').should('not.exist')
cy.getById('show-custom-table-modal').first().click()
cy.getById('custom-table-size-modal').should('be.visible')
cy.getById('custom-table-size-modal').find('input').first().type('5')
cy.getById('custom-table-size-modal').find('input').last().type('3')
cy.getById('custom-table-size-modal').find('.modal-footer > button').click()
cy.getByCypressId('custom-table-size-modal').should('not.exist')
cy.getByCypressId('show-custom-table-modal').first().click()
cy.getByCypressId('custom-table-size-modal').should('be.visible')
cy.getByCypressId('custom-table-size-modal').find('input').first().type('5')
cy.getByCypressId('custom-table-size-modal').find('input').last().type('3')
cy.getByCypressId('custom-table-size-modal').find('.modal-footer > button').click()
})
afterEach(() => {
@ -227,7 +225,7 @@ describe('Toolbar Buttons', () => {
describe('for the emoji-picker', () => {
it('should open overlay', () => {
cy.get('emoji-picker').should('not.be.visible')
cy.getById('show-emoji-picker').click()
cy.getByCypressId('show-emoji-picker').click()
cy.get('emoji-picker').should('be.visible')
})
})

View file

@ -12,32 +12,32 @@ describe('Test word count with', () => {
it('empty note', () => {
cy.setCodemirrorContent('')
cy.wait(500)
cy.getById('sidebar-btn-document-info').click()
cy.getById('document-info-modal').should('be.visible')
cy.getById('document-info-word-count').should('have.text', '0')
cy.getByCypressId('sidebar-btn-document-info').click()
cy.getByCypressId('document-info-modal').should('be.visible')
cy.getByCypressId('document-info-word-count').should('have.text', '0')
})
it('simple words', () => {
cy.setCodemirrorContent('five words should be enough')
cy.wait(500)
cy.getById('sidebar-btn-document-info').click()
cy.getById('document-info-modal').should('be.visible')
cy.getById('document-info-word-count').should('have.text', '5')
cy.getByCypressId('sidebar-btn-document-info').click()
cy.getByCypressId('document-info-modal').should('be.visible')
cy.getByCypressId('document-info-word-count').should('have.text', '5')
})
it('excluded codeblocks', () => {
cy.setCodemirrorContent('```\nthis is should be ignored\n```\n\ntwo `words`')
cy.wait(500)
cy.getById('sidebar-btn-document-info').click()
cy.getById('document-info-modal').should('be.visible')
cy.getById('document-info-word-count').should('have.text', '2')
cy.getByCypressId('sidebar-btn-document-info').click()
cy.getByCypressId('document-info-modal').should('be.visible')
cy.getByCypressId('document-info-word-count').should('have.text', '2')
})
it('excluded images', () => {
cy.setCodemirrorContent('![ignored alt text](https://dummyimage.com/48) not ignored text')
cy.wait(500)
cy.getById('sidebar-btn-document-info').click()
cy.getById('document-info-modal').should('be.visible')
cy.getById('document-info-word-count').should('have.text', '3')
cy.getByCypressId('sidebar-btn-document-info').click()
cy.getByCypressId('document-info-modal').should('be.visible')
cy.getByCypressId('document-info-word-count').should('have.text', '3')
})
})

View file

@ -11,16 +11,16 @@ describe('YAML Array for deprecated syntax of document tags in frontmatter', ()
it('is shown when using old syntax', () => {
cy.setCodemirrorContent('---\ntags: a, b, c\n---')
cy.getIframeBody().findById('yamlArrayDeprecationAlert').should('be.visible')
cy.getIframeBody().findByCypressId('yamlArrayDeprecationAlert').should('be.visible')
})
it("isn't shown when using inline yaml-array", () => {
cy.setCodemirrorContent("---\ntags: ['a', 'b', 'c']\n---")
cy.getIframeBody().findById('yamlArrayDeprecationAlert').should('not.exist')
cy.getIframeBody().findByCypressId('yamlArrayDeprecationAlert').should('not.exist')
})
it("isn't shown when using multi line yaml-array", () => {
cy.setCodemirrorContent('---\ntags:\n - a\n - b\n - c\n---')
cy.getIframeBody().findById('yamlArrayDeprecationAlert').should('not.exist')
cy.getIframeBody().findByCypressId('yamlArrayDeprecationAlert').should('not.exist')
})
})

View file

@ -5,19 +5,20 @@
*/
declare namespace Cypress {
interface Chainable {
getById(id: string): Chainable<Element>
findById(id: string): Chainable<Element>
getByCypressId(id: string): Chainable<Element>
findByCypressId(id: string): Chainable<Element>
}
}
const CYPRESS_ATTR = 'data-cypress-id'
Cypress.Commands.add('getById', (id: string) => {
Cypress.Commands.add('getByCypressId', (id: string) => {
return cy.get(`[${CYPRESS_ATTR}="${id}"]`)
})
Cypress.Commands.add(
'findById',
'findByCypressId',
{
prevSubject: 'element'
},

View file

@ -33,9 +33,9 @@ Cypress.Commands.add('getReveal', () => {
})
Cypress.Commands.add('getMarkdownBody', () => {
return cy.getIframeBody(RendererType.DOCUMENT).find('.markdown-body')
return cy.getIframeBody(RendererType.DOCUMENT).findByCypressId('markdown-body')
})
Cypress.Commands.add('getIntroBody', () => {
return cy.getIframeBody(RendererType.INTRO).find('.markdown-body')
return cy.getIframeBody(RendererType.INTRO).findByCypressId('markdown-body')
})

View file

@ -15,6 +15,6 @@ declare namespace Cypress {
}
Cypress.Commands.add('logout', () => {
cy.getById('user-dropdown').click()
cy.getById('user-dropdown-sign-out-button').click()
cy.getByCypressId('user-dropdown').click()
cy.getByCypressId('user-dropdown-sign-out-button').click()
})

View file

@ -1,28 +0,0 @@
/*
* SPDX-FileCopyrightText: 2021 The HedgeDoc developers (see AUTHORS file)
*
* SPDX-License-Identifier: CC0-1.0
*/
const path = require('path')
module.exports = {
entry: './src/index.ts',
module: {
rules: [
{
test: /\.tsx?$/,
use: 'ts-loader',
exclude: /node_modules/
}
]
},
resolve: {
extensions: ['.tsx', '.ts', '.js']
},
output: {
filename: 'bundle.js',
path: path.resolve(__dirname, 'dist')
}
}

View file

@ -4,8 +4,8 @@
* SPDX-License-Identifier: AGPL-3.0-only
*/
@import "../../../node_modules/bootstrap/scss/functions";
@import "../../../node_modules/bootstrap/scss/mixins";
@import "~bootstrap/scss/functions";
@import "~bootstrap/scss/mixins";
@import "reboot";
@import "type";

View file

@ -1,7 +1,7 @@
/*!
* SPDX-FileCopyrightText: 2021 The HedgeDoc developers (see AUTHORS file)
/*
* SPDX-FileCopyrightText: Original: Nicolas Gallagher and Jonathan Neal, Modified by the HedgeDoc developers (see AUTHORS file)
*
* SPDX-License-Identifier: AGPL-3.0-only
* SPDX-License-Identifier: MIT
*/
// stylelint-disable declaration-no-important, selector-no-qualifying-type, property-no-vendor-prefix

View file

@ -1,4 +1,4 @@
/*!
/*
* SPDX-FileCopyrightText: 2021 The HedgeDoc developers (see AUTHORS file)
*
* SPDX-License-Identifier: AGPL-3.0-only

View file

@ -0,0 +1,11 @@
/*
* SPDX-FileCopyrightText: 2021 The HedgeDoc developers (see AUTHORS file)
*
* SPDX-License-Identifier: AGPL-3.0-only
*/
@import '~highlight.js/styles/github';
body.dark {
@import '~highlight.js/styles/github-dark';
}

View file

@ -1,4 +1,4 @@
/*!
/*
* SPDX-FileCopyrightText: 2021 The HedgeDoc developers (see AUTHORS file)
*
* SPDX-License-Identifier: AGPL-3.0-only
@ -7,10 +7,17 @@
@import "variables";
@import "variables.light";
@import "bootstrap-vendor/bootstrap";
@import '../../node_modules/react-bootstrap-typeahead/css/Typeahead';
@import "../../node_modules/@fontsource/source-sans-pro/index";
@import "../../node_modules/twemoji-colr-font/twemoji";
@import '../../node_modules/fork-awesome/css/fork-awesome.min';
@import '~react-bootstrap-typeahead/css/Typeahead';
@import "~@fontsource/source-sans-pro/index.css";
@import "~twemoji-colr-font/twemoji";
@import '~fork-awesome/css/fork-awesome.min.css';
@import '~firacode/distr/fira_code';
@import "typeahead";
@import "./button-inside";
@import "./highlight-js";
@import "./github-markdown";
@import "./markdown-tweaks";
@import "./reveal";
.text-black, body.dark .text-black {
color: $black;
@ -24,7 +31,7 @@ body {
background-color: $dark;
}
#root {
#__next {
height: 100vh;
}

View file

@ -1,11 +1,9 @@
/*!
/*
* SPDX-FileCopyrightText: 2021 The HedgeDoc developers (see AUTHORS file)
*
* SPDX-License-Identifier: AGPL-3.0-only
*/
@import './github-markdown';
.markdown-body {
position: relative;
font-family: 'Source Sans Pro', "Twemoji", sans-serif;
@ -22,8 +20,10 @@
}
}
.alert > p, .alert > ul {
margin-bottom: 0;
.alert {
& > p, & > ul {
margin-bottom: 0;
}
}
// This is necessary since we need to set this for all DOM Element that could be children of .markdown-body and since we support all of HTML that would literally be everything
@ -33,7 +33,7 @@
}
h1, h2, h3, h4, h5, h6 {
a.heading-anchor {
.heading-anchor {
font-size: 0.75em;
margin-top: 0.25em;
opacity: 0.3;
@ -64,5 +64,4 @@
word-break: break-word;
}
}
}

View file

@ -4,9 +4,10 @@
* SPDX-License-Identifier: AGPL-3.0-only
*/
@import '~reveal.js/dist/reveal.css';
// Default mixins and settings -----------------
@import "../../../node_modules/reveal.js/css/theme/template/mixins";
@import "../../../node_modules/reveal.js/css/theme/template/settings";
@import "~reveal.js/css/theme/template/mixins";
@import "~reveal.js/css/theme/template/settings";
// ---------------------------------------------
@ -37,5 +38,5 @@ $heading4Size: 1.0em;
// Theme template ------------------------------
@import "../../../node_modules/reveal.js/css/theme/template/theme";
@import "~reveal.js/css/theme/template/theme";
// ---------------------------------------------

View file

@ -125,6 +125,6 @@ $close-text-shadow: none;
// Code
$pre-color: $dark;
@import "../../node_modules/bootstrap/scss/functions";
@import "../../node_modules/bootstrap/scss/mixins";
@import "../../node_modules/bootstrap/scss/variables";
@import "~bootstrap/scss/functions";
@import "~bootstrap/scss/mixins";
@import "~bootstrap/scss/variables";

View file

@ -1,4 +1,4 @@
/*!
/*
* SPDX-FileCopyrightText: 2021 The HedgeDoc developers (see AUTHORS file)
*
* SPDX-License-Identifier: AGPL-3.0-only
@ -8,9 +8,9 @@ $blue: #337ab7 !default;
$cyan: #5EB7E0 !default;
$dark: #222222 !default;
@import "../../node_modules/bootstrap/scss/functions";
@import "../../node_modules/bootstrap/scss/mixins";
@import "../../node_modules/bootstrap/scss/variables";
@import "~bootstrap/scss/functions";
@import "~bootstrap/scss/mixins";
@import "~bootstrap/scss/variables";
$toast-background-color: $white;

View file

@ -1,4 +1,4 @@
/*!
/*
* SPDX-FileCopyrightText: 2021 The HedgeDoc developers (see AUTHORS file)
*
* SPDX-License-Identifier: AGPL-3.0-only

24
jest.config.ts Normal file
View file

@ -0,0 +1,24 @@
/*
* SPDX-FileCopyrightText: 2021 The HedgeDoc developers (see AUTHORS file)
*
* SPDX-License-Identifier: AGPL-3.0-only
*/
import nextJest from 'next/jest'
const createJestConfig = nextJest({
// Provide the path to your Next.js app to load next.config.js and .env files in your test environment
dir: './',
})
// Add any custom config to be passed to Jest
const customJestConfig = {
setupFilesAfterEnv: ['<rootDir>/jest.setup.ts'],
moduleNameMapper: {
// Handle module aliases (this will be automatically configured for you soon)
'^@/components/(.*)$': '<rootDir>/components/$1',
},
testPathIgnorePatterns: ["/node_modules/", "/cypress/"]
}
// createJestConfig is exported this way to ensure that next/jest can load the Next.js config which is async
module.exports = createJestConfig(customJestConfig)

11
jest.setup.ts Normal file
View file

@ -0,0 +1,11 @@
/*
* SPDX-FileCopyrightText: 2021 The HedgeDoc developers (see AUTHORS file)
*
* SPDX-License-Identifier: AGPL-3.0-only
*/
// Optional: configure or set up a testing framework before each test.
// If you delete this file, remove `setupFilesAfterEnv` from `jest.config.js`
// Used for __tests__/testing-library.js
// Learn more: https://github.com/testing-library/jest-dom
import '@testing-library/jest-dom/extend-expect'

5
next-env.d.ts vendored Normal file
View file

@ -0,0 +1,5 @@
/// <reference types="next" />
/// <reference types="next/image-types/global" />
// NOTE: This file should not be edited
// see https://nextjs.org/docs/basic-features/typescript for more information.

View file

@ -1,3 +1,3 @@
SPDX-FileCopyrightText: 2021 The HedgeDoc developers (see AUTHORS file)
SPDX-License-Identifier: LicenseRef-HedgeDoc-Icon-Usage-Guidelines
SPDX-License-Identifier: CC0-1.0

86
next.config.js Normal file
View file

@ -0,0 +1,86 @@
/*
* SPDX-FileCopyrightText: 2021 The HedgeDoc developers (see AUTHORS file)
*
* SPDX-License-Identifier: AGPL-3.0-only
*/
console.log('Node env is', process.env.NODE_ENV)
if (process.env.NEXT_PUBLIC_USE_MOCK_API === 'true') {
console.log('Uses mock API')
} else if (!!process.env.NEXT_PUBLIC_BACKEND_BASE_URL) {
console.log('Backend base url is', process.env.NEXT_PUBLIC_BACKEND_BASE_URL)
} else {
console.error(`==============
Neither NEXT_PUBLIC_USE_MOCK_API or NEXT_PUBLIC_BACKEND_BASE_URL is set.
If you want to create a production build we suggest that you set a backend url with NEXT_PUBLIC_BACKEND_BASE_URL.
If you want to create a build that uses the mock api then use build:mock instead or set NEXT_PUBLIC_USE_MOCK_API to "true".
==============`)
process.exit(1)
}
if (!!process.env.NEXT_PUBLIC_TEST_MODE) {
console.log('Built in test mode')
}
const path = require('path')
const CopyWebpackPlugin = require('copy-webpack-plugin')
const withBundleAnalyzer = require('@next/bundle-analyzer')({
enabled: process.env.ANALYZE === 'true'
})
/** @type {import('next').NextConfig} */
const rawNextConfig = {
webpack: (config) => {
config.module.rules.push({
test: /\.svg$/i,
issuer: /\.[jt]sx?$/,
use: [
{
loader: '@svgr/webpack',
options: {
svgoConfig: {
plugins: [
{
name: 'preset-default',
params: {
overrides: {
removeViewBox: false
}
}
}
]
}
}
}
]
})
config.plugins.push(
new CopyWebpackPlugin({
patterns: [
{ from: path.join(__dirname, 'node_modules/@hpcc-js/wasm/dist/graphvizlib.wasm'), to: 'static/js' },
{ from: path.join(__dirname, 'node_modules/@hpcc-js/wasm/dist/expatlib.wasm'), to: 'static/js' },
{
from: path.join(__dirname, 'node_modules/emoji-picker-element-data/en/emojibase/data.json'),
to: 'static/js/emoji-data.json'
}
]
})
)
return config
},
reactStrictMode: true,
redirects: () => {
return Promise.resolve([
{
source: '/',
destination: '/intro',
permanent: true
}
])
}
}
const completeNextConfig = withBundleAnalyzer(rawNextConfig)
module.exports = completeNextConfig

View file

@ -1,69 +1,26 @@
{
"name": "react_client",
"name": "@hedgedoc/react-client",
"version": "0.0.1",
"license": "AGPL-3.0",
"version": "0.1.0",
"scripts": {
"start": "cross-env PORT=3001 craco start",
"start:test": "cross-env REACT_APP_TEST_MODE=true yarn start",
"start:for-real-backend": "cross-env REACT_APP_BACKEND_BASE_URL=http://localhost:3000/ yarn start",
"serve:build": "http-server build/ -s -p 3001 -P \"http://localhost:3001?\"",
"build:test": "cross-env REACT_APP_TEST_MODE=true craco build",
"build:mock": "cross-env craco build",
"build:production": "(cross-env node check-build-env-vars.js) && (cross-env craco build)",
"analyze": "cross-env ANALYZE=true yarn build:mock",
"test": "craco test",
"lint": "eslint --max-warnings=0 --ext .ts,.tsx src",
"lint:fix": "eslint --fix --ext .ts,.tsx src",
"build": "next build",
"build:mock": "cross-env NEXT_PUBLIC_USE_MOCK_API=true next build",
"build:test": "cross-env NEXT_PUBLIC_USE_MOCK_API=true NEXT_PUBLIC_TEST_MODE=true next build",
"analyze": "cross-env ANALYZE=true next build",
"dev": "cross-env PORT=3001 next dev",
"dev:test": "cross-env PORT=3001 NEXT_PUBLIC_TEST_MODE=true next dev",
"dev:for-real-backend": "cross-env PORT=3001 NEXT_PUBLIC_USE_MOCK_API=false NEXT_PUBLIC_BACKEND_BASE_URL=http://localhost:3000/ next dev",
"format": "prettier -c \"src/**/*.{ts,tsx,js}\" \"cypress/**/*.{ts,tsx}\"",
"format:fix": "prettier -w \"src/**/*.{ts,tsx,js}\" \"cypress/**/*.{ts,tsx}\"",
"eject": "react-scripts eject",
"lint": "eslint --max-warnings=0 --ext .ts,.tsx src",
"lint:fix": "eslint --fix --ext .ts,.tsx src",
"start": "cross-env PORT=3001 next start",
"start:ci": "cross-env NEXT_PUBLIC_USE_MOCK_API=true NEXT_PUBLIC_TEST_MODE=true PORT=3001 next start",
"cy:open": "cypress open",
"cy:run:chrome": "cypress run --browser chrome",
"cy:run:firefox": "cypress run --browser firefox"
},
"eslintConfig": {
"parserOptions": {
"tsconfigRootDir": "",
"project": [
"./tsconfig.json"
]
},
"rules": {
"no-use-before-define": "off",
"no-debugger": "warn",
"default-param-last": "off",
"@typescript-eslint/consistent-type-imports": [
"error",
{
"prefer": "type-imports",
"disallowTypeAnnotations": false
}
]
},
"plugins": [
"@typescript-eslint"
],
"extends": [
"react-app",
"eslint:recommended",
"plugin:@typescript-eslint/eslint-recommended",
"plugin:@typescript-eslint/recommended",
"plugin:@typescript-eslint/recommended-requiring-type-checking",
"plugin:import/recommended",
"plugin:import/typescript",
"prettier"
]
},
"prettier": {
"parser": "typescript",
"singleQuote": true,
"jsxSingleQuote": true,
"semi": false,
"tabWidth": 2,
"trailingComma": "none",
"bracketSpacing": true,
"bracketSameLine": true,
"arrowParens": "always"
"cy:run:firefox": "cypress run --browser firefox",
"test": "jest --watch",
"test:ci": "jest --ci"
},
"browserslist": {
"production": [
@ -77,71 +34,30 @@
"last 1 safari version"
]
},
"dependencies": {},
"devDependencies": {
"@craco/craco": "6.4.3",
"@cypress/webpack-preprocessor": "5.11.0",
"dependencies": {
"@fontsource/source-sans-pro": "4.5.1",
"@hedgedoc/html-to-react": "1.1.1",
"@hedgedoc/markdown-it-image-size": "1.0.3",
"@hedgedoc/markdown-it-task-lists": "1.0.3",
"@matejmazur/react-katex": "3.1.3",
"@redux-devtools/core": "3.9.1",
"@testing-library/cypress": "8.0.2",
"@testing-library/jest-dom": "5.16.1",
"@testing-library/react": "12.1.2",
"@testing-library/user-event": "13.5.0",
"@types/codemirror": "5.60.5",
"@types/d3-graphviz": "2.6.7",
"@types/diff": "5.0.2",
"@types/dompurify": "2.3.2",
"@types/jest": "27.0.3",
"@types/js-yaml": "4.0.5",
"@types/luxon": "2.0.8",
"@types/markdown-it": "12.2.3",
"@types/markdown-it-container": "2.0.4",
"@types/markdown-it-plantuml": "1.4.1",
"@types/mermaid": "8.2.7",
"@types/node": "16.11.17",
"@types/react": "17.0.38",
"@types/react-bootstrap-typeahead": "5.1.8",
"@types/react-dom": "17.0.11",
"@types/react-router": "5.1.17",
"@types/react-router-bootstrap": "0.24.5",
"@types/react-router-dom": "5.3.2",
"@types/redux-devtools": "3.0.47",
"@types/sass": "1.43.1",
"@types/uuid": "8.3.3",
"@typescript-eslint/eslint-plugin": "5.8.0",
"@typescript-eslint/parser": "5.8.0",
"@svgr/webpack": "6.1.2",
"abcjs": "6.0.0-beta.35",
"bootstrap": "4.6.1",
"codemirror": "5.65.0",
"copy-webpack-plugin": "6.4.1",
"copy-webpack-plugin": "10.2.0",
"cross-env": "7.0.3",
"cypress": "9.2.0",
"cypress-commands": "2.0.1",
"cypress-file-upload": "6.0.0-beta.0",
"d3-graphviz": "3.2.0",
"diff": "5.0.0",
"dompurify": "2.3.4",
"emoji-picker-element": "1.10.0",
"emoji-picker-element-data": "1.3.0",
"eslint-config-prettier": "8.3.0",
"eslint-config-react-app": "6.0.0",
"eslint-plugin-chai-friendly": "0.7.2",
"eslint-plugin-cypress": "2.12.1",
"eslint-plugin-import": "2.25.3",
"eslint-plugin-jsx-a11y": "6.5.1",
"eslint-plugin-node": "11.1.0",
"eslint-plugin-promise": "5.2.0",
"fast-deep-equal": "3.1.3",
"firacode": "6.2.0",
"flowchart.js": "1.17.0",
"fork-awesome": "1.2.0",
"highlight.js": "11.3.1",
"http-server": "14.0.0",
"i18next": "21.6.3",
"i18next": "21.6.4",
"i18next-browser-languagedetector": "6.1.2",
"i18next-resources-to-backend": "1.0.0",
"js-yaml": "4.1.0",
@ -166,38 +82,75 @@
"markmap-common": "0.1.6",
"markmap-lib": "0.12.0",
"markmap-view": "0.2.7",
"mermaid": "8.13.7",
"mermaid": "8.13.8",
"next": "12.0.7",
"optional-js": "2.3.0",
"prettier": "2.5.1",
"react": "17.0.2",
"react-bootstrap": "1.6.4",
"react-bootstrap-typeahead": "5.2.1",
"react-codemirror2": "7.2.1",
"react-diff-viewer": "3.1.1",
"react-dom": "17.0.2",
"react-i18next": "11.15.1",
"react-i18next": "11.15.2",
"react-redux": "7.2.6",
"react-router": "5.2.1",
"react-router-bootstrap": "0.25.0",
"react-router-dom": "5.3.0",
"react-router-use-location-state": "2.5.0",
"react-scripts": "4.0.3",
"react-use": "17.3.1",
"redux": "4.1.2",
"redux-devtools-extension": "2.13.9",
"reveal.js": "4.2.1",
"sanitize-filename": "1.6.3",
"sass": "1.45.1",
"ts-loader": "9.2.6",
"ts-mockery": "1.2.0",
"sharp": "0.29.3",
"twemoji-colr-font": "0.0.4",
"typescript": "4.5.4",
"use-resize-observer": "8.0.0",
"uuid": "8.3.2",
"vega": "5.21.0",
"vega-embed": "6.20.5",
"vega-lite": "5.2.0",
"webpack-bundle-analyzer": "4.5.0",
"words-count": "2.0.2"
},
"devDependencies": {
"@next/bundle-analyzer": "12.0.7",
"@testing-library/cypress": "8.0.2",
"@testing-library/jest-dom": "5.16.1",
"@testing-library/react": "12.1.2",
"@testing-library/user-event": "13.5.0",
"@types/codemirror": "5.60.5",
"@types/d3-graphviz": "2.6.7",
"@types/diff": "5.0.2",
"@types/dompurify": "2.3.2",
"@types/js-yaml": "4.0.5",
"@types/luxon": "2.0.8",
"@types/markdown-it": "12.2.3",
"@types/markdown-it-container": "2.0.4",
"@types/markdown-it-plantuml": "1.4.1",
"@types/mermaid": "8.2.7",
"@types/node": "16.11.17",
"@types/react": "17.0.38",
"@types/react-bootstrap-typeahead": "5.1.8",
"@types/react-dom": "17.0.11",
"@types/sass": "1.43.1",
"@types/uuid": "8.3.3",
"@typescript-eslint/eslint-plugin": "5.8.1",
"@typescript-eslint/parser": "5.8.1",
"cypress": "9.2.0",
"cypress-commands": "2.0.1",
"cypress-file-upload": "6.0.0-beta.0",
"eslint": "8.5.0",
"eslint-config-next": "12.0.7",
"eslint-config-prettier": "8.3.0",
"eslint-plugin-chai-friendly": "0.7.2",
"eslint-plugin-cypress": "2.12.1",
"eslint-plugin-import": "2.25.3",
"eslint-plugin-jsx-a11y": "6.5.1",
"eslint-plugin-node": "11.1.0",
"eslint-plugin-promise": "5.2.0",
"eslint-plugin-testing-library": "5.0.1",
"jest": "27.4.5",
"prettier": "2.5.1",
"react-test-renderer": "17.0.2",
"redux-devtools-extension": "2.13.9",
"ts-loader": "9.2.6",
"ts-mockery": "1.2.0",
"ts-node": "10.4.0",
"typescript": "4.5.4"
}
}

View file

@ -1,3 +1,3 @@
SPDX-FileCopyrightText: 2021 The HedgeDoc developers (see AUTHORS file)
SPDX-License-Identifier: AGPL-3.0-only
SPDX-License-Identifier: CC0-1.0

View file

@ -0,0 +1,94 @@
/*
* SPDX-FileCopyrightText: 2021 The HedgeDoc developers (see AUTHORS file)
*
* SPDX-License-Identifier: AGPL-3.0-only
*/
@keyframes animation-roll {
0% {
transform: translateX(calc(-100vw / 2 - 100%)) rotateZ(0deg);
}
100% {
transform: translateX(calc(100vw / 2 + 100%)) rotateZ(720deg);
}
}
@keyframes animation-jump {
0% {
transform: scale(1, 1) translateY(0);
}
10% {
transform: scale(1.1, .9) translateY(0);
}
30% {
transform: scale(.9, 1.1) translateY(-100px);
}
50% {
transform: scale(1.05, .95) translateY(0);
}
57% {
transform: scale(1, 1) translateY(-7px);
}
64% {
transform: scale(1, 1) translateY(0);
}
100% {
transform: scale(1, 1) translateY(0);
}
}
@keyframes animation-shake {
0% {
transform: translate(1px, 1px) rotate(0deg);
}
10% {
transform: translate(-1px, -2px) rotate(-1deg);
}
20% {
transform: translate(-3px, 0px) rotate(1deg);
}
30% {
transform: translate(3px, 2px) rotate(0deg);
}
40% {
transform: translate(1px, -1px) rotate(1deg);
}
50% {
transform: translate(-1px, 2px) rotate(-1deg);
}
60% {
transform: translate(-3px, 1px) rotate(0deg);
}
70% {
transform: translate(3px, 1px) rotate(-1deg);
}
80% {
transform: translate(-1px, -1px) rotate(1deg);
}
90% {
transform: translate(1px, 2px) rotate(0deg);
}
100% {
transform: translate(1px, -2px) rotate(-1deg);
}
}
.animation-roll {
transform-origin: center center;
animation-duration: 4s;
animation-iteration-count: infinite;
animation-name: animation-roll;
animation-timing-function: linear;
}
.animation-jump {
transform-origin: bottom;
animation-duration: 2s;
animation-iteration-count: infinite;
animation-name: animation-jump;
animation-timing-function: cubic-bezier(0.280, 0.840, 0.420, 1);
}
.animation-shake {
animation: animation-shake 0.3s ease-in-out;
}

View file

@ -3,13 +3,8 @@
*
* SPDX-License-Identifier: AGPL-3.0-only
*/
.header-nav {
.nav-link {
border-bottom: 2px solid transparent
}
.nav-link.active {
border-bottom-color: #fff;
export class ApplicationLoaderError extends Error {
constructor(taskName: string) {
super(`Task ${taskName} failed`)
}
}

View file

@ -0,0 +1,19 @@
/*
* SPDX-FileCopyrightText: 2021 The HedgeDoc developers (see AUTHORS file)
*
* SPDX-License-Identifier: AGPL-3.0-only
*/
.loader {
@import "./animations.scss";
height: 100%;
width: 100%;
&.middle, .middle {
display: flex;
align-items: center;
justify-content: center;
flex-direction: column;
}
}

View file

@ -1,110 +0,0 @@
/*
* SPDX-FileCopyrightText: 2021 The HedgeDoc developers (see AUTHORS file)
*
* SPDX-License-Identifier: AGPL-3.0-only
*/
.loader {
@keyframes animation-roll {
0% {
transform: translateX(calc(-100vw / 2 - 100%)) rotateZ(0deg);
}
100% {
transform: translateX(calc(100vw / 2 + 100%)) rotateZ(720deg);
}
}
@keyframes animation-jump {
0% {
transform: scale(1, 1) translateY(0);
}
10% {
transform: scale(1.1, .9) translateY(0);
}
30% {
transform: scale(.9, 1.1) translateY(-100px);
}
50% {
transform: scale(1.05, .95) translateY(0);
}
57% {
transform: scale(1, 1) translateY(-7px);
}
64% {
transform: scale(1, 1) translateY(0);
}
100% {
transform: scale(1, 1) translateY(0);
}
}
@keyframes animation-shake {
0% {
transform: translate(1px, 1px) rotate(0deg);
}
10% {
transform: translate(-1px, -2px) rotate(-1deg);
}
20% {
transform: translate(-3px, 0px) rotate(1deg);
}
30% {
transform: translate(3px, 2px) rotate(0deg);
}
40% {
transform: translate(1px, -1px) rotate(1deg);
}
50% {
transform: translate(-1px, 2px) rotate(-1deg);
}
60% {
transform: translate(-3px, 1px) rotate(0deg);
}
70% {
transform: translate(3px, 1px) rotate(-1deg);
}
80% {
transform: translate(-1px, -1px) rotate(1deg);
}
90% {
transform: translate(1px, 2px) rotate(0deg);
}
100% {
transform: translate(1px, -2px) rotate(-1deg);
}
}
.animation-roll {
transform-origin: center center;
animation-duration: 4s;
animation-iteration-count: infinite;
animation-name: animation-roll;
animation-timing-function: linear;
}
.animation-jump {
transform-origin: bottom;
animation-duration: 2s;
animation-iteration-count: infinite;
animation-name: animation-jump;
animation-timing-function: cubic-bezier(0.280, 0.840, 0.420, 1);
}
.animation-shake {
animation: animation-shake 0.3s ease-in-out;
}
height: 100vh;
width: 100vw;
&.middle, .middle {
display: flex;
align-items: center;
justify-content: center;
flex-direction: column;
}
.progress {
width: 50%;
}
}

Some files were not shown because too many files have changed in this diff Show more