Merge remote-tracking branch 'origin/staging' into develop

This commit is contained in:
Andrey Antukh
2025-11-27 18:01:08 +01:00
16 changed files with 136 additions and 89 deletions

View File

@@ -842,38 +842,6 @@
choices))]
{:pred pred}))})
;; (register!
;; {:type ::inst
;; :pred tm/instant?
;; :type-properties
;; {:title "inst"
;; :description "Satisfies Inst protocol"
;; :error/message "should be an instant"
;; :gen/gen (->> (sg/small-int :min 0 :max 100000)
;; (sg/fmap (fn [v] (tm/parse-inst v))))
;; :decode/string tm/parse-inst
;; :encode/string tm/format-inst
;; :decode/json tm/parse-inst
;; :encode/json tm/format-inst
;; ::oapi/type "string"
;; ::oapi/format "iso"}})
;; (register!
;; {:type ::timestamp
;; :pred tm/instant?
;; :type-properties
;; {:title "inst"
;; :description "Satisfies Inst protocol, the same as ::inst but encodes to epoch"
;; :error/message "should be an instant"
;; :gen/gen (->> (sg/small-int)
;; (sg/fmap (fn [v] (tm/parse-inst v))))
;; :decode/string tm/parse-inst
;; :encode/string inst-ms
;; :decode/json tm/parse-inst
;; :encode/json inst-ms
;; ::oapi/type "string"
;; ::oapi/format "number"}})
#?(:clj
(register!
@@ -951,7 +919,7 @@
:pred #(and (string? %) (not (str/blank? %)))
:property-pred
(fn [{:keys [min max] :as props}]
(if (seq props)
(if (or min max)
(fn [value]
(let [size (count value)]
(cond

View File

@@ -65,7 +65,7 @@ RUN set -eux; \
FROM base AS setup-jvm
ENV CLOJURE_VERSION=1.12.2.1565
ENV CLOJURE_VERSION=1.12.3.1577
RUN set -eux; \
ARCH="$(dpkg --print-architecture)"; \

View File

@@ -12,7 +12,7 @@ http {
sendfile on;
tcp_nopush on;
tcp_nodelay on;
keepalive_timeout 65;
keepalive_timeout 0;
types_hash_max_size 2048;
server_tokens off;
@@ -223,16 +223,6 @@ http {
add_header X-Cache-Status $upstream_cache_status;
}
location ~ ^/js/config.js$ {
add_header Cache-Control "no-store, no-cache, max-age=0" always;
}
location ~* \.(js|css|jpg|svg|png|mjs|map)$ {
# We set no cache only on devenv
add_header Cache-Control "no-store, no-cache, max-age=0" always;
# add_header Cache-Control "max-age=604800" always; # 7 days
}
location ~ ^/(/|css|fonts|images|js|wasm|mjs|map) {
}
@@ -240,9 +230,10 @@ http {
return 301 " /404";
}
add_header Last-Modified $date_gmt;
add_header Cache-Control "no-store, no-cache, max-age=0" always;
if_modified_since off;
add_header Cache-Control "no-store";
add_header Connection close always;
# This header is what we need to use on prod
# add_header Cache-Control "public, must-revalidate, max-age=0";
try_files $uri /index.html$is_args$args /index.html =404;
}
}

View File

@@ -499,7 +499,7 @@ test.describe("Tokens: Tokens Tab", () => {
await valueField.fill("");
// TODO: We need to fix this translation
await expect(
tokensUpdateCreateModal.getByText("Empty field"),
tokensUpdateCreateModal.getByText("Token value cannot be empty"),
).toBeVisible();
await valueSaturationSelector.click({ position: { x: 50, y: 50 } });
await expect(valueField).toHaveValue(/^#[A-Fa-f\d]+$/);

View File

@@ -33,6 +33,7 @@
[app.util.object :as obj]
[app.util.text.content :as content]
[app.util.text.content.styles :as styles]
[cuerdas.core :as str]
[rumext.v2 :as mf]))
(defn get-contrast-color [background-color]
@@ -268,7 +269,12 @@
"bottom" "flex-end"
nil))
;;
(defn- font-family-from-font-id [font-id]
(if (str/includes? font-id "gfont-noto-sans")
(let [lang (str/replace font-id #"gfont\-noto\-sans\-" "")]
(if (>= (count lang) 3) (str/capital lang) (str/upper lang)))
"Noto Color Emoji"))
;; Text Editor Wrapper
;; This is an SVG element that wraps the HTML editor.
;;
@@ -281,6 +287,10 @@
(let [shape-id (dm/get-prop shape :id)
modifiers (dm/get-in modifiers [shape-id :modifiers])
fallback-fonts (wasm.api/fonts-from-text-content (:content shape) false)
fallback-families (map (fn [font]
(font-family-from-font-id (:font-id font))) fallback-fonts)
clip-id (dm/str "text-edition-clip" shape-id)
text-modifier-ref
@@ -341,7 +351,8 @@
render-wasm?
(obj/merge!
#js {"--editor-container-width" (dm/str width "px")
"--editor-container-height" (dm/str height "px")})
"--editor-container-height" (dm/str height "px")
"--fallback-families" (dm/str (str/join ", " fallback-families))})
(not render-wasm?)
(obj/merge!

View File

@@ -32,6 +32,12 @@
[cuerdas.core :as str]
[rumext.v2 :as mf]))
(defn- token-value-error-fn
[{:keys [value]}]
(when (or (str/empty? value)
(str/blank? value))
(tr "workspace.tokens.empty-input")))
(defn- make-schema
[tokens-tree]
(sm/schema
@@ -44,7 +50,7 @@
[:fn {:error/fn #(tr "workspace.tokens.token-name-duplication-validation-error" (:value %))}
#(not (cft/token-name-path-exists? % tokens-tree))]]]
[:value ::sm/text]
[:value [::sm/text {:error/fn token-value-error-fn}]]
[:resolved-value ::sm/any]

View File

@@ -32,6 +32,12 @@
[cuerdas.core :as str]
[rumext.v2 :as mf]))
(defn- token-value-error-fn
[{:keys [value]}]
(when (or (str/empty? value)
(str/blank? value))
(tr "workspace.tokens.empty-input")))
(defn- make-schema
[tokens-tree]
(sm/schema
@@ -44,7 +50,7 @@
[:fn {:error/fn #(tr "workspace.tokens.token-name-duplication-validation-error" (:value %))}
#(not (cft/token-name-path-exists? % tokens-tree))]]]
[:value ::sm/text]
[:value [::sm/text {:error/fn token-value-error-fn}]]
[:resolved-value ::sm/any]

View File

@@ -32,6 +32,12 @@
[cuerdas.core :as str]
[rumext.v2 :as mf]))
(defn- token-value-error-fn
[{:keys [value]}]
(when (or (str/empty? value)
(str/blank? value))
(tr "workspace.tokens.empty-input")))
(defn- make-schema
[tokens-tree]
(sm/schema
@@ -44,7 +50,7 @@
[:fn {:error/fn #(tr "workspace.tokens.token-name-duplication-validation-error" (:value %))}
#(not (cft/token-name-path-exists? % tokens-tree))]]]
[:value ::sm/text]
[:value [::sm/text {:error/fn token-value-error-fn}]]
[:resolved-value ::sm/any]

View File

@@ -205,6 +205,7 @@
;; TODO: Review this value vs default-value
:value (or value "")
:hint-message (:message hint)
:variant "comfortable"
:slot-start swatch
:hint-type (:type hint)})

View File

@@ -83,6 +83,7 @@
(mf/spread-props props {:on-change on-change
:default-value value
:hint-message (:message hint)
:variant "comfortable"
:hint-type (:type hint)})
props

View File

@@ -32,6 +32,12 @@
[cuerdas.core :as str]
[rumext.v2 :as mf]))
(defn- token-value-error-fn
[{:keys [value]}]
(when (or (str/empty? value)
(str/blank? value))
(tr "workspace.tokens.empty-input")))
(defn- make-schema
[tokens-tree]
(sm/schema
@@ -44,7 +50,7 @@
[:fn {:error/fn #(tr "workspace.tokens.token-name-duplication-validation-error" (:value %))}
#(not (cft/token-name-path-exists? % tokens-tree))]]]
[:value ::sm/text]
[:value [::sm/text {:error/fn token-value-error-fn}]]
[:resolved-value ::sm/any]

View File

@@ -785,19 +785,10 @@
hidden)))
shadows))
(defn set-shape-text-content
"This function sets shape text content and returns a stream that loads the needed fonts asynchronously"
[shape-id content]
(h/call wasm/internal-module "_clear_shape_text")
(set-shape-vertical-align (get content :vertical-align))
(defn fonts-from-text-content [content fallback-fonts-only?]
(let [paragraph-set (first (get content :children))
paragraphs (get paragraph-set :children)
fonts (fonts/get-content-fonts content)
total (count paragraphs)]
(loop [index 0
emoji? false
langs #{}]
@@ -814,20 +805,36 @@
emoji? (if emoji? emoji? (t/contains-emoji? text))
langs (t/collect-used-languages langs text)]
(t/write-shape-text spans paragraph text)
;; FIXME: this should probably be somewhere else
(when fallback-fonts-only? (t/write-shape-text spans paragraph text))
(recur (inc index)
emoji?
langs))))
(let [updated-fonts
(-> fonts
(-> #{}
(cond-> ^boolean emoji? (f/add-emoji-font))
(f/add-noto-fonts langs))
result (f/store-fonts shape-id updated-fonts)]
fallback-fonts (filter #(get % :is-fallback) updated-fonts)]
(h/call wasm/internal-module "_update_shape_text_layout")
(if fallback-fonts-only? updated-fonts fallback-fonts))))))
result)))))
(defn set-shape-text-content
"This function sets shape text content and returns a stream that loads the needed fonts asynchronously"
[shape-id content]
(h/call wasm/internal-module "_clear_shape_text")
(set-shape-vertical-align (get content :vertical-align))
(let [fonts (fonts/get-content-fonts content)
fallback-fonts (fonts-from-text-content content true)
all-fonts (concat fonts fallback-fonts)
result (f/store-fonts shape-id all-fonts)]
(f/load-fallback-fonts-for-editor! fallback-fonts)
(h/call wasm/internal-module "_update_shape_text_layout")
result))
(defn set-shape-grow-type
[grow-type]

View File

@@ -266,6 +266,12 @@
(store-font-id shape-id font-data asset-id emoji? fallback?)))
;; FIXME: This is a temporary function to load the fallback fonts for the editor.
;; Once we render the editor content within wasm, we can remove this function.
(defn load-fallback-fonts-for-editor!
[fonts]
(doseq [font fonts]
(fonts/ensure-loaded! (:font-id font) (:font-variant-id font))))
(defn store-fonts
[shape-id fonts]
@@ -277,7 +283,8 @@
:font-variant-id "regular"
:style 0
:weight 400
:is-emoji true}))
:is-emoji true
:is-fallback true}))
(def noto-fonts
{:japanese {:font-id "gfont-noto-sans-jp" :font-variant-id "regular" :style 0 :weight 400 :is-fallback true}

View File

@@ -38,7 +38,8 @@
(str v "px")
(and (= k :font-family) (seq v))
(str/quote v)
;; pick just first family, avoid quoting twice, and add var(--fallback-families)
(str/concat (str/quote (str/unquote (first (str/split v ",")))) ", var(--fallback-families)")
:else
v))
@@ -53,7 +54,7 @@
(str/slice v 0 -2)
(= k :font-family)
(str/unquote v)
(str/unquote (str/replace v ", var(--fallback-families)" ""))
:else
v))

View File

@@ -13,6 +13,7 @@ import {
isLikeParagraph,
} from "./Paragraph.js";
import { isDisplayBlock, normalizeStyles } from "./Style.js";
import { sanitizeFontFamily } from "./Style.js";
const DEFAULT_FONT_SIZE = "14px";
const DEFAULT_FONT_WEIGHT = 400;
@@ -87,16 +88,16 @@ export function mapContentFragmentFromDocument(document, root, styleDefaults) {
styleDefaults?.getPropertyValue("font-size") ?? DEFAULT_FONT_SIZE,
);
}
const fontFamily = textSpan.style.getPropertyValue("font-family");
let fontFamily = textSpan.style.getPropertyValue("font-family");
if (!fontFamily) {
console.warn("font-family", fontFamily);
const fontFamilyValue =
fontFamily =
styleDefaults?.getPropertyValue("font-family") ?? DEFAULT_FONT_FAMILY;
const quotedFontFamily = fontFamilyValue.startsWith('"')
? fontFamilyValue
: `"${fontFamilyValue}"`;
textSpan.style.setProperty("font-family", quotedFontFamily);
}
fontFamily = sanitizeFontFamily(fontFamily);
textSpan.style.setProperty("font-family", fontFamily);
const fontWeight = textSpan.style.getPropertyValue("font-weight");
if (!fontWeight) {
console.warn("font-weight", fontWeight);
@@ -144,18 +145,29 @@ export function htmlToText(html) {
tmp.innerHTML = html;
const blockTags = [
"P", "DIV", "SECTION", "ARTICLE", "HEADER", "FOOTER",
"UL", "OL", "LI", "TABLE", "TR", "TD", "TH", "PRE"
"P",
"DIV",
"SECTION",
"ARTICLE",
"HEADER",
"FOOTER",
"UL",
"OL",
"LI",
"TABLE",
"TR",
"TD",
"TH",
"PRE",
];
function walk(node) {
let text = "";
node.childNodes.forEach(child => {
node.childNodes.forEach((child) => {
if (child.nodeType === Node.TEXT_NODE) {
text += child.textContent;
} else if (child.nodeType === Node.ELEMENT_NODE) {
if (child.tagName === "BR") {
text += "\n";
}
@@ -178,7 +190,6 @@ export function htmlToText(html) {
return result.trim();
}
/**
* Maps any HTML into a valid content DOM element.
*
@@ -187,10 +198,14 @@ export function htmlToText(html) {
* @param {boolean} [allowHTMLPaste=false]
* @returns {DocumentFragment}
*/
export function mapContentFragmentFromHTML(html, styleDefaults, allowHTMLPaste) {
export function mapContentFragmentFromHTML(
html,
styleDefaults,
allowHTMLPaste,
) {
if (allowHTMLPaste) {
try {
const parser = new DOMParser()
const parser = new DOMParser();
const document = parser.parseFromString(html, "text/html");
return mapContentFragmentFromDocument(document, styleDefaults);
} catch (error) {

View File

@@ -19,10 +19,31 @@ const DEFAULT_FONT_WEIGHT = "400";
* @param {string} value
*/
export function sanitizeFontFamily(value) {
if (value && value.length > 0 && !value.startsWith('"')) {
return `"${value}"`;
} else {
// NOTE: This is a fix for a bug introduced earlier that have might modified the font-family in the model
// adding extra double quotes.
if (value && value.startsWith('""')) {
//remove the first and last quotes
value = value.slice(1).replace(/"([^"]*)$/, "$1");
// remove quotes from font-family in 1-word font-families
// and repeated values
value = [
...new Set(
value
.split(", ")
.map((x) => (x.includes(" ") ? x : x.replace(/"/g, ""))),
),
].join(", ");
}
if (!value || value === "") {
return "var(--fallback-families)";
} else if (value.endsWith(" var(--fallback-families)")) {
return value;
} else if (value.startsWith('"')) {
return `${value}, var(--fallback-families)`;
} else {
return `"${value}", var(--fallback-families)`;
}
}