diff --git a/api/v1/rss.go b/api/v1/rss.go index 9ad4055a..ec2f8acf 100644 --- a/api/v1/rss.go +++ b/api/v1/rss.go @@ -13,14 +13,17 @@ import ( "github.com/labstack/echo/v4" "github.com/usememos/memos/internal/util" + "github.com/usememos/memos/plugin/gomark/ast" "github.com/usememos/memos/plugin/gomark/parser" "github.com/usememos/memos/plugin/gomark/parser/tokenizer" "github.com/usememos/memos/plugin/gomark/renderer" "github.com/usememos/memos/store" ) -const maxRSSItemCount = 100 -const maxRSSItemTitleLength = 100 +const ( + maxRSSItemCount = 100 + maxRSSItemTitleLength = 128 +) func (s *APIV1Service) registerRSSRoutes(g *echo.Group) { g.GET("/explore/rss.xml", s.GetExploreRSS) @@ -171,29 +174,24 @@ func (s *APIV1Service) getSystemCustomizedProfile(ctx context.Context) (*Customi } func getRSSItemTitle(content string) string { - var title string - if isTitleDefined(content) { - title = strings.Split(content, "\n")[0][2:] - } else { - title = strings.Split(content, "\n")[0] - var titleLengthLimit = util.Min(len(title), maxRSSItemTitleLength) - if titleLengthLimit < len(title) { - title = title[:titleLengthLimit] + "..." - } + tokens := tokenizer.Tokenize(content) + nodes, _ := parser.Parse(tokens) + if len(nodes) > 0 { + firstNode := nodes[0] + title := renderer.NewStringRenderer().Render([]ast.Node{firstNode}) + return title + } + + title := strings.Split(content, "\n")[0] + var titleLengthLimit = util.Min(len(title), maxRSSItemTitleLength) + if titleLengthLimit < len(title) { + title = title[:titleLengthLimit] + "..." } return title } func getRSSItemDescription(content string) (string, error) { - var description string - if isTitleDefined(content) { - var firstLineEnd = strings.Index(content, "\n") - description = strings.Trim(content[firstLineEnd+1:], " ") - } else { - description = content - } - - tokens := tokenizer.Tokenize(description) + tokens := tokenizer.Tokenize(content) nodes, err := parser.Parse(tokens) if err != nil { return "", err @@ -201,7 +199,3 @@ func getRSSItemDescription(content string) (string, error) { result := renderer.NewHTMLRenderer().Render(nodes) return result, nil } - -func isTitleDefined(content string) bool { - return strings.HasPrefix(content, "# ") -} diff --git a/server/frontend/frontend.go b/server/frontend/frontend.go index 561d7f97..3cc7ea91 100644 --- a/server/frontend/frontend.go +++ b/server/frontend/frontend.go @@ -1,6 +1,7 @@ package frontend import ( + "context" "fmt" "html/template" "net/http" @@ -10,7 +11,7 @@ import ( "github.com/labstack/echo/v4" "github.com/labstack/echo/v4/middleware" - v1 "github.com/usememos/memos/api/v1" + apiv1 "github.com/usememos/memos/api/v1" "github.com/usememos/memos/internal/util" "github.com/usememos/memos/plugin/gomark/parser" "github.com/usememos/memos/plugin/gomark/parser/tokenizer" @@ -19,6 +20,11 @@ import ( "github.com/usememos/memos/store" ) +const ( + // maxMetadataDescriptionLength is the maximum length of metadata description. + maxMetadataDescriptionLength = 256 +) + type FrontendService struct { Profile *profile.Profile Store *store.Store @@ -31,39 +37,67 @@ func NewFrontendService(profile *profile.Profile, store *store.Store) *FrontendS } } -func (s *FrontendService) Serve(e *echo.Echo) { +func (s *FrontendService) Serve(ctx context.Context, e *echo.Echo) { // Use echo static middleware to serve the built dist folder. // refer: https://github.com/labstack/echo/blob/master/middleware/static.go e.Use(middleware.StaticWithConfig(middleware.StaticConfig{ - HTML5: true, - Filesystem: http.Dir("dist"), + Root: "dist", + HTML5: true, Skipper: func(c echo.Context) bool { return util.HasPrefixes(c.Path(), "/api", "/memos.api.v2", "/robots.txt", "/sitemap.xml", "/m/:memoID") }, })) s.registerRoutes(e) + s.registerFileRoutes(ctx, e) } func (s *FrontendService) registerRoutes(e *echo.Echo) { rawIndexHTML := getRawIndexHTML() - e.GET("/robots.txt", func(c echo.Context) error { + e.GET("/m/:memoID", func(c echo.Context) error { ctx := c.Request().Context() - instanceURLSetting, err := s.Store.GetSystemSetting(ctx, &store.FindSystemSetting{ - Name: v1.SystemSettingInstanceURLName.String(), + memoID, err := util.ConvertStringToInt32(c.Param("memoID")) + if err != nil { + // Redirect to `index.html` if any error occurs. + return c.HTML(http.StatusOK, rawIndexHTML) + } + + memo, err := s.Store.GetMemo(ctx, &store.FindMemo{ + ID: &memoID, }) if err != nil { - return echo.NewHTTPError(http.StatusInternalServerError, "Failed to get instance URL system setting").SetInternal(err) + return c.HTML(http.StatusOK, rawIndexHTML) } - if instanceURLSetting == nil { - return echo.NewHTTPError(http.StatusInternalServerError, "Instance URL system setting is not set") + if memo == nil { + return c.HTML(http.StatusOK, rawIndexHTML) } - instanceURL := instanceURLSetting.Value - if instanceURL == "" { - return echo.NewHTTPError(http.StatusInternalServerError, "Instance URL system setting is not set") + creator, err := s.Store.GetUser(ctx, &store.FindUser{ + ID: &memo.CreatorID, + }) + if err != nil { + return c.HTML(http.StatusOK, rawIndexHTML) } + // Inject memo metadata into `index.html`. + indexHTML := strings.ReplaceAll(rawIndexHTML, "", generateMemoMetadata(memo, creator)) + return c.HTML(http.StatusOK, indexHTML) + }) +} + +func (s *FrontendService) registerFileRoutes(ctx context.Context, e *echo.Echo) { + instanceURLSetting, err := s.Store.GetSystemSetting(ctx, &store.FindSystemSetting{ + Name: apiv1.SystemSettingInstanceURLName.String(), + }) + if err != nil || instanceURLSetting == nil { + return + } + instanceURL := instanceURLSetting.Value + if instanceURL == "" { + return + } + + e.GET("/robots.txt", func(c echo.Context) error { robotsTxt := fmt.Sprintf(`User-agent: * Allow: / Host: %s @@ -73,20 +107,6 @@ Sitemap: %s/sitemap.xml`, instanceURL, instanceURL) e.GET("/sitemap.xml", func(c echo.Context) error { ctx := c.Request().Context() - instanceURLSetting, err := s.Store.GetSystemSetting(ctx, &store.FindSystemSetting{ - Name: v1.SystemSettingInstanceURLName.String(), - }) - if err != nil { - return echo.NewHTTPError(http.StatusInternalServerError, "Failed to get instance URL system setting").SetInternal(err) - } - if instanceURLSetting == nil { - return echo.NewHTTPError(http.StatusInternalServerError, "Instance URL system setting is not set") - } - instanceURL := instanceURLSetting.Value - if instanceURL == "" { - return echo.NewHTTPError(http.StatusInternalServerError, "Instance URL system setting is not set") - } - urlsets := []string{} // Append memo list. memoList, err := s.Store.ListMemos(ctx, &store.FindMemo{ @@ -110,35 +130,6 @@ Sitemap: %s/sitemap.xml`, instanceURL, instanceURL) sitemap := fmt.Sprintf(`%s`, strings.Join(urlsets, "\n")) return c.XMLBlob(http.StatusOK, []byte(sitemap)) }) - - e.GET("/m/:memoID", func(c echo.Context) error { - ctx := c.Request().Context() - memoID, err := util.ConvertStringToInt32(c.Param("memoID")) - if err != nil { - // Redirect to `index.html` if any error occurs. - return c.HTML(http.StatusOK, rawIndexHTML) - } - - memo, err := s.Store.GetMemo(ctx, &store.FindMemo{ - ID: &memoID, - }) - if err != nil { - return c.HTML(http.StatusOK, rawIndexHTML) - } - if memo == nil { - return c.HTML(http.StatusOK, rawIndexHTML) - } - creator, err := s.Store.GetUser(ctx, &store.FindUser{ - ID: &memo.CreatorID, - }) - if err != nil { - return c.HTML(http.StatusOK, rawIndexHTML) - } - - // Inject memo metadata into `index.html`. - indexHTML := strings.ReplaceAll(rawIndexHTML, "", generateMemoMetadata(memo, creator)) - return c.HTML(http.StatusOK, indexHTML) - }) } func generateMemoMetadata(memo *store.Memo, creator *store.User) string { @@ -154,8 +145,8 @@ func generateMemoMetadata(memo *store.Memo, creator *store.User) string { if len(description) == 0 { description = memo.Content } - if len(description) > 200 { - description = description[:200] + "..." + if len(description) > maxMetadataDescriptionLength { + description = description[:maxMetadataDescriptionLength] + "..." } }