Skip to content

Commit

Permalink
Merge pull request #11 from madeyoga/v0.2.0
Browse files Browse the repository at this point in the history
Add image upload command
  • Loading branch information
madeyoga authored Oct 23, 2024
2 parents 85b7e10 + 5b02280 commit 92f94b8
Show file tree
Hide file tree
Showing 7 changed files with 234 additions and 48 deletions.
2 changes: 1 addition & 1 deletion dist/chunmde.bundle.min.js

Large diffs are not rendered by default.

14 changes: 13 additions & 1 deletion index.html
Original file line number Diff line number Diff line change
Expand Up @@ -48,11 +48,23 @@ <h2>Preview:</h2>

<script src="dist/chunmde.bundle.min.js"></script>
<script>
let editor = new ChunMDE('editor-container', {
const { createChunEditor, createImageUploadPlugin } = Chun

const imageUploadPlugin = createImageUploadPlugin({
imageUploadUrl: "",
imageFormats: ["image/jpg", "image/jpeg", "image/gif", "image/png", "image/bmp", "image/webp"],
})

const editor = createChunEditor({
doc: initialContent.value,
lineWrapping: true,
indentWithTab: true,
toolbar: true,
})
.use(imageUploadPlugin)
.mount("editor-container")

console.log(editor)
</script>

</body>
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "chunmde",
"version": "0.1.0",
"version": "0.2.0",
"description": "Markdown editor based on codemirror 6",
"main": "dist/chunmde.bundle.js",
"type": "module",
Expand Down
6 changes: 5 additions & 1 deletion src/components/Toolbar.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ export class Toolbar {
public buttonActions: ToolbarButton[]
public dom: Element

constructor(editor: EditorView) {
constructor(editor: EditorView, toolbarItems: ((editor: EditorView) => HTMLElement)[] = []) {
this.buttonActions = [
{
text: "Add heading text",
Expand Down Expand Up @@ -79,6 +79,10 @@ export class Toolbar {
toolbarElement.appendChild(buttonElement)
}

for (let createToolbarItem of toolbarItems) {
toolbarElement.appendChild(createToolbarItem(editor))
}

this.dom = toolbarElement
}
}
Expand Down
145 changes: 101 additions & 44 deletions src/index.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { markdown, markdownLanguage } from "@codemirror/lang-markdown"
import { EditorState, EditorStateConfig } from '@codemirror/state'
import { keymap, ViewUpdate, EditorView } from '@codemirror/view'
import { keymap, ViewUpdate, EditorView, KeyBinding } from '@codemirror/view'
import { basicSetup } from 'codemirror'
import { indentWithTab } from "@codemirror/commands"
import { italicKeyBinding } from './commands/Italic'
Expand All @@ -10,6 +10,8 @@ import { linkKeyBinding } from "./commands/Link"
import { quoteKeyBinding } from "./commands/Quote"
import { ulKeyBinding } from "./commands/BulletedList"
import { Toolbar } from "./components/Toolbar"
import IEditorPlugin from "./plugins/IEditorPlugin"
import { createImageUploadPlugin } from "./plugins/ImageUpload"

interface ChunInterface {
dom: Element,
Expand All @@ -20,59 +22,39 @@ interface ChunInterface {

interface ChunConfig extends EditorStateConfig {
onUpdateListener?: (update: ViewUpdate) => void,
indentWithTab?: boolean,
lineWrapping?: boolean,
toolbar: boolean,
indentWithTab: boolean,
lineWrapping: boolean,
toolbarItems: ((editor: EditorView) => HTMLElement)[],
}

function ChunMDE(this: ChunInterface, containerId: string, customConfig?: ChunConfig) {
const ChunMDE = function ChunMDE(this: ChunInterface, containerId: string, customConfig: ChunConfig = {
doc: "",
toolbar: true,
indentWithTab: true,
lineWrapping: false,
toolbarItems: []
}) {
const parentElement = document.getElementById(containerId) as Element

const defaultKeybinds = [
italicKeyBinding,
boldKeyBinding,
codeKeyBinding,
linkKeyBinding,
quoteKeyBinding,
ulKeyBinding,
]

const defaultExtensions = [
keymap.of(defaultKeybinds),
markdown({ base: markdownLanguage }),
basicSetup,
]

let config: EditorStateConfig = {
doc: "Start writing!",
extensions: defaultExtensions,
}

if (customConfig) {
if (customConfig.onUpdateListener) {
defaultExtensions.push(EditorView.updateListener.of(customConfig.onUpdateListener!))
}
if (customConfig.lineWrapping) {
defaultExtensions.push(EditorView.lineWrapping)
}
if (customConfig.indentWithTab) {
defaultExtensions.push(keymap.of([indentWithTab]))
}
config.doc = customConfig.doc ? customConfig.doc : config.doc
config.extensions = customConfig.extensions ? customConfig.extensions : defaultExtensions
const config: EditorStateConfig = {
doc: customConfig.doc,
extensions: customConfig.extensions,
}

/** CodeMirror6's EditorView */
const editorView = new EditorView({
// parent: parentElement,
state: EditorState.create(config)
})

parentElement.className += " chunmde-container"

// toolbar
const toolbar = new Toolbar(editorView)

parentElement.appendChild(toolbar.dom)
if (customConfig.toolbar) {
const toolbar = new Toolbar(editorView, customConfig.toolbarItems)
parentElement.appendChild(toolbar.dom)
this.toolbar = toolbar
}
parentElement.appendChild(editorView.dom)

/** Shortcut to get the editor value */
Expand All @@ -81,14 +63,89 @@ function ChunMDE(this: ChunInterface, containerId: string, customConfig?: ChunCo
}

this.dom = parentElement
this.toolbar = toolbar
this.editor = editorView
} as any as { new (containerId: string, customConfig?: ChunConfig): ChunInterface; }

interface IEditorBuilder {
use: (plugin: IEditorPlugin) => IEditorBuilder;
mount: (selector: string) => void;
}

export function createChunEditor(customConfig: ChunConfig = {
doc: "",
toolbar: true,
indentWithTab: true,
lineWrapping: false,
toolbarItems: [],
}): IEditorBuilder {

const defaultKeybinds = [
italicKeyBinding,
boldKeyBinding,
codeKeyBinding,
linkKeyBinding,
quoteKeyBinding,
ulKeyBinding,
]

const defaultExtensions = [
keymap.of(defaultKeybinds),
markdown({ base: markdownLanguage }),
basicSetup,
]

const keybinds: KeyBinding[] = []
const toolbarItemDelegates: ((editor: EditorView) => HTMLButtonElement)[] = []

return {
use(plugin) {
if (plugin.keybind) {
keybinds.push(plugin.keybind)
}

if (plugin.createToolbarItem) {
toolbarItemDelegates.push(plugin.createToolbarItem)
}

return this
},
mount(selector) {

if (customConfig.onUpdateListener) {
defaultExtensions.push(EditorView.updateListener.of(customConfig.onUpdateListener!))
}
if (customConfig.lineWrapping) {
defaultExtensions.push(EditorView.lineWrapping)
}
if (customConfig.indentWithTab) {
keybinds.push(indentWithTab)
}

defaultExtensions.push(keymap.of(keybinds))
customConfig.extensions = defaultExtensions
customConfig.toolbarItems = toolbarItemDelegates

return new ChunMDE(selector, customConfig)
},
}
}

interface IGlobalChunEditor {
createChunEditor: () => IEditorBuilder,
createImageUploadPlugin: (imageUploadUrl: string, imageFormats: string[]) => IEditorPlugin,
}

declare global {
interface Window { ChunMDE: typeof ChunMDE; }
interface Window { ChunMDE: typeof ChunMDE; Chun: IGlobalChunEditor }
}

window.ChunMDE = ChunMDE;
const Chun = {
createChunEditor,
createImageUploadPlugin,
}

window.ChunMDE = ChunMDE
window.Chun = Chun;

export default Chun;

export default ChunMDE;
9 changes: 9 additions & 0 deletions src/plugins/IEditorPlugin.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import { EditorView } from "@codemirror/view";

export default interface IEditorPlugin {
createToolbarItem?: (editor: EditorView) => HTMLButtonElement,
keybind?: {
key: string;
run: (view: EditorView) => boolean;
},
}
104 changes: 104 additions & 0 deletions src/plugins/ImageUpload.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
import { EditorView } from "@codemirror/view";
import IEditorPlugin from "./IEditorPlugin"
import { EditorSelection } from "@codemirror/state";
import { trimSelection } from "../commands/Utilities";


function wrapImageUrl(view: EditorView, imageUrl: string) {

const transaction = view.state.changeByRange((range) => {
const originalText = view.state.sliceDoc(range.from, range.to)
const { text, rangeFrom, rangeTo } = trimSelection(originalText, range)

const newText = `![${text}](${imageUrl})`

const transaction = {
changes: {
from: rangeFrom,
insert: newText,
to: rangeTo,
},
range: EditorSelection.range(rangeFrom + 2, rangeFrom + 2 + text.length),
}

return transaction
})

view.dispatch(transaction)
return
}


export const createImageUploadPlugin: (config: any) => IEditorPlugin = (
config: any = {
imageUploadUrl: "",
imageFormats: ["image/jpg", "image/jpeg", "image/gif", "image/png", "image/bmp", "image/webp"],
csrfToken: "",
csrfFieldName: "",
}
) => {

return {
createToolbarItem: (editor: EditorView) => {

const fileInput = document.createElement("input")
fileInput.type = "file"
fileInput.style.display = "none";

fileInput.setAttribute("accept", config.imageFormats.join(","))
fileInput.addEventListener("change", async (event) => {
const files = (event.target as HTMLInputElement).files
if (files && files[0]) {
const formData = new FormData();

formData.append('image', files[0]);

if (config.csrfFieldName && config.csrfToken) {
formData.append(config.csrfFieldName, config.csrfToken)
}

const resp = await fetch(config.imageUploadUrl, {
method: "POST",
body: formData,
})

if (resp.ok) {
const obj = await resp.json()
wrapImageUrl(editor, obj.url)
}
else {
const text = await resp.text()
alert("Error uploading image: " + resp.status + resp.statusText + text)
}
}
})

const icon = document.createElement("iconify-icon")
icon.className += "iconify "
icon.setAttribute("icon", "mdi:image-outline")
icon.setAttribute("inline", "false")
icon.setAttribute("width", "16")
icon.setAttribute("height", "16")

const buttonElement = document.createElement("button")
buttonElement.appendChild(icon)
buttonElement.setAttribute("alt", "Select and upload an image")
buttonElement.setAttribute("title", "Select and upload an image")
buttonElement.setAttribute("type", "button")
buttonElement.addEventListener("click", () => {
fileInput.click()
})

buttonElement.appendChild(fileInput)

return buttonElement
},
// keybind: {
// key: "",
// run: (editor) => {
// fileInput.click()
// return true
// },
// }
}
}

0 comments on commit 92f94b8

Please sign in to comment.