Merge pull request #2030 from penpot/eva-palba-share-link

Eva palba share link
This commit is contained in:
Alejandro 2022-06-29 10:55:16 +02:00 committed by GitHub
commit cf2de3cfac
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
40 changed files with 701 additions and 647 deletions

View file

@ -6,6 +6,7 @@
- Allow for nested and rotated boards inside other boards and groups [Taiga #2874](https://tree.taiga.io/project/penpot/us/2874?milestone=319982)
- View mode improvements to enable access and use in different conditions [Taiga #3023](https://tree.taiga.io/project/penpot/us/3023)
- Improved share link options. Now you can allow non-team members to comment and/or inspect [Taiga #3056] (https://tree.taiga.io/project/penpot/us/3056)
### :bug: Bugs fixed
### :arrow_up: Deps updates

View file

@ -228,10 +228,13 @@
:fn (mg/resource "app/migrations/sql/0072-mod-file-object-thumbnail-table.sql")}
{:name "0073-mod-file-media-object-constraints"
:fn (mg/resource "app/migrations/sql/0073-mod-file-media-object-constraints.sql")}
:fn (mg/resource "app/migrations/sql/0073-mod-file-media-object-constraints.sql")}
{:name "0074-mod-file-library-rel-constraints"
:fn (mg/resource "app/migrations/sql/0074-mod-file-library-rel-constraints.sql")}
{:name "0075-mod-share-link-table"
:fn (mg/resource "app/migrations/sql/0075-mod-share-link-table.sql")}
])

View file

@ -0,0 +1,4 @@
ALTER TABLE share_link
ADD COLUMN who_comment text NOT NULL DEFAULT('team'),
ADD COLUMN who_inspect text NOT NULL DEFAULT('team'),
DROP COLUMN flags;

View file

@ -26,19 +26,21 @@
(s/def ::page-id ::us/uuid)
(s/def ::file-id ::us/uuid)
(s/def ::share-id (s/nilable ::us/uuid))
(s/def ::profile-id ::us/uuid)
(s/def ::position ::gpt/point)
(s/def ::content ::us/string)
(s/def ::create-comment-thread
(s/keys :req-un [::profile-id ::file-id ::position ::content ::page-id]))
(s/keys :req-un [::profile-id ::file-id ::position ::content ::page-id]
:opt-un [::share-id]))
(sv/defmethod ::create-comment-thread
{::retry/max-retries 3
::retry/matches retry/conflict-db-insert?}
[{:keys [pool] :as cfg} {:keys [profile-id file-id] :as params}]
[{:keys [pool] :as cfg} {:keys [profile-id file-id share-id] :as params}]
(db/with-atomic [conn pool]
(files/check-read-permissions! conn profile-id file-id)
(files/check-comment-permissions! conn profile-id file-id share-id)
(create-comment-thread conn params)))
(defn- retrieve-next-seqn
@ -92,18 +94,20 @@
;; --- Mutation: Update Comment Thread Status
(s/def ::id ::us/uuid)
(s/def ::share-id (s/nilable ::us/uuid))
(s/def ::update-comment-thread-status
(s/keys :req-un [::profile-id ::id]))
(s/keys :req-un [::profile-id ::id]
:opt-un [::share-id]))
(sv/defmethod ::update-comment-thread-status
[{:keys [pool] :as cfg} {:keys [profile-id id] :as params}]
[{:keys [pool] :as cfg} {:keys [profile-id id share-id] :as params}]
(db/with-atomic [conn pool]
(let [cthr (db/get-by-id conn :comment-thread id {:for-update true})]
(when-not cthr
(ex/raise :type :not-found))
(files/check-read-permissions! conn profile-id (:file-id cthr))
(files/check-comment-permissions! conn profile-id (:file-id cthr) share-id)
(upsert-comment-thread-status! conn profile-id (:id cthr)))))
(def sql:upsert-comment-thread-status
@ -122,16 +126,17 @@
(s/def ::is-resolved ::us/boolean)
(s/def ::update-comment-thread
(s/keys :req-un [::profile-id ::id ::is-resolved]))
(s/keys :req-un [::profile-id ::id ::is-resolved]
:opt-un [::share-id]))
(sv/defmethod ::update-comment-thread
[{:keys [pool] :as cfg} {:keys [profile-id id is-resolved] :as params}]
[{:keys [pool] :as cfg} {:keys [profile-id id is-resolved share-id] :as params}]
(db/with-atomic [conn pool]
(let [thread (db/get-by-id conn :comment-thread id {:for-update true})]
(when-not thread
(ex/raise :type :not-found))
(files/check-read-permissions! conn profile-id (:file-id thread))
(files/check-comment-permissions! conn profile-id (:file-id thread) share-id)
(db/update! conn :comment-thread
{:is-resolved is-resolved}
@ -142,10 +147,11 @@
;; --- Mutation: Add Comment
(s/def ::add-comment
(s/keys :req-un [::profile-id ::thread-id ::content]))
(s/keys :req-un [::profile-id ::thread-id ::content]
:opt-un [::share-id]))
(sv/defmethod ::add-comment
[{:keys [pool] :as cfg} {:keys [profile-id thread-id content] :as params}]
[{:keys [pool] :as cfg} {:keys [profile-id thread-id content share-id] :as params}]
(db/with-atomic [conn pool]
(let [thread (-> (db/get-by-id conn :comment-thread thread-id {:for-update true})
(comments/decode-row))
@ -155,7 +161,7 @@
(when-not thread (ex/raise :type :not-found))
;; Permission Checks
(files/check-read-permissions! conn profile-id (:file-id thread))
(files/check-comment-permissions! conn profile-id (:file-id thread) share-id)
;; Update the page-name cachedattribute on comment thread table.
(when (not= pname (:page-name thread))
@ -199,10 +205,11 @@
;; --- Mutation: Update Comment
(s/def ::update-comment
(s/keys :req-un [::profile-id ::id ::content]))
(s/keys :req-un [::profile-id ::id ::content]
:opt-un [::share-id]))
(sv/defmethod ::update-comment
[{:keys [pool] :as cfg} {:keys [profile-id id content] :as params}]
[{:keys [pool] :as cfg} {:keys [profile-id id content share-id] :as params}]
(db/with-atomic [conn pool]
(let [comment (db/get-by-id conn :comment id {:for-update true})
_ (when-not comment (ex/raise :type :not-found))
@ -210,7 +217,7 @@
_ (when-not thread (ex/raise :type :not-found))
pname (retrieve-page-name conn thread)]
(files/check-read-permissions! conn profile-id (:file-id thread))
(files/check-comment-permissions! conn profile-id (:file-id thread) share-id)
;; Don't allow edit comments to not owners
(when-not (= (:owner-id thread) profile-id)

View file

@ -19,7 +19,8 @@
(s/def ::id ::us/uuid)
(s/def ::profile-id ::us/uuid)
(s/def ::file-id ::us/uuid)
(s/def ::flags (s/every ::us/string :kind set?))
(s/def ::who-comment ::us/string)
(s/def ::who-inspect ::us/string)
(s/def ::pages (s/every ::us/uuid :kind set?))
;; --- Mutation: Create Share Link
@ -27,14 +28,13 @@
(declare create-share-link)
(s/def ::create-share-link
(s/keys :req-un [::profile-id ::file-id ::flags]
:opt-un [::pages]))
(s/keys :req-un [::profile-id ::file-id ::who-comment ::who-inspect ::pages]))
(sv/defmethod ::create-share-link
"Creates a share-link object.
Share links are resources that allows external users access to
specific files with specific permissions (flags)."
Share links are resources that allows external users access to specific
pages of a file with specific permissions (who-comment and who-inspect)."
[{:keys [pool] :as cfg} {:keys [profile-id file-id] :as params}]
(db/with-atomic [conn pool]
@ -42,19 +42,17 @@
(create-share-link conn params)))
(defn create-share-link
[conn {:keys [profile-id file-id pages flags]}]
[conn {:keys [profile-id file-id pages who-comment who-inspect]}]
(let [pages (db/create-array conn "uuid" pages)
flags (->> (map name flags)
(db/create-array conn "text"))
slink (db/insert! conn :share-link
{:id (uuid/next)
:file-id file-id
:flags flags
:who-comment who-comment
:who-inspect who-inspect
:pages pages
:owner-id profile-id})]
(-> slink
(update :pages db/decode-pgarray #{})
(update :flags db/decode-pgarray #{}))))
(update :pages db/decode-pgarray #{}))))
;; --- Mutation: Delete Share Link

View file

@ -53,6 +53,16 @@
([perms] (:can-read perms))
([conn & args] (check (apply qfn conn args)))))
(defn make-comment-predicate-fn
"A simple factory for comment permission predicate functions."
[qfn]
(us/assert fn? qfn)
(fn check
([perms]
(and (:is-logged perms) (= (:who-comment perms) "all")))
([conn & args]
(check (apply qfn conn args)))))
(defn make-check-fn
"Helper that converts a predicate permission function to a check
function (function that raises an exception)."

View file

@ -25,16 +25,16 @@
(s/def ::team-id ::us/uuid)
(s/def ::file-id ::us/uuid)
(s/def ::share-id (s/nilable ::us/uuid))
(s/def ::comment-threads
(s/and (s/keys :req-un [::profile-id]
:opt-un [::file-id ::team-id])
:opt-un [::file-id ::share-id ::team-id])
#(or (:file-id %) (:team-id %))))
(sv/defmethod ::comment-threads
[{:keys [pool] :as cfg} {:keys [profile-id file-id] :as params}]
[{:keys [pool] :as cfg} params]
(with-open [conn (db/open pool)]
(files/check-read-permissions! conn profile-id file-id)
(retrieve-comment-threads conn params)))
(def sql:comment-threads
@ -60,8 +60,8 @@
window w as (partition by c.thread_id order by c.created_at asc)")
(defn- retrieve-comment-threads
[conn {:keys [profile-id file-id]}]
(files/check-read-permissions! conn profile-id file-id)
[conn {:keys [profile-id file-id share-id]}]
(files/check-comment-permissions! conn profile-id file-id share-id)
(->> (db/exec! conn [sql:comment-threads profile-id file-id])
(into [] (map decode-row))))
@ -116,13 +116,15 @@
;; --- Query: Single Comment Thread
(s/def ::id ::us/uuid)
(s/def ::share-id (s/nilable ::us/uuid))
(s/def ::comment-thread
(s/keys :req-un [::profile-id ::file-id ::id]))
(s/keys :req-un [::profile-id ::file-id ::id]
:opt-un [::share-id]))
(sv/defmethod ::comment-thread
[{:keys [pool] :as cfg} {:keys [profile-id file-id id] :as params}]
[{:keys [pool] :as cfg} {:keys [profile-id file-id id share-id] :as params}]
(with-open [conn (db/open pool)]
(files/check-read-permissions! conn profile-id file-id)
(files/check-comment-permissions! conn profile-id file-id share-id)
(let [sql (str "with threads as (" sql:comment-threads ")"
"select * from threads where id = ?")]
(-> (db/exec-one! conn [sql profile-id file-id id])
@ -133,15 +135,17 @@
(declare retrieve-comments)
(s/def ::file-id ::us/uuid)
(s/def ::share-id (s/nilable ::us/uuid))
(s/def ::thread-id ::us/uuid)
(s/def ::comments
(s/keys :req-un [::profile-id ::thread-id]))
(s/keys :req-un [::profile-id ::thread-id]
:opt-un [::share-id]))
(sv/defmethod ::comments
[{:keys [pool] :as cfg} {:keys [profile-id thread-id] :as params}]
[{:keys [pool] :as cfg} {:keys [profile-id thread-id share-id] :as params}]
(with-open [conn (db/open pool)]
(let [thread (db/get-by-id conn :comment-thread thread-id)]
(files/check-read-permissions! conn profile-id (:file-id thread))
(files/check-comment-permissions! conn profile-id (:file-id thread) share-id)
(retrieve-comments conn thread-id))))
(def sql:comments
@ -153,3 +157,40 @@
[conn thread-id]
(->> (db/exec! conn [sql:comments thread-id])
(into [] (map decode-row))))
;; file-comments-users
(declare retrieve-file-comments-users)
(s/def ::file-id ::us/uuid)
(s/def ::share-id (s/nilable ::us/uuid))
(s/def ::file-comments-users
(s/keys :req-un [::profile-id ::file-id]
:opt-un [::share-id]))
(sv/defmethod ::file-comments-users
[{:keys [pool] :as cfg} {:keys [profile-id file-id share-id]}]
(with-open [conn (db/open pool)]
(files/check-comment-permissions! conn profile-id file-id share-id)
(retrieve-file-comments-users conn file-id profile-id)))
(def sql:file-comment-users
"select p.id,
p.email,
p.fullname as name,
p.fullname as fullname,
p.photo_id,
p.is_active
from profile p
where p.id in
(select owner_id from comment
where thread_id in
(select id from comment_thread
where file_id=?))
or p.id=?
") ;; all the users that had comment the file, plus the current user
(defn retrieve-file-comments-users
[conn file-id profile-id]
(db/exec! conn [sql:file-comment-users file-id profile-id]))

View file

@ -98,7 +98,9 @@
(some? perms) perms
(some? ldata) {:type :share-link
:can-read true
:flags (:flags ldata)}))))
:is-logged (some? profile-id)
:who-comment (:who-comment ldata)
:who-inspect (:who-inspect ldata)}))))
(def has-edit-permissions?
(perms/make-edition-predicate-fn get-permissions))
@ -106,12 +108,26 @@
(def has-read-permissions?
(perms/make-read-predicate-fn get-permissions))
(def has-comment-permissions?
(perms/make-comment-predicate-fn get-permissions))
(def check-edition-permissions!
(perms/make-check-fn has-edit-permissions?))
(def check-read-permissions!
(perms/make-check-fn has-read-permissions?))
;; A user has comment permissions if she has read permissions, or comment permissions
(defn check-comment-permissions!
[conn profile-id file-id share-id]
(let [can-read (has-read-permissions? conn profile-id file-id)
can-comment (has-comment-permissions? conn profile-id file-id share-id)
]
(when-not (or can-read can-comment)
(ex/raise :type :not-found
:code :object-not-found
:hint "not found"))))
;; --- Query: Files search
;; TODO: this query need to a good refactor

View file

@ -11,7 +11,6 @@
(defn decode-share-link-row
[row]
(-> row
(update :flags db/decode-pgarray #{})
(update :pages db/decode-pgarray #{})))
(defn retrieve-share-link

View file

@ -9,9 +9,9 @@
[app.common.exceptions :as ex]
[app.common.spec :as us]
[app.db :as db]
[app.rpc.queries.comments :as comments]
[app.rpc.queries.files :as files]
[app.rpc.queries.share-link :as slnk]
[app.rpc.queries.teams :as teams]
[app.rpc.queries.share-link :as slnk]
[app.util.services :as sv]
[clojure.spec.alpha :as s]
[promesa.core :as p]))
@ -23,11 +23,11 @@
(db/get-by-id pool :project id {:columns [:id :name :team-id]}))
(defn- retrieve-bundle
[{:keys [pool] :as cfg} file-id]
[{:keys [pool] :as cfg} file-id profile-id]
(p/let [file (files/retrieve-file cfg file-id)
project (retrieve-project pool (:project-id file))
libs (files/retrieve-file-libraries cfg false file-id)
users (teams/retrieve-users pool (:team-id project))
users (comments/retrieve-file-comments-users pool file-id profile-id)
links (->> (db/query pool :share-link {:file-id file-id})
(mapv slnk/decode-share-link-row))
@ -54,7 +54,7 @@
(p/let [slink (slnk/retrieve-share-link pool file-id share-id)
perms (files/get-permissions pool profile-id file-id share-id)
thumbs (files/retrieve-object-thumbnails cfg file-id)
bundle (p/-> (retrieve-bundle cfg file-id)
bundle (p/-> (retrieve-bundle cfg file-id profile-id)
(assoc :permissions perms)
(assoc-in [:file :thumbnails] thumbs))]

View file

@ -49,7 +49,8 @@
:profile-id (:id prof)
:file-id (:id file)
:pages #{(get-in file [:data :pages 0])}
:flags #{}}
:who-comment "team"
:who-inspect "all"}
out (th/mutation! data)]
;; (th/print-result! out)

View file

@ -1,139 +1,240 @@
.share-link-dialog {
width: 475px;
background-color: $color-white;
.share-modal {
display: block;
top: 50px;
left: calc(100vw - 500px);
.modal-footer {
display: flex;
align-items: center;
justify-content: flex-end;
height: unset;
padding: 16px 26px;
.share-link-dialog {
width: 480px;
background-color: $color-white;
.btn-primary,
.btn-secondary,
.btn-warning {
width: 126px;
margin-bottom: 0px;
&:not(:last-child) {
margin-right: 10px;
}
}
.confirm-dialog {
display: flex;
flex-direction: column;
background-color: unset;
.description {
font-size: $fs14;
margin-bottom: 16px;
}
.actions {
.modal-content {
padding: 16px 32px;
&:first-child {
border-top: 0px;
padding: 0;
height: 50px;
display: flex;
justify-content: flex-end;
justify-content: center;
}
}
}
.modal-content {
padding: 26px;
&:first-child {
border-top: 0px;
}
.title {
display: flex;
justify-content: space-between;
h2 {
font-size: $fs18;
color: $color-black;
}
.modal-close-button {
margin-right: 0px;
}
}
.share-link-section {
margin-top: 12px;
label {
font-size: $fs12;
color: $color-black;
}
.hint {
padding-top: 10px;
font-size: $fs14;
color: $color-gray-40;
}
.help-icon {
cursor: pointer;
}
}
.view-mode,
.access-mode {
display: flex;
flex-direction: column;
.title {
color: $color-black;
font-weight: 400;
display: flex;
justify-content: space-between;
align-items: center;
height: 100%;
margin-left: 32px;
h2 {
font-size: $fs18;
color: $color-black;
}
.modal-close-button {
margin-right: 16px;
}
}
.items {
padding-left: 20px;
display: flex;
> .input-checkbox,
> .input-radio {
.share-link-section {
.custom-input {
display: flex;
user-select: none;
/* input { */
/* appearance: checkbox; */
/* } */
label {
flex-direction: row;
margin-bottom: 15px;
border: 1px solid $color-gray-20;
input {
padding: 0 0 0 15px;
border: none;
}
}
.hint-wrapper {
display: flex;
justify-content: space-between;
align-items: center;
.hint {
font-size: $fs12;
color: $color-gray-40;
}
.confirm-dialog {
display: flex;
align-items: center;
color: $color-black;
flex-direction: column;
background-color: unset;
.actions {
display: flex;
justify-content: flex-end;
gap: 16px;
}
.description {
font-size: $fs12;
margin-bottom: 16px;
color: $color-black;
}
.btn-primary,
.btn-secondary,
.btn-warning {
width: 126px;
margin-bottom: 0px;
.hint {
margin-left: 5px;
&:not(:last-child) {
margin-right: 10px;
}
}
}
}
label {
font-size: $fs12;
color: $color-black;
}
.help-icon {
height: 40px;
width: 40px;
display: flex;
justify-content: center;
align-items: center;
cursor: pointer;
position: relative;
right: 0;
top: 0;
background-color: $color-gray-10;
border-left: 1px solid $color-gray-20;
svg {
fill: $color-gray-30;
}
&:hover {
background-color: $color-primary;
svg {
fill: $color-gray-60;
}
}
}
input {
margin: 0;
}
}
&.ops-section {
.manage-permissions {
display: flex;
color: $color-primary-dark;
font-size: $fs12;
cursor: pointer;
.icon {
svg {
height: 16px;
width: 16px;
fill: $color-primary-dark;
}
}
.title {
margin-left: 8px;
}
}
.view-mode {
min-height: 34px;
.subtitle {
height: 32px;
}
.row {
display: flex;
justify-content: space-between;
align-items: center;
.count-pages {
font-size: $fs12;
color: $color-gray-30;
}
}
&.disabled {
label {
color: $color-gray-30;
.current-tag {
font-size: $fs12;
color: $color-gray-30;
}
label {
color: $color-black;
}
}
.access-mode,
.inspect-mode {
display: grid;
grid-template-columns: auto 1fr;
.items {
display: flex;
justify-content: flex-end;
align-items: center;
}
}
.view-mode,
.access-mode,
.inspect-mode {
margin: 8px 0;
.subtitle {
display: flex;
justify-content: flex-start;
align-items: center;
color: $color-black;
font-size: $fs16;
.icon {
display: flex;
justify-content: center;
align-items: center;
margin-right: 10px;
svg {
height: 16px;
width: 16px;
}
}
}
.items {
.input-select {
background-image: url("/images/icons/arrow-down.svg");
margin: 0;
padding-right: 28px;
border: 1px solid $color-gray-10;
max-width: 227px;
text-overflow: ellipsis;
overflow: hidden;
white-space: nowrap;
}
> .input-radio {
display: flex;
user-select: none;
margin-top: 0;
margin-bottom: 0;
label {
display: flex;
align-items: center;
color: $color-black;
max-width: 115px;
&::before {
height: 16px;
width: 16px;
}
.hint {
margin-left: 5px;
color: $color-gray-30;
}
}
&.disabled {
label {
color: $color-gray-30;
}
}
}
}
}
.pages-selection {
border-top: 1px solid $color-gray-10;
border-bottom: 1px solid $color-gray-10;
padding-left: 20px;
max-height: 200px;
overflow-y: scroll;
user-select: none;
label {
color: $color-black;
}
}
}
}
.pages-selection {
padding-left: 20px;
max-height: 200px;
overflow-y: scroll;
user-select: none;
label {
color: $color-black;
}
}
.custom-input {
input {
padding: 0 40px 0 15px;
}
}
}
}

View file

@ -75,20 +75,23 @@
(ptk/reify ::create-comment-thread
ptk/WatchEvent
(watch [_ _ _]
(->> (rp/mutation :create-comment-thread params)
(rx/mapcat #(rp/query :comment-thread {:file-id (:file-id %) :id (:id %)}))
(rx/map #(partial created %))
(rx/catch #(rx/throw {:type :comment-error})))))))
(watch [_ state _]
(let [share-id (-> state :viewer-local :share-id)
params (assoc params :share-id share-id)]
(->> (rp/mutation :create-comment-thread params)
(rx/mapcat #(rp/query :comment-thread {:file-id (:file-id %) :id (:id %) :share-id share-id}))
(rx/map #(partial created %))
(rx/catch #(rx/throw {:type :comment-error}))))))))
(defn update-comment-thread-status
[{:keys [id] :as thread}]
(us/assert ::comment-thread thread)
(ptk/reify ::update-comment-thread-status
ptk/WatchEvent
(watch [_ _ _]
(let [done #(d/update-in-when % [:comment-threads id] assoc :count-unread-comments 0)]
(->> (rp/mutation :update-comment-thread-status {:id id})
(watch [_ state _]
(let [done #(d/update-in-when % [:comment-threads id] assoc :count-unread-comments 0)
share-id (-> state :viewer-local :share-id)]
(->> (rp/mutation :update-comment-thread-status {:id id :share-id share-id})
(rx/map (constantly done))
(rx/catch #(rx/throw {:type :comment-error})))))))
@ -105,10 +108,11 @@
(d/update-in-when state [:comment-threads id] assoc :is-resolved is-resolved))
ptk/WatchEvent
(watch [_ _ _]
(->> (rp/mutation :update-comment-thread {:id id :is-resolved is-resolved})
(rx/catch #(rx/throw {:type :comment-error}))
(rx/ignore)))))
(watch [_ state _]
(let [share-id (-> state :viewer-local :share-id)]
(->> (rp/mutation :update-comment-thread {:id id :is-resolved is-resolved :share-id share-id})
(rx/catch #(rx/throw {:type :comment-error}))
(rx/ignore))))))
(defn add-comment
@ -119,12 +123,13 @@
(update-in state [:comments (:id thread)] assoc (:id comment) comment))]
(ptk/reify ::create-comment
ptk/WatchEvent
(watch [_ _ _]
(rx/concat
(->> (rp/mutation :add-comment {:thread-id (:id thread) :content content})
(rx/map #(partial created %))
(rx/catch #(rx/throw {:type :comment-error})))
(rx/of (refresh-comment-thread thread)))))))
(watch [_ state _]
(let [share-id (-> state :viewer-local :share-id)]
(rx/concat
(->> (rp/mutation :add-comment {:thread-id (:id thread) :content content :share-id share-id})
(rx/map #(partial created %))
(rx/catch #(rx/throw {:type :comment-error})))
(rx/of (refresh-comment-thread thread))))))))
(defn update-comment
[{:keys [id content thread-id] :as comment}]
@ -135,10 +140,11 @@
(d/update-in-when state [:comments thread-id id] assoc :content content))
ptk/WatchEvent
(watch [_ _ _]
(->> (rp/mutation :update-comment {:id id :content content})
(rx/catch #(rx/throw {:type :comment-error}))
(rx/ignore)))))
(watch [_ state _]
(let [share-id (-> state :viewer-local :share-id)]
(->> (rp/mutation :update-comment {:id id :content content :share-id share-id})
(rx/catch #(rx/throw {:type :comment-error}))
(rx/ignore))))))
(defn delete-comment-thread
[{:keys [id] :as thread}]
@ -151,10 +157,11 @@
(update :comment-threads dissoc id)))
ptk/WatchEvent
(watch [_ _ _]
(->> (rp/mutation :delete-comment-thread {:id id})
(rx/catch #(rx/throw {:type :comment-error}))
(rx/ignore)))))
(watch [_ state _]
(let [share-id (-> state :viewer-local :share-id)]
(->> (rp/mutation :delete-comment-thread {:id id :share-id share-id})
(rx/catch #(rx/throw {:type :comment-error}))
(rx/ignore))))))
(defn delete-comment
[{:keys [id thread-id] :as comment}]
@ -165,10 +172,11 @@
(d/update-in-when state [:comments thread-id] dissoc id))
ptk/WatchEvent
(watch [_ _ _]
(->> (rp/mutation :delete-comment {:id id})
(rx/catch #(rx/throw {:type :comment-error}))
(rx/ignore)))))
(watch [_ state _]
(let [share-id (-> state :viewer-local :share-id)]
(->> (rp/mutation :delete-comment {:id id :share-id share-id})
(rx/catch #(rx/throw {:type :comment-error}))
(rx/ignore))))))
(defn refresh-comment-thread
[{:keys [id file-id] :as thread}]
@ -177,10 +185,11 @@
(assoc-in state [:comment-threads id] thread))]
(ptk/reify ::refresh-comment-thread
ptk/WatchEvent
(watch [_ _ _]
(->> (rp/query :comment-thread {:file-id file-id :id id})
(rx/map #(partial fetched %))
(rx/catch #(rx/throw {:type :comment-error})))))))
(watch [_ state _]
(let [share-id (-> state :viewer-local :share-id)]
(->> (rp/query :comment-thread {:file-id file-id :id id :share-id share-id})
(rx/map #(partial fetched %))
(rx/catch #(rx/throw {:type :comment-error}))))))))
(defn retrieve-comment-threads
[file-id]
@ -189,10 +198,11 @@
(assoc state :comment-threads (d/index-by :id data)))]
(ptk/reify ::retrieve-comment-threads
ptk/WatchEvent
(watch [_ _ _]
(->> (rp/query :comment-threads {:file-id file-id})
(rx/map #(partial fetched %))
(rx/catch #(rx/throw {:type :comment-error})))))))
(watch [_ state _]
(let [share-id (-> state :viewer-local :share-id)]
(->> (rp/query :comment-threads {:file-id file-id :share-id share-id})
(rx/map #(partial fetched %))
(rx/catch #(rx/throw {:type :comment-error}))))))))
(defn retrieve-comments
[thread-id]
@ -201,10 +211,11 @@
(update state :comments assoc thread-id (d/index-by :id comments)))]
(ptk/reify ::retrieve-comments
ptk/WatchEvent
(watch [_ _ _]
(->> (rp/query :comments {:thread-id thread-id})
(rx/map #(partial fetched %))
(rx/catch #(rx/throw {:type :comment-error})))))))
(watch [_ state _]
(let [share-id (-> state :viewer-local :share-id)]
(->> (rp/query :comments {:thread-id thread-id :share-id share-id})
(rx/map #(partial fetched %))
(rx/catch #(rx/throw {:type :comment-error}))))))))
(defn retrieve-unread-comment-threads
"A event used mainly in dashboard for retrieve all unread threads of a team."

View file

@ -436,7 +436,6 @@
(rx/map (constantly (fetch-profile)))
(rx/catch on-error))))))
(defn fetch-users
[{:keys [team-id] :as params}]
(us/assert ::us/uuid team-id)
@ -450,6 +449,20 @@
(->> (rp/query :team-users {:team-id team-id})
(rx/map #(partial fetched %)))))))
(defn fetch-file-comments-users
[{:keys [team-id] :as params}]
(us/assert ::us/uuid team-id)
(letfn [(fetched [users state]
(->> users
(d/index-by :id)
(assoc state :file-comments-users)))]
(ptk/reify ::fetch-team-users
ptk/WatchEvent
(watch [_ state _]
(let [share-id (-> state :viewer-local :share-id)]
(->> (rp/query :file-comments-users {:team-id team-id :share-id share-id})
(rx/map #(partial fetched %))))))))
;; --- EVENT: request-account-deletion
(defn request-account-deletion

View file

@ -33,7 +33,9 @@
:selected #{}
:collapsed #{}
:overlays []
:hover nil})
:hover nil
:share-id ""
:file-comments-users []})
(declare fetch-comment-threads)
(declare fetch-bundle)
@ -50,7 +52,7 @@
:opt-un [::share-id ::page-id]))
(defn initialize
[{:keys [file-id] :as params}]
[{:keys [file-id share-id] :as params}]
(us/assert ::initialize-params params)
(ptk/reify ::initialize
ptk/UpdateEvent
@ -61,7 +63,8 @@
(fn [lstate]
(if (nil? lstate)
default-local-state
lstate)))))
lstate)))
(assoc-in [:viewer-local :share-id] share-id)))
ptk/WatchEvent
(watch [_ _ _]
@ -138,7 +141,7 @@
(rx/of (go-to-frame-auto))))))))
(defn fetch-comment-threads
[{:keys [file-id page-id] :as params}]
[{:keys [file-id page-id share-id] :as params}]
(letfn [(fetched [data state]
(->> data
(filter #(= page-id (:page-id %)))
@ -153,7 +156,7 @@
(ptk/reify ::fetch-comment-threads
ptk/WatchEvent
(watch [_ _ _]
(->> (rp/query :comment-threads {:file-id file-id})
(->> (rp/query :comment-threads {:file-id file-id :share-id share-id})
(rx/map #(partial fetched %))
(rx/catch on-error))))))

View file

@ -140,7 +140,7 @@
(unchecked-set ug/global "name" name)))))
(defn- file-initialized
[{:keys [file users project libraries] :as bundle}]
[{:keys [file users project libraries file-comments-users] :as bundle}]
(ptk/reify ::file-initialized
ptk/UpdateEvent
(update [_ state]
@ -156,7 +156,8 @@
;; the version number
#_(assoc :version 17)
#_(app.common.pages.migrations/migrate-data 19))
:workspace-libraries (d/index-by :id libraries)))
:workspace-libraries (d/index-by :id libraries)
:current-file-comments-users (d/index-by :id file-comments-users)))
ptk/WatchEvent
(watch [_ _ _]

View file

@ -258,20 +258,23 @@
[project-id file-id]
(ptk/reify ::fetch-bundle
ptk/WatchEvent
(watch [_ _ _]
(->> (rx/zip (rp/query :file-raw {:id file-id})
(rp/query :team-users {:file-id file-id})
(rp/query :project {:id project-id})
(rp/query :file-libraries {:file-id file-id}))
(rx/take 1)
(rx/map (fn [[file-raw users project libraries]]
{:file-raw file-raw
:users users
:project project
:libraries libraries}))
(rx/mapcat (fn [{:keys [project] :as bundle}]
(rx/of (ptk/data-event ::bundle-fetched bundle)
(df/load-team-fonts (:team-id project)))))))))
(watch [_ state _]
(let [share-id (-> state :viewer-local :share-id)]
(->> (rx/zip (rp/query :file-raw {:id file-id})
(rp/query :team-users {:file-id file-id})
(rp/query :project {:id project-id})
(rp/query :file-libraries {:file-id file-id})
(rp/query :file-comments-users {:file-id file-id :share-id share-id}))
(rx/take 1)
(rx/map (fn [[file-raw users project libraries file-comments-users]]
{:file-raw file-raw
:users users
:project project
:libraries libraries
:file-comments-users file-comments-users}))
(rx/mapcat (fn [{:keys [project] :as bundle}]
(rx/of (ptk/data-event ::bundle-fetched bundle)
(df/load-team-fonts (:team-id project))))))))))
;; --- Helpers

View file

@ -387,6 +387,9 @@
(def users
(l/derived :users st/state))
(def current-file-comments-users
(l/derived :current-file-comments-users st/state))
(def viewer-fullscreen?
(l/derived (fn [state]
(dm/get-in state [:viewer-local :fullscreen?]))

View file

@ -345,7 +345,6 @@
(mf/defc comment-thread
[{:keys [item users on-click] :as props}]
(let [owner (get users (:owner-id item))
on-click*
(mf/use-callback
(mf/deps item)

View file

@ -31,7 +31,7 @@
show-dropdown (mf/use-fn #(reset! show-dropdown? true))
hide-dropdown (mf/use-fn #(reset! show-dropdown? false))
threads-map (mf/deref refs/comment-threads)
users (mf/deref refs/users)
users (mf/deref refs/current-file-comments-users)
tgroups (->> (vals threads-map)
(sort-by :modified-at)

View file

@ -24,53 +24,66 @@
(log/set-level! :warn)
(defn prepare-params
[{:keys [sections pages pages-mode]}]
{:pages pages
:flags (-> #{}
(into (map #(str "section-" %)) sections)
(into (map #(str "pages-" %)) [pages-mode]))})
[{:keys [pages who-comment who-inspect]}]
{:pages pages
:who-comment who-comment
:who-inspect who-inspect})
(mf/defc share-link-dialog
{::mf/register modal/components
::mf/register-as :share-link}
[{:keys [file page]}]
(let [slinks (mf/deref refs/share-links)
router (mf/deref refs/router)
route (mf/deref refs/route)
(let [current-page page
slinks (mf/deref refs/share-links)
router (mf/deref refs/router)
route (mf/deref refs/route)
link (mf/use-state nil)
confirm (mf/use-state false)
link (mf/use-state nil)
confirm (mf/use-state false)
open-ops (mf/use-state false)
opts (mf/use-state
{:pages-mode "current"
:all-pages false
:pages #{(:id page)}
:who-comment "team"
:who-inspect "team"})
opts (mf/use-state
{:sections #{"viewer"}
:pages-mode "current"
:pages #{(:id page)}})
close
(fn [event]
(dom/prevent-default event)
(st/emit! (modal/hide)))
(st/emit! (modal/hide))
(modal/disallow-click-outside!))
select-pages-mode
(fn [mode]
toggle-all
(fn []
(reset! confirm false)
(swap! opts
(fn [state]
(-> state
(assoc :pages-mode mode)
(cond-> (= mode "current") (assoc :pages #{(:id page)}))
(cond-> (= mode "all") (assoc :pages (into #{} (get-in file [:data :pages]))))))))
(if (= true (:all-pages state))
(-> state
(assoc :all-pages false)
(assoc :pages #{(:id page)}))
(-> state
(assoc :all-pages true)
(assoc :pages (into #{} (get-in file [:data :pages]))))))))
mark-checked-page
(fn [event id]
(let [target (dom/get-target event)
checked? (.-checked ^js target)]
(reset! confirm false)
(swap! opts update :pages
(fn [pages]
(if checked?
(conj pages id)
(disj pages id))))))
checked? (.-checked ^js target)
dif-pages? (not= id (first (:pages @opts)))
no-one-page (< 1 (count (:pages @opts)))
should-change (or no-one-page dif-pages?)]
(when should-change
(reset! confirm false)
(swap! opts update :pages
(fn [pages]
(if checked?
(conj pages id)
(disj pages id)))))))
create-link
(fn [_]
@ -83,7 +96,7 @@
(wapi/write-to-clipboard @link)
(st/emit! (dm/show {:type :info
:content (tr "common.share-link.link-copied-success")
:timeout 3000})))
:timeout 1000})))
try-delete-link
(fn [_]
@ -94,17 +107,27 @@
(let [params (prepare-params @opts)
slink (d/seek #(= (:flags %) (:flags params)) slinks)]
(reset! confirm false)
(st/emit! (dc/delete-share-link slink)
(dm/show {:type :info
:content (tr "common.share-link.link-deleted-success")
:timeout 3000}))))
]
(st/emit! (dc/delete-share-link slink))))
manage-open-ops
(fn [_]
(swap! open-ops not))
on-who-change
(fn [type event]
(let [target (dom/get-target event)
value (dom/get-value target)
value (keyword value)]
(reset! confirm false)
(if (= type :comment)
(swap! opts assoc :who-comment (d/name value))
(swap! opts assoc :who-inspect (d/name value)))))]
(mf/use-effect
(mf/deps file slinks @opts)
(fn []
(let [{:keys [flags pages] :as params} (prepare-params @opts)
slink (d/seek #(and (= (:flags %) flags) (= (:pages %) pages)) slinks)
(let [{:keys [pages who-comment who-inspect] :as params} (prepare-params @opts)
slink (d/seek #(and (= (:who-inspect %) who-inspect) (= (:who-comment %) who-comment) (= (:pages %) pages)) slinks)
href (when slink
(let [pparams (:path-params route)
qparams (-> (:query-params route)
@ -114,123 +137,123 @@
(assoc cf/public-uri :fragment href)))]
(reset! link (some-> href str)))))
[:div.modal-overlay
[:div.modal-overlay.share-modal
[:div.modal-container.share-link-dialog
[:div.modal-content
[:div.modal-content.initial
[:div.title
[:h2 (tr "common.share-link.title")]
[:div.modal-close-button
{:on-click close
:title (tr "labels.close")}
i/close]]
[:div.share-link-section
[:label (tr "labels.link")]
[:div.custom-input.with-icon
[:input {:type "text"
:value (or @link "")
:placeholder (tr "common.share-link.placeholder")
:read-only true}]
(when (some? @link)
[:div.help-icon {:title (tr "labels.copy")
:on-click copy-link}
i/copy])]
[:div.hint (tr "common.share-link.permissions-hint")]]]
i/close]]]
[:div.modal-content
(let [sections (:sections @opts)]
[:div.access-mode
[:div.title (tr "common.share-link.permissions-can-access")]
[:div.items
[:div.input-checkbox.check-primary.disabled
[:input.check-primary.input-checkbox {:type "checkbox" :disabled true}]
[:label (tr "labels.workspace")]]
[:div.share-link-section
(when (and (not @confirm) (some? @link))
[:div.custom-input.with-icon
[:input {:type "text"
:value (or @link "")
:placeholder (tr "common.share-link.placeholder")
:read-only true}]
[:div.help-icon {:title (tr "viewer.header.share.copy-link")
:on-click copy-link}
i/copy]])
[:div.hint-wrapper
(when (not @confirm) [:div.hint (tr "common.share-link.permissions-hint")])
(cond
(true? @confirm)
[:div.confirm-dialog
[:div.description (tr "common.share-link.confirm-deletion-link-description")]
[:div.actions
[:input.btn-secondary
{:type "button"
:on-click #(reset! confirm false)
:value (tr "labels.cancel")}]
[:input.btn-warning
{:type "button"
:on-click delete-link
:value (tr "common.share-link.destroy-link")}]]]
[:div.input-checkbox.check-primary
[:input {:type "checkbox"
:default-checked (contains? sections "viewer")}]
[:label (tr "labels.viewer")
[:span.hint "(" (tr "labels.default") ")"]]]
;; [:div.input-checkbox.check-primary
;; [:input.check-primary.input-checkbox {:type "checkbox"}]
;; [:label "Handoff" ]]
]])
(let [mode (:pages-mode @opts)]
[:*
[:div.view-mode
[:div.title (tr "common.share-link.permissions-can-view")]
[:div.items
[:div.input-radio.radio-primary
[:input {:type "radio"
:id "view-all"
:checked (= "all" mode)
:name "pages-mode"
:on-change #(select-pages-mode "all")}]
[:label {:for "view-all"} (tr "common.share-link.view-all-pages")]]
[:div.input-radio.radio-primary
[:input {:type "radio"
:id "view-current"
:name "pages-mode"
:checked (= "current" mode)
:on-change #(select-pages-mode "current")}]
[:label {:for "view-current"} (tr "common.share-link.view-current-page")]]
[:div.input-radio.radio-primary
[:input {:type "radio"
:id "view-selected"
:name "pages-mode"
:checked (= "selected" mode)
:on-change #(select-pages-mode "selected")}]
[:label {:for "view-selected"} (tr "common.share-link.view-selected-pages")]]]]
(when (= "selected" mode)
(let [pages (->> (get-in file [:data :pages])
(map #(get-in file [:data :pages-index %])))
selected (:pages @opts)]
[:ul.pages-selection
(for [page pages]
[:li.input-checkbox.check-primary {:key (str (:id page))}
[:input {:type "checkbox"
:id (str "page-" (:id page))
:on-change #(mark-checked-page % (:id page))
:checked (contains? selected (:id page))}]
[:label {:for (str "page-" (:id page))} (:name page)]])]))])]
[:div.modal-footer
(cond
(true? @confirm)
[:div.confirm-dialog
[:div.description (tr "common.share-link.confirm-deletion-link-description")]
[:div.actions
(some? @link)
[:input.btn-secondary
{:type "button"
:on-click #(reset! confirm false)
:value (tr "labels.cancel")}]
[:input.btn-warning
:class "primary"
:on-click try-delete-link
:value (tr "common.share-link.destroy-link")}]
:else
[:input.btn-primary
{:type "button"
:on-click delete-link
:value (tr "common.share-link.remove-link")
}]]]
:class "primary"
:on-click create-link
:value (tr "common.share-link.get-link")}])]]]
[:div.modal-content.ops-section
[:div.manage-permissions
{:on-click manage-open-ops}
[:span.icon i/picker-hsv]
[:div.title (tr "common.share-link.manage-ops")]]
(when @open-ops
[:*
(let [all-selected? (:all-pages @opts)
pages (->> (get-in file [:data :pages])
(map #(get-in file [:data :pages-index %])))
selected (:pages @opts)]
(some? @link)
[:input.btn-secondary
{:type "button"
:class "primary"
:on-click try-delete-link
:value (tr "common.share-link.remove-link")}]
[:*
[:div.view-mode
[:div.subtitle
[:span.icon i/play]
(tr "common.share-link.permissions-pages")]
[:div.items
(if (= 1 (count pages))
[:div.input-checkbox.check-primary
[:input {:type "checkbox"
:id (str "page-" (:id current-page))
:on-change #(mark-checked-page % (:id current-page))
:checked true}]
[:label {:for (str "page-" (:id current-page))} (:name current-page)]
[:span (str " " (tr "common.share-link.current-tag"))]]
:else
[:input.btn-primary
{:type "button"
:class "primary"
:on-click create-link
:value (tr "common.share-link.get-link")}])]
[:*
[:div.row
[:div.input-checkbox.check-primary
[:input {:type "checkbox"
:id "view-all"
:checked all-selected?
:name "pages-mode"
:on-change toggle-all}]
[:label {:for "view-all"} (tr "common.share-link.view-all")]]
[:span.count-pages (tr "common.share-link.page-shared" (i18n/c (count selected)))]]
]]))
[:ul.pages-selection
(for [page pages]
[:li.input-checkbox.check-primary {:key (str (:id page))}
[:input {:type "checkbox"
:id (str "page-" (:id page))
:on-change #(mark-checked-page % (:id page))
:checked (contains? selected (:id page))}]
(if (= (:id current-page) (:id page))
[:*
[:label {:for (str "page-" (:id page))} (:name page)]
[:span.current-tag (str " " (tr "common.share-link.current-tag"))]]
[:label {:for (str "page-" (:id page))} (:name page)])])]])]]])
[:div.access-mode
[:div.subtitle
[:span.icon i/chat]
(tr "common.share-link.permissions-can-comment")]
[:div.items
[:select.input-select {:on-change (partial on-who-change :comment)
:value (:who-comment @opts)}
[:option {:value "team"} (tr "common.share-link.team-members")]
[:option {:value "all"} (tr "common.share-link.all-users")]]]]
[:div.inspect-mode
[:div.subtitle
[:span.icon i/code]
(tr "common.share-link.permissions-can-inspect")]
[:div.items
[:select.input-select {:on-change (partial on-who-change :inspect)
:value (:who-inspect @opts)}
[:option {:value "team"} (tr "common.share-link.team-members")]
[:option {:value "all"} (tr "common.share-link.all-users")]]]]])]]]))

View file

@ -25,7 +25,7 @@
[app.main.ui.icons :as i]
[app.main.ui.share-link]
[app.main.ui.static :as static]
[app.main.ui.viewer.comments :refer [comments-layer comments-sidebar]]
[app.main.ui.viewer.comments :refer [comments-layer comments-sidebar]]
[app.main.ui.viewer.handoff :as handoff]
[app.main.ui.viewer.header :refer [header]]
[app.main.ui.viewer.interactions :as interactions]
@ -84,7 +84,7 @@
(when show-comments-list
[:& comments-sidebar {:users users :frame frame :page page}])
[:div.viewer-wrapper
{:style {:width (:width wrapper-size)
:height (:height wrapper-size)}}
@ -140,7 +140,7 @@
:on-click #(when (:close-click-outside overlay)
(close-overlay (:frame overlay)))}])
[:div.viewport-container.viewer-overlay
{:id (str "overlay-" (-> overlay :frame :id))
:style {:width (:width size-over)
:height (:height size-over)
@ -169,6 +169,17 @@
(let [{:keys [page-id section index]} params
{:keys [file users project permissions]} data
allowed (or
(= section :interactions)
(and (= section :comments)
(or (:can-edit permissions)
(and (true? (:is-logged permissions))
(= (:who-comment permissions) "all"))))
(and (= section :handoff)
(or (:can-edit permissions)
(and (true? (:is-logged permissions))
(= (:who-inspect permissions) "all")))))
local (mf/deref refs/viewer-local)
nav-scroll (:nav-scroll local)
@ -241,6 +252,9 @@
(when (nil? page)
(ex/raise :type :not-found))
(when (not allowed)
(st/emit! (dv/go-to-section :interactions)))
;; Set the page title
(mf/use-effect
(mf/deps (:name file))
@ -394,24 +408,23 @@
:index index
:viewer-pagination viewer-pagination}]
[:& viewer-wrapper
{:wrapper-size wrapper-size
:scroll scroll
:orig-frame orig-frame
:orig-viewport-ref orig-viewport-ref
:orig-size orig-size
:page page
:file file
:users users
:current-viewport-ref current-viewport-ref
:size size
:frame frame
:interactions-mode interactions-mode
:overlays overlays
:zoom zoom
:section section
:index index}]))]]]))
[:& viewer-wrapper
{:wrapper-size wrapper-size
:scroll scroll
:orig-frame orig-frame
:orig-viewport-ref orig-viewport-ref
:orig-size orig-size
:page page
:file file
:users users
:current-viewport-ref current-viewport-ref
:size size
:frame frame
:interactions-mode interactions-mode
:overlays overlays
:zoom zoom
:section section
:index index}]))]]]))
;; --- Component: Viewer Page

View file

@ -77,7 +77,8 @@
(mf/use-callback
(mf/deps page)
(fn []
(modal/show! :share-link {:page page :file file})))]
(modal/show! :share-link {:page page :file file})
(modal/allow-click-outside!)))]
[:div.options-zone
(case section
@ -187,7 +188,7 @@
[:div.main-icon
[:a {:on-click go-to-dashboard
;; If the user doesn't have permission we disable the link
:style {:pointer-events (when-not permissions "none")}} i/logo-icon]]
:style {:pointer-events (when-not (:can-edit permissions) "none")}} i/logo-icon]]
[:& header-sitemap {:project project :file file :page page :frame frame :index index}]]
@ -198,7 +199,9 @@
:alt (tr "viewer.header.interactions-section" (sc/get-tooltip :open-interactions))}
i/play]
(when (:can-edit permissions)
(when (or (:can-edit permissions)
(and (true? (:is-logged permissions))
(= (:who-comment permissions) "all")))
[:button.mode-zone-button.tooltip.tooltip-bottom
{:on-click #(navigate :comments)
:class (dom/classnames :active (= section :comments))
@ -207,7 +210,8 @@
(when (or (= (:type permissions) :membership)
(and (= (:type permissions) :share-link)
(contains? (:flags permissions) :section-handoff)))
(true? (:is-logged permissions))
(= (:who-inspect permissions) "all")))
[:button.mode-zone-button.tooltip.tooltip-bottom
{:on-click go-to-handoff
:class (dom/classnames :active (= section :handoff))

View file

@ -60,7 +60,7 @@
[{:keys [users threads page-id]}]
(let [threads-map (mf/deref refs/threads-ref)
profile (mf/deref refs/profile)
users-refs (mf/deref refs/users)
users-refs (mf/deref refs/current-file-comments-users)
users (or users users-refs)
local (mf/deref refs/comments-local)
options? (mf/use-state false)

View file

@ -20,7 +20,7 @@
pos-y (* (- (:y vbox)) zoom)
profile (mf/deref refs/profile)
users (mf/deref refs/users)
users (mf/deref refs/current-file-comments-users)
local (mf/deref refs/comments-local)
threads-map (mf/deref refs/threads-ref)

View file

@ -185,33 +185,15 @@ msgstr "S'ha copiat l'enllaç correctament"
msgid "common.share-link.link-deleted-success"
msgstr "S'ha eliminat l'enllaç correctament"
msgid "common.share-link.permissions-can-access"
msgstr "Pot accedir"
msgid "common.share-link.permissions-can-view"
msgstr "Lector"
msgid "common.share-link.permissions-hint"
msgstr "Qualsevol persona amb l'enllaç hi tindrà accés"
msgid "common.share-link.placeholder"
msgstr "L'enllaç per a compartir apareixerà aquí"
msgid "common.share-link.remove-link"
msgstr "Elimina l'enllaç"
msgid "common.share-link.title"
msgstr "Compartiu prototips"
msgid "common.share-link.view-all-pages"
msgstr "Totes les pàgines"
msgid "common.share-link.view-current-page"
msgstr "Només aquesta pàgina"
msgid "common.share-link.view-selected-pages"
msgstr "Pàgines seleccionades"
#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs
msgid "dashboard.add-shared"
msgstr "Afegeix a la biblioteca compartida"

View file

@ -188,33 +188,15 @@ msgstr "Link wurde erfolgreich kopiert"
msgid "common.share-link.link-deleted-success"
msgstr "Link wurde erfolgreich gelöscht"
msgid "common.share-link.permissions-can-access"
msgstr "Freigabe für"
msgid "common.share-link.permissions-can-view"
msgstr "Sichtbar"
msgid "common.share-link.permissions-hint"
msgstr "Jeder mit dem Link kann auf die Datei zugreifen"
msgid "common.share-link.placeholder"
msgstr "Link zum Teilen wird hier erscheinen"
msgid "common.share-link.remove-link"
msgstr "Link entfernen"
msgid "common.share-link.title"
msgstr "Prototypen teilen"
msgid "common.share-link.view-all-pages"
msgstr "Alle Seiten"
msgid "common.share-link.view-current-page"
msgstr "Nur diese Seite"
msgid "common.share-link.view-selected-pages"
msgstr "Ausgewählte Seiten"
#: src/app/main/ui/workspace/header.cljs,
#: src/app/main/ui/dashboard/file_menu.cljs
msgid "dashboard.add-shared"

View file

@ -183,11 +183,14 @@ msgstr "Link copied successfully"
msgid "common.share-link.link-deleted-success"
msgstr "Link deleted successfully"
msgid "common.share-link.permissions-can-access"
msgstr "Can access"
msgid "common.share-link.permissions-can-comment"
msgstr "Can comment"
msgid "common.share-link.permissions-can-view"
msgstr "Can view"
msgid "common.share-link.permissions-can-inspect"
msgstr "Can inspect code"
msgid "common.share-link.permissions-pages"
msgstr "Pages shared"
msgid "common.share-link.permissions-hint"
msgstr "Anyone with link will have access"
@ -195,20 +198,31 @@ msgstr "Anyone with link will have access"
msgid "common.share-link.placeholder"
msgstr "Shareable link will appear here"
msgid "common.share-link.remove-link"
msgstr "Remove link"
msgid "common.share-link.destroy-link"
msgstr "Destroy link"
msgid "common.share-link.title"
msgstr "Share prototypes"
msgid "common.share-link.view-all-pages"
msgstr "All pages"
msgid "common.share-link.view-all"
msgstr "Select All"
msgid "common.share-link.view-current-page"
msgstr "Only this page"
msgid "common.share-link.current-tag"
msgstr "(current)"
msgid "common.share-link.view-selected-pages"
msgstr "Selected pages"
msgid "common.share-link.manage-ops"
msgstr "Manage permissions"
msgid "common.share-link.team-members"
msgstr "Only team members"
msgid "common.share-link.all-users"
msgstr "All Penpot users"
msgid "common.share-link.page-shared"
msgid_plural "common.share-link.page-shared"
msgstr[0] "1 page shared"
msgstr[1] "%s pages shared"
#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs
msgid "dashboard.add-shared"

View file

@ -188,11 +188,14 @@ msgstr "Enlace copiado satisfactoriamente"
msgid "common.share-link.link-deleted-success"
msgstr "Enlace eliminado correctamente"
msgid "common.share-link.permissions-can-access"
msgstr "Puede acceder a"
msgid "common.share-link.permissions-can-comment"
msgstr "Pueden comentar"
msgid "common.share-link.permissions-can-view"
msgstr "Puede ver"
msgid "common.share-link.permissions-can-inspect"
msgstr "Pueden ver código"
msgid "common.share-link.permissions-pages"
msgstr "Páginas compartidas"
msgid "common.share-link.permissions-hint"
msgstr "Cualquiera con el enlace puede acceder"
@ -200,20 +203,31 @@ msgstr "Cualquiera con el enlace puede acceder"
msgid "common.share-link.placeholder"
msgstr "El enlace para compartir aparecerá aquí"
msgid "common.share-link.remove-link"
msgid "common.share-link.destroy-link"
msgstr "Eliminar enlace"
msgid "common.share-link.title"
msgstr "Compartir prototipos"
msgid "common.share-link.view-all-pages"
msgstr "Todas las paginas"
msgid "common.share-link.view-all"
msgstr "Selecctionar todas"
msgid "common.share-link.view-current-page"
msgstr "Solo esta pagina"
msgid "common.share-link.current-tag"
msgstr "(actual)"
msgid "common.share-link.view-selected-pages"
msgstr "Paginas seleccionadas"
msgid "common.share-link.manage-ops"
msgstr "Gestionar permisos"
msgid "common.share-link.team-members"
msgstr "Sólo integrantes del equipo"
msgid "common.share-link.all-users"
msgstr "Todo usario de Penpot"
msgid "common.share-link.page-shared"
msgid_plural "common.share-link.page-shared"
msgstr[0] "1 página compartida"
msgstr[1] "%s páginas compartidas"
#: src/app/main/ui/workspace/header.cljs,
#: src/app/main/ui/dashboard/file_menu.cljs

View file

@ -189,30 +189,12 @@ msgstr "لینک با موفقیت کپی شد"
msgid "common.share-link.link-deleted-success"
msgstr "لینک با موفقیت حذف شد"
msgid "common.share-link.permissions-can-access"
msgstr "می‌تواند دسترسی داشته باشد"
msgid "common.share-link.permissions-can-view"
msgstr "می‌تواند مشاهده کند"
msgid "common.share-link.permissions-hint"
msgstr "هر کسی که لینک داشته باشد دسترسی خواهد داشت"
msgid "common.share-link.remove-link"
msgstr "حذف لینک"
msgid "common.share-link.title"
msgstr "اشتراک‌گذاری پروتوتایپ‌ها"
msgid "common.share-link.view-all-pages"
msgstr "تمام صفحات"
msgid "common.share-link.view-current-page"
msgstr "فقط این صفحه"
msgid "common.share-link.view-selected-pages"
msgstr "صفحات انتخاب‌شده"
#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs
msgid "dashboard.add-shared"
msgstr "افزودن به‌عنوان کتابخانه مشترک"

View file

@ -186,33 +186,15 @@ msgstr "Lien copié avec succès"
msgid "common.share-link.link-deleted-success"
msgstr "Lien supprimé avec succès"
msgid "common.share-link.permissions-can-access"
msgstr "Peut y accéder"
msgid "common.share-link.permissions-can-view"
msgstr "Peut le visionner"
msgid "common.share-link.permissions-hint"
msgstr "N'importe qui possédant ce lien peut y accéder"
msgid "common.share-link.placeholder"
msgstr "Le lien à partager apparaîtra ici"
msgid "common.share-link.remove-link"
msgstr "Supprimer le lien"
msgid "common.share-link.title"
msgstr "Partager les prototypes"
msgid "common.share-link.view-all-pages"
msgstr "Toutes les pages"
msgid "common.share-link.view-current-page"
msgstr "Seulement cette page"
msgid "common.share-link.view-selected-pages"
msgstr "Pages sélectionnées"
#: src/app/main/ui/workspace/header.cljs,
#: src/app/main/ui/dashboard/file_menu.cljs
msgid "dashboard.add-shared"

View file

@ -179,33 +179,15 @@ msgstr "הקישור הועתק בהצלחה"
msgid "common.share-link.link-deleted-success"
msgstr "הקישור נמחק בהצלחה"
msgid "common.share-link.permissions-can-access"
msgstr "יש גישה"
msgid "common.share-link.permissions-can-view"
msgstr "אפשר לצפות"
msgid "common.share-link.permissions-hint"
msgstr "כל מי שיש לו את הקישור יכול לגשת"
msgid "common.share-link.placeholder"
msgstr "הקישור לשיתוף יופיע כאן"
msgid "common.share-link.remove-link"
msgstr "הסרת קישור"
msgid "common.share-link.title"
msgstr "שיתוף אבות טיפוס"
msgid "common.share-link.view-all-pages"
msgstr "כל העמודים"
msgid "common.share-link.view-current-page"
msgstr "רק העמוד הזה"
msgid "common.share-link.view-selected-pages"
msgstr "עמודים נבחרים"
#: src/app/main/ui/workspace/header.cljs,
#: src/app/main/ui/dashboard/file_menu.cljs
msgid "dashboard.add-shared"

View file

@ -189,20 +189,10 @@ msgstr "Tautan berhasil disalin"
msgid "common.share-link.link-deleted-success"
msgstr "Tautan berhasil dihapus"
msgid "common.share-link.permissions-can-access"
msgstr "Dapat mengakses"
msgid "common.share-link.permissions-can-view"
msgstr "Dapat melihat"
msgid "common.share-link.permissions-hint"
msgstr "Siapapun yang memiliki tautan dapat mengakses"
msgid "common.share-link.view-current-page"
msgstr "Hanya halaman ini"
msgid "common.share-link.view-selected-pages"
msgstr "Halaman yang dipilih"
#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs
msgid "dashboard.add-shared"

View file

@ -188,33 +188,15 @@ msgstr "Nuoroda sėkmingai nukopijuota"
msgid "common.share-link.link-deleted-success"
msgstr "Nuoroda sėkmingai ištrinta"
msgid "common.share-link.permissions-can-access"
msgstr "Gali pasiekti"
msgid "common.share-link.permissions-can-view"
msgstr "Galima peržiūrėti"
msgid "common.share-link.permissions-hint"
msgstr "Kiekvienas, turintis nuorodą, turės prieigą"
msgid "common.share-link.placeholder"
msgstr "Bendrinama nuoroda bus rodoma čia"
msgid "common.share-link.remove-link"
msgstr "Pašalinti nuorodą"
msgid "common.share-link.title"
msgstr "Dalinkitės prototipais"
msgid "common.share-link.view-all-pages"
msgstr "Visi puslapiai"
msgid "common.share-link.view-current-page"
msgstr "Tik šis puslapis"
msgid "common.share-link.view-selected-pages"
msgstr "Parinkti puslapiai"
#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs
msgid "dashboard.add-shared"
msgstr "Pridėti kaip bendrinamą biblioteką"

View file

@ -189,33 +189,15 @@ msgstr "കണ്ണി വിജയകരമായി പകർത്തി"
msgid "common.share-link.link-deleted-success"
msgstr "കണ്ണി വിജയകരമായി മായിച്ചു"
msgid "common.share-link.permissions-can-access"
msgstr "പ്രാപ്യമാണ്"
msgid "common.share-link.permissions-can-view"
msgstr "കാണാവുന്നതാണ്"
msgid "common.share-link.permissions-hint"
msgstr "കണ്ണിയുള്ള ആർക്കും പ്രാപ്യമാകും"
msgid "common.share-link.placeholder"
msgstr "പങ്കുവെക്കാവുന്ന കണ്ണി ഇവിടെ ലഭ്യമാകും"
msgid "common.share-link.remove-link"
msgstr "കണ്ണി നീക്കുക"
msgid "common.share-link.title"
msgstr "പ്രോട്ടോടൈപ്പുകൾ പങ്കുവെയ്ക്കുക"
msgid "common.share-link.view-all-pages"
msgstr "എല്ലാ താളുകളും"
msgid "common.share-link.view-current-page"
msgstr "ഈ താൾ മാത്രം"
msgid "common.share-link.view-selected-pages"
msgstr "തിരഞ്ഞെടുത്ത താളുകൾ"
#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs
msgid "dashboard.add-shared"
msgstr "പങ്കിട്ട ലൈബ്രറിയായി ചേർക്കുക"

View file

@ -186,33 +186,15 @@ msgstr "Link skopiowano pomyślnie"
msgid "common.share-link.link-deleted-success"
msgstr "Link usunięto pomyślnie"
msgid "common.share-link.permissions-can-access"
msgstr "Można uzyskać dostęp"
msgid "common.share-link.permissions-can-view"
msgstr "Można zobaczyć"
msgid "common.share-link.permissions-hint"
msgstr "Każdy, kto ma link, będzie miał dostęp"
msgid "common.share-link.placeholder"
msgstr "Tutaj pojawi się link do udostępniania"
msgid "common.share-link.remove-link"
msgstr "Usuń link"
msgid "common.share-link.title"
msgstr "Udostępnij prototypy"
msgid "common.share-link.view-all-pages"
msgstr "Wszystkie strony"
msgid "common.share-link.view-current-page"
msgstr "Tylko ta strona"
msgid "common.share-link.view-selected-pages"
msgstr "Wybrane strony"
#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs
msgid "dashboard.add-shared"
msgstr "Dodaj jako Udostępnioną Bibliotekę"

View file

@ -186,35 +186,16 @@ msgstr "Ссылка скопирована"
msgid "common.share-link.link-deleted-success"
msgstr "Ссылка удалена"
#, fuzzy
msgid "common.share-link.permissions-can-access"
msgstr "Могут зайти"
msgid "common.share-link.permissions-can-view"
msgstr "Могут видеть"
msgid "common.share-link.permissions-hint"
msgstr "Доступ открыт для получателей ссылки"
msgid "common.share-link.placeholder"
msgstr "Ссылка появится здесь"
msgid "common.share-link.remove-link"
msgstr "Удалить ссылку"
#, fuzzy
msgid "common.share-link.title"
msgstr "Поделиться прототипами"
msgid "common.share-link.view-all-pages"
msgstr "Все страницы"
msgid "common.share-link.view-current-page"
msgstr "Только эту страницу"
msgid "common.share-link.view-selected-pages"
msgstr "Выбранные страницы"
#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs
msgid "dashboard.add-shared"
msgstr "Добавить как общую библиотеку"

View file

@ -186,33 +186,15 @@ msgstr "Bağlantı başarıyla kopyalandı"
msgid "common.share-link.link-deleted-success"
msgstr "Bağlantı başarıyla silindi"
msgid "common.share-link.permissions-can-access"
msgstr "Erişebilir"
msgid "common.share-link.permissions-can-view"
msgstr "Görüntüleyebilir"
msgid "common.share-link.permissions-hint"
msgstr "Bağlantıya sahip olan herkes erişebilir"
msgid "common.share-link.placeholder"
msgstr "Paylaşılabilir bağlantı burada görünecek"
msgid "common.share-link.remove-link"
msgstr "Bağlantıyı kaldır"
msgid "common.share-link.title"
msgstr "Prototipleri paylaş"
msgid "common.share-link.view-all-pages"
msgstr "Tüm sayfalar"
msgid "common.share-link.view-current-page"
msgstr "Yalnızca bu sayfa"
msgid "common.share-link.view-selected-pages"
msgstr "Seçili sayfalar"
#: src/app/main/ui/workspace/header.cljs,
#: src/app/main/ui/dashboard/file_menu.cljs
msgid "dashboard.add-shared"

View file

@ -177,13 +177,6 @@ msgstr "链接已复制"
msgid "common.share-link.link-deleted-success"
msgstr "链接已移除"
#, fuzzy
msgid "common.share-link.permissions-can-access"
msgstr "可访问"
msgid "common.share-link.permissions-can-view"
msgstr "可浏览"
#, fuzzy
msgid "common.share-link.permissions-hint"
msgstr "任何人通过此链接都可访问"
@ -191,21 +184,9 @@ msgstr "任何人通过此链接都可访问"
msgid "common.share-link.placeholder"
msgstr "可分享的链接会在此处显示"
msgid "common.share-link.remove-link"
msgstr "移除链接"
msgid "common.share-link.title"
msgstr "分享原型"
msgid "common.share-link.view-all-pages"
msgstr "全部页面"
msgid "common.share-link.view-current-page"
msgstr "仅此页面"
msgid "common.share-link.view-selected-pages"
msgstr "选中的页面"
#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs
msgid "dashboard.add-shared"
msgstr "添加为共享库"

View file

@ -181,33 +181,15 @@ msgstr "成功複製連結"
msgid "common.share-link.link-deleted-success"
msgstr "成功刪除連結"
msgid "common.share-link.permissions-can-access"
msgstr "能夠存取"
msgid "common.share-link.permissions-can-view"
msgstr "能夠檢視"
msgid "common.share-link.permissions-hint"
msgstr "任何有連結的人皆能存取"
msgid "common.share-link.placeholder"
msgstr "可分享的連結將會在此顯示"
msgid "common.share-link.remove-link"
msgstr "移除連結"
msgid "common.share-link.title"
msgstr "分享原型"
msgid "common.share-link.view-all-pages"
msgstr "所有頁面"
msgid "common.share-link.view-current-page"
msgstr "僅此頁面"
msgid "common.share-link.view-selected-pages"
msgstr "選擇的頁面"
#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs
#, fuzzy
msgid "dashboard.add-shared"