mirror of
https://github.com/chinchang/web-maker.git
synced 2025-07-26 08:11:17 +02:00
Merge remote-tracking branch 'origin/master' into manifestv3
This commit is contained in:
@@ -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.
|
||||
|
||||
## [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
|
||||
|
||||
|
@@ -8,6 +8,6 @@
|
||||
- package.json
|
||||
- manifest.json
|
||||
- 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
|
||||
- If everything is good, push to master
|
||||
|
72
gulpfile.js
72
gulpfile.js
@@ -13,6 +13,7 @@ const merge = require('merge-stream');
|
||||
// const zip = require('gulp-zip');
|
||||
var packageJson = JSON.parse(fs.readFileSync('./package.json'));
|
||||
const connect = require('gulp-connect');
|
||||
const APP_FOLDER = 'create';
|
||||
|
||||
function minifyJs(fileName) {
|
||||
const content = fs.readFileSync(fileName, 'utf8');
|
||||
@@ -44,21 +45,27 @@ gulp.task('copyFiles', function () {
|
||||
return merge(
|
||||
gulp
|
||||
.src('src/lib/codemirror/theme/*')
|
||||
.pipe(gulp.dest('app/lib/codemirror/theme')),
|
||||
.pipe(gulp.dest(`${APP_FOLDER}/lib/codemirror/theme`)),
|
||||
gulp
|
||||
.src('src/lib/codemirror/mode/**/*')
|
||||
.pipe(gulp.dest('app/lib/codemirror/mode')),
|
||||
gulp.src('src/lib/transpilers/*').pipe(gulp.dest('app/lib/transpilers')),
|
||||
gulp.src('src/lib/prettier-worker.js').pipe(gulp.dest('app/lib/')),
|
||||
gulp.src('src/lib/prettier/*').pipe(gulp.dest('app/lib/prettier')),
|
||||
.pipe(gulp.dest(`${APP_FOLDER}/lib/codemirror/mode`)),
|
||||
gulp
|
||||
.src('src/lib/transpilers/*')
|
||||
.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
|
||||
.src(['!src/lib/monaco/monaco.bundle.js', 'src/lib/monaco/**/*'])
|
||||
.pipe(gulp.dest('app/lib/monaco')),
|
||||
gulp.src('src/lib/screenlog.js').pipe(gulp.dest('app/lib')),
|
||||
gulp.src('icons/*').pipe(gulp.dest('app/icons')),
|
||||
gulp.src('src/assets/*').pipe(gulp.dest('app/assets')),
|
||||
gulp.src('src/templates/*').pipe(gulp.dest('app/templates')),
|
||||
gulp.src('preview/*').pipe(gulp.dest('app/preview')),
|
||||
.pipe(gulp.dest(`${APP_FOLDER}/lib/monaco`)),
|
||||
gulp.src('src/lib/screenlog.js').pipe(gulp.dest(`${APP_FOLDER}/lib`)),
|
||||
gulp.src('icons/*').pipe(gulp.dest(`${APP_FOLDER}/icons`)),
|
||||
gulp.src('src/assets/*').pipe(gulp.dest(`${APP_FOLDER}/assets`)),
|
||||
gulp.src('src/templates/*').pipe(gulp.dest(`${APP_FOLDER}/templates`)),
|
||||
gulp.src('preview/*').pipe(gulp.dest(`${APP_FOLDER}/preview`)),
|
||||
gulp
|
||||
.src([
|
||||
'src/preview.html',
|
||||
@@ -68,24 +75,24 @@ gulp.task('copyFiles', function () {
|
||||
'src/icon-128.png',
|
||||
'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
|
||||
// useRef plugin to concat into one.
|
||||
gulp
|
||||
.src('src/lib/codemirror/lib/codemirror.css')
|
||||
.pipe(gulp.dest('build/lib/codemirror/lib')),
|
||||
.pipe(gulp.dest(`build/lib/codemirror/lib`)),
|
||||
gulp
|
||||
.src('src/lib/codemirror/addon/hint/show-hint.css')
|
||||
.pipe(gulp.dest('build/lib/codemirror/addon/hint')),
|
||||
.pipe(gulp.dest(`build/lib/codemirror/addon/hint`)),
|
||||
gulp
|
||||
.src('src/lib/codemirror/addon/fold/foldgutter.css')
|
||||
.pipe(gulp.dest('build/lib/codemirror/addon/fold')),
|
||||
.pipe(gulp.dest(`build/lib/codemirror/addon/fold`)),
|
||||
gulp
|
||||
.src('src/lib/codemirror/addon/dialog/dialog.css')
|
||||
.pipe(gulp.dest('build/lib/codemirror/addon/dialog')),
|
||||
.pipe(gulp.dest(`build/lib/codemirror/addon/dialog`)),
|
||||
gulp.src('src/lib/hint.min.css').pipe(gulp.dest('build/lib')),
|
||||
gulp.src('src/lib/inlet.css').pipe(gulp.dest('build/lib')),
|
||||
// gulp.src('src/style.css').pipe(gulp.dest('build')),
|
||||
@@ -97,34 +104,37 @@ gulp.task('copyFiles', function () {
|
||||
'src/Inconsolata.ttf',
|
||||
'src/Monoid.ttf'
|
||||
])
|
||||
.pipe(gulp.dest('app'))
|
||||
.pipe(gulp.dest(APP_FOLDER))
|
||||
);
|
||||
});
|
||||
|
||||
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 () {
|
||||
const bundleFile = fs
|
||||
.readdirSync('app')
|
||||
.readdirSync(APP_FOLDER)
|
||||
.filter(allFilesPaths => allFilesPaths.match(/bundle.*\.js$/) !== null)[0];
|
||||
|
||||
console.log('matched', bundleFile);
|
||||
|
||||
return gulp
|
||||
.src(['src/service-worker-registration.js', `app/${bundleFile}`])
|
||||
.src(['src/service-worker-registration.js', `${APP_FOLDER}/${bundleFile}`])
|
||||
.pipe(concat(bundleFile))
|
||||
.pipe(gulp.dest('app'));
|
||||
.pipe(gulp.dest(APP_FOLDER));
|
||||
});
|
||||
|
||||
gulp.task('minify', function () {
|
||||
// minifyJs('app/script.js');
|
||||
// minifyJs('app/vendor.js');
|
||||
minifyJs('app/lib/screenlog.js');
|
||||
minifyJs(`${APP_FOLDER}/lib/screenlog.js`);
|
||||
|
||||
return gulp
|
||||
.src('app/*.css')
|
||||
.src(`${APP_FOLDER}/*.css`)
|
||||
.pipe(
|
||||
cleanCSS(
|
||||
{
|
||||
@@ -137,7 +147,7 @@ gulp.task('minify', function () {
|
||||
}
|
||||
)
|
||||
)
|
||||
.pipe(gulp.dest('app'));
|
||||
.pipe(gulp.dest(APP_FOLDER));
|
||||
});
|
||||
|
||||
gulp.task('fixIndex', function (cb) {
|
||||
@@ -160,7 +170,7 @@ gulp.task('fixIndex', function (cb) {
|
||||
|
||||
gulp.task('generate-service-worker', function (callback) {
|
||||
var swPrecache = require('sw-precache');
|
||||
var rootDir = 'app';
|
||||
var rootDir = APP_FOLDER;
|
||||
|
||||
swPrecache.write(
|
||||
`${rootDir}/service-worker.js`,
|
||||
@@ -179,7 +189,7 @@ gulp.task('generate-service-worker', function (callback) {
|
||||
|
||||
gulp.task('packageExtension', function () {
|
||||
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/options.js extension');
|
||||
child_process.execSync('cp src/options.html extension');
|
||||
@@ -211,12 +221,12 @@ gulp.task('buildWebsite', function () {
|
||||
gulp.task('buildDistFolder', function (cb) {
|
||||
child_process.execSync('rm -rf dist');
|
||||
child_process.execSync('mv packages/website/_site dist');
|
||||
child_process.execSync('mv app dist/');
|
||||
child_process.execSync(`mv ${APP_FOLDER} dist/`);
|
||||
cb();
|
||||
});
|
||||
|
||||
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 () {
|
||||
@@ -227,6 +237,7 @@ gulp.task('start-preview-server', function () {
|
||||
});
|
||||
});
|
||||
|
||||
// TODO: fix tasks. eg. buildWebsite isn't needed anymore
|
||||
exports.release = series(
|
||||
parallel('runWebpack', 'buildWebsite'),
|
||||
'copyFiles',
|
||||
@@ -274,7 +285,8 @@ const buildExtension = series(
|
||||
'copyFiles',
|
||||
'fixIndex',
|
||||
'useRef',
|
||||
'packageExtension'
|
||||
'packageExtension',
|
||||
'cleanup'
|
||||
);
|
||||
|
||||
function runWatcher(cb) {
|
||||
|
17
netlify.toml
17
netlify.toml
@@ -18,10 +18,19 @@ ID = "webmaker"
|
||||
# been specified, include it in the publish directory path.
|
||||
publish = "dist"
|
||||
|
||||
# The following redirect is intended for use with most SPAs that handle
|
||||
# routing internally.
|
||||
[[redirects]]
|
||||
from = "https://preview.webmaker.app/*"
|
||||
to = "/app/preview/:splat"
|
||||
to = "/create/preview/:splat"
|
||||
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
|
20867
package-lock.json
generated
20867
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
17
package.json
17
package.json
@@ -1,11 +1,11 @@
|
||||
{
|
||||
"name": "web-maker",
|
||||
"version": "5.2.0",
|
||||
"version": "6.1.0",
|
||||
"description": "A blazing fast & offline web playground",
|
||||
"scripts": {
|
||||
"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",
|
||||
"dev": "preact watch --template src/index.html",
|
||||
"build": "preact build --template src/index.ejs --prerender false --no-inline-css --sw false",
|
||||
"dev": "preact watch --template src/index.ejs",
|
||||
"serve-website": "cd packages/website; npm start",
|
||||
"build-website": "cd packages/website; npm run build",
|
||||
"lint": "eslint src",
|
||||
@@ -65,24 +65,25 @@
|
||||
"markdown-it": "^8.4.2",
|
||||
"markdown-it-anchor": "^5.0.2",
|
||||
"merge-stream": "^1.0.1",
|
||||
"preact-cli": "^3.0.0",
|
||||
"preact-cli": "^4.0.0-next.6",
|
||||
"sw-precache": "^5.2.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"@emmetio/codemirror-plugin": "^0.5.4",
|
||||
"@lingui/react": "^2.8.3",
|
||||
"canvas-confetti": "^1.9.2",
|
||||
"code-blast-codemirror": "chinchang/code-blast-codemirror#web-maker",
|
||||
"codemirror": "^5.37.0",
|
||||
"codemirror": "^5.65.16",
|
||||
"copy-webpack-plugin": "^4.5.1",
|
||||
"esprima": "^4.0.0",
|
||||
"firebase": "^10.8.0",
|
||||
"jszip": "^3.1.5",
|
||||
"preact": "^10.5.13",
|
||||
"preact": "^10.17.0",
|
||||
"preact-portal": "^1.1.3",
|
||||
"preact-render-to-string": "^5.1.4",
|
||||
"preact-router": "^3.2.1",
|
||||
"prettier": "^2.2.1",
|
||||
"react-inspector": "^2.3.0",
|
||||
"prettier": "^3.0.2",
|
||||
"react-inspector": "^6.0.2",
|
||||
"split.js": "^1.5.11"
|
||||
},
|
||||
"engines": {
|
||||
|
@@ -7,8 +7,11 @@ excludeFromSitemap: true
|
||||
|
||||
<div class="ta-c">
|
||||
<figure>
|
||||
<img src="/images/404.png" style="width:65vh" />
|
||||
<figcaption>Image by https://icons8.com</figcaption>
|
||||
<img src="/images/404.png" style="width: 65vh" />
|
||||
<figcaption style="opacity: 0.5">Image by https://icons8.com</figcaption>
|
||||
</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>
|
||||
|
@@ -1,4 +1,4 @@
|
||||
<!doctype html>
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
@@ -114,7 +114,7 @@
|
||||
padding: 1rem;
|
||||
max-width: var(--layout-max-width);
|
||||
margin: 0 auto;
|
||||
min-height: 55vh;
|
||||
min-height: calc(100dvh - 4rem);
|
||||
}
|
||||
|
||||
@media screen and (max-width: 700px) {
|
||||
@@ -514,7 +514,7 @@
|
||||
A blazing fast & offline frontend playground in your browser
|
||||
</h2>
|
||||
<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>
|
||||
</a>
|
||||
<p style="margin-top: 3px">
|
||||
|
@@ -8,7 +8,7 @@ Web Maker is available as a Web app and a Chrome extension. Web app is recommend
|
||||
|
||||
**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**
|
||||
|
||||
@@ -20,7 +20,7 @@ Here is the link to the Chrome extension 👉 [Chrome Web Store](https://chrome.
|
||||
|
||||
**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**
|
||||
|
||||
|
@@ -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:
|
||||
|
||||
```
|
||||
https://webmaker.app/app?settings=autoPreview:false
|
||||
https://webmaker.app/create?settings=autoPreview:false
|
||||
```
|
||||
|
||||
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.
|
||||
|
@@ -1,5 +1,3 @@
|
||||
// var CommonsChunkPlugin = require('webpack/lib/optimize/CommonsChunkPlugin');
|
||||
|
||||
/**
|
||||
* Function that mutates original webpack config.
|
||||
* Supports asynchronous changes when promise is returned.
|
||||
@@ -21,9 +19,9 @@ export default function (config, env, helpers) {
|
||||
htmlWebpackPlugin.plugin.options.favicon = false;
|
||||
|
||||
// Required for lingui-macros
|
||||
let { rule } = helpers.getLoadersByName(config, 'babel-loader')[0];
|
||||
let babelConfig = rule.options;
|
||||
babelConfig.plugins.push('macros');
|
||||
// let { rule } = helpers.getLoadersByName(config, 'babel-loader')[0];
|
||||
// let babelConfig = rule.options;
|
||||
// babelConfig.plugins.push('macros');
|
||||
|
||||
if (env.isProd) {
|
||||
config.devtool = false; // disable sourcemaps
|
||||
@@ -33,25 +31,5 @@ export default function (config, env, helpers) {
|
||||
|
||||
// Remove the default hash append in chunk name
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
20
preview/detached-window.js
Normal file
20
preview/detached-window.js
Normal 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
22
preview/preview.html
Normal 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>
|
@@ -3,58 +3,59 @@
|
||||
import CodeMirror from 'codemirror';
|
||||
|
||||
// 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) {
|
||||
var countDown = n
|
||||
var countDown = n;
|
||||
return function () {
|
||||
if (--countDown === 0) cont()
|
||||
}
|
||||
if (--countDown === 0) cont();
|
||||
};
|
||||
}
|
||||
|
||||
function ensureDeps(mode, cont) {
|
||||
var deps = CodeMirror.modes[mode].dependencies
|
||||
if (!deps) return cont()
|
||||
var missing = []
|
||||
var deps = CodeMirror.modes[mode].dependencies;
|
||||
if (!deps) return cont();
|
||||
var missing = [];
|
||||
for (var i = 0; i < deps.length; ++i) {
|
||||
if (!CodeMirror.modes.hasOwnProperty(deps[i])) missing.push(deps[i])
|
||||
if (!CodeMirror.modes.hasOwnProperty(deps[i])) missing.push(deps[i]);
|
||||
}
|
||||
if (!missing.length) return cont()
|
||||
var split = splitCallback(cont, missing.length)
|
||||
for (i = 0; i < missing.length; ++i) CodeMirror.requireMode(missing[i], split)
|
||||
if (!missing.length) return cont();
|
||||
var split = splitCallback(cont, missing.length);
|
||||
for (i = 0; i < missing.length; ++i)
|
||||
CodeMirror.requireMode(missing[i], split);
|
||||
}
|
||||
|
||||
CodeMirror.requireMode = function (mode, cont) {
|
||||
if (typeof mode !== 'string') mode = mode.name
|
||||
if (CodeMirror.modes.hasOwnProperty(mode)) return ensureDeps(mode, cont)
|
||||
if (loading.hasOwnProperty(mode)) return loading[mode].push(cont)
|
||||
if (typeof mode !== 'string') mode = mode.name;
|
||||
if (CodeMirror.modes.hasOwnProperty(mode)) return ensureDeps(mode, cont);
|
||||
if (loading.hasOwnProperty(mode)) return loading[mode].push(cont);
|
||||
|
||||
var file = CodeMirror.modeURL.replace(/%N/g, mode)
|
||||
var file = CodeMirror.modeURL.replace(/%N/g, mode);
|
||||
|
||||
var script = document.createElement('script')
|
||||
script.src = file
|
||||
var others = document.getElementsByTagName('script')[0]
|
||||
var list = loading[mode] = [cont]
|
||||
var script = document.createElement('script');
|
||||
script.src = file;
|
||||
var others = document.getElementsByTagName('script')[0];
|
||||
var list = (loading[mode] = [cont]);
|
||||
|
||||
CodeMirror.on(script, 'load', function () {
|
||||
ensureDeps(mode, function () {
|
||||
for (var i = 0; i < list.length; ++i) list[i]()
|
||||
})
|
||||
})
|
||||
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) {
|
||||
if (CodeMirror.modes.hasOwnProperty(mode)) return
|
||||
if (CodeMirror.modes.hasOwnProperty(mode)) return;
|
||||
|
||||
CodeMirror.requireMode(mode, function () {
|
||||
instance.setOption('mode', instance.getOption('mode'))
|
||||
})
|
||||
}
|
||||
instance.setOption('mode', instance.getOption('mode'));
|
||||
});
|
||||
};
|
||||
|
||||
export default CodeMirror
|
||||
export default CodeMirror;
|
||||
|
@@ -10,6 +10,12 @@ export function trackEvent(category, action, label, value) {
|
||||
}
|
||||
if (window.ga) {
|
||||
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
BIN
src/assets/kaboom-logo.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 4.3 KiB |
BIN
src/assets/pro-panda-flying.png
Normal file
BIN
src/assets/pro-panda-flying.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 216 KiB |
BIN
src/assets/pro-panda.png
Normal file
BIN
src/assets/pro-panda.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 201 KiB |
16
src/assets/tailwind-logo.svg
Normal file
16
src/assets/tailwind-logo.svg
Normal 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
357
src/components/Assets.jsx
Normal 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 };
|
@@ -74,46 +74,94 @@ function Notification({ version, isLatest, ...props }) {
|
||||
</a>
|
||||
</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
|
||||
Web Maker helpful,{' '}
|
||||
<a
|
||||
href="https://chrome.google.com/webstore/detail/web-maker/lkfkkhfhhdkiemehlpkgjeojomhpccnh/reviews"
|
||||
href="https://chromewebstore.google.com/detail/web-maker/lkfkkhfhhdkiemehlpkgjeojomhpccnh/reviews"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
class="btn"
|
||||
>
|
||||
Please rate Web Maker <span class="star" />
|
||||
Please rate Web Maker
|
||||
<span class="star" />
|
||||
</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"
|
||||
rel="noopener noreferrer"
|
||||
class="btn"
|
||||
>
|
||||
Share it
|
||||
</a>
|
||||
|
||||
<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>
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
export function Notifications(props) {
|
||||
export function Changelog(props) {
|
||||
return (
|
||||
<div>
|
||||
<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}>
|
||||
<li>
|
||||
<strong>Improvement</strong>: Atomic CSS (Atomizer) has been updated
|
||||
@@ -157,7 +205,7 @@ export function Notifications(props) {
|
||||
Add library feature is fixed.
|
||||
</NotificationItem>
|
||||
</Notification>
|
||||
<Notification version="5.0.0" {...props} isLatest={true}>
|
||||
<Notification version="5.0.0" {...props}>
|
||||
<li>
|
||||
<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
|
||||
@@ -192,7 +240,7 @@ export function Notifications(props) {
|
||||
<strong>autoPreview</strong> and <strong>isMonacoEditorOn</strong> are
|
||||
supported. Example:{' '}
|
||||
<code>
|
||||
https://webmaker.app/app?settings=autoPreview:false,isMonacoEditorOn:true
|
||||
https://webmaker.app/create?settings=autoPreview:false,isMonacoEditorOn:true
|
||||
</code>
|
||||
</li>
|
||||
<NotificationItem type="bug">
|
||||
@@ -585,11 +633,11 @@ export function Notifications(props) {
|
||||
as web app that runs offline just like the extension! Checkout it
|
||||
out ->
|
||||
<a
|
||||
href="https://webmaker.app/app/"
|
||||
href="https://webmaker.app/create/"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
https://webmaker.app/app/
|
||||
https://webmaker.app/create/
|
||||
</a>
|
||||
.
|
||||
</li>
|
@@ -237,7 +237,7 @@ export default class CodeEditor extends Component {
|
||||
monacoDepsDeferred = deferred();
|
||||
loadCss({ url: 'lib/monaco/monaco.css', id: 'monaco-css' });
|
||||
import(
|
||||
/* webpackChunkName: "monaco" */ '../lib/monaco/monaco.bundle.js'
|
||||
/* webpackChunkName: "monaco" */ '/lib/monaco/monaco.bundle.js'
|
||||
).then(() => {
|
||||
monacoDepsDeferred.resolve();
|
||||
});
|
||||
|
@@ -12,15 +12,12 @@ class LogRow extends Component {
|
||||
const theme = {
|
||||
...chromeDark,
|
||||
...{
|
||||
OBJECT_VALUE_STRING_COLOR: 'green',
|
||||
BASE_FONT_SIZE: '20px',
|
||||
TREENODE_FONT_SIZE: '20px'
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<Inspector theme={theme} theme="chromeDark" data={this.props.data} />
|
||||
);
|
||||
return <Inspector theme={theme} data={this.props.data} />;
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -21,6 +21,12 @@ const minCodeWrapSize = 33;
|
||||
/* global htmlCodeEl
|
||||
*/
|
||||
|
||||
const PREVIEW_FRAME_HOST = window.DEBUG
|
||||
? 'http://localhost:7888'
|
||||
: `https://wbmakr.com`;
|
||||
|
||||
let cachedSandboxAttribute = '';
|
||||
|
||||
export default class ContentWrap extends Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
@@ -152,29 +158,56 @@ export default class ContentWrap extends Component {
|
||||
}
|
||||
|
||||
if (shouldInlineJs) {
|
||||
const writeInsideIframe = () => {
|
||||
if (this.detachedWindow) {
|
||||
log('✉️ Sending message to detached window');
|
||||
this.detachedWindow.postMessage({ contents }, '*');
|
||||
} else if (window.IS_EXTENSION) {
|
||||
if (this.detachedWindow) {
|
||||
log('✉️ Sending message to detached window');
|
||||
this.detachedWindow.postMessage({ contents }, '*');
|
||||
} else {
|
||||
// 1. we refresh the frame so that all JS is cleared in the frame. this will
|
||||
// break the iframe since sandboxed frame isn't served by SW (needed for offline support)
|
||||
// 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([
|
||||
// Just in case onload promise doesn't resolves
|
||||
new Promise(resolve => {
|
||||
setTimeout(resolve, 400);
|
||||
}),
|
||||
new Promise(resolve => {
|
||||
this.frame.onload = resolve;
|
||||
})
|
||||
]).then(fn);
|
||||
// Setting to blank string cause frame to reload
|
||||
// if (window.IS_EXTENSION) {
|
||||
// this.frame.src = '';
|
||||
// } else {
|
||||
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 }, '*');
|
||||
} else {
|
||||
this.frame.contentDocument.open();
|
||||
this.frame.contentDocument.write(contents);
|
||||
this.frame.contentDocument.close();
|
||||
}
|
||||
};
|
||||
Promise.race([
|
||||
// Just in case onload promise doesn't resolves
|
||||
new Promise(resolve => {
|
||||
setTimeout(resolve, 200);
|
||||
}),
|
||||
new Promise(resolve => {
|
||||
this.frame.onload = resolve;
|
||||
})
|
||||
]).then(writeInsideIframe);
|
||||
// Setting to blank string cause frame to reload
|
||||
this.frame.src = this.frame.src;
|
||||
// }
|
||||
};
|
||||
// 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 {
|
||||
// DEPRECATED
|
||||
// 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 &&
|
||||
!isForced &&
|
||||
currentCode.html === this.codeInPreview.html &&
|
||||
currentCode.js === this.codeInPreview.js
|
||||
currentCode.js === this.codeInPreview.js &&
|
||||
false
|
||||
) {
|
||||
computeCss(
|
||||
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
|
||||
if (prefs.editorTheme) {
|
||||
window.editorThemeLinkTag.href = `lib/codemirror/theme/${prefs.editorTheme}.css`;
|
||||
window.editorThemeLinkTag.href = `./lib/codemirror/theme/${prefs.editorTheme}.css`;
|
||||
}
|
||||
|
||||
window.fontStyleTag.textContent =
|
||||
@@ -517,11 +551,20 @@ export default class ContentWrap extends Component {
|
||||
const iframeHeight = iframeBounds.height;
|
||||
document.body.classList.add('is-detached-mode');
|
||||
|
||||
this.detachedWindow = window.open(
|
||||
'./preview.html',
|
||||
'Web Maker',
|
||||
`width=${iframeWidth},height=${iframeHeight},resizable,scrollbars=yes,status=1`
|
||||
);
|
||||
if (window.IS_EXTENSION) {
|
||||
this.detachedWindow = window.open(
|
||||
'./preview.html',
|
||||
'Web Maker',
|
||||
`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
|
||||
setTimeout(() => {
|
||||
this.setPreviewContent(true);
|
||||
@@ -837,7 +880,7 @@ export default class ContentWrap extends Component {
|
||||
<a
|
||||
class="code-wrap__header-btn "
|
||||
title="Format code"
|
||||
onClick={this.prettifyBtnClickHandler.bind(this, 'css')}
|
||||
onClick={this.prettifyBtnClickHandler.bind(this, 'js')}
|
||||
>
|
||||
<svg>
|
||||
<use xlinkHref="#code-brace-icon" />
|
||||
@@ -878,13 +921,26 @@ export default class ContentWrap extends Component {
|
||||
</div>
|
||||
</SplitPane>
|
||||
<div class="demo-side" id="js-demo-side" style="">
|
||||
<iframe
|
||||
ref={el => (this.frame = el)}
|
||||
frameborder="0"
|
||||
id="demo-frame"
|
||||
allowfullscreen
|
||||
src="./indexpm.html"
|
||||
/>
|
||||
{window.IS_EXTENSION ? (
|
||||
<iframe
|
||||
ref={el => (this.frame = el)}
|
||||
frameborder="0"
|
||||
id="demo-frame"
|
||||
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)} />
|
||||
|
||||
|
@@ -29,7 +29,7 @@ import { FileIcon } from './FileIcon';
|
||||
const minCodeWrapSize = 33;
|
||||
const PREVIEW_FRAME_HOST = window.DEBUG
|
||||
? 'http://localhost:7888'
|
||||
: `https://preview.${location.host}`;
|
||||
: `https://wbmakr.com`;
|
||||
|
||||
/* global htmlCodeEl
|
||||
*/
|
||||
@@ -355,7 +355,7 @@ export default class ContentWrapFiles extends Component {
|
||||
|
||||
// Replace correct css file in LINK tags's href
|
||||
if (prefs.editorTheme) {
|
||||
window.editorThemeLinkTag.href = `lib/codemirror/theme/${prefs.editorTheme}.css`;
|
||||
window.editorThemeLinkTag.href = `./lib/codemirror/theme/${prefs.editorTheme}.css`;
|
||||
}
|
||||
|
||||
window.fontStyleTag.textContent =
|
||||
|
@@ -5,6 +5,7 @@ import templates from '../templateList';
|
||||
import { BetaTag } from './common';
|
||||
import { trackEvent } from '../analytics';
|
||||
import Tabs, { TabPanel } from './Tabs';
|
||||
import { ProBadge } from './ProBadge';
|
||||
|
||||
export class CreateNewModal extends Component {
|
||||
constructor(props) {
|
||||
@@ -150,11 +151,10 @@ export class CreateNewModal extends Component {
|
||||
<h1 class="mt-0">Create New</h1>
|
||||
<Tabs horizontal onChange={this.modeChangeHandler}>
|
||||
<TabPanel label={option1}>
|
||||
<div class="d-f fxw-w">
|
||||
<div class="templates-container">
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn--primary"
|
||||
style="margin:20px 10px"
|
||||
onClick={() => {
|
||||
trackEvent('ui', 'startBlankBtnClick');
|
||||
onBlankTemplateSelect();
|
||||
@@ -170,17 +170,17 @@ export class CreateNewModal extends Component {
|
||||
item={template}
|
||||
focusable
|
||||
onClick={onTemplateSelect.bind(null, template, false)}
|
||||
hasOptions={false}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</TabPanel>
|
||||
<TabPanel label={option2}>
|
||||
<div class="d-f fxw-w show-when-app">
|
||||
<div class="templates-container show-when-app">
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn--primary"
|
||||
style="margin:20px 10px"
|
||||
onClick={() => {
|
||||
trackEvent('ui', 'startBlankFileBtnClick');
|
||||
onBlankFileTemplateSelect();
|
||||
@@ -196,14 +196,19 @@ export class CreateNewModal extends Component {
|
||||
item={template}
|
||||
focusable
|
||||
onClick={onTemplateSelect.bind(null, template, true)}
|
||||
hasOptions={false}
|
||||
/>
|
||||
);
|
||||
}
|
||||
})}
|
||||
</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">
|
||||
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>
|
||||
</TabPanel>
|
||||
</Tabs>
|
||||
|
@@ -1,337 +1,342 @@
|
||||
import { h, Component } from 'preact';
|
||||
import { Button } from './common';
|
||||
import { Trans, t } from '@lingui/macro';
|
||||
import { I18n } from '@lingui/react';
|
||||
import { ProBadge } from './ProBadge';
|
||||
import { HStack } from './Stack';
|
||||
import { useEffect, useState } from 'preact/hooks';
|
||||
|
||||
class JS13K extends Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
const compoDate = new Date('August 13 2018 11:00 GMT');
|
||||
var now = new Date();
|
||||
var daysLeft;
|
||||
const JS13K = props => {
|
||||
const [daysLeft, setDaysLeft] = useState(0);
|
||||
|
||||
useEffect(() => {
|
||||
const compoDate = new Date('August 13 2024 11:00 GMT');
|
||||
const now = new Date();
|
||||
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
|
||||
});
|
||||
}
|
||||
}, []);
|
||||
|
||||
render() {
|
||||
const codeSizeInKb = this.props.codeSize
|
||||
? (this.props.codeSize / 1024).toFixed(2)
|
||||
: 0;
|
||||
return (
|
||||
const codeSizeInKb = props.codeSize ? (props.codeSize / 1024).toFixed(2) : 0;
|
||||
|
||||
return (
|
||||
<div
|
||||
role="button"
|
||||
className="flex flex-v-center"
|
||||
tabIndex="0"
|
||||
onClick={props.onClick}
|
||||
onBlur={props.onBlur}
|
||||
>
|
||||
<img src="assets/js13kgames.png" alt="JS13K Games logo" height="24" />{' '}
|
||||
<div className="footer__js13k-days-left">{daysLeft} days to go</div>
|
||||
<div
|
||||
role="button"
|
||||
class="flex flex-v-center"
|
||||
tabIndex="0"
|
||||
onClick={this.props.onClick}
|
||||
onBlur={this.props.onBlur}
|
||||
className="footer__js13k-code-size"
|
||||
style={{
|
||||
color: codeSizeInKb > 10 ? 'crimson' : 'limegreen'
|
||||
}}
|
||||
>
|
||||
<img src="assets/js13kgames.png" alt="JS13K Games logo" height="24" />{' '}
|
||||
<div class="footer__js13k-days-left">
|
||||
{this.state.daysLeft} days to go
|
||||
</div>
|
||||
<div
|
||||
class="footer__js13k-code-size"
|
||||
style={{
|
||||
color: codeSizeInKb > 10 ? 'crimson' : 'limegreen'
|
||||
}}
|
||||
>
|
||||
{codeSizeInKb} KB/ 13KB
|
||||
</div>
|
||||
<span
|
||||
class="caret"
|
||||
style={`transition:0.3s ease; transform-origin: center 2px; ${
|
||||
this.props.isOpen ? 'transform:rotate(180deg);' : ''
|
||||
}`}
|
||||
/>
|
||||
{codeSizeInKb} KB/ 13KB
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
<span
|
||||
className="caret"
|
||||
style={{
|
||||
transition: '0.3s ease',
|
||||
transformOrigin: 'center 2px',
|
||||
transform: props.isOpen ? 'rotate(180deg)' : ''
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default class Footer extends Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
isKeyboardShortcutsModalOpen: false,
|
||||
isJs13kDropdownOpen: false
|
||||
};
|
||||
}
|
||||
layoutBtnClickhandler(layoutId) {
|
||||
this.props.layoutBtnClickHandler(layoutId);
|
||||
export const Footer = props => {
|
||||
const [isKeyboardShortcutsModalOpen, setIsKeyboardShortcutsModalOpen] =
|
||||
useState(false);
|
||||
const [isJs13kDropdownOpen, setIsJs13kDropdownOpen] = useState(false);
|
||||
|
||||
function layoutBtnClickhandler(layoutId) {
|
||||
props.layoutBtnClickHandler(layoutId);
|
||||
}
|
||||
|
||||
js13kClickHandler() {
|
||||
function js13kClickHandler() {
|
||||
// console.log(999);
|
||||
this.setState({
|
||||
isJs13kDropdownOpen: !this.state.isJs13kDropdownOpen
|
||||
});
|
||||
setIsJs13kDropdownOpen(!isJs13kDropdownOpen);
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<I18n>
|
||||
{({ i18n }) => (
|
||||
<div id="footer" class="footer">
|
||||
<div>
|
||||
<a href="/" target="_blank" rel="noopener noreferrer">
|
||||
<div class="logo" />
|
||||
</a>
|
||||
©
|
||||
<span class="web-maker-with-tag">Web Maker</span>
|
||||
<Button
|
||||
onClick={this.props.helpBtnClickHandler}
|
||||
data-event-category="ui"
|
||||
data-event-action="helpButtonClick"
|
||||
class="footer__link hint--rounded hint--top-right"
|
||||
aria-label={i18n._(t`Help`)}
|
||||
return (
|
||||
<I18n>
|
||||
{({ i18n }) => (
|
||||
<div id="footer" class="footer">
|
||||
<div>
|
||||
<a href="/" target="_blank" rel="noopener noreferrer">
|
||||
<div class="logo" />
|
||||
</a>
|
||||
©
|
||||
<span class="web-maker-with-tag">Web Maker</span>
|
||||
<Button
|
||||
onClick={props.helpBtnClickHandler}
|
||||
data-event-category="ui"
|
||||
data-event-action="helpButtonClick"
|
||||
class="footer__link hint--rounded hint--top-right"
|
||||
aria-label={i18n._(t`Help`)}
|
||||
>
|
||||
<svg
|
||||
style="width:20px; height:20px; vertical-align:text-bottom"
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<svg
|
||||
style="width:20px; height:20px; vertical-align:text-bottom"
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<path d="M15.07,11.25L14.17,12.17C13.45,12.89 13,13.5 13,15H11V14.5C11,13.39 11.45,12.39 12.17,11.67L13.41,10.41C13.78,10.05 14,9.55 14,9C14,7.89 13.1,7 12,7A2,2 0 0,0 10,9H8A4,4 0 0,1 12,5A4,4 0 0,1 16,9C16,9.88 15.64,10.67 15.07,11.25M13,19H11V17H13M12,2A10,10 0 0,0 2,12A10,10 0 0,0 12,22A10,10 0 0,0 22,12C22,6.47 17.5,2 12,2Z" />
|
||||
</svg>
|
||||
<path d="M15.07,11.25L14.17,12.17C13.45,12.89 13,13.5 13,15H11V14.5C11,13.39 11.45,12.39 12.17,11.67L13.41,10.41C13.78,10.05 14,9.55 14,9C14,7.89 13.1,7 12,7A2,2 0 0,0 10,9H8A4,4 0 0,1 12,5A4,4 0 0,1 16,9C16,9.88 15.64,10.67 15.07,11.25M13,19H11V17H13M12,2A10,10 0 0,0 2,12A10,10 0 0,0 12,22A10,10 0 0,0 22,12C22,6.47 17.5,2 12,2Z" />
|
||||
</svg>
|
||||
</Button>
|
||||
<Button
|
||||
onClick={props.keyboardShortcutsBtnClickHandler}
|
||||
data-event-category="ui"
|
||||
data-event-action="keyboardShortcutButtonClick"
|
||||
class="footer__link hint--rounded hint--top-right hide-on-mobile"
|
||||
aria-label={i18n._(t`Keyboard shortcuts`)}
|
||||
>
|
||||
<svg
|
||||
style={{
|
||||
width: '20px',
|
||||
height: '20px',
|
||||
verticalAlign: 'text-bottom'
|
||||
}}
|
||||
>
|
||||
<use xlinkHref="#keyboard-icon" />
|
||||
</svg>
|
||||
</Button>
|
||||
<a
|
||||
class="footer__link hint--rounded hint--top-right"
|
||||
aria-label={i18n._(t`Tweet about 'Web Maker'`)}
|
||||
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"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
<svg
|
||||
style={{
|
||||
width: '20px',
|
||||
height: '20px',
|
||||
verticalAlign: 'text-bottom'
|
||||
}}
|
||||
>
|
||||
<use xlinkHref="#twitter-icon" />
|
||||
</svg>
|
||||
</a>
|
||||
{props.user?.isPro ? (
|
||||
<Button
|
||||
onClick={props.proBtnClickHandler}
|
||||
data-event-category="ui"
|
||||
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={this.props.keyboardShortcutsBtnClickHandler}
|
||||
onClick={props.proBtnClickHandler}
|
||||
data-event-category="ui"
|
||||
data-event-action="keyboardShortcutButtonClick"
|
||||
class="footer__link hint--rounded hint--top-right hide-on-mobile"
|
||||
aria-label={i18n._(t`Keyboard shortcuts`)}
|
||||
>
|
||||
<svg
|
||||
style={{
|
||||
width: '20px',
|
||||
height: '20px',
|
||||
verticalAlign: 'text-bottom'
|
||||
}}
|
||||
>
|
||||
<use xlinkHref="#keyboard-icon" />
|
||||
</svg>
|
||||
</Button>
|
||||
<a
|
||||
class="footer__link hint--rounded hint--top-right"
|
||||
aria-label={i18n._(t`Tweet about 'Web Maker'`)}
|
||||
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"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
<svg
|
||||
style={{
|
||||
width: '20px',
|
||||
height: '20px',
|
||||
verticalAlign: 'text-bottom'
|
||||
}}
|
||||
>
|
||||
<use xlinkHref="#twitter-icon" />
|
||||
</svg>
|
||||
</a>
|
||||
<Button
|
||||
onClick={this.props.supportDeveloperBtnClickHandler}
|
||||
data-event-category="ui"
|
||||
data-event-action="supportDeveloperFooterBtnClick"
|
||||
data-event-action="proFooterBtnClick"
|
||||
class="footer__link ml-1 hint--rounded hint--top-right hide-on-mobile support-link"
|
||||
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>
|
||||
</div>
|
||||
|
||||
{this.props.prefs.isJs13kModeOn ? (
|
||||
<div class="flex flex-v-center">
|
||||
<JS13K
|
||||
isOpen={this.state.isJs13kDropdownOpen}
|
||||
codeSize={this.props.codeSize}
|
||||
onClick={this.js13kClickHandler.bind(this)}
|
||||
onBlur={() =>
|
||||
setTimeout(
|
||||
() => this.setState({ isJs13kDropdownOpen: false }),
|
||||
300
|
||||
)
|
||||
}
|
||||
/>
|
||||
{this.state.isJs13kDropdownOpen && (
|
||||
<div className="js13k__dropdown">
|
||||
<button
|
||||
class="btn"
|
||||
style={{
|
||||
width: '200px',
|
||||
display: 'block',
|
||||
marginBottom: '16px'
|
||||
}}
|
||||
onClick={this.props.onJs13KDownloadBtnClick}
|
||||
>
|
||||
<Trans>Download game as zip</Trans>
|
||||
</button>
|
||||
<a
|
||||
class="btn"
|
||||
rel="noopener"
|
||||
style={{
|
||||
width: '200px',
|
||||
display: 'block',
|
||||
marginBottom: '16px'
|
||||
}}
|
||||
href="https://pasteboard.co/"
|
||||
target="_blank"
|
||||
>
|
||||
<Trans>Upload Image</Trans>
|
||||
</a>
|
||||
<button
|
||||
class="btn"
|
||||
style={{ width: '200px', display: 'block' }}
|
||||
onClick={this.props.onJs13KHelpBtnClick}
|
||||
>
|
||||
<Trans>Help</Trans>
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
) : null}
|
||||
|
||||
<div class="footer__right">
|
||||
<button
|
||||
onClick={this.props.saveHtmlBtnClickHandler}
|
||||
id="saveHtmlBtn"
|
||||
class="mode-btn hint--rounded hint--top-left hide-on-mobile hide-in-file-mode"
|
||||
aria-label={i18n._(t`Save as HTML file`)}
|
||||
>
|
||||
<svg viewBox="0 0 24 24">
|
||||
<path d="M5,20H19V18H5M19,9H15V3H9V9H5L12,16L19,9Z" />
|
||||
</svg>
|
||||
</button>
|
||||
<svg style="display: none;" xmlns="http://www.w3.org/2000/svg">
|
||||
<symbol id="codepen-logo" viewBox="0 0 120 120">
|
||||
<path
|
||||
class="outer-ring"
|
||||
d="M60.048 0C26.884 0 0 26.9 0 60.048s26.884 60 60 60.047c33.163 0 60.047-26.883 60.047-60.047 S93.211 0 60 0z M60.048 110.233c-27.673 0-50.186-22.514-50.186-50.186S32.375 9.9 60 9.9 c27.672 0 50.2 22.5 50.2 50.186S87.72 110.2 60 110.233z"
|
||||
/>
|
||||
<path
|
||||
class="inner-box"
|
||||
d="M97.147 48.319c-0.007-0.047-0.019-0.092-0.026-0.139c-0.016-0.09-0.032-0.18-0.056-0.268 c-0.014-0.053-0.033-0.104-0.05-0.154c-0.025-0.078-0.051-0.156-0.082-0.232c-0.021-0.053-0.047-0.105-0.071-0.156 c-0.033-0.072-0.068-0.143-0.108-0.211c-0.029-0.051-0.061-0.1-0.091-0.148c-0.043-0.066-0.087-0.131-0.135-0.193 c-0.035-0.047-0.072-0.094-0.109-0.139c-0.051-0.059-0.104-0.117-0.159-0.172c-0.042-0.043-0.083-0.086-0.127-0.125 c-0.059-0.053-0.119-0.104-0.181-0.152c-0.048-0.037-0.095-0.074-0.145-0.109c-0.019-0.012-0.035-0.027-0.053-0.039L61.817 23.5 c-1.072-0.715-2.468-0.715-3.54 0L24.34 46.081c-0.018 0.012-0.034 0.027-0.053 0.039c-0.05 0.035-0.097 0.072-0.144 0.1 c-0.062 0.049-0.123 0.1-0.181 0.152c-0.045 0.039-0.086 0.082-0.128 0.125c-0.056 0.055-0.108 0.113-0.158 0.2 c-0.038 0.045-0.075 0.092-0.11 0.139c-0.047 0.062-0.092 0.127-0.134 0.193c-0.032 0.049-0.062 0.098-0.092 0.1 c-0.039 0.068-0.074 0.139-0.108 0.211c-0.024 0.051-0.05 0.104-0.071 0.156c-0.031 0.076-0.057 0.154-0.082 0.2 c-0.017 0.051-0.035 0.102-0.05 0.154c-0.023 0.088-0.039 0.178-0.056 0.268c-0.008 0.047-0.02 0.092-0.025 0.1 c-0.019 0.137-0.029 0.275-0.029 0.416V71.36c0 0.1 0 0.3 0 0.418c0.006 0 0 0.1 0 0.1 c0.017 0.1 0 0.2 0.1 0.268c0.015 0.1 0 0.1 0.1 0.154c0.025 0.1 0.1 0.2 0.1 0.2 c0.021 0.1 0 0.1 0.1 0.154c0.034 0.1 0.1 0.1 0.1 0.213c0.029 0 0.1 0.1 0.1 0.1 c0.042 0.1 0.1 0.1 0.1 0.193c0.035 0 0.1 0.1 0.1 0.139c0.05 0.1 0.1 0.1 0.2 0.2 c0.042 0 0.1 0.1 0.1 0.125c0.058 0.1 0.1 0.1 0.2 0.152c0.047 0 0.1 0.1 0.1 0.1 c0.019 0 0 0 0.1 0.039L58.277 96.64c0.536 0.4 1.2 0.5 1.8 0.537c0.616 0 1.233-0.18 1.77-0.537 l33.938-22.625c0.018-0.012 0.034-0.027 0.053-0.039c0.05-0.035 0.097-0.072 0.145-0.109c0.062-0.049 0.122-0.1 0.181-0.152 c0.044-0.039 0.085-0.082 0.127-0.125c0.056-0.055 0.108-0.113 0.159-0.172c0.037-0.045 0.074-0.09 0.109-0.139 c0.048-0.062 0.092-0.127 0.135-0.193c0.03-0.049 0.062-0.098 0.091-0.146c0.04-0.07 0.075-0.141 0.108-0.213 c0.024-0.051 0.05-0.102 0.071-0.154c0.031-0.078 0.057-0.156 0.082-0.234c0.017-0.051 0.036-0.102 0.05-0.154 c0.023-0.088 0.04-0.178 0.056-0.268c0.008-0.045 0.02-0.092 0.026-0.137c0.018-0.139 0.028-0.277 0.028-0.418V48.735 C97.176 48.6 97.2 48.5 97.1 48.319z M63.238 32.073l25.001 16.666L77.072 56.21l-13.834-9.254V32.073z M56.856 32.1 v14.883L43.023 56.21l-11.168-7.471L56.856 32.073z M29.301 54.708l7.983 5.34l-7.983 5.34V54.708z M56.856 88.022L31.855 71.4 l11.168-7.469l13.833 9.252V88.022z M60.048 67.597l-11.286-7.549l11.286-7.549l11.285 7.549L60.048 67.597z M63.238 88.022V73.14 l13.834-9.252l11.167 7.469L63.238 88.022z M90.794 65.388l-7.982-5.34l7.982-5.34V65.388z"
|
||||
/>
|
||||
</symbol>
|
||||
</svg>
|
||||
|
||||
<button
|
||||
onClick={this.props.codepenBtnClickHandler}
|
||||
id="codepenBtn"
|
||||
class="mode-btn hint--rounded hint--top-left hide-on-mobile hide-in-file-mode"
|
||||
aria-label={i18n._(t`Edit on CodePen`)}
|
||||
>
|
||||
<svg>
|
||||
<use xlinkHref="#codepen-logo" />
|
||||
</svg>
|
||||
</button>
|
||||
|
||||
<button
|
||||
id="screenshotBtn"
|
||||
class="mode-btn hint--rounded hint--top-left show-when-extension"
|
||||
onClick={this.props.screenshotBtnClickHandler}
|
||||
aria-label={i18n._(t`Take screenshot of preview`)}
|
||||
>
|
||||
<svg style="width:24px;height:24px" viewBox="0 0 24 24">
|
||||
<path d="M4,4H7L9,2H15L17,4H20A2,2 0 0,1 22,6V18A2,2 0 0,1 20,20H4A2,2 0 0,1 2,18V6A2,2 0 0,1 4,4M12,7A5,5 0 0,0 7,12A5,5 0 0,0 12,17A5,5 0 0,0 17,12A5,5 0 0,0 12,7M12,9A3,3 0 0,1 15,12A3,3 0 0,1 12,15A3,3 0 0,1 9,12A3,3 0 0,1 12,9Z" />
|
||||
</svg>
|
||||
</button>
|
||||
|
||||
<div class="footer__separator hide-on-mobile" />
|
||||
|
||||
<button
|
||||
onClick={this.layoutBtnClickhandler.bind(this, 1)}
|
||||
id="layoutBtn1"
|
||||
class="mode-btn hide-on-mobile hide-in-file-mode"
|
||||
aria-label={i18n._(t`Switch to layout with preview on right`)}
|
||||
>
|
||||
<svg viewBox="0 0 100 100" style="transform:rotate(-90deg)">
|
||||
<use xlinkHref="#mode-icon" />
|
||||
</svg>
|
||||
</button>
|
||||
<button
|
||||
onClick={this.layoutBtnClickhandler.bind(this, 2)}
|
||||
id="layoutBtn2"
|
||||
class="mode-btn hide-on-mobile hide-in-file-mode"
|
||||
aria-label={i18n._(t`Switch to layout with preview on bottom`)}
|
||||
>
|
||||
<svg viewBox="0 0 100 100">
|
||||
<use xlinkHref="#mode-icon" />
|
||||
</svg>
|
||||
</button>
|
||||
<button
|
||||
onClick={this.layoutBtnClickhandler.bind(this, 3)}
|
||||
id="layoutBtn3"
|
||||
class="mode-btn hide-on-mobile hide-in-file-mode"
|
||||
aria-label={i18n._(t`Switch to layout with preview on left`)}
|
||||
>
|
||||
<svg viewBox="0 0 100 100" style="transform:rotate(90deg)">
|
||||
<use xlinkHref="#mode-icon" />
|
||||
</svg>
|
||||
</button>
|
||||
<button
|
||||
onClick={this.layoutBtnClickhandler.bind(this, 5)}
|
||||
id="layoutBtn5"
|
||||
class="mode-btn hide-on-mobile hide-in-file-mode"
|
||||
aria-label={i18n._(t`Switch to layout with all vertical panes`)}
|
||||
>
|
||||
<svg viewBox="0 0 100 100">
|
||||
<use xlinkHref="#vertical-mode-icon" />
|
||||
</svg>
|
||||
</button>
|
||||
<button
|
||||
onClick={this.layoutBtnClickhandler.bind(this, 4)}
|
||||
id="layoutBtn4"
|
||||
class="mode-btn hint--top-left hint--rounded hide-on-mobile"
|
||||
aria-label={i18n._(t`Toggle full screen preview`)}
|
||||
>
|
||||
<svg viewBox="0 0 100 100">
|
||||
<rect x="0" y="0" width="100" height="100" />
|
||||
</svg>
|
||||
</button>
|
||||
<button
|
||||
class="mode-btn hint--top-left hint--rounded hide-on-mobile"
|
||||
aria-label={i18n._(t`Detach preview`)}
|
||||
onClick={this.props.detachedPreviewBtnHandler}
|
||||
>
|
||||
<svg viewBox="0 0 24 24">
|
||||
<path d="M22,17V7H6V17H22M22,5A2,2 0 0,1 24,7V17C24,18.11 23.1,19 22,19H16V21H18V23H10V21H12V19H6C4.89,19 4,18.11 4,17V7A2,2 0 0,1 6,5H22M2,3V15H0V3A2,2 0 0,1 2,1H20V3H2Z" />
|
||||
</svg>
|
||||
</button>
|
||||
|
||||
<div class="footer__separator" />
|
||||
|
||||
<button
|
||||
onClick={this.props.notificationsBtnClickHandler}
|
||||
id="notificationsBtn"
|
||||
class={`notifications-btn mode-btn hint--top-left hint--rounded ${
|
||||
this.props.hasUnseenChangelog ? 'has-new' : ''
|
||||
}`}
|
||||
aria-label={i18n._(t`See changelog`)}
|
||||
>
|
||||
<svg viewBox="0 0 24 24">
|
||||
<path d="M14,20A2,2 0 0,1 12,22A2,2 0 0,1 10,20H14M12,2A1,1 0 0,1 13,3V4.08C15.84,4.56 18,7.03 18,10V16L21,19H3L6,16V10C6,7.03 8.16,4.56 11,4.08V3A1,1 0 0,1 12,2Z" />
|
||||
</svg>
|
||||
<span class="notifications-btn__dot" />
|
||||
</button>
|
||||
<Button
|
||||
onClick={this.props.settingsBtnClickHandler}
|
||||
data-event-category="ui"
|
||||
data-event-action="settingsBtnClick"
|
||||
class="mode-btn hint--top-left hint--rounded"
|
||||
aria-label={i18n._(t`Settings`)}
|
||||
>
|
||||
<svg>
|
||||
<use xlinkHref="#settings-icon" />
|
||||
</svg>
|
||||
</Button>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</I18n>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
{props.prefs.isJs13kModeOn ? (
|
||||
<div class="flex flex-v-center">
|
||||
<JS13K
|
||||
isOpen={isJs13kDropdownOpen}
|
||||
codeSize={props.codeSize}
|
||||
onClick={js13kClickHandler}
|
||||
onBlur={() =>
|
||||
setTimeout(() => setIsJs13kDropdownOpen(false), 300)
|
||||
}
|
||||
/>
|
||||
{isJs13kDropdownOpen && (
|
||||
<div className="js13k__dropdown">
|
||||
<button
|
||||
class="btn"
|
||||
style={{
|
||||
width: '200px',
|
||||
display: 'block',
|
||||
marginBottom: '16px'
|
||||
}}
|
||||
onClick={props.onJs13KDownloadBtnClick}
|
||||
>
|
||||
<Trans>Download game as zip</Trans>
|
||||
</button>
|
||||
<a
|
||||
class="btn"
|
||||
rel="noopener"
|
||||
style={{
|
||||
width: '200px',
|
||||
display: 'block',
|
||||
marginBottom: '16px'
|
||||
}}
|
||||
href="https://pasteboard.co/"
|
||||
target="_blank"
|
||||
>
|
||||
<Trans>Upload Image</Trans>
|
||||
</a>
|
||||
<button
|
||||
class="btn"
|
||||
style={{ width: '200px', display: 'block' }}
|
||||
onClick={props.onJs13KHelpBtnClick}
|
||||
>
|
||||
<Trans>Help</Trans>
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
) : null}
|
||||
|
||||
<div class="footer__right">
|
||||
<button
|
||||
onClick={props.saveHtmlBtnClickHandler}
|
||||
id="saveHtmlBtn"
|
||||
class="mode-btn hint--rounded hint--top-left hide-on-mobile hide-in-file-mode"
|
||||
aria-label={i18n._(t`Save as HTML file`)}
|
||||
>
|
||||
<svg viewBox="0 0 24 24">
|
||||
<path d="M5,20H19V18H5M19,9H15V3H9V9H5L12,16L19,9Z" />
|
||||
</svg>
|
||||
</button>
|
||||
<svg style="display: none;" xmlns="http://www.w3.org/2000/svg">
|
||||
<symbol id="codepen-logo" viewBox="0 0 120 120">
|
||||
<path
|
||||
class="outer-ring"
|
||||
d="M60.048 0C26.884 0 0 26.9 0 60.048s26.884 60 60 60.047c33.163 0 60.047-26.883 60.047-60.047 S93.211 0 60 0z M60.048 110.233c-27.673 0-50.186-22.514-50.186-50.186S32.375 9.9 60 9.9 c27.672 0 50.2 22.5 50.2 50.186S87.72 110.2 60 110.233z"
|
||||
/>
|
||||
<path
|
||||
class="inner-box"
|
||||
d="M97.147 48.319c-0.007-0.047-0.019-0.092-0.026-0.139c-0.016-0.09-0.032-0.18-0.056-0.268 c-0.014-0.053-0.033-0.104-0.05-0.154c-0.025-0.078-0.051-0.156-0.082-0.232c-0.021-0.053-0.047-0.105-0.071-0.156 c-0.033-0.072-0.068-0.143-0.108-0.211c-0.029-0.051-0.061-0.1-0.091-0.148c-0.043-0.066-0.087-0.131-0.135-0.193 c-0.035-0.047-0.072-0.094-0.109-0.139c-0.051-0.059-0.104-0.117-0.159-0.172c-0.042-0.043-0.083-0.086-0.127-0.125 c-0.059-0.053-0.119-0.104-0.181-0.152c-0.048-0.037-0.095-0.074-0.145-0.109c-0.019-0.012-0.035-0.027-0.053-0.039L61.817 23.5 c-1.072-0.715-2.468-0.715-3.54 0L24.34 46.081c-0.018 0.012-0.034 0.027-0.053 0.039c-0.05 0.035-0.097 0.072-0.144 0.1 c-0.062 0.049-0.123 0.1-0.181 0.152c-0.045 0.039-0.086 0.082-0.128 0.125c-0.056 0.055-0.108 0.113-0.158 0.2 c-0.038 0.045-0.075 0.092-0.11 0.139c-0.047 0.062-0.092 0.127-0.134 0.193c-0.032 0.049-0.062 0.098-0.092 0.1 c-0.039 0.068-0.074 0.139-0.108 0.211c-0.024 0.051-0.05 0.104-0.071 0.156c-0.031 0.076-0.057 0.154-0.082 0.2 c-0.017 0.051-0.035 0.102-0.05 0.154c-0.023 0.088-0.039 0.178-0.056 0.268c-0.008 0.047-0.02 0.092-0.025 0.1 c-0.019 0.137-0.029 0.275-0.029 0.416V71.36c0 0.1 0 0.3 0 0.418c0.006 0 0 0.1 0 0.1 c0.017 0.1 0 0.2 0.1 0.268c0.015 0.1 0 0.1 0.1 0.154c0.025 0.1 0.1 0.2 0.1 0.2 c0.021 0.1 0 0.1 0.1 0.154c0.034 0.1 0.1 0.1 0.1 0.213c0.029 0 0.1 0.1 0.1 0.1 c0.042 0.1 0.1 0.1 0.1 0.193c0.035 0 0.1 0.1 0.1 0.139c0.05 0.1 0.1 0.1 0.2 0.2 c0.042 0 0.1 0.1 0.1 0.125c0.058 0.1 0.1 0.1 0.2 0.152c0.047 0 0.1 0.1 0.1 0.1 c0.019 0 0 0 0.1 0.039L58.277 96.64c0.536 0.4 1.2 0.5 1.8 0.537c0.616 0 1.233-0.18 1.77-0.537 l33.938-22.625c0.018-0.012 0.034-0.027 0.053-0.039c0.05-0.035 0.097-0.072 0.145-0.109c0.062-0.049 0.122-0.1 0.181-0.152 c0.044-0.039 0.085-0.082 0.127-0.125c0.056-0.055 0.108-0.113 0.159-0.172c0.037-0.045 0.074-0.09 0.109-0.139 c0.048-0.062 0.092-0.127 0.135-0.193c0.03-0.049 0.062-0.098 0.091-0.146c0.04-0.07 0.075-0.141 0.108-0.213 c0.024-0.051 0.05-0.102 0.071-0.154c0.031-0.078 0.057-0.156 0.082-0.234c0.017-0.051 0.036-0.102 0.05-0.154 c0.023-0.088 0.04-0.178 0.056-0.268c0.008-0.045 0.02-0.092 0.026-0.137c0.018-0.139 0.028-0.277 0.028-0.418V48.735 C97.176 48.6 97.2 48.5 97.1 48.319z M63.238 32.073l25.001 16.666L77.072 56.21l-13.834-9.254V32.073z M56.856 32.1 v14.883L43.023 56.21l-11.168-7.471L56.856 32.073z M29.301 54.708l7.983 5.34l-7.983 5.34V54.708z M56.856 88.022L31.855 71.4 l11.168-7.469l13.833 9.252V88.022z M60.048 67.597l-11.286-7.549l11.286-7.549l11.285 7.549L60.048 67.597z M63.238 88.022V73.14 l13.834-9.252l11.167 7.469L63.238 88.022z M90.794 65.388l-7.982-5.34l7.982-5.34V65.388z"
|
||||
/>
|
||||
</symbol>
|
||||
</svg>
|
||||
|
||||
<button
|
||||
onClick={props.codepenBtnClickHandler}
|
||||
id="codepenBtn"
|
||||
class="mode-btn hint--rounded hint--top-left hide-on-mobile hide-in-file-mode"
|
||||
aria-label={i18n._(t`Edit on CodePen`)}
|
||||
>
|
||||
<svg>
|
||||
<use xlinkHref="#codepen-logo" />
|
||||
</svg>
|
||||
</button>
|
||||
|
||||
<button
|
||||
id="screenshotBtn"
|
||||
class="mode-btn hint--rounded hint--top-left show-when-extension"
|
||||
onClick={props.screenshotBtnClickHandler}
|
||||
aria-label={i18n._(t`Take screenshot of preview`)}
|
||||
>
|
||||
<svg style="width:24px;height:24px" viewBox="0 0 24 24">
|
||||
<path d="M4,4H7L9,2H15L17,4H20A2,2 0 0,1 22,6V18A2,2 0 0,1 20,20H4A2,2 0 0,1 2,18V6A2,2 0 0,1 4,4M12,7A5,5 0 0,0 7,12A5,5 0 0,0 12,17A5,5 0 0,0 17,12A5,5 0 0,0 12,7M12,9A3,3 0 0,1 15,12A3,3 0 0,1 12,15A3,3 0 0,1 9,12A3,3 0 0,1 12,9Z" />
|
||||
</svg>
|
||||
</button>
|
||||
|
||||
<div class="footer__separator hide-on-mobile" />
|
||||
|
||||
<button
|
||||
onClick={() => layoutBtnClickhandler(1)}
|
||||
id="layoutBtn1"
|
||||
class="mode-btn hide-on-mobile hide-in-file-mode"
|
||||
aria-label={i18n._(t`Switch to layout with preview on right`)}
|
||||
>
|
||||
<svg viewBox="0 0 100 100" style="transform:rotate(-90deg)">
|
||||
<use xlinkHref="#mode-icon" />
|
||||
</svg>
|
||||
</button>
|
||||
<button
|
||||
onClick={() => layoutBtnClickhandler(2)}
|
||||
id="layoutBtn2"
|
||||
class="mode-btn hide-on-mobile hide-in-file-mode"
|
||||
aria-label={i18n._(t`Switch to layout with preview on bottom`)}
|
||||
>
|
||||
<svg viewBox="0 0 100 100">
|
||||
<use xlinkHref="#mode-icon" />
|
||||
</svg>
|
||||
</button>
|
||||
<button
|
||||
onClick={() => layoutBtnClickhandler(3)}
|
||||
id="layoutBtn3"
|
||||
class="mode-btn hide-on-mobile hide-in-file-mode"
|
||||
aria-label={i18n._(t`Switch to layout with preview on left`)}
|
||||
>
|
||||
<svg viewBox="0 0 100 100" style="transform:rotate(90deg)">
|
||||
<use xlinkHref="#mode-icon" />
|
||||
</svg>
|
||||
</button>
|
||||
<button
|
||||
onClick={() => layoutBtnClickhandler(5)}
|
||||
id="layoutBtn5"
|
||||
class="mode-btn hide-on-mobile hide-in-file-mode"
|
||||
aria-label={i18n._(t`Switch to layout with all vertical panes`)}
|
||||
>
|
||||
<svg viewBox="0 0 100 100">
|
||||
<use xlinkHref="#vertical-mode-icon" />
|
||||
</svg>
|
||||
</button>
|
||||
<button
|
||||
onClick={() => layoutBtnClickhandler(4)}
|
||||
id="layoutBtn4"
|
||||
class="mode-btn hint--top-left hint--rounded hide-on-mobile"
|
||||
aria-label={i18n._(t`Toggle full screen preview`)}
|
||||
>
|
||||
<svg viewBox="0 0 100 100">
|
||||
<rect x="0" y="0" width="100" height="100" />
|
||||
</svg>
|
||||
</button>
|
||||
<button
|
||||
class="mode-btn hint--top-left hint--rounded hide-on-mobile"
|
||||
aria-label={i18n._(t`Detach preview`)}
|
||||
onClick={props.detachedPreviewBtnHandler}
|
||||
>
|
||||
<svg viewBox="0 0 24 24">
|
||||
<path d="M22,17V7H6V17H22M22,5A2,2 0 0,1 24,7V17C24,18.11 23.1,19 22,19H16V21H18V23H10V21H12V19H6C4.89,19 4,18.11 4,17V7A2,2 0 0,1 6,5H22M2,3V15H0V3A2,2 0 0,1 2,1H20V3H2Z" />
|
||||
</svg>
|
||||
</button>
|
||||
|
||||
<div class="footer__separator" />
|
||||
|
||||
<button
|
||||
onClick={props.notificationsBtnClickHandler}
|
||||
id="notificationsBtn"
|
||||
class={`notifications-btn mode-btn hint--top-left hint--rounded ${
|
||||
props.hasUnseenChangelog ? 'has-new' : ''
|
||||
}`}
|
||||
aria-label={i18n._(t`See changelog`)}
|
||||
>
|
||||
<svg viewBox="0 0 24 24">
|
||||
<path d="M14,20A2,2 0 0,1 12,22A2,2 0 0,1 10,20H14M12,2A1,1 0 0,1 13,3V4.08C15.84,4.56 18,7.03 18,10V16L21,19H3L6,16V10C6,7.03 8.16,4.56 11,4.08V3A1,1 0 0,1 12,2Z" />
|
||||
</svg>
|
||||
<span class="notifications-btn__dot" />
|
||||
</button>
|
||||
<Button
|
||||
onClick={props.settingsBtnClickHandler}
|
||||
data-event-category="ui"
|
||||
data-event-action="settingsBtnClick"
|
||||
class="mode-btn hint--top-left hint--rounded"
|
||||
aria-label={i18n._(t`Settings`)}
|
||||
>
|
||||
<svg>
|
||||
<use xlinkHref="#settings-icon" />
|
||||
</svg>
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</I18n>
|
||||
);
|
||||
};
|
||||
|
@@ -1,5 +1,5 @@
|
||||
import { h } from 'preact';
|
||||
import Modal from './Modal';
|
||||
import { Stack, VStack } from './Stack';
|
||||
import { Button } from './common';
|
||||
import { Trans } from '@lingui/macro';
|
||||
|
||||
@@ -7,15 +7,21 @@ export function HelpModal(props) {
|
||||
return (
|
||||
<Modal show={props.show} closeHandler={props.closeHandler}>
|
||||
<h1>
|
||||
<div class="web-maker-with-tag">Web Maker</div>
|
||||
<small style="font-size:14px;">{props.version}</small>
|
||||
<Stack gap={1} align="center">
|
||||
<div class="web-maker-with-tag">Web Maker</div>
|
||||
<span className="badge">{props.version}</span>
|
||||
</Stack>
|
||||
</h1>
|
||||
|
||||
<div>
|
||||
<p>
|
||||
<Trans>
|
||||
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
|
||||
href="https://twitter.com/chinchang457"
|
||||
target="_blank"
|
||||
@@ -43,11 +49,11 @@ export function HelpModal(props) {
|
||||
</Trans>
|
||||
.
|
||||
</p>
|
||||
<p class="show-when-extension">
|
||||
<p>
|
||||
<Trans>
|
||||
Like this extension? Please{' '}
|
||||
Like this app? Please{' '}
|
||||
<a
|
||||
href="https://chrome.google.com/webstore/detail/web-maker/lkfkkhfhhdkiemehlpkgjeojomhpccnh/reviews"
|
||||
href="https://chromewebstore.google.com/detail/web-maker/lkfkkhfhhdkiemehlpkgjeojomhpccnh/reviews"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
@@ -56,20 +62,16 @@ export function HelpModal(props) {
|
||||
.
|
||||
</Trans>
|
||||
</p>
|
||||
<p>
|
||||
<Button
|
||||
onClick={props.onSupportBtnClick}
|
||||
data-event-action="supportDeveloperHelpBtnClick"
|
||||
data-event-category="ui"
|
||||
<p style={{ display: 'flex', gap: '0.5rem', flexWrap: 'wrap' }}>
|
||||
<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,frontend,playground,offline"
|
||||
rel="noopener noreferrer"
|
||||
class="btn btn-icon"
|
||||
>
|
||||
<svg>
|
||||
<use xlinkHref="#gift-icon" />
|
||||
</svg>
|
||||
<Trans>Support the developer</Trans>
|
||||
</Button>{' '}
|
||||
<Trans>Share Web Maker</Trans>
|
||||
</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"
|
||||
rel="noopener noreferrer"
|
||||
class="btn btn-icon"
|
||||
@@ -80,7 +82,7 @@ export function HelpModal(props) {
|
||||
<Trans>Review Web Maker</Trans>
|
||||
</a>{' '}
|
||||
<a
|
||||
href="https://spectrum.chat/web-maker"
|
||||
href="https://github.com/chinchang/web-maker/discussions"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
class="btn btn-icon"
|
||||
@@ -88,7 +90,7 @@ export function HelpModal(props) {
|
||||
<svg>
|
||||
<use xlinkHref="#chat-icon" />
|
||||
</svg>
|
||||
<Trans>Chat</Trans>
|
||||
<Trans>Discuss</Trans>
|
||||
</a>{' '}
|
||||
<a
|
||||
href="https://github.com/chinchang/web-maker/issues"
|
||||
@@ -190,29 +192,31 @@ export function HelpModal(props) {
|
||||
</details>
|
||||
</p>
|
||||
|
||||
<p>
|
||||
<VStack gap={1} align="stretch" fullWidth={true}>
|
||||
<h3>
|
||||
<Trans>License</Trans>
|
||||
</h3>
|
||||
<Trans>
|
||||
"Web Maker" is{' '}
|
||||
<a
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
href="https://github.com/chinchang/web-maker"
|
||||
>
|
||||
open-source
|
||||
</a>{' '}
|
||||
under the{' '}
|
||||
<a
|
||||
href="https://opensource.org/licenses/MIT"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
MIT License
|
||||
</a>
|
||||
</Trans>
|
||||
</p>
|
||||
<p>
|
||||
<Trans>
|
||||
"Web Maker" is{' '}
|
||||
<a
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
href="https://github.com/chinchang/web-maker"
|
||||
>
|
||||
open-source
|
||||
</a>{' '}
|
||||
under the{' '}
|
||||
<a
|
||||
href="https://opensource.org/licenses/MIT"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
MIT License
|
||||
</a>
|
||||
</Trans>
|
||||
</p>
|
||||
</VStack>
|
||||
</div>
|
||||
</Modal>
|
||||
);
|
||||
|
@@ -1,5 +1,3 @@
|
||||
import { h } from 'preact';
|
||||
|
||||
export function Icons() {
|
||||
return (
|
||||
<svg
|
||||
@@ -109,6 +107,30 @@ export function Icons() {
|
||||
<symbol id="search" viewBox="0 0 24 24">
|
||||
<path d="M9.5,3A6.5,6.5 0 0,1 16,9.5C16,11.11 15.41,12.59 14.44,13.73L14.71,14H15.5L20.5,19L19,20.5L14,15.5V14.71L13.73,14.44C12.59,15.41 11.11,16 9.5,16A6.5,6.5 0 0,1 3,9.5A6.5,6.5 0 0,1 9.5,3M9.5,5C7,5 5,7 5,9.5C5,12 7,14 9.5,14C12,14 14,12 14,9.5C14,7 12,5 9.5,5Z" />
|
||||
</symbol>
|
||||
<symbol id="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">
|
||||
{/* By Sam Herbert (@sherb), for everyone. More http://goo.gl/7AJzbL */}
|
||||
<g fill="none" fillRule="evenodd" strokeWidth={10}>
|
||||
@@ -162,3 +184,17 @@ export function Icons() {
|
||||
</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>
|
||||
);
|
||||
};
|
||||
|
@@ -1,14 +1,18 @@
|
||||
import { h } from 'preact';
|
||||
import { getHumanDate } from '../utils';
|
||||
import Modal from './Modal';
|
||||
import { HStack, Stack } from './Stack';
|
||||
import { Icon } from './Icons';
|
||||
|
||||
export function ItemTile({
|
||||
item,
|
||||
onClick,
|
||||
onForkBtnClick,
|
||||
onRemoveBtnClick,
|
||||
onToggleVisibilityBtnClick,
|
||||
focusable,
|
||||
inline
|
||||
inline,
|
||||
hasOptions = true
|
||||
}) {
|
||||
return (
|
||||
<div
|
||||
@@ -27,6 +31,13 @@ export function ItemTile({
|
||||
aria-label="Creates a duplicate of this creation (Ctrl/⌘ + F)"
|
||||
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>
|
||||
</button>
|
||||
) : null}
|
||||
@@ -36,13 +47,19 @@ export function ItemTile({
|
||||
aria-label="Remove"
|
||||
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>
|
||||
) : null}
|
||||
</div>
|
||||
<div className="flex flex-v-center">
|
||||
{item.img ? (
|
||||
<div>
|
||||
<div class="d-f">
|
||||
<img
|
||||
class="saved-item-tile__img"
|
||||
height="40"
|
||||
@@ -66,12 +83,32 @@ export function ItemTile({
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
{item.updatedOn ? (
|
||||
{hasOptions && (
|
||||
<div class="saved-item-tile__meta">
|
||||
Last updated:{' '}
|
||||
<time dateTime={item.updatedOn}>{getHumanDate(item.updatedOn)}</time>
|
||||
<HStack justify="space-between">
|
||||
<div>
|
||||
{item.updatedOn ? (
|
||||
<>
|
||||
Last updated:{' '}
|
||||
<time dateTime={item.updatedOn}>
|
||||
{getHumanDate(item.updatedOn)}
|
||||
</time>
|
||||
</>
|
||||
) : null}
|
||||
</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>
|
||||
) : null}
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
52
src/components/Loader.jsx
Normal file
52
src/components/Loader.jsx
Normal 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>
|
||||
);
|
||||
}
|
@@ -54,21 +54,8 @@ export default class Login extends Component {
|
||||
Login with Google
|
||||
</button>
|
||||
</p>
|
||||
<p class="mb-2">
|
||||
<button
|
||||
type="button"
|
||||
onClick={this.login.bind(this)}
|
||||
class="social-login-btn social-login-btn--facebook btn btn-icon btn--big full-width hint--right hint--always"
|
||||
data-auth-provider="facebook"
|
||||
data-hint="You logged in with Facebook last time"
|
||||
>
|
||||
<svg>
|
||||
<use xlinkHref="#fb-icon" />
|
||||
</svg>
|
||||
Login with Facebook (deprecated)
|
||||
</button>
|
||||
</p>
|
||||
<p>Join a community of 50,000+ Developers</p>
|
||||
|
||||
<p>Join a community of 70,000+ Developers</p>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
@@ -2,16 +2,32 @@ import { h } from 'preact';
|
||||
import { Button } from './common';
|
||||
import { Trans, NumberFormat, t } from '@lingui/macro';
|
||||
import { I18n } from '@lingui/react';
|
||||
import { ProBadge } from './ProBadge';
|
||||
import { HStack, Stack } from './Stack';
|
||||
import { Icon } from './Icons';
|
||||
|
||||
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";
|
||||
|
||||
export function MainHeader(props) {
|
||||
export function MainHeader({
|
||||
user,
|
||||
currentItem,
|
||||
titleInputBlurHandler,
|
||||
runBtnClickHandler,
|
||||
assetsBtnHandler,
|
||||
isFileMode,
|
||||
onItemFork,
|
||||
...props
|
||||
}) {
|
||||
const isAutoPreviewOn =
|
||||
window.forcedSettings.autoPreview !== undefined
|
||||
? window.forcedSettings
|
||||
: props.isAutoPreviewOn;
|
||||
|
||||
const isNotMine =
|
||||
currentItem.createdBy && user?.uid !== currentItem.createdBy;
|
||||
|
||||
// console.log(33, currentItem, user?.uid);
|
||||
return (
|
||||
<I18n>
|
||||
{({ i18n }) => (
|
||||
@@ -21,15 +37,15 @@ export function MainHeader(props) {
|
||||
id="titleInput"
|
||||
title="Click to edit"
|
||||
class="item-title-input"
|
||||
value={props.title}
|
||||
onBlur={props.titleInputBlurHandler}
|
||||
value={currentItem.title}
|
||||
onBlur={titleInputBlurHandler}
|
||||
/>
|
||||
<div class="main-header__btn-wrap flex flex-v-center">
|
||||
{!isAutoPreviewOn && (
|
||||
<button
|
||||
class="btn btn btn--dark flex flex-v-center hint--rounded hint--bottom-left"
|
||||
aria-label={i18n._(t`Run preview (Ctrl/⌘ + Shift + 5)`)}
|
||||
onClick={props.runBtnClickHandler}
|
||||
onClick={runBtnClickHandler}
|
||||
>
|
||||
<svg>
|
||||
<use xlinkHref="#play-icon" />
|
||||
@@ -37,8 +53,17 @@ export function MainHeader(props) {
|
||||
<Trans>Run</Trans>
|
||||
</button>
|
||||
)}
|
||||
|
||||
{!props.isFileMode && (
|
||||
<Button
|
||||
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
|
||||
onClick={props.addLibraryBtnHandler}
|
||||
data-event-category="ui"
|
||||
@@ -60,6 +85,38 @@ export function MainHeader(props) {
|
||||
</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
|
||||
class="btn btn--dark hint--rounded hint--bottom-left"
|
||||
aria-label={i18n._(t`Start a new creation`)}
|
||||
@@ -71,22 +128,25 @@ export function MainHeader(props) {
|
||||
</svg>
|
||||
<Trans>New</Trans>
|
||||
</button>
|
||||
<button
|
||||
id="saveBtn"
|
||||
class={`btn btn--dark hint--rounded hint--bottom-left ${
|
||||
props.isSaving ? 'is-loading' : ''
|
||||
} ${props.unsavedEditCount ? 'is-marked' : 0}`}
|
||||
aria-label={i18n._(t`Save current creation (Ctrl/⌘ + S)`)}
|
||||
onClick={props.saveBtnHandler}
|
||||
>
|
||||
<svg viewBox="0 0 24 24">
|
||||
<path d="M15,9H5V5H15M12,19A3,3 0 0,1 9,16A3,3 0 0,1 12,13A3,3 0 0,1 15,16A3,3 0 0,1 12,19M17,3H5C3.89,3 3,3.9 3,5V19A2,2 0 0,0 5,21H19A2,2 0 0,0 21,19V7L17,3Z" />
|
||||
</svg>
|
||||
<svg class="btn-loader" width="15" height="15" stroke="#fff">
|
||||
<use xlinkHref="#loader-icon" />
|
||||
</svg>
|
||||
<Trans>Save</Trans>
|
||||
</button>
|
||||
|
||||
{!isNotMine && (
|
||||
<button
|
||||
id="saveBtn"
|
||||
class={`btn btn--dark hint--rounded hint--bottom-left ${
|
||||
props.isSaving ? 'is-loading' : ''
|
||||
} ${props.unsavedEditCount ? 'is-marked' : 0}`}
|
||||
aria-label={i18n._(t`Save current creation (Ctrl/⌘ + S)`)}
|
||||
onClick={props.saveBtnHandler}
|
||||
>
|
||||
<svg viewBox="0 0 24 24">
|
||||
<path d="M15,9H5V5H15M12,19A3,3 0 0,1 9,16A3,3 0 0,1 12,13A3,3 0 0,1 15,16A3,3 0 0,1 12,19M17,3H5C3.89,3 3,3.9 3,5V19A2,2 0 0,0 5,21H19A2,2 0 0,0 21,19V7L17,3Z" />
|
||||
</svg>
|
||||
<svg class="btn-loader" width="15" height="15" stroke="#fff">
|
||||
<use xlinkHref="#loader-icon" />
|
||||
</svg>
|
||||
<Trans>Save</Trans>
|
||||
</button>
|
||||
)}
|
||||
<button
|
||||
id="openItemsBtn"
|
||||
class={`btn btn--dark hint--rounded hint--bottom-left ${
|
||||
@@ -103,13 +163,13 @@ export function MainHeader(props) {
|
||||
</svg>
|
||||
<Trans>Open</Trans>
|
||||
</button>
|
||||
{!props.user ? (
|
||||
{!user ? (
|
||||
<Button
|
||||
onClick={props.loginBtnHandler}
|
||||
data-event-category="ui"
|
||||
data-event-action="loginButtonClick"
|
||||
data-testid="loginButton"
|
||||
class="btn btn--dark hint--rounded hint--bottom-left"
|
||||
class="btn btn--dark"
|
||||
>
|
||||
<Trans>Login/Signup</Trans>
|
||||
</Button>
|
||||
@@ -121,14 +181,15 @@ export function MainHeader(props) {
|
||||
aria-label={i18n._(t`See profile or Logout`)}
|
||||
class="btn--dark hint--rounded hint--bottom-left"
|
||||
>
|
||||
<img
|
||||
id="headerAvatarImg"
|
||||
width="20"
|
||||
src={
|
||||
props.user ? props.user.photoURL || DEFAULT_PROFILE_IMG : ''
|
||||
}
|
||||
class="main-header__avatar-img"
|
||||
/>
|
||||
<HStack gap={1}>
|
||||
<img
|
||||
id="headerAvatarImg"
|
||||
width="20"
|
||||
src={user ? user.photoURL || DEFAULT_PROFILE_IMG : ''}
|
||||
class="main-header__avatar-img"
|
||||
/>
|
||||
{user?.isPro ? <ProBadge /> : null}
|
||||
</HStack>
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
|
@@ -77,31 +77,47 @@ const Modal = ({
|
||||
if (!show) return null;
|
||||
|
||||
return (
|
||||
<Portal into={`body`}>
|
||||
<div
|
||||
role="dialog"
|
||||
class={`${extraClasses || ''} modal is-modal-visible ${
|
||||
small ? 'modal--small' : ''
|
||||
}`}
|
||||
ref={overlayRef}
|
||||
onClick={onOverlayClick}
|
||||
>
|
||||
<div class="modal__content">
|
||||
{hideCloseButton ? null : (
|
||||
<button
|
||||
type="button"
|
||||
onClick={closeHandler}
|
||||
aria-label="Close modal"
|
||||
data-testid="closeModalButton"
|
||||
title="Close"
|
||||
class="js-modal__close-btn modal__close-btn"
|
||||
>
|
||||
Close
|
||||
</button>
|
||||
)}
|
||||
{children}
|
||||
<Portal into={`#portal`}>
|
||||
<>
|
||||
{/* <div class="modal-overlay" /> */}
|
||||
<div
|
||||
role="dialog"
|
||||
class={`${extraClasses || ''} modal is-modal-visible ${
|
||||
small ? 'modal--small' : ''
|
||||
}
|
||||
${noOverlay ? 'modal--no-overlay' : ''}
|
||||
`}
|
||||
ref={overlayRef}
|
||||
onClick={onOverlayClick}
|
||||
>
|
||||
<div class="modal__content">
|
||||
{hideCloseButton ? null : (
|
||||
<button
|
||||
type="button"
|
||||
onClick={closeHandler}
|
||||
aria-label="Close modal"
|
||||
data-testid="closeModalButton"
|
||||
title="Close"
|
||||
class="js-modal__close-btn dialog__close-btn modal__close-btn"
|
||||
>
|
||||
<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>
|
||||
)}
|
||||
{children}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
</Portal>
|
||||
);
|
||||
};
|
||||
|
@@ -1,84 +1,93 @@
|
||||
import { h } from 'preact';
|
||||
import Modal from './Modal.jsx';
|
||||
import { Stack } from './Stack.jsx';
|
||||
|
||||
export function OnboardingModal(props) {
|
||||
return (
|
||||
<Modal show={props.show} closeHandler={props.closeHandler}>
|
||||
<div class="tac">
|
||||
<svg width="130px" height="50px" aria-hidden="true">
|
||||
<Stack gap={3} justify="center" align="center">
|
||||
<svg width="83" height="32" aria-hidden="true">
|
||||
<use xlinkHref="#logo" />
|
||||
</svg>
|
||||
<h1 style="margin-top:20px">Welcome to Web Maker</h1>
|
||||
</div>
|
||||
<h1>Welcome to Web Maker</h1>
|
||||
</Stack>
|
||||
|
||||
<div class="flex--desk" style="margin-top:40px;">
|
||||
<div class="onboard-step show-when-app hide-on-mobile">
|
||||
<div class="tac">
|
||||
<div
|
||||
class="flex--desk"
|
||||
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">
|
||||
<path d="M13.64,21.97C13.14,22.21 12.54,22 12.31,21.5L10.13,16.76L7.62,18.78C7.45,18.92 7.24,19 7,19A1,1 0 0,1 6,18V3A1,1 0 0,1 7,2C7.24,2 7.47,2.09 7.64,2.23L7.65,2.22L19.14,11.86C19.57,12.22 19.62,12.85 19.27,13.27C19.12,13.45 18.91,13.57 18.7,13.61L15.54,14.23L17.74,18.96C18,19.46 17.76,20.05 17.26,20.28L13.64,21.97Z" />
|
||||
</svg>
|
||||
</div>
|
||||
<p>
|
||||
Open Web Maker anytime by visiting <a>https://webmaker.app/app/</a>{' '}
|
||||
- Even when you are offline! It just works! 😱{' '}
|
||||
<strong>Drag the following bookmarklet</strong> on your bookmark bar
|
||||
to create a quick access shortcut:
|
||||
<a class="ml-1 bookmarklet" href="https://webmaker.app/app/">
|
||||
<svg width="20" height="20" aria-hidden="true">
|
||||
|
||||
<p>
|
||||
Open Web Maker anytime by visiting{' '}
|
||||
<a>https://webmaker.app/create/</a> - Even when you are offline!
|
||||
It just works! 😱 <strong>Drag the following bookmarklet</strong>{' '}
|
||||
on your bookmark bar to create a quick access shortcut:
|
||||
<a class="ml-1 bookmarklet" href="https://webmaker.app/create/">
|
||||
<svg width="20" height="20" aria-hidden="true">
|
||||
<use xlinkHref="#logo" />
|
||||
</svg>
|
||||
Web Maker
|
||||
</a>
|
||||
</p>
|
||||
</Stack>
|
||||
</div>
|
||||
<div class="onboard-step onboard-step--full-width show-when-extension">
|
||||
<Stack gap={1} align="center">
|
||||
<svg class="onboard-step__icon" viewBox="0 0 24 24">
|
||||
<path d="M13.64,21.97C13.14,22.21 12.54,22 12.31,21.5L10.13,16.76L7.62,18.78C7.45,18.92 7.24,19 7,19A1,1 0 0,1 6,18V3A1,1 0 0,1 7,2C7.24,2 7.47,2.09 7.64,2.23L7.65,2.22L19.14,11.86C19.57,12.22 19.62,12.85 19.27,13.27C19.12,13.45 18.91,13.57 18.7,13.61L15.54,14.23L17.74,18.96C18,19.46 17.76,20.05 17.26,20.28L13.64,21.97Z" />
|
||||
</svg>
|
||||
|
||||
<p>
|
||||
Open Web Maker anytime by clicking the
|
||||
<svg class="relative" style="top:5px;" width="40" height="30">
|
||||
<use xlinkHref="#logo" />
|
||||
</svg>{' '}
|
||||
button in top-right side of your browser.
|
||||
</p>
|
||||
</Stack>
|
||||
</div>
|
||||
|
||||
<div class="onboard-step">
|
||||
<Stack gap={1} align="center">
|
||||
<svg class="onboard-step__icon" viewBox="0 0 24 24">
|
||||
<use xlinkHref="#settings-icon" />
|
||||
</svg>
|
||||
|
||||
<p>
|
||||
Configure and customize settings by clicking the gear icon (
|
||||
<svg
|
||||
style="width:18px;height:18px;position:relative;top:3px;fill:#888"
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<use xlinkHref="#settings-icon" />
|
||||
</svg>
|
||||
Web Maker
|
||||
</a>
|
||||
</p>
|
||||
</div>
|
||||
<div class="onboard-step show-when-extension">
|
||||
<div class="tac">
|
||||
<svg class="onboard-step__icon" viewBox="0 0 24 24">
|
||||
<path d="M13.64,21.97C13.14,22.21 12.54,22 12.31,21.5L10.13,16.76L7.62,18.78C7.45,18.92 7.24,19 7,19A1,1 0 0,1 6,18V3A1,1 0 0,1 7,2C7.24,2 7.47,2.09 7.64,2.23L7.65,2.22L19.14,11.86C19.57,12.22 19.62,12.85 19.27,13.27C19.12,13.45 18.91,13.57 18.7,13.61L15.54,14.23L17.74,18.96C18,19.46 17.76,20.05 17.26,20.28L13.64,21.97Z" />
|
||||
</svg>
|
||||
</div>
|
||||
<p>
|
||||
Open Web Maker anytime by clicking the
|
||||
<svg class="relative" style="top:5px;" width="40" height="30">
|
||||
<use xlinkHref="#logo" />
|
||||
</svg>{' '}
|
||||
button in top-right side of your browser.
|
||||
</p>
|
||||
) in bottom right of the app.
|
||||
</p>
|
||||
</Stack>
|
||||
</div>
|
||||
<div class="onboard-step">
|
||||
<div class="tac">
|
||||
<svg class="onboard-step__icon" viewBox="0 0 24 24">
|
||||
<use xlinkHref="#settings-icon" />
|
||||
</svg>
|
||||
</div>
|
||||
<p>
|
||||
Configure and customize settings by clicking the gear icon (
|
||||
<svg
|
||||
style="width:18px;height:18px;position:relative;top:3px;fill:#888"
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<use xlinkHref="#settings-icon" />
|
||||
</svg>
|
||||
) in bottom right of the app.
|
||||
</p>
|
||||
</div>
|
||||
<div class="onboard-step">
|
||||
<div class="tac">
|
||||
<Stack gap={1} align="center">
|
||||
<svg class="onboard-step__icon" style="stroke-width:0.3px;">
|
||||
<use xlinkHref="#twitter-icon" />
|
||||
</svg>
|
||||
</div>
|
||||
<p>
|
||||
Follow{' '}
|
||||
<a
|
||||
href="https://twitter.com/intent/follow?screen_name=webmakerApp"
|
||||
targe="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
@webmakerApp
|
||||
</a>{' '}
|
||||
to know about the new upcoming features!
|
||||
</p>
|
||||
|
||||
<p>
|
||||
Follow{' '}
|
||||
<a
|
||||
href="https://twitter.com/intent/follow?screen_name=webmakerApp"
|
||||
targe="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
@webmakerApp
|
||||
</a>{' '}
|
||||
to know about the new upcoming features!
|
||||
</p>
|
||||
</Stack>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -95,11 +104,11 @@ export function OnboardingModal(props) {
|
||||
.
|
||||
</p>
|
||||
|
||||
<p class="tac">
|
||||
<Stack justify="center">
|
||||
<button class="btn btn--primary" onClick={props.closeHandler}>
|
||||
Lets start!
|
||||
</button>
|
||||
</p>
|
||||
</Stack>
|
||||
</Modal>
|
||||
);
|
||||
}
|
||||
|
31
src/components/Panel.jsx
Normal file
31
src/components/Panel.jsx
Normal 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
157
src/components/Pro.jsx
Normal 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=" "
|
||||
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>
|
||||
);
|
||||
};
|
3
src/components/ProBadge.jsx
Normal file
3
src/components/ProBadge.jsx
Normal file
@@ -0,0 +1,3 @@
|
||||
export const ProBadge = () => {
|
||||
return <div className="badge pro-badge">PRO</div>;
|
||||
};
|
31
src/components/ProOnAppModal.js
Normal file
31
src/components/ProOnAppModal.js
Normal 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>
|
||||
);
|
||||
}
|
@@ -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 =
|
||||
"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 (
|
||||
<div class="tac">
|
||||
<img
|
||||
height="80"
|
||||
class="profile-modal__avatar-img"
|
||||
src={user ? user.photoURL || DEFAULT_PROFILE_IMG : ''}
|
||||
id="profileAvatarImg"
|
||||
alt="Profile image"
|
||||
/>
|
||||
<h3 id="profileUserName" class="mb-2">
|
||||
{user && user.displayName ? user.displayName : 'Anonymous Creator'}
|
||||
</h3>
|
||||
<p>
|
||||
<button
|
||||
class="btn"
|
||||
aria-label="Logout from your account"
|
||||
onClick={logoutBtnHandler}
|
||||
>
|
||||
Logout
|
||||
</button>
|
||||
</p>
|
||||
</div>
|
||||
<Stack gap={5}>
|
||||
<Stack gap={2} align="center">
|
||||
<img
|
||||
height="80"
|
||||
class={`profile-modal__avatar-img ${user?.isPro ? 'is-pro' : ''}`}
|
||||
src={user ? user.photoURL || DEFAULT_PROFILE_IMG : ''}
|
||||
id="profileAvatarImg"
|
||||
alt="Profile image"
|
||||
/>
|
||||
|
||||
<VStack gap={1} align="flex-start">
|
||||
<h3
|
||||
class={`profile-modal__name ${user?.isPro ? 's-pro' : ''}`}
|
||||
id="profileUserName"
|
||||
>
|
||||
{user && user.displayName ? user.displayName : 'Anonymous Creator'}
|
||||
</h3>
|
||||
{user.isPro && <ProBadge />}
|
||||
</VStack>
|
||||
</Stack>
|
||||
|
||||
<button
|
||||
class="btn btn--primary"
|
||||
aria-label="Logout from your account"
|
||||
onClick={logoutBtnHandler}
|
||||
>
|
||||
Logout
|
||||
</button>
|
||||
</Stack>
|
||||
);
|
||||
};
|
||||
|
||||
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>
|
||||
);
|
||||
}
|
||||
|
@@ -157,18 +157,29 @@ export default function SavedItemPane({
|
||||
>
|
||||
<button
|
||||
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"
|
||||
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>
|
||||
<div
|
||||
class="flex flex-v-center"
|
||||
style="justify-content: space-between;"
|
||||
>
|
||||
<h3>
|
||||
<Trans>My Library ({filteredItems.length})</Trans>
|
||||
<Trans>My Library</Trans> ({filteredItems.length})
|
||||
</h3>
|
||||
|
||||
<div>
|
||||
@@ -215,6 +226,9 @@ export default function SavedItemPane({
|
||||
onClick={() => itemClickHandler(item)}
|
||||
onForkBtnClick={e => itemForkBtnClickHandler(item, e)}
|
||||
onRemoveBtnClick={e => itemRemoveBtnClickHandler(item, e)}
|
||||
onToggleVisibilityBtnClick={e =>
|
||||
itemVisibilityToggleHandler(item, e)
|
||||
}
|
||||
/>
|
||||
))}
|
||||
{!items.length ? (
|
||||
@@ -224,7 +238,7 @@ export default function SavedItemPane({
|
||||
</h2>
|
||||
<img
|
||||
style="max-width: 80%; opacity:0.4"
|
||||
src="assets/empty.svg"
|
||||
src="./assets/empty.svg"
|
||||
/>
|
||||
</div>
|
||||
) : null}
|
||||
|
149
src/components/Share.jsx
Normal file
149
src/components/Share.jsx
Normal 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>
|
||||
);
|
||||
}
|
3
src/components/Skeleton.jsx
Normal file
3
src/components/Skeleton.jsx
Normal file
@@ -0,0 +1,3 @@
|
||||
export const Skeleton = ({ width }) => {
|
||||
return <div className="skeleton" style={{ width }}></div>;
|
||||
};
|
54
src/components/Stack.jsx
Normal file
54
src/components/Stack.jsx
Normal 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 };
|
@@ -1,5 +1,3 @@
|
||||
import { h } from 'preact';
|
||||
|
||||
export default function Switch({
|
||||
checked,
|
||||
onChange,
|
||||
|
68
src/components/Text.jsx
Normal file
68
src/components/Text.jsx
Normal 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>
|
||||
);
|
||||
}
|
||||
);
|
@@ -2,11 +2,12 @@
|
||||
*/
|
||||
|
||||
import { h, Component } from 'preact';
|
||||
import { route } from 'preact-router';
|
||||
// import '../service-worker-registration';
|
||||
import { MainHeader } from './MainHeader.jsx';
|
||||
import ContentWrap from './ContentWrap.jsx';
|
||||
import ContentWrapFiles from './ContentWrapFiles.jsx';
|
||||
import Footer from './Footer.jsx';
|
||||
import { Footer } from './Footer.jsx';
|
||||
import SavedItemPane from './SavedItemPane.jsx';
|
||||
import AddLibrary from './AddLibrary.jsx';
|
||||
import Modal from './Modal.jsx';
|
||||
@@ -22,7 +23,8 @@ import {
|
||||
getCompleteHtml,
|
||||
getFilenameFromUrl,
|
||||
prettify,
|
||||
sanitizeSplitSizes
|
||||
sanitizeSplitSizes,
|
||||
persistAuthUserLocally
|
||||
} from '../utils';
|
||||
import {
|
||||
linearizeFiles,
|
||||
@@ -35,7 +37,7 @@ import {
|
||||
|
||||
import { itemService } from '../itemService';
|
||||
import '../db';
|
||||
import { Notifications } from './Notifications';
|
||||
import { Changelog } from './Changelog';
|
||||
import Settings from './Settings.jsx';
|
||||
import { modes, HtmlModes, CssModes, JsModes } from '../codeModes';
|
||||
import { trackEvent } from '../analytics';
|
||||
@@ -68,14 +70,21 @@ import {
|
||||
import { commandPaletteService } from '../commandPaletteService';
|
||||
|
||||
import { I18nProvider } from '@lingui/react';
|
||||
import { Assets } from './Assets.jsx';
|
||||
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) {
|
||||
require('preact/debug');
|
||||
}
|
||||
|
||||
const UNSAVED_WARNING_COUNT = 15;
|
||||
const version = '5.2.0';
|
||||
const version = '6.1.0';
|
||||
|
||||
// Read forced settings as query parameters
|
||||
window.forcedSettings = {};
|
||||
@@ -98,10 +107,19 @@ if (location.search) {
|
||||
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 {
|
||||
constructor() {
|
||||
super();
|
||||
this.AUTO_SAVE_INTERVAL = 15000; // 15 seconds
|
||||
const savedUser = window.localStorage.getItem('user');
|
||||
this.modalDefaultStates = {
|
||||
isModalOpen: false,
|
||||
isAddLibraryModalOpen: false,
|
||||
@@ -116,7 +134,12 @@ export default class App extends Component {
|
||||
isOnboardModalOpen: false,
|
||||
isJs13KModalOpen: false,
|
||||
isCreateNewModalOpen: false,
|
||||
isCommandPaletteOpen: false
|
||||
isCommandPaletteOpen: false,
|
||||
isAssetsOpen: false,
|
||||
isShareModalOpen: false,
|
||||
isProModalOpen: false,
|
||||
isFilesLimitModalOpen: false,
|
||||
isProOnAppModalOpen: false
|
||||
};
|
||||
this.state = {
|
||||
isSavedItemPaneOpen: false,
|
||||
@@ -128,7 +151,8 @@ export default class App extends Component {
|
||||
html: window.codeHtml,
|
||||
css: window.codeCss
|
||||
},
|
||||
catalogs: {}
|
||||
catalogs: {},
|
||||
user: savedUser
|
||||
};
|
||||
this.defaultSettings = {
|
||||
preserveLastCode: true,
|
||||
@@ -161,13 +185,35 @@ export default class App extends Component {
|
||||
};
|
||||
this.prefs = {};
|
||||
|
||||
onAuthStateChanged(auth, user => {
|
||||
if (savedUser) {
|
||||
window.user = savedUser;
|
||||
}
|
||||
|
||||
onAuthStateChanged(auth, authUser => {
|
||||
this.setState({ isLoginModalOpen: false });
|
||||
if (user) {
|
||||
log('You are -> ', user);
|
||||
if (authUser) {
|
||||
log('You are -> ', authUser);
|
||||
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]) {
|
||||
this.fetchItems(false, true).then(items => {
|
||||
if (!items.length) {
|
||||
@@ -181,12 +227,23 @@ export default class App extends Component {
|
||||
trackEvent('ui', 'askToImportModalSeen');
|
||||
});
|
||||
}
|
||||
window.db.getUser(user.uid).then(customUser => {
|
||||
|
||||
window.db.getUser(authUser.uid).then(customUser => {
|
||||
if (customUser) {
|
||||
const prefs = { ...this.state.prefs };
|
||||
Object.assign(prefs, user.settings);
|
||||
this.setState({ prefs }, this.updateSetting);
|
||||
Object.assign(prefs, customUser.settings);
|
||||
|
||||
// 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 {
|
||||
// User is signed out.
|
||||
@@ -234,6 +291,18 @@ export default class App extends Component {
|
||||
this.setCurrentItem(this.state.currentItem).then(() => {
|
||||
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) {
|
||||
this.setState({ unsavedEditCount: 0 });
|
||||
log('Load last unsaved item', lastCode);
|
||||
@@ -267,8 +336,8 @@ export default class App extends Component {
|
||||
semverCompare(lastSeenVersion, version) === -1 &&
|
||||
!window.localStorage.pledgeModalSeen
|
||||
) {
|
||||
this.openSupportDeveloperModal();
|
||||
window.localStorage.pledgeModalSeen = true;
|
||||
// this.openSupportDeveloperModal();
|
||||
// window.localStorage.pledgeModalSeen = true;
|
||||
}
|
||||
|
||||
if (!lastSeenVersion || semverCompare(lastSeenVersion, version) === -1) {
|
||||
@@ -346,9 +415,12 @@ export default class App extends Component {
|
||||
}
|
||||
const fork = JSON.parse(JSON.stringify(sourceItem));
|
||||
delete fork.id;
|
||||
delete fork.createdBy;
|
||||
delete fork.isPublic;
|
||||
fork.title = '(Forked) ' + sourceItem.title;
|
||||
fork.updatedOn = Date.now();
|
||||
this.setCurrentItem(fork).then(() => this.refreshEditor());
|
||||
customRoute('/create');
|
||||
alertsService.add(`"${sourceItem.title}" was forked`);
|
||||
trackEvent('fn', 'itemForked');
|
||||
}
|
||||
@@ -408,10 +480,12 @@ export default class App extends Component {
|
||||
};
|
||||
}
|
||||
this.setCurrentItem(item).then(() => this.refreshEditor());
|
||||
customRoute('/create');
|
||||
alertsService.add('New item created');
|
||||
}
|
||||
openItem(item) {
|
||||
this.setCurrentItem(item).then(() => this.refreshEditor());
|
||||
customRoute(`/create/${item.id}`);
|
||||
alertsService.add('Saved item loaded');
|
||||
}
|
||||
removeItem(item) {
|
||||
@@ -538,6 +612,9 @@ export default class App extends Component {
|
||||
openAddLibrary() {
|
||||
this.setState({ isAddLibraryModalOpen: true });
|
||||
}
|
||||
assetsBtnClickHandler() {
|
||||
this.setState({ isAssetsOpen: true });
|
||||
}
|
||||
closeSavedItemsPane() {
|
||||
this.setState({
|
||||
isSavedItemPaneOpen: false
|
||||
@@ -556,6 +633,7 @@ export default class App extends Component {
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
console.log('itemId', this.props.itemId);
|
||||
function setBodySize() {
|
||||
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');
|
||||
}
|
||||
var isNewItem = !this.state.currentItem.id;
|
||||
this.state.currentItem.id =
|
||||
this.state.currentItem.id || 'item-' + generateRandomId();
|
||||
this.state.currentItem.id = this.state.currentItem.id || 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({
|
||||
isSaving: true
|
||||
});
|
||||
@@ -952,14 +1038,17 @@ export default class App extends Component {
|
||||
}
|
||||
|
||||
titleInputBlurHandler(e) {
|
||||
this.setState({
|
||||
currentItem: { ...this.state.currentItem, title: e.target.value }
|
||||
});
|
||||
|
||||
if (this.state.currentItem.id) {
|
||||
this.saveItem();
|
||||
trackEvent('ui', 'titleChanged');
|
||||
}
|
||||
this.setState(
|
||||
{
|
||||
currentItem: { ...this.state.currentItem, title: e.target.value }
|
||||
},
|
||||
() => {
|
||||
if (this.state.currentItem.id) {
|
||||
this.saveItem();
|
||||
trackEvent('ui', 'titleChanged');
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1045,6 +1134,7 @@ export default class App extends Component {
|
||||
trackEvent('fn', 'loggedOut');
|
||||
authh.logout();
|
||||
this.setState({ isProfileModalOpen: false });
|
||||
this.createNewItem();
|
||||
alertsService.add('Log out successfull');
|
||||
}
|
||||
|
||||
@@ -1087,6 +1177,14 @@ export default class App extends Component {
|
||||
trackEvent('ui', 'openBtnClick');
|
||||
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() {
|
||||
trackEvent('ui', 'detachPreviewBtnClick');
|
||||
|
||||
@@ -1095,7 +1193,7 @@ export default class App extends Component {
|
||||
notificationsBtnClickHandler() {
|
||||
this.setState({ isNotificationsModalOpen: true });
|
||||
|
||||
if (this.state.isNotificationsModalOpen && !this.hasSeenNotifications) {
|
||||
if (!this.state.isNotificationsModalOpen && !this.hasSeenNotifications) {
|
||||
this.hasSeenNotifications = true;
|
||||
this.setState({ hasUnseenChangelog: false });
|
||||
window.db.setUserLastSeenVersion(version);
|
||||
@@ -1103,6 +1201,15 @@ export default class App extends Component {
|
||||
trackEvent('ui', 'notificationButtonClick', version);
|
||||
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) {
|
||||
if (this.state.currentItem.cssMode === CssModes.ACSS) {
|
||||
alert(
|
||||
@@ -1443,9 +1550,11 @@ export default class App extends Component {
|
||||
this.setState({ isCreateNewModalOpen: false });
|
||||
} else {
|
||||
trackEvent('ui', 'FileModeCreationLimitMessageSeen');
|
||||
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.'
|
||||
);
|
||||
// this.closeAllOverlays();
|
||||
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
|
||||
if (typeof what === 'string') {
|
||||
prettify({
|
||||
content: this.state.currentItem[what],
|
||||
content: this.state.currentItem[what] || '',
|
||||
type: { html: 'html', js: 'js', css: 'css' }[what]
|
||||
}).then(formattedContent => {
|
||||
if (this.state.currentItem[what] === formattedContent) {
|
||||
@@ -1599,6 +1708,7 @@ export default class App extends Component {
|
||||
...this.state.currentItem,
|
||||
files: [...this.state.currentItem.files]
|
||||
};
|
||||
|
||||
prettify({ file: selectedFile }).then(formattedContent => {
|
||||
if (formattedContent !== selectedFile.content) {
|
||||
selectedFile.content = formattedContent;
|
||||
@@ -1621,10 +1731,12 @@ export default class App extends Component {
|
||||
loginBtnHandler={this.loginBtnClickHandler.bind(this)}
|
||||
profileBtnHandler={this.profileBtnClickHandler.bind(this)}
|
||||
addLibraryBtnHandler={this.openAddLibrary.bind(this)}
|
||||
assetsBtnHandler={this.assetsBtnClickHandler.bind(this)}
|
||||
shareBtnHandler={this.shareBtnClickHandler.bind(this)}
|
||||
runBtnClickHandler={this.runBtnClickHandler.bind(this)}
|
||||
isFetchingItems={this.state.isFetchingItems}
|
||||
isSaving={this.state.isSaving}
|
||||
title={this.state.currentItem.title}
|
||||
currentItem={this.state.currentItem}
|
||||
titleInputBlurHandler={this.titleInputBlurHandler.bind(this)}
|
||||
user={this.state.user}
|
||||
isAutoPreviewOn={this.state.prefs.autoPreview}
|
||||
@@ -1632,6 +1744,9 @@ export default class App extends Component {
|
||||
isFileMode={
|
||||
this.state.currentItem && this.state.currentItem.files
|
||||
}
|
||||
onItemFork={() => {
|
||||
this.forkItem(this.state.currentItem);
|
||||
}}
|
||||
/>
|
||||
{this.state.currentItem && this.state.currentItem.files ? (
|
||||
<ContentWrapFiles
|
||||
@@ -1667,6 +1782,7 @@ export default class App extends Component {
|
||||
|
||||
<Footer
|
||||
prefs={this.state.prefs}
|
||||
user={this.state.user}
|
||||
layoutBtnClickHandler={this.layoutBtnClickHandler.bind(this)}
|
||||
helpBtnClickHandler={() =>
|
||||
this.setState({ isHelpModalOpen: true })
|
||||
@@ -1693,11 +1809,11 @@ export default class App extends Component {
|
||||
onJs13KDownloadBtnClick={this.js13KDownloadBtnClickHandler.bind(
|
||||
this
|
||||
)}
|
||||
proBtnClickHandler={this.proBtnClickHandler.bind(this)}
|
||||
hasUnseenChangelog={this.state.hasUnseenChangelog}
|
||||
codeSize={this.state.codeSize}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<SavedItemPane
|
||||
itemsMap={this.state.savedItems}
|
||||
isOpen={this.state.isSavedItemPaneOpen}
|
||||
@@ -1708,9 +1824,7 @@ export default class App extends Component {
|
||||
onExport={this.exportBtnClickHandler.bind(this)}
|
||||
mergeImportedItems={this.mergeImportedItems.bind(this)}
|
||||
/>
|
||||
|
||||
<Alerts />
|
||||
|
||||
<Modal
|
||||
show={this.state.isAddLibraryModalOpen}
|
||||
closeHandler={() => this.setState({ isAddLibraryModalOpen: false })}
|
||||
@@ -1735,7 +1849,7 @@ export default class App extends Component {
|
||||
this.setState({ isNotificationsModalOpen: false })
|
||||
}
|
||||
>
|
||||
<Notifications
|
||||
<Changelog
|
||||
onSupportBtnClick={this.openSupportDeveloperModal.bind(this)}
|
||||
/>
|
||||
</Modal>
|
||||
@@ -1749,13 +1863,6 @@ export default class App extends Component {
|
||||
onChange={this.updateSetting.bind(this)}
|
||||
/>
|
||||
</Modal>
|
||||
<Modal
|
||||
extraClasses="login-modal"
|
||||
show={this.state.isLoginModalOpen}
|
||||
closeHandler={() => this.setState({ isLoginModalOpen: false })}
|
||||
>
|
||||
<Login />
|
||||
</Modal>
|
||||
<Modal
|
||||
show={this.state.isProfileModalOpen}
|
||||
closeHandler={() => this.setState({ isProfileModalOpen: false })}
|
||||
@@ -1765,6 +1872,73 @@ export default class App extends Component {
|
||||
logoutBtnHandler={this.logout.bind(this)}
|
||||
/>
|
||||
</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
|
||||
show={this.state.isHelpModalOpen}
|
||||
closeHandler={() => this.setState({ isHelpModalOpen: false })}
|
||||
@@ -1794,17 +1968,14 @@ export default class App extends Component {
|
||||
)}
|
||||
dontAskBtnClickHandler={this.dontAskToImportAnymore.bind(this)}
|
||||
/>
|
||||
|
||||
<OnboardingModal
|
||||
show={this.state.isOnboardModalOpen}
|
||||
closeHandler={() => this.setState({ isOnboardModalOpen: false })}
|
||||
/>
|
||||
|
||||
<Js13KModal
|
||||
show={this.state.isJs13KModalOpen}
|
||||
closeHandler={() => this.setState({ isJs13KModalOpen: false })}
|
||||
/>
|
||||
|
||||
<CreateNewModal
|
||||
show={this.state.isCreateNewModalOpen}
|
||||
closeHandler={() => this.setState({ isCreateNewModalOpen: false })}
|
||||
@@ -1817,21 +1988,38 @@ export default class App extends Component {
|
||||
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
|
||||
show={this.state.isCommandPaletteOpen}
|
||||
files={linearizeFiles(this.state.currentItem.files || [])}
|
||||
isCommandMode={this.state.isCommandPaletteInCommandMode}
|
||||
closeHandler={() => this.setState({ isCommandPaletteOpen: false })}
|
||||
/>
|
||||
|
||||
<Portal into="#portal">
|
||||
<div
|
||||
class="modal-overlay"
|
||||
onClick={this.modalOverlayClickHandler.bind(this)}
|
||||
/>
|
||||
</Portal>
|
||||
|
||||
<Icons />
|
||||
<form
|
||||
style="display:none;"
|
||||
|
@@ -4,11 +4,15 @@ import { trackEvent } from '../analytics';
|
||||
class Clickable extends Component {
|
||||
handleClick(e) {
|
||||
const el = e.currentTarget;
|
||||
trackEvent(
|
||||
el.getAttribute('data-event-category'),
|
||||
el.getAttribute('data-event-action')
|
||||
);
|
||||
this.props.onClick(e);
|
||||
if (el.getAttribute('data-event-category')) {
|
||||
trackEvent(
|
||||
el.getAttribute('data-event-category'),
|
||||
el.getAttribute('data-event-action')
|
||||
);
|
||||
}
|
||||
if (this.props.onClick) {
|
||||
this.props.onClick(e);
|
||||
}
|
||||
}
|
||||
render() {
|
||||
/* eslint-disable no-unused-vars */
|
||||
@@ -38,5 +42,5 @@ export function Divider(props) {
|
||||
}
|
||||
|
||||
export function BetaTag() {
|
||||
return <span class="beta-tag">Beta</span>;
|
||||
return <span class="badge beta-tag">Beta</span>;
|
||||
}
|
||||
|
59
src/db.js
59
src/db.js
@@ -6,6 +6,24 @@ import { deferred } from './deferred';
|
||||
import { trackEvent } from './analytics';
|
||||
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;
|
||||
|
||||
@@ -129,15 +147,28 @@ import { log } from './utils';
|
||||
|
||||
return getDoc(doc(remoteDb, `users/${userId}`)).then(doc => {
|
||||
if (!doc.exists()) {
|
||||
return setDoc(doc(remoteDb, `users/${userId}`), {}, { merge: true });
|
||||
// return setDoc(doc(remoteDb, `users/${userId}`), {}, { merge: true });
|
||||
return {};
|
||||
}
|
||||
|
||||
const user = doc.data();
|
||||
Object.assign(window.user, user);
|
||||
// Object.assign(window.user, 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.
|
||||
// This isn't hitting the remote db because remote settings
|
||||
// get fetch asynchronously (in user/) and update the envioronment.
|
||||
@@ -151,12 +182,36 @@ import { log } from './utils';
|
||||
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 = {
|
||||
getDb,
|
||||
getUser,
|
||||
getUserLastSeenVersion,
|
||||
setUserLastSeenVersion,
|
||||
getSettings,
|
||||
fetchItem,
|
||||
getPublicItemCount,
|
||||
getUserSubscriptionEvents,
|
||||
local: dbLocalAlias,
|
||||
sync: dbSyncAlias
|
||||
};
|
||||
|
20
src/hooks/useCheckout.js
Normal file
20
src/hooks/useCheckout.js
Normal 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 };
|
@@ -1,4 +1,4 @@
|
||||
<!DOCTYPE html>
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
@@ -11,11 +11,12 @@
|
||||
rel="manifest"
|
||||
href="<%= htmlWebpackPlugin.files.publicPath %>manifest.json"
|
||||
/>
|
||||
<% if (htmlWebpackPlugin.options.manifest.theme_color) { %>
|
||||
<meta
|
||||
name="theme-color"
|
||||
content="<%= htmlWebpackPlugin.options.manifest.theme_color %>"
|
||||
/>
|
||||
<% if (cli.env.isProd) { %>
|
||||
<base href="/create/" />
|
||||
<% } else { %>
|
||||
<base href="/" />
|
||||
<% } %> <% if (cli.manifest.theme_color) { %>
|
||||
<meta name="theme-color" content="<%= cli.manifest.theme_color %>" />
|
||||
<% } %>
|
||||
|
||||
<style>
|
||||
@@ -38,7 +39,7 @@
|
||||
<link
|
||||
rel="stylesheet"
|
||||
id="editorThemeLinkTag"
|
||||
href="lib/codemirror/theme/monokai.css"
|
||||
href="./lib/codemirror/theme/monokai.css"
|
||||
/>
|
||||
|
||||
<style id="fontStyleTemplate" type="template">
|
||||
@@ -73,13 +74,13 @@
|
||||
<body>
|
||||
<div id="root"></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>
|
||||
</html>
|
12
src/index.js
12
src/index.js
@@ -1,3 +1,4 @@
|
||||
import Router from 'preact-router';
|
||||
import App from './components/app.jsx';
|
||||
|
||||
import './lib/codemirror/lib/codemirror.css';
|
||||
@@ -8,4 +9,13 @@ import './lib/hint.min.css';
|
||||
import './lib/inlet.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>
|
||||
);
|
||||
}
|
||||
|
@@ -1,6 +1,6 @@
|
||||
<script>
|
||||
function callback(e) {
|
||||
console.log('post message recvd', e.data);
|
||||
// console.log('post message recvd', e.data);
|
||||
window.document.open();
|
||||
|
||||
const { contents } = e.data;
|
||||
|
@@ -59,8 +59,6 @@ export const itemService = {
|
||||
|
||||
if (window.user && !shouldFetchLocally) {
|
||||
var remoteDb = await window.db.getDb();
|
||||
// console.log(query, window.user.uid);
|
||||
// d.resolve([]);
|
||||
|
||||
const q = query(
|
||||
collection(remoteDb, 'items'),
|
||||
|
@@ -1,5 +1,5 @@
|
||||
// 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:
|
||||
@@ -349,6 +349,10 @@ CodeMirror.defineMode("coffeescript", function(conf, parserConf) {
|
||||
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/coffeescript", "coffeescript");
|
||||
|
||||
|
331
src/lib/codemirror/mode/css/css.js
vendored
331
src/lib/codemirror/mode/css/css.js
vendored
@@ -1,5 +1,5 @@
|
||||
// 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) {
|
||||
if (typeof exports == "object" && typeof module == "object") // CommonJS
|
||||
@@ -28,7 +28,9 @@ CodeMirror.defineMode("css", function(config, parserConfig) {
|
||||
colorKeywords = parserConfig.colorKeywords || {},
|
||||
valueKeywords = parserConfig.valueKeywords || {},
|
||||
allowNested = parserConfig.allowNested,
|
||||
supportsAtComponent = parserConfig.supportsAtComponent === true;
|
||||
lineComment = parserConfig.lineComment,
|
||||
supportsAtComponent = parserConfig.supportsAtComponent === true,
|
||||
highlightNonStandardPropertyKeywords = config.highlightNonStandardPropertyKeywords !== false;
|
||||
|
||||
var type, override;
|
||||
function ret(style, tp) { type = tp; return style; }
|
||||
@@ -62,7 +64,7 @@ CodeMirror.defineMode("css", function(config, parserConfig) {
|
||||
if (/[\d.]/.test(stream.peek())) {
|
||||
stream.eatWhile(/[\w.%]/);
|
||||
return ret("number", "unit");
|
||||
} else if (stream.match(/^-[\w\\\-]+/)) {
|
||||
} else if (stream.match(/^-[\w\\\-]*/)) {
|
||||
stream.eatWhile(/[\w\\\-]/);
|
||||
if (stream.match(/^\s*:/, false))
|
||||
return ret("variable-2", "variable-definition");
|
||||
@@ -76,12 +78,11 @@ CodeMirror.defineMode("css", function(config, parserConfig) {
|
||||
return ret("qualifier", "qualifier");
|
||||
} else if (/[:;{}\[\]\(\)]/.test(ch)) {
|
||||
return ret(null, ch);
|
||||
} else if ((ch == "u" && stream.match(/rl(-prefix)?\(/)) ||
|
||||
(ch == "d" && stream.match("omain(")) ||
|
||||
(ch == "r" && stream.match("egexp("))) {
|
||||
stream.backUp(1);
|
||||
state.tokenize = tokenParenthesized;
|
||||
return ret("property", "word");
|
||||
} else if (stream.match(/^[\w-.]+(?=\()/)) {
|
||||
if (/^(url(-prefix)?|domain|regexp)$/i.test(stream.current())) {
|
||||
state.tokenize = tokenParenthesized;
|
||||
}
|
||||
return ret("variable callee", "variable");
|
||||
} else if (/[\w\\\-]/.test(ch)) {
|
||||
stream.eatWhile(/[\w\\\-]/);
|
||||
return ret("property", "word");
|
||||
@@ -107,7 +108,7 @@ CodeMirror.defineMode("css", function(config, parserConfig) {
|
||||
|
||||
function tokenParenthesized(stream, state) {
|
||||
stream.next(); // Must be '('
|
||||
if (!stream.match(/\s*[\"\')]/, false))
|
||||
if (!stream.match(/^\s*[\"\')]/, false))
|
||||
state.tokenize = tokenString(")");
|
||||
else
|
||||
state.tokenize = null;
|
||||
@@ -161,16 +162,16 @@ CodeMirror.defineMode("css", function(config, parserConfig) {
|
||||
return pushContext(state, stream, "block");
|
||||
} else if (type == "}" && state.context.prev) {
|
||||
return popContext(state);
|
||||
} else if (supportsAtComponent && /@component/.test(type)) {
|
||||
} else if (supportsAtComponent && /@component/i.test(type)) {
|
||||
return pushContext(state, stream, "atComponentBlock");
|
||||
} else if (/^@(-moz-)?document$/.test(type)) {
|
||||
} else if (/^@(-moz-)?document$/i.test(type)) {
|
||||
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");
|
||||
} else if (/^@(font-face|counter-style)/.test(type)) {
|
||||
} else if (/^@(font-face|counter-style)/i.test(type)) {
|
||||
state.stateArg = type;
|
||||
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";
|
||||
} else if (type && type.charAt(0) == "@") {
|
||||
return pushContext(state, stream, "at");
|
||||
@@ -197,7 +198,7 @@ CodeMirror.defineMode("css", function(config, parserConfig) {
|
||||
override = "property";
|
||||
return "maybeprop";
|
||||
} else if (nonStandardPropertyKeywords.hasOwnProperty(word)) {
|
||||
override = "string-2";
|
||||
override = highlightNonStandardPropertyKeywords ? "string-2" : "property";
|
||||
return "maybeprop";
|
||||
} else if (allowNested) {
|
||||
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 == "(") 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";
|
||||
} else if (type == "word") {
|
||||
wordAsValue(stream);
|
||||
@@ -253,6 +254,8 @@ CodeMirror.defineMode("css", function(config, parserConfig) {
|
||||
};
|
||||
|
||||
states.pseudo = function(type, stream, state) {
|
||||
if (type == "meta") return "pseudo";
|
||||
|
||||
if (type == "word") {
|
||||
override = "variable-3";
|
||||
return state.context.type;
|
||||
@@ -289,7 +292,7 @@ CodeMirror.defineMode("css", function(config, parserConfig) {
|
||||
else if (propertyKeywords.hasOwnProperty(word))
|
||||
override = "property";
|
||||
else if (nonStandardPropertyKeywords.hasOwnProperty(word))
|
||||
override = "string-2";
|
||||
override = highlightNonStandardPropertyKeywords ? "string-2" : "property";
|
||||
else if (valueKeywords.hasOwnProperty(word))
|
||||
override = "atom";
|
||||
else if (colorKeywords.hasOwnProperty(word))
|
||||
@@ -380,7 +383,8 @@ CodeMirror.defineMode("css", function(config, parserConfig) {
|
||||
style = style[0];
|
||||
}
|
||||
override = style;
|
||||
state.state = states[state.state](type, stream, state);
|
||||
if (type != "comment")
|
||||
state.state = states[state.state](type, stream, state);
|
||||
return override;
|
||||
},
|
||||
|
||||
@@ -398,7 +402,6 @@ CodeMirror.defineMode("css", function(config, parserConfig) {
|
||||
ch == "{" && (cx.type == "at" || cx.type == "atBlock")) {
|
||||
// Dedent relative to current context.
|
||||
indent = Math.max(0, cx.indent - indentUnit);
|
||||
cx = cx.prev;
|
||||
}
|
||||
}
|
||||
return indent;
|
||||
@@ -407,6 +410,8 @@ CodeMirror.defineMode("css", function(config, parserConfig) {
|
||||
electricChars: "}",
|
||||
blockCommentStart: "/*",
|
||||
blockCommentEnd: "*/",
|
||||
blockCommentContinue: " * ",
|
||||
lineComment: lineComment,
|
||||
fold: "brace"
|
||||
};
|
||||
});
|
||||
@@ -414,7 +419,7 @@ CodeMirror.defineMode("css", function(config, parserConfig) {
|
||||
function keySet(array) {
|
||||
var keys = {};
|
||||
for (var i = 0; i < array.length; ++i) {
|
||||
keys[array[i]] = true;
|
||||
keys[array[i].toLowerCase()] = true;
|
||||
}
|
||||
return keys;
|
||||
}
|
||||
@@ -438,117 +443,151 @@ CodeMirror.defineMode("css", function(config, parserConfig) {
|
||||
"monochrome", "min-monochrome", "max-monochrome", "resolution",
|
||||
"min-resolution", "max-resolution", "scan", "grid", "orientation",
|
||||
"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_);
|
||||
|
||||
var mediaValueKeywords_ = [
|
||||
"landscape", "portrait", "none", "coarse", "fine", "on-demand", "hover",
|
||||
"interlace", "progressive"
|
||||
"interlace", "progressive",
|
||||
"dark", "light",
|
||||
"standard", "high"
|
||||
], mediaValueKeywords = keySet(mediaValueKeywords_);
|
||||
|
||||
var propertyKeywords_ = [
|
||||
"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-iteration-count", "animation-name", "animation-play-state",
|
||||
"animation-timing-function", "appearance", "azimuth", "backface-visibility",
|
||||
"background", "background-attachment", "background-blend-mode", "background-clip",
|
||||
"background-color", "background-image", "background-origin", "background-position",
|
||||
"background-repeat", "background-size", "baseline-shift", "binding",
|
||||
"bleed", "bookmark-label", "bookmark-level", "bookmark-state",
|
||||
"bookmark-target", "border", "border-bottom", "border-bottom-color",
|
||||
"border-bottom-left-radius", "border-bottom-right-radius",
|
||||
"border-bottom-style", "border-bottom-width", "border-collapse",
|
||||
"border-color", "border-image", "border-image-outset",
|
||||
"animation-timing-function", "appearance", "azimuth", "backdrop-filter",
|
||||
"backface-visibility", "background", "background-attachment",
|
||||
"background-blend-mode", "background-clip", "background-color",
|
||||
"background-image", "background-origin", "background-position",
|
||||
"background-position-x", "background-position-y", "background-repeat",
|
||||
"background-size", "baseline-shift", "binding", "bleed", "block-size",
|
||||
"bookmark-label", "bookmark-level", "bookmark-state", "bookmark-target",
|
||||
"border", "border-bottom", "border-bottom-color", "border-bottom-left-radius",
|
||||
"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-width", "border-left", "border-left-color",
|
||||
"border-left-style", "border-left-width", "border-radius", "border-right",
|
||||
"border-right-color", "border-right-style", "border-right-width",
|
||||
"border-spacing", "border-style", "border-top", "border-top-color",
|
||||
"border-top-left-radius", "border-top-right-radius", "border-top-style",
|
||||
"border-top-width", "border-width", "bottom", "box-decoration-break",
|
||||
"box-shadow", "box-sizing", "break-after", "break-before", "break-inside",
|
||||
"caption-side", "clear", "clip", "color", "color-profile", "column-count",
|
||||
"column-fill", "column-gap", "column-rule", "column-rule-color",
|
||||
"column-rule-style", "column-rule-width", "column-span", "column-width",
|
||||
"columns", "content", "counter-increment", "counter-reset", "crop", "cue",
|
||||
"cue-after", "cue-before", "cursor", "direction", "display",
|
||||
"dominant-baseline", "drop-initial-after-adjust",
|
||||
"drop-initial-after-align", "drop-initial-before-adjust",
|
||||
"drop-initial-before-align", "drop-initial-size", "drop-initial-value",
|
||||
"elevation", "empty-cells", "fit", "fit-position", "flex", "flex-basis",
|
||||
"flex-direction", "flex-flow", "flex-grow", "flex-shrink", "flex-wrap",
|
||||
"float", "float-offset", "flow-from", "flow-into", "font", "font-feature-settings",
|
||||
"font-family", "font-kerning", "font-language-override", "font-size", "font-size-adjust",
|
||||
"font-stretch", "font-style", "font-synthesis", "font-variant",
|
||||
"font-variant-alternates", "font-variant-caps", "font-variant-east-asian",
|
||||
"font-variant-ligatures", "font-variant-numeric", "font-variant-position",
|
||||
"font-weight", "grid", "grid-area", "grid-auto-columns", "grid-auto-flow",
|
||||
"grid-auto-position", "grid-auto-rows", "grid-column", "grid-column-end",
|
||||
"grid-column-start", "grid-row", "grid-row-end", "grid-row-start",
|
||||
"border-image-width", "border-left", "border-left-color", "border-left-style",
|
||||
"border-left-width", "border-radius", "border-right", "border-right-color",
|
||||
"border-right-style", "border-right-width", "border-spacing", "border-style",
|
||||
"border-top", "border-top-color", "border-top-left-radius",
|
||||
"border-top-right-radius", "border-top-style", "border-top-width",
|
||||
"border-width", "bottom", "box-decoration-break", "box-shadow", "box-sizing",
|
||||
"break-after", "break-before", "break-inside", "caption-side", "caret-color",
|
||||
"clear", "clip", "color", "color-profile", "column-count", "column-fill",
|
||||
"column-gap", "column-rule", "column-rule-color", "column-rule-style",
|
||||
"column-rule-width", "column-span", "column-width", "columns", "contain",
|
||||
"content", "counter-increment", "counter-reset", "crop", "cue", "cue-after",
|
||||
"cue-before", "cursor", "direction", "display", "dominant-baseline",
|
||||
"drop-initial-after-adjust", "drop-initial-after-align",
|
||||
"drop-initial-before-adjust", "drop-initial-before-align", "drop-initial-size",
|
||||
"drop-initial-value", "elevation", "empty-cells", "fit", "fit-content", "fit-position",
|
||||
"flex", "flex-basis", "flex-direction", "flex-flow", "flex-grow",
|
||||
"flex-shrink", "flex-wrap", "float", "float-offset", "flow-from", "flow-into",
|
||||
"font", "font-family", "font-feature-settings", "font-kerning",
|
||||
"font-language-override", "font-optical-sizing", "font-size",
|
||||
"font-size-adjust", "font-stretch", "font-style", "font-synthesis",
|
||||
"font-variant", "font-variant-alternates", "font-variant-caps",
|
||||
"font-variant-east-asian", "font-variant-ligatures", "font-variant-numeric",
|
||||
"font-variant-position", "font-variation-settings", "font-weight", "gap",
|
||||
"grid", "grid-area", "grid-auto-columns", "grid-auto-flow", "grid-auto-rows",
|
||||
"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-rows", "hanging-punctuation", "height", "hyphens",
|
||||
"icon", "image-orientation", "image-rendering", "image-resolution",
|
||||
"inline-box-align", "justify-content", "left", "letter-spacing",
|
||||
"line-break", "line-height", "line-stacking", "line-stacking-ruby",
|
||||
"grid-template-rows", "hanging-punctuation", "height", "hyphens", "icon",
|
||||
"image-orientation", "image-rendering", "image-resolution", "inline-box-align",
|
||||
"inset", "inset-block", "inset-block-end", "inset-block-start", "inset-inline",
|
||||
"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",
|
||||
"list-style-image", "list-style-position", "list-style-type", "margin",
|
||||
"margin-bottom", "margin-left", "margin-right", "margin-top",
|
||||
"marker-offset", "marks", "marquee-direction", "marquee-loop",
|
||||
"marquee-play-count", "marquee-speed", "marquee-style", "max-height",
|
||||
"max-width", "min-height", "min-width", "move-to", "nav-down", "nav-index",
|
||||
"nav-left", "nav-right", "nav-up", "object-fit", "object-position",
|
||||
"opacity", "order", "orphans", "outline",
|
||||
"outline-color", "outline-offset", "outline-style", "outline-width",
|
||||
"overflow", "overflow-style", "overflow-wrap", "overflow-x", "overflow-y",
|
||||
"padding", "padding-bottom", "padding-left", "padding-right", "padding-top",
|
||||
"page", "page-break-after", "page-break-before", "page-break-inside",
|
||||
"page-policy", "pause", "pause-after", "pause-before", "perspective",
|
||||
"perspective-origin", "pitch", "pitch-range", "play-during", "position",
|
||||
"presentation-level", "punctuation-trim", "quotes", "region-break-after",
|
||||
"region-break-before", "region-break-inside", "region-fragment",
|
||||
"rendering-intent", "resize", "rest", "rest-after", "rest-before", "richness",
|
||||
"right", "rotation", "rotation-point", "ruby-align", "ruby-overhang",
|
||||
"ruby-position", "ruby-span", "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-decoration",
|
||||
"margin-bottom", "margin-left", "margin-right", "margin-top", "marks",
|
||||
"marquee-direction", "marquee-loop", "marquee-play-count", "marquee-speed",
|
||||
"marquee-style", "mask-clip", "mask-composite", "mask-image", "mask-mode",
|
||||
"mask-origin", "mask-position", "mask-repeat", "mask-size","mask-type",
|
||||
"max-block-size", "max-height", "max-inline-size",
|
||||
"max-width", "min-block-size", "min-height", "min-inline-size", "min-width",
|
||||
"mix-blend-mode", "move-to", "nav-down", "nav-index", "nav-left", "nav-right",
|
||||
"nav-up", "object-fit", "object-position", "offset", "offset-anchor",
|
||||
"offset-distance", "offset-path", "offset-position", "offset-rotate",
|
||||
"opacity", "order", "orphans", "outline", "outline-color", "outline-offset",
|
||||
"outline-style", "outline-width", "overflow", "overflow-style",
|
||||
"overflow-wrap", "overflow-x", "overflow-y", "padding", "padding-bottom",
|
||||
"padding-left", "padding-right", "padding-top", "page", "page-break-after",
|
||||
"page-break-before", "page-break-inside", "page-policy", "pause",
|
||||
"pause-after", "pause-before", "perspective", "perspective-origin", "pitch",
|
||||
"pitch-range", "place-content", "place-items", "place-self", "play-during",
|
||||
"position", "presentation-level", "punctuation-trim", "quotes",
|
||||
"region-break-after", "region-break-before", "region-break-inside",
|
||||
"region-fragment", "rendering-intent", "resize", "rest", "rest-after",
|
||||
"rest-before", "richness", "right", "rotate", "rotation", "rotation-point",
|
||||
"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-style", "text-emphasis", "text-emphasis-color",
|
||||
"text-emphasis-position", "text-emphasis-style", "text-height",
|
||||
"text-indent", "text-justify", "text-outline", "text-overflow", "text-shadow",
|
||||
"text-size-adjust", "text-space-collapse", "text-transform", "text-underline-position",
|
||||
"text-wrap", "top", "transform", "transform-origin", "transform-style",
|
||||
"transition", "transition-delay", "transition-duration",
|
||||
"transition-property", "transition-timing-function", "unicode-bidi",
|
||||
"vertical-align", "visibility", "voice-balance", "voice-duration",
|
||||
"voice-family", "voice-pitch", "voice-range", "voice-rate", "voice-stress",
|
||||
"voice-volume", "volume", "white-space", "widows", "width", "word-break",
|
||||
"word-spacing", "word-wrap", "z-index",
|
||||
"text-decoration-skip-ink", "text-decoration-style", "text-emphasis",
|
||||
"text-emphasis-color", "text-emphasis-position", "text-emphasis-style",
|
||||
"text-height", "text-indent", "text-justify", "text-orientation",
|
||||
"text-outline", "text-overflow", "text-rendering", "text-shadow",
|
||||
"text-size-adjust", "text-space-collapse", "text-transform",
|
||||
"text-underline-position", "text-wrap", "top", "touch-action", "transform", "transform-origin",
|
||||
"transform-style", "transition", "transition-delay", "transition-duration",
|
||||
"transition-property", "transition-timing-function", "translate",
|
||||
"unicode-bidi", "user-select", "vertical-align", "visibility", "voice-balance",
|
||||
"voice-duration", "voice-family", "voice-pitch", "voice-range", "voice-rate",
|
||||
"voice-stress", "voice-volume", "volume", "white-space", "widows", "width",
|
||||
"will-change", "word-break", "word-spacing", "word-wrap", "writing-mode", "z-index",
|
||||
// SVG-specific
|
||||
"clip-path", "clip-rule", "mask", "enable-background", "filter", "flood-color",
|
||||
"flood-opacity", "lighting-color", "stop-color", "stop-opacity", "pointer-events",
|
||||
"color-interpolation", "color-interpolation-filters",
|
||||
"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-miterlimit", "stroke-opacity", "stroke-width", "text-rendering",
|
||||
"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_);
|
||||
|
||||
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-face-color", "scrollbar-highlight-color", "scrollbar-shadow-color",
|
||||
"scrollbar-3d-light-color", "scrollbar-track-color", "shape-inside",
|
||||
"searchfield-cancel-button", "searchfield-decoration", "searchfield-results-button",
|
||||
"searchfield-results-decoration", "zoom"
|
||||
"scrollbar-track-color", "searchfield-cancel-button", "searchfield-decoration",
|
||||
"searchfield-results-button", "searchfield-results-decoration", "shape-inside", "zoom"
|
||||
], nonStandardPropertyKeywords = keySet(nonStandardPropertyKeywords_);
|
||||
|
||||
var fontProperties_ = [
|
||||
"font-family", "src", "unicode-range", "font-variant", "font-feature-settings",
|
||||
"font-stretch", "font-weight", "font-style"
|
||||
"font-display", "font-family", "src", "unicode-range", "font-variant",
|
||||
"font-feature-settings", "font-stretch", "font-weight", "font-style"
|
||||
], fontProperties = keySet(fontProperties_);
|
||||
|
||||
var counterDescriptors_ = [
|
||||
@@ -561,16 +600,16 @@ CodeMirror.defineMode("css", function(config, parserConfig) {
|
||||
"bisque", "black", "blanchedalmond", "blue", "blueviolet", "brown",
|
||||
"burlywood", "cadetblue", "chartreuse", "chocolate", "coral", "cornflowerblue",
|
||||
"cornsilk", "crimson", "cyan", "darkblue", "darkcyan", "darkgoldenrod",
|
||||
"darkgray", "darkgreen", "darkkhaki", "darkmagenta", "darkolivegreen",
|
||||
"darkgray", "darkgreen", "darkgrey", "darkkhaki", "darkmagenta", "darkolivegreen",
|
||||
"darkorange", "darkorchid", "darkred", "darksalmon", "darkseagreen",
|
||||
"darkslateblue", "darkslategray", "darkturquoise", "darkviolet",
|
||||
"deeppink", "deepskyblue", "dimgray", "dodgerblue", "firebrick",
|
||||
"darkslateblue", "darkslategray", "darkslategrey", "darkturquoise", "darkviolet",
|
||||
"deeppink", "deepskyblue", "dimgray", "dimgrey", "dodgerblue", "firebrick",
|
||||
"floralwhite", "forestgreen", "fuchsia", "gainsboro", "ghostwhite",
|
||||
"gold", "goldenrod", "gray", "grey", "green", "greenyellow", "honeydew",
|
||||
"hotpink", "indianred", "indigo", "ivory", "khaki", "lavender",
|
||||
"lavenderblush", "lawngreen", "lemonchiffon", "lightblue", "lightcoral",
|
||||
"lightcyan", "lightgoldenrodyellow", "lightgray", "lightgreen", "lightpink",
|
||||
"lightsalmon", "lightseagreen", "lightskyblue", "lightslategray",
|
||||
"lightcyan", "lightgoldenrodyellow", "lightgray", "lightgreen", "lightgrey", "lightpink",
|
||||
"lightsalmon", "lightseagreen", "lightskyblue", "lightslategray", "lightslategrey",
|
||||
"lightsteelblue", "lightyellow", "lime", "limegreen", "linen", "magenta",
|
||||
"maroon", "mediumaquamarine", "mediumblue", "mediumorchid", "mediumpurple",
|
||||
"mediumseagreen", "mediumslateblue", "mediumspringgreen", "mediumturquoise",
|
||||
@@ -580,7 +619,7 @@ CodeMirror.defineMode("css", function(config, parserConfig) {
|
||||
"papayawhip", "peachpuff", "peru", "pink", "plum", "powderblue",
|
||||
"purple", "rebeccapurple", "red", "rosybrown", "royalblue", "saddlebrown",
|
||||
"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",
|
||||
"whitesmoke", "yellow", "yellowgreen"
|
||||
], colorKeywords = keySet(colorKeywords_);
|
||||
@@ -589,23 +628,23 @@ CodeMirror.defineMode("css", function(config, parserConfig) {
|
||||
"above", "absolute", "activeborder", "additive", "activecaption", "afar",
|
||||
"after-white-space", "ahead", "alias", "all", "all-scroll", "alphabetic", "alternate",
|
||||
"always", "amharic", "amharic-abegede", "antialiased", "appworkspace",
|
||||
"arabic-indic", "armenian", "asterisks", "attr", "auto", "avoid", "avoid-column", "avoid-page",
|
||||
"avoid-region", "background", "backwards", "baseline", "below", "bidi-override", "binary",
|
||||
"bengali", "blink", "block", "block-axis", "bold", "bolder", "border", "border-box",
|
||||
"both", "bottom", "break", "break-all", "break-word", "bullets", "button", "button-bevel",
|
||||
"arabic-indic", "armenian", "asterisks", "attr", "auto", "auto-flow", "avoid", "avoid-column", "avoid-page",
|
||||
"avoid-region", "axis-pan", "background", "backwards", "baseline", "below", "bidi-override", "binary",
|
||||
"bengali", "blink", "block", "block-axis", "blur", "bold", "bolder", "border", "border-box",
|
||||
"both", "bottom", "break", "break-all", "break-word", "brightness", "bullets", "button",
|
||||
"buttonface", "buttonhighlight", "buttonshadow", "buttontext", "calc", "cambodian",
|
||||
"capitalize", "caps-lock-indicator", "caption", "captiontext", "caret",
|
||||
"cell", "center", "checkbox", "circle", "cjk-decimal", "cjk-earthly-branch",
|
||||
"cjk-heavenly-stem", "cjk-ideographic", "clear", "clip", "close-quote",
|
||||
"col-resize", "collapse", "color", "color-burn", "color-dodge", "column", "column-reverse",
|
||||
"compact", "condensed", "contain", "content",
|
||||
"content-box", "context-menu", "continuous", "copy", "counter", "counters", "cover", "crop",
|
||||
"cross", "crosshair", "currentcolor", "cursive", "cyclic", "darken", "dashed", "decimal",
|
||||
"decimal-leading-zero", "default", "default-button", "destination-atop",
|
||||
"compact", "condensed", "conic-gradient", "contain", "content", "contents",
|
||||
"content-box", "context-menu", "continuous", "contrast", "copy", "counter", "counters", "cover", "crop",
|
||||
"cross", "crosshair", "cubic-bezier", "currentcolor", "cursive", "cyclic", "darken", "dashed", "decimal",
|
||||
"decimal-leading-zero", "default", "default-button", "dense", "destination-atop",
|
||||
"destination-in", "destination-out", "destination-over", "devanagari", "difference",
|
||||
"disc", "discard", "disclosure-closed", "disclosure-open", "document",
|
||||
"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",
|
||||
"ethiopic-abegede-am-et", "ethiopic-abegede-gez", "ethiopic-abegede-ti-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-ti-er", "ethiopic-halehame-ti-et", "ethiopic-halehame-tig",
|
||||
"ethiopic-numeric", "ew-resize", "exclusion", "expanded", "extends", "extra-condensed",
|
||||
"extra-expanded", "fantasy", "fast", "fill", "fixed", "flat", "flex", "flex-end", "flex-start", "footnotes",
|
||||
"forwards", "from", "geometricPrecision", "georgian", "graytext", "groove",
|
||||
"extra-expanded", "fantasy", "fast", "fill", "fill-box", "fixed", "flat", "flex", "flex-end", "flex-start", "footnotes",
|
||||
"forwards", "from", "geometricPrecision", "georgian", "grayscale", "graytext", "grid", "groove",
|
||||
"gujarati", "gurmukhi", "hand", "hangul", "hangul-consonant", "hard-light", "hebrew",
|
||||
"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",
|
||||
"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",
|
||||
"katakana", "katakana-iroha", "keep-all", "khmer",
|
||||
"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",
|
||||
"local", "logical", "loud", "lower", "lower-alpha", "lower-armenian",
|
||||
"lower-greek", "lower-hexadecimal", "lower-latin", "lower-norwegian",
|
||||
"lower-roman", "lowercase", "ltr", "luminosity", "malayalam", "match", "matrix", "matrix3d",
|
||||
"media-controls-background", "media-current-time-display",
|
||||
"media-fullscreen-button", "media-mute-button", "media-play-button",
|
||||
"media-return-to-realtime-button", "media-rewind-button",
|
||||
"media-seek-back-button", "media-seek-forward-button", "media-slider",
|
||||
"media-sliderthumb", "media-time-remaining-display", "media-volume-slider",
|
||||
"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",
|
||||
"lower-roman", "lowercase", "ltr", "luminosity", "malayalam", "manipulation", "match", "matrix", "matrix3d",
|
||||
"media-play-button", "media-slider", "media-sliderthumb",
|
||||
"media-volume-slider", "media-volume-sliderthumb", "medium",
|
||||
"menu", "menulist", "menulist-button",
|
||||
"menutext", "message-box", "middle", "min-intrinsic",
|
||||
"mix", "mongolian", "monospace", "move", "multiple", "multiple_mask_images", "multiply", "myanmar", "n-resize",
|
||||
"narrower", "ne-resize", "nesw-resize", "no-close-quote", "no-drop",
|
||||
"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",
|
||||
"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",
|
||||
"progress", "push-button", "radial-gradient", "radio", "read-only",
|
||||
"read-write", "read-write-plaintext-only", "rectangle", "region",
|
||||
"relative", "repeat", "repeating-linear-gradient",
|
||||
"repeating-radial-gradient", "repeat-x", "repeat-y", "reset", "reverse",
|
||||
"relative", "repeat", "repeating-linear-gradient", "repeating-radial-gradient",
|
||||
"repeating-conic-gradient", "repeat-x", "repeat-y", "reset", "reverse",
|
||||
"rgb", "rgba", "ridge", "right", "rotate", "rotate3d", "rotateX", "rotateY",
|
||||
"rotateZ", "round", "row", "row-resize", "row-reverse", "rtl", "run-in", "running",
|
||||
"s-resize", "sans-serif", "saturation", "scale", "scale3d", "scaleX", "scaleY", "scaleZ", "screen",
|
||||
"scroll", "scrollbar", "se-resize", "searchfield",
|
||||
"s-resize", "sans-serif", "saturate", "saturation", "scale", "scale3d", "scaleX", "scaleY", "scaleZ", "screen",
|
||||
"scroll", "scrollbar", "scroll-position", "se-resize", "searchfield",
|
||||
"searchfield-cancel-button", "searchfield-decoration",
|
||||
"searchfield-results-button", "searchfield-results-decoration",
|
||||
"semi-condensed", "semi-expanded", "separate", "serif", "show", "sidama",
|
||||
"searchfield-results-button", "searchfield-results-decoration", "self-start", "self-end",
|
||||
"semi-condensed", "semi-expanded", "separate", "sepia", "serif", "show", "sidama",
|
||||
"simp-chinese-formal", "simp-chinese-informal", "single",
|
||||
"skew", "skewX", "skewY", "skip-white-space", "slide", "slider-horizontal",
|
||||
"slider-vertical", "sliderthumb-horizontal", "sliderthumb-vertical", "slow",
|
||||
"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",
|
||||
"square-button", "start", "static", "status-bar", "stretch", "stroke", "sub",
|
||||
"subpixel-antialiased", "super", "sw-resize", "symbolic", "symbols", "table",
|
||||
"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", "stroke-box", "sub",
|
||||
"subpixel-antialiased", "svg_masks", "super", "sw-resize", "symbolic", "symbols", "system-ui", "table",
|
||||
"table-caption", "table-cell", "table-column", "table-column-group",
|
||||
"table-footer-group", "table-header-group", "table-row", "table-row-group",
|
||||
"tamil",
|
||||
@@ -671,12 +706,12 @@ CodeMirror.defineMode("css", function(config, parserConfig) {
|
||||
"thick", "thin", "threeddarkshadow", "threedface", "threedhighlight",
|
||||
"threedlightshadow", "threedshadow", "tibetan", "tigre", "tigrinya-er",
|
||||
"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",
|
||||
"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-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",
|
||||
"window", "windowframe", "windowtext", "words", "wrap", "wrap-reverse", "x-large", "x-small", "xor",
|
||||
"xx-large", "xx-small"
|
||||
@@ -730,6 +765,7 @@ CodeMirror.defineMode("css", function(config, parserConfig) {
|
||||
valueKeywords: valueKeywords,
|
||||
fontProperties: fontProperties,
|
||||
allowNested: true,
|
||||
lineComment: "//",
|
||||
tokenHooks: {
|
||||
"/": function(stream, state) {
|
||||
if (stream.eat("/")) {
|
||||
@@ -743,8 +779,8 @@ CodeMirror.defineMode("css", function(config, parserConfig) {
|
||||
}
|
||||
},
|
||||
":": function(stream) {
|
||||
if (stream.match(/\s*\{/))
|
||||
return [null, "{"];
|
||||
if (stream.match(/^\s*\{/, false))
|
||||
return [null, null]
|
||||
return false;
|
||||
},
|
||||
"$": function(stream) {
|
||||
@@ -772,6 +808,7 @@ CodeMirror.defineMode("css", function(config, parserConfig) {
|
||||
valueKeywords: valueKeywords,
|
||||
fontProperties: fontProperties,
|
||||
allowNested: true,
|
||||
lineComment: "//",
|
||||
tokenHooks: {
|
||||
"/": function(stream, state) {
|
||||
if (stream.eat("/")) {
|
||||
@@ -786,7 +823,7 @@ CodeMirror.defineMode("css", function(config, parserConfig) {
|
||||
},
|
||||
"@": function(stream) {
|
||||
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\\\-]/);
|
||||
if (stream.match(/^\s*:/, false))
|
||||
return ["variable-2", "variable-definition"];
|
||||
|
103
src/lib/codemirror/mode/css/gss.html
vendored
103
src/lib/codemirror/mode/css/gss.html
vendored
@@ -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>
|
17
src/lib/codemirror/mode/css/gss_test.js
vendored
17
src/lib/codemirror/mode/css/gss_test.js
vendored
@@ -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];",
|
||||
"}",
|
||||
"}");
|
||||
|
||||
})();
|
152
src/lib/codemirror/mode/css/less.html
vendored
152
src/lib/codemirror/mode/css/less.html
vendored
@@ -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>
|
157
src/lib/codemirror/mode/css/scss.html
vendored
157
src/lib/codemirror/mode/css/scss.html
vendored
@@ -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>
|
8
src/lib/codemirror/mode/haml/haml.js
vendored
8
src/lib/codemirror/mode/haml/haml.js
vendored
@@ -1,5 +1,5 @@
|
||||
// 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) {
|
||||
if (typeof exports == "object" && typeof module == "object") // CommonJS
|
||||
@@ -72,7 +72,7 @@
|
||||
}
|
||||
}
|
||||
|
||||
// donot handle --> as valid ruby, make it HTML close comment instead
|
||||
// do not handle --> as valid ruby, make it HTML close comment instead
|
||||
if (state.startOfLine && !stream.match("-->", false) && (ch == "=" || ch == "-" )) {
|
||||
state.tokenize = ruby;
|
||||
return state.tokenize(stream, state);
|
||||
@@ -98,8 +98,8 @@
|
||||
return {
|
||||
// default to html mode
|
||||
startState: function() {
|
||||
var htmlState = htmlMode.startState();
|
||||
var rubyState = rubyMode.startState();
|
||||
var htmlState = CodeMirror.startState(htmlMode);
|
||||
var rubyState = CodeMirror.startState(rubyMode);
|
||||
return {
|
||||
htmlState: htmlState,
|
||||
rubyState: rubyState,
|
||||
|
@@ -1,5 +1,5 @@
|
||||
// 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) {
|
||||
if (typeof exports == "object" && typeof module == "object") // CommonJS
|
||||
@@ -14,7 +14,16 @@
|
||||
"use strict";
|
||||
|
||||
CodeMirror.defineMode("htmlembedded", function(config, parserConfig) {
|
||||
var closeComment = parserConfig.closeComment || "--%>"
|
||||
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 || "<%",
|
||||
close: parserConfig.close || parserConfig.scriptEndRegex || "%>",
|
||||
mode: CodeMirror.getMode(config, parserConfig.scriptingModeSpec)
|
||||
|
21
src/lib/codemirror/mode/htmlmixed/htmlmixed.js
vendored
21
src/lib/codemirror/mode/htmlmixed/htmlmixed.js
vendored
@@ -1,5 +1,5 @@
|
||||
// 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) {
|
||||
if (typeof exports == "object" && typeof module == "object") // CommonJS
|
||||
@@ -14,7 +14,7 @@
|
||||
var defaultTags = {
|
||||
script: [
|
||||
["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"],
|
||||
[null, null, "javascript"]
|
||||
],
|
||||
@@ -46,11 +46,11 @@
|
||||
|
||||
function getAttrValue(text, attr) {
|
||||
var match = text.match(getAttrRegexp(attr))
|
||||
return match ? match[2] : ""
|
||||
return match ? /^\s*(.*?)\s*$/.exec(match[2])[1] : ""
|
||||
}
|
||||
|
||||
function getTagRegexp(tagName, anchored) {
|
||||
return new RegExp((anchored ? "^" : "") + "<\/\s*" + tagName + "\s*>", "i");
|
||||
return new RegExp((anchored ? "^" : "") + "<\/\\s*" + tagName + "\\s*>", "i");
|
||||
}
|
||||
|
||||
function addTags(from, to) {
|
||||
@@ -74,7 +74,8 @@
|
||||
name: "xml",
|
||||
htmlMode: true,
|
||||
multilineTagIndentFactor: parserConfig.multilineTagIndentFactor,
|
||||
multilineTagIndentPastTag: parserConfig.multilineTagIndentPastTag
|
||||
multilineTagIndentPastTag: parserConfig.multilineTagIndentPastTag,
|
||||
allowMissingTagName: parserConfig.allowMissingTagName,
|
||||
});
|
||||
|
||||
var tags = {};
|
||||
@@ -105,7 +106,7 @@
|
||||
return maybeBackup(stream, endTag, state.localMode.token(stream, state.localState));
|
||||
};
|
||||
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) {
|
||||
state.inTag += stream.current()
|
||||
if (stream.eol()) state.inTag += " "
|
||||
@@ -115,7 +116,7 @@
|
||||
|
||||
return {
|
||||
startState: function () {
|
||||
var state = htmlMode.startState();
|
||||
var state = CodeMirror.startState(htmlMode);
|
||||
return {token: html, inTag: null, localMode: null, localState: null, htmlState: state};
|
||||
},
|
||||
|
||||
@@ -133,11 +134,11 @@
|
||||
return state.token(stream, state);
|
||||
},
|
||||
|
||||
indent: function (state, textAfter) {
|
||||
indent: function (state, textAfter, line) {
|
||||
if (!state.localMode || /^\s*<\//.test(textAfter))
|
||||
return htmlMode.indent(state.htmlState, textAfter);
|
||||
return htmlMode.indent(state.htmlState, textAfter, line);
|
||||
else if (state.localMode.indent)
|
||||
return state.localMode.indent(state.localState, textAfter);
|
||||
return state.localMode.indent(state.localState, textAfter, line);
|
||||
else
|
||||
return CodeMirror.Pass;
|
||||
},
|
||||
|
588
src/lib/codemirror/mode/javascript/javascript.js
vendored
588
src/lib/codemirror/mode/javascript/javascript.js
vendored
@@ -1,7 +1,5 @@
|
||||
// CodeMirror, copyright (c) by Marijn Haverbeke and others
|
||||
// Distributed under an MIT license: http://codemirror.net/LICENSE
|
||||
|
||||
// TODO actually recognize syntax of TypeScript constructs
|
||||
// Distributed under an MIT license: https://codemirror.net/5/LICENSE
|
||||
|
||||
(function(mod) {
|
||||
if (typeof exports == "object" && typeof module == "object") // CommonJS
|
||||
@@ -13,16 +11,12 @@
|
||||
})(function(CodeMirror) {
|
||||
"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) {
|
||||
var indentUnit = config.indentUnit;
|
||||
var statementIndent = parserConfig.statementIndent;
|
||||
var jsonldMode = parserConfig.jsonld;
|
||||
var jsonMode = parserConfig.json || jsonldMode;
|
||||
var trackScope = parserConfig.trackScope !== false
|
||||
var isTS = parserConfig.typescript;
|
||||
var wordRE = parserConfig.wordCharacters || /[\w$\xa1-\uffff]/;
|
||||
|
||||
@@ -30,54 +24,24 @@ CodeMirror.defineMode("javascript", function(config, parserConfig) {
|
||||
|
||||
var keywords = function(){
|
||||
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 jsKeywords = {
|
||||
return {
|
||||
"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,
|
||||
"var": kw("var"), "const": kw("var"), "let": kw("var"),
|
||||
"return": D, "break": D, "continue": D, "new": kw("new"), "delete": C, "void": C, "throw": C,
|
||||
"debugger": kw("debugger"), "var": kw("var"), "const": kw("var"), "let": kw("var"),
|
||||
"function": kw("function"), "catch": kw("catch"),
|
||||
"for": kw("for"), "switch": kw("switch"), "case": kw("case"), "default": kw("default"),
|
||||
"in": operator, "typeof": operator, "instanceof": operator,
|
||||
"true": atom, "false": atom, "null": atom, "undefined": atom, "NaN": atom, "Infinity": 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)"/;
|
||||
|
||||
function readRegexp(stream) {
|
||||
@@ -104,7 +68,7 @@ CodeMirror.defineMode("javascript", function(config, parserConfig) {
|
||||
if (ch == '"' || ch == "'") {
|
||||
state.tokenize = tokenString(ch);
|
||||
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");
|
||||
} else if (ch == "." && stream.match("..")) {
|
||||
return ret("spread", "meta");
|
||||
@@ -112,17 +76,10 @@ CodeMirror.defineMode("javascript", function(config, parserConfig) {
|
||||
return ret(ch);
|
||||
} else if (ch == "=" && stream.eat(">")) {
|
||||
return ret("=>", "operator");
|
||||
} else if (ch == "0" && stream.eat(/x/i)) {
|
||||
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);
|
||||
} else if (ch == "0" && stream.match(/^(?:x[\dA-Fa-f_]+|o[0-7_]+|b[01_]+)n?/)) {
|
||||
return ret("number", "number");
|
||||
} else if (/\d/.test(ch)) {
|
||||
stream.match(/^\d*(?:\.\d*)?(?:[eE][+\-]?\d+)?/);
|
||||
stream.match(/^[\d_]*(?:n|(?:\.[\d_]*)?(?:[eE][+\-]?[\d_]+)?)?/);
|
||||
return ret("number", "number");
|
||||
} else if (ch == "/") {
|
||||
if (stream.eat("*")) {
|
||||
@@ -133,26 +90,47 @@ CodeMirror.defineMode("javascript", function(config, parserConfig) {
|
||||
return ret("comment", "comment");
|
||||
} else if (expressionAllowed(stream, state, 1)) {
|
||||
readRegexp(stream);
|
||||
stream.match(/^\b(([gimyu])(?![gimyu]*\2))+\b/);
|
||||
stream.match(/^\b(([gimyus])(?![gimyus]*\2))+\b/);
|
||||
return ret("regexp", "string-2");
|
||||
} else {
|
||||
stream.eatWhile(isOperatorChar);
|
||||
stream.eat("=");
|
||||
return ret("operator", "operator", stream.current());
|
||||
}
|
||||
} else if (ch == "`") {
|
||||
state.tokenize = tokenQuasi;
|
||||
return tokenQuasi(stream, state);
|
||||
} else if (ch == "#") {
|
||||
} else if (ch == "#" && stream.peek() == "!") {
|
||||
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)) {
|
||||
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());
|
||||
} else if (wordRE.test(ch)) {
|
||||
stream.eatWhile(wordRE);
|
||||
var word = stream.current(), known = keywords.propertyIsEnumerable(word) && keywords[word];
|
||||
return (known && state.lastType != ".") ? ret(known.type, known.style, word) :
|
||||
ret("variable", "variable", word);
|
||||
var word = stream.current()
|
||||
if (state.lastType != ".") {
|
||||
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);
|
||||
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;
|
||||
for (var pos = arrow - 1; pos >= 0; --pos) {
|
||||
var ch = stream.string.charAt(pos);
|
||||
var bracket = brackets.indexOf(ch);
|
||||
if (bracket >= 0 && bracket < 3) {
|
||||
if (!depth) { ++pos; break; }
|
||||
if (--depth == 0) break;
|
||||
if (--depth == 0) { if (ch == "(") sawSomething = true; break; }
|
||||
} else if (bracket >= 3 && bracket < 6) {
|
||||
++depth;
|
||||
} else if (wordRE.test(ch)) {
|
||||
sawSomething = true;
|
||||
} else if (/["'\/]/.test(ch)) {
|
||||
return;
|
||||
} else if (/["'\/`]/.test(ch)) {
|
||||
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) {
|
||||
++pos;
|
||||
break;
|
||||
@@ -232,7 +219,8 @@ CodeMirror.defineMode("javascript", function(config, parserConfig) {
|
||||
|
||||
// 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) {
|
||||
this.indented = indented;
|
||||
@@ -244,6 +232,7 @@ CodeMirror.defineMode("javascript", function(config, parserConfig) {
|
||||
}
|
||||
|
||||
function inScope(state, varname) {
|
||||
if (!trackScope) return false
|
||||
for (var v = state.localVars; v; v = v.next)
|
||||
if (v.name == varname) return true;
|
||||
for (var cx = state.context; cx; cx = cx.prev) {
|
||||
@@ -283,35 +272,70 @@ CodeMirror.defineMode("javascript", function(config, parserConfig) {
|
||||
pass.apply(null, arguments);
|
||||
return true;
|
||||
}
|
||||
function inList(name, list) {
|
||||
for (var v = list; v; v = v.next) if (v.name == name) return true
|
||||
return false;
|
||||
}
|
||||
function register(varname) {
|
||||
function inList(list) {
|
||||
for (var v = list; v; v = v.next)
|
||||
if (v.name == varname) return true;
|
||||
return false;
|
||||
}
|
||||
var state = cx.state;
|
||||
cx.marked = "def";
|
||||
if (!trackScope) return
|
||||
if (state.context) {
|
||||
if (inList(state.localVars)) return;
|
||||
state.localVars = {name: varname, next: state.localVars};
|
||||
} else {
|
||||
if (inList(state.globalVars)) return;
|
||||
if (parserConfig.globalVars)
|
||||
state.globalVars = {name: varname, next: state.globalVars};
|
||||
if (state.lexical.info == "var" && state.context && state.context.block) {
|
||||
// FIXME function decls are also not block scoped
|
||||
var newContext = registerVarScoped(varname, state.context)
|
||||
if (newContext != null) {
|
||||
state.context = newContext
|
||||
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
|
||||
|
||||
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() {
|
||||
cx.state.context = {prev: cx.state.context, vars: cx.state.localVars};
|
||||
cx.state.localVars = defaultVars;
|
||||
cx.state.context = new Context(cx.state.context, cx.state.localVars, false)
|
||||
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() {
|
||||
cx.state.localVars = cx.state.context.vars;
|
||||
cx.state.context = cx.state.context.prev;
|
||||
cx.state.localVars = cx.state.context.vars
|
||||
cx.state.context = cx.state.context.prev
|
||||
}
|
||||
popcontext.lex = true
|
||||
function pushlex(type, info) {
|
||||
var result = function() {
|
||||
var state = cx.state, indent = state.indented;
|
||||
@@ -336,56 +360,87 @@ CodeMirror.defineMode("javascript", function(config, parserConfig) {
|
||||
function expect(wanted) {
|
||||
function exp(type) {
|
||||
if (type == wanted) return cont();
|
||||
else if (wanted == ";") return pass();
|
||||
else if (wanted == ";" || type == "}" || type == ")" || type == "]") return pass();
|
||||
else return cont(exp);
|
||||
};
|
||||
return exp;
|
||||
}
|
||||
|
||||
function statement(type, value) {
|
||||
if (type == "var") return cont(pushlex("vardef", value.length), vardef, expect(";"), poplex);
|
||||
if (type == "keyword a") return cont(pushlex("form"), expression, statement, poplex);
|
||||
if (type == "var") return cont(pushlex("vardef", value), vardef, expect(";"), poplex);
|
||||
if (type == "keyword a") return cont(pushlex("form"), parenExpr, 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 == "if") {
|
||||
if (cx.state.lexical.info == "else" && cx.state.cc[cx.state.cc.length - 1] == poplex)
|
||||
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 == "for") return cont(pushlex("form"), forspec, statement, poplex);
|
||||
if (type == "variable") return cont(pushlex("stat"), maybelabel);
|
||||
if (type == "switch") return cont(pushlex("form"), expression, pushlex("}", "switch"), expect("{"),
|
||||
block, poplex, poplex);
|
||||
if (type == "for") return cont(pushlex("form"), pushblockcontext, forspec, statement, popcontext, poplex);
|
||||
if (type == "class" || (isTS && value == "interface")) {
|
||||
cx.marked = "keyword"
|
||||
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 == "default") return cont(expect(":"));
|
||||
if (type == "catch") return cont(pushlex("form"), pushcontext, expect("("), funarg, expect(")"),
|
||||
statement, poplex, popcontext);
|
||||
if (type == "class") return cont(pushlex("form"), className, poplex);
|
||||
if (type == "catch") return cont(pushlex("form"), pushcontext, maybeCatchBinding, statement, poplex, popcontext);
|
||||
if (type == "export") return cont(pushlex("stat"), afterExport, 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);
|
||||
}
|
||||
function expression(type) {
|
||||
return expressionInner(type, false);
|
||||
function maybeCatchBinding(type) {
|
||||
if (type == "(") return cont(funarg, expect(")"))
|
||||
}
|
||||
function expressionNoComma(type) {
|
||||
return expressionInner(type, true);
|
||||
function expression(type, value) {
|
||||
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) {
|
||||
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);
|
||||
}
|
||||
|
||||
var maybeop = noComma ? maybeoperatorNoComma : maybeoperatorComma;
|
||||
if (atomicTypes.hasOwnProperty(type)) return cont(maybeop);
|
||||
if (type == "function") return cont(functiondef, maybeop);
|
||||
if (type == "keyword c") return cont(noComma ? maybeexpressionNoComma : maybeexpression);
|
||||
if (type == "(") return cont(pushlex(")"), maybeexpression, comprehension, expect(")"), poplex, maybeop);
|
||||
if (type == "class" || (isTS && value == "interface")) { cx.marked = "keyword"; return cont(pushlex("form"), classExpression, poplex); }
|
||||
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 == "[") return cont(pushlex("]"), arrayLiteral, poplex, maybeop);
|
||||
if (type == "{") return contCommasep(objprop, "}", null, maybeop);
|
||||
@@ -397,13 +452,9 @@ CodeMirror.defineMode("javascript", function(config, parserConfig) {
|
||||
if (type.match(/[;\}\)\],]/)) return pass();
|
||||
return pass(expression);
|
||||
}
|
||||
function maybeexpressionNoComma(type) {
|
||||
if (type.match(/[;\}\)\],]/)) return pass();
|
||||
return pass(expressionNoComma);
|
||||
}
|
||||
|
||||
function maybeoperatorComma(type, value) {
|
||||
if (type == ",") return cont(expression);
|
||||
if (type == ",") return cont(maybeexpression);
|
||||
return maybeoperatorNoComma(type, value, false);
|
||||
}
|
||||
function maybeoperatorNoComma(type, value, noComma) {
|
||||
@@ -411,7 +462,9 @@ CodeMirror.defineMode("javascript", function(config, parserConfig) {
|
||||
var expr = noComma == false ? expression : expressionNoComma;
|
||||
if (type == "=>") return cont(pushcontext, noComma ? arrowBodyNoComma : arrowBody, popcontext);
|
||||
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);
|
||||
return cont(expr);
|
||||
}
|
||||
@@ -420,11 +473,17 @@ CodeMirror.defineMode("javascript", function(config, parserConfig) {
|
||||
if (type == "(") return contCommasep(expressionNoComma, ")", "call", me);
|
||||
if (type == ".") return cont(property, 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) {
|
||||
if (type != "quasi") return pass();
|
||||
if (value.slice(value.length - 2) != "${") return cont(quasi);
|
||||
return cont(expression, continueQuasi);
|
||||
return cont(maybeexpression, continueQuasi);
|
||||
}
|
||||
function continueQuasi(type) {
|
||||
if (type == "}") {
|
||||
@@ -444,6 +503,7 @@ CodeMirror.defineMode("javascript", function(config, parserConfig) {
|
||||
function maybeTarget(noComma) {
|
||||
return function(type) {
|
||||
if (type == ".") return cont(noComma ? targetNoComma : target);
|
||||
else if (type == "variable" && isTS) return cont(maybeTypeArgs, noComma ? maybeoperatorNoComma : maybeoperatorComma)
|
||||
else return pass(noComma ? expressionNoComma : expression);
|
||||
};
|
||||
}
|
||||
@@ -461,21 +521,33 @@ CodeMirror.defineMode("javascript", function(config, parserConfig) {
|
||||
if (type == "variable") {cx.marked = "property"; return cont();}
|
||||
}
|
||||
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";
|
||||
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);
|
||||
} else if (type == "number" || type == "string") {
|
||||
cx.marked = jsonldMode ? "property" : (cx.style + " property");
|
||||
return cont(afterprop);
|
||||
} else if (type == "jsonld-keyword") {
|
||||
return cont(afterprop);
|
||||
} else if (type == "modifier") {
|
||||
} else if (isTS && isModifier(value)) {
|
||||
cx.marked = "keyword"
|
||||
return cont(objprop)
|
||||
} else if (type == "[") {
|
||||
return cont(expression, expect("]"), afterprop);
|
||||
return cont(expression, maybetype, expect("]"), afterprop);
|
||||
} 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) {
|
||||
@@ -487,18 +559,22 @@ CodeMirror.defineMode("javascript", function(config, parserConfig) {
|
||||
if (type == ":") return cont(expressionNoComma);
|
||||
if (type == "(") return pass(functiondef);
|
||||
}
|
||||
function commasep(what, end) {
|
||||
function proceed(type) {
|
||||
if (type == ",") {
|
||||
function commasep(what, end, sep) {
|
||||
function proceed(type, value) {
|
||||
if (sep ? sep.indexOf(type) > -1 : type == ",") {
|
||||
var lex = cx.state.lexical;
|
||||
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 function(type) {
|
||||
if (type == end) return cont();
|
||||
return function(type, value) {
|
||||
if (type == end || value == end) return cont();
|
||||
return pass(what, proceed);
|
||||
};
|
||||
}
|
||||
@@ -511,23 +587,111 @@ CodeMirror.defineMode("javascript", function(config, parserConfig) {
|
||||
if (type == "}") return cont();
|
||||
return pass(statement, block);
|
||||
}
|
||||
function maybetype(type) {
|
||||
if (isTS && type == ":") return cont(typedef);
|
||||
function maybetype(type, value) {
|
||||
if (isTS) {
|
||||
if (type == ":") return cont(typeexpr);
|
||||
if (value == "?") return cont(maybetype);
|
||||
}
|
||||
}
|
||||
function maybedefault(_, value) {
|
||||
if (value == "=") return cont(expressionNoComma);
|
||||
function maybetypeOrIn(type, value) {
|
||||
if (isTS && (type == ":" || value == "in")) return cont(typeexpr)
|
||||
}
|
||||
function typedef(type) {
|
||||
if (type == "variable") {cx.marked = "variable-3"; return cont();}
|
||||
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 vardef() {
|
||||
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);
|
||||
}
|
||||
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 == "spread") return cont(pattern);
|
||||
if (type == "[") return contCommasep(pattern, "]");
|
||||
if (type == "[") return contCommasep(eltpattern, "]");
|
||||
if (type == "{") return contCommasep(proppattern, "}");
|
||||
}
|
||||
function proppattern(type, value) {
|
||||
@@ -538,8 +702,12 @@ CodeMirror.defineMode("javascript", function(config, parserConfig) {
|
||||
if (type == "variable") cx.marked = "property";
|
||||
if (type == "spread") return cont(pattern);
|
||||
if (type == "}") return pass();
|
||||
if (type == "[") return cont(expression, expect(']'), expect(':'), proppattern);
|
||||
return cont(expect(":"), pattern, maybeAssign);
|
||||
}
|
||||
function eltpattern() {
|
||||
return pass(pattern, maybeAssign)
|
||||
}
|
||||
function maybeAssign(_type, value) {
|
||||
if (value == "=") return cont(expressionNoComma);
|
||||
}
|
||||
@@ -549,73 +717,111 @@ CodeMirror.defineMode("javascript", function(config, parserConfig) {
|
||||
function maybeelse(type, value) {
|
||||
if (type == "keyword b" && value == "else") return cont(pushlex("form", "else"), statement, poplex);
|
||||
}
|
||||
function forspec(type) {
|
||||
if (type == "(") return cont(pushlex(")"), forspec1, expect(")"), poplex);
|
||||
function forspec(type, value) {
|
||||
if (value == "await") return cont(forspec);
|
||||
if (type == "(") return cont(pushlex(")"), forspec1, poplex);
|
||||
}
|
||||
function forspec1(type) {
|
||||
if (type == "var") return cont(vardef, expect(";"), forspec2);
|
||||
if (type == ";") return cont(forspec2);
|
||||
if (type == "variable") return cont(formaybeinof);
|
||||
return pass(expression, expect(";"), forspec2);
|
||||
}
|
||||
function formaybeinof(_type, value) {
|
||||
if (value == "in" || value == "of") { cx.marked = "keyword"; return cont(expression); }
|
||||
return cont(maybeoperatorComma, forspec2);
|
||||
if (type == "var") return cont(vardef, forspec2);
|
||||
if (type == "variable") return cont(forspec2);
|
||||
return pass(forspec2)
|
||||
}
|
||||
function forspec2(type, value) {
|
||||
if (type == ";") return cont(forspec3);
|
||||
if (value == "in" || value == "of") { cx.marked = "keyword"; return cont(expression); }
|
||||
return pass(expression, expect(";"), forspec3);
|
||||
}
|
||||
function forspec3(type) {
|
||||
if (type != ")") cont(expression);
|
||||
if (type == ")") return cont()
|
||||
if (type == ";") return cont(forspec2)
|
||||
if (value == "in" || value == "of") { cx.marked = "keyword"; return cont(expression, forspec2) }
|
||||
return pass(expression, forspec2)
|
||||
}
|
||||
function functiondef(type, value) {
|
||||
if (value == "*") {cx.marked = "keyword"; 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);
|
||||
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) {
|
||||
if (type == "variable") {register(value); return cont(classNameAfter);}
|
||||
}
|
||||
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);
|
||||
}
|
||||
function classBody(type, value) {
|
||||
if (type == "variable" || cx.style == "keyword") {
|
||||
if (value == "static") {
|
||||
cx.marked = "keyword";
|
||||
return cont(classBody);
|
||||
}
|
||||
cx.marked = "property";
|
||||
if (value == "get" || value == "set") return cont(classGetterSetter, functiondef, classBody);
|
||||
return cont(functiondef, classBody);
|
||||
if (type == "async" ||
|
||||
(type == "variable" &&
|
||||
(value == "static" || value == "get" || value == "set" || (isTS && isModifier(value))) &&
|
||||
cx.stream.match(/^\s+#?[\w$\xa1-\uffff]/, false))) {
|
||||
cx.marked = "keyword";
|
||||
return cont(classBody);
|
||||
}
|
||||
if (type == "variable" || cx.style == "keyword") {
|
||||
cx.marked = "property";
|
||||
return cont(classfield, classBody);
|
||||
}
|
||||
if (type == "number" || type == "string") return cont(classfield, classBody);
|
||||
if (type == "[")
|
||||
return cont(expression, maybetype, expect("]"), classfield, classBody)
|
||||
if (value == "*") {
|
||||
cx.marked = "keyword";
|
||||
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 (value == "@") return cont(expression, classBody)
|
||||
}
|
||||
function classGetterSetter(type) {
|
||||
if (type != "variable") return pass();
|
||||
cx.marked = "property";
|
||||
return cont();
|
||||
function classfield(type, value) {
|
||||
if (value == "!") return cont(classfield)
|
||||
if (value == "?") return cont(classfield)
|
||||
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 == "default") { cx.marked = "keyword"; return cont(expression, expect(";")); }
|
||||
if (type == "{") return cont(commasep(exportField, "}"), maybeFrom, expect(";"));
|
||||
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) {
|
||||
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) {
|
||||
if (type == "{") return contCommasep(importSpec, "}");
|
||||
@@ -623,6 +829,9 @@ CodeMirror.defineMode("javascript", function(config, parserConfig) {
|
||||
if (value == "*") cx.marked = "keyword";
|
||||
return cont(maybeAs);
|
||||
}
|
||||
function maybeMoreImports(type) {
|
||||
if (type == ",") return cont(importSpec, maybeMoreImports)
|
||||
}
|
||||
function maybeAs(_type, value) {
|
||||
if (value == "as") { cx.marked = "keyword"; return cont(importSpec); }
|
||||
}
|
||||
@@ -631,16 +840,13 @@ CodeMirror.defineMode("javascript", function(config, parserConfig) {
|
||||
}
|
||||
function arrayLiteral(type) {
|
||||
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, "]"));
|
||||
}
|
||||
function comprehension(type) {
|
||||
if (type == "for") return cont(forspec, comprehension);
|
||||
if (type == "if") return cont(expression, comprehension);
|
||||
function enumdef() {
|
||||
return pass(pushlex("form"), pattern, expect("{"), pushlex("}"), commasep(enummember, "}"), poplex, poplex)
|
||||
}
|
||||
function enummember() {
|
||||
return pass(pattern, maybeAssign);
|
||||
}
|
||||
|
||||
function isContinuedStatement(state, textAfter) {
|
||||
@@ -649,6 +855,12 @@ CodeMirror.defineMode("javascript", function(config, parserConfig) {
|
||||
/[,.]/.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
|
||||
|
||||
return {
|
||||
@@ -659,7 +871,7 @@ CodeMirror.defineMode("javascript", function(config, parserConfig) {
|
||||
cc: [],
|
||||
lexical: new JSLexical((basecolumn || 0) - indentUnit, 0, "block", false),
|
||||
localVars: parserConfig.localVars,
|
||||
context: parserConfig.localVars && {vars: parserConfig.localVars},
|
||||
context: parserConfig.localVars && new Context(null, null, false),
|
||||
indented: basecolumn || 0
|
||||
};
|
||||
if (parserConfig.globalVars && typeof parserConfig.globalVars == "object")
|
||||
@@ -682,21 +894,25 @@ CodeMirror.defineMode("javascript", function(config, parserConfig) {
|
||||
},
|
||||
|
||||
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;
|
||||
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
|
||||
if (!/^\s*else\b/.test(textAfter)) for (var i = state.cc.length - 1; i >= 0; --i) {
|
||||
var c = state.cc[i];
|
||||
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")
|
||||
lexical = lexical.prev;
|
||||
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") return lexical.indented + indentUnit;
|
||||
else if (type == "stat")
|
||||
@@ -710,6 +926,7 @@ CodeMirror.defineMode("javascript", function(config, parserConfig) {
|
||||
electricInput: /^\s*(?:case .*?:|default:|\{|\})$/,
|
||||
blockCommentStart: jsonMode ? null : "/*",
|
||||
blockCommentEnd: jsonMode ? null : "*/",
|
||||
blockCommentContinue: jsonMode ? null : " * ",
|
||||
lineComment: jsonMode ? null : "//",
|
||||
fold: "brace",
|
||||
closeBrackets: "()[]{}''\"\"``",
|
||||
@@ -719,9 +936,9 @@ CodeMirror.defineMode("javascript", function(config, parserConfig) {
|
||||
jsonMode: jsonMode,
|
||||
|
||||
expressionAllowed: expressionAllowed,
|
||||
|
||||
skipExpression: function(state) {
|
||||
var top = state.cc[state.cc.length - 1]
|
||||
if (top == expression || top == expressionNoComma) state.cc.pop()
|
||||
parseJS(state, "atom", "atom", "true", new CodeMirror.StringStream("", 2, null))
|
||||
}
|
||||
};
|
||||
});
|
||||
@@ -733,9 +950,10 @@ CodeMirror.defineMIME("text/ecmascript", "javascript");
|
||||
CodeMirror.defineMIME("application/javascript", "javascript");
|
||||
CodeMirror.defineMIME("application/x-javascript", "javascript");
|
||||
CodeMirror.defineMIME("application/ecmascript", "javascript");
|
||||
CodeMirror.defineMIME("application/json", {name: "javascript", json: true});
|
||||
CodeMirror.defineMIME("application/x-json", {name: "javascript", json: true});
|
||||
CodeMirror.defineMIME("application/ld+json", {name: "javascript", jsonld: true});
|
||||
CodeMirror.defineMIME("application/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("text/typescript", { name: "javascript", typescript: true });
|
||||
CodeMirror.defineMIME("application/typescript", { name: "javascript", typescript: true });
|
||||
|
||||
|
72
src/lib/codemirror/mode/javascript/json-ld.html
vendored
72
src/lib/codemirror/mode/javascript/json-ld.html
vendored
@@ -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>
|
@@ -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>
|
13
src/lib/codemirror/mode/jsx/jsx.js
vendored
13
src/lib/codemirror/mode/jsx/jsx.js
vendored
@@ -1,5 +1,5 @@
|
||||
// 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) {
|
||||
if (typeof exports == "object" && typeof module == "object") // CommonJS
|
||||
@@ -26,13 +26,13 @@
|
||||
}
|
||||
|
||||
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")
|
||||
|
||||
function flatXMLIndent(state) {
|
||||
var tagName = state.tagName
|
||||
state.tagName = null
|
||||
var result = xmlMode.indent(state, "")
|
||||
var result = xmlMode.indent(state, "", "")
|
||||
state.tagName = tagName
|
||||
return result
|
||||
}
|
||||
@@ -103,10 +103,11 @@
|
||||
}
|
||||
|
||||
function jsToken(stream, state, cx) {
|
||||
if (stream.peek() == "<" && jsMode.expressionAllowed(stream, cx.state)) {
|
||||
jsMode.skipExpression(cx.state)
|
||||
state.context = new Context(CodeMirror.startState(xmlMode, jsMode.indent(cx.state, "")),
|
||||
if (stream.peek() == "<" && !stream.match(/^<([^<>]|<[^>]*>)+,\s*>/, false) &&
|
||||
jsMode.expressionAllowed(stream, cx.state)) {
|
||||
state.context = new Context(CodeMirror.startState(xmlMode, jsMode.indent(cx.state, "", "")),
|
||||
xmlMode, 0, state.context)
|
||||
jsMode.skipExpression(cx.state)
|
||||
return null
|
||||
}
|
||||
|
||||
|
417
src/lib/codemirror/mode/markdown/markdown.js
vendored
417
src/lib/codemirror/mode/markdown/markdown.js
vendored
@@ -1,5 +1,5 @@
|
||||
// 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) {
|
||||
if (typeof exports == "object" && typeof module == "object") // CommonJS
|
||||
@@ -35,15 +35,6 @@ CodeMirror.defineMode("markdown", function(cmCfg, modeCfg) {
|
||||
if (modeCfg.maxBlockquoteDepth === undefined)
|
||||
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] ")
|
||||
if (modeCfg.taskLists === undefined) modeCfg.taskLists = false;
|
||||
|
||||
@@ -51,6 +42,18 @@ CodeMirror.defineMode("markdown", function(cmCfg, modeCfg) {
|
||||
if (modeCfg.strikethrough === undefined)
|
||||
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.
|
||||
if (modeCfg.tokenTypeOverrides === undefined)
|
||||
modeCfg.tokenTypeOverrides = {};
|
||||
@@ -63,7 +66,9 @@ CodeMirror.defineMode("markdown", function(cmCfg, modeCfg) {
|
||||
list2: "variable-3",
|
||||
list3: "keyword",
|
||||
hr: "hr",
|
||||
image: "tag",
|
||||
image: "image",
|
||||
imageAltText: "image-alt-text",
|
||||
imageMarker: "image-marker",
|
||||
formatting: "formatting",
|
||||
linkInline: "link",
|
||||
linkEmail: "link",
|
||||
@@ -71,7 +76,8 @@ CodeMirror.defineMode("markdown", function(cmCfg, modeCfg) {
|
||||
linkHref: "string",
|
||||
em: "em",
|
||||
strong: "strong",
|
||||
strikethrough: "strikethrough"
|
||||
strikethrough: "strikethrough",
|
||||
emoji: "builtin"
|
||||
};
|
||||
|
||||
for (var tokenType in tokenTypes) {
|
||||
@@ -81,14 +87,15 @@ CodeMirror.defineMode("markdown", function(cmCfg, modeCfg) {
|
||||
}
|
||||
|
||||
var hrRE = /^([*\-_])(?:\s*\1){2,}\s*$/
|
||||
, ulRE = /^[*\-+]\s+/
|
||||
, olRE = /^[0-9]+([.)])\s+/
|
||||
, taskListRE = /^\[(x| )\](?=\s)/ // Must follow ulRE or olRE
|
||||
, listRE = /^(?:[*\-+]|^[0-9]+([.)]))\s+/
|
||||
, taskListRE = /^\[(x| )\](?=\s)/i // Must follow listRE
|
||||
, atxHeaderRE = modeCfg.allowAtxHeaderWithoutSpace ? /^(#+)/ : /^(#+)(?: |$)/
|
||||
, setextHeaderRE = /^ *(?:\={1,}|-{1,})\s*$/
|
||||
, textRE = /^[^#!\[\]*_\\<>` "'(~]+/
|
||||
, fencedCodeRE = new RegExp("^(" + (modeCfg.fencedCodeBlocks === true ? "~~~+|```+" : modeCfg.fencedCodeBlocks) +
|
||||
")[ \\t]*([\\w+#\-]*)");
|
||||
, setextHeaderRE = /^ {0,3}(?:\={1,}|-{2,})\s*$/
|
||||
, textRE = /^[^#!\[\]*_\\<>` "'(~:]+/
|
||||
, fencedCodeRE = /^(~~~+|```+)[ \t]*([\w\/+#-]*)[^\n`]*$/
|
||||
, 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) {
|
||||
state.f = state.inline = f;
|
||||
@@ -109,6 +116,8 @@ CodeMirror.defineMode("markdown", function(cmCfg, modeCfg) {
|
||||
function blankLine(state) {
|
||||
// Reset linkTitle state
|
||||
state.linkTitle = false;
|
||||
state.linkHref = false;
|
||||
state.linkText = false;
|
||||
// Reset EM state
|
||||
state.em = false;
|
||||
// Reset STRONG state
|
||||
@@ -119,94 +128,106 @@ CodeMirror.defineMode("markdown", function(cmCfg, modeCfg) {
|
||||
state.quote = 0;
|
||||
// Reset state.indentedCode
|
||||
state.indentedCode = false;
|
||||
if (htmlModeMissing && state.f == htmlBlock) {
|
||||
state.f = inlineNormal;
|
||||
state.block = blockNormal;
|
||||
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.block = blockNormal;
|
||||
state.htmlState = null;
|
||||
}
|
||||
}
|
||||
// Reset state.trailingSpace
|
||||
state.trailingSpace = 0;
|
||||
state.trailingSpaceNewLine = false;
|
||||
// Mark this line as blank
|
||||
state.prevLine = state.thisLine
|
||||
state.thisLine = null
|
||||
state.thisLine = {stream: null}
|
||||
return null;
|
||||
}
|
||||
|
||||
function blockNormal(stream, state) {
|
||||
|
||||
var sol = stream.sol();
|
||||
|
||||
var prevLineIsList = state.list !== false,
|
||||
prevLineIsIndentedCode = state.indentedCode;
|
||||
var firstTokenOnLine = stream.column() === state.indentation;
|
||||
var prevLineLineIsEmpty = lineIsEmpty(state.prevLine.stream);
|
||||
var prevLineIsIndentedCode = state.indentedCode;
|
||||
var prevLineIsHr = state.prevLine.hr;
|
||||
var prevLineIsList = state.list !== false;
|
||||
var maxNonCodeIndentation = (state.listStack[state.listStack.length - 1] || 0) + 3;
|
||||
|
||||
state.indentedCode = false;
|
||||
|
||||
if (prevLineIsList) {
|
||||
if (state.indentationDiff >= 0) { // Continued list
|
||||
if (state.indentationDiff < 4) { // Only adjust indentation if *not* a code block
|
||||
state.indentation -= state.indentationDiff;
|
||||
var lineIndentation = state.indentation;
|
||||
// compute once per line (on first token)
|
||||
if (state.indentationDiff === null) {
|
||||
state.indentationDiff = state.indentation;
|
||||
if (prevLineIsList) {
|
||||
state.list = null;
|
||||
// While this list item's marker's indentation is less than the deepest
|
||||
// list item's content's indentation,pop the deepest list item
|
||||
// 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;
|
||||
}
|
||||
}
|
||||
if (state.list !== false) {
|
||||
state.indentationDiff = lineIndentation - state.listStack[state.listStack.length - 1]
|
||||
}
|
||||
state.list = null;
|
||||
} else if (state.indentation > 0) {
|
||||
state.list = null;
|
||||
} else { // No longer a list
|
||||
state.list = false;
|
||||
}
|
||||
}
|
||||
|
||||
// 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;
|
||||
if (state.indentationDiff >= 4) {
|
||||
if (state.indentationDiff >= 4 && (prevLineIsIndentedCode || state.prevLine.fencedCodeEnd ||
|
||||
state.prevLine.header || prevLineLineIsEmpty)) {
|
||||
stream.skipToEnd();
|
||||
if (prevLineIsIndentedCode || lineIsEmpty(state.prevLine)) {
|
||||
state.indentation -= 4;
|
||||
state.indentedCode = true;
|
||||
return tokenTypes.code;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
state.indentedCode = true;
|
||||
return tokenTypes.code;
|
||||
} else if (stream.eatSpace()) {
|
||||
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.thisLine.header = true;
|
||||
if (modeCfg.highlightFormatting) state.formatting = "header";
|
||||
state.f = state.inline;
|
||||
return getType(state);
|
||||
} else if (!lineIsEmpty(state.prevLine) && !state.quote && !prevLineIsList &&
|
||||
!prevLineIsIndentedCode && (match = stream.match(setextHeaderRE))) {
|
||||
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;
|
||||
} else if (state.indentation <= maxNonCodeIndentation && stream.eat('>')) {
|
||||
state.quote = firstTokenOnLine ? 1 : state.quote + 1;
|
||||
if (modeCfg.highlightFormatting) state.formatting = "quote";
|
||||
stream.eatSpace();
|
||||
return getType(state);
|
||||
} else if (stream.peek() === '[') {
|
||||
return switchInline(stream, state, footnoteLink);
|
||||
} 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;
|
||||
} else if (!isHr && !state.setext && firstTokenOnLine && state.indentation <= maxNonCodeIndentation && (match = stream.match(listRE))) {
|
||||
var listType = match[1] ? "ol" : "ul";
|
||||
|
||||
// While this list item's marker's indentation
|
||||
// is less than the deepest list item's content's indentation,
|
||||
// pop the deepest list item indentation off the stack.
|
||||
while (state.listStack && stream.column() < state.listStack[state.listStack.length - 1]) {
|
||||
state.listStack.pop();
|
||||
}
|
||||
state.indentation = lineIndentation + stream.current().length;
|
||||
state.list = true;
|
||||
state.quote = 0;
|
||||
|
||||
// Add this list item's content's indentation to the stack
|
||||
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)) {
|
||||
state.taskList = true;
|
||||
@@ -214,15 +235,47 @@ CodeMirror.defineMode("markdown", function(cmCfg, modeCfg) {
|
||||
state.f = state.inline;
|
||||
if (modeCfg.highlightFormatting) state.formatting = ["list", "list-" + listType];
|
||||
return getType(state);
|
||||
} else if (modeCfg.fencedCodeBlocks && (match = stream.match(fencedCodeRE, true))) {
|
||||
state.fencedChars = match[1]
|
||||
} else if (firstTokenOnLine && state.indentation <= maxNonCodeIndentation && (match = stream.match(fencedCodeRE, true))) {
|
||||
state.quote = 0;
|
||||
state.fencedEndRE = new RegExp(match[1] + "+ *$");
|
||||
// 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);
|
||||
state.f = state.block = local;
|
||||
if (modeCfg.highlightFormatting) state.formatting = "code-block";
|
||||
state.code = -1
|
||||
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);
|
||||
@@ -244,10 +297,21 @@ CodeMirror.defineMode("markdown", function(cmCfg, modeCfg) {
|
||||
}
|
||||
|
||||
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.f = state.block = leavingLocal;
|
||||
return null;
|
||||
state.block = blockNormal;
|
||||
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) {
|
||||
return state.localMode.token(stream, state.localState);
|
||||
} 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
|
||||
function getType(state) {
|
||||
var styles = [];
|
||||
@@ -311,8 +363,12 @@ CodeMirror.defineMode("markdown", function(cmCfg, modeCfg) {
|
||||
if (state.strong) { styles.push(tokenTypes.strong); }
|
||||
if (state.em) { styles.push(tokenTypes.em); }
|
||||
if (state.strikethrough) { styles.push(tokenTypes.strikethrough); }
|
||||
if (state.emoji) { styles.push(tokenTypes.emoji); }
|
||||
if (state.linkText) { styles.push(tokenTypes.linkText); }
|
||||
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); }
|
||||
@@ -366,7 +422,7 @@ CodeMirror.defineMode("markdown", function(cmCfg, modeCfg) {
|
||||
}
|
||||
|
||||
if (state.taskList) {
|
||||
var taskOpen = stream.match(taskListRE, true)[1] !== "x";
|
||||
var taskOpen = stream.match(taskListRE, true)[1] === " ";
|
||||
if (taskOpen) state.taskOpen = true;
|
||||
else state.taskClosed = true;
|
||||
if (modeCfg.highlightFormatting) state.formatting = "task";
|
||||
@@ -382,9 +438,6 @@ CodeMirror.defineMode("markdown", function(cmCfg, modeCfg) {
|
||||
return getType(state);
|
||||
}
|
||||
|
||||
// Get sol() value now, before character is consumed
|
||||
var sol = stream.sol();
|
||||
|
||||
var ch = stream.next();
|
||||
|
||||
// Matches link titles present on next line
|
||||
@@ -394,7 +447,7 @@ CodeMirror.defineMode("markdown", function(cmCfg, modeCfg) {
|
||||
if (ch === '(') {
|
||||
matchCh = ')';
|
||||
}
|
||||
matchCh = (matchCh+'').replace(/([.?*+^$[\]\\(){}|-])/g, "\\$1");
|
||||
matchCh = (matchCh+'').replace(/([.?*+^\[\]\\(){}|-])/g, "\\$1");
|
||||
var regex = '^\\s*(?:[^' + matchCh + '\\\\]+|\\\\\\\\|\\\\.)' + matchCh;
|
||||
if (stream.match(new RegExp(regex), true)) {
|
||||
return tokenTypes.linkHref;
|
||||
@@ -407,7 +460,7 @@ CodeMirror.defineMode("markdown", function(cmCfg, modeCfg) {
|
||||
if (modeCfg.highlightFormatting) state.formatting = "code";
|
||||
stream.eatWhile('`');
|
||||
var count = stream.current().length
|
||||
if (state.code == 0) {
|
||||
if (state.code == 0 && (!state.quote || count == 1)) {
|
||||
state.code = count
|
||||
return getType(state)
|
||||
} else if (count == state.code) { // Must be exact
|
||||
@@ -432,22 +485,40 @@ CodeMirror.defineMode("markdown", function(cmCfg, modeCfg) {
|
||||
}
|
||||
|
||||
if (ch === '!' && stream.match(/\[[^\]]*\] ?(?:\(|\[)/, false)) {
|
||||
stream.match(/\[[^\]]*\]/);
|
||||
state.inline = state.f = linkHref;
|
||||
return tokenTypes.image;
|
||||
state.imageMarker = true;
|
||||
state.image = true;
|
||||
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;
|
||||
if (modeCfg.highlightFormatting) state.formatting = "link";
|
||||
return getType(state);
|
||||
}
|
||||
|
||||
if (ch === ']' && state.linkText && stream.match(/\(.*?\)| ?\[.*?\]/, false)) {
|
||||
if (ch === ']' && state.linkText) {
|
||||
if (modeCfg.highlightFormatting) state.formatting = "link";
|
||||
var type = getType(state);
|
||||
state.linkText = false;
|
||||
state.inline = state.f = linkHref;
|
||||
state.inline = state.f = stream.match(/\(.*?\)| ?\[.*?\]/, false) ? linkHref : inlineNormal
|
||||
return type;
|
||||
}
|
||||
|
||||
@@ -475,7 +546,7 @@ CodeMirror.defineMode("markdown", function(cmCfg, modeCfg) {
|
||||
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);
|
||||
if (end != -1) {
|
||||
var atts = stream.string.substring(stream.start, end);
|
||||
@@ -486,44 +557,37 @@ CodeMirror.defineMode("markdown", function(cmCfg, modeCfg) {
|
||||
return switchBlock(stream, state, htmlBlock);
|
||||
}
|
||||
|
||||
if (ch === '<' && stream.match(/^\/\w*?>/)) {
|
||||
if (modeCfg.xml && ch === '<' && stream.match(/^\/\w*?>/)) {
|
||||
state.md_inside = false;
|
||||
return "tag";
|
||||
}
|
||||
|
||||
var ignoreUnderscore = false;
|
||||
if (!modeCfg.underscoresBreakWords) {
|
||||
if (ch === '_' && stream.peek() !== '_' && stream.match(/(\w)/, false)) {
|
||||
var prevPos = stream.pos - 2;
|
||||
if (prevPos >= 0) {
|
||||
var prevCh = stream.string.charAt(prevPos);
|
||||
if (prevCh !== '_' && prevCh.match(/(\w)/, false)) {
|
||||
ignoreUnderscore = true;
|
||||
}
|
||||
}
|
||||
} 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 (ch === '*' || (ch === '_' && !ignoreUnderscore)) {
|
||||
if (sol && stream.peek() === ' ') {
|
||||
// Do nothing, surrounded by newline and space
|
||||
} else if (state.strong === ch && stream.eat(ch)) { // Remove STRONG
|
||||
if (modeCfg.highlightFormatting) state.formatting = "strong";
|
||||
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);
|
||||
if (len > 1) { // Strong
|
||||
if (!state.strong && leftFlanking && (ch === "*" || !rightFlanking || punctuation.test(before)))
|
||||
setStrong = true
|
||||
else if (state.strong == ch && rightFlanking && (ch === "*" || !leftFlanking || punctuation.test(after)))
|
||||
setStrong = false
|
||||
}
|
||||
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 (setStrong === true) state.strong = ch
|
||||
var t = getType(state)
|
||||
if (setEm === false) state.em = false
|
||||
if (setStrong === false) state.strong = false
|
||||
return t
|
||||
}
|
||||
} else if (ch === ' ') {
|
||||
if (stream.eat('*') || stream.eat('_')) { // Probably surrounded by spaces
|
||||
@@ -548,7 +612,7 @@ CodeMirror.defineMode("markdown", function(cmCfg, modeCfg) {
|
||||
return getType(state);
|
||||
}
|
||||
} 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
|
||||
return getType(state);
|
||||
} 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 (stream.match(/ +$/, false)) {
|
||||
if (stream.match(/^ +$/, false)) {
|
||||
state.trailingSpace++;
|
||||
} else if (state.trailingSpace) {
|
||||
state.trailingSpaceNewLine = true;
|
||||
@@ -596,7 +668,7 @@ CodeMirror.defineMode("markdown", function(cmCfg, modeCfg) {
|
||||
}
|
||||
var ch = stream.next();
|
||||
if (ch === '(' || ch === '[') {
|
||||
state.f = state.inline = getLinkHrefInside(ch === "(" ? ")" : "]", 0);
|
||||
state.f = state.inline = getLinkHrefInside(ch === "(" ? ")" : "]");
|
||||
if (modeCfg.highlightFormatting) state.formatting = "link-string";
|
||||
state.linkHref = true;
|
||||
return getType(state);
|
||||
@@ -606,7 +678,7 @@ CodeMirror.defineMode("markdown", function(cmCfg, modeCfg) {
|
||||
|
||||
var linkRE = {
|
||||
")": /^(?:[^\\\(\)]|\\.|\((?:[^\\\(\)]|\\.)*\))*?(?=\))/,
|
||||
"]": /^(?:[^\\\[\]]|\\.|\[(?:[^\\\[\\]]|\\.)*\])*?(?=\])/
|
||||
"]": /^(?:[^\\\[\]]|\\.|\[(?:[^\\\[\]]|\\.)*\])*?(?=\])/
|
||||
}
|
||||
|
||||
function getLinkHrefInside(endChar) {
|
||||
@@ -639,7 +711,7 @@ CodeMirror.defineMode("markdown", function(cmCfg, modeCfg) {
|
||||
}
|
||||
|
||||
function footnoteLinkInside(stream, state) {
|
||||
if (stream.match(/^\]:/, true)) {
|
||||
if (stream.match(']:', true)) {
|
||||
state.f = state.inline = footnoteUrl;
|
||||
if (modeCfg.highlightFormatting) state.formatting = "link";
|
||||
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
|
||||
state.linkTitle = true;
|
||||
} else { // More content on line, check if link title
|
||||
stream.match(/^(?:\s+(?:"(?:[^"\\]|\\\\|\\.)+"|'(?:[^'\\]|\\\\|\\.)+'|\((?:[^)\\]|\\\\|\\.)+\)))?/, true);
|
||||
stream.match(/^(?:\s+(?:"(?:[^"\\]|\\.)+"|'(?:[^'\\]|\\.)+'|\((?:[^)\\]|\\.)+\)))?/, true);
|
||||
}
|
||||
state.f = state.inline = inlineNormal;
|
||||
return tokenTypes.linkHref + " url";
|
||||
@@ -674,8 +746,8 @@ CodeMirror.defineMode("markdown", function(cmCfg, modeCfg) {
|
||||
return {
|
||||
f: blockNormal,
|
||||
|
||||
prevLine: null,
|
||||
thisLine: null,
|
||||
prevLine: {stream: null},
|
||||
thisLine: {stream: null},
|
||||
|
||||
block: blockNormal,
|
||||
htmlState: null,
|
||||
@@ -692,6 +764,7 @@ CodeMirror.defineMode("markdown", function(cmCfg, modeCfg) {
|
||||
em: false,
|
||||
strong: false,
|
||||
header: 0,
|
||||
setext: 0,
|
||||
hr: false,
|
||||
taskList: false,
|
||||
list: false,
|
||||
@@ -700,7 +773,8 @@ CodeMirror.defineMode("markdown", function(cmCfg, modeCfg) {
|
||||
trailingSpace: 0,
|
||||
trailingSpaceNewLine: false,
|
||||
strikethrough: false,
|
||||
fencedChars: null
|
||||
emoji: false,
|
||||
fencedEndRE: null
|
||||
};
|
||||
},
|
||||
|
||||
@@ -721,12 +795,16 @@ CodeMirror.defineMode("markdown", function(cmCfg, modeCfg) {
|
||||
inline: s.inline,
|
||||
text: s.text,
|
||||
formatting: false,
|
||||
linkText: s.linkText,
|
||||
linkTitle: s.linkTitle,
|
||||
linkHref: s.linkHref,
|
||||
code: s.code,
|
||||
em: s.em,
|
||||
strong: s.strong,
|
||||
strikethrough: s.strikethrough,
|
||||
emoji: s.emoji,
|
||||
header: s.header,
|
||||
setext: s.setext,
|
||||
hr: s.hr,
|
||||
taskList: s.taskList,
|
||||
list: s.list,
|
||||
@@ -736,7 +814,7 @@ CodeMirror.defineMode("markdown", function(cmCfg, modeCfg) {
|
||||
trailingSpace: s.trailingSpace,
|
||||
trailingSpaceNewLine: s.trailingSpaceNewLine,
|
||||
md_inside: s.md_inside,
|
||||
fencedChars: s.fencedChars
|
||||
fencedEndRE: s.fencedEndRE
|
||||
};
|
||||
},
|
||||
|
||||
@@ -745,21 +823,17 @@ CodeMirror.defineMode("markdown", function(cmCfg, modeCfg) {
|
||||
// Reset state.formatting
|
||||
state.formatting = false;
|
||||
|
||||
if (stream != state.thisLine) {
|
||||
var forceBlankLine = state.header || state.hr;
|
||||
|
||||
// Reset state.header and state.hr
|
||||
if (stream != state.thisLine.stream) {
|
||||
state.header = 0;
|
||||
state.hr = false;
|
||||
|
||||
if (stream.match(/^\s*$/, true) || forceBlankLine) {
|
||||
if (stream.match(/^\s*$/, true)) {
|
||||
blankLine(state);
|
||||
if (!forceBlankLine) return null
|
||||
state.prevLine = null
|
||||
return null;
|
||||
}
|
||||
|
||||
state.prevLine = state.thisLine
|
||||
state.thisLine = stream
|
||||
state.thisLine = {stream: stream}
|
||||
|
||||
// Reset state.taskList
|
||||
state.taskList = false;
|
||||
@@ -768,11 +842,15 @@ CodeMirror.defineMode("markdown", function(cmCfg, modeCfg) {
|
||||
state.trailingSpace = 0;
|
||||
state.trailingSpaceNewLine = false;
|
||||
|
||||
state.f = state.block;
|
||||
var indentation = stream.match(/^\s*/, true)[0].replace(/\t/g, ' ').length;
|
||||
state.indentationDiff = Math.min(indentation - state.indentation, 4);
|
||||
state.indentation = state.indentation + state.indentationDiff;
|
||||
if (indentation > 0) return null;
|
||||
if (!state.localState) {
|
||||
state.f = state.block;
|
||||
if (state.f != htmlBlock) {
|
||||
var indentation = stream.match(/^\s*/, true)[0].replace(/\t/g, expandedTab).length;
|
||||
state.indentation = indentation;
|
||||
state.indentationDiff = null;
|
||||
if (indentation > 0) return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
return state.f(stream, state);
|
||||
},
|
||||
@@ -783,15 +861,26 @@ CodeMirror.defineMode("markdown", function(cmCfg, modeCfg) {
|
||||
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,
|
||||
|
||||
getType: getType,
|
||||
|
||||
blockCommentStart: "<!--",
|
||||
blockCommentEnd: "-->",
|
||||
closeBrackets: "()[]{}''\"\"``",
|
||||
fold: "markdown"
|
||||
};
|
||||
return mode;
|
||||
}, "xml");
|
||||
|
||||
CodeMirror.defineMIME("text/markdown", "markdown");
|
||||
|
||||
CodeMirror.defineMIME("text/x-markdown", "markdown");
|
||||
|
||||
});
|
||||
|
20
src/lib/codemirror/mode/pug/pug.js
vendored
20
src/lib/codemirror/mode/pug/pug.js
vendored
@@ -1,5 +1,5 @@
|
||||
// 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) {
|
||||
if (typeof exports == "object" && typeof module == "object") // CommonJS
|
||||
@@ -261,7 +261,7 @@ CodeMirror.defineMode("pug", function (config) {
|
||||
}
|
||||
return 'variable';
|
||||
}
|
||||
if (stream.match(/^\+#{/, false)) {
|
||||
if (stream.match('+#{', false)) {
|
||||
stream.next();
|
||||
state.mixinCallAfter = true;
|
||||
return interpolation(stream, state);
|
||||
@@ -545,12 +545,12 @@ CodeMirror.defineMode("pug", function (config) {
|
||||
|| javaScriptArguments(stream, state)
|
||||
|| callArguments(stream, state)
|
||||
|
||||
|| yieldStatement(stream, state)
|
||||
|| doctype(stream, state)
|
||||
|| yieldStatement(stream)
|
||||
|| doctype(stream)
|
||||
|| interpolation(stream, state)
|
||||
|| caseStatement(stream, state)
|
||||
|| when(stream, state)
|
||||
|| defaultStatement(stream, state)
|
||||
|| defaultStatement(stream)
|
||||
|| extendsStatement(stream, state)
|
||||
|| append(stream, state)
|
||||
|| prepend(stream, state)
|
||||
@@ -565,16 +565,16 @@ CodeMirror.defineMode("pug", function (config) {
|
||||
|| tag(stream, state)
|
||||
|| filter(stream, state)
|
||||
|| code(stream, state)
|
||||
|| id(stream, state)
|
||||
|| className(stream, state)
|
||||
|| id(stream)
|
||||
|| className(stream)
|
||||
|| attrs(stream, state)
|
||||
|| attributesBlock(stream, state)
|
||||
|| indent(stream, state)
|
||||
|| indent(stream)
|
||||
|| text(stream, state)
|
||||
|| comment(stream, state)
|
||||
|| colon(stream, state)
|
||||
|| colon(stream)
|
||||
|| dot(stream, state)
|
||||
|| fail(stream, state);
|
||||
|| fail(stream);
|
||||
|
||||
return tok === true ? null : tok;
|
||||
}
|
||||
|
107
src/lib/codemirror/mode/sass/sass.js
vendored
107
src/lib/codemirror/mode/sass/sass.js
vendored
@@ -1,17 +1,23 @@
|
||||
// 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) {
|
||||
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
|
||||
define(["../../lib/codemirror"], mod);
|
||||
define(["../../lib/codemirror", "../css/css"], mod);
|
||||
else // Plain browser env
|
||||
mod(CodeMirror);
|
||||
})(function(CodeMirror) {
|
||||
"use strict";
|
||||
|
||||
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) {
|
||||
return new RegExp("^" + words.join("|"));
|
||||
}
|
||||
@@ -25,6 +31,12 @@ CodeMirror.defineMode("sass", function(config) {
|
||||
|
||||
var pseudoElementsRegexp = /^::?[a-zA-Z_][\w\-]*/;
|
||||
|
||||
var word;
|
||||
|
||||
function isEndLine(stream) {
|
||||
return !stream.peek() || stream.match(/\s+$/, false);
|
||||
}
|
||||
|
||||
function urlTokens(stream, state) {
|
||||
var ch = stream.peek();
|
||||
|
||||
@@ -76,6 +88,9 @@ CodeMirror.defineMode("sass", function(config) {
|
||||
|
||||
if (endingString) {
|
||||
if (nextChar !== quote && greedy) { stream.next(); }
|
||||
if (isEndLine(stream)) {
|
||||
state.cursorHalf = 0;
|
||||
}
|
||||
state.tokenizer = tokenBase;
|
||||
return "string";
|
||||
} else if (nextChar === "#" && peekChar === "{") {
|
||||
@@ -147,14 +162,20 @@ CodeMirror.defineMode("sass", function(config) {
|
||||
// first half i.e. before : for key-value pairs
|
||||
// including selectors
|
||||
|
||||
if (ch === "-") {
|
||||
if (stream.match(/^-\w+-/)) {
|
||||
return "meta";
|
||||
}
|
||||
}
|
||||
|
||||
if (ch === ".") {
|
||||
stream.next();
|
||||
if (stream.match(/^[\w-]+/)) {
|
||||
indent(state);
|
||||
return "atom";
|
||||
return "qualifier";
|
||||
} else if (stream.peek() === "#") {
|
||||
indent(state);
|
||||
return "atom";
|
||||
return "tag";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -163,11 +184,11 @@ CodeMirror.defineMode("sass", function(config) {
|
||||
// ID selectors
|
||||
if (stream.match(/^[\w-]+/)) {
|
||||
indent(state);
|
||||
return "atom";
|
||||
return "builtin";
|
||||
}
|
||||
if (stream.peek() === "#") {
|
||||
indent(state);
|
||||
return "atom";
|
||||
return "tag";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -210,7 +231,7 @@ CodeMirror.defineMode("sass", function(config) {
|
||||
}
|
||||
|
||||
if(ch === "@"){
|
||||
if(stream.match(/@extend/)){
|
||||
if(stream.match('@extend')){
|
||||
if(!stream.match(/\s*[\w]/))
|
||||
dedent(state);
|
||||
}
|
||||
@@ -220,37 +241,48 @@ CodeMirror.defineMode("sass", function(config) {
|
||||
// Indent Directives
|
||||
if (stream.match(/^@(else if|if|media|else|for|each|while|mixin|function)/)) {
|
||||
indent(state);
|
||||
return "meta";
|
||||
return "def";
|
||||
}
|
||||
|
||||
// Other Directives
|
||||
if (ch === "@") {
|
||||
stream.next();
|
||||
stream.eatWhile(/[\w-]/);
|
||||
return "meta";
|
||||
return "def";
|
||||
}
|
||||
|
||||
if (stream.eatWhile(/[\w-]/)){
|
||||
if(stream.match(/ *: *[\w-\+\$#!\("']/,false)){
|
||||
return "property";
|
||||
word = stream.current().toLowerCase();
|
||||
var prop = state.prevProp + "-" + word;
|
||||
if (propertyKeywords.hasOwnProperty(prop)) {
|
||||
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)){
|
||||
indent(state);
|
||||
state.cursorHalf = 1;
|
||||
return "atom";
|
||||
state.prevProp = stream.current().toLowerCase();
|
||||
return "property";
|
||||
}
|
||||
else if(stream.match(/ *,/,false)){
|
||||
return "atom";
|
||||
return "tag";
|
||||
}
|
||||
else{
|
||||
indent(state);
|
||||
return "atom";
|
||||
return "tag";
|
||||
}
|
||||
}
|
||||
|
||||
if(ch === ":"){
|
||||
if (stream.match(pseudoElementsRegexp)){ // could be a pseudo-element
|
||||
return "keyword";
|
||||
return "variable-3";
|
||||
}
|
||||
stream.next();
|
||||
state.cursorHalf=1;
|
||||
@@ -264,7 +296,7 @@ CodeMirror.defineMode("sass", function(config) {
|
||||
stream.next();
|
||||
// Hex numbers
|
||||
if (stream.match(/[0-9a-fA-F]{6}|[0-9a-fA-F]{3}/)){
|
||||
if(!stream.peek()){
|
||||
if (isEndLine(stream)) {
|
||||
state.cursorHalf = 0;
|
||||
}
|
||||
return "number";
|
||||
@@ -273,7 +305,7 @@ CodeMirror.defineMode("sass", function(config) {
|
||||
|
||||
// Numbers
|
||||
if (stream.match(/^-?[0-9\.]+/)){
|
||||
if(!stream.peek()){
|
||||
if (isEndLine(stream)) {
|
||||
state.cursorHalf = 0;
|
||||
}
|
||||
return "number";
|
||||
@@ -281,14 +313,14 @@ CodeMirror.defineMode("sass", function(config) {
|
||||
|
||||
// Units
|
||||
if (stream.match(/^(px|em|in)\b/)){
|
||||
if(!stream.peek()){
|
||||
if (isEndLine(stream)) {
|
||||
state.cursorHalf = 0;
|
||||
}
|
||||
return "unit";
|
||||
}
|
||||
|
||||
if (stream.match(keywordsRegexp)){
|
||||
if(!stream.peek()){
|
||||
if (isEndLine(stream)) {
|
||||
state.cursorHalf = 0;
|
||||
}
|
||||
return "keyword";
|
||||
@@ -296,7 +328,7 @@ CodeMirror.defineMode("sass", function(config) {
|
||||
|
||||
if (stream.match(/^url/) && stream.peek() === "(") {
|
||||
state.tokenizer = urlTokens;
|
||||
if(!stream.peek()){
|
||||
if (isEndLine(stream)) {
|
||||
state.cursorHalf = 0;
|
||||
}
|
||||
return "atom";
|
||||
@@ -306,23 +338,21 @@ CodeMirror.defineMode("sass", function(config) {
|
||||
if (ch === "$") {
|
||||
stream.next();
|
||||
stream.eatWhile(/[\w-]/);
|
||||
if(!stream.peek()){
|
||||
if (isEndLine(stream)) {
|
||||
state.cursorHalf = 0;
|
||||
}
|
||||
return "variable-3";
|
||||
return "variable-2";
|
||||
}
|
||||
|
||||
// bang character for !important, !default, etc.
|
||||
if (ch === "!") {
|
||||
stream.next();
|
||||
if(!stream.peek()){
|
||||
state.cursorHalf = 0;
|
||||
}
|
||||
state.cursorHalf = 0;
|
||||
return stream.match(/^[\w]+/) ? "keyword": "operator";
|
||||
}
|
||||
|
||||
if (stream.match(opRegexp)){
|
||||
if(!stream.peek()){
|
||||
if (isEndLine(stream)) {
|
||||
state.cursorHalf = 0;
|
||||
}
|
||||
return "operator";
|
||||
@@ -330,14 +360,24 @@ CodeMirror.defineMode("sass", function(config) {
|
||||
|
||||
// attributes
|
||||
if (stream.eatWhile(/[\w-]/)) {
|
||||
if(!stream.peek()){
|
||||
if (isEndLine(stream)) {
|
||||
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();
|
||||
if(!stream.peek()){
|
||||
if (isEndLine(stream)) {
|
||||
state.cursorHalf = 0;
|
||||
return null;
|
||||
}
|
||||
@@ -405,9 +445,14 @@ CodeMirror.defineMode("sass", function(config) {
|
||||
|
||||
indent: function(state) {
|
||||
return state.scopes[0].offset;
|
||||
}
|
||||
},
|
||||
|
||||
blockCommentStart: "/*",
|
||||
blockCommentEnd: "*/",
|
||||
lineComment: "//",
|
||||
fold: "indent"
|
||||
};
|
||||
});
|
||||
}, "css");
|
||||
|
||||
CodeMirror.defineMIME("text/x-sass", "sass");
|
||||
|
||||
|
26
src/lib/codemirror/mode/stylus/stylus.js
vendored
26
src/lib/codemirror/mode/stylus/stylus.js
vendored
File diff suppressed because one or more lines are too long
43
src/lib/codemirror/mode/xml/xml.js
vendored
43
src/lib/codemirror/mode/xml/xml.js
vendored
@@ -1,5 +1,5 @@
|
||||
// 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) {
|
||||
if (typeof exports == "object" && typeof module == "object") // CommonJS
|
||||
@@ -52,6 +52,7 @@ var xmlConfig = {
|
||||
doNotIndent: {},
|
||||
allowUnquoted: false,
|
||||
allowMissing: false,
|
||||
allowMissingTagName: false,
|
||||
caseFold: false
|
||||
}
|
||||
|
||||
@@ -162,8 +163,9 @@ CodeMirror.defineMode("xml", function(editorConf, config_) {
|
||||
stream.next();
|
||||
}
|
||||
return style;
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
function doctype(depth) {
|
||||
return function(stream, state) {
|
||||
var ch;
|
||||
@@ -185,9 +187,13 @@ CodeMirror.defineMode("xml", function(editorConf, config_) {
|
||||
};
|
||||
}
|
||||
|
||||
function lower(tagName) {
|
||||
return tagName && tagName.toLowerCase();
|
||||
}
|
||||
|
||||
function Context(state, tagName, startOfLine) {
|
||||
this.prev = state.context;
|
||||
this.tagName = tagName;
|
||||
this.tagName = tagName || "";
|
||||
this.indent = state.indented;
|
||||
this.startOfLine = startOfLine;
|
||||
if (config.doNotIndent.hasOwnProperty(tagName) || (state.context && state.context.noIndent))
|
||||
@@ -203,8 +209,8 @@ CodeMirror.defineMode("xml", function(editorConf, config_) {
|
||||
return;
|
||||
}
|
||||
parentTagName = state.context.tagName;
|
||||
if (!config.contextGrabbers.hasOwnProperty(parentTagName) ||
|
||||
!config.contextGrabbers[parentTagName].hasOwnProperty(nextTagName)) {
|
||||
if (!config.contextGrabbers.hasOwnProperty(lower(parentTagName)) ||
|
||||
!config.contextGrabbers[lower(parentTagName)].hasOwnProperty(lower(nextTagName))) {
|
||||
return;
|
||||
}
|
||||
popContext(state);
|
||||
@@ -226,6 +232,9 @@ CodeMirror.defineMode("xml", function(editorConf, config_) {
|
||||
state.tagName = stream.current();
|
||||
setStyle = "tag";
|
||||
return attrState;
|
||||
} else if (config.allowMissingTagName && type == "endTag") {
|
||||
setStyle = "tag bracket";
|
||||
return attrState(type, stream, state);
|
||||
} else {
|
||||
setStyle = "error";
|
||||
return tagNameState;
|
||||
@@ -235,7 +244,7 @@ CodeMirror.defineMode("xml", function(editorConf, config_) {
|
||||
if (type == "word") {
|
||||
var tagName = stream.current();
|
||||
if (state.context && state.context.tagName != tagName &&
|
||||
config.implicitlyClosed.hasOwnProperty(state.context.tagName))
|
||||
config.implicitlyClosed.hasOwnProperty(lower(state.context.tagName)))
|
||||
popContext(state);
|
||||
if ((state.context && state.context.tagName == tagName) || config.matchClosing === false) {
|
||||
setStyle = "tag";
|
||||
@@ -244,6 +253,9 @@ CodeMirror.defineMode("xml", function(editorConf, config_) {
|
||||
setStyle = "tag error";
|
||||
return closeStateErr;
|
||||
}
|
||||
} else if (config.allowMissingTagName && type == "endTag") {
|
||||
setStyle = "tag bracket";
|
||||
return closeState(type, stream, state);
|
||||
} else {
|
||||
setStyle = "error";
|
||||
return closeStateErr;
|
||||
@@ -271,7 +283,7 @@ CodeMirror.defineMode("xml", function(editorConf, config_) {
|
||||
var tagName = state.tagName, tagStart = state.tagStart;
|
||||
state.tagName = state.tagStart = null;
|
||||
if (type == "selfcloseTag" ||
|
||||
config.autoSelfClosers.hasOwnProperty(tagName)) {
|
||||
config.autoSelfClosers.hasOwnProperty(lower(tagName))) {
|
||||
maybePopContext(state, tagName);
|
||||
} else {
|
||||
maybePopContext(state, tagName);
|
||||
@@ -351,7 +363,7 @@ CodeMirror.defineMode("xml", function(editorConf, config_) {
|
||||
if (context.tagName == tagAfter[2]) {
|
||||
context = context.prev;
|
||||
break;
|
||||
} else if (config.implicitlyClosed.hasOwnProperty(context.tagName)) {
|
||||
} else if (config.implicitlyClosed.hasOwnProperty(lower(context.tagName))) {
|
||||
context = context.prev;
|
||||
} else {
|
||||
break;
|
||||
@@ -359,8 +371,8 @@ CodeMirror.defineMode("xml", function(editorConf, config_) {
|
||||
}
|
||||
} else if (tagAfter) { // Opening tag spotted
|
||||
while (context) {
|
||||
var grabbers = config.contextGrabbers[context.tagName];
|
||||
if (grabbers && grabbers.hasOwnProperty(tagAfter[2]))
|
||||
var grabbers = config.contextGrabbers[lower(context.tagName)];
|
||||
if (grabbers && grabbers.hasOwnProperty(lower(tagAfter[2])))
|
||||
context = context.prev;
|
||||
else
|
||||
break;
|
||||
@@ -382,6 +394,17 @@ CodeMirror.defineMode("xml", function(editorConf, config_) {
|
||||
skipAttribute: function(state) {
|
||||
if (state.state == attrValueState)
|
||||
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
30
src/lib/hint.min.css
vendored
File diff suppressed because one or more lines are too long
@@ -95,9 +95,14 @@ export const jsLibs = [
|
||||
type: 'js'
|
||||
},
|
||||
{
|
||||
url: 'https://cdn.tailwindcss.com/3.3.1',
|
||||
url: 'https://cdn.tailwindcss.com/3.4.3',
|
||||
label: 'Tailwind 3',
|
||||
type: 'js'
|
||||
},
|
||||
{
|
||||
url: 'https://unpkg.com/kaboom@3000/dist/kaboom.js',
|
||||
label: 'Kaboom',
|
||||
type: 'js'
|
||||
}
|
||||
];
|
||||
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',
|
||||
type: 'css'
|
||||
},
|
||||
|
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "Web Maker",
|
||||
"version": "5.2.0",
|
||||
"manifest_version": 3,
|
||||
"version": "6.0.0",
|
||||
"manifest_version": 2,
|
||||
"description": "Blazing fast & offline playground for your web experiments",
|
||||
"homepage_url": "https://webmaker.app",
|
||||
"permissions": ["storage", "tabs"],
|
||||
@@ -9,7 +9,7 @@
|
||||
"host_permissions": ["<all_urls>"],
|
||||
"content_security_policy": {
|
||||
"extension_pages": "script-src 'self'; object-src 'self'",
|
||||
"sandbox": "sandbox allow-scripts allow-forms allow-popups allow-modals; script-src 'self' filesystem: http://localhost:* https://localhost:* https://apis.google.com https://ajax.googleapis.com https://code.jquery.com https://cdnjs.cloudflare.com https://unpkg.com https://maxcdn.com https://cdn77.com https://maxcdn.bootstrapcdn.com https://cdn.jsdelivr.net/ https://*.stripe.com/ https://builds.framerjs.com/ https://rawgit.com https://wzrd.in https://www.gstatic.com https://semantic-ui.com https://www.google-analytics.com https://cdn.tailwindcss.com '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": {
|
||||
"page": "options.html",
|
||||
|
@@ -35,7 +35,7 @@
|
||||
<body>
|
||||
<h3>
|
||||
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>
|
||||
<form name="optionsForm">
|
||||
<label>
|
||||
|
581
src/style.css
581
src/style.css
@@ -3,16 +3,33 @@
|
||||
--color-text-dark-1: #b3aec4;
|
||||
--color-bg: #252637;
|
||||
--color-popup: #3a2b63;
|
||||
--color-overlay: rgb(0 0 0 / 40%);
|
||||
--color-close-btn: #d12b4a;
|
||||
--code-font-size: 16px;
|
||||
--color-button: #d3a447;
|
||||
--color-button: #e3ba26;
|
||||
--color-focus-outline: #d3a447;
|
||||
--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;
|
||||
--console-height: 32px;
|
||||
|
||||
--duration-modal-show: 0.3s;
|
||||
--duration-modal-overlay-show: 0.2s;
|
||||
|
||||
--zindex-modal-overlay: 5;
|
||||
--zindex-footer: 6;
|
||||
--zindex-modal: 2000;
|
||||
}
|
||||
|
||||
html {
|
||||
@@ -85,6 +102,7 @@ p {
|
||||
button {
|
||||
font-family: inherit;
|
||||
font-size: 100%;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.hide {
|
||||
@@ -120,11 +138,13 @@ button {
|
||||
.ai-c {
|
||||
align-items: center;
|
||||
}
|
||||
.flex-h-center {
|
||||
.flex-h-center,
|
||||
.jc-c {
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.flex-h-end {
|
||||
.flex-h-end,
|
||||
.jc-fe {
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
@@ -213,6 +233,10 @@ button {
|
||||
color: #f7ae2d;
|
||||
}
|
||||
|
||||
.para {
|
||||
max-width: 60ch;
|
||||
}
|
||||
|
||||
@media screen and (max-width: 600px) {
|
||||
.block--mobile {
|
||||
display: block;
|
||||
@@ -256,11 +280,17 @@ label {
|
||||
width: 1px;
|
||||
height: 100%;
|
||||
}
|
||||
[class*='hint--']:after,
|
||||
[class*='hint--']:before {
|
||||
background-color: #000;
|
||||
border-radius: 0 4px 0 0;
|
||||
}
|
||||
[class*='hint--']:after {
|
||||
text-transform: none;
|
||||
font-weight: normal;
|
||||
letter-spacing: 0.5px;
|
||||
font-size: 14px;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.line {
|
||||
@@ -373,6 +403,7 @@ a > svg {
|
||||
float: right;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-inline-start: 2rem;
|
||||
}
|
||||
|
||||
.btn {
|
||||
@@ -382,7 +413,7 @@ a > svg {
|
||||
font-size: inherit;
|
||||
background: transparent;
|
||||
border: 3px solid var(--color-button);
|
||||
border-radius: 5px;
|
||||
border-radius: 2rem;
|
||||
padding: 9px 15px;
|
||||
cursor: pointer;
|
||||
letter-spacing: 0.2px;
|
||||
@@ -394,15 +425,22 @@ a > svg {
|
||||
}
|
||||
|
||||
.btn--primary {
|
||||
background: var(--color-button)
|
||||
linear-gradient(180deg, rgba(0, 0, 0, 0.15) 0px, transparent);
|
||||
--black-mix: 70%;
|
||||
background: linear-gradient(
|
||||
180deg,
|
||||
var(--color-button),
|
||||
color-mix(in lch, var(--color-button), black)
|
||||
);
|
||||
color: black;
|
||||
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 {
|
||||
padding: 15px 30px;
|
||||
border-radius: 3px;
|
||||
border-radius: 0.5rem;
|
||||
}
|
||||
|
||||
.btn-icon {
|
||||
@@ -410,11 +448,25 @@ a > svg {
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.btn:hover {
|
||||
text-decoration: none;
|
||||
box-shadow: 0 5px 5px 0 rgba(0, 0, 0, 0.25);
|
||||
.btn--small {
|
||||
padding: 0.2rem 0.5rem;
|
||||
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 {
|
||||
outline-width: 3px;
|
||||
outline-color: var(--color-button);
|
||||
@@ -431,9 +483,34 @@ a > svg {
|
||||
}
|
||||
|
||||
.btn--big > svg {
|
||||
width: 25px;
|
||||
height: 25px;
|
||||
margin-right: 12px;
|
||||
width: 2rem;
|
||||
height: 2rem;
|
||||
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 {
|
||||
@@ -472,15 +549,13 @@ a > svg {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow: hidden;
|
||||
transition: 0.1s ease;
|
||||
transition: 0.25s ease;
|
||||
will-change: filter, transform;
|
||||
}
|
||||
|
||||
body:not(.light-version).overlay-visible .main-container {
|
||||
transition-duration: 0.5s;
|
||||
transform: scale(0.98);
|
||||
/* transition-delay: 0.4s; */
|
||||
filter: blur(2px);
|
||||
}
|
||||
|
||||
.content-wrap {
|
||||
@@ -560,7 +635,7 @@ body:not(.light-version).overlay-visible .main-container {
|
||||
}
|
||||
|
||||
.is-detached-mode.layout-2 .code-side {
|
||||
height: auto !important;
|
||||
height: 100% !important;
|
||||
}
|
||||
|
||||
.code-wrap {
|
||||
@@ -571,9 +646,7 @@ body:not(.light-version).overlay-visible .main-container {
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
background: var(--color-bg);
|
||||
transition:
|
||||
height 0.3s ease,
|
||||
width 0.3s ease;
|
||||
transition: height 0.3s ease, width 0.3s ease;
|
||||
will-change: height;
|
||||
}
|
||||
|
||||
@@ -762,7 +835,7 @@ body > #demo-frame {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
/* Because .console is 6 */
|
||||
z-index: 6;
|
||||
z-index: var(--zindex-footer);
|
||||
}
|
||||
|
||||
.main-header {
|
||||
@@ -774,6 +847,8 @@ body > #demo-frame {
|
||||
|
||||
.btn--dark,
|
||||
.main-header__btn-wrap > button {
|
||||
--clr-1: hsl(0, 0%, 25%);
|
||||
--clr-2: hsl(0, 0%, 13%);
|
||||
box-sizing: content-box;
|
||||
/* text-transform: uppercase; */
|
||||
/* font-size: 0.875rem; */
|
||||
@@ -787,7 +862,8 @@ body > #demo-frame {
|
||||
margin-left: 10px;
|
||||
padding: 3px 8px;
|
||||
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);
|
||||
}
|
||||
|
||||
@@ -805,13 +881,20 @@ body > #demo-frame {
|
||||
}
|
||||
|
||||
.btn--dark:hover {
|
||||
background: #9297b3;
|
||||
/* --clr-1: #6844ad; */
|
||||
|
||||
--clr-1: hsl(53.35deg 100% 50%);
|
||||
--clr-2: hsl(38.96deg 100% 50%);
|
||||
color: #111;
|
||||
/* border-color: rgba(146, 151, 179, 0.5); */
|
||||
}
|
||||
.btn--dark:hover > svg {
|
||||
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 {
|
||||
box-shadow: none;
|
||||
background: transparent;
|
||||
@@ -938,6 +1021,29 @@ body > #demo-frame {
|
||||
/* 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 {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
@@ -949,13 +1055,23 @@ body > #demo-frame {
|
||||
display: flex;
|
||||
align-items: baseline;
|
||||
justify-content: center;
|
||||
z-index: 2000;
|
||||
z-index: var(--zindex-modal);
|
||||
visibility: hidden;
|
||||
/* background-color: rgba(102, 51, 153, 0.7); */
|
||||
/* background-color: rgba(0, 0, 0, 0.7); */
|
||||
/* 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;
|
||||
}
|
||||
.modal--no-overlay {
|
||||
background: none;
|
||||
backdrop-filter: none;
|
||||
}
|
||||
|
||||
@keyframes anim-modal-overlay {
|
||||
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 {
|
||||
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 {
|
||||
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);
|
||||
position: relative;
|
||||
border-radius: 5px;
|
||||
@@ -998,9 +1108,9 @@ body > #demo-frame {
|
||||
font-size: 1.1em;
|
||||
line-height: 1.4;
|
||||
max-width: 85vw;
|
||||
margin: 2rem auto;
|
||||
margin: 4rem auto 2rem;
|
||||
box-sizing: border-box;
|
||||
overflow-y: auto;
|
||||
/* overflow-y: auto; */
|
||||
pointer-events: auto;
|
||||
transform: scale(0.98);
|
||||
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; */
|
||||
/* transform: translateY(0px) scale(1); */
|
||||
/* opacity: 1; */
|
||||
backdrop-filter: blur(3px);
|
||||
}
|
||||
|
||||
.modal-overlay {
|
||||
@@ -1038,10 +1149,11 @@ body > #demo-frame {
|
||||
visibility: hidden;
|
||||
top: 0;
|
||||
left: 0;
|
||||
z-index: 5;
|
||||
z-index: var(--zindex-modal-overlay);
|
||||
opacity: 0;
|
||||
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);
|
||||
}
|
||||
|
||||
@@ -1071,11 +1183,13 @@ body > #demo-frame {
|
||||
right: 0;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
width: 450px;
|
||||
width: 35vw;
|
||||
min-width: 40ch;
|
||||
max-width: 60ch;
|
||||
padding: 20px 30px;
|
||||
z-index: 6;
|
||||
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-property: transform;
|
||||
will-change: transform;
|
||||
@@ -1096,23 +1210,16 @@ body > #demo-frame {
|
||||
}
|
||||
|
||||
.saved-items-pane__close-btn {
|
||||
position: absolute;
|
||||
left: -18px;
|
||||
top: 24px;
|
||||
opacity: 0;
|
||||
visibility: hidden;
|
||||
border-radius: 50%;
|
||||
padding: 10px 14px;
|
||||
background: crimson;
|
||||
color: white;
|
||||
border: 0;
|
||||
transform: scale(0);
|
||||
will-change: transform, opacity;
|
||||
transition: 0.3s ease;
|
||||
transition-property: transform, opacity;
|
||||
transition-delay: 0;
|
||||
}
|
||||
|
||||
.saved-items-pane.is-open .saved-items-pane__close-btn {
|
||||
opacity: 1;
|
||||
transition-delay: 0.4s;
|
||||
@@ -1124,8 +1231,6 @@ body > #demo-frame {
|
||||
padding: 20px;
|
||||
background-color: rgba(255, 255, 255, 0.06);
|
||||
position: relative;
|
||||
/*border: 1px solid rgba(255,255,255,0.1);*/
|
||||
margin: 20px 0;
|
||||
display: block;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
@@ -1135,6 +1240,10 @@ body > #demo-frame {
|
||||
box-shadow: 0 2px 4px 0 rgba(0, 0, 0, 0.2);
|
||||
/* animation: slide-left 0.35s ease forwards; */
|
||||
}
|
||||
.saved-item-tile + .saved-item-tile {
|
||||
margin-top: 1rem;
|
||||
}
|
||||
|
||||
.saved-item-tile--inline {
|
||||
display: inline-block;
|
||||
margin-left: 10px;
|
||||
@@ -1212,6 +1321,8 @@ body > #demo-frame {
|
||||
.saved-item-tile__btns {
|
||||
position: absolute;
|
||||
top: 6px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
z-index: 1;
|
||||
right: 8px;
|
||||
opacity: 0;
|
||||
@@ -1230,14 +1341,21 @@ body > #demo-frame {
|
||||
}
|
||||
|
||||
.saved-item-tile__btn {
|
||||
display: inline-flex;
|
||||
padding: 7px 10px;
|
||||
color: white;
|
||||
border: 0;
|
||||
border-radius: 20px;
|
||||
border-radius: 1in;
|
||||
font-size: 0.8rem;
|
||||
font-weight: 700;
|
||||
margin-left: 2px;
|
||||
background: rgba(255, 255, 255, 0.1);
|
||||
text-transform: uppercase;
|
||||
}
|
||||
.saved-item-tile__btn > svg {
|
||||
width: 1rem;
|
||||
aspect-ratio: 1;
|
||||
}
|
||||
|
||||
.saved-item-tile__btn:hover {
|
||||
background: rgba(255, 255, 255, 0.8);
|
||||
@@ -1260,7 +1378,9 @@ body > #demo-frame {
|
||||
.saved-items-pane__container {
|
||||
overflow-y: auto;
|
||||
overflow-x: hidden;
|
||||
max-height: calc(100vh - 90px);
|
||||
max-height: calc(100vh - 130px);
|
||||
margin-top: 1rem;
|
||||
padding-inline: 1rem;
|
||||
scroll-behavior: smooth;
|
||||
}
|
||||
|
||||
@@ -1484,13 +1604,16 @@ body > #demo-frame {
|
||||
}
|
||||
|
||||
.onboard-step {
|
||||
margin: 15px;
|
||||
padding: 20px 30px;
|
||||
margin: 0.5rem;
|
||||
padding: 1rem;
|
||||
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);
|
||||
flex: 1;
|
||||
}
|
||||
.onboard-step--full-width {
|
||||
grid-column: 1 / -1;
|
||||
}
|
||||
|
||||
.onboard-step__icon {
|
||||
fill: rgba(255, 255, 255, 0.3);
|
||||
@@ -1644,38 +1767,22 @@ body > #demo-frame {
|
||||
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 {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.social-login-btn--github {
|
||||
color: white;
|
||||
background: #656b6f;
|
||||
border-color: #656b6f;
|
||||
}
|
||||
|
||||
.social-login-btn--facebook {
|
||||
color: white;
|
||||
background: #4e62c0;
|
||||
border-color: #4e62c0;
|
||||
}
|
||||
|
||||
.social-login-btn--google {
|
||||
.social-login-btn {
|
||||
background: white;
|
||||
border: 2px solid currentColor;
|
||||
color: black;
|
||||
}
|
||||
|
||||
.social-login-btn--github {
|
||||
}
|
||||
|
||||
.social-login-btn--google {
|
||||
}
|
||||
|
||||
body.is-logged-in .hide-on-login,
|
||||
body:not(.is-logged-in) .hide-on-logout,
|
||||
body:not(.is-extension) .show-when-extension,
|
||||
@@ -2008,6 +2115,300 @@ while the theme CSS file is loading */
|
||||
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) {
|
||||
body {
|
||||
font-size: 70%;
|
||||
|
@@ -15,10 +15,25 @@ export default [
|
||||
title: 'Preact',
|
||||
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',
|
||||
title: 'Kontra Game Engine',
|
||||
img: 'assets/html5-logo.svg',
|
||||
isFileModeSupported: true
|
||||
},
|
||||
{
|
||||
id: 'kaboom',
|
||||
title: 'Kaboom',
|
||||
img: 'assets/kaboom-logo.png'
|
||||
}
|
||||
];
|
||||
|
18
src/templates/template-kaboom.json
Normal file
18
src/templates/template-kaboom.json
Normal 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]
|
||||
}
|
@@ -1,16 +1,16 @@
|
||||
{
|
||||
"title": "Preact template",
|
||||
"externalLibs": {
|
||||
"js": "\nhttps://cdnjs.cloudflare.com/ajax/libs/preact/8.2.9/preact.min.js",
|
||||
"css": ""
|
||||
},
|
||||
"sizes": ["calc(30% - 3px)", "30px", "calc(70% - 3px)"],
|
||||
"mainSizes": [68.1051, 31.6949],
|
||||
"sizes": [30, "30px", 70],
|
||||
"mainSizes": [60, 40],
|
||||
|
||||
"htmlMode": "html",
|
||||
"cssMode": "css",
|
||||
"jsMode": "es6",
|
||||
"layoutMode": 1,
|
||||
"js":
|
||||
"\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",
|
||||
"externalLibs": {
|
||||
"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"
|
||||
}
|
||||
|
17
src/templates/template-tailwind.json
Normal file
17
src/templates/template-tailwind.json
Normal 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]
|
||||
}
|
17
src/templates/template-tailwind2.json
Normal file
17
src/templates/template-tailwind2.json
Normal 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]
|
||||
}
|
89
src/utils.js
89
src/utils.js
@@ -1,9 +1,9 @@
|
||||
import { trackEvent } from './analytics';
|
||||
|
||||
import { computeHtml, computeCss, computeJs } from './computes';
|
||||
import { modes, HtmlModes, CssModes, JsModes } from './codeModes';
|
||||
import { deferred } from './deferred';
|
||||
import { getExtensionFromFileName } from './fileUtils';
|
||||
import confetti from 'canvas-confetti';
|
||||
const esprima = require('esprima');
|
||||
|
||||
window.DEBUG = document.cookie.indexOf('wmdebug') > -1;
|
||||
@@ -23,7 +23,7 @@ export const BASE_PATH =
|
||||
window.DEBUG ||
|
||||
process.env.NODE_ENV === 'development'
|
||||
? '/'
|
||||
: '/app';
|
||||
: '/create';
|
||||
/* eslint-enable no-process-env */
|
||||
|
||||
var alphaNum = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789';
|
||||
@@ -205,6 +205,41 @@ export function getHumanDate(timestamp) {
|
||||
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
|
||||
export function once(node, type, callback) {
|
||||
// create event
|
||||
@@ -585,3 +620,53 @@ if (window.IS_EXTENSION) {
|
||||
} else {
|
||||
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));
|
||||
}
|
||||
|
Reference in New Issue
Block a user