Files
outline/package.json
Tom Moor bda95e4952 feat: Date mentions (#12621)
* Add date mentions to the editor

Introduce a new "date" mention type alongside the existing user,
document and collection mentions. Typing @ with a natural language date
(e.g. "tomorrow", "next friday", "jan 2") surfaces a date suggestion,
parsed via chrono-node. Dates are stored as date-only ISO strings and
displayed with increasing granularity (Today / Tomorrow / January 2nd /
February 3rd, 2024), recomputed dynamically so relative labels stay
fresh. Clicking a date mention opens a Radix popover calendar to change
it.

* Load chrono-node lazily to keep it out of the main bundle

Convert parseNaturalLanguageDate to dynamically import chrono-node on
first use so the bundler splits it into a separate chunk fetched only
when a date is actually parsed. The mention menu now resolves the parse
asynchronously in an effect.

* Add DynamicCalendarIcon

* Lock page scroll while the date mention picker is open

Wrap the date picker popover content in RemoveScroll (via a Slot, with
the Radix content asChild), mirroring the inline editor menu, so the
page can't scroll behind the open calendar.

* Restyle the date mention calendar picker

The react-day-picker base stylesheet isn't loaded in the editor, so day
cells fell back to default browser button styling. Style the calendar
from scratch to match the rest of the app: reset button chrome, show
outside (previous/next month) days clearly de-emphasised, render the
selected day as a solid accent-filled circle, and emphasise today with
the accent colour. Enable showOutsideDays and fixedWeeks for a stable
6-week grid.

* Share one themed Calendar between the date mention and API key pickers

Extract the custom react-day-picker styling into a reusable Calendar
component and use it in both the date mention picker and the API key
expiry picker, so they look identical. The calendar owns its own padding
and the API key scene no longer needs the library's base stylesheet.

* Make the ISO date the single source of truth for date mentions

Date mentions no longer persist a human-readable label in the ProseMirror
data. Instead the displayed text, plaintext, DOM text and markdown link
text are all derived from the ISO modelId, so the saved data can never
drift or go stale. parseDOM/parseMarkdown no longer capture rendered text
as a label for dates, and the mention menu/picker stop writing one.

* tweaks

* Make DynamicCalendarIcon day text contrast with its fill

The day number is rendered white with mix-blend-mode difference, which
produces the exact inverse of the icon's (currentColor) fill, so it stays
legible whatever colour the icon takes. The SVG is isolated so the blend
only considers the icon's own fill.

* Lazy-load the date picker to keep Radix out of the editor schema graph

Importing @radix-ui/react-popover and react-day-picker at the top of
Mentions.tsx pulled them into the editor schema's static import graph,
which is also loaded on the server. Radix's prebuilt ESM does a bare
"react/jsx-runtime" import that the node/shared test resolvers can't
resolve, breaking all server and shared editor test suites.

Move the popover + calendar into DateMentionPicker, loaded via
React.lazy, so the browser-only dependencies are code-split out of the
schema graph and only fetched when an editable date mention renders.

* Deprecate block menu date/time commands in favor of date mention

Replace the block menu "Current date" entry so it inserts a date mention
for today instead of a static string/template token, and remove the
"Current time" and "Current date and time" entries. The underlying
DateTime extension and its {date}/{time}/{datetime} template placeholders
are left intact so existing documents and templates keep working.

* Omit the year from dateToReadable within the current year

dateToReadable now formats current-year dates without the year (e.g.
"June 8th") and includes it otherwise (e.g. "February 3rd, 2024"). This
keeps the mention menu subtitle compact while the relative title shows
"Today"/"Tomorrow".

* Let date mentions inherit surrounding font weight

The .mention style fixes font-weight to 500, which prevented a date
mention placed inside a heading from rendering bold like the rest of the
heading. Date mentions are plain text, so they now inherit the font
weight of their context.

* Address review feedback on date mentions

- MentionMenu: catch rejected date-parse promises so a chunk-load failure
  clears the suggestion instead of leaving stale state / an unhandled rejection.
- parseNaturalLanguageDate: don't cache a rejected chrono import so a later
  parse can retry after a transient failure.
- parseISODate: reject strings with a time component to honor the date-only
  contract and keep day-granular comparisons correct.
- DynamicCalendarIcon: mark the decorative SVG aria-hidden / focusable=false.

* tweaks

---------

Co-authored-by: Claude <noreply@anthropic.com>
2026-06-12 21:42:16 -04:00

400 lines
14 KiB
JSON

{
"name": "outline",
"private": true,
"license": "Business Source License 1.1",
"main": "index.js",
"scripts": {
"clean": "rimraf build",
"copy:i18n": "mkdir -p ./build/shared/i18n && cp -R ./shared/i18n/locales ./build/shared/i18n",
"build:i18n": "i18next --silent '{shared,app,server,plugins}/**/*.{ts,tsx}' && yarn copy:i18n",
"build:server": "node ./build.js",
"build": "yarn clean && yarn vite:build && yarn build:i18n && yarn build:server",
"start": "node ./build/server/index.js",
"dev": "NODE_ENV=development yarn concurrently -n api,collaboration -c \"blue,magenta\" \"node --inspect=0.0.0.0 build/server/index.js --services=cron,collaboration,websockets,admin,web,worker\"",
"dev:backend": "NODE_ENV=development nodemon --quiet --exec \"yarn build:server && yarn dev\" -e js,ts,tsx --watch server --watch shared --watch plugins --watch .env --watch .env.local --watch .env.development --ignore \"shared/components/**/*.tsx?\" --ignore \"plugins/client/**/*.tsx?\" --ignore \"**/*.test.ts\" --ignore data/ --ignore build/ --ignore app/ --ignore shared/editor --ignore server/migrations",
"dev:watch": "NODE_ENV=development yarn concurrently -n backend,frontend \"yarn dev:backend\" \"yarn vite:dev\"",
"lint": "oxlint --type-aware app server shared plugins",
"lint:changed": "git diff --name-only --diff-filter=ACMRTUXB | grep -E '\\.(js|jsx|ts|tsx)$' | xargs -r oxlint",
"format": "prettier --write .",
"format:check": "prettier --check .",
"prepare": "husky install",
"postinstall": "yarn patch-package",
"install-local-ssl": "node ./server/scripts/install-local-ssl.js",
"release": "node ./server/scripts/release.js",
"heroku-postbuild": "yarn build && yarn db:migrate",
"db:create-migration": "sequelize migration:create",
"db:create": "sequelize db:create",
"db:migrate": "sequelize db:migrate",
"db:rollback": "sequelize db:migrate:undo",
"db:reset": "sequelize db:drop && sequelize db:create && sequelize db:migrate",
"upgrade": "git fetch && git pull && yarn install && yarn heroku-postbuild",
"test": "TZ=UTC vitest run",
"test:app": "TZ=UTC vitest run --project app",
"test:shared": "TZ=UTC vitest run --project shared-node --project shared-jsdom",
"test:server": "TZ=UTC vitest run --project server",
"test:watch": "TZ=UTC vitest",
"vite:dev": "VITE_CJS_IGNORE_WARNING=true vite",
"vite:build": "VITE_CJS_IGNORE_WARNING=true vite build",
"vite:preview": "VITE_CJS_IGNORE_WARNING=true vite preview"
},
"funding": {
"type": "GitHub Sponsors ❤",
"url": "https://github.com/sponsors/outline"
},
"engines": {
"node": ">=20.12 <21 || 22 || 24"
},
"repository": {
"type": "git",
"url": "git+ssh://git@github.com/outline/outline.git"
},
"browserslist": [
"> 0.25%, not dead"
],
"dependencies": {
"@aws-sdk/client-s3": "^3.1053.0",
"@aws-sdk/lib-storage": "^3.1053.0",
"@aws-sdk/s3-presigned-post": "^3.1053.0",
"@aws-sdk/s3-request-presigner": "^3.1053.0",
"@aws-sdk/signature-v4-crt": "^3.1053.0",
"@benrbray/prosemirror-math": "^0.2.2",
"@bull-board/api": "^6.21.3",
"@bull-board/koa": "^6.21.3",
"@css-inline/css-inline-wasm": "^0.20.2",
"@dnd-kit/core": "^6.3.1",
"@dnd-kit/modifiers": "^6.0.1",
"@dnd-kit/sortable": "^7.0.2",
"@dotenvx/dotenvx": "^1.66.0",
"@emoji-mart/data": "^1.2.1",
"@fast-csv/parse": "^5.0.7",
"@fortawesome/fontawesome-svg-core": "^7.2.0",
"@fortawesome/free-brands-svg-icons": "^7.2.0",
"@fortawesome/free-solid-svg-icons": "^7.2.0",
"@fortawesome/react-fontawesome": "^0.2.6",
"@getoutline/react-roving-tabindex": "^3.2.4",
"@gitbeaker/rest": "^43.8.0",
"@hocuspocus/extension-redis": "1.1.3",
"@hocuspocus/extension-throttle": "1.1.3",
"@hocuspocus/provider": "1.1.3",
"@hocuspocus/server": "1.1.3",
"@juggle/resize-observer": "^3.4.0",
"@linear/sdk": "^58.1.0",
"@mermaid-js/layout-elk": "^0.2.1",
"@modelcontextprotocol/sdk": "^1.25.3",
"@node-oauth/oauth2-server": "^5.3.0",
"@notionhq/client": "^2.3.0",
"@octokit/auth-app": "^8.2.0",
"@octokit/webhooks-types": "^7.6.1",
"@outlinewiki/koa-passport": "^4.2.1",
"@outlinewiki/passport-azure-ad-oauth2": "^0.1.0",
"@radix-ui/react-collapsible": "^1.1.12",
"@radix-ui/react-context-menu": "^2.2.16",
"@radix-ui/react-dialog": "^1.1.15",
"@radix-ui/react-direction": "^1.1.1",
"@radix-ui/react-dropdown-menu": "^2.1.16",
"@radix-ui/react-one-time-password-field": "^0.1.8",
"@radix-ui/react-popover": "^1.1.15",
"@radix-ui/react-select": "^2.2.6",
"@radix-ui/react-slot": "^1.2.3",
"@radix-ui/react-switch": "^1.2.6",
"@radix-ui/react-tabs": "^1.1.13",
"@radix-ui/react-toolbar": "^1.1.11",
"@radix-ui/react-tooltip": "^1.2.8",
"@radix-ui/react-visually-hidden": "^1.2.4",
"@sentry/node": "^7.120.4",
"@sentry/react": "^7.120.4",
"@simplewebauthn/browser": "^13.3.0",
"@simplewebauthn/server": "^13.3.1",
"@tanstack/react-table": "^8.21.3",
"@tanstack/react-virtual": "^3.13.24",
"@types/form-data": "^2.5.2",
"@types/mailparser": "^3.4.6",
"@types/pako": "^2.0.4",
"@types/sanitize-filename": "^1.6.3",
"@vitejs/plugin-react-oxc": "^0.2.3",
"addressparser": "^1.0.1",
"async-sema": "^3.1.1",
"bull": "^4.16.5",
"chrono-node": "^2.9.1",
"class-validator": "^0.15.1",
"command-score": "^0.1.2",
"compressorjs": "^1.3.0",
"content-disposition": "^0.5.4",
"cookie": "^0.7.2",
"copy-to-clipboard": "^3.3.3",
"core-js": "^3.45.1",
"crypto-js": "^4.2.0",
"date-fns": "^3.6.0",
"dd-trace": "^5.98.0",
"diff": "^5.2.2",
"email-providers": "^1.14.0",
"emoji-mart": "^5.6.0",
"emoji-regex": "^10.6.0",
"es-toolkit": "^1.46.1",
"es6-error": "^4.1.1",
"fast-deep-equal": "^3.1.3",
"fetch-retry": "^5.0.6",
"form-data": "^4.0.5",
"fractional-index": "^1.0.0",
"framer-motion": "^6.5.1",
"franc": "^6.2.0",
"fs-extra": "^11.3.4",
"fuzzy-search": "^3.2.1",
"glob": "^11.1.0",
"hot-shots": "^12.1.0",
"http-errors": "2.0.1",
"https-proxy-agent": "^7.0.6",
"i18next": "^22.5.1",
"i18next-fs-backend": "^2.6.5",
"i18next-http-backend": "^3.0.6",
"invariant": "^2.2.4",
"ioredis": "^5.10.1",
"ipaddr.js": "^2.4.0",
"is-printable-key-event": "^1.0.0",
"iso-639-3": "^3.0.1",
"js-yaml": "^4.1.1",
"jsdom": "^22.1.0",
"jsonwebtoken": "^9.0.3",
"katex": "^0.16.45",
"kbar": "0.1.0-beta.48",
"koa": "^3.0.3",
"koa-body": "^6.0.1",
"koa-compress": "^5.2.1",
"koa-helmet": "^6.1.0",
"koa-logger": "^3.2.1",
"koa-mount": "^4.2.0",
"koa-router": "7.4.0",
"koa-send": "5.0.1",
"koa-sslify": "5.0.1",
"koa-useragent": "^4.1.0",
"mailparser": "^3.7.5",
"mammoth": "^1.11.0",
"markdown-it": "^14.1.1",
"markdown-it-container": "^3.0.0",
"markdown-it-emoji": "^3.0.0",
"mermaid": "11.15.0",
"mime-types": "^3.0.2",
"mobx": "^4.15.7",
"mobx-react": "^6.3.1",
"mobx-utils": "^4.0.1",
"natural-sort": "^1.0.0",
"node-fetch": "2.7.0",
"nodemailer": "^7.0.13",
"octokit": "^5.0.5",
"outline-icons": "^4.3.0",
"oy-vey": "^0.12.1",
"pako": "^2.1.0",
"passport": "^0.7.0",
"passport-google-oauth2": "^0.2.0",
"passport-oauth2": "^1.8.0",
"passport-slack-oauth2": "^1.2.0",
"patch-package": "^8.0.1",
"pg": "^8.20.0",
"pg-tsquery": "^8.4.2",
"pluralize": "^8.0.0",
"png-chunks-extract": "^1.0.0",
"polished": "^4.3.1",
"prosemirror-changeset": "2.4.1",
"prosemirror-codemark": "^0.4.2",
"prosemirror-commands": "^1.7.1",
"prosemirror-dropcursor": "^1.8.2",
"prosemirror-gapcursor": "^1.4.1",
"prosemirror-history": "^1.5.0",
"prosemirror-inputrules": "^1.5.1",
"prosemirror-keymap": "^1.2.3",
"prosemirror-markdown": "^1.13.4",
"prosemirror-model": "^1.25.4",
"prosemirror-schema-list": "^1.5.1",
"prosemirror-state": "^1.4.4",
"prosemirror-tables": "^1.8.5",
"prosemirror-transform": "1.10.5",
"prosemirror-view": "^1.41.8",
"proxy-from-env": "^1.1.0",
"query-string": "^7.1.3",
"rate-limiter-flexible": "^2.4.2",
"react": "^17.0.2",
"react-avatar-editor": "^13.0.2",
"react-colorful": "^5.7.0",
"react-day-picker": "^8.10.2",
"react-dnd": "^16.0.1",
"react-dnd-html5-backend": "^16.0.1",
"react-dom": "^17.0.2",
"react-dropzone": "^11.7.1",
"react-helmet-async": "^2.0.5",
"react-hook-form": "^7.76.0",
"react-i18next": "^12.3.1",
"react-merge-refs": "^2.1.1",
"react-portal": "^4.3.0",
"react-remove-scroll": "^2.7.2",
"react-router-dom": "^5.3.4",
"react-use-measure": "^2.1.7",
"react-virtualized-auto-sizer": "^1.0.26",
"react-waypoint": "^10.3.0",
"react-window": "^1.8.11",
"react-zoom-pan-pinch": "^3.7.0",
"redlock": "^5.0.0-beta2",
"reflect-metadata": "^0.2.2",
"refractor": "^3.6.0",
"resolve-path": "^1.4.0",
"sanitize-filename": "^1.6.4",
"scroll-into-view-if-needed": "^3.1.0",
"semver": "^7.8.1",
"sequelize": "^6.37.8",
"sequelize-cli": "^6.6.5",
"sequelize-encrypted": "^1.0.0",
"sequelize-strict-attributes": "^1.0.2",
"sequelize-typescript": "^2.1.6",
"slug": "^5.3.0",
"slugify": "^1.6.9",
"socket.io": "^4.8.3",
"socket.io-client": "^4.8.3",
"socket.io-redis": "^6.1.1",
"sonner": "^1.7.4",
"stoppable": "^1.1.0",
"string-replace-to-array": "^2.1.1",
"styled-components": "^5.3.11",
"styled-components-breakpoint": "^2.1.1",
"styled-normalize": "^8.1.1",
"throng": "^5.0.0",
"tiny-cookie": "^2.5.1",
"tmp": "^0.2.6",
"tunnel-agent": "^0.6.0",
"ukkonen": "^2.2.0",
"umzug": "^3.8.3",
"utility-types": "^3.11.0",
"uuid": "^11.1.1",
"validator": "13.15.35",
"vaul": "^1.1.2",
"vite": "npm:rolldown-vite@7.3.1",
"vite-plugin-pwa": "1.3.0",
"web-haptics": "^0.0.6",
"winston": "^3.17.0",
"ws": "^7.5.10",
"y-indexeddb": "^9.0.12",
"y-prosemirror": "^1.3.7",
"y-protocols": "^1.0.7",
"yauzl": "^3.3.1",
"yazl": "^3.3.1",
"yjs": "^13.6.31",
"zod": "^4.4.3"
},
"devDependencies": {
"@babel/cli": "^7.29.7",
"@babel/core": "^7.29.7",
"@babel/plugin-proposal-decorators": "^7.29.7",
"@babel/plugin-transform-class-properties": "^7.29.7",
"@babel/preset-env": "^7.29.7",
"@babel/preset-react": "^7.29.7",
"@babel/preset-typescript": "^7.29.7",
"@faker-js/faker": "^8.4.1",
"@relative-ci/agent": "^4.3.1",
"@swc/core": "^1.15.32",
"@types/addressparser": "^1.0.3",
"@types/cookie": "0.6.0",
"@types/crypto-js": "^4.2.2",
"@types/diff": "^5.0.9",
"@types/dotenv": "^8.2.3",
"@types/emoji-regex": "^9.2.2",
"@types/escape-html": "^1.0.4",
"@types/express-useragent": "^1.0.5",
"@types/formidable": "^2.0.6",
"@types/fs-extra": "^11.0.4",
"@types/fuzzy-search": "^2.1.5",
"@types/glob": "^8.0.1",
"@types/google.analytics": "^0.0.46",
"@types/invariant": "^2.2.37",
"@types/ioredis-mock": "^8.2.7",
"@types/js-yaml": "^4.0.9",
"@types/jsdom": "^28.0.1",
"@types/jsonwebtoken": "^8.5.9",
"@types/katex": "^0.16.8",
"@types/koa": "^2.15.1",
"@types/koa-compress": "^4.0.7",
"@types/koa-logger": "^3.1.5",
"@types/koa-mount": "^4.0.5",
"@types/koa-router": "^7.4.9",
"@types/koa-send": "^4.1.6",
"@types/koa-sslify": "^4.0.6",
"@types/koa-useragent": "^2.1.2",
"@types/markdown-it": "14.1.2",
"@types/markdown-it-container": "^2.0.11",
"@types/markdown-it-emoji": "^3.0.1",
"@types/mime-types": "^3.0.1",
"@types/natural-sort": "^0.0.24",
"@types/node": "20.19.39",
"@types/node-fetch": "^2.6.13",
"@types/nodemailer": "^6.4.23",
"@types/passport-oauth2": "^1.8.0",
"@types/pluralize": "^0.0.33",
"@types/png-chunks-extract": "^1.0.2",
"@types/proxy-from-env": "^1.0.4",
"@types/quoted-printable": "^1.0.2",
"@types/react": "17.0.91",
"@types/react-avatar-editor": "^13.0.4",
"@types/react-dom": "^17.0.26",
"@types/react-helmet": "^6.1.11",
"@types/react-portal": "^4.0.7",
"@types/react-router-dom": "^5.3.3",
"@types/react-virtualized-auto-sizer": "^1.0.8",
"@types/react-window": "^1.8.8",
"@types/readable-stream": "^4.0.23",
"@types/redis-info": "^3.0.3",
"@types/refractor": "^3.4.1",
"@types/resolve-path": "^1.4.3",
"@types/semver": "^7.7.1",
"@types/sequelize": "^4.28.20",
"@types/slug": "^5.0.9",
"@types/stoppable": "^1.1.3",
"@types/styled-components": "^5.1.36",
"@types/throng": "^5.0.7",
"@types/tmp": "^0.2.6",
"@types/utf8": "^3.0.3",
"@types/validator": "^13.15.10",
"@types/yauzl": "^2.10.3",
"@types/yazl": "^2.4.6",
"@vitest/ui": "^4.1.8",
"babel-plugin-module-resolver": "^5.0.3",
"babel-plugin-styled-components": "^2.1.4",
"babel-plugin-transform-inline-environment-variables": "^0.4.4",
"babel-plugin-transform-typescript-metadata": "^0.4.0",
"browserslist-to-esbuild": "^1.2.0",
"concurrently": "^8.2.2",
"discord-api-types": "^0.38.48",
"husky": "^8.0.3",
"i18next-parser": "^9.4.0",
"ioredis-mock": "^8.13.1",
"lint-staged": "^16.4.0",
"msw": "^2.14.2",
"nodemon": "^3.1.14",
"oxlint": "1.66.0",
"oxlint-tsgolint": "0.22.1",
"postinstall-postinstall": "^2.1.0",
"prettier": "^3.8.3",
"react-refresh": "^0.18.0",
"rimraf": "^6.1.3",
"rollup-plugin-webpack-stats": "2.1.11",
"terser": "^5.48.0",
"typescript": "^5.9.3",
"unplugin-swc": "^1.5.9",
"vite-plugin-babel": "^1.7.3",
"vite-tsconfig-paths": "^6.1.1",
"vitest": "^4.1.5"
},
"resolutions": {
"@types/react": "17.0.91",
"@types/koa": "2.15.1",
"prosemirror-transform": "1.10.5",
"prismjs": "1.30.0",
"zod": "^4.4.3",
"@types/markdown-it": "14.1.2",
"ip-address@npm:10.1.0": "^10.2.0",
"minimatch@npm:9.0.1": "9.0.9",
"lodash@npm:4.17.21": "^4.18.1",
"i18next-parser/i18next": "^23.16.8",
"ws@npm:~8.17.1": "^8.20.1",
"uuid": "^11.1.1"
},
"version": "1.8.1",
"packageManager": "yarn@4.11.0"
}