├── .eslintrc.json
├── .gitignore
├── .prettierignore
├── .vscode
├── launch.json
└── settings.json
├── LICENSE
├── README.md
├── name.svg
├── package.json
├── prettier.config.js
├── public
├── 404.html
├── CNAME
├── favicon.png
├── flag.svg
├── index.html
├── logo.png
└── manifest.json
├── src
├── assets
│ └── logo.svg
├── components
│ ├── button
│ │ ├── Button.module.css
│ │ ├── Button.tsx
│ │ └── index.ts
│ ├── card
│ │ ├── Card.module.css
│ │ ├── Card.tsx
│ │ └── index.ts
│ ├── github
│ │ ├── GitHubLogo.tsx
│ │ └── index.ts
│ └── preview-item
│ │ ├── PreviewItem.module.css
│ │ ├── PreviewItem.tsx
│ │ └── index.ts
├── index.css
├── index.tsx
├── react-app-env.d.ts
├── serviceWorker.ts
├── utils
│ ├── converter.test.ts
│ ├── converter.ts
│ └── decorate.ts
└── views
│ ├── App.module.css
│ ├── App.test.tsx
│ ├── App.tsx
│ ├── home
│ ├── Home.module.css
│ ├── Home.tsx
│ └── index.ts
│ ├── reactive-editor
│ ├── ReactiveEditor.module.css
│ ├── ReactiveEditor.tsx
│ └── index.ts
│ ├── select-decorator
│ ├── SelectDecorator.module.css
│ ├── SelectDecorator.tsx
│ └── index.ts
│ └── select-mapper
│ ├── SelectMapper.module.css
│ ├── SelectMapper.tsx
│ └── index.ts
├── tsconfig.json
└── yarn.lock
/.eslintrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "react-app"
3 | }
4 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
2 |
3 | # dependencies
4 | /node_modules
5 | /.pnp
6 | .pnp.js
7 |
8 | # testing
9 | /coverage
10 |
11 | # production
12 | /build
13 |
14 | # misc
15 | .DS_Store
16 | .env.local
17 | .env.development.local
18 | .env.test.local
19 | .env.production.local
20 |
21 | npm-debug.log*
22 | yarn-debug.log*
23 | yarn-error.log*
24 |
--------------------------------------------------------------------------------
/.prettierignore:
--------------------------------------------------------------------------------
1 | build/**
--------------------------------------------------------------------------------
/.vscode/launch.json:
--------------------------------------------------------------------------------
1 | {
2 | "version": "0.2.0",
3 | "configurations": [
4 | {
5 | "name": "Web",
6 | "type": "chrome",
7 | "request": "launch",
8 | "url": "http://localhost:3000",
9 | "webRoot": "${workspaceFolder}/web/src",
10 | "sourceMapPathOverrides": {
11 | "webpack:///src/*": "${webRoot}/*"
12 | }
13 | }
14 | ]
15 | }
16 |
--------------------------------------------------------------------------------
/.vscode/settings.json:
--------------------------------------------------------------------------------
1 | {
2 | "editor.formatOnSave": true,
3 | "eslint.validate": [
4 | "javascript",
5 | "javascriptreact",
6 | { "language": "typescript", "autoFix": true },
7 | { "language": "typescriptreact", "autoFix": true }
8 | ]
9 | }
10 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2019 Hardo
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | #
2 |
3 | Transform text into mathematical alphanumeric 𝗌𝗒𝗆𝖻𝗈𝗅𝗌.
4 |
5 |
6 |
--------------------------------------------------------------------------------
/name.svg:
--------------------------------------------------------------------------------
1 |
2 |
14 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "symbols",
3 | "version": "0.1.0",
4 | "homepage": "https://symbols.hardo.me",
5 | "scripts": {
6 | "start": "react-scripts start",
7 | "build": "react-scripts build",
8 | "test": "react-scripts test",
9 | "eject": "react-scripts eject",
10 | "lint": "prettier --list-different \"src/**/*.{js,jsx,ts,tsx,json,css,scss,md}\"",
11 | "format": "prettier --write \"src/**/*.{js,jsx,ts,tsx,json,css,scss,md}\"",
12 | "predeploy": "npm run build",
13 | "deploy": "gh-pages -d build"
14 | },
15 | "dependencies": {
16 | "classnames": "^2.2.6",
17 | "react": "^16.8.6",
18 | "react-clipboard.js": "^2.0.7",
19 | "react-dom": "^16.8.6",
20 | "react-router-dom": "^5.0.0"
21 | },
22 | "devDependencies": {
23 | "@types/classnames": "^2.2.7",
24 | "@types/jest": "24.0.13",
25 | "@types/node": "12.0.2",
26 | "@types/react": "16.8.18",
27 | "@types/react-dom": "16.8.4",
28 | "@types/react-router-dom": "^4.3.3",
29 | "gh-pages": "^2.0.1",
30 | "husky": "^2.3.0",
31 | "lint-staged": "^8.1.7",
32 | "prettier": "^1.17.1",
33 | "react-scripts": "3.0.1",
34 | "typescript": "3.4.5"
35 | },
36 | "eslintConfig": {
37 | "extends": "react-app"
38 | },
39 | "browserslist": {
40 | "production": [
41 | ">0.2%",
42 | "not dead",
43 | "not op_mini all"
44 | ],
45 | "development": [
46 | "last 1 chrome version",
47 | "last 1 firefox version",
48 | "last 1 safari version"
49 | ]
50 | },
51 | "husky": {
52 | "hooks": {
53 | "pre-commit": "lint-staged"
54 | }
55 | },
56 | "lint-staged": {
57 | "src/**/*.{js,jsx,ts,tsx,json,css,scss,md}": [
58 | "prettier --write",
59 | "git add"
60 | ]
61 | }
62 | }
63 |
--------------------------------------------------------------------------------
/prettier.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | printWidth: 80,
3 | tabWidth: 2,
4 | useTabs: false,
5 | semi: true,
6 | singleQuote: true,
7 | trailingComma: 'all',
8 | bracketSpacing: true,
9 | };
10 |
--------------------------------------------------------------------------------
/public/404.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Single Page Apps for GitHub Pages
6 |
47 |
48 |
49 |
50 |
--------------------------------------------------------------------------------
/public/CNAME:
--------------------------------------------------------------------------------
1 | symbols.hardo.me
--------------------------------------------------------------------------------
/public/favicon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/noobnooc/symbols/09080fc9a5deca477156cfb13ecb9d6ccb32e984/public/favicon.png
--------------------------------------------------------------------------------
/public/flag.svg:
--------------------------------------------------------------------------------
1 |
2 |
33 |
--------------------------------------------------------------------------------
/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
12 |
13 |
22 |
23 | 𝒮𝓎𝓂𝒷ℴ𝓁𝓈 - Transform text into mathematical alphanumeric 𝗌𝗒𝗆𝖻𝗈𝗅𝗌.
24 |
25 |
26 |
27 |
65 |
66 |
67 |
68 |
69 |
72 |

77 |
84 |
85 |
86 |
96 |
97 |
98 |
--------------------------------------------------------------------------------
/public/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/noobnooc/symbols/09080fc9a5deca477156cfb13ecb9d6ccb32e984/public/logo.png
--------------------------------------------------------------------------------
/public/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "short_name": "𝒮𝓎𝓂𝒷ℴ𝓁𝓈",
3 | "name": "Transform text into mathematical alphanumeric 𝗌𝗒𝗆𝖻𝗈𝗅𝗌.",
4 | "icons": [
5 | {
6 | "src": "logo.png",
7 | "sizes": "64x64 32x32 24x24 16x16",
8 | "type": "image/x-icon"
9 | }
10 | ],
11 | "start_url": ".",
12 | "display": "standalone",
13 | "theme_color": "#ff4757",
14 | "background_color": "#dfe4ea"
15 | }
16 |
--------------------------------------------------------------------------------
/src/assets/logo.svg:
--------------------------------------------------------------------------------
1 |
2 |
14 |
--------------------------------------------------------------------------------
/src/components/button/Button.module.css:
--------------------------------------------------------------------------------
1 | .button {
2 | color: var(--color-text);
3 | background-color: var(--color-secondary);
4 | padding: var(--dimen-medium) var(--dimen-large);
5 | border-radius: var(--dimen-small);
6 | box-shadow: var(--shadow-light);
7 | border: 0;
8 | outline: none;
9 | cursor: pointer;
10 | transition: background-color 0.3s, box-shadow 0.3s;
11 | }
12 |
13 | .button:hover {
14 | background-color: var(--color-secondary-dark);
15 | box-shadow: var(--shadow-dark);
16 | }
17 |
--------------------------------------------------------------------------------
/src/components/button/Button.tsx:
--------------------------------------------------------------------------------
1 | import React, { FC, MouseEvent } from 'react';
2 | import classNames from 'classnames';
3 |
4 | import styles from './Button.module.css';
5 |
6 | export type ButtonProps = {
7 | className?: string;
8 | onClick?(event: MouseEvent): void;
9 | };
10 |
11 | export const Button: FC = ({ className, children, onClick }) => {
12 | return (
13 |
16 | );
17 | };
18 |
--------------------------------------------------------------------------------
/src/components/button/index.ts:
--------------------------------------------------------------------------------
1 | export * from './Button';
2 |
--------------------------------------------------------------------------------
/src/components/card/Card.module.css:
--------------------------------------------------------------------------------
1 | .card {
2 | box-shadow: var(--shadow-light);
3 | border-radius: var(--radius-medium);
4 | padding: var(--dimen-medium);
5 | }
6 |
--------------------------------------------------------------------------------
/src/components/card/Card.tsx:
--------------------------------------------------------------------------------
1 | import React, { FC } from 'react';
2 | import classNames from 'classnames';
3 |
4 | import styles from './Card.module.css';
5 |
6 | export type CardProps = {
7 | className?: string;
8 | };
9 |
10 | export const Card: FC = ({ className, children }) => {
11 | return {children}
;
12 | };
13 |
--------------------------------------------------------------------------------
/src/components/card/index.ts:
--------------------------------------------------------------------------------
1 | export * from './Card';
2 |
--------------------------------------------------------------------------------
/src/components/github/GitHubLogo.tsx:
--------------------------------------------------------------------------------
1 | import React, { FC } from 'react';
2 |
3 | export type GitHubLogoProps = {
4 | className?: string;
5 | };
6 |
7 | export const GitHubLogo: FC = ({ className }) => {
8 | return (
9 |
19 | );
20 | };
21 |
--------------------------------------------------------------------------------
/src/components/github/index.ts:
--------------------------------------------------------------------------------
1 | export * from './GitHubLogo';
2 |
--------------------------------------------------------------------------------
/src/components/preview-item/PreviewItem.module.css:
--------------------------------------------------------------------------------
1 | .previewItem {
2 | padding: var(--dimen-medium) var(--dimen-large);
3 | border-radius: var(--dimen-small);
4 | border-left: solid 3px var(--color-primary);
5 | box-shadow: var(--shadow-light);
6 | cursor: pointer;
7 | transition: box-shadow 0.3s;
8 | display: flex;
9 | justify-content: space-between;
10 | align-items: center;
11 | }
12 |
13 | .previewItem:hover {
14 | box-shadow: var(--shadow-dark);
15 | }
16 |
--------------------------------------------------------------------------------
/src/components/preview-item/PreviewItem.tsx:
--------------------------------------------------------------------------------
1 | import React, { FC, MouseEvent } from 'react';
2 | import classNames from 'classnames';
3 |
4 | import styles from './PreviewItem.module.css';
5 |
6 | export type PreviewItemProps = {
7 | className?: string;
8 | onClick?(event: MouseEvent): void;
9 | };
10 |
11 | export const PreviewItem: FC = ({
12 | className,
13 | children,
14 | onClick,
15 | }) => {
16 | return (
17 |
21 | {children}
22 |
23 | );
24 | };
25 |
--------------------------------------------------------------------------------
/src/components/preview-item/index.ts:
--------------------------------------------------------------------------------
1 | export * from './PreviewItem';
2 |
--------------------------------------------------------------------------------
/src/index.css:
--------------------------------------------------------------------------------
1 | :root {
2 | --color-primary: #ff4757;
3 | --color-primary-dark: #c5002e;
4 | --color-primary-light: #ff7d84;
5 | --color-secondary: #70a1ff;
6 | --color-secondary-dark: #3473cb;
7 | --color-secondary-light: #a6d2ff;
8 | --color-text: #ffffff;
9 | --color-icon: #ffffff;
10 | --color-text-primary: #212121;
11 | --color-text-secondary: #757575;
12 | --color-divider: #bdbdbd;
13 | --color-error: #c5002e;
14 | --color-success: #2ed573;
15 | --color-info: #1e90ff;
16 | --color-warning: #ffa502;
17 | --color-white: #ffffff;
18 | --color-black: #000000;
19 |
20 | --shadow-dark: 0 2px 4px 0 rgba(47, 53, 66, 0.1),
21 | 0 0 10px 0 rgba(87, 96, 111, 0.3);
22 | --shadow-light: 0 2px 4px 0 rgba(47, 53, 66, 0.1),
23 | 0 0 10px 0 rgba(87, 96, 111, 0.15);
24 |
25 | --radius-huge: 20px;
26 | --radius-large: 10px;
27 | --radius-medium: 5px;
28 | --radius-small: 2px;
29 |
30 | --dimen-huge: 50px;
31 | --dimen-large: 20px;
32 | --dimen-medium: 10px;
33 | --dimen-small: 5px;
34 | }
35 |
36 | * {
37 | box-sizing: border-box;
38 | }
39 |
40 | html,
41 | body,
42 | #root {
43 | height: 100%;
44 | }
45 |
46 | body {
47 | margin: 0;
48 | font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
49 | 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
50 | sans-serif, PingFangSC-Light, 'Microsoft YaHei UI', 'Microsoft YaHei';
51 | -webkit-font-smoothing: antialiased;
52 | -moz-osx-font-smoothing: grayscale;
53 | color: var(--color-text-primary);
54 | }
55 |
56 | code {
57 | font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New',
58 | monospace;
59 | }
60 |
--------------------------------------------------------------------------------
/src/index.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import ReactDOM from 'react-dom';
3 |
4 | import App from 'views/App';
5 |
6 | import 'index.css';
7 | import * as serviceWorker from 'serviceWorker';
8 |
9 | ReactDOM.render(, document.getElementById('root'));
10 |
11 | // If you want your app to work offline and load faster, you can change
12 | // unregister() to register() below. Note this comes with some pitfalls.
13 | // Learn more about service workers: https://bit.ly/CRA-PWA
14 | serviceWorker.register();
15 |
--------------------------------------------------------------------------------
/src/react-app-env.d.ts:
--------------------------------------------------------------------------------
1 | ///
2 |
--------------------------------------------------------------------------------
/src/serviceWorker.ts:
--------------------------------------------------------------------------------
1 | // This optional code is used to register a service worker.
2 | // register() is not called by default.
3 |
4 | // This lets the app load faster on subsequent visits in production, and gives
5 | // it offline capabilities. However, it also means that developers (and users)
6 | // will only see deployed updates on subsequent visits to a page, after all the
7 | // existing tabs open on the page have been closed, since previously cached
8 | // resources are updated in the background.
9 |
10 | // To learn more about the benefits of this model and instructions on how to
11 | // opt-in, read https://bit.ly/CRA-PWA
12 |
13 | const isLocalhost = Boolean(
14 | window.location.hostname === 'localhost' ||
15 | // [::1] is the IPv6 localhost address.
16 | window.location.hostname === '[::1]' ||
17 | // 127.0.0.1/8 is considered localhost for IPv4.
18 | window.location.hostname.match(
19 | /^127(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/,
20 | ),
21 | );
22 |
23 | type Config = {
24 | onSuccess?: (registration: ServiceWorkerRegistration) => void;
25 | onUpdate?: (registration: ServiceWorkerRegistration) => void;
26 | };
27 |
28 | export function register(config?: Config) {
29 | if (process.env.NODE_ENV === 'production' && 'serviceWorker' in navigator) {
30 | // The URL constructor is available in all browsers that support SW.
31 | const publicUrl = new URL(
32 | (process as { env: { [key: string]: string } }).env.PUBLIC_URL,
33 | window.location.href,
34 | );
35 | if (publicUrl.origin !== window.location.origin) {
36 | // Our service worker won't work if PUBLIC_URL is on a different origin
37 | // from what our page is served on. This might happen if a CDN is used to
38 | // serve assets; see https://github.com/facebook/create-react-app/issues/2374
39 | return;
40 | }
41 |
42 | window.addEventListener('load', () => {
43 | const swUrl = `${process.env.PUBLIC_URL}/service-worker.js`;
44 |
45 | if (isLocalhost) {
46 | // This is running on localhost. Let's check if a service worker still exists or not.
47 | checkValidServiceWorker(swUrl, config);
48 |
49 | // Add some additional logging to localhost, pointing developers to the
50 | // service worker/PWA documentation.
51 | navigator.serviceWorker.ready.then(() => {
52 | console.log(
53 | 'This web app is being served cache-first by a service ' +
54 | 'worker. To learn more, visit https://bit.ly/CRA-PWA',
55 | );
56 | });
57 | } else {
58 | // Is not localhost. Just register service worker
59 | registerValidSW(swUrl, config);
60 | }
61 | });
62 | }
63 | }
64 |
65 | function registerValidSW(swUrl: string, config?: Config) {
66 | navigator.serviceWorker
67 | .register(swUrl)
68 | .then(registration => {
69 | registration.onupdatefound = () => {
70 | const installingWorker = registration.installing;
71 | if (installingWorker == null) {
72 | return;
73 | }
74 | installingWorker.onstatechange = () => {
75 | if (installingWorker.state === 'installed') {
76 | if (navigator.serviceWorker.controller) {
77 | // At this point, the updated precached content has been fetched,
78 | // but the previous service worker will still serve the older
79 | // content until all client tabs are closed.
80 | console.log(
81 | 'New content is available and will be used when all ' +
82 | 'tabs for this page are closed. See https://bit.ly/CRA-PWA.',
83 | );
84 |
85 | // Execute callback
86 | if (config && config.onUpdate) {
87 | config.onUpdate(registration);
88 | }
89 | } else {
90 | // At this point, everything has been precached.
91 | // It's the perfect time to display a
92 | // "Content is cached for offline use." message.
93 | console.log('Content is cached for offline use.');
94 |
95 | // Execute callback
96 | if (config && config.onSuccess) {
97 | config.onSuccess(registration);
98 | }
99 | }
100 | }
101 | };
102 | };
103 | })
104 | .catch(error => {
105 | console.error('Error during service worker registration:', error);
106 | });
107 | }
108 |
109 | function checkValidServiceWorker(swUrl: string, config?: Config) {
110 | // Check if the service worker can be found. If it can't reload the page.
111 | fetch(swUrl)
112 | .then(response => {
113 | // Ensure service worker exists, and that we really are getting a JS file.
114 | const contentType = response.headers.get('content-type');
115 | if (
116 | response.status === 404 ||
117 | (contentType != null && contentType.indexOf('javascript') === -1)
118 | ) {
119 | // No service worker found. Probably a different app. Reload the page.
120 | navigator.serviceWorker.ready.then(registration => {
121 | registration.unregister().then(() => {
122 | window.location.reload();
123 | });
124 | });
125 | } else {
126 | // Service worker found. Proceed as normal.
127 | registerValidSW(swUrl, config);
128 | }
129 | })
130 | .catch(() => {
131 | console.log(
132 | 'No internet connection found. App is running in offline mode.',
133 | );
134 | });
135 | }
136 |
137 | export function unregister() {
138 | if ('serviceWorker' in navigator) {
139 | navigator.serviceWorker.ready.then(registration => {
140 | registration.unregister();
141 | });
142 | }
143 | }
144 |
--------------------------------------------------------------------------------
/src/utils/converter.test.ts:
--------------------------------------------------------------------------------
1 | import { SymbolMapperType, convertToSymbols } from './converter';
2 |
3 | const TEST_TEXT =
4 | 'If you shed tears when you miss the sun, you also miss the stars.';
5 |
6 | const TEST_CASE_MAP = new Map([
7 | [
8 | 'circled',
9 | 'Ⓘⓕ ⓨⓞⓤ ⓢⓗⓔⓓ ⓣⓔⓐⓡⓢ ⓦⓗⓔⓝ ⓨⓞⓤ ⓜⓘⓢⓢ ⓣⓗⓔ ⓢⓤⓝ, ⓨⓞⓤ ⓐⓛⓢⓞ ⓜⓘⓢⓢ ⓣⓗⓔ ⓢⓣⓐⓡⓢ.',
10 | ],
11 | [
12 | 'double-struck',
13 | '𝕀𝕗 𝕪𝕠𝕦 𝕤𝕙𝕖𝕕 𝕥𝕖𝕒𝕣𝕤 𝕨𝕙𝕖𝕟 𝕪𝕠𝕦 𝕞𝕚𝕤𝕤 𝕥𝕙𝕖 𝕤𝕦𝕟, 𝕪𝕠𝕦 𝕒𝕝𝕤𝕠 𝕞𝕚𝕤𝕤 𝕥𝕙𝕖 𝕤𝕥𝕒𝕣𝕤.',
14 | ],
15 | [
16 | 'full-width',
17 | 'If you shed tears when you miss the sun, you also miss the stars.',
18 | ],
19 | [
20 | 'mono-space',
21 | '𝙸𝚏 𝚢𝚘𝚞 𝚜𝚑𝚎𝚍 𝚝𝚎𝚊𝚛𝚜 𝚠𝚑𝚎𝚗 𝚢𝚘𝚞 𝚖𝚒𝚜𝚜 𝚝𝚑𝚎 𝚜𝚞𝚗, 𝚢𝚘𝚞 𝚊𝚕𝚜𝚘 𝚖𝚒𝚜𝚜 𝚝𝚑𝚎 𝚜𝚝𝚊𝚛𝚜.',
22 | ],
23 | [
24 | 'fraktur',
25 | 'ℑ𝔣 𝔶𝔬𝔲 𝔰𝔥𝔢𝔡 𝔱𝔢𝔞𝔯𝔰 𝔴𝔥𝔢𝔫 𝔶𝔬𝔲 𝔪𝔦𝔰𝔰 𝔱𝔥𝔢 𝔰𝔲𝔫, 𝔶𝔬𝔲 𝔞𝔩𝔰𝔬 𝔪𝔦𝔰𝔰 𝔱𝔥𝔢 𝔰𝔱𝔞𝔯𝔰.',
26 | ],
27 | [
28 | 'script',
29 | 'ℐ𝒻 𝓎ℴ𝓊 𝓈𝒽ℯ𝒹 𝓉ℯ𝒶𝓇𝓈 𝓌𝒽ℯ𝓃 𝓎ℴ𝓊 𝓂𝒾𝓈𝓈 𝓉𝒽ℯ 𝓈𝓊𝓃, 𝓎ℴ𝓊 𝒶𝓁𝓈ℴ 𝓂𝒾𝓈𝓈 𝓉𝒽ℯ 𝓈𝓉𝒶𝓇𝓈.',
30 | ],
31 | ]);
32 |
33 | it('convert text to symbols', () => {
34 | for (let [type, result] of TEST_CASE_MAP) {
35 | let convertedText = convertToSymbols(TEST_TEXT, type);
36 |
37 | expect(convertedText).toBe(result);
38 | }
39 | });
40 |
--------------------------------------------------------------------------------
/src/utils/converter.ts:
--------------------------------------------------------------------------------
1 | const UPPER_CASE_RANGE_START = 'A'.charCodeAt(0);
2 | const UPPER_CASE_RANGE_END = 'Z'.charCodeAt(0);
3 |
4 | const LOWER_CASE_RANGE_START = 'a'.charCodeAt(0);
5 | const LOWER_CASE_RANGE_END = 'z'.charCodeAt(0);
6 |
7 | const NUMBER_RANGE_START = '0'.charCodeAt(0);
8 | const NUMBER_RANGE_END = '9'.charCodeAt(0);
9 |
10 | export type SymbolMapperType =
11 | | 'normal'
12 | | 'circled'
13 | | 'circled-negative'
14 | | 'squared'
15 | | 'squared-negative'
16 | | 'full-width'
17 | | 'script'
18 | | 'script-bold'
19 | | 'fraktur'
20 | | 'fraktur-bold'
21 | | 'double-struck'
22 | | 'mono-space'
23 | | 'sans-serif'
24 | | 'sans-serif-bold'
25 | | 'sans-serif-italic'
26 | | 'sans-serif-bold-italic'
27 | | 'serif-bold'
28 | | 'serif-italic'
29 | | 'serif-bold-italic'
30 | | 'capitalized';
31 |
32 | export const symbolMapperMap: Map = new Map([
33 | ['normal', ''],
34 | ['circled', 'ⒶⒷⒸⒹⒺⒻⒼⒽⒾⒿⓀⓁⓂⓃⓄⓅⓆⓇⓈⓉⓊⓋⓌⓍⓎⓏⓐⓑⓒⓓⓔⓕⓖⓗⓘⓙⓚⓛⓜⓝⓞⓟⓠⓡⓢⓣⓤⓥⓦⓧⓨⓩ⓪①②③④⑤⑥⑦⑧⑨'],
35 | ['circled-negative', '🅐🅑🅒🅓🅔🅕🅖🅗🅘🅙🅚🅛🅜🅝🅞🅟🅠🅡🅢🅣🅤🅥🅦🅧🅨🅩🅐🅑🅒🅓🅔🅕🅖🅗🅘🅙🅚🅛🅜🅝🅞🅟🅠🅡🅢🅣🅤🅥🅦🅧🅨🅩'],
36 | ['squared', '🄰🄱🄲🄳🄴🄵🄶🄷🄸🄹🄺🄻🄼🄽🄾🄿🅀🅁🅂🅃🅄🅅🅆🅇🅈🅉🄰🄱🄲🄳🄴🄵🄶🄷🄸🄹🄺🄻🄼🄽🄾🄿🅀🅁🅂🅃🅄🅅🅆🅇🅈🅉'],
37 | ['squared-negative', '🅰🅱🅲🅳🅴🅵🅶🅷🅸🅹🅺🅻🅼🅽🅾🅿🆀🆁🆂🆃🆄🆅🆆🆇🆈🆉🅰🅱🅲🅳🅴🅵🅶🅷🅸🅹🅺🅻🅼🅽🅾🅿🆀🆁🆂🆃🆄🆅🆆🆇🆈🆉'],
38 | [
39 | 'full-width',
40 | 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789',
41 | ],
42 | ['capitalized', 'ABCDEFGHIJKLMNOPQRSTUVWXYZᴀʙᴄᴅᴇꜰɢʜɪᴊᴋʟᴍɴᴏᴘqʀꜱᴛᴜᴠᴡxʏᴢ'],
43 | ['script', '𝒜ℬ𝒞𝒟ℰℱ𝒢ℋℐ𝒥𝒦ℒℳ𝒩𝒪𝒫𝒬ℛ𝒮𝒯𝒰𝒱𝒲𝒳𝒴𝒵𝒶𝒷𝒸𝒹ℯ𝒻ℊ𝒽𝒾𝒿𝓀𝓁𝓂𝓃ℴ𝓅𝓆𝓇𝓈𝓉𝓊𝓋𝓌𝓍𝓎𝓏'],
44 | ['script-bold', '𝓐𝓑𝓒𝓓𝓔𝓕𝓖𝓗𝓘𝓙𝓚𝓛𝓜𝓝𝓞𝓟𝓠𝓡𝓢𝓣𝓤𝓥𝓦𝓧𝓨𝓩𝓪𝓫𝓬𝓭𝓮𝓯𝓰𝓱𝓲𝓳𝓴𝓵𝓶𝓷𝓸𝓹𝓺𝓻𝓼𝓽𝓾𝓿𝔀𝔁𝔂𝔃'],
45 | ['fraktur', '𝔄𝔅ℭ𝔇𝔈𝔉𝔊ℌℑ𝔍𝔎𝔏𝔐𝔑𝔒𝔓𝔔ℜ𝔖𝔗𝔘𝔙𝔚𝔛𝔜ℨ𝔞𝔟𝔠𝔡𝔢𝔣𝔤𝔥𝔦𝔧𝔨𝔩𝔪𝔫𝔬𝔭𝔮𝔯𝔰𝔱𝔲𝔳𝔴𝔵𝔶𝔷'],
46 | ['fraktur-bold', '𝕬𝕭𝕮𝕯𝕰𝕱𝕲𝕳𝕴𝕵𝕶𝕷𝕸𝕹𝕺𝕻𝕼𝕽𝕾𝕿𝖀𝖁𝖂𝖃𝖄𝖅𝖆𝖇𝖈𝖉𝖊𝖋𝖌𝖍𝖎𝖏𝖐𝖑𝖒𝖓𝖔𝖕𝖖𝖗𝖘𝖙𝖚𝖛𝖜𝖝𝖞𝖟'],
47 | [
48 | 'double-struck',
49 | '𝔸𝔹ℂ𝔻𝔼𝔽𝔾ℍ𝕀𝕁𝕂𝕃𝕄ℕ𝕆ℙℚℝ𝕊𝕋𝕌𝕍𝕎𝕏𝕐ℤ𝕒𝕓𝕔𝕕𝕖𝕗𝕘𝕙𝕚𝕛𝕜𝕝𝕞𝕟𝕠𝕡𝕢𝕣𝕤𝕥𝕦𝕧𝕨𝕩𝕪𝕫𝟘𝟙𝟚𝟛𝟜𝟝𝟞𝟟𝟠𝟡',
50 | ],
51 | [
52 | 'mono-space',
53 | '𝙰𝙱𝙲𝙳𝙴𝙵𝙶𝙷𝙸𝙹𝙺𝙻𝙼𝙽𝙾𝙿𝚀𝚁𝚂𝚃𝚄𝚅𝚆𝚇𝚈𝚉𝚊𝚋𝚌𝚍𝚎𝚏𝚐𝚑𝚒𝚓𝚔𝚕𝚖𝚗𝚘𝚙𝚚𝚛𝚜𝚝𝚞𝚟𝚠𝚡𝚢𝚣𝟶𝟷𝟸𝟹𝟺𝟻𝟼𝟽𝟾𝟿',
54 | ],
55 | [
56 | 'sans-serif',
57 | '𝖠𝖡𝖢𝖣𝖤𝖥𝖦𝖧𝖨𝖩𝖪𝖫𝖬𝖭𝖮𝖯𝖰𝖱𝖲𝖳𝖴𝖵𝖶𝖷𝖸𝖹𝖺𝖻𝖼𝖽𝖾𝖿𝗀𝗁𝗂𝗃𝗄𝗅𝗆𝗇𝗈𝗉𝗊𝗋𝗌𝗍𝗎𝗏𝗐𝗑𝗒𝗓𝟢𝟣𝟤𝟥𝟦𝟧𝟨𝟩𝟪𝟫',
58 | ],
59 | [
60 | 'sans-serif-bold',
61 | '𝗔𝗕𝗖𝗗𝗘𝗙𝗚𝗛𝗜𝗝𝗞𝗟𝗠𝗡𝗢𝗣𝗤𝗥𝗦𝗧𝗨𝗩𝗪𝗫𝗬𝗭𝗮𝗯𝗰𝗱𝗲𝗳𝗴𝗵𝗶𝗷𝗸𝗹𝗺𝗻𝗼𝗽𝗾𝗿𝘀𝘁𝘂𝘃𝘄𝘅𝘆𝘇𝟬𝟭𝟮𝟯𝟰𝟱𝟲𝟳𝟴𝟵',
62 | ],
63 | ['sans-serif-italic', '𝘈𝘉𝘊𝘋𝘌𝘍𝘎𝘏𝘐𝘑𝘒𝘓𝘔𝘕𝘖𝘗𝘘𝘙𝘚𝘛𝘜𝘝𝘞𝘟𝘠𝘡𝘢𝘣𝘤𝘥𝘦𝘧𝘨𝘩𝘪𝘫𝘬𝘭𝘮𝘯𝘰𝘱𝘲𝘳𝘴𝘵𝘶𝘷𝘸𝘹𝘺𝘻'],
64 | [
65 | 'sans-serif-bold-italic',
66 | '𝘼𝘽𝘾𝘿𝙀𝙁𝙂𝙃𝙄𝙅𝙆𝙇𝙈𝙉𝙊𝙋𝙌𝙍𝙎𝙏𝙐𝙑𝙒𝙓𝙔𝙕𝙖𝙗𝙘𝙙𝙚𝙛𝙜𝙝𝙞𝙟𝙠𝙡𝙢𝙣𝙤𝙥𝙦𝙧𝙨𝙩𝙪𝙫𝙬𝙭𝙮𝙯',
67 | ],
68 | [
69 | 'serif-bold',
70 | '𝐀𝐁𝐂𝐃𝐄𝐅𝐆𝐇𝐈𝐉𝐊𝐋𝐌𝐍𝐎𝐏𝐐𝐑𝐒𝐓𝐔𝐕𝐖𝐗𝐘𝐙𝐚𝐛𝐜𝐝𝐞𝐟𝐠𝐡𝐢𝐣𝐤𝐥𝐦𝐧𝐨𝐩𝐪𝐫𝐬𝐭𝐮𝐯𝐰𝐱𝐲𝐳𝟎𝟏𝟐𝟑𝟒𝟓𝟔𝟕𝟖𝟗',
71 | ],
72 | ['serif-italic', '𝐴𝐵𝐶𝐷𝐸𝐹𝐺𝐻𝐼𝐽𝐾𝐿𝑀𝑁𝑂𝑃𝑄𝑅𝑆𝑇𝑈𝑉𝑊𝑋𝑌𝑍𝑎𝑏𝑐𝑑𝑒𝑓𝑔ℎ𝑖𝑗𝑘𝑙𝑚𝑛𝑜𝑝𝑞𝑟𝑠𝑡𝑢𝑣𝑤𝑥𝑦𝑧'],
73 | ['serif-bold-italic', '𝑨𝑩𝑪𝑫𝑬𝑭𝑮𝑯𝑰𝑱𝑲𝑳𝑴𝑵𝑶𝑷𝑸𝑹𝑺𝑻𝑼𝑽𝑾𝑿𝒀𝒁𝒂𝒃𝒄𝒅𝒆𝒇𝒈𝒉𝒊𝒋𝒌𝒍𝒎𝒏𝒐𝒑𝒒𝒓𝒔𝒕𝒖𝒗𝒘𝒙𝒚𝒛'],
74 | ]);
75 |
76 | export function getSymbolMapperTypes(): SymbolMapperType[] {
77 | return Array.from(symbolMapperMap.keys());
78 | }
79 |
80 | export function convertToSymbols(text: string, type: SymbolMapperType) {
81 | let mapper = symbolMapperMap.get(type);
82 |
83 | if (!mapper) {
84 | return text;
85 | }
86 |
87 | return convertToSymbolsByMapper(text, mapper);
88 | }
89 |
90 | function convertToSymbolsByMapper(text: string, mapper: string): string {
91 | return text
92 | .split('')
93 | .map(char => {
94 | let index = getMapperIndex(char);
95 |
96 | return index === -1 ? char : Array.from(mapper)[index];
97 | })
98 | .join('');
99 | }
100 |
101 | function getMapperIndex(char: string): number {
102 | let charCode = char.charCodeAt(0);
103 |
104 | if (charCode >= UPPER_CASE_RANGE_START && charCode <= UPPER_CASE_RANGE_END) {
105 | return charCode - UPPER_CASE_RANGE_START;
106 | } else if (
107 | charCode >= LOWER_CASE_RANGE_START &&
108 | charCode <= LOWER_CASE_RANGE_END
109 | ) {
110 | return charCode - LOWER_CASE_RANGE_START + 26;
111 | } else if (charCode >= NUMBER_RANGE_START && charCode <= NUMBER_RANGE_END) {
112 | return charCode - NUMBER_RANGE_START + 26 + 26;
113 | }
114 |
115 | return -1;
116 | }
117 |
--------------------------------------------------------------------------------
/src/utils/decorate.ts:
--------------------------------------------------------------------------------
1 | export function decorate(text: string, decorator: number) {
2 | if (!decorator || !Number.isInteger(decorator)) {
3 | return text;
4 | }
5 |
6 | return Array.from(text)
7 | .map(char => char + String.fromCodePoint(decorator))
8 | .join('');
9 | }
10 |
11 | export function getDecorators(): number[] {
12 | return [0, ...range(768, 879)];
13 | }
14 |
15 | function range(start: number, end: number): number[] {
16 | const length = end - start + 1;
17 | return Array.from({ length }, (_, i) => start + i);
18 | }
19 |
--------------------------------------------------------------------------------
/src/views/App.module.css:
--------------------------------------------------------------------------------
1 | .app {
2 | min-height: 100%;
3 | display: flex;
4 | flex-direction: column;
5 | }
6 |
7 | .header {
8 | display: flex;
9 | justify-content: space-between;
10 | }
11 |
12 | .logo {
13 | width: 150px;
14 | padding: var(--dimen-small) var(--dimen-medium);
15 | display: flex;
16 | align-items: center;
17 | }
18 |
19 | .logo img {
20 | width: 100%;
21 | }
22 |
23 | .onGitHub {
24 | align-self: flex-start;
25 | margin: var(--dimen-medium);
26 | padding: var(--dimen-small) var(--dimen-medium);
27 | border-radius: var(--dimen-small);
28 | box-shadow: var(--shadow-light);
29 | background-color: var(--color-secondary);
30 | color: var(--color-text);
31 | display: flex;
32 | align-items: center;
33 | text-decoration: none;
34 | cursor: pointer;
35 | }
36 |
37 | .gitHubLogo {
38 | margin-right: var(--dimen-small);
39 | fill: var(--color-icon);
40 | }
41 |
42 | .main {
43 | flex: 1;
44 | display: flex;
45 | flex-direction: column;
46 | }
47 |
48 | .footer {
49 | display: flex;
50 | flex-direction: column;
51 | justify-content: flex-end;
52 | align-items: center;
53 | color: var(--color-text-secondary);
54 | padding: var(--dimen-medium);
55 | text-decoration: none;
56 | }
57 |
58 | .footer a,
59 | .footer a:visited {
60 | color: var(--color-text-secondary);
61 | text-decoration: none;
62 | }
63 |
64 | .footer a:hover {
65 | text-decoration: underline;
66 | }
67 |
--------------------------------------------------------------------------------
/src/views/App.test.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import ReactDOM from 'react-dom';
3 | import App from './App';
4 |
5 | it('renders without crashing', () => {
6 | const div = document.createElement('div');
7 | ReactDOM.render(, div);
8 | ReactDOM.unmountComponentAtNode(div);
9 | });
10 |
--------------------------------------------------------------------------------
/src/views/App.tsx:
--------------------------------------------------------------------------------
1 | import React, { FC } from 'react';
2 | import { BrowserRouter as Router, Switch, Route, Link } from 'react-router-dom';
3 |
4 | import logo from 'assets/logo.svg';
5 | import { GitHubLogo } from 'components/github';
6 |
7 | import styles from './App.module.css';
8 | import { Home } from './home';
9 | import { SelectMapper } from './select-mapper';
10 | import { ReactiveEditor } from './reactive-editor';
11 | import { SelectDecorator } from './select-decorator';
12 |
13 | const App: FC = () => {
14 | return (
15 |
16 |
17 |
31 |
32 |
33 |
34 |
35 |
39 |
43 |
44 |
45 |
46 |
55 |
56 |
57 | );
58 | };
59 |
60 | export default App;
61 |
--------------------------------------------------------------------------------
/src/views/home/Home.module.css:
--------------------------------------------------------------------------------
1 | .home {
2 | flex: 1;
3 | display: flex;
4 | height: 100%;
5 | flex-direction: column;
6 | justify-content: center;
7 | width: 100%;
8 | max-width: 500px;
9 | align-self: center;
10 | }
11 |
12 | .mainCard {
13 | width: 100%;
14 | max-width: calc(100% - 50px);
15 | margin: 0 25px;
16 | transition: box-shadow 0.3s;
17 | display: flex;
18 | flex-direction: column;
19 | align-self: center;
20 | background-color: var(--color-primary);
21 | }
22 |
23 | .mainCard:hover {
24 | box-shadow: var(--shadow-dark);
25 | }
26 |
27 | .logo {
28 | align-self: flex-end;
29 | font-size: 2em;
30 | color: var(--color-text);
31 | margin-bottom: var(--dimen-medium);
32 | }
33 |
34 | .mainCardTextArea {
35 | resize: vertical;
36 | min-height: 200px;
37 | border: 0;
38 | border-radius: var(--radius-medium);
39 | outline: none;
40 | padding: var(--dimen-medium);
41 | font-size: 1.1em;
42 | }
43 |
44 | .transformLink {
45 | margin: 25px;
46 | }
47 |
48 | .transformButton {
49 | width: 100%;
50 | padding: var(--dimen-large);
51 | font-size: 1.3em;
52 | }
53 |
--------------------------------------------------------------------------------
/src/views/home/Home.tsx:
--------------------------------------------------------------------------------
1 | import React, {
2 | FC,
3 | useState,
4 | useCallback,
5 | ChangeEvent,
6 | MouseEvent,
7 | } from 'react';
8 | import classNames from 'classnames';
9 |
10 | import { Card } from 'components/card';
11 |
12 | import styles from './Home.module.css';
13 | import { Button } from 'components/button';
14 | import { Link } from 'react-router-dom';
15 |
16 | export const Home: FC = () => {
17 | let [text, setText] = useState('');
18 |
19 | let onTextAreaChange = useCallback(
20 | ({ currentTarget: { value } }: ChangeEvent) => {
21 | setText(value);
22 | },
23 | [],
24 | );
25 |
26 | let onLinkClick = useCallback(
27 | (event: MouseEvent) => {
28 | if (!text) {
29 | event.preventDefault();
30 | }
31 | },
32 | [text],
33 | );
34 |
35 | return (
36 |
37 |
38 | 𝒮𝓎𝓂𝒷ℴ𝓁𝓈
39 |
45 |
46 |
51 |
52 |
53 |
54 | );
55 | };
56 |
--------------------------------------------------------------------------------
/src/views/home/index.ts:
--------------------------------------------------------------------------------
1 | export * from './Home';
2 |
--------------------------------------------------------------------------------
/src/views/reactive-editor/ReactiveEditor.module.css:
--------------------------------------------------------------------------------
1 | .reactiveEditor {
2 | flex: 1;
3 | width: 100%;
4 | max-width: 500px;
5 | padding: var(--dimen-medium);
6 | align-self: center;
7 | margin: -10px;
8 | display: flex;
9 | flex-direction: column;
10 | justify-content: center;
11 | }
12 |
13 | .mainCard {
14 | width: 100%;
15 | max-width: calc(100% - 50px);
16 | margin: 0 25px;
17 | transition: box-shadow 0.3s;
18 | display: flex;
19 | flex-direction: column;
20 | align-self: center;
21 | background-color: var(--color-primary);
22 | }
23 |
24 | .mainCard:hover {
25 | box-shadow: var(--shadow-dark);
26 | }
27 |
28 | .logo {
29 | align-self: flex-end;
30 | font-size: 2em;
31 | color: var(--color-text);
32 | margin-bottom: var(--dimen-medium);
33 | }
34 |
35 | .mainCardTextArea {
36 | resize: vertical;
37 | min-height: 100px;
38 | border: 0;
39 | border-radius: var(--radius-medium);
40 | outline: none;
41 | padding: var(--dimen-medium);
42 | font-size: 1.1em;
43 | }
44 |
45 | .resultCard {
46 | display: flex;
47 | flex-direction: column;
48 | background-color: var(--color-secondary);
49 | color: var(--color-text);
50 | margin: 25px;
51 | }
52 |
53 | .resultWrapper {
54 | word-break: break-all;
55 | }
56 |
57 | .copyButton {
58 | align-self: flex-end;
59 | margin-top: var(--dimen-medium);
60 | background-color: var(--color-white);
61 | color: var(--color-secondary);
62 | box-shadow: var(--shadow-light);
63 | padding: var(--dimen-medium) var(--dimen-large);
64 | border-radius: var(--dimen-small);
65 | border: 0;
66 | cursor: pointer;
67 | transition: color 0.3s, background-color 0.3s, box-shadow 0.3s, width 0.3s;
68 | }
69 |
70 | .copyButton:hover {
71 | color: var(--color-white);
72 | background-color: var(--color-secondary);
73 | box-shadow: var(--shadow-dark);
74 | }
75 |
76 | .copyButton.copySuccess {
77 | background-color: var(--color-success);
78 | color: var(--color-text);
79 | }
80 |
--------------------------------------------------------------------------------
/src/views/reactive-editor/ReactiveEditor.tsx:
--------------------------------------------------------------------------------
1 | import React, { FC, useState, useCallback, ChangeEvent } from 'react';
2 | import { RouteComponentProps } from 'react-router';
3 | import Clipboard from 'react-clipboard.js';
4 |
5 | import { SymbolMapperType, convertToSymbols } from 'utils/converter';
6 |
7 | import styles from './ReactiveEditor.module.css';
8 | import { Card } from 'components/card';
9 | import classNames from 'classnames';
10 | import { decorate } from 'utils/decorate';
11 |
12 | const COPY_SUCCESS_NOTICE_TIMEOUT = 3000;
13 |
14 | export type ReactiveEditorParams = {
15 | symbolType: SymbolMapperType;
16 | decorator: string;
17 | text: string;
18 | };
19 |
20 | export const ReactiveEditor: FC> = ({
21 | match: {
22 | params: { symbolType, decorator: decoratorString, text: originText },
23 | },
24 | }) => {
25 | let decorator = Number(decoratorString);
26 | let decodedText = decodeURIComponent(originText);
27 |
28 | let [text, setText] = useState(decodedText);
29 | let [copySuccess, setCopySuccess] = useState(false);
30 |
31 | let onTextAreaChange = useCallback(
32 | ({ currentTarget: { value } }: ChangeEvent) => {
33 | setText(value);
34 | },
35 | [],
36 | );
37 |
38 | let onCopySuccess = useCallback(() => {
39 | setCopySuccess(true);
40 | setTimeout(() => {
41 | setCopySuccess(false);
42 | }, COPY_SUCCESS_NOTICE_TIMEOUT);
43 | }, []);
44 |
45 | let convertedText = convertToSymbols(text, symbolType);
46 | let decoratedText = decorate(convertedText, decorator);
47 |
48 | return (
49 |
50 |
51 | 𝒮𝓎𝓂𝒷ℴ𝓁𝓈
52 |
59 |
60 |
61 | {decoratedText || '𝒮𝓎𝓂𝒷ℴ𝓁𝓈'}
62 |
69 | {copySuccess ? 'Copy Success' : 'Copy'}
70 |
71 |
72 |
73 | );
74 | };
75 |
--------------------------------------------------------------------------------
/src/views/reactive-editor/index.ts:
--------------------------------------------------------------------------------
1 | export * from './ReactiveEditor';
2 |
--------------------------------------------------------------------------------
/src/views/select-decorator/SelectDecorator.module.css:
--------------------------------------------------------------------------------
1 | .selectMapper {
2 | width: 100%;
3 | max-width: 500px;
4 | padding: var(--dimen-medium);
5 | align-self: center;
6 | margin: -10px;
7 | }
8 |
9 | .title {
10 | font-size: 1.5em;
11 | margin: 10px 10px 30px 10px;
12 | color: var(--color-text-primary);
13 | }
14 |
15 | .previewLink {
16 | color: var(--color-text-primary);
17 | text-decoration: none;
18 | }
19 |
20 | .previewLink:visited {
21 | color: var(--color-text-primary);
22 | }
23 |
24 | .selectMapper .previewItem {
25 | margin: 10px;
26 | }
27 |
28 | .decorator {
29 | color: var(--color-text-secondary);
30 | font-size: 0.8em;
31 | }
32 |
--------------------------------------------------------------------------------
/src/views/select-decorator/SelectDecorator.tsx:
--------------------------------------------------------------------------------
1 | import React, { FC } from 'react';
2 | import { RouteComponentProps } from 'react-router';
3 |
4 | import { convertToSymbols, SymbolMapperType } from 'utils/converter';
5 | import { PreviewItem } from 'components/preview-item';
6 |
7 | import styles from './SelectDecorator.module.css';
8 | import { Link } from 'react-router-dom';
9 | import { getDecorators, decorate } from 'utils/decorate';
10 |
11 | const MAX_PREVIEW_TEXT_LENGTH = 30;
12 |
13 | export type SelectDecoratorRouteParams = {
14 | text: string;
15 | symbolType: SymbolMapperType;
16 | };
17 |
18 | export const SelectDecorator: FC<
19 | RouteComponentProps
20 | > = ({
21 | match: {
22 | params: { text, symbolType },
23 | },
24 | }) => {
25 | let previewText = text.substr(0, MAX_PREVIEW_TEXT_LENGTH);
26 | let transformedText = convertToSymbols(previewText, symbolType);
27 |
28 | return (
29 |
30 |
Please select a kind of decorator:
31 | {getDecorators().map(decorator => {
32 | let convertedText = decorate(transformedText, decorator);
33 |
34 | return (
35 |
40 |
41 | {convertedText}
42 | #{decorator}
43 |
44 |
45 | );
46 | })}
47 |
48 | );
49 | };
50 |
--------------------------------------------------------------------------------
/src/views/select-decorator/index.ts:
--------------------------------------------------------------------------------
1 | export * from './SelectDecorator';
2 |
--------------------------------------------------------------------------------
/src/views/select-mapper/SelectMapper.module.css:
--------------------------------------------------------------------------------
1 | .selectMapper {
2 | width: 100%;
3 | max-width: 500px;
4 | padding: var(--dimen-medium);
5 | align-self: center;
6 | margin: -10px;
7 | }
8 |
9 | .title {
10 | font-size: 1.5em;
11 | margin: 10px 10px 30px 10px;
12 | color: var(--color-text-primary);
13 | }
14 |
15 | .previewLink {
16 | color: var(--color-text-primary);
17 | text-decoration: none;
18 | }
19 |
20 | .previewLink:visited {
21 | color: var(--color-text-primary);
22 | }
23 |
24 | .selectMapper .previewItem {
25 | margin: 10px;
26 | }
27 |
28 | .mapperType {
29 | color: var(--color-text-secondary);
30 | font-size: 0.8em;
31 | }
32 |
--------------------------------------------------------------------------------
/src/views/select-mapper/SelectMapper.tsx:
--------------------------------------------------------------------------------
1 | import React, { FC } from 'react';
2 | import { RouteComponentProps } from 'react-router';
3 |
4 | import { getSymbolMapperTypes, convertToSymbols } from 'utils/converter';
5 |
6 | import styles from './SelectMapper.module.css';
7 | import { Link } from 'react-router-dom';
8 | import { PreviewItem } from 'components/preview-item';
9 |
10 | const MAX_PREVIEW_TEXT_LENGTH = 30;
11 |
12 | export type SelectMapperRouteParams = {
13 | text: string;
14 | };
15 |
16 | export const SelectMapper: FC> = ({
17 | match: {
18 | params: { text },
19 | },
20 | }) => {
21 | let previewText = text.substr(0, MAX_PREVIEW_TEXT_LENGTH);
22 |
23 | return (
24 |
25 |
Please select a kind of symbol:
26 | {getSymbolMapperTypes().map(type => {
27 | let convertedText = convertToSymbols(previewText, type);
28 |
29 | return (
30 |
35 |
36 | {convertedText}
37 | {type}
38 |
39 |
40 | );
41 | })}
42 |
43 | );
44 | };
45 |
--------------------------------------------------------------------------------
/src/views/select-mapper/index.ts:
--------------------------------------------------------------------------------
1 | export * from './SelectMapper';
2 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "es6",
4 | "lib": ["dom", "dom.iterable", "esnext"],
5 | "allowJs": true,
6 | "skipLibCheck": true,
7 | "esModuleInterop": true,
8 | "allowSyntheticDefaultImports": true,
9 | "strict": true,
10 | "forceConsistentCasingInFileNames": true,
11 | "module": "esnext",
12 | "moduleResolution": "node",
13 | "resolveJsonModule": true,
14 | "isolatedModules": true,
15 | "noEmit": true,
16 | "jsx": "preserve",
17 |
18 | "baseUrl": "src"
19 | },
20 | "include": ["src"]
21 | }
22 |
--------------------------------------------------------------------------------