mirror of
https://github.com/chinchang/web-maker.git
synced 2025-05-13 22:05:21 +02:00
1835 lines
59 KiB
JavaScript
1835 lines
59 KiB
JavaScript
/* global trackEvent */
|
|
/* global layoutBtn1, layoutBtn2, layoutBtn3, helpModal, notificationsModal, addLibraryModal,
|
|
onboardModal, layoutBtn1, layoutBtn2, layoutBtn3, layoutBtn4, helpBtn, onboardModal, onboardModal,
|
|
addLibraryModal, addLibraryModal, notificationsBtn, notificationsModal, notificationsModal,
|
|
notificationsModal, notificationsBtn, codepenBtn, saveHtmlBtn, saveBtn, settingsBtn,
|
|
onboardModal, settingsModal, notificationsBtn, onboardShowInTabOptionBtn, editorThemeLinkTag,
|
|
onboardDontShowInTabOptionBtn, TextareaAutoComplete, savedItemCountEl, indentationSizeValueEl,
|
|
runBtn, searchInput, consoleEl, consoleLogEl, logCountEl, fontStyleTag, fontStyleTemplate
|
|
*/
|
|
/* eslint-disable no-extra-semi */
|
|
;(function (alertsService) {
|
|
|
|
/* eslint-enable no-extra-semi */
|
|
var scope = scope || {};
|
|
var version = '2.7.0';
|
|
|
|
if (window.DEBUG) {
|
|
window.scope = scope;
|
|
}
|
|
|
|
var HtmlModes = {
|
|
HTML: 'html',
|
|
MARKDOWN: 'markdown',
|
|
JADE: 'jade' // unsafe eval is put in manifest for this file
|
|
};
|
|
var CssModes = {
|
|
CSS: 'css',
|
|
SCSS: 'scss',
|
|
SASS: 'sass',
|
|
LESS: 'less',
|
|
STYLUS: 'stylus',
|
|
ACSS: 'acss'
|
|
};
|
|
var JsModes = {
|
|
JS: 'js',
|
|
ES6: 'es6',
|
|
COFFEESCRIPT: 'coffee',
|
|
TS: 'typescript'
|
|
};
|
|
var 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 };
|
|
|
|
var updateTimer
|
|
, updateDelay = 500
|
|
, unsavedEditWarningCount = 15
|
|
, currentLayoutMode
|
|
, hasSeenNotifications = true
|
|
, htmlMode = HtmlModes.HTML
|
|
, jsMode = JsModes.JS
|
|
, cssMode = CssModes.CSS
|
|
, sass
|
|
, currentItem
|
|
, unsavedEditCount
|
|
, savedItems
|
|
, minCodeWrapSize = 33
|
|
, mainSplitInstance
|
|
, codeSplitInstance
|
|
, prefs = {}
|
|
, codeInPreview = { html: null, css: null, js: null }
|
|
, isSavedItemsPaneOpen = false
|
|
, editorWithFocus
|
|
, logCount = 0
|
|
|
|
// DOM nodes
|
|
, frame = $('#demo-frame')
|
|
, htmlCode = $('#js-html-code')
|
|
, cssCode = $('#js-css-code')
|
|
, jsCode = $('#js-js-code')
|
|
, codepenForm = $('#js-codepen-form')
|
|
, savedItemsPane = $('#js-saved-items-pane')
|
|
, savedItemsPaneCloseBtn = $('#js-saved-items-pane-close-btn')
|
|
, htmlModelLabel = $('#js-html-mode-label')
|
|
, cssModelLabel = $('#js-css-mode-label')
|
|
, jsModelLabel = $('#js-js-mode-label')
|
|
, titleInput = $('#js-title-input')
|
|
, addLibrarySelect = $('#js-add-library-select')
|
|
, addLibraryBtn = $('#js-add-library-btn')
|
|
, externalJsTextarea = $('#js-external-js')
|
|
, externalCssTextarea = $('#js-external-css')
|
|
;
|
|
|
|
scope.cm = {};
|
|
scope.frame = frame;
|
|
scope.demoFrameDocument = frame.contentDocument || frame.contentWindow.document;
|
|
|
|
// Check all the code wrap if they are minimized or not
|
|
function updateCodeWrapCollapseStates() {
|
|
clearTimeout(updateCodeWrapCollapseStates.timeout);
|
|
updateCodeWrapCollapseStates.timeout = setTimeout(function () {
|
|
[ htmlCode, cssCode, jsCode ].forEach(function (el) {
|
|
var bounds = el.getBoundingClientRect();
|
|
if (bounds[currentLayoutMode === 2 ? 'width' : 'height'] < 100) {
|
|
el.classList.add('is-minimized');
|
|
} else {
|
|
el.classList.remove('is-minimized');
|
|
}
|
|
});
|
|
}, 50);
|
|
}
|
|
|
|
function toggleCodeWrapCollapse(codeWrapEl) {
|
|
if (codeWrapEl.classList.contains('is-minimized')) {
|
|
codeWrapEl.classList.remove('is-minimized');
|
|
codeSplitInstance.setSizes([ 33.3, 33.3, 33.3 ]);
|
|
} else {
|
|
codeSplitInstance.collapse(parseInt(codeWrapEl.dataset.codeWrapId, 10));
|
|
codeWrapEl.classList.add('is-minimized');
|
|
}
|
|
}
|
|
// Returns the sizes of main code & preview panes.
|
|
function getMainSplitSizesToApply() {
|
|
var mainSplitSizes;
|
|
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 = [ 50, 50];
|
|
}
|
|
return mainSplitSizes;
|
|
}
|
|
|
|
function resetSplitting() {
|
|
if (codeSplitInstance) {
|
|
codeSplitInstance.destroy();
|
|
}
|
|
if (mainSplitInstance) {
|
|
mainSplitInstance.destroy();
|
|
}
|
|
|
|
var options = {
|
|
direction: (currentLayoutMode === 2 ? 'horizontal' : 'vertical'),
|
|
minSize: minCodeWrapSize,
|
|
gutterSize: 6,
|
|
onDragStart: function () {
|
|
document.body.classList.add('is-dragging');
|
|
},
|
|
onDragEnd: function () {
|
|
updateCodeWrapCollapseStates();
|
|
document.body.classList.remove('is-dragging');
|
|
}
|
|
};
|
|
if (currentItem && currentItem.sizes) {
|
|
options.sizes = currentItem.sizes;
|
|
} else {
|
|
options.sizes = [ 33.33, 33.33, 33.33 ];
|
|
}
|
|
|
|
codeSplitInstance = Split(['#js-html-code', '#js-css-code', '#js-js-code'], options);
|
|
mainSplitInstance = Split(['#js-code-side', '#js-demo-side' ], {
|
|
direction: (currentLayoutMode === 2 ? 'vertical' : 'horizontal'),
|
|
minSize: 150,
|
|
gutterSize: 6,
|
|
sizes: getMainSplitSizesToApply(),
|
|
onDragEnd: function () {
|
|
if (prefs.refreshOnResize) {
|
|
// Running preview updation in next call stack, so that error there
|
|
// doesn't affect this dragend listener.
|
|
setTimeout(function () {
|
|
scope.setPreviewContent(true);
|
|
}, 1);
|
|
}
|
|
}
|
|
});
|
|
}
|
|
function toggleLayout(mode) {
|
|
if (currentLayoutMode === mode) {
|
|
mainSplitInstance.setSizes(getMainSplitSizesToApply());
|
|
codeSplitInstance.setSizes(currentItem.sizes || [ 33.33, 33.33, 33.33 ]);
|
|
currentLayoutMode = mode;
|
|
return;
|
|
}
|
|
currentLayoutMode = mode;
|
|
layoutBtn1.classList.remove('selected');
|
|
layoutBtn2.classList.remove('selected');
|
|
layoutBtn3.classList.remove('selected');
|
|
layoutBtn4.classList.remove('selected');
|
|
$('#layoutBtn' + mode).classList.add('selected');
|
|
document.body.classList.remove('layout-1');
|
|
document.body.classList.remove('layout-2');
|
|
document.body.classList.remove('layout-3');
|
|
document.body.classList.remove('layout-4');
|
|
document.body.classList.add('layout-' + mode);
|
|
|
|
resetSplitting();
|
|
scope.setPreviewContent(true);
|
|
}
|
|
|
|
function onExternalLibChange() {
|
|
utils.log('onExternalLibChange');
|
|
updateExternalLibUi();
|
|
scope.setPreviewContent(true);
|
|
}
|
|
|
|
function updateExternalLibUi() {
|
|
// Calculate no. of external libs
|
|
var noOfExternalLibs = 0;
|
|
noOfExternalLibs += externalJsTextarea.value.split('\n').filter((lib) => !!lib).length;
|
|
noOfExternalLibs += externalCssTextarea.value.split('\n').filter((lib) => !!lib).length;
|
|
if (noOfExternalLibs) {
|
|
$('#js-external-lib-count').textContent = noOfExternalLibs;
|
|
$('#js-external-lib-count').style.display = 'inline';
|
|
} else {
|
|
$('#js-external-lib-count').style.display = 'none';
|
|
}
|
|
}
|
|
|
|
function saveSetting(setting, value, cb) {
|
|
var obj = {};
|
|
obj[setting] = value;
|
|
chrome.storage.local.set(obj, cb || function(){});
|
|
}
|
|
|
|
// Save current item to storage
|
|
function saveItem() {
|
|
var isNewItem = !currentItem.id;
|
|
currentItem.id = currentItem.id || ('item-' + utils.generateRandomId());
|
|
saveCode();
|
|
|
|
// Push into the items hash if its a new item being saved
|
|
if (isNewItem) {
|
|
chrome.storage.local.get({
|
|
items: {}
|
|
}, function (result) {
|
|
result.items[currentItem.id] = true;
|
|
chrome.storage.local.set({
|
|
items: result.items
|
|
});
|
|
});
|
|
}
|
|
}
|
|
|
|
// Calculates the sizes of html, css & js code panes.
|
|
function getCodePaneSizes() {
|
|
var sizes;
|
|
var dimensionProperty = currentLayoutMode === 2 ? 'width' : 'height';
|
|
try {
|
|
sizes = [
|
|
+htmlCode.style[dimensionProperty].match(/([\d.]+)%/)[1],
|
|
+cssCode.style[dimensionProperty].match(/([\d.]+)%/)[1],
|
|
+jsCode.style[dimensionProperty].match(/([\d.]+)%/)[1]
|
|
];
|
|
} catch (e) {
|
|
sizes = [ 33.33, 33.33, 33.33 ]
|
|
} finally {
|
|
|
|
/* eslint-disable no-unsafe-finally */
|
|
return sizes;
|
|
|
|
/* eslint-enable no-unsafe-finally */
|
|
}
|
|
}
|
|
|
|
// Calculates the current sizes of code & preview panes.
|
|
function getMainPaneSizes() {
|
|
var sizes;
|
|
var dimensionProperty = currentLayoutMode === 2 ? 'height' : 'width';
|
|
try {
|
|
sizes = [
|
|
+$('#js-code-side').style[dimensionProperty].match(/([\d.]+)%/)[1],
|
|
+$('#js-demo-side').style[dimensionProperty].match(/([\d.]+)%/)[1]
|
|
];
|
|
} catch (e) {
|
|
sizes = [ 50, 50 ]
|
|
} finally {
|
|
|
|
/* eslint-disable no-unsafe-finally */
|
|
return sizes;
|
|
|
|
/* eslint-enable no-unsafe-finally */
|
|
}
|
|
}
|
|
|
|
function saveCode(key) {
|
|
currentItem.title = titleInput.value;
|
|
currentItem.html = scope.cm.html.getValue();
|
|
currentItem.css = scope.cm.css.getValue();
|
|
currentItem.js = scope.cm.js.getValue();
|
|
currentItem.htmlMode = htmlMode;
|
|
currentItem.cssMode = cssMode;
|
|
currentItem.jsMode = jsMode;
|
|
currentItem.updatedOn = Date.now();
|
|
currentItem.layoutMode = currentLayoutMode;
|
|
currentItem.externalLibs = { js: externalJsTextarea.value, css: externalCssTextarea.value };
|
|
|
|
currentItem.sizes = getCodePaneSizes();
|
|
currentItem.mainSizes = getMainPaneSizes();
|
|
|
|
utils.log('saving key', key || currentItem.id, currentItem)
|
|
saveSetting(key || currentItem.id, currentItem, function () {
|
|
alertsService.add('Item saved.');
|
|
unsavedEditCount = 0;
|
|
saveBtn.classList.remove('is-marked');
|
|
});
|
|
}
|
|
|
|
function populateItemsInSavedPane(items) {
|
|
var html = '';
|
|
if (items.length) {
|
|
// TODO: sort desc. by updation date
|
|
items.sort(function (a, b) {
|
|
return b.updatedOn - a.updatedOn;
|
|
});
|
|
items.forEach(function (item) {
|
|
html += '<div class="js-saved-item-tile saved-item-tile" data-item-id="' + item.id + '">'
|
|
+ '<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)">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">X</a></div>'
|
|
+ '<h3 class="saved-item-tile__title">' + item.title + '</h3><span class="saved-item-tile__meta">Last updated: ' + utils.getHumanDate(item.updatedOn) + '</span></div>';
|
|
});
|
|
savedItemCountEl.textContent = '(' + items.length + ')';
|
|
savedItemCountEl.style.display = 'inline';
|
|
} else {
|
|
html += '<h2 class="opacity--30">Nothing saved here.</h2>';
|
|
savedItemCountEl.style.display = 'none';
|
|
}
|
|
savedItemsPane.querySelector('#js-saved-items-wrap').innerHTML = html;
|
|
toggleSavedItemsPane();
|
|
// HACK: Set overflow after sometime so that the items can animate without getting cropped.
|
|
// setTimeout(() => $('#js-saved-items-wrap').style.overflowY = 'auto', 1000);
|
|
}
|
|
|
|
function toggleSavedItemsPane(shouldOpen) {
|
|
if (shouldOpen === false) {
|
|
savedItemsPane.classList.remove('is-open');
|
|
} else {
|
|
savedItemsPane.classList.toggle('is-open');
|
|
}
|
|
isSavedItemsPaneOpen = savedItemsPane.classList.contains('is-open');
|
|
if (isSavedItemsPaneOpen) {
|
|
searchInput.focus();
|
|
} else {
|
|
searchInput.value = '';
|
|
// Give last focused editor, focus again
|
|
if (editorWithFocus) {
|
|
editorWithFocus.focus();
|
|
}
|
|
}
|
|
document.body.classList[isSavedItemsPaneOpen ? 'add' : 'remove']('overlay-visible');
|
|
}
|
|
|
|
/**
|
|
* Fetches all items from storage
|
|
* @param {boolean} shouldSaveGlobally Whether to store the fetched items in global arr for later use.
|
|
* @return {promise} Promise.
|
|
*/
|
|
function fetchItems(shouldSaveGlobally) {
|
|
var d = deferred();
|
|
chrome.storage.local.get('items', function (result) {
|
|
var itemIds = Object.getOwnPropertyNames(result.items || {}),
|
|
items = [];
|
|
if (!itemIds.length) {
|
|
d.resolve([]);
|
|
}
|
|
|
|
savedItems = savedItems || {};
|
|
trackEvent('fn', 'fetchItems', itemIds.length);
|
|
for (let i = 0; i < itemIds.length; i++) {
|
|
|
|
/* eslint-disable no-loop-func */
|
|
chrome.storage.local.get(itemIds[i], function (itemResult) {
|
|
if (shouldSaveGlobally) {
|
|
savedItems[itemIds[i]] = itemResult[itemIds[i]];
|
|
}
|
|
items.push(itemResult[itemIds[i]]);
|
|
// Check if we have all items now.
|
|
if (itemIds.length === items.length) {
|
|
d.resolve(items)
|
|
}
|
|
});
|
|
|
|
/* eslint-enable no-loop-func */
|
|
}
|
|
});
|
|
return d.promise;
|
|
}
|
|
|
|
function openSavedItemsPane() {
|
|
fetchItems(true).then(function (items) {
|
|
populateItemsInSavedPane(items);
|
|
});
|
|
}
|
|
function setCurrentItem(item) {
|
|
currentItem = item;
|
|
// Reset unsaved count, in UI also.
|
|
unsavedEditCount = 0;
|
|
saveBtn.classList.remove('is-marked');
|
|
}
|
|
// Creates a new item with passed item's contents
|
|
function forkItem(sourceItem) {
|
|
if (unsavedEditCount) {
|
|
var shouldDiscard = confirm('You have unsaved changes in your current work. Do you want to discard unsaved changes and continue?');
|
|
if (!shouldDiscard) { return; }
|
|
}
|
|
const fork = JSON.parse(JSON.stringify(sourceItem));
|
|
delete fork.id;
|
|
fork.title = '(Forked) ' + sourceItem.title;
|
|
fork.updatedOn = Date.now();
|
|
setCurrentItem(fork);
|
|
refreshEditor();
|
|
alertsService.add(`"${sourceItem.title}" was forked`);
|
|
trackEvent('fn', 'itemForked');
|
|
}
|
|
function createNewItem() {
|
|
var d = new Date();
|
|
setCurrentItem({
|
|
title: 'Untitled ' + d.getDate() + '-' + (d.getMonth() + 1) + '-' + d.getHours() + ':' + d.getMinutes(),
|
|
html: '',
|
|
css: '',
|
|
js: '',
|
|
externalLibs: { js: '', css: '' },
|
|
layoutMode: currentLayoutMode
|
|
});
|
|
refreshEditor();
|
|
alertsService.add('New item created');
|
|
}
|
|
function openItem(itemId) {
|
|
setCurrentItem(savedItems[itemId]);
|
|
refreshEditor();
|
|
alertsService.add('Saved item loaded');
|
|
}
|
|
function removeItem(itemId) {
|
|
var itemTile = document.querySelector('.js-saved-item-tile[data-item-id="' + itemId + '"]');
|
|
var answer = confirm(`Are you sure you want to delete "${savedItems[itemId].title}"?`);
|
|
if (!answer) { return; }
|
|
|
|
itemTile.remove();
|
|
// Remove from items list
|
|
chrome.storage.local.get({
|
|
items: {}
|
|
}, function (result) {
|
|
delete result.items[itemId]
|
|
chrome.storage.local.set({
|
|
items: result.items
|
|
});
|
|
});
|
|
|
|
// Remove individual item too.
|
|
chrome.storage.local.remove(itemId, function () {
|
|
alertsService.add('Item removed.');
|
|
// This item is open in the editor. Lets open a new one.
|
|
if (currentItem.id === itemId) {
|
|
createNewItem();
|
|
}
|
|
});
|
|
// Remove from cached list
|
|
delete savedItems[itemId];
|
|
|
|
trackEvent('fn', 'itemRemoved');
|
|
}
|
|
|
|
function refreshEditor() {
|
|
titleInput.value = currentItem.title || 'Untitled';
|
|
externalJsTextarea.value = (currentItem.externalLibs && currentItem.externalLibs.js) || '';
|
|
externalCssTextarea.value = (currentItem.externalLibs && currentItem.externalLibs.css) || '';
|
|
|
|
utils.log('refresh editor')
|
|
// Set the modes manually here, so that the preview refresh triggered by the `setValue`
|
|
// statements below, operate on correct modes.
|
|
htmlMode = currentItem.htmlMode || prefs.htmlMode || HtmlModes.HTML;
|
|
cssMode = currentItem.cssMode || prefs.cssMode || CssModes.CSS;
|
|
jsMode = currentItem.jsMode || prefs.jsMode || JsModes.JS;
|
|
|
|
scope.cm.html.setValue(currentItem.html);
|
|
scope.cm.css.setValue(currentItem.css);
|
|
scope.cm.js.setValue(currentItem.js);
|
|
scope.cm.html.refresh();
|
|
scope.cm.css.refresh();
|
|
scope.cm.js.refresh();
|
|
|
|
scope.clearConsole();
|
|
|
|
// To have the library count updated
|
|
updateExternalLibUi();
|
|
|
|
// 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([
|
|
updateHtmlMode(htmlMode),
|
|
updateCssMode(cssMode),
|
|
updateJsMode(jsMode)
|
|
]).then(() => scope.setPreviewContent(true));
|
|
|
|
toggleLayout(currentItem.layoutMode || prefs.layoutMode);
|
|
}
|
|
|
|
function closeAllOverlays() {
|
|
helpModal.classList.remove('is-modal-visible');
|
|
notificationsModal.classList.remove('is-modal-visible');
|
|
addLibraryModal.classList.remove('is-modal-visible');
|
|
onboardModal.classList.remove('is-modal-visible');
|
|
settingsModal.classList.remove('is-modal-visible');
|
|
toggleSavedItemsPane(false);
|
|
document.dispatchEvent(new Event('overlaysClosed'));
|
|
}
|
|
|
|
/**
|
|
* Loaded the code comiler based on the mode selected
|
|
*/
|
|
function handleModeRequirements(mode) {
|
|
// 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('lib/jade.js').then(setLoadedFlag);
|
|
} else if (mode === HtmlModes.MARKDOWN) {
|
|
loadJS('lib/marked.js').then(setLoadedFlag);
|
|
} else if (mode === CssModes.LESS) {
|
|
loadJS('lib/less.min.js').then(setLoadedFlag);
|
|
} else if (mode === CssModes.SCSS || mode === CssModes.SASS) {
|
|
loadJS('lib/sass.js').then(function () {
|
|
sass = new Sass('lib/sass.worker.js');
|
|
setLoadedFlag();
|
|
});
|
|
} else if (mode === CssModes.STYLUS) {
|
|
loadJS('lib/stylus.min.js').then(setLoadedFlag);
|
|
} else if (mode === CssModes.ACSS) {
|
|
loadJS('lib/atomizer.browser.js').then(setLoadedFlag);
|
|
} else if (mode === JsModes.COFFEESCRIPT) {
|
|
loadJS('lib/coffee-script.js').then(setLoadedFlag);
|
|
} else if (mode === JsModes.ES6) {
|
|
loadJS('lib/babel.min.js').then(setLoadedFlag);
|
|
} else if (mode === JsModes.TS) {
|
|
loadJS('lib/typescript.js').then(setLoadedFlag);
|
|
} else {
|
|
d.resolve();
|
|
}
|
|
|
|
return d.promise;
|
|
}
|
|
|
|
function updateHtmlMode(value) {
|
|
htmlMode = value;
|
|
htmlModelLabel.textContent = modes[value].label;
|
|
scope.cm.html.setOption('mode', modes[value].cmMode);
|
|
CodeMirror.autoLoadMode(scope.cm.html, modes[value].cmPath || modes[value].cmMode);
|
|
return handleModeRequirements(value);
|
|
}
|
|
function updateCssMode(value) {
|
|
cssMode = value;
|
|
cssModelLabel.textContent = modes[value].label;
|
|
scope.cm.css.setOption('mode', modes[value].cmMode);
|
|
scope.cm.css.setOption('readOnly', modes[value].cmDisable);
|
|
CodeMirror.autoLoadMode(scope.cm.css, modes[value].cmPath || modes[value].cmMode);
|
|
return handleModeRequirements(value);
|
|
}
|
|
function updateJsMode(value) {
|
|
jsMode = value;
|
|
jsModelLabel.textContent = modes[value].label;
|
|
scope.cm.js.setOption('mode', modes[value].cmMode);
|
|
CodeMirror.autoLoadMode(scope.cm.js, modes[value].cmPath || modes[value].cmMode);
|
|
return handleModeRequirements(value);
|
|
}
|
|
|
|
// computeHtml, computeCss & computeJs evaluate the final code according
|
|
// to whatever mode is selected and resolve the returned promise with the code.
|
|
function computeHtml() {
|
|
var d = deferred();
|
|
var code = scope.cm.html.getValue();
|
|
if (htmlMode === HtmlModes.HTML) {
|
|
d.resolve(code);
|
|
} else if (htmlMode === HtmlModes.MARKDOWN) {
|
|
d.resolve(marked ? marked(code) : code);
|
|
} else if (htmlMode === HtmlModes.JADE) {
|
|
d.resolve(window.jade ? jade.render(code) : code);
|
|
}
|
|
|
|
return d.promise;
|
|
}
|
|
function computeCss() {
|
|
var d = deferred();
|
|
var code = scope.cm.css.getValue();
|
|
cleanupErrors('css');
|
|
|
|
if (cssMode === CssModes.CSS) {
|
|
d.resolve(code);
|
|
} else if (cssMode === CssModes.SCSS || cssMode === CssModes.SASS) {
|
|
if (sass && code) {
|
|
sass.compile(code, { indentedSyntax: cssMode === CssModes.SASS }, function(result) {
|
|
// Something was wrong
|
|
if (result.line && result.message) {
|
|
showErrors('css', [ { lineNumber: result.line - 1, message: result.message } ]);
|
|
}
|
|
d.resolve(result.text);
|
|
});
|
|
} else {
|
|
d.resolve(code);
|
|
}
|
|
} else if (cssMode === CssModes.LESS) {
|
|
less.render(code).then(function (result) {
|
|
d.resolve(result.css);
|
|
}, function (error) {
|
|
showErrors('css', [ { lineNumber: error.line, message: error.message } ]);
|
|
});
|
|
} else if (cssMode === 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
|
|
showErrors('css', [ { lineNumber: +error.message.match(/stylus:(\d+):/)[1] - 298, message: tempArr.pop() } ]);
|
|
}
|
|
d.resolve(result);
|
|
});
|
|
} else if (cssMode === CssModes.ACSS) {
|
|
const html = scope.cm.html.getValue();
|
|
const foundClasses = atomizer.findClassNames(html);
|
|
const finalConfig = atomizer.getConfig(foundClasses, {});
|
|
const acss = atomizer.getCss(finalConfig);
|
|
scope.cm.css.setValue(acss);
|
|
d.resolve(acss)
|
|
}
|
|
|
|
return d.promise;
|
|
}
|
|
function computeJs(shouldPreventInfiniteLoops) {
|
|
var d = deferred();
|
|
var code = scope.cm.js.getValue();
|
|
|
|
cleanupErrors('js');
|
|
if (!code) {
|
|
d.resolve('');
|
|
return d.promise;
|
|
}
|
|
|
|
if (jsMode === JsModes.JS) {
|
|
try {
|
|
esprima.parse(code, {
|
|
tolerant: true
|
|
});
|
|
} catch (e) {
|
|
showErrors('js', [ { lineNumber: e.lineNumber - 1, message: e.description } ]);
|
|
} finally {
|
|
if (shouldPreventInfiniteLoops !== false) {
|
|
code = utils.addInfiniteLoopProtection(code);
|
|
}
|
|
d.resolve(code);
|
|
}
|
|
} else if (jsMode === JsModes.COFFEESCRIPT) {
|
|
var coffeeCode;
|
|
if (!window.CoffeeScript) {
|
|
d.resolve('');
|
|
return d.promise;
|
|
}
|
|
try {
|
|
coffeeCode = CoffeeScript.compile(code, { bare: true });
|
|
} catch (e) {
|
|
showErrors('js', [ { lineNumber: e.location.first_line, message: e.message } ]);
|
|
} finally {
|
|
if (shouldPreventInfiniteLoops !== false) {
|
|
code = utils.addInfiniteLoopProtection(coffeeCode);
|
|
}
|
|
d.resolve(code);
|
|
}
|
|
} else if (jsMode === JsModes.ES6) {
|
|
if (!window.Babel) {
|
|
d.resolve('');
|
|
return d.promise;
|
|
}
|
|
try {
|
|
esprima.parse(code, {
|
|
tolerant: true,
|
|
jsx: true
|
|
});
|
|
} catch (e) {
|
|
showErrors('js', [ { lineNumber: e.lineNumber - 1, message: e.description } ]);
|
|
} finally {
|
|
code = Babel.transform(code, { presets: ['latest', 'stage-2', 'react'] }).code;
|
|
if (shouldPreventInfiniteLoops !== false) {
|
|
code = utils.addInfiniteLoopProtection(code);
|
|
}
|
|
d.resolve(code);
|
|
}
|
|
} else if (jsMode === JsModes.TS) {
|
|
try {
|
|
if (!window.ts) {
|
|
d.resolve('');
|
|
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 */
|
|
throw ({ description: code.diagnostics[0].messageText, lineNumber: ts.getLineOfLocalPosition(code.diagnostics[0].file,code.diagnostics[0].start) });
|
|
}
|
|
if (shouldPreventInfiniteLoops !== false) {
|
|
code = utils.addInfiniteLoopProtection(code.outputText);
|
|
}
|
|
d.resolve(code);
|
|
} catch (e) {
|
|
showErrors('js', [ { lineNumber: e.lineNumber - 1, message: e.description } ]);
|
|
}
|
|
}
|
|
|
|
return d.promise;
|
|
}
|
|
|
|
window.previewException = function (error) {
|
|
console.error('Possible infinite loop detected.', error.stack)
|
|
window.onMessageFromConsole('Possible infinite loop detected.', error.stack);
|
|
}
|
|
window.onunload = function () {
|
|
saveCode('code');
|
|
};
|
|
|
|
function cleanupErrors(lang) {
|
|
scope.cm[lang].clearGutter('error-gutter');
|
|
}
|
|
function showErrors(lang, errors) {
|
|
var editor = scope.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);
|
|
});
|
|
});
|
|
}
|
|
|
|
function getCompleteHtml(html, css, js) {
|
|
var externalJs = externalJsTextarea.value.split('\n').reduce(function (scripts, url) {
|
|
return scripts + (url ? '\n<script src="' + url + '"></script>' : '');
|
|
}, '');
|
|
var externalCss = externalCssTextarea.value.split('\n').reduce(function (links, url) {
|
|
return links + (url ? '\n<link rel="stylesheet" href="' + url + '"></link>' : '');
|
|
}, '');
|
|
var contents = '<html>\n<head>\n'
|
|
+ externalCss + '\n'
|
|
+ '<style id="webmakerstyle">\n' + css + '\n</style>\n'
|
|
+ '</head>\n'
|
|
+ '<body>\n' + html + '\n'
|
|
+ externalJs + '\n';
|
|
|
|
contents += '<script src="'
|
|
+ chrome.extension.getURL('lib/screenlog.js') + '"></script>';
|
|
|
|
if (js) {
|
|
contents += '<script>\n' + js + '\n//# sourceURL=userscript.js';
|
|
} else {
|
|
contents += '<script src="'
|
|
+ 'filesystem:chrome-extension://'
|
|
+ chrome.i18n.getMessage('@@extension_id') + '/temporary/' + 'script.js' + '">'
|
|
}
|
|
contents += '\n</script>\n</body>\n</html>';
|
|
|
|
return contents;
|
|
}
|
|
|
|
function writeFile(name, blob, cb) {
|
|
var fileWritten = false;
|
|
function errorHandler() {
|
|
utils.log(arguments);
|
|
trackEvent('fn', 'error', 'writeFile');
|
|
// When there are too many write errors, show a message.
|
|
writeFile.errorCount = (writeFile.errorCount || 0) + 1;
|
|
if (writeFile.errorCount === 10) {
|
|
setTimeout(function () {
|
|
alert('Oops! Seems like your preview isn\'t updating. Please try the following steps until it fixes:\n - Refresh Web Maker\n - Restart Chrome 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)
|
|
}
|
|
}
|
|
|
|
// utils.log('writing file ', name);
|
|
window.webkitRequestFileSystem(window.TEMPORARY, 1024 * 1024 * 5, function(fs){
|
|
fs.root.getFile(name, { create: true }, function(fileEntry) {
|
|
fileEntry.createWriter(function(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);
|
|
|
|
}, errorHandler);
|
|
}, errorHandler);
|
|
}, errorHandler);
|
|
|
|
}
|
|
|
|
function createPreviewFile(html, css, js) {
|
|
var contents = getCompleteHtml(html, css);
|
|
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;
|
|
}
|
|
|
|
// we need to store user script in external JS file to prevent inline-script
|
|
// CSP from affecting it.
|
|
writeFile('script.js', blobjs, function () {
|
|
writeFile('preview.html', blob, function () {
|
|
frame.src = 'filesystem:chrome-extension://'
|
|
+ chrome.i18n.getMessage('@@extension_id') + '/temporary/' + 'preview.html';
|
|
});
|
|
});
|
|
}
|
|
|
|
scope.setPreviewContent = function (isForced) {
|
|
var currentCode = {
|
|
html: scope.cm.html.getValue(),
|
|
css: scope.cm.css.getValue(),
|
|
js: scope.cm.js.getValue()
|
|
};
|
|
utils.log('🔎 setPreviewContent', isForced)
|
|
// If just CSS was changed (and everything shudn't be empty),
|
|
// change the styles inside the iframe.
|
|
if (!isForced && currentCode.html === codeInPreview.html && currentCode.js === codeInPreview.js) {
|
|
computeCss().then(function (css) {
|
|
if (frame.contentDocument.querySelector('#webmakerstyle')) {
|
|
frame.contentDocument.querySelector('#webmakerstyle').textContent = css;
|
|
}
|
|
});
|
|
} else {
|
|
var htmlPromise = computeHtml();
|
|
var cssPromise = computeCss();
|
|
var jsPromise = computeJs();
|
|
Promise.all([htmlPromise, cssPromise, jsPromise]).then(function (result) {
|
|
createPreviewFile(result[0], result[1], result[2]);
|
|
});
|
|
}
|
|
|
|
codeInPreview.html = currentCode.html;
|
|
codeInPreview.css = currentCode.css;
|
|
codeInPreview.js = currentCode.js;
|
|
};
|
|
|
|
function saveFile() {
|
|
var htmlPromise = computeHtml();
|
|
var cssPromise = computeCss();
|
|
var jsPromise = computeJs(false);
|
|
Promise.all([htmlPromise, cssPromise, jsPromise]).then(function (result) {
|
|
var html = result[0],
|
|
css = result[1],
|
|
js = result[2];
|
|
|
|
var fileContent = getCompleteHtml(html, css, js);
|
|
|
|
var d = new Date();
|
|
var fileName = [ 'web-maker', d.getFullYear(), (d.getMonth() + 1), d.getDate(), d.getHours(), d.getMinutes(), d.getSeconds() ].join('-');
|
|
fileName += '.html';
|
|
|
|
if (currentItem.title) {
|
|
fileName = currentItem.title;
|
|
}
|
|
|
|
var a = document.createElement('a');
|
|
var blob = new Blob([ fileContent ], { type: "text/html;charset=UTF-8" });
|
|
a.href = window.URL.createObjectURL(blob);
|
|
a.download = fileName;
|
|
a.style.display = 'none';
|
|
document.body.appendChild(a);
|
|
a.click();
|
|
a.remove();
|
|
trackEvent('fn', 'saveFileComplete');
|
|
});
|
|
}
|
|
|
|
function initEditor(element, options) {
|
|
var cm = CodeMirror(element, {
|
|
mode: options.mode,
|
|
lineNumbers: true,
|
|
lineWrapping: true,
|
|
autofocus: options.autofocus || false,
|
|
autoCloseBrackets: true,
|
|
autoCloseTags: true,
|
|
matchBrackets: true,
|
|
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) {
|
|
var 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);
|
|
}
|
|
}
|
|
}
|
|
});
|
|
cm.on('focus', (editor) => {
|
|
editorWithFocus = editor;
|
|
});
|
|
cm.on('change', function onChange(editor, change) {
|
|
clearTimeout(updateTimer);
|
|
|
|
updateTimer = setTimeout(function () {
|
|
// 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 (prefs.autoPreview !== false) {
|
|
scope.setPreviewContent();
|
|
}
|
|
|
|
saveBtn.classList.add('is-marked');
|
|
unsavedEditCount += 1;
|
|
if (unsavedEditCount % unsavedEditWarningCount === 0 && unsavedEditCount >= unsavedEditWarningCount) {
|
|
saveBtn.classList.add('animated');
|
|
saveBtn.classList.add('wobble');
|
|
saveBtn.addEventListener('animationend', () => {
|
|
saveBtn.classList.remove('animated');
|
|
saveBtn.classList.remove('wobble');
|
|
});
|
|
}
|
|
|
|
// Track when people actually are working.
|
|
trackEvent.previewCount = (trackEvent.previewCount || 0) + 1;
|
|
if (trackEvent.previewCount === 4) {
|
|
trackEvent('fn', 'usingPreview');
|
|
}
|
|
}
|
|
}, updateDelay);
|
|
});
|
|
if (options.noAutocomplete) {
|
|
cm.addKeyMap({
|
|
'Ctrl-Space': 'autocomplete'
|
|
});
|
|
} else {
|
|
cm.on('inputRead', function onChange(editor, input) {
|
|
if (input.text[0] === ';' || input.text[0] === ' ') { return; }
|
|
CodeMirror.commands.autocomplete(cm, null, { completeSingle: false })
|
|
});
|
|
}
|
|
return cm;
|
|
}
|
|
|
|
scope.cm.html = initEditor(htmlCode, {
|
|
mode: 'htmlmixed',
|
|
profile: 'xhtml',
|
|
gutters: [ 'CodeMirror-linenumbers', 'CodeMirror-foldgutter' ],
|
|
noAutocomplete: true
|
|
});
|
|
emmetCodeMirror(scope.cm.html);
|
|
scope.cm.css = initEditor(cssCode, {
|
|
mode: 'css',
|
|
gutters: [ 'error-gutter', 'CodeMirror-linenumbers', 'CodeMirror-foldgutter' ]
|
|
});
|
|
emmetCodeMirror(scope.cm.css);
|
|
Inlet(scope.cm.css);
|
|
scope.cm.js = initEditor(jsCode, {
|
|
mode: 'javascript',
|
|
gutters: [ 'error-gutter', 'CodeMirror-linenumbers', 'CodeMirror-foldgutter' ]
|
|
});
|
|
Inlet(scope.cm.js);
|
|
|
|
// Initialize codemirror in console
|
|
scope.consoleCm = CodeMirror(consoleLogEl, {
|
|
mode: 'javascript',
|
|
lineWrapping: true,
|
|
theme: 'monokai',
|
|
foldGutter: true,
|
|
readOnly: true,
|
|
gutters: [ 'CodeMirror-foldgutter' ]
|
|
});
|
|
|
|
function openSettings() {
|
|
scope.toggleModal(settingsModal);
|
|
|
|
/* if (chrome.runtime.openOptionsPage) {
|
|
// New way to open options pages, if supported (Chrome 42+).
|
|
// Bug: https://bugs.chromium.org/p/chromium/issues/detail?id=601997
|
|
// Until this bug fixes, use the
|
|
// fallback.
|
|
chrome.runtime.openOptionsPage();
|
|
} else {
|
|
// Fallback.
|
|
chrome.tabs.create({
|
|
url: 'chrome://extensions?options=' + chrome.i18n.getMessage('@@extension_id')
|
|
});
|
|
} */
|
|
}
|
|
|
|
scope.onModalSettingsLinkClick = function onModalSettingsLinkClick() {
|
|
openSettings();
|
|
trackEvent('ui', 'onboardSettingsBtnClick');
|
|
}
|
|
|
|
scope.onShowInTabClicked = function onShowInTabClicked() {
|
|
onboardDontShowInTabOptionBtn.classList.remove('selected');
|
|
onboardShowInTabOptionBtn.classList.add('selected');
|
|
trackEvent('ui', 'onboardShowInTabClick');
|
|
}
|
|
scope.onDontShowInTabClicked = function onDontShowInTabClicked() {
|
|
onboardDontShowInTabOptionBtn.classList.add('selected');
|
|
onboardShowInTabOptionBtn.classList.remove('selected');
|
|
trackEvent('ui', 'onboardDontShowInTabClick');
|
|
}
|
|
|
|
scope.exportItems = function exportItems(e) {
|
|
fetchItems().then(function (items) {
|
|
var d = new Date();
|
|
var fileName = [ 'web-maker-export', d.getFullYear(), (d.getMonth() + 1), d.getDate(), d.getHours(), d.getMinutes(), d.getSeconds() ].join('-');
|
|
fileName += '.json';
|
|
var blob = new Blob([ JSON.stringify(items,false,2) ], { type: "application/json;charset=UTF-8" });
|
|
var a = document.createElement('a');
|
|
a.href = window.URL.createObjectURL(blob);
|
|
a.download = fileName;
|
|
a.style.display = 'none';
|
|
document.body.appendChild(a);
|
|
a.click();
|
|
a.remove();
|
|
trackEvent('ui', 'exportBtnClicked');
|
|
});
|
|
e.preventDefault();
|
|
}
|
|
|
|
function mergeImportedItems(items) {
|
|
var existingItemIds = [];
|
|
var toMergeItems = {};
|
|
items.forEach((item) => {
|
|
if (savedItems[item.id]) {
|
|
// Item already exists
|
|
existingItemIds.push(item.id);
|
|
} else {
|
|
utils.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) {
|
|
utils.log('shouldreplace', shouldReplace);
|
|
items.forEach((item) => {
|
|
toMergeItems[item.id] = item;
|
|
});
|
|
mergedItemCount = items.length;
|
|
}
|
|
}
|
|
if (mergedItemCount) {
|
|
// save new items
|
|
chrome.storage.local.set(toMergeItems, function () {
|
|
alertsService.add(mergedItemCount + ' creations imported successfully.');
|
|
});
|
|
// Push in new item IDs
|
|
chrome.storage.local.get({
|
|
items: {}
|
|
}, function (result) {
|
|
|
|
/* eslint-disable guard-for-in */
|
|
for (var id in toMergeItems) {
|
|
result.items[id] = true;
|
|
}
|
|
chrome.storage.local.set({
|
|
items: result.items
|
|
});
|
|
trackEvent('fn', 'itemsImported', mergedItemCount);
|
|
|
|
/* eslint-enable guard-for-in */
|
|
});
|
|
alertsService.add(mergedItemCount + ' creations imported successfully.');
|
|
}
|
|
// FIXME: Move from here
|
|
toggleSavedItemsPane(false);
|
|
}
|
|
|
|
function onImportFileChange(e) {
|
|
var file = e.target.files[0];
|
|
// if (!f.type.match('image.*')) {
|
|
// continue;
|
|
// }
|
|
|
|
var reader = new FileReader();
|
|
reader.onload = function(progressEvent) {
|
|
var items;
|
|
try {
|
|
items = JSON.parse(progressEvent.target.result);
|
|
utils.log(items);
|
|
mergeImportedItems(items);
|
|
} catch (ex) {
|
|
alert('Oops! Selected file is corrupted. Please select a file that was generated by clicking the "Export" button.')
|
|
}
|
|
};
|
|
|
|
reader.readAsText(file, 'utf-8');
|
|
}
|
|
|
|
scope.onImportBtnClicked = function exportItems(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', onImportFileChange);
|
|
input.click();
|
|
trackEvent('ui', 'importBtnClicked');
|
|
e.preventDefault();
|
|
}
|
|
|
|
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) {
|
|
utils.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);
|
|
}
|
|
|
|
scope.takeScreenshot = function (e) {
|
|
// 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 = frame.getBoundingClientRect();
|
|
c.width = iframeBounds.width;
|
|
c.height = iframeBounds.height;
|
|
var ctx = c.getContext('2d');
|
|
ctx.drawImage(image,
|
|
iframeBounds.left, iframeBounds.top, iframeBounds.width, iframeBounds.height,
|
|
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');
|
|
e.preventDefault();
|
|
}
|
|
|
|
// Populate the settings in the settings UI
|
|
function updateSettingsInUi() {
|
|
$('[data-setting=preserveLastCode]').checked = prefs.preserveLastCode;
|
|
$('[data-setting=replaceNewTab]').checked = prefs.replaceNewTab;
|
|
$('[data-setting=htmlMode]').value = prefs.htmlMode;
|
|
$('[data-setting=cssMode]').value = prefs.cssMode;
|
|
$('[data-setting=jsMode]').value = prefs.jsMode;
|
|
$('[data-setting=indentSize]').value = prefs.indentSize;
|
|
indentationSizeValueEl.textContent = prefs.indentSize;
|
|
$('[data-setting=indentWith][value=' + (prefs.indentWith || 'spaces') + ']').checked = true;
|
|
$('[data-setting=isCodeBlastOn]').checked = prefs.isCodeBlastOn;
|
|
$('[data-setting=editorTheme]').value = prefs.editorTheme;
|
|
$('[data-setting=keymap][value=' + (prefs.keymap || 'sublime') + ']').checked = true;
|
|
$('[data-setting=fontSize]').value = prefs.fontSize || 16;
|
|
$('[data-setting=refreshOnResize]').checked = prefs.refreshOnResize;
|
|
$('[data-setting=autoPreview]').checked = prefs.autoPreview;
|
|
$('[data-setting=editorFont]').value = prefs.editorFont;
|
|
}
|
|
|
|
/**
|
|
* Handles all user triggered preference changes in the UI.
|
|
*/
|
|
scope.updateSetting = function updateSetting(e) {
|
|
// If this was triggered from user interaction, save the setting
|
|
if (e) {
|
|
var settingName = e.target.dataset.setting;
|
|
var obj = {};
|
|
var el = e.target;
|
|
utils.log(settingName, (el.type === 'checkbox') ? el.checked : el.value);
|
|
prefs[settingName] = el.type === 'checkbox' ? el.checked : el.value;
|
|
obj[settingName] = prefs[settingName];
|
|
chrome.storage.sync.set(obj, function() {
|
|
alertsService.add('setting saved');
|
|
});
|
|
trackEvent('ui', 'updatePref-' + settingName, prefs[settingName]);
|
|
}
|
|
|
|
// Show/hide RUN button based on autoPreview setting.
|
|
runBtn.classList[prefs.autoPreview ? 'add' : 'remove']('hide');
|
|
|
|
htmlCode.querySelector('.CodeMirror').style.fontSize = prefs.fontSize;
|
|
cssCode.querySelector('.CodeMirror').style.fontSize = prefs.fontSize;
|
|
jsCode.querySelector('.CodeMirror').style.fontSize = prefs.fontSize;
|
|
consoleEl.querySelector('.CodeMirror').style.fontSize = prefs.fontSize;
|
|
|
|
// Update indentation count when slider is updated
|
|
indentationSizeValueEl.textContent = $('[data-setting=indentSize]').value;
|
|
|
|
// Replace correct css file in LINK tags's href
|
|
editorThemeLinkTag.href = '/lib/codemirror/theme/' + prefs.editorTheme + '.css';
|
|
fontStyleTag.textContent = fontStyleTemplate.textContent.replace(/fontname/g, prefs.editorFont || 'FiraCode');
|
|
|
|
['html', 'js', 'css'].forEach((type) => {
|
|
scope.cm[type].setOption(
|
|
'indentWithTabs',
|
|
$('[data-setting=indentWith]:checked').value !== 'spaces'
|
|
);
|
|
scope.cm[type].setOption('blastCode', $('[data-setting=isCodeBlastOn]').checked ? { effect: 2, shake: false } : false);
|
|
scope.cm[type].setOption('indentUnit', +$('[data-setting=indentSize]').value);
|
|
scope.cm[type].setOption('tabSize', +$('[data-setting=indentSize]').value);
|
|
scope.cm[type].setOption('theme', $('[data-setting=editorTheme]').value);
|
|
|
|
scope.cm[type].setOption('keyMap', $('[data-setting=keymap]:checked').value);
|
|
scope.cm[type].refresh();
|
|
});
|
|
scope.consoleCm.setOption('theme', $('[data-setting=editorTheme]').value);
|
|
};
|
|
|
|
scope.onNewBtnClick = function () {
|
|
trackEvent('ui', 'newBtnClick');
|
|
if (unsavedEditCount) {
|
|
var shouldDiscard = confirm('You have unsaved changes. Do you still want to create something new?');
|
|
if (shouldDiscard) {
|
|
createNewItem();
|
|
}
|
|
} else {
|
|
createNewItem();
|
|
}
|
|
};
|
|
scope.onOpenBtnClick = function () {
|
|
trackEvent('ui', 'openBtnClick');
|
|
openSavedItemsPane();
|
|
};
|
|
scope.onSaveBtnClick = function () {
|
|
trackEvent('ui', 'saveBtnClick', currentItem.id ? 'saved' : 'new');
|
|
saveItem();
|
|
};
|
|
|
|
/**
|
|
* Toggles a modal and logs an event.
|
|
* @param {Node} modal modal to be toggled
|
|
*/
|
|
scope.toggleModal = function (modal) {
|
|
modal.classList.toggle('is-modal-visible');
|
|
document.body.classList[modal.classList.contains('is-modal-visible') ? 'add' : 'remove']('overlay-visible');
|
|
};
|
|
scope.onSearchInputChange = function (e) {
|
|
const text = e.target.value;
|
|
let el;
|
|
for (const [itemId, item] of Object.entries(savedItems)) {
|
|
el = $(`#js-saved-items-pane [data-item-id=${itemId}]`);
|
|
if (item.title.toLowerCase().indexOf(text) === -1) {
|
|
el.classList.add('hide');
|
|
} else {
|
|
el.classList.remove('hide');
|
|
}
|
|
}
|
|
trackEvent('ui', 'searchInputType');
|
|
};
|
|
|
|
scope.toggleConsole = function () {
|
|
consoleEl.classList.toggle('is-minimized');
|
|
trackEvent('ui', 'consoleToggle');
|
|
};
|
|
// `clearConsole` is on window because it gets called from inside iframe also.
|
|
scope.clearConsole = window.clearConsole = function () {
|
|
scope.consoleCm.setValue('');
|
|
logCount = 0;
|
|
logCountEl.textContent = logCount;
|
|
};
|
|
scope.onClearConsoleBtnClick = function () {
|
|
scope.clearConsole();
|
|
trackEvent('ui', 'consoleClearBtnClick');
|
|
};
|
|
scope.evalConsoleExpr = function (e) {
|
|
// Clear console on CTRL + L
|
|
if (((e.which === 76 || e.which === 12) && e.ctrlKey)) {
|
|
scope.clearConsole();
|
|
trackEvent('ui', 'consoleClearKeyboardShortcut');
|
|
} else if (e.which === 13) {
|
|
window.onMessageFromConsole('> ' + e.target.value);
|
|
|
|
/* eslint-disable no-underscore-dangle */
|
|
frame.contentWindow._wmEvaluate(e.target.value);
|
|
|
|
/* eslint-enable no-underscore-dangle */
|
|
|
|
e.target.value = '';
|
|
trackEvent('fn', 'evalConsoleExpr');
|
|
}
|
|
};
|
|
window.onMessageFromConsole = function() {
|
|
|
|
/* eslint-disable no-param-reassign */
|
|
[...arguments].forEach(function(arg) {
|
|
if (arg && arg.indexOf && arg.indexOf('filesystem:chrome-extension') !== -1) {
|
|
arg = arg.replace(/filesystem:chrome-extension.*\.js:(\d+):*(\d*)/g, 'script $1:$2');
|
|
}
|
|
scope.consoleCm.replaceRange(arg + ' ' + ((arg + '').match(/\[object \w+]/) ? JSON.stringify(arg) : '') + '\n', { line: Infinity });
|
|
scope.consoleCm.scrollTo(0, Infinity);
|
|
logCount++;
|
|
});
|
|
logCountEl.textContent = logCount;
|
|
|
|
/* eslint-enable no-param-reassign */
|
|
};
|
|
|
|
function compileNodes() {
|
|
function attachListenerForEvent(eventName) {
|
|
const nodes = $all(`[d-${eventName}]`);
|
|
nodes.forEach(function (el) {
|
|
el.addEventListener(eventName, function (e) {
|
|
scope[el.getAttribute(`d-${eventName}`)].call(window, e)
|
|
});
|
|
});
|
|
}
|
|
attachListenerForEvent('click');
|
|
attachListenerForEvent('change');
|
|
attachListenerForEvent('input');
|
|
attachListenerForEvent('keyup');
|
|
}
|
|
|
|
function init () {
|
|
var lastCode;
|
|
|
|
CodeMirror.modeURL = "lib/codemirror/mode/%N/%N.js";
|
|
|
|
function getToggleLayoutButtonListener(mode) {
|
|
return function () {
|
|
saveSetting('layoutMode', mode);
|
|
trackEvent('ui', 'toggleLayoutClick', mode);
|
|
toggleLayout(mode);
|
|
return false;
|
|
};
|
|
}
|
|
layoutBtn1.addEventListener('click', getToggleLayoutButtonListener(1));
|
|
layoutBtn2.addEventListener('click', getToggleLayoutButtonListener(2));
|
|
layoutBtn3.addEventListener('click', getToggleLayoutButtonListener(3));
|
|
layoutBtn4.addEventListener('click', getToggleLayoutButtonListener(4));
|
|
|
|
utils.onButtonClick(helpBtn, function () {
|
|
scope.toggleModal(helpModal);
|
|
trackEvent('ui', 'helpButtonClick');
|
|
});
|
|
utils.onButtonClick(addLibraryBtn, function () {
|
|
scope.toggleModal(addLibraryModal);
|
|
trackEvent('ui', 'addLibraryButtonClick');
|
|
});
|
|
|
|
notificationsBtn.addEventListener('click', function () {
|
|
scope.toggleModal(notificationsModal);
|
|
|
|
if (notificationsModal.classList.contains('is-modal-visible') && !hasSeenNotifications) {
|
|
hasSeenNotifications = true;
|
|
notificationsBtn.classList.remove('has-new');
|
|
chrome.storage.sync.set({
|
|
lastSeenVersion: version
|
|
}, function () {});
|
|
}
|
|
trackEvent('ui', 'notificationButtonClick', version);
|
|
return false;
|
|
});
|
|
|
|
codepenBtn.addEventListener('click', function (e) {
|
|
if (cssMode === CssModes.ACSS) {
|
|
alert('Oops! CodePen doesn\'t supports Atomic CSS currently.');
|
|
e.preventDefault();
|
|
return;
|
|
}
|
|
var json = {
|
|
title: 'A Web Maker experiment',
|
|
html: scope.cm.html.getValue(),
|
|
css: scope.cm.css.getValue(),
|
|
js: scope.cm.js.getValue(),
|
|
|
|
/* eslint-disable camelcase */
|
|
html_pre_processor: modes[htmlMode].codepenVal,
|
|
css_pre_processor: modes[cssMode].codepenVal,
|
|
js_pre_processor: modes[jsMode].codepenVal,
|
|
|
|
css_external: externalCssTextarea.value.split('\n').join(';'),
|
|
js_external: externalJsTextarea.value.split('\n').join(';')
|
|
|
|
/* eslint-enable camelcase */
|
|
};
|
|
if (!currentItem.title.match(/Untitled\s\d\d*-\d/)) {
|
|
json.title = currentItem.title;
|
|
}
|
|
json = JSON.stringify(json);
|
|
codepenForm.querySelector('input').value = json;
|
|
codepenForm.submit();
|
|
trackEvent('ui', 'openInCodepen');
|
|
e.preventDefault();
|
|
});
|
|
|
|
utils.onButtonClick(saveHtmlBtn, function () {
|
|
saveFile();
|
|
trackEvent('ui', 'saveHtmlClick');
|
|
});
|
|
|
|
utils.onButtonClick(savedItemsPaneCloseBtn, toggleSavedItemsPane);
|
|
utils.onButtonClick(savedItemsPane, function (e) {
|
|
// TODO: warn about unsaved changes in current item
|
|
if (e.target.classList.contains('js-saved-item-tile')) {
|
|
setTimeout(function () {
|
|
openItem(e.target.dataset.itemId);
|
|
}, 350);
|
|
toggleSavedItemsPane();
|
|
}
|
|
if (e.target.classList.contains('js-saved-item-tile__remove-btn')) {
|
|
removeItem(e.target.parentElement.parentElement.dataset.itemId);
|
|
} else if (e.target.classList.contains('js-saved-item-tile__fork-btn')) {
|
|
toggleSavedItemsPane();
|
|
setTimeout(function () {
|
|
forkItem(savedItems[e.target.parentElement.parentElement.dataset.itemId]);
|
|
}, 350);
|
|
}
|
|
});
|
|
|
|
titleInput.addEventListener('blur', function () {
|
|
if (currentItem.id) {
|
|
saveItem();
|
|
trackEvent('ui', 'titleChanged');
|
|
}
|
|
});
|
|
|
|
// Attach listeners on mode change menu items
|
|
var modeItems = $all('.js-modes-menu a');
|
|
modeItems.forEach(function (item) {
|
|
item.addEventListener('click', function (e) {
|
|
var mode = e.currentTarget.dataset.mode;
|
|
var type = e.currentTarget.dataset.type;
|
|
var currentMode = type === 'html' ? htmlMode : (type === 'css' ? cssMode : jsMode);
|
|
if (currentMode !== mode) {
|
|
if (type === 'html') {
|
|
updateHtmlMode(mode).then(() => scope.setPreviewContent(true));
|
|
} else if (type === 'js') {
|
|
updateJsMode(mode).then(() => scope.setPreviewContent(true));
|
|
} else if (type === 'css') {
|
|
updateCssMode(mode).then(() => scope.setPreviewContent(true));
|
|
}
|
|
trackEvent('ui', 'updateCodeMode', mode);
|
|
}
|
|
});
|
|
});
|
|
|
|
// Collapse btn event listeners
|
|
var collapseBtns = $all('.js-code-collapse-btn');
|
|
collapseBtns.forEach(function (btn) {
|
|
btn.addEventListener('click', function (e) {
|
|
var codeWrapParent = e.currentTarget.parentElement.parentElement.parentElement;
|
|
toggleCodeWrapCollapse(codeWrapParent);
|
|
trackEvent('ui', 'paneCollapseBtnClick', codeWrapParent.dataset.type);
|
|
return false;
|
|
});
|
|
});
|
|
|
|
// Update code wrap collapse states whenever any of them transitions due to any
|
|
// reason.
|
|
[ htmlCode, cssCode, jsCode ].forEach(function (el) {
|
|
el.addEventListener('transitionend', function() {
|
|
updateCodeWrapCollapseStates();
|
|
});
|
|
});
|
|
|
|
// Editor keyboard shortucuts
|
|
window.addEventListener('keydown', function (event) {
|
|
var selectedItemElement;
|
|
// TODO: refactor common listener code
|
|
// Ctrl/⌘ + S
|
|
if ((event.ctrlKey || event.metaKey) && (event.keyCode === 83)) {
|
|
event.preventDefault();
|
|
saveItem();
|
|
trackEvent('ui', 'saveItemKeyboardShortcut');
|
|
}
|
|
// Ctrl/⌘ + Shift + 5
|
|
if (!prefs.autoPreview && (event.ctrlKey || event.metaKey) && event.shiftKey && (event.keyCode === 53)) {
|
|
event.preventDefault();
|
|
scope.setPreviewContent(true);
|
|
trackEvent('ui', 'previewKeyboardShortcut');
|
|
}
|
|
// Ctrl/⌘ + O
|
|
else if ((event.ctrlKey || event.metaKey) && (event.keyCode === 79)) {
|
|
event.preventDefault();
|
|
openSavedItemsPane();
|
|
trackEvent('ui', 'openCreationKeyboardShortcut');
|
|
}
|
|
else if (event.keyCode === 27) {
|
|
closeAllOverlays();
|
|
}
|
|
if (event.keyCode === 40 && isSavedItemsPaneOpen) {
|
|
// Return if no items present.
|
|
if (!$all('.js-saved-item-tile').length) {
|
|
return;
|
|
}
|
|
selectedItemElement = $('.js-saved-item-tile.selected');
|
|
if (selectedItemElement) {
|
|
selectedItemElement.classList.remove('selected');
|
|
selectedItemElement.nextUntil('.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);
|
|
} else if (event.keyCode === 38 && isSavedItemsPaneOpen) {
|
|
if (!$all('.js-saved-item-tile').length) {
|
|
return;
|
|
}
|
|
selectedItemElement = $('.js-saved-item-tile.selected');
|
|
if (selectedItemElement) {
|
|
selectedItemElement.classList.remove('selected');
|
|
selectedItemElement.previousUntil('.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);
|
|
} else if (event.keyCode === 13 && isSavedItemsPaneOpen) {
|
|
selectedItemElement = $('.js-saved-item-tile.selected');
|
|
if (!selectedItemElement) { return; }
|
|
setTimeout(function () {
|
|
openItem(selectedItemElement.dataset.itemId);
|
|
}, 350);
|
|
toggleSavedItemsPane();
|
|
}
|
|
|
|
// Fork shortcut inside saved creations panel with Ctrl/⌘ + F
|
|
if (isSavedItemsPaneOpen && (event.ctrlKey || event.metaKey) && (event.keyCode === 70)) {
|
|
event.preventDefault();
|
|
selectedItemElement = $('.js-saved-item-tile.selected');
|
|
setTimeout(function () {
|
|
forkItem(savedItems[selectedItemElement.dataset.itemId]);
|
|
}, 350);
|
|
toggleSavedItemsPane();
|
|
trackEvent('ui', 'forkKeyboardShortcut');
|
|
}
|
|
});
|
|
|
|
window.addEventListener('click', function(e) {
|
|
if (typeof e.target.className === 'string' && e.target.className.indexOf('modal-overlay') !== -1) {
|
|
closeAllOverlays();
|
|
}
|
|
});
|
|
window.addEventListener('dblclick', function(e) {
|
|
var target = e.target;
|
|
if (target.classList.contains('js-console__header')) {
|
|
scope.toggleConsole();
|
|
trackEvent('ui', 'consoleToggleDblClick');
|
|
}
|
|
if (target.classList.contains('js-code-wrap__header')) {
|
|
var codeWrapParent = target.parentElement;
|
|
toggleCodeWrapCollapse(codeWrapParent);
|
|
trackEvent('ui', 'paneHeaderDblClick', codeWrapParent.dataset.type);
|
|
}
|
|
});
|
|
|
|
utils.onButtonClick(settingsBtn, function() {
|
|
openSettings();
|
|
trackEvent('ui', 'settingsBtnClick');
|
|
});
|
|
|
|
// Initialize add library select box
|
|
var libOptions = window.jsLibs.reduce(
|
|
(html, lib) => html + `<option data-type="${lib.type}" value="${lib.url}">${lib.label}</option>`,
|
|
'');
|
|
addLibrarySelect.children[1].innerHTML = libOptions;
|
|
libOptions = window.cssLibs.reduce(
|
|
(html, lib) => html + `<option data-type="${lib.type}" value="${lib.url}">${lib.label}</option>`,
|
|
'');
|
|
addLibrarySelect.children[2].innerHTML = libOptions;
|
|
addLibrarySelect.addEventListener('change', function onSelectChange(e) {
|
|
var target = e.target;
|
|
if (!target.value) { return; }
|
|
$('#js-external-' + target.selectedOptions[0].dataset.type).value += '\n' + target.value;
|
|
trackEvent('ui', 'addLibrarySelect', target.selectedOptions[0].label);
|
|
onExternalLibChange();
|
|
// Reset the select to the default value
|
|
target.value = '';
|
|
});
|
|
externalJsTextarea.addEventListener('blur', onExternalLibChange);
|
|
externalCssTextarea.addEventListener('blur', onExternalLibChange);
|
|
|
|
new TextareaAutoComplete(externalJsTextarea, (obj) => obj.latest.match(/\.js$/));
|
|
new TextareaAutoComplete(externalCssTextarea, (obj) => obj.latest.match(/\.css$/));
|
|
|
|
// Console header drag resize logic
|
|
var consoleHeaderDragStartY;
|
|
var consoleInitialHeight;
|
|
function onConsoleHeaderDrag(e) {
|
|
consoleEl.style.height = (consoleInitialHeight + consoleHeaderDragStartY - e.pageY) + 'px';
|
|
}
|
|
$('.js-console__header').addEventListener('mousedown', (e) => {
|
|
consoleHeaderDragStartY = e.pageY;
|
|
consoleInitialHeight = consoleEl.getBoundingClientRect().height;
|
|
$('#demo-frame').classList.add('pointer-none');
|
|
window.addEventListener('mousemove', onConsoleHeaderDrag);
|
|
});
|
|
$('.js-console__header').addEventListener('mouseup', () => {
|
|
window.removeEventListener('mousemove', onConsoleHeaderDrag);
|
|
$('#demo-frame').classList.remove('pointer-none');
|
|
});
|
|
|
|
chrome.storage.local.get({
|
|
layoutMode: 1,
|
|
code: ''
|
|
}, function localGetCallback(result) {
|
|
toggleLayout(result.layoutMode);
|
|
prefs.layoutMode = result.layoutMode;
|
|
if (result.code) {
|
|
lastCode = result.code;
|
|
}
|
|
});
|
|
|
|
// Get synced `preserveLastCode` setting to get back last code (or not).
|
|
chrome.storage.sync.get({
|
|
preserveLastCode: true,
|
|
replaceNewTab: false,
|
|
htmlMode: 'html',
|
|
jsMode: 'js',
|
|
cssMode: 'css',
|
|
isCodeBlastOn: false,
|
|
indentWith: 'spaces',
|
|
indentSize: 2,
|
|
editorTheme: 'monokai',
|
|
keymap: 'sublime',
|
|
fontSize: 16,
|
|
refreshOnResize: false,
|
|
autoPreview: true,
|
|
editorFont: 'FiraCode'
|
|
}, function syncGetCallback(result) {
|
|
if (result.preserveLastCode && lastCode) {
|
|
unsavedEditCount = 0;
|
|
if (lastCode.id) {
|
|
chrome.storage.local.get(lastCode.id, function (itemResult) {
|
|
utils.log('Load item ', lastCode.id)
|
|
currentItem = itemResult[lastCode.id];
|
|
refreshEditor();
|
|
});
|
|
} else {
|
|
utils.log('Load last unsaved item');
|
|
currentItem = lastCode;
|
|
refreshEditor();
|
|
}
|
|
} else {
|
|
createNewItem();
|
|
}
|
|
prefs.preserveLastCode = result.preserveLastCode;
|
|
prefs.replaceNewTab = result.replaceNewTab;
|
|
prefs.htmlMode = result.htmlMode;
|
|
prefs.cssMode = result.cssMode;
|
|
prefs.jsMode = result.jsMode;
|
|
prefs.isCodeBlastOn = result.isCodeBlastOn;
|
|
prefs.indentSize = result.indentSize;
|
|
prefs.indentWith = result.indentWith;
|
|
prefs.editorTheme = result.editorTheme;
|
|
prefs.keymap = result.keymap;
|
|
prefs.fontSize = result.fontSize;
|
|
prefs.refreshOnResize = result.refreshOnResize;
|
|
prefs.autoPreview = result.autoPreview;
|
|
prefs.editorFont = result.editorFont;
|
|
|
|
updateSettingsInUi();
|
|
scope.updateSetting();
|
|
});
|
|
|
|
// Check for new version notifications
|
|
chrome.storage.sync.get({
|
|
lastSeenVersion: ''
|
|
}, function syncGetCallback(result) {
|
|
// Check if new user
|
|
if (!result.lastSeenVersion) {
|
|
onboardModal.classList.add('is-modal-visible');
|
|
trackEvent('ui', 'onboardModalSeen');
|
|
// set the current version as seen on closing the onboard modal
|
|
utils.once(document, 'overlaysClosed', function () {
|
|
chrome.storage.sync.set({
|
|
lastSeenVersion: version
|
|
}, function () {});
|
|
|
|
chrome.storage.sync.set({
|
|
replaceNewTab: onboardShowInTabOptionBtn.classList.contains('selected')
|
|
}, function () {
|
|
trackEvent('fn', 'setReplaceNewTabFromOnboard', onboardShowInTabOptionBtn.classList.contains('selected'));
|
|
});
|
|
});
|
|
}
|
|
if (!result.lastSeenVersion || utils.semverCompare(result.lastSeenVersion, version) === -1) {
|
|
notificationsBtn.classList.add('has-new');
|
|
hasSeenNotifications = false;
|
|
}
|
|
});
|
|
|
|
var options = '';
|
|
[
|
|
'3024-day',
|
|
'3024-night',
|
|
'abcdef',
|
|
'ambiance',
|
|
'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'
|
|
].forEach((theme) => { options += '<option value="' + theme + '">' + theme + '</option>'; });
|
|
document.querySelector('[data-setting="editorTheme"]').innerHTML = options;
|
|
|
|
requestAnimationFrame(compileNodes);
|
|
}
|
|
|
|
// Set few stuff on a 'scope' object so that they can be referenced dynamically.
|
|
scope.closeAllOverlays = closeAllOverlays;
|
|
|
|
init();
|
|
|
|
})(window.alertsService); |