Send a draft with a break in it as the lines it is
The terminal's compose box takes a paragraph now, and there is no newline on the wire: a PRIVMSG is one line and ends where the protocol says it does. So the box that let a reader write the paragraph is what takes it apart again — one message a line, in order, blank lines dropped. The reply tag goes on the first, since that is the one answering something, and the picture on the last, since a link at the end of a paragraph is where a link goes. A command and a rewrite are single-line things whatever the box holds, and read the lines joined. And the placeholder says so where it is new: Enter sends, as it always has, and Shift+Enter is the break nobody would think to try unasked. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
eb6778a parent: 3d7c428 modified
src/frq/app.clj +4 -1 | @@ -1468,7 +1468,10 @@ | ||
| 1468 | 1468 | ;; the minimum it always was. |
| 1469 | 1469 | :hexpand true |
| 1470 | 1470 | :rows (if @terminal? 3 1) |
| 1471 | - :placeholder "Message" | |
| 1471 | + ;; The break is worth saying out loud where it is new: Enter | |
| 1472 | + ;; sends, as it always has, and the box under it takes a | |
| 1473 | + ;; paragraph now — which nobody would think to try unasked. | |
| 1474 | + :placeholder (if @terminal? "Message — Shift+Enter for a new line" "Message") | |
| 1472 | 1475 | :on-change #(reset! s/draft %) |
| 1473 | 1476 | :on-paste-empty s/paste-image! |
| 1474 | 1477 | :on-activate s/send-draft!}] |
| @@ -1468,7 +1468,10 @@ | |||
| 1468 | ;; the minimum it always was. | 1468 | ;; the minimum it always was. |
| 1469 | :hexpand true | 1469 | :hexpand true |
| 1470 | :rows (if @terminal? 3 1) | 1470 | :rows (if @terminal? 3 1) |
| 1471 | - :placeholder "Message" | 1471 | + ;; The break is worth saying out loud where it is new: Enter |
| 1472 | + ;; sends, as it always has, and the box under it takes a | ||
| 1473 | + ;; paragraph now — which nobody would think to try unasked. | ||
| 1474 | + :placeholder (if @terminal? "Message — Shift+Enter for a new line" "Message") | ||
| 1472 | :on-change #(reset! s/draft %) | 1475 | :on-change #(reset! s/draft %) |
| 1473 | :on-paste-empty s/paste-image! | 1476 | :on-paste-empty s/paste-image! |
| 1474 | :on-activate s/send-draft!}] | 1477 | :on-activate s/send-draft!}] |
modified
src/frq/state.clj +32 -10 | @@ -1574,9 +1574,21 @@ | ||
| 1574 | 1574 | A picture still on its way up holds the send rather than losing it: the line |
| 1575 | 1575 | is left in the box, said so, and the reader presses send again a moment |
| 1576 | 1576 | later. Sending the text without its picture would be the one outcome nobody |
| 1577 | - asked for." | |
| 1577 | + asked for. | |
| 1578 | + | |
| 1579 | + A draft with a break in it is several messages. There is no newline on the | |
| 1580 | + wire — a PRIVMSG is one line and a line ends where the protocol says it does | |
| 1581 | + — so the box that lets a reader write a paragraph has to be the thing that | |
| 1582 | + takes it apart again: one message a line, in order, blank lines dropped. Only | |
| 1583 | + the last one carries the picture, and only the first one answers the message | |
| 1584 | + being replied to; the rest are the same thought continuing." | |
| 1578 | 1585 | [] |
| 1579 | - (let [text (str/trim @draft) | |
| 1586 | + (let [lines (->> (str/split-lines @draft) | |
| 1587 | + (map str/trim) | |
| 1588 | + (remove str/blank?)) | |
| 1589 | + ;; What the branches below that are about one line read: a command and | |
| 1590 | + ;; a rewrite are single-line things whatever the box holds. | |
| 1591 | + text (str/join " " lines) | |
| 1580 | 1592 | target @current |
| 1581 | 1593 | reply-to @replying-to |
| 1582 | 1594 | edit @editing |
| @@ -1616,16 +1628,26 @@ | ||
| 1616 | 1628 | (reset! draft "")) |
| 1617 | 1629 | (and (str/blank? text) (not url)) nil |
| 1618 | 1630 | :else |
| 1619 | - (let [text (if (str/starts-with? text "//") (subs text 1) text) | |
| 1620 | - line (str/trim (str text (when url (str " " url))))] | |
| 1631 | + (let [lines (map #(if (str/starts-with? % "//") (subs % 1) %) lines) | |
| 1632 | + ;; The picture rides the last line, so a message that is only a | |
| 1633 | + ;; picture is the link on its own. | |
| 1634 | + lines (if (seq lines) (vec lines) [""]) | |
| 1635 | + last-i (dec (count lines)) | |
| 1636 | + lines (map-indexed (fn [i line] | |
| 1637 | + (str/trim (str line (when (and url (= i last-i)) | |
| 1638 | + (str " " url))))) | |
| 1639 | + lines)] | |
| 1621 | 1640 | ;; Saying something is a way of asking to see it. |
| 1622 | 1641 | (jump-to-present!) |
| 1623 | - (when-let [c @conn] (irc/privmsg! c target line (:id reply-to))) | |
| 1624 | - ;; Only when the server will not send the line back itself. Its copy | |
| 1625 | - ;; carries the msgid, and a message with no id is one nobody can react | |
| 1626 | - ;; or reply to; echoing locally as well would put the line up twice. | |
| 1627 | - (when-not (some-> @conn (irc/cap-acked? "echo-message")) | |
| 1628 | - (push-message! target @form-nick line {:reply-to (:id reply-to)})) | |
| 1642 | + (doseq [[i line] (map-indexed vector lines)] | |
| 1643 | + (when-let [c @conn] | |
| 1644 | + (irc/privmsg! c target line (when (zero? i) (:id reply-to)))) | |
| 1645 | + ;; Only when the server will not send the line back itself. Its copy | |
| 1646 | + ;; carries the msgid, and a message with no id is one nobody can react | |
| 1647 | + ;; or reply to; echoing locally as well would put the line up twice. | |
| 1648 | + (when-not (some-> @conn (irc/cap-acked? "echo-message")) | |
| 1649 | + (push-message! target @form-nick line | |
| 1650 | + {:reply-to (when (zero? i) (:id reply-to))}))) | |
| 1629 | 1651 | (reset! replying-to nil) |
| 1630 | 1652 | (reset! draft "") |
| 1631 | 1653 | (when att |
| @@ -1574,9 +1574,21 @@ | |||
| 1574 | A picture still on its way up holds the send rather than losing it: the line | 1574 | A picture still on its way up holds the send rather than losing it: the line |
| 1575 | is left in the box, said so, and the reader presses send again a moment | 1575 | is left in the box, said so, and the reader presses send again a moment |
| 1576 | later. Sending the text without its picture would be the one outcome nobody | 1576 | later. Sending the text without its picture would be the one outcome nobody |
| 1577 | - asked for." | 1577 | + asked for. |
| 1578 | + | ||
| 1579 | + A draft with a break in it is several messages. There is no newline on the | ||
| 1580 | + wire — a PRIVMSG is one line and a line ends where the protocol says it does | ||
| 1581 | + — so the box that lets a reader write a paragraph has to be the thing that | ||
| 1582 | + takes it apart again: one message a line, in order, blank lines dropped. Only | ||
| 1583 | + the last one carries the picture, and only the first one answers the message | ||
| 1584 | + being replied to; the rest are the same thought continuing." | ||
| 1578 | [] | 1585 | [] |
| 1579 | - (let [text (str/trim @draft) | 1586 | + (let [lines (->> (str/split-lines @draft) |
| 1587 | + (map str/trim) | ||
| 1588 | + (remove str/blank?)) | ||
| 1589 | + ;; What the branches below that are about one line read: a command and | ||
| 1590 | + ;; a rewrite are single-line things whatever the box holds. | ||
| 1591 | + text (str/join " " lines) | ||
| 1580 | target @current | 1592 | target @current |
| 1581 | reply-to @replying-to | 1593 | reply-to @replying-to |
| 1582 | edit @editing | 1594 | edit @editing |
| @@ -1616,16 +1628,26 @@ | |||
| 1616 | (reset! draft "")) | 1628 | (reset! draft "")) |
| 1617 | (and (str/blank? text) (not url)) nil | 1629 | (and (str/blank? text) (not url)) nil |
| 1618 | :else | 1630 | :else |
| 1619 | - (let [text (if (str/starts-with? text "//") (subs text 1) text) | 1631 | + (let [lines (map #(if (str/starts-with? % "//") (subs % 1) %) lines) |
| 1620 | - line (str/trim (str text (when url (str " " url))))] | 1632 | + ;; The picture rides the last line, so a message that is only a |
| 1633 | + ;; picture is the link on its own. | ||
| 1634 | + lines (if (seq lines) (vec lines) [""]) | ||
| 1635 | + last-i (dec (count lines)) | ||
| 1636 | + lines (map-indexed (fn [i line] | ||
| 1637 | + (str/trim (str line (when (and url (= i last-i)) | ||
| 1638 | + (str " " url))))) | ||
| 1639 | + lines)] | ||
| 1621 | ;; Saying something is a way of asking to see it. | 1640 | ;; Saying something is a way of asking to see it. |
| 1622 | (jump-to-present!) | 1641 | (jump-to-present!) |
| 1623 | - (when-let [c @conn] (irc/privmsg! c target line (:id reply-to))) | 1642 | + (doseq [[i line] (map-indexed vector lines)] |
| 1624 | - ;; Only when the server will not send the line back itself. Its copy | 1643 | + (when-let [c @conn] |
| 1625 | - ;; carries the msgid, and a message with no id is one nobody can react | 1644 | + (irc/privmsg! c target line (when (zero? i) (:id reply-to)))) |
| 1626 | - ;; or reply to; echoing locally as well would put the line up twice. | 1645 | + ;; Only when the server will not send the line back itself. Its copy |
| 1627 | - (when-not (some-> @conn (irc/cap-acked? "echo-message")) | 1646 | + ;; carries the msgid, and a message with no id is one nobody can react |
| 1628 | - (push-message! target @form-nick line {:reply-to (:id reply-to)})) | 1647 | + ;; or reply to; echoing locally as well would put the line up twice. |
| 1648 | + (when-not (some-> @conn (irc/cap-acked? "echo-message")) | ||
| 1649 | + (push-message! target @form-nick line | ||
| 1650 | + {:reply-to (when (zero? i) (:id reply-to))}))) | ||
| 1629 | (reset! replying-to nil) | 1651 | (reset! replying-to nil) |
| 1630 | (reset! draft "") | 1652 | (reset! draft "") |
| 1631 | (when att | 1653 | (when att |