Commit 155c5baf authored by Steven's avatar Steven

refactor: add markdown service

parent 43382346
...@@ -160,7 +160,7 @@ func initConfig() { ...@@ -160,7 +160,7 @@ func initConfig() {
var err error var err error
instanceProfile, err = profile.GetProfile() instanceProfile, err = profile.GetProfile()
if err != nil { if err != nil {
fmt.Printf("failed to get profile, error: %+v\n", err) slog.Error("failed to get profile", err)
return return
} }
......
This diff is collapsed.
...@@ -32,7 +32,7 @@ func GetHTMLMeta(urlStr string) (*HTMLMeta, error) { ...@@ -32,7 +32,7 @@ func GetHTMLMeta(urlStr string) (*HTMLMeta, error) {
return nil, err return nil, err
} }
if mediatype != "text/html" { if mediatype != "text/html" {
return nil, errors.New("Wrong website mediatype") return nil, errors.New("not a HTML page")
} }
htmlMeta := extractHTMLMeta(response.Body) htmlMeta := extractHTMLMeta(response.Body)
......
syntax = "proto3";
package memos.api.v1;
import "google/api/annotations.proto";
option go_package = "gen/api/v1";
service LinkService {
// GetLinkMetadata returns metadata for a given link.
rpc GetLinkMetadata(GetLinkMetadataRequest) returns (GetLinkMetadataResponse) {
option (google.api.http) = {get: "/api/v1/linkMetadata"};
}
}
message GetLinkMetadataRequest {
string link = 1;
}
message GetLinkMetadataResponse {
LinkMetadata link_metadata = 1;
}
message LinkMetadata {
string title = 1;
string description = 2;
string image = 3;
}
syntax = "proto3";
package memos.api.v1;
import "google/api/annotations.proto";
option go_package = "gen/api/v1";
service MarkdownService {
// Parses the given markdown content and returns a list of nodes.
rpc ParseMarkdown(ParseMarkdownRequest) returns (ParseMarkdownResponse) {
option (google.api.http) = {
post: "/api/v1/markdown/parse"
body: "*"
};
}
// Restores the given nodes to markdown content.
rpc RestoreMarkdown(RestoreMarkdownRequest) returns (RestoreMarkdownResponse) {
option (google.api.http) = {
post: "/api/v1/markdown:restore"
body: "*"
};
}
// GetLinkMetadata returns metadata for a given link.
rpc GetLinkMetadata(GetLinkMetadataRequest) returns (LinkMetadata) {
option (google.api.http) = {get: "/api/v1/markdown/link:metadata"};
}
}
message ParseMarkdownRequest {
string markdown = 1;
}
message ParseMarkdownResponse {
repeated Node nodes = 1;
}
message RestoreMarkdownRequest {
repeated Node nodes = 1;
}
message RestoreMarkdownResponse {
string markdown = 1;
}
message GetLinkMetadataRequest {
string link = 1;
}
message LinkMetadata {
string title = 1;
string description = 2;
string image = 3;
}
enum NodeType {
NODE_UNSPECIFIED = 0;
LINE_BREAK = 1;
PARAGRAPH = 2;
CODE_BLOCK = 3;
HEADING = 4;
HORIZONTAL_RULE = 5;
BLOCKQUOTE = 6;
ORDERED_LIST = 7;
UNORDERED_LIST = 8;
TASK_LIST = 9;
MATH_BLOCK = 10;
TABLE = 11;
EMBEDDED_CONTENT = 12;
TEXT = 13;
BOLD = 14;
ITALIC = 15;
BOLD_ITALIC = 16;
CODE = 17;
IMAGE = 18;
LINK = 19;
AUTO_LINK = 20;
TAG = 21;
STRIKETHROUGH = 22;
ESCAPING_CHARACTER = 23;
MATH = 24;
HIGHLIGHT = 25;
SUBSCRIPT = 26;
SUPERSCRIPT = 27;
REFERENCED_CONTENT = 28;
SPOILER = 29;
}
message Node {
NodeType type = 1;
oneof node {
LineBreakNode line_break_node = 2;
ParagraphNode paragraph_node = 3;
CodeBlockNode code_block_node = 4;
HeadingNode heading_node = 5;
HorizontalRuleNode horizontal_rule_node = 6;
BlockquoteNode blockquote_node = 7;
OrderedListNode ordered_list_node = 8;
UnorderedListNode unordered_list_node = 9;
TaskListNode task_list_node = 10;
MathBlockNode math_block_node = 11;
TableNode table_node = 12;
EmbeddedContentNode embedded_content_node = 13;
TextNode text_node = 14;
BoldNode bold_node = 15;
ItalicNode italic_node = 16;
BoldItalicNode bold_italic_node = 17;
CodeNode code_node = 18;
ImageNode image_node = 19;
LinkNode link_node = 20;
AutoLinkNode auto_link_node = 21;
TagNode tag_node = 22;
StrikethroughNode strikethrough_node = 23;
EscapingCharacterNode escaping_character_node = 24;
MathNode math_node = 25;
HighlightNode highlight_node = 26;
SubscriptNode subscript_node = 27;
SuperscriptNode superscript_node = 28;
ReferencedContentNode referenced_content_node = 29;
SpoilerNode spoiler_node = 30;
}
}
message LineBreakNode {}
message ParagraphNode {
repeated Node children = 1;
}
message CodeBlockNode {
string language = 1;
string content = 2;
}
message HeadingNode {
int32 level = 1;
repeated Node children = 2;
}
message HorizontalRuleNode {
string symbol = 1;
}
message BlockquoteNode {
repeated Node children = 1;
}
message OrderedListNode {
string number = 1;
int32 indent = 2;
repeated Node children = 3;
}
message UnorderedListNode {
string symbol = 1;
int32 indent = 2;
repeated Node children = 3;
}
message TaskListNode {
string symbol = 1;
int32 indent = 2;
bool complete = 3;
repeated Node children = 4;
}
message MathBlockNode {
string content = 1;
}
message TableNode {
repeated string header = 1;
repeated string delimiter = 2;
message Row {
repeated string cells = 1;
}
repeated Row rows = 3;
}
message EmbeddedContentNode {
string resource_name = 1;
string params = 2;
}
message TextNode {
string content = 1;
}
message BoldNode {
string symbol = 1;
repeated Node children = 2;
}
message ItalicNode {
string symbol = 1;
string content = 2;
}
message BoldItalicNode {
string symbol = 1;
string content = 2;
}
message CodeNode {
string content = 1;
}
message ImageNode {
string alt_text = 1;
string url = 2;
}
message LinkNode {
string text = 1;
string url = 2;
}
message AutoLinkNode {
string url = 1;
bool is_raw_text = 2;
}
message TagNode {
string content = 1;
}
message StrikethroughNode {
string content = 1;
}
message EscapingCharacterNode {
string symbol = 1;
}
message MathNode {
string content = 1;
}
message HighlightNode {
string content = 1;
}
message SubscriptNode {
string content = 1;
}
message SuperscriptNode {
string content = 1;
}
message ReferencedContentNode {
string resource_name = 1;
string params = 2;
}
message SpoilerNode {
string content = 1;
}
...@@ -3,6 +3,7 @@ syntax = "proto3"; ...@@ -3,6 +3,7 @@ syntax = "proto3";
package memos.api.v1; package memos.api.v1;
import "api/v1/common.proto"; import "api/v1/common.proto";
import "api/v1/markdown_service.proto";
import "api/v1/memo_relation_service.proto"; import "api/v1/memo_relation_service.proto";
import "api/v1/reaction_service.proto"; import "api/v1/reaction_service.proto";
import "api/v1/resource_service.proto"; import "api/v1/resource_service.proto";
...@@ -146,21 +147,23 @@ message Memo { ...@@ -146,21 +147,23 @@ message Memo {
google.protobuf.Timestamp update_time = 6; google.protobuf.Timestamp update_time = 6;
google.protobuf.Timestamp display_time = 78; google.protobuf.Timestamp display_time = 7;
string content = 8; string content = 8;
Visibility visibility = 9; repeated Node nodes = 9 [(google.api.field_behavior) = OUTPUT_ONLY];
bool pinned = 10; Visibility visibility = 10;
optional int32 parent_id = 11 [(google.api.field_behavior) = OUTPUT_ONLY]; bool pinned = 11;
repeated Resource resources = 12 [(google.api.field_behavior) = OUTPUT_ONLY]; optional int32 parent_id = 12 [(google.api.field_behavior) = OUTPUT_ONLY];
repeated MemoRelation relations = 13 [(google.api.field_behavior) = OUTPUT_ONLY]; repeated Resource resources = 13 [(google.api.field_behavior) = OUTPUT_ONLY];
repeated Reaction reactions = 14 [(google.api.field_behavior) = OUTPUT_ONLY]; repeated MemoRelation relations = 14 [(google.api.field_behavior) = OUTPUT_ONLY];
repeated Reaction reactions = 15 [(google.api.field_behavior) = OUTPUT_ONLY];
} }
message CreateMemoRequest { message CreateMemoRequest {
......
This diff is collapsed.
// Code generated by protoc-gen-grpc-gateway. DO NOT EDIT.
// source: api/v1/link_service.proto
/*
Package apiv1 is a reverse proxy.
It translates gRPC into RESTful JSON APIs.
*/
package apiv1
import (
"context"
"io"
"net/http"
"github.com/grpc-ecosystem/grpc-gateway/v2/runtime"
"github.com/grpc-ecosystem/grpc-gateway/v2/utilities"
"google.golang.org/grpc"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/grpclog"
"google.golang.org/grpc/metadata"
"google.golang.org/grpc/status"
"google.golang.org/protobuf/proto"
)
// Suppress "imported and not used" errors
var _ codes.Code
var _ io.Reader
var _ status.Status
var _ = runtime.String
var _ = utilities.NewDoubleArray
var _ = metadata.Join
var (
filter_LinkService_GetLinkMetadata_0 = &utilities.DoubleArray{Encoding: map[string]int{}, Base: []int(nil), Check: []int(nil)}
)
func request_LinkService_GetLinkMetadata_0(ctx context.Context, marshaler runtime.Marshaler, client LinkServiceClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
var protoReq GetLinkMetadataRequest
var metadata runtime.ServerMetadata
if err := req.ParseForm(); err != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
}
if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_LinkService_GetLinkMetadata_0); err != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
}
msg, err := client.GetLinkMetadata(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD))
return msg, metadata, err
}
func local_request_LinkService_GetLinkMetadata_0(ctx context.Context, marshaler runtime.Marshaler, server LinkServiceServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
var protoReq GetLinkMetadataRequest
var metadata runtime.ServerMetadata
if err := req.ParseForm(); err != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
}
if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_LinkService_GetLinkMetadata_0); err != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
}
msg, err := server.GetLinkMetadata(ctx, &protoReq)
return msg, metadata, err
}
// RegisterLinkServiceHandlerServer registers the http handlers for service LinkService to "mux".
// UnaryRPC :call LinkServiceServer directly.
// StreamingRPC :currently unsupported pending https://github.com/grpc/grpc-go/issues/906.
// Note that using this registration option will cause many gRPC library features to stop working. Consider using RegisterLinkServiceHandlerFromEndpoint instead.
func RegisterLinkServiceHandlerServer(ctx context.Context, mux *runtime.ServeMux, server LinkServiceServer) error {
mux.Handle("GET", pattern_LinkService_GetLinkMetadata_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
ctx, cancel := context.WithCancel(req.Context())
defer cancel()
var stream runtime.ServerTransportStream
ctx = grpc.NewContextWithServerTransportStream(ctx, &stream)
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
var err error
var annotatedContext context.Context
annotatedContext, err = runtime.AnnotateIncomingContext(ctx, mux, req, "/memos.api.v1.LinkService/GetLinkMetadata", runtime.WithHTTPPathPattern("/api/v1/linkMetadata"))
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
resp, md, err := local_request_LinkService_GetLinkMetadata_0(annotatedContext, inboundMarshaler, server, req, pathParams)
md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer())
annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md)
if err != nil {
runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err)
return
}
forward_LinkService_GetLinkMetadata_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
})
return nil
}
// RegisterLinkServiceHandlerFromEndpoint is same as RegisterLinkServiceHandler but
// automatically dials to "endpoint" and closes the connection when "ctx" gets done.
func RegisterLinkServiceHandlerFromEndpoint(ctx context.Context, mux *runtime.ServeMux, endpoint string, opts []grpc.DialOption) (err error) {
conn, err := grpc.DialContext(ctx, endpoint, opts...)
if err != nil {
return err
}
defer func() {
if err != nil {
if cerr := conn.Close(); cerr != nil {
grpclog.Infof("Failed to close conn to %s: %v", endpoint, cerr)
}
return
}
go func() {
<-ctx.Done()
if cerr := conn.Close(); cerr != nil {
grpclog.Infof("Failed to close conn to %s: %v", endpoint, cerr)
}
}()
}()
return RegisterLinkServiceHandler(ctx, mux, conn)
}
// RegisterLinkServiceHandler registers the http handlers for service LinkService to "mux".
// The handlers forward requests to the grpc endpoint over "conn".
func RegisterLinkServiceHandler(ctx context.Context, mux *runtime.ServeMux, conn *grpc.ClientConn) error {
return RegisterLinkServiceHandlerClient(ctx, mux, NewLinkServiceClient(conn))
}
// RegisterLinkServiceHandlerClient registers the http handlers for service LinkService
// to "mux". The handlers forward requests to the grpc endpoint over the given implementation of "LinkServiceClient".
// Note: the gRPC framework executes interceptors within the gRPC handler. If the passed in "LinkServiceClient"
// doesn't go through the normal gRPC flow (creating a gRPC client etc.) then it will be up to the passed in
// "LinkServiceClient" to call the correct interceptors.
func RegisterLinkServiceHandlerClient(ctx context.Context, mux *runtime.ServeMux, client LinkServiceClient) error {
mux.Handle("GET", pattern_LinkService_GetLinkMetadata_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
ctx, cancel := context.WithCancel(req.Context())
defer cancel()
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
var err error
var annotatedContext context.Context
annotatedContext, err = runtime.AnnotateContext(ctx, mux, req, "/memos.api.v1.LinkService/GetLinkMetadata", runtime.WithHTTPPathPattern("/api/v1/linkMetadata"))
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
resp, md, err := request_LinkService_GetLinkMetadata_0(annotatedContext, inboundMarshaler, client, req, pathParams)
annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md)
if err != nil {
runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err)
return
}
forward_LinkService_GetLinkMetadata_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
})
return nil
}
var (
pattern_LinkService_GetLinkMetadata_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2}, []string{"api", "v1", "linkMetadata"}, ""))
)
var (
forward_LinkService_GetLinkMetadata_0 = runtime.ForwardResponseMessage
)
// Code generated by protoc-gen-go-grpc. DO NOT EDIT.
// versions:
// - protoc-gen-go-grpc v1.3.0
// - protoc (unknown)
// source: api/v1/link_service.proto
package apiv1
import (
context "context"
grpc "google.golang.org/grpc"
codes "google.golang.org/grpc/codes"
status "google.golang.org/grpc/status"
)
// This is a compile-time assertion to ensure that this generated file
// is compatible with the grpc package it is being compiled against.
// Requires gRPC-Go v1.32.0 or later.
const _ = grpc.SupportPackageIsVersion7
const (
LinkService_GetLinkMetadata_FullMethodName = "/memos.api.v1.LinkService/GetLinkMetadata"
)
// LinkServiceClient is the client API for LinkService service.
//
// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream.
type LinkServiceClient interface {
// GetLinkMetadata returns metadata for a given link.
GetLinkMetadata(ctx context.Context, in *GetLinkMetadataRequest, opts ...grpc.CallOption) (*GetLinkMetadataResponse, error)
}
type linkServiceClient struct {
cc grpc.ClientConnInterface
}
func NewLinkServiceClient(cc grpc.ClientConnInterface) LinkServiceClient {
return &linkServiceClient{cc}
}
func (c *linkServiceClient) GetLinkMetadata(ctx context.Context, in *GetLinkMetadataRequest, opts ...grpc.CallOption) (*GetLinkMetadataResponse, error) {
out := new(GetLinkMetadataResponse)
err := c.cc.Invoke(ctx, LinkService_GetLinkMetadata_FullMethodName, in, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
// LinkServiceServer is the server API for LinkService service.
// All implementations must embed UnimplementedLinkServiceServer
// for forward compatibility
type LinkServiceServer interface {
// GetLinkMetadata returns metadata for a given link.
GetLinkMetadata(context.Context, *GetLinkMetadataRequest) (*GetLinkMetadataResponse, error)
mustEmbedUnimplementedLinkServiceServer()
}
// UnimplementedLinkServiceServer must be embedded to have forward compatible implementations.
type UnimplementedLinkServiceServer struct {
}
func (UnimplementedLinkServiceServer) GetLinkMetadata(context.Context, *GetLinkMetadataRequest) (*GetLinkMetadataResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method GetLinkMetadata not implemented")
}
func (UnimplementedLinkServiceServer) mustEmbedUnimplementedLinkServiceServer() {}
// UnsafeLinkServiceServer may be embedded to opt out of forward compatibility for this service.
// Use of this interface is not recommended, as added methods to LinkServiceServer will
// result in compilation errors.
type UnsafeLinkServiceServer interface {
mustEmbedUnimplementedLinkServiceServer()
}
func RegisterLinkServiceServer(s grpc.ServiceRegistrar, srv LinkServiceServer) {
s.RegisterService(&LinkService_ServiceDesc, srv)
}
func _LinkService_GetLinkMetadata_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(GetLinkMetadataRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(LinkServiceServer).GetLinkMetadata(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: LinkService_GetLinkMetadata_FullMethodName,
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(LinkServiceServer).GetLinkMetadata(ctx, req.(*GetLinkMetadataRequest))
}
return interceptor(ctx, in, info, handler)
}
// LinkService_ServiceDesc is the grpc.ServiceDesc for LinkService service.
// It's only intended for direct use with grpc.RegisterService,
// and not to be introspected or modified (even as a copy)
var LinkService_ServiceDesc = grpc.ServiceDesc{
ServiceName: "memos.api.v1.LinkService",
HandlerType: (*LinkServiceServer)(nil),
Methods: []grpc.MethodDesc{
{
MethodName: "GetLinkMetadata",
Handler: _LinkService_GetLinkMetadata_Handler,
},
},
Streams: []grpc.StreamDesc{},
Metadata: "api/v1/link_service.proto",
}
This source diff could not be displayed because it is too large. You can view the blob instead.
This diff is collapsed.
// Code generated by protoc-gen-go-grpc. DO NOT EDIT.
// versions:
// - protoc-gen-go-grpc v1.3.0
// - protoc (unknown)
// source: api/v1/markdown_service.proto
package apiv1
import (
context "context"
grpc "google.golang.org/grpc"
codes "google.golang.org/grpc/codes"
status "google.golang.org/grpc/status"
)
// This is a compile-time assertion to ensure that this generated file
// is compatible with the grpc package it is being compiled against.
// Requires gRPC-Go v1.32.0 or later.
const _ = grpc.SupportPackageIsVersion7
const (
MarkdownService_ParseMarkdown_FullMethodName = "/memos.api.v1.MarkdownService/ParseMarkdown"
MarkdownService_RestoreMarkdown_FullMethodName = "/memos.api.v1.MarkdownService/RestoreMarkdown"
MarkdownService_GetLinkMetadata_FullMethodName = "/memos.api.v1.MarkdownService/GetLinkMetadata"
)
// MarkdownServiceClient is the client API for MarkdownService service.
//
// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream.
type MarkdownServiceClient interface {
// Parses the given markdown content and returns a list of nodes.
ParseMarkdown(ctx context.Context, in *ParseMarkdownRequest, opts ...grpc.CallOption) (*ParseMarkdownResponse, error)
// Restores the given nodes to markdown content.
RestoreMarkdown(ctx context.Context, in *RestoreMarkdownRequest, opts ...grpc.CallOption) (*RestoreMarkdownResponse, error)
// GetLinkMetadata returns metadata for a given link.
GetLinkMetadata(ctx context.Context, in *GetLinkMetadataRequest, opts ...grpc.CallOption) (*LinkMetadata, error)
}
type markdownServiceClient struct {
cc grpc.ClientConnInterface
}
func NewMarkdownServiceClient(cc grpc.ClientConnInterface) MarkdownServiceClient {
return &markdownServiceClient{cc}
}
func (c *markdownServiceClient) ParseMarkdown(ctx context.Context, in *ParseMarkdownRequest, opts ...grpc.CallOption) (*ParseMarkdownResponse, error) {
out := new(ParseMarkdownResponse)
err := c.cc.Invoke(ctx, MarkdownService_ParseMarkdown_FullMethodName, in, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *markdownServiceClient) RestoreMarkdown(ctx context.Context, in *RestoreMarkdownRequest, opts ...grpc.CallOption) (*RestoreMarkdownResponse, error) {
out := new(RestoreMarkdownResponse)
err := c.cc.Invoke(ctx, MarkdownService_RestoreMarkdown_FullMethodName, in, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *markdownServiceClient) GetLinkMetadata(ctx context.Context, in *GetLinkMetadataRequest, opts ...grpc.CallOption) (*LinkMetadata, error) {
out := new(LinkMetadata)
err := c.cc.Invoke(ctx, MarkdownService_GetLinkMetadata_FullMethodName, in, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
// MarkdownServiceServer is the server API for MarkdownService service.
// All implementations must embed UnimplementedMarkdownServiceServer
// for forward compatibility
type MarkdownServiceServer interface {
// Parses the given markdown content and returns a list of nodes.
ParseMarkdown(context.Context, *ParseMarkdownRequest) (*ParseMarkdownResponse, error)
// Restores the given nodes to markdown content.
RestoreMarkdown(context.Context, *RestoreMarkdownRequest) (*RestoreMarkdownResponse, error)
// GetLinkMetadata returns metadata for a given link.
GetLinkMetadata(context.Context, *GetLinkMetadataRequest) (*LinkMetadata, error)
mustEmbedUnimplementedMarkdownServiceServer()
}
// UnimplementedMarkdownServiceServer must be embedded to have forward compatible implementations.
type UnimplementedMarkdownServiceServer struct {
}
func (UnimplementedMarkdownServiceServer) ParseMarkdown(context.Context, *ParseMarkdownRequest) (*ParseMarkdownResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method ParseMarkdown not implemented")
}
func (UnimplementedMarkdownServiceServer) RestoreMarkdown(context.Context, *RestoreMarkdownRequest) (*RestoreMarkdownResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method RestoreMarkdown not implemented")
}
func (UnimplementedMarkdownServiceServer) GetLinkMetadata(context.Context, *GetLinkMetadataRequest) (*LinkMetadata, error) {
return nil, status.Errorf(codes.Unimplemented, "method GetLinkMetadata not implemented")
}
func (UnimplementedMarkdownServiceServer) mustEmbedUnimplementedMarkdownServiceServer() {}
// UnsafeMarkdownServiceServer may be embedded to opt out of forward compatibility for this service.
// Use of this interface is not recommended, as added methods to MarkdownServiceServer will
// result in compilation errors.
type UnsafeMarkdownServiceServer interface {
mustEmbedUnimplementedMarkdownServiceServer()
}
func RegisterMarkdownServiceServer(s grpc.ServiceRegistrar, srv MarkdownServiceServer) {
s.RegisterService(&MarkdownService_ServiceDesc, srv)
}
func _MarkdownService_ParseMarkdown_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(ParseMarkdownRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(MarkdownServiceServer).ParseMarkdown(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: MarkdownService_ParseMarkdown_FullMethodName,
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(MarkdownServiceServer).ParseMarkdown(ctx, req.(*ParseMarkdownRequest))
}
return interceptor(ctx, in, info, handler)
}
func _MarkdownService_RestoreMarkdown_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(RestoreMarkdownRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(MarkdownServiceServer).RestoreMarkdown(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: MarkdownService_RestoreMarkdown_FullMethodName,
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(MarkdownServiceServer).RestoreMarkdown(ctx, req.(*RestoreMarkdownRequest))
}
return interceptor(ctx, in, info, handler)
}
func _MarkdownService_GetLinkMetadata_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(GetLinkMetadataRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(MarkdownServiceServer).GetLinkMetadata(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: MarkdownService_GetLinkMetadata_FullMethodName,
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(MarkdownServiceServer).GetLinkMetadata(ctx, req.(*GetLinkMetadataRequest))
}
return interceptor(ctx, in, info, handler)
}
// MarkdownService_ServiceDesc is the grpc.ServiceDesc for MarkdownService service.
// It's only intended for direct use with grpc.RegisterService,
// and not to be introspected or modified (even as a copy)
var MarkdownService_ServiceDesc = grpc.ServiceDesc{
ServiceName: "memos.api.v1.MarkdownService",
HandlerType: (*MarkdownServiceServer)(nil),
Methods: []grpc.MethodDesc{
{
MethodName: "ParseMarkdown",
Handler: _MarkdownService_ParseMarkdown_Handler,
},
{
MethodName: "RestoreMarkdown",
Handler: _MarkdownService_RestoreMarkdown_Handler,
},
{
MethodName: "GetLinkMetadata",
Handler: _MarkdownService_GetLinkMetadata_Handler,
},
},
Streams: []grpc.StreamDesc{},
Metadata: "api/v1/markdown_service.proto",
}
This diff is collapsed.
This diff is collapsed.
...@@ -27,8 +27,7 @@ message WorkspaceSetting { ...@@ -27,8 +27,7 @@ message WorkspaceSetting {
} }
message WorkspaceBasicSetting { message WorkspaceBasicSetting {
string server_id = 1; string secret_key = 1;
string secret_key = 2;
} }
message WorkspaceGeneralSetting { message WorkspaceGeneralSetting {
......
package v1
import (
"context"
getter "github.com/usememos/memos/plugin/http-getter"
v1pb "github.com/usememos/memos/proto/gen/api/v1"
)
func (*APIV1Service) GetLinkMetadata(_ context.Context, request *v1pb.GetLinkMetadataRequest) (*v1pb.GetLinkMetadataResponse, error) {
htmlMeta, err := getter.GetHTMLMeta(request.Link)
if err != nil {
return nil, err
}
return &v1pb.GetLinkMetadataResponse{
LinkMetadata: &v1pb.LinkMetadata{
Title: htmlMeta.Title,
Description: htmlMeta.Description,
Image: htmlMeta.Image,
},
}, nil
}
This diff is collapsed.
...@@ -11,6 +11,8 @@ import ( ...@@ -11,6 +11,8 @@ import (
"github.com/google/cel-go/cel" "github.com/google/cel-go/cel"
"github.com/lithammer/shortuuid/v4" "github.com/lithammer/shortuuid/v4"
"github.com/pkg/errors" "github.com/pkg/errors"
"github.com/yourselfhosted/gomark/parser"
"github.com/yourselfhosted/gomark/parser/tokenizer"
expr "google.golang.org/genproto/googleapis/api/expr/v1alpha1" expr "google.golang.org/genproto/googleapis/api/expr/v1alpha1"
"google.golang.org/grpc/codes" "google.golang.org/grpc/codes"
"google.golang.org/grpc/status" "google.golang.org/grpc/status"
...@@ -543,6 +545,11 @@ func (s *APIV1Service) convertMemoFromStore(ctx context.Context, memo *store.Mem ...@@ -543,6 +545,11 @@ func (s *APIV1Service) convertMemoFromStore(ctx context.Context, memo *store.Mem
return nil, errors.Wrap(err, "failed to list memo reactions") return nil, errors.Wrap(err, "failed to list memo reactions")
} }
nodes, err := parser.Parse(tokenizer.Tokenize(memo.Content))
if err != nil {
return nil, errors.Wrap(err, "failed to parse content")
}
return &v1pb.Memo{ return &v1pb.Memo{
Name: name, Name: name,
Uid: memo.UID, Uid: memo.UID,
...@@ -552,6 +559,7 @@ func (s *APIV1Service) convertMemoFromStore(ctx context.Context, memo *store.Mem ...@@ -552,6 +559,7 @@ func (s *APIV1Service) convertMemoFromStore(ctx context.Context, memo *store.Mem
UpdateTime: timestamppb.New(time.Unix(memo.UpdatedTs, 0)), UpdateTime: timestamppb.New(time.Unix(memo.UpdatedTs, 0)),
DisplayTime: timestamppb.New(time.Unix(displayTs, 0)), DisplayTime: timestamppb.New(time.Unix(displayTs, 0)),
Content: memo.Content, Content: memo.Content,
Nodes: convertFromASTNodes(nodes),
Visibility: convertVisibilityFromStore(memo.Visibility), Visibility: convertVisibilityFromStore(memo.Visibility),
Pinned: memo.Pinned, Pinned: memo.Pinned,
ParentId: memo.ParentID, ParentId: memo.ParentID,
......
...@@ -27,7 +27,7 @@ type APIV1Service struct { ...@@ -27,7 +27,7 @@ type APIV1Service struct {
v1pb.UnimplementedInboxServiceServer v1pb.UnimplementedInboxServiceServer
v1pb.UnimplementedActivityServiceServer v1pb.UnimplementedActivityServiceServer
v1pb.UnimplementedWebhookServiceServer v1pb.UnimplementedWebhookServiceServer
v1pb.UnimplementedLinkServiceServer v1pb.UnimplementedMarkdownServiceServer
v1pb.UnimplementedIdentityProviderServiceServer v1pb.UnimplementedIdentityProviderServiceServer
Secret string Secret string
...@@ -55,7 +55,7 @@ func NewAPIV1Service(secret string, profile *profile.Profile, store *store.Store ...@@ -55,7 +55,7 @@ func NewAPIV1Service(secret string, profile *profile.Profile, store *store.Store
v1pb.RegisterInboxServiceServer(grpcServer, apiv1Service) v1pb.RegisterInboxServiceServer(grpcServer, apiv1Service)
v1pb.RegisterActivityServiceServer(grpcServer, apiv1Service) v1pb.RegisterActivityServiceServer(grpcServer, apiv1Service)
v1pb.RegisterWebhookServiceServer(grpcServer, apiv1Service) v1pb.RegisterWebhookServiceServer(grpcServer, apiv1Service)
v1pb.RegisterLinkServiceServer(grpcServer, apiv1Service) v1pb.RegisterMarkdownServiceServer(grpcServer, apiv1Service)
v1pb.RegisterIdentityProviderServiceServer(grpcServer, apiv1Service) v1pb.RegisterIdentityProviderServiceServer(grpcServer, apiv1Service)
reflection.Register(grpcServer) reflection.Register(grpcServer)
return apiv1Service return apiv1Service
...@@ -105,7 +105,7 @@ func (s *APIV1Service) RegisterGateway(ctx context.Context, echoServer *echo.Ech ...@@ -105,7 +105,7 @@ func (s *APIV1Service) RegisterGateway(ctx context.Context, echoServer *echo.Ech
if err := v1pb.RegisterWebhookServiceHandler(context.Background(), gwMux, conn); err != nil { if err := v1pb.RegisterWebhookServiceHandler(context.Background(), gwMux, conn); err != nil {
return err return err
} }
if err := v1pb.RegisterLinkServiceHandler(context.Background(), gwMux, conn); err != nil { if err := v1pb.RegisterMarkdownServiceHandler(context.Background(), gwMux, conn); err != nil {
return err return err
} }
if err := v1pb.RegisterIdentityProviderServiceHandler(context.Background(), gwMux, conn); err != nil { if err := v1pb.RegisterIdentityProviderServiceHandler(context.Background(), gwMux, conn); err != nil {
......
...@@ -27,7 +27,6 @@ import ( ...@@ -27,7 +27,6 @@ import (
) )
type Server struct { type Server struct {
ID string
Secret string Secret string
Profile *profile.Profile Profile *profile.Profile
Store *store.Store Store *store.Store
...@@ -60,7 +59,6 @@ func NewServer(ctx context.Context, profile *profile.Profile, store *store.Store ...@@ -60,7 +59,6 @@ func NewServer(ctx context.Context, profile *profile.Profile, store *store.Store
if profile.Mode == "prod" { if profile.Mode == "prod" {
secret = workspaceBasicSetting.SecretKey secret = workspaceBasicSetting.SecretKey
} }
s.ID = workspaceBasicSetting.ServerId
s.Secret = secret s.Secret = secret
// Register healthz endpoint. // Register healthz endpoint.
...@@ -133,12 +131,12 @@ func (s *Server) Shutdown(ctx context.Context) { ...@@ -133,12 +131,12 @@ func (s *Server) Shutdown(ctx context.Context) {
ctx, cancel := context.WithTimeout(ctx, 10*time.Second) ctx, cancel := context.WithTimeout(ctx, 10*time.Second)
defer cancel() defer cancel()
// Shutdown echo server // Shutdown echo server.
if err := s.echoServer.Shutdown(ctx); err != nil { if err := s.echoServer.Shutdown(ctx); err != nil {
fmt.Printf("failed to shutdown server, error: %v\n", err) fmt.Printf("failed to shutdown server, error: %v\n", err)
} }
// Close database connection // Close database connection.
if err := s.Store.Close(); err != nil { if err := s.Store.Close(); err != nil {
fmt.Printf("failed to close database, error: %v\n", err) fmt.Printf("failed to close database, error: %v\n", err)
} }
...@@ -156,10 +154,6 @@ func (s *Server) getOrUpsertWorkspaceBasicSetting(ctx context.Context) (*storepb ...@@ -156,10 +154,6 @@ func (s *Server) getOrUpsertWorkspaceBasicSetting(ctx context.Context) (*storepb
return nil, errors.Wrap(err, "failed to get workspace basic setting") return nil, errors.Wrap(err, "failed to get workspace basic setting")
} }
modified := false modified := false
if workspaceBasicSetting.ServerId == "" {
workspaceBasicSetting.ServerId = uuid.NewString()
modified = true
}
if workspaceBasicSetting.SecretKey == "" { if workspaceBasicSetting.SecretKey == "" {
workspaceBasicSetting.SecretKey = uuid.NewString() workspaceBasicSetting.SecretKey = uuid.NewString()
modified = true modified = true
......
This diff is collapsed.
import { Node } from "@/types/node"; import { Node } from "@/types/proto/api/v1/markdown_service";
import Renderer from "./Renderer"; import Renderer from "./Renderer";
import { BaseProps } from "./types"; import { BaseProps } from "./types";
......
import { Node } from "@/types/node"; import { Node } from "@/types/proto/api/v1/markdown_service";
import Renderer from "./Renderer"; import Renderer from "./Renderer";
interface Props { interface Props {
......
...@@ -44,7 +44,7 @@ const EmbeddedMemo = ({ resourceId, params: paramsStr }: Props) => { ...@@ -44,7 +44,7 @@ const EmbeddedMemo = ({ resourceId, params: paramsStr }: Props) => {
<MemoContent <MemoContent
key={`${memo.name}-${memo.updateTime}`} key={`${memo.name}-${memo.updateTime}`}
memoName={memo.name} memoName={memo.name}
content={memo.content} nodes={memo.nodes}
embeddedMemos={context.embeddedMemos} embeddedMemos={context.embeddedMemos}
/> />
<MemoResourceListView resources={memo.resources} /> <MemoResourceListView resources={memo.resources} />
...@@ -62,12 +62,7 @@ const EmbeddedMemo = ({ resourceId, params: paramsStr }: Props) => { ...@@ -62,12 +62,7 @@ const EmbeddedMemo = ({ resourceId, params: paramsStr }: Props) => {
<Icon.ArrowUpRight className="w-5 h-auto opacity-80 text-gray-400" /> <Icon.ArrowUpRight className="w-5 h-auto opacity-80 text-gray-400" />
</Link> </Link>
</div> </div>
<MemoContent <MemoContent key={`${memo.name}-${memo.updateTime}`} memoName={memo.name} nodes={memo.nodes} embeddedMemos={context.embeddedMemos} />
key={`${memo.name}-${memo.updateTime}`}
memoName={memo.name}
content={memo.content}
embeddedMemos={context.embeddedMemos}
/>
<MemoResourceListView resources={memo.resources} /> <MemoResourceListView resources={memo.resources} />
</div> </div>
); );
......
import { Node } from "@/types/node"; import { Node } from "@/types/proto/api/v1/markdown_service";
import Renderer from "./Renderer"; import Renderer from "./Renderer";
import { BaseProps } from "./types"; import { BaseProps } from "./types";
......
import { Link as MLink, Tooltip } from "@mui/joy"; import { Link as MLink, Tooltip } from "@mui/joy";
import { useState } from "react"; import { useState } from "react";
import { linkServiceClient } from "@/grpcweb"; import { markdownServiceClient } from "@/grpcweb";
import { LinkMetadata } from "@/types/proto/api/v1/link_service"; import { LinkMetadata } from "@/types/proto/api/v1/markdown_service";
interface Props { interface Props {
url: string; url: string;
...@@ -25,7 +25,7 @@ const Link: React.FC<Props> = ({ text, url }: Props) => { ...@@ -25,7 +25,7 @@ const Link: React.FC<Props> = ({ text, url }: Props) => {
const handleMouseEnter = async () => { const handleMouseEnter = async () => {
if (!initialized) { if (!initialized) {
try { try {
const { linkMetadata } = await linkServiceClient.getLinkMetadata({ link: url }, {}); const linkMetadata = await markdownServiceClient.getLinkMetadata({ link: url });
setLinkMetadata(linkMetadata); setLinkMetadata(linkMetadata);
} catch (error) { } catch (error) {
console.error("Error fetching URL metadata:", error); console.error("Error fetching URL metadata:", error);
......
import { repeat } from "lodash-es"; import { repeat } from "lodash-es";
import { Node } from "@/types/node"; import { Node } from "@/types/proto/api/v1/markdown_service";
import Renderer from "./Renderer"; import Renderer from "./Renderer";
import { BaseProps } from "./types"; import { BaseProps } from "./types";
......
import { Node } from "@/types/node"; import { Node } from "@/types/proto/api/v1/markdown_service";
import Renderer from "./Renderer"; import Renderer from "./Renderer";
import { BaseProps } from "./types"; import { BaseProps } from "./types";
......
...@@ -13,6 +13,7 @@ import { ...@@ -13,6 +13,7 @@ import {
ImageNode, ImageNode,
ItalicNode, ItalicNode,
LinkNode, LinkNode,
MathBlockNode,
MathNode, MathNode,
Node, Node,
NodeType, NodeType,
...@@ -28,7 +29,7 @@ import { ...@@ -28,7 +29,7 @@ import {
TaskListNode, TaskListNode,
TextNode, TextNode,
UnorderedListNode, UnorderedListNode,
} from "@/types/node"; } from "@/types/proto/api/v1/markdown_service";
import Blockquote from "./Blockquote"; import Blockquote from "./Blockquote";
import Bold from "./Bold"; import Bold from "./Bold";
import BoldItalic from "./BoldItalic"; import BoldItalic from "./BoldItalic";
...@@ -67,61 +68,61 @@ const Renderer: React.FC<Props> = ({ index, node }: Props) => { ...@@ -67,61 +68,61 @@ const Renderer: React.FC<Props> = ({ index, node }: Props) => {
case NodeType.LINE_BREAK: case NodeType.LINE_BREAK:
return <LineBreak index={index} />; return <LineBreak index={index} />;
case NodeType.PARAGRAPH: case NodeType.PARAGRAPH:
return <Paragraph index={index} {...(node.value as ParagraphNode)} />; return <Paragraph index={index} {...(node.paragraphNode as ParagraphNode)} />;
case NodeType.CODE_BLOCK: case NodeType.CODE_BLOCK:
return <CodeBlock index={index} {...(node.value as CodeBlockNode)} />; return <CodeBlock index={index} {...(node.codeBlockNode as CodeBlockNode)} />;
case NodeType.HEADING: case NodeType.HEADING:
return <Heading index={index} {...(node.value as HeadingNode)} />; return <Heading index={index} {...(node.headingNode as HeadingNode)} />;
case NodeType.HORIZONTAL_RULE: case NodeType.HORIZONTAL_RULE:
return <HorizontalRule index={index} {...(node.value as HorizontalRuleNode)} />; return <HorizontalRule index={index} {...(node.horizontalRuleNode as HorizontalRuleNode)} />;
case NodeType.BLOCKQUOTE: case NodeType.BLOCKQUOTE:
return <Blockquote index={index} {...(node.value as BlockquoteNode)} />; return <Blockquote index={index} {...(node.blockquoteNode as BlockquoteNode)} />;
case NodeType.ORDERED_LIST: case NodeType.ORDERED_LIST:
return <OrderedList index={index} {...(node.value as OrderedListNode)} />; return <OrderedList index={index} {...(node.orderedListNode as OrderedListNode)} />;
case NodeType.UNORDERED_LIST: case NodeType.UNORDERED_LIST:
return <UnorderedList {...(node.value as UnorderedListNode)} />; return <UnorderedList {...(node.unorderedListNode as UnorderedListNode)} />;
case NodeType.TASK_LIST: case NodeType.TASK_LIST:
return <TaskList index={index} {...(node.value as TaskListNode)} />; return <TaskList index={index} {...(node.taskListNode as TaskListNode)} />;
case NodeType.MATH_BLOCK: case NodeType.MATH_BLOCK:
return <Math {...(node.value as MathNode)} block={true} />; return <Math {...(node.mathBlockNode as MathBlockNode)} block={true} />;
case NodeType.TABLE: case NodeType.TABLE:
return <Table {...(node.value as TableNode)} />; return <Table {...(node.tableNode as TableNode)} />;
case NodeType.EMBEDDED_CONTENT: case NodeType.EMBEDDED_CONTENT:
return <EmbeddedContent {...(node.value as EmbeddedContentNode)} />; return <EmbeddedContent {...(node.embeddedContentNode as EmbeddedContentNode)} />;
case NodeType.TEXT: case NodeType.TEXT:
return <Text {...(node.value as TextNode)} />; return <Text {...(node.textNode as TextNode)} />;
case NodeType.BOLD: case NodeType.BOLD:
return <Bold {...(node.value as BoldNode)} />; return <Bold {...(node.boldNode as BoldNode)} />;
case NodeType.ITALIC: case NodeType.ITALIC:
return <Italic {...(node.value as ItalicNode)} />; return <Italic {...(node.italicNode as ItalicNode)} />;
case NodeType.BOLD_ITALIC: case NodeType.BOLD_ITALIC:
return <BoldItalic {...(node.value as BoldItalicNode)} />; return <BoldItalic {...(node.boldItalicNode as BoldItalicNode)} />;
case NodeType.CODE: case NodeType.CODE:
return <Code {...(node.value as CodeNode)} />; return <Code {...(node.codeNode as CodeNode)} />;
case NodeType.IMAGE: case NodeType.IMAGE:
return <Image {...(node.value as ImageNode)} />; return <Image {...(node.imageNode as ImageNode)} />;
case NodeType.LINK: case NodeType.LINK:
return <Link {...(node.value as LinkNode)} />; return <Link {...(node.linkNode as LinkNode)} />;
case NodeType.AUTO_LINK: case NodeType.AUTO_LINK:
return <Link {...(node.value as AutoLinkNode)} />; return <Link {...(node.autoLinkNode as AutoLinkNode)} />;
case NodeType.TAG: case NodeType.TAG:
return <Tag {...(node.value as TagNode)} />; return <Tag {...(node.tagNode as TagNode)} />;
case NodeType.STRIKETHROUGH: case NodeType.STRIKETHROUGH:
return <Strikethrough {...(node.value as StrikethroughNode)} />; return <Strikethrough {...(node.strikethroughNode as StrikethroughNode)} />;
case NodeType.MATH: case NodeType.MATH:
return <Math {...(node.value as MathNode)} />; return <Math {...(node.mathNode as MathNode)} />;
case NodeType.HIGHLIGHT: case NodeType.HIGHLIGHT:
return <Highlight {...(node.value as HighlightNode)} />; return <Highlight {...(node.highlightNode as HighlightNode)} />;
case NodeType.ESCAPING_CHARACTER: case NodeType.ESCAPING_CHARACTER:
return <EscapingCharacter {...(node.value as EscapingCharacterNode)} />; return <EscapingCharacter {...(node.escapingCharacterNode as EscapingCharacterNode)} />;
case NodeType.SUBSCRIPT: case NodeType.SUBSCRIPT:
return <Subscript {...(node.value as SubscriptNode)} />; return <Subscript {...(node.subscriptNode as SubscriptNode)} />;
case NodeType.SUPERSCRIPT: case NodeType.SUPERSCRIPT:
return <Superscript {...(node.value as SuperscriptNode)} />; return <Superscript {...(node.superscriptNode as SuperscriptNode)} />;
case NodeType.REFERENCED_CONTENT: case NodeType.REFERENCED_CONTENT:
return <ReferencedContent {...(node.value as ReferencedContentNode)} />; return <ReferencedContent {...(node.referencedContentNode as ReferencedContentNode)} />;
case NodeType.SPOILER: case NodeType.SPOILER:
return <Spoiler {...(node.value as SpoilerNode)} />; return <Spoiler {...(node.spoilerNode as SpoilerNode)} />;
default: default:
return null; return null;
} }
......
import { TableNode_Row } from "@/types/node"; import { TableNode_Row } from "@/types/proto/api/v1/markdown_service";
interface Props { interface Props {
header: string[]; header: string[];
......
...@@ -2,8 +2,9 @@ import { Checkbox } from "@mui/joy"; ...@@ -2,8 +2,9 @@ import { Checkbox } from "@mui/joy";
import clsx from "clsx"; import clsx from "clsx";
import { repeat } from "lodash-es"; import { repeat } from "lodash-es";
import { useContext, useState } from "react"; import { useContext, useState } from "react";
import { markdownServiceClient } from "@/grpcweb";
import { useMemoStore } from "@/store/v1"; import { useMemoStore } from "@/store/v1";
import { Node, NodeType, TaskListNode } from "@/types/node"; import { Node, NodeType, TaskListNode } from "@/types/proto/api/v1/markdown_service";
import Renderer from "./Renderer"; import Renderer from "./Renderer";
import { RendererContext } from "./types"; import { RendererContext } from "./types";
...@@ -31,16 +32,16 @@ const TaskList: React.FC<Props> = ({ index, indent, complete, children }: Props) ...@@ -31,16 +32,16 @@ const TaskList: React.FC<Props> = ({ index, indent, complete, children }: Props)
} }
const node = context.nodes[nodeIndex]; const node = context.nodes[nodeIndex];
if (node.type !== NodeType.TASK_LIST || !node.value) { if (node.type !== NodeType.TASK_LIST) {
return; return;
} }
(node.value as TaskListNode)!.complete = on; (node.taskListNode as TaskListNode)!.complete = on;
const content = window.restore(context.nodes); const { markdown } = await markdownServiceClient.restoreMarkdown({ nodes: context.nodes });
await memoStore.updateMemo( await memoStore.updateMemo(
{ {
name: context.memoName, name: context.memoName,
content, content: markdown,
}, },
["content"], ["content"],
); );
......
import { repeat } from "lodash-es"; import { repeat } from "lodash-es";
import { Node } from "@/types/node"; import { Node } from "@/types/proto/api/v1/markdown_service";
import Renderer from "./Renderer"; import Renderer from "./Renderer";
interface Props { interface Props {
......
...@@ -2,7 +2,7 @@ import clsx from "clsx"; ...@@ -2,7 +2,7 @@ import clsx from "clsx";
import { memo, useEffect, useRef, useState } from "react"; import { memo, useEffect, useRef, useState } from "react";
import useCurrentUser from "@/hooks/useCurrentUser"; import useCurrentUser from "@/hooks/useCurrentUser";
import { useMemoStore } from "@/store/v1"; import { useMemoStore } from "@/store/v1";
import { Node, NodeType } from "@/types/node"; import { Node, NodeType } from "@/types/proto/api/v1/markdown_service";
import { useTranslate } from "@/utils/i18n"; import { useTranslate } from "@/utils/i18n";
import Renderer from "./Renderer"; import Renderer from "./Renderer";
import { RendererContext } from "./types"; import { RendererContext } from "./types";
...@@ -11,7 +11,7 @@ import { RendererContext } from "./types"; ...@@ -11,7 +11,7 @@ import { RendererContext } from "./types";
const MAX_DISPLAY_HEIGHT = 256; const MAX_DISPLAY_HEIGHT = 256;
interface Props { interface Props {
content: string; nodes: Node[];
memoName?: string; memoName?: string;
compact?: boolean; compact?: boolean;
readonly?: boolean; readonly?: boolean;
...@@ -24,14 +24,13 @@ interface Props { ...@@ -24,14 +24,13 @@ interface Props {
} }
const MemoContent: React.FC<Props> = (props: Props) => { const MemoContent: React.FC<Props> = (props: Props) => {
const { className, content, memoName, embeddedMemos, onClick } = props; const { className, nodes, memoName, embeddedMemos, onClick } = props;
const t = useTranslate(); const t = useTranslate();
const currentUser = useCurrentUser(); const currentUser = useCurrentUser();
const memoStore = useMemoStore(); const memoStore = useMemoStore();
const memoContentContainerRef = useRef<HTMLDivElement>(null); const memoContentContainerRef = useRef<HTMLDivElement>(null);
const [showCompactMode, setShowCompactMode] = useState<boolean>(false); const [showCompactMode, setShowCompactMode] = useState<boolean>(false);
const memo = memoName ? memoStore.getMemoByName(memoName) : null; const memo = memoName ? memoStore.getMemoByName(memoName) : null;
const nodes = window.parse(content);
const allowEdit = !props.readonly && memo && currentUser?.name === memo.creator; const allowEdit = !props.readonly && memo && currentUser?.name === memo.creator;
// Initial compact mode. // Initial compact mode.
......
import { createContext } from "react"; import { createContext } from "react";
import { Node } from "@/types/node"; import { Node } from "@/types/proto/api/v1/markdown_service";
interface Context { interface Context {
nodes: Node[]; nodes: Node[];
......
import clsx from "clsx"; import clsx from "clsx";
import { last } from "lodash-es"; import { last } from "lodash-es";
import { forwardRef, ReactNode, useCallback, useEffect, useImperativeHandle, useRef, useState } from "react"; import { forwardRef, ReactNode, useCallback, useEffect, useImperativeHandle, useRef, useState } from "react";
import { NodeType, OrderedListNode, TaskListNode, UnorderedListNode } from "@/types/node"; import { markdownServiceClient } from "@/grpcweb";
import { NodeType, OrderedListNode, TaskListNode, UnorderedListNode } from "@/types/proto/api/v1/markdown_service";
import TagSuggestions from "./TagSuggestions"; import TagSuggestions from "./TagSuggestions";
export interface EditorRefActions { export interface EditorRefActions {
...@@ -149,24 +150,29 @@ const Editor = forwardRef(function Editor(props: Props, ref: React.ForwardedRef< ...@@ -149,24 +150,29 @@ const Editor = forwardRef(function Editor(props: Props, ref: React.ForwardedRef<
updateEditorHeight(); updateEditorHeight();
}, []); }, []);
const handleEditorKeyDown = (event: React.KeyboardEvent<HTMLTextAreaElement>) => { const handleEditorKeyDown = async (event: React.KeyboardEvent<HTMLTextAreaElement>) => {
if (event.key === "Enter" && !isInIME) { if (event.key === "Enter" && !isInIME) {
if (event.shiftKey || event.ctrlKey || event.metaKey || event.altKey) {
return;
}
const cursorPosition = editorActions.getCursorPosition(); const cursorPosition = editorActions.getCursorPosition();
const prevContent = editorActions.getContent().substring(0, cursorPosition); const prevContent = editorActions.getContent().substring(0, cursorPosition);
const lastNode = last(window.parse(prevContent)); const { nodes } = await markdownServiceClient.parseMarkdown({ markdown: prevContent });
const lastNode = last(nodes);
if (!lastNode) { if (!lastNode) {
return; return;
} }
let insertText = ""; let insertText = "";
if (lastNode.type === NodeType.TASK_LIST) { if (lastNode.type === NodeType.TASK_LIST) {
const { complete } = lastNode.value as TaskListNode; const { complete } = lastNode.taskListNode as TaskListNode;
insertText = complete ? "- [x] " : "- [ ] "; insertText = complete ? "- [x] " : "- [ ] ";
} else if (lastNode.type === NodeType.UNORDERED_LIST) { } else if (lastNode.type === NodeType.UNORDERED_LIST) {
const { symbol } = lastNode.value as UnorderedListNode; const { symbol } = lastNode.unorderedListNode as UnorderedListNode;
insertText = `${symbol} `; insertText = `${symbol} `;
} else if (lastNode.type === NodeType.ORDERED_LIST) { } else if (lastNode.type === NodeType.ORDERED_LIST) {
const { number } = lastNode.value as OrderedListNode; const { number } = lastNode.orderedListNode as OrderedListNode;
insertText = `${Number(number) + 1}. `; insertText = `${Number(number) + 1}. `;
} }
if (insertText) { if (insertText) {
......
...@@ -364,7 +364,7 @@ const MemoEditor = (props: Props) => { ...@@ -364,7 +364,7 @@ const MemoEditor = (props: Props) => {
} }
// Batch upsert tags. // Batch upsert tags.
const tags = extractTagsFromContent(content); const tags = await extractTagsFromContent(content);
await tagStore.batchUpsertTag(tags); await tagStore.batchUpsertTag(tags);
setState((state) => { setState((state) => {
......
...@@ -148,7 +148,7 @@ const MemoView: React.FC<Props> = (props: Props) => { ...@@ -148,7 +148,7 @@ const MemoView: React.FC<Props> = (props: Props) => {
<MemoContent <MemoContent
key={`${memo.name}-${memo.updateTime}`} key={`${memo.name}-${memo.updateTime}`}
memoName={memo.name} memoName={memo.name}
content={memo.content} nodes={memo.nodes}
readonly={readonly} readonly={readonly}
onClick={handleMemoContentClick} onClick={handleMemoContentClick}
compact={props.compact ?? true} compact={props.compact ?? true}
......
import { IconButton } from "@mui/joy"; import { IconButton } from "@mui/joy";
import { useEffect, useState } from "react";
import { markdownServiceClient } from "@/grpcweb";
import { Node } from "@/types/proto/api/v1/markdown_service";
import { generateDialog } from "./Dialog"; import { generateDialog } from "./Dialog";
import Icon from "./Icon"; import Icon from "./Icon";
import MemoContent from "./MemoContent"; import MemoContent from "./MemoContent";
...@@ -8,6 +11,19 @@ interface Props extends DialogProps { ...@@ -8,6 +11,19 @@ interface Props extends DialogProps {
} }
const PreviewMarkdownDialog: React.FC<Props> = ({ content, destroy }: Props) => { const PreviewMarkdownDialog: React.FC<Props> = ({ content, destroy }: Props) => {
const [nodes, setNodes] = useState<Node[]>([]);
useEffect(() => {
(async () => {
try {
const { nodes } = await markdownServiceClient.parseMarkdown({ markdown: content });
setNodes(nodes);
} catch (error) {
console.error("Error parsing markdown:", error);
}
})();
}, [content]);
const handleCloseBtnClick = () => { const handleCloseBtnClick = () => {
destroy(); destroy();
}; };
...@@ -23,7 +39,7 @@ const PreviewMarkdownDialog: React.FC<Props> = ({ content, destroy }: Props) => ...@@ -23,7 +39,7 @@ const PreviewMarkdownDialog: React.FC<Props> = ({ content, destroy }: Props) =>
</IconButton> </IconButton>
</div> </div>
<div className="flex flex-col justify-start items-start max-w-full w-[32rem]"> <div className="flex flex-col justify-start items-start max-w-full w-[32rem]">
{content !== "" ? <MemoContent content={content} /> : <p className="text-gray-400 dark:text-gray-600">Nothing to preview</p>} {nodes.length > 0 ? <MemoContent nodes={nodes} /> : <p className="text-gray-400 dark:text-gray-600">Nothing to preview</p>}
</div> </div>
</> </>
); );
......
...@@ -155,7 +155,7 @@ const ShareMemoDialog: React.FC<Props> = (props: Props) => { ...@@ -155,7 +155,7 @@ const ShareMemoDialog: React.FC<Props> = (props: Props) => {
> >
<span className="w-full px-6 pt-5 pb-2 text-sm text-gray-500">{getDateTimeString(memo.displayTime)}</span> <span className="w-full px-6 pt-5 pb-2 text-sm text-gray-500">{getDateTimeString(memo.displayTime)}</span>
<div className="w-full px-6 text-base pb-4 space-y-2"> <div className="w-full px-6 text-base pb-4 space-y-2">
<MemoContent memoName={memo.name} content={memo.content} readonly={true} disableFilter /> <MemoContent memoName={memo.name} nodes={memo.nodes} readonly={true} disableFilter />
<MemoResourceListView resources={memo.resources} /> <MemoResourceListView resources={memo.resources} />
</div> </div>
<div className="flex flex-row justify-between items-center w-full bg-gray-100 dark:bg-zinc-900 py-4 px-6"> <div className="flex flex-row justify-between items-center w-full bg-gray-100 dark:bg-zinc-900 py-4 px-6">
......
...@@ -3,7 +3,7 @@ import { ActivityServiceDefinition } from "./types/proto/api/v1/activity_service ...@@ -3,7 +3,7 @@ import { ActivityServiceDefinition } from "./types/proto/api/v1/activity_service
import { AuthServiceDefinition } from "./types/proto/api/v1/auth_service"; import { AuthServiceDefinition } from "./types/proto/api/v1/auth_service";
import { IdentityProviderServiceDefinition } from "./types/proto/api/v1/idp_service"; import { IdentityProviderServiceDefinition } from "./types/proto/api/v1/idp_service";
import { InboxServiceDefinition } from "./types/proto/api/v1/inbox_service"; import { InboxServiceDefinition } from "./types/proto/api/v1/inbox_service";
import { LinkServiceDefinition } from "./types/proto/api/v1/link_service"; import { MarkdownServiceDefinition } from "./types/proto/api/v1/markdown_service";
import { MemoServiceDefinition } from "./types/proto/api/v1/memo_service"; import { MemoServiceDefinition } from "./types/proto/api/v1/memo_service";
import { ResourceServiceDefinition } from "./types/proto/api/v1/resource_service"; import { ResourceServiceDefinition } from "./types/proto/api/v1/resource_service";
import { TagServiceDefinition } from "./types/proto/api/v1/tag_service"; import { TagServiceDefinition } from "./types/proto/api/v1/tag_service";
...@@ -41,6 +41,6 @@ export const activityServiceClient = clientFactory.create(ActivityServiceDefinit ...@@ -41,6 +41,6 @@ export const activityServiceClient = clientFactory.create(ActivityServiceDefinit
export const webhookServiceClient = clientFactory.create(WebhookServiceDefinition, channel); export const webhookServiceClient = clientFactory.create(WebhookServiceDefinition, channel);
export const linkServiceClient = clientFactory.create(LinkServiceDefinition, channel); export const markdownServiceClient = clientFactory.create(MarkdownServiceDefinition, channel);
export const identityProviderServiceClient = clientFactory.create(IdentityProviderServiceDefinition, channel); export const identityProviderServiceClient = clientFactory.create(IdentityProviderServiceDefinition, channel);
...@@ -4,8 +4,6 @@ import { createRoot } from "react-dom/client"; ...@@ -4,8 +4,6 @@ import { createRoot } from "react-dom/client";
import { Toaster } from "react-hot-toast"; import { Toaster } from "react-hot-toast";
import { Provider } from "react-redux"; import { Provider } from "react-redux";
import { RouterProvider } from "react-router-dom"; import { RouterProvider } from "react-router-dom";
import gomarkWasm from "./assets/gomark.wasm?url";
import "./assets/wasm_exec.js";
import "./css/global.css"; import "./css/global.css";
import "./css/tailwind.css"; import "./css/tailwind.css";
import "./helpers/polyfill"; import "./helpers/polyfill";
...@@ -17,10 +15,6 @@ import store from "./store"; ...@@ -17,10 +15,6 @@ import store from "./store";
import theme from "./theme"; import theme from "./theme";
(async () => { (async () => {
const go = new window.Go();
const { instance } = await WebAssembly.instantiateStreaming(fetch(gomarkWasm), go.importObject);
go.run(instance);
const container = document.getElementById("root"); const container = document.getElementById("root");
const root = createRoot(container as HTMLElement); const root = createRoot(container as HTMLElement);
root.render( root.render(
......
...@@ -121,7 +121,7 @@ const Archived = () => { ...@@ -121,7 +121,7 @@ const Archived = () => {
</Tooltip> </Tooltip>
</div> </div>
</div> </div>
<MemoContent key={`${memo.name}-${memo.displayTime}`} memoName={memo.name} content={memo.content} readonly={true} /> <MemoContent key={`${memo.name}-${memo.displayTime}`} memoName={memo.name} nodes={memo.nodes} readonly={true} />
</div> </div>
))} ))}
{isRequesting ? ( {isRequesting ? (
......
import { Node } from "./node";
declare class Go {
argv: string[];
env: { [envKey: string]: string };
exit: (code: number) => void;
importObject: WebAssembly.Imports;
exited: boolean;
mem: DataView;
run(instance: WebAssembly.Instance): Promise<void>;
}
declare global {
interface Window {
Go: typeof Go;
parse: (content: string) => Node[];
restore: (input: Node[]) => string;
}
}
export {};
export enum NodeType {
LINE_BREAK = "LINE_BREAK",
PARAGRAPH = "PARAGRAPH",
CODE_BLOCK = "CODE_BLOCK",
HEADING = "HEADING",
HORIZONTAL_RULE = "HORIZONTAL_RULE",
BLOCKQUOTE = "BLOCKQUOTE",
ORDERED_LIST = "ORDERED_LIST",
UNORDERED_LIST = "UNORDERED_LIST",
TASK_LIST = "TASK_LIST",
MATH_BLOCK = "MATH_BLOCK",
TABLE = "TABLE",
EMBEDDED_CONTENT = "EMBEDDED_CONTENT",
TEXT = "TEXT",
BOLD = "BOLD",
ITALIC = "ITALIC",
BOLD_ITALIC = "BOLD_ITALIC",
CODE = "CODE",
IMAGE = "IMAGE",
LINK = "LINK",
AUTO_LINK = "AUTO_LINK",
TAG = "TAG",
STRIKETHROUGH = "STRIKETHROUGH",
ESCAPING_CHARACTER = "ESCAPING_CHARACTER",
MATH = "MATH",
HIGHLIGHT = "HIGHLIGHT",
SUBSCRIPT = "SUBSCRIPT",
SUPERSCRIPT = "SUPERSCRIPT",
REFERENCED_CONTENT = "REFERENCED_CONTENT",
SPOILER = "SPOILER",
}
export interface Node {
type: NodeType;
value:
| LineBreakNode
| ParagraphNode
| CodeBlockNode
| HeadingNode
| HorizontalRuleNode
| BlockquoteNode
| OrderedListNode
| UnorderedListNode
| TaskListNode
| MathBlockNode
| TableNode
| EmbeddedContentNode
| TextNode
| BoldNode
| ItalicNode
| BoldItalicNode
| CodeNode
| ImageNode
| LinkNode
| AutoLinkNode
| TagNode
| StrikethroughNode
| EscapingCharacterNode
| MathNode
| HighlightNode
| SubscriptNode
| SuperscriptNode
| ReferencedContentNode
| SpoilerNode;
}
export interface LineBreakNode {}
export interface ParagraphNode {
children: Node[];
}
export interface CodeBlockNode {
language: string;
content: string;
}
export interface HeadingNode {
level: number;
children: Node[];
}
export interface HorizontalRuleNode {
symbol: string;
}
export interface BlockquoteNode {
children: Node[];
}
export interface OrderedListNode {
number: string;
indent: number;
children: Node[];
}
export interface UnorderedListNode {
symbol: string;
indent: number;
children: Node[];
}
export interface TaskListNode {
symbol: string;
indent: number;
complete: boolean;
children: Node[];
}
export interface MathBlockNode {
content: string;
}
export interface TableNode {
header: string[];
delimiter: string[];
rows: TableNode_Row[];
}
export interface TableNode_Row {
cells: string[];
}
export interface EmbeddedContentNode {
resourceName: string;
params: string;
}
export interface TextNode {
content: string;
}
export interface BoldNode {
symbol: string;
children: Node[];
}
export interface ItalicNode {
symbol: string;
content: string;
}
export interface BoldItalicNode {
symbol: string;
content: string;
}
export interface CodeNode {
content: string;
}
export interface ImageNode {
altText: string;
url: string;
}
export interface LinkNode {
text: string;
url: string;
}
export interface AutoLinkNode {
url: string;
isRawText: boolean;
}
export interface TagNode {
content: string;
}
export interface StrikethroughNode {
content: string;
}
export interface EscapingCharacterNode {
symbol: string;
}
export interface MathNode {
content: string;
}
export interface HighlightNode {
content: string;
}
export interface SubscriptNode {
content: string;
}
export interface SuperscriptNode {
content: string;
}
export interface ReferencedContentNode {
resourceName: string;
params: string;
}
export interface SpoilerNode {
content: string;
}
import { Node, TagNode } from "@/types/node"; import { markdownServiceClient } from "@/grpcweb";
import { Node, NodeType, TagNode } from "@/types/proto/api/v1/markdown_service";
export const TAG_REG = /#([^\s#,]+)/; export const TAG_REG = /#([^\s#,]+)/;
// extractTagsFromContent extracts tags from content. // extractTagsFromContent extracts tags from content.
export const extractTagsFromContent = (content: string) => { export const extractTagsFromContent = async (content: string) => {
const nodes = window.parse(content); const { nodes } = await markdownServiceClient.parseMarkdown({ markdown: content });
const tags = new Set<string>(); const tags = new Set<string>();
const traverse = (nodes: Node[], handle: (node: Node) => void) => { const traverse = (nodes: Node[], handle: (node: Node) => void) => {
...@@ -15,7 +16,7 @@ export const extractTagsFromContent = (content: string) => { ...@@ -15,7 +16,7 @@ export const extractTagsFromContent = (content: string) => {
handle(node); handle(node);
if (node.type === "PARAGRAPH" || node.type === "ORDERED_LIST" || node.type === "UNORDERED_LIST") { if (node.type === "PARAGRAPH" || node.type === "ORDERED_LIST" || node.type === "UNORDERED_LIST") {
const children = (node.value as any).children; const children = node.paragraphNode?.children || node.orderedListNode?.children || node.unorderedListNode?.children;
if (Array.isArray(children)) { if (Array.isArray(children)) {
traverse(children, handle); traverse(children, handle);
} }
...@@ -24,8 +25,8 @@ export const extractTagsFromContent = (content: string) => { ...@@ -24,8 +25,8 @@ export const extractTagsFromContent = (content: string) => {
}; };
traverse(nodes, (node) => { traverse(nodes, (node) => {
if (node.type === "TAG" && node.value) { if (node.type === NodeType.TAG && node.tagNode) {
tags.add((node.value as TagNode).content); tags.add((node.tagNode as TagNode).content);
} }
}); });
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment