social-kit / src / ardegazu / social / notify.cljs
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
;; ported-from: src/notify.ts @ v1.2.0
;;
;; Framework-free invite toast: a small fixed card any suite app can show
;; when a friend's invite arrives live. Styles are injected once and scoped
;; under .soc-toast; accept navigates to the capability URL.
(ns ardegazu.social.notify
  (:require [ardegazu.social.text :as text]))

(def ^:private CSS "
.soc-toast {
  position: fixed; z-index: 9999;
  right: calc(env(safe-area-inset-right, 0px) + 16px);
  bottom: calc(env(safe-area-inset-bottom, 0px) + 64px);
  max-width: min(320px, calc(100vw - 32px)); padding: 14px 16px; border-radius: 12px;
  background: var(--soc-bg, #1e1c2a); color: var(--soc-fg, #eae6f2); border: 1px solid var(--soc-line, #2b2838);
  font: 14px/1.5 -apple-system, BlinkMacSystemFont, \"Segoe UI\", Roboto, sans-serif;
  box-shadow: 0 10px 26px -14px rgba(0,0,0,.7);
  animation: soc-toast-in .25s ease;
}
@keyframes soc-toast-in { from { transform: translateY(12px); opacity: 0; } }
.soc-toast .who { font-weight: 650; }
.soc-toast .what { color: var(--soc-dim, #a49fb5); margin: 4px 0 10px; }
.soc-toast .row { display: flex; gap: 8px; }
.soc-toast button {
  font: 600 13px/1 inherit; color: var(--soc-fg, #eae6f2); cursor: pointer;
  background: var(--soc-bg, #272334); border: 1px solid var(--soc-line, #2b2838); border-radius: 8px; padding: 8px 12px;
}
.soc-toast button.go { border-color: var(--soc-accent, #93c1a5); }
")

(def ^:private css-injected (volatile! false))

(defn- ensure-css []
  (when-not @css-injected
    (vreset! css-injected true)
    (let [style (js/document.createElement "style")]
      (set! (.-textContent style) CSS)
      (.appendChild js/document.head style))))

(defn invite-toast [inv opts]
  (ensure-css)
  (let [tt (text/mk-t (unchecked-get opts "t"))
        card (js/document.createElement "div")
        can-share (identical? (js* "typeof ~{}" (.-share js/navigator)) "function")]
    (set! (.-className card) "soc-toast")
    (set! (.-innerHTML card) "
    <div class=\"who\"></div>
    <div class=\"what\"></div>
    <div class=\"row\">
      <button class=\"go\"></button>
      <button class=\"grab\"></button>
      <button class=\"later\"></button>
    </div>")
    (set! (.-textContent (.querySelector card ".go")) (tt "social.toast.join" js/undefined))
    (set! (.-textContent (.querySelector card ".grab"))
          (if can-share (tt "social.toast.share" js/undefined) (tt "social.toast.copy" js/undefined)))
    (set! (.-textContent (.querySelector card ".later")) (tt "social.toast.not-now" js/undefined))
    (set! (.-textContent (.querySelector card ".who"))
          (tt "social.toast.invites-you" (js-obj "name" (unchecked-get opts "label"))))
    (let [inv-app (unchecked-get inv "app")
          app-short (if ^boolean (js* "!!(~{})" inv-app)
                      (aget (.split ^js inv-app ".") 0)
                      "")
          msg (unchecked-get inv "msg")
          msg-k (when (some? msg) (unchecked-get msg "k"))
          inv-label (unchecked-get inv "label")]
      ;; a message code renders in the receiver's language; legacy envelopes fall
      ;; back to the sender's prose label, then to the generic line
      (set! (.-textContent (.querySelector card ".what"))
            (if (and (identical? msg-k "join") ^boolean (js* "!!(~{})" app-short))
              (tt "social.invite.join" (js-obj "app" app-short))
              (if ^boolean (js* "!!(~{})" inv-label)
                inv-label
                (if ^boolean (js* "!!(~{})" app-short)
                  (tt "social.toast.come-to" (js-obj "app" app-short))
                  (tt "social.toast.come-play" js/undefined))))))
    (let [close (fn [] (.remove card))]
      (.addEventListener (.querySelector card ".go") "click"
                         (fn []
                           (close)
                           (if-let [on-accept (unchecked-get opts "onAccept")]
                             (on-accept inv)
                             (set! (.-href js/location) (unchecked-get inv "url")))))
      ;; installed-PWA path: a cross-app navigation from standalone is janky on
      ;; phones — carry the capability link out via the share sheet (or clipboard)
      ;; and open it wherever suits you
      (let [grab (.querySelector card ".grab")]
        (.addEventListener grab "click"
                           (fn []
                             (if can-share
                               (-> (.share js/navigator (js-obj "title" "ardegazu invite"
                                                                "url" (unchecked-get inv "url")))
                                   (.catch (fn [_] nil)))
                               (when-let [cb (.-clipboard js/navigator)]
                                 (.then (.writeText ^js cb (unchecked-get inv "url"))
                                        (fn [_] (set! (.-textContent grab) (tt "social.toast.copied" js/undefined)))
                                        (fn [_] (set! (.-textContent grab)
                                                      (str (.slice ^js (unchecked-get inv "url") 0 24) "…")))))))))
      (.addEventListener (.querySelector card ".later") "click" (fn [] (close)))
      (js/setTimeout close (let [t (unchecked-get opts "timeoutMs")] (if (nil? t) 45000 t)))
      (.appendChild js/document.body card)
      js/undefined)))

static mirror of HEAD · about · clone: git clone https://git.ardegazu.ro/social-kit.git