1
0
mirror of https://github.com/chinchang/web-maker.git synced 2025-07-27 00:30:09 +02:00

Merge remote-tracking branch 'origin/master' into manifestv3

This commit is contained in:
Kushagra Gour
2024-05-06 17:54:20 +05:30
83 changed files with 10240 additions and 16784 deletions

View File

@@ -4,7 +4,7 @@
**Web-Maker** is an offline playground for your web experiments. Something like CodePen or JSFiddle, but much more faster and works offline because it runs completely on your system. **Web-Maker** is an offline playground for your web experiments. Something like CodePen or JSFiddle, but much more faster and works offline because it runs completely on your system.
## [Open Web App](https://webmaker.app/app/) (Recommended: More features. More fun!) ## [Open Web App](https://webmaker.app/create/) (Recommended: More features. More fun!)
or or

View File

@@ -8,6 +8,6 @@
- package.json - package.json
- manifest.json - manifest.json
- commit and tag (`git tag {version}`) - commit and tag (`git tag {version}`)
- Run `gulp buildExtension` - Run `gulp buildExtension`. This will generate a zip - `extension-{version}.zip`
- Test out extension-{version}.zip - Test out extension-{version}.zip
- If everything is good, push to master - If everything is good, push to master

View File

@@ -13,6 +13,7 @@ const merge = require('merge-stream');
// const zip = require('gulp-zip'); // const zip = require('gulp-zip');
var packageJson = JSON.parse(fs.readFileSync('./package.json')); var packageJson = JSON.parse(fs.readFileSync('./package.json'));
const connect = require('gulp-connect'); const connect = require('gulp-connect');
const APP_FOLDER = 'create';
function minifyJs(fileName) { function minifyJs(fileName) {
const content = fs.readFileSync(fileName, 'utf8'); const content = fs.readFileSync(fileName, 'utf8');
@@ -44,21 +45,27 @@ gulp.task('copyFiles', function () {
return merge( return merge(
gulp gulp
.src('src/lib/codemirror/theme/*') .src('src/lib/codemirror/theme/*')
.pipe(gulp.dest('app/lib/codemirror/theme')), .pipe(gulp.dest(`${APP_FOLDER}/lib/codemirror/theme`)),
gulp gulp
.src('src/lib/codemirror/mode/**/*') .src('src/lib/codemirror/mode/**/*')
.pipe(gulp.dest('app/lib/codemirror/mode')), .pipe(gulp.dest(`${APP_FOLDER}/lib/codemirror/mode`)),
gulp.src('src/lib/transpilers/*').pipe(gulp.dest('app/lib/transpilers')), gulp
gulp.src('src/lib/prettier-worker.js').pipe(gulp.dest('app/lib/')), .src('src/lib/transpilers/*')
gulp.src('src/lib/prettier/*').pipe(gulp.dest('app/lib/prettier')), .pipe(gulp.dest(`${APP_FOLDER}/lib/transpilers`)),
gulp
.src('src/lib/prettier-worker.js')
.pipe(gulp.dest(`${APP_FOLDER}/lib/`)),
gulp
.src('src/lib/prettier/*')
.pipe(gulp.dest(`${APP_FOLDER}/lib/prettier`)),
gulp gulp
.src(['!src/lib/monaco/monaco.bundle.js', 'src/lib/monaco/**/*']) .src(['!src/lib/monaco/monaco.bundle.js', 'src/lib/monaco/**/*'])
.pipe(gulp.dest('app/lib/monaco')), .pipe(gulp.dest(`${APP_FOLDER}/lib/monaco`)),
gulp.src('src/lib/screenlog.js').pipe(gulp.dest('app/lib')), gulp.src('src/lib/screenlog.js').pipe(gulp.dest(`${APP_FOLDER}/lib`)),
gulp.src('icons/*').pipe(gulp.dest('app/icons')), gulp.src('icons/*').pipe(gulp.dest(`${APP_FOLDER}/icons`)),
gulp.src('src/assets/*').pipe(gulp.dest('app/assets')), gulp.src('src/assets/*').pipe(gulp.dest(`${APP_FOLDER}/assets`)),
gulp.src('src/templates/*').pipe(gulp.dest('app/templates')), gulp.src('src/templates/*').pipe(gulp.dest(`${APP_FOLDER}/templates`)),
gulp.src('preview/*').pipe(gulp.dest('app/preview')), gulp.src('preview/*').pipe(gulp.dest(`${APP_FOLDER}/preview`)),
gulp gulp
.src([ .src([
'src/preview.html', 'src/preview.html',
@@ -68,24 +75,24 @@ gulp.task('copyFiles', function () {
'src/icon-128.png', 'src/icon-128.png',
'src/manifest.json' 'src/manifest.json'
]) ])
.pipe(gulp.dest('app')), .pipe(gulp.dest(APP_FOLDER)),
gulp.src('build/*').pipe(gulp.dest('app')), gulp.src('build/*').pipe(gulp.dest(APP_FOLDER)),
// Following CSS are copied to build/ folder where they'll be referenced by // Following CSS are copied to build/ folder where they'll be referenced by
// useRef plugin to concat into one. // useRef plugin to concat into one.
gulp gulp
.src('src/lib/codemirror/lib/codemirror.css') .src('src/lib/codemirror/lib/codemirror.css')
.pipe(gulp.dest('build/lib/codemirror/lib')), .pipe(gulp.dest(`build/lib/codemirror/lib`)),
gulp gulp
.src('src/lib/codemirror/addon/hint/show-hint.css') .src('src/lib/codemirror/addon/hint/show-hint.css')
.pipe(gulp.dest('build/lib/codemirror/addon/hint')), .pipe(gulp.dest(`build/lib/codemirror/addon/hint`)),
gulp gulp
.src('src/lib/codemirror/addon/fold/foldgutter.css') .src('src/lib/codemirror/addon/fold/foldgutter.css')
.pipe(gulp.dest('build/lib/codemirror/addon/fold')), .pipe(gulp.dest(`build/lib/codemirror/addon/fold`)),
gulp gulp
.src('src/lib/codemirror/addon/dialog/dialog.css') .src('src/lib/codemirror/addon/dialog/dialog.css')
.pipe(gulp.dest('build/lib/codemirror/addon/dialog')), .pipe(gulp.dest(`build/lib/codemirror/addon/dialog`)),
gulp.src('src/lib/hint.min.css').pipe(gulp.dest('build/lib')), gulp.src('src/lib/hint.min.css').pipe(gulp.dest('build/lib')),
gulp.src('src/lib/inlet.css').pipe(gulp.dest('build/lib')), gulp.src('src/lib/inlet.css').pipe(gulp.dest('build/lib')),
// gulp.src('src/style.css').pipe(gulp.dest('build')), // gulp.src('src/style.css').pipe(gulp.dest('build')),
@@ -97,34 +104,37 @@ gulp.task('copyFiles', function () {
'src/Inconsolata.ttf', 'src/Inconsolata.ttf',
'src/Monoid.ttf' 'src/Monoid.ttf'
]) ])
.pipe(gulp.dest('app')) .pipe(gulp.dest(APP_FOLDER))
); );
}); });
gulp.task('useRef', function () { gulp.task('useRef', function () {
return gulp.src('build/index.html').pipe(useref()).pipe(gulp.dest('app')); return gulp
.src('build/index.html')
.pipe(useref())
.pipe(gulp.dest(APP_FOLDER));
}); });
gulp.task('concatSwRegistration', function () { gulp.task('concatSwRegistration', function () {
const bundleFile = fs const bundleFile = fs
.readdirSync('app') .readdirSync(APP_FOLDER)
.filter(allFilesPaths => allFilesPaths.match(/bundle.*\.js$/) !== null)[0]; .filter(allFilesPaths => allFilesPaths.match(/bundle.*\.js$/) !== null)[0];
console.log('matched', bundleFile); console.log('matched', bundleFile);
return gulp return gulp
.src(['src/service-worker-registration.js', `app/${bundleFile}`]) .src(['src/service-worker-registration.js', `${APP_FOLDER}/${bundleFile}`])
.pipe(concat(bundleFile)) .pipe(concat(bundleFile))
.pipe(gulp.dest('app')); .pipe(gulp.dest(APP_FOLDER));
}); });
gulp.task('minify', function () { gulp.task('minify', function () {
// minifyJs('app/script.js'); // minifyJs('app/script.js');
// minifyJs('app/vendor.js'); // minifyJs('app/vendor.js');
minifyJs('app/lib/screenlog.js'); minifyJs(`${APP_FOLDER}/lib/screenlog.js`);
return gulp return gulp
.src('app/*.css') .src(`${APP_FOLDER}/*.css`)
.pipe( .pipe(
cleanCSS( cleanCSS(
{ {
@@ -137,7 +147,7 @@ gulp.task('minify', function () {
} }
) )
) )
.pipe(gulp.dest('app')); .pipe(gulp.dest(APP_FOLDER));
}); });
gulp.task('fixIndex', function (cb) { gulp.task('fixIndex', function (cb) {
@@ -160,7 +170,7 @@ gulp.task('fixIndex', function (cb) {
gulp.task('generate-service-worker', function (callback) { gulp.task('generate-service-worker', function (callback) {
var swPrecache = require('sw-precache'); var swPrecache = require('sw-precache');
var rootDir = 'app'; var rootDir = APP_FOLDER;
swPrecache.write( swPrecache.write(
`${rootDir}/service-worker.js`, `${rootDir}/service-worker.js`,
@@ -179,7 +189,7 @@ gulp.task('generate-service-worker', function (callback) {
gulp.task('packageExtension', function () { gulp.task('packageExtension', function () {
child_process.execSync('rm -rf extension'); child_process.execSync('rm -rf extension');
child_process.execSync('cp -R app extension'); child_process.execSync(`cp -R ${APP_FOLDER} extension`);
child_process.execSync('cp src/manifest.json extension'); child_process.execSync('cp src/manifest.json extension');
child_process.execSync('cp src/options.js extension'); child_process.execSync('cp src/options.js extension');
child_process.execSync('cp src/options.html extension'); child_process.execSync('cp src/options.html extension');
@@ -211,12 +221,12 @@ gulp.task('buildWebsite', function () {
gulp.task('buildDistFolder', function (cb) { gulp.task('buildDistFolder', function (cb) {
child_process.execSync('rm -rf dist'); child_process.execSync('rm -rf dist');
child_process.execSync('mv packages/website/_site dist'); child_process.execSync('mv packages/website/_site dist');
child_process.execSync('mv app dist/'); child_process.execSync(`mv ${APP_FOLDER} dist/`);
cb(); cb();
}); });
gulp.task('cleanup', function () { gulp.task('cleanup', function () {
return child_process.exec('rm -rf build'); return child_process.exec('rm -rf build create');
}); });
gulp.task('start-preview-server', function () { gulp.task('start-preview-server', function () {
@@ -227,6 +237,7 @@ gulp.task('start-preview-server', function () {
}); });
}); });
// TODO: fix tasks. eg. buildWebsite isn't needed anymore
exports.release = series( exports.release = series(
parallel('runWebpack', 'buildWebsite'), parallel('runWebpack', 'buildWebsite'),
'copyFiles', 'copyFiles',
@@ -274,7 +285,8 @@ const buildExtension = series(
'copyFiles', 'copyFiles',
'fixIndex', 'fixIndex',
'useRef', 'useRef',
'packageExtension' 'packageExtension',
'cleanup'
); );
function runWatcher(cb) { function runWatcher(cb) {

View File

@@ -18,10 +18,19 @@ ID = "webmaker"
# been specified, include it in the publish directory path. # been specified, include it in the publish directory path.
publish = "dist" publish = "dist"
# The following redirect is intended for use with most SPAs that handle
# routing internally.
[[redirects]] [[redirects]]
from = "https://preview.webmaker.app/*" from = "https://preview.webmaker.app/*"
to = "/app/preview/:splat" to = "/create/preview/:splat"
status = 200 status = 200
force = true force = true
[[redirects]]
from = "https://preview.v6--webmaker.netlify.app/*"
to = "/create/preview/:splat"
status = 200
force = true
[[redirects]]
from = "/create/*"
to = "/create/index.html"
status = 200

20859
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,11 +1,11 @@
{ {
"name": "web-maker", "name": "web-maker",
"version": "5.2.0", "version": "6.1.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\"",
"build": "preact build --template src/index.html --prerender false --no-inline-css --sw false --esm false", "build": "preact build --template src/index.ejs --prerender false --no-inline-css --sw false",
"dev": "preact watch --template src/index.html", "dev": "preact watch --template src/index.ejs",
"serve-website": "cd packages/website; npm start", "serve-website": "cd packages/website; npm start",
"build-website": "cd packages/website; npm run build", "build-website": "cd packages/website; npm run build",
"lint": "eslint src", "lint": "eslint src",
@@ -65,24 +65,25 @@
"markdown-it": "^8.4.2", "markdown-it": "^8.4.2",
"markdown-it-anchor": "^5.0.2", "markdown-it-anchor": "^5.0.2",
"merge-stream": "^1.0.1", "merge-stream": "^1.0.1",
"preact-cli": "^3.0.0", "preact-cli": "^4.0.0-next.6",
"sw-precache": "^5.2.0" "sw-precache": "^5.2.0"
}, },
"dependencies": { "dependencies": {
"@emmetio/codemirror-plugin": "^0.5.4", "@emmetio/codemirror-plugin": "^0.5.4",
"@lingui/react": "^2.8.3", "@lingui/react": "^2.8.3",
"canvas-confetti": "^1.9.2",
"code-blast-codemirror": "chinchang/code-blast-codemirror#web-maker", "code-blast-codemirror": "chinchang/code-blast-codemirror#web-maker",
"codemirror": "^5.37.0", "codemirror": "^5.65.16",
"copy-webpack-plugin": "^4.5.1", "copy-webpack-plugin": "^4.5.1",
"esprima": "^4.0.0", "esprima": "^4.0.0",
"firebase": "^10.8.0", "firebase": "^10.8.0",
"jszip": "^3.1.5", "jszip": "^3.1.5",
"preact": "^10.5.13", "preact": "^10.17.0",
"preact-portal": "^1.1.3", "preact-portal": "^1.1.3",
"preact-render-to-string": "^5.1.4", "preact-render-to-string": "^5.1.4",
"preact-router": "^3.2.1", "preact-router": "^3.2.1",
"prettier": "^2.2.1", "prettier": "^3.0.2",
"react-inspector": "^2.3.0", "react-inspector": "^6.0.2",
"split.js": "^1.5.11" "split.js": "^1.5.11"
}, },
"engines": { "engines": {

View File

@@ -8,7 +8,10 @@ excludeFromSitemap: true
<div class="ta-c"> <div class="ta-c">
<figure> <figure>
<img src="/images/404.png" style="width: 65vh" /> <img src="/images/404.png" style="width: 65vh" />
<figcaption>Image by https://icons8.com</figcaption> <figcaption style="opacity: 0.5">Image by https://icons8.com</figcaption>
</figure> </figure>
<p>Uh oh, the page you wanted to see isn't here. How about <a href="/">going to the homepage</a>?</p> <p style="margin-top: 2rem">
Uh oh, the page you wanted to see isn't here. How about
<a href="/">going to the homepage</a>?
</p>
</div> </div>

View File

@@ -1,4 +1,4 @@
<!doctype html> <!DOCTYPE html>
<html> <html>
<head> <head>
<meta charset="utf-8" /> <meta charset="utf-8" />
@@ -114,7 +114,7 @@
padding: 1rem; padding: 1rem;
max-width: var(--layout-max-width); max-width: var(--layout-max-width);
margin: 0 auto; margin: 0 auto;
min-height: 55vh; min-height: calc(100dvh - 4rem);
} }
@media screen and (max-width: 700px) { @media screen and (max-width: 700px) {
@@ -514,7 +514,7 @@
A blazing fast & offline frontend playground in your browser A blazing fast & offline frontend playground in your browser
</h2> </h2>
<div style="margin-top: 30px" id="cta" class="mb-2"> <div style="margin-top: 30px" id="cta" class="mb-2">
<a class="btn download-btn web-app-btn" href="/app/"> <a class="btn download-btn web-app-btn" href="/create/">
<span>Open Web App</span> <span>Open Web App</span>
</a> </a>
<p style="margin-top: 3px"> <p style="margin-top: 3px">

View File

@@ -8,7 +8,7 @@ Web Maker is available as a Web app and a Chrome extension. Web app is recommend
**Web app** **Web app**
The web app requires no installation. Just [goto the app url](/app/). Once you open the app, it is available for offline use in future. The web app requires no installation. Just [goto the app url](/create/). Once you open the app, it is available for offline use in future.
**Chrome extension** **Chrome extension**
@@ -20,7 +20,7 @@ Here is the link to the Chrome extension 👉 [Chrome Web Store](https://chrome.
**Web app** **Web app**
As mentioned, the Web app can be simply opened by visiting [https://webmaker.app/app/](/app/). As mentioned, the Web app can be simply opened by visiting [https://webmaker.app/create/](/create/).
**Chrome extension** **Chrome extension**

View File

@@ -9,13 +9,13 @@ At rare times, you might find yourself in a situation where you are not able to
Say, you want to turn off `autoPreview` (which runs your code automatically on fresh launch and then on subsequent changes). You can do so by opening the following URL: Say, you want to turn off `autoPreview` (which runs your code automatically on fresh launch and then on subsequent changes). You can do so by opening the following URL:
``` ```
https://webmaker.app/app?settings=autoPreview:false https://webmaker.app/create?settings=autoPreview:false
``` ```
Say, you want to turn off monaco editor also: Say, you want to turn off monaco editor also:
``` ```
https://webmaker.app/app?settings=autoPreview:false,isMonacoEditorOn:false https://webmaker.app/create?settings=autoPreview:false,isMonacoEditorOn:false
``` ```
**Note**: Currently overrides work only for `autoPreview` and `isMonacoEditorOn` options. More coming soon. **Note**: Currently overrides work only for `autoPreview` and `isMonacoEditorOn` options. More coming soon.

View File

@@ -1,5 +1,3 @@
// var CommonsChunkPlugin = require('webpack/lib/optimize/CommonsChunkPlugin');
/** /**
* Function that mutates original webpack config. * Function that mutates original webpack config.
* Supports asynchronous changes when promise is returned. * Supports asynchronous changes when promise is returned.
@@ -21,9 +19,9 @@ export default function (config, env, helpers) {
htmlWebpackPlugin.plugin.options.favicon = false; htmlWebpackPlugin.plugin.options.favicon = false;
// Required for lingui-macros // Required for lingui-macros
let { rule } = helpers.getLoadersByName(config, 'babel-loader')[0]; // let { rule } = helpers.getLoadersByName(config, 'babel-loader')[0];
let babelConfig = rule.options; // let babelConfig = rule.options;
babelConfig.plugins.push('macros'); // babelConfig.plugins.push('macros');
if (env.isProd) { if (env.isProd) {
config.devtool = false; // disable sourcemaps config.devtool = false; // disable sourcemaps
@@ -33,25 +31,5 @@ export default function (config, env, helpers) {
// Remove the default hash append in chunk name // Remove the default hash append in chunk name
config.output.chunkFilename = '[name].chunk.js'; config.output.chunkFilename = '[name].chunk.js';
// config.plugins.push(
// new CommonsChunkPlugin({
// name: 'vendor',
// minChunks: ({ resource }) => /node_modules/.test(resource)
// })
// );
const swPlugin = helpers.getPluginsByName(
config,
'SWPrecacheWebpackPlugin'
)[0];
if (swPlugin) {
// config.plugins.splice(swPlugin.index, 1);
}
const uglifyPlugin = helpers.getPluginsByName(config, 'UglifyJsPlugin')[0];
if (uglifyPlugin) {
// config.plugins.splice(uglifyPlugin.index, 1);
}
} }
} }

View File

@@ -0,0 +1,20 @@
window.addEventListener('message', e => {
// Recieving from app window
if (e.data && e.data.contents && e.data.contents.match(/<html/)) {
const frame = document.querySelector('iframe');
frame.src = frame.src;
setTimeout(() => {
frame.contentDocument.open();
frame.contentDocument.write(e.data.contents);
frame.contentDocument.close();
}, 10);
}
if (e.data && e.data.url && e.data.url.match(/index\.html/)) {
document.querySelector('iframe').src = e.data.url;
}
// Recieving from preview iframe
if (e.data && e.data.logs) {
window.opener.postMessage(e.data, '*');
}
});

22
preview/preview.html Normal file
View File

@@ -0,0 +1,22 @@
<style>
body {
margin: 0;
}
#demo-frame {
border: 0;
width: 100%;
background: white;
height: 100%;
}
</style>
<body>
<iframe
src="about://blank"
frameborder="0"
id="demo-frame"
allowfullscreen
></iframe>
<script src="detached-window.js"></script>
</body>

View File

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

View File

@@ -10,6 +10,12 @@ export function trackEvent(category, action, label, value) {
} }
if (window.ga) { if (window.ga) {
ga('send', 'event', category, action, label, value); ga('send', 'event', category, action, label, value);
fetch('https://event-ajhkrtmkaq-uc.a.run.app', {
method: 'post',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ category, action, label, value })
});
} }
} }

BIN
src/assets/kaboom-logo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 216 KiB

BIN
src/assets/pro-panda.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 201 KiB

View File

@@ -0,0 +1,16 @@
<?xml version="1.0" encoding="UTF-8" standalone="no" ?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" width="128" height="128" viewBox="0 0 128 128" xml:space="preserve">
<desc>Created with Fabric.js 5.2.4</desc>
<defs>
</defs>
<rect x="0" y="0" width="100%" height="100%" fill="transparent"></rect>
<g transform="matrix(1 0 0 1 64 64)" id="f3d7db2e-8d04-4ee3-99c9-b0befc232687" >
<rect style="stroke: none; stroke-width: 1; stroke-dasharray: none; stroke-linecap: butt; stroke-dashoffset: 0; stroke-linejoin: miter; stroke-miterlimit: 4; fill: rgb(255,255,255); fill-rule: nonzero; opacity: 1; visibility: hidden;" vector-effect="non-scaling-stroke" x="-64" y="-64" rx="0" ry="0" width="128" height="128" />
</g>
<g transform="matrix(Infinity NaN NaN Infinity 0 0)" id="3dd1926a-5f11-4f73-a873-48f6092f16b5" >
</g>
<g transform="matrix(2.54 0 0 2.54 63.96 65.38)" >
<path style="stroke: none; stroke-width: 1; stroke-dasharray: none; stroke-linecap: butt; stroke-dashoffset: 0; stroke-linejoin: miter; stroke-miterlimit: 4; fill: rgb(56,189,248); fill-rule: evenodd; opacity: 1;" vector-effect="non-scaling-stroke" transform=" translate(-25.52, -15.22)" d="M 25.517 0 C 18.712 0 14.46 3.382 12.758 10.146 C 15.309999999999999 6.764000000000001 18.287 5.496 21.689 6.341000000000001 C 23.63 6.823000000000001 25.018 8.223 26.553 9.773000000000001 C 29.055 12.297 31.951 15.218000000000002 38.275 15.218000000000002 C 45.079 15.218000000000002 49.332 11.836000000000002 51.033 5.073000000000002 C 48.482 8.455000000000002 45.505 9.723000000000003 42.103 8.877000000000002 C 40.161 8.395000000000003 38.773 6.995000000000003 37.238 5.446000000000002 C 34.736 2.92 31.841 0 25.517 0 z M 12.758 15.218 C 5.954 15.218 1.701 18.6 0 25.364 C 2.552 21.982 5.529 20.714 8.93 21.559 C 10.872 22.041 12.26 23.441000000000003 13.795 24.991 C 16.297 27.515 19.192 30.436 25.517 30.436 C 32.321 30.436 36.574 27.055 38.275 20.291 C 35.723 23.673000000000002 32.745999999999995 24.941000000000003 29.344 24.096 C 27.403000000000002 23.613 26.015 22.213 24.48 20.664 C 21.978 18.14 19.082 15.218000000000002 12.758000000000001 15.218000000000002 z" stroke-linecap="round" />
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.3 KiB

357
src/components/Assets.jsx Normal file
View File

@@ -0,0 +1,357 @@
import React, { useState, useEffect } from 'react';
import firebase from 'firebase/app';
import 'firebase/storage';
import { HStack, Stack, VStack } from './Stack';
import { copyToClipboard } from '../utils';
import { Trans } from '@lingui/macro';
import { ProBadge } from './ProBadge';
import { LoaderWithText } from './Loader';
import { Text } from './Text';
import { Icon } from './Icons';
function getFileType(url) {
// get extension from a url using URL API
const ext = new URL(url).pathname.split('.').pop();
if (['jpg', 'jpeg', 'png', 'gif', 'svg'].includes(ext)) {
return 'image';
}
return ext;
}
const Assets = ({ onProBtnClick, onLoginBtnClick }) => {
const [files, setFiles] = useState([]);
const [isFetchingFiles, setIsFetchingFiles] = useState(false);
const [searchTerm, setSearchTerm] = useState('');
const [filteredFiles, setFilteredFiles] = useState([]);
const [isUploading, setIsUploading] = useState(false);
const [uploadProgress, setUploadProgress] = useState();
const [listType, setListType] = useState('grid');
const storageRef = firebase.storage().ref(`assets/${window.user?.uid}`);
const uploadFile = file => {
if (file.size > 1024 * 1024) {
// 1MB limit
alert('File size must be less than 1MB');
return;
}
setIsUploading(true);
const metadata = {
cacheControl: 'public, max-age=3600' // 1 hr
};
const fileRef = storageRef.child(file.name);
const task = fileRef.put(file, metadata);
task.on(
'state_changed',
snapshot => {
// 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
var progress = (snapshot.bytesTransferred / snapshot.totalBytes) * 100;
console.log('Upload is ' + progress + '% done');
},
error => {
// Handle unsuccessful uploads
setIsUploading(false);
console.error('File upload error:', error);
alertsService.add('⚠️ File upload failed');
},
() => {
// uploadTask.snapshot.ref.getDownloadURL().then((downloadURL) => {
// console.log('File available at', downloadURL);
// });
alertsService.add('File uploaded successfully');
fetchFiles();
setIsUploading(false);
}
);
};
// Function to handle file upload
const handleFileUpload = e => {
const file = e.target.files[0];
uploadFile(file);
};
// Function to fetch existing files
const fetchFiles = () => {
setIsFetchingFiles(true);
storageRef
.listAll()
.then(result => {
const filePromises = result.items.map(item => {
return item.getDownloadURL().then(url => {
return { name: item.name, url };
});
});
Promise.all(filePromises).then(files => {
files.forEach(f => (f.ext = getFileType(f.url)));
setFiles(files);
});
setIsFetchingFiles(false);
})
.catch(error => {
console.error('File fetch error:', error);
setIsFetchingFiles(false);
});
};
// Function to handle search input change
const handleSearchChange = e => {
const term = e.target.value;
setSearchTerm(term);
};
useEffect(() => {
if (window.user?.isPro) {
fetchFiles();
}
}, []);
useEffect(() => {
if (searchTerm) {
setFilteredFiles(
files.filter(file =>
file.name.toLowerCase().includes(searchTerm.toLowerCase())
)
);
} else {
setFilteredFiles(files);
}
}, [files, searchTerm]);
const [isDropTarget, setIsDropTarget] = useState(false);
const handleDragDropEvent = e => {
if (e.type === 'dragover') {
// required for drop to work
e.preventDefault();
} else if (e.type === 'dragleave') {
e.preventDefault();
// so that individual nested elements don't trigger dragleave
if (e.currentTarget.contains(e.target)) return;
setIsDropTarget(false);
} else if (e.type === 'dragenter') {
setIsDropTarget(true);
}
};
const handleDrop = e => {
e.preventDefault();
setIsDropTarget(false);
if (e.dataTransfer.items) {
const file = e.dataTransfer.items[0].getAsFile();
uploadFile(file);
}
};
const [lastCopiedFile, setLastCopiedFile] = useState({ name: '', count: 0 });
const copyFileUrl = url => {
let copyContent = url;
if (lastCopiedFile.name === url) {
lastCopiedFile.count = (lastCopiedFile.count + 1) % 3;
} else {
lastCopiedFile.count = 0;
lastCopiedFile.name = url;
}
switch (lastCopiedFile.count) {
case 0:
copyContent = url;
break;
case 1:
copyContent = `<img src="${url}" />`;
break;
case 2:
copyContent = `url("${url}")`;
break;
}
setLastCopiedFile({ ...lastCopiedFile });
copyToClipboard(copyContent).then(() => {
switch (lastCopiedFile.count) {
case 0:
alertsService.add('File URL copied');
break;
case 1:
alertsService.add('File URL copied as <IMG> tag');
break;
case 2:
alertsService.add('File URL copied as CSS image URL');
break;
}
});
};
const removeFileHandler = index => {
const file = files[index];
const answer = confirm(`Are you sure you want to delete "${file.name}"?`);
if (!answer) return;
const fileRef = storageRef.child(file.name);
fileRef
.delete()
.then(() => {
alertsService.add('File deleted successfully');
setFiles(files.filter((_, i) => i !== index));
})
.catch(error => {
console.error('File delete error:', error);
});
};
if (!window.user?.isPro) {
return (
<VStack align="stretch" gap={2}>
<p>Assets feature is available in PRO plan.</p>
<button
class="btn btn--pro"
onClick={window.user ? onProBtnClick : onLoginBtnClick}
>
<HStack gap={1} fullWidth justify="center">
{window.user ? <>Upgrade to PRO</> : <>Login & upgrade to PRO</>}
</HStack>
</button>
</VStack>
);
}
return (
<div
onDragEnter={handleDragDropEvent}
onDragLeave={handleDragDropEvent}
onDragOver={handleDragDropEvent}
onDrop={handleDrop}
>
<HStack gap={1} align="center">
<h1>
<Trans>Assets</Trans>
</h1>
<ProBadge />
</HStack>
<div
class="asset-manager__upload-box"
style={{
background: isDropTarget ? '#19a61940' : 'transparent',
borderColor: isDropTarget ? 'limegreen' : null
}}
>
{isUploading ? <div class="asset-manager__progress-bar"></div> : null}
<div style={{ visibility: isUploading ? 'hidden' : 'visible' }}>
<VStack gap={1} align="stretch">
<label style="background: #00000001">
<Text tag="p" align="center">
Drop files or click here to upload
</Text>
<Text tag="p" appearance="secondary" align="center">
File should be max 300KB in size
</Text>
<input
type="file"
onChange={handleFileUpload}
style={{ marginTop: 'auto', display: 'none' }}
/>
</label>
</VStack>
</div>
</div>
{isFetchingFiles && <LoaderWithText>Fetching files...</LoaderWithText>}
<VStack align="stretch" gap={1}>
{files.length ? (
<Stack gap={1}>
<input
type="text"
placeholder="Search files"
value={searchTerm}
onChange={handleSearchChange}
style={{ width: '100%' }}
/>
<button
class={`btn btn--dark ${
listType === 'list' ? 'btn--active' : ''
} hint--rounded hint--top-left`}
onClick={() => setListType('list')}
aria-label="List view"
>
<Icon name="view-list" />
</button>
<button
class={`btn btn--dark ${
listType === 'grid' ? 'btn--active' : ''
} hint--rounded hint--top-left`}
onClick={() => setListType('grid')}
aria-label="Grid view"
>
<Icon name="view-grid" />
</button>
</Stack>
) : null}
<div
class={`asset-manager__file-container ${
listType === 'grid' ? 'asset-manager__file-container--grid' : ''
}`}
>
{filteredFiles.map((file, index) => (
<div
key={index}
class={`asset-manager__file ${
listType === 'grid' ? 'asset-manager__file--grid' : ''
}`}
>
{/* <a href={file.url} target="_blank" rel="noopener noreferrer"> */}
{file.ext === 'image' ? (
<img src={file.url} class="asset-manager__file-image" />
) : (
<div
style={{
position: 'relative',
display: 'flex'
}}
class="asset-manager__file-image"
>
<svg
xmlns="http://www.w3.org/2000/svg"
fill="#ffffff33"
viewBox="0 0 24 24"
>
<path d="M13,9V3.5L18.5,9M6,2C4.89,2 4,2.89 4,4V20A2,2 0 0,0 6,22H18A2,2 0 0,0 20,20V8L14,2H6Z" />
</svg>
<span className="asset-manager__file-ext">{file.ext}</span>
</div>
)}
<div class="asset-manager__file-actions">
<Stack gap={1} fullWidth justify="center">
<button
class={`btn btn--dark ${
listType === 'list' ? 'btn--active' : ''
} hint--rounded hint--top hint--medium`}
onClick={() => copyFileUrl(file.url)}
aria-label="Copy URL (or keep clicking to copy other formats)"
>
<Icon name="copy" />
</button>
<button
class={`btn btn--dark ${
listType === 'list' ? 'btn--active' : ''
} hint--rounded hint--top-left`}
onClick={() => removeFileHandler(index)}
aria-label="Delete"
>
<Icon name="trash" />
</button>
</Stack>
</div>
<span class="asset-manager__file-name">{file.name}</span>
{/* </a> */}
</div>
))}
</div>
</VStack>
</div>
);
};
export { Assets };

View File

@@ -74,46 +74,94 @@ function Notification({ version, isLatest, ...props }) {
</a> </a>
</p> </p>
<p> <p>
Web Maker now has more than 50K weekly active users! Thank you for Web Maker now has more than 70K weekly active users! Thank you for
being a part of this community of awesome developers. If you find being a part of this community of awesome developers. If you find
Web Maker helpful,{' '} Web Maker helpful,{' '}
<a <a
href="https://chrome.google.com/webstore/detail/web-maker/lkfkkhfhhdkiemehlpkgjeojomhpccnh/reviews" href="https://chromewebstore.google.com/detail/web-maker/lkfkkhfhhdkiemehlpkgjeojomhpccnh/reviews"
target="_blank" target="_blank"
rel="noopener noreferrer" rel="noopener noreferrer"
class="btn" class="btn"
> >
Please rate Web Maker <span class="star" /> Please rate Web Maker &nbsp;
<span class="star" />
</a> </a>
&nbsp; &nbsp;
<a <a
href="http://twitter.com/share?url=https://webmaker.app/&text=Web Maker - A blazing fast %26 offline web playground! via @webmakerApp&related=webmakerApp&hashtags=web,editor,chrome,extension" class="btn"
href="http://twitter.com/share?url=https://webmaker.app/&text=Web Maker - A blazing fast %26 offline frontend playground! via @webmakerApp&related=webmakerApp&hashtags=web,editor,chrome,extension"
target="_blank" target="_blank"
rel="noopener noreferrer" rel="noopener noreferrer"
class="btn" class="btn"
> >
Share it Share it
</a> </a>
&nbsp;
<Button
aria-label="Support the developer"
onClick={props.onSupportBtnClick}
data-event-action="supportDeveloperChangelogBtnClick"
data-event-category="ui"
class="btn btn-icon"
>
Support the developer
</Button>
</p> </p>
</div> </div>
) : null} ) : null}
</div> </div>
); );
} }
export function Notifications(props) { export function Changelog(props) {
return ( return (
<div> <div>
<h1>Whats new?</h1> <h1>Whats new?</h1>
<Notification version="6.1.0" {...props} isLatest={true}>
<li>
<strong>🕹 Kaboom game engine</strong>: Kaboom.js is now available in
quick-add library list. Also, a new template to quick-start in Kaboom
has been added.
</li>
<NotificationItem type="bug">
Incorrect/old preview rendering bug is fixed.
</NotificationItem>
</Notification>
<Notification version="6.0.0" {...props}>
<li>
<strong>🎁 PRO plan 🎉</strong>: Today I introduce to you - Web
Maker's PRO plan! A set of additional super-features which you can
buy. The PRO plan is available as monthly/annual subscription as well
as a one-time lifetime price! Let's see what you get as a PRO.
</li>
<li>
<strong>🔓 Share your creations</strong>: Web Maker has always been a
privacy-first app. Continuing that culture, today we introduce "Share
your creation" feature. Your creations are still created as private
but now you can securely make them public to share with the world. As
a free user you can have 1 creation public at a time. Upgrading to PRO
gives you unlimited public creations.
</li>
<li>
<strong>🗄 Asset hosting</strong>: No more going to other places in
order to host your images, CSS or JS files. Web Maker PRO gives you
the ability to host your assets right inside Web Maker. You can upload
images, CSS and JS files and use them in your creations.
</li>
<li>
<strong>📁 Files mode</strong>: As a free user you could always create
2 creations in Files mode. With PRO, you can create unlimited
creations in Files mode.
</li>
<NotificationItem type="ui">
Web app is now available on webmaker.app/create (Previously available
on webmaker.app/app)
</NotificationItem>
<NotificationItem type="ui">
Fork button is now available in the header too
</NotificationItem>
</Notification>
<Notification version="5.3.0" {...props} isLatest={true}>
<li>
<strong>Tailwind CSS templates</strong>: Tailwind CSS template is now
available! If you are on the Chrome extension, use the Tailwind CSS 2
template which is old but works without JavaScript (since JavaScript
is disabled currently in Chrome extension). That said, JavaScript is
coming back soon to the Chrome extension! 😃
</li>
<li> Popular libraries updated to latest versions.</li>
</Notification>
<Notification version="5.2.0" {...props}> <Notification version="5.2.0" {...props}>
<li> <li>
<strong>Improvement</strong>: Atomic CSS (Atomizer) has been updated <strong>Improvement</strong>: Atomic CSS (Atomizer) has been updated
@@ -157,7 +205,7 @@ export function Notifications(props) {
Add library feature is fixed. Add library feature is fixed.
</NotificationItem> </NotificationItem>
</Notification> </Notification>
<Notification version="5.0.0" {...props} isLatest={true}> <Notification version="5.0.0" {...props}>
<li> <li>
<strong>We are Back! 😎</strong>: After almost 2 years of nothing <strong>We are Back! 😎</strong>: After almost 2 years of nothing
being shipped, we are back in action. That too with a bang! We have a being shipped, we are back in action. That too with a bang! We have a
@@ -192,7 +240,7 @@ export function Notifications(props) {
<strong>autoPreview</strong> and <strong>isMonacoEditorOn</strong> are <strong>autoPreview</strong> and <strong>isMonacoEditorOn</strong> are
supported. Example:{' '} supported. Example:{' '}
<code> <code>
https://webmaker.app/app?settings=autoPreview:false,isMonacoEditorOn:true https://webmaker.app/create?settings=autoPreview:false,isMonacoEditorOn:true
</code> </code>
</li> </li>
<NotificationItem type="bug"> <NotificationItem type="bug">
@@ -585,11 +633,11 @@ export function Notifications(props) {
as web app that runs offline just like the extension! Checkout it as web app that runs offline just like the extension! Checkout it
out -&gt; out -&gt;
<a <a
href="https://webmaker.app/app/" href="https://webmaker.app/create/"
target="_blank" target="_blank"
rel="noopener noreferrer" rel="noopener noreferrer"
> >
https://webmaker.app/app/ https://webmaker.app/create/
</a> </a>
. .
</li> </li>

View File

@@ -237,7 +237,7 @@ export default class CodeEditor extends Component {
monacoDepsDeferred = deferred(); monacoDepsDeferred = deferred();
loadCss({ url: 'lib/monaco/monaco.css', id: 'monaco-css' }); loadCss({ url: 'lib/monaco/monaco.css', id: 'monaco-css' });
import( import(
/* webpackChunkName: "monaco" */ '../lib/monaco/monaco.bundle.js' /* webpackChunkName: "monaco" */ '/lib/monaco/monaco.bundle.js'
).then(() => { ).then(() => {
monacoDepsDeferred.resolve(); monacoDepsDeferred.resolve();
}); });

View File

@@ -12,15 +12,12 @@ class LogRow extends Component {
const theme = { const theme = {
...chromeDark, ...chromeDark,
...{ ...{
OBJECT_VALUE_STRING_COLOR: 'green',
BASE_FONT_SIZE: '20px', BASE_FONT_SIZE: '20px',
TREENODE_FONT_SIZE: '20px' TREENODE_FONT_SIZE: '20px'
} }
}; };
return ( return <Inspector theme={theme} data={this.props.data} />;
<Inspector theme={theme} theme="chromeDark" data={this.props.data} />
);
} }
} }

View File

@@ -21,6 +21,12 @@ const minCodeWrapSize = 33;
/* global htmlCodeEl /* global htmlCodeEl
*/ */
const PREVIEW_FRAME_HOST = window.DEBUG
? 'http://localhost:7888'
: `https://wbmakr.com`;
let cachedSandboxAttribute = '';
export default class ContentWrap extends Component { export default class ContentWrap extends Component {
constructor(props) { constructor(props) {
super(props); super(props);
@@ -152,29 +158,56 @@ export default class ContentWrap extends Component {
} }
if (shouldInlineJs) { if (shouldInlineJs) {
const writeInsideIframe = () => {
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 }, '*');
} else if (window.IS_EXTENSION) {
this.frame.contentWindow.postMessage({ contents }, '*');
} else { } else {
this.frame.contentDocument.open(); // 1. we refresh the frame so that all JS is cleared in the frame. this will
this.frame.contentDocument.write(contents); // break the iframe since sandboxed frame isn't served by SW (needed for offline support)
this.frame.contentDocument.close(); // 2. we cache and remove the sandbox attribute and refresh again so that it gets served by SW
} // 3. we add back cached sandbox attr & write the contents to the iframe
}; const refreshAndDo = fn => {
Promise.race([ Promise.race([
// Just in case onload promise doesn't resolves // Just in case onload promise doesn't resolves
new Promise(resolve => { new Promise(resolve => {
setTimeout(resolve, 200); setTimeout(resolve, 400);
}), }),
new Promise(resolve => { new Promise(resolve => {
this.frame.onload = resolve; this.frame.onload = resolve;
}) })
]).then(writeInsideIframe); ]).then(fn);
// Setting to blank string cause frame to reload // 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 = () => {
if (!cachedSandboxAttribute && window.DEBUG) {
// alert('sandbox empty');
}
// console.log('setting back sandbox attr', sandbox);
// this.frame.setAttribute('sandbox', cachedSandboxAttribute);
// this.frame.removeAttribute('sweet');
// if (window.IS_EXTENSION) {
// this.frame.contentDocument.open();
// this.frame.contentDocument.write(contents);
// this.frame.contentDocument.close();
// } else {
this.frame.contentWindow.postMessage({ contents }, '*');
// }
};
// refreshAndDo(() => {
// cachedSandboxAttribute = this.frame.getAttribute('sandbox');
// // console.log('removing sandbox', sandbox);
// // this.frame.setAttribute('sweet', sandbox);
// // this.frame.removeAttribute('sandbox');
// refreshAndDo(writeInsideIframe);
// });
refreshAndDo(writeInsideIframe);
}
} else { } else {
// DEPRECATED // DEPRECATED
// we need to store user script in external JS file to prevent inline-script // we need to store user script in external JS file to prevent inline-script
@@ -240,7 +273,8 @@ export default class ContentWrap extends Component {
false && false &&
!isForced && !isForced &&
currentCode.html === this.codeInPreview.html && currentCode.html === this.codeInPreview.html &&
currentCode.js === this.codeInPreview.js currentCode.js === this.codeInPreview.js &&
false
) { ) {
computeCss( computeCss(
cssMode === CssModes.ACSS ? currentCode.html : currentCode.css, cssMode === CssModes.ACSS ? currentCode.html : currentCode.css,
@@ -326,7 +360,7 @@ export default class ContentWrap extends Component {
// Replace correct css file in LINK tags's href // Replace correct css file in LINK tags's href
if (prefs.editorTheme) { if (prefs.editorTheme) {
window.editorThemeLinkTag.href = `lib/codemirror/theme/${prefs.editorTheme}.css`; window.editorThemeLinkTag.href = `./lib/codemirror/theme/${prefs.editorTheme}.css`;
} }
window.fontStyleTag.textContent = window.fontStyleTag.textContent =
@@ -517,11 +551,20 @@ export default class ContentWrap extends Component {
const iframeHeight = iframeBounds.height; const iframeHeight = iframeBounds.height;
document.body.classList.add('is-detached-mode'); document.body.classList.add('is-detached-mode');
if (window.IS_EXTENSION) {
this.detachedWindow = window.open( this.detachedWindow = window.open(
'./preview.html', './preview.html',
'Web Maker', 'Web Maker',
`width=${iframeWidth},height=${iframeHeight},resizable,scrollbars=yes,status=1` `width=${iframeWidth},height=${iframeHeight},resizable,scrollbars=yes,status=1`
); );
} else {
this.detachedWindow = window.open(
`${PREVIEW_FRAME_HOST}/preview.html`,
'Web Maker',
`width=${iframeWidth},height=${iframeHeight},resizable,scrollbars=yes,status=1`
);
}
// Trigger initial render in detached window // Trigger initial render in detached window
setTimeout(() => { setTimeout(() => {
this.setPreviewContent(true); this.setPreviewContent(true);
@@ -837,7 +880,7 @@ export default class ContentWrap extends Component {
<a <a
class="code-wrap__header-btn " class="code-wrap__header-btn "
title="Format code" title="Format code"
onClick={this.prettifyBtnClickHandler.bind(this, 'css')} onClick={this.prettifyBtnClickHandler.bind(this, 'js')}
> >
<svg> <svg>
<use xlinkHref="#code-brace-icon" /> <use xlinkHref="#code-brace-icon" />
@@ -878,13 +921,26 @@ export default class ContentWrap extends Component {
</div> </div>
</SplitPane> </SplitPane>
<div class="demo-side" id="js-demo-side" style=""> <div class="demo-side" id="js-demo-side" style="">
{window.IS_EXTENSION ? (
<iframe <iframe
ref={el => (this.frame = el)} ref={el => (this.frame = el)}
frameborder="0" frameborder="0"
id="demo-frame" id="demo-frame"
allowfullscreen
src="./indexpm.html" src="./indexpm.html"
allowfullscreen="true"
/> />
) : (
<iframe
src={`./indexpm.html`}
ref={el => (this.frame = el)}
frameborder="0"
id="demo-frame"
sandbox="allow-downloads allow-forms allow-modals allow-pointer-lock allow-popups allow-presentation allow-scripts allow-top-navigation-by-user-activation"
allow="accelerometer; camera; encrypted-media; display-capture; geolocation; gyroscope; microphone; midi; clipboard-read; clipboard-write; web-share"
allowpaymentrequest="true"
allowfullscreen="true"
/>
)}
<PreviewDimension ref={comp => (this.previewDimension = comp)} /> <PreviewDimension ref={comp => (this.previewDimension = comp)} />

View File

@@ -29,7 +29,7 @@ import { FileIcon } from './FileIcon';
const minCodeWrapSize = 33; const minCodeWrapSize = 33;
const PREVIEW_FRAME_HOST = window.DEBUG const PREVIEW_FRAME_HOST = window.DEBUG
? 'http://localhost:7888' ? 'http://localhost:7888'
: `https://preview.${location.host}`; : `https://wbmakr.com`;
/* global htmlCodeEl /* global htmlCodeEl
*/ */
@@ -355,7 +355,7 @@ export default class ContentWrapFiles extends Component {
// Replace correct css file in LINK tags's href // Replace correct css file in LINK tags's href
if (prefs.editorTheme) { if (prefs.editorTheme) {
window.editorThemeLinkTag.href = `lib/codemirror/theme/${prefs.editorTheme}.css`; window.editorThemeLinkTag.href = `./lib/codemirror/theme/${prefs.editorTheme}.css`;
} }
window.fontStyleTag.textContent = window.fontStyleTag.textContent =

View File

@@ -5,6 +5,7 @@ import templates from '../templateList';
import { BetaTag } from './common'; import { BetaTag } from './common';
import { trackEvent } from '../analytics'; import { trackEvent } from '../analytics';
import Tabs, { TabPanel } from './Tabs'; import Tabs, { TabPanel } from './Tabs';
import { ProBadge } from './ProBadge';
export class CreateNewModal extends Component { export class CreateNewModal extends Component {
constructor(props) { constructor(props) {
@@ -150,11 +151,10 @@ export class CreateNewModal extends Component {
<h1 class="mt-0">Create New</h1> <h1 class="mt-0">Create New</h1>
<Tabs horizontal onChange={this.modeChangeHandler}> <Tabs horizontal onChange={this.modeChangeHandler}>
<TabPanel label={option1}> <TabPanel label={option1}>
<div class="d-f fxw-w"> <div class="templates-container">
<button <button
type="button" type="button"
class="btn btn--primary" class="btn btn--primary"
style="margin:20px 10px"
onClick={() => { onClick={() => {
trackEvent('ui', 'startBlankBtnClick'); trackEvent('ui', 'startBlankBtnClick');
onBlankTemplateSelect(); onBlankTemplateSelect();
@@ -170,17 +170,17 @@ export class CreateNewModal extends Component {
item={template} item={template}
focusable focusable
onClick={onTemplateSelect.bind(null, template, false)} onClick={onTemplateSelect.bind(null, template, false)}
hasOptions={false}
/> />
); );
})} })}
</div> </div>
</TabPanel> </TabPanel>
<TabPanel label={option2}> <TabPanel label={option2}>
<div class="d-f fxw-w show-when-app"> <div class="templates-container show-when-app">
<button <button
type="button" type="button"
class="btn btn--primary" class="btn btn--primary"
style="margin:20px 10px"
onClick={() => { onClick={() => {
trackEvent('ui', 'startBlankFileBtnClick'); trackEvent('ui', 'startBlankFileBtnClick');
onBlankFileTemplateSelect(); onBlankFileTemplateSelect();
@@ -196,14 +196,19 @@ export class CreateNewModal extends Component {
item={template} item={template}
focusable focusable
onClick={onTemplateSelect.bind(null, template, true)} onClick={onTemplateSelect.bind(null, template, true)}
hasOptions={false}
/> />
); );
} }
})} })}
</div> </div>
<p>
2 files mode creations available in Free plan. To create unlimited
files mode creations, upgrade to <ProBadge />.
</p>
<div class="show-when-extension"> <div class="show-when-extension">
Files modes is currently only available in Web app.{' '} Files modes is currently only available in Web app.{' '}
<a href="https://webmaker.app/app/">Try the Web app now</a>. <a href="https://webmaker.app/create/">Try the Web app now</a>.
</div> </div>
</TabPanel> </TabPanel>
</Tabs> </Tabs>

View File

@@ -1,40 +1,36 @@
import { h, Component } from 'preact';
import { Button } from './common'; import { Button } from './common';
import { Trans, t } from '@lingui/macro'; import { Trans, t } from '@lingui/macro';
import { I18n } from '@lingui/react'; import { I18n } from '@lingui/react';
import { ProBadge } from './ProBadge';
import { HStack } from './Stack';
import { useEffect, useState } from 'preact/hooks';
class JS13K extends Component { const JS13K = props => {
constructor(props) { const [daysLeft, setDaysLeft] = useState(0);
super(props);
const compoDate = new Date('August 13 2018 11:00 GMT'); useEffect(() => {
var now = new Date(); const compoDate = new Date('August 13 2024 11:00 GMT');
var daysLeft; const now = new Date();
if (+compoDate > +now) { if (+compoDate > +now) {
daysLeft = Math.floor((compoDate - now) / 1000 / 3600 / 24); const _daysLeft = Math.floor((compoDate - now) / 1000 / 3600 / 24);
} setDaysLeft(_daysLeft);
this.setState({
daysLeft
});
} }
}, []);
const codeSizeInKb = props.codeSize ? (props.codeSize / 1024).toFixed(2) : 0;
render() {
const codeSizeInKb = this.props.codeSize
? (this.props.codeSize / 1024).toFixed(2)
: 0;
return ( return (
<div <div
role="button" role="button"
class="flex flex-v-center" className="flex flex-v-center"
tabIndex="0" tabIndex="0"
onClick={this.props.onClick} onClick={props.onClick}
onBlur={this.props.onBlur} onBlur={props.onBlur}
> >
<img src="assets/js13kgames.png" alt="JS13K Games logo" height="24" />{' '} <img src="assets/js13kgames.png" alt="JS13K Games logo" height="24" />{' '}
<div class="footer__js13k-days-left"> <div className="footer__js13k-days-left">{daysLeft} days to go</div>
{this.state.daysLeft} days to go
</div>
<div <div
class="footer__js13k-code-size" className="footer__js13k-code-size"
style={{ style={{
color: codeSizeInKb > 10 ? 'crimson' : 'limegreen' color: codeSizeInKb > 10 ? 'crimson' : 'limegreen'
}} }}
@@ -42,36 +38,31 @@ class JS13K extends Component {
{codeSizeInKb} KB/ 13KB {codeSizeInKb} KB/ 13KB
</div> </div>
<span <span
class="caret" className="caret"
style={`transition:0.3s ease; transform-origin: center 2px; ${ style={{
this.props.isOpen ? 'transform:rotate(180deg);' : '' transition: '0.3s ease',
}`} transformOrigin: 'center 2px',
transform: props.isOpen ? 'rotate(180deg)' : ''
}}
/> />
</div> </div>
); );
}
}
export default class Footer extends Component {
constructor(props) {
super(props);
this.state = {
isKeyboardShortcutsModalOpen: false,
isJs13kDropdownOpen: false
}; };
}
layoutBtnClickhandler(layoutId) { export const Footer = props => {
this.props.layoutBtnClickHandler(layoutId); const [isKeyboardShortcutsModalOpen, setIsKeyboardShortcutsModalOpen] =
useState(false);
const [isJs13kDropdownOpen, setIsJs13kDropdownOpen] = useState(false);
function layoutBtnClickhandler(layoutId) {
props.layoutBtnClickHandler(layoutId);
} }
js13kClickHandler() { function js13kClickHandler() {
// console.log(999); // console.log(999);
this.setState({ setIsJs13kDropdownOpen(!isJs13kDropdownOpen);
isJs13kDropdownOpen: !this.state.isJs13kDropdownOpen
});
} }
render() {
return ( return (
<I18n> <I18n>
{({ i18n }) => ( {({ i18n }) => (
@@ -83,7 +74,7 @@ export default class Footer extends Component {
&copy; &copy;
<span class="web-maker-with-tag">Web Maker</span> &nbsp;&nbsp; <span class="web-maker-with-tag">Web Maker</span> &nbsp;&nbsp;
<Button <Button
onClick={this.props.helpBtnClickHandler} onClick={props.helpBtnClickHandler}
data-event-category="ui" data-event-category="ui"
data-event-action="helpButtonClick" data-event-action="helpButtonClick"
class="footer__link hint--rounded hint--top-right" class="footer__link hint--rounded hint--top-right"
@@ -97,7 +88,7 @@ export default class Footer extends Component {
</svg> </svg>
</Button> </Button>
<Button <Button
onClick={this.props.keyboardShortcutsBtnClickHandler} onClick={props.keyboardShortcutsBtnClickHandler}
data-event-category="ui" data-event-category="ui"
data-event-action="keyboardShortcutButtonClick" data-event-action="keyboardShortcutButtonClick"
class="footer__link hint--rounded hint--top-right hide-on-mobile" class="footer__link hint--rounded hint--top-right hide-on-mobile"
@@ -130,33 +121,48 @@ export default class Footer extends Component {
<use xlinkHref="#twitter-icon" /> <use xlinkHref="#twitter-icon" />
</svg> </svg>
</a> </a>
{props.user?.isPro ? (
<Button <Button
onClick={this.props.supportDeveloperBtnClickHandler} onClick={props.proBtnClickHandler}
data-event-category="ui" data-event-category="ui"
data-event-action="supportDeveloperFooterBtnClick" data-event-action="manageProFooterBtnClick"
class="footer__link ml-1 hint--rounded hint--top-right hide-on-mobile support-link"
aria-label={i18n._(t`Manage your PRO subscription`)}
>
<HStack gap={1}>
<Trans>Manage</Trans>
<ProBadge />
</HStack>
</Button>
) : (
<Button
onClick={props.proBtnClickHandler}
data-event-category="ui"
data-event-action="proFooterBtnClick"
class="footer__link ml-1 hint--rounded hint--top-right hide-on-mobile support-link" class="footer__link ml-1 hint--rounded hint--top-right hide-on-mobile support-link"
aria-label={i18n._( aria-label={i18n._(
t`Support the developer by pledging some amount` t`Upgrade to PRO and get some advanced superpowers!`
)} )}
> >
<Trans>Donate</Trans> <HStack gap={1}>
<Trans>Get</Trans>
<ProBadge />
</HStack>
</Button> </Button>
)}
</div> </div>
{this.props.prefs.isJs13kModeOn ? ( {props.prefs.isJs13kModeOn ? (
<div class="flex flex-v-center"> <div class="flex flex-v-center">
<JS13K <JS13K
isOpen={this.state.isJs13kDropdownOpen} isOpen={isJs13kDropdownOpen}
codeSize={this.props.codeSize} codeSize={props.codeSize}
onClick={this.js13kClickHandler.bind(this)} onClick={js13kClickHandler}
onBlur={() => onBlur={() =>
setTimeout( setTimeout(() => setIsJs13kDropdownOpen(false), 300)
() => this.setState({ isJs13kDropdownOpen: false }),
300
)
} }
/> />
{this.state.isJs13kDropdownOpen && ( {isJs13kDropdownOpen && (
<div className="js13k__dropdown"> <div className="js13k__dropdown">
<button <button
class="btn" class="btn"
@@ -165,7 +171,7 @@ export default class Footer extends Component {
display: 'block', display: 'block',
marginBottom: '16px' marginBottom: '16px'
}} }}
onClick={this.props.onJs13KDownloadBtnClick} onClick={props.onJs13KDownloadBtnClick}
> >
<Trans>Download game as zip</Trans> <Trans>Download game as zip</Trans>
</button> </button>
@@ -185,7 +191,7 @@ export default class Footer extends Component {
<button <button
class="btn" class="btn"
style={{ width: '200px', display: 'block' }} style={{ width: '200px', display: 'block' }}
onClick={this.props.onJs13KHelpBtnClick} onClick={props.onJs13KHelpBtnClick}
> >
<Trans>Help</Trans> <Trans>Help</Trans>
</button> </button>
@@ -196,7 +202,7 @@ export default class Footer extends Component {
<div class="footer__right"> <div class="footer__right">
<button <button
onClick={this.props.saveHtmlBtnClickHandler} onClick={props.saveHtmlBtnClickHandler}
id="saveHtmlBtn" id="saveHtmlBtn"
class="mode-btn hint--rounded hint--top-left hide-on-mobile hide-in-file-mode" class="mode-btn hint--rounded hint--top-left hide-on-mobile hide-in-file-mode"
aria-label={i18n._(t`Save as HTML file`)} aria-label={i18n._(t`Save as HTML file`)}
@@ -219,7 +225,7 @@ export default class Footer extends Component {
</svg> </svg>
<button <button
onClick={this.props.codepenBtnClickHandler} onClick={props.codepenBtnClickHandler}
id="codepenBtn" id="codepenBtn"
class="mode-btn hint--rounded hint--top-left hide-on-mobile hide-in-file-mode" class="mode-btn hint--rounded hint--top-left hide-on-mobile hide-in-file-mode"
aria-label={i18n._(t`Edit on CodePen`)} aria-label={i18n._(t`Edit on CodePen`)}
@@ -232,7 +238,7 @@ export default class Footer extends Component {
<button <button
id="screenshotBtn" id="screenshotBtn"
class="mode-btn hint--rounded hint--top-left show-when-extension" class="mode-btn hint--rounded hint--top-left show-when-extension"
onClick={this.props.screenshotBtnClickHandler} onClick={props.screenshotBtnClickHandler}
aria-label={i18n._(t`Take screenshot of preview`)} aria-label={i18n._(t`Take screenshot of preview`)}
> >
<svg style="width:24px;height:24px" viewBox="0 0 24 24"> <svg style="width:24px;height:24px" viewBox="0 0 24 24">
@@ -243,7 +249,7 @@ export default class Footer extends Component {
<div class="footer__separator hide-on-mobile" /> <div class="footer__separator hide-on-mobile" />
<button <button
onClick={this.layoutBtnClickhandler.bind(this, 1)} onClick={() => layoutBtnClickhandler(1)}
id="layoutBtn1" id="layoutBtn1"
class="mode-btn hide-on-mobile hide-in-file-mode" class="mode-btn hide-on-mobile hide-in-file-mode"
aria-label={i18n._(t`Switch to layout with preview on right`)} aria-label={i18n._(t`Switch to layout with preview on right`)}
@@ -253,7 +259,7 @@ export default class Footer extends Component {
</svg> </svg>
</button> </button>
<button <button
onClick={this.layoutBtnClickhandler.bind(this, 2)} onClick={() => layoutBtnClickhandler(2)}
id="layoutBtn2" id="layoutBtn2"
class="mode-btn hide-on-mobile hide-in-file-mode" class="mode-btn hide-on-mobile hide-in-file-mode"
aria-label={i18n._(t`Switch to layout with preview on bottom`)} aria-label={i18n._(t`Switch to layout with preview on bottom`)}
@@ -263,7 +269,7 @@ export default class Footer extends Component {
</svg> </svg>
</button> </button>
<button <button
onClick={this.layoutBtnClickhandler.bind(this, 3)} onClick={() => layoutBtnClickhandler(3)}
id="layoutBtn3" id="layoutBtn3"
class="mode-btn hide-on-mobile hide-in-file-mode" class="mode-btn hide-on-mobile hide-in-file-mode"
aria-label={i18n._(t`Switch to layout with preview on left`)} aria-label={i18n._(t`Switch to layout with preview on left`)}
@@ -273,7 +279,7 @@ export default class Footer extends Component {
</svg> </svg>
</button> </button>
<button <button
onClick={this.layoutBtnClickhandler.bind(this, 5)} onClick={() => layoutBtnClickhandler(5)}
id="layoutBtn5" id="layoutBtn5"
class="mode-btn hide-on-mobile hide-in-file-mode" class="mode-btn hide-on-mobile hide-in-file-mode"
aria-label={i18n._(t`Switch to layout with all vertical panes`)} aria-label={i18n._(t`Switch to layout with all vertical panes`)}
@@ -283,7 +289,7 @@ export default class Footer extends Component {
</svg> </svg>
</button> </button>
<button <button
onClick={this.layoutBtnClickhandler.bind(this, 4)} onClick={() => layoutBtnClickhandler(4)}
id="layoutBtn4" id="layoutBtn4"
class="mode-btn hint--top-left hint--rounded hide-on-mobile" class="mode-btn hint--top-left hint--rounded hide-on-mobile"
aria-label={i18n._(t`Toggle full screen preview`)} aria-label={i18n._(t`Toggle full screen preview`)}
@@ -295,7 +301,7 @@ export default class Footer extends Component {
<button <button
class="mode-btn hint--top-left hint--rounded hide-on-mobile" class="mode-btn hint--top-left hint--rounded hide-on-mobile"
aria-label={i18n._(t`Detach preview`)} aria-label={i18n._(t`Detach preview`)}
onClick={this.props.detachedPreviewBtnHandler} onClick={props.detachedPreviewBtnHandler}
> >
<svg viewBox="0 0 24 24"> <svg viewBox="0 0 24 24">
<path d="M22,17V7H6V17H22M22,5A2,2 0 0,1 24,7V17C24,18.11 23.1,19 22,19H16V21H18V23H10V21H12V19H6C4.89,19 4,18.11 4,17V7A2,2 0 0,1 6,5H22M2,3V15H0V3A2,2 0 0,1 2,1H20V3H2Z" /> <path d="M22,17V7H6V17H22M22,5A2,2 0 0,1 24,7V17C24,18.11 23.1,19 22,19H16V21H18V23H10V21H12V19H6C4.89,19 4,18.11 4,17V7A2,2 0 0,1 6,5H22M2,3V15H0V3A2,2 0 0,1 2,1H20V3H2Z" />
@@ -305,10 +311,10 @@ export default class Footer extends Component {
<div class="footer__separator" /> <div class="footer__separator" />
<button <button
onClick={this.props.notificationsBtnClickHandler} onClick={props.notificationsBtnClickHandler}
id="notificationsBtn" id="notificationsBtn"
class={`notifications-btn mode-btn hint--top-left hint--rounded ${ class={`notifications-btn mode-btn hint--top-left hint--rounded ${
this.props.hasUnseenChangelog ? 'has-new' : '' props.hasUnseenChangelog ? 'has-new' : ''
}`} }`}
aria-label={i18n._(t`See changelog`)} aria-label={i18n._(t`See changelog`)}
> >
@@ -318,7 +324,7 @@ export default class Footer extends Component {
<span class="notifications-btn__dot" /> <span class="notifications-btn__dot" />
</button> </button>
<Button <Button
onClick={this.props.settingsBtnClickHandler} onClick={props.settingsBtnClickHandler}
data-event-category="ui" data-event-category="ui"
data-event-action="settingsBtnClick" data-event-action="settingsBtnClick"
class="mode-btn hint--top-left hint--rounded" class="mode-btn hint--top-left hint--rounded"
@@ -333,5 +339,4 @@ export default class Footer extends Component {
)} )}
</I18n> </I18n>
); );
} };
}

View File

@@ -1,5 +1,5 @@
import { h } from 'preact';
import Modal from './Modal'; import Modal from './Modal';
import { Stack, VStack } from './Stack';
import { Button } from './common'; import { Button } from './common';
import { Trans } from '@lingui/macro'; import { Trans } from '@lingui/macro';
@@ -7,15 +7,21 @@ export function HelpModal(props) {
return ( return (
<Modal show={props.show} closeHandler={props.closeHandler}> <Modal show={props.show} closeHandler={props.closeHandler}>
<h1> <h1>
<Stack gap={1} align="center">
<div class="web-maker-with-tag">Web Maker</div> <div class="web-maker-with-tag">Web Maker</div>
<small style="font-size:14px;">{props.version}</small> <span className="badge">{props.version}</span>
</Stack>
</h1> </h1>
<div> <div>
<p> <p>
<Trans> <Trans>
Made with <span style="margin-right: 8px;">💖</span>&{' '} Made with <span style="margin-right: 8px;">💖</span>&{' '}
<span style="margin-right: 8px;"> 🙌</span> by{' '} <span style="margin-right: 0.2rem;position:relative;top:-3px;">
{' '}
🙌
</span>{' '}
by{' '}
<a <a
href="https://twitter.com/chinchang457" href="https://twitter.com/chinchang457"
target="_blank" target="_blank"
@@ -43,11 +49,11 @@ export function HelpModal(props) {
</Trans> </Trans>
. .
</p> </p>
<p class="show-when-extension"> <p>
<Trans> <Trans>
Like this extension? Please{' '} Like this app? Please{' '}
<a <a
href="https://chrome.google.com/webstore/detail/web-maker/lkfkkhfhhdkiemehlpkgjeojomhpccnh/reviews" href="https://chromewebstore.google.com/detail/web-maker/lkfkkhfhhdkiemehlpkgjeojomhpccnh/reviews"
target="_blank" target="_blank"
rel="noopener noreferrer" rel="noopener noreferrer"
> >
@@ -56,20 +62,16 @@ export function HelpModal(props) {
. .
</Trans> </Trans>
</p> </p>
<p> <p style={{ display: 'flex', gap: '0.5rem', flexWrap: 'wrap' }}>
<Button <a
onClick={props.onSupportBtnClick} href="http://twitter.com/share?url=https://webmaker.app/&text=Web Maker - A blazing fast %26 offline web playground! via @webmakerApp&related=webmakerApp&hashtags=web,frontend,playground,offline"
data-event-action="supportDeveloperHelpBtnClick" rel="noopener noreferrer"
data-event-category="ui"
class="btn btn-icon" class="btn btn-icon"
> >
<svg> <Trans>Share Web Maker</Trans>
<use xlinkHref="#gift-icon" /> </a>{' '}
</svg>
<Trans>Support the developer</Trans>
</Button>{' '}
<a <a
href="https://chrome.google.com/webstore/detail/web-maker/lkfkkhfhhdkiemehlpkgjeojomhpccnh/reviews" href="https://chromewebstore.google.com/detail/web-maker/lkfkkhfhhdkiemehlpkgjeojomhpccnh/reviews"
target="_blank" target="_blank"
rel="noopener noreferrer" rel="noopener noreferrer"
class="btn btn-icon" class="btn btn-icon"
@@ -80,7 +82,7 @@ export function HelpModal(props) {
<Trans>Review Web Maker</Trans> <Trans>Review Web Maker</Trans>
</a>{' '} </a>{' '}
<a <a
href="https://spectrum.chat/web-maker" href="https://github.com/chinchang/web-maker/discussions"
target="_blank" target="_blank"
rel="noopener noreferrer" rel="noopener noreferrer"
class="btn btn-icon" class="btn btn-icon"
@@ -88,7 +90,7 @@ export function HelpModal(props) {
<svg> <svg>
<use xlinkHref="#chat-icon" /> <use xlinkHref="#chat-icon" />
</svg> </svg>
<Trans>Chat</Trans> <Trans>Discuss</Trans>
</a>{' '} </a>{' '}
<a <a
href="https://github.com/chinchang/web-maker/issues" href="https://github.com/chinchang/web-maker/issues"
@@ -190,10 +192,11 @@ export function HelpModal(props) {
</details> </details>
</p> </p>
<p> <VStack gap={1} align="stretch" fullWidth={true}>
<h3> <h3>
<Trans>License</Trans> <Trans>License</Trans>
</h3> </h3>
<p>
<Trans> <Trans>
"Web Maker" is{' '} "Web Maker" is{' '}
<a <a
@@ -213,6 +216,7 @@ export function HelpModal(props) {
</a> </a>
</Trans> </Trans>
</p> </p>
</VStack>
</div> </div>
</Modal> </Modal>
); );

View File

@@ -1,5 +1,3 @@
import { h } from 'preact';
export function Icons() { export function Icons() {
return ( return (
<svg <svg
@@ -109,6 +107,30 @@ export function Icons() {
<symbol id="search" viewBox="0 0 24 24"> <symbol id="search" viewBox="0 0 24 24">
<path d="M9.5,3A6.5,6.5 0 0,1 16,9.5C16,11.11 15.41,12.59 14.44,13.73L14.71,14H15.5L20.5,19L19,20.5L14,15.5V14.71L13.73,14.44C12.59,15.41 11.11,16 9.5,16A6.5,6.5 0 0,1 3,9.5A6.5,6.5 0 0,1 9.5,3M9.5,5C7,5 5,7 5,9.5C5,12 7,14 9.5,14C12,14 14,12 14,9.5C14,7 12,5 9.5,5Z" /> <path d="M9.5,3A6.5,6.5 0 0,1 16,9.5C16,11.11 15.41,12.59 14.44,13.73L14.71,14H15.5L20.5,19L19,20.5L14,15.5V14.71L13.73,14.44C12.59,15.41 11.11,16 9.5,16A6.5,6.5 0 0,1 3,9.5A6.5,6.5 0 0,1 9.5,3M9.5,5C7,5 5,7 5,9.5C5,12 7,14 9.5,14C12,14 14,12 14,9.5C14,7 12,5 9.5,5Z" />
</symbol> </symbol>
<symbol id="copy" viewBox="0 0 24 24">
<path d="M19 21H8V7h11m0-2H8a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h11a2 2 0 0 0 2-2V7a2 2 0 0 0-2-2m-3-4H4a2 2 0 0 0-2 2v14h2V3h12V1Z" />
</symbol>
<symbol id="trash" viewBox="0 0 24 24">
<path d="M9 3v1H4v2h1v13a2 2 0 0 0 2 2h10a2 2 0 0 0 2-2V6h1V4h-5V3H9M7 6h10v13H7V6m2 2v9h2V8H9m4 0v9h2V8h-2Z" />
</symbol>
<symbol id="view-grid" viewBox="0 0 24 24">
<path d="M3 11h8V3H3m0 18h8v-8H3m10 8h8v-8h-8m0-10v8h8V3" />
</symbol>
<symbol id="view-list" viewBox="0 0 24 24">
<path d="M9,5V9H21V5M9,19H21V15H9M9,14H21V10H9M4,9H8V5H4M4,19H8V15H4M4,14H8V10H4V14Z" />
</symbol>
<symbol id="eye" viewBox="0 0 24 24">
<path d="M12 9a3 3 0 0 0-3 3 3 3 0 0 0 3 3 3 3 0 0 0 3-3 3 3 0 0 0-3-3m0 8a5 5 0 0 1-5-5 5 5 0 0 1 5-5 5 5 0 0 1 5 5 5 5 0 0 1-5 5m0-12.5C7 4.5 2.73 7.61 1 12c1.73 4.39 6 7.5 11 7.5s9.27-3.11 11-7.5c-1.73-4.39-6-7.5-11-7.5Z" />
</symbol>
<symbol id="eye-striked" viewBox="0 0 24 24">
<path d="M11.83 9 15 12.16V12a3 3 0 0 0-3-3h-.17m-4.3.8 1.55 1.55c-.05.21-.08.42-.08.65a3 3 0 0 0 3 3c.22 0 .44-.03.65-.08l1.55 1.55c-.67.33-1.41.53-2.2.53a5 5 0 0 1-5-5c0-.79.2-1.53.53-2.2M2 4.27l2.28 2.28.45.45C3.08 8.3 1.78 10 1 12c1.73 4.39 6 7.5 11 7.5 1.55 0 3.03-.3 4.38-.84l.43.42L19.73 22 21 20.73 3.27 3M12 7a5 5 0 0 1 5 5c0 .64-.13 1.26-.36 1.82l2.93 2.93c1.5-1.25 2.7-2.89 3.43-4.75-1.73-4.39-6-7.5-11-7.5-1.4 0-2.74.25-4 .7l2.17 2.15C10.74 7.13 11.35 7 12 7Z" />
</symbol>
<symbol id="check-circle" viewBox="0 0 24 24">
<path d="M12 2C6.5 2 2 6.5 2 12S6.5 22 12 22 22 17.5 22 12 17.5 2 12 2M10 17L5 12L6.41 10.59L10 14.17L17.59 6.58L19 8L10 17Z" />
</symbol>
<symbol id="fork" viewBox="0 0 24 24">
<path d="M13 14c-3.36 0-4.46 1.35-4.82 2.24C9.25 16.7 10 17.76 10 19a3 3 0 0 1-3 3 3 3 0 0 1-3-3c0-1.31.83-2.42 2-2.83V7.83A2.99 2.99 0 0 1 4 5a3 3 0 0 1 3-3 3 3 0 0 1 3 3c0 1.31-.83 2.42-2 2.83v5.29c.88-.65 2.16-1.12 4-1.12 2.67 0 3.56-1.34 3.85-2.23A3.006 3.006 0 0 1 14 7a3 3 0 0 1 3-3 3 3 0 0 1 3 3c0 1.34-.88 2.5-2.09 2.86C17.65 11.29 16.68 14 13 14m-6 4a1 1 0 0 0-1 1 1 1 0 0 0 1 1 1 1 0 0 0 1-1 1 1 0 0 0-1-1M7 4a1 1 0 0 0-1 1 1 1 0 0 0 1 1 1 1 0 0 0 1-1 1 1 0 0 0-1-1m10 2a1 1 0 0 0-1 1 1 1 0 0 0 1 1 1 1 0 0 0 1-1 1 1 0 0 0-1-1Z" />
</symbol>
<symbol id="loader-icon" viewBox="0 0 44 44"> <symbol id="loader-icon" viewBox="0 0 44 44">
{/* By Sam Herbert (@sherb), for everyone. More http://goo.gl/7AJzbL */} {/* By Sam Herbert (@sherb), for everyone. More http://goo.gl/7AJzbL */}
<g fill="none" fillRule="evenodd" strokeWidth={10}> <g fill="none" fillRule="evenodd" strokeWidth={10}>
@@ -162,3 +184,17 @@ export function Icons() {
</svg> </svg>
); );
} }
export const Icon = ({ name, color = 'currentColor', size, ...rest }) => {
return (
<svg
xmlns="http://www.w3.org/2000/svg"
width={size}
height={size}
style={{ fill: color }}
{...rest}
>
<use xlinkHref={`#${name}`} />
</svg>
);
};

View File

@@ -1,14 +1,18 @@
import { h } from 'preact'; import { h } from 'preact';
import { getHumanDate } from '../utils'; import { getHumanDate } from '../utils';
import Modal from './Modal'; import Modal from './Modal';
import { HStack, Stack } from './Stack';
import { Icon } from './Icons';
export function ItemTile({ export function ItemTile({
item, item,
onClick, onClick,
onForkBtnClick, onForkBtnClick,
onRemoveBtnClick, onRemoveBtnClick,
onToggleVisibilityBtnClick,
focusable, focusable,
inline inline,
hasOptions = true
}) { }) {
return ( return (
<div <div
@@ -27,6 +31,13 @@ export function ItemTile({
aria-label="Creates a duplicate of this creation (Ctrl/⌘ + F)" aria-label="Creates a duplicate of this creation (Ctrl/⌘ + F)"
onClick={onForkBtnClick} onClick={onForkBtnClick}
> >
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
fill="currentColor"
>
<path d="M13 14c-3.36 0-4.46 1.35-4.82 2.24C9.25 16.7 10 17.76 10 19a3 3 0 0 1-3 3 3 3 0 0 1-3-3c0-1.31.83-2.42 2-2.83V7.83A2.99 2.99 0 0 1 4 5a3 3 0 0 1 3-3 3 3 0 0 1 3 3c0 1.31-.83 2.42-2 2.83v5.29c.88-.65 2.16-1.12 4-1.12 2.67 0 3.56-1.34 3.85-2.23A3.006 3.006 0 0 1 14 7a3 3 0 0 1 3-3 3 3 0 0 1 3 3c0 1.34-.88 2.5-2.09 2.86C17.65 11.29 16.68 14 13 14m-6 4a1 1 0 0 0-1 1 1 1 0 0 0 1 1 1 1 0 0 0 1-1 1 1 0 0 0-1-1M7 4a1 1 0 0 0-1 1 1 1 0 0 0 1 1 1 1 0 0 0 1-1 1 1 0 0 0-1-1m10 2a1 1 0 0 0-1 1 1 1 0 0 0 1 1 1 1 0 0 0 1-1 1 1 0 0 0-1-1Z" />
</svg>
Fork<span class="show-when-selected">(Ctrl/ + F)</span> Fork<span class="show-when-selected">(Ctrl/ + F)</span>
</button> </button>
) : null} ) : null}
@@ -36,13 +47,19 @@ export function ItemTile({
aria-label="Remove" aria-label="Remove"
onClick={onRemoveBtnClick} onClick={onRemoveBtnClick}
> >
X <svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
fill="currentColor"
>
<path d="M9 3v1H4v2h1v13a2 2 0 0 0 2 2h10a2 2 0 0 0 2-2V6h1V4h-5V3H9M7 6h10v13H7V6m2 2v9h2V8H9m4 0v9h2V8h-2Z" />
</svg>
</button> </button>
) : null} ) : null}
</div> </div>
<div className="flex flex-v-center"> <div className="flex flex-v-center">
{item.img ? ( {item.img ? (
<div> <div class="d-f">
<img <img
class="saved-item-tile__img" class="saved-item-tile__img"
height="40" height="40"
@@ -66,12 +83,32 @@ export function ItemTile({
</div> </div>
) : null} ) : null}
</div> </div>
{item.updatedOn ? ( {hasOptions && (
<div class="saved-item-tile__meta"> <div class="saved-item-tile__meta">
<HStack justify="space-between">
<div>
{item.updatedOn ? (
<>
Last updated:{' '} Last updated:{' '}
<time dateTime={item.updatedOn}>{getHumanDate(item.updatedOn)}</time> <time dateTime={item.updatedOn}>
</div> {getHumanDate(item.updatedOn)}
</time>
</>
) : null} ) : null}
</div> </div>
<div>
<Stack gap={1} align="center">
<Icon
size="16"
color="currentColor"
name={item.isPublic ? 'eye' : 'eye-striked'}
/>
{item.isPublic ? 'Public' : ''}
</Stack>
</div>
</HStack>
</div>
)}
</div>
); );
} }

52
src/components/Loader.jsx Normal file
View File

@@ -0,0 +1,52 @@
export function Loader({ height, noMargin, leftMargin }) {
return (
<svg
viewBox="0 0 166 166"
height={height || '1.6em'}
style={{
margin: noMargin
? null
: leftMargin
? ` 0 0 0 ${leftMargin}`
: '0 0.8rem'
}}
class="new-loader"
>
<g fill="none" fillRule="evenodd">
<path
d="M83 166c-45.84 0-83-37.16-83-83S37.16 0 83 0s83 37.16 83 83-37.16 83-83 83zm0-29c29.823 0 54-24.177 54-54s-24.177-54-54-54-54 24.177-54 54 24.177 54 54 54z"
fill="currentColor"
style={{ opacity: 0.2 }}
/>
<path
d="M137.008 83H137c0-29.823-24.177-54-54-54S29 53.177 29 83h-.008c.005.166.008.333.008.5C29 91.508 22.508 98 14.5 98S0 91.508 0 83.5c0-.167.003-.334.008-.5H0C0 37.16 37.16 0 83 0s83 37.16 83 83h-.008c.005.166.008.333.008.5 0 8.008-6.492 14.5-14.5 14.5S137 91.508 137 83.5c0-.167.003-.334.008-.5z"
fill="currentColor"
/>
<animateTransform
attributeName="transform"
attributeType="XML"
type="rotate"
dur="1s"
from="0 83 83"
to="360 83 83"
repeatCount="indefinite"
/>
</g>
</svg>
);
}
export function LoaderWithText({ children }) {
return (
<div
style={{
display: 'flex',
justifyContent: 'center',
alignItems: 'center',
margin: '2rem 1rem'
}}
>
<Loader /> {children}
</div>
);
}

View File

@@ -54,21 +54,8 @@ export default class Login extends Component {
Login with Google Login with Google
</button> </button>
</p> </p>
<p class="mb-2">
<button <p>Join a community of 70,000+ Developers</p>
type="button"
onClick={this.login.bind(this)}
class="social-login-btn social-login-btn--facebook btn btn-icon btn--big full-width hint--right hint--always"
data-auth-provider="facebook"
data-hint="You logged in with Facebook last time"
>
<svg>
<use xlinkHref="#fb-icon" />
</svg>
Login with Facebook (deprecated)
</button>
</p>
<p>Join a community of 50,000+ Developers</p>
</div> </div>
</div> </div>
); );

View File

@@ -2,16 +2,32 @@ import { h } from 'preact';
import { Button } from './common'; import { Button } from './common';
import { Trans, NumberFormat, t } from '@lingui/macro'; import { Trans, NumberFormat, t } from '@lingui/macro';
import { I18n } from '@lingui/react'; import { I18n } from '@lingui/react';
import { ProBadge } from './ProBadge';
import { HStack, Stack } from './Stack';
import { Icon } from './Icons';
const DEFAULT_PROFILE_IMG = const DEFAULT_PROFILE_IMG =
"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath fill='%23ccc' d='M12,19.2C9.5,19.2 7.29,17.92 6,16C6.03,14 10,12.9 12,12.9C14,12.9 17.97,14 18,16C16.71,17.92 14.5,19.2 12,19.2M12,5A3,3 0 0,1 15,8A3,3 0 0,1 12,11A3,3 0 0,1 9,8A3,3 0 0,1 12,5M12,2A10,10 0 0,0 2,12A10,10 0 0,0 12,22A10,10 0 0,0 22,12C22,6.47 17.5,2 12,2Z'/%3E%3C/svg%3E"; "data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath fill='%23ccc' d='M12,19.2C9.5,19.2 7.29,17.92 6,16C6.03,14 10,12.9 12,12.9C14,12.9 17.97,14 18,16C16.71,17.92 14.5,19.2 12,19.2M12,5A3,3 0 0,1 15,8A3,3 0 0,1 12,11A3,3 0 0,1 9,8A3,3 0 0,1 12,5M12,2A10,10 0 0,0 2,12A10,10 0 0,0 12,22A10,10 0 0,0 22,12C22,6.47 17.5,2 12,2Z'/%3E%3C/svg%3E";
export function MainHeader(props) { export function MainHeader({
user,
currentItem,
titleInputBlurHandler,
runBtnClickHandler,
assetsBtnHandler,
isFileMode,
onItemFork,
...props
}) {
const isAutoPreviewOn = const isAutoPreviewOn =
window.forcedSettings.autoPreview !== undefined window.forcedSettings.autoPreview !== undefined
? window.forcedSettings ? window.forcedSettings
: props.isAutoPreviewOn; : props.isAutoPreviewOn;
const isNotMine =
currentItem.createdBy && user?.uid !== currentItem.createdBy;
// console.log(33, currentItem, user?.uid);
return ( return (
<I18n> <I18n>
{({ i18n }) => ( {({ i18n }) => (
@@ -21,15 +37,15 @@ export function MainHeader(props) {
id="titleInput" id="titleInput"
title="Click to edit" title="Click to edit"
class="item-title-input" class="item-title-input"
value={props.title} value={currentItem.title}
onBlur={props.titleInputBlurHandler} onBlur={titleInputBlurHandler}
/> />
<div class="main-header__btn-wrap flex flex-v-center"> <div class="main-header__btn-wrap flex flex-v-center">
{!isAutoPreviewOn && ( {!isAutoPreviewOn && (
<button <button
class="btn btn btn--dark flex flex-v-center hint--rounded hint--bottom-left" class="btn btn btn--dark flex flex-v-center hint--rounded hint--bottom-left"
aria-label={i18n._(t`Run preview (Ctrl/⌘ + Shift + 5)`)} aria-label={i18n._(t`Run preview (Ctrl/⌘ + Shift + 5)`)}
onClick={props.runBtnClickHandler} onClick={runBtnClickHandler}
> >
<svg> <svg>
<use xlinkHref="#play-icon" /> <use xlinkHref="#play-icon" />
@@ -37,8 +53,17 @@ export function MainHeader(props) {
<Trans>Run</Trans> <Trans>Run</Trans>
</button> </button>
)} )}
<Button
{!props.isFileMode && ( onClick={assetsBtnHandler}
data-event-category="ui"
data-event-action="addLibraryButtonClick"
data-testid="addLibraryButton"
class="btn btn--dark hint--rounded hint--bottom-left"
aria-label={i18n._(t`Upload/Use assets`)}
>
<Trans>Assets</Trans>
</Button>
{!isFileMode && (
<Button <Button
onClick={props.addLibraryBtnHandler} onClick={props.addLibraryBtnHandler}
data-event-category="ui" data-event-category="ui"
@@ -60,6 +85,38 @@ export function MainHeader(props) {
</Button> </Button>
)} )}
{!isNotMine && (
<button
class="btn btn--dark hint--bottom-left"
aria-label={i18n._(t`Share this creation publicly`)}
data-testid="newButton"
onClick={props.shareBtnHandler}
>
<svg
viewBox="0 0 24 24"
style={{
fill: currentItem.isPublic ? 'limegreen' : 'currentColor'
}}
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
>
<path d="M18 16.08C17.24 16.08 16.56 16.38 16.04 16.85L8.91 12.7C8.96 12.47 9 12.24 9 12S8.96 11.53 8.91 11.3L15.96 7.19C16.5 7.69 17.21 8 18 8C19.66 8 21 6.66 21 5S19.66 2 18 2 15 3.34 15 5C15 5.24 15.04 5.47 15.09 5.7L8.04 9.81C7.5 9.31 6.79 9 6 9C4.34 9 3 10.34 3 12S4.34 15 6 15C6.79 15 7.5 14.69 8.04 14.19L15.16 18.34C15.11 18.55 15.08 18.77 15.08 19C15.08 20.61 16.39 21.91 18 21.91S20.92 20.61 20.92 19C20.92 17.39 19.61 16.08 18 16.08M18 4C18.55 4 19 4.45 19 5S18.55 6 18 6 17 5.55 17 5 17.45 4 18 4M6 13C5.45 13 5 12.55 5 12S5.45 11 6 11 7 11.45 7 12 6.55 13 6 13M18 20C17.45 20 17 19.55 17 19S17.45 18 18 18 19 18.45 19 19 18.55 20 18 20Z" />
</svg>
{currentItem.isPublic ? null : <Trans>Share</Trans>}
</button>
)}
<button
class="btn btn--dark hint--bottom-left"
aria-label={i18n._(t`Fork this creation`)}
data-testid="headerForkButton"
onClick={onItemFork}
>
<Icon name="fork" />
<Trans>Fork</Trans>
</button>
<button <button
class="btn btn--dark hint--rounded hint--bottom-left" class="btn btn--dark hint--rounded hint--bottom-left"
aria-label={i18n._(t`Start a new creation`)} aria-label={i18n._(t`Start a new creation`)}
@@ -71,6 +128,8 @@ export function MainHeader(props) {
</svg> </svg>
<Trans>New</Trans> <Trans>New</Trans>
</button> </button>
{!isNotMine && (
<button <button
id="saveBtn" id="saveBtn"
class={`btn btn--dark hint--rounded hint--bottom-left ${ class={`btn btn--dark hint--rounded hint--bottom-left ${
@@ -87,6 +146,7 @@ export function MainHeader(props) {
</svg> </svg>
<Trans>Save</Trans> <Trans>Save</Trans>
</button> </button>
)}
<button <button
id="openItemsBtn" id="openItemsBtn"
class={`btn btn--dark hint--rounded hint--bottom-left ${ class={`btn btn--dark hint--rounded hint--bottom-left ${
@@ -103,13 +163,13 @@ export function MainHeader(props) {
</svg> </svg>
<Trans>Open</Trans> <Trans>Open</Trans>
</button> </button>
{!props.user ? ( {!user ? (
<Button <Button
onClick={props.loginBtnHandler} onClick={props.loginBtnHandler}
data-event-category="ui" data-event-category="ui"
data-event-action="loginButtonClick" data-event-action="loginButtonClick"
data-testid="loginButton" data-testid="loginButton"
class="btn btn--dark hint--rounded hint--bottom-left" class="btn btn--dark"
> >
<Trans>Login/Signup</Trans> <Trans>Login/Signup</Trans>
</Button> </Button>
@@ -121,14 +181,15 @@ export function MainHeader(props) {
aria-label={i18n._(t`See profile or Logout`)} aria-label={i18n._(t`See profile or Logout`)}
class="btn--dark hint--rounded hint--bottom-left" class="btn--dark hint--rounded hint--bottom-left"
> >
<HStack gap={1}>
<img <img
id="headerAvatarImg" id="headerAvatarImg"
width="20" width="20"
src={ src={user ? user.photoURL || DEFAULT_PROFILE_IMG : ''}
props.user ? props.user.photoURL || DEFAULT_PROFILE_IMG : ''
}
class="main-header__avatar-img" class="main-header__avatar-img"
/> />
{user?.isPro ? <ProBadge /> : null}
</HStack>
</Button> </Button>
)} )}
</div> </div>

View File

@@ -77,12 +77,16 @@ const Modal = ({
if (!show) return null; if (!show) return null;
return ( return (
<Portal into={`body`}> <Portal into={`#portal`}>
<>
{/* <div class="modal-overlay" /> */}
<div <div
role="dialog" role="dialog"
class={`${extraClasses || ''} modal is-modal-visible ${ class={`${extraClasses || ''} modal is-modal-visible ${
small ? 'modal--small' : '' small ? 'modal--small' : ''
}`} }
${noOverlay ? 'modal--no-overlay' : ''}
`}
ref={overlayRef} ref={overlayRef}
onClick={onOverlayClick} onClick={onOverlayClick}
> >
@@ -94,14 +98,26 @@ const Modal = ({
aria-label="Close modal" aria-label="Close modal"
data-testid="closeModalButton" data-testid="closeModalButton"
title="Close" title="Close"
class="js-modal__close-btn modal__close-btn" class="js-modal__close-btn dialog__close-btn modal__close-btn"
> >
Close <svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth="3.5"
strokeLinecap="round"
strokeLinejoin="round"
>
<line x1="18" y1="6" x2="6" y2="18"></line>
<line x1="6" y1="6" x2="18" y2="18"></line>
</svg>
</button> </button>
)} )}
{children} {children}
</div> </div>
</div> </div>
</>
</Portal> </Portal>
); );
}; };

View File

@@ -1,42 +1,47 @@
import { h } from 'preact'; import { h } from 'preact';
import Modal from './Modal.jsx'; import Modal from './Modal.jsx';
import { Stack } from './Stack.jsx';
export function OnboardingModal(props) { export function OnboardingModal(props) {
return ( return (
<Modal show={props.show} closeHandler={props.closeHandler}> <Modal show={props.show} closeHandler={props.closeHandler}>
<div class="tac"> <Stack gap={3} justify="center" align="center">
<svg width="130px" height="50px" aria-hidden="true"> <svg width="83" height="32" aria-hidden="true">
<use xlinkHref="#logo" /> <use xlinkHref="#logo" />
</svg> </svg>
<h1 style="margin-top:20px">Welcome to Web Maker</h1> <h1>Welcome to Web Maker</h1>
</div> </Stack>
<div class="flex--desk" style="margin-top:40px;"> <div
<div class="onboard-step show-when-app hide-on-mobile"> class="flex--desk"
<div class="tac"> style="margin-top:40px; display:grid; grid-template-columns:1fr 1fr"
>
<div class="onboard-step onboard-step--full-width show-when-app hide-on-mobile">
<Stack gap={1} align="center">
<svg class="onboard-step__icon" viewBox="0 0 24 24"> <svg class="onboard-step__icon" viewBox="0 0 24 24">
<path d="M13.64,21.97C13.14,22.21 12.54,22 12.31,21.5L10.13,16.76L7.62,18.78C7.45,18.92 7.24,19 7,19A1,1 0 0,1 6,18V3A1,1 0 0,1 7,2C7.24,2 7.47,2.09 7.64,2.23L7.65,2.22L19.14,11.86C19.57,12.22 19.62,12.85 19.27,13.27C19.12,13.45 18.91,13.57 18.7,13.61L15.54,14.23L17.74,18.96C18,19.46 17.76,20.05 17.26,20.28L13.64,21.97Z" /> <path d="M13.64,21.97C13.14,22.21 12.54,22 12.31,21.5L10.13,16.76L7.62,18.78C7.45,18.92 7.24,19 7,19A1,1 0 0,1 6,18V3A1,1 0 0,1 7,2C7.24,2 7.47,2.09 7.64,2.23L7.65,2.22L19.14,11.86C19.57,12.22 19.62,12.85 19.27,13.27C19.12,13.45 18.91,13.57 18.7,13.61L15.54,14.23L17.74,18.96C18,19.46 17.76,20.05 17.26,20.28L13.64,21.97Z" />
</svg> </svg>
</div>
<p> <p>
Open Web Maker anytime by visiting <a>https://webmaker.app/app/</a>{' '} Open Web Maker anytime by visiting{' '}
- Even when you are offline! It just works! 😱{' '} <a>https://webmaker.app/create/</a> - Even when you are offline!
<strong>Drag the following bookmarklet</strong> on your bookmark bar It just works! 😱 <strong>Drag the following bookmarklet</strong>{' '}
to create a quick access shortcut: on your bookmark bar to create a quick access shortcut:
<a class="ml-1 bookmarklet" href="https://webmaker.app/app/"> <a class="ml-1 bookmarklet" href="https://webmaker.app/create/">
<svg width="20" height="20" aria-hidden="true"> <svg width="20" height="20" aria-hidden="true">
<use xlinkHref="#logo" /> <use xlinkHref="#logo" />
</svg> </svg>
Web Maker Web Maker
</a> </a>
</p> </p>
</Stack>
</div> </div>
<div class="onboard-step show-when-extension"> <div class="onboard-step onboard-step--full-width show-when-extension">
<div class="tac"> <Stack gap={1} align="center">
<svg class="onboard-step__icon" viewBox="0 0 24 24"> <svg class="onboard-step__icon" viewBox="0 0 24 24">
<path d="M13.64,21.97C13.14,22.21 12.54,22 12.31,21.5L10.13,16.76L7.62,18.78C7.45,18.92 7.24,19 7,19A1,1 0 0,1 6,18V3A1,1 0 0,1 7,2C7.24,2 7.47,2.09 7.64,2.23L7.65,2.22L19.14,11.86C19.57,12.22 19.62,12.85 19.27,13.27C19.12,13.45 18.91,13.57 18.7,13.61L15.54,14.23L17.74,18.96C18,19.46 17.76,20.05 17.26,20.28L13.64,21.97Z" /> <path d="M13.64,21.97C13.14,22.21 12.54,22 12.31,21.5L10.13,16.76L7.62,18.78C7.45,18.92 7.24,19 7,19A1,1 0 0,1 6,18V3A1,1 0 0,1 7,2C7.24,2 7.47,2.09 7.64,2.23L7.65,2.22L19.14,11.86C19.57,12.22 19.62,12.85 19.27,13.27C19.12,13.45 18.91,13.57 18.7,13.61L15.54,14.23L17.74,18.96C18,19.46 17.76,20.05 17.26,20.28L13.64,21.97Z" />
</svg> </svg>
</div>
<p> <p>
Open Web Maker anytime by clicking the Open Web Maker anytime by clicking the
<svg class="relative" style="top:5px;" width="40" height="30"> <svg class="relative" style="top:5px;" width="40" height="30">
@@ -44,13 +49,15 @@ export function OnboardingModal(props) {
</svg>{' '} </svg>{' '}
button in top-right side of your browser. button in top-right side of your browser.
</p> </p>
</Stack>
</div> </div>
<div class="onboard-step"> <div class="onboard-step">
<div class="tac"> <Stack gap={1} align="center">
<svg class="onboard-step__icon" viewBox="0 0 24 24"> <svg class="onboard-step__icon" viewBox="0 0 24 24">
<use xlinkHref="#settings-icon" /> <use xlinkHref="#settings-icon" />
</svg> </svg>
</div>
<p> <p>
Configure and customize settings by clicking the gear icon ( Configure and customize settings by clicking the gear icon (
<svg <svg
@@ -61,13 +68,14 @@ export function OnboardingModal(props) {
</svg> </svg>
) in bottom right of the app. ) in bottom right of the app.
</p> </p>
</Stack>
</div> </div>
<div class="onboard-step"> <div class="onboard-step">
<div class="tac"> <Stack gap={1} align="center">
<svg class="onboard-step__icon" style="stroke-width:0.3px;"> <svg class="onboard-step__icon" style="stroke-width:0.3px;">
<use xlinkHref="#twitter-icon" /> <use xlinkHref="#twitter-icon" />
</svg> </svg>
</div>
<p> <p>
Follow{' '} Follow{' '}
<a <a
@@ -79,6 +87,7 @@ export function OnboardingModal(props) {
</a>{' '} </a>{' '}
to know about the new upcoming features! to know about the new upcoming features!
</p> </p>
</Stack>
</div> </div>
</div> </div>
@@ -95,11 +104,11 @@ export function OnboardingModal(props) {
. .
</p> </p>
<p class="tac"> <Stack justify="center">
<button class="btn btn--primary" onClick={props.closeHandler}> <button class="btn btn--primary" onClick={props.closeHandler}>
Lets start! Lets start!
</button> </button>
</p> </Stack>
</Modal> </Modal>
); );
} }

31
src/components/Panel.jsx Normal file
View File

@@ -0,0 +1,31 @@
import { forwardRef } from 'preact/compat';
export const Panel = forwardRef(function Panel(
{
classes = '',
padding = '2rem',
fullWidth = true,
fullHeight = false,
glowing = false,
topFocus = false,
onlyBorder = false,
children
},
ref
) {
return (
<div
ref={ref}
style={{
padding: padding,
width: fullWidth ? '100%' : 'auto',
height: fullHeight ? '100%' : 'auto'
}}
className={`panel ${classes} ${glowing && 'panelGlowing'} ${
topFocus && 'panelTopFocus'
} ${onlyBorder && 'panelOnlyBorder'}`}
>
{children}
</div>
);
});

157
src/components/Pro.jsx Normal file
View File

@@ -0,0 +1,157 @@
import { useEffect, useState } from 'preact/hooks';
import { ProBadge } from './ProBadge';
import { HStack, Stack, VStack } from './Stack';
import Switch from './Switch';
import { alertsService } from '../notifications';
import { A, Button } from './common';
import { useCheckout } from '../hooks/useCheckout';
import { Text } from './Text';
import { Icon } from './Icons';
import { showConfetti } from '../utils';
import { trackEvent } from '../analytics';
const checkoutIds = {
monthly: '1601bc00-9623-435b-b1de-2a70a2445c13',
annual: 'aae95d78-05c8-46f5-b11e-2d40ddd211d3',
generic: 'd1d2da1a-ae8f-4222-bbaf-6e07da8954bf' //'f8c64e50-7734-438b-a122-3510156f14ed'
};
export function Pro({ user, onLoginClick, onBuyFromExtensionClick }) {
const hasCheckoutLoaded = useCheckout();
const [isAnnual, setIsAnnual] = useState(true);
useEffect(() => {
if (hasCheckoutLoaded) {
window.LemonSqueezy.Setup({
eventHandler: e => {
console.log('eventHandler', e);
if (e.event === 'Checkout.Success') {
showConfetti(2);
alertsService.add(
'You are now PRO! 🎉. Reloading your superpowers...'
);
setTimeout(() => {
window.location.reload();
}, 2000);
}
}
});
window.LemonSqueezy.Refresh();
}
}, [hasCheckoutLoaded]);
let upgradeActionEl;
if (window.IS_EXTENSION) {
upgradeActionEl = (
<button
type="button"
className="btn btn--pro jc-c"
onClick={onBuyFromExtensionClick}
>
Upgrade to PRO
</button>
);
} else if (window.user) {
upgradeActionEl = (
<A
class="btn btn--pro lemonsqueezy-button d-f jc-c ai-c"
style="gap:0.2rem"
href={`https://web-maker.lemonsqueezy.com/checkout/buy/${checkoutIds.generic}?embed=1&checkout[custom][userId]=${user?.uid}`}
onClick={() => {
trackEvent('ui', 'buyBtnClick');
}}
>
Go PRO
</A>
);
} else {
upgradeActionEl = (
<button
type="button"
className="btn btn--pro jc-c"
onClick={onLoginClick}
>
Login & upgrade to PRO
</button>
);
}
return (
<VStack gap={2} align="stretch">
{/* <Stack justify="center">
<Switch
labels={['Monthly', 'Annually']}
checked={isAnnual}
showBothLabels={true}
onChange={e => {
setIsAnnual(e.target.checked);
}}
/>
</Stack> */}
<Stack gap={2} align="stretch">
<Card
price="Free"
subTitle="&nbsp;"
name="Starter"
features={[
'Unlimited private creations',
'1 Public creation',
'2 Files mode creations'
]}
/>
<Card
bg="#674dad"
price={'Starting $6/mo'}
subTitle="Annual & One-time pricing available"
name="Pro"
action={upgradeActionEl}
features={[
'Unlimited private creations',
'Unlimited public creations',
'Unlimited files mode creations',
'Asset hosting',
'Priority support',
'No Ads'
]}
/>
</Stack>
<Stack justify="center">
<Text tag="p" appearance="secondary">
30 days refund policy if not satisfied.
</Text>
</Stack>
</VStack>
);
}
const Card = ({ bg, name, price, subTitle, action, features }) => {
return (
<div class="plan-card" style={{ background: bg }}>
<VStack gap={2} align="stretch" justify="flex-start">
<VStack gap={0} align="stretch" justify="flex-start">
<Text transform="uppercase" weight="600">
{name}
</Text>
<Text size="5" weight="800" appearance="primary">
{price}
</Text>
<Text size="1" weight="400">
{subTitle}
</Text>
</VStack>
{action}
{!action && (
<a class="btn" aria-hidden="true" style="visibility:hidden">
s
</a>
)}
<VStack gap={1} align="flex-start">
{features.map(f => (
<HStack gap={1} align="center">
<Icon name="check-circle" size="1.4rem" />
{f}
</HStack>
))}
</VStack>
</VStack>
</div>
);
};

View File

@@ -0,0 +1,3 @@
export const ProBadge = () => {
return <div className="badge pro-badge">PRO</div>;
};

View File

@@ -0,0 +1,31 @@
import { h } from 'preact';
import Modal from './Modal';
import { Text } from './Text';
import { Stack } from './Stack';
export function ProOnAppModal({ show, closeHandler }) {
return (
<Modal extraClasses="" show={show} closeHandler={closeHandler}>
<div class="ta">
<Text appearance="primary" tag="h1" size="4" weight="700">
Upgrade on Web app
</Text>
<p className="para">
Upgrading to PRO from the extension is not available yet. You can
instead upgrade on the Web app, come back here, refresh and get all
the PRO features right here!
</p>
<Stack justify="center">
<a
href="https://webmaker.app/create"
className="btn btn--pro"
target="_blank"
>
Upgrade on Web app
</a>
</Stack>
</div>
</Modal>
);
}

View File

@@ -1,30 +1,158 @@
import { h } from 'preact'; import { useState, useEffect } from 'preact/hooks';
import { ProBadge } from './ProBadge';
import { HStack, Stack, VStack } from './Stack';
import { Panel } from './Panel';
import { Text } from './Text';
import { getHumanReadableDate } from '../utils';
import { LoaderWithText } from './Loader';
const DEFAULT_PROFILE_IMG = const DEFAULT_PROFILE_IMG =
"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath fill='%23ccc' d='M12,19.2C9.5,19.2 7.29,17.92 6,16C6.03,14 10,12.9 12,12.9C14,12.9 17.97,14 18,16C16.71,17.92 14.5,19.2 12,19.2M12,5A3,3 0 0,1 15,8A3,3 0 0,1 12,11A3,3 0 0,1 9,8A3,3 0 0,1 12,5M12,2A10,10 0 0,0 2,12A10,10 0 0,0 12,22A10,10 0 0,0 22,12C22,6.47 17.5,2 12,2Z'/%3E%3C/svg%3E"; "data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath fill='%23ccc' d='M12,19.2C9.5,19.2 7.29,17.92 6,16C6.03,14 10,12.9 12,12.9C14,12.9 17.97,14 18,16C16.71,17.92 14.5,19.2 12,19.2M12,5A3,3 0 0,1 15,8A3,3 0 0,1 12,11A3,3 0 0,1 9,8A3,3 0 0,1 12,5M12,2A10,10 0 0,0 2,12A10,10 0 0,0 12,22A10,10 0 0,0 22,12C22,6.47 17.5,2 12,2Z'/%3E%3C/svg%3E";
export function Profile({ user, logoutBtnHandler }) { const Header = ({ user, logoutBtnHandler }) => {
return ( return (
<div class="tac"> <Stack gap={5}>
<Stack gap={2} align="center">
<img <img
height="80" height="80"
class="profile-modal__avatar-img" class={`profile-modal__avatar-img ${user?.isPro ? 'is-pro' : ''}`}
src={user ? user.photoURL || DEFAULT_PROFILE_IMG : ''} src={user ? user.photoURL || DEFAULT_PROFILE_IMG : ''}
id="profileAvatarImg" id="profileAvatarImg"
alt="Profile image" alt="Profile image"
/> />
<h3 id="profileUserName" class="mb-2">
<VStack gap={1} align="flex-start">
<h3
class={`profile-modal__name ${user?.isPro ? 's-pro' : ''}`}
id="profileUserName"
>
{user && user.displayName ? user.displayName : 'Anonymous Creator'} {user && user.displayName ? user.displayName : 'Anonymous Creator'}
</h3> </h3>
<p> {user.isPro && <ProBadge />}
</VStack>
</Stack>
<button <button
class="btn" class="btn btn--primary"
aria-label="Logout from your account" aria-label="Logout from your account"
onClick={logoutBtnHandler} onClick={logoutBtnHandler}
> >
Logout Logout
</button> </button>
</p> </Stack>
</div> );
};
export function Profile({ user, logoutBtnHandler }) {
const [currentSubscription, setCurrentSubscription] = useState(null);
const [isFetchingSubscription, setIsFetchingSubscription] = useState(false);
useEffect(() => {
if (user?.isPro) {
setIsFetchingSubscription(true);
window.db.getUserSubscriptionEvents(user.uid).then(events => {
setIsFetchingSubscription(false);
let creationEvent = events
.filter(
event =>
event.type === 'subscription_created' ||
event.type === 'order_created'
)
.sort((a, b) => b.timestamp.seconds - a.timestamp.seconds)
// remove order_created events which correspond to subscriptions (non lifetime orders)
.filter(
event =>
!(
event.type === 'order_created' &&
!event.data.data.attributes.first_order_item?.variant_name?.match(
/lifetime/i
)
)
)[0];
if (creationEvent) {
// log(creationEvent);
creationEvent.attributes = creationEvent.data.data.attributes;
setCurrentSubscription(creationEvent);
}
});
}
}, [user]);
return (
<VStack gap={4}>
<Header user={user} logoutBtnHandler={logoutBtnHandler} />
{window.user?.isPro && (
<Panel>
{isFetchingSubscription ? (
<LoaderWithText>Loading billing details...</LoaderWithText>
) : null}
{currentSubscription ? (
<VStack align="stretch" gap={1}>
<Text>
Plan:
<Text weight="700">
{' '}
Web Maker PRO (
{currentSubscription.attributes.variant_name ||
currentSubscription.attributes.first_order_item
?.variant_name}
)
</Text>
</Text>
<Text>
Subscription Status:{' '}
<Text weight="700">
{currentSubscription.attributes.status === 'paid'
? 'PRO for life ❤️'
: currentSubscription.attributes.status}
</Text>
</Text>
<Text>
Renews on:{' '}
<Text weight="700">
{currentSubscription.attributes.status === 'paid'
? 'Never ever'
: getHumanReadableDate(
currentSubscription.attributes.renews_at
)}
</Text>
</Text>
{currentSubscription.attributes.status === 'paid' ? null : (
<a
target="_blank"
href={currentSubscription.attributes.urls.customer_portal}
>
Cancel subscription
</a>
)}
{/* <a
target="_blank"
href={
currentSubscription.attributes.urls
.customer_portal_update_subscription
}
>
Link 2
</a>
<a
target="_blank"
href={currentSubscription.attributes.urls.update_payment_method}
>
Link 3
</a> */}
</VStack>
) : null}
</Panel>
)}
{user?.isPro && currentSubscription ? (
<img
class="profile-modal__panda"
src="assets/pro-panda.png"
width="300"
style="position:absolute;bottom:-3rem; right: -7rem;"
/>
) : null}
</VStack>
); );
} }

View File

@@ -157,18 +157,29 @@ export default function SavedItemPane({
> >
<button <button
onClick={onCloseIntent} onClick={onCloseIntent}
class="btn saved-items-pane__close-btn" class="btn dialog__close-btn saved-items-pane__close-btn"
id="js-saved-items-pane-close-btn" id="js-saved-items-pane-close-btn"
aria-label={i18n._(t`Close saved creations pane`)} aria-label={i18n._(t`Close saved creations pane`)}
> >
X <svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth="3.5"
strokeLinecap="round"
strokeLinejoin="round"
>
<line x1="18" y1="6" x2="6" y2="18"></line>
<line x1="6" y1="6" x2="18" y2="18"></line>
</svg>
</button> </button>
<div <div
class="flex flex-v-center" class="flex flex-v-center"
style="justify-content: space-between;" style="justify-content: space-between;"
> >
<h3> <h3>
<Trans>My Library ({filteredItems.length})</Trans> <Trans>My Library</Trans> ({filteredItems.length})
</h3> </h3>
<div> <div>
@@ -215,6 +226,9 @@ export default function SavedItemPane({
onClick={() => itemClickHandler(item)} onClick={() => itemClickHandler(item)}
onForkBtnClick={e => itemForkBtnClickHandler(item, e)} onForkBtnClick={e => itemForkBtnClickHandler(item, e)}
onRemoveBtnClick={e => itemRemoveBtnClickHandler(item, e)} onRemoveBtnClick={e => itemRemoveBtnClickHandler(item, e)}
onToggleVisibilityBtnClick={e =>
itemVisibilityToggleHandler(item, e)
}
/> />
))} ))}
{!items.length ? ( {!items.length ? (
@@ -224,7 +238,7 @@ export default function SavedItemPane({
</h2> </h2>
<img <img
style="max-width: 80%; opacity:0.4" style="max-width: 80%; opacity:0.4"
src="assets/empty.svg" src="./assets/empty.svg"
/> />
</div> </div>
) : null} ) : null}

149
src/components/Share.jsx Normal file
View File

@@ -0,0 +1,149 @@
import { useEffect, useState } from 'preact/hooks';
import { Skeleton } from './Skeleton';
import { HStack, Stack, VStack } from './Stack';
import Switch from './Switch';
import { itemService } from '../itemService';
import { alertsService } from '../notifications';
import { Button } from './common';
import { Icon } from './Icons';
import { Text } from './Text';
const FREE_PUBLIC_ITEM_COUNT = 1;
const BASE_URL = location.origin;
const TOGGLE_VISIBILITY_API =
/*!window.location.origin.includes('localhost')
? 'http://127.0.0.1:5001/web-maker-app/us-central1/toggleVisibility'
: */ 'https://togglevisibility-ajhkrtmkaq-uc.a.run.app';
export function Share({
user,
item,
onVisibilityChange,
onLoginBtnClick,
onProBtnClick
}) {
const [publicItemCount, setPublicItemCount] = useState();
useEffect(() => {
if (!user) return;
window.db.getPublicItemCount(user.uid).then(c => {
setPublicItemCount(c);
// console.log('public items', c);
});
}, []);
const [val, setVal] = useState(item.isPublic);
const onChange = async e => {
const newVal = e.target.checked;
setVal(newVal);
if (newVal) {
const token = await window.user.firebaseUser.getIdToken();
let res;
try {
res = await fetch(
`${TOGGLE_VISIBILITY_API}?token=${token}&itemId=${item.id}`
);
} catch (e) {
alertsService.add('Could not set visiblity to public');
setTimeout(() => {
setVal(!newVal);
}, 400);
return;
}
if (res.status >= 200 && res.status < 400) {
setPublicItemCount(publicItemCount + 1);
onVisibilityChange(true);
alertsService.add('Visiblity set to public');
} else {
alertsService.add('Could not set visiblity to public');
setTimeout(() => {
setVal(!newVal);
}, 400);
}
} else {
itemService.setItem(item.id, { isPublic: false });
setPublicItemCount(publicItemCount - 1);
onVisibilityChange(false);
alertsService.add('Visiblity set to private');
}
};
const copyUrl = () => {
navigator.clipboard.writeText(`${BASE_URL}/create/${item.id}`);
alertsService.add('URL copied to clipboard');
};
if (!user) {
return (
<HStack justify="center" gap={2}>
<Text>Login to share this creation publicly</Text>
<Button class="btn btn--primary" onClick={onLoginBtnClick}>
Login
</Button>
</HStack>
);
}
return (
<VStack gap={4} align="stretch">
<div style="min-width: 46ch">
<VStack gap={1} align="stretch">
<Switch
checked={val}
onChange={onChange}
labels={['Private', 'Public']}
>
Access
</Switch>
{item.isPublic && (
<p>
Public at{' '}
<a href={`${BASE_URL}/create/${item.id}`} target="_blank">
{BASE_URL}/create/{item.id}
</a>{' '}
<Button
class="btn btn--dark hint--bottom hint--rounded"
onClick={copyUrl}
aria-label="Copy"
>
<Icon name="copy" />
</Button>
</p>
)}
</VStack>
</div>
{!user?.isPro ? (
<VStack gap={1} align="stretch">
<p>
<HStack gap={3} justify="flex-start">
<span>Public creations available: {FREE_PUBLIC_ITEM_COUNT}</span>
<span>
Used:{' '}
{publicItemCount === undefined ? (
<Skeleton width="2ch" />
) : (
publicItemCount
)}
</span>
<span>
Left:{' '}
{publicItemCount === undefined ? (
<Skeleton width="2ch" />
) : (
Math.max(0, FREE_PUBLIC_ITEM_COUNT - publicItemCount)
)}
</span>
</HStack>
</p>
<p>
<HStack gap={1}>
<span>For unlimited public creations, </span>
<button onClick={onProBtnClick} class="btn btn--pro btn--small">
Upgrade to Pro
</button>
</HStack>
</p>
</VStack>
) : null}
</VStack>
);
}

View File

@@ -0,0 +1,3 @@
export const Skeleton = ({ width }) => {
return <div className="skeleton" style={{ width }}></div>;
};

54
src/components/Stack.jsx Normal file
View File

@@ -0,0 +1,54 @@
const gaps = [0, '0.5rem', '1rem', '1.5rem', '3rem', '5rem'];
const Stack = function ({
classes = '',
gap = 0,
align = 'center',
justify = 'flex-start',
direction = 'horizontal',
fullWidth = false,
fullHeight = false,
wrap,
children
}) {
return (
<div
style={{
display: 'flex',
gap: gaps[gap] || gap,
alignItems: align,
justifyContent: justify,
flexDirection: direction === 'vertical' ? 'column' : 'row',
height: fullHeight ? '100%' : null,
width: fullWidth ? '100%' : null,
flexWrap: wrap ? 'wrap' : null
}}
class={`stack ${classes}`}
>
{children}
</div>
);
};
const VStack = props => {
return <Stack {...props} direction="vertical" />;
};
const HStack = props => {
return (
<Stack
classes={`hstack ${props.responsive ? 'hstack--responsive' : ''}`}
{...props}
/>
);
};
const Spacer = () => {
return (
<>
<div style={{ flexGrow: '1' }}></div>
</>
);
};
export { Stack, VStack, HStack, Spacer };

View File

@@ -1,5 +1,3 @@
import { h } from 'preact';
export default function Switch({ export default function Switch({
checked, checked,
onChange, onChange,

68
src/components/Text.jsx Normal file
View File

@@ -0,0 +1,68 @@
import { forwardRef } from 'preact/compat';
const appearanceStyles = {
normal: {
color: 'var(--color-text)'
},
primary: {
color: 'var(--color-heading)'
},
secondary: {
color: 'var(--color-text-light)'
},
tertiary: {
color: 'var(--color-text-lightest-final)'
},
brand: {
color: 'var(--color-brand)'
}
};
const sizes = {
0: '0.875rem',
1: '1rem',
2: '1.125rem',
3: '1.25rem',
4: '1.5rem',
5: '2rem',
6: '2.5rem',
7: '3rem',
8: '4rem'
};
export const Text = forwardRef(
(
{
size = 1,
weight = 'normal',
tag,
style = 'normal',
appearance = 'normal',
letterSpacing = 0,
lineHeight = 1.4,
align = 'left',
transform,
classes = '',
children
},
ref
) => {
const Tag = tag || 'span';
const styles = {
letterSpacing: letterSpacing,
fontSize: sizes[size],
textTransform: transform,
fontWeight: weight,
textAlign: align,
lineHeight: lineHeight,
fontStyle: style === 'italic' ? 'italic' : 'normal',
...appearanceStyles[appearance]
};
return (
<Tag style={styles} className={classes} ref={ref}>
{children}
</Tag>
);
}
);

View File

@@ -2,11 +2,12 @@
*/ */
import { h, Component } from 'preact'; import { h, Component } from 'preact';
import { route } from 'preact-router';
// import '../service-worker-registration'; // import '../service-worker-registration';
import { MainHeader } from './MainHeader.jsx'; import { MainHeader } from './MainHeader.jsx';
import ContentWrap from './ContentWrap.jsx'; import ContentWrap from './ContentWrap.jsx';
import ContentWrapFiles from './ContentWrapFiles.jsx'; import ContentWrapFiles from './ContentWrapFiles.jsx';
import Footer from './Footer.jsx'; import { Footer } from './Footer.jsx';
import SavedItemPane from './SavedItemPane.jsx'; import SavedItemPane from './SavedItemPane.jsx';
import AddLibrary from './AddLibrary.jsx'; import AddLibrary from './AddLibrary.jsx';
import Modal from './Modal.jsx'; import Modal from './Modal.jsx';
@@ -22,7 +23,8 @@ import {
getCompleteHtml, getCompleteHtml,
getFilenameFromUrl, getFilenameFromUrl,
prettify, prettify,
sanitizeSplitSizes sanitizeSplitSizes,
persistAuthUserLocally
} from '../utils'; } from '../utils';
import { import {
linearizeFiles, linearizeFiles,
@@ -35,7 +37,7 @@ import {
import { itemService } from '../itemService'; import { itemService } from '../itemService';
import '../db'; import '../db';
import { Notifications } from './Notifications'; import { Changelog } from './Changelog';
import Settings from './Settings.jsx'; import Settings from './Settings.jsx';
import { modes, HtmlModes, CssModes, JsModes } from '../codeModes'; import { modes, HtmlModes, CssModes, JsModes } from '../codeModes';
import { trackEvent } from '../analytics'; import { trackEvent } from '../analytics';
@@ -68,14 +70,21 @@ import {
import { commandPaletteService } from '../commandPaletteService'; import { commandPaletteService } from '../commandPaletteService';
import { I18nProvider } from '@lingui/react'; import { I18nProvider } from '@lingui/react';
import { Assets } from './Assets.jsx';
import { LocalStorageKeys } from '../constants.js'; import { LocalStorageKeys } from '../constants.js';
import { Share } from './Share.jsx';
import { Pro } from './Pro.jsx';
import { VStack } from './Stack.jsx';
import { ProBadge } from './ProBadge.jsx';
import { Text } from './Text.jsx';
import { ProOnAppModal } from './ProOnAppModal.js';
if (module.hot) { if (module.hot) {
require('preact/debug'); require('preact/debug');
} }
const UNSAVED_WARNING_COUNT = 15; const UNSAVED_WARNING_COUNT = 15;
const version = '5.2.0'; const version = '6.1.0';
// Read forced settings as query parameters // Read forced settings as query parameters
window.forcedSettings = {}; window.forcedSettings = {};
@@ -98,10 +107,19 @@ if (location.search) {
window.codeCss = params.get('css') || ''; window.codeCss = params.get('css') || '';
} }
function customRoute(path) {
// we don't /create redirections on extension since SPA paths don't work there
if (window.IS_EXTENSION) return;
else {
route(path);
}
}
export default class App extends Component { export default class App extends Component {
constructor() { constructor() {
super(); super();
this.AUTO_SAVE_INTERVAL = 15000; // 15 seconds this.AUTO_SAVE_INTERVAL = 15000; // 15 seconds
const savedUser = window.localStorage.getItem('user');
this.modalDefaultStates = { this.modalDefaultStates = {
isModalOpen: false, isModalOpen: false,
isAddLibraryModalOpen: false, isAddLibraryModalOpen: false,
@@ -116,7 +134,12 @@ export default class App extends Component {
isOnboardModalOpen: false, isOnboardModalOpen: false,
isJs13KModalOpen: false, isJs13KModalOpen: false,
isCreateNewModalOpen: false, isCreateNewModalOpen: false,
isCommandPaletteOpen: false isCommandPaletteOpen: false,
isAssetsOpen: false,
isShareModalOpen: false,
isProModalOpen: false,
isFilesLimitModalOpen: false,
isProOnAppModalOpen: false
}; };
this.state = { this.state = {
isSavedItemPaneOpen: false, isSavedItemPaneOpen: false,
@@ -128,7 +151,8 @@ export default class App extends Component {
html: window.codeHtml, html: window.codeHtml,
css: window.codeCss css: window.codeCss
}, },
catalogs: {} catalogs: {},
user: savedUser
}; };
this.defaultSettings = { this.defaultSettings = {
preserveLastCode: true, preserveLastCode: true,
@@ -161,13 +185,35 @@ export default class App extends Component {
}; };
this.prefs = {}; this.prefs = {};
onAuthStateChanged(auth, user => { if (savedUser) {
window.user = savedUser;
}
onAuthStateChanged(auth, authUser => {
this.setState({ isLoginModalOpen: false }); this.setState({ isLoginModalOpen: false });
if (user) { if (authUser) {
log('You are -> ', user); log('You are -> ', authUser);
alertsService.add('You are now logged in!'); alertsService.add('You are now logged in!');
this.setState({ user });
window.user = user; let newUser = {
uid: authUser.uid,
photoURL: authUser.photoURL
};
// port some keys from localstorage user to new auth user
const keysToPort = ['isPro', 'displayName', 'settings'];
keysToPort.forEach(key => {
if (this.state.user && this.state.user[key] !== undefined) {
newUser[key] = this.state.user[key];
}
});
// storing actual firebase user object for accessing functions like updateProfile
newUser.firebaseUser = authUser;
this.setState({ user: newUser });
window.user = newUser;
// 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 => {
if (!items.length) { if (!items.length) {
@@ -181,12 +227,23 @@ export default class App extends Component {
trackEvent('ui', 'askToImportModalSeen'); trackEvent('ui', 'askToImportModalSeen');
}); });
} }
window.db.getUser(user.uid).then(customUser => {
window.db.getUser(authUser.uid).then(customUser => {
if (customUser) { if (customUser) {
const prefs = { ...this.state.prefs }; const prefs = { ...this.state.prefs };
Object.assign(prefs, user.settings); Object.assign(prefs, customUser.settings);
this.setState({ prefs }, this.updateSetting);
// spreading authUser doesn't work below anymore because the required properties are
// not enumerable anymore
newUser = {
...newUser,
isPro: false,
...customUser
};
window.user = newUser;
this.setState({ user: newUser, prefs }, this.updateSetting);
} }
persistAuthUserLocally(newUser);
}); });
} else { } else {
// User is signed out. // User is signed out.
@@ -234,6 +291,18 @@ export default class App extends Component {
this.setCurrentItem(this.state.currentItem).then(() => { this.setCurrentItem(this.state.currentItem).then(() => {
this.refreshEditor(); this.refreshEditor();
}); });
} else if (this.props.itemId) {
window.db
.fetchItem(this.props.itemId)
.then(item => {
this.setCurrentItem(item).then(() => this.refreshEditor());
})
.catch(err => {
alert('No such creation found!');
this.createNewItem();
// route('/');
});
} else if (result.preserveLastCode && lastCode) { } else if (result.preserveLastCode && lastCode) {
this.setState({ unsavedEditCount: 0 }); this.setState({ unsavedEditCount: 0 });
log('Load last unsaved item', lastCode); log('Load last unsaved item', lastCode);
@@ -267,8 +336,8 @@ export default class App extends Component {
semverCompare(lastSeenVersion, version) === -1 && semverCompare(lastSeenVersion, version) === -1 &&
!window.localStorage.pledgeModalSeen !window.localStorage.pledgeModalSeen
) { ) {
this.openSupportDeveloperModal(); // this.openSupportDeveloperModal();
window.localStorage.pledgeModalSeen = true; // window.localStorage.pledgeModalSeen = true;
} }
if (!lastSeenVersion || semverCompare(lastSeenVersion, version) === -1) { if (!lastSeenVersion || semverCompare(lastSeenVersion, version) === -1) {
@@ -346,9 +415,12 @@ export default class App extends Component {
} }
const fork = JSON.parse(JSON.stringify(sourceItem)); const fork = JSON.parse(JSON.stringify(sourceItem));
delete fork.id; delete fork.id;
delete fork.createdBy;
delete fork.isPublic;
fork.title = '(Forked) ' + sourceItem.title; fork.title = '(Forked) ' + sourceItem.title;
fork.updatedOn = Date.now(); fork.updatedOn = Date.now();
this.setCurrentItem(fork).then(() => this.refreshEditor()); this.setCurrentItem(fork).then(() => this.refreshEditor());
customRoute('/create');
alertsService.add(`"${sourceItem.title}" was forked`); alertsService.add(`"${sourceItem.title}" was forked`);
trackEvent('fn', 'itemForked'); trackEvent('fn', 'itemForked');
} }
@@ -408,10 +480,12 @@ export default class App extends Component {
}; };
} }
this.setCurrentItem(item).then(() => this.refreshEditor()); this.setCurrentItem(item).then(() => this.refreshEditor());
customRoute('/create');
alertsService.add('New item created'); alertsService.add('New item created');
} }
openItem(item) { openItem(item) {
this.setCurrentItem(item).then(() => this.refreshEditor()); this.setCurrentItem(item).then(() => this.refreshEditor());
customRoute(`/create/${item.id}`);
alertsService.add('Saved item loaded'); alertsService.add('Saved item loaded');
} }
removeItem(item) { removeItem(item) {
@@ -538,6 +612,9 @@ export default class App extends Component {
openAddLibrary() { openAddLibrary() {
this.setState({ isAddLibraryModalOpen: true }); this.setState({ isAddLibraryModalOpen: true });
} }
assetsBtnClickHandler() {
this.setState({ isAssetsOpen: true });
}
closeSavedItemsPane() { closeSavedItemsPane() {
this.setState({ this.setState({
isSavedItemPaneOpen: false isSavedItemPaneOpen: false
@@ -556,6 +633,7 @@ 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`;
} }
@@ -891,8 +969,16 @@ export default class App extends Component {
trackEvent('ui', LocalStorageKeys.LOGIN_AND_SAVE_MESSAGE_SEEN, 'local'); trackEvent('ui', LocalStorageKeys.LOGIN_AND_SAVE_MESSAGE_SEEN, 'local');
} }
var isNewItem = !this.state.currentItem.id; var isNewItem = !this.state.currentItem.id;
this.state.currentItem.id = this.state.currentItem.id = this.state.currentItem.id || generateRandomId();
this.state.currentItem.id || 'item-' + generateRandomId(); if (
this.state.currentItem.createdBy &&
this.state.currentItem.createdBy !== this.state.user.uid
) {
alertsService.add(
'You cannot save this item as it was created by someone else. Fork it to save it as your own.'
);
return;
}
this.setState({ this.setState({
isSaving: true isSaving: true
}); });
@@ -952,15 +1038,18 @@ export default class App extends Component {
} }
titleInputBlurHandler(e) { titleInputBlurHandler(e) {
this.setState({ this.setState(
{
currentItem: { ...this.state.currentItem, title: e.target.value } currentItem: { ...this.state.currentItem, title: e.target.value }
}); },
() => {
if (this.state.currentItem.id) { if (this.state.currentItem.id) {
this.saveItem(); this.saveItem();
trackEvent('ui', 'titleChanged'); trackEvent('ui', 'titleChanged');
} }
} }
);
}
/** /**
* Handles all user triggered preference changes in the UI. * Handles all user triggered preference changes in the UI.
@@ -1045,6 +1134,7 @@ export default class App extends Component {
trackEvent('fn', 'loggedOut'); trackEvent('fn', 'loggedOut');
authh.logout(); authh.logout();
this.setState({ isProfileModalOpen: false }); this.setState({ isProfileModalOpen: false });
this.createNewItem();
alertsService.add('Log out successfull'); alertsService.add('Log out successfull');
} }
@@ -1087,6 +1177,14 @@ export default class App extends Component {
trackEvent('ui', 'openBtnClick'); trackEvent('ui', 'openBtnClick');
this.openSavedItemsPane(); this.openSavedItemsPane();
} }
shareBtnClickHandler() {
trackEvent('ui', 'shareBtnClick');
if (!window.user || this.state.currentItem.id) {
this.setState({ isShareModalOpen: true });
} else {
alertsService.add('Please save your creation before sharing.');
}
}
detachedPreviewBtnHandler() { detachedPreviewBtnHandler() {
trackEvent('ui', 'detachPreviewBtnClick'); trackEvent('ui', 'detachPreviewBtnClick');
@@ -1095,7 +1193,7 @@ export default class App extends Component {
notificationsBtnClickHandler() { notificationsBtnClickHandler() {
this.setState({ isNotificationsModalOpen: true }); this.setState({ isNotificationsModalOpen: true });
if (this.state.isNotificationsModalOpen && !this.hasSeenNotifications) { if (!this.state.isNotificationsModalOpen && !this.hasSeenNotifications) {
this.hasSeenNotifications = true; this.hasSeenNotifications = true;
this.setState({ hasUnseenChangelog: false }); this.setState({ hasUnseenChangelog: false });
window.db.setUserLastSeenVersion(version); window.db.setUserLastSeenVersion(version);
@@ -1103,6 +1201,15 @@ export default class App extends Component {
trackEvent('ui', 'notificationButtonClick', version); trackEvent('ui', 'notificationButtonClick', version);
return false; return false;
} }
proBtnClickHandler() {
if (this.state.user?.isPro) {
this.setState({ isProfileModalOpen: true });
trackEvent('ui', 'manageProBtnClick');
} else {
this.setState({ isProModalOpen: true });
trackEvent('ui', 'proBtnClick');
}
}
codepenBtnClickHandler(e) { codepenBtnClickHandler(e) {
if (this.state.currentItem.cssMode === CssModes.ACSS) { if (this.state.currentItem.cssMode === CssModes.ACSS) {
alert( alert(
@@ -1443,9 +1550,11 @@ export default class App extends Component {
this.setState({ isCreateNewModalOpen: false }); this.setState({ isCreateNewModalOpen: false });
} else { } else {
trackEvent('ui', 'FileModeCreationLimitMessageSeen'); trackEvent('ui', 'FileModeCreationLimitMessageSeen');
return alert( // this.closeAllOverlays();
'"Files mode" is currently in beta and is limited to only 2 creations per user. You have already made 2 creations in Files mode.\n\nNote: You can choose to delete old ones to create new.' this.setState({ isFilesLimitModalOpen: true });
); // return alert(
// '"Files mode" is currently in beta and is limited to only 2 creations per user. You have already made 2 creations in Files mode.\n\nNote: You can choose to delete old ones to create new.'
// );
} }
}); });
} }
@@ -1578,7 +1687,7 @@ export default class App extends Component {
// 3 pane mode // 3 pane mode
if (typeof what === 'string') { if (typeof what === 'string') {
prettify({ prettify({
content: this.state.currentItem[what], content: this.state.currentItem[what] || '',
type: { html: 'html', js: 'js', css: 'css' }[what] type: { html: 'html', js: 'js', css: 'css' }[what]
}).then(formattedContent => { }).then(formattedContent => {
if (this.state.currentItem[what] === formattedContent) { if (this.state.currentItem[what] === formattedContent) {
@@ -1599,6 +1708,7 @@ export default class App extends Component {
...this.state.currentItem, ...this.state.currentItem,
files: [...this.state.currentItem.files] files: [...this.state.currentItem.files]
}; };
prettify({ file: selectedFile }).then(formattedContent => { prettify({ file: selectedFile }).then(formattedContent => {
if (formattedContent !== selectedFile.content) { if (formattedContent !== selectedFile.content) {
selectedFile.content = formattedContent; selectedFile.content = formattedContent;
@@ -1621,10 +1731,12 @@ export default class App extends Component {
loginBtnHandler={this.loginBtnClickHandler.bind(this)} loginBtnHandler={this.loginBtnClickHandler.bind(this)}
profileBtnHandler={this.profileBtnClickHandler.bind(this)} profileBtnHandler={this.profileBtnClickHandler.bind(this)}
addLibraryBtnHandler={this.openAddLibrary.bind(this)} addLibraryBtnHandler={this.openAddLibrary.bind(this)}
assetsBtnHandler={this.assetsBtnClickHandler.bind(this)}
shareBtnHandler={this.shareBtnClickHandler.bind(this)}
runBtnClickHandler={this.runBtnClickHandler.bind(this)} runBtnClickHandler={this.runBtnClickHandler.bind(this)}
isFetchingItems={this.state.isFetchingItems} isFetchingItems={this.state.isFetchingItems}
isSaving={this.state.isSaving} isSaving={this.state.isSaving}
title={this.state.currentItem.title} currentItem={this.state.currentItem}
titleInputBlurHandler={this.titleInputBlurHandler.bind(this)} titleInputBlurHandler={this.titleInputBlurHandler.bind(this)}
user={this.state.user} user={this.state.user}
isAutoPreviewOn={this.state.prefs.autoPreview} isAutoPreviewOn={this.state.prefs.autoPreview}
@@ -1632,6 +1744,9 @@ export default class App extends Component {
isFileMode={ isFileMode={
this.state.currentItem && this.state.currentItem.files this.state.currentItem && this.state.currentItem.files
} }
onItemFork={() => {
this.forkItem(this.state.currentItem);
}}
/> />
{this.state.currentItem && this.state.currentItem.files ? ( {this.state.currentItem && this.state.currentItem.files ? (
<ContentWrapFiles <ContentWrapFiles
@@ -1667,6 +1782,7 @@ export default class App extends Component {
<Footer <Footer
prefs={this.state.prefs} prefs={this.state.prefs}
user={this.state.user}
layoutBtnClickHandler={this.layoutBtnClickHandler.bind(this)} layoutBtnClickHandler={this.layoutBtnClickHandler.bind(this)}
helpBtnClickHandler={() => helpBtnClickHandler={() =>
this.setState({ isHelpModalOpen: true }) this.setState({ isHelpModalOpen: true })
@@ -1693,11 +1809,11 @@ export default class App extends Component {
onJs13KDownloadBtnClick={this.js13KDownloadBtnClickHandler.bind( onJs13KDownloadBtnClick={this.js13KDownloadBtnClickHandler.bind(
this this
)} )}
proBtnClickHandler={this.proBtnClickHandler.bind(this)}
hasUnseenChangelog={this.state.hasUnseenChangelog} hasUnseenChangelog={this.state.hasUnseenChangelog}
codeSize={this.state.codeSize} codeSize={this.state.codeSize}
/> />
</div> </div>
<SavedItemPane <SavedItemPane
itemsMap={this.state.savedItems} itemsMap={this.state.savedItems}
isOpen={this.state.isSavedItemPaneOpen} isOpen={this.state.isSavedItemPaneOpen}
@@ -1708,9 +1824,7 @@ export default class App extends Component {
onExport={this.exportBtnClickHandler.bind(this)} onExport={this.exportBtnClickHandler.bind(this)}
mergeImportedItems={this.mergeImportedItems.bind(this)} mergeImportedItems={this.mergeImportedItems.bind(this)}
/> />
<Alerts /> <Alerts />
<Modal <Modal
show={this.state.isAddLibraryModalOpen} show={this.state.isAddLibraryModalOpen}
closeHandler={() => this.setState({ isAddLibraryModalOpen: false })} closeHandler={() => this.setState({ isAddLibraryModalOpen: false })}
@@ -1735,7 +1849,7 @@ export default class App extends Component {
this.setState({ isNotificationsModalOpen: false }) this.setState({ isNotificationsModalOpen: false })
} }
> >
<Notifications <Changelog
onSupportBtnClick={this.openSupportDeveloperModal.bind(this)} onSupportBtnClick={this.openSupportDeveloperModal.bind(this)}
/> />
</Modal> </Modal>
@@ -1749,13 +1863,6 @@ export default class App extends Component {
onChange={this.updateSetting.bind(this)} onChange={this.updateSetting.bind(this)}
/> />
</Modal> </Modal>
<Modal
extraClasses="login-modal"
show={this.state.isLoginModalOpen}
closeHandler={() => this.setState({ isLoginModalOpen: false })}
>
<Login />
</Modal>
<Modal <Modal
show={this.state.isProfileModalOpen} show={this.state.isProfileModalOpen}
closeHandler={() => this.setState({ isProfileModalOpen: false })} closeHandler={() => this.setState({ isProfileModalOpen: false })}
@@ -1765,6 +1872,73 @@ export default class App extends Component {
logoutBtnHandler={this.logout.bind(this)} logoutBtnHandler={this.logout.bind(this)}
/> />
</Modal> </Modal>
<Modal
show={this.state.isAssetsOpen}
closeHandler={() => this.setState({ isAssetsOpen: false })}
>
<Assets
onProBtnClick={() => {
this.setState({ isAssetsOpen: false });
this.proBtnClickHandler();
}}
onLoginBtnClick={() => {
this.closeAllOverlays();
this.loginBtnClickHandler();
}}
/>
</Modal>
<Modal
show={this.state.isShareModalOpen}
closeHandler={() => this.setState({ isShareModalOpen: false })}
>
<Share
user={this.state.user}
item={this.state.currentItem}
onVisibilityChange={visibility => {
const item = {
...this.state.currentItem,
isPublic: visibility
};
this.setState({ currentItem: item });
}}
onLoginBtnClick={() => {
this.closeAllOverlays();
this.loginBtnClickHandler();
}}
onProBtnClick={() => {
this.closeAllOverlays();
this.proBtnClickHandler();
}}
/>
</Modal>
<Modal
show={this.state.isProModalOpen}
closeHandler={() => this.setState({ isProModalOpen: false })}
extraClasses="pro-modal"
>
<Pro
user={this.state.user}
onLoginClick={() => {
this.closeAllOverlays();
this.loginBtnClickHandler();
}}
onBuyFromExtensionClick={() => {
console.log('open modal');
this.closeAllOverlays();
this.setState({ isProOnAppModalOpen: true });
}}
/>
</Modal>
{/* Login modal is intentionally kept here after assets & share modal because
they trigger this modal and if order isn't maintainer, the modal overlay doesn't
show properly */}
<Modal
extraClasses="login-modal"
show={this.state.isLoginModalOpen}
closeHandler={() => this.setState({ isLoginModalOpen: false })}
>
<Login />
</Modal>
<HelpModal <HelpModal
show={this.state.isHelpModalOpen} show={this.state.isHelpModalOpen}
closeHandler={() => this.setState({ isHelpModalOpen: false })} closeHandler={() => this.setState({ isHelpModalOpen: false })}
@@ -1794,17 +1968,14 @@ export default class App extends Component {
)} )}
dontAskBtnClickHandler={this.dontAskToImportAnymore.bind(this)} dontAskBtnClickHandler={this.dontAskToImportAnymore.bind(this)}
/> />
<OnboardingModal <OnboardingModal
show={this.state.isOnboardModalOpen} show={this.state.isOnboardModalOpen}
closeHandler={() => this.setState({ isOnboardModalOpen: false })} closeHandler={() => this.setState({ isOnboardModalOpen: false })}
/> />
<Js13KModal <Js13KModal
show={this.state.isJs13KModalOpen} show={this.state.isJs13KModalOpen}
closeHandler={() => this.setState({ isJs13KModalOpen: false })} closeHandler={() => this.setState({ isJs13KModalOpen: false })}
/> />
<CreateNewModal <CreateNewModal
show={this.state.isCreateNewModalOpen} show={this.state.isCreateNewModalOpen}
closeHandler={() => this.setState({ isCreateNewModalOpen: false })} closeHandler={() => this.setState({ isCreateNewModalOpen: false })}
@@ -1817,21 +1988,38 @@ export default class App extends Component {
this this
)} )}
/> />
<ProOnAppModal
show={this.state.isProOnAppModalOpen}
closeHandler={() => this.setState({ isProOnAppModalOpen: false })}
/>
<Modal
extraClasses=""
show={this.state.isFilesLimitModalOpen}
closeHandler={() => this.setState({ isFilesLimitModalOpen: false })}
>
<VStack align="stretch" gap={2}>
<Text tag="p">
You have used your quota of 2 'Files mode' creations in Free
plan.
</Text>
<Text tag="p">
You can choose to delete old ones to free quota or upgrade to{' '}
<ProBadge />.{' '}
</Text>
</VStack>
</Modal>
<CommandPalette <CommandPalette
show={this.state.isCommandPaletteOpen} show={this.state.isCommandPaletteOpen}
files={linearizeFiles(this.state.currentItem.files || [])} files={linearizeFiles(this.state.currentItem.files || [])}
isCommandMode={this.state.isCommandPaletteInCommandMode} isCommandMode={this.state.isCommandPaletteInCommandMode}
closeHandler={() => this.setState({ isCommandPaletteOpen: false })} closeHandler={() => this.setState({ isCommandPaletteOpen: false })}
/> />
<Portal into="#portal"> <Portal into="#portal">
<div <div
class="modal-overlay" class="modal-overlay"
onClick={this.modalOverlayClickHandler.bind(this)} onClick={this.modalOverlayClickHandler.bind(this)}
/> />
</Portal> </Portal>
<Icons /> <Icons />
<form <form
style="display:none;" style="display:none;"

View File

@@ -4,12 +4,16 @@ import { trackEvent } from '../analytics';
class Clickable extends Component { class Clickable extends Component {
handleClick(e) { handleClick(e) {
const el = e.currentTarget; const el = e.currentTarget;
if (el.getAttribute('data-event-category')) {
trackEvent( trackEvent(
el.getAttribute('data-event-category'), el.getAttribute('data-event-category'),
el.getAttribute('data-event-action') el.getAttribute('data-event-action')
); );
}
if (this.props.onClick) {
this.props.onClick(e); this.props.onClick(e);
} }
}
render() { render() {
/* eslint-disable no-unused-vars */ /* eslint-disable no-unused-vars */
const { onClick, Tag, ...props } = this.props; const { onClick, Tag, ...props } = this.props;
@@ -38,5 +42,5 @@ export function Divider(props) {
} }
export function BetaTag() { export function BetaTag() {
return <span class="beta-tag">Beta</span>; return <span class="badge beta-tag">Beta</span>;
} }

View File

@@ -6,6 +6,24 @@ import { deferred } from './deferred';
import { trackEvent } from './analytics'; import { trackEvent } from './analytics';
import { log } from './utils'; import { log } from './utils';
/**
* Converts a firestore query snapshot into native array
* @param {snapshot} querySnapshot Snapshot object returned by a firestore query
*/
function getArrayFromQuerySnapshot(querySnapshot) {
const arr = [];
querySnapshot.forEach(doc => {
// doc.data() has to be after doc.id because docs can have `id` key in them which
// should override the explicit `id` being set
arr.push({
id: doc.id,
...doc.data()
});
// documentCache[doc.id] = doc.data()
});
return arr;
}
(() => { (() => {
const FAUX_DELAY = 1; const FAUX_DELAY = 1;
@@ -129,15 +147,28 @@ import { log } from './utils';
return getDoc(doc(remoteDb, `users/${userId}`)).then(doc => { return getDoc(doc(remoteDb, `users/${userId}`)).then(doc => {
if (!doc.exists()) { if (!doc.exists()) {
return setDoc(doc(remoteDb, `users/${userId}`), {}, { merge: true }); // return setDoc(doc(remoteDb, `users/${userId}`), {}, { merge: true });
return {};
} }
const user = doc.data(); const user = doc.data();
Object.assign(window.user, user); // Object.assign(window.user, user);
return user; return user;
}); });
} }
async function fetchItem(itemId) {
const remoteDb = await getDb();
return remoteDb
.doc(`items/${itemId}`)
.get()
.then(doc => {
if (!doc.exists) return {};
const data = doc.data();
return data;
});
}
// Fetch user settings. // Fetch user settings.
// This isn't hitting the remote db because remote settings // This isn't hitting the remote db because remote settings
// get fetch asynchronously (in user/) and update the envioronment. // get fetch asynchronously (in user/) and update the envioronment.
@@ -151,12 +182,36 @@ import { log } from './utils';
return d.promise; return d.promise;
} }
async function getPublicItemCount(userId) {
const remoteDb = await getDb();
return remoteDb
.collection('items')
.where('createdBy', '==', userId)
.where('isPublic', '==', true)
.get()
.then(snapShot => {
return snapShot.size;
});
}
async function getUserSubscriptionEvents(userId) {
const remoteDb = await getDb();
return remoteDb
.collection('subscriptions')
.where('userId', '==', userId)
.get()
.then(getArrayFromQuerySnapshot);
}
window.db = { window.db = {
getDb, getDb,
getUser, getUser,
getUserLastSeenVersion, getUserLastSeenVersion,
setUserLastSeenVersion, setUserLastSeenVersion,
getSettings, getSettings,
fetchItem,
getPublicItemCount,
getUserSubscriptionEvents,
local: dbLocalAlias, local: dbLocalAlias,
sync: dbSyncAlias sync: dbSyncAlias
}; };

20
src/hooks/useCheckout.js Normal file
View File

@@ -0,0 +1,20 @@
import { useState, useEffect } from 'react';
function useCheckout() {
const [hasVendorScriptLoaded, setHasVendorScriptLoaded] = useState();
useEffect(() => {
const script = document.createElement('script');
script.src = 'https://app.lemonsqueezy.com/js/lemon.js';
script.async = 'true';
script.defer = 'true';
script.addEventListener('load', () => {
window.createLemonSqueezy();
setHasVendorScriptLoaded(true);
});
document.body.appendChild(script);
}, []);
return hasVendorScriptLoaded;
}
export { useCheckout };

View File

@@ -1,4 +1,4 @@
<!DOCTYPE html> <!doctype html>
<html lang="en"> <html lang="en">
<head> <head>
<meta charset="utf-8" /> <meta charset="utf-8" />
@@ -11,11 +11,12 @@
rel="manifest" rel="manifest"
href="<%= htmlWebpackPlugin.files.publicPath %>manifest.json" href="<%= htmlWebpackPlugin.files.publicPath %>manifest.json"
/> />
<% if (htmlWebpackPlugin.options.manifest.theme_color) { %> <% if (cli.env.isProd) { %>
<meta <base href="/create/" />
name="theme-color" <% } else { %>
content="<%= htmlWebpackPlugin.options.manifest.theme_color %>" <base href="/" />
/> <% } %> <% if (cli.manifest.theme_color) { %>
<meta name="theme-color" content="<%= cli.manifest.theme_color %>" />
<% } %> <% } %>
<style> <style>
@@ -38,7 +39,7 @@
<link <link
rel="stylesheet" rel="stylesheet"
id="editorThemeLinkTag" id="editorThemeLinkTag"
href="lib/codemirror/theme/monokai.css" href="./lib/codemirror/theme/monokai.css"
/> />
<style id="fontStyleTemplate" type="template"> <style id="fontStyleTemplate" type="template">
@@ -73,13 +74,13 @@
<body> <body>
<div id="root"></div> <div id="root"></div>
<div id="portal"></div> <div id="portal"></div>
<!-- SCRIPT-TAGS -->
<%= htmlWebpackPlugin.options.ssr({ url: '/' }) %>
<script
defer
src="<%= htmlWebpackPlugin.files.chunks['bundle'].entry %>"
></script>
<!-- END-SCRIPT-TAGS --> <%= cli.ssr %> <% if (cli.config.prerender === true) { %>
<script type="__PREACT_CLI_DATA__">
<%= encodeURI(JSON.stringify(cli.CLI_DATA)) %>
</script>
<% } %>
<script type="module" src="<%= cli.entrypoints['bundle'] %>"></script>
<script nomodule src="<%= cli.entrypoints['dom-polyfills'] %>"></script>
</body> </body>
</html> </html>

View File

@@ -1,3 +1,4 @@
import Router from 'preact-router';
import App from './components/app.jsx'; import App from './components/app.jsx';
import './lib/codemirror/lib/codemirror.css'; import './lib/codemirror/lib/codemirror.css';
@@ -8,4 +9,13 @@ import './lib/hint.min.css';
import './lib/inlet.css'; import './lib/inlet.css';
import './style.css'; import './style.css';
export default App; export default function () {
return (
<Router>
<App path="/" />
<App path="/create/:itemId" />
<App path="/app/create/:itemId" />
<App default />
</Router>
);
}

View File

@@ -1,6 +1,6 @@
<script> <script>
function callback(e) { function callback(e) {
console.log('post message recvd', e.data); // console.log('post message recvd', e.data);
window.document.open(); window.document.open();
const { contents } = e.data; const { contents } = e.data;

View File

@@ -59,8 +59,6 @@ export const itemService = {
if (window.user && !shouldFetchLocally) { if (window.user && !shouldFetchLocally) {
var remoteDb = await window.db.getDb(); var remoteDb = await window.db.getDb();
// console.log(query, window.user.uid);
// d.resolve([]);
const q = query( const q = query(
collection(remoteDb, 'items'), collection(remoteDb, 'items'),

View File

@@ -1,5 +1,5 @@
// CodeMirror, copyright (c) by Marijn Haverbeke and others // CodeMirror, copyright (c) by Marijn Haverbeke and others
// Distributed under an MIT license: http://codemirror.net/LICENSE // Distributed under an MIT license: https://codemirror.net/5/LICENSE
/** /**
* Link to the project's GitHub page: * Link to the project's GitHub page:
@@ -349,6 +349,10 @@ CodeMirror.defineMode("coffeescript", function(conf, parserConf) {
return external; return external;
}); });
// IANA registered media type
// https://www.iana.org/assignments/media-types/
CodeMirror.defineMIME("application/vnd.coffeescript", "coffeescript");
CodeMirror.defineMIME("text/x-coffeescript", "coffeescript"); CodeMirror.defineMIME("text/x-coffeescript", "coffeescript");
CodeMirror.defineMIME("text/coffeescript", "coffeescript"); CodeMirror.defineMIME("text/coffeescript", "coffeescript");

View File

@@ -1,5 +1,5 @@
// CodeMirror, copyright (c) by Marijn Haverbeke and others // CodeMirror, copyright (c) by Marijn Haverbeke and others
// Distributed under an MIT license: http://codemirror.net/LICENSE // Distributed under an MIT license: https://codemirror.net/5/LICENSE
(function(mod) { (function(mod) {
if (typeof exports == "object" && typeof module == "object") // CommonJS if (typeof exports == "object" && typeof module == "object") // CommonJS
@@ -28,7 +28,9 @@ CodeMirror.defineMode("css", function(config, parserConfig) {
colorKeywords = parserConfig.colorKeywords || {}, colorKeywords = parserConfig.colorKeywords || {},
valueKeywords = parserConfig.valueKeywords || {}, valueKeywords = parserConfig.valueKeywords || {},
allowNested = parserConfig.allowNested, allowNested = parserConfig.allowNested,
supportsAtComponent = parserConfig.supportsAtComponent === true; lineComment = parserConfig.lineComment,
supportsAtComponent = parserConfig.supportsAtComponent === true,
highlightNonStandardPropertyKeywords = config.highlightNonStandardPropertyKeywords !== false;
var type, override; var type, override;
function ret(style, tp) { type = tp; return style; } function ret(style, tp) { type = tp; return style; }
@@ -62,7 +64,7 @@ CodeMirror.defineMode("css", function(config, parserConfig) {
if (/[\d.]/.test(stream.peek())) { if (/[\d.]/.test(stream.peek())) {
stream.eatWhile(/[\w.%]/); stream.eatWhile(/[\w.%]/);
return ret("number", "unit"); return ret("number", "unit");
} else if (stream.match(/^-[\w\\\-]+/)) { } else if (stream.match(/^-[\w\\\-]*/)) {
stream.eatWhile(/[\w\\\-]/); stream.eatWhile(/[\w\\\-]/);
if (stream.match(/^\s*:/, false)) if (stream.match(/^\s*:/, false))
return ret("variable-2", "variable-definition"); return ret("variable-2", "variable-definition");
@@ -76,12 +78,11 @@ CodeMirror.defineMode("css", function(config, parserConfig) {
return ret("qualifier", "qualifier"); return ret("qualifier", "qualifier");
} else if (/[:;{}\[\]\(\)]/.test(ch)) { } else if (/[:;{}\[\]\(\)]/.test(ch)) {
return ret(null, ch); return ret(null, ch);
} else if ((ch == "u" && stream.match(/rl(-prefix)?\(/)) || } else if (stream.match(/^[\w-.]+(?=\()/)) {
(ch == "d" && stream.match("omain(")) || if (/^(url(-prefix)?|domain|regexp)$/i.test(stream.current())) {
(ch == "r" && stream.match("egexp("))) {
stream.backUp(1);
state.tokenize = tokenParenthesized; state.tokenize = tokenParenthesized;
return ret("property", "word"); }
return ret("variable callee", "variable");
} else if (/[\w\\\-]/.test(ch)) { } else if (/[\w\\\-]/.test(ch)) {
stream.eatWhile(/[\w\\\-]/); stream.eatWhile(/[\w\\\-]/);
return ret("property", "word"); return ret("property", "word");
@@ -107,7 +108,7 @@ CodeMirror.defineMode("css", function(config, parserConfig) {
function tokenParenthesized(stream, state) { function tokenParenthesized(stream, state) {
stream.next(); // Must be '(' stream.next(); // Must be '('
if (!stream.match(/\s*[\"\')]/, false)) if (!stream.match(/^\s*[\"\')]/, false))
state.tokenize = tokenString(")"); state.tokenize = tokenString(")");
else else
state.tokenize = null; state.tokenize = null;
@@ -161,16 +162,16 @@ CodeMirror.defineMode("css", function(config, parserConfig) {
return pushContext(state, stream, "block"); return pushContext(state, stream, "block");
} else if (type == "}" && state.context.prev) { } else if (type == "}" && state.context.prev) {
return popContext(state); return popContext(state);
} else if (supportsAtComponent && /@component/.test(type)) { } else if (supportsAtComponent && /@component/i.test(type)) {
return pushContext(state, stream, "atComponentBlock"); return pushContext(state, stream, "atComponentBlock");
} else if (/^@(-moz-)?document$/.test(type)) { } else if (/^@(-moz-)?document$/i.test(type)) {
return pushContext(state, stream, "documentTypes"); return pushContext(state, stream, "documentTypes");
} else if (/^@(media|supports|(-moz-)?document|import)$/.test(type)) { } else if (/^@(media|supports|(-moz-)?document|import)$/i.test(type)) {
return pushContext(state, stream, "atBlock"); return pushContext(state, stream, "atBlock");
} else if (/^@(font-face|counter-style)/.test(type)) { } else if (/^@(font-face|counter-style)/i.test(type)) {
state.stateArg = type; state.stateArg = type;
return "restricted_atBlock_before"; return "restricted_atBlock_before";
} else if (/^@(-(moz|ms|o|webkit)-)?keyframes$/.test(type)) { } else if (/^@(-(moz|ms|o|webkit)-)?keyframes$/i.test(type)) {
return "keyframes"; return "keyframes";
} else if (type && type.charAt(0) == "@") { } else if (type && type.charAt(0) == "@") {
return pushContext(state, stream, "at"); return pushContext(state, stream, "at");
@@ -197,7 +198,7 @@ CodeMirror.defineMode("css", function(config, parserConfig) {
override = "property"; override = "property";
return "maybeprop"; return "maybeprop";
} else if (nonStandardPropertyKeywords.hasOwnProperty(word)) { } else if (nonStandardPropertyKeywords.hasOwnProperty(word)) {
override = "string-2"; override = highlightNonStandardPropertyKeywords ? "string-2" : "property";
return "maybeprop"; return "maybeprop";
} else if (allowNested) { } else if (allowNested) {
override = stream.match(/^\s*:(?:\s|$)/, false) ? "property" : "tag"; override = stream.match(/^\s*:(?:\s|$)/, false) ? "property" : "tag";
@@ -227,7 +228,7 @@ CodeMirror.defineMode("css", function(config, parserConfig) {
if (type == "}" || type == "{") return popAndPass(type, stream, state); if (type == "}" || type == "{") return popAndPass(type, stream, state);
if (type == "(") return pushContext(state, stream, "parens"); if (type == "(") return pushContext(state, stream, "parens");
if (type == "hash" && !/^#([0-9a-fA-f]{3,4}|[0-9a-fA-f]{6}|[0-9a-fA-f]{8})$/.test(stream.current())) { if (type == "hash" && !/^#([0-9a-fA-F]{3,4}|[0-9a-fA-F]{6}|[0-9a-fA-F]{8})$/.test(stream.current())) {
override += " error"; override += " error";
} else if (type == "word") { } else if (type == "word") {
wordAsValue(stream); wordAsValue(stream);
@@ -253,6 +254,8 @@ CodeMirror.defineMode("css", function(config, parserConfig) {
}; };
states.pseudo = function(type, stream, state) { states.pseudo = function(type, stream, state) {
if (type == "meta") return "pseudo";
if (type == "word") { if (type == "word") {
override = "variable-3"; override = "variable-3";
return state.context.type; return state.context.type;
@@ -289,7 +292,7 @@ CodeMirror.defineMode("css", function(config, parserConfig) {
else if (propertyKeywords.hasOwnProperty(word)) else if (propertyKeywords.hasOwnProperty(word))
override = "property"; override = "property";
else if (nonStandardPropertyKeywords.hasOwnProperty(word)) else if (nonStandardPropertyKeywords.hasOwnProperty(word))
override = "string-2"; override = highlightNonStandardPropertyKeywords ? "string-2" : "property";
else if (valueKeywords.hasOwnProperty(word)) else if (valueKeywords.hasOwnProperty(word))
override = "atom"; override = "atom";
else if (colorKeywords.hasOwnProperty(word)) else if (colorKeywords.hasOwnProperty(word))
@@ -380,6 +383,7 @@ CodeMirror.defineMode("css", function(config, parserConfig) {
style = style[0]; style = style[0];
} }
override = style; override = style;
if (type != "comment")
state.state = states[state.state](type, stream, state); state.state = states[state.state](type, stream, state);
return override; return override;
}, },
@@ -398,7 +402,6 @@ CodeMirror.defineMode("css", function(config, parserConfig) {
ch == "{" && (cx.type == "at" || cx.type == "atBlock")) { ch == "{" && (cx.type == "at" || cx.type == "atBlock")) {
// Dedent relative to current context. // Dedent relative to current context.
indent = Math.max(0, cx.indent - indentUnit); indent = Math.max(0, cx.indent - indentUnit);
cx = cx.prev;
} }
} }
return indent; return indent;
@@ -407,6 +410,8 @@ CodeMirror.defineMode("css", function(config, parserConfig) {
electricChars: "}", electricChars: "}",
blockCommentStart: "/*", blockCommentStart: "/*",
blockCommentEnd: "*/", blockCommentEnd: "*/",
blockCommentContinue: " * ",
lineComment: lineComment,
fold: "brace" fold: "brace"
}; };
}); });
@@ -414,7 +419,7 @@ CodeMirror.defineMode("css", function(config, parserConfig) {
function keySet(array) { function keySet(array) {
var keys = {}; var keys = {};
for (var i = 0; i < array.length; ++i) { for (var i = 0; i < array.length; ++i) {
keys[array[i]] = true; keys[array[i].toLowerCase()] = true;
} }
return keys; return keys;
} }
@@ -438,117 +443,151 @@ CodeMirror.defineMode("css", function(config, parserConfig) {
"monochrome", "min-monochrome", "max-monochrome", "resolution", "monochrome", "min-monochrome", "max-monochrome", "resolution",
"min-resolution", "max-resolution", "scan", "grid", "orientation", "min-resolution", "max-resolution", "scan", "grid", "orientation",
"device-pixel-ratio", "min-device-pixel-ratio", "max-device-pixel-ratio", "device-pixel-ratio", "min-device-pixel-ratio", "max-device-pixel-ratio",
"pointer", "any-pointer", "hover", "any-hover" "pointer", "any-pointer", "hover", "any-hover", "prefers-color-scheme",
"dynamic-range", "video-dynamic-range"
], mediaFeatures = keySet(mediaFeatures_); ], mediaFeatures = keySet(mediaFeatures_);
var mediaValueKeywords_ = [ var mediaValueKeywords_ = [
"landscape", "portrait", "none", "coarse", "fine", "on-demand", "hover", "landscape", "portrait", "none", "coarse", "fine", "on-demand", "hover",
"interlace", "progressive" "interlace", "progressive",
"dark", "light",
"standard", "high"
], mediaValueKeywords = keySet(mediaValueKeywords_); ], mediaValueKeywords = keySet(mediaValueKeywords_);
var propertyKeywords_ = [ var propertyKeywords_ = [
"align-content", "align-items", "align-self", "alignment-adjust", "align-content", "align-items", "align-self", "alignment-adjust",
"alignment-baseline", "anchor-point", "animation", "animation-delay", "alignment-baseline", "all", "anchor-point", "animation", "animation-delay",
"animation-direction", "animation-duration", "animation-fill-mode", "animation-direction", "animation-duration", "animation-fill-mode",
"animation-iteration-count", "animation-name", "animation-play-state", "animation-iteration-count", "animation-name", "animation-play-state",
"animation-timing-function", "appearance", "azimuth", "backface-visibility", "animation-timing-function", "appearance", "azimuth", "backdrop-filter",
"background", "background-attachment", "background-blend-mode", "background-clip", "backface-visibility", "background", "background-attachment",
"background-color", "background-image", "background-origin", "background-position", "background-blend-mode", "background-clip", "background-color",
"background-repeat", "background-size", "baseline-shift", "binding", "background-image", "background-origin", "background-position",
"bleed", "bookmark-label", "bookmark-level", "bookmark-state", "background-position-x", "background-position-y", "background-repeat",
"bookmark-target", "border", "border-bottom", "border-bottom-color", "background-size", "baseline-shift", "binding", "bleed", "block-size",
"border-bottom-left-radius", "border-bottom-right-radius", "bookmark-label", "bookmark-level", "bookmark-state", "bookmark-target",
"border-bottom-style", "border-bottom-width", "border-collapse", "border", "border-bottom", "border-bottom-color", "border-bottom-left-radius",
"border-color", "border-image", "border-image-outset", "border-bottom-right-radius", "border-bottom-style", "border-bottom-width",
"border-collapse", "border-color", "border-image", "border-image-outset",
"border-image-repeat", "border-image-slice", "border-image-source", "border-image-repeat", "border-image-slice", "border-image-source",
"border-image-width", "border-left", "border-left-color", "border-image-width", "border-left", "border-left-color", "border-left-style",
"border-left-style", "border-left-width", "border-radius", "border-right", "border-left-width", "border-radius", "border-right", "border-right-color",
"border-right-color", "border-right-style", "border-right-width", "border-right-style", "border-right-width", "border-spacing", "border-style",
"border-spacing", "border-style", "border-top", "border-top-color", "border-top", "border-top-color", "border-top-left-radius",
"border-top-left-radius", "border-top-right-radius", "border-top-style", "border-top-right-radius", "border-top-style", "border-top-width",
"border-top-width", "border-width", "bottom", "box-decoration-break", "border-width", "bottom", "box-decoration-break", "box-shadow", "box-sizing",
"box-shadow", "box-sizing", "break-after", "break-before", "break-inside", "break-after", "break-before", "break-inside", "caption-side", "caret-color",
"caption-side", "clear", "clip", "color", "color-profile", "column-count", "clear", "clip", "color", "color-profile", "column-count", "column-fill",
"column-fill", "column-gap", "column-rule", "column-rule-color", "column-gap", "column-rule", "column-rule-color", "column-rule-style",
"column-rule-style", "column-rule-width", "column-span", "column-width", "column-rule-width", "column-span", "column-width", "columns", "contain",
"columns", "content", "counter-increment", "counter-reset", "crop", "cue", "content", "counter-increment", "counter-reset", "crop", "cue", "cue-after",
"cue-after", "cue-before", "cursor", "direction", "display", "cue-before", "cursor", "direction", "display", "dominant-baseline",
"dominant-baseline", "drop-initial-after-adjust", "drop-initial-after-adjust", "drop-initial-after-align",
"drop-initial-after-align", "drop-initial-before-adjust", "drop-initial-before-adjust", "drop-initial-before-align", "drop-initial-size",
"drop-initial-before-align", "drop-initial-size", "drop-initial-value", "drop-initial-value", "elevation", "empty-cells", "fit", "fit-content", "fit-position",
"elevation", "empty-cells", "fit", "fit-position", "flex", "flex-basis", "flex", "flex-basis", "flex-direction", "flex-flow", "flex-grow",
"flex-direction", "flex-flow", "flex-grow", "flex-shrink", "flex-wrap", "flex-shrink", "flex-wrap", "float", "float-offset", "flow-from", "flow-into",
"float", "float-offset", "flow-from", "flow-into", "font", "font-feature-settings", "font", "font-family", "font-feature-settings", "font-kerning",
"font-family", "font-kerning", "font-language-override", "font-size", "font-size-adjust", "font-language-override", "font-optical-sizing", "font-size",
"font-stretch", "font-style", "font-synthesis", "font-variant", "font-size-adjust", "font-stretch", "font-style", "font-synthesis",
"font-variant-alternates", "font-variant-caps", "font-variant-east-asian", "font-variant", "font-variant-alternates", "font-variant-caps",
"font-variant-ligatures", "font-variant-numeric", "font-variant-position", "font-variant-east-asian", "font-variant-ligatures", "font-variant-numeric",
"font-weight", "grid", "grid-area", "grid-auto-columns", "grid-auto-flow", "font-variant-position", "font-variation-settings", "font-weight", "gap",
"grid-auto-position", "grid-auto-rows", "grid-column", "grid-column-end", "grid", "grid-area", "grid-auto-columns", "grid-auto-flow", "grid-auto-rows",
"grid-column-start", "grid-row", "grid-row-end", "grid-row-start", "grid-column", "grid-column-end", "grid-column-gap", "grid-column-start",
"grid-gap", "grid-row", "grid-row-end", "grid-row-gap", "grid-row-start",
"grid-template", "grid-template-areas", "grid-template-columns", "grid-template", "grid-template-areas", "grid-template-columns",
"grid-template-rows", "hanging-punctuation", "height", "hyphens", "grid-template-rows", "hanging-punctuation", "height", "hyphens", "icon",
"icon", "image-orientation", "image-rendering", "image-resolution", "image-orientation", "image-rendering", "image-resolution", "inline-box-align",
"inline-box-align", "justify-content", "left", "letter-spacing", "inset", "inset-block", "inset-block-end", "inset-block-start", "inset-inline",
"line-break", "line-height", "line-stacking", "line-stacking-ruby", "inset-inline-end", "inset-inline-start", "isolation", "justify-content",
"justify-items", "justify-self", "left", "letter-spacing", "line-break",
"line-height", "line-height-step", "line-stacking", "line-stacking-ruby",
"line-stacking-shift", "line-stacking-strategy", "list-style", "line-stacking-shift", "line-stacking-strategy", "list-style",
"list-style-image", "list-style-position", "list-style-type", "margin", "list-style-image", "list-style-position", "list-style-type", "margin",
"margin-bottom", "margin-left", "margin-right", "margin-top", "margin-bottom", "margin-left", "margin-right", "margin-top", "marks",
"marker-offset", "marks", "marquee-direction", "marquee-loop", "marquee-direction", "marquee-loop", "marquee-play-count", "marquee-speed",
"marquee-play-count", "marquee-speed", "marquee-style", "max-height", "marquee-style", "mask-clip", "mask-composite", "mask-image", "mask-mode",
"max-width", "min-height", "min-width", "move-to", "nav-down", "nav-index", "mask-origin", "mask-position", "mask-repeat", "mask-size","mask-type",
"nav-left", "nav-right", "nav-up", "object-fit", "object-position", "max-block-size", "max-height", "max-inline-size",
"opacity", "order", "orphans", "outline", "max-width", "min-block-size", "min-height", "min-inline-size", "min-width",
"outline-color", "outline-offset", "outline-style", "outline-width", "mix-blend-mode", "move-to", "nav-down", "nav-index", "nav-left", "nav-right",
"overflow", "overflow-style", "overflow-wrap", "overflow-x", "overflow-y", "nav-up", "object-fit", "object-position", "offset", "offset-anchor",
"padding", "padding-bottom", "padding-left", "padding-right", "padding-top", "offset-distance", "offset-path", "offset-position", "offset-rotate",
"page", "page-break-after", "page-break-before", "page-break-inside", "opacity", "order", "orphans", "outline", "outline-color", "outline-offset",
"page-policy", "pause", "pause-after", "pause-before", "perspective", "outline-style", "outline-width", "overflow", "overflow-style",
"perspective-origin", "pitch", "pitch-range", "play-during", "position", "overflow-wrap", "overflow-x", "overflow-y", "padding", "padding-bottom",
"presentation-level", "punctuation-trim", "quotes", "region-break-after", "padding-left", "padding-right", "padding-top", "page", "page-break-after",
"region-break-before", "region-break-inside", "region-fragment", "page-break-before", "page-break-inside", "page-policy", "pause",
"rendering-intent", "resize", "rest", "rest-after", "rest-before", "richness", "pause-after", "pause-before", "perspective", "perspective-origin", "pitch",
"right", "rotation", "rotation-point", "ruby-align", "ruby-overhang", "pitch-range", "place-content", "place-items", "place-self", "play-during",
"ruby-position", "ruby-span", "shape-image-threshold", "shape-inside", "shape-margin", "position", "presentation-level", "punctuation-trim", "quotes",
"shape-outside", "size", "speak", "speak-as", "speak-header", "region-break-after", "region-break-before", "region-break-inside",
"speak-numeral", "speak-punctuation", "speech-rate", "stress", "string-set", "region-fragment", "rendering-intent", "resize", "rest", "rest-after",
"tab-size", "table-layout", "target", "target-name", "target-new", "rest-before", "richness", "right", "rotate", "rotation", "rotation-point",
"target-position", "text-align", "text-align-last", "text-decoration", "row-gap", "ruby-align", "ruby-overhang", "ruby-position", "ruby-span",
"scale", "scroll-behavior", "scroll-margin", "scroll-margin-block",
"scroll-margin-block-end", "scroll-margin-block-start", "scroll-margin-bottom",
"scroll-margin-inline", "scroll-margin-inline-end",
"scroll-margin-inline-start", "scroll-margin-left", "scroll-margin-right",
"scroll-margin-top", "scroll-padding", "scroll-padding-block",
"scroll-padding-block-end", "scroll-padding-block-start",
"scroll-padding-bottom", "scroll-padding-inline", "scroll-padding-inline-end",
"scroll-padding-inline-start", "scroll-padding-left", "scroll-padding-right",
"scroll-padding-top", "scroll-snap-align", "scroll-snap-type",
"shape-image-threshold", "shape-inside", "shape-margin", "shape-outside",
"size", "speak", "speak-as", "speak-header", "speak-numeral",
"speak-punctuation", "speech-rate", "stress", "string-set", "tab-size",
"table-layout", "target", "target-name", "target-new", "target-position",
"text-align", "text-align-last", "text-combine-upright", "text-decoration",
"text-decoration-color", "text-decoration-line", "text-decoration-skip", "text-decoration-color", "text-decoration-line", "text-decoration-skip",
"text-decoration-style", "text-emphasis", "text-emphasis-color", "text-decoration-skip-ink", "text-decoration-style", "text-emphasis",
"text-emphasis-position", "text-emphasis-style", "text-height", "text-emphasis-color", "text-emphasis-position", "text-emphasis-style",
"text-indent", "text-justify", "text-outline", "text-overflow", "text-shadow", "text-height", "text-indent", "text-justify", "text-orientation",
"text-size-adjust", "text-space-collapse", "text-transform", "text-underline-position", "text-outline", "text-overflow", "text-rendering", "text-shadow",
"text-wrap", "top", "transform", "transform-origin", "transform-style", "text-size-adjust", "text-space-collapse", "text-transform",
"transition", "transition-delay", "transition-duration", "text-underline-position", "text-wrap", "top", "touch-action", "transform", "transform-origin",
"transition-property", "transition-timing-function", "unicode-bidi", "transform-style", "transition", "transition-delay", "transition-duration",
"vertical-align", "visibility", "voice-balance", "voice-duration", "transition-property", "transition-timing-function", "translate",
"voice-family", "voice-pitch", "voice-range", "voice-rate", "voice-stress", "unicode-bidi", "user-select", "vertical-align", "visibility", "voice-balance",
"voice-volume", "volume", "white-space", "widows", "width", "word-break", "voice-duration", "voice-family", "voice-pitch", "voice-range", "voice-rate",
"word-spacing", "word-wrap", "z-index", "voice-stress", "voice-volume", "volume", "white-space", "widows", "width",
"will-change", "word-break", "word-spacing", "word-wrap", "writing-mode", "z-index",
// SVG-specific // SVG-specific
"clip-path", "clip-rule", "mask", "enable-background", "filter", "flood-color", "clip-path", "clip-rule", "mask", "enable-background", "filter", "flood-color",
"flood-opacity", "lighting-color", "stop-color", "stop-opacity", "pointer-events", "flood-opacity", "lighting-color", "stop-color", "stop-opacity", "pointer-events",
"color-interpolation", "color-interpolation-filters", "color-interpolation", "color-interpolation-filters",
"color-rendering", "fill", "fill-opacity", "fill-rule", "image-rendering", "color-rendering", "fill", "fill-opacity", "fill-rule", "image-rendering",
"marker", "marker-end", "marker-mid", "marker-start", "shape-rendering", "stroke", "marker", "marker-end", "marker-mid", "marker-start", "paint-order", "shape-rendering", "stroke",
"stroke-dasharray", "stroke-dashoffset", "stroke-linecap", "stroke-linejoin", "stroke-dasharray", "stroke-dashoffset", "stroke-linecap", "stroke-linejoin",
"stroke-miterlimit", "stroke-opacity", "stroke-width", "text-rendering", "stroke-miterlimit", "stroke-opacity", "stroke-width", "text-rendering",
"baseline-shift", "dominant-baseline", "glyph-orientation-horizontal", "baseline-shift", "dominant-baseline", "glyph-orientation-horizontal",
"glyph-orientation-vertical", "text-anchor", "writing-mode" "glyph-orientation-vertical", "text-anchor", "writing-mode","view-transition-name"
], propertyKeywords = keySet(propertyKeywords_); ], propertyKeywords = keySet(propertyKeywords_);
var nonStandardPropertyKeywords_ = [ var nonStandardPropertyKeywords_ = [
"accent-color", "aspect-ratio", "border-block", "border-block-color", "border-block-end",
"border-block-end-color", "border-block-end-style", "border-block-end-width",
"border-block-start", "border-block-start-color", "border-block-start-style",
"border-block-start-width", "border-block-style", "border-block-width",
"border-inline", "border-inline-color", "border-inline-end",
"border-inline-end-color", "border-inline-end-style",
"border-inline-end-width", "border-inline-start", "border-inline-start-color",
"border-inline-start-style", "border-inline-start-width",
"border-inline-style", "border-inline-width", "content-visibility", "margin-block",
"margin-block-end", "margin-block-start", "margin-inline", "margin-inline-end",
"margin-inline-start", "overflow-anchor", "overscroll-behavior", "padding-block", "padding-block-end",
"padding-block-start", "padding-inline", "padding-inline-end",
"padding-inline-start", "scroll-snap-stop", "scrollbar-3d-light-color",
"scrollbar-arrow-color", "scrollbar-base-color", "scrollbar-dark-shadow-color", "scrollbar-arrow-color", "scrollbar-base-color", "scrollbar-dark-shadow-color",
"scrollbar-face-color", "scrollbar-highlight-color", "scrollbar-shadow-color", "scrollbar-face-color", "scrollbar-highlight-color", "scrollbar-shadow-color",
"scrollbar-3d-light-color", "scrollbar-track-color", "shape-inside", "scrollbar-track-color", "searchfield-cancel-button", "searchfield-decoration",
"searchfield-cancel-button", "searchfield-decoration", "searchfield-results-button", "searchfield-results-button", "searchfield-results-decoration", "shape-inside", "zoom"
"searchfield-results-decoration", "zoom"
], nonStandardPropertyKeywords = keySet(nonStandardPropertyKeywords_); ], nonStandardPropertyKeywords = keySet(nonStandardPropertyKeywords_);
var fontProperties_ = [ var fontProperties_ = [
"font-family", "src", "unicode-range", "font-variant", "font-feature-settings", "font-display", "font-family", "src", "unicode-range", "font-variant",
"font-stretch", "font-weight", "font-style" "font-feature-settings", "font-stretch", "font-weight", "font-style"
], fontProperties = keySet(fontProperties_); ], fontProperties = keySet(fontProperties_);
var counterDescriptors_ = [ var counterDescriptors_ = [
@@ -561,16 +600,16 @@ CodeMirror.defineMode("css", function(config, parserConfig) {
"bisque", "black", "blanchedalmond", "blue", "blueviolet", "brown", "bisque", "black", "blanchedalmond", "blue", "blueviolet", "brown",
"burlywood", "cadetblue", "chartreuse", "chocolate", "coral", "cornflowerblue", "burlywood", "cadetblue", "chartreuse", "chocolate", "coral", "cornflowerblue",
"cornsilk", "crimson", "cyan", "darkblue", "darkcyan", "darkgoldenrod", "cornsilk", "crimson", "cyan", "darkblue", "darkcyan", "darkgoldenrod",
"darkgray", "darkgreen", "darkkhaki", "darkmagenta", "darkolivegreen", "darkgray", "darkgreen", "darkgrey", "darkkhaki", "darkmagenta", "darkolivegreen",
"darkorange", "darkorchid", "darkred", "darksalmon", "darkseagreen", "darkorange", "darkorchid", "darkred", "darksalmon", "darkseagreen",
"darkslateblue", "darkslategray", "darkturquoise", "darkviolet", "darkslateblue", "darkslategray", "darkslategrey", "darkturquoise", "darkviolet",
"deeppink", "deepskyblue", "dimgray", "dodgerblue", "firebrick", "deeppink", "deepskyblue", "dimgray", "dimgrey", "dodgerblue", "firebrick",
"floralwhite", "forestgreen", "fuchsia", "gainsboro", "ghostwhite", "floralwhite", "forestgreen", "fuchsia", "gainsboro", "ghostwhite",
"gold", "goldenrod", "gray", "grey", "green", "greenyellow", "honeydew", "gold", "goldenrod", "gray", "grey", "green", "greenyellow", "honeydew",
"hotpink", "indianred", "indigo", "ivory", "khaki", "lavender", "hotpink", "indianred", "indigo", "ivory", "khaki", "lavender",
"lavenderblush", "lawngreen", "lemonchiffon", "lightblue", "lightcoral", "lavenderblush", "lawngreen", "lemonchiffon", "lightblue", "lightcoral",
"lightcyan", "lightgoldenrodyellow", "lightgray", "lightgreen", "lightpink", "lightcyan", "lightgoldenrodyellow", "lightgray", "lightgreen", "lightgrey", "lightpink",
"lightsalmon", "lightseagreen", "lightskyblue", "lightslategray", "lightsalmon", "lightseagreen", "lightskyblue", "lightslategray", "lightslategrey",
"lightsteelblue", "lightyellow", "lime", "limegreen", "linen", "magenta", "lightsteelblue", "lightyellow", "lime", "limegreen", "linen", "magenta",
"maroon", "mediumaquamarine", "mediumblue", "mediumorchid", "mediumpurple", "maroon", "mediumaquamarine", "mediumblue", "mediumorchid", "mediumpurple",
"mediumseagreen", "mediumslateblue", "mediumspringgreen", "mediumturquoise", "mediumseagreen", "mediumslateblue", "mediumspringgreen", "mediumturquoise",
@@ -580,7 +619,7 @@ CodeMirror.defineMode("css", function(config, parserConfig) {
"papayawhip", "peachpuff", "peru", "pink", "plum", "powderblue", "papayawhip", "peachpuff", "peru", "pink", "plum", "powderblue",
"purple", "rebeccapurple", "red", "rosybrown", "royalblue", "saddlebrown", "purple", "rebeccapurple", "red", "rosybrown", "royalblue", "saddlebrown",
"salmon", "sandybrown", "seagreen", "seashell", "sienna", "silver", "skyblue", "salmon", "sandybrown", "seagreen", "seashell", "sienna", "silver", "skyblue",
"slateblue", "slategray", "snow", "springgreen", "steelblue", "tan", "slateblue", "slategray", "slategrey", "snow", "springgreen", "steelblue", "tan",
"teal", "thistle", "tomato", "turquoise", "violet", "wheat", "white", "teal", "thistle", "tomato", "turquoise", "violet", "wheat", "white",
"whitesmoke", "yellow", "yellowgreen" "whitesmoke", "yellow", "yellowgreen"
], colorKeywords = keySet(colorKeywords_); ], colorKeywords = keySet(colorKeywords_);
@@ -589,23 +628,23 @@ CodeMirror.defineMode("css", function(config, parserConfig) {
"above", "absolute", "activeborder", "additive", "activecaption", "afar", "above", "absolute", "activeborder", "additive", "activecaption", "afar",
"after-white-space", "ahead", "alias", "all", "all-scroll", "alphabetic", "alternate", "after-white-space", "ahead", "alias", "all", "all-scroll", "alphabetic", "alternate",
"always", "amharic", "amharic-abegede", "antialiased", "appworkspace", "always", "amharic", "amharic-abegede", "antialiased", "appworkspace",
"arabic-indic", "armenian", "asterisks", "attr", "auto", "avoid", "avoid-column", "avoid-page", "arabic-indic", "armenian", "asterisks", "attr", "auto", "auto-flow", "avoid", "avoid-column", "avoid-page",
"avoid-region", "background", "backwards", "baseline", "below", "bidi-override", "binary", "avoid-region", "axis-pan", "background", "backwards", "baseline", "below", "bidi-override", "binary",
"bengali", "blink", "block", "block-axis", "bold", "bolder", "border", "border-box", "bengali", "blink", "block", "block-axis", "blur", "bold", "bolder", "border", "border-box",
"both", "bottom", "break", "break-all", "break-word", "bullets", "button", "button-bevel", "both", "bottom", "break", "break-all", "break-word", "brightness", "bullets", "button",
"buttonface", "buttonhighlight", "buttonshadow", "buttontext", "calc", "cambodian", "buttonface", "buttonhighlight", "buttonshadow", "buttontext", "calc", "cambodian",
"capitalize", "caps-lock-indicator", "caption", "captiontext", "caret", "capitalize", "caps-lock-indicator", "caption", "captiontext", "caret",
"cell", "center", "checkbox", "circle", "cjk-decimal", "cjk-earthly-branch", "cell", "center", "checkbox", "circle", "cjk-decimal", "cjk-earthly-branch",
"cjk-heavenly-stem", "cjk-ideographic", "clear", "clip", "close-quote", "cjk-heavenly-stem", "cjk-ideographic", "clear", "clip", "close-quote",
"col-resize", "collapse", "color", "color-burn", "color-dodge", "column", "column-reverse", "col-resize", "collapse", "color", "color-burn", "color-dodge", "column", "column-reverse",
"compact", "condensed", "contain", "content", "compact", "condensed", "conic-gradient", "contain", "content", "contents",
"content-box", "context-menu", "continuous", "copy", "counter", "counters", "cover", "crop", "content-box", "context-menu", "continuous", "contrast", "copy", "counter", "counters", "cover", "crop",
"cross", "crosshair", "currentcolor", "cursive", "cyclic", "darken", "dashed", "decimal", "cross", "crosshair", "cubic-bezier", "currentcolor", "cursive", "cyclic", "darken", "dashed", "decimal",
"decimal-leading-zero", "default", "default-button", "destination-atop", "decimal-leading-zero", "default", "default-button", "dense", "destination-atop",
"destination-in", "destination-out", "destination-over", "devanagari", "difference", "destination-in", "destination-out", "destination-over", "devanagari", "difference",
"disc", "discard", "disclosure-closed", "disclosure-open", "document", "disc", "discard", "disclosure-closed", "disclosure-open", "document",
"dot-dash", "dot-dot-dash", "dot-dash", "dot-dot-dash",
"dotted", "double", "down", "e-resize", "ease", "ease-in", "ease-in-out", "ease-out", "dotted", "double", "down", "drop-shadow", "e-resize", "ease", "ease-in", "ease-in-out", "ease-out",
"element", "ellipse", "ellipsis", "embed", "end", "ethiopic", "ethiopic-abegede", "element", "ellipse", "ellipsis", "embed", "end", "ethiopic", "ethiopic-abegede",
"ethiopic-abegede-am-et", "ethiopic-abegede-gez", "ethiopic-abegede-ti-er", "ethiopic-abegede-am-et", "ethiopic-abegede-gez", "ethiopic-abegede-ti-er",
"ethiopic-abegede-ti-et", "ethiopic-halehame-aa-er", "ethiopic-abegede-ti-et", "ethiopic-halehame-aa-er",
@@ -614,14 +653,14 @@ CodeMirror.defineMode("css", function(config, parserConfig) {
"ethiopic-halehame-sid-et", "ethiopic-halehame-so-et", "ethiopic-halehame-sid-et", "ethiopic-halehame-so-et",
"ethiopic-halehame-ti-er", "ethiopic-halehame-ti-et", "ethiopic-halehame-tig", "ethiopic-halehame-ti-er", "ethiopic-halehame-ti-et", "ethiopic-halehame-tig",
"ethiopic-numeric", "ew-resize", "exclusion", "expanded", "extends", "extra-condensed", "ethiopic-numeric", "ew-resize", "exclusion", "expanded", "extends", "extra-condensed",
"extra-expanded", "fantasy", "fast", "fill", "fixed", "flat", "flex", "flex-end", "flex-start", "footnotes", "extra-expanded", "fantasy", "fast", "fill", "fill-box", "fixed", "flat", "flex", "flex-end", "flex-start", "footnotes",
"forwards", "from", "geometricPrecision", "georgian", "graytext", "groove", "forwards", "from", "geometricPrecision", "georgian", "grayscale", "graytext", "grid", "groove",
"gujarati", "gurmukhi", "hand", "hangul", "hangul-consonant", "hard-light", "hebrew", "gujarati", "gurmukhi", "hand", "hangul", "hangul-consonant", "hard-light", "hebrew",
"help", "hidden", "hide", "higher", "highlight", "highlighttext", "help", "hidden", "hide", "higher", "highlight", "highlighttext",
"hiragana", "hiragana-iroha", "horizontal", "hsl", "hsla", "hue", "icon", "ignore", "hiragana", "hiragana-iroha", "horizontal", "hsl", "hsla", "hue", "hue-rotate", "icon", "ignore",
"inactiveborder", "inactivecaption", "inactivecaptiontext", "infinite", "inactiveborder", "inactivecaption", "inactivecaptiontext", "infinite",
"infobackground", "infotext", "inherit", "initial", "inline", "inline-axis", "infobackground", "infotext", "inherit", "initial", "inline", "inline-axis",
"inline-block", "inline-flex", "inline-table", "inset", "inside", "intrinsic", "invert", "inline-block", "inline-flex", "inline-grid", "inline-table", "inset", "inside", "intrinsic", "invert",
"italic", "japanese-formal", "japanese-informal", "justify", "kannada", "italic", "japanese-formal", "japanese-informal", "justify", "kannada",
"katakana", "katakana-iroha", "keep-all", "khmer", "katakana", "katakana-iroha", "keep-all", "khmer",
"korean-hangul-formal", "korean-hanja-formal", "korean-hanja-informal", "korean-hangul-formal", "korean-hanja-formal", "korean-hanja-informal",
@@ -629,41 +668,37 @@ CodeMirror.defineMode("css", function(config, parserConfig) {
"line-through", "linear", "linear-gradient", "lines", "list-item", "listbox", "listitem", "line-through", "linear", "linear-gradient", "lines", "list-item", "listbox", "listitem",
"local", "logical", "loud", "lower", "lower-alpha", "lower-armenian", "local", "logical", "loud", "lower", "lower-alpha", "lower-armenian",
"lower-greek", "lower-hexadecimal", "lower-latin", "lower-norwegian", "lower-greek", "lower-hexadecimal", "lower-latin", "lower-norwegian",
"lower-roman", "lowercase", "ltr", "luminosity", "malayalam", "match", "matrix", "matrix3d", "lower-roman", "lowercase", "ltr", "luminosity", "malayalam", "manipulation", "match", "matrix", "matrix3d",
"media-controls-background", "media-current-time-display", "media-play-button", "media-slider", "media-sliderthumb",
"media-fullscreen-button", "media-mute-button", "media-play-button", "media-volume-slider", "media-volume-sliderthumb", "medium",
"media-return-to-realtime-button", "media-rewind-button", "menu", "menulist", "menulist-button",
"media-seek-back-button", "media-seek-forward-button", "media-slider", "menutext", "message-box", "middle", "min-intrinsic",
"media-sliderthumb", "media-time-remaining-display", "media-volume-slider", "mix", "mongolian", "monospace", "move", "multiple", "multiple_mask_images", "multiply", "myanmar", "n-resize",
"media-volume-slider-container", "media-volume-sliderthumb", "medium",
"menu", "menulist", "menulist-button", "menulist-text",
"menulist-textfield", "menutext", "message-box", "middle", "min-intrinsic",
"mix", "mongolian", "monospace", "move", "multiple", "multiply", "myanmar", "n-resize",
"narrower", "ne-resize", "nesw-resize", "no-close-quote", "no-drop", "narrower", "ne-resize", "nesw-resize", "no-close-quote", "no-drop",
"no-open-quote", "no-repeat", "none", "normal", "not-allowed", "nowrap", "no-open-quote", "no-repeat", "none", "normal", "not-allowed", "nowrap",
"ns-resize", "numbers", "numeric", "nw-resize", "nwse-resize", "oblique", "octal", "open-quote", "ns-resize", "numbers", "numeric", "nw-resize", "nwse-resize", "oblique", "octal", "opacity", "open-quote",
"optimizeLegibility", "optimizeSpeed", "oriya", "oromo", "outset", "optimizeLegibility", "optimizeSpeed", "oriya", "oromo", "outset",
"outside", "outside-shape", "overlay", "overline", "padding", "padding-box", "outside", "outside-shape", "overlay", "overline", "padding", "padding-box",
"painted", "page", "paused", "persian", "perspective", "plus-darker", "plus-lighter", "painted", "page", "paused", "persian", "perspective", "pinch-zoom", "plus-darker", "plus-lighter",
"pointer", "polygon", "portrait", "pre", "pre-line", "pre-wrap", "preserve-3d", "pointer", "polygon", "portrait", "pre", "pre-line", "pre-wrap", "preserve-3d",
"progress", "push-button", "radial-gradient", "radio", "read-only", "progress", "push-button", "radial-gradient", "radio", "read-only",
"read-write", "read-write-plaintext-only", "rectangle", "region", "read-write", "read-write-plaintext-only", "rectangle", "region",
"relative", "repeat", "repeating-linear-gradient", "relative", "repeat", "repeating-linear-gradient", "repeating-radial-gradient",
"repeating-radial-gradient", "repeat-x", "repeat-y", "reset", "reverse", "repeating-conic-gradient", "repeat-x", "repeat-y", "reset", "reverse",
"rgb", "rgba", "ridge", "right", "rotate", "rotate3d", "rotateX", "rotateY", "rgb", "rgba", "ridge", "right", "rotate", "rotate3d", "rotateX", "rotateY",
"rotateZ", "round", "row", "row-resize", "row-reverse", "rtl", "run-in", "running", "rotateZ", "round", "row", "row-resize", "row-reverse", "rtl", "run-in", "running",
"s-resize", "sans-serif", "saturation", "scale", "scale3d", "scaleX", "scaleY", "scaleZ", "screen", "s-resize", "sans-serif", "saturate", "saturation", "scale", "scale3d", "scaleX", "scaleY", "scaleZ", "screen",
"scroll", "scrollbar", "se-resize", "searchfield", "scroll", "scrollbar", "scroll-position", "se-resize", "searchfield",
"searchfield-cancel-button", "searchfield-decoration", "searchfield-cancel-button", "searchfield-decoration",
"searchfield-results-button", "searchfield-results-decoration", "searchfield-results-button", "searchfield-results-decoration", "self-start", "self-end",
"semi-condensed", "semi-expanded", "separate", "serif", "show", "sidama", "semi-condensed", "semi-expanded", "separate", "sepia", "serif", "show", "sidama",
"simp-chinese-formal", "simp-chinese-informal", "single", "simp-chinese-formal", "simp-chinese-informal", "single",
"skew", "skewX", "skewY", "skip-white-space", "slide", "slider-horizontal", "skew", "skewX", "skewY", "skip-white-space", "slide", "slider-horizontal",
"slider-vertical", "sliderthumb-horizontal", "sliderthumb-vertical", "slow", "slider-vertical", "sliderthumb-horizontal", "sliderthumb-vertical", "slow",
"small", "small-caps", "small-caption", "smaller", "soft-light", "solid", "somali", "small", "small-caps", "small-caption", "smaller", "soft-light", "solid", "somali",
"source-atop", "source-in", "source-out", "source-over", "space", "space-around", "space-between", "spell-out", "square", "source-atop", "source-in", "source-out", "source-over", "space", "space-around", "space-between", "space-evenly", "spell-out", "square",
"square-button", "start", "static", "status-bar", "stretch", "stroke", "sub", "square-button", "start", "static", "status-bar", "stretch", "stroke", "stroke-box", "sub",
"subpixel-antialiased", "super", "sw-resize", "symbolic", "symbols", "table", "subpixel-antialiased", "svg_masks", "super", "sw-resize", "symbolic", "symbols", "system-ui", "table",
"table-caption", "table-cell", "table-column", "table-column-group", "table-caption", "table-cell", "table-column", "table-column-group",
"table-footer-group", "table-header-group", "table-row", "table-row-group", "table-footer-group", "table-header-group", "table-row", "table-row-group",
"tamil", "tamil",
@@ -671,12 +706,12 @@ CodeMirror.defineMode("css", function(config, parserConfig) {
"thick", "thin", "threeddarkshadow", "threedface", "threedhighlight", "thick", "thin", "threeddarkshadow", "threedface", "threedhighlight",
"threedlightshadow", "threedshadow", "tibetan", "tigre", "tigrinya-er", "threedlightshadow", "threedshadow", "tibetan", "tigre", "tigrinya-er",
"tigrinya-er-abegede", "tigrinya-et", "tigrinya-et-abegede", "to", "top", "tigrinya-er-abegede", "tigrinya-et", "tigrinya-et-abegede", "to", "top",
"trad-chinese-formal", "trad-chinese-informal", "trad-chinese-formal", "trad-chinese-informal", "transform",
"translate", "translate3d", "translateX", "translateY", "translateZ", "translate", "translate3d", "translateX", "translateY", "translateZ",
"transparent", "ultra-condensed", "ultra-expanded", "underline", "up", "transparent", "ultra-condensed", "ultra-expanded", "underline", "unidirectional-pan", "unset", "up",
"upper-alpha", "upper-armenian", "upper-greek", "upper-hexadecimal", "upper-alpha", "upper-armenian", "upper-greek", "upper-hexadecimal",
"upper-latin", "upper-norwegian", "upper-roman", "uppercase", "urdu", "url", "upper-latin", "upper-norwegian", "upper-roman", "uppercase", "urdu", "url",
"var", "vertical", "vertical-text", "visible", "visibleFill", "visiblePainted", "var", "vertical", "vertical-text", "view-box", "visible", "visibleFill", "visiblePainted",
"visibleStroke", "visual", "w-resize", "wait", "wave", "wider", "visibleStroke", "visual", "w-resize", "wait", "wave", "wider",
"window", "windowframe", "windowtext", "words", "wrap", "wrap-reverse", "x-large", "x-small", "xor", "window", "windowframe", "windowtext", "words", "wrap", "wrap-reverse", "x-large", "x-small", "xor",
"xx-large", "xx-small" "xx-large", "xx-small"
@@ -730,6 +765,7 @@ CodeMirror.defineMode("css", function(config, parserConfig) {
valueKeywords: valueKeywords, valueKeywords: valueKeywords,
fontProperties: fontProperties, fontProperties: fontProperties,
allowNested: true, allowNested: true,
lineComment: "//",
tokenHooks: { tokenHooks: {
"/": function(stream, state) { "/": function(stream, state) {
if (stream.eat("/")) { if (stream.eat("/")) {
@@ -743,8 +779,8 @@ CodeMirror.defineMode("css", function(config, parserConfig) {
} }
}, },
":": function(stream) { ":": function(stream) {
if (stream.match(/\s*\{/)) if (stream.match(/^\s*\{/, false))
return [null, "{"]; return [null, null]
return false; return false;
}, },
"$": function(stream) { "$": function(stream) {
@@ -772,6 +808,7 @@ CodeMirror.defineMode("css", function(config, parserConfig) {
valueKeywords: valueKeywords, valueKeywords: valueKeywords,
fontProperties: fontProperties, fontProperties: fontProperties,
allowNested: true, allowNested: true,
lineComment: "//",
tokenHooks: { tokenHooks: {
"/": function(stream, state) { "/": function(stream, state) {
if (stream.eat("/")) { if (stream.eat("/")) {
@@ -786,7 +823,7 @@ CodeMirror.defineMode("css", function(config, parserConfig) {
}, },
"@": function(stream) { "@": function(stream) {
if (stream.eat("{")) return [null, "interpolation"]; if (stream.eat("{")) return [null, "interpolation"];
if (stream.match(/^(charset|document|font-face|import|(-(moz|ms|o|webkit)-)?keyframes|media|namespace|page|supports)\b/, false)) return false; if (stream.match(/^(charset|document|font-face|import|(-(moz|ms|o|webkit)-)?keyframes|media|namespace|page|supports)\b/i, false)) return false;
stream.eatWhile(/[\w\\\-]/); stream.eatWhile(/[\w\\\-]/);
if (stream.match(/^\s*:/, false)) if (stream.match(/^\s*:/, false))
return ["variable-2", "variable-definition"]; return ["variable-2", "variable-definition"];

View File

@@ -1,103 +0,0 @@
<!doctype html>
<title>CodeMirror: Closure Stylesheets (GSS) mode</title>
<meta charset="utf-8"/>
<link rel=stylesheet href="../../doc/docs.css">
<link rel="stylesheet" href="../../lib/codemirror.css">
<link rel="stylesheet" href="../../addon/hint/show-hint.css">
<script src="../../lib/codemirror.js"></script>
<script src="css.js"></script>
<script src="../../addon/hint/show-hint.js"></script>
<script src="../../addon/hint/css-hint.js"></script>
<style>.CodeMirror {background: #f8f8f8;}</style>
<div id=nav>
<a href="http://codemirror.net"><h1>CodeMirror</h1><img id=logo src="../../doc/logo.png"></a>
<ul>
<li><a href="../../index.html">Home</a>
<li><a href="../../doc/manual.html">Manual</a>
<li><a href="https://github.com/codemirror/codemirror">Code</a>
</ul>
<ul>
<li><a href="../index.html">Language modes</a>
<li><a class=active href="#">Closure Stylesheets (GSS)</a>
</ul>
</div>
<article>
<h2>Closure Stylesheets (GSS) mode</h2>
<form><textarea id="code" name="code">
/* Some example Closure Stylesheets */
@provide 'some.styles';
@require 'other.styles';
@component {
@def FONT_FAMILY "Times New Roman", Georgia, Serif;
@def FONT_SIZE_NORMAL 15px;
@def FONT_NORMAL normal FONT_SIZE_NORMAL FONT_FAMILY;
@def BG_COLOR rgb(235, 239, 249);
@def DIALOG_BORDER_COLOR rgb(107, 144, 218);
@def DIALOG_BG_COLOR BG_COLOR;
@def LEFT_HAND_NAV_WIDTH 180px;
@def LEFT_HAND_NAV_PADDING 3px;
@defmixin size(WIDTH, HEIGHT) {
width: WIDTH;
height: HEIGHT;
}
body {
background-color: BG_COLOR;
margin: 0;
padding: 3em 6em;
font: FONT_NORMAL;
color: #000;
}
#navigation a {
font-weight: bold;
text-decoration: none !important;
}
.dialog {
background-color: DIALOG_BG_COLOR;
border: 1px solid DIALOG_BORDER_COLOR;
}
.content {
position: absolute;
margin-left: add(LEFT_HAND_NAV_PADDING, /* padding left */
LEFT_HAND_NAV_WIDTH,
LEFT_HAND_NAV_PADDING); /* padding right */
}
.logo {
@mixin size(150px, 55px);
background-image: url('http://www.google.com/images/logo_sm.gif');
}
}
</textarea></form>
<script>
var editor = CodeMirror.fromTextArea(document.getElementById("code"), {
extraKeys: {"Ctrl-Space": "autocomplete"},
lineNumbers: true,
matchBrackets: "text/x-less",
mode: "text/x-gss"
});
</script>
<p>A mode for <a href="https://github.com/google/closure-stylesheets">Closure Stylesheets</a> (GSS).</p>
<p><strong>MIME type defined:</strong> <code>text/x-gss</code>.</p>
<p><strong>Parsing/Highlighting Tests:</strong> <a href="../../test/index.html#gss_*">normal</a>, <a href="../../test/index.html#verbose,gss_*">verbose</a>.</p>
</article>

View File

@@ -1,17 +0,0 @@
// CodeMirror, copyright (c) by Marijn Haverbeke and others
// Distributed under an MIT license: http://codemirror.net/LICENSE
(function() {
"use strict";
var mode = CodeMirror.getMode({indentUnit: 2}, "text/x-gss");
function MT(name) { test.mode(name, mode, Array.prototype.slice.call(arguments, 1), "gss"); }
MT("atComponent",
"[def @component] {",
"[tag foo] {",
" [property color]: [keyword black];",
"}",
"}");
})();

View File

@@ -1,152 +0,0 @@
<!doctype html>
<title>CodeMirror: LESS mode</title>
<meta charset="utf-8"/>
<link rel=stylesheet href="../../doc/docs.css">
<link rel="stylesheet" href="../../lib/codemirror.css">
<script src="../../lib/codemirror.js"></script>
<script src="../../addon/edit/matchbrackets.js"></script>
<script src="css.js"></script>
<style>.CodeMirror {border: 1px solid #ddd; line-height: 1.2;}</style>
<div id=nav>
<a href="http://codemirror.net"><h1>CodeMirror</h1><img id=logo src="../../doc/logo.png"></a>
<ul>
<li><a href="../../index.html">Home</a>
<li><a href="../../doc/manual.html">Manual</a>
<li><a href="https://github.com/codemirror/codemirror">Code</a>
</ul>
<ul>
<li><a href="../index.html">Language modes</a>
<li><a class=active href="#">LESS</a>
</ul>
</div>
<article>
<h2>LESS mode</h2>
<form><textarea id="code" name="code">@media screen and (device-aspect-ratio: 16/9) { … }
@media screen and (device-aspect-ratio: 1280/720) { … }
@media screen and (device-aspect-ratio: 2560/1440) { … }
html:lang(fr-be)
tr:nth-child(2n+1) /* represents every odd row of an HTML table */
img:nth-of-type(2n+1) { float: right; }
img:nth-of-type(2n) { float: left; }
body > h2:not(:first-of-type):not(:last-of-type)
html|*:not(:link):not(:visited)
*|*:not(:hover)
p::first-line { text-transform: uppercase }
@namespace foo url(http://www.example.com);
foo|h1 { color: blue } /* first rule */
span[hello="Ocean"][goodbye="Land"]
E[foo]{
padding:65px;
}
input[type="search"]::-webkit-search-decoration,
input[type="search"]::-webkit-search-cancel-button {
-webkit-appearance: none; // Inner-padding issues in Chrome OSX, Safari 5
}
button::-moz-focus-inner,
input::-moz-focus-inner { // Inner padding and border oddities in FF3/4
padding: 0;
border: 0;
}
.btn {
// reset here as of 2.0.3 due to Recess property order
border-color: #ccc;
border-color: rgba(0,0,0,.1) rgba(0,0,0,.1) rgba(0,0,0,.25);
}
fieldset span button, fieldset span input[type="file"] {
font-size:12px;
font-family:Arial, Helvetica, sans-serif;
}
.rounded-corners (@radius: 5px) {
border-radius: @radius;
-webkit-border-radius: @radius;
-moz-border-radius: @radius;
}
@import url("something.css");
@light-blue: hsl(190, 50%, 65%);
#menu {
position: absolute;
width: 100%;
z-index: 3;
clear: both;
display: block;
background-color: @blue;
height: 42px;
border-top: 2px solid lighten(@alpha-blue, 20%);
border-bottom: 2px solid darken(@alpha-blue, 25%);
.box-shadow(0, 1px, 8px, 0.6);
-moz-box-shadow: 0 0 0 #000; // Because firefox sucks.
&.docked {
background-color: hsla(210, 60%, 40%, 0.4);
}
&:hover {
background-color: @blue;
}
#dropdown {
margin: 0 0 0 117px;
padding: 0;
padding-top: 5px;
display: none;
width: 190px;
border-top: 2px solid @medium;
color: @highlight;
border: 2px solid darken(@medium, 25%);
border-left-color: darken(@medium, 15%);
border-right-color: darken(@medium, 15%);
border-top-width: 0;
background-color: darken(@medium, 10%);
ul {
padding: 0px;
}
li {
font-size: 14px;
display: block;
text-align: left;
padding: 0;
border: 0;
a {
display: block;
padding: 0px 15px;
text-decoration: none;
color: white;
&:hover {
background-color: darken(@medium, 15%);
text-decoration: none;
}
}
}
.border-radius(5px, bottom);
.box-shadow(0, 6px, 8px, 0.5);
}
}
</textarea></form>
<script>
var editor = CodeMirror.fromTextArea(document.getElementById("code"), {
lineNumbers : true,
matchBrackets : true,
mode: "text/x-less"
});
</script>
<p>The LESS mode is a sub-mode of the <a href="index.html">CSS mode</a> (defined in <code>css.js</code>).</p>
<p><strong>Parsing/Highlighting Tests:</strong> <a href="../../test/index.html#less_*">normal</a>, <a href="../../test/index.html#verbose,less_*">verbose</a>.</p>
</article>

View File

@@ -1,157 +0,0 @@
<!doctype html>
<title>CodeMirror: SCSS mode</title>
<meta charset="utf-8"/>
<link rel=stylesheet href="../../doc/docs.css">
<link rel="stylesheet" href="../../lib/codemirror.css">
<script src="../../lib/codemirror.js"></script>
<script src="css.js"></script>
<style>.CodeMirror {background: #f8f8f8;}</style>
<div id=nav>
<a href="http://codemirror.net"><h1>CodeMirror</h1><img id=logo src="../../doc/logo.png"></a>
<ul>
<li><a href="../../index.html">Home</a>
<li><a href="../../doc/manual.html">Manual</a>
<li><a href="https://github.com/codemirror/codemirror">Code</a>
</ul>
<ul>
<li><a href="../index.html">Language modes</a>
<li><a class=active href="#">SCSS</a>
</ul>
</div>
<article>
<h2>SCSS mode</h2>
<form><textarea id="code" name="code">
/* Some example SCSS */
@import "compass/css3";
$variable: #333;
$blue: #3bbfce;
$margin: 16px;
.content-navigation {
#nested {
background-color: black;
}
border-color: $blue;
color:
darken($blue, 9%);
}
.border {
padding: $margin / 2;
margin: $margin / 2;
border-color: $blue;
}
@mixin table-base {
th {
text-align: center;
font-weight: bold;
}
td, th {padding: 2px}
}
table.hl {
margin: 2em 0;
td.ln {
text-align: right;
}
}
li {
font: {
family: serif;
weight: bold;
size: 1.2em;
}
}
@mixin left($dist) {
float: left;
margin-left: $dist;
}
#data {
@include left(10px);
@include table-base;
}
.source {
@include flow-into(target);
border: 10px solid green;
margin: 20px;
width: 200px; }
.new-container {
@include flow-from(target);
border: 10px solid red;
margin: 20px;
width: 200px; }
body {
margin: 0;
padding: 3em 6em;
font-family: tahoma, arial, sans-serif;
color: #000;
}
@mixin yellow() {
background: yellow;
}
.big {
font-size: 14px;
}
.nested {
@include border-radius(3px);
@extend .big;
p {
background: whitesmoke;
a {
color: red;
}
}
}
#navigation a {
font-weight: bold;
text-decoration: none !important;
}
h1 {
font-size: 2.5em;
}
h2 {
font-size: 1.7em;
}
h1:before, h2:before {
content: "::";
}
code {
font-family: courier, monospace;
font-size: 80%;
color: #418A8A;
}
</textarea></form>
<script>
var editor = CodeMirror.fromTextArea(document.getElementById("code"), {
lineNumbers: true,
matchBrackets: true,
mode: "text/x-scss"
});
</script>
<p>The SCSS mode is a sub-mode of the <a href="index.html">CSS mode</a> (defined in <code>css.js</code>).</p>
<p><strong>Parsing/Highlighting Tests:</strong> <a href="../../test/index.html#scss_*">normal</a>, <a href="../../test/index.html#verbose,scss_*">verbose</a>.</p>
</article>

View File

@@ -1,5 +1,5 @@
// CodeMirror, copyright (c) by Marijn Haverbeke and others // CodeMirror, copyright (c) by Marijn Haverbeke and others
// Distributed under an MIT license: http://codemirror.net/LICENSE // Distributed under an MIT license: https://codemirror.net/5/LICENSE
(function(mod) { (function(mod) {
if (typeof exports == "object" && typeof module == "object") // CommonJS if (typeof exports == "object" && typeof module == "object") // CommonJS
@@ -98,8 +98,8 @@
return { return {
// default to html mode // default to html mode
startState: function() { startState: function() {
var htmlState = htmlMode.startState(); var htmlState = CodeMirror.startState(htmlMode);
var rubyState = rubyMode.startState(); var rubyState = CodeMirror.startState(rubyMode);
return { return {
htmlState: htmlState, htmlState: htmlState,
rubyState: rubyState, rubyState: rubyState,

View File

@@ -1,5 +1,5 @@
// CodeMirror, copyright (c) by Marijn Haverbeke and others // CodeMirror, copyright (c) by Marijn Haverbeke and others
// Distributed under an MIT license: http://codemirror.net/LICENSE // Distributed under an MIT license: https://codemirror.net/5/LICENSE
(function(mod) { (function(mod) {
if (typeof exports == "object" && typeof module == "object") // CommonJS if (typeof exports == "object" && typeof module == "object") // CommonJS
@@ -14,7 +14,16 @@
"use strict"; "use strict";
CodeMirror.defineMode("htmlembedded", function(config, parserConfig) { CodeMirror.defineMode("htmlembedded", function(config, parserConfig) {
var closeComment = parserConfig.closeComment || "--%>"
return CodeMirror.multiplexingMode(CodeMirror.getMode(config, "htmlmixed"), { return CodeMirror.multiplexingMode(CodeMirror.getMode(config, "htmlmixed"), {
open: parserConfig.openComment || "<%--",
close: closeComment,
delimStyle: "comment",
mode: {token: function(stream) {
stream.skipTo(closeComment) || stream.skipToEnd()
return "comment"
}}
}, {
open: parserConfig.open || parserConfig.scriptStartRegex || "<%", open: parserConfig.open || parserConfig.scriptStartRegex || "<%",
close: parserConfig.close || parserConfig.scriptEndRegex || "%>", close: parserConfig.close || parserConfig.scriptEndRegex || "%>",
mode: CodeMirror.getMode(config, parserConfig.scriptingModeSpec) mode: CodeMirror.getMode(config, parserConfig.scriptingModeSpec)

View File

@@ -1,5 +1,5 @@
// CodeMirror, copyright (c) by Marijn Haverbeke and others // CodeMirror, copyright (c) by Marijn Haverbeke and others
// Distributed under an MIT license: http://codemirror.net/LICENSE // Distributed under an MIT license: https://codemirror.net/5/LICENSE
(function(mod) { (function(mod) {
if (typeof exports == "object" && typeof module == "object") // CommonJS if (typeof exports == "object" && typeof module == "object") // CommonJS
@@ -14,7 +14,7 @@
var defaultTags = { var defaultTags = {
script: [ script: [
["lang", /(javascript|babel)/i, "javascript"], ["lang", /(javascript|babel)/i, "javascript"],
["type", /^(?:text|application)\/(?:x-)?(?:java|ecma)script$|^$/i, "javascript"], ["type", /^(?:text|application)\/(?:x-)?(?:java|ecma)script$|^module$|^$/i, "javascript"],
["type", /./, "text/plain"], ["type", /./, "text/plain"],
[null, null, "javascript"] [null, null, "javascript"]
], ],
@@ -46,11 +46,11 @@
function getAttrValue(text, attr) { function getAttrValue(text, attr) {
var match = text.match(getAttrRegexp(attr)) var match = text.match(getAttrRegexp(attr))
return match ? match[2] : "" return match ? /^\s*(.*?)\s*$/.exec(match[2])[1] : ""
} }
function getTagRegexp(tagName, anchored) { function getTagRegexp(tagName, anchored) {
return new RegExp((anchored ? "^" : "") + "<\/\s*" + tagName + "\s*>", "i"); return new RegExp((anchored ? "^" : "") + "<\/\\s*" + tagName + "\\s*>", "i");
} }
function addTags(from, to) { function addTags(from, to) {
@@ -74,7 +74,8 @@
name: "xml", name: "xml",
htmlMode: true, htmlMode: true,
multilineTagIndentFactor: parserConfig.multilineTagIndentFactor, multilineTagIndentFactor: parserConfig.multilineTagIndentFactor,
multilineTagIndentPastTag: parserConfig.multilineTagIndentPastTag multilineTagIndentPastTag: parserConfig.multilineTagIndentPastTag,
allowMissingTagName: parserConfig.allowMissingTagName,
}); });
var tags = {}; var tags = {};
@@ -105,7 +106,7 @@
return maybeBackup(stream, endTag, state.localMode.token(stream, state.localState)); return maybeBackup(stream, endTag, state.localMode.token(stream, state.localState));
}; };
state.localMode = mode; state.localMode = mode;
state.localState = CodeMirror.startState(mode, htmlMode.indent(state.htmlState, "")); state.localState = CodeMirror.startState(mode, htmlMode.indent(state.htmlState, "", ""));
} else if (state.inTag) { } else if (state.inTag) {
state.inTag += stream.current() state.inTag += stream.current()
if (stream.eol()) state.inTag += " " if (stream.eol()) state.inTag += " "
@@ -115,7 +116,7 @@
return { return {
startState: function () { startState: function () {
var state = htmlMode.startState(); var state = CodeMirror.startState(htmlMode);
return {token: html, inTag: null, localMode: null, localState: null, htmlState: state}; return {token: html, inTag: null, localMode: null, localState: null, htmlState: state};
}, },
@@ -133,11 +134,11 @@
return state.token(stream, state); return state.token(stream, state);
}, },
indent: function (state, textAfter) { indent: function (state, textAfter, line) {
if (!state.localMode || /^\s*<\//.test(textAfter)) if (!state.localMode || /^\s*<\//.test(textAfter))
return htmlMode.indent(state.htmlState, textAfter); return htmlMode.indent(state.htmlState, textAfter, line);
else if (state.localMode.indent) else if (state.localMode.indent)
return state.localMode.indent(state.localState, textAfter); return state.localMode.indent(state.localState, textAfter, line);
else else
return CodeMirror.Pass; return CodeMirror.Pass;
}, },

View File

@@ -1,7 +1,5 @@
// CodeMirror, copyright (c) by Marijn Haverbeke and others // CodeMirror, copyright (c) by Marijn Haverbeke and others
// Distributed under an MIT license: http://codemirror.net/LICENSE // Distributed under an MIT license: https://codemirror.net/5/LICENSE
// TODO actually recognize syntax of TypeScript constructs
(function(mod) { (function(mod) {
if (typeof exports == "object" && typeof module == "object") // CommonJS if (typeof exports == "object" && typeof module == "object") // CommonJS
@@ -13,16 +11,12 @@
})(function(CodeMirror) { })(function(CodeMirror) {
"use strict"; "use strict";
function expressionAllowed(stream, state, backUp) {
return /^(?:operator|sof|keyword c|case|new|[\[{}\(,;:]|=>)$/.test(state.lastType) ||
(state.lastType == "quasi" && /\{\s*$/.test(stream.string.slice(0, stream.pos - (backUp || 0))))
}
CodeMirror.defineMode("javascript", function(config, parserConfig) { CodeMirror.defineMode("javascript", function(config, parserConfig) {
var indentUnit = config.indentUnit; var indentUnit = config.indentUnit;
var statementIndent = parserConfig.statementIndent; var statementIndent = parserConfig.statementIndent;
var jsonldMode = parserConfig.jsonld; var jsonldMode = parserConfig.jsonld;
var jsonMode = parserConfig.json || jsonldMode; var jsonMode = parserConfig.json || jsonldMode;
var trackScope = parserConfig.trackScope !== false
var isTS = parserConfig.typescript; var isTS = parserConfig.typescript;
var wordRE = parserConfig.wordCharacters || /[\w$\xa1-\uffff]/; var wordRE = parserConfig.wordCharacters || /[\w$\xa1-\uffff]/;
@@ -30,54 +24,24 @@ CodeMirror.defineMode("javascript", function(config, parserConfig) {
var keywords = function(){ var keywords = function(){
function kw(type) {return {type: type, style: "keyword"};} function kw(type) {return {type: type, style: "keyword"};}
var A = kw("keyword a"), B = kw("keyword b"), C = kw("keyword c"); var A = kw("keyword a"), B = kw("keyword b"), C = kw("keyword c"), D = kw("keyword d");
var operator = kw("operator"), atom = {type: "atom", style: "atom"}; var operator = kw("operator"), atom = {type: "atom", style: "atom"};
var jsKeywords = { return {
"if": kw("if"), "while": A, "with": A, "else": B, "do": B, "try": B, "finally": B, "if": kw("if"), "while": A, "with": A, "else": B, "do": B, "try": B, "finally": B,
"return": C, "break": C, "continue": C, "new": kw("new"), "delete": C, "throw": C, "debugger": C, "return": D, "break": D, "continue": D, "new": kw("new"), "delete": C, "void": C, "throw": C,
"var": kw("var"), "const": kw("var"), "let": kw("var"), "debugger": kw("debugger"), "var": kw("var"), "const": kw("var"), "let": kw("var"),
"function": kw("function"), "catch": kw("catch"), "function": kw("function"), "catch": kw("catch"),
"for": kw("for"), "switch": kw("switch"), "case": kw("case"), "default": kw("default"), "for": kw("for"), "switch": kw("switch"), "case": kw("case"), "default": kw("default"),
"in": operator, "typeof": operator, "instanceof": operator, "in": operator, "typeof": operator, "instanceof": operator,
"true": atom, "false": atom, "null": atom, "undefined": atom, "NaN": atom, "Infinity": atom, "true": atom, "false": atom, "null": atom, "undefined": atom, "NaN": atom, "Infinity": atom,
"this": kw("this"), "class": kw("class"), "super": kw("atom"), "this": kw("this"), "class": kw("class"), "super": kw("atom"),
"yield": C, "export": kw("export"), "import": kw("import"), "extends": C "yield": C, "export": kw("export"), "import": kw("import"), "extends": C,
"await": C
}; };
// Extend the 'normal' keywords with the TypeScript language extensions
if (isTS) {
var type = {type: "variable", style: "variable-3"};
var tsKeywords = {
// object-like things
"interface": kw("class"),
"implements": C,
"namespace": C,
"module": kw("module"),
"enum": kw("module"),
// scope modifiers
"public": kw("modifier"),
"private": kw("modifier"),
"protected": kw("modifier"),
"abstract": kw("modifier"),
// operators
"as": operator,
// types
"string": type, "number": type, "boolean": type, "any": type
};
for (var attr in tsKeywords) {
jsKeywords[attr] = tsKeywords[attr];
}
}
return jsKeywords;
}(); }();
var isOperatorChar = /[+\-*&%=<>!?|~^]/; var isOperatorChar = /[+\-*&%=<>!?|~^@]/;
var isJsonldKeyword = /^@(context|id|value|language|type|container|list|set|reverse|index|base|vocab|graph)"/; var isJsonldKeyword = /^@(context|id|value|language|type|container|list|set|reverse|index|base|vocab|graph)"/;
function readRegexp(stream) { function readRegexp(stream) {
@@ -104,7 +68,7 @@ CodeMirror.defineMode("javascript", function(config, parserConfig) {
if (ch == '"' || ch == "'") { if (ch == '"' || ch == "'") {
state.tokenize = tokenString(ch); state.tokenize = tokenString(ch);
return state.tokenize(stream, state); return state.tokenize(stream, state);
} else if (ch == "." && stream.match(/^\d+(?:[eE][+\-]?\d+)?/)) { } else if (ch == "." && stream.match(/^\d[\d_]*(?:[eE][+\-]?[\d_]+)?/)) {
return ret("number", "number"); return ret("number", "number");
} else if (ch == "." && stream.match("..")) { } else if (ch == "." && stream.match("..")) {
return ret("spread", "meta"); return ret("spread", "meta");
@@ -112,17 +76,10 @@ CodeMirror.defineMode("javascript", function(config, parserConfig) {
return ret(ch); return ret(ch);
} else if (ch == "=" && stream.eat(">")) { } else if (ch == "=" && stream.eat(">")) {
return ret("=>", "operator"); return ret("=>", "operator");
} else if (ch == "0" && stream.eat(/x/i)) { } else if (ch == "0" && stream.match(/^(?:x[\dA-Fa-f_]+|o[0-7_]+|b[01_]+)n?/)) {
stream.eatWhile(/[\da-f]/i);
return ret("number", "number");
} else if (ch == "0" && stream.eat(/o/i)) {
stream.eatWhile(/[0-7]/i);
return ret("number", "number");
} else if (ch == "0" && stream.eat(/b/i)) {
stream.eatWhile(/[01]/i);
return ret("number", "number"); return ret("number", "number");
} else if (/\d/.test(ch)) { } else if (/\d/.test(ch)) {
stream.match(/^\d*(?:\.\d*)?(?:[eE][+\-]?\d+)?/); stream.match(/^[\d_]*(?:n|(?:\.[\d_]*)?(?:[eE][+\-]?[\d_]+)?)?/);
return ret("number", "number"); return ret("number", "number");
} else if (ch == "/") { } else if (ch == "/") {
if (stream.eat("*")) { if (stream.eat("*")) {
@@ -133,26 +90,47 @@ CodeMirror.defineMode("javascript", function(config, parserConfig) {
return ret("comment", "comment"); return ret("comment", "comment");
} else if (expressionAllowed(stream, state, 1)) { } else if (expressionAllowed(stream, state, 1)) {
readRegexp(stream); readRegexp(stream);
stream.match(/^\b(([gimyu])(?![gimyu]*\2))+\b/); stream.match(/^\b(([gimyus])(?![gimyus]*\2))+\b/);
return ret("regexp", "string-2"); return ret("regexp", "string-2");
} else { } else {
stream.eatWhile(isOperatorChar); stream.eat("=");
return ret("operator", "operator", stream.current()); return ret("operator", "operator", stream.current());
} }
} else if (ch == "`") { } else if (ch == "`") {
state.tokenize = tokenQuasi; state.tokenize = tokenQuasi;
return tokenQuasi(stream, state); return tokenQuasi(stream, state);
} else if (ch == "#") { } else if (ch == "#" && stream.peek() == "!") {
stream.skipToEnd(); stream.skipToEnd();
return ret("error", "error"); return ret("meta", "meta");
} else if (ch == "#" && stream.eatWhile(wordRE)) {
return ret("variable", "property")
} else if (ch == "<" && stream.match("!--") ||
(ch == "-" && stream.match("->") && !/\S/.test(stream.string.slice(0, stream.start)))) {
stream.skipToEnd()
return ret("comment", "comment")
} else if (isOperatorChar.test(ch)) { } else if (isOperatorChar.test(ch)) {
stream.eatWhile(isOperatorChar); if (ch != ">" || !state.lexical || state.lexical.type != ">") {
if (stream.eat("=")) {
if (ch == "!" || ch == "=") stream.eat("=")
} else if (/[<>*+\-|&?]/.test(ch)) {
stream.eat(ch)
if (ch == ">") stream.eat(ch)
}
}
if (ch == "?" && stream.eat(".")) return ret(".")
return ret("operator", "operator", stream.current()); return ret("operator", "operator", stream.current());
} else if (wordRE.test(ch)) { } else if (wordRE.test(ch)) {
stream.eatWhile(wordRE); stream.eatWhile(wordRE);
var word = stream.current(), known = keywords.propertyIsEnumerable(word) && keywords[word]; var word = stream.current()
return (known && state.lastType != ".") ? ret(known.type, known.style, word) : if (state.lastType != ".") {
ret("variable", "variable", word); if (keywords.propertyIsEnumerable(word)) {
var kw = keywords[word]
return ret(kw.type, kw.style, word)
}
if (word == "async" && stream.match(/^(\s|\/\*([^*]|\*(?!\/))*?\*\/)*[\[\(\w]/, false))
return ret("async", "keyword", word)
}
return ret("variable", "variable", word)
} }
} }
@@ -209,19 +187,28 @@ CodeMirror.defineMode("javascript", function(config, parserConfig) {
var arrow = stream.string.indexOf("=>", stream.start); var arrow = stream.string.indexOf("=>", stream.start);
if (arrow < 0) return; if (arrow < 0) return;
if (isTS) { // Try to skip TypeScript return type declarations after the arguments
var m = /:\s*(?:\w+(?:<[^>]*>|\[\])?|\{[^}]*\})\s*$/.exec(stream.string.slice(stream.start, arrow))
if (m) arrow = m.index
}
var depth = 0, sawSomething = false; var depth = 0, sawSomething = false;
for (var pos = arrow - 1; pos >= 0; --pos) { for (var pos = arrow - 1; pos >= 0; --pos) {
var ch = stream.string.charAt(pos); var ch = stream.string.charAt(pos);
var bracket = brackets.indexOf(ch); var bracket = brackets.indexOf(ch);
if (bracket >= 0 && bracket < 3) { if (bracket >= 0 && bracket < 3) {
if (!depth) { ++pos; break; } if (!depth) { ++pos; break; }
if (--depth == 0) break; if (--depth == 0) { if (ch == "(") sawSomething = true; break; }
} else if (bracket >= 3 && bracket < 6) { } else if (bracket >= 3 && bracket < 6) {
++depth; ++depth;
} else if (wordRE.test(ch)) { } else if (wordRE.test(ch)) {
sawSomething = true; sawSomething = true;
} else if (/["'\/]/.test(ch)) { } else if (/["'\/`]/.test(ch)) {
return; for (;; --pos) {
if (pos == 0) return
var next = stream.string.charAt(pos - 1)
if (next == ch && stream.string.charAt(pos - 2) != "\\") { pos--; break }
}
} else if (sawSomething && !depth) { } else if (sawSomething && !depth) {
++pos; ++pos;
break; break;
@@ -232,7 +219,8 @@ CodeMirror.defineMode("javascript", function(config, parserConfig) {
// Parser // Parser
var atomicTypes = {"atom": true, "number": true, "variable": true, "string": true, "regexp": true, "this": true, "jsonld-keyword": true}; var atomicTypes = {"atom": true, "number": true, "variable": true, "string": true,
"regexp": true, "this": true, "import": true, "jsonld-keyword": true};
function JSLexical(indented, column, type, align, prev, info) { function JSLexical(indented, column, type, align, prev, info) {
this.indented = indented; this.indented = indented;
@@ -244,6 +232,7 @@ CodeMirror.defineMode("javascript", function(config, parserConfig) {
} }
function inScope(state, varname) { function inScope(state, varname) {
if (!trackScope) return false
for (var v = state.localVars; v; v = v.next) for (var v = state.localVars; v; v = v.next)
if (v.name == varname) return true; if (v.name == varname) return true;
for (var cx = state.context; cx; cx = cx.prev) { for (var cx = state.context; cx; cx = cx.prev) {
@@ -283,35 +272,70 @@ CodeMirror.defineMode("javascript", function(config, parserConfig) {
pass.apply(null, arguments); pass.apply(null, arguments);
return true; return true;
} }
function register(varname) { function inList(name, list) {
function inList(list) { for (var v = list; v; v = v.next) if (v.name == name) return true
for (var v = list; v; v = v.next)
if (v.name == varname) return true;
return false; return false;
} }
function register(varname) {
var state = cx.state; var state = cx.state;
cx.marked = "def"; cx.marked = "def";
if (!trackScope) return
if (state.context) { if (state.context) {
if (inList(state.localVars)) return; if (state.lexical.info == "var" && state.context && state.context.block) {
state.localVars = {name: varname, next: state.localVars}; // FIXME function decls are also not block scoped
} else { var newContext = registerVarScoped(varname, state.context)
if (inList(state.globalVars)) return; if (newContext != null) {
if (parserConfig.globalVars) state.context = newContext
state.globalVars = {name: varname, next: state.globalVars}; return
} }
} else if (!inList(varname, state.localVars)) {
state.localVars = new Var(varname, state.localVars)
return
}
}
// Fall through means this is global
if (parserConfig.globalVars && !inList(varname, state.globalVars))
state.globalVars = new Var(varname, state.globalVars)
}
function registerVarScoped(varname, context) {
if (!context) {
return null
} else if (context.block) {
var inner = registerVarScoped(varname, context.prev)
if (!inner) return null
if (inner == context.prev) return context
return new Context(inner, context.vars, true)
} else if (inList(varname, context.vars)) {
return context
} else {
return new Context(context.prev, new Var(varname, context.vars), false)
}
}
function isModifier(name) {
return name == "public" || name == "private" || name == "protected" || name == "abstract" || name == "readonly"
} }
// Combinators // Combinators
var defaultVars = {name: "this", next: {name: "arguments"}}; function Context(prev, vars, block) { this.prev = prev; this.vars = vars; this.block = block }
function Var(name, next) { this.name = name; this.next = next }
var defaultVars = new Var("this", new Var("arguments", null))
function pushcontext() { function pushcontext() {
cx.state.context = {prev: cx.state.context, vars: cx.state.localVars}; cx.state.context = new Context(cx.state.context, cx.state.localVars, false)
cx.state.localVars = defaultVars; cx.state.localVars = defaultVars
} }
function pushblockcontext() {
cx.state.context = new Context(cx.state.context, cx.state.localVars, true)
cx.state.localVars = null
}
pushcontext.lex = pushblockcontext.lex = true
function popcontext() { function popcontext() {
cx.state.localVars = cx.state.context.vars; cx.state.localVars = cx.state.context.vars
cx.state.context = cx.state.context.prev; cx.state.context = cx.state.context.prev
} }
popcontext.lex = true
function pushlex(type, info) { function pushlex(type, info) {
var result = function() { var result = function() {
var state = cx.state, indent = state.indented; var state = cx.state, indent = state.indented;
@@ -336,56 +360,87 @@ CodeMirror.defineMode("javascript", function(config, parserConfig) {
function expect(wanted) { function expect(wanted) {
function exp(type) { function exp(type) {
if (type == wanted) return cont(); if (type == wanted) return cont();
else if (wanted == ";") return pass(); else if (wanted == ";" || type == "}" || type == ")" || type == "]") return pass();
else return cont(exp); else return cont(exp);
}; };
return exp; return exp;
} }
function statement(type, value) { function statement(type, value) {
if (type == "var") return cont(pushlex("vardef", value.length), vardef, expect(";"), poplex); if (type == "var") return cont(pushlex("vardef", value), vardef, expect(";"), poplex);
if (type == "keyword a") return cont(pushlex("form"), expression, statement, poplex); if (type == "keyword a") return cont(pushlex("form"), parenExpr, statement, poplex);
if (type == "keyword b") return cont(pushlex("form"), statement, poplex); if (type == "keyword b") return cont(pushlex("form"), statement, poplex);
if (type == "{") return cont(pushlex("}"), block, poplex); if (type == "keyword d") return cx.stream.match(/^\s*$/, false) ? cont() : cont(pushlex("stat"), maybeexpression, expect(";"), poplex);
if (type == "debugger") return cont(expect(";"));
if (type == "{") return cont(pushlex("}"), pushblockcontext, block, poplex, popcontext);
if (type == ";") return cont(); if (type == ";") return cont();
if (type == "if") { if (type == "if") {
if (cx.state.lexical.info == "else" && cx.state.cc[cx.state.cc.length - 1] == poplex) if (cx.state.lexical.info == "else" && cx.state.cc[cx.state.cc.length - 1] == poplex)
cx.state.cc.pop()(); cx.state.cc.pop()();
return cont(pushlex("form"), expression, statement, poplex, maybeelse); return cont(pushlex("form"), parenExpr, statement, poplex, maybeelse);
} }
if (type == "function") return cont(functiondef); if (type == "function") return cont(functiondef);
if (type == "for") return cont(pushlex("form"), forspec, statement, poplex); if (type == "for") return cont(pushlex("form"), pushblockcontext, forspec, statement, popcontext, poplex);
if (type == "variable") return cont(pushlex("stat"), maybelabel); if (type == "class" || (isTS && value == "interface")) {
if (type == "switch") return cont(pushlex("form"), expression, pushlex("}", "switch"), expect("{"), cx.marked = "keyword"
block, poplex, poplex); return cont(pushlex("form", type == "class" ? type : value), className, poplex)
}
if (type == "variable") {
if (isTS && value == "declare") {
cx.marked = "keyword"
return cont(statement)
} else if (isTS && (value == "module" || value == "enum" || value == "type") && cx.stream.match(/^\s*\w/, false)) {
cx.marked = "keyword"
if (value == "enum") return cont(enumdef);
else if (value == "type") return cont(typename, expect("operator"), typeexpr, expect(";"));
else return cont(pushlex("form"), pattern, expect("{"), pushlex("}"), block, poplex, poplex)
} else if (isTS && value == "namespace") {
cx.marked = "keyword"
return cont(pushlex("form"), expression, statement, poplex)
} else if (isTS && value == "abstract") {
cx.marked = "keyword"
return cont(statement)
} else {
return cont(pushlex("stat"), maybelabel);
}
}
if (type == "switch") return cont(pushlex("form"), parenExpr, expect("{"), pushlex("}", "switch"), pushblockcontext,
block, poplex, poplex, popcontext);
if (type == "case") return cont(expression, expect(":")); if (type == "case") return cont(expression, expect(":"));
if (type == "default") return cont(expect(":")); if (type == "default") return cont(expect(":"));
if (type == "catch") return cont(pushlex("form"), pushcontext, expect("("), funarg, expect(")"), if (type == "catch") return cont(pushlex("form"), pushcontext, maybeCatchBinding, statement, poplex, popcontext);
statement, poplex, popcontext);
if (type == "class") return cont(pushlex("form"), className, poplex);
if (type == "export") return cont(pushlex("stat"), afterExport, poplex); if (type == "export") return cont(pushlex("stat"), afterExport, poplex);
if (type == "import") return cont(pushlex("stat"), afterImport, poplex); if (type == "import") return cont(pushlex("stat"), afterImport, poplex);
if (type == "module") return cont(pushlex("form"), pattern, pushlex("}"), expect("{"), block, poplex, poplex) if (type == "async") return cont(statement)
if (value == "@") return cont(expression, statement)
return pass(pushlex("stat"), expression, expect(";"), poplex); return pass(pushlex("stat"), expression, expect(";"), poplex);
} }
function expression(type) { function maybeCatchBinding(type) {
return expressionInner(type, false); if (type == "(") return cont(funarg, expect(")"))
} }
function expressionNoComma(type) { function expression(type, value) {
return expressionInner(type, true); return expressionInner(type, value, false);
} }
function expressionInner(type, noComma) { function expressionNoComma(type, value) {
return expressionInner(type, value, true);
}
function parenExpr(type) {
if (type != "(") return pass()
return cont(pushlex(")"), maybeexpression, expect(")"), poplex)
}
function expressionInner(type, value, noComma) {
if (cx.state.fatArrowAt == cx.stream.start) { if (cx.state.fatArrowAt == cx.stream.start) {
var body = noComma ? arrowBodyNoComma : arrowBody; var body = noComma ? arrowBodyNoComma : arrowBody;
if (type == "(") return cont(pushcontext, pushlex(")"), commasep(pattern, ")"), poplex, expect("=>"), body, popcontext); if (type == "(") return cont(pushcontext, pushlex(")"), commasep(funarg, ")"), poplex, expect("=>"), body, popcontext);
else if (type == "variable") return pass(pushcontext, pattern, expect("=>"), body, popcontext); else if (type == "variable") return pass(pushcontext, pattern, expect("=>"), body, popcontext);
} }
var maybeop = noComma ? maybeoperatorNoComma : maybeoperatorComma; var maybeop = noComma ? maybeoperatorNoComma : maybeoperatorComma;
if (atomicTypes.hasOwnProperty(type)) return cont(maybeop); if (atomicTypes.hasOwnProperty(type)) return cont(maybeop);
if (type == "function") return cont(functiondef, maybeop); if (type == "function") return cont(functiondef, maybeop);
if (type == "keyword c") return cont(noComma ? maybeexpressionNoComma : maybeexpression); if (type == "class" || (isTS && value == "interface")) { cx.marked = "keyword"; return cont(pushlex("form"), classExpression, poplex); }
if (type == "(") return cont(pushlex(")"), maybeexpression, comprehension, expect(")"), poplex, maybeop); if (type == "keyword c" || type == "async") return cont(noComma ? expressionNoComma : expression);
if (type == "(") return cont(pushlex(")"), maybeexpression, expect(")"), poplex, maybeop);
if (type == "operator" || type == "spread") return cont(noComma ? expressionNoComma : expression); if (type == "operator" || type == "spread") return cont(noComma ? expressionNoComma : expression);
if (type == "[") return cont(pushlex("]"), arrayLiteral, poplex, maybeop); if (type == "[") return cont(pushlex("]"), arrayLiteral, poplex, maybeop);
if (type == "{") return contCommasep(objprop, "}", null, maybeop); if (type == "{") return contCommasep(objprop, "}", null, maybeop);
@@ -397,13 +452,9 @@ CodeMirror.defineMode("javascript", function(config, parserConfig) {
if (type.match(/[;\}\)\],]/)) return pass(); if (type.match(/[;\}\)\],]/)) return pass();
return pass(expression); return pass(expression);
} }
function maybeexpressionNoComma(type) {
if (type.match(/[;\}\)\],]/)) return pass();
return pass(expressionNoComma);
}
function maybeoperatorComma(type, value) { function maybeoperatorComma(type, value) {
if (type == ",") return cont(expression); if (type == ",") return cont(maybeexpression);
return maybeoperatorNoComma(type, value, false); return maybeoperatorNoComma(type, value, false);
} }
function maybeoperatorNoComma(type, value, noComma) { function maybeoperatorNoComma(type, value, noComma) {
@@ -411,7 +462,9 @@ CodeMirror.defineMode("javascript", function(config, parserConfig) {
var expr = noComma == false ? expression : expressionNoComma; var expr = noComma == false ? expression : expressionNoComma;
if (type == "=>") return cont(pushcontext, noComma ? arrowBodyNoComma : arrowBody, popcontext); if (type == "=>") return cont(pushcontext, noComma ? arrowBodyNoComma : arrowBody, popcontext);
if (type == "operator") { if (type == "operator") {
if (/\+\+|--/.test(value)) return cont(me); if (/\+\+|--/.test(value) || isTS && value == "!") return cont(me);
if (isTS && value == "<" && cx.stream.match(/^([^<>]|<[^<>]*>)*>\s*\(/, false))
return cont(pushlex(">"), commasep(typeexpr, ">"), poplex, me);
if (value == "?") return cont(expression, expect(":"), expr); if (value == "?") return cont(expression, expect(":"), expr);
return cont(expr); return cont(expr);
} }
@@ -420,11 +473,17 @@ CodeMirror.defineMode("javascript", function(config, parserConfig) {
if (type == "(") return contCommasep(expressionNoComma, ")", "call", me); if (type == "(") return contCommasep(expressionNoComma, ")", "call", me);
if (type == ".") return cont(property, me); if (type == ".") return cont(property, me);
if (type == "[") return cont(pushlex("]"), maybeexpression, expect("]"), poplex, me); if (type == "[") return cont(pushlex("]"), maybeexpression, expect("]"), poplex, me);
if (isTS && value == "as") { cx.marked = "keyword"; return cont(typeexpr, me) }
if (type == "regexp") {
cx.state.lastType = cx.marked = "operator"
cx.stream.backUp(cx.stream.pos - cx.stream.start - 1)
return cont(expr)
}
} }
function quasi(type, value) { function quasi(type, value) {
if (type != "quasi") return pass(); if (type != "quasi") return pass();
if (value.slice(value.length - 2) != "${") return cont(quasi); if (value.slice(value.length - 2) != "${") return cont(quasi);
return cont(expression, continueQuasi); return cont(maybeexpression, continueQuasi);
} }
function continueQuasi(type) { function continueQuasi(type) {
if (type == "}") { if (type == "}") {
@@ -444,6 +503,7 @@ CodeMirror.defineMode("javascript", function(config, parserConfig) {
function maybeTarget(noComma) { function maybeTarget(noComma) {
return function(type) { return function(type) {
if (type == ".") return cont(noComma ? targetNoComma : target); if (type == ".") return cont(noComma ? targetNoComma : target);
else if (type == "variable" && isTS) return cont(maybeTypeArgs, noComma ? maybeoperatorNoComma : maybeoperatorComma)
else return pass(noComma ? expressionNoComma : expression); else return pass(noComma ? expressionNoComma : expression);
}; };
} }
@@ -461,21 +521,33 @@ CodeMirror.defineMode("javascript", function(config, parserConfig) {
if (type == "variable") {cx.marked = "property"; return cont();} if (type == "variable") {cx.marked = "property"; return cont();}
} }
function objprop(type, value) { function objprop(type, value) {
if (type == "variable" || cx.style == "keyword") { if (type == "async") {
cx.marked = "property";
return cont(objprop);
} else if (type == "variable" || cx.style == "keyword") {
cx.marked = "property"; cx.marked = "property";
if (value == "get" || value == "set") return cont(getterSetter); if (value == "get" || value == "set") return cont(getterSetter);
var m // Work around fat-arrow-detection complication for detecting typescript typed arrow params
if (isTS && cx.state.fatArrowAt == cx.stream.start && (m = cx.stream.match(/^\s*:\s*/, false)))
cx.state.fatArrowAt = cx.stream.pos + m[0].length
return cont(afterprop); return cont(afterprop);
} else if (type == "number" || type == "string") { } else if (type == "number" || type == "string") {
cx.marked = jsonldMode ? "property" : (cx.style + " property"); cx.marked = jsonldMode ? "property" : (cx.style + " property");
return cont(afterprop); return cont(afterprop);
} else if (type == "jsonld-keyword") { } else if (type == "jsonld-keyword") {
return cont(afterprop); return cont(afterprop);
} else if (type == "modifier") { } else if (isTS && isModifier(value)) {
cx.marked = "keyword"
return cont(objprop) return cont(objprop)
} else if (type == "[") { } else if (type == "[") {
return cont(expression, expect("]"), afterprop); return cont(expression, maybetype, expect("]"), afterprop);
} else if (type == "spread") { } else if (type == "spread") {
return cont(expression); return cont(expressionNoComma, afterprop);
} else if (value == "*") {
cx.marked = "keyword";
return cont(objprop);
} else if (type == ":") {
return pass(afterprop)
} }
} }
function getterSetter(type) { function getterSetter(type) {
@@ -487,18 +559,22 @@ CodeMirror.defineMode("javascript", function(config, parserConfig) {
if (type == ":") return cont(expressionNoComma); if (type == ":") return cont(expressionNoComma);
if (type == "(") return pass(functiondef); if (type == "(") return pass(functiondef);
} }
function commasep(what, end) { function commasep(what, end, sep) {
function proceed(type) { function proceed(type, value) {
if (type == ",") { if (sep ? sep.indexOf(type) > -1 : type == ",") {
var lex = cx.state.lexical; var lex = cx.state.lexical;
if (lex.info == "call") lex.pos = (lex.pos || 0) + 1; if (lex.info == "call") lex.pos = (lex.pos || 0) + 1;
return cont(what, proceed); return cont(function(type, value) {
if (type == end || value == end) return pass()
return pass(what)
}, proceed);
} }
if (type == end) return cont(); if (type == end || value == end) return cont();
if (sep && sep.indexOf(";") > -1) return pass(what)
return cont(expect(end)); return cont(expect(end));
} }
return function(type) { return function(type, value) {
if (type == end) return cont(); if (type == end || value == end) return cont();
return pass(what, proceed); return pass(what, proceed);
}; };
} }
@@ -511,23 +587,111 @@ CodeMirror.defineMode("javascript", function(config, parserConfig) {
if (type == "}") return cont(); if (type == "}") return cont();
return pass(statement, block); return pass(statement, block);
} }
function maybetype(type) { function maybetype(type, value) {
if (isTS && type == ":") return cont(typedef); if (isTS) {
if (type == ":") return cont(typeexpr);
if (value == "?") return cont(maybetype);
} }
function maybedefault(_, value) {
if (value == "=") return cont(expressionNoComma);
} }
function typedef(type) { function maybetypeOrIn(type, value) {
if (type == "variable") {cx.marked = "variable-3"; return cont();} if (isTS && (type == ":" || value == "in")) return cont(typeexpr)
} }
function vardef() { function mayberettype(type) {
if (isTS && type == ":") {
if (cx.stream.match(/^\s*\w+\s+is\b/, false)) return cont(expression, isKW, typeexpr)
else return cont(typeexpr)
}
}
function isKW(_, value) {
if (value == "is") {
cx.marked = "keyword"
return cont()
}
}
function typeexpr(type, value) {
if (value == "keyof" || value == "typeof" || value == "infer" || value == "readonly") {
cx.marked = "keyword"
return cont(value == "typeof" ? expressionNoComma : typeexpr)
}
if (type == "variable" || value == "void") {
cx.marked = "type"
return cont(afterType)
}
if (value == "|" || value == "&") return cont(typeexpr)
if (type == "string" || type == "number" || type == "atom") return cont(afterType);
if (type == "[") return cont(pushlex("]"), commasep(typeexpr, "]", ","), poplex, afterType)
if (type == "{") return cont(pushlex("}"), typeprops, poplex, afterType)
if (type == "(") return cont(commasep(typearg, ")"), maybeReturnType, afterType)
if (type == "<") return cont(commasep(typeexpr, ">"), typeexpr)
if (type == "quasi") { return pass(quasiType, afterType); }
}
function maybeReturnType(type) {
if (type == "=>") return cont(typeexpr)
}
function typeprops(type) {
if (type.match(/[\}\)\]]/)) return cont()
if (type == "," || type == ";") return cont(typeprops)
return pass(typeprop, typeprops)
}
function typeprop(type, value) {
if (type == "variable" || cx.style == "keyword") {
cx.marked = "property"
return cont(typeprop)
} else if (value == "?" || type == "number" || type == "string") {
return cont(typeprop)
} else if (type == ":") {
return cont(typeexpr)
} else if (type == "[") {
return cont(expect("variable"), maybetypeOrIn, expect("]"), typeprop)
} else if (type == "(") {
return pass(functiondecl, typeprop)
} else if (!type.match(/[;\}\)\],]/)) {
return cont()
}
}
function quasiType(type, value) {
if (type != "quasi") return pass();
if (value.slice(value.length - 2) != "${") return cont(quasiType);
return cont(typeexpr, continueQuasiType);
}
function continueQuasiType(type) {
if (type == "}") {
cx.marked = "string-2";
cx.state.tokenize = tokenQuasi;
return cont(quasiType);
}
}
function typearg(type, value) {
if (type == "variable" && cx.stream.match(/^\s*[?:]/, false) || value == "?") return cont(typearg)
if (type == ":") return cont(typeexpr)
if (type == "spread") return cont(typearg)
return pass(typeexpr)
}
function afterType(type, value) {
if (value == "<") return cont(pushlex(">"), commasep(typeexpr, ">"), poplex, afterType)
if (value == "|" || type == "." || value == "&") return cont(typeexpr)
if (type == "[") return cont(typeexpr, expect("]"), afterType)
if (value == "extends" || value == "implements") { cx.marked = "keyword"; return cont(typeexpr) }
if (value == "?") return cont(typeexpr, expect(":"), typeexpr)
}
function maybeTypeArgs(_, value) {
if (value == "<") return cont(pushlex(">"), commasep(typeexpr, ">"), poplex, afterType)
}
function typeparam() {
return pass(typeexpr, maybeTypeDefault)
}
function maybeTypeDefault(_, value) {
if (value == "=") return cont(typeexpr)
}
function vardef(_, value) {
if (value == "enum") {cx.marked = "keyword"; return cont(enumdef)}
return pass(pattern, maybetype, maybeAssign, vardefCont); return pass(pattern, maybetype, maybeAssign, vardefCont);
} }
function pattern(type, value) { function pattern(type, value) {
if (type == "modifier") return cont(pattern) if (isTS && isModifier(value)) { cx.marked = "keyword"; return cont(pattern) }
if (type == "variable") { register(value); return cont(); } if (type == "variable") { register(value); return cont(); }
if (type == "spread") return cont(pattern); if (type == "spread") return cont(pattern);
if (type == "[") return contCommasep(pattern, "]"); if (type == "[") return contCommasep(eltpattern, "]");
if (type == "{") return contCommasep(proppattern, "}"); if (type == "{") return contCommasep(proppattern, "}");
} }
function proppattern(type, value) { function proppattern(type, value) {
@@ -538,8 +702,12 @@ CodeMirror.defineMode("javascript", function(config, parserConfig) {
if (type == "variable") cx.marked = "property"; if (type == "variable") cx.marked = "property";
if (type == "spread") return cont(pattern); if (type == "spread") return cont(pattern);
if (type == "}") return pass(); if (type == "}") return pass();
if (type == "[") return cont(expression, expect(']'), expect(':'), proppattern);
return cont(expect(":"), pattern, maybeAssign); return cont(expect(":"), pattern, maybeAssign);
} }
function eltpattern() {
return pass(pattern, maybeAssign)
}
function maybeAssign(_type, value) { function maybeAssign(_type, value) {
if (value == "=") return cont(expressionNoComma); if (value == "=") return cont(expressionNoComma);
} }
@@ -549,73 +717,111 @@ CodeMirror.defineMode("javascript", function(config, parserConfig) {
function maybeelse(type, value) { function maybeelse(type, value) {
if (type == "keyword b" && value == "else") return cont(pushlex("form", "else"), statement, poplex); if (type == "keyword b" && value == "else") return cont(pushlex("form", "else"), statement, poplex);
} }
function forspec(type) { function forspec(type, value) {
if (type == "(") return cont(pushlex(")"), forspec1, expect(")"), poplex); if (value == "await") return cont(forspec);
if (type == "(") return cont(pushlex(")"), forspec1, poplex);
} }
function forspec1(type) { function forspec1(type) {
if (type == "var") return cont(vardef, expect(";"), forspec2); if (type == "var") return cont(vardef, forspec2);
if (type == ";") return cont(forspec2); if (type == "variable") return cont(forspec2);
if (type == "variable") return cont(formaybeinof); return pass(forspec2)
return pass(expression, expect(";"), forspec2);
}
function formaybeinof(_type, value) {
if (value == "in" || value == "of") { cx.marked = "keyword"; return cont(expression); }
return cont(maybeoperatorComma, forspec2);
} }
function forspec2(type, value) { function forspec2(type, value) {
if (type == ";") return cont(forspec3); if (type == ")") return cont()
if (value == "in" || value == "of") { cx.marked = "keyword"; return cont(expression); } if (type == ";") return cont(forspec2)
return pass(expression, expect(";"), forspec3); if (value == "in" || value == "of") { cx.marked = "keyword"; return cont(expression, forspec2) }
} return pass(expression, forspec2)
function forspec3(type) {
if (type != ")") cont(expression);
} }
function functiondef(type, value) { function functiondef(type, value) {
if (value == "*") {cx.marked = "keyword"; return cont(functiondef);} if (value == "*") {cx.marked = "keyword"; return cont(functiondef);}
if (type == "variable") {register(value); return cont(functiondef);} if (type == "variable") {register(value); return cont(functiondef);}
if (type == "(") return cont(pushcontext, pushlex(")"), commasep(funarg, ")"), poplex, statement, popcontext); if (type == "(") return cont(pushcontext, pushlex(")"), commasep(funarg, ")"), poplex, mayberettype, statement, popcontext);
if (isTS && value == "<") return cont(pushlex(">"), commasep(typeparam, ">"), poplex, functiondef)
} }
function funarg(type) { function functiondecl(type, value) {
if (value == "*") {cx.marked = "keyword"; return cont(functiondecl);}
if (type == "variable") {register(value); return cont(functiondecl);}
if (type == "(") return cont(pushcontext, pushlex(")"), commasep(funarg, ")"), poplex, mayberettype, popcontext);
if (isTS && value == "<") return cont(pushlex(">"), commasep(typeparam, ">"), poplex, functiondecl)
}
function typename(type, value) {
if (type == "keyword" || type == "variable") {
cx.marked = "type"
return cont(typename)
} else if (value == "<") {
return cont(pushlex(">"), commasep(typeparam, ">"), poplex)
}
}
function funarg(type, value) {
if (value == "@") cont(expression, funarg)
if (type == "spread") return cont(funarg); if (type == "spread") return cont(funarg);
return pass(pattern, maybetype, maybedefault); if (isTS && isModifier(value)) { cx.marked = "keyword"; return cont(funarg); }
if (isTS && type == "this") return cont(maybetype, maybeAssign)
return pass(pattern, maybetype, maybeAssign);
}
function classExpression(type, value) {
// Class expressions may have an optional name.
if (type == "variable") return className(type, value);
return classNameAfter(type, value);
} }
function className(type, value) { function className(type, value) {
if (type == "variable") {register(value); return cont(classNameAfter);} if (type == "variable") {register(value); return cont(classNameAfter);}
} }
function classNameAfter(type, value) { function classNameAfter(type, value) {
if (value == "extends") return cont(expression, classNameAfter); if (value == "<") return cont(pushlex(">"), commasep(typeparam, ">"), poplex, classNameAfter)
if (value == "extends" || value == "implements" || (isTS && type == ",")) {
if (value == "implements") cx.marked = "keyword";
return cont(isTS ? typeexpr : expression, classNameAfter);
}
if (type == "{") return cont(pushlex("}"), classBody, poplex); if (type == "{") return cont(pushlex("}"), classBody, poplex);
} }
function classBody(type, value) { function classBody(type, value) {
if (type == "variable" || cx.style == "keyword") { if (type == "async" ||
if (value == "static") { (type == "variable" &&
(value == "static" || value == "get" || value == "set" || (isTS && isModifier(value))) &&
cx.stream.match(/^\s+#?[\w$\xa1-\uffff]/, false))) {
cx.marked = "keyword"; cx.marked = "keyword";
return cont(classBody); return cont(classBody);
} }
if (type == "variable" || cx.style == "keyword") {
cx.marked = "property"; cx.marked = "property";
if (value == "get" || value == "set") return cont(classGetterSetter, functiondef, classBody); return cont(classfield, classBody);
return cont(functiondef, classBody);
} }
if (type == "number" || type == "string") return cont(classfield, classBody);
if (type == "[")
return cont(expression, maybetype, expect("]"), classfield, classBody)
if (value == "*") { if (value == "*") {
cx.marked = "keyword"; cx.marked = "keyword";
return cont(classBody); return cont(classBody);
} }
if (type == ";") return cont(classBody); if (isTS && type == "(") return pass(functiondecl, classBody)
if (type == ";" || type == ",") return cont(classBody);
if (type == "}") return cont(); if (type == "}") return cont();
if (value == "@") return cont(expression, classBody)
} }
function classGetterSetter(type) { function classfield(type, value) {
if (type != "variable") return pass(); if (value == "!") return cont(classfield)
cx.marked = "property"; if (value == "?") return cont(classfield)
return cont(); if (type == ":") return cont(typeexpr, maybeAssign)
if (value == "=") return cont(expressionNoComma)
var context = cx.state.lexical.prev, isInterface = context && context.info == "interface"
return pass(isInterface ? functiondecl : functiondef)
} }
function afterExport(_type, value) { function afterExport(type, value) {
if (value == "*") { cx.marked = "keyword"; return cont(maybeFrom, expect(";")); } if (value == "*") { cx.marked = "keyword"; return cont(maybeFrom, expect(";")); }
if (value == "default") { cx.marked = "keyword"; return cont(expression, expect(";")); } if (value == "default") { cx.marked = "keyword"; return cont(expression, expect(";")); }
if (type == "{") return cont(commasep(exportField, "}"), maybeFrom, expect(";"));
return pass(statement); return pass(statement);
} }
function exportField(type, value) {
if (value == "as") { cx.marked = "keyword"; return cont(expect("variable")); }
if (type == "variable") return pass(expressionNoComma, exportField);
}
function afterImport(type) { function afterImport(type) {
if (type == "string") return cont(); if (type == "string") return cont();
return pass(importSpec, maybeFrom); if (type == "(") return pass(expression);
if (type == ".") return pass(maybeoperatorComma);
return pass(importSpec, maybeMoreImports, maybeFrom);
} }
function importSpec(type, value) { function importSpec(type, value) {
if (type == "{") return contCommasep(importSpec, "}"); if (type == "{") return contCommasep(importSpec, "}");
@@ -623,6 +829,9 @@ CodeMirror.defineMode("javascript", function(config, parserConfig) {
if (value == "*") cx.marked = "keyword"; if (value == "*") cx.marked = "keyword";
return cont(maybeAs); return cont(maybeAs);
} }
function maybeMoreImports(type) {
if (type == ",") return cont(importSpec, maybeMoreImports)
}
function maybeAs(_type, value) { function maybeAs(_type, value) {
if (value == "as") { cx.marked = "keyword"; return cont(importSpec); } if (value == "as") { cx.marked = "keyword"; return cont(importSpec); }
} }
@@ -631,16 +840,13 @@ CodeMirror.defineMode("javascript", function(config, parserConfig) {
} }
function arrayLiteral(type) { function arrayLiteral(type) {
if (type == "]") return cont(); if (type == "]") return cont();
return pass(expressionNoComma, maybeArrayComprehension);
}
function maybeArrayComprehension(type) {
if (type == "for") return pass(comprehension, expect("]"));
if (type == ",") return cont(commasep(maybeexpressionNoComma, "]"));
return pass(commasep(expressionNoComma, "]")); return pass(commasep(expressionNoComma, "]"));
} }
function comprehension(type) { function enumdef() {
if (type == "for") return cont(forspec, comprehension); return pass(pushlex("form"), pattern, expect("{"), pushlex("}"), commasep(enummember, "}"), poplex, poplex)
if (type == "if") return cont(expression, comprehension); }
function enummember() {
return pass(pattern, maybeAssign);
} }
function isContinuedStatement(state, textAfter) { function isContinuedStatement(state, textAfter) {
@@ -649,6 +855,12 @@ CodeMirror.defineMode("javascript", function(config, parserConfig) {
/[,.]/.test(textAfter.charAt(0)); /[,.]/.test(textAfter.charAt(0));
} }
function expressionAllowed(stream, state, backUp) {
return state.tokenize == tokenBase &&
/^(?:operator|sof|keyword [bcd]|case|new|export|default|spread|[\[{}\(,;:]|=>)$/.test(state.lastType) ||
(state.lastType == "quasi" && /\{\s*$/.test(stream.string.slice(0, stream.pos - (backUp || 0))))
}
// Interface // Interface
return { return {
@@ -659,7 +871,7 @@ CodeMirror.defineMode("javascript", function(config, parserConfig) {
cc: [], cc: [],
lexical: new JSLexical((basecolumn || 0) - indentUnit, 0, "block", false), lexical: new JSLexical((basecolumn || 0) - indentUnit, 0, "block", false),
localVars: parserConfig.localVars, localVars: parserConfig.localVars,
context: parserConfig.localVars && {vars: parserConfig.localVars}, context: parserConfig.localVars && new Context(null, null, false),
indented: basecolumn || 0 indented: basecolumn || 0
}; };
if (parserConfig.globalVars && typeof parserConfig.globalVars == "object") if (parserConfig.globalVars && typeof parserConfig.globalVars == "object")
@@ -682,21 +894,25 @@ CodeMirror.defineMode("javascript", function(config, parserConfig) {
}, },
indent: function(state, textAfter) { indent: function(state, textAfter) {
if (state.tokenize == tokenComment) return CodeMirror.Pass; if (state.tokenize == tokenComment || state.tokenize == tokenQuasi) return CodeMirror.Pass;
if (state.tokenize != tokenBase) return 0; if (state.tokenize != tokenBase) return 0;
var firstChar = textAfter && textAfter.charAt(0), lexical = state.lexical; var firstChar = textAfter && textAfter.charAt(0), lexical = state.lexical, top
// Kludge to prevent 'maybelse' from blocking lexical scope pops // Kludge to prevent 'maybelse' from blocking lexical scope pops
if (!/^\s*else\b/.test(textAfter)) for (var i = state.cc.length - 1; i >= 0; --i) { if (!/^\s*else\b/.test(textAfter)) for (var i = state.cc.length - 1; i >= 0; --i) {
var c = state.cc[i]; var c = state.cc[i];
if (c == poplex) lexical = lexical.prev; if (c == poplex) lexical = lexical.prev;
else if (c != maybeelse) break; else if (c != maybeelse && c != popcontext) break;
} }
if (lexical.type == "stat" && firstChar == "}") lexical = lexical.prev; while ((lexical.type == "stat" || lexical.type == "form") &&
(firstChar == "}" || ((top = state.cc[state.cc.length - 1]) &&
(top == maybeoperatorComma || top == maybeoperatorNoComma) &&
!/^[,\.=+\-*:?[\(]/.test(textAfter))))
lexical = lexical.prev;
if (statementIndent && lexical.type == ")" && lexical.prev.type == "stat") if (statementIndent && lexical.type == ")" && lexical.prev.type == "stat")
lexical = lexical.prev; lexical = lexical.prev;
var type = lexical.type, closing = firstChar == type; var type = lexical.type, closing = firstChar == type;
if (type == "vardef") return lexical.indented + (state.lastType == "operator" || state.lastType == "," ? lexical.info + 1 : 0); if (type == "vardef") return lexical.indented + (state.lastType == "operator" || state.lastType == "," ? lexical.info.length + 1 : 0);
else if (type == "form" && firstChar == "{") return lexical.indented; else if (type == "form" && firstChar == "{") return lexical.indented;
else if (type == "form") return lexical.indented + indentUnit; else if (type == "form") return lexical.indented + indentUnit;
else if (type == "stat") else if (type == "stat")
@@ -710,6 +926,7 @@ CodeMirror.defineMode("javascript", function(config, parserConfig) {
electricInput: /^\s*(?:case .*?:|default:|\{|\})$/, electricInput: /^\s*(?:case .*?:|default:|\{|\})$/,
blockCommentStart: jsonMode ? null : "/*", blockCommentStart: jsonMode ? null : "/*",
blockCommentEnd: jsonMode ? null : "*/", blockCommentEnd: jsonMode ? null : "*/",
blockCommentContinue: jsonMode ? null : " * ",
lineComment: jsonMode ? null : "//", lineComment: jsonMode ? null : "//",
fold: "brace", fold: "brace",
closeBrackets: "()[]{}''\"\"``", closeBrackets: "()[]{}''\"\"``",
@@ -719,9 +936,9 @@ CodeMirror.defineMode("javascript", function(config, parserConfig) {
jsonMode: jsonMode, jsonMode: jsonMode,
expressionAllowed: expressionAllowed, expressionAllowed: expressionAllowed,
skipExpression: function(state) { skipExpression: function(state) {
var top = state.cc[state.cc.length - 1] parseJS(state, "atom", "atom", "true", new CodeMirror.StringStream("", 2, null))
if (top == expression || top == expressionNoComma) state.cc.pop()
} }
}; };
}); });
@@ -735,6 +952,7 @@ CodeMirror.defineMIME("application/x-javascript", "javascript");
CodeMirror.defineMIME("application/ecmascript", "javascript"); CodeMirror.defineMIME("application/ecmascript", "javascript");
CodeMirror.defineMIME("application/json", { name: "javascript", json: true }); CodeMirror.defineMIME("application/json", { name: "javascript", json: true });
CodeMirror.defineMIME("application/x-json", { name: "javascript", json: true }); CodeMirror.defineMIME("application/x-json", { name: "javascript", json: true });
CodeMirror.defineMIME("application/manifest+json", { name: "javascript", json: true })
CodeMirror.defineMIME("application/ld+json", { name: "javascript", jsonld: true }); CodeMirror.defineMIME("application/ld+json", { name: "javascript", jsonld: true });
CodeMirror.defineMIME("text/typescript", { name: "javascript", typescript: true }); CodeMirror.defineMIME("text/typescript", { name: "javascript", typescript: true });
CodeMirror.defineMIME("application/typescript", { name: "javascript", typescript: true }); CodeMirror.defineMIME("application/typescript", { name: "javascript", typescript: true });

View File

@@ -1,72 +0,0 @@
<!doctype html>
<title>CodeMirror: JSON-LD mode</title>
<meta charset="utf-8"/>
<link rel=stylesheet href="../../doc/docs.css">
<link rel="stylesheet" href="../../lib/codemirror.css">
<script src="../../lib/codemirror.js"></script>
<script src="../../addon/edit/matchbrackets.js"></script>
<script src="../../addon/comment/continuecomment.js"></script>
<script src="../../addon/comment/comment.js"></script>
<script src="javascript.js"></script>
<style type="text/css">.CodeMirror {border-top: 1px solid black; border-bottom: 1px solid black;}</style>
<div id="nav">
<a href="http://codemirror.net"><h1>CodeMirror</h1><img id=logo src="../../doc/logo.png"/></a>
<ul>
<li><a href="../../index.html">Home</a>
<li><a href="../../doc/manual.html">Manual</a>
<li><a href="https://github.com/codemirror/codemirror">Code</a>
</ul>
<ul>
<li><a href="../index.html">Language modes</a>
<li><a class=active href="#">JSON-LD</a>
</ul>
</div>
<article>
<h2>JSON-LD mode</h2>
<div><textarea id="code" name="code">
{
"@context": {
"name": "http://schema.org/name",
"description": "http://schema.org/description",
"image": {
"@id": "http://schema.org/image",
"@type": "@id"
},
"geo": "http://schema.org/geo",
"latitude": {
"@id": "http://schema.org/latitude",
"@type": "xsd:float"
},
"longitude": {
"@id": "http://schema.org/longitude",
"@type": "xsd:float"
},
"xsd": "http://www.w3.org/2001/XMLSchema#"
},
"name": "The Empire State Building",
"description": "The Empire State Building is a 102-story landmark in New York City.",
"image": "http://www.civil.usherbrooke.ca/cours/gci215a/empire-state-building.jpg",
"geo": {
"latitude": "40.75",
"longitude": "73.98"
}
}
</textarea></div>
<script>
var editor = CodeMirror.fromTextArea(document.getElementById("code"), {
matchBrackets: true,
autoCloseBrackets: true,
mode: "application/ld+json",
lineWrapping: true
});
</script>
<p>This is a specialization of the <a href="index.html">JavaScript mode</a>.</p>
</article>

View File

@@ -1,61 +0,0 @@
<!doctype html>
<title>CodeMirror: TypeScript mode</title>
<meta charset="utf-8"/>
<link rel=stylesheet href="../../doc/docs.css">
<link rel="stylesheet" href="../../lib/codemirror.css">
<script src="../../lib/codemirror.js"></script>
<script src="javascript.js"></script>
<style type="text/css">.CodeMirror {border-top: 1px solid black; border-bottom: 1px solid black;}</style>
<div id=nav>
<a href="http://codemirror.net"><h1>CodeMirror</h1><img id=logo src="../../doc/logo.png"></a>
<ul>
<li><a href="../../index.html">Home</a>
<li><a href="../../doc/manual.html">Manual</a>
<li><a href="https://github.com/codemirror/codemirror">Code</a>
</ul>
<ul>
<li><a href="../index.html">Language modes</a>
<li><a class=active href="#">TypeScript</a>
</ul>
</div>
<article>
<h2>TypeScript mode</h2>
<div><textarea id="code" name="code">
class Greeter {
greeting: string;
constructor (message: string) {
this.greeting = message;
}
greet() {
return "Hello, " + this.greeting;
}
}
var greeter = new Greeter("world");
var button = document.createElement('button')
button.innerText = "Say Hello"
button.onclick = function() {
alert(greeter.greet())
}
document.body.appendChild(button)
</textarea></div>
<script>
var editor = CodeMirror.fromTextArea(document.getElementById("code"), {
lineNumbers: true,
matchBrackets: true,
mode: "text/typescript"
});
</script>
<p>This is a specialization of the <a href="index.html">JavaScript mode</a>.</p>
</article>

View File

@@ -1,5 +1,5 @@
// CodeMirror, copyright (c) by Marijn Haverbeke and others // CodeMirror, copyright (c) by Marijn Haverbeke and others
// Distributed under an MIT license: http://codemirror.net/LICENSE // Distributed under an MIT license: https://codemirror.net/5/LICENSE
(function(mod) { (function(mod) {
if (typeof exports == "object" && typeof module == "object") // CommonJS if (typeof exports == "object" && typeof module == "object") // CommonJS
@@ -26,13 +26,13 @@
} }
CodeMirror.defineMode("jsx", function(config, modeConfig) { CodeMirror.defineMode("jsx", function(config, modeConfig) {
var xmlMode = CodeMirror.getMode(config, {name: "xml", allowMissing: true, multilineTagIndentPastTag: false}) var xmlMode = CodeMirror.getMode(config, {name: "xml", allowMissing: true, multilineTagIndentPastTag: false, allowMissingTagName: true})
var jsMode = CodeMirror.getMode(config, modeConfig && modeConfig.base || "javascript") var jsMode = CodeMirror.getMode(config, modeConfig && modeConfig.base || "javascript")
function flatXMLIndent(state) { function flatXMLIndent(state) {
var tagName = state.tagName var tagName = state.tagName
state.tagName = null state.tagName = null
var result = xmlMode.indent(state, "") var result = xmlMode.indent(state, "", "")
state.tagName = tagName state.tagName = tagName
return result return result
} }
@@ -103,10 +103,11 @@
} }
function jsToken(stream, state, cx) { function jsToken(stream, state, cx) {
if (stream.peek() == "<" && jsMode.expressionAllowed(stream, cx.state)) { if (stream.peek() == "<" && !stream.match(/^<([^<>]|<[^>]*>)+,\s*>/, false) &&
jsMode.skipExpression(cx.state) jsMode.expressionAllowed(stream, cx.state)) {
state.context = new Context(CodeMirror.startState(xmlMode, jsMode.indent(cx.state, "")), state.context = new Context(CodeMirror.startState(xmlMode, jsMode.indent(cx.state, "", "")),
xmlMode, 0, state.context) xmlMode, 0, state.context)
jsMode.skipExpression(cx.state)
return null return null
} }

View File

@@ -1,5 +1,5 @@
// CodeMirror, copyright (c) by Marijn Haverbeke and others // CodeMirror, copyright (c) by Marijn Haverbeke and others
// Distributed under an MIT license: http://codemirror.net/LICENSE // Distributed under an MIT license: https://codemirror.net/5/LICENSE
(function(mod) { (function(mod) {
if (typeof exports == "object" && typeof module == "object") // CommonJS if (typeof exports == "object" && typeof module == "object") // CommonJS
@@ -35,15 +35,6 @@ CodeMirror.defineMode("markdown", function(cmCfg, modeCfg) {
if (modeCfg.maxBlockquoteDepth === undefined) if (modeCfg.maxBlockquoteDepth === undefined)
modeCfg.maxBlockquoteDepth = 0; modeCfg.maxBlockquoteDepth = 0;
// Should underscores in words open/close em/strong?
if (modeCfg.underscoresBreakWords === undefined)
modeCfg.underscoresBreakWords = true;
// Use `fencedCodeBlocks` to configure fenced code blocks. false to
// disable, string to specify a precise regexp that the fence should
// match, and true to allow three or more backticks or tildes (as
// per CommonMark).
// Turn on task lists? ("- [ ] " and "- [x] ") // Turn on task lists? ("- [ ] " and "- [x] ")
if (modeCfg.taskLists === undefined) modeCfg.taskLists = false; if (modeCfg.taskLists === undefined) modeCfg.taskLists = false;
@@ -51,6 +42,18 @@ CodeMirror.defineMode("markdown", function(cmCfg, modeCfg) {
if (modeCfg.strikethrough === undefined) if (modeCfg.strikethrough === undefined)
modeCfg.strikethrough = false; modeCfg.strikethrough = false;
if (modeCfg.emoji === undefined)
modeCfg.emoji = false;
if (modeCfg.fencedCodeBlockHighlighting === undefined)
modeCfg.fencedCodeBlockHighlighting = true;
if (modeCfg.fencedCodeBlockDefaultMode === undefined)
modeCfg.fencedCodeBlockDefaultMode = 'text/plain';
if (modeCfg.xml === undefined)
modeCfg.xml = true;
// Allow token types to be overridden by user-provided token types. // Allow token types to be overridden by user-provided token types.
if (modeCfg.tokenTypeOverrides === undefined) if (modeCfg.tokenTypeOverrides === undefined)
modeCfg.tokenTypeOverrides = {}; modeCfg.tokenTypeOverrides = {};
@@ -63,7 +66,9 @@ CodeMirror.defineMode("markdown", function(cmCfg, modeCfg) {
list2: "variable-3", list2: "variable-3",
list3: "keyword", list3: "keyword",
hr: "hr", hr: "hr",
image: "tag", image: "image",
imageAltText: "image-alt-text",
imageMarker: "image-marker",
formatting: "formatting", formatting: "formatting",
linkInline: "link", linkInline: "link",
linkEmail: "link", linkEmail: "link",
@@ -71,7 +76,8 @@ CodeMirror.defineMode("markdown", function(cmCfg, modeCfg) {
linkHref: "string", linkHref: "string",
em: "em", em: "em",
strong: "strong", strong: "strong",
strikethrough: "strikethrough" strikethrough: "strikethrough",
emoji: "builtin"
}; };
for (var tokenType in tokenTypes) { for (var tokenType in tokenTypes) {
@@ -81,14 +87,15 @@ CodeMirror.defineMode("markdown", function(cmCfg, modeCfg) {
} }
var hrRE = /^([*\-_])(?:\s*\1){2,}\s*$/ var hrRE = /^([*\-_])(?:\s*\1){2,}\s*$/
, ulRE = /^[*\-+]\s+/ , listRE = /^(?:[*\-+]|^[0-9]+([.)]))\s+/
, olRE = /^[0-9]+([.)])\s+/ , taskListRE = /^\[(x| )\](?=\s)/i // Must follow listRE
, taskListRE = /^\[(x| )\](?=\s)/ // Must follow ulRE or olRE
, atxHeaderRE = modeCfg.allowAtxHeaderWithoutSpace ? /^(#+)/ : /^(#+)(?: |$)/ , atxHeaderRE = modeCfg.allowAtxHeaderWithoutSpace ? /^(#+)/ : /^(#+)(?: |$)/
, setextHeaderRE = /^ *(?:\={1,}|-{1,})\s*$/ , setextHeaderRE = /^ {0,3}(?:\={1,}|-{2,})\s*$/
, textRE = /^[^#!\[\]*_\\<>` "'(~]+/ , textRE = /^[^#!\[\]*_\\<>` "'(~:]+/
, fencedCodeRE = new RegExp("^(" + (modeCfg.fencedCodeBlocks === true ? "~~~+|```+" : modeCfg.fencedCodeBlocks) + , fencedCodeRE = /^(~~~+|```+)[ \t]*([\w\/+#-]*)[^\n`]*$/
")[ \\t]*([\\w+#\-]*)"); , linkDefRE = /^\s*\[[^\]]+?\]:.*$/ // naive link-definition
, punctuation = /[!"#$%&'()*+,\-.\/:;<=>?@\[\\\]^_`{|}~\xA1\xA7\xAB\xB6\xB7\xBB\xBF\u037E\u0387\u055A-\u055F\u0589\u058A\u05BE\u05C0\u05C3\u05C6\u05F3\u05F4\u0609\u060A\u060C\u060D\u061B\u061E\u061F\u066A-\u066D\u06D4\u0700-\u070D\u07F7-\u07F9\u0830-\u083E\u085E\u0964\u0965\u0970\u0AF0\u0DF4\u0E4F\u0E5A\u0E5B\u0F04-\u0F12\u0F14\u0F3A-\u0F3D\u0F85\u0FD0-\u0FD4\u0FD9\u0FDA\u104A-\u104F\u10FB\u1360-\u1368\u1400\u166D\u166E\u169B\u169C\u16EB-\u16ED\u1735\u1736\u17D4-\u17D6\u17D8-\u17DA\u1800-\u180A\u1944\u1945\u1A1E\u1A1F\u1AA0-\u1AA6\u1AA8-\u1AAD\u1B5A-\u1B60\u1BFC-\u1BFF\u1C3B-\u1C3F\u1C7E\u1C7F\u1CC0-\u1CC7\u1CD3\u2010-\u2027\u2030-\u2043\u2045-\u2051\u2053-\u205E\u207D\u207E\u208D\u208E\u2308-\u230B\u2329\u232A\u2768-\u2775\u27C5\u27C6\u27E6-\u27EF\u2983-\u2998\u29D8-\u29DB\u29FC\u29FD\u2CF9-\u2CFC\u2CFE\u2CFF\u2D70\u2E00-\u2E2E\u2E30-\u2E42\u3001-\u3003\u3008-\u3011\u3014-\u301F\u3030\u303D\u30A0\u30FB\uA4FE\uA4FF\uA60D-\uA60F\uA673\uA67E\uA6F2-\uA6F7\uA874-\uA877\uA8CE\uA8CF\uA8F8-\uA8FA\uA8FC\uA92E\uA92F\uA95F\uA9C1-\uA9CD\uA9DE\uA9DF\uAA5C-\uAA5F\uAADE\uAADF\uAAF0\uAAF1\uABEB\uFD3E\uFD3F\uFE10-\uFE19\uFE30-\uFE52\uFE54-\uFE61\uFE63\uFE68\uFE6A\uFE6B\uFF01-\uFF03\uFF05-\uFF0A\uFF0C-\uFF0F\uFF1A\uFF1B\uFF1F\uFF20\uFF3B-\uFF3D\uFF3F\uFF5B\uFF5D\uFF5F-\uFF65]|\uD800[\uDD00-\uDD02\uDF9F\uDFD0]|\uD801\uDD6F|\uD802[\uDC57\uDD1F\uDD3F\uDE50-\uDE58\uDE7F\uDEF0-\uDEF6\uDF39-\uDF3F\uDF99-\uDF9C]|\uD804[\uDC47-\uDC4D\uDCBB\uDCBC\uDCBE-\uDCC1\uDD40-\uDD43\uDD74\uDD75\uDDC5-\uDDC9\uDDCD\uDDDB\uDDDD-\uDDDF\uDE38-\uDE3D\uDEA9]|\uD805[\uDCC6\uDDC1-\uDDD7\uDE41-\uDE43\uDF3C-\uDF3E]|\uD809[\uDC70-\uDC74]|\uD81A[\uDE6E\uDE6F\uDEF5\uDF37-\uDF3B\uDF44]|\uD82F\uDC9F|\uD836[\uDE87-\uDE8B]/
, expandedTab = " " // CommonMark specifies tab as 4 spaces
function switchInline(stream, state, f) { function switchInline(stream, state, f) {
state.f = state.inline = f; state.f = state.inline = f;
@@ -109,6 +116,8 @@ CodeMirror.defineMode("markdown", function(cmCfg, modeCfg) {
function blankLine(state) { function blankLine(state) {
// Reset linkTitle state // Reset linkTitle state
state.linkTitle = false; state.linkTitle = false;
state.linkHref = false;
state.linkText = false;
// Reset EM state // Reset EM state
state.em = false; state.em = false;
// Reset STRONG state // Reset STRONG state
@@ -119,94 +128,106 @@ CodeMirror.defineMode("markdown", function(cmCfg, modeCfg) {
state.quote = 0; state.quote = 0;
// Reset state.indentedCode // Reset state.indentedCode
state.indentedCode = false; state.indentedCode = false;
if (htmlModeMissing && state.f == htmlBlock) { if (state.f == htmlBlock) {
var exit = htmlModeMissing
if (!exit) {
var inner = CodeMirror.innerMode(htmlMode, state.htmlState)
exit = inner.mode.name == "xml" && inner.state.tagStart === null &&
(!inner.state.context && inner.state.tokenize.isInText)
}
if (exit) {
state.f = inlineNormal; state.f = inlineNormal;
state.block = blockNormal; state.block = blockNormal;
state.htmlState = null;
}
} }
// Reset state.trailingSpace // Reset state.trailingSpace
state.trailingSpace = 0; state.trailingSpace = 0;
state.trailingSpaceNewLine = false; state.trailingSpaceNewLine = false;
// Mark this line as blank // Mark this line as blank
state.prevLine = state.thisLine state.prevLine = state.thisLine
state.thisLine = null state.thisLine = {stream: null}
return null; return null;
} }
function blockNormal(stream, state) { function blockNormal(stream, state) {
var firstTokenOnLine = stream.column() === state.indentation;
var sol = stream.sol(); var prevLineLineIsEmpty = lineIsEmpty(state.prevLine.stream);
var prevLineIsIndentedCode = state.indentedCode;
var prevLineIsList = state.list !== false, var prevLineIsHr = state.prevLine.hr;
prevLineIsIndentedCode = state.indentedCode; var prevLineIsList = state.list !== false;
var maxNonCodeIndentation = (state.listStack[state.listStack.length - 1] || 0) + 3;
state.indentedCode = false; state.indentedCode = false;
var lineIndentation = state.indentation;
// compute once per line (on first token)
if (state.indentationDiff === null) {
state.indentationDiff = state.indentation;
if (prevLineIsList) { if (prevLineIsList) {
if (state.indentationDiff >= 0) { // Continued list
if (state.indentationDiff < 4) { // Only adjust indentation if *not* a code block
state.indentation -= state.indentationDiff;
}
state.list = null; state.list = null;
} else if (state.indentation > 0) { // While this list item's marker's indentation is less than the deepest
state.list = null; // list item's content's indentation,pop the deepest list item
} else { // No longer a list // indentation off the stack, and update block indentation state
while (lineIndentation < state.listStack[state.listStack.length - 1]) {
state.listStack.pop();
if (state.listStack.length) {
state.indentation = state.listStack[state.listStack.length - 1];
// less than the first list's indent -> the line is no longer a list
} else {
state.list = false; state.list = false;
} }
} }
if (state.list !== false) {
state.indentationDiff = lineIndentation - state.listStack[state.listStack.length - 1]
}
}
}
// not comprehensive (currently only for setext detection purposes)
var allowsInlineContinuation = (
!prevLineLineIsEmpty && !prevLineIsHr && !state.prevLine.header &&
(!prevLineIsList || !prevLineIsIndentedCode) &&
!state.prevLine.fencedCodeEnd
);
var isHr = (state.list === false || prevLineIsHr || prevLineLineIsEmpty) &&
state.indentation <= maxNonCodeIndentation && stream.match(hrRE);
var match = null; var match = null;
if (state.indentationDiff >= 4) { if (state.indentationDiff >= 4 && (prevLineIsIndentedCode || state.prevLine.fencedCodeEnd ||
state.prevLine.header || prevLineLineIsEmpty)) {
stream.skipToEnd(); stream.skipToEnd();
if (prevLineIsIndentedCode || lineIsEmpty(state.prevLine)) {
state.indentation -= 4;
state.indentedCode = true; state.indentedCode = true;
return tokenTypes.code; return tokenTypes.code;
} else {
return null;
}
} else if (stream.eatSpace()) { } else if (stream.eatSpace()) {
return null; return null;
} else if ((match = stream.match(atxHeaderRE)) && match[1].length <= 6) { } else if (firstTokenOnLine && state.indentation <= maxNonCodeIndentation && (match = stream.match(atxHeaderRE)) && match[1].length <= 6) {
state.quote = 0;
state.header = match[1].length; state.header = match[1].length;
state.thisLine.header = true;
if (modeCfg.highlightFormatting) state.formatting = "header"; if (modeCfg.highlightFormatting) state.formatting = "header";
state.f = state.inline; state.f = state.inline;
return getType(state); return getType(state);
} else if (!lineIsEmpty(state.prevLine) && !state.quote && !prevLineIsList && } else if (state.indentation <= maxNonCodeIndentation && stream.eat('>')) {
!prevLineIsIndentedCode && (match = stream.match(setextHeaderRE))) { state.quote = firstTokenOnLine ? 1 : state.quote + 1;
state.header = match[0].charAt(0) == '=' ? 1 : 2;
if (modeCfg.highlightFormatting) state.formatting = "header";
state.f = state.inline;
return getType(state);
} else if (stream.eat('>')) {
state.quote = sol ? 1 : state.quote + 1;
if (modeCfg.highlightFormatting) state.formatting = "quote"; if (modeCfg.highlightFormatting) state.formatting = "quote";
stream.eatSpace(); stream.eatSpace();
return getType(state); return getType(state);
} else if (stream.peek() === '[') { } else if (!isHr && !state.setext && firstTokenOnLine && state.indentation <= maxNonCodeIndentation && (match = stream.match(listRE))) {
return switchInline(stream, state, footnoteLink); var listType = match[1] ? "ol" : "ul";
} else if (stream.match(hrRE, true)) {
state.hr = true;
return tokenTypes.hr;
} else if ((lineIsEmpty(state.prevLine) || prevLineIsList) && (stream.match(ulRE, false) || stream.match(olRE, false))) {
var listType = null;
if (stream.match(ulRE, true)) {
listType = 'ul';
} else {
stream.match(olRE, true);
listType = 'ol';
}
state.indentation = stream.column() + stream.current().length;
state.list = true;
// While this list item's marker's indentation state.indentation = lineIndentation + stream.current().length;
// is less than the deepest list item's content's indentation, state.list = true;
// pop the deepest list item indentation off the stack. state.quote = 0;
while (state.listStack && stream.column() < state.listStack[state.listStack.length - 1]) {
state.listStack.pop();
}
// Add this list item's content's indentation to the stack // Add this list item's content's indentation to the stack
state.listStack.push(state.indentation); state.listStack.push(state.indentation);
// Reset inline styles which shouldn't propagate across list items
state.em = false;
state.strong = false;
state.code = false;
state.strikethrough = false;
if (modeCfg.taskLists && stream.match(taskListRE, false)) { if (modeCfg.taskLists && stream.match(taskListRE, false)) {
state.taskList = true; state.taskList = true;
@@ -214,15 +235,47 @@ CodeMirror.defineMode("markdown", function(cmCfg, modeCfg) {
state.f = state.inline; state.f = state.inline;
if (modeCfg.highlightFormatting) state.formatting = ["list", "list-" + listType]; if (modeCfg.highlightFormatting) state.formatting = ["list", "list-" + listType];
return getType(state); return getType(state);
} else if (modeCfg.fencedCodeBlocks && (match = stream.match(fencedCodeRE, true))) { } else if (firstTokenOnLine && state.indentation <= maxNonCodeIndentation && (match = stream.match(fencedCodeRE, true))) {
state.fencedChars = match[1] state.quote = 0;
state.fencedEndRE = new RegExp(match[1] + "+ *$");
// try switching mode // try switching mode
state.localMode = getMode(match[2]); state.localMode = modeCfg.fencedCodeBlockHighlighting && getMode(match[2] || modeCfg.fencedCodeBlockDefaultMode );
if (state.localMode) state.localState = CodeMirror.startState(state.localMode); if (state.localMode) state.localState = CodeMirror.startState(state.localMode);
state.f = state.block = local; state.f = state.block = local;
if (modeCfg.highlightFormatting) state.formatting = "code-block"; if (modeCfg.highlightFormatting) state.formatting = "code-block";
state.code = -1 state.code = -1
return getType(state); return getType(state);
// SETEXT has lowest block-scope precedence after HR, so check it after
// the others (code, blockquote, list...)
} else if (
// if setext set, indicates line after ---/===
state.setext || (
// line before ---/===
(!allowsInlineContinuation || !prevLineIsList) && !state.quote && state.list === false &&
!state.code && !isHr && !linkDefRE.test(stream.string) &&
(match = stream.lookAhead(1)) && (match = match.match(setextHeaderRE))
)
) {
if ( !state.setext ) {
state.header = match[0].charAt(0) == '=' ? 1 : 2;
state.setext = state.header;
} else {
state.header = state.setext;
// has no effect on type so we can reset it now
state.setext = 0;
stream.skipToEnd();
if (modeCfg.highlightFormatting) state.formatting = "header";
}
state.thisLine.header = true;
state.f = state.inline;
return getType(state);
} else if (isHr) {
stream.skipToEnd();
state.hr = true;
state.thisLine.hr = true;
return tokenTypes.hr;
} else if (stream.peek() === '[') {
return switchInline(stream, state, footnoteLink);
} }
return switchInline(stream, state, state.inline); return switchInline(stream, state, state.inline);
@@ -244,10 +297,21 @@ CodeMirror.defineMode("markdown", function(cmCfg, modeCfg) {
} }
function local(stream, state) { function local(stream, state) {
if (state.fencedChars && stream.match(state.fencedChars, false)) { var currListInd = state.listStack[state.listStack.length - 1] || 0;
var hasExitedList = state.indentation < currListInd;
var maxFencedEndInd = currListInd + 3;
if (state.fencedEndRE && state.indentation <= maxFencedEndInd && (hasExitedList || stream.match(state.fencedEndRE))) {
if (modeCfg.highlightFormatting) state.formatting = "code-block";
var returnType;
if (!hasExitedList) returnType = getType(state)
state.localMode = state.localState = null; state.localMode = state.localState = null;
state.f = state.block = leavingLocal; state.block = blockNormal;
return null; state.f = inlineNormal;
state.fencedEndRE = null;
state.code = 0
state.thisLine.fencedCodeEnd = true;
if (hasExitedList) return switchBlock(stream, state, state.block);
return returnType;
} else if (state.localMode) { } else if (state.localMode) {
return state.localMode.token(stream, state.localState); return state.localMode.token(stream, state.localState);
} else { } else {
@@ -256,18 +320,6 @@ CodeMirror.defineMode("markdown", function(cmCfg, modeCfg) {
} }
} }
function leavingLocal(stream, state) {
stream.match(state.fencedChars);
state.block = blockNormal;
state.f = inlineNormal;
state.fencedChars = null;
if (modeCfg.highlightFormatting) state.formatting = "code-block";
state.code = 1
var returnType = getType(state);
state.code = 0
return returnType;
}
// Inline // Inline
function getType(state) { function getType(state) {
var styles = []; var styles = [];
@@ -311,8 +363,12 @@ CodeMirror.defineMode("markdown", function(cmCfg, modeCfg) {
if (state.strong) { styles.push(tokenTypes.strong); } if (state.strong) { styles.push(tokenTypes.strong); }
if (state.em) { styles.push(tokenTypes.em); } if (state.em) { styles.push(tokenTypes.em); }
if (state.strikethrough) { styles.push(tokenTypes.strikethrough); } if (state.strikethrough) { styles.push(tokenTypes.strikethrough); }
if (state.emoji) { styles.push(tokenTypes.emoji); }
if (state.linkText) { styles.push(tokenTypes.linkText); } if (state.linkText) { styles.push(tokenTypes.linkText); }
if (state.code) { styles.push(tokenTypes.code); } if (state.code) { styles.push(tokenTypes.code); }
if (state.image) { styles.push(tokenTypes.image); }
if (state.imageAltText) { styles.push(tokenTypes.imageAltText, "link"); }
if (state.imageMarker) { styles.push(tokenTypes.imageMarker); }
} }
if (state.header) { styles.push(tokenTypes.header, tokenTypes.header + "-" + state.header); } if (state.header) { styles.push(tokenTypes.header, tokenTypes.header + "-" + state.header); }
@@ -366,7 +422,7 @@ CodeMirror.defineMode("markdown", function(cmCfg, modeCfg) {
} }
if (state.taskList) { if (state.taskList) {
var taskOpen = stream.match(taskListRE, true)[1] !== "x"; var taskOpen = stream.match(taskListRE, true)[1] === " ";
if (taskOpen) state.taskOpen = true; if (taskOpen) state.taskOpen = true;
else state.taskClosed = true; else state.taskClosed = true;
if (modeCfg.highlightFormatting) state.formatting = "task"; if (modeCfg.highlightFormatting) state.formatting = "task";
@@ -382,9 +438,6 @@ CodeMirror.defineMode("markdown", function(cmCfg, modeCfg) {
return getType(state); return getType(state);
} }
// Get sol() value now, before character is consumed
var sol = stream.sol();
var ch = stream.next(); var ch = stream.next();
// Matches link titles present on next line // Matches link titles present on next line
@@ -394,7 +447,7 @@ CodeMirror.defineMode("markdown", function(cmCfg, modeCfg) {
if (ch === '(') { if (ch === '(') {
matchCh = ')'; matchCh = ')';
} }
matchCh = (matchCh+'').replace(/([.?*+^$[\]\\(){}|-])/g, "\\$1"); matchCh = (matchCh+'').replace(/([.?*+^\[\]\\(){}|-])/g, "\\$1");
var regex = '^\\s*(?:[^' + matchCh + '\\\\]+|\\\\\\\\|\\\\.)' + matchCh; var regex = '^\\s*(?:[^' + matchCh + '\\\\]+|\\\\\\\\|\\\\.)' + matchCh;
if (stream.match(new RegExp(regex), true)) { if (stream.match(new RegExp(regex), true)) {
return tokenTypes.linkHref; return tokenTypes.linkHref;
@@ -407,7 +460,7 @@ CodeMirror.defineMode("markdown", function(cmCfg, modeCfg) {
if (modeCfg.highlightFormatting) state.formatting = "code"; if (modeCfg.highlightFormatting) state.formatting = "code";
stream.eatWhile('`'); stream.eatWhile('`');
var count = stream.current().length var count = stream.current().length
if (state.code == 0) { if (state.code == 0 && (!state.quote || count == 1)) {
state.code = count state.code = count
return getType(state) return getType(state)
} else if (count == state.code) { // Must be exact } else if (count == state.code) { // Must be exact
@@ -432,22 +485,40 @@ CodeMirror.defineMode("markdown", function(cmCfg, modeCfg) {
} }
if (ch === '!' && stream.match(/\[[^\]]*\] ?(?:\(|\[)/, false)) { if (ch === '!' && stream.match(/\[[^\]]*\] ?(?:\(|\[)/, false)) {
stream.match(/\[[^\]]*\]/); state.imageMarker = true;
state.inline = state.f = linkHref; state.image = true;
return tokenTypes.image; if (modeCfg.highlightFormatting) state.formatting = "image";
return getType(state);
} }
if (ch === '[' && stream.match(/[^\]]*\](\(.*\)| ?\[.*?\])/, false)) { if (ch === '[' && state.imageMarker && stream.match(/[^\]]*\](\(.*?\)| ?\[.*?\])/, false)) {
state.imageMarker = false;
state.imageAltText = true
if (modeCfg.highlightFormatting) state.formatting = "image";
return getType(state);
}
if (ch === ']' && state.imageAltText) {
if (modeCfg.highlightFormatting) state.formatting = "image";
var type = getType(state);
state.imageAltText = false;
state.image = false;
state.inline = state.f = linkHref;
return type;
}
if (ch === '[' && !state.image) {
if (state.linkText && stream.match(/^.*?\]/)) return getType(state)
state.linkText = true; state.linkText = true;
if (modeCfg.highlightFormatting) state.formatting = "link"; if (modeCfg.highlightFormatting) state.formatting = "link";
return getType(state); return getType(state);
} }
if (ch === ']' && state.linkText && stream.match(/\(.*?\)| ?\[.*?\]/, false)) { if (ch === ']' && state.linkText) {
if (modeCfg.highlightFormatting) state.formatting = "link"; if (modeCfg.highlightFormatting) state.formatting = "link";
var type = getType(state); var type = getType(state);
state.linkText = false; state.linkText = false;
state.inline = state.f = linkHref; state.inline = state.f = stream.match(/\(.*?\)| ?\[.*?\]/, false) ? linkHref : inlineNormal
return type; return type;
} }
@@ -475,7 +546,7 @@ CodeMirror.defineMode("markdown", function(cmCfg, modeCfg) {
return type + tokenTypes.linkEmail; return type + tokenTypes.linkEmail;
} }
if (ch === '<' && stream.match(/^(!--|\w)/, false)) { if (modeCfg.xml && ch === '<' && stream.match(/^(!--|\?|!\[CDATA\[|[a-z][a-z0-9-]*(?:\s+[a-z_:.\-]+(?:\s*=\s*[^>]+)?)*\s*(?:>|$))/i, false)) {
var end = stream.string.indexOf(">", stream.pos); var end = stream.string.indexOf(">", stream.pos);
if (end != -1) { if (end != -1) {
var atts = stream.string.substring(stream.start, end); var atts = stream.string.substring(stream.start, end);
@@ -486,44 +557,37 @@ CodeMirror.defineMode("markdown", function(cmCfg, modeCfg) {
return switchBlock(stream, state, htmlBlock); return switchBlock(stream, state, htmlBlock);
} }
if (ch === '<' && stream.match(/^\/\w*?>/)) { if (modeCfg.xml && ch === '<' && stream.match(/^\/\w*?>/)) {
state.md_inside = false; state.md_inside = false;
return "tag"; return "tag";
} else if (ch === "*" || ch === "_") {
var len = 1, before = stream.pos == 1 ? " " : stream.string.charAt(stream.pos - 2)
while (len < 3 && stream.eat(ch)) len++
var after = stream.peek() || " "
// See http://spec.commonmark.org/0.27/#emphasis-and-strong-emphasis
var leftFlanking = !/\s/.test(after) && (!punctuation.test(after) || /\s/.test(before) || punctuation.test(before))
var rightFlanking = !/\s/.test(before) && (!punctuation.test(before) || /\s/.test(after) || punctuation.test(after))
var setEm = null, setStrong = null
if (len % 2) { // Em
if (!state.em && leftFlanking && (ch === "*" || !rightFlanking || punctuation.test(before)))
setEm = true
else if (state.em == ch && rightFlanking && (ch === "*" || !leftFlanking || punctuation.test(after)))
setEm = false
} }
if (len > 1) { // Strong
var ignoreUnderscore = false; if (!state.strong && leftFlanking && (ch === "*" || !rightFlanking || punctuation.test(before)))
if (!modeCfg.underscoresBreakWords) { setStrong = true
if (ch === '_' && stream.peek() !== '_' && stream.match(/(\w)/, false)) { else if (state.strong == ch && rightFlanking && (ch === "*" || !leftFlanking || punctuation.test(after)))
var prevPos = stream.pos - 2; setStrong = false
if (prevPos >= 0) {
var prevCh = stream.string.charAt(prevPos);
if (prevCh !== '_' && prevCh.match(/(\w)/, false)) {
ignoreUnderscore = true;
} }
} if (setStrong != null || setEm != null) {
} if (modeCfg.highlightFormatting) state.formatting = setEm == null ? "strong" : setStrong == null ? "em" : "strong em"
} if (setEm === true) state.em = ch
if (ch === '*' || (ch === '_' && !ignoreUnderscore)) { if (setStrong === true) state.strong = ch
if (sol && stream.peek() === ' ') { var t = getType(state)
// Do nothing, surrounded by newline and space if (setEm === false) state.em = false
} else if (state.strong === ch && stream.eat(ch)) { // Remove STRONG if (setStrong === false) state.strong = false
if (modeCfg.highlightFormatting) state.formatting = "strong"; return t
var t = getType(state);
state.strong = false;
return t;
} else if (!state.strong && stream.eat(ch)) { // Add STRONG
state.strong = ch;
if (modeCfg.highlightFormatting) state.formatting = "strong";
return getType(state);
} else if (state.em === ch) { // Remove EM
if (modeCfg.highlightFormatting) state.formatting = "em";
var t = getType(state);
state.em = false;
return t;
} else if (!state.em) { // Add EM
state.em = ch;
if (modeCfg.highlightFormatting) state.formatting = "em";
return getType(state);
} }
} else if (ch === ' ') { } else if (ch === ' ') {
if (stream.eat('*') || stream.eat('_')) { // Probably surrounded by spaces if (stream.eat('*') || stream.eat('_')) { // Probably surrounded by spaces
@@ -548,7 +612,7 @@ CodeMirror.defineMode("markdown", function(cmCfg, modeCfg) {
return getType(state); return getType(state);
} }
} else if (ch === ' ') { } else if (ch === ' ') {
if (stream.match(/^~~/, true)) { // Probably surrounded by space if (stream.match('~~', true)) { // Probably surrounded by space
if (stream.peek() === ' ') { // Surrounded by spaces, ignore if (stream.peek() === ' ') { // Surrounded by spaces, ignore
return getType(state); return getType(state);
} else { // Not surrounded by spaces, back up pointer } else { // Not surrounded by spaces, back up pointer
@@ -558,8 +622,16 @@ CodeMirror.defineMode("markdown", function(cmCfg, modeCfg) {
} }
} }
if (modeCfg.emoji && ch === ":" && stream.match(/^(?:[a-z_\d+][a-z_\d+-]*|\-[a-z_\d+][a-z_\d+-]*):/)) {
state.emoji = true;
if (modeCfg.highlightFormatting) state.formatting = "emoji";
var retType = getType(state);
state.emoji = false;
return retType;
}
if (ch === ' ') { if (ch === ' ') {
if (stream.match(/ +$/, false)) { if (stream.match(/^ +$/, false)) {
state.trailingSpace++; state.trailingSpace++;
} else if (state.trailingSpace) { } else if (state.trailingSpace) {
state.trailingSpaceNewLine = true; state.trailingSpaceNewLine = true;
@@ -596,7 +668,7 @@ CodeMirror.defineMode("markdown", function(cmCfg, modeCfg) {
} }
var ch = stream.next(); var ch = stream.next();
if (ch === '(' || ch === '[') { if (ch === '(' || ch === '[') {
state.f = state.inline = getLinkHrefInside(ch === "(" ? ")" : "]", 0); state.f = state.inline = getLinkHrefInside(ch === "(" ? ")" : "]");
if (modeCfg.highlightFormatting) state.formatting = "link-string"; if (modeCfg.highlightFormatting) state.formatting = "link-string";
state.linkHref = true; state.linkHref = true;
return getType(state); return getType(state);
@@ -606,7 +678,7 @@ CodeMirror.defineMode("markdown", function(cmCfg, modeCfg) {
var linkRE = { var linkRE = {
")": /^(?:[^\\\(\)]|\\.|\((?:[^\\\(\)]|\\.)*\))*?(?=\))/, ")": /^(?:[^\\\(\)]|\\.|\((?:[^\\\(\)]|\\.)*\))*?(?=\))/,
"]": /^(?:[^\\\[\]]|\\.|\[(?:[^\\\[\\]]|\\.)*\])*?(?=\])/ "]": /^(?:[^\\\[\]]|\\.|\[(?:[^\\\[\]]|\\.)*\])*?(?=\])/
} }
function getLinkHrefInside(endChar) { function getLinkHrefInside(endChar) {
@@ -639,7 +711,7 @@ CodeMirror.defineMode("markdown", function(cmCfg, modeCfg) {
} }
function footnoteLinkInside(stream, state) { function footnoteLinkInside(stream, state) {
if (stream.match(/^\]:/, true)) { if (stream.match(']:', true)) {
state.f = state.inline = footnoteUrl; state.f = state.inline = footnoteUrl;
if (modeCfg.highlightFormatting) state.formatting = "link"; if (modeCfg.highlightFormatting) state.formatting = "link";
var returnType = getType(state); var returnType = getType(state);
@@ -663,7 +735,7 @@ CodeMirror.defineMode("markdown", function(cmCfg, modeCfg) {
if (stream.peek() === undefined) { // End of line, set flag to check next line if (stream.peek() === undefined) { // End of line, set flag to check next line
state.linkTitle = true; state.linkTitle = true;
} else { // More content on line, check if link title } else { // More content on line, check if link title
stream.match(/^(?:\s+(?:"(?:[^"\\]|\\\\|\\.)+"|'(?:[^'\\]|\\\\|\\.)+'|\((?:[^)\\]|\\\\|\\.)+\)))?/, true); stream.match(/^(?:\s+(?:"(?:[^"\\]|\\.)+"|'(?:[^'\\]|\\.)+'|\((?:[^)\\]|\\.)+\)))?/, true);
} }
state.f = state.inline = inlineNormal; state.f = state.inline = inlineNormal;
return tokenTypes.linkHref + " url"; return tokenTypes.linkHref + " url";
@@ -674,8 +746,8 @@ CodeMirror.defineMode("markdown", function(cmCfg, modeCfg) {
return { return {
f: blockNormal, f: blockNormal,
prevLine: null, prevLine: {stream: null},
thisLine: null, thisLine: {stream: null},
block: blockNormal, block: blockNormal,
htmlState: null, htmlState: null,
@@ -692,6 +764,7 @@ CodeMirror.defineMode("markdown", function(cmCfg, modeCfg) {
em: false, em: false,
strong: false, strong: false,
header: 0, header: 0,
setext: 0,
hr: false, hr: false,
taskList: false, taskList: false,
list: false, list: false,
@@ -700,7 +773,8 @@ CodeMirror.defineMode("markdown", function(cmCfg, modeCfg) {
trailingSpace: 0, trailingSpace: 0,
trailingSpaceNewLine: false, trailingSpaceNewLine: false,
strikethrough: false, strikethrough: false,
fencedChars: null emoji: false,
fencedEndRE: null
}; };
}, },
@@ -721,12 +795,16 @@ CodeMirror.defineMode("markdown", function(cmCfg, modeCfg) {
inline: s.inline, inline: s.inline,
text: s.text, text: s.text,
formatting: false, formatting: false,
linkText: s.linkText,
linkTitle: s.linkTitle, linkTitle: s.linkTitle,
linkHref: s.linkHref,
code: s.code, code: s.code,
em: s.em, em: s.em,
strong: s.strong, strong: s.strong,
strikethrough: s.strikethrough, strikethrough: s.strikethrough,
emoji: s.emoji,
header: s.header, header: s.header,
setext: s.setext,
hr: s.hr, hr: s.hr,
taskList: s.taskList, taskList: s.taskList,
list: s.list, list: s.list,
@@ -736,7 +814,7 @@ CodeMirror.defineMode("markdown", function(cmCfg, modeCfg) {
trailingSpace: s.trailingSpace, trailingSpace: s.trailingSpace,
trailingSpaceNewLine: s.trailingSpaceNewLine, trailingSpaceNewLine: s.trailingSpaceNewLine,
md_inside: s.md_inside, md_inside: s.md_inside,
fencedChars: s.fencedChars fencedEndRE: s.fencedEndRE
}; };
}, },
@@ -745,21 +823,17 @@ CodeMirror.defineMode("markdown", function(cmCfg, modeCfg) {
// Reset state.formatting // Reset state.formatting
state.formatting = false; state.formatting = false;
if (stream != state.thisLine) { if (stream != state.thisLine.stream) {
var forceBlankLine = state.header || state.hr;
// Reset state.header and state.hr
state.header = 0; state.header = 0;
state.hr = false; state.hr = false;
if (stream.match(/^\s*$/, true) || forceBlankLine) { if (stream.match(/^\s*$/, true)) {
blankLine(state); blankLine(state);
if (!forceBlankLine) return null return null;
state.prevLine = null
} }
state.prevLine = state.thisLine state.prevLine = state.thisLine
state.thisLine = stream state.thisLine = {stream: stream}
// Reset state.taskList // Reset state.taskList
state.taskList = false; state.taskList = false;
@@ -768,12 +842,16 @@ CodeMirror.defineMode("markdown", function(cmCfg, modeCfg) {
state.trailingSpace = 0; state.trailingSpace = 0;
state.trailingSpaceNewLine = false; state.trailingSpaceNewLine = false;
if (!state.localState) {
state.f = state.block; state.f = state.block;
var indentation = stream.match(/^\s*/, true)[0].replace(/\t/g, ' ').length; if (state.f != htmlBlock) {
state.indentationDiff = Math.min(indentation - state.indentation, 4); var indentation = stream.match(/^\s*/, true)[0].replace(/\t/g, expandedTab).length;
state.indentation = state.indentation + state.indentationDiff; state.indentation = indentation;
state.indentationDiff = null;
if (indentation > 0) return null; if (indentation > 0) return null;
} }
}
}
return state.f(stream, state); return state.f(stream, state);
}, },
@@ -783,15 +861,26 @@ CodeMirror.defineMode("markdown", function(cmCfg, modeCfg) {
return {state: state, mode: mode}; return {state: state, mode: mode};
}, },
indent: function(state, textAfter, line) {
if (state.block == htmlBlock && htmlMode.indent) return htmlMode.indent(state.htmlState, textAfter, line)
if (state.localState && state.localMode.indent) return state.localMode.indent(state.localState, textAfter, line)
return CodeMirror.Pass
},
blankLine: blankLine, blankLine: blankLine,
getType: getType, getType: getType,
blockCommentStart: "<!--",
blockCommentEnd: "-->",
closeBrackets: "()[]{}''\"\"``",
fold: "markdown" fold: "markdown"
}; };
return mode; return mode;
}, "xml"); }, "xml");
CodeMirror.defineMIME("text/markdown", "markdown");
CodeMirror.defineMIME("text/x-markdown", "markdown"); CodeMirror.defineMIME("text/x-markdown", "markdown");
}); });

View File

@@ -1,5 +1,5 @@
// CodeMirror, copyright (c) by Marijn Haverbeke and others // CodeMirror, copyright (c) by Marijn Haverbeke and others
// Distributed under an MIT license: http://codemirror.net/LICENSE // Distributed under an MIT license: https://codemirror.net/5/LICENSE
(function(mod) { (function(mod) {
if (typeof exports == "object" && typeof module == "object") // CommonJS if (typeof exports == "object" && typeof module == "object") // CommonJS
@@ -261,7 +261,7 @@ CodeMirror.defineMode("pug", function (config) {
} }
return 'variable'; return 'variable';
} }
if (stream.match(/^\+#{/, false)) { if (stream.match('+#{', false)) {
stream.next(); stream.next();
state.mixinCallAfter = true; state.mixinCallAfter = true;
return interpolation(stream, state); return interpolation(stream, state);
@@ -545,12 +545,12 @@ CodeMirror.defineMode("pug", function (config) {
|| javaScriptArguments(stream, state) || javaScriptArguments(stream, state)
|| callArguments(stream, state) || callArguments(stream, state)
|| yieldStatement(stream, state) || yieldStatement(stream)
|| doctype(stream, state) || doctype(stream)
|| interpolation(stream, state) || interpolation(stream, state)
|| caseStatement(stream, state) || caseStatement(stream, state)
|| when(stream, state) || when(stream, state)
|| defaultStatement(stream, state) || defaultStatement(stream)
|| extendsStatement(stream, state) || extendsStatement(stream, state)
|| append(stream, state) || append(stream, state)
|| prepend(stream, state) || prepend(stream, state)
@@ -565,16 +565,16 @@ CodeMirror.defineMode("pug", function (config) {
|| tag(stream, state) || tag(stream, state)
|| filter(stream, state) || filter(stream, state)
|| code(stream, state) || code(stream, state)
|| id(stream, state) || id(stream)
|| className(stream, state) || className(stream)
|| attrs(stream, state) || attrs(stream, state)
|| attributesBlock(stream, state) || attributesBlock(stream, state)
|| indent(stream, state) || indent(stream)
|| text(stream, state) || text(stream, state)
|| comment(stream, state) || comment(stream, state)
|| colon(stream, state) || colon(stream)
|| dot(stream, state) || dot(stream, state)
|| fail(stream, state); || fail(stream);
return tok === true ? null : tok; return tok === true ? null : tok;
} }

View File

@@ -1,17 +1,23 @@
// CodeMirror, copyright (c) by Marijn Haverbeke and others // CodeMirror, copyright (c) by Marijn Haverbeke and others
// Distributed under an MIT license: http://codemirror.net/LICENSE // Distributed under an MIT license: https://codemirror.net/5/LICENSE
(function(mod) { (function(mod) {
if (typeof exports == "object" && typeof module == "object") // CommonJS if (typeof exports == "object" && typeof module == "object") // CommonJS
mod(require("../../lib/codemirror")); mod(require("../../lib/codemirror"), require("../css/css"));
else if (typeof define == "function" && define.amd) // AMD else if (typeof define == "function" && define.amd) // AMD
define(["../../lib/codemirror"], mod); define(["../../lib/codemirror", "../css/css"], mod);
else // Plain browser env else // Plain browser env
mod(CodeMirror); mod(CodeMirror);
})(function(CodeMirror) { })(function(CodeMirror) {
"use strict"; "use strict";
CodeMirror.defineMode("sass", function(config) { CodeMirror.defineMode("sass", function(config) {
var cssMode = CodeMirror.mimeModes["text/css"];
var propertyKeywords = cssMode.propertyKeywords || {},
colorKeywords = cssMode.colorKeywords || {},
valueKeywords = cssMode.valueKeywords || {},
fontProperties = cssMode.fontProperties || {};
function tokenRegexp(words) { function tokenRegexp(words) {
return new RegExp("^" + words.join("|")); return new RegExp("^" + words.join("|"));
} }
@@ -25,6 +31,12 @@ CodeMirror.defineMode("sass", function(config) {
var pseudoElementsRegexp = /^::?[a-zA-Z_][\w\-]*/; var pseudoElementsRegexp = /^::?[a-zA-Z_][\w\-]*/;
var word;
function isEndLine(stream) {
return !stream.peek() || stream.match(/\s+$/, false);
}
function urlTokens(stream, state) { function urlTokens(stream, state) {
var ch = stream.peek(); var ch = stream.peek();
@@ -76,6 +88,9 @@ CodeMirror.defineMode("sass", function(config) {
if (endingString) { if (endingString) {
if (nextChar !== quote && greedy) { stream.next(); } if (nextChar !== quote && greedy) { stream.next(); }
if (isEndLine(stream)) {
state.cursorHalf = 0;
}
state.tokenizer = tokenBase; state.tokenizer = tokenBase;
return "string"; return "string";
} else if (nextChar === "#" && peekChar === "{") { } else if (nextChar === "#" && peekChar === "{") {
@@ -147,14 +162,20 @@ CodeMirror.defineMode("sass", function(config) {
// first half i.e. before : for key-value pairs // first half i.e. before : for key-value pairs
// including selectors // including selectors
if (ch === "-") {
if (stream.match(/^-\w+-/)) {
return "meta";
}
}
if (ch === ".") { if (ch === ".") {
stream.next(); stream.next();
if (stream.match(/^[\w-]+/)) { if (stream.match(/^[\w-]+/)) {
indent(state); indent(state);
return "atom"; return "qualifier";
} else if (stream.peek() === "#") { } else if (stream.peek() === "#") {
indent(state); indent(state);
return "atom"; return "tag";
} }
} }
@@ -163,11 +184,11 @@ CodeMirror.defineMode("sass", function(config) {
// ID selectors // ID selectors
if (stream.match(/^[\w-]+/)) { if (stream.match(/^[\w-]+/)) {
indent(state); indent(state);
return "atom"; return "builtin";
} }
if (stream.peek() === "#") { if (stream.peek() === "#") {
indent(state); indent(state);
return "atom"; return "tag";
} }
} }
@@ -210,7 +231,7 @@ CodeMirror.defineMode("sass", function(config) {
} }
if(ch === "@"){ if(ch === "@"){
if(stream.match(/@extend/)){ if(stream.match('@extend')){
if(!stream.match(/\s*[\w]/)) if(!stream.match(/\s*[\w]/))
dedent(state); dedent(state);
} }
@@ -220,37 +241,48 @@ CodeMirror.defineMode("sass", function(config) {
// Indent Directives // Indent Directives
if (stream.match(/^@(else if|if|media|else|for|each|while|mixin|function)/)) { if (stream.match(/^@(else if|if|media|else|for|each|while|mixin|function)/)) {
indent(state); indent(state);
return "meta"; return "def";
} }
// Other Directives // Other Directives
if (ch === "@") { if (ch === "@") {
stream.next(); stream.next();
stream.eatWhile(/[\w-]/); stream.eatWhile(/[\w-]/);
return "meta"; return "def";
} }
if (stream.eatWhile(/[\w-]/)){ if (stream.eatWhile(/[\w-]/)){
if(stream.match(/ *: *[\w-\+\$#!\("']/,false)){ if(stream.match(/ *: *[\w-\+\$#!\("']/,false)){
word = stream.current().toLowerCase();
var prop = state.prevProp + "-" + word;
if (propertyKeywords.hasOwnProperty(prop)) {
return "property"; return "property";
} else if (propertyKeywords.hasOwnProperty(word)) {
state.prevProp = word;
return "property";
} else if (fontProperties.hasOwnProperty(word)) {
return "property";
}
return "tag";
} }
else if(stream.match(/ *:/,false)){ else if(stream.match(/ *:/,false)){
indent(state); indent(state);
state.cursorHalf = 1; state.cursorHalf = 1;
return "atom"; state.prevProp = stream.current().toLowerCase();
return "property";
} }
else if(stream.match(/ *,/,false)){ else if(stream.match(/ *,/,false)){
return "atom"; return "tag";
} }
else{ else{
indent(state); indent(state);
return "atom"; return "tag";
} }
} }
if(ch === ":"){ if(ch === ":"){
if (stream.match(pseudoElementsRegexp)){ // could be a pseudo-element if (stream.match(pseudoElementsRegexp)){ // could be a pseudo-element
return "keyword"; return "variable-3";
} }
stream.next(); stream.next();
state.cursorHalf=1; state.cursorHalf=1;
@@ -264,7 +296,7 @@ CodeMirror.defineMode("sass", function(config) {
stream.next(); stream.next();
// Hex numbers // Hex numbers
if (stream.match(/[0-9a-fA-F]{6}|[0-9a-fA-F]{3}/)){ if (stream.match(/[0-9a-fA-F]{6}|[0-9a-fA-F]{3}/)){
if(!stream.peek()){ if (isEndLine(stream)) {
state.cursorHalf = 0; state.cursorHalf = 0;
} }
return "number"; return "number";
@@ -273,7 +305,7 @@ CodeMirror.defineMode("sass", function(config) {
// Numbers // Numbers
if (stream.match(/^-?[0-9\.]+/)){ if (stream.match(/^-?[0-9\.]+/)){
if(!stream.peek()){ if (isEndLine(stream)) {
state.cursorHalf = 0; state.cursorHalf = 0;
} }
return "number"; return "number";
@@ -281,14 +313,14 @@ CodeMirror.defineMode("sass", function(config) {
// Units // Units
if (stream.match(/^(px|em|in)\b/)){ if (stream.match(/^(px|em|in)\b/)){
if(!stream.peek()){ if (isEndLine(stream)) {
state.cursorHalf = 0; state.cursorHalf = 0;
} }
return "unit"; return "unit";
} }
if (stream.match(keywordsRegexp)){ if (stream.match(keywordsRegexp)){
if(!stream.peek()){ if (isEndLine(stream)) {
state.cursorHalf = 0; state.cursorHalf = 0;
} }
return "keyword"; return "keyword";
@@ -296,7 +328,7 @@ CodeMirror.defineMode("sass", function(config) {
if (stream.match(/^url/) && stream.peek() === "(") { if (stream.match(/^url/) && stream.peek() === "(") {
state.tokenizer = urlTokens; state.tokenizer = urlTokens;
if(!stream.peek()){ if (isEndLine(stream)) {
state.cursorHalf = 0; state.cursorHalf = 0;
} }
return "atom"; return "atom";
@@ -306,23 +338,21 @@ CodeMirror.defineMode("sass", function(config) {
if (ch === "$") { if (ch === "$") {
stream.next(); stream.next();
stream.eatWhile(/[\w-]/); stream.eatWhile(/[\w-]/);
if(!stream.peek()){ if (isEndLine(stream)) {
state.cursorHalf = 0; state.cursorHalf = 0;
} }
return "variable-3"; return "variable-2";
} }
// bang character for !important, !default, etc. // bang character for !important, !default, etc.
if (ch === "!") { if (ch === "!") {
stream.next(); stream.next();
if(!stream.peek()){
state.cursorHalf = 0; state.cursorHalf = 0;
}
return stream.match(/^[\w]+/) ? "keyword": "operator"; return stream.match(/^[\w]+/) ? "keyword": "operator";
} }
if (stream.match(opRegexp)){ if (stream.match(opRegexp)){
if(!stream.peek()){ if (isEndLine(stream)) {
state.cursorHalf = 0; state.cursorHalf = 0;
} }
return "operator"; return "operator";
@@ -330,14 +360,24 @@ CodeMirror.defineMode("sass", function(config) {
// attributes // attributes
if (stream.eatWhile(/[\w-]/)) { if (stream.eatWhile(/[\w-]/)) {
if(!stream.peek()){ if (isEndLine(stream)) {
state.cursorHalf = 0; state.cursorHalf = 0;
} }
return "attribute"; word = stream.current().toLowerCase();
if (valueKeywords.hasOwnProperty(word)) {
return "atom";
} else if (colorKeywords.hasOwnProperty(word)) {
return "keyword";
} else if (propertyKeywords.hasOwnProperty(word)) {
state.prevProp = stream.current().toLowerCase();
return "property";
} else {
return "tag";
}
} }
//stream.eatSpace(); //stream.eatSpace();
if(!stream.peek()){ if (isEndLine(stream)) {
state.cursorHalf = 0; state.cursorHalf = 0;
return null; return null;
} }
@@ -405,9 +445,14 @@ CodeMirror.defineMode("sass", function(config) {
indent: function(state) { indent: function(state) {
return state.scopes[0].offset; return state.scopes[0].offset;
} },
blockCommentStart: "/*",
blockCommentEnd: "*/",
lineComment: "//",
fold: "indent"
}; };
}); }, "css");
CodeMirror.defineMIME("text/x-sass", "sass"); CodeMirror.defineMIME("text/x-sass", "sass");

File diff suppressed because one or more lines are too long

View File

@@ -1,5 +1,5 @@
// CodeMirror, copyright (c) by Marijn Haverbeke and others // CodeMirror, copyright (c) by Marijn Haverbeke and others
// Distributed under an MIT license: http://codemirror.net/LICENSE // Distributed under an MIT license: https://codemirror.net/5/LICENSE
(function(mod) { (function(mod) {
if (typeof exports == "object" && typeof module == "object") // CommonJS if (typeof exports == "object" && typeof module == "object") // CommonJS
@@ -52,6 +52,7 @@ var xmlConfig = {
doNotIndent: {}, doNotIndent: {},
allowUnquoted: false, allowUnquoted: false,
allowMissing: false, allowMissing: false,
allowMissingTagName: false,
caseFold: false caseFold: false
} }
@@ -162,8 +163,9 @@ CodeMirror.defineMode("xml", function(editorConf, config_) {
stream.next(); stream.next();
} }
return style; return style;
};
} }
}
function doctype(depth) { function doctype(depth) {
return function(stream, state) { return function(stream, state) {
var ch; var ch;
@@ -185,9 +187,13 @@ CodeMirror.defineMode("xml", function(editorConf, config_) {
}; };
} }
function lower(tagName) {
return tagName && tagName.toLowerCase();
}
function Context(state, tagName, startOfLine) { function Context(state, tagName, startOfLine) {
this.prev = state.context; this.prev = state.context;
this.tagName = tagName; this.tagName = tagName || "";
this.indent = state.indented; this.indent = state.indented;
this.startOfLine = startOfLine; this.startOfLine = startOfLine;
if (config.doNotIndent.hasOwnProperty(tagName) || (state.context && state.context.noIndent)) if (config.doNotIndent.hasOwnProperty(tagName) || (state.context && state.context.noIndent))
@@ -203,8 +209,8 @@ CodeMirror.defineMode("xml", function(editorConf, config_) {
return; return;
} }
parentTagName = state.context.tagName; parentTagName = state.context.tagName;
if (!config.contextGrabbers.hasOwnProperty(parentTagName) || if (!config.contextGrabbers.hasOwnProperty(lower(parentTagName)) ||
!config.contextGrabbers[parentTagName].hasOwnProperty(nextTagName)) { !config.contextGrabbers[lower(parentTagName)].hasOwnProperty(lower(nextTagName))) {
return; return;
} }
popContext(state); popContext(state);
@@ -226,6 +232,9 @@ CodeMirror.defineMode("xml", function(editorConf, config_) {
state.tagName = stream.current(); state.tagName = stream.current();
setStyle = "tag"; setStyle = "tag";
return attrState; return attrState;
} else if (config.allowMissingTagName && type == "endTag") {
setStyle = "tag bracket";
return attrState(type, stream, state);
} else { } else {
setStyle = "error"; setStyle = "error";
return tagNameState; return tagNameState;
@@ -235,7 +244,7 @@ CodeMirror.defineMode("xml", function(editorConf, config_) {
if (type == "word") { if (type == "word") {
var tagName = stream.current(); var tagName = stream.current();
if (state.context && state.context.tagName != tagName && if (state.context && state.context.tagName != tagName &&
config.implicitlyClosed.hasOwnProperty(state.context.tagName)) config.implicitlyClosed.hasOwnProperty(lower(state.context.tagName)))
popContext(state); popContext(state);
if ((state.context && state.context.tagName == tagName) || config.matchClosing === false) { if ((state.context && state.context.tagName == tagName) || config.matchClosing === false) {
setStyle = "tag"; setStyle = "tag";
@@ -244,6 +253,9 @@ CodeMirror.defineMode("xml", function(editorConf, config_) {
setStyle = "tag error"; setStyle = "tag error";
return closeStateErr; return closeStateErr;
} }
} else if (config.allowMissingTagName && type == "endTag") {
setStyle = "tag bracket";
return closeState(type, stream, state);
} else { } else {
setStyle = "error"; setStyle = "error";
return closeStateErr; return closeStateErr;
@@ -271,7 +283,7 @@ CodeMirror.defineMode("xml", function(editorConf, config_) {
var tagName = state.tagName, tagStart = state.tagStart; var tagName = state.tagName, tagStart = state.tagStart;
state.tagName = state.tagStart = null; state.tagName = state.tagStart = null;
if (type == "selfcloseTag" || if (type == "selfcloseTag" ||
config.autoSelfClosers.hasOwnProperty(tagName)) { config.autoSelfClosers.hasOwnProperty(lower(tagName))) {
maybePopContext(state, tagName); maybePopContext(state, tagName);
} else { } else {
maybePopContext(state, tagName); maybePopContext(state, tagName);
@@ -351,7 +363,7 @@ CodeMirror.defineMode("xml", function(editorConf, config_) {
if (context.tagName == tagAfter[2]) { if (context.tagName == tagAfter[2]) {
context = context.prev; context = context.prev;
break; break;
} else if (config.implicitlyClosed.hasOwnProperty(context.tagName)) { } else if (config.implicitlyClosed.hasOwnProperty(lower(context.tagName))) {
context = context.prev; context = context.prev;
} else { } else {
break; break;
@@ -359,8 +371,8 @@ CodeMirror.defineMode("xml", function(editorConf, config_) {
} }
} else if (tagAfter) { // Opening tag spotted } else if (tagAfter) { // Opening tag spotted
while (context) { while (context) {
var grabbers = config.contextGrabbers[context.tagName]; var grabbers = config.contextGrabbers[lower(context.tagName)];
if (grabbers && grabbers.hasOwnProperty(tagAfter[2])) if (grabbers && grabbers.hasOwnProperty(lower(tagAfter[2])))
context = context.prev; context = context.prev;
else else
break; break;
@@ -382,6 +394,17 @@ CodeMirror.defineMode("xml", function(editorConf, config_) {
skipAttribute: function(state) { skipAttribute: function(state) {
if (state.state == attrValueState) if (state.state == attrValueState)
state.state = attrState state.state = attrState
},
xmlCurrentTag: function(state) {
return state.tagName ? {name: state.tagName, close: state.type == "closeTag"} : null
},
xmlCurrentContext: function(state) {
var context = []
for (var cx = state.context; cx; cx = cx.prev)
context.push(cx.tagName)
return context.reverse()
} }
}; };
}); });

30
src/lib/hint.min.css vendored

File diff suppressed because one or more lines are too long

View File

@@ -95,9 +95,14 @@ export const jsLibs = [
type: 'js' type: 'js'
}, },
{ {
url: 'https://cdn.tailwindcss.com/3.3.1', url: 'https://cdn.tailwindcss.com/3.4.3',
label: 'Tailwind 3', label: 'Tailwind 3',
type: 'js' type: 'js'
},
{
url: 'https://unpkg.com/kaboom@3000/dist/kaboom.js',
label: 'Kaboom',
type: 'js'
} }
]; ];
export const cssLibs = [ export const cssLibs = [
@@ -128,7 +133,7 @@ export const cssLibs = [
}, },
{ {
url: 'https://cdnjs.cloudflare.com/ajax/libs/hint.css/2.7.0/hint.min.css', url: 'https://cdnjs.cloudflare.com/ajax/libs/hint.css/3.0.0/hint.min.css',
label: 'Hint.css', label: 'Hint.css',
type: 'css' type: 'css'
}, },

View File

@@ -1,7 +1,7 @@
{ {
"name": "Web Maker", "name": "Web Maker",
"version": "5.2.0", "version": "6.0.0",
"manifest_version": 3, "manifest_version": 2,
"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"], "permissions": ["storage", "tabs"],
@@ -9,7 +9,7 @@
"host_permissions": ["<all_urls>"], "host_permissions": ["<all_urls>"],
"content_security_policy": { "content_security_policy": {
"extension_pages": "script-src 'self'; object-src 'self'", "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 'unsafe-inline' 'unsafe-eval'; child-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 'unsafe-inline' 'unsafe-eval'; child-src 'self';"
}, },
"options_ui": { "options_ui": {
"page": "options.html", "page": "options.html",

View File

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

View File

@@ -3,16 +3,33 @@
--color-text-dark-1: #b3aec4; --color-text-dark-1: #b3aec4;
--color-bg: #252637; --color-bg: #252637;
--color-popup: #3a2b63; --color-popup: #3a2b63;
--color-overlay: rgb(0 0 0 / 40%);
--color-close-btn: #d12b4a;
--code-font-size: 16px; --code-font-size: 16px;
--color-button: #d3a447; --color-button: #e3ba26;
--color-focus-outline: #d3a447; --color-focus-outline: #d3a447;
--color-form-control-bg: #2c214b; --color-form-control-bg: #2c214b;
--color-heading: #fff;
--color-text-light: #b0a5d6;
--color-text-lightest-final: #787090;
--clr-brand: purple;
--color-pro-1: #1fffb3;
--color-pro-2: #f2ff00;
--color-btn-hover-1: hsl(53.35deg 100% 50%);
--color-btn-hover-2: hsl(38.96deg 100% 50%);
--footer-height: 37px; --footer-height: 37px;
--console-height: 32px; --console-height: 32px;
--duration-modal-show: 0.3s; --duration-modal-show: 0.3s;
--duration-modal-overlay-show: 0.2s; --duration-modal-overlay-show: 0.2s;
--zindex-modal-overlay: 5;
--zindex-footer: 6;
--zindex-modal: 2000;
} }
html { html {
@@ -85,6 +102,7 @@ p {
button { button {
font-family: inherit; font-family: inherit;
font-size: 100%; font-size: 100%;
cursor: pointer;
} }
.hide { .hide {
@@ -120,11 +138,13 @@ button {
.ai-c { .ai-c {
align-items: center; align-items: center;
} }
.flex-h-center { .flex-h-center,
.jc-c {
justify-content: center; justify-content: center;
} }
.flex-h-end { .flex-h-end,
.jc-fe {
justify-content: flex-end; justify-content: flex-end;
} }
@@ -213,6 +233,10 @@ button {
color: #f7ae2d; color: #f7ae2d;
} }
.para {
max-width: 60ch;
}
@media screen and (max-width: 600px) { @media screen and (max-width: 600px) {
.block--mobile { .block--mobile {
display: block; display: block;
@@ -256,11 +280,17 @@ label {
width: 1px; width: 1px;
height: 100%; height: 100%;
} }
[class*='hint--']:after,
[class*='hint--']:before {
background-color: #000;
border-radius: 0 4px 0 0;
}
[class*='hint--']:after { [class*='hint--']:after {
text-transform: none; text-transform: none;
font-weight: normal; font-weight: normal;
letter-spacing: 0.5px; letter-spacing: 0.5px;
font-size: 14px; font-size: 14px;
border-radius: 4px;
} }
.line { .line {
@@ -373,6 +403,7 @@ a > svg {
float: right; float: right;
display: flex; display: flex;
align-items: center; align-items: center;
margin-inline-start: 2rem;
} }
.btn { .btn {
@@ -382,7 +413,7 @@ a > svg {
font-size: inherit; font-size: inherit;
background: transparent; background: transparent;
border: 3px solid var(--color-button); border: 3px solid var(--color-button);
border-radius: 5px; border-radius: 2rem;
padding: 9px 15px; padding: 9px 15px;
cursor: pointer; cursor: pointer;
letter-spacing: 0.2px; letter-spacing: 0.2px;
@@ -394,15 +425,22 @@ a > svg {
} }
.btn--primary { .btn--primary {
background: var(--color-button) --black-mix: 70%;
linear-gradient(180deg, rgba(0, 0, 0, 0.15) 0px, transparent); background: linear-gradient(
180deg,
var(--color-button),
color-mix(in lch, var(--color-button), black)
);
color: black; color: black;
font-weight: 600; font-weight: 600;
border: 1px solid
color-mix(in lch, var(--color-button), black var(--black-mix));
box-shadow: inset 0 1px 0px 0 rgba(255, 255, 255, 0.15);
} }
.btn--big { .btn--big {
padding: 15px 30px; padding: 15px 30px;
border-radius: 3px; border-radius: 0.5rem;
} }
.btn-icon { .btn-icon {
@@ -410,11 +448,25 @@ a > svg {
align-items: center; align-items: center;
} }
.btn:hover { .btn--small {
text-decoration: none; padding: 0.2rem 0.5rem;
box-shadow: 0 5px 5px 0 rgba(0, 0, 0, 0.25); text-transform: uppercase;
/* border-radius: 3px; */
font-size: 0.8rem;
} }
.btn:hover {
--black-mix: 90%;
text-decoration: none;
box-shadow: rgba(0, 0, 0, 0.1) 0px 20px 25px -5px,
rgba(0, 0, 0, 0.04) 0px 10px 10px -5px;
}
.btn:disabled {
opacity: 0.5;
pointer-events: none;
cursor: not-allowed;
}
*:focus { *:focus {
outline-width: 3px; outline-width: 3px;
outline-color: var(--color-button); outline-color: var(--color-button);
@@ -431,9 +483,34 @@ a > svg {
} }
.btn--big > svg { .btn--big > svg {
width: 25px; width: 2rem;
height: 25px; height: 2rem;
margin-right: 12px; margin-right: 1rem;
}
@property --angle {
syntax: '<angle>';
initial-value: 0deg;
inherits: false;
}
.btn--pro {
background: linear-gradient(
var(--angle),
var(--color-pro-1),
var(--color-pro-2)
);
color: #222;
border: 0;
font-weight: 700;
animation: gradient_move 3s linear infinite;
}
@keyframes gradient_move {
from {
--angle: 0deg;
}
to {
--angle: 360deg;
}
} }
.btn-loader { .btn-loader {
@@ -472,15 +549,13 @@ a > svg {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
overflow: hidden; overflow: hidden;
transition: 0.1s ease; transition: 0.25s ease;
will-change: filter, transform; will-change: filter, transform;
} }
body:not(.light-version).overlay-visible .main-container { body:not(.light-version).overlay-visible .main-container {
transition-duration: 0.5s; transition-duration: 0.5s;
transform: scale(0.98); transform: scale(0.98);
/* transition-delay: 0.4s; */
filter: blur(2px);
} }
.content-wrap { .content-wrap {
@@ -560,7 +635,7 @@ body:not(.light-version).overlay-visible .main-container {
} }
.is-detached-mode.layout-2 .code-side { .is-detached-mode.layout-2 .code-side {
height: auto !important; height: 100% !important;
} }
.code-wrap { .code-wrap {
@@ -571,9 +646,7 @@ body:not(.light-version).overlay-visible .main-container {
overflow: hidden; overflow: hidden;
position: relative; position: relative;
background: var(--color-bg); background: var(--color-bg);
transition: transition: height 0.3s ease, width 0.3s ease;
height 0.3s ease,
width 0.3s ease;
will-change: height; will-change: height;
} }
@@ -762,7 +835,7 @@ body > #demo-frame {
display: flex; display: flex;
justify-content: space-between; justify-content: space-between;
/* Because .console is 6 */ /* Because .console is 6 */
z-index: 6; z-index: var(--zindex-footer);
} }
.main-header { .main-header {
@@ -774,6 +847,8 @@ body > #demo-frame {
.btn--dark, .btn--dark,
.main-header__btn-wrap > button { .main-header__btn-wrap > button {
--clr-1: hsl(0, 0%, 25%);
--clr-2: hsl(0, 0%, 13%);
box-sizing: content-box; box-sizing: content-box;
/* text-transform: uppercase; */ /* text-transform: uppercase; */
/* font-size: 0.875rem; */ /* font-size: 0.875rem; */
@@ -787,7 +862,8 @@ body > #demo-frame {
margin-left: 10px; margin-left: 10px;
padding: 3px 8px; padding: 3px 8px;
border: 1px solid rgba(0, 0, 0, 0.9); border: 1px solid rgba(0, 0, 0, 0.9);
background: linear-gradient(180deg, hsl(0, 0%, 25%) 0, hsl(0, 0%, 13%) 100%); border-radius: 5px;
background: linear-gradient(180deg, var(--clr-1) 0, var(--clr-2) 100%);
box-shadow: inset 0 1px 0px 0 rgba(255, 255, 255, 0.15); box-shadow: inset 0 1px 0px 0 rgba(255, 255, 255, 0.15);
} }
@@ -805,13 +881,20 @@ body > #demo-frame {
} }
.btn--dark:hover { .btn--dark:hover {
background: #9297b3; /* --clr-1: #6844ad; */
--clr-1: hsl(53.35deg 100% 50%);
--clr-2: hsl(38.96deg 100% 50%);
color: #111; color: #111;
/* border-color: rgba(146, 151, 179, 0.5); */ /* border-color: rgba(146, 151, 179, 0.5); */
} }
.btn--dark:hover > svg { .btn--dark:hover > svg {
fill: #111; fill: #111;
} }
.btn--dark.btn--active {
background: linear-gradient(0deg, hsl(0, 0%, 25%) 0, hsl(0, 0%, 13%) 100%);
box-shadow: inset 0 -1px 0px 0 rgba(255, 255, 255, 0.15);
}
.btn--chromeless { .btn--chromeless {
box-shadow: none; box-shadow: none;
background: transparent; background: transparent;
@@ -938,6 +1021,29 @@ body > #demo-frame {
/* border-radius: 4px; */ /* border-radius: 4px; */
} }
.dialog__close-btn {
display: flex;
position: absolute;
border: none;
background: var(--color-close-btn);
background: linear-gradient(
to bottom,
var(--color-close-btn),
color-mix(in lch, var(--color-close-btn), black)
);
color: white;
border-radius: 0.3rem;
padding: 0.4rem 0.5rem;
}
.dialog__close-btn > svg {
width: 1.2rem;
aspect-ratio: 1;
}
.dialog__close-btn:hover {
--color-close-btn: var(--color-btn-hover-1);
color: #111;
}
.modal { .modal {
position: fixed; position: fixed;
top: 0; top: 0;
@@ -949,13 +1055,23 @@ body > #demo-frame {
display: flex; display: flex;
align-items: baseline; align-items: baseline;
justify-content: center; justify-content: center;
z-index: 2000; z-index: var(--zindex-modal);
visibility: hidden; visibility: hidden;
/* background-color: rgba(102, 51, 153, 0.7); */
/* background-color: rgba(0, 0, 0, 0.7); */
/* To prevent scroll repaint inside modal */ /* To prevent scroll repaint inside modal */
z-index: var(--zindex-modal-overlay);
/* opacity: 0; */
will-change: opacity;
background: var(--color-overlay);
backdrop-filter: blur(5px) grayscale(1);
transition: opacity var(--duration-modal-overlay-show);
will-change: transform; will-change: transform;
} }
.modal--no-overlay {
background: none;
backdrop-filter: none;
}
@keyframes anim-modal-overlay { @keyframes anim-modal-overlay {
to { to {
@@ -964,32 +1080,26 @@ body > #demo-frame {
} }
} }
.modal__close-btn {
position: absolute;
right: 1rem;
top: 1rem;
text-transform: uppercase;
font-weight: 700;
font-size: 0.8rem;
opacity: 0.8;
transition: 0.25s ease;
border: 1px solid black;
border-radius: 2px;
padding: 0.2rem 0.5rem;
}
.modal__close-btn > svg {
fill: black;
width: 30px;
height: 30px;
}
.modal__close-btn:hover { .modal__close-btn:hover {
opacity: 0.7; --color-close-btn: var(--color-btn-hover-1);
color: #111;
}
.modal__close-btn:hover > svg {
fill: #111;
} }
.modal__close-btn {
right: 0rem;
bottom: calc(100% + 0.2rem);
transition: 0.25s ease;
}
.modal__content { .modal__content {
background: var(--color-popup); background: var(--color-popup);
/* fix me */
background: linear-gradient(45deg, #2d063cad, #3a2b63);
box-shadow: inset 1px -1px 0 0 #ffffff17, 0 20px 31px 0 #0000008a;
color: var(--color-text); color: var(--color-text);
position: relative; position: relative;
border-radius: 5px; border-radius: 5px;
@@ -998,9 +1108,9 @@ body > #demo-frame {
font-size: 1.1em; font-size: 1.1em;
line-height: 1.4; line-height: 1.4;
max-width: 85vw; max-width: 85vw;
margin: 2rem auto; margin: 4rem auto 2rem;
box-sizing: border-box; box-sizing: border-box;
overflow-y: auto; /* overflow-y: auto; */
pointer-events: auto; pointer-events: auto;
transform: scale(0.98); transform: scale(0.98);
animation: anim-modal var(--duration-modal-show) cubic-bezier(0.4, 0, 0.2, 1) animation: anim-modal var(--duration-modal-show) cubic-bezier(0.4, 0, 0.2, 1)
@@ -1029,6 +1139,7 @@ body > #demo-frame {
/* transition-duration: 0.3s; */ /* transition-duration: 0.3s; */
/* transform: translateY(0px) scale(1); */ /* transform: translateY(0px) scale(1); */
/* opacity: 1; */ /* opacity: 1; */
backdrop-filter: blur(3px);
} }
.modal-overlay { .modal-overlay {
@@ -1038,10 +1149,11 @@ body > #demo-frame {
visibility: hidden; visibility: hidden;
top: 0; top: 0;
left: 0; left: 0;
z-index: 5; z-index: var(--zindex-modal-overlay);
opacity: 0; opacity: 0;
will-change: opacity; will-change: opacity;
background: rgba(0, 0, 0, 0.5); /* background: var(--color-overlay); */
backdrop-filter: blur(5px) grayscale(1);
transition: opacity var(--duration-modal-overlay-show); transition: opacity var(--duration-modal-overlay-show);
} }
@@ -1071,11 +1183,13 @@ body > #demo-frame {
right: 0; right: 0;
top: 0; top: 0;
bottom: 0; bottom: 0;
width: 450px; width: 35vw;
min-width: 40ch;
max-width: 60ch;
padding: 20px 30px; padding: 20px 30px;
z-index: 6; z-index: 6;
visibility: hidden; /* prevents tabbing */ visibility: hidden; /* prevents tabbing */
background-color: var(--color-popup); background: var(--color-popup);
transition: 0.3s cubic-bezier(1, 0.13, 0.21, 0.87); transition: 0.3s cubic-bezier(1, 0.13, 0.21, 0.87);
transition-property: transform; transition-property: transform;
will-change: transform; will-change: transform;
@@ -1096,23 +1210,16 @@ body > #demo-frame {
} }
.saved-items-pane__close-btn { .saved-items-pane__close-btn {
position: absolute;
left: -18px; left: -18px;
top: 24px; top: 24px;
opacity: 0; opacity: 0;
visibility: hidden; visibility: hidden;
border-radius: 50%;
padding: 10px 14px;
background: crimson;
color: white;
border: 0;
transform: scale(0); transform: scale(0);
will-change: transform, opacity; will-change: transform, opacity;
transition: 0.3s ease; transition: 0.3s ease;
transition-property: transform, opacity; transition-property: transform, opacity;
transition-delay: 0; transition-delay: 0;
} }
.saved-items-pane.is-open .saved-items-pane__close-btn { .saved-items-pane.is-open .saved-items-pane__close-btn {
opacity: 1; opacity: 1;
transition-delay: 0.4s; transition-delay: 0.4s;
@@ -1124,8 +1231,6 @@ body > #demo-frame {
padding: 20px; padding: 20px;
background-color: rgba(255, 255, 255, 0.06); background-color: rgba(255, 255, 255, 0.06);
position: relative; position: relative;
/*border: 1px solid rgba(255,255,255,0.1);*/
margin: 20px 0;
display: block; display: block;
border-radius: 4px; border-radius: 4px;
cursor: pointer; cursor: pointer;
@@ -1135,6 +1240,10 @@ body > #demo-frame {
box-shadow: 0 2px 4px 0 rgba(0, 0, 0, 0.2); box-shadow: 0 2px 4px 0 rgba(0, 0, 0, 0.2);
/* animation: slide-left 0.35s ease forwards; */ /* animation: slide-left 0.35s ease forwards; */
} }
.saved-item-tile + .saved-item-tile {
margin-top: 1rem;
}
.saved-item-tile--inline { .saved-item-tile--inline {
display: inline-block; display: inline-block;
margin-left: 10px; margin-left: 10px;
@@ -1212,6 +1321,8 @@ body > #demo-frame {
.saved-item-tile__btns { .saved-item-tile__btns {
position: absolute; position: absolute;
top: 6px; top: 6px;
display: flex;
align-items: center;
z-index: 1; z-index: 1;
right: 8px; right: 8px;
opacity: 0; opacity: 0;
@@ -1230,14 +1341,21 @@ body > #demo-frame {
} }
.saved-item-tile__btn { .saved-item-tile__btn {
display: inline-flex;
padding: 7px 10px; padding: 7px 10px;
color: white; color: white;
border: 0; border: 0;
border-radius: 20px; border-radius: 1in;
font-size: 0.8rem;
font-weight: 700;
margin-left: 2px; margin-left: 2px;
background: rgba(255, 255, 255, 0.1); background: rgba(255, 255, 255, 0.1);
text-transform: uppercase; text-transform: uppercase;
} }
.saved-item-tile__btn > svg {
width: 1rem;
aspect-ratio: 1;
}
.saved-item-tile__btn:hover { .saved-item-tile__btn:hover {
background: rgba(255, 255, 255, 0.8); background: rgba(255, 255, 255, 0.8);
@@ -1260,7 +1378,9 @@ body > #demo-frame {
.saved-items-pane__container { .saved-items-pane__container {
overflow-y: auto; overflow-y: auto;
overflow-x: hidden; overflow-x: hidden;
max-height: calc(100vh - 90px); max-height: calc(100vh - 130px);
margin-top: 1rem;
padding-inline: 1rem;
scroll-behavior: smooth; scroll-behavior: smooth;
} }
@@ -1484,13 +1604,16 @@ body > #demo-frame {
} }
.onboard-step { .onboard-step {
margin: 15px; margin: 0.5rem;
padding: 20px 30px; padding: 1rem;
background-color: rgba(255, 255, 255, 0.1); background-color: rgba(255, 255, 255, 0.1);
border-radius: 10px; border-radius: 0.8rem;
box-shadow: 0 16px 22px rgba(0, 0, 0, 0.1); box-shadow: 0 16px 22px rgba(0, 0, 0, 0.1);
flex: 1; flex: 1;
} }
.onboard-step--full-width {
grid-column: 1 / -1;
}
.onboard-step__icon { .onboard-step__icon {
fill: rgba(255, 255, 255, 0.3); fill: rgba(255, 255, 255, 0.3);
@@ -1644,38 +1767,22 @@ body > #demo-frame {
display: inline-block; display: inline-block;
} }
.beta-tag {
border-radius: 4px;
text-transform: uppercase;
background: #c68955;
color: white;
letter-spacing: 0.6px;
padding: 2px 5px;
font-size: 0.9em;
}
.is-extension .web-maker-with-tag:after { .is-extension .web-maker-with-tag:after {
display: none; display: none;
} }
.social-login-btn--github { .social-login-btn {
color: white;
background: #656b6f;
border-color: #656b6f;
}
.social-login-btn--facebook {
color: white;
background: #4e62c0;
border-color: #4e62c0;
}
.social-login-btn--google {
background: white; background: white;
border: 2px solid currentColor; border: 2px solid currentColor;
color: black; color: black;
} }
.social-login-btn--github {
}
.social-login-btn--google {
}
body.is-logged-in .hide-on-login, body.is-logged-in .hide-on-login,
body:not(.is-logged-in) .hide-on-logout, body:not(.is-logged-in) .hide-on-logout,
body:not(.is-extension) .show-when-extension, body:not(.is-extension) .show-when-extension,
@@ -2008,6 +2115,300 @@ while the theme CSS file is loading */
justify-content: center; justify-content: center;
} }
.asset-manager__upload-box {
padding: 1rem 2rem;
border: 3px dashed rgb(255 255 255 / 10%);
border-radius: 1rem;
text-align: center;
margin-bottom: 1rem;
transition: 0.3s ease;
position: relative;
}
@keyframes move {
0% {
background-position: 0 0;
}
100% {
background-position: 50px 50px;
}
}
.asset-manager__progress-bar {
position: absolute;
inset: 1rem;
/* height: 2rem; */
background-image: linear-gradient(
-45deg,
rgba(255, 255, 255, 0.2) 25%,
transparent 25%,
transparent 50%,
rgba(255, 255, 255, 0.2) 50%,
rgba(255, 255, 255, 0.2) 75%,
transparent 75%,
transparent
);
z-index: 1;
background-size: 50px 50px;
animation: move 2s linear infinite;
border-top-right-radius: 8px;
border-bottom-right-radius: 8px;
border-top-left-radius: 20px;
border-bottom-left-radius: 20px;
overflow: hidden;
}
.asset-manager__file {
/* list-style: none; */
/* padding: 0; */
/* margin: 0; */
display: flex;
background: none;
gap: 0.5rem;
padding: 0.2rem;
border-radius: 0.4rem;
color: inherit;
border: 0;
transition: 0.3s ease;
/* align-items: center; */
}
.asset-manager__file:hover {
background: rgb(255 255 255 / 5%);
}
.asset-manager__file--grid {
display: flex;
flex-direction: column;
/* align-items: flex-start; */
}
.asset-manager__file-container {
padding: 0;
gap: 0.3rem;
}
.asset-manager__file-container--grid {
display: grid;
grid-template-columns: repeat(7, minmax(90px, 1fr));
max-width: 50rem;
}
.asset-manager__file {
display: flex;
gap: 0.5rem;
position: relative;
}
.asset-manager__file-name {
max-width: 100%;
white-space: nowrap;
text-overflow: ellipsis;
overflow: hidden;
}
.asset-manager__file-image {
height: 1.5rem;
aspect-ratio: 1;
border-radius: 0.3rem;
object-fit: contain;
}
.asset-manager__file--grid .asset-manager__file-image {
width: 100%;
height: auto;
}
.asset-manager__file-ext {
text-transform: uppercase;
position: absolute;
bottom: 0.5rem;
left: 1.4rem;
letter-spacing: -1px;
font-weight: 800;
display: none;
}
.asset-manager__file--grid .asset-manager__file-ext {
display: block;
}
.asset-manager__file-actions {
position: absolute;
bottom: 0;
right: 0;
opacity: 0;
padding: 0.5rem 0;
visibility: hidden;
transform: translateY(-0.5rem);
transition: 0.3s ease;
background-color: rgb(255 255 255 / 15%);
backdrop-filter: blur(5px);
}
.asset-manager__file--grid .asset-manager__file-actions {
left: 0;
bottom: 1.5em;
}
.asset-manager__file:hover .asset-manager__file-actions {
transform: translateY(0rem);
opacity: 1;
visibility: visible;
}
.stack {
display: flex;
align-items: center;
}
.stack > * {
margin: 0;
}
.badge {
text-transform: uppercase;
display: inline-block;
padding: 0.1rem 0.3rem;
background: rgb(255 255 255 / 40%);
border-radius: 1rem;
font-size: 0.75rem;
color: #222;
font-weight: 800;
line-height: 1;
}
.beta-tag {
background: #ee9d59;
}
.pro-badge {
display: inline-block;
background: linear-gradient(45deg, var(--color-pro-1), var(--color-pro-2));
color: #222;
box-shadow: inset 2px 2px 3px rgba(0, 0, 0, 0.3);
}
.templates-container {
display: flex;
flex-wrap: wrap;
gap: 1rem;
}
.templates-container .saved-item-tile {
margin: 0;
}
.pro-modal {
background: url('/assets/pro-panda-flying.png') no-repeat;
background-position: bottom right;
}
.plan-card {
background: rgb(255 255 255 / 10%);
border-radius: 0.4rem;
padding: 1rem;
--shadow-color: 253deg 47% 15%;
--shadow-elevation-low: 0.3px 0.5px 0.7px hsl(var(--shadow-color) / 0.34),
0.4px 0.8px 1px -1.2px hsl(var(--shadow-color) / 0.34),
1px 2px 2.5px -2.5px hsl(var(--shadow-color) / 0.34);
--shadow-elevation-medium: 0.3px 0.5px 0.7px hsl(var(--shadow-color) / 0.36),
0.8px 1.6px 2px -0.8px hsl(var(--shadow-color) / 0.36),
2.1px 4.1px 5.2px -1.7px hsl(var(--shadow-color) / 0.36),
5px 10px 12.6px -2.5px hsl(var(--shadow-color) / 0.36);
box-shadow: var(--shadow-elevation-low);
}
.profile-modal__avatar-img.is-pro {
background: linear-gradient(45deg, var(--color-pro-1), var(--color-pro-2));
padding: 0.2rem;
animation: avatar-rotate 2s forwards;
}
@keyframes avatar-rotate {
0% {
transform: rotate(10deg);
}
100% {
transform: rotate(0deg);
}
}
.profile-modal__name.is-pro {
background: linear-gradient(45deg, var(--color-pro-1), var(--color-pro-2));
color: transparent;
background-clip: text;
}
.profile-modal__panda {
animation: slide-up 0.3s forwards;
}
@keyframes slide-up {
0% {
opacity: 0;
transform: translateX(-1.5rem);
}
100% {
opacity: 1;
transform: rotate(0deg);
}
}
/* .PANEL */
.panel {
--panel-bg: rgb(255 255 255 / 5%);
position: relative;
background: var(--panel-bg);
box-shadow: var(--panel-shadow);
border-radius: 1rem;
/* backdrop-filter: blur(20px); */
overflow: hidden;
}
.panelOnlyBorder {
background: none;
box-shadow: none;
backdrop-filter: none;
border: 2px solid var(--clr-border-1);
}
.panelGlowing {
box-shadow: var(--glow-shadow);
}
.panelTopFocus {
background: radial-gradient(
82.25% 100% at 50% 0%,
rgba(var(--rgb-gray-1), 0.75) 37.28%,
rgba(var(--rgb-gray-0), 0) 100%
);
box-shadow: 0 0 30px rgba(var(--rgb-brand), 0),
0px 20px 50px rgba(0, 0, 0, 0.1), inset 0px 1px 3px rgba(255, 255, 255, 0.1);
}
.skeleton {
display: inline-block;
height: 0.5em;
background-color: currentColor;
border-radius: 999px;
animation: 2s linear infinite skeleton;
transform-origin: left;
}
@keyframes skeleton {
20% {
transform-origin: left;
opacity: 1;
transform: scaleX(1.5);
opacity: 0.5;
}
20.1% {
transform-origin: right;
transform: scaleX(1.5) translateX(33%);
}
50% {
transform-origin: right;
transform: scaleX(1) translateX(50%);
opacity: 1;
}
70% {
transform-origin: right;
transform: scaleX(1.5) translateX(33%);
}
70.1% {
transform-origin: left;
transform: scaleX(1.5);
}
90% {
transform-origin: left;
transform: scaleX(1);
opacity: 0.5;
}
/* 100% {
transform-origin: right;
transform: scaleX(1.5) translateX(33%);
opacity: 1;
} */
}
@media screen and (max-width: 600px) { @media screen and (max-width: 600px) {
body { body {
font-size: 70%; font-size: 70%;

View File

@@ -15,10 +15,25 @@ 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',
title: 'Tailwind CSS 3',
img: 'assets/tailwind-logo.svg'
},
{ {
id: 'kontra-game-engine', id: 'kontra-game-engine',
title: 'Kontra Game Engine', title: 'Kontra Game Engine',
img: 'assets/html5-logo.svg', img: 'assets/html5-logo.svg',
isFileModeSupported: true isFileModeSupported: true
},
{
id: 'kaboom',
title: 'Kaboom',
img: 'assets/kaboom-logo.png'
} }
]; ];

View File

@@ -0,0 +1,18 @@
{
"title": "Kaboom game engine template",
"content": "",
"html": "",
"css": "",
"js": "// Responding to gravity & jumping\n\n// Start kaboom\nkaboom()\n\n// Load assets\nloadSprite(\"bean\", \"https://kaboomjs.com/sprites/bean.png\")\n\n// Set the gravity acceleration (pixels per second)\nsetGravity(1600)\n\n// Add player game object\nconst player = add([\n\tsprite(\"bean\"),\n\tpos(center()),\n\tarea(),\n\t// body() component gives the ability to respond to gravity\n\tbody(),\n])\n\nonKeyPress(\"space\", () => {\n\t// .isGrounded() is provided by body()\n\tif (player.isGrounded()) {\n\t\t// .jump() is provided by body()\n\t\tplayer.jump()\n\t}\n})\n\n// .onGround() is provided by body(). It registers an event that runs whenever player hits the ground.\nplayer.onGround(() => {\n\tdebug.log(\"ouch\")\n})\n\n// Accelerate falling when player holding down arrow key\nonKeyDown(\"down\", () => {\n\tif (!player.isGrounded()) {\n\t\tplayer.vel.y += dt() * 1200\n\t}\n})\n\n// Jump higher if space is held\nonKeyDown(\"space\", () => {\n\tif (!player.isGrounded() && player.vel.y < 0) {\n\t\tplayer.vel.y -= dt() * 600\n\t}\n})\n\n// Add a platform to hold the player\nadd([\n\trect(width(), 48),\n\toutline(4),\n\tarea(),\n\tpos(0, height() - 48),\n\t// Give objects a body() component if you don't want other solid objects pass through\n\tbody({ isStatic: true }),\n])\n\nadd([\n\ttext(\"Press space key\", { width: width() / 2 }),\n\tpos(12, 12),\n])\n\n// Check out https://kaboomjs.com#BodyComp for everything body() provides\n",
"externalLibs": {
"js": "\nhttps://unpkg.com/kaboom@3000/dist/kaboom.js",
"css": ""
},
"layoutMode": 1,
"htmlMode": "html",
"cssMode": "css",
"jsMode": "js",
"mainSizes": [50, 50],
"sizes": ["33px", "33px", 100]
}

View File

@@ -1,16 +1,16 @@
{ {
"title": "Preact template", "title": "Preact template",
"externalLibs": { "sizes": [30, "30px", 70],
"js": "\nhttps://cdnjs.cloudflare.com/ajax/libs/preact/8.2.9/preact.min.js", "mainSizes": [60, 40],
"css": ""
},
"sizes": ["calc(30% - 3px)", "30px", "calc(70% - 3px)"],
"mainSizes": [68.1051, 31.6949],
"htmlMode": "html", "htmlMode": "html",
"cssMode": "css", "cssMode": "css",
"jsMode": "es6", "jsMode": "es6",
"layoutMode": 1, "layoutMode": 1,
"js": "externalLibs": {
"\nconst { h, Component, render, createElement } = window.preact;\nconst React = {createElement}\n\nclass App extends Component {\n constructor() {\n super();\n this.message = 'World';\n }\n render() {\n return (<div>Hello {this.message}</div>)\n }\n}\nrender(<App/>, window.root)\n\n", "js": "https://unpkg.com/preact@10.21.0/dist/preact.min.umd.js\nhttps://unpkg.com/preact@10.21.0/hooks/dist/hooks.umd.js",
"css": ""
},
"js": "\nconst { h, render, createElement } = window.preact;\nconst { useState} = window.preactHooks;\nconst React = {createElement};\n\n\nfunction App() {\n const [count, setCount] = useState(0)\n return <div>\n <h1>Hello, I'm Preact!</h1>\n <p>{ count }</p>\n <button onClick={() => setCount(count+1)}>Click me!</button>\n </div>\n}\n\nrender(<App/>, document.body)\n\n\n",
"html": "<div id=\"root\"></div>\n" "html": "<div id=\"root\"></div>\n"
} }

View File

@@ -0,0 +1,17 @@
{
"title": "Tailwind CSS 3 template",
"html": "<div class=\"m-6 p-3 shadow-lg text-xl bg-purple-100 text-purple-800 rounded-md\">\n Hello world\n</div>",
"css": "",
"js": "",
"externalLibs": {
"js": "https://cdn.tailwindcss.com/3.4.3",
"css": ""
},
"layoutMode": 1,
"htmlMode": "html",
"cssMode": "css",
"jsMode": "js",
"updatedOn": 1713157902575,
"mainSizes": [50, 50],
"sizes": [51, 45, 4]
}

View File

@@ -0,0 +1,17 @@
{
"title": "Tailwind CSS 3 template",
"html": "<div class=\"m-6 p-3 shadow-lg text-xl bg-purple-100 text-purple-800 rounded-md\">\n Hello world\n</div>",
"css": "",
"js": "",
"externalLibs": {
"js": "",
"css": "https://cdn.jsdelivr.net/npm/tailwindcss/dist/tailwind.min.css"
},
"layoutMode": 1,
"htmlMode": "html",
"cssMode": "css",
"jsMode": "js",
"updatedOn": 1713157902575,
"mainSizes": [50, 50],
"sizes": [51, 45, 4]
}

View File

@@ -1,9 +1,9 @@
import { trackEvent } from './analytics'; import { trackEvent } from './analytics';
import { computeHtml, computeCss, computeJs } from './computes'; import { computeHtml, computeCss, computeJs } from './computes';
import { modes, HtmlModes, CssModes, JsModes } from './codeModes'; import { modes, HtmlModes, CssModes, JsModes } from './codeModes';
import { deferred } from './deferred'; import { deferred } from './deferred';
import { getExtensionFromFileName } from './fileUtils'; import { getExtensionFromFileName } from './fileUtils';
import confetti from 'canvas-confetti';
const esprima = require('esprima'); const esprima = require('esprima');
window.DEBUG = document.cookie.indexOf('wmdebug') > -1; window.DEBUG = document.cookie.indexOf('wmdebug') > -1;
@@ -23,7 +23,7 @@ export const BASE_PATH =
window.DEBUG || window.DEBUG ||
process.env.NODE_ENV === 'development' process.env.NODE_ENV === 'development'
? '/' ? '/'
: '/app'; : '/create';
/* eslint-enable no-process-env */ /* eslint-enable no-process-env */
var alphaNum = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'; var alphaNum = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789';
@@ -205,6 +205,41 @@ export function getHumanDate(timestamp) {
return retVal; return retVal;
} }
/**
* Convert any date-ish string/obj to human readable form -> Jul 02, 2021
* @param {string?object} date date to be formatted
* @returns string
*/
export function getHumanReadableDate(
date,
{ showTime = true, utc = false } = {}
) {
if (!date) return '';
let d = typeof date.toDate === 'function' ? date.toDate() : new Date(date);
if (utc) {
d = new Date(
Date.UTC(d.getFullYear(), d.getMonth(), d.getDate(), d.getHours())
);
}
let options = {
year: 'numeric',
month: 'short',
day: 'numeric'
};
if (showTime) {
options = {
...options,
hour: '2-digit',
minute: '2-digit',
second: '2-digit',
hour12: true
};
}
const dateTimeString = d.toLocaleString(false, options);
return dateTimeString;
}
// create a one-time event // create a one-time event
export function once(node, type, callback) { export function once(node, type, callback) {
// create event // create event
@@ -585,3 +620,53 @@ if (window.IS_EXTENSION) {
} else { } else {
document.body.classList.add('is-app'); document.body.classList.add('is-app');
} }
export async function copyToClipboard(text) {
try {
await navigator.clipboard.writeText(text);
} catch (err) {
console.error('Failed to copy text: ', err);
}
}
export function showConfetti(time = 4) {
var end = Date.now() + time * 1000;
(function frame() {
confetti({
particleCount: 1,
startVelocity: 0,
ticks: 100,
origin: {
x: Math.random(),
// since they fall down, start a bit higher than random
y: Math.random() - 0.2
},
colors: [
[
'#26ccff',
'#a25afd',
'#ff5e7e',
'#88ff5a',
'#fcff42',
'#ffa62d',
'#ff36ff'
][~~(Math.random() * 7)]
]
});
if (Date.now() < end) {
requestAnimationFrame(frame);
}
})();
}
/**
* Persists the firebase user with a subset of it's keys.
* @param {object} user User object from firebase
*/
export function persistAuthUserLocally(user) {
const keys = ['uid', 'displayName', 'photoURL', 'isPro', 'settings'];
const obj = {};
keys.map(key => (obj[key] = user[key]));
window.localStorage.setItem('user', JSON.stringify(obj));
}