{"id":4213,"date":"2013-04-12T09:22:25","date_gmt":"2013-04-12T16:22:25","guid":{"rendered":"https:\/\/creativemarket.com\/blog\/?p=4213"},"modified":"2024-03-22T00:05:02","modified_gmt":"2024-03-22T07:05:02","slug":"how-we-built-a-photoshop-extension-with-html-css-and-js","status":"publish","type":"post","link":"https:\/\/creativemarket.com\/blog\/how-we-built-a-photoshop-extension-with-html-css-and-js","title":{"rendered":"How We Built a Photoshop Extension with HTML, CSS, and JS."},"content":{"rendered":"<p>Earlier this week, we released the <a href=\"https:\/\/creativemarket.com\/photoshop-extension\">Creative Market Photoshop Extension<\/a> &acirc;&euro;&rdquo; a new way to buy, download, and install awesome design resources without leaving Photoshop! <a href=\"https:\/\/creativemarket.com\/gerrenlamson\">Gerren<\/a> shared a few <a href=\"http:\/\/t.co\/EVmDaVcGtm\">high-level design considerations<\/a> from the process of planning the app; now let&acirc;&euro;&trade;s focus on the nerd guts of it.<br \/>\nThe Creative Market Photoshop Extension is a <a href=\"http:\/\/backbonejs.org\/\">Backbone.js<\/a> web app that lives inside of Photoshop. We can update it without you having to install a new update. How&acirc;&euro;&trade;s that work? Read on!<\/p>\n<h2 id=\"architecture\"><span class=\"ez-toc-section\" id=\"Architecture\"><\/span>Architecture<span class=\"ez-toc-section-end\"><\/span><\/h2>\n<p>\nCreative Suite Extensions are really <a href=\"http:\/\/www.adobe.com\/products\/air.html\">AIR<\/a> applications that link a few special libraries from Adobe&acirc;&euro;&trade;s <a href=\"http:\/\/www.adobe.com\/devnet\/creativesuite.html\">Creative Suite SDK<\/a>.<br \/>\nWe developed the initial prototype as a traditional Flex application, entirely using <a href=\"http:\/\/help.adobe.com\/en_US\/FlashPlatform\/reference\/actionscript\/3\/fl\/controls\/package-detail.html\">Flex UI controls<\/a>. Quickly we realized we wanted something more flexible&acirc;&euro;&rdquo;a platform that would let us iterate like we iterate on the web. The rigidity of the code combined with the pains of recompiling and issuing updates for the smallest of changes just wasn&acirc;&euro;&trade;t going to work.<br \/>\nAt that point, we chose to build the core of the application as a web app that lives on our servers that gets loaded into a <a href=\"http:\/\/help.adobe.com\/en_US\/FlashPlatform\/reference\/actionscript\/3\/mx\/controls\/HTML.html\"><code>mx:HTML<\/code><\/a> control (Webkit).<br \/>\n<img decoding=\"async\" class=\"alignnone size-full wp-image-4486\" style=\"width: 100%\" src=\"https:\/\/d3ui957tjb5bqd.cloudfront.net\/uploads\/2013\/04\/20014633\/sketch12.jpg.webp\" alt=\"\">\n<\/p>\n<h3 id=\"advantages\"><span class=\"ez-toc-section\" id=\"Advantages\"><\/span>Advantages<span class=\"ez-toc-section-end\"><\/span><\/h3>\n<ul>\n<li><strong>Developing with HTML\/CSS\/JS is <em>fast<\/em>.<\/strong><\/li>\n<li><strong>Rich open-source ecosystem.<\/strong> Using and contributing to OS projects like <a href=\"http:\/\/jquery.com\/\">jQuery<\/a>, <a href=\"http:\/\/handlebarsjs.com\/\">Handlebars.js<\/a>, <a href=\"http:\/\/backbonejs.org\/\">Backbone.js<\/a>, <a href=\"https:\/\/github.com\/thedersen\/backbone.validation\">Backbone.Validation<\/a>, <a href=\"http:\/\/requirejs.org\/\">RequireJS<\/a>, and <a href=\"https:\/\/github.com\/jrburke\/almond\">Almond<\/a> makes our lives easier.<\/li>\n<li><strong>Share components made for the web.<\/strong> Components like the credit card widget built by <a href=\"https:\/\/creativemarket.com\/levi\">Levi<\/a> for the web purchase process can be dropped in without modification.<\/li>\n<\/ul>\n<p>\nCertain functions like downloading &amp; installing products, checking for updates, and general interactions with the filesystem still happen in AS3, but are invoked via Javascript&acirc;&euro;&rdquo;explained more later.\n<\/p>\n<h2><span class=\"ez-toc-section\" id=\"Native_Feel\"><\/span>Native Feel<span class=\"ez-toc-section-end\"><\/span><\/h2>\n<p>\nAn experience that felt like a webpage wasn&acirc;&euro;&trade;t an option&acirc;&euro;&rdquo;we did everything we could to make it snappy &amp; feel like it was a part of Photoshop.\n<\/p>\n<h3 id=\"htmlassetcaching\"><span class=\"ez-toc-section\" id=\"HTML_Asset_Caching\"><\/span>HTML \/ Asset Caching<span class=\"ez-toc-section-end\"><\/span><\/h3>\n<p>\nThe <a href=\"http:\/\/help.adobe.com\/en_US\/FlashPlatform\/reference\/actionscript\/3\/mx\/controls\/HTML.html\">mx:HTML<\/a> control is great for online use, but there&acirc;&euro;&trade;s little to no control of cache use&acirc;&euro;&ldquo;which makes offline usage nearly impossible. If you&acirc;&euro;&trade;re disconnected from the Internet, the window will hang until the connection becomes available or it times out (even if the page exists in cache).<br \/>\nWe developed a custom <code>OfflineHTML<\/code> control that extends <code>mx:HTML<\/code>. It parses HTML and CSS stylesheets, picks out all the static resources (like images and javascript files), downloads them, and rewrites all references to the local copies. That way, when the user opens up the extension again, everything is <em>immediately<\/em> available and served off disk&acirc;&euro;&rdquo;even if disconnected from the internet.\n<\/p>\n<p class=\"small\">(If there were a good way to proxy requests within the HTML control, we would have just used that&acirc;&euro;&brvbar; but there&acirc;&euro;&trade;s not. Sad panda.)<\/p>\n<h3 id=\"liveskinning\"><span class=\"ez-toc-section\" id=\"Live_Re-skinning\"><\/span>Live Re-skinning<span class=\"ez-toc-section-end\"><\/span><\/h3>\n<p>\nWe generate &amp; apply CSS stylesheets on-the-fly when the extension receives <a href=\"http:\/\/cssdk.host.adobe.com\/sdk\/1.5\/docs\/WebHelp\/programmers_guide\/customizing_the_ui.htm\">reskin events<\/a> that contain current UI colors. To generate the stylesheets and add them to the DOM, we use <a href=\"https:\/\/github.com\/diy\/jquery-stylist\">jquery-stylist<\/a>.\n<\/p>\n<p class=\"small\"><strong>Note:<\/strong> On Photoshop CS5.0, the rendered brightness is off, so we have to lighten every color channel by 0x08 to get it to match Photoshop&acirc;&euro;&trade;s UI.<\/p>\n<h3 id=\"imagepreloading\"><span class=\"ez-toc-section\" id=\"Image_Preloading\"><\/span>Image Preloading<span class=\"ez-toc-section-end\"><\/span><\/h3>\n<p>\nWhen the application&acirc;&euro;&trade;s compiled, we generate a &acirc;&euro;&oelig;manifest.json&acirc;&euro; file that lists all image assets used. As the app starts, we prefetch each in the list so that when the user gets to a view that needs an image&acirc;&euro;&brvbar; it&acirc;&euro;&trade;s available.\n<\/p>\n<h2 id=\"communication\"><span class=\"ez-toc-section\" id=\"Communication\"><\/span>Communication<span class=\"ez-toc-section-end\"><\/span><\/h2>\n<h3 id=\"js-as3\"><span class=\"ez-toc-section\" id=\"JS_a%E2%80%A1%E2%80%9D_AS3\"><\/span>JS &acirc;&Dagger;&rdquo; AS3<span class=\"ez-toc-section-end\"><\/span><\/h3>\n<p>\nGetting the Javascript web application to talk to the Flex container (and vice versa) happens through a <a href=\"http:\/\/www.sitepen.com\/blog\/building-on-air-working-with-the-sandbox-bridges\/\">sandbox bridge<\/a>. It allows you to safely isolate the two domains and treat objects and methods on the other side as if they&#8217;re not from another language. With that comes a few caveats:\n<\/p>\n<ul>\n<li>Objects <em>cannot<\/em> contain functions. You&Acirc;&nbsp;<em>can<\/em> pass function references alone to the other side and invoke them, however.<\/li>\n<li>Object prototypes will not match. If passing objects, it&#8217;s best to serialize them then deserialize on the other end. In AS3, objects coming from JS are represented as <code>__HTMLScriptObject<\/code> and <code>__HTMLScriptArray<\/code> types. These types are undocumented and wreak havoc with <a href=\"https:\/\/github.com\/mikechambers\/as3corelib\/tree\/master\/src\/com\/adobe\/serialization\/json\">JSON.encode<\/a>. They do not always behave.<\/li>\n<\/ul>\n<h3 id=\"as3-photoshop\"><span class=\"ez-toc-section\" id=\"AS3_a%E2%80%A1_Photoshop\"><\/span>AS3 &acirc;&Dagger;&rsquo; Photoshop<span class=\"ez-toc-section-end\"><\/span><\/h3>\n<p>\nControlling Photoshop is easy with <a href=\"http:\/\/www.adobe.com\/devnet\/photoshop\/scripting.html\">JSX scripting<\/a>. With the <a href=\"http:\/\/blogs.adobe.com\/crawlspace\/2006\/05\/installing_and_1.html\">Scripting Listener plugin<\/a> installed, Photoshop logs all actions performed as code to &#8220;ScriptingListenerJS.log&#8221;. If taken, wrapped in a function, and dropped in your project&#8217;s *.jsx file, the code can be executed from AS3 using <a href=\"http:\/\/cssdk.host.adobe.com\/sdk\/1.0\/docs\/WebHelp\/references\/csxslib\/com\/adobe\/csxs\/core\/CSXSInterface.html#evalScript()\"><code>evalScript<\/code><\/a>:\n<\/p>\n<pre><code>var result:SyncResult = CSXSInterface.instance.evalScript('jsxFunction');<\/code><\/pre>\n<p>\nIf needed, you can pass along arguments:\n<\/p>\n<pre><code>CSXSInterface.instance.evalScript('jsxFunction', 'Yo!');<\/code><\/pre>\n<p>\nHowever, in CS4 products, only one argument is supported. In CS5+, passing strings with certain characters like quotation marks and backslashes in them will cause a <code>PlugPlugRequestFailed<\/code> result&acirc;&euro;&rdquo;for no valid reason.<br \/>\nThe solution is to serialize and encode the arguments (hexadecimal) in AS3 then decode them in the JSX script. This will prevent failures from mysteriously-forbidden characters and bypass CS4&#8217;s limit of only one argument.\n<\/p>\n<h2 id=\"frontend\"><span class=\"ez-toc-section\" id=\"Front-end_Structure\"><\/span>Front-end Structure<span class=\"ez-toc-section-end\"><\/span><\/h2>\n<p><a href=\"http:\/\/backbonejs.org\/\">Backbone.js<\/a><\/p>\n<p>&Acirc;&nbsp;worked wonderfully for defining views tied to data models (and keeping the two decoupled). The UI is made up of many views within views.\n<\/p>\n<h3><span class=\"ez-toc-section\" id=\"Fundamental_Views\"><\/span>Fundamental Views<span class=\"ez-toc-section-end\"><\/span><\/h3>\n<ul>\n<li><strong><code>View<\/code><\/strong>: The view that all other views are derived from. Contains methods like: &#8220;activate&#8221;, &#8220;deactivate&#8221;, &#8220;repaint&#8221;, &#8220;reflow&#8221;, and methods for setting up form validation via <a href=\"https:\/\/github.com\/thedersen\/backbone.validation\">Backbone.Validation<\/a>.<\/li>\n<li><strong><code>Page<\/code><\/strong>: An extensible view for all pages (Home, Account, Product Detail, etc).<\/li>\n<li><strong><code>PageStack<\/code><\/strong>: A container for Page views, controlled by push and pop methods. It manages the visibility of its children so that the last item in the stack is the only one visible. Also, it sets the &#8220;previousPage&#8221; property on children which is useful for displaying a &#8220;Back&#8221; button in each.<\/li>\n<li><strong><code>Dialog<\/code><\/strong>: An extensible view for all modal dialogs. Dialogs are opened by pages: <code>page.openDialog(dialogView);<\/code><\/li>\n<li><strong><code>Root<\/code><\/strong>: The main view of the application, containing the footer tab strip and two PageStacks: one for the Browse tab and one for the Account tab.<\/li>\n<\/ul>\n<h3><span class=\"ez-toc-section\" id=\"Event_Bus\"><\/span>Event Bus<span class=\"ez-toc-section-end\"><\/span><\/h3>\n<p>\nModels and views are connected to a global event bus. Both can declare an event namespace that will cause all emitted events to be prefixed when sent to the global bus. For example, with our <code>Session<\/code> model, the namespace is &#8220;session&#8221;. Other models and views can listen for its events easily: &#8220;session:change:id&#8221;.\n<\/p>\n<h3><span class=\"ez-toc-section\" id=\"Session_Management\"><\/span>Session Management<span class=\"ez-toc-section-end\"><\/span><\/h3>\n<p>\nWhen a user signs in or out, all views with user state must be updated to reflect the change. Basic controls listen for &#8220;session:change&#8221; events and update accordingly.<br \/>\nPages require slightly more care. For instance, once a user has signed out, it&#8217;s important to <em>remove<\/em> the &#8220;Account&#8221; page. Rather than doing this manually and having handlers strewn about the application, each subclass of Page simply declares whether it needs a session or not in order to exist:\n<\/p>\n<table width=\"100%\">\n<tbody>\n<tr>\n<td><code>false<\/code><\/td>\n<td>View is cleared when the user signs in.<\/td>\n<\/tr>\n<tr>\n<td><code>true<\/code><\/td>\n<td>View is cleared when the user signs out.<\/td>\n<\/tr>\n<tr>\n<td><code>null<\/code><\/td>\n<td>&#8220;Don&#8217;t care&#8221;. Nothing is done in either case.<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<p>\nPageStack views listen for session changes and selectively remove child pages that don&#8217;t match their defined requirement.\n<\/p>\n<h2 id=\"buildtooling\"><span class=\"ez-toc-section\" id=\"Build_Tooling\"><\/span>Build Tooling<span class=\"ez-toc-section-end\"><\/span><\/h2>\n<p>\nThe conventional method of compiling extensions involves <a href=\"http:\/\/www.adobe.com\/devnet\/flash-builder.html\">Flash Builder<\/a> with the <a href=\"http:\/\/www.adobe.com\/devnet\/creativesuite\/cs-extension-builder.html\">Extension Builder<\/a> plugin. It makes getting started easy, but long term, it becomes difficult to work with as a project&acirc;&euro;&trade;s needs diverge from the basics.<br \/>\nWe automated it all&acirc;&euro;&rdquo;compiling, packaging, application signing, and deploys&acirc;&euro;&rdquo;using <a href=\"http:\/\/nodejs.org\/\">node.js<\/a>. This is cool for two reasons: (1) use any IDE you want, and (2) it&acirc;&euro;&trade;s fast!<br \/>\nAll project settings live in a single &#8220;package.json&#8221; file. The properties get synthesized to various XML &amp; MXI files needed by the Flex compiler and <a href=\"https:\/\/www.adobeexchange.com\/resources\/7#packman\">UCF tool<\/a>.\n<\/p>\n<h3 id=\"compilation\"><span class=\"ez-toc-section\" id=\"Compilation\"><\/span>Compilation<span class=\"ez-toc-section-end\"><\/span><\/h3>\n<p>\nWhen compiling a release, we compile two separate versions: one for CS5 and one for CS6. These are both packaged up as a single ZXP installer.\n<\/p>\n<h3 id=\"deploys\"><span class=\"ez-toc-section\" id=\"Deploys\"><\/span>Deploys<span class=\"ez-toc-section-end\"><\/span><\/h3>\n<p>\nTo deploy, we generate changelogs, automatically tag the version on <a href=\"https:\/\/github.com\/creativemarket\/\">Github<\/a>, generate &#8220;update.xml&#8221; (along with a JSON representation), and push the files to S3. It&#8217;s all scripted&acirc;&euro;&rdquo;there&#8217;s nothing manual to do with sending out new versions.<br \/>\n<del>In the next few days, we&acirc;&euro;&trade;ll be open sourcing the tool for all of this \ud83d\ude42<\/del> The tool is open-source and available at: <a href=\"https:\/\/github.com\/creativemarket\/csxs\">https:\/\/github.com\/creativemarket\/csxs<\/a>\n<\/p>\n<h2 id=\"conclusion\"><span class=\"ez-toc-section\" id=\"Conclusion\"><\/span>Conclusion<span class=\"ez-toc-section-end\"><\/span><\/h2>\n<p>\nAdobe provides a pretty cool platform that makes it quick to develop for their products. The difficulties lie with scattered &amp; incomplete documentation and platform bugs that are out of your control as a 3<sup>rd<\/sup> party developer. Sadly there&#8217;s a &#8220;fend for yourself&#8221; developer ecosystem right now. I&#8217;d love to see that change.<br \/>\nOur Photoshop Extension is still in its infancy&acirc;&euro;&rdquo;there&#8217;s still so much room to grow. As we dream, plan, and develop new features and&Acirc;&nbsp;tighter integration, I hope to share what we learn.\n<\/p>\n<h3><span class=\"ez-toc-section\" id=\"Upcoming_API\"><\/span>Upcoming API<span class=\"ez-toc-section-end\"><\/span><\/h3>\n<p>\nAre you a developer looking to integrate Creative Market content and inline-purchasing in your own website or app? <a href=\"https:\/\/creativemarket.com\/platform\/api\">Apply for an API key<\/a> today!<\/p><\/p>\n","protected":false},"excerpt":{"rendered":"<p>Earlier this week, we released the Creative Market Photoshop Extension &acirc;&euro;&rdquo; a new way to buy, download, and install awesome design resources without leaving Photoshop! Gerren shared a few high-level design considerations from the process of planning the app; now let&acirc;&euro;&trade;s focus on the nerd guts of it. The Creative Market Photoshop Extension is a [&hellip;]<\/p>\n","protected":false},"author":57,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"inline_featured_image":false,"footnotes":""},"categories":[127],"tags":[],"topics":[],"class_list":["post-4213","post","type-post","status-publish","format-standard","hentry","category-shop-center"],"acf":[],"yoast_head":"<!-- This site is optimized with the Yoast SEO Premium plugin v28.4 (Yoast SEO v28.4) - https:\/\/yoast.com\/product\/yoast-seo-premium-wordpress\/ -->\n<title>How We Built a Photoshop Extension with HTML, CSS, and JS. - Creative Market Blog<\/title>\n<meta name=\"robots\" content=\"index, follow, max-snippet:-1, max-image-preview:large, max-video-preview:-1\" \/>\n<link rel=\"canonical\" href=\"https:\/\/creativemarket.com\/blog\/how-we-built-a-photoshop-extension-with-html-css-and-js\" \/>\n<meta property=\"og:locale\" content=\"en_US\" \/>\n<meta property=\"og:type\" content=\"article\" \/>\n<meta property=\"og:title\" content=\"How We Built a Photoshop Extension with HTML, CSS, and JS.\" \/>\n<meta property=\"og:description\" content=\"Earlier this week, we released the Creative Market Photoshop Extension &acirc;&euro;&rdquo; a new way to buy, download, and install awesome design\" \/>\n<meta property=\"og:url\" content=\"https:\/\/creativemarket.com\/blog\/how-we-built-a-photoshop-extension-with-html-css-and-js\" \/>\n<meta property=\"og:site_name\" content=\"Creative Market Blog\" \/>\n<meta property=\"article:publisher\" content=\"https:\/\/www.facebook.com\/crtvmrkt\" \/>\n<meta property=\"article:published_time\" content=\"2013-04-12T16:22:25+00:00\" \/>\n<meta property=\"article:modified_time\" content=\"2024-03-22T07:05:02+00:00\" \/>\n<meta property=\"og:image\" content=\"https:\/\/d3ui957tjb5bqd.cloudfront.net\/uploads\/2013\/04\/20014633\/sketch12.jpg.webp\" \/>\n<meta name=\"author\" content=\"Creative Market\" \/>\n<meta name=\"twitter:card\" content=\"summary_large_image\" \/>\n<meta name=\"twitter:creator\" content=\"@creativemarket\" \/>\n<meta name=\"twitter:site\" content=\"@creativemarket\" \/>\n<meta name=\"twitter:label1\" content=\"Written by\" \/>\n\t<meta name=\"twitter:data1\" content=\"Creative Market\" \/>\n\t<meta name=\"twitter:label2\" content=\"Est. reading time\" \/>\n\t<meta name=\"twitter:data2\" content=\"8 minutes\" \/>\n<script type=\"application\/ld+json\" class=\"yoast-schema-graph\">{\"@context\":\"https:\\\/\\\/schema.org\",\"@graph\":[{\"@type\":\"WebPage\",\"@id\":\"https:\\\/\\\/creativemarket.com\\\/blog\\\/how-we-built-a-photoshop-extension-with-html-css-and-js\",\"url\":\"https:\\\/\\\/creativemarket.com\\\/blog\\\/how-we-built-a-photoshop-extension-with-html-css-and-js\",\"name\":\"How We Built a Photoshop Extension with HTML, CSS, and JS. - Creative Market Blog\",\"isPartOf\":{\"@id\":\"https:\\\/\\\/creativemarket.com\\\/blog\\\/#website\"},\"primaryImageOfPage\":{\"@id\":\"https:\\\/\\\/creativemarket.com\\\/blog\\\/how-we-built-a-photoshop-extension-with-html-css-and-js#primaryimage\"},\"image\":{\"@id\":\"https:\\\/\\\/creativemarket.com\\\/blog\\\/how-we-built-a-photoshop-extension-with-html-css-and-js#primaryimage\"},\"thumbnailUrl\":\"https:\\\/\\\/d3ui957tjb5bqd.cloudfront.net\\\/uploads\\\/2013\\\/04\\\/20014633\\\/sketch12.jpg.webp\",\"datePublished\":\"2013-04-12T16:22:25+00:00\",\"dateModified\":\"2024-03-22T07:05:02+00:00\",\"author\":{\"@id\":\"https:\\\/\\\/creativemarket.com\\\/blog\\\/#\\\/schema\\\/person\\\/d6927fab89f28e3dd8f6bb7b36219d66\"},\"breadcrumb\":{\"@id\":\"https:\\\/\\\/creativemarket.com\\\/blog\\\/how-we-built-a-photoshop-extension-with-html-css-and-js#breadcrumb\"},\"inLanguage\":\"en-US\",\"potentialAction\":[{\"@type\":\"ReadAction\",\"target\":[\"https:\\\/\\\/creativemarket.com\\\/blog\\\/how-we-built-a-photoshop-extension-with-html-css-and-js\"]}]},{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\\\/\\\/creativemarket.com\\\/blog\\\/how-we-built-a-photoshop-extension-with-html-css-and-js#primaryimage\",\"url\":\"https:\\\/\\\/d3ui957tjb5bqd.cloudfront.net\\\/uploads\\\/2013\\\/04\\\/20014633\\\/sketch12.jpg.webp\",\"contentUrl\":\"https:\\\/\\\/d3ui957tjb5bqd.cloudfront.net\\\/uploads\\\/2013\\\/04\\\/20014633\\\/sketch12.jpg.webp\"},{\"@type\":\"BreadcrumbList\",\"@id\":\"https:\\\/\\\/creativemarket.com\\\/blog\\\/how-we-built-a-photoshop-extension-with-html-css-and-js#breadcrumb\",\"itemListElement\":[{\"@type\":\"ListItem\",\"position\":1,\"name\":\"Home\",\"item\":\"https:\\\/\\\/creativemarket.com\\\/blog\\\/\"},{\"@type\":\"ListItem\",\"position\":2,\"name\":\"How We Built a Photoshop Extension with HTML, CSS, and JS.\"}]},{\"@type\":\"WebSite\",\"@id\":\"https:\\\/\\\/creativemarket.com\\\/blog\\\/#website\",\"url\":\"https:\\\/\\\/creativemarket.com\\\/blog\\\/\",\"name\":\"Creative Market Blog\",\"description\":\"Creative Market Blog\",\"potentialAction\":[{\"@type\":\"SearchAction\",\"target\":{\"@type\":\"EntryPoint\",\"urlTemplate\":\"https:\\\/\\\/creativemarket.com\\\/blog\\\/?s={search_term_string}\"},\"query-input\":{\"@type\":\"PropertyValueSpecification\",\"valueRequired\":true,\"valueName\":\"search_term_string\"}}],\"inLanguage\":\"en-US\"}]}<\/script>\n<!-- \/ Yoast SEO Premium plugin. -->","yoast_head_json":{"title":"How We Built a Photoshop Extension with HTML, CSS, and JS. - Creative Market Blog","robots":{"index":"index","follow":"follow","max-snippet":"max-snippet:-1","max-image-preview":"max-image-preview:large","max-video-preview":"max-video-preview:-1"},"canonical":"https:\/\/creativemarket.com\/blog\/how-we-built-a-photoshop-extension-with-html-css-and-js","og_locale":"en_US","og_type":"article","og_title":"How We Built a Photoshop Extension with HTML, CSS, and JS.","og_description":"Earlier this week, we released the Creative Market Photoshop Extension &acirc;&euro;&rdquo; a new way to buy, download, and install awesome design","og_url":"https:\/\/creativemarket.com\/blog\/how-we-built-a-photoshop-extension-with-html-css-and-js","og_site_name":"Creative Market Blog","article_publisher":"https:\/\/www.facebook.com\/crtvmrkt","article_published_time":"2013-04-12T16:22:25+00:00","article_modified_time":"2024-03-22T07:05:02+00:00","og_image":[{"url":"https:\/\/d3ui957tjb5bqd.cloudfront.net\/uploads\/2013\/04\/20014633\/sketch12.jpg.webp","type":"","width":"","height":""}],"author":"Creative Market","twitter_card":"summary_large_image","twitter_creator":"@creativemarket","twitter_site":"@creativemarket","twitter_misc":{"Written by":"Creative Market","Est. reading time":"8 minutes"},"schema":{"@context":"https:\/\/schema.org","@graph":[{"@type":"WebPage","@id":"https:\/\/creativemarket.com\/blog\/how-we-built-a-photoshop-extension-with-html-css-and-js","url":"https:\/\/creativemarket.com\/blog\/how-we-built-a-photoshop-extension-with-html-css-and-js","name":"How We Built a Photoshop Extension with HTML, CSS, and JS. - Creative Market Blog","isPartOf":{"@id":"https:\/\/creativemarket.com\/blog\/#website"},"primaryImageOfPage":{"@id":"https:\/\/creativemarket.com\/blog\/how-we-built-a-photoshop-extension-with-html-css-and-js#primaryimage"},"image":{"@id":"https:\/\/creativemarket.com\/blog\/how-we-built-a-photoshop-extension-with-html-css-and-js#primaryimage"},"thumbnailUrl":"https:\/\/d3ui957tjb5bqd.cloudfront.net\/uploads\/2013\/04\/20014633\/sketch12.jpg.webp","datePublished":"2013-04-12T16:22:25+00:00","dateModified":"2024-03-22T07:05:02+00:00","author":{"@id":"https:\/\/creativemarket.com\/blog\/#\/schema\/person\/d6927fab89f28e3dd8f6bb7b36219d66"},"breadcrumb":{"@id":"https:\/\/creativemarket.com\/blog\/how-we-built-a-photoshop-extension-with-html-css-and-js#breadcrumb"},"inLanguage":"en-US","potentialAction":[{"@type":"ReadAction","target":["https:\/\/creativemarket.com\/blog\/how-we-built-a-photoshop-extension-with-html-css-and-js"]}]},{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/creativemarket.com\/blog\/how-we-built-a-photoshop-extension-with-html-css-and-js#primaryimage","url":"https:\/\/d3ui957tjb5bqd.cloudfront.net\/uploads\/2013\/04\/20014633\/sketch12.jpg.webp","contentUrl":"https:\/\/d3ui957tjb5bqd.cloudfront.net\/uploads\/2013\/04\/20014633\/sketch12.jpg.webp"},{"@type":"BreadcrumbList","@id":"https:\/\/creativemarket.com\/blog\/how-we-built-a-photoshop-extension-with-html-css-and-js#breadcrumb","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"https:\/\/creativemarket.com\/blog\/"},{"@type":"ListItem","position":2,"name":"How We Built a Photoshop Extension with HTML, CSS, and JS."}]},{"@type":"WebSite","@id":"https:\/\/creativemarket.com\/blog\/#website","url":"https:\/\/creativemarket.com\/blog\/","name":"Creative Market Blog","description":"Creative Market Blog","potentialAction":[{"@type":"SearchAction","target":{"@type":"EntryPoint","urlTemplate":"https:\/\/creativemarket.com\/blog\/?s={search_term_string}"},"query-input":{"@type":"PropertyValueSpecification","valueRequired":true,"valueName":"search_term_string"}}],"inLanguage":"en-US"}]}},"_links":{"self":[{"href":"https:\/\/creativemarket.com\/blog\/wp-json\/wp\/v2\/posts\/4213","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/creativemarket.com\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/creativemarket.com\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/creativemarket.com\/blog\/wp-json\/wp\/v2\/users\/57"}],"replies":[{"embeddable":true,"href":"https:\/\/creativemarket.com\/blog\/wp-json\/wp\/v2\/comments?post=4213"}],"version-history":[{"count":0,"href":"https:\/\/creativemarket.com\/blog\/wp-json\/wp\/v2\/posts\/4213\/revisions"}],"wp:attachment":[{"href":"https:\/\/creativemarket.com\/blog\/wp-json\/wp\/v2\/media?parent=4213"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/creativemarket.com\/blog\/wp-json\/wp\/v2\/categories?post=4213"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/creativemarket.com\/blog\/wp-json\/wp\/v2\/tags?post=4213"},{"taxonomy":"topics","embeddable":true,"href":"https:\/\/creativemarket.com\/blog\/wp-json\/wp\/v2\/topics?post=4213"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}