mirror of
https://github.com/penpot/penpot.git
synced 2025-02-08 16:18:11 -05:00
✨ New plugin install workflow
This commit is contained in:
parent
a510d01136
commit
d1277afee6
11 changed files with 252 additions and 41 deletions
|
@ -941,7 +941,7 @@
|
|||
(update-in [:dashboard-projects project-id :count] inc)))))
|
||||
|
||||
(defn create-file
|
||||
[{:keys [project-id] :as params}]
|
||||
[{:keys [project-id name] :as params}]
|
||||
(dm/assert! (uuid? project-id))
|
||||
(ptk/reify ::create-file
|
||||
ev/Event
|
||||
|
@ -955,7 +955,7 @@
|
|||
|
||||
files (get state :dashboard-files)
|
||||
unames (cfh/get-used-names files)
|
||||
name (cfh/generate-unique-name unames (str (tr "dashboard.new-file-prefix") " 1"))
|
||||
name (or name (cfh/generate-unique-name unames (str (tr "dashboard.new-file-prefix") " 1")))
|
||||
features (-> (features/get-team-enabled-features state)
|
||||
(set/difference cfeat/frontend-only-features))
|
||||
params (-> params
|
||||
|
|
42
frontend/src/app/main/data/plugins.cljs
Normal file
42
frontend/src/app/main/data/plugins.cljs
Normal file
|
@ -0,0 +1,42 @@
|
|||
;; This Source Code Form is subject to the terms of the Mozilla Public
|
||||
;; License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
;;
|
||||
;; Copyright (c) KALEIDOS INC
|
||||
|
||||
(ns app.main.data.plugins
|
||||
(:require
|
||||
[app.plugins.register :as pr]
|
||||
[potok.v2.core :as ptk]))
|
||||
|
||||
(defn open-plugin!
|
||||
[{:keys [plugin-id name description host code icon permissions]}]
|
||||
(try
|
||||
(.ɵloadPlugin
|
||||
js/window
|
||||
#js {:pluginId plugin-id
|
||||
:name name
|
||||
:description description
|
||||
:host host
|
||||
:code code
|
||||
:icon icon
|
||||
:permissions (apply array permissions)})
|
||||
(catch :default e
|
||||
(.error js/console "Error" e))))
|
||||
|
||||
(defn delay-open-plugin
|
||||
[plugin]
|
||||
(ptk/reify ::delay-open-plugin
|
||||
ptk/UpdateEvent
|
||||
(update [_ state]
|
||||
(assoc state ::open-plugin (:plugin-id plugin)))))
|
||||
|
||||
(defn check-open-plugin
|
||||
[]
|
||||
(ptk/reify ::check-open-plugin
|
||||
ptk/WatchEvent
|
||||
(watch [_ state _]
|
||||
(when-let [pid (::open-plugin state)]
|
||||
(open-plugin! (pr/get-plugin pid))
|
||||
(fn [state]
|
||||
(dissoc state ::open-plugin))))))
|
|
@ -42,6 +42,7 @@
|
|||
[app.main.data.modal :as modal]
|
||||
[app.main.data.notifications :as ntf]
|
||||
[app.main.data.persistence :as dps]
|
||||
[app.main.data.plugins :as dp]
|
||||
[app.main.data.users :as du]
|
||||
[app.main.data.workspace.bool :as dwb]
|
||||
[app.main.data.workspace.collapse :as dwco]
|
||||
|
@ -131,6 +132,7 @@
|
|||
(when (and (not (boolean (-> state :profile :props :v2-info-shown)))
|
||||
(features/active-feature? state "components/v2"))
|
||||
(modal/show :v2-info {}))
|
||||
(dp/check-open-plugin)
|
||||
(fdf/fix-deleted-fonts)
|
||||
(fbs/fix-broken-shapes)))))
|
||||
|
||||
|
|
|
@ -8,10 +8,15 @@
|
|||
(:require-macros [app.main.style :as stl])
|
||||
(:require
|
||||
[app.common.data :as d]
|
||||
[app.common.data.macros :as dm]
|
||||
[app.common.spec :as us]
|
||||
[app.config :as cf]
|
||||
[app.main.data.dashboard :as dd]
|
||||
[app.main.data.dashboard.shortcuts :as sc]
|
||||
[app.main.data.events :as ev]
|
||||
[app.main.data.modal :as modal]
|
||||
[app.main.data.notifications :as notif]
|
||||
[app.main.data.plugins :as dp]
|
||||
[app.main.refs :as refs]
|
||||
[app.main.store :as st]
|
||||
[app.main.ui.context :as ctx]
|
||||
|
@ -25,11 +30,17 @@
|
|||
[app.main.ui.dashboard.team :refer [team-settings-page team-members-page team-invitations-page team-webhooks-page]]
|
||||
[app.main.ui.dashboard.templates :refer [templates-section]]
|
||||
[app.main.ui.hooks :as hooks]
|
||||
[app.main.ui.workspace.plugins]
|
||||
[app.plugins.register :as preg]
|
||||
[app.util.dom :as dom]
|
||||
[app.util.http :as http]
|
||||
[app.util.keyboard :as kbd]
|
||||
[app.util.object :as obj]
|
||||
[app.util.router :as rt]
|
||||
[beicon.v2.core :as rx]
|
||||
[goog.events :as events]
|
||||
[okulary.core :as l]
|
||||
[potok.v2.core :as ptk]
|
||||
[rumext.v2 :as mf]))
|
||||
|
||||
(defn ^boolean uuid-str?
|
||||
|
@ -143,6 +154,70 @@
|
|||
(def dashboard-initialized
|
||||
(l/derived :current-team-id st/state))
|
||||
|
||||
(defn use-plugin-register
|
||||
[plugin-url team-id project-id]
|
||||
|
||||
(let [navegate-file!
|
||||
(fn [plugin {:keys [project-id id data]}]
|
||||
(st/emit!
|
||||
(dp/delay-open-plugin plugin)
|
||||
(rt/nav :workspace
|
||||
{:project-id project-id :file-id id}
|
||||
{:page-id (dm/get-in data [:pages 0])})))
|
||||
|
||||
create-file!
|
||||
(fn [plugin]
|
||||
(st/emit!
|
||||
(modal/hide)
|
||||
(let [data
|
||||
(with-meta
|
||||
{:project-id project-id
|
||||
:name (dm/str "Try plugin: " (:name plugin))}
|
||||
{:on-success (partial navegate-file! plugin)})]
|
||||
(-> (dd/create-file data)
|
||||
(with-meta {::ev/origin "plugin-try-out"})))))
|
||||
|
||||
open-try-out-dialog
|
||||
(fn [plugin]
|
||||
(modal/show
|
||||
:plugin-try-out
|
||||
{:plugin plugin
|
||||
:on-accept #(create-file! plugin)
|
||||
:on-close #(modal/hide!)}))
|
||||
|
||||
open-permissions-dialog
|
||||
(fn [plugin]
|
||||
(modal/show!
|
||||
:plugin-permissions
|
||||
{:plugin plugin
|
||||
:on-accept
|
||||
#(do (preg/install-plugin! plugin)
|
||||
(st/emit! (modal/hide)
|
||||
(rt/nav :dashboard-projects {:team-id team-id})
|
||||
(open-try-out-dialog plugin)))
|
||||
:on-close
|
||||
#(st/emit! (modal/hide)
|
||||
(rt/nav :dashboard-projects {:team-id team-id}))}))]
|
||||
|
||||
(mf/with-layout-effect
|
||||
[plugin-url team-id project-id]
|
||||
(when plugin-url
|
||||
(->> (http/send! {:method :get
|
||||
:uri plugin-url
|
||||
:omit-default-headers true
|
||||
:response-type :json})
|
||||
(rx/map :body)
|
||||
(rx/subs!
|
||||
(fn [body]
|
||||
(if-let [plugin (preg/parse-manifest plugin-url body)]
|
||||
(do
|
||||
(st/emit! (ptk/event ::ev/event {::ev/name "install-plugin" :name (:name plugin) :url plugin-url}))
|
||||
(open-permissions-dialog plugin))
|
||||
(st/emit! (notif/error "Cannot parser the plugin manifest"))))
|
||||
|
||||
(fn [_]
|
||||
(st/emit! (notif/error "The plugin URL is incorrect")))))))))
|
||||
|
||||
(mf/defc dashboard
|
||||
{::mf/props :obj}
|
||||
[{:keys [route profile]}]
|
||||
|
@ -150,8 +225,12 @@
|
|||
params (parse-params route)
|
||||
|
||||
project-id (:project-id params)
|
||||
|
||||
team-id (:team-id params)
|
||||
search-term (:search-term params)
|
||||
|
||||
plugin-url (-> route :query-params :plugin)
|
||||
|
||||
invite-email (-> route :query-params :invite-email)
|
||||
|
||||
teams (mf/deref refs/teams)
|
||||
|
@ -160,6 +239,8 @@
|
|||
projects (mf/deref refs/dashboard-projects)
|
||||
project (get projects project-id)
|
||||
|
||||
default-project (->> projects vals (d/seek :is-default))
|
||||
|
||||
initialized? (mf/deref dashboard-initialized)]
|
||||
|
||||
(hooks/use-shortcuts ::dashboard sc/shortcuts)
|
||||
|
@ -178,6 +259,8 @@
|
|||
(fn []
|
||||
(events/unlistenByKey key))))
|
||||
|
||||
(use-plugin-register plugin-url team-id (:id default-project))
|
||||
|
||||
[:& (mf/provider ctx/current-team-id) {:value team-id}
|
||||
[:& (mf/provider ctx/current-project-id) {:value project-id}
|
||||
;; NOTE: dashboard events and other related functions assumes
|
||||
|
@ -206,4 +289,3 @@
|
|||
:search-term search-term
|
||||
:team team
|
||||
:invite-email invite-email}])])]]))
|
||||
|
||||
|
|
|
@ -16,6 +16,7 @@
|
|||
[app.util.router :as rt]
|
||||
[beicon.v2.core :as rx]
|
||||
[cljs.spec.alpha :as s]
|
||||
[cuerdas.core :as str]
|
||||
[potok.v2.core :as ptk]))
|
||||
|
||||
(s/def ::page-id ::us/uuid)
|
||||
|
@ -94,10 +95,17 @@
|
|||
(defn on-navigate
|
||||
[router path]
|
||||
(let [location (.-location js/document)
|
||||
[base-path qs] (str/split path "?")
|
||||
|
||||
qstring
|
||||
(->> (str/split qs "&")
|
||||
(map #(str/split % "="))
|
||||
(into {}))
|
||||
|
||||
location-path (dm/str (.-origin location) (.-pathname location))
|
||||
valid-location? (= location-path (dm/str cf/public-uri))
|
||||
match (match-path router path)
|
||||
empty-path? (or (= path "") (= path "/"))]
|
||||
empty-path? (or (= base-path "") (= base-path "/"))]
|
||||
(cond
|
||||
(not valid-location?)
|
||||
(st/emit! (rt/assign-exception {:type :not-found}))
|
||||
|
@ -116,7 +124,7 @@
|
|||
(st/emit! (rt/nav :auth-login))
|
||||
|
||||
empty-path?
|
||||
(st/emit! (rt/nav :dashboard-projects {:team-id (du/get-current-team-id profile)}))
|
||||
(st/emit! (rt/nav :dashboard-projects {:team-id (du/get-current-team-id profile)} qstring))
|
||||
|
||||
:else
|
||||
(st/emit! (rt/assign-exception {:type :not-found})))))))))
|
||||
|
|
|
@ -16,6 +16,7 @@
|
|||
[app.main.data.events :as ev]
|
||||
[app.main.data.exports :as de]
|
||||
[app.main.data.modal :as modal]
|
||||
[app.main.data.plugins :as dp]
|
||||
[app.main.data.shortcuts :as scd]
|
||||
[app.main.data.users :as du]
|
||||
[app.main.data.workspace :as dw]
|
||||
|
@ -29,7 +30,6 @@
|
|||
[app.main.ui.context :as ctx]
|
||||
[app.main.ui.hooks.resize :as r]
|
||||
[app.main.ui.icons :as i]
|
||||
[app.main.ui.workspace.plugins :as uwp]
|
||||
[app.plugins.register :as preg]
|
||||
[app.util.dom :as dom]
|
||||
[app.util.i18n :as i18n :refer [tr]]
|
||||
|
@ -637,7 +637,7 @@
|
|||
::ev/origin "workspace:menu"
|
||||
:name name
|
||||
:host host}))
|
||||
(uwp/open-plugin! manifest))
|
||||
(dp/open-plugin! manifest))
|
||||
:class (stl/css :submenu-item)
|
||||
:on-key-down (fn [event]
|
||||
(when (kbd/enter? event)
|
||||
|
@ -646,7 +646,7 @@
|
|||
::ev/origin "workspace:menu"
|
||||
:name name
|
||||
:host host}))
|
||||
(uwp/open-plugin! manifest))))}
|
||||
(dp/open-plugin! manifest))))}
|
||||
[:span {:class (stl/css :item-name)} name]])])))
|
||||
|
||||
(mf/defc menu
|
||||
|
|
|
@ -12,6 +12,7 @@
|
|||
[app.config :as cf]
|
||||
[app.main.data.events :as ev]
|
||||
[app.main.data.modal :as modal]
|
||||
[app.main.data.plugins :as dp]
|
||||
[app.main.store :as st]
|
||||
[app.main.ui.components.search-bar :refer [search-bar]]
|
||||
[app.main.ui.components.title-bar :refer [title-bar]]
|
||||
|
@ -59,22 +60,6 @@
|
|||
[:button {:class (stl/css :trash-button)
|
||||
:on-click handle-delete-click} i/delete]]))
|
||||
|
||||
|
||||
(defn open-plugin!
|
||||
[{:keys [plugin-id name description host code icon permissions]}]
|
||||
(try
|
||||
(.ɵloadPlugin
|
||||
js/window
|
||||
#js {:pluginId plugin-id
|
||||
:name name
|
||||
:description description
|
||||
:host host
|
||||
:code code
|
||||
:icon icon
|
||||
:permissions (apply array permissions)})
|
||||
(catch :default e
|
||||
(.error js/console "Error" e))))
|
||||
|
||||
(mf/defc plugin-management-dialog
|
||||
{::mf/register modal/components
|
||||
::mf/register-as :plugin-management}
|
||||
|
@ -144,7 +129,7 @@
|
|||
::ev/origin "workspace:plugins"
|
||||
:name (:name manifest)
|
||||
:host (:host manifest)}))
|
||||
(open-plugin! manifest)
|
||||
(dp/open-plugin! manifest)
|
||||
(modal/hide!)))
|
||||
|
||||
handle-remove-plugin
|
||||
|
@ -215,7 +200,7 @@
|
|||
(mf/defc plugins-permissions-dialog
|
||||
{::mf/register modal/components
|
||||
::mf/register-as :plugin-permissions}
|
||||
[{:keys [plugin on-accept]}]
|
||||
[{:keys [plugin on-accept on-close]}]
|
||||
|
||||
(let [{:keys [host permissions]} plugin
|
||||
permissions (set permissions)
|
||||
|
@ -224,25 +209,26 @@
|
|||
(mf/use-callback
|
||||
(fn [event]
|
||||
(dom/prevent-default event)
|
||||
(st/emit! (modal/hide))
|
||||
(ptk/event ::ev/event {::ev/name "allow-plugin-permissions"
|
||||
:host host
|
||||
:permissions (->> permissions (str/join ", "))})
|
||||
(on-accept)))
|
||||
(st/emit! (ptk/event ::ev/event {::ev/name "allow-plugin-permissions"
|
||||
:host host
|
||||
:permissions (->> permissions (str/join ", "))})
|
||||
(modal/hide))
|
||||
(when on-accept (on-accept))))
|
||||
|
||||
handle-close-dialog
|
||||
(mf/use-callback
|
||||
(fn [event]
|
||||
(dom/prevent-default event)
|
||||
(ptk/event ::ev/event {::ev/name "reject-plugin-permissions"
|
||||
:host host
|
||||
:permissions (->> permissions (str/join ", "))})
|
||||
(st/emit! (modal/hide))))]
|
||||
(st/emit! (ptk/event ::ev/event {::ev/name "reject-plugin-permissions"
|
||||
:host host
|
||||
:permissions (->> permissions (str/join ", "))})
|
||||
(modal/hide))
|
||||
(when on-close (on-close))))]
|
||||
|
||||
[:div {:class (stl/css :modal-overlay)}
|
||||
[:div {:class (stl/css :modal-dialog :plugin-permissions)}
|
||||
[:button {:class (stl/css :close-btn) :on-click handle-close-dialog} close-icon]
|
||||
[:div {:class (stl/css :modal-title)} (tr "workspace.plugins.permissions.title")]
|
||||
[:div {:class (stl/css :modal-title)} (tr "workspace.plugins.permissions.title" (str/upper (:name plugin)))]
|
||||
|
||||
[:div {:class (stl/css :modal-content)}
|
||||
[:div {:class (stl/css :permissions-list)}
|
||||
|
@ -293,7 +279,7 @@
|
|||
(tr "workspace.plugins.permissions.comment-read")]])
|
||||
|
||||
(cond
|
||||
(contains? permissions "allow:download")
|
||||
(contains? permissions "allow:downloads")
|
||||
[:div {:class (stl/css :permissions-list-entry)}
|
||||
i/oauth-1
|
||||
[:p {:class (stl/css :permissions-list-text)}
|
||||
|
@ -315,3 +301,55 @@
|
|||
:type "button"
|
||||
:value (tr "ds.confirm-allow")
|
||||
:on-click handle-accept-dialog}]]]]]))
|
||||
|
||||
|
||||
(mf/defc plugins-try-out-dialog
|
||||
{::mf/register modal/components
|
||||
::mf/register-as :plugin-try-out}
|
||||
[{:keys [plugin on-accept on-close]}]
|
||||
|
||||
(let [{:keys [icon host name]} plugin
|
||||
|
||||
handle-accept-dialog
|
||||
(mf/use-callback
|
||||
(fn [event]
|
||||
(dom/prevent-default event)
|
||||
(st/emit! (ptk/event ::ev/event {::ev/name "try-out-accept"})
|
||||
(modal/hide))
|
||||
(when on-accept (on-accept))))
|
||||
|
||||
handle-close-dialog
|
||||
(mf/use-callback
|
||||
(fn [event]
|
||||
(dom/prevent-default event)
|
||||
(st/emit! (ptk/event ::ev/event {::ev/name "try-out-cancel"})
|
||||
(modal/hide))
|
||||
(when on-close (on-close))))]
|
||||
|
||||
[:div {:class (stl/css :modal-overlay)}
|
||||
[:div {:class (stl/css :modal-dialog :plugin-try-out)}
|
||||
[:button {:class (stl/css :close-btn) :on-click handle-close-dialog} close-icon]
|
||||
[:div {:class (stl/css :modal-title)}
|
||||
[:div {:class (stl/css :plugin-icon)}
|
||||
[:img {:src (if (some? icon)
|
||||
(dm/str host icon)
|
||||
(avatars/generate {:name name}))}]]
|
||||
(tr "workspace.plugins.try-out.title" (str/upper (:name plugin)))]
|
||||
|
||||
[:div {:class (stl/css :modal-content)}
|
||||
[:div {:class (stl/css :modal-message)}
|
||||
(tr "workspace.plugins.try-out.message")]]
|
||||
|
||||
[:div {:class (stl/css :modal-footer)}
|
||||
[:div {:class (stl/css :action-buttons)}
|
||||
[:input
|
||||
{:class (stl/css :cancel-button :button-expand)
|
||||
:type "button"
|
||||
:value (tr "workspace.plugins.try-out.cancel")
|
||||
:on-click handle-close-dialog}]
|
||||
|
||||
[:input
|
||||
{:class (stl/css :primary-button :button-expand)
|
||||
:type "button"
|
||||
:value (tr "workspace.plugins.try-out.try")
|
||||
:on-click handle-accept-dialog}]]]]]))
|
||||
|
|
|
@ -26,6 +26,11 @@
|
|||
max-width: $s-472;
|
||||
}
|
||||
|
||||
&.plugin-try-out {
|
||||
width: $s-452;
|
||||
max-width: $s-452;
|
||||
}
|
||||
|
||||
hr {
|
||||
border-color: var(--color-background-tertiary);
|
||||
}
|
||||
|
@ -48,6 +53,8 @@
|
|||
@include headlineMediumTypography;
|
||||
margin-block-end: $s-32;
|
||||
color: var(--modal-title-foreground-color);
|
||||
display: flex;
|
||||
gap: $s-12;
|
||||
}
|
||||
|
||||
.modal-content {
|
||||
|
@ -64,6 +71,11 @@
|
|||
}
|
||||
}
|
||||
|
||||
.modal-message {
|
||||
font-size: $fs-14;
|
||||
color: var(--color-foreground-secondary);
|
||||
}
|
||||
|
||||
.primary-button {
|
||||
@extend .button-primary;
|
||||
@include headlineSmallTypography;
|
||||
|
|
|
@ -5,7 +5,6 @@
|
|||
;; Copyright (c) KALEIDOS INC
|
||||
|
||||
(ns app.plugins.register
|
||||
"RPC for plugins runtime."
|
||||
(:require
|
||||
[app.common.data :as d]
|
||||
[app.common.data.macros :as dm]
|
||||
|
@ -26,6 +25,10 @@
|
|||
(->> (:ids @registry)
|
||||
(mapv #(dm/get-in @registry [:data %]))))
|
||||
|
||||
(defn get-plugin
|
||||
[id]
|
||||
(dm/get-in @registry [:data id]))
|
||||
|
||||
(defn parse-manifest
|
||||
"Read the manifest.json defined by the plugins definition and transforms it into an
|
||||
object that will be stored in the register."
|
||||
|
|
|
@ -5601,12 +5601,24 @@ msgstr "Start file downloads."
|
|||
|
||||
#: src/app/main/ui/workspace/plugins.cljs:236
|
||||
msgid "workspace.plugins.permissions.title"
|
||||
msgstr "THIS PLUGIN WANTS ACCESS TO:"
|
||||
msgstr "'%s' PLUGIN WANTS ACCESS TO:"
|
||||
|
||||
#: src/app/main/ui/workspace/plugins.cljs:258
|
||||
msgid "workspace.plugins.permissions.user-read"
|
||||
msgstr "Read the profile information of the current user."
|
||||
|
||||
msgid "workspace.plugins.try-out.title"
|
||||
msgstr "'%s' PLUGIN IS INSTALLED FOR YOUR USER!"
|
||||
|
||||
msgid "workspace.plugins.try-out.message"
|
||||
msgstr "Want to take a look? It will open in a new draft for your current team. (If not, you can always find it in the installed plugins of any file.)"
|
||||
|
||||
msgid "workspace.plugins.try-out.cancel"
|
||||
msgstr "NOT NOW"
|
||||
|
||||
msgid "workspace.plugins.try-out.try"
|
||||
msgstr "TRY PLUGIN"
|
||||
|
||||
#: src/app/main/ui/workspace/plugins.cljs:192
|
||||
msgid "workspace.plugins.plugin-list-link"
|
||||
msgstr "Plugins List"
|
||||
|
|
|
@ -5567,7 +5567,7 @@ msgstr "Leer y modificar el contenido de sus archivos."
|
|||
|
||||
#: src/app/main/ui/workspace/plugins.cljs:274
|
||||
msgid "workspace.plugins.permissions.disclaimer"
|
||||
msgstr "Tenga en cuenta que las extensiones están desarrolladas por terceros, asegursé que confía antes de conceder el permiso. Su privacidad y seguridad es importante para nosotros. Si tiene cualquier duda, contacte soporte."
|
||||
msgstr "Ten en cuenta que las extensiones están desarrolladas por terceros, aseguraté que confías antes de conceder el permiso. Tu privacidad y seguridad es importante para nosotros. Si tienes cualquier duda, contacta con soporte."
|
||||
|
||||
#: src/app/main/ui/workspace/plugins.cljs:271
|
||||
msgid "workspace.plugins.permissions.library-read"
|
||||
|
@ -5588,12 +5588,24 @@ msgstr "Comenzar descargas de ficheros."
|
|||
|
||||
#: src/app/main/ui/workspace/plugins.cljs:236
|
||||
msgid "workspace.plugins.permissions.title"
|
||||
msgstr "LA EXTENSIÓN SOLICITA PERMISO PARA ACCEDER:"
|
||||
msgstr "LA EXTENSIÓN '%s' SOLICITA PERMISO PARA ACCEDER:"
|
||||
|
||||
#: src/app/main/ui/workspace/plugins.cljs:258
|
||||
msgid "workspace.plugins.permissions.user-read"
|
||||
msgstr "Leer la información del usuario actual."
|
||||
|
||||
msgid "workspace.plugins.try-out.title"
|
||||
msgstr "¡LA EXTENSIÓN '%s' HA SIDO INSTALADA PARA TU USUARIO!"
|
||||
|
||||
msgid "workspace.plugins.try-out.message"
|
||||
msgstr "¿Quieres echar un vistazo?. Crearemos un nuevo borrador en tu equipo actual. (Si no, puedes encontrar los plugins instalados en cualquier fichero.)"
|
||||
|
||||
msgid "workspace.plugins.try-out.cancel"
|
||||
msgstr "AHORA NO"
|
||||
|
||||
msgid "workspace.plugins.try-out.try"
|
||||
msgstr "PROBAR PLUGIN"
|
||||
|
||||
#: src/app/main/ui/workspace/plugins.cljs:192
|
||||
msgid "workspace.plugins.plugin-list-link"
|
||||
msgstr "Lista de extensiones"
|
||||
|
|
Loading…
Add table
Reference in a new issue