diff --git a/go.mod b/go.mod index 25b3e0dbe..ca6273c55 100644 --- a/go.mod +++ b/go.mod @@ -23,7 +23,7 @@ require ( github.com/spf13/cobra v1.10.1 github.com/spf13/viper v1.20.1 github.com/stretchr/testify v1.10.0 - github.com/usememos/gomark v0.0.0-20250917125604-82623ecaf218 + github.com/usememos/gomark v0.0.0-20250925160223-606d7debad77 golang.org/x/crypto v0.41.0 golang.org/x/mod v0.27.0 golang.org/x/net v0.43.0 diff --git a/go.sum b/go.sum index b831664f6..2b0059054 100644 --- a/go.sum +++ b/go.sum @@ -433,8 +433,8 @@ github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVM github.com/ugorji/go/codec v1.1.7/go.mod h1:Ax+UKWsSmolVDwsd+7N3ZtXu+yMGCf907BLYF3GoBXY= github.com/urfave/cli v1.20.0/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA= github.com/urfave/cli v1.22.1/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0= -github.com/usememos/gomark v0.0.0-20250917125604-82623ecaf218 h1:rkH9CKI0AzWOIkwzZOs0PYepWt9fJTZZ9c5W1lL/bMQ= -github.com/usememos/gomark v0.0.0-20250917125604-82623ecaf218/go.mod h1:7CZRoYFQyyljzplOTeyODFR26O+wr0BbnpTWVLGfKJA= +github.com/usememos/gomark v0.0.0-20250925160223-606d7debad77 h1:eDJ/NUqDIvQbyXwqiKaUC3Y4lvbauSME+KLmpaxRytk= +github.com/usememos/gomark v0.0.0-20250925160223-606d7debad77/go.mod h1:7CZRoYFQyyljzplOTeyODFR26O+wr0BbnpTWVLGfKJA= github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw= github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= github.com/valyala/fasttemplate v1.2.2 h1:lxLXG0uE3Qnshl9QyaK6XJxMXlQZELvChBOCmQD0Loo= diff --git a/proto/api/v1/markdown_service.proto b/proto/api/v1/markdown_service.proto index 8ac0e8e89..8fc7fd526 100644 --- a/proto/api/v1/markdown_service.proto +++ b/proto/api/v1/markdown_service.proto @@ -326,4 +326,6 @@ message SpoilerNode { message HTMLElementNode { string tag_name = 1; map attributes = 2; + repeated Node children = 3; + bool is_self_closing = 4; } diff --git a/proto/gen/api/v1/activity_service.pb.go b/proto/gen/api/v1/activity_service.pb.go index 13635d355..2dcb8ac65 100644 --- a/proto/gen/api/v1/activity_service.pb.go +++ b/proto/gen/api/v1/activity_service.pb.go @@ -1,6 +1,6 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.36.8 +// protoc-gen-go v1.36.9 // protoc (unknown) // source: api/v1/activity_service.proto diff --git a/proto/gen/api/v1/attachment_service.pb.go b/proto/gen/api/v1/attachment_service.pb.go index a551d5e5f..16b6d9931 100644 --- a/proto/gen/api/v1/attachment_service.pb.go +++ b/proto/gen/api/v1/attachment_service.pb.go @@ -1,6 +1,6 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.36.8 +// protoc-gen-go v1.36.9 // protoc (unknown) // source: api/v1/attachment_service.proto diff --git a/proto/gen/api/v1/auth_service.pb.go b/proto/gen/api/v1/auth_service.pb.go index ebd86c793..c55e666a6 100644 --- a/proto/gen/api/v1/auth_service.pb.go +++ b/proto/gen/api/v1/auth_service.pb.go @@ -1,6 +1,6 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.36.8 +// protoc-gen-go v1.36.9 // protoc (unknown) // source: api/v1/auth_service.proto diff --git a/proto/gen/api/v1/common.pb.go b/proto/gen/api/v1/common.pb.go index 05b7722af..76ea1f953 100644 --- a/proto/gen/api/v1/common.pb.go +++ b/proto/gen/api/v1/common.pb.go @@ -1,6 +1,6 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.36.8 +// protoc-gen-go v1.36.9 // protoc (unknown) // source: api/v1/common.proto diff --git a/proto/gen/api/v1/idp_service.pb.go b/proto/gen/api/v1/idp_service.pb.go index 818430182..a4f0588fc 100644 --- a/proto/gen/api/v1/idp_service.pb.go +++ b/proto/gen/api/v1/idp_service.pb.go @@ -1,6 +1,6 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.36.8 +// protoc-gen-go v1.36.9 // protoc (unknown) // source: api/v1/idp_service.proto diff --git a/proto/gen/api/v1/inbox_service.pb.go b/proto/gen/api/v1/inbox_service.pb.go index cb7ebf161..d43ea64ef 100644 --- a/proto/gen/api/v1/inbox_service.pb.go +++ b/proto/gen/api/v1/inbox_service.pb.go @@ -1,6 +1,6 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.36.8 +// protoc-gen-go v1.36.9 // protoc (unknown) // source: api/v1/inbox_service.proto diff --git a/proto/gen/api/v1/markdown_service.pb.go b/proto/gen/api/v1/markdown_service.pb.go index 392ae7afc..d74b54633 100644 --- a/proto/gen/api/v1/markdown_service.pb.go +++ b/proto/gen/api/v1/markdown_service.pb.go @@ -1,6 +1,6 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.36.8 +// protoc-gen-go v1.36.9 // protoc (unknown) // source: api/v1/markdown_service.proto @@ -2634,6 +2634,8 @@ type HTMLElementNode struct { state protoimpl.MessageState `protogen:"open.v1"` TagName string `protobuf:"bytes,1,opt,name=tag_name,json=tagName,proto3" json:"tag_name,omitempty"` Attributes map[string]string `protobuf:"bytes,2,rep,name=attributes,proto3" json:"attributes,omitempty" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"bytes,2,opt,name=value"` + Children []*Node `protobuf:"bytes,3,rep,name=children,proto3" json:"children,omitempty"` + IsSelfClosing bool `protobuf:"varint,4,opt,name=is_self_closing,json=isSelfClosing,proto3" json:"is_self_closing,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } @@ -2682,6 +2684,20 @@ func (x *HTMLElementNode) GetAttributes() map[string]string { return nil } +func (x *HTMLElementNode) GetChildren() []*Node { + if x != nil { + return x.Children + } + return nil +} + +func (x *HTMLElementNode) GetIsSelfClosing() bool { + if x != nil { + return x.IsSelfClosing + } + return false +} + type TableNode_Row struct { state protoimpl.MessageState `protogen:"open.v1"` Cells []*Node `protobuf:"bytes,1,rep,name=cells,proto3" json:"cells,omitempty"` @@ -2874,12 +2890,14 @@ const file_api_v1_markdown_service_proto_rawDesc = "" + "\rresource_name\x18\x01 \x01(\tR\fresourceName\x12\x16\n" + "\x06params\x18\x02 \x01(\tR\x06params\"'\n" + "\vSpoilerNode\x12\x18\n" + - "\acontent\x18\x01 \x01(\tR\acontent\"\xba\x01\n" + + "\acontent\x18\x01 \x01(\tR\acontent\"\x92\x02\n" + "\x0fHTMLElementNode\x12\x19\n" + "\btag_name\x18\x01 \x01(\tR\atagName\x12M\n" + "\n" + "attributes\x18\x02 \x03(\v2-.memos.api.v1.HTMLElementNode.AttributesEntryR\n" + - "attributes\x1a=\n" + + "attributes\x12.\n" + + "\bchildren\x18\x03 \x03(\v2\x12.memos.api.v1.NodeR\bchildren\x12&\n" + + "\x0fis_self_closing\x18\x04 \x01(\bR\risSelfClosing\x1a=\n" + "\x0fAttributesEntry\x12\x10\n" + "\x03key\x18\x01 \x01(\tR\x03key\x12\x14\n" + "\x05value\x18\x02 \x01(\tR\x05value:\x028\x01*\x83\x04\n" + @@ -3039,20 +3057,21 @@ var file_api_v1_markdown_service_proto_depIdxs = []int32{ 10, // 46: memos.api.v1.ItalicNode.children:type_name -> memos.api.v1.Node 10, // 47: memos.api.v1.LinkNode.content:type_name -> memos.api.v1.Node 43, // 48: memos.api.v1.HTMLElementNode.attributes:type_name -> memos.api.v1.HTMLElementNode.AttributesEntry - 10, // 49: memos.api.v1.TableNode.Row.cells:type_name -> memos.api.v1.Node - 2, // 50: memos.api.v1.MarkdownService.ParseMarkdown:input_type -> memos.api.v1.ParseMarkdownRequest - 4, // 51: memos.api.v1.MarkdownService.RestoreMarkdownNodes:input_type -> memos.api.v1.RestoreMarkdownNodesRequest - 6, // 52: memos.api.v1.MarkdownService.StringifyMarkdownNodes:input_type -> memos.api.v1.StringifyMarkdownNodesRequest - 8, // 53: memos.api.v1.MarkdownService.GetLinkMetadata:input_type -> memos.api.v1.GetLinkMetadataRequest - 3, // 54: memos.api.v1.MarkdownService.ParseMarkdown:output_type -> memos.api.v1.ParseMarkdownResponse - 5, // 55: memos.api.v1.MarkdownService.RestoreMarkdownNodes:output_type -> memos.api.v1.RestoreMarkdownNodesResponse - 7, // 56: memos.api.v1.MarkdownService.StringifyMarkdownNodes:output_type -> memos.api.v1.StringifyMarkdownNodesResponse - 9, // 57: memos.api.v1.MarkdownService.GetLinkMetadata:output_type -> memos.api.v1.LinkMetadata - 54, // [54:58] is the sub-list for method output_type - 50, // [50:54] is the sub-list for method input_type - 50, // [50:50] is the sub-list for extension type_name - 50, // [50:50] is the sub-list for extension extendee - 0, // [0:50] is the sub-list for field type_name + 10, // 49: memos.api.v1.HTMLElementNode.children:type_name -> memos.api.v1.Node + 10, // 50: memos.api.v1.TableNode.Row.cells:type_name -> memos.api.v1.Node + 2, // 51: memos.api.v1.MarkdownService.ParseMarkdown:input_type -> memos.api.v1.ParseMarkdownRequest + 4, // 52: memos.api.v1.MarkdownService.RestoreMarkdownNodes:input_type -> memos.api.v1.RestoreMarkdownNodesRequest + 6, // 53: memos.api.v1.MarkdownService.StringifyMarkdownNodes:input_type -> memos.api.v1.StringifyMarkdownNodesRequest + 8, // 54: memos.api.v1.MarkdownService.GetLinkMetadata:input_type -> memos.api.v1.GetLinkMetadataRequest + 3, // 55: memos.api.v1.MarkdownService.ParseMarkdown:output_type -> memos.api.v1.ParseMarkdownResponse + 5, // 56: memos.api.v1.MarkdownService.RestoreMarkdownNodes:output_type -> memos.api.v1.RestoreMarkdownNodesResponse + 7, // 57: memos.api.v1.MarkdownService.StringifyMarkdownNodes:output_type -> memos.api.v1.StringifyMarkdownNodesResponse + 9, // 58: memos.api.v1.MarkdownService.GetLinkMetadata:output_type -> memos.api.v1.LinkMetadata + 55, // [55:59] is the sub-list for method output_type + 51, // [51:55] is the sub-list for method input_type + 51, // [51:51] is the sub-list for extension type_name + 51, // [51:51] is the sub-list for extension extendee + 0, // [0:51] is the sub-list for field type_name } func init() { file_api_v1_markdown_service_proto_init() } diff --git a/proto/gen/api/v1/memo_service.pb.go b/proto/gen/api/v1/memo_service.pb.go index 667327bea..16360a3a0 100644 --- a/proto/gen/api/v1/memo_service.pb.go +++ b/proto/gen/api/v1/memo_service.pb.go @@ -1,6 +1,6 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.36.8 +// protoc-gen-go v1.36.9 // protoc (unknown) // source: api/v1/memo_service.proto diff --git a/proto/gen/api/v1/shortcut_service.pb.go b/proto/gen/api/v1/shortcut_service.pb.go index 907fdbfef..32f79e9f8 100644 --- a/proto/gen/api/v1/shortcut_service.pb.go +++ b/proto/gen/api/v1/shortcut_service.pb.go @@ -1,6 +1,6 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.36.8 +// protoc-gen-go v1.36.9 // protoc (unknown) // source: api/v1/shortcut_service.proto diff --git a/proto/gen/api/v1/user_service.pb.go b/proto/gen/api/v1/user_service.pb.go index 80291da17..72ec59a3f 100644 --- a/proto/gen/api/v1/user_service.pb.go +++ b/proto/gen/api/v1/user_service.pb.go @@ -1,6 +1,6 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.36.8 +// protoc-gen-go v1.36.9 // protoc (unknown) // source: api/v1/user_service.proto diff --git a/proto/gen/api/v1/workspace_service.pb.go b/proto/gen/api/v1/workspace_service.pb.go index 96047c832..1262ee25b 100644 --- a/proto/gen/api/v1/workspace_service.pb.go +++ b/proto/gen/api/v1/workspace_service.pb.go @@ -1,6 +1,6 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.36.8 +// protoc-gen-go v1.36.9 // protoc (unknown) // source: api/v1/workspace_service.proto diff --git a/proto/gen/openapi.yaml b/proto/gen/openapi.yaml index ad86bc3a9..34db04b4b 100644 --- a/proto/gen/openapi.yaml +++ b/proto/gen/openapi.yaml @@ -15,13 +15,19 @@ paths: parameters: - name: pageSize in: query - description: "The maximum number of activities to return.\r\n The service may return fewer than this value.\r\n If unspecified, at most 100 activities will be returned.\r\n The maximum value is 1000; values above 1000 will be coerced to 1000." + description: |- + The maximum number of activities to return. + The service may return fewer than this value. + If unspecified, at most 100 activities will be returned. + The maximum value is 1000; values above 1000 will be coerced to 1000. schema: type: integer format: int32 - name: pageToken in: query - description: "A page token, received from a previous `ListActivities` call.\r\n Provide this to retrieve the subsequent page." + description: |- + A page token, received from a previous `ListActivities` call. + Provide this to retrieve the subsequent page. schema: type: string responses: @@ -72,23 +78,35 @@ paths: parameters: - name: pageSize in: query - description: "Optional. The maximum number of attachments to return.\r\n The service may return fewer than this value.\r\n If unspecified, at most 50 attachments will be returned.\r\n The maximum value is 1000; values above 1000 will be coerced to 1000." + description: |- + Optional. The maximum number of attachments to return. + The service may return fewer than this value. + If unspecified, at most 50 attachments will be returned. + The maximum value is 1000; values above 1000 will be coerced to 1000. schema: type: integer format: int32 - name: pageToken in: query - description: "Optional. A page token, received from a previous `ListAttachments` call.\r\n Provide this to retrieve the subsequent page." + description: |- + Optional. A page token, received from a previous `ListAttachments` call. + Provide this to retrieve the subsequent page. schema: type: string - name: filter in: query - description: "Optional. Filter to apply to the list results.\r\n Example: \"type=image/png\" or \"filename:*.jpg\"\r\n Supported operators: =, !=, <, <=, >, >=, :\r\n Supported fields: filename, type, size, create_time, memo" + description: |- + Optional. Filter to apply to the list results. + Example: "type=image/png" or "filename:*.jpg" + Supported operators: =, !=, <, <=, >, >=, : + Supported fields: filename, type, size, create_time, memo schema: type: string - name: orderBy in: query - description: "Optional. The order to sort results by.\r\n Example: \"create_time desc\" or \"filename asc\"" + description: |- + Optional. The order to sort results by. + Example: "create_time desc" or "filename asc" schema: type: string responses: @@ -112,7 +130,9 @@ paths: parameters: - name: attachmentId in: query - description: "Optional. The attachment ID to use for this attachment.\r\n If empty, a unique ID will be generated." + description: |- + Optional. The attachment ID to use for this attachment. + If empty, a unique ID will be generated. schema: type: string requestBody: @@ -223,7 +243,9 @@ paths: post: tags: - AuthService - description: "CreateSession authenticates a user and creates a new session.\r\n Returns the authenticated user information upon successful authentication." + description: |- + CreateSession authenticates a user and creates a new session. + Returns the authenticated user information upon successful authentication. operationId: AuthService_CreateSession requestBody: content: @@ -248,7 +270,9 @@ paths: get: tags: - AuthService - description: "GetCurrentSession returns the current active session information.\r\n This method is idempotent and safe, suitable for checking current session state." + description: |- + GetCurrentSession returns the current active session information. + This method is idempotent and safe, suitable for checking current session state. operationId: AuthService_GetCurrentSession responses: "200": @@ -266,7 +290,9 @@ paths: delete: tags: - AuthService - description: "DeleteSession terminates the current user session.\r\n This is an idempotent operation that invalidates the user's authentication." + description: |- + DeleteSession terminates the current user session. + This is an idempotent operation that invalidates the user's authentication. operationId: AuthService_DeleteSession responses: "200": @@ -305,7 +331,9 @@ paths: parameters: - name: identityProviderId in: query - description: "Optional. The ID to use for the identity provider, which will become the final component of the resource name.\r\n If not provided, the system will generate one." + description: |- + Optional. The ID to use for the identity provider, which will become the final component of the resource name. + If not provided, the system will generate one. schema: type: string requestBody: @@ -389,7 +417,9 @@ paths: type: string - name: updateMask in: query - description: "Required. The update mask applies to the resource. Only the top level fields of\r\n IdentityProvider are supported." + description: |- + Required. The update mask applies to the resource. Only the top level fields of + IdentityProvider are supported. schema: type: string format: field-mask @@ -481,7 +511,9 @@ paths: get: tags: - MarkdownService - description: "GetLinkMetadata returns metadata for a given link.\r\n This is useful for generating link previews." + description: |- + GetLinkMetadata returns metadata for a given link. + This is useful for generating link previews. operationId: MarkdownService_GetLinkMetadata parameters: - name: link @@ -506,7 +538,9 @@ paths: post: tags: - MarkdownService - description: "ParseMarkdown parses the given markdown content and returns a list of nodes.\r\n This is a utility method that transforms markdown text into structured nodes." + description: |- + ParseMarkdown parses the given markdown content and returns a list of nodes. + This is a utility method that transforms markdown text into structured nodes. operationId: MarkdownService_ParseMarkdown requestBody: content: @@ -531,7 +565,9 @@ paths: post: tags: - MarkdownService - description: "RestoreMarkdownNodes restores the given nodes to markdown content.\r\n This is the inverse operation of ParseMarkdown." + description: |- + RestoreMarkdownNodes restores the given nodes to markdown content. + This is the inverse operation of ParseMarkdown. operationId: MarkdownService_RestoreMarkdownNodes requestBody: content: @@ -556,7 +592,9 @@ paths: post: tags: - MarkdownService - description: "StringifyMarkdownNodes stringify the given nodes to plain text content.\r\n This removes all markdown formatting and returns plain text." + description: |- + StringifyMarkdownNodes stringify the given nodes to plain text content. + This removes all markdown formatting and returns plain text. operationId: MarkdownService_StringifyMarkdownNodes requestBody: content: @@ -586,18 +624,26 @@ paths: parameters: - name: pageSize in: query - description: "Optional. The maximum number of memos to return.\r\n The service may return fewer than this value.\r\n If unspecified, at most 50 memos will be returned.\r\n The maximum value is 1000; values above 1000 will be coerced to 1000." + description: |- + Optional. The maximum number of memos to return. + The service may return fewer than this value. + If unspecified, at most 50 memos will be returned. + The maximum value is 1000; values above 1000 will be coerced to 1000. schema: type: integer format: int32 - name: pageToken in: query - description: "Optional. A page token, received from a previous `ListMemos` call.\r\n Provide this to retrieve the subsequent page." + description: |- + Optional. A page token, received from a previous `ListMemos` call. + Provide this to retrieve the subsequent page. schema: type: string - name: state in: query - description: "Optional. The state of the memos to list.\r\n Default to `NORMAL`. Set to `ARCHIVED` to list archived memos." + description: |- + Optional. The state of the memos to list. + Default to `NORMAL`. Set to `ARCHIVED` to list archived memos. schema: enum: - STATE_UNSPECIFIED @@ -607,12 +653,18 @@ paths: format: enum - name: orderBy in: query - description: "Optional. The order to sort results by.\r\n Default to \"display_time desc\".\r\n Example: \"display_time desc\" or \"create_time asc\"" + description: |- + Optional. The order to sort results by. + Default to "display_time desc". + Example: "display_time desc" or "create_time asc" schema: type: string - name: filter in: query - description: "Optional. Filter to apply to the list results.\r\n Filter is a CEL expression to filter memos.\r\n Refer to `Shortcut.filter`." + description: |- + Optional. Filter to apply to the list results. + Filter is a CEL expression to filter memos. + Refer to `Shortcut.filter`. schema: type: string - name: showDeleted @@ -641,7 +693,9 @@ paths: parameters: - name: memoId in: query - description: "Optional. The memo ID to use for this memo.\r\n If empty, a unique ID will be generated." + description: |- + Optional. The memo ID to use for this memo. + If empty, a unique ID will be generated. schema: type: string - name: validateOnly @@ -688,7 +742,9 @@ paths: type: string - name: readMask in: query - description: "Optional. The fields to return in the response.\r\n If not specified, all fields are returned." + description: |- + Optional. The fields to return in the response. + If not specified, all fields are returned. schema: type: string format: field-mask @@ -1140,18 +1196,28 @@ paths: parameters: - name: pageSize in: query - description: "Optional. The maximum number of users to return.\r\n The service may return fewer than this value.\r\n If unspecified, at most 50 users will be returned.\r\n The maximum value is 1000; values above 1000 will be coerced to 1000." + description: |- + Optional. The maximum number of users to return. + The service may return fewer than this value. + If unspecified, at most 50 users will be returned. + The maximum value is 1000; values above 1000 will be coerced to 1000. schema: type: integer format: int32 - name: pageToken in: query - description: "Optional. A page token, received from a previous `ListUsers` call.\r\n Provide this to retrieve the subsequent page." + description: |- + Optional. A page token, received from a previous `ListUsers` call. + Provide this to retrieve the subsequent page. schema: type: string - name: filter in: query - description: "Optional. Filter to apply to the list results.\r\n Example: \"state=ACTIVE\" or \"role=USER\" or \"email:@example.com\"\r\n Supported operators: =, !=, <, <=, >, >=, :\r\n Supported fields: username, email, role, state, create_time, update_time" + description: |- + Optional. Filter to apply to the list results. + Example: "state=ACTIVE" or "role=USER" or "email:@example.com" + Supported operators: =, !=, <, <=, >, >=, : + Supported fields: username, email, role, state, create_time, update_time schema: type: string - name: showDeleted @@ -1180,7 +1246,10 @@ paths: parameters: - name: userId in: query - description: "Optional. The user ID to use for this user.\r\n If empty, a unique ID will be generated.\r\n Must match the pattern [a-z0-9-]+" + description: |- + Optional. The user ID to use for this user. + If empty, a unique ID will be generated. + Must match the pattern [a-z0-9-]+ schema: type: string - name: validateOnly @@ -1190,7 +1259,9 @@ paths: type: boolean - name: requestId in: query - description: "Optional. An idempotency token that can be used to ensure that multiple\r\n requests to create a user have the same result." + description: |- + Optional. An idempotency token that can be used to ensure that multiple + requests to create a user have the same result. schema: type: string requestBody: @@ -1227,7 +1298,9 @@ paths: type: string - name: readMask in: query - description: "Optional. The fields to return in the response.\r\n If not specified, all fields are returned." + description: |- + Optional. The fields to return in the response. + If not specified, all fields are returned. schema: type: string format: field-mask @@ -1454,23 +1527,35 @@ paths: type: string - name: pageSize in: query - description: "Optional. The maximum number of inboxes to return.\r\n The service may return fewer than this value.\r\n If unspecified, at most 50 inboxes will be returned.\r\n The maximum value is 1000; values above 1000 will be coerced to 1000." + description: |- + Optional. The maximum number of inboxes to return. + The service may return fewer than this value. + If unspecified, at most 50 inboxes will be returned. + The maximum value is 1000; values above 1000 will be coerced to 1000. schema: type: integer format: int32 - name: pageToken in: query - description: "Optional. A page token, received from a previous `ListInboxes` call.\r\n Provide this to retrieve the subsequent page." + description: |- + Optional. A page token, received from a previous `ListInboxes` call. + Provide this to retrieve the subsequent page. schema: type: string - name: filter in: query - description: "Optional. Filter to apply to the list results.\r\n Example: \"status=UNREAD\" or \"type=MEMO_COMMENT\"\r\n Supported operators: =, !=\r\n Supported fields: status, type, sender, create_time" + description: |- + Optional. Filter to apply to the list results. + Example: "status=UNREAD" or "type=MEMO_COMMENT" + Supported operators: =, != + Supported fields: status, type, sender, create_time schema: type: string - name: orderBy in: query - description: "Optional. The order to sort results by.\r\n Example: \"create_time desc\" or \"status asc\"" + description: |- + Optional. The order to sort results by. + Example: "create_time desc" or "status asc" schema: type: string responses: @@ -1556,13 +1641,19 @@ paths: type: string - name: pageSize in: query - description: "Optional. The maximum number of settings to return.\r\n The service may return fewer than this value.\r\n If unspecified, at most 50 settings will be returned.\r\n The maximum value is 1000; values above 1000 will be coerced to 1000." + description: |- + Optional. The maximum number of settings to return. + The service may return fewer than this value. + If unspecified, at most 50 settings will be returned. + The maximum value is 1000; values above 1000 will be coerced to 1000. schema: type: integer format: int32 - name: pageToken in: query - description: "Optional. A page token, received from a previous `ListUserSettings` call.\r\n Provide this to retrieve the subsequent page." + description: |- + Optional. A page token, received from a previous `ListUserSettings` call. + Provide this to retrieve the subsequent page. schema: type: string responses: @@ -2117,11 +2208,15 @@ components: name: readOnly: true type: string - description: "The name of the activity.\r\n Format: activities/{id}" + description: |- + The name of the activity. + Format: activities/{id} creator: readOnly: true type: string - description: "The name of the creator.\r\n Format: users/{user}" + description: |- + The name of the creator. + Format: users/{user} type: readOnly: true enum: @@ -2156,10 +2251,14 @@ components: properties: memo: type: string - description: "The memo name of comment.\r\n Format: memos/{memo}" + description: |- + The memo name of comment. + Format: memos/{memo} relatedMemo: type: string - description: "The name of related memo.\r\n Format: memos/{memo}" + description: |- + The name of related memo. + Format: memos/{memo} description: ActivityMemoCommentPayload represents the payload of a memo comment activity. ActivityPayload: type: object @@ -2176,7 +2275,9 @@ components: properties: name: type: string - description: "The name of the attachment.\r\n Format: attachments/{attachment}" + description: |- + The name of the attachment. + Format: attachments/{attachment} createTime: readOnly: true type: string @@ -2202,7 +2303,9 @@ components: description: Output only. The size of the attachment in bytes. memo: type: string - description: "Optional. The related memo. Refer to `Memo.name`.\r\n Format: memos/{memo}" + description: |- + Optional. The related memo. Refer to `Memo.name`. + Format: memos/{memo} AutoLinkNode: type: object properties: @@ -2264,10 +2367,14 @@ components: properties: username: type: string - description: "The username to sign in with.\r\n Required field for password-based authentication." + description: |- + The username to sign in with. + Required field for password-based authentication. password: type: string - description: "The password to sign in with.\r\n Required field for password-based authentication." + description: |- + The password to sign in with. + Required field for password-based authentication. description: Nested message for password-based authentication credentials. CreateSessionRequest_SSOCredentials: required: @@ -2278,14 +2385,20 @@ components: properties: idpId: type: integer - description: "The ID of the SSO provider.\r\n Required field to identify the SSO provider." + description: |- + The ID of the SSO provider. + Required field to identify the SSO provider. format: int32 code: type: string - description: "The authorization code from the SSO provider.\r\n Required field for completing the SSO flow." + description: |- + The authorization code from the SSO provider. + Required field for completing the SSO flow. redirectUri: type: string - description: "The redirect URI used in the SSO flow.\r\n Required field for security validation." + description: |- + The redirect URI used in the SSO flow. + Required field for security validation. description: Nested message for SSO authentication credentials. CreateSessionResponse: type: object @@ -2296,7 +2409,9 @@ components: description: The authenticated user information. lastAccessedAt: type: string - description: "Last time the session was accessed.\r\n Used for sliding expiration calculation (last_accessed_time + 2 weeks)." + description: |- + Last time the session was accessed. + Used for sliding expiration calculation (last_accessed_time + 2 weeks). format: date-time DeleteMemoTagRequest: required: @@ -2306,7 +2421,9 @@ components: properties: parent: type: string - description: "Required. The parent, who owns the tags.\r\n Format: memos/{memo}. Use \"memos/-\" to delete all tags." + description: |- + Required. The parent, who owns the tags. + Format: memos/{memo}. Use "memos/-" to delete all tags. tag: type: string description: Required. The tag name to delete. @@ -2357,7 +2474,9 @@ components: $ref: '#/components/schemas/User' lastAccessedAt: type: string - description: "Last time the session was accessed.\r\n Used for sliding expiration calculation (last_accessed_time + 2 weeks)." + description: |- + Last time the session was accessed. + Used for sliding expiration calculation (last_accessed_time + 2 weeks). format: date-time GoogleProtobufAny: type: object @@ -2376,6 +2495,12 @@ components: type: object additionalProperties: type: string + children: + type: array + items: + $ref: '#/components/schemas/Node' + isSelfClosing: + type: boolean HeadingNode: type: object properties: @@ -2405,7 +2530,9 @@ components: properties: name: type: string - description: "The resource name of the identity provider.\r\n Format: identityProviders/{idp}" + description: |- + The resource name of the identity provider. + Format: identityProviders/{idp} type: enum: - TYPE_UNSPECIFIED @@ -2440,15 +2567,21 @@ components: properties: name: type: string - description: "The resource name of the inbox.\r\n Format: inboxes/{inbox}" + description: |- + The resource name of the inbox. + Format: inboxes/{inbox} sender: readOnly: true type: string - description: "The sender of the inbox notification.\r\n Format: users/{user}" + description: |- + The sender of the inbox notification. + Format: users/{user} receiver: readOnly: true type: string - description: "The receiver of the inbox notification.\r\n Format: users/{user}" + description: |- + The receiver of the inbox notification. + Format: users/{user} status: enum: - STATUS_UNSPECIFIED @@ -2518,7 +2651,10 @@ components: description: The activities. nextPageToken: type: string - description: "A token to retrieve the next page of results.\r\n Pass this value in the page_token field in the subsequent call to `ListActivities`\r\n method to retrieve the next page of results." + description: |- + A token to retrieve the next page of results. + Pass this value in the page_token field in the subsequent call to `ListActivities` + method to retrieve the next page of results. ListAllUserStatsResponse: type: object properties: @@ -2537,7 +2673,9 @@ components: description: The list of attachments. nextPageToken: type: string - description: "A token that can be sent as `page_token` to retrieve the next page.\r\n If this field is omitted, there are no subsequent pages." + description: |- + A token that can be sent as `page_token` to retrieve the next page. + If this field is omitted, there are no subsequent pages. totalSize: type: integer description: The total count of attachments (may be approximate). @@ -2560,7 +2698,9 @@ components: description: The list of inboxes. nextPageToken: type: string - description: "A token that can be sent as `page_token` to retrieve the next page.\r\n If this field is omitted, there are no subsequent pages." + description: |- + A token that can be sent as `page_token` to retrieve the next page. + If this field is omitted, there are no subsequent pages. totalSize: type: integer description: The total count of inboxes (may be approximate). @@ -2635,7 +2775,9 @@ components: description: The list of memos. nextPageToken: type: string - description: "A token that can be sent as `page_token` to retrieve the next page.\r\n If this field is omitted, there are no subsequent pages." + description: |- + A token that can be sent as `page_token` to retrieve the next page. + If this field is omitted, there are no subsequent pages. totalSize: type: integer description: The total count of memos (may be approximate). @@ -2699,7 +2841,9 @@ components: description: The list of user settings. nextPageToken: type: string - description: "A token that can be sent as `page_token` to retrieve the next page.\r\n If this field is omitted, there are no subsequent pages." + description: |- + A token that can be sent as `page_token` to retrieve the next page. + If this field is omitted, there are no subsequent pages. totalSize: type: integer description: The total count of settings (may be approximate). @@ -2723,7 +2867,9 @@ components: description: The list of users. nextPageToken: type: string - description: "A token that can be sent as `page_token` to retrieve the next page.\r\n If this field is omitted, there are no subsequent pages." + description: |- + A token that can be sent as `page_token` to retrieve the next page. + If this field is omitted, there are no subsequent pages. totalSize: type: integer description: The total count of users (may be approximate). @@ -2761,7 +2907,9 @@ components: properties: name: type: string - description: "The resource name of the memo.\r\n Format: memos/{memo}, memo is the user defined id or uuid." + description: |- + The resource name of the memo. + Format: memos/{memo}, memo is the user defined id or uuid. state: enum: - STATE_UNSPECIFIED @@ -2773,7 +2921,9 @@ components: creator: readOnly: true type: string - description: "The name of the creator.\r\n Format: users/{user}" + description: |- + The name of the creator. + Format: users/{user} createTime: readOnly: true type: string @@ -2839,7 +2989,9 @@ components: parent: readOnly: true type: string - description: "Output only. The name of the parent memo.\r\n Format: memos/{memo}" + description: |- + Output only. The name of the parent memo. + Format: memos/{memo} snippet: readOnly: true type: string @@ -2877,7 +3029,9 @@ components: properties: name: type: string - description: "The resource name of the memo.\r\n Format: memos/{memo}" + description: |- + The resource name of the memo. + Format: memos/{memo} snippet: readOnly: true type: string @@ -3063,14 +3217,21 @@ components: name: readOnly: true type: string - description: "The resource name of the reaction.\r\n Format: reactions/{reaction}" + description: |- + The resource name of the reaction. + Format: reactions/{reaction} creator: readOnly: true type: string - description: "The resource name of the creator.\r\n Format: users/{user}" + description: |- + The resource name of the creator. + Format: users/{user} contentId: type: string - description: "The resource name of the content.\r\n For memo reactions, this should be the memo's resource name.\r\n Format: memos/{memo}" + description: |- + The resource name of the content. + For memo reactions, this should be the memo's resource name. + Format: memos/{memo} reactionType: type: string description: "Required. The type of reaction (e.g., \"\U0001F44D\", \"❤️\", \"\U0001F604\")." @@ -3097,7 +3258,9 @@ components: properties: parent: type: string - description: "Required. The parent, who owns the tags.\r\n Format: memos/{memo}. Use \"memos/-\" to rename all tags." + description: |- + Required. The parent, who owns the tags. + Format: memos/{memo}. Use "memos/-" to rename all tags. oldTag: type: string description: Required. The old tag name to rename. @@ -3128,7 +3291,9 @@ components: properties: name: type: string - description: "Required. The resource name of the memo.\r\n Format: memos/{memo}" + description: |- + Required. The resource name of the memo. + Format: memos/{memo} attachments: type: array items: @@ -3142,7 +3307,9 @@ components: properties: name: type: string - description: "Required. The resource name of the memo.\r\n Format: memos/{memo}" + description: |- + Required. The resource name of the memo. + Format: memos/{memo} relations: type: array items: @@ -3155,7 +3322,9 @@ components: properties: name: type: string - description: "The resource name of the shortcut.\r\n Format: users/{user}/shortcuts/{shortcut}" + description: |- + The resource name of the shortcut. + Format: users/{user}/shortcuts/{shortcut} title: type: string description: The title of the shortcut. @@ -3198,7 +3367,9 @@ components: type: string usePathStyle: type: boolean - description: "S3 configuration for cloud storage backend.\r\n Reference: https://developers.cloudflare.com/r2/examples/aws/aws-sdk-go/" + description: |- + S3 configuration for cloud storage backend. + Reference: https://developers.cloudflare.com/r2/examples/aws/aws-sdk-go/ StrikethroughNode: type: object properties: @@ -3296,7 +3467,9 @@ components: properties: name: type: string - description: "Required. The resource name of the memo.\r\n Format: memos/{memo}" + description: |- + Required. The resource name of the memo. + Format: memos/{memo} reaction: allOf: - $ref: '#/components/schemas/Reaction' @@ -3310,7 +3483,9 @@ components: properties: name: type: string - description: "The resource name of the user.\r\n Format: users/{user}" + description: |- + The resource name of the user. + Format: users/{user} role: enum: - ROLE_UNSPECIFIED @@ -3362,7 +3537,9 @@ components: properties: name: type: string - description: "The resource name of the access token.\r\n Format: users/{user}/accessTokens/{access_token}" + description: |- + The resource name of the access token. + Format: users/{user}/accessTokens/{access_token} accessToken: readOnly: true type: string @@ -3385,7 +3562,9 @@ components: properties: name: type: string - description: "The resource name of the session.\r\n Format: users/{user}/sessions/{session}" + description: |- + The resource name of the session. + Format: users/{user}/sessions/{session} sessionId: readOnly: true type: string @@ -3398,7 +3577,9 @@ components: lastAccessedTime: readOnly: true type: string - description: "The timestamp when the session was last accessed.\r\n Used for sliding expiration calculation (last_accessed_time + 2 weeks)." + description: |- + The timestamp when the session was last accessed. + Used for sliding expiration calculation (last_accessed_time + 2 weeks). format: date-time clientInfo: readOnly: true @@ -3428,7 +3609,10 @@ components: properties: name: type: string - description: "The name of the user setting.\r\n Format: users/{user}/settings/{setting}, {setting} is the key for the setting.\r\n For example, \"users/123/settings/GENERAL\" for general settings." + description: |- + The name of the user setting. + Format: users/{user}/settings/{setting}, {setting} is the key for the setting. + For example, "users/123/settings/GENERAL" for general settings. generalSetting: $ref: '#/components/schemas/UserSetting_GeneralSetting' sessionsSetting: @@ -3458,7 +3642,10 @@ components: description: The default visibility of the memo. theme: type: string - description: "The preferred theme of the user.\r\n This references a CSS file in the web/public/themes/ directory.\r\n If not set, the default theme will be used." + description: |- + The preferred theme of the user. + This references a CSS file in the web/public/themes/ directory. + If not set, the default theme will be used. description: General user settings configuration. UserSetting_SessionsSetting: type: object @@ -3483,7 +3670,9 @@ components: properties: name: type: string - description: "The resource name of the user whose stats these are.\r\n Format: users/{user}" + description: |- + The resource name of the user whose stats these are. + Format: users/{user} memoDisplayTimestamps: type: array items: @@ -3531,7 +3720,9 @@ components: properties: name: type: string - description: "The name of the webhook.\r\n Format: users/{user}/webhooks/{webhook}" + description: |- + The name of the webhook. + Format: users/{user}/webhooks/{webhook} url: type: string description: The URL to send the webhook to. @@ -3554,7 +3745,9 @@ components: properties: owner: type: string - description: "The name of instance owner.\r\n Format: users/{user}" + description: |- + The name of instance owner. + Format: users/{user} version: type: string description: Version is the current version of instance. @@ -3570,7 +3763,9 @@ components: properties: name: type: string - description: "The name of the workspace setting.\r\n Format: workspace/settings/{setting}" + description: |- + The name of the workspace setting. + Format: workspace/settings/{setting} generalSetting: $ref: '#/components/schemas/WorkspaceSetting_GeneralSetting' storageSetting: @@ -3583,7 +3778,9 @@ components: properties: theme: type: string - description: "theme is the name of the selected theme.\r\n This references a CSS file in the web/public/themes/ directory." + description: |- + theme is the name of the selected theme. + This references a CSS file in the web/public/themes/ directory. disallowUserRegistration: type: boolean description: disallow_user_registration disallows user registration. @@ -3602,7 +3799,10 @@ components: description: custom_profile is the custom profile. weekStartDayOffset: type: integer - description: "week_start_day_offset is the week start day offset from Sunday.\r\n 0: Sunday, 1: Monday, 2: Tuesday, 3: Wednesday, 4: Thursday, 5: Friday, 6: Saturday\r\n Default is Sunday." + description: |- + week_start_day_offset is the week start day offset from Sunday. + 0: Sunday, 1: Monday, 2: Tuesday, 3: Wednesday, 4: Thursday, 5: Friday, 6: Saturday + Default is Sunday. format: int32 disallowChangeUsername: type: boolean @@ -3661,7 +3861,9 @@ components: format: enum filepathTemplate: type: string - description: "The template of file path.\r\n e.g. assets/{timestamp}_{filename}" + description: |- + The template of file path. + e.g. assets/{timestamp}_{filename} uploadSizeLimitMb: type: string description: The max upload size in megabytes. diff --git a/proto/gen/store/activity.pb.go b/proto/gen/store/activity.pb.go index 1b62a0f55..28c3049a9 100644 --- a/proto/gen/store/activity.pb.go +++ b/proto/gen/store/activity.pb.go @@ -1,6 +1,6 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.36.8 +// protoc-gen-go v1.36.9 // protoc (unknown) // source: store/activity.proto diff --git a/proto/gen/store/attachment.pb.go b/proto/gen/store/attachment.pb.go index b2c445c55..26d422d93 100644 --- a/proto/gen/store/attachment.pb.go +++ b/proto/gen/store/attachment.pb.go @@ -1,6 +1,6 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.36.8 +// protoc-gen-go v1.36.9 // protoc (unknown) // source: store/attachment.proto diff --git a/proto/gen/store/idp.pb.go b/proto/gen/store/idp.pb.go index 80e517e6e..3633f0d82 100644 --- a/proto/gen/store/idp.pb.go +++ b/proto/gen/store/idp.pb.go @@ -1,6 +1,6 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.36.8 +// protoc-gen-go v1.36.9 // protoc (unknown) // source: store/idp.proto diff --git a/proto/gen/store/inbox.pb.go b/proto/gen/store/inbox.pb.go index b7e1f49c9..02cfe12de 100644 --- a/proto/gen/store/inbox.pb.go +++ b/proto/gen/store/inbox.pb.go @@ -1,6 +1,6 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.36.8 +// protoc-gen-go v1.36.9 // protoc (unknown) // source: store/inbox.proto diff --git a/proto/gen/store/memo.pb.go b/proto/gen/store/memo.pb.go index b748a00f2..41ed40508 100644 --- a/proto/gen/store/memo.pb.go +++ b/proto/gen/store/memo.pb.go @@ -1,6 +1,6 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.36.8 +// protoc-gen-go v1.36.9 // protoc (unknown) // source: store/memo.proto diff --git a/proto/gen/store/user_setting.pb.go b/proto/gen/store/user_setting.pb.go index cda71bd47..054510585 100644 --- a/proto/gen/store/user_setting.pb.go +++ b/proto/gen/store/user_setting.pb.go @@ -1,6 +1,6 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.36.8 +// protoc-gen-go v1.36.9 // protoc (unknown) // source: store/user_setting.proto diff --git a/proto/gen/store/workspace_setting.pb.go b/proto/gen/store/workspace_setting.pb.go index 054ef2f35..fcbea13b4 100644 --- a/proto/gen/store/workspace_setting.pb.go +++ b/proto/gen/store/workspace_setting.pb.go @@ -1,6 +1,6 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.36.8 +// protoc-gen-go v1.36.9 // protoc (unknown) // source: store/workspace_setting.proto diff --git a/server/router/api/v1/markdown_service.go b/server/router/api/v1/markdown_service.go index 85585b6fe..42775a942 100644 --- a/server/router/api/v1/markdown_service.go +++ b/server/router/api/v1/markdown_service.go @@ -128,7 +128,12 @@ func convertFromASTNode(rawNode ast.Node) *v1pb.Node { case *ast.Spoiler: node.Node = &v1pb.Node_SpoilerNode{SpoilerNode: &v1pb.SpoilerNode{Content: n.Content}} case *ast.HTMLElement: - node.Node = &v1pb.Node_HtmlElementNode{HtmlElementNode: &v1pb.HTMLElementNode{TagName: n.TagName, Attributes: n.Attributes}} + node.Node = &v1pb.Node_HtmlElementNode{HtmlElementNode: &v1pb.HTMLElementNode{ + TagName: n.TagName, + Attributes: n.Attributes, + Children: convertFromASTNodes(n.Children), + IsSelfClosing: n.IsSelfClosing, + }} default: node.Node = &v1pb.Node_TextNode{TextNode: &v1pb.TextNode{}} } @@ -168,7 +173,7 @@ func convertListKindFromASTNode(node ast.ListKind) v1pb.ListNode_Kind { return v1pb.ListNode_ORDERED case ast.UnorderedList: return v1pb.ListNode_UNORDERED - case ast.DescrpitionList: + case ast.DescriptionList: return v1pb.ListNode_DESCRIPTION default: return v1pb.ListNode_KIND_UNSPECIFIED @@ -249,7 +254,16 @@ func convertToASTNode(node *v1pb.Node) ast.Node { case *v1pb.Node_SpoilerNode: return &ast.Spoiler{Content: n.SpoilerNode.Content} case *v1pb.Node_HtmlElementNode: - return &ast.HTMLElement{TagName: n.HtmlElementNode.TagName, Attributes: n.HtmlElementNode.Attributes} + var children []ast.Node + if len(n.HtmlElementNode.Children) > 0 { + children = convertToASTNodes(n.HtmlElementNode.Children) + } + return &ast.HTMLElement{ + TagName: n.HtmlElementNode.TagName, + Attributes: n.HtmlElementNode.Attributes, + Children: children, + IsSelfClosing: n.HtmlElementNode.IsSelfClosing, + } default: return &ast.Text{} } @@ -286,9 +300,9 @@ func convertListKindToASTNode(kind v1pb.ListNode_Kind) ast.ListKind { case v1pb.ListNode_UNORDERED: return ast.UnorderedList case v1pb.ListNode_DESCRIPTION: - return ast.DescrpitionList + return ast.DescriptionList default: // Default to description list. - return ast.DescrpitionList + return ast.DescriptionList } } diff --git a/web/src/components/MemoContent/HTMLElement.tsx b/web/src/components/MemoContent/HTMLElement.tsx index aef87741f..1131cb9e6 100644 --- a/web/src/components/MemoContent/HTMLElement.tsx +++ b/web/src/components/MemoContent/HTMLElement.tsx @@ -1,12 +1,24 @@ import { createElement } from "react"; +import { Node } from "@/types/proto/api/v1/markdown_service"; +import Renderer from "./Renderer"; interface Props { tagName: string; attributes: { [key: string]: string }; + children: Node[]; + isSelfClosing: boolean; } -const HTMLElement: React.FC = ({ tagName, attributes }: Props) => { - return createElement(tagName, attributes); +const HTMLElement: React.FC = ({ tagName, attributes, children, isSelfClosing }: Props) => { + if (isSelfClosing) { + return createElement(tagName, attributes); + } + + return createElement( + tagName, + attributes, + children.map((child, index) => ), + ); }; export default HTMLElement; diff --git a/web/src/types/proto/api/v1/markdown_service.ts b/web/src/types/proto/api/v1/markdown_service.ts index ae212a44c..3bf55c43e 100644 --- a/web/src/types/proto/api/v1/markdown_service.ts +++ b/web/src/types/proto/api/v1/markdown_service.ts @@ -503,6 +503,8 @@ export interface SpoilerNode { export interface HTMLElementNode { tagName: string; attributes: { [key: string]: string }; + children: Node[]; + isSelfClosing: boolean; } export interface HTMLElementNode_AttributesEntry { @@ -3085,7 +3087,7 @@ export const SpoilerNode: MessageFns = { }; function createBaseHTMLElementNode(): HTMLElementNode { - return { tagName: "", attributes: {} }; + return { tagName: "", attributes: {}, children: [], isSelfClosing: false }; } export const HTMLElementNode: MessageFns = { @@ -3096,6 +3098,12 @@ export const HTMLElementNode: MessageFns = { Object.entries(message.attributes).forEach(([key, value]) => { HTMLElementNode_AttributesEntry.encode({ key: key as any, value }, writer.uint32(18).fork()).join(); }); + for (const v of message.children) { + Node.encode(v!, writer.uint32(26).fork()).join(); + } + if (message.isSelfClosing !== false) { + writer.uint32(32).bool(message.isSelfClosing); + } return writer; }, @@ -3125,6 +3133,22 @@ export const HTMLElementNode: MessageFns = { } continue; } + case 3: { + if (tag !== 26) { + break; + } + + message.children.push(Node.decode(reader, reader.uint32())); + continue; + } + case 4: { + if (tag !== 32) { + break; + } + + message.isSelfClosing = reader.bool(); + continue; + } } if ((tag & 7) === 4 || tag === 0) { break; @@ -3149,6 +3173,8 @@ export const HTMLElementNode: MessageFns = { }, {}, ); + message.children = object.children?.map((e) => Node.fromPartial(e)) || []; + message.isSelfClosing = object.isSelfClosing ?? false; return message; }, };