In this post we'll create a small website that will permit the user to edit files with monaco-editor, select the file format and download it on their computer.
Features that we'll implement:
- file editing
- file name editing
- language picker
- download button
Project setup
First, let's install Svelte:
npx sv create myapp
Make sure to select typescript.
Now move into the new folder and install monaco-editor:
pnpm install -D @monaco-editor/loader
Coding time!
Adding the editor into the window
Let's create a MonacoEditor.svelte
into our components
folder:
<script lang="ts">
import { type Monaco } from '@monaco-editor/loader'
import { onDestroy, onMount } from 'svelte'
let container: HTMLElement
let monaco = $state<Monaco | undefined>()
onMount(async () => {
// we'll setup monaco here
})
onDestroy(() => {
// we need to provide a way to delete our component, so our editor too
})
</script>
<div id="editor-container" bind:this={container}></div>
<style>
/* some styles */
#editor-container {
width: 100%;
height: 600px;
}
</style>
So we need to implement the functions that will setup our editor and delete it properly.
So let's create a file .src/lib/editor.ts
:
import loader, { type Monaco } from '@monaco-editor/loader'
type SetupEditorOptions = {
content?: string
language?: string
}
export async function setupEditor(container: HTMLElement, options: SetupEditorOptions) {
}
export function deleteEditor(monaco: Monaco) {
}
As you can see, to setup the editor, will need a HTML container and a default content and language.
For the removing system, we need to get all different editors of monaco and delete them. Here's the implementation:
export async function setupEditor(container: HTMLElement, options: SetupEditorOptions) {
const monaco = await loader.init()
monaco.editor.create(container, {
value: options.content ?? "",
language: options.language ?? "text"
})
return monaco
}
export function deleteEditor(monaco: Monaco) {
const editors = monaco?.editor.getEditors()
monaco?.editor.getModels().forEach((model) => model.dispose())
editors?.forEach(editor => editor.dispose())
}
In the MonacoEditor
we have now:
<script lang="ts">
import { type Monaco } from '@monaco-editor/loader'
import { onDestroy, onMount } from 'svelte'
import { deleteEditor, setupEditor } from '$lib/editor'
let container: HTMLElement
let monaco = $state<Monaco | undefined>()
onMount(async () => {
monaco = await setupEditor(container, {})
})
onDestroy(() => {
if (monaco) deleteEditor(monaco)
})
</script>
Now we can put our MonacoEditor
component into our ./src/pages/+page.svelte
(our homepage):
<script>
import MonacoEditor from "../components/MonacoEditor.svelte"
</script>
<MonacoEditor />
And run, you can see that here's an editor in your window:
Change the editor's language
Now we want to have a <select>
that will permit to the user to change the coloration of the text.
We create ./src/components/SelectLangForm.svelte
:
<script lang="ts">
let { monaco } = $props()
let lang = $state("text")
$effect(() => {
})
</script>
<select bind:value={lang}>
<option value={"javascript"}>js</option>
<option value={"python"}>py</option>
<option value={"text"}>txt</option>
<!-- ... -->
</select>
We want to implement a function that will update the language of the editor, so in our ./src/lib/editor.ts
, we'll add this:
export function setLang(monaco: Monaco, language: string) {
// we are getting the last editor to stock their text content and HTML container
const lastEditor = monaco.editor.getEditors()[0]
const lastEditorValue = lastEditor.getValue()
deleteEditor(monaco) // we don't need it anymore, so we delete him
// we don't have any editor left, so let's create another
monaco.editor.create(lastEditor.getContainerDomNode(), {
value: lastEditorValue,
language: language
})
return monaco
}
Now our SelectLangForm
looks like this:
<script lang="ts">
import { setLang } from "$lib/editor"
// ...
$effect(() => {
// if lang changes, change it in the editor too
if (monaco) monaco = setLang(monaco, lang)
})
</script>
Now we need to call this component into MonacoEditor
:
<script lang="ts">
// ...
import SelectLangForm from './SelectLangForm.svelte'
// ....
</script>
<div class="banner">
<SelectLangForm {monaco} />
</div>
You can run now and test it!
Customize file name
Now we want to edit the file name.
So we need to create a new component called FileNameInput.svelte
:
<script>
let { fileName = $bindable() } = $props()
</script>
<input type="text" bind:value={fileName}>
We need the fileName
property bindable for reactivity.
Now let's integrate this new component into our main one:
<script lang="ts">
// ...
import SelectLangForm from './SelectLangForm.svelte'
import FileNameInput from './FileNameInput.svelte'
let container: HTMLElement
let monaco = $state<Monaco | undefined>()
let fileName = $state("untitled.txt") // we declare our state here, with a default value
//....
</script>
<div class="banner">
<FileNameInput bind:fileName={fileName} />
<SelectLangForm {monaco} />
</div>
File download
Now we want that if the user clicks on a button, the current file is downloaded.
So let's create another component (the last one :)) DownloadFileButton.svelte
:
<script lang="ts">
function formatFile(content: string) {
return `data:text/plain;charset=utf-8,${encodeURIComponent(content)}`
}
function save() {
}
let { monaco = $bindable(), fileName = $bindable() } = $props()
let href = $state(formatFile(""))
</script>
<svelte:window on:keydown={save} />
<a download={fileName} {href}>
Download file
</a>
Ok so when a key is pressed, the save()
function will be called to get the editor's content and to put convert it into a kind of data link (the file content).
We need to implement the function that will get the editor's content, so in lib/editor.ts
let's add this:
export function getEditorValue(monaco: Monaco) {
return monaco.editor.getEditors()[0].getValue()
}
Now our component looks like this:
<script lang="ts">
import { getEditorValue } from "$lib/editor"
function formatFile(content: string) {
return `data:text/plain;charset=utf-8,${encodeURIComponent(content)}`
}
function save() {
href = formatFile(getEditorValue(monaco))
}
// ...
</script>
<!-- ... -->
Now if we run we have something like this.
As a homework, you'll need to bring new features to this app!
Bye, and thank you for reading this post!
Top comments (0)