diff --git a/backend/src/app/rpc/commands/comments.clj b/backend/src/app/rpc/commands/comments.clj
index a11677e4a..60ff1fe5b 100644
--- a/backend/src/app/rpc/commands/comments.clj
+++ b/backend/src/app/rpc/commands/comments.clj
@@ -38,6 +38,8 @@
(def r-mentions-split #"@\[[^\]]*\]\([^\)]*\)")
(def r-mentions #"@\[([^\]]*)\]\(([^\)]*)\)")
+(def comment-max-length 750)
+
(defn- format-comment
[{:keys [content]}]
(->> (d/interleave-all
@@ -442,7 +444,7 @@
[:map {:title "create-comment-thread"}
[:file-id ::sm/uuid]
[:position ::gpt/point]
- [:content [:string {:max 750}]]
+ [:content [:string {:max comment-max-length}]]
[:page-id ::sm/uuid]
[:frame-id ::sm/uuid]
[:share-id {:optional true} [:maybe ::sm/uuid]]
@@ -585,7 +587,7 @@
schema:create-comment
[:map {:title "create-comment"}
[:thread-id ::sm/uuid]
- [:content [:string {:max 250}]]
+ [:content [:string {:max comment-max-length}]]
[:share-id {:optional true} [:maybe ::sm/uuid]]
[:mentions {:optional true} [::sm/set ::sm/uuid]]])
@@ -655,7 +657,7 @@
schema:update-comment
[:map {:title "update-comment"}
[:id ::sm/uuid]
- [:content [:string {:max 250}]]
+ [:content [:string {:max comment-max-length}]]
[:share-id {:optional true} [:maybe ::sm/uuid]]
[:mentions {:optional true} [::sm/set ::sm/uuid]]])
diff --git a/common/src/app/common/logging.cljc b/common/src/app/common/logging.cljc
index 77318c864..7a4df8ebe 100644
--- a/common/src/app/common/logging.cljc
+++ b/common/src/app/common/logging.cljc
@@ -275,7 +275,7 @@
[_ _ _ {:keys [::logger ::props ::level ::cause ::trace ::message]}]
(when (enabled? logger level)
(let [hstyles (str/ffmt "font-weight: 600; color: %" (level->color level))
- mstyles (str/ffmt "font-weight: 300; color: %" "#282a2e")
+ mstyles (str/ffmt "font-weight: 300; color: %" (level->color level))
header (str/concat "%c" (level->name level) " [" logger "] ")
message (str/concat header "%c" @message)]
diff --git a/docs/img/layers/copy-css.webp b/docs/img/layers/copy-css.webp
new file mode 100644
index 000000000..fd9422b47
Binary files /dev/null and b/docs/img/layers/copy-css.webp differ
diff --git a/docs/img/objects/board-copy-link.webp b/docs/img/objects/board-copy-link.webp
new file mode 100644
index 000000000..324833b3e
Binary files /dev/null and b/docs/img/objects/board-copy-link.webp differ
diff --git a/docs/img/styling/copy-properties.mp4 b/docs/img/styling/copy-properties.mp4
new file mode 100644
index 000000000..feb21994f
Binary files /dev/null and b/docs/img/styling/copy-properties.mp4 differ
diff --git a/docs/img/styling/copy-properties.webp b/docs/img/styling/copy-properties.webp
new file mode 100644
index 000000000..d2d77f458
Binary files /dev/null and b/docs/img/styling/copy-properties.webp differ
diff --git a/docs/user-guide/introduction/shortcuts.njk b/docs/user-guide/introduction/shortcuts.njk
index cfc2b5acf..71db0cbb6 100644
--- a/docs/user-guide/introduction/shortcuts.njk
+++ b/docs/user-guide/introduction/shortcuts.njk
@@ -130,6 +130,21 @@ title: Shortcuts
Ctrl Z
⌘ Z
+
+ Copy link to board
+ Shift Alt C
+ ⇧ Alt C
+
+
+ Copy properties
+ Ctrl Alt C
+ ⌘ ⌥ C
+
+
+ Paste properties
+ Ctrl Alt V
+ ⌘ ⌥ V
+
diff --git a/docs/user-guide/layer-basics/index.njk b/docs/user-guide/layer-basics/index.njk
index e02ee48d9..7888396c3 100644
--- a/docs/user-guide/layer-basics/index.njk
+++ b/docs/user-guide/layer-basics/index.njk
@@ -103,7 +103,6 @@ title: 04· Layer basics
At the dropdown menu (right click on a layer to show it) there's the option "Select layer" that allows the user to select one layer among the ones that are under the cursor's location.
-
Group layers
Grouped layers can be moved, transformed or styled at the same time.
@@ -128,7 +127,6 @@ title: 04· Layer basics
-
Move layers
To move one or more layers on the viewport you have to select them first and then click and drag the selection where you want to place them. You can also use the design panel to set a precise position relative to the viewport or the board.
@@ -137,7 +135,6 @@ title: 04· Layer basics
-
Resize layers
To resize a selected layer you can use the handles at the edges of the selection box. Make sure the cursor is in resizing mode. You can also use the design panel where you can link width and height.
@@ -204,6 +201,18 @@ title: 04· Layer basics
+Copy CSS properties
+To copy CSS properties from layers:
+
+ Select one or more layers.
+ Right click to show the layer menu.
+ Press Copy/Paste as... > Copy as CSS in case you only want to get the CSS properties from the selected layer/s.
+ Press Copy/Paste as... > Copy as CSS (nested layers) in case you only want to get the CSS properties from the selected layer/s and all the contained layers.
+
+
+
+
+
Collapse groups and boards
Groups and boards can have their contents expanded and collapsed. Click on the arrow at the
right side to toggle the visibility of their contents.
diff --git a/docs/user-guide/objects/index.njk b/docs/user-guide/objects/index.njk
index 7946dd9f3..80c1a33f0 100644
--- a/docs/user-guide/objects/index.njk
+++ b/docs/user-guide/objects/index.njk
@@ -60,6 +60,17 @@ are shown by default at the View mode , actin
+Copy link to board
+You can get the link to each individual board, making it easy to share them with team members or include direct links in documentation.
+
+
+
+There are two ways to copy a direct link to a board:
+
+ Using the menu: Select the board, right click and select the "Copy link" option.
+ Using the shortcut: Select the board and press Shift/⇧ + Alt/⌥ + C .
+
+
Clip content
Boards offer the option to clip its content (or not).
diff --git a/docs/user-guide/styling/index.njk b/docs/user-guide/styling/index.njk
index b54db7a09..928b4d351 100644
--- a/docs/user-guide/styling/index.njk
+++ b/docs/user-guide/styling/index.njk
@@ -181,4 +181,29 @@ title: 06· Styling
Saturation
Color
Luminosity
-
\ No newline at end of file
+
+
+Copy/Paste properties
+You can copy and apply properties, including fills, strokes, shadows, and others from one layer to another—or multiple layers with just a few clicks. You can do it using the layer's menu or shortcuts.
+
+
+
+
+
+
+
+Using the layer menu
+
+ Select one layer.
+ Right click to show the layer menu.
+ Press Copy/Paste as... > Copy properties .
+ Select one or more other layers.
+ Right click to show the layer/s menu.
+ Press Copy/Paste as... > Paste properties .
+
+
+Using Shortcuts
+
+ Copy properties : Ctrl/⌘ + Alt/⌥ + C
+ Paste properties : Ctrl/⌘ + Alt/⌥ + V
+
diff --git a/frontend/src/app/main/ui/comments.cljs b/frontend/src/app/main/ui/comments.cljs
index ff93524e4..c41e4273f 100644
--- a/frontend/src/app/main/ui/comments.cljs
+++ b/frontend/src/app/main/ui/comments.cljs
@@ -146,7 +146,7 @@
;; Input text for comments with mentions
(mf/defc comment-input*
{::mf/private true}
- [{:keys [value placeholder max-length autofocus on-focus on-blur on-change on-esc on-ctrl-enter]}]
+ [{:keys [value placeholder autofocus on-focus on-blur on-change on-esc on-ctrl-enter]}]
(let [value (d/nilv value "")
prev-value (h/use-previous value)
@@ -196,7 +196,7 @@
(dom/append-child! node (create-text-node)))
(let [new-input (parse-nodes node)]
- (when (and on-change (<= (count new-input) max-length))
+ (when on-change
(on-change new-input))))))
handle-select
@@ -637,6 +637,10 @@
:disabled is-disabled}
(tr "labels.post")]]))
+(defn- exceeds-length?
+ [content]
+ (> (count content) 750))
+
(mf/defc comment-reply-form*
{::mf/props :obj
::mf/private true}
@@ -644,7 +648,8 @@
(let [show-buttons? (mf/use-state false)
content (mf/use-state "")
- disabled? (blank-content? @content)
+ disabled? (or (blank-content? @content)
+ (exceeds-length? @content))
on-focus
(mf/use-fn
@@ -678,8 +683,10 @@
:on-blur on-blur
:on-focus on-focus
:on-ctrl-enter on-submit*
- :on-change on-change
- :max-length 750}]
+ :on-change on-change}]
+ (when (exceeds-length? @content)
+ [:div {:class (stl/css :error-text)}
+ (tr "errors.character-limit-exceeded")])
(when (or @show-buttons? (seq @content))
[:> comment-form-buttons* {:on-submit on-submit*
:on-cancel on-cancel
@@ -690,7 +697,8 @@
[{:keys [content on-submit on-cancel]}]
(let [content (mf/use-state content)
- disabled? (blank-content? @content)
+ disabled? (or (blank-content? @content)
+ (exceeds-length? @content))
on-change
(mf/use-fn
@@ -706,8 +714,10 @@
{:value @content
:autofocus true
:on-ctrl-enter on-submit*
- :on-change on-change
- :max-length 750}]
+ :on-change on-change}]
+ (when (exceeds-length? @content)
+ [:div {:class (stl/css :error-text)}
+ (tr "errors.character-limit-exceeded")])
[:> comment-form-buttons* {:on-submit on-submit*
:on-cancel on-cancel
:is-disabled disabled?}]]))
@@ -726,7 +736,8 @@
pos-x (* (:x position) zoom)
pos-y (* (:y position) zoom)
- disabled? (blank-content? content)
+ disabled? (or (blank-content? content)
+ (exceeds-length? content))
on-esc
(mf/use-fn
@@ -769,8 +780,10 @@
:autofocus true
:on-esc on-esc
:on-change on-change
- :on-ctrl-enter on-submit*
- :max-length 750}]
+ :on-ctrl-enter on-submit*}]
+ (when (exceeds-length? content)
+ [:div {:class (stl/css :error-text)}
+ (tr "errors.character-limit-exceeded")])
[:> comment-form-buttons* {:on-submit on-submit*
:on-cancel on-esc
:is-disabled disabled?}]]
diff --git a/frontend/src/app/main/ui/comments.scss b/frontend/src/app/main/ui/comments.scss
index 019120b39..70fd5b793 100644
--- a/frontend/src/app/main/ui/comments.scss
+++ b/frontend/src/app/main/ui/comments.scss
@@ -22,6 +22,11 @@
color: var(--comment-subtitle-color);
}
+.error-text {
+ @include bodySmallTypography;
+ color: var(--color-foreground-error);
+}
+
.location {
color: var(--comment-subtitle-color);
display: flex;
@@ -246,6 +251,7 @@
grid-template-columns: 1fr auto auto;
justify-content: flex-end;
gap: $s-8;
+ margin-top: $s-8;
}
.open-mentions-button {
@@ -321,7 +327,6 @@
border: $s-1 solid var(--input-border-color);
color: var(--input-foreground-color);
height: $s-36;
- margin-bottom: $s-8;
max-width: $s-260;
overflow-y: auto;
padding: $s-8;
diff --git a/frontend/text-editor/src/editor/content/dom/Content.js b/frontend/text-editor/src/editor/content/dom/Content.js
index 1b2ca84ed..9f3ed49ff 100644
--- a/frontend/text-editor/src/editor/content/dom/Content.js
+++ b/frontend/text-editor/src/editor/content/dom/Content.js
@@ -73,10 +73,11 @@ export function mapContentFragmentFromDocument(document, root, styleDefaults) {
currentParagraph = createParagraph(undefined, currentStyle);
}
}
-
const inline = createInline(new Text(currentNode.nodeValue), currentStyle);
const fontSize = inline.style.getPropertyValue("font-size");
if (!fontSize) console.warn("font-size", fontSize);
+ const fontFamily = inline.style.getPropertyValue("font-family");
+ if (!fontFamily) console.warn("font-family", fontFamily);
currentParagraph.appendChild(inline);
currentNode = nodeIterator.nextNode();
diff --git a/frontend/text-editor/src/editor/content/dom/Style.js b/frontend/text-editor/src/editor/content/dom/Style.js
index b7abe2f8b..d194a1336 100644
--- a/frontend/text-editor/src/editor/content/dom/Style.js
+++ b/frontend/text-editor/src/editor/content/dom/Style.js
@@ -23,7 +23,8 @@ export function mergeStyleDeclarations(target, source) {
// for (const styleName of source) {
for (let index = 0; index < source.length; index++) {
const styleName = source.item(index);
- target.setProperty(styleName, source.getPropertyValue(styleName));
+ const styleValue = source.getPropertyValue(styleName);
+ target.setProperty(styleName, styleValue);
}
return target
}
@@ -108,9 +109,10 @@ export function getComputedStyle(element) {
inertElement.style.setProperty(styleName, newValue);
}
} else {
+ const newValue = currentElement.style.getPropertyValue(styleName);
inertElement.style.setProperty(
styleName,
- currentElement.style.getPropertyValue(styleName)
+ newValue
);
}
}
@@ -130,9 +132,10 @@ export function getComputedStyle(element) {
* @returns {CSSStyleDeclaration}
*/
export function normalizeStyles(node, styleDefaults = getStyleDefaultsDeclaration()) {
+ const computedStyle = getComputedStyle(node.parentElement);
const styleDeclaration = mergeStyleDeclarations(
styleDefaults,
- getComputedStyle(node.parentElement)
+ computedStyle
);
// If there's a color property, we should convert it to
@@ -149,7 +152,7 @@ export function normalizeStyles(node, styleDefaults = getStyleDefaultsDeclaratio
// If there's a font-family property and not a --font-id, then
// we remove the font-family because it will not work.
const fontFamily = styleDeclaration.getPropertyValue("font-family");
- const fontId = styleDeclaration.getPropertyPriority("--font-id");
+ const fontId = styleDeclaration.getPropertyValue("--font-id");
if (fontFamily && !fontId) {
styleDeclaration.removeProperty("font-family");
}
diff --git a/frontend/translations/en.po b/frontend/translations/en.po
index e10f34d0c..27b3c0504 100644
--- a/frontend/translations/en.po
+++ b/frontend/translations/en.po
@@ -1152,6 +1152,10 @@ msgstr "The fonts %s could not be loaded"
msgid "errors.cannot-upload"
msgstr "Cannot upload the media file."
+#: src/app/main/ui/comments.cljs:689
+msgid "errors.character-limit-exceeded"
+msgstr "Character limit exceeded"
+
#: src/app/main/data/workspace.cljs:1463, src/app/main/data/workspace.cljs:1660
msgid "errors.clipboard-not-implemented"
msgstr "Your browser cannot do this operation"
@@ -3528,7 +3532,7 @@ msgstr "Copy"
#: src/app/main/ui/workspace/sidebar/shortcuts.cljs:94
msgid "shortcuts.copy-link"
-msgstr "Copy link to clipboard"
+msgstr "Copy link"
#: src/app/main/ui/workspace/sidebar/shortcuts.cljs:106
msgid "shortcuts.copy-props"
@@ -6188,7 +6192,7 @@ msgstr "Copy as CSS (nested layers)"
#: src/app/main/ui/workspace/context_menu.cljs:188
msgid "workspace.shape.menu.copy-link"
-msgstr "Copy link to clipboard"
+msgstr "Copy link"
#: src/app/main/ui/workspace/context_menu.cljs:201
msgid "workspace.shape.menu.copy-paste-as"
diff --git a/frontend/translations/es.po b/frontend/translations/es.po
index 191984820..642e22aff 100644
--- a/frontend/translations/es.po
+++ b/frontend/translations/es.po
@@ -1160,6 +1160,10 @@ msgstr "No se han podido cargar las fuentes %s"
msgid "errors.cannot-upload"
msgstr "No se puede cargar el archivo multimedia."
+#: src/app/main/ui/comments.cljs:689
+msgid "errors.character-limit-exceeded"
+msgstr "Se ha superado el límite de caracteres"
+
#: src/app/main/data/workspace.cljs:1463, src/app/main/data/workspace.cljs:1660
msgid "errors.clipboard-not-implemented"
msgstr "Tu navegador no puede realizar esta operación"
@@ -3524,7 +3528,7 @@ msgstr "Copiar"
#: src/app/main/ui/workspace/sidebar/shortcuts.cljs:94
msgid "shortcuts.copy-link"
-msgstr "Copiar enlace al portapapeles"
+msgstr "Copiar enlace"
#: src/app/main/ui/workspace/sidebar/shortcuts.cljs:95
msgid "shortcuts.create-component"
@@ -6186,7 +6190,7 @@ msgstr "Copiar como CSS (capas anidadas)"
#: src/app/main/ui/workspace/context_menu.cljs:188
msgid "workspace.shape.menu.copy-link"
-msgstr "Copiar enlace al portapapeles"
+msgstr "Copiar enlace"
#: src/app/main/ui/workspace/context_menu.cljs:201
msgid "workspace.shape.menu.copy-paste-as"