feat: redesign snippet editor form and enhance CodeMirror with advanced editing tools and persistence
This commit is contained in:
+4
-4
@@ -32,9 +32,9 @@ public class ManageSnippetController {
|
|||||||
|
|
||||||
@PostMapping("/create")
|
@PostMapping("/create")
|
||||||
public String createSnippet(@ModelAttribute HtmlSnippet snippet, RedirectAttributes redirectAttributes) {
|
public String createSnippet(@ModelAttribute HtmlSnippet snippet, RedirectAttributes redirectAttributes) {
|
||||||
snippetService.save(snippet);
|
HtmlSnippet saved = snippetService.save(snippet);
|
||||||
redirectAttributes.addFlashAttribute("successMessage", "Snippet created successfully.");
|
redirectAttributes.addFlashAttribute("successMessage", "Snippet created successfully.");
|
||||||
return "redirect:/manage/snippets";
|
return "redirect:/manage/snippets/" + saved.getId() + "/edit";
|
||||||
}
|
}
|
||||||
|
|
||||||
@GetMapping("/{id}/edit")
|
@GetMapping("/{id}/edit")
|
||||||
@@ -52,9 +52,9 @@ public class ManageSnippetController {
|
|||||||
@PostMapping("/{id}")
|
@PostMapping("/{id}")
|
||||||
public String updateSnippet(@PathVariable Long id, @ModelAttribute HtmlSnippet snippet, RedirectAttributes redirectAttributes) {
|
public String updateSnippet(@PathVariable Long id, @ModelAttribute HtmlSnippet snippet, RedirectAttributes redirectAttributes) {
|
||||||
snippet.setId(id);
|
snippet.setId(id);
|
||||||
snippetService.save(snippet);
|
HtmlSnippet saved = snippetService.save(snippet);
|
||||||
redirectAttributes.addFlashAttribute("successMessage", "Snippet updated successfully.");
|
redirectAttributes.addFlashAttribute("successMessage", "Snippet updated successfully.");
|
||||||
return "redirect:/manage/snippets";
|
return "redirect:/manage/snippets/" + saved.getId() + "/edit";
|
||||||
}
|
}
|
||||||
|
|
||||||
@PostMapping("/{id}/delete")
|
@PostMapping("/{id}/delete")
|
||||||
|
|||||||
@@ -60,10 +60,4 @@
|
|||||||
/* Ensures it sits above the video */
|
/* Ensures it sits above the video */
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Custom Color Utilities */
|
|
||||||
.text-old-brick {
|
|
||||||
color: var(--color-old-brick) !important;
|
|
||||||
}
|
|
||||||
.bg-old-brick {
|
|
||||||
background-color: var(--color-old-brick) !important;
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -8,6 +8,8 @@
|
|||||||
<!-- CodeMirror CSS for beautiful code editing -->
|
<!-- CodeMirror CSS for beautiful code editing -->
|
||||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.13/codemirror.min.css">
|
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.13/codemirror.min.css">
|
||||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.13/theme/monokai.min.css">
|
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.13/theme/monokai.min.css">
|
||||||
|
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.13/addon/dialog/dialog.min.css">
|
||||||
|
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.13/addon/fold/foldgutter.min.css">
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
/* ── GrapesJS Layout Overrides ── */
|
/* ── GrapesJS Layout Overrides ── */
|
||||||
@@ -112,6 +114,16 @@
|
|||||||
}
|
}
|
||||||
.gjs-clm-tags { padding: 5px; }
|
.gjs-clm-tags { padding: 5px; }
|
||||||
.gjs-layer-name { font-size: 12px; }
|
.gjs-layer-name { font-size: 12px; }
|
||||||
|
|
||||||
|
/* CodeMirror Editor Height */
|
||||||
|
.CodeMirror {
|
||||||
|
height: 75vh !important;
|
||||||
|
min-height: 550px !important;
|
||||||
|
border: 1px solid #d1d3e2;
|
||||||
|
border-radius: 0.35rem;
|
||||||
|
font-size: 14px;
|
||||||
|
font-family: Consolas, "Courier New", monospace;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
@@ -129,21 +141,13 @@
|
|||||||
<input type="hidden" th:field="*{createdDate}" />
|
<input type="hidden" th:field="*{createdDate}" />
|
||||||
|
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-12">
|
<!-- Main Content Column -->
|
||||||
|
<div class="col-xl-9 col-lg-8">
|
||||||
<div class="card shadow mb-4">
|
<div class="card shadow mb-4">
|
||||||
<div class="card-header py-3">
|
<div class="card-header py-3">
|
||||||
<h6 class="m-0 font-weight-bold text-primary">Snippet Details</h6>
|
<h6 class="m-0 font-weight-bold text-primary">HTML Content</h6>
|
||||||
</div>
|
</div>
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<div class="form-group">
|
|
||||||
<label for="name">Name</label>
|
|
||||||
<input type="text" class="form-control" id="name" th:field="*{name}" required placeholder="E.g., Footer Contact Text">
|
|
||||||
</div>
|
|
||||||
<div class="form-group">
|
|
||||||
<label for="slug">Slug</label>
|
|
||||||
<input type="text" class="form-control" id="slug" th:field="*{slug}" required placeholder="E.g., footer-contact">
|
|
||||||
<small class="form-text text-muted">A unique identifier used to call this snippet in templates. Use lowercase letters and hyphens.</small>
|
|
||||||
</div>
|
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<div class="d-flex justify-content-between align-items-center mb-2">
|
<div class="d-flex justify-content-between align-items-center mb-2">
|
||||||
<label for="snippetContent" class="mb-0">HTML Content</label>
|
<label for="snippetContent" class="mb-0">HTML Content</label>
|
||||||
@@ -177,12 +181,23 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="col-12">
|
<!-- Sidebar Publish Column -->
|
||||||
|
<div class="col-xl-3 col-lg-4">
|
||||||
<div class="card shadow mb-4">
|
<div class="card shadow mb-4">
|
||||||
<div class="card-header py-3">
|
<div class="card-header py-3">
|
||||||
<h6 class="m-0 font-weight-bold text-primary">Publish</h6>
|
<h6 class="m-0 font-weight-bold text-primary">Settings & Publish</h6>
|
||||||
</div>
|
</div>
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="name">Name</label>
|
||||||
|
<input type="text" class="form-control" id="name" th:field="*{name}" required placeholder="E.g., Footer Contact Text">
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="slug">Slug</label>
|
||||||
|
<input type="text" class="form-control" id="slug" th:field="*{slug}" required placeholder="E.g., footer-contact">
|
||||||
|
<small class="form-text text-muted">A unique identifier.</small>
|
||||||
|
</div>
|
||||||
|
<hr>
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<div class="custom-control custom-switch">
|
<div class="custom-control custom-switch">
|
||||||
<input type="checkbox" class="custom-control-input" id="active" th:field="*{active}">
|
<input type="checkbox" class="custom-control-input" id="active" th:field="*{active}">
|
||||||
@@ -212,6 +227,23 @@
|
|||||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.13/mode/javascript/javascript.min.js"></script>
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.13/mode/javascript/javascript.min.js"></script>
|
||||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.13/mode/htmlmixed/htmlmixed.min.js"></script>
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.13/mode/htmlmixed/htmlmixed.min.js"></script>
|
||||||
|
|
||||||
|
<!-- CodeMirror Addons for VS Code-like experience -->
|
||||||
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.13/addon/edit/closetag.min.js"></script>
|
||||||
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.13/addon/edit/closebrackets.min.js"></script>
|
||||||
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.13/addon/edit/matchbrackets.min.js"></script>
|
||||||
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.13/addon/edit/matchtags.min.js"></script>
|
||||||
|
|
||||||
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.13/addon/search/searchcursor.min.js"></script>
|
||||||
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.13/addon/search/search.min.js"></script>
|
||||||
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.13/addon/dialog/dialog.min.js"></script>
|
||||||
|
|
||||||
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.13/keymap/sublime.min.js"></script>
|
||||||
|
|
||||||
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.13/addon/fold/foldcode.min.js"></script>
|
||||||
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.13/addon/fold/foldgutter.min.js"></script>
|
||||||
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.13/addon/fold/xml-fold.min.js"></script>
|
||||||
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.13/addon/fold/brace-fold.min.js"></script>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
document.addEventListener('DOMContentLoaded', function() {
|
document.addEventListener('DOMContentLoaded', function() {
|
||||||
var textArea = document.getElementById('snippetContent');
|
var textArea = document.getElementById('snippetContent');
|
||||||
@@ -653,6 +685,7 @@
|
|||||||
|
|
||||||
toggleBtn.addEventListener('click', function() {
|
toggleBtn.addEventListener('click', function() {
|
||||||
if (isVisual) {
|
if (isVisual) {
|
||||||
|
localStorage.setItem('snippetEditorMode', 'code');
|
||||||
var rawOutput;
|
var rawOutput;
|
||||||
|
|
||||||
if (isGrapesJsModified) {
|
if (isGrapesJsModified) {
|
||||||
@@ -683,9 +716,18 @@
|
|||||||
theme: "monokai",
|
theme: "monokai",
|
||||||
lineNumbers: true,
|
lineNumbers: true,
|
||||||
matchBrackets: true,
|
matchBrackets: true,
|
||||||
|
matchTags: {bothTags: true},
|
||||||
autoCloseTags: true,
|
autoCloseTags: true,
|
||||||
|
autoCloseBrackets: true,
|
||||||
indentUnit: 4,
|
indentUnit: 4,
|
||||||
lineWrapping: true
|
lineWrapping: true,
|
||||||
|
keyMap: "sublime",
|
||||||
|
foldGutter: true,
|
||||||
|
gutters: ["CodeMirror-linenumbers", "CodeMirror-foldgutter"],
|
||||||
|
extraKeys: {
|
||||||
|
"Ctrl-Space": "autocomplete",
|
||||||
|
"Ctrl-Q": function(cm){ cm.foldCode(cm.getCursor()); }
|
||||||
|
}
|
||||||
});
|
});
|
||||||
// Fix for initially hidden textarea
|
// Fix for initially hidden textarea
|
||||||
setTimeout(function() {
|
setTimeout(function() {
|
||||||
@@ -705,6 +747,7 @@
|
|||||||
|
|
||||||
toggleBtn.innerHTML = '<i class="fas fa-paint-brush"></i> Use Visual Builder';
|
toggleBtn.innerHTML = '<i class="fas fa-paint-brush"></i> Use Visual Builder';
|
||||||
} else {
|
} else {
|
||||||
|
localStorage.setItem('snippetEditorMode', 'visual');
|
||||||
// Get value from CodeMirror back to textarea and GrapesJS
|
// Get value from CodeMirror back to textarea and GrapesJS
|
||||||
if (cmEditor) {
|
if (cmEditor) {
|
||||||
textArea.value = cmEditor.getValue();
|
textArea.value = cmEditor.getValue();
|
||||||
@@ -737,6 +780,11 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Restore previous mode if it was Code Editor
|
||||||
|
if (localStorage.getItem('snippetEditorMode') === 'code') {
|
||||||
|
toggleBtn.click();
|
||||||
|
}
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
</section>
|
</section>
|
||||||
|
|||||||
Reference in New Issue
Block a user