Merge pull request #303 from chinchang/preact
Migration from vanilla JS to Preact!
@@ -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
@@ -9,3 +9,4 @@
|
||||
node_modules/
|
||||
*.map
|
||||
.sass-cache
|
||||
extension/
|
@@ -1,2 +1,3 @@
|
||||
src/lib/
|
||||
app/
|
||||
build/
|
@@ -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
BIN
app/icons/icon-128x128.png
Executable file
After Width: | Height: | Size: 2.2 KiB |
BIN
app/icons/icon-144x144.png
Executable file
After Width: | Height: | Size: 2.5 KiB |
BIN
app/icons/icon-152x152.png
Executable file
After Width: | Height: | Size: 2.4 KiB |
BIN
app/icons/icon-192x192.png
Executable file
After Width: | Height: | Size: 3.0 KiB |
BIN
app/icons/icon-384x384.png
Executable file
After Width: | Height: | Size: 5.3 KiB |
BIN
app/icons/icon-512x512.png
Executable file
After Width: | Height: | Size: 4.2 KiB |
BIN
app/icons/icon-72x72.png
Executable file
After Width: | Height: | Size: 1.4 KiB |
BIN
app/icons/icon-96x96.png
Executable file
After Width: | Height: | Size: 1.7 KiB |
663
app/index.html
@@ -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>
|
||||
©
|
||||
<span class="web-maker-with-tag">Web Maker</span>
|
||||
<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>
|
||||
|
@@ -4,6 +4,5 @@
|
||||
|
||||
<iframe src="about://blank" frameborder="0" id="demo-frame" allowfullscreen></iframe>
|
||||
|
||||
|
||||
<script src="detached-window.js"></script>
|
||||
</body>
|
||||
|
165
gulpfile.js
@@ -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
After Width: | Height: | Size: 2.2 KiB |
BIN
icons/icon-144x144.png
Executable file
After Width: | Height: | Size: 2.5 KiB |
BIN
icons/icon-152x152.png
Executable file
After Width: | Height: | Size: 2.4 KiB |
BIN
icons/icon-192x192.png
Executable file
After Width: | Height: | Size: 3.0 KiB |
BIN
icons/icon-384x384.png
Executable file
After Width: | Height: | Size: 5.3 KiB |
BIN
icons/icon-512x512.png
Executable file
After Width: | Height: | Size: 4.2 KiB |
BIN
icons/icon-72x72.png
Executable file
After Width: | Height: | Size: 1.4 KiB |
BIN
icons/icon-96x96.png
Executable file
After Width: | Height: | Size: 1.7 KiB |
52
manifest.json
Executable 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
|
||||
}
|
97
package.json
@@ -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
@@ -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
@@ -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
@@ -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 */ });
|
||||
}
|
||||
|
21
src/auth.js
@@ -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
@@ -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
|
||||
};
|
145
src/components/AddLibrary.jsx
Normal 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
@@ -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" />;
|
||||
}
|
||||
}
|
40
src/components/AskToImportModal.jsx
Normal 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>
|
||||
);
|
||||
}
|
36
src/components/CodeMirrorBox.jsx
Normal 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"
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
942
src/components/ContentWrap.jsx
Normal 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>
|
||||
);
|
||||
}
|
||||
}
|
52
src/components/CssSettingsModal.jsx
Normal 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
@@ -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>
|
||||
©
|
||||
<span class="web-maker-with-tag">Web Maker</span>
|
||||
<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>
|
||||
);
|
||||
}
|
||||
}
|
203
src/components/HelpModal.jsx
Normal 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
@@ -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>
|
||||
);
|
||||
}
|
94
src/components/KeyboardShortcutsModal.jsx
Normal 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>
|
||||
);
|
||||
}
|
164
src/components/LibraryAutoSuggest.jsx
Normal 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
@@ -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>
|
||||
);
|
||||
}
|
||||
}
|
123
src/components/MainHeader.jsx
Normal 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
@@ -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>
|
||||
);
|
||||
}
|
||||
}
|
954
src/components/Notifications.jsx
Normal 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>
|
||||
<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>
|
||||
<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>
|
||||
);
|
||||
}
|
103
src/components/OnboardingModal.jsx
Normal 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>
|
||||
);
|
||||
}
|
30
src/components/Profile.jsx
Normal 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>
|
||||
);
|
||||
}
|
269
src/components/SavedItemPane.jsx
Normal 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
@@ -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>
|
||||
);
|
||||
}
|
||||
}
|
54
src/components/SplitPane.jsx
Normal 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>;
|
||||
}
|
||||
}
|
62
src/components/SupportDeveloperModal.jsx
Normal 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>
|
||||
);
|
||||
}
|
144
src/components/UserCodeMirror.jsx
Normal 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
28
src/components/common.jsx
Normal 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
@@ -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 */
|
34
src/db.js
@@ -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;
|
||||
|
@@ -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);
|
||||
};
|
||||
})();
|
||||
}
|
||||
|
@@ -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
@@ -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
@@ -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);
|
719
src/index.html
@@ -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>
|
||||
©
|
||||
<span class="web-maker-with-tag">Web Maker</span>
|
||||
<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
@@ -0,0 +1,3 @@
|
||||
import App from './components/app.jsx';
|
||||
|
||||
export default App;
|
@@ -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));
|
||||
}
|
||||
};
|
||||
})();
|
||||
|
6346
src/lib/esprima.js
569
src/lib/split.js
@@ -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);
|
@@ -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',
|
@@ -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);
|
@@ -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;
|
||||
|
@@ -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>
|
@@ -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>
|
||||
<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>
|
||||
<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>
|
@@ -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>
|
@@ -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>
|
@@ -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>
|
@@ -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>
|
@@ -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>
|
@@ -4,6 +4,5 @@
|
||||
|
||||
<iframe src="about://blank" frameborder="0" id="demo-frame" allowfullscreen></iframe>
|
||||
|
||||
|
||||
<script src="detached-window.js"></script>
|
||||
</body>
|
||||
|
2700
src/script.js
345
src/style.css
139
src/takeScreenshot.js
Normal 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');
|
||||
});
|
||||
}
|
21
src/tests/__mocks__/browserMocks.js
Normal 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
|
||||
}); */
|
3
src/tests/__mocks__/fileMocks.js
Normal 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
@@ -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);
|
||||
});
|
||||
});
|
@@ -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;
|
||||
})();
|
307
src/utils.js
@@ -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');
|
||||
}
|
||||
})();
|
||||
|