1
0
mirror of https://github.com/chinchang/web-maker.git synced 2025-10-14 04:54:24 +02:00

Merge pull request #561 from chinchang/manifestv3

Manifestv3
This commit is contained in:
Kushagra Gour
2024-05-10 17:28:17 +05:30
committed by GitHub
27 changed files with 1582 additions and 987 deletions

View File

@@ -195,6 +195,8 @@ gulp.task('packageExtension', function () {
child_process.execSync('cp src/options.html extension'); child_process.execSync('cp src/options.html extension');
child_process.execSync('cp src/eventPage.js extension'); child_process.execSync('cp src/eventPage.js extension');
child_process.execSync('cp src/icon-16.png extension'); child_process.execSync('cp src/icon-16.png extension');
child_process.execSync('cp offscreen.html extension');
child_process.execSync('cp offscreen.js extension');
child_process.execSync('rm -rf extension/service-worker.js'); child_process.execSync('rm -rf extension/service-worker.js');
return merge( return merge(
gulp gulp
@@ -292,10 +294,13 @@ const buildExtension = series(
); );
function runWatcher(cb) { function runWatcher(cb) {
return watch(['src/**/*.js', 'src/**/*.jsx'], function (cbb) { return watch(
['src/**/*.js', 'src/**/*.jsx', 'src/**/*.json'],
function (cbb) {
buildExtension(); buildExtension();
cbb(); cbb();
}); }
);
cb(); cb();
} }

2
offscreen.html Normal file
View File

@@ -0,0 +1,2 @@
<!doctype html>
<script src="./offscreen.js"></script>

44
offscreen.js Normal file
View File

@@ -0,0 +1,44 @@
// This URL must point to the public site
const _URL = 'https://webmaker.app/signup';
const iframe = document.createElement('iframe');
iframe.src = _URL;
document.documentElement.appendChild(iframe);
chrome.runtime.onMessage.addListener(handleChromeMessages);
function handleChromeMessages(message, sender, sendResponse) {
// Extensions may have an number of other reasons to send messages, so you
// should filter out any that are not meant for the offscreen document.
if (message.target !== 'offscreen') {
return false;
}
function handleIframeMessage({ data }) {
console.log('got postmessage in offscreen doc from iframe');
try {
if (data.startsWith('!_{')) {
// Other parts of the Firebase library send messages using postMessage.
// You don't care about them in this context, so return early.
return;
}
data = JSON.parse(data);
self.removeEventListener('message', handleIframeMessage);
// being sent back to worker
sendResponse(data);
} catch (e) {
console.log(`json parse failed - ${e.message}`);
}
}
globalThis.addEventListener('message', handleIframeMessage, false);
// Initialize the authentication flow in the iframed document. You must set the
// second argument (targetOrigin) of the message in order for it to be successfully
// delivered.
iframe.contentWindow.postMessage(
{ initAuth: true, providerName: message.providerName },
new URL(_URL).origin
);
return true;
}

1697
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,6 +1,6 @@
{ {
"name": "web-maker", "name": "web-maker",
"version": "6.2.0", "version": "6.3.0",
"description": "A blazing fast & offline web playground", "description": "A blazing fast & offline web playground",
"scripts": { "scripts": {
"start": "concurrently --kill-others \"gulp start-preview-server\" \"npm run -s dev\"", "start": "concurrently --kill-others \"gulp start-preview-server\" \"npm run -s dev\"",
@@ -75,8 +75,8 @@
"code-blast-codemirror": "chinchang/code-blast-codemirror#web-maker", "code-blast-codemirror": "chinchang/code-blast-codemirror#web-maker",
"codemirror": "^5.65.16", "codemirror": "^5.65.16",
"copy-webpack-plugin": "^4.5.1", "copy-webpack-plugin": "^4.5.1",
"firebase": "^10.8.0",
"esprima-next": "^6.0.3", "esprima-next": "^6.0.3",
"firebase": "^8.10.0",
"jszip": "^3.1.5", "jszip": "^3.1.5",
"preact": "^10.17.0", "preact": "^10.17.0",
"preact-portal": "^1.1.3", "preact-portal": "^1.1.3",

View File

@@ -1 +1,11 @@
<!doctype html><html><head><title>signInWithPopup</title></head><body> <h1>signInWithPopup</h1> <script type="module" src="/signup/index.1903c1b7.js"></script> </body></html> <!doctype html>
<html>
<head>
<title>signInWithPopup</title>
</head>
<body>
<h1>signInWithPopup</h1>
<script src="/index.a3cfa626.js" defer=""></script>
</body>
</html>

View File

@@ -1,35 +1,78 @@
import { trackEvent } from './analytics'; import { trackEvent } from './analytics';
import firebase from 'firebase/app'; import { auth } from './firebaseInit';
import { log } from './utils'; import { log } from './utils';
import {
GithubAuthProvider as ExtensionGithubAuthProvider,
GoogleAuthProvider as ExtensionGoogleAuthProvider,
signOut,
signInWithCredential
} from 'firebase/auth/web-extension';
import {
signInWithPopup,
GithubAuthProvider,
GoogleAuthProvider
} from 'firebase/auth';
export const auth = { export const authh = {
logout() { logout() {
firebase.auth().signOut(); signOut(auth);
}, },
login(providerName) { async login(providerName) {
var provider; const onSuccess = () => {
if (providerName === 'facebook') { trackEvent(
provider = new firebase.auth.FacebookAuthProvider(); 'fn',
} else if (providerName === 'twitter') { 'loggedIn',
provider = new firebase.auth.TwitterAuthProvider(); providerName,
} else if (providerName === 'google') { window.IS_EXTENSION ? 'extension' : 'web'
provider = new firebase.auth.GoogleAuthProvider(); );
provider.addScope('https://www.googleapis.com/auth/userinfo.profile');
} else {
provider = new firebase.auth.GithubAuthProvider();
}
return firebase
.auth()
.signInWithPopup(provider)
.then(function() {
trackEvent('fn', 'loggedIn', providerName);
// Save to recommend next time // Save to recommend next time
window.db.local.set({ window.db.local.set({
lastAuthProvider: providerName lastAuthProvider: providerName
}); });
}) };
.catch(function(error) {
if (window.IS_EXTENSION) {
const result = await chrome.runtime.sendMessage({
type: 'firebase-auth',
providerName
});
if (
result.name === 'FirebaseError' &&
result.code === 'auth/account-exists-with-different-credential'
) {
alert(
'You have already signed up with the same email using different social login'
);
return;
}
let credential;
switch (providerName) {
case 'google':
credential = ExtensionGoogleAuthProvider.credentialFromResult(result);
break;
case 'github':
credential = ExtensionGithubAuthProvider.credentialFromResult(result);
break;
}
// authenticationObject is of the type UserCredentialImpl. Use it to authenticate here
return signInWithCredential(auth, credential).then(onSuccess);
}
var provider;
if (providerName === 'google') {
provider = new GoogleAuthProvider();
provider.addScope('https://www.googleapis.com/auth/userinfo.profile');
} else {
provider = new GithubAuthProvider();
}
return signInWithPopup(auth, provider)
.then(onSuccess)
.catch(function (error) {
log(error); log(error);
if (error.code === 'auth/account-exists-with-different-credential') { if (error.code === 'auth/account-exists-with-different-credential') {
alert( alert(

View File

@@ -134,7 +134,8 @@ export default class AddLibrary extends Component {
https://cdnjs.cloudflare.com, https://unpkg.com, https://cdnjs.cloudflare.com, https://unpkg.com,
https://maxcdn.com, https://cdn77.com, https://maxcdn.com, https://cdn77.com,
https://maxcdn.bootstrapcdn.com, https://cdn.jsdelivr.net/, https://maxcdn.bootstrapcdn.com, https://cdn.jsdelivr.net/,
https://rawgit.com, https://wzrd.in, https://cdn.tailwindcss.com https://rawgit.com, https://wzrd.in, https://cdn.tailwindcss.com,
https://assets.webmaker.app
</p> </p>
<textarea <textarea

View File

@@ -1,6 +1,12 @@
import React, { useState, useEffect } from 'react'; import React, { useState, useEffect } from 'react';
import firebase from 'firebase/app'; import {
import 'firebase/storage'; deleteObject,
uploadBytesResumable,
ref,
listAll,
getDownloadURL
} from 'firebase/storage';
import { storage } from '../firebaseInit';
import { HStack, Stack, VStack } from './Stack'; import { HStack, Stack, VStack } from './Stack';
import { copyToClipboard } from '../utils'; import { copyToClipboard } from '../utils';
import { Trans } from '@lingui/macro'; import { Trans } from '@lingui/macro';
@@ -17,6 +23,7 @@ function getFileType(url) {
} }
return ext; return ext;
} }
const Assets = ({ onProBtnClick, onLoginBtnClick }) => { const Assets = ({ onProBtnClick, onLoginBtnClick }) => {
const [files, setFiles] = useState([]); const [files, setFiles] = useState([]);
const [isFetchingFiles, setIsFetchingFiles] = useState(false); const [isFetchingFiles, setIsFetchingFiles] = useState(false);
@@ -26,8 +33,6 @@ const Assets = ({ onProBtnClick, onLoginBtnClick }) => {
const [uploadProgress, setUploadProgress] = useState(); const [uploadProgress, setUploadProgress] = useState();
const [listType, setListType] = useState('grid'); const [listType, setListType] = useState('grid');
const storageRef = firebase.storage().ref(`assets/${window.user?.uid}`);
const uploadFile = file => { const uploadFile = file => {
if (file.size > 1024 * 1024) { if (file.size > 1024 * 1024) {
// 1MB limit // 1MB limit
@@ -39,9 +44,11 @@ const Assets = ({ onProBtnClick, onLoginBtnClick }) => {
const metadata = { const metadata = {
cacheControl: 'public, max-age=3600' // 1 hr cacheControl: 'public, max-age=3600' // 1 hr
}; };
const task = uploadBytesResumable(
const fileRef = storageRef.child(file.name); ref(storage, `assets/${window.user?.uid}/${file.name}`),
const task = fileRef.put(file, metadata); file,
metadata
);
task.on( task.on(
'state_changed', 'state_changed',
@@ -49,12 +56,12 @@ const Assets = ({ onProBtnClick, onLoginBtnClick }) => {
// Observe state change events such as progress, pause, and resume // Observe state change events such as progress, pause, and resume
// Get task progress, including the number of bytes uploaded and the total number of bytes to be uploaded // Get task progress, including the number of bytes uploaded and the total number of bytes to be uploaded
var progress = (snapshot.bytesTransferred / snapshot.totalBytes) * 100; var progress = (snapshot.bytesTransferred / snapshot.totalBytes) * 100;
console.log('Upload is ' + progress + '% done'); // console.log('Upload is ' + progress + '% done');
}, },
error => { error => {
// Handle unsuccessful uploads // Handle unsuccessful uploads
setIsUploading(false); setIsUploading(false);
console.error('File upload error:', error); // console.log('File upload error:', error);
alertsService.add('⚠️ File upload failed'); alertsService.add('⚠️ File upload failed');
}, },
() => { () => {
@@ -78,11 +85,11 @@ const Assets = ({ onProBtnClick, onLoginBtnClick }) => {
// Function to fetch existing files // Function to fetch existing files
const fetchFiles = () => { const fetchFiles = () => {
setIsFetchingFiles(true); setIsFetchingFiles(true);
storageRef
.listAll() listAll(ref(storage, `assets/${window.user?.uid}`))
.then(result => { .then(result => {
const filePromises = result.items.map(item => { const filePromises = result.items.map(item => {
return item.getDownloadURL().then(url => { return getDownloadURL(item).then(url => {
return { name: item.name, url }; return { name: item.name, url };
}); });
}); });
@@ -190,9 +197,8 @@ const Assets = ({ onProBtnClick, onLoginBtnClick }) => {
const file = files[index]; const file = files[index];
const answer = confirm(`Are you sure you want to delete "${file.name}"?`); const answer = confirm(`Are you sure you want to delete "${file.name}"?`);
if (!answer) return; if (!answer) return;
const fileRef = storageRef.child(file.name); const fileRef = ref(storage, file.url);
fileRef deleteObject(fileRef)
.delete()
.then(() => { .then(() => {
alertsService.add('File deleted successfully'); alertsService.add('File deleted successfully');
setFiles(files.filter((_, i) => i !== index)); setFiles(files.filter((_, i) => i !== index));
@@ -259,6 +265,15 @@ const Assets = ({ onProBtnClick, onLoginBtnClick }) => {
</div> </div>
</div> </div>
{isFetchingFiles && <LoaderWithText>Fetching files...</LoaderWithText>} {isFetchingFiles && <LoaderWithText>Fetching files...</LoaderWithText>}
{!isFetchingFiles && !files.length ? (
<Stack justify="center">
<Text align="center" appearance="secondary">
No files uploaded yet
</Text>
</Stack>
) : null}
<VStack align="stretch" gap={1}> <VStack align="stretch" gap={1}>
{files.length ? ( {files.length ? (
<Stack gap={1}> <Stack gap={1}>

View File

@@ -106,6 +106,19 @@ export function Changelog(props) {
return ( return (
<div> <div>
<h1>Whats new?</h1> <h1>Whats new?</h1>
<Notification version="6.3.0" {...props} isLatest={true}>
<NotificationItem type="bug">
Extension Only: JavaScript is NOW WORKING, again! 🎉 The extension is
now migrated to MV3 which means its much more secure, and of course,
JavaScript now works again! 🥳
</NotificationItem>
<NotificationItem type="ui">
Tailwind CSS 2 template has been removed since Extension now supports
the lateat Tailwind CSS 3 template.
</NotificationItem>
</Notification>
<Notification version="6.2.0" {...props} isLatest={true}> <Notification version="6.2.0" {...props} isLatest={true}>
<li>Preact template updated to latest version with hooks.</li> <li>Preact template updated to latest version with hooks.</li>
<li>Mail option added in help modal.</li> <li>Mail option added in help modal.</li>

View File

@@ -38,7 +38,7 @@ export default class ContentWrap extends Component {
window.localStorage.getItem(LocalStorageKeys.WAS_CONSOLE_OPEN) === window.localStorage.getItem(LocalStorageKeys.WAS_CONSOLE_OPEN) ===
'true', 'true',
isCssSettingsModalOpen: false, isCssSettingsModalOpen: false,
isPreviewNotWorkingModalVisible: false,
logs: [] logs: []
}; };
@@ -63,15 +63,11 @@ export default class ContentWrap extends Component {
this.clearConsoleBtnClickHandler.bind(this); this.clearConsoleBtnClickHandler.bind(this);
this.toggleConsole = this.toggleConsole.bind(this); this.toggleConsole = this.toggleConsole.bind(this);
this.evalConsoleExpr = this.evalConsoleExpr.bind(this); this.evalConsoleExpr = this.evalConsoleExpr.bind(this);
this.dismissPreviewNotWorkingModal =
this.dismissPreviewNotWorkingModal.bind(this);
} }
shouldComponentUpdate(nextProps, nextState) { shouldComponentUpdate(nextProps, nextState) {
return ( return (
this.state.isConsoleOpen !== nextState.isConsoleOpen || this.state.isConsoleOpen !== nextState.isConsoleOpen ||
this.state.isCssSettingsModalOpen !== nextState.isCssSettingsModalOpen || this.state.isCssSettingsModalOpen !== nextState.isCssSettingsModalOpen ||
this.state.isPreviewNotWorkingModalVisible !==
nextState.isPreviewNotWorkingModalVisible ||
this.state.logs !== nextState.logs || this.state.logs !== nextState.logs ||
this.state.codeSplitSizes !== nextState.codeSplitSizes || this.state.codeSplitSizes !== nextState.codeSplitSizes ||
this.state.mainSplitSizes !== nextState.mainSplitSizes || this.state.mainSplitSizes !== nextState.mainSplitSizes ||
@@ -145,10 +141,7 @@ export default class ContentWrap extends Component {
createPreviewFile(html, css, js) { createPreviewFile(html, css, js) {
const versionMatch = navigator.userAgent.match(/Chrome\/(\d+)/); const versionMatch = navigator.userAgent.match(/Chrome\/(\d+)/);
const shouldInlineJs = const shouldInlineJs = true;
!window.webkitRequestFileSystem ||
!window.IS_EXTENSION ||
(versionMatch && +versionMatch[1] >= 104);
var contents = getCompleteHtml( var contents = getCompleteHtml(
html, html,
css, css,
@@ -164,7 +157,6 @@ export default class ContentWrap extends Component {
trackEvent.hasTrackedCode = true; trackEvent.hasTrackedCode = true;
} }
if (shouldInlineJs) {
if (this.detachedWindow) { if (this.detachedWindow) {
log('✉️ Sending message to detached window'); log('✉️ Sending message to detached window');
this.detachedWindow.postMessage({ contents }, '*'); this.detachedWindow.postMessage({ contents }, '*');
@@ -192,12 +184,8 @@ export default class ContentWrap extends Component {
log('resolved with ', resolutionReason); log('resolved with ', resolutionReason);
fn(); fn();
}); });
// Setting to blank string cause frame to reload
if (window.IS_EXTENSION) {
this.frame.src = '';
} else {
this.frame.src = this.frame.src; this.frame.src = this.frame.src;
}
}; };
const writeInsideIframe = () => { const writeInsideIframe = () => {
if (!cachedSandboxAttribute && window.DEBUG) { if (!cachedSandboxAttribute && window.DEBUG) {
@@ -205,13 +193,7 @@ export default class ContentWrap extends Component {
} }
log('sending PM'); log('sending PM');
if (window.IS_EXTENSION) {
this.frame.contentDocument.open();
this.frame.contentDocument.write(contents);
this.frame.contentDocument.close();
} else {
this.frame.contentWindow.postMessage({ contents }, '*'); this.frame.contentWindow.postMessage({ contents }, '*');
}
}; };
// refreshAndDo(() => { // refreshAndDo(() => {
// cachedSandboxAttribute = this.frame.getAttribute('sandbox'); // cachedSandboxAttribute = this.frame.getAttribute('sandbox');
@@ -222,23 +204,6 @@ export default class ContentWrap extends Component {
// }); // });
refreshAndDo(writeInsideIframe); refreshAndDo(writeInsideIframe);
} }
} 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({ url: src }, '*');
} else {
this.frame.src = src;
}
});
});
}
} }
cleanupErrors(lang) { cleanupErrors(lang) {
this.cm[lang].clearGutter('error-gutter'); this.cm[lang].clearGutter('error-gutter');
@@ -277,14 +242,13 @@ export default class ContentWrap extends Component {
js: this.cmCodes.js js: this.cmCodes.js
}; };
log('🔎 setPreviewContent', isForced); log('🔎 setPreviewContent', isForced);
const targetFrame = this.detachedWindow const targetFrame = this.detachedWindow ? this.detachedWindow : this.frame;
? this.detachedWindow //this.detachedWindow.document.querySelector('iframe')
: this.frame;
const cssMode = this.props.currentItem.cssMode; const cssMode = this.props.currentItem.cssMode;
// If just CSS was changed (and everything shudn't be empty), // If just CSS was changed (and everything shudn't be empty),
// change the styles inside the iframe. // change the styles inside the iframe.
if ( if (
false &&
!isForced && !isForced &&
currentCode.html === this.codeInPreview.html && currentCode.html === this.codeInPreview.html &&
currentCode.js === this.codeInPreview.js && currentCode.js === this.codeInPreview.js &&
@@ -335,19 +299,6 @@ export default class ContentWrap extends Component {
this.showErrors(resultItem.errors.lang, resultItem.errors.data); this.showErrors(resultItem.errors.lang, resultItem.errors.data);
} }
}); });
const versionMatch = navigator.userAgent.match(/Chrome\/(\d+)/);
if (
result[2].code &&
versionMatch &&
+versionMatch[1] >= 104 &&
window.IS_EXTENSION &&
!window.sessionStorage.getItem('previewErrorMessageDismissed')
) {
this.setState({
isPreviewNotWorkingModalVisible: true
});
}
}); });
} }
@@ -687,13 +638,6 @@ export default class ContentWrap extends Component {
this.props.onPrettifyBtnClick(codeType); this.props.onPrettifyBtnClick(codeType);
} }
dismissPreviewNotWorkingModal() {
this.setState({
isPreviewNotWorkingModalVisible: false
});
window.sessionStorage.setItem('previewErrorMessageDismissed', true);
}
render() { render() {
// log('contentwrap update'); // log('contentwrap update');
@@ -960,7 +904,7 @@ export default class ContentWrap extends Component {
ref={el => (this.frame = el)} ref={el => (this.frame = el)}
frameborder="0" frameborder="0"
id="demo-frame" id="demo-frame"
allowpaymentrequest="true" src="./indexpm.html"
allowfullscreen="true" allowfullscreen="true"
/> />
) : ( ) : (
@@ -996,34 +940,6 @@ export default class ContentWrap extends Component {
settings={this.props.currentItem.cssSettings} settings={this.props.currentItem.cssSettings}
editorTheme={this.props.prefs.editorTheme} editorTheme={this.props.prefs.editorTheme}
/> />
<Modal
show={this.state.isPreviewNotWorkingModalVisible}
closeHandler={this.dismissPreviewNotWorkingModal}
>
<h1>🔴 JavaScript preview not working!</h1>
<p>
Latest version of Chrome has changed a few things because of which
JavaScript preview has stopped working.
</p>
<p>
If you want to work with just HTML & CSS, you can still continue
here. Otherwise, it is recommended to switch to the Web Maker web
app which is much more powerful and works offline too -{' '}
<a href="https://webmaker.app/app" target="_blank">
Go to web app
</a>
</p>
<div class="flex flex-h-end">
<button
class="btn btn--primary"
onClick={this.dismissPreviewNotWorkingModal}
>
Dismiss
</button>
</div>
</Modal>
</div> </div>
</SplitPane> </SplitPane>
); );

View File

@@ -257,7 +257,7 @@ export default class ContentWrapFiles extends Component {
obj[file.path] = obj[file.path] =
'<script src="' + '<script src="' +
(chrome.extension (chrome.extension
? chrome.extension.getURL('/lib/screenlog.js') ? chrome.runtime.getURL('lib/screenlog.js')
: `${location.origin}${ : `${location.origin}${
window.DEBUG ? '' : BASE_PATH window.DEBUG ? '' : BASE_PATH
}/lib/screenlog.js`) + }/lib/screenlog.js`) +

View File

@@ -1,12 +1,12 @@
import { h, Component } from 'preact'; import { h, Component } from 'preact';
import { trackEvent } from '../analytics'; import { trackEvent } from '../analytics';
import { auth } from '../auth'; import { authh } from '../auth';
export default class Login extends Component { export default class Login extends Component {
login(e) { login(e) {
const provider = e.target.dataset.authProvider; const provider = e.target.dataset.authProvider;
trackEvent('ui', 'loginProviderClick', provider); trackEvent('ui', 'loginProviderClick', provider);
auth.login(provider); authh.login(provider);
} }
componentDidMount() { componentDidMount() {
window.db.local.get( window.db.local.get(

View File

@@ -9,7 +9,9 @@ import { Icon } from './Icons';
import { Text } from './Text'; import { Text } from './Text';
const FREE_PUBLIC_ITEM_COUNT = 1; const FREE_PUBLIC_ITEM_COUNT = 1;
const BASE_URL = location.origin; const BASE_URL = location.origin.includes('chrome-extension://')
? 'webmaker.app'
: location.origin;
const TOGGLE_VISIBILITY_API = const TOGGLE_VISIBILITY_API =
/*!window.location.origin.includes('localhost') /*!window.location.origin.includes('localhost')
? 'http://127.0.0.1:5001/web-maker-app/us-central1/toggleVisibility' ? 'http://127.0.0.1:5001/web-maker-app/us-central1/toggleVisibility'

View File

@@ -43,10 +43,10 @@ import { modes, HtmlModes, CssModes, JsModes } from '../codeModes';
import { trackEvent } from '../analytics'; import { trackEvent } from '../analytics';
import { deferred } from '../deferred'; import { deferred } from '../deferred';
import { alertsService } from '../notifications'; import { alertsService } from '../notifications';
import firebase from 'firebase/app'; import { auth } from '../firebaseInit';
import 'firebase/auth'; import { onAuthStateChanged } from 'firebase/auth';
import { Profile } from './Profile'; import { Profile } from './Profile';
import { auth } from '../auth'; import { authh } from '../auth';
import { SupportDeveloperModal } from './SupportDeveloperModal'; import { SupportDeveloperModal } from './SupportDeveloperModal';
import { KeyboardShortcutsModal } from './KeyboardShortcutsModal'; import { KeyboardShortcutsModal } from './KeyboardShortcutsModal';
import { takeScreenshot } from '../takeScreenshot'; import { takeScreenshot } from '../takeScreenshot';
@@ -84,7 +84,7 @@ if (module.hot) {
} }
const UNSAVED_WARNING_COUNT = 15; const UNSAVED_WARNING_COUNT = 15;
const version = '6.2.0'; const version = '6.3.0';
// Read forced settings as query parameters // Read forced settings as query parameters
window.forcedSettings = {}; window.forcedSettings = {};
@@ -197,7 +197,7 @@ export default class App extends Component {
window.user = savedUser; window.user = savedUser;
} }
firebase.auth().onAuthStateChanged(authUser => { onAuthStateChanged(auth, authUser => {
this.setState({ isLoginModalOpen: false }); this.setState({ isLoginModalOpen: false });
if (authUser) { if (authUser) {
log('You are -> ', authUser); log('You are -> ', authUser);
@@ -220,7 +220,6 @@ export default class App extends Component {
this.setState({ user: newUser }); this.setState({ user: newUser });
window.user = newUser; window.user = newUser;
// window.localStorage.setItem('user', authUser); // window.localStorage.setItem('user', authUser);
trackEvent('fn', 'loggedIn', window.IS_EXTENSION ? 'extension' : 'web');
if (!window.localStorage[LocalStorageKeys.ASKED_TO_IMPORT_CREATIONS]) { if (!window.localStorage[LocalStorageKeys.ASKED_TO_IMPORT_CREATIONS]) {
this.fetchItems(false, true).then(items => { this.fetchItems(false, true).then(items => {
@@ -641,7 +640,6 @@ export default class App extends Component {
} }
componentDidMount() { componentDidMount() {
console.log('itemId', this.props.itemId);
function setBodySize() { function setBodySize() {
document.body.style.height = `${window.innerHeight}px`; document.body.style.height = `${window.innerHeight}px`;
} }
@@ -1081,18 +1079,15 @@ export default class App extends Component {
alertsService.add('Setting saved'); alertsService.add('Setting saved');
}); });
if (window.user) { if (window.user) {
window.db.getDb().then(remoteDb => { db.updateUserSetting(
remoteDb window.user.uid,
.collection('users') settingName,
.doc(window.user.uid) this.state.prefs[settingName]
.update({ )
[`settings.${settingName}`]: this.state.prefs[settingName]
})
.then(arg => { .then(arg => {
log(`Setting "${settingName}" for user`, arg); log(`Setting "${settingName}" saved`, arg);
}) })
.catch(error => log(error)); .catch(error => log(error));
});
} }
trackEvent('ui', 'updatePref-' + settingName, prefs[settingName]); trackEvent('ui', 'updatePref-' + settingName, prefs[settingName]);
} }
@@ -1140,7 +1135,7 @@ export default class App extends Component {
} }
} }
trackEvent('fn', 'loggedOut'); trackEvent('fn', 'loggedOut');
auth.logout(); authh.logout();
this.setState({ isProfileModalOpen: false }); this.setState({ isProfileModalOpen: false });
this.createNewItem(); this.createNewItem();
alertsService.add('Log out successfull'); alertsService.add('Log out successfull');
@@ -1943,7 +1938,6 @@ export default class App extends Component {
this.loginBtnClickHandler(); this.loginBtnClickHandler();
}} }}
onBuyFromExtensionClick={() => { onBuyFromExtensionClick={() => {
console.log('open modal');
this.closeAllOverlays(); this.closeAllOverlays();
this.setState({ isProOnAppModalOpen: true }); this.setState({ isProOnAppModalOpen: true });
}} }}

121
src/db.js
View File

@@ -1,6 +1,17 @@
import './firebaseInit'; // import './firebaseInit';
import firebase from 'firebase/app';
import 'firebase/firestore'; import 'firebase/firestore';
import { db } from './firebaseInit';
import {
getDoc,
getDocs,
doc,
updateDoc,
setDoc,
where,
collection,
getCountFromServer,
query
} from 'firebase/firestore';
import { deferred } from './deferred'; import { deferred } from './deferred';
import { trackEvent } from './analytics'; import { trackEvent } from './analytics';
import { log } from './utils'; import { log } from './utils';
@@ -26,9 +37,6 @@ function getArrayFromQuerySnapshot(querySnapshot) {
(() => { (() => {
const FAUX_DELAY = 1; const FAUX_DELAY = 1;
var db;
var dbPromise;
var local = { var local = {
get: (obj, cb) => { get: (obj, cb) => {
const retVal = {}; const retVal = {};
@@ -64,46 +72,9 @@ function getArrayFromQuerySnapshot(querySnapshot) {
const dbLocalAlias = chrome && chrome.storage ? chrome.storage.local : local; const dbLocalAlias = chrome && chrome.storage ? chrome.storage.local : local;
const dbSyncAlias = chrome && chrome.storage ? chrome.storage.sync : local; const dbSyncAlias = chrome && chrome.storage ? chrome.storage.sync : local;
async function getDb() { function getDb() {
if (dbPromise) {
return dbPromise;
}
log('Initializing firestore'); log('Initializing firestore');
dbPromise = new Promise((resolve, reject) => { return db;
if (db) {
return resolve(db);
}
const firestoreInstance = firebase.firestore();
return firestoreInstance
.enablePersistence({ experimentalTabSynchronization: true })
.then(function () {
// Initialize Cloud Firestore through firebase
db = firebase.firestore();
// const settings = {
// timestampsInSnapshots: true
// };
// db.settings(settings);
log('firebase db ready', db);
resolve(db);
})
.catch(function (err) {
reject(err.code);
if (err.code === 'failed-precondition') {
// Multiple tabs open, persistence can only be enabled
// in one tab at a a time.
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."
);
trackEvent('fn', 'multiTabError');
} else if (err.code === 'unimplemented') {
// The current browser does not support all of the
// features required to enable persistence
// ...
}
});
});
return dbPromise;
} }
async function getUserLastSeenVersion() { async function getUserLastSeenVersion() {
@@ -134,40 +105,27 @@ function getArrayFromQuerySnapshot(querySnapshot) {
function () {} function () {}
); );
if (window.user) { if (window.user) {
const remoteDb = await getDb(); updateDoc(doc(db, `users/${window.user.uid}`), {
remoteDb.doc(`users/${window.user.uid}`).update({
lastSeenVersion: version lastSeenVersion: version
}); });
} }
} }
async function getUser(userId) { async function getUser(userId) {
const remoteDb = await getDb(); return getDoc(doc(db, `users/${userId}`)).then(doc => {
return remoteDb if (!doc.exists()) {
.doc(`users/${userId}`) // return setDoc(doc(db, `users/${userId}`), {}, { merge: true });
.get()
.then(doc => {
if (!doc.exists) {
// return remoteDb.doc(`users/${userId}`).set(
// {},
// {
// merge: true
// }
// );
return {}; return {};
} }
const user = doc.data();
const user = doc.data();
// Object.assign(window.user, user);
return user; return user;
}); });
} }
async function fetchItem(itemId) { async function fetchItem(itemId) {
const remoteDb = await getDb(); getDoc(doc(db, `items/${itemId}`)).then(doc => {
return remoteDb
.doc(`items/${itemId}`)
.get()
.then(doc => {
if (!doc.exists) return {}; if (!doc.exists) return {};
const data = doc.data(); const data = doc.data();
return data; return data;
@@ -188,24 +146,28 @@ function getArrayFromQuerySnapshot(querySnapshot) {
} }
async function getPublicItemCount(userId) { async function getPublicItemCount(userId) {
const remoteDb = await getDb(); const q = query(
return remoteDb collection(db, 'items'),
.collection('items') where('createdBy', '==', userId),
.where('createdBy', '==', userId) where('isPublic', '==', true)
.where('isPublic', '==', true) );
.get() const snapshot = await getCountFromServer(q);
.then(snapShot => { return snapshot.data().count;
return snapShot.size;
});
} }
async function getUserSubscriptionEvents(userId) { async function getUserSubscriptionEvents(userId) {
const remoteDb = await getDb(); const q = query(
return remoteDb collection(db, 'subscriptions'),
.collection('subscriptions') where('userId', '==', userId)
.where('userId', '==', userId) );
.get()
.then(getArrayFromQuerySnapshot); return getDocs(q).then(getArrayFromQuerySnapshot);
}
async function updateUserSetting(userId, settingName, settingValue) {
return updateDoc(doc(db, `users/${userId}`), {
[`settings.${settingName}`]: settingValue
});
} }
window.db = { window.db = {
@@ -217,6 +179,7 @@ function getArrayFromQuerySnapshot(querySnapshot) {
fetchItem, fetchItem,
getPublicItemCount, getPublicItemCount,
getUserSubscriptionEvents, getUserSubscriptionEvents,
updateUserSetting,
local: dbLocalAlias, local: dbLocalAlias,
sync: dbSyncAlias sync: dbSyncAlias
}; };

View File

@@ -4,9 +4,7 @@ window.addEventListener('message', e => {
const frame = document.querySelector('iframe'); const frame = document.querySelector('iframe');
frame.src = frame.src; frame.src = frame.src;
setTimeout(() => { setTimeout(() => {
frame.contentDocument.open(); frame.contentWindow.postMessage(e.data, '*');
frame.contentDocument.write(e.data.contents);
frame.contentDocument.close();
}, 10); }, 10);
} }
if (e.data && e.data.url && e.data.url.match(/index\.html/)) { if (e.data && e.data.url && e.data.url.match(/index\.html/)) {

View File

@@ -1,16 +1,16 @@
function openApp() { function openApp() {
chrome.tabs.create({ chrome.tabs.create({
url: chrome.extension.getURL('index.html'), url: chrome.runtime.getURL('index.html'),
selected: true selected: true
}); });
} }
chrome.browserAction.onClicked.addListener(function() { chrome.action.onClicked.addListener(function () {
openApp(); openApp();
}); });
// Listen for tabs getting created. // Listen for tabs getting created.
chrome.tabs.onCreated.addListener(function(tab) { chrome.tabs.onCreated.addListener(function (tab) {
// If a new tab is opened (without any URL), check user's // If a new tab is opened (without any URL), check user's
// replace Tab setting and act accordingly. Default is false. // replace Tab setting and act accordingly. Default is false.
if (tab.url === 'chrome://newtab/') { if (tab.url === 'chrome://newtab/') {
@@ -18,12 +18,12 @@ chrome.tabs.onCreated.addListener(function(tab) {
{ {
replaceNewTab: false replaceNewTab: false
}, },
function(items) { function (items) {
if (items.replaceNewTab) { if (items.replaceNewTab) {
chrome.tabs.update( chrome.tabs.update(
tab.id, tab.id,
{ {
url: chrome.extension.getURL('index.html') url: chrome.runtime.getURL('index.html')
}, },
function callback() { function callback() {
console.log('ho gaya'); console.log('ho gaya');
@@ -45,3 +45,78 @@ chrome.runtime.onInstalled.addListener(function callback(details) {
} }
} }
}); });
const OFFSCREEN_DOCUMENT_PATH = '/offscreen.html';
// Chrome only allows for a single offscreenDocument. This is a helper function
// that returns a boolean indicating if a document is already active.
async function hasDocument() {
// Check all windows controlled by the service worker to see if one
// of them is the offscreen document with the given path
const matchedClients = await clients.matchAll();
return matchedClients.some(
c => c.url === chrome.runtime.getURL(OFFSCREEN_DOCUMENT_PATH)
);
}
async function setupOffscreenDocument(path) {
// If we do not have a document, we are already setup and can skip
if (await chrome.offscreen.hasDocument()) return;
await chrome.offscreen.createDocument({
url: path,
reasons: [chrome.offscreen.Reason.DOM_SCRAPING],
justification: 'authentication'
});
}
async function closeOffscreenDocument() {
if (!(await hasDocument())) {
return;
}
await chrome.offscreen.closeDocument();
}
function getAuth(providerName) {
return new Promise(async (resolve, reject) => {
// sending to offscreen document
const auth = await chrome.runtime.sendMessage({
type: 'firebase-auth',
target: 'offscreen',
providerName
});
auth?.name !== 'FirebaseError' ? resolve(auth) : reject(auth);
});
}
async function firebaseAuth(providerName) {
await setupOffscreenDocument(OFFSCREEN_DOCUMENT_PATH);
const auth = await getAuth(providerName)
.then(auth => {
console.log('User Authenticated', auth);
return auth;
})
.catch(err => {
if (err.code === 'auth/operation-not-allowed') {
console.error(
'You must enable an OAuth provider in the Firebase' +
' console in order to use signInWithPopup. This sample' +
' uses Google by default.'
);
} else {
console.error(err);
return err;
}
})
.finally(closeOffscreenDocument);
return auth;
}
chrome.runtime.onMessage.addListener(function (message, sender, sendResponse) {
// received from the app
if (message.type === 'firebase-auth') {
firebaseAuth(message.providerName).then(sendResponse);
return true;
}
});

View File

@@ -1,5 +1,12 @@
import firebase from 'firebase/app'; import { initializeApp } from 'firebase/app';
// import 'firebase/firestore'; import { getAuth } from 'firebase/auth';
import {
initializeFirestore,
persistentLocalCache,
persistentMultipleTabManager
} from 'firebase/firestore';
import { getStorage } from 'firebase/storage';
const config = { const config = {
apiKey: 'AIzaSyBl8Dz7ZOE7aP75mipYl2zKdLSRzBU2fFc', apiKey: 'AIzaSyBl8Dz7ZOE7aP75mipYl2zKdLSRzBU2fFc',
authDomain: 'web-maker-app.firebaseapp.com', authDomain: 'web-maker-app.firebaseapp.com',
@@ -8,4 +15,14 @@ const config = {
storageBucket: 'web-maker-app.appspot.com', storageBucket: 'web-maker-app.appspot.com',
messagingSenderId: '560473480645' messagingSenderId: '560473480645'
}; };
firebase.initializeApp(config);
const app = initializeApp(config);
export { app };
export const auth = getAuth(app);
export const storage = getStorage(app);
export const db = initializeFirestore(app, {
localCache: persistentLocalCache({
tabManager: persistentMultipleTabManager()
})
});

View File

@@ -1,16 +1,27 @@
import { deferred } from './deferred'; import { deferred } from './deferred';
import { log } from './utils'; import { log } from './utils';
import firebase from 'firebase/app'; import {
collection,
deleteField,
where,
getDoc,
query,
setDoc,
writeBatch,
doc,
deleteDoc,
updateDoc,
getDocs
} from 'firebase/firestore';
export const itemService = { export const itemService = {
async getItem(id) { async getItem(id) {
var remoteDb = await window.db.getDb(); var remoteDb = window.db.getDb();
return remoteDb return getDoc(doc(remoteDb, `items/${id}`))
.doc(`items/${id}`)
.get()
.then(doc => { .then(doc => {
return doc.data(); return doc.data();
}); })
.catch(error => log(error));
}, },
/** /**
@@ -41,23 +52,23 @@ export const itemService = {
const items = []; const items = [];
if (window.user && !shouldFetchLocally) { if (window.user && !shouldFetchLocally) {
var remoteDb = await window.db.getDb(); var remoteDb = window.db.getDb();
remoteDb
.collection('items') const q = query(
.where('createdBy', '==', window.user.uid) collection(remoteDb, 'items'),
.onSnapshot( where('createdBy', '==', window.user.uid)
function (querySnapshot) { );
querySnapshot.forEach(function (doc) { getDocs(q)
.then(querySnapshot => {
querySnapshot.forEach(doc => {
items.push(doc.data()); items.push(doc.data());
}); });
log('Items fetched in ', Date.now() - t, 'ms'); log(`${items.length} items fetched in `, Date.now() - t, 'ms');
d.resolve(items); d.resolve(items);
}, })
function () { .catch(() => {
d.resolve([]); d.resolve([]);
} });
);
} else { } else {
let itemIds = await this.getUserItemIds(shouldFetchLocally); let itemIds = await this.getUserItemIds(shouldFetchLocally);
itemIds = Object.getOwnPropertyNames(itemIds || {}); itemIds = Object.getOwnPropertyNames(itemIds || {});
@@ -83,8 +94,8 @@ export const itemService = {
}, },
async setUser() { async setUser() {
const remoteDb = await window.db.getDb(); const remoteDb = window.db.getDb();
return remoteDb.doc(`users/${window.user.uid}`).set({ return setDoc(doc(remoteDb, `users/${window.user.uid}`), {
items: {} items: {}
}); });
}, },
@@ -112,16 +123,13 @@ export const itemService = {
} }
// LOGGED IN // LOGGED IN
var remoteDb = await window.db.getDb(); var remoteDb = window.db.getDb();
item.createdBy = window.user.uid; item.createdBy = window.user.uid;
remoteDb setDoc(doc(remoteDb, `items/${id}`), item, {
.collection('items')
.doc(id)
.set(item, {
merge: true merge: true
}) })
.then(arg => { .then(() => {
log('Document written', arg); log(`Document written`);
d.resolve(); d.resolve();
}) })
.catch(d.reject); .catch(d.reject);
@@ -161,14 +169,15 @@ export const itemService = {
); );
} else { } else {
window.db.getDb().then(remoteDb => { window.db.getDb().then(remoteDb => {
const batch = remoteDb.batch(); const batch = writeBatch(remoteDb);
/* eslint-disable guard-for-in */ /* eslint-disable guard-for-in */
for (var id in items) { for (var id in items) {
items[id].createdBy = window.user.uid; items[id].createdBy = window.user.uid;
batch.set(remoteDb.doc(`items/${id}`), items[id]); batch.set(doc(remoteDb, `items/${id}`), items[id]);
batch.update(remoteDb.doc(`users/${window.user.uid}`), { batch.update(doc(remoteDb, `users/${window.user.uid}`), {
[`items.${id}`]: true [`items.${id}`]: true
}); });
// Set these items on our cached user object too // Set these items on our cached user object too
window.user.items = window.user.items || {}; window.user.items = window.user.items || {};
window.user.items[id] = true; window.user.items[id] = true;
@@ -187,14 +196,11 @@ export const itemService = {
window.db.local.remove(id, d.resolve); window.db.local.remove(id, d.resolve);
return d.promise; return d.promise;
} }
const remoteDb = await window.db.getDb(); const remoteDb = window.db.getDb();
log(`Starting to save item ${id}`); log(`Starting to save item ${id}`);
return remoteDb return deleteDoc(doc(remoteDb, `items/${id}`))
.collection('items') .then(() => {
.doc(id) log('Document removed');
.delete()
.then(arg => {
log('Document removed', arg);
}) })
.catch(error => log(error)); .catch(error => log(error));
}, },
@@ -214,11 +220,8 @@ export const itemService = {
} }
); );
} }
const remoteDb = await window.db.getDb(); const remoteDb = window.db.getDb();
return remoteDb return updateDoc(doc(remoteDb, `users/${window.user.uid}`), {
.collection('users')
.doc(window.user.uid)
.update({
[`items.${itemId}`]: true [`items.${itemId}`]: true
}) })
.then(arg => { .then(arg => {
@@ -244,16 +247,13 @@ export const itemService = {
} }
); );
} }
const remoteDb = await window.db.getDb(); const remoteDb = window.db.getDb();
return remoteDb return updateDoc(doc(remoteDb, `users/${window.user.uid}`), {
.collection('users') [`items.${itemId}`]: deleteField()
.doc(window.user.uid)
.update({
[`items.${itemId}`]: firebase.firestore.FieldValue.delete()
}) })
.then(arg => { .then(arg => {
delete window.user.items[itemId];
log(`Item ${itemId} unset for user`, arg); log(`Item ${itemId} unset for user`, arg);
delete window.user.items[itemId];
}) })
.catch(error => log(error)); .catch(error => log(error));
}, },

View File

@@ -1,4 +1,4 @@
let mainWindow = window.parent; let mainWindow = window.parent === window ? window.opener : window.parent;
function sanitizeDomNode(node) { function sanitizeDomNode(node) {
const fakeNode = { const fakeNode = {

View File

@@ -1,24 +1,33 @@
{ {
"name": "Web Maker", "name": "Web Maker",
"version": "6.0.0", "version": "6.3.0",
"manifest_version": 2, "manifest_version": 3,
"description": "Blazing fast & offline playground for your web experiments", "description": "Blazing fast & offline playground for your web experiments",
"homepage_url": "https://webmaker.app", "homepage_url": "https://webmaker.app",
"permissions": ["storage", "tabs", "<all_urls>"], "permissions": ["storage", "tabs", "offscreen"],
"optional_permissions": ["downloads"], "optional_permissions": ["downloads"],
"content_security_policy": "script-src 'self' filesystem: http://localhost:* https://localhost:* https://apis.google.com 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://*.stripe.com/ https://builds.framerjs.com/ https://rawgit.com https://wzrd.in https://www.gstatic.com https://semantic-ui.com https://www.google-analytics.com https://cdn.tailwindcss.com https://www.googletagmanager.com 'unsafe-eval'; object-src 'self'", "host_permissions": ["<all_urls>"],
"content_security_policy": {
"extension_pages": "script-src 'self'; object-src 'self'",
"sandbox": "sandbox allow-scripts allow-forms allow-popups allow-modals; script-src 'self' filesystem: http://localhost:* https://localhost:* https://apis.google.com 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://*.stripe.com/ https://builds.framerjs.com/ https://rawgit.com https://wzrd.in https://www.gstatic.com https://semantic-ui.com https://www.google-analytics.com https://cdn.tailwindcss.com https://www.googletagmanager.com https://firebasestorage.googleapis.com https://assets.webmaker.app 'unsafe-inline' 'unsafe-eval'; child-src 'self';"
},
"options_ui": { "options_ui": {
"page": "options.html", "page": "options.html",
"chrome_style": true "open_in_tab": false
}, },
"browser_action": { "action": {
"default_title": "Start Web Maker", "default_title": "Start Web Maker",
"default_icon": "icon-16.png" "default_icon": "icon-16.png"
}, },
"background": { "sandbox": {
"scripts": ["eventPage.js"], "pages": ["indexpm.html"]
"persistent": false
}, },
"background": {
"service_worker": "eventPage.js",
"type": "module"
},
"key": "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAp4s9naKH2y92qItJCcj3deRGjjqhDfgUR2WDe34IEYEB4PGtmx/9IO4PaO49gMr82DBRuwxCg/vr9SyiVlG1/j0TApJlkaHVsRZE4EME2rbL1vzIQRE8gNB+b5L6rPl4GPX/eFqIuUe/UgQPZAadLBVxdwBdOv5ou8OY0jrLx/h0wcLVvk9d8/ELpk28Hb2LArCBd+vIngHK55Db1GMEGAyNUVzFCTg/MZ7w5fKLpA94mF2X0/Bv9sCjj7vDir24Mp5Z/Y3obTHvpzddppAE6ZEQ3fmpRkOJ3MbuaKYm9hNHs4az6XLW6Sdlv2YcIcCfvsev/WEKUZeD8KIRtRyyPQIDAQAB",
"icons": { "icons": {
"16": "icon-16.png", "16": "icon-16.png",
"48": "icon-48.png" "48": "icon-48.png"

View File

@@ -35,7 +35,7 @@
<body> <body>
<h3> <h3>
Settings Settings
<span style="opacity: 0.6; font-size: 0.7em"> v6.0.0</span> <span style="opacity: 0.6; font-size: 0.7em"> v6.3.0</span>
</h3> </h3>
<form name="optionsForm"> <form name="optionsForm">
<label> <label>

View File

@@ -12,7 +12,7 @@
<body> <body>
<iframe <iframe
src="about://blank" src="indexpm.html"
frameborder="0" frameborder="0"
id="demo-frame" id="demo-frame"
allowfullscreen allowfullscreen

View File

@@ -15,11 +15,6 @@ export default [
title: 'Preact', title: 'Preact',
img: 'assets/preact-logo.svg' img: 'assets/preact-logo.svg'
}, },
{
id: 'tailwind2',
title: 'Tailwind CSS 2',
img: 'assets/tailwind-logo.svg'
},
{ {
id: 'tailwind', id: 'tailwind',
title: 'Tailwind CSS 3', title: 'Tailwind CSS 3',

View File

@@ -406,26 +406,26 @@ export function getCompleteHtml(html, css, js, item, isForExport) {
'</head>\n' + '</head>\n' +
'<body>\n' + '<body>\n' +
html + html +
'\n' +
externalJs +
'\n'; '\n';
if (!isForExport) { if (!isForExport) {
contents += contents +=
'<script src="' + '<script src="' +
(chrome.extension (chrome.extension
? chrome.extension.getURL('lib/screenlog.js') ? chrome.runtime.getURL('lib/screenlog.js')
: `${location.origin}${ : `${location.origin}${
window.DEBUG ? '' : BASE_PATH window.DEBUG ? '' : BASE_PATH
}/lib/screenlog.js`) + }/lib/screenlog.js`) +
'"></script>'; '"></script>';
} }
contents += '\n' + externalJs;
if (item.jsMode === JsModes.ES6) { if (item.jsMode === JsModes.ES6) {
contents += contents +=
'<script src="' + '<script src="' +
(chrome.extension (chrome.extension
? chrome.extension.getURL('lib/transpilers/babel-polyfill.min.js') ? chrome.runtime.getURL('lib/transpilers/babel-polyfill.min.js')
: `${location.origin}${BASE_PATH}/lib/transpilers/babel-polyfill.min.js`) + : `${location.origin}${BASE_PATH}/lib/transpilers/babel-polyfill.min.js`) +
'"></script>'; '"></script>';
} }
@@ -532,7 +532,7 @@ export function prettify({ file, content, type }) {
} }
const worker = new Worker( const worker = new Worker(
chrome.extension chrome.extension
? chrome.extension.getURL('lib/prettier-worker.js') ? chrome.runtime.getURL('lib/prettier-worker.js')
: `${BASE_PATH !== '/' ? BASE_PATH : ''}/lib/prettier-worker.js` : `${BASE_PATH !== '/' ? BASE_PATH : ''}/lib/prettier-worker.js`
); );
worker.postMessage({ content, type }); worker.postMessage({ content, type });