mirror of https://github.com/usememos/memos.git
Merge 05cd6c5f6a into b0558824c4
This commit is contained in:
commit
f1a1e85a99
|
|
@ -20,7 +20,10 @@ docs/plans/
|
|||
# Docker Compose Environment File
|
||||
.env
|
||||
|
||||
dist
|
||||
# Embedded frontend dist (except placeholder files)
|
||||
server/router/frontend/dist/*
|
||||
!server/router/frontend/dist/.gitkeep
|
||||
!server/router/frontend/dist/index.html
|
||||
|
||||
# VSCode settings
|
||||
.vscode
|
||||
|
|
|
|||
|
|
@ -153,7 +153,7 @@ func (s *service) ExtractProperties(content []byte) (*storepb.MemoPayload_Proper
|
|||
}
|
||||
|
||||
switch n.Kind() {
|
||||
case gast.KindLink:
|
||||
case gast.KindLink, gast.KindAutoLink:
|
||||
prop.HasLink = true
|
||||
|
||||
case gast.KindCodeBlock, gast.KindFencedCodeBlock, gast.KindCodeSpan:
|
||||
|
|
@ -310,7 +310,7 @@ func (s *service) ExtractAll(content []byte) (*ExtractedData, error) {
|
|||
|
||||
// Extract properties based on node kind
|
||||
switch n.Kind() {
|
||||
case gast.KindLink:
|
||||
case gast.KindLink, gast.KindAutoLink:
|
||||
data.Property.HasLink = true
|
||||
|
||||
case gast.KindCodeBlock, gast.KindFencedCodeBlock, gast.KindCodeSpan:
|
||||
|
|
|
|||
|
|
@ -98,23 +98,6 @@ func (s *FileServerService) serveAttachmentFile(c echo.Context) error {
|
|||
return err
|
||||
}
|
||||
|
||||
// Get the binary content
|
||||
blob, err := s.getAttachmentBlob(attachment)
|
||||
if err != nil {
|
||||
return echo.NewHTTPError(http.StatusInternalServerError, "failed to get attachment blob").SetInternal(err)
|
||||
}
|
||||
|
||||
// Handle thumbnail requests for images
|
||||
if thumbnail && s.isImageType(attachment.Type) {
|
||||
thumbnailBlob, err := s.getOrGenerateThumbnail(ctx, attachment)
|
||||
if err != nil {
|
||||
// Log warning but fall back to original image
|
||||
c.Logger().Warnf("failed to get thumbnail: %v", err)
|
||||
} else {
|
||||
blob = thumbnailBlob
|
||||
}
|
||||
}
|
||||
|
||||
// Determine content type
|
||||
contentType := attachment.Type
|
||||
if strings.HasPrefix(contentType, "text/") {
|
||||
|
|
@ -161,18 +144,47 @@ func (s *FileServerService) serveAttachmentFile(c echo.Context) error {
|
|||
|
||||
// For video/audio: Use http.ServeContent for automatic range request support
|
||||
// This is critical for Safari which REQUIRES range request support
|
||||
// Use streaming to avoid loading large files entirely into memory
|
||||
if strings.HasPrefix(contentType, "video/") || strings.HasPrefix(contentType, "audio/") {
|
||||
// Get content as ReadSeeker for efficient streaming
|
||||
content, err := s.getAttachmentReadSeeker(attachment)
|
||||
if err != nil {
|
||||
return echo.NewHTTPError(http.StatusInternalServerError, "failed to get attachment content").SetInternal(err)
|
||||
}
|
||||
defer func() {
|
||||
if closer, ok := content.(io.Closer); ok {
|
||||
closer.Close()
|
||||
}
|
||||
}()
|
||||
|
||||
// ServeContent automatically handles:
|
||||
// - Range request parsing
|
||||
// - HTTP 206 Partial Content responses
|
||||
// - Content-Range headers
|
||||
// - Accept-Ranges: bytes header
|
||||
modTime := time.Unix(attachment.UpdatedTs, 0)
|
||||
http.ServeContent(c.Response(), c.Request(), attachment.Filename, modTime, bytes.NewReader(blob))
|
||||
http.ServeContent(c.Response(), c.Request(), attachment.Filename, modTime, content)
|
||||
return nil
|
||||
}
|
||||
|
||||
// For other files: Simple blob response
|
||||
// For other files: Get the binary content
|
||||
blob, err := s.getAttachmentBlob(attachment)
|
||||
if err != nil {
|
||||
return echo.NewHTTPError(http.StatusInternalServerError, "failed to get attachment blob").SetInternal(err)
|
||||
}
|
||||
|
||||
// Handle thumbnail requests for images
|
||||
if thumbnail && s.isImageType(attachment.Type) {
|
||||
thumbnailBlob, err := s.getOrGenerateThumbnail(ctx, attachment)
|
||||
if err != nil {
|
||||
// Log warning but fall back to original image
|
||||
c.Logger().Warnf("failed to get thumbnail: %v", err)
|
||||
} else {
|
||||
blob = thumbnailBlob
|
||||
}
|
||||
}
|
||||
|
||||
// Simple blob response
|
||||
return c.Blob(http.StatusOK, contentType, blob)
|
||||
}
|
||||
|
||||
|
|
@ -407,6 +419,36 @@ func (s *FileServerService) getAttachmentReader(attachment *store.Attachment) (i
|
|||
return io.NopCloser(bytes.NewReader(attachment.Blob)), nil
|
||||
}
|
||||
|
||||
// getAttachmentReadSeeker returns an io.ReadSeeker for the attachment content.
|
||||
// This is more efficient than getAttachmentBlob for large files as it streams
|
||||
// content without loading everything into memory.
|
||||
func (s *FileServerService) getAttachmentReadSeeker(attachment *store.Attachment) (io.ReadSeeker, error) {
|
||||
// For local storage, open the file and return it (files are ReadSeekers)
|
||||
if attachment.StorageType == storepb.AttachmentStorageType_LOCAL {
|
||||
attachmentPath := filepath.FromSlash(attachment.Reference)
|
||||
if !filepath.IsAbs(attachmentPath) {
|
||||
attachmentPath = filepath.Join(s.Profile.Data, attachmentPath)
|
||||
}
|
||||
|
||||
file, err := os.Open(attachmentPath)
|
||||
if err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
return nil, errors.Wrap(err, "file not found")
|
||||
}
|
||||
return nil, errors.Wrap(err, "failed to open the file")
|
||||
}
|
||||
return file, nil
|
||||
}
|
||||
|
||||
// For S3 storage and database storage, we need to load into memory
|
||||
// since we don't have seekable streams from these sources
|
||||
blob, err := s.getAttachmentBlob(attachment)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return bytes.NewReader(blob), nil
|
||||
}
|
||||
|
||||
// getAttachmentBlob retrieves the binary content of an attachment from storage.
|
||||
func (s *FileServerService) getAttachmentBlob(attachment *store.Attachment) ([]byte, error) {
|
||||
// For local storage, read the file from the local disk.
|
||||
|
|
|
|||
|
|
@ -0,0 +1,2 @@
|
|||
# This file ensures the dist directory exists for Go embed.
|
||||
# Run `cd web && pnpm release` to build the frontend assets here.
|
||||
|
|
@ -1,11 +1,22 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Memos</title>
|
||||
</head>
|
||||
<body>
|
||||
No embeddable frontend found.
|
||||
</body>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>Memos - Frontend Not Built</title>
|
||||
<style>
|
||||
body { font-family: system-ui, sans-serif; display: flex; justify-content: center; align-items: center; min-height: 100vh; margin: 0; background: #f5f5f5; }
|
||||
.container { text-align: center; padding: 2rem; background: white; border-radius: 8px; box-shadow: 0 2px 8px rgba(0,0,0,0.1); max-width: 500px; }
|
||||
h1 { color: #333; margin-bottom: 1rem; }
|
||||
p { color: #666; line-height: 1.6; }
|
||||
code { background: #e8e8e8; padding: 0.2rem 0.5rem; border-radius: 4px; font-size: 0.9rem; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<h1>🚧 Frontend Not Built</h1>
|
||||
<p>The frontend assets have not been built yet. Please run:</p>
|
||||
<p><code>cd web && pnpm install && pnpm release</code></p>
|
||||
<p>Then restart the server.</p>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
|
|
|
|||
Loading…
Reference in New Issue