1
0
mirror of https://github.com/chinchang/web-maker.git synced 2025-10-11 19:44:25 +02:00

Merge pull request #303 from chinchang/preact

Migration from vanilla JS to Preact!
This commit is contained in:
Kushagra Gour
2018-06-22 21:57:31 +05:30
committed by GitHub
94 changed files with 17910 additions and 12711 deletions

View File

@@ -3,7 +3,7 @@
"browser": true
},
"parser": "babel-eslint",
"extends": "eslint:recommended",
"extends": ["eslint:recommended", "eslint-config-synacor"],
"rules": {
"accessor-pairs": "error",
@@ -26,7 +26,13 @@
"eol-last": "off",
"eqeqeq": "error",
"func-names": "off",
"func-style": ["error", "declaration", { "allowArrowFunctions": true }],
"func-style": [
"error",
"declaration",
{
"allowArrowFunctions": true
}
],
"generator-star-spacing": "error",
"global-require": "off",
"guard-for-in": "error",
@@ -71,7 +77,7 @@
"no-implied-eval": "error",
"no-inline-comments": "off",
"no-inner-declarations": ["error", "functions"],
"no-invalid-this": "error",
"no-invalid-this": "off",
"no-iterator": "error",
"no-label-var": "error",
"no-labels": "error",
@@ -165,7 +171,7 @@
"before": false
}
],
"sort-imports": "error",
"sort-imports": "off",
"sort-vars": "off",
"strict": ["error", "never"],
"template-curly-spacing": "error",
@@ -174,7 +180,15 @@
"vars-on-top": "off",
"wrap-regex": "error",
"yield-star-spacing": "error",
"yoda": ["error", "never"]
"yoda": ["error", "never"],
"brace-style": "off",
"lines-around-comment": "off",
"arrow-body-style": "off",
"indent": "off",
"react/sort-comp": "off",
"react/jsx-no-bind": "off",
"no-extra-semi": "off",
"compat/compat": "off"
},
"globals": {
"ArrayBuffer": true,

1
.gitignore vendored
View File

@@ -9,3 +9,4 @@
node_modules/
*.map
.sass-cache
extension/

View File

@@ -1,2 +1,3 @@
src/lib/
app/
build/

View File

@@ -1,8 +1,11 @@
language: node_js
node_js:
- '8.1.2'
- '10.1.0'
install:
- npm install -g eslint
- npm install -g babel-eslint
- npm install -g eslint-plugin-react
- npm install -g eslint-plugin-mocha
- npm install -g eslint-config-synacor
script:
- npm run lint

0
app/Inconsolata.ttf Executable file → Normal file
View File

BIN
app/icons/icon-128x128.png Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

BIN
app/icons/icon-144x144.png Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 KiB

BIN
app/icons/icon-152x152.png Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

BIN
app/icons/icon-192x192.png Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.0 KiB

BIN
app/icons/icon-384x384.png Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.3 KiB

BIN
app/icons/icon-512x512.png Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

BIN
app/icons/icon-72x72.png Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

BIN
app/icons/icon-96x96.png Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

View File

@@ -1,10 +1,15 @@
<html>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Web Maker</title>
<link rel="icon" href="icon-128.png">
<meta name=viewport content="width=device-width, initial-scale=1">
<meta name="viewport" content="width=device-width,initial-scale=1">
<meta name="mobile-web-app-capable" content="yes">
<meta name="apple-mobile-web-app-capable" content="yes">
<link rel="manifest" href="/manifest.json">
<style>
/* Critically acclaimed CSS */
@@ -26,23 +31,16 @@
<link rel="stylesheet" href="vendor.css">
<link rel="stylesheet" id="editorThemeLinkTag" href="lib/codemirror/theme/monokai.css"></link>
<link rel="stylesheet" id="editorThemeLinkTag" href="lib/codemirror/theme/monokai.css">
<link rel="stylesheet" href="style.css">
<style id="fontStyleTemplate" type="template">
@font-face {
font-family: 'fontname';
font-style: normal;
font-weight: 400;
src: url(fontname.ttf) format('truetype');
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2212, U+2215, U+E0FF, U+EFFD, U+F000;
}
.Codemirror pre {
font-family: 'fontname', monospace;
}
@font-face { font-family: 'fontname'; font-style: normal; font-weight: 400; src: url(fontname.ttf) format('truetype'); unicode-range:
U+0000-00FF, U+0131, U+0152-0153, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2212, U+2215, U+E0FF, U+EFFD,
U+F000; } .CodeMirror pre { font-family: 'fontname', monospace; }
</style>
<style type="text/css" id="fontStyleTag">
<style id="fontStyleTag">
@font-face {
font-family: 'FiraCode';
font-style: normal;
@@ -50,641 +48,16 @@
src: url(FiraCode.ttf) format('truetype');
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2212, U+2215, U+E0FF, U+EFFD, U+F000;
}
.Codemirror pre {
.CodeMirror pre {
font-family: 'FiraCode', monospace;
}
</style>
</head>
<link rel="shortcut icon" href="/favicon.ico"></head>
<body>
<div class="main-container">
<div class="main-header">
<input type="text" id="js-title-input" title="Click to edit" class="item-title-input" value="Untitled Work">
<div class="main-header__btn-wrap flex flex-v-center">
<a id="runBtn" class="hide flex flex-v-center hint--rounded hint--bottom-left" aria-label="Run preview (Ctrl/⌘ + Shift + 5)" d-click="onRunBtnClick">
<svg style="width: 14px; height: 14px;">
<use xlink:href="#play-icon"></use>
</svg>Run
</a>
<a d-open-modal="addLibraryModal" data-event-category="ui" data-event-action="addLibraryButtonClick" class="flex-v-center hint--rounded hint--bottom-left" aria-label="Add a JS/CSS library">
Add library <span id="js-external-lib-count" style="display:none;" class="count-label"></span>
</a>
<a class="flex flex-v-center hint--rounded hint--bottom-left" aria-label="Start a new creation" d-click="onNewBtnClick">
<svg style="vertical-align:middle;width:14px;height:14px" viewBox="0 0 24 24">
<path d="M19,13H13V19H11V13H5V11H11V5H13V11H19V13Z" />
</svg>New
</a>
<a id="saveBtn" class="flex flex-v-center hint--rounded hint--bottom-left" aria-label="Save current creation (Ctrl/⌘ + S)" d-click="onSaveBtnClick">
<svg style="vertical-align:middle;width:14px;height:14px" viewBox="0 0 24 24">
<path d="M15,9H5V5H15M12,19A3,3 0 0,1 9,16A3,3 0 0,1 12,13A3,3 0 0,1 15,16A3,3 0 0,1 12,19M17,3H5C3.89,3 3,3.9 3,5V19A2,2 0 0,0 5,21H19A2,2 0 0,0 21,19V7L17,3Z" />
</svg>
<svg class="btn-loader" width="15" height="15" stroke="#fff">
<use xlink:href="#loader-icon"></use>
</svg>
Save
</a>
<a id="openItemsBtn" class="flex flex-v-center hint--rounded hint--bottom-left" aria-label="Open a saved creation (Ctrl/⌘ + O)" d-click="onOpenBtnClick">
<svg style="width:14px;height:14px;vertical-align:middle;" viewBox="0 0 24 24">
<path d="M13,9V3.5L18.5,9M6,2C4.89,2 4,2.89 4,4V20A2,2 0 0,0 6,22H18A2,2 0 0,0 20,20V8L14,2H6Z" />
</svg>
<svg class="btn-loader" width="15" height="15" stroke="#fff">
<use xlink:href="#loader-icon"></use>
</svg>
Open
</a>
<a d-open-modal="loginModal" data-event-category="ui" data-event-action="loginButtonClick" class="hide-on-login flex flex-v-center hint--rounded hint--bottom-left" aria-label="Login/Signup">
Login/Signup
</a>
<a d-open-modal="profileModal" data-event-category="ui" data-event-action="headerAvatarClick" aria-label="See profile or Logout" class="hide-on-logout hint--rounded hint--bottom-left">
<img id="headerAvatarImg" width="20" src="" class="main-header__avatar-img"/>
</a>
</div>
</div>
<div class="content-wrap flex flex-grow">
<div class="code-side" id="js-code-side">
<div data-code-wrap-id="0" id="js-html-code" data-type="html" class="code-wrap">
<div class="js-code-wrap__header code-wrap__header" title="Double click to toggle code pane">
<label class="btn-group" dropdow title="Click to change">
<span id="js-html-mode-label" class="code-wrap__header-label">HTML</span><span class="caret"></span>
<select data-type="html" class="js-mode-select hidden-select" name="">
<option value="html">HTML</option>
<option value="markdown">Markdown</option>
<option value="jade">Pug</option>
</select>
</label>
<div class="code-wrap__header-right-options">
<a class="js-code-collapse-btn code-wrap__header-btn code-wrap__collapse-btn" title="Toggle code pane">
</a>
</div>
</div>
</div>
<div data-code-wrap-id="1" id="js-css-code" data-type="css" class="code-wrap">
<div class="js-code-wrap__header code-wrap__header" title="Double click to toggle code pane">
<label class="btn-group" title="Click to change">
<span id="js-css-mode-label" class="code-wrap__header-label">CSS</span><span class="caret"></span>
<select data-type="css" class="js-mode-select hidden-select" name="">
<option value="css">CSS</option>
<option value="scss">SCSS</option>
<option value="sass">SASS</option>
<option value="less">LESS</option>
<option value="stylus">Stylus</option>
<option value="acss">Atomic CSS</option>
</select>
</label>
<div class="code-wrap__header-right-options">
<a href="#" id="cssSettingsBtn" title="Atomic CSS configuration" d-click="openCssSettingsModal" class="code-wrap__header-btn hide">
<svg>
<use xlink:href="#settings-icon"></use>
</svg>
</a>
<a class="js-code-collapse-btn code-wrap__header-btn code-wrap__collapse-btn" title="Toggle code pane">
</a>
</div>
</div>
</div>
<div data-code-wrap-id="2" id="js-js-code" data-type="js" class="code-wrap">
<div class="js-code-wrap__header code-wrap__header" title="Double click to toggle code pane">
<label class="btn-group" title="Click to change">
<span id="js-js-mode-label" class="code-wrap__header-label">JS</span><span class="caret"></span>
<select data-type="js" class="js-mode-select hidden-select">
<option value="js">JS</option>
<option value="coffee">CoffeeScript</option>
<option value="es6">ES6 (Babel)</option>
<option value="typescript">TypeScript</option>
</select>
</label>
<div class="code-wrap__header-right-options">
<a class="js-code-collapse-btn code-wrap__header-btn code-wrap__collapse-btn" title="Toggle code pane">
</a>
</div>
</div>
</div>
</div>
<div class="demo-side" id="js-demo-side">
<iframe src="about://blank" frameborder="0" id="demo-frame" allowfullscreen></iframe>
<div id="consoleEl" class="console is-minimized">
<div id="consoleLogEl" class="console__log" class="code">
<div class="js-console__header code-wrap__header" title="Double click to toggle console">
<span class="code-wrap__header-label">Console (<span id="logCountEl">0</span>)</span>
<div class="code-wrap__header-right-options">
<a class="code-wrap__header-btn" title="Clear console (CTRL + L)" d-click="onClearConsoleBtnClick">
<svg>
<use xlink:href="#cancel-icon"></use>
</svg>
</a>
<a class="code-wrap__header-btn code-wrap__collapse-btn" title="Toggle console" d-click="toggleConsole">
</a>
</div>
</div>
</div>
<div id="consolePromptEl" class="console__prompt flex flex-v-center">
<svg width="18" height="18" fill="#346fd2">
<use xlink:href="#chevron-icon"></use>
</svg>
<input d-keyup="evalConsoleExpr" class="console-exec-input">
</div>
</div>
</div>
</div>
<div class="global-console-container" id="globalConsoleContainerEl"></div>
<div id="footer" class="footer">
<div class="footer__right fr">
<a id="saveHtmlBtn" class="mode-btn hint--rounded hint--top-left hide-on-mobile" data-hint="Save as HTML file">
<svg viewBox="0 0 24 24">
<path d="M5,20H19V18H5M19,9H15V3H9V9H5L12,16L19,9Z" />
</svg>
</a>
<svg style="display: none;" xmlns="http://www.w3.org/2000/svg">
<symbol id="codepen-logo" viewBox="0 0 120 120">
<path class="outer-ring" d="M60.048 0C26.884 0 0 26.9 0 60.048s26.884 60 60 60.047c33.163 0 60.047-26.883 60.047-60.047 S93.211 0 60 0z M60.048 110.233c-27.673 0-50.186-22.514-50.186-50.186S32.375 9.9 60 9.9 c27.672 0 50.2 22.5 50.2 50.186S87.72 110.2 60 110.233z"
/>
<path class="inner-box" d="M97.147 48.319c-0.007-0.047-0.019-0.092-0.026-0.139c-0.016-0.09-0.032-0.18-0.056-0.268 c-0.014-0.053-0.033-0.104-0.05-0.154c-0.025-0.078-0.051-0.156-0.082-0.232c-0.021-0.053-0.047-0.105-0.071-0.156 c-0.033-0.072-0.068-0.143-0.108-0.211c-0.029-0.051-0.061-0.1-0.091-0.148c-0.043-0.066-0.087-0.131-0.135-0.193 c-0.035-0.047-0.072-0.094-0.109-0.139c-0.051-0.059-0.104-0.117-0.159-0.172c-0.042-0.043-0.083-0.086-0.127-0.125 c-0.059-0.053-0.119-0.104-0.181-0.152c-0.048-0.037-0.095-0.074-0.145-0.109c-0.019-0.012-0.035-0.027-0.053-0.039L61.817 23.5 c-1.072-0.715-2.468-0.715-3.54 0L24.34 46.081c-0.018 0.012-0.034 0.027-0.053 0.039c-0.05 0.035-0.097 0.072-0.144 0.1 c-0.062 0.049-0.123 0.1-0.181 0.152c-0.045 0.039-0.086 0.082-0.128 0.125c-0.056 0.055-0.108 0.113-0.158 0.2 c-0.038 0.045-0.075 0.092-0.11 0.139c-0.047 0.062-0.092 0.127-0.134 0.193c-0.032 0.049-0.062 0.098-0.092 0.1 c-0.039 0.068-0.074 0.139-0.108 0.211c-0.024 0.051-0.05 0.104-0.071 0.156c-0.031 0.076-0.057 0.154-0.082 0.2 c-0.017 0.051-0.035 0.102-0.05 0.154c-0.023 0.088-0.039 0.178-0.056 0.268c-0.008 0.047-0.02 0.092-0.025 0.1 c-0.019 0.137-0.029 0.275-0.029 0.416V71.36c0 0.1 0 0.3 0 0.418c0.006 0 0 0.1 0 0.1 c0.017 0.1 0 0.2 0.1 0.268c0.015 0.1 0 0.1 0.1 0.154c0.025 0.1 0.1 0.2 0.1 0.2 c0.021 0.1 0 0.1 0.1 0.154c0.034 0.1 0.1 0.1 0.1 0.213c0.029 0 0.1 0.1 0.1 0.1 c0.042 0.1 0.1 0.1 0.1 0.193c0.035 0 0.1 0.1 0.1 0.139c0.05 0.1 0.1 0.1 0.2 0.2 c0.042 0 0.1 0.1 0.1 0.125c0.058 0.1 0.1 0.1 0.2 0.152c0.047 0 0.1 0.1 0.1 0.1 c0.019 0 0 0 0.1 0.039L58.277 96.64c0.536 0.4 1.2 0.5 1.8 0.537c0.616 0 1.233-0.18 1.77-0.537 l33.938-22.625c0.018-0.012 0.034-0.027 0.053-0.039c0.05-0.035 0.097-0.072 0.145-0.109c0.062-0.049 0.122-0.1 0.181-0.152 c0.044-0.039 0.085-0.082 0.127-0.125c0.056-0.055 0.108-0.113 0.159-0.172c0.037-0.045 0.074-0.09 0.109-0.139 c0.048-0.062 0.092-0.127 0.135-0.193c0.03-0.049 0.062-0.098 0.091-0.146c0.04-0.07 0.075-0.141 0.108-0.213 c0.024-0.051 0.05-0.102 0.071-0.154c0.031-0.078 0.057-0.156 0.082-0.234c0.017-0.051 0.036-0.102 0.05-0.154 c0.023-0.088 0.04-0.178 0.056-0.268c0.008-0.045 0.02-0.092 0.026-0.137c0.018-0.139 0.028-0.277 0.028-0.418V48.735 C97.176 48.6 97.2 48.5 97.1 48.319z M63.238 32.073l25.001 16.666L77.072 56.21l-13.834-9.254V32.073z M56.856 32.1 v14.883L43.023 56.21l-11.168-7.471L56.856 32.073z M29.301 54.708l7.983 5.34l-7.983 5.34V54.708z M56.856 88.022L31.855 71.4 l11.168-7.469l13.833 9.252V88.022z M60.048 67.597l-11.286-7.549l11.286-7.549l11.285 7.549L60.048 67.597z M63.238 88.022V73.14 l13.834-9.252l11.167 7.469L63.238 88.022z M90.794 65.388l-7.982-5.34l7.982-5.34V65.388z"
/>
</symbol>
</svg>
<a href="" id="codepenBtn" class="mode-btn hint--rounded hint--top-left hide-on-mobile" aria-label="Edit on CodePen">
<svg>
<use xlink:href="#codepen-logo"></use>
</svg>
</a>
<a href="" id="screenshotBtn" class="mode-btn hint--rounded hint--top-left show-when-extension" d-click="takeScreenshot"
aria-label="Take screenshot of preview">
<svg style="width:24px;height:24px" viewBox="0 0 24 24">
<path d="M4,4H7L9,2H15L17,4H20A2,2 0 0,1 22,6V18A2,2 0 0,1 20,20H4A2,2 0 0,1 2,18V6A2,2 0 0,1 4,4M12,7A5,5 0 0,0 7,12A5,5 0 0,0 12,17A5,5 0 0,0 17,12A5,5 0 0,0 12,7M12,9A3,3 0 0,1 15,12A3,3 0 0,1 12,15A3,3 0 0,1 9,12A3,3 0 0,1 12,9Z"
/>
</svg>
</a>
<div class="footer__separator hide-on-mobile"></div>
<a id="layoutBtn1" class="mode-btn hide-on-mobile">
<svg viewBox="0 0 100 100" style="transform:rotate(-90deg)">
<use xlink:href="#mode-icon" />
</svg>
</a>
<a id="layoutBtn2" class="mode-btn hide-on-mobile">
<svg viewBox="0 0 100 100">
<use xlink:href="#mode-icon" />
</svg>
</a>
<a id="layoutBtn3" class="mode-btn hide-on-mobile">
<svg viewBox="0 0 100 100" style="transform:rotate(90deg)">
<use xlink:href="#mode-icon" />
</svg>
</a>
<a id="layoutBtn5" class="mode-btn hide-on-mobile">
<svg viewBox="0 0 100 100">
<use xlink:href="#vertical-mode-icon" />
</svg>
</a>
<a id="layoutBtn4" class="mode-btn hint--top-left hint--rounded hide-on-mobile" aria-label="Full Screen">
<svg viewBox="0 0 100 100">
<rect x="0" y="0" width="100" height="100" />
</svg>
</a>
<a class="mode-btn hint--top-left hint--rounded hide-on-mobile" aria-label="Detach Preview" d-click="openDetachedPreview">
<svg viewBox="0 0 24 24">
<path d="M22,17V7H6V17H22M22,5A2,2 0 0,1 24,7V17C24,18.11 23.1,19 22,19H16V21H18V23H10V21H12V19H6C4.89,19 4,18.11 4,17V7A2,2 0 0,1 6,5H22M2,3V15H0V3A2,2 0 0,1 2,1H20V3H2Z"
/>
</svg>
</a>
<div class="footer__separator"></div>
<a id="notificationsBtn" class="notifications-btn mode-btn hint--top-left hint--rounded" aria-label="Notifications">
<svg viewBox="0 0 24 24">
<path d="M14,20A2,2 0 0,1 12,22A2,2 0 0,1 10,20H14M12,2A1,1 0 0,1 13,3V4.08C15.84,4.56 18,7.03 18,10V16L21,19H3L6,16V10C6,7.03 8.16,4.56 11,4.08V3A1,1 0 0,1 12,2Z" />
</svg>
<span class="notifications-btn__dot"></span>
</a>
<a d-open-modal="settingsModal" data-event-category="ui" data-event-action="settingsBtnClick" class="mode-btn hint--top-left hint--rounded" aria-label="Settings">
<svg>
<use xlink:href="#settings-icon"></use>
</svg>
</a>
</div>
<a href="https://webmakerapp.com/" target="_blank">
<div class="logo"></div>
</a>
&copy;
<span class="web-maker-with-tag">Web Maker</span> &nbsp;&nbsp;
<a d-open-modal="helpModal" data-event-category="ui" data-event-action="helpButtonClick" class="footer__link hint--rounded hint--top-right"
aria-label="Help">
<svg style="width:20px; height:20px; vertical-align:text-bottom" viewBox="0 0 24 24">
<path d="M15.07,11.25L14.17,12.17C13.45,12.89 13,13.5 13,15H11V14.5C11,13.39 11.45,12.39 12.17,11.67L13.41,10.41C13.78,10.05 14,9.55 14,9C14,7.89 13.1,7 12,7A2,2 0 0,0 10,9H8A4,4 0 0,1 12,5A4,4 0 0,1 16,9C16,9.88 15.64,10.67 15.07,11.25M13,19H11V17H13M12,2A10,10 0 0,0 2,12A10,10 0 0,0 12,22A10,10 0 0,0 22,12C22,6.47 17.5,2 12,2Z"
/>
</svg>
</a>
<a d-open-modal="keyboardShortcutsModal" data-event-category="ui" data-event-action="keyboardShortcutButtonClick" class="footer__link hint--rounded hint--top-right hide-on-mobile"
aria-label="Keyboard shortcuts">
<svg style="width:20px; height:20px; vertical-align:text-bottom">
<use xlink:href="#keyboard-icon"></use>
</svg>
</a>
<!-- #00ACED -->
<a class="footer__link hint--rounded hint--top-right" aria-label="Tweet about 'Web Maker'" href="http://twitter.com/share?url=https://webmakerapp.com/&text=Web Maker - A blazing fast %26 offline web playground! via @webmakerApp&related=webmakerApp&hashtags=web,frontend,playground,offline"
target="_blank">
<svg style="width:20px; height:20px; vertical-align:text-bottom">
<use xlink:href="#twitter-icon"></use>
</svg>
</a>
<a d-click="openSupportDeveloperModal" data-event-action="supportDeveloperFooterBtnClick" class="footer__link ml-1 hint--rounded hint--top-right hide-on-mobile"
aria-label="Support the developer by pledging some amount" target="_blank">
Support the developer
</a>
</div>
</div>
<div class="modal" id="addLibraryModal">
<div class="modal__content">
<a d-click="onModalCloseBtnClick" href="" aria-label="Close add library modal" title="Close" class="js-modal__close-btn modal__close-btn">
<svg>
<use xlink:href="#cross-icon"></use>
</svg>
</a>
<h1>Add Library</h1>
<input type="text" id="externalLibrarySearchInput" class="full-width" placeholder="Type here to search libraries">
<div class="tar opacity--70">
<small>Powered by cdnjs</small>
</div>
<div style="margin:20px 0;">
Choose from popular libraries:
<select name="" id="js-add-library-select">
<option value="">-------</option>
<optgroup label="JavaScript Libraries">
</optgroup>
<optgroup label="CSS Libraries">
</optgroup>
</select>
</div>
<h3>JavaScript</h3>
<p style="font-size: 0.8em;" class="show-when-extension opacity--70">Note: You can load external scripts from following domains: localhost, https://ajax.googleapis.com, https://code.jquery.com, https://cdnjs.cloudflare.com, https://unpkg.com, https://maxcdn.com, https://cdn77.com, https://maxcdn.bootstrapcdn.com, https://cdn.jsdelivr.net/, https://rawgit.com, https://wzrd.in</p>
<textarea id="js-external-js" class="full-width" id="" cols="30" rows="5" placeholder="Start typing name of a library. Put each library in new line"></textarea>
<h3>CSS</h3>
<textarea id="js-external-css" class="full-width" id="" cols="30" rows="5" placeholder="Start typing name of a library. Put each library in new line"></textarea>
</div>
</div>
<div class="modal" id="cssSettingsModal">
<div class="modal__content">
<a d-click="onModalCloseBtnClick" href="" aria-label="Close CSS settings modal" title="Close" class="js-modal__close-btn modal__close-btn">
<svg>
<use xlink:href="#cross-icon"></use>
</svg>
</a>
<h1>Atomic CSS Settings</h1>
<h3>Configure Atomizer settings. <a href="https://github.com/acss-io/atomizer#api" target="_blank">Read more</a> about available settings.</h3>
<div style="height: calc(100vh - 350px);">
<textarea id="acssSettingsTextarea" cols="30" rows="10"></textarea>
</div>
</div>
</div>
<div class="modal" id="helpModal">
<div class="modal__content" d-html="partials/help-modal.html"></div>
</div>
<div class="modal" id="keyboardShortcutsModal">
<div class="modal__content" d-html="partials/keyboard-shortcuts.html"></div>
</div>
<div class="modal" id="onboardModal">
<div class="modal__content" d-html="partials/onboard-modal.html"></div>
</div>
<div class="modal login-modal" id="loginModal">
<div class="modal__content" d-html="partials/login-modal.html"></div>
</div>
<div class="modal" id="profileModal">
<div class="modal__content">
<a d-click="onModalCloseBtnClick" href="" aria-label="Close logout modal" title="Close" class="js-modal__close-btn modal__close-btn">
<svg>
<use xlink:href="#cross-icon"></use>
</svg>
</a>
<div class="tac">
<img height="80" class="profile-modal__avatar-img" src="" id="profileAvatarImg" alt="Profile image">
<h3 id="profileUserName" class="mb-2"></h3>
<p>
<button class="btn" aria-label="Logout from your account" d-click="logout">Logout</button>
</p>
</div>
</div>
</div>
<div class="modal pledge-modal" id="pledgeModal">
<div class="modal__content" d-html="partials/pledge-modal.html"></div>
</div>
<div class="modal ask-to-import-modal" id="askToImportModal">
<div class="modal__content" d-html="partials/ask-to-import-modal.html"></div>
</div>
<div class="modal modal--settings" id="settingsModal">
<div class="modal__content">
<a d-click="onModalCloseBtnClick" href="" aria-label="Close Settings" title="Close" class="js-modal__close-btn modal__close-btn">
<svg>
<use xlink:href="#cross-icon"></use>
</svg>
</a>
<h1>Settings</h1>
<h3>Indentation</h3>
<div class="line" title="I know this is tough, but you have to decide one!">
<label>
<input type="radio" checked="true" name="indentation" value="spaces" d-change="updateSetting" data-setting="indentWith"> Spaces
</label>
<label class="ml-1">
<input type="radio" name="indentation" value="tabs" d-change="updateSetting" data-setting="indentWith"> Tabs
</label>
</div>
<label class="line" title="">
Indentation Size <input type="range" class="va-m ml-1" value="2" min="1" max="7" list="indentationSizeList" data-setting="indentSize" d-change="updateSetting">
<span id="indentationSizeValueEl"></span>
<datalist id="indentationSizeList">
<option>1</option>
<option>2</option>
<option>3</option>
<option>4</option>
<option>5</option>
<option>6</option>
<option>7</option>
</datalist>
</label>
<hr>
<h3>Editor</h3>
<div class="flex block--mobile">
<div>
<label class="line">
Default Preprocessors
</label>
<div class="flex line">
<select style="flex:1;margin-left:20px" data-setting="htmlMode" d-change="updateSetting">
<option value="html">HTML</option>
<option value="markdown">Markdown</option>
<option value="jade">Pug</option>
</select>
<select style="flex:1;margin-left:20px" data-setting="cssMode" d-change="updateSetting">
<option value="css">CSS</option>
<option value="scss">SCSS</option>
<option value="sass">SASS</option>
<option value="less">LESS</option>
<option value="stylus">Stylus</option>
<option value="acss">Atomic CSS</option>
</select>
<select style="flex:1;margin-left:20px" data-setting="jsMode" d-change="updateSetting">
<option value="js">JS</option>
<option value="coffee">CoffeeScript</option>
<option value="es6">ES6 (Babel)</option>
<option value="typescript">TypeScript</option>
</select>
</div>
<label class="line">
Theme
<select style="flex:1;margin:0 20px" data-setting="editorTheme" d-change="updateSetting"></select>
</label>
<label class="line">
Font
<select style="flex:1;margin:0 20px" data-setting="editorFont" d-change="updateSetting">
<option value="FiraCode">Fira Code</option>
<option value="Inconsolata">Inconsolata</option>
<option value="Monoid">Monoid</option>
<option value="FixedSys">FixedSys</option>
<option disabled="disabled">----</option>
<option value="other">Other font from system</option>
</select>
<input id="customEditorFontInput" type="text" value="" placeholder="Custom font name here" data-setting="editorCustomFont" d-change="updateSetting">
</label>
<label class="line">
Font Size <input style="width:70px" type="number" value="16" data-setting="fontSize" d-change="updateSetting"> px
</label>
<div class="line">
Key bindings
<label class="ml-1">
<input type="radio" checked="true" name="keymap" value="sublime" d-change="updateSetting" data-setting="keymap"> Sublime
</label>
<label class="ml-1">
<input type="radio" name="keymap" value="vim" d-change="updateSetting" data-setting="keymap"> Vim
</label>
</div>
</div>
<div class="ml-2 ml-0--mobile">
<label class="line" title="Toggle wrapping of long sentences onto new line">
<input type="checkbox" d-change="updateSetting" data-setting="lineWrap"> Line wrap
</label>
<label class="line" title="Your Preview will refresh when you resize the preview split">
<input type="checkbox" d-change="updateSetting" data-setting="refreshOnResize"> Refresh preview on resize
</label>
<label class="line" title="Turns on the auto-completion suggestions as you type">
<input type="checkbox" d-change="updateSetting" data-setting="autoComplete"> Auto-complete suggestions
</label>
<label class="line" title="Refreshes the preview as you code. Otherwise use the Run button">
<input type="checkbox" d-change="updateSetting" data-setting="autoPreview"> Auto-preview
</label>
<label class="line" title="Auto-save keeps saving your code at regular intervals after you hit the first save manually">
<input type="checkbox" d-change="updateSetting" data-setting="autoSave"> Auto-save
</label>
<label class="line" title="Loads the last open creation when app starts">
<input type="checkbox" d-change="updateSetting" data-setting="preserveLastCode"> Preserve last written code
</label>
<label class="line show-when-extension" title="Turning this on will start showing Web Maker in every new tab you open">
<input type="checkbox" d-change="updateSetting" data-setting="replaceNewTab"> Replace new tab page
</label>
<label class="line" title="Preserves the console logs across your preview refreshes">
<input type="checkbox" d-change="updateSetting" data-setting="preserveConsoleLogs"> Preserve console logs
</label>
<label class="line" title="Switch to lighter version for better performance. Removes things like blur etc.">
<input type="checkbox" d-change="updateSetting" data-setting="lightVersion"> Fast/light version
</label>
</div>
</div>
<hr>
<h3>Fun</h3>
<p>
<label class="line" title="Enjoy wonderful particle blasts while you type">
<input type="checkbox" d-change="updateSetting" data-setting="isCodeBlastOn"> Code blast!
</label>
</p>
<hr>
<h3>Advanced</h3>
<p>
<label class="line" title="This timeout is used to indentify a possible infinite loop and prevent it.">
Maximum time allowed in a loop iteration
<input type="number" data-setting="infiniteLoopTimeout" d-change="updateSetting"> ms
</label>
<div class="help-text">If any loop iteration takes more than the defined time, it is detected as a potential infinite loop and further iterations are stopped.</div>
</p>
</div>
</div>
<div class="modal" id="notificationsModal">
<div class="modal__content" d-html="partials/changelog.html"></div>
</div>
<div id="js-saved-items-pane" class="saved-items-pane">
<button class="btn saved-items-pane__close-btn" id="js-saved-items-pane-close-btn">X</button>
<div class="flex flex-v-center" style="justify-content: space-between;">
<h3>My Library <span id="savedItemCountEl"></span></h3>
<div class="main-header__btn-wrap">
<a d-click="exportItems" href="" class="btn btn-icon hint--bottom-left hint--rounded hint--medium" aria-label="Export all your creations into a single importable file.">Export
</a>
<a d-click="onImportBtnClicked" href="" class="btn btn-icon hint--bottom-left hint--rounded hint--medium" aria-label="Only the file that you export through the 'Export' button can be imported.">Import
</a>
</div>
</div>
<input type="" id="searchInput" class="search-input" d-input="onSearchInputChange" placeholder="Search your creations here...">
<div id="js-saved-items-wrap" class="saved-items-pane__container">
</div>
</div>
<div class="modal-overlay"></div>
<div class="alerts-container" id="js-alerts-container"></div>
<form style="display:none;" action="https://codepen.io/pen/define" method="POST" target="_blank" id="js-codepen-form">
<input type="hidden" name="data" value='{"title": "New Pen!", "html": "<div>Hello, World!</div>"}'>
</form>
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" style="display:none">
<symbol id="logo" viewBox="-145 -2 372 175">
<g stroke="none" stroke-width="1" fill="none" fill-rule="evenodd" transform="translate(-145.000000, -1.000000)">
<polygon id="Path-1" fill="#FF4600" points="31 0 232 0 132 173.310547"></polygon>
<polygon id="Path-1" fill="#FF6C00" points="0 0 201 0 101 173.310547"></polygon>
<polygon id="Path-1" fill="#FF6C00" transform="translate(271.500000, 86.500000) scale(1, -1) translate(-271.500000, -86.500000) " points="171 0 372 0 272 173.310547"></polygon>
<polygon id="Path-1" fill="#FF4600" transform="translate(241.500000, 86.500000) scale(1, -1) translate(-241.500000, -86.500000) " points="141 0 342 0 242 173.310547"></polygon>
</g>
</symbol>
<symbol id="bug-icon" viewBox="0 0 24 24">
<path d="M14,12H10V10H14M14,16H10V14H14M20,8H17.19C16.74,7.22 16.12,6.55 15.37,6.04L17,4.41L15.59,3L13.42,5.17C12.96,5.06 12.5,5 12,5C11.5,5 11.04,5.06 10.59,5.17L8.41,3L7,4.41L8.62,6.04C7.88,6.55 7.26,7.22 6.81,8H4V10H6.09C6.04,10.33 6,10.66 6,11V12H4V14H6V15C6,15.34 6.04,15.67 6.09,16H4V18H6.81C7.85,19.79 9.78,21 12,21C14.22,21 16.15,19.79 17.19,18H20V16H17.91C17.96,15.67 18,15.34 18,15V14H20V12H18V11C18,10.66 17.96,10.33 17.91,10H20V8Z" />
</symbol>
<symbol id="google-icon" viewBox="0 0 24 24">
<path d="M21.35,11.1H12.18V13.83H18.69C18.36,17.64 15.19,19.27 12.19,19.27C8.36,19.27 5,16.25 5,12C5,7.9 8.2,4.73 12.2,4.73C15.29,4.73 17.1,6.7 17.1,6.7L19,4.72C19,4.72 16.56,2 12.1,2C6.42,2 2.03,6.8 2.03,12C2.03,17.05 6.16,22 12.25,22C17.6,22 21.5,18.33 21.5,12.91C21.5,11.76 21.35,11.1 21.35,11.1V11.1Z" />
</symbol>
<symbol id="fb-icon" viewBox="0 0 24 24">
<path d="M17,2V2H17V6H15C14.31,6 14,6.81 14,7.5V10H14L17,10V14H14V22H10V14H7V10H10V6A4,4 0 0,1 14,2H17Z" />
</symbol>
<symbol id="github-icon" viewBox="0 0 24 24">
<path d="M12,2A10,10 0 0,0 2,12C2,16.42 4.87,20.17 8.84,21.5C9.34,21.58 9.5,21.27 9.5,21C9.5,20.77 9.5,20.14 9.5,19.31C6.73,19.91 6.14,17.97 6.14,17.97C5.68,16.81 5.03,16.5 5.03,16.5C4.12,15.88 5.1,15.9 5.1,15.9C6.1,15.97 6.63,16.93 6.63,16.93C7.5,18.45 8.97,18 9.54,17.76C9.63,17.11 9.89,16.67 10.17,16.42C7.95,16.17 5.62,15.31 5.62,11.5C5.62,10.39 6,9.5 6.65,8.79C6.55,8.54 6.2,7.5 6.75,6.15C6.75,6.15 7.59,5.88 9.5,7.17C10.29,6.95 11.15,6.84 12,6.84C12.85,6.84 13.71,6.95 14.5,7.17C16.41,5.88 17.25,6.15 17.25,6.15C17.8,7.5 17.45,8.54 17.35,8.79C18,9.5 18.38,10.39 18.38,11.5C18.38,15.32 16.04,16.16 13.81,16.41C14.17,16.72 14.5,17.33 14.5,18.26C14.5,19.6 14.5,20.68 14.5,21C14.5,21.27 14.66,21.59 15.17,21.5C19.14,20.16 22,16.42 22,12A10,10 0 0,0 12,2Z" />
</symbol>
<symbol id="settings-icon" viewBox="0 0 24 24">
<path d="M12,15.5A3.5,3.5 0 0,1 8.5,12A3.5,3.5 0 0,1 12,8.5A3.5,3.5 0 0,1 15.5,12A3.5,3.5 0 0,1 12,15.5M19.43,12.97C19.47,12.65 19.5,12.33 19.5,12C19.5,11.67 19.47,11.34 19.43,11L21.54,9.37C21.73,9.22 21.78,8.95 21.66,8.73L19.66,5.27C19.54,5.05 19.27,4.96 19.05,5.05L16.56,6.05C16.04,5.66 15.5,5.32 14.87,5.07L14.5,2.42C14.46,2.18 14.25,2 14,2H10C9.75,2 9.54,2.18 9.5,2.42L9.13,5.07C8.5,5.32 7.96,5.66 7.44,6.05L4.95,5.05C4.73,4.96 4.46,5.05 4.34,5.27L2.34,8.73C2.21,8.95 2.27,9.22 2.46,9.37L4.57,11C4.53,11.34 4.5,11.67 4.5,12C4.5,12.33 4.53,12.65 4.57,12.97L2.46,14.63C2.27,14.78 2.21,15.05 2.34,15.27L4.34,18.73C4.46,18.95 4.73,19.03 4.95,18.95L7.44,17.94C7.96,18.34 8.5,18.68 9.13,18.93L9.5,21.58C9.54,21.82 9.75,22 10,22H14C14.25,22 14.46,21.82 14.5,21.58L14.87,18.93C15.5,18.67 16.04,18.34 16.56,17.94L19.05,18.95C19.27,19.03 19.54,18.95 19.66,18.73L21.66,15.27C21.78,15.05 21.73,14.78 21.54,14.63L19.43,12.97Z"></path>
</symbol>
<symbol id="twitter-icon" viewBox="0 0 16 16">
<path d="M15.969,3.058c-0.586,0.26-1.217,0.436-1.878,0.515c0.675-0.405,1.194-1.045,1.438-1.809
c-0.632,0.375-1.332,0.647-2.076,0.793c-0.596-0.636-1.446-1.033-2.387-1.033c-1.806,0-3.27,1.464-3.27,3.27 c0,0.256,0.029,0.506,0.085,0.745C5.163,5.404,2.753,4.102,1.14,2.124C0.859,2.607,0.698,3.168,0.698,3.767 c0,1.134,0.577,2.135,1.455,2.722C1.616,6.472,1.112,6.325,0.671,6.08c0,0.014,0,0.027,0,0.041c0,1.584,1.127,2.906,2.623,3.206 C3.02,9.402,2.731,9.442,2.433,9.442c-0.211,0-0.416-0.021-0.615-0.059c0.416,1.299,1.624,2.245,3.055,2.271 c-1.119,0.877-2.529,1.4-4.061,1.4c-0.264,0-0.524-0.015-0.78-0.046c1.447,0.928,3.166,1.469,5.013,1.469 c6.015,0,9.304-4.983,9.304-9.304c0-0.142-0.003-0.283-0.009-0.423C14.976,4.29,15.531,3.714,15.969,3.058z"/>
</symbol>
<symbol id="heart-icon" viewBox="0 0 24 24">
<path d="M12,21.35L10.55,20.03C5.4,15.36 2,12.27 2,8.5C2,5.41 4.42,3 7.5,3C9.24,3 10.91,3.81 12,5.08C13.09,3.81 14.76,3 16.5,3C19.58,3 22,5.41 22,8.5C22,12.27 18.6,15.36 13.45,20.03L12,21.35Z" />
</symbol>
<symbol id="play-icon" viewBox="0 0 24 24">
<svg>
<path d="M8,5.14V19.14L19,12.14L8,5.14Z" />
</svg>
</symbol>
<symbol id="cancel-icon" viewBox="0 0 24 24">
<svg>
<path d="M12,2A10,10 0 0,1 22,12A10,10 0 0,1 12,22A10,10 0 0,1 2,12A10,10 0 0,1 12,2M12,4A8,8 0 0,0 4,12C4,13.85 4.63,15.55 5.68,16.91L16.91,5.68C15.55,4.63 13.85,4 12,4M12,20A8,8 0 0,0 20,12C20,10.15 19.37,8.45 18.32,7.09L7.09,18.32C8.45,19.37 10.15,20 12,20Z" />
</svg>
</symbol>
<symbol id="chevron-icon" viewBox="0 0 24 24">
<svg>
<path d="M8.59,16.58L13.17,12L8.59,7.41L10,6L16,12L10,18L8.59,16.58Z" />
</svg>
</symbol>
<symbol id="chat-icon" viewBox="0 0 24 24">
<path d="M20,2H4A2,2 0 0,0 2,4V22L6,18H20A2,2 0 0,0 22,16V4A2,2 0 0,0 20,2M8,14H6V12H8V14M8,11H6V9H8V11M8,8H6V6H8V8M15,14H10V12H15V14M18,11H10V9H18V11M18,8H10V6H18V8Z" />
</symbol>
<symbol id="gift-icon" viewBox="0 0 24 24">
<path d="M22,12V20A2,2 0 0,1 20,22H4A2,2 0 0,1 2,20V12A1,1 0 0,1 1,11V8A2,2 0 0,1 3,6H6.17C6.06,5.69 6,5.35 6,5A3,3 0 0,1 9,2C10,2 10.88,2.5 11.43,3.24V3.23L12,4L12.57,3.23V3.24C13.12,2.5 14,2 15,2A3,3 0 0,1 18,5C18,5.35 17.94,5.69 17.83,6H21A2,2 0 0,1 23,8V11A1,1 0 0,1 22,12M4,20H11V12H4V20M20,20V12H13V20H20M9,4A1,1 0 0,0 8,5A1,1 0 0,0 9,6A1,1 0 0,0 10,5A1,1 0 0,0 9,4M15,4A1,1 0 0,0 14,5A1,1 0 0,0 15,6A1,1 0 0,0 16,5A1,1 0 0,0 15,4M3,8V10H11V8H3M13,8V10H21V8H13Z" />
<symbol id="gift-icon" viewBox="0 0 24 24">
</symbol>
<symbol id="cross-icon" viewBox="0 0 24 24">
<path d="M19,6.41L17.59,5L12,10.59L6.41,5L5,6.41L10.59,12L5,17.59L6.41,19L12,13.41L17.59,19L19,17.59L13.41,12L19,6.41Z" />
</symbol>
<symbol id="keyboard-icon" viewBox="0 0 24 24">
<path d="M19,10H17V8H19M19,13H17V11H19M16,10H14V8H16M16,13H14V11H16M16,17H8V15H16M7,10H5V8H7M7,13H5V11H7M8,11H10V13H8M8,8H10V10H8M11,11H13V13H11M11,8H13V10H11M20,5H4C2.89,5 2,5.89 2,7V17A2,2 0 0,0 4,19H20A2,2 0 0,0 22,17V7C22,5.89 21.1,5 20,5Z" />
</symbol>
<symbol id="mode-icon" viewBox="0 0 100 100">
<g>
<rect x="0" y="0" width="28" height="47" />
<rect x="36" y="0" width="28" height="47"/>
<rect x="72" y="0" width="28" height="47"/>
<rect x="0" y="53" width="100" height="47"/>
</g>
</symbol>
<symbol id="vertical-mode-icon" viewBox="0 0 100 100">
<g>
<rect x="0" y="0" width="20" height="100" />
<rect x="23" y="0" width="20" height="100"/>
<rect x="46" y="0" width="20" height="100"/>
<rect x="69" y="0" width="32" height="100"/>
</g>
</symbol>
<symbol id="loader-icon" viewBox="0 0 44 44">
<!-- By Sam Herbert (@sherb), for everyone. More @ http://goo.gl/7AJzbL -->
<g fill="none" fill-rule="evenodd" stroke-width="10">
<circle cx="22" cy="22" r="1">
<animate attributeName="r"
begin="0s" dur="1.8s"
values="1; 20"
calcMode="spline"
keyTimes="0; 1"
keySplines="0.165, 0.84, 0.44, 1"
repeatCount="indefinite" />
<animate attributeName="stroke-opacity"
begin="0s" dur="1.8s"
values="1; 0"
calcMode="spline"
keyTimes="0; 1"
keySplines="0.3, 0.61, 0.355, 1"
repeatCount="indefinite" />
</circle>
<circle cx="22" cy="22" r="1">
<animate attributeName="r"
begin="-0.9s" dur="1.8s"
values="1; 20"
calcMode="spline"
keyTimes="0; 1"
keySplines="0.165, 0.84, 0.44, 1"
repeatCount="indefinite" />
<animate attributeName="stroke-opacity"
begin="-0.9s" dur="1.8s"
values="1; 0"
calcMode="spline"
keyTimes="0; 1"
keySplines="0.3, 0.61, 0.355, 1"
repeatCount="indefinite" />
</circle>
</g>
</symbol>
</svg>
<script src="vendor.js"></script>
<script src="script.js"></script>
<script defer src="vendor.js"></script><script defer src="script.js"></script>
</body>
</html>

View File

@@ -4,6 +4,5 @@
<iframe src="about://blank" frameborder="0" id="demo-frame" allowfullscreen></iframe>
<script src="detached-window.js"></script>
</body>

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -2,9 +2,15 @@
const fs = require('fs');
const gulp = require('gulp');
const runSequence = require('run-sequence');
const useref = require('gulp-useref');
const cleanCSS = require('gulp-clean-css');
const rename = require('gulp-rename');
const concat = require('gulp-concat');
const babelMinify = require('babel-minify');
const child_process = require('child_process');
const merge = require('merge-stream');
const zip = require('gulp-zip');
function minifyJs(fileName) {
const content = fs.readFileSync(fileName, 'utf8');
@@ -14,21 +20,59 @@ function minifyJs(fileName) {
`[${fileName}]: ${content.length}kb -> ${minifiedContent.length}kb`
);
}
gulp.task('copyFiles', [], function() {
gulp.task('runWebpack', function() {
return child_process.execSync('yarn run build');
});
gulp.task('copyFiles', function() {
return merge(
gulp
.src('src/lib/codemirror/theme/*')
.pipe(gulp.dest('app/lib/codemirror/theme'));
.pipe(gulp.dest('app/lib/codemirror/theme')),
gulp
.src('src/lib/codemirror/mode/**/*')
.pipe(gulp.dest('app/lib/codemirror/mode'));
gulp.src('src/lib/transpilers/*').pipe(gulp.dest('app/lib/transpilers'));
gulp.src('src/partials/*').pipe(gulp.dest('app/partials'));
gulp.src('src/lib/screenlog.js').pipe(gulp.dest('app/lib'));
gulp.src('src/preview.html').pipe(gulp.dest('app'));
gulp.src('src/detached-window.js').pipe(gulp.dest('app'));
gulp.src('src/icon-48.png').pipe(gulp.dest('app'));
gulp.src('src/icon-128.png').pipe(gulp.dest('app'));
gulp.src('src/patreon.png').pipe(gulp.dest('app'));
.pipe(gulp.dest('app/lib/codemirror/mode')),
gulp.src('src/lib/transpilers/*').pipe(gulp.dest('app/lib/transpilers')),
gulp.src('src/lib/screenlog.js').pipe(gulp.dest('app/lib')),
gulp.src('icons/*').pipe(gulp.dest('app/icons')),
gulp
.src([
'src/preview.html',
'src/detached-window.js',
'src/icon-48.png',
'src/icon-128.png',
'src/patreon.png',
'manifest.json'
])
.pipe(gulp.dest('app')),
gulp
.src('build/bundle.*.js')
.pipe(rename('script.js'))
.pipe(gulp.dest('app')),
gulp
.src('build/vendor.*.js')
.pipe(rename('vendor.js'))
.pipe(gulp.dest('app')),
// Following CSS are copied to build/ folder where they'll be referenced by
// useRef plugin to concat into one.
gulp
.src('src/lib/codemirror/lib/codemirror.css')
.pipe(gulp.dest('build/lib/codemirror/lib')),
gulp
.src('src/lib/codemirror/addon/hint/show-hint.css')
.pipe(gulp.dest('build/lib/codemirror/addon/hint')),
gulp
.src('src/lib/codemirror/addon/fold/foldgutter.css')
.pipe(gulp.dest('build/lib/codemirror/addon/fold')),
gulp
.src('src/lib/codemirror/addon/dialog/dialog.css')
.pipe(gulp.dest('build/lib/codemirror/addon/dialog')),
gulp.src('src/lib/hint.min.css').pipe(gulp.dest('build/lib')),
gulp.src('src/lib/inlet.css').pipe(gulp.dest('build/lib')),
gulp.src('src/style.css').pipe(gulp.dest('build')),
gulp
.src([
'src/FiraCode.ttf',
@@ -36,17 +80,25 @@ gulp.task('copyFiles', [], function() {
'src/Inconsolata.ttf',
'src/Monoid.ttf'
])
.pipe(gulp.dest('app'));
.pipe(gulp.dest('app'))
);
});
gulp.task('useRef', ['copyFiles'], function() {
gulp.task('useRef', function() {
return gulp
.src('src/index.html')
.src('build/index.html')
.pipe(useref())
.pipe(gulp.dest('app'));
});
gulp.task('minify', ['useRef'], function() {
gulp.task('concatSwRegistration', function() {
gulp
.src(['src/service-worker-registration.js', 'app/script.js'])
.pipe(concat('script.js'))
.pipe(gulp.dest('app'));
});
gulp.task('minify', function() {
minifyJs('app/script.js');
minifyJs('app/vendor.js');
minifyJs('app/lib/screenlog.js');
@@ -54,15 +106,37 @@ gulp.task('minify', ['useRef'], function() {
gulp
.src('app/*.css')
.pipe(
cleanCSS({ debug: true }, details => {
cleanCSS(
{
debug: true
},
details => {
console.log(`${details.name}: ${details.stats.originalSize}`);
console.log(`${details.name}: ${details.stats.minifiedSize}`);
})
}
)
)
.pipe(gulp.dest('app'));
});
gulp.task('generate-service-worker', ['minify'], function(callback) {
gulp.task('fixIndex', function() {
var contents = fs.readFileSync('build/index.html', 'utf8');
// Replace hashed-filename script tags with unhashed ones
contents = contents.replace(
/\<\!\-\- SCRIPT-TAGS \-\-\>[\S\s]*?\<\!\-\- END-SCRIPT-TAGS \-\-\>/,
'<script defer src="vendor.js"></script><script defer src="script.js"></script>'
);
// vendor.hash.js gets created outside our markers, so remove it
contents = contents.replace(
/\<script src="\/vendor\.[\S\s]*?\<\/script\>/,
''
);
fs.writeFileSync('build/index.html', contents, 'utf8');
});
gulp.task('generate-service-worker', function(callback) {
var swPrecache = require('sw-precache');
var rootDir = 'app';
@@ -81,4 +155,57 @@ gulp.task('generate-service-worker', ['minify'], function(callback) {
);
});
gulp.task('default', ['generate-service-worker']);
gulp.task('packageExtension', function() {
child_process.execSync('cp -R app extension/');
child_process.execSync('cp src/manifest.json extension');
child_process.execSync('cp src/options.js extension');
child_process.execSync('cp src/options.html extension');
child_process.execSync('cp src/eventPage.js extension');
child_process.execSync('cp src/icon-16.png extension');
child_process.execSync(
'rm -rf extension/service-worker.js extension/partials'
);
return merge(
gulp
.src('build/bundle.*.js')
.pipe(rename('script.js'))
.pipe(gulp.dest('extension')),
gulp
.src('build/vendor.*.js')
.pipe(rename('vendor.js'))
.pipe(gulp.dest('extension')),
gulp
.src('extension/*')
.pipe(zip('extension.zip'))
.pipe(gulp.dest('./'))
);
});
gulp.task('cleanup', function() {
return child_process.execSync('rm -rf build');
});
gulp.task('release', function(callback) {
runSequence(
'runWebpack',
'copyFiles',
'fixIndex',
'useRef',
'concatSwRegistration',
'minify',
'generate-service-worker',
'packageExtension',
'cleanup',
function(error) {
if (error) {
console.log(error.message);
} else {
console.log('RELEASE FINISHED SUCCESSFULLY');
}
callback(error);
}
);
});
// gulp.task('default', ['generate-service-worker']);

BIN
icons/icon-128x128.png Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

BIN
icons/icon-144x144.png Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 KiB

BIN
icons/icon-152x152.png Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

BIN
icons/icon-192x192.png Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.0 KiB

BIN
icons/icon-384x384.png Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.3 KiB

BIN
icons/icon-512x512.png Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

BIN
icons/icon-72x72.png Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

BIN
icons/icon-96x96.png Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

52
manifest.json Executable file
View File

@@ -0,0 +1,52 @@
{
"name": "Web Maker",
"short_name": "web maker",
"theme_color": "#ff6200",
"background_color": "#b3b5b6",
"display": "standalone",
"Scope": "/",
"start_url": "/",
"icons": [
{
"src": "icons/icon-72x72.png",
"sizes": "72x72",
"type": "image/png"
},
{
"src": "icons/icon-96x96.png",
"sizes": "96x96",
"type": "image/png"
},
{
"src": "icons/icon-128x128.png",
"sizes": "128x128",
"type": "image/png"
},
{
"src": "icons/icon-144x144.png",
"sizes": "144x144",
"type": "image/png"
},
{
"src": "icons/icon-152x152.png",
"sizes": "152x152",
"type": "image/png"
},
{
"src": "icons/icon-192x192.png",
"sizes": "192x192",
"type": "image/png"
},
{
"src": "icons/icon-384x384.png",
"sizes": "384x384",
"type": "image/png"
},
{
"src": "icons/icon-512x512.png",
"sizes": "512x512",
"type": "image/png"
}
],
"splash_pages": null
}

View File

@@ -2,37 +2,110 @@
"name": "web-maker",
"version": "3.2.0",
"description": "A blazing fast & offline web playground",
"main": "index.html",
"scripts": {
"start": "if-env NODE_ENV=production && npm run -s serve || npm run -s dev",
"build": "preact build --template src/index.html --no-prerender",
"serve": "preact build && preact serve",
"dev": "preact watch --template src/index.html --https --no-prerender",
"lint": "eslint src",
"test": "jest ./tests",
"precommit": "lint-staged"
},
"eslintConfig": {
"extends": "eslint-config-synacor"
},
"eslintIgnore": [
"build/*",
"src/lib/",
"src/tests/",
"src/CodeMirror.js"
],
"lint-staged": {
"*.{js,jsx,json,css,md}": [
"prettier --write",
"git add"
]
},
"devDependencies": {
"babel-eslint": "^7.2.3",
"babel-minify": "^0.2.0",
"eslint": "^4.1.1",
"eslint": "^4.9.0",
"eslint-config-prettier": "^2.3.0",
"eslint-config-synacor": "^2.0.2",
"gulp": "^3.9.1",
"gulp-clean-css": "^3.9.2",
"gulp-concat": "^2.6.1",
"gulp-rename": "^1.3.0",
"gulp-uglify": "^3.0.0",
"gulp-useref": "^3.1.3",
"gulp-zip": "^4.1.0",
"husky": "^0.14.3",
"identity-obj-proxy": "^3.0.0",
"if-env": "^1.0.0",
"jest": "^21.2.1",
"lint-staged": "^7.2.0",
"merge-stream": "^1.0.1",
"preact-cli": "^2.1.0",
"preact-render-spy": "^1.2.1",
"prettier": "^1.10.2",
"run-sequence": "^2.2.1",
"sw-precache": "^5.2.0"
},
"scripts": {
"install": "ln -sf ../../git-hooks/pre-commit .git/hooks/pre-commit",
"lint": "eslint src/*.js"
"dependencies": {
"@emmetio/codemirror-plugin": "^0.5.4",
"code-blast-codemirror": "chinchang/code-blast-codemirror#web-maker",
"codemirror": "^5.37.0",
"copy-webpack-plugin": "^4.5.1",
"esprima": "^4.0.0",
"firebase": "^5.0.4",
"preact": "^8.2.6",
"preact-compat": "^3.17.0",
"preact-portal": "^1.1.3",
"preact-router": "^2.5.7",
"split.js": "1.3.4"
},
"jest": {
"verbose": true,
"setupFiles": [
"<rootDir>/src/tests/__mocks__/browserMocks.js"
],
"testURL": "http://localhost:8080",
"moduleFileExtensions": [
"js",
"jsx"
],
"moduleDirectories": [
"node_modules"
],
"moduleNameMapper": {
"\\.(jpg|jpeg|png|gif|eot|otf|webp|svg|ttf|woff|woff2|mp4|webm|wav|mp3|m4a|aac|oga)$": "<rootDir>/src/tests/__mocks__/fileMock.js",
"\\.(css|less|scss)$": "identity-obj-proxy",
"^./style$": "identity-obj-proxy",
"^preact$": "<rootDir>/node_modules/preact/dist/preact.min.js",
"^react$": "preact-compat",
"^react-dom$": "preact-compat",
"^create-react-class$": "preact-compat/lib/create-react-class",
"^react-addons-css-transition-group$": "preact-css-transition-group"
}
},
"browserslist": [
"last 3 Chrome versions",
"last 3 Firefox versions"
],
"repository": {
"type": "git",
"url": "git+https://github.com/chinchang/web-maker.git"
},
"keywords": ["frontend", "playground", "web", "editor"],
"keywords": [
"frontend",
"playground",
"web",
"editor"
],
"author": "Kushagra Gour",
"license": "MIT",
"bugs": {
"url": "https://github.com/chinchang/web-maker/issues"
},
"homepage": "https://webmakerapp.com",
"dependencies": {
"@emmetio/codemirror-plugin": "^0.3.5",
"babel-polyfill": "^6.23.0",
"babel-standalone": "^6.25.0"
}
"homepage": "https://webmakerapp.com"
}

43
preact.config.js Normal file
View File

@@ -0,0 +1,43 @@
import CopyWebpackPlugin from 'copy-webpack-plugin';
var CommonsChunkPlugin = require('webpack/lib/optimize/CommonsChunkPlugin');
/**
* Function that mutates original webpack config.
* Supports asynchronous changes when promise is returned.
*
* @param {object} config - original webpack config.
* @param {object} env - options passed to CLI.
* @param {WebpackConfigHelpers} helpers - object with useful helpers when working with config.
**/
export default function(config, env, helpers) {
const htmlWebpackPlugin = helpers.getPluginsByName(
config,
'HtmlWebpackPlugin'
)[0];
Object.assign(htmlWebpackPlugin.plugin.options.minify, {
removeComments: false,
collapseWhitespace: false
});
htmlWebpackPlugin.plugin.options.preload = false;
htmlWebpackPlugin.plugin.options.favicon = false;
if (env.isProd) {
config.devtool = false; // disable sourcemaps
config.plugins.push(
new CommonsChunkPlugin({
name: 'vendor',
minChunks: ({ resource }) => /node_modules/.test(resource)
})
);
const swPlugin = helpers.getPluginsByName(
config,
'SWPrecacheWebpackPlugin'
)[0];
config.plugins.splice(swPlugin.index, 1);
const uglifyPlugin = helpers.getPluginsByName(config, 'UglifyJsPlugin')[0];
config.plugins.splice(uglifyPlugin.index, 1);
}
}

60
src/CodeMirror.js Normal file
View File

@@ -0,0 +1,60 @@
// Most of the code from this file comes from:
// https://github.com/codemirror/CodeMirror/blob/master/addon/mode/loadmode.js
import CodeMirror from 'codemirror';
// Make CodeMirror available globally so the modes' can register themselves.
window.CodeMirror = CodeMirror
if (!CodeMirror.modeURL) CodeMirror.modeURL = 'lib/codemirror/mode/%N/%N.js';
var loading = {}
function splitCallback(cont, n) {
var countDown = n
return function () {
if (--countDown === 0) cont()
}
}
function ensureDeps(mode, cont) {
var deps = CodeMirror.modes[mode].dependencies
if (!deps) return cont()
var missing = []
for (var i = 0; i < deps.length; ++i) {
if (!CodeMirror.modes.hasOwnProperty(deps[i])) missing.push(deps[i])
}
if (!missing.length) return cont()
var split = splitCallback(cont, missing.length)
for (i = 0; i < missing.length; ++i) CodeMirror.requireMode(missing[i], split)
}
CodeMirror.requireMode = function (mode, cont) {
if (typeof mode !== 'string') mode = mode.name
if (CodeMirror.modes.hasOwnProperty(mode)) return ensureDeps(mode, cont)
if (loading.hasOwnProperty(mode)) return loading[mode].push(cont)
var file = CodeMirror.modeURL.replace(/%N/g, mode)
var script = document.createElement('script')
script.src = file
var others = document.getElementsByTagName('script')[0]
var list = loading[mode] = [cont]
CodeMirror.on(script, 'load', function () {
ensureDeps(mode, function () {
for (var i = 0; i < list.length; ++i) list[i]()
})
})
others.parentNode.insertBefore(script, others)
}
CodeMirror.autoLoadMode = function (instance, mode) {
if (CodeMirror.modes.hasOwnProperty(mode)) return
CodeMirror.requireMode(mode, function () {
instance.setOption('mode', instance.getOption('mode'))
})
}
export default CodeMirror

0
src/Inconsolata.ttf Executable file → Normal file
View File

View File

@@ -1,14 +1,17 @@
import { log } from './utils';
/* global ga */
// eslint-disable-next-line max-params
window.trackEvent = function(category, action, label, value) {
export function trackEvent(category, action, label, value) {
if (window.DEBUG) {
utils.log('trackevent', category, action, label, value);
log('trackevent', category, action, label, value);
return;
}
if (window.ga) {
ga('send', 'event', category, action, label, value);
}
};
}
// if online, load after sometime
if (navigator.onLine && !window.DEBUG) {
@@ -19,14 +22,21 @@ if (navigator.onLine && !window.DEBUG) {
(function (i, s, o, g, r, a, m) {
i['GoogleAnalyticsObject'] = r;
i[r] = i[r] || function () {
(i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),
m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)
(i[r].q = i[r].q || []).push(arguments)
}, i[r].l = 1 * new Date();
a = s.createElement(o),
m = s.getElementsByTagName(o)[0];
a.async = 1;
a.src = g;
m.parentNode.insertBefore(a, m)
})(window, document, 'script', 'https://www.google-analytics.com/analytics.js', 'ga');
if (location.href.indexOf('chrome-extension://') === -1) {
ga('create', 'UA-87786708-1');
} else {
ga('create', 'UA-87786708-1', {'cookieDomain': 'none'});
ga('create', 'UA-87786708-1', {
'cookieDomain': 'none'
});
// required for chrome extension protocol
ga('set', 'checkProtocolTask', function () { /* nothing */ });
}

View File

@@ -1,7 +1,12 @@
window.logout = function logout() {
import { trackEvent } from './analytics';
import firebase from 'firebase/app';
import { log } from './utils';
export const auth = {
logout() {
firebase.auth().signOut();
};
function login(providerName) {
},
login(providerName) {
var provider;
if (providerName === 'facebook') {
provider = new firebase.auth.FacebookAuthProvider();
@@ -18,12 +23,14 @@ function login(providerName) {
.auth()
.signInWithPopup(provider)
.then(function() {
window.trackEvent('fn', 'loggedIn', providerName);
trackEvent('fn', 'loggedIn', providerName);
// Save to recommend next time
window.db.local.set({ lastAuthProvider: providerName });
window.db.local.set({
lastAuthProvider: providerName
});
})
.catch(function(error) {
utils.log(error);
log(error);
if (error.code === 'auth/account-exists-with-different-credential') {
alert(
'You have already signed up with the same email using different social login'
@@ -31,4 +38,4 @@ function login(providerName) {
}
});
}
window.login = login;
};

92
src/codeModes.js Normal file
View File

@@ -0,0 +1,92 @@
export const HtmlModes = {
HTML: 'html',
MARKDOWN: 'markdown',
JADE: 'jade' // unsafe eval is put in manifest for this file
};
export const CssModes = {
CSS: 'css',
SCSS: 'scss',
SASS: 'sass',
LESS: 'less',
STYLUS: 'stylus',
ACSS: 'acss'
};
export const JsModes = {
JS: 'js',
ES6: 'es6',
COFFEESCRIPT: 'coffee',
TS: 'typescript'
};
export const modes = {};
modes[HtmlModes.HTML] = {
label: 'HTML',
cmMode: 'htmlmixed',
codepenVal: 'none'
};
modes[HtmlModes.MARKDOWN] = {
label: 'Markdown',
cmMode: 'markdown',
codepenVal: 'markdown'
};
modes[HtmlModes.JADE] = {
label: 'Pug',
cmMode: 'pug',
codepenVal: 'pug'
};
modes[JsModes.JS] = {
label: 'JS',
cmMode: 'javascript',
codepenVal: 'none'
};
modes[JsModes.COFFEESCRIPT] = {
label: 'CoffeeScript',
cmMode: 'coffeescript',
codepenVal: 'coffeescript'
};
modes[JsModes.ES6] = {
label: 'ES6 (Babel)',
cmMode: 'jsx',
codepenVal: 'babel'
};
modes[JsModes.TS] = {
label: 'TypeScript',
cmPath: 'jsx',
cmMode: 'text/typescript-jsx',
codepenVal: 'typescript'
};
modes[CssModes.CSS] = {
label: 'CSS',
cmPath: 'css',
cmMode: 'css',
codepenVal: 'none'
};
modes[CssModes.SCSS] = {
label: 'SCSS',
cmPath: 'css',
cmMode: 'text/x-scss',
codepenVal: 'scss'
};
modes[CssModes.SASS] = {
label: 'SASS',
cmMode: 'sass',
codepenVal: 'sass'
};
modes[CssModes.LESS] = {
label: 'LESS',
cmPath: 'css',
cmMode: 'text/x-less',
codepenVal: 'less'
};
modes[CssModes.STYLUS] = {
label: 'Stylus',
cmMode: 'stylus',
codepenVal: 'stylus'
};
modes[CssModes.ACSS] = {
label: 'Atomic CSS',
cmPath: 'css',
cmMode: 'css',
codepenVal: 'notsupported',
cmDisable: true,
hasSettings: true
};

View File

@@ -0,0 +1,145 @@
import { h, Component } from 'preact';
import { jsLibs, cssLibs } from '../libraryList';
import { trackEvent } from '../analytics';
import { LibraryAutoSuggest } from './LibraryAutoSuggest';
export default class AddLibrary extends Component {
constructor(props) {
super(props);
this.state = {
css: props.css || '',
js: props.js || ''
};
}
onSelectChange(e) {
const target = e.target;
if (!target.value) {
return;
}
const type = target.selectedOptions[0].dataset.type;
if (type === 'js') {
this.setState({
js: `${this.state.js}\n${target.value}`
});
} else {
this.setState({
css: `${this.state.css}\n${target.value}`
});
}
trackEvent('ui', 'addLibrarySelect', target.selectedOptions[0].label);
this.props.onChange({ js: this.state.js, css: this.state.css });
// Reset the select to the default value
target.value = '';
}
textareaBlurHandler(e, textarea) {
const target = e ? e.target : textarea;
const type = target.dataset.lang;
if (type === 'js') {
this.setState({
js: target.value || ''
});
} else {
this.setState({
css: target.value || ''
});
}
// trackEvent('ui', 'addLibrarySelect', target.selectedOptions[0].label);
this.props.onChange({ js: this.state.js, css: this.state.css });
}
suggestionSelectHandler(value) {
const textarea = value.match(/\.js$/)
? window.externalJsTextarea
: window.externalCssTextarea;
textarea.value = `${textarea.value}\n${value}`;
window.externalLibrarySearchInput.value = '';
this.textareaBlurHandler(null, textarea);
}
render() {
return (
<div>
<h1>Add Library</h1>
<div class="flex">
<svg style="width: 30px; height: 30px;fill:#999">
<use xlinkHref="#search" />
</svg>
<LibraryAutoSuggest
fullWidth
onSelect={this.suggestionSelectHandler.bind(this)}
>
<input
type="text"
id="externalLibrarySearchInput"
class="full-width"
placeholder="Type here to search libraries"
/>
</LibraryAutoSuggest>
</div>
<div class="tar opacity--70">
<small>Powered by cdnjs</small>
</div>
<div style="margin:20px 0;">
Choose from popular libraries:{' '}
<select
name=""
id="js-add-library-select"
onChange={this.onSelectChange.bind(this)}
>
<option value="">-------</option>
<optgroup label="JavaScript Libraries">
{jsLibs.map(lib => (
<option data-type={lib.type} value={lib.url}>
{lib.label}
</option>
))}
</optgroup>
<optgroup label="CSS Libraries">
{cssLibs.map(lib => (
<option data-type={lib.type} value={lib.url}>
{lib.label}
</option>
))}
</optgroup>
</select>
</div>
<h3 class="mb-0">JS</h3>
<p class="mt-0 help-text">Put each library in new line</p>
<p style="font-size: 0.8em;" class="show-when-extension opacity--70">
Note: You can load external scripts from following domains: localhost,
https://ajax.googleapis.com, https://code.jquery.com,
https://cdnjs.cloudflare.com, https://unpkg.com, https://maxcdn.com,
https://cdn77.com, https://maxcdn.bootstrapcdn.com,
https://cdn.jsdelivr.net/, https://rawgit.com, https://wzrd.in
</p>
<textarea
onBlur={this.textareaBlurHandler.bind(this)}
data-lang="js"
class="full-width"
id="externalJsTextarea"
cols="30"
rows="5"
placeholder="Put each library in new line"
value={this.state.js}
/>
<h3 class="mb-0">CSS</h3>
<p class="mt-0 help-text">Put each library in new line</p>
<textarea
onBlur={this.textareaBlurHandler.bind(this)}
data-lang="css"
class="full-width"
id="externalCssTextarea"
cols="30"
rows="5"
placeholder="Put each library in new line"
value={this.state.css}
/>
</div>
);
}
}

11
src/components/Alerts.jsx Normal file
View File

@@ -0,0 +1,11 @@
import { h, Component } from 'preact';
export class Alerts extends Component {
shouldComponentUpdate(nextProps, nextState) {
return false;
}
render() {
return <div class="alerts-container" id="js-alerts-container" />;
}
}

View File

@@ -0,0 +1,40 @@
import { h } from 'preact';
import Modal from './Modal';
export function AskToImportModal({
show,
closeHandler,
oldSavedCreationsCount,
dontAskBtnClickHandler,
importBtnClickHandler
}) {
return (
<Modal
extraClasses="ask-to-import-modal"
show={show}
closeHandler={closeHandler}
>
<h2>Import your creations in your account</h2>
<div>
<p>
You have <span>{oldSavedCreationsCount}</span> creations saved in your
local machine. Do you want to import those creations in your account
so they are more secure and accessible anywhere?
</p>
<p>
It's okay if you don't want to. You can simply logout and access them
anytime on this browser.
</p>
<div class="flex flex-h-end">
<button onClick={dontAskBtnClickHandler} class="btn">
Don't ask me again
</button>
<button onClick={importBtnClickHandler} class="btn btn--primary ml-1">
Yes, please import
</button>
</div>
</div>
</Modal>
);
}

View File

@@ -0,0 +1,36 @@
import { h, Component } from 'preact';
import CodeMirror from '../CodeMirror';
import 'codemirror/mode/javascript/javascript.js';
export default class CodeMirrorBox extends Component {
componentDidMount() {
this.initEditor();
}
shouldComponentUpdate() {
return false;
}
initEditor() {
this.cm = CodeMirror.fromTextArea(this.textarea, this.props.options);
if (this.props.onChange) {
this.cm.on('change', this.props.onChange);
}
if (this.props.onBlur) {
this.cm.on('blur', this.props.onBlur);
}
this.props.onCreation(this.cm);
}
render() {
return (
<textarea
ref={el => (this.textarea = el)}
name=""
id=""
cols="30"
rows="10"
/>
);
}
}

View File

@@ -0,0 +1,942 @@
import { h, Component } from 'preact';
import UserCodeMirror from './UserCodeMirror.jsx';
import { computeHtml, computeCss, computeJs } from '../computes';
import { modes, HtmlModes, CssModes, JsModes } from '../codeModes';
import { log, writeFile, loadJS, getCompleteHtml } from '../utils';
import { SplitPane } from './SplitPane.jsx';
import { trackEvent } from '../analytics';
import CodeMirror from '../CodeMirror';
import CodeMirrorBox from './CodeMirrorBox';
import { deferred } from '../deferred';
import CssSettingsModal from './CssSettingsModal';
const minCodeWrapSize = 33;
/* global htmlCodeEl, jsCodeEl, cssCodeEl, logCountEl
*/
export default class ContentWrap extends Component {
constructor(props) {
super(props);
this.state = {
isConsoleOpen: false,
isCssSettingsModalOpen: false
};
this.updateTimer = null;
this.updateDelay = 500;
this.htmlMode = HtmlModes.HTML;
this.jsMode = HtmlModes.HTML;
this.cssMode = CssModes.CSS;
this.jsMode = JsModes.JS;
this.prefs = {};
this.codeInPreview = { html: null, css: null, js: null };
this.cmCodes = { html: props.currentItem.html, css: '', js: '' };
this.cm = {};
this.logCount = 0;
window.onMessageFromConsole = this.onMessageFromConsole.bind(this);
window.previewException = this.previewException.bind(this);
// `clearConsole` is on window because it gets called from inside iframe also.
window.clearConsole = this.clearConsole.bind(this);
}
shouldComponentUpdate(nextProps, nextState) {
return (
this.state.isConsoleOpen !== nextState.isConsoleOpen ||
this.state.isCssSettingsModalOpen !== nextState.isCssSettingsModalOpen ||
this.state.codeSplitSizes !== nextState.codeSplitSizes ||
this.state.mainSplitSizes !== nextState.mainSplitSizes ||
this.props.currentLayoutMode !== nextProps.currentLayoutMode ||
this.props.currentItem !== nextProps.currentItem
);
}
componentDidUpdate() {
// HACK: becuase its a DOM manipulation
window.logCountEl.textContent = this.logCount;
// log('🚀', 'didupdate', this.props.currentItem);
// if (this.isValidItem(this.props.currentItem)) {
// this.refreshEditor();
// }
}
componentDidMount() {
this.props.onRef(this);
}
onHtmlCodeChange(editor, change) {
this.cmCodes.html = editor.getValue();
this.props.onCodeChange(
'html',
this.cmCodes.html,
change.origin !== 'setValue'
);
this.onCodeChange(editor, change);
}
onCssCodeChange(editor, change) {
this.cmCodes.css = editor.getValue();
this.props.onCodeChange(
'css',
this.cmCodes.css,
change.origin !== 'setValue'
);
this.onCodeChange(editor, change);
}
onJsCodeChange(editor, change) {
this.cmCodes.js = editor.getValue();
this.props.onCodeChange(
'js',
this.cmCodes.js,
change.origin !== 'setValue'
);
this.onCodeChange(editor, change);
}
onCodeChange(editor, change) {
clearTimeout(this.updateTimer);
this.updateTimer = setTimeout(() => {
// This is done so that multiple simultaneous setValue don't trigger too many preview refreshes
// and in turn too many file writes on a single file (eg. preview.html).
if (change.origin !== 'setValue') {
// Specifically checking for false so that the condition doesn't get true even
// on absent key - possible when the setting key hasn't been fetched yet.
if (this.prefs.autoPreview !== false) {
this.setPreviewContent();
}
// Track when people actually are working.
trackEvent.previewCount = (trackEvent.previewCount || 0) + 1;
if (trackEvent.previewCount === 4) {
trackEvent('fn', 'usingPreview');
}
}
}, this.updateDelay);
}
createPreviewFile(html, css, js) {
const shouldInlineJs =
!window.webkitRequestFileSystem || !window.IS_EXTENSION;
var contents = getCompleteHtml(
html,
css,
shouldInlineJs ? js : null,
this.props.currentItem
);
var blob = new Blob([contents], { type: 'text/plain;charset=UTF-8' });
var blobjs = new Blob([js], { type: 'text/plain;charset=UTF-8' });
// Track if people have written code.
if (!trackEvent.hasTrackedCode && (html || css || js)) {
trackEvent('fn', 'hasCode');
trackEvent.hasTrackedCode = true;
}
if (shouldInlineJs) {
if (this.detachedWindow) {
log('✉️ Sending message to detached window');
this.detachedWindow.postMessage({ contents }, '*');
} else {
this.frame.src = this.frame.src;
setTimeout(() => {
this.frame.contentDocument.open();
this.frame.contentDocument.write(contents);
this.frame.contentDocument.close();
}, 10);
}
} else {
// we need to store user script in external JS file to prevent inline-script
// CSP from affecting it.
writeFile('script.js', blobjs, () => {
writeFile('preview.html', blob, () => {
var origin = chrome.i18n.getMessage()
? `chrome-extension://${chrome.i18n.getMessage('@@extension_id')}`
: `${location.origin}`;
var src = `filesystem:${origin}/temporary/preview.html`;
if (this.detachedWindow) {
this.detachedWindow.postMessage(src, '*');
} else {
this.frame.src = src;
}
});
});
}
}
cleanupErrors(lang) {
this.cm[lang].clearGutter('error-gutter');
}
showErrors(lang, errors) {
var editor = this.cm[lang];
errors.forEach(function(e) {
editor.operation(function() {
var n = document.createElement('div');
n.setAttribute('data-title', e.message);
n.classList.add('gutter-error-marker');
editor.setGutterMarker(e.lineNumber, 'error-gutter', n);
});
});
}
/**
* Generates the preview from the current code.
* @param {boolean} isForced Should refresh everything without any check or not
* @param {boolean} isManual Is this a manual preview request from user?
*/
setPreviewContent(isForced, isManual) {
if (!this.props.prefs.autoPreview && !isManual) {
return;
}
if (!this.props.prefs.preserveConsoleLogs) {
this.clearConsole();
}
this.cleanupErrors('html');
this.cleanupErrors('css');
this.cleanupErrors('js');
var currentCode = {
html: this.cmCodes.html,
css: this.cmCodes.css,
js: this.cmCodes.js
};
log('🔎 setPreviewContent', isForced);
const targetFrame = this.detachedWindow
? this.detachedWindow.document.querySelector('iframe')
: this.frame;
const cssMode = this.props.currentItem.cssMode;
// If just CSS was changed (and everything shudn't be empty),
// change the styles inside the iframe.
if (
!isForced &&
currentCode.html === this.codeInPreview.html &&
currentCode.js === this.codeInPreview.js
) {
computeCss(
cssMode === CssModes.ACSS ? currentCode.html : currentCode.css,
cssMode,
this.props.currentItem.cssSettings
).then(result => {
if (cssMode === CssModes.ACSS) {
this.cm.css.setValue(result.code || '');
}
if (targetFrame.contentDocument.querySelector('#webmakerstyle')) {
targetFrame.contentDocument.querySelector(
'#webmakerstyle'
).textContent =
result.code || '';
}
});
} else {
var htmlPromise = computeHtml(
currentCode.html,
this.props.currentItem.htmlMode
);
var cssPromise = computeCss(
cssMode === CssModes.ACSS ? currentCode.html : currentCode.css,
cssMode,
this.props.currentItem.cssSettings
);
var jsPromise = computeJs(
currentCode.js,
this.props.currentItem.jsMode,
true,
this.props.prefs.infiniteLoopTimeout
);
Promise.all([htmlPromise, cssPromise, jsPromise]).then(result => {
if (cssMode === CssModes.ACSS) {
this.cm.css.setValue(result[1].code || '');
}
this.createPreviewFile(
result[0].code || '',
result[1].code || '',
result[2].code || ''
);
result.forEach(resultItem => {
if (resultItem.errors) {
this.showErrors(resultItem.errors.lang, resultItem.errors.data);
}
});
});
}
this.codeInPreview.html = currentCode.html;
this.codeInPreview.css = currentCode.css;
this.codeInPreview.js = currentCode.js;
}
isValidItem(item) {
return !!item.title;
}
refreshEditor() {
this.cmCodes.html = this.props.currentItem.html;
this.cmCodes.css = this.props.currentItem.css;
this.cmCodes.js = this.props.currentItem.js;
this.cm.html.setValue(this.cmCodes.html || '');
this.cm.css.setValue(this.cmCodes.css || '');
this.cm.js.setValue(this.cmCodes.js || '');
this.cm.html.refresh();
this.cm.css.refresh();
this.cm.js.refresh();
this.clearConsole();
// Set preview only when all modes are updated so that preview doesn't generate on partially
// correct modes and also doesn't happen 3 times.
Promise.all([
this.updateHtmlMode(this.props.currentItem.htmlMode),
this.updateCssMode(this.props.currentItem.cssMode),
this.updateJsMode(this.props.currentItem.jsMode)
]).then(() => this.setPreviewContent(true));
}
applyCodemirrorSettings(prefs) {
if (!this.cm) {
return;
}
htmlCodeEl.querySelector(
'.CodeMirror'
).style.fontSize = cssCodeEl.querySelector(
'.CodeMirror'
).style.fontSize = jsCodeEl.querySelector(
'.CodeMirror'
).style.fontSize = `${parseInt(prefs.fontSize, 10)}px`;
window.consoleEl.querySelector('.CodeMirror').style.fontSize = `${parseInt(
prefs.fontSize,
10
)}px`;
// Replace correct css file in LINK tags's href
window.editorThemeLinkTag.href = `lib/codemirror/theme/${
prefs.editorTheme
}.css`;
window.fontStyleTag.textContent = window.fontStyleTemplate.textContent.replace(
/fontname/g,
(prefs.editorFont === 'other'
? prefs.editorCustomFont
: prefs.editorFont) || 'FiraCode'
);
// window.customEditorFontInput.classList[
// prefs.editorFont === 'other' ? 'remove' : 'add'
// ]('hide');
this.consoleCm.setOption('theme', prefs.editorTheme);
['html', 'js', 'css'].forEach(type => {
this.cm[type].setOption('indentWithTabs', prefs.indentWith !== 'spaces');
this.cm[type].setOption(
'blastCode',
prefs.isCodeBlastOn ? { effect: 2, shake: false } : false
);
this.cm[type].setOption('indentUnit', +prefs.indentSize);
this.cm[type].setOption('tabSize', +prefs.indentSize);
this.cm[type].setOption('theme', prefs.editorTheme);
this.cm[type].setOption('keyMap', prefs.keymap);
this.cm[type].setOption('lineWrapping', prefs.lineWrap);
this.cm[type].refresh();
});
}
// Check all the code wrap if they are minimized or maximized
updateCodeWrapCollapseStates() {
// This is debounced!
clearTimeout(this.updateCodeWrapCollapseStates.timeout);
this.updateCodeWrapCollapseStates.timeout = setTimeout(() => {
const { currentLayoutMode } = this.props;
const prop =
currentLayoutMode === 2 || currentLayoutMode === 5 ? 'width' : 'height';
[htmlCodeEl, cssCodeEl, jsCodeEl].forEach(function(el) {
const bounds = el.getBoundingClientRect();
const size = bounds[prop];
if (size < 100) {
el.classList.add('is-minimized');
} else {
el.classList.remove('is-minimized');
}
if (el.style[prop].indexOf(`100% - ${minCodeWrapSize * 2}px`) !== -1) {
el.classList.add('is-maximized');
} else {
el.classList.remove('is-maximized');
}
});
}, 50);
}
toggleCodeWrapCollapse(codeWrapEl) {
if (
codeWrapEl.classList.contains('is-minimized') ||
codeWrapEl.classList.contains('is-maximized')
) {
codeWrapEl.classList.remove('is-minimized');
codeWrapEl.classList.remove('is-maximized');
this.codeSplitInstance.setSizes([33.3, 33.3, 33.3]);
} else {
const id = parseInt(codeWrapEl.dataset.codeWrapId, 10);
var arr = [
`${minCodeWrapSize}px`,
`${minCodeWrapSize}px`,
`${minCodeWrapSize}px`
];
arr[id] = `calc(100% - ${minCodeWrapSize * 2}px)`;
this.codeSplitInstance.setSizes(arr);
codeWrapEl.classList.add('is-maximized');
}
}
collapseBtnHandler(e) {
var codeWrapParent =
e.currentTarget.parentElement.parentElement.parentElement;
this.toggleCodeWrapCollapse(codeWrapParent);
trackEvent('ui', 'paneCollapseBtnClick', codeWrapParent.dataset.type);
}
codeWrapHeaderDblClickHandler(e) {
if (!e.target.classList.contains('js-code-wrap__header')) {
return;
}
const codeWrapParent = e.target.parentElement;
this.toggleCodeWrapCollapse(codeWrapParent);
trackEvent('ui', 'paneHeaderDblClick', codeWrapParent.dataset.type);
}
resetSplitting() {
this.setState({
codeSplitSizes: this.getCodeSplitSizes(),
mainSplitSizes: this.getMainSplitSizesToApply()
});
}
// Returns the sizes of main code & preview panes.
getMainSplitSizesToApply() {
var mainSplitSizes;
const { currentItem, currentLayoutMode } = this.props;
if (currentItem && currentItem.mainSizes) {
// For layout mode 3, main panes are reversed using flex-direction.
// So we need to apply the saved sizes in reverse order.
mainSplitSizes =
currentLayoutMode === 3
? [currentItem.mainSizes[1], currentItem.mainSizes[0]]
: currentItem.mainSizes;
} else {
mainSplitSizes = currentLayoutMode === 5 ? [75, 25] : [50, 50];
}
return mainSplitSizes;
}
getCodeSplitSizes() {
if (this.props.currentItem && this.props.currentItem.sizes) {
return this.props.currentItem.sizes;
}
return [33.33, 33.33, 33.33];
}
mainSplitDragEndHandler() {
if (this.props.prefs.refreshOnResize) {
// Running preview updation in next call stack, so that error there
// doesn't affect this dragend listener.
setTimeout(() => {
this.setPreviewContent(true);
}, 1);
}
}
codeSplitDragStart() {
document.body.classList.add('is-dragging');
}
codeSplitDragEnd() {
this.updateCodeWrapCollapseStates();
document.body.classList.remove('is-dragging');
}
/**
* Loaded the code comiler based on the mode selected
*/
handleModeRequirements(mode) {
const baseTranspilerPath = 'lib/transpilers';
// Exit if already loaded
var d = deferred();
if (modes[mode].hasLoaded) {
d.resolve();
return d.promise;
}
function setLoadedFlag() {
modes[mode].hasLoaded = true;
d.resolve();
}
if (mode === HtmlModes.JADE) {
loadJS(`${baseTranspilerPath}/jade.js`).then(setLoadedFlag);
} else if (mode === HtmlModes.MARKDOWN) {
loadJS(`${baseTranspilerPath}/marked.js`).then(setLoadedFlag);
} else if (mode === CssModes.LESS) {
loadJS(`${baseTranspilerPath}/less.min.js`).then(setLoadedFlag);
} else if (mode === CssModes.SCSS || mode === CssModes.SASS) {
loadJS(`${baseTranspilerPath}/sass.js`).then(function() {
window.sass = new Sass(`${baseTranspilerPath}/sass.worker.js`);
setLoadedFlag();
});
} else if (mode === CssModes.STYLUS) {
loadJS(`${baseTranspilerPath}/stylus.min.js`).then(setLoadedFlag);
} else if (mode === CssModes.ACSS) {
loadJS(`${baseTranspilerPath}/atomizer.browser.js`).then(setLoadedFlag);
} else if (mode === JsModes.COFFEESCRIPT) {
loadJS(`${baseTranspilerPath}/coffee-script.js`).then(setLoadedFlag);
} else if (mode === JsModes.ES6) {
loadJS(`${baseTranspilerPath}/babel.min.js`).then(setLoadedFlag);
} else if (mode === JsModes.TS) {
loadJS(`${baseTranspilerPath}/typescript.js`).then(setLoadedFlag);
} else {
d.resolve();
}
return d.promise;
}
updateHtmlMode(value) {
this.props.onCodeModeChange('html', value);
this.props.currentItem.htmlMode = value;
this.cm.html.setOption('mode', modes[value].cmMode);
CodeMirror.autoLoadMode(
this.cm.html,
modes[value].cmPath || modes[value].cmMode
);
return this.handleModeRequirements(value);
}
updateCssMode(value) {
this.props.onCodeModeChange('css', value);
this.props.currentItem.cssMode = value;
this.cm.css.setOption('mode', modes[value].cmMode);
this.cm.css.setOption('readOnly', modes[value].cmDisable);
window.cssSettingsBtn.classList[
modes[value].hasSettings ? 'remove' : 'add'
]('hide');
CodeMirror.autoLoadMode(
this.cm.css,
modes[value].cmPath || modes[value].cmMode
);
return this.handleModeRequirements(value);
}
updateJsMode(value) {
this.props.onCodeModeChange('js', value);
this.props.currentItem.jsMode = value;
this.cm.js.setOption('mode', modes[value].cmMode);
CodeMirror.autoLoadMode(
this.cm.js,
modes[value].cmPath || modes[value].cmMode
);
return this.handleModeRequirements(value);
}
codeModeChangeHandler(e) {
var mode = e.target.value;
var type = e.target.dataset.type;
var currentMode = this.props.currentItem[
type === 'html' ? 'htmlMode' : type === 'css' ? 'cssMode' : 'jsMode'
];
if (currentMode !== mode) {
if (type === 'html') {
this.updateHtmlMode(mode).then(() => this.setPreviewContent(true));
} else if (type === 'js') {
this.updateJsMode(mode).then(() => this.setPreviewContent(true));
} else if (type === 'css') {
this.updateCssMode(mode).then(() => this.setPreviewContent(true));
}
trackEvent('ui', 'updateCodeMode', mode);
}
}
detachPreview() {
if (this.detachedWindow) {
this.detachedWindow.focus();
return;
}
const iframeBounds = this.frame.getBoundingClientRect();
const iframeWidth = iframeBounds.width;
const iframeHeight = iframeBounds.height;
document.body.classList.add('is-detached-mode');
window.globalConsoleContainerEl.insertBefore(window.consoleEl, null);
this.detachedWindow = window.open(
'./preview.html',
'Web Maker',
`width=${iframeWidth},height=${iframeHeight},resizable,scrollbars=yes,status=1`
);
// Trigger initial render in detached window
setTimeout(() => {
this.setPreviewContent(true);
}, 1500);
var intervalID = window.setInterval(checkWindow => {
if (this.detachedWindow && this.detachedWindow.closed) {
clearInterval(intervalID);
document.body.classList.remove('is-detached-mode');
$('#js-demo-side').insertBefore(window.consoleEl, null);
this.detachedWindow = null;
// Update main frame preview to get latest changes (which were not
// getting reflected while detached window was open)
this.setPreviewContent(true);
}
}, 500);
}
onMessageFromConsole() {
/* eslint-disable no-param-reassign */
[...arguments].forEach(arg => {
if (
arg &&
arg.indexOf &&
arg.indexOf('filesystem:chrome-extension') !== -1
) {
arg = arg.replace(
/filesystem:chrome-extension.*\.js:(\d+):*(\d*)/g,
'script $1:$2'
);
}
try {
this.consoleCm.replaceRange(
arg +
' ' +
((arg + '').match(/\[object \w+]/) ? JSON.stringify(arg) : '') +
'\n',
{
line: Infinity
}
);
} catch (e) {
this.consoleCm.replaceRange('🌀\n', {
line: Infinity
});
}
this.consoleCm.scrollTo(0, Infinity);
this.logCount++;
});
logCountEl.textContent = this.logCount;
/* eslint-enable no-param-reassign */
}
previewException(error) {
console.error('Possible infinite loop detected.', error.stack);
this.onMessageFromConsole('Possible infinite loop detected.', error.stack);
}
toggleConsole() {
this.setState({ isConsoleOpen: !this.state.isConsoleOpen });
trackEvent('ui', 'consoleToggle');
}
consoleHeaderDblClickHandler(e) {
if (!e.target.classList.contains('js-console__header')) {
return;
}
trackEvent('ui', 'consoleToggleDblClick');
this.toggleConsole();
}
clearConsole() {
this.consoleCm.setValue('');
this.logCount = 0;
window.logCountEl.textContent = this.logCount;
}
clearConsoleBtnClickHandler() {
this.clearConsole();
trackEvent('ui', 'consoleClearBtnClick');
}
evalConsoleExpr(e) {
// Clear console on CTRL + L
if ((e.which === 76 || e.which === 12) && e.ctrlKey) {
this.clearConsole();
trackEvent('ui', 'consoleClearKeyboardShortcut');
} else if (e.which === 13) {
this.onMessageFromConsole('> ' + e.target.value);
/* eslint-disable no-underscore-dangle */
this.frame.contentWindow._wmEvaluate(e.target.value);
/* eslint-enable no-underscore-dangle */
e.target.value = '';
trackEvent('fn', 'evalConsoleExpr');
}
}
cssSettingsBtnClickHandler() {
this.setState({ isCssSettingsModalOpen: true });
trackEvent('ui', 'cssSettingsBtnClick');
}
cssSettingsChangeHandler(settings) {
this.props.onCodeSettingsChange('css', settings);
this.setPreviewContent(true);
}
getDemoFrame(callback) {
callback(this.frame);
}
editorFocusHandler(editor) {
this.props.onEditorFocus(editor);
}
render() {
return (
<SplitPane
class="content-wrap flex flex-grow"
sizes={this.state.mainSplitSizes}
minSize={150}
style=""
direction={
this.props.currentLayoutMode === 2 ? 'vertical' : 'horizontal'
}
onDragEnd={this.mainSplitDragEndHandler.bind(this)}
>
<SplitPane
class="code-side"
id="js-code-side"
sizes={this.state.codeSplitSizes}
minSize={minCodeWrapSize}
direction={
this.props.currentLayoutMode === 2 ||
this.props.currentLayoutMode === 5
? 'horizontal'
: 'vertical'
}
onDragStart={this.codeSplitDragStart.bind(this)}
onDragEnd={this.codeSplitDragEnd.bind(this)}
onSplit={splitInstance => (this.codeSplitInstance = splitInstance)}
>
<div
data-code-wrap-id="0"
id="htmlCodeEl"
data-type="html"
class="code-wrap"
onTransitionEnd={this.updateCodeWrapCollapseStates.bind(this)}
>
<div
class="js-code-wrap__header code-wrap__header"
title="Double click to toggle code pane"
onDblClick={this.codeWrapHeaderDblClickHandler.bind(this)}
>
<label class="btn-group" dropdow title="Click to change">
<span class="code-wrap__header-label">
{modes[this.props.currentItem.htmlMode || 'html'].label}
</span>
<span class="caret" />
<select
data-type="html"
class="js-mode-select hidden-select"
onChange={this.codeModeChangeHandler.bind(this)}
value={this.props.currentItem.htmlMode}
>
<option value="html">HTML</option>
<option value="markdown">Markdown</option>
<option value="jade">Pug</option>
</select>
</label>
<div class="code-wrap__header-right-options">
<a
class="js-code-collapse-btn code-wrap__header-btn code-wrap__collapse-btn"
title="Toggle code pane"
onClick={this.collapseBtnHandler.bind(this)}
/>
</div>
</div>
<UserCodeMirror
options={{
mode: 'htmlmixed',
profile: 'xhtml',
gutters: ['CodeMirror-linenumbers', 'CodeMirror-foldgutter'],
noAutocomplete: true,
matchTags: { bothTags: true },
emmet: true
}}
onChange={this.onHtmlCodeChange.bind(this)}
onCreation={el => (this.cm.html = el)}
onFocus={this.editorFocusHandler.bind(this)}
/>
</div>
<div
data-code-wrap-id="1"
id="cssCodeEl"
data-type="css"
class="code-wrap"
onTransitionEnd={this.updateCodeWrapCollapseStates.bind(this)}
>
<div
class="js-code-wrap__header code-wrap__header"
title="Double click to toggle code pane"
onDblClick={this.codeWrapHeaderDblClickHandler.bind(this)}
>
<label class="btn-group" title="Click to change">
<span class="code-wrap__header-label">
{modes[this.props.currentItem.cssMode || 'css'].label}
</span>
<span class="caret" />
<select
data-type="css"
class="js-mode-select hidden-select"
onChange={this.codeModeChangeHandler.bind(this)}
value={this.props.currentItem.cssMode}
>
<option value="css">CSS</option>
<option value="scss">SCSS</option>
<option value="sass">SASS</option>
<option value="less">LESS</option>
<option value="stylus">Stylus</option>
<option value="acss">Atomic CSS</option>
</select>
</label>
<div class="code-wrap__header-right-options">
<a
href="#"
id="cssSettingsBtn"
title="Atomic CSS configuration"
onClick={this.cssSettingsBtnClickHandler.bind(this)}
class="code-wrap__header-btn hide"
>
<svg>
<use xlinkHref="#settings-icon" />
</svg>
</a>
<a
class="js-code-collapse-btn code-wrap__header-btn code-wrap__collapse-btn"
title="Toggle code pane"
onClick={this.collapseBtnHandler.bind(this)}
/>
</div>
</div>
<UserCodeMirror
options={{
mode: 'css',
gutters: [
'error-gutter',
'CodeMirror-linenumbers',
'CodeMirror-foldgutter'
],
emmet: true
}}
onChange={this.onCssCodeChange.bind(this)}
onCreation={el => (this.cm.css = el)}
onFocus={this.editorFocusHandler.bind(this)}
/>
</div>
<div
data-code-wrap-id="2"
id="jsCodeEl"
data-type="js"
class="code-wrap"
onTransitionEnd={this.updateCodeWrapCollapseStates.bind(this)}
>
<div
class="js-code-wrap__header code-wrap__header"
title="Double click to toggle code pane"
onDblClick={this.codeWrapHeaderDblClickHandler.bind(this)}
>
<label class="btn-group" title="Click to change">
<span class="code-wrap__header-label">
{modes[this.props.currentItem.jsMode || 'js'].label}
</span>
<span class="caret" />
<select
data-type="js"
class="js-mode-select hidden-select"
onChange={this.codeModeChangeHandler.bind(this)}
value={this.props.currentItem.jsMode}
>
<option value="js">JS</option>
<option value="coffee">CoffeeScript</option>
<option value="es6">ES6 (Babel)</option>
<option value="typescript">TypeScript</option>
</select>
</label>
<div class="code-wrap__header-right-options">
<a
class="js-code-collapse-btn code-wrap__header-btn code-wrap__collapse-btn"
title="Toggle code pane"
onClick={this.collapseBtnHandler.bind(this)}
/>
</div>
</div>
<UserCodeMirror
options={{
mode: 'javascript',
gutters: [
'error-gutter',
'CodeMirror-linenumbers',
'CodeMirror-foldgutter'
]
}}
autoComplete={this.props.prefs.autoComplete}
onChange={this.onJsCodeChange.bind(this)}
onCreation={el => (this.cm.js = el)}
onFocus={this.editorFocusHandler.bind(this)}
/>
{/* Inlet(scope.cm.js); */}
</div>
</SplitPane>
<div class="demo-side" id="js-demo-side" style="">
<iframe
ref={el => (this.frame = el)}
src="about://blank"
frameborder="0"
id="demo-frame"
allowfullscreen
/>
<div
id="consoleEl"
class={`console ${this.state.isConsoleOpen ? '' : 'is-minimized'}`}
>
<div id="consoleLogEl" class="console__log">
<div
class="js-console__header code-wrap__header"
title="Double click to toggle console"
onDblClick={this.toggleConsole.bind(this)}
>
<span class="code-wrap__header-label">
Console (<span id="logCountEl">0</span>)
</span>
<div class="code-wrap__header-right-options">
<a
class="code-wrap__header-btn"
title="Clear console (CTRL + L)"
onClick={this.clearConsoleBtnClickHandler.bind(this)}
>
<svg>
<use xlinkHref="#cancel-icon" />
</svg>
</a>
<a
class="code-wrap__header-btn code-wrap__collapse-btn"
title="Toggle console"
onClick={this.toggleConsole.bind(this)}
/>
</div>
</div>
<CodeMirrorBox
options={{
mode: 'javascript',
lineWrapping: true,
theme: 'monokai',
foldGutter: true,
readOnly: true,
gutters: ['CodeMirror-foldgutter']
}}
onCreation={el => (this.consoleCm = el)}
/>
</div>
<div
id="consolePromptEl"
class="console__prompt flex flex-v-center"
>
<svg width="18" height="18" fill="#346fd2">
<use xlinkHref="#chevron-icon" />
</svg>
<input
onKeyUp={this.evalConsoleExpr.bind(this)}
class="console-exec-input"
/>
</div>
</div>
<CssSettingsModal
show={this.state.isCssSettingsModalOpen}
closeHandler={() =>
this.setState({ isCssSettingsModalOpen: false })
}
onChange={this.cssSettingsChangeHandler.bind(this)}
settings={this.props.currentItem.cssSettings}
editorTheme={this.props.prefs.editorTheme}
/>
</div>
</SplitPane>
);
}
}

View File

@@ -0,0 +1,52 @@
import { h, Component } from 'preact';
import Modal from './Modal';
import CodeMirrorBox from './CodeMirrorBox';
export default class CssSettingsModal extends Component {
componentDidUpdate() {
if (this.props.show) {
setTimeout(() => {
if (this.props.settings) {
this.cm.setValue(this.props.settings.acssConfig);
}
// Refresh is required because codemirror gets scaled inside modal and loses alignement.
this.cm.refresh();
this.cm.focus();
}, 500);
}
}
render() {
return (
<Modal show={this.props.show} closeHandler={this.props.closeHandler}>
<h1>Atomic CSS Settings</h1>
<h3>
Configure Atomizer settings.{' '}
<a
href="https://github.com/acss-io/atomizer#api"
target="_blank"
rel="noopener noreferrer"
>
Read more
</a>{' '}
about available settings.
</h3>
<div style="height: calc(100vh - 350px);">
<CodeMirrorBox
options={{
mode: 'application/ld+json',
theme: this.props.editorTheme
}}
onCreation={cm => (this.cm = cm)}
onBlur={cm => this.props.onChange(cm.getValue())}
/>
</div>
<div class="flex flex-h-end">
<button class="btn btn--primary" onClick={this.props.closeHandler}>
Apply and Close
</button>
</div>
</Modal>
);
}
}

222
src/components/Footer.jsx Normal file
View File

@@ -0,0 +1,222 @@
import { h, Component } from 'preact';
import { A } from './common';
export default class Footer extends Component {
constructor(props) {
super(props);
this.state = {
isKeyboardShortcutsModalOpen: false
};
}
layoutBtnClickhandler(layoutId) {
this.props.layoutBtnClickHandler(layoutId);
}
render() {
return (
<div id="footer" class="footer">
<div class="footer__right fr">
<a
onClick={this.props.saveHtmlBtnClickHandler}
id="saveHtmlBtn"
class="mode-btn hint--rounded hint--top-left hide-on-mobile"
data-hint="Save as HTML file"
>
<svg viewBox="0 0 24 24">
<path d="M5,20H19V18H5M19,9H15V3H9V9H5L12,16L19,9Z" />
</svg>
</a>
<svg style="display: none;" xmlns="http://www.w3.org/2000/svg">
<symbol id="codepen-logo" viewBox="0 0 120 120">
<path
class="outer-ring"
d="M60.048 0C26.884 0 0 26.9 0 60.048s26.884 60 60 60.047c33.163 0 60.047-26.883 60.047-60.047 S93.211 0 60 0z M60.048 110.233c-27.673 0-50.186-22.514-50.186-50.186S32.375 9.9 60 9.9 c27.672 0 50.2 22.5 50.2 50.186S87.72 110.2 60 110.233z"
/>
<path
class="inner-box"
d="M97.147 48.319c-0.007-0.047-0.019-0.092-0.026-0.139c-0.016-0.09-0.032-0.18-0.056-0.268 c-0.014-0.053-0.033-0.104-0.05-0.154c-0.025-0.078-0.051-0.156-0.082-0.232c-0.021-0.053-0.047-0.105-0.071-0.156 c-0.033-0.072-0.068-0.143-0.108-0.211c-0.029-0.051-0.061-0.1-0.091-0.148c-0.043-0.066-0.087-0.131-0.135-0.193 c-0.035-0.047-0.072-0.094-0.109-0.139c-0.051-0.059-0.104-0.117-0.159-0.172c-0.042-0.043-0.083-0.086-0.127-0.125 c-0.059-0.053-0.119-0.104-0.181-0.152c-0.048-0.037-0.095-0.074-0.145-0.109c-0.019-0.012-0.035-0.027-0.053-0.039L61.817 23.5 c-1.072-0.715-2.468-0.715-3.54 0L24.34 46.081c-0.018 0.012-0.034 0.027-0.053 0.039c-0.05 0.035-0.097 0.072-0.144 0.1 c-0.062 0.049-0.123 0.1-0.181 0.152c-0.045 0.039-0.086 0.082-0.128 0.125c-0.056 0.055-0.108 0.113-0.158 0.2 c-0.038 0.045-0.075 0.092-0.11 0.139c-0.047 0.062-0.092 0.127-0.134 0.193c-0.032 0.049-0.062 0.098-0.092 0.1 c-0.039 0.068-0.074 0.139-0.108 0.211c-0.024 0.051-0.05 0.104-0.071 0.156c-0.031 0.076-0.057 0.154-0.082 0.2 c-0.017 0.051-0.035 0.102-0.05 0.154c-0.023 0.088-0.039 0.178-0.056 0.268c-0.008 0.047-0.02 0.092-0.025 0.1 c-0.019 0.137-0.029 0.275-0.029 0.416V71.36c0 0.1 0 0.3 0 0.418c0.006 0 0 0.1 0 0.1 c0.017 0.1 0 0.2 0.1 0.268c0.015 0.1 0 0.1 0.1 0.154c0.025 0.1 0.1 0.2 0.1 0.2 c0.021 0.1 0 0.1 0.1 0.154c0.034 0.1 0.1 0.1 0.1 0.213c0.029 0 0.1 0.1 0.1 0.1 c0.042 0.1 0.1 0.1 0.1 0.193c0.035 0 0.1 0.1 0.1 0.139c0.05 0.1 0.1 0.1 0.2 0.2 c0.042 0 0.1 0.1 0.1 0.125c0.058 0.1 0.1 0.1 0.2 0.152c0.047 0 0.1 0.1 0.1 0.1 c0.019 0 0 0 0.1 0.039L58.277 96.64c0.536 0.4 1.2 0.5 1.8 0.537c0.616 0 1.233-0.18 1.77-0.537 l33.938-22.625c0.018-0.012 0.034-0.027 0.053-0.039c0.05-0.035 0.097-0.072 0.145-0.109c0.062-0.049 0.122-0.1 0.181-0.152 c0.044-0.039 0.085-0.082 0.127-0.125c0.056-0.055 0.108-0.113 0.159-0.172c0.037-0.045 0.074-0.09 0.109-0.139 c0.048-0.062 0.092-0.127 0.135-0.193c0.03-0.049 0.062-0.098 0.091-0.146c0.04-0.07 0.075-0.141 0.108-0.213 c0.024-0.051 0.05-0.102 0.071-0.154c0.031-0.078 0.057-0.156 0.082-0.234c0.017-0.051 0.036-0.102 0.05-0.154 c0.023-0.088 0.04-0.178 0.056-0.268c0.008-0.045 0.02-0.092 0.026-0.137c0.018-0.139 0.028-0.277 0.028-0.418V48.735 C97.176 48.6 97.2 48.5 97.1 48.319z M63.238 32.073l25.001 16.666L77.072 56.21l-13.834-9.254V32.073z M56.856 32.1 v14.883L43.023 56.21l-11.168-7.471L56.856 32.073z M29.301 54.708l7.983 5.34l-7.983 5.34V54.708z M56.856 88.022L31.855 71.4 l11.168-7.469l13.833 9.252V88.022z M60.048 67.597l-11.286-7.549l11.286-7.549l11.285 7.549L60.048 67.597z M63.238 88.022V73.14 l13.834-9.252l11.167 7.469L63.238 88.022z M90.794 65.388l-7.982-5.34l7.982-5.34V65.388z"
/>
</symbol>
</svg>
<a
href=""
onClick={this.props.codepenBtnClickHandler}
id="codepenBtn"
class="mode-btn hint--rounded hint--top-left hide-on-mobile"
aria-label="Edit on CodePen"
>
<svg>
<use xlinkHref="#codepen-logo" />
</svg>
</a>
<a
href=""
id="screenshotBtn"
class="mode-btn hint--rounded hint--top-left show-when-extension"
onClick={this.props.screenshotBtnClickHandler}
aria-label="Take screenshot of preview"
>
<svg style="width:24px;height:24px" viewBox="0 0 24 24">
<path d="M4,4H7L9,2H15L17,4H20A2,2 0 0,1 22,6V18A2,2 0 0,1 20,20H4A2,2 0 0,1 2,18V6A2,2 0 0,1 4,4M12,7A5,5 0 0,0 7,12A5,5 0 0,0 12,17A5,5 0 0,0 17,12A5,5 0 0,0 12,7M12,9A3,3 0 0,1 15,12A3,3 0 0,1 12,15A3,3 0 0,1 9,12A3,3 0 0,1 12,9Z" />
</svg>
</a>
<div class="footer__separator hide-on-mobile" />
<a
onClick={this.layoutBtnClickhandler.bind(this, 1)}
id="layoutBtn1"
class="mode-btn hide-on-mobile"
>
<svg viewBox="0 0 100 100" style="transform:rotate(-90deg)">
<use xlinkHref="#mode-icon" />
</svg>
</a>
<a
onClick={this.layoutBtnClickhandler.bind(this, 2)}
id="layoutBtn2"
class="mode-btn hide-on-mobile"
>
<svg viewBox="0 0 100 100">
<use xlinkHref="#mode-icon" />
</svg>
</a>
<a
onClick={this.layoutBtnClickhandler.bind(this, 3)}
id="layoutBtn3"
class="mode-btn hide-on-mobile"
>
<svg viewBox="0 0 100 100" style="transform:rotate(90deg)">
<use xlinkHref="#mode-icon" />
</svg>
</a>
<a
onClick={this.layoutBtnClickhandler.bind(this, 5)}
id="layoutBtn5"
class="mode-btn hide-on-mobile"
>
<svg viewBox="0 0 100 100">
<use xlinkHref="#vertical-mode-icon" />
</svg>
</a>
<a
onClick={this.layoutBtnClickhandler.bind(this, 4)}
id="layoutBtn4"
class="mode-btn hint--top-left hint--rounded hide-on-mobile"
aria-label="Full Screen"
>
<svg viewBox="0 0 100 100">
<rect x="0" y="0" width="100" height="100" />
</svg>
</a>
<a
class="mode-btn hint--top-left hint--rounded hide-on-mobile"
aria-label="Detach Preview"
onClick={this.props.detachedPreviewBtnHandler}
>
<svg viewBox="0 0 24 24">
<path d="M22,17V7H6V17H22M22,5A2,2 0 0,1 24,7V17C24,18.11 23.1,19 22,19H16V21H18V23H10V21H12V19H6C4.89,19 4,18.11 4,17V7A2,2 0 0,1 6,5H22M2,3V15H0V3A2,2 0 0,1 2,1H20V3H2Z" />
</svg>
</a>
<div class="footer__separator" />
<a
onClick={this.props.notificationsBtnClickHandler}
id="notificationsBtn"
class={`notifications-btn mode-btn hint--top-left hint--rounded ${
this.props.hasUnseenChangelog ? 'has-new' : ''
}`}
aria-label="Notifications"
>
<svg viewBox="0 0 24 24">
<path d="M14,20A2,2 0 0,1 12,22A2,2 0 0,1 10,20H14M12,2A1,1 0 0,1 13,3V4.08C15.84,4.56 18,7.03 18,10V16L21,19H3L6,16V10C6,7.03 8.16,4.56 11,4.08V3A1,1 0 0,1 12,2Z" />
</svg>
<span class="notifications-btn__dot" />
</a>
<A
onClick={this.props.settingsBtnClickHandler}
data-event-category="ui"
data-event-action="settingsBtnClick"
class="mode-btn hint--top-left hint--rounded"
aria-label="Settings"
>
<svg>
<use xlinkHref="#settings-icon" />
</svg>
</A>
</div>
<a
href="https://webmakerapp.com/"
target="_blank"
rel="noopener noreferrer"
>
<div class="logo" />
</a>
&copy;
<span class="web-maker-with-tag">Web Maker</span> &nbsp;&nbsp;
<A
onClick={this.props.helpBtnClickHandler}
data-event-category="ui"
data-event-action="helpButtonClick"
class="footer__link hint--rounded hint--top-right"
aria-label="Help"
>
<svg
style="width:20px; height:20px; vertical-align:text-bottom"
viewBox="0 0 24 24"
>
<path d="M15.07,11.25L14.17,12.17C13.45,12.89 13,13.5 13,15H11V14.5C11,13.39 11.45,12.39 12.17,11.67L13.41,10.41C13.78,10.05 14,9.55 14,9C14,7.89 13.1,7 12,7A2,2 0 0,0 10,9H8A4,4 0 0,1 12,5A4,4 0 0,1 16,9C16,9.88 15.64,10.67 15.07,11.25M13,19H11V17H13M12,2A10,10 0 0,0 2,12A10,10 0 0,0 12,22A10,10 0 0,0 22,12C22,6.47 17.5,2 12,2Z" />
</svg>
</A>
<A
onClick={this.props.keyboardShortcutsBtnClickHandler}
data-event-category="ui"
data-event-action="keyboardShortcutButtonClick"
class="footer__link hint--rounded hint--top-right hide-on-mobile"
aria-label="Keyboard shortcuts"
>
<svg
style={{
width: '20px',
height: '20px',
verticalAlign: 'text-bottom'
}}
>
<use xlinkHref="#keyboard-icon" />
</svg>
</A>
<a
class="footer__link hint--rounded hint--top-right"
aria-label="Tweet about 'Web Maker'"
href="http://twitter.com/share?url=https://webmakerapp.com/&text=Web Maker - A blazing fast %26 offline web playground! via @webmakerApp&related=webmakerApp&hashtags=web,frontend,playground,offline"
target="_blank"
rel="noopener noreferrer"
>
<svg
style={{
width: '20px',
height: '20px',
verticalAlign: 'text-bottom'
}}
>
<use xlinkHref="#twitter-icon" />
</svg>
</a>
<A
onClick={this.props.supportDeveloperBtnClickHandler}
data-event-category="ui"
data-event-action="supportDeveloperFooterBtnClick"
class="footer__link ml-1 hint--rounded hint--top-right hide-on-mobile"
aria-label="Support the developer by pledging some amount"
target="_blank"
rel="noopener noreferrer"
>
Support the developer
</A>
</div>
);
}
}

View File

@@ -0,0 +1,203 @@
import { h } from 'preact';
import Modal from './Modal';
import { Button } from './common';
export function HelpModal(props) {
return (
<Modal show={props.show} closeHandler={props.closeHandler}>
<h1>
<div class="web-maker-with-tag">Web Maker</div>
<small style="font-size:14px;"> v3.2.0</small>
</h1>
<div>
<p>
Made with <span style="margin-right: 8px;">💖</span> &{' '}
<span style="margin-right: 8px;">🙌</span> by{' '}
<a
href="https://twitter.com/chinchang457"
target="_blank"
rel="noopener noreferrer"
>
Kushagra Gour
</a>
</p>
<p>
<a href="/docs" target="_blank" rel="noopener noreferrer">
Read the documentation
</a>.
</p>
<p>
Tweet out your feature requests, comments & suggestions to{' '}
<a
target="_blank"
rel="noopener noreferrer"
href="https://twitter.com/webmakerApp"
>
@webmakerApp
</a>.
</p>
<p>
Like this extension? Please{' '}
<a
href="https://chrome.google.com/webstore/detail/web-maker/lkfkkhfhhdkiemehlpkgjeojomhpccnh/reviews"
target="_blank"
rel="noopener noreferrer"
>
rate it here
</a>.
</p>
<p>
<Button
aria-label="Support the developer"
onClick={props.onSupportBtnClick}
data-event-action="supportDeveloperHelpBtnClick"
data-event-category="ui"
class="btn btn-icon"
>
<svg>
<use xlinkHref="#gift-icon" />
</svg>Support the developer
</Button>{' '}
<a
aria-label="Rate Web Maker"
href="https://chrome.google.com/webstore/detail/web-maker/lkfkkhfhhdkiemehlpkgjeojomhpccnh/reviews"
target="_blank"
rel="noopener noreferrer"
class="btn btn-icon"
>
<svg>
<use xlinkHref="#heart-icon" />
</svg>Share Web Maker
</a>{' '}
<a
aria-label="Chat"
href="https://web-maker.slack.com"
target="_blank"
rel="noopener noreferrer"
class="btn btn-icon"
>
<svg>
<use xlinkHref="#chat-icon" />
</svg>Chat
</a>{' '}
<a
aria-label="Report a Bug"
href="https://github.com/chinchang/web-maker/issues"
target="_blank"
rel="noopener noreferrer"
class="btn btn-icon"
>
<svg>
<use xlinkHref="#bug-icon" />
</svg>Report a bug
</a>
</p>
<p>
<h3>Awesome libraries used</h3>
<ul>
<li>
<a
target="_blank"
rel="noopener noreferrer"
href="https://kushagragour.in/lab/hint/"
>
Hint.css
</a>{' '}
&
<a
target="_blank"
rel="noopener noreferrer"
href="https://github.com/chinchang/screenlog.js"
>
Screenlog.js
</a>{' '}
- By me :)
</li>
<li>
<a
target="_blank"
rel="noopener noreferrer"
href="https://nathancahill.github.io/Split.js/"
>
Split.js
</a>{' '}
- Nathan Cahill
</li>
<li>
<a
target="_blank"
rel="noopener noreferrer"
href="https://codemirror.net/"
>
Codemirror
</a>{' '}
- Marijn Haverbeke
</li>
<li>
<a
target="_blank"
rel="noopener noreferrer"
href="https://emmet.io/"
>
Emmet
</a>{' '}
- Sergey Chikuyonok
</li>
<li>
<a
target="_blank"
rel="noopener noreferrer"
href="http://esprima.org/"
>
Esprima
</a>{' '}
- Ariya Hidayat
</li>
<li>
<a
target="_blank"
rel="noopener noreferrer"
href="https://github.com/enjalot/Inlet"
>
Inlet
</a>{' '}
- Ian Johnson
</li>
<li>
<a
target="_blank"
rel="noopener noreferrer"
href="https://webmakerapp.com/"
>
Web Maker!
</a>{' '}
- whhat!
</li>
</ul>
</p>
<p>
<h3>License</h3>
"Web Maker" is{' '}
<a
target="_blank"
rel="noopener noreferrer"
href="https://github.com/chinchang/web-maker"
>
open-source
</a>{' '}
under the{' '}
<a
href="https://opensource.org/licenses/MIT"
target="_blank"
rel="noopener noreferrer"
>
MIT License
</a>.
</p>
</div>
</Modal>
);
}

161
src/components/Icons.jsx Normal file
View File

@@ -0,0 +1,161 @@
import { h } from 'preact';
export function Icons() {
return (
<svg
version="1.1"
xmlns="http://www.w3.org/2000/svg"
style={{ display: 'none' }}
>
<symbol id="logo" viewBox="-145 -2 372 175">
<g
stroke="none"
strokeWidth={1}
fill="none"
fillRule="evenodd"
transform="translate(-145.000000, -1.000000)"
>
<polygon
id="Path-1"
fill="#FF4600"
points="31 0 232 0 132 173.310547"
/>
<polygon
id="Path-1"
fill="#FF6C00"
points="0 0 201 0 101 173.310547"
/>
<polygon
id="Path-1"
fill="#FF6C00"
transform="translate(271.500000, 86.500000) scale(1, -1) translate(-271.500000, -86.500000) "
points="171 0 372 0 272 173.310547"
/>
<polygon
id="Path-1"
fill="#FF4600"
transform="translate(241.500000, 86.500000) scale(1, -1) translate(-241.500000, -86.500000) "
points="141 0 342 0 242 173.310547"
/>
</g>
</symbol>
<symbol id="bug-icon" viewBox="0 0 24 24">
<path d="M14,12H10V10H14M14,16H10V14H14M20,8H17.19C16.74,7.22 16.12,6.55 15.37,6.04L17,4.41L15.59,3L13.42,5.17C12.96,5.06 12.5,5 12,5C11.5,5 11.04,5.06 10.59,5.17L8.41,3L7,4.41L8.62,6.04C7.88,6.55 7.26,7.22 6.81,8H4V10H6.09C6.04,10.33 6,10.66 6,11V12H4V14H6V15C6,15.34 6.04,15.67 6.09,16H4V18H6.81C7.85,19.79 9.78,21 12,21C14.22,21 16.15,19.79 17.19,18H20V16H17.91C17.96,15.67 18,15.34 18,15V14H20V12H18V11C18,10.66 17.96,10.33 17.91,10H20V8Z" />
</symbol>
<symbol id="google-icon" viewBox="0 0 24 24">
<path d="M21.35,11.1H12.18V13.83H18.69C18.36,17.64 15.19,19.27 12.19,19.27C8.36,19.27 5,16.25 5,12C5,7.9 8.2,4.73 12.2,4.73C15.29,4.73 17.1,6.7 17.1,6.7L19,4.72C19,4.72 16.56,2 12.1,2C6.42,2 2.03,6.8 2.03,12C2.03,17.05 6.16,22 12.25,22C17.6,22 21.5,18.33 21.5,12.91C21.5,11.76 21.35,11.1 21.35,11.1V11.1Z" />
</symbol>
<symbol id="fb-icon" viewBox="0 0 24 24">
<path d="M17,2V2H17V6H15C14.31,6 14,6.81 14,7.5V10H14L17,10V14H14V22H10V14H7V10H10V6A4,4 0 0,1 14,2H17Z" />
</symbol>
<symbol id="github-icon" viewBox="0 0 24 24">
<path d="M12,2A10,10 0 0,0 2,12C2,16.42 4.87,20.17 8.84,21.5C9.34,21.58 9.5,21.27 9.5,21C9.5,20.77 9.5,20.14 9.5,19.31C6.73,19.91 6.14,17.97 6.14,17.97C5.68,16.81 5.03,16.5 5.03,16.5C4.12,15.88 5.1,15.9 5.1,15.9C6.1,15.97 6.63,16.93 6.63,16.93C7.5,18.45 8.97,18 9.54,17.76C9.63,17.11 9.89,16.67 10.17,16.42C7.95,16.17 5.62,15.31 5.62,11.5C5.62,10.39 6,9.5 6.65,8.79C6.55,8.54 6.2,7.5 6.75,6.15C6.75,6.15 7.59,5.88 9.5,7.17C10.29,6.95 11.15,6.84 12,6.84C12.85,6.84 13.71,6.95 14.5,7.17C16.41,5.88 17.25,6.15 17.25,6.15C17.8,7.5 17.45,8.54 17.35,8.79C18,9.5 18.38,10.39 18.38,11.5C18.38,15.32 16.04,16.16 13.81,16.41C14.17,16.72 14.5,17.33 14.5,18.26C14.5,19.6 14.5,20.68 14.5,21C14.5,21.27 14.66,21.59 15.17,21.5C19.14,20.16 22,16.42 22,12A10,10 0 0,0 12,2Z" />
</symbol>
<symbol id="settings-icon" viewBox="0 0 24 24">
<path d="M12,15.5A3.5,3.5 0 0,1 8.5,12A3.5,3.5 0 0,1 12,8.5A3.5,3.5 0 0,1 15.5,12A3.5,3.5 0 0,1 12,15.5M19.43,12.97C19.47,12.65 19.5,12.33 19.5,12C19.5,11.67 19.47,11.34 19.43,11L21.54,9.37C21.73,9.22 21.78,8.95 21.66,8.73L19.66,5.27C19.54,5.05 19.27,4.96 19.05,5.05L16.56,6.05C16.04,5.66 15.5,5.32 14.87,5.07L14.5,2.42C14.46,2.18 14.25,2 14,2H10C9.75,2 9.54,2.18 9.5,2.42L9.13,5.07C8.5,5.32 7.96,5.66 7.44,6.05L4.95,5.05C4.73,4.96 4.46,5.05 4.34,5.27L2.34,8.73C2.21,8.95 2.27,9.22 2.46,9.37L4.57,11C4.53,11.34 4.5,11.67 4.5,12C4.5,12.33 4.53,12.65 4.57,12.97L2.46,14.63C2.27,14.78 2.21,15.05 2.34,15.27L4.34,18.73C4.46,18.95 4.73,19.03 4.95,18.95L7.44,17.94C7.96,18.34 8.5,18.68 9.13,18.93L9.5,21.58C9.54,21.82 9.75,22 10,22H14C14.25,22 14.46,21.82 14.5,21.58L14.87,18.93C15.5,18.67 16.04,18.34 16.56,17.94L19.05,18.95C19.27,19.03 19.54,18.95 19.66,18.73L21.66,15.27C21.78,15.05 21.73,14.78 21.54,14.63L19.43,12.97Z" />
</symbol>
<symbol id="twitter-icon" viewBox="0 0 16 16">
<path d="M15.969,3.058c-0.586,0.26-1.217,0.436-1.878,0.515c0.675-0.405,1.194-1.045,1.438-1.809 c-0.632,0.375-1.332,0.647-2.076,0.793c-0.596-0.636-1.446-1.033-2.387-1.033c-1.806,0-3.27,1.464-3.27,3.27 c0,0.256,0.029,0.506,0.085,0.745C5.163,5.404,2.753,4.102,1.14,2.124C0.859,2.607,0.698,3.168,0.698,3.767 c0,1.134,0.577,2.135,1.455,2.722C1.616,6.472,1.112,6.325,0.671,6.08c0,0.014,0,0.027,0,0.041c0,1.584,1.127,2.906,2.623,3.206 C3.02,9.402,2.731,9.442,2.433,9.442c-0.211,0-0.416-0.021-0.615-0.059c0.416,1.299,1.624,2.245,3.055,2.271 c-1.119,0.877-2.529,1.4-4.061,1.4c-0.264,0-0.524-0.015-0.78-0.046c1.447,0.928,3.166,1.469,5.013,1.469 c6.015,0,9.304-4.983,9.304-9.304c0-0.142-0.003-0.283-0.009-0.423C14.976,4.29,15.531,3.714,15.969,3.058z" />
</symbol>
<symbol id="heart-icon" viewBox="0 0 24 24">
<path d="M12,21.35L10.55,20.03C5.4,15.36 2,12.27 2,8.5C2,5.41 4.42,3 7.5,3C9.24,3 10.91,3.81 12,5.08C13.09,3.81 14.76,3 16.5,3C19.58,3 22,5.41 22,8.5C22,12.27 18.6,15.36 13.45,20.03L12,21.35Z" />
</symbol>
<symbol id="play-icon" viewBox="0 0 24 24">
<svg>
<path d="M8,5.14V19.14L19,12.14L8,5.14Z" />
</svg>
</symbol>
<symbol id="cancel-icon" viewBox="0 0 24 24">
<svg>
<path d="M12,2A10,10 0 0,1 22,12A10,10 0 0,1 12,22A10,10 0 0,1 2,12A10,10 0 0,1 12,2M12,4A8,8 0 0,0 4,12C4,13.85 4.63,15.55 5.68,16.91L16.91,5.68C15.55,4.63 13.85,4 12,4M12,20A8,8 0 0,0 20,12C20,10.15 19.37,8.45 18.32,7.09L7.09,18.32C8.45,19.37 10.15,20 12,20Z" />
</svg>
</symbol>
<symbol id="chevron-icon" viewBox="0 0 24 24">
<svg>
<path d="M8.59,16.58L13.17,12L8.59,7.41L10,6L16,12L10,18L8.59,16.58Z" />
</svg>
</symbol>
<symbol id="chat-icon" viewBox="0 0 24 24">
<path d="M20,2H4A2,2 0 0,0 2,4V22L6,18H20A2,2 0 0,0 22,16V4A2,2 0 0,0 20,2M8,14H6V12H8V14M8,11H6V9H8V11M8,8H6V6H8V8M15,14H10V12H15V14M18,11H10V9H18V11M18,8H10V6H18V8Z" />
</symbol>
<symbol id="gift-icon" viewBox="0 0 24 24">
<path d="M22,12V20A2,2 0 0,1 20,22H4A2,2 0 0,1 2,20V12A1,1 0 0,1 1,11V8A2,2 0 0,1 3,6H6.17C6.06,5.69 6,5.35 6,5A3,3 0 0,1 9,2C10,2 10.88,2.5 11.43,3.24V3.23L12,4L12.57,3.23V3.24C13.12,2.5 14,2 15,2A3,3 0 0,1 18,5C18,5.35 17.94,5.69 17.83,6H21A2,2 0 0,1 23,8V11A1,1 0 0,1 22,12M4,20H11V12H4V20M20,20V12H13V20H20M9,4A1,1 0 0,0 8,5A1,1 0 0,0 9,6A1,1 0 0,0 10,5A1,1 0 0,0 9,4M15,4A1,1 0 0,0 14,5A1,1 0 0,0 15,6A1,1 0 0,0 16,5A1,1 0 0,0 15,4M3,8V10H11V8H3M13,8V10H21V8H13Z" />
<symbol id="gift-icon" viewBox="0 0 24 24" />
<symbol id="cross-icon" viewBox="0 0 24 24">
<path d="M19,6.41L17.59,5L12,10.59L6.41,5L5,6.41L10.59,12L5,17.59L6.41,19L12,13.41L17.59,19L19,17.59L13.41,12L19,6.41Z" />
</symbol>
<symbol id="keyboard-icon" viewBox="0 0 24 24">
<path d="M19,10H17V8H19M19,13H17V11H19M16,10H14V8H16M16,13H14V11H16M16,17H8V15H16M7,10H5V8H7M7,13H5V11H7M8,11H10V13H8M8,8H10V10H8M11,11H13V13H11M11,8H13V10H11M20,5H4C2.89,5 2,5.89 2,7V17A2,2 0 0,0 4,19H20A2,2 0 0,0 22,17V7C22,5.89 21.1,5 20,5Z" />
</symbol>
<symbol id="mode-icon" viewBox="0 0 100 100">
<g>
<rect x={0} y={0} width={28} height={47} />
<rect x={36} y={0} width={28} height={47} />
<rect x={72} y={0} width={28} height={47} />
<rect x={0} y={53} width={100} height={47} />
</g>
</symbol>
<symbol id="vertical-mode-icon" viewBox="0 0 100 100">
<g>
<rect x={0} y={0} width={20} height={100} />
<rect x={23} y={0} width={20} height={100} />
<rect x={46} y={0} width={20} height={100} />
<rect x={69} y={0} width={32} height={100} />
</g>
</symbol>
<symbol id="search" viewBox="0 0 24 24">
<path d="M9.5,3A6.5,6.5 0 0,1 16,9.5C16,11.11 15.41,12.59 14.44,13.73L14.71,14H15.5L20.5,19L19,20.5L14,15.5V14.71L13.73,14.44C12.59,15.41 11.11,16 9.5,16A6.5,6.5 0 0,1 3,9.5A6.5,6.5 0 0,1 9.5,3M9.5,5C7,5 5,7 5,9.5C5,12 7,14 9.5,14C12,14 14,12 14,9.5C14,7 12,5 9.5,5Z" />
</symbol>
<symbol id="loader-icon" viewBox="0 0 44 44">
{/* By Sam Herbert (@sherb), for everyone. More http://goo.gl/7AJzbL */}
<g fill="none" fillRule="evenodd" strokeWidth={10}>
<circle cx={22} cy={22} r={1}>
<animate
attributeName="r"
begin="0s"
dur="1.8s"
values="1; 20"
calcMode="spline"
keyTimes="0; 1"
keySplines="0.165, 0.84, 0.44, 1"
repeatCount="indefinite"
/>
<animate
attributeName="stroke-opacity"
begin="0s"
dur="1.8s"
values="1; 0"
calcMode="spline"
keyTimes="0; 1"
keySplines="0.3, 0.61, 0.355, 1"
repeatCount="indefinite"
/>
</circle>
<circle cx={22} cy={22} r={1}>
<animate
attributeName="r"
begin="-0.9s"
dur="1.8s"
values="1; 20"
calcMode="spline"
keyTimes="0; 1"
keySplines="0.165, 0.84, 0.44, 1"
repeatCount="indefinite"
/>
<animate
attributeName="stroke-opacity"
begin="-0.9s"
dur="1.8s"
values="1; 0"
calcMode="spline"
keyTimes="0; 1"
keySplines="0.3, 0.61, 0.355, 1"
repeatCount="indefinite"
/>
</circle>
</g>
</symbol>
</symbol>
</svg>
);
}

View File

@@ -0,0 +1,94 @@
import { h } from 'preact';
import Modal from './Modal';
export function KeyboardShortcutsModal({ show, closeHandler }) {
return (
<Modal show={show} closeHandler={closeHandler}>
<h1>Keyboard Shortcuts</h1>
<div class="flex">
<div>
<h2>Global</h2>
<p>
<span class="kbd-shortcut__keys">Ctrl/ + Shift + ?</span>
<span class="kbd-shortcut__details">See keyboard shortcuts</span>
</p>
<p>
<span class="kbd-shortcut__keys">Ctrl/ + Shift + 5</span>
<span class="kbd-shortcut__details">Refresh preview</span>
</p>
<p>
<span class="kbd-shortcut__keys">Ctrl/ + S</span>
<span class="kbd-shortcut__details">Save current creations</span>
</p>
<p>
<span class="kbd-shortcut__keys">Ctrl/ + O</span>
<span class="kbd-shortcut__details">
Open list of saved creations
</span>
</p>
<p>
<span class="kbd-shortcut__keys">Ctrl + L</span>
<span class="kbd-shortcut__details">
Clear console (works when console input is focused)
</span>
</p>
<p>
<span class="kbd-shortcut__keys">Esc</span>
<span class="kbd-shortcut__details">
Close saved creations panel & modals
</span>
</p>
</div>
<div class="ml-2">
<h2>Editor</h2>
<p>
<span class="kbd-shortcut__keys">Ctrl/ + F</span>
<span class="kbd-shortcut__details">Find</span>
</p>
<p>
<span class="kbd-shortcut__keys">Ctrl/ + G</span>
<span class="kbd-shortcut__details">Select next match</span>
</p>
<p>
<span class="kbd-shortcut__keys">Ctrl/ + Shift + G</span>
<span class="kbd-shortcut__details">Select previous match</span>
</p>
<p>
<span class="kbd-shortcut__keys">Ctrl/ + Opt/Alt + F</span>
<span class="kbd-shortcut__details">Find & replace</span>
</p>
<p>
<span class="kbd-shortcut__keys">Shift + Tab</span>
<span class="kbd-shortcut__details">Realign code</span>
</p>
<p>
<span class="kbd-shortcut__keys">Ctrl/ + ]</span>
<span class="kbd-shortcut__details">Indent code right</span>
</p>
<p>
<span class="kbd-shortcut__keys">Ctrl/ + [</span>
<span class="kbd-shortcut__details">Indent code left</span>
</p>
<p>
<span class="kbd-shortcut__keys">Tab</span>
<span class="kbd-shortcut__details">
Emmet code completion{' '}
<a
href="https://emmet.io/"
target="_blank"
rel="noopener noreferrer"
>
Read more
</a>
</span>
</p>
<p>
<span class="kbd-shortcut__keys">Ctrl/ + /</span>
<span class="kbd-shortcut__details">Single line comment</span>
</p>
</div>
</div>
</Modal>
);
}

View File

@@ -0,0 +1,164 @@
import { h, Component } from 'preact';
import { trackEvent } from '../analytics';
export class LibraryAutoSuggest extends Component {
componentDidMount() {
this.t = this.wrap.querySelector('input,textarea');
this.filter = this.props.filter;
this.selectedCallback = this.props.onSelect;
// after list is insrted into the DOM, we put it in the body
// fixed at same position
setTimeout(() => {
requestIdleCallback(() => {
document.body.appendChild(this.list);
this.list.style.position = 'fixed';
});
}, 100);
this.t.addEventListener('input', e => this.onInput(e));
this.t.addEventListener('keydown', e => this.onKeyDown(e));
this.t.addEventListener('blur', e => this.closeSuggestions(e));
this.list.addEventListener('mousedown', e => this.onListMouseDown(e));
}
componentWillUnmount() {
this.t.removeEventListener('input', e => this.onInput(e));
this.t.removeEventListener('keydown', e => this.onKeyDown(e));
this.t.removeEventListener('blur', e => this.closeSuggestions(e));
this.list.removeEventListener('mousedown', e => this.onListMouseDown(e));
}
get currentLineNumber() {
return this.t.value.substr(0, this.t.selectionStart).split('\n').length;
}
get currentLine() {
var line = this.currentLineNumber;
return this.t.value.split('\n')[line - 1];
}
closeSuggestions() {
this.list.classList.remove('is-open');
this.isShowingSuggestions = false;
}
getList(input) {
var url = 'https://api.cdnjs.com/libraries?search=';
return fetch(url + input).then(response => {
return response.json().then(json => json.results);
});
}
replaceCurrentLine(val) {
var lines = this.t.value.split('\n');
lines.splice(this.currentLineNumber - 1, 1, val);
this.t.value = lines.join('\n');
}
onInput() {
var currentLine = this.currentLine;
if (currentLine) {
if (currentLine.indexOf('/') !== -1 || currentLine.match(/https*:\/\//)) {
return;
}
clearTimeout(this.timeout);
this.timeout = setTimeout(() => {
this.loader.style.display = 'block';
this.getList(currentLine).then(arr => {
this.loader.style.display = 'none';
if (!arr.length) {
this.closeSuggestions();
return;
}
this.list.innerHTML = '';
if (this.filter) {
/* eslint-disable no-param-reassign */
arr = arr.filter(this.filter);
}
for (var i = 0; i < Math.min(arr.length, 10); i++) {
this.list.innerHTML += `<li data-url="${arr[i].latest}"><a>${
arr[i].name
}</a></li>`;
}
this.isShowingSuggestions = true;
if (!this.textareaBounds) {
this.textareaBounds = this.t.getBoundingClientRect();
this.list.style.top = this.textareaBounds.bottom + 'px';
this.list.style.left = this.textareaBounds.left + 'px';
this.list.style.width = this.textareaBounds.width + 'px';
}
this.list.classList.add('is-open');
});
}, 500);
}
}
onKeyDown(event) {
var selectedItemElement;
if (!this.isShowingSuggestions) {
return;
}
if (event.keyCode === 27) {
this.closeSuggestions();
event.stopPropagation();
}
if (event.keyCode === 40 && this.isShowingSuggestions) {
selectedItemElement = this.list.querySelector('.selected');
if (selectedItemElement) {
selectedItemElement.classList.remove('selected');
selectedItemElement.nextElementSibling.classList.add('selected');
} else {
this.list.querySelector('li:first-child').classList.add('selected');
}
this.list.querySelector('.selected').scrollIntoView(false);
event.preventDefault();
} else if (event.keyCode === 38 && this.isShowingSuggestions) {
selectedItemElement = this.list.querySelector('.selected');
if (selectedItemElement) {
selectedItemElement.classList.remove('selected');
selectedItemElement.previousElementSibling.classList.add('selected');
} else {
this.list.querySelector('li:first-child').classList.add('selected');
}
this.list.querySelector('.selected').scrollIntoView(false);
event.preventDefault();
} else if (event.keyCode === 13 && this.isShowingSuggestions) {
selectedItemElement = this.list.querySelector('.selected');
this.selectSuggestion(selectedItemElement.dataset.url);
this.closeSuggestions();
}
}
onListMouseDown(event) {
var target = event.target;
if (target.parentElement.dataset.url) {
this.selectSuggestion(target.parentElement.dataset.url);
}
}
selectSuggestion(value) {
// Return back the focus which is getting lost for some reason
this.t.focus();
trackEvent('ui', 'autoSuggestionLibSelected', value);
if (this.selectedCallback) {
this.selectedCallback.call(null, value);
} else {
this.replaceCurrentLine(value);
}
this.closeSuggestions();
}
render() {
return (
<div
class={`btn-group ${this.props.fullWidth ? 'flex-grow' : ''}`}
ref={el => (this.wrap = el)}
>
{this.props.children}
<ul
ref={el => (this.list = el)}
class="dropdown__menu autocomplete-dropdown"
/>
<div
ref={el => (this.loader = el)}
class="loader autocomplete__loader"
style="display:none"
/>
</div>
);
}
}

73
src/components/Login.jsx Normal file
View File

@@ -0,0 +1,73 @@
import { h, Component } from 'preact';
import { trackEvent } from '../analytics';
import { auth } from '../auth';
export default class Login extends Component {
login(e) {
const provider = e.target.dataset.authProvider;
trackEvent('ui', 'loginProviderClick', provider);
auth.login(provider);
}
componentDidMount() {
window.db.local.get(
{
lastAuthProvider: ''
},
result => {
if (result.lastAuthProvider) {
document.body.classList.add(`last-login-${result.lastAuthProvider}`);
}
}
);
}
render() {
return (
<div>
<h2>Login / Signup</h2>
<div>
<p>
<button
type="button"
onClick={this.login.bind(this)}
class="social-login-btn social-login-btn--github btn btn-icon btn--big full-width hint--right hint--always"
data-auth-provider="github"
data-hint="You logged in with Github last time"
>
<svg>
<use xlinkHref="#github-icon" />
</svg>Login with Github
</button>
</p>
<p>
<button
type="button"
onClick={this.login.bind(this)}
class="social-login-btn social-login-btn--google btn btn-icon btn--big full-width hint--right hint--always"
data-auth-provider="google"
data-hint="You logged in with Google last time"
>
<svg>
<use xlinkHref="#google-icon" />
</svg>Login with Google
</button>
</p>
<p class="mb-2">
<button
type="button"
onClick={this.login.bind(this)}
class="social-login-btn social-login-btn--facebook btn btn-icon btn--big full-width hint--right hint--always"
data-auth-provider="facebook"
data-hint="You logged in with Facebook last time"
>
<svg>
<use xlinkHref="#fb-icon" />
</svg>Login with Facebook
</button>
</p>
<p>Join a community of 50,000+ Developers</p>
</div>
</div>
);
}
}

View File

@@ -0,0 +1,123 @@
import { h } from 'preact';
import { A } from './common';
const DEFAULT_PROFILE_IMG =
"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath fill='#ccc' d='M12,19.2C9.5,19.2 7.29,17.92 6,16C6.03,14 10,12.9 12,12.9C14,12.9 17.97,14 18,16C16.71,17.92 14.5,19.2 12,19.2M12,5A3,3 0 0,1 15,8A3,3 0 0,1 12,11A3,3 0 0,1 9,8A3,3 0 0,1 12,5M12,2A10,10 0 0,0 2,12A10,10 0 0,0 12,22A10,10 0 0,0 22,12C22,6.47 17.5,2 12,2Z' /%3E%3C/svg%3E";
export function MainHeader(props) {
return (
<div class="main-header">
<input
type="text"
id="titleInput"
title="Click to edit"
class="item-title-input"
value={props.title}
onBlur={props.titleInputBlurHandler}
/>
<div class="main-header__btn-wrap flex flex-v-center">
<a
id="runBtn"
class="hide flex flex-v-center hint--rounded hint--bottom-left"
aria-label="Run preview (Ctrl/⌘ + Shift + 5)"
onClick={props.runBtnClickHandler}
>
<svg style="width: 14px; height: 14px;">
<use xlinkHref="#play-icon" />
</svg>Run
</a>
<A
onClick={props.addLibraryBtnHandler}
data-event-category="ui"
data-event-action="addLibraryButtonClick"
class="flex-v-center hint--rounded hint--bottom-left"
aria-label="Add a JS/CSS library"
>
Add library{' '}
<span
id="js-external-lib-count"
style={`display:${props.externalLibCount ? 'inline' : 'none'}`}
class="count-label"
>
{props.externalLibCount}
</span>
</A>
<a
class="flex flex-v-center hint--rounded hint--bottom-left"
aria-label="Start a new creation"
onClick={props.newBtnHandler}
>
<svg
style="vertical-align:middle;width:14px;height:14px"
viewBox="0 0 24 24"
>
<path d="M19,13H13V19H11V13H5V11H11V5H13V11H19V13Z" />
</svg>New
</a>
<a
id="saveBtn"
class={`flex flex-v-center hint--rounded hint--bottom-left ${
props.isSaving ? 'is-loading' : ''
} ${props.unsavedEditCount ? 'is-marked' : 0}`}
aria-label="Save current creation (Ctrl/⌘ + S)"
onClick={props.saveBtnHandler}
>
<svg
style="vertical-align:middle;width:14px;height:14px"
viewBox="0 0 24 24"
>
<path d="M15,9H5V5H15M12,19A3,3 0 0,1 9,16A3,3 0 0,1 12,13A3,3 0 0,1 15,16A3,3 0 0,1 12,19M17,3H5C3.89,3 3,3.9 3,5V19A2,2 0 0,0 5,21H19A2,2 0 0,0 21,19V7L17,3Z" />
</svg>
<svg class="btn-loader" width="15" height="15" stroke="#fff">
<use xlinkHref="#loader-icon" />
</svg>
Save
</a>
<a
id="openItemsBtn"
class={`flex flex-v-center hint--rounded hint--bottom-left ${
props.isFetchingItems ? 'is-loading' : ''
}`}
aria-label="Open a saved creation (Ctrl/⌘ + O)"
onClick={props.openBtnHandler}
>
<svg
style="width:14px;height:14px;vertical-align:middle;"
viewBox="0 0 24 24"
>
<path d="M13,9V3.5L18.5,9M6,2C4.89,2 4,2.89 4,4V20A2,2 0 0,0 6,22H18A2,2 0 0,0 20,20V8L14,2H6Z" />
</svg>
<svg class="btn-loader" width="15" height="15" stroke="#fff">
<use xlinkHref="#loader-icon" />
</svg>
Open
</a>
<A
onClick={props.loginBtnHandler}
data-event-category="ui"
data-event-action="loginButtonClick"
class="hide-on-login flex flex-v-center hint--rounded hint--bottom-left"
aria-label="Login/Signup"
>
Login/Signup
</A>
<A
onClick={props.profileBtnHandler}
data-event-category="ui"
data-event-action="headerAvatarClick"
aria-label="See profile or Logout"
class="hide-on-logout hint--rounded hint--bottom-left"
>
<img
id="headerAvatarImg"
width="20"
src={props.user ? props.user.photoURL || DEFAULT_PROFILE_IMG : ''}
class="main-header__avatar-img"
/>
</A>
</div>
</div>
);
}

79
src/components/Modal.jsx Normal file
View File

@@ -0,0 +1,79 @@
import { h, Component } from 'preact';
import Portal from 'preact-portal';
export default class Modal extends Component {
componentDidMount() {
window.addEventListener('keydown', this.onKeyDownHandler.bind(this));
}
componentWillUnmount() {
window.removeEventListener('keydown', this.onKeyDownHandler.bind(this));
if (this.focusGrabber) {
this.focusGrabber.remove();
this.focusGrabber = null;
}
}
onKeyDownHandler(e) {
if (e.keyCode === 27) {
this.props.closeHandler();
}
}
onOverlayClick(e) {
if (e.target === this.overlayEl) {
this.props.closeHandler();
}
}
componentDidUpdate(prevProps) {
if (this.props.show !== prevProps.show) {
document.body.classList[this.props.show ? 'add' : 'remove'](
'overlay-visible'
);
if (this.props.show) {
// HACK: refs will evaluate on next tick due to portals
setTimeout(() => {
this.overlayEl.querySelector('.js-modal__close-btn').focus();
}, 0);
/* We insert a dummy hidden input which will take focus as soon as focus
escapes the modal, instead of focus going outside modal because modal
is last focusable element. */
this.focusGrabber = document.createElement('input');
this.focusGrabber.setAttribute(
'style',
'height:0;opacity:0;overflow:hidden;width:0;'
);
setTimeout(() => {
document.body.appendChild(this.focusGrabber);
}, 10);
} else {
this.focusGrabber.remove();
this.focusGrabber = null;
}
}
}
render() {
if (!this.props.show) return null;
return (
<Portal into="body">
<div
class={`${this.props.extraClasses || ''} modal is-modal-visible`}
ref={el => (this.overlayEl = el)}
onClick={this.onOverlayClick.bind(this)}
>
<div class="modal__content">
<button
type="button"
onClick={this.props.closeHandler}
aria-label="Close modal"
title="Close"
class="js-modal__close-btn modal__close-btn"
>
Close
</button>
{this.props.children}
</div>
</div>
</Portal>
);
}
}

View File

@@ -0,0 +1,954 @@
import { h } from 'preact';
import { A } from './common';
export function Notifications(props) {
return (
<div>
<h1>Whats new?</h1>
<div class="notification">
<span class="notification__version">3.2.0</span>
<ul>
<li>
<strong>🚀 Loop timeout setting</strong>: You now have a setting to
tweak the maximum timeout of a loop iteration before it's marked as
infinite loop.
</li>
<li>
<strong>♿️ Accessibility</strong>: Modals now have proper keyboard
navigation integrated.
</li>
<li>
<strong>♿️ Accessibility</strong>: Color contrast improvements.
</li>
<li>
🚀 Popular libraries list updated. Thanks
<a
href="https://github.com/diomed"
target="_blank"
rel="noopener noreferrer"
>
@diomed
</a>{' '}
&{' '}
<a
href="https://github.com/leninalbertolp"
target="_blank"
rel="noopener noreferrer"
>
@leninalbertolp
</a>
</li>
<li>
<strong>🔧 Bugfix</strong>: Modal take up appropriate width instead
of spanning full width.
</li>
<br />
<li>
<strong>🚀 Announcement</strong>: Hi! I am Kushagra Gour (creator of
Web Maker) and I have launched a
<a
href="https://patreon.com/kushagra"
target="_blank"
rel="noopener noreferrer"
>
Patreon campaign
</a>. If you love Web Maker, consider pledging to
<a
href="https://patreon.com/kushagra"
target="_blank"
rel="noopener noreferrer"
>
support me
</a>{' '}
:)
</li>
<li>
<a
href="https://github.com/chinchang/web-maker/issues"
target="_blank"
rel="noopener noreferrer"
>
Suggest features or report bugs.
</a>
</li>
<li>
Web Maker now has more than 50K weekly active users! Thank you for
being a part of this community of awesome developers. If you find
Web Maker helpful,
<a
href="https://chrome.google.com/webstore/detail/web-maker/lkfkkhfhhdkiemehlpkgjeojomhpccnh/reviews"
target="_blank"
rel="noopener noreferrer"
class="btn"
>
Please rate Web Maker <span class="star" />
</a>&nbsp;
<a
href="http://twitter.com/share?url=https://webmakerapp.com/&text=Web Maker - A blazing fast %26 offline web playground! via @webmakerApp&related=webmakerApp&hashtags=web,editor,chrome,extension"
target="_blank"
rel="noopener noreferrer"
class="btn"
>
Share it
</a>&nbsp;
<A
aria-label="Support the developer"
onClick={props.onSupportBtnClick}
data-event-action="supportDeveloperChangelogBtnClick"
data-event-category="ui"
class="btn btn-icon"
>
Support the developer
</A>
</li>
</ul>
</div>
<div class="notification">
<span class="notification__version">3.1.1</span>
<ul>
<li>
<strong>Bugfix</strong>: Fix the "Run" button not refreshing the
preview after release 3.0.4.
</li>
</ul>
</div>
<div class="notification">
<span class="notification__version">3.1.0</span>
<ul>
<li>
<strong>Mobile Support (app only).</strong>: Make the Web Maker app
usable on mobile. This is only for web app as Chrome extensions
don't run on mobile.
</li>
</ul>
</div>
<div class="notification">
<span class="notification__version">3.0.4</span>
<ul>
<li>
<strong>Bugfix</strong>: Guarantee code doesn't execute when "auto
preview" is off.
</li>
<li>
Add link to our new
<a
href="https://web-maker.slack.com"
target="_blank"
rel="noopener noreferrer"
>
Slack channel
</a>{' '}
🤗.
</li>
</ul>
</div>
<div class="notification">
<span class="notification__version">3.0.3</span>
<ul>
<li>
<strong>Bugfix (extension)</strong>: "Save as HTML" file saves with
correct extension.
</li>
</ul>
</div>
<div class="notification">
<span class="notification__version">3.0.1</span>
<ul>
<li>
After months of work, here is Web Maker 3.0.
<a
href="https://medium.com/web-maker/web-maker-3-0-is-here-f158a40eeaee"
target="_blank"
rel="noopener noreferrer"
>
Read the blog post about it
</a>.
</li>
<li>
Web Maker is no more just a Chrome extension, it is also available
as web app that runs offline just like the extension! Checkout it
out ->
<a
href="https://webmakerapp.com/app/"
target="_blank"
rel="noopener noreferrer"
>
https://webmakerapp.com/app/
</a>.
</li>
<li>
Now use Web Maker web app on any modern browser (tested with Chrome
and Firefox).
</li>
<li>
<strong>User Accounts</strong> - The much requested user accounts
are here. Now maintain your account and store all your creations in
the cloud and access them anywhere anytime.
</li>
<li>
<strong>New layout mode</strong> - One more layout mode, that lets
you align all the panes vertically.
</li>
<li>
<strong>No more restriction on scripts (Web app only)</strong> - If
you are using the web app, there is no more a restriction to load
scripts from only specific domains. Load any script!
</li>
<li>
<strong>Inline scripts (Web app only)</strong> - The restriction of
writing JavaScript only in JS pane is also removed.
</li>
</ul>
</div>
<div class="notification">
<span class="notification__version">2.9.7</span>
<ul>
<li>
<a
href="https://tailwindcss.com/"
target="_blank"
rel="noopener noreferrer"
>
Tailwind CSS
</a>{' '}
added to popular CSS libraries list. Thanks
<a
href="https://github.com/diomed"
target="_blank"
rel="noopener noreferrer"
>
diomed
</a>.
</li>
<li>
Popular libraries list updated. Thanks
<a
href="https://github.com/diomed"
target="_blank"
rel="noopener noreferrer"
>
diomed
</a>.
</li>
<li>
<strong>Dev</strong>: Bug fixes and code refactoring to make things
simple. Thanks
<a
href="https://github.com/iamandrewluca"
target="_blank"
rel="noopener noreferrer"
>
iamandrewluca
</a>.
</li>
</ul>
</div>
<div class="notification">
<span class="notification__version">2.9.6</span>
<ul>
<li>
<strong>Bugfix</strong>: Fix close buttons not working in
notifications and keyboard shortcuts modal.
</li>
<li>
<strong>Bugfix</strong>: Fix keyboard shortcut to see keyboard
shortcuts :) Thanks
<a
href="https://github.com/ClassicOldSong"
target="_blank"
rel="noopener noreferrer"
>
ClassicOldSong
</a>.
</li>
</ul>
</div>
<div class="notification">
<span class="notification__version">2.9.5</span>
<ul>
<li>
<a
href="https://medium.com/web-maker/release-2-9-5-add-library-search-pane-collapsing-ux-improvements-more-1085216c1301"
target="_blank"
rel="noopener noreferrer"
>
Read blog post about this release.
</a>
</li>
<li>
<strong>Keyboard shortcuts panel</strong>: Add a list of all
keyboard shotcuts. Access with
<code> Ctrl/⌘ + Shift + ?</code> or click keyboard button in footer.
</li>
<li>
<strong>Add external library</strong>: Better UX for searching third
party libraries.
</li>
<li>
<strong>Improvement</strong>: Code panes now go fullscreen when
double-clicked on their headers - which is much more intuitive
behavior based on feedback from lot of developers.
</li>
<li>
<strong>Improvement</strong>: Add
<code>allowfullscreen</code> attribute on iframes. Thanks
<a
href="https://github.com/ClassicOldSong"
target="_blank"
rel="noopener noreferrer"
>
ClassicOldSong
</a>.
</li>
<li>
<strong>Bugfix</strong> - Stop screenlog.js from showing up in the
exported HTML.
</li>
<li>
Popular external libraries list updated. Thanks
<a
href="https://github.com/jlapitan"
target="_blank"
rel="noopener noreferrer"
>
jlapitan
</a>.
</li>
</ul>
</div>
<div class="notification">
<span class="notification__version">2.9.4</span>
<ul>
<li>
<strong>Improvement</strong>: Atomic CSS (Atomizer) has been updated
to latest version. Now you can do things like psuedo elements. Learn
More.
</li>
<li>
<strong>Bugfix</strong> - Logging circular objects is now possible.
It won't show in the Web Maker console, but will show fine in
browser's console.
</li>
<li>
<strong>Bugfix</strong> - Console's z-index issue has been fixed.
</li>
</ul>
</div>
<div class="notification">
<span class="notification__version">2.9.3</span>
<ul>
<li>
Choose the save location while exporting your saved creations. Now
easily sync them to your Dropbox or any cloud storage.
</li>
<li>All modals inside the app now have a close button.</li>
<li>
Checkbox that showed on clicking a boolean value is now removed.
Thanks
<a
href="https://github.com/gauravmuk"
target="_blank"
rel="noopener noreferrer"
>
Gaurav Nanda
</a>.
</li>
<li>
<strong>Bugfix</strong> - Screenshots on retina device are now
correct. Thanks
<a
href="https://github.com/AshBardhan"
target="_blank"
rel="noopener noreferrer"
>
Ashish Bardhan
</a>.
</li>
<li>
<strong>Bugfix</strong> - Double console log in detached mode fixed.
</li>
<li>
<strong>Bugfix</strong> - Console.clear now works in detached mode
too.
</li>
<li>
<strong>Bugfix</strong> - DOCTYPE added in preview.
</li>
<li>
<strong>Bugfix</strong> - Typo correction in README. Thanks
<a
href="https://github.com/AdilMah"
target="_blank"
rel="noopener noreferrer"
>
Adil Mahmood
</a>.
</li>
<li>gstatic.com is available to load external JavaScripts from.</li>
<li>
Popular libraries list updated. Thanks
<a
href="https://github.com/diomed"
target="_blank"
rel="noopener noreferrer"
>
diomed
</a>.
</li>
<li>
Added
<a
href="https://github.com/chinchang/web-maker/blob/master/CONTRIBUTING.md"
target="_blank"
rel="noopener noreferrer"
>
contribution guidelines
</a>{' '}
in the Github repository.
</li>
</ul>
</div>
<div class="notification">
<span class="notification__version">2.9.2</span>
<ul>
<li>Minor bug fixes.</li>
</ul>
</div>
<div class="notification">
<span class="notification__version">2.9.1</span>
<ul>
<li>
<a
href="https://medium.com/web-maker/v2-9-lots-of-goodies-bd1e939571f6"
target="_blank"
rel="noopener noreferrer"
>
Read blog post about last release.
</a>
</li>
<li>
Use Ctrl/Cmd+D to select next occurence of matching selection.
</li>
<li>Improve onboard experience.</li>
</ul>
</div>
<div class="notification">
<span class="notification__version">2.9.0</span>
<ul>
<li>
<a
href="https://medium.com/web-maker/v2-9-lots-of-goodies-bd1e939571f6"
target="_blank"
rel="noopener noreferrer"
>
Read blog post about this release.
</a>
</li>
<li>
<strong>Detached Preview</strong> - Yes, you read that correct! You
can now detach your preview and send it to your secondary monitor.
</li>
<li>
<strong>Find & Replace</strong> - Long awaited, now its there.
Ctrl/Cmd+f to find and add Alt to replace.
</li>
<li>
<strong>Atomic CSS (Atomizer) configurations</strong> - Add custom
config for Atomic CSS.
<a
href="https://github.com/acss-io/atomizer#api"
target="_blank"
rel="noopener noreferrer"
>
Read more
</a>.
</li>
<li>
<strong>Light mode</strong> - This new setting makes Web Maker drop
some heavy effects like blur etc to gain more performance. Thanks
<a
href="https://github.com/iamandrewluca"
target="_blank"
rel="noopener noreferrer"
>
Andrew
</a>.
</li>
<li>
<strong>Preserve logs setting</strong> - Choose whether or not to
preserve logs across preview refreshes. Thanks
<a
href="https://github.com/BasitAli"
target="_blank"
rel="noopener noreferrer"
>
Basit
</a>.
</li>
<li>
<strong>Line wrap setting</strong> - As the name says.
</li>
<li>Semantic UI added to popular libraries.</li>
<li>
Bootstrap, Vue, UI-Kit and more updated to latest versions in
popular libraries.
</li>
<li>UX improvements in settings UI</li>
<li>
<strong>Bugfix</strong> - Trigger preview refresh anytime with
Ctrl/ + Shift + 5. Even with auto-preview on.
</li>
</ul>
</div>
<div class="notification">
<span class="notification__version">2.8.1</span>
<ul>
<li>
Vue.js & UIKit version updated to latest version in 'Add Library'
list.
</li>
<li>
UTF-8 charset added to preview HTML to support universal characters.
</li>
</ul>
</div>
<div class="notification">
<span class="notification__version">2.8.0</span>
<ul>
<li>
<a
href="https://medium.com/web-maker/release-v2-8-is-out-f44e6ea5d9c4"
target="_blank"
rel="noopener noreferrer"
>
Read blog post about this release.
</a>
</li>
<li>
<strong>Auto Save</strong> - Your creations now auto-save after your
first manual save. This is configurable from settings.
</li>
<li>
<strong>Base2Tone-Meadow Editor Theme</strong> - First user
contributed theme. Thanks to Diomed.
</li>
<li>
<strong>Use System Fonts</strong> - You can now use any of your
existing system fonts in the editor!
</li>
<li>
<strong>Matching Tag Highlight</strong> - Cursor over any HTML tag
would highlight the matching pair tag.
</li>
<li>
Auto-completion suggestion can now be switched off from settings.
</li>
<li>
<strong>Improvement</strong> - Stop white flicker in editor when the
app opens.
</li>
<li>
<strong>Bugfix</strong> - Add Babel Polyfill to enable use of
next-gen built-ins like Promise or WeakMap.
</li>
<li>Vue.js version updated to 2.4.0 in popular library list.</li>
<li>
Downloads permission is optional. Asked only when you take
screenshot.
</li>
</ul>
</div>
<div class="notification">
<span class="notification__version">2.7.2</span>
<ul>
<li>
<strong>External Libraries</strong> - Add Foundation.js and update
UIKit 3 to latest beta.
</li>
<li>
<strong>rawgit.com</strong> &
<strong>wzrd.in</strong> domains are now allowed for loading
external libraries from.
</li>
<li>Minor booting speed improvements</li>
</ul>
</div>
<div class="notification">
<span class="notification__version">2.7.1</span>
<ul>
<li>
<strong>Framer.js support</strong> - You can now load the latest
framer.js library from
<a
href="https://builds.framerjs.com/"
target="_blank"
rel="noopener noreferrer"
>
framer builds page
</a>{' '}
and start coding framer prototypes.
</li>
<li>
<strong>Bugfix</strong>: Edit on CodePen is back in action.
</li>
<li>
<strong>Bugfix</strong>: Autocompletion menu doesn't show on cut and
paste now.
</li>
<li>
<strong>Bugfix</strong>: Updated & fixed urls of some common
external libraries to latest versions. UIKit3 & Bootstrap 4α are now
in the list.
</li>
<li>Preprocessor selector are now more accessible.</li>
</ul>
</div>
<div class="notification">
<span class="notification__version">2.7.0</span>
<ul>
<li>
<strong>Fork any creation!</strong>: Now you can fork any existing
creation of yours to start a new work based on it. One big use case
of this feature is "Templates"!
<a
target="_blank"
rel="noopener noreferrer"
href="https://kushagragour.in/blog/2017/05/web-maker-fork-templates/?utm_source=webmakerapp&utm_medium=referral"
>
Read more about it
</a>.
</li>
<li>
<strong>Fonts 😍 </strong>: Super-awesome 4 fonts (mostly with
ligature support) now available to choose from. Fira Code is the
default font now.
</li>
<li>Updated most used external libraries to latest versions.</li>
<li>
<strong>Bugfix</strong>: Add missing Bootstrap JS file to most used
external libraries list.
</li>
<li>
Several other minor bugfixes and improvements to make Web Maker
awesome!
</li>
<li>
Great news to share with you - Web Maker has been featured on the
Chrome Webstore homepage! Thanks for all the love :)
</li>
</ul>
</div>
<div class="notification">
<span class="notification__version">2.6.1</span>
<ul>
<li>
<strong>Bugfix</strong>: Emojis vanishing while exporting to Codepen
has been fixed.
</li>
<li>
<strong>Bugfix</strong>:
<code>console.clear()</code> now doesn't error and clears the
inbuilt console.
</li>
<li>
<strong>Bugfix</strong>: External libraries added to the creation
are exported as well to Codepen.
</li>
</ul>
</div>
<div class="notification">
<span class="notification__version">2.6.0</span>
<ul>
<li>
<strong>The "Console"</strong>: The most awaited feature is here!
There is now an inbuilt console to see your logs, errors and for
quickly evaluating JavaScript code inside your preview. Enjoy! I
also a
<a
href="https://kushagragour.in/blog/2017/05/web-maker-console-is-here/?utm_source=webmakerapp&utm_medium=referral"
target="_blank"
rel="noopener noreferrer"
>
blog post about it
</a>.
</li>
<li>
Number slider which popped on clicking any number in the code has
been removed due to poor user experience.
</li>
<li>Minor usability improvements.</li>
</ul>
</div>
<div class="notification">
<span class="notification__version">2.5.0</span>
<ul>
<li>
<strong>Atomic CSS</strong>: Use can now use Atomic CSS(ACSS) in
your work!
<a
href="https://acss.io/"
target="_blank"
rel="noopener noreferrer"
>
Read more about it here
</a>.
</li>
<li>
<strong>Search your saved creations</strong>: Easily search through
all your saved creations by title.
</li>
<li>
<strong>Configurable Auto-preview</strong> - You can turn off the
auto preview in settings if you don't want the preview to update as
you type.
</li>
<li>
<strong>Configurable refresh on resize</strong> - You can configure
whether you want the preview to refresh when you resize the preview
panel.
</li>
<li>
<strong>Bugfix</strong> - Fix indentation
<a
href="https://github.com/chinchang/web-maker/issues/104"
target="_blank"
rel="noopener noreferrer"
>
issue
</a>{' '}
with custom indentation size.
</li>
</ul>
</div>
<div class="notification">
<span class="notification__version">2.4.2</span>
<ul>
<li>
<strong>Improved infinite loop protection</strong>: Infinite loop
protection is now faster and more reliable. And works without the
need of Escodegen. Thanks to Ariya Hidayat!
</li>
<li>
<strong>Bugfix</strong> - Default parameters not working in
JavaScript is fixed.
</li>
</ul>
</div>
<div class="notification">
<span class="notification__version">2.4.0</span>
<ul>
<li>
<strong>Import/Export</strong>: Your creations are most important.
Now export all your creations into a single file as a backup that
can be imported anytime & anywhere.
</li>
<li>
<strong>Editor themes</strong>: You have been heard. Now you can
choose from a huge list of wonderful editor themes!
</li>
<li>
<strong>Identation settings</strong>: Not a spaces fan? Switch to
tabs and set your indentation size.
</li>
<li>
<strong>Vim key bindings</strong>: Rejoice Vim lovers!
</li>
<li>
<strong>Code blast</strong>: Why don't you try coding with this
switched on from the settings? Go on...
</li>
<li>
<strong>Important</strong>: Due to security policy changes from
Chrome 57 onwards, Web Maker now allows loading external JavaScript
libraries only from certain whitelisted domains (localhost,
https://ajax.googleapis.com, https://code.jquery.com,
https://cdnjs.cloudflare.com, https://unpkg.com, https://maxcdn.com,
https://cdn77.com, https://maxcdn.bootstrapcdn.com,
https://cdn.jsdelivr.net/)
</li>
<li>Save button now highlights when you have unsaved changes.</li>
<li>Jade is now called Pug. Just a name change.</li>
</ul>
</div>
<div class="notification">
<span class="notification__version">2.3.2</span>
<ul>
<li>Update Babel to support latest and coolest ES6 features.</li>
<li>Improve onboarding experience at first install.</li>
</ul>
</div>
<div class="notification">
<span class="notification__version">2.3.1</span>
<ul>
<li>
<strong>Bugfix</strong> - Splitting of code and preview panes is
remembered by the editor.
</li>
<li>
Title of the creation is used for the file name when saving as HTML.
</li>
</ul>
</div>
<div class="notification">
<span class="notification__version">2.3.0</span>
<ul>
<li>
<strong>Add Library Autocompletion</strong> - Just start typing the
name of library and you'll be shown matching libraries from cdnjs.
</li>
<li>
<strong>Preview Screenshot Capture</strong> - Want to grab a nice
screenshot of your creation. You have it! Click and capture.
</li>
<li>
<strong>Auto Indent Code</strong> - Select your code and hit
Shift-Tab to auto-indent it!
</li>
<li>
<strong>Keyboard Navigation in Saved List</strong> - Now select your
creation using arrow keys and hit ENTER to open it.
</li>
<li>Highlight active line in code panes.</li>
<li>
<strong>Bugfix</strong> - Fix in generated title of new creation.
</li>
<li>
<strong>Bugfix</strong> - HTML autocompletion is manual triggered
now with Ctrl+Space.
</li>
</ul>
</div>
<div class="notification">
<span class="notification__version">2.2.0</span>
<ul>
<li>
<strong>Code Autocompletion</strong> - See code suggestions while
you type!
</li>
<li>
<strong>Full Screen Preview</strong> - Checkout your creation in a
full-screen layout.
</li>
<li>
<strong>SASS</strong> - SASS support added for CSS.
</li>
<li>
<strong>Faster CSS update</strong> - Preview updates instantly
without refresh when just CSS is changed.
</li>
<li>
<strong>Bugfix</strong> - Indentation fixed while going on new line.
</li>
<li>
<strong>Bugfix</strong> - Works even in Chrome Canary now. Though
libraries can be added only through CDNs.
</li>
</ul>
</div>
<div class="notification">
<span class="notification__version">2.1.0</span>
<ul>
<li>
<strong>TypeScript</strong> - Now you can code in TypeScript too!
</li>
<li>
<strong>Stylus Preprocessor</strong> - Stylus supported adding for
CSS.
</li>
<li>
<strong>Code Folding</strong> - Collapse large code blocks for easy
editing.
</li>
<li>
<strong>Bugfix</strong> - Support JSX in JavaScript.
</li>
<li>Better onboarding for first time users.</li>
</ul>
</div>
<div class="notification">
<span class="notification__version">2.0.0</span>
<ul>
<li>
<strong>Save and Load</strong> - Long pending and super-useful, now
you can save your creations and resume them anytime later.
</li>
<li>
<strong>Insert JS & CSS</strong> - Load popular JavaScript & CSS
libraries in your work without writing any code.
</li>
<li>
<strong>Collapsed Panes</strong> - Collapse/uncollapse code panes
with a single click. Your pane configuration is even saved with
every creation!
</li>
<li>
<strong>Quick color & number change</strong> - Click on any color or
number and experiment with quick values using a slider.
</li>
<li>
<strong>Linting</strong> - See your code errors right where you are
coding.
</li>
<li>No more browser hang due to infinite loops!</li>
</ul>
</div>
<div class="notification">
<span class="notification__version">1.7.0</span>
<ul>
<li>
<strong>Preprocessors!</strong> - Enjoy a whole list of
preprocessors for HTML(Jade & markdown), CSS(SCSS & LESS) and
JavaScript(CoffeeScript & Babel).
</li>
<li>More awesome font for code.</li>
</ul>
</div>
<div class="notification">
<span class="notification__version">1.6.0</span>
<ul>
<li>
You can now configure Web-Maker to not replace new tab page from the
settings. It is always accessible from the icon in the top-right.
</li>
<li>
Download current code as HTML file with Ctrl/ + S keyboard
shortcut.
</li>
<li>
New notifications panel added so you are always aware of the new
changes in Web-Maker.
</li>
</ul>
</div>
</div>
);
}

View File

@@ -0,0 +1,103 @@
import { h } from 'preact';
import Modal from './Modal.jsx';
export function OnboardingModal(props) {
return (
<Modal show={props.show} closeHandler={props.closeHandler}>
<div class="tac">
<svg width="130px" height="50px" aria-hidden="true">
<use xlinkHref="#logo" />
</svg>
<h1 style="margin-top:20px">Welcome to Web Maker</h1>
</div>
<div class="flex--desk" style="margin-top:40px;">
<div class="onboard-step show-when-app hide-on-mobile">
<div class="tac">
<svg class="onboard-step__icon" viewBox="0 0 24 24">
<path d="M13.64,21.97C13.14,22.21 12.54,22 12.31,21.5L10.13,16.76L7.62,18.78C7.45,18.92 7.24,19 7,19A1,1 0 0,1 6,18V3A1,1 0 0,1 7,2C7.24,2 7.47,2.09 7.64,2.23L7.65,2.22L19.14,11.86C19.57,12.22 19.62,12.85 19.27,13.27C19.12,13.45 18.91,13.57 18.7,13.61L15.54,14.23L17.74,18.96C18,19.46 17.76,20.05 17.26,20.28L13.64,21.97Z" />
</svg>
</div>
<p>
Open Web Maker anytime by visiting{' '}
<a>https://webmakerapp.com/app/</a> - Even when you are offline! It
just works! 😱 <strong>Drag the following bookmarklet</strong> on
your bookmark bar to create a quick access shortcut:
<a class="ml-1 bookmarklet" href="https://webmakerapp.com/app/">
<svg width="20" height="20" aria-hidden="true">
<use xlinkHref="#logo" />
</svg>
Web Maker
</a>
</p>
</div>
<div class="onboard-step show-when-extension">
<div class="tac">
<svg class="onboard-step__icon" viewBox="0 0 24 24">
<path d="M13.64,21.97C13.14,22.21 12.54,22 12.31,21.5L10.13,16.76L7.62,18.78C7.45,18.92 7.24,19 7,19A1,1 0 0,1 6,18V3A1,1 0 0,1 7,2C7.24,2 7.47,2.09 7.64,2.23L7.65,2.22L19.14,11.86C19.57,12.22 19.62,12.85 19.27,13.27C19.12,13.45 18.91,13.57 18.7,13.61L15.54,14.23L17.74,18.96C18,19.46 17.76,20.05 17.26,20.28L13.64,21.97Z" />
</svg>
</div>
<p>
Open Web Maker anytime by clicking the
<svg class="relative" style="top:5px;" width="40" height="30">
<use xlinkHref="#logo" />
</svg>{' '}
button in top-right side of your browser.
</p>
</div>
<div class="onboard-step">
<div class="tac">
<svg class="onboard-step__icon" viewBox="0 0 24 24">
<use xlinkHref="#settings-icon" />
</svg>
</div>
<p>
Configure and customize settings by clicking the gear icon (
<svg
style="width:18px;height:18px;position:relative;top:3px;fill:#888"
viewBox="0 0 24 24"
>
<use xlinkHref="#settings-icon" />
</svg>) in bottom right of the app.
</p>
</div>
<div class="onboard-step">
<div class="tac">
<svg class="onboard-step__icon" style="stroke-width:0.3px;">
<use xlinkHref="#twitter-icon" />
</svg>
</div>
<p>
Follow{' '}
<a
href="https://twitter.com/intent/follow?screen_name=webmakerApp"
targe="_blank"
rel="noopener noreferrer"
>
@webmakerApp
</a>{' '}
to know about the new upcoming features!
</p>
</div>
</div>
<p class="tac show-when-app">
If you are an existing Chrome extension user, you can import your
creations from there to here.{' '}
<a
href="https://medium.com/web-maker/importing-exporting-your-creations-d92e7de5c3dc"
target="_blank"
rel="noopener noreferrer"
>
Learn how to export/import
</a>.
</p>
<p class="tac">
<button class="btn btn--primary" onClick={props.closeHandler}>
Lets start!
</button>
</p>
</Modal>
);
}

View File

@@ -0,0 +1,30 @@
import { h } from 'preact';
const DEFAULT_PROFILE_IMG =
"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath fill='#ccc' d='M12,19.2C9.5,19.2 7.29,17.92 6,16C6.03,14 10,12.9 12,12.9C14,12.9 17.97,14 18,16C16.71,17.92 14.5,19.2 12,19.2M12,5A3,3 0 0,1 15,8A3,3 0 0,1 12,11A3,3 0 0,1 9,8A3,3 0 0,1 12,5M12,2A10,10 0 0,0 2,12A10,10 0 0,0 12,22A10,10 0 0,0 22,12C22,6.47 17.5,2 12,2Z' /%3E%3C/svg%3E";
export function Profile({ user, logoutBtnHandler }) {
return (
<div class="tac">
<img
height="80"
class="profile-modal__avatar-img"
src={user ? user.photoURL || DEFAULT_PROFILE_IMG : ''}
id="profileAvatarImg"
alt="Profile image"
/>
<h3 id="profileUserName" class="mb-2">
{user && user.displayName ? user.displayName : 'Anonymous Creator'}
</h3>
<p>
<button
class="btn"
aria-label="Logout from your account"
onClick={logoutBtnHandler}
>
Logout
</button>
</p>
</div>
);
}

View File

@@ -0,0 +1,269 @@
import { h, Component } from 'preact';
import { log, getHumanDate } from '../utils';
import { trackEvent } from '../analytics';
import { itemService } from '../itemService';
import { alertsService } from '../notifications';
import { deferred } from '../deferred';
export default class SavedItemPane extends Component {
constructor(props) {
super(props);
this.items = [];
this.state = {
filteredItems: []
};
}
componentWillUpdate(nextProps) {
if (this.props.items !== nextProps.items) {
this.items = Object.values(nextProps.items);
this.items.sort(function(a, b) {
return b.updatedOn - a.updatedOn;
});
this.setState({
filteredItems: this.items
});
}
}
componentDidUpdate(prevProps) {
if (this.props.isOpen && !prevProps.isOpen) {
window.searchInput.value = '';
}
}
onCloseIntent() {
this.props.closeHandler();
}
itemClickHandler(item) {
this.props.itemClickHandler(item);
}
itemRemoveBtnClickHandler(item, e) {
e.stopPropagation();
this.props.itemRemoveBtnClickHandler(item);
}
itemForkBtnClickHandler(item, e) {
e.stopPropagation();
this.props.itemForkBtnClickHandler(item);
}
keyDownHandler(event) {
if (!this.props.isOpen) {
return;
}
const isCtrlOrMetaPressed = event.ctrlKey || event.metaKey;
const isForkKeyPressed = isCtrlOrMetaPressed && event.keyCode === 70;
const isDownKeyPressed = event.keyCode === 40;
const isUpKeyPressed = event.keyCode === 38;
const isEnterKeyPressed = event.keyCode === 13;
const selectedItemElement = $('.js-saved-item-tile.selected');
const havePaneItems = $all('.js-saved-item-tile').length !== 0;
if ((isDownKeyPressed || isUpKeyPressed) && havePaneItems) {
const method = isDownKeyPressed ? 'nextUntil' : 'previousUntil';
if (selectedItemElement) {
selectedItemElement.classList.remove('selected');
selectedItemElement[method](
'.js-saved-item-tile:not(.hide)'
).classList.add('selected');
} else {
$('.js-saved-item-tile:not(.hide)').classList.add('selected');
}
$('.js-saved-item-tile.selected').scrollIntoView(false);
}
if (isEnterKeyPressed && selectedItemElement) {
const item = this.props.items[selectedItemElement.dataset.itemId];
console.log('opening', item);
this.props.itemClickHandler(item);
trackEvent('ui', 'openItemKeyboardShortcut');
}
// Fork shortcut inside saved creations panel with Ctrl/⌘ + F
if (isForkKeyPressed) {
event.preventDefault();
const item = this.props.items[selectedItemElement.dataset.itemId];
this.props.itemForkBtnClickHandler(item);
trackEvent('ui', 'forkKeyboardShortcut');
}
}
mergeImportedItems(items) {
var existingItemIds = [];
var toMergeItems = {};
const d = deferred();
const savedItems = {};
this.items.forEach(item => (savedItems[item.id] = item));
items.forEach(item => {
// We can access `savedItems` here because this gets set when user
// opens the saved creations panel. And import option is available
// inside the saved items panel.
if (savedItems[item.id]) {
// Item already exists
existingItemIds.push(item.id);
} else {
log('merging', item.id);
toMergeItems[item.id] = item;
}
});
var mergedItemCount = items.length - existingItemIds.length;
if (existingItemIds.length) {
var shouldReplace = confirm(
existingItemIds.length +
' creations already exist. Do you want to replace them?'
);
if (shouldReplace) {
log('shouldreplace', shouldReplace);
items.forEach(item => {
toMergeItems[item.id] = item;
});
mergedItemCount = items.length;
}
}
if (mergedItemCount) {
itemService.saveItems(toMergeItems).then(() => {
d.resolve();
alertsService.add(
mergedItemCount + ' creations imported successfully.'
);
trackEvent('fn', 'itemsImported', mergedItemCount);
});
} else {
d.resolve();
}
this.props.closeHandler();
return d.promise;
}
importFileChangeHandler(e) {
var file = e.target.files[0];
var reader = new FileReader();
reader.addEventListener('load', progressEvent => {
var items;
try {
items = JSON.parse(progressEvent.target.result);
log(items);
this.mergeImportedItems(items);
} catch (exception) {
log(exception);
alert(
'Oops! Selected file is corrupted. Please select a file that was generated by clicking the "Export" button.'
);
}
});
reader.readAsText(file, 'utf-8');
}
importBtnClickHandler(e) {
var input = document.createElement('input');
input.type = 'file';
input.style.display = 'none';
input.accept = 'accept="application/json';
document.body.appendChild(input);
input.addEventListener('change', this.importFileChangeHandler.bind(this));
input.click();
trackEvent('ui', 'importBtnClicked');
e.preventDefault();
}
searchInputHandler(e) {
const text = e.target.value;
if (!text) {
this.setState({
filteredItems: this.items
});
} else {
this.setState({
filteredItems: this.items.filter(
item => item.title.toLowerCase().indexOf(text) !== -1
)
});
}
trackEvent('ui', 'searchInputType');
}
render() {
return (
<div
id="js-saved-items-pane"
class={`saved-items-pane ${this.props.isOpen ? 'is-open' : ''}`}
onKeyDown={this.keyDownHandler.bind(this)}
>
<button
onClick={this.onCloseIntent.bind(this)}
class="btn saved-items-pane__close-btn"
id="js-saved-items-pane-close-btn"
>
X
</button>
<div class="flex flex-v-center" style="justify-content: space-between;">
<h3>My Library ({this.items.length})</h3>
<div class="main-header__btn-wrap">
<a
onClick={this.props.exportBtnClickHandler}
href=""
class="btn btn-icon hint--bottom-left hint--rounded hint--medium"
aria-label="Export all your creations into a single importable file."
>
Export
</a>
<a
onClick={this.importBtnClickHandler.bind(this)}
href=""
class="btn btn-icon hint--bottom-left hint--rounded hint--medium"
aria-label="Only the file that you export through the 'Export' button can be imported."
>
Import
</a>
</div>
</div>
<input
id="searchInput"
class="search-input"
onInput={this.searchInputHandler.bind(this)}
placeholder="Search your creations here..."
/>
<div id="js-saved-items-wrap" class="saved-items-pane__container">
{!this.state.filteredItems.length && this.items.length ? (
<div class="mt-1">No match found.</div>
) : null}
{this.state.filteredItems.map(item => (
<div
class="js-saved-item-tile saved-item-tile"
data-item-id={item.id}
onClick={this.itemClickHandler.bind(this, item)}
>
<div class="saved-item-tile__btns">
<a
class="js-saved-item-tile__fork-btn saved-item-tile__btn hint--left hint--medium"
aria-label="Creates a duplicate of this creation (Ctrl/⌘ + F)"
onClick={this.itemForkBtnClickHandler.bind(this, item)}
>
Fork<span class="show-when-selected">(Ctrl/ + F)</span>
</a>
<a
class="js-saved-item-tile__remove-btn saved-item-tile__btn hint--left"
aria-label="Remove"
onClick={this.itemRemoveBtnClickHandler.bind(this, item)}
>
X
</a>
</div>
<h3 class="saved-item-tile__title">{item.title}</h3>
<span class="saved-item-tile__meta">
Last updated: {getHumanDate(item.updatedOn)}
</span>
</div>
))}
{!this.items.length ? (
<h2 class="opacity--30">Nothing saved here.</h2>
) : null}
</div>
</div>
);
}
}

317
src/components/Settings.jsx Normal file
View File

@@ -0,0 +1,317 @@
import { h, Component } from 'preact';
import { editorThemes } from '../editorThemes';
function CheckboxSetting({
title,
label,
onChange,
pref,
name,
showWhenExtension
}) {
return (
<label
class={`line ${showWhenExtension ? 'show-when-extension' : ''} `}
title={title}
>
<input
type="checkbox"
checked={pref}
onChange={onChange}
data-setting={name}
/>{' '}
{label}
</label>
);
}
export default class Settings extends Component {
updateSetting(e) {
this.props.onChange(e);
}
shouldComponentUpdate() {
// TODO: add check on prefs
return true;
}
render() {
return (
<div>
<h1>Settings</h1>
<h3>Indentation</h3>
<div
class="line"
title="I know this is tough, but you have to decide one!"
>
<label>
<input
type="radio"
name="indentation"
value="spaces"
checked={this.props.prefs.indentation === 'spaces'}
onChange={this.updateSetting.bind(this)}
data-setting="indentWith"
/>{' '}
Spaces
</label>
<label class="ml-1">
<input
type="radio"
name="indentation"
value="tabs"
checked={this.props.prefs.indentation === 'tabs'}
onChange={this.updateSetting.bind(this)}
data-setting="indentWith"
/>{' '}
Tabs
</label>
</div>
<label class="line" title="">
Indentation Size{' '}
<input
type="range"
class="va-m ml-1"
value={this.props.prefs.indentSize}
min="1"
max="7"
list="indentationSizeList"
data-setting="indentSize"
onChange={this.updateSetting.bind(this)}
/>
<span id="indentationSizeValueEl">{this.props.prefs.indentSize}</span>
<datalist id="indentationSizeList">
<option>1</option>
<option>2</option>
<option>3</option>
<option>4</option>
<option>5</option>
<option>6</option>
<option>7</option>
</datalist>
</label>
<hr />
<h3>Editor</h3>
<div class="flex block--mobile">
<div>
<label class="line">Default Preprocessors</label>
<div class="flex line">
<select
style="flex:1;margin-left:20px"
data-setting="htmlMode"
value={this.props.prefs.htmlMode}
onChange={this.updateSetting.bind(this)}
>
<option value="html">HTML</option>
<option value="markdown">Markdown</option>
<option value="jade">Pug</option>
</select>
<select
style="flex:1;margin-left:20px"
data-setting="cssMode"
value={this.props.prefs.cssMode}
onChange={this.updateSetting.bind(this)}
>
<option value="css">CSS</option>
<option value="scss">SCSS</option>
<option value="sass">SASS</option>
<option value="less">LESS</option>
<option value="stylus">Stylus</option>
<option value="acss">Atomic CSS</option>
</select>
<select
style="flex:1;margin-left:20px"
data-setting="jsMode"
value={this.props.prefs.jsMode}
onChange={this.updateSetting.bind(this)}
>
<option value="js">JS</option>
<option value="coffee">CoffeeScript</option>
<option value="es6">ES6 (Babel)</option>
<option value="typescript">TypeScript</option>
</select>
</div>
<label class="line">
Theme
<select
style="flex:1;margin:0 20px"
data-setting="editorTheme"
value={this.props.prefs.editorTheme}
onChange={this.updateSetting.bind(this)}
>
{editorThemes.map(theme => (
<option value={theme}>{theme}</option>
))}
</select>
</label>
<label class="line">
Font
<select
style="flex:1;margin:0 20px"
data-setting="editorFont"
value={this.props.prefs.editorFont}
onChange={this.updateSetting.bind(this)}
>
<option value="FiraCode">Fira Code</option>
<option value="Inconsolata">Inconsolata</option>
<option value="Monoid">Monoid</option>
<option value="FixedSys">FixedSys</option>
<option disabled="disabled">----</option>
<option value="other">Other font from system</option>
</select>
{this.props.prefs.editorFont === 'other' && (
<input
id="customEditorFontInput"
type="text"
value={this.props.prefs.editorCustomFont}
placeholder="Custom font name here"
data-setting="editorCustomFont"
onChange={this.updateSetting.bind(this)}
/>
)}
</label>
<label class="line">
Font Size{' '}
<input
style="width:70px"
type="number"
value={this.props.prefs.fontSize}
data-setting="fontSize"
onChange={this.updateSetting.bind(this)}
/>{' '}
px
</label>
<div class="line">
Key bindings
<label class="ml-1">
<input
type="radio"
name="keymap"
value="sublime"
checked={this.props.prefs.keymap === 'sublime'}
data-setting="keymap"
onChange={this.updateSetting.bind(this)}
/>{' '}
Sublime
</label>
<label class="ml-1">
<input
type="radio"
name="keymap"
value="vim"
checked={this.props.prefs.keymap === 'vim'}
data-setting="keymap"
onChange={this.updateSetting.bind(this)}
/>{' '}
Vim
</label>
</div>
</div>
<div class="ml-2 ml-0--mobile">
<CheckboxSetting
name="lineWrap"
title="Toggle wrapping of long sentences onto new line"
label="Line wrap"
pref={this.props.prefs.lineWrap}
onChange={this.updateSetting.bind(this)}
/>
<CheckboxSetting
name="refreshOnResize"
title="Your Preview will refresh when you resize the preview split"
label="Refresh preview on resize"
pref={this.props.prefs.refreshOnResize}
onChange={this.updateSetting.bind(this)}
/>
<CheckboxSetting
name="autoComplete"
title="Turns on the auto-completion suggestions as you type"
label="Auto-complete suggestions"
pref={this.props.prefs.autoComplete}
onChange={this.updateSetting.bind(this)}
/>
<CheckboxSetting
name="autoPreview"
title="Refreshes the preview as you code. Otherwise use the Run button"
label="Auto-preview"
pref={this.props.prefs.autoPreview}
onChange={this.updateSetting.bind(this)}
/>
<CheckboxSetting
name="autoSave"
title="Auto-save keeps saving your code at regular intervals after you hit the first save manually"
label="Auto-save"
pref={this.props.prefs.autoSave}
onChange={this.updateSetting.bind(this)}
/>
<CheckboxSetting
name="preserveLastCode"
title="Loads the last open creation when app starts"
label="Preserve last written code"
pref={this.props.prefs.preserveLastCode}
onChange={this.updateSetting.bind(this)}
/>
<CheckboxSetting
name="replaceNewTab"
title="Turning this on will start showing Web Maker in every new tab you open"
label="Replace new tab page"
pref={this.props.prefs.replaceNewTab}
onChange={this.updateSetting.bind(this)}
showWhenExtension
/>
<CheckboxSetting
name="preserveConsoleLogs"
title="Preserves the console logs across your preview refreshes"
label="Preserve console logs"
pref={this.props.prefs.preserveConsoleLogs}
onChange={this.updateSetting.bind(this)}
/>
<CheckboxSetting
name="lightVersion"
title="Switch to lighter version for better performance. Removes things like blur etc."
label="Fast/light version"
pref={this.props.prefs.lightVersion}
onChange={this.updateSetting.bind(this)}
/>
</div>
</div>
<hr />
<h3>Fun</h3>
<p>
<CheckboxSetting
title="Enjoy wonderful particle blasts while you type"
label="Code blast!"
name="isCodeBlastOn"
pref={this.props.prefs.isCodeBlastOn}
onChange={this.updateSetting.bind(this)}
/>
</p>
<hr />
<h3>Advanced</h3>
<p>
<label
class="line"
title="This timeout is used to indentify a possible infinite loop and prevent it."
>
Maximum time allowed in a loop iteration
<input
type="number"
value={this.props.prefs.infiniteLoopTimeout}
data-setting="infiniteLoopTimeout"
onChange={this.updateSetting.bind(this)}
/>{' '}
ms
</label>
<div class="help-text">
If any loop iteration takes more than the defined time, it is
detected as a potential infinite loop and further iterations are
stopped.
</div>
</p>
</div>
);
}
}

View File

@@ -0,0 +1,54 @@
import { h, Component } from 'preact';
import Split from 'split.js';
export class SplitPane extends Component {
// shouldComponentUpdate(nextProps, nextState) {
// return (
// nextProps.direction !== this.props.direction ||
// nextProps.sizes.join('') !== this.props.sizes.join('')
// );
// }
componentDidMount() {
this.updateSplit();
}
componentWillUpdate() {
if (this.splitInstance) {
this.splitInstance.destroy();
}
}
componentDidUpdate() {
this.updateSplit();
}
updateSplit() {
const options = {
direction: this.props.direction,
minSize: this.props.minSize,
gutterSize: 6,
sizes: this.props.sizes
};
if (this.props.onDragEnd) {
options.onDragEnd = this.props.onDragEnd;
}
if (this.props.onDragStart) {
options.onDragStart = this.props.onDragStart;
}
/* eslint-disable new-cap */
this.splitInstance = Split(
this.props.children.map(node => '#' + node.attributes.id),
options
);
/* eslint-enable new-cap */
if (this.props.onSplit) {
this.props.onSplit(this.splitInstance);
}
}
render() {
/* eslint-disable no-unused-vars */
const { children, ...props } = this.props;
/* eslint-enable no-unused-vars */
return <div {...props}>{this.props.children}</div>;
}
}

View File

@@ -0,0 +1,62 @@
import { h } from 'preact';
import Modal from './Modal';
export function SupportDeveloperModal({ show, closeHandler }) {
return (
<Modal extraClasses="pledge-modal" show={show} closeHandler={closeHandler}>
<div class="tac">
<h1>Support the Developer</h1>
<p>
Hi,{' '}
<a
href="https://kushagragour.in"
target="_blank"
rel="noopener noreferrer"
>
Kushagra
</a>{' '}
here! Web Maker is a free and open-source project. To keep myself
motivated for working on such open-source and free{' '}
<a
href="https://kushagragour.in/lab/"
target="_blank"
rel="noopener noreferrer"
>
side projects
</a>, I am accepting donations. Your pledge, no matter how small, will
act as an appreciation towards my work and keep me going forward
making Web Maker more awesome🔥. So please consider donating. 🙏🏼
(could be as small as $1/month).
</p>
<div
class="flex flex-h-center"
id="onboardDontShowInTabOptionBtn"
d-click="onDontShowInTabClicked"
>
<a
class="onboard-selection"
href="https://patreon.com/kushagra"
target="_blank"
rel="noopener noreferrer"
aria-label="Make a monthly pledge on Patreon"
>
<img src="patreon.png" height="60" alt="Become a patron image" />
<h3 class="onboard-selection-text">
Make a monthly pledge on Patreon
</h3>
</a>
</div>
<a
href="https://www.paypal.me/kushagragour"
target="_blank"
rel="noopener noreferrer"
aria-label="Make a one time donation on Paypal"
>
Or, make a one time donation
</a>
</div>
</Modal>
);
}

View File

@@ -0,0 +1,144 @@
import { h, Component } from 'preact';
import CodeMirror from '../CodeMirror';
import 'codemirror/addon/edit/matchbrackets.js';
import 'codemirror/addon/edit/matchtags.js';
import 'codemirror/addon/edit/closebrackets.js';
import 'codemirror/addon/edit/closetag.js';
import 'codemirror/addon/comment/comment.js';
import 'codemirror/addon/fold/foldcode.js';
import 'codemirror/addon/fold/foldgutter.js';
import 'codemirror/addon/fold/xml-fold.js';
import 'codemirror/addon/fold/indent-fold.js';
import 'codemirror/addon/fold/comment-fold.js';
import 'codemirror/addon/fold/brace-fold.js';
import 'codemirror/addon/hint/show-hint.js';
import 'codemirror/addon/hint/javascript-hint.js';
import 'codemirror/addon/hint/xml-hint.js';
import 'codemirror/addon/hint/html-hint.js';
import 'codemirror/addon/hint/css-hint.js';
import 'codemirror/addon/selection/active-line.js';
import 'codemirror/addon/search/searchcursor.js';
import 'codemirror/addon/search/search.js';
import 'codemirror/addon/dialog/dialog.js';
import 'codemirror/addon/search/jump-to-line.js';
import 'codemirror/mode/xml/xml.js';
import 'codemirror/mode/css/css.js';
import 'codemirror/mode/javascript/javascript.js';
import 'codemirror/mode/htmlmixed/htmlmixed.js';
import 'codemirror/keymap/sublime.js';
import 'codemirror/keymap/vim.js';
import 'code-blast-codemirror/code-blast.js';
import emmet from '@emmetio/codemirror-plugin';
emmet(CodeMirror);
export default class UserCodeMirror extends Component {
componentDidMount() {
this.initEditor();
}
shouldComponentUpdate() {
return false;
}
initEditor() {
const options = this.props.options;
this.cm = CodeMirror.fromTextArea(this.textarea, {
mode: options.mode,
lineNumbers: true,
lineWrapping: true,
autofocus: options.autofocus || false,
autoCloseBrackets: true,
autoCloseTags: true,
matchBrackets: true,
matchTags: options.matchTags || false,
tabMode: 'indent',
keyMap: 'sublime',
theme: 'monokai',
lint: !!options.lint,
tabSize: 2,
foldGutter: true,
styleActiveLine: true,
gutters: options.gutters || [],
// cursorScrollMargin: '20', has issue with scrolling
profile: options.profile || '',
extraKeys: {
Up: function(editor) {
// Stop up/down keys default behavior when saveditempane is open
// if (isSavedItemsPaneOpen) {
// return;
// }
CodeMirror.commands.goLineUp(editor);
},
Down: function(editor) {
// if (isSavedItemsPaneOpen) {
// return;
// }
CodeMirror.commands.goLineDown(editor);
},
'Shift-Tab': function(editor) {
CodeMirror.commands.indentAuto(editor);
},
Tab: function(editor) {
if (options.emmet) {
const didEmmetWork = editor.execCommand('emmetExpandAbbreviation');
if (didEmmetWork === true) {
return;
}
}
const input = $('[data-setting=indentWith]:checked');
if (
!editor.somethingSelected() &&
(!input || input.value === 'spaces')
) {
// softtabs adds spaces. This is required because by default tab key will put tab, but we want
// to indent with spaces if `spaces` is preferred mode of indentation.
// `somethingSelected` needs to be checked otherwise, all selected code is replaced with softtab.
CodeMirror.commands.insertSoftTab(editor);
} else {
CodeMirror.commands.defaultTab(editor);
}
},
Enter: 'emmetInsertLineBreak'
}
});
this.cm.on('focus', editor => {
if (typeof this.props.onFocus === 'function') this.props.onFocus(editor);
});
this.cm.on('change', this.props.onChange);
this.cm.addKeyMap({
'Ctrl-Space': 'autocomplete'
});
if (!options.noAutocomplete) {
this.cm.on('inputRead', (editor, input) => {
if (
!this.props.autoComplete ||
input.origin !== '+input' ||
input.text[0] === ';' ||
input.text[0] === ',' ||
input.text[0] === ' '
) {
return;
}
CodeMirror.commands.autocomplete(this.cm, null, {
completeSingle: false
});
});
}
this.props.onCreation(this.cm);
}
render() {
return (
<textarea
ref={el => (this.textarea = el)}
name=""
id=""
cols="30"
rows="10"
/>
);
}
}

1165
src/components/app.jsx Normal file

File diff suppressed because it is too large Load Diff

28
src/components/common.jsx Normal file
View File

@@ -0,0 +1,28 @@
import { h, Component } from 'preact';
import { trackEvent } from '../analytics';
class Clickable extends Component {
handleClick(e) {
const el = e.currentTarget;
trackEvent(
el.getAttribute('data-event-category'),
el.getAttribute('data-event-action')
);
this.props.onClick(e);
}
render() {
/* eslint-disable no-unused-vars */
const { onClick, Tag, ...props } = this.props;
/* eslint-enable no-unused-vars */
return <Tag onClick={this.handleClick.bind(this)} {...props} />;
}
}
export function A(props) {
return <Clickable Tag={'a'} {...props} />;
}
export function Button(props) {
return <Clickable Tag={'button'} {...props} />;
}

317
src/computes.js Normal file
View File

@@ -0,0 +1,317 @@
import { deferred } from './deferred';
import { addInfiniteLoopProtection } from './utils';
import { HtmlModes, CssModes, JsModes } from './codeModes';
const esprima = require('esprima');
// computeHtml, computeCss & computeJs evaluate the final code according
// to whatever mode is selected and resolve the returned promise with the code.
export function computeHtml(userCode, mode) {
var code = userCode;
var d = deferred();
if (mode === HtmlModes.HTML) {
d.resolve({
code
});
} else if (mode === HtmlModes.MARKDOWN) {
d.resolve(
window.marked
? {
code: marked(code)
}
: {
code
}
);
} else if (mode === HtmlModes.JADE) {
d.resolve(
window.jade
? {
code: jade.render(code)
}
: {
code
}
);
}
return d.promise;
}
export function computeCss(userCode, mode, settings) {
var code = userCode;
var d = deferred();
var errors;
if (mode === CssModes.CSS) {
d.resolve({
code
});
} else if (mode === CssModes.SCSS || mode === CssModes.SASS) {
if (window.sass && code) {
window.sass.compile(
code,
{
indentedSyntax: mode === CssModes.SASS
},
function(result) {
// Something was wrong
if (result.line && result.message) {
errors = {
lang: 'css',
data: [
{
lineNumber: result.line - 1,
message: result.message
}
]
};
}
d.resolve({
code: result.text,
errors
});
}
);
} else {
d.resolve({
code
});
}
} else if (mode === CssModes.LESS) {
less.render(code).then(
function(result) {
d.resolve({
code: result.css
});
},
function(error) {
errors = {
lang: 'css',
data: [
{
lineNumber: error.line,
message: error.message
}
]
};
d.resolve({
code: '',
errors
});
}
);
} else if (mode === CssModes.STYLUS) {
stylus(code).render(function(error, result) {
if (error) {
window.err = error;
// Last line of message is the actual message
var tempArr = error.message.split('\n');
tempArr.pop(); // This is empty string in the end
errors = {
lang: 'css',
data: [
{
lineNumber: +error.message.match(/stylus:(\d+):/)[1] - 298,
message: tempArr.pop()
}
]
};
}
d.resolve({
code: result,
errors
});
});
} else if (mode === CssModes.ACSS) {
if (!window.atomizer) {
d.resolve({
code: ''
});
} else {
const html = code;
const foundClasses = atomizer.findClassNames(html);
var finalConfig;
try {
finalConfig = atomizer.getConfig(
foundClasses,
JSON.parse(settings.acssConfig)
);
} catch (e) {
finalConfig = atomizer.getConfig(foundClasses, {});
}
const acss = atomizer.getCss(finalConfig);
d.resolve({
code: acss
});
}
}
return d.promise;
}
/* eslint-disable max-params */
/* eslint-disable complexity */
export function computeJs(
userCode,
mode,
shouldPreventInfiniteLoops,
infiniteLoopTimeout
) {
var code = userCode;
var d = deferred();
var errors;
if (!code) {
d.resolve('');
return d.promise;
}
if (mode === JsModes.JS) {
try {
esprima.parse(code, {
tolerant: true
});
} catch (e) {
errors = {
lang: 'js',
data: [
{
lineNumber: e.lineNumber - 1,
message: e.description
}
]
};
} finally {
if (shouldPreventInfiniteLoops !== false) {
// If errors are found in last parse, we don't run infinite loop
// protection otherwise it will again throw error.
code = errors
? code
: addInfiniteLoopProtection(code, {
timeout: infiniteLoopTimeout
});
}
d.resolve({
code,
errors
});
}
} else if (mode === JsModes.COFFEESCRIPT) {
if (!window.CoffeeScript) {
d.resolve('');
return d.promise;
}
try {
code = CoffeeScript.compile(code, {
bare: true
});
} catch (e) {
errors = {
lang: 'js',
data: [
{
lineNumber: e.location.first_line,
message: e.message
}
]
};
} finally {
if (shouldPreventInfiniteLoops !== false) {
code = errors
? code
: addInfiniteLoopProtection(code, {
timeout: infiniteLoopTimeout
});
}
d.resolve({
code,
errors
});
}
} else if (mode === JsModes.ES6) {
if (!window.Babel) {
d.resolve('');
return d.promise;
}
try {
esprima.parse(code, {
tolerant: true,
jsx: true
});
} catch (e) {
errors = {
lang: 'js',
data: [
{
lineNumber: e.lineNumber - 1,
message: e.description
}
]
};
} finally {
code = Babel.transform(code, {
presets: ['latest', 'stage-2', 'react']
}).code;
if (shouldPreventInfiniteLoops !== false) {
code = errors
? code
: addInfiniteLoopProtection(code, {
timeout: infiniteLoopTimeout
});
}
d.resolve({
code,
errors
});
}
} else if (mode === JsModes.TS) {
try {
if (!window.ts) {
d.resolve({
code: ''
});
return d.promise;
}
code = ts.transpileModule(code, {
reportDiagnostics: true,
compilerOptions: {
noEmitOnError: true,
diagnostics: true,
module: ts.ModuleKind.ES2015
}
});
if (code.diagnostics.length) {
/* eslint-disable no-throw-literal */
errors = {
lang: 'js',
data: [
{
message: code.diagnostics[0].messageText,
lineNumber:
ts.getLineOfLocalPosition(
code.diagnostics[0].file,
code.diagnostics[0].start
) - 1
}
]
};
}
code = code.outputText;
if (shouldPreventInfiniteLoops !== false && !errors) {
code = addInfiniteLoopProtection(code, {
timeout: infiniteLoopTimeout
});
}
d.resolve({
code,
errors
});
} catch (e) {}
}
return d.promise;
}
/* eslint-enable max-params */
/* eslint-enable complexity */

View File

@@ -1,3 +1,10 @@
import './firebaseInit';
import firebase from 'firebase/app';
import 'firebase/firestore';
import { deferred } from './deferred';
import { trackEvent } from './analytics';
import { log } from './utils';
(() => {
const FAUX_DELAY = 1;
@@ -30,6 +37,10 @@
}
}, FAUX_DELAY);
/* eslint-enable consistent-return */
},
remove: (key, cb) => {
window.localStorage.removeItem(key);
setTimeout(() => cb(), FAUX_DELAY);
}
};
const dbLocalAlias = chrome && chrome.storage ? chrome.storage.local : local;
@@ -39,7 +50,7 @@
if (dbPromise) {
return dbPromise;
}
utils.log('Initializing firestore');
log('Initializing firestore');
dbPromise = new Promise((resolve, reject) => {
if (db) {
return resolve(db);
@@ -50,7 +61,11 @@
.then(function() {
// Initialize Cloud Firestore through firebase
db = firebase.firestore();
utils.log('firebase db ready', db);
// const settings = {
// timestampsInSnapshots: true
// };
// db.settings(settings);
log('firebase db ready', db);
resolve(db);
})
.catch(function(err) {
@@ -61,7 +76,7 @@
alert(
"Opening Web Maker web app in multiple tabs isn't supported at present and it seems like you already have it opened in another tab. Please use in one tab."
);
window.trackEvent('fn', 'multiTabError');
trackEvent('fn', 'multiTabError');
} else if (err.code === 'unimplemented') {
// The current browser does not support all of the
// features required to enable persistence
@@ -101,9 +116,9 @@
);
if (window.user) {
const remoteDb = await getDb();
remoteDb
.doc(`users/${window.user.uid}`)
.update({ lastSeenVersion: version });
remoteDb.doc(`users/${window.user.uid}`).update({
lastSeenVersion: version
});
}
}
@@ -114,7 +129,12 @@
.get()
.then(doc => {
if (!doc.exists)
return remoteDb.doc(`users/${userId}`).set({}, { merge: true });
return remoteDb.doc(`users/${userId}`).set(
{},
{
merge: true
}
);
const user = doc.data();
Object.assign(window.user, user);
return user;

View File

@@ -1,5 +1,4 @@
(function() {
window.deferred = function() {
export function deferred() {
var d = {};
var promise = new Promise(function (resolve, reject) {
d.resolve = resolve;
@@ -10,5 +9,4 @@
d.promise = promise;
// Also move all props/methods of native promise on the deferred obj.
return Object.assign(d, promise);
};
})();
}

View File

@@ -1,30 +0,0 @@
// Dropdown.js
(function($all) {
var openDropdown;
// Closes all dropdowns except the passed one.
function closeOpenDropdown(except) {
if (openDropdown && (!except || except !== openDropdown)) {
openDropdown.classList.remove('open');
openDropdown = null;
}
}
function init() {
var dropdowns = $all('[dropdown]');
dropdowns.forEach(function(dropdown) {
dropdown.addEventListener('click', function(e) {
closeOpenDropdown(e.currentTarget);
e.currentTarget.classList.toggle('open');
openDropdown = e.currentTarget;
e.stopPropagation();
});
});
document.addEventListener('click', function() {
closeOpenDropdown();
});
}
init();
})($all);

51
src/editorThemes.js Normal file
View File

@@ -0,0 +1,51 @@
export const editorThemes = [
'3024-day',
'3024-night',
'abcdef',
'ambiance',
'base2tone-meadow-dark',
'base16-dark',
'base16-light',
'bespin',
'blackboard',
'cobalt',
'colorforth',
'dracula',
'duotone-dark',
'duotone-light',
'eclipse',
'elegant',
'erlang-dark',
'hopscotch',
'icecoder',
'isotope',
'lesser-dark',
'liquibyte',
'material',
'mbo',
'mdn-like',
'midnight',
'monokai',
'neat',
'neo',
'night',
'panda-syntax',
'paraiso-dark',
'paraiso-light',
'pastel-on-dark',
'railscasts',
'rubyblue',
'seti',
'solarized dark',
'solarized light',
'the-matrix',
'tomorrow-night-bright',
'tomorrow-night-eighties',
'ttcn',
'twilight',
'vibrant-ink',
'xq-dark',
'xq-light',
'yeti',
'zenburn'
];

11
src/firebaseInit.js Normal file
View File

@@ -0,0 +1,11 @@
import firebase from 'firebase/app';
// import 'firebase/firestore';
const config = {
apiKey: 'AIzaSyBl8Dz7ZOE7aP75mipYl2zKdLSRzBU2fFc',
authDomain: 'web-maker-app.firebaseapp.com',
databaseURL: 'https://web-maker-app.firebaseio.com',
projectId: 'web-maker-app',
storageBucket: 'web-maker-app.appspot.com',
messagingSenderId: '560473480645'
};
firebase.initializeApp(config);

View File

@@ -1,10 +1,17 @@
<html>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Web Maker</title>
<link rel="icon" href="icon-128.png">
<link rel="shortcut icon" href="icon-128.png">
<meta name=viewport content="width=device-width, initial-scale=1">
<meta name="mobile-web-app-capable" content="yes">
<meta name="apple-mobile-web-app-capable" content="yes">
<link rel="manifest" href="<%= htmlWebpackPlugin.files.publicPath %>manifest.json">
<% if (htmlWebpackPlugin.options.manifest.theme_color) { %>
<meta name="theme-color" content="<%= htmlWebpackPlugin.options.manifest.theme_color %>">
<% } %>
<style>
/* Critically acclaimed CSS */
@@ -40,16 +47,9 @@
<!-- endbuild -->
<style id="fontStyleTemplate" type="template">
@font-face {
font-family: 'fontname';
font-style: normal;
font-weight: 400;
src: url(fontname.ttf) format('truetype');
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2212, U+2215, U+E0FF, U+EFFD, U+F000;
}
.Codemirror pre {
font-family: 'fontname', monospace;
}
@font-face { font-family: 'fontname'; font-style: normal; font-weight: 400; src: url(fontname.ttf) format('truetype'); unicode-range:
U+0000-00FF, U+0131, U+0152-0153, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2212, U+2215, U+E0FF, U+EFFD,
U+F000; } .CodeMirror pre { font-family: 'fontname', monospace; }
</style>
<style type="text/css" id="fontStyleTag">
@font-face {
@@ -59,694 +59,25 @@
src: url(FiraCode.ttf) format('truetype');
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2212, U+2215, U+E0FF, U+EFFD, U+F000;
}
.Codemirror pre {
.CodeMirror pre {
font-family: 'FiraCode', monospace;
}
</style>
</head>
<body>
<div class="main-container">
<div class="main-header">
<input type="text" id="js-title-input" title="Click to edit" class="item-title-input" value="Untitled Work">
<div class="main-header__btn-wrap flex flex-v-center">
<a id="runBtn" class="hide flex flex-v-center hint--rounded hint--bottom-left" aria-label="Run preview (Ctrl/⌘ + Shift + 5)" d-click="onRunBtnClick">
<svg style="width: 14px; height: 14px;">
<use xlink:href="#play-icon"></use>
</svg>Run
</a>
<!-- SCRIPT-TAGS -->
<%= htmlWebpackPlugin.options.ssr({
url: '/'
}) %>
<script defer src="<%= htmlWebpackPlugin.files.chunks['bundle'].entry %>"></script>
<script>
window.fetch || document.write('<script src="<%= htmlWebpackPlugin.files.chunks["polyfills"].entry %>"><\/script>')
<a d-open-modal="addLibraryModal" data-event-category="ui" data-event-action="addLibraryButtonClick" class="flex-v-center hint--rounded hint--bottom-left" aria-label="Add a JS/CSS library">
Add library <span id="js-external-lib-count" style="display:none;" class="count-label"></span>
</a>
<a class="flex flex-v-center hint--rounded hint--bottom-left" aria-label="Start a new creation" d-click="onNewBtnClick">
<svg style="vertical-align:middle;width:14px;height:14px" viewBox="0 0 24 24">
<path d="M19,13H13V19H11V13H5V11H11V5H13V11H19V13Z" />
</svg>New
</a>
<a id="saveBtn" class="flex flex-v-center hint--rounded hint--bottom-left" aria-label="Save current creation (Ctrl/⌘ + S)" d-click="onSaveBtnClick">
<svg style="vertical-align:middle;width:14px;height:14px" viewBox="0 0 24 24">
<path d="M15,9H5V5H15M12,19A3,3 0 0,1 9,16A3,3 0 0,1 12,13A3,3 0 0,1 15,16A3,3 0 0,1 12,19M17,3H5C3.89,3 3,3.9 3,5V19A2,2 0 0,0 5,21H19A2,2 0 0,0 21,19V7L17,3Z" />
</svg>
<svg class="btn-loader" width="15" height="15" stroke="#fff">
<use xlink:href="#loader-icon"></use>
</svg>
Save
</a>
<a id="openItemsBtn" class="flex flex-v-center hint--rounded hint--bottom-left" aria-label="Open a saved creation (Ctrl/⌘ + O)" d-click="onOpenBtnClick">
<svg style="width:14px;height:14px;vertical-align:middle;" viewBox="0 0 24 24">
<path d="M13,9V3.5L18.5,9M6,2C4.89,2 4,2.89 4,4V20A2,2 0 0,0 6,22H18A2,2 0 0,0 20,20V8L14,2H6Z" />
</svg>
<svg class="btn-loader" width="15" height="15" stroke="#fff">
<use xlink:href="#loader-icon"></use>
</svg>
Open
</a>
<a d-open-modal="loginModal" data-event-category="ui" data-event-action="loginButtonClick" class="hide-on-login flex flex-v-center hint--rounded hint--bottom-left" aria-label="Login/Signup">
Login/Signup
</a>
<a d-open-modal="profileModal" data-event-category="ui" data-event-action="headerAvatarClick" aria-label="See profile or Logout" class="hide-on-logout hint--rounded hint--bottom-left">
<img id="headerAvatarImg" width="20" src="" class="main-header__avatar-img"/>
</a>
</div>
</div>
<div class="content-wrap flex flex-grow">
<div class="code-side" id="js-code-side">
<div data-code-wrap-id="0" id="js-html-code" data-type="html" class="code-wrap">
<div class="js-code-wrap__header code-wrap__header" title="Double click to toggle code pane">
<label class="btn-group" dropdow title="Click to change">
<span id="js-html-mode-label" class="code-wrap__header-label">HTML</span><span class="caret"></span>
<select data-type="html" class="js-mode-select hidden-select" name="">
<option value="html">HTML</option>
<option value="markdown">Markdown</option>
<option value="jade">Pug</option>
</select>
</label>
<div class="code-wrap__header-right-options">
<a class="js-code-collapse-btn code-wrap__header-btn code-wrap__collapse-btn" title="Toggle code pane">
</a>
</div>
</div>
</div>
<div data-code-wrap-id="1" id="js-css-code" data-type="css" class="code-wrap">
<div class="js-code-wrap__header code-wrap__header" title="Double click to toggle code pane">
<label class="btn-group" title="Click to change">
<span id="js-css-mode-label" class="code-wrap__header-label">CSS</span><span class="caret"></span>
<select data-type="css" class="js-mode-select hidden-select" name="">
<option value="css">CSS</option>
<option value="scss">SCSS</option>
<option value="sass">SASS</option>
<option value="less">LESS</option>
<option value="stylus">Stylus</option>
<option value="acss">Atomic CSS</option>
</select>
</label>
<div class="code-wrap__header-right-options">
<a href="#" id="cssSettingsBtn" title="Atomic CSS configuration" d-click="openCssSettingsModal" class="code-wrap__header-btn hide">
<svg>
<use xlink:href="#settings-icon"></use>
</svg>
</a>
<a class="js-code-collapse-btn code-wrap__header-btn code-wrap__collapse-btn" title="Toggle code pane">
</a>
</div>
</div>
</div>
<div data-code-wrap-id="2" id="js-js-code" data-type="js" class="code-wrap">
<div class="js-code-wrap__header code-wrap__header" title="Double click to toggle code pane">
<label class="btn-group" title="Click to change">
<span id="js-js-mode-label" class="code-wrap__header-label">JS</span><span class="caret"></span>
<select data-type="js" class="js-mode-select hidden-select">
<option value="js">JS</option>
<option value="coffee">CoffeeScript</option>
<option value="es6">ES6 (Babel)</option>
<option value="typescript">TypeScript</option>
</select>
</label>
<div class="code-wrap__header-right-options">
<a class="js-code-collapse-btn code-wrap__header-btn code-wrap__collapse-btn" title="Toggle code pane">
</a>
</div>
</div>
</div>
</div>
<div class="demo-side" id="js-demo-side">
<iframe src="about://blank" frameborder="0" id="demo-frame" allowfullscreen></iframe>
<div id="consoleEl" class="console is-minimized">
<div id="consoleLogEl" class="console__log" class="code">
<div class="js-console__header code-wrap__header" title="Double click to toggle console">
<span class="code-wrap__header-label">Console (<span id="logCountEl">0</span>)</span>
<div class="code-wrap__header-right-options">
<a class="code-wrap__header-btn" title="Clear console (CTRL + L)" d-click="onClearConsoleBtnClick">
<svg>
<use xlink:href="#cancel-icon"></use>
</svg>
</a>
<a class="code-wrap__header-btn code-wrap__collapse-btn" title="Toggle console" d-click="toggleConsole">
</a>
</div>
</div>
</div>
<div id="consolePromptEl" class="console__prompt flex flex-v-center">
<svg width="18" height="18" fill="#346fd2">
<use xlink:href="#chevron-icon"></use>
</svg>
<input d-keyup="evalConsoleExpr" class="console-exec-input">
</div>
</div>
</div>
</div>
<div class="global-console-container" id="globalConsoleContainerEl"></div>
<div id="footer" class="footer">
<div class="footer__right fr">
<a id="saveHtmlBtn" class="mode-btn hint--rounded hint--top-left hide-on-mobile" data-hint="Save as HTML file">
<svg viewBox="0 0 24 24">
<path d="M5,20H19V18H5M19,9H15V3H9V9H5L12,16L19,9Z" />
</svg>
</a>
<svg style="display: none;" xmlns="http://www.w3.org/2000/svg">
<symbol id="codepen-logo" viewBox="0 0 120 120">
<path class="outer-ring" d="M60.048 0C26.884 0 0 26.9 0 60.048s26.884 60 60 60.047c33.163 0 60.047-26.883 60.047-60.047 S93.211 0 60 0z M60.048 110.233c-27.673 0-50.186-22.514-50.186-50.186S32.375 9.9 60 9.9 c27.672 0 50.2 22.5 50.2 50.186S87.72 110.2 60 110.233z"
/>
<path class="inner-box" d="M97.147 48.319c-0.007-0.047-0.019-0.092-0.026-0.139c-0.016-0.09-0.032-0.18-0.056-0.268 c-0.014-0.053-0.033-0.104-0.05-0.154c-0.025-0.078-0.051-0.156-0.082-0.232c-0.021-0.053-0.047-0.105-0.071-0.156 c-0.033-0.072-0.068-0.143-0.108-0.211c-0.029-0.051-0.061-0.1-0.091-0.148c-0.043-0.066-0.087-0.131-0.135-0.193 c-0.035-0.047-0.072-0.094-0.109-0.139c-0.051-0.059-0.104-0.117-0.159-0.172c-0.042-0.043-0.083-0.086-0.127-0.125 c-0.059-0.053-0.119-0.104-0.181-0.152c-0.048-0.037-0.095-0.074-0.145-0.109c-0.019-0.012-0.035-0.027-0.053-0.039L61.817 23.5 c-1.072-0.715-2.468-0.715-3.54 0L24.34 46.081c-0.018 0.012-0.034 0.027-0.053 0.039c-0.05 0.035-0.097 0.072-0.144 0.1 c-0.062 0.049-0.123 0.1-0.181 0.152c-0.045 0.039-0.086 0.082-0.128 0.125c-0.056 0.055-0.108 0.113-0.158 0.2 c-0.038 0.045-0.075 0.092-0.11 0.139c-0.047 0.062-0.092 0.127-0.134 0.193c-0.032 0.049-0.062 0.098-0.092 0.1 c-0.039 0.068-0.074 0.139-0.108 0.211c-0.024 0.051-0.05 0.104-0.071 0.156c-0.031 0.076-0.057 0.154-0.082 0.2 c-0.017 0.051-0.035 0.102-0.05 0.154c-0.023 0.088-0.039 0.178-0.056 0.268c-0.008 0.047-0.02 0.092-0.025 0.1 c-0.019 0.137-0.029 0.275-0.029 0.416V71.36c0 0.1 0 0.3 0 0.418c0.006 0 0 0.1 0 0.1 c0.017 0.1 0 0.2 0.1 0.268c0.015 0.1 0 0.1 0.1 0.154c0.025 0.1 0.1 0.2 0.1 0.2 c0.021 0.1 0 0.1 0.1 0.154c0.034 0.1 0.1 0.1 0.1 0.213c0.029 0 0.1 0.1 0.1 0.1 c0.042 0.1 0.1 0.1 0.1 0.193c0.035 0 0.1 0.1 0.1 0.139c0.05 0.1 0.1 0.1 0.2 0.2 c0.042 0 0.1 0.1 0.1 0.125c0.058 0.1 0.1 0.1 0.2 0.152c0.047 0 0.1 0.1 0.1 0.1 c0.019 0 0 0 0.1 0.039L58.277 96.64c0.536 0.4 1.2 0.5 1.8 0.537c0.616 0 1.233-0.18 1.77-0.537 l33.938-22.625c0.018-0.012 0.034-0.027 0.053-0.039c0.05-0.035 0.097-0.072 0.145-0.109c0.062-0.049 0.122-0.1 0.181-0.152 c0.044-0.039 0.085-0.082 0.127-0.125c0.056-0.055 0.108-0.113 0.159-0.172c0.037-0.045 0.074-0.09 0.109-0.139 c0.048-0.062 0.092-0.127 0.135-0.193c0.03-0.049 0.062-0.098 0.091-0.146c0.04-0.07 0.075-0.141 0.108-0.213 c0.024-0.051 0.05-0.102 0.071-0.154c0.031-0.078 0.057-0.156 0.082-0.234c0.017-0.051 0.036-0.102 0.05-0.154 c0.023-0.088 0.04-0.178 0.056-0.268c0.008-0.045 0.02-0.092 0.026-0.137c0.018-0.139 0.028-0.277 0.028-0.418V48.735 C97.176 48.6 97.2 48.5 97.1 48.319z M63.238 32.073l25.001 16.666L77.072 56.21l-13.834-9.254V32.073z M56.856 32.1 v14.883L43.023 56.21l-11.168-7.471L56.856 32.073z M29.301 54.708l7.983 5.34l-7.983 5.34V54.708z M56.856 88.022L31.855 71.4 l11.168-7.469l13.833 9.252V88.022z M60.048 67.597l-11.286-7.549l11.286-7.549l11.285 7.549L60.048 67.597z M63.238 88.022V73.14 l13.834-9.252l11.167 7.469L63.238 88.022z M90.794 65.388l-7.982-5.34l7.982-5.34V65.388z"
/>
</symbol>
</svg>
<a href="" id="codepenBtn" class="mode-btn hint--rounded hint--top-left hide-on-mobile" aria-label="Edit on CodePen">
<svg>
<use xlink:href="#codepen-logo"></use>
</svg>
</a>
<a href="" id="screenshotBtn" class="mode-btn hint--rounded hint--top-left show-when-extension" d-click="takeScreenshot"
aria-label="Take screenshot of preview">
<svg style="width:24px;height:24px" viewBox="0 0 24 24">
<path d="M4,4H7L9,2H15L17,4H20A2,2 0 0,1 22,6V18A2,2 0 0,1 20,20H4A2,2 0 0,1 2,18V6A2,2 0 0,1 4,4M12,7A5,5 0 0,0 7,12A5,5 0 0,0 12,17A5,5 0 0,0 17,12A5,5 0 0,0 12,7M12,9A3,3 0 0,1 15,12A3,3 0 0,1 12,15A3,3 0 0,1 9,12A3,3 0 0,1 12,9Z"
/>
</svg>
</a>
<div class="footer__separator hide-on-mobile"></div>
<a id="layoutBtn1" class="mode-btn hide-on-mobile">
<svg viewBox="0 0 100 100" style="transform:rotate(-90deg)">
<use xlink:href="#mode-icon" />
</svg>
</a>
<a id="layoutBtn2" class="mode-btn hide-on-mobile">
<svg viewBox="0 0 100 100">
<use xlink:href="#mode-icon" />
</svg>
</a>
<a id="layoutBtn3" class="mode-btn hide-on-mobile">
<svg viewBox="0 0 100 100" style="transform:rotate(90deg)">
<use xlink:href="#mode-icon" />
</svg>
</a>
<a id="layoutBtn5" class="mode-btn hide-on-mobile">
<svg viewBox="0 0 100 100">
<use xlink:href="#vertical-mode-icon" />
</svg>
</a>
<a id="layoutBtn4" class="mode-btn hint--top-left hint--rounded hide-on-mobile" aria-label="Full Screen">
<svg viewBox="0 0 100 100">
<rect x="0" y="0" width="100" height="100" />
</svg>
</a>
<a class="mode-btn hint--top-left hint--rounded hide-on-mobile" aria-label="Detach Preview" d-click="openDetachedPreview">
<svg viewBox="0 0 24 24">
<path d="M22,17V7H6V17H22M22,5A2,2 0 0,1 24,7V17C24,18.11 23.1,19 22,19H16V21H18V23H10V21H12V19H6C4.89,19 4,18.11 4,17V7A2,2 0 0,1 6,5H22M2,3V15H0V3A2,2 0 0,1 2,1H20V3H2Z"
/>
</svg>
</a>
<div class="footer__separator"></div>
<a id="notificationsBtn" class="notifications-btn mode-btn hint--top-left hint--rounded" aria-label="Notifications">
<svg viewBox="0 0 24 24">
<path d="M14,20A2,2 0 0,1 12,22A2,2 0 0,1 10,20H14M12,2A1,1 0 0,1 13,3V4.08C15.84,4.56 18,7.03 18,10V16L21,19H3L6,16V10C6,7.03 8.16,4.56 11,4.08V3A1,1 0 0,1 12,2Z" />
</svg>
<span class="notifications-btn__dot"></span>
</a>
<a d-open-modal="settingsModal" data-event-category="ui" data-event-action="settingsBtnClick" class="mode-btn hint--top-left hint--rounded" aria-label="Settings">
<svg>
<use xlink:href="#settings-icon"></use>
</svg>
</a>
</div>
<a href="https://webmakerapp.com/" target="_blank">
<div class="logo"></div>
</a>
&copy;
<span class="web-maker-with-tag">Web Maker</span> &nbsp;&nbsp;
<a d-open-modal="helpModal" data-event-category="ui" data-event-action="helpButtonClick" class="footer__link hint--rounded hint--top-right"
aria-label="Help">
<svg style="width:20px; height:20px; vertical-align:text-bottom" viewBox="0 0 24 24">
<path d="M15.07,11.25L14.17,12.17C13.45,12.89 13,13.5 13,15H11V14.5C11,13.39 11.45,12.39 12.17,11.67L13.41,10.41C13.78,10.05 14,9.55 14,9C14,7.89 13.1,7 12,7A2,2 0 0,0 10,9H8A4,4 0 0,1 12,5A4,4 0 0,1 16,9C16,9.88 15.64,10.67 15.07,11.25M13,19H11V17H13M12,2A10,10 0 0,0 2,12A10,10 0 0,0 12,22A10,10 0 0,0 22,12C22,6.47 17.5,2 12,2Z"
/>
</svg>
</a>
<a d-open-modal="keyboardShortcutsModal" data-event-category="ui" data-event-action="keyboardShortcutButtonClick" class="footer__link hint--rounded hint--top-right hide-on-mobile"
aria-label="Keyboard shortcuts">
<svg style="width:20px; height:20px; vertical-align:text-bottom">
<use xlink:href="#keyboard-icon"></use>
</svg>
</a>
<!-- #00ACED -->
<a class="footer__link hint--rounded hint--top-right" aria-label="Tweet about 'Web Maker'" href="http://twitter.com/share?url=https://webmakerapp.com/&text=Web Maker - A blazing fast %26 offline web playground! via @webmakerApp&related=webmakerApp&hashtags=web,frontend,playground,offline"
target="_blank">
<svg style="width:20px; height:20px; vertical-align:text-bottom">
<use xlink:href="#twitter-icon"></use>
</svg>
</a>
<a d-click="openSupportDeveloperModal" data-event-action="supportDeveloperFooterBtnClick" class="footer__link ml-1 hint--rounded hint--top-right hide-on-mobile"
aria-label="Support the developer by pledging some amount" target="_blank">
Support the developer
</a>
</div>
</div>
<div class="modal" id="addLibraryModal">
<div class="modal__content">
<a d-click="onModalCloseBtnClick" href="" aria-label="Close add library modal" title="Close" class="js-modal__close-btn modal__close-btn">
<svg>
<use xlink:href="#cross-icon"></use>
</svg>
</a>
<h1>Add Library</h1>
<input type="text" id="externalLibrarySearchInput" class="full-width" placeholder="Type here to search libraries">
<div class="tar opacity--70">
<small>Powered by cdnjs</small>
</div>
<div style="margin:20px 0;">
Choose from popular libraries:
<select name="" id="js-add-library-select">
<option value="">-------</option>
<optgroup label="JavaScript Libraries">
</optgroup>
<optgroup label="CSS Libraries">
</optgroup>
</select>
</div>
<h3>JavaScript</h3>
<p style="font-size: 0.8em;" class="show-when-extension opacity--70">Note: You can load external scripts from following domains: localhost, https://ajax.googleapis.com, https://code.jquery.com, https://cdnjs.cloudflare.com, https://unpkg.com, https://maxcdn.com, https://cdn77.com, https://maxcdn.bootstrapcdn.com, https://cdn.jsdelivr.net/, https://rawgit.com, https://wzrd.in</p>
<textarea id="js-external-js" class="full-width" id="" cols="30" rows="5" placeholder="Start typing name of a library. Put each library in new line"></textarea>
<h3>CSS</h3>
<textarea id="js-external-css" class="full-width" id="" cols="30" rows="5" placeholder="Start typing name of a library. Put each library in new line"></textarea>
</div>
</div>
<div class="modal" id="cssSettingsModal">
<div class="modal__content">
<a d-click="onModalCloseBtnClick" href="" aria-label="Close CSS settings modal" title="Close" class="js-modal__close-btn modal__close-btn">
<svg>
<use xlink:href="#cross-icon"></use>
</svg>
</a>
<h1>Atomic CSS Settings</h1>
<h3>Configure Atomizer settings. <a href="https://github.com/acss-io/atomizer#api" target="_blank">Read more</a> about available settings.</h3>
<div style="height: calc(100vh - 350px);">
<textarea id="acssSettingsTextarea" cols="30" rows="10"></textarea>
</div>
</div>
</div>
<div class="modal" id="helpModal">
<div class="modal__content" d-html="partials/help-modal.html"></div>
</div>
<div class="modal" id="keyboardShortcutsModal">
<div class="modal__content" d-html="partials/keyboard-shortcuts.html"></div>
</div>
<div class="modal" id="onboardModal">
<div class="modal__content" d-html="partials/onboard-modal.html"></div>
</div>
<div class="modal login-modal" id="loginModal">
<div class="modal__content" d-html="partials/login-modal.html"></div>
</div>
<div class="modal" id="profileModal">
<div class="modal__content">
<a d-click="onModalCloseBtnClick" href="" aria-label="Close logout modal" title="Close" class="js-modal__close-btn modal__close-btn">
<svg>
<use xlink:href="#cross-icon"></use>
</svg>
</a>
<div class="tac">
<img height="80" class="profile-modal__avatar-img" src="" id="profileAvatarImg" alt="Profile image">
<h3 id="profileUserName" class="mb-2"></h3>
<p>
<button class="btn" aria-label="Logout from your account" d-click="logout">Logout</button>
</p>
</div>
</div>
</div>
<div class="modal pledge-modal" id="pledgeModal">
<div class="modal__content" d-html="partials/pledge-modal.html"></div>
</div>
<div class="modal ask-to-import-modal" id="askToImportModal">
<div class="modal__content" d-html="partials/ask-to-import-modal.html"></div>
</div>
<div class="modal modal--settings" id="settingsModal">
<div class="modal__content">
<a d-click="onModalCloseBtnClick" href="" aria-label="Close Settings" title="Close" class="js-modal__close-btn modal__close-btn">
<svg>
<use xlink:href="#cross-icon"></use>
</svg>
</a>
<h1>Settings</h1>
<h3>Indentation</h3>
<div class="line" title="I know this is tough, but you have to decide one!">
<label>
<input type="radio" checked="true" name="indentation" value="spaces" d-change="updateSetting" data-setting="indentWith"> Spaces
</label>
<label class="ml-1">
<input type="radio" name="indentation" value="tabs" d-change="updateSetting" data-setting="indentWith"> Tabs
</label>
</div>
<label class="line" title="">
Indentation Size <input type="range" class="va-m ml-1" value="2" min="1" max="7" list="indentationSizeList" data-setting="indentSize" d-change="updateSetting">
<span id="indentationSizeValueEl"></span>
<datalist id="indentationSizeList">
<option>1</option>
<option>2</option>
<option>3</option>
<option>4</option>
<option>5</option>
<option>6</option>
<option>7</option>
</datalist>
</label>
<hr>
<h3>Editor</h3>
<div class="flex block--mobile">
<div>
<label class="line">
Default Preprocessors
</label>
<div class="flex line">
<select style="flex:1;margin-left:20px" data-setting="htmlMode" d-change="updateSetting">
<option value="html">HTML</option>
<option value="markdown">Markdown</option>
<option value="jade">Pug</option>
</select>
<select style="flex:1;margin-left:20px" data-setting="cssMode" d-change="updateSetting">
<option value="css">CSS</option>
<option value="scss">SCSS</option>
<option value="sass">SASS</option>
<option value="less">LESS</option>
<option value="stylus">Stylus</option>
<option value="acss">Atomic CSS</option>
</select>
<select style="flex:1;margin-left:20px" data-setting="jsMode" d-change="updateSetting">
<option value="js">JS</option>
<option value="coffee">CoffeeScript</option>
<option value="es6">ES6 (Babel)</option>
<option value="typescript">TypeScript</option>
</select>
</div>
<label class="line">
Theme
<select style="flex:1;margin:0 20px" data-setting="editorTheme" d-change="updateSetting"></select>
</label>
<label class="line">
Font
<select style="flex:1;margin:0 20px" data-setting="editorFont" d-change="updateSetting">
<option value="FiraCode">Fira Code</option>
<option value="Inconsolata">Inconsolata</option>
<option value="Monoid">Monoid</option>
<option value="FixedSys">FixedSys</option>
<option disabled="disabled">----</option>
<option value="other">Other font from system</option>
</select>
<input id="customEditorFontInput" type="text" value="" placeholder="Custom font name here" data-setting="editorCustomFont" d-change="updateSetting">
</label>
<label class="line">
Font Size <input style="width:70px" type="number" value="16" data-setting="fontSize" d-change="updateSetting"> px
</label>
<div class="line">
Key bindings
<label class="ml-1">
<input type="radio" checked="true" name="keymap" value="sublime" d-change="updateSetting" data-setting="keymap"> Sublime
</label>
<label class="ml-1">
<input type="radio" name="keymap" value="vim" d-change="updateSetting" data-setting="keymap"> Vim
</label>
</div>
</div>
<div class="ml-2 ml-0--mobile">
<label class="line" title="Toggle wrapping of long sentences onto new line">
<input type="checkbox" d-change="updateSetting" data-setting="lineWrap"> Line wrap
</label>
<label class="line" title="Your Preview will refresh when you resize the preview split">
<input type="checkbox" d-change="updateSetting" data-setting="refreshOnResize"> Refresh preview on resize
</label>
<label class="line" title="Turns on the auto-completion suggestions as you type">
<input type="checkbox" d-change="updateSetting" data-setting="autoComplete"> Auto-complete suggestions
</label>
<label class="line" title="Refreshes the preview as you code. Otherwise use the Run button">
<input type="checkbox" d-change="updateSetting" data-setting="autoPreview"> Auto-preview
</label>
<label class="line" title="Auto-save keeps saving your code at regular intervals after you hit the first save manually">
<input type="checkbox" d-change="updateSetting" data-setting="autoSave"> Auto-save
</label>
<label class="line" title="Loads the last open creation when app starts">
<input type="checkbox" d-change="updateSetting" data-setting="preserveLastCode"> Preserve last written code
</label>
<label class="line show-when-extension" title="Turning this on will start showing Web Maker in every new tab you open">
<input type="checkbox" d-change="updateSetting" data-setting="replaceNewTab"> Replace new tab page
</label>
<label class="line" title="Preserves the console logs across your preview refreshes">
<input type="checkbox" d-change="updateSetting" data-setting="preserveConsoleLogs"> Preserve console logs
</label>
<label class="line" title="Switch to lighter version for better performance. Removes things like blur etc.">
<input type="checkbox" d-change="updateSetting" data-setting="lightVersion"> Fast/light version
</label>
</div>
</div>
<hr>
<h3>Fun</h3>
<p>
<label class="line" title="Enjoy wonderful particle blasts while you type">
<input type="checkbox" d-change="updateSetting" data-setting="isCodeBlastOn"> Code blast!
</label>
</p>
<hr>
<h3>Advanced</h3>
<p>
<label class="line" title="This timeout is used to indentify a possible infinite loop and prevent it.">
Maximum time allowed in a loop iteration
<input type="number" data-setting="infiniteLoopTimeout" d-change="updateSetting"> ms
</label>
<div class="help-text">If any loop iteration takes more than the defined time, it is detected as a potential infinite loop and further iterations are stopped.</div>
</p>
</div>
</div>
<div class="modal" id="notificationsModal">
<div class="modal__content" d-html="partials/changelog.html"></div>
</div>
<div id="js-saved-items-pane" class="saved-items-pane">
<button class="btn saved-items-pane__close-btn" id="js-saved-items-pane-close-btn">X</button>
<div class="flex flex-v-center" style="justify-content: space-between;">
<h3>My Library <span id="savedItemCountEl"></span></h3>
<div class="main-header__btn-wrap">
<a d-click="exportItems" href="" class="btn btn-icon hint--bottom-left hint--rounded hint--medium" aria-label="Export all your creations into a single importable file.">Export
</a>
<a d-click="onImportBtnClicked" href="" class="btn btn-icon hint--bottom-left hint--rounded hint--medium" aria-label="Only the file that you export through the 'Export' button can be imported.">Import
</a>
</div>
</div>
<input type="" id="searchInput" class="search-input" d-input="onSearchInputChange" placeholder="Search your creations here...">
<div id="js-saved-items-wrap" class="saved-items-pane__container">
</div>
</div>
<div class="modal-overlay"></div>
<div class="alerts-container" id="js-alerts-container"></div>
<form style="display:none;" action="https://codepen.io/pen/define" method="POST" target="_blank" id="js-codepen-form">
<input type="hidden" name="data" value='{"title": "New Pen!", "html": "<div>Hello, World!</div>"}'>
</form>
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" style="display:none">
<symbol id="logo" viewBox="-145 -2 372 175">
<g stroke="none" stroke-width="1" fill="none" fill-rule="evenodd" transform="translate(-145.000000, -1.000000)">
<polygon id="Path-1" fill="#FF4600" points="31 0 232 0 132 173.310547"></polygon>
<polygon id="Path-1" fill="#FF6C00" points="0 0 201 0 101 173.310547"></polygon>
<polygon id="Path-1" fill="#FF6C00" transform="translate(271.500000, 86.500000) scale(1, -1) translate(-271.500000, -86.500000) " points="171 0 372 0 272 173.310547"></polygon>
<polygon id="Path-1" fill="#FF4600" transform="translate(241.500000, 86.500000) scale(1, -1) translate(-241.500000, -86.500000) " points="141 0 342 0 242 173.310547"></polygon>
</g>
</symbol>
<symbol id="bug-icon" viewBox="0 0 24 24">
<path d="M14,12H10V10H14M14,16H10V14H14M20,8H17.19C16.74,7.22 16.12,6.55 15.37,6.04L17,4.41L15.59,3L13.42,5.17C12.96,5.06 12.5,5 12,5C11.5,5 11.04,5.06 10.59,5.17L8.41,3L7,4.41L8.62,6.04C7.88,6.55 7.26,7.22 6.81,8H4V10H6.09C6.04,10.33 6,10.66 6,11V12H4V14H6V15C6,15.34 6.04,15.67 6.09,16H4V18H6.81C7.85,19.79 9.78,21 12,21C14.22,21 16.15,19.79 17.19,18H20V16H17.91C17.96,15.67 18,15.34 18,15V14H20V12H18V11C18,10.66 17.96,10.33 17.91,10H20V8Z" />
</symbol>
<symbol id="google-icon" viewBox="0 0 24 24">
<path d="M21.35,11.1H12.18V13.83H18.69C18.36,17.64 15.19,19.27 12.19,19.27C8.36,19.27 5,16.25 5,12C5,7.9 8.2,4.73 12.2,4.73C15.29,4.73 17.1,6.7 17.1,6.7L19,4.72C19,4.72 16.56,2 12.1,2C6.42,2 2.03,6.8 2.03,12C2.03,17.05 6.16,22 12.25,22C17.6,22 21.5,18.33 21.5,12.91C21.5,11.76 21.35,11.1 21.35,11.1V11.1Z" />
</symbol>
<symbol id="fb-icon" viewBox="0 0 24 24">
<path d="M17,2V2H17V6H15C14.31,6 14,6.81 14,7.5V10H14L17,10V14H14V22H10V14H7V10H10V6A4,4 0 0,1 14,2H17Z" />
</symbol>
<symbol id="github-icon" viewBox="0 0 24 24">
<path d="M12,2A10,10 0 0,0 2,12C2,16.42 4.87,20.17 8.84,21.5C9.34,21.58 9.5,21.27 9.5,21C9.5,20.77 9.5,20.14 9.5,19.31C6.73,19.91 6.14,17.97 6.14,17.97C5.68,16.81 5.03,16.5 5.03,16.5C4.12,15.88 5.1,15.9 5.1,15.9C6.1,15.97 6.63,16.93 6.63,16.93C7.5,18.45 8.97,18 9.54,17.76C9.63,17.11 9.89,16.67 10.17,16.42C7.95,16.17 5.62,15.31 5.62,11.5C5.62,10.39 6,9.5 6.65,8.79C6.55,8.54 6.2,7.5 6.75,6.15C6.75,6.15 7.59,5.88 9.5,7.17C10.29,6.95 11.15,6.84 12,6.84C12.85,6.84 13.71,6.95 14.5,7.17C16.41,5.88 17.25,6.15 17.25,6.15C17.8,7.5 17.45,8.54 17.35,8.79C18,9.5 18.38,10.39 18.38,11.5C18.38,15.32 16.04,16.16 13.81,16.41C14.17,16.72 14.5,17.33 14.5,18.26C14.5,19.6 14.5,20.68 14.5,21C14.5,21.27 14.66,21.59 15.17,21.5C19.14,20.16 22,16.42 22,12A10,10 0 0,0 12,2Z" />
</symbol>
<symbol id="settings-icon" viewBox="0 0 24 24">
<path d="M12,15.5A3.5,3.5 0 0,1 8.5,12A3.5,3.5 0 0,1 12,8.5A3.5,3.5 0 0,1 15.5,12A3.5,3.5 0 0,1 12,15.5M19.43,12.97C19.47,12.65 19.5,12.33 19.5,12C19.5,11.67 19.47,11.34 19.43,11L21.54,9.37C21.73,9.22 21.78,8.95 21.66,8.73L19.66,5.27C19.54,5.05 19.27,4.96 19.05,5.05L16.56,6.05C16.04,5.66 15.5,5.32 14.87,5.07L14.5,2.42C14.46,2.18 14.25,2 14,2H10C9.75,2 9.54,2.18 9.5,2.42L9.13,5.07C8.5,5.32 7.96,5.66 7.44,6.05L4.95,5.05C4.73,4.96 4.46,5.05 4.34,5.27L2.34,8.73C2.21,8.95 2.27,9.22 2.46,9.37L4.57,11C4.53,11.34 4.5,11.67 4.5,12C4.5,12.33 4.53,12.65 4.57,12.97L2.46,14.63C2.27,14.78 2.21,15.05 2.34,15.27L4.34,18.73C4.46,18.95 4.73,19.03 4.95,18.95L7.44,17.94C7.96,18.34 8.5,18.68 9.13,18.93L9.5,21.58C9.54,21.82 9.75,22 10,22H14C14.25,22 14.46,21.82 14.5,21.58L14.87,18.93C15.5,18.67 16.04,18.34 16.56,17.94L19.05,18.95C19.27,19.03 19.54,18.95 19.66,18.73L21.66,15.27C21.78,15.05 21.73,14.78 21.54,14.63L19.43,12.97Z"></path>
</symbol>
<symbol id="twitter-icon" viewBox="0 0 16 16">
<path d="M15.969,3.058c-0.586,0.26-1.217,0.436-1.878,0.515c0.675-0.405,1.194-1.045,1.438-1.809
c-0.632,0.375-1.332,0.647-2.076,0.793c-0.596-0.636-1.446-1.033-2.387-1.033c-1.806,0-3.27,1.464-3.27,3.27 c0,0.256,0.029,0.506,0.085,0.745C5.163,5.404,2.753,4.102,1.14,2.124C0.859,2.607,0.698,3.168,0.698,3.767 c0,1.134,0.577,2.135,1.455,2.722C1.616,6.472,1.112,6.325,0.671,6.08c0,0.014,0,0.027,0,0.041c0,1.584,1.127,2.906,2.623,3.206 C3.02,9.402,2.731,9.442,2.433,9.442c-0.211,0-0.416-0.021-0.615-0.059c0.416,1.299,1.624,2.245,3.055,2.271 c-1.119,0.877-2.529,1.4-4.061,1.4c-0.264,0-0.524-0.015-0.78-0.046c1.447,0.928,3.166,1.469,5.013,1.469 c6.015,0,9.304-4.983,9.304-9.304c0-0.142-0.003-0.283-0.009-0.423C14.976,4.29,15.531,3.714,15.969,3.058z"/>
</symbol>
<symbol id="heart-icon" viewBox="0 0 24 24">
<path d="M12,21.35L10.55,20.03C5.4,15.36 2,12.27 2,8.5C2,5.41 4.42,3 7.5,3C9.24,3 10.91,3.81 12,5.08C13.09,3.81 14.76,3 16.5,3C19.58,3 22,5.41 22,8.5C22,12.27 18.6,15.36 13.45,20.03L12,21.35Z" />
</symbol>
<symbol id="play-icon" viewBox="0 0 24 24">
<svg>
<path d="M8,5.14V19.14L19,12.14L8,5.14Z" />
</svg>
</symbol>
<symbol id="cancel-icon" viewBox="0 0 24 24">
<svg>
<path d="M12,2A10,10 0 0,1 22,12A10,10 0 0,1 12,22A10,10 0 0,1 2,12A10,10 0 0,1 12,2M12,4A8,8 0 0,0 4,12C4,13.85 4.63,15.55 5.68,16.91L16.91,5.68C15.55,4.63 13.85,4 12,4M12,20A8,8 0 0,0 20,12C20,10.15 19.37,8.45 18.32,7.09L7.09,18.32C8.45,19.37 10.15,20 12,20Z" />
</svg>
</symbol>
<symbol id="chevron-icon" viewBox="0 0 24 24">
<svg>
<path d="M8.59,16.58L13.17,12L8.59,7.41L10,6L16,12L10,18L8.59,16.58Z" />
</svg>
</symbol>
<symbol id="chat-icon" viewBox="0 0 24 24">
<path d="M20,2H4A2,2 0 0,0 2,4V22L6,18H20A2,2 0 0,0 22,16V4A2,2 0 0,0 20,2M8,14H6V12H8V14M8,11H6V9H8V11M8,8H6V6H8V8M15,14H10V12H15V14M18,11H10V9H18V11M18,8H10V6H18V8Z" />
</symbol>
<symbol id="gift-icon" viewBox="0 0 24 24">
<path d="M22,12V20A2,2 0 0,1 20,22H4A2,2 0 0,1 2,20V12A1,1 0 0,1 1,11V8A2,2 0 0,1 3,6H6.17C6.06,5.69 6,5.35 6,5A3,3 0 0,1 9,2C10,2 10.88,2.5 11.43,3.24V3.23L12,4L12.57,3.23V3.24C13.12,2.5 14,2 15,2A3,3 0 0,1 18,5C18,5.35 17.94,5.69 17.83,6H21A2,2 0 0,1 23,8V11A1,1 0 0,1 22,12M4,20H11V12H4V20M20,20V12H13V20H20M9,4A1,1 0 0,0 8,5A1,1 0 0,0 9,6A1,1 0 0,0 10,5A1,1 0 0,0 9,4M15,4A1,1 0 0,0 14,5A1,1 0 0,0 15,6A1,1 0 0,0 16,5A1,1 0 0,0 15,4M3,8V10H11V8H3M13,8V10H21V8H13Z" />
<symbol id="gift-icon" viewBox="0 0 24 24">
</symbol>
<symbol id="cross-icon" viewBox="0 0 24 24">
<path d="M19,6.41L17.59,5L12,10.59L6.41,5L5,6.41L10.59,12L5,17.59L6.41,19L12,13.41L17.59,19L19,17.59L13.41,12L19,6.41Z" />
</symbol>
<symbol id="keyboard-icon" viewBox="0 0 24 24">
<path d="M19,10H17V8H19M19,13H17V11H19M16,10H14V8H16M16,13H14V11H16M16,17H8V15H16M7,10H5V8H7M7,13H5V11H7M8,11H10V13H8M8,8H10V10H8M11,11H13V13H11M11,8H13V10H11M20,5H4C2.89,5 2,5.89 2,7V17A2,2 0 0,0 4,19H20A2,2 0 0,0 22,17V7C22,5.89 21.1,5 20,5Z" />
</symbol>
<symbol id="mode-icon" viewBox="0 0 100 100">
<g>
<rect x="0" y="0" width="28" height="47" />
<rect x="36" y="0" width="28" height="47"/>
<rect x="72" y="0" width="28" height="47"/>
<rect x="0" y="53" width="100" height="47"/>
</g>
</symbol>
<symbol id="vertical-mode-icon" viewBox="0 0 100 100">
<g>
<rect x="0" y="0" width="20" height="100" />
<rect x="23" y="0" width="20" height="100"/>
<rect x="46" y="0" width="20" height="100"/>
<rect x="69" y="0" width="32" height="100"/>
</g>
</symbol>
<symbol id="loader-icon" viewBox="0 0 44 44">
<!-- By Sam Herbert (@sherb), for everyone. More @ http://goo.gl/7AJzbL -->
<g fill="none" fill-rule="evenodd" stroke-width="10">
<circle cx="22" cy="22" r="1">
<animate attributeName="r"
begin="0s" dur="1.8s"
values="1; 20"
calcMode="spline"
keyTimes="0; 1"
keySplines="0.165, 0.84, 0.44, 1"
repeatCount="indefinite" />
<animate attributeName="stroke-opacity"
begin="0s" dur="1.8s"
values="1; 0"
calcMode="spline"
keyTimes="0; 1"
keySplines="0.3, 0.61, 0.355, 1"
repeatCount="indefinite" />
</circle>
<circle cx="22" cy="22" r="1">
<animate attributeName="r"
begin="-0.9s" dur="1.8s"
values="1; 20"
calcMode="spline"
keyTimes="0; 1"
keySplines="0.165, 0.84, 0.44, 1"
repeatCount="indefinite" />
<animate attributeName="stroke-opacity"
begin="-0.9s" dur="1.8s"
values="1; 0"
calcMode="spline"
keyTimes="0; 1"
keySplines="0.3, 0.61, 0.355, 1"
repeatCount="indefinite" />
</circle>
</g>
</symbol>
</svg>
<!-- build:js vendor.js -->
<script src="lib/codemirror/lib/codemirror.js"></script>
<script src="lib/codemirror/addon/edit/matchbrackets.js"></script>
<script src="lib/codemirror/addon/edit/matchtags.js"></script>
<script src="lib/codemirror/addon/edit/closebrackets.js"></script>
<script src="lib/codemirror/addon/edit/closetag.js"></script>
<script src="lib/codemirror/addon/comment/comment.js"></script>
<script src="lib/codemirror/addon/fold/foldcode.js"></script>
<script src="lib/codemirror/addon/fold/foldgutter.js"></script>
<script src="lib/codemirror/addon/fold/xml-fold.js"></script>
<script src="lib/codemirror/addon/fold/indent-fold.js"></script>
<script src="lib/codemirror/addon/fold/comment-fold.js"></script>
<script src="lib/codemirror/addon/fold/brace-fold.js"></script>
<script src="lib/codemirror/addon/mode/loadmode.js"></script>
<script src="lib/codemirror/addon/hint/show-hint.js"></script>
<script src="lib/codemirror/addon/hint/javascript-hint.js"></script>
<script src="lib/codemirror/addon/hint/xml-hint.js"></script>
<script src="lib/codemirror/addon/hint/html-hint.js"></script>
<script src="lib/codemirror/addon/hint/css-hint.js"></script>
<script src="lib/codemirror/addon/selection/active-line.js"></script>
<script src="lib/codemirror/addon/search/searchcursor.js"></script>
<script src="lib/codemirror/addon/search/search.js"></script>
<script src="lib/codemirror/addon/dialog/dialog.js"></script>
<script src="lib/codemirror/addon/search/jump-to-line.js"></script>
<script src="lib/codemirror/mode/xml/xml.js"></script>
<script src="lib/codemirror/mode/javascript/javascript.js"></script>
<script src="lib/codemirror/mode/css/css.js"></script>
<script src="lib/codemirror/mode/htmlmixed/htmlmixed.js"></script>
<script src="lib/codemirror/keymap/sublime.js"></script>
<script src="lib/codemirror/keymap/vim.js"></script>
<script src="lib/emmet.js"></script>
<script src="lib/code-blast.js"></script>
<script src="lib/split.js"></script>
<script src="lib/inlet.min.js"></script>
<script src="lib/esprima.js"></script>
<script src="lib/firebase.js"></script>
<script src="lib/firebase-firestore.js"></script>
<!-- endbuild -->
<!-- build:js script.js -->
<script src="service-worker-registration.js"></script>
<script src="utils.js"></script>
<script src="db.js"></script>
<script src="auth.js"></script>
<script src="analytics.js"></script>
<script src="deferred.js"></script>
<script src="loader.js"></script>
<script src="notifications.js"></script>
<script src="library-list.js"></script>
<script src="textarea-autocomplete.js"></script>
<script src="itemService.js"></script>
<script src="script.js"></script>
<script src="dropdown.js"></script>
<!-- endbuild -->
</script>
<!-- END-SCRIPT-TAGS -->
</body>
</html>

3
src/index.js Normal file
View File

@@ -0,0 +1,3 @@
import App from './components/app.jsx';
export default App;

View File

@@ -1,5 +1,7 @@
(() => {
window.itemService = {
import { deferred } from './deferred';
import { log } from 'util';
export const itemService = {
async getItem(id) {
var remoteDb = await window.db.getDb();
return remoteDb
@@ -28,11 +30,11 @@
},
async getAllItems() {
var t=Date.now()
var t = Date.now();
var d = deferred();
let itemIds = await this.getUserItemIds();
itemIds = Object.getOwnPropertyNames(itemIds || {});
utils.log('itemids', itemIds);
log('itemids', itemIds);
if (!itemIds.length) {
d.resolve([]);
@@ -42,16 +44,19 @@
remoteDb
.collection('items')
.where('createdBy', '==', window.user.uid)
.onSnapshot(function(querySnapshot) {
.onSnapshot(
function(querySnapshot) {
querySnapshot.forEach(function(doc) {
items.push(doc.data());
});
utils.log('Items fetched in ', Date.now()-t, 'ms')
log('Items fetched in ', Date.now() - t, 'ms');
d.resolve(items);
}, function() {
d.resolve([])
});
},
function() {
d.resolve([]);
}
);
return d.promise;
},
@@ -87,7 +92,7 @@
}
if (window.user) {
var remoteDb = await window.db.getDb();
utils.log(`Starting to save item ${id}`);
log(`Starting to save item ${id}`);
item.createdBy = window.user.uid;
remotePromise = remoteDb
.collection('items')
@@ -96,7 +101,7 @@
merge: true
})
.then(arg => {
utils.log('Document written', arg);
log('Document written', arg);
d.resolve();
})
.catch(d.reject);
@@ -156,19 +161,19 @@
// When not logged in
if (!window.user) {
var d = deferred();
db.local.remove(id, d.resolve);
window.db.local.remove(id, d.resolve);
return d.promise;
}
const remoteDb = await window.db.getDb();
utils.log(`Starting to save item ${id}`);
log(`Starting to save item ${id}`);
return remoteDb
.collection('items')
.doc(id)
.delete()
.then(arg => {
utils.log('Document removed', arg);
log('Document removed', arg);
})
.catch(error => utils.log(error));
.catch(error => log(error));
},
async setItemForUser(itemId) {
@@ -194,11 +199,11 @@
[`items.${itemId}`]: true
})
.then(arg => {
utils.log(`Item ${itemId} set for user`, arg);
log(`Item ${itemId} set for user`, arg);
window.user.items = window.user.items || {};
window.user.items[itemId] = true;
})
.catch(error => utils.log(error));
.catch(error => log(error));
},
async unsetItemForUser(itemId) {
@@ -210,7 +215,7 @@
},
function(result) {
delete result.items[itemId];
db.local.set({
window.db.local.set({
items: result.items
});
}
@@ -225,9 +230,8 @@
})
.then(arg => {
delete window.user.items[itemId];
utils.log(`Item ${itemId} unset for user`, arg);
log(`Item ${itemId} unset for user`, arg);
})
.catch(error => utils.log(error));
.catch(error => log(error));
}
};
})();

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -1,569 +0,0 @@
// The programming goals of Split.js are to deliver readable, understandable and
// maintainable code, while at the same time manually optimizing for tiny minified file size,
// browser compatibility without additional requirements, graceful fallback (IE8 is supported)
// and very few assumptions about the user's page layout.
//
// Make sure all browsers handle this JS library correctly with ES5.
// More information here: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Strict_mode
'use strict';
// A wrapper function that does a couple things:
//
// 1. Doesn't pollute the global namespace. This is important for a library.
// 2. Allows us to mount the library in different module systems, as well as
// directly in the browser.
(function() {
// Save the global `this` for use later. In this case, since the library only
// runs in the browser, it will refer to `window`. Also, figure out if we're in IE8
// or not. IE8 will still render correctly, but will be static instead of draggable.
//
// Save a couple long function names that are used frequently.
// This optimization saves around 400 bytes.
var global = this
, isIE8 = global.attachEvent && !global[addEventListener]
, document = global.document
, addEventListener = 'addEventListener'
, removeEventListener = 'removeEventListener'
, getBoundingClientRect = 'getBoundingClientRect'
// This library only needs two helper functions:
//
// The first determines which prefixes of CSS calc we need.
// We only need to do this once on startup, when this anonymous function is called.
//
// Tests -webkit, -moz and -o prefixes. Modified from StackOverflow:
// http://stackoverflow.com/questions/16625140/js-feature-detection-to-detect-the-usage-of-webkit-calc-over-calc/16625167#16625167
, calc = (function () {
var el
, prefixes = ["", "-webkit-", "-moz-", "-o-"]
for (var i = 0; i < prefixes.length; i++) {
el = document.createElement('div')
el.style.cssText = "width:" + prefixes[i] + "calc(9px)"
if (el.style.length) {
return prefixes[i] + "calc"
}
}
})()
// The second helper function allows elements and string selectors to be used
// interchangeably. In either case an element is returned. This allows us to
// do `Split(elem1, elem2)` as well as `Split('#id1', '#id2')`.
, elementOrSelector = function (el) {
if (typeof el === 'string' || el instanceof String) {
return document.querySelector(el)
} else {
return el
}
}
// The main function to initialize a split. Split.js thinks about each pair
// of elements as an independant pair. Dragging the gutter between two elements
// only changes the dimensions of elements in that pair. This is key to understanding
// how the following functions operate, since each function is bound to a pair.
//
// A pair object is shaped like this:
//
// {
// a: DOM element,
// b: DOM element,
// aMin: Number,
// bMin: Number,
// dragging: Boolean,
// parent: DOM element,
// isFirst: Boolean,
// isLast: Boolean,
// direction: 'horizontal' | 'vertical'
// }
//
// The basic sequence:
//
// 1. Set defaults to something sane. `options` doesn't have to be passed at all.
// 2. Initialize a bunch of strings based on the direction we're splitting.
// A lot of the behavior in the rest of the library is paramatized down to
// rely on CSS strings and classes.
// 3. Define the dragging helper functions, and a few helpers to go with them.
// 4. Define a few more functions that "balance" the entire split instance.
// Split.js tries it's best to cope with min sizes that don't add up.
// 5. Loop through the elements while pairing them off. Every pair gets an
// `pair` object, a gutter, and special isFirst/isLast properties.
// 6. Actually size the pair elements, insert gutters and attach event listeners.
// 7. Balance all of the pairs to accomodate min sizes as best as possible.
, Split = function (ids, options) {
var dimension
, i
, clientDimension
, clientAxis
, position
, gutterClass
, paddingA
, paddingB
, pairs = []
// 1. Set defaults to something sane. `options` doesn't have to be passed at all,
// so create an options object if none exists. Pixel values 10, 100 and 30 are
// arbitrary but feel natural.
options = typeof options !== 'undefined' ? options : {}
if (typeof options.gutterSize === 'undefined') options.gutterSize = 10
if (typeof options.minSize === 'undefined') options.minSize = 100
if (typeof options.snapOffset === 'undefined') options.snapOffset = 30
if (typeof options.direction === 'undefined') options.direction = 'horizontal'
// 2. Initialize a bunch of strings based on the direction we're splitting.
// A lot of the behavior in the rest of the library is paramatized down to
// rely on CSS strings and classes.
if (options.direction == 'horizontal') {
dimension = 'width'
clientDimension = 'clientWidth'
clientAxis = 'clientX'
position = 'left'
gutterClass = 'gutter gutter-horizontal'
paddingA = 'paddingLeft'
paddingB = 'paddingRight'
if (!options.cursor) options.cursor = 'ew-resize'
} else if (options.direction == 'vertical') {
dimension = 'height'
clientDimension = 'clientHeight'
clientAxis = 'clientY'
position = 'top'
gutterClass = 'gutter gutter-vertical'
paddingA = 'paddingTop'
paddingB = 'paddingBottom'
if (!options.cursor) options.cursor = 'ns-resize'
}
// 3. Define the dragging helper functions, and a few helpers to go with them.
// Each helper is bound to a pair object that contains it's metadata. This
// also makes it easy to store references to listeners that that will be
// added and removed.
//
// Even though there are no other functions contained in them, aliasing
// this to self saves 50 bytes or so since it's used so frequently.
//
// The pair object saves metadata like dragging state, position and
// event listener references.
//
// startDragging calls `calculateSizes` to store the inital size in the pair object.
// It also adds event listeners for mouse/touch events,
// and prevents selection while dragging so avoid the selecting text.
var startDragging = function (e) {
// Alias frequently used variables to save space. 200 bytes.
var self = this
, a = self.a
, b = self.b
// Call the onDragStart callback.
if (!self.dragging && options.onDragStart) {
options.onDragStart()
}
// Don't actually drag the element. We emulate that in the drag function.
e.preventDefault()
// Set the dragging property of the pair object.
self.dragging = true
// Create two event listeners bound to the same pair object and store
// them in the pair object.
self.move = drag.bind(self)
self.stop = stopDragging.bind(self)
// All the binding. `window` gets the stop events in case we drag out of the elements.
global[addEventListener]('mouseup', self.stop)
global[addEventListener]('touchend', self.stop)
global[addEventListener]('touchcancel', self.stop)
self.parent[addEventListener]('mousemove', self.move)
self.parent[addEventListener]('touchmove', self.move)
// Disable selection. Disable!
a[addEventListener]('selectstart', noop)
a[addEventListener]('dragstart', noop)
b[addEventListener]('selectstart', noop)
b[addEventListener]('dragstart', noop)
a.style.userSelect = 'none'
a.style.webkitUserSelect = 'none'
a.style.MozUserSelect = 'none'
a.style.pointerEvents = 'none'
b.style.userSelect = 'none'
b.style.webkitUserSelect = 'none'
b.style.MozUserSelect = 'none'
b.style.pointerEvents = 'none'
// Set the cursor, both on the gutter and the parent element.
// Doing only a, b and gutter causes flickering.
self.gutter.style.cursor = options.cursor
self.parent.style.cursor = options.cursor
// Cache the initial sizes of the pair.
calculateSizes.call(self)
}
// stopDragging is very similar to startDragging in reverse.
, stopDragging = function () {
var self = this
, a = self.a
, b = self.b
if (self.dragging && options.onDragEnd) {
options.onDragEnd()
}
self.dragging = false
// Remove the stored event listeners. This is why we store them.
global[removeEventListener]('mouseup', self.stop)
global[removeEventListener]('touchend', self.stop)
global[removeEventListener]('touchcancel', self.stop)
self.parent[removeEventListener]('mousemove', self.move)
self.parent[removeEventListener]('touchmove', self.move)
// Delete them once they are removed. I think this makes a difference
// in memory usage with a lot of splits on one page. But I don't know for sure.
delete self.stop
delete self.move
a[removeEventListener]('selectstart', noop)
a[removeEventListener]('dragstart', noop)
b[removeEventListener]('selectstart', noop)
b[removeEventListener]('dragstart', noop)
a.style.userSelect = ''
a.style.webkitUserSelect = ''
a.style.MozUserSelect = ''
a.style.pointerEvents = ''
b.style.userSelect = ''
b.style.webkitUserSelect = ''
b.style.MozUserSelect = ''
b.style.pointerEvents = ''
self.gutter.style.cursor = ''
self.parent.style.cursor = ''
}
// drag, where all the magic happens. The logic is really quite simple:
//
// 1. Ignore if the pair is not dragging.
// 2. Get the offset of the event.
// 3. Snap offset to min if within snappable range (within min + snapOffset).
// 4. Actually adjust each element in the pair to offset.
//
// ---------------------------------------------------------------------
// | | <- this.aMin || this.bMin -> | |
// | | | <- this.snapOffset || this.snapOffset -> | | |
// | | | || | | |
// | | | || | | |
// ---------------------------------------------------------------------
// | <- this.start this.size -> |
, drag = function (e) {
var offset
if (!this.dragging) return
// Get the offset of the event from the first side of the
// pair `this.start`. Supports touch events, but not multitouch, so only the first
// finger `touches[0]` is counted.
if ('touches' in e) {
offset = e.touches[0][clientAxis] - this.start
} else {
offset = e[clientAxis] - this.start
}
// If within snapOffset of min or max, set offset to min or max.
// snapOffset buffers aMin and bMin, so logic is opposite for both.
// Include the appropriate gutter sizes to prevent overflows.
if (offset <= this.aMin + options.snapOffset + this.aGutterSize) {
offset = this.aMin + this.aGutterSize
} else if (offset >= this.size - (this.bMin + options.snapOffset + this.bGutterSize)) {
offset = this.size - (this.bMin + this.bGutterSize)
}
// Actually adjust the size.
adjust.call(this, offset)
// Call the drag callback continously. Don't do anything too intensive
// in this callback.
if (options.onDrag) {
options.onDrag()
}
}
// Cache some important sizes when drag starts, so we don't have to do that
// continously:
//
// `size`: The total size of the pair. First element + second element + first gutter + second gutter.
// `percentage`: The percentage between 0-100 that the pair occupies in the parent.
// `start`: The leading side of the first element.
//
// ------------------------------------------------ - - - - - - - - - - -
// | aGutterSize -> ||| | |
// | ||| | |
// | ||| | |
// | ||| <- bGutterSize | |
// ------------------------------------------------ - - - - - - - - - - -
// | <- start size -> | parentSize -> |
, calculateSizes = function () {
// Figure out the parent size minus padding.
var computedStyle = global.getComputedStyle(this.parent)
, parentSize = this.parent[clientDimension] - parseFloat(computedStyle[paddingA]) - parseFloat(computedStyle[paddingB])
this.size = this.a[getBoundingClientRect]()[dimension] + this.b[getBoundingClientRect]()[dimension] + this.aGutterSize + this.bGutterSize
this.percentage = Math.min(this.size / parentSize * 100, 100)
this.start = this.a[getBoundingClientRect]()[position]
}
// Actually adjust the size of elements `a` and `b` to `offset` while dragging.
// calc is used to allow calc(percentage + gutterpx) on the whole split instance,
// which allows the viewport to be resized without additional logic.
// Element a's size is the same as offset. b's size is total size - a size.
// Both sizes are calculated from the initial parent percentage, then the gutter size is subtracted.
, adjust = function (offset) {
this.a.style[dimension] = calc + '(' + (offset / this.size * this.percentage) + '% - ' + this.aGutterSize + 'px)'
this.b.style[dimension] = calc + '(' + (this.percentage - (offset / this.size * this.percentage)) + '% - ' + this.bGutterSize + 'px)'
}
// 4. Define a few more functions that "balance" the entire split instance.
// Split.js tries it's best to cope with min sizes that don't add up.
// At some point this should go away since it breaks out of the calc(% - px) model.
// Maybe it's a user error if you pass uncomputable minSizes.
, fitMin = function () {
var self = this
, a = self.a
, b = self.b
if (a[getBoundingClientRect]()[dimension] < self.aMin) {
a.style[dimension] = (self.aMin - self.aGutterSize) + 'px'
b.style[dimension] = (self.size - self.aMin - self.aGutterSize) + 'px'
} else if (b[getBoundingClientRect]()[dimension] < self.bMin) {
a.style[dimension] = (self.size - self.bMin - self.bGutterSize) + 'px'
b.style[dimension] = (self.bMin - self.bGutterSize) + 'px'
}
}
, fitMinReverse = function () {
var self = this
, a = self.a
, b = self.b
if (b[getBoundingClientRect]()[dimension] < self.bMin) {
a.style[dimension] = (self.size - self.bMin - self.bGutterSize) + 'px'
b.style[dimension] = (self.bMin - self.bGutterSize) + 'px'
} else if (a[getBoundingClientRect]()[dimension] < self.aMin) {
a.style[dimension] = (self.aMin - self.aGutterSize) + 'px'
b.style[dimension] = (self.size - self.aMin - self.aGutterSize) + 'px'
}
}
, balancePairs = function (pairs) {
for (var i = 0; i < pairs.length; i++) {
calculateSizes.call(pairs[i])
fitMin.call(pairs[i])
}
for (i = pairs.length - 1; i >= 0; i--) {
calculateSizes.call(pairs[i])
fitMinReverse.call(pairs[i])
}
}
, setElementSize = function (el, size, gutterSize) {
// Split.js allows setting sizes via numbers (ideally), or if you must,
// by string, like '300px'. This is less than ideal, because it breaks
// the fluid layout that `calc(% - px)` provides. You're on your own if you do that,
// make sure you calculate the gutter size by hand.
if (typeof size !== 'string' && !(size instanceof String)) {
if (!isIE8) {
size = calc + '(' + size + '% - ' + gutterSize + 'px)'
} else {
size = options.sizes[i] + '%'
}
}
el.style[dimension] = size
}
// No-op function to prevent default. Used to prevent selection.
, noop = function () { return false }
// All DOM elements in the split should have a common parent. We can grab
// the first elements parent and hope users read the docs because the
// behavior will be whacky otherwise.
, parent = elementOrSelector(ids[0]).parentNode
// Set default options.sizes to equal percentages of the parent element.
if (!options.sizes) {
var percent = 100 / ids.length
options.sizes = []
for (i = 0; i < ids.length; i++) {
options.sizes.push(percent)
}
}
// Standardize minSize to an array if it isn't already. This allows minSize
// to be passed as a number.
if (!Array.isArray(options.minSize)) {
var minSizes = []
for (i = 0; i < ids.length; i++) {
minSizes.push(options.minSize)
}
options.minSize = minSizes
}
// 5. Loop through the elements while pairing them off. Every pair gets a
// `pair` object, a gutter, and isFirst/isLast properties.
//
// Basic logic:
//
// - Starting with the second element `i > 0`, create `pair` objects with
// `a = ids[i - 1]` and `b = ids[i]`
// - Set gutter sizes based on the _pair_ being first/last. The first and last
// pair have gutterSize / 2, since they only have one half gutter, and not two.
// - Create gutter elements and add event listeners.
// - Set the size of the elements, minus the gutter sizes.
//
// -----------------------------------------------------------------------
// | i=0 | i=1 | i=2 | i=3 |
// | | isFirst | | isLast |
// | pair 0 pair 1 pair 2 |
// | | | | |
// -----------------------------------------------------------------------
for (i = 0; i < ids.length; i++) {
var el = elementOrSelector(ids[i])
, isFirstPair = (i == 1)
, isLastPair = (i == ids.length - 1)
, size = options.sizes[i]
, gutterSize = options.gutterSize
, pair
, parentFlexDirection = window.getComputedStyle(parent).flexDirection
, temp
if (i > 0) {
// Create the pair object with it's metadata.
pair = {
a: elementOrSelector(ids[i - 1]),
b: el,
aMin: options.minSize[i - 1],
bMin: options.minSize[i],
dragging: false,
parent: parent,
isFirst: isFirstPair,
isLast: isLastPair,
direction: options.direction
}
// For first and last pairs, first and last gutter width is half.
pair.aGutterSize = options.gutterSize
pair.bGutterSize = options.gutterSize
if (isFirstPair) {
pair.aGutterSize = options.gutterSize / 2
}
if (isLastPair) {
pair.bGutterSize = options.gutterSize / 2
}
// if the parent has a reverse flex-direction, switch the pair elements.
if (parentFlexDirection === 'row-reverse' || parentFlexDirection === 'column-reverse') {
temp = pair.a;
pair.a = pair.b;
pair.b = temp;
}
}
// Determine the size of the current element. IE8 is supported by
// staticly assigning sizes without draggable gutters. Assigns a string
// to `size`.
//
// IE9 and above
if (!isIE8) {
// Create gutter elements for each pair.
if (i > 0) {
var gutter = document.createElement('div')
gutter.className = gutterClass
gutter.style[dimension] = options.gutterSize + 'px'
gutter[addEventListener]('mousedown', startDragging.bind(pair))
gutter[addEventListener]('touchstart', startDragging.bind(pair))
parent.insertBefore(gutter, el)
pair.gutter = gutter
}
// Half-size gutters for first and last elements.
if (i === 0 || i == ids.length - 1) {
gutterSize = options.gutterSize / 2
}
}
// Set the element size to our determined size.
setElementSize(el, size, gutterSize)
// After the first iteration, and we have a pair object, append it to the
// list of pairs.
if (i > 0) {
pairs.push(pair)
}
}
// Balance the pairs to try to accomodate min sizes.
balancePairs(pairs)
return {
setSizes: function (sizes) {
for (var i = 0; i < sizes.length; i++) {
if (i > 0) {
var pair = pairs[i - 1]
setElementSize(pair.a, sizes[i - 1], pair.aGutterSize)
setElementSize(pair.b, sizes[i], pair.bGutterSize)
}
}
},
collapse: function (i) {
var pair
if (i === pairs.length) {
pair = pairs[i - 1]
calculateSizes.call(pair)
adjust.call(pair, pair.size - Math.max(pair.bGutterSize, pair.aMin))
} else {
pair = pairs[i]
calculateSizes.call(pair)
adjust.call(pair, Math.max(pair.aGutterSize, pair.aMin))
}
},
destroy: function () {
for (var i = 0; i < pairs.length; i++) {
pairs[i].parent.removeChild(pairs[i].gutter)
pairs[i].a.style[dimension] = ''
pairs[i].b.style[dimension] = ''
}
}
}
}
// Play nicely with module systems, and the browser too if you include it raw.
if (typeof exports !== 'undefined') {
if (typeof module !== 'undefined' && module.exports) {
exports = module.exports = Split
}
exports.Split = Split
} else {
global.Split = Split
}
// Call our wrapper function with the current global. In this case, `window`.
}).call(window);

View File

@@ -1,4 +1,4 @@
window.jsLibs = [
export const jsLibs = [
{
url: 'https://code.jquery.com/jquery-3.2.1.min.js',
label: 'jQuery',
@@ -58,6 +58,23 @@ window.jsLibs = [
label: 'D3',
type: 'js'
},
{
url: 'https://cdnjs.cloudflare.com/ajax/libs/p5.js/0.6.1/p5.min.js',
label: 'P5.js',
type: 'js'
},
{
url:
'https://cdnjs.cloudflare.com/ajax/libs/p5.js/0.6.1/addons/p5.dom.min.js',
label: 'P5.js DOM',
type: 'js'
},
{
url:
'https://cdnjs.cloudflare.com/ajax/libs/p5.js/0.6.1/addons/p5.sound.min.js',
label: 'P5.js Sound',
type: 'js'
},
{
url:
'https://cdnjs.cloudflare.com/ajax/libs/underscore.js/1.8.3/underscore-min.js',
@@ -96,7 +113,7 @@ window.jsLibs = [
type: 'js'
}
];
window.cssLibs = [
export const cssLibs = [
{
url:
'https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css',

View File

@@ -1,14 +0,0 @@
(function(w) {
window.loadJS = function(src) {
var d = deferred();
var ref = w.document.getElementsByTagName('script')[0];
var script = w.document.createElement('script');
script.src = src;
script.async = true;
ref.parentNode.insertBefore(script, ref);
script.onload = function() {
d.resolve();
};
return d.promise;
};
})(window);

View File

@@ -1,8 +1,8 @@
(function() {
const noticationContainerEL = $('#js-alerts-container');
var hideTimeout;
function addNotification(msg) {
const noticationContainerEL = $('#js-alerts-container');
// var n = document.createElement('div');
// div.textContent = msg;
// noticationContainerEL.appendChild(n);
@@ -15,7 +15,7 @@
}, 2000);
}
window.alertsService = {
export const alertsService = {
add: addNotification
};
})();
window.alertsService = alertsService;

View File

@@ -1,16 +0,0 @@
<a d-click="onModalCloseBtnClick" href="" aria-label="Close modal" title="Close" class="js-modal__close-btn modal__close-btn">
<svg>
<use xlink:href="#cross-icon"></use>
</svg>
</a>
<h2>Import your creations in your account</h2>
<div>
<p>You have <span id="oldSavedCreationsCountEl"></span> creations saved in your local machine. Do you want to import those creations in your account so they are more secure and accessible anywhere?</p>
<p>It's okay if you don't want to. You can simply logout and access them anytime on this browser.</p>
<div class="flex flex-h-end">
<button d-click="dontAskToImportAnymore" class="btn">Don't ask me again</button>
<button d-click="importCreationsAndSettingsIntoApp" class="btn btn--primary ml-1">Yes, please import</button>
</div>
</div>

View File

@@ -1,520 +0,0 @@
<a d-click="onModalCloseBtnClick" href="" aria-label="Close notifications" title="Close" class="js-modal__close-btn modal__close-btn">
<svg>
<use xlink:href="#cross-icon"></use>
</svg>
</a>
<h1>Whats new?</h1>
<div class="notification">
<span class="notification__version">3.2.0</span>
<ul>
<li>
<strong>🚀 Loop timeout setting</strong>: You now have a setting to tweak the maximum timeout of a loop iteration before it's marked as infinite loop.
</li>
<li><strong>♿️ Accessibility</strong>: Modals now have proper keyboard navigation integrated.</li>
<li><strong>♿️ Accessibility</strong>: Color contrast improvements.</li>
<li>🚀 Popular libraries list updated. Thanks
<a href="https://github.com/diomed" target="_blank">@diomed</a> & <a href="https://github.com/leninalbertolp" target="_blank">@leninalbertolp</a>
</li>
<li>
<strong>🔧 Bugfix</strong>: Modal take up appropriate width instead of spanning full width.
</li>
<br>
<li>
<strong>🚀 Announcement</strong>: Hi! I am Kushagra Gour (creator of Web Maker) and I have launched a
<a href="https://patreon.com/kushagra"
target="_blank">Patreon campaign</a>. If you love Web Maker, consider pledging to
<a href="https://patreon.com/kushagra" target="_blank">support me</a> :)</li>
<li>
<a href="https://github.com/chinchang/web-maker/issues" target="_blank">Suggest features or report bugs.</a>
</li>
<li>Web Maker now has more than 50K weekly active users! Thank you for being a part of this community of awesome developers.
If you find Web Maker helpful,
<a href="https://chrome.google.com/webstore/detail/web-maker/lkfkkhfhhdkiemehlpkgjeojomhpccnh/reviews"
target="_blank" class="btn">Please rate Web Maker
<span class="star"></span>
</a>&nbsp;
<a href="http://twitter.com/share?url=https://webmakerapp.com/&text=Web Maker - A blazing fast %26 offline web playground! via @webmakerApp&related=webmakerApp&hashtags=web,editor,chrome,extension"
target="_blank" target="_blank" class="btn">Share it</a>&nbsp;
<a aria-label="Support the developer" d-click="openSupportDeveloperModal" data-event-action="supportDeveloperChangelogBtnClick"
class="btn btn-icon">Support the developer</a>
</li>
</ul>
</div>
<div class="notification">
<span class="notification__version">3.1.1</span>
<ul>
<li>
<strong>Bugfix</strong>: Fix the "Run" button not refreshing the preview after release 3.0.4.
</li>
</ul>
</div>
<div class="notification">
<span class="notification__version">3.1.0</span>
<ul>
<li>
<strong>Mobile Support (app only).</strong>: Make the Web Maker app usable on mobile. This is only for web app as Chrome extensions don't run on mobile.
</li>
</ul>
</div>
<div class="notification">
<span class="notification__version">3.0.4</span>
<ul>
<li>
<strong>Bugfix</strong>: Guarantee code doesn't execute when "auto preview" is off.</li>
<li>Add link to our new
<a href="https://web-maker.slack.com" target="_blank">Slack channel</a> 🤗.
</li>
</ul>
</div>
<div class="notification">
<span class="notification__version">3.0.3</span>
<ul>
<li>
<strong>Bugfix (extension)</strong>: "Save as HTML" file saves with correct extension.</li>
</ul>
</div>
<div class="notification">
<span class="notification__version">3.0.1</span>
<ul>
<li>After months of work, here is Web Maker 3.0.
<a href="https://medium.com/web-maker/web-maker-3-0-is-here-f158a40eeaee"
target="_blank">Read the blog post about it</a>.</li>
<li>Web Maker is no more just a Chrome extension, it is also available as web app that runs offline just like the extension!
Checkout it out ->
<a href="https://webmakerapp.com/app/" target="_blank">https://webmakerapp.com/app/</a>.</li>
<li>Now use Web Maker web app on any modern browser (tested with Chrome and Firefox).</li>
<li>
<strong>User Accounts</strong> - The much requested user accounts are here. Now maintain your account and store all your creations
in the cloud and access them anywhere anytime.</li>
<li>
<strong>New layout mode</strong> - One more layout mode, that lets you align all the panes vertically.</li>
<li>
<strong>No more restriction on scripts (Web app only)</strong> - If you are using the web app, there is no more a restriction
to load scripts from only specific domains. Load any script!</li>
<li>
<strong>Inline scripts (Web app only)</strong> - The restriction of writing JavaScript only in JS pane is also removed.</li>
</ul>
</div>
<div class="notification">
<span class="notification__version">2.9.7</span>
<ul>
<li>
<a href="https://tailwindcss.com/" target="_blank">Tailwind CSS</a> added to popular CSS libraries list. Thanks
<a href="https://github.com/diomed" target="_blank">diomed</a>.</li>
<li>Popular libraries list updated. Thanks
<a href="https://github.com/diomed" target="_blank">diomed</a>.</li>
<li>
<strong>Dev</strong>: Bug fixes and code refactoring to make things simple. Thanks
<a href="https://github.com/iamandrewluca"
target="_blank">iamandrewluca</a>.</li>
</ul>
</div>
<div class="notification">
<span class="notification__version">2.9.6</span>
<ul>
<li>
<strong>Bugfix</strong>: Fix close buttons not working in notifications and keyboard shortcuts modal.</li>
<li>
<strong>Bugfix</strong>: Fix keyboard shortcut to see keyboard shortcuts :) Thanks
<a href="https://github.com/ClassicOldSong"
target="_blank">ClassicOldSong</a>.</li>
</ul>
</div>
<div class="notification">
<span class="notification__version">2.9.5</span>
<ul>
<li>
<a href="https://medium.com/web-maker/release-2-9-5-add-library-search-pane-collapsing-ux-improvements-more-1085216c1301"
target="_blank">Read blog post about this release.</a>
</li>
<li>
<strong>Keyboard shortcuts panel</strong>: Add a list of all keyboard shotcuts. Access with
<code> Ctrl/⌘ + Shift + ?</code> or click keyboard button in footer.</li>
<li>
<strong>Add external library</strong>: Better UX for searching third party libraries.</li>
<li>
<strong>Improvement</strong>: Code panes now go fullscreen when double-clicked on their headers - which is much more intuitive
behavior based on feedback from lot of developers.</li>
<li>
<strong>Improvement</strong>: Add
<code>allowfullscreen</code> attribute on iframes. Thanks
<a href="https://github.com/ClassicOldSong" target="_blank">ClassicOldSong</a>.</li>
<li>
<strong>Bugfix</strong> - Stop screenlog.js from showing up in the exported HTML.</li>
<li>Popular external libraries list updated. Thanks
<a href="https://github.com/jlapitan" target="_blank">jlapitan</a>.</li>
</ul>
</div>
<div class="notification">
<span class="notification__version">2.9.4</span>
<ul>
<li>
<strong>Improvement</strong>: Atomic CSS (Atomizer) has been updated to latest version. Now you can do things like psuedo elements.
Learn More.</li>
<li>
<strong>Bugfix</strong> - Logging circular objects is now possible. It won't show in the Web Maker console, but will show fine
in browser's console.</li>
<li>
<strong>Bugfix</strong> - Console's z-index issue has been fixed.</li>
</ul>
</div>
<div class="notification">
<span class="notification__version">2.9.3</span>
<ul>
<li>Choose the save location while exporting your saved creations. Now easily sync them to your Dropbox or any cloud storage.</li>
<li>All modals inside the app now have a close button.</li>
<li>Checkbox that showed on clicking a boolean value is now removed. Thanks
<a href="https://github.com/gauravmuk" target="_blank">Gaurav Nanda</a>.</li>
<li>
<strong>Bugfix</strong> - Screenshots on retina device are now correct. Thanks
<a href="https://github.com/AshBardhan" target="_blank">Ashish Bardhan</a>.</li>
<li>
<strong>Bugfix</strong> - Double console log in detached mode fixed.</li>
<li>
<strong>Bugfix</strong> - Console.clear now works in detached mode too.</li>
<li>
<strong>Bugfix</strong> - DOCTYPE added in preview.</li>
<li>
<strong>Bugfix</strong> - Typo correction in README. Thanks
<a href="https://github.com/AdilMah" target="_blank">Adil Mahmood</a>.</li>
<li>gstatic.com is available to load external JavaScripts from.</li>
<li>Popular libraries list updated. Thanks
<a href="https://github.com/diomed" target="_blank">diomed</a>.</li>
<li>Added
<a href="https://github.com/chinchang/web-maker/blob/master/CONTRIBUTING.md" target="_blank">contribution guidelines</a> in the Github repository.</li>
</ul>
</div>
<div class="notification">
<span class="notification__version">2.9.2</span>
<ul>
<li>Minor bug fixes.</li>
</ul>
</div>
<div class="notification">
<span class="notification__version">2.9.1</span>
<ul>
<li>
<a href="https://medium.com/web-maker/v2-9-lots-of-goodies-bd1e939571f6" target="_blank">Read blog post about last release.</a>
</li>
<li>Use Ctrl/Cmd+D to select next occurence of matching selection.</li>
<li>Improve onboard experience.</li>
</ul>
</div>
<div class="notification">
<span class="notification__version">2.9.0</span>
<ul>
<li>
<a href="https://medium.com/web-maker/v2-9-lots-of-goodies-bd1e939571f6" target="_blank">Read blog post about this release.</a>
</li>
<li>
<strong>Detached Preview</strong> - Yes, you read that correct! You can now detach your preview and send it to your secondary
monitor.
</li>
<li>
<strong>Find & Replace</strong> - Long awaited, now its there. Ctrl/Cmd+f to find and add Alt to replace.</li>
<li>
<strong>Atomic CSS (Atomizer) configurations</strong> - Add custom config for Atomic CSS.
<a href="https://github.com/acss-io/atomizer#api"
target="_blank">Read more</a>.</li>
<li>
<strong>Light mode</strong> - This new setting makes Web Maker drop some heavy effects like blur etc to gain more performance.
Thanks
<a href="https://github.com/iamandrewluca" target="_blank">Andrew</a>.</li>
<li>
<strong>Preserve logs setting</strong> - Choose whether or not to preserve logs across preview refreshes. Thanks
<a href="https://github.com/BasitAli"
target="_blank">Basit</a>.</li>
<li>
<strong>Line wrap setting</strong> - As the name says.</li>
<li>Semantic UI added to popular libraries.</li>
<li>Bootstrap, Vue, UI-Kit and more updated to latest versions in popular libraries.</li>
<li>UX improvements in settings UI</li>
<li>
<strong>Bugfix</strong> - Trigger preview refresh anytime with Ctrl/⌘ + Shift + 5. Even with auto-preview on.</li>
</ul>
</div>
<div class="notification">
<span class="notification__version">2.8.1</span>
<ul>
<li>Vue.js & UIKit version updated to latest version in 'Add Library' list.</li>
<li>UTF-8 charset added to preview HTML to support universal characters.</li>
</ul>
</div>
<div class="notification">
<span class="notification__version">2.8.0</span>
<ul>
<li>
<a href="https://medium.com/web-maker/release-v2-8-is-out-f44e6ea5d9c4" target="_blank">Read blog post about this release.</a>
</li>
<li>
<strong>Auto Save</strong> - Your creations now auto-save after your first manual save. This is configurable from settings.
</li>
<li>
<strong>Base2Tone-Meadow Editor Theme</strong> - First user contributed theme. Thanks to Diomed.</li>
<li>
<strong>Use System Fonts</strong> - You can now use any of your existing system fonts in the editor!</li>
<li>
<strong>Matching Tag Highlight</strong> - Cursor over any HTML tag would highlight the matching pair tag.</li>
<li>Auto-completion suggestion can now be switched off from settings.</li>
<li>
<strong>Improvement</strong> - Stop white flicker in editor when the app opens.
</li>
<li>
<strong>Bugfix</strong> - Add Babel Polyfill to enable use of next-gen built-ins like Promise or WeakMap.
</li>
<li>Vue.js version updated to 2.4.0 in popular library list.</li>
<li>Downloads permission is optional. Asked only when you take screenshot.</li>
</ul>
</div>
<div class="notification">
<span class="notification__version">2.7.2</span>
<ul>
<li>
<strong>External Libraries</strong> - Add Foundation.js and update UIKit 3 to latest beta.</li>
<li>
<strong>rawgit.com</strong> &
<strong>wzrd.in</strong> domains are now allowed for loading external libraries from.</li>
<li>Minor booting speed improvements</li>
</ul>
</div>
<div class="notification">
<span class="notification__version">2.7.1</span>
<ul>
<li>
<strong>Framer.js support</strong> - You can now load the latest framer.js library from
<a href="https://builds.framerjs.com/"
target="_blank">framer builds page</a> and start coding framer prototypes.</li>
<li>
<strong>Bugfix</strong>: Edit on CodePen is back in action.</li>
<li>
<strong>Bugfix</strong>: Autocompletion menu doesn't show on cut and paste now.</li>
<li>
<strong>Bugfix</strong>: Updated & fixed urls of some common external libraries to latest versions. UIKit3 & Bootstrap 4α are
now in the list.</li>
<li>Preprocessor selector are now more accessible.</li>
</ul>
</div>
<div class="notification">
<span class="notification__version">2.7.0</span>
<ul>
<li>
<strong>Fork any creation!</strong>: Now you can fork any existing creation of yours to start a new work based on it. One big
use case of this feature is "Templates"!
<a target="_blank" href="https://kushagragour.in/blog/2017/05/web-maker-fork-templates/?utm_source=webmakerapp&utm_medium=referral">Read more about it</a>.</li>
<li>
<strong>Fonts 😍 </strong>: Super-awesome 4 fonts (mostly with ligature support) now available to choose from. Fira Code is the
default font now.</li>
<li>Updated most used external libraries to latest versions.</li>
<li>
<strong>Bugfix</strong>: Add missing Bootstrap JS file to most used external libraries list.</li>
<li>Several other minor bugfixes and improvements to make Web Maker awesome!</li>
<li>Great news to share with you - Web Maker has been featured on the Chrome Webstore homepage! Thanks for all the love :)</li>
</ul>
</div>
<div class="notification">
<span class="notification__version">2.6.1</span>
<ul>
<li>
<strong>Bugfix</strong>: Emojis vanishing while exporting to Codepen has been fixed.</li>
<li>
<strong>Bugfix</strong>:
<code>console.clear()</code> now doesn't error and clears the inbuilt console.</li>
<li>
<strong>Bugfix</strong>: External libraries added to the creation are exported as well to Codepen.</li>
</ul>
</div>
<div class="notification">
<span class="notification__version">2.6.0</span>
<ul>
<li>
<strong>The "Console"</strong>: The most awaited feature is here! There is now an inbuilt console to see your logs, errors and
for quickly evaluating JavaScript code inside your preview. Enjoy! I also a
<a href="https://kushagragour.in/blog/2017/05/web-maker-console-is-here/?utm_source=webmakerapp&utm_medium=referral"
target="_blank">blog post about it</a>.</li>
<li>Number slider which popped on clicking any number in the code has been removed due to poor user experience.</li>
<li>Minor usability improvements.</li>
</ul>
</div>
<div class="notification">
<span class="notification__version">2.5.0</span>
<ul>
<li>
<strong>Atomic CSS</strong>: Use can now use Atomic CSS(ACSS) in your work!
<a href="https://acss.io/" target="_blank">Read more about it here</a>.</li>
<li>
<strong>Search your saved creations</strong>: Easily search through all your saved creations by title.</li>
<li>
<strong>Configurable Auto-preview</strong> - You can turn off the auto preview in settings if you don't want the preview to update
as you type.</li>
<li>
<strong>Configurable refresh on resize</strong> - You can configure whether you want the preview to refresh when you resize the
preview panel.</li>
<li>
<strong>Bugfix</strong> - Fix indentation
<a href="https://github.com/chinchang/web-maker/issues/104" target="_blank">issue</a> with custom indentation size.</li>
</ul>
</div>
<div class="notification">
<span class="notification__version">2.4.2</span>
<ul>
<li>
<strong>Improved infinite loop protection</strong>: Infinite loop protection is now faster and more reliable. And works without
the need of Escodegen. Thanks to Ariya Hidayat!</li>
<li>
<strong>Bugfix</strong> - Default parameters not working in JavaScript is fixed.</li>
</ul>
</div>
<div class="notification">
<span class="notification__version">2.4.0</span>
<ul>
<li>
<strong>Import/Export</strong>: Your creations are most important. Now export all your creations into a single file as a backup
that can be imported anytime & anywhere.</li>
<li>
<strong>Editor themes</strong>: You have been heard. Now you can choose from a huge list of wonderful editor themes!</li>
<li>
<strong>Identation settings</strong>: Not a spaces fan? Switch to tabs and set your indentation size.</li>
<li>
<strong>Vim key bindings</strong>: Rejoice Vim lovers!</li>
<li>
<strong>Code blast</strong>: Why don't you try coding with this switched on from the settings? Go on...</li>
<li>
<strong>Important</strong>: Due to security policy changes from Chrome 57 onwards, Web Maker now allows loading external JavaScript
libraries only from certain whitelisted domains (localhost, https://ajax.googleapis.com, https://code.jquery.com, https://cdnjs.cloudflare.com,
https://unpkg.com, https://maxcdn.com, https://cdn77.com, https://maxcdn.bootstrapcdn.com, https://cdn.jsdelivr.net/)</li>
<li>Save button now highlights when you have unsaved changes.</li>
<li>Jade is now called Pug. Just a name change.</li>
</ul>
</div>
<div class="notification">
<span class="notification__version">2.3.2</span>
<ul>
<li>Update Babel to support latest and coolest ES6 features.</li>
<li>Improve onboarding experience at first install.</li>
</ul>
</div>
<div class="notification">
<span class="notification__version">2.3.1</span>
<ul>
<li>
<strong>Bugfix</strong> - Splitting of code and preview panes is remembered by the editor.</li>
<li>Title of the creation is used for the file name when saving as HTML.</li>
</ul>
</div>
<div class="notification">
<span class="notification__version">2.3.0</span>
<ul>
<li>
<strong>Add Library Autocompletion</strong> - Just start typing the name of library and you'll be shown matching libraries from
cdnjs.</li>
<li>
<strong>Preview Screenshot Capture</strong> - Want to grab a nice screenshot of your creation. You have it! Click and capture.</li>
<li>
<strong>Auto Indent Code</strong> - Select your code and hit Shift-Tab to auto-indent it!</li>
<li>
<strong>Keyboard Navigation in Saved List</strong> - Now select your creation using arrow keys and hit ENTER to open it.</li>
<li>Highlight active line in code panes.</li>
<li>
<strong>Bugfix</strong> - Fix in generated title of new creation.</li>
<li>
<strong>Bugfix</strong> - HTML autocompletion is manual triggered now with Ctrl+Space.</li>
</ul>
</div>
<div class="notification">
<span class="notification__version">2.2.0</span>
<ul>
<li>
<strong>Code Autocompletion</strong> - See code suggestions while you type!</li>
<li>
<strong>Full Screen Preview</strong> - Checkout your creation in a full-screen layout.</li>
<li>
<strong>SASS</strong> - SASS support added for CSS.</li>
<li>
<strong>Faster CSS update</strong> - Preview updates instantly without refresh when just CSS is changed.</li>
<li>
<strong>Bugfix</strong> - Indentation fixed while going on new line.</li>
<li>
<strong>Bugfix</strong> - Works even in Chrome Canary now. Though libraries can be added only through CDNs.</li>
</ul>
</div>
<div class="notification">
<span class="notification__version">2.1.0</span>
<ul>
<li>
<strong>TypeScript</strong> - Now you can code in TypeScript too!</li>
<li>
<strong>Stylus Preprocessor</strong> - Stylus supported adding for CSS.</li>
<li>
<strong>Code Folding</strong> - Collapse large code blocks for easy editing.</li>
<li>
<strong>Bugfix</strong> - Support JSX in JavaScript.</li>
<li>Better onboarding for first time users.</li>
</ul>
</div>
<div class="notification">
<span class="notification__version">2.0.0</span>
<ul>
<li>
<strong>Save and Load</strong> - Long pending and super-useful, now you can save your creations and resume them anytime later.</li>
<li>
<strong>Insert JS & CSS</strong> - Load popular JavaScript & CSS libraries in your work without writing any code.</li>
<li>
<strong>Collapsed Panes</strong> - Collapse/uncollapse code panes with a single click. Your pane configuration is even saved with
every creation!</li>
<li>
<strong>Quick color & number change</strong> - Click on any color or number and experiment with quick values using a slider.</li>
<li>
<strong>Linting</strong> - See your code errors right where you are coding.</li>
<li>No more browser hang due to infinite loops!</li>
<!-- <li><a href="https://kushagragour.in/blog/web-maker-2">Read more about this big release</a></li> -->
</ul>
</div>
<div class="notification">
<span class="notification__version">1.7.0</span>
<ul>
<li>
<strong>Preprocessors!</strong> - Enjoy a whole list of preprocessors for HTML(Jade & markdown), CSS(SCSS & LESS) and JavaScript(CoffeeScript
& Babel).</li>
<li>More awesome font for code.</li>
</ul>
</div>
<div class="notification">
<span class="notification__version">1.6.0</span>
<ul>
<li>You can now configure Web-Maker to not replace new tab page from the settings. It is always accessible from the icon in
the top-right.</li>
<li>Download current code as HTML file with Ctrl/⌘ + S keyboard shortcut.</li>
<li>New notifications panel added so you are always aware of the new changes in Web-Maker.</li>
</ul>
</div>

View File

@@ -1,73 +0,0 @@
<a d-click="onModalCloseBtnClick" href="" aria-label="Close help modal" title="Close" class="js-modal__close-btn modal__close-btn">
<svg>
<use xlink:href="#cross-icon"></use>
</svg>
</a>
<h1>
<div class="web-maker-with-tag">Web Maker</div>
<small style="font-size:14px;"> v3.2.0</small>
</h1>
<div>
<p>Made with
<span style="margin-right: 8px;">💖</span> &
<span style="margin-right: 8px;">🙌</span> by
<a href="https://twitter.com/chinchang457" target="_blank">Kushagra Gour</a>
</p>
<p>
<a href="/docs" target="_blank">Read the documentation</a>.
</p>
<p>Tweet out your feature requests, comments & suggestions to
<a target="_blank" href="https://twitter.com/webmakerApp">@webmakerApp</a>.</p>
<p>Like this extension? Please
<a href="https://chrome.google.com/webstore/detail/web-maker/lkfkkhfhhdkiemehlpkgjeojomhpccnh/reviews" target="_blank">rate it here</a>.</p>
<p>
<button aria-label="Support the developer" d-click="openSupportDeveloperModal" data-event-action="supportDeveloperHelpBtnClick"
class="btn btn-icon">
<svg>
<use xlink:href="#gift-icon"></use>
</svg>Support the developer</button>
<a aria-label="Rate Web Maker" href="https://chrome.google.com/webstore/detail/web-maker/lkfkkhfhhdkiemehlpkgjeojomhpccnh/reviews"
target="_blank" class="btn btn-icon">
<svg>
<use xlink:href="#heart-icon"></use>
</svg>Share Web Maker</a>
<a aria-label="Chat" href="https://web-maker.slack.com" target="_blank" class="btn btn-icon">
<svg>
<use xlink:href="#chat-icon"></use>
</svg>Chat</a>
<a aria-label="Report a Bug" href="https://github.com/chinchang/web-maker/issues" target="_blank" class="btn btn-icon">
<svg>
<use xlink:href="#bug-icon"></use>
</svg>Report a bug</a>
</p>
<p>
<h3>Awesome libraries used</h3>
<ul>
<li>
<a target="_blank" href="https://kushagragour.in/lab/hint/">Hint.css</a> &
<a target="_blank" href="https://github.com/chinchang/screenlog.js">Screenlog.js</a> - By me :)</li>
<li>
<a target="_blank" href="https://nathancahill.github.io/Split.js/">Split.js</a> - Nathan Cahill</li>
<li>
<a target="_blank" href="https://codemirror.net/">Codemirror</a> - Marijn Haverbeke</li>
<li>
<a target="_blank" href="https://emmet.io/">Emmet</a> - Sergey Chikuyonok</li>
<li>
<a target="_blank" href="http://esprima.org/">Esprima</a> - Ariya Hidayat</li>
<li>
<a target="_blank" href="https://github.com/enjalot/Inlet">Inlet</a> - Ian Johnson</li>
<li>
<a target="_blank" href="https://webmakerapp.com/">Web Maker!</a> - whhat!</li>
</ul>
</p>
<p>
<h3>License</h3>
"Web Maker" is
<a target="_blank" href="https://github.com/chinchang/web-maker">open-source</a> under the
<a href="https://opensource.org/licenses/MIT" target="_blank">MIT License</a>.
</p>
</div>

View File

@@ -1,106 +0,0 @@
<a d-click="onModalCloseBtnClick" href="" aria-label="Close keyboard shortcuts modal" title="Close" class="js-modal__close-btn modal__close-btn">
<svg>
<use xlink:href="#cross-icon"></use>
</svg>
</a>
<h1>Keyboard Shortcuts</h1>
<div class="flex">
<div>
<h2>Global</h2>
<p>
<span class="kbd-shortcut__keys">
Ctrl/⌘ + Shift + ?
</span>
<span class="kbd-shortcut__details">See keyboard shortcuts</span>
</p>
<p>
<span class="kbd-shortcut__keys">
Ctrl/⌘ + Shift + 5
</span>
<span class="kbd-shortcut__details">Refresh preview</span>
</p>
<p>
<span class="kbd-shortcut__keys">
Ctrl/⌘ + S
</span>
<span class="kbd-shortcut__details">Save current creations</span>
</p>
<p>
<span class="kbd-shortcut__keys">
Ctrl/⌘ + O
</span>
<span class="kbd-shortcut__details">Open list of saved creations</span>
</p>
<p>
<span class="kbd-shortcut__keys">
Ctrl + L
</span>
<span class="kbd-shortcut__details">Clear console (works when console input is focused)</span>
</p>
<p>
<span class="kbd-shortcut__keys">
Esc
</span>
<span class="kbd-shortcut__details">Close saved creations panel & modals</span>
</p>
</div>
<div class="ml-2">
<h2>Editor</h2>
<p>
<span class="kbd-shortcut__keys">
Ctrl/⌘ + F
</span>
<span class="kbd-shortcut__details">Find</span>
</p>
<p>
<span class="kbd-shortcut__keys">
Ctrl/⌘ + G
</span>
<span class="kbd-shortcut__details">Select next match</span>
</p>
<p>
<span class="kbd-shortcut__keys">
Ctrl/⌘ + Shift + G
</span>
<span class="kbd-shortcut__details">Select previous match</span>
</p>
<p>
<span class="kbd-shortcut__keys">
Ctrl/⌘ + Opt/Alt + F
</span>
<span class="kbd-shortcut__details">Find & replace</span>
</p>
<p>
<span class="kbd-shortcut__keys">
Shift + Tab
</span>
<span class="kbd-shortcut__details">Realign code</span>
</p>
<p>
<span class="kbd-shortcut__keys">
Ctrl/⌘ + ]
</span>
<span class="kbd-shortcut__details">Indent code right</span>
</p>
<p>
<span class="kbd-shortcut__keys">
Ctrl/⌘ + [
</span>
<span class="kbd-shortcut__details">Indent code left</span>
</p>
<p>
<span class="kbd-shortcut__keys">
Tab
</span>
<span class="kbd-shortcut__details">Emmet code completion <a href="https://emmet.io/" target="_blank">Read more</a></span>
</p>
<p>
<span class="kbd-shortcut__keys">
Ctrl/⌘ + /
</span>
<span class="kbd-shortcut__details">Single line comment</span>
</p>
</div>
</div>

View File

@@ -1,40 +0,0 @@
<a d-click="onModalCloseBtnClick" href="" aria-label="Close Login modal" title="Close" class="js-modal__close-btn modal__close-btn">
<svg>
<use xlink:href="#cross-icon"></use>
</svg>
</a>
<h2>Login / Signup</h2>
<div>
<p>
<button
d-click="login"
class="social-login-btn social-login-btn--github btn btn-icon btn--big full-width hint--right hint--always"
data-auth-provider="github"
data-hint="You logged in with Github last time">
<svg><use xlink:href="#github-icon"></use></svg>Login with Github
</button>
</p>
<p>
<button
d-click="login"
class="social-login-btn social-login-btn--google btn btn-icon btn--big full-width hint--right hint--always"
data-auth-provider="google"
data-hint="You logged in with Google last time">
<svg><use xlink:href="#google-icon"></use></svg>Login with Google
</button>
</p>
<p class="mb-2">
<button
d-click="login"
class="social-login-btn social-login-btn--facebook btn btn-icon btn--big full-width hint--right hint--always"
data-auth-provider="facebook"
data-hint="You logged in with Facebook last time">
<svg><use xlink:href="#fb-icon"></use></svg>Login with Facebook
</button>
</p>
<p>
Join a community of 50,000+ Developers
</p>
</div>

View File

@@ -1,73 +0,0 @@
<div class="tac">
<svg width="130px" height="50px" aria-hidden="true">
<use xlink:href="#logo" />
</svg>
<h1 style="margin-top:20px">Welcome to Web Maker<span class="show-when-app"> 3.0 (beta)</span></h1>
</div>
<div class="flex" style="margin-top:40px;">
<div class="onboard-step show-when-app">
<div class="tac">
<svg class="onboard-step__icon" viewBox="0 0 24 24">
<path d="M13.64,21.97C13.14,22.21 12.54,22 12.31,21.5L10.13,16.76L7.62,18.78C7.45,18.92 7.24,19 7,19A1,1 0 0,1 6,18V3A1,1 0 0,1 7,2C7.24,2 7.47,2.09 7.64,2.23L7.65,2.22L19.14,11.86C19.57,12.22 19.62,12.85 19.27,13.27C19.12,13.45 18.91,13.57 18.7,13.61L15.54,14.23L17.74,18.96C18,19.46 17.76,20.05 17.26,20.28L13.64,21.97Z"
/>
</svg>
</div>
<p>
Open Web Maker anytime by visiting <a>https://webmakerapp.com/app/</a> - Even when you are offline! It just works! 😱 <strong>Drag the following bookmarklet</strong> on your bookmark bar to create a quick access shortcut:
<a class="ml-1 bookmarklet" href="https://webmakerapp.com/app/">
<svg width="20" height="20" aria-hidden="true">
<use xlink:href="#logo" />
</svg>
Web Maker
</a>
</p>
</div>
<div class="onboard-step show-when-extension">
<div class="tac">
<svg class="onboard-step__icon" viewBox="0 0 24 24">
<path d="M13.64,21.97C13.14,22.21 12.54,22 12.31,21.5L10.13,16.76L7.62,18.78C7.45,18.92 7.24,19 7,19A1,1 0 0,1 6,18V3A1,1 0 0,1 7,2C7.24,2 7.47,2.09 7.64,2.23L7.65,2.22L19.14,11.86C19.57,12.22 19.62,12.85 19.27,13.27C19.12,13.45 18.91,13.57 18.7,13.61L15.54,14.23L17.74,18.96C18,19.46 17.76,20.05 17.26,20.28L13.64,21.97Z"
/>
</svg>
</div>
<p>
Open Web Maker anytime by clicking the
<svg class="relative" style="top:5px;" width="40" height="30">
<use xlink:href="#logo" />
</svg> button in top-right side of your browser.
</p>
</div>
<div class="onboard-step">
<div class="tac">
<svg class="onboard-step__icon" viewBox="0 0 24 24">
<use xlink:href="#settings-icon"></use>
</svg>
</div>
<p>
Configure and customize settings by clicking the gear icon (
<svg style="width:18px;height:18px;position:relative;top:3px;fill:#888"
viewBox="0 0 24 24">
<use xlink:href="#settings-icon"></use>
</svg>) in bottom right of the app.
</p>
</div>
<div class="onboard-step">
<div class="tac">
<svg class="onboard-step__icon" style="stroke-width:0.3px;">
<use xlink:href="#twitter-icon"></use>
</svg>
</div>
<p>
Follow <a href="https://twitter.com/intent/follow?screen_name=webmakerApp" targe="_blank">@webmakerApp</a> to know about the new upcoming
features!
</p>
</div>
</div>
<p class="tac show-when-app">
If you are an existing Chrome extension user, you can import your creations from there to here. <a href="https://medium.com/web-maker/importing-exporting-your-creations-d92e7de5c3dc" target="_blank">Learn how to export/import</a>.
</p>
<p class="tac">
<button class="btn btn--primary" d-click="closeAllOverlays">Lets start!</button>
</p>

View File

@@ -1,22 +0,0 @@
<a d-click="onModalCloseBtnClick" href="" aria-label="Close modal" title="Close" class="js-modal__close-btn modal__close-btn">
<svg>
<use xlink:href="#cross-icon"></use>
</svg>
</a>
<div class="tac">
<h1>Support the Developer</h1>
<p>Hi, <a href="https://kushagragour.in" target="_blank">Kushagra</a> here! Web Maker is a free and open-source project. To keep myself motivated for working on such open-source and free <a href="https://kushagragour.in/lab/" target="_blank">side projects</a>, I am accepting donations. Your pledge, no matter how small, will act as an appreciation towards my work and keep me going forward making Web Maker more awesome🔥. So please consider donating. 🙏🏼 (could be as small as $1/month).
</p>
<div class="flex flex-h-center" id="onboardDontShowInTabOptionBtn" d-click="onDontShowInTabClicked">
<a class="onboard-selection" href="https://patreon.com/kushagra" target="_blank" aria-label="Make a monthly pledge on Patreon">
<img src="patreon.png" height="60" alt="Become a patron image">
<h3 class="onboard-selection-text">Make a monthly pledge on Patreon</h3>
</a>
</div>
<a href="https://www.paypal.me/kushagragour" target="_blank" aria-label="Make a one time donation on Paypal">
Or, make a one time donation
</a>
</div>

View File

@@ -4,6 +4,5 @@
<iframe src="about://blank" frameborder="0" id="demo-frame" allowfullscreen></iframe>
<script src="detached-window.js"></script>
</body>

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

139
src/takeScreenshot.js Normal file
View File

@@ -0,0 +1,139 @@
import { handleDownloadsPermission, log } from './utils';
import { trackEvent } from './analytics';
function saveScreenshot(dataURI) {
// convert base64 to raw binary data held in a string
// doesn't handle URLEncoded DataURIs
var byteString = atob(dataURI.split(',')[1]);
// separate out the mime component
var mimeString = dataURI
.split(',')[0]
.split(':')[1]
.split(';')[0];
// write the bytes of the string to an ArrayBuffer
var ab = new ArrayBuffer(byteString.length);
var ia = new Uint8Array(ab);
for (var i = 0; i < byteString.length; i++) {
ia[i] = byteString.charCodeAt(i);
}
// create a blob for writing to a file
var blob = new Blob([ab], {
type: mimeString
});
var size = blob.size + 1024 / 2;
var d = new Date();
var fileName = [
'web-maker-screenshot',
d.getFullYear(),
d.getMonth() + 1,
d.getDate(),
d.getHours(),
d.getMinutes(),
d.getSeconds()
].join('-');
fileName += '.png';
function onWriteEnd() {
var filePath =
'filesystem:chrome-extension://' +
chrome.i18n.getMessage('@@extension_id') +
'/temporary/' +
fileName;
chrome.downloads.download(
{
url: filePath
},
function() {
// If there was an error, just open the screenshot in a tab.
// This happens in incognito mode where extension cannot access filesystem.
if (chrome.runtime.lastError) {
window.open(filePath);
}
}
);
}
function errorHandler(e) {
log(e);
}
// create a blob for writing to a file
window.webkitRequestFileSystem(
window.TEMPORARY,
size,
fs => {
fs.root.getFile(
fileName,
{
create: true
},
fileEntry => {
fileEntry.createWriter(fileWriter => {
fileWriter.onwriteend = onWriteEnd;
fileWriter.write(blob);
}, errorHandler);
},
errorHandler
);
},
errorHandler
);
}
export function takeScreenshot(boundRect) {
handleDownloadsPermission().then(() => {
// Hide tooltips so that they don't show in the screenshot
var s = document.createElement('style');
s.textContent =
'[class*="hint"]:after, [class*="hint"]:before { display: none!important; }';
document.body.appendChild(s);
function onImgLoad(image) {
var c = document.createElement('canvas');
var iframeBounds = boundRect;
c.width = iframeBounds.width;
c.height = iframeBounds.height;
var ctx = c.getContext('2d');
var devicePixelRatio = window.devicePixelRatio || 1;
ctx.drawImage(
image,
iframeBounds.left * devicePixelRatio,
iframeBounds.top * devicePixelRatio,
iframeBounds.width * devicePixelRatio,
iframeBounds.height * devicePixelRatio,
0,
0,
iframeBounds.width,
iframeBounds.height
);
image.removeEventListener('load', onImgLoad);
saveScreenshot(c.toDataURL());
}
setTimeout(() => {
chrome.tabs.captureVisibleTab(
null,
{
format: 'png',
quality: 100
},
function(dataURI) {
s.remove();
if (dataURI) {
var image = new Image();
image.src = dataURI;
image.addEventListener('load', () => onImgLoad(image, dataURI));
}
}
);
}, 50);
trackEvent('ui', 'takeScreenshotBtnClick');
});
}

View File

@@ -0,0 +1,21 @@
// Mock Browser API's which are not supported by JSDOM, e.g. ServiceWorker, LocalStorage
/**
* An example how to mock localStorage is given below 👇
*/
/*
// Mocks localStorage
const localStorageMock = (function() {
let store = {};
return {
getItem: (key) => store[key] || null,
setItem: (key, value) => store[key] = value.toString(),
clear: () => store = {}
};
})();
Object.defineProperty(window, 'localStorage', {
value: localStorageMock
}); */

View File

@@ -0,0 +1,3 @@
// This fixed an error related to the CSS and loading gif breaking my Jest test
// See https://facebook.github.io/jest/docs/en/webpack.html#handling-static-assets
module.exports = 'test-file-stub';

14
src/tests/header.test.js Normal file
View File

@@ -0,0 +1,14 @@
import { h, Component } from 'preact';
import Header from '../components/header';
import { Link } from 'preact-router/match';
// See: https://github.com/mzgoddard/preact-render-spy
import { shallow, deep } from 'preact-render-spy';
describe('Initial Test of the Header', () => {
test('Header renders 3 nav items', () => {
const context = shallow(<Header />);
expect(context.find('h1').text()).toBe('Preact App');
expect(context.find(<Link />).length).toBe(3);
});
});

View File

@@ -1,154 +0,0 @@
// textarea-autocomplete.js
(function() {
class TextareaAutoComplete {
constructor(textarea, options) {
this.t = textarea;
this.filter = options.filter;
this.selectedCallback = options.selectedCallback;
var wrap = document.createElement('div');
wrap.classList.add('btn-group');
textarea.parentElement.insertBefore(wrap, textarea);
wrap.insertBefore(textarea, null);
this.list = document.createElement('ul');
this.list.classList.add('dropdown__menu');
this.list.classList.add('autocomplete-dropdown');
wrap.insertBefore(this.list, null);
this.loader = document.createElement('div');
this.loader.classList.add('loader');
this.loader.classList.add('autocomplete__loader');
this.loader.style.display = 'none';
wrap.insertBefore(this.loader, null);
// after list is insrted into the DOM, we put it in the body
// fixed at same position
setTimeout(() => {
requestIdleCallback(() => {
document.body.appendChild(this.list);
this.list.style.position = 'fixed';
});
}, 100);
this.t.addEventListener('input', e => this.onInput(e));
this.t.addEventListener('keydown', e => this.onKeyDown(e));
this.t.addEventListener('blur', e => this.closeSuggestions(e));
this.list.addEventListener('mousedown', e => this.onListMouseDown(e));
}
get currentLineNumber() {
return this.t.value.substr(0, this.t.selectionStart).split('\n').length;
}
get currentLine() {
var line = this.currentLineNumber;
return this.t.value.split('\n')[line - 1];
}
closeSuggestions() {
this.list.classList.remove('is-open');
this.isShowingSuggestions = false;
}
getList(input) {
var url = 'https://api.cdnjs.com/libraries?search=';
return fetch(url + input).then(response => {
return response.json().then(json => json.results);
});
}
replaceCurrentLine(val) {
var lines = this.t.value.split('\n');
lines.splice(this.currentLineNumber - 1, 1, val);
this.t.value = lines.join('\n');
}
onInput() {
var currentLine = this.currentLine;
if (currentLine) {
if (
currentLine.indexOf('/') !== -1 ||
currentLine.match(/https*:\/\//)
) {
return;
}
clearTimeout(this.timeout);
this.timeout = setTimeout(() => {
this.loader.style.display = 'block';
this.getList(currentLine).then(arr => {
this.loader.style.display = 'none';
if (!arr.length) {
this.closeSuggestions();
return;
}
this.list.innerHTML = '';
if (this.filter) {
/* eslint-disable no-param-reassign */
arr = arr.filter(this.filter);
}
for (var i = 0; i < Math.min(arr.length, 10); i++) {
this.list.innerHTML += `<li data-url="${arr[i].latest}"><a>${arr[
i
].name}</a></li>`;
}
this.isShowingSuggestions = true;
if (!this.textareaBounds) {
this.textareaBounds = this.t.getBoundingClientRect();
this.list.style.top = this.textareaBounds.bottom + 'px';
this.list.style.left = this.textareaBounds.left + 'px';
this.list.style.width = this.textareaBounds.width + 'px';
}
this.list.classList.add('is-open');
});
}, 500);
}
}
onKeyDown(event) {
var selectedItemElement;
if (!this.isShowingSuggestions) {
return;
}
if (event.keyCode === 27) {
this.closeSuggestions();
event.stopPropagation();
}
if (event.keyCode === 40 && this.isShowingSuggestions) {
selectedItemElement = this.list.querySelector('.selected');
if (selectedItemElement) {
selectedItemElement.classList.remove('selected');
selectedItemElement.nextElementSibling.classList.add('selected');
} else {
this.list.querySelector('li:first-child').classList.add('selected');
}
this.list.querySelector('.selected').scrollIntoView(false);
event.preventDefault();
} else if (event.keyCode === 38 && this.isShowingSuggestions) {
selectedItemElement = this.list.querySelector('.selected');
if (selectedItemElement) {
selectedItemElement.classList.remove('selected');
selectedItemElement.previousElementSibling.classList.add('selected');
} else {
this.list.querySelector('li:first-child').classList.add('selected');
}
this.list.querySelector('.selected').scrollIntoView(false);
event.preventDefault();
} else if (event.keyCode === 13 && this.isShowingSuggestions) {
selectedItemElement = this.list.querySelector('.selected');
this.selectSuggestion(selectedItemElement.dataset.url);
this.closeSuggestions();
}
}
onListMouseDown(event) {
var target = event.target;
if (target.parentElement.dataset.url) {
this.selectSuggestion(target.parentElement.dataset.url);
}
}
selectSuggestion(value) {
if (this.selectedCallback) {
this.selectedCallback.call(null, value);
} else {
this.replaceCurrentLine(value);
}
this.closeSuggestions();
}
}
window.TextareaAutoComplete = TextareaAutoComplete;
})();

View File

@@ -1,10 +1,23 @@
(function() {
window.DEBUG = document.cookie.indexOf('wmdebug') > -1;
import { trackEvent } from './analytics';
import { computeHtml, computeCss, computeJs } from './computes';
import { JsModes } from './codeModes';
import { deferred } from './deferred';
const esprima = require('esprima');
window.DEBUG = document.cookie.indexOf('wmdebug') > -1;
window.$ = document.querySelector.bind(document);
window.chrome = window.chrome || {};
window.chrome.i18n = {
getMessage: () => {}
};
window.$all = selector => [...document.querySelectorAll(selector)];
var alphaNum =
'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789';
window.IS_EXTENSION = !!window.chrome.extension;
export const BASE_PATH = window.chrome.extension || window.DEBUG ? '/' : '/app';
var alphaNum = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789';
/**
* The following 2 functions are supposed to find the next/previous sibling until the
@@ -15,11 +28,21 @@
* @return element Next element that mathes `selector`
*/
Node.prototype.nextUntil = function(selector) {
const siblings = [...this.parentNode.querySelectorAll(selector)];
const siblings = Array.from(this.parentNode.querySelectorAll(selector));
const index = siblings.indexOf(this);
return siblings[index + 1];
};
/*
* @param Selector that should match for next siblings
* @return element Next element that mathes `selector`
*/
Node.prototype.previousUntil = function(selector) {
const siblings = Array.from(this.parentNode.querySelectorAll(selector));
const index = siblings.indexOf(this);
return siblings[index - 1];
};
// Safari doesn't have this!
window.requestIdleCallback =
window.requestIdleCallback ||
@@ -27,18 +50,8 @@
setTimeout(fn, 10);
};
/*
* @param Selector that should match for next siblings
* @return element Next element that mathes `selector`
*/
Node.prototype.previousUntil = function(selector) {
const siblings = [...this.parentNode.querySelectorAll(selector)];
const index = siblings.indexOf(this);
return siblings[index - 1];
};
// https://github.com/substack/semver-compare/blob/master/index.js
function semverCompare(a, b) {
export function semverCompare(a, b) {
var pa = a.split('.');
var pb = b.split('.');
for (var i = 0; i < 3; i++) {
@@ -60,7 +73,7 @@
return 0;
}
function generateRandomId(len) {
export function generateRandomId(len) {
var length = len || 10;
var id = '';
for (var i = length; i--; ) {
@@ -69,16 +82,16 @@
return id;
}
function onButtonClick(btn, listener) {
export function onButtonClick(btn, listener) {
btn.addEventListener('click', function buttonClickListener(e) {
listener(e);
return false;
});
}
function log() {
export function log() {
if (window.DEBUG) {
console.log(...arguments);
console.log(Date.now(), ...arguments);
}
}
@@ -87,16 +100,21 @@
* Contributed by Ariya Hidayat!
* @param code {string} Code to be protected from infinite loops.
*/
function addInfiniteLoopProtection(code, { timeout }) {
export function addInfiniteLoopProtection(code, { timeout }) {
var loopId = 1;
var patches = [];
var varPrefix = '_wmloopvar';
var varStr = 'var %d = Date.now();\n';
var checkStr = `\nif (Date.now() - %d > ${timeout}) { window.top.previewException(new Error("Infinite loop")); break;}\n`;
esprima.parse(code, { tolerant: true, range: true, jsx: true }, function(
node
) {
esprima.parse(
code,
{
tolerant: true,
range: true,
jsx: true
},
function(node) {
switch (node.type) {
case 'DoWhileStatement':
case 'ForStatement':
@@ -115,8 +133,14 @@
--start;
}
patches.push({ pos: start, str: prolog });
patches.push({ pos: end, str: epilog });
patches.push({
pos: start,
str: prolog
});
patches.push({
pos: end,
str: epilog
});
patches.push({
pos: node.range[0],
str: varStr.replace('%d', varPrefix + loopId)
@@ -127,7 +151,8 @@
default:
break;
}
});
}
);
/* eslint-disable no-param-reassign */
patches
@@ -142,7 +167,7 @@
return code;
}
function getHumanDate(timestamp) {
export function getHumanDate(timestamp) {
var d = new Date(timestamp);
var retVal =
d.getDate() +
@@ -167,7 +192,7 @@
}
// create a one-time event
function once(node, type, callback) {
export function once(node, type, callback) {
// create event
node.addEventListener(type, function(e) {
// remove event
@@ -177,7 +202,7 @@
});
}
function downloadFile(fileName, blob) {
export function downloadFile(fileName, blob) {
function downloadWithAnchor() {
var a = document.createElement('a');
a.href = window.URL.createObjectURL(blob);
@@ -206,24 +231,220 @@
}
}
window.utils = {
semverCompare,
generateRandomId,
onButtonClick,
addInfiniteLoopProtection,
getHumanDate,
log,
once,
downloadFile
export function writeFile(name, blob, cb) {
var fileWritten = false;
function getErrorHandler(type) {
return function() {
log(arguments);
trackEvent('fn', 'error', type);
// When there are too many write errors, show a message.
writeFile.errorCount = (writeFile.errorCount || 0) + 1;
if (writeFile.errorCount === 4) {
setTimeout(function() {
alert(
"Oops! Seems like your preview isn't updating. It's recommended to switch to the web app: https://webmakerapp.com/app/.\n\n If you still want to get the extension working, please try the following steps until it fixes:\n - Refresh Web Maker\n - Restart browser\n - Update browser\n - Reinstall Web Maker (don't forget to export all your creations from saved items pane (click the OPEN button) before reinstalling)\n\nIf nothing works, please tweet out to @webmakerApp."
);
trackEvent('ui', 'writeFileMessageSeen');
}, 1000);
}
};
}
window.chrome = window.chrome || {};
window.chrome.i18n = { getMessage: () => {} };
// utils.log('writing file ', name);
window.webkitRequestFileSystem(
window.TEMPORARY,
1024 * 1024 * 5,
function(fs) {
fs.root.getFile(
name,
{
create: true
},
function(fileEntry) {
fileEntry.createWriter(fileWriter => {
function onWriteComplete() {
if (fileWritten) {
// utils.log('file written ', name);
return cb();
}
fileWritten = true;
// Set the write pointer to starting of file
fileWriter.seek(0);
fileWriter.write(blob);
return false;
}
fileWriter.onwriteend = onWriteComplete;
// Empty the file contents
fileWriter.truncate(0);
// utils.log('truncating file ', name);
}, getErrorHandler('createWriterFail'));
},
getErrorHandler('getFileFail')
);
},
getErrorHandler('webkitRequestFileSystemFail')
);
}
export function loadJS(src) {
var d = deferred();
var ref = window.document.getElementsByTagName('script')[0];
var script = window.document.createElement('script');
script.src = src;
script.async = true;
ref.parentNode.insertBefore(script, ref);
script.onload = function() {
d.resolve();
};
return d.promise;
}
/* eslint-disable max-params */
export function getCompleteHtml(html, css, js, item, isForExport) {
/* eslint-enable max-params */
if (!item) {
return '';
}
var externalJs = '',
externalCss = '';
if (item.externalLibs) {
externalJs = item.externalLibs.js
.split('\n')
.reduce(function(scripts, url) {
return scripts + (url ? '\n<script src="' + url + '"></script>' : '');
}, '');
externalCss = item.externalLibs.css
.split('\n')
.reduce(function(links, url) {
return (
links +
(url ? '\n<link rel="stylesheet" href="' + url + '"></link>' : '')
);
}, '');
}
var contents =
'<!DOCTYPE html>\n' +
'<html>\n<head>\n' +
'<meta charset="UTF-8" />\n' +
externalCss +
'\n' +
'<style id="webmakerstyle">\n' +
css +
'\n</style>\n' +
'</head>\n' +
'<body>\n' +
html +
'\n' +
externalJs +
'\n';
if (!isForExport) {
contents +=
'<script src="' +
(chrome.extension
? chrome.extension.getURL('lib/screenlog.js')
: `${location.origin}${BASE_PATH}/lib/screenlog.js`) +
'"></script>';
}
if (item.jsMode === JsModes.ES6) {
contents +=
'<script src="' +
(chrome.extension
? chrome.extension.getURL('lib/transpilers/babel-polyfill.min.js')
: `${
location.origin
}${BASE_PATH}/lib/transpilers/babel-polyfill.min.js`) +
'"></script>';
}
if (typeof js === 'string') {
contents += '<script>\n' + js + '\n//# sourceURL=userscript.js';
} else {
var origin = chrome.i18n.getMessage()
? `chrome-extension://${chrome.i18n.getMessage('@@extension_id')}`
: `${location.origin}`;
contents +=
'<script src="' + `filesystem:${origin}/temporary/script.js` + '">';
}
contents += '\n</script>\n</body>\n</html>';
return contents;
}
export function saveAsHtml(item) {
var htmlPromise = computeHtml(item.html, item.htmlMode);
var cssPromise = computeCss(item.css, item.cssMode);
var jsPromise = computeJs(item.js, item.jsMode, false);
Promise.all([htmlPromise, cssPromise, jsPromise]).then(function(result) {
var html = result[0].code,
css = result[1].code,
js = result[2].code;
var fileContent = getCompleteHtml(html, css, js, item, true);
var d = new Date();
var fileName = [
'web-maker',
d.getFullYear(),
d.getMonth() + 1,
d.getDate(),
d.getHours(),
d.getMinutes(),
d.getSeconds()
].join('-');
if (item.title) {
fileName = item.title;
}
fileName += '.html';
var blob = new Blob([fileContent], {
type: 'text/html;charset=UTF-8'
});
downloadFile(fileName, blob);
trackEvent('fn', 'saveFileComplete');
});
}
export function handleDownloadsPermission() {
var d = deferred();
if (!window.IS_EXTENSION) {
d.resolve();
return d.promise;
}
chrome.permissions.contains(
{
permissions: ['downloads']
},
function(result) {
if (result) {
d.resolve();
} else {
chrome.permissions.request(
{
permissions: ['downloads']
},
function(granted) {
if (granted) {
trackEvent('fn', 'downloadsPermGiven');
d.resolve();
} else {
d.reject();
}
}
);
}
}
);
return d.promise;
}
window.IS_EXTENSION = !!window.chrome.extension;
if (window.IS_EXTENSION) {
document.body.classList.add('is-extension');
} else {
document.body.classList.add('is-app');
}
})();

10100
yarn.lock Normal file

File diff suppressed because it is too large Load Diff