mirror of
https://github.com/logseq/logseq.git
synced 2026-02-01 22:47:36 +00:00
macro render
This commit is contained in:
27
deps/publish/src/logseq/publish/publish.css
vendored
27
deps/publish/src/logseq/publish/publish.css
vendored
@@ -224,6 +224,33 @@ a:hover {
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
.macro-embed {
|
||||
width: min(100%, 560px);
|
||||
aspect-ratio: 16 / 9;
|
||||
margin: 10px 0;
|
||||
border-radius: 14px;
|
||||
overflow: hidden;
|
||||
background: var(--surface-strong);
|
||||
box-shadow: var(--image-shadow);
|
||||
}
|
||||
|
||||
.macro-embed iframe {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
border: 0;
|
||||
}
|
||||
|
||||
.macro-embed--tweet {
|
||||
aspect-ratio: 4 / 5;
|
||||
}
|
||||
|
||||
.cloze {
|
||||
padding: 0 6px;
|
||||
border-radius: 6px;
|
||||
background: var(--surface-strong);
|
||||
box-shadow: inset 0 0 0 1px rgba(40, 39, 38, 0.12);
|
||||
}
|
||||
|
||||
.code-block {
|
||||
flex: 1;
|
||||
position: relative;
|
||||
|
||||
52
deps/publish/src/logseq/publish/publish.js
vendored
52
deps/publish/src/logseq/publish/publish.js
vendored
@@ -127,9 +127,61 @@ window.toggleTheme = () => {
|
||||
window.localStorage.setItem(THEME_KEY, next);
|
||||
};
|
||||
|
||||
const initTwitterEmbeds = () => {
|
||||
const tweetTargets = document.querySelectorAll(".twitter-tweet");
|
||||
if (!tweetTargets.length) return;
|
||||
|
||||
const ensureTwitterScript = () =>
|
||||
new Promise((resolve) => {
|
||||
if (window.twttr?.widgets?.createTweet) {
|
||||
return resolve(window.twttr);
|
||||
}
|
||||
|
||||
let script = document.querySelector("script[data-twitter-widget]");
|
||||
if (!script) {
|
||||
script = document.createElement("script");
|
||||
script.src = "https://platform.twitter.com/widgets.js";
|
||||
script.async = true;
|
||||
script.defer = true;
|
||||
script.setAttribute("data-twitter-widget", "true");
|
||||
document.body.appendChild(script);
|
||||
}
|
||||
|
||||
script.addEventListener("load", () => {
|
||||
resolve(window.twttr);
|
||||
});
|
||||
});
|
||||
|
||||
ensureTwitterScript().then((twttr) => {
|
||||
if (!twttr?.widgets?.createTweet) return;
|
||||
|
||||
tweetTargets.forEach((el) => {
|
||||
const a = el.querySelector("a[href*='/status/']");
|
||||
if (!a) return;
|
||||
const m = a.href.match(/status\/(\d+)/);
|
||||
if (!m) return;
|
||||
const tweetId = m[1];
|
||||
|
||||
// Clear fallback text
|
||||
el.innerHTML = "";
|
||||
|
||||
// Optional: theme based on your current theme
|
||||
const theme =
|
||||
(document.documentElement.getAttribute("data-theme") || "light") ===
|
||||
"dark"
|
||||
? "dark"
|
||||
: "light";
|
||||
|
||||
twttr.widgets.createTweet(tweetId, el, { theme });
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
const initPublish = () => {
|
||||
applyTheme(preferredTheme());
|
||||
|
||||
initTwitterEmbeds();
|
||||
|
||||
document.querySelectorAll(".math-block").forEach((el) => {
|
||||
const tex = el.textContent;
|
||||
try {
|
||||
|
||||
161
deps/publish/src/logseq/publish/render.cljs
vendored
161
deps/publish/src/logseq/publish/render.cljs
vendored
@@ -218,6 +218,154 @@
|
||||
[:dd.property-value
|
||||
(into [:span] (property-value->nodes v k ctx entities))]])]))
|
||||
|
||||
(def ^:private youtube-regex #"^((?:https?:)?//)?((?:www|m).)?((?:youtube.com|youtu.be|y2u.be|youtube-nocookie.com))(/(?:[\w-]+\?v=|embed/|v/)?)([\w-]+)([\S^\?]+)?$")
|
||||
(def ^:private vimeo-regex #"^((?:https?:)?//)?((?:www).)?((?:player.vimeo.com|vimeo.com))(/(?:video/)?)([\w-]+)(\S+)?$")
|
||||
(def ^:private bilibili-regex #"^((?:https?:)?//)?((?:www).)?((?:bilibili.com))(/(?:video/)?)([\w-]+)(\?p=(\d+))?(\S+)?$")
|
||||
(def ^:private loom-regex #"^((?:https?:)?//)?((?:www).)?((?:loom.com))(/(?:share/|embed/))([\w-]+)(\S+)?$")
|
||||
|
||||
(defn- safe-match
|
||||
[re value]
|
||||
(when (and (string? value) (not (string/blank? value)))
|
||||
(re-find re value)))
|
||||
|
||||
(defn- macro-iframe
|
||||
[src {:keys [class title]}]
|
||||
(when (and (string? src) (not (string/blank? src)))
|
||||
(let [class-name (string/join " " (remove nil? ["macro-embed" class]))]
|
||||
[:div {:class class-name}
|
||||
[:iframe {:src src
|
||||
:title (or title "Embedded content")
|
||||
:loading "lazy"
|
||||
:allow "accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture"
|
||||
:allowfullscreen true}]])))
|
||||
|
||||
(defn- youtube-embed
|
||||
[url]
|
||||
(let [id (cond
|
||||
(and (string? url) (= 11 (count url))) url
|
||||
:else (nth (safe-match youtube-regex url) 5 nil))]
|
||||
(when (and id (string? id))
|
||||
(macro-iframe (str "https://www.youtube.com/embed/" id) {:class "macro-embed--video" :title "YouTube"}))))
|
||||
|
||||
(defn- vimeo-embed
|
||||
[url]
|
||||
(let [id (nth (safe-match vimeo-regex url) 5 nil)]
|
||||
(when (and id (string? id))
|
||||
(macro-iframe (str "https://player.vimeo.com/video/" id) {:class "macro-embed--video" :title "Vimeo"}))))
|
||||
|
||||
(defn- bilibili-embed
|
||||
[url]
|
||||
(let [id (if (<= (count (or url "")) 15)
|
||||
url
|
||||
(nth (safe-match bilibili-regex url) 5 nil))]
|
||||
(when (and id (string? id) (not (string/blank? id)))
|
||||
(macro-iframe (str "https://player.bilibili.com/player.html?bvid=" id "&high_quality=1&autoplay=0")
|
||||
{:class "macro-embed--video" :title "Bilibili"}))))
|
||||
|
||||
(defn- video-embed
|
||||
[url]
|
||||
(when (common-util/url? url)
|
||||
(let [matches (or (safe-match youtube-regex url)
|
||||
(safe-match loom-regex url)
|
||||
(safe-match vimeo-regex url)
|
||||
(safe-match bilibili-regex url))
|
||||
src (cond
|
||||
(and matches (contains? #{"youtube.com" "youtu.be" "y2u.be" "youtube-nocookie.com"} (nth matches 3)))
|
||||
(let [id (nth matches 5)]
|
||||
(when (= 11 (count (or id "")))
|
||||
(str "https://www.youtube.com/embed/" id)))
|
||||
|
||||
(and matches (string/ends-with? (nth matches 3) "loom.com"))
|
||||
(str "https://www.loom.com/embed/" (nth matches 5))
|
||||
|
||||
(and matches (string/ends-with? (nth matches 3) "vimeo.com"))
|
||||
(str "https://player.vimeo.com/video/" (nth matches 5))
|
||||
|
||||
(and matches (= (nth matches 3) "bilibili.com"))
|
||||
(str "https://player.bilibili.com/player.html?bvid=" (nth matches 5) "&high_quality=1&autoplay=0")
|
||||
|
||||
:else
|
||||
url)]
|
||||
(macro-iframe src {:class "macro-embed--video" :title "Video"}))))
|
||||
|
||||
(defn- tweet-embed
|
||||
[url]
|
||||
(let [url (cond
|
||||
(and (string? url) (<= (count url) 15)) (str "https://x.com/i/status/" url)
|
||||
:else url)]
|
||||
(when url
|
||||
[:div.twitter-tweet
|
||||
[:a {:href url} url]])))
|
||||
|
||||
(defn- tweet-embed-from-html
|
||||
[html]
|
||||
(let [id (last (safe-match #"/status/(\d+)" html))]
|
||||
(when (and id (string? id))
|
||||
(tweet-embed id))))
|
||||
|
||||
(defn- macro->nodes
|
||||
[ctx {:keys [name arguments]}]
|
||||
(let [name (string/lower-case (or name ""))
|
||||
arguments (if (sequential? arguments) arguments [])
|
||||
first-arg (first arguments)]
|
||||
(cond
|
||||
(= name "cloze")
|
||||
[[:span.cloze (string/join ", " arguments)]]
|
||||
|
||||
(= name "youtube")
|
||||
(when-let [node (youtube-embed first-arg)] [node])
|
||||
|
||||
(= name "vimeo")
|
||||
(when-let [node (vimeo-embed first-arg)] [node])
|
||||
|
||||
(= name "bilibili")
|
||||
(when-let [node (bilibili-embed first-arg)] [node])
|
||||
|
||||
(= name "video")
|
||||
(when-let [node (video-embed first-arg)] [node])
|
||||
|
||||
(contains? #{"tweet" "twitter"} name)
|
||||
(when-let [node (tweet-embed first-arg)] [node])
|
||||
|
||||
:else
|
||||
(content->nodes (str "{{" name (when (seq arguments)
|
||||
(str " " (string/join ", " arguments))) "}}")
|
||||
(:uuid->title ctx)
|
||||
(:graph-uuid ctx)))))
|
||||
|
||||
(defn- parse-macro-text
|
||||
[value]
|
||||
(when-let [[_ name args] (and (string? value)
|
||||
(re-find #"\{\{\s*([^\s\}]+)\s*([^}]*)\}\}" value))]
|
||||
(let [args (->> (string/split (or args "") #",")
|
||||
(map string/trim)
|
||||
(remove string/blank?)
|
||||
vec)]
|
||||
{:name name
|
||||
:arguments args})))
|
||||
|
||||
(defn- normalize-macro-data
|
||||
[data]
|
||||
(cond
|
||||
(map? data) data
|
||||
(string? data) (parse-macro-text data)
|
||||
(and (sequential? data) (seq data))
|
||||
(let [name (first data)
|
||||
args (second data)]
|
||||
{:name (when (string? name) name)
|
||||
:arguments (if (sequential? args) args [])})
|
||||
:else nil))
|
||||
|
||||
(defn- macro-embed-node?
|
||||
[node]
|
||||
(when (vector? node)
|
||||
(let [tag (first node)
|
||||
attrs (second node)]
|
||||
(and (= tag :div)
|
||||
(map? attrs)
|
||||
(string? (:class attrs))
|
||||
(string/includes? (:class attrs) "macro-embed")))))
|
||||
|
||||
(defn inline->nodes [ctx item]
|
||||
(let [[type data] item
|
||||
{:keys [uuid->title name->uuid graph-uuid]} ctx]
|
||||
@@ -277,6 +425,16 @@
|
||||
[[:a.page-ref {:href (str "/page/" graph-uuid "/" page-uuid)} (str "#" s)]]
|
||||
[(str "#" s)]))
|
||||
|
||||
(= "Macro" type)
|
||||
(if-let [macro-data (normalize-macro-data data)]
|
||||
(or (macro->nodes ctx macro-data) [])
|
||||
(content->nodes (str data) uuid->title graph-uuid))
|
||||
|
||||
(or (= "Inline_Html" type) (= "Export_Snippet" type))
|
||||
(if-let [node (tweet-embed-from-html data)]
|
||||
[node]
|
||||
[])
|
||||
|
||||
:else
|
||||
(content->nodes (str data) uuid->title graph-uuid))))
|
||||
|
||||
@@ -291,7 +449,8 @@
|
||||
content (if (seq ast)
|
||||
(mapcat #(inline->nodes ctx %) ast)
|
||||
(content->nodes raw (:uuid->title ctx) (:graph-uuid ctx)))]
|
||||
(into [:span.block-text] content)))
|
||||
(let [container (if (some macro-embed-node? content) :div :span)]
|
||||
(into [(keyword (str (name container) ".block-text"))] content))))
|
||||
|
||||
(defn block-raw-content [block]
|
||||
(or (:block/content block)
|
||||
|
||||
Reference in New Issue
Block a user