diff --git a/css/shared/src/main/scala/Styles.scala b/css/shared/src/main/scala/Styles.scala index 1ae7eecc9..49e7c141a 100644 --- a/css/shared/src/main/scala/Styles.scala +++ b/css/shared/src/main/scala/Styles.scala @@ -111,6 +111,13 @@ object Styles extends StyleSheet.Inline { gridAutoRows := "minmax(50px, 1fr)" ) + val overflowEllipsis = style( + /* BOTH of the following are required for text-overflow */ + whiteSpace.nowrap, + overflow.hidden, + textOverflow := "ellipsis", + ) + val dragFeedBackKf = keyframes( (0 %%) -> style(boxShadow := "0px 0px 0px 0px rgba(0,0,0,1)"), (100 %%) -> style(boxShadow := "0px 0px 0px 20px rgba(0,0,0,0)") @@ -262,10 +269,7 @@ object CommonStyles extends StyleSheet.Standalone { ) ".breadcrumb .markdown *" - ( - /* BOTH of the following are required for text-overflow */ - whiteSpace.nowrap, - overflow.hidden, - textOverflow := "ellipsis", + Styles.overflowEllipsis ) // first/last breadcrumb should not have any margin. @@ -831,10 +835,7 @@ object CommonStyles extends StyleSheet.Standalone { ) ".tag .markdown *" - ( - /* BOTH of the following are required for text-overflow */ - whiteSpace.nowrap, - overflow.hidden, - textOverflow := "ellipsis", + Styles.overflowEllipsis ) ".tag.colorful a" - ( diff --git a/util/shared/src/main/scala/StringOps.scala b/util/shared/src/main/scala/StringOps.scala index 7f4a236a2..24be92337 100644 --- a/util/shared/src/main/scala/StringOps.scala +++ b/util/shared/src/main/scala/StringOps.scala @@ -7,12 +7,14 @@ import scala.util.control.NonFatal object StringOps { + @deprecated("", "use text-overflow: ellipsis instead") def trimToMaxLength(str: String, maxLength: Int): String = { val rawString = str.trim if(rawString.length > maxLength) rawString.take(maxLength - 3) + "..." else rawString } + @deprecated("", "use text-overflow: ellipsis instead") def trimToMaxLength(str: String, maxLength: Option[Int]): String = { maxLength.fold(str)(trimToMaxLength(str, _)) } diff --git a/webApp/src/main/scala/views/ChatView.scala b/webApp/src/main/scala/views/ChatView.scala index b57a438dd..82620f7d9 100644 --- a/webApp/src/main/scala/views/ChatView.scala +++ b/webApp/src/main/scala/views/ChatView.scala @@ -380,10 +380,10 @@ object ChatView { case NodeRole.Task => VDomModifier( Styles.flex, "Task: ", - renderNodeData(node.data, maxLength = Some(100)) + renderNodeData(node.data) ) case _ => VDomModifier( - renderNodeData(node.data, maxLength = Some(100)) + renderNodeData(node.data) ) } ), diff --git a/webApp/src/main/scala/views/Components.scala b/webApp/src/main/scala/views/Components.scala index 83ac51c38..ce3328cea 100644 --- a/webApp/src/main/scala/views/Components.scala +++ b/webApp/src/main/scala/views/Components.scala @@ -67,21 +67,21 @@ object Components { def displayRelativeDate(data: NodeData.RelativeDate) = VDomModifier(span(color := "lightgray", "X + "), span(StringJsOps.durationToString(data.content))) - def renderNodeData(nodeData: NodeData, maxLength: Option[Int] = None): VNode = nodeData match { - case NodeData.Markdown(content) => markdownVNode(trimToMaxLength(content, maxLength)) - case NodeData.PlainText(content) => div(trimToMaxLength(content, maxLength)) + def renderNodeData(nodeData: NodeData): VNode = nodeData match { + case NodeData.Markdown(content) => markdownVNode(content) + case NodeData.PlainText(content) => div(content) case user: NodeData.User => div(displayUserName(user)) case data: NodeData.RelativeDate => div(displayRelativeDate(data)) - case d => div(trimToMaxLength(d.str, maxLength)) + case d => div(d.str) } - def renderNodeDataWithFile(state: GlobalState, nodeId: NodeId, nodeData: NodeData, maxLength: Option[Int] = None)(implicit ctx: Ctx.Owner): VNode = nodeData match { - case NodeData.Markdown(content) => markdownVNode(trimToMaxLength(content, maxLength)) - case NodeData.PlainText(content) => div(trimToMaxLength(content, maxLength)) + def renderNodeDataWithFile(state: GlobalState, nodeId: NodeId, nodeData: NodeData)(implicit ctx: Ctx.Owner): VNode = nodeData match { + case NodeData.Markdown(content) => markdownVNode(content) + case NodeData.PlainText(content) => div(content) case user: NodeData.User => div(displayUserName(user)) case file: NodeData.File => renderUploadedFile(state, nodeId,file) case data: NodeData.RelativeDate => div(displayRelativeDate(data)) - case d => div(trimToMaxLength(d.str, maxLength)) + case d => div(d.str) } def renderText(str: String): VNode = { @@ -312,6 +312,7 @@ object Components { flexWrap.wrap, alignItems.flexStart, b( + Styles.flexStatic, Styles.flex, EditableContent.inputInlineOrRender[String](key, editKey, key => span(key + ":")).editValue.collect { case newKey if newKey != key => GraphChanges(addEdges = properties.map(p => p.edge.copy(data = p.edge.data.copy(key = newKey)))(breakOut), delEdges = properties.map(_.edge)(breakOut)), @@ -330,12 +331,11 @@ object Components { div( Styles.flex, justifyContent.flexEnd, - margin := "3px 0px", Elements.icon(ItemProperties.iconByNodeData(property.node.data))(marginRight := "5px"), editablePropertyNode(state, property.node, property.edge, editMode = editValue, nonPropertyModifier = VDomModifier(writeHoveredNode(state, property.node.id), cursor.pointer, onClick.stopPropagation(Some(FocusPreference(property.node.id))) --> state.rightSidebarNode), - maxLength = Some(100), config = EditableContent.Config.default, + config = EditableContent.Config.default, ), div( marginLeft := "5px", @@ -377,6 +377,8 @@ object Components { div( alignSelf.flexStart, + Styles.flexStatic, + Styles.flex, alignItems.center, color.gray, @@ -385,10 +387,10 @@ object Components { ), property.role match { - case NodeRole.Neutral => renderNodeDataWithFile(state, property.id, property.data, maxLength = Some(50)) + case NodeRole.Neutral => renderNodeDataWithFile(state, property.id, property.data) case _ => VDomModifier( writeHoveredNode(state, property.id), - nodeCardWithFile(state, property, maxLength = Some(50)).apply( + nodeCardWithFile(state, property).apply( margin := "3px 0", sidebarNodeFocusMod(state.rightSidebarNode, property.id), cursor.pointer @@ -533,30 +535,30 @@ object Components { ), ) } - def nodeCard(node: Node, contentInject: VDomModifier = VDomModifier.empty, nodeInject: VDomModifier = VDomModifier.empty, maxLength: Option[Int] = None): VNode = { + def nodeCard(node: Node, contentInject: VDomModifier = VDomModifier.empty, nodeInject: VDomModifier = VDomModifier.empty): VNode = { renderNodeCard( node, - contentInject = VDomModifier(renderNodeData(node.data, maxLength).apply(nodeInject), contentInject) + contentInject = VDomModifier(renderNodeData(node.data).apply(nodeInject), contentInject) ) } - def nodeCardWithFile(state: GlobalState, node: Node, contentInject: VDomModifier = VDomModifier.empty, nodeInject: VDomModifier = VDomModifier.empty, maxLength: Option[Int] = None)(implicit ctx: Ctx.Owner): VNode = { + def nodeCardWithFile(state: GlobalState, node: Node, contentInject: VDomModifier = VDomModifier.empty, nodeInject: VDomModifier = VDomModifier.empty)(implicit ctx: Ctx.Owner): VNode = { renderNodeCard( node, - contentInject = VDomModifier(renderNodeDataWithFile(state, node.id, node.data, maxLength).apply(nodeInject), contentInject) + contentInject = VDomModifier(renderNodeDataWithFile(state, node.id, node.data).apply(nodeInject), contentInject) ) } - def nodeCardWithoutRender(node: Node, contentInject: VDomModifier = VDomModifier.empty, nodeInject: VDomModifier = VDomModifier.empty, maxLength: Option[Int] = None): VNode = { + def nodeCardWithoutRender(node: Node, contentInject: VDomModifier = VDomModifier.empty, nodeInject: VDomModifier = VDomModifier.empty): VNode = { renderNodeCard( node, - contentInject = VDomModifier(p(StringOps.trimToMaxLength(node.str, maxLength), nodeInject), contentInject) + contentInject = VDomModifier(p(node.str, nodeInject), contentInject) ) } - def nodeCardEditable(state: GlobalState, node: Node, editMode: Var[Boolean], contentInject: VDomModifier = VDomModifier.empty, nodeInject: VDomModifier = VDomModifier.empty, maxLength: Option[Int] = None, prependInject: VDomModifier = VDomModifier.empty)(implicit ctx: Ctx.Owner): VNode = { + def nodeCardEditable(state: GlobalState, node: Node, editMode: Var[Boolean], contentInject: VDomModifier = VDomModifier.empty, nodeInject: VDomModifier = VDomModifier.empty, prependInject: VDomModifier = VDomModifier.empty)(implicit ctx: Ctx.Owner): VNode = { renderNodeCard( node, contentInject = VDomModifier( prependInject, - editableNode(state, node, editMode, maxLength).apply(nodeInject), + editableNode(state, node, editMode).apply(nodeInject), contentInject ), ).apply( @@ -564,13 +566,13 @@ object Components { ) } - def nodeCardEditableOnClick(state: GlobalState, node: Node, contentInject: VDomModifier = VDomModifier.empty, nodeInject: VDomModifier = VDomModifier.empty, maxLength: Option[Int] = None, prependInject: VDomModifier = VDomModifier.empty)(implicit ctx: Ctx.Owner): VNode = { + def nodeCardEditableOnClick(state: GlobalState, node: Node, contentInject: VDomModifier = VDomModifier.empty, nodeInject: VDomModifier = VDomModifier.empty, prependInject: VDomModifier = VDomModifier.empty)(implicit ctx: Ctx.Owner): VNode = { val editMode = Var(false) renderNodeCard( node, contentInject = VDomModifier( prependInject, - editableNodeOnClick(state, node, maxLength).apply(nodeInject), + editableNodeOnClick(state, node).apply(nodeInject), contentInject ), ).apply( @@ -719,10 +721,10 @@ object Components { ) } - def editableNodeOnClick(state: GlobalState, node: Node, maxLength: Option[Int] = None, editMode: Var[Boolean] = Var(false), config: EditableContent.Config = EditableContent.Config.cancelOnError)( + def editableNodeOnClick(state: GlobalState, node: Node, editMode: Var[Boolean] = Var(false), config: EditableContent.Config = EditableContent.Config.cancelOnError)( implicit ctx: Ctx.Owner ): VNode = { - editableNode(state, node, editMode, maxLength, config)(ctx)( + editableNode(state, node, editMode, config)(ctx)( onClick.stopPropagation foreach { if(!editMode.now) { editMode() = true @@ -732,10 +734,10 @@ object Components { ) } - def editablePropertyNodeOnClick(state: GlobalState, node: Node, edge: Edge.LabeledProperty, nonPropertyModifier: VDomModifier = VDomModifier.empty, maxLength: Option[Int] = None, editMode: Var[Boolean] = Var(false), config: EditableContent.Config = EditableContent.Config.cancelOnError)( + def editablePropertyNodeOnClick(state: GlobalState, node: Node, edge: Edge.LabeledProperty, nonPropertyModifier: VDomModifier = VDomModifier.empty, editMode: Var[Boolean] = Var(false), config: EditableContent.Config = EditableContent.Config.cancelOnError)( implicit ctx: Ctx.Owner ): VNode = { - editablePropertyNode(state, node, edge, editMode, nonPropertyModifier, maxLength, config)(ctx)( + editablePropertyNode(state, node, edge, editMode, nonPropertyModifier, config)(ctx)( onClick.stopPropagation foreach { if(!editMode.now) { editMode() = true @@ -745,20 +747,20 @@ object Components { ) } - def editableNode(state: GlobalState, node: Node, editMode: Var[Boolean], maxLength: Option[Int] = None, config: EditableContent.Config = EditableContent.Config.cancelOnError)(implicit ctx: Ctx.Owner): VNode = { + def editableNode(state: GlobalState, node: Node, editMode: Var[Boolean], config: EditableContent.Config = EditableContent.Config.cancelOnError)(implicit ctx: Ctx.Owner): VNode = { div( - EditableContent.ofNodeOrRender(state, node, editMode, node => renderNodeDataWithFile(state, node.id, node.data, maxLength), config).editValue.map(GraphChanges.addNode) --> state.eventProcessor.changes, + EditableContent.ofNodeOrRender(state, node, editMode, node => renderNodeDataWithFile(state, node.id, node.data), config).editValue.map(GraphChanges.addNode) --> state.eventProcessor.changes, ) } - def editablePropertyNode(state: GlobalState, node: Node, edge: Edge.LabeledProperty, editMode: Var[Boolean], nonPropertyModifier: VDomModifier = VDomModifier.empty, maxLength: Option[Int] = None, config: EditableContent.Config = EditableContent.Config.cancelOnError)(implicit ctx: Ctx.Owner): VNode = { + def editablePropertyNode(state: GlobalState, node: Node, edge: Edge.LabeledProperty, editMode: Var[Boolean], nonPropertyModifier: VDomModifier = VDomModifier.empty, config: EditableContent.Config = EditableContent.Config.cancelOnError)(implicit ctx: Ctx.Owner): VNode = { div( node.role match { case NodeRole.Neutral => VDomModifier( - EditableContent.ofNodeOrRender(state, node, editMode, node => renderNodeDataWithFile(state, node.id, node.data, maxLength), config).editValue.map(GraphChanges.addNode) --> state.eventProcessor.changes + EditableContent.ofNodeOrRender(state, node, editMode, node => renderNodeDataWithFile(state, node.id, node.data), config).editValue.map(GraphChanges.addNode) --> state.eventProcessor.changes ) case _ => VDomModifier( - EditableContent.customOrRender[Node](node, editMode, node => nodeCardWithFile(state, node, maxLength = maxLength).apply(Styles.wordWrap, nonPropertyModifier), handler => searchAndSelectNode(state, handler.collectHandler[Option[NodeId]] { case id => EditInteraction.fromOption(id.map(state.rawGraph.now.nodesById(_))) } { case EditInteraction.Input(v) => Some(v.id) }.transformObservable(_.prepend(Some(node.id)))), config).editValue.collect { case newNode if newNode.id != edge.propertyId => + EditableContent.customOrRender[Node](node, editMode, node => nodeCardWithFile(state, node).apply(Styles.wordWrap, nonPropertyModifier), handler => searchAndSelectNode(state, handler.collectHandler[Option[NodeId]] { case id => EditInteraction.fromOption(id.map(state.rawGraph.now.nodesById(_))) } { case EditInteraction.Input(v) => Some(v.id) }.transformObservable(_.prepend(Some(node.id)))), config).editValue.collect { case newNode if newNode.id != edge.propertyId => GraphChanges(delEdges = Set(edge), addEdges = Set(edge.copy(propertyId = PropertyId(newNode.id)))) } --> state.eventProcessor.changes, @@ -784,7 +786,7 @@ object Components { span("Selected:", color.gray, margin := "0px 5px 0px 5px"), state.graph.map { g => val node = g.nodesById(nodeId) - Components.nodeCardWithFile(state, node, maxLength = Some(100)).apply(Styles.wordWrap) + Components.nodeCardWithFile(state, node).apply(Styles.wordWrap) } ) case None => VDomModifier.empty diff --git a/webApp/src/main/scala/views/CreateNewPrompt.scala b/webApp/src/main/scala/views/CreateNewPrompt.scala index 957e9a294..7ac36e8c7 100644 --- a/webApp/src/main/scala/views/CreateNewPrompt.scala +++ b/webApp/src/main/scala/views/CreateNewPrompt.scala @@ -156,7 +156,7 @@ object CreateNewPrompt { Rx { val nodes = childNodes().flatMap { id => state.graph().nodesByIdGet(id).map { node => - nodeCard(node, contentInject = VDomModifier(Styles.flex, flexDirection.row, justifyContent.spaceBetween, span(freeSolid.faTimes, cursor.pointer, onClick.mapTo(childNodes.now.filterNot(_ == node.id)) --> childNodes)), maxLength = Some(20)) + nodeCard(node, contentInject = VDomModifier(Styles.flex, flexDirection.row, justifyContent.spaceBetween, span(freeSolid.faTimes, cursor.pointer, onClick.mapTo(childNodes.now.filterNot(_ == node.id)) --> childNodes))) } } diff --git a/webApp/src/main/scala/views/GraphChangesAutomationUI.scala b/webApp/src/main/scala/views/GraphChangesAutomationUI.scala index 238a67ed5..0ba4c4f76 100644 --- a/webApp/src/main/scala/views/GraphChangesAutomationUI.scala +++ b/webApp/src/main/scala/views/GraphChangesAutomationUI.scala @@ -109,7 +109,7 @@ object GraphChangesAutomationUI { )({ templateNode => val propertySingle = PropertyData.Single(graph, graph.idToIdxOrThrow(templateNode.id)) - Components.nodeCard(templateNode, maxLength = Some(100)).apply( + Components.nodeCard(templateNode).apply( padding := "3px", width := "200px", div( diff --git a/webApp/src/main/scala/views/KanbanView.scala b/webApp/src/main/scala/views/KanbanView.scala index e129e1653..d017f17ab 100644 --- a/webApp/src/main/scala/views/KanbanView.scala +++ b/webApp/src/main/scala/views/KanbanView.scala @@ -69,7 +69,6 @@ object KanbanView { } } - private val maxLength = 300 // TODO: use text-overflow:ellipsis instead. def apply(state: GlobalState, focusState: FocusState)(implicit ctx: Ctx.Owner): VNode = { val activeAddCardFields = Var(Set.empty[List[NodeId]]) // until we use thunks, we have to track, which text fields are active, so they don't get lost when rerendering the whole kanban board @@ -196,7 +195,7 @@ object KanbanView { )(implicit ctx: Ctx.Owner): VNode = { val editable = Var(false) - val columnTitle = editableNode(state, node, editable, maxLength = Some(maxLength))(ctx)(cls := "kanbancolumntitle") + val columnTitle = editableNode(state, node, editable)(ctx)(cls := "kanbancolumntitle") val messageChildrenCount = Rx { val graph = state.graph() @@ -521,7 +520,6 @@ object KanbanView { nodeCard( node, - maxLength = Some(maxLength), contentInject = VDomModifier( VDomModifier.ifTrue(isDone)(textDecoration.lineThrough), VDomModifier.ifTrue(inOneLine)(alignItems.flexStart, cardDescription, marginRight := "40px"), // marginRight to not interfere with button bar... diff --git a/webApp/src/main/scala/views/RightSidebar.scala b/webApp/src/main/scala/views/RightSidebar.scala index 398234727..e03fed313 100644 --- a/webApp/src/main/scala/views/RightSidebar.scala +++ b/webApp/src/main/scala/views/RightSidebar.scala @@ -269,10 +269,10 @@ object RightSidebar { VDomModifier( div( - marginTop := "10px", + margin := "10px", propertySingle.properties.map { property => Components.removablePropertySection(state, property.key, property.values).apply( - marginBottom := "10px", + marginBottom := "5px", ) }, @@ -282,7 +282,7 @@ object RightSidebar { fontSize.small, span("Backlinks: ", color.gray), propertySingle.info.reverseProperties.map { node => - Components.nodeCard(node, maxLength = Some(50)).apply( + Components.nodeCard(node).apply( margin := "3px", Components.sidebarNodeFocusMod(state.rightSidebarNode, node.id) ) diff --git a/webApp/src/main/scala/views/SelectedNodes.scala b/webApp/src/main/scala/views/SelectedNodes.scala index 7d6b735f9..5448fa7bb 100644 --- a/webApp/src/main/scala/views/SelectedNodes.scala +++ b/webApp/src/main/scala/views/SelectedNodes.scala @@ -122,9 +122,7 @@ object SelectedNodes { selectedNodes.update(_.filterNot(data => data.nodeId == node.id)) } ), - ), - maxLength = Some(20) - )( + ))( drag(DragItem.SelectedNode(node.id)), cls := "draggable" ) diff --git a/webApp/src/main/scala/views/SharedViewElements.scala b/webApp/src/main/scala/views/SharedViewElements.scala index 6785c8e0b..533df6dc6 100644 --- a/webApp/src/main/scala/views/SharedViewElements.scala +++ b/webApp/src/main/scala/views/SharedViewElements.scala @@ -340,7 +340,7 @@ object SharedViewElements { def render(node: Node, isDeletedNow: Boolean)(implicit ctx: Ctx.Owner) = { if(isDeletedNow) - nodeCardWithoutRender(node, maxLength = Some(25)).apply(cls := "node-deleted") + nodeCardWithoutRender(node).apply(cls := "node-deleted") else { node.role match { case NodeRole.Task => diff --git a/webApp/src/main/scala/views/TableView.scala b/webApp/src/main/scala/views/TableView.scala index c00de9875..8056a97f8 100644 --- a/webApp/src/main/scala/views/TableView.scala +++ b/webApp/src/main/scala/views/TableView.scala @@ -49,12 +49,14 @@ object TableView { }.mkString(", "), value = VDomModifier( edges.map { - case (Some(edge), node: Node.Content) => Components.editablePropertyNodeOnClick(state, node, edge, maxLength = Some(50), config = EditableContent.Config.default) + case (Some(edge), node: Node.Content) => Components.editablePropertyNodeOnClick(state, node, edge, config = EditableContent.Config.default) case (_, tag: Node.Content) if tag.role == NodeRole.Tag => Components.removableNodeTag(state, tag, row) case (_, stage: Node.Content) if stage.role == NodeRole.Stage => Components.removableNodeTag(state, stage, row) - case (_, node: Node.Content) => Components.editableNodeOnClick(state, node, maxLength = Some(50), config = EditableContent.Config.default) + case (_, node: Node.Content) => Components.editableNodeOnClick(state, node, config = EditableContent.Config.default) case (_, user: Node.User) => Components.removableAssignedUser(state, user, row) }, + maxWidth := "300px", + Styles.wordWrap, cellModifier ) )