Hang a message under its sender's name, in a terminal
A face is a picture, and a cell grid has none: the avatar in a message came out as the sender's initial, one column wide, which indented every nick past the text it heads. The name is the heading here, so the face goes and the words hang under it — the shape a terminal has read a conversation in for forty years. The words are held in by a spacer rather than a margin. The backend insets a node by `:margin` on all four sides or not at all: there is no left margin in a cell grid, and a uniform one would spend a blank row above every message to buy two columns beside it. And two of frq's gaps are written as lengths too small to survive the trip. `:margin-top 10` between messages is ten points against a row of sixteen, which rounds to nothing — so every message touched the one above it and the backlog read as one paragraph with names scattered through it. `:spacing 4` between two chips is half a column, which rounds the same way and put 🙂 and ↩️ hard against each other, one wide glyph as far as a reader is concerned. The terminal buys both outright: a row between messages, a column between chips. The window keeps its own numbers, which were never wrong there. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
f978612 parent: 57c6de2 modified
src/frq/app.jolt +97 -39 | @@ -180,6 +180,46 @@ | ||
| 180 | 180 | |
| 181 | 181 | (defn- chrome-scale [] (/ (double @chrome-row) window-row)) |
| 182 | 182 | |
| 183 | +;; Whether the backend under this tree is a terminal, set by `frq.tui` before | |
| 184 | +;; the first paint and never again. Two things in a message hang on it, and | |
| 185 | +;; both are about a cell grid rather than a canvas: there is no picture to | |
| 186 | +;; draw a face with, and a name with nothing to its left is the heading the | |
| 187 | +;; text hangs off — the shape a terminal has read messages in for forty years. | |
| 188 | +(defonce terminal? (atom false)) | |
| 189 | + | |
| 190 | +(defn- chip-gap | |
| 191 | + "The air between two chips on a row. | |
| 192 | + | |
| 193 | + Four points is half a column, which rounds to none: in a window that is the | |
| 194 | + gap a pair of lozenges want, and in a terminal it puts two emoji hard against | |
| 195 | + each other and they read as one wide glyph. A column, where a column is the | |
| 196 | + smallest thing there is." | |
| 197 | + [] | |
| 198 | + (if @terminal? 8 4)) | |
| 199 | + | |
| 200 | +(defn- indented | |
| 201 | + "One of a message's columns, held in from the name above it — in a terminal. | |
| 202 | + | |
| 203 | + The nick is the heading and the words hang under it, which is the shape a | |
| 204 | + terminal has read a conversation in for forty years; in a window the column | |
| 205 | + of faces already marks that edge, so there is nothing to hold in. | |
| 206 | + | |
| 207 | + A cell of its own rather than a margin, because the backend insets a node by | |
| 208 | + `:margin` on all four sides or not at all: there is no left margin in a cell | |
| 209 | + grid, and a uniform one would spend a blank row above every message to buy | |
| 210 | + two columns beside it. | |
| 211 | + | |
| 212 | + The spacer asks for its width and no height. A `:size` is a length down the | |
| 213 | + page, and a spacer two cells tall beside an empty column — a message with no | |
| 214 | + picture and no reactions — is two blank rows in the middle of the backlog, | |
| 215 | + which is a message and a half at this size." | |
| 216 | + [k node] | |
| 217 | + (if @terminal? | |
| 218 | + [:hbox {:key k :spacing 0} | |
| 219 | + [:spacer {:key :indent :size 0 :width-request 16}] | |
| 220 | + node] | |
| 221 | + node)) | |
| 222 | + | |
| 183 | 223 | (defn- below-list [] |
| 184 | 224 | ;; What the strip under the list needs, counted: the gap after the list, the |
| 185 | 225 | ;; separator, the gap under it, the tab bar's row of buttons and the margin |
| @@ -408,7 +448,7 @@ | ||
| 408 | 448 | row at a remove from the pair it belongs with. It keys itself, so the line |
| 409 | 449 | that has no pencil is a row of two chips and not a row with a hole in it." |
| 410 | 450 | [channel m] |
| 411 | - (into [:hbox {:key :actions :align :end :spacing 4} | |
| 451 | + (into [:hbox {:key :actions :align :end :spacing (chip-gap)} | |
| 412 | 452 | [:reaction {:key :react |
| 413 | 453 | :emoji "🙂" |
| 414 | 454 | :size pill-size |
| @@ -450,7 +490,7 @@ | ||
| 450 | 490 | Twemoji pack, in colour, where a label gets whatever the text font has." |
| 451 | 491 | [channel m] |
| 452 | 492 | (let [reactions (:reactions m)] |
| 453 | - [:hbox {:key :pills :spacing 4} | |
| 493 | + [:hbox {:key :pills :spacing (chip-gap)} | |
| 454 | 494 | (for [emoji (sort (keys reactions))] |
| 455 | 495 | [:reaction (cond-> {:key emoji |
| 456 | 496 | :emoji emoji |
| @@ -634,6 +674,13 @@ | ||
| 634 | 674 | :margin-top 10 |
| 635 | 675 | ;; The jump target is what a "go to message" click scrolls to. |
| 636 | 676 | :scroll-here (boolean (and (:id m) (= (:id m) @s/jump-to)))} |
| 677 | + ;; The gap above, where the margin cannot be one. `:margin-top 10` is | |
| 678 | + ;; most of a row in a window and nothing at all in a terminal — ten points | |
| 679 | + ;; against a row of sixteen, rounded down, because a gap that thin is what | |
| 680 | + ;; it looks like at this size. So the terminal buys the row outright: | |
| 681 | + ;; without it every message in the backlog touches the one above it, and a | |
| 682 | + ;; conversation reads as one paragraph with names scattered through it. | |
| 683 | + (when @terminal? [:spacer {:key :gap :size 16}]) | |
| 637 | 684 | ;; Likewise a key each: the surface a jump leaves behind is a different |
| 638 | 685 | ;; node from the plain column, not the same node wearing another tag. |
| 639 | 686 | [(if highlit? :card :vbox) {:key (if highlit? :body-card :body-plain) |
| @@ -661,19 +708,24 @@ | ||
| 661 | 708 | ;; And, where there is a pointer to ask with, resting on the face |
| 662 | 709 | ;; answers before the click does: the card under it is the profile |
| 663 | 710 | ;; screen's first few lines, painted beside the pointer. |
| 664 | - (let [_ @s/media-tick] | |
| 665 | - [:avatar (cond-> {:label (:from m) | |
| 666 | - :src (or (avatars/path-when-ready (:actor m)) "") | |
| 667 | - :size 32 | |
| 668 | - :on-click #(profile/open! (:from m) (:actor m))} | |
| 669 | - (platform/desktop?) | |
| 670 | - (assoc :on-hover #(profile/hover! (:from m) (:actor m)) | |
| 671 | - :on-unhover #(profile/unhover! (:from m)))) | |
| 672 | - ;; Only the hovered face carries one: a card is painted when its | |
| 673 | - ;; node has children, and the pointer is on one face at a time. | |
| 674 | - (when (and (platform/desktop?) | |
| 675 | - (= (:from m) (:nick @profile/hovering))) | |
| 676 | - [hover-card (:from m) (:actor m)])]) | |
| 711 | + ;; And in a terminal there is no face at all: a cell grid has no | |
| 712 | + ;; picture to put in one, and the initial standing in for it is the | |
| 713 | + ;; first letter of the name printed twice, indenting every nick past | |
| 714 | + ;; the words it heads. | |
| 715 | + (when-not @terminal? | |
| 716 | + (let [_ @s/media-tick] | |
| 717 | + [:avatar (cond-> {:label (:from m) | |
| 718 | + :src (or (avatars/path-when-ready (:actor m)) "") | |
| 719 | + :size 32 | |
| 720 | + :on-click #(profile/open! (:from m) (:actor m))} | |
| 721 | + (platform/desktop?) | |
| 722 | + (assoc :on-hover #(profile/hover! (:from m) (:actor m)) | |
| 723 | + :on-unhover #(profile/unhover! (:from m)))) | |
| 724 | + ;; Only the hovered face carries one: a card is painted when its | |
| 725 | + ;; node has children, and the pointer is on one face at a time. | |
| 726 | + (when (and (platform/desktop?) | |
| 727 | + (= (:from m) (:nick @profile/hovering))) | |
| 728 | + [hover-card (:from m) (:actor m)])])) | |
| 677 | 729 | ;; The name carries the row, so it is set at body size in the plain |
| 678 | 730 | ;; text colour: dimmed caption made the one thing you scan a column |
| 679 | 731 | ;; for the faintest thing on it. |
| @@ -700,16 +752,18 @@ | ||
| 700 | 752 | ;; for what follows, and a heading that touches its text is not one. |
| 701 | 753 | ;; The text keeps a column of its own: a wrapping label directly in a |
| 702 | 754 | ;; row wraps against the row. |
| 703 | - [:vbox {:key :text :spacing 2 :margin-top 4} | |
| 704 | - ;; What this answers, in the column the answer itself is in: the chip | |
| 705 | - ;; is a quote of a line, so it wraps to the width a line has. Outside | |
| 706 | - ;; this column it wrapped to the whole row instead — wider than any | |
| 707 | - ;; message, and out to the window's edge on a narrow screen. | |
| 708 | - [:vbox {:key :reply-chip} | |
| 709 | - (when-let [reply-to (:reply-to m)] | |
| 710 | - [reply-chip @s/current reply-to])] | |
| 711 | - (map-indexed (fn [j run] (run-node j run (:system? m))) | |
| 712 | - (text-runs (:text m)))] | |
| 755 | + (indented | |
| 756 | + :text | |
| 757 | + [:vbox {:key :text :spacing 2 :margin-top 4} | |
| 758 | + ;; What this answers, in the column the answer itself is in: the chip | |
| 759 | + ;; is a quote of a line, so it wraps to the width a line has. Outside | |
| 760 | + ;; this column it wrapped to the whole row instead — wider than any | |
| 761 | + ;; message, and out to the window's edge on a narrow screen. | |
| 762 | + [:vbox {:key :reply-chip} | |
| 763 | + (when-let [reply-to (:reply-to m)] | |
| 764 | + [reply-chip @s/current reply-to])] | |
| 765 | + (map-indexed (fn [j run] (run-node j run (:system? m))) | |
| 766 | + (text-runs (:text m)))]) | |
| 713 | 767 | ;; And the picker, when this is the message it was opened on: under the |
| 714 | 768 | ;; line it is about, where the reader is already looking. |
| 715 | 769 | ;; |
| @@ -721,25 +775,29 @@ | ||
| 721 | 775 | [emoji-picker])] |
| 722 | 776 | ;; Pictures under the line that linked them. The link stays: it is what a |
| 723 | 777 | ;; failed fetch, an unsupported format, or a phone with no TLS leaves you. |
| 724 | - [:vbox {:key :images :spacing 4} | |
| 725 | - (when (seq (:images m)) | |
| 726 | - ;; Reading the tick is what subscribes this row to a fetch finishing. | |
| 727 | - (let [_ @s/media-tick] | |
| 728 | - (for [url (:images m)] | |
| 729 | - (when-let [path (media/path-when-ready url)] | |
| 730 | - [:image {:key url | |
| 731 | - :src path | |
| 732 | - :max-height (preview-height) | |
| 733 | - :max-width (preview-width) | |
| 734 | - :on-click #(reset! s/lightbox {:path path :url url})}]))))] | |
| 778 | + (indented | |
| 779 | + :images | |
| 780 | + [:vbox {:key :images :spacing 4} | |
| 781 | + (when (seq (:images m)) | |
| 782 | + ;; Reading the tick is what subscribes this row to a fetch finishing. | |
| 783 | + (let [_ @s/media-tick] | |
| 784 | + (for [url (:images m)] | |
| 785 | + (when-let [path (media/path-when-ready url)] | |
| 786 | + [:image {:key url | |
| 787 | + :src path | |
| 788 | + :max-height (preview-height) | |
| 789 | + :max-width (preview-width) | |
| 790 | + :on-click #(reset! s/lightbox {:path path :url url})}]))))]) | |
| 735 | 791 | ;; Reactions go last, under whatever the message turned out to be: a |
| 736 | 792 | ;; line with a picture on it is the picture, and pills between the words |
| 737 | 793 | ;; and the image they introduce read as reactions to the words alone. |
| 738 | 794 | ;; Nothing moves for a message with no picture — the box above it is |
| 739 | 795 | ;; empty, and the pills still sit under the last line of text. |
| 740 | - [:vbox {:key :reactions-row :margin-top 6} | |
| 741 | - (when (and (:id m) (not (:system? m)) (seq (:reactions m))) | |
| 742 | - [reaction-row @s/current m])]]])) | |
| 796 | + (indented | |
| 797 | + :reactions-row | |
| 798 | + [:vbox {:key :reactions-row :margin-top 6} | |
| 799 | + (when (and (:id m) (not (:system? m)) (seq (:reactions m))) | |
| 800 | + [reaction-row @s/current m])])]])) | |
| 743 | 801 | |
| 744 | 802 | (defn- day-separator [day-key label] |
| 745 | 803 | [:vbox {:key day-key :spacing 4 :margin 0} |
| @@ -180,6 +180,46 @@ | |||
| 180 | 180 | ||
| 181 | (defn- chrome-scale [] (/ (double @chrome-row) window-row)) | 181 | (defn- chrome-scale [] (/ (double @chrome-row) window-row)) |
| 182 | 182 | ||
| 183 | +;; Whether the backend under this tree is a terminal, set by `frq.tui` before | ||
| 184 | +;; the first paint and never again. Two things in a message hang on it, and | ||
| 185 | +;; both are about a cell grid rather than a canvas: there is no picture to | ||
| 186 | +;; draw a face with, and a name with nothing to its left is the heading the | ||
| 187 | +;; text hangs off — the shape a terminal has read messages in for forty years. | ||
| 188 | +(defonce terminal? (atom false)) | ||
| 189 | + | ||
| 190 | +(defn- chip-gap | ||
| 191 | + "The air between two chips on a row. | ||
| 192 | + | ||
| 193 | + Four points is half a column, which rounds to none: in a window that is the | ||
| 194 | + gap a pair of lozenges want, and in a terminal it puts two emoji hard against | ||
| 195 | + each other and they read as one wide glyph. A column, where a column is the | ||
| 196 | + smallest thing there is." | ||
| 197 | + [] | ||
| 198 | + (if @terminal? 8 4)) | ||
| 199 | + | ||
| 200 | +(defn- indented | ||
| 201 | + "One of a message's columns, held in from the name above it — in a terminal. | ||
| 202 | + | ||
| 203 | + The nick is the heading and the words hang under it, which is the shape a | ||
| 204 | + terminal has read a conversation in for forty years; in a window the column | ||
| 205 | + of faces already marks that edge, so there is nothing to hold in. | ||
| 206 | + | ||
| 207 | + A cell of its own rather than a margin, because the backend insets a node by | ||
| 208 | + `:margin` on all four sides or not at all: there is no left margin in a cell | ||
| 209 | + grid, and a uniform one would spend a blank row above every message to buy | ||
| 210 | + two columns beside it. | ||
| 211 | + | ||
| 212 | + The spacer asks for its width and no height. A `:size` is a length down the | ||
| 213 | + page, and a spacer two cells tall beside an empty column — a message with no | ||
| 214 | + picture and no reactions — is two blank rows in the middle of the backlog, | ||
| 215 | + which is a message and a half at this size." | ||
| 216 | + [k node] | ||
| 217 | + (if @terminal? | ||
| 218 | + [:hbox {:key k :spacing 0} | ||
| 219 | + [:spacer {:key :indent :size 0 :width-request 16}] | ||
| 220 | + node] | ||
| 221 | + node)) | ||
| 222 | + | ||
| 183 | (defn- below-list [] | 223 | (defn- below-list [] |
| 184 | ;; What the strip under the list needs, counted: the gap after the list, the | 224 | ;; What the strip under the list needs, counted: the gap after the list, the |
| 185 | ;; separator, the gap under it, the tab bar's row of buttons and the margin | 225 | ;; separator, the gap under it, the tab bar's row of buttons and the margin |
| @@ -408,7 +448,7 @@ | |||
| 408 | row at a remove from the pair it belongs with. It keys itself, so the line | 448 | row at a remove from the pair it belongs with. It keys itself, so the line |
| 409 | that has no pencil is a row of two chips and not a row with a hole in it." | 449 | that has no pencil is a row of two chips and not a row with a hole in it." |
| 410 | [channel m] | 450 | [channel m] |
| 411 | - (into [:hbox {:key :actions :align :end :spacing 4} | 451 | + (into [:hbox {:key :actions :align :end :spacing (chip-gap)} |
| 412 | [:reaction {:key :react | 452 | [:reaction {:key :react |
| 413 | :emoji "🙂" | 453 | :emoji "🙂" |
| 414 | :size pill-size | 454 | :size pill-size |
| @@ -450,7 +490,7 @@ | |||
| 450 | Twemoji pack, in colour, where a label gets whatever the text font has." | 490 | Twemoji pack, in colour, where a label gets whatever the text font has." |
| 451 | [channel m] | 491 | [channel m] |
| 452 | (let [reactions (:reactions m)] | 492 | (let [reactions (:reactions m)] |
| 453 | - [:hbox {:key :pills :spacing 4} | 493 | + [:hbox {:key :pills :spacing (chip-gap)} |
| 454 | (for [emoji (sort (keys reactions))] | 494 | (for [emoji (sort (keys reactions))] |
| 455 | [:reaction (cond-> {:key emoji | 495 | [:reaction (cond-> {:key emoji |
| 456 | :emoji emoji | 496 | :emoji emoji |
| @@ -634,6 +674,13 @@ | |||
| 634 | :margin-top 10 | 674 | :margin-top 10 |
| 635 | ;; The jump target is what a "go to message" click scrolls to. | 675 | ;; The jump target is what a "go to message" click scrolls to. |
| 636 | :scroll-here (boolean (and (:id m) (= (:id m) @s/jump-to)))} | 676 | :scroll-here (boolean (and (:id m) (= (:id m) @s/jump-to)))} |
| 677 | + ;; The gap above, where the margin cannot be one. `:margin-top 10` is | ||
| 678 | + ;; most of a row in a window and nothing at all in a terminal — ten points | ||
| 679 | + ;; against a row of sixteen, rounded down, because a gap that thin is what | ||
| 680 | + ;; it looks like at this size. So the terminal buys the row outright: | ||
| 681 | + ;; without it every message in the backlog touches the one above it, and a | ||
| 682 | + ;; conversation reads as one paragraph with names scattered through it. | ||
| 683 | + (when @terminal? [:spacer {:key :gap :size 16}]) | ||
| 637 | ;; Likewise a key each: the surface a jump leaves behind is a different | 684 | ;; Likewise a key each: the surface a jump leaves behind is a different |
| 638 | ;; node from the plain column, not the same node wearing another tag. | 685 | ;; node from the plain column, not the same node wearing another tag. |
| 639 | [(if highlit? :card :vbox) {:key (if highlit? :body-card :body-plain) | 686 | [(if highlit? :card :vbox) {:key (if highlit? :body-card :body-plain) |
| @@ -661,19 +708,24 @@ | |||
| 661 | ;; And, where there is a pointer to ask with, resting on the face | 708 | ;; And, where there is a pointer to ask with, resting on the face |
| 662 | ;; answers before the click does: the card under it is the profile | 709 | ;; answers before the click does: the card under it is the profile |
| 663 | ;; screen's first few lines, painted beside the pointer. | 710 | ;; screen's first few lines, painted beside the pointer. |
| 664 | - (let [_ @s/media-tick] | 711 | + ;; And in a terminal there is no face at all: a cell grid has no |
| 665 | - [:avatar (cond-> {:label (:from m) | 712 | + ;; picture to put in one, and the initial standing in for it is the |
| 666 | - :src (or (avatars/path-when-ready (:actor m)) "") | 713 | + ;; first letter of the name printed twice, indenting every nick past |
| 667 | - :size 32 | 714 | + ;; the words it heads. |
| 668 | - :on-click #(profile/open! (:from m) (:actor m))} | 715 | + (when-not @terminal? |
| 669 | - (platform/desktop?) | 716 | + (let [_ @s/media-tick] |
| 670 | - (assoc :on-hover #(profile/hover! (:from m) (:actor m)) | 717 | + [:avatar (cond-> {:label (:from m) |
| 671 | - :on-unhover #(profile/unhover! (:from m)))) | 718 | + :src (or (avatars/path-when-ready (:actor m)) "") |
| 672 | - ;; Only the hovered face carries one: a card is painted when its | 719 | + :size 32 |
| 673 | - ;; node has children, and the pointer is on one face at a time. | 720 | + :on-click #(profile/open! (:from m) (:actor m))} |
| 674 | - (when (and (platform/desktop?) | 721 | + (platform/desktop?) |
| 675 | - (= (:from m) (:nick @profile/hovering))) | 722 | + (assoc :on-hover #(profile/hover! (:from m) (:actor m)) |
| 676 | - [hover-card (:from m) (:actor m)])]) | 723 | + :on-unhover #(profile/unhover! (:from m)))) |
| 724 | + ;; Only the hovered face carries one: a card is painted when its | ||
| 725 | + ;; node has children, and the pointer is on one face at a time. | ||
| 726 | + (when (and (platform/desktop?) | ||
| 727 | + (= (:from m) (:nick @profile/hovering))) | ||
| 728 | + [hover-card (:from m) (:actor m)])])) | ||
| 677 | ;; The name carries the row, so it is set at body size in the plain | 729 | ;; The name carries the row, so it is set at body size in the plain |
| 678 | ;; text colour: dimmed caption made the one thing you scan a column | 730 | ;; text colour: dimmed caption made the one thing you scan a column |
| 679 | ;; for the faintest thing on it. | 731 | ;; for the faintest thing on it. |
| @@ -700,16 +752,18 @@ | |||
| 700 | ;; for what follows, and a heading that touches its text is not one. | 752 | ;; for what follows, and a heading that touches its text is not one. |
| 701 | ;; The text keeps a column of its own: a wrapping label directly in a | 753 | ;; The text keeps a column of its own: a wrapping label directly in a |
| 702 | ;; row wraps against the row. | 754 | ;; row wraps against the row. |
| 703 | - [:vbox {:key :text :spacing 2 :margin-top 4} | 755 | + (indented |
| 704 | - ;; What this answers, in the column the answer itself is in: the chip | 756 | + :text |
| 705 | - ;; is a quote of a line, so it wraps to the width a line has. Outside | 757 | + [:vbox {:key :text :spacing 2 :margin-top 4} |
| 706 | - ;; this column it wrapped to the whole row instead — wider than any | 758 | + ;; What this answers, in the column the answer itself is in: the chip |
| 707 | - ;; message, and out to the window's edge on a narrow screen. | 759 | + ;; is a quote of a line, so it wraps to the width a line has. Outside |
| 708 | - [:vbox {:key :reply-chip} | 760 | + ;; this column it wrapped to the whole row instead — wider than any |
| 709 | - (when-let [reply-to (:reply-to m)] | 761 | + ;; message, and out to the window's edge on a narrow screen. |
| 710 | - [reply-chip @s/current reply-to])] | 762 | + [:vbox {:key :reply-chip} |
| 711 | - (map-indexed (fn [j run] (run-node j run (:system? m))) | 763 | + (when-let [reply-to (:reply-to m)] |
| 712 | - (text-runs (:text m)))] | 764 | + [reply-chip @s/current reply-to])] |
| 765 | + (map-indexed (fn [j run] (run-node j run (:system? m))) | ||
| 766 | + (text-runs (:text m)))]) | ||
| 713 | ;; And the picker, when this is the message it was opened on: under the | 767 | ;; And the picker, when this is the message it was opened on: under the |
| 714 | ;; line it is about, where the reader is already looking. | 768 | ;; line it is about, where the reader is already looking. |
| 715 | ;; | 769 | ;; |
| @@ -721,25 +775,29 @@ | |||
| 721 | [emoji-picker])] | 775 | [emoji-picker])] |
| 722 | ;; Pictures under the line that linked them. The link stays: it is what a | 776 | ;; Pictures under the line that linked them. The link stays: it is what a |
| 723 | ;; failed fetch, an unsupported format, or a phone with no TLS leaves you. | 777 | ;; failed fetch, an unsupported format, or a phone with no TLS leaves you. |
| 724 | - [:vbox {:key :images :spacing 4} | 778 | + (indented |
| 725 | - (when (seq (:images m)) | 779 | + :images |
| 726 | - ;; Reading the tick is what subscribes this row to a fetch finishing. | 780 | + [:vbox {:key :images :spacing 4} |
| 727 | - (let [_ @s/media-tick] | 781 | + (when (seq (:images m)) |
| 728 | - (for [url (:images m)] | 782 | + ;; Reading the tick is what subscribes this row to a fetch finishing. |
| 729 | - (when-let [path (media/path-when-ready url)] | 783 | + (let [_ @s/media-tick] |
| 730 | - [:image {:key url | 784 | + (for [url (:images m)] |
| 731 | - :src path | 785 | + (when-let [path (media/path-when-ready url)] |
| 732 | - :max-height (preview-height) | 786 | + [:image {:key url |
| 733 | - :max-width (preview-width) | 787 | + :src path |
| 734 | - :on-click #(reset! s/lightbox {:path path :url url})}]))))] | 788 | + :max-height (preview-height) |
| 789 | + :max-width (preview-width) | ||
| 790 | + :on-click #(reset! s/lightbox {:path path :url url})}]))))]) | ||
| 735 | ;; Reactions go last, under whatever the message turned out to be: a | 791 | ;; Reactions go last, under whatever the message turned out to be: a |
| 736 | ;; line with a picture on it is the picture, and pills between the words | 792 | ;; line with a picture on it is the picture, and pills between the words |
| 737 | ;; and the image they introduce read as reactions to the words alone. | 793 | ;; and the image they introduce read as reactions to the words alone. |
| 738 | ;; Nothing moves for a message with no picture — the box above it is | 794 | ;; Nothing moves for a message with no picture — the box above it is |
| 739 | ;; empty, and the pills still sit under the last line of text. | 795 | ;; empty, and the pills still sit under the last line of text. |
| 740 | - [:vbox {:key :reactions-row :margin-top 6} | 796 | + (indented |
| 741 | - (when (and (:id m) (not (:system? m)) (seq (:reactions m))) | 797 | + :reactions-row |
| 742 | - [reaction-row @s/current m])]]])) | 798 | + [:vbox {:key :reactions-row :margin-top 6} |
| 799 | + (when (and (:id m) (not (:system? m)) (seq (:reactions m))) | ||
| 800 | + [reaction-row @s/current m])])]])) | ||
| 743 | 801 | ||
| 744 | (defn- day-separator [day-key label] | 802 | (defn- day-separator [day-key label] |
| 745 | [:vbox {:key day-key :spacing 4 :margin 0} | 803 | [:vbox {:key day-key :spacing 4 :margin 0} |
modified
src/frq/tui.jolt +4 -0 | @@ -107,6 +107,10 @@ | ||
| 107 | 107 | ;; under a scrolling list reserves the three rows it needs rather than the |
| 108 | 108 | ;; nine the window's count works out to. |
| 109 | 109 | (reset! app/chrome-row points-per-row) |
| 110 | + ;; And a message here is a name with its words hanging under it, rather than | |
| 111 | + ;; a face with them beside it: there is no picture in a cell, and the initial | |
| 112 | + ;; that stood in for one only pushed every nick in past its own text. | |
| 113 | + (reset! app/terminal? true) | |
| 110 | 114 | (app/start! {:after! tui/after! |
| 111 | 115 | :every! tui/every! |
| 112 | 116 | :title! nil |
| @@ -107,6 +107,10 @@ | |||
| 107 | ;; under a scrolling list reserves the three rows it needs rather than the | 107 | ;; under a scrolling list reserves the three rows it needs rather than the |
| 108 | ;; nine the window's count works out to. | 108 | ;; nine the window's count works out to. |
| 109 | (reset! app/chrome-row points-per-row) | 109 | (reset! app/chrome-row points-per-row) |
| 110 | + ;; And a message here is a name with its words hanging under it, rather than | ||
| 111 | + ;; a face with them beside it: there is no picture in a cell, and the initial | ||
| 112 | + ;; that stood in for one only pushed every nick in past its own text. | ||
| 113 | + (reset! app/terminal? true) | ||
| 110 | (app/start! {:after! tui/after! | 114 | (app/start! {:after! tui/after! |
| 111 | :every! tui/every! | 115 | :every! tui/every! |
| 112 | :title! nil | 116 | :title! nil |