@jvs-milkdown/crepe 1.2.24 → 1.2.26

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.
@@ -3,8 +3,8 @@ import { type ListenerManager } from '@jvs-milkdown/kit/plugin/listener';
3
3
  import type { CrepeFeatureConfig } from '../feature';
4
4
  import type { DefineFeature } from '../feature/shared';
5
5
  import { CrepeFeature } from '../feature';
6
- import { type CrepeTranslations } from './locale';
7
6
  import { type ViewMenuState } from '../feature/fixed-toolbar/view-menu-state';
7
+ import { type CrepeTranslations } from './locale';
8
8
  export interface CrepeBuilderConfig {
9
9
  root?: Node | string | null;
10
10
  defaultValue?: DefaultValue;
@@ -1 +1 @@
1
- {"version":3,"file":"builder.d.ts","sourceRoot":"","sources":["../../../src/core/builder.ts"],"names":[],"mappings":"AACA,OAAO,EACL,KAAK,YAAY,EAEjB,MAAM,EAKP,MAAM,wBAAwB,CAAA;AAI/B,OAAO,EAGL,KAAK,eAAe,EACrB,MAAM,mCAAmC,CAAA;AAO1C,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,YAAY,CAAA;AACpD,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,mBAAmB,CAAA;AAEtD,OAAO,EAAE,YAAY,EAAE,MAAM,YAAY,CAAA;AAGzC,OAAO,EAAc,KAAK,iBAAiB,EAAE,MAAM,UAAU,CAAA;AAQ7D,OAAO,EAAoB,KAAK,aAAa,EAAE,MAAM,0CAA0C,CAAA;AAG/F,MAAM,WAAW,kBAAkB;IAIjC,IAAI,CAAC,EAAE,IAAI,GAAG,MAAM,GAAG,IAAI,CAAA;IAG3B,YAAY,CAAC,EAAE,YAAY,CAAA;IAG3B,MAAM,CAAC,EAAE,MAAM,CAAA;IAGf,YAAY,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,iBAAiB,CAAC,CAAC,CAAA;CAC1D;AAID,qBAAa,YAAY;;gBAYX,EACV,IAAI,EACJ,YAAiB,EACjB,MAAgB,EAChB,YAAY,GACb,GAAE,kBAAuB;IA0H1B,UAAU,EAAE;QACV,CAAC,CAAC,SAAS,YAAY,EACrB,OAAO,EAAE,aAAa,CAAC,kBAAkB,CAAC,CAAC,CAAC,CAAC,EAC7C,MAAM,CAAC,EAAE,kBAAkB,CAAC,CAAC,CAAC,GAC7B,YAAY,CAAA;QACf,CAAC,CAAC,EAAE,OAAO,EAAE,aAAa,CAAC,CAAC,CAAC,EAAE,MAAM,CAAC,EAAE,CAAC,GAAG,YAAY,CAAA;KACzD,CAGA;IAGD,MAAM,wBAEL;IAGD,OAAO,wBAEN;IAGD,IAAI,MAAM,IAAI,MAAM,CAEnB;IAGD,IAAI,QAAQ,YAEX;IAGD,WAAW,GAAI,OAAO,OAAO,UAW5B;IAGD,WAAW,eAEV;IAGD,QAAQ,GAAI,aAAa,MAAM,EAAE,aAAa,MAAM,UAMnD;IAGD,QAAQ,aAMP;IAGD,EAAE,GAAI,IAAI,CAAC,GAAG,EAAE,eAAe,KAAK,IAAI,UAavC;IAGD,IAAI,cAAc,YAejB;IAGD,iBAAiB,GAAI,SAAS,OAAO,UAcpC;IAGD,gBAAgB,YAwBf;IAGD,gBAAgB,GAAI,OAAO,OAAO,CAAC,aAAa,CAAC,UAchD;CACF"}
1
+ {"version":3,"file":"builder.d.ts","sourceRoot":"","sources":["../../../src/core/builder.ts"],"names":[],"mappings":"AACA,OAAO,EACL,KAAK,YAAY,EAEjB,MAAM,EAKP,MAAM,wBAAwB,CAAA;AAI/B,OAAO,EAGL,KAAK,eAAe,EACrB,MAAM,mCAAmC,CAAA;AAO1C,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,YAAY,CAAA;AACpD,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,mBAAmB,CAAA;AAEtD,OAAO,EAAE,YAAY,EAAE,MAAM,YAAY,CAAA;AAEzC,OAAO,EAEL,KAAK,aAAa,EACnB,MAAM,0CAA0C,CAAA;AAEjD,OAAO,EAAc,KAAK,iBAAiB,EAAE,MAAM,UAAU,CAAA;AAU7D,MAAM,WAAW,kBAAkB;IAIjC,IAAI,CAAC,EAAE,IAAI,GAAG,MAAM,GAAG,IAAI,CAAA;IAG3B,YAAY,CAAC,EAAE,YAAY,CAAA;IAG3B,MAAM,CAAC,EAAE,MAAM,CAAA;IAGf,YAAY,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,iBAAiB,CAAC,CAAC,CAAA;CAC1D;AAID,qBAAa,YAAY;;gBAYX,EACV,IAAI,EACJ,YAAiB,EACjB,MAAgB,EAChB,YAAY,GACb,GAAE,kBAAuB;IA0H1B,UAAU,EAAE;QACV,CAAC,CAAC,SAAS,YAAY,EACrB,OAAO,EAAE,aAAa,CAAC,kBAAkB,CAAC,CAAC,CAAC,CAAC,EAC7C,MAAM,CAAC,EAAE,kBAAkB,CAAC,CAAC,CAAC,GAC7B,YAAY,CAAA;QACf,CAAC,CAAC,EAAE,OAAO,EAAE,aAAa,CAAC,CAAC,CAAC,EAAE,MAAM,CAAC,EAAE,CAAC,GAAG,YAAY,CAAA;KACzD,CAGA;IAGD,MAAM,wBAEL;IAGD,OAAO,wBAEN;IAGD,IAAI,MAAM,IAAI,MAAM,CAEnB;IAGD,IAAI,QAAQ,YAEX;IAGD,WAAW,GAAI,OAAO,OAAO,UAW5B;IAGD,WAAW,eAEV;IAGD,QAAQ,GAAI,aAAa,MAAM,EAAE,aAAa,MAAM,UAMnD;IAGD,QAAQ,aAMP;IAGD,EAAE,GAAI,IAAI,CAAC,GAAG,EAAE,eAAe,KAAK,IAAI,UAavC;IAGD,IAAI,cAAc,YAejB;IAGD,iBAAiB,GAAI,SAAS,OAAO,UAcpC;IAGD,gBAAgB,YAwBf;IAGD,gBAAgB,GAAI,OAAO,OAAO,CAAC,aAAa,CAAC,UAchD;CACF"}
@@ -1 +1 @@
1
- {"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../../../../src/feature/fixed-toolbar/config.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,uBAAuB,CAAA;AAkChD,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,aAAa,CAAA;AAC/C,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAA;AACpD,OAAO,KAAK,EAAE,yBAAyB,EAAE,MAAM,SAAS,CAAA;AAqDxD,wBAAgB,wBAAwB,CACtC,OAAO,EAAE,YAAY,CAAC,WAAW,CAAC,EAClC,OAAO,CAAC,EAAE,yBAAyB,EACnC,GAAG,CAAC,EAAE,GAAG;;;;;;;;IAmhBV"}
1
+ {"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../../../../src/feature/fixed-toolbar/config.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,uBAAuB,CAAA;AAkChD,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,aAAa,CAAA;AAC/C,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAA;AACpD,OAAO,KAAK,EAAE,yBAAyB,EAAE,MAAM,SAAS,CAAA;AAqDxD,wBAAgB,wBAAwB,CACtC,OAAO,EAAE,YAAY,CAAC,WAAW,CAAC,EAClC,OAAO,CAAC,EAAE,yBAAyB,EACnC,GAAG,CAAC,EAAE,GAAG;;;;;;;;IAyiBV"}
@@ -29,8 +29,10 @@ export interface FixedToolbarConfig {
29
29
  showHistory?: boolean;
30
30
  showExport?: boolean;
31
31
  onExport?: (markdown: string, ctx: Ctx) => void;
32
+ exportItems?: ('markdown' | 'word' | 'pdf')[];
32
33
  showImport?: boolean;
33
34
  onImport?: (replaceContent: (markdown: string) => void, ctx: Ctx) => void;
35
+ importItems?: ('markdown' | 'word' | 'pdf')[];
34
36
  useLocalStorage?: boolean;
35
37
  id?: string;
36
38
  outlineVisible?: boolean;
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/feature/fixed-toolbar/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,uBAAuB,CAAA;AAIhD,OAAO,EAAU,SAAS,EAAiB,MAAM,+BAA+B,CAAA;AAehF,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,aAAa,CAAA;AAC/C,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,WAAW,CAAA;AAC9C,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAA;AAUpD,OAAO,EAAoC,KAAK,WAAW,EAAE,MAAM,mBAAmB,CAAA;AAEtF,MAAM,WAAW,kBAAkB;IACjC,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,iBAAiB,CAAC,EAAE,MAAM,CAAA;IAC1B,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,YAAY,CAAC,EAAE,CAAC,OAAO,EAAE,YAAY,CAAC,WAAW,CAAC,KAAK,IAAI,CAAA;IAC3D,eAAe,CAAC,EAAE,MAAM,GAAG,OAAO,CAAA;IAClC,aAAa,CAAC,EAAE,CAAC,IAAI,EAAE,IAAI,KAAK,OAAO,CAAC,MAAM,CAAC,CAAA;IAC/C,kBAAkB,CAAC,EAAE,MAAM,EAAE,CAAA;IAC7B,WAAW,CAAC,EAAE,OAAO,CAAA;IACrB,YAAY,CAAC,EAAE;QACb,IAAI,CAAC,EAAE,OAAO,CAAA;QACd,IAAI,CAAC,EAAE,OAAO,CAAA;QACd,IAAI,CAAC,EAAE,OAAO,CAAA;QACd,MAAM,CAAC,EAAE,OAAO,CAAA;QAChB,MAAM,CAAC,EAAE,OAAO,CAAA;KACjB,CAAA;IACD,WAAW,CAAC,EAAE,OAAO,CAAA;IACrB,UAAU,CAAC,EAAE,OAAO,CAAA;IACpB,QAAQ,CAAC,EAAE,CAAC,QAAQ,EAAE,MAAM,EAAE,GAAG,EAAE,GAAG,KAAK,IAAI,CAAA;IAC/C,UAAU,CAAC,EAAE,OAAO,CAAA;IACpB,QAAQ,CAAC,EAAE,CAAC,cAAc,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,IAAI,EAAE,GAAG,EAAE,GAAG,KAAK,IAAI,CAAA;IACzE,eAAe,CAAC,EAAE,OAAO,CAAA;IACzB,EAAE,CAAC,EAAE,MAAM,CAAA;IACX,cAAc,CAAC,EAAE,OAAO,CAAA;IACxB,YAAY,CAAC,EAAE,MAAM,CAAA;IACrB,kBAAkB,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IAClC,SAAS,CAAC,EAAE,OAAO,CAAA;IACnB,SAAS,CAAC,EAAE,OAAO,CAAA;IACnB,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,WAAW,CAAC,EAAE,WAAW,CAAA;CAC1B;AAED,MAAM,MAAM,yBAAyB,GAAG,OAAO,CAAC,kBAAkB,CAAC,CAAA;AAEnE,eAAO,MAAM,kBAAkB,0FAG9B,CAAA;AAED,eAAO,MAAM,eAAe,gBAA0C,CAAA;AA+WtE,eAAO,MAAM,kBAAkB,sCAK7B,CAAA;AAEF,eAAO,MAAM,YAAY,EAAE,aAAa,CAAC,yBAAyB,CA6BjE,CAAA"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/feature/fixed-toolbar/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,uBAAuB,CAAA;AAIhD,OAAO,EAAU,SAAS,EAAiB,MAAM,+BAA+B,CAAA;AAehF,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,aAAa,CAAA;AAC/C,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,WAAW,CAAA;AAC9C,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAA;AAUpD,OAAO,EAGL,KAAK,WAAW,EACjB,MAAM,mBAAmB,CAAA;AAE1B,MAAM,WAAW,kBAAkB;IACjC,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,iBAAiB,CAAC,EAAE,MAAM,CAAA;IAC1B,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,YAAY,CAAC,EAAE,CAAC,OAAO,EAAE,YAAY,CAAC,WAAW,CAAC,KAAK,IAAI,CAAA;IAC3D,eAAe,CAAC,EAAE,MAAM,GAAG,OAAO,CAAA;IAClC,aAAa,CAAC,EAAE,CAAC,IAAI,EAAE,IAAI,KAAK,OAAO,CAAC,MAAM,CAAC,CAAA;IAC/C,kBAAkB,CAAC,EAAE,MAAM,EAAE,CAAA;IAC7B,WAAW,CAAC,EAAE,OAAO,CAAA;IACrB,YAAY,CAAC,EAAE;QACb,IAAI,CAAC,EAAE,OAAO,CAAA;QACd,IAAI,CAAC,EAAE,OAAO,CAAA;QACd,IAAI,CAAC,EAAE,OAAO,CAAA;QACd,MAAM,CAAC,EAAE,OAAO,CAAA;QAChB,MAAM,CAAC,EAAE,OAAO,CAAA;KACjB,CAAA;IACD,WAAW,CAAC,EAAE,OAAO,CAAA;IACrB,UAAU,CAAC,EAAE,OAAO,CAAA;IACpB,QAAQ,CAAC,EAAE,CAAC,QAAQ,EAAE,MAAM,EAAE,GAAG,EAAE,GAAG,KAAK,IAAI,CAAA;IAC/C,WAAW,CAAC,EAAE,CAAC,UAAU,GAAG,MAAM,GAAG,KAAK,CAAC,EAAE,CAAA;IAC7C,UAAU,CAAC,EAAE,OAAO,CAAA;IACpB,QAAQ,CAAC,EAAE,CAAC,cAAc,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,IAAI,EAAE,GAAG,EAAE,GAAG,KAAK,IAAI,CAAA;IACzE,WAAW,CAAC,EAAE,CAAC,UAAU,GAAG,MAAM,GAAG,KAAK,CAAC,EAAE,CAAA;IAC7C,eAAe,CAAC,EAAE,OAAO,CAAA;IACzB,EAAE,CAAC,EAAE,MAAM,CAAA;IACX,cAAc,CAAC,EAAE,OAAO,CAAA;IACxB,YAAY,CAAC,EAAE,MAAM,CAAA;IACrB,kBAAkB,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IAClC,SAAS,CAAC,EAAE,OAAO,CAAA;IACnB,SAAS,CAAC,EAAE,OAAO,CAAA;IACnB,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,WAAW,CAAC,EAAE,WAAW,CAAA;CAC1B;AAED,MAAM,MAAM,yBAAyB,GAAG,OAAO,CAAC,kBAAkB,CAAC,CAAA;AAEnE,eAAO,MAAM,kBAAkB,0FAG9B,CAAA;AAED,eAAO,MAAM,eAAe,gBAA0C,CAAA;AAoXtE,eAAO,MAAM,kBAAkB,sCAK7B,CAAA;AAEF,eAAO,MAAM,YAAY,EAAE,aAAa,CAAC,yBAAyB,CA6BjE,CAAA"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jvs-milkdown/crepe",
3
- "version": "1.2.24",
3
+ "version": "1.2.26",
4
4
  "keywords": [
5
5
  "crepe",
6
6
  "editor",
@@ -107,9 +107,9 @@
107
107
  "@codemirror/theme-one-dark": "^6.1.2",
108
108
  "@codemirror/view": "^6.26.0",
109
109
  "@floating-ui/dom": "^1.7.6",
110
- "@jvs-milkdown/kit": "^1.2.24",
111
- "@jvs-milkdown/prose": "^1.2.24",
112
- "@jvs-milkdown/utils": "^1.2.24",
110
+ "@jvs-milkdown/kit": "^1.2.26",
111
+ "@jvs-milkdown/prose": "^1.2.26",
112
+ "@jvs-milkdown/utils": "^1.2.26",
113
113
  "@types/lodash-es": "^4.17.12",
114
114
  "clsx": "^2.0.0",
115
115
  "codemirror": "^6.0.1",
@@ -27,6 +27,10 @@ import type { DefineFeature } from '../feature/shared'
27
27
 
28
28
  import { CrepeFeature } from '../feature'
29
29
  import { attachmentConfig } from '../feature/attachment/config'
30
+ import {
31
+ viewMenuStateCtx,
32
+ type ViewMenuState,
33
+ } from '../feature/fixed-toolbar/view-menu-state'
30
34
  import { inlineDiffApiCtx } from '../feature/inline-diff/config'
31
35
  import { zhCN, enUS, type CrepeTranslations } from './locale'
32
36
  import {
@@ -36,7 +40,6 @@ import {
36
40
  translationsCtx,
37
41
  useCrepeFeatures,
38
42
  } from './slice'
39
- import { viewMenuStateCtx, type ViewMenuState } from '../feature/fixed-toolbar/view-menu-state'
40
43
 
41
44
  /// The crepe builder configuration.
42
45
  export interface CrepeBuilderConfig {
@@ -25,3 +25,18 @@ test('should use custom config to override the default config', () => {
25
25
  mockCppLanguageDescription,
26
26
  ])
27
27
  })
28
+
29
+ test('should apply exportItems and importItems config to FixedToolbar', () => {
30
+ const myConfig = applyConfig({
31
+ [CrepeFeature.FixedToolbar]: {
32
+ exportItems: ['markdown', 'word'],
33
+ importItems: ['markdown'],
34
+ },
35
+ })
36
+
37
+ expect(myConfig[CrepeFeature.FixedToolbar]?.exportItems).toEqual([
38
+ 'markdown',
39
+ 'word',
40
+ ])
41
+ expect(myConfig[CrepeFeature.FixedToolbar]?.importItems).toEqual(['markdown'])
42
+ })
@@ -3,7 +3,7 @@ import type { Node as ProseNode } from '@jvs-milkdown/kit/prose/model'
3
3
 
4
4
  import { imageBlockSchema } from '@jvs-milkdown/kit/component/image-block'
5
5
  import { toggleLinkCommand } from '@jvs-milkdown/kit/component/link-tooltip'
6
- import { commandsCtx, editorViewCtx } from '@jvs-milkdown/kit/core'
6
+ import { commandsCtx, editorViewCtx, rootCtx } from '@jvs-milkdown/kit/core'
7
7
  import {
8
8
  addBlockTypeCommand,
9
9
  blockquoteSchema,
@@ -564,7 +564,18 @@ export function buildDefaultFixedToolbar(
564
564
  active: () => false,
565
565
  onRun: (ctx) => {
566
566
  const markdown = getMarkdown()(ctx)
567
- if (_config?.onExport) {
567
+ const view = ctx.get(editorViewCtx)
568
+ const root = ctx.get(rootCtx) as HTMLElement
569
+ const rootNode = view.dom.getRootNode() as ShadowRoot | Document
570
+
571
+ if (_config?.exportItems && _config.exportItems.length > 0) {
572
+ const exportItems = _config.exportItems
573
+ if (exportItems.length === 1) {
574
+ handleDirectExport(exportItems[0]!, markdown, view, root)
575
+ } else {
576
+ showDownloadPopover(rootNode, root, view, markdown, exportItems)
577
+ }
578
+ } else if (_config?.onExport) {
568
579
  _config.onExport(markdown, ctx)
569
580
  } else {
570
581
  const blob = new Blob([markdown], {
@@ -587,7 +598,18 @@ export function buildDefaultFixedToolbar(
587
598
  icon: importIcon,
588
599
  active: () => false,
589
600
  onRun: (ctx) => {
590
- if (_config?.onImport) {
601
+ const view = ctx.get(editorViewCtx)
602
+ const root = ctx.get(rootCtx) as HTMLElement
603
+ const rootNode = view.dom.getRootNode() as ShadowRoot | Document
604
+
605
+ if (_config?.importItems && _config.importItems.length > 0) {
606
+ const importItems = _config.importItems
607
+ if (importItems.length === 1) {
608
+ handleDirectImport(importItems[0]!, root, ctx)
609
+ } else {
610
+ showImportPopover(rootNode, root, ctx, importItems)
611
+ }
612
+ } else if (_config?.onImport) {
591
613
  _config.onImport((markdown) => replaceAll(markdown)(ctx), ctx)
592
614
  } else {
593
615
  const input = document.createElement('input')
@@ -622,3 +644,460 @@ export function buildDefaultFixedToolbar(
622
644
 
623
645
  return builder.build()
624
646
  }
647
+
648
+ const activeDownloadPopovers = new WeakMap<HTMLElement, HTMLElement>()
649
+ const activeImportPopovers = new WeakMap<HTMLElement, HTMLElement>()
650
+
651
+ const ensureStyles = (rootNode: ShadowRoot | Document) => {
652
+ const target = rootNode instanceof ShadowRoot ? rootNode : document.head
653
+ if (target.querySelector('#download-popover-styles')) return
654
+
655
+ const styleEl = document.createElement('style')
656
+ styleEl.id = 'download-popover-styles'
657
+ styleEl.textContent = `
658
+ .download-popover, .import-popover {
659
+ position: fixed;
660
+ z-index: 10000;
661
+ width: 140px;
662
+ padding: 6px 0;
663
+ display: flex;
664
+ flex-direction: column;
665
+ background-color: var(--crepe-color-surface, #ffffff);
666
+ border: 1px solid var(--crepe-color-outline-variant, color-mix(in srgb, var(--crepe-color-outline), transparent 80%));
667
+ border-radius: 8px;
668
+ box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
669
+ opacity: 0;
670
+ transform: translateY(-8px);
671
+ transition: opacity 0.15s ease, transform 0.15s ease;
672
+ pointer-events: none;
673
+ font-family: system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
674
+ }
675
+ .download-popover.show, .import-popover.show {
676
+ opacity: 1;
677
+ transform: translateY(0);
678
+ pointer-events: auto;
679
+ }
680
+ .download-popover-item, .import-popover-item {
681
+ cursor: pointer;
682
+ padding: 8px 14px;
683
+ display: flex;
684
+ align-items: center;
685
+ gap: 8px;
686
+ font-size: 13px;
687
+ font-weight: 500;
688
+ color: var(--crepe-color-primary, #363B4C);
689
+ transition: background-color 0.2s;
690
+ user-select: none;
691
+ }
692
+ .download-popover-item:hover, .import-popover-item:hover {
693
+ background-color: var(--crepe-color-hover, #f5f5f5);
694
+ }
695
+ .download-popover-item-icon, .import-popover-item-icon {
696
+ display: flex;
697
+ align-items: center;
698
+ justify-content: center;
699
+ width: 14px;
700
+ height: 14px;
701
+ color: var(--crepe-color-primary, #363B4C);
702
+ opacity: 0.8;
703
+ }
704
+ `
705
+ target.appendChild(styleEl)
706
+ }
707
+
708
+ function handleDirectExport(
709
+ type: 'markdown' | 'word' | 'pdf',
710
+ markdown: string,
711
+ view: any,
712
+ root: HTMLElement
713
+ ) {
714
+ if (type === 'markdown') {
715
+ const blob = new Blob([markdown], { type: 'text/markdown;charset=utf-8;' })
716
+ const url = URL.createObjectURL(blob)
717
+ const link = document.createElement('a')
718
+ link.href = url
719
+ link.download = 'document.md'
720
+ link.click()
721
+ URL.revokeObjectURL(url)
722
+ }
723
+
724
+ const event = new CustomEvent('download-click', {
725
+ detail: {
726
+ type,
727
+ markdown,
728
+ html: view.dom.innerHTML || '',
729
+ },
730
+ bubbles: true,
731
+ composed: true,
732
+ })
733
+ root.dispatchEvent(event)
734
+ }
735
+
736
+ function handleDirectImport(
737
+ type: 'markdown' | 'word' | 'pdf',
738
+ root: HTMLElement,
739
+ ctx: Ctx
740
+ ) {
741
+ if (type === 'markdown') {
742
+ const input = document.createElement('input')
743
+ input.type = 'file'
744
+ input.accept = '.md'
745
+ input.onchange = (evt) => {
746
+ const file = (evt.target as HTMLInputElement).files?.[0]
747
+ if (!file) return
748
+ file
749
+ .text()
750
+ .then((text) => {
751
+ replaceAll(text)(ctx)
752
+
753
+ const event = new CustomEvent('import-click', {
754
+ detail: { type: 'markdown', file },
755
+ bubbles: true,
756
+ composed: true,
757
+ })
758
+ root.dispatchEvent(event)
759
+ })
760
+ .catch((err) => {
761
+ console.error('Failed to read file:', err)
762
+ })
763
+ }
764
+ input.click()
765
+ } else {
766
+ const event = new CustomEvent('import-click', {
767
+ detail: { type },
768
+ bubbles: true,
769
+ composed: true,
770
+ })
771
+ root.dispatchEvent(event)
772
+ }
773
+ }
774
+
775
+ function showDownloadPopover(
776
+ rootNode: ShadowRoot | Document,
777
+ root: HTMLElement,
778
+ view: any,
779
+ markdown: string,
780
+ exportItems: ('markdown' | 'word' | 'pdf')[]
781
+ ) {
782
+ const button = root.querySelector('button[data-key="export"]') as HTMLElement
783
+ if (!button) return
784
+
785
+ const existing = activeDownloadPopovers.get(button)
786
+ if (existing) {
787
+ ;(existing as any).closePopover()
788
+ return
789
+ }
790
+
791
+ ensureStyles(rootNode)
792
+
793
+ const container = rootNode instanceof ShadowRoot ? rootNode : document.body
794
+ const popover = document.createElement('div')
795
+ popover.className = 'download-popover'
796
+
797
+ let popoverHtml = ''
798
+ if (exportItems.includes('markdown')) {
799
+ popoverHtml += `
800
+ <div class="download-popover-item" id="download-md-btn">
801
+ <span class="download-popover-item-icon">
802
+ <svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><path d="M14.5 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V7.5L14.5 2z"></path><polyline points="14 2 14 8 20 8"></polyline><line x1="16" y1="13" x2="8" y2="13"></line><line x1="16" y1="17" x2="8" y2="17"></line><line x1="10" y1="9" x2="8" y2="9"></line></svg>
803
+ </span>
804
+ <span>下载 MD</span>
805
+ </div>
806
+ `
807
+ }
808
+ if (exportItems.includes('word')) {
809
+ popoverHtml += `
810
+ <div class="download-popover-item" id="download-word-btn">
811
+ <span class="download-popover-item-icon">
812
+ <svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"></path><polyline points="14 2 14 8 20 8"></polyline><line x1="16" y1="13" x2="8" y2="13"></line><line x1="16" y1="17" x2="8" y2="17"></line><polyline points="10 9 9 9 8 9"></polyline></svg>
813
+ </span>
814
+ <span>下载 WORD</span>
815
+ </div>
816
+ `
817
+ }
818
+ if (exportItems.includes('pdf')) {
819
+ popoverHtml += `
820
+ <div class="download-popover-item" id="download-pdf-btn">
821
+ <span class="download-popover-item-icon">
822
+ <svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><polyline points="6 9 6 2 18 2 18 9"></polyline><path d="M6 18H4a2 2 0 0 1-2-2v-5a2 2 0 0 1 2-2h16a2 2 0 0 1 2 2v5a2 2 0 0 1-2 2h-2"></path><rect x="6" y="14" width="12" height="8"></rect></svg>
823
+ </span>
824
+ <span>下载 PDF</span>
825
+ </div>
826
+ `
827
+ }
828
+ popover.innerHTML = popoverHtml
829
+
830
+ container.appendChild(popover)
831
+ activeDownloadPopovers.set(button, popover)
832
+
833
+ const updatePosition = () => {
834
+ const rect = button.getBoundingClientRect()
835
+ popover.style.top = `${rect.bottom + 4}px`
836
+ popover.style.left = `${rect.left + (rect.width - 140) / 2}px`
837
+ }
838
+
839
+ updatePosition()
840
+
841
+ requestAnimationFrame(() => {
842
+ popover.classList.add('show')
843
+ })
844
+
845
+ const closePopover = () => {
846
+ popover.classList.remove('show')
847
+ activeDownloadPopovers.delete(button)
848
+ container.removeEventListener('pointerdown', handleContainerClick, true)
849
+ document.removeEventListener('pointerdown', handleOuterClick, true)
850
+ window.removeEventListener('resize', updatePosition)
851
+ popover.addEventListener(
852
+ 'transitionend',
853
+ () => {
854
+ popover.remove()
855
+ },
856
+ { once: true }
857
+ )
858
+ }
859
+ ;(popover as any).closePopover = closePopover
860
+
861
+ const handleContainerClick = (e: Event) => {
862
+ const target = e.target as HTMLElement
863
+ if (
864
+ !popover.contains(target) &&
865
+ target !== button &&
866
+ !button.contains(target)
867
+ ) {
868
+ closePopover()
869
+ }
870
+ }
871
+
872
+ const handleOuterClick = (e: Event) => {
873
+ const target = e.target as HTMLElement
874
+ if (target !== root && !root.contains(target)) {
875
+ closePopover()
876
+ }
877
+ }
878
+
879
+ popover.addEventListener('pointerdown', (e) => {
880
+ e.stopPropagation()
881
+ })
882
+
883
+ container.addEventListener('pointerdown', handleContainerClick, true)
884
+ document.addEventListener('pointerdown', handleOuterClick, true)
885
+ window.addEventListener('resize', updatePosition)
886
+
887
+ popover.querySelector('#download-md-btn')?.addEventListener('click', (e) => {
888
+ e.stopPropagation()
889
+ const blob = new Blob([markdown], { type: 'text/markdown;charset=utf-8;' })
890
+ const url = URL.createObjectURL(blob)
891
+ const link = document.createElement('a')
892
+ link.href = url
893
+ link.download = 'document.md'
894
+ link.click()
895
+ URL.revokeObjectURL(url)
896
+ closePopover()
897
+
898
+ const event = new CustomEvent('download-click', {
899
+ detail: {
900
+ type: 'markdown',
901
+ markdown,
902
+ html: view.dom.innerHTML || '',
903
+ },
904
+ bubbles: true,
905
+ composed: true,
906
+ })
907
+ root.dispatchEvent(event)
908
+ })
909
+
910
+ popover
911
+ .querySelector('#download-word-btn')
912
+ ?.addEventListener('click', (e) => {
913
+ e.stopPropagation()
914
+ closePopover()
915
+ const event = new CustomEvent('download-click', {
916
+ detail: {
917
+ type: 'word',
918
+ markdown,
919
+ html: view.dom.innerHTML || '',
920
+ },
921
+ bubbles: true,
922
+ composed: true,
923
+ })
924
+ root.dispatchEvent(event)
925
+ })
926
+
927
+ popover.querySelector('#download-pdf-btn')?.addEventListener('click', (e) => {
928
+ e.stopPropagation()
929
+ closePopover()
930
+ const event = new CustomEvent('download-click', {
931
+ detail: {
932
+ type: 'pdf',
933
+ markdown,
934
+ html: view.dom.innerHTML || '',
935
+ },
936
+ bubbles: true,
937
+ composed: true,
938
+ })
939
+ root.dispatchEvent(event)
940
+ })
941
+ }
942
+
943
+ function showImportPopover(
944
+ rootNode: ShadowRoot | Document,
945
+ root: HTMLElement,
946
+ ctx: Ctx,
947
+ importItems: ('markdown' | 'word' | 'pdf')[]
948
+ ) {
949
+ const button = root.querySelector('button[data-key="import"]') as HTMLElement
950
+ if (!button) return
951
+
952
+ const existing = activeImportPopovers.get(button)
953
+ if (existing) {
954
+ ;(existing as any).closePopover()
955
+ return
956
+ }
957
+
958
+ ensureStyles(rootNode)
959
+
960
+ const container = rootNode instanceof ShadowRoot ? rootNode : document.body
961
+ const popover = document.createElement('div')
962
+ popover.className = 'import-popover'
963
+
964
+ let popoverHtml = ''
965
+ if (importItems.includes('markdown')) {
966
+ popoverHtml += `
967
+ <div class="import-popover-item" id="import-md-btn">
968
+ <span class="import-popover-item-icon">
969
+ <svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><path d="M14.5 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V7.5L14.5 2z"></path><polyline points="14 2 14 8 20 8"></polyline><line x1="16" y1="13" x2="8" y2="13"></line><line x1="16" y1="17" x2="8" y2="17"></line><line x1="10" y1="9" x2="8" y2="9"></line></svg>
970
+ </span>
971
+ <span>导入 MD</span>
972
+ </div>
973
+ `
974
+ }
975
+ if (importItems.includes('word')) {
976
+ popoverHtml += `
977
+ <div class="import-popover-item" id="import-word-btn">
978
+ <span class="import-popover-item-icon">
979
+ <svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"></path><polyline points="14 2 14 8 20 8"></polyline><line x1="16" y1="13" x2="8" y2="13"></line><line x1="16" y1="17" x2="8" y2="17"></line><polyline points="10 9 9 9 8 9"></polyline></svg>
980
+ </span>
981
+ <span>导入 WORD</span>
982
+ </div>
983
+ `
984
+ }
985
+ if (importItems.includes('pdf')) {
986
+ popoverHtml += `
987
+ <div class="import-popover-item" id="import-pdf-btn">
988
+ <span class="import-popover-item-icon">
989
+ <svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><polyline points="6 9 6 2 18 2 18 9"></polyline><path d="M6 18H4a2 2 0 0 1-2-2v-5a2 2 0 0 1 2-2h16a2 2 0 0 1 2 2v5a2 2 0 0 1-2 2h-2"></path><rect x="6" y="14" width="12" height="8"></rect></svg>
990
+ </span>
991
+ <span>导入 PDF</span>
992
+ </div>
993
+ `
994
+ }
995
+ popover.innerHTML = popoverHtml
996
+
997
+ container.appendChild(popover)
998
+ activeImportPopovers.set(button, popover)
999
+
1000
+ const updatePosition = () => {
1001
+ const rect = button.getBoundingClientRect()
1002
+ popover.style.top = `${rect.bottom + 4}px`
1003
+ popover.style.left = `${rect.left + (rect.width - 140) / 2}px`
1004
+ }
1005
+
1006
+ updatePosition()
1007
+
1008
+ requestAnimationFrame(() => {
1009
+ popover.classList.add('show')
1010
+ })
1011
+
1012
+ const closePopover = () => {
1013
+ popover.classList.remove('show')
1014
+ activeImportPopovers.delete(button)
1015
+ container.removeEventListener('pointerdown', handleContainerClick, true)
1016
+ document.removeEventListener('pointerdown', handleOuterClick, true)
1017
+ window.removeEventListener('resize', updatePosition)
1018
+ popover.addEventListener(
1019
+ 'transitionend',
1020
+ () => {
1021
+ popover.remove()
1022
+ },
1023
+ { once: true }
1024
+ )
1025
+ }
1026
+ ;(popover as any).closePopover = closePopover
1027
+
1028
+ const handleContainerClick = (e: Event) => {
1029
+ const target = e.target as HTMLElement
1030
+ if (
1031
+ !popover.contains(target) &&
1032
+ target !== button &&
1033
+ !button.contains(target)
1034
+ ) {
1035
+ closePopover()
1036
+ }
1037
+ }
1038
+
1039
+ const handleOuterClick = (e: Event) => {
1040
+ const target = e.target as HTMLElement
1041
+ if (target !== root && !root.contains(target)) {
1042
+ closePopover()
1043
+ }
1044
+ }
1045
+
1046
+ popover.addEventListener('pointerdown', (e) => {
1047
+ e.stopPropagation()
1048
+ })
1049
+
1050
+ container.addEventListener('pointerdown', handleContainerClick, true)
1051
+ document.addEventListener('pointerdown', handleOuterClick, true)
1052
+ window.addEventListener('resize', updatePosition)
1053
+
1054
+ popover.querySelector('#import-md-btn')?.addEventListener('click', (e) => {
1055
+ e.stopPropagation()
1056
+ closePopover()
1057
+ const input = document.createElement('input')
1058
+ input.type = 'file'
1059
+ input.accept = '.md'
1060
+ input.onchange = (evt) => {
1061
+ const file = (evt.target as HTMLInputElement).files?.[0]
1062
+ if (!file) return
1063
+ file
1064
+ .text()
1065
+ .then((text) => {
1066
+ replaceAll(text)(ctx)
1067
+
1068
+ const event = new CustomEvent('import-click', {
1069
+ detail: { type: 'markdown', file },
1070
+ bubbles: true,
1071
+ composed: true,
1072
+ })
1073
+ root.dispatchEvent(event)
1074
+ })
1075
+ .catch((err) => {
1076
+ console.error('Failed to read file:', err)
1077
+ })
1078
+ }
1079
+ input.click()
1080
+ })
1081
+
1082
+ popover.querySelector('#import-word-btn')?.addEventListener('click', (e) => {
1083
+ e.stopPropagation()
1084
+ closePopover()
1085
+ const event = new CustomEvent('import-click', {
1086
+ detail: { type: 'word' },
1087
+ bubbles: true,
1088
+ composed: true,
1089
+ })
1090
+ root.dispatchEvent(event)
1091
+ })
1092
+
1093
+ popover.querySelector('#import-pdf-btn')?.addEventListener('click', (e) => {
1094
+ e.stopPropagation()
1095
+ closePopover()
1096
+ const event = new CustomEvent('import-click', {
1097
+ detail: { type: 'pdf' },
1098
+ bubbles: true,
1099
+ composed: true,
1100
+ })
1101
+ root.dispatchEvent(event)
1102
+ })
1103
+ }
@@ -29,7 +29,11 @@ import { FixedToolbarComponent } from './component'
29
29
  import { buildDefaultFixedToolbar } from './config'
30
30
  import { DocumentHeader } from './document-header'
31
31
  import { OutlinePanel } from './outline-panel'
32
- import { viewMenuStateCtx, editorWidthMap, type EditorWidth } from './view-menu-state'
32
+ import {
33
+ viewMenuStateCtx,
34
+ editorWidthMap,
35
+ type EditorWidth,
36
+ } from './view-menu-state'
33
37
 
34
38
  export interface FixedToolbarConfig {
35
39
  boldIcon?: string
@@ -56,8 +60,10 @@ export interface FixedToolbarConfig {
56
60
  showHistory?: boolean
57
61
  showExport?: boolean
58
62
  onExport?: (markdown: string, ctx: Ctx) => void
63
+ exportItems?: ('markdown' | 'word' | 'pdf')[]
59
64
  showImport?: boolean
60
65
  onImport?: (replaceContent: (markdown: string) => void, ctx: Ctx) => void
66
+ importItems?: ('markdown' | 'word' | 'pdf')[]
61
67
  useLocalStorage?: boolean
62
68
  id?: string
63
69
  outlineVisible?: boolean
@@ -102,14 +108,19 @@ class FixedToolbarView implements PluginView {
102
108
  const config = ctx.get(fixedToolbarConfig.key)
103
109
  const viewState = ctx.get(viewMenuStateCtx.key)
104
110
 
105
- if (config?.outlineVisible !== undefined) viewState.outlineVisible = config.outlineVisible
106
- if (config?.outlinePosition !== undefined) viewState.outlinePosition = config.outlinePosition
107
- if (config?.outlineWidth !== undefined) viewState.outlineWidth = config.outlineWidth
108
- if (config?.documentBackground !== undefined) viewState.documentBackground = config.documentBackground
111
+ if (config?.outlineVisible !== undefined)
112
+ viewState.outlineVisible = config.outlineVisible
113
+ if (config?.outlinePosition !== undefined)
114
+ viewState.outlinePosition = config.outlinePosition
115
+ if (config?.outlineWidth !== undefined)
116
+ viewState.outlineWidth = config.outlineWidth
117
+ if (config?.documentBackground !== undefined)
118
+ viewState.documentBackground = config.documentBackground
109
119
  if (config?.showTitle !== undefined) viewState.showTitle = config.showTitle
110
120
  if (config?.showCover !== undefined) viewState.showCover = config.showCover
111
121
  if (config?.coverUrl !== undefined) viewState.coverUrl = config.coverUrl
112
- if (config?.editorWidth !== undefined) viewState.editorWidth = config.editorWidth
122
+ if (config?.editorWidth !== undefined)
123
+ viewState.editorWidth = config.editorWidth
113
124
 
114
125
  // Load initial view menu state from localStorage if useLocalStorage is enabled
115
126
  if (config?.useLocalStorage) {
@@ -93,7 +93,7 @@
93
93
  color-mix(in srgb, var(--crepe-color-outline), transparent 80%)
94
94
  );
95
95
  padding: 0 10px;
96
- z-index: 101;
96
+ z-index: 150;
97
97
  position: sticky;
98
98
  top: 0;
99
99
  flex-shrink: 0;