backend/app/model/frontend/HighlightableText.scala (56 lines of code) (raw):
package model.frontend
import enumeratum.EnumEntry.Snakecase
import enumeratum.{EnumEntry, PlayEnum}
import play.api.libs.json.{Format, Json}
import scala.annotation.tailrec
sealed trait HighlightRangeType extends EnumEntry with Snakecase
object HighlightRangeType extends PlayEnum[HighlightRangeType] {
case object SearchResult extends HighlightRangeType
// Comment highlights are wired up on the client
override val values = findValues
}
case class HighlightRange(startCharacter: Int, endCharacter: Int)
object HighlightRange {
implicit val format: Format[HighlightRange] = Json.format[HighlightRange]
}
case class TextHighlight(id: String, index: Int, `type`: HighlightRangeType, range: HighlightRange)
object TextHighlight {
implicit val format: Format[TextHighlight] = Json.format[TextHighlight]
}
case class HighlightableText(
contents: String,
highlights: List[TextHighlight]
)
object HighlightableText {
implicit val highlightableTextFormat = Json.format[HighlightableText]
def searchHighlightId(ix: Int, page: Option[Long], isFind: Boolean): String = {
val pageIdPrefix = page.map { p => s"page-$p-" }.getOrElse("")
val findPrefix = if (isFind) "find-" else ""
findPrefix + pageIdPrefix + s"search-result-$ix"
}
@tailrec
private def _fromString(s: String, page: Option[Long], acc: HighlightableText, isFind: Boolean): HighlightableText = {
val startOfTag = s.indexOf("<result-highlight>")
val endOfTag = s.indexOf("</result-highlight>")
if(startOfTag == -1) {
HighlightableText(acc.contents + s, acc.highlights)
} else {
val beforeSlice = s.slice(0, startOfTag)
val inSlice = s.slice(startOfTag + "<result-highlight>".length, endOfTag)
val highlight = TextHighlight(
id = searchHighlightId(acc.highlights.length, page, isFind),
index = acc.highlights.length,
HighlightRangeType.SearchResult,
HighlightRange(
startOfTag + acc.contents.length,
(endOfTag - "<result-highlight>".length) + acc.contents.length
)
)
val newText = acc.contents + beforeSlice + inSlice
val textRemaining = s.slice(endOfTag + "</result-highlight>".length, s.length)
_fromString(textRemaining, page, HighlightableText(newText, acc.highlights :+ highlight), isFind)
}
}
def fromString(s: String, page: Option[Long], isFind: Boolean = false): HighlightableText = {
_fromString(s, page, HighlightableText("", List.empty), isFind)
}
}