feat(frontend): add TipTap editor and Reasoning panel

Phase 2 implementation:
- Add TipTap editor with bold, italic, headings, lists, code blocks
- Add Reasoning panel with editable reasoning_type, confidence, steps
- Add Markdown to TipTap conversion on document load
- Add PUT /documents/{id}/content endpoint integration
- Add PATCH /documents/{id}/reasoning endpoint integration
- New types: ReasoningStep, ReasoningPanelData, TipTapContentResponse
- New store methods: updateReasoning, addReasoningStep, deleteReasoningStep
- New components: TipTapEditor.vue, ReasoningPanel.vue
This commit is contained in:
Hiro
2026-03-30 23:06:46 +00:00
parent b2153ceb4b
commit b733306773
7 changed files with 2023 additions and 67 deletions

864
package-lock.json generated
View File

@@ -8,6 +8,10 @@
"name": "claudia-docs-frontend",
"version": "1.0.0",
"dependencies": {
"@tiptap/extension-code-block-lowlight": "^3.21.0",
"@tiptap/starter-kit": "^3.21.0",
"@tiptap/vue-3": "^3.21.0",
"lowlight": "^3.3.0",
"pinia": "^2.1.7",
"vue": "^3.4.21",
"vue-router": "^4.3.0"
@@ -456,12 +460,43 @@
"node": ">=12"
}
},
"node_modules/@floating-ui/core": {
"version": "1.7.5",
"resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.7.5.tgz",
"integrity": "sha512-1Ih4WTWyw0+lKyFMcBHGbb5U5FtuHJuujoyyr5zTaWS5EYMeT6Jb2AuDeftsCsEuchO+mM2ij5+q9crhydzLhQ==",
"license": "MIT",
"dependencies": {
"@floating-ui/utils": "^0.2.11"
}
},
"node_modules/@floating-ui/dom": {
"version": "1.7.6",
"resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.7.6.tgz",
"integrity": "sha512-9gZSAI5XM36880PPMm//9dfiEngYoC6Am2izES1FF406YFsjvyBMmeJ2g4SAju3xWwtuynNRFL2s9hgxpLI5SQ==",
"license": "MIT",
"dependencies": {
"@floating-ui/core": "^1.7.5",
"@floating-ui/utils": "^0.2.11"
}
},
"node_modules/@floating-ui/utils": {
"version": "0.2.11",
"resolved": "https://registry.npmjs.org/@floating-ui/utils/-/utils-0.2.11.tgz",
"integrity": "sha512-RiB/yIh78pcIxl6lLMG0CgBXAZ2Y0eVHqMPYugu+9U0AeT6YBeiJpf7lbdJNIugFP5SIjwNRgo4DhR1Qxi26Gg==",
"license": "MIT"
},
"node_modules/@jridgewell/sourcemap-codec": {
"version": "1.5.5",
"resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz",
"integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==",
"license": "MIT"
},
"node_modules/@remirror/core-constants": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/@remirror/core-constants/-/core-constants-3.0.0.tgz",
"integrity": "sha512-42aWfPrimMfDKDi4YegyS7x+/0tlzaqwPQCULLanv3DMIlu96KTJR0fM5isWX2UViOqlGnX6YFgqWepcX+XMNg==",
"license": "MIT"
},
"node_modules/@rollup/rollup-android-arm-eabi": {
"version": "4.60.1",
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.60.1.tgz",
@@ -851,6 +886,450 @@
"win32"
]
},
"node_modules/@tiptap/core": {
"version": "3.21.0",
"resolved": "https://registry.npmjs.org/@tiptap/core/-/core-3.21.0.tgz",
"integrity": "sha512-IfnQiuEeabDSPr1C/zHFTbnvlTf5z0DE/d/xz4C6bkL4ZBDJ3rr99h2qsaV0l8F+kbNswZMlQdM8rxNlMy95fQ==",
"license": "MIT",
"funding": {
"type": "github",
"url": "https://github.com/sponsors/ueberdosis"
},
"peerDependencies": {
"@tiptap/pm": "^3.21.0"
}
},
"node_modules/@tiptap/extension-blockquote": {
"version": "3.21.0",
"resolved": "https://registry.npmjs.org/@tiptap/extension-blockquote/-/extension-blockquote-3.21.0.tgz",
"integrity": "sha512-JDM/RR6rM0dMCZ1UnEf7eqmN6pAdIa2llhN+E24HdTGNJCklMFhLAGE/OT8/1r7M0WWA9GVO7/PTe4EdGh6+lQ==",
"license": "MIT",
"funding": {
"type": "github",
"url": "https://github.com/sponsors/ueberdosis"
},
"peerDependencies": {
"@tiptap/core": "^3.21.0"
}
},
"node_modules/@tiptap/extension-bold": {
"version": "3.21.0",
"resolved": "https://registry.npmjs.org/@tiptap/extension-bold/-/extension-bold-3.21.0.tgz",
"integrity": "sha512-iyEJRzG7XTCPlHwEDzUw3HnuYYCfL7lNpcCHmxcpYMrIUA8rv7EUxerIwApT6xY8hQ/07ljuJKgOyPvnJOOzuA==",
"license": "MIT",
"funding": {
"type": "github",
"url": "https://github.com/sponsors/ueberdosis"
},
"peerDependencies": {
"@tiptap/core": "^3.21.0"
}
},
"node_modules/@tiptap/extension-bubble-menu": {
"version": "3.21.0",
"resolved": "https://registry.npmjs.org/@tiptap/extension-bubble-menu/-/extension-bubble-menu-3.21.0.tgz",
"integrity": "sha512-/fabRRhhf8i4LAx9e8xz9ppqN5KgdJk3TxMuxAD5vAWGsejvhSoPa8O8H/QwwyntXm1Vue8aQiMHsUk48b2hGQ==",
"license": "MIT",
"optional": true,
"dependencies": {
"@floating-ui/dom": "^1.0.0"
},
"funding": {
"type": "github",
"url": "https://github.com/sponsors/ueberdosis"
},
"peerDependencies": {
"@tiptap/core": "^3.21.0",
"@tiptap/pm": "^3.21.0"
}
},
"node_modules/@tiptap/extension-bullet-list": {
"version": "3.21.0",
"resolved": "https://registry.npmjs.org/@tiptap/extension-bullet-list/-/extension-bullet-list-3.21.0.tgz",
"integrity": "sha512-PWNF+xwxgOeXYGD88sCQLKL0eBoQqjUnZNALxBjN3Y7x4llalh42rHOp2Nt2t6UbQgqTBtBzU/uFcussTpxreQ==",
"license": "MIT",
"funding": {
"type": "github",
"url": "https://github.com/sponsors/ueberdosis"
},
"peerDependencies": {
"@tiptap/extension-list": "^3.21.0"
}
},
"node_modules/@tiptap/extension-code": {
"version": "3.21.0",
"resolved": "https://registry.npmjs.org/@tiptap/extension-code/-/extension-code-3.21.0.tgz",
"integrity": "sha512-D7wA9jp+4X2r1f3FIoga73s6Rn4rmZY57Jes6a4rK3HY+3yHk1r057pPIZSY8Drfs97jxHQVFdfUYUomLSFYBA==",
"license": "MIT",
"funding": {
"type": "github",
"url": "https://github.com/sponsors/ueberdosis"
},
"peerDependencies": {
"@tiptap/core": "^3.21.0"
}
},
"node_modules/@tiptap/extension-code-block": {
"version": "3.21.0",
"resolved": "https://registry.npmjs.org/@tiptap/extension-code-block/-/extension-code-block-3.21.0.tgz",
"integrity": "sha512-zrVOcOzDCjHQ8NJcC+qHmZZKiwnP/NMSb3qVJlSMN8TzuHept1MZCDa2Mbo70O6I0txo456SGuXB9sqV1vHmGg==",
"license": "MIT",
"funding": {
"type": "github",
"url": "https://github.com/sponsors/ueberdosis"
},
"peerDependencies": {
"@tiptap/core": "^3.21.0",
"@tiptap/pm": "^3.21.0"
}
},
"node_modules/@tiptap/extension-code-block-lowlight": {
"version": "3.21.0",
"resolved": "https://registry.npmjs.org/@tiptap/extension-code-block-lowlight/-/extension-code-block-lowlight-3.21.0.tgz",
"integrity": "sha512-79sS0tqoGVX6wq30ejzohpUVLeGOkTTUn5hCqjsniyYPTEtrn4tHyBnS4Du2TbrDV4SqcwgisWExhuB8pfEdYQ==",
"license": "MIT",
"funding": {
"type": "github",
"url": "https://github.com/sponsors/ueberdosis"
},
"peerDependencies": {
"@tiptap/core": "^3.21.0",
"@tiptap/extension-code-block": "^3.21.0",
"@tiptap/pm": "^3.21.0",
"highlight.js": "^11",
"lowlight": "^2 || ^3"
}
},
"node_modules/@tiptap/extension-document": {
"version": "3.21.0",
"resolved": "https://registry.npmjs.org/@tiptap/extension-document/-/extension-document-3.21.0.tgz",
"integrity": "sha512-7oCyzXI9ChvJQUlr23AURdfVar4OIsrYUvqdhEwo3bjcI/Q/j0KJiXfuh6ZzL5eVaINSailH53sZaGg4THQtUg==",
"license": "MIT",
"funding": {
"type": "github",
"url": "https://github.com/sponsors/ueberdosis"
},
"peerDependencies": {
"@tiptap/core": "^3.21.0"
}
},
"node_modules/@tiptap/extension-dropcursor": {
"version": "3.21.0",
"resolved": "https://registry.npmjs.org/@tiptap/extension-dropcursor/-/extension-dropcursor-3.21.0.tgz",
"integrity": "sha512-6fsDSVAM2iz7eElvT6iivMrGBGjIP/oPigVZ/SPm6f31phaYhz6TIOEgV/Lr2jaPIOgyK4U0cU4Yd4KUBCmhzQ==",
"license": "MIT",
"funding": {
"type": "github",
"url": "https://github.com/sponsors/ueberdosis"
},
"peerDependencies": {
"@tiptap/extensions": "^3.21.0"
}
},
"node_modules/@tiptap/extension-floating-menu": {
"version": "3.21.0",
"resolved": "https://registry.npmjs.org/@tiptap/extension-floating-menu/-/extension-floating-menu-3.21.0.tgz",
"integrity": "sha512-n2HzTB+I/5rAl8R/1sKMv92JiY1oDK1hroXizxEKYa6dskJcAMW0CfYyPcPOZWQQEe7qoeOvQISr2ooLAKW+Mw==",
"license": "MIT",
"optional": true,
"funding": {
"type": "github",
"url": "https://github.com/sponsors/ueberdosis"
},
"peerDependencies": {
"@floating-ui/dom": "^1.0.0",
"@tiptap/core": "^3.21.0",
"@tiptap/pm": "^3.21.0"
}
},
"node_modules/@tiptap/extension-gapcursor": {
"version": "3.21.0",
"resolved": "https://registry.npmjs.org/@tiptap/extension-gapcursor/-/extension-gapcursor-3.21.0.tgz",
"integrity": "sha512-wGjgAoYBTvPAe9QYMI5px355XcNeMkaUrMY9IHbMqgqdmHcDxqooxM4H6sYVX2CRcHwXy4I8NQAoOhSYrQJDMg==",
"license": "MIT",
"funding": {
"type": "github",
"url": "https://github.com/sponsors/ueberdosis"
},
"peerDependencies": {
"@tiptap/extensions": "^3.21.0"
}
},
"node_modules/@tiptap/extension-hard-break": {
"version": "3.21.0",
"resolved": "https://registry.npmjs.org/@tiptap/extension-hard-break/-/extension-hard-break-3.21.0.tgz",
"integrity": "sha512-6JFVSAOQ1qhQHi9mVcdn2/XO8YIMgYV8zjarzNUzP6Sf2waeE5BLXjlg6rIH/945sY1J+FndTojLru6gQ07a5A==",
"license": "MIT",
"funding": {
"type": "github",
"url": "https://github.com/sponsors/ueberdosis"
},
"peerDependencies": {
"@tiptap/core": "^3.21.0"
}
},
"node_modules/@tiptap/extension-heading": {
"version": "3.21.0",
"resolved": "https://registry.npmjs.org/@tiptap/extension-heading/-/extension-heading-3.21.0.tgz",
"integrity": "sha512-ji6VJmoRnDzAHYflEYEZohMHRi77UGLW1o3ua7UhI32iJ9nuYssbPNuzEeE4SvENMQwZRszad5+a+dKAa+NC7g==",
"license": "MIT",
"funding": {
"type": "github",
"url": "https://github.com/sponsors/ueberdosis"
},
"peerDependencies": {
"@tiptap/core": "^3.21.0"
}
},
"node_modules/@tiptap/extension-horizontal-rule": {
"version": "3.21.0",
"resolved": "https://registry.npmjs.org/@tiptap/extension-horizontal-rule/-/extension-horizontal-rule-3.21.0.tgz",
"integrity": "sha512-vNBnOfFEY62CoJPGo4nonRM7RiOvhII1vhoO+WFr1GxDqCAfmEFjToflt7JT1UJdo6lMVcD+aaaAgOiuSz5p6g==",
"license": "MIT",
"funding": {
"type": "github",
"url": "https://github.com/sponsors/ueberdosis"
},
"peerDependencies": {
"@tiptap/core": "^3.21.0",
"@tiptap/pm": "^3.21.0"
}
},
"node_modules/@tiptap/extension-italic": {
"version": "3.21.0",
"resolved": "https://registry.npmjs.org/@tiptap/extension-italic/-/extension-italic-3.21.0.tgz",
"integrity": "sha512-2I8oPvwyXhRn1k8lbDFIutzvhtLEjoO5mmQCNX4TnT4PdxxaSrK9+ihYg12VeqhUeO7dg1MKiFqws0HVBrwzWg==",
"license": "MIT",
"funding": {
"type": "github",
"url": "https://github.com/sponsors/ueberdosis"
},
"peerDependencies": {
"@tiptap/core": "^3.21.0"
}
},
"node_modules/@tiptap/extension-link": {
"version": "3.21.0",
"resolved": "https://registry.npmjs.org/@tiptap/extension-link/-/extension-link-3.21.0.tgz",
"integrity": "sha512-oMU7Yve1sbgBsaFAUc2R0GPf4d3ZPVJeMUFC6b6X9rJIvx/IhEUEn9toQcSBGfp02uWK9NdQyIFYFdWlVXH++w==",
"license": "MIT",
"dependencies": {
"linkifyjs": "^4.3.2"
},
"funding": {
"type": "github",
"url": "https://github.com/sponsors/ueberdosis"
},
"peerDependencies": {
"@tiptap/core": "^3.21.0",
"@tiptap/pm": "^3.21.0"
}
},
"node_modules/@tiptap/extension-list": {
"version": "3.21.0",
"resolved": "https://registry.npmjs.org/@tiptap/extension-list/-/extension-list-3.21.0.tgz",
"integrity": "sha512-KeBlEtLrGce2d3dgL89hmwWEtREuzlW4XY5bYWpKNvCbFqvdSb3n7vkdkw32YclZmMWxAcABgW6ucCStkE0rsQ==",
"license": "MIT",
"funding": {
"type": "github",
"url": "https://github.com/sponsors/ueberdosis"
},
"peerDependencies": {
"@tiptap/core": "^3.21.0",
"@tiptap/pm": "^3.21.0"
}
},
"node_modules/@tiptap/extension-list-item": {
"version": "3.21.0",
"resolved": "https://registry.npmjs.org/@tiptap/extension-list-item/-/extension-list-item-3.21.0.tgz",
"integrity": "sha512-1ZymZmlQVbAoC4q5x3cro0v5+3I6l+BHqbhIMQLjQFlAOJfcE0pvqRzAFW7PduxUj41tXEtsYqp2NREvO9F5Fg==",
"license": "MIT",
"funding": {
"type": "github",
"url": "https://github.com/sponsors/ueberdosis"
},
"peerDependencies": {
"@tiptap/extension-list": "^3.21.0"
}
},
"node_modules/@tiptap/extension-list-keymap": {
"version": "3.21.0",
"resolved": "https://registry.npmjs.org/@tiptap/extension-list-keymap/-/extension-list-keymap-3.21.0.tgz",
"integrity": "sha512-EzrfW3ASNFPWKhR8sNOq7Kqw4hvaTAOn4dlI7chB8HIANSrlyPOUn+eKAnO6HQgsUgsbjg2GbTUrGrxcoLykUg==",
"license": "MIT",
"funding": {
"type": "github",
"url": "https://github.com/sponsors/ueberdosis"
},
"peerDependencies": {
"@tiptap/extension-list": "^3.21.0"
}
},
"node_modules/@tiptap/extension-ordered-list": {
"version": "3.21.0",
"resolved": "https://registry.npmjs.org/@tiptap/extension-ordered-list/-/extension-ordered-list-3.21.0.tgz",
"integrity": "sha512-+d+0orokMfqaBfvr9tUBgGvo2ZCV+fR3JzsJTmnLBWOkhBSJN7H4pnfXPTue0qwspUwRmkLJxdIlU+J7HkMrng==",
"license": "MIT",
"funding": {
"type": "github",
"url": "https://github.com/sponsors/ueberdosis"
},
"peerDependencies": {
"@tiptap/extension-list": "^3.21.0"
}
},
"node_modules/@tiptap/extension-paragraph": {
"version": "3.21.0",
"resolved": "https://registry.npmjs.org/@tiptap/extension-paragraph/-/extension-paragraph-3.21.0.tgz",
"integrity": "sha512-cMPG/jCoZ9NmLZ5ctFziILaxJGfDtMTb5OLBhifMFZeMVwF1pEJIygDEfnX/HSruv507weZSQG4pERO2tRszMg==",
"license": "MIT",
"funding": {
"type": "github",
"url": "https://github.com/sponsors/ueberdosis"
},
"peerDependencies": {
"@tiptap/core": "^3.21.0"
}
},
"node_modules/@tiptap/extension-strike": {
"version": "3.21.0",
"resolved": "https://registry.npmjs.org/@tiptap/extension-strike/-/extension-strike-3.21.0.tgz",
"integrity": "sha512-easnVaN11Wl+5fOtfvzJ10J762S9TRXZaMj5rLBGavgf82DCYHqhGhBqpLQrJ41r4nPABGlYvTRoxfvBLB74Lg==",
"license": "MIT",
"funding": {
"type": "github",
"url": "https://github.com/sponsors/ueberdosis"
},
"peerDependencies": {
"@tiptap/core": "^3.21.0"
}
},
"node_modules/@tiptap/extension-text": {
"version": "3.21.0",
"resolved": "https://registry.npmjs.org/@tiptap/extension-text/-/extension-text-3.21.0.tgz",
"integrity": "sha512-Zx8QdB8a5iBuE4uO21c3BjmpBfaJEr2Jd1QFnsdgx11fm6P7dGgZaGko1FaINhfOPRGTN6O/kiF02cDMdOHa/w==",
"license": "MIT",
"funding": {
"type": "github",
"url": "https://github.com/sponsors/ueberdosis"
},
"peerDependencies": {
"@tiptap/core": "^3.21.0"
}
},
"node_modules/@tiptap/extension-underline": {
"version": "3.21.0",
"resolved": "https://registry.npmjs.org/@tiptap/extension-underline/-/extension-underline-3.21.0.tgz",
"integrity": "sha512-gGmBEymbWnr8AIS8bI/bPw5rcwo7wAFcBw/TsLd1nAanu1dDqSRNDBrit3m02Ru+D88u2SfNvmbOPI1pz+1f5w==",
"license": "MIT",
"funding": {
"type": "github",
"url": "https://github.com/sponsors/ueberdosis"
},
"peerDependencies": {
"@tiptap/core": "^3.21.0"
}
},
"node_modules/@tiptap/extensions": {
"version": "3.21.0",
"resolved": "https://registry.npmjs.org/@tiptap/extensions/-/extensions-3.21.0.tgz",
"integrity": "sha512-MN1uh5PmHT1F2BNsbc21MIS0AMFFA73oODlp/4ckpBR4o5AxRwV+8f43Cd52UL4MgMkKj/A+QfZ7iK9IDb0h5A==",
"license": "MIT",
"funding": {
"type": "github",
"url": "https://github.com/sponsors/ueberdosis"
},
"peerDependencies": {
"@tiptap/core": "^3.21.0",
"@tiptap/pm": "^3.21.0"
}
},
"node_modules/@tiptap/pm": {
"version": "3.21.0",
"resolved": "https://registry.npmjs.org/@tiptap/pm/-/pm-3.21.0.tgz",
"integrity": "sha512-I3sNo7oMMsR6FFz1ecvPb9uCF0VQuS2WV67j8Io2M7DJicRWCE/GM5DaiYjTeWBbnByk6BuG0txoJATAqPVliQ==",
"license": "MIT",
"dependencies": {
"prosemirror-changeset": "^2.3.0",
"prosemirror-collab": "^1.3.1",
"prosemirror-commands": "^1.6.2",
"prosemirror-dropcursor": "^1.8.1",
"prosemirror-gapcursor": "^1.3.2",
"prosemirror-history": "^1.4.1",
"prosemirror-inputrules": "^1.4.0",
"prosemirror-keymap": "^1.2.2",
"prosemirror-markdown": "^1.13.1",
"prosemirror-menu": "^1.2.4",
"prosemirror-model": "^1.24.1",
"prosemirror-schema-basic": "^1.2.3",
"prosemirror-schema-list": "^1.5.0",
"prosemirror-state": "^1.4.3",
"prosemirror-tables": "^1.6.4",
"prosemirror-trailing-node": "^3.0.0",
"prosemirror-transform": "^1.10.2",
"prosemirror-view": "^1.38.1"
},
"funding": {
"type": "github",
"url": "https://github.com/sponsors/ueberdosis"
}
},
"node_modules/@tiptap/starter-kit": {
"version": "3.21.0",
"resolved": "https://registry.npmjs.org/@tiptap/starter-kit/-/starter-kit-3.21.0.tgz",
"integrity": "sha512-w7fWxglDtqXFBgRYH+LforJyUboSAQllnWQbGVSTyX4rsICqZjkb3f6CTSUWpGoGKmlmbb2ZpEuoik7tur9d8Q==",
"license": "MIT",
"dependencies": {
"@tiptap/core": "^3.21.0",
"@tiptap/extension-blockquote": "^3.21.0",
"@tiptap/extension-bold": "^3.21.0",
"@tiptap/extension-bullet-list": "^3.21.0",
"@tiptap/extension-code": "^3.21.0",
"@tiptap/extension-code-block": "^3.21.0",
"@tiptap/extension-document": "^3.21.0",
"@tiptap/extension-dropcursor": "^3.21.0",
"@tiptap/extension-gapcursor": "^3.21.0",
"@tiptap/extension-hard-break": "^3.21.0",
"@tiptap/extension-heading": "^3.21.0",
"@tiptap/extension-horizontal-rule": "^3.21.0",
"@tiptap/extension-italic": "^3.21.0",
"@tiptap/extension-link": "^3.21.0",
"@tiptap/extension-list": "^3.21.0",
"@tiptap/extension-list-item": "^3.21.0",
"@tiptap/extension-list-keymap": "^3.21.0",
"@tiptap/extension-ordered-list": "^3.21.0",
"@tiptap/extension-paragraph": "^3.21.0",
"@tiptap/extension-strike": "^3.21.0",
"@tiptap/extension-text": "^3.21.0",
"@tiptap/extension-underline": "^3.21.0",
"@tiptap/extensions": "^3.21.0",
"@tiptap/pm": "^3.21.0"
},
"funding": {
"type": "github",
"url": "https://github.com/sponsors/ueberdosis"
}
},
"node_modules/@tiptap/vue-3": {
"version": "3.21.0",
"resolved": "https://registry.npmjs.org/@tiptap/vue-3/-/vue-3-3.21.0.tgz",
"integrity": "sha512-dfjxBwxg9+GNvsgkCbxLnj/vmG+YZMdcD/qF7bKM710bANWfqzimRUhH5W2KZcxqlYzqpz0u/P0zi7dUMR5IZA==",
"license": "MIT",
"funding": {
"type": "github",
"url": "https://github.com/sponsors/ueberdosis"
},
"optionalDependencies": {
"@tiptap/extension-bubble-menu": "^3.21.0",
"@tiptap/extension-floating-menu": "^3.21.0"
},
"peerDependencies": {
"@floating-ui/dom": "^1.0.0",
"@tiptap/core": "^3.21.0",
"@tiptap/pm": "^3.21.0",
"vue": "^3.0.0"
}
},
"node_modules/@types/estree": {
"version": "1.0.8",
"resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz",
@@ -858,6 +1337,43 @@
"dev": true,
"license": "MIT"
},
"node_modules/@types/hast": {
"version": "3.0.4",
"resolved": "https://registry.npmjs.org/@types/hast/-/hast-3.0.4.tgz",
"integrity": "sha512-WPs+bbQw5aCj+x6laNGWLH3wviHtoCv/P3+otBhbOhJgG8qtpdAMlTCxLtsTWA7LH1Oh/bFCHsBn0TPS5m30EQ==",
"license": "MIT",
"dependencies": {
"@types/unist": "*"
}
},
"node_modules/@types/linkify-it": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/@types/linkify-it/-/linkify-it-5.0.0.tgz",
"integrity": "sha512-sVDA58zAw4eWAffKOaQH5/5j3XeayukzDk+ewSsnv3p4yJEZHCCzMDiZM8e0OUrRvmpGZ85jf4yDHkHsgBNr9Q==",
"license": "MIT"
},
"node_modules/@types/markdown-it": {
"version": "14.1.2",
"resolved": "https://registry.npmjs.org/@types/markdown-it/-/markdown-it-14.1.2.tgz",
"integrity": "sha512-promo4eFwuiW+TfGxhi+0x3czqTYJkG8qB17ZUJiVF10Xm7NLVRSLUsfRTU/6h1e24VvRnXCx+hG7li58lkzog==",
"license": "MIT",
"dependencies": {
"@types/linkify-it": "^5",
"@types/mdurl": "^2"
}
},
"node_modules/@types/mdurl": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/@types/mdurl/-/mdurl-2.0.0.tgz",
"integrity": "sha512-RGdgjQUZba5p6QEFAVx2OGb8rQDL/cPRG7GiedRzMcJ1tYnUANBncjbSB1NRGwbvjcPeikRABz2nshyPk1bhWg==",
"license": "MIT"
},
"node_modules/@types/unist": {
"version": "3.0.3",
"resolved": "https://registry.npmjs.org/@types/unist/-/unist-3.0.3.tgz",
"integrity": "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==",
"license": "MIT"
},
"node_modules/@vitejs/plugin-vue": {
"version": "5.2.4",
"resolved": "https://registry.npmjs.org/@vitejs/plugin-vue/-/plugin-vue-5.2.4.tgz",
@@ -1050,6 +1566,12 @@
"dev": true,
"license": "MIT"
},
"node_modules/argparse": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz",
"integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==",
"license": "Python-2.0"
},
"node_modules/balanced-match": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
@@ -1067,6 +1589,12 @@
"balanced-match": "^1.0.0"
}
},
"node_modules/crelt": {
"version": "1.0.6",
"resolved": "https://registry.npmjs.org/crelt/-/crelt-1.0.6.tgz",
"integrity": "sha512-VQ2MBenTq1fWZUH9DJNGti7kKv6EeAuYr3cLwxUWhIu1baTaXh4Ib5W2CqHVqib4/MqbYGJqiL3Zb8GJZr3l4g==",
"license": "MIT"
},
"node_modules/csstype": {
"version": "3.2.3",
"resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz",
@@ -1080,6 +1608,28 @@
"dev": true,
"license": "MIT"
},
"node_modules/dequal": {
"version": "2.0.3",
"resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz",
"integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==",
"license": "MIT",
"engines": {
"node": ">=6"
}
},
"node_modules/devlop": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/devlop/-/devlop-1.1.0.tgz",
"integrity": "sha512-RWmIqhcFf1lRYBvNmr7qTNuyCt/7/ns2jbpp1+PalgE/rDQcBT0fioSMUpJ93irlUhC5hrg4cYqe6U+0ImW0rA==",
"license": "MIT",
"dependencies": {
"dequal": "^2.0.0"
},
"funding": {
"type": "github",
"url": "https://github.com/sponsors/wooorm"
}
},
"node_modules/entities": {
"version": "7.0.1",
"resolved": "https://registry.npmjs.org/entities/-/entities-7.0.1.tgz",
@@ -1131,6 +1681,18 @@
"@esbuild/win32-x64": "0.21.5"
}
},
"node_modules/escape-string-regexp": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz",
"integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==",
"license": "MIT",
"engines": {
"node": ">=10"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/estree-walker": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz",
@@ -1162,6 +1724,45 @@
"he": "bin/he"
}
},
"node_modules/highlight.js": {
"version": "11.11.1",
"resolved": "https://registry.npmjs.org/highlight.js/-/highlight.js-11.11.1.tgz",
"integrity": "sha512-Xwwo44whKBVCYoliBQwaPvtd/2tYFkRQtXDWj1nackaV2JPXx3L0+Jvd8/qCJ2p+ML0/XVkJ2q+Mr+UVdpJK5w==",
"license": "BSD-3-Clause",
"engines": {
"node": ">=12.0.0"
}
},
"node_modules/linkify-it": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/linkify-it/-/linkify-it-5.0.0.tgz",
"integrity": "sha512-5aHCbzQRADcdP+ATqnDuhhJ/MRIqDkZX5pyjFHRRysS8vZ5AbqGEoFIb6pYHPZ+L/OC2Lc+xT8uHVVR5CAK/wQ==",
"license": "MIT",
"dependencies": {
"uc.micro": "^2.0.0"
}
},
"node_modules/linkifyjs": {
"version": "4.3.2",
"resolved": "https://registry.npmjs.org/linkifyjs/-/linkifyjs-4.3.2.tgz",
"integrity": "sha512-NT1CJtq3hHIreOianA8aSXn6Cw0JzYOuDQbOrSPe7gqFnCpKP++MQe3ODgO3oh2GJFORkAAdqredOa60z63GbA==",
"license": "MIT"
},
"node_modules/lowlight": {
"version": "3.3.0",
"resolved": "https://registry.npmjs.org/lowlight/-/lowlight-3.3.0.tgz",
"integrity": "sha512-0JNhgFoPvP6U6lE/UdVsSq99tn6DhjjpAj5MxG49ewd2mOBVtwWYIT8ClyABhq198aXXODMU6Ox8DrGy/CpTZQ==",
"license": "MIT",
"dependencies": {
"@types/hast": "^3.0.0",
"devlop": "^1.0.0",
"highlight.js": "~11.11.0"
},
"funding": {
"type": "github",
"url": "https://github.com/sponsors/wooorm"
}
},
"node_modules/magic-string": {
"version": "0.30.21",
"resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz",
@@ -1171,6 +1772,41 @@
"@jridgewell/sourcemap-codec": "^1.5.5"
}
},
"node_modules/markdown-it": {
"version": "14.1.1",
"resolved": "https://registry.npmjs.org/markdown-it/-/markdown-it-14.1.1.tgz",
"integrity": "sha512-BuU2qnTti9YKgK5N+IeMubp14ZUKUUw7yeJbkjtosvHiP0AZ5c8IAgEMk79D0eC8F23r4Ac/q8cAIFdm2FtyoA==",
"license": "MIT",
"dependencies": {
"argparse": "^2.0.1",
"entities": "^4.4.0",
"linkify-it": "^5.0.0",
"mdurl": "^2.0.0",
"punycode.js": "^2.3.1",
"uc.micro": "^2.1.0"
},
"bin": {
"markdown-it": "bin/markdown-it.mjs"
}
},
"node_modules/markdown-it/node_modules/entities": {
"version": "4.5.0",
"resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz",
"integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==",
"license": "BSD-2-Clause",
"engines": {
"node": ">=0.12"
},
"funding": {
"url": "https://github.com/fb55/entities?sponsor=1"
}
},
"node_modules/mdurl": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/mdurl/-/mdurl-2.0.0.tgz",
"integrity": "sha512-Lf+9+2r+Tdp5wXDXC4PcIBjTDtq4UKjCPMQhKIuzpJNW0b96kVqSwW0bT7FhRSfmAiFYgP+SCRvdrDozfh0U5w==",
"license": "MIT"
},
"node_modules/minimatch": {
"version": "9.0.9",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.9.tgz",
@@ -1212,6 +1848,12 @@
"node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1"
}
},
"node_modules/orderedmap": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/orderedmap/-/orderedmap-2.1.1.tgz",
"integrity": "sha512-TvAWxi0nDe1j/rtMcWcIj94+Ffe6n7zhow33h40SKxmsmozs6dz/e+EajymfoFcHd7sxNn8yHM8839uixMOV6g==",
"license": "MIT"
},
"node_modules/path-browserify": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/path-browserify/-/path-browserify-1.0.1.tgz",
@@ -1275,6 +1917,210 @@
"node": "^10 || ^12 || >=14"
}
},
"node_modules/prosemirror-changeset": {
"version": "2.4.0",
"resolved": "https://registry.npmjs.org/prosemirror-changeset/-/prosemirror-changeset-2.4.0.tgz",
"integrity": "sha512-LvqH2v7Q2SF6yxatuPP2e8vSUKS/L+xAU7dPDC4RMyHMhZoGDfBC74mYuyYF4gLqOEG758wajtyhNnsTkuhvng==",
"license": "MIT",
"dependencies": {
"prosemirror-transform": "^1.0.0"
}
},
"node_modules/prosemirror-collab": {
"version": "1.3.1",
"resolved": "https://registry.npmjs.org/prosemirror-collab/-/prosemirror-collab-1.3.1.tgz",
"integrity": "sha512-4SnynYR9TTYaQVXd/ieUvsVV4PDMBzrq2xPUWutHivDuOshZXqQ5rGbZM84HEaXKbLdItse7weMGOUdDVcLKEQ==",
"license": "MIT",
"dependencies": {
"prosemirror-state": "^1.0.0"
}
},
"node_modules/prosemirror-commands": {
"version": "1.7.1",
"resolved": "https://registry.npmjs.org/prosemirror-commands/-/prosemirror-commands-1.7.1.tgz",
"integrity": "sha512-rT7qZnQtx5c0/y/KlYaGvtG411S97UaL6gdp6RIZ23DLHanMYLyfGBV5DtSnZdthQql7W+lEVbpSfwtO8T+L2w==",
"license": "MIT",
"dependencies": {
"prosemirror-model": "^1.0.0",
"prosemirror-state": "^1.0.0",
"prosemirror-transform": "^1.10.2"
}
},
"node_modules/prosemirror-dropcursor": {
"version": "1.8.2",
"resolved": "https://registry.npmjs.org/prosemirror-dropcursor/-/prosemirror-dropcursor-1.8.2.tgz",
"integrity": "sha512-CCk6Gyx9+Tt2sbYk5NK0nB1ukHi2ryaRgadV/LvyNuO3ena1payM2z6Cg0vO1ebK8cxbzo41ku2DE5Axj1Zuiw==",
"license": "MIT",
"dependencies": {
"prosemirror-state": "^1.0.0",
"prosemirror-transform": "^1.1.0",
"prosemirror-view": "^1.1.0"
}
},
"node_modules/prosemirror-gapcursor": {
"version": "1.4.1",
"resolved": "https://registry.npmjs.org/prosemirror-gapcursor/-/prosemirror-gapcursor-1.4.1.tgz",
"integrity": "sha512-pMdYaEnjNMSwl11yjEGtgTmLkR08m/Vl+Jj443167p9eB3HVQKhYCc4gmHVDsLPODfZfjr/MmirsdyZziXbQKw==",
"license": "MIT",
"dependencies": {
"prosemirror-keymap": "^1.0.0",
"prosemirror-model": "^1.0.0",
"prosemirror-state": "^1.0.0",
"prosemirror-view": "^1.0.0"
}
},
"node_modules/prosemirror-history": {
"version": "1.5.0",
"resolved": "https://registry.npmjs.org/prosemirror-history/-/prosemirror-history-1.5.0.tgz",
"integrity": "sha512-zlzTiH01eKA55UAf1MEjtssJeHnGxO0j4K4Dpx+gnmX9n+SHNlDqI2oO1Kv1iPN5B1dm5fsljCfqKF9nFL6HRg==",
"license": "MIT",
"dependencies": {
"prosemirror-state": "^1.2.2",
"prosemirror-transform": "^1.0.0",
"prosemirror-view": "^1.31.0",
"rope-sequence": "^1.3.0"
}
},
"node_modules/prosemirror-inputrules": {
"version": "1.5.1",
"resolved": "https://registry.npmjs.org/prosemirror-inputrules/-/prosemirror-inputrules-1.5.1.tgz",
"integrity": "sha512-7wj4uMjKaXWAQ1CDgxNzNtR9AlsuwzHfdFH1ygEHA2KHF2DOEaXl1CJfNPAKCg9qNEh4rum975QLaCiQPyY6Fw==",
"license": "MIT",
"dependencies": {
"prosemirror-state": "^1.0.0",
"prosemirror-transform": "^1.0.0"
}
},
"node_modules/prosemirror-keymap": {
"version": "1.2.3",
"resolved": "https://registry.npmjs.org/prosemirror-keymap/-/prosemirror-keymap-1.2.3.tgz",
"integrity": "sha512-4HucRlpiLd1IPQQXNqeo81BGtkY8Ai5smHhKW9jjPKRc2wQIxksg7Hl1tTI2IfT2B/LgX6bfYvXxEpJl7aKYKw==",
"license": "MIT",
"dependencies": {
"prosemirror-state": "^1.0.0",
"w3c-keyname": "^2.2.0"
}
},
"node_modules/prosemirror-markdown": {
"version": "1.13.4",
"resolved": "https://registry.npmjs.org/prosemirror-markdown/-/prosemirror-markdown-1.13.4.tgz",
"integrity": "sha512-D98dm4cQ3Hs6EmjK500TdAOew4Z03EV71ajEFiWra3Upr7diytJsjF4mPV2dW+eK5uNectiRj0xFxYI9NLXDbw==",
"license": "MIT",
"dependencies": {
"@types/markdown-it": "^14.0.0",
"markdown-it": "^14.0.0",
"prosemirror-model": "^1.25.0"
}
},
"node_modules/prosemirror-menu": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/prosemirror-menu/-/prosemirror-menu-1.3.0.tgz",
"integrity": "sha512-TImyPXCHPcDsSka2/lwJ6WjTASr4re/qWq1yoTTuLOqfXucwF6VcRa2LWCkM/EyTD1UO3CUwiH8qURJoWJRxwg==",
"license": "MIT",
"dependencies": {
"crelt": "^1.0.0",
"prosemirror-commands": "^1.0.0",
"prosemirror-history": "^1.0.0",
"prosemirror-state": "^1.0.0"
}
},
"node_modules/prosemirror-model": {
"version": "1.25.4",
"resolved": "https://registry.npmjs.org/prosemirror-model/-/prosemirror-model-1.25.4.tgz",
"integrity": "sha512-PIM7E43PBxKce8OQeezAs9j4TP+5yDpZVbuurd1h5phUxEKIu+G2a+EUZzIC5nS1mJktDJWzbqS23n1tsAf5QA==",
"license": "MIT",
"dependencies": {
"orderedmap": "^2.0.0"
}
},
"node_modules/prosemirror-schema-basic": {
"version": "1.2.4",
"resolved": "https://registry.npmjs.org/prosemirror-schema-basic/-/prosemirror-schema-basic-1.2.4.tgz",
"integrity": "sha512-ELxP4TlX3yr2v5rM7Sb70SqStq5NvI15c0j9j/gjsrO5vaw+fnnpovCLEGIcpeGfifkuqJwl4fon6b+KdrODYQ==",
"license": "MIT",
"dependencies": {
"prosemirror-model": "^1.25.0"
}
},
"node_modules/prosemirror-schema-list": {
"version": "1.5.1",
"resolved": "https://registry.npmjs.org/prosemirror-schema-list/-/prosemirror-schema-list-1.5.1.tgz",
"integrity": "sha512-927lFx/uwyQaGwJxLWCZRkjXG0p48KpMj6ueoYiu4JX05GGuGcgzAy62dfiV8eFZftgyBUvLx76RsMe20fJl+Q==",
"license": "MIT",
"dependencies": {
"prosemirror-model": "^1.0.0",
"prosemirror-state": "^1.0.0",
"prosemirror-transform": "^1.7.3"
}
},
"node_modules/prosemirror-state": {
"version": "1.4.4",
"resolved": "https://registry.npmjs.org/prosemirror-state/-/prosemirror-state-1.4.4.tgz",
"integrity": "sha512-6jiYHH2CIGbCfnxdHbXZ12gySFY/fz/ulZE333G6bPqIZ4F+TXo9ifiR86nAHpWnfoNjOb3o5ESi7J8Uz1jXHw==",
"license": "MIT",
"dependencies": {
"prosemirror-model": "^1.0.0",
"prosemirror-transform": "^1.0.0",
"prosemirror-view": "^1.27.0"
}
},
"node_modules/prosemirror-tables": {
"version": "1.8.5",
"resolved": "https://registry.npmjs.org/prosemirror-tables/-/prosemirror-tables-1.8.5.tgz",
"integrity": "sha512-V/0cDCsHKHe/tfWkeCmthNUcEp1IVO3p6vwN8XtwE9PZQLAZJigbw3QoraAdfJPir4NKJtNvOB8oYGKRl+t0Dw==",
"license": "MIT",
"dependencies": {
"prosemirror-keymap": "^1.2.3",
"prosemirror-model": "^1.25.4",
"prosemirror-state": "^1.4.4",
"prosemirror-transform": "^1.10.5",
"prosemirror-view": "^1.41.4"
}
},
"node_modules/prosemirror-trailing-node": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/prosemirror-trailing-node/-/prosemirror-trailing-node-3.0.0.tgz",
"integrity": "sha512-xiun5/3q0w5eRnGYfNlW1uU9W6x5MoFKWwq/0TIRgt09lv7Hcser2QYV8t4muXbEr+Fwo0geYn79Xs4GKywrRQ==",
"license": "MIT",
"dependencies": {
"@remirror/core-constants": "3.0.0",
"escape-string-regexp": "^4.0.0"
},
"peerDependencies": {
"prosemirror-model": "^1.22.1",
"prosemirror-state": "^1.4.2",
"prosemirror-view": "^1.33.8"
}
},
"node_modules/prosemirror-transform": {
"version": "1.12.0",
"resolved": "https://registry.npmjs.org/prosemirror-transform/-/prosemirror-transform-1.12.0.tgz",
"integrity": "sha512-GxboyN4AMIsoHNtz5uf2r2Ru551i5hWeCMD6E2Ib4Eogqoub0NflniaBPVQ4MrGE5yZ8JV9tUHg9qcZTTrcN4w==",
"license": "MIT",
"dependencies": {
"prosemirror-model": "^1.21.0"
}
},
"node_modules/prosemirror-view": {
"version": "1.41.7",
"resolved": "https://registry.npmjs.org/prosemirror-view/-/prosemirror-view-1.41.7.tgz",
"integrity": "sha512-jUwKNCEIGiqdvhlS91/2QAg21e4dfU5bH2iwmSDQeosXJgKF7smG0YSplOWK0cjSNgIqXe7VXqo7EIfUFJdt3w==",
"license": "MIT",
"dependencies": {
"prosemirror-model": "^1.20.0",
"prosemirror-state": "^1.0.0",
"prosemirror-transform": "^1.1.0"
}
},
"node_modules/punycode.js": {
"version": "2.3.1",
"resolved": "https://registry.npmjs.org/punycode.js/-/punycode.js-2.3.1.tgz",
"integrity": "sha512-uxFIHU0YlHYhDQtV4R9J6a52SLx28BCjT+4ieh7IGbgwVJWO+km431c4yRlREUAsAmt/uMjQUyQHNEPf0M39CA==",
"license": "MIT",
"engines": {
"node": ">=6"
}
},
"node_modules/rollup": {
"version": "4.60.1",
"resolved": "https://registry.npmjs.org/rollup/-/rollup-4.60.1.tgz",
@@ -1320,6 +2166,12 @@
"fsevents": "~2.3.2"
}
},
"node_modules/rope-sequence": {
"version": "1.3.4",
"resolved": "https://registry.npmjs.org/rope-sequence/-/rope-sequence-1.3.4.tgz",
"integrity": "sha512-UT5EDe2cu2E/6O4igUr5PSFs23nvvukicWHx6GnOPlHAiiYbzNuCRQCuiUdHJQcqKalLKlrYJnjY0ySGsXNQXQ==",
"license": "MIT"
},
"node_modules/source-map-js": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz",
@@ -1343,6 +2195,12 @@
"node": ">=14.17"
}
},
"node_modules/uc.micro": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/uc.micro/-/uc.micro-2.1.0.tgz",
"integrity": "sha512-ARDJmphmdvUk6Glw7y9DQ2bFkKBHwQHLi2lsaH6PPmz/Ka9sFOBsBluozhDltWmnv9u/cF6Rt87znRTPV+yp/A==",
"license": "MIT"
},
"node_modules/vite": {
"version": "5.4.21",
"resolved": "https://registry.npmjs.org/vite/-/vite-5.4.21.tgz",
@@ -1488,6 +2346,12 @@
"peerDependencies": {
"typescript": ">=5.0.0"
}
},
"node_modules/w3c-keyname": {
"version": "2.2.8",
"resolved": "https://registry.npmjs.org/w3c-keyname/-/w3c-keyname-2.2.8.tgz",
"integrity": "sha512-dpojBhNsCNN7T82Tm7k26A6G9ML3NkhDsnw9n/eoxSRlVBB4CEtIQ/KTCLI2Fwf3ataSXRhYFkQi3SlnFwPvPQ==",
"license": "MIT"
}
}
}

View File

@@ -9,6 +9,10 @@
"preview": "vite preview"
},
"dependencies": {
"@tiptap/extension-code-block-lowlight": "^3.21.0",
"@tiptap/starter-kit": "^3.21.0",
"@tiptap/vue-3": "^3.21.0",
"lowlight": "^3.3.0",
"pinia": "^2.1.7",
"vue": "^3.4.21",
"vue-router": "^4.3.0"

View File

@@ -0,0 +1,514 @@
<script setup lang="ts">
import { ref, computed, watch } from 'vue'
import type { DocumentReasoning, ReasoningStep } from '@/types'
import Button from '@/components/common/Button.vue'
const props = defineProps<{
documentId: string
reasoning: DocumentReasoning | null
editable?: boolean
}>()
const emit = defineEmits<{
'update': [reasoning: DocumentReasoning]
'add-step': [thought: string, conclusion: string | null]
'delete-step': [step: number]
}>()
const reasoningTypes = [
{ value: 'chain', label: 'Chain' },
{ value: 'idea', label: 'Idea' },
{ value: 'context', label: 'Context' },
{ value: 'reflection', label: 'Reflection' },
] as const
const localReasoning = ref<DocumentReasoning | null>(null)
const showAddStep = ref(false)
const newStepThought = ref('')
const newStepConclusion = ref('')
watch(() => props.reasoning, (newVal) => {
localReasoning.value = newVal ? JSON.parse(JSON.stringify(newVal)) : null
}, { immediate: true, deep: true })
const hasReasoning = computed(() => !!localReasoning.value && localReasoning.value.reasoning_type !== null)
function updateReasoningType(type: string) {
if (!localReasoning.value) return
localReasoning.value.reasoning_type = type as DocumentReasoning['reasoning_type']
emitUpdate()
}
function updateConfidence(value: string) {
if (!localReasoning.value) return
const num = parseFloat(value)
if (!isNaN(num) && num >= 0 && num <= 1) {
localReasoning.value.confidence = num
emitUpdate()
}
}
function updateStep(index: number, field: 'thought' | 'conclusion', value: string) {
if (!localReasoning.value) return
localReasoning.value.reasoning_steps[index][field] = value || null
emitUpdate()
}
function emitUpdate() {
if (!localReasoning.value) return
emit('update', { ...localReasoning.value })
}
function openAddStep() {
newStepThought.value = ''
newStepConclusion.value = ''
showAddStep.value = true
}
function submitAddStep() {
if (!newStepThought.value.trim()) return
emit('add-step', newStepThought.value.trim(), newStepConclusion.value.trim() || null)
showAddStep.value = false
}
function removeStep(step: number) {
emit('delete-step', step)
}
function getConfidenceColor(confidence: number | null): string {
if (confidence === null) return 'var(--text-secondary)'
if (confidence >= 0.8) return '#22c55e'
if (confidence >= 0.5) return '#eab308'
return '#ef4444'
}
</script>
<template>
<aside class="reasoning-panel">
<div class="reasoning-panel__header">
<h3 class="reasoning-panel__title">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<path d="M12 2a8 8 0 0 1 8 8c0 3.5-2 6-4 8l-2 2-2-2c-2-2-4-4.5-4-8a8 8 0 0 1 8-8z"/>
<circle cx="12" cy="10" r="3"/>
</svg>
Reasoning
</h3>
</div>
<div v-if="!hasReasoning" class="reasoning-panel__empty">
<p>No reasoning data for this document.</p>
<Button variant="secondary" size="small" @click="openAddStep">
Add Reasoning
</Button>
</div>
<div v-else class="reasoning-panel__content">
<!-- Reasoning Type -->
<div class="reasoning-panel__field">
<label class="reasoning-panel__label">Type</label>
<select
v-if="editable"
:value="localReasoning?.reasoning_type"
class="reasoning-panel__select"
@change="updateReasoningType(($event.target as HTMLSelectElement).value)"
>
<option v-for="rt in reasoningTypes" :key="rt.value" :value="rt.value">
{{ rt.label }}
</option>
</select>
<span v-else class="reasoning-panel__value">
{{ reasoningTypes.find(r => r.value === localReasoning?.reasoning_type)?.label }}
</span>
</div>
<!-- Confidence -->
<div class="reasoning-panel__field">
<label class="reasoning-panel__label">Confidence</label>
<div class="reasoning-panel__confidence">
<input
v-if="editable"
type="number"
min="0"
max="1"
step="0.01"
:value="localReasoning?.confidence"
class="reasoning-panel__input"
@change="updateConfidence(($event.target as HTMLInputElement).value)"
/>
<span
v-else
class="reasoning-panel__value"
:style="{ color: getConfidenceColor(localReasoning?.confidence ?? null) }"
>
{{ localReasoning?.confidence !== null ? (localReasoning!.confidence * 100).toFixed(0) + '%' : 'N/A' }}
</span>
<div
v-if="localReasoning?.confidence !== null"
class="reasoning-panel__confidence-bar"
>
<div
class="reasoning-panel__confidence-fill"
:style="{
width: ((localReasoning?.confidence ?? 0) * 100) + '%',
backgroundColor: getConfidenceColor(localReasoning?.confidence ?? null)
}"
></div>
</div>
</div>
</div>
<!-- Model Source -->
<div v-if="localReasoning?.model_source" class="reasoning-panel__field">
<label class="reasoning-panel__label">Model</label>
<span class="reasoning-panel__value">{{ localReasoning.model_source }}</span>
</div>
<!-- Reasoning Steps -->
<div class="reasoning-panel__steps">
<div class="reasoning-panel__steps-header">
<label class="reasoning-panel__label">Steps</label>
<Button v-if="editable" variant="secondary" size="small" @click="openAddStep">
+ Add
</Button>
</div>
<div
v-for="(step, index) in localReasoning?.reasoning_steps"
:key="step.step"
class="reasoning-panel__step"
>
<div class="reasoning-panel__step-header">
<span class="reasoning-panel__step-number">Step {{ step.step }}</span>
<button
v-if="editable"
class="reasoning-panel__step-delete"
@click="removeStep(step.step)"
title="Delete step"
>
×
</button>
</div>
<div class="reasoning-panel__step-body">
<textarea
v-if="editable"
:value="step.thought"
class="reasoning-panel__textarea"
placeholder="Thought..."
rows="2"
@input="updateStep(index, 'thought', ($event.target as HTMLTextAreaElement).value)"
></textarea>
<p v-else class="reasoning-panel__step-text">{{ step.thought }}</p>
<div v-if="step.conclusion !== undefined" class="reasoning-panel__step-conclusion">
<label class="reasoning-panel__sublabel">Conclusion</label>
<textarea
v-if="editable"
:value="step.conclusion ?? ''"
class="reasoning-panel__textarea"
placeholder="Conclusion (optional)..."
rows="2"
@input="updateStep(index, 'conclusion', ($event.target as HTMLTextAreaElement).value)"
></textarea>
<p v-else-if="step.conclusion" class="reasoning-panel__step-text reasoning-panel__step-text--conclusion">
{{ step.conclusion }}
</p>
</div>
</div>
</div>
<p v-if="!localReasoning?.reasoning_steps?.length" class="reasoning-panel__no-steps">
No steps yet.
</p>
</div>
</div>
<!-- Add Step Modal -->
<div v-if="showAddStep" class="reasoning-panel__modal-overlay" @click.self="showAddStep = false">
<div class="reasoning-panel__modal">
<h4 class="reasoning-panel__modal-title">Add Reasoning Step</h4>
<div class="reasoning-panel__field">
<label class="reasoning-panel__label">Thought</label>
<textarea
v-model="newStepThought"
class="reasoning-panel__textarea"
placeholder="Describe the reasoning thought..."
rows="3"
autofocus
></textarea>
</div>
<div class="reasoning-panel__field">
<label class="reasoning-panel__label">Conclusion (optional)</label>
<textarea
v-model="newStepConclusion"
class="reasoning-panel__textarea"
placeholder="Optional conclusion reached..."
rows="2"
></textarea>
</div>
<div class="reasoning-panel__modal-actions">
<Button variant="secondary" size="small" @click="showAddStep = false">Cancel</Button>
<Button variant="primary" size="small" @click="submitAddStep">Add Step</Button>
</div>
</div>
</div>
</aside>
</template>
<style scoped>
.reasoning-panel {
width: 320px;
min-width: 320px;
height: 100%;
background: var(--bg-secondary);
border-left: 1px solid var(--border);
display: flex;
flex-direction: column;
overflow: hidden;
}
.reasoning-panel__header {
padding: 1rem;
border-bottom: 1px solid var(--border);
}
.reasoning-panel__title {
display: flex;
align-items: center;
gap: 0.5rem;
font-size: 0.9375rem;
font-weight: 600;
color: var(--text-primary);
margin: 0;
}
.reasoning-panel__empty {
flex: 1;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
gap: 1rem;
padding: 2rem;
color: var(--text-secondary);
text-align: center;
}
.reasoning-panel__content {
flex: 1;
overflow-y: auto;
padding: 1rem;
}
.reasoning-panel__field {
margin-bottom: 1rem;
}
.reasoning-panel__label {
display: block;
font-size: 0.75rem;
font-weight: 600;
text-transform: uppercase;
letter-spacing: 0.05em;
color: var(--text-secondary);
margin-bottom: 0.375rem;
}
.reasoning-panel__sublabel {
display: block;
font-size: 0.6875rem;
font-weight: 500;
color: var(--text-secondary);
margin-bottom: 0.25rem;
}
.reasoning-panel__value {
font-size: 0.875rem;
color: var(--text-primary);
}
.reasoning-panel__select {
width: 100%;
padding: 0.5rem 0.75rem;
background: var(--bg-primary);
border: 1px solid var(--border);
border-radius: 6px;
font-size: 0.875rem;
color: var(--text-primary);
cursor: pointer;
outline: none;
}
.reasoning-panel__select:focus {
border-color: var(--accent);
}
.reasoning-panel__input {
width: 100%;
padding: 0.5rem 0.75rem;
background: var(--bg-primary);
border: 1px solid var(--border);
border-radius: 6px;
font-size: 0.875rem;
color: var(--text-primary);
outline: none;
}
.reasoning-panel__input:focus {
border-color: var(--accent);
}
.reasoning-panel__confidence {
display: flex;
flex-direction: column;
gap: 0.5rem;
}
.reasoning-panel__confidence-bar {
height: 4px;
background: var(--border);
border-radius: 2px;
overflow: hidden;
}
.reasoning-panel__confidence-fill {
height: 100%;
border-radius: 2px;
transition: width 0.3s ease;
}
.reasoning-panel__steps {
margin-top: 1.5rem;
}
.reasoning-panel__steps-header {
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 0.75rem;
}
.reasoning-panel__step {
background: var(--bg-primary);
border: 1px solid var(--border);
border-radius: 8px;
margin-bottom: 0.75rem;
overflow: hidden;
}
.reasoning-panel__step-header {
display: flex;
align-items: center;
justify-content: space-between;
padding: 0.5rem 0.75rem;
background: var(--bg-secondary);
border-bottom: 1px solid var(--border);
}
.reasoning-panel__step-number {
font-size: 0.75rem;
font-weight: 600;
color: var(--text-secondary);
}
.reasoning-panel__step-delete {
display: flex;
align-items: center;
justify-content: center;
width: 20px;
height: 20px;
padding: 0;
background: none;
border: none;
border-radius: 4px;
font-size: 1.25rem;
color: var(--text-secondary);
cursor: pointer;
transition: all 0.15s;
}
.reasoning-panel__step-delete:hover {
background: #ef444420;
color: #ef4444;
}
.reasoning-panel__step-body {
padding: 0.75rem;
}
.reasoning-panel__textarea {
width: 100%;
padding: 0.5rem 0.625rem;
background: var(--bg-primary);
border: 1px solid var(--border);
border-radius: 6px;
font-size: 0.8125rem;
font-family: inherit;
color: var(--text-primary);
resize: vertical;
outline: none;
box-sizing: border-box;
}
.reasoning-panel__textarea:focus {
border-color: var(--accent);
}
.reasoning-panel__step-text {
font-size: 0.8125rem;
line-height: 1.5;
color: var(--text-primary);
margin: 0;
}
.reasoning-panel__step-text--conclusion {
color: var(--text-secondary);
font-style: italic;
}
.reasoning-panel__step-conclusion {
margin-top: 0.5rem;
padding-top: 0.5rem;
border-top: 1px dashed var(--border);
}
.reasoning-panel__no-steps {
font-size: 0.8125rem;
color: var(--text-secondary);
text-align: center;
padding: 1rem;
}
/* Modal */
.reasoning-panel__modal-overlay {
position: fixed;
inset: 0;
background: rgba(0, 0, 0, 0.5);
display: flex;
align-items: center;
justify-content: center;
z-index: 1000;
}
.reasoning-panel__modal {
background: var(--bg-primary);
border: 1px solid var(--border);
border-radius: 12px;
padding: 1.25rem;
width: 90%;
max-width: 400px;
}
.reasoning-panel__modal-title {
font-size: 1rem;
font-weight: 600;
color: var(--text-primary);
margin: 0 0 1rem 0;
}
.reasoning-panel__modal-actions {
display: flex;
justify-content: flex-end;
gap: 0.5rem;
margin-top: 1rem;
}
</style>

View File

@@ -0,0 +1,337 @@
<script setup lang="ts">
import { watch, onBeforeUnmount } from 'vue'
import { useEditor, EditorContent } from '@tiptap/vue-3'
import StarterKit from '@tiptap/starter-kit'
import CodeBlockLowlight from '@tiptap/extension-code-block-lowlight'
import { common, createLowlight } from 'lowlight'
const lowlight = createLowlight(common)
const props = defineProps<{
modelValue: Record<string, unknown> | null
placeholder?: string
}>()
const emit = defineEmits<{
'update:modelValue': [value: Record<string, unknown>]
'ready': []
}>()
const editor = useEditor({
extensions: [
StarterKit.configure({
codeBlock: false,
}),
CodeBlockLowlight.configure({
lowlight,
}),
],
content: props.modelValue || '',
editorProps: {
attributes: {
class: 'tiptap-editor',
},
},
onUpdate: ({ editor }) => {
emit('update:modelValue', editor.getJSON())
},
onCreate: () => {
emit('ready')
},
})
watch(() => props.modelValue, (newValue) => {
if (!editor.value) return
if (JSON.stringify(editor.value.getJSON()) !== JSON.stringify(newValue)) {
editor.value.commands.setContent(newValue || '', false)
}
})
onBeforeUnmount(() => {
editor.value?.destroy()
})
</script>
<template>
<div class="tiptap-wrapper">
<div v-if="editor" class="tiptap-toolbar">
<button
type="button"
class="tiptap-btn"
:class="{ active: editor.isActive('bold') }"
@click="editor.chain().focus().toggleBold().run()"
title="Bold"
>
<strong>B</strong>
</button>
<button
type="button"
class="tiptap-btn"
:class="{ active: editor.isActive('italic') }"
@click="editor.chain().focus().toggleItalic().run()"
title="Italic"
>
<em>I</em>
</button>
<span class="tiptap-separator"></span>
<button
type="button"
class="tiptap-btn"
:class="{ active: editor.isActive('heading', { level: 1 }) }"
@click="editor.chain().focus().toggleHeading({ level: 1 }).run()"
title="Heading 1"
>
H1
</button>
<button
type="button"
class="tiptap-btn"
:class="{ active: editor.isActive('heading', { level: 2 }) }"
@click="editor.chain().focus().toggleHeading({ level: 2 }).run()"
title="Heading 2"
>
H2
</button>
<button
type="button"
class="tiptap-btn"
:class="{ active: editor.isActive('heading', { level: 3 }) }"
@click="editor.chain().focus().toggleHeading({ level: 3 }).run()"
title="Heading 3"
>
H3
</button>
<span class="tiptap-separator"></span>
<button
type="button"
class="tiptap-btn"
:class="{ active: editor.isActive('bulletList') }"
@click="editor.chain().focus().toggleBulletList().run()"
title="Bullet List"
>
</button>
<button
type="button"
class="tiptap-btn"
:class="{ active: editor.isActive('orderedList') }"
@click="editor.chain().focus().toggleOrderedList().run()"
title="Ordered List"
>
1.
</button>
<button
type="button"
class="tiptap-btn"
:class="{ active: editor.isActive('codeBlock') }"
@click="editor.chain().focus().toggleCodeBlock().run()"
title="Code Block"
>
&lt;/&gt;
</button>
<button
type="button"
class="tiptap-btn"
:class="{ active: editor.isActive('blockquote') }"
@click="editor.chain().focus().toggleBlockquote().run()"
title="Blockquote"
>
&ldquo;
</button>
</div>
<EditorContent :editor="editor" />
</div>
</template>
<style>
.tiptap-wrapper {
display: flex;
flex-direction: column;
height: 100%;
}
.tiptap-toolbar {
display: flex;
flex-wrap: wrap;
align-items: center;
gap: 0.25rem;
padding: 0.5rem;
background: var(--bg-secondary);
border: 1px solid var(--border);
border-bottom: none;
border-radius: 8px 8px 0 0;
}
.tiptap-btn {
display: flex;
align-items: center;
justify-content: center;
min-width: 32px;
height: 32px;
padding: 0 0.5rem;
background: none;
border: 1px solid transparent;
border-radius: 4px;
font-size: 0.875rem;
color: var(--text-secondary);
cursor: pointer;
transition: all 0.15s;
}
.tiptap-btn:hover {
background: var(--bg-primary);
color: var(--text-primary);
}
.tiptap-btn.active {
background: var(--accent);
color: white;
border-color: var(--accent);
}
.tiptap-separator {
width: 1px;
height: 20px;
background: var(--border);
margin: 0 0.25rem;
}
.tiptap-editor {
flex: 1;
padding: 1rem;
background: var(--bg-secondary);
border: 1px solid var(--border);
border-radius: 0 0 8px 8px;
font-family: 'JetBrains Mono', 'Fira Code', monospace;
font-size: 0.9375rem;
line-height: 1.7;
color: var(--text-primary);
outline: none;
overflow-y: auto;
min-height: 400px;
}
.tiptap-editor:focus {
border-color: var(--accent);
}
.tiptap-editor p {
margin: 0 0 1rem 0;
}
.tiptap-editor h1,
.tiptap-editor h2,
.tiptap-editor h3 {
font-weight: 600;
margin: 1.5rem 0 0.75rem 0;
}
.tiptap-editor h1 {
font-size: 1.75rem;
}
.tiptap-editor h2 {
font-size: 1.5rem;
}
.tiptap-editor h3 {
font-size: 1.25rem;
}
.tiptap-editor ul,
.tiptap-editor ol {
padding-left: 1.5rem;
margin: 0 0 1rem 0;
}
.tiptap-editor li {
margin: 0.25rem 0;
}
.tiptap-editor code {
background: var(--bg-primary);
border: 1px solid var(--border);
border-radius: 4px;
padding: 0.125rem 0.375rem;
font-family: inherit;
font-size: 0.875em;
}
.tiptap-editor pre {
background: var(--bg-primary);
border: 1px solid var(--border);
border-radius: 6px;
padding: 1rem;
margin: 0 0 1rem 0;
overflow-x: auto;
}
.tiptap-editor pre code {
background: none;
border: none;
padding: 0;
}
.tiptap-editor blockquote {
border-left: 3px solid var(--accent);
padding-left: 1rem;
margin: 0 0 1rem 0;
color: var(--text-secondary);
}
.tiptap-editor p.is-editor-empty:first-child::before {
content: attr(data-placeholder);
float: left;
color: var(--text-secondary);
pointer-events: none;
height: 0;
}
/* Syntax highlighting */
.tiptap-editor .hljs-comment,
.tiptap-editor .hljs-quote {
color: #6a737d;
}
.tiptap-editor .hljs-variable,
.tiptap-editor .hljs-template-variable,
.tiptap-editor .hljs-tag,
.tiptap-editor .hljs-name,
.tiptap-editor .hljs-selector-id,
.tiptap-editor .hljs-selector-class,
.tiptap-editor .hljs-regexp,
.tiptap-editor .hljs-deletion {
color: #d73a49;
}
.tiptap-editor .hljs-number,
.tiptap-editor .hljs-built_in,
.tiptap-editor .hljs-literal,
.tiptap-editor .hljs-type,
.tiptap-editor .hljs-params,
.tiptap-editor .hljs-meta,
.tiptap-editor .hljs-link {
color: #005cc5;
}
.tiptap-editor .hljs-attribute {
color: #e36209;
}
.tiptap-editor .hljs-string,
.tiptap-editor .hljs-symbol,
.tiptap-editor .hljs-bullet,
.tiptap-editor .hljs-addition {
color: #22863a;
}
.tiptap-editor .hljs-title,
.tiptap-editor .hljs-section {
color: #6f42c1;
}
.tiptap-editor .hljs-keyword,
.tiptap-editor .hljs-selector-tag {
color: #d73a49;
}
</style>

View File

@@ -1,6 +1,6 @@
import { defineStore } from 'pinia'
import { ref } from 'vue'
import type { Document, Tag } from '@/types'
import type { Document, Tag, DocumentReasoning, ReasoningStep, ReasoningPanelData, TipTapContentResponse } from '@/types'
import { useApi } from '@/composables/useApi'
export const useDocumentsStore = defineStore('documents', () => {
@@ -95,6 +95,58 @@ export const useDocumentsStore = defineStore('documents', () => {
}
}
async function updateDocumentTipTapContent(id: string, tiptapContent: Record<string, unknown>) {
saving.value = true
try {
await api.put(`/documents/${id}/content`, {
content: tiptapContent,
format: 'tiptap'
})
if (currentDocument.value?.id === id) {
currentDocument.value.tiptap_content = tiptapContent
}
} finally {
saving.value = false
}
}
async function fetchTipTapContent(id: string): Promise<Record<string, unknown> | null> {
try {
const response = await api.get<TipTapContentResponse>(`/documents/${id}/content?format=tiptap`)
return response.content
} catch {
return null
}
}
async function fetchReasoningPanel(id: string): Promise<ReasoningPanelData | null> {
try {
return await api.get<ReasoningPanelData>(`/documents/${id}/reasoning-panel`)
} catch {
return null
}
}
async function updateReasoning(id: string, reasoning: Partial<DocumentReasoning>) {
const updated = await api.patch<DocumentReasoning>(`/documents/${id}/reasoning`, reasoning)
if (currentDocument.value?.id === id) {
currentDocument.value.reasoning = updated
}
return updated
}
async function addReasoningStep(documentId: string, thought: string, conclusion: string | null): Promise<ReasoningStep | null> {
try {
return await api.post<ReasoningStep>(`/documents/${documentId}/reasoning-steps`, { thought, conclusion })
} catch {
return null
}
}
async function deleteReasoningStep(documentId: string, step: number) {
await api.delete(`/documents/${documentId}/reasoning-steps/${step}`)
}
return {
currentDocument,
tags,
@@ -105,6 +157,12 @@ export const useDocumentsStore = defineStore('documents', () => {
createDocument,
updateDocument,
updateDocumentContent,
updateDocumentTipTapContent,
fetchTipTapContent,
fetchReasoningPanel,
updateReasoning,
addReasoningStep,
deleteReasoningStep,
deleteDocument,
fetchTags,
createTag,

View File

@@ -40,17 +40,36 @@ export interface Tag {
color: string
}
export interface ReasoningStep {
step: number
thought: string
conclusion: string | null
}
export interface DocumentReasoning {
reasoning_type: 'chain' | 'idea' | 'context' | 'reflection'
reasoning_type: 'chain' | 'idea' | 'context' | 'reflection' | null
confidence: number | null
reasoning_steps: Array<{
step: number
thought: string
conclusion: string | null
}>
reasoning_steps: ReasoningStep[]
model_source: string | null
}
export interface ReasoningPanelData {
document_id: string
has_reasoning: boolean
reasoning: DocumentReasoning | null
editable: boolean
}
export interface TipTapContentResponse {
content: Record<string, unknown>
format: 'tiptap'
}
export interface ContentUpdateRequest {
content: Record<string, unknown>
format: 'tiptap' | 'markdown'
}
export interface Document {
id: string
title: string
@@ -60,6 +79,7 @@ export interface Document {
path: string
tags: Tag[]
reasoning: DocumentReasoning | null
tiptap_content: Record<string, unknown> | null
created_at: string
updated_at: string
}

View File

@@ -1,39 +1,60 @@
<script setup lang="ts">
import { ref, onMounted, watch } from 'vue'
import { ref, onMounted, watch, computed } from 'vue'
import { useRoute, useRouter } from 'vue-router'
import { useDocumentsStore } from '@/stores/documents'
import type { DocumentReasoning } from '@/types'
import Header from '@/components/layout/Header.vue'
import Button from '@/components/common/Button.vue'
import Modal from '@/components/common/Modal.vue'
import TipTapEditor from '@/components/editor/TipTapEditor.vue'
import ReasoningPanel from '@/components/editor/ReasoningPanel.vue'
const route = useRoute()
const router = useRouter()
const documentsStore = useDocumentsStore()
const editTitle = ref('')
const editContent = ref('')
const tiptapContent = ref<Record<string, unknown> | null>(null)
const showTagModal = ref(false)
const newTagName = ref('')
const newTagColor = ref('#6366f1')
const showReasoningPanel = ref(true)
const saveTimeout = ref<ReturnType<typeof setTimeout> | null>(null)
const isReady = ref(false)
onMounted(async () => {
const docId = route.params.id as string
await documentsStore.fetchDocument(docId)
await documentsStore.fetchTags()
if (documentsStore.currentDocument) {
editTitle.value = documentsStore.currentDocument.title
editContent.value = documentsStore.currentDocument.content
// Try to load TipTap content first, fall back to markdown conversion
const savedTipTap = await documentsStore.fetchTipTapContent(docId)
if (savedTipTap) {
tiptapContent.value = savedTipTap
} else if (documentsStore.currentDocument.content) {
// Convert markdown to TipTap JSON (basic conversion)
tiptapContent.value = markdownToTiptap(documentsStore.currentDocument.content)
} else {
tiptapContent.value = { type: 'doc', content: [{ type: 'paragraph' }] }
}
}
})
watch(() => route.params.id, async (newId) => {
if (newId) {
isReady.value = false
await documentsStore.fetchDocument(newId as string)
if (documentsStore.currentDocument) {
editTitle.value = documentsStore.currentDocument.title
editContent.value = documentsStore.currentDocument.content
const savedTipTap = await documentsStore.fetchTipTapContent(newId as string)
if (savedTipTap) {
tiptapContent.value = savedTipTap
} else {
tiptapContent.value = markdownToTiptap(documentsStore.currentDocument.content)
}
}
}
})
@@ -41,12 +62,80 @@ watch(() => route.params.id, async (newId) => {
watch(() => documentsStore.currentDocument, (doc) => {
if (doc) {
editTitle.value = doc.title
editContent.value = doc.content
}
})
function handleContentChange() {
// Auto-save with debounce
// Convert markdown string to basic TipTap JSON
function markdownToTiptap(markdown: string): Record<string, unknown> {
if (!markdown) {
return { type: 'doc', content: [{ type: 'paragraph' }] }
}
const lines = markdown.split('\n')
const content: unknown[] = []
for (const line of lines) {
if (line.startsWith('### ')) {
content.push({
type: 'heading',
attrs: { level: 3 },
content: [{ type: 'text', text: line.substring(4) }]
})
} else if (line.startsWith('## ')) {
content.push({
type: 'heading',
attrs: { level: 2 },
content: [{ type: 'text', text: line.substring(3) }]
})
} else if (line.startsWith('# ')) {
content.push({
type: 'heading',
attrs: { level: 1 },
content: [{ type: 'text', text: line.substring(2) }]
})
} else if (line.startsWith('- ') || line.startsWith('* ')) {
content.push({
type: 'bulletList',
content: [{
type: 'listItem',
content: [{
type: 'paragraph',
content: [{ type: 'text', text: line.substring(2) }]
}]
}]
})
} else if (/^\d+\.\s/.test(line)) {
content.push({
type: 'orderedList',
content: [{
type: 'listItem',
content: [{
type: 'paragraph',
content: [{ type: 'text', text: line.replace(/^\d+\.\s/, '') }]
}]
}]
})
} else if (line.startsWith('```')) {
// Skip code block markers for now (would need more complex handling)
content.push({
type: 'paragraph',
content: [{ type: 'text', text: line }]
})
} else if (line.trim()) {
content.push({
type: 'paragraph',
content: [{ type: 'text', text: line }]
})
} else {
content.push({ type: 'paragraph' })
}
}
return { type: 'doc', content }
}
function handleContentChange(content: Record<string, unknown>) {
tiptapContent.value = content
if (saveTimeout.value) {
clearTimeout(saveTimeout.value)
}
@@ -55,11 +144,15 @@ function handleContentChange() {
}, 1500)
}
function handleEditorReady() {
isReady.value = true
}
async function saveContent() {
if (!documentsStore.currentDocument) return
await documentsStore.updateDocumentContent(
if (!documentsStore.currentDocument || !tiptapContent.value) return
await documentsStore.updateDocumentTipTapContent(
documentsStore.currentDocument.id,
editContent.value
tiptapContent.value
)
}
@@ -78,11 +171,11 @@ async function removeTag(tagId: string) {
async function createAndAssignTag() {
if (!newTagName.value.trim() || !documentsStore.currentDocument) return
const tag = await documentsStore.createTag(newTagName.value.trim(), newTagColor.value)
const currentTagIds = documentsStore.currentDocument.tags.map(t => t.id)
await documentsStore.assignTags(documentsStore.currentDocument.id, [...currentTagIds, tag.id])
showTagModal.value = false
newTagName.value = ''
newTagColor.value = '#6366f1'
@@ -96,6 +189,27 @@ async function goBack() {
}
}
async function handleReasoningUpdate(reasoning: DocumentReasoning) {
if (!documentsStore.currentDocument) return
await documentsStore.updateReasoning(documentsStore.currentDocument.id, reasoning)
}
async function handleAddStep(thought: string, conclusion: string | null) {
if (!documentsStore.currentDocument) return
const step = await documentsStore.addReasoningStep(documentsStore.currentDocument.id, thought, conclusion)
if (step && documentsStore.currentDocument.reasoning) {
documentsStore.currentDocument.reasoning.reasoning_steps.push(step)
}
}
async function handleDeleteStep(step: number) {
if (!documentsStore.currentDocument) return
await documentsStore.deleteReasoningStep(documentsStore.currentDocument.id, step)
if (documentsStore.currentDocument.reasoning) {
documentsStore.currentDocument.reasoning.reasoning_steps = documentsStore.currentDocument.reasoning.reasoning_steps.filter(s => s.step !== step)
}
}
const tagColors = [
'#6366f1', '#8b5cf6', '#ec4899', '#ef4444', '#f97316',
'#eab308', '#22c55e', '#10b981', '#14b8a6', '#06b6d4'
@@ -106,7 +220,7 @@ const tagColors = [
<div class="layout">
<Header />
<div class="layout__body">
<main class="layout__content">
<main class="layout__content" :class="{ 'layout__content--with-panel': showReasoningPanel }">
<div v-if="documentsStore.loading" class="loading">
<div class="loading__spinner"></div>
<p>Loading document...</p>
@@ -119,22 +233,38 @@ const tagColors = [
<div v-else-if="documentsStore.currentDocument" class="doc-view">
<div class="doc-view__toolbar">
<button class="doc-view__back" @click="goBack">
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<path d="m15 18-6-6 6-6"/>
</svg>
Back
</button>
<div class="doc-view__toolbar-left">
<button class="doc-view__back" @click="goBack">
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<path d="m15 18-6-6 6-6"/>
</svg>
Back
</button>
<div class="doc-view__saving" v-if="documentsStore.saving">
<div class="doc-view__saving-dot"></div>
Saving...
<button
class="doc-view__panel-toggle"
:class="{ active: showReasoningPanel }"
@click="showReasoningPanel = !showReasoningPanel"
title="Toggle Reasoning Panel"
>
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<path d="M12 2a8 8 0 0 1 8 8c0 3.5-2 6-4 8l-2 2-2-2c-2-2-4-4.5-4-8a8 8 0 0 1 8-8z"/>
<circle cx="12" cy="10" r="3"/>
</svg>
</button>
</div>
<div class="doc-view__saved" v-else>
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<polyline points="20 6 9 17 4 12"/>
</svg>
Saved
<div class="doc-view__toolbar-right">
<div class="doc-view__saving" v-if="documentsStore.saving">
<div class="doc-view__saving-dot"></div>
Saving...
</div>
<div class="doc-view__saved" v-else-if="isReady">
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<polyline points="20 6 9 17 4 12"/>
</svg>
Saved
</div>
</div>
</div>
@@ -169,15 +299,27 @@ const tagColors = [
</div>
<div class="doc-view__editor">
<textarea
v-model="editContent"
class="doc-view__textarea"
placeholder="Start writing in Markdown..."
@input="handleContentChange"
></textarea>
<TipTapEditor
v-if="tiptapContent"
:model-value="tiptapContent"
placeholder="Start writing..."
@update:model-value="handleContentChange"
@ready="handleEditorReady"
/>
</div>
</div>
</main>
<!-- Reasoning Panel Sidebar -->
<ReasoningPanel
v-if="showReasoningPanel && documentsStore.currentDocument"
:document-id="documentsStore.currentDocument.id"
:reasoning="documentsStore.currentDocument.reasoning"
:editable="true"
@update="handleReasoningUpdate"
@add-step="handleAddStep"
@delete-step="handleDeleteStep"
/>
</div>
<!-- Tag Modal -->
@@ -232,6 +374,10 @@ const tagColors = [
background: var(--bg-primary);
}
.layout__content--with-panel {
/* Content takes remaining space when panel is visible */
}
.loading,
.error {
display: flex;
@@ -259,7 +405,7 @@ const tagColors = [
}
.doc-view {
max-width: 800px;
max-width: 900px;
margin: 0 auto;
padding: 2rem;
}
@@ -271,6 +417,17 @@ const tagColors = [
margin-bottom: 1.5rem;
}
.doc-view__toolbar-left {
display: flex;
align-items: center;
gap: 0.5rem;
}
.doc-view__toolbar-right {
display: flex;
align-items: center;
}
.doc-view__back {
display: flex;
align-items: center;
@@ -290,6 +447,31 @@ const tagColors = [
color: var(--text-primary);
}
.doc-view__panel-toggle {
display: flex;
align-items: center;
justify-content: center;
width: 36px;
height: 36px;
background: none;
border: 1px solid var(--border);
border-radius: 6px;
color: var(--text-secondary);
cursor: pointer;
transition: all 0.15s;
}
.doc-view__panel-toggle:hover {
background: var(--bg-secondary);
color: var(--text-primary);
}
.doc-view__panel-toggle.active {
background: var(--accent);
border-color: var(--accent);
color: white;
}
.doc-view__saving {
display: flex;
align-items: center;
@@ -403,31 +585,8 @@ const tagColors = [
.doc-view__editor {
flex: 1;
}
.doc-view__textarea {
width: 100%;
min-height: 400px;
padding: 1rem;
background: var(--bg-secondary);
border: 1px solid var(--border);
border-radius: 8px;
font-family: 'JetBrains Mono', 'Fira Code', monospace;
font-size: 0.9375rem;
line-height: 1.7;
color: var(--text-primary);
resize: vertical;
outline: none;
transition: border-color 0.15s;
box-sizing: border-box;
}
.doc-view__textarea::placeholder {
color: var(--text-secondary);
}
.doc-view__textarea:focus {
border-color: var(--accent);
display: flex;
flex-direction: column;
}
/* Form styles */