// Vikunja is a to-do list application to facilitate your life. // Copyright 2018-present Vikunja and contributors. All rights reserved. // // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU Affero General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Affero General Public License for more details. // // You should have received a copy of the GNU Affero General Public License // along with this program. If not, see . package richtext import ( "github.com/JohannesKaufmann/dom" "github.com/JohannesKaufmann/html-to-markdown/v2/converter" "golang.org/x/net/html" ) // registerTipTapRules teaches the HTML→Markdown converter about the two // Vikunja-specific nodes that standard GFM doesn't model: TipTap mentions and // TipTap task lists. func registerTipTapRules(conv *converter.Converter) { // Empty mention elements (the common stored form is ) // would otherwise be treated as content-less by the whitespace collapser, eating the // following space. Giving them a text child before collapse (PriorityLate) preserves it. conv.Register.PreRenderer(ensureMentionContent, converter.PriorityEarly) conv.Register.RendererFor("mention-user", converter.TagTypeInline, renderMentionUser, converter.PriorityEarly) // Normalize TipTap task-list items to a single that // renderTaskCheckbox turns into the GFM "[x]"/"[ ]" marker. We drive off the //
  • attribute (the same source of truth resetDescriptionChecklist // uses) rather than TipTap's
  • so its checkbox // state is carried by a single leading , removing // TipTap's
  • level instead would put a block boundary between // the marker and the text. host := firstParagraph(li) if host == nil { host = li } host.InsertBefore(input, host.FirstChild) } } func firstParagraph(n *html.Node) *html.Node { for _, c := range dom.AllChildNodes(n) { if c.Type == html.ElementNode && c.Data == "p" { return c } if found := firstParagraph(c); found != nil { return found } } return nil }