mirror of
https://github.com/penpot/penpot.git
synced 2025-02-21 06:16:28 -05:00
Merge remote-tracking branch 'origin/staging' into develop
This commit is contained in:
commit
d79fac7729
29 changed files with 555 additions and 461 deletions
|
@ -84,6 +84,7 @@ is a number of cores)
|
|||
- Fix problem with grid layout crashing [Taiga #10127](https://tree.taiga.io/project/penpot/issue/10127)
|
||||
- Fix rename locked boards [Taiga #10174](https://tree.taiga.io/project/penpot/issue/10174)
|
||||
- Fix update-libraries dialog disappear when clicking outside [Taiga #10238](https://tree.taiga.io/project/penpot/issue/10238)
|
||||
- Fix incorrect handling of team access requests with deleted/recreated users
|
||||
|
||||
## 2.4.3
|
||||
|
||||
|
|
|
@ -207,7 +207,7 @@
|
|||
<td align="center" bgcolor="#6911d4" role="presentation"
|
||||
style="border:none;border-radius:8px;cursor:auto;mso-padding-alt:10px 25px;background:#6911d4;"
|
||||
valign="middle">
|
||||
<a href="{{ public-uri }}/#/view/{{file-id}}?page-id={{page-id}}§ion=interactions&index=0&share=true"
|
||||
<a href="{{ public-uri }}/#/view?file-id={{file-id}}&page-id={{page-id}}§ion=interactions&index=0&share=true"
|
||||
style="display:inline-block;background:#6911d4;color:#FFFFFF;font-family:Source Sans Pro, sans-serif;font-size:16px;font-weight:normal;line-height:120%;margin:0;text-decoration:none;text-transform:none;padding:10px 25px;mso-padding-alt:0px;border-radius:8px;"
|
||||
target="_blank"> SEND A VIEW-ONLY LINK </a>
|
||||
</td>
|
||||
|
@ -251,4 +251,4 @@
|
|||
</div>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
</html>
|
||||
|
|
|
@ -6,7 +6,7 @@ Since this file is in your Penpot team, you can provide access by sending a view
|
|||
|
||||
To proceed, please click the link below to generate and send the view-only link:
|
||||
|
||||
{{ public-uri }}/#/view/{{file-id}}?page-id={{page-id}}§ion=interactions&index=0&share=true
|
||||
{{ public-uri }}/#/view?file-id={{file-id}}&page-id={{page-id}}§ion=interactions&index=0&share=true
|
||||
|
||||
|
||||
|
||||
|
|
|
@ -230,9 +230,9 @@
|
|||
<td align="center" bgcolor="#6911d4" role="presentation"
|
||||
style="border:none;border-radius:8px;cursor:auto;mso-padding-alt:10px 25px;background:#6911d4;"
|
||||
valign="middle">
|
||||
<a href="{{ public-uri }}/#/view/{{file-id}}?page-id={{page-id}}§ion=interactions&index=0&share=true"
|
||||
style="display:inline-block;background:#6911d4;color:#FFFFFF;font-family:Source Sans Pro, sans-serif;font-size:16px;font-weight:normal;line-height:120%;margin:0;text-decoration:none;text-transform:none;padding:10px 25px;mso-padding-alt:0px;border-radius:8px;"
|
||||
target="_blank"> SEND A VIEW-ONLY LINK </a>
|
||||
<a href="{{ public-uri }}/#/view?file-id={{file-id}}&page-id={{page-id}}§ion=interactions&index=0&share=true"
|
||||
style="display:inline-block;background:#6911d4;color:#FFFFFF;font-family:Source Sans Pro, sans-serif;font-size:16px;font-weight:normal;line-height:120%;margin:0;text-decoration:none;text-transform:none;padding:10px 25px;mso-padding-alt:0px;border-radius:8px;"
|
||||
target="_blank"> SEND A VIEW-ONLY LINK </a>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
@ -274,4 +274,4 @@
|
|||
</div>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
</html>
|
||||
|
|
|
@ -19,7 +19,7 @@ Alternatively, you can create and share a view-only link to the file. This will
|
|||
|
||||
Click the link below to generate and send the link:
|
||||
|
||||
{{ public-uri }}/#/view/{{file-id}}?page-id={{page-id}}§ion=interactions&index=0&share=true
|
||||
{{ public-uri }}/#/view?file-id={{file-id}}&page-id={{page-id}}§ion=interactions&index=0&share=true
|
||||
|
||||
|
||||
|
||||
|
|
|
@ -214,7 +214,7 @@
|
|||
<td align="center" bgcolor="#6911d4" role="presentation"
|
||||
style="border:none;border-radius:8px;cursor:auto;mso-padding-alt:10px 25px;background:#6911d4;"
|
||||
valign="middle">
|
||||
<a href="{{ public-uri }}/#/dashboard/team/{{team-id}}/members?invite-email={{requested-by-email|urlescape }}"
|
||||
<a href="{{ public-uri }}/#/dashboard/members?team-id={{team-id}}&invite-email={{requested-by-email|urlescape }}"
|
||||
style="display:inline-block;background:#6911d4;color:#FFFFFF;font-family:Source Sans Pro, sans-serif;font-size:16px;font-weight:normal;line-height:120%;margin:0;text-decoration:none;text-transform:none;padding:10px 25px;mso-padding-alt:0px;border-radius:8px;"
|
||||
target="_blank"> GIVE ACCESS TO “{{team-name|abbreviate:25}}” TEAM </a>
|
||||
</td>
|
||||
|
@ -247,9 +247,9 @@
|
|||
<td align="center" bgcolor="#6911d4" role="presentation"
|
||||
style="border:none;border-radius:8px;cursor:auto;mso-padding-alt:10px 25px;background:#6911d4;"
|
||||
valign="middle">
|
||||
<a href="{{ public-uri }}/#/view/{{file-id}}?page-id={{page-id}}§ion=interactions&index=0&share=true"
|
||||
style="display:inline-block;background:#6911d4;color:#FFFFFF;font-family:Source Sans Pro, sans-serif;font-size:16px;font-weight:normal;line-height:120%;margin:0;text-decoration:none;text-transform:none;padding:10px 25px;mso-padding-alt:0px;border-radius:8px;"
|
||||
target="_blank"> SEND A VIEW-ONLY LINK </a>
|
||||
<a href="{{ public-uri }}/#/view?file-id={{file-id}}&page-id={{page-id}}§ion=interactions&index=0&share=true"
|
||||
style="display:inline-block;background:#6911d4;color:#FFFFFF;font-family:Source Sans Pro, sans-serif;font-size:16px;font-weight:normal;line-height:120%;margin:0;text-decoration:none;text-transform:none;padding:10px 25px;mso-padding-alt:0px;border-radius:8px;"
|
||||
target="_blank"> SEND A VIEW-ONLY LINK </a>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
@ -292,4 +292,4 @@
|
|||
</div>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
</html>
|
||||
|
|
|
@ -13,7 +13,7 @@ This will automatically include {{requested-by|abbreviate:25}} in the team, so t
|
|||
|
||||
Click the link below to provide team access:
|
||||
|
||||
{{ public-uri }}/#/dashboard/team/{{team-id}}/members?invite-email={{requested-by-email|urlescape}}
|
||||
{{ public-uri }}/#/dashboard/members?team-id{{team-id}}&invite-email={{requested-by-email|urlescape}}
|
||||
|
||||
|
||||
|
||||
|
@ -23,8 +23,7 @@ Alternatively, you can create and share a view-only link to the file. This will
|
|||
|
||||
Click the link below to generate and send the link:
|
||||
|
||||
{{ public-uri }}/#/view/{{file-id}}?page-id={{page-id}}§ion=interactions&index=0&share=true
|
||||
|
||||
{{ public-uri }}/#/view?file-id={{file-id}}&page-id={{page-id}}§ion=interactions&index=0&share=true
|
||||
|
||||
|
||||
If you do not wish to grant access at this time, you can simply disregard this email.
|
||||
|
|
|
@ -205,7 +205,7 @@
|
|||
<td align="center" bgcolor="#6911d4" role="presentation"
|
||||
style="border:none;border-radius:8px;cursor:auto;mso-padding-alt:10px 25px;background:#6911d4;"
|
||||
valign="middle">
|
||||
<a href="{{ public-uri }}/#/dashboard/team/{{team-id}}/members?invite-email={{requested-by-email|urlescape}}"
|
||||
<a href="{{ public-uri }}/#/dashboard/members?team-id={{team-id}}&invite-email={{requested-by-email|urlescape}}"
|
||||
style="display:inline-block;background:#6911d4;color:#FFFFFF;font-family:Source Sans Pro, sans-serif;font-size:16px;font-weight:normal;line-height:120%;margin:0;text-decoration:none;text-transform:none;padding:10px 25px;mso-padding-alt:0px;border-radius:8px;"
|
||||
target="_blank"> GIVE ACCESS TO “{{team-name|abbreviate:25}}” TEAM </a>
|
||||
</td>
|
||||
|
@ -249,4 +249,4 @@
|
|||
</div>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
</html>
|
||||
|
|
|
@ -4,7 +4,7 @@ Hello!
|
|||
|
||||
To provide access, please click the link below:
|
||||
|
||||
{{ public-uri }}/#/dashboard/team/{{team-id}}/members?invite-email={{requested-by-email|urlescape}}
|
||||
{{ public-uri }}/#/dashboard/members?team-id={{team-id}}&invite-email={{requested-by-email|urlescape}}
|
||||
|
||||
|
||||
If you do not wish to grant access at this time, you can simply disregard this email.
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
|
||||
(ns app.rpc.commands.teams-invitations
|
||||
(:require
|
||||
[app.common.data :as d]
|
||||
[app.common.data.macros :as dm]
|
||||
[app.common.exceptions :as ex]
|
||||
[app.common.features :as cfeat]
|
||||
|
@ -15,7 +16,6 @@
|
|||
[app.common.uuid :as uuid]
|
||||
[app.config :as cf]
|
||||
[app.db :as db]
|
||||
[app.db.sql :as sql]
|
||||
[app.email :as eml]
|
||||
[app.loggers.audit :as audit]
|
||||
[app.main :as-alias main]
|
||||
|
@ -168,19 +168,16 @@
|
|||
|
||||
itoken))))
|
||||
|
||||
(defn- add-user-to-team
|
||||
[conn profile team role email]
|
||||
(defn- add-member-to-team
|
||||
[conn profile team role member]
|
||||
|
||||
(let [team-id (:id team)
|
||||
member (db/get* conn :profile
|
||||
{:email (str/lower email)}
|
||||
{::sql/columns [:id :email]})
|
||||
params (merge
|
||||
{:team-id team-id
|
||||
:profile-id (:id member)}
|
||||
(get types.team/permissions-for-role role))]
|
||||
|
||||
;; Do not allow blocked users to join teams.
|
||||
;; Do not allow blocked users to join teams.
|
||||
(when (:is-blocked member)
|
||||
(ex/raise :type :restriction
|
||||
:code :profile-blocked))
|
||||
|
@ -205,29 +202,33 @@
|
|||
(eml/send! {::eml/conn conn
|
||||
::eml/factory eml/join-team
|
||||
:public-uri (cf/get :public-uri)
|
||||
:to email
|
||||
:to (:email member)
|
||||
:invited-by (:fullname profile)
|
||||
:team (:name team)
|
||||
:team-id (:id team)})))
|
||||
|
||||
(def sql:valid-requests-email
|
||||
"SELECT p.email
|
||||
(def ^:private sql:valid-access-request-profiles
|
||||
"SELECT p.id, p.email, p.is_blocked
|
||||
FROM team_access_request AS tr
|
||||
JOIN profile AS p ON (tr.requester_id = p.id)
|
||||
WHERE tr.team_id = ?
|
||||
AND tr.auto_join_until > now()")
|
||||
AND tr.auto_join_until > now()
|
||||
AND (p.deleted_at IS NULL OR
|
||||
p.deleted_at > now())")
|
||||
|
||||
(defn- get-valid-requests-email
|
||||
(defn- get-valid-access-request-profiles
|
||||
[conn team-id]
|
||||
(db/exec! conn [sql:valid-requests-email team-id]))
|
||||
(db/exec! conn [sql:valid-access-request-profiles team-id]))
|
||||
|
||||
(def ^:private xf:map-email
|
||||
(map :email))
|
||||
(def ^:private xf:map-email (map :email))
|
||||
|
||||
(defn- create-team-invitations
|
||||
[{:keys [::db/conn] :as cfg} {:keys [profile team role emails] :as params}]
|
||||
(let [join-requests (into #{} xf:map-email
|
||||
(get-valid-requests-email conn (:id team)))
|
||||
(let [emails (set emails)
|
||||
|
||||
join-requests (->> (get-valid-access-request-profiles conn (:id team))
|
||||
(d/index-by :email))
|
||||
|
||||
team-members (into #{} xf:map-email
|
||||
(teams/get-team-members conn (:id team)))
|
||||
|
||||
|
@ -245,8 +246,10 @@
|
|||
|
||||
;; For requested invitations, do not send invitation emails, add
|
||||
;; the user directly to the team
|
||||
(->> (filter join-requests emails)
|
||||
(run! (partial add-user-to-team conn profile team role)))
|
||||
(->> join-requests
|
||||
(filter #(contains? emails (key %)))
|
||||
(map val)
|
||||
(run! (partial add-member-to-team conn profile team role)))
|
||||
|
||||
invitations))
|
||||
|
||||
|
@ -572,5 +575,3 @@
|
|||
|
||||
(with-meta {:request request}
|
||||
{::audit/props {:request 1}}))))
|
||||
|
||||
|
||||
|
|
|
@ -37,18 +37,17 @@
|
|||
:role :editor}]
|
||||
|
||||
;; invite external user without complaints
|
||||
(let [data (assoc data :emails ["foo@bar.com"])
|
||||
out (th/command! data)
|
||||
(let [data (assoc data :emails ["foo@bar.com"])
|
||||
out (th/command! data)
|
||||
;; retrieve the value from the database and check its content
|
||||
invitation (db/exec-one!
|
||||
th/*pool*
|
||||
["select count(*) as num from team_invitation where team_id = ? and email_to = ?"
|
||||
(:team-id data) "foo@bar.com"])]
|
||||
invitations (th/db-query :team-invitation
|
||||
{:team-id (:team-id data)
|
||||
:email-to "foo@bar.com"})]
|
||||
|
||||
;; (th/print-result! out)
|
||||
(t/is (th/success? out))
|
||||
(t/is (= 1 (:call-count (deref mock))))
|
||||
(t/is (= 1 (:num invitation))))
|
||||
(t/is (= 1 (count invitations))))
|
||||
|
||||
;; invite internal user without complaints
|
||||
(th/reset-mock! mock)
|
||||
|
@ -102,6 +101,105 @@
|
|||
(t/is (= :validation (:type edata)))
|
||||
(t/is (= :member-is-muted (:code edata))))))))
|
||||
|
||||
(t/deftest create-team-invitations-with-request-access
|
||||
(with-mocks [mock {:target 'app.email/send! :return nil}]
|
||||
(let [profile1 (th/create-profile* 1 {:is-active true})
|
||||
requester (th/create-profile* 2 {:is-active true :email "requester@example.com"})
|
||||
|
||||
team (th/create-team* 1 {:profile-id (:id profile1)})
|
||||
proj (th/create-project* 1 {:profile-id (:id profile1)
|
||||
:team-id (:id team)})
|
||||
file (th/create-file* 1 {:profile-id (:id profile1)
|
||||
:project-id (:id proj)})]
|
||||
(let [data {::th/type :create-team-access-request
|
||||
::rpc/profile-id (:id requester)
|
||||
:file-id (:id file)}
|
||||
out (th/command! data)]
|
||||
(t/is (th/success? out))
|
||||
(t/is (= 1 (:call-count @mock))))
|
||||
|
||||
(th/reset-mock! mock)
|
||||
|
||||
(let [data {::th/type :create-team-invitations
|
||||
::rpc/profile-id (:id profile1)
|
||||
:team-id (:id team)
|
||||
:role :editor
|
||||
:emails ["requester@example.com"]}
|
||||
out (th/command! data)]
|
||||
(t/is (th/success? out))
|
||||
(t/is (= 1 (:call-count @mock)))
|
||||
|
||||
;; Check that request is properly removed
|
||||
(let [requests (th/db-query :team-access-request
|
||||
{:requester-id (:id requester)})]
|
||||
(t/is (= 0 (count requests))))
|
||||
|
||||
(let [rows (th/db-query :team-profile-rel {:team-id (:id team)})]
|
||||
(t/is (= 2 (count rows))))))))
|
||||
|
||||
|
||||
(t/deftest create-team-invitations-with-request-access-2
|
||||
(with-mocks [mock {:target 'app.email/send! :return nil}]
|
||||
(let [profile1 (th/create-profile* 1 {:is-active true})
|
||||
requester (th/create-profile* 2 {:is-active true
|
||||
:email "requester@example.com"})
|
||||
|
||||
team (th/create-team* 1 {:profile-id (:id profile1)})
|
||||
proj (th/create-project* 1 {:profile-id (:id profile1)
|
||||
:team-id (:id team)})
|
||||
file (th/create-file* 1 {:profile-id (:id profile1)
|
||||
:project-id (:id proj)})]
|
||||
|
||||
;; Create the first access request
|
||||
(let [data {::th/type :create-team-access-request
|
||||
::rpc/profile-id (:id requester)
|
||||
:file-id (:id file)}
|
||||
out (th/command! data)]
|
||||
(t/is (th/success? out))
|
||||
(t/is (= 1 (:call-count @mock))))
|
||||
|
||||
(th/reset-mock! mock)
|
||||
|
||||
;; Proceed to delete the requester user
|
||||
(th/db-update! :profile
|
||||
{:deleted-at (dt/in-past "1h")}
|
||||
{:id (:id requester)})
|
||||
|
||||
;; Create a new profile with the same email
|
||||
(let [requester' (th/create-profile* 3 {:is-active true :email "requester@example.com"})]
|
||||
|
||||
;; Create a request access with new requester
|
||||
(let [data {::th/type :create-team-access-request
|
||||
::rpc/profile-id (:id requester')
|
||||
:file-id (:id file)}
|
||||
out (th/command! data)]
|
||||
(t/is (th/success? out))
|
||||
(t/is (= 1 (:call-count @mock))))
|
||||
|
||||
(th/reset-mock! mock)
|
||||
|
||||
;; Create an invitation for the requester email
|
||||
(let [data {::th/type :create-team-invitations
|
||||
::rpc/profile-id (:id profile1)
|
||||
:team-id (:id team)
|
||||
:role :editor
|
||||
:emails ["requester@example.com"]}
|
||||
out (th/command! data)]
|
||||
(t/is (th/success? out))
|
||||
(t/is (= 1 (:call-count @mock))))
|
||||
|
||||
;; Check that request is properly removed
|
||||
(let [requests (th/db-query :team-access-request
|
||||
{:requester-id (:id requester')})]
|
||||
(t/is (= 0 (count requests))))
|
||||
|
||||
(let [[r1 r2 :as rows] (th/db-query :team-profile-rel
|
||||
{:team-id (:id team)}
|
||||
{:order-by [:created-at]})]
|
||||
(t/is (= 2 (count rows)))
|
||||
(t/is (= (:profile-id r1) (:id profile1)))
|
||||
(t/is (= (:profile-id r2) (:id requester'))))))))
|
||||
|
||||
|
||||
(t/deftest invitation-tokens
|
||||
(with-mocks [mock {:target 'app.email/send! :return nil}]
|
||||
|
@ -486,14 +584,12 @@
|
|||
;; request success
|
||||
(let [out (th/command! data)
|
||||
;; retrieve the value from the database and check its content
|
||||
request (db/exec-one!
|
||||
th/*pool*
|
||||
["select count(*) as num from team_access_request where team_id = ? and requester_id = ?"
|
||||
(:id team) (:id requester)])]
|
||||
|
||||
requests (th/db-query :team-access-request
|
||||
{:team-id (:id team)
|
||||
:requester-id (:id requester)})]
|
||||
(t/is (th/success? out))
|
||||
(t/is (= 1 (:call-count @mock)))
|
||||
(t/is (= 1 (:num request))))
|
||||
(t/is (= 1 (count requests))))
|
||||
|
||||
;; request again fails
|
||||
(th/reset-mock! mock)
|
||||
|
@ -509,10 +605,10 @@
|
|||
;; request again when is expired success
|
||||
(th/reset-mock! mock)
|
||||
|
||||
(db/exec-one!
|
||||
th/*pool*
|
||||
["update team_access_request set valid_until = ? where team_id = ? and requester_id = ?"
|
||||
(dt/in-past "1h") (:id team) (:id requester)])
|
||||
(th/db-update! :team-access-request
|
||||
{:valid-until (dt/in-past "1h")}
|
||||
{:team-id (:id team)
|
||||
:requester-id (:id requester)})
|
||||
|
||||
(t/is (th/success? (th/command! data)))
|
||||
(t/is (= 1 (:call-count @mock))))))
|
||||
|
|
|
@ -71,7 +71,9 @@
|
|||
</main>
|
||||
|
||||
<div class="pre-footer">
|
||||
<a href="https://github.com/penpot/penpot/blob/main/docs/{{ page.inputPath }}">Edit this page on GitHub</a>
|
||||
<a href="https://github.com/penpot/penpot/blob/main/docs/{{ page.inputPath }}">Edit this page on GitHub</a>
|
||||
or ask a
|
||||
<a href="https://github.com/penpot/penpot/issues/new/choose">question</a>.
|
||||
</div>
|
||||
<footer class="footer">
|
||||
<div class="footer-inside">
|
||||
|
|
|
@ -2,73 +2,107 @@
|
|||
title: 2. Penpot Configuration
|
||||
---
|
||||
|
||||
# Penpot Configuration #
|
||||
# Penpot Configuration
|
||||
|
||||
This section intends to explain all available configuration options, when you
|
||||
are self-hosting Penpot or also if you are using the Penpot developer setup.
|
||||
This section explains the configuration options, both for self-hosting and developer setup.
|
||||
|
||||
Penpot is configured using environment variables. All variables start with <code class="language-bash">PENPOT_</code>
|
||||
prefix.
|
||||
<p class="advice">
|
||||
Penpot is configured using environment variables and flags.
|
||||
</p>
|
||||
|
||||
Variables are initialized in the <code class="language-bash">docker-compose.yaml</code> file, as explained in the
|
||||
Self-hosting guide with [Elestio][1] or [Docker][2].
|
||||
## How the configuration works
|
||||
|
||||
Additionally, if you are using the developer environment, you may override their values in
|
||||
the startup scripts, as explained in the [Developer Guide][3].
|
||||
Penpot is configured using environment variables and flags. **Environment variables** start
|
||||
with <code class="language-bash">PENPOT_</code>. **Flags** use the format
|
||||
<code class="language-bash"><enable|disable>-<flag-name></code>.
|
||||
|
||||
**NOTE**: All the examples that have values represent the **default** values, and the
|
||||
examples that do not have values are optional, and inactive by default.
|
||||
|
||||
|
||||
## Common ##
|
||||
|
||||
This section will list all common configuration between backend and frontend.
|
||||
|
||||
There are two types of configuration: options (properties that require some value) and
|
||||
flags (that just enables or disables something). All flags are set in a single
|
||||
<code class="language-bash">PENPOT_FLAGS</code> environment variable. The envvar is a list of strings using this
|
||||
format: <code class="language-bash"><enable|disable>-\<flag-name></code>. For example:
|
||||
Flags are used to enable/disable a feature or behaviour (registration, feedback),
|
||||
while environment variables are used to configure the settings (auth, smtp, etc).
|
||||
Flags and evironment variables are also used together; for example:
|
||||
|
||||
```bash
|
||||
PENPOT_FLAGS: enable-smtp disable-registration disable-email-verification
|
||||
# This flag enables the use of SMTP email
|
||||
PENPOT_FLAGS: enable-smtp
|
||||
|
||||
# These environment variables configure the specific SMPT service
|
||||
# Backend
|
||||
PENPOT_SMTP_HOST: <host>
|
||||
PENPOT_SMTP_PORT: 587
|
||||
```
|
||||
|
||||
### Registration ###
|
||||
**Flags** are configured in a single list, no matter they affect the backend, the frontend,
|
||||
the exporter, or all of them; on the other hand, **environment variables** are configured for
|
||||
each specific service. For example:
|
||||
|
||||
Penpot comes with an option to completely disable the registration process;
|
||||
for this, use the following variable:
|
||||
```bash
|
||||
PENPOT_FLAGS: enable-login-with-google
|
||||
|
||||
# Backend
|
||||
PENPOT_GOOGLE_CLIENT_ID: <client-id>
|
||||
PENPOT_GOOGLE_CLIENT_SECRET: <client-secret>
|
||||
```
|
||||
|
||||
Check the configuration guide for [Elestio][1] or [Docker][2]. Additionally, if you are using
|
||||
the developer environment, you may override its values in the startup scripts,
|
||||
as explained in the [Developer Guide][3].
|
||||
|
||||
**NOTE**: All the examples that have value represent the **default** value, and the
|
||||
examples that do not have value are optional, and inactive or disabled by default.
|
||||
|
||||
## Telemetries
|
||||
|
||||
Penpot uses anonymous telemetries from the self-hosted instances to improve the platform experience.
|
||||
Consider sharing these anonymous telemetries enabling the corresponding flag:
|
||||
|
||||
```bash
|
||||
PENPOT_FLAGS: enable-telemetries
|
||||
```
|
||||
|
||||
## Registration and authentication
|
||||
|
||||
There are different ways of registration and authentication in Penpot:
|
||||
- email/password
|
||||
- Authentication providers like Google, Github or GitLab
|
||||
- LDAP
|
||||
|
||||
You can choose one of them or combine several methods, depending on your needs.
|
||||
By default, the email/password registration is enabled and the rest are disabled.
|
||||
|
||||
### Penpot
|
||||
|
||||
This method of registration and authentication is enabled by default. For a production environment,
|
||||
it should be configured next to the SMTP settings, so there is a proper registration and verification
|
||||
process.
|
||||
|
||||
You may want to restrict the registrations to a closed list of domains,
|
||||
or exclude a specific list of domains:
|
||||
|
||||
```bash
|
||||
# Backend
|
||||
# comma separated list of domains
|
||||
PENPOT_REGISTRATION_DOMAIN_WHITELIST:
|
||||
|
||||
# Backend
|
||||
# or a file with a domain per line
|
||||
PENPOT_EMAIL_DOMAIN_WHITELIST: path/to/whitelist.txt
|
||||
PENPOT_EMAIL_DOMAIN_BLACKLIST: path/to/blacklist.txt
|
||||
```
|
||||
|
||||
__Since version 2.1__
|
||||
|
||||
Email whitelisting should be explicitly
|
||||
enabled with <code class="language-bash">enable-email-whitelist</code> flag. For backward compatibility, we
|
||||
autoenable it when <code class="language-bash">PENPOT_REGISTRATION_DOMAIN_WHITELIST</code> is set with
|
||||
not-empty content.
|
||||
|
||||
Penpot also comes with an option to completely disable the registration process;
|
||||
for this, use the following flag:
|
||||
|
||||
```bash
|
||||
PENPOT_FLAGS: [...] disable-registration
|
||||
```
|
||||
|
||||
You may also want to restrict the registrations to a closed list of domains:
|
||||
|
||||
```bash
|
||||
# comma separated list of domains (backend only)
|
||||
PENPOT_REGISTRATION_DOMAIN_WHITELIST:
|
||||
|
||||
# OR (backend only)
|
||||
PENPOT_EMAIL_DOMAIN_WHITELIST: path/to/whitelist.txt
|
||||
```
|
||||
|
||||
**NOTE**: Since version 2.1, email whitelisting should be explicitly
|
||||
enabled with <code class="language-bash">enable-email-whitelist</code> flag. For backward compatibility, we
|
||||
autoenable it when <code class="language-bash">PENPOT_REGISTRATION_DOMAIN_WHITELIST</code> is set with
|
||||
not-empty content.
|
||||
|
||||
### Demo users ###
|
||||
|
||||
Penpot comes with facilities for fast creation of demo users without the need of a
|
||||
registration process. The demo users by default have an expiration time of 7 days, and
|
||||
once expired they are completely deleted with all the generated content. Very useful for
|
||||
testing or demonstration purposes.
|
||||
|
||||
You can enable demo users using the following variable:
|
||||
|
||||
```bash
|
||||
PENPOT_FLAGS: [...] enable-demo-users
|
||||
```
|
||||
This option is only recommended for demo instances, not for production environments.
|
||||
|
||||
### Authentication Providers
|
||||
|
||||
|
@ -82,7 +116,6 @@ The callback has the following format:
|
|||
https://<your_domain>/api/auth/oauth/<oauth_provider>/callback
|
||||
```
|
||||
|
||||
|
||||
You will need to change <your_domain> and <oauth_provider> according to your setup.
|
||||
This is how it looks with Gitlab provider:
|
||||
|
||||
|
@ -90,22 +123,6 @@ This is how it looks with Gitlab provider:
|
|||
https://<your_domain>/api/auth/oauth/gitlab/callback
|
||||
```
|
||||
|
||||
#### Penpot
|
||||
|
||||
Consists on registration and authentication via email / password. It is enabled by default,
|
||||
but login can be disabled with the following flags:
|
||||
|
||||
```bash
|
||||
PENPOT_FLAGS: [...] disable-login-with-password
|
||||
```
|
||||
|
||||
And the registration also can be disabled with:
|
||||
|
||||
```bash
|
||||
PENPOT_FLAGS: [...] disable-registration
|
||||
```
|
||||
|
||||
|
||||
#### Google
|
||||
|
||||
Allows integrating with Google as OAuth provider:
|
||||
|
@ -145,7 +162,7 @@ PENPOT_GITHUB_CLIENT_SECRET: <client-secret>
|
|||
|
||||
#### OpenID Connect
|
||||
|
||||
**NOTE:** Since version 1.5.0
|
||||
__Since version 1.5.0__
|
||||
|
||||
Allows integrating with a generic authentication provider that implements the OIDC
|
||||
protocol (usually used for SSO).
|
||||
|
@ -155,7 +172,7 @@ All the other options are backend only:
|
|||
```bash
|
||||
PENPOT_FLAGS: [...] enable-login-with-oidc
|
||||
|
||||
## Backend only
|
||||
# Backend
|
||||
PENPOT_OIDC_CLIENT_ID: <client-id>
|
||||
|
||||
# Mainly used for auto discovery the openid endpoints
|
||||
|
@ -231,7 +248,6 @@ register with another method.
|
|||
PENPOT_FLAGS: [...] enable-oidc-registration
|
||||
```
|
||||
|
||||
|
||||
#### Azure Active Directory using OpenID Connect
|
||||
|
||||
Allows integrating with Azure Active Directory as authentication provider:
|
||||
|
@ -240,12 +256,12 @@ Allows integrating with Azure Active Directory as authentication provider:
|
|||
# Backend & Frontend
|
||||
PENPOT_OIDC_CLIENT_ID: <client-id>
|
||||
|
||||
## Backend only
|
||||
# Backend
|
||||
PENPOT_OIDC_BASE_URI: https://login.microsoftonline.com/<tenant-id>/v2.0/
|
||||
PENPOT_OIDC_CLIENT_SECRET: <client-secret>
|
||||
```
|
||||
|
||||
### LDAP ###
|
||||
### LDAP
|
||||
|
||||
Penpot comes with support for *Lightweight Directory Access Protocol* (LDAP). This is the
|
||||
example configuration we use internally for testing this authentication backend.
|
||||
|
@ -253,7 +269,7 @@ example configuration we use internally for testing this authentication backend.
|
|||
```bash
|
||||
PENPOT_FLAGS: [...] enable-login-with-ldap
|
||||
|
||||
## Backend only
|
||||
# Backend
|
||||
PENPOT_LDAP_HOST: ldap
|
||||
PENPOT_LDAP_PORT: 10389
|
||||
PENPOT_LDAP_SSL: false
|
||||
|
@ -268,39 +284,34 @@ PENPOT_LDAP_ATTRS_FULLNAME: cn
|
|||
PENPOT_LDAP_ATTRS_PHOTO: jpegPhoto
|
||||
```
|
||||
|
||||
If you miss something, please open an issue and we discuss it.
|
||||
## Penpot URI
|
||||
|
||||
|
||||
## Backend ##
|
||||
|
||||
This section enumerates the backend only configuration variables.
|
||||
|
||||
|
||||
### Database
|
||||
|
||||
We only support PostgreSQL and we highly recommend >=13 version. If you are using official
|
||||
docker images this is already solved for you.
|
||||
|
||||
Essential database configuration:
|
||||
You will need to set the <code class="language-bash">PENPOT_PUBLIC_URI</code> environment variable in case you go to serve Penpot to the users;
|
||||
it should point to public URI where users will access the application:
|
||||
|
||||
```bash
|
||||
# Backend
|
||||
PENPOT_DATABASE_USERNAME: penpot
|
||||
PENPOT_DATABASE_PASSWORD: penpot
|
||||
PENPOT_DATABASE_URI: postgresql://127.0.0.1/penpot
|
||||
PENPOT_PUBLIC_URI: https://penpot.mycompany.com
|
||||
|
||||
# Frontend
|
||||
PENPOT_PUBLIC_URI: https://penpot.mycompany.com
|
||||
|
||||
# Exporter
|
||||
PENPOT_PUBLIC_URI: https://penpot.mycompany.com
|
||||
```
|
||||
|
||||
The username and password are optional. These settings should be compatible with the ones
|
||||
in the postgres configuration:
|
||||
If you're using the official <code class="language-bash">docker-compose.yml</code> you only need to configure the
|
||||
<code class="language-bash">PENPOT_PUBLIC_URI</code> envvar in the top of the file.
|
||||
|
||||
```bash
|
||||
# Postgres
|
||||
POSTGRES_DATABASE: penpot
|
||||
POSTGRES_USER: penpot
|
||||
POSTGRES_PASSWORD: penpot
|
||||
```
|
||||
<p class="advice">
|
||||
If you plan to serve Penpot under different domain than `localhost` without HTTPS,
|
||||
you need to disable the `secure` flag on cookies, with the `disable-secure-session-cookies` flag.
|
||||
This is a configuration NOT recommended for production environments; as some browser APIs do
|
||||
not work properly under non-https environments, this unsecure configuration
|
||||
may limit the usage of Penpot; as an example, the clipboard does not work with HTTP.
|
||||
</p>
|
||||
|
||||
### Email (SMTP)
|
||||
## Email configuration
|
||||
|
||||
By default, <code class="language-bash">smpt</code> flag is disabled, the email will be
|
||||
printed to the console, which means that the emails will be shown in the stdout.
|
||||
|
@ -326,6 +337,7 @@ Enable SMTP:
|
|||
|
||||
```bash
|
||||
PENPOT_FLAGS: [...] enable-smtp
|
||||
|
||||
# Backend
|
||||
PENPOT_SMTP_HOST: <host>
|
||||
PENPOT_SMTP_PORT: 587
|
||||
|
@ -334,14 +346,108 @@ PENPOT_SMTP_PASSWORD: <password>
|
|||
PENPOT_SMTP_TLS: true
|
||||
```
|
||||
|
||||
If you are not using SMTP configuration and want to log the emails in the console, you should use the following flag:
|
||||
|
||||
```bash
|
||||
PENPOT_FLAGS: [...] enable-log-emails
|
||||
```
|
||||
|
||||
## Redis
|
||||
|
||||
The Redis configuration is very simple, just provide a valid redis URI. Redis is used
|
||||
mainly for websocket notifications coordination.
|
||||
|
||||
```bash
|
||||
# Backend
|
||||
PENPOT_REDIS_URI: redis://localhost/0
|
||||
|
||||
# Exporter
|
||||
PENPOT_REDIS_URI: redis://localhost/0
|
||||
```
|
||||
|
||||
If you are using the official docker compose file, this is already configurRed.
|
||||
|
||||
## Demo environment
|
||||
|
||||
Penpot comes with facilities to create a demo environment so you can test the system quickly.
|
||||
This is an example of a demo configuration:
|
||||
|
||||
```bash
|
||||
PENPOT_FLAGS: disable-registration enable-demo-users enable-demo-warning
|
||||
```
|
||||
|
||||
**disable-registration** prevents any user from registering in the platform.
|
||||
**enable-demo-users** creates users with a default expiration time of 7 days, and
|
||||
once expired they are completely deleted with all the generated content.
|
||||
From the registration page, there is a link with a `Create demo account` which creates one of these
|
||||
users and logs in automatically.
|
||||
**enable-demo-warning** is a modal in the registration and login page saying that the
|
||||
environment is a testing one and the data may be wiped without notice.
|
||||
|
||||
Another way to work in a demo environment is allowing users to register but removing the
|
||||
verification process:
|
||||
|
||||
```bash
|
||||
PENPOT_FLAGS: disable-email-verification enable-demo-warning
|
||||
```
|
||||
|
||||
## Backend
|
||||
|
||||
This section enumerates the backend only configuration variables.
|
||||
|
||||
### Secret key
|
||||
|
||||
The <code class="language-bash">PENPOT_SECRET_KEY</code> envvar serves a master key from which other keys
|
||||
for subsystems (eg http sessions, or invitations) are derived.
|
||||
|
||||
If you don't use it, all created sessions and invitations will become invalid on container restart
|
||||
or service restart.
|
||||
|
||||
To use it, we recommend using a truly randomly generated 512 bits base64 encoded string here.
|
||||
You can generate one with:
|
||||
|
||||
```bash
|
||||
python3 -c "import secrets; print(secrets.token_urlsafe(64))"
|
||||
```
|
||||
|
||||
And configure it:
|
||||
```bash
|
||||
# Backend
|
||||
PENPOT_SECRET_KEY: my-super-secure-key
|
||||
```
|
||||
|
||||
### Database
|
||||
|
||||
Penpot only supports PostgreSQL and we highly recommend >=13 version. If you are using official
|
||||
docker images this is already solved for you.
|
||||
|
||||
Essential database configuration:
|
||||
|
||||
```bash
|
||||
# Backend
|
||||
PENPOT_DATABASE_USERNAME: penpot
|
||||
PENPOT_DATABASE_PASSWORD: penpot
|
||||
PENPOT_DATABASE_URI: postgresql://127.0.0.1/penpot
|
||||
```
|
||||
|
||||
The username and password are optional. These settings should be compatible with the ones
|
||||
in the postgres configuration:
|
||||
|
||||
```bash
|
||||
# Postgres
|
||||
POSTGRES_DATABASE: penpot
|
||||
POSTGRES_USER: penpot
|
||||
POSTGRES_PASSWORD: penpot
|
||||
```
|
||||
|
||||
### Storage
|
||||
|
||||
Storage refers to storage used for store the user uploaded assets.
|
||||
Storage refers to storing the user uploaded assets.
|
||||
|
||||
Assets storage is implemented using "plugable" backends. Currently there are three
|
||||
Assets storage is implemented using "plugable" backends. Currently there are two
|
||||
backends available: <code class="language-bash">fs</code> and <code class="language-bash">s3</code> (for AWS S3).
|
||||
|
||||
#### FS Backend (default) ####
|
||||
#### FS Backend (default)
|
||||
|
||||
This is the default backend when you use the official docker images and the default
|
||||
configuration looks like this:
|
||||
|
@ -360,8 +466,7 @@ configure the nginx yourself.
|
|||
In case you want understand how it internally works, you can take a look on the [nginx
|
||||
configuration file][4] used in the docker images.
|
||||
|
||||
|
||||
#### AWS S3 Backend ####
|
||||
#### AWS S3 Backend
|
||||
|
||||
This backend uses AWS S3 bucket for store the user uploaded assets. For use it you should
|
||||
have an appropriate account on AWS cloud and have the credentials, region and the bucket.
|
||||
|
@ -369,11 +474,9 @@ have an appropriate account on AWS cloud and have the credentials, region and th
|
|||
This is how configuration looks for S3 backend:
|
||||
|
||||
```bash
|
||||
# AWS Credentials
|
||||
# Backend
|
||||
AWS_ACCESS_KEY_ID: <you-access-key-id-here>
|
||||
AWS_SECRET_ACCESS_KEY: <your-secret-access-key-here>
|
||||
|
||||
# Backend configuration
|
||||
PENPOT_ASSETS_STORAGE_BACKEND: assets-s3
|
||||
PENPOT_STORAGE_ASSETS_S3_REGION: <aws-region>
|
||||
PENPOT_STORAGE_ASSETS_S3_BUCKET: <bucket-name>
|
||||
|
@ -382,38 +485,11 @@ PENPOT_STORAGE_ASSETS_S3_BUCKET: <bucket-name>
|
|||
PENPOT_STORAGE_ASSETS_S3_ENDPOINT: <endpoint-uri>
|
||||
```
|
||||
|
||||
### Redis
|
||||
|
||||
The redis configuration is very simple, just provide with a valid redis URI. Redis is used
|
||||
mainly for websocket notifications coordination.
|
||||
|
||||
```bash
|
||||
# Backend
|
||||
PENPOT_REDIS_URI: redis://localhost/0
|
||||
```
|
||||
|
||||
If you are using the official docker compose file, this is already configured.
|
||||
|
||||
|
||||
### HTTP
|
||||
|
||||
You will need to set the <code class="language-bash">PENPOT_PUBLIC_URI</code> environment
|
||||
variable in case you go to serve Penpot to the users; it should point to public URI
|
||||
where users will access the application:
|
||||
|
||||
```bash
|
||||
PENPOT_PUBLIC_URI: http://localhost:9001
|
||||
```
|
||||
|
||||
<p class="advice">
|
||||
If you plan to serve Penpot under different domain than `localhost` without HTTPS,
|
||||
you need to disable the `secure` flag on cookies, with the `disable-secure-session-cookies` flag.
|
||||
This is a configuration NOT recommended for production environments.
|
||||
These settings are equally useful if you have a Minio storage system.
|
||||
</p>
|
||||
|
||||
Check all the [flags](#other-flags) to fully customize your instance.
|
||||
|
||||
## Frontend ##
|
||||
## Frontend
|
||||
|
||||
In comparison with backend, frontend only has a small number of runtime configuration
|
||||
options, and they are located in the <code class="language-bash">\<dist>/js/config.js</code> file.
|
||||
|
@ -422,10 +498,7 @@ If you are using the official docker images, the best approach to set any config
|
|||
using environment variables, and the image automatically generates the <code class="language-bash">config.js</code> from
|
||||
them.
|
||||
|
||||
**NOTE**: many frontend related configuration variables are explained in the
|
||||
[Common](#common) section, this section explains **frontend only** options.
|
||||
|
||||
But in case you have a custom setup you probably need setup the following environment
|
||||
In case you have a custom setup, you probably need to configure the following environment
|
||||
variables on the frontend container:
|
||||
|
||||
To connect the frontend to the exporter and backend, you need to fill out these environment variables.
|
||||
|
@ -438,54 +511,36 @@ PENPOT_EXPORTER_URI: http://your-penpot-exporter:6061
|
|||
|
||||
These variables are used for generate correct nginx.conf file on container startup.
|
||||
|
||||
|
||||
### Demo warning ###
|
||||
|
||||
If you want to show a warning in the register and login page saying that this is a
|
||||
demonstration purpose instance (no backups, periodical data wipe, ...), set the following
|
||||
variable:
|
||||
|
||||
```bash
|
||||
PENPOT_FLAGS: [...] enable-demo-warning
|
||||
```
|
||||
|
||||
## Other flags
|
||||
|
||||
There are other flags that are useful for a more customized Penpot experience. This section has the list of the flags meant
|
||||
for the user:
|
||||
|
||||
- <code class="language-bash">enable-cors</code>: Enables the default cors cofiguration that allows all domains
|
||||
(this configuration is designed only for dev purposes right now)
|
||||
- <code class="language-bash">enable-backend-api-doc</code>: Enables the <code class="language-bash">/api/doc</code>
|
||||
endpoint that lists all rpc methods available on backend
|
||||
- <code class="language-bash">disable-email-verification</code>: Deactivates the email verification process
|
||||
(only recommended for local or internal setups)
|
||||
- <code class="language-bash">disable-secure-session-cookies</code>: By default, Penpot uses the
|
||||
<code class="language-bash">secure</code> flag on cookies, this flag disables it;
|
||||
it is useful if you plan to serve Penpot under different
|
||||
domain than <code class="language-bash">localhost</code> without HTTPS
|
||||
- <code class="language-bash">disable-login-with-password</code>: allows disable password based login form
|
||||
- <code class="language-bash">disable-registration</code>: disables registration (still enabled for invitations only).
|
||||
- <code class="language-bash">enable-prepl-server</code>: enables PREPL server, used by manage.py and other additional
|
||||
tools for communicate internally with Penpot backend
|
||||
tools to communicate internally with Penpot backend. Check the [CLI section][5] to get more detail.
|
||||
|
||||
__Since version 1.13.0__
|
||||
|
||||
- <code class="language-bash">enable-log-invitation-tokens</code>: for cases where you don't have email configured, this
|
||||
will log to console the invitation tokens
|
||||
- <code class="language-bash">enable-log-emails</code>: if you want to log in console send emails. This only works if smtp
|
||||
is not configured
|
||||
will log to console the invitation tokens.
|
||||
|
||||
__Since version 2.0.0__
|
||||
|
||||
- <code class="language-bash">disable-onboarding-team</code>: for disable onboarding team creation modal
|
||||
- <code class="language-bash">disable-onboarding-newsletter</code>: for disable onboarding newsletter modal
|
||||
- <code class="language-bash">disable-onboarding-questions</code>: for disable onboarding survey
|
||||
- <code class="language-bash">disable-onboarding</code>: for disable onboarding modal
|
||||
- <code class="language-bash">disable-dashboard-templates-section</code>: for hide the templates section from dashboard
|
||||
- <code class="language-bash">enable-webhooks</code>: for enable webhooks
|
||||
- <code class="language-bash">enable-access-tokens</code>: for enable access tokens
|
||||
- <code class="language-bash">disable-google-fonts-provider</code>: disables the google fonts provider (frontend)
|
||||
- <code class="language-bash">disable-onboarding</code>: disables the onboarding modals.
|
||||
- <code class="language-bash">disable-dashboard-templates-section</code>: hides the templates section from dashboard.
|
||||
- <code class="language-bash">enable-webhooks</code>: enables webhooks. More detail about this configuration in [webhooks section][6].
|
||||
- <code class="language-bash">enable-access-tokens</code>: enables access tokens. More detail about this configuration in [access tokens section][7].
|
||||
- <code class="language-bash">disable-google-fonts-provider</code>: disables the google fonts provider.
|
||||
|
||||
[1]: /technical-guide/getting-started#configure-penpot-with-elestio
|
||||
[2]: /technical-guide/getting-started#configure-penpot-with-docker
|
||||
[3]: /technical-guide/developer/common#dev-environment
|
||||
[4]: https://github.com/penpot/penpot/blob/main/docker/images/files/nginx.conf
|
||||
|
||||
[5]: /technical-guide/getting-started/#using-the-cli-for-administrative-tasks
|
||||
[6]: /technical-guide/integration/#webhooks
|
||||
[7]: /technical-guide/integration/#access-tokens
|
||||
|
|
|
@ -195,23 +195,23 @@ If you want to stop running Penpot, just type
|
|||
docker compose -p penpot -f docker-compose.yaml down
|
||||
```
|
||||
|
||||
|
||||
### Configure Penpot with Docker
|
||||
|
||||
The configuration is defined using environment variables in the <code class="language-bash">docker-compose.yaml</code>
|
||||
file. The default downloaded file already comes with the essential variables already set,
|
||||
The configuration is defined using flags and environment variables in the <code class="language-bash">docker-compose.yaml</code>
|
||||
file. The default downloaded file comes with the essential flags and variables already set,
|
||||
and other ones commented out with some explanations.
|
||||
|
||||
#### Create users using CLI
|
||||
You can find all configuration options in the [Configuration][1] section.
|
||||
|
||||
By default (or when <code class="language-bash">disable-email-verification</code> flag is used), the email verification process
|
||||
is completely disabled for new registrations but it is highly recommended enabling email
|
||||
verification or disabling registration if you are going to expose your penpot instance to
|
||||
the internet.
|
||||
### Using the CLI for administrative tasks
|
||||
|
||||
Penpot provides a script (`manage.py`) with some administrative tasks to perform in the server.
|
||||
|
||||
If you have registration disabled, you can create additional profiles using the
|
||||
command line interface:
|
||||
**NOTE**: this script will only work with the <code class="language-bash">enable-prepl-server</code>
|
||||
flag set in the docker-compose.yaml file. For older versions of docker-compose.yaml file,
|
||||
this flag is set in the backend service.
|
||||
|
||||
For instance, if the registration is disabled, the only way to create a new user is with this script:
|
||||
|
||||
```bash
|
||||
docker exec -ti penpot-penpot-backend-1 python3 manage.py create-profile
|
||||
|
@ -221,12 +221,6 @@ docker exec -ti penpot-penpot-backend-1 python3 manage.py create-profile
|
|||
For example it could be <code class="language-bash">penpot-penpot-backend-1</code> or <code class="language-bash">penpot_penpot-backend-1</code>.
|
||||
You can check the correct name executing <code class="language-bash">docker ps</code>.
|
||||
|
||||
**NOTE:** This script only will works when you properly have the <code class="language-bash">enable-prepl-server</code>
|
||||
flag set on backend (is set by default on the latest docker-compose.yaml file)
|
||||
|
||||
You can find all configuration options in the [Configuration][1] section.
|
||||
|
||||
|
||||
### Update Penpot
|
||||
|
||||
To get the latest version of Penpot in your local installation, you just need to
|
||||
|
|
|
@ -72,6 +72,7 @@
|
|||
(def profile-fetched?
|
||||
(ptk/type? ::profile-fetched))
|
||||
|
||||
;; FIXME: make it as general purpose handler, not only on profile
|
||||
(defn- on-fetch-profile-exception
|
||||
[cause]
|
||||
(let [data (ex-data cause)]
|
||||
|
|
|
@ -206,7 +206,7 @@
|
|||
nil))
|
||||
(rx/of
|
||||
(cond
|
||||
(some? frame-id) (go-to-frame (uuid frame-id))
|
||||
(some? frame-id) (go-to-frame frame-id)
|
||||
(some? index) (go-to-frame-by-index index)
|
||||
:else (go-to-frame-auto)))))))))
|
||||
|
||||
|
|
|
@ -8,12 +8,10 @@
|
|||
(:require
|
||||
[app.common.data :as d]
|
||||
[app.common.data.macros :as dm]
|
||||
[app.common.files.changes-builder :as pcb]
|
||||
[app.common.geom.point :as gpt]
|
||||
[app.common.geom.shapes :as gsh]
|
||||
[app.common.schema :as sm]
|
||||
[app.common.types.shape-tree :as ctst]
|
||||
[app.main.data.changes :as dch]
|
||||
[app.main.data.comments :as dcmt]
|
||||
[app.main.data.common :as dcm]
|
||||
[app.main.data.event :as ev]
|
||||
|
@ -125,38 +123,32 @@
|
|||
{::ev/origin "workspace"}))))))))
|
||||
|
||||
(defn update-comment-thread-position
|
||||
([thread [new-x new-y]]
|
||||
(update-comment-thread-position thread [new-x new-y] nil))
|
||||
([thread [new-x new-y]]
|
||||
(update-comment-thread-position thread [new-x new-y] nil))
|
||||
|
||||
([thread [new-x new-y] frame-id]
|
||||
([thread [new-x new-y] frame-id]
|
||||
(dm/assert!
|
||||
"expected valid comment thread"
|
||||
(dcmt/check-comment-thread! thread))
|
||||
(ptk/reify ::update-comment-thread-position
|
||||
ptk/WatchEvent
|
||||
(watch [it state _]
|
||||
(watch [_ state _]
|
||||
(let [page (dsh/lookup-page state)
|
||||
page-id (:id page)
|
||||
objects (dsh/lookup-page-objects state page-id)
|
||||
frame-id (if (nil? frame-id)
|
||||
(ctst/get-frame-id-by-position objects (gpt/point new-x new-y))
|
||||
(:frame-id thread))
|
||||
|
||||
thread (-> thread
|
||||
(assoc :position (gpt/point new-x new-y))
|
||||
(assoc :frame-id frame-id))
|
||||
|
||||
changes (-> (pcb/empty-changes it)
|
||||
(pcb/with-page page)
|
||||
(pcb/set-comment-thread-position thread))]
|
||||
thread-id (:id thread)]
|
||||
|
||||
(rx/concat
|
||||
(rx/merge
|
||||
(rx/of (dch/commit-changes changes))
|
||||
(->> (rp/cmd! :update-comment-thread-position thread)
|
||||
(rx/catch #(rx/throw {:type :update-comment-thread-position}))
|
||||
(rx/ignore)))
|
||||
(rx/of (dcmt/refresh-comment-thread thread))))))))
|
||||
(rx/of #(update % :comment-threads assoc thread-id thread))
|
||||
(->> (rp/cmd! :update-comment-thread-position thread)
|
||||
(rx/catch #(rx/throw {:type :update-comment-thread-position}))
|
||||
(rx/ignore))))))))
|
||||
|
||||
;; Move comment threads that are inside a frame when that frame is moved"
|
||||
(defmethod ptk/resolve ::move-frame-comment-threads
|
||||
|
|
|
@ -587,11 +587,6 @@
|
|||
:subsections [:shape]
|
||||
:fn #(emit-when-no-readonly (dw/create-bool :exclude))}
|
||||
|
||||
:fit-content-selected {:tooltip (ds/meta-shift (ds/alt "R"))
|
||||
:command (ds/c-mod "shift+alt+r")
|
||||
:subsections [:shape]
|
||||
:fn #(emit-when-no-readonly (dwt/selected-fit-content))}
|
||||
|
||||
;; THEME
|
||||
:toggle-theme {:tooltip (ds/alt "M")
|
||||
:command (ds/a-mod "m")
|
||||
|
|
|
@ -90,22 +90,29 @@
|
|||
(dom/set-data! "fullname" fullname)
|
||||
(obj/set! "textContent" fullname)))
|
||||
|
||||
(defn- current-text-node*
|
||||
"Retrieves the text node and the offset that the cursor is positioned on"
|
||||
[node anchor-node]
|
||||
(when (.contains node anchor-node)
|
||||
(let [span-node (if (instance? js/Text anchor-node)
|
||||
(dom/get-parent anchor-node)
|
||||
anchor-node)
|
||||
container (dom/get-parent span-node)]
|
||||
(when (= node container)
|
||||
span-node))))
|
||||
|
||||
(defn- current-text-node
|
||||
"Retrieves the text node and the offset that the cursor is positioned on"
|
||||
[node]
|
||||
(assert (some? node) "expected valid node")
|
||||
|
||||
(let [selection (wapi/get-selection)
|
||||
range (wapi/get-range selection 0)
|
||||
anchor-node (wapi/range-start-container range)
|
||||
anchor-offset (wapi/range-start-offset range)]
|
||||
(when (and node (.contains node anchor-node))
|
||||
(let [span-node
|
||||
(if (instance? js/Text anchor-node)
|
||||
(dom/get-parent anchor-node)
|
||||
anchor-node)
|
||||
container (dom/get-parent span-node)]
|
||||
(when (= node container)
|
||||
[span-node anchor-offset])))))
|
||||
(when-let [selection (wapi/get-selection)]
|
||||
(let [range (wapi/get-range selection 0)
|
||||
anchor-node (wapi/range-start-container range)
|
||||
offset (wapi/range-start-offset range)
|
||||
span-node (current-text-node* node anchor-node)]
|
||||
(when span-node
|
||||
[span-node offset]))))
|
||||
|
||||
(defn- absolute-offset
|
||||
[node child offset]
|
||||
|
@ -156,7 +163,8 @@
|
|||
mentions-s (mf/use-ctx mentions-context)
|
||||
cur-mention (mf/use-var nil)
|
||||
|
||||
prev-selection (mf/use-var nil)
|
||||
prev-selection-ref
|
||||
(mf/use-ref)
|
||||
|
||||
init-input
|
||||
(mf/use-fn
|
||||
|
@ -203,58 +211,59 @@
|
|||
handle-select
|
||||
(mf/use-fn
|
||||
(fn []
|
||||
(let [node (mf/ref-val local-ref)
|
||||
selection (wapi/get-selection)
|
||||
range (wapi/get-range selection 0)
|
||||
anchor-node (wapi/range-start-container range)]
|
||||
(when (and (= node anchor-node) (.-collapsed range))
|
||||
(wapi/set-cursor-after! anchor-node)))
|
||||
(when-let [node (mf/ref-val local-ref)]
|
||||
(when-let [selection (wapi/get-selection)]
|
||||
(let [range (wapi/get-range selection 0)
|
||||
anchor-node (wapi/range-start-container range)
|
||||
offset (wapi/range-start-offset range)]
|
||||
|
||||
(let [node (mf/ref-val local-ref)
|
||||
[span-node offset] (current-text-node node)
|
||||
[prev-span prev-offset] @prev-selection]
|
||||
(when (and (= node anchor-node) (.-collapsed ^js range))
|
||||
(wapi/set-cursor-after! anchor-node))
|
||||
|
||||
(reset! prev-selection #js [span-node offset])
|
||||
(when-let [span-node (current-text-node* node anchor-node)]
|
||||
(let [[prev-span prev-offset]
|
||||
(mf/ref-val prev-selection-ref)
|
||||
|
||||
(when (= (dom/get-data span-node "type") "mention")
|
||||
(let [from-offset (absolute-offset node prev-span prev-offset)
|
||||
to-offset (absolute-offset node span-node offset)
|
||||
node-text
|
||||
(subs (dom/get-text span-node) 0 offset)
|
||||
|
||||
[_ prev next]
|
||||
(->> node
|
||||
(dom/seq-nodes)
|
||||
(d/with-prev-next)
|
||||
(filter (fn [[elem _ _]] (= elem span-node)))
|
||||
(first))]
|
||||
current-at-symbol
|
||||
(str/last-index-of (subs node-text 0 offset) "@")
|
||||
|
||||
(if (> from-offset to-offset)
|
||||
(wapi/set-cursor-after! prev)
|
||||
(wapi/set-cursor-before! next))))
|
||||
mention-text
|
||||
(subs node-text current-at-symbol)
|
||||
|
||||
(when span-node
|
||||
(let [node-text (subs (dom/get-text span-node) 0 offset)
|
||||
at-symbol-inside-word?
|
||||
(and (> current-at-symbol 0)
|
||||
(str/word? (str/slice node-text (- current-at-symbol 1) current-at-symbol)))]
|
||||
|
||||
current-at-symbol
|
||||
(str/last-index-of (subs node-text 0 offset) "@")
|
||||
(mf/set-ref-val! prev-selection-ref #js [span-node offset])
|
||||
|
||||
mention-text
|
||||
(subs node-text current-at-symbol)
|
||||
(when (= (dom/get-data span-node "type") "mention")
|
||||
(let [from-offset (absolute-offset node prev-span prev-offset)
|
||||
to-offset (absolute-offset node span-node offset)
|
||||
|
||||
at-symbol-inside-word?
|
||||
(and (> current-at-symbol 0)
|
||||
(str/word? (str/slice node-text (- current-at-symbol 1) current-at-symbol)))]
|
||||
[_ prev next]
|
||||
(->> node
|
||||
(dom/seq-nodes)
|
||||
(d/with-prev-next)
|
||||
(filter (fn [[elem _ _]] (= elem span-node)))
|
||||
(first))]
|
||||
(if (> from-offset to-offset)
|
||||
(wapi/set-cursor-after! prev)
|
||||
(wapi/set-cursor-before! next))))
|
||||
|
||||
(if (and (not at-symbol-inside-word?)
|
||||
(re-matches #"@\w*" mention-text))
|
||||
(do
|
||||
(reset! cur-mention mention-text)
|
||||
(rx/push! mentions-s {:type :display-mentions})
|
||||
(let [mention (subs mention-text 1)]
|
||||
(when (d/not-empty? mention)
|
||||
(rx/push! mentions-s {:type :filter-mentions :data mention}))))
|
||||
(do
|
||||
(reset! cur-mention nil)
|
||||
(rx/push! mentions-s {:type :hide-mentions}))))))))
|
||||
(if (and (not at-symbol-inside-word?)
|
||||
(re-matches #"@\w*" mention-text))
|
||||
(do
|
||||
(reset! cur-mention mention-text)
|
||||
(rx/push! mentions-s {:type :display-mentions})
|
||||
(let [mention (subs mention-text 1)]
|
||||
(when (d/not-empty? mention)
|
||||
(rx/push! mentions-s {:type :filter-mentions :data mention}))))
|
||||
(do
|
||||
(reset! cur-mention nil)
|
||||
(rx/push! mentions-s {:type :hide-mentions}))))))))))
|
||||
|
||||
handle-focus
|
||||
(mf/use-fn
|
||||
|
@ -279,9 +288,8 @@
|
|||
(mf/use-fn
|
||||
(mf/deps on-change)
|
||||
(fn [data]
|
||||
(let [node (mf/ref-val local-ref)
|
||||
[span-node offset] (current-text-node node)]
|
||||
(when span-node
|
||||
(when-let [node (mf/ref-val local-ref)]
|
||||
(when-let [[span-node offset] (current-text-node node)]
|
||||
(let [node-text
|
||||
(dom/get-text span-node)
|
||||
|
||||
|
@ -314,8 +322,8 @@
|
|||
handle-insert-at-symbol
|
||||
(mf/use-fn
|
||||
(fn []
|
||||
(let [node (mf/ref-val local-ref) [span-node] (current-text-node node)]
|
||||
(when span-node
|
||||
(when-let [node (mf/ref-val local-ref)]
|
||||
(when-let [[span-node] (current-text-node node)]
|
||||
(let [node-text (dom/get-text span-node)
|
||||
at-symbol (if (blank-content? node-text) "@" " @")]
|
||||
|
||||
|
@ -327,66 +335,62 @@
|
|||
(mf/deps on-esc on-ctrl-enter handle-select handle-input)
|
||||
(fn [event]
|
||||
(handle-select event)
|
||||
(when-let [node (mf/ref-val local-ref)]
|
||||
(when-let [[span-node offset] (current-text-node node)]
|
||||
(cond
|
||||
(and @cur-mention (kbd/enter? event))
|
||||
(do (dom/prevent-default event)
|
||||
(dom/stop-propagation event)
|
||||
(rx/push! mentions-s {:type :insert-selected-mention}))
|
||||
|
||||
(let [node (mf/ref-val local-ref)
|
||||
[span-node offset] (current-text-node node)]
|
||||
(and @cur-mention (kbd/down-arrow? event))
|
||||
(do (dom/prevent-default event)
|
||||
(dom/stop-propagation event)
|
||||
(rx/push! mentions-s {:type :insert-next-mention}))
|
||||
|
||||
(cond
|
||||
(and @cur-mention (kbd/enter? event))
|
||||
(do (dom/prevent-default event)
|
||||
(dom/stop-propagation event)
|
||||
(rx/push! mentions-s {:type :insert-selected-mention}))
|
||||
(and @cur-mention (kbd/up-arrow? event))
|
||||
(do (dom/prevent-default event)
|
||||
(dom/stop-propagation event)
|
||||
(rx/push! mentions-s {:type :insert-prev-mention}))
|
||||
|
||||
(and @cur-mention (kbd/down-arrow? event))
|
||||
(do (dom/prevent-default event)
|
||||
(dom/stop-propagation event)
|
||||
(rx/push! mentions-s {:type :insert-next-mention}))
|
||||
(and @cur-mention (kbd/esc? event))
|
||||
(do (dom/prevent-default event)
|
||||
(dom/stop-propagation event)
|
||||
(rx/push! mentions-s {:type :hide-mentions}))
|
||||
|
||||
(and @cur-mention (kbd/up-arrow? event))
|
||||
(do (dom/prevent-default event)
|
||||
(dom/stop-propagation event)
|
||||
(rx/push! mentions-s {:type :insert-prev-mention}))
|
||||
(and (kbd/esc? event) (fn? on-esc))
|
||||
(on-esc event)
|
||||
|
||||
(and @cur-mention (kbd/esc? event))
|
||||
(do (dom/prevent-default event)
|
||||
(dom/stop-propagation event)
|
||||
(rx/push! mentions-s {:type :hide-mentions}))
|
||||
(and (kbd/mod? event) (kbd/enter? event) (fn? on-ctrl-enter))
|
||||
(on-ctrl-enter event)
|
||||
|
||||
(and (kbd/esc? event) (fn? on-esc))
|
||||
(on-esc event)
|
||||
|
||||
(and (kbd/mod? event) (kbd/enter? event) (fn? on-ctrl-enter))
|
||||
(on-ctrl-enter event)
|
||||
|
||||
(kbd/enter? event)
|
||||
(let [sel (wapi/get-selection)
|
||||
range (.getRangeAt sel 0)]
|
||||
(dom/prevent-default event)
|
||||
(dom/stop-propagation event)
|
||||
(let [[span-node offset] (current-text-node node)]
|
||||
(.deleteContents range)
|
||||
(handle-input)
|
||||
|
||||
(when span-node
|
||||
(let [txt (.-textContent span-node)]
|
||||
(dom/set-html! span-node (dm/str (subs txt 0 offset) "\n" zero-width-space (subs txt offset)))
|
||||
(wapi/set-cursor! span-node (inc offset))
|
||||
(handle-input)))))
|
||||
|
||||
(kbd/backspace? event)
|
||||
(let [prev-node (get-prev-node node span-node)]
|
||||
(when (and (some? prev-node)
|
||||
(= "mention" (dom/get-data prev-node "type"))
|
||||
(= offset 1))
|
||||
(kbd/enter? event)
|
||||
(let [sel (wapi/get-selection)
|
||||
range (.getRangeAt sel 0)]
|
||||
(dom/prevent-default event)
|
||||
(dom/stop-propagation event)
|
||||
(.remove prev-node)))))))]
|
||||
(let [[span-node offset] (current-text-node node)]
|
||||
(.deleteContents range)
|
||||
(handle-input)
|
||||
|
||||
(mf/use-layout-effect
|
||||
(mf/deps autofocus)
|
||||
(fn []
|
||||
(when autofocus
|
||||
(dom/focus! (mf/ref-val local-ref)))))
|
||||
(when span-node
|
||||
(let [txt (.-textContent span-node)]
|
||||
(dom/set-html! span-node (dm/str (subs txt 0 offset) "\n" zero-width-space (subs txt offset)))
|
||||
(wapi/set-cursor! span-node (inc offset))
|
||||
(handle-input)))))
|
||||
|
||||
(kbd/backspace? event)
|
||||
(let [prev-node (get-prev-node node span-node)]
|
||||
(when (and (some? prev-node)
|
||||
(= "mention" (dom/get-data prev-node "type"))
|
||||
(= offset 1))
|
||||
(dom/prevent-default event)
|
||||
(dom/stop-propagation event)
|
||||
(.remove prev-node))))))))]
|
||||
|
||||
(mf/with-layout-effect [autofocus]
|
||||
(when ^boolean autofocus
|
||||
(dom/focus! (mf/ref-val local-ref))))
|
||||
|
||||
;; Creates the handlers for selection
|
||||
(mf/with-effect [handle-select]
|
||||
|
@ -410,12 +414,12 @@
|
|||
|
||||
;; Auto resize input to display the comment
|
||||
(mf/with-layout-effect nil
|
||||
(let [^js node (mf/ref-val local-ref)]
|
||||
(when-let [^js node (mf/ref-val local-ref)]
|
||||
(set! (.-height (.-style node)) "0")
|
||||
(set! (.-height (.-style node)) (str (+ 2 (.-scrollHeight node)) "px"))))
|
||||
|
||||
(mf/with-effect [value prev-value]
|
||||
(let [node (mf/ref-val local-ref)]
|
||||
(when-let [node (mf/ref-val local-ref)]
|
||||
(cond
|
||||
(and (d/not-empty? prev-value) (empty? value))
|
||||
(do (dom/set-html! node "")
|
||||
|
|
|
@ -109,7 +109,11 @@
|
|||
;; avoids some race conditions that causes unexpected redirects
|
||||
;; on invitations workflows (and probably other cases).
|
||||
(->> (rp/cmd! :get-profile)
|
||||
(rx/subs! (fn [{:keys [id] :as profile}]
|
||||
(rx/mapcat (fn [profile]
|
||||
(->> (rp/cmd! :get-teams {})
|
||||
(rx/map (fn [teams]
|
||||
(assoc profile ::teams (into #{} (map :id) teams)))))))
|
||||
(rx/subs! (fn [{:keys [id ::teams] :as profile}]
|
||||
(cond
|
||||
(= id uuid/zero)
|
||||
(do
|
||||
|
@ -117,10 +121,12 @@
|
|||
(st/emit! (rt/nav :auth-login)))
|
||||
|
||||
empty-path?
|
||||
(let [team-id (or (dtm/get-last-team-id)
|
||||
(:default-team-id profile))]
|
||||
(st/emit! (rt/nav :dashboard-recent
|
||||
(assoc query-params :team-id team-id))))
|
||||
(let [team-id (dtm/get-last-team-id)]
|
||||
(if (contains? teams team-id)
|
||||
(st/emit! (rt/nav :dashboard-recent
|
||||
(assoc query-params :team-id team-id)))
|
||||
(st/emit! (rt/nav :dashboard-recent
|
||||
(assoc query-params :team-id (:default-team-id profile))))))
|
||||
|
||||
:else
|
||||
(st/emit! (rt/assign-exception {:type :not-found})))))))))
|
||||
|
|
|
@ -116,7 +116,7 @@
|
|||
|
||||
|
||||
assets-tab
|
||||
(mf/html [:& assets-toolbox {:size (- size 58)}])
|
||||
(mf/html [:& assets-toolbox {:size (- size 58) :file-id file}])
|
||||
|
||||
tokens-tab
|
||||
(when design-tokens?
|
||||
|
|
|
@ -8,6 +8,7 @@
|
|||
(:require-macros [app.main.style :as stl])
|
||||
(:require
|
||||
[app.common.data.macros :as dm]
|
||||
[app.common.types.components-list :as ctkl]
|
||||
[app.main.data.modal :as modal]
|
||||
[app.main.data.workspace :as dw]
|
||||
[app.main.data.workspace.assets :as dwa]
|
||||
|
@ -73,7 +74,7 @@
|
|||
(mf/defc assets-toolbox
|
||||
{::mf/wrap [mf/memo]
|
||||
::mf/wrap-props false}
|
||||
[{:keys [size]}]
|
||||
[{:keys [size file-id]}]
|
||||
(let [components-v2 (mf/use-ctx ctx/components-v2)
|
||||
read-only? (mf/use-ctx ctx/workspace-read-only?)
|
||||
filters* (mf/use-state
|
||||
|
@ -89,7 +90,10 @@
|
|||
section (:section filters)
|
||||
ordering (:ordering filters)
|
||||
reverse-sort? (= :desc ordering)
|
||||
num-libs (count (mf/deref refs/libraries))
|
||||
libs (mf/deref refs/libraries)
|
||||
num-libs (count libs)
|
||||
file (get libs (:id file-id))
|
||||
components (mf/with-memo [file] (ctkl/components (:data file)))
|
||||
|
||||
toggle-ordering
|
||||
(mf/use-fn
|
||||
|
@ -159,7 +163,7 @@
|
|||
[:article {:class (stl/css :assets-bar)}
|
||||
[:div {:class (stl/css :assets-header)}
|
||||
(when-not ^boolean read-only?
|
||||
(if (= num-libs 1)
|
||||
(if (and (= num-libs 1) (empty? components))
|
||||
[:button {:class (stl/css :add-library-button)
|
||||
:on-click show-libraries-dialog
|
||||
:data-testid "libraries"}
|
||||
|
@ -168,9 +172,7 @@
|
|||
[:button {:class (stl/css :libraries-button)
|
||||
:on-click show-libraries-dialog
|
||||
:data-testid "libraries"}
|
||||
[:span {:class (stl/css :libraries-icon)}
|
||||
i/library]
|
||||
(tr "workspace.assets.libraries")]))
|
||||
(tr "workspace.assets.manage-library")]))
|
||||
|
||||
|
||||
[:div {:class (stl/css :search-wrapper)}
|
||||
|
|
|
@ -26,42 +26,22 @@
|
|||
margin-bottom: $s-4;
|
||||
border-radius: $s-8;
|
||||
|
||||
.libraries-icon {
|
||||
@include flexCenter;
|
||||
width: $s-24;
|
||||
height: 100%;
|
||||
|
||||
svg {
|
||||
@include flexCenter;
|
||||
@extend .button-icon;
|
||||
stroke: var(--icon-foreground);
|
||||
}
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background-color: var(--button-secondary-background-color-hover);
|
||||
color: var(--button-secondary-foreground-color-hover);
|
||||
border: $s-1 solid var(--button-secondary-border-color-hover);
|
||||
|
||||
svg {
|
||||
stroke: var(--button-secondary-foreground-color-hover);
|
||||
}
|
||||
}
|
||||
|
||||
&:focus {
|
||||
background-color: var(--button-secondary-background-color-focus);
|
||||
color: var(--button-secondary-foreground-color-focus);
|
||||
border: $s-1 solid var(--button-secondary-border-color-focus);
|
||||
|
||||
svg {
|
||||
stroke: var(--button-secondary-foreground-color-focus);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.add-library-button {
|
||||
@extend .button-primary;
|
||||
text-transform: uppercase;
|
||||
@include uppercaseTitleTipography;
|
||||
gap: $s-2;
|
||||
height: $s-32;
|
||||
width: 100%;
|
||||
|
|
|
@ -301,7 +301,6 @@
|
|||
|
||||
(when show-comments?
|
||||
[:> comments/comments-layer* {:vbox vbox
|
||||
:page-id page-id
|
||||
:file-id file-id
|
||||
:vport vport
|
||||
:zoom zoom
|
||||
|
|
|
@ -9,30 +9,14 @@
|
|||
(:require
|
||||
[app.common.data.macros :as dm]
|
||||
[app.main.data.comments :as dcm]
|
||||
[app.main.data.helpers :as dsh]
|
||||
[app.main.data.workspace.comments :as dwcm]
|
||||
[app.main.refs :as refs]
|
||||
[app.main.store :as st]
|
||||
[app.main.ui.comments :as cmt]
|
||||
[okulary.core :as l]
|
||||
[rumext.v2 :as mf]))
|
||||
|
||||
(defn- update-position
|
||||
[positions {:keys [id] :as thread}]
|
||||
(if (contains? positions id)
|
||||
(-> thread
|
||||
(assoc :position (dm/get-in positions [id :position]))
|
||||
(assoc :frame-id (dm/get-in positions [id :frame-id])))
|
||||
thread))
|
||||
|
||||
(def ^:private ref:thread-positions
|
||||
(l/derived (fn [state]
|
||||
(-> (dsh/lookup-page state)
|
||||
(get :comment-thread-positions)))
|
||||
st/state))
|
||||
|
||||
(mf/defc comments-layer*
|
||||
[{:keys [vbox vport zoom drawing file-id page-id]}]
|
||||
[{:keys [vbox vport zoom drawing file-id]}]
|
||||
(let [vbox-x (dm/get-prop vbox :x)
|
||||
vbox-y (dm/get-prop vbox :y)
|
||||
vport-w (dm/get-prop vport :width)
|
||||
|
@ -44,16 +28,7 @@
|
|||
profile (mf/deref refs/profile)
|
||||
local (mf/deref refs/comments-local)
|
||||
|
||||
positions (mf/deref ref:thread-positions)
|
||||
|
||||
threads-map (mf/deref refs/threads)
|
||||
threads-map (mf/with-memo [threads-map page-id positions]
|
||||
(reduce-kv (fn [threads id thread]
|
||||
(if (= (:page-id thread) page-id)
|
||||
(assoc threads id (update-position positions thread))
|
||||
threads))
|
||||
{}
|
||||
threads-map))
|
||||
|
||||
threads
|
||||
(mf/with-memo [threads-map local profile]
|
||||
|
@ -93,7 +68,7 @@
|
|||
(when-let [thread (get threads-map id)]
|
||||
(when (seq (dcm/apply-filters local profile [thread]))
|
||||
[:> cmt/comment-floating-thread*
|
||||
{:thread (update-position positions thread)
|
||||
{:thread thread
|
||||
:viewport viewport
|
||||
:zoom zoom}])))
|
||||
|
||||
|
|
|
@ -343,7 +343,6 @@
|
|||
|
||||
(when show-comments?
|
||||
[:> comments/comments-layer* {:vbox vbox
|
||||
:page-id page-id
|
||||
:vport vport
|
||||
:zoom zoom
|
||||
:drawing drawing}])
|
||||
|
|
|
@ -282,9 +282,12 @@
|
|||
(.selectAllChildren selection node))
|
||||
|
||||
(defn get-selection
|
||||
"Only returns valid selection"
|
||||
[]
|
||||
(when-let [document globals/document]
|
||||
(.getSelection document)))
|
||||
(let [selection (.getSelection document)]
|
||||
(when (not= (.-type selection) "None")
|
||||
selection))))
|
||||
|
||||
(defn get-anchor-node
|
||||
[^js selection]
|
||||
|
|
|
@ -3618,10 +3618,6 @@ msgstr "Export shapes"
|
|||
msgid "shortcuts.fit-all"
|
||||
msgstr "Zoom to fit all"
|
||||
|
||||
#: src/app/main/ui/workspace/sidebar/shortcuts.cljs:590
|
||||
msgid "shortcuts.fit-content-selected"
|
||||
msgstr "Resize board to fit content"
|
||||
|
||||
#: src/app/main/ui/workspace/sidebar/shortcuts.cljs:113
|
||||
msgid "shortcuts.flip-horizontal"
|
||||
msgstr "Flip horizontally"
|
||||
|
@ -4226,6 +4222,9 @@ msgstr "Align top (%s)"
|
|||
msgid "workspace.assets.add-library"
|
||||
msgstr "Add library"
|
||||
|
||||
msgid "workspace.assets.manage-library"
|
||||
msgstr "Manage library"
|
||||
|
||||
#: src/app/main/ui/workspace/sidebar/assets.cljs
|
||||
#, unused
|
||||
msgid "workspace.assets.assets"
|
||||
|
@ -4295,10 +4294,6 @@ msgstr "Group"
|
|||
msgid "workspace.assets.group-name"
|
||||
msgstr "Group name"
|
||||
|
||||
#: src/app/main/ui/workspace/sidebar/assets.cljs:186
|
||||
msgid "workspace.assets.libraries"
|
||||
msgstr "Libraries"
|
||||
|
||||
#: src/app/main/ui/workspace/sidebar/assets/components.cljs:501
|
||||
msgid "workspace.assets.list-view"
|
||||
msgstr "List view"
|
||||
|
|
|
@ -3514,10 +3514,6 @@ msgstr "Diferencia"
|
|||
msgid "shortcuts.bool-exclude"
|
||||
msgstr "Exclusión"
|
||||
|
||||
#: src/app/main/ui/workspace/sidebar/shortcuts.cljs:590
|
||||
msgid "shortcuts.fit-content-selected"
|
||||
msgstr "Redimensionar para ajustar al contenido"
|
||||
|
||||
#: src/app/main/ui/workspace/sidebar/shortcuts.cljs:86
|
||||
msgid "shortcuts.bool-intersection"
|
||||
msgstr "Interescción"
|
||||
|
@ -4234,6 +4230,9 @@ msgstr "Alinear arriba (%s)"
|
|||
msgid "workspace.assets.add-library"
|
||||
msgstr "Añadir biblioteca"
|
||||
|
||||
msgid "workspace.assets.manage-library"
|
||||
msgstr "Gestionar biblioteca"
|
||||
|
||||
#: src/app/main/ui/workspace/sidebar/assets.cljs
|
||||
#, unused
|
||||
msgid "workspace.assets.assets"
|
||||
|
@ -4305,10 +4304,6 @@ msgstr "Agrupar"
|
|||
msgid "workspace.assets.group-name"
|
||||
msgstr "Nombre del grupo"
|
||||
|
||||
#: src/app/main/ui/workspace/sidebar/assets.cljs:186
|
||||
msgid "workspace.assets.libraries"
|
||||
msgstr "Bibliotecas"
|
||||
|
||||
#: src/app/main/ui/workspace/sidebar/assets/components.cljs:501
|
||||
msgid "workspace.assets.list-view"
|
||||
msgstr "Ver como lista"
|
||||
|
|
Loading…
Add table
Reference in a new issue