pinstripe 0.26.0 → 0.29.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +1 -1
- package/README.md +1 -3
- package/babel.config.cjs +18 -0
- package/cli.js +46 -0
- package/jest.config.cjs +6 -0
- package/lib/app.js +37 -0
- package/lib/apps/_file_importer.js +1 -0
- package/lib/apps/main.js +6 -0
- package/lib/class.js +63 -1
- package/lib/client.js +20 -0
- package/lib/command.js +43 -0
- package/lib/commands/_file_importer.js +1 -0
- package/lib/commands/drop_database.js +6 -0
- package/lib/commands/generate_app.js +41 -0
- package/lib/commands/generate_command.js +39 -0
- package/lib/commands/generate_component.js +51 -0
- package/lib/commands/generate_migration.js +55 -0
- package/lib/commands/generate_model.js +43 -0
- package/lib/commands/generate_project.js +142 -0
- package/lib/commands/generate_service.js +34 -0
- package/lib/commands/generate_static_site.js +94 -0
- package/lib/commands/generate_view.js +80 -0
- package/lib/commands/init_database.js +9 -0
- package/lib/commands/list_apps.js +15 -0
- package/lib/commands/list_commands.js +15 -0
- package/lib/commands/list_components.js +16 -0
- package/lib/commands/list_migrations.js +15 -0
- package/lib/commands/list_models.js +15 -0
- package/lib/commands/list_services.js +15 -0
- package/lib/commands/list_views.js +39 -0
- package/lib/commands/migrate_database.js +6 -0
- package/lib/commands/reset_database.js +9 -0
- package/lib/commands/seed_database.js +6 -0
- package/lib/commands/show_config.js +6 -0
- package/lib/commands/start_repl.js +6 -0
- package/lib/commands/start_server.js +31 -0
- package/lib/component.js +102 -3
- package/lib/components/helpers.js +18 -5
- package/lib/components/{a.js → pinstripe_anchor.js} +4 -4
- package/lib/components/{document.js → pinstripe_document.js} +2 -2
- package/lib/components/{form.js → pinstripe_form.js} +12 -3
- package/lib/components/pinstripe_frame.js +7 -7
- package/lib/components/pinstripe_modal.js +3 -1
- package/lib/components/{script.js → pinstripe_script.js} +0 -2
- package/lib/constants.js +26 -1
- package/lib/context.js +40 -0
- package/lib/database/client.js +257 -0
- package/lib/database/column_reference.js +13 -0
- package/lib/database/constants.js +87 -0
- package/lib/database/index.js +7 -0
- package/lib/database/migration.js +32 -0
- package/lib/database/migrator.js +28 -0
- package/lib/database/row.js +392 -0
- package/lib/database/singleton.js +12 -0
- package/lib/database/table.js +518 -0
- package/lib/database/table_reference.js +33 -0
- package/lib/database/union.js +130 -0
- package/lib/database.js +139 -0
- package/lib/defer.js +35 -0
- package/lib/defer.test.js +37 -0
- package/lib/escape_html.js +2 -0
- package/lib/html.js +72 -0
- package/lib/import_all.js +94 -0
- package/lib/index.js +12 -2
- package/lib/inflector.js +184 -1
- package/lib/lru_cache.js +52 -1
- package/lib/lru_cache.test.js +45 -0
- package/lib/markdown.js +58 -0
- package/lib/model.js +110 -0
- package/lib/project.js +72 -0
- package/lib/registry.js +137 -1
- package/lib/service_consumer.js +16 -0
- package/lib/service_factory.js +22 -0
- package/lib/services/_file_importer.js +1 -0
- package/lib/services/app.js +11 -0
- package/lib/services/args.js +9 -0
- package/lib/services/bot.js +70 -0
- package/lib/services/cli_utils.js +77 -0
- package/lib/services/client_builder.js +70 -0
- package/lib/services/config.js +66 -0
- package/lib/services/cookies.js +19 -0
- package/lib/services/create_model.js +8 -0
- package/lib/services/css_classes_for.js +13 -0
- package/lib/services/database.js +14 -0
- package/lib/services/defer.js +8 -0
- package/lib/services/fetch.js +115 -0
- package/lib/services/format_date.js +8 -0
- package/lib/services/fs_builder.js +132 -0
- package/lib/services/inflector.js +8 -0
- package/lib/services/initial_params.js +13 -0
- package/lib/services/params.js +13 -0
- package/lib/services/parse_html.js +8 -0
- package/lib/services/project.js +8 -0
- package/lib/services/render_form.js +118 -0
- package/lib/services/render_html.js +8 -0
- package/lib/services/render_markdown.js +9 -0
- package/lib/services/render_view.js +6 -0
- package/lib/services/repl.js +54 -0
- package/lib/services/run_command.js +8 -0
- package/lib/services/run_in_new_workspace.js +11 -0
- package/lib/services/send_mail.js +47 -0
- package/lib/services/server.js +105 -0
- package/lib/services/session.js +19 -0
- package/lib/services/trapify.js +8 -0
- package/lib/services/view.js +6 -0
- package/lib/singleton.js +13 -0
- package/lib/string_reader.js +22 -0
- package/lib/trapify.js +31 -1
- package/lib/unescape_html.js +2 -0
- package/lib/unescape_html.test.js +9 -0
- package/lib/util.js +12 -0
- package/lib/validation_error.js +7 -0
- package/lib/view.js +115 -0
- package/lib/view_file_importers/js.js +38 -0
- package/lib/view_file_importers/md.js +50 -0
- package/lib/views/_file_importer.js +1 -0
- package/lib/views/shared/_button.js +59 -0
- package/lib/views/shared/_content.js +85 -0
- package/lib/views/shared/_editable_area.js +37 -0
- package/lib/views/shared/_form.js +239 -0
- package/lib/views/shared/_navbar.js +138 -0
- package/lib/views/shared/_panel.js +65 -0
- package/lib/views/shared/_section.js +41 -0
- package/lib/views/shared/assets/javascripts/all.js.js +7 -0
- package/lib/views/shared/assets/javascripts/all.js.map.js +7 -0
- package/lib/views/shared/assets/stylesheets/all.css +4 -0
- package/lib/views/shared/assets/stylesheets/global.css +126 -0
- package/lib/views/shared/assets/stylesheets/reset.css +74 -0
- package/lib/views/shared/assets/stylesheets/vars.css +25 -0
- package/lib/views/shared/assets/stylesheets/view.css.js +43 -0
- package/lib/virtual_node.js +171 -1
- package/lib/virtual_node.test.js +28 -0
- package/lib/workspace.js +21 -0
- package/package.json +40 -7
- package/lib/internal.js +0 -7
- /package/lib/components/{body.js → pinstripe_body.js} +0 -0
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
|
|
2
|
+
export default {
|
|
3
|
+
async run(){
|
|
4
|
+
const [ name = '' ] = this.args;
|
|
5
|
+
if(name == ''){
|
|
6
|
+
console.error('A service name must be given.');
|
|
7
|
+
process.exit();
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
const { inProjectRootDir, generateFile, line, indent } = this.fsBuilder;
|
|
11
|
+
|
|
12
|
+
await inProjectRootDir(async () => {
|
|
13
|
+
|
|
14
|
+
await generateFile(`lib/services/_file_importer.js`, { skipIfExists: true }, () => {
|
|
15
|
+
line();
|
|
16
|
+
line(`export { ServiceFactory as default } from 'pinstripe';`);
|
|
17
|
+
line();
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
await generateFile(`lib/services/${this.inflector.snakeify(name)}.js`, () => {
|
|
21
|
+
line(`export default {`);
|
|
22
|
+
indent(() => {
|
|
23
|
+
line('create(){');
|
|
24
|
+
indent(() => {
|
|
25
|
+
line(`return 'Example ${this.inflector.camelize(name)} service'`);
|
|
26
|
+
});
|
|
27
|
+
line('}');
|
|
28
|
+
});
|
|
29
|
+
line('};');
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
};
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
|
|
2
|
+
import { default as mimeTypes } from 'mime-types';
|
|
3
|
+
|
|
4
|
+
import { App, View } from 'pinstripe';
|
|
5
|
+
|
|
6
|
+
export default {
|
|
7
|
+
async run(){
|
|
8
|
+
|
|
9
|
+
const { extractOptions } = this.cliUtils;
|
|
10
|
+
|
|
11
|
+
const { app = 'main' } = extractOptions();
|
|
12
|
+
|
|
13
|
+
const { viewNames } = View.mapperFor(App.create(app, this.context).compose());
|
|
14
|
+
|
|
15
|
+
this.pages = {};
|
|
16
|
+
const urls = viewNames.filter(path => !path.match(/(^|\/)_/)).map(path => {
|
|
17
|
+
return new URL(path, 'http://127.0.0.1/');
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
urls.push(new URL('http://127.0.0.1/404'));
|
|
21
|
+
|
|
22
|
+
while(urls.length){
|
|
23
|
+
await this.crawlPage({ _url: urls.shift(), _headers: { 'x-app': app } });
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const pages = Object.values(this.pages).filter(page => {
|
|
27
|
+
const { _method, _url, _headers, ...otherParams } = page.params;
|
|
28
|
+
return _url.pathname == '/404' || (page.status == 200 && !Object.keys(otherParams).length)
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
const { inProjectRootDir, generateDir, generateFile, echo } = this.fsBuilder;
|
|
32
|
+
|
|
33
|
+
const isGenerated = {};
|
|
34
|
+
|
|
35
|
+
await inProjectRootDir(async () => {
|
|
36
|
+
await generateDir('build/static', async () => {
|
|
37
|
+
while(pages.length){
|
|
38
|
+
const { params, headers } = pages.shift();
|
|
39
|
+
const contentType = headers['content-type'];
|
|
40
|
+
|
|
41
|
+
const path = params._url.pathname;
|
|
42
|
+
let filePath = path.replace(/^\//, '');
|
|
43
|
+
if(filePath.match(/(^|\/)$/)){
|
|
44
|
+
filePath = `${filePath}index`;
|
|
45
|
+
}
|
|
46
|
+
if(!filePath.match(/[^/]+\.[^/]+$/)){
|
|
47
|
+
filePath = `${filePath}.${mimeTypes.extension(contentType)}`
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
const data = (await this.fetch({ _url: new URL(path, 'http://127.0.0.1/'), _headers: { 'x-app': app } }))[2];
|
|
51
|
+
|
|
52
|
+
if(!isGenerated[filePath]){
|
|
53
|
+
isGenerated[filePath] = true;
|
|
54
|
+
await generateFile(filePath, () => {
|
|
55
|
+
echo(data.join(''));
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
});
|
|
60
|
+
});
|
|
61
|
+
},
|
|
62
|
+
|
|
63
|
+
async crawlPage(params){
|
|
64
|
+
const hash = JSON.stringify(params);
|
|
65
|
+
if(this.pages[hash]) return;
|
|
66
|
+
const page = { params };
|
|
67
|
+
this.pages[hash] = page;
|
|
68
|
+
const [ status, headers, data ] = await this.fetch(params);
|
|
69
|
+
page.status = status;
|
|
70
|
+
page.headers = headers;
|
|
71
|
+
if(status != 200 || headers['content-type'] != 'text/html') return;
|
|
72
|
+
const html = data.join('');
|
|
73
|
+
const virtualDom = this.parseHtml(html);
|
|
74
|
+
const urls = this.extractUrls(virtualDom);
|
|
75
|
+
while(urls.length){
|
|
76
|
+
const url = urls.shift();
|
|
77
|
+
await this.crawlPage({ ...url.params, _headers: params._headers, _url: url });
|
|
78
|
+
}
|
|
79
|
+
},
|
|
80
|
+
|
|
81
|
+
extractUrls(virtualDom){
|
|
82
|
+
const out = [];
|
|
83
|
+
virtualDom.traverse(({ attributes }) => {
|
|
84
|
+
['src', 'href'].forEach(name => {
|
|
85
|
+
const value = attributes[name];
|
|
86
|
+
if(!value) return;
|
|
87
|
+
const url = new URL(value, 'http://127.0.0.1/');
|
|
88
|
+
if(url.host != '127.0.0.1') return;
|
|
89
|
+
out.push(url);
|
|
90
|
+
});
|
|
91
|
+
});
|
|
92
|
+
return out;
|
|
93
|
+
}
|
|
94
|
+
};
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
|
|
2
|
+
import { View } from 'pinstripe';
|
|
3
|
+
import { readFile } from 'fs/promises';
|
|
4
|
+
|
|
5
|
+
export default {
|
|
6
|
+
async run(){
|
|
7
|
+
const [ name = '' ] = this.args;
|
|
8
|
+
let normalizedName = name.replace(/^\//, '');
|
|
9
|
+
if(name == ''){
|
|
10
|
+
console.error('A view name must be given.');
|
|
11
|
+
process.exit();
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
const existingFilePaths = [ ...View.for(normalizedName).filePaths ];
|
|
15
|
+
const existingFilePath = existingFilePaths.pop();
|
|
16
|
+
const existingFileExtension = (existingFilePath ? existingFilePath.match(/^.*\.([^\/]+)$/) : [])[1];
|
|
17
|
+
if(!normalizedName.match(/\.[^\/]+$/)){
|
|
18
|
+
if(existingFileExtension) {
|
|
19
|
+
normalizedName = `${normalizedName}.${existingFileExtension}`;
|
|
20
|
+
} else {
|
|
21
|
+
normalizedName = `${normalizedName}.js`;
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const normalizedNameExtension = normalizedName.match(/^.*\.([^\/]+)$/)[1];
|
|
26
|
+
|
|
27
|
+
const useExistingFile = normalizedNameExtension == existingFileExtension;
|
|
28
|
+
|
|
29
|
+
const existingFileData = useExistingFile ? (await readFile(existingFilePath)).toString('utf8') : '';
|
|
30
|
+
|
|
31
|
+
const { inProjectRootDir, generateFile, line, indent, echo } = this.fsBuilder;
|
|
32
|
+
|
|
33
|
+
await inProjectRootDir(async () => {
|
|
34
|
+
|
|
35
|
+
await generateFile(`lib/views/_file_importer.js`, { skipIfExists: true }, () => {
|
|
36
|
+
line();
|
|
37
|
+
line(`export { View as default } from 'pinstripe';`);
|
|
38
|
+
line();
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
await generateFile(`lib/views/${normalizedName}`, () => {
|
|
42
|
+
if(useExistingFile){
|
|
43
|
+
echo(existingFileData);
|
|
44
|
+
} else if(normalizedNameExtension == 'js') {
|
|
45
|
+
line();
|
|
46
|
+
line('export const styles = `');
|
|
47
|
+
indent(() => {
|
|
48
|
+
line(".root {");
|
|
49
|
+
indent(() => {
|
|
50
|
+
line("background: yellow;");
|
|
51
|
+
});
|
|
52
|
+
line("}");
|
|
53
|
+
});
|
|
54
|
+
line('`;');
|
|
55
|
+
line();
|
|
56
|
+
line('export default {');
|
|
57
|
+
indent(() => {
|
|
58
|
+
line('render(){')
|
|
59
|
+
indent(() => {
|
|
60
|
+
line('return this.renderHtml`')
|
|
61
|
+
indent(() => {
|
|
62
|
+
line('<div class="${this.cssClasses.root}">');
|
|
63
|
+
indent(() => {
|
|
64
|
+
line(`<h1>${normalizedName} view</h1>`);
|
|
65
|
+
});
|
|
66
|
+
line(`</div>`)
|
|
67
|
+
});
|
|
68
|
+
line('`;');
|
|
69
|
+
});
|
|
70
|
+
line('}');
|
|
71
|
+
});
|
|
72
|
+
line('};');
|
|
73
|
+
line();
|
|
74
|
+
} else {
|
|
75
|
+
line();
|
|
76
|
+
}
|
|
77
|
+
});
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
};
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
|
|
2
|
+
import chalk from 'chalk';
|
|
3
|
+
import { App } from 'pinstripe';
|
|
4
|
+
|
|
5
|
+
export default {
|
|
6
|
+
run(){
|
|
7
|
+
console.log('');
|
|
8
|
+
console.log('The following apps are available:');
|
|
9
|
+
console.log('');
|
|
10
|
+
App.names.forEach(appName => {
|
|
11
|
+
console.log(` * ${chalk.green(appName)} (composed of ${JSON.stringify(App.create(appName, this.context).compose())} views)`);
|
|
12
|
+
});
|
|
13
|
+
console.log('');
|
|
14
|
+
}
|
|
15
|
+
};
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
|
|
2
|
+
import chalk from 'chalk';
|
|
3
|
+
import { Command } from 'pinstripe';
|
|
4
|
+
|
|
5
|
+
export default {
|
|
6
|
+
run(){
|
|
7
|
+
console.log('');
|
|
8
|
+
console.log('The following commands are available:');
|
|
9
|
+
console.log('');
|
|
10
|
+
Command.names.forEach(commandName => {
|
|
11
|
+
console.log(` * ${chalk.green(commandName)}`);
|
|
12
|
+
});
|
|
13
|
+
console.log('');
|
|
14
|
+
}
|
|
15
|
+
};
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
|
|
2
|
+
import chalk from 'chalk';
|
|
3
|
+
import { Component } from 'pinstripe';
|
|
4
|
+
|
|
5
|
+
export default {
|
|
6
|
+
run(){
|
|
7
|
+
console.log('');
|
|
8
|
+
console.log('The following components are available:');
|
|
9
|
+
console.log('');
|
|
10
|
+
Component.names.forEach(componentName => {
|
|
11
|
+
console.log(` * ${chalk.green(componentName)}`);
|
|
12
|
+
});
|
|
13
|
+
console.log('');
|
|
14
|
+
}
|
|
15
|
+
};
|
|
16
|
+
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
|
|
2
|
+
import chalk from 'chalk';
|
|
3
|
+
import { Migration } from 'pinstripe/database';
|
|
4
|
+
|
|
5
|
+
export default {
|
|
6
|
+
run(){
|
|
7
|
+
console.log('');
|
|
8
|
+
console.log('The following migrations are available:');
|
|
9
|
+
console.log('');
|
|
10
|
+
Migration.names.forEach(migrationName => {
|
|
11
|
+
console.log(` * ${chalk.green(migrationName)}`);
|
|
12
|
+
});
|
|
13
|
+
console.log('');
|
|
14
|
+
}
|
|
15
|
+
};
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
|
|
2
|
+
import chalk from 'chalk';
|
|
3
|
+
import { Row } from 'pinstripe/database';
|
|
4
|
+
|
|
5
|
+
export default {
|
|
6
|
+
run(){
|
|
7
|
+
console.log('');
|
|
8
|
+
console.log('The following models are available:');
|
|
9
|
+
console.log('');
|
|
10
|
+
Row.names.forEach(modelName => {
|
|
11
|
+
console.log(` * ${chalk.green(modelName)}`);
|
|
12
|
+
});
|
|
13
|
+
console.log('');
|
|
14
|
+
}
|
|
15
|
+
};
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
|
|
2
|
+
import chalk from 'chalk';
|
|
3
|
+
import { ServiceFactory } from 'pinstripe';
|
|
4
|
+
|
|
5
|
+
export default {
|
|
6
|
+
run(){
|
|
7
|
+
console.log('');
|
|
8
|
+
console.log('The following views are available:');
|
|
9
|
+
console.log('');
|
|
10
|
+
ServiceFactory.names.forEach(serviceFactoryName => {
|
|
11
|
+
console.log(` * ${chalk.green(serviceFactoryName)}`);
|
|
12
|
+
});
|
|
13
|
+
console.log('');
|
|
14
|
+
}
|
|
15
|
+
};
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
|
|
2
|
+
import chalk from 'chalk';
|
|
3
|
+
import { App, View } from 'pinstripe';
|
|
4
|
+
|
|
5
|
+
export default {
|
|
6
|
+
run(){
|
|
7
|
+
const { extractOptions } = this.cliUtils;
|
|
8
|
+
|
|
9
|
+
const { app } = extractOptions();
|
|
10
|
+
|
|
11
|
+
if(app){
|
|
12
|
+
this.listComposedViews(typeof app == 'string' ? app : 'main');
|
|
13
|
+
} else {
|
|
14
|
+
this.listAllViews();
|
|
15
|
+
}
|
|
16
|
+
},
|
|
17
|
+
|
|
18
|
+
listComposedViews(appName){
|
|
19
|
+
const { viewNames, resolveView } = View.mapperFor(App.create(appName, this.context).compose());
|
|
20
|
+
console.log('');
|
|
21
|
+
console.log(`The following views have been composed for app "${appName}":`);
|
|
22
|
+
console.log('');
|
|
23
|
+
viewNames.forEach(viewName => {
|
|
24
|
+
console.log(` * ${chalk.green(viewName)} -> ${chalk.green(resolveView(viewName))}`);
|
|
25
|
+
});
|
|
26
|
+
console.log('');
|
|
27
|
+
},
|
|
28
|
+
|
|
29
|
+
listAllViews(){
|
|
30
|
+
console.log('');
|
|
31
|
+
console.log(`The following views are available:`);
|
|
32
|
+
console.log('');
|
|
33
|
+
View.names.forEach(viewName => {
|
|
34
|
+
console.log(` * ${chalk.green(viewName)}`);
|
|
35
|
+
});
|
|
36
|
+
console.log('');
|
|
37
|
+
}
|
|
38
|
+
};
|
|
39
|
+
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
|
|
2
|
+
export default {
|
|
3
|
+
run(){
|
|
4
|
+
const { extractOptions } = this.cliUtils;
|
|
5
|
+
|
|
6
|
+
const { app, withoutBot } = extractOptions({
|
|
7
|
+
app: `main:${process.env.HOST || '127.0.0.1'}:${parseInt(process.env.PORT || '3000')}`,
|
|
8
|
+
withoutBot: false
|
|
9
|
+
});
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
const apps = [];
|
|
13
|
+
let currentPort = 3000;
|
|
14
|
+
|
|
15
|
+
app.trim().split(/\s+/).forEach((app) => {
|
|
16
|
+
const [name, ...serverConfig] = app.split(/:/);
|
|
17
|
+
const [ port, host = '127.0.0.1'] = serverConfig.reverse();
|
|
18
|
+
|
|
19
|
+
if(port){
|
|
20
|
+
apps.push({ name, port: parseInt(port), host });
|
|
21
|
+
} else {
|
|
22
|
+
while(apps.filter(app => app.host == host).map(({ port }) => port).includes(currentPort)) currentPort++;
|
|
23
|
+
apps.push({ name, port: currentPort, host });
|
|
24
|
+
}
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
this.server.start(apps);
|
|
28
|
+
|
|
29
|
+
if(!withoutBot) this.bot.start();
|
|
30
|
+
}
|
|
31
|
+
};
|
package/lib/component.js
CHANGED
|
@@ -1,10 +1,13 @@
|
|
|
1
1
|
|
|
2
|
+
import { fileURLToPath } from 'url'; // pinstripe-if-client: const fileURLToPath = undefined;
|
|
3
|
+
|
|
2
4
|
import { Class } from './class.js';
|
|
3
|
-
import { TEXT_ONLY_TAGS } from './constants.js';
|
|
5
|
+
import { TEXT_ONLY_TAGS, IS_SERVER } from './constants.js';
|
|
4
6
|
import { Inflector } from './inflector.js';
|
|
5
7
|
import { VirtualNode } from './virtual_node.js';
|
|
6
8
|
import { Registry } from './registry.js';
|
|
7
9
|
import { ComponentEvent } from './component_event.js';
|
|
10
|
+
import { Client } from './client.js'; // pinstripe-if-client: const Client = undefined;
|
|
8
11
|
|
|
9
12
|
export const Component = Class.extend().include({
|
|
10
13
|
meta(){
|
|
@@ -17,9 +20,15 @@ export const Component = Class.extend().include({
|
|
|
17
20
|
if(!node._component){
|
|
18
21
|
node._component = Component.new(node, true);
|
|
19
22
|
node._component = Component.create(
|
|
20
|
-
node._component.attributes['data-component'] || node._component.type,
|
|
23
|
+
node._component.attributes['data-component'] || (node._component.type == '#document' ? 'pinstripe-document' : node._component.type),
|
|
21
24
|
node
|
|
22
25
|
);
|
|
26
|
+
(node._component.attributes.class || '').trim().split(/\s+/).forEach((className) => {
|
|
27
|
+
const decoratorMethodName = `.${className}`;
|
|
28
|
+
if(typeof node._component[decoratorMethodName] == 'function'){
|
|
29
|
+
node._component[decoratorMethodName]();
|
|
30
|
+
}
|
|
31
|
+
});
|
|
23
32
|
if(node.isConnected) node._component.trigger('init', { bubbles: false });
|
|
24
33
|
}
|
|
25
34
|
return node._component;
|
|
@@ -29,6 +38,30 @@ export const Component = Class.extend().include({
|
|
|
29
38
|
return Inflector.instance.dasherize(name);
|
|
30
39
|
}
|
|
31
40
|
});
|
|
41
|
+
|
|
42
|
+
this.FileImporter.register('js', {
|
|
43
|
+
meta(){
|
|
44
|
+
const { importFile } = this.prototype;
|
|
45
|
+
|
|
46
|
+
this.include({
|
|
47
|
+
async importFile(params){
|
|
48
|
+
const { filePath, relativeFilePathWithoutExtension } = params;
|
|
49
|
+
if((await import(filePath)).default){
|
|
50
|
+
Client.instance.addModule(`
|
|
51
|
+
import { Component } from ${JSON.stringify(fileURLToPath(`${import.meta.url}/../index.js`))};
|
|
52
|
+
import include from ${JSON.stringify(filePath)};
|
|
53
|
+
Component.register(${JSON.stringify(relativeFilePathWithoutExtension)}, include);
|
|
54
|
+
`);
|
|
55
|
+
} else {
|
|
56
|
+
Client.instance.addModule(`
|
|
57
|
+
import ${JSON.stringify(filePath)};
|
|
58
|
+
`);
|
|
59
|
+
}
|
|
60
|
+
return importFile.call(this, params);
|
|
61
|
+
}
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
});
|
|
32
65
|
},
|
|
33
66
|
|
|
34
67
|
initialize(node, skipInit = false){
|
|
@@ -236,6 +269,14 @@ export const Component = Class.extend().include({
|
|
|
236
269
|
return this.parents.find(({ isOverlay }) => isOverlay);
|
|
237
270
|
},
|
|
238
271
|
|
|
272
|
+
get modal(){
|
|
273
|
+
return this.parents.find(({ isModal }) => isModal);
|
|
274
|
+
},
|
|
275
|
+
|
|
276
|
+
get form(){
|
|
277
|
+
return this.parents.find(({ isForm }) => isForm);
|
|
278
|
+
},
|
|
279
|
+
|
|
239
280
|
get shadow(){
|
|
240
281
|
if(!this.node.shadowRoot){
|
|
241
282
|
this.node.attachShadow({ mode: 'open' });
|
|
@@ -403,7 +444,7 @@ export const Component = Class.extend().include({
|
|
|
403
444
|
},
|
|
404
445
|
|
|
405
446
|
async fetch(url, options = {}){
|
|
406
|
-
const { minimumDelay = 0, ...otherOptions } = options;
|
|
447
|
+
const { minimumDelay = 0, requiresProofOfWork = false, ...otherOptions } = options;
|
|
407
448
|
const { progressBar } = this.document;
|
|
408
449
|
const frame = this.frame || this;
|
|
409
450
|
const normalizedUrl = new URL(url, frame.url);
|
|
@@ -417,6 +458,12 @@ export const Component = Class.extend().include({
|
|
|
417
458
|
progressBar.stop();
|
|
418
459
|
};
|
|
419
460
|
try {
|
|
461
|
+
if(requiresProofOfWork){
|
|
462
|
+
if(!(otherOptions.body instanceof FormData)) throw new Error(`Proof of work requires form data to be present`);
|
|
463
|
+
const values = {};
|
|
464
|
+
otherOptions.body.forEach((value, key) => values[key] = value);
|
|
465
|
+
otherOptions.body.append('_proofOfWork', await generateProofOfWork(values, abortController.signal))
|
|
466
|
+
}
|
|
420
467
|
const promises = [
|
|
421
468
|
fetch(normalizedUrl, { signal: abortController.signal, ...otherOptions }),
|
|
422
469
|
new Promise(resolve => minimumDelayTimeout = setTimeout(resolve, minimumDelay))
|
|
@@ -645,6 +692,58 @@ function normalizeVirtualNode(){
|
|
|
645
692
|
if(this.parent && this.parent.type == 'textarea' && this.type == '#text'){
|
|
646
693
|
this.attributes.value = this.attributes.value.replace(/^\n/, '');
|
|
647
694
|
}
|
|
695
|
+
|
|
696
|
+
if(!this.attributes['data-component']){
|
|
697
|
+
if(this.type == 'a'){
|
|
698
|
+
this.attributes['data-component'] = 'pinstripe-anchor';
|
|
699
|
+
}
|
|
700
|
+
|
|
701
|
+
if(this.type == 'body'){
|
|
702
|
+
this.attributes['data-component'] = 'pinstripe-body';
|
|
703
|
+
}
|
|
704
|
+
|
|
705
|
+
if(this.type == 'form'){
|
|
706
|
+
this.attributes['data-component'] = 'pinstripe-form';
|
|
707
|
+
}
|
|
708
|
+
|
|
709
|
+
if(this.type == 'script' && this.attributes.type == 'pinstripe'){
|
|
710
|
+
this.attributes['data-component'] = 'pinstripe-script';
|
|
711
|
+
}
|
|
712
|
+
}
|
|
713
|
+
}
|
|
714
|
+
|
|
715
|
+
async function generateProofOfWork(values, abortSignal){
|
|
716
|
+
const stringifiedValues = JSON.stringify(values);
|
|
717
|
+
const timestamp = getUTCTimestamp();
|
|
718
|
+
const random = btoa(`${Math.random()}`);
|
|
719
|
+
let counter = 0;
|
|
720
|
+
while(!abortSignal.aborted){
|
|
721
|
+
const candidateSolution = `1:20:${timestamp}:?::${random}:${btoa(`${counter}`)}`;
|
|
722
|
+
const hash = await createSha1Hash(candidateSolution.replace(/\?/, stringifiedValues));
|
|
723
|
+
if(hash.match(/^00000/)) return candidateSolution;
|
|
724
|
+
counter++;
|
|
725
|
+
}
|
|
726
|
+
return '';
|
|
727
|
+
}
|
|
728
|
+
|
|
729
|
+
function getUTCTimestamp() {
|
|
730
|
+
const now = new Date();
|
|
731
|
+
|
|
732
|
+
const year = now.getUTCFullYear() % 100;
|
|
733
|
+
const month = String(now.getUTCMonth() + 1).padStart(2, '0');
|
|
734
|
+
const day = String(now.getUTCDate()).padStart(2, '0');
|
|
735
|
+
const hours = String(now.getUTCHours()).padStart(2, '0');
|
|
736
|
+
const minutes = String(now.getUTCMinutes()).padStart(2, '0');
|
|
737
|
+
const seconds = String(now.getUTCSeconds()).padStart(2, '0');
|
|
738
|
+
|
|
739
|
+
return `${year}${month}${day}${hours}${minutes}${seconds}`;
|
|
740
|
+
}
|
|
741
|
+
|
|
742
|
+
|
|
743
|
+
async function createSha1Hash(input) {
|
|
744
|
+
const buffer = new TextEncoder().encode(input);
|
|
745
|
+
const hashArray = Array.from(new Uint8Array(await crypto.subtle.digest('SHA-1', buffer)));
|
|
746
|
+
return hashArray.map(byte => byte.toString(16).padStart(2, '0')).join('');
|
|
648
747
|
}
|
|
649
748
|
|
|
650
749
|
ComponentEvent.Component = Component;
|
|
@@ -3,7 +3,7 @@ import { LruCache } from '../lru_cache.js';
|
|
|
3
3
|
|
|
4
4
|
export const loadCache = LruCache.new();
|
|
5
5
|
|
|
6
|
-
export function loadFrame(confirm, target, method, url, placeholderUrl){
|
|
6
|
+
export function loadFrame(confirm, target, method, url, placeholderUrl, requiresProofOfWork = false){
|
|
7
7
|
if(confirm && !window.confirm(confirm)){
|
|
8
8
|
return;
|
|
9
9
|
}
|
|
@@ -20,20 +20,20 @@ export function loadFrame(confirm, target, method, url, placeholderUrl){
|
|
|
20
20
|
if(!frame) return;
|
|
21
21
|
}
|
|
22
22
|
|
|
23
|
-
url =
|
|
23
|
+
url = normalizeUrl(url || frame.url, this.frame.url);
|
|
24
24
|
if(url.protocol != 'data:' && (url.host != frame.url.host || url.port != frame.url.port)){
|
|
25
25
|
return;
|
|
26
26
|
}
|
|
27
27
|
|
|
28
|
-
if(placeholderUrl) placeholderUrl =
|
|
28
|
+
if(placeholderUrl) placeholderUrl = normalizeUrl(placeholderUrl, this.frame.url);
|
|
29
29
|
|
|
30
30
|
if(method.match(/POST|PUT|PATCH/i)){
|
|
31
31
|
const formData = new FormData();
|
|
32
32
|
const values = this.values;
|
|
33
33
|
Object.keys(values).forEach((name) => formData.append(name, values[name]));
|
|
34
|
-
frame.load(url, { method, body: formData, placeholderUrl });
|
|
34
|
+
frame.load(url, { method, body: formData, placeholderUrl, requiresProofOfWork });
|
|
35
35
|
} else {
|
|
36
|
-
frame.load(url, { method, placeholderUrl });
|
|
36
|
+
frame.load(url, { method, placeholderUrl, requiresProofOfWork });
|
|
37
37
|
}
|
|
38
38
|
}
|
|
39
39
|
|
|
@@ -46,3 +46,16 @@ export function getFrame(target){
|
|
|
46
46
|
}
|
|
47
47
|
return this.frame.descendants.find(n => n.isFrame && n.data.name == target);
|
|
48
48
|
}
|
|
49
|
+
|
|
50
|
+
export function normalizeUrl(url, referenceUrl = window.location){
|
|
51
|
+
const matches = `${url}`.match(/^&(.*)$/);
|
|
52
|
+
const out = matches ? new URL(referenceUrl) : new URL(url, referenceUrl);
|
|
53
|
+
if(matches){
|
|
54
|
+
if(out.search){
|
|
55
|
+
out.search = `${out.search}&${matches[1]}`;
|
|
56
|
+
} else {
|
|
57
|
+
out.search = `?${matches[1]}`;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
return out;
|
|
61
|
+
}
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
|
|
2
|
-
import { loadFrame, getFrame } from "./helpers.js";
|
|
2
|
+
import { loadFrame, getFrame, normalizeUrl } from "./helpers.js";
|
|
3
3
|
|
|
4
4
|
export default {
|
|
5
5
|
initialize(...args){
|
|
6
6
|
this.constructor.parent.prototype.initialize.call(this, ...args);
|
|
7
7
|
this.on('click', (event) => {
|
|
8
8
|
const { confirm, target = '_self', method = 'GET', href, placeholder } = this.params;
|
|
9
|
-
if(
|
|
9
|
+
if(normalizeUrl(href, window.location.href).host != window.location.host) return;
|
|
10
10
|
event.preventDefault();
|
|
11
11
|
event.stopPropagation();
|
|
12
12
|
loadFrame.call(this, confirm, target, method, href, placeholder);
|
|
@@ -15,8 +15,8 @@ export default {
|
|
|
15
15
|
const { target = '_self', method = 'GET', href, placeholder, preload } = this.params;
|
|
16
16
|
if(method == 'GET' && target != '_blank'){
|
|
17
17
|
const frame = target == '_overlay' ? this.frame : getFrame.call(this, target);
|
|
18
|
-
if(preload != undefined) this.document.preload(
|
|
19
|
-
if(placeholder != undefined) this.document.preload(
|
|
18
|
+
if(preload != undefined) this.document.preload(normalizeUrl(href, frame.url));
|
|
19
|
+
if(placeholder != undefined) this.document.preload(normalizeUrl(placeholder, frame.url));
|
|
20
20
|
}
|
|
21
21
|
|
|
22
22
|
if(this.is('input, textarea')) this.on('keyup', (event) => this.trigger('click'));
|