stencil ace markdown editor

卓能文發表於2024-08-22

src/components/ace-markdown/ace-markdown.tsx:

import { Component, Prop, State, h } from "@stencil/core";
import ace from "ace-builds/src-min-noconflict/ace.js";
import { marked } from "marked";

@Component({
	tag: "ace-markdown",
	shadow: true,
})
export class AceMarkdown {
	containerElement!: HTMLDivElement;
	@Prop({ mutable: true })
	content = "# Hello world";
	private editor!: any;
	editorElement!: HTMLDivElement;
	@State()
	fullscreen = false;
	previewerElement!: HTMLDivElement;

	componentDidLoad() {
		ace.config.set("basePath", "/");
		const editor = ace.edit(this.editorElement, {
			// fontSize: "14px",
			mode: "ace/mode/markdown",
			// theme: "ace/theme/monokai",
			value: this.content,
			wrap: true,
		});
		this.editor = editor;
		editor.renderer.attachToShadowRoot(); // !!!important
		editor.on("input", () => {
			this.updatePreview();
		});
		this.updatePreview();
	}

	fullscreenIcon() {
		return this.fullscreen ? "-" : "+";
	}

	fullscreenSwitch() {
		this.fullscreen = !this.fullscreen;
		if (this.fullscreen) {
			this.containerElement.requestFullscreen();
			this.containerElement.style.height = "100vh";
		} else {
			document.exitFullscreen();
			this.containerElement.style.height = "40vh";
		}
	}

	render() {
		return (
			<div
				ref={(el) => {
					this.containerElement  = el as HTMLDivElement;
				}}
				style={{ height: "40vh" }}
			>
				<div>
					<div style={{ display: "flex", "justify-content": "end" }}>
						<button
							onClick={() => {
								this.save();
							}}
							type="button"
						>
							下載
						</button>
						<button
							onClick={() => {
								this.fullscreenSwitch();
							}}
							title="全屏/還原"
							type="button"
						>
							{this.fullscreenIcon()}
						</button>
					</div>
				</div>
				<div style={{ display: "flex", height: "100%" }}>
					<div style={{ flex: "50%" }}>
						<div
							ref={(el) => {
								this.editorElement = el as HTMLDivElement;
							}}
							style={{ width: "100%", height: "100%" }}
						>
							{this.content}
						</div>
					</div>
					<div style={{ flex: "50%" }}>
						<div
							ref={(el) => {
								this.previewerElement = el as HTMLDivElement;
							}}
							style={{ width: "100%", height: "100%", overflow: "auto" }}
						/>
					</div>
				</div>
			</div>
		);
	}

	save() {
		const fileParts = [this.content];
		const blob = new Blob(fileParts, { type: "text/plain" });
		const a = document.createElement("a");
		a.href = URL.createObjectURL(blob);
		a.download = "paper.md";
		document.body.appendChild(a);
		a.click();
		document.body.removeChild(a);
	}

	updatePreview() {
		this.content = this.editor.getValue();
		this.previewerElement.innerHTML = marked(this.content).toString();
	}
}

justfile:

build:
    #!/usr/bin/env bash
    cp node_modules/ace-builds/src-min-noconflict/mode-markdown.js www/
    cp node_modules/ace-builds/src-min-noconflict/theme-monokai.js www/

相關文章