1
0
mirror of https://github.com/thomiceli/opengist synced 2024-11-08 12:55:50 +01:00

Markdown preview (#224)

This commit is contained in:
Thomas Miceli 2024-02-24 18:09:23 +01:00
parent 2bf0e9b7ce
commit fc9a75ce8f
9 changed files with 69 additions and 3 deletions

@ -43,6 +43,7 @@ gist.new.add-file: Add file
gist.new.create-public-button: Create public gist gist.new.create-public-button: Create public gist
gist.new.create-unlisted-button: Create unlisted gist gist.new.create-unlisted-button: Create unlisted gist
gist.new.create-private-button: Create private gist gist.new.create-private-button: Create private gist
gist.new.preview: Preview
gist.edit.editing: Editing gist.edit.editing: Editing
gist.edit.change-visibility: Make gist.edit.change-visibility: Make

@ -41,6 +41,12 @@ func MarkdownFile(file *git.File) (RenderedFile, error) {
Type: "Markdown", Type: "Markdown",
}, err }, err
} }
func MarkdownString(content string) (string, error) {
var buf bytes.Buffer
err := newMarkdown().Convert([]byte(content), &buf)
return buf.String(), err
}
func newMarkdown() goldmark.Markdown { func newMarkdown() goldmark.Markdown {
return goldmark.New( return goldmark.New(

@ -56,7 +56,7 @@ func validateReservedKeywords(fl validator.FieldLevel) bool {
name := fl.Field().String() name := fl.Field().String()
restrictedNames := map[string]struct{}{} restrictedNames := map[string]struct{}{}
for _, restrictedName := range []string{"assets", "register", "login", "logout", "settings", "admin-panel", "all", "search", "init", "healthcheck"} { for _, restrictedName := range []string{"assets", "register", "login", "logout", "settings", "admin-panel", "all", "search", "init", "healthcheck", "preview"} {
restrictedNames[restrictedName] = struct{}{} restrictedNames[restrictedName] = struct{}{}
} }

@ -890,3 +890,14 @@ func checkbox(ctx echo.Context) error {
return plainText(ctx, 200, "ok") return plainText(ctx, 200, "ok")
} }
func preview(ctx echo.Context) error {
content := ctx.FormValue("content")
previewStr, err := render.MarkdownString(content)
if err != nil {
return errorRes(500, "Error rendering markdown", err)
}
return plainText(ctx, 200, previewStr)
}

@ -235,6 +235,7 @@ func NewServer(isDev bool) *Server {
g1.GET("/", create, logged) g1.GET("/", create, logged)
g1.POST("/", processCreate, logged) g1.POST("/", processCreate, logged)
g1.GET("/preview", preview, logged)
g1.GET("/healthcheck", healthcheck) g1.GET("/healthcheck", healthcheck)

@ -34,6 +34,45 @@ document.addEventListener("DOMContentLoaded", () => {
], ],
}); });
let mdpreview = dom.querySelector(".md-preview") as HTMLElement;
// event if the filename ends with .md; trigger event
dom.querySelector<HTMLInputElement>(".form-filename")!.onkeyup = (e) => {
let filename = (e.target as HTMLInputElement).value;
if (filename.endsWith(".md")) {
mdpreview!.classList.remove("hidden");
} else {
mdpreview!.classList.add("hidden");
}
};
// @ts-ignore
const baseUrl = window.opengist_base_url || '';
let previewShown = false;
mdpreview.onclick = () => {
previewShown = !previewShown;
let divpreview = dom.querySelector("div.preview") as HTMLElement;
let cmeditor = dom.querySelector(".cm-editor") as HTMLElement;
if (!previewShown) {
divpreview!.classList.add("hidden");
cmeditor!.classList.remove("hidden-important");
return;
} else {
fetch(`${baseUrl}/preview?` + new URLSearchParams({
content: editor.state.doc.toString()
}), {
method: 'GET',
credentials: 'same-origin',
}).then(r => r.text()).then(r => {
let divpreview = dom.querySelector("div.preview") as HTMLElement;
divpreview!.innerHTML = r;
divpreview!.classList.remove("hidden");
cmeditor!.classList.add("hidden-important");
})
}
}
dom.querySelector<HTMLInputElement>(".editor-indent-type")!.onchange = (e) => { dom.querySelector<HTMLInputElement>(".editor-indent-type")!.onchange = (e) => {
let newTabType = (e.target as HTMLInputElement).value; let newTabType = (e.target as HTMLInputElement).value;
setIndentType(editor, !["tab", "space"].includes(newTabType) ? "space" : newTabType); setIndentType(editor, !["tab", "space"].includes(newTabType) ? "space" : newTabType);

4
public/style.css vendored

@ -174,3 +174,7 @@ dl.dl-config dd {
.mermaid { .mermaid {
background: #f6f8fa !important; background: #f6f8fa !important;
} }
.hidden-important {
@apply hidden !important;
}

@ -31,8 +31,9 @@
<div class="rounded-md border border-1 border-gray-200 dark:border-gray-700 editor"> <div class="rounded-md border border-1 border-gray-200 dark:border-gray-700 editor">
<div class="border-b-1 border-gray-200 dark:border-gray-700 bg-gray-50 dark:bg-gray-800 my-auto flex"> <div class="border-b-1 border-gray-200 dark:border-gray-700 bg-gray-50 dark:bg-gray-800 my-auto flex">
<p class="mx-2 my-2 inline-flex"> <p class="mx-2 my-2 inline-flex">
<input type="text" name="name" placeholder="{{ .locale.Tr "gist.new.filename-with-extension" }}" style="line-height: 0.05em" class="form-filename bg-white dark:bg-gray-900 shadow-sm focus:ring-primary-500 focus:border-primary-500 block w-full sm:text-sm border-gray-200 dark:border-gray-700 rounded-md"> <input type="text" name="name" placeholder="{{ .locale.Tr "gist.new.filename-with-extension" }}" style="line-height: 0.05em" class="form-filename bg-white dark:bg-gray-900 shadow-sm focus:ring-primary-500 focus:border-primary-500 block w-full sm:text-sm border-gray-200 dark:border-gray-700 rounded-md gist-title">
</p> </p>
<button type="button" class="md-preview hidden whitespace-nowrap text-slate-700 dark:text-slate-300 rounded border border-gray-200 dark:border-gray-600 bg-white dark:bg-gray-900 my-2 px-2 text-xs font-medium shadow-sm hover:bg-gray-200 dark:hover:bg-gray-700 hover:border-gray-500 hover:text-slate-700 dark:hover:text-slate-300 focus:outline-none focus:ring-1 focus:border-primary-500 focus:ring-primary-500">{{ .locale.Tr "gist.new.preview" }}</button>
<div class="hidden mx-2 my-2 sm:inline-flex ml-auto space-x-2"> <div class="hidden mx-2 my-2 sm:inline-flex ml-auto space-x-2">
<select class="editor-indent-type whitespace-nowrap text-slate-700 dark:text-slate-300 rounded border border-gray-200 dark:border-gray-600 bg-white dark:bg-gray-900 pr-8 text-xs font-medium shadow-sm hover:bg-gray-200 dark:hover:bg-gray-700 hover:border-gray-500 hover:text-slate-700 dark:hover:text-slate-300 focus:outline-none focus:ring-1 focus:border-primary-500 focus:ring-primary-500"> <select class="editor-indent-type whitespace-nowrap text-slate-700 dark:text-slate-300 rounded border border-gray-200 dark:border-gray-600 bg-white dark:bg-gray-900 pr-8 text-xs font-medium shadow-sm hover:bg-gray-200 dark:hover:bg-gray-700 hover:border-gray-500 hover:text-slate-700 dark:hover:text-slate-300 focus:outline-none focus:ring-1 focus:border-primary-500 focus:ring-primary-500">
<optgroup label="{{ .locale.Tr "gist.new.indent-mode" }}"> <optgroup label="{{ .locale.Tr "gist.new.indent-mode" }}">
@ -56,6 +57,7 @@
</div> </div>
</div> </div>
<input type="hidden" value="" name="content" class="form-filecontent" autocomplete="off"> <input type="hidden" value="" name="content" class="form-filecontent" autocomplete="off">
<div class="hidden preview chroma markdown markdown-body p-8"></div>
</div> </div>
</div> </div>

@ -61,13 +61,14 @@
<div class="rounded-md border border-1 border-gray-200 dark:border-gray-700 editor"> <div class="rounded-md border border-1 border-gray-200 dark:border-gray-700 editor">
<div class="border-b-1 border-gray-200 dark:border-gray-700 bg-gray-50 dark:bg-gray-800 my-auto flex"> <div class="border-b-1 border-gray-200 dark:border-gray-700 bg-gray-50 dark:bg-gray-800 my-auto flex">
<p class="mx-2 my-2 inline-flex"> <p class="mx-2 my-2 inline-flex">
<input type="text" value="{{ $file.Filename }}" name="name" placeholder="Filename with extension" style="line-height: 0.05em; z-index: 99999" class="form-filename bg-white dark:bg-gray-900 shadow-sm focus:ring-primary-500 focus:border-primary-500 block w-full sm:text-sm border-gray-200 dark:border-gray-700 rounded-l-md"> <input type="text" value="{{ $file.Filename }}" name="name" placeholder="Filename with extension" style="line-height: 0.05em; z-index: 99999" class="form-filename bg-white dark:bg-gray-900 shadow-sm focus:ring-primary-500 focus:border-primary-500 block w-full sm:text-sm border-gray-200 dark:border-gray-700 rounded-l-md gist-title">
<button style="line-height: 0.05em" class="delete-file -ml-px relative inline-flex items-center space-x-2 px-4 py-2 border border-gray-200 dark:border-gray-700 text-sm font-medium rounded-r-md text-slate-700 dark:text-slate-300 bg-gray-50 dark:bg-gray-800 hover:bg-white dark:hover:bg-gray-900 focus:outline-none" type="button"> <button style="line-height: 0.05em" class="delete-file -ml-px relative inline-flex items-center space-x-2 px-4 py-2 border border-gray-200 dark:border-gray-700 text-sm font-medium rounded-r-md text-slate-700 dark:text-slate-300 bg-gray-50 dark:bg-gray-800 hover:bg-white dark:hover:bg-gray-900 focus:outline-none" type="button">
<svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"> <svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2">
<path stroke-linecap="round" stroke-linejoin="round" d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16" /> <path stroke-linecap="round" stroke-linejoin="round" d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16" />
</svg> </svg>
</button> </button>
</p> </p>
<button type="button" class="md-preview hidden whitespace-nowrap text-slate-700 dark:text-slate-300 rounded border border-gray-200 dark:border-gray-600 bg-white dark:bg-gray-900 my-2 px-2 text-xs font-medium shadow-sm hover:bg-gray-200 dark:hover:bg-gray-700 hover:border-gray-500 hover:text-slate-700 dark:hover:text-slate-300 focus:outline-none focus:ring-1 focus:border-primary-500 focus:ring-primary-500">{{ $.locale.Tr "gist.new.preview" }}</button>
<div class="hidden mx-2 my-2 sm:inline-flex ml-auto space-x-2"> <div class="hidden mx-2 my-2 sm:inline-flex ml-auto space-x-2">
<select class="editor-indent-type whitespace-nowrap text-slate-700 dark:text-slate-300 rounded border border-gray-200 dark:border-gray-600 bg-white dark:bg-gray-900 pr-8 text-xs font-medium shadow-sm hover:bg-gray-200 dark:hover:bg-gray-700 hover:border-gray-500 hover:text-slate-700 dark:hover:text-slate-300 focus:outline-none focus:ring-1 focus:border-primary-500 focus:ring-primary-500"> <select class="editor-indent-type whitespace-nowrap text-slate-700 dark:text-slate-300 rounded border border-gray-200 dark:border-gray-600 bg-white dark:bg-gray-900 pr-8 text-xs font-medium shadow-sm hover:bg-gray-200 dark:hover:bg-gray-700 hover:border-gray-500 hover:text-slate-700 dark:hover:text-slate-300 focus:outline-none focus:ring-1 focus:border-primary-500 focus:ring-primary-500">
<optgroup label="{{ $.locale.Tr "gist.new.indent-mode" }}"> <optgroup label="{{ $.locale.Tr "gist.new.indent-mode" }}">
@ -91,6 +92,7 @@
</div> </div>
</div> </div>
<input type="hidden" value="{{ $file.Content }}" name="content" class="form-filecontent" autocomplete="off"> <input type="hidden" value="{{ $file.Content }}" name="content" class="form-filecontent" autocomplete="off">
<div class="hidden preview chroma markdown markdown-body p-8"></div>
</div> </div>
{{ end }} {{ end }}
</div> </div>