Compare commits
52 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
404297f837 | ||
|
|
33f853ff2e | ||
|
|
d16513be9d | ||
|
|
ad077696b0 | ||
|
|
1cbeafe85c | ||
|
|
76c8523f44 | ||
|
|
f277d8b125 | ||
|
|
60af8d0bcb | ||
|
|
d652ed8e68 | ||
|
|
09d73a2f51 | ||
|
|
7d4535ebd4 | ||
|
|
a5a53219bf | ||
|
|
8716f81765 | ||
|
|
7aa46a1f62 | ||
|
|
d62eb3d3f4 | ||
|
|
e4c427609d | ||
|
|
883a26845a | ||
|
|
bcdf5d86ae | ||
|
|
3eab9da74e | ||
|
|
2813fda136 | ||
|
|
a0022a804b | ||
|
|
068acb4303 | ||
|
|
d6f98a6c79 | ||
|
|
7b6c2da6da | ||
|
|
affed049ee | ||
|
|
377f636b8e | ||
|
|
2d512ef273 | ||
|
|
b8ebbe8c3c | ||
|
|
09c184200d | ||
|
|
bbe0b22a8b | ||
|
|
8603085a69 | ||
|
|
29ec44482d | ||
|
|
fd4d4ec6e3 | ||
|
|
0945dd2920 | ||
|
|
664cacbe9d | ||
|
|
08516ac7ca | ||
|
|
24e51eef5b | ||
|
|
ee62016c34 | ||
|
|
7c10f20b95 | ||
|
|
4958da63e5 | ||
|
|
f39a994fed | ||
|
|
5ef59d5e2e | ||
|
|
98221c6b51 | ||
|
|
74713cde63 | ||
|
|
a5084c35b5 | ||
|
|
cdce1df919 | ||
|
|
c75b886548 | ||
|
|
abd41e825e | ||
|
|
97b9a7d31c | ||
|
|
dbeebf181f | ||
|
|
0eec09acbf | ||
|
|
81e250e27d |
@@ -1,5 +1,11 @@
|
||||
# CHANGELOG
|
||||
|
||||
## 2.4.1
|
||||
|
||||
### :bug: Bugs fixed
|
||||
|
||||
- Fix error when importing files with touched components [Taiga #9625](https://tree.taiga.io/project/penpot/issue/9625)
|
||||
|
||||
## 2.4.0
|
||||
|
||||
### :rocket: Epics and highlights
|
||||
@@ -31,6 +37,8 @@
|
||||
### :bug: Bugs fixed
|
||||
|
||||
- Fix problem with some texts desynchronization [Taiga #9379](https://tree.taiga.io/project/penpot/issue/9379)
|
||||
- Fix problem with reoder grid layers [#5446](https://github.com/penpot/penpot/issues/5446)
|
||||
- Fix problem with swap component style [#9542](https://tree.taiga.io/project/penpot/issue/9542)
|
||||
|
||||
## 2.3.3
|
||||
|
||||
|
||||
@@ -273,7 +273,8 @@
|
||||
(merge {:viewed-tutorial? false
|
||||
:viewed-walkthrough? false
|
||||
:nudge {:big 10 :small 1}
|
||||
:v2-info-shown true})
|
||||
:v2-info-shown true
|
||||
:release-notes-viewed (:main cf/version)})
|
||||
(db/tjson))
|
||||
|
||||
password (or (:password params) "!")
|
||||
|
||||
@@ -28,13 +28,19 @@
|
||||
[cuerdas.core :as str]))
|
||||
|
||||
(def sql:get-file-snapshots
|
||||
"SELECT id, label, revn, created_at, created_by, profile_id
|
||||
FROM file_change
|
||||
WHERE file_id = ?
|
||||
AND data IS NOT NULL
|
||||
AND (deleted_at IS NULL OR deleted_at > now())
|
||||
ORDER BY created_at DESC
|
||||
LIMIT 20")
|
||||
"WITH changes AS (
|
||||
SELECT id, label, revn, created_at, created_by, profile_id
|
||||
FROM file_change
|
||||
WHERE file_id = ?
|
||||
AND data IS NOT NULL
|
||||
AND (deleted_at IS NULL OR deleted_at > now())
|
||||
), versions AS (
|
||||
(SELECT * FROM changes WHERE created_by = 'system' LIMIT 1000)
|
||||
UNION ALL
|
||||
(SELECT * FROM changes WHERE created_by != 'system' LIMIT 1000)
|
||||
)
|
||||
SELECT * FROM versions
|
||||
ORDER BY created_at DESC;")
|
||||
|
||||
(defn get-file-snapshots
|
||||
[conn file-id]
|
||||
|
||||
@@ -402,7 +402,10 @@
|
||||
|
||||
[cfg {:keys [::rpc/profile-id file-id] :as params}]
|
||||
(db/tx-run! cfg (fn [{:keys [::db/conn] :as cfg}]
|
||||
(files/check-edition-permissions! conn profile-id file-id)
|
||||
;; TODO For now we check read permissions instead of write,
|
||||
;; to allow viewer users to update thumbnails. We might
|
||||
;; review this approach on the future.
|
||||
(files/check-read-permissions! conn profile-id file-id)
|
||||
(when-not (db/read-only? conn)
|
||||
(let [media (create-file-thumbnail! cfg params)]
|
||||
{:uri (files/resolve-public-uri (:id media))
|
||||
|
||||
@@ -166,23 +166,26 @@
|
||||
;; invited team.
|
||||
(let [props {:team-id (:team-id claims)
|
||||
:role (:role claims)
|
||||
:invitation-id (:id invitation)}
|
||||
:invitation-id (:id invitation)}]
|
||||
|
||||
accept-invitation-event
|
||||
(-> (audit/event-from-rpc-params params)
|
||||
(assoc ::audit/name "accept-team-invitation")
|
||||
(assoc ::audit/props props))
|
||||
(audit/submit!
|
||||
cfg
|
||||
(-> (audit/event-from-rpc-params params)
|
||||
(assoc ::audit/name "accept-team-invitation")
|
||||
(assoc ::audit/props props)))
|
||||
|
||||
accept-invitation-from-event
|
||||
(-> (audit/event-from-rpc-params params)
|
||||
(assoc ::audit/profile-id (:created-by invitation))
|
||||
(assoc ::audit/name "accept-team-invitation-from")
|
||||
(assoc ::audit/props (assoc props
|
||||
:profile-id (:id profile)
|
||||
:email (:email profile))))]
|
||||
|
||||
(audit/submit! cfg accept-invitation-event)
|
||||
(audit/submit! cfg accept-invitation-from-event)
|
||||
;; NOTE: Backward compatibility; old invitations can
|
||||
;; have the `created-by` to be nil; so in this case we
|
||||
;; don't submit this event to the audit-log
|
||||
(when-let [created-by (:created-by invitation)]
|
||||
(audit/submit!
|
||||
cfg
|
||||
(-> (audit/event-from-rpc-params params)
|
||||
(assoc ::audit/profile-id created-by)
|
||||
(assoc ::audit/name "accept-team-invitation-from")
|
||||
(assoc ::audit/props (assoc props
|
||||
:profile-id (:id profile)
|
||||
:email (:email profile))))))
|
||||
|
||||
(accept-invitation cfg claims invitation profile)
|
||||
(assoc claims :state :created))
|
||||
|
||||
@@ -6,4 +6,4 @@
|
||||
|
||||
(ns app.common.files.defaults)
|
||||
|
||||
(def version 57)
|
||||
(def version 59)
|
||||
|
||||
@@ -1130,6 +1130,21 @@
|
||||
(update :pages-index dissoc nil)
|
||||
(update :pages-index update-vals update-page))))
|
||||
|
||||
(defn migrate-up-59
|
||||
[data]
|
||||
(letfn [(fix-touched [elem]
|
||||
(cond-> elem (string? elem) keyword))
|
||||
|
||||
(update-shape [shape]
|
||||
(d/update-when shape :touched #(into #{} (map fix-touched) %)))
|
||||
|
||||
(update-container [container]
|
||||
(d/update-when container :objects update-vals update-shape))]
|
||||
|
||||
(-> data
|
||||
(update :pages-index update-vals update-container)
|
||||
(update :components update-vals update-container))))
|
||||
|
||||
(def migrations
|
||||
"A vector of all applicable migrations"
|
||||
[{:id 2 :migrate-up migrate-up-2}
|
||||
@@ -1178,5 +1193,6 @@
|
||||
{:id 54 :migrate-up migrate-up-54}
|
||||
{:id 55 :migrate-up migrate-up-55}
|
||||
{:id 56 :migrate-up migrate-up-56}
|
||||
{:id 57 :migrate-up migrate-up-57}])
|
||||
{:id 57 :migrate-up migrate-up-57}
|
||||
{:id 59 :migrate-up migrate-up-59}])
|
||||
|
||||
|
||||
@@ -304,7 +304,9 @@
|
||||
(->> ids
|
||||
(mapcat #(ctn/get-child-heads objects %))
|
||||
(map :id)))
|
||||
cell (or cell (ctl/get-cell-by-index parent to-index))]
|
||||
|
||||
index-cell-data (when to-index (ctl/get-cell-by-index parent to-index))
|
||||
cell (or cell (and index-cell-data [(:row index-cell-data) (:column index-cell-data)]))]
|
||||
|
||||
(-> changes
|
||||
(pcb/with-page-id page-id)
|
||||
|
||||
@@ -191,7 +191,8 @@
|
||||
[:grow-type {:optional true}
|
||||
[::sm/one-of grow-types]]
|
||||
[:applied-tokens {:optional true} ::cto/applied-tokens]
|
||||
[:plugin-data {:optional true} ::ctpg/plugin-data]])
|
||||
[:plugin-data {:optional true} ::ctpg/plugin-data]
|
||||
[:touched {:optional true} [:maybe [:set :keyword]]]])
|
||||
|
||||
(def schema:group-attrs
|
||||
[:map {:title "GroupAttrs"}
|
||||
|
||||
@@ -1479,7 +1479,7 @@
|
||||
(defn get-cell-by-index
|
||||
[parent to-index]
|
||||
(let [cells (get-cells parent {:sort? true :remove-empty? true})
|
||||
to-index (- (count cells) to-index)]
|
||||
to-index (- (count cells) to-index 1)]
|
||||
(nth cells to-index nil)))
|
||||
|
||||
(defn add-children-to-index
|
||||
|
||||
@@ -90,6 +90,7 @@ http {
|
||||
proxy_hide_header x-amz-meta-server-side-encryption;
|
||||
proxy_hide_header x-amz-server-side-encryption;
|
||||
proxy_pass $redirect_uri;
|
||||
proxy_ssl_server_name on;
|
||||
|
||||
add_header x-internal-redirect "$redirect_uri";
|
||||
add_header x-cache-control "$redirect_cache_control";
|
||||
|
||||
@@ -92,6 +92,7 @@ http {
|
||||
proxy_hide_header x-amz-request-id;
|
||||
proxy_hide_header x-amz-meta-server-side-encryption;
|
||||
proxy_hide_header x-amz-server-side-encryption;
|
||||
proxy_ssl_server_name on;
|
||||
proxy_pass $redirect_uri;
|
||||
|
||||
add_header x-internal-redirect "$redirect_uri";
|
||||
|
||||
|
Before Width: | Height: | Size: 16 KiB After Width: | Height: | Size: 18 KiB |
|
Before Width: | Height: | Size: 6.5 KiB After Width: | Height: | Size: 6.0 KiB |
BIN
docs/img/styling/blend-opacity.webp
Normal file
|
After Width: | Height: | Size: 3.6 KiB |
BIN
docs/img/workspace-basics/history-actions.webp
Normal file
|
After Width: | Height: | Size: 9.4 KiB |
BIN
docs/img/workspace-basics/history-autosaved.webp
Normal file
|
After Width: | Height: | Size: 9.9 KiB |
|
Before Width: | Height: | Size: 12 KiB |
BIN
docs/img/workspace-basics/history-pin.webp
Normal file
|
After Width: | Height: | Size: 9.4 KiB |
BIN
docs/img/workspace-basics/history-restore.webp
Normal file
|
After Width: | Height: | Size: 10 KiB |
BIN
docs/img/workspace-basics/history-save.webp
Normal file
|
After Width: | Height: | Size: 9.2 KiB |
BIN
docs/img/workspace-basics/history-view.webp
Normal file
|
After Width: | Height: | Size: 4.6 KiB |
|
Before Width: | Height: | Size: 10 KiB |
@@ -216,3 +216,9 @@ Success! - Published to example-plugin-penpot.surge.sh
|
||||
```
|
||||
|
||||
5. Done!
|
||||
|
||||
## 3.5. Submitting to Penpot
|
||||
|
||||
To make your finished plugin available in our catalog, submit in on the [plugin submission page](https://penpot.app/penpothub/plugins/create-plugin). Once it becomes available any Penpot user will be able to install and use it.
|
||||
|
||||
|
||||
|
||||
@@ -5,45 +5,25 @@ title: 14· Import/export files
|
||||
<h1 id="import-export">Import and export files</h1>
|
||||
<p class="main-paragraph">You can export Penpot files to your computer and import them from your computer to your projects.</p>
|
||||
|
||||
<h2 id="penpot-formats">Penpot file formats</h2>
|
||||
<p>There are two different formats in which you can import/export Penpot files. A standard one and a binary one. You always have the chance to use both for any file.</p>
|
||||
<h3>Penpot file (.penpot).</h3>
|
||||
<p>The fast one. Binary Penpot specific.</p>
|
||||
<ul>
|
||||
<li>✅ Highly efficient in terms of memory and transfer time when exporting and importing.</li>
|
||||
<li>❌ It can be opened only in Penpot.</li>
|
||||
<li>❌ Not transparent, code difficult to explore.</li>
|
||||
</ul>
|
||||
<h3>Standard file (.zip).</h3>
|
||||
<p>The open one. A compressed file that includes SVG and JSON.</p>
|
||||
<ul>
|
||||
<li>✅ Allows the file to be opened by other softwares (still, for those cases export to SVG seems to be the common practice).</li>
|
||||
<li>✅ Allows some automations and integrations.</li>
|
||||
<li>✅ Is a transparent, existing, open standard format.</li>
|
||||
<li>❌ Highly inefficient in terms of memory and transfer time when exporting and importing (this is because SVG).</li>
|
||||
</ul>
|
||||
|
||||
<h2 id="files-export">Export Penpot files</h2>
|
||||
<p>Exporting files is useful for many reasons. Sometimes you want to have a backup of your files and sometimes it is useful to share Penpot files with a user that does not belong to one of your teams, or you want to have a backup of your files outside Penpot, both SaaS (design.penpot.app) or at a self-hosted instance.</p>
|
||||
|
||||
<h3 id="export-penpot-files">How to export Penpot files</h3>
|
||||
<h4>Export a single file</h4>
|
||||
<p>You can download (export) files from the workspace and from the dashboard.</p>
|
||||
<ul>
|
||||
<li>
|
||||
<strong>From the <a href="/user-guide/the-interface/#interface-workspace">workspace</a></strong>: Select the download option at the main menu.
|
||||
<figure><img src="/img/import-export/export-card.webp" alt="Export penpot file" /></figure>
|
||||
</li>
|
||||
<li>
|
||||
<strong>From the <a href="/user-guide/the-interface/#interface-dashboard">dashboard</a></strong>: Select the download option at the file card menu.
|
||||
<figure><img src="/img/import-export/export-menu.webp" alt="Export penpot file" /></figure>
|
||||
</li>
|
||||
</ul>
|
||||
<p>
|
||||
<strong>From the <a href="/user-guide/the-interface/#interface-dashboard">dashboard</a></strong>: Select the download option at the file card menu.
|
||||
<figure><img src="/img/import-export/export-card.webp" alt="Export penpot file" /></figure>
|
||||
</p>
|
||||
<p>
|
||||
<strong>From the <a href="/user-guide/the-interface/#interface-workspace">workspace</a></strong>: Select the download option at the main menu.
|
||||
<figure><img src="/img/import-export/export-menu.webp" alt="Export penpot file" /></figure>
|
||||
</p>
|
||||
|
||||
<h4>Export multiple files</h4>
|
||||
<p>Select multiple files to export them at the same time. An overlay will show you the progress of the different exports.</p>
|
||||
<figure>
|
||||
<video title="Export multiple files" muted="" playsinline="" controls="" width="100%" poster="/img/import-export/export-multiple.webp" height="auto">
|
||||
<video title="Export multiple files" muted="" playsinline="" controls="" width="auto" poster="/img/import-export/export-multiple.webp" height="auto">
|
||||
<source src="/img/import-export/export-multiple.mp4" type="video/mp4">
|
||||
</video>
|
||||
</figure>
|
||||
@@ -63,4 +43,27 @@ title: 14· Import/export files
|
||||
<p>The import option is at the projects menu. Press “Import files” and then select one or more .penpot files to import. You can import a .zip file as well.</p>
|
||||
<figure><img src="/img/import-export/import-menu.webp" alt="Import penpot file" /></figure>
|
||||
<p>Right before importing the files to your project, you’ll still have the opportunity to review the items to be imported, have the information about the ones that can not be imported and also the chance to discard files.</p>
|
||||
<figure><img src="/img/import-export/import-selection.webp" alt="Import penpot file" /></figure
|
||||
<figure><img src="/img/import-export/import-selection.webp" alt="Import penpot file" /></figure>
|
||||
|
||||
<h2 id="penpot-formats">Penpot file format</h2>
|
||||
<p>Penpot export to a unique format that streamline the import and export of files and assets by being more efficient and interoperable.</p>
|
||||
<p>Unlike other design tools, <strong>Penpot's format is built on standard languages</strong>. The exported file is essentially a ZIP archive containing binary assets (such as bitmap and vector images) alongside a readable JSON structure. By avoiding proprietary formats, Penpot empowers users with autonomy from specific tools while enabling seamless third-party integrations.</p>
|
||||
|
||||
<h3>Deprecated Penpot file formats</h3>
|
||||
<p class="advice">These formats can only be exported from version 2.3 or earlier versions, but can be imported to any Penpot version</p>
|
||||
<p>There are two different deprecated Penpot file formats in which you can import/export Penpot files. A standard one and a binary one. You always have the chance to use both for any file.</p>
|
||||
<h4>[Deprecated] Penpot file (.penpot).</h4>
|
||||
<p>The fast one. Binary Penpot specific.</p>
|
||||
<ul>
|
||||
<li>✅ Highly efficient in terms of memory and transfer time when exporting and importing.</li>
|
||||
<li>❌ It can be opened only in Penpot.</li>
|
||||
<li>❌ Not transparent, code difficult to explore.</li>
|
||||
</ul>
|
||||
<h4>[Deprecated] Standard file (.zip).</h4>
|
||||
<p>The open one. A compressed file that includes SVG and JSON.</p>
|
||||
<ul>
|
||||
<li>✅ Allows the file to be opened by other softwares (still, for those cases export to SVG seems to be the common practice).</li>
|
||||
<li>✅ Allows some automations and integrations.</li>
|
||||
<li>✅ Is a transparent, existing, open standard format.</li>
|
||||
<li>❌ Highly inefficient in terms of memory and transfer time when exporting and importing (this is because SVG).</li>
|
||||
</ul>
|
||||
@@ -424,11 +424,6 @@ title: Shortcuts
|
||||
<td style="text-align: center;"><kbd>Alt</kbd><kbd>P</kbd></td>
|
||||
<td style="text-align: center;"><kbd>⌥</kbd><kbd>P</kbd></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>History</td>
|
||||
<td style="text-align: center;"><kbd>Alt</kbd><kbd>H</kbd></td>
|
||||
<td style="text-align: center;"><kbd>⌥</kbd><kbd>H</kbd></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Layers</td>
|
||||
<td style="text-align: center;"><kbd>Alt</kbd><kbd>L</kbd></td>
|
||||
|
||||
@@ -155,4 +155,30 @@ title: 06· Styling
|
||||
<video title="Apply blur to a layer" muted="" playsinline="" controls="" width="100%" poster="/img/styling/blur.webp" height="auto">
|
||||
<source src="/img/styling/blur.mp4" type="video/mp4">
|
||||
</video>
|
||||
</figure>
|
||||
</figure>
|
||||
|
||||
<h2 id="blend">Opacity and blend</h2>
|
||||
<p>Set the overal opacity for layers and their blend mode.</p>
|
||||
<p>Blend allows you to control how a layer interacts with the layers beneath it, determining how pixels from the current layer are combined with pixels in the underlying layers. Use blend to achive various effects, such as shading, highlights, or creative visual styles.</p>
|
||||
<figure>
|
||||
<img alt="Layer blend and opacity" src="/img/styling/blend-opacity.webp"/>
|
||||
</figure>
|
||||
<p>Blend options available:</p>
|
||||
<ul>
|
||||
<li><strong>Normal</strong></li>
|
||||
<li><strong>Darken</strong></li>
|
||||
<li><strong>Multiply</strong></li>
|
||||
<li><strong>Color burn</strong></li>
|
||||
<li><strong>Lighten</strong></li>
|
||||
<li><strong>Screen</strong></li>
|
||||
<li><strong>Color dodge</strong></li>
|
||||
<li><strong>Overlay</strong></li>
|
||||
<li><strong>Soft light</strong></li>
|
||||
<li><strong>Hard light</strong></li>
|
||||
<li><strong>Difference</strong></li>
|
||||
<li><strong>Exclusion</strong></li>
|
||||
<li><strong>Hue</strong></li>
|
||||
<li><strong>Saturation</strong></li>
|
||||
<li><strong>Color</strong></li>
|
||||
<li><strong>Luminosity</strong></li>
|
||||
</ul>
|
||||
@@ -199,26 +199,58 @@ geometric structure. In Penpot there are three types of guides:
|
||||
<img src="/img/workspace-basics/shortcuts.webp" alt="Shortcuts panel" />
|
||||
</figure>
|
||||
|
||||
<h2 id="history">History</h2>
|
||||
<p>The history panel keeps track of the latest changes on an opened file.</p>
|
||||
<h2 id="history">File history versions</h2>
|
||||
<p>The history panel keeps track of the latest changes on an opened file as well as the different versions of the file, making it easier to track changes, revert to previous states and collaborate.</p>
|
||||
|
||||
<h4>View history</h4>
|
||||
<p>To view the recent history of a file at the workspace click at the history icon on the toolbar at the left.</p>
|
||||
<p>At the history you can see items with information about the last changes. At first sight you have object type (rectangle, text, image...) and type of change (New, Modified, Deleted...). If you press the item further details are shown.</p>
|
||||
<h3>View history</h3>
|
||||
<p>To view the recent history of a file at the workspace click the history icon on the navbar at the left:</p>
|
||||
<ul>
|
||||
<li>To see the history of file versions go to the <strong>History</strong> tab.</li>
|
||||
<li>To see the history of item changes go to the <strong>Actions</strong> tab.</li>
|
||||
</ul>
|
||||
<figure>
|
||||
<img src="/img/workspace-basics/history.webp" alt="History panel" />
|
||||
<img src="/img/workspace-basics/history-view.webp" alt="History versions button" />
|
||||
</figure>
|
||||
<p><strong>Note:</strong> History panel is still in a very early state and shows only a limited list of changes at a current browser tab session. Refreshing the browser means refreshing the History as well. Eventually, Penpot will have a proper version history capacity.</p>
|
||||
|
||||
<h4>Navigate history</h4>
|
||||
<p>To navigate through the history press <kbd>Ctrl/⌘</kbd> + <kbd>Z</kbd> to go backwards and <kbd>Ctrl/⌘</kbd> + <kbd>Shift/⇧</kbd> + <kbd>Z</kbd> to go forward.</p>
|
||||
<p>You can also press any item of the history list to get to this specific state.</p>
|
||||
<h3>History panel</h3>
|
||||
<p>At the History panel, you can save the current version of your file, as well as access previous versions for up to 7 days.</p>
|
||||
|
||||
<h4>Restore versions</h4>
|
||||
<p>All saved versions of the file—whether manually saved, autosaved, or pinned—can be restored, reverting the file back to its state at the selected time.</p>
|
||||
<figure>
|
||||
<video title="Navigate history" muted="" playsinline="" controls="" width="auto" poster="/img/workspace-basics/history-navigate.webp" height="auto">
|
||||
<source src="/img/workspace-basics/history-navigate.mp4" type="video/mp4">
|
||||
</video>
|
||||
<img src="/img/workspace-basics/history-restore.webp" alt="Restore versions" />
|
||||
</figure>
|
||||
|
||||
<h4>Saved versions</h4>
|
||||
<p>You can save the current version of your file by clicking the pin icon at the History tab. This will allow the version to be named and it will add it to your list of versions.</p>
|
||||
<figure>
|
||||
<img src="/img/workspace-basics/history-save.webp" alt="Saved versions" />
|
||||
</figure>
|
||||
|
||||
<h4>Autosaved versions</h4>
|
||||
<p>When you start working on a file, Penpot will start to automatically save versions of that file across time so that you can later restore them as needed.</p>
|
||||
<p>In the History tab, if you click on the autosaved versions, you’ll see a list of the exact date and time when the version was automatically saved.</p>
|
||||
<figure>
|
||||
<img src="/img/workspace-basics/history-autosaved.webp" alt="Autosaved versions" />
|
||||
</figure>
|
||||
|
||||
<h4>Pinned versions</h4>
|
||||
<p>File versions can also be pinned. Pinning a file version will allow you to name it, making it easier to access at the History tab. Pinned file versions will be saved forever and can be renamed, restored or deleted at any time.</p>
|
||||
<figure>
|
||||
<img src="/img/workspace-basics/history-pin.webp" alt="Pin versions" />
|
||||
</figure>
|
||||
|
||||
<h3>Actions panel</h3>
|
||||
<p>At the Actions panel, you have the object type (rectangle, text, image...) and type of change (New, Modified, Deleted...). If you press the item, it will be reverted to its state before that specific action was performed.</p>
|
||||
<figure>
|
||||
<img src="/img/workspace-basics/history-actions.webp" alt="Actions panel" />
|
||||
</figure>
|
||||
<p class="advice">The Actions panel shows only a limited list of changes at a current browser tab session. Refreshing the browser means refreshing the history of actions as well.</p>
|
||||
|
||||
<h4>Navigate actions</h4>
|
||||
<p>To navigate through the actions press <kbd>Ctrl/⌘</kbd> + <kbd>Z</kbd> to go backwards and <kbd>Ctrl/⌘</kbd> + <kbd>Shift/⇧</kbd> + <kbd>Z</kbd> to go forward.</p>
|
||||
<p>You can also press any item of the actions list to get to this specific state.</p>
|
||||
|
||||
<h2 id="comments">Comments</h2>
|
||||
<p>Comments allow the team to have one priceless conversation getting and providing feedback right over the designs and prototypes.<p>
|
||||
|
||||
|
||||
3392
frontend/externs/main.txt
Normal file
1
frontend/externs/worker.txt
Symbolic link
@@ -0,0 +1 @@
|
||||
main.txt
|
||||
BIN
frontend/resources/images/features/2.4-format.gif
Normal file
|
After Width: | Height: | Size: 212 KiB |
BIN
frontend/resources/images/features/2.4-history.gif
Normal file
|
After Width: | Height: | Size: 224 KiB |
BIN
frontend/resources/images/features/2.4-slide-0.jpg
Normal file
|
After Width: | Height: | Size: 47 KiB |
BIN
frontend/resources/images/features/2.4-viewer.gif
Normal file
|
After Width: | Height: | Size: 30 KiB |
@@ -143,6 +143,11 @@
|
||||
(let [f (obj/get global "externalSessionId")]
|
||||
(when (fn? f) (f))))
|
||||
|
||||
(defn external-context-info
|
||||
[]
|
||||
(let [f (obj/get global "externalContextInfo")]
|
||||
(when (fn? f) (f))))
|
||||
|
||||
;; --- Helper Functions
|
||||
|
||||
(defn ^boolean check-browser? [candidate]
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
(:require
|
||||
["ua-parser-js" :as ua]
|
||||
[app.common.data :as d]
|
||||
[app.common.json :as json]
|
||||
[app.common.logging :as l]
|
||||
[app.config :as cf]
|
||||
[app.main.repo :as rp]
|
||||
@@ -93,6 +94,11 @@
|
||||
data
|
||||
data))
|
||||
|
||||
(defn add-external-context-info
|
||||
[context]
|
||||
(let [external-context-info (json/->clj (cf/external-context-info))]
|
||||
(merge context external-context-info)))
|
||||
|
||||
(defn- process-event-by-proto
|
||||
[event]
|
||||
(let [data (d/deep-merge (-data event) (meta event))
|
||||
@@ -102,6 +108,7 @@
|
||||
(assoc :event-origin (::origin data))
|
||||
(assoc :event-namespace (namespace type))
|
||||
(assoc :event-symbol ev-name)
|
||||
(add-external-context-info)
|
||||
(d/without-nils))
|
||||
props (-> data d/without-qualified simplify-props)]
|
||||
|
||||
@@ -119,6 +126,7 @@
|
||||
(let [type (::type data "action")
|
||||
context (-> (::context data)
|
||||
(assoc :event-origin (::origin data))
|
||||
(add-external-context-info)
|
||||
(d/without-nils))
|
||||
props (-> data d/without-qualified simplify-props)]
|
||||
{:type type
|
||||
|
||||
@@ -52,6 +52,11 @@
|
||||
(defonce queue
|
||||
(q/create find-request (/ 1000 30)))
|
||||
|
||||
(defn clear-queue!
|
||||
[]
|
||||
(l/dbg :hint "clearing thumbnail queue")
|
||||
(q/clear! queue))
|
||||
|
||||
;; This function first renders the HTML calling `render/render-frame` that
|
||||
;; returns HTML as a string, then we send that data to the iframe rasterizer
|
||||
;; that returns the image as a Blob. Finally we create a URI for that blob.
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
[app.main.data.events :as ev]
|
||||
[app.main.data.persistence :as dwp]
|
||||
[app.main.data.workspace :as dw]
|
||||
[app.main.data.workspace.thumbnails :as th]
|
||||
[app.main.refs :as refs]
|
||||
[app.main.repo :as rp]
|
||||
[app.util.time :as dt]
|
||||
@@ -132,6 +133,7 @@
|
||||
(rx/filter #(or (nil? %) (= :saved %)))
|
||||
(rx/take 1)
|
||||
(rx/mapcat #(rp/cmd! :restore-file-snapshot {:file-id file-id :id id}))
|
||||
(rx/tap #(th/clear-queue!))
|
||||
(rx/map #(dw/initialize-file project-id file-id)))
|
||||
(case origin
|
||||
:version
|
||||
|
||||
@@ -67,6 +67,7 @@
|
||||
|
||||
show-release-modal?
|
||||
(and (contains? cf/flags :onboarding)
|
||||
(not (contains? cf/flags :hide-release-modal))
|
||||
(:onboarding-viewed props)
|
||||
(not= (:release-notes-viewed props) (:main cf/version))
|
||||
(not= "0.0" (:main cf/version)))]
|
||||
@@ -106,7 +107,7 @@
|
||||
:dashboard-team-webhooks
|
||||
:dashboard-team-settings)
|
||||
[:?
|
||||
#_[:& app.main.ui.releases/release-notes-modal {:version "2.3"}]
|
||||
#_[:& app.main.ui.releases/release-notes-modal {:version "2.4"}]
|
||||
#_[:& app.main.ui.onboarding/onboarding-templates-modal]
|
||||
#_[:& app.main.ui.onboarding/onboarding-modal]
|
||||
#_[:& app.main.ui.onboarding.team-choice/onboarding-team-modal]
|
||||
|
||||
@@ -30,6 +30,7 @@
|
||||
[app.main.ui.releases.v2-1]
|
||||
[app.main.ui.releases.v2-2]
|
||||
[app.main.ui.releases.v2-3]
|
||||
[app.main.ui.releases.v2-4]
|
||||
[app.util.object :as obj]
|
||||
[app.util.timers :as tm]
|
||||
[rumext.v2 :as mf]))
|
||||
@@ -94,4 +95,4 @@
|
||||
|
||||
(defmethod rc/render-release-notes "0.0"
|
||||
[params]
|
||||
(rc/render-release-notes (assoc params :version "2.2")))
|
||||
(rc/render-release-notes (assoc params :version "2.4")))
|
||||
|
||||
141
frontend/src/app/main/ui/releases/v2_4.cljs
Normal file
@@ -0,0 +1,141 @@
|
||||
;; This Source Code Form is subject to the terms of the Mozilla Public
|
||||
;; License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
;;
|
||||
;; Copyright (c) KALEIDOS INC
|
||||
|
||||
(ns app.main.ui.releases.v2-4
|
||||
(:require-macros [app.main.style :as stl])
|
||||
(:require
|
||||
[app.common.data.macros :as dm]
|
||||
[app.main.ui.releases.common :as c]
|
||||
[rumext.v2 :as mf]))
|
||||
|
||||
;; TODO: Review all copies and alt text
|
||||
(defmethod c/render-release-notes "2.4"
|
||||
[{:keys [slide klass next finish navigate version]}]
|
||||
(mf/html
|
||||
(case slide
|
||||
:start
|
||||
[:div {:class (stl/css-case :modal-overlay true)}
|
||||
[:div.animated {:class klass}
|
||||
[:div {:class (stl/css :modal-container)}
|
||||
[:img {:src "images/features/2.4-slide-0.jpg"
|
||||
:class (stl/css :start-image)
|
||||
:border "0"
|
||||
:alt "A graphic illustration with Penpot style"}]
|
||||
|
||||
[:div {:class (stl/css :modal-content)}
|
||||
[:div {:class (stl/css :modal-header)}
|
||||
[:h1 {:class (stl/css :modal-title)}
|
||||
"What’s new in Penpot?"]
|
||||
|
||||
[:div {:class (stl/css :version-tag)}
|
||||
(dm/str "Version " version)]]
|
||||
|
||||
[:div {:class (stl/css :features-block)}
|
||||
[:span {:class (stl/css :feature-title)}
|
||||
"At Penpot we are at full speed!"]
|
||||
|
||||
[:p {:class (stl/css :feature-content)}
|
||||
"With the release of the long-awaited Plugins System still fresh, this 2.4 brings improvements in a wide range of areas that will serve a variety of use cases."]
|
||||
|
||||
[:p {:class (stl/css :feature-content)}
|
||||
"This release combines some of the most requested features—such as versioning and the viewer-only role—with performance improvements and a new .penpot format that will streamline the export of files and assets."]
|
||||
|
||||
[:p {:class (stl/css :feature-content)}
|
||||
"Let’s dive in!"]]
|
||||
|
||||
[:div {:class (stl/css :navigation)}
|
||||
[:button {:class (stl/css :next-btn)
|
||||
:on-click next} "Continue"]]]]]]
|
||||
|
||||
0
|
||||
[:div {:class (stl/css-case :modal-overlay true)}
|
||||
[:div.animated {:class klass}
|
||||
[:div {:class (stl/css :modal-container)}
|
||||
[:img {:src "images/features/2.4-viewer.gif"
|
||||
:class (stl/css :start-image)
|
||||
:border "0"
|
||||
:alt "Viewer role, designed to enhance collaboration"}]
|
||||
|
||||
[:div {:class (stl/css :modal-content)}
|
||||
[:div {:class (stl/css :modal-header)}
|
||||
[:h1 {:class (stl/css :modal-title)}
|
||||
"Viewer role, designed to enhance collaboration"]]
|
||||
|
||||
[:div {:class (stl/css :feature)}
|
||||
[:p {:class (stl/css :feature-content)}
|
||||
"Collaboration takes many forms, and sometimes the risk of making unwanted or accidental adjustments can be a barrier to engaging with a design file."]
|
||||
|
||||
[:p {:class (stl/css :feature-content)}
|
||||
"Now, you can invite members to your teams who only need to view and comment on files. Team members, stakeholders, developers… pick your case. Anyone who doesn't need to edit can participate confidently."]]
|
||||
|
||||
[:div {:class (stl/css :navigation)}
|
||||
[:& c/navigation-bullets
|
||||
{:slide slide
|
||||
:navigate navigate
|
||||
:total 3}]
|
||||
|
||||
[:button {:on-click next
|
||||
:class (stl/css :next-btn)} "Continue"]]]]]]
|
||||
|
||||
1
|
||||
[:div {:class (stl/css-case :modal-overlay true)}
|
||||
[:div.animated {:class klass}
|
||||
[:div {:class (stl/css :modal-container)}
|
||||
[:img {:src "images/features/2.4-history.gif"
|
||||
:class (stl/css :start-image)
|
||||
:border "0"
|
||||
:alt "A timeline for your design process"}]
|
||||
|
||||
[:div {:class (stl/css :modal-content)}
|
||||
[:div {:class (stl/css :modal-header)}
|
||||
[:h1 {:class (stl/css :modal-title)}
|
||||
"A timeline for your design process"]]
|
||||
|
||||
[:div {:class (stl/css :feature)}
|
||||
[:p {:class (stl/css :feature-content)}
|
||||
"Version History allows you to save different stages of your design process, so you can revisit them whenever needed."]
|
||||
|
||||
[:p {:class (stl/css :feature-content)}
|
||||
"Some versions are saved automatically, serving as an invaluable emergency backup. Additionally, you can manually save versions, giving you full control over the timeline associated with a file. This way, you can always restore specific versions that you've intentionally saved."]]
|
||||
|
||||
[:div {:class (stl/css :navigation)}
|
||||
[:& c/navigation-bullets
|
||||
{:slide slide
|
||||
:navigate navigate
|
||||
:total 3}]
|
||||
|
||||
[:button {:on-click next
|
||||
:class (stl/css :next-btn)} "Continue"]]]]]]
|
||||
|
||||
2
|
||||
[:div {:class (stl/css-case :modal-overlay true)}
|
||||
[:div.animated {:class klass}
|
||||
[:div {:class (stl/css :modal-container)}
|
||||
[:img {:src "images/features/2.4-format.gif"
|
||||
:class (stl/css :start-image)
|
||||
:border "0"
|
||||
:alt "New export format: fast and open"}]
|
||||
|
||||
[:div {:class (stl/css :modal-content)}
|
||||
[:div {:class (stl/css :modal-header)}
|
||||
[:h1 {:class (stl/css :modal-title)}
|
||||
"New export format: fast and open"]]
|
||||
[:div {:class (stl/css :feature)}
|
||||
[:p {:class (stl/css :feature-content)}
|
||||
"The new .penpot format will streamline the import and export of files and assets by being more efficient and interoperable."]
|
||||
[:p {:class (stl/css :feature-content)}
|
||||
"This format replaces the previous two—so no more choosing between them or accidentally picking the wrong one! It's better for both scenarios: if you just need to import or export files quickly, it’ll be a bit faster. And if you want to extract data (like a list of color assets), this new format is much easier to read."]]
|
||||
|
||||
[:div {:class (stl/css :navigation)}
|
||||
|
||||
[:& c/navigation-bullets
|
||||
{:slide slide
|
||||
:navigate navigate
|
||||
:total 3}]
|
||||
|
||||
[:button {:on-click finish
|
||||
:class (stl/css :next-btn)} "Let's go"]]]]]])))
|
||||
|
||||
102
frontend/src/app/main/ui/releases/v2_4.scss
Normal file
@@ -0,0 +1,102 @@
|
||||
// This Source Code Form is subject to the terms of the Mozilla Public
|
||||
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
//
|
||||
// Copyright (c) KALEIDOS INC
|
||||
|
||||
@import "refactor/common-refactor.scss";
|
||||
|
||||
.modal-overlay {
|
||||
@extend .modal-overlay-base;
|
||||
}
|
||||
|
||||
.modal-container {
|
||||
display: grid;
|
||||
grid-template-columns: $s-324 1fr;
|
||||
height: $s-500;
|
||||
width: $s-888;
|
||||
border-radius: $br-8;
|
||||
background-color: var(--modal-background-color);
|
||||
border: $s-2 solid var(--modal-border-color);
|
||||
}
|
||||
|
||||
.start-image {
|
||||
width: $s-324;
|
||||
border-radius: $br-8 0 0 $br-8;
|
||||
}
|
||||
|
||||
.modal-content {
|
||||
padding: $s-40;
|
||||
display: grid;
|
||||
grid-template-rows: auto 1fr $s-32;
|
||||
gap: $s-24;
|
||||
|
||||
a {
|
||||
color: var(--button-primary-background-color-rest);
|
||||
}
|
||||
}
|
||||
|
||||
.modal-header {
|
||||
display: grid;
|
||||
gap: $s-8;
|
||||
}
|
||||
|
||||
.version-tag {
|
||||
@include flexCenter;
|
||||
@include headlineSmallTypography;
|
||||
height: $s-32;
|
||||
width: $s-96;
|
||||
background-color: var(--communication-tag-background-color);
|
||||
color: var(--communication-tag-foreground-color);
|
||||
border-radius: $br-8;
|
||||
}
|
||||
|
||||
.modal-title {
|
||||
@include headlineLargeTypography;
|
||||
color: var(--modal-title-foreground-color);
|
||||
}
|
||||
|
||||
.features-block {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: $s-16;
|
||||
width: $s-440;
|
||||
}
|
||||
|
||||
.feature {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: $s-8;
|
||||
}
|
||||
|
||||
.feature-title {
|
||||
@include bodyLargeTypography;
|
||||
color: var(--modal-title-foreground-color);
|
||||
}
|
||||
|
||||
.feature-content {
|
||||
@include bodyMediumTypography;
|
||||
margin: 0;
|
||||
color: var(--modal-text-foreground-color);
|
||||
}
|
||||
|
||||
.feature-list {
|
||||
@include bodyMediumTypography;
|
||||
color: var(--modal-text-foreground-color);
|
||||
list-style: disc;
|
||||
display: grid;
|
||||
gap: $s-8;
|
||||
}
|
||||
|
||||
.navigation {
|
||||
width: 100%;
|
||||
display: grid;
|
||||
grid-template-areas: "bullets button";
|
||||
}
|
||||
|
||||
.next-btn {
|
||||
@extend .button-primary;
|
||||
width: $s-100;
|
||||
justify-self: flex-end;
|
||||
grid-area: button;
|
||||
}
|
||||
@@ -89,6 +89,12 @@
|
||||
reverse-sort? (= :desc ordering)
|
||||
num-libs (count (mf/deref refs/workspace-libraries))
|
||||
|
||||
show-templates-04-test1?
|
||||
(and (cf/external-feature-flag "templates-04" "test1") (zero? num-libs))
|
||||
|
||||
show-templates-04-test2?
|
||||
(and (cf/external-feature-flag "templates-04" "test2") (zero? num-libs))
|
||||
|
||||
toggle-ordering
|
||||
(mf/use-fn
|
||||
(mf/deps ordering)
|
||||
@@ -157,12 +163,18 @@
|
||||
[:article {:class (stl/css :assets-bar)}
|
||||
[:div {:class (stl/css :assets-header)}
|
||||
(when-not ^boolean read-only?
|
||||
(if (and (cf/external-feature-flag "templates-02" "test")
|
||||
(zero? num-libs))
|
||||
(cond
|
||||
show-templates-04-test1?
|
||||
[:button {:class (stl/css :libraries-button)
|
||||
:on-click show-libraries-dialog
|
||||
:data-testid "libraries"}
|
||||
(tr "workspace.assets.add-library")]
|
||||
show-templates-04-test2?
|
||||
[:button {:class (stl/css :add-library-button)
|
||||
:on-click show-libraries-dialog
|
||||
:data-testid "libraries"}
|
||||
(tr "workspace.assets.add-library")]
|
||||
:else
|
||||
[:button {:class (stl/css :libraries-button)
|
||||
:on-click show-libraries-dialog
|
||||
:data-testid "libraries"}
|
||||
@@ -170,6 +182,7 @@
|
||||
i/library]
|
||||
(tr "workspace.assets.libraries")]))
|
||||
|
||||
|
||||
[:div {:class (stl/css :search-wrapper)}
|
||||
[:& search-bar {:on-change on-search-term-change
|
||||
:value term
|
||||
|
||||
@@ -402,6 +402,7 @@
|
||||
|
||||
.component-swap {
|
||||
padding-top: $s-12;
|
||||
max-width: $s-248;
|
||||
}
|
||||
|
||||
.component-swap-content {
|
||||
|
||||
@@ -340,76 +340,6 @@
|
||||
(when (some? guides)
|
||||
(into [] (map parse-frame-guide) guides)))
|
||||
|
||||
;;interface PathCommand {
|
||||
;; command:
|
||||
;; | 'M' | 'move-to'
|
||||
;; | 'Z' | 'close-path'
|
||||
;; | 'L' | 'line-to'
|
||||
;; | 'H' | 'line-to-horizontal'
|
||||
;; | 'V' | 'line-to-vertical'
|
||||
;; | 'C' | 'curve-to'
|
||||
;; | 'S' | 'smooth-curve-to'
|
||||
;; | 'Q' | 'quadratic-bezier-curve-to'
|
||||
;; | 'T' | 'smooth-quadratic-bezier-curve-to'
|
||||
;; | 'A' | 'elliptical-arc';
|
||||
;;
|
||||
;; params?: {
|
||||
;; x?: number;
|
||||
;; y?: number;
|
||||
;; c1x: number;
|
||||
;; c1y: number;
|
||||
;; c2x: number;
|
||||
;; c2y: number;
|
||||
;; rx?: number;
|
||||
;; ry?: number;
|
||||
;; xAxisRotation?: number;
|
||||
;; largeArcFlag?: boolean;
|
||||
;; sweepFlag?: boolean;
|
||||
;; };
|
||||
;;}
|
||||
(defn parse-command-type
|
||||
[^string command-type]
|
||||
(case command-type
|
||||
"M" :move-to
|
||||
"Z" :close-path
|
||||
"L" :line-to
|
||||
"H" :line-to-horizontal
|
||||
"V" :line-to-vertical
|
||||
"C" :curve-to
|
||||
"S" :smooth-curve-to
|
||||
"Q" :quadratic-bezier-curve-to
|
||||
"T" :smooth-quadratic-bezier-curve-to
|
||||
"A" :elliptical-arc
|
||||
(parse-keyword command-type)))
|
||||
|
||||
(defn parse-command-params
|
||||
[^js params]
|
||||
(when (some? params)
|
||||
(d/without-nils
|
||||
{:x (obj/get params "x")
|
||||
:y (obj/get params "y")
|
||||
:c1x (obj/get params "c1x")
|
||||
:c1y (obj/get params "c1y")
|
||||
:c2x (obj/get params "c2x")
|
||||
:c2y (obj/get params "c2y")
|
||||
:rx (obj/get params "rx")
|
||||
:ry (obj/get params "ry")
|
||||
:x-axis-rotation (obj/get params "xAxisRotation")
|
||||
:large-arc-flag (obj/get params "largeArcFlag")
|
||||
:sweep-flag (obj/get params "sweepFlag")})))
|
||||
|
||||
(defn parse-command
|
||||
[^js command]
|
||||
(when (some? command)
|
||||
(d/without-nils
|
||||
{:command (-> (obj/get command "command") parse-command-type)
|
||||
:params (-> (obj/get command "params") parse-command-params)})))
|
||||
|
||||
(defn parse-path-content
|
||||
[^js content]
|
||||
(when (some? content)
|
||||
(into [] (map parse-command) content)))
|
||||
|
||||
;; export interface Dissolve {
|
||||
;; type: 'dissolve';
|
||||
;; duration: number;
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
[app.common.record :as crc]
|
||||
[app.common.schema :as sm]
|
||||
[app.common.spec :as us]
|
||||
[app.common.svg.path.legacy-parser2 :as spp]
|
||||
[app.common.svg.path :as path]
|
||||
[app.common.text :as txt]
|
||||
[app.common.types.component :as ctk]
|
||||
[app.common.types.container :as ctn]
|
||||
@@ -1326,12 +1326,10 @@
|
||||
(cond-> (or (cfh/path-shape? data) (cfh/bool-shape? data))
|
||||
(crc/add-properties!
|
||||
{:name "content"
|
||||
:get #(-> % u/proxy->shape :content format/format-path-content)
|
||||
:get #(-> % u/proxy->shape :content upf/format-path)
|
||||
:set
|
||||
(fn [_ value]
|
||||
(let [content
|
||||
(->> (parser/parse-path-content value)
|
||||
(spp/simplify-commands))]
|
||||
(let [content (->> (path/parse value))]
|
||||
(cond
|
||||
(not (cfh/path-shape? data))
|
||||
(u/display-not-valid :content-type type)
|
||||
|
||||
@@ -18,26 +18,24 @@
|
||||
|
||||
(defn- add-session-properties
|
||||
[user-proxy session-id]
|
||||
(let [plugin-id (obj/get user-proxy "$plugin")]
|
||||
(crc/add-properties!
|
||||
user-proxy
|
||||
{:name "$plugin" :enumerable false :get (constantly plugin-id)}
|
||||
{:name "$session" :enumerable false :get (constantly session-id)}
|
||||
(crc/add-properties!
|
||||
user-proxy
|
||||
{:name "$session" :enumerable false :get (constantly session-id)}
|
||||
|
||||
{:name "id"
|
||||
:get (fn [_] (-> (u/locate-profile session-id) :id str))}
|
||||
{:name "id"
|
||||
:get (fn [_] (-> (u/locate-profile session-id) :id str))}
|
||||
|
||||
{:name "name"
|
||||
:get (fn [_] (-> (u/locate-profile session-id) :fullname))}
|
||||
{:name "name"
|
||||
:get (fn [_] (-> (u/locate-profile session-id) :fullname))}
|
||||
|
||||
{:name "avatarUrl"
|
||||
:get (fn [_] (cfg/resolve-profile-photo-url (u/locate-profile session-id)))}
|
||||
{:name "avatarUrl"
|
||||
:get (fn [_] (cfg/resolve-profile-photo-url (u/locate-profile session-id)))}
|
||||
|
||||
{:name "color"
|
||||
:get (fn [_] (-> (u/locate-presence session-id) :color))}
|
||||
{:name "color"
|
||||
:get (fn [_] (-> (u/locate-presence session-id) :color))}
|
||||
|
||||
{:name "sessionId"
|
||||
:get (fn [_] (str session-id))})))
|
||||
{:name "sessionId"
|
||||
:get (fn [_] (str session-id))}))
|
||||
|
||||
|
||||
(defn current-user-proxy? [p]
|
||||
@@ -46,7 +44,8 @@
|
||||
(defn current-user-proxy
|
||||
[plugin-id session-id]
|
||||
(-> (obj/reify {:name "CurrentUserProxy"}
|
||||
:$plugin {:enumerable false :get (fn [] plugin-id)})
|
||||
:$plugin
|
||||
{:enumerable false :get (fn [] plugin-id)})
|
||||
(add-session-properties session-id)))
|
||||
|
||||
(defn active-user-proxy? [p]
|
||||
@@ -55,7 +54,8 @@
|
||||
(defn active-user-proxy
|
||||
[plugin-id session-id]
|
||||
(-> (obj/reify {:name "ActiveUserProxy"}
|
||||
:$plugin {:enumerable false :get (fn [] plugin-id)}
|
||||
:$plugin
|
||||
{:enumerable false :get (fn [] plugin-id)}
|
||||
|
||||
:position
|
||||
{:get (fn [] (-> (u/locate-presence session-id) :point format/format-point))}
|
||||
@@ -66,19 +66,16 @@
|
||||
|
||||
(defn- add-user-properties
|
||||
[user-proxy data]
|
||||
(let [plugin-id (obj/get user-proxy "$plugin")]
|
||||
(crc/add-properties!
|
||||
user-proxy
|
||||
{:name "$plugin" :enumerable false :get (constantly plugin-id)}
|
||||
(crc/add-properties!
|
||||
user-proxy
|
||||
{:name "id"
|
||||
:get (fn [_] (-> data :id str))}
|
||||
|
||||
{:name "id"
|
||||
:get (fn [_] (-> data :id str))}
|
||||
{:name "name"
|
||||
:get (fn [_] (-> data :fullname))}
|
||||
|
||||
{:name "name"
|
||||
:get (fn [_] (-> data :fullname))}
|
||||
|
||||
{:name "avatarUrl"
|
||||
:get (fn [_] (cfg/resolve-profile-photo-url data))})))
|
||||
{:name "avatarUrl"
|
||||
:get (fn [_] (cfg/resolve-profile-photo-url data))}))
|
||||
|
||||
(defn user-proxy
|
||||
[plugin-id data]
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
(:require
|
||||
[app.common.logging :as l]
|
||||
[app.common.math :as mth]
|
||||
[app.util.object :as obj]
|
||||
[app.util.time :as t]
|
||||
[beicon.v2.core :as rx]))
|
||||
|
||||
@@ -47,13 +48,14 @@
|
||||
|
||||
;; NOTE: Right now there are no cases where we need to cancel a process
|
||||
;; but if we do, we can use this function
|
||||
;; (defn- cancel-process
|
||||
;; [queue]
|
||||
;; (l/dbg :hint "queue::cancel-process")
|
||||
;; (let [timeout (unchecked-get queue "timeout")]
|
||||
;; (when (some? timeout)
|
||||
;; (js/clearTimeout timeout))
|
||||
;; (unchecked-set queue "timeout" nil)))
|
||||
(defn- cancel-process!
|
||||
[queue]
|
||||
(l/dbg :hint "queue::cancel-process")
|
||||
(let [timeout (unchecked-get queue "timeout")]
|
||||
(when (some? timeout)
|
||||
(js/clearTimeout timeout))
|
||||
(unchecked-set queue "timeout" nil))
|
||||
queue)
|
||||
|
||||
(defn- process
|
||||
[queue iterations]
|
||||
@@ -131,3 +133,10 @@
|
||||
(enqueue-last queue request))))
|
||||
|
||||
(rx/to-observable result)))
|
||||
|
||||
(defn clear!
|
||||
[queue]
|
||||
(-> queue
|
||||
(cancel-process!)
|
||||
(obj/set! "items" #js [])
|
||||
(obj/set! "time" 0)))
|
||||
|
||||
@@ -139,7 +139,7 @@ export function normalizeStyles(node, styleDefaults = getStyleDefaultsDeclaratio
|
||||
// a --fills CSS variable property.
|
||||
const fills = styleDeclaration.getPropertyValue("--fills");
|
||||
const color = styleDeclaration.getPropertyValue("color");
|
||||
if (color) {
|
||||
if (color && !fills) {
|
||||
styleDeclaration.removeProperty("color");
|
||||
styleDeclaration.setProperty("--fills", getFills(color));
|
||||
} else {
|
||||
|
||||
@@ -30,6 +30,7 @@ import {
|
||||
splitParagraph,
|
||||
mergeParagraphs,
|
||||
fixParagraph,
|
||||
createParagraph,
|
||||
} from "../content/dom/Paragraph.js";
|
||||
import {
|
||||
removeBackward,
|
||||
@@ -42,7 +43,7 @@ import { getTextNodeLength, getClosestTextNode, isTextNode } from "../content/do
|
||||
import TextNodeIterator from "../content/dom/TextNodeIterator.js";
|
||||
import TextEditor from "../TextEditor.js";
|
||||
import CommandMutations from "../commands/CommandMutations.js";
|
||||
import { setRootStyles } from "../content/dom/Root.js";
|
||||
import { isRoot, setRootStyles } from "../content/dom/Root.js";
|
||||
import { SelectionDirection } from "./SelectionDirection.js";
|
||||
import SafeGuard from "./SafeGuard.js";
|
||||
|
||||
@@ -946,6 +947,15 @@ export class SelectionController extends EventTarget {
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Is true if the current focus node is a root.
|
||||
*
|
||||
* @type {boolean}
|
||||
*/
|
||||
get isRootFocus() {
|
||||
return isRoot(this.focusNode)
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates that we have multiple nodes selected.
|
||||
*
|
||||
@@ -1201,6 +1211,16 @@ export class SelectionController extends EventTarget {
|
||||
);
|
||||
} else if (this.isLineBreakFocus) {
|
||||
this.focusNode.replaceWith(new Text(newText));
|
||||
} else if (this.isRootFocus) {
|
||||
const newTextNode = new Text(newText);
|
||||
const newInline = createInline(newTextNode, this.#currentStyle);
|
||||
const newParagraph = createParagraph([
|
||||
newInline
|
||||
], this.#currentStyle)
|
||||
this.focusNode.replaceChildren(
|
||||
newParagraph
|
||||
);
|
||||
return this.collapse(newTextNode, newText.length + 1);
|
||||
} else {
|
||||
throw new Error('Unknown node type');
|
||||
}
|
||||
|
||||