├── .babelrc ├── .dockerignore ├── .editorconfig ├── .env.example ├── .eslintignore ├── .eslintrc.js ├── .github ├── ISSUE_TEMPLATE │ ├── bug_report.md │ ├── enhancement_request.md │ └── feature_request.md ├── actions │ └── setup-node │ │ └── action.yml └── workflows │ └── release.yml ├── .gitignore ├── .mailmap ├── .remarkrc ├── AUTHORS ├── CHANGELOG.md ├── CODE-OF-CONDUCT.md ├── CONTRIBUTING.md ├── Dockerfile ├── LICENSE ├── NEXTCLOUDCONFIG.md ├── README.md ├── app.js ├── app.json ├── bin ├── cleanup ├── heroku ├── manage_users ├── migrate_from_fs_to_minio └── setup ├── docker-compose.yml ├── docs ├── content │ ├── configuration.md │ ├── dev │ │ ├── api.md │ │ ├── documentation.md │ │ ├── getting-started.md │ │ ├── openapi.yml │ │ ├── ot.md │ │ └── webpack.md │ ├── faq.md │ ├── faq │ │ ├── interface_broken.png │ │ └── interface_okay.png │ ├── guides │ │ ├── auth │ │ │ ├── authelia.md │ │ │ ├── github.md │ │ │ ├── gitlab-self-hosted.md │ │ │ ├── keycloak.md │ │ │ ├── ldap-ad.md │ │ │ ├── mattermost-self-hosted.md │ │ │ ├── nextcloud.md │ │ │ ├── oauth.md │ │ │ ├── saml-keycloak.md │ │ │ ├── saml-onelogin.md │ │ │ ├── saml.md │ │ │ └── twitter.md │ │ ├── migrate-etherpad.md │ │ ├── migration-troubleshooting.md │ │ ├── minio-image-upload.md │ │ ├── providing-terms.md │ │ ├── reverse-proxy.md │ │ └── s3-image-upload.md │ ├── images │ │ ├── auth │ │ │ ├── application-page.png │ │ │ ├── create-oauth-app.png │ │ │ ├── create-twitter-app.png │ │ │ ├── gitlab-application-details.png │ │ │ ├── gitlab-new-application.png │ │ │ ├── gitlab-sign-in.png │ │ │ ├── keycloak_add_client.png │ │ │ ├── keycloak_client_overview.png │ │ │ ├── keycloak_clients_overview.png │ │ │ ├── keycloak_force_idformat.png │ │ │ ├── keycloak_idp_cert.png │ │ │ ├── keycloak_mapper_email.png │ │ │ ├── keycloak_mapper_overview.png │ │ │ ├── keycloak_mapper_username.png │ │ │ ├── keycloak_saml_export_cert.png │ │ │ ├── keycloak_saml_export_cert_details.png │ │ │ ├── keycloak_saml_import_cert.png │ │ │ ├── keycloak_saml_import_cert_details.png │ │ │ ├── mattermost-enable-oauth2.png │ │ │ ├── mattermost-oauth-app-add.png │ │ │ ├── mattermost-oauth-app-done.png │ │ │ ├── mattermost-oauth-app-form.png │ │ │ ├── nextcloud-oauth2-1-settings.png │ │ │ ├── nextcloud-oauth2-2-client-add.png │ │ │ ├── nextcloud-oauth2-3-clientid-secret.png │ │ │ ├── onelogin-add-app.png │ │ │ ├── onelogin-copy-idp-metadata.png │ │ │ ├── onelogin-edit-app-name.png │ │ │ ├── onelogin-edit-sp-metadata.png │ │ │ ├── onelogin-select-template.png │ │ │ ├── onelogin-use-dashboard.png │ │ │ ├── register-oauth-application-form.png │ │ │ ├── register-twitter-application.png │ │ │ ├── twitter-app-confirmation.png │ │ │ └── twitter-app-keys.png │ │ ├── favicon.png │ │ ├── hedgedoc_logo_black.svg │ │ ├── hedgedoc_logo_white.svg │ │ ├── logo.svg │ │ ├── minio-image-upload │ │ │ ├── create-bucket.png │ │ │ ├── create-policy.png │ │ │ ├── default-view.png │ │ │ ├── docker-logs.png │ │ │ └── open-edit-policy.png │ │ └── s3-image-upload │ │ │ ├── bucket-policy-editor.png │ │ │ ├── bucket-property.png │ │ │ ├── create-bucket.png │ │ │ ├── custom-policy.png │ │ │ ├── iam-user.png │ │ │ └── review-policy.png │ ├── index.md │ ├── legal │ │ └── developer-certificate-of-origin.txt │ ├── references │ │ ├── hfm.md │ │ ├── slide-options.md │ │ └── url-scheme.md │ ├── setup │ │ ├── community.md │ │ ├── docker.md │ │ ├── getting-started.md │ │ └── manual-setup.md │ └── theme │ │ └── styles │ │ ├── Roboto │ │ ├── roboto-latin-regular.woff │ │ ├── roboto-latin-regular.woff2 │ │ ├── roboto-mono-latin-regular.woff │ │ └── roboto-mono-latin-regular.woff2 │ │ ├── hedgedoc-custom.css │ │ └── roboto.css ├── mkdocs.yml └── requirements.txt ├── lib ├── config │ ├── buildDomainOriginWithProtocol.js │ ├── default.js │ ├── defaultSSL.js │ ├── dockerSecret.js │ ├── enum.js │ ├── environment.js │ ├── hackmdEnvironment.js │ ├── index.js │ ├── oldDefault.js │ ├── oldEnvironment.js │ └── utils.js ├── csp.js ├── errors.js ├── history.js ├── letter-avatars.js ├── logger.js ├── migrations │ ├── 20150504155329-create-users.js │ ├── 20150508114741-create-notes.js │ ├── 20150515125813-create-temp.js │ ├── 20150702001020-update-to-0_3_1.js │ ├── 20150915153700-change-notes-title-to-text.js │ ├── 20160112220142-note-add-lastchange.js │ ├── 20160420180355-note-add-alias.js │ ├── 20160515114000-user-add-tokens.js │ ├── 20160607060246-support-revision.js │ ├── 20160703062241-support-authorship.js │ ├── 20161009040430-support-delete-note.js │ ├── 20161201050312-support-email-signin.js │ ├── 20171009121200-longtext-for-mysql.js │ ├── 20180209120907-longtext-of-authorship.js │ ├── 20180306150303-fix-enum.js │ ├── 20180326103000-use-text-in-tokens.js │ ├── 20180525153000-user-add-delete-token.js │ ├── 20200321153000-fix-account-deletion.js │ ├── 20220901102800-convert-history-to-longtext.js │ └── 20230207060246-support-templates.js ├── models │ ├── author.js │ ├── index.js │ ├── note.js │ ├── revision.js │ ├── temp.js │ └── user.js ├── ot │ ├── client.js │ ├── editor-socketio-server.js │ ├── index.js │ ├── selection.js │ ├── server.js │ ├── simple-text-operation.js │ ├── text-operation.js │ └── wrapped-operation.js ├── realtime.js ├── response.js ├── utils.js ├── web │ ├── auth │ │ ├── email │ │ │ └── index.js │ │ ├── index.js │ │ ├── oauth2 │ │ │ └── index.js │ │ └── utils.js │ ├── baseRouter.js │ ├── historyRouter.js │ ├── imageRouter │ │ ├── filesystem.js │ │ ├── index.js │ │ ├── minio.js │ │ └── nextcloud.js │ ├── middleware │ │ ├── checkURIValid.js │ │ ├── hedgeDocVersion.js │ │ ├── redirectWithoutTrailingSlashes.js │ │ └── tooBusy.js │ ├── note │ │ ├── actions.js │ │ ├── controller.js │ │ ├── router.js │ │ ├── slide.js │ │ └── util.js │ ├── statusRouter.js │ ├── userRouter.js │ └── utils.js └── workers │ └── dmpWorker.js ├── locales ├── _supported.json ├── ar.json ├── bg.json ├── ca.json ├── cs.json ├── da.json ├── de.json ├── el.json ├── en.json ├── eo.json ├── es.json ├── eu.json ├── fa.json ├── fr.json ├── gl.json ├── he.json ├── hi.json ├── hr.json ├── hu.json ├── id.json ├── it.json ├── ja.json ├── ko.json ├── lt.json ├── ml.json ├── nl.json ├── oc.json ├── pl.json ├── pt-br.json ├── pt.json ├── ro.json ├── ru.json ├── sk.json ├── sl.json ├── sr.json ├── sv.json ├── tr.json ├── uk.json ├── vi.json ├── zh-CN.json └── zh-TW.json ├── package.json ├── public ├── .eslintrc.js ├── banner │ ├── banner_h_bw.svg │ ├── banner_h_wb.svg │ └── banner_vertical_color.svg ├── css │ ├── bootstrap-social.css │ ├── center.css │ ├── cover.css │ ├── extra.css │ ├── font.css │ ├── github-extract.css │ ├── index.css │ ├── markdown.css │ ├── mermaid.css │ ├── mod.css │ ├── site.css │ ├── slide-preview.css │ ├── slide.css │ └── ui │ │ └── toolbar.css ├── default.md ├── docs │ ├── features.md │ ├── privacy.md.example │ ├── release-notes.md │ ├── slide-example.md │ └── yaml-metadata.md ├── fonts │ ├── SourceCodePro-Black.eot │ ├── SourceCodePro-Black.ttf │ ├── SourceCodePro-Black.woff │ ├── SourceCodePro-Bold.eot │ ├── SourceCodePro-Bold.ttf │ ├── SourceCodePro-Bold.woff │ ├── SourceCodePro-ExtraLight.eot │ ├── SourceCodePro-ExtraLight.ttf │ ├── SourceCodePro-ExtraLight.woff │ ├── SourceCodePro-Light.eot │ ├── SourceCodePro-Light.ttf │ ├── SourceCodePro-Light.woff │ ├── SourceCodePro-Medium.eot │ ├── SourceCodePro-Medium.ttf │ ├── SourceCodePro-Medium.woff │ ├── SourceCodePro-Regular.eot │ ├── SourceCodePro-Regular.ttf │ ├── SourceCodePro-Regular.woff │ ├── SourceCodePro-Semibold.eot │ ├── SourceCodePro-Semibold.ttf │ ├── SourceCodePro-Semibold.woff │ ├── SourceSansPro-Black.eot │ ├── SourceSansPro-Black.ttf │ ├── SourceSansPro-Black.woff │ ├── SourceSansPro-BlackItalic.eot │ ├── SourceSansPro-BlackItalic.ttf │ ├── SourceSansPro-BlackItalic.woff │ ├── SourceSansPro-Bold.eot │ ├── SourceSansPro-Bold.ttf │ ├── SourceSansPro-Bold.woff │ ├── SourceSansPro-BoldItalic.eot │ ├── SourceSansPro-BoldItalic.ttf │ ├── SourceSansPro-BoldItalic.woff │ ├── SourceSansPro-ExtraLight.eot │ ├── SourceSansPro-ExtraLight.ttf │ ├── SourceSansPro-ExtraLight.woff │ ├── SourceSansPro-ExtraLightItalic.eot │ ├── SourceSansPro-ExtraLightItalic.ttf │ ├── SourceSansPro-ExtraLightItalic.woff │ ├── SourceSansPro-Italic.eot │ ├── SourceSansPro-Italic.ttf │ ├── SourceSansPro-Italic.woff │ ├── SourceSansPro-Light.eot │ ├── SourceSansPro-Light.ttf │ ├── SourceSansPro-Light.woff │ ├── SourceSansPro-LightItalic.eot │ ├── SourceSansPro-LightItalic.ttf │ ├── SourceSansPro-LightItalic.woff │ ├── SourceSansPro-Regular.eot │ ├── SourceSansPro-Regular.ttf │ ├── SourceSansPro-Regular.woff │ ├── SourceSansPro-Semibold.eot │ ├── SourceSansPro-Semibold.ttf │ ├── SourceSansPro-Semibold.woff │ ├── SourceSansPro-SemiboldItalic.eot │ ├── SourceSansPro-SemiboldItalic.ttf │ ├── SourceSansPro-SemiboldItalic.woff │ ├── SourceSerifPro-Bold.eot │ ├── SourceSerifPro-Bold.ttf │ ├── SourceSerifPro-Bold.woff │ ├── SourceSerifPro-Regular.eot │ ├── SourceSerifPro-Regular.ttf │ ├── SourceSerifPro-Regular.woff │ ├── SourceSerifPro-Semibold.eot │ ├── SourceSerifPro-Semibold.ttf │ ├── SourceSerifPro-Semibold.woff │ ├── nunito-v25-latin-regular.eot │ ├── nunito-v25-latin-regular.svg │ ├── nunito-v25-latin-regular.ttf │ ├── nunito-v25-latin-regular.woff │ └── nunito-v25-latin-regular.woff2 ├── icons │ ├── android-chrome-192x192.png │ ├── android-chrome-512x512.png │ ├── apple-touch-icon.png │ ├── browserconfig.xml │ ├── favicon-16x16.png │ ├── favicon-32x32.png │ ├── favicon.ico │ ├── logo.png │ ├── mstile-144x144.png │ ├── mstile-150x150.png │ ├── mstile-310x150.png │ ├── mstile-310x310.png │ ├── mstile-70x70.png │ └── site.webmanifest ├── js │ ├── cover.js │ ├── extra.js │ ├── fix-aria-hidden-for-modals.js │ ├── history.js │ ├── htmlExport.js │ ├── index.js │ ├── lib │ │ ├── appState.js │ │ ├── common │ │ │ ├── constant.ejs │ │ │ └── login.js │ │ ├── config │ │ │ └── index.js │ │ ├── editor │ │ │ ├── config.js │ │ │ ├── index.js │ │ │ ├── statusbar.html │ │ │ ├── toolbar.html │ │ │ ├── ui-elements.js │ │ │ └── utils.js │ │ ├── modeType.js │ │ └── syncscroll.js │ ├── locale.js │ ├── mathjax-config-extra.js │ ├── pretty.js │ ├── render.js │ ├── reveal-markdown.js │ ├── slide.js │ └── utils.js ├── screenshot.png ├── uploads │ └── .gitkeep ├── vendor │ ├── bootstrap │ │ ├── tooltip.min.css │ │ └── tooltip.min.js │ ├── codemirror-spell-checker │ │ ├── en_US.aff │ │ ├── en_US.dic │ │ ├── spell-checker.min.css │ │ └── spell-checker.min.js │ ├── inlineAttachment │ │ ├── codemirror.inline-attachment.js │ │ └── inline-attachment.js │ ├── jquery-textcomplete │ │ └── jquery.textcomplete.js │ ├── md-toc.js │ ├── ot │ │ ├── ajax-adapter.js │ │ ├── client.js │ │ ├── codemirror-adapter.js │ │ ├── compress.sh │ │ ├── editor-client.js │ │ ├── hex2rgb.js │ │ ├── ot.min.js │ │ ├── selection.js │ │ ├── socketio-adapter.js │ │ ├── text-operation.js │ │ ├── undo-manager.js │ │ └── wrapped-operation.js │ └── showup │ │ ├── showup.css │ │ └── showup.js └── views │ ├── error.ejs │ ├── hedgedoc.ejs │ ├── hedgedoc │ ├── body.ejs │ ├── footer.ejs │ ├── head.ejs │ └── header.ejs │ ├── htmlexport.ejs │ ├── includes │ ├── favicon.ejs │ ├── header.ejs │ └── scripts.ejs │ ├── index.ejs │ ├── index │ ├── body.ejs │ ├── footer.ejs │ ├── head.ejs │ └── header.ejs │ ├── pretty.ejs │ ├── shared │ ├── help-modal.ejs │ ├── refresh-modal.ejs │ ├── revision-modal.ejs │ ├── signin-modal.ejs │ └── templates-modal.ejs │ └── slide.ejs ├── renovate.json ├── resources ├── config.json ├── docker-entrypoint.sh ├── healthcheck.mjs └── utf8.cnf ├── test ├── csp.js ├── letter-avatars.js └── user.js ├── webpack.common.js ├── webpack.dev.js ├── webpack.htmlexport.js ├── webpack.prod.js └── yarn.lock /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | ["env", { 4 | "targets": { 5 | "browsers": "defaults" 6 | } 7 | }] 8 | ], 9 | "plugins": [ 10 | "transform-runtime" 11 | ] 12 | } 13 | -------------------------------------------------------------------------------- /.dockerignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | package-lock.json 3 | composer.phar 4 | composer.lock 5 | .env.*.php 6 | .env.php 7 | .env 8 | .DS_Store 9 | .idea/ 10 | Thumbs.db 11 | npm-debug.log 12 | newrelic_agent.log 13 | logs/ 14 | tmp/ 15 | backups/ 16 | *.pid 17 | *.log 18 | *.sqlite 19 | 20 | # ignore config files 21 | config.json 22 | .sequelizerc 23 | 24 | # ignore webpack build 25 | public/build 26 | public/views/build 27 | 28 | public/uploads/* 29 | !public/uploads/.gitkeep 30 | 31 | site/ 32 | .git 33 | config.2.json -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | indent_style = space 5 | indent_size = 2 6 | trim_trailing_whitespace = true 7 | insert_final_newline = true 8 | 9 | [{*.html,*.ejs}] 10 | indent_style = space 11 | indent_size = 4 12 | trim_trailing_whitespace = true 13 | 14 | [*.md] 15 | trim_trailing_whitespace = false 16 | 17 | [{npm-shrinkwrap.json,package.json}] 18 | indent_style = space 19 | indent_size = 2 20 | 21 | [locales/*.json] 22 | # this is the exact style poeditor.com exports, so this should prevent churn. 23 | insert_final_newline = false 24 | indent_style = space 25 | indent_size = 4 26 | -------------------------------------------------------------------------------- /.env.example: -------------------------------------------------------------------------------- 1 | DATABASE_USER=hedhedoc 2 | DATABASE_PASSWORD=secret # change me! i.e. use the output of openssl rand -hex 32 3 | DATABASE_NAME=hedgedoc 4 | DATABASE_HOST=database # keep this if you use docker-compose 5 | DATABASE_PORT=5432 6 | 7 | NEXTCLOUD_HOST=https://nextcloud.example.com # the url of your nextcloud, no trailing slash, including protocol 8 | NEXTCLOUD_SECRET=secret123 # get this from "Additional Settings" in your nextcloud admin settings 9 | NEXTCLOUD_CDN_USERNAME=cdn # the username of the nextcloud user that you want to use as CDN 10 | NEXTCLOUD_CDN_PASSWORD=secret # the password of the nextcloud user that you want to use as CDN 11 | NEXTCLOUD_CDN_FOLDER=cdnfolder # the folder of the nextcloud user that you want to use as CDN 12 | 13 | BROWSERLESS_URL=https://demo.browserless.tld # the url of your browserless instance, no trailing slash, including protocol See https://www.browserless.io/docs/docker 14 | BROWSERLESS_TOKEN=secret # the token of your browserless instance 15 | CMD_SESSION_SECRET=secret # change me! i.e. use the output of openssl rand -hex 32 16 | CMD_HOST=pad.example.com # the url of your pad, no trailing slash, excluding protocol 17 | CMD_PORT=3000 # the port of your pad, keep this unless you know what you are doing 18 | CMD_PROTOCOL_USESSL=true # true, even if you're behind a reverse proxy. Only set this to false if you know what you are doing. 19 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | lib/ot 2 | public/vendor 3 | public/build 4 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | root: true, 3 | extends: ['standard'], 4 | env: { 5 | node: true 6 | }, 7 | rules: {} 8 | } 9 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve HedgeDoc. 4 | title: '' 5 | labels: 'type: bug' 6 | assignees: '' 7 | 8 | --- 9 | 10 | 11 | 12 | 13 | **Description** 14 | 15 | 16 | **To Reproduce** 17 | 22 | 23 | **Expected behavior** 24 | 25 | 26 | **Logs** 27 | 28 | 29 | **Config** 30 | 31 | 32 | **Your Setup (please complete the following information):** 33 | - Host OS: [e.g. Ubuntu 20.04] 34 | - NodeJS version [run `node --version`] 35 | - HedgeDoc version (Click "Version info" in the footer) 36 | - server [e.g. 2.0] 37 | - client [e.g. 2.0] 38 | 39 | **Additional context** 40 | 41 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/enhancement_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Enhancement request 3 | about: Suggest an enhancement of an existing feature. 4 | title: '' 5 | labels: 'type: enhancement' 6 | assignees: '' 7 | 8 | --- 9 | 10 | 11 | 12 | 13 | **Which part of the project should be enhanced?** 14 | 15 | 16 | **Is your enhancement request related to a problem? Please describe.** 17 | 18 | 19 | **Describe the solution you'd like** 20 | 22 | 23 | **Describe alternatives you've considered** 24 | 26 | 27 | **Additional context** 28 | 30 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest a new feature for this project, which isn't existing yet. 4 | title: '' 5 | labels: 'type: feature' 6 | assignees: '' 7 | 8 | --- 9 | 10 | 11 | 12 | 13 | 14 | **Is your feature request related to a problem? Please describe.** 15 | 16 | 17 | **Describe the solution you'd like** 18 | 20 | 21 | **Describe alternatives you've considered** 22 | 24 | 25 | **Additional context** 26 | 28 | -------------------------------------------------------------------------------- /.github/actions/setup-node/action.yml: -------------------------------------------------------------------------------- 1 | # SPDX-FileCopyrightText: 2021 The HedgeDoc developers (see AUTHORS file) 2 | # 3 | # SPDX-License-Identifier: AGPL-3.0-only 4 | 5 | name: Setup Node 6 | description: "Setups node and configures the cache" 7 | 8 | inputs: 9 | NODEJS_VERSION: 10 | required: true 11 | description: "NodeJS version to install" 12 | 13 | runs: 14 | using: "composite" 15 | steps: 16 | - name: Get yarn cache directory path 17 | id: yarn-cache-dir-path 18 | run: echo "dir=$(yarn cache dir)" >> $GITHUB_OUTPUT 19 | shell: bash 20 | 21 | - name: Cache yarn cache 22 | uses: actions/cache@6998d139ddd3e68c71e9e398d8e40b71a2f39812 # v3.2.5 23 | id: yarn-cache 24 | with: 25 | path: ${{ steps.yarn-cache-dir-path.outputs.dir }} 26 | key: ${{ runner.os }}-yarn-master 27 | 28 | - name: Set up NodeJS 29 | uses: actions/setup-node@64ed1c7eab4cce3362f8c340dee64e5eaeef8f7c # v3.6.0 30 | with: 31 | node-version: ${{ inputs.NODEJS_VERSION }} 32 | 33 | - name: Install dependencies 34 | run: yarn install --frozen-lockfile --prefer-offline 35 | working-directory: . 36 | shell: bash 37 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Push into main branch 2 | on: 3 | release 4 | 5 | env: 6 | REGISTRY: ghcr.io 7 | IMAGE_NAME: ${{ github.repository }} 8 | jobs: 9 | context: 10 | runs-on: ubuntu-latest 11 | permissions: 12 | contents: read 13 | packages: write 14 | 15 | steps: 16 | - name: Checkout 17 | uses: actions/checkout@v3 18 | 19 | - name: Log in to the Container registry 20 | uses: docker/login-action@65b78e6e13532edd9afa3aa52ac7964289d1a9c1 21 | with: 22 | registry: ${{ env.REGISTRY }} 23 | username: ${{ github.actor }} 24 | password: ${{ secrets.GITHUB_TOKEN }} 25 | 26 | - name: Extract metadata (tags, labels) for Docker 27 | id: meta 28 | uses: docker/metadata-action@9ec57ed1fcdbf14dcef7dfbe97b2010124a938b7 29 | with: 30 | images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }} 31 | - name: Build and push Docker image 32 | uses: docker/build-push-action@f2a1d5e99d037542a71f64918e516c093c6f3fc4 33 | with: 34 | context: . 35 | push: true 36 | tags: ${{ steps.meta.outputs.tags }} 37 | labels: ${{ steps.meta.outputs.labels }} -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | package-lock.json 3 | composer.phar 4 | composer.lock 5 | .env.*.php 6 | .env.php 7 | .DS_Store 8 | .idea/ 9 | Thumbs.db 10 | npm-debug.log 11 | newrelic_agent.log 12 | logs/ 13 | tmp/ 14 | backups/ 15 | *.pid 16 | *.log 17 | *.sqlite 18 | *.json.env 19 | # ignore config files 20 | config.env.json 21 | .sequelizerc 22 | 23 | # ignore webpack build 24 | public/build 25 | public/views/build 26 | 27 | public/uploads/* 28 | !public/uploads/.gitkeep 29 | 30 | site/ 31 | .env 32 | config.2.json -------------------------------------------------------------------------------- /.mailmap: -------------------------------------------------------------------------------- 1 | Max Wu Wu Cheng-Han 2 | Max Wu Cheng-Han, Wu 3 | Max Wu jackycute 4 | Max Wu Wu, Cheng-Han 5 | Max Wu jackycute 6 | 7 | Christoph (Sheogorath) Kern Sheogorath 8 | 9 | Raccoon Raccoon Li 10 | Raccoon Raccoon 11 | 12 | Peter Dave Hello Peter Dave Hello 13 | 14 | Claudius Coenen Claudius Coenen 15 | Claudius Coenen Claudius 16 | 17 | David Mehren David Mehren 18 | David Mehren David Mehren 19 | 20 | mcnesium mcnesium 21 | 22 | Sandro Jäckel Sandro 23 | 24 | Tilman Vatteroth mrdrogdrog 25 | Tilman Vatteroth Tilman Vatteroth 26 | 27 | Erik Michelson Erik Michelson 28 | 29 | Jonas Zohren Jonas Zohren <15788906+jfowl@users.noreply.github.com> 30 | 31 | Philip Molares Philip Molares 32 | 33 | Yannick Bungers Yannick Bungers 34 | 35 | Renovate Bot renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> 36 | -------------------------------------------------------------------------------- /.remarkrc: -------------------------------------------------------------------------------- 1 | { 2 | "plugins": [ 3 | "preset-lint-markdown-style-guide", 4 | 5 | ["lint-list-item-indent", "space"], 6 | ["lint-maximum-line-length", false], 7 | ["lint-ordered-list-marker-value", "ordered"] 8 | ] 9 | } 10 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # CHANGELOG 2 | 3 | Please refer to the release notes published under 4 | [`public/docs/release-notes.md`](public/docs/release-notes.md). 5 | 6 | These are also available on each HedgeDoc instance under 7 | 8 | -------------------------------------------------------------------------------- /CODE-OF-CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Code of Conduct 2 | 3 | As contributors and maintainers of this project, and in the interest of fostering an open and 4 | welcoming community, we pledge to respect all people who contribute through reporting issues, 5 | posting feature requests, updating documentation, submitting pull requests or patches, and other 6 | activities. 7 | 8 | We are committed to making participation in this project a harassment-free experience for everyone, 9 | regardless of level of experience, gender, gender identity and expression, sexual orientation, 10 | disability, personal appearance, body size, race, ethnicity, age, religion, or nationality. 11 | 12 | Examples of unacceptable behavior by participants include: 13 | 14 | - The use of sexualized language or imagery 15 | 16 | - Personal attacks 17 | 18 | - Trolling or insulting/derogatory comments 19 | 20 | - Public or private harassment 21 | 22 | - Publishing other's private information, such as physical or electronic addresses, without explicit 23 | permission 24 | 25 | - Other unethical or unprofessional conduct. 26 | 27 | Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, 28 | code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct. By 29 | adopting this Code of Conduct, project maintainers commit themselves to fairly and consistently 30 | applying these principles to every aspect of managing this project. Project maintainers who do not 31 | follow or enforce the Code of Conduct may be permanently removed from the project team. 32 | 33 | This code of conduct applies both within project spaces and in public spaces when an individual is 34 | representing the project or its community. 35 | 36 | Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by opening an 37 | issue or contacting one or more of the project maintainers. 38 | 39 | This Code of Conduct is adapted from the [Contributor Covenant](http://contributor-covenant.org), 40 | version 1.2.0, available at 41 | [http://contributor-covenant.org/version/1/2/0/](http://contributor-covenant.org/version/1/2/0/) 42 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM node:16.19.1-bullseye-slim@sha256:97de7e6381540b593c55cd628a44ddffacdbadf9d3ba2f56b56f4913d8f95541 AS base 2 | 3 | FROM base AS builder 4 | # Build arguments to change source url, branch or tag 5 | 6 | # Clone the source and remove git repository but keep the HEAD file 7 | RUN apt-get update && apt-get install --no-install-recommends -y git jq ca-certificates python-is-python3 build-essential 8 | COPY . /hedgedoc 9 | 10 | # Install app dependencies and build 11 | WORKDIR /hedgedoc 12 | RUN yarn install --production=false --pure-lockfile 13 | RUN yarn run build 14 | RUN yarn install --production=true --pure-lockfile 15 | 16 | 17 | FROM base 18 | ARG UID=10000 19 | ENV NODE_ENV=production 20 | ENV UPLOADS_MODE=0700 21 | 22 | RUN apt-get update && \ 23 | apt-get install --no-install-recommends -y gosu && \ 24 | rm -r /var/lib/apt/lists/* 25 | 26 | # Create hedgedoc user 27 | RUN adduser --uid $UID --home /hedgedoc/ --disabled-password --system hedgedoc 28 | 29 | COPY --chown=$UID --from=builder /hedgedoc /hedgedoc 30 | 31 | # Add configuraton files 32 | COPY ["resources/config.json", "/files/"] 33 | 34 | # Healthcheck 35 | COPY --chown=$UID /resources/healthcheck.mjs /hedgedoc/healthcheck.mjs 36 | HEALTHCHECK --interval=5s CMD node healthcheck.mjs 37 | 38 | # For backwards compatibility 39 | RUN ln -s /hedgedoc /codimd 40 | 41 | # Symlink configuration files 42 | RUN rm -f /hedgedoc/config.json 43 | RUN ln -s /files/config.json /hedgedoc/config.json 44 | 45 | WORKDIR /hedgedoc 46 | EXPOSE 3000 47 | 48 | COPY ["resources/docker-entrypoint.sh", "/usr/local/bin/docker-entrypoint.sh"] 49 | RUN chmod +x /usr/local/bin/docker-entrypoint.sh 50 | ENTRYPOINT ["/usr/local/bin/docker-entrypoint.sh"] 51 | RUN npm install -g pg --save 52 | CMD ["node", "app.js"] -------------------------------------------------------------------------------- /NEXTCLOUDCONFIG.md: -------------------------------------------------------------------------------- 1 | # ENVS 2 | 3 | 4 | BROWSERLESS_URL 5 | BROWSERLESS_TOKEN 6 | NC_USER 7 | NC_PASSWORD 8 | NC_FOLDER 9 | CMD_OAUTH2_BASEURL 10 | CMD_DB_URL 11 | CMD_DOMAIN 12 | CMD_PORT 13 | CMD_HOST 14 | CMD_OAUTH2_CLIENT_ID 15 | CMD_OAUTH2_CLIENT_SECRET 16 | CMD_SESSION_SECRET= 17 | CMD_CUSTOM_NAME= 18 | CMD_ALLOW_ORIGIN=[''] -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # HedgeNext 2 | - Fork of [HedgeDoc](https://github.com/hedgedoc/hedgedoc) focussed on Nextcloud integration. 3 | - Various features and customizations. 4 | - Use https://github.com/libnewton/hedgenext-plugin (https://apps.nextcloud.com/apps/hedgenext) 5 | - More documentation coming. 6 | ### Quickstart 7 | - copy the `docker-compose.yml` and the `.env.example` on your server. 8 | - move `.env.example` to `.env` 9 | - edit the values in `.env` 10 | - `docker-compose up -d` 11 | 12 | 13 | ## Setting up PDF Export 14 | 15 | Deploy `browserless/chrome:latest` [Guide](https://www.browserless.io/docs/docker-quickstart) on the container runtime of your choice. Fly.io or Northflank are some free-ish options. 16 | 17 | Setting the following environment variables worked for me: 18 | ``` 19 | CHROME_REFRESH_TIME=3600000 20 | CONNECTION_TIMEOUT=90000 21 | FUNCTION_ENABLE_INCOGNITO_MODE=true 22 | MAX_CONCURRENT_SESSIONS=3 23 | PREBOOT_CHROME=true 24 | TOKEN= 25 | ``` 26 | Make sure to adjust the token stated here inside your `.env` file. -------------------------------------------------------------------------------- /bin/cleanup: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | const cleanup = require('../lib/migrations/20200321153000-fix-account-deletion').cleanup 4 | 5 | cleanup().then(() => { 6 | process.exit(0) 7 | }) 8 | -------------------------------------------------------------------------------- /bin/heroku: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -e 4 | 5 | cat << EOF > config.json 6 | 7 | { 8 | "production": { 9 | "db": { 10 | "dialect": "postgres", 11 | "protocol": "postgres", 12 | "dialectOptions": { 13 | "ssl": { 14 | "require": true, 15 | "rejectUnauthorized": false 16 | } 17 | } 18 | } 19 | } 20 | } 21 | 22 | EOF 23 | -------------------------------------------------------------------------------- /bin/setup: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -e 4 | 5 | version_lt() { test "$(printf '%s\n' "$@" | { [ "$(uname)" = "Linux" ] && (sort -V || sort -t. -k 1,1n -k 2,2n -k 3,3n -k 4,4n;) } | tail -n 1)" != "$1"; } 6 | 7 | # run command at repo root 8 | CURRENT_PATH=$PWD 9 | if [ -d .git ]; then 10 | cd "$(git rev-parse --show-toplevel)" 11 | fi 12 | 13 | if ! type yarn > /dev/null; then 14 | cat << EOF 15 | FATAL: Yarn could not be found. 16 | 17 | Please follow the official installation instructions at 18 | https://classic.yarnpkg.com/en/docs/install 19 | and try again. 20 | EOF 21 | exit 1 22 | fi 23 | 24 | if version_lt "$(yarn --version)" '1.22.0'; then 25 | cat << EOF 26 | FATAL: Your Yarn version is not supported. 27 | 28 | Please upgrade to version 1.22.0 or higher and try again. 29 | See https://classic.yarnpkg.com/en/docs/install for instructions. 30 | EOF 31 | exit 1 32 | fi 33 | 34 | if version_lt "$(node --version)" 'v14.13.1'; then 35 | cat << EOF 36 | FATAL: Your Node.js version is not supported. 37 | 38 | Please upgrade to version 14.13.1 or higher and try again. 39 | We recommend running the latest LTS release, see https://nodejs.org/en/about/releases/ for details. 40 | EOF 41 | exit 1 42 | fi 43 | 44 | echo "Copying config files..." 45 | if [ ! -f config.json ]; then 46 | cp config.json.example config.json 47 | fi 48 | 49 | echo "Installing packages..." 50 | yarn install --production=true --frozen-lockfile 51 | 52 | cat << EOF 53 | If you want to build the frontend yourself, you need to run 'yarn install --frozen-lockfile' before 'yarn build' to install the devDependencies for the build process. 54 | 55 | Edit the following config file to setup HedgeDoc server and client. 56 | Read more info at https://docs.hedgedoc.org/configuration/ 57 | 58 | * config.json -- HedgeDoc config 59 | EOF 60 | 61 | # change directory back 62 | cd "$CURRENT_PATH" 63 | -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: '3' 2 | services: 3 | database: 4 | image: postgres:alpine 5 | environment: 6 | - POSTGRES_USER=${DATABASE_USER} 7 | - POSTGRES_PASSWORD=${DATABASE_PASSWORD} 8 | - POSTGRES_DB=${DATABASE_NAME} 9 | volumes: 10 | - database:/var/lib/postgresql/data 11 | restart: unless-stopped 12 | app: 13 | image: ghcr.io/libnewton/hedgenext-server:latest 14 | environment: 15 | - CMD_DB_URL=postgres://${DATABASE_USER}:${DATABASE_PASSWORD}@${DATABASE_HOST}:${DATABASE_PORT}/${DATABASE_NAME} 16 | - CMD_DOMAIN=${CMD_HOST} 17 | - CMD_PORT=${CMD_PORT} 18 | - CMD_MEDIA_BACKEND=webdav 19 | - CMD_DATABASE_DIALECT=postgres 20 | - CMD_DATABASE_TYPE=postgres 21 | - CMD_DATABASE_HOST=${DATABASE_HOST} 22 | - CMD_DATABASE_NAME=${DATABASE_NAME} 23 | - CMD_DATABASE_PORT=${DATABASE_PORT} 24 | - CMD_DATABASE_USER=${DATABASE_USER} 25 | - CMD_DATABASE_PASS=${DATABASE_PASSWORD} 26 | - CMD_GUEST_ACCESS=read 27 | - BROWSERLESS_URL=${BROWSERLESS_URL} 28 | - BROWSERLESS_TOKEN=${BROWSERLESS_TOKEN} 29 | - NC_USER=${NEXTCLOUD_CDN_USERNAME} 30 | - NC_PASSWORD=${NEXTCLOUD_CDN_PASSWORD} 31 | - NC_HOST=${NEXTCLOUD_HOST} 32 | - NC_FOLDER=${NEXTCLOUD_CDN_FOLDER} 33 | - NC_SECRET=${NEXTCLOUD_SECRET} 34 | - CMD_HOST=0.0.0.0 35 | - CMD_URL_ADDPORT=${CMD_URL_ADDPORT} 36 | - CMD_ALLOW_GRAVATAR=false 37 | - CMD_PROTOCOL_USESSL=${CMD_PROTOCOL_USESSL} 38 | - CMD_ALLOW_ANONYMOUS=false 39 | - CMD_SESSION_SECRET=${CMD_SESSION_SECRET} 40 | - CMD_EMAIL=false 41 | - CMD_ALLOW_EMAIL_REGISTER=false 42 | ports: 43 | - "127.0.0.1:3000:3000" 44 | restart: unless-stopped 45 | depends_on: 46 | - database 47 | 48 | volumes: 49 | database: 50 | -------------------------------------------------------------------------------- /docs/content/dev/documentation.md: -------------------------------------------------------------------------------- 1 | # Documentation 2 | 3 | Our documentation is build with [mkdocs](https://www.mkdocs.org). 4 | 5 | ## Writing 6 | 7 | All documentation files are found in the `docs/content` directory of the [hedgedoc/hedgedoc repo](https://github.com/hedgedoc/hedgedoc). These files are just normal markdown files with nothing special about them. 8 | 9 | The configuration for mkdocs lies in the `docs` folder in a file called `mkdocs.yml`. With that file the theme and menu - amoung others - can be configured. 10 | **Please note:** Any new files need to be linked to by other files or put in the navigation or the files will be very hard to find on the documentation website. 11 | 12 | ## Building 13 | 14 | To build the documentation locally you need to perform the following steps: 15 | 16 | 0. Make sure you have python3 installed. 17 | 1. Go into the `docs` folder. 18 | 2. Install all the dependencies (E.g. with a [venv](https://docs.python.org/3/library/venv.html)) with `pip install -r requirements.txt` 19 | 3. Start the mkdocs dev server (`mkdocs serve`) or build the documentation (`mkdocs build`). 20 | 21 | ## Deployment 22 | 23 | The documentation is deployed with [Messor Structor](https://github.com/traefik/structor). 24 | 25 | The necessary Dockerfile and version menu template and also the github action to build the whole documentation can be found in the [docs.hedgedoc.org repo](https://github.com/hedgedoc/docs.hedgedoc.org). This repo is also used to deploy the actuall website to github.io. 26 | 27 | Messor Structor builds and deploys the documentation by finding all branches that follow the pattern `v*`. For each branch the docs are generated separately by first installing the dependencies from `requirements.txt` and then running mkdocs. Afterwards the menu go template is used to include a version switcher in the theme. 28 | -------------------------------------------------------------------------------- /docs/content/dev/getting-started.md: -------------------------------------------------------------------------------- 1 | # Getting started 2 | 3 | ## Preparing for running the code 4 | 5 | **Notice:** *There are [specialised instructions for docker](../setup/docker.md) if you prefer running code this way!* 6 | 7 | 1. Clone the repository with `git clone https://github.com/hedgedoc/hedgedoc.git hedgedoc-server` 8 | (cloning is the preferred way, but you can also download and unzip a release) 9 | 10 | 2. Enter the directory and run `bin/setup`, which will install npm dependencies 11 | and create configs. The setup script is written in Bash, you would need bash 12 | as a prerequisite. 13 | 14 | 3. Setup the [config file](../configuration.md) or set up 15 | [environment variables](../configuration.md). 16 | 17 | ## Running the Code 18 | 19 | Now that everything is in place, we can start HedgeDoc: 20 | 21 | 1. `yarn run build` will build the frontend bundle. It uses webpack to do that. 22 | 2. Run the server with `node app.js` 23 | 24 | ## Running the Code with Auto-Reload 25 | 26 | The commands above are fine for production, but you're a developer and surely 27 | you want to change things. You would need to restart both commands whenever you 28 | change something. Luckily, you can run these commands that will automatically 29 | rebuild the frontend or restart the server if necessary. 30 | 31 | The commands will stay active in your terminal, so you will need multiple tabs 32 | to run both at the same time. 33 | 34 | 1. Use `yarn run dev` if you want webpack to continuously rebuild the frontend 35 | code. 36 | 37 | 2. To auto-reload the server, the easiest method is to install [nodemon](https://www.npmjs.com/package/nodemon) 38 | and run `nodemon --watch app.js --watch lib --watch locales app.js`. 39 | 40 | ## Structure 41 | 42 | The repository contains two parts: a server (backend) and a client (frontend). 43 | most of the server code is in `/lib` and most of the client code is in `public`. 44 | 45 | ```text 46 | hedgedoc-server/ 47 | ├── docs/ --- documentation 48 | ├── lib/ --- server code 49 | ├── test/ --- test suite 50 | └── public/ --- client code 51 | ├── css/ --- css styles 52 | ├── docs/ --- default documents 53 | ├── js/ --- js scripts 54 | ├── vendor/ --- vendor includes 55 | └── views/ --- view templates 56 | ``` 57 | -------------------------------------------------------------------------------- /docs/content/dev/ot.md: -------------------------------------------------------------------------------- 1 | # Operational Transformation 2 | 3 | From 0.3.2, we started supporting operational transformation. 4 | It makes concurrent editing safe and will not break up other users' operations. 5 | Additionally, now can show other clients' selections. 6 | 7 | See more at [https://operational-transformation.github.io/](https://operational-transformation.github.io/) 8 | 9 | And even more in this 2010 article series: 10 | 11 | - 12 | - 13 | - 14 | -------------------------------------------------------------------------------- /docs/content/dev/webpack.md: -------------------------------------------------------------------------------- 1 | # Webpack 2 | 3 | Webpack is a JavaScript build system for frontend code. You can find out all 4 | about it on [the webpack website](https://webpack.js.org/). 5 | 6 | Here's how we're using it: 7 | 8 | ## `webpack.common.js` 9 | This file contains all common definitions for chunks and plugins that are needed by the whole app. 10 | 11 | The various entrypoints under the `entry` key define groups of files (npm packages or .css/.js files directly from this project) that need to be included together to be useful. 12 | The `index` group for example bundles all javascript files and libraries used for the note editor. 13 | 14 | Entrypoints are referenced in the `plugins` section. 15 | The `HtmlWebpackPlugin` uses templates in `public/views/includes` to include the path to the generated resources in new templates under `public/views/build`. These templates are then used by the backend to serve HTML to the browser. 16 | 17 | **TODO:** Document which entry points are used for what. 18 | 19 | ## `webpack.htmlexport.js` 20 | Separate config for the "save as html" feature. 21 | Packs all CSS from `public/js/htmlExport.js` to `build/html.min.css`. 22 | This file is then downloaded by client-side JS and used to create the HTML. 23 | See `exportToHTML()` in `public/js/extra.js`. 24 | 25 | ## `webpack.dev.js` 26 | The development config uses both common configs, enables development mode and enables "cheap" source maps (lines only). 27 | If you need more detailed source maps while developing, you might want to use the `source-maps` option. 28 | See for details. 29 | 30 | ## `webpack.prod.js` 31 | The production config uses both common configs and enables production mode. 32 | This automatically enables various optimizations (e.g. UglifyJS). See for details. 33 | 34 | For the global app config, the name of the emitted chunks is changed to include the content hash. 35 | See on why this is a good idea. 36 | 37 | For the HTML export config, CSS minification is enabled. 38 | -------------------------------------------------------------------------------- /docs/content/faq/interface_broken.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/libnewton/hedgenext-server/6e5378fb762acd151cc7a818801d4a1e7d887579/docs/content/faq/interface_broken.png -------------------------------------------------------------------------------- /docs/content/faq/interface_okay.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/libnewton/hedgenext-server/6e5378fb762acd151cc7a818801d4a1e7d887579/docs/content/faq/interface_okay.png -------------------------------------------------------------------------------- /docs/content/guides/auth/github.md: -------------------------------------------------------------------------------- 1 | # Authentication guide - GitHub 2 | 3 | 1. Sign-in or sign-up for a GitHub account 4 | 5 | 2. Navigate to developer settings in your GitHub account [here](https://github.com/settings/developers) and select the "OAuth Apps" tab 6 | 7 | 3. Click on the **New OAuth App** button, to create a new OAuth App: 8 | ![create-oauth-app](../../images/auth/create-oauth-app.png) 9 | 10 | 4. Fill out the new OAuth application registration form, and click **Register Application** 11 | ![register-oauth-application-form](../../images/auth/register-oauth-application-form.png) 12 | 13 | **Note:** *The callback URL is /auth/github/callback* 14 | 15 | 5. After successfully registering the application, you'll receive the Client ID and Client Secret for the application 16 | ![application-page](../../images/auth/application-page.png) 17 | 18 | 6. Add the Client ID and Client Secret to your config.json file or pass them as environment variables 19 | - `config.json`: 20 | ```json 21 | { 22 | "production": { 23 | "github": { 24 | "clientID": "3747d30eaccXXXXXXXXX", 25 | "clientSecret": "2a8e682948eee0c580XXXXXXXXXXXXXXXXXXXXXX" 26 | } 27 | } 28 | } 29 | ``` 30 | 31 | - environment variables: 32 | ```shell 33 | CMD_GITHUB_CLIENTID=3747d30eaccXXXXXXXXX 34 | CMD_GITHUB_CLIENTSECRET=2a8e682948eee0c580XXXXXXXXXXXXXXXXXXXXXX 35 | ``` 36 | -------------------------------------------------------------------------------- /docs/content/guides/auth/gitlab-self-hosted.md: -------------------------------------------------------------------------------- 1 | # GitLab (self-hosted) 2 | 3 | **Note:** *This guide was written before the renaming. Just replace `HackMD` with `HedgeDoc` in your mind 😃 thanks!* 4 | 5 | 1. Sign in to your GitLab 6 | 2. Navigate to the application management page at `https://your.gitlab.domain/admin/applications` (admin permissions required) 7 | 3. Click **New application** to create a new application and fill out the registration form: 8 | 9 | ![New GitLab application](../../images/auth/gitlab-new-application.png) 10 | 11 | 4. Click **Submit** 12 | 5. In the list of applications select **HackMD**. Leave that site open to copy the application ID and secret in the next 13 | step. 14 | 15 | ![Application: HackMD](../../images/auth/gitlab-application-details.png) 16 | 17 | 6. In the `docker-compose.yml` add the following environment variables to `app:` `environment:` 18 | 19 | ```yaml 20 | - CMD_DOMAIN=your.hedgedoc.domain 21 | - CMD_URL_ADDPORT=true 22 | - CMD_PROTOCOL_USESSL=true 23 | - CMD_GITLAB_BASEURL=https://your.gitlab.domain 24 | - CMD_GITLAB_CLIENTID=23462a34example99XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX 25 | - CMD_GITLAB_CLIENTSECRET=5532e9dexamplXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX 26 | ``` 27 | 28 | 7. Run `docker-compose up -d` to apply your settings. 29 | 8. Sign in to your HedgeDoc using your GitLab ID: 30 | 31 | ![Sign in via GitLab](../../images/auth/gitlab-sign-in.png) 32 | -------------------------------------------------------------------------------- /docs/content/guides/auth/ldap-ad.md: -------------------------------------------------------------------------------- 1 | # AD LDAP auth 2 | 3 | To setup your HedgeDoc instance with Active Directory you need the following configs: 4 | 5 | ```shell 6 | CMD_LDAP_URL=ldap://internal.example.com 7 | CMD_LDAP_BINDDN=cn=binduser,cn=Users,dc=internal,dc=example,dc=com 8 | CMD_LDAP_BINDCREDENTIALS="" 9 | CMD_LDAP_SEARCHBASE=dc=internal,dc=example,dc=com 10 | CMD_LDAP_SEARCHFILTER=(&(objectcategory=person)(objectclass=user)(|(sAMAccountName={{username}})(mail={{username}}))) 11 | CMD_LDAP_USERIDFIELD=sAMAccountName 12 | CMD_LDAP_PROVIDERNAME="Example Inc AD" 13 | ``` 14 | 15 | `CMD_LDAP_BINDDN` is either the `distinguishedName` or the `userPrincipalName`. 16 | *This can cause "username/password is invalid" when either this value or the password from `CMD_LDAP_BINDCREDENTIALS` 17 | are incorrect.* 18 | 19 | `CMD_LDAP_SEARCHFILTER` matches on all users and uses either the email address or the `sAMAccountName` (usually the 20 | login name you also use to login to Windows). 21 | 22 | *Only using `sAMAccountName` looks like this:* `(&(objectcategory=person)(objectclass=user)(sAMAccountName={{username}}))` 23 | 24 | `CMD_LDAP_USERIDFIELD` says we want to use `sAMAccountName` as unique identifier for the account itself. 25 | 26 | `CMD_LDAP_PROVIDERNAME` just the name written above the username and password field on the login page. 27 | 28 | Same in json: 29 | 30 | ```json 31 | "ldap": { 32 | "url": "ldap://internal.example.com", 33 | "bindDn": "cn=binduser,cn=Users,dc=internal,dc=example,dc=com", 34 | "bindCredentials": "", 35 | "searchBase": "dc=internal,dc=example,dc=com", 36 | "searchFilter": "(&(objectcategory=person)(objectclass=user)(|(sAMAccountName={{username}})(mail={{username}})))", 37 | "useridField": "sAMAccountName", 38 | }, 39 | ``` 40 | 41 | More details and example: 42 | -------------------------------------------------------------------------------- /docs/content/guides/auth/oauth.md: -------------------------------------------------------------------------------- 1 | # OAuth general information 2 | 3 | | service | callback URL (after the server URL) | 4 | | ---------- | ----------------------------------- | 5 | | facebook | `/auth/facebook/callback` | 6 | | twitter | `/auth/twitter/callback` | 7 | | github | `/auth/github/callback` | 8 | | gitlab | `/auth/gitlab/callback` | 9 | | mattermost | `/auth/mattermost/callback` | 10 | | dropbox | `/auth/dropbox/callback` | 11 | | google | `/auth/google/callback` | 12 | | saml | `/auth/saml/callback` | 13 | -------------------------------------------------------------------------------- /docs/content/guides/auth/saml-onelogin.md: -------------------------------------------------------------------------------- 1 | # Authentication guide - SAML (OneLogin) 2 | 3 | **Note:** *This guide was written before the renaming. Just replace `HackMD` with `HedgeDoc` in your mind 😃 thanks!* 4 | 5 | 1. Sign-in or sign-up for an OneLogin account. (available free trial for 2 weeks) 6 | 7 | 2. Go to the administration page. 8 | 9 | 3. Select the **APPS** menu and click on the **Add Apps**. 10 | ![onelogin-add-app](../../images/auth/onelogin-add-app.png) 11 | 12 | 4. Find "SAML Test Connector (SP)" for template of settings and select it. 13 | ![onelogin-select-template](../../images/auth/onelogin-select-template.png) 14 | 15 | 5. Edit display name and icons for OneLogin dashboard as you want, and click **SAVE**. 16 | ![onelogin-edit-app-name](../../images/auth/onelogin-edit-app-name.png) 17 | 18 | 6. After that other tabs will appear, click the **Configuration**, and fill out the below items, and click **SAVE**. 19 | - RelayState: The base URL of your HedgeDoc, which is issuer. (last slash is not needed) 20 | 21 | - ACS (Consumer) URL Validator: The callback URL of your HedgeDoc. (serverurl + /auth/saml/callback) 22 | 23 | - ACS (Consumer) URL: same as above. 24 | 25 | - Login URL: login URL(SAML requester) of your CopiMD. (serverurl + /auth/saml) 26 | ![onelogin-edit-sp-metadata](../../images/auth/onelogin-edit-sp-metadata.png) 27 | 28 | 7. The registration is completed. Next, click **SSO** and copy or download the items below. 29 | - X.509 Certificate: Click **View Details** and **DOWNLOAD** or copy the content of certificate ....(A) 30 | 31 | - SAML 2.0 Endpoint (HTTP): Copy the URL ....(B) 32 | ![onelogin-copy-idp-metadata](../../images/auth/onelogin-copy-idp-metadata.png) 33 | 34 | 8. In your HedgeDoc server, create IdP certificate file from (A) 35 | 9. Add the IdP URL (B) and the Idp certificate file path to your config.json file or pass them as environment variables. 36 | - `config.json`: 37 | ```json 38 | { 39 | "production": { 40 | "saml": { 41 | "idpSsoUrl": "https://*******.onelogin.com/trust/saml2/http-post/sso/******", 42 | "idpCert": "/path/to/idp_cert.pem" 43 | } 44 | } 45 | } 46 | ``` 47 | 48 | - environment variables 49 | ```shell 50 | CMD_SAML_IDPSSOURL=https://*******.onelogin.com/trust/saml2/http-post/sso/****** 51 | CMD_SAML_IDPCERT=/path/to/idp_cert.pem 52 | ``` 53 | 54 | 10. Try sign-in with SAML from your HedgeDoc sign-in button or OneLogin dashboard (like the screenshot below). 55 | ![onelogin-use-dashboard](../../images/auth/onelogin-use-dashboard.png) 56 | -------------------------------------------------------------------------------- /docs/content/guides/auth/twitter.md: -------------------------------------------------------------------------------- 1 | # Authentication guide - Twitter 2 | 3 | **Note:** *This guide was written before the renaming. Just replace `HackMD` with `HedgeDoc` in your mind 😃 thanks!* 4 | 5 | 1. Sign-in or sign-up for a Twitter account 6 | 7 | 2. Go to the Twitter Application management page [here](https://apps.twitter.com/) 8 | 9 | 3. Click on the **Create New App** button to create a new Twitter app: 10 | ![create-twitter-app](../../images/auth/create-twitter-app.png) 11 | 12 | 4. Fill out the create application form, check the developer agreement box, and click **Create Your Twitter Application** 13 | ![register-twitter-application](../../images/auth/register-twitter-application.png) 14 | 15 | *Note:* you may have to register your phone number with Twitter to create a Twitter application 16 | 17 | To do this Click your profile icon --> Settings and privacy --> Mobile --> Select Country/region --> Enter phone number --> Click Continue 18 | 19 | 5. After you receive confirmation that the Twitter application was created, click **Keys and Access Tokens** 20 | ![twitter-app-confirmation](../../images/auth/twitter-app-confirmation.png) 21 | 22 | 6. Obtain your Twitter Consumer Key and Consumer Secret 23 | ![twitter-app-keys](../../images/auth/twitter-app-keys.png) 24 | 25 | 7. Add your Consumer Key and Consumer Secret to your `config.json` file or pass them as environment variables: 26 | - `config.json`: 27 | ```json 28 | { 29 | "production": { 30 | "twitter": { 31 | "consumerKey": "esTCJFXXXXXXXXXXXXXXXXXXX", 32 | "consumerSecret": "zpCs4tU86pRVXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX" 33 | } 34 | } 35 | } 36 | ``` 37 | 38 | - environment variables: 39 | ```shell 40 | CMD_TWITTER_CONSUMERKEY=esTCJFXXXXXXXXXXXXXXXXXXX 41 | CMD_TWITTER_CONSUMERSECRET=zpCs4tU86pRVXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX 42 | ``` 43 | -------------------------------------------------------------------------------- /docs/content/guides/migration-troubleshooting.md: -------------------------------------------------------------------------------- 1 | # Troubleshooting Migrations 2 | 3 | In some cases, HedgeDoc might apply migrations without correctly saving the progress. 4 | In these cases, HedgeDoc will refuse to start with “already exists”-errors like 5 | `ERROR: type "enum_Notes_permission" already exists`. 6 | 7 | To fix these issues, manual intervention in the database is required: 8 | 9 | 1. Make sure you have a way to edit the database directly. For SQLite, PostgreSQL and MariaDB/MySQL, you can use the 10 | respective command-line tools `sqlite3`, `psql` and `mysql`. 11 | 2. Get the name of the failing migration and append `.js` to it. 12 | For example, if you encounter this error 13 | ``` 14 | == 20180306150303-fix-enum: migrating ======= 15 | 16 | ERROR: type "enum_Notes_permission" already exists 17 | ``` 18 | the name of the failed migration would be `20180306150303-fix-enum.js`. 19 | 3. Make sure HedgeDoc does not run and insert the name into the `SequelizeMeta` table. 20 | Ensure your database shell is connected to the HedgeDoc database. The SQL-statement may look like this: 21 | ```sql 22 | INSERT INTO "SequelizeMeta" (name) VALUES ('20180306150303-fix-enum.js'); 23 | ``` 24 | 4. Start HedgeDoc again and observe if it starts correctly. It may be necessary to repeat this process 25 | and insert multiple migrations into the `SequelizeMeta` table. 26 | -------------------------------------------------------------------------------- /docs/content/guides/minio-image-upload.md: -------------------------------------------------------------------------------- 1 | # Minio Guide for HedgeDoc 2 | 3 | **Note:** *This guide was written before the renaming. Just replace `HackMD` with `HedgeDoc` in your mind 😃 thanks!* 4 | 5 | 1. First of all you need to setup Minio itself. 6 | 7 | Please refer to the [official Minio docs](https://docs.minio.io/) for an 8 | production setup. 9 | 10 | For checking it out and development purposes a non-persistent setup is enough: 11 | ```sh 12 | docker run --name test-minio --rm -d -p 9000:9000 minio/minio server /data 13 | ``` 14 | 15 | *Please notice this is not for productive use as all your data gets lost 16 | when you stop this container* 17 | 18 | 2. Next step is to get the credentials form the container: 19 | 20 | ```sh 21 | docker logs test-minio 22 | ``` 23 | 24 | ![docker logs](../images/minio-image-upload/docker-logs.png) 25 | 26 | 3. Open and login with the shown credentials. 27 | 28 | ![minio default view](../images/minio-image-upload/default-view.png) 29 | 30 | 4. Create a bucket for HedgeDoc 31 | 32 | ![minio create bucket](../images/minio-image-upload/create-bucket.png) 33 | 34 | 5. Add a policy for the prefix `uploads` and make it read-only. 35 | 36 | ![minio edit policy](../images/minio-image-upload/open-edit-policy.png) 37 | *Open policy editor* 38 | 39 | ![minio policy adding](../images/minio-image-upload/create-policy.png) 40 | *Add policy for uploads* 41 | 42 | 6. Set credentials and configs for Minio in HedgeDoc's `config.json` 43 | 44 | ```JSON 45 | "minio": { 46 | "accessKey": "888MXJ7EP4XXXXXXXXX", 47 | "secretKey": "yQS2EbM1Y6IJrp/1BUKWq2/XXXXXXXXXXXXXXX", 48 | "endPoint": "localhost", 49 | "port": 9000, 50 | "secure": false 51 | } 52 | ``` 53 | *You have to use different values for `endpoint` and `port` for a production 54 | setup. Keep in mind the `endpoint`-address has to be public accessible from 55 | your browser.* 56 | 57 | 7. Set bucket name 58 | 59 | ```JSON 60 | "s3bucket": "hedgedoc" 61 | ``` 62 | 63 | 8. Set upload type. 64 | 65 | ```JSON 66 | "imageuploadtype": "minio" 67 | ``` 68 | 69 | 9. Review your config. 70 | 71 | ```json 72 | { 73 | // all your other config… 74 | "minio": { 75 | "accessKey": "888MXJ7EP4XXXXXXXXX", 76 | "secretKey": "yQS2EbM1Y6IJrp/1BUKWq2/XXXXXXXXXXXXXXX", 77 | "endPoint": "localhost", 78 | "port": 9000, 79 | "secure": false 80 | }, 81 | "s3bucket": "hedgedoc", 82 | "imageuploadtype": "minio" 83 | } 84 | ``` 85 | 86 | 10. If you were using filesystem before 87 | 88 | and you want to migrate assets to minio. 89 | 90 | You could use a convenience script located in `bin/migrate_from_fs_to_minio`. 91 | 92 | Be careful, read carefully what it does, it was not tested in all environments. 93 | 94 | Take it as an inspiration to make your own migration script. 95 | -------------------------------------------------------------------------------- /docs/content/guides/providing-terms.md: -------------------------------------------------------------------------------- 1 | # How to set up your terms of use 2 | 3 | ## Setup your terms of use 4 | 5 | To setup your terms of use, you need to provide a document called `terms-of-use.md` which contains them. Of course written in Markdown. 6 | 7 | It has to be provided under `./public/docs/` and will be automatically turned into a HedgeDoc document. It will also automatically updated as soon as you change the document on disk. 8 | 9 | As soon as the file exists a link will show up in the bottom part along with the release notes and link to them. 10 | 11 | ## Setup your privacy policy 12 | 13 | To add a privacy policy you can use the same technique as for the terms of use. The main difference is that the document is called `privacy.md`. 14 | 15 | See our example file `./public/docs/privacy.md.example` container some useful hints for writing your own privacy policy. 16 | 17 | As with the terms of use, a link to the privacy notices will show up in the area where the release notes are provided on the index page. 18 | 19 | ## Setup your imprint 20 | 21 | To add an imprint you can use the same technique as for the terms of use. The main difference is that the document is called `imprint.md`. 22 | 23 | It has to be provided under `./public/docs/` and will be automatically turned into a HedgeDoc document. It will also automatically updated as soon as you change the document on disk. 24 | 25 | As with the terms of use, a link to the imprint will show up in the area where the release notes are provided on the index page. 26 | -------------------------------------------------------------------------------- /docs/content/guides/s3-image-upload.md: -------------------------------------------------------------------------------- 1 | # Guide - Setup HedgeDoc S3 image upload 2 | 3 | **Note:** *This guide was written before the renaming. Just replace `HackMD` with `HedgeDoc` in your mind 😃 thanks!* 4 | 5 | 1. Go to [AWS S3 console](https://console.aws.amazon.com/s3/home) and create a new bucket. 6 | ![create-bucket](../images/s3-image-upload/create-bucket.png) 7 | 8 | 2. Click on bucket, select **Properties** on the side panel, and find **Permission** section. Click **Edit bucket policy**. 9 | ![bucket-property](../images/s3-image-upload/bucket-property.png) 10 | 11 | 3. Enter the following policy, replace `bucket_name` with your bucket name: 12 | ![bucket-policy-editor](../images/s3-image-upload/bucket-policy-editor.png) 13 | 14 | ```json 15 | { 16 | "Version": "2012-10-17", 17 | "Statement": [ 18 | { 19 | "Effect": "Allow", 20 | "Principal": "*", 21 | "Action": "s3:GetObject", 22 | "Resource": "arn:aws:s3:::bucket_name/uploads/*" 23 | } 24 | ] 25 | } 26 | ``` 27 | 28 | 4. Go to IAM console and create a new IAM user. Remember your user credentials(`key`/`access token`) 29 | 30 | 5. Enter user page, select **Permission** tab, look at **Inline Policies** section, and click **Create User Policy** 31 | ![iam-user](../images/s3-image-upload/iam-user.png) 32 | 33 | 6. Select **Custom Policy** 34 | ![custom-policy](../images/s3-image-upload/custom-policy.png) 35 | 36 | 7. Enter the following policy, replace `bucket_name` with your bucket name: 37 | ![review-policy](../images/s3-image-upload/review-policy.png) 38 | 39 | ```json 40 | { 41 | "Version": "2012-10-17", 42 | "Statement": [ 43 | { 44 | "Effect": "Allow", 45 | "Action": [ 46 | "s3:*" 47 | ], 48 | "Resource": [ 49 | "arn:aws:s3:::bucket_name/uploads/*" 50 | ] 51 | } 52 | ] 53 | } 54 | ``` 55 | 56 | 8. Edit `config.json` and set following keys: 57 | 58 | ```javascript 59 | { 60 | "production": { 61 | ... 62 | "imageuploadtype": "s3", 63 | "s3": { 64 | "accessKeyId": "YOUR_S3_ACCESS_KEY_ID", 65 | "secretAccessKey": "YOUR_S3_ACCESS_KEY", 66 | "region": "YOUR_S3_REGION" // example: ap-northeast-1 67 | }, 68 | "s3bucket": "YOUR_S3_BUCKET_NAME" 69 | } 70 | } 71 | ``` 72 | 73 | 9. In additional to edit `config.json` directly, you could also try [environment variables](../configuration.md). 74 | 75 | ## Related Tools 76 | 77 | - [AWS Policy Generator](http://awspolicygen.s3.amazonaws.com/policygen.html) 78 | -------------------------------------------------------------------------------- /docs/content/images/auth/application-page.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/libnewton/hedgenext-server/6e5378fb762acd151cc7a818801d4a1e7d887579/docs/content/images/auth/application-page.png -------------------------------------------------------------------------------- /docs/content/images/auth/create-oauth-app.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/libnewton/hedgenext-server/6e5378fb762acd151cc7a818801d4a1e7d887579/docs/content/images/auth/create-oauth-app.png -------------------------------------------------------------------------------- /docs/content/images/auth/create-twitter-app.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/libnewton/hedgenext-server/6e5378fb762acd151cc7a818801d4a1e7d887579/docs/content/images/auth/create-twitter-app.png -------------------------------------------------------------------------------- /docs/content/images/auth/gitlab-application-details.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/libnewton/hedgenext-server/6e5378fb762acd151cc7a818801d4a1e7d887579/docs/content/images/auth/gitlab-application-details.png -------------------------------------------------------------------------------- /docs/content/images/auth/gitlab-new-application.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/libnewton/hedgenext-server/6e5378fb762acd151cc7a818801d4a1e7d887579/docs/content/images/auth/gitlab-new-application.png -------------------------------------------------------------------------------- /docs/content/images/auth/gitlab-sign-in.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/libnewton/hedgenext-server/6e5378fb762acd151cc7a818801d4a1e7d887579/docs/content/images/auth/gitlab-sign-in.png -------------------------------------------------------------------------------- /docs/content/images/auth/keycloak_add_client.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/libnewton/hedgenext-server/6e5378fb762acd151cc7a818801d4a1e7d887579/docs/content/images/auth/keycloak_add_client.png -------------------------------------------------------------------------------- /docs/content/images/auth/keycloak_client_overview.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/libnewton/hedgenext-server/6e5378fb762acd151cc7a818801d4a1e7d887579/docs/content/images/auth/keycloak_client_overview.png -------------------------------------------------------------------------------- /docs/content/images/auth/keycloak_clients_overview.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/libnewton/hedgenext-server/6e5378fb762acd151cc7a818801d4a1e7d887579/docs/content/images/auth/keycloak_clients_overview.png -------------------------------------------------------------------------------- /docs/content/images/auth/keycloak_force_idformat.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/libnewton/hedgenext-server/6e5378fb762acd151cc7a818801d4a1e7d887579/docs/content/images/auth/keycloak_force_idformat.png -------------------------------------------------------------------------------- /docs/content/images/auth/keycloak_idp_cert.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/libnewton/hedgenext-server/6e5378fb762acd151cc7a818801d4a1e7d887579/docs/content/images/auth/keycloak_idp_cert.png -------------------------------------------------------------------------------- /docs/content/images/auth/keycloak_mapper_email.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/libnewton/hedgenext-server/6e5378fb762acd151cc7a818801d4a1e7d887579/docs/content/images/auth/keycloak_mapper_email.png -------------------------------------------------------------------------------- /docs/content/images/auth/keycloak_mapper_overview.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/libnewton/hedgenext-server/6e5378fb762acd151cc7a818801d4a1e7d887579/docs/content/images/auth/keycloak_mapper_overview.png -------------------------------------------------------------------------------- /docs/content/images/auth/keycloak_mapper_username.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/libnewton/hedgenext-server/6e5378fb762acd151cc7a818801d4a1e7d887579/docs/content/images/auth/keycloak_mapper_username.png -------------------------------------------------------------------------------- /docs/content/images/auth/keycloak_saml_export_cert.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/libnewton/hedgenext-server/6e5378fb762acd151cc7a818801d4a1e7d887579/docs/content/images/auth/keycloak_saml_export_cert.png -------------------------------------------------------------------------------- /docs/content/images/auth/keycloak_saml_export_cert_details.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/libnewton/hedgenext-server/6e5378fb762acd151cc7a818801d4a1e7d887579/docs/content/images/auth/keycloak_saml_export_cert_details.png -------------------------------------------------------------------------------- /docs/content/images/auth/keycloak_saml_import_cert.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/libnewton/hedgenext-server/6e5378fb762acd151cc7a818801d4a1e7d887579/docs/content/images/auth/keycloak_saml_import_cert.png -------------------------------------------------------------------------------- /docs/content/images/auth/keycloak_saml_import_cert_details.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/libnewton/hedgenext-server/6e5378fb762acd151cc7a818801d4a1e7d887579/docs/content/images/auth/keycloak_saml_import_cert_details.png -------------------------------------------------------------------------------- /docs/content/images/auth/mattermost-enable-oauth2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/libnewton/hedgenext-server/6e5378fb762acd151cc7a818801d4a1e7d887579/docs/content/images/auth/mattermost-enable-oauth2.png -------------------------------------------------------------------------------- /docs/content/images/auth/mattermost-oauth-app-add.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/libnewton/hedgenext-server/6e5378fb762acd151cc7a818801d4a1e7d887579/docs/content/images/auth/mattermost-oauth-app-add.png -------------------------------------------------------------------------------- /docs/content/images/auth/mattermost-oauth-app-done.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/libnewton/hedgenext-server/6e5378fb762acd151cc7a818801d4a1e7d887579/docs/content/images/auth/mattermost-oauth-app-done.png -------------------------------------------------------------------------------- /docs/content/images/auth/mattermost-oauth-app-form.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/libnewton/hedgenext-server/6e5378fb762acd151cc7a818801d4a1e7d887579/docs/content/images/auth/mattermost-oauth-app-form.png -------------------------------------------------------------------------------- /docs/content/images/auth/nextcloud-oauth2-1-settings.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/libnewton/hedgenext-server/6e5378fb762acd151cc7a818801d4a1e7d887579/docs/content/images/auth/nextcloud-oauth2-1-settings.png -------------------------------------------------------------------------------- /docs/content/images/auth/nextcloud-oauth2-2-client-add.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/libnewton/hedgenext-server/6e5378fb762acd151cc7a818801d4a1e7d887579/docs/content/images/auth/nextcloud-oauth2-2-client-add.png -------------------------------------------------------------------------------- /docs/content/images/auth/nextcloud-oauth2-3-clientid-secret.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/libnewton/hedgenext-server/6e5378fb762acd151cc7a818801d4a1e7d887579/docs/content/images/auth/nextcloud-oauth2-3-clientid-secret.png -------------------------------------------------------------------------------- /docs/content/images/auth/onelogin-add-app.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/libnewton/hedgenext-server/6e5378fb762acd151cc7a818801d4a1e7d887579/docs/content/images/auth/onelogin-add-app.png -------------------------------------------------------------------------------- /docs/content/images/auth/onelogin-copy-idp-metadata.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/libnewton/hedgenext-server/6e5378fb762acd151cc7a818801d4a1e7d887579/docs/content/images/auth/onelogin-copy-idp-metadata.png -------------------------------------------------------------------------------- /docs/content/images/auth/onelogin-edit-app-name.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/libnewton/hedgenext-server/6e5378fb762acd151cc7a818801d4a1e7d887579/docs/content/images/auth/onelogin-edit-app-name.png -------------------------------------------------------------------------------- /docs/content/images/auth/onelogin-edit-sp-metadata.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/libnewton/hedgenext-server/6e5378fb762acd151cc7a818801d4a1e7d887579/docs/content/images/auth/onelogin-edit-sp-metadata.png -------------------------------------------------------------------------------- /docs/content/images/auth/onelogin-select-template.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/libnewton/hedgenext-server/6e5378fb762acd151cc7a818801d4a1e7d887579/docs/content/images/auth/onelogin-select-template.png -------------------------------------------------------------------------------- /docs/content/images/auth/onelogin-use-dashboard.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/libnewton/hedgenext-server/6e5378fb762acd151cc7a818801d4a1e7d887579/docs/content/images/auth/onelogin-use-dashboard.png -------------------------------------------------------------------------------- /docs/content/images/auth/register-oauth-application-form.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/libnewton/hedgenext-server/6e5378fb762acd151cc7a818801d4a1e7d887579/docs/content/images/auth/register-oauth-application-form.png -------------------------------------------------------------------------------- /docs/content/images/auth/register-twitter-application.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/libnewton/hedgenext-server/6e5378fb762acd151cc7a818801d4a1e7d887579/docs/content/images/auth/register-twitter-application.png -------------------------------------------------------------------------------- /docs/content/images/auth/twitter-app-confirmation.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/libnewton/hedgenext-server/6e5378fb762acd151cc7a818801d4a1e7d887579/docs/content/images/auth/twitter-app-confirmation.png -------------------------------------------------------------------------------- /docs/content/images/auth/twitter-app-keys.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/libnewton/hedgenext-server/6e5378fb762acd151cc7a818801d4a1e7d887579/docs/content/images/auth/twitter-app-keys.png -------------------------------------------------------------------------------- /docs/content/images/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/libnewton/hedgenext-server/6e5378fb762acd151cc7a818801d4a1e7d887579/docs/content/images/favicon.png -------------------------------------------------------------------------------- /docs/content/images/minio-image-upload/create-bucket.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/libnewton/hedgenext-server/6e5378fb762acd151cc7a818801d4a1e7d887579/docs/content/images/minio-image-upload/create-bucket.png -------------------------------------------------------------------------------- /docs/content/images/minio-image-upload/create-policy.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/libnewton/hedgenext-server/6e5378fb762acd151cc7a818801d4a1e7d887579/docs/content/images/minio-image-upload/create-policy.png -------------------------------------------------------------------------------- /docs/content/images/minio-image-upload/default-view.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/libnewton/hedgenext-server/6e5378fb762acd151cc7a818801d4a1e7d887579/docs/content/images/minio-image-upload/default-view.png -------------------------------------------------------------------------------- /docs/content/images/minio-image-upload/docker-logs.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/libnewton/hedgenext-server/6e5378fb762acd151cc7a818801d4a1e7d887579/docs/content/images/minio-image-upload/docker-logs.png -------------------------------------------------------------------------------- /docs/content/images/minio-image-upload/open-edit-policy.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/libnewton/hedgenext-server/6e5378fb762acd151cc7a818801d4a1e7d887579/docs/content/images/minio-image-upload/open-edit-policy.png -------------------------------------------------------------------------------- /docs/content/images/s3-image-upload/bucket-policy-editor.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/libnewton/hedgenext-server/6e5378fb762acd151cc7a818801d4a1e7d887579/docs/content/images/s3-image-upload/bucket-policy-editor.png -------------------------------------------------------------------------------- /docs/content/images/s3-image-upload/bucket-property.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/libnewton/hedgenext-server/6e5378fb762acd151cc7a818801d4a1e7d887579/docs/content/images/s3-image-upload/bucket-property.png -------------------------------------------------------------------------------- /docs/content/images/s3-image-upload/create-bucket.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/libnewton/hedgenext-server/6e5378fb762acd151cc7a818801d4a1e7d887579/docs/content/images/s3-image-upload/create-bucket.png -------------------------------------------------------------------------------- /docs/content/images/s3-image-upload/custom-policy.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/libnewton/hedgenext-server/6e5378fb762acd151cc7a818801d4a1e7d887579/docs/content/images/s3-image-upload/custom-policy.png -------------------------------------------------------------------------------- /docs/content/images/s3-image-upload/iam-user.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/libnewton/hedgenext-server/6e5378fb762acd151cc7a818801d4a1e7d887579/docs/content/images/s3-image-upload/iam-user.png -------------------------------------------------------------------------------- /docs/content/images/s3-image-upload/review-policy.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/libnewton/hedgenext-server/6e5378fb762acd151cc7a818801d4a1e7d887579/docs/content/images/s3-image-upload/review-policy.png -------------------------------------------------------------------------------- /docs/content/index.md: -------------------------------------------------------------------------------- 1 | # Welcome to the HedgeDoc Documentation 2 | 3 | HedgeDoc Logo 4 | HedgeDoc Logo 5 | 6 | HedgeDoc lets you create real-time collaborative markdown notes. You can test-drive 7 | it by visiting our [HedgeDoc demo server][hedgedoc-demo]. 8 | 9 | It is inspired by Hackpad, Etherpad and similar collaborative editors. This 10 | project originated with the team at [HackMD](https://hackmd.io) and now forked 11 | into its own organization. [A longer write-up can be read in the history doc][hedgedoc-history-details] or [you can have a look at an explanatory graph over at our website][hedgedoc-history]. 12 | 13 | If you have any questions that aren't answered here, feel free to ask us on [Matrix][matrix.org-url], stop by our [community forums][hedgedoc-community] or have a look at our [FAQ][hedgedoc-faq]. 14 | 15 | 16 | [hedgedoc-demo]: https://demo.hedgedoc.org 17 | [hedgedoc-history]: https://hedgedoc.org/history 18 | [hedgedoc-history-details]: https://hedgedoc.org/history/details 19 | [hedgedoc-faq]: /faq 20 | [matrix.org-url]: https://chat.hedgedoc.org 21 | [hedgedoc-community]: https://community.hedgedoc.org 22 | -------------------------------------------------------------------------------- /docs/content/legal/developer-certificate-of-origin.txt: -------------------------------------------------------------------------------- 1 | Developer Certificate of Origin 2 | Version 1.1 3 | 4 | Copyright (C) 2004, 2006 The Linux Foundation and its contributors. 5 | 660 York Street, Suite 102, 6 | San Francisco, CA 94110 USA 7 | 8 | Everyone is permitted to copy and distribute verbatim copies of this 9 | license document, but changing it is not allowed. 10 | 11 | Developer's Certificate of Origin 1.1 12 | 13 | By making a contribution to this project, I certify that: 14 | 15 | (a) The contribution was created in whole or in part by me and I 16 | have the right to submit it under the open source license 17 | indicated in the file; or 18 | 19 | (b) The contribution is based upon previous work that, to the best 20 | of my knowledge, is covered under an appropriate open source 21 | license and I have the right under that license to submit that 22 | work with modifications, whether created in whole or in part 23 | by me, under the same open source license (unless I am 24 | permitted to submit under a different license), as indicated 25 | in the file; or 26 | 27 | (c) The contribution was provided directly to me by some other 28 | person who certified (a), (b) or (c) and I have not modified 29 | it. 30 | 31 | (d) I understand and agree that this project and the contribution 32 | are public and that a record of the contribution (including all 33 | personal information I submit with it, including my sign-off) is 34 | maintained indefinitely and may be redistributed consistent with 35 | this project or the open source license(s) involved. 36 | -------------------------------------------------------------------------------- /docs/content/references/url-scheme.md: -------------------------------------------------------------------------------- 1 | # URL scheme 2 | 3 | HedgeDoc has three different modes for viewing a stored note. Each mode has a slightly different URL for accessing it. This document gives an overview about these URLs. 4 | We assume that you replace `pad.example.com` with the domain of your instance. 5 | 6 | ## Default (random) 7 | 8 | When you create a new note by clicking the "New note" button, your note is given a long random id and a random short-id. The long id is needed for accessing the editor and the live-update view. The short-id is used for the "published" version of a note that is read-only and does not update in realtime as well as for the presentation mode. 9 | 10 | | example URL | prefix | mode | content updates | 11 | | -------------------------------------- | ------ | ----------------- | --------------- | 12 | | pad.example.com/Ndmv3oCyREKZMjSGR9uhnQ | *none* | editor | in realtime | 13 | | pad.example.com/s/ByXF7k-YI | s/ | read-only version | on reload | 14 | | pad.example.com/p/ByXF7k-YI | p/ | presentation mode | on reload | 15 | 16 | ## FreeURL mode 17 | 18 | If the setting `CMD_ALLOW_FREEURL` is enabled, users may create notes with a custom alias URL by just visiting the editor version of a custom alias. The published version and the presentation mode may also be accessed with the custom alias. 19 | 20 | | example URL | prefix | mode | content updates | 21 | | --------------------------------- | ------ | ----------------- | --------------- | 22 | | pad.example.com/my-awesome-note | *none* | editor | in realtime | 23 | | pad.example.com/s/my-awesome-note | s/ | read-only version | on reload | 24 | | pad.example.com/p/my-awesome-note | p/ | presentation mode | on reload | 25 | 26 | ## Different editor modes 27 | 28 | The editor has three different sub-modes. All of these update the content in realtime. 29 | 30 | | example URL | icon in the navbar | behaviour | 31 | | ------------------------------- | -------------------| ----------------------------------------------- | 32 | | pad.example.com/longnoteid?edit | pencil | Full-screen markdown editor for the content | 33 | | pad.example.com/longnoteid?view | eye | Full-screen view of the note without the editor | 34 | | pad.example.com/longnoteid?both | columns | markdown editor and view mode side-by-side | 35 | -------------------------------------------------------------------------------- /docs/content/setup/getting-started.md: -------------------------------------------------------------------------------- 1 | # Getting started 2 | 3 | Thank you for choosing HedgeDoc! 4 | To set up your instance follow these steps: 5 | 6 | 1. Choose an installation method and follow the instructions 7 | 2. [Configure your reverse proxy](https://docs.hedgedoc.org/guides/reverse-proxy/) 8 | 3. [Configure HedgeDoc](https://docs.hedgedoc.org/configuration/) 9 | 4. If you didn't disable [local accounts](/configuration/#email-local-account), you can use the "Sign In" button to 10 | create an account, login and start using HedgeDoc. 11 | 12 | Follow us on :fontawesome-brands-mastodon:{: .mastodon }Mastodon or :fontawesome-brands-twitter:{: .twitter }Twitter for updates. 13 | 14 | ## Upgrading HedgeDoc 15 | 16 | HedgeDoc follows [Semantic Versioning](https://semver.org/). 17 | This means that minor and patch releases should not introduce user-facing backwards-incompatible changes. 18 | 19 | You can find more details about upgrading in the instructions of your installation method. 20 | 21 | !!! warning 22 | Before you upgrade, **always read the release notes**. 23 | You can find them on our [releases page](https://hedgedoc.org/releases/). 24 | 25 | ## Migrating from CodiMD & HackMD 26 | Migrating from CodiMD <= 1.6.0 or HackMD <= 1.1.0 to HedgeDoc should be safe, 27 | just make sure to read the release notes. 28 | A particular issue that has come up is when handling TLS connections using a reverse proxy. 29 | You must [set the `X-Forwarded-Proto` header correctly](https://docs.hedgedoc.org/guides/reverse-proxy/#reverse-proxy-config). 30 | 31 | Migrating from more recent versions of CodiMD is not guaranteed to work, although some community members 32 | [reported success migrating from CodiMD 2.2](https://community.hedgedoc.org/t/solved-upgrade-from-dockerlized-codimd/271). 33 | If you successfully migrated from other versions, please report your upgrade results in the [community forum](https://community.hedgedoc.org/). 34 | -------------------------------------------------------------------------------- /docs/content/theme/styles/Roboto/roboto-latin-regular.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/libnewton/hedgenext-server/6e5378fb762acd151cc7a818801d4a1e7d887579/docs/content/theme/styles/Roboto/roboto-latin-regular.woff -------------------------------------------------------------------------------- /docs/content/theme/styles/Roboto/roboto-latin-regular.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/libnewton/hedgenext-server/6e5378fb762acd151cc7a818801d4a1e7d887579/docs/content/theme/styles/Roboto/roboto-latin-regular.woff2 -------------------------------------------------------------------------------- /docs/content/theme/styles/Roboto/roboto-mono-latin-regular.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/libnewton/hedgenext-server/6e5378fb762acd151cc7a818801d4a1e7d887579/docs/content/theme/styles/Roboto/roboto-mono-latin-regular.woff -------------------------------------------------------------------------------- /docs/content/theme/styles/Roboto/roboto-mono-latin-regular.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/libnewton/hedgenext-server/6e5378fb762acd151cc7a818801d4a1e7d887579/docs/content/theme/styles/Roboto/roboto-mono-latin-regular.woff2 -------------------------------------------------------------------------------- /docs/content/theme/styles/hedgedoc-custom.css: -------------------------------------------------------------------------------- 1 | [data-md-color-scheme="slate"][data-md-color-primary=hedgedoc] { 2 | --md-hue: 8; 3 | --md-default-fg-color: hsla(var(--md-hue), 20%, 80%, 1); 4 | --md-default-fg-color--light: hsla(var(--md-hue), 20%, 80%, 1); 5 | --md-default-fg-color--lighter: hsla(var(--md-hue), 20%, 80%, 0.5); 6 | --md-default-fg-color--lightest: hsla(var(--md-hue), 20%, 80%, 0.2); 7 | 8 | --md-default-bg-color: hsla(var(--md-hue), 0%, 5%, 1); 9 | --md-default-bg-color--light: hsla(var(--md-hue), 0%, 5%, 0.54); 10 | --md-default-bg-color--lighter: hsla(var(--md-hue), 0%, 5%, 0.26); 11 | --md-default-bg-color--lightest: hsla(var(--md-hue), 0%, 5%, 0.07); 12 | 13 | --md-typeset-color: hsl(var(--md-hue), 5%, 80%); 14 | --md-typeset-a-color: hsl(var(--md-hue), 92%, 67%, 1); 15 | --md-primary-fg-color: hsl(var(--md-hue), 92%, 25%, 1); 16 | --md-table-header-fg-color: var(--md-typeset-color); 17 | --md-table-header-bg-color: hsla(var(--md-hue), 0%, 20%, 1); 18 | 19 | --md-code-fg-color: hsla(var(--md-hue),0%,86%,1); 20 | --md-code-bg-color: hsla(var(--md-hue),0%,15%,1); 21 | } 22 | 23 | [data-md-color-primary=hedgedoc] .md-typeset table:not([class]) th { 24 | background-color: var(--md-table-header-bg-color); 25 | color: var(--md-table-header-fg-color); 26 | } 27 | 28 | [data-md-color-primary=hedgedoc] { 29 | --md-hue: 8; 30 | --md-primary-fg-color: hsl(var(--md-hue), 92%, 37%, 1); 31 | --md-primary-fg-color--dark: var(--md-primary-fg-color); 32 | --md-primary-fg-color--light: var(--md-primary-fg-color); 33 | --md-primary-bg-color: hsla(0, 15%, 100%, 1); 34 | --md-footer-bg-color: hsla(var(--md-hue), 0%, 12%, 1); 35 | --md-footer-bg-color--dark: hsla(var(--md-hue), 0%, 16%, 1); 36 | 37 | --md-table-header-fg-color: var(--md-default-bg-color); 38 | --md-table-header-bg-color: var(--md-default-fg-color--light); 39 | } 40 | 41 | [data-md-color-accent=hedgedoc] { 42 | --md-accent-fg-color: #b51f08; 43 | --md-accent-fg-color--transparent: hsla(348, 100%, 55%, 0.1); 44 | --md-accent-bg-color: hsla(0, 0%, 100%, 1); 45 | --md-accent-bg-color--light: hsla(0, 0%, 100%, 0.7); 46 | } 47 | 48 | .md-grid { 49 | max-width: 1440px; 50 | } 51 | 52 | .twitter { 53 | color: #1DA1F2; 54 | } 55 | 56 | .mastodon { 57 | color: #2b90d9; 58 | } 59 | 60 | [data-md-color-scheme="slate"] .light-mode-only { 61 | display: none; 62 | } 63 | 64 | [data-md-color-scheme="light"] .dark-mode-only { 65 | display: none; 66 | } 67 | -------------------------------------------------------------------------------- /docs/content/theme/styles/roboto.css: -------------------------------------------------------------------------------- 1 | 2 | body, input { 3 | font-family: "Roboto",-apple-system,BlinkMacSystemFont,Helvetica,Arial,sans-serif; 4 | } 5 | 6 | code, kbd, pre { 7 | font-family: "Roboto Mono",SFMono-Regular,Consolas,Menlo,monospace; 8 | } 9 | 10 | @font-face { 11 | font-family: 'Roboto'; 12 | font-style: normal; 13 | font-weight: 400; 14 | font-display: swap; 15 | src: local('Roboto'), 16 | url('./Roboto/roboto-latin-regular.woff2') format('woff2'), 17 | url('./Roboto/roboto-latin-regular.woff') format('woff'), 18 | } 19 | 20 | @font-face { 21 | font-family: 'Roboto Mono'; 22 | font-style: normal; 23 | font-weight: 400; 24 | font-display: swap; 25 | src: local('Roboto Mono'), 26 | url('./Roboto/roboto-mono-latin-regular.woff2') format('woff2'), 27 | url('./Roboto/roboto-mono-latin-regular.woff') format('woff'), 28 | } 29 | -------------------------------------------------------------------------------- /docs/requirements.txt: -------------------------------------------------------------------------------- 1 | mkdocs==1.4.2 2 | mkdocs-material==9.0.13 3 | pymdown-extensions==9.9.2 4 | mdx_truly_sane_lists==1.3 5 | mkdocs-redirects==1.2.0 6 | -------------------------------------------------------------------------------- /lib/config/buildDomainOriginWithProtocol.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | buildDomainOriginWithProtocol: function (config, baseProtocol) { 3 | const isStandardHTTPsPort = config.protocolUseSSL && config.port === 443 4 | const isStandardHTTPPort = !config.protocolUseSSL && config.port === 80 5 | 6 | if (!config.domain) { 7 | return '' 8 | } 9 | let origin = '' 10 | const protocol = baseProtocol + (config.protocolUseSSL ? 's' : '') + '://' 11 | origin = protocol + config.domain 12 | if (config.urlAddPort) { 13 | if (!isStandardHTTPPort || !isStandardHTTPsPort) { 14 | origin += ':' + config.port 15 | } 16 | } 17 | return origin 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /lib/config/defaultSSL.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const fs = require('fs') 4 | 5 | function getFile (path) { 6 | if (fs.existsSync(path)) { 7 | return path 8 | } 9 | return undefined 10 | } 11 | 12 | module.exports = { 13 | sslKeyPath: getFile('/run/secrets/key.pem'), 14 | sslCertPath: getFile('/run/secrets/cert.pem'), 15 | sslCAPath: getFile('/run/secrets/ca.pem') !== undefined ? [getFile('/run/secrets/ca.pem')] : [], 16 | dhParamPath: getFile('/run/secrets/dhparam.pem') 17 | } 18 | -------------------------------------------------------------------------------- /lib/config/dockerSecret.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const fs = require('fs') 4 | const path = require('path') 5 | 6 | const basePath = path.resolve('/run/secrets/') 7 | 8 | function getSecret (secret) { 9 | const filePath = path.join(basePath, secret) 10 | if (fs.existsSync(filePath)) return fs.readFileSync(filePath, 'utf-8') 11 | return undefined 12 | } 13 | 14 | if (fs.existsSync(basePath)) { 15 | module.exports = { 16 | dbURL: getSecret('dbURL'), 17 | sessionSecret: getSecret('sessionsecret'), 18 | sslKeyPath: getSecret('sslkeypath'), 19 | sslCertPath: getSecret('sslcertpath'), 20 | sslCAPath: getSecret('sslcapath'), 21 | dhParamPath: getSecret('dhparampath'), 22 | s3: { 23 | accessKeyId: getSecret('s3_acccessKeyId'), 24 | secretAccessKey: getSecret('s3_secretAccessKey') 25 | }, 26 | azure: { 27 | connectionString: getSecret('azure_connectionString') 28 | }, 29 | facebook: { 30 | clientID: getSecret('facebook_clientID'), 31 | clientSecret: getSecret('facebook_clientSecret') 32 | }, 33 | twitter: { 34 | consumerKey: getSecret('twitter_consumerKey'), 35 | consumerSecret: getSecret('twitter_consumerSecret') 36 | }, 37 | github: { 38 | clientID: getSecret('github_clientID'), 39 | clientSecret: getSecret('github_clientSecret') 40 | }, 41 | gitlab: { 42 | clientID: getSecret('gitlab_clientID'), 43 | clientSecret: getSecret('gitlab_clientSecret') 44 | }, 45 | mattermost: { 46 | clientID: getSecret('mattermost_clientID'), 47 | clientSecret: getSecret('mattermost_clientSecret') 48 | }, 49 | dropbox: { 50 | clientID: getSecret('dropbox_clientID'), 51 | clientSecret: getSecret('dropbox_clientSecret'), 52 | appKey: getSecret('dropbox_appKey') 53 | }, 54 | google: { 55 | clientID: getSecret('google_clientID'), 56 | clientSecret: getSecret('google_clientSecret'), 57 | hostedDomain: getSecret('google_hostedDomain') 58 | }, 59 | imgur: getSecret('imgur_clientid') 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /lib/config/enum.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | exports.Environment = { 4 | development: 'development', 5 | production: 'production', 6 | test: 'test' 7 | } 8 | 9 | exports.Permission = { 10 | freely: 'freely', 11 | editable: 'editable', 12 | limited: 'limited', 13 | locked: 'locked', 14 | protected: 'protected', 15 | private: 'private' 16 | } 17 | -------------------------------------------------------------------------------- /lib/config/oldDefault.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | module.exports = { 4 | urlpath: undefined, 5 | urladdport: undefined, 6 | alloworigin: undefined, 7 | usessl: undefined, 8 | protocolusessl: undefined, 9 | allowanonymous: undefined, 10 | allowanonymousedits: undefined, 11 | allowfreeurl: undefined, 12 | defaultpermission: undefined, 13 | dburl: undefined, 14 | // ssl path 15 | sslkeypath: undefined, 16 | sslcertpath: undefined, 17 | sslcapath: undefined, 18 | dhparampath: undefined, 19 | // other path 20 | tmppath: undefined, 21 | defaultnotepath: undefined, 22 | docspath: undefined, 23 | indexpath: undefined, 24 | hackmdpath: undefined, 25 | errorpath: undefined, 26 | prettypath: undefined, 27 | slidepath: undefined, 28 | // session 29 | sessionname: undefined, 30 | sessionsecret: undefined, 31 | sessionlife: undefined, 32 | staticcachetime: undefined, 33 | // socket.io 34 | heartbeatinterval: undefined, 35 | heartbeattimeout: undefined, 36 | // document 37 | documentmaxlength: undefined, 38 | imageuploadtype: undefined, 39 | allowemailregister: undefined 40 | } 41 | -------------------------------------------------------------------------------- /lib/config/oldEnvironment.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const { toBooleanConfig } = require('./utils') 4 | 5 | module.exports = { 6 | debug: toBooleanConfig(process.env.DEBUG), 7 | dburl: process.env.DATABASE_URL, 8 | urlpath: process.env.URL_PATH, 9 | port: process.env.PORT 10 | } 11 | -------------------------------------------------------------------------------- /lib/config/utils.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const fs = require('fs') 4 | const path = require('path') 5 | 6 | exports.toBooleanConfig = function toBooleanConfig (configValue) { 7 | if (configValue && typeof configValue === 'string') { 8 | return (configValue === 'true') 9 | } 10 | return configValue 11 | } 12 | 13 | exports.toArrayConfig = function toArrayConfig (configValue, separator = ',', fallback) { 14 | if (configValue && typeof configValue === 'string') { 15 | return (configValue.split(separator).map(arrayItem => arrayItem.trim())) 16 | } 17 | return fallback 18 | } 19 | 20 | exports.toIntegerConfig = function toIntegerConfig (configValue) { 21 | if (configValue && typeof configValue === 'string') { 22 | return parseInt(configValue) 23 | } 24 | return configValue 25 | } 26 | 27 | exports.getGitCommit = function getGitCommit (repodir) { 28 | try { 29 | // prefer using git to get the current ref, as poking in .git is very fragile 30 | return require('child_process').execSync('git rev-parse HEAD', { 31 | stdio: ['pipe', 'pipe', 'ignore'], 32 | encoding: 'utf-8' 33 | }).replace('\n', '') 34 | } catch (e) { 35 | // there was an error running git, try to parse refs ourselves 36 | if (!fs.existsSync(repodir + '/.git/HEAD')) { 37 | // there is no HEAD information 38 | return undefined 39 | } 40 | let reference = fs.readFileSync(repodir + '/.git/HEAD', 'utf8') 41 | if (reference.startsWith('ref: ')) { 42 | // HEAD references another ref, try to get the commit SHA from .git/ref/heads 43 | reference = reference.substr(5).replace('\n', '') 44 | const refPath = path.resolve(repodir + '/.git', reference) 45 | if (!fs.existsSync(refPath)) { 46 | // ref does not exist in .git/ref/heads 47 | return undefined 48 | } 49 | reference = fs.readFileSync(refPath, 'utf8') 50 | } 51 | reference = reference.replace('\n', '') 52 | return reference 53 | } 54 | } 55 | 56 | exports.getGitHubURL = function getGitHubURL (repo, reference) { 57 | // if it's not a github reference, we handle handle that anyway 58 | if (!repo.startsWith('https://github.com') && !repo.startsWith('git@github.com')) { 59 | return repo 60 | } 61 | if (repo.startsWith('git@github.com') || repo.startsWith('ssh://git@github.com')) { 62 | repo = repo.replace(/^(ssh:\/\/)?git@github.com:/, 'https://github.com/') 63 | } 64 | 65 | if (repo.endsWith('.git')) { 66 | repo = repo.replace(/\.git$/, '/') 67 | } else if (!repo.endsWith('/')) { 68 | repo = repo + '/' 69 | } 70 | return repo + 'tree/' + reference 71 | } 72 | -------------------------------------------------------------------------------- /lib/errors.js: -------------------------------------------------------------------------------- 1 | const config = require('./config') 2 | 3 | module.exports = { 4 | errorForbidden: function (res) { 5 | const { req } = res 6 | if (req.user) { 7 | responseError(res, 403, 'Forbidden', 'oh no.') 8 | } else { 9 | if (!req.session) req.session = {} 10 | if (req.originalUrl !== '/403') { 11 | req.session.returnTo = config.serverURL + (req.originalUrl || '/') 12 | req.flash('error', 'You are not allowed to access this page. Maybe try logging in?') 13 | } 14 | res.redirect(config.serverURL + '/auth/oauth2') 15 | } 16 | }, 17 | errorNotFound: function (res) { 18 | responseError(res, 404, 'Not Found', 'oops.') 19 | }, 20 | errorBadRequest: function (res) { 21 | responseError(res, 400, 'Bad Request', 'something not right.') 22 | }, 23 | errorConflict: function (res) { 24 | responseError(res, 409, 'Conflict', 'This note already exists.') 25 | }, 26 | errorTooLong: function (res) { 27 | responseError(res, 413, 'Payload Too Large', 'Shorten your note!') 28 | }, 29 | errorInternalError: function (res) { 30 | responseError(res, 500, 'Internal Error', 'wtf.') 31 | }, 32 | errorServiceUnavailable: function (res) { 33 | res.status(503).send('I\'m busy right now, try again later.') 34 | } 35 | } 36 | 37 | function responseError (res, code, detail, msg) { 38 | res.status(code).render('error.ejs', { 39 | title: code + ' ' + detail + ' ' + msg, 40 | code, 41 | detail, 42 | msg, 43 | opengraph: [] 44 | }) 45 | } 46 | -------------------------------------------------------------------------------- /lib/letter-avatars.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | // external modules 3 | const crypto = require('crypto') 4 | const randomcolor = require('randomcolor') 5 | const config = require('./config') 6 | 7 | // core 8 | exports.generateAvatar = function (name) { 9 | const color = randomcolor({ 10 | seed: name, 11 | luminosity: 'dark' 12 | }) 13 | const letter = name.substring(0, 1).toUpperCase() 14 | 15 | let svg = '' 16 | svg += '' 17 | svg += '' 18 | svg += '' 19 | svg += '' 20 | svg += '' + letter + '' 21 | svg += '' 22 | svg += '' 23 | svg += '' 24 | 25 | return svg 26 | } 27 | 28 | exports.generateAvatarURL = function (name, email = '', big = true) { 29 | let photo 30 | if (typeof email !== 'string') { 31 | email = '' + name + '@example.com' 32 | } 33 | name = encodeURIComponent(name) 34 | 35 | const hash = crypto.createHash('md5') 36 | hash.update(email.toLowerCase()) 37 | const hexDigest = hash.digest('hex') 38 | 39 | if (email !== '' && config.allowGravatar) { 40 | photo = `https://cdn.libravatar.org/avatar/${hexDigest}?default=identicon` 41 | if (big) { 42 | photo += '&s=400' 43 | } else { 44 | photo += '&s=96' 45 | } 46 | } else { 47 | photo = config.serverURL + '/user/' + (name || email.substring(0, email.lastIndexOf('@')) || hexDigest) + '/avatar.svg' 48 | } 49 | return photo 50 | } 51 | -------------------------------------------------------------------------------- /lib/logger.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | const { createLogger, format, transports } = require('winston') 3 | 4 | const logger = createLogger({ 5 | level: 'debug', 6 | format: format.combine( 7 | format.uncolorize(), 8 | format.timestamp(), 9 | format.align(), 10 | format.splat(), 11 | format.printf(info => `${info.timestamp} ${info.level}: ${info.message}`) 12 | ), 13 | transports: [ 14 | new transports.Console({ 15 | handleExceptions: true 16 | }) 17 | ], 18 | exitOnError: false 19 | }) 20 | 21 | logger.stream = { 22 | write: function (message, encoding) { 23 | logger.info(message) 24 | } 25 | } 26 | 27 | module.exports = logger 28 | -------------------------------------------------------------------------------- /lib/migrations/20150504155329-create-users.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | module.exports = { 3 | up: function (queryInterface, Sequelize) { 4 | return queryInterface.createTable('Users', { 5 | id: { 6 | type: Sequelize.UUID, 7 | primaryKey: true, 8 | defaultValue: Sequelize.UUIDV4 9 | }, 10 | profileid: { 11 | type: Sequelize.STRING, 12 | unique: true 13 | }, 14 | profile: Sequelize.TEXT, 15 | history: Sequelize.TEXT, 16 | createdAt: Sequelize.DATE, 17 | updatedAt: Sequelize.DATE 18 | }) 19 | }, 20 | 21 | down: function (queryInterface, Sequelize) { 22 | return queryInterface.dropTable('Users') 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /lib/migrations/20150508114741-create-notes.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | module.exports = { 3 | up: function (queryInterface, Sequelize) { 4 | return queryInterface.createTable('Notes', { 5 | id: { 6 | type: Sequelize.UUID, 7 | primaryKey: true, 8 | defaultValue: Sequelize.UUIDV4 9 | }, 10 | ownerId: Sequelize.UUID, 11 | content: Sequelize.TEXT, 12 | title: Sequelize.STRING, 13 | createdAt: Sequelize.DATE, 14 | updatedAt: Sequelize.DATE 15 | }) 16 | }, 17 | 18 | down: function (queryInterface, Sequelize) { 19 | return queryInterface.dropTable('Notes') 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /lib/migrations/20150515125813-create-temp.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | module.exports = { 3 | up: function (queryInterface, Sequelize) { 4 | return queryInterface.createTable('Temp', { 5 | id: { 6 | type: Sequelize.STRING, 7 | primaryKey: true 8 | }, 9 | date: Sequelize.TEXT, 10 | createdAt: Sequelize.DATE, 11 | updatedAt: Sequelize.DATE 12 | }) 13 | }, 14 | 15 | down: function (queryInterface, Sequelize) { 16 | return queryInterface.dropTable('Temp') 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /lib/migrations/20150702001020-update-to-0_3_1.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | module.exports = { 3 | up: function (queryInterface, Sequelize) { 4 | return queryInterface.addColumn('Notes', 'shortid', { 5 | type: Sequelize.STRING, 6 | defaultValue: '0000000000', 7 | allowNull: false 8 | }).then(function () { 9 | return queryInterface.addIndex('Notes', ['shortid'], { 10 | indicesType: 'UNIQUE' 11 | }) 12 | }).then(function () { 13 | return queryInterface.addColumn('Notes', 'permission', { 14 | type: Sequelize.STRING, 15 | defaultValue: 'private', 16 | allowNull: false 17 | }) 18 | }).then(function () { 19 | return queryInterface.addColumn('Notes', 'viewcount', { 20 | type: Sequelize.INTEGER, 21 | defaultValue: 0 22 | }) 23 | }).catch(function (error) { 24 | if (error.message === 'column "shortid" of relation "Notes" already exists' || 25 | error.message.toLowerCase().includes('duplicate column name')) { 26 | // eslint-disable-next-line no-console 27 | console.log('Migration has already run… ignoring.') 28 | } else { 29 | throw error 30 | } 31 | }) 32 | }, 33 | 34 | down: function (queryInterface, Sequelize) { 35 | return queryInterface.removeColumn('Notes', 'viewcount') 36 | .then(function () { 37 | return queryInterface.removeColumn('Notes', 'permission') 38 | }) 39 | .then(function () { 40 | return queryInterface.removeIndex('Notes', ['shortid']) 41 | }) 42 | .then(function () { 43 | return queryInterface.removeColumn('Notes', 'shortid') 44 | }) 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /lib/migrations/20150915153700-change-notes-title-to-text.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | const isSQLite = require('../utils').isSQLite 3 | module.exports = { 4 | up: function (queryInterface, Sequelize) { 5 | return queryInterface.changeColumn('Notes', 'title', { 6 | type: Sequelize.TEXT 7 | }).then(function () { 8 | if (isSQLite(queryInterface.sequelize)) { 9 | // manual added index will be removed in sqlite 10 | return queryInterface.addIndex('Notes', ['shortid']) 11 | } 12 | }) 13 | }, 14 | 15 | down: function (queryInterface, Sequelize) { 16 | return queryInterface.changeColumn('Notes', 'title', { 17 | type: Sequelize.STRING 18 | }).then(function () { 19 | if (isSQLite(queryInterface.sequelize)) { 20 | // manual added index will be removed in sqlite 21 | return queryInterface.addIndex('Notes', ['shortid']) 22 | } 23 | }) 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /lib/migrations/20160112220142-note-add-lastchange.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | module.exports = { 3 | up: function (queryInterface, Sequelize) { 4 | return queryInterface.addColumn('Notes', 'lastchangeuserId', { 5 | type: Sequelize.UUID 6 | }).then(function () { 7 | return queryInterface.addColumn('Notes', 'lastchangeAt', { 8 | type: Sequelize.DATE 9 | }) 10 | }).catch(function (error) { 11 | if (error.message === 'column "lastchangeuserId" of relation "Notes" already exists' || 12 | error.message.toLowerCase().includes('duplicate column name')) { 13 | // eslint-disable-next-line no-console 14 | console.log('Migration has already run… ignoring.') 15 | } else { 16 | throw error 17 | } 18 | }) 19 | }, 20 | 21 | down: function (queryInterface, Sequelize) { 22 | return queryInterface.removeColumn('Notes', 'lastchangeAt') 23 | .then(function () { 24 | return queryInterface.removeColumn('Notes', 'lastchangeuserId') 25 | }) 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /lib/migrations/20160420180355-note-add-alias.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | module.exports = { 3 | up: function (queryInterface, Sequelize) { 4 | return queryInterface.addColumn('Notes', 'alias', { 5 | type: Sequelize.STRING 6 | }).then(function () { 7 | return queryInterface.addIndex('Notes', ['alias'], { 8 | indicesType: 'UNIQUE' 9 | }) 10 | }).catch(function (error) { 11 | if (error.message.toLowerCase().includes('duplicate column name') || 12 | error.message === 'column "alias" of relation "Notes" already exists') { 13 | // eslint-disable-next-line no-console 14 | console.log('Migration has already run… ignoring.') 15 | } else { 16 | throw error 17 | } 18 | }) 19 | }, 20 | 21 | down: function (queryInterface, Sequelize) { 22 | return queryInterface.removeColumn('Notes', 'alias').then(function () { 23 | return queryInterface.removeIndex('Notes', ['alias']) 24 | }) 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /lib/migrations/20160515114000-user-add-tokens.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | module.exports = { 3 | up: function (queryInterface, Sequelize) { 4 | return queryInterface.addColumn('Users', 'accessToken', Sequelize.STRING).then(function () { 5 | return queryInterface.addColumn('Users', 'refreshToken', Sequelize.STRING) 6 | }).catch(function (error) { 7 | if (error.message.toLowerCase().includes('duplicate column name') || 8 | error.message === 'column "accessToken" of relation "Users" already exists') { 9 | // eslint-disable-next-line no-console 10 | console.log('Migration has already run… ignoring.') 11 | } else { 12 | throw error 13 | } 14 | }) 15 | }, 16 | 17 | down: function (queryInterface, Sequelize) { 18 | return queryInterface.removeColumn('Users', 'accessToken').then(function () { 19 | return queryInterface.removeColumn('Users', 'refreshToken') 20 | }) 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /lib/migrations/20160607060246-support-revision.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | module.exports = { 3 | up: function (queryInterface, Sequelize) { 4 | return queryInterface.addColumn('Notes', 'savedAt', Sequelize.DATE).then(function () { 5 | return queryInterface.createTable('Revisions', { 6 | id: { 7 | type: Sequelize.UUID, 8 | primaryKey: true 9 | }, 10 | noteId: Sequelize.UUID, 11 | patch: Sequelize.TEXT, 12 | lastContent: Sequelize.TEXT, 13 | content: Sequelize.TEXT, 14 | length: Sequelize.INTEGER, 15 | createdAt: Sequelize.DATE, 16 | updatedAt: Sequelize.DATE 17 | }) 18 | }).catch(function (error) { 19 | if (error.message.toLowerCase().includes('duplicate column name') || 20 | error.message === 'column "savedAt" of relation "Notes" already exists') { 21 | // eslint-disable-next-line no-console 22 | console.log('Migration has already run… ignoring.') 23 | } else { 24 | throw error 25 | } 26 | }) 27 | }, 28 | 29 | down: function (queryInterface, Sequelize) { 30 | return queryInterface.dropTable('Revisions').then(function () { 31 | return queryInterface.removeColumn('Notes', 'savedAt') 32 | }) 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /lib/migrations/20160703062241-support-authorship.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | module.exports = { 3 | up: function (queryInterface, Sequelize) { 4 | return queryInterface.addColumn('Notes', 'authorship', Sequelize.TEXT).then(function () { 5 | return queryInterface.addColumn('Revisions', 'authorship', Sequelize.TEXT) 6 | }).then(function () { 7 | return queryInterface.createTable('Authors', { 8 | id: { 9 | type: Sequelize.INTEGER, 10 | primaryKey: true, 11 | autoIncrement: true 12 | }, 13 | color: Sequelize.STRING, 14 | noteId: Sequelize.UUID, 15 | userId: Sequelize.UUID, 16 | createdAt: Sequelize.DATE, 17 | updatedAt: Sequelize.DATE 18 | }) 19 | }).catch(function (error) { 20 | if (error.message.toLowerCase().includes('duplicate column name') || 21 | error.message === 'column "authorship" of relation "Notes" already exists') { 22 | // eslint-disable-next-line no-console 23 | console.log('Migration has already run… ignoring.') 24 | } else { 25 | throw error 26 | } 27 | }) 28 | }, 29 | 30 | down: function (queryInterface, Sequelize) { 31 | return queryInterface.dropTable('Authors').then(function () { 32 | return queryInterface.removeColumn('Revisions', 'authorship') 33 | }).then(function () { 34 | return queryInterface.removeColumn('Notes', 'authorship') 35 | }) 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /lib/migrations/20161009040430-support-delete-note.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | module.exports = { 3 | up: function (queryInterface, Sequelize) { 4 | return queryInterface.addColumn('Notes', 'deletedAt', Sequelize.DATE).catch(function (error) { 5 | if (error.message.toLowerCase().includes('duplicate column name') || 6 | error.message === 'column "deletedAt" of relation "Notes" already exists') { 7 | // eslint-disable-next-line no-console 8 | console.log('Migration has already run… ignoring.') 9 | } else { 10 | throw error 11 | } 12 | }) 13 | }, 14 | 15 | down: function (queryInterface, Sequelize) { 16 | return queryInterface.removeColumn('Notes', 'deletedAt') 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /lib/migrations/20161201050312-support-email-signin.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | module.exports = { 3 | up: function (queryInterface, Sequelize) { 4 | return queryInterface.addColumn('Users', 'email', Sequelize.TEXT).then(function () { 5 | return queryInterface.addColumn('Users', 'password', Sequelize.TEXT).catch(function (error) { 6 | if (error.message.toLowerCase().includes('duplicate column name') || 7 | error.message === 'column "password" of relation "Users" already exists') { 8 | // eslint-disable-next-line no-console 9 | console.log('Migration has already run… ignoring.') 10 | } else { 11 | throw error 12 | } 13 | }) 14 | }).catch(function (error) { 15 | if (error.message.toLowerCase().includes('duplicate column name') || 16 | error.message === 'column "email" of relation "Users" already exists') { 17 | // eslint-disable-next-line no-console 18 | console.log('Migration has already run… ignoring.') 19 | } else { 20 | throw error 21 | } 22 | }) 23 | }, 24 | 25 | down: function (queryInterface, Sequelize) { 26 | return queryInterface.removeColumn('Users', 'email').then(function () { 27 | return queryInterface.removeColumn('Users', 'password') 28 | }) 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /lib/migrations/20171009121200-longtext-for-mysql.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | module.exports = { 3 | up: async function (queryInterface, Sequelize) { 4 | await queryInterface.changeColumn('Notes', 'content', { type: Sequelize.TEXT('long') }) 5 | await queryInterface.changeColumn('Revisions', 'patch', { type: Sequelize.TEXT('long') }) 6 | await queryInterface.changeColumn('Revisions', 'content', { type: Sequelize.TEXT('long') }) 7 | await queryInterface.changeColumn('Revisions', 'lastContent', { type: Sequelize.TEXT('long') }) 8 | }, 9 | 10 | down: async function (queryInterface, Sequelize) { 11 | await queryInterface.changeColumn('Notes', 'content', { type: Sequelize.TEXT }) 12 | await queryInterface.changeColumn('Revisions', 'patch', { type: Sequelize.TEXT }) 13 | await queryInterface.changeColumn('Revisions', 'content', { type: Sequelize.TEXT }) 14 | await queryInterface.changeColumn('Revisions', 'lastContent', { type: Sequelize.TEXT }) 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /lib/migrations/20180209120907-longtext-of-authorship.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | module.exports = { 4 | up: async function (queryInterface, Sequelize) { 5 | await queryInterface.changeColumn('Notes', 'authorship', { type: Sequelize.TEXT('long') }) 6 | await queryInterface.changeColumn('Revisions', 'authorship', { type: Sequelize.TEXT('long') }) 7 | }, 8 | 9 | down: async function (queryInterface, Sequelize) { 10 | await queryInterface.changeColumn('Notes', 'authorship', { type: Sequelize.TEXT }) 11 | await queryInterface.changeColumn('Revisions', 'authorship', { type: Sequelize.TEXT }) 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /lib/migrations/20180306150303-fix-enum.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | module.exports = { 4 | up: function (queryInterface, Sequelize) { 5 | return queryInterface.changeColumn('Notes', 'permission', { type: Sequelize.ENUM('freely', 'editable', 'limited', 'locked', 'protected', 'private') }) 6 | }, 7 | 8 | down: function (queryInterface, Sequelize) { 9 | return queryInterface.changeColumn('Notes', 'permission', { type: Sequelize.ENUM('freely', 'editable', 'locked', 'private') }) 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /lib/migrations/20180326103000-use-text-in-tokens.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | module.exports = { 4 | up: function (queryInterface, Sequelize) { 5 | return queryInterface.changeColumn('Users', 'accessToken', { 6 | type: Sequelize.TEXT 7 | }).then(function () { 8 | return queryInterface.changeColumn('Users', 'refreshToken', { 9 | type: Sequelize.TEXT 10 | }) 11 | }) 12 | }, 13 | 14 | down: function (queryInterface, Sequelize) { 15 | return queryInterface.changeColumn('Users', 'accessToken', { 16 | type: Sequelize.STRING 17 | }).then(function () { 18 | return queryInterface.changeColumn('Users', 'refreshToken', { 19 | type: Sequelize.STRING 20 | }) 21 | }) 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /lib/migrations/20180525153000-user-add-delete-token.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | module.exports = { 3 | up: function (queryInterface, Sequelize) { 4 | return queryInterface.addColumn('Users', 'deleteToken', { 5 | type: Sequelize.UUID, 6 | defaultValue: Sequelize.UUIDV4 7 | }).catch(function (error) { 8 | if (error.message.toLowerCase().includes('duplicate column name') || 9 | error.message === 'column "deleteToken" of relation "Users" already exists') { 10 | // eslint-disable-next-line no-console 11 | console.log('Migration has already run… ignoring.') 12 | } else { 13 | throw error 14 | } 15 | }) 16 | }, 17 | 18 | down: function (queryInterface, Sequelize) { 19 | return queryInterface.removeColumn('Users', 'deleteToken') 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /lib/migrations/20220901102800-convert-history-to-longtext.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | module.exports = { 4 | up: function (queryInterface, Sequelize) { 5 | return queryInterface.changeColumn('Users', 'history', { 6 | type: Sequelize.TEXT('long') 7 | }) 8 | }, 9 | 10 | down: function (queryInterface, Sequelize) { 11 | return queryInterface.changeColumn('Users', 'history', { 12 | type: Sequelize.TEXT 13 | }) 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /lib/migrations/20230207060246-support-templates.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | module.exports = { 3 | up: function (queryInterface, Sequelize) { 4 | return queryInterface.addColumn('Users', 'templates', { 5 | type: Sequelize.TEXT('long'), 6 | defaultValue: '[]' 7 | }).catch(function (error) { 8 | if (error.message.toLowerCase().includes('duplicate column name') || 9 | error.message === 'column "templates" of relation "Users" already exists') { 10 | // eslint-disable-next-line no-console 11 | console.log('Migration has already run… ignoring.') 12 | } else { 13 | throw error 14 | } 15 | }) 16 | }, 17 | 18 | down: function (queryInterface, Sequelize) { 19 | return queryInterface.removeColumn('Users', 'templates') 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /lib/models/author.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | // external modules 3 | const Sequelize = require('sequelize') 4 | 5 | module.exports = function (sequelize, DataTypes) { 6 | const Author = sequelize.define('Author', { 7 | id: { 8 | type: Sequelize.INTEGER, 9 | primaryKey: true, 10 | autoIncrement: true 11 | }, 12 | color: { 13 | type: DataTypes.STRING 14 | } 15 | }, { 16 | indexes: [ 17 | { 18 | unique: true, 19 | fields: ['noteId', 'userId'] 20 | } 21 | ] 22 | }) 23 | 24 | Author.associate = function (models) { 25 | Author.belongsTo(models.Note, { 26 | foreignKey: 'noteId', 27 | as: 'note', 28 | constraints: false, 29 | onDelete: 'CASCADE', 30 | hooks: true 31 | }) 32 | Author.belongsTo(models.User, { 33 | foreignKey: 'userId', 34 | as: 'user', 35 | constraints: false, 36 | onDelete: 'CASCADE', 37 | hooks: true 38 | }) 39 | } 40 | 41 | return Author 42 | } 43 | -------------------------------------------------------------------------------- /lib/models/temp.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | // external modules 3 | const shortId = require('shortid') 4 | 5 | module.exports = function (sequelize, DataTypes) { 6 | const Temp = sequelize.define('Temp', { 7 | id: { 8 | type: DataTypes.STRING, 9 | primaryKey: true, 10 | defaultValue: shortId.generate 11 | }, 12 | data: { 13 | type: DataTypes.TEXT 14 | } 15 | }) 16 | 17 | return Temp 18 | } 19 | -------------------------------------------------------------------------------- /lib/ot/index.js: -------------------------------------------------------------------------------- 1 | exports.version = '0.0.15'; 2 | 3 | exports.TextOperation = require('./text-operation'); 4 | exports.SimpleTextOperation = require('./simple-text-operation'); 5 | exports.Client = require('./client'); 6 | exports.Server = require('./server'); 7 | exports.Selection = require('./selection'); 8 | exports.EditorSocketIOServer = require('./editor-socketio-server'); 9 | -------------------------------------------------------------------------------- /lib/ot/server.js: -------------------------------------------------------------------------------- 1 | var config = require('../config'); 2 | 3 | if (typeof ot === 'undefined') { 4 | var ot = {}; 5 | } 6 | 7 | ot.Server = (function (global) { 8 | 'use strict'; 9 | 10 | // Constructor. Takes the current document as a string and optionally the array 11 | // of all operations. 12 | function Server (document, operations) { 13 | this.document = document; 14 | this.operations = operations || []; 15 | } 16 | 17 | // Call this method whenever you receive an operation from a client. 18 | Server.prototype.receiveOperation = function (revision, operation) { 19 | if (revision < 0 || this.operations.length < revision) { 20 | throw new Error("operation revision not in history"); 21 | } 22 | // Find all operations that the client didn't know of when it sent the 23 | // operation ... 24 | var concurrentOperations = this.operations.slice(revision); 25 | 26 | // ... and transform the operation against all these operations ... 27 | var transform = operation.constructor.transform; 28 | for (var i = 0; i < concurrentOperations.length; i++) { 29 | operation = transform(operation, concurrentOperations[i])[0]; 30 | } 31 | 32 | // ... and apply that on the document. 33 | var newDocument = operation.apply(this.document); 34 | // ignore if exceed the max length of document 35 | if(newDocument.length > config.documentMaxLength && newDocument.length > this.document.length) 36 | return; 37 | this.document = newDocument; 38 | // Store operation in history. 39 | this.operations.push(operation); 40 | 41 | // It's the caller's responsibility to send the operation to all connected 42 | // clients and an acknowledgement to the creator. 43 | return operation; 44 | }; 45 | 46 | return Server; 47 | 48 | }(this)); 49 | 50 | if (typeof module === 'object') { 51 | module.exports = ot.Server; 52 | } -------------------------------------------------------------------------------- /lib/ot/wrapped-operation.js: -------------------------------------------------------------------------------- 1 | if (typeof ot === 'undefined') { 2 | // Export for browsers 3 | var ot = {}; 4 | } 5 | 6 | ot.WrappedOperation = (function (global) { 7 | 'use strict'; 8 | 9 | // A WrappedOperation contains an operation and corresponing metadata. 10 | function WrappedOperation (operation, meta) { 11 | this.wrapped = operation; 12 | this.meta = meta; 13 | } 14 | 15 | WrappedOperation.prototype.apply = function () { 16 | return this.wrapped.apply.apply(this.wrapped, arguments); 17 | }; 18 | 19 | WrappedOperation.prototype.invert = function () { 20 | var meta = this.meta; 21 | return new WrappedOperation( 22 | this.wrapped.invert.apply(this.wrapped, arguments), 23 | meta && typeof meta === 'object' && typeof meta.invert === 'function' ? 24 | meta.invert.apply(meta, arguments) : meta 25 | ); 26 | }; 27 | 28 | // Copy all properties from source to target. 29 | function copy (source, target) { 30 | for (var key in source) { 31 | if (source.hasOwnProperty(key)) { 32 | target[key] = source[key]; 33 | } 34 | } 35 | } 36 | 37 | function composeMeta (a, b) { 38 | if (a && typeof a === 'object') { 39 | if (typeof a.compose === 'function') { return a.compose(b); } 40 | var meta = {}; 41 | copy(a, meta); 42 | copy(b, meta); 43 | return meta; 44 | } 45 | return b; 46 | } 47 | 48 | WrappedOperation.prototype.compose = function (other) { 49 | return new WrappedOperation( 50 | this.wrapped.compose(other.wrapped), 51 | composeMeta(this.meta, other.meta) 52 | ); 53 | }; 54 | 55 | function transformMeta (meta, operation) { 56 | if (meta && typeof meta === 'object') { 57 | if (typeof meta.transform === 'function') { 58 | return meta.transform(operation); 59 | } 60 | } 61 | return meta; 62 | } 63 | 64 | WrappedOperation.transform = function (a, b) { 65 | var transform = a.wrapped.constructor.transform; 66 | var pair = transform(a.wrapped, b.wrapped); 67 | return [ 68 | new WrappedOperation(pair[0], transformMeta(a.meta, b.wrapped)), 69 | new WrappedOperation(pair[1], transformMeta(b.meta, a.wrapped)) 70 | ]; 71 | }; 72 | 73 | return WrappedOperation; 74 | 75 | }(this)); 76 | 77 | // Export for CommonJS 78 | if (typeof module === 'object') { 79 | module.exports = ot.WrappedOperation; 80 | } -------------------------------------------------------------------------------- /lib/utils.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | exports.isSQLite = function isSQLite (sequelize) { 4 | return sequelize.options.dialect === 'sqlite' 5 | } 6 | 7 | exports.getImageMimeType = function getImageMimeType (imagePath) { 8 | const fileExtension = /[^.]+$/.exec(imagePath) 9 | 10 | switch (fileExtension[0].toLowerCase()) { 11 | case 'bmp': 12 | return 'image/bmp' 13 | case 'gif': 14 | return 'image/gif' 15 | case 'jpg': 16 | case 'jpeg': 17 | return 'image/jpeg' 18 | case 'png': 19 | return 'image/png' 20 | case 'tiff': 21 | return 'image/tiff' 22 | case 'svg': 23 | return 'image/svg+xml' 24 | default: 25 | return undefined 26 | } 27 | } 28 | 29 | exports.useUnless = function excludeRoute (paths, middleware) { 30 | return function (req, res, next) { 31 | if (paths.includes(req.path)) { 32 | return next() 33 | } 34 | return middleware(req, res, next) 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /lib/web/auth/email/index.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const Router = require('express').Router 4 | const passport = require('passport') 5 | const validator = require('validator') 6 | const LocalStrategy = require('passport-local').Strategy 7 | const config = require('../../../config') 8 | const models = require('../../../models') 9 | const logger = require('../../../logger') 10 | const { urlencodedParser } = require('../../utils') 11 | const errors = require('../../../errors') 12 | 13 | const emailAuth = module.exports = Router() 14 | 15 | passport.use(new LocalStrategy({ 16 | usernameField: 'email' 17 | }, function (email, password, done) { 18 | if (!validator.isEmail(email)) return done(null, false) 19 | models.User.findOne({ 20 | where: { 21 | email 22 | } 23 | }).then(function (user) { 24 | if (!user) return done(null, false) 25 | user.verifyPassword(password).then(verified => { 26 | if (verified) { 27 | return done(null, user) 28 | } else { 29 | logger.warn('invalid password given for %s', user.email) 30 | return done(null, false) 31 | } 32 | }) 33 | }).catch(function (err) { 34 | logger.error(err) 35 | return done(err) 36 | }) 37 | })) 38 | 39 | if (config.allowEmailRegister) { 40 | emailAuth.post('/register', urlencodedParser, function (req, res, next) { 41 | if (!req.body.email || !req.body.password) return errors.errorBadRequest(res) 42 | if (!validator.isEmail(req.body.email)) return errors.errorBadRequest(res) 43 | models.User.findOrCreate({ 44 | where: { 45 | email: req.body.email 46 | }, 47 | defaults: { 48 | password: req.body.password 49 | } 50 | }).spread(function (user, created) { 51 | if (user) { 52 | if (created) { 53 | logger.debug('user registered: ' + user.id) 54 | req.flash('info', "You've successfully registered, please signin.") 55 | } else { 56 | logger.debug('user found: ' + user.id) 57 | req.flash('error', 'This email has been used, please try another one.') 58 | } 59 | return res.redirect(config.serverURL + '/') 60 | } 61 | req.flash('error', 'Failed to register your account, please try again.') 62 | return res.redirect(config.serverURL + '/') 63 | }).catch(function (err) { 64 | logger.error('auth callback failed: ' + err) 65 | return errors.errorInternalError(res) 66 | }) 67 | }) 68 | } 69 | 70 | emailAuth.post('/login', urlencodedParser, function (req, res, next) { 71 | if (!req.body.email || !req.body.password) return errors.errorBadRequest(res) 72 | if (!validator.isEmail(req.body.email)) return errors.errorBadRequest(res) 73 | passport.authenticate('local', { 74 | successReturnToOrRedirect: config.serverURL + '/', 75 | failureRedirect: config.serverURL + '/', 76 | failureFlash: 'Invalid email or password.' 77 | })(req, res, next) 78 | }) 79 | -------------------------------------------------------------------------------- /lib/web/auth/index.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const Router = require('express').Router 4 | const passport = require('passport') 5 | 6 | const config = require('../../config') 7 | const logger = require('../../logger') 8 | const models = require('../../models') 9 | 10 | const authRouter = module.exports = Router() 11 | 12 | // serialize and deserialize 13 | passport.serializeUser(function (user, done) { 14 | logger.info('serializeUser: ' + user.id) 15 | return done(null, user.id) 16 | }) 17 | 18 | passport.deserializeUser(function (id, done) { 19 | models.User.findOne({ 20 | where: { 21 | id 22 | } 23 | }).then(function (user) { 24 | // Don't die on non-existent user 25 | if (user == null) { 26 | return done(null, false, { message: 'Invalid UserID' }) 27 | } 28 | 29 | logger.info('deserializeUser: ' + user.id) 30 | return done(null, user) 31 | }).catch(function (err) { 32 | logger.error(err) 33 | return done(err, null) 34 | }) 35 | }) 36 | 37 | if (config.isOAuth2Enable) authRouter.use(require('./oauth2')) 38 | if (config.isEmailEnable) authRouter.use(require('./email')) 39 | if (config.isOpenIDEnable) authRouter.use(require('./openid')) 40 | 41 | // logout 42 | authRouter.get('/logout', function (req, res) { 43 | if (config.debug && req.isAuthenticated()) { 44 | logger.debug('user logout: ' + req.user.id) 45 | } 46 | req.logout(() => { 47 | res.redirect(config.serverURL + '/') 48 | }) 49 | }) 50 | -------------------------------------------------------------------------------- /lib/web/auth/utils.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const models = require('../../models') 4 | const logger = require('../../logger') 5 | 6 | exports.passportGeneralCallback = function callback (accessToken, refreshToken, profile, done) { 7 | const stringifiedProfile = JSON.stringify(profile) 8 | models.User.findOrCreate({ 9 | where: { 10 | profileid: profile.id.toString() 11 | }, 12 | defaults: { 13 | profile: stringifiedProfile, 14 | accessToken, 15 | refreshToken 16 | } 17 | }).spread(function (user, created) { 18 | if (user) { 19 | let needSave = false 20 | if (user.profile !== stringifiedProfile) { 21 | user.profile = stringifiedProfile 22 | needSave = true 23 | } 24 | if (user.accessToken !== accessToken) { 25 | user.accessToken = accessToken 26 | needSave = true 27 | } 28 | if (user.refreshToken !== refreshToken) { 29 | user.refreshToken = refreshToken 30 | needSave = true 31 | } 32 | if (needSave) { 33 | user.save().then(function () { 34 | logger.debug(`user login: ${user.id}`) 35 | return done(null, user) 36 | }) 37 | } else { 38 | logger.debug(`user login: ${user.id}`) 39 | return done(null, user) 40 | } 41 | } 42 | }).catch(function (err) { 43 | logger.error('auth callback failed: ' + err) 44 | return done(err, null) 45 | }) 46 | } 47 | -------------------------------------------------------------------------------- /lib/web/baseRouter.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const Router = require('express').Router 4 | 5 | const response = require('../response') 6 | 7 | const baseRouter = module.exports = Router() 8 | 9 | const errors = require('../errors') 10 | 11 | // get index 12 | baseRouter.get('/', response.showIndex) 13 | // get 403 forbidden 14 | baseRouter.get('/403', function (req, res) { 15 | errors.errorForbidden(res) 16 | }) 17 | // get 404 not found 18 | baseRouter.get('/404', function (req, res) { 19 | errors.errorNotFound(res) 20 | }) 21 | // get 500 internal error 22 | baseRouter.get('/500', function (req, res) { 23 | errors.errorInternalError(res) 24 | }) 25 | -------------------------------------------------------------------------------- /lib/web/historyRouter.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const Router = require('express').Router 4 | 5 | const { urlencodedParser } = require('./utils') 6 | const history = require('../history') 7 | const historyRouter = module.exports = Router() 8 | 9 | // get history 10 | historyRouter.get('/history', history.historyGet) 11 | // post history 12 | historyRouter.post('/history', urlencodedParser, history.historyPost) 13 | // post history by note id 14 | historyRouter.post('/history/:noteId', urlencodedParser, history.historyPost) 15 | // delete history 16 | historyRouter.delete('/history', history.historyDelete) 17 | // delete history by note id 18 | historyRouter.delete('/history/:noteId', history.historyDelete) 19 | -------------------------------------------------------------------------------- /lib/web/imageRouter/filesystem.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | const URL = require('url').URL 3 | const path = require('path') 4 | const fs = require('fs') 5 | 6 | const config = require('../../config') 7 | const logger = require('../../logger') 8 | 9 | exports.uploadImage = function (imagePath, callback) { 10 | if (!callback || typeof callback !== 'function') { 11 | logger.error('Callback has to be a function') 12 | return 13 | } 14 | 15 | if (!imagePath || typeof imagePath !== 'string') { 16 | callback(new Error('Image path is missing or wrong'), null) 17 | return 18 | } 19 | 20 | const fileName = path.basename(imagePath) 21 | // move image from temporary path to upload directory 22 | try { 23 | fs.copyFileSync(imagePath, path.join(config.uploadsPath, fileName)) 24 | } catch (e) { 25 | callback(new Error(`Error while moving file: ${e.message}`), null) 26 | return 27 | } 28 | callback(null, (new URL(fileName, config.serverURL + '/uploads/')).href) 29 | } 30 | -------------------------------------------------------------------------------- /lib/web/imageRouter/minio.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | const fs = require('fs') 3 | const path = require('path') 4 | 5 | const config = require('../../config') 6 | const { getImageMimeType } = require('../../utils') 7 | const logger = require('../../logger') 8 | 9 | const Minio = require('minio') 10 | const minioClient = new Minio.Client({ 11 | endPoint: config.minio.endPoint, 12 | port: config.minio.port, 13 | useSSL: config.minio.secure, 14 | accessKey: config.minio.accessKey, 15 | secretKey: config.minio.secretKey 16 | }) 17 | 18 | exports.uploadImage = function (imagePath, callback) { 19 | if (!imagePath || typeof imagePath !== 'string') { 20 | callback(new Error('Image path is missing or wrong'), null) 21 | return 22 | } 23 | 24 | if (!callback || typeof callback !== 'function') { 25 | logger.error('Callback has to be a function') 26 | return 27 | } 28 | 29 | fs.readFile(imagePath, function (err, buffer) { 30 | if (err) { 31 | callback(new Error(err), null) 32 | return 33 | } 34 | 35 | const key = path.basename(imagePath) 36 | const protocol = config.minio.secure ? 'https' : 'http' 37 | 38 | minioClient.putObject(config.s3bucket, key, buffer, buffer.size, getImageMimeType(imagePath), function (err, data) { 39 | if (err) { 40 | callback(new Error(err), null) 41 | return 42 | } 43 | const hidePort = [80, 443].includes(config.minio.port) 44 | const urlPort = hidePort ? '' : `:${config.minio.port}` 45 | callback(null, `${protocol}://${config.minio.endPoint}${urlPort}/${config.s3bucket}/${key}`) 46 | }) 47 | }) 48 | } 49 | -------------------------------------------------------------------------------- /lib/web/imageRouter/nextcloud.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | const config = require('../../config') 3 | const logger = require('../../logger') 4 | const fetch = require('node-fetch') 5 | const fs = require('fs') 6 | const { randomBytes } = require('crypto') 7 | 8 | exports.uploadImage = function (imagePath, callback) { 9 | if (!callback || typeof callback !== 'function') { 10 | logger.error('Callback has to be a function') 11 | return 12 | } 13 | 14 | if (!imagePath || typeof imagePath !== 'string') { 15 | callback(new Error('Image path is missing or wrong'), null) 16 | return 17 | } 18 | if (!config.ncUser || !config.ncPassword || !config.ncFolder || !config.ncHost) { 19 | callback(new Error('Nextcloud credentials are missing'), null) 20 | return 21 | } 22 | const fname = randomBytes(10).toString('hex') + '.' + imagePath.split('.').pop() 23 | const url = config.ncHost + '/remote.php/dav/files/' + config.ncUser + '/' + config.ncFolder + '/' + fname 24 | const readStream = fs.readFileSync(imagePath) 25 | const options = { 26 | method: 'PUT', 27 | headers: { 28 | Authorization: 'Basic ' + Buffer.from(config.ncUser + ':' + config.ncPassword).toString('base64') 29 | }, 30 | body: readStream 31 | } 32 | fetch(url, options).then(function (res) { 33 | if (res.status < 400) { 34 | fetch(config.ncHost + '/ocs/v2.php/apps/files_sharing/api/v1/shares?format=json', { 35 | method: 'POST', 36 | headers: { 37 | Authorization: 'Basic ' + Buffer.from(config.ncUser + ':' + config.ncPassword).toString('base64'), 38 | 'Content-Type': 'application/json', 39 | 'OCS-APIRequest': 'true', 40 | Accept: 'application/json' 41 | }, 42 | body: JSON.stringify({ 43 | path: config.ncFolder + '/' + fname, 44 | shareType: 3, 45 | permissions: 1 46 | }) 47 | }).then(function (res1) { 48 | if (res1.status < 400) { 49 | res1.json().then(function (json) { 50 | if (json.ocs?.data?.url) { 51 | callback(null, json.ocs.data.url + '/preview') 52 | } else { 53 | console.log(res1) 54 | callback(new Error('Error while sharing image 1 '), null) 55 | } 56 | }) 57 | } else { 58 | callback(new Error('Error while sharing image 2 '), null) 59 | } 60 | }) 61 | } else { 62 | callback(new Error('Error while uploading image 3 '), null) 63 | } 64 | }) 65 | } 66 | -------------------------------------------------------------------------------- /lib/web/middleware/checkURIValid.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const logger = require('../../logger') 4 | const errors = require('../../errors') 5 | 6 | module.exports = function (req, res, next) { 7 | try { 8 | decodeURIComponent(req.path) 9 | } catch (err) { 10 | logger.error(err) 11 | return errors.errorBadRequest(res) 12 | } 13 | next() 14 | } 15 | -------------------------------------------------------------------------------- /lib/web/middleware/hedgeDocVersion.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const config = require('../../config') 4 | 5 | module.exports = function (req, res, next) { 6 | res.set({ 7 | 'HedgeNext-Version': config.version 8 | }) 9 | return next() 10 | } 11 | -------------------------------------------------------------------------------- /lib/web/middleware/redirectWithoutTrailingSlashes.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const config = require('../../config') 4 | 5 | module.exports = function (req, res, next) { 6 | if (req.method === 'GET' && req.path.substr(-1) === '/' && req.path.length > 1) { 7 | const queryString = req.url.slice(req.path.length) 8 | const urlPath = req.path.slice(0, -1) 9 | let serverURL = config.serverURL 10 | if (config.urlPath) { 11 | serverURL = serverURL.slice(0, -(config.urlPath.length + 1)) 12 | } 13 | res.redirect(301, serverURL + urlPath + queryString) 14 | } else { 15 | next() 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /lib/web/middleware/tooBusy.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const toobusy = require('toobusy-js') 4 | 5 | const errors = require('../../errors') 6 | const config = require('../../config') 7 | 8 | toobusy.maxLag(config.tooBusyLag) 9 | 10 | module.exports = function (req, res, next) { 11 | if (toobusy()) { 12 | errors.errorServiceUnavailable(res) 13 | } else { 14 | next() 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /lib/web/note/router.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const Router = require('express').Router 4 | const { markdownParser } = require('../utils') 5 | 6 | const router = module.exports = Router() 7 | 8 | const noteController = require('./controller') 9 | const slide = require('./slide') 10 | 11 | // get new note 12 | router.get('/new', noteController.createFromPOST) 13 | // post new note with content 14 | router.post('/new', markdownParser, noteController.createFromPOST) 15 | router.get('/new-handoff', noteController.createFromPOSThandoff) 16 | // post new note with content and alias 17 | router.post('/new/:noteId', markdownParser, noteController.createFromPOST) 18 | // get publish note 19 | router.get('/s/:shortid', noteController.showPublishNote) 20 | // publish note actions 21 | // router.get('/s/:shortid/:action', noteController.publishNoteActions) 22 | // get publish slide 23 | router.get('/p/:shortid', slide.showPublishSlide) 24 | // publish slide actions 25 | // router.get('/p/:shortid/:action', slide.publishSlideActions) 26 | // get note by id 27 | router.get('/:noteId', noteController.showNote) 28 | router.get('/handoff/:noteId', noteController.showNoteHandoff) 29 | // note actions 30 | router.get('/:noteId/:action', noteController.doAction) 31 | // note actions with action id 32 | router.get('/:noteId/:action/:actionId', noteController.doAction) 33 | -------------------------------------------------------------------------------- /lib/web/note/slide.js: -------------------------------------------------------------------------------- 1 | const noteUtil = require('./util') 2 | const errors = require('../../errors') 3 | const config = require('../../config') 4 | 5 | // exports.publishSlideActions = function (req, res, next) { 6 | // noteUtil.findNote(req, res, function (note) { 7 | // const action = req.params.action 8 | // if (action === 'edit') { 9 | // res.redirect(config.serverURL + '/' + (note.alias ? note.alias : models.Note.encodeNoteId(note.id)) + '?both') 10 | // } else { res.redirect(config.serverURL + '/p/' + note.shortid) } 11 | // }) 12 | // } 13 | 14 | exports.showPublishSlide = function (req, res, next) { 15 | // const include = [{ 16 | // model: models.User, 17 | // as: 'owner' 18 | // }, { 19 | // model: models.User, 20 | // as: 'lastchangeuser' 21 | // }] 22 | noteUtil.findNote(req, res, true, function (note) { 23 | // force to use short id 24 | const shortid = req.params.shortid 25 | if ((note.alias && shortid !== note.alias) || (!note.alias && shortid !== note.shortid)) { 26 | return res.redirect(config.serverURL + '/p/' + (note.alias || note.shortid)) 27 | } 28 | if (!note) { 29 | return errors.errorNotFound(res) 30 | } 31 | 32 | noteUtil.getPublishData(req, res, note, (data) => { 33 | res.set({ 34 | 'Cache-Control': 'private', // only cache by client 35 | 'X-Robots-Tag': 'noindex, nofollow' // prevent crawling 36 | 37 | }) 38 | return res.render('slide.ejs', data) 39 | }) 40 | }, null, false) 41 | } 42 | -------------------------------------------------------------------------------- /lib/web/utils.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const bodyParser = require('body-parser') 4 | 5 | // create application/x-www-form-urlencoded parser 6 | exports.urlencodedParser = bodyParser.urlencoded({ 7 | extended: false, 8 | limit: 1024 * 1024 * 10 // 10 mb 9 | }) 10 | exports.jsonParser = bodyParser.json({ 11 | limit: 1024 * 1024 * 10 // 10 mb 12 | }) 13 | 14 | // create text/markdown parser 15 | exports.markdownParser = bodyParser.text({ 16 | inflate: true, 17 | type: ['text/plain', 'text/markdown'], 18 | limit: 1024 * 1024 * 10 // 10 mb 19 | }) 20 | -------------------------------------------------------------------------------- /locales/_supported.json: -------------------------------------------------------------------------------- 1 | { 2 | "en": "English", 3 | "zh-CN": "简体中文", 4 | "zh-TW": "繁體中文", 5 | "fr": "Français", 6 | "de": "Deutsch", 7 | "ja": "日本語", 8 | "es": "Español", 9 | "ca": "Català", 10 | "el": "Ελληνικά", 11 | "pt": "Português", 12 | "it": "Italiano", 13 | "tr": "Türkçe", 14 | "ru": "Русский", 15 | "nl": "Nederlands", 16 | "hr": "Hrvatski", 17 | "pl": "Polski", 18 | "uk": "Українська", 19 | "hi": "हिन्दी", 20 | "sv": "Svenska", 21 | "eo": "Esperanto", 22 | "da": "Dansk", 23 | "ko": "한국어", 24 | "id": "Bahasa Indonesia", 25 | "sr": "Cрпски", 26 | "vi": "Tiếng Việt", 27 | "ar": "العربية", 28 | "cs": "Česky", 29 | "sk": "Slovensky", 30 | "ml": "മലയാളം", 31 | "bg": "български език", 32 | "fa": "فارسی", 33 | "gl": "Galego", 34 | "he": "עברית", 35 | "hu": "Magyar", 36 | "oc": "Occitan", 37 | "pt-br": "Português do Brasil", 38 | "lt": "Lietuvių kalba", 39 | "ro": "Română", 40 | "sl": "Slovenski jezik", 41 | "eu": "Euskara" 42 | } -------------------------------------------------------------------------------- /public/.eslintrc.js: -------------------------------------------------------------------------------- 1 | // this config file is used in concert with the root .eslintrc.js in the root dir. 2 | module.exports = { 3 | env: { 4 | browser: true 5 | }, 6 | globals: { 7 | $: false, 8 | CodeMirror: false, 9 | Cookies: false, 10 | moment: false, 11 | editor: false, 12 | ui: false, 13 | Spinner: false, 14 | modeType: false, 15 | Idle: false, 16 | serverurl: false, 17 | key: false, 18 | gapi: false, 19 | Dropbox: false, 20 | FilePicker: false, 21 | ot: false, 22 | MediaUploader: false, 23 | num_loaded: false, 24 | Visibility: false, 25 | inlineAttachment: false 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /public/css/center.css: -------------------------------------------------------------------------------- 1 | html, 2 | body, 3 | .container-fluid { 4 | height: 98%; 5 | } 6 | .container-fluid { 7 | display: table; 8 | vertical-align: middle; 9 | } 10 | .vertical-center-row { 11 | display: table-cell; 12 | vertical-align: middle; 13 | } -------------------------------------------------------------------------------- /public/css/site.css: -------------------------------------------------------------------------------- 1 | /* for all pages should include this */ 2 | body { 3 | font-smoothing: subpixel-antialiased !important; 4 | -webkit-font-smoothing: subpixel-antialiased !important; 5 | -moz-osx-font-smoothing: auto !important; 6 | text-shadow: 0 0 1em transparent, 1px 1px 1.2px rgba(0, 0, 0, 0.004); 7 | /*text-rendering: optimizeLegibility;*/ 8 | -webkit-overflow-scrolling: touch; 9 | font-family: "Source Sans Pro", Helvetica, Arial, sans-serif; 10 | letter-spacing: 0.025em; 11 | } 12 | :focus, .focus { 13 | outline: none !important; 14 | } 15 | ::-moz-focus-inner { 16 | border: 0 !important; 17 | } 18 | 19 | /* manual fix for bootstrap issue 14040, there is an unnecessary padding-right on modal open */ 20 | body.modal-open { 21 | overflow-y: auto; 22 | padding-right: 0 !important; 23 | } 24 | -------------------------------------------------------------------------------- /public/css/slide-preview.css: -------------------------------------------------------------------------------- 1 | .markdown-body.slides { 2 | position: relative; 3 | z-index: 1; 4 | color: #222; 5 | } 6 | 7 | .markdown-body.slides::before { 8 | content: ''; 9 | display: block; 10 | position: absolute; 11 | top: 0; 12 | left: 0; 13 | right: 0; 14 | bottom: 0; 15 | z-index: -1; 16 | background-color: currentColor; 17 | box-shadow: 0 0 0 50vw; 18 | } 19 | 20 | .markdown-body.slides section[data-markdown] { 21 | position: relative; 22 | margin-bottom: 1.5em; 23 | background-color: #fff; 24 | text-align: center; 25 | } 26 | 27 | .markdown-body.slides section[data-markdown] code { 28 | text-align: left; 29 | } 30 | 31 | .markdown-body.slides section[data-markdown]::before { 32 | content: ''; 33 | display: block; 34 | padding-bottom: 56.23%; 35 | } 36 | 37 | .markdown-body.slides section[data-markdown] > div:first-child { 38 | position: absolute; 39 | top: 50%; 40 | left: 1em; 41 | right: 1em; 42 | transform: translateY(-50%); 43 | max-height: 100%; 44 | overflow: hidden; 45 | } 46 | 47 | .markdown-body.slides section[data-markdown] > ul { 48 | display: inline-block; 49 | } 50 | 51 | .markdown-body.slides > section > section + section::after { 52 | content: ''; 53 | position: absolute; 54 | top: -1.5em; 55 | right: 1em; 56 | height: 1.5em; 57 | border: 3px solid #777; 58 | } 59 | 60 | .markdown-body.slides aside.notes { 61 | display: none; 62 | } 63 | 64 | .markdown-body.slides ul, .markdown-body.slides ol { 65 | display: inline-block; 66 | text-align: left; 67 | margin: 0 0 0 1em; 68 | padding: 0; 69 | } 70 | 71 | .markdown-body.slides table { 72 | width: 50%; 73 | margin: 0 auto; 74 | border-collapse: collapse; 75 | border-spacing: 0; 76 | display: table; 77 | } 78 | 79 | .markdown-body.slides table th, .markdown-body.slides table td { 80 | text-align: left; 81 | padding: 0.2em 0.5em 0.2em 0.5em; 82 | border:none; 83 | border-bottom: 1px solid; 84 | } 85 | 86 | .markdown-body.slides table tr { 87 | border-top: 0; 88 | background-color: #fff; 89 | } 90 | 91 | .markdown-body.slides table tr:nth-child(2n) { 92 | background-color: inherit; 93 | } 94 | 95 | .markdown-body.slides table tbody tr:last-child th, .markdown-body.slides table tbody tr:last-child td { 96 | border-bottom: none; 97 | } 98 | 99 | .markdown-body.slides h1, .markdown-body.slides h2 { 100 | border-bottom: 0; 101 | } 102 | 103 | .night .markdown-body.slides h1, 104 | .night .markdown-body.slides h2, 105 | .night .markdown-body.slides h3, 106 | .night .markdown-body.slides h4, 107 | .night .markdown-body.slides h5, 108 | .night .markdown-body.slides h6 { 109 | color: black; 110 | } 111 | 112 | .markdown-body section > section:last-child { 113 | margin-bottom: 1.5em !important; 114 | } 115 | 116 | /* slides previews get a black background, controlled by js */ 117 | .ui-view-area.black { 118 | background-color: black !important;; 119 | } 120 | -------------------------------------------------------------------------------- /public/css/ui/toolbar.css: -------------------------------------------------------------------------------- 1 | .toolbar { 2 | background-color: #fafafa; 3 | border: 1px solid #ededed; 4 | } 5 | 6 | .toolbar > .btn-toolbar > .btn-group > .btn { 7 | background-color: #fafafa; 8 | padding: 5px; 9 | font-size: 1em; 10 | color: #555; 11 | } 12 | 13 | .toolbar > .btn-toolbar > .btn-group > .btn:hover { 14 | background-color: #e1e1e1; 15 | padding: 5px; 16 | } 17 | 18 | body.night .toolbar { 19 | background-color: #1c1c1e; 20 | border: 1px solid #353538; 21 | } 22 | 23 | body.night .toolbar > .btn-toolbar > .btn-group > .btn { 24 | background-color: #1c1c1e; 25 | padding: 5px; 26 | font-size: 1em; 27 | color: #5EB7E0; 28 | } 29 | 30 | body.night .toolbar > .btn-toolbar > .btn-group > .btn:hover { 31 | background-color: #37373b; 32 | padding: 5px; 33 | } 34 | -------------------------------------------------------------------------------- /public/default.md: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/libnewton/hedgenext-server/6e5378fb762acd151cc7a818801d4a1e7d887579/public/default.md -------------------------------------------------------------------------------- /public/fonts/SourceCodePro-Black.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/libnewton/hedgenext-server/6e5378fb762acd151cc7a818801d4a1e7d887579/public/fonts/SourceCodePro-Black.eot -------------------------------------------------------------------------------- /public/fonts/SourceCodePro-Black.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/libnewton/hedgenext-server/6e5378fb762acd151cc7a818801d4a1e7d887579/public/fonts/SourceCodePro-Black.ttf -------------------------------------------------------------------------------- /public/fonts/SourceCodePro-Black.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/libnewton/hedgenext-server/6e5378fb762acd151cc7a818801d4a1e7d887579/public/fonts/SourceCodePro-Black.woff -------------------------------------------------------------------------------- /public/fonts/SourceCodePro-Bold.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/libnewton/hedgenext-server/6e5378fb762acd151cc7a818801d4a1e7d887579/public/fonts/SourceCodePro-Bold.eot -------------------------------------------------------------------------------- /public/fonts/SourceCodePro-Bold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/libnewton/hedgenext-server/6e5378fb762acd151cc7a818801d4a1e7d887579/public/fonts/SourceCodePro-Bold.ttf -------------------------------------------------------------------------------- /public/fonts/SourceCodePro-Bold.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/libnewton/hedgenext-server/6e5378fb762acd151cc7a818801d4a1e7d887579/public/fonts/SourceCodePro-Bold.woff -------------------------------------------------------------------------------- /public/fonts/SourceCodePro-ExtraLight.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/libnewton/hedgenext-server/6e5378fb762acd151cc7a818801d4a1e7d887579/public/fonts/SourceCodePro-ExtraLight.eot -------------------------------------------------------------------------------- /public/fonts/SourceCodePro-ExtraLight.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/libnewton/hedgenext-server/6e5378fb762acd151cc7a818801d4a1e7d887579/public/fonts/SourceCodePro-ExtraLight.ttf -------------------------------------------------------------------------------- /public/fonts/SourceCodePro-ExtraLight.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/libnewton/hedgenext-server/6e5378fb762acd151cc7a818801d4a1e7d887579/public/fonts/SourceCodePro-ExtraLight.woff -------------------------------------------------------------------------------- /public/fonts/SourceCodePro-Light.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/libnewton/hedgenext-server/6e5378fb762acd151cc7a818801d4a1e7d887579/public/fonts/SourceCodePro-Light.eot -------------------------------------------------------------------------------- /public/fonts/SourceCodePro-Light.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/libnewton/hedgenext-server/6e5378fb762acd151cc7a818801d4a1e7d887579/public/fonts/SourceCodePro-Light.ttf -------------------------------------------------------------------------------- /public/fonts/SourceCodePro-Light.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/libnewton/hedgenext-server/6e5378fb762acd151cc7a818801d4a1e7d887579/public/fonts/SourceCodePro-Light.woff -------------------------------------------------------------------------------- /public/fonts/SourceCodePro-Medium.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/libnewton/hedgenext-server/6e5378fb762acd151cc7a818801d4a1e7d887579/public/fonts/SourceCodePro-Medium.eot -------------------------------------------------------------------------------- /public/fonts/SourceCodePro-Medium.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/libnewton/hedgenext-server/6e5378fb762acd151cc7a818801d4a1e7d887579/public/fonts/SourceCodePro-Medium.ttf -------------------------------------------------------------------------------- /public/fonts/SourceCodePro-Medium.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/libnewton/hedgenext-server/6e5378fb762acd151cc7a818801d4a1e7d887579/public/fonts/SourceCodePro-Medium.woff -------------------------------------------------------------------------------- /public/fonts/SourceCodePro-Regular.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/libnewton/hedgenext-server/6e5378fb762acd151cc7a818801d4a1e7d887579/public/fonts/SourceCodePro-Regular.eot -------------------------------------------------------------------------------- /public/fonts/SourceCodePro-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/libnewton/hedgenext-server/6e5378fb762acd151cc7a818801d4a1e7d887579/public/fonts/SourceCodePro-Regular.ttf -------------------------------------------------------------------------------- /public/fonts/SourceCodePro-Regular.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/libnewton/hedgenext-server/6e5378fb762acd151cc7a818801d4a1e7d887579/public/fonts/SourceCodePro-Regular.woff -------------------------------------------------------------------------------- /public/fonts/SourceCodePro-Semibold.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/libnewton/hedgenext-server/6e5378fb762acd151cc7a818801d4a1e7d887579/public/fonts/SourceCodePro-Semibold.eot -------------------------------------------------------------------------------- /public/fonts/SourceCodePro-Semibold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/libnewton/hedgenext-server/6e5378fb762acd151cc7a818801d4a1e7d887579/public/fonts/SourceCodePro-Semibold.ttf -------------------------------------------------------------------------------- /public/fonts/SourceCodePro-Semibold.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/libnewton/hedgenext-server/6e5378fb762acd151cc7a818801d4a1e7d887579/public/fonts/SourceCodePro-Semibold.woff -------------------------------------------------------------------------------- /public/fonts/SourceSansPro-Black.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/libnewton/hedgenext-server/6e5378fb762acd151cc7a818801d4a1e7d887579/public/fonts/SourceSansPro-Black.eot -------------------------------------------------------------------------------- /public/fonts/SourceSansPro-Black.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/libnewton/hedgenext-server/6e5378fb762acd151cc7a818801d4a1e7d887579/public/fonts/SourceSansPro-Black.ttf -------------------------------------------------------------------------------- /public/fonts/SourceSansPro-Black.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/libnewton/hedgenext-server/6e5378fb762acd151cc7a818801d4a1e7d887579/public/fonts/SourceSansPro-Black.woff -------------------------------------------------------------------------------- /public/fonts/SourceSansPro-BlackItalic.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/libnewton/hedgenext-server/6e5378fb762acd151cc7a818801d4a1e7d887579/public/fonts/SourceSansPro-BlackItalic.eot -------------------------------------------------------------------------------- /public/fonts/SourceSansPro-BlackItalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/libnewton/hedgenext-server/6e5378fb762acd151cc7a818801d4a1e7d887579/public/fonts/SourceSansPro-BlackItalic.ttf -------------------------------------------------------------------------------- /public/fonts/SourceSansPro-BlackItalic.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/libnewton/hedgenext-server/6e5378fb762acd151cc7a818801d4a1e7d887579/public/fonts/SourceSansPro-BlackItalic.woff -------------------------------------------------------------------------------- /public/fonts/SourceSansPro-Bold.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/libnewton/hedgenext-server/6e5378fb762acd151cc7a818801d4a1e7d887579/public/fonts/SourceSansPro-Bold.eot -------------------------------------------------------------------------------- /public/fonts/SourceSansPro-Bold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/libnewton/hedgenext-server/6e5378fb762acd151cc7a818801d4a1e7d887579/public/fonts/SourceSansPro-Bold.ttf -------------------------------------------------------------------------------- /public/fonts/SourceSansPro-Bold.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/libnewton/hedgenext-server/6e5378fb762acd151cc7a818801d4a1e7d887579/public/fonts/SourceSansPro-Bold.woff -------------------------------------------------------------------------------- /public/fonts/SourceSansPro-BoldItalic.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/libnewton/hedgenext-server/6e5378fb762acd151cc7a818801d4a1e7d887579/public/fonts/SourceSansPro-BoldItalic.eot -------------------------------------------------------------------------------- /public/fonts/SourceSansPro-BoldItalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/libnewton/hedgenext-server/6e5378fb762acd151cc7a818801d4a1e7d887579/public/fonts/SourceSansPro-BoldItalic.ttf -------------------------------------------------------------------------------- /public/fonts/SourceSansPro-BoldItalic.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/libnewton/hedgenext-server/6e5378fb762acd151cc7a818801d4a1e7d887579/public/fonts/SourceSansPro-BoldItalic.woff -------------------------------------------------------------------------------- /public/fonts/SourceSansPro-ExtraLight.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/libnewton/hedgenext-server/6e5378fb762acd151cc7a818801d4a1e7d887579/public/fonts/SourceSansPro-ExtraLight.eot -------------------------------------------------------------------------------- /public/fonts/SourceSansPro-ExtraLight.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/libnewton/hedgenext-server/6e5378fb762acd151cc7a818801d4a1e7d887579/public/fonts/SourceSansPro-ExtraLight.ttf -------------------------------------------------------------------------------- /public/fonts/SourceSansPro-ExtraLight.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/libnewton/hedgenext-server/6e5378fb762acd151cc7a818801d4a1e7d887579/public/fonts/SourceSansPro-ExtraLight.woff -------------------------------------------------------------------------------- /public/fonts/SourceSansPro-ExtraLightItalic.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/libnewton/hedgenext-server/6e5378fb762acd151cc7a818801d4a1e7d887579/public/fonts/SourceSansPro-ExtraLightItalic.eot -------------------------------------------------------------------------------- /public/fonts/SourceSansPro-ExtraLightItalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/libnewton/hedgenext-server/6e5378fb762acd151cc7a818801d4a1e7d887579/public/fonts/SourceSansPro-ExtraLightItalic.ttf -------------------------------------------------------------------------------- /public/fonts/SourceSansPro-ExtraLightItalic.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/libnewton/hedgenext-server/6e5378fb762acd151cc7a818801d4a1e7d887579/public/fonts/SourceSansPro-ExtraLightItalic.woff -------------------------------------------------------------------------------- /public/fonts/SourceSansPro-Italic.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/libnewton/hedgenext-server/6e5378fb762acd151cc7a818801d4a1e7d887579/public/fonts/SourceSansPro-Italic.eot -------------------------------------------------------------------------------- /public/fonts/SourceSansPro-Italic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/libnewton/hedgenext-server/6e5378fb762acd151cc7a818801d4a1e7d887579/public/fonts/SourceSansPro-Italic.ttf -------------------------------------------------------------------------------- /public/fonts/SourceSansPro-Italic.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/libnewton/hedgenext-server/6e5378fb762acd151cc7a818801d4a1e7d887579/public/fonts/SourceSansPro-Italic.woff -------------------------------------------------------------------------------- /public/fonts/SourceSansPro-Light.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/libnewton/hedgenext-server/6e5378fb762acd151cc7a818801d4a1e7d887579/public/fonts/SourceSansPro-Light.eot -------------------------------------------------------------------------------- /public/fonts/SourceSansPro-Light.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/libnewton/hedgenext-server/6e5378fb762acd151cc7a818801d4a1e7d887579/public/fonts/SourceSansPro-Light.ttf -------------------------------------------------------------------------------- /public/fonts/SourceSansPro-Light.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/libnewton/hedgenext-server/6e5378fb762acd151cc7a818801d4a1e7d887579/public/fonts/SourceSansPro-Light.woff -------------------------------------------------------------------------------- /public/fonts/SourceSansPro-LightItalic.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/libnewton/hedgenext-server/6e5378fb762acd151cc7a818801d4a1e7d887579/public/fonts/SourceSansPro-LightItalic.eot -------------------------------------------------------------------------------- /public/fonts/SourceSansPro-LightItalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/libnewton/hedgenext-server/6e5378fb762acd151cc7a818801d4a1e7d887579/public/fonts/SourceSansPro-LightItalic.ttf -------------------------------------------------------------------------------- /public/fonts/SourceSansPro-LightItalic.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/libnewton/hedgenext-server/6e5378fb762acd151cc7a818801d4a1e7d887579/public/fonts/SourceSansPro-LightItalic.woff -------------------------------------------------------------------------------- /public/fonts/SourceSansPro-Regular.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/libnewton/hedgenext-server/6e5378fb762acd151cc7a818801d4a1e7d887579/public/fonts/SourceSansPro-Regular.eot -------------------------------------------------------------------------------- /public/fonts/SourceSansPro-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/libnewton/hedgenext-server/6e5378fb762acd151cc7a818801d4a1e7d887579/public/fonts/SourceSansPro-Regular.ttf -------------------------------------------------------------------------------- /public/fonts/SourceSansPro-Regular.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/libnewton/hedgenext-server/6e5378fb762acd151cc7a818801d4a1e7d887579/public/fonts/SourceSansPro-Regular.woff -------------------------------------------------------------------------------- /public/fonts/SourceSansPro-Semibold.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/libnewton/hedgenext-server/6e5378fb762acd151cc7a818801d4a1e7d887579/public/fonts/SourceSansPro-Semibold.eot -------------------------------------------------------------------------------- /public/fonts/SourceSansPro-Semibold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/libnewton/hedgenext-server/6e5378fb762acd151cc7a818801d4a1e7d887579/public/fonts/SourceSansPro-Semibold.ttf -------------------------------------------------------------------------------- /public/fonts/SourceSansPro-Semibold.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/libnewton/hedgenext-server/6e5378fb762acd151cc7a818801d4a1e7d887579/public/fonts/SourceSansPro-Semibold.woff -------------------------------------------------------------------------------- /public/fonts/SourceSansPro-SemiboldItalic.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/libnewton/hedgenext-server/6e5378fb762acd151cc7a818801d4a1e7d887579/public/fonts/SourceSansPro-SemiboldItalic.eot -------------------------------------------------------------------------------- /public/fonts/SourceSansPro-SemiboldItalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/libnewton/hedgenext-server/6e5378fb762acd151cc7a818801d4a1e7d887579/public/fonts/SourceSansPro-SemiboldItalic.ttf -------------------------------------------------------------------------------- /public/fonts/SourceSansPro-SemiboldItalic.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/libnewton/hedgenext-server/6e5378fb762acd151cc7a818801d4a1e7d887579/public/fonts/SourceSansPro-SemiboldItalic.woff -------------------------------------------------------------------------------- /public/fonts/SourceSerifPro-Bold.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/libnewton/hedgenext-server/6e5378fb762acd151cc7a818801d4a1e7d887579/public/fonts/SourceSerifPro-Bold.eot -------------------------------------------------------------------------------- /public/fonts/SourceSerifPro-Bold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/libnewton/hedgenext-server/6e5378fb762acd151cc7a818801d4a1e7d887579/public/fonts/SourceSerifPro-Bold.ttf -------------------------------------------------------------------------------- /public/fonts/SourceSerifPro-Bold.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/libnewton/hedgenext-server/6e5378fb762acd151cc7a818801d4a1e7d887579/public/fonts/SourceSerifPro-Bold.woff -------------------------------------------------------------------------------- /public/fonts/SourceSerifPro-Regular.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/libnewton/hedgenext-server/6e5378fb762acd151cc7a818801d4a1e7d887579/public/fonts/SourceSerifPro-Regular.eot -------------------------------------------------------------------------------- /public/fonts/SourceSerifPro-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/libnewton/hedgenext-server/6e5378fb762acd151cc7a818801d4a1e7d887579/public/fonts/SourceSerifPro-Regular.ttf -------------------------------------------------------------------------------- /public/fonts/SourceSerifPro-Regular.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/libnewton/hedgenext-server/6e5378fb762acd151cc7a818801d4a1e7d887579/public/fonts/SourceSerifPro-Regular.woff -------------------------------------------------------------------------------- /public/fonts/SourceSerifPro-Semibold.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/libnewton/hedgenext-server/6e5378fb762acd151cc7a818801d4a1e7d887579/public/fonts/SourceSerifPro-Semibold.eot -------------------------------------------------------------------------------- /public/fonts/SourceSerifPro-Semibold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/libnewton/hedgenext-server/6e5378fb762acd151cc7a818801d4a1e7d887579/public/fonts/SourceSerifPro-Semibold.ttf -------------------------------------------------------------------------------- /public/fonts/SourceSerifPro-Semibold.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/libnewton/hedgenext-server/6e5378fb762acd151cc7a818801d4a1e7d887579/public/fonts/SourceSerifPro-Semibold.woff -------------------------------------------------------------------------------- /public/fonts/nunito-v25-latin-regular.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/libnewton/hedgenext-server/6e5378fb762acd151cc7a818801d4a1e7d887579/public/fonts/nunito-v25-latin-regular.eot -------------------------------------------------------------------------------- /public/fonts/nunito-v25-latin-regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/libnewton/hedgenext-server/6e5378fb762acd151cc7a818801d4a1e7d887579/public/fonts/nunito-v25-latin-regular.ttf -------------------------------------------------------------------------------- /public/fonts/nunito-v25-latin-regular.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/libnewton/hedgenext-server/6e5378fb762acd151cc7a818801d4a1e7d887579/public/fonts/nunito-v25-latin-regular.woff -------------------------------------------------------------------------------- /public/fonts/nunito-v25-latin-regular.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/libnewton/hedgenext-server/6e5378fb762acd151cc7a818801d4a1e7d887579/public/fonts/nunito-v25-latin-regular.woff2 -------------------------------------------------------------------------------- /public/icons/android-chrome-192x192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/libnewton/hedgenext-server/6e5378fb762acd151cc7a818801d4a1e7d887579/public/icons/android-chrome-192x192.png -------------------------------------------------------------------------------- /public/icons/android-chrome-512x512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/libnewton/hedgenext-server/6e5378fb762acd151cc7a818801d4a1e7d887579/public/icons/android-chrome-512x512.png -------------------------------------------------------------------------------- /public/icons/apple-touch-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/libnewton/hedgenext-server/6e5378fb762acd151cc7a818801d4a1e7d887579/public/icons/apple-touch-icon.png -------------------------------------------------------------------------------- /public/icons/browserconfig.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | #b51f08 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /public/icons/favicon-16x16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/libnewton/hedgenext-server/6e5378fb762acd151cc7a818801d4a1e7d887579/public/icons/favicon-16x16.png -------------------------------------------------------------------------------- /public/icons/favicon-32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/libnewton/hedgenext-server/6e5378fb762acd151cc7a818801d4a1e7d887579/public/icons/favicon-32x32.png -------------------------------------------------------------------------------- /public/icons/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/libnewton/hedgenext-server/6e5378fb762acd151cc7a818801d4a1e7d887579/public/icons/favicon.ico -------------------------------------------------------------------------------- /public/icons/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/libnewton/hedgenext-server/6e5378fb762acd151cc7a818801d4a1e7d887579/public/icons/logo.png -------------------------------------------------------------------------------- /public/icons/mstile-144x144.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/libnewton/hedgenext-server/6e5378fb762acd151cc7a818801d4a1e7d887579/public/icons/mstile-144x144.png -------------------------------------------------------------------------------- /public/icons/mstile-150x150.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/libnewton/hedgenext-server/6e5378fb762acd151cc7a818801d4a1e7d887579/public/icons/mstile-150x150.png -------------------------------------------------------------------------------- /public/icons/mstile-310x150.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/libnewton/hedgenext-server/6e5378fb762acd151cc7a818801d4a1e7d887579/public/icons/mstile-310x150.png -------------------------------------------------------------------------------- /public/icons/mstile-310x310.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/libnewton/hedgenext-server/6e5378fb762acd151cc7a818801d4a1e7d887579/public/icons/mstile-310x310.png -------------------------------------------------------------------------------- /public/icons/mstile-70x70.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/libnewton/hedgenext-server/6e5378fb762acd151cc7a818801d4a1e7d887579/public/icons/mstile-70x70.png -------------------------------------------------------------------------------- /public/icons/site.webmanifest: -------------------------------------------------------------------------------- 1 | { 2 | "name": "HedgeNext", 3 | "short_name": "HedgeNext", 4 | "icons": [ 5 | { 6 | "src": "/icons/android-chrome-192x192.png", 7 | "sizes": "192x192", 8 | "type": "image/png" 9 | }, 10 | { 11 | "src": "/icons/android-chrome-512x512.png", 12 | "sizes": "512x512", 13 | "type": "image/png" 14 | } 15 | ], 16 | "theme_color": "#b51f08", 17 | "background_color": "#303030" 18 | } 19 | -------------------------------------------------------------------------------- /public/js/fix-aria-hidden-for-modals.js: -------------------------------------------------------------------------------- 1 | $(document).on('shown.bs.modal', function (event) { 2 | $(event.target).attr('aria-hidden', 'false') 3 | }).on('hidden.bs.modal', function (event) { 4 | $(event.target).attr('aria-hidden', 'true') 5 | }) 6 | -------------------------------------------------------------------------------- /public/js/htmlExport.js: -------------------------------------------------------------------------------- 1 | require('bootstrap/dist/css/bootstrap.min.css') 2 | require('fork-awesome/css/fork-awesome.min.css') 3 | require('ionicons/css/ionicons.min.css') 4 | require('prismjs/prism') 5 | require('prismjs/themes/prism.css') 6 | require('prismjs/components/prism-wiki') 7 | require('prismjs/components/prism-haskell') 8 | require('prismjs/components/prism-go') 9 | require('prismjs/components/prism-typescript') 10 | require('prismjs/components/prism-jsx') 11 | require('prismjs/components/prism-makefile') 12 | require('prismjs/components/prism-gherkin') 13 | require('highlight.js/styles/github-gist.css') 14 | require('emojify.js/dist/css/basic/emojify.min.css') 15 | require('../css/github-extract.css') 16 | require('../css/markdown.css') 17 | require('../css/extra.css') 18 | require('../css/slide-preview.css') 19 | require('../css/font.css') 20 | require('../css/site.css') 21 | 22 | const $ = require('jquery') 23 | window.jQuery = $ 24 | window.$ = $ 25 | require('bootstrap') 26 | require('gist-embed/gist-embed.min') 27 | require('../css/mod.css') 28 | -------------------------------------------------------------------------------- /public/js/lib/appState.js: -------------------------------------------------------------------------------- 1 | import modeType from './modeType' 2 | 3 | const state = { 4 | syncscroll: true, 5 | currentMode: modeType.view 6 | } 7 | 8 | export default state 9 | -------------------------------------------------------------------------------- /public/js/lib/common/constant.ejs: -------------------------------------------------------------------------------- 1 | window.domain = '<%- domain %>' 2 | window.urlpath = '<%- urlpath %>' 3 | window.debug = <%- debug %> 4 | window.version = '<%- version %>' 5 | 6 | window.allowedUploadMimeTypes = <%- JSON.stringify(allowedUploadMimeTypes) %> 7 | 8 | window.linkifyHeaderStyle = '<%- linkifyHeaderStyle %>' 9 | 10 | window.DROPBOX_APP_KEY = '<%- DROPBOX_APP_KEY %>' 11 | 12 | window.cookiePolicy = '<%- cookiePolicy %>' 13 | -------------------------------------------------------------------------------- /public/js/lib/config/index.js: -------------------------------------------------------------------------------- 1 | export const DROPBOX_APP_KEY = window.DROPBOX_APP_KEY || '' 2 | 3 | export const domain = window.domain || '' // domain name 4 | export const urlpath = window.urlpath || '' // sub url path, like: www.example.com/ 5 | export const debug = window.debug || false 6 | 7 | export const port = window.location.port 8 | export const serverurl = `${window.location.protocol}//${domain || window.location.hostname}${port ? ':' + port : ''}${urlpath ? '/' + urlpath : ''}` 9 | window.serverurl = serverurl 10 | export const noteid = decodeURIComponent(urlpath ? window.location.pathname.slice(urlpath.length + 1, window.location.pathname.length).split('/')[1] : window.location.pathname.split('/')[1]) 11 | export const noteurl = `${serverurl}/${noteid}` 12 | export const version = window.version 13 | -------------------------------------------------------------------------------- /public/js/lib/editor/config.js: -------------------------------------------------------------------------------- 1 | const config = { 2 | docmaxlength: null 3 | } 4 | 5 | export default config 6 | -------------------------------------------------------------------------------- /public/js/lib/editor/statusbar.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 | 5 | 6 |
7 |
8 |
9 |
10 |
11 | 19 |
20 | 23 | 28 |
29 |
30 |
Spaces:
31 |
4
32 | 33 |
34 |
35 | 36 |
37 |
38 | 39 |
40 |
41 |
42 | -------------------------------------------------------------------------------- /public/js/lib/editor/ui-elements.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Global UI elements references 3 | */ 4 | 5 | export const getUIElements = () => ({ 6 | spinner: $('.ui-spinner'), 7 | content: $('.ui-content'), 8 | toolbar: { 9 | shortStatus: $('.ui-short-status'), 10 | status: $('.ui-status'), 11 | new: $('.ui-new'), 12 | publish: $('.ui-publish'), 13 | pdf: $('.ui-dl'), 14 | raw: $('.ui-raw'), 15 | extra: { 16 | revision: $('.ui-extra-revision'), 17 | slide: $('.ui-extra-slide') 18 | }, 19 | download: { 20 | markdown: $('.ui-download-markdown'), 21 | html: $('.ui-download-html'), 22 | rawhtml: $('.ui-download-raw-html') 23 | }, 24 | export: { 25 | dropbox: $('.ui-save-dropbox'), 26 | gist: $('.ui-save-gist'), 27 | snippet: $('.ui-save-snippet') 28 | }, 29 | import: { 30 | dropbox: $('.ui-import-dropbox'), 31 | gist: $('.ui-import-gist'), 32 | snippet: $('.ui-import-snippet'), 33 | clipboard: $('.ui-import-clipboard') 34 | }, 35 | mode: $('.ui-mode'), 36 | edit: $('.ui-edit'), 37 | view: $('.ui-view'), 38 | both: $('.ui-both'), 39 | night: $('.ui-night') 40 | }, 41 | infobar: { 42 | lastchange: $('.ui-lastchange'), 43 | lastchangeuser: $('.ui-lastchangeuser'), 44 | nolastchangeuser: $('.ui-no-lastchangeuser'), 45 | permission: { 46 | permission: $('.ui-permission'), 47 | label: $('.ui-permission-label'), 48 | freely: $('.ui-permission-freely'), 49 | editable: $('.ui-permission-editable'), 50 | locked: $('.ui-permission-locked'), 51 | private: $('.ui-permission-private'), 52 | limited: $('.ui-permission-limited'), 53 | protected: $('.ui-permission-protected') 54 | }, 55 | delete: $('.ui-delete-note') 56 | }, 57 | toc: { 58 | toc: $('.ui-toc'), 59 | affix: $('.ui-affix-toc'), 60 | label: $('.ui-toc-label'), 61 | dropdown: $('.ui-toc-dropdown') 62 | }, 63 | area: { 64 | edit: $('.ui-edit-area'), 65 | view: $('.ui-view-area'), 66 | codemirror: $('.ui-edit-area .CodeMirror'), 67 | codemirrorScroll: $('.ui-edit-area .CodeMirror .CodeMirror-scroll'), 68 | codemirrorSizer: $('.ui-edit-area .CodeMirror .CodeMirror-sizer'), 69 | codemirrorSizerInner: $( 70 | '.ui-edit-area .CodeMirror .CodeMirror-sizer > div' 71 | ), 72 | markdown: $('.ui-view-area .markdown-body'), 73 | resize: { 74 | handle: $('.ui-resizable-handle'), 75 | syncToggle: $('.ui-sync-toggle') 76 | } 77 | }, 78 | modal: { 79 | snippetImportProjects: $('#snippetImportModalProjects'), 80 | snippetImportSnippets: $('#snippetImportModalSnippets'), 81 | revision: $('#revisionModal'), 82 | templates: $('#templatesModal') 83 | } 84 | }) 85 | 86 | export default getUIElements 87 | -------------------------------------------------------------------------------- /public/js/lib/modeType.js: -------------------------------------------------------------------------------- 1 | export default { 2 | edit: { 3 | name: 'edit' 4 | }, 5 | view: { 6 | name: 'view' 7 | }, 8 | both: { 9 | name: 'both' 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /public/js/locale.js: -------------------------------------------------------------------------------- 1 | /* eslint-env browser, jquery */ 2 | /* global Cookies */ 3 | const supportedLanguages = require('../../locales/_supported.json') 4 | 5 | function detectLang () { 6 | if (Cookies.get('locale')) { 7 | let lang = Cookies.get('locale') 8 | if (lang === 'zh') { 9 | lang = 'zh-TW' 10 | } 11 | return lang 12 | } 13 | const userLang = navigator.language || navigator.userLanguage 14 | const userLangCode = userLang.split('-')[0] 15 | const supportedLanguagesList = Object.keys(supportedLanguages) 16 | if (supportedLanguagesList.includes(userLangCode)) { 17 | return userLangCode 18 | } else if (supportedLanguagesList.includes(userLang)) { 19 | return userLang 20 | } 21 | return 'en' 22 | } 23 | 24 | const lang = detectLang() 25 | const localeSelector = $('.ui-locale') 26 | Object.entries(supportedLanguages).forEach(function ([isoCode, nativeName]) { 27 | localeSelector.append(``) 28 | }) 29 | 30 | // the following condition is needed as the selector is only available in the intro/history page 31 | if (localeSelector.length > 0) { 32 | localeSelector.val(lang) 33 | $('select.ui-locale option[value="' + lang + '"]').attr('selected', 'selected') 34 | localeSelector.change(function () { 35 | Cookies.set('locale', $(this).val(), { 36 | expires: 365, 37 | sameSite: window.cookiePolicy, 38 | secure: window.location.protocol === 'https:' 39 | }) 40 | window.location.reload() 41 | }) 42 | } 43 | 44 | window.moment.locale(lang) 45 | -------------------------------------------------------------------------------- /public/js/mathjax-config-extra.js: -------------------------------------------------------------------------------- 1 | window.MathJax = { 2 | messageStyle: 'none', 3 | skipStartupTypeset: true, 4 | tex2jax: { 5 | inlineMath: [['$', '$'], ['\\(', '\\)']], 6 | processEscapes: true 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /public/js/utils.js: -------------------------------------------------------------------------------- 1 | import base64url from 'base64url' 2 | 3 | const uuidRegex = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i 4 | 5 | export function checkNoteIdValid (id) { 6 | const result = id.match(uuidRegex) 7 | if (result && result.length === 1) { 8 | return true 9 | } else { 10 | return false 11 | } 12 | } 13 | 14 | export function encodeNoteId (id) { 15 | // remove dashes in UUID and encode in url-safe base64 16 | const str = id.replace(/-/g, '') 17 | const hexStr = Buffer.from(str, 'hex') 18 | return base64url.encode(hexStr) 19 | } 20 | 21 | export function decodeNoteId (encodedId) { 22 | // decode from url-safe base64 23 | const id = base64url.toBuffer(encodedId).toString('hex') 24 | // add dashes between the UUID string parts 25 | const idParts = [] 26 | idParts.push(id.substr(0, 8)) 27 | idParts.push(id.substr(8, 4)) 28 | idParts.push(id.substr(12, 4)) 29 | idParts.push(id.substr(16, 4)) 30 | idParts.push(id.substr(20, 12)) 31 | return idParts.join('-') 32 | } 33 | -------------------------------------------------------------------------------- /public/screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/libnewton/hedgenext-server/6e5378fb762acd151cc7a818801d4a1e7d887579/public/screenshot.png -------------------------------------------------------------------------------- /public/uploads/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/libnewton/hedgenext-server/6e5378fb762acd151cc7a818801d4a1e7d887579/public/uploads/.gitkeep -------------------------------------------------------------------------------- /public/vendor/codemirror-spell-checker/spell-checker.min.css: -------------------------------------------------------------------------------- 1 | /** 2 | * codemirror-spell-checker v1.0.6 3 | * Copyright Next Step Webs, Inc. 4 | * @link https://github.com/NextStepWebs/codemirror-spell-checker 5 | * @license MIT 6 | */ 7 | .CodeMirror .cm-spell-error:not(.cm-url):not(.cm-comment):not(.cm-tag):not(.cm-word){border-bottom:2px dotted rgba(255,0,0,.8)} -------------------------------------------------------------------------------- /public/vendor/inlineAttachment/codemirror.inline-attachment.js: -------------------------------------------------------------------------------- 1 | /*jslint newcap: true */ 2 | /*global inlineAttachment: false */ 3 | /** 4 | * CodeMirror version for inlineAttachment 5 | * 6 | * Call inlineAttachment.attach(editor) to attach to a codemirror instance 7 | */ 8 | (function() { 9 | 'use strict'; 10 | 11 | var codeMirrorEditor = function(instance) { 12 | 13 | if (!instance.getWrapperElement) { 14 | throw "Invalid CodeMirror object given"; 15 | } 16 | 17 | this.codeMirror = instance; 18 | }; 19 | 20 | codeMirrorEditor.prototype.getValue = function() { 21 | return this.codeMirror.getValue(); 22 | }; 23 | 24 | codeMirrorEditor.prototype.insertValue = function(val) { 25 | this.codeMirror.replaceSelection(val); 26 | }; 27 | 28 | codeMirrorEditor.prototype.setValue = function(val) { 29 | var cursor = this.codeMirror.getCursor(); 30 | this.codeMirror.setValue(val); 31 | this.codeMirror.setCursor(cursor); 32 | }; 33 | 34 | codeMirrorEditor.prototype.replaceRange = function(val) { 35 | this.codeMirror.replaceRange(val.replacement, val.from, val.to, "+input"); 36 | }; 37 | 38 | /** 39 | * Attach InlineAttachment to CodeMirror 40 | * 41 | * @param {CodeMirror} codeMirror 42 | */ 43 | codeMirrorEditor.attach = function(codeMirror, options) { 44 | 45 | options = options || {}; 46 | 47 | var editor = new codeMirrorEditor(codeMirror), 48 | inlineattach = new inlineAttachment(options, editor), 49 | el = codeMirror.getWrapperElement(); 50 | 51 | el.addEventListener('paste', function(e) { 52 | inlineattach.onPaste(e); 53 | }, false); 54 | 55 | codeMirror.setOption('onDragEvent', function(data, e) { 56 | if (e.type === "drop") { 57 | e.stopPropagation(); 58 | e.preventDefault(); 59 | return inlineattach.onDrop(e); 60 | } 61 | }); 62 | }; 63 | 64 | inlineAttachment.editors.codemirror3 = codeMirrorEditor; 65 | 66 | var codeMirrorEditor4 = function(instance) { 67 | codeMirrorEditor.call(this, instance); 68 | }; 69 | 70 | codeMirrorEditor4.attach = function(codeMirror, options) { 71 | 72 | options = options || {}; 73 | 74 | var editor = new codeMirrorEditor(codeMirror), 75 | inlineattach = new inlineAttachment(options, editor), 76 | el = codeMirror.getWrapperElement(); 77 | 78 | el.addEventListener('paste', function(e) { 79 | inlineattach.onPaste(e); 80 | }, false); 81 | 82 | codeMirror.on('drop', function(data, e) { 83 | if (inlineattach.onDrop(e)) { 84 | e.stopPropagation(); 85 | e.preventDefault(); 86 | return true; 87 | } else { 88 | return false; 89 | } 90 | }); 91 | 92 | return inlineattach; 93 | }; 94 | 95 | inlineAttachment.editors.codemirror4 = codeMirrorEditor4; 96 | 97 | })(); -------------------------------------------------------------------------------- /public/vendor/ot/compress.sh: -------------------------------------------------------------------------------- 1 | uglifyjs --compress --mangle --output ot.min.js \ 2 | ./text-operation.js \ 3 | ./selection.js \ 4 | ./wrapped-operation.js \ 5 | ./undo-manager.js \ 6 | ./client.js \ 7 | ./codemirror-adapter.js \ 8 | ./socketio-adapter.js \ 9 | ./ajax-adapter.js \ 10 | ./editor-client.js -------------------------------------------------------------------------------- /public/vendor/ot/hex2rgb.js: -------------------------------------------------------------------------------- 1 | function hex2rgb (hex) { 2 | if (hex[0] == '#') hex = hex.substr(1) 3 | if (hex.length == 3) { 4 | var temp = hex 5 | hex = '' 6 | temp = /^([a-f0-9])([a-f0-9])([a-f0-9])$/i.exec(temp).slice(1) 7 | for (var i = 0; i < 3; i++) hex += temp[i] + temp[i] 8 | } 9 | var triplets = /^([a-f0-9]{2})([a-f0-9]{2})([a-f0-9]{2})$/i.exec(hex).slice(1) 10 | return { 11 | red: parseInt(triplets[0], 16), 12 | green: parseInt(triplets[1], 16), 13 | blue: parseInt(triplets[2], 16) 14 | } 15 | } 16 | 17 | module.exports = hex2rgb 18 | -------------------------------------------------------------------------------- /public/vendor/ot/socketio-adapter.js: -------------------------------------------------------------------------------- 1 | /*global ot */ 2 | 3 | ot.SocketIOAdapter = (function () { 4 | 'use strict'; 5 | 6 | function SocketIOAdapter(socket) { 7 | this.socket = socket; 8 | 9 | var self = this; 10 | socket.on('client_left', function (clientId) { 11 | self.trigger('client_left', clientId); 12 | }); 13 | socket.on('set_name', function (clientId, name) { 14 | self.trigger('set_name', clientId, name); 15 | }); 16 | socket.on('set_color', function (clientId, color) { 17 | self.trigger('set_color', clientId, color); 18 | }); 19 | socket.on('ack', function (revision) { 20 | self.trigger('ack', revision); 21 | }); 22 | socket.on('operation', function (clientId, revision, operation, selection) { 23 | self.trigger('operation', revision, operation); 24 | self.trigger('selection', clientId, selection); 25 | }); 26 | socket.on('operations', function (head, operations) { 27 | self.trigger('operations', head, operations); 28 | }); 29 | socket.on('selection', function (clientId, selection) { 30 | self.trigger('selection', clientId, selection); 31 | }); 32 | socket.on('reconnect', function () { 33 | self.trigger('reconnect'); 34 | }); 35 | } 36 | 37 | SocketIOAdapter.prototype.sendOperation = function (revision, operation, selection) { 38 | this.socket.emit('operation', revision, operation, selection); 39 | }; 40 | 41 | SocketIOAdapter.prototype.sendSelection = function (selection) { 42 | this.socket.emit('selection', selection); 43 | }; 44 | 45 | SocketIOAdapter.prototype.getOperations = function (base, head) { 46 | this.socket.emit('get_operations', base, head); 47 | }; 48 | 49 | SocketIOAdapter.prototype.registerCallbacks = function (cb) { 50 | this.callbacks = cb; 51 | }; 52 | 53 | SocketIOAdapter.prototype.trigger = function (event) { 54 | var args = Array.prototype.slice.call(arguments, 1); 55 | var action = this.callbacks && this.callbacks[event]; 56 | if (action) { 57 | action.apply(this, args); 58 | } 59 | }; 60 | 61 | return SocketIOAdapter; 62 | 63 | }()); -------------------------------------------------------------------------------- /public/vendor/ot/wrapped-operation.js: -------------------------------------------------------------------------------- 1 | if (typeof ot === 'undefined') { 2 | // Export for browsers 3 | var ot = {}; 4 | } 5 | 6 | ot.WrappedOperation = (function (global) { 7 | 'use strict'; 8 | 9 | // A WrappedOperation contains an operation and corresponing metadata. 10 | function WrappedOperation (operation, meta) { 11 | this.wrapped = operation; 12 | this.meta = meta; 13 | } 14 | 15 | WrappedOperation.prototype.apply = function () { 16 | return this.wrapped.apply.apply(this.wrapped, arguments); 17 | }; 18 | 19 | WrappedOperation.prototype.invert = function () { 20 | var meta = this.meta; 21 | return new WrappedOperation( 22 | this.wrapped.invert.apply(this.wrapped, arguments), 23 | meta && typeof meta === 'object' && typeof meta.invert === 'function' ? 24 | meta.invert.apply(meta, arguments) : meta 25 | ); 26 | }; 27 | 28 | // Copy all properties from source to target. 29 | function copy (source, target) { 30 | for (var key in source) { 31 | if (source.hasOwnProperty(key)) { 32 | target[key] = source[key]; 33 | } 34 | } 35 | } 36 | 37 | function composeMeta (a, b) { 38 | if (a && typeof a === 'object') { 39 | if (typeof a.compose === 'function') { return a.compose(b); } 40 | var meta = {}; 41 | copy(a, meta); 42 | copy(b, meta); 43 | return meta; 44 | } 45 | return b; 46 | } 47 | 48 | WrappedOperation.prototype.compose = function (other) { 49 | return new WrappedOperation( 50 | this.wrapped.compose(other.wrapped), 51 | composeMeta(this.meta, other.meta) 52 | ); 53 | }; 54 | 55 | function transformMeta (meta, operation) { 56 | if (meta && typeof meta === 'object') { 57 | if (typeof meta.transform === 'function') { 58 | return meta.transform(operation); 59 | } 60 | } 61 | return meta; 62 | } 63 | 64 | WrappedOperation.transform = function (a, b) { 65 | var transform = a.wrapped.constructor.transform; 66 | var pair = transform(a.wrapped, b.wrapped); 67 | return [ 68 | new WrappedOperation(pair[0], transformMeta(a.meta, b.wrapped)), 69 | new WrappedOperation(pair[1], transformMeta(b.meta, a.wrapped)) 70 | ]; 71 | }; 72 | 73 | return WrappedOperation; 74 | 75 | }(this)); 76 | 77 | // Export for CommonJS 78 | if (typeof module === 'object') { 79 | module.exports = ot.WrappedOperation; 80 | } -------------------------------------------------------------------------------- /public/vendor/showup/showup.css: -------------------------------------------------------------------------------- 1 | /* 2 | * Showup.js jQuery Plugin 3 | * http://github.com/jonschlinkert/showup 4 | * 5 | * Copyright (c) 2013 Jon Schlinkert, contributors 6 | * Licensed under the MIT License (MIT). 7 | */ 8 | 9 | /** 10 | * Docs navbar transitions effects 11 | */ 12 | 13 | .navbar-tall, 14 | .navbar-show { 15 | -webkit-transition: -webkit-transform .3s; 16 | -moz-transition: -moz-transform .3s; 17 | -o-transition: -o-transform .3s; 18 | transition: transform .3s; 19 | -webkit-transform: translate(0, 0); 20 | -ms-transform: translate(0, 0); 21 | transform: translate(0, 0); 22 | } 23 | 24 | .navbar-hide { 25 | -webkit-transition: -webkit-transform .2s; 26 | -moz-transition: -moz-transform .2s; 27 | -o-transition: -o-transform .2s; 28 | transition: transform .2s; 29 | -webkit-transform: translate(0, -60px); 30 | -ms-transform: translate(0, -60px); 31 | transform: translate(0, -60px); 32 | } 33 | 34 | 35 | .navbar-tall, 36 | .navbar-short, 37 | .navbar-tall .navbar-brand, 38 | .navbar-short .navbar-brand, 39 | .navbar-tall .navbar-nav > li > a, 40 | .navbar-short .navbar-nav > li > a { 41 | -webkit-transition: all 0.2s linear; 42 | transition: all 0.2s linear; 43 | } 44 | 45 | .navbar-short { 46 | min-height: 40px; 47 | } 48 | .navbar-short .navbar-brand { 49 | font-size: 16px; 50 | padding: 13px 15px 10px; 51 | } 52 | .navbar-short .navbar-nav > li > a { 53 | padding-top: 12px; 54 | padding-bottom: 12px; 55 | } 56 | 57 | 58 | .navbar-tall { 59 | min-height: 70px; 60 | } 61 | .navbar-tall .navbar-brand { 62 | font-size: 24px; 63 | padding: 25px 15px; 64 | } 65 | .navbar-tall .navbar-nav > li > a { 66 | padding-top: 25px; 67 | } 68 | 69 | 70 | 71 | /** 72 | * Docs Buttons 73 | */ 74 | 75 | /* Fixed button, bottom right */ 76 | .btn-fixed-bottom { 77 | position: fixed; 78 | bottom: 30px; 79 | display: none; 80 | z-index: 5; 81 | width: 40px; 82 | height: 40px; 83 | } 84 | 85 | /* Toggles navbar classes */ 86 | .btn-hide-show { 87 | margin-right: 10px; 88 | } 89 | 90 | /* Buttons displayed throughout the content */ 91 | .btn-showup { 92 | position: relative; 93 | color: #fff; 94 | font-weight: normal; 95 | background-color: #463265; 96 | border-color: #3F2961; 97 | } 98 | 99 | .btn-showup:hover, 100 | .btn-showup:focus { 101 | color: #fff; 102 | outline: none; 103 | background-color: #39235A; 104 | border-color: #39235A; 105 | } 106 | -------------------------------------------------------------------------------- /public/vendor/showup/showup.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Showup.js jQuery Plugin 3 | * http://github.com/jonschlinkert/showup 4 | * 5 | * Copyright (c) 2013 Jon Schlinkert, contributors 6 | * Licensed under the MIT License (MIT). 7 | */ 8 | 9 | 10 | (function( $ ) { 11 | $.fn.showUp = function(ele, options) { 12 | options = options || {}; 13 | 14 | var target = $(ele); 15 | var down = options.down || 'navbar-hide'; 16 | var up = options.up || 'navbar-show'; 17 | var btnHideShow = options.btnHideShow || '.btn-hide-show'; 18 | var hideOffset = options.offset || 60; 19 | var previousScroll = 0; 20 | var isHide = false; 21 | 22 | $(window).scroll(function () { 23 | checkScrollTop(); 24 | }); 25 | 26 | $(window).resize(function () { 27 | checkScrollTop(); 28 | }); 29 | 30 | $(window).mousewheel(function () { 31 | checkScrollTop(); 32 | }); 33 | 34 | function checkScrollTop() 35 | { 36 | target.clearQueue(); 37 | target.stop(); 38 | var currentScroll = $(this).scrollTop(); 39 | if (currentScroll > hideOffset && !target.hasClass('locked')) { 40 | if(Math.abs(previousScroll - currentScroll) < 50) return; 41 | if (currentScroll > previousScroll) { 42 | // Action on scroll down 43 | target.removeClass(up).addClass(down); 44 | } else if (currentScroll < previousScroll) { 45 | // Action on scroll up 46 | target.removeClass(down).addClass(up); 47 | } 48 | } else { 49 | target.removeClass(down).addClass(up); 50 | } 51 | previousScroll = $(this).scrollTop(); 52 | } 53 | 54 | // Toggle visibility of target on click 55 | $(btnHideShow).click(function () { 56 | if (target.hasClass(down)) { 57 | target.removeClass(down).addClass(up); 58 | } else { 59 | target.removeClass(up).addClass(down); 60 | } 61 | }); 62 | }; 63 | })( jQuery ); 64 | 65 | // TODO: make customizable 66 | $(document).ready(function () { 67 | var duration = 420; 68 | var showOffset = 220; 69 | var btnFixed = '.btn-fixed-bottom'; 70 | var btnToTopClass = '.back-to-top'; 71 | 72 | $(window).scroll(function () { 73 | if ($(this).scrollTop() > showOffset) { 74 | $(btnFixed).fadeIn(duration); 75 | } else { 76 | $(btnFixed).fadeOut(duration); 77 | } 78 | }); 79 | 80 | $(btnToTopClass).click(function (event) { 81 | event.preventDefault(); 82 | $('html, body').animate({ 83 | scrollTop: 0 84 | }, duration); 85 | return false; 86 | }); 87 | }); -------------------------------------------------------------------------------- /public/views/error.ejs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | <%- include('hedgedoc/head') %> 6 | 7 | 8 | 9 | 10 | <%- include('hedgedoc/header') %> 11 |
12 |
13 |

<%- code %> <%- detail %> <%- msg %>

14 |
15 |
16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /public/views/hedgedoc.ejs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | <%- include('hedgedoc/head') %> 6 | 7 | 8 | 9 | <%- include('hedgedoc/header') %> 10 | <%- include('hedgedoc/body') %> 11 | <%- include('hedgedoc/footer') %> 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /public/views/hedgedoc/footer.ejs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | <%- include('../build/index-pack-scripts') %> 6 | -------------------------------------------------------------------------------- /public/views/hedgedoc/head.ejs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | <%- include('../includes/favicon') %> 8 | <% for (var og in opengraph) { %> 9 | <% if (opengraph.hasOwnProperty(og) && opengraph[og].trim() !== '') { %> 10 | 11 | <% }} if (!opengraph.hasOwnProperty('image')) { %> 12 | 13 | 14 | 15 | <% } %> 16 | 17 | <%= title %> 18 | 19 | <%- include('../build/index-pack-header') %> 20 | -------------------------------------------------------------------------------- /public/views/includes/favicon.ejs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /public/views/includes/header.ejs: -------------------------------------------------------------------------------- 1 | <% for (var css in htmlWebpackPlugin.files.css) { %> 2 | 3 | <% } %> 4 | -------------------------------------------------------------------------------- /public/views/includes/scripts.ejs: -------------------------------------------------------------------------------- 1 | 2 | <% for (var js in htmlWebpackPlugin.files.js) { %> 3 | 4 | <% } %> 5 | -------------------------------------------------------------------------------- /public/views/index.ejs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | <%- include('index/head') %> 6 | 7 | 8 | 9 | <%- include('index/header') %> 10 | <%- include('index/body') %> 11 | <%- include('index/footer') %> 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /public/views/index/footer.ejs: -------------------------------------------------------------------------------- 1 | <%- include('../build/cover-pack-scripts') %> 2 | -------------------------------------------------------------------------------- /public/views/index/head.ejs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | <%- include('../includes/favicon') %> 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | HedgeNext - <%= __('Collaborative markdown notes') %> 19 | <%- include('../build/cover-pack-header') %> 20 | -------------------------------------------------------------------------------- /public/views/index/header.ejs: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/libnewton/hedgenext-server/6e5378fb762acd151cc7a818801d4a1e7d887579/public/views/index/header.ejs -------------------------------------------------------------------------------- /public/views/pretty.ejs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | <% if(typeof robots !== 'undefined' && robots) { %> 12 | 13 | <% } %> 14 | <% if(typeof description !== 'undefined' && description) { %> 15 | 16 | <% } %> 17 | <% for (var og in opengraph) { %> 18 | <% if (opengraph.hasOwnProperty(og) && opengraph[og].trim() !== '') { %> 19 | 20 | <% }} if (!opengraph.hasOwnProperty('image')) { %> 21 | 22 | 23 | 24 | <% } %> 25 | 26 | <%= title %> 27 | <%- include('includes/favicon.ejs') %> 28 | 29 | <%- include('build/pretty-pack-header') %> 30 | 31 | 32 | 33 | 34 |
lang="<%= lang %>"<% } %>><%= body %>
35 |
36 | 45 | 46 | 47 |
48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | <%- include('build/pretty-pack-scripts') %> -------------------------------------------------------------------------------- /public/views/shared/refresh-modal.ejs: -------------------------------------------------------------------------------- 1 | 2 | 32 | -------------------------------------------------------------------------------- /public/views/shared/revision-modal.ejs: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /public/views/shared/templates-modal.ejs: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /renovate.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": [ 3 | "config:base", 4 | "group:definitelyTyped", 5 | "group:socketio", 6 | "group:linters", 7 | "group:test", 8 | "group:nextjsMonorepo", 9 | ":disableMajorUpdates", 10 | ":gitSignOff" 11 | ], 12 | "labels": [ 13 | "type: maintenance" 14 | ] 15 | } 16 | -------------------------------------------------------------------------------- /resources/config.json: -------------------------------------------------------------------------------- 1 | {} -------------------------------------------------------------------------------- /resources/docker-entrypoint.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # Use gosu if the container started with root privileges 4 | UID="$(id -u)" 5 | [ "$UID" -eq 0 ] && GOSU="gosu hedgedoc" || GOSU="" 6 | 7 | if [ "$HMD_IMAGE_UPLOAD_TYPE" != "" ] && [ "$CMD_IMAGE_UPLOAD_TYPE" = "" ]; then 8 | CMD_IMAGE_UPLOAD_TYPE="$HMD_IMAGE_UPLOAD_TYPE" 9 | fi 10 | 11 | # Print warning if local data storage is used but no volume is mounted 12 | [ "$CMD_IMAGE_UPLOAD_TYPE" = "filesystem" ] && { mountpoint -q ./public/uploads || { 13 | echo " 14 | ################################################################# 15 | ### ### 16 | ### !!!WARNING!!! ### 17 | ### ### 18 | ### Using local uploads without persistence is ### 19 | ### dangerous. You'll loose your data on ### 20 | ### container removal. Check out: ### 21 | ### https://docs.docker.com/engine/tutorials/dockervolumes/ ### 22 | ### ### 23 | ### !!!WARNING!!! ### 24 | ### ### 25 | ################################################################# 26 | "; 27 | } ; } 28 | 29 | # Change owner and permission if filesystem backend is used and user has root permissions 30 | if [ "$UID" -eq 0 ] && [ "$CMD_IMAGE_UPLOAD_TYPE" = "filesystem" ]; then 31 | if [ "$UID" -eq 0 ]; then 32 | echo "Updating uploads directory permissions ($UPLOADS_MODE)" 33 | chown -R hedgedoc ./public/uploads 34 | chmod $UPLOADS_MODE ./public/uploads 35 | find ./public/uploads -type f -executable -exec chmod a-x {} \; 36 | else 37 | echo " 38 | ################################################################# 39 | ### ### 40 | ### !!!WARNING!!! ### 41 | ### ### 42 | ### Container was started without root permissions ### 43 | ### and filesystem storage is being used. ### 44 | ### In case of filesystem errors these need to be ### 45 | ### changed manually ### 46 | ### ### 47 | ### !!!WARNING!!! ### 48 | ### ### 49 | ################################################################# 50 | "; 51 | fi 52 | fi 53 | 54 | # Sleep to make sure everything is fine... 55 | sleep 3 56 | 57 | # run 58 | exec $GOSU "$@" -------------------------------------------------------------------------------- /resources/healthcheck.mjs: -------------------------------------------------------------------------------- 1 | import fetch from 'node-fetch' 2 | 3 | // Kill myself after 5 second timeout 4 | setTimeout(() => { 5 | process.exit(1) 6 | }, 5000) 7 | 8 | fetch(`http://localhost:${process.env.CMD_PORT || '3000' }/status`, {headers: { "user-agent": "hedgenext-container-healthcheck/1.0"}}).then((response) => { 9 | if (!response.ok) { 10 | process.exit(1) 11 | } 12 | process.exit(0) 13 | }).catch(() => { 14 | process.exit(1) 15 | }) -------------------------------------------------------------------------------- /resources/utf8.cnf: -------------------------------------------------------------------------------- 1 | [client] 2 | default-character-set=utf8mb4 3 | 4 | [mysql] 5 | default-character-set=utf8mb4 6 | 7 | [mysqld] 8 | collation-server = utf8mb4_unicode_ci 9 | init-connect='SET NAMES utf8mb4' 10 | character-set-server = utf8mb4 -------------------------------------------------------------------------------- /test/letter-avatars.js: -------------------------------------------------------------------------------- 1 | /* eslint-env node, mocha */ 2 | 3 | 'use strict' 4 | 5 | const assert = require('assert') 6 | const mock = require('mock-require') 7 | 8 | describe('generateAvatarURL() gravatar enabled', function () { 9 | let avatars 10 | beforeEach(function () { 11 | // Reset config to make sure we don't influence other tests 12 | const testconfig = { 13 | allowGravatar: true, 14 | serverURL: 'http://localhost:3000', 15 | port: 3000 16 | } 17 | mock('../lib/config', testconfig) 18 | avatars = mock.reRequire('../lib/letter-avatars') 19 | }) 20 | 21 | it('should return correct urls', function () { 22 | assert.strictEqual(avatars.generateAvatarURL('Daan Sprenkels', 'hello@dsprenkels.com', true), 'https://cdn.libravatar.org/avatar/d41b5f3508cc3f31865566a47dd0336b?default=identicon&s=400') 23 | assert.strictEqual(avatars.generateAvatarURL('Daan Sprenkels', 'hello@dsprenkels.com', false), 'https://cdn.libravatar.org/avatar/d41b5f3508cc3f31865566a47dd0336b?default=identicon&s=96') 24 | }) 25 | 26 | it('should return correct urls for names with spaces', function () { 27 | assert.strictEqual(avatars.generateAvatarURL('Daan Sprenkels'), 'http://localhost:3000/user/Daan%20Sprenkels/avatar.svg') 28 | }) 29 | }) 30 | 31 | describe('generateAvatarURL() gravatar disabled', function () { 32 | let avatars 33 | beforeEach(function () { 34 | // Reset config to make sure we don't influence other tests 35 | const testconfig = { 36 | allowGravatar: false, 37 | serverURL: 'http://localhost:3000', 38 | port: 3000 39 | } 40 | mock('../lib/config', testconfig) 41 | avatars = mock.reRequire('../lib/letter-avatars') 42 | }) 43 | 44 | it('should return correct urls', function () { 45 | assert.strictEqual(avatars.generateAvatarURL('Daan Sprenkels', 'hello@dsprenkels.com', true), 'http://localhost:3000/user/Daan%20Sprenkels/avatar.svg') 46 | assert.strictEqual(avatars.generateAvatarURL('Daan Sprenkels', 'hello@dsprenkels.com', false), 'http://localhost:3000/user/Daan%20Sprenkels/avatar.svg') 47 | }) 48 | 49 | it('should return correct urls for names with spaces', function () { 50 | assert.strictEqual(avatars.generateAvatarURL('Daan Sprenkels'), 'http://localhost:3000/user/Daan%20Sprenkels/avatar.svg') 51 | }) 52 | }) 53 | -------------------------------------------------------------------------------- /webpack.dev.js: -------------------------------------------------------------------------------- 1 | const common = require('./webpack.common.js') 2 | const htmlexport = require('./webpack.htmlexport') 3 | const { merge } = require('webpack-merge'); 4 | 5 | module.exports = [ 6 | // merge common config 7 | merge(common, { 8 | mode: 'development', 9 | devtool: 'cheap-module-source-map' 10 | }), 11 | merge(htmlexport, { 12 | mode: 'development', 13 | devtool: 'cheap-module-source-map' 14 | })] 15 | -------------------------------------------------------------------------------- /webpack.htmlexport.js: -------------------------------------------------------------------------------- 1 | const MiniCssExtractPlugin = require('mini-css-extract-plugin') 2 | const path = require('path') 3 | const HtmlWebpackPlugin = require('html-webpack-plugin') 4 | 5 | module.exports = { 6 | name: 'save-as-html', 7 | entry: { 8 | htmlExport: path.join(__dirname, 'public/js/htmlExport.js') 9 | }, 10 | module: { 11 | rules: [{ 12 | test: /\.css$/, 13 | use: [MiniCssExtractPlugin.loader, 'css-loader'] 14 | }, 15 | { 16 | test: /\.(woff(2)?|ttf|eot|svg)(\?v=\d+\.\d+\.\d+)?$/, 17 | use: [ 18 | { 19 | loader: 'url-loader' 20 | } 21 | ] 22 | }] 23 | }, 24 | output: { 25 | path: path.join(__dirname, 'public/build'), 26 | publicPath: 'build/', 27 | filename: '[name].js' 28 | }, 29 | plugins: [ 30 | new HtmlWebpackPlugin({ 31 | // Load a custom template (uses lodash templating) 32 | template: 'public/views/htmlexport.ejs', 33 | filename: 'htmlexport.html', 34 | inject: false, 35 | cache: false 36 | }), 37 | new MiniCssExtractPlugin({ filename: 'htmlexport.css' }) 38 | ] 39 | } 40 | -------------------------------------------------------------------------------- /webpack.prod.js: -------------------------------------------------------------------------------- 1 | const common = require('./webpack.common.js') 2 | const htmlexport = require('./webpack.htmlexport') 3 | const { merge } = require('webpack-merge') 4 | const path = require('path') 5 | const OptimizeCSSAssetsPlugin = require('optimize-css-assets-webpack-plugin') 6 | const { EsbuildPlugin } = require('esbuild-loader') 7 | 8 | module.exports = [ 9 | merge(common, { 10 | mode: 'production', 11 | output: { 12 | path: path.join(__dirname, 'public/build'), 13 | publicPath: 'build/', 14 | filename: '[name].[contenthash].js' 15 | }, 16 | optimization: { 17 | minimizer: [ 18 | new EsbuildPlugin({ 19 | target: 'es2015', 20 | format: "cjs", 21 | exclude: ['MathJax/extensions/a11y/mathmaps'] 22 | }) 23 | ], 24 | splitChunks: { 25 | chunks: 'all' 26 | } 27 | }, 28 | devtool: 'source-map' 29 | }), 30 | merge(htmlexport, { 31 | mode: 'production', 32 | optimization: { 33 | minimizer: [ 34 | new EsbuildPlugin({ 35 | target: 'es2015', 36 | format: "cjs" 37 | }), 38 | new OptimizeCSSAssetsPlugin({}) 39 | ] 40 | } 41 | })] 42 | --------------------------------------------------------------------------------