mirror of
https://github.com/thomiceli/opengist.git
synced 2024-12-22 20:42:40 +00:00
Markdown preview (#224)
This commit is contained in:
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
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;
|
||||||
|
}
|
4
templates/pages/create.html
vendored
4
templates/pages/create.html
vendored
|
@ -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>
|
||||||
|
|
||||||
|
|
4
templates/pages/edit.html
vendored
4
templates/pages/edit.html
vendored
|
@ -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>
|
||||||
|
|
Loading…
Reference in a new issue