@musnows/scriverse 0.3.10 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.en.md +2 -0
- package/README.md +2 -0
- package/dist/ai.js +292 -19
- package/dist/ai.js.map +1 -1
- package/dist/app.js +50 -6
- package/dist/app.js.map +1 -1
- package/dist/database.js +47 -0
- package/dist/database.js.map +1 -1
- package/dist/public/app.js +994 -194
- package/dist/public/character-markdown.js +12 -0
- package/dist/public/index.html +69 -26
- package/dist/public/model-config.d.ts +1 -0
- package/dist/public/model-config.js +12 -3
- package/dist/public/module-layout.d.ts +17 -0
- package/dist/public/module-layout.js +28 -0
- package/dist/public/page-route.js +1 -1
- package/dist/public/styles.css +191 -59
- package/dist/public/vendor/vditor/dist/css/content-theme/ant-design.css +210 -0
- package/dist/public/vendor/vditor/dist/css/content-theme/dark.css +123 -0
- package/dist/public/vendor/vditor/dist/css/content-theme/light.css +101 -0
- package/dist/public/vendor/vditor/dist/css/content-theme/wechat.css +94 -0
- package/dist/public/vendor/vditor/dist/images/emoji/b3log.png +0 -0
- package/dist/public/vendor/vditor/dist/images/emoji/chainbook.png +0 -0
- package/dist/public/vendor/vditor/dist/images/emoji/doge.png +0 -0
- package/dist/public/vendor/vditor/dist/images/emoji/hacpai.png +0 -0
- package/dist/public/vendor/vditor/dist/images/emoji/huaji.gif +0 -0
- package/dist/public/vendor/vditor/dist/images/emoji/latke.png +0 -0
- package/dist/public/vendor/vditor/dist/images/emoji/liandi.png +0 -0
- package/dist/public/vendor/vditor/dist/images/emoji/lute.png +0 -0
- package/dist/public/vendor/vditor/dist/images/emoji/octocat.png +0 -0
- package/dist/public/vendor/vditor/dist/images/emoji/pipe.png +0 -0
- package/dist/public/vendor/vditor/dist/images/emoji/siyuan.png +0 -0
- package/dist/public/vendor/vditor/dist/images/emoji/solo.png +0 -0
- package/dist/public/vendor/vditor/dist/images/emoji/sym.png +0 -0
- package/dist/public/vendor/vditor/dist/images/emoji/trollface.png +0 -0
- package/dist/public/vendor/vditor/dist/images/emoji/vditor.png +0 -0
- package/dist/public/vendor/vditor/dist/images/emoji/wide.png +0 -0
- package/dist/public/vendor/vditor/dist/images/emoji/wulian.png +0 -0
- package/dist/public/vendor/vditor/dist/images/img-loading.svg +1 -0
- package/dist/public/vendor/vditor/dist/images/logo.png +0 -0
- package/dist/public/vendor/vditor/dist/index.css +1823 -0
- package/dist/public/vendor/vditor/dist/index.d.ts +82 -0
- package/dist/public/vendor/vditor/dist/index.js +15995 -0
- package/dist/public/vendor/vditor/dist/index.min.js +1 -0
- package/dist/public/vendor/vditor/dist/js/abcjs/abcjs_basic.min.js +3 -0
- package/dist/public/vendor/vditor/dist/js/echarts/echarts.min.js +45 -0
- package/dist/public/vendor/vditor/dist/js/flowchart.js/flowchart.min.js +8 -0
- package/dist/public/vendor/vditor/dist/js/graphviz/full.render.js +90 -0
- package/dist/public/vendor/vditor/dist/js/graphviz/viz.js +333 -0
- package/dist/public/vendor/vditor/dist/js/highlight.js/LICENSE +29 -0
- package/dist/public/vendor/vditor/dist/js/highlight.js/highlight.min.js +3835 -0
- package/dist/public/vendor/vditor/dist/js/highlight.js/styles/a11y-dark.min.css +7 -0
- package/dist/public/vendor/vditor/dist/js/highlight.js/styles/a11y-light.min.css +7 -0
- package/dist/public/vendor/vditor/dist/js/highlight.js/styles/agate.min.css +20 -0
- package/dist/public/vendor/vditor/dist/js/highlight.js/styles/an-old-hope.min.css +9 -0
- package/dist/public/vendor/vditor/dist/js/highlight.js/styles/androidstudio.min.css +1 -0
- package/dist/public/vendor/vditor/dist/js/highlight.js/styles/ant-design.min.css +84 -0
- package/dist/public/vendor/vditor/dist/js/highlight.js/styles/arduino-light.min.css +1 -0
- package/dist/public/vendor/vditor/dist/js/highlight.js/styles/arta.min.css +1 -0
- package/dist/public/vendor/vditor/dist/js/highlight.js/styles/ascetic.min.css +1 -0
- package/dist/public/vendor/vditor/dist/js/highlight.js/styles/atom-one-dark-reasonable.min.css +1 -0
- package/dist/public/vendor/vditor/dist/js/highlight.js/styles/atom-one-dark.min.css +1 -0
- package/dist/public/vendor/vditor/dist/js/highlight.js/styles/atom-one-light.min.css +1 -0
- package/dist/public/vendor/vditor/dist/js/highlight.js/styles/base16/3024.min.css +7 -0
- package/dist/public/vendor/vditor/dist/js/highlight.js/styles/base16/apathy.min.css +7 -0
- package/dist/public/vendor/vditor/dist/js/highlight.js/styles/base16/apprentice.min.css +7 -0
- package/dist/public/vendor/vditor/dist/js/highlight.js/styles/base16/ashes.min.css +7 -0
- package/dist/public/vendor/vditor/dist/js/highlight.js/styles/base16/atelier-cave-light.min.css +7 -0
- package/dist/public/vendor/vditor/dist/js/highlight.js/styles/base16/atelier-cave.min.css +7 -0
- package/dist/public/vendor/vditor/dist/js/highlight.js/styles/base16/atelier-dune-light.min.css +7 -0
- package/dist/public/vendor/vditor/dist/js/highlight.js/styles/base16/atelier-dune.min.css +7 -0
- package/dist/public/vendor/vditor/dist/js/highlight.js/styles/base16/atelier-estuary-light.min.css +7 -0
- package/dist/public/vendor/vditor/dist/js/highlight.js/styles/base16/atelier-estuary.min.css +7 -0
- package/dist/public/vendor/vditor/dist/js/highlight.js/styles/base16/atelier-forest-light.min.css +7 -0
- package/dist/public/vendor/vditor/dist/js/highlight.js/styles/base16/atelier-forest.min.css +7 -0
- package/dist/public/vendor/vditor/dist/js/highlight.js/styles/base16/atelier-heath-light.min.css +7 -0
- package/dist/public/vendor/vditor/dist/js/highlight.js/styles/base16/atelier-heath.min.css +7 -0
- package/dist/public/vendor/vditor/dist/js/highlight.js/styles/base16/atelier-lakeside-light.min.css +7 -0
- package/dist/public/vendor/vditor/dist/js/highlight.js/styles/base16/atelier-lakeside.min.css +7 -0
- package/dist/public/vendor/vditor/dist/js/highlight.js/styles/base16/atelier-plateau-light.min.css +7 -0
- package/dist/public/vendor/vditor/dist/js/highlight.js/styles/base16/atelier-plateau.min.css +7 -0
- package/dist/public/vendor/vditor/dist/js/highlight.js/styles/base16/atelier-savanna-light.min.css +7 -0
- package/dist/public/vendor/vditor/dist/js/highlight.js/styles/base16/atelier-savanna.min.css +7 -0
- package/dist/public/vendor/vditor/dist/js/highlight.js/styles/base16/atelier-seaside-light.min.css +7 -0
- package/dist/public/vendor/vditor/dist/js/highlight.js/styles/base16/atelier-seaside.min.css +7 -0
- package/dist/public/vendor/vditor/dist/js/highlight.js/styles/base16/atelier-sulphurpool-light.min.css +7 -0
- package/dist/public/vendor/vditor/dist/js/highlight.js/styles/base16/atelier-sulphurpool.min.css +7 -0
- package/dist/public/vendor/vditor/dist/js/highlight.js/styles/base16/atlas.min.css +7 -0
- package/dist/public/vendor/vditor/dist/js/highlight.js/styles/base16/bespin.min.css +7 -0
- package/dist/public/vendor/vditor/dist/js/highlight.js/styles/base16/black-metal-bathory.min.css +7 -0
- package/dist/public/vendor/vditor/dist/js/highlight.js/styles/base16/black-metal-burzum.min.css +7 -0
- package/dist/public/vendor/vditor/dist/js/highlight.js/styles/base16/black-metal-dark-funeral.min.css +7 -0
- package/dist/public/vendor/vditor/dist/js/highlight.js/styles/base16/black-metal-gorgoroth.min.css +7 -0
- package/dist/public/vendor/vditor/dist/js/highlight.js/styles/base16/black-metal-immortal.min.css +7 -0
- package/dist/public/vendor/vditor/dist/js/highlight.js/styles/base16/black-metal-khold.min.css +7 -0
- package/dist/public/vendor/vditor/dist/js/highlight.js/styles/base16/black-metal-marduk.min.css +7 -0
- package/dist/public/vendor/vditor/dist/js/highlight.js/styles/base16/black-metal-mayhem.min.css +7 -0
- package/dist/public/vendor/vditor/dist/js/highlight.js/styles/base16/black-metal-nile.min.css +7 -0
- package/dist/public/vendor/vditor/dist/js/highlight.js/styles/base16/black-metal-venom.min.css +7 -0
- package/dist/public/vendor/vditor/dist/js/highlight.js/styles/base16/black-metal.min.css +7 -0
- package/dist/public/vendor/vditor/dist/js/highlight.js/styles/base16/brewer.min.css +7 -0
- package/dist/public/vendor/vditor/dist/js/highlight.js/styles/base16/bright.min.css +7 -0
- package/dist/public/vendor/vditor/dist/js/highlight.js/styles/base16/brogrammer.min.css +7 -0
- package/dist/public/vendor/vditor/dist/js/highlight.js/styles/base16/brush-trees-dark.min.css +7 -0
- package/dist/public/vendor/vditor/dist/js/highlight.js/styles/base16/brush-trees.min.css +7 -0
- package/dist/public/vendor/vditor/dist/js/highlight.js/styles/base16/chalk.min.css +7 -0
- package/dist/public/vendor/vditor/dist/js/highlight.js/styles/base16/circus.min.css +7 -0
- package/dist/public/vendor/vditor/dist/js/highlight.js/styles/base16/classic-dark.min.css +7 -0
- package/dist/public/vendor/vditor/dist/js/highlight.js/styles/base16/classic-light.min.css +7 -0
- package/dist/public/vendor/vditor/dist/js/highlight.js/styles/base16/codeschool.min.css +7 -0
- package/dist/public/vendor/vditor/dist/js/highlight.js/styles/base16/colors.min.css +7 -0
- package/dist/public/vendor/vditor/dist/js/highlight.js/styles/base16/cupcake.min.css +7 -0
- package/dist/public/vendor/vditor/dist/js/highlight.js/styles/base16/cupertino.min.css +7 -0
- package/dist/public/vendor/vditor/dist/js/highlight.js/styles/base16/danqing.min.css +7 -0
- package/dist/public/vendor/vditor/dist/js/highlight.js/styles/base16/darcula.min.css +7 -0
- package/dist/public/vendor/vditor/dist/js/highlight.js/styles/base16/dark-violet.min.css +7 -0
- package/dist/public/vendor/vditor/dist/js/highlight.js/styles/base16/darkmoss.min.css +7 -0
- package/dist/public/vendor/vditor/dist/js/highlight.js/styles/base16/darktooth.min.css +7 -0
- package/dist/public/vendor/vditor/dist/js/highlight.js/styles/base16/decaf.min.css +7 -0
- package/dist/public/vendor/vditor/dist/js/highlight.js/styles/base16/default-dark.min.css +7 -0
- package/dist/public/vendor/vditor/dist/js/highlight.js/styles/base16/default-light.min.css +7 -0
- package/dist/public/vendor/vditor/dist/js/highlight.js/styles/base16/dirtysea.min.css +7 -0
- package/dist/public/vendor/vditor/dist/js/highlight.js/styles/base16/dracula.min.css +7 -0
- package/dist/public/vendor/vditor/dist/js/highlight.js/styles/base16/edge-dark.min.css +7 -0
- package/dist/public/vendor/vditor/dist/js/highlight.js/styles/base16/edge-light.min.css +7 -0
- package/dist/public/vendor/vditor/dist/js/highlight.js/styles/base16/eighties.min.css +7 -0
- package/dist/public/vendor/vditor/dist/js/highlight.js/styles/base16/embers.min.css +7 -0
- package/dist/public/vendor/vditor/dist/js/highlight.js/styles/base16/equilibrium-dark.min.css +7 -0
- package/dist/public/vendor/vditor/dist/js/highlight.js/styles/base16/equilibrium-gray-dark.min.css +7 -0
- package/dist/public/vendor/vditor/dist/js/highlight.js/styles/base16/equilibrium-gray-light.min.css +7 -0
- package/dist/public/vendor/vditor/dist/js/highlight.js/styles/base16/equilibrium-light.min.css +7 -0
- package/dist/public/vendor/vditor/dist/js/highlight.js/styles/base16/espresso.min.css +7 -0
- package/dist/public/vendor/vditor/dist/js/highlight.js/styles/base16/eva-dim.min.css +7 -0
- package/dist/public/vendor/vditor/dist/js/highlight.js/styles/base16/eva.min.css +7 -0
- package/dist/public/vendor/vditor/dist/js/highlight.js/styles/base16/flat.min.css +7 -0
- package/dist/public/vendor/vditor/dist/js/highlight.js/styles/base16/framer.min.css +7 -0
- package/dist/public/vendor/vditor/dist/js/highlight.js/styles/base16/fruit-soda.min.css +7 -0
- package/dist/public/vendor/vditor/dist/js/highlight.js/styles/base16/gigavolt.min.css +7 -0
- package/dist/public/vendor/vditor/dist/js/highlight.js/styles/base16/github.min.css +7 -0
- package/dist/public/vendor/vditor/dist/js/highlight.js/styles/base16/google-dark.min.css +7 -0
- package/dist/public/vendor/vditor/dist/js/highlight.js/styles/base16/google-light.min.css +7 -0
- package/dist/public/vendor/vditor/dist/js/highlight.js/styles/base16/grayscale-dark.min.css +7 -0
- package/dist/public/vendor/vditor/dist/js/highlight.js/styles/base16/grayscale-light.min.css +7 -0
- package/dist/public/vendor/vditor/dist/js/highlight.js/styles/base16/green-screen.min.css +7 -0
- package/dist/public/vendor/vditor/dist/js/highlight.js/styles/base16/gruvbox-dark-hard.min.css +7 -0
- package/dist/public/vendor/vditor/dist/js/highlight.js/styles/base16/gruvbox-dark-medium.min.css +7 -0
- package/dist/public/vendor/vditor/dist/js/highlight.js/styles/base16/gruvbox-dark-pale.min.css +7 -0
- package/dist/public/vendor/vditor/dist/js/highlight.js/styles/base16/gruvbox-dark-soft.min.css +7 -0
- package/dist/public/vendor/vditor/dist/js/highlight.js/styles/base16/gruvbox-light-hard.min.css +7 -0
- package/dist/public/vendor/vditor/dist/js/highlight.js/styles/base16/gruvbox-light-medium.min.css +7 -0
- package/dist/public/vendor/vditor/dist/js/highlight.js/styles/base16/gruvbox-light-soft.min.css +7 -0
- package/dist/public/vendor/vditor/dist/js/highlight.js/styles/base16/hardcore.min.css +7 -0
- package/dist/public/vendor/vditor/dist/js/highlight.js/styles/base16/harmonic16-dark.min.css +7 -0
- package/dist/public/vendor/vditor/dist/js/highlight.js/styles/base16/harmonic16-light.min.css +7 -0
- package/dist/public/vendor/vditor/dist/js/highlight.js/styles/base16/heetch-dark.min.css +7 -0
- package/dist/public/vendor/vditor/dist/js/highlight.js/styles/base16/heetch-light.min.css +7 -0
- package/dist/public/vendor/vditor/dist/js/highlight.js/styles/base16/helios.min.css +7 -0
- package/dist/public/vendor/vditor/dist/js/highlight.js/styles/base16/hopscotch.min.css +7 -0
- package/dist/public/vendor/vditor/dist/js/highlight.js/styles/base16/horizon-dark.min.css +7 -0
- package/dist/public/vendor/vditor/dist/js/highlight.js/styles/base16/horizon-light.min.css +7 -0
- package/dist/public/vendor/vditor/dist/js/highlight.js/styles/base16/humanoid-dark.min.css +7 -0
- package/dist/public/vendor/vditor/dist/js/highlight.js/styles/base16/humanoid-light.min.css +7 -0
- package/dist/public/vendor/vditor/dist/js/highlight.js/styles/base16/ia-dark.min.css +7 -0
- package/dist/public/vendor/vditor/dist/js/highlight.js/styles/base16/ia-light.min.css +7 -0
- package/dist/public/vendor/vditor/dist/js/highlight.js/styles/base16/icy-dark.min.css +7 -0
- package/dist/public/vendor/vditor/dist/js/highlight.js/styles/base16/ir-black.min.css +7 -0
- package/dist/public/vendor/vditor/dist/js/highlight.js/styles/base16/isotope.min.css +7 -0
- package/dist/public/vendor/vditor/dist/js/highlight.js/styles/base16/kimber.min.css +7 -0
- package/dist/public/vendor/vditor/dist/js/highlight.js/styles/base16/london-tube.min.css +7 -0
- package/dist/public/vendor/vditor/dist/js/highlight.js/styles/base16/macintosh.min.css +7 -0
- package/dist/public/vendor/vditor/dist/js/highlight.js/styles/base16/marrakesh.min.css +7 -0
- package/dist/public/vendor/vditor/dist/js/highlight.js/styles/base16/materia.min.css +7 -0
- package/dist/public/vendor/vditor/dist/js/highlight.js/styles/base16/material-darker.min.css +7 -0
- package/dist/public/vendor/vditor/dist/js/highlight.js/styles/base16/material-lighter.min.css +7 -0
- package/dist/public/vendor/vditor/dist/js/highlight.js/styles/base16/material-palenight.min.css +7 -0
- package/dist/public/vendor/vditor/dist/js/highlight.js/styles/base16/material-vivid.min.css +7 -0
- package/dist/public/vendor/vditor/dist/js/highlight.js/styles/base16/material.min.css +7 -0
- package/dist/public/vendor/vditor/dist/js/highlight.js/styles/base16/mellow-purple.min.css +7 -0
- package/dist/public/vendor/vditor/dist/js/highlight.js/styles/base16/mexico-light.min.css +7 -0
- package/dist/public/vendor/vditor/dist/js/highlight.js/styles/base16/mocha.min.css +7 -0
- package/dist/public/vendor/vditor/dist/js/highlight.js/styles/base16/monokai.min.css +7 -0
- package/dist/public/vendor/vditor/dist/js/highlight.js/styles/base16/nebula.min.css +7 -0
- package/dist/public/vendor/vditor/dist/js/highlight.js/styles/base16/nord.min.css +7 -0
- package/dist/public/vendor/vditor/dist/js/highlight.js/styles/base16/nova.min.css +7 -0
- package/dist/public/vendor/vditor/dist/js/highlight.js/styles/base16/ocean.min.css +7 -0
- package/dist/public/vendor/vditor/dist/js/highlight.js/styles/base16/oceanicnext.min.css +7 -0
- package/dist/public/vendor/vditor/dist/js/highlight.js/styles/base16/one-light.min.css +7 -0
- package/dist/public/vendor/vditor/dist/js/highlight.js/styles/base16/onedark.min.css +7 -0
- package/dist/public/vendor/vditor/dist/js/highlight.js/styles/base16/outrun-dark.min.css +7 -0
- package/dist/public/vendor/vditor/dist/js/highlight.js/styles/base16/papercolor-dark.min.css +7 -0
- package/dist/public/vendor/vditor/dist/js/highlight.js/styles/base16/papercolor-light.min.css +7 -0
- package/dist/public/vendor/vditor/dist/js/highlight.js/styles/base16/paraiso.min.css +7 -0
- package/dist/public/vendor/vditor/dist/js/highlight.js/styles/base16/pasque.min.css +7 -0
- package/dist/public/vendor/vditor/dist/js/highlight.js/styles/base16/phd.min.css +7 -0
- package/dist/public/vendor/vditor/dist/js/highlight.js/styles/base16/pico.min.css +7 -0
- package/dist/public/vendor/vditor/dist/js/highlight.js/styles/base16/pop.min.css +7 -0
- package/dist/public/vendor/vditor/dist/js/highlight.js/styles/base16/porple.min.css +7 -0
- package/dist/public/vendor/vditor/dist/js/highlight.js/styles/base16/qualia.min.css +7 -0
- package/dist/public/vendor/vditor/dist/js/highlight.js/styles/base16/railscasts.min.css +7 -0
- package/dist/public/vendor/vditor/dist/js/highlight.js/styles/base16/rebecca.min.css +7 -0
- package/dist/public/vendor/vditor/dist/js/highlight.js/styles/base16/ros-pine-dawn.min.css +7 -0
- package/dist/public/vendor/vditor/dist/js/highlight.js/styles/base16/ros-pine-moon.min.css +7 -0
- package/dist/public/vendor/vditor/dist/js/highlight.js/styles/base16/ros-pine.min.css +7 -0
- package/dist/public/vendor/vditor/dist/js/highlight.js/styles/base16/sagelight.min.css +7 -0
- package/dist/public/vendor/vditor/dist/js/highlight.js/styles/base16/sandcastle.min.css +7 -0
- package/dist/public/vendor/vditor/dist/js/highlight.js/styles/base16/seti-ui.min.css +7 -0
- package/dist/public/vendor/vditor/dist/js/highlight.js/styles/base16/shapeshifter.min.css +7 -0
- package/dist/public/vendor/vditor/dist/js/highlight.js/styles/base16/silk-dark.min.css +7 -0
- package/dist/public/vendor/vditor/dist/js/highlight.js/styles/base16/silk-light.min.css +7 -0
- package/dist/public/vendor/vditor/dist/js/highlight.js/styles/base16/snazzy.min.css +7 -0
- package/dist/public/vendor/vditor/dist/js/highlight.js/styles/base16/solar-flare-light.min.css +7 -0
- package/dist/public/vendor/vditor/dist/js/highlight.js/styles/base16/solar-flare.min.css +7 -0
- package/dist/public/vendor/vditor/dist/js/highlight.js/styles/base16/solarized-dark.min.css +7 -0
- package/dist/public/vendor/vditor/dist/js/highlight.js/styles/base16/solarized-light.min.css +7 -0
- package/dist/public/vendor/vditor/dist/js/highlight.js/styles/base16/spacemacs.min.css +7 -0
- package/dist/public/vendor/vditor/dist/js/highlight.js/styles/base16/summercamp.min.css +7 -0
- package/dist/public/vendor/vditor/dist/js/highlight.js/styles/base16/summerfruit-dark.min.css +7 -0
- package/dist/public/vendor/vditor/dist/js/highlight.js/styles/base16/summerfruit-light.min.css +7 -0
- package/dist/public/vendor/vditor/dist/js/highlight.js/styles/base16/synth-midnight-terminal-dark.min.css +7 -0
- package/dist/public/vendor/vditor/dist/js/highlight.js/styles/base16/synth-midnight-terminal-light.min.css +7 -0
- package/dist/public/vendor/vditor/dist/js/highlight.js/styles/base16/tango.min.css +7 -0
- package/dist/public/vendor/vditor/dist/js/highlight.js/styles/base16/tender.min.css +7 -0
- package/dist/public/vendor/vditor/dist/js/highlight.js/styles/base16/tomorrow-night.min.css +7 -0
- package/dist/public/vendor/vditor/dist/js/highlight.js/styles/base16/tomorrow.min.css +7 -0
- package/dist/public/vendor/vditor/dist/js/highlight.js/styles/base16/twilight.min.css +7 -0
- package/dist/public/vendor/vditor/dist/js/highlight.js/styles/base16/unikitty-dark.min.css +7 -0
- package/dist/public/vendor/vditor/dist/js/highlight.js/styles/base16/unikitty-light.min.css +7 -0
- package/dist/public/vendor/vditor/dist/js/highlight.js/styles/base16/vulcan.min.css +7 -0
- package/dist/public/vendor/vditor/dist/js/highlight.js/styles/base16/windows-10-light.min.css +7 -0
- package/dist/public/vendor/vditor/dist/js/highlight.js/styles/base16/windows-10.min.css +7 -0
- package/dist/public/vendor/vditor/dist/js/highlight.js/styles/base16/windows-95-light.min.css +7 -0
- package/dist/public/vendor/vditor/dist/js/highlight.js/styles/base16/windows-95.min.css +7 -0
- package/dist/public/vendor/vditor/dist/js/highlight.js/styles/base16/windows-high-contrast-light.min.css +7 -0
- package/dist/public/vendor/vditor/dist/js/highlight.js/styles/base16/windows-high-contrast.min.css +7 -0
- package/dist/public/vendor/vditor/dist/js/highlight.js/styles/base16/windows-nt-light.min.css +7 -0
- package/dist/public/vendor/vditor/dist/js/highlight.js/styles/base16/windows-nt.min.css +7 -0
- package/dist/public/vendor/vditor/dist/js/highlight.js/styles/base16/woodland.min.css +7 -0
- package/dist/public/vendor/vditor/dist/js/highlight.js/styles/base16/xcode-dusk.min.css +7 -0
- package/dist/public/vendor/vditor/dist/js/highlight.js/styles/base16/zenburn.min.css +7 -0
- package/dist/public/vendor/vditor/dist/js/highlight.js/styles/brown-paper.min.css +1 -0
- package/dist/public/vendor/vditor/dist/js/highlight.js/styles/brown-papersq.png +0 -0
- package/dist/public/vendor/vditor/dist/js/highlight.js/styles/codepen-embed.min.css +1 -0
- package/dist/public/vendor/vditor/dist/js/highlight.js/styles/color-brewer.min.css +1 -0
- package/dist/public/vendor/vditor/dist/js/highlight.js/styles/dark.min.css +1 -0
- package/dist/public/vendor/vditor/dist/js/highlight.js/styles/default.min.css +9 -0
- package/dist/public/vendor/vditor/dist/js/highlight.js/styles/devibeans.min.css +7 -0
- package/dist/public/vendor/vditor/dist/js/highlight.js/styles/docco.min.css +1 -0
- package/dist/public/vendor/vditor/dist/js/highlight.js/styles/far.min.css +1 -0
- package/dist/public/vendor/vditor/dist/js/highlight.js/styles/felipec.min.css +7 -0
- package/dist/public/vendor/vditor/dist/js/highlight.js/styles/foundation.min.css +1 -0
- package/dist/public/vendor/vditor/dist/js/highlight.js/styles/github-dark-dimmed.min.css +9 -0
- package/dist/public/vendor/vditor/dist/js/highlight.js/styles/github-dark.min.css +10 -0
- package/dist/public/vendor/vditor/dist/js/highlight.js/styles/github.min.css +10 -0
- package/dist/public/vendor/vditor/dist/js/highlight.js/styles/gml.min.css +1 -0
- package/dist/public/vendor/vditor/dist/js/highlight.js/styles/googlecode.min.css +1 -0
- package/dist/public/vendor/vditor/dist/js/highlight.js/styles/gradient-dark.min.css +1 -0
- package/dist/public/vendor/vditor/dist/js/highlight.js/styles/gradient-light.min.css +1 -0
- package/dist/public/vendor/vditor/dist/js/highlight.js/styles/grayscale.min.css +1 -0
- package/dist/public/vendor/vditor/dist/js/highlight.js/styles/hybrid.min.css +1 -0
- package/dist/public/vendor/vditor/dist/js/highlight.js/styles/idea.min.css +1 -0
- package/dist/public/vendor/vditor/dist/js/highlight.js/styles/intellij-light.min.css +1 -0
- package/dist/public/vendor/vditor/dist/js/highlight.js/styles/ir-black.min.css +1 -0
- package/dist/public/vendor/vditor/dist/js/highlight.js/styles/isbl-editor-dark.min.css +1 -0
- package/dist/public/vendor/vditor/dist/js/highlight.js/styles/isbl-editor-light.min.css +1 -0
- package/dist/public/vendor/vditor/dist/js/highlight.js/styles/kimbie-dark.min.css +1 -0
- package/dist/public/vendor/vditor/dist/js/highlight.js/styles/kimbie-light.min.css +1 -0
- package/dist/public/vendor/vditor/dist/js/highlight.js/styles/lightfair.min.css +1 -0
- package/dist/public/vendor/vditor/dist/js/highlight.js/styles/lioshi.min.css +1 -0
- package/dist/public/vendor/vditor/dist/js/highlight.js/styles/magula.min.css +1 -0
- package/dist/public/vendor/vditor/dist/js/highlight.js/styles/mono-blue.min.css +1 -0
- package/dist/public/vendor/vditor/dist/js/highlight.js/styles/monokai-sublime.min.css +1 -0
- package/dist/public/vendor/vditor/dist/js/highlight.js/styles/monokai.min.css +1 -0
- package/dist/public/vendor/vditor/dist/js/highlight.js/styles/night-owl.min.css +1 -0
- package/dist/public/vendor/vditor/dist/js/highlight.js/styles/nnfx-dark.min.css +10 -0
- package/dist/public/vendor/vditor/dist/js/highlight.js/styles/nnfx-light.min.css +10 -0
- package/dist/public/vendor/vditor/dist/js/highlight.js/styles/nord.min.css +1 -0
- package/dist/public/vendor/vditor/dist/js/highlight.js/styles/obsidian.min.css +1 -0
- package/dist/public/vendor/vditor/dist/js/highlight.js/styles/panda-syntax-dark.min.css +1 -0
- package/dist/public/vendor/vditor/dist/js/highlight.js/styles/panda-syntax-light.min.css +1 -0
- package/dist/public/vendor/vditor/dist/js/highlight.js/styles/paraiso-dark.min.css +1 -0
- package/dist/public/vendor/vditor/dist/js/highlight.js/styles/paraiso-light.min.css +1 -0
- package/dist/public/vendor/vditor/dist/js/highlight.js/styles/pojoaque.jpg +0 -0
- package/dist/public/vendor/vditor/dist/js/highlight.js/styles/pojoaque.min.css +1 -0
- package/dist/public/vendor/vditor/dist/js/highlight.js/styles/purebasic.min.css +1 -0
- package/dist/public/vendor/vditor/dist/js/highlight.js/styles/qtcreator-dark.min.css +1 -0
- package/dist/public/vendor/vditor/dist/js/highlight.js/styles/qtcreator-light.min.css +1 -0
- package/dist/public/vendor/vditor/dist/js/highlight.js/styles/rainbow.min.css +1 -0
- package/dist/public/vendor/vditor/dist/js/highlight.js/styles/routeros.min.css +1 -0
- package/dist/public/vendor/vditor/dist/js/highlight.js/styles/school-book.min.css +1 -0
- package/dist/public/vendor/vditor/dist/js/highlight.js/styles/shades-of-purple.min.css +1 -0
- package/dist/public/vendor/vditor/dist/js/highlight.js/styles/srcery.min.css +1 -0
- package/dist/public/vendor/vditor/dist/js/highlight.js/styles/stackoverflow-dark.min.css +13 -0
- package/dist/public/vendor/vditor/dist/js/highlight.js/styles/stackoverflow-light.min.css +13 -0
- package/dist/public/vendor/vditor/dist/js/highlight.js/styles/sunburst.min.css +1 -0
- package/dist/public/vendor/vditor/dist/js/highlight.js/styles/tokyo-night-dark.min.css +8 -0
- package/dist/public/vendor/vditor/dist/js/highlight.js/styles/tokyo-night-light.min.css +8 -0
- package/dist/public/vendor/vditor/dist/js/highlight.js/styles/tomorrow-night-blue.min.css +1 -0
- package/dist/public/vendor/vditor/dist/js/highlight.js/styles/tomorrow-night-bright.min.css +1 -0
- package/dist/public/vendor/vditor/dist/js/highlight.js/styles/vs.min.css +1 -0
- package/dist/public/vendor/vditor/dist/js/highlight.js/styles/vs2015.min.css +1 -0
- package/dist/public/vendor/vditor/dist/js/highlight.js/styles/xcode.min.css +1 -0
- package/dist/public/vendor/vditor/dist/js/highlight.js/styles/xt256.min.css +1 -0
- package/dist/public/vendor/vditor/dist/js/highlight.js/third-languages.js +135 -0
- package/dist/public/vendor/vditor/dist/js/i18n/de_DE.js +86 -0
- package/dist/public/vendor/vditor/dist/js/i18n/en_US.js +86 -0
- package/dist/public/vendor/vditor/dist/js/i18n/es_ES.js +86 -0
- package/dist/public/vendor/vditor/dist/js/i18n/fr_FR.js +86 -0
- package/dist/public/vendor/vditor/dist/js/i18n/ja_JP.js +86 -0
- package/dist/public/vendor/vditor/dist/js/i18n/ko_KR.js +86 -0
- package/dist/public/vendor/vditor/dist/js/i18n/pt_BR.js +86 -0
- package/dist/public/vendor/vditor/dist/js/i18n/ru_RU.js +86 -0
- package/dist/public/vendor/vditor/dist/js/i18n/sv_SE.js +86 -0
- package/dist/public/vendor/vditor/dist/js/i18n/vi_VN.js +86 -0
- package/dist/public/vendor/vditor/dist/js/i18n/zh_CN.js +86 -0
- package/dist/public/vendor/vditor/dist/js/i18n/zh_TW.js +86 -0
- package/dist/public/vendor/vditor/dist/js/icons/ant.js +174 -0
- package/dist/public/vendor/vditor/dist/js/icons/material.js +195 -0
- package/dist/public/vendor/vditor/dist/js/katex/fonts/KaTeX_AMS-Regular.ttf +0 -0
- package/dist/public/vendor/vditor/dist/js/katex/fonts/KaTeX_AMS-Regular.woff +0 -0
- package/dist/public/vendor/vditor/dist/js/katex/fonts/KaTeX_AMS-Regular.woff2 +0 -0
- package/dist/public/vendor/vditor/dist/js/katex/fonts/KaTeX_Caligraphic-Bold.ttf +0 -0
- package/dist/public/vendor/vditor/dist/js/katex/fonts/KaTeX_Caligraphic-Bold.woff +0 -0
- package/dist/public/vendor/vditor/dist/js/katex/fonts/KaTeX_Caligraphic-Bold.woff2 +0 -0
- package/dist/public/vendor/vditor/dist/js/katex/fonts/KaTeX_Caligraphic-Regular.ttf +0 -0
- package/dist/public/vendor/vditor/dist/js/katex/fonts/KaTeX_Caligraphic-Regular.woff +0 -0
- package/dist/public/vendor/vditor/dist/js/katex/fonts/KaTeX_Caligraphic-Regular.woff2 +0 -0
- package/dist/public/vendor/vditor/dist/js/katex/fonts/KaTeX_Fraktur-Bold.ttf +0 -0
- package/dist/public/vendor/vditor/dist/js/katex/fonts/KaTeX_Fraktur-Bold.woff +0 -0
- package/dist/public/vendor/vditor/dist/js/katex/fonts/KaTeX_Fraktur-Bold.woff2 +0 -0
- package/dist/public/vendor/vditor/dist/js/katex/fonts/KaTeX_Fraktur-Regular.ttf +0 -0
- package/dist/public/vendor/vditor/dist/js/katex/fonts/KaTeX_Fraktur-Regular.woff +0 -0
- package/dist/public/vendor/vditor/dist/js/katex/fonts/KaTeX_Fraktur-Regular.woff2 +0 -0
- package/dist/public/vendor/vditor/dist/js/katex/fonts/KaTeX_Main-Bold.ttf +0 -0
- package/dist/public/vendor/vditor/dist/js/katex/fonts/KaTeX_Main-Bold.woff +0 -0
- package/dist/public/vendor/vditor/dist/js/katex/fonts/KaTeX_Main-Bold.woff2 +0 -0
- package/dist/public/vendor/vditor/dist/js/katex/fonts/KaTeX_Main-BoldItalic.ttf +0 -0
- package/dist/public/vendor/vditor/dist/js/katex/fonts/KaTeX_Main-BoldItalic.woff +0 -0
- package/dist/public/vendor/vditor/dist/js/katex/fonts/KaTeX_Main-BoldItalic.woff2 +0 -0
- package/dist/public/vendor/vditor/dist/js/katex/fonts/KaTeX_Main-Italic.ttf +0 -0
- package/dist/public/vendor/vditor/dist/js/katex/fonts/KaTeX_Main-Italic.woff +0 -0
- package/dist/public/vendor/vditor/dist/js/katex/fonts/KaTeX_Main-Italic.woff2 +0 -0
- package/dist/public/vendor/vditor/dist/js/katex/fonts/KaTeX_Main-Regular.ttf +0 -0
- package/dist/public/vendor/vditor/dist/js/katex/fonts/KaTeX_Main-Regular.woff +0 -0
- package/dist/public/vendor/vditor/dist/js/katex/fonts/KaTeX_Main-Regular.woff2 +0 -0
- package/dist/public/vendor/vditor/dist/js/katex/fonts/KaTeX_Math-BoldItalic.ttf +0 -0
- package/dist/public/vendor/vditor/dist/js/katex/fonts/KaTeX_Math-BoldItalic.woff +0 -0
- package/dist/public/vendor/vditor/dist/js/katex/fonts/KaTeX_Math-BoldItalic.woff2 +0 -0
- package/dist/public/vendor/vditor/dist/js/katex/fonts/KaTeX_Math-Italic.ttf +0 -0
- package/dist/public/vendor/vditor/dist/js/katex/fonts/KaTeX_Math-Italic.woff +0 -0
- package/dist/public/vendor/vditor/dist/js/katex/fonts/KaTeX_Math-Italic.woff2 +0 -0
- package/dist/public/vendor/vditor/dist/js/katex/fonts/KaTeX_SansSerif-Bold.ttf +0 -0
- package/dist/public/vendor/vditor/dist/js/katex/fonts/KaTeX_SansSerif-Bold.woff +0 -0
- package/dist/public/vendor/vditor/dist/js/katex/fonts/KaTeX_SansSerif-Bold.woff2 +0 -0
- package/dist/public/vendor/vditor/dist/js/katex/fonts/KaTeX_SansSerif-Italic.ttf +0 -0
- package/dist/public/vendor/vditor/dist/js/katex/fonts/KaTeX_SansSerif-Italic.woff +0 -0
- package/dist/public/vendor/vditor/dist/js/katex/fonts/KaTeX_SansSerif-Italic.woff2 +0 -0
- package/dist/public/vendor/vditor/dist/js/katex/fonts/KaTeX_SansSerif-Regular.ttf +0 -0
- package/dist/public/vendor/vditor/dist/js/katex/fonts/KaTeX_SansSerif-Regular.woff +0 -0
- package/dist/public/vendor/vditor/dist/js/katex/fonts/KaTeX_SansSerif-Regular.woff2 +0 -0
- package/dist/public/vendor/vditor/dist/js/katex/fonts/KaTeX_Script-Regular.ttf +0 -0
- package/dist/public/vendor/vditor/dist/js/katex/fonts/KaTeX_Script-Regular.woff +0 -0
- package/dist/public/vendor/vditor/dist/js/katex/fonts/KaTeX_Script-Regular.woff2 +0 -0
- package/dist/public/vendor/vditor/dist/js/katex/fonts/KaTeX_Size1-Regular.ttf +0 -0
- package/dist/public/vendor/vditor/dist/js/katex/fonts/KaTeX_Size1-Regular.woff +0 -0
- package/dist/public/vendor/vditor/dist/js/katex/fonts/KaTeX_Size1-Regular.woff2 +0 -0
- package/dist/public/vendor/vditor/dist/js/katex/fonts/KaTeX_Size2-Regular.ttf +0 -0
- package/dist/public/vendor/vditor/dist/js/katex/fonts/KaTeX_Size2-Regular.woff +0 -0
- package/dist/public/vendor/vditor/dist/js/katex/fonts/KaTeX_Size2-Regular.woff2 +0 -0
- package/dist/public/vendor/vditor/dist/js/katex/fonts/KaTeX_Size3-Regular.ttf +0 -0
- package/dist/public/vendor/vditor/dist/js/katex/fonts/KaTeX_Size3-Regular.woff +0 -0
- package/dist/public/vendor/vditor/dist/js/katex/fonts/KaTeX_Size3-Regular.woff2 +0 -0
- package/dist/public/vendor/vditor/dist/js/katex/fonts/KaTeX_Size4-Regular.ttf +0 -0
- package/dist/public/vendor/vditor/dist/js/katex/fonts/KaTeX_Size4-Regular.woff +0 -0
- package/dist/public/vendor/vditor/dist/js/katex/fonts/KaTeX_Size4-Regular.woff2 +0 -0
- package/dist/public/vendor/vditor/dist/js/katex/fonts/KaTeX_Typewriter-Regular.ttf +0 -0
- package/dist/public/vendor/vditor/dist/js/katex/fonts/KaTeX_Typewriter-Regular.woff +0 -0
- package/dist/public/vendor/vditor/dist/js/katex/fonts/KaTeX_Typewriter-Regular.woff2 +0 -0
- package/dist/public/vendor/vditor/dist/js/katex/katex.min.css +1 -0
- package/dist/public/vendor/vditor/dist/js/katex/katex.min.js +1 -0
- package/dist/public/vendor/vditor/dist/js/katex/mhchem.min.js +1 -0
- package/dist/public/vendor/vditor/dist/js/lute/lute.min.js +72 -0
- package/dist/public/vendor/vditor/dist/js/markmap/katex.min.css +1 -0
- package/dist/public/vendor/vditor/dist/js/markmap/markmap.min.js +8251 -0
- package/dist/public/vendor/vditor/dist/js/markmap/prism.css +140 -0
- package/dist/public/vendor/vditor/dist/js/mathjax/LICENSE +202 -0
- package/dist/public/vendor/vditor/dist/js/mathjax/a11y/assistive-mml.js +1 -0
- package/dist/public/vendor/vditor/dist/js/mathjax/a11y/complexity.js +1 -0
- package/dist/public/vendor/vditor/dist/js/mathjax/a11y/explorer.js +1 -0
- package/dist/public/vendor/vditor/dist/js/mathjax/a11y/semantic-enrich.js +1 -0
- package/dist/public/vendor/vditor/dist/js/mathjax/input/asciimath.js +1 -0
- package/dist/public/vendor/vditor/dist/js/mathjax/input/mml/entities.js +1 -0
- package/dist/public/vendor/vditor/dist/js/mathjax/input/mml.js +1 -0
- package/dist/public/vendor/vditor/dist/js/mathjax/input/tex/extensions/action.js +1 -0
- package/dist/public/vendor/vditor/dist/js/mathjax/input/tex/extensions/all-packages.js +1 -0
- package/dist/public/vendor/vditor/dist/js/mathjax/input/tex/extensions/ams.js +1 -0
- package/dist/public/vendor/vditor/dist/js/mathjax/input/tex/extensions/amscd.js +1 -0
- package/dist/public/vendor/vditor/dist/js/mathjax/input/tex/extensions/autoload.js +1 -0
- package/dist/public/vendor/vditor/dist/js/mathjax/input/tex/extensions/bbox.js +1 -0
- package/dist/public/vendor/vditor/dist/js/mathjax/input/tex/extensions/boldsymbol.js +1 -0
- package/dist/public/vendor/vditor/dist/js/mathjax/input/tex/extensions/braket.js +1 -0
- package/dist/public/vendor/vditor/dist/js/mathjax/input/tex/extensions/bussproofs.js +1 -0
- package/dist/public/vendor/vditor/dist/js/mathjax/input/tex/extensions/cancel.js +1 -0
- package/dist/public/vendor/vditor/dist/js/mathjax/input/tex/extensions/color.js +1 -0
- package/dist/public/vendor/vditor/dist/js/mathjax/input/tex/extensions/colorV2.js +1 -0
- package/dist/public/vendor/vditor/dist/js/mathjax/input/tex/extensions/configMacros.js +1 -0
- package/dist/public/vendor/vditor/dist/js/mathjax/input/tex/extensions/enclose.js +1 -0
- package/dist/public/vendor/vditor/dist/js/mathjax/input/tex/extensions/extpfeil.js +1 -0
- package/dist/public/vendor/vditor/dist/js/mathjax/input/tex/extensions/html.js +1 -0
- package/dist/public/vendor/vditor/dist/js/mathjax/input/tex/extensions/mhchem.js +1 -0
- package/dist/public/vendor/vditor/dist/js/mathjax/input/tex/extensions/newcommand.js +1 -0
- package/dist/public/vendor/vditor/dist/js/mathjax/input/tex/extensions/noerrors.js +1 -0
- package/dist/public/vendor/vditor/dist/js/mathjax/input/tex/extensions/noundefined.js +1 -0
- package/dist/public/vendor/vditor/dist/js/mathjax/input/tex/extensions/physics.js +1 -0
- package/dist/public/vendor/vditor/dist/js/mathjax/input/tex/extensions/require.js +1 -0
- package/dist/public/vendor/vditor/dist/js/mathjax/input/tex/extensions/tagFormat.js +1 -0
- package/dist/public/vendor/vditor/dist/js/mathjax/input/tex/extensions/textmacros.js +1 -0
- package/dist/public/vendor/vditor/dist/js/mathjax/input/tex/extensions/unicode.js +1 -0
- package/dist/public/vendor/vditor/dist/js/mathjax/input/tex/extensions/verb.js +1 -0
- package/dist/public/vendor/vditor/dist/js/mathjax/input/tex-base.js +1 -0
- package/dist/public/vendor/vditor/dist/js/mathjax/input/tex-full.js +1 -0
- package/dist/public/vendor/vditor/dist/js/mathjax/input/tex.js +1 -0
- package/dist/public/vendor/vditor/dist/js/mathjax/sre/mathmaps/de.js +104 -0
- package/dist/public/vendor/vditor/dist/js/mathjax/sre/mathmaps/en.js +110 -0
- package/dist/public/vendor/vditor/dist/js/mathjax/sre/mathmaps/es.js +104 -0
- package/dist/public/vendor/vditor/dist/js/mathjax/sre/mathmaps/fr.js +104 -0
- package/dist/public/vendor/vditor/dist/js/mathjax/sre/mathmaps/mathmaps_ie.js +518 -0
- package/dist/public/vendor/vditor/dist/js/mathjax/sre/mathmaps/nemeth.js +104 -0
- package/dist/public/vendor/vditor/dist/js/mathjax/sre/sre-node.js +11 -0
- package/dist/public/vendor/vditor/dist/js/mathjax/sre/sre_browser.js +1633 -0
- package/dist/public/vendor/vditor/dist/js/mathjax/tex-svg-full.js +1 -0
- package/dist/public/vendor/vditor/dist/js/mermaid/mermaid.min.js +2607 -0
- package/dist/public/vendor/vditor/dist/js/plantuml/plantuml-encoder.min.js +1 -0
- package/dist/public/vendor/vditor/dist/js/smiles-drawer/smiles-drawer.min.js +71 -0
- package/dist/public/vendor/vditor/dist/method.d.ts +51 -0
- package/dist/public/vendor/vditor/dist/method.js +2673 -0
- package/dist/public/vendor/vditor/dist/method.min.js +1 -0
- package/dist/public/vendor/vditor/dist/ts/constants.d.ts +45 -0
- package/dist/public/vendor/vditor/dist/ts/devtools/index.d.ts +7 -0
- package/dist/public/vendor/vditor/dist/ts/export/index.d.ts +4 -0
- package/dist/public/vendor/vditor/dist/ts/hint/index.d.ts +14 -0
- package/dist/public/vendor/vditor/dist/ts/ir/expandMarker.d.ts +1 -0
- package/dist/public/vendor/vditor/dist/ts/ir/highlightToolbarIR.d.ts +1 -0
- package/dist/public/vendor/vditor/dist/ts/ir/index.d.ts +13 -0
- package/dist/public/vendor/vditor/dist/ts/ir/input.d.ts +1 -0
- package/dist/public/vendor/vditor/dist/ts/ir/process.d.ts +8 -0
- package/dist/public/vendor/vditor/dist/ts/ir/processKeydown.d.ts +1 -0
- package/dist/public/vendor/vditor/dist/ts/markdown/SMILESRender.d.ts +1 -0
- package/dist/public/vendor/vditor/dist/ts/markdown/abcRender.d.ts +1 -0
- package/dist/public/vendor/vditor/dist/ts/markdown/adapterRender.d.ts +41 -0
- package/dist/public/vendor/vditor/dist/ts/markdown/anchorRender.d.ts +1 -0
- package/dist/public/vendor/vditor/dist/ts/markdown/chartRender.d.ts +1 -0
- package/dist/public/vendor/vditor/dist/ts/markdown/codeRender.d.ts +1 -0
- package/dist/public/vendor/vditor/dist/ts/markdown/flowchartRender.d.ts +1 -0
- package/dist/public/vendor/vditor/dist/ts/markdown/getHTML.d.ts +1 -0
- package/dist/public/vendor/vditor/dist/ts/markdown/getMarkdown.d.ts +1 -0
- package/dist/public/vendor/vditor/dist/ts/markdown/graphvizRender.d.ts +1 -0
- package/dist/public/vendor/vditor/dist/ts/markdown/highlightRender.d.ts +1 -0
- package/dist/public/vendor/vditor/dist/ts/markdown/lazyLoadImageRender.d.ts +6 -0
- package/dist/public/vendor/vditor/dist/ts/markdown/markmapRender.d.ts +1 -0
- package/dist/public/vendor/vditor/dist/ts/markdown/mathRender.d.ts +9 -0
- package/dist/public/vendor/vditor/dist/ts/markdown/mediaRender.d.ts +1 -0
- package/dist/public/vendor/vditor/dist/ts/markdown/mermaidRender.d.ts +1 -0
- package/dist/public/vendor/vditor/dist/ts/markdown/mindmapRender.d.ts +1 -0
- package/dist/public/vendor/vditor/dist/ts/markdown/outlineRender.d.ts +1 -0
- package/dist/public/vendor/vditor/dist/ts/markdown/plantumlRender.d.ts +1 -0
- package/dist/public/vendor/vditor/dist/ts/markdown/previewRender.d.ts +2 -0
- package/dist/public/vendor/vditor/dist/ts/markdown/setLute.d.ts +1 -0
- package/dist/public/vendor/vditor/dist/ts/markdown/speechRender.d.ts +6 -0
- package/dist/public/vendor/vditor/dist/ts/outline/index.d.ts +7 -0
- package/dist/public/vendor/vditor/dist/ts/preview/image.d.ts +1 -0
- package/dist/public/vendor/vditor/dist/ts/preview/index.d.ts +10 -0
- package/dist/public/vendor/vditor/dist/ts/resize/index.d.ts +6 -0
- package/dist/public/vendor/vditor/dist/ts/sv/combineFootnote.d.ts +6 -0
- package/dist/public/vendor/vditor/dist/ts/sv/index.d.ts +13 -0
- package/dist/public/vendor/vditor/dist/ts/sv/inputEvent.d.ts +1 -0
- package/dist/public/vendor/vditor/dist/ts/sv/process.d.ts +11 -0
- package/dist/public/vendor/vditor/dist/ts/sv/processKeydown.d.ts +1 -0
- package/dist/public/vendor/vditor/dist/ts/tip/index.d.ts +6 -0
- package/dist/public/vendor/vditor/dist/ts/toolbar/Both.d.ts +5 -0
- package/dist/public/vendor/vditor/dist/ts/toolbar/Br.d.ts +4 -0
- package/dist/public/vendor/vditor/dist/ts/toolbar/CodeTheme.d.ts +6 -0
- package/dist/public/vendor/vditor/dist/ts/toolbar/ContentTheme.d.ts +6 -0
- package/dist/public/vendor/vditor/dist/ts/toolbar/Counter.d.ts +6 -0
- package/dist/public/vendor/vditor/dist/ts/toolbar/Custom.d.ts +5 -0
- package/dist/public/vendor/vditor/dist/ts/toolbar/Devtools.d.ts +5 -0
- package/dist/public/vendor/vditor/dist/ts/toolbar/Divider.d.ts +4 -0
- package/dist/public/vendor/vditor/dist/ts/toolbar/EditMode.d.ts +8 -0
- package/dist/public/vendor/vditor/dist/ts/toolbar/Emoji.d.ts +7 -0
- package/dist/public/vendor/vditor/dist/ts/toolbar/Export.d.ts +6 -0
- package/dist/public/vendor/vditor/dist/ts/toolbar/Fullscreen.d.ts +6 -0
- package/dist/public/vendor/vditor/dist/ts/toolbar/Headings.d.ts +7 -0
- package/dist/public/vendor/vditor/dist/ts/toolbar/Help.d.ts +5 -0
- package/dist/public/vendor/vditor/dist/ts/toolbar/Indent.d.ts +5 -0
- package/dist/public/vendor/vditor/dist/ts/toolbar/Info.d.ts +5 -0
- package/dist/public/vendor/vditor/dist/ts/toolbar/InsertAfter.d.ts +5 -0
- package/dist/public/vendor/vditor/dist/ts/toolbar/InsertBefore.d.ts +5 -0
- package/dist/public/vendor/vditor/dist/ts/toolbar/MenuItem.d.ts +5 -0
- package/dist/public/vendor/vditor/dist/ts/toolbar/Outdent.d.ts +5 -0
- package/dist/public/vendor/vditor/dist/ts/toolbar/Outline.d.ts +5 -0
- package/dist/public/vendor/vditor/dist/ts/toolbar/Preview.d.ts +6 -0
- package/dist/public/vendor/vditor/dist/ts/toolbar/Record.d.ts +6 -0
- package/dist/public/vendor/vditor/dist/ts/toolbar/Redo.d.ts +5 -0
- package/dist/public/vendor/vditor/dist/ts/toolbar/Undo.d.ts +5 -0
- package/dist/public/vendor/vditor/dist/ts/toolbar/Upload.d.ts +6 -0
- package/dist/public/vendor/vditor/dist/ts/toolbar/index.d.ts +10 -0
- package/dist/public/vendor/vditor/dist/ts/toolbar/setToolbar.d.ts +20 -0
- package/dist/public/vendor/vditor/dist/ts/ui/initUI.d.ts +4 -0
- package/dist/public/vendor/vditor/dist/ts/ui/setCodeTheme.d.ts +1 -0
- package/dist/public/vendor/vditor/dist/ts/ui/setContentTheme.d.ts +1 -0
- package/dist/public/vendor/vditor/dist/ts/ui/setPreviewMode.d.ts +1 -0
- package/dist/public/vendor/vditor/dist/ts/ui/setTheme.d.ts +1 -0
- package/dist/public/vendor/vditor/dist/ts/undo/index.d.ts +19 -0
- package/dist/public/vendor/vditor/dist/ts/upload/getElement.d.ts +1 -0
- package/dist/public/vendor/vditor/dist/ts/upload/index.d.ts +8 -0
- package/dist/public/vendor/vditor/dist/ts/upload/setHeaders.d.ts +1 -0
- package/dist/public/vendor/vditor/dist/ts/util/Options.d.ts +8 -0
- package/dist/public/vendor/vditor/dist/ts/util/RecordMedia.d.ts +18 -0
- package/dist/public/vendor/vditor/dist/ts/util/addScript.d.ts +2 -0
- package/dist/public/vendor/vditor/dist/ts/util/addStyle.d.ts +1 -0
- package/dist/public/vendor/vditor/dist/ts/util/code160to32.d.ts +1 -0
- package/dist/public/vendor/vditor/dist/ts/util/compatibility.d.ts +7 -0
- package/dist/public/vendor/vditor/dist/ts/util/editorCommonEvent.d.ts +9 -0
- package/dist/public/vendor/vditor/dist/ts/util/fixBrowserBehavior.d.ts +39 -0
- package/dist/public/vendor/vditor/dist/ts/util/function.d.ts +3 -0
- package/dist/public/vendor/vditor/dist/ts/util/getSelectText.d.ts +1 -0
- package/dist/public/vendor/vditor/dist/ts/util/hasClosest.d.ts +9 -0
- package/dist/public/vendor/vditor/dist/ts/util/hasClosestByHeadings.d.ts +2 -0
- package/dist/public/vendor/vditor/dist/ts/util/highlightToolbar.d.ts +1 -0
- package/dist/public/vendor/vditor/dist/ts/util/hotKey.d.ts +1 -0
- package/dist/public/vendor/vditor/dist/ts/util/log.d.ts +1 -0
- package/dist/public/vendor/vditor/dist/ts/util/merge.d.ts +1 -0
- package/dist/public/vendor/vditor/dist/ts/util/processCode.d.ts +2 -0
- package/dist/public/vendor/vditor/dist/ts/util/selection.d.ts +14 -0
- package/dist/public/vendor/vditor/dist/ts/util/toc.d.ts +5 -0
- package/dist/public/vendor/vditor/dist/ts/wysiwyg/afterRenderEvent.d.ts +5 -0
- package/dist/public/vendor/vditor/dist/ts/wysiwyg/highlightToolbarWYSIWYG.d.ts +4 -0
- package/dist/public/vendor/vditor/dist/ts/wysiwyg/index.d.ts +22 -0
- package/dist/public/vendor/vditor/dist/ts/wysiwyg/inlineTag.d.ts +10 -0
- package/dist/public/vendor/vditor/dist/ts/wysiwyg/input.d.ts +1 -0
- package/dist/public/vendor/vditor/dist/ts/wysiwyg/processKeydown.d.ts +2 -0
- package/dist/public/vendor/vditor/dist/ts/wysiwyg/renderDomByMd.d.ts +5 -0
- package/dist/public/vendor/vditor/dist/ts/wysiwyg/setHeading.d.ts +2 -0
- package/dist/public/vendor/vditor/dist/ts/wysiwyg/showCode.d.ts +1 -0
- package/dist/public/vendor/vditor/dist/ts/wysiwyg/toolbarEvent.d.ts +1 -0
- package/dist/public/vendor/vditor/dist/types/index.d.ts +921 -0
- package/dist/public/work-permissions.d.ts +5 -2
- package/dist/public/work-permissions.js +40 -13
- package/dist/security.js +11 -0
- package/dist/security.js.map +1 -1
- package/dist/server-runtime.js +5 -2
- package/dist/server-runtime.js.map +1 -1
- package/dist/store.js +252 -43
- package/dist/store.js.map +1 -1
- package/dist/user-auth.js +40 -8
- package/dist/user-auth.js.map +1 -1
- package/dist/version.js +1 -1
- package/dist/version.js.map +1 -1
- package/dist/work-permissions.js +30 -6
- package/dist/work-permissions.js.map +1 -1
- package/package.json +2 -1
package/dist/public/app.js
CHANGED
|
@@ -4,7 +4,7 @@ import { renderMarkdown } from "/markdown.js?v=20260722-inline-code";
|
|
|
4
4
|
import { buildAiReferenceScope, findAiMention, listAiMentionOptions } from "/ai-mentions.js?v=20260716-chapter-references";
|
|
5
5
|
import { shouldShowAiQuickActions } from "/ai-conversation.js?v=20260713-quick-actions";
|
|
6
6
|
import { calculateLineNumberRowHeight, calculateLineNumberRowTop, calculateLineNumberTextOffset, calculateLineNumberTop } from "/line-number-layout.js?v=20260713-row-box-alignment";
|
|
7
|
-
import { MODEL_PURPOSE_OPTIONS, modelFormValues, modelOptionLabel, modelPayload } from "/model-config.js?v=
|
|
7
|
+
import { MODEL_PURPOSE_OPTIONS, isKimiModelId, modelFormValues, modelOptionLabel, modelPayload } from "/model-config.js?v=20260723-kimi-temperature";
|
|
8
8
|
import { shouldSendAiPrompt } from "/ai-prompt-keyboard.js?v=20260713-enter-to-send";
|
|
9
9
|
import { estimateAiMessageTokens, formatAiMessageMeta } from "/ai-message-meta.js?v=20260713-persisted-output-tokens";
|
|
10
10
|
import { formatAiMessageTime } from "/ai-message-time.js?v=20260713-cross-day-time";
|
|
@@ -14,12 +14,13 @@ import { THEME_STORAGE_KEY, nextTheme, normalizeTheme, themeToggleLabel } from "
|
|
|
14
14
|
import { buildCharacterDetails, buildCharacterState, characterStateEntries, normalizeCharacterDetails, normalizeCharacterSections } from "/character-profile.js?v=20260713-character-editor";
|
|
15
15
|
import { characterVersionSourceLabel, describeCharacterVersionChanges } from "/character-version.js?v=20260713-character-history";
|
|
16
16
|
import { VERSIONED_ENTITY_LABELS, entityVersionSnapshotSummary, entityVersionSourceLabel } from "/entity-version.js?v=20260714-all-knowledge-history";
|
|
17
|
-
import { parsePageRoute, serializePageRoute } from "/page-route.js?v=
|
|
17
|
+
import { parsePageRoute, serializePageRoute } from "/page-route.js?v=20260723-knowledge-editor-page";
|
|
18
18
|
import { splitRelationshipKeywordInput, splitRelationshipKeywords, uniqueRelationshipKeywords } from "/relationship-keywords.js?v=20260720-relationship-keyword-chips";
|
|
19
19
|
import { tokenizeVisibleSpaces } from "/whitespace-visualization.js?v=20260718-visible-whitespace";
|
|
20
20
|
import { buildRaceForest, eligibleRaceParents, racePathLabel } from "/race-hierarchy.js?v=20260721-race-hierarchy";
|
|
21
21
|
import { ANALYSIS_TYPES, analysisTypeDescription } from "/analysis-types.js?v=20260721-analysis-descriptions";
|
|
22
|
-
import { WORK_PERMISSION_MODULES, canReadUiModule, canWriteUiModule, emptyModulePermissions, firstReadableUiModule, normalizeModulePermissions, permissionSummary } from "/work-permissions.js?v=
|
|
22
|
+
import { WORK_PERMISSION_MODULES, canReadPermissionModule, canReadUiModule, canWritePermissionModule, canWriteUiModule, emptyModulePermissions, firstReadableUiModule, normalizeModulePermissions, permissionSummary } from "/work-permissions.js?v=20260723-ai-analysis-permission";
|
|
23
|
+
import { MODULE_LAYOUT_STORAGE_KEY, LEGACY_SETTINGS_LAYOUT_STORAGE_KEY, normalizeModuleLayout, moduleLayoutLabel } from "/module-layout.js?v=20260723-module-layout-toggle";
|
|
23
24
|
|
|
24
25
|
const state = {
|
|
25
26
|
user: null,
|
|
@@ -82,13 +83,19 @@ function analysisTaskStatusLabel(status) {
|
|
|
82
83
|
}
|
|
83
84
|
|
|
84
85
|
function canEditWork(work = state.work) {
|
|
85
|
-
return WORK_PERMISSION_MODULES.some((item) =>
|
|
86
|
+
return WORK_PERMISSION_MODULES.some((item) => canWritePermissionModule(work, item.id));
|
|
86
87
|
}
|
|
87
88
|
|
|
88
89
|
function canEditProse(work = state.work) {
|
|
89
90
|
return canWriteUiModule(work, "editor");
|
|
90
91
|
}
|
|
91
92
|
|
|
93
|
+
function canReplaceProse(work = state.work) {
|
|
94
|
+
return WORK_PERMISSION_MODULES
|
|
95
|
+
.filter((item) => item.id !== "ai-settings")
|
|
96
|
+
.every((item) => canWritePermissionModule(work, item.id));
|
|
97
|
+
}
|
|
98
|
+
|
|
92
99
|
function canManageWork(work = state.work) {
|
|
93
100
|
return ["admin", "owner"].includes(String(work?.accessRole));
|
|
94
101
|
}
|
|
@@ -110,8 +117,8 @@ function applyWorkAccessMode() {
|
|
|
110
117
|
const viewOnly = Boolean(state.work) && !canEditWork();
|
|
111
118
|
const proseReadOnly = Boolean(state.work) && !canEditProse();
|
|
112
119
|
const proseHidden = Boolean(state.work) && !canReadModule("editor");
|
|
113
|
-
const aiHidden = Boolean(state.work) && !
|
|
114
|
-
const aiReadOnly = Boolean(state.work) && !
|
|
120
|
+
const aiHidden = Boolean(state.work) && !canReadPermissionModule(state.work, "ai-chat");
|
|
121
|
+
const aiReadOnly = Boolean(state.work) && !canWritePermissionModule(state.work, "ai-chat");
|
|
115
122
|
const moduleReadOnly = Boolean(state.work) && !canEditModule(state.module);
|
|
116
123
|
$("#app").classList.toggle("view-only-mode", viewOnly);
|
|
117
124
|
$("#app").classList.toggle("prose-read-only-mode", proseReadOnly);
|
|
@@ -119,10 +126,16 @@ function applyWorkAccessMode() {
|
|
|
119
126
|
$("#app").classList.toggle("ai-hidden-mode", aiHidden);
|
|
120
127
|
document.body.classList.toggle("work-viewer-mode", moduleReadOnly);
|
|
121
128
|
for (const item of WORK_PERMISSION_MODULES) {
|
|
129
|
+
if (!item.uiModule) continue;
|
|
122
130
|
const button = $(`#module-nav [data-module="${item.uiModule}"]`);
|
|
123
131
|
if (button) button.classList.toggle("permission-hidden", !canReadModule(item.uiModule));
|
|
124
132
|
}
|
|
125
133
|
$("#module-nav [data-work-settings]").classList.toggle("permission-hidden", Boolean(state.work) && !canManageWork());
|
|
134
|
+
$("#new-volume-button").classList.toggle("permission-hidden", Boolean(state.work) && proseReadOnly);
|
|
135
|
+
$("#welcome-new-work").classList.toggle("permission-hidden", Boolean(state.work) && proseReadOnly);
|
|
136
|
+
$("#import-file-button").setAttribute("aria-disabled", String(proseReadOnly));
|
|
137
|
+
$("#import-file-button").setAttribute("title", proseReadOnly ? "当前权限不能导入正文" : "导入 TXT / DOCX");
|
|
138
|
+
$("#import-file").disabled = proseReadOnly;
|
|
126
139
|
$(".ai-panel").classList.toggle("permission-hidden", aiHidden);
|
|
127
140
|
$("#chapter-title").readOnly = proseReadOnly;
|
|
128
141
|
$("#chapter-content").readOnly = proseReadOnly;
|
|
@@ -198,6 +211,10 @@ let aiReferencesLoadPromise = null;
|
|
|
198
211
|
let aiReferencesLoadWorkId = null;
|
|
199
212
|
let aiConversationsLoadPromise = null;
|
|
200
213
|
let aiConversationsLoadWorkId = null;
|
|
214
|
+
let workScopedUiGeneration = 0;
|
|
215
|
+
let importHistoryRecords = [];
|
|
216
|
+
let importHistoryNextPage = null;
|
|
217
|
+
let importHistoryRequestId = 0;
|
|
201
218
|
|
|
202
219
|
const shelfOnboardingSteps = [
|
|
203
220
|
{ selector: "#home-button", eyebrow: "作品入口", title: "这里是你的创作书架", description: "点击左上角的叙界标志,可以随时回到书架,在不同作品之间切换。", placement: "bottom" },
|
|
@@ -212,7 +229,7 @@ const shelfOnboardingSteps = [
|
|
|
212
229
|
const workspaceOnboardingSteps = [
|
|
213
230
|
{ selector: "#home-button", eyebrow: "作品入口", title: "随时返回书架", description: "点击叙界标志返回书架,切换作品或创建新的故事世界。", placement: "bottom" },
|
|
214
231
|
{ selector: ".file-button", eyebrow: "稿件导入", title: "导入 TXT 或 DOCX", description: "已有稿件可以直接导入,系统会解析分卷与章节结构。", placement: "right" },
|
|
215
|
-
{ selector: "
|
|
232
|
+
{ selector: "[data-new-chapter-volume]", eyebrow: "正文结构", title: "新建章节", description: "使用分卷和章节组织长篇正文,章节会自动保存并保留版本。", placement: "right" },
|
|
216
233
|
{ selector: "#versions-button", eyebrow: "版本安全", title: "查看章节版本", description: "每次保存都会生成可恢复版本,误改内容时可以随时回溯。", placement: "bottom" },
|
|
217
234
|
{ selector: "[data-module=\"characters\"]", eyebrow: "作品知识", title: "维护角色与世界资料", description: "角色、种族、组织、设定和时间线共同构成 AI 可引用的作品知识。", placement: "right" },
|
|
218
235
|
{ selector: "[data-module=\"outlines\"]", eyebrow: "创作规划", title: "跟踪大纲与伏笔", description: "记录剧情目标、冲突、转折和伏笔回收,避免长线遗漏。", placement: "right" },
|
|
@@ -394,7 +411,7 @@ function replacePageRoute(route) {
|
|
|
394
411
|
function currentPageRoute() {
|
|
395
412
|
const workId = state.work?.id ?? null;
|
|
396
413
|
if (!$("#entity-editor-view").classList.contains("hidden") && workId && entityEditorType) {
|
|
397
|
-
const entityId = entityEditorType === "setting" ? settingEditorItem?.id : characterEditorItem?.id;
|
|
414
|
+
const entityId = entityEditorType === "setting" ? settingEditorItem?.id : entityEditorType === "character" ? characterEditorItem?.id : knowledgeEditorItem?.id;
|
|
398
415
|
return { view: "entity-editor", workId, entity: entityEditorType, entityId: entityId ?? null };
|
|
399
416
|
}
|
|
400
417
|
if (!$("#settings-hub-view").classList.contains("hidden")) return { view: "settings", workId, ...settingsRouteContext() };
|
|
@@ -510,50 +527,76 @@ let entityEditorType = null;
|
|
|
510
527
|
let entityEditorDirty = false;
|
|
511
528
|
let settingEditorItem = null;
|
|
512
529
|
let characterEditorItem = null;
|
|
530
|
+
let knowledgeEditorItem = null;
|
|
531
|
+
let knowledgeEditorKind = null;
|
|
532
|
+
let knowledgeEditorSections = [];
|
|
533
|
+
let knowledgeSectionEditorIndex = null;
|
|
534
|
+
let knowledgeSectionEditorDirty = false;
|
|
513
535
|
let characterEditorVersions = [];
|
|
514
536
|
let characterEditorRelationships = [];
|
|
515
537
|
let characterEditorRelationshipsLoading = false;
|
|
516
538
|
let characterEditorSections = [];
|
|
517
|
-
let characterSectionPreviewTimer = null;
|
|
518
539
|
let characterSectionPendingAttachments = [];
|
|
540
|
+
let markdownEditorPendingAttachments = [];
|
|
519
541
|
let characterSectionEditorDirty = false;
|
|
542
|
+
let settingEditorVditor = null;
|
|
543
|
+
let knowledgeSectionVditor = null;
|
|
544
|
+
let characterSectionVditor = null;
|
|
520
545
|
let entityHistoryContext = null;
|
|
521
546
|
|
|
522
547
|
function showEntityEditorPage(type) {
|
|
523
548
|
entityEditorType = type;
|
|
524
549
|
entityEditorDirty = false;
|
|
525
550
|
characterSectionEditorDirty = false;
|
|
551
|
+
knowledgeSectionEditorDirty = false;
|
|
526
552
|
$("#entity-editor-view").classList.remove("hidden");
|
|
527
553
|
$("#setting-editor-form").classList.toggle("hidden", type !== "setting");
|
|
528
554
|
$("#character-editor-form").classList.toggle("hidden", type !== "character");
|
|
555
|
+
$("#knowledge-editor-form").classList.toggle("hidden", !["race", "organization"].includes(type));
|
|
529
556
|
$("#character-section-editor-view").classList.add("hidden");
|
|
557
|
+
$("#knowledge-section-editor-view").classList.add("hidden");
|
|
530
558
|
$("#app").inert = true;
|
|
531
559
|
document.body.classList.add("entity-editor-open");
|
|
532
560
|
replacePageRoute(currentPageRoute());
|
|
533
561
|
}
|
|
534
562
|
|
|
535
563
|
function markEntityEditorDirty() {
|
|
536
|
-
const module = entityEditorType === "setting" ? "settings" : "characters";
|
|
564
|
+
const module = entityEditorType === "setting" ? "settings" : entityEditorType === "character" ? "characters" : entityEditorType === "race" ? "races" : "organizations";
|
|
537
565
|
if (entityEditorType && canEditModule(module)) entityEditorDirty = true;
|
|
538
566
|
}
|
|
539
567
|
|
|
540
|
-
function confirmEntityEditorDiscard(message) {
|
|
568
|
+
async function confirmEntityEditorDiscard(message) {
|
|
541
569
|
if (!entityEditorDirty) return true;
|
|
542
|
-
return
|
|
570
|
+
return confirmToast(message ?? "当前资料有未保存修改,返回列表将丢弃这些修改。是否继续?", {
|
|
571
|
+
title: "放弃未保存修改",
|
|
572
|
+
confirmLabel: "放弃并继续",
|
|
573
|
+
cancelLabel: "继续编辑"
|
|
574
|
+
});
|
|
543
575
|
}
|
|
544
576
|
|
|
545
577
|
async function closeEntityEditor({ force = false } = {}) {
|
|
546
578
|
if (!$("#character-section-editor-view").classList.contains("hidden")) return closeCharacterSectionEditor({ force });
|
|
547
|
-
if (
|
|
579
|
+
if (!$("#knowledge-section-editor-view").classList.contains("hidden")) return closeKnowledgeSectionEditor({ force });
|
|
580
|
+
if (!force && !(await confirmEntityEditorDiscard())) return false;
|
|
548
581
|
await discardPendingCharacterAttachments();
|
|
549
|
-
|
|
582
|
+
await discardPendingMarkdownAttachments();
|
|
583
|
+
destroyVditorEditor(settingEditorVditor);
|
|
584
|
+
settingEditorVditor = null;
|
|
585
|
+
const module = entityEditorType === "setting" ? "settings" : entityEditorType === "character" ? "characters" : entityEditorType === "race" ? "races" : "organizations";
|
|
550
586
|
entityEditorType = null;
|
|
551
587
|
entityEditorDirty = false;
|
|
552
588
|
settingEditorItem = null;
|
|
553
589
|
characterEditorItem = null;
|
|
590
|
+
knowledgeEditorItem = null;
|
|
591
|
+
knowledgeEditorKind = null;
|
|
592
|
+
knowledgeEditorSections = [];
|
|
593
|
+
knowledgeSectionEditorIndex = null;
|
|
594
|
+
knowledgeSectionEditorDirty = false;
|
|
554
595
|
$("#entity-editor-view").classList.add("hidden");
|
|
555
596
|
$("#setting-editor-form").classList.add("hidden");
|
|
556
597
|
$("#character-editor-form").classList.add("hidden");
|
|
598
|
+
$("#knowledge-editor-form").classList.add("hidden");
|
|
599
|
+
$("#knowledge-section-editor-view").classList.add("hidden");
|
|
557
600
|
$("#app").inert = false;
|
|
558
601
|
document.body.classList.remove("entity-editor-open");
|
|
559
602
|
await showModule(module);
|
|
@@ -1106,8 +1149,9 @@ function renderAiConversationHistory() {
|
|
|
1106
1149
|
async function loadAiConversations(openLatest = true) {
|
|
1107
1150
|
const workId = state.work?.id;
|
|
1108
1151
|
if (!workId) return;
|
|
1152
|
+
const generation = workScopedUiGeneration;
|
|
1109
1153
|
const conversations = (await apiPage(`/api/works/${workId}/ai-conversations`)).items;
|
|
1110
|
-
if (state.work?.id !== workId) return;
|
|
1154
|
+
if (state.work?.id !== workId || generation !== workScopedUiGeneration) return;
|
|
1111
1155
|
state.aiConversations = conversations;
|
|
1112
1156
|
loadedAiConversationsWorkId = workId;
|
|
1113
1157
|
renderAiConversationHistory();
|
|
@@ -1392,6 +1436,7 @@ function applyColorTheme(theme) {
|
|
|
1392
1436
|
button.setAttribute("aria-pressed", String(normalized === "dark"));
|
|
1393
1437
|
const themeColor = document.querySelector('meta[name="theme-color"]');
|
|
1394
1438
|
if (themeColor) themeColor.content = normalized === "dark" ? "#171714" : "#8b3d2c";
|
|
1439
|
+
[settingEditorVditor, knowledgeSectionVditor, characterSectionVditor].forEach((editor) => editor?.setTheme(normalized === "dark" ? "dark" : "classic"));
|
|
1395
1440
|
}
|
|
1396
1441
|
|
|
1397
1442
|
function saveColorTheme(theme) {
|
|
@@ -1686,6 +1731,47 @@ function toast(message, type = "info") {
|
|
|
1686
1731
|
}, 3600);
|
|
1687
1732
|
}
|
|
1688
1733
|
|
|
1734
|
+
function confirmToast(message, { title = "请再次确认", confirmLabel = "确认", cancelLabel = "取消" } = {}) {
|
|
1735
|
+
const region = $("#toast-region");
|
|
1736
|
+
const element = document.createElement("section");
|
|
1737
|
+
element.className = "toast toast-confirmation";
|
|
1738
|
+
element.setAttribute("role", "alertdialog");
|
|
1739
|
+
element.setAttribute("aria-label", title);
|
|
1740
|
+
const heading = document.createElement("strong");
|
|
1741
|
+
heading.textContent = title;
|
|
1742
|
+
const description = document.createElement("p");
|
|
1743
|
+
description.textContent = message;
|
|
1744
|
+
const actions = document.createElement("div");
|
|
1745
|
+
actions.className = "toast-confirmation-actions";
|
|
1746
|
+
const cancel = document.createElement("button");
|
|
1747
|
+
cancel.className = "ghost-button";
|
|
1748
|
+
cancel.type = "button";
|
|
1749
|
+
cancel.textContent = cancelLabel;
|
|
1750
|
+
const confirm = document.createElement("button");
|
|
1751
|
+
confirm.className = "primary-button";
|
|
1752
|
+
confirm.type = "button";
|
|
1753
|
+
confirm.textContent = confirmLabel;
|
|
1754
|
+
actions.append(cancel, confirm);
|
|
1755
|
+
element.append(heading, description, actions);
|
|
1756
|
+
region.append(element);
|
|
1757
|
+
raiseToastRegion();
|
|
1758
|
+
cancel.focus();
|
|
1759
|
+
return new Promise((resolve) => {
|
|
1760
|
+
const finish = (confirmed) => {
|
|
1761
|
+
element.remove();
|
|
1762
|
+
if (!region.childElementCount && typeof region.hidePopover === "function" && region.matches(":popover-open")) region.hidePopover();
|
|
1763
|
+
resolve(confirmed);
|
|
1764
|
+
};
|
|
1765
|
+
cancel.addEventListener("click", () => finish(false), { once: true });
|
|
1766
|
+
confirm.addEventListener("click", () => finish(true), { once: true });
|
|
1767
|
+
element.addEventListener("keydown", (event) => {
|
|
1768
|
+
if (event.key !== "Escape") return;
|
|
1769
|
+
event.preventDefault();
|
|
1770
|
+
finish(false);
|
|
1771
|
+
});
|
|
1772
|
+
});
|
|
1773
|
+
}
|
|
1774
|
+
|
|
1689
1775
|
document.addEventListener("toggle", (event) => {
|
|
1690
1776
|
const target = event.target;
|
|
1691
1777
|
if (target instanceof HTMLDialogElement && target.open && $("#toast-region").childElementCount) {
|
|
@@ -1791,20 +1877,34 @@ async function persistChapter({ automatic = false } = {}) {
|
|
|
1791
1877
|
}
|
|
1792
1878
|
}
|
|
1793
1879
|
|
|
1794
|
-
function confirmDiscardChanges(message = "当前章节有未保存修改,继续将丢弃这些修改。是否继续?") {
|
|
1880
|
+
async function confirmDiscardChanges(message = "当前章节有未保存修改,继续将丢弃这些修改。是否继续?") {
|
|
1795
1881
|
if (!state.dirty) return true;
|
|
1796
|
-
return
|
|
1882
|
+
return confirmToast(message, {
|
|
1883
|
+
title: "放弃未保存修改",
|
|
1884
|
+
confirmLabel: "放弃并继续",
|
|
1885
|
+
cancelLabel: "继续编辑"
|
|
1886
|
+
});
|
|
1797
1887
|
}
|
|
1798
1888
|
|
|
1799
1889
|
function chooseExistingWorkImportMode(file) {
|
|
1800
1890
|
const dialog = $("#import-mode-dialog");
|
|
1891
|
+
const form = dialog.querySelector("form");
|
|
1892
|
+
const confirm = $("#import-mode-confirm");
|
|
1893
|
+
const canOverwrite = canReplaceProse();
|
|
1801
1894
|
$("#import-mode-file-summary").textContent = `文件:${file.name};当前作品:《${state.work.title}》`;
|
|
1802
1895
|
$("#import-mode-unsaved-warning").classList.toggle("hidden", !state.dirty);
|
|
1896
|
+
form.reset();
|
|
1897
|
+
$("#import-mode-overwrite").disabled = !canOverwrite;
|
|
1898
|
+
$("#import-mode-overwrite-permission").classList.toggle("hidden", canOverwrite);
|
|
1899
|
+
confirm.disabled = true;
|
|
1900
|
+
form.onchange = () => { confirm.disabled = !form.querySelector('input[name="importMode"]:checked'); };
|
|
1803
1901
|
dialog.returnValue = "cancel";
|
|
1804
1902
|
dialog.showModal();
|
|
1903
|
+
$("#import-mode-dialog-title").focus();
|
|
1805
1904
|
return new Promise((resolve) => {
|
|
1806
1905
|
dialog.addEventListener("close", () => {
|
|
1807
|
-
|
|
1906
|
+
const mode = form.querySelector('input[name="importMode"]:checked')?.value;
|
|
1907
|
+
resolve(dialog.returnValue === "confirm" && ["append", "overwrite"].includes(mode) ? mode : null);
|
|
1808
1908
|
}, { once: true });
|
|
1809
1909
|
});
|
|
1810
1910
|
}
|
|
@@ -1856,7 +1956,7 @@ async function initializePage() {
|
|
|
1856
1956
|
state.module = route.view === "module"
|
|
1857
1957
|
? route.module
|
|
1858
1958
|
: route.view === "entity-editor"
|
|
1859
|
-
? (
|
|
1959
|
+
? ({ setting: "settings", character: "characters", race: "races", organization: "organizations" }[route.entity] ?? "characters")
|
|
1860
1960
|
: "editor";
|
|
1861
1961
|
await selectWork(requestedWork.id, route.view === "editor" ? route.chapterId : null);
|
|
1862
1962
|
}
|
|
@@ -1867,14 +1967,16 @@ async function initializePage() {
|
|
|
1867
1967
|
}
|
|
1868
1968
|
if (route.view === "module") return;
|
|
1869
1969
|
if (route.view === "entity-editor") {
|
|
1870
|
-
const records = route.entity === "setting" ? state.settings : state.characters;
|
|
1970
|
+
const records = route.entity === "setting" ? state.settings : route.entity === "character" ? state.characters : route.entity === "race" ? state.races : state.organizations;
|
|
1871
1971
|
const item = route.entityId ? records.find((record) => record.id === route.entityId) : null;
|
|
1872
1972
|
if (route.entityId && !item) {
|
|
1873
|
-
toast(
|
|
1973
|
+
toast(({ setting: "未找到要编辑的设定", character: "未找到要编辑的角色", race: "未找到要编辑的种族", organization: "未找到要编辑的组织" }[route.entity] ?? "未找到要编辑的档案"), "error");
|
|
1874
1974
|
return;
|
|
1875
1975
|
}
|
|
1876
1976
|
if (route.entity === "setting") openSettingEditor(item);
|
|
1877
|
-
else await openCharacterEditor(item);
|
|
1977
|
+
else if (route.entity === "character") await openCharacterEditor(item);
|
|
1978
|
+
else if (route.entity === "race") await openRaceDialog(item);
|
|
1979
|
+
else if (route.entity === "organization") await openOrganizationDialog(item);
|
|
1878
1980
|
return;
|
|
1879
1981
|
}
|
|
1880
1982
|
if (route.view === "welcome") {
|
|
@@ -1882,7 +1984,7 @@ async function initializePage() {
|
|
|
1882
1984
|
return;
|
|
1883
1985
|
}
|
|
1884
1986
|
if (route.view === "settings") {
|
|
1885
|
-
showSettingsHub();
|
|
1987
|
+
await showSettingsHub();
|
|
1886
1988
|
settingsReturnContext = restoredSettingsReturnContext(route);
|
|
1887
1989
|
renderSettingsHub();
|
|
1888
1990
|
return;
|
|
@@ -2177,10 +2279,10 @@ async function openSearchResult(result) {
|
|
|
2177
2279
|
}
|
|
2178
2280
|
}
|
|
2179
2281
|
|
|
2180
|
-
function showSettingsHub() {
|
|
2282
|
+
async function showSettingsHub() {
|
|
2181
2283
|
const alreadyInSettings = !$("#settings-hub-view").classList.contains("hidden") || !$("#platform-ai-view").classList.contains("hidden");
|
|
2182
2284
|
if (!alreadyInSettings) {
|
|
2183
|
-
if (state.dirty && !confirmDiscardChanges("当前章节有未保存修改,进入设置将放弃本地修改。是否继续?")) return false;
|
|
2285
|
+
if (state.dirty && !(await confirmDiscardChanges("当前章节有未保存修改,进入设置将放弃本地修改。是否继续?"))) return false;
|
|
2184
2286
|
settingsReturnContext = captureSettingsReturnContext();
|
|
2185
2287
|
state.dirty = false;
|
|
2186
2288
|
}
|
|
@@ -2217,7 +2319,7 @@ async function returnFromSettings() {
|
|
|
2217
2319
|
}
|
|
2218
2320
|
|
|
2219
2321
|
async function showPlatformAi() {
|
|
2220
|
-
if (state.dirty && !confirmDiscardChanges("当前章节有未保存修改,进入平台 AI 管理将放弃本地修改。是否继续?")) return false;
|
|
2322
|
+
if (state.dirty && !(await confirmDiscardChanges("当前章节有未保存修改,进入平台 AI 管理将放弃本地修改。是否继续?"))) return false;
|
|
2221
2323
|
state.dirty = false;
|
|
2222
2324
|
updateDocumentTitle();
|
|
2223
2325
|
$("#app").classList.add("shelf-mode");
|
|
@@ -2257,34 +2359,45 @@ function renderShelf() {
|
|
|
2257
2359
|
$("#book-add-card").addEventListener("click", openWorkDialog);
|
|
2258
2360
|
}
|
|
2259
2361
|
|
|
2362
|
+
function resetWorkScopedUiCaches() {
|
|
2363
|
+
workScopedUiGeneration += 1;
|
|
2364
|
+
loadedAiModelsWorkId = null;
|
|
2365
|
+
loadedAiReferencesWorkId = null;
|
|
2366
|
+
loadedAiConversationsWorkId = null;
|
|
2367
|
+
aiModelsLoadPromise = null;
|
|
2368
|
+
aiModelsLoadWorkId = null;
|
|
2369
|
+
aiReferencesLoadPromise = null;
|
|
2370
|
+
aiReferencesLoadWorkId = null;
|
|
2371
|
+
aiConversationsLoadPromise = null;
|
|
2372
|
+
aiConversationsLoadWorkId = null;
|
|
2373
|
+
state.models = [];
|
|
2374
|
+
state.characters = [];
|
|
2375
|
+
state.settings = [];
|
|
2376
|
+
state.collapsedVolumeIds.clear();
|
|
2377
|
+
lastSavedChapterSnapshot = null;
|
|
2378
|
+
if (aiContextUsageTimer !== null) clearTimeout(aiContextUsageTimer);
|
|
2379
|
+
aiContextUsageTimer = null;
|
|
2380
|
+
aiContextUsageRequest += 1;
|
|
2381
|
+
state.aiCitations = [];
|
|
2382
|
+
state.aiReferences = [];
|
|
2383
|
+
state.aiPromptSent = false;
|
|
2384
|
+
state.aiConversationId = null;
|
|
2385
|
+
state.aiConversations = [];
|
|
2386
|
+
renderAiCitations();
|
|
2387
|
+
renderAiReferences();
|
|
2388
|
+
renderAiQuickActions();
|
|
2389
|
+
resetAiFeed();
|
|
2390
|
+
$("#ai-conversation-title").textContent = "新对话";
|
|
2391
|
+
$("#ai-model").innerHTML = '<option value="">使用创作助手时加载模型</option>';
|
|
2392
|
+
setAiContextMeter(null);
|
|
2393
|
+
renderAiConversationHistory();
|
|
2394
|
+
}
|
|
2395
|
+
|
|
2260
2396
|
async function selectWork(workId, preferredChapterId = null) {
|
|
2261
2397
|
const discarding = state.work?.id !== workId && state.dirty;
|
|
2262
|
-
if (discarding && !confirmDiscardChanges()) return false;
|
|
2398
|
+
if (discarding && !(await confirmDiscardChanges())) return false;
|
|
2263
2399
|
const nextWork = await api(`/api/works/${workId}?page=1&limit=100`);
|
|
2264
|
-
if (state.work?.id !== nextWork.id)
|
|
2265
|
-
loadedAiModelsWorkId = null;
|
|
2266
|
-
loadedAiReferencesWorkId = null;
|
|
2267
|
-
loadedAiConversationsWorkId = null;
|
|
2268
|
-
state.models = [];
|
|
2269
|
-
state.characters = [];
|
|
2270
|
-
state.settings = [];
|
|
2271
|
-
if (aiContextUsageTimer !== null) clearTimeout(aiContextUsageTimer);
|
|
2272
|
-
aiContextUsageTimer = null;
|
|
2273
|
-
aiContextUsageRequest += 1;
|
|
2274
|
-
state.aiCitations = [];
|
|
2275
|
-
state.aiReferences = [];
|
|
2276
|
-
state.aiPromptSent = false;
|
|
2277
|
-
state.aiConversationId = null;
|
|
2278
|
-
state.aiConversations = [];
|
|
2279
|
-
renderAiCitations();
|
|
2280
|
-
renderAiReferences();
|
|
2281
|
-
renderAiQuickActions();
|
|
2282
|
-
resetAiFeed();
|
|
2283
|
-
$("#ai-conversation-title").textContent = "新对话";
|
|
2284
|
-
$("#ai-model").innerHTML = '<option value="">使用创作助手时加载模型</option>';
|
|
2285
|
-
setAiContextMeter(null);
|
|
2286
|
-
renderAiConversationHistory();
|
|
2287
|
-
}
|
|
2400
|
+
if (state.work?.id !== nextWork.id) resetWorkScopedUiCaches();
|
|
2288
2401
|
if (discarding) setSaveState("就绪");
|
|
2289
2402
|
$("#app").classList.remove("shelf-mode");
|
|
2290
2403
|
$("#shelf-view").classList.add("hidden");
|
|
@@ -2313,11 +2426,15 @@ async function selectWork(workId, preferredChapterId = null) {
|
|
|
2313
2426
|
function renderTree() {
|
|
2314
2427
|
if (!state.work) return;
|
|
2315
2428
|
const count = state.work.volumes.reduce((total, volume) => total + volume.chapters.length, 0);
|
|
2429
|
+
const proseEditable = canEditProse();
|
|
2316
2430
|
$("#chapter-count").textContent = `${count} 章`;
|
|
2317
2431
|
$("#novel-tree").classList.remove("empty-copy");
|
|
2318
2432
|
$("#novel-tree").innerHTML = state.work.volumes.map((volume) => `
|
|
2319
2433
|
<div class="volume-node ${state.collapsedVolumeIds.has(volume.id) ? "is-collapsed" : ""}" data-volume-id="${esc(volume.id)}">
|
|
2320
|
-
<
|
|
2434
|
+
<div class="volume-title">
|
|
2435
|
+
<button class="volume-toggle" type="button" data-volume-toggle="${esc(volume.id)}" aria-expanded="${state.collapsedVolumeIds.has(volume.id) ? "false" : "true"}" title="左键折叠,右键设置分卷"><span>${esc(volume.title)}</span><span>${volume.chapters.length} 章</span></button>
|
|
2436
|
+
${proseEditable ? `<button class="add-button chapter-add-button" type="button" data-new-chapter-volume="${esc(volume.id)}" aria-label="在“${esc(volume.title)}”中新建章节" title="在“${esc(volume.title)}”中新建章节">+</button>` : ""}
|
|
2437
|
+
</div>
|
|
2321
2438
|
<div class="volume-chapters">
|
|
2322
2439
|
${volume.chapters.map((chapter) => `
|
|
2323
2440
|
<button class="chapter-node ${state.chapter?.id === chapter.id ? "active" : ""}" type="button" data-chapter-id="${esc(chapter.id)}">
|
|
@@ -2338,6 +2455,9 @@ function renderTree() {
|
|
|
2338
2455
|
openVolumeDialog(state.work.volumes.find((volume) => volume.id === button.dataset.volumeToggle));
|
|
2339
2456
|
});
|
|
2340
2457
|
});
|
|
2458
|
+
$("#novel-tree").querySelectorAll("[data-new-chapter-volume]").forEach((button) => {
|
|
2459
|
+
button.addEventListener("click", () => openChapterDialog(button.dataset.newChapterVolume));
|
|
2460
|
+
});
|
|
2341
2461
|
$("#novel-tree").querySelectorAll("[data-chapter-id]").forEach((button) => {
|
|
2342
2462
|
button.addEventListener("click", () => selectChapter(button.dataset.chapterId));
|
|
2343
2463
|
button.addEventListener("contextmenu", (event) => {
|
|
@@ -2370,7 +2490,7 @@ function openChapterTypeMenu(chapterId, clientX, clientY) {
|
|
|
2370
2490
|
}
|
|
2371
2491
|
|
|
2372
2492
|
async function selectChapter(chapterId) {
|
|
2373
|
-
if (state.chapter?.id !== chapterId && !confirmDiscardChanges("当前章节有未保存修改,仍要切换吗?")) return;
|
|
2493
|
+
if (state.chapter?.id !== chapterId && !(await confirmDiscardChanges("当前章节有未保存修改,仍要切换吗?"))) return;
|
|
2374
2494
|
cancelChapterAutoSave();
|
|
2375
2495
|
state.chapter = await api(`/api/chapters/${chapterId}`);
|
|
2376
2496
|
lastSavedChapterSnapshot = { chapterId: state.chapter.id, title: state.chapter.title, content: state.chapter.content };
|
|
@@ -2458,7 +2578,7 @@ async function showModule(module) {
|
|
|
2458
2578
|
}
|
|
2459
2579
|
module = fallback;
|
|
2460
2580
|
}
|
|
2461
|
-
if (module !== "editor" && state.module === "editor" && !confirmDiscardChanges()) return;
|
|
2581
|
+
if (module !== "editor" && state.module === "editor" && !(await confirmDiscardChanges())) return;
|
|
2462
2582
|
if (module !== "editor" && state.module === "editor" && state.dirty) setSaveState("已放弃修改");
|
|
2463
2583
|
state.module = module;
|
|
2464
2584
|
applyWorkAccessMode();
|
|
@@ -2587,8 +2707,10 @@ function openEntityMergeDialog({ typeLabel, source, candidates, endpoint, body,
|
|
|
2587
2707
|
}
|
|
2588
2708
|
|
|
2589
2709
|
async function deleteManagedEntity({ typeLabel, item, endpoint, refresh, warning = "" }) {
|
|
2590
|
-
const
|
|
2591
|
-
|
|
2710
|
+
const message = warning
|
|
2711
|
+
? `确认删除${typeLabel}“${item.name}”吗?\n${warning}`
|
|
2712
|
+
: `确认删除${typeLabel}“${item.name}”吗?`;
|
|
2713
|
+
if (!(await confirmToast(message, { title: `删除${typeLabel}`, confirmLabel: "确认删除" }))) return;
|
|
2592
2714
|
try {
|
|
2593
2715
|
await api(endpoint(item), { method: "DELETE" });
|
|
2594
2716
|
await refresh();
|
|
@@ -2599,14 +2721,76 @@ async function deleteManagedEntity({ typeLabel, item, endpoint, refresh, warning
|
|
|
2599
2721
|
}
|
|
2600
2722
|
}
|
|
2601
2723
|
|
|
2724
|
+
function readModuleLayout() {
|
|
2725
|
+
try {
|
|
2726
|
+
const stored = localStorage.getItem(MODULE_LAYOUT_STORAGE_KEY) ?? localStorage.getItem(LEGACY_SETTINGS_LAYOUT_STORAGE_KEY);
|
|
2727
|
+
return normalizeModuleLayout(stored);
|
|
2728
|
+
} catch {
|
|
2729
|
+
return "cards";
|
|
2730
|
+
}
|
|
2731
|
+
}
|
|
2732
|
+
|
|
2733
|
+
function saveModuleLayout(layout) {
|
|
2734
|
+
const normalized = normalizeModuleLayout(layout);
|
|
2735
|
+
try {
|
|
2736
|
+
localStorage.setItem(MODULE_LAYOUT_STORAGE_KEY, normalized);
|
|
2737
|
+
} catch {
|
|
2738
|
+
/* 浏览器禁用存储时仅保留当前会话选择 */
|
|
2739
|
+
}
|
|
2740
|
+
return normalized;
|
|
2741
|
+
}
|
|
2742
|
+
|
|
2743
|
+
function moduleRowPreview(text, max = 180) {
|
|
2744
|
+
const preview = String(text ?? "").replace(/\s+/g, " ").trim();
|
|
2745
|
+
return preview.length > max ? `${preview.slice(0, max)}…` : preview;
|
|
2746
|
+
}
|
|
2747
|
+
|
|
2748
|
+
function renderModuleLayoutToggle(layout, ariaLabel = "列表样式") {
|
|
2749
|
+
return `<div class="module-layout-toolbar">
|
|
2750
|
+
<div class="module-layout-toggle" role="group" aria-label="${esc(ariaLabel)}">
|
|
2751
|
+
<button type="button" data-module-layout="cards" aria-pressed="${layout === "cards"}">卡片</button>
|
|
2752
|
+
<button type="button" data-module-layout="rows" aria-pressed="${layout === "rows"}">列表</button>
|
|
2753
|
+
</div>
|
|
2754
|
+
<span class="module-layout-hint">当前:${esc(moduleLayoutLabel(layout))}</span>
|
|
2755
|
+
</div>`;
|
|
2756
|
+
}
|
|
2757
|
+
|
|
2758
|
+
function bindModuleLayoutToggle(refresh) {
|
|
2759
|
+
$("#module-content").querySelectorAll("[data-module-layout]").forEach((button) => button.addEventListener("click", async () => {
|
|
2760
|
+
saveModuleLayout(button.dataset.moduleLayout);
|
|
2761
|
+
await refresh();
|
|
2762
|
+
}));
|
|
2763
|
+
}
|
|
2764
|
+
|
|
2765
|
+
function settingRecordActions(item) {
|
|
2766
|
+
return `${item.status === "pending" ? `<button data-setting-status="confirmed" data-setting-id="${esc(item.id)}">确认候选</button><button data-setting-status="deprecated" data-setting-id="${esc(item.id)}">弃用</button>` : ""}<button data-edit-setting="${esc(item.id)}">编辑</button><button data-entity-history="setting" data-entity-id="${esc(item.id)}" data-entity-title="${esc(item.title)}">版本历史</button>`;
|
|
2767
|
+
}
|
|
2768
|
+
|
|
2769
|
+
function renderSettingCards(records) {
|
|
2770
|
+
return `<div class="card-grid">${records.map((item) => `
|
|
2771
|
+
<article class="record-card"><small>${esc(item.category)} · ${item.locked ? "已锁定" : esc(item.status)}</small>
|
|
2772
|
+
<h3>${esc(item.title)}</h3><div class="record-markdown-preview message-body">${renderMarkdown(item.content) || '<p class="markdown-editor-empty">暂无正文</p>'}</div>
|
|
2773
|
+
<div class="card-actions">${settingRecordActions(item)}</div></article>`).join("")}</div>`;
|
|
2774
|
+
}
|
|
2775
|
+
|
|
2776
|
+
function renderSettingRows(records) {
|
|
2777
|
+
return `<div class="module-row-list">${records.map((item) => {
|
|
2778
|
+
const preview = moduleRowPreview(item.content);
|
|
2779
|
+
return `
|
|
2780
|
+
<article class="record-card module-row"><small>${esc(item.category)} · ${item.locked ? "已锁定" : esc(item.status)}</small>
|
|
2781
|
+
<h3>${esc(item.title)}</h3><p class="module-row-preview" title="${esc(preview)}">${esc(preview)}</p>
|
|
2782
|
+
<div class="card-actions">${settingRecordActions(item)}</div></article>`;
|
|
2783
|
+
}).join("")}</div>`;
|
|
2784
|
+
}
|
|
2785
|
+
|
|
2602
2786
|
async function renderSettings() {
|
|
2603
2787
|
const records = (await apiPage(`/api/works/${state.work.id}/settings`)).items;
|
|
2604
2788
|
state.settings = records;
|
|
2605
|
-
|
|
2606
|
-
|
|
2607
|
-
|
|
2608
|
-
<div class="card-actions">${item.status === "pending" ? `<button data-setting-status="confirmed" data-setting-id="${esc(item.id)}">确认候选</button><button data-setting-status="deprecated" data-setting-id="${esc(item.id)}">弃用</button>` : ""}<button data-edit-setting="${esc(item.id)}">编辑</button><button data-entity-history="setting" data-entity-id="${esc(item.id)}" data-entity-title="${esc(item.title)}">版本历史</button></div></article>`).join("")}</div>`
|
|
2789
|
+
const layout = readModuleLayout();
|
|
2790
|
+
$("#module-content").innerHTML = records.length
|
|
2791
|
+
? `${renderModuleLayoutToggle(layout, "设定列表样式")}${layout === "rows" ? renderSettingRows(records) : renderSettingCards(records)}`
|
|
2609
2792
|
: emptyModule("还没有世界观设定", "新建规则、地点、组织、科技或创作约束。AI 提取的候选也会进入这里。");
|
|
2793
|
+
bindModuleLayoutToggle(renderSettings);
|
|
2610
2794
|
$("#module-content").querySelectorAll("[data-setting-status]").forEach((button) => button.addEventListener("click", async () => {
|
|
2611
2795
|
await api(`/api/settings/${button.dataset.settingId}`, { method: "PATCH", body: { status: button.dataset.settingStatus, changeNote: button.dataset.settingStatus === "confirmed" ? "确认 AI 设定候选" : "弃用 AI 设定候选" } });
|
|
2612
2796
|
await renderSettings();
|
|
@@ -2622,8 +2806,9 @@ async function renderCharacters() {
|
|
|
2622
2806
|
canReadModule("races") ? apiAllPages(`/api/works/${state.work.id}/races`) : Promise.resolve([]),
|
|
2623
2807
|
canReadModule("organizations") ? apiAllPages(`/api/works/${state.work.id}/organizations`) : Promise.resolve([])
|
|
2624
2808
|
]);
|
|
2625
|
-
const
|
|
2626
|
-
$("
|
|
2809
|
+
const layout = readModuleLayout();
|
|
2810
|
+
const characterActions = (item) => `<button data-edit-character="${esc(item.id)}">编辑</button>${canEditModule("characters") && state.characters.length > 1 ? `<button data-merge-character="${esc(item.id)}">合并</button>` : ""}${canEditModule("characters") ? `<button class="danger-button" data-delete-character="${esc(item.id)}">删除</button>` : ""}`;
|
|
2811
|
+
const characterCards = () => `<div class="card-grid">${state.characters.map((item) => {
|
|
2627
2812
|
const details = normalizeCharacterDetails(item.attributes?.details);
|
|
2628
2813
|
return `
|
|
2629
2814
|
<article class="record-card character-card" data-open-character="${esc(item.id)}" role="button" tabindex="0" aria-label="查看角色 ${esc(item.name)}"><small>${item.lockedFields.length ? `锁定 ${item.lockedFields.length} 项` : esc(item.visibility)}</small>
|
|
@@ -2634,9 +2819,29 @@ async function renderCharacters() {
|
|
|
2634
2819
|
<div class="organization-links"><b>所属组织</b>${(item.organizations ?? []).length ? item.organizations.map((organization) => `<span class="pill organization-pill">${esc(organization.name)}</span>`).join("") : '<span class="organization-empty">未加入组织</span>'}</div>
|
|
2635
2820
|
${item.profile?.summary ? `<p class="character-summary">${esc(item.profile.summary)}</p>` : `<p>${esc(Object.entries(item.currentState).map(([key, value]) => `${key}:${value}`).join("\n") || "尚未记录当前状态")}</p>`}
|
|
2636
2821
|
${item.profileSectionCount ? `<small class="character-section-count">${item.profileSectionCount} 个设定章节</small>` : ""}
|
|
2637
|
-
<div class="card-actions"
|
|
2638
|
-
}).join("")}</div
|
|
2822
|
+
<div class="card-actions">${characterActions(item)}</div></article>`;
|
|
2823
|
+
}).join("")}</div>`;
|
|
2824
|
+
const characterRows = () => `<div class="module-row-list">${state.characters.map((item) => {
|
|
2825
|
+
const preview = moduleRowPreview(item.profile?.summary || item.attributes?.identity || Object.entries(item.currentState).map(([key, value]) => `${key}:${value}`).join(" ") || "尚未记录当前状态");
|
|
2826
|
+
const meta = [
|
|
2827
|
+
item.species ? (racePathLabel(item.race) || item.species) : "",
|
|
2828
|
+
...(item.aliases ?? []).slice(0, 3),
|
|
2829
|
+
(item.organizations ?? []).length ? (item.organizations ?? []).map((organization) => organization.name).join("、") : ""
|
|
2830
|
+
].filter(Boolean).join(" · ");
|
|
2831
|
+
const line = meta ? `${meta} · ${preview}` : preview;
|
|
2832
|
+
return `
|
|
2833
|
+
<article class="record-card module-row character-card" data-open-character="${esc(item.id)}" role="button" tabindex="0" aria-label="查看角色 ${esc(item.name)}">
|
|
2834
|
+
<small>${item.lockedFields.length ? `锁定 ${item.lockedFields.length} 项` : esc(item.visibility)}</small>
|
|
2835
|
+
<h3>${esc(item.name)}</h3>
|
|
2836
|
+
<p class="module-row-preview" title="${esc(line)}">${esc(line)}</p>
|
|
2837
|
+
<div class="card-actions">${characterActions(item)}</div>
|
|
2838
|
+
</article>`;
|
|
2839
|
+
}).join("")}</div>`;
|
|
2840
|
+
const auditPanel = canEditModule("tasks") ? `<section class="character-audit-panel"><div><strong>角色身份确认</strong><small>让 AI 查询角色档案并搜索正文,找出可能被误建成两个档案的同一角色。AI 只提交审核建议,不会自动合并。</small></div><button id="create-character-audit-task" class="ghost-button" type="button" ${state.characters.length < 2 ? "disabled" : ""}>AI 角色查重</button></section>` : "";
|
|
2841
|
+
$("#module-content").innerHTML = auditPanel + (state.characters.length
|
|
2842
|
+
? `${renderModuleLayoutToggle(layout, "角色列表样式")}${layout === "rows" ? characterRows() : characterCards()}`
|
|
2639
2843
|
: emptyModule("还没有角色档案", "创建主要人物,并维护别名、身份、动机和当前状态。"));
|
|
2844
|
+
bindModuleLayoutToggle(renderCharacters);
|
|
2640
2845
|
$("#create-character-audit-task")?.addEventListener("click", async () => {
|
|
2641
2846
|
const button = $("#create-character-audit-task");
|
|
2642
2847
|
button.disabled = true;
|
|
@@ -2694,20 +2899,36 @@ async function renderRaces() {
|
|
|
2694
2899
|
apiAllPages(`/api/works/${state.work.id}/races`),
|
|
2695
2900
|
canReadModule("characters") ? apiAllPages(`/api/works/${state.work.id}/characters`) : Promise.resolve([])
|
|
2696
2901
|
]);
|
|
2902
|
+
const layout = readModuleLayout();
|
|
2903
|
+
const raceActions = (item) => `<button data-edit-race="${esc(item.id)}">编辑</button><button data-entity-history="race" data-entity-id="${esc(item.id)}" data-entity-title="${esc(item.name)}">版本历史</button>${canEditModule("races") && state.races.length > 1 ? `<button data-merge-race="${esc(item.id)}">合并</button>` : ""}${canEditModule("races") ? `<button class="danger-button" data-delete-race="${esc(item.id)}">删除</button>` : ""}`;
|
|
2697
2904
|
const renderRaceNode = (item) => `<details class="race-tree-node" open data-race-node="${esc(item.id)}">
|
|
2698
2905
|
<summary><span>${esc(item.name)}</span><small>${item.children.length} 个直接子种族</small></summary>
|
|
2699
2906
|
<div class="race-tree-branch">
|
|
2700
|
-
<article class="record-card race-card"><small>${item.memberIds.length} 位直接角色 · ${item.settings.length}
|
|
2907
|
+
<article class="record-card race-card"><small>${item.memberIds.length} 位直接角色 · ${item.settings.length ? "已填写共同设定" : "暂无共同设定"}</small>
|
|
2701
2908
|
<div class="race-path" aria-label="种族路径">${esc(racePathLabel(item))}</div>
|
|
2702
2909
|
<p>${esc(item.description || "尚未填写种族简介")}</p>
|
|
2703
|
-
<div class="race-settings">${item.effectiveSettings.length ? item.effectiveSettings.map((setting) => `<
|
|
2910
|
+
<div class="race-settings">${item.effectiveSettings.length ? item.effectiveSettings.map((setting) => `<section class="knowledge-markdown-block${setting.inherited ? " inherited" : ""}"><div class="knowledge-markdown-block-heading"><h4>${esc(setting.title || "未命名章节")}</h4><small>${esc(setting.inherited ? `继承自 ${setting.sourceRaceName}` : `定义于 ${setting.sourceRaceName}`)}</small></div><div class="message-body">${renderMarkdown(setting.value) || '<p class="markdown-editor-empty">暂无内容</p>'}</div></section>`).join("") : '<span class="pill">暂无共同设定</span>'}</div>
|
|
2704
2911
|
<p class="race-members">直接角色:${item.members.length ? item.members.map((member) => esc(member.name)).join("、") : "暂无绑定角色"}</p>
|
|
2705
|
-
<div class="card-actions"
|
|
2912
|
+
<div class="card-actions">${raceActions(item)}</div>
|
|
2706
2913
|
</article>
|
|
2707
2914
|
${item.children.length ? `<div class="race-tree-children">${item.children.map(renderRaceNode).join("")}</div>` : ""}
|
|
2708
2915
|
</div>
|
|
2709
2916
|
</details>`;
|
|
2710
|
-
|
|
2917
|
+
const raceRows = () => `<div class="module-row-list">${state.races.map((item) => {
|
|
2918
|
+
const preview = moduleRowPreview(item.description || "尚未填写种族简介");
|
|
2919
|
+
const meta = `${item.memberIds.length} 位直接角色 · ${item.settings.length ? "已填写共同设定" : "暂无共同设定"}`;
|
|
2920
|
+
return `
|
|
2921
|
+
<article class="record-card module-row race-card">
|
|
2922
|
+
<small>${esc(meta)}</small>
|
|
2923
|
+
<h3>${esc(item.name)}<span class="module-row-path">${esc(racePathLabel(item))}</span></h3>
|
|
2924
|
+
<p class="module-row-preview" title="${esc(preview)}">${esc(preview)}${item.members.length ? ` · ${esc(item.members.map((member) => member.name).join("、"))}` : ""}</p>
|
|
2925
|
+
<div class="card-actions">${raceActions(item)}</div>
|
|
2926
|
+
</article>`;
|
|
2927
|
+
}).join("")}</div>`;
|
|
2928
|
+
$("#module-content").innerHTML = state.races.length
|
|
2929
|
+
? `${renderModuleLayoutToggle(layout, "种族列表样式")}${layout === "rows" ? raceRows() : `<section class="race-tree" aria-label="种族层级">${buildRaceForest(state.races).map(renderRaceNode).join("")}</section>`}`
|
|
2930
|
+
: emptyModule("还没有种族档案", "先创建种族及共同设定,之后角色编辑器才能选择该种族。");
|
|
2931
|
+
bindModuleLayoutToggle(renderRaces);
|
|
2711
2932
|
$("#module-content").querySelectorAll("[data-edit-race]").forEach((button) => button.addEventListener("click", () => openRaceDialog(state.races.find((item) => item.id === button.dataset.editRace))));
|
|
2712
2933
|
$("#module-content").querySelectorAll("[data-merge-race]").forEach((button) => button.addEventListener("click", () => {
|
|
2713
2934
|
const source = state.races.find((item) => item.id === button.dataset.mergeRace);
|
|
@@ -2741,13 +2962,30 @@ async function renderOrganizations() {
|
|
|
2741
2962
|
apiAllPages(`/api/works/${state.work.id}/organizations`),
|
|
2742
2963
|
canReadModule("characters") ? apiAllPages(`/api/works/${state.work.id}/characters`) : Promise.resolve([])
|
|
2743
2964
|
]);
|
|
2744
|
-
|
|
2745
|
-
|
|
2965
|
+
const layout = readModuleLayout();
|
|
2966
|
+
const organizationActions = (item) => `<button data-edit-organization="${esc(item.id)}">编辑</button><button data-entity-history="organization" data-entity-id="${esc(item.id)}" data-entity-title="${esc(item.name)}">版本历史</button>${canEditModule("organizations") && state.organizations.length > 1 ? `<button data-merge-organization="${esc(item.id)}">合并</button>` : ""}${canEditModule("organizations") ? `<button class="danger-button" data-delete-organization="${esc(item.id)}">删除</button>` : ""}`;
|
|
2967
|
+
const organizationCards = () => `<div class="card-grid organization-grid">${state.organizations.map((item) => `
|
|
2968
|
+
<article class="record-card organization-card"><small>${item.memberIds.length} 位成员 · ${item.settings.length ? "已填写组织设定" : "暂无组织设定"}</small>
|
|
2746
2969
|
<h3>${esc(item.name)}</h3><p>${esc(item.description || "尚未填写组织简介")}</p>
|
|
2747
|
-
<div class="organization-settings">${item.
|
|
2970
|
+
<div class="organization-settings">${item.settingsSections?.length ? item.settingsSections.map((section) => `<article class="knowledge-markdown-block"><div class="knowledge-markdown-block-heading"><h4>${esc(section.title || "未命名章节")}</h4></div><div class="message-body">${renderMarkdown(section.contentMarkdown) || '<p class="markdown-editor-empty">暂无内容</p>'}</div></article>`).join("") : '<span class="pill">暂无组织设定</span>'}</div>
|
|
2748
2971
|
<p class="organization-members">成员:${item.members.length ? item.members.map((member) => esc(member.name)).join("、") : "暂无绑定角色"}</p>
|
|
2749
|
-
<div class="card-actions"
|
|
2750
|
-
</article>`).join("")}</div
|
|
2972
|
+
<div class="card-actions">${organizationActions(item)}</div>
|
|
2973
|
+
</article>`).join("")}</div>`;
|
|
2974
|
+
const organizationRows = () => `<div class="module-row-list">${state.organizations.map((item) => {
|
|
2975
|
+
const preview = moduleRowPreview(item.description || "尚未填写组织简介");
|
|
2976
|
+
const members = item.members.length ? item.members.map((member) => member.name).join("、") : "暂无绑定角色";
|
|
2977
|
+
return `
|
|
2978
|
+
<article class="record-card module-row organization-card">
|
|
2979
|
+
<small>${item.memberIds.length} 位成员 · ${item.settings.length ? "已填写组织设定" : "暂无组织设定"}</small>
|
|
2980
|
+
<h3>${esc(item.name)}</h3>
|
|
2981
|
+
<p class="module-row-preview" title="${esc(`${preview} · 成员:${members}`)}">${esc(preview)} · ${esc(members)}</p>
|
|
2982
|
+
<div class="card-actions">${organizationActions(item)}</div>
|
|
2983
|
+
</article>`;
|
|
2984
|
+
}).join("")}</div>`;
|
|
2985
|
+
$("#module-content").innerHTML = state.organizations.length
|
|
2986
|
+
? `${renderModuleLayoutToggle(layout, "组织列表样式")}${layout === "rows" ? organizationRows() : organizationCards()}`
|
|
2987
|
+
: emptyModule("还没有组织", "创建国家、机构、阵营或团队,并维护组织设定与成员。");
|
|
2988
|
+
bindModuleLayoutToggle(renderOrganizations);
|
|
2751
2989
|
$("#module-content").querySelectorAll("[data-edit-organization]").forEach((button) => button.addEventListener("click", () => openOrganizationDialog(state.organizations.find((item) => item.id === button.dataset.editOrganization))));
|
|
2752
2990
|
$("#module-content").querySelectorAll("[data-merge-organization]").forEach((button) => button.addEventListener("click", () => {
|
|
2753
2991
|
const source = state.organizations.find((item) => item.id === button.dataset.mergeOrganization);
|
|
@@ -2815,17 +3053,35 @@ async function renderOutlines() {
|
|
|
2815
3053
|
apiPage(`/api/works/${state.work.id}/outlines`).then((result) => result.items),
|
|
2816
3054
|
apiPage(`/api/works/${state.work.id}/foreshadows?status=all${currentChapterId ? `¤tChapterId=${encodeURIComponent(currentChapterId)}` : ""}`).then((result) => result.items)
|
|
2817
3055
|
]);
|
|
3056
|
+
const layout = readModuleLayout();
|
|
2818
3057
|
const unresolved = foreshadows.filter((item) => item.unresolved);
|
|
2819
3058
|
const overdue = unresolved.filter((item) => item.overdue);
|
|
2820
3059
|
const navButton = $("#module-nav [data-module=outlines] .nav-label");
|
|
2821
3060
|
if (navButton) navButton.textContent = unresolved.length ? `大纲与伏笔 · ${unresolved.length}` : "大纲与伏笔";
|
|
2822
|
-
const
|
|
3061
|
+
const foreshadowActions = (item) => `<button data-edit-foreshadow="${esc(item.id)}">编辑伏笔</button><button data-entity-history="foreshadow" data-entity-id="${esc(item.id)}" data-entity-title="${esc(item.title)}">版本历史</button>`;
|
|
3062
|
+
const foreshadowCards = () => `<div class="card-grid foreshadow-grid">${foreshadows.map((item) => `
|
|
2823
3063
|
<article class="record-card foreshadow-card ${item.overdue ? "is-overdue" : ""}">
|
|
2824
3064
|
<small>${esc(item.importance)} · ${esc(item.status)}${item.overdue ? " · 已逾期" : ""}</small>
|
|
2825
3065
|
<h3>${esc(item.title)}</h3><p>${esc(item.description || "暂无说明")}</p>
|
|
2826
3066
|
<div class="foreshadow-links">${item.occurrences.length ? item.occurrences.map((link) => `<span class="pill">${esc({ setup: "埋设", reminder: "提醒", payoff: "回收" }[link.role] ?? link.role)} · ${esc(link.volumeTitle)} / ${esc(link.chapterTitle)}</span>`).join("") : '<span class="pill">尚未关联章节</span>'}</div>
|
|
2827
|
-
<div class="card-actions"
|
|
2828
|
-
</article>`).join("")}</div
|
|
3067
|
+
<div class="card-actions">${foreshadowActions(item)}</div>
|
|
3068
|
+
</article>`).join("")}</div>`;
|
|
3069
|
+
const foreshadowRows = () => `<div class="module-row-list">${foreshadows.map((item) => {
|
|
3070
|
+
const preview = moduleRowPreview(item.description || "暂无说明");
|
|
3071
|
+
const links = item.occurrences.length
|
|
3072
|
+
? item.occurrences.map((link) => `${({ setup: "埋设", reminder: "提醒", payoff: "回收" }[link.role] ?? link.role)} · ${link.volumeTitle} / ${link.chapterTitle}`).join(";")
|
|
3073
|
+
: "尚未关联章节";
|
|
3074
|
+
return `
|
|
3075
|
+
<article class="record-card module-row foreshadow-card ${item.overdue ? "is-overdue" : ""}">
|
|
3076
|
+
<small>${esc(item.importance)} · ${esc(item.status)}${item.overdue ? " · 已逾期" : ""}</small>
|
|
3077
|
+
<h3>${esc(item.title)}</h3>
|
|
3078
|
+
<p class="module-row-preview" title="${esc(`${preview} · ${links}`)}">${esc(preview)} · ${esc(links)}</p>
|
|
3079
|
+
<div class="card-actions">${foreshadowActions(item)}</div>
|
|
3080
|
+
</article>`;
|
|
3081
|
+
}).join("")}</div>`;
|
|
3082
|
+
const foreshadowHtml = foreshadows.length
|
|
3083
|
+
? `${renderModuleLayoutToggle(layout, "伏笔列表样式")}${layout === "rows" ? foreshadowRows() : foreshadowCards()}`
|
|
3084
|
+
: emptyModule("还没有伏笔", "创建伏笔并关联埋设、提醒与回收章节,未回收项会持续显示。\n");
|
|
2829
3085
|
const outlineHtml = outlines.length ? `<div class="outline-list">${outlines.map((item) => `
|
|
2830
3086
|
<article class="outline-row ${item.status === "completed" ? "is-complete" : ""}">
|
|
2831
3087
|
<div><small>${esc(item.volumeTitle)} · ${esc(item.status)}</small><h3>${esc(item.chapterTitle)}</h3></div>
|
|
@@ -2835,6 +3091,7 @@ async function renderOutlines() {
|
|
|
2835
3091
|
<div class="outline-actions">${item.unresolvedForeshadowCount ? `<span>${item.unresolvedForeshadowCount} 个未回收伏笔</span>` : ""}<button data-edit-outline="${esc(item.chapterId)}">编辑</button><button data-entity-history="chapter-outline" data-entity-id="${esc(item.chapterId)}" data-entity-title="${esc(item.chapterTitle)}">版本历史</button></div>
|
|
2836
3092
|
</article>`).join("")}</div>` : emptyModule("还没有章节", "先创建章节,再为每章维护目标、冲突和转折。\n");
|
|
2837
3093
|
$("#module-content").innerHTML = `<div class="outline-summary"><article><strong>${outlines.length}</strong><span>章节规划</span></article><article><strong>${unresolved.length}</strong><span>未回收伏笔</span></article><article class="${overdue.length ? "danger-text" : ""}"><strong>${overdue.length}</strong><span>已逾期</span></article></div><section class="planning-section"><div class="section-title"><div><span class="eyebrow">伏笔追踪</span><h2>尚未回收与历史伏笔</h2></div></div>${foreshadowHtml}</section><section class="planning-section"><div class="section-title"><div><span class="eyebrow">逐章规划</span><h2>章节目标、冲突与转折</h2></div></div>${outlineHtml}</section>`;
|
|
3094
|
+
bindModuleLayoutToggle(renderOutlines);
|
|
2838
3095
|
$("#module-content").querySelectorAll("[data-edit-outline]").forEach((button) => button.addEventListener("click", () => openOutlineDialog(outlines.find((item) => item.chapterId === button.dataset.editOutline))));
|
|
2839
3096
|
$("#module-content").querySelectorAll("[data-edit-foreshadow]").forEach((button) => button.addEventListener("click", () => openForeshadowDialog(foreshadows.find((item) => item.id === button.dataset.editForeshadow))));
|
|
2840
3097
|
bindEntityHistoryButtons(renderOutlines);
|
|
@@ -2897,11 +3154,28 @@ async function renderReviews() {
|
|
|
2897
3154
|
: "";
|
|
2898
3155
|
return `<article class="record-card character-duplicate-review"><small>角色查重 · ${esc(item.severity)} · ${esc(item.status)}</small><h3>${esc(item.title)}</h3><div class="character-duplicate-pair">${sideHtml}</div><p>${esc(item.description)}${item.suggestion ? `\n建议:${esc(item.suggestion)}` : ""}</p>${evidenceHtml ? `<ul class="character-duplicate-evidence">${evidenceHtml}</ul>` : ""}${actions}${item.resolutionNote ? `<p class="review-resolution-note">处理结果:${esc(item.resolutionNote)}</p>` : ""}</article>`;
|
|
2899
3156
|
};
|
|
2900
|
-
|
|
3157
|
+
const layout = readModuleLayout();
|
|
3158
|
+
const reviewCard = (item) => item.itemType === "character-duplicate" ? duplicateCard(item) : `
|
|
2901
3159
|
<article class="record-card"><small>${esc(item.itemType)} · ${esc(item.severity)} · ${esc(item.status)}</small><h3>${esc(item.title)}</h3>
|
|
2902
3160
|
<p>${esc(item.description)}${item.suggestion ? `\n建议:${esc(item.suggestion)}` : ""}</p>
|
|
2903
|
-
${item.status === "pending" && canResolveReview ? `<div class="card-actions"><button data-review-status="fixed" data-review-id="${esc(item.id)}">标为已修复</button><button data-review-status="ignored" data-review-id="${esc(item.id)}">忽略</button></div>` : ""}</article
|
|
3161
|
+
${item.status === "pending" && canResolveReview ? `<div class="card-actions"><button data-review-status="fixed" data-review-id="${esc(item.id)}">标为已修复</button><button data-review-status="ignored" data-review-id="${esc(item.id)}">忽略</button></div>` : ""}</article>`;
|
|
3162
|
+
const reviewRow = (item) => {
|
|
3163
|
+
if (item.itemType === "character-duplicate") {
|
|
3164
|
+
return `<div class="module-row-span">${duplicateCard(item)}</div>`;
|
|
3165
|
+
}
|
|
3166
|
+
const preview = moduleRowPreview(`${item.description || ""}${item.suggestion ? ` 建议:${item.suggestion}` : ""}`);
|
|
3167
|
+
return `
|
|
3168
|
+
<article class="record-card module-row">
|
|
3169
|
+
<small>${esc(item.itemType)} · ${esc(item.severity)} · ${esc(item.status)}</small>
|
|
3170
|
+
<h3>${esc(item.title)}</h3>
|
|
3171
|
+
<p class="module-row-preview" title="${esc(preview)}">${esc(preview)}</p>
|
|
3172
|
+
<div class="card-actions">${item.status === "pending" && canResolveReview ? `<button data-review-status="fixed" data-review-id="${esc(item.id)}">标为已修复</button><button data-review-status="ignored" data-review-id="${esc(item.id)}">忽略</button>` : ""}</div>
|
|
3173
|
+
</article>`;
|
|
3174
|
+
};
|
|
3175
|
+
$("#module-content").innerHTML = reviews.length
|
|
3176
|
+
? `${renderModuleLayoutToggle(layout, "审核列表样式")}${layout === "rows" ? `<div class="module-row-list">${reviews.map(reviewRow).join("")}</div>` : `<div class="card-grid">${reviews.map(reviewCard).join("")}</div>`}`
|
|
2904
3177
|
: emptyModule("没有待审核事项", "候选设定、冲突与低置信度结论会集中显示在这里。");
|
|
3178
|
+
bindModuleLayoutToggle(renderReviews);
|
|
2905
3179
|
$("#module-content").querySelectorAll("[data-review-id]").forEach((button) => button.addEventListener("click", async () => {
|
|
2906
3180
|
await api(`/api/reviews/${button.dataset.reviewId}`, { method: "PATCH", body: { status: button.dataset.reviewStatus } });
|
|
2907
3181
|
await renderReviews();
|
|
@@ -2909,7 +3183,10 @@ async function renderReviews() {
|
|
|
2909
3183
|
$("#module-content").querySelectorAll("[data-merge-review]").forEach((button) => button.addEventListener("click", async () => {
|
|
2910
3184
|
const target = characterById.get(button.dataset.mergeTarget);
|
|
2911
3185
|
const source = characterById.get(button.dataset.mergeSource);
|
|
2912
|
-
if (!target || !source || !
|
|
3186
|
+
if (!target || !source || !(await confirmToast(
|
|
3187
|
+
`确认把“${source.name}”合并到“${target.name}”?来源角色的别名、组织、时间线和关系会迁移到目标角色。`,
|
|
3188
|
+
{ title: "确认合并角色", confirmLabel: "确认合并" }
|
|
3189
|
+
))) return;
|
|
2913
3190
|
button.disabled = true;
|
|
2914
3191
|
try {
|
|
2915
3192
|
await api(`/api/reviews/${button.dataset.mergeReview}/character-resolution`, { method: "POST", body: {
|
|
@@ -2942,7 +3219,7 @@ async function renderReviews() {
|
|
|
2942
3219
|
|
|
2943
3220
|
async function renderTasks() {
|
|
2944
3221
|
const [tasks, settings] = await Promise.all([
|
|
2945
|
-
apiPage(`/api/works/${state.work.id}/tasks`).then((result) => result.items),
|
|
3222
|
+
apiPage(`/api/works/${state.work.id}/tasks?view=summary`).then((result) => result.items),
|
|
2946
3223
|
canReadModule("ai-settings")
|
|
2947
3224
|
? api(`/api/works/${state.work.id}/ai-settings`)
|
|
2948
3225
|
: Promise.resolve({ autoRunEnabled: false, autoRunConcurrency: 2, autoRunBatchLimit: 20 })
|
|
@@ -3013,9 +3290,13 @@ async function renderTasks() {
|
|
|
3013
3290
|
}
|
|
3014
3291
|
});
|
|
3015
3292
|
|
|
3016
|
-
const taskById = new Map(tasks.map((item) => [item.id, item]));
|
|
3017
3293
|
$("#module-content").querySelectorAll("[data-task-detail]").forEach((button) => button.addEventListener("click", () => {
|
|
3018
|
-
|
|
3294
|
+
if (button.disabled) return;
|
|
3295
|
+
button.disabled = true;
|
|
3296
|
+
api(`/api/tasks/${encodeURIComponent(button.dataset.taskDetail)}`)
|
|
3297
|
+
.then((task) => openTaskDetailDialog(task))
|
|
3298
|
+
.catch((error) => toast(error.message, "error"))
|
|
3299
|
+
.finally(() => { button.disabled = false; });
|
|
3019
3300
|
}));
|
|
3020
3301
|
$("#module-content").querySelectorAll("[data-run-task]").forEach((button) => button.addEventListener("click", async () => {
|
|
3021
3302
|
const workId = state.work.id;
|
|
@@ -3241,8 +3522,9 @@ async function renderBookAiSettings() {
|
|
|
3241
3522
|
async function loadModels() {
|
|
3242
3523
|
const workId = state.work?.id;
|
|
3243
3524
|
if (!workId) return;
|
|
3525
|
+
const generation = workScopedUiGeneration;
|
|
3244
3526
|
const models = await api(`/api/works/${workId}/models`);
|
|
3245
|
-
if (state.work?.id !== workId) return;
|
|
3527
|
+
if (state.work?.id !== workId || generation !== workScopedUiGeneration) return;
|
|
3246
3528
|
state.models = models;
|
|
3247
3529
|
loadedAiModelsWorkId = workId;
|
|
3248
3530
|
const select = $("#ai-model");
|
|
@@ -3277,17 +3559,19 @@ let aiContextUsageTimer = null;
|
|
|
3277
3559
|
let aiContextUsageRequest = 0;
|
|
3278
3560
|
|
|
3279
3561
|
function currentAiRequestScope() {
|
|
3280
|
-
if (!state.work
|
|
3562
|
+
if (!state.work) return null;
|
|
3281
3563
|
const taskType = $("#ai-task").value;
|
|
3282
3564
|
const scopeType = $("#ai-scope").value;
|
|
3283
|
-
const
|
|
3284
|
-
|
|
3565
|
+
const requiresChapter = taskType === "polish" || taskType === "continue" || scopeType !== "none";
|
|
3566
|
+
if (requiresChapter && !state.chapter) return null;
|
|
3567
|
+
const selection = state.chapter ? $("#chapter-content").value.slice($("#chapter-content").selectionStart, $("#chapter-content").selectionEnd) : "";
|
|
3568
|
+
const volume = state.chapter ? state.work.volumes.find((item) => item.id === state.chapter.volumeId) : null;
|
|
3285
3569
|
const includeBookSummary = scopeType === "chapter-summary";
|
|
3286
|
-
const scope = taskType === "polish" ? { type: "chapter", chapterId: state.chapter
|
|
3287
|
-
: scopeType === "none" ? { type: "none", ...(taskType === "continue" ? { chapterId: state.chapter.id } : {}) }
|
|
3570
|
+
const scope = taskType === "polish" ? { type: "chapter", chapterId: state.chapter?.id, selection }
|
|
3571
|
+
: scopeType === "none" ? { type: "none", ...(taskType === "continue" && state.chapter ? { chapterId: state.chapter.id } : {}) }
|
|
3288
3572
|
: scopeType === "book" ? { type: "book" }
|
|
3289
3573
|
: scopeType === "volume" ? { type: "volume", volumeId: volume?.id }
|
|
3290
|
-
: { type: "chapter", chapterId: state.chapter
|
|
3574
|
+
: { type: "chapter", chapterId: state.chapter?.id };
|
|
3291
3575
|
Object.assign(scope, buildAiReferenceScope(state.aiReferences));
|
|
3292
3576
|
if (includeBookSummary) scope.includeBookSummary = true;
|
|
3293
3577
|
return { taskType, scope, selection };
|
|
@@ -3383,11 +3667,12 @@ async function refreshAiContextUsage() {
|
|
|
3383
3667
|
async function loadAiReferences() {
|
|
3384
3668
|
const workId = state.work?.id;
|
|
3385
3669
|
if (!workId) return;
|
|
3670
|
+
const generation = workScopedUiGeneration;
|
|
3386
3671
|
const [characters, settings] = await Promise.all([
|
|
3387
3672
|
canReadModule("characters") ? apiAllPages(`/api/works/${workId}/characters`) : Promise.resolve([]),
|
|
3388
3673
|
canReadModule("settings") ? apiAllPages(`/api/works/${workId}/settings`) : Promise.resolve([])
|
|
3389
3674
|
]);
|
|
3390
|
-
if (state.work?.id !== workId) return;
|
|
3675
|
+
if (state.work?.id !== workId || generation !== workScopedUiGeneration) return;
|
|
3391
3676
|
state.characters = characters;
|
|
3392
3677
|
state.settings = settings;
|
|
3393
3678
|
loadedAiReferencesWorkId = workId;
|
|
@@ -3411,6 +3696,7 @@ async function ensureAiReferencesLoaded() {
|
|
|
3411
3696
|
|
|
3412
3697
|
function field(name, label, type = "text", value = "", options = []) {
|
|
3413
3698
|
if (type === "textarea") return `<label>${esc(label)}<textarea name="${esc(name)}">${esc(value)}</textarea></label>`;
|
|
3699
|
+
if (type === "markdown") return `<div class="form-field markdown-editor-field" data-vditor-editor-field><div class="vditor-editor-host" data-vditor-editor data-placeholder="${esc(options.placeholder ?? `在这里编辑${label}`)}" aria-label="Markdown 编辑器"></div><textarea class="hidden" name="${esc(name)}" data-vditor-value maxlength="200000" aria-label="Markdown 原文">${esc(value)}</textarea></div>`;
|
|
3414
3700
|
if (type === "item-list") {
|
|
3415
3701
|
const values = Array.isArray(value) && value.length ? value : [""];
|
|
3416
3702
|
return `<div class="form-field item-list-field"><span>${esc(label)}</span><div class="item-list-rows" data-item-list-rows data-name="${esc(name)}" data-label="${esc(label)}">${values.map((item) => `<div class="item-list-row"><input name="${esc(name)}" value="${esc(item)}" aria-label="${esc(label)}"><button type="button" data-item-list-remove aria-label="删除此条">删除</button></div>`).join("")}</div><button class="item-list-add" type="button" data-item-list-add>添加一条</button></div>`;
|
|
@@ -3452,6 +3738,52 @@ function field(name, label, type = "text", value = "", options = []) {
|
|
|
3452
3738
|
return `<label>${esc(label)}<input name="${esc(name)}" type="${esc(type)}" value="${esc(value)}" ${type === "password" ? 'autocomplete="new-password"' : ""} ${type === "number" ? 'step="any"' : ""}></label>`;
|
|
3453
3739
|
}
|
|
3454
3740
|
|
|
3741
|
+
function knowledgeSectionTitleFromMarkdown(content, index) {
|
|
3742
|
+
return String(content).match(/^\s{0,3}#{1,6}\s+(.+?)\s*#*\s*$/mu)?.[1]?.trim() || `设定 ${index + 1}`;
|
|
3743
|
+
}
|
|
3744
|
+
|
|
3745
|
+
function normalizeKnowledgeEditorSections(item) {
|
|
3746
|
+
const sections = Array.isArray(item?.settingsSections)
|
|
3747
|
+
? item.settingsSections.map((section, index) => ({
|
|
3748
|
+
title: String(section.title ?? "").trim() || knowledgeSectionTitleFromMarkdown(section.contentMarkdown ?? "", index),
|
|
3749
|
+
contentMarkdown: String(section.contentMarkdown ?? section.content ?? ""),
|
|
3750
|
+
summary: String(section.summary ?? ""),
|
|
3751
|
+
sortOrder: Number.isFinite(Number(section.sortOrder)) ? Number(section.sortOrder) : index
|
|
3752
|
+
}))
|
|
3753
|
+
: [];
|
|
3754
|
+
if (sections.length > 0) return sections;
|
|
3755
|
+
const legacy = String(item?.settingsMarkdown ?? (Array.isArray(item?.settings) ? item.settings.join("\n\n") : ""));
|
|
3756
|
+
return legacy.trim() ? [{ title: knowledgeSectionTitleFromMarkdown(legacy, 0), contentMarkdown: legacy, summary: "", sortOrder: 0 }] : [];
|
|
3757
|
+
}
|
|
3758
|
+
|
|
3759
|
+
function knowledgeSectionPreviewText(section) {
|
|
3760
|
+
const content = String(section.contentMarkdown ?? "").replace(/!\[[^\]]*\]\([^)]*\)/gu, "图片").replace(/[`*_>#-]/gu, "").replace(/\s+/gu, " ").trim();
|
|
3761
|
+
return content ? `${content.slice(0, 180)}${content.length > 180 ? "…" : ""}` : "暂无正文内容";
|
|
3762
|
+
}
|
|
3763
|
+
|
|
3764
|
+
function renderKnowledgeMarkdownSections() {
|
|
3765
|
+
const host = $("#knowledge-markdown-sections");
|
|
3766
|
+
if (!host) return;
|
|
3767
|
+
const label = knowledgeEditorKind === "race" ? "种族" : "组织";
|
|
3768
|
+
const canEdit = canEditModule(knowledgeEditorKind === "race" ? "races" : "organizations");
|
|
3769
|
+
const sections = Array.isArray(knowledgeEditorSections) ? knowledgeEditorSections : [];
|
|
3770
|
+
host.innerHTML = `<div class="knowledge-markdown-list-toolbar"><div><b>${label} Markdown 设定</b><span>将每条设定单独保存为章节,需要编辑时打开大编辑器。</span></div>${canEdit ? '<button type="button" class="ghost-button" data-knowledge-section-create>新建设定</button>' : ""}</div>${sections.length ? `<div class="knowledge-markdown-list">${sections.map((section, index) => `<article class="knowledge-markdown-section" data-knowledge-section-index="${index}"><header><div><span>设定 ${index + 1}</span><h4>${esc(section.title || `未命名设定 ${index + 1}`)}</h4>${section.summary ? `<p>${esc(section.summary)}</p>` : ""}</div><div>${canEdit ? `<button type="button" data-knowledge-section-edit="${index}">编辑</button><button type="button" data-knowledge-section-delete="${index}">删除</button>` : ""}</div></header><p class="knowledge-section-card-preview">${esc(knowledgeSectionPreviewText(section))}</p></article>`).join("")}</div>` : '<p class="knowledge-markdown-empty">还没有 Markdown 设定,点击“新建设定”开始记录。</p>'}`;
|
|
3771
|
+
host.querySelector("[data-knowledge-section-create]")?.addEventListener("click", () => void openKnowledgeSectionEditor());
|
|
3772
|
+
host.querySelectorAll("[data-knowledge-section-edit]").forEach((button) => button.addEventListener("click", () => void openKnowledgeSectionEditor(Number(button.dataset.knowledgeSectionEdit))));
|
|
3773
|
+
host.querySelectorAll("[data-knowledge-section-delete]").forEach((button) => button.addEventListener("click", async () => {
|
|
3774
|
+
const index = Number(button.dataset.knowledgeSectionDelete);
|
|
3775
|
+
const section = knowledgeEditorSections[index];
|
|
3776
|
+
if (!section || !(await confirmToast(
|
|
3777
|
+
`确定删除“${section.title || `设定 ${index + 1}`}”吗?`,
|
|
3778
|
+
{ title: "删除设定", confirmLabel: "确认删除" }
|
|
3779
|
+
))) return;
|
|
3780
|
+
knowledgeEditorSections.splice(index, 1);
|
|
3781
|
+
knowledgeEditorSections.forEach((item, sortOrder) => { item.sortOrder = sortOrder; });
|
|
3782
|
+
markEntityEditorDirty();
|
|
3783
|
+
renderKnowledgeMarkdownSections();
|
|
3784
|
+
}));
|
|
3785
|
+
}
|
|
3786
|
+
|
|
3455
3787
|
function bindDynamicListControls(container) {
|
|
3456
3788
|
container.querySelectorAll("[data-item-list-add]").forEach((button) => button.addEventListener("click", () => {
|
|
3457
3789
|
const rows = button.previousElementSibling;
|
|
@@ -3534,6 +3866,7 @@ function commitRelationshipKeywordInputs(container) {
|
|
|
3534
3866
|
}
|
|
3535
3867
|
|
|
3536
3868
|
function openDialog(title, fields, onSubmit, eyebrow = "新增", options = {}) {
|
|
3869
|
+
void discardPendingMarkdownAttachments();
|
|
3537
3870
|
$("#dialog-title").textContent = title;
|
|
3538
3871
|
$("#dialog-eyebrow").textContent = eyebrow;
|
|
3539
3872
|
$("#dialog-fields").innerHTML = fields;
|
|
@@ -3541,15 +3874,21 @@ function openDialog(title, fields, onSubmit, eyebrow = "新增", options = {}) {
|
|
|
3541
3874
|
$("#form-dialog").classList.toggle("wide-dialog", Boolean(options.wide));
|
|
3542
3875
|
bindDynamicListControls($("#dialog-fields"));
|
|
3543
3876
|
bindRelationshipKeywordControls($("#dialog-fields"));
|
|
3877
|
+
bindVditorEditors($("#dialog-fields"));
|
|
3544
3878
|
const form = $("#dynamic-form");
|
|
3545
3879
|
form.onsubmit = async (event) => {
|
|
3546
|
-
if (event.submitter?.value === "cancel")
|
|
3880
|
+
if (event.submitter?.value === "cancel") {
|
|
3881
|
+
void discardPendingMarkdownAttachments();
|
|
3882
|
+
return;
|
|
3883
|
+
}
|
|
3547
3884
|
event.preventDefault();
|
|
3548
3885
|
const submit = $("#dialog-submit");
|
|
3549
3886
|
submit.disabled = true;
|
|
3550
3887
|
try {
|
|
3551
3888
|
commitRelationshipKeywordInputs(form);
|
|
3552
3889
|
await onSubmit(new FormData(form));
|
|
3890
|
+
const markdown = [...form.querySelectorAll("[data-vditor-value]")].map((textarea) => textarea.value).join("\n\n");
|
|
3891
|
+
await cleanupPendingMarkdownAttachments(markdown);
|
|
3553
3892
|
$("#form-dialog").close();
|
|
3554
3893
|
} catch (error) {
|
|
3555
3894
|
toast(error.message, "error");
|
|
@@ -3621,12 +3960,21 @@ function bindWorkCoverControls(work) {
|
|
|
3621
3960
|
function openWorkSettingsDialog(work) {
|
|
3622
3961
|
if (!work) return;
|
|
3623
3962
|
const canManageAccess = ["admin", "owner"].includes(String(work.accessRole));
|
|
3963
|
+
const isCurrentWork = state.work?.id === work.id;
|
|
3964
|
+
const canOpenImportHistory = isCurrentWork && canReplaceProse(work);
|
|
3965
|
+
const importHistoryAction = isCurrentWork
|
|
3966
|
+
? canOpenImportHistory ? "查看导入历史" : "需要完整编辑权限"
|
|
3967
|
+
: "打开作品后可用";
|
|
3624
3968
|
const accessField = `<section class="work-access-field" aria-labelledby="work-access-title">
|
|
3625
3969
|
<div><strong id="work-access-title">成员权限</strong><small>选择成员后,可为每个作品模块单独设置无权限、只读或可编辑。</small><div class="work-access-options" aria-label="成员权限配置方式"><span>按成员配置</span><span>按模块授权</span><span>读写分离</span></div></div>
|
|
3626
3970
|
${canManageAccess ? '<button id="work-access-manage" class="ghost-button" type="button">配置成员权限</button>' : '<small>仅作品创建者或系统管理员可以调整访问权限。</small>'}
|
|
3627
3971
|
</section>`;
|
|
3972
|
+
const importHistoryField = `<section class="work-access-field" aria-labelledby="import-history-settings-title">
|
|
3973
|
+
<div><strong id="import-history-settings-title">正文导入历史</strong><small>查看导入前快照并恢复被覆盖的分卷、章节标题和正文;大纲、伏笔等章节关联资料不在快照中。</small></div>
|
|
3974
|
+
<button id="import-history-button" class="ghost-button" type="button" aria-controls="import-history-dialog" aria-haspopup="dialog" ${canOpenImportHistory ? "" : "disabled"}>${importHistoryAction}</button>
|
|
3975
|
+
</section>`;
|
|
3628
3976
|
openDialog("作品信息",
|
|
3629
|
-
workCoverFieldHtml(work) + field("title", "作品名称", "text", work.title) + field("author", "作者", "text", work.author) + field("description", "简介", "textarea", work.description) + accessField,
|
|
3977
|
+
workCoverFieldHtml(work) + field("title", "作品名称", "text", work.title) + field("author", "作者", "text", work.author) + field("description", "简介", "textarea", work.description) + accessField + importHistoryField,
|
|
3630
3978
|
async (form) => {
|
|
3631
3979
|
await api(`/api/works/${work.id}`, { method: "PATCH", body: { title: form.get("title"), author: form.get("author"), description: form.get("description") } });
|
|
3632
3980
|
state.works = (await apiPage("/api/works")).items;
|
|
@@ -3644,13 +3992,17 @@ function openWorkSettingsDialog(work) {
|
|
|
3644
3992
|
toast("作品信息已保存");
|
|
3645
3993
|
}, "作品设置");
|
|
3646
3994
|
bindWorkCoverControls(work);
|
|
3995
|
+
$("#import-history-button")?.addEventListener("click", () => {
|
|
3996
|
+
$("#form-dialog").close();
|
|
3997
|
+
void openImportHistory();
|
|
3998
|
+
});
|
|
3647
3999
|
$("#work-access-manage")?.addEventListener("click", () => {
|
|
3648
4000
|
$("#form-dialog").close();
|
|
3649
4001
|
openMembersDialog(work);
|
|
3650
4002
|
});
|
|
3651
4003
|
}
|
|
3652
4004
|
|
|
3653
|
-
async function openChapterDialog() {
|
|
4005
|
+
async function openChapterDialog(volumeId = null) {
|
|
3654
4006
|
if (!state.work) return openWorkDialog();
|
|
3655
4007
|
if (!canEditProse()) return toast("当前权限只能编辑设定资料,正文为只读", "error");
|
|
3656
4008
|
if (!state.work.volumes.length) {
|
|
@@ -3658,7 +4010,8 @@ async function openChapterDialog() {
|
|
|
3658
4010
|
state.work = await api(`/api/works/${state.work.id}`);
|
|
3659
4011
|
renderTree();
|
|
3660
4012
|
}
|
|
3661
|
-
|
|
4013
|
+
const selectedVolumeId = state.work.volumes.some((volume) => volume.id === volumeId) ? volumeId : state.work.volumes[0].id;
|
|
4014
|
+
openDialog("新建章节", field("title", "章节标题") + field("volumeId", "所属卷", "select", selectedVolumeId, state.work.volumes.map((volume) => [volume.id, volume.title])) + field("chapterType", "章节类型", "select", "正文", chapterTypes.map((value) => [value, value])), async (form) => {
|
|
3662
4015
|
const chapter = await api(`/api/works/${state.work.id}/chapters`, { method: "POST", body: { title: form.get("title"), volumeId: form.get("volumeId"), chapterType: form.get("chapterType"), content: "" } });
|
|
3663
4016
|
state.work = await api(`/api/works/${state.work.id}`);
|
|
3664
4017
|
await selectChapter(chapter.id);
|
|
@@ -3673,13 +4026,13 @@ function openVolumeDialog(item) {
|
|
|
3673
4026
|
field("title", "分卷名称", "text", item?.title) +
|
|
3674
4027
|
field("kind", "分卷类型", "select", item?.kind ?? "main", kindOptions) +
|
|
3675
4028
|
field("description", "分卷简介", "textarea", item?.description) +
|
|
3676
|
-
field("keywords", "
|
|
4029
|
+
field("keywords", "分卷关键词", "keyword-chips", item?.keywords ?? []),
|
|
3677
4030
|
async (form) => {
|
|
3678
4031
|
const body = {
|
|
3679
4032
|
title: form.get("title"),
|
|
3680
4033
|
kind: form.get("kind"),
|
|
3681
4034
|
description: form.get("description"),
|
|
3682
|
-
keywords: form.getAll("keywords").map(
|
|
4035
|
+
keywords: uniqueRelationshipKeywords(form.getAll("keywords").map(String))
|
|
3683
4036
|
};
|
|
3684
4037
|
await api(item ? `/api/volumes/${item.id}` : `/api/works/${state.work.id}/volumes`, { method: item ? "PATCH" : "POST", body });
|
|
3685
4038
|
state.work = await api(`/api/works/${state.work.id}`);
|
|
@@ -3689,9 +4042,10 @@ function openVolumeDialog(item) {
|
|
|
3689
4042
|
}
|
|
3690
4043
|
|
|
3691
4044
|
function openSettingEditor(item = null) {
|
|
4045
|
+
destroyVditorEditor(settingEditorVditor);
|
|
4046
|
+
settingEditorVditor = null;
|
|
3692
4047
|
settingEditorItem = item;
|
|
3693
4048
|
$("#setting-editor-eyebrow").textContent = item ? "人工修正" : "作者事实";
|
|
3694
|
-
$("#setting-editor-title").textContent = item ? `编辑“${item.title}”` : "新建设定";
|
|
3695
4049
|
$("#setting-editor-name").value = item?.title ?? "";
|
|
3696
4050
|
$("#setting-editor-category").value = item?.category ?? "世界规则";
|
|
3697
4051
|
$("#setting-editor-locked").checked = Boolean(item?.locked);
|
|
@@ -3710,16 +4064,29 @@ function openSettingEditor(item = null) {
|
|
|
3710
4064
|
const submit = $("#setting-editor-submit");
|
|
3711
4065
|
submit.disabled = true;
|
|
3712
4066
|
try {
|
|
4067
|
+
const title = String(form.get("title") ?? "").trim();
|
|
4068
|
+
if (!title) {
|
|
4069
|
+
toast("请填写设定标题", "error");
|
|
4070
|
+
$("#setting-editor-name").focus();
|
|
4071
|
+
return;
|
|
4072
|
+
}
|
|
4073
|
+
const content = String(form.get("content") ?? "");
|
|
4074
|
+
if (!content.trim()) {
|
|
4075
|
+
toast("请填写设定正文", "error");
|
|
4076
|
+
$("#setting-editor-body").focus();
|
|
4077
|
+
return;
|
|
4078
|
+
}
|
|
3713
4079
|
const locked = form.get("locked") === "on";
|
|
3714
4080
|
const body = {
|
|
3715
|
-
title
|
|
4081
|
+
title,
|
|
3716
4082
|
category: String(form.get("category") ?? "世界规则"),
|
|
3717
|
-
content
|
|
4083
|
+
content,
|
|
3718
4084
|
locked,
|
|
3719
4085
|
status: locked ? "confirmed" : (item?.status ?? "draft"),
|
|
3720
4086
|
...(item ? { changeNote: String(form.get("changeNote") ?? "").trim() } : {})
|
|
3721
4087
|
};
|
|
3722
4088
|
await api(item ? `/api/settings/${item.id}` : `/api/works/${state.work.id}/settings`, { method: item ? "PATCH" : "POST", body });
|
|
4089
|
+
await cleanupPendingMarkdownAttachments(body.content);
|
|
3723
4090
|
entityEditorDirty = false;
|
|
3724
4091
|
await loadAiReferences();
|
|
3725
4092
|
await closeEntityEditor({ force: true });
|
|
@@ -3731,6 +4098,10 @@ function openSettingEditor(item = null) {
|
|
|
3731
4098
|
}
|
|
3732
4099
|
};
|
|
3733
4100
|
showEntityEditorPage("setting");
|
|
4101
|
+
settingEditorVditor = createVditorEditor($("#setting-editor-markdown"), item?.content ?? "", {
|
|
4102
|
+
onInput: (markdown) => { $("#setting-editor-body").value = markdown; markEntityEditorDirty(); },
|
|
4103
|
+
readOnly: viewOnly
|
|
4104
|
+
});
|
|
3734
4105
|
$("#setting-editor-name").focus();
|
|
3735
4106
|
}
|
|
3736
4107
|
|
|
@@ -3856,22 +4227,165 @@ async function discardPendingCharacterAttachments() {
|
|
|
3856
4227
|
}));
|
|
3857
4228
|
}
|
|
3858
4229
|
|
|
3859
|
-
function
|
|
3860
|
-
|
|
3861
|
-
|
|
3862
|
-
|
|
3863
|
-
|
|
3864
|
-
const content = $("#character-section-markdown");
|
|
3865
|
-
if (preview && content) preview.innerHTML = renderMarkdown(content.value) || '<p class="character-markdown-empty">预览区域暂无内容。</p>';
|
|
3866
|
-
}, 260);
|
|
4230
|
+
async function discardPendingMarkdownAttachments() {
|
|
4231
|
+
const pending = markdownEditorPendingAttachments.splice(0);
|
|
4232
|
+
await Promise.all(pending.map(async (attachmentId) => {
|
|
4233
|
+
try { await api(`/api/attachments/${attachmentId}`, { method: "DELETE" }); } catch { /* 已被引用的附件由正常引用生命周期管理。 */ }
|
|
4234
|
+
}));
|
|
3867
4235
|
}
|
|
3868
4236
|
|
|
3869
|
-
async function
|
|
3870
|
-
|
|
3871
|
-
|
|
3872
|
-
|
|
3873
|
-
|
|
4237
|
+
async function cleanupPendingMarkdownAttachments(contentMarkdown) {
|
|
4238
|
+
const referenced = new Set([...String(contentMarkdown).matchAll(/attachment:\/\/([A-Za-z0-9_-]{1,300})/gu)].map((match) => String(match[1])));
|
|
4239
|
+
const pending = markdownEditorPendingAttachments.splice(0);
|
|
4240
|
+
await Promise.all(pending.filter((attachmentId) => !referenced.has(attachmentId)).map((attachmentId) => api(`/api/attachments/${attachmentId}`, { method: "DELETE" }).catch(() => null)));
|
|
4241
|
+
}
|
|
4242
|
+
|
|
4243
|
+
function markdownImageLabel(file, fallback = "图片附件") {
|
|
4244
|
+
return String(file?.name ?? "").replace(/[\[\]\r\n]/gu, "").trim() || fallback;
|
|
4245
|
+
}
|
|
4246
|
+
|
|
4247
|
+
async function uploadMarkdownAttachment(file) {
|
|
4248
|
+
const body = new FormData();
|
|
4249
|
+
body.append("file", file);
|
|
4250
|
+
const attachment = await api(`/api/works/${state.work.id}/attachments`, { method: "POST", body });
|
|
4251
|
+
if (!attachment.deduplicated) markdownEditorPendingAttachments.push(String(attachment.id));
|
|
4252
|
+
return { attachment, imageLabel: markdownImageLabel(file) };
|
|
4253
|
+
}
|
|
4254
|
+
|
|
4255
|
+
function createVditorUploadHandler(uploadAttachment, getEditor) {
|
|
4256
|
+
return async (files) => {
|
|
4257
|
+
const selection = window.getSelection();
|
|
4258
|
+
const range = selection?.rangeCount ? selection.getRangeAt(0).cloneRange() : null;
|
|
4259
|
+
const insertions = [];
|
|
4260
|
+
for (const file of files) {
|
|
4261
|
+
try {
|
|
4262
|
+
const { attachment, imageLabel } = await uploadAttachment(file);
|
|
4263
|
+
insertions.push(``);
|
|
4264
|
+
} catch (error) {
|
|
4265
|
+
toast(error.message, "error");
|
|
4266
|
+
}
|
|
4267
|
+
}
|
|
4268
|
+
const editor = getEditor();
|
|
4269
|
+
if (editor && insertions.length > 0) {
|
|
4270
|
+
if (range && editor.vditor?.element?.contains(range.startContainer)) {
|
|
4271
|
+
selection?.removeAllRanges();
|
|
4272
|
+
selection?.addRange(range);
|
|
4273
|
+
}
|
|
4274
|
+
editor.insertMD(insertions.join("\n\n"));
|
|
4275
|
+
normalizeVditorAttachmentImages(editor);
|
|
4276
|
+
}
|
|
4277
|
+
return null;
|
|
4278
|
+
};
|
|
4279
|
+
}
|
|
4280
|
+
|
|
4281
|
+
function createVditorEditor(host, value, { onInput = () => {}, uploadAttachment = uploadMarkdownAttachment, placeholder = "", readOnly = false } = {}) {
|
|
4282
|
+
if (!window.Vditor) {
|
|
4283
|
+
toast("Markdown 编辑器资源加载失败,请刷新页面后重试", "error");
|
|
4284
|
+
return null;
|
|
3874
4285
|
}
|
|
4286
|
+
ensureVditorIconScript();
|
|
4287
|
+
let editor = null;
|
|
4288
|
+
editor = new window.Vditor(host, {
|
|
4289
|
+
cdn: "/vendor/vditor",
|
|
4290
|
+
lang: "zh_CN",
|
|
4291
|
+
theme: currentColorTheme() === "dark" ? "dark" : "classic",
|
|
4292
|
+
mode: "ir",
|
|
4293
|
+
value: String(value ?? ""),
|
|
4294
|
+
height: "100%",
|
|
4295
|
+
minHeight: 260,
|
|
4296
|
+
placeholder,
|
|
4297
|
+
preview: { transform: transformVditorPreview },
|
|
4298
|
+
cache: { enable: false },
|
|
4299
|
+
toolbar: ["headings", "bold", "italic", "strike", "|", "line", "quote", "list", "ordered-list", "check", "|", "code", "inline-code", "link", "table", "upload", "|", "undo", "redo", "edit-mode", "fullscreen"],
|
|
4300
|
+
upload: {
|
|
4301
|
+
accept: "image/*",
|
|
4302
|
+
max: 10 * 1024 * 1024,
|
|
4303
|
+
multiple: true,
|
|
4304
|
+
handler: createVditorUploadHandler(uploadAttachment, () => editor)
|
|
4305
|
+
},
|
|
4306
|
+
input: (markdown) => {
|
|
4307
|
+
normalizeVditorAttachmentImages(editor);
|
|
4308
|
+
onInput(markdown);
|
|
4309
|
+
},
|
|
4310
|
+
after: () => {
|
|
4311
|
+
normalizeVditorAttachmentImages(editor);
|
|
4312
|
+
if (readOnly) editor?.disabled();
|
|
4313
|
+
}
|
|
4314
|
+
});
|
|
4315
|
+
const attachmentObserver = new MutationObserver(() => normalizeVditorAttachmentImages(editor));
|
|
4316
|
+
attachmentObserver.observe(host, { subtree: true, childList: true, attributes: true, attributeFilter: ["src"] });
|
|
4317
|
+
editor.__attachmentObserver = attachmentObserver;
|
|
4318
|
+
host.__vditor = editor;
|
|
4319
|
+
if (readOnly) editor.disabled();
|
|
4320
|
+
return editor;
|
|
4321
|
+
}
|
|
4322
|
+
|
|
4323
|
+
function transformVditorPreview(html) {
|
|
4324
|
+
return String(html ?? "").replace(/(<img\b[^>]*\bsrc\s*=\s*)(["'])attachment:\/\/([A-Za-z0-9_-]{1,300})\2/giu, (_match, prefix, quote, attachmentId) => `${prefix}${quote}/api/attachments/${encodeURIComponent(attachmentId)}/content${quote}`);
|
|
4325
|
+
}
|
|
4326
|
+
|
|
4327
|
+
function normalizeVditorAttachmentImages(editor) {
|
|
4328
|
+
const root = editor?.vditor?.element;
|
|
4329
|
+
if (!root) return;
|
|
4330
|
+
root.querySelectorAll('img[src^="attachment://"]').forEach((image) => {
|
|
4331
|
+
const target = image.getAttribute("src")?.trim() ?? "";
|
|
4332
|
+
const attachment = target.match(/^attachment:\/\/([A-Za-z0-9_-]{1,300})$/u);
|
|
4333
|
+
if (attachment) image.setAttribute("src", `/api/attachments/${encodeURIComponent(attachment[1])}/content`);
|
|
4334
|
+
});
|
|
4335
|
+
}
|
|
4336
|
+
|
|
4337
|
+
function ensureVditorIconScript() {
|
|
4338
|
+
if (document.getElementById("vditorIconScript")) return;
|
|
4339
|
+
const script = document.createElement("script");
|
|
4340
|
+
script.id = "vditorIconScript";
|
|
4341
|
+
script.src = "/vendor/vditor/dist/js/icons/ant.js?v=3.11.2";
|
|
4342
|
+
document.body.appendChild(script);
|
|
4343
|
+
}
|
|
4344
|
+
|
|
4345
|
+
function destroyVditorEditor(editor) {
|
|
4346
|
+
if (!editor) return;
|
|
4347
|
+
editor.__attachmentObserver?.disconnect();
|
|
4348
|
+
const host = editor.vditor?.element;
|
|
4349
|
+
editor.destroy();
|
|
4350
|
+
if (host) delete host.__vditor;
|
|
4351
|
+
}
|
|
4352
|
+
|
|
4353
|
+
function bindVditorEditors(container) {
|
|
4354
|
+
container.querySelectorAll("[data-vditor-editor]").forEach((host) => {
|
|
4355
|
+
const valueField = host.parentElement?.querySelector("[data-vditor-value]");
|
|
4356
|
+
const editor = createVditorEditor(host, valueField?.value ?? "", {
|
|
4357
|
+
onInput: (markdown) => {
|
|
4358
|
+
if (valueField) valueField.value = markdown;
|
|
4359
|
+
markEntityEditorDirty();
|
|
4360
|
+
},
|
|
4361
|
+
placeholder: "",
|
|
4362
|
+
readOnly: Boolean(valueField?.readOnly)
|
|
4363
|
+
});
|
|
4364
|
+
});
|
|
4365
|
+
}
|
|
4366
|
+
|
|
4367
|
+
function characterSectionImageLabel(file, fallback = "图片附件") {
|
|
4368
|
+
return String(file?.name ?? "").replace(/[\[\]\r\n]/gu, "").trim() || fallback;
|
|
4369
|
+
}
|
|
4370
|
+
|
|
4371
|
+
async function uploadCharacterSectionAttachment(file) {
|
|
4372
|
+
const body = new FormData();
|
|
4373
|
+
body.append("file", file);
|
|
4374
|
+
const attachment = await api(`/api/works/${state.work.id}/attachments`, { method: "POST", body });
|
|
4375
|
+
if (!attachment.deduplicated) characterSectionPendingAttachments.push(String(attachment.id));
|
|
4376
|
+
return {
|
|
4377
|
+
attachment,
|
|
4378
|
+
imageLabel: characterSectionImageLabel(file)
|
|
4379
|
+
};
|
|
4380
|
+
}
|
|
4381
|
+
|
|
4382
|
+
async function closeCharacterSectionEditor({ force = false } = {}) {
|
|
4383
|
+
if (!force && characterSectionEditorDirty && !(await confirmToast(
|
|
4384
|
+
"当前 Markdown 章节有未保存修改,返回人物档案将丢弃这些修改。是否继续?",
|
|
4385
|
+
{ title: "放弃未保存修改", confirmLabel: "放弃并继续", cancelLabel: "继续编辑" }
|
|
4386
|
+
))) return false;
|
|
4387
|
+
destroyVditorEditor(characterSectionVditor);
|
|
4388
|
+
characterSectionVditor = null;
|
|
3875
4389
|
await discardPendingCharacterAttachments();
|
|
3876
4390
|
characterSectionEditorDirty = false;
|
|
3877
4391
|
$("#character-section-editor-view").classList.add("hidden");
|
|
@@ -3881,6 +4395,82 @@ async function closeCharacterSectionEditor({ force = false } = {}) {
|
|
|
3881
4395
|
return true;
|
|
3882
4396
|
}
|
|
3883
4397
|
|
|
4398
|
+
function knowledgeSectionEditorHtml(section = null) {
|
|
4399
|
+
const label = knowledgeEditorKind === "race" ? "种族" : "组织";
|
|
4400
|
+
return `<div class="character-section-editor-shell knowledge-section-editor-shell">
|
|
4401
|
+
<header class="character-section-editor-header">
|
|
4402
|
+
<div><span class="eyebrow">${label} Markdown 设定</span><h2 id="knowledge-section-editor-title">${section ? `编辑“${esc(section.title)}”` : "新建设定"}</h2></div>
|
|
4403
|
+
<button class="entity-editor-back" type="button" data-knowledge-section-edit-close>返回设定列表</button>
|
|
4404
|
+
</header>
|
|
4405
|
+
<section class="character-markdown-editor" aria-label="${section ? "编辑" : "新建"}${label} Markdown 设定">
|
|
4406
|
+
<div class="character-markdown-editor-meta">
|
|
4407
|
+
<label>设定标题<input id="knowledge-section-title" maxlength="200" value="${esc(section?.title ?? "")}" placeholder="例如:组织章程、种族特征" required></label>
|
|
4408
|
+
</div>
|
|
4409
|
+
<div id="knowledge-section-markdown" class="vditor-editor-host" data-vditor-editor aria-label="Markdown 编辑器"></div>
|
|
4410
|
+
<div class="character-markdown-editor-footer">
|
|
4411
|
+
<div class="character-markdown-editor-actions"><button type="button" data-knowledge-section-edit-cancel>取消</button><button type="button" class="primary-button" data-knowledge-section-edit-save>${section ? "保存设定" : "添加设定"}</button></div>
|
|
4412
|
+
</div>
|
|
4413
|
+
</section>
|
|
4414
|
+
</div>`;
|
|
4415
|
+
}
|
|
4416
|
+
|
|
4417
|
+
async function closeKnowledgeSectionEditor({ force = false } = {}) {
|
|
4418
|
+
if (!force && knowledgeSectionEditorDirty && !(await confirmToast(
|
|
4419
|
+
"当前 Markdown 设定有未保存修改,返回设定列表将丢弃这些修改。是否继续?",
|
|
4420
|
+
{ title: "放弃未保存修改", confirmLabel: "放弃并继续", cancelLabel: "继续编辑" }
|
|
4421
|
+
))) return false;
|
|
4422
|
+
destroyVditorEditor(knowledgeSectionVditor);
|
|
4423
|
+
knowledgeSectionVditor = null;
|
|
4424
|
+
knowledgeSectionEditorDirty = false;
|
|
4425
|
+
knowledgeSectionEditorIndex = null;
|
|
4426
|
+
$("#knowledge-section-editor-view").classList.add("hidden");
|
|
4427
|
+
$("#knowledge-editor-form").classList.remove("hidden");
|
|
4428
|
+
$("#knowledge-section-editor-host").innerHTML = "";
|
|
4429
|
+
renderKnowledgeMarkdownSections();
|
|
4430
|
+
replacePageRoute(currentPageRoute());
|
|
4431
|
+
return true;
|
|
4432
|
+
}
|
|
4433
|
+
|
|
4434
|
+
async function openKnowledgeSectionEditor(index = null) {
|
|
4435
|
+
if (!canEditModule(knowledgeEditorKind === "race" ? "races" : "organizations")) return;
|
|
4436
|
+
destroyVditorEditor(knowledgeSectionVditor);
|
|
4437
|
+
knowledgeSectionVditor = null;
|
|
4438
|
+
const section = Number.isInteger(index) ? knowledgeEditorSections[index] : null;
|
|
4439
|
+
if (Number.isInteger(index) && !section) return;
|
|
4440
|
+
knowledgeSectionEditorIndex = Number.isInteger(index) ? index : null;
|
|
4441
|
+
knowledgeSectionEditorDirty = false;
|
|
4442
|
+
const host = $("#knowledge-section-editor-host");
|
|
4443
|
+
host.innerHTML = knowledgeSectionEditorHtml(section);
|
|
4444
|
+
$("#knowledge-editor-form").classList.add("hidden");
|
|
4445
|
+
$("#knowledge-section-editor-view").classList.remove("hidden");
|
|
4446
|
+
const titleInput = $("#knowledge-section-title");
|
|
4447
|
+
host.querySelectorAll("input, textarea").forEach((control) => control.addEventListener("input", () => { knowledgeSectionEditorDirty = true; }));
|
|
4448
|
+
knowledgeSectionVditor = createVditorEditor($("#knowledge-section-markdown"), section?.contentMarkdown ?? "", {
|
|
4449
|
+
onInput: () => { knowledgeSectionEditorDirty = true; }
|
|
4450
|
+
});
|
|
4451
|
+
host.querySelector("[data-knowledge-section-edit-close]").addEventListener("click", () => void closeKnowledgeSectionEditor());
|
|
4452
|
+
host.querySelector("[data-knowledge-section-edit-cancel]").addEventListener("click", () => void closeKnowledgeSectionEditor());
|
|
4453
|
+
host.querySelector("[data-knowledge-section-edit-save]").addEventListener("click", async (event) => {
|
|
4454
|
+
const button = event.currentTarget;
|
|
4455
|
+
const wasEditing = knowledgeSectionEditorIndex !== null;
|
|
4456
|
+
const title = titleInput.value.trim();
|
|
4457
|
+
if (!title) {
|
|
4458
|
+
toast("请填写设定标题", "error");
|
|
4459
|
+
titleInput.focus();
|
|
4460
|
+
return;
|
|
4461
|
+
}
|
|
4462
|
+
button.disabled = true;
|
|
4463
|
+
const nextSection = { title, contentMarkdown: knowledgeSectionVditor?.getValue() ?? "", summary: String(section?.summary ?? ""), sortOrder: knowledgeSectionEditorIndex ?? knowledgeEditorSections.length };
|
|
4464
|
+
if (knowledgeSectionEditorIndex === null) knowledgeEditorSections.push(nextSection);
|
|
4465
|
+
else knowledgeEditorSections[knowledgeSectionEditorIndex] = nextSection;
|
|
4466
|
+
knowledgeEditorSections.forEach((item, sortOrder) => { item.sortOrder = sortOrder; });
|
|
4467
|
+
markEntityEditorDirty();
|
|
4468
|
+
await closeKnowledgeSectionEditor({ force: true });
|
|
4469
|
+
toast(wasEditing ? "设定已更新" : "设定已添加");
|
|
4470
|
+
});
|
|
4471
|
+
titleInput.focus();
|
|
4472
|
+
}
|
|
4473
|
+
|
|
3884
4474
|
function characterSectionEditorHtml(section = null) {
|
|
3885
4475
|
const options = Object.entries(characterSectionTypeLabels).map(([value, label]) => `<option value="${value}" ${section?.sectionType === value ? "selected" : ""}>${esc(label)}</option>`).join("");
|
|
3886
4476
|
return `<div class="character-section-editor-shell">
|
|
@@ -3894,15 +4484,7 @@ function characterSectionEditorHtml(section = null) {
|
|
|
3894
4484
|
<label>章节标题<input id="character-section-title" maxlength="200" value="${esc(section?.title ?? "")}" placeholder="例如:背景故事" required></label>
|
|
3895
4485
|
<label class="character-markdown-summary-field">章节摘要<textarea id="character-section-summary" maxlength="20000" placeholder="用于角色列表和 AI 快速定位,不会替代正文">${esc(section?.summary ?? "")}</textarea></label>
|
|
3896
4486
|
</div>
|
|
3897
|
-
<div
|
|
3898
|
-
<label class="ghost-button" for="character-section-attachment">上传并插入图片</label>
|
|
3899
|
-
<input id="character-section-attachment" class="hidden" type="file" accept=".png,.jpg,.jpeg,.webp,.gif,image/png,image/jpeg,image/webp,image/gif">
|
|
3900
|
-
<span>图片会优先转换为体积更小的无损 WebP</span>
|
|
3901
|
-
</div>
|
|
3902
|
-
<div class="character-markdown-compose">
|
|
3903
|
-
<label>Markdown 原文<textarea id="character-section-markdown" maxlength="500000" spellcheck="true" placeholder="支持标题、列表、引用、表格、链接和图片">${esc(section?.contentMarkdown ?? "")}</textarea></label>
|
|
3904
|
-
<div><span class="character-markdown-preview-label">安全预览</span><article id="character-section-preview" class="character-markdown-document message-body">${renderMarkdown(section?.contentMarkdown ?? "") || '<p class="character-markdown-empty">预览区域暂无内容。</p>'}</article></div>
|
|
3905
|
-
</div>
|
|
4487
|
+
<div id="character-section-markdown" class="vditor-editor-host" data-vditor-editor aria-label="Markdown 编辑器"></div>
|
|
3906
4488
|
<div class="character-markdown-editor-footer">
|
|
3907
4489
|
<label class="character-markdown-change-note">版本说明<input id="character-section-change-note" maxlength="500" placeholder="可选,例如:补充远古时期经历"></label>
|
|
3908
4490
|
<div class="character-markdown-editor-actions"><button type="button" data-character-section-edit-cancel>取消</button><button type="button" class="primary-button" data-character-section-edit-save>${section ? "保存章节版本" : "创建章节"}</button></div>
|
|
@@ -3913,41 +4495,17 @@ function characterSectionEditorHtml(section = null) {
|
|
|
3913
4495
|
|
|
3914
4496
|
async function openCharacterSectionEditor(section = null) {
|
|
3915
4497
|
await discardPendingCharacterAttachments();
|
|
4498
|
+
destroyVditorEditor(characterSectionVditor);
|
|
4499
|
+
characterSectionVditor = null;
|
|
3916
4500
|
const host = $("#character-section-editor-host");
|
|
3917
4501
|
host.innerHTML = characterSectionEditorHtml(section);
|
|
3918
4502
|
characterSectionEditorDirty = false;
|
|
3919
4503
|
$("#character-editor-form").classList.add("hidden");
|
|
3920
4504
|
$("#character-section-editor-view").classList.remove("hidden");
|
|
3921
|
-
const textarea = $("#character-section-markdown");
|
|
3922
4505
|
host.querySelectorAll("input, textarea, select").forEach((control) => control.addEventListener("input", () => { characterSectionEditorDirty = true; }));
|
|
3923
|
-
|
|
3924
|
-
|
|
3925
|
-
|
|
3926
|
-
if (!file) return;
|
|
3927
|
-
const input = event.currentTarget;
|
|
3928
|
-
input.disabled = true;
|
|
3929
|
-
const body = new FormData();
|
|
3930
|
-
body.append("file", file);
|
|
3931
|
-
try {
|
|
3932
|
-
const attachment = await api(`/api/works/${state.work.id}/attachments`, { method: "POST", body });
|
|
3933
|
-
if (!attachment.deduplicated) characterSectionPendingAttachments.push(String(attachment.id));
|
|
3934
|
-
const imageLabel = file.name.replace(/[\]\r\n]/gu, "").trim() || "图片附件";
|
|
3935
|
-
const insertion = ``;
|
|
3936
|
-
const start = textarea.selectionStart ?? textarea.value.length;
|
|
3937
|
-
const end = textarea.selectionEnd ?? start;
|
|
3938
|
-
const prefix = start > 0 && !textarea.value.slice(0, start).endsWith("\n") ? "\n\n" : "";
|
|
3939
|
-
const suffix = end < textarea.value.length && !textarea.value.slice(end).startsWith("\n") ? "\n\n" : "";
|
|
3940
|
-
textarea.setRangeText(`${prefix}${insertion}${suffix}`, start, end, "end");
|
|
3941
|
-
textarea.focus();
|
|
3942
|
-
characterSectionEditorDirty = true;
|
|
3943
|
-
scheduleCharacterSectionPreview();
|
|
3944
|
-
toast(attachment.storedMimeType === "image/webp" ? "图片已转换为无损 WebP 并插入" : "图片已插入;转换后未变小,因此保留原格式");
|
|
3945
|
-
} catch (error) {
|
|
3946
|
-
toast(error.message, "error");
|
|
3947
|
-
} finally {
|
|
3948
|
-
input.disabled = false;
|
|
3949
|
-
input.value = "";
|
|
3950
|
-
}
|
|
4506
|
+
characterSectionVditor = createVditorEditor($("#character-section-markdown"), section?.contentMarkdown ?? "", {
|
|
4507
|
+
uploadAttachment: uploadCharacterSectionAttachment,
|
|
4508
|
+
onInput: () => { characterSectionEditorDirty = true; }
|
|
3951
4509
|
});
|
|
3952
4510
|
host.querySelector("[data-character-section-edit-close]").addEventListener("click", () => void closeCharacterSectionEditor());
|
|
3953
4511
|
host.querySelector("[data-character-section-edit-cancel]").addEventListener("click", () => void closeCharacterSectionEditor());
|
|
@@ -3960,7 +4518,7 @@ async function openCharacterSectionEditor(section = null) {
|
|
|
3960
4518
|
return;
|
|
3961
4519
|
}
|
|
3962
4520
|
button.disabled = true;
|
|
3963
|
-
const contentMarkdown =
|
|
4521
|
+
const contentMarkdown = characterSectionVditor?.getValue() ?? "";
|
|
3964
4522
|
try {
|
|
3965
4523
|
const saved = await api(section ? `/api/character-sections/${section.id}` : `/api/characters/${characterEditorItem.id}/sections`, {
|
|
3966
4524
|
method: section ? "PATCH" : "POST",
|
|
@@ -4018,7 +4576,7 @@ function renderCharacterMarkdownSections() {
|
|
|
4018
4576
|
const host = $("#character-markdown-sections");
|
|
4019
4577
|
if (!host) return;
|
|
4020
4578
|
if (!characterEditorItem?.id) {
|
|
4021
|
-
host.innerHTML = '<div class="character-editor-empty-field"><b
|
|
4579
|
+
host.innerHTML = '<div class="character-editor-empty-field" role="note" aria-label="保存角色提示"><b>请先保存当前角色</b><span>完成基础资料后,请点击页面底部的“创建人物档案”。保存成功后即可新建 Markdown 档案章节。</span></div>';
|
|
4022
4580
|
return;
|
|
4023
4581
|
}
|
|
4024
4582
|
const toolbar = `<div class="character-markdown-list-toolbar"><div><b>Markdown 档案章节</b><span>长篇内容独立保存、渲染、检索和版本管理。</span></div>${canEditModule("characters") ? '<button type="button" class="primary-button" data-character-section-create>新建章节</button>' : ""}</div>`;
|
|
@@ -4284,44 +4842,115 @@ async function openCharacterEditor(item = null) {
|
|
|
4284
4842
|
}
|
|
4285
4843
|
}
|
|
4286
4844
|
|
|
4287
|
-
|
|
4845
|
+
function knowledgeEditorSection(key, title, description, content) {
|
|
4846
|
+
return `<section class="character-editor-section${key === "basic" ? "" : " hidden"}" data-knowledge-editor-panel="${esc(key)}" role="tabpanel"><header><div><span class="eyebrow">${esc(title)}</span><h3>${esc(title)}</h3></div>${description ? `<p>${esc(description)}</p>` : ""}</header><div class="character-editor-section-fields">${content}</div></section>`;
|
|
4847
|
+
}
|
|
4848
|
+
|
|
4849
|
+
function activateKnowledgeEditorTab(key) {
|
|
4850
|
+
document.querySelectorAll("[data-knowledge-editor-tab]").forEach((button) => {
|
|
4851
|
+
const active = button.dataset.knowledgeEditorTab === key;
|
|
4852
|
+
button.setAttribute("aria-selected", String(active));
|
|
4853
|
+
button.tabIndex = active ? 0 : -1;
|
|
4854
|
+
});
|
|
4855
|
+
document.querySelectorAll("[data-knowledge-editor-panel]").forEach((panel) => panel.classList.toggle("hidden", panel.dataset.knowledgeEditorPanel !== key));
|
|
4856
|
+
}
|
|
4857
|
+
|
|
4858
|
+
function renderKnowledgeEditorFields(kind, item, memberOptions, parentOptions) {
|
|
4859
|
+
const isRace = kind === "race";
|
|
4860
|
+
const label = isRace ? "种族" : "组织";
|
|
4861
|
+
const title = isRace ? "种族共同设定" : "组织设定";
|
|
4862
|
+
const tabs = isRace
|
|
4863
|
+
? [["basic", "基础资料", "名称、层级与简介"], ["settings", "共同设定", "Markdown 设定章节"], ["members", "种族成员", "角色归属"]]
|
|
4864
|
+
: [["basic", "基础资料", "名称与简介"], ["settings", "组织设定", "Markdown 设定章节"], ["members", "组织成员", "角色归属"]];
|
|
4865
|
+
$("#knowledge-editor-nav").innerHTML = tabs.map(([key, tabTitle, description], index) => `<button type="button" role="tab" data-knowledge-editor-tab="${key}" aria-selected="${index === 0}" tabindex="${index === 0 ? "0" : "-1"}">${tabTitle}<small>${description}</small></button>`).join("");
|
|
4866
|
+
const basicFields = field("name", `${label}名称`, "text", item?.name, []) + (isRace ? field("parentRaceId", "父种族", "select", item?.parentRaceId ?? "", parentOptions) : "") + field("description", `${label}简介`, "textarea", item?.description, []);
|
|
4867
|
+
const memberField = memberOptions.length
|
|
4868
|
+
? field("memberIds", isRace ? "属于该种族的角色(可多选)" : "组织成员(可多选)", "chips", item?.memberIds ?? [], memberOptions)
|
|
4869
|
+
: `<div class="character-editor-empty-field"><strong>${isRace ? "种族成员" : "组织成员"}</strong><span>当前还没有可绑定的角色。</span></div>`;
|
|
4870
|
+
$("#knowledge-editor-fields").innerHTML = knowledgeEditorSection("basic", "基础资料", isRace ? "先定义名称、层级和简介,再补充共同设定。" : "先定义组织名称和简介,再补充完整的组织设定。", basicFields)
|
|
4871
|
+
+ knowledgeEditorSection("settings", title, "", '<div id="knowledge-markdown-sections" class="knowledge-markdown-sections"></div>')
|
|
4872
|
+
+ knowledgeEditorSection("members", isRace ? "种族成员" : "组织成员", "成员关系会同步到角色档案中。", memberField);
|
|
4873
|
+
document.querySelectorAll("[data-knowledge-editor-tab]").forEach((button) => {
|
|
4874
|
+
button.onclick = () => activateKnowledgeEditorTab(button.dataset.knowledgeEditorTab);
|
|
4875
|
+
});
|
|
4876
|
+
renderKnowledgeMarkdownSections();
|
|
4877
|
+
activateKnowledgeEditorTab("basic");
|
|
4878
|
+
}
|
|
4879
|
+
|
|
4880
|
+
async function openKnowledgeEditor(kind, item) {
|
|
4881
|
+
await discardPendingMarkdownAttachments();
|
|
4288
4882
|
state.characters = canReadModule("characters") ? await apiAllPages(`/api/works/${state.work.id}/characters`) : [];
|
|
4289
4883
|
const memberOptions = state.characters.map((character) => [character.id, `${character.name}${character.aliases.length ? `(${character.aliases.join("、")})` : ""}`]);
|
|
4290
|
-
const
|
|
4291
|
-
|
|
4292
|
-
|
|
4293
|
-
|
|
4294
|
-
|
|
4295
|
-
|
|
4296
|
-
|
|
4297
|
-
|
|
4298
|
-
|
|
4299
|
-
|
|
4300
|
-
|
|
4301
|
-
|
|
4302
|
-
|
|
4303
|
-
|
|
4304
|
-
|
|
4884
|
+
const isRace = kind === "race";
|
|
4885
|
+
const module = isRace ? "races" : "organizations";
|
|
4886
|
+
const label = isRace ? "种族" : "组织";
|
|
4887
|
+
const parentOptions = isRace
|
|
4888
|
+
? [["", "无(根种族)"], ...eligibleRaceParents(state.races, item?.id)
|
|
4889
|
+
.sort((left, right) => racePathLabel(left).localeCompare(racePathLabel(right), "zh-CN"))
|
|
4890
|
+
.map((race) => [race.id, racePathLabel(race)])]
|
|
4891
|
+
: [];
|
|
4892
|
+
knowledgeEditorItem = item ?? null;
|
|
4893
|
+
knowledgeEditorKind = kind;
|
|
4894
|
+
knowledgeEditorSections = normalizeKnowledgeEditorSections(item);
|
|
4895
|
+
knowledgeSectionEditorIndex = null;
|
|
4896
|
+
knowledgeSectionEditorDirty = false;
|
|
4897
|
+
$("#knowledge-editor-eyebrow").textContent = item ? `${label}档案` : `建立${label}档案`;
|
|
4898
|
+
$("#knowledge-editor-title").textContent = item?.name || `新建${label}`;
|
|
4899
|
+
$("#knowledge-editor-version").textContent = item ? `v${item.versionNo ?? 1}` : "新档案";
|
|
4900
|
+
$("#knowledge-editor-header-note").textContent = isRace ? "层级、共同设定与角色归属" : "简介、组织设定与成员归属";
|
|
4901
|
+
$("#knowledge-editor-footer-note").textContent = `保存${label}档案后返回${isRace ? "种族" : "组织"}列表。`;
|
|
4902
|
+
$("#knowledge-editor-submit").textContent = item ? `保存${label}档案` : `创建${label}档案`;
|
|
4903
|
+
renderKnowledgeEditorFields(kind, item, memberOptions, parentOptions);
|
|
4904
|
+
const viewOnly = !canEditModule(module);
|
|
4905
|
+
if (viewOnly) {
|
|
4906
|
+
$("#knowledge-editor-eyebrow").textContent = `${label}档案`;
|
|
4907
|
+
$("#knowledge-editor-fields").querySelectorAll("input, textarea").forEach((control) => { control.readOnly = true; });
|
|
4908
|
+
$("#knowledge-editor-fields").querySelectorAll("select, input[type='checkbox']").forEach((control) => { control.disabled = true; });
|
|
4909
|
+
}
|
|
4910
|
+
$("#knowledge-editor-submit").classList.toggle("hidden", viewOnly);
|
|
4911
|
+
const form = $("#knowledge-editor-form");
|
|
4912
|
+
form.onsubmit = async (event) => {
|
|
4913
|
+
event.preventDefault();
|
|
4914
|
+
if (!canEditModule(module)) return;
|
|
4915
|
+
const submit = $("#knowledge-editor-submit");
|
|
4916
|
+
submit.disabled = true;
|
|
4917
|
+
try {
|
|
4918
|
+
const data = new FormData(form);
|
|
4919
|
+
const name = String(data.get("name") ?? "").trim();
|
|
4920
|
+
if (!name) throw new Error(`请填写${label}名称`);
|
|
4921
|
+
const settingsSections = knowledgeEditorSections
|
|
4922
|
+
.map((section, index) => ({ title: String(section.title ?? "").trim(), contentMarkdown: String(section.contentMarkdown ?? ""), summary: String(section.summary ?? "").trim(), sortOrder: index }))
|
|
4923
|
+
.filter((section) => section.title || section.contentMarkdown.trim());
|
|
4924
|
+
const untitled = settingsSections.findIndex((section) => !section.title);
|
|
4925
|
+
if (untitled >= 0) throw new Error(`请填写第 ${untitled + 1} 条 Markdown 设定的标题`);
|
|
4926
|
+
const settingsMarkdown = settingsSections.map((section) => section.contentMarkdown).join("\n\n");
|
|
4927
|
+
const body = isRace
|
|
4928
|
+
? { name, parentRaceId: data.get("parentRaceId") || null, description: data.get("description"), settingsMarkdown, settingsSections }
|
|
4929
|
+
: { name, description: data.get("description"), settingsMarkdown, settingsSections };
|
|
4930
|
+
if (canReadModule("characters")) body.memberIds = data.getAll("memberIds").map(String);
|
|
4931
|
+
const wasEditing = Boolean(knowledgeEditorItem);
|
|
4932
|
+
await api(wasEditing ? `/api/${module}/${knowledgeEditorItem.id}` : `/api/works/${state.work.id}/${module}`, { method: wasEditing ? "PATCH" : "POST", body });
|
|
4933
|
+
await cleanupPendingMarkdownAttachments(settingsMarkdown);
|
|
4934
|
+
entityEditorDirty = false;
|
|
4305
4935
|
await loadAiReferences();
|
|
4306
|
-
|
|
4936
|
+
await closeEntityEditor({ force: true });
|
|
4937
|
+
toast(wasEditing ? `${label}档案已保存` : `${label}档案已创建`);
|
|
4938
|
+
} catch (error) {
|
|
4939
|
+
toast(error.message, "error");
|
|
4940
|
+
} finally {
|
|
4941
|
+
submit.disabled = false;
|
|
4942
|
+
}
|
|
4943
|
+
};
|
|
4944
|
+
showEntityEditorPage(kind);
|
|
4945
|
+
$("#knowledge-editor-fields").querySelector("input:not([type='checkbox']), textarea")?.focus();
|
|
4946
|
+
}
|
|
4947
|
+
|
|
4948
|
+
async function openRaceDialog(item) {
|
|
4949
|
+
await openKnowledgeEditor("race", item);
|
|
4307
4950
|
}
|
|
4308
4951
|
|
|
4309
4952
|
async function openOrganizationDialog(item) {
|
|
4310
|
-
|
|
4311
|
-
const memberOptions = state.characters.map((character) => [character.id, `${character.name}${character.aliases.length ? `(${character.aliases.join("、")})` : ""}`]);
|
|
4312
|
-
openDialog(item ? "编辑组织" : "新建组织",
|
|
4313
|
-
field("name", "组织名称", "text", item?.name) +
|
|
4314
|
-
field("description", "组织简介", "textarea", item?.description) +
|
|
4315
|
-
field("settings", "组织设定(逐条填写)", "item-list", item?.settings ?? []) +
|
|
4316
|
-
(memberOptions.length ? field("memberIds", "组织成员(可多选)", "chips", item?.memberIds ?? [], memberOptions) : ""),
|
|
4317
|
-
async (form) => {
|
|
4318
|
-
const settings = form.getAll("settings").map((value) => String(value).trim()).filter(Boolean);
|
|
4319
|
-
const body = { name: form.get("name"), description: form.get("description"), settings };
|
|
4320
|
-
if (canReadModule("characters")) body.memberIds = form.getAll("memberIds").map(String);
|
|
4321
|
-
await api(item ? `/api/organizations/${item.id}` : `/api/works/${state.work.id}/organizations`, { method: item ? "PATCH" : "POST", body });
|
|
4322
|
-
await renderOrganizations();
|
|
4323
|
-
await loadAiReferences();
|
|
4324
|
-
}, item ? "组织档案" : "世界内组织");
|
|
4953
|
+
await openKnowledgeEditor("organization", item);
|
|
4325
4954
|
}
|
|
4326
4955
|
|
|
4327
4956
|
function openTimelineTrackDialog(item) {
|
|
@@ -4462,16 +5091,29 @@ function openProviderDialog(item) {
|
|
|
4462
5091
|
|
|
4463
5092
|
function openModelDialog(providerId, item = null) {
|
|
4464
5093
|
const values = modelFormValues(item);
|
|
4465
|
-
|
|
5094
|
+
const temperatureField = `<div class="form-field model-temperature-field"><label for="model-temperature">默认温度<input id="model-temperature" name="temperature" type="number" value="${esc(values.temperature)}" step="any" aria-describedby="model-temperature-hint"></label><small id="model-temperature-hint" class="model-temperature-hint" hidden>Kimi 模型必须设置温度为 1。</small></div>`;
|
|
5095
|
+
openDialog(item ? "编辑模型" : "添加模型", field("displayName", "显示名称", "text", values.displayName) + field("modelId", "模型标识符", "text", values.modelId) + field("purposes", "支持用途(可多选)", "chips", values.purposes, MODEL_PURPOSE_OPTIONS) + field("contextWindow", "模型上下文总量(Token)", "number", values.contextWindow) + temperatureField + field("maxTokens", "默认 max_tokens", "number", values.maxTokens) + field("thinkingEnabled", "开启 Thinking(供应商需支持 thinking 参数)", "checkbox", values.thinkingEnabled) + field("enabled", "启用模型", "checkbox", values.enabled), async (form) => {
|
|
4466
5096
|
const body = modelPayload({ displayName: form.get("displayName"), modelId: form.get("modelId"), purposes: form.getAll("purposes"), contextWindow: form.get("contextWindow"), temperature: form.get("temperature"), maxTokens: form.get("maxTokens"), thinkingEnabled: form.get("thinkingEnabled") === "on", enabled: form.get("enabled") === "on" }, item?.preset);
|
|
4467
5097
|
await api(item ? `/api/models/${item.id}` : `/api/providers/${providerId}/models`, { method: item ? "PATCH" : "POST", body });
|
|
4468
5098
|
await renderPlatformAiConfig();
|
|
4469
5099
|
await loadModels();
|
|
4470
5100
|
}, item ? "模型配置" : "供应商模型");
|
|
5101
|
+
const modelIdInput = $("#dialog-fields input[name='modelId']");
|
|
5102
|
+
const temperatureInput = $("#dialog-fields input[name='temperature']");
|
|
5103
|
+
const temperatureHint = $("#model-temperature-hint");
|
|
5104
|
+
const syncKimiTemperature = () => {
|
|
5105
|
+
const isKimi = isKimiModelId(modelIdInput.value);
|
|
5106
|
+
temperatureHint.hidden = !isKimi;
|
|
5107
|
+
};
|
|
5108
|
+
modelIdInput.addEventListener("input", () => {
|
|
5109
|
+
if (isKimiModelId(modelIdInput.value)) temperatureInput.value = "1";
|
|
5110
|
+
syncKimiTemperature();
|
|
5111
|
+
});
|
|
5112
|
+
syncKimiTemperature();
|
|
4471
5113
|
}
|
|
4472
5114
|
|
|
4473
5115
|
async function sendAi() {
|
|
4474
|
-
if (!state.work
|
|
5116
|
+
if (!state.work) return toast("请先选择作品", "error");
|
|
4475
5117
|
try {
|
|
4476
5118
|
await Promise.all([ensureAiModelsLoaded(), ensureAiConversationsLoaded()]);
|
|
4477
5119
|
} catch (error) {
|
|
@@ -4727,7 +5369,10 @@ async function showVersions() {
|
|
|
4727
5369
|
const versions = await api(`/api/chapters/${state.chapter.id}/versions`);
|
|
4728
5370
|
$("#versions-list").innerHTML = versions.map((version) => `<div class="version-row"><div><b>v${version.versionNo}</b><small>${esc(version.source)} · ${esc(version.actor || "历史数据")}</small></div><p>${esc(version.content.slice(0, 300) || "空白章节")}</p>${canEditProse() ? `<button class="ghost-button" data-restore-version="${version.versionNo}">恢复</button>` : ""}</div>`).join("");
|
|
4729
5371
|
$("#versions-list").querySelectorAll("[data-restore-version]").forEach((button) => button.addEventListener("click", async () => {
|
|
4730
|
-
if (!
|
|
5372
|
+
if (!(await confirmToast(
|
|
5373
|
+
`将版本 v${button.dataset.restoreVersion} 恢复为一个新的保存版本?`,
|
|
5374
|
+
{ title: "恢复历史版本", confirmLabel: "确认恢复" }
|
|
5375
|
+
))) return;
|
|
4731
5376
|
state.chapter = await api(`/api/chapters/${state.chapter.id}/restore`, { method: "POST", body: { versionNo: Number(button.dataset.restoreVersion) } });
|
|
4732
5377
|
lastSavedChapterSnapshot = { chapterId: state.chapter.id, title: state.chapter.title, content: state.chapter.content };
|
|
4733
5378
|
$("#chapter-title").value = state.chapter.title;
|
|
@@ -4741,6 +5386,120 @@ async function showVersions() {
|
|
|
4741
5386
|
$("#versions-dialog").showModal();
|
|
4742
5387
|
}
|
|
4743
5388
|
|
|
5389
|
+
function importHistoryRecordLabel(version) {
|
|
5390
|
+
const restorePrefix = "before-restore:";
|
|
5391
|
+
if (version.fileType === "snapshot" && version.fileName.startsWith(restorePrefix)) {
|
|
5392
|
+
return {
|
|
5393
|
+
title: `恢复操作前备份:${version.fileName.slice(restorePrefix.length)}`,
|
|
5394
|
+
kind: "自动备份",
|
|
5395
|
+
action: "恢复此操作前备份"
|
|
5396
|
+
};
|
|
5397
|
+
}
|
|
5398
|
+
return { title: version.fileName, kind: String(version.fileType).toUpperCase(), action: "恢复到此次导入前" };
|
|
5399
|
+
}
|
|
5400
|
+
|
|
5401
|
+
function renderImportHistory(versions, nextPage = null) {
|
|
5402
|
+
const host = $("#import-history-list");
|
|
5403
|
+
if (!versions.length) {
|
|
5404
|
+
host.innerHTML = '<p class="entity-history-empty">还没有正文导入记录。首次导入后会自动保存导入前快照。</p>';
|
|
5405
|
+
return;
|
|
5406
|
+
}
|
|
5407
|
+
host.innerHTML = versions.map((version) => {
|
|
5408
|
+
const label = importHistoryRecordLabel(version);
|
|
5409
|
+
return `<article class="entity-version-card import-history-card" data-file-version="${esc(version.id)}">
|
|
5410
|
+
<header><strong title="${esc(label.title)}">${esc(label.title)}</strong><span class="import-history-kind">${esc(label.kind)}</span></header>
|
|
5411
|
+
<time>${esc(formatDateTime(version.createdAt))} · ${esc(version.actor || "历史数据")}</time>
|
|
5412
|
+
<p>${version.fileType === "snapshot" ? "恢复操作执行前自动保存的完整正文。" : "保存的是这次文件导入开始前的完整正文。"}</p>
|
|
5413
|
+
<small>自动备份仅包含正文,不能撤销章节关联信息的变化。</small>
|
|
5414
|
+
<button type="button" data-file-version-restore="${esc(version.id)}" data-default-label="${esc(label.action)}">${esc(label.action)}</button>
|
|
5415
|
+
</article>`;
|
|
5416
|
+
}).join("") + (nextPage ? '<button class="import-history-load-more" type="button" data-import-history-load-more>加载更多记录</button>' : "");
|
|
5417
|
+
host.querySelectorAll("[data-file-version-restore]").forEach((button) => button.addEventListener("click", async () => {
|
|
5418
|
+
if (!state.work || !canReplaceProse()) return;
|
|
5419
|
+
const defaultLabel = button.dataset.defaultLabel;
|
|
5420
|
+
if (button.dataset.confirmed !== "true") {
|
|
5421
|
+
host.querySelectorAll("[data-file-version-restore]").forEach((other) => {
|
|
5422
|
+
other.dataset.confirmed = "false";
|
|
5423
|
+
other.classList.remove("is-confirming");
|
|
5424
|
+
other.textContent = other.dataset.defaultLabel;
|
|
5425
|
+
});
|
|
5426
|
+
button.dataset.confirmed = "true";
|
|
5427
|
+
button.classList.add("is-confirming");
|
|
5428
|
+
button.textContent = "再次点击确认恢复";
|
|
5429
|
+
window.setTimeout(() => {
|
|
5430
|
+
if (!button.isConnected || button.dataset.confirmed !== "true") return;
|
|
5431
|
+
button.dataset.confirmed = "false";
|
|
5432
|
+
button.classList.remove("is-confirming");
|
|
5433
|
+
button.textContent = defaultLabel;
|
|
5434
|
+
}, 5000);
|
|
5435
|
+
return;
|
|
5436
|
+
}
|
|
5437
|
+
if (!(await confirmDiscardChanges("当前章节有未保存修改,恢复正文会丢弃这些本地修改。是否继续?"))) return;
|
|
5438
|
+
button.disabled = true;
|
|
5439
|
+
cancelChapterAutoSave();
|
|
5440
|
+
const workId = state.work.id;
|
|
5441
|
+
try {
|
|
5442
|
+
await api(`/api/works/${encodeURIComponent(workId)}/file-versions/${encodeURIComponent(button.dataset.fileVersionRestore)}/restore`, {
|
|
5443
|
+
method: "POST",
|
|
5444
|
+
body: { expectedVersionNo: state.work.versionNo }
|
|
5445
|
+
});
|
|
5446
|
+
state.dirty = false;
|
|
5447
|
+
resetWorkScopedUiCaches();
|
|
5448
|
+
$("#import-history-dialog").close();
|
|
5449
|
+
await loadWorks(workId);
|
|
5450
|
+
toast("正文已恢复;恢复前正文已自动备份");
|
|
5451
|
+
} catch (error) {
|
|
5452
|
+
button.disabled = false;
|
|
5453
|
+
button.dataset.confirmed = "false";
|
|
5454
|
+
button.classList.remove("is-confirming");
|
|
5455
|
+
button.textContent = defaultLabel;
|
|
5456
|
+
if (state.dirty) scheduleChapterAutoSave();
|
|
5457
|
+
toast(error.message, "error");
|
|
5458
|
+
}
|
|
5459
|
+
}));
|
|
5460
|
+
host.querySelector("[data-import-history-load-more]")?.addEventListener("click", async (event) => {
|
|
5461
|
+
const button = event.currentTarget;
|
|
5462
|
+
button.disabled = true;
|
|
5463
|
+
button.textContent = "正在加载…";
|
|
5464
|
+
try {
|
|
5465
|
+
await loadImportHistoryPage(nextPage);
|
|
5466
|
+
} catch (error) {
|
|
5467
|
+
button.disabled = false;
|
|
5468
|
+
button.textContent = "加载更多记录";
|
|
5469
|
+
toast(error.message, "error");
|
|
5470
|
+
}
|
|
5471
|
+
});
|
|
5472
|
+
}
|
|
5473
|
+
|
|
5474
|
+
async function loadImportHistoryPage(page) {
|
|
5475
|
+
const workId = state.work?.id;
|
|
5476
|
+
if (!workId || !page) return;
|
|
5477
|
+
const requestId = ++importHistoryRequestId;
|
|
5478
|
+
const result = await apiPage(`/api/works/${encodeURIComponent(workId)}/file-versions`, page, 25);
|
|
5479
|
+
if (requestId !== importHistoryRequestId || state.work?.id !== workId || !$("#import-history-dialog").open) return;
|
|
5480
|
+
importHistoryRecords = page === 1 ? result.items : [...importHistoryRecords, ...result.items];
|
|
5481
|
+
importHistoryNextPage = result.nextPage;
|
|
5482
|
+
renderImportHistory(importHistoryRecords, importHistoryNextPage);
|
|
5483
|
+
}
|
|
5484
|
+
|
|
5485
|
+
async function openImportHistory() {
|
|
5486
|
+
if (!state.work || !canReplaceProse()) {
|
|
5487
|
+
toast("恢复整本正文需要所有受影响模块的编辑权限", "error");
|
|
5488
|
+
return;
|
|
5489
|
+
}
|
|
5490
|
+
importHistoryRecords = [];
|
|
5491
|
+
importHistoryNextPage = null;
|
|
5492
|
+
importHistoryRequestId += 1;
|
|
5493
|
+
$("#import-history-list").innerHTML = '<p class="entity-history-empty">正在读取导入历史…</p>';
|
|
5494
|
+
$("#import-history-dialog").showModal();
|
|
5495
|
+
try {
|
|
5496
|
+
await loadImportHistoryPage(1);
|
|
5497
|
+
} catch (error) {
|
|
5498
|
+
$("#import-history-dialog").close();
|
|
5499
|
+
toast(error.message, "error");
|
|
5500
|
+
}
|
|
5501
|
+
}
|
|
5502
|
+
|
|
4744
5503
|
async function showChapterInsight() {
|
|
4745
5504
|
if (!state.chapter) return;
|
|
4746
5505
|
const panel = $("#chapter-insight");
|
|
@@ -4756,11 +5515,13 @@ async function showChapterInsight() {
|
|
|
4756
5515
|
panel.innerHTML = `<strong>章节概览${esc(stale)}</strong>${esc(insight.summary || "暂无梗概")}${eventNames.length ? `<br><strong>事件</strong>${esc(eventNames.join(";"))}` : ""}${insight.uncertainties.length ? `<br><strong>待确认</strong>${esc(insight.uncertainties.map((item) => typeof item === "string" ? item : JSON.stringify(item)).join(";"))}` : ""}`;
|
|
4757
5516
|
}
|
|
4758
5517
|
|
|
4759
|
-
$("#home-button").addEventListener("click", () => {
|
|
4760
|
-
if (!confirmDiscardChanges()) return;
|
|
5518
|
+
$("#home-button").addEventListener("click", async () => {
|
|
5519
|
+
if (!(await confirmDiscardChanges())) return;
|
|
4761
5520
|
loadWorks().catch((error) => toast(error.message, "error"));
|
|
4762
5521
|
});
|
|
4763
|
-
$("#settings-button").addEventListener("click",
|
|
5522
|
+
$("#settings-button").addEventListener("click", () => {
|
|
5523
|
+
void showSettingsHub();
|
|
5524
|
+
});
|
|
4764
5525
|
$("#account-button").addEventListener("click", () => {
|
|
4765
5526
|
const expanded = $("#account-menu").classList.toggle("hidden") === false;
|
|
4766
5527
|
$("#account-button").setAttribute("aria-expanded", String(expanded));
|
|
@@ -4841,7 +5602,7 @@ $("#avatar-file").addEventListener("change", async (event) => {
|
|
|
4841
5602
|
}
|
|
4842
5603
|
});
|
|
4843
5604
|
$("#avatar-remove-button").addEventListener("click", async () => {
|
|
4844
|
-
if (!state.user?.avatarUrl || !
|
|
5605
|
+
if (!state.user?.avatarUrl || !(await confirmToast("确定移除当前头像吗?", { title: "移除头像", confirmLabel: "确认移除" }))) return;
|
|
4845
5606
|
$("#avatar-upload-button").disabled = true;
|
|
4846
5607
|
$("#avatar-remove-button").disabled = true;
|
|
4847
5608
|
try {
|
|
@@ -4869,14 +5630,35 @@ $("#profile-form").addEventListener("submit", async (event) => {
|
|
|
4869
5630
|
$("#password-form").addEventListener("submit", async (event) => {
|
|
4870
5631
|
event.preventDefault();
|
|
4871
5632
|
const form = new FormData(event.currentTarget);
|
|
5633
|
+
const newPassword = String(form.get("newPassword") ?? "");
|
|
5634
|
+
const passwordConfirmation = String(form.get("passwordConfirmation") ?? "");
|
|
5635
|
+
if (newPassword !== passwordConfirmation) {
|
|
5636
|
+
const confirmationInput = $("#password-form input[name='passwordConfirmation']");
|
|
5637
|
+
confirmationInput.setCustomValidity("两次输入的密码不一致");
|
|
5638
|
+
confirmationInput.reportValidity();
|
|
5639
|
+
confirmationInput.focus();
|
|
5640
|
+
return;
|
|
5641
|
+
}
|
|
4872
5642
|
try {
|
|
4873
|
-
await api("/api/auth/password", { method: "PATCH", body: { currentPassword: form.get("currentPassword"), newPassword
|
|
5643
|
+
await api("/api/auth/password", { method: "PATCH", body: { currentPassword: form.get("currentPassword"), newPassword, passwordConfirmation } });
|
|
4874
5644
|
event.currentTarget.reset();
|
|
4875
5645
|
toast("密码已更新,其他设备的会话已退出");
|
|
4876
5646
|
} catch (error) { toast(error.message, "error"); }
|
|
4877
5647
|
});
|
|
5648
|
+
function validatePasswordChangeConfirmation() {
|
|
5649
|
+
const newPasswordInput = $("#password-form input[name='newPassword']");
|
|
5650
|
+
const confirmationInput = $("#password-form input[name='passwordConfirmation']");
|
|
5651
|
+
const matches = !confirmationInput.value || newPasswordInput.value === confirmationInput.value;
|
|
5652
|
+
confirmationInput.setCustomValidity(matches ? "" : "两次输入的密码不一致");
|
|
5653
|
+
return matches;
|
|
5654
|
+
}
|
|
5655
|
+
$("#password-form input[name='newPassword']").addEventListener("input", validatePasswordChangeConfirmation);
|
|
5656
|
+
$("#password-form input[name='passwordConfirmation']").addEventListener("input", validatePasswordChangeConfirmation);
|
|
4878
5657
|
$("#api-key-reset-button").addEventListener("click", async () => {
|
|
4879
|
-
if ($("#api-key-reset-button").textContent.includes("重置") && !
|
|
5658
|
+
if ($("#api-key-reset-button").textContent.includes("重置") && !(await confirmToast(
|
|
5659
|
+
"重置后,所有使用旧 API Key 的 CLI 会立刻退出登录。确定继续吗?",
|
|
5660
|
+
{ title: "重置 API Key", confirmLabel: "确认重置" }
|
|
5661
|
+
))) return;
|
|
4880
5662
|
try {
|
|
4881
5663
|
const result = await api("/api/auth/api-key/reset", { method: "POST", body: {} });
|
|
4882
5664
|
$("#api-key-status").textContent = `已配置 ${result.prefix}…,尚未使用`;
|
|
@@ -5050,22 +5832,26 @@ $("#member-permission-form").addEventListener("submit", async (event) => {
|
|
|
5050
5832
|
$("#platform-new-provider").addEventListener("click", () => openProviderDialog());
|
|
5051
5833
|
$("#shelf-new-work").addEventListener("click", openWorkDialog);
|
|
5052
5834
|
$("#welcome-new-work").addEventListener("click", () => state.work ? openChapterDialog() : openWorkDialog());
|
|
5053
|
-
$("#new-chapter-button").addEventListener("click", openChapterDialog);
|
|
5054
5835
|
$("#save-button").addEventListener("click", saveChapter);
|
|
5055
5836
|
$("#tidy-blank-lines-button").addEventListener("click", tidyChapterBlankLines);
|
|
5056
5837
|
$("#new-volume-button").addEventListener("click", () => openVolumeDialog());
|
|
5057
5838
|
$("#insight-button").addEventListener("click", () => showChapterInsight().catch((error) => toast(error.message, "error")));
|
|
5058
5839
|
$("#versions-button").addEventListener("click", showVersions);
|
|
5059
5840
|
$("#versions-close").addEventListener("click", () => $("#versions-dialog").close());
|
|
5841
|
+
$("#import-history-close").addEventListener("click", () => $("#import-history-dialog").close());
|
|
5060
5842
|
$("#entity-history-close").addEventListener("click", () => $("#entity-history-dialog").close());
|
|
5061
5843
|
$("#ai-tool-call-close").addEventListener("click", () => $("#ai-tool-call-dialog").close());
|
|
5062
5844
|
$("#setting-editor-back").addEventListener("click", () => { void closeEntityEditor(); });
|
|
5063
5845
|
$("#character-editor-close").addEventListener("click", () => { void closeEntityEditor(); });
|
|
5064
5846
|
$("#character-editor-cancel").addEventListener("click", () => { void closeEntityEditor(); });
|
|
5847
|
+
$("#knowledge-editor-close").addEventListener("click", () => { void closeEntityEditor(); });
|
|
5848
|
+
$("#knowledge-editor-cancel").addEventListener("click", () => { void closeEntityEditor(); });
|
|
5065
5849
|
$("#setting-editor-form").addEventListener("input", markEntityEditorDirty);
|
|
5066
5850
|
$("#setting-editor-form").addEventListener("change", markEntityEditorDirty);
|
|
5067
5851
|
$("#character-editor-form").addEventListener("input", markEntityEditorDirty);
|
|
5068
5852
|
$("#character-editor-form").addEventListener("change", markEntityEditorDirty);
|
|
5853
|
+
$("#knowledge-editor-form").addEventListener("input", markEntityEditorDirty);
|
|
5854
|
+
$("#knowledge-editor-form").addEventListener("change", markEntityEditorDirty);
|
|
5069
5855
|
$("#character-editor-fields").addEventListener("click", (event) => {
|
|
5070
5856
|
if (event.target.closest("[data-item-list-add], [data-structured-list-add], [data-item-list-remove], [data-structured-list-remove]")) markEntityEditorDirty();
|
|
5071
5857
|
});
|
|
@@ -5192,6 +5978,13 @@ $("#ai-mention-menu").addEventListener("click", (event) => {
|
|
|
5192
5978
|
const button = event.target.closest("[data-ai-reference-id]");
|
|
5193
5979
|
if (button) selectAiMention(button);
|
|
5194
5980
|
});
|
|
5981
|
+
$("#import-file-button").addEventListener("click", (event) => {
|
|
5982
|
+
if (!state.work || canEditProse()) return;
|
|
5983
|
+
event.preventDefault();
|
|
5984
|
+
event.stopPropagation();
|
|
5985
|
+
$("#import-file").value = "";
|
|
5986
|
+
toast("当前权限只能编辑设定资料,不能导入正文", "error");
|
|
5987
|
+
});
|
|
5195
5988
|
$("#import-file").addEventListener("change", async (event) => {
|
|
5196
5989
|
const file = event.target.files[0];
|
|
5197
5990
|
if (!state.work || !file) return;
|
|
@@ -5205,6 +5998,13 @@ $("#import-file").addEventListener("change", async (event) => {
|
|
|
5205
5998
|
event.target.value = "";
|
|
5206
5999
|
return;
|
|
5207
6000
|
}
|
|
6001
|
+
if (mode === "overwrite" && !(await confirmToast(
|
|
6002
|
+
`当前选项:覆盖正文。确认后将使用“${file.name}”替换《${state.work.title}》的现有正文目录。`,
|
|
6003
|
+
{ title: "覆盖正文二次确认", confirmLabel: "确认覆盖" }
|
|
6004
|
+
))) {
|
|
6005
|
+
event.target.value = "";
|
|
6006
|
+
return;
|
|
6007
|
+
}
|
|
5208
6008
|
cancelChapterAutoSave();
|
|
5209
6009
|
const body = new FormData();
|
|
5210
6010
|
body.append("file", file);
|