refactor: lexical (#1954)

This commit is contained in:
Mo
2022-11-07 10:42:44 -06:00
committed by GitHub
parent 99bae83f8b
commit 2ed01a071c
182 changed files with 8525 additions and 1126 deletions

View File

@@ -0,0 +1,12 @@
# NOTE: In general this should be kept in sync with .eslintignore
**/dist/**
**/config/**
**/build/**
**/npm/**
**/*.js.flow
**/*.d.ts
**/playwright*/**
**/vite.config.js
**/vite.prod.config.js
**/node_modules

View File

@@ -0,0 +1,254 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
*/
'use strict';
const restrictedGlobals = require('confusing-browser-globals');
const OFF = 0;
const ERROR = 2;
module.exports = {
root: true,
// Prettier must be last so it can override other configs (https://github.com/prettier/eslint-config-prettier#installation)
extends: [
'fbjs',
'plugin:react-hooks/recommended',
'plugin:lexical/all',
'prettier',
],
globals: {
JSX: true,
__DEV__: true,
},
overrides: [
{
// We apply these settings to the source files that get compiled.
// They can use all features including JSX (but shouldn't use `var`).
files: [
'packages/*/src/**/*.js',
'packages/*/__tests__/**/*.?(m)js',
'packages/*/src/**/*.jsx',
],
parser: 'babel-eslint',
parserOptions: {
allowImportExportEverywhere: true,
sourceType: 'module',
},
rules: {
'no-var': ERROR,
'prefer-const': ERROR,
strict: OFF,
},
},
{
// node scripts should be console logging so don't lint against that
files: ['scripts/**/*.js'],
rules: {
'no-console': OFF,
},
},
{
env: {
browser: true,
},
extends: [
'eslint:recommended',
'plugin:@typescript-eslint/eslint-recommended',
'plugin:@typescript-eslint/recommended',
],
files: ['**/*.ts', '**/*.tsx'],
parser: '@typescript-eslint/parser',
parserOptions: {
sourceType: 'module',
},
plugins: ['react', '@typescript-eslint', 'header'],
rules: {
'@typescript-eslint/ban-ts-comment': OFF,
'@typescript-eslint/no-this-alias': OFF,
'@typescript-eslint/no-unused-vars': [ERROR, {args: 'none'}],
'header/header': [2, 'scripts/www/headerTemplate.js'],
},
},
{
// don't lint headers in entrypoint files so we can add TypeDoc module comments
files: ['packages/**/src/index.ts'],
rules: {
'header/header': OFF,
},
},
{
files: [
'packages/**/src/__tests__/**',
'packages/lexical-playground/**',
'packages/lexical-devtools/**',
],
rules: {
'lexical/no-optional-chaining': OFF,
},
},
],
parser: 'babel-eslint',
parserOptions: {
ecmaFeatures: {
experimentalObjectRestSpread: true,
},
ecmaVersion: 8,
sourceType: 'script',
},
plugins: [
'sort-keys-fix',
'simple-import-sort',
'header',
// import helps to configure simple-import-sort
'import',
'jest',
'no-function-declare-after-return',
'react',
'no-only-tests',
'lexical',
],
// Stop ESLint from looking for a configuration file in parent folders
root: true,
// We're stricter than the default config, mostly. We'll override a few rules
// and then enable some React specific ones.
rules: {
'accessor-pairs': OFF,
'brace-style': [ERROR, '1tbs'],
'consistent-return': OFF,
'dot-location': [ERROR, 'property'],
// We use console['error']() as a signal to not transform it:
'dot-notation': [ERROR, {allowPattern: '^(error|warn)$'}],
'eol-last': ERROR,
eqeqeq: [ERROR, 'allow-null'],
// Prettier forces semicolons in a few places
'flowtype/object-type-delimiter': OFF,
'flowtype/sort-keys': ERROR,
'header/header': [2, 'scripts/www/headerTemplate.js'],
// (This helps configure simple-import-sort) Make sure all imports are at the top of the file
'import/first': ERROR,
// (This helps configure simple-import-sort) Make sure there's a newline after the imports
'import/newline-after-import': ERROR,
// (This helps configure simple-import-sort) Merge imports of the same file
'import/no-duplicates': ERROR,
indent: OFF,
'jsx-quotes': [ERROR, 'prefer-double'],
'keyword-spacing': [ERROR, {after: true, before: true}],
// Enforced by Prettier
// TODO: Prettier doesn't handle long strings or long comments. Not a big
// deal. But I turned it off because loading the plugin causes some obscure
// syntax error and it didn't seem worth investigating.
'max-len': OFF,
'no-bitwise': OFF,
'no-console': ERROR,
'no-debugger': ERROR,
// Prevent function declarations after return statements
'no-function-declare-after-return/no-function-declare-after-return': ERROR,
'no-inner-declarations': [ERROR, 'functions'],
'no-multi-spaces': ERROR,
'no-only-tests/no-only-tests': ERROR,
'no-restricted-globals': [ERROR].concat(restrictedGlobals),
'no-restricted-syntax': [ERROR, 'WithStatement'],
'no-shadow': ERROR,
'no-unused-expressions': ERROR,
'no-unused-vars': [ERROR, {args: 'none'}],
'no-use-before-define': OFF,
// Flow fails with with non-string literal keys
'no-useless-computed-key': OFF,
'no-useless-concat': OFF,
// We apply these settings to files that should run on Node.
// They can't use JSX or ES6 modules, and must be in strict mode.
// They can, however, use other ES6 features.
// (Note these rules are overridden later for source files.)
'no-var': ERROR,
quotes: [ERROR, 'single', {allowTemplateLiterals: true, avoidEscape: true}],
// React & JSX
// Our transforms set this automatically
'react/jsx-boolean-value': [ERROR, 'always'],
'react/jsx-no-undef': ERROR,
// We don't care to do this
'react/jsx-sort-prop-types': OFF,
'react/jsx-tag-spacing': ERROR,
'react/jsx-uses-react': ERROR,
// We don't care to do this
'react/jsx-wrap-multilines': [
ERROR,
{assignment: false, declaration: false},
],
'react/no-is-mounted': OFF,
// This isn't useful in our test code
'react/react-in-jsx-scope': ERROR,
'react/self-closing-comp': ERROR,
// This sorts re-exports (`export * from 'foo';`), but not other types of exports.
'simple-import-sort/exports': ERROR,
'simple-import-sort/imports': [
ERROR,
{
// The default grouping, but with type imports first as a separate group.
// See: https://github.com/lydell/eslint-plugin-simple-import-sort/blob/d9a116f71302c5dcfc1581fc7ded8d77392f1924/examples/.eslintrc.js#L122-L133
groups: [['^.*\\u0000$'], ['^\\u0000'], ['^@?\\w'], ['^'], ['^\\.']],
},
],
'sort-keys-fix/sort-keys-fix': ERROR,
'space-before-blocks': ERROR,
'space-before-function-paren': OFF,
strict: ERROR,
'valid-typeof': [ERROR, {requireStringLiterals: true}],
},
};

1
packages/blocks-editor/.gitignore vendored Normal file
View File

@@ -0,0 +1 @@
dist

View File

@@ -0,0 +1,15 @@
# NOTE: In general this should be kept in sync with .eslintignore
packages/**/dist/*.js
packages/**/build/*.js
packages/**/npm/**/*
packages/**/config/*.js
packages/playwright
packages/playwright-core
packages/**/vite.config.js
packages/**/vite.prod.config.js
**/*.md
**/node_modules
flow-typed
.github/CODEOWNERS
.prettierignore

View File

@@ -0,0 +1,11 @@
'use strict';
module.exports = {
bracketSpacing: false,
singleQuote: true,
bracketSameLine: true,
printWidth: 80,
trailingComma: 'all',
htmlWhitespaceSensitivity: 'ignore',
attributeGroups: ['$DEFAULT', '^data-'],
};

View File

@@ -0,0 +1,94 @@
# Change Log
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.3.6](https://github.com/standardnotes/app/compare/@standardnotes/toast@1.3.5...@standardnotes/toast@1.3.6) (2022-11-04)
**Note:** Version bump only for package @standardnotes/toast
## [1.3.5](https://github.com/standardnotes/app/compare/@standardnotes/toast@1.3.4...@standardnotes/toast@1.3.5) (2022-08-11)
### Bug Fixes
* optimize toasts for mobile ([#1392](https://github.com/standardnotes/app/issues/1392)) ([40d9392](https://github.com/standardnotes/app/commit/40d9392599e871225abcabcddd51de6cc99a0fe9))
## [1.3.4](https://github.com/standardnotes/app/compare/@standardnotes/toast@1.3.3...@standardnotes/toast@1.3.4) (2022-07-14)
**Note:** Version bump only for package @standardnotes/toast
## [1.3.3](https://github.com/standardnotes/app/compare/@standardnotes/toast@1.3.2...@standardnotes/toast@1.3.3) (2022-07-13)
**Note:** Version bump only for package @standardnotes/toast
## [1.3.2](https://github.com/standardnotes/app/compare/@standardnotes/toast@1.3.1...@standardnotes/toast@1.3.2) (2022-07-06)
**Note:** Version bump only for package @standardnotes/toast
## [1.3.1](https://github.com/standardnotes/app/compare/@standardnotes/toast@1.3.0...@standardnotes/toast@1.3.1) (2022-06-28)
**Note:** Version bump only for package @standardnotes/toast
# [1.3.0](https://github.com/standardnotes/app/compare/@standardnotes/toast@1.2.4...@standardnotes/toast@1.3.0) (2022-06-27)
### Features
* **web:** tailwind css ([#1147](https://github.com/standardnotes/app/issues/1147)) ([b80038f](https://github.com/standardnotes/app/commit/b80038f607d7411912fa99366abf559a44874ef3))
## [1.2.4](https://github.com/standardnotes/app/compare/@standardnotes/toast@1.2.4-alpha.0...@standardnotes/toast@1.2.4) (2022-06-18)
**Note:** Version bump only for package @standardnotes/toast
## [1.2.4-alpha.0](https://github.com/standardnotes/app/compare/@standardnotes/toast@1.2.3...@standardnotes/toast@1.2.4-alpha.0) (2022-06-18)
**Note:** Version bump only for package @standardnotes/toast
## 1.2.3 (2022-06-16)
**Note:** Version bump only for package @standardnotes/toast
## 1.2.2 (2022-06-16)
**Note:** Version bump only for package @standardnotes/toast
## [1.2.1](https://github.com/standardnotes/app/compare/@standardnotes/toast@1.2.1-alpha.4...@standardnotes/toast@1.2.1) (2022-06-16)
**Note:** Version bump only for package @standardnotes/toast
## 1.2.1-alpha.4 (2022-06-16)
**Note:** Version bump only for package @standardnotes/toast
## 1.2.1-alpha.3 (2022-06-16)
**Note:** Version bump only for package @standardnotes/toast
## 1.2.1-alpha.2 (2022-06-15)
**Note:** Version bump only for package @standardnotes/toast
## [1.2.1-alpha.1](https://github.com/standardnotes/app/compare/@standardnotes/toast@1.2.1-alpha.0...@standardnotes/toast@1.2.1-alpha.1) (2022-06-14)
**Note:** Version bump only for package @standardnotes/toast
## [1.2.1-alpha.0](https://github.com/standardnotes/app/compare/@standardnotes/toast@1.2.0...@standardnotes/toast@1.2.1-alpha.0) (2022-06-14)
**Note:** Version bump only for package @standardnotes/toast
# 1.2.0 (2022-06-10)
### Features
* mobile app package ([#1075](https://github.com/standardnotes/app/issues/1075)) ([8248a38](https://github.com/standardnotes/app/commit/8248a38280cb7c92da2b2e9c7db298f34ae8ffdf))
* styles package ([#1074](https://github.com/standardnotes/app/issues/1074)) ([3100327](https://github.com/standardnotes/app/commit/31003276b73d3e89824bc002fe616fa055e918c4))
* toast package ([#1073](https://github.com/standardnotes/app/issues/1073)) ([6d0b6e9](https://github.com/standardnotes/app/commit/6d0b6e9018b2a612b8df4827336883fe04033128))
* **wip:** components monorepo ([#1082](https://github.com/standardnotes/app/issues/1082)) ([e3d6001](https://github.com/standardnotes/app/commit/e3d6001a178e11e619ca724b2b155b7c0405c023))
# 1.1.0 (2022-06-10)
### Features
* mobile app package ([#1075](https://github.com/standardnotes/app/issues/1075)) ([8248a38](https://github.com/standardnotes/app/commit/8248a38280cb7c92da2b2e9c7db298f34ae8ffdf))
* styles package ([#1074](https://github.com/standardnotes/app/issues/1074)) ([3100327](https://github.com/standardnotes/app/commit/31003276b73d3e89824bc002fe616fa055e918c4))
* toast package ([#1073](https://github.com/standardnotes/app/issues/1073)) ([6d0b6e9](https://github.com/standardnotes/app/commit/6d0b6e9018b2a612b8df4827336883fe04033128))
* **wip:** components monorepo ([8c5e11c](https://github.com/standardnotes/app/commit/8c5e11c22b717ada7a6a9b3115fc4c9b757ec71c))

View File

@@ -0,0 +1 @@
Based on https://github.com/facebook/lexical/tree/main/packages/lexical-playground

View File

@@ -0,0 +1,32 @@
const path = require('path')
module.exports = () => {
return {
entry: './src/index.ts',
output: {
filename: './dist.js',
},
mode: 'production',
resolve: {
extensions: ['.ts', '.tsx', '.js'],
},
externals: {
"@standardnotes/icons": path.resolve(__dirname, "./node_modules/@standardnotes/icons")
},
module: {
rules: [
{
test: /\.(js|tsx?)$/,
use: [
'babel-loader',
{
loader: 'ts-loader',
options: {
transpileOnly: true,
},
},
],
},
],
},
}
}

View File

@@ -0,0 +1,23 @@
{
"name": "@standardnotes/blocks-editor",
"version": "1.3.6",
"private": true,
"main": "./src/index.ts",
"scripts": {
"start": "tsc -p tsconfig.json --watch"
},
"dependencies": {
"@lexical/react": "^0.6.0",
"@standardnotes/icons": "workspace:*",
"lexical": "^0.6.0",
"react": "link:../web/node_modules/react",
"react-dom": "link:../web/node_modules/react-dom"
},
"devDependencies": {
"@types/react": "link:../web/node_modules/@types/react",
"@types/react-dom": "link:../web/node_modules/@types/react-dom",
"eslint": "*",
"prettier": "*",
"typescript": "*"
}
}

View File

@@ -0,0 +1,115 @@
import {FunctionComponent, useCallback, useState} from 'react';
import {LexicalComposer} from '@lexical/react/LexicalComposer';
import {RichTextPlugin} from '@lexical/react/LexicalRichTextPlugin';
import {ContentEditable} from '@lexical/react/LexicalContentEditable';
import {OnChangePlugin} from '@lexical/react/LexicalOnChangePlugin';
import {AutoFocusPlugin} from '@lexical/react/LexicalAutoFocusPlugin';
import {CheckListPlugin} from '@lexical/react/LexicalCheckListPlugin';
import {ClearEditorPlugin} from '@lexical/react/LexicalClearEditorPlugin';
import {MarkdownShortcutPlugin} from '@lexical/react/LexicalMarkdownShortcutPlugin';
import {TablePlugin} from '@lexical/react/LexicalTablePlugin';
import {
CHECK_LIST,
ELEMENT_TRANSFORMERS,
TEXT_FORMAT_TRANSFORMERS,
TEXT_MATCH_TRANSFORMERS,
} from '@lexical/markdown';
import LexicalErrorBoundary from '@lexical/react/LexicalErrorBoundary';
import {HashtagPlugin} from '@lexical/react/LexicalHashtagPlugin';
import {HistoryPlugin} from '@lexical/react/LexicalHistoryPlugin';
import {LinkPlugin} from '@lexical/react/LexicalLinkPlugin';
import {ListPlugin} from '@lexical/react/LexicalListPlugin';
import {EditorState, LexicalEditor} from 'lexical';
import ComponentPickerMenuPlugin from '../Lexical/Plugins/ComponentPickerPlugin';
import BlocksEditorTheme from '../Lexical/Theme/Theme';
import HorizontalRulePlugin from '../Lexical/Plugins/HorizontalRulePlugin';
import TwitterPlugin from '../Lexical/Plugins/TwitterPlugin';
import YouTubePlugin from '../Lexical/Plugins/YouTubePlugin';
import AutoEmbedPlugin from '../Lexical/Plugins/AutoEmbedPlugin';
import CollapsiblePlugin from '../Lexical/Plugins/CollapsiblePlugin';
import {BlockEditorNodes} from '../Lexical/Nodes/AllNodes';
// import DraggableBlockPlugin from '../Lexical/Plugins/DraggableBlockPlugin';
type BlocksEditorProps = {
initialValue: string;
onChange: (value: string) => void;
className?: string;
};
export const BlocksEditor: FunctionComponent<BlocksEditorProps> = ({
initialValue,
onChange,
className,
}) => {
const handleChange = useCallback(
(editorState: EditorState, _editor: LexicalEditor) => {
const stringifiedEditorState = JSON.stringify(editorState.toJSON());
onChange(stringifiedEditorState);
},
[onChange],
);
const [floatingAnchorElem, setFloatingAnchorElem] =
useState<HTMLDivElement | null>(null);
const onRef = (_floatingAnchorElem: HTMLDivElement) => {
if (_floatingAnchorElem !== null) {
setFloatingAnchorElem(_floatingAnchorElem);
}
};
return (
<LexicalComposer
initialConfig={{
namespace: 'BlocksEditor',
theme: BlocksEditorTheme,
onError: (error: Error) => console.error(error),
editorState:
initialValue && initialValue.length > 0 ? initialValue : undefined,
nodes: BlockEditorNodes,
}}>
<>
<RichTextPlugin
contentEditable={
<div id="blocks-editor" className="editor-scroller">
<div className="editor" ref={onRef}>
<ContentEditable
className={`ContentEditable__root ${className}`}
/>
</div>
</div>
}
placeholder=""
ErrorBoundary={LexicalErrorBoundary}
/>
<ListPlugin />
<MarkdownShortcutPlugin
transformers={[
CHECK_LIST,
...ELEMENT_TRANSFORMERS,
...TEXT_FORMAT_TRANSFORMERS,
...TEXT_MATCH_TRANSFORMERS,
]}
/>
<TablePlugin />
<OnChangePlugin onChange={handleChange} />
<HistoryPlugin />
<HorizontalRulePlugin />
<AutoFocusPlugin />
<ComponentPickerMenuPlugin />
<ClearEditorPlugin />
<CheckListPlugin />
<LinkPlugin />
<HashtagPlugin />
<AutoEmbedPlugin />
<TwitterPlugin />
<YouTubePlugin />
<CollapsiblePlugin />
{floatingAnchorElem && (
<>{/* <DraggableBlockPlugin anchorElem={floatingAnchorElem} /> */}</>
)}
</>
</LexicalComposer>
);
};

View File

@@ -0,0 +1,59 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
*/
import {useCallback, useMemo, useState} from 'react';
import Modal from '../UI/Modal';
export default function useModal(): [
JSX.Element | null,
(title: string, showModal: (onClose: () => void) => JSX.Element) => void,
] {
const [modalContent, setModalContent] = useState<null | {
closeOnClickOutside: boolean;
content: JSX.Element;
title: string;
}>(null);
const onClose = useCallback(() => {
setModalContent(null);
}, []);
const modal = useMemo(() => {
if (modalContent === null) {
return null;
}
const {title, content, closeOnClickOutside} = modalContent;
return (
<Modal
onClose={onClose}
title={title}
closeOnClickOutside={closeOnClickOutside}>
{content}
</Modal>
);
}, [modalContent, onClose]);
const showModal = useCallback(
(
title: string,
// eslint-disable-next-line no-shadow
getContent: (onClose: () => void) => JSX.Element,
closeOnClickOutside = false,
) => {
setModalContent({
closeOnClickOutside,
content: getContent(onClose),
title,
});
},
[onClose],
);
return [modal, showModal];
}

View File

@@ -0,0 +1,5 @@
Bootstrap Icons
https://icons.getbootstrap.com
Licensed under MIT license
https://github.com/twbs/icons/blob/main/LICENSE.md

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-arrow-clockwise"><path fill-rule="evenodd" d="M8 3a5 5 0 1 0 4.546 2.914.5.5 0 0 1 .908-.417A6 6 0 1 1 8 2v1z"/><path d="M8 4.466V.534a.25.25 0 0 1 .41-.192l2.36 1.966c.12.1.12.284 0 .384L8.41 4.658A.25.25 0 0 1 8 4.466z"/></svg>

After

Width:  |  Height:  |  Size: 325 B

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-arrow-counterclockwise"><path fill-rule="evenodd" d="M8 3a5 5 0 1 1-4.546 2.914.5.5 0 0 0-.908-.417A6 6 0 1 0 8 2v1z"/><path d="M8 4.466V.534a.25.25 0 0 0-.41-.192L5.23 2.308a.25.25 0 0 0 0 .384l2.36 1.966A.25.25 0 0 0 8 4.466z"/></svg>

After

Width:  |  Height:  |  Size: 332 B

View File

@@ -0,0 +1 @@
<svg width="16" height="16" viewBox="0 0 48 48" fill="none" xmlns="http://www.w3.org/2000/svg"><path fill="#fff" fill-opacity=".01" d="M0 0h48v48H0z"/><path fill-rule="evenodd" clip-rule="evenodd" d="M37 37a4 4 0 0 0 4-4c0-1.473-1.333-3.473-4-6-2.667 2.527-4 4.527-4 6a4 4 0 0 0 4 4Z" fill="#777"/><path d="m20.854 5.504 3.535 3.536" stroke="#777" stroke-width="4" stroke-linecap="round"/><path d="M23.682 8.333 8.125 23.889 19.44 35.203l15.556-15.557L23.682 8.333Z" stroke="#777" stroke-width="4" stroke-linejoin="round"/><path d="m12 20.073 16.961 5.577M4 43h40" stroke="#777" stroke-width="4" stroke-linecap="round"/></svg>

After

Width:  |  Height:  |  Size: 626 B

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-camera"><path d="M15 12a1 1 0 0 1-1 1H2a1 1 0 0 1-1-1V6a1 1 0 0 1 1-1h1.172a3 3 0 0 0 2.12-.879l.83-.828A1 1 0 0 1 6.827 3h2.344a1 1 0 0 1 .707.293l.828.828A3 3 0 0 0 12.828 5H14a1 1 0 0 1 1 1v6zM2 4a2 2 0 0 0-2 2v6a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V6a2 2 0 0 0-2-2h-1.172a2 2 0 0 1-1.414-.586l-.828-.828A2 2 0 0 0 9.172 2H6.828a2 2 0 0 0-1.414.586l-.828.828A2 2 0 0 1 3.172 4H2z"/><path d="M8 11a2.5 2.5 0 1 1 0-5 2.5 2.5 0 0 1 0 5zm0 1a3.5 3.5 0 1 0 0-7 3.5 3.5 0 0 0 0 7zM3 6.5a.5.5 0 1 1-1 0 .5.5 0 0 1 1 0z"/></svg>

After

Width:  |  Height:  |  Size: 613 B

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-card-checklist"><path d="M14.5 3a.5.5 0 0 1 .5.5v9a.5.5 0 0 1-.5.5h-13a.5.5 0 0 1-.5-.5v-9a.5.5 0 0 1 .5-.5h13zm-13-1A1.5 1.5 0 0 0 0 3.5v9A1.5 1.5 0 0 0 1.5 14h13a1.5 1.5 0 0 0 1.5-1.5v-9A1.5 1.5 0 0 0 14.5 2h-13z"/><path d="M7 5.5a.5.5 0 0 1 .5-.5h5a.5.5 0 0 1 0 1h-5a.5.5 0 0 1-.5-.5zm-1.496-.854a.5.5 0 0 1 0 .708l-1.5 1.5a.5.5 0 0 1-.708 0l-.5-.5a.5.5 0 1 1 .708-.708l.146.147 1.146-1.147a.5.5 0 0 1 .708 0zM7 9.5a.5.5 0 0 1 .5-.5h5a.5.5 0 0 1 0 1h-5a.5.5 0 0 1-.5-.5zm-1.496-.854a.5.5 0 0 1 0 .708l-1.5 1.5a.5.5 0 0 1-.708 0l-.5-.5a.5.5 0 0 1 .708-.708l.146.147 1.146-1.147a.5.5 0 0 1 .708 0z"/></svg>

After

Width:  |  Height:  |  Size: 703 B

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-caret-right-fill"><path d="m12.14 8.753-5.482 4.796c-.646.566-1.658.106-1.658-.753V3.204a1 1 0 0 1 1.659-.753l5.48 4.796a1 1 0 0 1 0 1.506z"/></svg>

After

Width:  |  Height:  |  Size: 244 B

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-chat-left-text"><path d="M14 1a1 1 0 0 1 1 1v8a1 1 0 0 1-1 1H4.414A2 2 0 0 0 3 11.586l-2 2V2a1 1 0 0 1 1-1h12zM2 0a2 2 0 0 0-2 2v12.793a.5.5 0 0 0 .854.353l2.853-2.853A1 1 0 0 1 4.414 12H14a2 2 0 0 0 2-2V2a2 2 0 0 0-2-2H2z"/><path d="M3 3.5a.5.5 0 0 1 .5-.5h9a.5.5 0 0 1 0 1h-9a.5.5 0 0 1-.5-.5zM3 6a.5.5 0 0 1 .5-.5h9a.5.5 0 0 1 0 1h-9A.5.5 0 0 1 3 6zm0 2.5a.5.5 0 0 1 .5-.5h5a.5.5 0 0 1 0 1h-5a.5.5 0 0 1-.5-.5z"/></svg>

After

Width:  |  Height:  |  Size: 518 B

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-chat-right-dots"><path d="M2 1a1 1 0 0 0-1 1v8a1 1 0 0 0 1 1h9.586a2 2 0 0 1 1.414.586l2 2V2a1 1 0 0 0-1-1H2zm12-1a2 2 0 0 1 2 2v12.793a.5.5 0 0 1-.854.353l-2.853-2.853a1 1 0 0 0-.707-.293H2a2 2 0 0 1-2-2V2a2 2 0 0 1 2-2h12z"/><path d="M5 6a1 1 0 1 1-2 0 1 1 0 0 1 2 0zm4 0a1 1 0 1 1-2 0 1 1 0 0 1 2 0zm4 0a1 1 0 1 1-2 0 1 1 0 0 1 2 0z"/></svg>

After

Width:  |  Height:  |  Size: 440 B

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-chat-right-text"><path d="M2 1a1 1 0 0 0-1 1v8a1 1 0 0 0 1 1h9.586a2 2 0 0 1 1.414.586l2 2V2a1 1 0 0 0-1-1H2zm12-1a2 2 0 0 1 2 2v12.793a.5.5 0 0 1-.854.353l-2.853-2.853a1 1 0 0 0-.707-.293H2a2 2 0 0 1-2-2V2a2 2 0 0 1 2-2h12z"/><path d="M3 3.5a.5.5 0 0 1 .5-.5h9a.5.5 0 0 1 0 1h-9a.5.5 0 0 1-.5-.5zM3 6a.5.5 0 0 1 .5-.5h9a.5.5 0 0 1 0 1h-9A.5.5 0 0 1 3 6zm0 2.5a.5.5 0 0 1 .5-.5h5a.5.5 0 0 1 0 1h-5a.5.5 0 0 1-.5-.5z"/></svg>

After

Width:  |  Height:  |  Size: 520 B

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-chat-right"><path d="M2 1a1 1 0 0 0-1 1v8a1 1 0 0 0 1 1h9.586a2 2 0 0 1 1.414.586l2 2V2a1 1 0 0 0-1-1H2zm12-1a2 2 0 0 1 2 2v12.793a.5.5 0 0 1-.854.353l-2.853-2.853a1 1 0 0 0-.707-.293H2a2 2 0 0 1-2-2V2a2 2 0 0 1 2-2h12z"/></svg>

After

Width:  |  Height:  |  Size: 324 B

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-chat-square-quote"><path d="M14 1a1 1 0 0 1 1 1v8a1 1 0 0 1-1 1h-2.5a2 2 0 0 0-1.6.8L8 14.333 6.1 11.8a2 2 0 0 0-1.6-.8H2a1 1 0 0 1-1-1V2a1 1 0 0 1 1-1h12zM2 0a2 2 0 0 0-2 2v8a2 2 0 0 0 2 2h2.5a1 1 0 0 1 .8.4l1.9 2.533a1 1 0 0 0 1.6 0l1.9-2.533a1 1 0 0 1 .8-.4H14a2 2 0 0 0 2-2V2a2 2 0 0 0-2-2H2z"/><path d="M7.066 4.76A1.665 1.665 0 0 0 4 5.668a1.667 1.667 0 0 0 2.561 1.406c-.131.389-.375.804-.777 1.22a.417.417 0 1 0 .6.58c1.486-1.54 1.293-3.214.682-4.112zm4 0A1.665 1.665 0 0 0 8 5.668a1.667 1.667 0 0 0 2.561 1.406c-.131.389-.375.804-.777 1.22a.417.417 0 1 0 .6.58c1.486-1.54 1.293-3.214.682-4.112z"/></svg>

After

Width:  |  Height:  |  Size: 708 B

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-chevron-down"><path fill-rule="evenodd" d="M1.646 4.646a.5.5 0 0 1 .708 0L8 10.293l5.646-5.647a.5.5 0 0 1 .708.708l-6 6a.5.5 0 0 1-.708 0l-6-6a.5.5 0 0 1 0-.708z"/></svg>

After

Width:  |  Height:  |  Size: 266 B

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-clipboard"><path d="M4 1.5H3a2 2 0 0 0-2 2V14a2 2 0 0 0 2 2h10a2 2 0 0 0 2-2V3.5a2 2 0 0 0-2-2h-1v1h1a1 1 0 0 1 1 1V14a1 1 0 0 1-1 1H3a1 1 0 0 1-1-1V3.5a1 1 0 0 1 1-1h1v-1z"/><path d="M9.5 1a.5.5 0 0 1 .5.5v1a.5.5 0 0 1-.5.5h-3a.5.5 0 0 1-.5-.5v-1a.5.5 0 0 1 .5-.5h3zm-3-1A1.5 1.5 0 0 0 5 1.5v1A1.5 1.5 0 0 0 6.5 4h3A1.5 1.5 0 0 0 11 2.5v-1A1.5 1.5 0 0 0 9.5 0h-3z"/></svg>

After

Width:  |  Height:  |  Size: 469 B

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><!--! Font Awesome Pro 6.1.1 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license (Commercial License) Copyright 2022 Fonticons, Inc.--><path d="M0 256C0 114.6 114.6 0 256 0s256 114.6 256 256-114.6 256-256 256S0 397.4 0 256zm175-47.9 47.1 47L175 303c-9.3 9.4-9.3 24.6 0 33.1 9.4 10.2 24.6 10.2 33.1 0l47-46.2 47.9 46.2c9.4 10.2 24.6 10.2 33.1 0 10.2-8.5 10.2-23.7 0-33.1l-46.2-47.9 46.2-47c10.2-8.5 10.2-23.7 0-33.1-8.5-9.3-23.7-9.3-33.1 0l-47.9 47.1-47-47.1c-8.5-9.3-23.7-9.3-33.1 0-9.3 9.4-9.3 24.6 0 33.1z"/></svg>

After

Width:  |  Height:  |  Size: 610 B

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-code"><path d="M5.854 4.854a.5.5 0 1 0-.708-.708l-3.5 3.5a.5.5 0 0 0 0 .708l3.5 3.5a.5.5 0 0 0 .708-.708L2.707 8l3.147-3.146zm4.292 0a.5.5 0 0 1 .708-.708l3.5 3.5a.5.5 0 0 1 0 .708l-3.5 3.5a.5.5 0 0 1-.708-.708L13.293 8l-3.147-3.146z"/></svg>

After

Width:  |  Height:  |  Size: 338 B

View File

@@ -0,0 +1 @@
<svg width="16" height="16" fill="none" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" clip-rule="evenodd" d="M13 5.885v1.166a3.95 3.95 0 0 1-3.949 3.95H6.917a.748.748 0 0 0-.45.15l-1.345 1.007a.752.752 0 0 0-.032 1.181A2.933 2.933 0 0 0 6.95 14h2.716l2.534 1.901a.506.506 0 0 0 .524.047A.501.501 0 0 0 13 15.5V14h.051a2.949 2.949 0 0 0 2.95-2.949v-3.05a3.002 3.002 0 0 0-2.002-2.83.756.756 0 0 0-.999.714" fill="#050505"/><path fill-rule="evenodd" clip-rule="evenodd" d="M9.05 1H2.95A2.952 2.952 0 0 0 0 3.949v3.102A2.952 2.952 0 0 0 2.949 10H3v1.5a.502.502 0 0 0 .8.4L6.334 10H9.05A2.952 2.952 0 0 0 12 7.05V3.95A2.952 2.952 0 0 0 9.05 1" fill="#050505"/></svg>

After

Width:  |  Height:  |  Size: 677 B

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1000 1000"><path d="M710 10H360c-38.6 0-70 31.4-70 70v630c0 38.6 31.4 70 70 70h490c38.6 0 70-31.4 70-70V220L710 10zm0 99 111 111H710V109zm140 601H360V80h280v210h210v420z"/><path d="M430 360h350v70H430v-70zm0 140h350v70H430v-70z"/><path d="M640 920H150V290h70v-70h-70c-38.6 0-70 31.4-70 70v630c0 38.6 31.4 70 70 70h490c38.6 0 70-31.4 70-70v-70h-70v70z"/></svg>

After

Width:  |  Height:  |  Size: 412 B

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-diagram-2"><path fill-rule="evenodd" d="M6 3.5A1.5 1.5 0 0 1 7.5 2h1A1.5 1.5 0 0 1 10 3.5v1A1.5 1.5 0 0 1 8.5 6v1H11a.5.5 0 0 1 .5.5v1a.5.5 0 0 1-1 0V8h-5v.5a.5.5 0 0 1-1 0v-1A.5.5 0 0 1 5 7h2.5V6A1.5 1.5 0 0 1 6 4.5v-1zM8.5 5a.5.5 0 0 0 .5-.5v-1a.5.5 0 0 0-.5-.5h-1a.5.5 0 0 0-.5.5v1a.5.5 0 0 0 .5.5h1zM3 11.5A1.5 1.5 0 0 1 4.5 10h1A1.5 1.5 0 0 1 7 11.5v1A1.5 1.5 0 0 1 5.5 14h-1A1.5 1.5 0 0 1 3 12.5v-1zm1.5-.5a.5.5 0 0 0-.5.5v1a.5.5 0 0 0 .5.5h1a.5.5 0 0 0 .5-.5v-1a.5.5 0 0 0-.5-.5h-1zm4.5.5a1.5 1.5 0 0 1 1.5-1.5h1a1.5 1.5 0 0 1 1.5 1.5v1a1.5 1.5 0 0 1-1.5 1.5h-1A1.5 1.5 0 0 1 9 12.5v-1zm1.5-.5a.5.5 0 0 0-.5.5v1a.5.5 0 0 0 .5.5h1a.5.5 0 0 0 .5-.5v-1a.5.5 0 0 0-.5-.5h-1z"/></svg>

After

Width:  |  Height:  |  Size: 782 B

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-download"><path d="M.5 9.9a.5.5 0 0 1 .5.5v2.5a1 1 0 0 0 1 1h12a1 1 0 0 0 1-1v-2.5a.5.5 0 0 1 1 0v2.5a2 2 0 0 1-2 2H2a2 2 0 0 1-2-2v-2.5a.5.5 0 0 1 .5-.5z"/><path d="M7.646 11.854a.5.5 0 0 0 .708 0l3-3a.5.5 0 0 0-.708-.708L8.5 10.293V1.5a.5.5 0 0 0-1 0v8.793L5.354 8.146a.5.5 0 1 0-.708.708l3 3z"/></svg>

After

Width:  |  Height:  |  Size: 400 B

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" data-name="Layer 1" viewBox="0 0 24 24" fill="currentColor"><path stroke="currentColor" d="M8.5 10a2 2 0 1 0 2 2 2 2 0 0 0-2-2Zm0 7a2 2 0 1 0 2 2 2 2 0 0 0-2-2Zm7-10a2 2 0 1 0-2-2 2 2 0 0 0 2 2Zm-7-4a2 2 0 1 0 2 2 2 2 0 0 0-2-2Zm7 14a2 2 0 1 0 2 2 2 2 0 0 0-2-2Zm0-7a2 2 0 1 0 2 2 2 2 0 0 0-2-2Z"/></svg>

After

Width:  |  Height:  |  Size: 344 B

View File

@@ -0,0 +1 @@
<svg width="16" height="16" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M4.648 4.475 1.824 12.25H.67l3.252-8.531h.744l-.018.756Zm2.368 7.775-2.83-7.775-.018-.756h.744l3.264 8.531h-1.16Zm-.147-3.158v.926H2.076v-.926H6.87Zm6.024 2.074V7.902c0-.25-.051-.466-.153-.65a.997.997 0 0 0-.445-.434c-.2-.101-.445-.152-.738-.152-.274 0-.514.047-.721.14a1.255 1.255 0 0 0-.48.37.809.809 0 0 0-.17.492H9.101c0-.227.058-.451.175-.674.118-.223.286-.424.504-.603.223-.184.489-.329.797-.434.313-.11.66-.164 1.043-.164.461 0 .867.078 1.219.234.355.157.633.393.832.71.203.312.305.704.305 1.177v2.953c0 .211.017.436.052.674.04.238.096.443.17.615v.094h-1.13a2.022 2.022 0 0 1-.13-.498 4.011 4.011 0 0 1-.046-.586Zm.187-2.76.012.762h-1.096c-.309 0-.584.025-.826.076a1.89 1.89 0 0 0-.61.217.979.979 0 0 0-.504.879c0 .2.046.38.135.545a.98.98 0 0 0 .405.392c.183.094.408.141.674.141.332 0 .625-.07.878-.211a1.83 1.83 0 0 0 .604-.516c.152-.203.234-.4.246-.591l.463.521a1.572 1.572 0 0 1-.223.545 2.607 2.607 0 0 1-1.2 1.025 2.328 2.328 0 0 1-.927.176c-.43 0-.806-.084-1.13-.252a1.933 1.933 0 0 1-.75-.674 1.784 1.784 0 0 1-.264-.955c0-.34.066-.638.199-.896a1.73 1.73 0 0 1 .574-.65c.25-.176.551-.31.903-.399a4.76 4.76 0 0 1 1.177-.135h1.26Z" fill="#000"/></svg>

After

Width:  |  Height:  |  Size: 1.2 KiB

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 384 512"><!--! Font Awesome Pro 6.1.1 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license (Commercial License) Copyright 2022 Fonticons, Inc.--><path d="M14 95.792C14 42.888 56.888 0 109.793 0h164.368c52.905 0 95.793 42.888 95.793 95.792 0 33.5-17.196 62.984-43.243 80.105 26.047 17.122 43.243 46.605 43.243 80.105 0 52.905-42.888 95.793-95.793 95.793h-2.08c-24.802 0-47.403-9.426-64.415-24.891v88.263c0 53.61-44.009 96.833-97.357 96.833C57.536 512 14 469.243 14 416.207c0-33.498 17.195-62.98 43.24-80.102C31.193 318.983 14 289.5 14 256.002c0-33.5 17.196-62.983 43.242-80.105C31.197 158.776 14 129.292 14 95.792Zm162.288 95.795h-66.495c-35.576 0-64.415 28.84-64.415 64.415 0 35.438 28.617 64.192 64.003 64.414l.412-.001h66.495V191.587Zm31.378 64.415c0 35.575 28.839 64.415 64.415 64.415h2.08c35.576 0 64.415-28.84 64.415-64.415s-28.839-64.415-64.415-64.415h-2.08c-35.576 0-64.415 28.84-64.415 64.415Zm-97.873 95.793-.412-.001c-35.386.221-64.003 28.975-64.003 64.413 0 35.445 29.225 64.415 64.931 64.415 36.282 0 65.979-29.436 65.979-65.455v-63.372h-66.495Zm0-320.417c-35.576 0-64.415 28.84-64.415 64.414 0 35.576 28.84 64.415 64.415 64.415h66.495V31.377h-66.495Zm97.873 128.829h66.495c35.576 0 64.415-28.839 64.415-64.415 0-35.575-28.839-64.414-64.415-64.414h-66.495v128.829Z"/></svg>

After

Width:  |  Height:  |  Size: 1.3 KiB

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-file-image"><path d="M8.002 5.5a1.5 1.5 0 1 1-3 0 1.5 1.5 0 0 1 3 0z"/><path d="M12 0H4a2 2 0 0 0-2 2v12a2 2 0 0 0 2 2h8a2 2 0 0 0 2-2V2a2 2 0 0 0-2-2zM3 2a1 1 0 0 1 1-1h8a1 1 0 0 1 1 1v8l-2.083-2.083a.5.5 0 0 0-.76.063L8 11 5.835 9.7a.5.5 0 0 0-.611.076L3 12V2z"/></svg>

After

Width:  |  Height:  |  Size: 367 B

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-filetype-gif"><path fill-rule="evenodd" d="M14 4.5V14a2 2 0 0 1-2 2H9v-1h3a1 1 0 0 0 1-1V4.5h-2A1.5 1.5 0 0 1 9.5 3V1H4a1 1 0 0 0-1 1v9H2V2a2 2 0 0 1 2-2h5.5L14 4.5ZM3.278 13.124a1.403 1.403 0 0 0-.14-.492 1.317 1.317 0 0 0-.314-.407 1.447 1.447 0 0 0-.48-.275 1.88 1.88 0 0 0-.636-.1c-.361 0-.67.076-.926.229a1.48 1.48 0 0 0-.583.632 2.136 2.136 0 0 0-.199.95v.506c0 .272.035.52.105.745.07.224.177.417.32.58.142.162.32.288.533.377.215.088.466.132.753.132.268 0 .5-.037.697-.111a1.29 1.29 0 0 0 .788-.77c.065-.174.097-.358.097-.551v-.797H1.717v.589h.823v.255c0 .132-.03.254-.09.363a.67.67 0 0 1-.273.264.967.967 0 0 1-.457.096.87.87 0 0 1-.519-.146.881.881 0 0 1-.305-.413 1.785 1.785 0 0 1-.096-.615v-.499c0-.365.078-.648.234-.85.158-.2.38-.301.665-.301a.96.96 0 0 1 .3.044c.09.03.17.071.236.126a.689.689 0 0 1 .17.19.797.797 0 0 1 .097.25h.776Zm1.353 2.801v-3.999H3.84v4h.79Zm1.493-1.59v1.59h-.791v-3.999H7.88v.653H6.124v1.117h1.605v.638H6.124Z"/></svg>

After

Width:  |  Height:  |  Size: 1.0 KiB

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 512 512"><path fill="#777" d="M221.631 109 109.92 392h58.055l24.079-61h127.892l24.079 61h58.055L290.369 109Zm-8.261 168L256 169l42.63 108Z"/></svg>

After

Width:  |  Height:  |  Size: 223 B

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-fonts"><path d="M12.258 3h-8.51l-.083 2.46h.479c.26-1.544.758-1.783 2.693-1.845l.424-.013v7.827c0 .663-.144.82-1.3.923v.52h4.082v-.52c-1.162-.103-1.306-.26-1.306-.923V3.602l.431.013c1.934.062 2.434.301 2.693 1.846h.479L12.258 3z"/></svg>

After

Width:  |  Height:  |  Size: 333 B

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-gear"><path d="M8 4.754a3.246 3.246 0 1 0 0 6.492 3.246 3.246 0 0 0 0-6.492zM5.754 8a2.246 2.246 0 1 1 4.492 0 2.246 2.246 0 0 1-4.492 0z"/><path d="M9.796 1.343c-.527-1.79-3.065-1.79-3.592 0l-.094.319a.873.873 0 0 1-1.255.52l-.292-.16c-1.64-.892-3.433.902-2.54 2.541l.159.292a.873.873 0 0 1-.52 1.255l-.319.094c-1.79.527-1.79 3.065 0 3.592l.319.094a.873.873 0 0 1 .52 1.255l-.16.292c-.892 1.64.901 3.434 2.541 2.54l.292-.159a.873.873 0 0 1 1.255.52l.094.319c.527 1.79 3.065 1.79 3.592 0l.094-.319a.873.873 0 0 1 1.255-.52l.292.16c1.64.893 3.434-.902 2.54-2.541l-.159-.292a.873.873 0 0 1 .52-1.255l.319-.094c1.79-.527 1.79-3.065 0-3.592l-.319-.094a.873.873 0 0 1-.52-1.255l.16-.292c.893-1.64-.902-3.433-2.541-2.54l-.292.159a.873.873 0 0 1-1.255-.52l-.094-.319zm-2.633.283c.246-.835 1.428-.835 1.674 0l.094.319a1.873 1.873 0 0 0 2.693 1.115l.291-.16c.764-.415 1.6.42 1.184 1.185l-.159.292a1.873 1.873 0 0 0 1.116 2.692l.318.094c.835.246.835 1.428 0 1.674l-.319.094a1.873 1.873 0 0 0-1.115 2.693l.16.291c.415.764-.42 1.6-1.185 1.184l-.291-.159a1.873 1.873 0 0 0-2.693 1.116l-.094.318c-.246.835-1.428.835-1.674 0l-.094-.319a1.873 1.873 0 0 0-2.692-1.115l-.292.16c-.764.415-1.6-.42-1.184-1.185l.159-.291A1.873 1.873 0 0 0 1.945 8.93l-.319-.094c-.835-.246-.835-1.428 0-1.674l.319-.094A1.873 1.873 0 0 0 3.06 4.377l-.16-.292c-.415-.764.42-1.6 1.185-1.184l.292.159a1.873 1.873 0 0 0 2.692-1.115l.094-.319z"/></svg>

After

Width:  |  Height:  |  Size: 1.5 KiB

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-file-break"><path d="M0 10.5a.5.5 0 0 1 .5-.5h15a.5.5 0 0 1 0 1H.5a.5.5 0 0 1-.5-.5zM12 0H4a2 2 0 0 0-2 2v7h1V2a1 1 0 0 1 1-1h8a1 1 0 0 1 1 1v7h1V2a2 2 0 0 0-2-2zm2 12h-1v2a1 1 0 0 1-1 1H4a1 1 0 0 1-1-1v-2H2v2a2 2 0 0 0 2 2h8a2 2 0 0 0 2-2v-2z"/></svg>

After

Width:  |  Height:  |  Size: 348 B

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-text-indent-left"><path d="M2 3.5a.5.5 0 0 1 .5-.5h11a.5.5 0 0 1 0 1h-11a.5.5 0 0 1-.5-.5zm.646 2.146a.5.5 0 0 1 .708 0l2 2a.5.5 0 0 1 0 .708l-2 2a.5.5 0 0 1-.708-.708L4.293 8 2.646 6.354a.5.5 0 0 1 0-.708zM7 6.5a.5.5 0 0 1 .5-.5h6a.5.5 0 0 1 0 1h-6a.5.5 0 0 1-.5-.5zm0 3a.5.5 0 0 1 .5-.5h6a.5.5 0 0 1 0 1h-6a.5.5 0 0 1-.5-.5zm-5 3a.5.5 0 0 1 .5-.5h11a.5.5 0 0 1 0 1h-11a.5.5 0 0 1-.5-.5z"/></svg>

After

Width:  |  Height:  |  Size: 493 B

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-journal-code"><path fill-rule="evenodd" d="M8.646 5.646a.5.5 0 0 1 .708 0l2 2a.5.5 0 0 1 0 .708l-2 2a.5.5 0 0 1-.708-.708L10.293 8 8.646 6.354a.5.5 0 0 1 0-.708zm-1.292 0a.5.5 0 0 0-.708 0l-2 2a.5.5 0 0 0 0 .708l2 2a.5.5 0 0 0 .708-.708L5.707 8l1.647-1.646a.5.5 0 0 0 0-.708z"/><path d="M3 0h10a2 2 0 0 1 2 2v12a2 2 0 0 1-2 2H3a2 2 0 0 1-2-2v-1h1v1a1 1 0 0 0 1 1h10a1 1 0 0 0 1-1V2a1 1 0 0 0-1-1H3a1 1 0 0 0-1 1v1H1V2a2 2 0 0 1 2-2z"/><path d="M1 5v-.5a.5.5 0 0 1 1 0V5h.5a.5.5 0 0 1 0 1h-2a.5.5 0 0 1 0-1H1zm0 3v-.5a.5.5 0 0 1 1 0V8h.5a.5.5 0 0 1 0 1h-2a.5.5 0 0 1 0-1H1zm0 3v-.5a.5.5 0 0 1 1 0v.5h.5a.5.5 0 0 1 0 1h-2a.5.5 0 0 1 0-1H1z"/></svg>

After

Width:  |  Height:  |  Size: 742 B

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-journal-text"><path d="M5 10.5a.5.5 0 0 1 .5-.5h2a.5.5 0 0 1 0 1h-2a.5.5 0 0 1-.5-.5zm0-2a.5.5 0 0 1 .5-.5h5a.5.5 0 0 1 0 1h-5a.5.5 0 0 1-.5-.5zm0-2a.5.5 0 0 1 .5-.5h5a.5.5 0 0 1 0 1h-5a.5.5 0 0 1-.5-.5zm0-2a.5.5 0 0 1 .5-.5h5a.5.5 0 0 1 0 1h-5a.5.5 0 0 1-.5-.5z"/><path d="M3 0h10a2 2 0 0 1 2 2v12a2 2 0 0 1-2 2H3a2 2 0 0 1-2-2v-1h1v1a1 1 0 0 0 1 1h10a1 1 0 0 0 1-1V2a1 1 0 0 0-1-1H3a1 1 0 0 0-1 1v1H1V2a2 2 0 0 1 2-2z"/><path d="M1 5v-.5a.5.5 0 0 1 1 0V5h.5a.5.5 0 0 1 0 1h-2a.5.5 0 0 1 0-1H1zm0 3v-.5a.5.5 0 0 1 1 0V8h.5a.5.5 0 0 1 0 1h-2a.5.5 0 0 1 0-1H1zm0 3v-.5a.5.5 0 0 1 1 0v.5h.5a.5.5 0 0 1 0 1h-2a.5.5 0 0 1 0-1H1z"/></svg>

After

Width:  |  Height:  |  Size: 729 B

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-justify"><path fill-rule="evenodd" d="M2 12.5a.5.5 0 0 1 .5-.5h11a.5.5 0 0 1 0 1h-11a.5.5 0 0 1-.5-.5zm0-3a.5.5 0 0 1 .5-.5h11a.5.5 0 0 1 0 1h-11a.5.5 0 0 1-.5-.5zm0-3a.5.5 0 0 1 .5-.5h11a.5.5 0 0 1 0 1h-11a.5.5 0 0 1-.5-.5zm0-3a.5.5 0 0 1 .5-.5h11a.5.5 0 0 1 0 1h-11a.5.5 0 0 1-.5-.5z"/></svg>

After

Width:  |  Height:  |  Size: 390 B

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-link"><path d="M6.354 5.5H4a3 3 0 0 0 0 6h3a3 3 0 0 0 2.83-4H9c-.086 0-.17.01-.25.031A2 2 0 0 1 7 10.5H4a2 2 0 1 1 0-4h1.535c.218-.376.495-.714.82-1z"/><path d="M9 5.5a3 3 0 0 0-2.83 4h1.098A2 2 0 0 1 9 6.5h3a2 2 0 1 1 0 4h-1.535a4.02 4.02 0 0 1-.82 1H12a3 3 0 1 0 0-6H9z"/></svg>

After

Width:  |  Height:  |  Size: 376 B

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-list-ol"><path fill-rule="evenodd" d="M5 11.5a.5.5 0 0 1 .5-.5h9a.5.5 0 0 1 0 1h-9a.5.5 0 0 1-.5-.5zm0-4a.5.5 0 0 1 .5-.5h9a.5.5 0 0 1 0 1h-9a.5.5 0 0 1-.5-.5zm0-4a.5.5 0 0 1 .5-.5h9a.5.5 0 0 1 0 1h-9a.5.5 0 0 1-.5-.5z"/><path d="M1.713 11.865v-.474H2c.217 0 .363-.137.363-.317 0-.185-.158-.31-.361-.31-.223 0-.367.152-.373.31h-.59c.016-.467.373-.787.986-.787.588-.002.954.291.957.703a.595.595 0 0 1-.492.594v.033a.615.615 0 0 1 .569.631c.003.533-.502.8-1.051.8-.656 0-1-.37-1.008-.794h.582c.008.178.186.306.422.309.254 0 .424-.145.422-.35-.002-.195-.155-.348-.414-.348h-.3zm-.004-4.699h-.604v-.035c0-.408.295-.844.958-.844.583 0 .96.326.96.756 0 .389-.257.617-.476.848l-.537.572v.03h1.054V9H1.143v-.395l.957-.99c.138-.142.293-.304.293-.508 0-.18-.147-.32-.342-.32a.33.33 0 0 0-.342.338v.041zM2.564 5h-.635V2.924h-.031l-.598.42v-.567l.629-.443h.635V5z"/></svg>

After

Width:  |  Height:  |  Size: 956 B

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-list-ul"><path fill-rule="evenodd" d="M5 11.5a.5.5 0 0 1 .5-.5h9a.5.5 0 0 1 0 1h-9a.5.5 0 0 1-.5-.5zm0-4a.5.5 0 0 1 .5-.5h9a.5.5 0 0 1 0 1h-9a.5.5 0 0 1-.5-.5zm0-4a.5.5 0 0 1 .5-.5h9a.5.5 0 0 1 0 1h-9a.5.5 0 0 1-.5-.5zm-3 1a1 1 0 1 0 0-2 1 1 0 0 0 0 2zm0 4a1 1 0 1 0 0-2 1 1 0 0 0 0 2zm0 4a1 1 0 1 0 0-2 1 1 0 0 0 0 2z"/></svg>

After

Width:  |  Height:  |  Size: 423 B

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-lock-fill"><path d="M8 1a2 2 0 0 1 2 2v4H6V3a2 2 0 0 1 2-2zm3 6V3a3 3 0 0 0-6 0v4a2 2 0 0 0-2 2v5a2 2 0 0 0 2 2h6a2 2 0 0 0 2-2V9a2 2 0 0 0-2-2z"/></svg>

After

Width:  |  Height:  |  Size: 249 B

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-lock"><path d="M8 1a2 2 0 0 1 2 2v4H6V3a2 2 0 0 1 2-2zm3 6V3a3 3 0 0 0-6 0v4a2 2 0 0 0-2 2v5a2 2 0 0 0 2 2h6a2 2 0 0 0 2-2V9a2 2 0 0 0-2-2zM5 8h6a1 1 0 0 1 1 1v5a1 1 0 0 1-1 1H5a1 1 0 0 1-1-1V9a1 1 0 0 1 1-1z"/></svg>

After

Width:  |  Height:  |  Size: 313 B

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-markdown"><path d="M14 3a1 1 0 0 1 1 1v8a1 1 0 0 1-1 1H2a1 1 0 0 1-1-1V4a1 1 0 0 1 1-1h12zM2 2a2 2 0 0 0-2 2v8a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V4a2 2 0 0 0-2-2H2z"/><path fill-rule="evenodd" d="M9.146 8.146a.5.5 0 0 1 .708 0L11.5 9.793l1.646-1.647a.5.5 0 0 1 .708.708l-2 2a.5.5 0 0 1-.708 0l-2-2a.5.5 0 0 1 0-.708z"/><path fill-rule="evenodd" d="M11.5 5a.5.5 0 0 1 .5.5v4a.5.5 0 0 1-1 0v-4a.5.5 0 0 1 .5-.5z"/><path d="M3.56 11V7.01h.056l1.428 3.239h.774l1.42-3.24h.056V11h1.073V5.001h-1.2l-1.71 3.894h-.039l-1.71-3.894H2.5V11h1.06z"/></svg>

After

Width:  |  Height:  |  Size: 635 B

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-mic"><path d="M3.5 6.5A.5.5 0 0 1 4 7v1a4 4 0 0 0 8 0V7a.5.5 0 0 1 1 0v1a5 5 0 0 1-4.5 4.975V15h3a.5.5 0 0 1 0 1h-7a.5.5 0 0 1 0-1h3v-2.025A5 5 0 0 1 3 8V7a.5.5 0 0 1 .5-.5z"/><path d="M10 8a2 2 0 1 1-4 0V3a2 2 0 1 1 4 0v5zM8 0a3 3 0 0 0-3 3v5a3 3 0 0 0 6 0V3a3 3 0 0 0-3-3z"/></svg>

After

Width:  |  Height:  |  Size: 379 B

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-text-indent-right"><path d="M2 3.5a.5.5 0 0 1 .5-.5h11a.5.5 0 0 1 0 1h-11a.5.5 0 0 1-.5-.5zm10.646 2.146a.5.5 0 0 1 .708.708L11.707 8l1.647 1.646a.5.5 0 0 1-.708.708l-2-2a.5.5 0 0 1 0-.708l2-2zM2 6.5a.5.5 0 0 1 .5-.5h6a.5.5 0 0 1 0 1h-6a.5.5 0 0 1-.5-.5zm0 3a.5.5 0 0 1 .5-.5h6a.5.5 0 0 1 0 1h-6a.5.5 0 0 1-.5-.5zm0 3a.5.5 0 0 1 .5-.5h11a.5.5 0 0 1 0 1h-11a.5.5 0 0 1-.5-.5z"/></svg>

After

Width:  |  Height:  |  Size: 479 B

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-paint-bucket"><path d="M6.192 2.78c-.458-.677-.927-1.248-1.35-1.643a2.972 2.972 0 0 0-.71-.515c-.217-.104-.56-.205-.882-.02-.367.213-.427.63-.43.896-.003.304.064.664.173 1.044.196.687.556 1.528 1.035 2.402L.752 8.22c-.277.277-.269.656-.218.918.055.283.187.593.36.903.348.627.92 1.361 1.626 2.068.707.707 1.441 1.278 2.068 1.626.31.173.62.305.903.36.262.05.64.059.918-.218l5.615-5.615c.118.257.092.512.05.939-.03.292-.068.665-.073 1.176v.123h.003a1 1 0 0 0 1.993 0H14v-.057a1.01 1.01 0 0 0-.004-.117c-.055-1.25-.7-2.738-1.86-3.494a4.322 4.322 0 0 0-.211-.434c-.349-.626-.92-1.36-1.627-2.067-.707-.707-1.441-1.279-2.068-1.627-.31-.172-.62-.304-.903-.36-.262-.05-.64-.058-.918.219l-.217.216zM4.16 1.867c.381.356.844.922 1.311 1.632l-.704.705c-.382-.727-.66-1.402-.813-1.938a3.283 3.283 0 0 1-.131-.673c.091.061.204.15.337.274zm.394 3.965c.54.852 1.107 1.567 1.607 2.033a.5.5 0 1 0 .682-.732c-.453-.422-1.017-1.136-1.564-2.027l1.088-1.088c.054.12.115.243.183.365.349.627.92 1.361 1.627 2.068.706.707 1.44 1.278 2.068 1.626.122.068.244.13.365.183l-4.861 4.862a.571.571 0 0 1-.068-.01c-.137-.027-.342-.104-.608-.252-.524-.292-1.186-.8-1.846-1.46-.66-.66-1.168-1.32-1.46-1.846-.147-.265-.225-.47-.251-.607a.573.573 0 0 1-.01-.068l3.048-3.047zm2.87-1.935a2.44 2.44 0 0 1-.241-.561c.135.033.324.11.562.241.524.292 1.186.8 1.846 1.46.45.45.83.901 1.118 1.31a3.497 3.497 0 0 0-1.066.091 11.27 11.27 0 0 1-.76-.694c-.66-.66-1.167-1.322-1.458-1.847z"/></svg>

After

Width:  |  Height:  |  Size: 1.5 KiB

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-palette"><path d="M8 5a1.5 1.5 0 1 0 0-3 1.5 1.5 0 0 0 0 3zm4 3a1.5 1.5 0 1 0 0-3 1.5 1.5 0 0 0 0 3zM5.5 7a1.5 1.5 0 1 1-3 0 1.5 1.5 0 0 1 3 0zm.5 6a1.5 1.5 0 1 0 0-3 1.5 1.5 0 0 0 0 3z"/><path d="M16 8c0 3.15-1.866 2.585-3.567 2.07C11.42 9.763 10.465 9.473 10 10c-.603.683-.475 1.819-.351 2.92C9.826 14.495 9.996 16 8 16a8 8 0 1 1 8-8zm-8 7c.611 0 .654-.171.655-.176.078-.146.124-.464.07-1.119-.014-.168-.037-.37-.061-.591-.052-.464-.112-1.005-.118-1.462-.01-.707.083-1.61.704-2.314.369-.417.845-.578 1.272-.618.404-.038.812.026 1.16.104.343.077.702.186 1.025.284l.028.008c.346.105.658.199.953.266.653.148.904.083.991.024C14.717 9.38 15 9.161 15 8a7 7 0 1 0-7 7z"/></svg>

After

Width:  |  Height:  |  Size: 768 B

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-pencil-fill"><path d="M12.854.146a.5.5 0 0 0-.707 0L10.5 1.793 14.207 5.5l1.647-1.646a.5.5 0 0 0 0-.708l-3-3zm.646 6.061L9.793 2.5 3.293 9H3.5a.5.5 0 0 1 .5.5v.5h.5a.5.5 0 0 1 .5.5v.5h.5a.5.5 0 0 1 .5.5v.5h.5a.5.5 0 0 1 .5.5v.207l6.5-6.5zm-7.468 7.468A.5.5 0 0 1 6 13.5V13h-.5a.5.5 0 0 1-.5-.5V12h-.5a.5.5 0 0 1-.5-.5V11h-.5a.5.5 0 0 1-.5-.5V10h-.5a.499.499 0 0 1-.175-.032l-.179.178a.5.5 0 0 0-.11.168l-2 5a.5.5 0 0 0 .65.65l5-2a.5.5 0 0 0 .168-.11l.178-.178z"/></svg>

After

Width:  |  Height:  |  Size: 565 B

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-plug-fill"><path d="M6 0a.5.5 0 0 1 .5.5V3h3V.5a.5.5 0 0 1 1 0V3h1a.5.5 0 0 1 .5.5v3A3.5 3.5 0 0 1 8.5 10c-.002.434-.01.845-.04 1.22-.041.514-.126 1.003-.317 1.424a2.083 2.083 0 0 1-.97 1.028C6.725 13.9 6.169 14 5.5 14c-.998 0-1.61.33-1.974.718A1.922 1.922 0 0 0 3 16H2c0-.616.232-1.367.797-1.968C3.374 13.42 4.261 13 5.5 13c.581 0 .962-.088 1.218-.219.241-.123.4-.3.514-.55.121-.266.193-.621.23-1.09.027-.34.035-.718.037-1.141A3.5 3.5 0 0 1 4 6.5v-3a.5.5 0 0 1 .5-.5h1V.5A.5.5 0 0 1 6 0z"/></svg>

After

Width:  |  Height:  |  Size: 593 B

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-plug"><path d="M6 0a.5.5 0 0 1 .5.5V3h3V.5a.5.5 0 0 1 1 0V3h1a.5.5 0 0 1 .5.5v3A3.5 3.5 0 0 1 8.5 10c-.002.434-.01.845-.04 1.22-.041.514-.126 1.003-.317 1.424a2.083 2.083 0 0 1-.97 1.028C6.725 13.9 6.169 14 5.5 14c-.998 0-1.61.33-1.974.718A1.922 1.922 0 0 0 3 16H2c0-.616.232-1.367.797-1.968C3.374 13.42 4.261 13 5.5 13c.581 0 .962-.088 1.218-.219.241-.123.4-.3.514-.55.121-.266.193-.621.23-1.09.027-.34.035-.718.037-1.141A3.5 3.5 0 0 1 4 6.5v-3a.5.5 0 0 1 .5-.5h1V.5A.5.5 0 0 1 6 0zM5 4v2.5A2.5 2.5 0 0 0 7.5 9h1A2.5 2.5 0 0 0 11 6.5V4H5z"/></svg>

After

Width:  |  Height:  |  Size: 644 B

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-plus-slash-minus"><path d="m1.854 14.854 13-13a.5.5 0 0 0-.708-.708l-13 13a.5.5 0 0 0 .708.708ZM4 1a.5.5 0 0 1 .5.5v2h2a.5.5 0 0 1 0 1h-2v2a.5.5 0 0 1-1 0v-2h-2a.5.5 0 0 1 0-1h2v-2A.5.5 0 0 1 4 1Zm5 11a.5.5 0 0 1 .5-.5h5a.5.5 0 0 1 0 1h-5A.5.5 0 0 1 9 12Z"/></svg>

After

Width:  |  Height:  |  Size: 360 B

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-plus"><path d="M8 4a.5.5 0 0 1 .5.5v3h3a.5.5 0 0 1 0 1h-3v3a.5.5 0 0 1-1 0v-3h-3a.5.5 0 0 1 0-1h3v-3A.5.5 0 0 1 8 4z"/></svg>

After

Width:  |  Height:  |  Size: 221 B

View File

@@ -0,0 +1 @@
<svg style="color:red" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path d="M8.571 23.429A.571.571 0 0 1 8 24H2.286a.571.571 0 0 1 0-1.143H8c.316 0 .571.256.571.572zM8 20.57H6.857a.571.571 0 0 0 0 1.143H8a.571.571 0 0 0 0-1.143zm-5.714 1.143H4.57a.571.571 0 0 0 0-1.143H2.286a.571.571 0 0 0 0 1.143zM8 18.286H2.286a.571.571 0 0 0 0 1.143H8a.571.571 0 0 0 0-1.143zM16 16H5.714a.571.571 0 0 0 0 1.143H16A.571.571 0 0 0 16 16zM2.286 17.143h1.143a.571.571 0 0 0 0-1.143H2.286a.571.571 0 0 0 0 1.143zm17.143-3.429H16a.571.571 0 0 0 0 1.143h3.429a.571.571 0 0 0 0-1.143zM9.143 14.857h4.571a.571.571 0 0 0 0-1.143H9.143a.571.571 0 0 0 0 1.143zm-6.857 0h4.571a.571.571 0 0 0 0-1.143H2.286a.571.571 0 0 0 0 1.143zM20.57 11.43h-9.14a.571.571 0 0 0 0 1.142h9.142a.571.571 0 0 0 0-1.142zM9.714 12a.571.571 0 0 0-.571-.571H5.714a.571.571 0 0 0 0 1.142h3.429A.571.571 0 0 0 9.714 12zm-7.428.571h1.143a.571.571 0 0 0 0-1.142H2.286a.571.571 0 0 0 0 1.142zm19.428-3.428H16a.571.571 0 0 0 0 1.143h5.714a.571.571 0 0 0 0-1.143zM2.286 10.286H8a.571.571 0 0 0 0-1.143H2.286a.571.571 0 0 0 0 1.143zm13.143-2.857A.57.57 0 0 0 16 8h5.714a.571.571 0 0 0 0-1.143H16a.571.571 0 0 0-.571.572zm-8.572-.572a.571.571 0 0 0 0 1.143H8a.571.571 0 0 0 0-1.143H6.857zM2.286 8H4.57a.571.571 0 0 0 0-1.143H2.286a.571.571 0 0 0 0 1.143zm16.571-2.857c0 .315.256.571.572.571h1.142a.571.571 0 0 0 0-1.143H19.43a.571.571 0 0 0-.572.572zm-1.143 0a.571.571 0 0 0-.571-.572H12.57a.571.571 0 0 0 0 1.143h4.572a.571.571 0 0 0 .571-.571zm-15.428.571h8a.571.571 0 0 0 0-1.143h-8a.571.571 0 0 0 0 1.143zm5.143-2.857c0 .316.255.572.571.572h11.429a.571.571 0 0 0 0-1.143H8a.571.571 0 0 0-.571.571zm-5.143.572h3.428a.571.571 0 0 0 0-1.143H2.286a.571.571 0 0 0 0 1.143zm0-2.286H16A.571.571 0 0 0 16 0H2.286a.571.571 0 0 0 0 1.143z" fill="red"/></svg>

After

Width:  |  Height:  |  Size: 1.8 KiB

View File

@@ -0,0 +1 @@
<svg viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path d="M8.571 23.429A.571.571 0 0 1 8 24H2.286a.571.571 0 0 1 0-1.143H8c.316 0 .571.256.571.572zM8 20.57H6.857a.571.571 0 0 0 0 1.143H8a.571.571 0 0 0 0-1.143zm-5.714 1.143H4.57a.571.571 0 0 0 0-1.143H2.286a.571.571 0 0 0 0 1.143zM8 18.286H2.286a.571.571 0 0 0 0 1.143H8a.571.571 0 0 0 0-1.143zM16 16H5.714a.571.571 0 0 0 0 1.143H16A.571.571 0 0 0 16 16zM2.286 17.143h1.143a.571.571 0 0 0 0-1.143H2.286a.571.571 0 0 0 0 1.143zm17.143-3.429H16a.571.571 0 0 0 0 1.143h3.429a.571.571 0 0 0 0-1.143zM9.143 14.857h4.571a.571.571 0 0 0 0-1.143H9.143a.571.571 0 0 0 0 1.143zm-6.857 0h4.571a.571.571 0 0 0 0-1.143H2.286a.571.571 0 0 0 0 1.143zM20.57 11.43h-9.14a.571.571 0 0 0 0 1.142h9.142a.571.571 0 0 0 0-1.142zM9.714 12a.571.571 0 0 0-.571-.571H5.714a.571.571 0 0 0 0 1.142h3.429A.571.571 0 0 0 9.714 12zm-7.428.571h1.143a.571.571 0 0 0 0-1.142H2.286a.571.571 0 0 0 0 1.142zm19.428-3.428H16a.571.571 0 0 0 0 1.143h5.714a.571.571 0 0 0 0-1.143zM2.286 10.286H8a.571.571 0 0 0 0-1.143H2.286a.571.571 0 0 0 0 1.143zm13.143-2.857A.57.57 0 0 0 16 8h5.714a.571.571 0 0 0 0-1.143H16a.571.571 0 0 0-.571.572zm-8.572-.572a.571.571 0 0 0 0 1.143H8a.571.571 0 0 0 0-1.143H6.857zM2.286 8H4.57a.571.571 0 0 0 0-1.143H2.286a.571.571 0 0 0 0 1.143zm16.571-2.857c0 .315.256.571.572.571h1.142a.571.571 0 0 0 0-1.143H19.43a.571.571 0 0 0-.572.572zm-1.143 0a.571.571 0 0 0-.571-.572H12.57a.571.571 0 0 0 0 1.143h4.572a.571.571 0 0 0 .571-.571zm-15.428.571h8a.571.571 0 0 0 0-1.143h-8a.571.571 0 0 0 0 1.143zm5.143-2.857c0 .316.255.572.571.572h11.429a.571.571 0 0 0 0-1.143H8a.571.571 0 0 0-.571.571zm-5.143.572h3.428a.571.571 0 0 0 0-1.143H2.286a.571.571 0 0 0 0 1.143zm0-2.286H16A.571.571 0 0 0 16 0H2.286a.571.571 0 0 0 0 1.143z"/></svg>

After

Width:  |  Height:  |  Size: 1.7 KiB

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-send"><path d="M15.854.146a.5.5 0 0 1 .11.54l-5.819 14.547a.75.75 0 0 1-1.329.124l-3.178-4.995L.643 7.184a.75.75 0 0 1 .124-1.33L15.314.037a.5.5 0 0 1 .54.11ZM6.636 10.07l2.761 4.338L14.13 2.576 6.636 10.07Zm6.787-8.201L1.591 6.602l4.339 2.76 7.494-7.493Z"/></svg>

After

Width:  |  Height:  |  Size: 360 B

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-check-square"><path d="M14 1a1 1 0 0 1 1 1v12a1 1 0 0 1-1 1H2a1 1 0 0 1-1-1V2a1 1 0 0 1 1-1h12zM2 0a2 2 0 0 0-2 2v12a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V2a2 2 0 0 0-2-2H2z"/><path d="M10.97 4.97a.75.75 0 0 1 1.071 1.05l-3.992 4.99a.75.75 0 0 1-1.08.02L4.324 8.384a.75.75 0 1 1 1.06-1.06l2.094 2.093 3.473-4.425a.235.235 0 0 1 .02-.022z"/></svg>

After

Width:  |  Height:  |  Size: 435 B

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-sticky"><path d="M2.5 1A1.5 1.5 0 0 0 1 2.5v11A1.5 1.5 0 0 0 2.5 15h6.086a1.5 1.5 0 0 0 1.06-.44l4.915-4.914A1.5 1.5 0 0 0 15 8.586V2.5A1.5 1.5 0 0 0 13.5 1h-11zM2 2.5a.5.5 0 0 1 .5-.5h11a.5.5 0 0 1 .5.5V8H9.5A1.5 1.5 0 0 0 8 9.5V14H2.5a.5.5 0 0 1-.5-.5v-11zm7 11.293V9.5a.5.5 0 0 1 .5-.5h4.293L9 13.793z"/></svg>

After

Width:  |  Height:  |  Size: 409 B

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 50 50"><circle cx="25" cy="25" r="25" fill="#25ae88"/><path fill="none" stroke="#fff" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" d="M38 15 22 33l-10-8"/></svg>

After

Width:  |  Height:  |  Size: 256 B

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-table"><path d="M0 2a2 2 0 0 1 2-2h12a2 2 0 0 1 2 2v12a2 2 0 0 1-2 2H2a2 2 0 0 1-2-2V2zm15 2h-4v3h4V4zm0 4h-4v3h4V8zm0 4h-4v3h3a1 1 0 0 0 1-1v-2zm-5 3v-3H6v3h4zm-5 0v-3H1v2a1 1 0 0 0 1 1h3zm-4-4h4V8H1v3zm0-4h4V4H1v3zm5-3v3h4V4H6zm4 4H6v3h4V8z"/></svg>

After

Width:  |  Height:  |  Size: 347 B

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-text-center"><path fill-rule="evenodd" d="M4 12.5a.5.5 0 0 1 .5-.5h7a.5.5 0 0 1 0 1h-7a.5.5 0 0 1-.5-.5zm-2-3a.5.5 0 0 1 .5-.5h11a.5.5 0 0 1 0 1h-11a.5.5 0 0 1-.5-.5zm2-3a.5.5 0 0 1 .5-.5h7a.5.5 0 0 1 0 1h-7a.5.5 0 0 1-.5-.5zm-2-3a.5.5 0 0 1 .5-.5h11a.5.5 0 0 1 0 1h-11a.5.5 0 0 1-.5-.5z"/></svg>

After

Width:  |  Height:  |  Size: 392 B

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-text-left"><path fill-rule="evenodd" d="M2 12.5a.5.5 0 0 1 .5-.5h7a.5.5 0 0 1 0 1h-7a.5.5 0 0 1-.5-.5zm0-3a.5.5 0 0 1 .5-.5h11a.5.5 0 0 1 0 1h-11a.5.5 0 0 1-.5-.5zm0-3a.5.5 0 0 1 .5-.5h7a.5.5 0 0 1 0 1h-7a.5.5 0 0 1-.5-.5zm0-3a.5.5 0 0 1 .5-.5h11a.5.5 0 0 1 0 1h-11a.5.5 0 0 1-.5-.5z"/></svg>

After

Width:  |  Height:  |  Size: 388 B

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-text-paragraph"><path fill-rule="evenodd" d="M2 12.5a.5.5 0 0 1 .5-.5h7a.5.5 0 0 1 0 1h-7a.5.5 0 0 1-.5-.5zm0-3a.5.5 0 0 1 .5-.5h11a.5.5 0 0 1 0 1h-11a.5.5 0 0 1-.5-.5zm0-3a.5.5 0 0 1 .5-.5h11a.5.5 0 0 1 0 1h-11a.5.5 0 0 1-.5-.5zm4-3a.5.5 0 0 1 .5-.5h7a.5.5 0 0 1 0 1h-7a.5.5 0 0 1-.5-.5z"/></svg>

After

Width:  |  Height:  |  Size: 393 B

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-text-right"><path fill-rule="evenodd" d="M6 12.5a.5.5 0 0 1 .5-.5h7a.5.5 0 0 1 0 1h-7a.5.5 0 0 1-.5-.5zm-4-3a.5.5 0 0 1 .5-.5h11a.5.5 0 0 1 0 1h-11a.5.5 0 0 1-.5-.5zm4-3a.5.5 0 0 1 .5-.5h7a.5.5 0 0 1 0 1h-7a.5.5 0 0 1-.5-.5zm-4-3a.5.5 0 0 1 .5-.5h11a.5.5 0 0 1 0 1h-11a.5.5 0 0 1-.5-.5z"/></svg>

After

Width:  |  Height:  |  Size: 391 B

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-trash"><path d="M5.5 5.5A.5.5 0 0 1 6 6v6a.5.5 0 0 1-1 0V6a.5.5 0 0 1 .5-.5zm2.5 0a.5.5 0 0 1 .5.5v6a.5.5 0 0 1-1 0V6a.5.5 0 0 1 .5-.5zm3 .5a.5.5 0 0 0-1 0v6a.5.5 0 0 0 1 0V6z"/><path fill-rule="evenodd" d="M14.5 3a1 1 0 0 1-1 1H13v9a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V4h-.5a1 1 0 0 1-1-1V2a1 1 0 0 1 1-1H6a1 1 0 0 1 1-1h2a1 1 0 0 1 1 1h3.5a1 1 0 0 1 1 1v1zM4.118 4 4 4.059V13a1 1 0 0 0 1 1h6a1 1 0 0 0 1-1V4.059L11.882 4H4.118zM2.5 3V2h11v1h-11z"/></svg>

After

Width:  |  Height:  |  Size: 546 B

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-trash3"><path d="M6.5 1h3a.5.5 0 0 1 .5.5v1H6v-1a.5.5 0 0 1 .5-.5ZM11 2.5v-1A1.5 1.5 0 0 0 9.5 0h-3A1.5 1.5 0 0 0 5 1.5v1H2.506a.58.58 0 0 0-.01 0H1.5a.5.5 0 0 0 0 1h.538l.853 10.66A2 2 0 0 0 4.885 16h6.23a2 2 0 0 0 1.994-1.84l.853-10.66h.538a.5.5 0 0 0 0-1h-.995a.59.59 0 0 0-.01 0H11Zm1.958 1-.846 10.58a1 1 0 0 1-.997.92h-6.23a1 1 0 0 1-.997-.92L3.042 3.5h9.916Zm-7.487 1a.5.5 0 0 1 .528.47l.5 8.5a.5.5 0 0 1-.998.06L5 5.03a.5.5 0 0 1 .47-.53Zm5.058 0a.5.5 0 0 1 .47.53l-.5 8.5a.5.5 0 1 1-.998-.06l.5-8.5a.5.5 0 0 1 .528-.47ZM8 4.5a.5.5 0 0 1 .5.5v8.5a.5.5 0 0 1-1 0V5a.5.5 0 0 1 .5-.5Z"/></svg>

After

Width:  |  Height:  |  Size: 694 B

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-twitter"><path d="M5.026 15c6.038 0 9.341-5.003 9.341-9.334 0-.14 0-.282-.006-.422A6.685 6.685 0 0 0 16 3.542a6.658 6.658 0 0 1-1.889.518 3.301 3.301 0 0 0 1.447-1.817 6.533 6.533 0 0 1-2.087.793A3.286 3.286 0 0 0 7.875 6.03a9.325 9.325 0 0 1-6.767-3.429 3.289 3.289 0 0 0 1.018 4.382A3.323 3.323 0 0 1 .64 6.575v.045a3.288 3.288 0 0 0 2.632 3.218 3.203 3.203 0 0 1-.865.115 3.23 3.23 0 0 1-.614-.057 3.283 3.283 0 0 0 3.067 2.277A6.588 6.588 0 0 1 .78 13.58a6.32 6.32 0 0 1-.78-.045A9.344 9.344 0 0 0 5.026 15z"/></svg>

After

Width:  |  Height:  |  Size: 616 B

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-type-bold"><path d="M8.21 13c2.106 0 3.412-1.087 3.412-2.823 0-1.306-.984-2.283-2.324-2.386v-.055a2.176 2.176 0 0 0 1.852-2.14c0-1.51-1.162-2.46-3.014-2.46H3.843V13H8.21zM5.908 4.674h1.696c.963 0 1.517.451 1.517 1.244 0 .834-.629 1.32-1.73 1.32H5.908V4.673zm0 6.788V8.598h1.73c1.217 0 1.88.492 1.88 1.415 0 .943-.643 1.449-1.832 1.449H5.907z"/></svg>

After

Width:  |  Height:  |  Size: 446 B

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-type-h1"><path d="M8.637 13V3.669H7.379V7.62H2.758V3.67H1.5V13h1.258V8.728h4.62V13h1.259zm5.329 0V3.669h-1.244L10.5 5.316v1.265l2.16-1.565h.062V13h1.244z"/></svg>

After

Width:  |  Height:  |  Size: 258 B

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-type-h2"><path d="M7.638 13V3.669H6.38V7.62H1.759V3.67H.5V13h1.258V8.728h4.62V13h1.259zm3.022-6.733v-.048c0-.889.63-1.668 1.716-1.668.957 0 1.675.608 1.675 1.572 0 .855-.554 1.504-1.067 2.085l-3.513 3.999V13H15.5v-1.094h-4.245v-.075l2.481-2.844c.875-.998 1.586-1.784 1.586-2.953 0-1.463-1.155-2.556-2.919-2.556-1.941 0-2.966 1.326-2.966 2.74v.049h1.223z"/></svg>

After

Width:  |  Height:  |  Size: 458 B

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-type-h3"><path d="M7.637 13V3.669H6.379V7.62H1.758V3.67H.5V13h1.258V8.728h4.62V13h1.259zm3.625-4.272h1.018c1.142 0 1.935.67 1.949 1.674.013 1.005-.78 1.737-2.01 1.73-1.08-.007-1.853-.588-1.935-1.32H9.108c.069 1.327 1.224 2.386 3.083 2.386 1.935 0 3.343-1.155 3.309-2.789-.027-1.51-1.251-2.16-2.037-2.249v-.068c.704-.123 1.764-.91 1.723-2.229-.035-1.353-1.176-2.4-2.954-2.385-1.873.006-2.857 1.162-2.898 2.358h1.196c.062-.69.711-1.299 1.696-1.299.998 0 1.695.622 1.695 1.525.007.922-.718 1.592-1.695 1.592h-.964v1.074z"/></svg>

After

Width:  |  Height:  |  Size: 622 B

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-type-h1"><path d="M7.637 13V3.669H6.379V7.62H1.758V3.67H.5V13h1.258V8.728h4.62V13Zm5.337.2v-2.328H9.108V9.828l3.441-6.35h1.632v6.141H15.5v1.253h-1.319V13.2Zm-2.615-3.581h2.615V6.7L13 4.689l-.872 1.7z"/></svg>

After

Width:  |  Height:  |  Size: 304 B

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-type-h1"><path d="M7.637 13V3.669H6.379V7.62H1.758V3.67H.5V13h1.258V8.728h4.62V13Zm2.755-5.791a3.763 3.763 0 0 1 2.113-.517 2.973 2.973 0 0 1 2.995 3.1 3.45 3.45 0 0 1-.9 2.442 3.111 3.111 0 0 1-2.393.968 3.327 3.327 0 0 1-2.094-.671 2.758 2.758 0 0 1-1.007-2h1.284a1.387 1.387 0 0 0 .511 1.1 2.384 2.384 0 0 0 1.4.421 1.819 1.819 0 0 0 1.479-.638 2.042 2.042 0 0 0 .437-1.514 2.17 2.17 0 0 0-.567-1.584 1.958 1.958 0 0 0-1.468-.58 2.358 2.358 0 0 0-1.79.789H9.108V3.478h5.931v1.134h-4.647Z"/></svg>

After

Width:  |  Height:  |  Size: 595 B

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-type-h1"><path d="M7.637 13V3.669H6.379V7.62H1.758V3.67H.5V13h1.258V8.728h4.62V13Zm5.039-6.13a2.823 2.823 0 0 1 1.419.364 2.69 2.69 0 0 1 1.022 1.05 3.327 3.327 0 0 1 .383 1.642 3.594 3.594 0 0 1-.39 1.7 2.878 2.878 0 0 1-1.1 1.158 3.165 3.165 0 0 1-1.635.416 2.812 2.812 0 0 1-1.734-.545A3.49 3.49 0 0 1 9.51 11.1a6.515 6.515 0 0 1-.4-2.411A7.726 7.726 0 0 1 9.542 6a4.289 4.289 0 0 1 1.233-1.851 2.831 2.831 0 0 1 1.889-.673A2.7 2.7 0 0 1 13.8 3.7a2.463 2.463 0 0 1 .812.586 2.886 2.886 0 0 1 .514.8 2.768 2.768 0 0 1 .223.861H14a1.488 1.488 0 0 0-.453-.923 1.346 1.346 0 0 0-.935-.329 1.509 1.509 0 0 0-1.072.425 2.839 2.839 0 0 0-.71 1.18 6.808 6.808 0 0 0-.323 1.771 2.639 2.639 0 0 1 .918-.889 2.48 2.48 0 0 1 1.251-.312Zm-.285 5.117a1.617 1.617 0 0 0 .91-.256 1.752 1.752 0 0 0 .614-.713 2.336 2.336 0 0 0 .223-1.037 2.211 2.211 0 0 0-.217-1.01 1.6 1.6 0 0 0-.6-.666 1.671 1.671 0 0 0-.892-.236 1.833 1.833 0 0 0-1.164.377 2.4 2.4 0 0 0-.743 1.009 3.749 3.749 0 0 0 .6 1.845 1.5 1.5 0 0 0 1.269.687Z"/></svg>

After

Width:  |  Height:  |  Size: 1.1 KiB

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-type-italic"><path d="M7.991 11.674 9.53 4.455c.123-.595.246-.71 1.347-.807l.11-.52H7.211l-.11.52c1.06.096 1.128.212 1.005.807L6.57 11.674c-.123.595-.246.71-1.346.806l-.11.52h3.774l.11-.52c-1.06-.095-1.129-.211-1.006-.806z"/></svg>

After

Width:  |  Height:  |  Size: 327 B

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-type-strikethrough"><path d="M6.333 5.686c0 .31.083.581.27.814H5.166a2.776 2.776 0 0 1-.099-.76c0-1.627 1.436-2.768 3.48-2.768 1.969 0 3.39 1.175 3.445 2.85h-1.23c-.11-1.08-.964-1.743-2.25-1.743-1.23 0-2.18.602-2.18 1.607zm2.194 7.478c-2.153 0-3.589-1.107-3.705-2.81h1.23c.144 1.06 1.129 1.703 2.544 1.703 1.34 0 2.31-.705 2.31-1.675 0-.827-.547-1.374-1.914-1.675L8.046 8.5H1v-1h14v1h-3.504c.468.437.675.994.675 1.697 0 1.826-1.436 2.967-3.644 2.967z"/></svg>

After

Width:  |  Height:  |  Size: 555 B

View File

@@ -0,0 +1 @@
<svg width="16" height="16" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M11.354 14.5v-.665l1.553-1.438c.132-.128.243-.243.332-.345.091-.102.16-.203.207-.3.047-.1.07-.207.07-.322a.574.574 0 0 0-.326-.546.748.748 0 0 0-.343-.077.721.721 0 0 0-.35.082.557.557 0 0 0-.23.232.753.753 0 0 0-.08.36h-.876c0-.286.065-.534.194-.744.13-.21.31-.373.543-.488.233-.115.502-.172.806-.172.312 0 .584.055.816.166.233.11.414.261.543.456.13.194.194.418.194.669 0 .165-.033.327-.098.488-.064.16-.178.339-.343.535a7.918 7.918 0 0 1-.697.7l-.637.625v.03h1.832v.754h-3.11ZM5.04 4.273 6.96 7.469h.068l1.93-3.196h1.803L8.073 8.636 10.805 13H8.972L7.03 9.825h-.068L5.018 13H3.194l2.757-4.364-2.723-4.363H5.04Z" fill="currentColor"/></svg>

After

Width:  |  Height:  |  Size: 725 B

View File

@@ -0,0 +1 @@
<svg width="16" height="16" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M11.354 6v-.665l1.553-1.438c.132-.128.243-.243.332-.345a1.31 1.31 0 0 0 .207-.3c.047-.1.07-.207.07-.322a.574.574 0 0 0-.326-.545.748.748 0 0 0-.343-.077.721.721 0 0 0-.35.08.557.557 0 0 0-.23.233.753.753 0 0 0-.08.36h-.876c0-.286.065-.534.194-.744.13-.21.31-.373.543-.488.233-.115.502-.172.806-.172.312 0 .584.055.816.166.233.11.414.261.543.456.13.194.194.417.194.669 0 .165-.033.327-.098.488-.064.16-.178.339-.343.535a7.92 7.92 0 0 1-.697.7l-.637.625v.03h1.832V6h-3.11ZM5.04 4.273 6.96 7.469h.068l1.93-3.196h1.803L8.073 8.636 10.805 13H8.972L7.03 9.825h-.068L5.018 13H3.194l2.757-4.364-2.723-4.363H5.04Z" fill="currentColor"/></svg>

After

Width:  |  Height:  |  Size: 717 B

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-type-underline"><path d="M5.313 3.136h-1.23V9.54c0 2.105 1.47 3.623 3.917 3.623s3.917-1.518 3.917-3.623V3.136h-1.23v6.323c0 1.49-.978 2.57-2.687 2.57-1.709 0-2.687-1.08-2.687-2.57V3.136zM12.5 15h-9v-1h9v1z"/></svg>

After

Width:  |  Height:  |  Size: 310 B

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-upload"><path d="M.5 9.9a.5.5 0 0 1 .5.5v2.5a1 1 0 0 0 1 1h12a1 1 0 0 0 1-1v-2.5a.5.5 0 0 1 1 0v2.5a2 2 0 0 1-2 2H2a2 2 0 0 1-2-2v-2.5a.5.5 0 0 1 .5-.5z"/><path d="M7.646 1.146a.5.5 0 0 1 .708 0l3 3a.5.5 0 0 1-.708.708L8.5 2.707V11.5a.5.5 0 0 1-1 0V2.707L5.354 4.854a.5.5 0 1 1-.708-.708l3-3z"/></svg>

After

Width:  |  Height:  |  Size: 397 B

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><!--! Font Awesome Pro 6.1.1 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license (Commercial License) Copyright 2022 Fonticons, Inc.--><path d="M256 0C114.6 0 0 114.6 0 256s114.6 256 256 256 256-114.6 256-256S397.4 0 256 0zm0 128c39.77 0 72 32.24 72 72s-32.2 72-72 72c-39.76 0-72-32.24-72-72s32.2-72 72-72zm0 320c-52.93 0-100.9-21.53-135.7-56.29C136.5 349.9 176.5 320 224 320h64c47.54 0 87.54 29.88 103.7 71.71C356.9 426.5 308.9 448 256 448z"/></svg>

After

Width:  |  Height:  |  Size: 544 B

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-youtube"><path d="M8.051 1.999h.089c.822.003 4.987.033 6.11.335a2.01 2.01 0 0 1 1.415 1.42c.101.38.172.883.22 1.402l.01.104.022.26.008.104c.065.914.073 1.77.074 1.957v.075c-.001.194-.01 1.108-.082 2.06l-.008.105-.009.104c-.05.572-.124 1.14-.235 1.558a2.007 2.007 0 0 1-1.415 1.42c-1.16.312-5.569.334-6.18.335h-.142c-.309 0-1.587-.006-2.927-.052l-.17-.006-.087-.004-.171-.007-.171-.007c-1.11-.049-2.167-.128-2.654-.26a2.007 2.007 0 0 1-1.415-1.419c-.111-.417-.185-.986-.235-1.558L.09 9.82l-.008-.104A31.4 31.4 0 0 1 0 7.68v-.123c.002-.215.01-.958.064-1.778l.007-.103.003-.052.008-.104.022-.26.01-.104c.048-.519.119-1.023.22-1.402a2.007 2.007 0 0 1 1.415-1.42c.487-.13 1.544-.21 2.654-.26l.17-.007.172-.006.086-.003.171-.007A99.788 99.788 0 0 1 7.858 2h.193zM6.4 5.209v4.818l4.157-2.408L6.4 5.209z"/></svg>

After

Width:  |  Height:  |  Size: 900 B

View File

@@ -0,0 +1,37 @@
import {CodeHighlightNode, CodeNode} from '@lexical/code';
import {HashtagNode} from '@lexical/hashtag';
import {AutoLinkNode, LinkNode} from '@lexical/link';
import {ListItemNode, ListNode} from '@lexical/list';
import {MarkNode} from '@lexical/mark';
import {OverflowNode} from '@lexical/overflow';
import {HorizontalRuleNode} from '@lexical/react/LexicalHorizontalRuleNode';
import {HeadingNode, QuoteNode} from '@lexical/rich-text';
import {TableCellNode, TableNode, TableRowNode} from '@lexical/table';
import {TweetNode} from './TweetNode';
import {YouTubeNode} from './YouTubeNode';
import {CollapsibleContainerNode} from '../Plugins/CollapsiblePlugin/CollapsibleContainerNode';
import {CollapsibleContentNode} from '../Plugins/CollapsiblePlugin/CollapsibleContentNode';
import {CollapsibleTitleNode} from '../Plugins/CollapsiblePlugin/CollapsibleTitleNode';
export const BlockEditorNodes = [
AutoLinkNode,
CodeHighlightNode,
CodeNode,
CollapsibleContainerNode,
CollapsibleContentNode,
CollapsibleTitleNode,
HashtagNode,
HeadingNode,
HorizontalRuleNode,
LinkNode,
ListItemNode,
ListNode,
MarkNode,
OverflowNode,
QuoteNode,
TableCellNode,
TableNode,
TableRowNode,
TweetNode,
YouTubeNode,
];

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,416 @@
import {
DOMConversionMap,
DOMConversionOutput,
DOMExportOutput,
EditorConfig,
LexicalEditor,
LexicalNode,
NodeKey,
SerializedLexicalNode,
Spread,
DecoratorNode,
} from 'lexical';
import * as React from 'react';
import {Suspense} from 'react';
export type Cell = {
colSpan: number;
json: string;
type: 'normal' | 'header';
id: string;
width: number | null;
};
export type Row = {
cells: Array<Cell>;
height: null | number;
id: string;
};
export type Rows = Array<Row>;
export const cellHTMLCache: Map<string, string> = new Map();
export const cellTextContentCache: Map<string, string> = new Map();
const emptyEditorJSON =
'{"root":{"children":[{"children":[],"direction":null,"format":"","indent":0,"type":"paragraph","version":1}],"direction":null,"format":"","indent":0,"type":"root","version":1}}';
const plainTextEditorJSON = (text: string) =>
text === ''
? emptyEditorJSON
: `{"root":{"children":[{"children":[{"detail":0,"format":0,"mode":"normal","style":"","text":${text},"type":"text","version":1}],"direction":"ltr","format":"","indent":0,"type":"paragraph","version":1}],"direction":"ltr","format":"","indent":0,"type":"root","version":1}}`;
const TableComponent = React.lazy(
// @ts-ignore
() => import('./TableComponent'),
);
export function createUID(): string {
return Math.random()
.toString(36)
.replace(/[^a-z]+/g, '')
.substr(0, 5);
}
function createCell(type: 'normal' | 'header'): Cell {
return {
colSpan: 1,
id: createUID(),
json: emptyEditorJSON,
type,
width: null,
};
}
export function createRow(): Row {
return {
cells: [],
height: null,
id: createUID(),
};
}
export type SerializedTableNode = Spread<
{
rows: Rows;
type: 'tablesheet';
version: 1;
},
SerializedLexicalNode
>;
export function extractRowsFromHTML(tableElem: HTMLTableElement): Rows {
const rowElems = tableElem.querySelectorAll('tr');
const rows: Rows = [];
for (let y = 0; y < rowElems.length; y++) {
const rowElem = rowElems[y];
const cellElems = rowElem.querySelectorAll('td,th');
if (!cellElems || cellElems.length === 0) {
continue;
}
const cells: Array<Cell> = [];
for (let x = 0; x < cellElems.length; x++) {
const cellElem = cellElems[x] as HTMLElement;
const isHeader = cellElem.nodeName === 'TH';
const cell = createCell(isHeader ? 'header' : 'normal');
cell.json = plainTextEditorJSON(
JSON.stringify(cellElem.innerText.replace(/\n/g, ' ')),
);
cells.push(cell);
}
const row = createRow();
row.cells = cells;
rows.push(row);
}
return rows;
}
function convertTableElement(domNode: HTMLElement): null | DOMConversionOutput {
const rowElems = domNode.querySelectorAll('tr');
if (!rowElems || rowElems.length === 0) {
return null;
}
const rows: Rows = [];
for (let y = 0; y < rowElems.length; y++) {
const rowElem = rowElems[y];
const cellElems = rowElem.querySelectorAll('td,th');
if (!cellElems || cellElems.length === 0) {
continue;
}
const cells: Array<Cell> = [];
for (let x = 0; x < cellElems.length; x++) {
const cellElem = cellElems[x] as HTMLElement;
const isHeader = cellElem.nodeName === 'TH';
const cell = createCell(isHeader ? 'header' : 'normal');
cell.json = plainTextEditorJSON(
JSON.stringify(cellElem.innerText.replace(/\n/g, ' ')),
);
cells.push(cell);
}
const row = createRow();
row.cells = cells;
rows.push(row);
}
return {node: $createTableNode(rows)};
}
export function exportTableCellsToHTML(
rows: Rows,
rect?: {startX: number; endX: number; startY: number; endY: number},
): HTMLElement {
const table = document.createElement('table');
const colGroup = document.createElement('colgroup');
const tBody = document.createElement('tbody');
const firstRow = rows[0];
for (
let x = rect != null ? rect.startX : 0;
x < (rect != null ? rect.endX + 1 : firstRow.cells.length);
x++
) {
const col = document.createElement('col');
colGroup.append(col);
}
for (
let y = rect != null ? rect.startY : 0;
y < (rect != null ? rect.endY + 1 : rows.length);
y++
) {
const row = rows[y];
const cells = row.cells;
const rowElem = document.createElement('tr');
for (
let x = rect != null ? rect.startX : 0;
x < (rect != null ? rect.endX + 1 : cells.length);
x++
) {
const cell = cells[x];
const cellElem = document.createElement(
cell.type === 'header' ? 'th' : 'td',
);
cellElem.innerHTML = cellHTMLCache.get(cell.json) || '';
rowElem.appendChild(cellElem);
}
tBody.appendChild(rowElem);
}
table.appendChild(colGroup);
table.appendChild(tBody);
return table;
}
export class TableNode extends DecoratorNode<JSX.Element> {
__rows: Rows;
static getType(): string {
return 'tablesheet';
}
static clone(node: TableNode): TableNode {
return new TableNode(Array.from(node.__rows), node.__key);
}
static importJSON(serializedNode: SerializedTableNode): TableNode {
return $createTableNode(serializedNode.rows);
}
exportJSON(): SerializedTableNode {
return {
rows: this.__rows,
type: 'tablesheet',
version: 1,
};
}
static importDOM(): DOMConversionMap | null {
return {
table: (_node: Node) => ({
conversion: convertTableElement,
priority: 0,
}),
};
}
exportDOM(): DOMExportOutput {
return {element: exportTableCellsToHTML(this.__rows)};
}
constructor(rows?: Rows, key?: NodeKey) {
super(key);
this.__rows = rows || [];
}
createDOM(): HTMLElement {
const div = document.createElement('div');
div.style.display = 'contents';
return div;
}
updateDOM(): false {
return false;
}
mergeRows(startX: number, startY: number, mergeRows: Rows): void {
const self = this.getWritable();
const rows = self.__rows;
const endY = Math.min(rows.length, startY + mergeRows.length);
for (let y = startY; y < endY; y++) {
const row = rows[y];
const mergeRow = mergeRows[y - startY];
const cells = row.cells;
const cellsClone = Array.from(cells);
const rowClone = {...row, cells: cellsClone};
const mergeCells = mergeRow.cells;
const endX = Math.min(cells.length, startX + mergeCells.length);
for (let x = startX; x < endX; x++) {
const cell = cells[x];
const mergeCell = mergeCells[x - startX];
const cellClone = {...cell, json: mergeCell.json, type: mergeCell.type};
cellsClone[x] = cellClone;
}
rows[y] = rowClone;
}
}
updateCellJSON(x: number, y: number, json: string): void {
const self = this.getWritable();
const rows = self.__rows;
const row = rows[y];
const cells = row.cells;
const cell = cells[x];
const cellsClone = Array.from(cells);
const cellClone = {...cell, json};
const rowClone = {...row, cells: cellsClone};
cellsClone[x] = cellClone;
rows[y] = rowClone;
}
updateCellType(x: number, y: number, type: 'header' | 'normal'): void {
const self = this.getWritable();
const rows = self.__rows;
const row = rows[y];
const cells = row.cells;
const cell = cells[x];
const cellsClone = Array.from(cells);
const cellClone = {...cell, type};
const rowClone = {...row, cells: cellsClone};
cellsClone[x] = cellClone;
rows[y] = rowClone;
}
insertColumnAt(x: number): void {
const self = this.getWritable();
const rows = self.__rows;
for (let y = 0; y < rows.length; y++) {
const row = rows[y];
const cells = row.cells;
const cellsClone = Array.from(cells);
const rowClone = {...row, cells: cellsClone};
const type = (cells[x] || cells[x - 1]).type;
cellsClone.splice(x, 0, createCell(type));
rows[y] = rowClone;
}
}
deleteColumnAt(x: number): void {
const self = this.getWritable();
const rows = self.__rows;
for (let y = 0; y < rows.length; y++) {
const row = rows[y];
const cells = row.cells;
const cellsClone = Array.from(cells);
const rowClone = {...row, cells: cellsClone};
cellsClone.splice(x, 1);
rows[y] = rowClone;
}
}
addColumns(count: number): void {
const self = this.getWritable();
const rows = self.__rows;
for (let y = 0; y < rows.length; y++) {
const row = rows[y];
const cells = row.cells;
const cellsClone = Array.from(cells);
const rowClone = {...row, cells: cellsClone};
const type = cells[cells.length - 1].type;
for (let x = 0; x < count; x++) {
cellsClone.push(createCell(type));
}
rows[y] = rowClone;
}
}
insertRowAt(y: number): void {
const self = this.getWritable();
const rows = self.__rows;
const prevRow = rows[y] || rows[y - 1];
const cellCount = prevRow.cells.length;
const row = createRow();
for (let x = 0; x < cellCount; x++) {
const cell = createCell(prevRow.cells[x].type);
row.cells.push(cell);
}
rows.splice(y, 0, row);
}
deleteRowAt(y: number): void {
const self = this.getWritable();
const rows = self.__rows;
rows.splice(y, 1);
}
addRows(count: number): void {
const self = this.getWritable();
const rows = self.__rows;
const prevRow = rows[rows.length - 1];
const cellCount = prevRow.cells.length;
for (let y = 0; y < count; y++) {
const row = createRow();
for (let x = 0; x < cellCount; x++) {
const cell = createCell(prevRow.cells[x].type);
row.cells.push(cell);
}
rows.push(row);
}
}
updateColumnWidth(x: number, width: number): void {
const self = this.getWritable();
const rows = self.__rows;
for (let y = 0; y < rows.length; y++) {
const row = rows[y];
const cells = row.cells;
const cellsClone = Array.from(cells);
const rowClone = {...row, cells: cellsClone};
cellsClone[x].width = width;
rows[y] = rowClone;
}
}
decorate(_: LexicalEditor, config: EditorConfig): JSX.Element {
return (
<Suspense>
<TableComponent
nodeKey={this.__key}
theme={config.theme}
rows={this.__rows}
/>
</Suspense>
);
}
}
export function $isTableNode(
node: LexicalNode | null | undefined,
): node is TableNode {
return node instanceof TableNode;
}
export function $createTableNode(rows: Rows): TableNode {
return new TableNode(rows);
}
export function $createTableNodeWithDimensions(
rowCount: number,
columnCount: number,
includeHeaders = true,
): TableNode {
const rows: Rows = [];
for (let y = 0; y < columnCount; y++) {
const row: Row = createRow();
rows.push(row);
for (let x = 0; x < rowCount; x++) {
row.cells.push(
createCell(
includeHeaders === true && (y === 0 || x === 0) ? 'header' : 'normal',
),
);
}
}
return new TableNode(rows);
}

View File

@@ -0,0 +1,228 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
*/
import type {
DOMConversionMap,
DOMConversionOutput,
DOMExportOutput,
EditorConfig,
ElementFormatType,
LexicalEditor,
LexicalNode,
NodeKey,
Spread,
} from 'lexical';
import {BlockWithAlignableContents} from '@lexical/react/LexicalBlockWithAlignableContents';
import {
DecoratorBlockNode,
SerializedDecoratorBlockNode,
} from '@lexical/react/LexicalDecoratorBlockNode';
import {useCallback, useEffect, useRef, useState} from 'react';
const WIDGET_SCRIPT_URL = 'https://platform.twitter.com/widgets.js';
type TweetComponentProps = Readonly<{
className: Readonly<{
base: string;
focus: string;
}>;
format: ElementFormatType | null;
loadingComponent?: JSX.Element | string;
nodeKey: NodeKey;
onError?: (error: string) => void;
onLoad?: () => void;
tweetID: string;
}>;
function convertTweetElement(
domNode: HTMLDivElement,
): DOMConversionOutput | null {
const id = domNode.getAttribute('data-lexical-tweet-id');
if (id) {
const node = $createTweetNode(id);
return {node};
}
return null;
}
let isTwitterScriptLoading = true;
function TweetComponent({
className,
format,
loadingComponent,
nodeKey,
onError,
onLoad,
tweetID,
}: TweetComponentProps) {
const containerRef = useRef<HTMLDivElement | null>(null);
const previousTweetIDRef = useRef<string>('');
const [isTweetLoading, setIsTweetLoading] = useState(false);
const createTweet = useCallback(async () => {
try {
// @ts-expect-error Twitter is attached to the window.
await window.twttr.widgets.createTweet(tweetID, containerRef.current);
setIsTweetLoading(false);
isTwitterScriptLoading = false;
if (onLoad) {
onLoad();
}
} catch (error) {
if (onError) {
onError(String(error));
}
}
}, [onError, onLoad, tweetID]);
useEffect(() => {
if (tweetID !== previousTweetIDRef.current) {
setIsTweetLoading(true);
if (isTwitterScriptLoading) {
const script = document.createElement('script');
script.src = WIDGET_SCRIPT_URL;
script.async = true;
document.body?.appendChild(script);
script.onload = createTweet;
if (onError) {
script.onerror = onError as OnErrorEventHandler;
}
} else {
createTweet();
}
if (previousTweetIDRef) {
previousTweetIDRef.current = tweetID;
}
}
}, [createTweet, onError, tweetID]);
return (
<BlockWithAlignableContents
className={className}
format={format}
nodeKey={nodeKey}>
{isTweetLoading ? loadingComponent : null}
<div
style={{display: 'inline-block', width: '550px'}}
ref={containerRef}
/>
</BlockWithAlignableContents>
);
}
export type SerializedTweetNode = Spread<
{
id: string;
type: 'tweet';
version: 1;
},
SerializedDecoratorBlockNode
>;
export class TweetNode extends DecoratorBlockNode {
__id: string;
static getType(): string {
return 'tweet';
}
static clone(node: TweetNode): TweetNode {
return new TweetNode(node.__id, node.__format, node.__key);
}
static importJSON(serializedNode: SerializedTweetNode): TweetNode {
const node = $createTweetNode(serializedNode.id);
node.setFormat(serializedNode.format);
return node;
}
exportJSON(): SerializedTweetNode {
return {
...super.exportJSON(),
id: this.getId(),
type: 'tweet',
version: 1,
};
}
static importDOM(): DOMConversionMap<HTMLDivElement> | null {
return {
div: (domNode: HTMLDivElement) => {
if (!domNode.hasAttribute('data-lexical-tweet-id')) {
return null;
}
return {
conversion: convertTweetElement,
priority: 2,
};
},
};
}
exportDOM(): DOMExportOutput {
const element = document.createElement('div');
element.setAttribute('data-lexical-tweet-id', this.__id);
const text = document.createTextNode(this.getTextContent());
element.append(text);
return {element};
}
constructor(id: string, format?: ElementFormatType, key?: NodeKey) {
super(format, key);
this.__id = id;
}
getId(): string {
return this.__id;
}
getTextContent(
_includeInert?: boolean | undefined,
_includeDirectionless?: false | undefined,
): string {
return `https://twitter.com/i/web/status/${this.__id}`;
}
decorate(editor: LexicalEditor, config: EditorConfig): JSX.Element {
const embedBlockTheme = config.theme.embedBlock || {};
const className = {
base: embedBlockTheme.base || '',
focus: embedBlockTheme.focus || '',
};
return (
<TweetComponent
className={className}
format={this.__format}
loadingComponent="Loading..."
nodeKey={this.getKey()}
tweetID={this.__id}
/>
);
}
isInline(): false {
return false;
}
}
export function $createTweetNode(tweetID: string): TweetNode {
return new TweetNode(tweetID);
}
export function $isTweetNode(
node: TweetNode | LexicalNode | null | undefined,
): node is TweetNode {
return node instanceof TweetNode;
}

View File

@@ -0,0 +1,142 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
*/
import type {
EditorConfig,
ElementFormatType,
LexicalEditor,
LexicalNode,
NodeKey,
Spread,
} from 'lexical';
import {BlockWithAlignableContents} from '@lexical/react/LexicalBlockWithAlignableContents';
import {
DecoratorBlockNode,
SerializedDecoratorBlockNode,
} from '@lexical/react/LexicalDecoratorBlockNode';
type YouTubeComponentProps = Readonly<{
className: Readonly<{
base: string;
focus: string;
}>;
format: ElementFormatType | null;
nodeKey: NodeKey;
videoID: string;
}>;
function YouTubeComponent({
className,
format,
nodeKey,
videoID,
}: YouTubeComponentProps) {
return (
<BlockWithAlignableContents
className={className}
format={format}
nodeKey={nodeKey}>
<iframe
width="560"
height="315"
src={`https://www.youtube.com/embed/${videoID}`}
frameBorder="0"
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture"
allowFullScreen={true}
title="YouTube video"
/>
</BlockWithAlignableContents>
);
}
export type SerializedYouTubeNode = Spread<
{
videoID: string;
type: 'youtube';
version: 1;
},
SerializedDecoratorBlockNode
>;
export class YouTubeNode extends DecoratorBlockNode {
__id: string;
static getType(): string {
return 'youtube';
}
static clone(node: YouTubeNode): YouTubeNode {
return new YouTubeNode(node.__id, node.__format, node.__key);
}
static importJSON(serializedNode: SerializedYouTubeNode): YouTubeNode {
const node = $createYouTubeNode(serializedNode.videoID);
node.setFormat(serializedNode.format);
return node;
}
exportJSON(): SerializedYouTubeNode {
return {
...super.exportJSON(),
type: 'youtube',
version: 1,
videoID: this.__id,
};
}
constructor(id: string, format?: ElementFormatType, key?: NodeKey) {
super(format, key);
this.__id = id;
}
updateDOM(): false {
return false;
}
getId(): string {
return this.__id;
}
getTextContent(
_includeInert?: boolean | undefined,
_includeDirectionless?: false | undefined,
): string {
return `https://www.youtube.com/watch?v=${this.__id}`;
}
decorate(_editor: LexicalEditor, config: EditorConfig): JSX.Element {
const embedBlockTheme = config.theme.embedBlock || {};
const className = {
base: embedBlockTheme.base || '',
focus: embedBlockTheme.focus || '',
};
return (
<YouTubeComponent
className={className}
format={this.__format}
nodeKey={this.getKey()}
videoID={this.__id}
/>
);
}
isInline(): false {
return false;
}
}
export function $createYouTubeNode(videoID: string): YouTubeNode {
return new YouTubeNode(videoID);
}
export function $isYouTubeNode(
node: YouTubeNode | LexicalNode | null | undefined,
): node is YouTubeNode {
return node instanceof YouTubeNode;
}

View File

@@ -0,0 +1,286 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
*/
import type {LexicalEditor} from 'lexical';
import {
AutoEmbedOption,
EmbedConfig,
EmbedMatchResult,
LexicalAutoEmbedPlugin,
URL_MATCHER,
} from '@lexical/react/LexicalAutoEmbedPlugin';
import {useLexicalComposerContext} from '@lexical/react/LexicalComposerContext';
import {useState} from 'react';
import * as ReactDOM from 'react-dom';
import useModal from '../../Hooks/useModal';
import Button from '../../UI/Button';
import {DialogActions} from '../../UI/Dialog';
import {INSERT_TWEET_COMMAND} from '../TwitterPlugin';
import {INSERT_YOUTUBE_COMMAND} from '../YouTubePlugin';
interface PlaygroundEmbedConfig extends EmbedConfig {
// Human readable name of the embeded content e.g. Tweet or Google Map.
contentName: string;
// Icon for display.
icon?: JSX.Element;
// An example of a matching url https://twitter.com/jack/status/20
exampleUrl: string;
// For extra searching.
keywords: Array<string>;
// Embed a Figma Project.
description?: string;
}
export const YoutubeEmbedConfig: PlaygroundEmbedConfig = {
contentName: 'Youtube Video',
exampleUrl: 'https://www.youtube.com/watch?v=jNQXAC9IVRw',
// Icon for display.
icon: <i className="icon youtube" />,
insertNode: (editor: LexicalEditor, result: EmbedMatchResult) => {
editor.dispatchCommand(INSERT_YOUTUBE_COMMAND, result.id);
},
keywords: ['youtube', 'video'],
// Determine if a given URL is a match and return url data.
parseUrl: (url: string) => {
const match =
/^.*(youtu\.be\/|v\/|u\/\w\/|embed\/|watch\?v=|&v=)([^#&?]*).*/.exec(url);
const id = match ? (match?.[2].length === 11 ? match[2] : null) : null;
if (id != null) {
return {
id,
url,
};
}
return null;
},
type: 'youtube-video',
};
export const TwitterEmbedConfig: PlaygroundEmbedConfig = {
// e.g. Tweet or Google Map.
contentName: 'Tweet',
exampleUrl: 'https://twitter.com/jack/status/20',
// Icon for display.
icon: <i className="icon tweet" />,
// Create the Lexical embed node from the url data.
insertNode: (editor: LexicalEditor, result: EmbedMatchResult) => {
editor.dispatchCommand(INSERT_TWEET_COMMAND, result.id);
},
// For extra searching.
keywords: ['tweet', 'twitter'],
// Determine if a given URL is a match and return url data.
parseUrl: (text: string) => {
const match =
/^https:\/\/twitter\.com\/(#!\/)?(\w+)\/status(es)*\/(\d+)$/.exec(text);
if (match != null) {
return {
id: match[4],
url: match[0],
};
}
return null;
},
type: 'tweet',
};
export const EmbedConfigs = [TwitterEmbedConfig, YoutubeEmbedConfig];
function AutoEmbedMenuItem({
index,
isSelected,
onClick,
onMouseEnter,
option,
}: {
index: number;
isSelected: boolean;
onClick: () => void;
onMouseEnter: () => void;
option: AutoEmbedOption;
}) {
let className = 'item';
if (isSelected) {
className += ' selected';
}
return (
<li
key={option.key}
tabIndex={-1}
className={className}
ref={option.setRefElement}
role="option"
aria-selected={isSelected}
id={'typeahead-item-' + index}
onMouseEnter={onMouseEnter}
onClick={onClick}>
<span className="text">{option.title}</span>
</li>
);
}
function AutoEmbedMenu({
options,
selectedItemIndex,
onOptionClick,
onOptionMouseEnter,
}: {
selectedItemIndex: number | null;
onOptionClick: (option: AutoEmbedOption, index: number) => void;
onOptionMouseEnter: (index: number) => void;
options: Array<AutoEmbedOption>;
}) {
return (
<div className="typeahead-popover">
<ul>
{options.map((option: AutoEmbedOption, i: number) => (
<AutoEmbedMenuItem
index={i}
isSelected={selectedItemIndex === i}
onClick={() => onOptionClick(option, i)}
onMouseEnter={() => onOptionMouseEnter(i)}
key={option.key}
option={option}
/>
))}
</ul>
</div>
);
}
export function AutoEmbedDialog({
embedConfig,
onClose,
}: {
embedConfig: PlaygroundEmbedConfig;
onClose: () => void;
}): JSX.Element {
const [text, setText] = useState('');
const [editor] = useLexicalComposerContext();
const urlMatch = URL_MATCHER.exec(text);
const embedResult =
text != null && urlMatch != null ? embedConfig.parseUrl(text) : null;
const onClick = () => {
if (embedResult != null) {
embedConfig.insertNode(editor, embedResult);
onClose();
}
};
return (
<div style={{width: '600px'}}>
<div className="Input__wrapper">
<input
type="text"
className="Input__input"
placeholder={embedConfig.exampleUrl}
value={text}
data-test-id={`${embedConfig.type}-embed-modal-url`}
onChange={(e) => {
setText(e.target.value);
}}
/>
</div>
<DialogActions>
<Button
disabled={!embedResult}
onClick={onClick}
data-test-id={`${embedConfig.type}-embed-modal-submit-btn`}>
Embed
</Button>
</DialogActions>
</div>
);
}
export default function AutoEmbedPlugin(): JSX.Element {
const [modal, showModal] = useModal();
const openEmbedModal = (embedConfig: PlaygroundEmbedConfig) => {
showModal(`Embed ${embedConfig.contentName}`, (onClose) => (
<AutoEmbedDialog embedConfig={embedConfig} onClose={onClose} />
));
};
const getMenuOptions = (
activeEmbedConfig: PlaygroundEmbedConfig,
embedFn: () => void,
dismissFn: () => void,
) => {
return [
new AutoEmbedOption('Dismiss', {
onSelect: dismissFn,
}),
new AutoEmbedOption(`Embed ${activeEmbedConfig.contentName}`, {
onSelect: embedFn,
}),
];
};
return (
<>
{modal}
<LexicalAutoEmbedPlugin<PlaygroundEmbedConfig>
embedConfigs={EmbedConfigs}
onOpenEmbedModalForConfig={openEmbedModal}
getMenuOptions={getMenuOptions}
menuRenderFn={(
anchorElementRef,
{selectedIndex, options, selectOptionAndCleanUp, setHighlightedIndex},
) =>
anchorElementRef.current
? ReactDOM.createPortal(
<div
className="typeahead-popover auto-embed-menu"
style={{
marginLeft: anchorElementRef.current.style.width,
}}>
<AutoEmbedMenu
options={options}
selectedItemIndex={selectedIndex}
onOptionClick={(option: AutoEmbedOption, index: number) => {
setHighlightedIndex(index);
selectOptionAndCleanUp(option);
}}
onOptionMouseEnter={(index: number) => {
setHighlightedIndex(index);
}}
/>
</div>,
anchorElementRef.current,
)
: null
}
/>
</>
);
}

View File

@@ -0,0 +1,57 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
*
*/
.Collapsible__container {
background: #fcfcfc;
border: 1px solid #eee;
border-radius: 10px;
margin-bottom: 8px;
}
.Collapsible__title {
cursor: pointer;
padding: 5px 5px 5px 20px;
position: relative;
font-weight: bold;
list-style: none;
outline: none;
}
.Collapsible__title::marker,
.Collapsible__title::-webkit-details-marker {
display: none;
}
.Collapsible__title:before {
border-style: solid;
border-color: transparent;
border-width: 4px 6px 4px 6px;
border-left-color: #000;
display: block;
content: '';
position: absolute;
left: 7px;
top: 50%;
transform: translateY(-50%);
}
.Collapsible__container[open] .Collapsible__title:before {
border-color: transparent;
border-width: 6px 4px 0 4px;
border-top-color: #000;
}
.Collapsible__content {
padding: 0 5px 5px 20px;
}
.Collapsible__collapsed .Collapsible__content {
display: none;
user-select: none;
}

View File

@@ -0,0 +1,102 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
*/
import {
DOMConversionMap,
EditorConfig,
ElementNode,
LexicalNode,
NodeKey,
SerializedElementNode,
Spread,
} from 'lexical';
type SerializedCollapsibleContainerNode = Spread<
{
type: 'collapsible-container';
version: 1;
},
SerializedElementNode
>;
export class CollapsibleContainerNode extends ElementNode {
__open: boolean;
constructor(open: boolean, key?: NodeKey) {
super(key);
this.__open = open;
}
static getType(): string {
return 'collapsible-container';
}
static clone(node: CollapsibleContainerNode): CollapsibleContainerNode {
return new CollapsibleContainerNode(node.__open, node.__key);
}
createDOM(config: EditorConfig): HTMLElement {
const dom = document.createElement('details');
dom.classList.add('Collapsible__container');
dom.open = this.__open;
return dom;
}
updateDOM(
prevNode: CollapsibleContainerNode,
dom: HTMLDetailsElement,
): boolean {
if (prevNode.__open !== this.__open) {
dom.open = this.__open;
}
return false;
}
static importDOM(): DOMConversionMap | null {
return {};
}
static importJSON(
serializedNode: SerializedCollapsibleContainerNode,
): CollapsibleContainerNode {
const node = $createCollapsibleContainerNode();
return node;
}
exportJSON(): SerializedCollapsibleContainerNode {
return {
...super.exportJSON(),
type: 'collapsible-container',
version: 1,
};
}
setOpen(open: boolean): void {
const writable = this.getWritable();
writable.__open = open;
}
getOpen(): boolean {
return this.__open;
}
toggleOpen(): void {
this.setOpen(!this.getOpen());
}
}
export function $createCollapsibleContainerNode(): CollapsibleContainerNode {
return new CollapsibleContainerNode(true);
}
export function $isCollapsibleContainerNode(
node: LexicalNode | null | undefined,
): node is CollapsibleContainerNode {
return node instanceof CollapsibleContainerNode;
}

View File

@@ -0,0 +1,76 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
*/
import {
DOMConversionMap,
EditorConfig,
ElementNode,
LexicalNode,
SerializedElementNode,
Spread,
} from 'lexical';
type SerializedCollapsibleContentNode = Spread<
{
type: 'collapsible-content';
version: 1;
},
SerializedElementNode
>;
export class CollapsibleContentNode extends ElementNode {
static getType(): string {
return 'collapsible-content';
}
static clone(node: CollapsibleContentNode): CollapsibleContentNode {
return new CollapsibleContentNode(node.__key);
}
createDOM(config: EditorConfig): HTMLElement {
const dom = document.createElement('div');
dom.classList.add('Collapsible__content');
return dom;
}
updateDOM(prevNode: CollapsibleContentNode, dom: HTMLElement): boolean {
return false;
}
static importDOM(): DOMConversionMap | null {
return {};
}
static importJSON(
serializedNode: SerializedCollapsibleContentNode,
): CollapsibleContentNode {
return $createCollapsibleContentNode();
}
isShadowRoot(): boolean {
return true;
}
exportJSON(): SerializedCollapsibleContentNode {
return {
...super.exportJSON(),
type: 'collapsible-content',
version: 1,
};
}
}
export function $createCollapsibleContentNode(): CollapsibleContentNode {
return new CollapsibleContentNode();
}
export function $isCollapsibleContentNode(
node: LexicalNode | null | undefined,
): node is CollapsibleContentNode {
return node instanceof CollapsibleContentNode;
}

View File

@@ -0,0 +1,116 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
*/
import {
$createParagraphNode,
$isElementNode,
DOMConversionMap,
EditorConfig,
ElementNode,
LexicalEditor,
LexicalNode,
RangeSelection,
SerializedElementNode,
Spread,
} from 'lexical';
import {$isCollapsibleContainerNode} from './CollapsibleContainerNode';
import {$isCollapsibleContentNode} from './CollapsibleContentNode';
type SerializedCollapsibleTitleNode = Spread<
{
type: 'collapsible-title';
version: 1;
},
SerializedElementNode
>;
export class CollapsibleTitleNode extends ElementNode {
static getType(): string {
return 'collapsible-title';
}
static clone(node: CollapsibleTitleNode): CollapsibleTitleNode {
return new CollapsibleTitleNode(node.__key);
}
createDOM(config: EditorConfig, editor: LexicalEditor): HTMLElement {
const dom = document.createElement('summary');
dom.classList.add('Collapsible__title');
return dom;
}
updateDOM(prevNode: CollapsibleTitleNode, dom: HTMLElement): boolean {
return false;
}
static importDOM(): DOMConversionMap | null {
return {};
}
static importJSON(
serializedNode: SerializedCollapsibleTitleNode,
): CollapsibleTitleNode {
return $createCollapsibleTitleNode();
}
exportJSON(): SerializedCollapsibleTitleNode {
return {
...super.exportJSON(),
type: 'collapsible-title',
version: 1,
};
}
collapseAtStart(_selection: RangeSelection): boolean {
this.getParentOrThrow().insertBefore(this);
return true;
}
insertNewAfter(): ElementNode {
const containerNode = this.getParentOrThrow();
if (!$isCollapsibleContainerNode(containerNode)) {
throw new Error(
'CollapsibleTitleNode expects to be child of CollapsibleContainerNode',
);
}
if (containerNode.getOpen()) {
const contentNode = this.getNextSibling();
if (!$isCollapsibleContentNode(contentNode)) {
throw new Error(
'CollapsibleTitleNode expects to have CollapsibleContentNode sibling',
);
}
const firstChild = contentNode.getFirstChild();
if ($isElementNode(firstChild)) {
return firstChild;
} else {
const paragraph = $createParagraphNode();
contentNode.append(paragraph);
return paragraph;
}
} else {
const paragraph = $createParagraphNode();
containerNode.insertAfter(paragraph);
return paragraph;
}
}
}
export function $createCollapsibleTitleNode(): CollapsibleTitleNode {
return new CollapsibleTitleNode();
}
export function $isCollapsibleTitleNode(
node: LexicalNode | null | undefined,
): node is CollapsibleTitleNode {
return node instanceof CollapsibleTitleNode;
}

View File

@@ -0,0 +1,236 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
*/
import './Collapsible.css';
import {useLexicalComposerContext} from '@lexical/react/LexicalComposerContext';
import {$findMatchingParent, mergeRegister} from '@lexical/utils';
import {
$createParagraphNode,
$getNodeByKey,
$getPreviousSelection,
$getSelection,
$isElementNode,
$isRangeSelection,
$setSelection,
COMMAND_PRIORITY_EDITOR,
COMMAND_PRIORITY_LOW,
createCommand,
DELETE_CHARACTER_COMMAND,
INSERT_PARAGRAPH_COMMAND,
KEY_ARROW_DOWN_COMMAND,
NodeKey,
} from 'lexical';
import {useEffect} from 'react';
import {
$createCollapsibleContainerNode,
$isCollapsibleContainerNode,
CollapsibleContainerNode,
} from './CollapsibleContainerNode';
import {
$createCollapsibleContentNode,
$isCollapsibleContentNode,
CollapsibleContentNode,
} from './CollapsibleContentNode';
import {
$createCollapsibleTitleNode,
$isCollapsibleTitleNode,
CollapsibleTitleNode,
} from './CollapsibleTitleNode';
export const INSERT_COLLAPSIBLE_COMMAND = createCommand<void>();
export const TOGGLE_COLLAPSIBLE_COMMAND = createCommand<NodeKey>();
export default function CollapsiblePlugin(): JSX.Element | null {
const [editor] = useLexicalComposerContext();
useEffect(() => {
if (
!editor.hasNodes([
CollapsibleContainerNode,
CollapsibleTitleNode,
CollapsibleContentNode,
])
) {
throw new Error(
'CollapsiblePlugin: CollapsibleContainerNode, CollapsibleTitleNode, or CollapsibleContentNode not registered on editor',
);
}
return mergeRegister(
// Structure enforcing transformers for each node type. In case nesting structure is not
// "Container > Title + Content" it'll unwrap nodes and convert it back
// to regular content.
editor.registerNodeTransform(CollapsibleContentNode, (node) => {
const parent = node.getParent();
if (!$isCollapsibleContainerNode(parent)) {
const children = node.getChildren();
for (const child of children) {
node.insertAfter(child);
}
node.remove();
}
}),
editor.registerNodeTransform(CollapsibleTitleNode, (node) => {
const parent = node.getParent();
if (!$isCollapsibleContainerNode(parent)) {
node.replace($createParagraphNode().append(...node.getChildren()));
}
}),
editor.registerNodeTransform(CollapsibleContainerNode, (node) => {
const children = node.getChildren();
if (
children.length !== 2 ||
!$isCollapsibleTitleNode(children[0]) ||
!$isCollapsibleContentNode(children[1])
) {
for (const child of children) {
node.insertAfter(child);
}
node.remove();
}
}),
// This handles the case when container is collapsed and we delete its previous sibling
// into it, it would cause collapsed content deleted (since it's display: none, and selection
// swallows it when deletes single char). Instead we expand container, which is although
// not perfect, but avoids bigger problem
editor.registerCommand(
DELETE_CHARACTER_COMMAND,
() => {
const selection = $getSelection();
if (
!$isRangeSelection(selection) ||
!selection.isCollapsed() ||
selection.anchor.offset !== 0
) {
return false;
}
const anchorNode = selection.anchor.getNode();
const topLevelElement = anchorNode.getTopLevelElement();
if (topLevelElement === null) {
return false;
}
const container = topLevelElement.getPreviousSibling();
if (!$isCollapsibleContainerNode(container) || container.getOpen()) {
return false;
}
container.setOpen(true);
return true;
},
COMMAND_PRIORITY_LOW,
),
// When collapsible is the last child pressing down arrow will insert paragraph
// below it to allow adding more content. It's similar what $insertBlockNode
// (mainly for decorators), except it'll always be possible to continue adding
// new content even if trailing paragraph is accidentally deleted
editor.registerCommand(
KEY_ARROW_DOWN_COMMAND,
() => {
const selection = $getSelection();
if (!$isRangeSelection(selection) || !selection.isCollapsed()) {
return false;
}
const container = $findMatchingParent(
selection.anchor.getNode(),
$isCollapsibleContainerNode,
);
if (container === null) {
return false;
}
const parent = container.getParent();
if (parent !== null && parent.getLastChild() === container) {
parent.append($createParagraphNode());
}
return false;
},
COMMAND_PRIORITY_LOW,
),
// Handling CMD+Enter to toggle collapsible element collapsed state
editor.registerCommand(
INSERT_PARAGRAPH_COMMAND,
() => {
// @ts-ignore
const windowEvent: KeyboardEvent | undefined = editor._window?.event;
if (
windowEvent &&
(windowEvent.ctrlKey || windowEvent.metaKey) &&
windowEvent.key === 'Enter'
) {
const selection = $getPreviousSelection();
if ($isRangeSelection(selection) && selection.isCollapsed()) {
const parent = $findMatchingParent(
selection.anchor.getNode(),
(node) => $isElementNode(node) && !node.isInline(),
);
if ($isCollapsibleTitleNode(parent)) {
const container = parent.getParent();
if ($isCollapsibleContainerNode(container)) {
container.toggleOpen();
$setSelection(selection.clone());
return true;
}
}
}
}
return false;
},
COMMAND_PRIORITY_LOW,
),
editor.registerCommand(
INSERT_COLLAPSIBLE_COMMAND,
() => {
editor.update(() => {
const selection = $getSelection();
if (!$isRangeSelection(selection)) {
return;
}
const title = $createCollapsibleTitleNode();
const content = $createCollapsibleContentNode().append(
$createParagraphNode(),
);
const container = $createCollapsibleContainerNode().append(
title,
content,
);
selection.insertNodes([container]);
title.selectStart();
});
return true;
},
COMMAND_PRIORITY_EDITOR,
),
editor.registerCommand(
TOGGLE_COLLAPSIBLE_COMMAND,
(key: NodeKey) => {
editor.update(() => {
const containerNode = $getNodeByKey(key);
if ($isCollapsibleContainerNode(containerNode)) {
containerNode.toggleOpen();
}
});
return true;
},
COMMAND_PRIORITY_EDITOR,
),
);
}, [editor]);
return null;
}

View File

@@ -0,0 +1,357 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
*/
import {$createCodeNode} from '@lexical/code';
import {
INSERT_CHECK_LIST_COMMAND,
INSERT_ORDERED_LIST_COMMAND,
INSERT_UNORDERED_LIST_COMMAND,
} from '@lexical/list';
import {INSERT_EMBED_COMMAND} from '@lexical/react/LexicalAutoEmbedPlugin';
import {useLexicalComposerContext} from '@lexical/react/LexicalComposerContext';
import {INSERT_HORIZONTAL_RULE_COMMAND} from '@lexical/react/LexicalHorizontalRuleNode';
import {
LexicalTypeaheadMenuPlugin,
TypeaheadOption,
useBasicTypeaheadTriggerMatch,
} from '@lexical/react/LexicalTypeaheadMenuPlugin';
import {$createHeadingNode, $createQuoteNode} from '@lexical/rich-text';
import {$wrapNodes} from '@lexical/selection';
import {INSERT_TABLE_COMMAND} from '@lexical/table';
import {
$createParagraphNode,
$getSelection,
$isRangeSelection,
FORMAT_ELEMENT_COMMAND,
TextNode,
} from 'lexical';
import {useCallback, useMemo, useState} from 'react';
import * as ReactDOM from 'react-dom';
import useModal from '../../Hooks/useModal';
import {EmbedConfigs} from '../AutoEmbedPlugin';
import {INSERT_COLLAPSIBLE_COMMAND} from '../CollapsiblePlugin';
import {InsertTableDialog} from '../TablePlugin';
class ComponentPickerOption extends TypeaheadOption {
// What shows up in the editor
title: string;
// Icon for display
icon?: JSX.Element;
// For extra searching.
keywords: Array<string>;
// TBD
keyboardShortcut?: string;
// What happens when you select this option?
onSelect: (queryString: string) => void;
constructor(
title: string,
options: {
icon?: JSX.Element;
keywords?: Array<string>;
keyboardShortcut?: string;
onSelect: (queryString: string) => void;
},
) {
super(title);
this.title = title;
this.keywords = options.keywords || [];
this.icon = options.icon;
this.keyboardShortcut = options.keyboardShortcut;
this.onSelect = options.onSelect.bind(this);
}
}
function ComponentPickerMenuItem({
index,
isSelected,
onClick,
onMouseEnter,
option,
}: {
index: number;
isSelected: boolean;
onClick: () => void;
onMouseEnter: () => void;
option: ComponentPickerOption;
}) {
let className = 'item';
if (isSelected) {
className += ' selected';
}
return (
<li
key={option.key}
tabIndex={-1}
className={className}
ref={option.setRefElement}
role="option"
aria-selected={isSelected}
id={'typeahead-item-' + index}
onMouseEnter={onMouseEnter}
onClick={onClick}>
{option.icon}
<span className="text">{option.title}</span>
</li>
);
}
export default function ComponentPickerMenuPlugin(): JSX.Element {
const [editor] = useLexicalComposerContext();
const [modal, showModal] = useModal();
const [queryString, setQueryString] = useState<string | null>(null);
const checkForTriggerMatch = useBasicTypeaheadTriggerMatch('/', {
minLength: 0,
});
const getDynamicOptions = useCallback(() => {
const options: Array<ComponentPickerOption> = [];
if (queryString == null) {
return options;
}
const fullTableRegex = new RegExp(/^([1-9]|10)x([1-9]|10)$/);
const partialTableRegex = new RegExp(/^([1-9]|10)x?$/);
const fullTableMatch = fullTableRegex.exec(queryString);
const partialTableMatch = partialTableRegex.exec(queryString);
if (fullTableMatch) {
const [rows, columns] = fullTableMatch[0]
.split('x')
.map((n: string) => parseInt(n, 10));
options.push(
new ComponentPickerOption(`${rows}x${columns} Table`, {
icon: <i className="icon table" />,
keywords: ['table'],
onSelect: () =>
// @ts-ignore Correct types, but since they're dynamic TS doesn't like it.
editor.dispatchCommand(INSERT_TABLE_COMMAND, {columns, rows}),
}),
);
} else if (partialTableMatch) {
const rows = parseInt(partialTableMatch[0], 10);
options.push(
...Array.from({length: 5}, (_, i) => i + 1).map(
(columns) =>
new ComponentPickerOption(`${rows}x${columns} Table`, {
icon: <i className="icon table" />,
keywords: ['table'],
onSelect: () =>
// @ts-ignore Correct types, but since they're dynamic TS doesn't like it.
editor.dispatchCommand(INSERT_TABLE_COMMAND, {columns, rows}),
}),
),
);
}
return options;
}, [editor, queryString]);
const options = useMemo(() => {
const baseOptions = [
new ComponentPickerOption('Paragraph', {
icon: <i className="icon paragraph" />,
keywords: ['normal', 'paragraph', 'p', 'text'],
onSelect: () =>
editor.update(() => {
const selection = $getSelection();
if ($isRangeSelection(selection)) {
$wrapNodes(selection, () => $createParagraphNode());
}
}),
}),
...Array.from({length: 3}, (_, i) => i + 1).map(
(n) =>
new ComponentPickerOption(`Heading ${n}`, {
icon: <i className={`icon h${n}`} />,
keywords: ['heading', 'header', `h${n}`],
onSelect: () =>
editor.update(() => {
const selection = $getSelection();
if ($isRangeSelection(selection)) {
$wrapNodes(selection, () =>
// @ts-ignore Correct types, but since they're dynamic TS doesn't like it.
$createHeadingNode(`h${n}`),
);
}
}),
}),
),
new ComponentPickerOption('Table', {
icon: <i className="icon table" />,
keywords: ['table', 'grid', 'spreadsheet', 'rows', 'columns'],
onSelect: () =>
showModal('Insert Table', (onClose) => (
<InsertTableDialog activeEditor={editor} onClose={onClose} />
)),
}),
new ComponentPickerOption('Numbered List', {
icon: <i className="icon number" />,
keywords: ['numbered list', 'ordered list', 'ol'],
onSelect: () =>
editor.dispatchCommand(INSERT_ORDERED_LIST_COMMAND, undefined),
}),
new ComponentPickerOption('Bulleted List', {
icon: <i className="icon bullet" />,
keywords: ['bulleted list', 'unordered list', 'ul'],
onSelect: () =>
editor.dispatchCommand(INSERT_UNORDERED_LIST_COMMAND, undefined),
}),
new ComponentPickerOption('Check List', {
icon: <i className="icon check" />,
keywords: ['check list', 'todo list'],
onSelect: () =>
editor.dispatchCommand(INSERT_CHECK_LIST_COMMAND, undefined),
}),
new ComponentPickerOption('Quote', {
icon: <i className="icon quote" />,
keywords: ['block quote'],
onSelect: () =>
editor.update(() => {
const selection = $getSelection();
if ($isRangeSelection(selection)) {
$wrapNodes(selection, () => $createQuoteNode());
}
}),
}),
new ComponentPickerOption('Code', {
icon: <i className="icon code" />,
keywords: ['javascript', 'python', 'js', 'codeblock'],
onSelect: () =>
editor.update(() => {
const selection = $getSelection();
if ($isRangeSelection(selection)) {
if (selection.isCollapsed()) {
$wrapNodes(selection, () => $createCodeNode());
} else {
// Will this ever happen?
const textContent = selection.getTextContent();
const codeNode = $createCodeNode();
selection.insertNodes([codeNode]);
selection.insertRawText(textContent);
}
}
}),
}),
new ComponentPickerOption('Divider', {
icon: <i className="icon horizontal-rule" />,
keywords: ['horizontal rule', 'divider', 'hr'],
onSelect: () =>
editor.dispatchCommand(INSERT_HORIZONTAL_RULE_COMMAND, undefined),
}),
...EmbedConfigs.map(
(embedConfig) =>
new ComponentPickerOption(`Embed ${embedConfig.contentName}`, {
icon: embedConfig.icon,
keywords: [...embedConfig.keywords, 'embed'],
onSelect: () =>
editor.dispatchCommand(INSERT_EMBED_COMMAND, embedConfig.type),
}),
),
new ComponentPickerOption('Collapsible', {
icon: <i className="icon caret-right" />,
keywords: ['collapse', 'collapsible', 'toggle'],
onSelect: () =>
editor.dispatchCommand(INSERT_COLLAPSIBLE_COMMAND, undefined),
}),
...['left', 'center', 'right', 'justify'].map(
(alignment) =>
new ComponentPickerOption(`Align ${alignment}`, {
icon: <i className={`icon ${alignment}-align`} />,
keywords: ['align', 'justify', alignment],
onSelect: () =>
// @ts-ignore Correct types, but since they're dynamic TS doesn't like it.
editor.dispatchCommand(FORMAT_ELEMENT_COMMAND, alignment),
}),
),
];
const dynamicOptions = getDynamicOptions();
return queryString
? [
...dynamicOptions,
...baseOptions.filter((option) => {
return new RegExp(queryString, 'gi').exec(option.title) ||
option.keywords != null
? option.keywords.some((keyword) =>
new RegExp(queryString, 'gi').exec(keyword),
)
: false;
}),
]
: baseOptions;
}, [editor, getDynamicOptions, queryString, showModal]);
const onSelectOption = useCallback(
(
selectedOption: ComponentPickerOption,
nodeToRemove: TextNode | null,
closeMenu: () => void,
matchingString: string,
) => {
editor.update(() => {
if (nodeToRemove) {
nodeToRemove.remove();
}
selectedOption.onSelect(matchingString);
closeMenu();
});
},
[editor],
);
return (
<>
{modal}
<LexicalTypeaheadMenuPlugin<ComponentPickerOption>
onQueryChange={setQueryString}
onSelectOption={onSelectOption}
triggerFn={checkForTriggerMatch}
options={options}
menuRenderFn={(
anchorElementRef,
{selectedIndex, selectOptionAndCleanUp, setHighlightedIndex},
) =>
anchorElementRef.current && options.length
? ReactDOM.createPortal(
<div className="typeahead-popover component-picker-menu">
<ul>
{options.map((option, i: number) => (
<ComponentPickerMenuItem
index={i}
isSelected={selectedIndex === i}
onClick={() => {
setHighlightedIndex(i);
selectOptionAndCleanUp(option);
}}
onMouseEnter={() => {
setHighlightedIndex(i);
}}
key={option.key}
option={option}
/>
))}
</ul>
</div>,
anchorElementRef.current,
)
: null
}
/>
</>
);
}

Some files were not shown because too many files have changed in this diff Show More