Everywhere: Use branch self-highlighting as website from now on
3
.babelrc
|
@ -1,3 +0,0 @@
|
|||
{
|
||||
"presets": ["next/babel"]
|
||||
}
|
113
.eslintrc.json
Normal file
|
@ -0,0 +1,113 @@
|
|||
{
|
||||
"env": {
|
||||
"node": true,
|
||||
"mocha": true,
|
||||
"es6": true
|
||||
},
|
||||
"extends": [
|
||||
"eslint:recommended",
|
||||
"plugin:import/recommended",
|
||||
"plugin:import/typescript",
|
||||
"plugin:@typescript-eslint/recommended"
|
||||
],
|
||||
"parser": "@typescript-eslint/parser",
|
||||
"plugins": [
|
||||
"@typescript-eslint",
|
||||
"only-warn",
|
||||
"import"
|
||||
],
|
||||
"ignorePatterns": [
|
||||
"*.js",
|
||||
"*.d.ts"
|
||||
],
|
||||
"rules": {
|
||||
"@typescript-eslint/no-empty-interface": "off",
|
||||
"@typescript-eslint/no-inferrable-types": "off",
|
||||
"@typescript-eslint/no-explicit-any": "off",
|
||||
"@typescript-eslint/member-delimiter-style": [
|
||||
"warn",
|
||||
{
|
||||
"multiline": {
|
||||
"delimiter": "comma",
|
||||
"requireLast": false
|
||||
},
|
||||
"singleline": {
|
||||
"delimiter": "comma",
|
||||
"requireLast": false
|
||||
},
|
||||
"multilineDetection": "brackets",
|
||||
"overrides": {
|
||||
"interface": {
|
||||
"multiline": {
|
||||
"delimiter": "semi",
|
||||
"requireLast": true
|
||||
},
|
||||
"singleline": {
|
||||
"delimiter": "semi",
|
||||
"requireLast": false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"@typescript-eslint/explicit-module-boundary-types": [
|
||||
"warn",
|
||||
{
|
||||
"allowArgumentsExplicitlyTypedAsAny": true
|
||||
}
|
||||
],
|
||||
"@typescript-eslint/semi": "warn",
|
||||
"import/first": "warn",
|
||||
"import/no-duplicates": "warn",
|
||||
"import/no-unresolved": "warn",
|
||||
"import/no-self-import": "warn",
|
||||
"import/extensions": [ "warn", "always" ],
|
||||
"import/order": [ "warn", { "newlines-between": "ignore", "alphabetize": { "order": "asc", "caseInsensitive": true } } ],
|
||||
"semi": "off", // Using @typescript-eslint/semi instead
|
||||
"no-else-return": "warn",
|
||||
"no-trailing-spaces": "warn",
|
||||
"eqeqeq": "warn",
|
||||
"template-curly-spacing": "warn",
|
||||
"no-template-curly-in-string": "warn",
|
||||
"object-curly-spacing": [
|
||||
"warn",
|
||||
"always"
|
||||
],
|
||||
"array-bracket-spacing": [
|
||||
"warn",
|
||||
"always"
|
||||
],
|
||||
"linebreak-style": [
|
||||
"warn",
|
||||
"unix"
|
||||
],
|
||||
"quotes": [
|
||||
"warn",
|
||||
"single"
|
||||
],
|
||||
"eol-last": [
|
||||
"warn",
|
||||
"always"
|
||||
],
|
||||
"indent": [
|
||||
"warn",
|
||||
4,
|
||||
{
|
||||
"SwitchCase": 1
|
||||
}
|
||||
],
|
||||
"no-empty": [
|
||||
"warn",
|
||||
{
|
||||
"allowEmptyCatch": true
|
||||
}
|
||||
],
|
||||
"no-multiple-empty-lines": [
|
||||
"warn",
|
||||
{
|
||||
"max": 999,
|
||||
"maxEOF": 0
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
48
.gitignore
vendored
|
@ -1,36 +1,18 @@
|
|||
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
|
||||
# editor
|
||||
.idea
|
||||
.vscode
|
||||
.history
|
||||
|
||||
# dependencies
|
||||
/node_modules
|
||||
/.pnp
|
||||
.pnp.js
|
||||
# npm
|
||||
node_modules
|
||||
lib
|
||||
|
||||
# testing
|
||||
/coverage
|
||||
# storage
|
||||
files
|
||||
config.json
|
||||
*.key
|
||||
|
||||
# next.js
|
||||
/.next/
|
||||
/out/
|
||||
|
||||
# production
|
||||
/build
|
||||
|
||||
# misc
|
||||
.DS_Store
|
||||
*.pem
|
||||
|
||||
# debug
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
|
||||
# local env files
|
||||
.env.local
|
||||
.env.development.local
|
||||
.env.test.local
|
||||
.env.production.local
|
||||
|
||||
# vercel
|
||||
.vercel
|
||||
|
||||
src/tailwind.output.css
|
||||
# build files
|
||||
*.js
|
||||
*.js.map
|
||||
public/script
|
||||
|
|
8
.idea/.gitignore
vendored
|
@ -1,8 +0,0 @@
|
|||
# Default ignored files
|
||||
/shelf/
|
||||
/workspace.xml
|
||||
# Datasource local storage ignored files
|
||||
/dataSources/
|
||||
/dataSources.local.xml
|
||||
# Editor-based HTTP Client requests
|
||||
/httpRequests/
|
|
@ -1,9 +0,0 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<module type="JAVA_MODULE" version="4">
|
||||
<component name="NewModuleRootManager" inherit-compiler-output="true">
|
||||
<exclude-output />
|
||||
<content url="file://$MODULE_DIR$" />
|
||||
<orderEntry type="inheritedJdk" />
|
||||
<orderEntry type="sourceFolder" forTests="false" />
|
||||
</component>
|
||||
</module>
|
|
@ -1,6 +0,0 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="ProjectRootManager" version="2" languageLevel="JDK_15" project-jdk-name="15" project-jdk-type="JavaSDK">
|
||||
<output url="file://$PROJECT_DIR$/out" />
|
||||
</component>
|
||||
</project>
|
|
@ -1,8 +0,0 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="ProjectModuleManager">
|
||||
<modules>
|
||||
<module fileurl="file://$PROJECT_DIR$/.idea/nwex.de.iml" filepath="$PROJECT_DIR$/.idea/nwex.de.iml" />
|
||||
</modules>
|
||||
</component>
|
||||
</project>
|
|
@ -1,9 +0,0 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<module type="JAVA_MODULE" version="4">
|
||||
<component name="NewModuleRootManager" inherit-compiler-output="true">
|
||||
<exclude-output />
|
||||
<content url="file://$MODULE_DIR$" />
|
||||
<orderEntry type="inheritedJdk" />
|
||||
<orderEntry type="sourceFolder" forTests="false" />
|
||||
</component>
|
||||
</module>
|
|
@ -1,6 +0,0 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="VcsDirectoryMappings">
|
||||
<mapping directory="" vcs="Git" />
|
||||
</component>
|
||||
</project>
|
|
@ -1,59 +0,0 @@
|
|||
import { Parser } from './tokenizer/parser.class.js';
|
||||
import { Token } from './tokenizer/token.interfance.js';
|
||||
|
||||
Array.from(document.getElementsByClassName('tokenized')).map(tokenized => tokenized as HTMLDivElement).forEach(tokenized => {
|
||||
const run = () => {
|
||||
const source: string = tokenized.innerText;
|
||||
|
||||
console.time('tokenized');
|
||||
const tokens: Array<Token> = new Parser(source).tokenize();
|
||||
console.timeEnd('tokenized');
|
||||
|
||||
tokenized.innerHTML = '';
|
||||
|
||||
const lines: Array<string> = source.split('\n');
|
||||
|
||||
for (let line = 0; line < lines.length; line++) {
|
||||
const lineValue: string = lines[line];
|
||||
|
||||
for (let column = 0; column < lineValue.length; column++) {
|
||||
let found: boolean = false;
|
||||
|
||||
for (const token of tokens) {
|
||||
if (token.position.starts(line, column)) {
|
||||
found = true;
|
||||
|
||||
const element: HTMLSpanElement = document.createElement('span');
|
||||
|
||||
element.style.color = token.type;
|
||||
element.innerText = lineValue.substring(token.position.column, token.position.length + token.position.column);
|
||||
|
||||
tokenized.appendChild(element);
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
if (token.position.intersects(line, column)) {
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (found) continue;
|
||||
if (column >= lineValue.length) break;
|
||||
|
||||
tokenized.innerHTML += lineValue[column];
|
||||
}
|
||||
|
||||
const element: HTMLSpanElement = document.createElement('span');
|
||||
element.innerText = '\n';
|
||||
tokenized.appendChild(element);
|
||||
}
|
||||
|
||||
tokenized.lastChild.remove();
|
||||
};
|
||||
|
||||
tokenized.addEventListener('focusout', run);
|
||||
|
||||
run();
|
||||
});
|
|
@ -1,230 +0,0 @@
|
|||
import { Position } from './position.class.js';
|
||||
import { IdentifierState } from './state/identifierState.class.js';
|
||||
import { NumberState } from './state/numberState.class.js';
|
||||
import { StringState } from './state/stringState.class.js';
|
||||
import { Token } from './token.interfance.js';
|
||||
import { Type } from './type.enum.js';
|
||||
|
||||
export class Parser {
|
||||
private string: StringState;
|
||||
private identifier: IdentifierState;
|
||||
private number: NumberState;
|
||||
private position: Position;
|
||||
|
||||
public constructor(private source: string) {
|
||||
this.string = StringState.none.duplicate();
|
||||
this.identifier = IdentifierState.none.duplicate();
|
||||
this.number = NumberState.none.duplicate();
|
||||
this.position = Position.none.duplicate();
|
||||
}
|
||||
|
||||
public tokenize(): Array<Token> {
|
||||
const tokens: Array<Token> = new Array<Token>();
|
||||
|
||||
const commitIdentifier = () => {
|
||||
if (!this.identifier.active) return;
|
||||
|
||||
tokens.push({
|
||||
type: Type.Identifier,
|
||||
value: this.identifier.value,
|
||||
position: this.identifier.start.withLength(this.identifier.value.length)
|
||||
});
|
||||
|
||||
this.identifier = IdentifierState.none;
|
||||
};
|
||||
|
||||
const commitString = () => {
|
||||
if (!this.string.active) return;
|
||||
|
||||
tokens.push({
|
||||
type: Type.Literal,
|
||||
value: this.string.value,
|
||||
position: this.string.start.withLength(this.string.value.length + 2)
|
||||
});
|
||||
|
||||
this.position.addColumn(this.string.value.length + 2);
|
||||
|
||||
this.string = StringState.none;
|
||||
};
|
||||
|
||||
const commitNumber = () => {
|
||||
if (!this.number.active) return;
|
||||
|
||||
tokens.push({
|
||||
type: Type.Literal,
|
||||
value: this.number.value,
|
||||
position: this.number.start.withLength(this.number.value.length)
|
||||
});
|
||||
|
||||
this.number = NumberState.none;
|
||||
};
|
||||
|
||||
while (this.source.length > 0) {
|
||||
if (this.string.active) {
|
||||
if (this.matches(this.string.quote)) {
|
||||
this.consume();
|
||||
|
||||
commitString();
|
||||
} else {
|
||||
this.string.value += this.consume();
|
||||
}
|
||||
} else if (this.matches('"', '\'', '`')) {
|
||||
commitIdentifier();
|
||||
commitNumber();
|
||||
|
||||
this.string = StringState.start(this.consume(), this.position);
|
||||
} else if (this.number.active) {
|
||||
if (this.matches('.')) {
|
||||
if (this.number.decimal) {
|
||||
commitNumber();
|
||||
} else {
|
||||
this.number.decimal = true;
|
||||
this.number.value += this.consume();
|
||||
this.position.addColumn(1);
|
||||
}
|
||||
} else if (this.matches('0', '1', '2', '3', '4', '5', '6', '7', '8', '9')) {
|
||||
this.number.value += this.consume();
|
||||
this.position.addColumn(1);
|
||||
} else commitNumber();
|
||||
} else if (this.matches('0', '1', '2', '3', '4', '5', '6', '7', '8', '9')) {
|
||||
commitIdentifier();
|
||||
|
||||
this.number = NumberState.start(this.position);
|
||||
this.number.value += this.consume();
|
||||
this.position.addColumn(1);
|
||||
} else if (this.peek(2).match(/-\d/)) {
|
||||
commitIdentifier();
|
||||
|
||||
this.number = NumberState.start(this.position);
|
||||
this.number.value += this.consume(2);
|
||||
this.position.addColumn(2);
|
||||
} else if (this.matches('.', ',', ':', ';', '+', '-', '*', '/', '=', '<', '>', '|')) {
|
||||
commitIdentifier();
|
||||
commitNumber();
|
||||
|
||||
tokens.push({
|
||||
type: Type.Punctuation,
|
||||
value: this.consume(),
|
||||
position: this.position.withLength(1)
|
||||
});
|
||||
|
||||
this.position.addColumn(1);
|
||||
} else if (this.matches('(', ')', '[', ']', '{', '}')) {
|
||||
commitIdentifier();
|
||||
commitNumber();
|
||||
|
||||
tokens.push({
|
||||
type: Type.Nesting,
|
||||
value: this.consume(),
|
||||
position: this.position.withLength(1)
|
||||
});
|
||||
|
||||
this.position.addColumn(1);
|
||||
} else if (this.matches(' ')) {
|
||||
commitIdentifier();
|
||||
commitNumber();
|
||||
|
||||
this.consume();
|
||||
this.position.addColumn(1);
|
||||
} else if (this.matches('\n')) {
|
||||
commitIdentifier();
|
||||
commitNumber();
|
||||
|
||||
this.consume();
|
||||
this.position.addLine(1).setColumn(0);
|
||||
} else {
|
||||
if (!this.identifier.active) {
|
||||
this.identifier = IdentifierState.start(this.position);
|
||||
}
|
||||
|
||||
this.identifier.value += this.consume();
|
||||
this.position.addColumn(1);
|
||||
}
|
||||
}
|
||||
|
||||
commitIdentifier();
|
||||
commitString();
|
||||
commitNumber();
|
||||
|
||||
return this.mark(tokens);
|
||||
}
|
||||
|
||||
private consume(length: number = 1): string {
|
||||
const consumed: string = this.peek(length);
|
||||
|
||||
this.source = this.source.substring(length);
|
||||
|
||||
return consumed;
|
||||
}
|
||||
|
||||
private peek(length: number = 1): string {
|
||||
return this.source.substring(0, length);
|
||||
}
|
||||
|
||||
private matches(...values: Array<string>): boolean {
|
||||
for (const value of values) {
|
||||
if (this.source.startsWith(value)) return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private mark(tokens: Array<Token>): Array<Token> {
|
||||
const marked: Array<Token> = new Array<Token>();
|
||||
|
||||
const keywords: Array<string> = [
|
||||
'let',
|
||||
'const',
|
||||
'var',
|
||||
|
||||
'class',
|
||||
'interface',
|
||||
'enum',
|
||||
|
||||
'if',
|
||||
'else',
|
||||
|
||||
'return',
|
||||
'break',
|
||||
'continue',
|
||||
|
||||
'try',
|
||||
'catch',
|
||||
'finally',
|
||||
|
||||
'for',
|
||||
'while',
|
||||
'do',
|
||||
|
||||
'of',
|
||||
'in',
|
||||
'as',
|
||||
'new',
|
||||
|
||||
'private',
|
||||
'public',
|
||||
'readonly',
|
||||
'static'
|
||||
];
|
||||
|
||||
tokens.forEach(token => {
|
||||
if (token.type === Type.Identifier && keywords.includes(token.value)) {
|
||||
token.type = Type.Keyword;
|
||||
}
|
||||
|
||||
if (token.type === Type.Identifier && token.value === 'true' || token.value === 'false') {
|
||||
token.type = Type.Literal;
|
||||
}
|
||||
|
||||
if (token.type === Type.Identifier && token.value === 'null' || token.value === 'undefined') {
|
||||
token.type = Type.Literal;
|
||||
}
|
||||
|
||||
marked.push(token);
|
||||
});
|
||||
|
||||
console.log('Marked Tokens:', marked);
|
||||
|
||||
return marked;
|
||||
}
|
||||
}
|
|
@ -1,44 +0,0 @@
|
|||
export class Position {
|
||||
public constructor(public line: number, public column: number, public length: number) {
|
||||
}
|
||||
|
||||
public starts(line: number, column: number): boolean {
|
||||
return line === this.line && column === this.column;
|
||||
}
|
||||
|
||||
public intersects(line: number, column: number): boolean {
|
||||
return line === this.line && column > this.column && column < this.column + this.length;
|
||||
}
|
||||
|
||||
public duplicate(): Position {
|
||||
return new Position(this.line, this.column, this.length);
|
||||
}
|
||||
|
||||
public withLength(length: number): Position {
|
||||
const duplicate: Position = this.duplicate();
|
||||
|
||||
duplicate.length = length;
|
||||
|
||||
return duplicate;
|
||||
}
|
||||
|
||||
public setColumn(column: number): Position {
|
||||
this.column = column;
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
public addColumn(column: number): Position {
|
||||
this.column += column;
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
public addLine(line: number): Position {
|
||||
this.line += line;
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
public static none: Position = new Position(0, 0, 0);
|
||||
}
|
|
@ -1,16 +0,0 @@
|
|||
import { Position } from '../position.class.js';
|
||||
|
||||
export class IdentifierState {
|
||||
private constructor(public value: string, public active: boolean, public start: Position) {
|
||||
}
|
||||
|
||||
public duplicate(): IdentifierState {
|
||||
return new IdentifierState(this.value, this.active, this.start.duplicate());
|
||||
}
|
||||
|
||||
public static start(position: Position): IdentifierState {
|
||||
return new IdentifierState('', true, position.duplicate());
|
||||
}
|
||||
|
||||
public static none: IdentifierState = new IdentifierState(null, false, Position.none.duplicate());
|
||||
}
|
|
@ -1,16 +0,0 @@
|
|||
import { Position } from '../position.class.js';
|
||||
|
||||
export class NumberState {
|
||||
private constructor(public value: string, public decimal: boolean, public active: boolean, public start: Position) {
|
||||
}
|
||||
|
||||
public duplicate(): NumberState {
|
||||
return new NumberState(this.value, this.decimal, this.active, this.start.duplicate());
|
||||
}
|
||||
|
||||
public static start(position: Position): NumberState {
|
||||
return new NumberState('', false, true, position.duplicate());
|
||||
}
|
||||
|
||||
public static none: NumberState = new NumberState(null, false, false, Position.none.duplicate());
|
||||
}
|
|
@ -1,16 +0,0 @@
|
|||
import { Position } from '../position.class.js';
|
||||
|
||||
export class StringState {
|
||||
private constructor(public value: string, public quote: string, public active: boolean, public start: Position) {
|
||||
}
|
||||
|
||||
public duplicate(): StringState {
|
||||
return new StringState(this.value, this.quote, this.active, this.start.duplicate());
|
||||
}
|
||||
|
||||
public static start(quote: string, position: Position): StringState {
|
||||
return new StringState('', quote, true, position.duplicate());
|
||||
}
|
||||
|
||||
public static none: StringState = new StringState(null, null, false, Position.none.duplicate());
|
||||
}
|
|
@ -1,7 +0,0 @@
|
|||
import { Position } from './position.class.js';
|
||||
|
||||
export interface Token {
|
||||
type: string;
|
||||
value: string;
|
||||
position: Position;
|
||||
}
|
|
@ -1,7 +0,0 @@
|
|||
export enum Type {
|
||||
Identifier = '#606872',
|
||||
Keyword = '#494f56',
|
||||
Punctuation = '#72777c',
|
||||
Nesting = '#78818d',
|
||||
Literal = '#8f969e'
|
||||
}
|
|
@ -1,4 +0,0 @@
|
|||
{
|
||||
"presets": ["next/babel"],
|
||||
"plugins": []
|
||||
}
|
|
@ -1,82 +0,0 @@
|
|||
const path = require("path")
|
||||
|
||||
const toPath = (_path) => path.join(process.cwd(), _path)
|
||||
|
||||
module.exports = {
|
||||
stories: ["../src/**/*.stories.@(js|jsx|ts|tsx)"],
|
||||
addons: ["@storybook/addon-links", "@storybook/addon-essentials"],
|
||||
|
||||
webpackFinal: async (config, { configType }) => {
|
||||
// `configType` has a value of 'DEVELOPMENT' or 'PRODUCTION'
|
||||
// You can change the configuration based on that.
|
||||
// 'PRODUCTION' is used when building the static version of storybook.
|
||||
|
||||
// Added to support PostCSS v8.X
|
||||
/**
|
||||
* CSS handling, specifically overriding postcss loader
|
||||
*/
|
||||
// Find the only Storybook webpack rule that tests for css
|
||||
const cssRule = config.module.rules.find((rule) =>
|
||||
"test.css".match(rule.test)
|
||||
)
|
||||
// Which loader in this rule mentions the custom Storybook postcss-loader?
|
||||
const loaderIndex = cssRule.use.findIndex((loader) => {
|
||||
// Loaders can be strings or objects
|
||||
const loaderString = typeof loader === "string" ? loader : loader.loader
|
||||
// Find the first mention of "postcss-loader", it may be in a string like:
|
||||
// "@storybook/core/node_modules/postcss-loader"
|
||||
return loaderString.includes("postcss-loader")
|
||||
})
|
||||
// Simple loader string form, removes the obsolete "options" key
|
||||
cssRule.use[loaderIndex] = "postcss-loader"
|
||||
|
||||
// ignore *.po files
|
||||
config.module.rules.push({
|
||||
test: /\.(po)$/,
|
||||
use: [
|
||||
{
|
||||
loader: require.resolve("ignore-loader")
|
||||
}
|
||||
]
|
||||
})
|
||||
|
||||
// SVG
|
||||
// Needed for SVG importing using svgr
|
||||
const indexOfRuleToRemove = config.module.rules.findIndex((rule) =>
|
||||
rule.test?.toString().includes("svg")
|
||||
)
|
||||
|
||||
config.module.rules.splice(indexOfRuleToRemove, 1, {
|
||||
test: /\.(ico|jpg|jpeg|png|gif|eot|otf|webp|ttf|woff|woff2|cur|ani|pdf)(\?.*)?$/,
|
||||
loader: require.resolve("file-loader"),
|
||||
options: {
|
||||
name: "static/media/[name].[hash:8].[ext]",
|
||||
esModule: false
|
||||
}
|
||||
})
|
||||
config.module.rules.push({
|
||||
test: /\.svg$/,
|
||||
use: [
|
||||
{
|
||||
loader: "@svgr/webpack",
|
||||
options: {
|
||||
svgo: false
|
||||
}
|
||||
}
|
||||
]
|
||||
})
|
||||
|
||||
return {
|
||||
...config,
|
||||
resolve: {
|
||||
...config.resolve,
|
||||
alias: {
|
||||
...config.resolve.alias
|
||||
// "@emotion/core": toPath("node_modules/@emotion/react"),
|
||||
// "@emotion/styled": toPath("node_modules/@emotion/styled"),
|
||||
// "emotion-theming": toPath("node_modules/@emotion/react")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,213 +0,0 @@
|
|||
// .storybook/preview.js
|
||||
import { themes } from "@storybook/theming"
|
||||
|
||||
import "../src/styles/tailwind.css"
|
||||
|
||||
import * as nextImage from "next/image"
|
||||
|
||||
// or global addParameters
|
||||
export const parameters = {
|
||||
docs: {
|
||||
theme: themes.normal
|
||||
},
|
||||
actions: { argTypesRegex: "^on[A-Z].*" },
|
||||
viewport: {
|
||||
viewports: {
|
||||
mobile1: {
|
||||
name: "Small mobile",
|
||||
styles: {
|
||||
height: "568px",
|
||||
width: "320px"
|
||||
},
|
||||
type: "mobile"
|
||||
},
|
||||
mobile2: {
|
||||
name: "Large mobile",
|
||||
styles: {
|
||||
height: "896px",
|
||||
width: "414px"
|
||||
},
|
||||
type: "mobile"
|
||||
},
|
||||
tablet: {
|
||||
name: "Tablet",
|
||||
styles: {
|
||||
height: "1112px",
|
||||
width: "834px"
|
||||
},
|
||||
type: "tablet"
|
||||
},
|
||||
iphone5: {
|
||||
name: "iPhone 5",
|
||||
styles: {
|
||||
height: "568px",
|
||||
width: "320px"
|
||||
},
|
||||
type: "mobile"
|
||||
},
|
||||
iphone6: {
|
||||
name: "iPhone 6",
|
||||
styles: {
|
||||
height: "667px",
|
||||
width: "375px"
|
||||
},
|
||||
type: "mobile"
|
||||
},
|
||||
iphone6p: {
|
||||
name: "iPhone 6 Plus",
|
||||
styles: {
|
||||
height: "736px",
|
||||
width: "414px"
|
||||
},
|
||||
type: "mobile"
|
||||
},
|
||||
iphone8p: {
|
||||
name: "iPhone 8 Plus",
|
||||
styles: {
|
||||
height: "736px",
|
||||
width: "414px"
|
||||
},
|
||||
type: "mobile"
|
||||
},
|
||||
iphonex: {
|
||||
name: "iPhone X",
|
||||
styles: {
|
||||
height: "812px",
|
||||
width: "375px"
|
||||
},
|
||||
type: "mobile"
|
||||
},
|
||||
iphonexr: {
|
||||
name: "iPhone XR",
|
||||
styles: {
|
||||
height: "896px",
|
||||
width: "414px"
|
||||
},
|
||||
type: "mobile"
|
||||
},
|
||||
iphonexsmax: {
|
||||
name: "iPhone XS Max",
|
||||
styles: {
|
||||
height: "896px",
|
||||
width: "414px"
|
||||
},
|
||||
type: "mobile"
|
||||
},
|
||||
ipad: {
|
||||
name: "iPad",
|
||||
styles: {
|
||||
height: "1024px",
|
||||
width: "768px"
|
||||
},
|
||||
type: "tablet"
|
||||
},
|
||||
ipad10p: {
|
||||
name: "iPad Pro 10.5-in",
|
||||
styles: {
|
||||
height: "1112px",
|
||||
width: "834px"
|
||||
},
|
||||
type: "tablet"
|
||||
},
|
||||
ipad12p: {
|
||||
name: "iPad Pro 12.9-in",
|
||||
styles: {
|
||||
height: "1366px",
|
||||
width: "1024px"
|
||||
},
|
||||
type: "tablet"
|
||||
},
|
||||
galaxys5: {
|
||||
name: "Galaxy S5",
|
||||
styles: {
|
||||
height: "640px",
|
||||
width: "360px"
|
||||
},
|
||||
type: "mobile"
|
||||
},
|
||||
galaxys9: {
|
||||
name: "Galaxy S9",
|
||||
styles: {
|
||||
height: "740px",
|
||||
width: "360px"
|
||||
},
|
||||
type: "mobile"
|
||||
},
|
||||
nexus5x: {
|
||||
name: "Nexus 5X",
|
||||
styles: {
|
||||
height: "660px",
|
||||
width: "412px"
|
||||
},
|
||||
type: "mobile"
|
||||
},
|
||||
nexus6p: {
|
||||
name: "Nexus 6P",
|
||||
styles: {
|
||||
height: "732px",
|
||||
width: "412px"
|
||||
},
|
||||
type: "mobile"
|
||||
},
|
||||
pixel: {
|
||||
name: "Pixel",
|
||||
styles: {
|
||||
height: "960px",
|
||||
width: "540px"
|
||||
},
|
||||
type: "mobile"
|
||||
},
|
||||
pixelxl: {
|
||||
name: "Pixel XL",
|
||||
styles: {
|
||||
height: "1280px",
|
||||
width: "720px"
|
||||
},
|
||||
type: "mobile"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Replace next/image for Storybook
|
||||
Object.defineProperty(nextImage, "default", {
|
||||
configurable: true,
|
||||
value: (props) => {
|
||||
const { width, height, layout } = props
|
||||
|
||||
if (layout === "fill") {
|
||||
return (
|
||||
<img
|
||||
style={{
|
||||
objectFit: "cover",
|
||||
position: "absolute",
|
||||
width: "100%",
|
||||
height: "100%"
|
||||
}}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
const ratio = (height / width) * 100
|
||||
|
||||
return (
|
||||
<div
|
||||
style={{
|
||||
paddingBottom: `${ratio}%`,
|
||||
position: "relative"
|
||||
}}
|
||||
>
|
||||
<img
|
||||
style={{
|
||||
objectFit: "cover",
|
||||
position: "absolute",
|
||||
width: "100%",
|
||||
height: "100%"
|
||||
}}
|
||||
{...props}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
})
|
5
.vscode/extensions.json
vendored
|
@ -1,5 +0,0 @@
|
|||
{
|
||||
"recommendations": [
|
||||
"bradlc.vscode-tailwindcss"
|
||||
]
|
||||
}
|
4
@types/index.d.ts
vendored
|
@ -1,4 +0,0 @@
|
|||
declare module "*.svg" {
|
||||
const content: any
|
||||
export default content
|
||||
}
|
|
@ -1,76 +0,0 @@
|
|||
# Contributor Covenant Code of Conduct
|
||||
|
||||
## Our Pledge
|
||||
|
||||
In the interest of fostering an open and welcoming environment, we as
|
||||
contributors and maintainers pledge to making participation in our project and
|
||||
our community a harassment-free experience for everyone, regardless of age, body
|
||||
size, disability, ethnicity, sex characteristics, gender identity and expression,
|
||||
level of experience, education, socio-economic status, nationality, personal
|
||||
appearance, race, religion, or sexual identity and orientation.
|
||||
|
||||
## Our Standards
|
||||
|
||||
Examples of behavior that contributes to creating a positive environment
|
||||
include:
|
||||
|
||||
* Using welcoming and inclusive language
|
||||
* Being respectful of differing viewpoints and experiences
|
||||
* Gracefully accepting constructive criticism
|
||||
* Focusing on what is best for the community
|
||||
* Showing empathy towards other community members
|
||||
|
||||
Examples of unacceptable behavior by participants include:
|
||||
|
||||
* The use of sexualized language or imagery and unwelcome sexual attention or
|
||||
advances
|
||||
* Trolling, insulting/derogatory comments, and personal or political attacks
|
||||
* Public or private harassment
|
||||
* Publishing others' private information, such as a physical or electronic
|
||||
address, without explicit permission
|
||||
* Other conduct which could reasonably be considered inappropriate in a
|
||||
professional setting
|
||||
|
||||
## Our Responsibilities
|
||||
|
||||
Project maintainers are responsible for clarifying the standards of acceptable
|
||||
behavior and are expected to take appropriate and fair corrective action in
|
||||
response to any instances of unacceptable behavior.
|
||||
|
||||
Project maintainers have the right and responsibility to remove, edit, or
|
||||
reject comments, commits, code, wiki edits, issues, and other contributions
|
||||
that are not aligned to this Code of Conduct, or to ban temporarily or
|
||||
permanently any contributor for other behaviors that they deem inappropriate,
|
||||
threatening, offensive, or harmful.
|
||||
|
||||
## Scope
|
||||
|
||||
This Code of Conduct applies both within project spaces and in public spaces
|
||||
when an individual is representing the project or its community. Examples of
|
||||
representing a project or community include using an official project e-mail
|
||||
address, posting via an official social media account, or acting as an appointed
|
||||
representative at an online or offline event. Representation of a project may be
|
||||
further defined and clarified by project maintainers.
|
||||
|
||||
## Enforcement
|
||||
|
||||
Instances of abusive, harassing, or otherwise unacceptable behavior may be
|
||||
reported by contacting the project team at contact@elitizon.com. All
|
||||
complaints will be reviewed and investigated and will result in a response that
|
||||
is deemed necessary and appropriate to the circumstances. The project team is
|
||||
obligated to maintain confidentiality with regard to the reporter of an incident.
|
||||
Further details of specific enforcement policies may be posted separately.
|
||||
|
||||
Project maintainers who do not follow or enforce the Code of Conduct in good
|
||||
faith may face temporary or permanent repercussions as determined by other
|
||||
members of the project's leadership.
|
||||
|
||||
## Attribution
|
||||
|
||||
This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,
|
||||
available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html
|
||||
|
||||
[homepage]: https://www.contributor-covenant.org
|
||||
|
||||
For answers to common questions about this code of conduct, see
|
||||
https://www.contributor-covenant.org/faq
|
|
@ -1,92 +0,0 @@
|
|||
# Contributing
|
||||
|
||||
When contributing to this repository, please first discuss the change you wish to make via issue,
|
||||
email, or any other method with the owners of this repository before making a change.
|
||||
|
||||
Please note we have a code of conduct, please follow it in all your interactions with the project.
|
||||
|
||||
## Pull Request Process
|
||||
|
||||
1. Ensure any install or build dependencies are removed before the end of the layer when doing a
|
||||
build.
|
||||
2. Update the README.md with details of changes to the interface, this includes new environment
|
||||
variables, exposed ports, useful file locations and container parameters.
|
||||
3. Increase the version numbers in any examples files and the README.md to the new version that this
|
||||
Pull Request would represent. The versioning scheme we use is [SemVer](http://semver.org/).
|
||||
4. You may merge the Pull Request in once you have the sign-off of two other developers, or if you
|
||||
do not have permission to do that, you may request the second reviewer to merge it for you.
|
||||
|
||||
## Code of Conduct
|
||||
|
||||
### Our Pledge
|
||||
|
||||
In the interest of fostering an open and welcoming environment, we as
|
||||
contributors and maintainers pledge to making participation in our project and
|
||||
our community a harassment-free experience for everyone, regardless of age, body
|
||||
size, disability, ethnicity, gender identity and expression, level of experience,
|
||||
nationality, personal appearance, race, religion, or sexual identity and
|
||||
orientation.
|
||||
|
||||
### Our Standards
|
||||
|
||||
Examples of behavior that contributes to creating a positive environment
|
||||
include:
|
||||
|
||||
* Using welcoming and inclusive language
|
||||
* Being respectful of differing viewpoints and experiences
|
||||
* Gracefully accepting constructive criticism
|
||||
* Focusing on what is best for the community
|
||||
* Showing empathy towards other community members
|
||||
|
||||
Examples of unacceptable behavior by participants include:
|
||||
|
||||
* The use of sexualized language or imagery and unwelcome sexual attention or
|
||||
advances
|
||||
* Trolling, insulting/derogatory comments, and personal or political attacks
|
||||
* Public or private harassment
|
||||
* Publishing others' private information, such as a physical or electronic
|
||||
address, without explicit permission
|
||||
* Other conduct which could reasonably be considered inappropriate in a
|
||||
professional setting
|
||||
|
||||
### Our Responsibilities
|
||||
|
||||
Project maintainers are responsible for clarifying the standards of acceptable
|
||||
behavior and are expected to take appropriate and fair corrective action in
|
||||
response to any instances of unacceptable behavior.
|
||||
|
||||
Project maintainers have the right and responsibility to remove, edit, or
|
||||
reject comments, commits, code, wiki edits, issues, and other contributions
|
||||
that are not aligned to this Code of Conduct, or to ban temporarily or
|
||||
permanently any contributor for other behaviors that they deem inappropriate,
|
||||
threatening, offensive, or harmful.
|
||||
|
||||
### Scope
|
||||
|
||||
This Code of Conduct applies both within project spaces and in public spaces
|
||||
when an individual is representing the project or its community. Examples of
|
||||
representing a project or community include using an official project e-mail
|
||||
address, posting via an official social media account, or acting as an appointed
|
||||
representative at an online or offline event. Representation of a project may be
|
||||
further defined and clarified by project maintainers.
|
||||
|
||||
### Enforcement
|
||||
|
||||
Instances of abusive, harassing, or otherwise unacceptable behavior may be
|
||||
reported by contacting the project team at [contact@elitizon.com]. All
|
||||
complaints will be reviewed and investigated and will result in a response that
|
||||
is deemed necessary and appropriate to the circumstances. The project team is
|
||||
obligated to maintain confidentiality with regard to the reporter of an incident.
|
||||
Further details of specific enforcement policies may be posted separately.
|
||||
|
||||
Project maintainers who do not follow or enforce the Code of Conduct in good
|
||||
faith may face temporary or permanent repercussions as determined by other
|
||||
members of the project's leadership.
|
||||
|
||||
### Attribution
|
||||
|
||||
This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,
|
||||
available at [http://contributor-covenant.org/version/1/4][version]
|
||||
|
||||
[homepage]: http://contributor-covenant.org
|
||||
[version]: http://contributor-covenant.org/version/1/4/
|
18
Dockerfile
Normal file
|
@ -0,0 +1,18 @@
|
|||
FROM node:alpine as build
|
||||
|
||||
WORKDIR /usr/src/app
|
||||
|
||||
RUN sed -i 's/dl-cdn/dl-5/g' /etc/apk/repositories && sed -i 's/http:/https:/g' /etc/apk/repositories
|
||||
|
||||
COPY .npmrc /usr/src/app/
|
||||
COPY package.json /usr/src/app/
|
||||
COPY package-lock.json /usr/src/app/
|
||||
|
||||
RUN npm install --production
|
||||
|
||||
COPY . /usr/src/app
|
||||
RUN npx tsc -p tsconfig.json
|
||||
|
||||
FROM httpd:alpine
|
||||
|
||||
COPY --from=build /usr/src/app/public/ /usr/local/apache2/htdocs/
|
21
LICENSE
|
@ -1,21 +0,0 @@
|
|||
MIT License
|
||||
|
||||
Copyright (c) 2021 Elitizon Ltd
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
189
README.md
|
@ -1,189 +0,0 @@
|
|||
# A [NextJS](https://nextjs.org/) template with typescript, tailwindcss and storybook support
|
||||
|
||||
> The configuration of NextJS, TailwindCSS and Storybook can be **complex** 😰 🤪
|
||||
> 👉 So we have decided to make this template public 🎉
|
||||
|
||||
This project was bootstrapped with [npx create-next-app](https://nextjs.org/learn/basics/create-nextjs-app/setup) and we have added support for:
|
||||
|
||||
- ✅ [Typescript](https://www.typescriptlang.org/)
|
||||
- ✅ [Taillwindcss](https://www.tailwindcss.com)
|
||||
- ✅ [Storybook](https://storybook.js.org/)
|
||||
- ✅ [Jest](https://jestjs.io/)
|
||||
- ✅ [Import SVG as React Component (SVGR)](https://react-svgr.com/) (Thanks to [@neoziro](https://twitter.com/neoziro))
|
||||
|
||||
You are welcome to contribute to this project to make it better. Contact us at [contact@elitizon.com](contact@elitizon.com)
|
||||
|
||||
Created with ❤️ by [Elitizon](https://www.elitizon.com)
|
||||
|
||||
This template is available at [https://github.com/elitizon/nextjs-tailwind-storybook](https://github.com/elitizon/nextjs-tailwind-storybook)
|
||||
|
||||
## To use this template:
|
||||
|
||||
- clone it
|
||||
- remove the .git folder
|
||||
- git init .
|
||||
- git add .
|
||||
- git commit -m "First commit"
|
||||
|
||||
## Structure of the template
|
||||
|
||||
```bash
|
||||
.
|
||||
├── CODE_OF_CONDUCT.md
|
||||
├── CONTRIBUTING.md
|
||||
├── LICENSE
|
||||
├── README.md
|
||||
├── SECURITY.md
|
||||
├── build.toml
|
||||
├── next-env.d.ts
|
||||
├── nextjs.config.js
|
||||
├── out
|
||||
│ ├── 404.html
|
||||
│ ├── _next
|
||||
│ ├── favicon.ico
|
||||
│ ├── index.html
|
||||
│ └── vercel.svg
|
||||
├── package.json
|
||||
├── postcss.config.js
|
||||
├── public
|
||||
│ ├── favicon.ico
|
||||
│ └── vercel.svg
|
||||
├── src
|
||||
│ ├── assets
|
||||
│ ├── components
|
||||
│ ├── pages
|
||||
│ └── styles
|
||||
├── tailwind.config.js
|
||||
├── tsconfig.json
|
||||
└── yarn.lock
|
||||
|
||||
```
|
||||
|
||||
Pages and components are developed in `src` directory.
|
||||
|
||||
## Install all the dependencies
|
||||
|
||||
### 👉 `yarn install`
|
||||
|
||||
## Available Scripts
|
||||
|
||||
In the project directory, you can run:
|
||||
|
||||
### 👉 `yarn dev`
|
||||
|
||||
**Results:**
|
||||
|
||||
```bash
|
||||
ready - started server on http://localhost:3000
|
||||
✅ purgeEnabled=false
|
||||
|
||||
event - compiled successfully
|
||||
event - build page: /next/dist/pages/_error
|
||||
wait - compiling...
|
||||
event - compiled successfully
|
||||
event - build page: /
|
||||
wait - compiling...```
|
||||
|
||||
Run the project in the dev mode.
|
||||
````
|
||||
|
||||
Runs the app in the development mode.\
|
||||
Open [http://localhost:3000](http://localhost:3000) to view it in the browser.
|
||||
|
||||
The page will reload if you make edits.\
|
||||
You will also see any lint errors in the console.
|
||||
|
||||
### 👉 `yarn storybook`
|
||||
|
||||
Runs storybook.
|
||||
|
||||
Open [http://localhost:6006](http://localhost:6006) to view it in the browser.
|
||||
|
||||
### 👉 `yarn test`
|
||||
|
||||
Launches the test runner in the interactive watch mode.\
|
||||
See the section about [running tests](https://facebook.github.io/create-react-app/docs/running-tests) for more information.
|
||||
|
||||
> If you get an error that contained this line:
|
||||
|
||||
Use this command:
|
||||
|
||||
`brew install watchman`
|
||||
|
||||
### 👉`yarn build`
|
||||
|
||||
Builds the app for production to the `.next` folder.\
|
||||
It correctly bundles NextJS in production mode and optimizes the build for the best performance.
|
||||
|
||||
The build is minified and the filenames include the hashes.\
|
||||
Your app is ready to be deployed!
|
||||
|
||||
👉 **Result of execution**
|
||||
|
||||
```bash
|
||||
yarn run v1.22.10
|
||||
$ cross-env NODE_ENV=production next build
|
||||
info - Creating an optimized production build...
|
||||
|
||||
|
||||
TailwindCSS
|
||||
|
||||
-----------
|
||||
|
||||
✅ purgeEnabled=true
|
||||
|
||||
info - Compiled successfully
|
||||
info - Collecting page data...
|
||||
info - Generating static pages (0/2)
|
||||
info - Generating static pages (2/2)
|
||||
info - Finalizing page optimization...
|
||||
|
||||
Page Size First Load JS
|
||||
┌ ○ / 1.55 kB 64.3 kB
|
||||
├ /_app 0 B 62.7 kB
|
||||
├ ○ /404 3.46 kB 66.2 kB
|
||||
└ λ /api/hello 0 B 62.7 kB
|
||||
+ First Load JS shared by all 62.7 kB
|
||||
├ chunks/f6078781a05fe1bcb0902d23dbbb2662c8d200b3.d4f570.js 13.1 kB
|
||||
├ chunks/framework.abffcf.js 41.8 kB
|
||||
├ chunks/main.1fee81.js 6.62 kB
|
||||
├ chunks/pages/_app.1315ea.js 523 B
|
||||
├ chunks/webpack.50bee0.js 751 B
|
||||
└ css/ff7ad52a1259dc7bd680.css 1.88 kB
|
||||
|
||||
λ (Server) server-side renders at runtime (uses getInitialProps or getServerSideProps)
|
||||
○ (Static) automatically rendered as static HTML (uses no initial props)
|
||||
● (SSG) automatically generated as static HTML + JSON (uses getStaticProps)
|
||||
(ISR) incremental static regeneration (uses revalidate in getStaticProps)
|
||||
|
||||
Done in 9.00s.
|
||||
|
||||
```
|
||||
|
||||
See the section about [deployment](https://nextjs.org/docs/deployment) for more information.
|
||||
|
||||
### 👉`yarn start`
|
||||
|
||||
Starts a server with the output for the `yarn build` command.
|
||||
|
||||
`yarn build` must be executed before to use this command.
|
||||
|
||||
### 👉`yarn export`
|
||||
|
||||
Export the output of the `yarn build` command execution to the `./out` directory.
|
||||
|
||||
`yarn build` must be executed before to use this command.
|
||||
|
||||
### 👉`npx serve ./out`
|
||||
|
||||
To launch a **static server** from the `./out` directory. This command can be useful to control the outcome of `yarn export`.
|
||||
|
||||
`yarn build` and `yarn export` must be executed before to use this command.
|
||||
|
||||
## Learn More
|
||||
|
||||
You can learn more in the [NextJS documentation](https://nextjs.org/docs/getting-started).
|
||||
|
||||
To learn React, check out the [React documentation](https://reactjs.org/).
|
||||
|
||||
To learn how to develop UIs with component and design systems with Storybook, check out the [Learn Storybook documentation](https://www.learnstorybook.com/)
|
10
SECURITY.md
|
@ -1,10 +0,0 @@
|
|||
# Security Policy
|
||||
|
||||
## Supported Versions
|
||||
|
||||
|
||||
|
||||
| Version | Supported |
|
||||
| ------- | ------------------ |
|
||||
| 1.0.x | :white_check_mark: |
|
||||
|
|
@ -1,3 +0,0 @@
|
|||
[build]
|
||||
command = "yarn build && yarn export"
|
||||
publish = "out"
|
2
next-env.d.ts
vendored
|
@ -1,2 +0,0 @@
|
|||
/// <reference types="next" />
|
||||
/// <reference types="next/types/global" />
|
|
@ -1,48 +0,0 @@
|
|||
module.exports = {
|
||||
/* config options here */
|
||||
webpack(config, { isServer, dev: isDevelopmentMode }) {
|
||||
config.module.rules.push({
|
||||
test: /\.svg$/,
|
||||
issuer: {
|
||||
test: /\.(js|ts)x?$/
|
||||
},
|
||||
use: [
|
||||
{
|
||||
loader: "@svgr/webpack"
|
||||
// https://react-svgr.com/docs/options/
|
||||
}
|
||||
]
|
||||
})
|
||||
|
||||
config.module.rules.push({
|
||||
test: /\.po$/,
|
||||
use: [
|
||||
{
|
||||
loader: "ignore-loader"
|
||||
}
|
||||
]
|
||||
})
|
||||
|
||||
// Fixes npm packages that depend on `fs` module
|
||||
if (!isServer) {
|
||||
config.node = {
|
||||
fs: "empty"
|
||||
}
|
||||
}
|
||||
|
||||
// Attempt to ignore storybook files when doing a production build,
|
||||
// see also: https://github.com/vercel/next.js/issues/1914
|
||||
if (!isDevelopmentMode) {
|
||||
config.module.rules.push({
|
||||
test: /\.stories.(js|tsx?)/,
|
||||
loader: "ignore-loader"
|
||||
})
|
||||
}
|
||||
|
||||
return config
|
||||
},
|
||||
poweredByHeader: false,
|
||||
images: {
|
||||
domains: ["unsplash.com"]
|
||||
}
|
||||
}
|
5184
package-lock.json
generated
Normal file
104
package.json
|
@ -1,93 +1,25 @@
|
|||
{
|
||||
"name": "nwex-de",
|
||||
"name": "nwex.de",
|
||||
"version": "1.0.0",
|
||||
"keywords": [
|
||||
"react",
|
||||
"nextjs",
|
||||
"tailwind",
|
||||
"storybook",
|
||||
"jest"
|
||||
],
|
||||
"description": "Landing page for nwex.de",
|
||||
"scripts": {
|
||||
"dev": "next dev",
|
||||
"build": "cross-env NODE_ENV=production next build",
|
||||
"start": "next start",
|
||||
"export": "next export",
|
||||
"test": "jest --watch",
|
||||
"storybook": "start-storybook -p 6006 -s public",
|
||||
"build-storybook": "build-storybook -s public"
|
||||
"start": "concurrently --kill-others 'tsc -p tsconfig.json --watch' 'reload -b -d public'"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://gitlab.upi.li/networkException/nwex.de"
|
||||
},
|
||||
"author": "networkException",
|
||||
"license": "UNLICENSED",
|
||||
"devDependencies": {
|
||||
"@typescript-eslint/eslint-plugin": "^5.9.0",
|
||||
"concurrently": "^6.3.0",
|
||||
"eslint": "^8.6.0",
|
||||
"eslint-plugin-import": "^2.25.4",
|
||||
"eslint-plugin-only-warn": "^1.0.3",
|
||||
"reload": "^3.2.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"framer-motion": "^4.1.2",
|
||||
"fs": "^0.0.1-security",
|
||||
"next": "10.0.5",
|
||||
"react": "17.0.1",
|
||||
"react-calendar-heatmap": "^1.8.1",
|
||||
"react-dom": "17.0.1",
|
||||
"react-visibility-sensor": "^5.1.1",
|
||||
"swr": "^0.5.5"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/core": "^7.12.10",
|
||||
"@babel/preset-env": "^7.12.11",
|
||||
"@babel/preset-react": "^7.12.10",
|
||||
"@next/plugin-storybook": "^10.0.5",
|
||||
"@storybook/addon-actions": "^6.1.14",
|
||||
"@storybook/addon-essentials": "^6.1.14",
|
||||
"@storybook/addon-links": "^6.1.14",
|
||||
"@storybook/addon-viewport": "^6.1.14",
|
||||
"@storybook/addons": "^6.1.14",
|
||||
"@storybook/node-logger": "^6.1.14",
|
||||
"@storybook/react": "^6.1.14",
|
||||
"@storybook/theming": "^6.1.14",
|
||||
"@svgr/webpack": "^5.5.0",
|
||||
"@testing-library/dom": "^7.29.4",
|
||||
"@testing-library/jest-dom": "^5.11.9",
|
||||
"@testing-library/react": "^11.2.3",
|
||||
"@testing-library/user-event": "^12.1.10",
|
||||
"@types/jest": "^26.0.20",
|
||||
"@types/node": "^14.14.21",
|
||||
"@types/react": "^17.0.0",
|
||||
"@types/react-dom": "^17.0.0",
|
||||
"autoprefixer": "^10.2.1",
|
||||
"babel-core": "7.0.0-bridge.0",
|
||||
"babel-jest": "^26.6.3",
|
||||
"chokidar": "^3.5.1",
|
||||
"chokidar-cli": "^2.1.0",
|
||||
"cross-env": "^7.0.3",
|
||||
"ignore-loader": "^0.1.2",
|
||||
"jest": "^26.6.3",
|
||||
"npm-run-all": "^4.1.5",
|
||||
"postcss": "^8.2.4",
|
||||
"postcss-cli": "^8.3.1",
|
||||
"postcss-loader": "^4.1.0",
|
||||
"react-test-renderer": "^17.0.1",
|
||||
"sharp": "^0.27.0",
|
||||
"storybook": "^6.1.14",
|
||||
"storybook-addon-next-router": "^2.0.3",
|
||||
"tailwindcss": "^2.0.2",
|
||||
"typescript": "^4.1.3"
|
||||
},
|
||||
"resolutions": {
|
||||
"react": "17.0.1"
|
||||
},
|
||||
"browserslist": [
|
||||
">0.3%",
|
||||
"not ie 11",
|
||||
"not dead",
|
||||
"not op_mini all"
|
||||
],
|
||||
"jest": {
|
||||
"testPathIgnorePatterns": [
|
||||
"./.next/",
|
||||
"./node_modules/",
|
||||
"./.storybook/"
|
||||
],
|
||||
"transform": {
|
||||
"^.+\\.(js|jsx|ts|tsx)$": "<rootDir>/node_modules/babel-jest"
|
||||
},
|
||||
"moduleNameMapper": {
|
||||
"\\.svg": "<rootDir>/__mocks__/svgrMock.js"
|
||||
}
|
||||
"typescript": "^4.4.4"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,3 +0,0 @@
|
|||
module.exports = {
|
||||
plugins: { tailwindcss: {}, autoprefixer: {} }
|
||||
}
|
|
@ -1,10 +0,0 @@
|
|||
<svg width="124" height="124" viewBox="0 0 124 124" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g clip-path="url(#clip0)">
|
||||
<path d="M47.7325 22.0005C46.3142 22.0062 34.581 22.3375 22.2031 31.4896C22.2031 31.4896 9 55.1198 9 84.2239C9 84.2239 16.7017 97.3617 36.9648 98.0003C36.9648 98.0003 40.3572 93.986 43.1078 90.519C31.4634 87.0521 27.0624 79.8447 27.0624 79.8447C27.0624 79.8447 27.9795 80.4833 29.6299 81.3956C29.7216 81.3956 29.813 81.4867 29.9964 81.578C30.2714 81.7604 30.5466 81.8517 30.8217 82.0341C33.1139 83.3114 35.406 84.3152 37.5148 85.1363C41.274 86.6873 45.7669 88.0557 50.9931 89.0593C57.8697 90.3366 65.9381 90.7927 74.7402 89.1504C79.0495 88.3293 83.4508 87.1434 88.0353 85.2275C91.2443 84.0414 94.82 82.3081 98.5793 79.8447C98.5793 79.8447 93.9949 87.2348 81.9838 90.6105C84.7344 93.9862 88.0353 97.9092 88.0353 97.9092C108.298 97.2705 116 84.1327 116 84.2239C116 55.1198 102.797 31.4896 102.797 31.4896C89.6855 21.7274 77.1241 22.0012 77.1241 22.0012L75.8407 23.4609C91.4277 28.1139 98.6709 34.9568 98.6709 34.9568C89.1353 29.8476 79.7833 27.2928 71.0729 26.2892C64.4714 25.5593 58.1447 25.742 52.5517 26.4719C52.0016 26.4719 51.5432 26.563 50.9931 26.6542C47.784 27.0192 39.9906 28.1139 30.18 32.402C26.7875 33.8617 24.7702 34.9568 24.7702 34.9568C24.7702 34.9568 32.2886 27.7489 48.7925 23.0959L47.8756 22.0012C47.8756 22.0012 47.827 22.0001 47.7325 22.0004V22.0005ZM45.4001 55.6672C50.6263 55.6672 54.844 60.1376 54.7523 65.7029C54.7523 71.2683 50.6263 75.739 45.4001 75.739C40.2656 75.739 36.0479 71.2683 36.0479 65.7029C36.0479 60.1376 40.1739 55.6672 45.4001 55.6672ZM78.8662 55.6672C84.0008 55.6672 88.2185 60.1376 88.2185 65.7029C88.2185 71.2683 84.0925 75.739 78.8662 75.739C73.7317 75.739 69.514 71.2683 69.514 65.7029C69.514 60.1376 73.64 55.6672 78.8662 55.6672Z" fill="#2D3135" />
|
||||
</g>
|
||||
<defs>
|
||||
<clipPath id="clip0">
|
||||
<rect width="107" height="76" fill="white" transform="translate(9 22)" />
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
Before Width: | Height: | Size: 1.9 KiB |
|
@ -1,3 +0,0 @@
|
|||
<svg width="49" height="49" viewBox="0 0 49 49" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M45.7132 12.8015C43.5223 9.04771 40.5506 6.07586 36.7972 3.88524C33.0432 1.69451 28.945 0.599426 24.4996 0.599426C20.0548 0.599426 15.9552 1.69485 12.202 3.88524C8.44829 6.07575 5.47666 9.04771 3.28582 12.8015C1.09531 16.5551 0 20.6541 0 25.0984C0 30.4369 1.55755 35.2375 4.67342 39.5013C7.78896 43.7654 11.8138 46.7161 16.7475 48.3535C17.3218 48.4601 17.747 48.3851 18.0234 48.1305C18.2999 47.8755 18.438 47.5562 18.438 47.1738C18.438 47.11 18.4326 46.5361 18.4219 45.4513C18.411 44.3665 18.4058 43.4201 18.4058 42.6126L17.6721 42.7395C17.2043 42.8252 16.6141 42.8615 15.9016 42.8513C15.1894 42.8413 14.4501 42.7667 13.6846 42.6279C12.9188 42.4904 12.2065 42.1714 11.5472 41.6716C10.8882 41.1718 10.4204 40.5176 10.1438 39.71L9.82483 38.9759C9.6122 38.4872 9.27745 37.9443 8.82013 37.3492C8.36281 36.7536 7.90035 36.3498 7.43253 36.1372L7.20918 35.9773C7.06035 35.871 6.92225 35.7429 6.79454 35.5941C6.66694 35.4454 6.57141 35.2966 6.50761 35.1476C6.4437 34.9984 6.49666 34.876 6.66705 34.7801C6.83744 34.6841 7.14538 34.6375 7.5922 34.6375L8.22996 34.7328C8.65533 34.8181 9.18147 35.0727 9.80907 35.4983C10.4363 35.9235 10.952 36.4764 11.3561 37.1566C11.8455 38.0288 12.4351 38.6934 13.1266 39.1507C13.8176 39.608 14.5142 39.8363 15.2159 39.8363C15.9176 39.8363 16.5236 39.7831 17.0342 39.6773C17.5443 39.5709 18.0228 39.411 18.4696 39.1985C18.661 37.773 19.1822 36.6779 20.0326 35.9125C18.8205 35.7851 17.7308 35.5933 16.7628 35.3382C15.7955 35.0827 14.7958 34.6682 13.7645 34.0935C12.7327 33.5195 11.8767 32.8068 11.1963 31.9565C10.5159 31.1058 9.95745 29.9889 9.52181 28.6069C9.08594 27.2243 8.86795 25.6294 8.86795 23.8218C8.86795 21.2481 9.70818 19.0579 11.3883 17.2501C10.6013 15.3151 10.6756 13.1459 11.6114 10.7428C12.2282 10.5512 13.1428 10.695 14.3549 11.1733C15.5672 11.6519 16.4548 12.0618 17.0186 12.4017C17.5824 12.7415 18.0341 13.0294 18.3745 13.2629C20.3527 12.7102 22.3941 12.4338 24.4994 12.4338C26.6046 12.4338 28.6465 12.7102 30.6249 13.2629L31.8371 12.4977C32.666 11.9871 33.6449 11.5191 34.7714 11.0938C35.8985 10.6686 36.7604 10.5515 37.3562 10.7431C38.3128 13.1464 38.3981 15.3154 37.6108 17.2504C39.2908 19.0582 40.1314 21.249 40.1314 23.8222C40.1314 25.6298 39.9126 27.2297 39.4774 28.6228C39.0416 30.0163 38.4784 31.132 37.7875 31.9725C37.0957 32.8128 36.2343 33.52 35.203 34.0938C34.1715 34.6681 33.1715 35.0826 32.2041 35.3381C31.2363 35.5935 30.1465 35.7854 28.9345 35.913C30.04 36.8697 30.5928 38.3797 30.5928 40.4425V47.1729C30.5928 47.5553 30.7258 47.8745 30.9919 48.1296C31.2577 48.3842 31.6775 48.4592 32.2518 48.3525C37.1862 46.7153 41.211 43.7646 44.3265 39.5004C47.4416 35.2366 48.9997 30.436 48.9997 25.0975C48.9985 20.6538 47.9027 16.5551 45.7132 12.8015Z" fill="#2D3135" />
|
||||
</svg>
|
Before Width: | Height: | Size: 2.8 KiB |
|
@ -1,9 +0,0 @@
|
|||
<svg width="124" height="124" viewBox="0 0 124 124" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M62.5313 116.824L83.6186 56.1375H41.3817L62.5313 116.824Z" fill="#2D3135" />
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M62.5313 116.824L42.8433 56.1374L42.0856 55.4786H16.0395L15.251 56.1374V57.6001L62.5313 116.824Z" fill="#51575E" />
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M15.2517 56.1376L9.26889 74.5796C8.72319 76.2616 9.32087 78.1043 10.7496 79.1438L62.532 116.824L15.2517 56.1376Z" fill="#889097" />
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M15.2511 56.1376H42.8433L30.9852 19.5867C30.3753 17.7057 27.7186 17.7062 27.1087 19.5867L15.2511 56.1376Z" fill="#2D3135" />
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M62.5313 116.824L82.2193 56.1374L82.9145 55.4786H108.961L109.812 56.1374V57.6001L62.5313 116.824Z" fill="#51575E" />
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M109.812 56.1376L115.794 74.5796C116.34 76.2616 115.742 78.1043 114.314 79.1438L62.5313 116.824L109.812 56.1376Z" fill="#889097" />
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M109.812 56.1376H82.2192L94.0774 19.5867C94.6873 17.7057 97.344 17.7062 97.9539 19.5867L109.812 56.1376Z" fill="#2D3135" />
|
||||
</svg>
|
Before Width: | Height: | Size: 1.3 KiB |
|
@ -1,15 +0,0 @@
|
|||
<svg width="124" height="124" viewBox="0 0 124 124" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M62.5 14.2821C34.2756 14.2821 11.3955 37.3562 11.3955 65.8197C11.3955 75.6467 14.1238 84.83 18.8561 92.6444L32.2072 78.3485L99.782 78.7743L99.2927 101.584C108.154 92.3158 113.604 79.7104 113.604 65.8197C113.604 37.3562 90.7244 14.2821 62.5 14.2821Z" fill="#2D3135" />
|
||||
<path d="M39.3984 28.9712C36.9077 28.8173 34.9989 26.6469 35.1439 24.1325L35.4819 18.6264C35.6301 16.2176 37.6219 14.3301 40.0155 14.3301C40.1049 14.3301 40.195 14.3326 40.2851 14.3384L45.7417 14.6792C46.9491 14.7533 48.0587 15.299 48.8623 16.2163C49.6672 17.1337 50.0684 18.3139 49.9911 19.5379L49.6563 25.0228C49.6365 25.3398 49.5733 25.591 49.5343 25.7282L49.3401 26.6862L45.7494 29.3674L39.3984 28.9712Z" fill="#889097" />
|
||||
<path d="M40.0156 11.5648C36.1764 11.5648 32.983 14.5913 32.7454 18.4553L32.4074 23.9518L32.4068 23.9621L32.4062 23.9718C32.1736 28.004 35.2361 31.4847 39.2324 31.7314L44.5824 32.0651L46.5774 32.1895L48.184 30.9899L50.1273 29.5398L51.8156 28.279L52.2034 26.3676C52.28 26.0745 52.3631 25.6796 52.3931 25.1964L52.7278 19.7064C52.8505 17.7448 52.2059 15.854 50.9143 14.3813C49.6284 12.9138 47.848 12.0376 45.9041 11.9184L40.4628 11.5783H40.4551L40.4468 11.5776C40.3037 11.5693 40.1587 11.5648 40.0156 11.5648ZM44.0899 23.5608L44.2905 20.2914L41.0479 20.0891L40.8467 23.3591L44.0899 23.5608ZM40.0156 17.0954C40.0514 17.0954 40.0878 17.0967 40.1242 17.0986L45.5719 17.4394C46.0491 17.4684 46.4893 17.6842 46.8067 18.0469C47.1262 18.4109 47.2852 18.8779 47.2546 19.3618L46.9192 24.8531C46.9141 24.9381 46.8802 25.0128 46.8642 25.094L44.921 26.5448L39.5678 26.2111C38.5802 26.1499 37.8239 25.2899 37.8814 24.2933L38.2193 18.7974C38.2781 17.8382 39.067 17.0954 40.0156 17.0954" fill="#282828" />
|
||||
<path d="M62.5 40.9957C41.1626 40.9957 23.865 58.4398 23.865 79.9581C23.865 87.9599 26.258 95.3968 30.3597 101.584H96.1919C99.6568 95.2016 101.135 87.6449 101.135 79.9581C101.135 60.0426 83.8374 40.9957 62.5 40.9957Z" fill="#889097" />
|
||||
<path d="M62.4999 38.2301C39.6843 38.2301 21.1226 56.9492 21.1226 79.9582C21.1226 87.8692 23.3175 95.2739 27.1248 101.584H33.7345C29.2597 95.5464 26.6073 88.0586 26.6073 79.9582C26.6073 59.999 42.7085 43.7608 62.4999 43.7608C71.9006 43.7608 80.9046 47.7298 87.8529 54.9367C94.5514 61.8833 98.3932 71.0035 98.3932 79.9582C98.3932 86.0892 97.389 94.4209 93.024 101.584H99.2837C102.305 95.4517 103.877 88.1198 103.877 79.9582C103.877 69.5759 99.4702 59.0507 91.7854 51.0804C83.7952 42.7938 73.3948 38.2301 62.4999 38.2301Z" fill="#282827" />
|
||||
<path d="M54.6996 91.2686C54.6996 93.3346 53.0393 95.0089 50.9907 95.0089C48.942 95.0089 47.2818 93.3346 47.2818 91.2686C47.2818 89.2026 48.942 87.5282 50.9907 87.5282C53.0393 87.5282 54.6996 89.2026 54.6996 91.2686Z" fill="#282827" />
|
||||
<path d="M77.7181 91.2686C77.7181 93.3346 76.0579 95.0089 74.0092 95.0089C71.9606 95.0089 70.3003 93.3346 70.3003 91.2686C70.3003 89.2026 71.9606 87.5282 74.0092 87.5282C76.0579 87.5282 77.7181 89.2026 77.7181 91.2686Z" fill="#282827" />
|
||||
<path d="M20.3463 89.7847L40.9337 67.7652L35.9785 83.432C52.0515 71.6364 68.9249 73.5014 80.8061 77.0201C93.4167 80.754 102.246 69.522 93.6026 57.5402C89.4957 51.8459 83.8621 47.118 77.4632 44.2506C60.6799 36.7319 43.8058 41.1158 32.4229 52.7118C20.3648 64.9957 20.3463 78.6203 20.3463 89.7847Z" fill="#51575D" />
|
||||
<path d="M60.5654 43.2979C58.1756 43.2979 55.8025 43.5382 53.4702 44.022C46.3571 45.4972 39.7531 49.1751 34.3718 54.6574C25.433 63.7634 23.5754 73.5369 23.1895 82.7125L47.2105 57.0204L40.8512 77.1252C52.802 70.8241 66.4667 69.8919 81.5783 74.3667C86.3872 75.7904 90.7458 74.7113 92.9541 71.5508C95.2225 68.3033 94.6361 63.6739 91.3852 59.1669C87.5007 53.7813 82.1609 49.3819 76.3497 46.7779C71.1958 44.4691 65.8413 43.2979 60.5654 43.2979ZM17.6044 96.7488V89.7848C17.6044 78.2423 17.6044 63.8775 30.4738 50.7663C36.6191 44.5064 44.189 40.301 52.3651 38.6047C60.9723 36.819 70.0357 37.8974 78.5765 41.7234C85.254 44.7152 91.3782 49.7549 95.8205 55.913C100.447 62.3281 101.067 69.5408 97.4373 74.7358C93.8887 79.8155 87.0586 81.7526 80.033 79.6731C63.8303 74.8749 49.5505 76.892 37.592 85.6682L30.8207 90.6371L34.6567 78.5096L17.6044 96.7488Z" fill="#282827" />
|
||||
<path d="M72.3628 38.9205C69.922 45.3343 62.844 48.9155 56.286 47.0537C48.8976 44.9555 44.9964 38.9675 45.9329 31.3721C46.8649 23.8187 51.9261 17.1961 57.7604 9.03253C52.968 19.3201 57.0392 20.2838 58.327 20.2323C59.5995 20.1814 61.4571 20.4004 64.1012 21.2849C71.305 23.6963 75.1129 31.6936 72.3628 38.9205Z" fill="#51575D" />
|
||||
<path d="M53.4477 20.0568C50.9168 24.0587 49.1396 27.7784 48.6541 31.7133C47.8856 37.9448 51.0164 42.6844 57.0295 44.3915C62.2498 45.8739 67.8598 43.0355 69.8031 37.93C70.8565 35.1612 70.7466 32.1662 69.4939 29.4965C68.2399 26.8243 66.0182 24.8401 63.2374 23.9099C60.7972 23.093 59.2781 22.9635 58.4362 22.9957C56.5459 23.0705 54.8014 22.1827 53.7729 20.6186C53.6541 20.4382 53.5455 20.2513 53.4477 20.0568ZM59.8211 50.3113C58.3991 50.3113 56.9624 50.1187 55.543 49.7154C51.2847 48.5062 47.8818 46.1161 45.7028 42.8035C43.5303 39.5013 42.6692 35.4304 43.2115 31.0304C44.1697 23.2631 48.9167 16.6456 54.413 8.9833C54.7835 8.46664 55.1578 7.94482 55.536 7.41592L60.2421 10.2086C57.9194 15.1955 58.1858 17.0418 58.3078 17.4632C60.1763 17.4058 62.4159 17.8072 64.9648 18.6601C69.1815 20.0716 72.5506 23.0795 74.451 27.131C76.3508 31.1792 76.5182 35.7184 74.9231 39.9116C72.5033 46.2694 66.3337 50.3113 59.8211 50.3113Z" fill="#282827" />
|
||||
<path d="M65.4332 63.4152C64.473 63.4152 63.5736 62.9848 62.9667 62.235L61.1219 59.9563C60.0525 58.6357 60.2058 56.7037 61.4419 55.5686C61.0426 55.3714 60.6856 55.0873 60.3955 54.7291L59.9746 54.2093C58.8669 52.8416 59.0694 50.8207 60.4249 49.7042L60.69 49.4858L57.9879 46.1411C56.7863 44.6568 57.0067 42.4606 58.4798 41.2469C59.0975 40.7393 59.8717 40.4616 60.6626 40.4616C61.7013 40.4616 62.6754 40.9267 63.334 41.7385L74.86 56.0446C76.0597 57.527 75.8393 59.7231 74.3662 60.9368C73.9599 61.2718 73.4732 61.5128 72.9609 61.632C72.7066 61.6912 72.4447 61.7209 72.1834 61.7209C71.2853 61.7209 70.4363 61.3736 69.7968 60.7558L67.4454 62.6917C66.8788 63.1581 66.164 63.4152 65.4332 63.4152Z" fill="#889097" />
|
||||
<path d="M65.0699 40.3049C63.9832 38.9662 62.3766 38.199 60.6627 38.199C59.357 38.199 58.0794 38.6576 57.0656 39.49C54.6292 41.4974 54.2664 45.1211 56.2511 47.573L57.7523 49.4316C56.6498 51.3436 56.7692 53.8277 58.2365 55.6399L58.5189 55.9891C57.8552 57.7549 58.1165 59.8222 59.3838 61.387L61.2287 63.6662C62.2629 64.9444 63.7954 65.6775 65.4327 65.6775C66.6796 65.6775 67.8985 65.2401 68.8644 64.4445L69.9695 63.5348C70.6607 63.8279 71.4113 63.9838 72.1836 63.9838C72.6141 63.9838 73.0453 63.9342 73.465 63.8363C74.3108 63.6398 75.1131 63.243 75.7858 62.689C78.2165 60.6861 78.5793 57.0623 76.5952 54.6104L65.0807 40.3184L65.0699 40.3049ZM60.6627 42.724C61.0122 42.724 61.359 42.8773 61.5979 43.1723L73.1188 57.4714C73.5404 57.9926 73.4631 58.7592 72.947 59.1844C72.7981 59.3068 72.6301 59.3867 72.4557 59.4273C72.3663 59.4485 72.2749 59.4588 72.1836 59.4588C71.8335 59.4588 71.4867 59.3062 71.2477 59.0111L70.1004 57.5855L66.0268 60.9386C65.8524 61.0829 65.6422 61.1525 65.4327 61.1525C65.1606 61.1525 64.8903 61.0333 64.7045 60.804L62.8596 58.5254C62.5351 58.1247 62.5945 57.5346 62.9918 57.2073L67.0853 53.8374L65.4129 51.7578L63.3879 53.4264C63.2218 53.5623 63.0225 53.6287 62.8245 53.6287C62.5664 53.6287 62.3102 53.5159 62.1339 53.2988L61.7123 52.7776C61.3871 52.3763 61.4459 51.7849 61.8439 51.457L63.8453 49.8091L59.7269 44.7114C59.3053 44.1908 59.3826 43.4242 59.8987 42.999C60.1242 42.8141 60.3944 42.724 60.6627 42.724" fill="#282828" />
|
||||
</svg>
|
Before Width: | Height: | Size: 7.5 KiB |
|
@ -1,16 +0,0 @@
|
|||
<svg width="124" height="130" viewBox="0 0 124 130" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g filter="url(#filter0_d)">
|
||||
<path d="M64.2159 112.568C54.6932 112.568 46.5909 110.852 39.9091 107.42C33.2273 103.989 28.1364 99.0114 24.6364 92.4886C21.1591 85.9432 19.4091 78.0455 19.3864 68.7955C19.4091 59.75 21.1818 51.8977 24.7045 45.2386C28.25 38.5568 33.3523 33.3864 40.0114 29.7273C46.6932 26.0682 54.75 24.2386 64.1818 24.2386C70.8182 24.2386 76.7159 25.25 81.875 27.2727C87.0341 29.2727 91.3864 32.0795 94.9318 35.6932C98.5 39.3068 101.193 43.5227 103.011 48.3409C104.852 53.1591 105.773 58.3636 105.773 63.9545C105.795 69.1364 105.25 73.9091 104.136 78.2727C103.045 82.6364 101.125 86.1705 98.375 88.875C95.6477 91.5568 91.8409 92.9886 86.9545 93.1705C83.5227 93.3523 80.7273 92.7841 78.5682 91.4659C76.4318 90.125 75.1591 88.1818 74.75 85.6364H74.3409C73.5455 87.6591 71.75 89.3977 68.9545 90.8523C66.1818 92.3068 62.8636 92.9432 59 92.7614C55.2955 92.5795 51.9886 91.5114 49.0795 89.5568C46.1705 87.6023 43.875 84.8409 42.1932 81.2727C40.5341 77.6818 39.7045 73.3864 39.7045 68.3864C39.7045 63.5227 40.5909 59.4091 42.3636 56.0455C44.1591 52.6818 46.4773 50.0568 49.3182 48.1705C52.1818 46.2841 55.1932 45.1364 58.3523 44.7273C60.8295 44.3864 63.1364 44.4205 65.2727 44.8295C67.4318 45.2159 69.2273 45.8409 70.6591 46.7045C72.1136 47.5455 73.0227 48.4659 73.3864 49.4659H73.8636V45.6477H83.375V78.1023C83.3977 79.7841 83.8068 81.1591 84.6023 82.2273C85.4205 83.2727 86.5568 83.7955 88.0114 83.7955C90.7386 83.7955 92.6932 82.1591 93.875 78.8864C95.0568 75.5909 95.6364 70.4205 95.6136 63.375C95.6364 56.9205 94.2614 51.4886 91.4886 47.0795C88.7386 42.6705 84.9773 39.3409 80.2045 37.0909C75.4318 34.8409 70.0114 33.7159 63.9432 33.7159C56.5341 33.7159 50.3068 35.2045 45.2614 38.1818C40.2159 41.1364 36.4091 45.2386 33.8409 50.4886C31.2955 55.7386 30.0227 61.7955 30.0227 68.6591C30.0227 79.7045 33 88.1818 38.9545 94.0909C44.9091 100 53.5227 102.955 64.7955 102.955C67.3864 102.955 69.9205 102.75 72.3977 102.341C74.875 101.932 77.1023 101.455 79.0795 100.909C81.0568 100.364 82.5795 99.8864 83.6477 99.4773L86.5455 108.136C85.1364 108.864 83.2273 109.568 80.8182 110.25C78.4318 110.932 75.7955 111.489 72.9091 111.92C70.0455 112.352 67.1477 112.568 64.2159 112.568ZM61.8295 82.9773C66.1477 82.9773 69.1932 81.7159 70.9659 79.1932C72.7386 76.6705 73.6023 72.9205 73.5568 67.9432C73.5341 63.3068 72.6477 59.9091 70.8977 57.75C69.1477 55.5909 66.1477 54.5114 61.8977 54.5114C58.0795 54.5114 55.125 55.7841 53.0341 58.3295C50.9659 60.875 49.9205 64.0909 49.8977 67.9773C49.9205 70.5682 50.3068 73.0114 51.0568 75.3068C51.8068 77.5795 53.0455 79.4318 54.7727 80.8636C56.5227 82.2727 58.875 82.9773 61.8295 82.9773Z" fill="#2D3135" />
|
||||
</g>
|
||||
<defs>
|
||||
<filter id="filter0_d" x="13.3864" y="19.2386" width="98.387" height="100.33" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
|
||||
<feFlood flood-opacity="0" result="BackgroundImageFix" />
|
||||
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" />
|
||||
<feOffset dy="1" />
|
||||
<feGaussianBlur stdDeviation="3" />
|
||||
<feColorMatrix type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.02 0" />
|
||||
<feBlend mode="normal" in2="BackgroundImageFix" result="effect1_dropShadow" />
|
||||
<feBlend mode="normal" in="SourceGraphic" in2="effect1_dropShadow" result="shape" />
|
||||
</filter>
|
||||
</defs>
|
||||
</svg>
|
Before Width: | Height: | Size: 3.3 KiB |
|
@ -1,3 +0,0 @@
|
|||
<svg width="124" height="124" viewBox="0 0 124 124" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M61.9844 15.0016C50.1162 15.0568 38.3215 16.5656 32.1941 19.3809C32.1941 19.3809 19.001 25.3794 19.001 45.8407C18.9826 70.2029 18.9826 100.803 40.8607 106.709C49.2329 108.954 56.4459 109.451 62.2421 109.12C72.7487 108.531 77.956 105.311 77.956 105.311L77.6064 97.5459C77.6064 97.5459 70.7983 99.938 62.3525 99.6804C53.9987 99.386 45.1848 98.7604 43.8048 88.3273C43.676 87.3521 43.6208 86.3769 43.6208 85.4016C61.3404 89.7993 76.4472 87.3153 80.6057 86.8185C92.2164 85.4016 102.318 78.1335 103.606 71.4725C105.63 60.9843 105.465 45.8959 105.465 45.8959C105.465 25.4346 92.29 19.4361 92.29 19.4361C85.813 16.3632 73.8527 14.9464 61.9844 15.0016ZM49.2697 30.6051C53.3362 30.7155 57.3659 32.5372 59.8316 36.3829L62.2237 40.4494L64.6157 36.3829C69.5838 28.6547 80.7345 29.1331 86.0338 35.1132C90.9283 40.799 89.8427 44.4791 89.8427 69.9085H80.2193V47.7911C80.2193 37.4317 67.0078 37.0269 67.0078 49.2264V62.0515H57.4579V49.2264C57.4579 37.0269 44.2648 37.4317 44.2648 47.7911V69.9085H34.623C34.623 44.4607 33.5558 40.7622 38.4319 35.1132C41.0999 32.0956 45.2032 30.4947 49.2697 30.6051Z" fill="#2D3135" />
|
||||
</svg>
|
Before Width: | Height: | Size: 1.2 KiB |
Before Width: | Height: | Size: 5.2 KiB |
|
@ -1,3 +0,0 @@
|
|||
<svg width="124" height="124" viewBox="0 0 124 124" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M44.2572 105.672C81.3691 105.672 101.663 75.149 101.663 48.6812C101.663 47.8143 101.645 46.9512 101.606 46.0921C105.545 43.2647 108.97 39.7363 111.67 35.7197C108.055 37.315 104.165 38.389 100.083 38.8732C104.249 36.3934 107.447 32.4706 108.956 27.794C105.058 30.0884 100.74 31.7559 96.1439 32.6561C92.462 28.7625 87.2205 26.3276 81.4164 26.3276C70.2743 26.3276 61.2388 35.298 61.2388 46.3557C61.2388 47.9275 61.4158 49.4564 61.7619 50.9228C44.9928 50.0852 30.1237 42.1146 20.1736 29.9947C18.4408 32.9548 17.4417 36.3934 17.4417 40.0624C17.4417 47.0117 21.0036 53.1469 26.4202 56.7358C23.11 56.6343 20.0005 55.7322 17.2823 54.2306C17.2794 54.3146 17.2794 54.3966 17.2794 54.4864C17.2794 64.187 24.234 72.2865 33.4663 74.1219C31.7709 74.5808 29.987 74.8268 28.146 74.8268C26.8479 74.8268 25.5833 74.7 24.354 74.4656C26.9227 82.4245 34.371 88.216 43.202 88.378C36.2965 93.7516 27.5973 96.952 18.1428 96.952C16.5162 96.952 14.9093 96.8601 13.33 96.6747C22.2593 102.357 32.8625 105.672 44.2582 105.672" fill="#2D3135" />
|
||||
</svg>
|
Before Width: | Height: | Size: 1.1 KiB |
|
@ -1,22 +0,0 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg width="84px" height="84px" viewBox="0 0 84 84" version="1.1" xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<!-- Generator: Sketch 64 (93537) - https://sketch.com -->
|
||||
<title>favicon</title>
|
||||
<desc>Created with Sketch.</desc>
|
||||
<g id="Page-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
|
||||
<g id="ElitizonFavicon" transform="translate(-187.000000, -109.000000)">
|
||||
<g id="favicon" transform="translate(189.000000, 111.000000)">
|
||||
<rect id="background" stroke="#FA3366" stroke-width="3" fill="#F6F6F6" x="0" y="0" width="80"
|
||||
height="80" rx="40"></rect>
|
||||
<g id="icon" transform="translate(17.000000, 19.000000)">
|
||||
<path
|
||||
d="M21.3702857,41.5489524 C27.2845714,41.5489524 31.3020181,42.0024879 36.3033202,37.9894671 L32.3828171,31.0938095 C30.1748171,33.3806667 25.0765714,32.7958095 21.528,32.7958095 C14.7462857,32.7958095 10.5668571,29.3260952 9.936,24.5158095 L40.296,24.5158095 C41.7942857,8.50780952 33.672,0.464380952 20.6605714,0.464380952 C8.04342857,0.464380952 -6.22570778e-14,8.98095238 -6.22570778e-14,20.8095238 C-6.22570778e-14,33.2689524 7.96457143,41.5489524 21.3702857,41.5489524 Z M31.0697143,16.5512381 L10.0937143,16.5512381 C11.5131429,11.504381 15.7714286,8.98095238 20.976,8.98095238 C26.496,8.98095238 30.4388571,11.504381 31.0697143,16.5512381 Z"
|
||||
id="elitizon" fill="#2A3045" fill-rule="nonzero"></path>
|
||||
<rect id="Rectangle" fill="#FA3366" x="35.4024762" y="27.3809524" width="9.61619048"
|
||||
height="9.61619048" rx="4.80809524"></rect>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
Before Width: | Height: | Size: 1.8 KiB |
Before Width: | Height: | Size: 15 KiB |
69
public/index.html
Normal file
|
@ -0,0 +1,69 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<link rel="stylesheet" href="style/index.css">
|
||||
<title>nwex.de</title>
|
||||
</head>
|
||||
<body>
|
||||
<h1>networkException</h1>
|
||||
<h2>try to catch(this: Exception);</h2>
|
||||
|
||||
<main>
|
||||
I'm a TypeScript developer working on backend code, libraries and anything that scales.
|
||||
</main>
|
||||
<section>
|
||||
<h3>Projects I maintain in my free time</h3>
|
||||
|
||||
<ul>
|
||||
<li><a href="https://github.com/t2linux/wiki">The t2linux.org wiki - A project to run Linux on Apple T2 devices</a></li>
|
||||
<li><a href="https://github.com/Eloston/ungoogled-chromium">Chromium sans integration with Google</a></li>
|
||||
<li><a href="https://github.com/ungoogled-software/ungoogled-chromium-archlinux">Arch Linux packaging for ungoogled-chromium</a></li>
|
||||
</ul>
|
||||
</section>
|
||||
<section>
|
||||
<h3>Links</h3>
|
||||
|
||||
<ul>
|
||||
<li><a href="https://github.com/networkException">github.com</a></li>
|
||||
<li><a href="https://gitlab.upi.li/networkException">gitlab.upi.li</a></li>
|
||||
<li><a href="https://twitter.com/netwrkException">twitter.com</a></li>
|
||||
<li><a href="https://matrix.to/#/@networkexception:chat.upi.li">matrix.org</a></li>
|
||||
<li><a href="https://chaos.social/@networkexception">mastodon.social</a></li>
|
||||
<li><a href="mailto:hello@nwex.de">email</a></li>
|
||||
|
||||
<li><a href="/gpg.key">My GPG key</a></li>
|
||||
</ul>
|
||||
</section>
|
||||
<section>
|
||||
<h3>This website</h3>
|
||||
|
||||
<p>
|
||||
Although I also know my way around frontend development and design as well, I'm far less skilled at it.
|
||||
As such this website is trying to impress in a different way:
|
||||
</p>
|
||||
<p>
|
||||
It implements parts of the <a href="https://html.spec.whatwg.org/multipage/parsing.html#tokenization">HTML parser spec</a>
|
||||
to tokenize and highlight it's own source code.
|
||||
</p>
|
||||
</section>
|
||||
|
||||
<!-- Inner workings on the page -->
|
||||
|
||||
<script type="module">
|
||||
import { tokenize, normalizeNewlines, highlight } from './script/html.js';
|
||||
import { render } from './script/view.js';
|
||||
import { Inspector } from './script/html/inspector.js';
|
||||
|
||||
const response = await fetch(window.location.href);
|
||||
const text = await response.text();
|
||||
|
||||
const tokens = tokenize(normalizeNewlines(text));
|
||||
const spans = highlight(tokens);
|
||||
|
||||
const inspector = new Inspector();
|
||||
|
||||
render(text, spans, inspector);
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
33
public/style/index.css
Normal file
|
@ -0,0 +1,33 @@
|
|||
body {
|
||||
background: #292D3E;
|
||||
|
||||
font-family: monospace;
|
||||
}
|
||||
|
||||
pre {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
body > pre > span {
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
a {
|
||||
color: white;
|
||||
}
|
||||
|
||||
body {
|
||||
color: white;
|
||||
font-size: 1vmax;
|
||||
}
|
||||
|
||||
#inspector {
|
||||
position: fixed;
|
||||
pointer-events: none;
|
||||
|
||||
background: inherit;
|
||||
color: white;
|
||||
border: 1px #424864 solid;
|
||||
|
||||
padding: .5vw;
|
||||
}
|
|
@ -1,4 +0,0 @@
|
|||
<svg width="283" height="64" viewBox="0 0 283 64" fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M141.04 16c-11.04 0-19 7.2-19 18s8.96 18 20 18c6.67 0 12.55-2.64 16.19-7.09l-7.65-4.42c-2.02 2.21-5.09 3.5-8.54 3.5-4.79 0-8.86-2.5-10.37-6.5h28.02c.22-1.12.35-2.28.35-3.5 0-10.79-7.96-17.99-19-17.99zm-9.46 14.5c1.25-3.99 4.67-6.5 9.45-6.5 4.79 0 8.21 2.51 9.45 6.5h-18.9zM248.72 16c-11.04 0-19 7.2-19 18s8.96 18 20 18c6.67 0 12.55-2.64 16.19-7.09l-7.65-4.42c-2.02 2.21-5.09 3.5-8.54 3.5-4.79 0-8.86-2.5-10.37-6.5h28.02c.22-1.12.35-2.28.35-3.5 0-10.79-7.96-17.99-19-17.99zm-9.45 14.5c1.25-3.99 4.67-6.5 9.45-6.5 4.79 0 8.21 2.51 9.45 6.5h-18.9zM200.24 34c0 6 3.92 10 10 10 4.12 0 7.21-1.87 8.8-4.92l7.68 4.43c-3.18 5.3-9.14 8.49-16.48 8.49-11.05 0-19-7.2-19-18s7.96-18 19-18c7.34 0 13.29 3.19 16.48 8.49l-7.68 4.43c-1.59-3.05-4.68-4.92-8.8-4.92-6.07 0-10 4-10 10zm82.48-29v46h-9V5h9zM36.95 0L73.9 64H0L36.95 0zm92.38 5l-27.71 48L73.91 5H84.3l17.32 30 17.32-30h10.39zm58.91 12v9.69c-1-.29-2.06-.49-3.2-.49-5.81 0-10 4-10 10V51h-9V17h9v9.2c0-5.08 5.91-9.2 13.2-9.2z" fill="#000"/>
|
||||
</svg>
|
Before Width: | Height: | Size: 1.1 KiB |
|
@ -1,27 +0,0 @@
|
|||
import React from "react"
|
||||
import { Story, Meta } from "@storybook/react/types-6-0"
|
||||
import { SocialBadge, SocialBadgeProps } from "."
|
||||
|
||||
export default {
|
||||
title: "Component/SocialBadge",
|
||||
component: SocialBadge,
|
||||
argTypes: {
|
||||
title: { description: "The text displayed to the right of the icon" },
|
||||
link: { description: "The link that is opened when the Badge is clicked" },
|
||||
imagePath: { description: "The path to the circular image on the left" },
|
||||
}
|
||||
} as Meta
|
||||
|
||||
const Template: Story<SocialBadgeProps> = (args) => <SocialBadge {...args} />
|
||||
|
||||
// Default scenario
|
||||
export const Default = Template.bind({})
|
||||
Default.args = {}
|
||||
|
||||
// GitHub Variant
|
||||
export const Github = Template.bind({})
|
||||
Github.args = {
|
||||
title: "github.com",
|
||||
link: "https://github.com/networkException",
|
||||
imagePath: "/assets/icons/github.svg"
|
||||
}
|
|
@ -1,22 +0,0 @@
|
|||
import React from "react";
|
||||
|
||||
export interface SocialBadgeProps {
|
||||
title: string
|
||||
link: string,
|
||||
imagePath: string
|
||||
}
|
||||
|
||||
export const SocialBadge: React.FC<SocialBadgeProps> = ({title, link, imagePath}) => {
|
||||
link = link ?? "/404";
|
||||
title = title ?? "Unbekannt";
|
||||
|
||||
console.log(imagePath);
|
||||
|
||||
return (
|
||||
<a className="block bg-white rounded-full shadow-lg p-2 flex w-max items-center cursor-pointer transition-transform transform hover:scale-105 focus:outline-none focus:ring h-16"
|
||||
href={link}>
|
||||
{imagePath?(<img className={`flex rounded-full h-12 w-12 object-cover`} src={imagePath}/>):null}
|
||||
<div className="mx-4 font-semibold text-xl">{title}</div>
|
||||
</a>
|
||||
)
|
||||
}
|
34
src/html.ts
Normal file
|
@ -0,0 +1,34 @@
|
|||
import { Highlighter } from './html/highlighter.js';
|
||||
import { Span } from './html/highlighter/span.js';
|
||||
import { Tokenizer } from './html/tokenizer.js';
|
||||
import { Token, Type } from './html/tokenizer/token.js';
|
||||
|
||||
export function normalizeNewlines(input: string): string {
|
||||
return input.replaceAll('\u000D\u000A', '\u000A').replaceAll('\u000D', '\u000A');
|
||||
}
|
||||
|
||||
export function tokenize(input: string): Array<Token> {
|
||||
console.time('html tokenizer');
|
||||
|
||||
const tokenizer = new Tokenizer(input);
|
||||
|
||||
while (tokenizer.tokens[tokenizer.tokens.length - 1]?.type !== Type.EndOfFile)
|
||||
tokenizer.spin();
|
||||
|
||||
console.timeEnd('html tokenizer');
|
||||
|
||||
return tokenizer.tokens;
|
||||
}
|
||||
|
||||
export function highlight(tokens: Array<Token>): Array<Span> {
|
||||
const highlighter = new Highlighter(tokens);
|
||||
|
||||
console.time('html highlighter');
|
||||
|
||||
while (!highlighter.finished)
|
||||
highlighter.spin();
|
||||
|
||||
console.timeEnd('html highlighter');
|
||||
|
||||
return highlighter.spans;
|
||||
}
|
20
src/html/errors.ts
Normal file
|
@ -0,0 +1,20 @@
|
|||
// FIXME: Convert to const enum
|
||||
export type ParseError = 'unexpected-null-character' |
|
||||
'unexpected-question-mark-instead-of-tag-name' |
|
||||
'eof-before-tag-name' |
|
||||
'invalid-first-character-of-tag-name' |
|
||||
'missing-end-tag-name' |
|
||||
'cdata-in-html-content' |
|
||||
'incorrectly-opened-comment' |
|
||||
'eof-in-doctype' |
|
||||
'missing-whitespace-before-doctype-name' |
|
||||
'eof-in-tag' |
|
||||
'unexpected-equals-sign-before-attribute-name' |
|
||||
'unexpected-character-in-attribute-name' |
|
||||
'missing-attribute-value' |
|
||||
'unexpected-character-in-unquoted-attribute-value' |
|
||||
'missing-whitespace-between-attributes' |
|
||||
'abrupt-closing-of-empty-comment' |
|
||||
'eof-in-comment' |
|
||||
'missing-semicolon-after-character-reference' |
|
||||
'unknown-named-character-reference';
|
70
src/html/highlighter.ts
Normal file
|
@ -0,0 +1,70 @@
|
|||
import { Color } from './highlighter/properties/color.js';
|
||||
import { Cursor } from './highlighter/properties/cursor.js';
|
||||
import { Font } from './highlighter/properties/font.js';
|
||||
import { Link } from './highlighter/properties/link.js';
|
||||
import { Span } from './highlighter/span.js';
|
||||
import { Token } from './tokenizer/token.js';
|
||||
import { CommentToken } from './tokenizer/tokens/comment.js';
|
||||
import { DOCTYPEToken } from './tokenizer/tokens/doctype.js';
|
||||
import { EndTagToken } from './tokenizer/tokens/endTag.js';
|
||||
import { StartTagToken } from './tokenizer/tokens/startTag.js';
|
||||
|
||||
export class Highlighter {
|
||||
public spans: Array<Span> = new Array<Span>();
|
||||
|
||||
public finished: boolean = false;
|
||||
|
||||
public constructor(private tokens: Array<Token>) {
|
||||
}
|
||||
|
||||
public spin(): void {
|
||||
for (const token of this.tokens) {
|
||||
if (token instanceof CommentToken) {
|
||||
this.spans.push(Span.createFromRange(token, token.range, Color.Comment));
|
||||
}
|
||||
|
||||
if (token instanceof DOCTYPEToken) {
|
||||
this.spans.push(Span.createFromRange(token, { start: token.range.start.copy().decrement(8), end: token.range.start }, Color.Tag));
|
||||
this.spans.push(Span.createFromRange(token, { start: token.range.start, end: token.range.end }, Color.Attribute, Font.Italic));
|
||||
this.spans.push(Span.createFromRange(token, { start: token.range.start.copy().decrement(10), end: token.range.start.copy().decrement(9) }, Color.Punctuator));
|
||||
this.spans.push(Span.createAt(token, token.range.end, Color.Punctuator));
|
||||
}
|
||||
|
||||
if (token instanceof StartTagToken || token instanceof EndTagToken) {
|
||||
this.spans.push(Span.createFromRange(token, token.range, Color.Tag));
|
||||
|
||||
for (const attribute of token.attributes.list) {
|
||||
this.spans.push(Span.createFromRange(attribute, attribute.nameRange, Color.Attribute, Font.Italic));
|
||||
|
||||
if (attribute.valueRange !== undefined) {
|
||||
if (attribute.name === 'href') {
|
||||
this.spans.push(Span.createAnchorFromRange(attribute, attribute.valueRange, Color.String, Font.Underline, Cursor.Pointer, Link.of(attribute.value)));
|
||||
} else {
|
||||
this.spans.push(Span.createFromRange(attribute, attribute.valueRange, Color.String));
|
||||
}
|
||||
|
||||
if (attribute.quoted) {
|
||||
this.spans.push(Span.createAt(attribute, attribute.valueRange.start, Color.Punctuator));
|
||||
this.spans.push(Span.createAt(attribute, attribute.valueRange.end, Color.Punctuator));
|
||||
}
|
||||
}
|
||||
|
||||
if (attribute.equalsPosition !== undefined)
|
||||
this.spans.push(Span.createAt(attribute, attribute.equalsPosition, Color.Punctuator));
|
||||
}
|
||||
|
||||
if (token instanceof StartTagToken) {
|
||||
this.spans.push(Span.createAt(token, token.range.start.copy().decrement(1), Color.Punctuator));
|
||||
this.spans.push(Span.createAt(token, token.range.end, Color.Punctuator));
|
||||
}
|
||||
|
||||
if (token instanceof EndTagToken) {
|
||||
this.spans.push(Span.createFromRange(token, { start: token.range.start.copy().decrement(2), end: token.range.start.copy().decrement(1) }, Color.Punctuator));
|
||||
this.spans.push(Span.createAt(token, token.range.end, Color.Punctuator));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
this.finished = true;
|
||||
}
|
||||
}
|
3
src/html/highlighter/inspectable.ts
Normal file
|
@ -0,0 +1,3 @@
|
|||
export abstract class Inspectable {
|
||||
public abstract inspect(indent: number): string;
|
||||
}
|
36
src/html/highlighter/properties/color.ts
Normal file
|
@ -0,0 +1,36 @@
|
|||
import { Property } from '../property.js';
|
||||
|
||||
export class Color extends Property {
|
||||
public static Plain = new Color('#a6accd');
|
||||
public static Punctuator = new Color('#89ddff');
|
||||
public static Tag = new Color('#f07178');
|
||||
public static Attribute = new Color('#c792ea');
|
||||
public static String = new Color('#c3e88d');
|
||||
public static Comment = new Color('#676e95');
|
||||
|
||||
#color: string;
|
||||
|
||||
private constructor(color: string) {
|
||||
super();
|
||||
|
||||
this.#color = color;
|
||||
}
|
||||
|
||||
public get color(): string {
|
||||
return this.#color;
|
||||
}
|
||||
|
||||
public override equals(other: Property): boolean {
|
||||
if (!(other instanceof Color)) return false;
|
||||
|
||||
return other.#color === this.#color;
|
||||
}
|
||||
|
||||
public override apply(element: HTMLElement): void {
|
||||
element.style.color = this.#color;
|
||||
}
|
||||
|
||||
public override inspect(indent: number): string {
|
||||
return `Color { ${this.#color} }`;
|
||||
}
|
||||
}
|
28
src/html/highlighter/properties/cursor.ts
Normal file
|
@ -0,0 +1,28 @@
|
|||
import { Property } from '../property.js';
|
||||
|
||||
export class Cursor extends Property {
|
||||
public static Default = new Cursor('default');
|
||||
public static Pointer = new Cursor('pointer');
|
||||
|
||||
#value: string;
|
||||
|
||||
private constructor(value: string) {
|
||||
super();
|
||||
|
||||
this.#value = value;
|
||||
}
|
||||
|
||||
public override equals(other: Property): boolean {
|
||||
if (!(other instanceof Cursor)) return false;
|
||||
|
||||
return other.#value === this.#value;
|
||||
}
|
||||
|
||||
public override apply(element: HTMLElement): void {
|
||||
element.style.cursor = this.#value;
|
||||
}
|
||||
|
||||
public override inspect(indent: number): string {
|
||||
return `Cursor { ${this.#value} }`;
|
||||
}
|
||||
}
|
30
src/html/highlighter/properties/font.ts
Normal file
|
@ -0,0 +1,30 @@
|
|||
import { Property } from '../property.js';
|
||||
|
||||
export class Font extends Property {
|
||||
public static Italic = new Font(style => style.fontStyle = 'italic', 'italic');
|
||||
public static Underline = new Font(style => style.textDecoration = ' underline', 'underline');
|
||||
|
||||
#impelementation: (style: CSSStyleDeclaration) => void;
|
||||
#value: string;
|
||||
|
||||
private constructor(impelementation: (style: CSSStyleDeclaration) => void, value: string) {
|
||||
super();
|
||||
|
||||
this.#impelementation = impelementation;
|
||||
this.#value = value;
|
||||
}
|
||||
|
||||
public override equals(other: Property): boolean {
|
||||
if (!(other instanceof Font)) return false;
|
||||
|
||||
return other.#impelementation === this.#impelementation;
|
||||
}
|
||||
|
||||
public override apply(element: HTMLElement): void {
|
||||
this.#impelementation(element.style);
|
||||
}
|
||||
|
||||
public override inspect(indent: number): string {
|
||||
return `Font { ${this.#value} }`;
|
||||
}
|
||||
}
|
29
src/html/highlighter/properties/link.ts
Normal file
|
@ -0,0 +1,29 @@
|
|||
import { Property } from '../property.js';
|
||||
|
||||
export class Link extends Property {
|
||||
#href: string;
|
||||
|
||||
private constructor(href: string) {
|
||||
super();
|
||||
|
||||
this.#href = href;
|
||||
}
|
||||
|
||||
public override equals(other: Property): boolean {
|
||||
if (!(other instanceof Link)) return false;
|
||||
|
||||
return other.#href === this.#href;
|
||||
}
|
||||
|
||||
public override apply(element: HTMLAnchorElement): void {
|
||||
element.href = this.#href;
|
||||
}
|
||||
|
||||
public static of(href: string): Link {
|
||||
return new Link(href);
|
||||
}
|
||||
|
||||
public override inspect(indent: number): string {
|
||||
return `Link { href: '${this.#href}' }`;
|
||||
}
|
||||
}
|
6
src/html/highlighter/property.ts
Normal file
|
@ -0,0 +1,6 @@
|
|||
import { Inspectable } from './inspectable.js';
|
||||
|
||||
export abstract class Property extends Inspectable {
|
||||
public abstract equals(other: Property): boolean;
|
||||
public abstract apply(element: HTMLElement): void;
|
||||
}
|
72
src/html/highlighter/span.ts
Normal file
|
@ -0,0 +1,72 @@
|
|||
import { Attribute } from '../tokenizer/attribute.js';
|
||||
import { Position } from '../tokenizer/position.js';
|
||||
import { Range } from '../tokenizer/range.js';
|
||||
import { Token } from '../tokenizer/token.js';
|
||||
import { Inspectable } from './inspectable.js';
|
||||
import { Property } from './property.js';
|
||||
|
||||
export type Source = Attribute | Token | null;
|
||||
|
||||
export class Span extends Inspectable {
|
||||
#source: Source;
|
||||
#from: Position;
|
||||
#to: Position;
|
||||
#properties: Array<Property>;
|
||||
#tagName: keyof HTMLElementTagNameMap;
|
||||
|
||||
private constructor(source: Source, from: Position, to: Position, tagName: keyof HTMLElementTagNameMap, properties: Array<Property>) {
|
||||
super();
|
||||
|
||||
this.#source = source;
|
||||
this.#from = from;
|
||||
this.#to = to;
|
||||
this.#tagName = tagName;
|
||||
this.#properties = properties;
|
||||
}
|
||||
|
||||
public get source(): Source {
|
||||
return this.#source;
|
||||
}
|
||||
|
||||
public contains(index: number): boolean {
|
||||
return this.#from.index <= index && this.#to.index >= index;
|
||||
}
|
||||
|
||||
public get properties(): Array<Property> {
|
||||
return this.#properties;
|
||||
}
|
||||
|
||||
public get tagName(): keyof HTMLElementTagNameMap {
|
||||
return this.#tagName;
|
||||
}
|
||||
|
||||
public static createFromRange(source: Source, range: Range, ...properties: Array<Property>): Span {
|
||||
return new Span(source, range.start, range.end, 'span', properties);
|
||||
}
|
||||
|
||||
public static createAnchorFromRange(source: Source, range: Range, ...properties: Array<Property>): Span {
|
||||
return new Span(source, range.start, range.end, 'a', properties);
|
||||
}
|
||||
|
||||
public static createAt(source: Source, position: Position, ...properties: Array<Property>): Span {
|
||||
return new Span(source, position, position, 'span', properties);
|
||||
}
|
||||
|
||||
public static createAnchorAt(source: Source, position: Position, ...properties: Array<Property>): Span {
|
||||
return new Span(source, position, position, 'a', properties);
|
||||
}
|
||||
|
||||
public override inspect(indent: number): string {
|
||||
let string = 'Span {\n';
|
||||
|
||||
string += ` from: ${this.#from.inspect(0)}\n`;
|
||||
string += ` to: ${this.#to.inspect(0)}\n`;
|
||||
string += ` properties: [ ${this.#properties.map(property => property.inspect(0)).join(', ')} ]\n`;
|
||||
|
||||
if (this.#source !== null) string += ` source: ${this.#source.inspect(0)}\n`;
|
||||
|
||||
string += '}';
|
||||
|
||||
return string;
|
||||
}
|
||||
}
|
48
src/html/inspector.ts
Normal file
|
@ -0,0 +1,48 @@
|
|||
import { Color } from './highlighter/properties/color.js';
|
||||
import { Span } from './highlighter/span.js';
|
||||
|
||||
export class Inspector {
|
||||
#element: HTMLDivElement;
|
||||
|
||||
public constructor() {
|
||||
this.#element = document.createElement('div');
|
||||
this.#element.id = 'inspector';
|
||||
|
||||
document.body.appendChild(this.#element);
|
||||
document.addEventListener('mousemove', event => this.#element.style.transform = `translate(${event.clientX + 10}px, ${event.clientY + 10}px)`);
|
||||
}
|
||||
|
||||
public instrument(element: HTMLElement, spans: Array<Span>): void {
|
||||
if (spans.length === 0)
|
||||
return;
|
||||
|
||||
const container = document.createElement('pre');
|
||||
|
||||
container.textContent += spans[spans.length - 1].inspect(0);
|
||||
|
||||
element.addEventListener('mouseenter', () => {
|
||||
element.style.background = Color.Comment.color;
|
||||
this.show(container);
|
||||
});
|
||||
|
||||
element.addEventListener('mouseleave', () => {
|
||||
element.style.background = 'none';
|
||||
this.hide();
|
||||
});
|
||||
}
|
||||
|
||||
public show(element: HTMLElement): void {
|
||||
this.#element.style.display = 'block';
|
||||
|
||||
if (this.#element.children[0] !== element)
|
||||
this.#element.replaceChildren(element);
|
||||
}
|
||||
|
||||
public hide(): void {
|
||||
this.#element.style.display = 'none';
|
||||
}
|
||||
|
||||
public get element(): HTMLDivElement {
|
||||
return this.#element;
|
||||
}
|
||||
}
|
698
src/html/tokenizer.ts
Normal file
|
@ -0,0 +1,698 @@
|
|||
import { TODO, VERIFY, VERIFY_NOT_REACHED } from '../util/assertions.js';
|
||||
import { Constructor } from '../util/guards.js';
|
||||
import { ParseError } from './errors.js';
|
||||
import { Attribute } from './tokenizer/attribute.js';
|
||||
import { entities } from './tokenizer/entities.js';
|
||||
import { Position } from './tokenizer/position.js';
|
||||
import { State } from './tokenizer/state.js';
|
||||
import { Token } from './tokenizer/token.js';
|
||||
import { CharacterToken } from './tokenizer/tokens/character.js';
|
||||
import { CommentToken } from './tokenizer/tokens/comment.js';
|
||||
import { DOCTYPEToken } from './tokenizer/tokens/doctype.js';
|
||||
import { EndOfFileToken } from './tokenizer/tokens/endOfFile.js';
|
||||
import { EndTagToken } from './tokenizer/tokens/endTag.js';
|
||||
import { StartTagToken } from './tokenizer/tokens/startTag.js';
|
||||
|
||||
export class Tokenizer {
|
||||
private state: State = State.Data;
|
||||
private returnState!: State;
|
||||
|
||||
private temporaryBuffer!: string;
|
||||
|
||||
private currentToken!: Token;
|
||||
private currentInputCharacter!: string;
|
||||
|
||||
private currentPosition: Position = Position.createStarting();
|
||||
|
||||
public tokens: Array<Token> = new Array<Token>();
|
||||
private pointer: number = 0;
|
||||
|
||||
public constructor(private input: string) {
|
||||
}
|
||||
|
||||
public spin(): void {
|
||||
switch (this.state) {
|
||||
case State.Data: {
|
||||
switch (this.consumeNext()) {
|
||||
case '\u0026':
|
||||
this.returnState = State.Data;
|
||||
this.state = State.CharacterReference;
|
||||
break;
|
||||
case '\u003C': this.state = State.TagOpen; break;
|
||||
case '\u0000':
|
||||
this.parseError('unexpected-null-character');
|
||||
this.emit(CharacterToken.createWith(this.currentInputCharacter).at(this.currentPosition));
|
||||
break;
|
||||
case undefined: this.emit(EndOfFileToken.create()); break;
|
||||
default: this.emit(CharacterToken.createWith(this.currentInputCharacter).at(this.currentPosition));
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
case State.RCDATA: {
|
||||
switch (this.consumeNext()) {
|
||||
case '\u003C': this.state = State.RAWTEXTLessThan; break;
|
||||
case '\u0000': this.parseError('unexpected-null-character'); this.emit(CharacterToken.createReplacementCharacter().at(this.currentPosition)); break;
|
||||
case undefined: this.emit(EndOfFileToken.create()); break;
|
||||
default: this.emit(CharacterToken.createWith(this.currentInputCharacter).at(this.currentPosition));
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
case State.TagOpen: {
|
||||
switch (this.consumeNext()) {
|
||||
case '\u0021': this.state = State.MarkupDeclarationOpen; break;
|
||||
case '\u002F': this.state = State.EndTagOpen; break;
|
||||
case '\u003F':
|
||||
this.parseError('unexpected-question-mark-instead-of-tag-name');
|
||||
this.create(CommentToken.createEmpty().startingAt(this.currentPosition));
|
||||
this.reconsumeIn(State.BogusComment);
|
||||
break;
|
||||
case undefined:
|
||||
this.parseError('eof-before-tag-name');
|
||||
this.emit(CharacterToken.createWith('\u003C').at(this.currentPosition));
|
||||
this.emit(EndOfFileToken.create());
|
||||
break;
|
||||
default: {
|
||||
if (this.asciiAlpha(this.currentInputCharacter)) {
|
||||
this.create(StartTagToken.createEmpty().startingAt(this.currentPosition));
|
||||
this.reconsumeIn(State.TagName);
|
||||
break;
|
||||
}
|
||||
|
||||
this.parseError('invalid-first-character-of-tag-name');
|
||||
this.emit(CharacterToken.createWith('\u003C').at(this.currentPosition));
|
||||
this.reconsumeIn(State.Data);
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
case State.EndTagOpen: {
|
||||
switch (this.consumeNext()) {
|
||||
case '\u003E': this.parseError('missing-end-tag-name'); this.state = State.Data; break;
|
||||
case undefined:
|
||||
this.parseError('eof-before-tag-name');
|
||||
this.emit(CharacterToken.createWith('\u003C').at(this.currentPosition));
|
||||
this.emit(CharacterToken.createWith('\u002F').at(this.currentPosition));
|
||||
this.emit(EndOfFileToken.create());
|
||||
break;
|
||||
default: {
|
||||
if (this.asciiAlpha(this.currentInputCharacter)) {
|
||||
this.create(EndTagToken.createEmpty().startingAt(this.currentPosition));
|
||||
this.reconsumeIn(State.TagName);
|
||||
break;
|
||||
}
|
||||
|
||||
this.parseError('invalid-first-character-of-tag-name');
|
||||
this.create(CommentToken.createEmpty().startingAt(this.currentPosition));
|
||||
this.reconsumeIn(State.BogusComment);
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
case State.MarkupDeclarationOpen: {
|
||||
if (this.matchNextFew('--')) {
|
||||
this.consumeNextFew('--');
|
||||
this.create(CommentToken.createEmpty().startingAt(this.currentPosition.copy().decrement(4)));
|
||||
this.state = State.CommentStart;
|
||||
} else if (this.matchNextFewCaseInsensitive('DOCTYPE')) {
|
||||
this.consumeNextFewCaseInsensitive('DOCTYPE');
|
||||
this.state = State.DOCTYPE;
|
||||
} else if (this.matchNextFew('[CDATA[')) {
|
||||
this.consumeNextFew('[CDATA[');
|
||||
// NOTE: This parser will never be generated as part of the fragment parsing algorithm, as such the CDATA section state does not
|
||||
// exist and will not be started here.
|
||||
this.parseError('cdata-in-html-content');
|
||||
this.create(CommentToken.createWith('[CDATA[').startingAt(this.currentPosition));
|
||||
this.state = State.BogusComment;
|
||||
} else {
|
||||
this.parseError('incorrectly-opened-comment');
|
||||
this.create(CommentToken.createEmpty().startingAt(this.currentPosition));
|
||||
this.state = State.BogusComment;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
case State.DOCTYPE: {
|
||||
switch (this.consumeNext()) {
|
||||
case '\u0009':
|
||||
case '\u000A':
|
||||
case '\u000C':
|
||||
case '\u0020': this.state = State.BeforeDOCTYPEName; break;
|
||||
case '\u003E': this.reconsumeIn(State.BeforeDOCTYPEName); break;
|
||||
case undefined:
|
||||
this.parseError('eof-in-doctype');
|
||||
this.emit(DOCTYPEToken.createWithForcedQuirks().at(this.currentPosition));
|
||||
this.emit(EndOfFileToken.create());
|
||||
break;
|
||||
default:
|
||||
this.parseError('missing-whitespace-before-doctype-name');
|
||||
this.reconsumeIn(State.BeforeDOCTYPEName);
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
case State.BeforeDOCTYPEName: {
|
||||
switch (this.consumeNext()) {
|
||||
case '\u0009':
|
||||
case '\u000A':
|
||||
case '\u000C':
|
||||
case '\u0020': break;
|
||||
case '\u0000':
|
||||
this.parseError('unexpected-null-character');
|
||||
this.create(DOCTYPEToken.createWithName('\uFFFD').startingAt(this.currentPosition));
|
||||
this.state = State.DOCTYPEName;
|
||||
break;
|
||||
case undefined:
|
||||
this.parseError('eof-in-doctype');
|
||||
this.emit(DOCTYPEToken.createWithForcedQuirks().at(this.currentPosition));
|
||||
this.emit(EndOfFileToken.create());
|
||||
break;
|
||||
default: {
|
||||
if (this.asciiUpperAlpha(this.currentInputCharacter)) {
|
||||
this.create(DOCTYPEToken.createWithName(this.currentInputCharacter.toLowerCase()).startingAt(this.currentPosition));
|
||||
this.state = State.DOCTYPEName;
|
||||
break;
|
||||
}
|
||||
|
||||
this.create(DOCTYPEToken.createWithName(this.currentInputCharacter).startingAt(this.currentPosition));
|
||||
this.state = State.DOCTYPEName;
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
case State.DOCTYPEName: {
|
||||
switch (this.consumeNext()) {
|
||||
case '\u0009':
|
||||
case '\u000A':
|
||||
case '\u000C':
|
||||
case '\u0020': this.state = State.AfterDOCTYPEName; break;
|
||||
case '\u003E': this.state = State.Data; this.emitCurrentOfType(DOCTYPEToken); break;
|
||||
case '\u0000': this.parseError('unexpected-null-character'); this.currentOfType(DOCTYPEToken).appendReplacementCharacterToName(); break;
|
||||
case undefined:
|
||||
this.parseError('eof-in-doctype');
|
||||
this.currentOfType(DOCTYPEToken).forceQuirks = true;
|
||||
this.emitCurrentOfType(DOCTYPEToken);
|
||||
this.emit(EndOfFileToken.create());
|
||||
break;
|
||||
default: {
|
||||
if (this.asciiUpperAlpha(this.currentInputCharacter)) {
|
||||
this.currentOfType(DOCTYPEToken).appendToName(this.currentInputCharacter.toLowerCase());
|
||||
break;
|
||||
}
|
||||
|
||||
this.currentOfType(DOCTYPEToken).appendToName(this.currentInputCharacter);
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
case State.TagName: {
|
||||
switch (this.consumeNext()) {
|
||||
case '\u0009':
|
||||
case '\u000A':
|
||||
case '\u000C':
|
||||
case '\u0020': this.state = State.BeforeAttributeName; break;
|
||||
case '\u002F': this.state = State.SelfClosingStartTag; break;
|
||||
case '\u003E': this.state = State.Data; this.emitCurrentOfEitherType(StartTagToken, EndTagToken); break;
|
||||
case '\u0000':
|
||||
this.parseError('unexpected-null-character');
|
||||
this.currentOfEitherType(StartTagToken, EndTagToken).appendReplacementCharacterToName();
|
||||
break;
|
||||
case undefined: this.parseError('eof-in-tag'); this.emit(EndOfFileToken.create()); break;
|
||||
default: {
|
||||
if (this.asciiUpperAlpha(this.currentInputCharacter)) {
|
||||
this.currentOfEitherType(StartTagToken, EndTagToken).appendToName(this.currentInputCharacter.toLowerCase());
|
||||
break;
|
||||
}
|
||||
|
||||
this.currentOfEitherType(StartTagToken, EndTagToken).appendToName(this.currentInputCharacter);
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
case State.BeforeAttributeName: {
|
||||
switch (this.consumeNext()) {
|
||||
case '\u0009':
|
||||
case '\u000A':
|
||||
case '\u000C':
|
||||
case '\u0020': break;
|
||||
case '\u002F':
|
||||
case '\u003E':
|
||||
case undefined: this.reconsumeIn(State.AfterAttributeName); break;
|
||||
case '\u003D': {
|
||||
this.parseError('unexpected-equals-sign-before-attribute-name');
|
||||
this.currentOfEitherType(StartTagToken, EndTagToken).attributes.append(Attribute.createWithEmptyValue(this.currentInputCharacter).startingNameAt(this.currentPosition));
|
||||
this.state = State.AttributeName;
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
this.currentOfEitherType(StartTagToken, EndTagToken).attributes.append(Attribute.createWithEmptyNameAndValue().startingNameAt(this.currentPosition));
|
||||
this.reconsumeIn(State.AttributeName);
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
case State.AttributeName: {
|
||||
switch (this.consumeNext()) {
|
||||
case '\u0009':
|
||||
case '\u000A':
|
||||
case '\u000C':
|
||||
case '\u0020':
|
||||
case '\u002F':
|
||||
case '\u003E':
|
||||
case undefined: this.reconsumeIn(State.AfterAttributeName); break;
|
||||
case '\u003D':
|
||||
this.currentOfEitherType(StartTagToken, EndTagToken).attributes.current.endingNameAt(this.currentPosition.copy().decrement(1));
|
||||
this.currentOfEitherType(StartTagToken, EndTagToken).attributes.current.equalsAt(this.currentPosition);
|
||||
this.state = State.BeforeAttributeValue;
|
||||
break;
|
||||
case '\u0000': this.parseError('unexpected-null-character');
|
||||
this.currentOfEitherType(StartTagToken, EndTagToken).attributes.current.appendReplacementCharacterToName();
|
||||
break;
|
||||
case '\u0022':
|
||||
case '\u0027':
|
||||
case '\u003C':
|
||||
this.parseError('unexpected-character-in-attribute-name');
|
||||
this.currentOfEitherType(StartTagToken, EndTagToken).attributes.current.appendToName(this.currentInputCharacter);
|
||||
break;
|
||||
default: {
|
||||
if (this.asciiUpperAlpha(this.currentInputCharacter)) {
|
||||
this.currentOfEitherType(StartTagToken, EndTagToken).attributes.current.appendToName(this.currentInputCharacter.toLowerCase());
|
||||
break;
|
||||
}
|
||||
|
||||
this.currentOfEitherType(StartTagToken, EndTagToken).attributes.current.appendToName(this.currentInputCharacter);
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
case State.AfterAttributeName: {
|
||||
switch (this.consumeNext()) {
|
||||
case '\u0009':
|
||||
case '\u000A':
|
||||
case '\u000C':
|
||||
case '\u0020': break;
|
||||
case '\u002F':
|
||||
this.currentOfEitherType(StartTagToken, EndTagToken).attributes.current.endingNameAt(this.currentPosition);
|
||||
this.state = State.SelfClosingStartTag;
|
||||
break;
|
||||
case '\u003D':
|
||||
this.currentOfEitherType(StartTagToken, EndTagToken).attributes.current.endingNameAt(this.currentPosition);
|
||||
this.currentOfEitherType(StartTagToken, EndTagToken).attributes.current.equalsAt(this.currentPosition);
|
||||
this.state = State.BeforeAttributeValue;
|
||||
break;
|
||||
case '\u003E':
|
||||
this.currentOfEitherType(StartTagToken, EndTagToken).attributes.current.endingNameAt(this.currentPosition);
|
||||
this.state = State.Data;
|
||||
this.emitCurrentOfEitherType(StartTagToken, EndTagToken);
|
||||
break;
|
||||
case undefined: this.parseError('eof-in-tag'); this.emit(EndOfFileToken.create()); break;
|
||||
default:
|
||||
this.currentOfEitherType(StartTagToken, EndTagToken).attributes.append(Attribute.createWithEmptyNameAndValue().startingNameAt(this.currentPosition));
|
||||
this.reconsumeIn(State.AttributeName);
|
||||
break;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
case State.BeforeAttributeValue: {
|
||||
switch (this.consumeNext()) {
|
||||
case '\u0009':
|
||||
case '\u000A':
|
||||
case '\u000C':
|
||||
case '\u0020': break;
|
||||
case '\u0022':
|
||||
this.currentOfEitherType(StartTagToken, EndTagToken).attributes.current.startingValueAt(this.currentPosition);
|
||||
this.state = State.AttributeValueDouble;
|
||||
break;
|
||||
case '\u0027':
|
||||
this.currentOfEitherType(StartTagToken, EndTagToken).attributes.current.startingValueAt(this.currentPosition);
|
||||
this.state = State.AttributeValueSingle;
|
||||
break;
|
||||
case '\u003E':
|
||||
this.parseError('missing-attribute-value');
|
||||
this.state = State.Data;
|
||||
this.currentOfEitherType(StartTagToken, EndTagToken).attributes.current.endingNameAt(this.currentPosition);
|
||||
this.emitCurrentOfEitherType(StartTagToken, EndTagToken);
|
||||
break;
|
||||
default:
|
||||
this.currentOfEitherType(StartTagToken, EndTagToken).attributes.current.startingValueAt(this.currentPosition);
|
||||
this.reconsumeIn(State.AttributeValueUnquoted);
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
case State.AttributeValueDouble: {
|
||||
switch (this.consumeNext()) {
|
||||
case '\u0022':
|
||||
this.currentOfEitherType(StartTagToken, EndTagToken).attributes.current.endingValueAt(this.currentPosition);
|
||||
this.state = State.AfterAttributeValue;
|
||||
break;
|
||||
case '\u0026': this.returnState = State.AttributeValueDouble; this.state = State.CharacterReference; break;
|
||||
case '\u0000':
|
||||
this.parseError('unexpected-null-character');
|
||||
this.currentOfEitherType(StartTagToken, EndTagToken).attributes.current.appendReplacementCharacterToValue();
|
||||
break;
|
||||
case undefined: this.parseError('eof-in-tag'); this.emit(EndOfFileToken.create()); break;
|
||||
default: this.currentOfEitherType(StartTagToken, EndTagToken).attributes.current.appendToValue(this.currentInputCharacter);
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
case State.AttributeValueSingle: {
|
||||
switch (this.consumeNext()) {
|
||||
case '\u0027':
|
||||
this.currentOfEitherType(StartTagToken, EndTagToken).attributes.current.endingValueAt(this.currentPosition);
|
||||
this.state = State.AfterAttributeValue;
|
||||
break;
|
||||
case '\u0026': this.returnState = State.AttributeValueSingle; this.state = State.CharacterReference; break;
|
||||
case '\u0000':
|
||||
this.parseError('unexpected-null-character');
|
||||
this.currentOfEitherType(StartTagToken, EndTagToken).attributes.current.appendReplacementCharacterToValue();
|
||||
break;
|
||||
case undefined: this.parseError('eof-in-tag'); this.emit(EndOfFileToken.create()); break;
|
||||
default: this.currentOfEitherType(StartTagToken, EndTagToken).attributes.current.appendToValue(this.currentInputCharacter);
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
case State.AttributeValueUnquoted: {
|
||||
this.currentOfEitherType(StartTagToken, EndTagToken).attributes.current.setUnquoted();
|
||||
|
||||
switch (this.consumeNext()) {
|
||||
case '\u0009':
|
||||
case '\u000A':
|
||||
case '\u000C':
|
||||
case '\u0020': this.state = State.BeforeAttributeName; break;
|
||||
case '\u0026': this.returnState = State.AttributeValueUnquoted; this.state = State.CharacterReference; break;
|
||||
case '\u003E':
|
||||
this.currentOfEitherType(StartTagToken, EndTagToken).attributes.current.endingValueAt(this.currentPosition);
|
||||
this.state = State.Data;
|
||||
this.emitCurrentOfEitherType(StartTagToken, EndTagToken);
|
||||
break;
|
||||
case '\u0000':
|
||||
this.parseError('unexpected-null-character');
|
||||
this.currentOfEitherType(StartTagToken, EndTagToken).attributes.current.appendReplacementCharacterToValue();
|
||||
break;
|
||||
case '\u0022':
|
||||
case '\u0027':
|
||||
case '\u003C':
|
||||
case '\u003D':
|
||||
case '\u0060':
|
||||
this.parseError('unexpected-character-in-unquoted-attribute-value');
|
||||
this.currentOfEitherType(StartTagToken, EndTagToken).attributes.current.appendToValue(this.currentInputCharacter);
|
||||
break;
|
||||
case undefined: this.parseError('eof-in-tag'); this.emit(EndOfFileToken.create()); break;
|
||||
default: this.currentOfEitherType(StartTagToken, EndTagToken).attributes.current.appendToValue(this.currentInputCharacter);
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
case State.AfterAttributeValue: {
|
||||
switch (this.consumeNext()) {
|
||||
case '\u0009':
|
||||
case '\u000A':
|
||||
case '\u000C':
|
||||
case '\u0020': this.state = State.BeforeAttributeName; break;
|
||||
case '\u002F': this.state = State.SelfClosingStartTag; break;
|
||||
case '\u003E': this.state = State.Data; this.emitCurrentOfEitherType(StartTagToken, EndTagToken); break;
|
||||
case undefined: this.parseError('eof-in-tag'); this.emit(EndOfFileToken.create()); break;
|
||||
default: this.parseError('missing-whitespace-between-attributes'); this.reconsumeIn(State.BeforeAttributeName);
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
case State.CommentStart: {
|
||||
switch (this.consumeNext()) {
|
||||
case '\u002D': this.state = State.CommentStartDash; break;
|
||||
case '\u003E': this.parseError('abrupt-closing-of-empty-comment'); this.state = State.Data; this.emitCurrentOfType(CommentToken); break;
|
||||
default: this.reconsumeIn(State.Comment);
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
// FIXME: Possible improvement to https://html.spec.whatwg.org/multipage/parsing.html#comment-state (adding **current** in some places)
|
||||
case State.Comment: {
|
||||
switch (this.consumeNext()) {
|
||||
case '\u003C': this.currentOfType(CommentToken).append(this.currentInputCharacter); this.state = State.CommentLessThanSign; break;
|
||||
case '\u002D': this.state = State.CommentEndDash; break;
|
||||
case '\u0000': this.parseError('unexpected-null-character'); this.currentOfType(CommentToken).appendReplacementCharacter(); break;
|
||||
case undefined: this.parseError('eof-in-comment'); this.emitCurrentOfType(CommentToken); this.emit(EndOfFileToken.create()); break;
|
||||
default: this.currentOfType(CommentToken).append(this.currentInputCharacter);
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
case State.CommentEndDash: {
|
||||
switch (this.consumeNext()) {
|
||||
case '\u002D': this.state = State.CommentEnd; break;
|
||||
case undefined: this.parseError('eof-in-comment'); this.emitCurrentOfType(CommentToken); this.emit(EndOfFileToken.create()); break;
|
||||
default: this.currentOfType(CommentToken).append('\u002D'); this.reconsumeIn(State.Comment);
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
// Same as above fixme https://html.spec.whatwg.org/multipage/parsing.html#comment-end-state
|
||||
case State.CommentEnd: {
|
||||
switch (this.consumeNext()) {
|
||||
case '\u003E': this.state = State.Data; this.emit(this.currentOfType(CommentToken).endingAt(this.currentPosition.copy().increment(1))); break;
|
||||
case '\u0021': this.state = State.CommentEndBang; break;
|
||||
case '\u002D': this.currentOfType(CommentToken).append('\u002D'); break;
|
||||
case undefined: this.parseError('eof-in-comment'); this.emitCurrentOfType(CommentToken); this.emit(EndOfFileToken.create()); break;
|
||||
default: this.currentOfType(CommentToken).append('\u002D\u002D'); this.reconsumeIn(State.Comment);
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
// Same as above https://html.spec.whatwg.org/multipage/parsing.html#bogus-comment-state
|
||||
case State.BogusComment: {
|
||||
switch (this.consumeNext()) {
|
||||
case '\u003E': this.state = State.Data; this.emitCurrentOfType(CommentToken); break;
|
||||
case undefined: this.emitCurrentOfType(CommentToken); this.emit(EndOfFileToken.create()); break;
|
||||
case '\u0000': this.parseError('unexpected-null-character'); this.currentOfType(CommentToken).appendReplacementCharacter(); break;
|
||||
default: this.currentOfType(CommentToken).append(this.currentInputCharacter);
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
case State.CharacterReference: {
|
||||
this.temporaryBuffer = '';
|
||||
this.temporaryBuffer += '\u0026';
|
||||
|
||||
switch (this.consumeNext()) {
|
||||
case '\u0023': this.temporaryBuffer += this.currentInputCharacter; this.state = State.NumericCharacterReference; break;
|
||||
default: {
|
||||
if (this.asciiAlphanumeric(this.currentInputCharacter)) {
|
||||
this.reconsumeIn(State.NamedCharacterReference);
|
||||
break;
|
||||
}
|
||||
|
||||
this.flushCodePointsConsumedAsCharacterReference();
|
||||
this.reconsumeIn(this.returnState);
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
case State.NamedCharacterReference: {
|
||||
let match = false;
|
||||
|
||||
for (const entry in entities) {
|
||||
if (this.matchNextFew(entry)) {
|
||||
match = true;
|
||||
|
||||
this.consumeNextFew(entry);
|
||||
this.temporaryBuffer += entry;
|
||||
|
||||
if (this.consumedAsPartOfAnAttribute() && entry[entry.length - 1] !== '\u003B' && (this.next() === '\u003D' || this.asciiAlphanumeric(this.next() ?? ''))) {
|
||||
this.flushCodePointsConsumedAsCharacterReference();
|
||||
this.state = this.returnState;
|
||||
break;
|
||||
}
|
||||
|
||||
if (entry[entry.length - 1] !== '\u003B')
|
||||
this.parseError('missing-semicolon-after-character-reference');
|
||||
|
||||
this.temporaryBuffer = '';
|
||||
this.temporaryBuffer += entities[entry].characters;
|
||||
this.flushCodePointsConsumedAsCharacterReference();
|
||||
this.state = this.returnState;
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!match) {
|
||||
this.flushCodePointsConsumedAsCharacterReference();
|
||||
this.state = State.AmbiguousAmpersand;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
case State.AmbiguousAmpersand: {
|
||||
switch (this.consumeNext()) {
|
||||
case '\u003B': this.parseError('unknown-named-character-reference'); this.reconsumeIn(this.returnState); break;
|
||||
default: {
|
||||
if (this.asciiAlphanumeric(this.currentInputCharacter)) {
|
||||
if (this.consumedAsPartOfAnAttribute()) {
|
||||
this.currentOfEitherType(StartTagToken, EndTagToken).attributes.current.appendToValue(this.currentInputCharacter);
|
||||
} else {
|
||||
this.emit(CharacterToken.createWith(this.currentInputCharacter));
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
this.reconsumeIn(this.returnState);
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
default: TODO(`Unimplemented state '${this.state}'`);
|
||||
}
|
||||
}
|
||||
|
||||
private flushCodePointsConsumedAsCharacterReference(): void {
|
||||
if (this.consumedAsPartOfAnAttribute()) {
|
||||
this.currentOfEitherType(StartTagToken, EndTagToken).attributes.current.appendToValue(this.temporaryBuffer);
|
||||
return;
|
||||
}
|
||||
|
||||
for (const codePoint of this.temporaryBuffer)
|
||||
this.emit(CharacterToken.createWith(codePoint));
|
||||
}
|
||||
|
||||
private consumedAsPartOfAnAttribute(): boolean {
|
||||
return this.returnState === State.AttributeValueDouble || this.returnState === State.AttributeValueSingle || this.returnState === State.AttributeValueUnquoted;
|
||||
}
|
||||
|
||||
private asciiAlphanumeric(input: string): boolean {
|
||||
return this.asciiAlpha(input) || this.asciiDigit(input);
|
||||
}
|
||||
|
||||
private asciiAlpha(input: string): boolean {
|
||||
return this.asciiUpperAlpha(input) || this.asciiLowerAlpha(input);
|
||||
}
|
||||
|
||||
private asciiUpperAlpha(input: string): boolean {
|
||||
return /[\u0041-\u005A]/.test(input);
|
||||
}
|
||||
|
||||
private asciiLowerAlpha(input: string): boolean {
|
||||
return /[\u0061-\u007A]/.test(input);
|
||||
}
|
||||
|
||||
private asciiDigit(input: string): boolean {
|
||||
return /[\u0030-\u0030]/.test(input);
|
||||
}
|
||||
|
||||
private reconsumeIn(state: State): void {
|
||||
this.pointer--;
|
||||
|
||||
this.currentPosition.decrement();
|
||||
|
||||
this.state = state;
|
||||
this.spin();
|
||||
}
|
||||
|
||||
private parseError(error: ParseError): void {
|
||||
console.error('Parse error: ' + error);
|
||||
}
|
||||
|
||||
private consumeNext(): string | undefined {
|
||||
this.currentInputCharacter = this.input[this.pointer];
|
||||
this.pointer++;
|
||||
|
||||
this.currentPosition.increment();
|
||||
|
||||
if (this.currentInputCharacter === '\n')
|
||||
this.currentPosition.incrementLine();
|
||||
|
||||
return this.currentInputCharacter;
|
||||
}
|
||||
|
||||
private next(): string | undefined {
|
||||
return this.input[this.pointer];
|
||||
}
|
||||
|
||||
private matchNextFew(input: string): boolean {
|
||||
return this.input.substr(this.pointer, input.length) === input;
|
||||
}
|
||||
|
||||
private matchNextFewCaseInsensitive(input: string): boolean {
|
||||
return this.input.substr(this.pointer, input.length).toLowerCase() === input.toLowerCase();
|
||||
}
|
||||
|
||||
private consumeNextFew(input: string): void {
|
||||
for (let i = 0; i < input.length; i++) {
|
||||
const consumed = this.consumeNext();
|
||||
|
||||
VERIFY(consumed === input[i], `Expected '${input[i]}' (${input} at ${i}), got ${consumed} instead`);
|
||||
}
|
||||
}
|
||||
|
||||
private consumeNextFewCaseInsensitive(input: string): void {
|
||||
for (let i = 0; i < input.length; i++) {
|
||||
const consumed = this.consumeNext()?.toLowerCase();
|
||||
|
||||
VERIFY(consumed === input[i].toLowerCase(), `Expected '${input[i].toLowerCase()}' (${input.toLowerCase()} at ${i}), got ${consumed} instead`);
|
||||
}
|
||||
}
|
||||
|
||||
private emit(token: Token): void {
|
||||
this.populateRangeOnEmit(token);
|
||||
this.tokens.push(token);
|
||||
}
|
||||
|
||||
private emitCurrentOfType(type: Constructor<Token>): void {
|
||||
VERIFY(this.currentToken instanceof type, `Expected '${type.name}', got '${this.currentToken.constructor.name}' instead`);
|
||||
|
||||
this.populateRangeOnEmit(this.currentToken);
|
||||
this.tokens.push(this.currentToken);
|
||||
}
|
||||
|
||||
private emitCurrentOfEitherType<T extends Token, U extends Token>(a: Constructor<T>, b: Constructor<U>): void {
|
||||
VERIFY(this.currentToken instanceof a || this.currentToken instanceof b, `Expected '${a.name}' or '${b.name}', got '${this.currentToken.constructor.name}' instead`);
|
||||
|
||||
this.populateRangeOnEmit(this.currentToken);
|
||||
this.tokens.push(this.currentToken);
|
||||
}
|
||||
|
||||
private currentOfType<T extends Token>(type: Constructor<T>): T {
|
||||
VERIFY(this.currentToken instanceof type, `Expected '${type.name}', got '${this.currentToken.constructor.name}' instead`);
|
||||
|
||||
return this.currentToken;
|
||||
}
|
||||
|
||||
private currentOfEitherType<T extends Token, U extends Token>(a: Constructor<T>, b: Constructor<U>): T | U {
|
||||
VERIFY(this.currentToken instanceof a || this.currentToken instanceof b, `Expected '${a.name}' or '${b.name}', got '${this.currentToken.constructor.name}' instead`);
|
||||
|
||||
return this.currentToken;
|
||||
}
|
||||
|
||||
private populateRangeOnEmit(token: Token): void {
|
||||
if (token.range.start === undefined && token.range.end === undefined)
|
||||
token.at(this.currentPosition);
|
||||
|
||||
if (token.range.start !== undefined && token.range.end === undefined)
|
||||
token.endingAt(this.currentPosition);
|
||||
|
||||
if (token.range.start === undefined && token.range.end !== undefined)
|
||||
VERIFY_NOT_REACHED();
|
||||
}
|
||||
|
||||
private create(token: Token): Token {
|
||||
if (token.range.start === undefined)
|
||||
token.startingAt(this.currentPosition);
|
||||
|
||||
return this.currentToken = token;
|
||||
}
|
||||
}
|
117
src/html/tokenizer/attribute.ts
Normal file
|
@ -0,0 +1,117 @@
|
|||
import { VERIFY } from '../../util/assertions.js';
|
||||
import { Inspectable } from '../highlighter/inspectable.js';
|
||||
import { Position } from './position.js';
|
||||
import { Range } from './range.js';
|
||||
import { REPLACEMENT_CHARACTER } from './token.js';
|
||||
|
||||
export class Attribute extends Inspectable {
|
||||
public name: string;
|
||||
public value: string;
|
||||
public nameRange!: Range;
|
||||
public valueRange?: Range;
|
||||
public equalsPosition?: Position;
|
||||
public quoted: boolean;
|
||||
|
||||
public constructor(name: string, value: string) {
|
||||
super();
|
||||
|
||||
this.name = name;
|
||||
this.value = value;
|
||||
this.quoted = true;
|
||||
|
||||
// @ts-expect-error
|
||||
this.nameRange = {};
|
||||
}
|
||||
|
||||
public setUnquoted(): void {
|
||||
this.quoted = false;
|
||||
}
|
||||
|
||||
public appendToName(characters: string): void {
|
||||
this.name += characters;
|
||||
}
|
||||
|
||||
public appendReplacementCharacterToName(): void {
|
||||
this.appendToName(REPLACEMENT_CHARACTER);
|
||||
}
|
||||
|
||||
public appendToValue(characters: string): void {
|
||||
this.value += characters;
|
||||
}
|
||||
|
||||
public appendReplacementCharacterToValue(): void {
|
||||
this.appendToValue(REPLACEMENT_CHARACTER);
|
||||
}
|
||||
|
||||
public startingNameAt(position: Position): this {
|
||||
this.nameRange.start = position.copy();
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
public endingNameAt(position: Position): this {
|
||||
this.nameRange.end = position.copy();
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
public equalsAt(position: Position): this {
|
||||
this.equalsPosition = position.copy();
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
public startingValueAt(position: Position): this {
|
||||
// @ts-expect-error
|
||||
if (this.valueRange === undefined) this.valueRange = {};
|
||||
VERIFY(this.valueRange !== undefined);
|
||||
this.valueRange.start = position.copy();
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
public endingValueAt(position: Position): this {
|
||||
// @ts-expect-error
|
||||
if (this.valueRange === undefined) this.valueRange = {};
|
||||
VERIFY(this.valueRange !== undefined);
|
||||
this.valueRange.end = position.copy();
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
public static createWithEmptyNameAndValue(): Attribute {
|
||||
return new Attribute('', '');
|
||||
}
|
||||
|
||||
public static createWithEmptyValue(name: string): Attribute {
|
||||
return new Attribute(name, '');
|
||||
}
|
||||
|
||||
public override inspect(indent: number): string {
|
||||
return `Attribute { name: '${this.name}', value: '${this.value}' }`;
|
||||
}
|
||||
}
|
||||
|
||||
export class AttributeList {
|
||||
private attributes: Array<Attribute>;
|
||||
|
||||
public constructor() {
|
||||
this.attributes = new Array<Attribute>();
|
||||
}
|
||||
|
||||
public get current(): Attribute {
|
||||
return this.attributes[this.attributes.length - 1];
|
||||
}
|
||||
|
||||
public get list(): Array<Attribute> {
|
||||
return this.attributes;
|
||||
}
|
||||
|
||||
public nonEmpty(): boolean {
|
||||
return this.list.length !== 0;
|
||||
}
|
||||
|
||||
public append(attribute: Attribute): void {
|
||||
this.attributes.push(attribute);
|
||||
}
|
||||
}
|
2236
src/html/tokenizer/entities.ts
Normal file
60
src/html/tokenizer/position.ts
Normal file
|
@ -0,0 +1,60 @@
|
|||
import { Inspectable } from '../highlighter/inspectable.js';
|
||||
|
||||
export class Position extends Inspectable {
|
||||
#line: number;
|
||||
#column: number;
|
||||
#index: number;
|
||||
|
||||
private constructor(line: number, column: number, index: number) {
|
||||
super();
|
||||
|
||||
this.#line = line;
|
||||
this.#column = column;
|
||||
this.#index = index;
|
||||
}
|
||||
|
||||
public get line(): number {
|
||||
return this.#line;
|
||||
}
|
||||
|
||||
public get column(): number {
|
||||
return this.#column;
|
||||
}
|
||||
|
||||
public get index(): number {
|
||||
return this.#index;
|
||||
}
|
||||
|
||||
public increment(by: number = 1): this {
|
||||
this.#index += by;
|
||||
this.#column += by;
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
public decrement(by: number = 1): this {
|
||||
this.#index -= by;
|
||||
this.#column -= by;
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
public incrementLine(): this {
|
||||
this.#line++;
|
||||
this.#column = 0;
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
public copy(): Position {
|
||||
return new Position(this.line, this.column, this.index);
|
||||
}
|
||||
|
||||
public static createStarting(): Position {
|
||||
return new Position(0, 0, -1);
|
||||
}
|
||||
|
||||
public override inspect(indent: number): string {
|
||||
return `Position { line: ${this.#line}, column: ${this.#column}, index: ${this.#index} }`;
|
||||
}
|
||||
}
|
6
src/html/tokenizer/range.ts
Normal file
|
@ -0,0 +1,6 @@
|
|||
import { Position } from './position.js';
|
||||
|
||||
export type Range = {
|
||||
start: Position,
|
||||
end: Position
|
||||
};
|
82
src/html/tokenizer/state.ts
Normal file
|
@ -0,0 +1,82 @@
|
|||
export const enum State {
|
||||
Data = 'Data',
|
||||
RCDATA = 'RCDATA',
|
||||
RAWTEXT = 'RAWTEXT',
|
||||
ScriptData = 'Script data',
|
||||
PLAINTEXT = 'PLAINTEXT',
|
||||
TagOpen = 'Tag open',
|
||||
EndTagOpen = 'End tag open',
|
||||
TagName = 'Tag name',
|
||||
RCDATALessThanSign = 'RCDATA less-than sign',
|
||||
RCDATAEndTagOpen = 'RCDATA end tag open',
|
||||
RCDATAEndTagName = 'RCDATA end tag name',
|
||||
RAWTEXTLessThan = 'RAWTEXT less-than',
|
||||
RAWTEXTEndTagOpen = 'RAWTEXT end tag open',
|
||||
RAWTEXTEndTagName = 'RAWTEXT end tag name',
|
||||
ScriptDataLessThanSign = 'Script data less-than sign',
|
||||
ScriptDataEndTagOpen = 'Script data end tag open',
|
||||
ScriptDataEndTagName = 'Script data end tag name',
|
||||
ScriptDataEscapeStart = 'Script data escape start',
|
||||
ScriptDataEscapeStartDash = 'Script data escape start dash',
|
||||
ScriptDataEscaped = 'Script data escaped',
|
||||
ScriptDataEscapedDash = 'Script data escaped dash',
|
||||
ScriptDataEscapedDashDash = 'Script data escaped dash dash',
|
||||
ScriptDataEscapedLessThanSign = 'Script data escaped less-than sign',
|
||||
ScriptDataEscapedEndTagOpen = 'Script data escaped end tag open',
|
||||
ScriptDataEscapedEndTagName = 'Script data escaped end tag name',
|
||||
ScriptDataDoubleEscapeStart = 'Script data double escape start',
|
||||
ScriptDataDoubleEscaped = 'Script data double escaped',
|
||||
ScriptDataDoubleEscapedDash = 'Script data double escaped dash',
|
||||
ScriptDataDoubleEscapedDashDash = 'Script data double escaped dash dash',
|
||||
ScriptDataDoubleEscapedLessThanSign = 'Script data double escaped less-than sign',
|
||||
ScriptDataDoubleEscapedEnd = 'Script data double escape end',
|
||||
BeforeAttributeName = 'Before attribute name',
|
||||
AttributeName = 'Attribute name',
|
||||
AfterAttributeName = 'After attribute name',
|
||||
BeforeAttributeValue = 'Before attribute value',
|
||||
AttributeValueDouble = 'Attribute value (double-quoted)',
|
||||
AttributeValueSingle = 'Attribute value (single-quoted)',
|
||||
AttributeValueUnquoted = 'Attribute value (unquoted)',
|
||||
AfterAttributeValue = 'After attribute value (quoted)',
|
||||
SelfClosingStartTag = 'Self-closing start tag',
|
||||
BogusComment = 'Bogus comment',
|
||||
MarkupDeclarationOpen = 'Markup declaration open',
|
||||
CommentStart = 'Comment start',
|
||||
CommentStartDash = 'Comment start dash',
|
||||
Comment = 'Comment',
|
||||
CommentLessThanSign = 'Comment less-than sign',
|
||||
CommentLessThanSignBang = 'Comment less-than sign bang',
|
||||
CommentLessThanSignBangDash= 'Comment less-than sign bang dash',
|
||||
CommentLessThanSignBangDashDash = 'Comment less-than sign bang dash dash',
|
||||
CommentEndDash = 'Comment end dash',
|
||||
CommentEnd = 'Comment end',
|
||||
CommentEndBang = 'Comment end bang',
|
||||
DOCTYPE = 'DOCTYPE',
|
||||
BeforeDOCTYPEName = 'Before DOCTYPE name',
|
||||
DOCTYPEName = 'DOCTYPE name',
|
||||
AfterDOCTYPEName= 'After DOCTYPE name',
|
||||
AfterDOCTYPEPublicKeyword = 'After DOCTYPE public keyword',
|
||||
BeforeDOCTYPEPublicIdentifier = 'Before DOCTYPE public identifier',
|
||||
DOCTYPEPublicIdentifierDouble = 'DOCTYPE public identifier (double-quoted)',
|
||||
DOCTYPEPublicIdentifierSingle = 'DOCTYPE public identifier (single-quoted)',
|
||||
AfterDOCTYPEPublicIdentifier = 'After DOCTYPE public identifier',
|
||||
BetweenDOCTYPEPublicAndSystemIdentifiers = 'Between DOCTYPE public and system identifiers',
|
||||
AfterDOCTYPESystemKeyword = 'After DOCTYPE system keyword',
|
||||
BeforeDOCTYPESystemIdentifier = 'Before DOCTYPE system identifier',
|
||||
DOCTYPESystemIdentifierDouble = 'DOCTYPE system identifier (double-quoted)',
|
||||
DOCTYPESystemIdentifierSingle = 'DOCTYPE system identifier (single-quoted)',
|
||||
AfterDOCTYPESystemIdentifier = 'After DOCTYPE system identifier',
|
||||
BogusDOCTYPE = 'Bogus DOCTYPE',
|
||||
CDATASection = 'CDATA section',
|
||||
CDATASectionBracket = 'CDATA section bracket',
|
||||
CDATASectionEnd = 'CDATA section end',
|
||||
CharacterReference = 'Character reference',
|
||||
NamedCharacterReference = 'Named character reference',
|
||||
AmbiguousAmpersand = 'Ambiguous ampersand',
|
||||
NumericCharacterReference = 'Numeric character reference',
|
||||
HexadecimalCharacterReferenceStart = 'Hexadecimal character reference start',
|
||||
DecimalCharacterReferenceStart = 'Decimal character reference start',
|
||||
HexadecimalCharacterReference = 'Hexadecimal character reference',
|
||||
DecimalCharacterReference = 'Decimal character reference',
|
||||
NumericCharacterReferenceEnd = 'Numeric character reference end'
|
||||
}
|
55
src/html/tokenizer/token.ts
Normal file
|
@ -0,0 +1,55 @@
|
|||
import { Inspectable } from '../highlighter/inspectable.js';
|
||||
import { Position } from './position.js';
|
||||
import { Range } from './range.js';
|
||||
|
||||
export const enum Type {
|
||||
DOCTYPE = 'DOCTYPE',
|
||||
StartTag = 'start tag',
|
||||
EndTag = 'end tag',
|
||||
Comment = 'comment',
|
||||
Character = 'character',
|
||||
EndOfFile = 'end-of-file'
|
||||
}
|
||||
|
||||
export const REPLACEMENT_CHARACTER = '\uFFFD';
|
||||
|
||||
export abstract class Token extends Inspectable {
|
||||
#type: Type;
|
||||
#range!: Range;
|
||||
|
||||
protected constructor(type: Type) {
|
||||
super();
|
||||
|
||||
this.#type = type;
|
||||
|
||||
// @ts-expect-error
|
||||
this.#range = {};
|
||||
}
|
||||
|
||||
public startingAt(position: Position): this {
|
||||
this.#range.start = position.copy();
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
public endingAt(position: Position): this {
|
||||
this.#range.end = position.copy();
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
public at(position: Position): this {
|
||||
this.#range.start = position.copy();
|
||||
this.#range.end = position.copy();
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
public get range(): Range {
|
||||
return this.#range;
|
||||
}
|
||||
|
||||
public get type(): Type {
|
||||
return this.#type;
|
||||
}
|
||||
}
|
23
src/html/tokenizer/tokens/character.ts
Normal file
|
@ -0,0 +1,23 @@
|
|||
import { REPLACEMENT_CHARACTER, Token, Type } from '../token.js';
|
||||
|
||||
export class CharacterToken extends Token {
|
||||
public readonly data: NonNullable<string>;
|
||||
|
||||
public constructor(data: NonNullable<string>) {
|
||||
super(Type.Character);
|
||||
|
||||
this.data = data;
|
||||
}
|
||||
|
||||
public static createWith(data: NonNullable<string>): CharacterToken {
|
||||
return new CharacterToken(data);
|
||||
}
|
||||
|
||||
public static createReplacementCharacter(): CharacterToken {
|
||||
return new CharacterToken(REPLACEMENT_CHARACTER);
|
||||
}
|
||||
|
||||
public override inspect(indent: number): string {
|
||||
return `CharacterToken { '${this.data}' }`;
|
||||
}
|
||||
}
|
31
src/html/tokenizer/tokens/comment.ts
Normal file
|
@ -0,0 +1,31 @@
|
|||
import { Token, Type, REPLACEMENT_CHARACTER } from '../token.js';
|
||||
|
||||
export class CommentToken extends Token {
|
||||
public data: NonNullable<string>;
|
||||
|
||||
public constructor(data: NonNullable<string>) {
|
||||
super(Type.Comment);
|
||||
|
||||
this.data = data;
|
||||
}
|
||||
|
||||
public append(characters: string): void {
|
||||
this.data += characters;
|
||||
}
|
||||
|
||||
public appendReplacementCharacter(): void {
|
||||
this.append(REPLACEMENT_CHARACTER);
|
||||
}
|
||||
|
||||
public static createEmpty(): CommentToken {
|
||||
return new CommentToken('');
|
||||
}
|
||||
|
||||
public static createWith(data: string): CommentToken {
|
||||
return new CommentToken(data);
|
||||
}
|
||||
|
||||
public override inspect(indent: number): string {
|
||||
return `CommentToken { '${this.data}' }`;
|
||||
}
|
||||
}
|
40
src/html/tokenizer/tokens/doctype.ts
Normal file
|
@ -0,0 +1,40 @@
|
|||
import { VERIFY } from '../../../util/assertions.js';
|
||||
import { Token, Type, REPLACEMENT_CHARACTER } from '../token.js';
|
||||
|
||||
export class DOCTYPEToken extends Token {
|
||||
public name?: string;
|
||||
public publicIdentifier?: string;
|
||||
public systemIdentifier?: string;
|
||||
public forceQuirks?: true;
|
||||
|
||||
public constructor(name?: string, publicIdentifier?: string, systemIdentifier?: string, forceQuirks?: true) {
|
||||
super(Type.DOCTYPE);
|
||||
|
||||
this.name = name;
|
||||
this.publicIdentifier = publicIdentifier;
|
||||
this.systemIdentifier = systemIdentifier;
|
||||
this.forceQuirks = forceQuirks;
|
||||
}
|
||||
|
||||
public appendToName(characters: string): void {
|
||||
VERIFY(this.name !== undefined);
|
||||
|
||||
this.name += characters;
|
||||
}
|
||||
|
||||
public appendReplacementCharacterToName(): void {
|
||||
this.appendToName(REPLACEMENT_CHARACTER);
|
||||
}
|
||||
|
||||
public static createWithForcedQuirks(): DOCTYPEToken {
|
||||
return new DOCTYPEToken(undefined, undefined, undefined, true);
|
||||
}
|
||||
|
||||
public static createWithName(name: string): DOCTYPEToken {
|
||||
return new DOCTYPEToken(name, undefined, undefined, undefined);
|
||||
}
|
||||
|
||||
public override inspect(indent: number): string {
|
||||
return `DOCTYPEToken { '${this.name}' }`;
|
||||
}
|
||||
}
|
15
src/html/tokenizer/tokens/endOfFile.ts
Normal file
|
@ -0,0 +1,15 @@
|
|||
import { Token, Type } from '../token.js';
|
||||
|
||||
export class EndOfFileToken extends Token {
|
||||
public constructor() {
|
||||
super(Type.EndOfFile);
|
||||
}
|
||||
|
||||
public static create(): EndOfFileToken {
|
||||
return new EndOfFileToken();
|
||||
}
|
||||
|
||||
public override inspect(indent: number): string {
|
||||
return 'EndOfFileToken';
|
||||
}
|
||||
}
|
30
src/html/tokenizer/tokens/endTag.ts
Normal file
|
@ -0,0 +1,30 @@
|
|||
import { AttributeList } from '../attribute.js';
|
||||
import { Token, Type, REPLACEMENT_CHARACTER } from '../token.js';
|
||||
|
||||
export class EndTagToken extends Token {
|
||||
public name: NonNullable<string>;
|
||||
public readonly attributes: AttributeList;
|
||||
|
||||
public constructor(name: NonNullable<string>, attributes: AttributeList) {
|
||||
super(Type.EndTag);
|
||||
|
||||
this.name = name;
|
||||
this.attributes = attributes;
|
||||
}
|
||||
|
||||
public appendToName(characters: string): void {
|
||||
this.name += characters;
|
||||
}
|
||||
|
||||
public appendReplacementCharacterToName(): void {
|
||||
this.appendToName(REPLACEMENT_CHARACTER);
|
||||
}
|
||||
|
||||
public static createEmpty(): EndTagToken {
|
||||
return new EndTagToken('', new AttributeList());
|
||||
}
|
||||
|
||||
public override inspect(indent: number): string {
|
||||
return `EndTagToken { '${this.name}' }`;
|
||||
}
|
||||
}
|
30
src/html/tokenizer/tokens/startTag.ts
Normal file
|
@ -0,0 +1,30 @@
|
|||
import { AttributeList } from '../attribute.js';
|
||||
import { Token, Type, REPLACEMENT_CHARACTER } from '../token.js';
|
||||
|
||||
export class StartTagToken extends Token {
|
||||
public name: NonNullable<string>;
|
||||
public readonly attributes: AttributeList;
|
||||
|
||||
public constructor(name: NonNullable<string>, attributes: AttributeList) {
|
||||
super(Type.StartTag);
|
||||
|
||||
this.name = name;
|
||||
this.attributes = attributes;
|
||||
}
|
||||
|
||||
public appendToName(characters: string): void {
|
||||
this.name += characters;
|
||||
}
|
||||
|
||||
public appendReplacementCharacterToName(): void {
|
||||
this.appendToName(REPLACEMENT_CHARACTER);
|
||||
}
|
||||
|
||||
public static createEmpty(): StartTagToken {
|
||||
return new StartTagToken('', new AttributeList());
|
||||
}
|
||||
|
||||
public override inspect(indent: number): string {
|
||||
return `StartTagToken { '${this.name}' }`;
|
||||
}
|
||||
}
|
0
src/javascript.ts
Normal file
0
src/javascript/tokenizer.ts
Normal file
|
@ -1,6 +0,0 @@
|
|||
import "../styles/tailwind.css"
|
||||
|
||||
function MyApp({ Component, pageProps }) {
|
||||
return <Component {...pageProps} />
|
||||
}
|
||||
export default MyApp
|
|
@ -1,3 +0,0 @@
|
|||
@import "tailwindcss/base";
|
||||
@import "tailwindcss/components";
|
||||
@import "tailwindcss/utilities";
|
15
src/util/assertions.ts
Normal file
|
@ -0,0 +1,15 @@
|
|||
export function VERIFY(condition: boolean, message?: string): asserts condition {
|
||||
if (!condition) throw new Error(`VERIFY: ${message ?? 'Condition was not met.'}`);
|
||||
}
|
||||
|
||||
export function VERIFY_NOT_REACHED(message?: string): void {
|
||||
if (message !== undefined) throw new Error(`VERIFY_NOT_REACHED: ${message}`);
|
||||
|
||||
throw new Error('VERIFY_NOT_REACHED');
|
||||
}
|
||||
|
||||
export function TODO(message?: string): void {
|
||||
if (message !== undefined) throw new Error(`TODO: ${message}`);
|
||||
|
||||
throw new Error('TODO');
|
||||
}
|
2
src/util/guards.ts
Normal file
|
@ -0,0 +1,2 @@
|
|||
export type NonEmptyArray<T> = [T, ...T[]];
|
||||
export type Constructor<T> = new(...args: Array<any>) => T;
|
89
src/view.ts
Normal file
|
@ -0,0 +1,89 @@
|
|||
import { Color } from './html/highlighter/properties/color.js';
|
||||
import { Property } from './html/highlighter/property.js';
|
||||
import { Span } from './html/highlighter/span.js';
|
||||
import { Inspector } from './html/inspector.js';
|
||||
|
||||
const sameProperties = (a: Array<Property> | undefined, b: Array<Property> | undefined): boolean => {
|
||||
if (a === undefined || b === undefined) return false;
|
||||
if (a.length !== b.length) return false;
|
||||
|
||||
for (const property of a) {
|
||||
let found = false;
|
||||
|
||||
for (const otherProperty of b)
|
||||
if (property.equals(otherProperty))
|
||||
found = true;
|
||||
|
||||
if (!found) return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
const applyProperties = (element: HTMLSpanElement, properties: Array<Property>): void => {
|
||||
for (const property of properties)
|
||||
property.apply(element);
|
||||
};
|
||||
|
||||
export function render(text: string, spans: Array<Span>, inspector: Inspector): void {
|
||||
console.time('render');
|
||||
|
||||
const container = document.createElement('pre');
|
||||
container.ariaHidden = 'true';
|
||||
|
||||
for (const child of document.body.children) {
|
||||
(child as HTMLElement).style.display = 'none';
|
||||
child.ariaHidden = 'false';
|
||||
}
|
||||
|
||||
document.body.appendChild(container);
|
||||
|
||||
const defaultProperties: Array<Property> = [ Color.Plain ];
|
||||
const defaultTagName: keyof HTMLElementTagNameMap = 'span';
|
||||
|
||||
let lastProperties: Array<Property> = defaultProperties;
|
||||
let lastTagName: keyof HTMLElementTagNameMap = defaultTagName;
|
||||
let lastElement: HTMLSpanElement = document.createElement(lastTagName);
|
||||
|
||||
applyProperties(lastElement, lastProperties);
|
||||
|
||||
container.appendChild(lastElement);
|
||||
|
||||
for (let characterIndex = 0; characterIndex < text.length; characterIndex++) {
|
||||
const character = text[characterIndex];
|
||||
|
||||
let topMostProperties: Array<Property> = defaultProperties;
|
||||
let topMostTagName: keyof HTMLElementTagNameMap = defaultTagName;
|
||||
|
||||
const matchingSpans = new Array<Span>();
|
||||
|
||||
for (const span of spans) {
|
||||
if (span.contains(characterIndex)) {
|
||||
matchingSpans.push(span);
|
||||
|
||||
topMostProperties = span.properties;
|
||||
topMostTagName = span.tagName;
|
||||
}
|
||||
}
|
||||
|
||||
if (sameProperties(lastProperties, topMostProperties) && topMostTagName === lastTagName) {
|
||||
lastElement.textContent += character;
|
||||
|
||||
inspector.instrument(lastElement, matchingSpans);
|
||||
} else {
|
||||
lastElement = document.createElement(topMostTagName);
|
||||
lastElement.textContent = character;
|
||||
|
||||
inspector.instrument(lastElement, matchingSpans);
|
||||
|
||||
applyProperties(lastElement, topMostProperties);
|
||||
|
||||
lastProperties = topMostProperties;
|
||||
lastTagName = topMostTagName;
|
||||
|
||||
container.appendChild(lastElement);
|
||||
}
|
||||
}
|
||||
|
||||
console.timeEnd('render');
|
||||
}
|
|
@ -1,19 +1,15 @@
|
|||
{
|
||||
"$schema": "https://json.schemastore.org/tsconfig",
|
||||
"compilerOptions": {
|
||||
"target": "es6",
|
||||
"lib": ["dom", "dom.iterable", "esnext"],
|
||||
"allowJs": true,
|
||||
"skipLibCheck": true,
|
||||
"strict": false,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"noEmit": true,
|
||||
"esModuleInterop": true,
|
||||
"module": "esnext",
|
||||
"moduleResolution": "node",
|
||||
"resolveJsonModule": true,
|
||||
"isolatedModules": true,
|
||||
"jsx": "preserve"
|
||||
},
|
||||
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx"],
|
||||
"exclude": ["**/node_modules"]
|
||||
"target": "esnext",
|
||||
"sourceMap": true,
|
||||
"rootDir": "src",
|
||||
"outDir": "public/script",
|
||||
"strict": true,
|
||||
"experimentalDecorators": true,
|
||||
"emitDecoratorMetadata": true,
|
||||
"noImplicitAny": true,
|
||||
"noImplicitOverride": true
|
||||
}
|
||||
}
|
||||
|
|