@blocksuite/editor
Advanced tools
Comparing version 0.3.0-20221224062401-54874d6 to 0.3.0-20221224075546-5606029
import { LitElement } from 'lit'; | ||
import type { Page } from '@blocksuite/store'; | ||
import { Page } from '@blocksuite/store'; | ||
import { ClipboardManager, ContentParser } from '../../index.js'; | ||
@@ -15,5 +15,3 @@ import type { MouseMode, PageBlockModel } from '@blocksuite/blocks'; | ||
private _disposables; | ||
private _subscribeStore; | ||
createRenderRoot(): this; | ||
private _handleSwitchMouseMode; | ||
connectedCallback(): void; | ||
@@ -20,0 +18,0 @@ disconnectedCallback(): void; |
@@ -10,2 +10,4 @@ var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) { | ||
import { choose } from 'lit/directives/choose.js'; | ||
import { Signal } from '@blocksuite/store'; | ||
import { DisposableGroup } from '@blocksuite/store'; | ||
import { ClipboardManager, ContentParser } from '../../index.js'; | ||
@@ -23,6 +25,3 @@ let EditorContainer = class EditorContainer extends LitElement { | ||
this.contentParser = new ContentParser(this); | ||
this._disposables = []; | ||
this._handleSwitchMouseMode = ({ detail }) => { | ||
this.mouseMode = detail; | ||
}; | ||
this._disposables = new DisposableGroup(); | ||
} | ||
@@ -32,8 +31,2 @@ get model() { | ||
} | ||
_subscribeStore() { | ||
const rootAddedDisposable = this.page.signals.rootAdded.on(() => { | ||
this.requestUpdate(); | ||
}); | ||
this._disposables.push(rootAddedDisposable); | ||
} | ||
// disable shadow DOM to workaround quill | ||
@@ -45,12 +38,19 @@ createRenderRoot() { | ||
super.connectedCallback(); | ||
window.addEventListener('keydown', e => { | ||
// Question: Why do we prevent this? | ||
this._disposables.add(Signal.fromEvent(window, 'keydown').on(e => { | ||
if (e.altKey && e.metaKey && e.code === 'KeyC') { | ||
e.preventDefault(); | ||
} | ||
}); | ||
})); | ||
if (!this.page) { | ||
throw new Error('Missing page for EditorContainer!'); | ||
} | ||
window.addEventListener('affine.switch-mouse-mode', this._handleSwitchMouseMode); | ||
this._subscribeStore(); | ||
// connect mouse mode event changes | ||
this._disposables.add(Signal.fromEvent(window, 'affine.switch-mouse-mode').on(({ detail }) => { | ||
this.mouseMode = detail; | ||
})); | ||
// subscribe store | ||
this._disposables.add(this.page.signals.rootAdded.on(() => { | ||
this.requestUpdate(); | ||
})); | ||
this._placeholderInput?.focus(); | ||
@@ -60,4 +60,4 @@ } | ||
super.disconnectedCallback(); | ||
window.removeEventListener('affine.switch-mouse-mode', this._handleSwitchMouseMode); | ||
this._disposables.forEach(disposable => disposable.dispose()); | ||
this._disposables.dispose(); | ||
this._disposables = new DisposableGroup(); | ||
} | ||
@@ -64,0 +64,0 @@ render() { |
@@ -27,3 +27,3 @@ import { marked } from 'marked'; | ||
const htmlContent = this.block2Html(this._getSelectedBlock(root).children); | ||
FileExporter.exportMarkdown(root.title, htmlContent); | ||
FileExporter.exportHtmlAsMarkdown(root.title, htmlContent); | ||
} | ||
@@ -30,0 +30,0 @@ block2Html(blocks) { |
@@ -1,9 +0,26 @@ | ||
declare const FileExporter: { | ||
injectHtmlCss: () => string; | ||
exportFile: (filename: string, text: string, format: string) => void; | ||
decorateHtml: (pageTitle: string, htmlContent: string) => string; | ||
exportHtml: (pageTitle: string, htmlContent: string) => void; | ||
exportMarkdown: (pageTitle: string, htmlContent: string) => void; | ||
/** Tools for exporting files to device. For example, via browser download. */ | ||
export declare const FileExporter: { | ||
/** | ||
* Create a download for the user's browser. | ||
* | ||
* @param mimeType like `"text/plain"`, `"text/html"`, `"application/javascript"`, etc. See {@link https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/MIME_types/Common_types mdn docs List of MIME types}. | ||
* | ||
* @remarks | ||
* Only accepts data in utf-8 encoding (html files, javascript source, text files, etc). | ||
* | ||
* @example | ||
* const todoMDText = `# Todo items | ||
* [ ] Item 1 | ||
* [ ] Item 2 | ||
* ` | ||
* FileExporter.exportFile("Todo list.md", todoMDText, "text/plain") | ||
* | ||
* @example | ||
* const stateJsonContent = JSON.stringify({ a: 1, b: 2, c: 3 }) | ||
* FileExporter.exportFile("state.json", jsonContent, "application/json") | ||
*/ | ||
exportTextFile(filename: string, text: string, mimeType: string): void; | ||
exportHtml(pageTitle: string | undefined, htmlContent: string): void; | ||
exportHtmlAsMarkdown(pageTitle: string | undefined, htmlContent: string): void; | ||
}; | ||
export { FileExporter }; | ||
//# sourceMappingURL=file-exporter.d.ts.map |
import TurndownService from 'turndown'; | ||
const FileExporter = { | ||
injectHtmlCss: () => { | ||
//TODO why not use css file? | ||
return ` | ||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/normalize/8.0.1/normalize.min.css"> | ||
<style> | ||
:root { | ||
--affine-primary-color: #3a4c5c; | ||
--affine-font-family: Avenir Next, apple-system, BlinkMacSystemFont, Helvetica Neue, Tahoma, PingFang SC, Microsoft Yahei, Arial, Hiragino Sans GB, sans-serif, Apple Color Emoji, Segoe UI Emoji, Segoe UI Symbol, Noto Color Emoji; | ||
--affine-font-family2: Roboto Mono, apple-system, BlinkMacSystemFont, Helvetica Neue, Tahoma, PingFang SC, Microsoft Yahei, Arial, Hiragino Sans GB, sans-serif, Apple Color Emoji, Segoe UI Emoji, Segoe UI Symbol, Noto Color Emoji; | ||
} | ||
body { | ||
font-family: var(--affine-font-family); | ||
color: var(--affine-primary-color); | ||
} | ||
</style> | ||
`; | ||
}, | ||
exportFile: (filename, text, format) => { | ||
// Context: Lean towards breaking out any localizable content into constants so it's | ||
// easier to track content we may need to localize in the future. (i18n) | ||
const UNTITLED_PAGE_NAME = 'Untitled'; | ||
/** Tools for exporting files to device. For example, via browser download. */ | ||
export const FileExporter = { | ||
/** | ||
* Create a download for the user's browser. | ||
* | ||
* @param mimeType like `"text/plain"`, `"text/html"`, `"application/javascript"`, etc. See {@link https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/MIME_types/Common_types mdn docs List of MIME types}. | ||
* | ||
* @remarks | ||
* Only accepts data in utf-8 encoding (html files, javascript source, text files, etc). | ||
* | ||
* @example | ||
* const todoMDText = `# Todo items | ||
* [ ] Item 1 | ||
* [ ] Item 2 | ||
* ` | ||
* FileExporter.exportFile("Todo list.md", todoMDText, "text/plain") | ||
* | ||
* @example | ||
* const stateJsonContent = JSON.stringify({ a: 1, b: 2, c: 3 }) | ||
* FileExporter.exportFile("state.json", jsonContent, "application/json") | ||
*/ | ||
exportTextFile(filename, text, mimeType) { | ||
const element = document.createElement('a'); | ||
element.setAttribute('href', 'data:' + format + ';charset=utf-8,' + encodeURIComponent(text)); | ||
element.setAttribute('href', 'data:' + mimeType + ';charset=utf-8,' + encodeURIComponent(text)); | ||
// Consider if we should replace invalid characters in filenames before downloading, or if the browser | ||
// will do that for us automatically... | ||
// // replace illegal characters that cannot appear in file names | ||
// const safeFilename = filename.replace(/[ <>:/|?*]+/g, " ") | ||
element.setAttribute('download', filename); | ||
@@ -29,22 +39,7 @@ element.style.display = 'none'; | ||
}, | ||
decorateHtml: (pageTitle, htmlContent) => { | ||
const htmlCss = FileExporter.injectHtmlCss(); | ||
return `<!DOCTYPE html> | ||
<html lang="en"> | ||
<head> | ||
<meta charset="UTF-8"> | ||
<title>${pageTitle}</title> | ||
${htmlCss} | ||
</head> | ||
<body> | ||
<div style="margin:0 auto;width:720px" > | ||
${htmlContent} | ||
</div> | ||
</body> | ||
</html>`; | ||
exportHtml(pageTitle, htmlContent) { | ||
const title = pageTitle?.trim() || UNTITLED_PAGE_NAME; | ||
FileExporter.exportTextFile(title + '.html', wrapHtmlWithHtmlDocumentText(title, htmlContent), 'text/html'); | ||
}, | ||
exportHtml: (pageTitle, htmlContent) => { | ||
FileExporter.exportFile((pageTitle || 'Untitled') + '.html', FileExporter.decorateHtml(pageTitle, htmlContent), 'text/html'); | ||
}, | ||
exportMarkdown: (pageTitle, htmlContent) => { | ||
exportHtmlAsMarkdown(pageTitle, htmlContent) { | ||
const turndownService = new TurndownService(); | ||
@@ -60,6 +55,37 @@ turndownService.addRule('input', { | ||
const markdown = turndownService.turndown(htmlContent); | ||
FileExporter.exportFile((pageTitle || 'Undefined') + '.md', markdown, 'text/plain'); | ||
const title = pageTitle?.trim() || UNTITLED_PAGE_NAME; | ||
FileExporter.exportTextFile(title + '.md', markdown, 'text/plain'); | ||
}, | ||
}; | ||
export { FileExporter }; | ||
/** @internal surround plain html content in a document with head and basic styles */ | ||
function wrapHtmlWithHtmlDocumentText(pageTitle, htmlContent) { | ||
// Question: Why not embed css directly into html? | ||
const htmlCss = `<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/normalize/8.0.1/normalize.min.css"> | ||
<style> | ||
:root { | ||
--affine-primary-color: #3a4c5c; | ||
--affine-font-family: Avenir Next, apple-system, BlinkMacSystemFont, Helvetica Neue, Tahoma, PingFang SC, Microsoft Yahei, Arial, Hiragino Sans GB, sans-serif, Apple Color Emoji, Segoe UI Emoji, Segoe UI Symbol, Noto Color Emoji; | ||
--affine-font-family2: Roboto Mono, apple-system, BlinkMacSystemFont, Helvetica Neue, Tahoma, PingFang SC, Microsoft Yahei, Arial, Hiragino Sans GB, sans-serif, Apple Color Emoji, Segoe UI Emoji, Segoe UI Symbol, Noto Color Emoji; | ||
} | ||
body { | ||
font-family: var(--affine-font-family); | ||
color: var(--affine-primary-color); | ||
} | ||
</style>`; | ||
// Question: Do we really need the extra div container? | ||
return `<!DOCTYPE html> | ||
<html lang="en"> | ||
<head> | ||
<meta charset="UTF-8"> | ||
<title>${pageTitle}</title> | ||
${htmlCss} | ||
</head> | ||
<body> | ||
<div style="margin:0 auto;padding:1rem;max-width:720px"> | ||
${htmlContent} | ||
</div> | ||
</body> | ||
</html> | ||
`; | ||
} | ||
//# sourceMappingURL=file-exporter.js.map |
{ | ||
"name": "@blocksuite/editor", | ||
"version": "0.3.0-20221224062401-54874d6", | ||
"version": "0.3.0-20221224075546-5606029", | ||
"description": "Default BlockSuite-based editor built for AFFiNE.", | ||
@@ -11,4 +11,4 @@ "main": "dist/index.js", | ||
"dependencies": { | ||
"@blocksuite/blocks": "0.3.0-20221224062401-54874d6", | ||
"@blocksuite/store": "0.3.0-20221224062401-54874d6", | ||
"@blocksuite/blocks": "0.3.0-20221224075546-5606029", | ||
"@blocksuite/store": "0.3.0-20221224075546-5606029", | ||
"lit": "^2.3.1", | ||
@@ -15,0 +15,0 @@ "marked": "^4.1.0", |
@@ -5,3 +5,4 @@ import { html, LitElement } from 'lit'; | ||
import type { Page, Disposable } from '@blocksuite/store'; | ||
import { Page, Signal } from '@blocksuite/store'; | ||
import { DisposableGroup } from '@blocksuite/store'; | ||
import { ClipboardManager, ContentParser } from '../../index.js'; | ||
@@ -40,11 +41,4 @@ import type { MouseMode, PageBlockModel } from '@blocksuite/blocks'; | ||
private _disposables: Disposable[] = []; | ||
private _disposables = new DisposableGroup(); | ||
private _subscribeStore() { | ||
const rootAddedDisposable = this.page.signals.rootAdded.on(() => { | ||
this.requestUpdate(); | ||
}); | ||
this._disposables.push(rootAddedDisposable); | ||
} | ||
// disable shadow DOM to workaround quill | ||
@@ -55,14 +49,13 @@ createRenderRoot() { | ||
private _handleSwitchMouseMode = ({ detail }: CustomEvent<MouseMode>) => { | ||
this.mouseMode = detail; | ||
}; | ||
override connectedCallback() { | ||
super.connectedCallback(); | ||
window.addEventListener('keydown', e => { | ||
if (e.altKey && e.metaKey && e.code === 'KeyC') { | ||
e.preventDefault(); | ||
} | ||
}); | ||
// Question: Why do we prevent this? | ||
this._disposables.add( | ||
Signal.fromEvent(window, 'keydown').on(e => { | ||
if (e.altKey && e.metaKey && e.code === 'KeyC') { | ||
e.preventDefault(); | ||
} | ||
}) | ||
); | ||
@@ -73,8 +66,15 @@ if (!this.page) { | ||
window.addEventListener( | ||
'affine.switch-mouse-mode', | ||
this._handleSwitchMouseMode | ||
// connect mouse mode event changes | ||
this._disposables.add( | ||
Signal.fromEvent(window, 'affine.switch-mouse-mode').on(({ detail }) => { | ||
this.mouseMode = detail; | ||
}) | ||
); | ||
this._subscribeStore(); | ||
// subscribe store | ||
this._disposables.add( | ||
this.page.signals.rootAdded.on(() => { | ||
this.requestUpdate(); | ||
}) | ||
); | ||
@@ -86,8 +86,4 @@ this._placeholderInput?.focus(); | ||
super.disconnectedCallback(); | ||
window.removeEventListener( | ||
'affine.switch-mouse-mode', | ||
this._handleSwitchMouseMode | ||
); | ||
this._disposables.forEach(disposable => disposable.dispose()); | ||
this._disposables.dispose(); | ||
this._disposables = new DisposableGroup(); | ||
} | ||
@@ -94,0 +90,0 @@ |
@@ -39,3 +39,6 @@ import { marked } from 'marked'; | ||
const htmlContent = this.block2Html(this._getSelectedBlock(root).children); | ||
FileExporter.exportMarkdown((root as PageBlockModel).title, htmlContent); | ||
FileExporter.exportHtmlAsMarkdown( | ||
(root as PageBlockModel).title, | ||
htmlContent | ||
); | ||
} | ||
@@ -42,0 +45,0 @@ |
import TurndownService from 'turndown'; | ||
const FileExporter = { | ||
injectHtmlCss: () => { | ||
//TODO why not use css file? | ||
return ` | ||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/normalize/8.0.1/normalize.min.css"> | ||
<style> | ||
:root { | ||
--affine-primary-color: #3a4c5c; | ||
--affine-font-family: Avenir Next, apple-system, BlinkMacSystemFont, Helvetica Neue, Tahoma, PingFang SC, Microsoft Yahei, Arial, Hiragino Sans GB, sans-serif, Apple Color Emoji, Segoe UI Emoji, Segoe UI Symbol, Noto Color Emoji; | ||
--affine-font-family2: Roboto Mono, apple-system, BlinkMacSystemFont, Helvetica Neue, Tahoma, PingFang SC, Microsoft Yahei, Arial, Hiragino Sans GB, sans-serif, Apple Color Emoji, Segoe UI Emoji, Segoe UI Symbol, Noto Color Emoji; | ||
} | ||
body { | ||
font-family: var(--affine-font-family); | ||
color: var(--affine-primary-color); | ||
} | ||
</style> | ||
`; | ||
}, | ||
exportFile: (filename: string, text: string, format: string) => { | ||
// Context: Lean towards breaking out any localizable content into constants so it's | ||
// easier to track content we may need to localize in the future. (i18n) | ||
const UNTITLED_PAGE_NAME = 'Untitled'; | ||
/** Tools for exporting files to device. For example, via browser download. */ | ||
export const FileExporter = { | ||
/** | ||
* Create a download for the user's browser. | ||
* | ||
* @param mimeType like `"text/plain"`, `"text/html"`, `"application/javascript"`, etc. See {@link https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/MIME_types/Common_types mdn docs List of MIME types}. | ||
* | ||
* @remarks | ||
* Only accepts data in utf-8 encoding (html files, javascript source, text files, etc). | ||
* | ||
* @example | ||
* const todoMDText = `# Todo items | ||
* [ ] Item 1 | ||
* [ ] Item 2 | ||
* ` | ||
* FileExporter.exportFile("Todo list.md", todoMDText, "text/plain") | ||
* | ||
* @example | ||
* const stateJsonContent = JSON.stringify({ a: 1, b: 2, c: 3 }) | ||
* FileExporter.exportFile("state.json", jsonContent, "application/json") | ||
*/ | ||
exportTextFile(filename: string, text: string, mimeType: string) { | ||
const element = document.createElement('a'); | ||
element.setAttribute( | ||
'href', | ||
'data:' + format + ';charset=utf-8,' + encodeURIComponent(text) | ||
'data:' + mimeType + ';charset=utf-8,' + encodeURIComponent(text) | ||
); | ||
// Consider if we should replace invalid characters in filenames before downloading, or if the browser | ||
// will do that for us automatically... | ||
// // replace illegal characters that cannot appear in file names | ||
// const safeFilename = filename.replace(/[ <>:/|?*]+/g, " ") | ||
element.setAttribute('download', filename); | ||
@@ -36,27 +47,11 @@ | ||
}, | ||
decorateHtml: (pageTitle: string, htmlContent: string) => { | ||
const htmlCss = FileExporter.injectHtmlCss(); | ||
return `<!DOCTYPE html> | ||
<html lang="en"> | ||
<head> | ||
<meta charset="UTF-8"> | ||
<title>${pageTitle}</title> | ||
${htmlCss} | ||
</head> | ||
<body> | ||
<div style="margin:0 auto;width:720px" > | ||
${htmlContent} | ||
</div> | ||
</body> | ||
</html>`; | ||
}, | ||
exportHtml: (pageTitle: string, htmlContent: string) => { | ||
FileExporter.exportFile( | ||
(pageTitle || 'Untitled') + '.html', | ||
FileExporter.decorateHtml(pageTitle, htmlContent), | ||
exportHtml(pageTitle: string | undefined, htmlContent: string) { | ||
const title = pageTitle?.trim() || UNTITLED_PAGE_NAME; | ||
FileExporter.exportTextFile( | ||
title + '.html', | ||
wrapHtmlWithHtmlDocumentText(title, htmlContent), | ||
'text/html' | ||
); | ||
}, | ||
exportMarkdown: (pageTitle: string, htmlContent: string) => { | ||
exportHtmlAsMarkdown(pageTitle: string | undefined, htmlContent: string) { | ||
const turndownService = new TurndownService(); | ||
@@ -72,10 +67,37 @@ turndownService.addRule('input', { | ||
const markdown = turndownService.turndown(htmlContent); | ||
FileExporter.exportFile( | ||
(pageTitle || 'Undefined') + '.md', | ||
markdown, | ||
'text/plain' | ||
); | ||
const title = pageTitle?.trim() || UNTITLED_PAGE_NAME; | ||
FileExporter.exportTextFile(title + '.md', markdown, 'text/plain'); | ||
}, | ||
}; | ||
export { FileExporter }; | ||
/** @internal surround plain html content in a document with head and basic styles */ | ||
function wrapHtmlWithHtmlDocumentText(pageTitle: string, htmlContent: string) { | ||
// Question: Why not embed css directly into html? | ||
const htmlCss = `<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/normalize/8.0.1/normalize.min.css"> | ||
<style> | ||
:root { | ||
--affine-primary-color: #3a4c5c; | ||
--affine-font-family: Avenir Next, apple-system, BlinkMacSystemFont, Helvetica Neue, Tahoma, PingFang SC, Microsoft Yahei, Arial, Hiragino Sans GB, sans-serif, Apple Color Emoji, Segoe UI Emoji, Segoe UI Symbol, Noto Color Emoji; | ||
--affine-font-family2: Roboto Mono, apple-system, BlinkMacSystemFont, Helvetica Neue, Tahoma, PingFang SC, Microsoft Yahei, Arial, Hiragino Sans GB, sans-serif, Apple Color Emoji, Segoe UI Emoji, Segoe UI Symbol, Noto Color Emoji; | ||
} | ||
body { | ||
font-family: var(--affine-font-family); | ||
color: var(--affine-primary-color); | ||
} | ||
</style>`; | ||
// Question: Do we really need the extra div container? | ||
return `<!DOCTYPE html> | ||
<html lang="en"> | ||
<head> | ||
<meta charset="UTF-8"> | ||
<title>${pageTitle}</title> | ||
${htmlCss} | ||
</head> | ||
<body> | ||
<div style="margin:0 auto;padding:1rem;max-width:720px"> | ||
${htmlContent} | ||
</div> | ||
</body> | ||
</html> | ||
`; | ||
} |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
322510
4468
+ Added@blocksuite/blocks@0.3.0-20221224075546-5606029(transitive)
+ Added@blocksuite/store@0.3.0-20221224075546-5606029(transitive)
- Removed@blocksuite/blocks@0.3.0-20221224062401-54874d6(transitive)
- Removed@blocksuite/store@0.3.0-20221224062401-54874d6(transitive)