add other files view/edit
This commit is contained in:
@@ -67,10 +67,28 @@ func (h *Handlers) CreateNoteHandler(c *gin.Context) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Ensure title ends with .md
|
// Determine extension logic
|
||||||
if !strings.HasSuffix(title, ".md") {
|
ext := strings.TrimPrefix(strings.ToLower(filepath.Ext(title)), ".")
|
||||||
title += ".md"
|
if ext == "" {
|
||||||
}
|
// No extension provided: default to markdown
|
||||||
|
title += ".md"
|
||||||
|
ext = "md"
|
||||||
|
} else {
|
||||||
|
// Has extension: allow if md or in allowed file extensions
|
||||||
|
allowed := ext == "md"
|
||||||
|
if !allowed {
|
||||||
|
for _, a := range h.config.AllowedFileExtensions {
|
||||||
|
if strings.EqualFold(a, ext) {
|
||||||
|
allowed = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !allowed {
|
||||||
|
c.JSON(http.StatusBadRequest, gin.H{"error": "File extension not allowed"})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Create full path
|
// Create full path
|
||||||
var notePath string
|
var notePath string
|
||||||
@@ -101,12 +119,18 @@ func (h *Handlers) CreateNoteHandler(c *gin.Context) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
c.JSON(http.StatusOK, gin.H{
|
// Redirect based on extension
|
||||||
"success": true,
|
redirect := "/note/" + notePath
|
||||||
"message": "Note created successfully",
|
if strings.ToLower(ext) != "md" {
|
||||||
"note_path": notePath,
|
redirect = "/view_text/" + notePath
|
||||||
"redirect": "/note/" + notePath,
|
}
|
||||||
})
|
|
||||||
|
c.JSON(http.StatusOK, gin.H{
|
||||||
|
"success": true,
|
||||||
|
"message": "Note created successfully",
|
||||||
|
"note_path": notePath,
|
||||||
|
"redirect": redirect,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *Handlers) EditNotePageHandler(c *gin.Context) {
|
func (h *Handlers) EditNotePageHandler(c *gin.Context) {
|
||||||
|
|||||||
@@ -24,6 +24,137 @@ type Handlers struct {
|
|||||||
renderer *markdown.Renderer
|
renderer *markdown.Renderer
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// EditTextPageHandler renders an editor for allowed text files (json, html, xml, yaml, etc.)
|
||||||
|
func (h *Handlers) EditTextPageHandler(c *gin.Context) {
|
||||||
|
filePath := strings.TrimPrefix(c.Param("path"), "/")
|
||||||
|
|
||||||
|
// Security check
|
||||||
|
if strings.Contains(filePath, "..") {
|
||||||
|
c.HTML(http.StatusBadRequest, "error", gin.H{
|
||||||
|
"error": "Invalid path",
|
||||||
|
"app_name": h.config.AppName,
|
||||||
|
"message": "Path traversal is not allowed",
|
||||||
|
"ContentTemplate": "error_content",
|
||||||
|
"ScriptsTemplate": "error_scripts",
|
||||||
|
"Page": "error",
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
fullPath := filepath.Join(h.config.NotesDir, filePath)
|
||||||
|
|
||||||
|
// Ensure file exists
|
||||||
|
if _, err := os.Stat(fullPath); os.IsNotExist(err) {
|
||||||
|
c.HTML(http.StatusNotFound, "error", gin.H{
|
||||||
|
"error": "File not found",
|
||||||
|
"app_name": h.config.AppName,
|
||||||
|
"message": "The requested file does not exist",
|
||||||
|
"ContentTemplate": "error_content",
|
||||||
|
"ScriptsTemplate": "error_scripts",
|
||||||
|
"Page": "error",
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Only allow editing of configured text file types (not markdown here)
|
||||||
|
ext := filepath.Ext(fullPath)
|
||||||
|
ftype := models.GetFileType(ext, h.config.AllowedImageExtensions, h.config.AllowedFileExtensions)
|
||||||
|
if ftype != models.FileTypeText {
|
||||||
|
c.HTML(http.StatusForbidden, "error", gin.H{
|
||||||
|
"error": "Editing not allowed",
|
||||||
|
"app_name": h.config.AppName,
|
||||||
|
"message": "This file type cannot be edited here",
|
||||||
|
"ContentTemplate": "error_content",
|
||||||
|
"ScriptsTemplate": "error_scripts",
|
||||||
|
"Page": "error",
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Load content
|
||||||
|
data, err := os.ReadFile(fullPath)
|
||||||
|
if err != nil {
|
||||||
|
c.HTML(http.StatusInternalServerError, "error", gin.H{
|
||||||
|
"error": "Failed to read file",
|
||||||
|
"app_name": h.config.AppName,
|
||||||
|
"message": err.Error(),
|
||||||
|
"ContentTemplate": "error_content",
|
||||||
|
"ScriptsTemplate": "error_scripts",
|
||||||
|
"Page": "error",
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Build notes tree
|
||||||
|
notesTree, err := utils.BuildTreeStructure(h.config.NotesDir, h.config.NotesDirHideSidepane, h.config)
|
||||||
|
if err != nil {
|
||||||
|
c.HTML(http.StatusInternalServerError, "error", gin.H{
|
||||||
|
"error": "Failed to build notes tree",
|
||||||
|
"app_name": h.config.AppName,
|
||||||
|
"message": err.Error(),
|
||||||
|
"ContentTemplate": "error_content",
|
||||||
|
"ScriptsTemplate": "error_scripts",
|
||||||
|
"Page": "error",
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
folderPath := filepath.Dir(filePath)
|
||||||
|
if folderPath == "." {
|
||||||
|
folderPath = ""
|
||||||
|
}
|
||||||
|
|
||||||
|
c.HTML(http.StatusOK, "edit_text", gin.H{
|
||||||
|
"app_name": h.config.AppName,
|
||||||
|
"title": filepath.Base(filePath),
|
||||||
|
"content": string(data),
|
||||||
|
"file_path": filePath,
|
||||||
|
"file_ext": strings.TrimPrefix(strings.ToLower(ext), "."),
|
||||||
|
"folder_path": folderPath,
|
||||||
|
"notes_tree": notesTree,
|
||||||
|
"active_path": utils.GetActivePath(folderPath),
|
||||||
|
"breadcrumbs": utils.GenerateBreadcrumbs(folderPath),
|
||||||
|
"ContentTemplate": "edit_text_content",
|
||||||
|
"ScriptsTemplate": "edit_text_scripts",
|
||||||
|
"Page": "edit_text",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// PostEditTextHandler saves changes to an allowed text file
|
||||||
|
func (h *Handlers) PostEditTextHandler(c *gin.Context) {
|
||||||
|
filePath := strings.TrimPrefix(c.Param("path"), "/")
|
||||||
|
|
||||||
|
if strings.Contains(filePath, "..") {
|
||||||
|
c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid path"})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
fullPath := filepath.Join(h.config.NotesDir, filePath)
|
||||||
|
|
||||||
|
// Enforce allowed file type
|
||||||
|
ext := filepath.Ext(fullPath)
|
||||||
|
ftype := models.GetFileType(ext, h.config.AllowedImageExtensions, h.config.AllowedFileExtensions)
|
||||||
|
if ftype != models.FileTypeText {
|
||||||
|
c.JSON(http.StatusForbidden, gin.H{"error": "This file type cannot be edited"})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
content := c.PostForm("content")
|
||||||
|
|
||||||
|
// Ensure parent directory exists
|
||||||
|
if err := os.MkdirAll(filepath.Dir(fullPath), 0o755); err != nil {
|
||||||
|
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to create parent directory"})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := os.WriteFile(fullPath, []byte(content), 0o644); err != nil {
|
||||||
|
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to save file"})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
c.JSON(http.StatusOK, gin.H{"success": true, "redirect": "/view_text/" + filePath})
|
||||||
|
}
|
||||||
|
|
||||||
func New(cfg *config.Config, store *sessions.CookieStore) *Handlers {
|
func New(cfg *config.Config, store *sessions.CookieStore) *Handlers {
|
||||||
return &Handlers{
|
return &Handlers{
|
||||||
config: cfg,
|
config: cfg,
|
||||||
@@ -431,11 +562,18 @@ func (h *Handlers) ViewTextHandler(c *gin.Context) {
|
|||||||
folderPath = ""
|
folderPath = ""
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Determine extension and whether file is editable as text
|
||||||
|
ext := filepath.Ext(filePath)
|
||||||
|
ftype := models.GetFileType(ext, h.config.AllowedImageExtensions, h.config.AllowedFileExtensions)
|
||||||
|
isEditable := ftype == models.FileTypeText
|
||||||
|
|
||||||
c.HTML(http.StatusOK, "view_text", gin.H{
|
c.HTML(http.StatusOK, "view_text", gin.H{
|
||||||
"app_name": h.config.AppName,
|
"app_name": h.config.AppName,
|
||||||
"file_name": filepath.Base(filePath),
|
"file_name": filepath.Base(filePath),
|
||||||
"file_path": filePath,
|
"file_path": filePath,
|
||||||
"content": string(content),
|
"content": string(content),
|
||||||
|
"file_ext": strings.TrimPrefix(strings.ToLower(ext), "."),
|
||||||
|
"is_editable": isEditable,
|
||||||
"folder_path": folderPath,
|
"folder_path": folderPath,
|
||||||
"notes_tree": notesTree,
|
"notes_tree": notesTree,
|
||||||
"active_path": utils.GetActivePath(folderPath),
|
"active_path": utils.GetActivePath(folderPath),
|
||||||
|
|||||||
@@ -74,6 +74,8 @@ func (s *Server) setupRoutes() {
|
|||||||
s.router.GET("/serve_stored_image/:filename", h.ServeStoredImageHandler)
|
s.router.GET("/serve_stored_image/:filename", h.ServeStoredImageHandler)
|
||||||
s.router.GET("/download/*path", h.DownloadHandler)
|
s.router.GET("/download/*path", h.DownloadHandler)
|
||||||
s.router.GET("/view_text/*path", h.ViewTextHandler)
|
s.router.GET("/view_text/*path", h.ViewTextHandler)
|
||||||
|
s.router.GET("/edit_text/*path", h.EditTextPageHandler)
|
||||||
|
s.router.POST("/edit_text/*path", h.PostEditTextHandler)
|
||||||
|
|
||||||
// Upload routes
|
// Upload routes
|
||||||
s.router.POST("/upload", h.UploadHandler)
|
s.router.POST("/upload", h.UploadHandler)
|
||||||
|
|||||||
@@ -324,6 +324,8 @@
|
|||||||
{{template "note_content" .}}
|
{{template "note_content" .}}
|
||||||
{{else if eq .Page "view_text"}}
|
{{else if eq .Page "view_text"}}
|
||||||
{{template "view_text_content" .}}
|
{{template "view_text_content" .}}
|
||||||
|
{{else if eq .Page "edit_text"}}
|
||||||
|
{{template "edit_text_content" .}}
|
||||||
{{else if eq .Page "create"}}
|
{{else if eq .Page "create"}}
|
||||||
{{template "create_content" .}}
|
{{template "create_content" .}}
|
||||||
{{else if eq .Page "edit"}}
|
{{else if eq .Page "edit"}}
|
||||||
@@ -585,6 +587,8 @@
|
|||||||
{{template "note_scripts" .}}
|
{{template "note_scripts" .}}
|
||||||
{{else if eq .Page "view_text"}}
|
{{else if eq .Page "view_text"}}
|
||||||
{{template "view_text_scripts" .}}
|
{{template "view_text_scripts" .}}
|
||||||
|
{{else if eq .Page "edit_text"}}
|
||||||
|
{{template "edit_text_scripts" .}}
|
||||||
{{else if eq .Page "create"}}
|
{{else if eq .Page "create"}}
|
||||||
{{template "create_scripts" .}}
|
{{template "create_scripts" .}}
|
||||||
{{else if eq .Page "edit"}}
|
{{else if eq .Page "edit"}}
|
||||||
|
|||||||
195
web/templates/edit_text.html
Normal file
195
web/templates/edit_text.html
Normal file
@@ -0,0 +1,195 @@
|
|||||||
|
{{define "edit_text"}}
|
||||||
|
{{template "base" .}}
|
||||||
|
{{end}}
|
||||||
|
|
||||||
|
{{define "edit_text_content"}}
|
||||||
|
<div class="max-w-4xl mx-auto p-6">
|
||||||
|
<!-- Header -->
|
||||||
|
<div class="mb-6">
|
||||||
|
<div class="flex items-center justify-between mb-4">
|
||||||
|
<h1 class="text-3xl font-bold text-white">Edit: {{.title}}</h1>
|
||||||
|
<div class="flex items-center space-x-3">
|
||||||
|
<button id="format-btn" type="button" class="btn-secondary">
|
||||||
|
<i class="fas fa-wand-magic-sparkles mr-2"></i>Format
|
||||||
|
</button>
|
||||||
|
<label class="text-sm text-gray-300 mr-2 inline-flex items-center">
|
||||||
|
<input id="format-on-save" type="checkbox" class="mr-2">
|
||||||
|
Format on save
|
||||||
|
</label>
|
||||||
|
<button type="submit" form="edit-text-form" class="btn-primary">
|
||||||
|
<i class="fas fa-save mr-2"></i>Save
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{{if .folder_path}}
|
||||||
|
<p class="text-gray-400">
|
||||||
|
<i class="fas fa-folder mr-2"></i>
|
||||||
|
<a href="/folder/{{.folder_path}}" class="text-blue-400 hover:text-blue-300">{{.folder_path}}</a>
|
||||||
|
</p>
|
||||||
|
{{end}}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Edit Form -->
|
||||||
|
<form id="edit-text-form" class="space-y-6">
|
||||||
|
<div class="bg-gray-800 rounded-lg p-6">
|
||||||
|
<div class="mb-2">
|
||||||
|
<div class="flex items-center justify-between mb-2">
|
||||||
|
<label for="content" class="block text-sm font-medium text-gray-300">Content ({{.file_ext}})</label>
|
||||||
|
<div class="text-xs text-gray-500">Ctrl+S to save</div>
|
||||||
|
</div>
|
||||||
|
<textarea id="content" name="content" rows="24" class="editor-textarea">{{.content}}</textarea>
|
||||||
|
<div id="cm-container" class="mt-3"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
{{end}}
|
||||||
|
|
||||||
|
{{define "edit_text_scripts"}}
|
||||||
|
<!-- CodeMirror -->
|
||||||
|
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.16/codemirror.min.css" />
|
||||||
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.16/codemirror.min.js"></script>
|
||||||
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.16/mode/javascript/javascript.min.js"></script>
|
||||||
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.16/mode/xml/xml.min.js"></script>
|
||||||
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.16/mode/yaml/yaml.min.js"></script>
|
||||||
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.16/mode/htmlmixed/htmlmixed.min.js"></script>
|
||||||
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.16/mode/css/css.min.js"></script>
|
||||||
|
<style>
|
||||||
|
.CodeMirror { height: auto; min-height: 24rem; background-color: #1f2937; color: #d1d5db; border: 1px solid #4b5563; border-radius: 0.5rem; }
|
||||||
|
.cm-s-default .CodeMirror-gutters { background: #1f2937; border-right: 1px solid #374151; }
|
||||||
|
.cm-s-default .CodeMirror-linenumber { color: #94a3b8; }
|
||||||
|
</style>
|
||||||
|
<script src="https://cdn.jsdelivr.net/npm/js-yaml@4.1.0/dist/js-yaml.min.js"></script>
|
||||||
|
<script>
|
||||||
|
const form = document.getElementById('edit-text-form');
|
||||||
|
const contentEl = document.getElementById('content');
|
||||||
|
const formatBtn = document.getElementById('format-btn');
|
||||||
|
const formatOnSaveEl = document.getElementById('format-on-save');
|
||||||
|
const fileExt = '{{.file_ext}}'.toLowerCase();
|
||||||
|
const filePath = '{{.file_path}}';
|
||||||
|
|
||||||
|
// Initialize CodeMirror
|
||||||
|
let cm = null;
|
||||||
|
function getModeByExt(ext) {
|
||||||
|
switch (ext) {
|
||||||
|
case 'json': return { name: 'javascript', json: true };
|
||||||
|
case 'yaml':
|
||||||
|
case 'yml': return 'yaml';
|
||||||
|
case 'xml': return 'xml';
|
||||||
|
case 'html': return 'htmlmixed';
|
||||||
|
case 'css': return 'css';
|
||||||
|
case 'js': return 'javascript';
|
||||||
|
default: return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function ensureCodeMirror() {
|
||||||
|
if (cm) return cm;
|
||||||
|
cm = CodeMirror(document.getElementById('cm-container'), {
|
||||||
|
value: contentEl.value,
|
||||||
|
lineNumbers: true,
|
||||||
|
mode: getModeByExt(fileExt) || undefined,
|
||||||
|
tabSize: 2,
|
||||||
|
indentUnit: 2,
|
||||||
|
theme: 'default',
|
||||||
|
});
|
||||||
|
// Keep textarea hidden but in sync
|
||||||
|
contentEl.style.display = 'none';
|
||||||
|
cm.on('change', () => { contentEl.value = cm.getValue(); });
|
||||||
|
return cm;
|
||||||
|
}
|
||||||
|
|
||||||
|
function formatJSON(text) {
|
||||||
|
try { return JSON.stringify(JSON.parse(text), null, 2); } catch { return text; }
|
||||||
|
}
|
||||||
|
|
||||||
|
function formatYAML(text) {
|
||||||
|
try { return jsyaml.dump(jsyaml.load(text), { indent: 2, lineWidth: 120 }); } catch { return text; }
|
||||||
|
}
|
||||||
|
|
||||||
|
function formatXMLLike(text) {
|
||||||
|
try {
|
||||||
|
// Basic pretty printer for XML/HTML
|
||||||
|
const PADDING = ' ';
|
||||||
|
const reg = /(>)(<)(\/*)/g;
|
||||||
|
let xml = text.replace(reg, '$1\n$2$3');
|
||||||
|
let pad = 0;
|
||||||
|
return xml.split('\n').map((line) => {
|
||||||
|
let indent = 0;
|
||||||
|
if (line.match(/.+<\/\w[^>]*>$/)) {
|
||||||
|
indent = 0;
|
||||||
|
} else if (line.match(/^<\/\w/)) {
|
||||||
|
if (pad) pad -= 1;
|
||||||
|
} else if (line.match(/^<\w([^>]*[^\/])?>.*$/)) {
|
||||||
|
indent = 1;
|
||||||
|
} else {
|
||||||
|
indent = 0;
|
||||||
|
}
|
||||||
|
const padding = PADDING.repeat(pad);
|
||||||
|
pad += indent;
|
||||||
|
return padding + line;
|
||||||
|
}).join('\n');
|
||||||
|
} catch {
|
||||||
|
return text;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function doFormat() {
|
||||||
|
const current = cm ? cm.getValue() : contentEl.value;
|
||||||
|
if (fileExt === 'json') {
|
||||||
|
const out = formatJSON(current);
|
||||||
|
if (cm) cm.setValue(out); else contentEl.value = out;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (fileExt === 'yaml' || fileExt === 'yml') {
|
||||||
|
const out = formatYAML(current);
|
||||||
|
if (cm) cm.setValue(out); else contentEl.value = out;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (fileExt === 'html' || fileExt === 'xml') {
|
||||||
|
const out = formatXMLLike(current);
|
||||||
|
if (cm) cm.setValue(out); else contentEl.value = out;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// no-op for other types
|
||||||
|
}
|
||||||
|
|
||||||
|
// Init editor lazily
|
||||||
|
ensureCodeMirror();
|
||||||
|
|
||||||
|
formatBtn?.addEventListener('click', () => {
|
||||||
|
doFormat();
|
||||||
|
showNotification('Formatted', 'success');
|
||||||
|
});
|
||||||
|
|
||||||
|
form.addEventListener('submit', function(e) {
|
||||||
|
e.preventDefault();
|
||||||
|
if (formatOnSaveEl && formatOnSaveEl.checked) {
|
||||||
|
doFormat();
|
||||||
|
}
|
||||||
|
const formData = new FormData();
|
||||||
|
formData.append('content', cm ? cm.getValue() : contentEl.value);
|
||||||
|
fetch('/edit_text/' + filePath, { method: 'POST', body: formData })
|
||||||
|
.then(r => r.json())
|
||||||
|
.then(data => {
|
||||||
|
if (data.success) {
|
||||||
|
showNotification('File saved', 'success');
|
||||||
|
if (data.redirect) {
|
||||||
|
setTimeout(() => { window.location.href = data.redirect; }, 500);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
throw new Error(data.error || 'Save failed');
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch(err => showNotification('Error: ' + err.message, 'error'));
|
||||||
|
});
|
||||||
|
|
||||||
|
// Ctrl+S to save
|
||||||
|
document.addEventListener('keydown', function(e) {
|
||||||
|
if ((e.ctrlKey || e.metaKey) && e.key === 's') {
|
||||||
|
e.preventDefault();
|
||||||
|
form.dispatchEvent(new Event('submit'));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
{{end}}
|
||||||
@@ -74,6 +74,11 @@
|
|||||||
<i class="fas fa-edit"></i>
|
<i class="fas fa-edit"></i>
|
||||||
</a>
|
</a>
|
||||||
{{end}}
|
{{end}}
|
||||||
|
{{if eq .Type "text"}}
|
||||||
|
<a href="/edit_text/{{.Path}}" class="text-blue-400 hover:text-blue-300 p-2" title="Edit">
|
||||||
|
<i class="fas fa-edit"></i>
|
||||||
|
</a>
|
||||||
|
{{end}}
|
||||||
{{if eq .Type "image"}}
|
{{if eq .Type "image"}}
|
||||||
<a href="/serve_attached_image/{{.Path}}" target="_blank" class="text-yellow-400 hover:text-yellow-300 p-2" title="View">
|
<a href="/serve_attached_image/{{.Path}}" target="_blank" class="text-yellow-400 hover:text-yellow-300 p-2" title="View">
|
||||||
<i class="fas fa-eye"></i>
|
<i class="fas fa-eye"></i>
|
||||||
|
|||||||
@@ -9,6 +9,11 @@
|
|||||||
<div class="flex items-center justify-between mb-4">
|
<div class="flex items-center justify-between mb-4">
|
||||||
<h1 class="text-3xl font-bold text-white">{{.file_name}}</h1>
|
<h1 class="text-3xl font-bold text-white">{{.file_name}}</h1>
|
||||||
<div class="flex items-center space-x-3">
|
<div class="flex items-center space-x-3">
|
||||||
|
{{if .is_editable}}
|
||||||
|
<a href="/edit_text/{{.file_path}}" class="btn-primary">
|
||||||
|
<i class="fas fa-edit mr-2"></i>Edit
|
||||||
|
</a>
|
||||||
|
{{end}}
|
||||||
<a href="/download/{{.file_path}}" class="btn-secondary">
|
<a href="/download/{{.file_path}}" class="btn-secondary">
|
||||||
<i class="fas fa-download mr-2"></i>Download
|
<i class="fas fa-download mr-2"></i>Download
|
||||||
</a>
|
</a>
|
||||||
|
|||||||
Reference in New Issue
Block a user