A focused Docker Compose management web application.
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

feat: basic code formatter

Brooke d0fa3d08 27952f14

+54 -4
+53 -4
packages/panel/src/lib/component/ComposeEditor.svelte
··· 1 1 <script lang="ts"> 2 - import { catppuccinMacchiato } from "@catppuccin/codemirror"; 2 + import { faAlignLeft, faArrowRightFromBracket } from "@fortawesome/free-solid-svg-icons"; 3 3 import type { Attachment } from "svelte/attachments"; 4 4 5 5 import { indentationMarkers } from "@replit/codemirror-indentation-markers"; 6 + import { catppuccinMacchiato } from "@catppuccin/codemirror"; 6 7 import { yamlSchema } from "codemirror-json-schema/yaml"; 7 8 import * as autocomplete from "@codemirror/autocomplete"; 8 9 import * as language from "@codemirror/language"; 10 + import prettierYaml from "prettier/plugins/yaml"; 9 11 import * as commands from "@codemirror/commands"; 10 12 import { yaml } from "@codemirror/lang-yaml"; 11 13 import * as estate from "@codemirror/state"; 12 14 import * as view from "@codemirror/view"; 13 15 import * as lint from "@codemirror/lint"; 16 + import * as prettier from "prettier"; 14 17 15 18 import schema from "../schema.json"; 19 + import { error } from "$lib"; 20 + import Fa from "svelte-fa"; 16 21 17 22 let { content = $bindable() }: { content: string } = $props(); 18 23 19 24 let focused = $state(false); 20 25 26 + async function format(view: view.EditorView) { 27 + const doc = view.state.doc.toString(); 28 + 29 + try { 30 + const { formatted, cursorOffset } = await prettier.formatWithCursor(doc, { 31 + cursorOffset: view.state.selection.main.head, 32 + plugins: [prettierYaml], 33 + parser: "yaml", 34 + }); 35 + 36 + if (formatted === doc) return; 37 + 38 + view.dispatch({ 39 + changes: { from: 0, to: doc.length, insert: formatted }, 40 + selection: { anchor: cursorOffset }, 41 + }); 42 + } catch (e) { 43 + error("Failed to format YAML", String(e)); 44 + } 45 + } 46 + 21 47 const editor: Attachment<HTMLElement> = (parent) => { 22 48 const editor = new view.EditorView({ 23 49 extensions: [ ··· 48 74 ...language.foldKeymap, 49 75 ...autocomplete.completionKeymap, 50 76 ...lint.lintKeymap, 77 + 78 + // Format keybind 79 + { 80 + key: "Alt-f", 81 + run(view) { 82 + format(view); 83 + return true; 84 + }, 85 + }, 51 86 ]), 52 87 53 88 // Update content on every change ··· 69 104 70 105 <div class="container"> 71 106 {#if focused} 72 - <div class="message">Esc</div> 107 + <div class="messages"> 108 + <div class="message"><Fa icon={faArrowRightFromBracket} /> Esc</div> 109 + <div class="message"><Fa icon={faAlignLeft} /> Alt F</div> 110 + </div> 73 111 {/if} 74 112 <div onfocusin={() => (focused = true)} onfocusout={() => (focused = false)} class="editor" {@attach editor}></div> 75 113 </div> ··· 83 121 justify-content: end; 84 122 } 85 123 86 - .message { 124 + .messages { 125 + pointer-events: none; 126 + display: flex; 87 127 position: absolute; 128 + gap: 10px; 129 + 130 + margin: 10px; 131 + } 132 + 133 + .message { 134 + display: flex; 135 + align-items: center; 136 + gap: 10px; 137 + 88 138 z-index: 10; 89 139 90 140 padding: 5px 10px; 91 - margin: 10px; 92 141 93 142 background-color: var(--crust); 94 143 border: var(--subtext0);
+1
packages/panel/src/routes/Toaster.svelte
··· 191 191 font-family: "DejaVu Mono", monospace; 192 192 background-color: var(--crust); 193 193 flex-direction: column; 194 + white-space: pre-line; 194 195 word-wrap: break-word; 195 196 border-radius: 5px; 196 197 margin-top: 15px;