├── .cspell.json ├── .devcontainer └── devcontainer.json ├── .editorconfig ├── .firebaserc ├── .github ├── .bundlemonrc.json ├── CODEOWNERS ├── renovate.json ├── screenshot-bot.config.toml └── workflows │ ├── assign-author.yml │ ├── auto-merge.yml │ ├── build-demo.yml │ ├── build.yml │ ├── cleanup-resources.yml │ ├── deploy-prod.yml │ ├── deploy-staging.yml │ ├── e2e.yml │ ├── lint.yml │ ├── ready-to-merge.yml │ ├── release.yml │ ├── snapshots.yml │ └── test.yml ├── .gitignore ├── .husky ├── .gitignore ├── commit-msg └── pre-commit ├── .release-it.js ├── CHANGELOG.md ├── LICENSE ├── README.md ├── codecov.yml ├── eslint.config.ts ├── firebase.json ├── jest.config.ts ├── nx.json ├── package-lock.json ├── package.json ├── projects ├── demo-playwright │ ├── playwright.config.ts │ ├── project.json │ ├── stubs │ │ ├── github-avatar.jpeg │ │ ├── html.ts │ │ └── web-api.svg │ ├── tests │ │ ├── anchors.spec.ts │ │ ├── font.spec.ts │ │ ├── groups.spec.ts │ │ ├── img.spec.ts │ │ ├── links.spec.ts │ │ ├── reset.spec.ts │ │ └── toolbar.spec.ts │ └── utils │ │ ├── goto.ts │ │ ├── index.ts │ │ ├── mock-date.ts │ │ ├── mock-images.ts │ │ ├── wait-for-fonts.ts │ │ └── wait-stable-state.ts ├── demo │ ├── project.json │ ├── server │ │ ├── generate-routes.ts │ │ ├── server.ts │ │ └── webpack.server.config.ts │ ├── src │ │ ├── 404.html │ │ ├── app │ │ │ ├── app.component.css │ │ │ ├── app.component.html │ │ │ ├── app.component.ts │ │ │ ├── app.config.ts │ │ │ ├── app.pages.ts │ │ │ ├── app.routes.ts │ │ │ ├── pages │ │ │ │ ├── anchors │ │ │ │ │ ├── examples │ │ │ │ │ │ └── 1 │ │ │ │ │ │ │ ├── index.html │ │ │ │ │ │ │ └── index.ts │ │ │ │ │ ├── index.html │ │ │ │ │ └── index.ts │ │ │ │ ├── appearance │ │ │ │ │ ├── examples │ │ │ │ │ │ └── 1 │ │ │ │ │ │ │ ├── index.html │ │ │ │ │ │ │ ├── index.less │ │ │ │ │ │ │ └── index.ts │ │ │ │ │ ├── index.html │ │ │ │ │ └── index.ts │ │ │ │ ├── checkbox │ │ │ │ │ ├── examples │ │ │ │ │ │ └── 1 │ │ │ │ │ │ │ ├── index.html │ │ │ │ │ │ │ └── index.ts │ │ │ │ │ ├── index.html │ │ │ │ │ └── index.ts │ │ │ │ ├── custom-tool │ │ │ │ │ ├── color-picker │ │ │ │ │ │ ├── examples │ │ │ │ │ │ │ └── 1 │ │ │ │ │ │ │ │ ├── custom-color-picker │ │ │ │ │ │ │ │ ├── index.html │ │ │ │ │ │ │ │ ├── index.less │ │ │ │ │ │ │ │ └── index.ts │ │ │ │ │ │ │ │ ├── index.html │ │ │ │ │ │ │ │ └── index.ts │ │ │ │ │ │ ├── index.html │ │ │ │ │ │ └── index.ts │ │ │ │ │ ├── paste-emoji │ │ │ │ │ │ ├── examples │ │ │ │ │ │ │ └── 1 │ │ │ │ │ │ │ │ ├── index.html │ │ │ │ │ │ │ │ ├── index.less │ │ │ │ │ │ │ │ ├── index.ts │ │ │ │ │ │ │ │ └── smiles-tool │ │ │ │ │ │ │ │ ├── emoji.extension.ts │ │ │ │ │ │ │ │ ├── smiles-tool.component.ts │ │ │ │ │ │ │ │ ├── smiles-tool.styles.less │ │ │ │ │ │ │ │ └── smiles-tool.template.html │ │ │ │ │ │ ├── index.html │ │ │ │ │ │ └── index.ts │ │ │ │ │ └── paste-image │ │ │ │ │ │ ├── examples │ │ │ │ │ │ └── 1 │ │ │ │ │ │ │ ├── image-tool │ │ │ │ │ │ │ ├── image-tool.component.ts │ │ │ │ │ │ │ ├── image-tool.styles.less │ │ │ │ │ │ │ ├── image-tool.template.html │ │ │ │ │ │ │ └── paste.extension.ts │ │ │ │ │ │ │ ├── index.html │ │ │ │ │ │ │ ├── index.less │ │ │ │ │ │ │ └── index.ts │ │ │ │ │ │ ├── index.html │ │ │ │ │ │ └── index.ts │ │ │ │ ├── embed │ │ │ │ │ ├── html5 │ │ │ │ │ │ ├── examples │ │ │ │ │ │ │ └── 1 │ │ │ │ │ │ │ │ ├── index.html │ │ │ │ │ │ │ │ ├── index.less │ │ │ │ │ │ │ │ └── index.ts │ │ │ │ │ │ ├── index.html │ │ │ │ │ │ └── index.ts │ │ │ │ │ ├── iframe │ │ │ │ │ │ ├── examples │ │ │ │ │ │ │ └── 1 │ │ │ │ │ │ │ │ ├── embed-tool │ │ │ │ │ │ │ │ ├── embed-tool.component.ts │ │ │ │ │ │ │ │ ├── embed-tool.styles.less │ │ │ │ │ │ │ │ └── embed-tool.template.html │ │ │ │ │ │ │ │ ├── index.html │ │ │ │ │ │ │ │ ├── index.less │ │ │ │ │ │ │ │ └── index.ts │ │ │ │ │ │ ├── index.html │ │ │ │ │ │ └── index.ts │ │ │ │ │ ├── pdf │ │ │ │ │ │ ├── examples │ │ │ │ │ │ │ └── 1 │ │ │ │ │ │ │ │ ├── index.html │ │ │ │ │ │ │ │ ├── index.less │ │ │ │ │ │ │ │ └── index.ts │ │ │ │ │ │ ├── index.html │ │ │ │ │ │ └── index.ts │ │ │ │ │ └── youtube │ │ │ │ │ │ ├── examples │ │ │ │ │ │ └── 1 │ │ │ │ │ │ │ ├── index.html │ │ │ │ │ │ │ ├── index.less │ │ │ │ │ │ │ ├── index.ts │ │ │ │ │ │ │ └── youtube-tool │ │ │ │ │ │ │ ├── youtube-tool.component.ts │ │ │ │ │ │ │ ├── youtube-tool.styles.less │ │ │ │ │ │ │ └── youtube-tool.template.html │ │ │ │ │ │ ├── index.html │ │ │ │ │ │ └── index.ts │ │ │ │ ├── focus │ │ │ │ │ ├── examples │ │ │ │ │ │ └── 1 │ │ │ │ │ │ │ ├── index.html │ │ │ │ │ │ │ ├── index.less │ │ │ │ │ │ │ └── index.ts │ │ │ │ │ ├── index.html │ │ │ │ │ └── index.ts │ │ │ │ ├── font │ │ │ │ │ ├── examples │ │ │ │ │ │ ├── 1 │ │ │ │ │ │ │ ├── index.html │ │ │ │ │ │ │ ├── index.less │ │ │ │ │ │ │ └── index.ts │ │ │ │ │ │ ├── 2 │ │ │ │ │ │ │ ├── index.html │ │ │ │ │ │ │ └── index.ts │ │ │ │ │ │ ├── 3 │ │ │ │ │ │ │ ├── font-size-tool │ │ │ │ │ │ │ │ ├── index.html │ │ │ │ │ │ │ │ ├── index.less │ │ │ │ │ │ │ │ └── index.ts │ │ │ │ │ │ │ ├── index.html │ │ │ │ │ │ │ └── index.ts │ │ │ │ │ │ └── 4 │ │ │ │ │ │ │ ├── index.html │ │ │ │ │ │ │ └── index.ts │ │ │ │ │ ├── index.html │ │ │ │ │ └── index.ts │ │ │ │ ├── groups │ │ │ │ │ ├── examples │ │ │ │ │ │ ├── 1 │ │ │ │ │ │ │ ├── index.html │ │ │ │ │ │ │ └── index.ts │ │ │ │ │ │ ├── 2 │ │ │ │ │ │ │ ├── index.html │ │ │ │ │ │ │ ├── index.less │ │ │ │ │ │ │ └── index.ts │ │ │ │ │ │ └── 3 │ │ │ │ │ │ │ ├── index.html │ │ │ │ │ │ │ ├── index.less │ │ │ │ │ │ │ └── index.ts │ │ │ │ │ ├── index.html │ │ │ │ │ └── index.ts │ │ │ │ ├── highlight │ │ │ │ │ ├── code │ │ │ │ │ │ ├── examples │ │ │ │ │ │ │ └── 1 │ │ │ │ │ │ │ │ ├── example.md │ │ │ │ │ │ │ │ ├── index.html │ │ │ │ │ │ │ │ ├── index.less │ │ │ │ │ │ │ │ └── index.ts │ │ │ │ │ │ ├── index.html │ │ │ │ │ │ └── index.ts │ │ │ │ │ └── text │ │ │ │ │ │ ├── examples │ │ │ │ │ │ └── 1 │ │ │ │ │ │ │ ├── index.html │ │ │ │ │ │ │ ├── index.less │ │ │ │ │ │ │ └── index.ts │ │ │ │ │ │ ├── index.html │ │ │ │ │ │ └── index.ts │ │ │ │ ├── images │ │ │ │ │ ├── preview │ │ │ │ │ │ ├── examples │ │ │ │ │ │ │ └── 1 │ │ │ │ │ │ │ │ ├── image-preview │ │ │ │ │ │ │ │ ├── image-preview.component.ts │ │ │ │ │ │ │ │ ├── image-preview.style.less │ │ │ │ │ │ │ │ └── image-preview.template.html │ │ │ │ │ │ │ │ ├── index.html │ │ │ │ │ │ │ │ └── index.ts │ │ │ │ │ │ ├── index.html │ │ │ │ │ │ └── index.ts │ │ │ │ │ ├── resizable │ │ │ │ │ │ ├── examples │ │ │ │ │ │ │ └── 1 │ │ │ │ │ │ │ │ ├── index.html │ │ │ │ │ │ │ │ └── index.ts │ │ │ │ │ │ ├── index.html │ │ │ │ │ │ └── index.ts │ │ │ │ │ └── upload │ │ │ │ │ │ ├── examples │ │ │ │ │ │ └── 1 │ │ │ │ │ │ │ ├── image-loader.ts │ │ │ │ │ │ │ ├── imgbb.service.ts │ │ │ │ │ │ │ ├── index.html │ │ │ │ │ │ │ └── index.ts │ │ │ │ │ │ ├── index.html │ │ │ │ │ │ └── index.ts │ │ │ │ ├── mention │ │ │ │ │ ├── examples │ │ │ │ │ │ └── 1 │ │ │ │ │ │ │ ├── index.html │ │ │ │ │ │ │ ├── index.less │ │ │ │ │ │ │ ├── index.ts │ │ │ │ │ │ │ └── mention │ │ │ │ │ │ │ ├── index.html │ │ │ │ │ │ │ └── index.ts │ │ │ │ │ ├── index.html │ │ │ │ │ └── index.ts │ │ │ │ ├── options │ │ │ │ │ ├── examples │ │ │ │ │ │ ├── 1 │ │ │ │ │ │ │ ├── index.html │ │ │ │ │ │ │ └── index.ts │ │ │ │ │ │ └── 2 │ │ │ │ │ │ │ ├── index.html │ │ │ │ │ │ │ └── index.ts │ │ │ │ │ ├── import │ │ │ │ │ │ └── tokens │ │ │ │ │ │ │ └── options.md │ │ │ │ │ ├── index.html │ │ │ │ │ └── index.ts │ │ │ │ ├── processing │ │ │ │ │ ├── cleanup-html │ │ │ │ │ │ ├── examples │ │ │ │ │ │ │ └── 1 │ │ │ │ │ │ │ │ ├── index.html │ │ │ │ │ │ │ │ ├── index.ts │ │ │ │ │ │ │ │ └── transformer.ts │ │ │ │ │ │ ├── index.html │ │ │ │ │ │ └── index.ts │ │ │ │ │ ├── legacy-html │ │ │ │ │ │ ├── examples │ │ │ │ │ │ │ └── 1 │ │ │ │ │ │ │ │ ├── index.html │ │ │ │ │ │ │ │ ├── index.ts │ │ │ │ │ │ │ │ └── transformer.ts │ │ │ │ │ │ ├── index.html │ │ │ │ │ │ └── index.ts │ │ │ │ │ ├── markdown-extension │ │ │ │ │ │ ├── examples │ │ │ │ │ │ │ └── 1 │ │ │ │ │ │ │ │ ├── index.html │ │ │ │ │ │ │ │ └── index.ts │ │ │ │ │ │ ├── index.html │ │ │ │ │ │ └── index.ts │ │ │ │ │ └── markdown │ │ │ │ │ │ ├── examples │ │ │ │ │ │ └── 1 │ │ │ │ │ │ │ ├── example.md │ │ │ │ │ │ │ ├── index.html │ │ │ │ │ │ │ ├── index.less │ │ │ │ │ │ │ ├── index.ts │ │ │ │ │ │ │ └── transformer.ts │ │ │ │ │ │ ├── index.html │ │ │ │ │ │ └── index.ts │ │ │ │ ├── starter │ │ │ │ │ ├── import │ │ │ │ │ │ ├── angular.json.md │ │ │ │ │ │ ├── component.md │ │ │ │ │ │ ├── styles.less.md │ │ │ │ │ │ └── template.md │ │ │ │ │ ├── index.html │ │ │ │ │ ├── index.less │ │ │ │ │ └── index.ts │ │ │ │ ├── toolbar │ │ │ │ │ ├── bottom │ │ │ │ │ │ ├── examples │ │ │ │ │ │ │ └── 1 │ │ │ │ │ │ │ │ ├── index.html │ │ │ │ │ │ │ │ ├── index.less │ │ │ │ │ │ │ │ └── index.ts │ │ │ │ │ │ ├── index.html │ │ │ │ │ │ └── index.ts │ │ │ │ │ ├── custom │ │ │ │ │ │ ├── examples │ │ │ │ │ │ │ └── 1 │ │ │ │ │ │ │ │ ├── custom-toolbar │ │ │ │ │ │ │ │ ├── index.html │ │ │ │ │ │ │ │ ├── index.less │ │ │ │ │ │ │ │ └── index.ts │ │ │ │ │ │ │ │ ├── index.html │ │ │ │ │ │ │ │ └── index.ts │ │ │ │ │ │ ├── index.html │ │ │ │ │ │ └── index.ts │ │ │ │ │ ├── floating │ │ │ │ │ │ ├── examples │ │ │ │ │ │ │ └── 1 │ │ │ │ │ │ │ │ ├── index.html │ │ │ │ │ │ │ │ ├── index.less │ │ │ │ │ │ │ │ └── index.ts │ │ │ │ │ │ ├── index.html │ │ │ │ │ │ ├── index.less │ │ │ │ │ │ └── index.ts │ │ │ │ │ └── shared │ │ │ │ │ │ ├── examples │ │ │ │ │ │ └── 1 │ │ │ │ │ │ │ ├── index.html │ │ │ │ │ │ │ ├── index.less │ │ │ │ │ │ │ └── index.ts │ │ │ │ │ │ ├── index.html │ │ │ │ │ │ └── index.ts │ │ │ │ └── upload-files │ │ │ │ │ ├── examples │ │ │ │ │ └── 1 │ │ │ │ │ │ ├── file-loader.ts │ │ │ │ │ │ ├── filesio.service.ts │ │ │ │ │ │ ├── index.html │ │ │ │ │ │ ├── index.less │ │ │ │ │ │ └── index.ts │ │ │ │ │ ├── file-attach.md │ │ │ │ │ ├── index.html │ │ │ │ │ └── index.ts │ │ │ └── shared │ │ │ │ ├── logo │ │ │ │ ├── index.html │ │ │ │ ├── index.less │ │ │ │ └── index.ts │ │ │ │ └── routes.ts │ │ ├── assets │ │ │ ├── icons │ │ │ │ ├── angular.svg │ │ │ │ ├── github.svg │ │ │ │ ├── javascript.svg │ │ │ │ ├── logo.svg │ │ │ │ ├── stackblitz.svg │ │ │ │ └── telegram.svg │ │ │ ├── images │ │ │ │ ├── angular.svg │ │ │ │ ├── big-wallpaper.jpg │ │ │ │ ├── lumberjack.png │ │ │ │ └── piece-and-war.jpg │ │ │ └── manifest.webmanifest │ │ ├── environments │ │ │ ├── environment.prod.ts │ │ │ └── environment.ts │ │ ├── index.html │ │ ├── main.server.ts │ │ ├── main.ts │ │ ├── polyfills.ts │ │ ├── styles.less │ │ └── typings.d.ts │ ├── tsconfig.app.json │ ├── tsconfig.server.json │ └── webpack.config.ts └── editor │ ├── common │ ├── attached.ts │ ├── default-editor-colors.ts │ ├── default-editor-tools.ts │ ├── default-events.ts │ ├── default-font-options-handler.ts │ ├── default-html5-media-attributes.ts │ ├── default-link-options-handler.ts │ ├── editor-adapter.ts │ ├── editor-extensions.ts │ ├── editor-font-option.ts │ ├── editor-options.ts │ ├── editor-sanitizer.ts │ ├── editor-tool.ts │ ├── editor-value-transformer.ts │ ├── files-loader.ts │ ├── gradient-direction.ts │ ├── hack.ts │ ├── i18n.ts │ ├── iframe.ts │ ├── image-loader.ts │ ├── image.ts │ ├── index.ts │ ├── ng-package.json │ ├── parsed-gradient.ts │ ├── tiptap-editor.ts │ ├── tui-link-attributes.ts │ ├── types │ │ ├── index.ts │ │ └── ng-package.json │ └── youtube.ts │ ├── components │ ├── edit-link │ │ ├── edit-link.component.ts │ │ ├── edit-link.style.less │ │ ├── edit-link.template.html │ │ ├── index.ts │ │ ├── ng-package.json │ │ ├── pipes │ │ │ ├── filter-anchors.pipe.ts │ │ │ └── short-url.pipe.ts │ │ └── utils │ │ │ └── edit-link-parse-url.ts │ ├── editor-resizable │ │ ├── editor-resizable.abstract.ts │ │ ├── editor-resizable.component.html │ │ ├── editor-resizable.component.less │ │ ├── editor-resizable.component.ts │ │ ├── index.ts │ │ └── ng-package.json │ ├── editor-socket │ │ ├── editor-socket.component.less │ │ ├── editor-socket.component.ts │ │ ├── index.ts │ │ ├── ng-package.json │ │ └── styles │ │ │ ├── checkbox.less │ │ │ ├── code-blocks.less │ │ │ ├── details.less │ │ │ ├── groups.less │ │ │ ├── link.less │ │ │ ├── list.less │ │ │ ├── media.less │ │ │ ├── placeholder.less │ │ │ ├── table.less │ │ │ └── text.less │ ├── editor │ │ ├── dropdown │ │ │ └── dropdown-toolbar.directive.ts │ │ ├── editor.component.html │ │ ├── editor.component.less │ │ ├── editor.component.ts │ │ ├── editor.providers.ts │ │ ├── index.ts │ │ ├── ng-package.json │ │ └── portal │ │ │ ├── editor-portal-host.component.ts │ │ │ ├── editor-portal-host.style.less │ │ │ ├── editor-portal.directive.ts │ │ │ └── editor-portal.service.ts │ ├── index.ts │ ├── ng-package.json │ ├── toolbar-host │ │ ├── index.ts │ │ ├── ng-package.json │ │ ├── toolbar-host.component.html │ │ ├── toolbar-host.component.ts │ │ ├── toolbar-host.style.less │ │ └── toolbar-navigation-manager.directive.ts │ ├── toolbar-tools │ │ ├── align-content │ │ │ ├── index.html │ │ │ ├── index.ts │ │ │ └── ng-package.json │ │ ├── anchor │ │ │ ├── index.html │ │ │ ├── index.ts │ │ │ └── ng-package.json │ │ ├── attach │ │ │ ├── index.html │ │ │ ├── index.ts │ │ │ └── ng-package.json │ │ ├── clear │ │ │ ├── index.html │ │ │ ├── index.ts │ │ │ └── ng-package.json │ │ ├── code │ │ │ ├── index.html │ │ │ ├── index.ts │ │ │ └── ng-package.json │ │ ├── details-remove │ │ │ ├── index.html │ │ │ ├── index.ts │ │ │ └── ng-package.json │ │ ├── details │ │ │ ├── index.html │ │ │ ├── index.ts │ │ │ └── ng-package.json │ │ ├── font-size │ │ │ ├── index.html │ │ │ ├── index.ts │ │ │ └── ng-package.json │ │ ├── font-style │ │ │ ├── index.html │ │ │ ├── index.ts │ │ │ └── ng-package.json │ │ ├── group │ │ │ ├── index.html │ │ │ ├── index.ts │ │ │ └── ng-package.json │ │ ├── highlight-color │ │ │ ├── index.html │ │ │ ├── index.ts │ │ │ └── ng-package.json │ │ ├── hr │ │ │ ├── index.html │ │ │ ├── index.ts │ │ │ └── ng-package.json │ │ ├── image │ │ │ ├── index.html │ │ │ ├── index.ts │ │ │ └── ng-package.json │ │ ├── index.ts │ │ ├── link │ │ │ ├── index.html │ │ │ ├── index.ts │ │ │ └── ng-package.json │ │ ├── list-configs │ │ │ ├── index.html │ │ │ ├── index.ts │ │ │ └── ng-package.json │ │ ├── ng-package.json │ │ ├── quote │ │ │ ├── index.html │ │ │ ├── index.ts │ │ │ └── ng-package.json │ │ ├── redo │ │ │ ├── index.html │ │ │ ├── index.ts │ │ │ └── ng-package.json │ │ ├── subscript │ │ │ ├── index.html │ │ │ ├── index.ts │ │ │ └── ng-package.json │ │ ├── superscript │ │ │ ├── index.html │ │ │ ├── index.ts │ │ │ └── ng-package.json │ │ ├── table-cell-color │ │ │ ├── index.html │ │ │ ├── index.ts │ │ │ └── ng-package.json │ │ ├── table-create │ │ │ ├── index.html │ │ │ ├── index.ts │ │ │ ├── ng-package.json │ │ │ └── table-size-selector │ │ │ │ ├── index.html │ │ │ │ ├── index.less │ │ │ │ ├── index.ts │ │ │ │ └── ng-package.json │ │ ├── table-merge-cells │ │ │ ├── index.html │ │ │ ├── index.ts │ │ │ └── ng-package.json │ │ ├── table-row-column-manager │ │ │ ├── index.html │ │ │ ├── index.ts │ │ │ └── ng-package.json │ │ ├── tex │ │ │ ├── index.html │ │ │ ├── index.ts │ │ │ └── ng-package.json │ │ ├── text-color │ │ │ ├── index.html │ │ │ ├── index.ts │ │ │ └── ng-package.json │ │ └── undo │ │ │ ├── index.html │ │ │ ├── index.ts │ │ │ └── ng-package.json │ └── toolbar │ │ ├── index.ts │ │ ├── ng-package.json │ │ ├── toolbar.component.ts │ │ ├── toolbar.style.less │ │ └── toolbar.template.html │ ├── directives │ ├── image-preview │ │ ├── index.ts │ │ └── ng-package.json │ ├── index.ts │ ├── ng-package.json │ └── tiptap-editor │ │ ├── index.ts │ │ ├── ng-package.json │ │ ├── tiptap-editor.directive.ts │ │ ├── tiptap-editor.service.ts │ │ └── utils │ │ └── is-empty-paragraph.ts │ ├── extensions │ ├── background-color │ │ ├── index.ts │ │ └── ng-package.json │ ├── default-editor-extensions │ │ ├── index.ts │ │ └── ng-package.json │ ├── details │ │ ├── index.ts │ │ └── ng-package.json │ ├── enter │ │ ├── index.ts │ │ └── ng-package.json │ ├── file-link │ │ ├── index.ts │ │ └── ng-package.json │ ├── font-color │ │ ├── index.ts │ │ └── ng-package.json │ ├── font-size │ │ ├── index.ts │ │ └── ng-package.json │ ├── group │ │ ├── index.ts │ │ └── ng-package.json │ ├── horizontal │ │ ├── index.ts │ │ └── ng-package.json │ ├── iframe-editor │ │ ├── iframe-editor.component.html │ │ ├── iframe-editor.component.less │ │ ├── iframe-editor.component.ts │ │ ├── iframe-editor.extension.ts │ │ ├── iframe-editor.options.ts │ │ ├── index.ts │ │ └── ng-package.json │ ├── image-editor │ │ ├── image-editor.component.html │ │ ├── image-editor.component.less │ │ ├── image-editor.component.ts │ │ ├── image-editor.extension.ts │ │ ├── image-editor.options.ts │ │ ├── image-options-position.directive.ts │ │ ├── index.ts │ │ ├── ng-package.json │ │ └── options │ │ │ └── image-align │ │ │ ├── image-align.component.html │ │ │ ├── image-align.component.less │ │ │ └── image-align.component.ts │ ├── indent-outdent │ │ ├── index.ts │ │ └── ng-package.json │ ├── index.ts │ ├── jump-anchor │ │ ├── index.ts │ │ └── ng-package.json │ ├── link │ │ ├── index.ts │ │ └── ng-package.json │ ├── markdown │ │ ├── clipboard │ │ │ └── index.ts │ │ ├── extension │ │ │ └── index.ts │ │ ├── extensions │ │ │ ├── index.ts │ │ │ ├── marks │ │ │ │ ├── bold │ │ │ │ │ └── index.ts │ │ │ │ ├── code │ │ │ │ │ └── index.ts │ │ │ │ ├── html │ │ │ │ │ └── index.ts │ │ │ │ ├── index.ts │ │ │ │ ├── italic │ │ │ │ │ └── index.ts │ │ │ │ ├── link │ │ │ │ │ └── index.ts │ │ │ │ └── strike │ │ │ │ │ └── index.ts │ │ │ ├── nodes │ │ │ │ ├── blockquote │ │ │ │ │ └── index.ts │ │ │ │ ├── bullet-list │ │ │ │ │ └── index.ts │ │ │ │ ├── code-block │ │ │ │ │ └── index.ts │ │ │ │ ├── hard-break │ │ │ │ │ └── index.ts │ │ │ │ ├── heading │ │ │ │ │ └── index.ts │ │ │ │ ├── horizontal-rule │ │ │ │ │ └── index.ts │ │ │ │ ├── html │ │ │ │ │ └── index.ts │ │ │ │ ├── image │ │ │ │ │ └── index.ts │ │ │ │ ├── index.ts │ │ │ │ ├── list-item │ │ │ │ │ └── index.ts │ │ │ │ ├── ordered-list │ │ │ │ │ └── index.ts │ │ │ │ ├── paragraph │ │ │ │ │ └── index.ts │ │ │ │ ├── table │ │ │ │ │ └── index.ts │ │ │ │ ├── task-item │ │ │ │ │ └── index.ts │ │ │ │ ├── task-list │ │ │ │ │ └── index.ts │ │ │ │ └── text │ │ │ │ │ └── index.ts │ │ │ └── util │ │ │ │ └── index.ts │ │ ├── index.ts │ │ ├── ng-package.json │ │ ├── parse │ │ │ └── index.ts │ │ ├── serialize │ │ │ ├── index.ts │ │ │ ├── markdown-serializer.ts │ │ │ └── state.ts │ │ ├── tight-lists │ │ │ └── index.ts │ │ └── util │ │ │ ├── dom.ts │ │ │ ├── index.ts │ │ │ ├── markdown-it-task-lists.ts │ │ │ ├── markdown.ts │ │ │ └── prosemirror.ts │ ├── media │ │ ├── index.ts │ │ └── ng-package.json │ ├── mention │ │ ├── index.ts │ │ └── ng-package.json │ ├── ng-package.json │ ├── starter-kit │ │ ├── index.ts │ │ └── ng-package.json │ ├── table-cell-background │ │ ├── index.ts │ │ └── ng-package.json │ ├── table-cell │ │ ├── index.ts │ │ └── ng-package.json │ ├── table │ │ ├── index.ts │ │ └── ng-package.json │ ├── tiptap-node-view │ │ ├── index.ts │ │ └── ng-package.json │ └── youtube │ │ ├── index.ts │ │ └── ng-package.json │ ├── index.ts │ ├── jest.config.ts │ ├── ng-package.json │ ├── package.json │ ├── project.json │ ├── styles │ ├── editor-socket.css │ ├── editor-toolbar.css │ └── editor.css │ ├── test │ ├── edit-link-parse.spec.ts │ └── legacy-converter.spec.ts │ └── utils │ ├── delete-nodes.ts │ ├── get-current-word-bounds.ts │ ├── get-mark-range.ts │ ├── get-nested-nodes.ts │ ├── get-selected-content.ts │ ├── get-selection-state.ts │ ├── get-sliced-fragment.ts │ ├── index.ts │ ├── is-selection-in.ts │ ├── legacy-converter.ts │ ├── ng-package.json │ ├── parse-node-attributes.ts │ ├── parse-style.ts │ ├── safe-link-range.ts │ └── to-gradient.ts ├── scripts └── visual-testing │ ├── combine-playwright-failed-screenshots.ts │ └── combine-snapshots.ts ├── setup-jest.ts ├── tsconfig.build.json ├── tsconfig.json └── tsconfig.spec.json /.cspell.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://raw.githubusercontent.com/streetsidesoftware/cspell/master/cspell.schema.json", 3 | "import": ["@taiga-ui/cspell-config/cspell.config.js"], 4 | "ignoreWords": ["todoify", "Lyyy", "Textblock", "hardbreak", "softbreak", "inlines"], 5 | "files": ["*/*.*"] 6 | } 7 | -------------------------------------------------------------------------------- /.devcontainer/devcontainer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "editor dev container", 3 | "image": "mcr.microsoft.com/vscode/devcontainers/javascript-node:18", 4 | "hostRequirements": { 5 | "memory": "8gb", 6 | "cpus": 4 7 | }, 8 | "waitFor": "onCreateCommand", 9 | "updateContentCommand": "npm ci --force --loglevel silent --audit false --ignore-engines --ignore-platform --no-fund", 10 | "postCreateCommand": "", 11 | "postAttachCommand": "npm start -- --host 0.0.0.0 --disable-host-check", 12 | "customizations": { 13 | "codespaces": { 14 | "openFiles": ["CONTRIBUTING.md"] 15 | } 16 | }, 17 | "portsAttributes": { 18 | "3333": { 19 | "label": "Demo", 20 | "onAutoForward": "notify" 21 | } 22 | }, 23 | "forwardPorts": [3333] 24 | } 25 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # Editor configuration, see https://editorconfig.org 2 | root = true 3 | 4 | [*] 5 | charset = utf-8 6 | indent_style = space 7 | indent_size = 4 8 | insert_final_newline = true 9 | trim_trailing_whitespace = true 10 | 11 | [*.md] 12 | max_line_length = off 13 | trim_trailing_whitespace = false 14 | -------------------------------------------------------------------------------- /.firebaserc: -------------------------------------------------------------------------------- 1 | { 2 | "projects": {}, 3 | "targets": { 4 | "taiga-editor": { 5 | "hosting": { 6 | "taiga-editor": [ 7 | "taiga-editor" 8 | ], 9 | "taiga-editor-e2e-report": [ 10 | "taiga-editor-e2e-report" 11 | ] 12 | } 13 | } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /.github/.bundlemonrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "baseDir": "./dist", 3 | "files": [ 4 | { 5 | "path": "demo/browser/main..js", 6 | "maxPercentIncrease": 10 7 | }, 8 | { 9 | "path": "demo/browser/runtime..js", 10 | "maxPercentIncrease": 10 11 | }, 12 | { 13 | "path": "demo/browser/polyfills..js", 14 | "maxPercentIncrease": 10 15 | }, 16 | { 17 | "path": "demo/browser/styles..css", 18 | "maxPercentIncrease": 50 19 | } 20 | ], 21 | "groups": [ 22 | { 23 | "path": "demo/browser/*..js" 24 | } 25 | ], 26 | "reportOutput": [ 27 | [ 28 | "github", 29 | { 30 | "statusCheck": true, 31 | "prComment": true 32 | } 33 | ] 34 | ] 35 | } 36 | -------------------------------------------------------------------------------- /.github/CODEOWNERS: -------------------------------------------------------------------------------- 1 | * @taiga-family/core-team 2 | -------------------------------------------------------------------------------- /.github/renovate.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://docs.renovatebot.com/renovate-schema.json", 3 | "extends": ["github>taiga-family/renovate-config"], 4 | "packageRules": [ 5 | { 6 | "matchPackageNames": ["/^@nx.*/", "/^nx$/"], 7 | "enabled": false 8 | }, 9 | { 10 | "matchDepTypes": ["dependencies", "peerDependencies", "optionalDependencies"], 11 | "semanticCommitType": "fix" 12 | } 13 | ] 14 | } 15 | -------------------------------------------------------------------------------- /.github/screenshot-bot.config.toml: -------------------------------------------------------------------------------- 1 | # array of RegExp strings to match workflow names 2 | # which should be watched by bot 3 | workflowWithTests = [ 4 | '.*E2E*', # all workflows with sub-string "e2e" in their names will be watched by bot 5 | ] 6 | 7 | # array of RegExp strings to match images inside artifacts (by their path or file name) 8 | # which shows difference between two screenshot and which will be added to bot report comment 9 | screenshotsDiffsPaths = [ 10 | '.*__diff_output__.*', # it is default cypress folder name into which snapshot diffs are put 11 | '.*.diff.png' 12 | ] 13 | 14 | # RegExp string to match images inside artifacts (by their path or file name) 15 | # which are created by new screenshot tests. 16 | newScreenshotMark = '.*==new==.*' 17 | 18 | # array of RegExp strings to match branch names which should be skipped by bot 19 | branchesIgnore = ["^release/.*", "^v[0-9].x$"] 20 | 21 | # array of attributes (key="value") for html-tag (screenshots) 22 | screenshotImageAttrs = [] 23 | 24 | # Text which is placed at the beginning of section "Failed tests" 25 | failedTestsReportDescription = '

Before (main) ← Diff → After (local)

' 26 | -------------------------------------------------------------------------------- /.github/workflows/assign-author.yml: -------------------------------------------------------------------------------- 1 | name: 🤖 PR author as an assignee 2 | on: 3 | pull_request: 4 | types: [opened] 5 | 6 | jobs: 7 | assign: 8 | runs-on: ubuntu-latest 9 | steps: 10 | - uses: actions/checkout@v4.2.2 11 | - uses: taiga-family/ci/actions/setup/variables@v1.125.0 12 | - uses: toshimaru/auto-author-assign@v2.1.1 13 | continue-on-error: true 14 | 15 | concurrency: 16 | group: ${{ github.workflow }}-${{ github.ref }} 17 | cancel-in-progress: true 18 | -------------------------------------------------------------------------------- /.github/workflows/build-demo.yml: -------------------------------------------------------------------------------- 1 | name: ⚙️ Build demo 2 | on: 3 | push: 4 | branches: 5 | - main 6 | 7 | jobs: 8 | build-demo: 9 | name: Build demo 10 | runs-on: ubuntu-latest 11 | steps: 12 | - uses: actions/checkout@v4.2.2 13 | - uses: taiga-family/ci/actions/setup/variables@v1.125.0 14 | - uses: taiga-family/ci/actions/setup/node@v1.125.0 15 | - run: npx nx prerender editor-demo 16 | 17 | concurrency: 18 | group: ${{ github.workflow }}-${{ github.ref }} 19 | cancel-in-progress: true 20 | -------------------------------------------------------------------------------- /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: ⚙️ Build lib 2 | on: 3 | pull_request: 4 | push: 5 | branches: 6 | - main 7 | 8 | jobs: 9 | build: 10 | name: Build package 11 | runs-on: ubuntu-latest 12 | steps: 13 | - uses: actions/checkout@v4.2.2 14 | - uses: taiga-family/ci/actions/setup/variables@v1.125.0 15 | - uses: taiga-family/ci/actions/setup/node@v1.125.0 16 | - run: npx nx build editor 17 | 18 | concurrency: 19 | group: ${{ github.workflow }}-${{ github.ref }} 20 | cancel-in-progress: true 21 | -------------------------------------------------------------------------------- /.github/workflows/deploy-prod.yml: -------------------------------------------------------------------------------- 1 | name: 🚀 Deploy 2 | on: 3 | push: 4 | branches: [main] 5 | 6 | jobs: 7 | prod: 8 | runs-on: ubuntu-latest 9 | steps: 10 | - uses: actions/checkout@v4.2.2 11 | - uses: taiga-family/ci/actions/setup/variables@v1.125.0 12 | - uses: taiga-family/ci/actions/setup/node@v1.125.0 13 | - run: npx nx prerender editor-demo -c github 14 | - uses: taiga-family/ci/actions/deploy/github-pages@v1.125.0 15 | with: 16 | token: ${{ secrets.TAIGA_FAMILY_BOT_PAT }} 17 | folder: dist/demo/browser 18 | 19 | concurrency: 20 | group: ${{ github.workflow }}-${{ github.ref }} 21 | cancel-in-progress: true 22 | -------------------------------------------------------------------------------- /.github/workflows/deploy-staging.yml: -------------------------------------------------------------------------------- 1 | name: 🚀 Deploy 2 | on: pull_request 3 | 4 | jobs: 5 | staging: 6 | runs-on: ubuntu-latest 7 | steps: 8 | - uses: actions/checkout@v4.2.2 9 | - uses: taiga-family/ci/actions/setup/variables@v1.125.0 10 | - uses: taiga-family/ci/actions/setup/node@v1.125.0 11 | - run: npx nx build editor-demo 12 | - uses: FirebaseExtended/action-hosting-deploy@v0.9.0 13 | continue-on-error: true 14 | if: ${{ env.IS_FORK == 'false' && env.IS_DEPENDABOT == 'false' }} 15 | with: 16 | firebaseServiceAccount: ${{ secrets.FIREBASE_SERVICE_ACCOUNT_TUI_EDITOR }} 17 | channelId: pr${{ github.event.number }}-${{ github.head_ref }}-demo 18 | repoToken: ${{ secrets.GITHUB_TOKEN }} 19 | projectId: taiga-editor 20 | target: taiga-editor 21 | expires: 1d 22 | 23 | concurrency: 24 | group: ${{ github.workflow }}-${{ github.ref }} 25 | cancel-in-progress: true 26 | -------------------------------------------------------------------------------- /.github/workflows/lint.yml: -------------------------------------------------------------------------------- 1 | name: ⚙️ Lint 2 | on: 3 | pull_request: 4 | push: 5 | branches: 6 | - main 7 | 8 | jobs: 9 | lint: 10 | name: Lint 11 | runs-on: ubuntu-latest 12 | steps: 13 | - uses: actions/checkout@v4.2.2 14 | with: 15 | persist-credentials: false 16 | - uses: taiga-family/ci/actions/setup/variables@v1.125.0 17 | - uses: taiga-family/ci/actions/setup/node@v1.125.0 18 | - run: | 19 | npm run cspell 20 | 21 | if [[ "${{ env.SUPPORT_AUTO_PUSH }}" == "true" ]]; then 22 | npm run stylelint -- --fix 23 | npm run prettier -- --write 24 | npm run lint -- --fix 25 | else 26 | npm run stylelint 27 | npm run prettier -- --check 28 | npm run lint 29 | fi 30 | - uses: taiga-family/ci/actions/auto/push@v1.125.0 31 | with: 32 | token: ${{ secrets.TAIGA_FAMILY_BOT_PAT }} 33 | 34 | concurrency: 35 | group: ${{ github.workflow }}-${{ github.ref }} 36 | cancel-in-progress: true 37 | -------------------------------------------------------------------------------- /.github/workflows/ready-to-merge.yml: -------------------------------------------------------------------------------- 1 | name: 🤖 PR is ready to merge 2 | on: 3 | pull_request_review: 4 | types: [submitted] 5 | 6 | jobs: 7 | label-when-approved: 8 | name: Label when approved 9 | runs-on: ubuntu-latest 10 | if: github.event.review.state == 'approved' 11 | steps: 12 | - uses: actions/checkout@v4.2.2 13 | - uses: taiga-family/ci/actions/setup/variables@v1.125.0 14 | - uses: taiga-family/ci/actions/auto/label-when-approved@v1.125.0 15 | with: 16 | approvals: 1 17 | token: ${{ secrets.GITHUB_TOKEN }} 18 | 19 | concurrency: 20 | group: ${{ github.workflow }}-${{ github.ref }} 21 | cancel-in-progress: true 22 | -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: ⚙️ Tests 2 | on: 3 | pull_request: 4 | push: 5 | branches: 6 | - main 7 | 8 | jobs: 9 | tests: 10 | name: Tests 11 | runs-on: ubuntu-latest 12 | steps: 13 | - uses: actions/checkout@v4.2.2 14 | - uses: taiga-family/ci/actions/setup/variables@v1.125.0 15 | - uses: taiga-family/ci/actions/setup/node@v1.125.0 16 | - run: npx nx test editor --nxBail 17 | - uses: codecov/codecov-action@v5.4.3 18 | with: 19 | directory: ./coverage/editor 20 | flags: editor 21 | name: editor 22 | 23 | concurrency: 24 | group: ${{ github.workflow }}-${{ github.ref }} 25 | cancel-in-progress: true 26 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # compiled output 2 | /dist 3 | /tmp 4 | 5 | # dependencies 6 | **/node_modules 7 | 8 | # IDEs, VSCode 9 | .idea 10 | *.iml 11 | *.launch 12 | .vscode/* 13 | !.vscode/settings.json 14 | !.vscode/tasks.json 15 | !.vscode/launch.json 16 | !.vscode/extensions.json 17 | 18 | # misc 19 | /coverage 20 | test-results.json 21 | *.log 22 | 23 | # System Files 24 | .DS_Store 25 | Thumbs.db 26 | 27 | /projects/demo-playwright/tests-results/ 28 | /projects/demo-playwright/tests-report/ 29 | /projects/demo-playwright/snapshots/ 30 | /projects/demo-playwright/playwright/.cache/ 31 | 32 | # special 33 | routes.txt 34 | .angular 35 | .nx 36 | -------------------------------------------------------------------------------- /.husky/.gitignore: -------------------------------------------------------------------------------- 1 | _ 2 | -------------------------------------------------------------------------------- /.husky/commit-msg: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # shellcheck disable=SC1090 3 | . "$(dirname "$0")/_/husky.sh" 4 | 5 | npx commitlint --edit $1 6 | -------------------------------------------------------------------------------- /.husky/pre-commit: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # shellcheck disable=SC1090 3 | . "$(dirname "$0")/_/husky.sh" 4 | 5 | npx lint-staged 6 | -------------------------------------------------------------------------------- /.release-it.js: -------------------------------------------------------------------------------- 1 | module.exports = require('@taiga-ui/release-it-config'); 2 | -------------------------------------------------------------------------------- /codecov.yml: -------------------------------------------------------------------------------- 1 | codecov: 2 | branch: main 3 | notify: 4 | require_ci_to_pass: no 5 | 6 | coverage: 7 | # This value is used to customize the visible color range in Codecov. 8 | # The first number represents the red, and the second represents green. 9 | # You can change the range of colors by adjusting this configuration. 10 | range: 50..100 # by default 70..100 11 | round: down 12 | precision: 2 13 | 14 | # Disable codecov/patch check 15 | status: 16 | project: 17 | default: 18 | enabled: false 19 | patch: 20 | default: 21 | enabled: false 22 | -------------------------------------------------------------------------------- /eslint.config.ts: -------------------------------------------------------------------------------- 1 | import taiga from '@taiga-ui/eslint-plugin-experience-next'; 2 | 3 | export default [ 4 | ...taiga.configs.recommended, 5 | ...taiga.configs['taiga-specific'], 6 | { 7 | ignores: ['.release-it.js'], 8 | }, 9 | ]; 10 | -------------------------------------------------------------------------------- /firebase.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://raw.githubusercontent.com/firebase/firebase-tools/master/schema/firebase-config.json", 3 | "hosting": [ 4 | { 5 | "target": "taiga-editor", 6 | "public": "dist/demo/browser", 7 | "ignore": ["firebase.json", "**/.*", "**/node_modules/**"], 8 | "rewrites": [ 9 | { 10 | "source": "**", 11 | "destination": "/index.html" 12 | } 13 | ] 14 | }, 15 | { 16 | "target": "taiga-editor-e2e-report", 17 | "public": "projects/demo-playwright/tests-report", 18 | "ignore": ["firebase.json", "**/.*", "**/node_modules/**"], 19 | "rewrites": [ 20 | { 21 | "source": "**", 22 | "destination": "/index.html" 23 | } 24 | ] 25 | } 26 | ] 27 | } 28 | -------------------------------------------------------------------------------- /nx.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "./node_modules/nx/schemas/nx-schema.json", 3 | "defaultProject": "editor-demo", 4 | "workspaceLayout": { 5 | "libsDir": "projects", 6 | "appsDir": "projects" 7 | }, 8 | "namedInputs": { 9 | "default": ["{projectRoot}/**/*", "sharedGlobals"], 10 | "sharedGlobals": [ 11 | "{workspaceRoot}/angular.json", 12 | "{workspaceRoot}/nx.json", 13 | "{workspaceRoot}/tsconfig.*.json", 14 | "{workspaceRoot}/tsconfig.json" 15 | ], 16 | "production": ["default"] 17 | }, 18 | "targetDefaults": { 19 | "build": { 20 | "inputs": ["production", "^production"], 21 | "cache": true 22 | }, 23 | "test": { 24 | "cache": true 25 | } 26 | }, 27 | "parallel": 1, 28 | "defaultBase": "origin/main" 29 | } 30 | -------------------------------------------------------------------------------- /projects/demo-playwright/project.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "editor-demo-playwright", 3 | "$schema": "../../node_modules/nx/schemas/project-schema.json", 4 | "sourceRoot": "projects/demo-playwright", 5 | "projectType": "application", 6 | "prefix": "app", 7 | "targets": { 8 | "e2e": { 9 | "executor": "nx:run-commands", 10 | "options": { 11 | "command": "playwright test --config projects/demo-playwright/playwright.config.ts" 12 | } 13 | }, 14 | "e2e-ui": { 15 | "executor": "nx:run-commands", 16 | "options": { 17 | "command": "nx e2e editor-demo-playwright --ui --debug --update-snapshots" 18 | } 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /projects/demo-playwright/stubs/github-avatar.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/taiga-family/editor/9a95ffc5564fd972beec4719efbddc885906d532/projects/demo-playwright/stubs/github-avatar.jpeg -------------------------------------------------------------------------------- /projects/demo-playwright/tests/reset.spec.ts: -------------------------------------------------------------------------------- 1 | import {expect, test} from '@playwright/test'; 2 | 3 | import {tuiGoto} from '../utils'; 4 | 5 | test.describe('Reset', () => { 6 | test('Correct reset value from wysiwyg', async ({page}) => { 7 | await tuiGoto(page, '/starter-kit?placeholder=Hello'); 8 | 9 | await page.locator('tui-editor [contenteditable]').focus(); 10 | await page.waitForTimeout(300); 11 | 12 | await expect(page.locator('tui-editor')).toHaveScreenshot('Reset-01.png'); 13 | 14 | await page.locator('button:has-text("Reset")').click(); 15 | await page.waitForTimeout(300); 16 | 17 | await expect(page.locator('tui-editor')).toHaveScreenshot('Reset-02.png'); 18 | 19 | await page.locator('tui-editor [contenteditable]').fill('12345'); 20 | await page.waitForTimeout(300); 21 | 22 | await expect(page.locator('tui-editor')).toHaveScreenshot('Reset-03.png'); 23 | 24 | await page.locator('button:has-text("Reset")').click(); 25 | await page.waitForTimeout(300); 26 | 27 | await expect(page.locator('tui-editor')).toHaveScreenshot('Reset-04.png'); 28 | }); 29 | }); 30 | -------------------------------------------------------------------------------- /projects/demo-playwright/utils/index.ts: -------------------------------------------------------------------------------- 1 | export * from './goto'; 2 | export * from './mock-date'; 3 | export * from './mock-images'; 4 | -------------------------------------------------------------------------------- /projects/demo-playwright/utils/mock-date.ts: -------------------------------------------------------------------------------- 1 | import type {Page} from '@playwright/test'; 2 | 3 | export async function tuiMockDate(page: Page, date: Date): Promise { 4 | await page.addInitScript((fakeNow) => { 5 | // @ts-ignore 6 | globalThis.Date = class extends Date { 7 | constructor(...args: any[]) { 8 | if (args.length === 0) { 9 | super(fakeNow); 10 | } else { 11 | // @ts-ignore 12 | super(...args); 13 | } 14 | } 15 | }; 16 | 17 | Date.now = () => fakeNow; 18 | }, date.valueOf()); 19 | } 20 | -------------------------------------------------------------------------------- /projects/demo-playwright/utils/mock-images.ts: -------------------------------------------------------------------------------- 1 | import type {Page} from '@playwright/test'; 2 | 3 | const DEFAULT_MOCKS = [ 4 | { 5 | patterns: [ 6 | /.*avatar.jpg/, 7 | /.*avatars.githubusercontent.com.*/, 8 | /https:\/\/github.com\/.*.png.*/, 9 | /https:\/\/m.media-amazon.com\/.*.jpg.*/, 10 | /.*bf2e94ae58ee713717faf397958a8e3d.jpg/, // filename - MD5 hash value of file content (waterplea avatar) 11 | ], 12 | mockImage: `${__dirname}/../stubs/github-avatar.jpeg`, 13 | }, 14 | {patterns: [/.*web-api.svg/], mockImage: `${__dirname}/../stubs/web-api.svg`}, 15 | ] as const; 16 | 17 | export async function tuiMockImages( 18 | page: Page, 19 | imagesMocks: ReadonlyArray<{ 20 | patterns: readonly RegExp[]; 21 | mockImage: string; 22 | }> = DEFAULT_MOCKS, 23 | ): Promise { 24 | for (const {patterns, mockImage} of imagesMocks) { 25 | const pattern = new RegExp(patterns.map((reg) => reg.source).join('|')); 26 | 27 | await page.route(pattern, async (route) => route.fulfill({path: mockImage})); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /projects/demo-playwright/utils/wait-for-fonts.ts: -------------------------------------------------------------------------------- 1 | import type {Page} from '@playwright/test'; 2 | import {expect} from '@playwright/test'; 3 | 4 | export async function tuiWaitForFonts(page: Page): Promise { 5 | await expect(async () => { 6 | expect(await page.evaluate(() => (document as any).fonts.size)).toBeGreaterThan( 7 | 0, 8 | ); 9 | expect(await page.evaluate(() => (document as any).fonts.ready)).toBeTruthy(); 10 | expect(await page.evaluate(() => (document as any).fonts.status)).toBe('loaded'); 11 | }).toPass({timeout: 30_000}); 12 | } 13 | -------------------------------------------------------------------------------- /projects/demo-playwright/utils/wait-stable-state.ts: -------------------------------------------------------------------------------- 1 | import type {Locator} from '@playwright/test'; 2 | 3 | export async function waitStableState(locator: Locator): Promise { 4 | try { 5 | const handle = await locator.elementHandle(); 6 | 7 | // https://playwright.dev/docs/actionability#stable 8 | // element is Stable, as in not animating or completed animation 9 | // Element is considered stable when it has maintained 10 | // the same bounding box for at least two consecutive animation frames. 11 | await handle?.waitForElementState('stable'); 12 | 13 | // https://playwright.dev/docs/actionability#visible 14 | // Element is considered visible when it has non-empty bounding box 15 | await handle?.waitForElementState('visible'); 16 | } catch {} 17 | } 18 | -------------------------------------------------------------------------------- /projects/demo/server/webpack.server.config.ts: -------------------------------------------------------------------------------- 1 | import {makeWebpackConfig} from '../webpack.config'; 2 | 3 | export default makeWebpackConfig({server: true}); 4 | -------------------------------------------------------------------------------- /projects/demo/src/app/app.component.css: -------------------------------------------------------------------------------- 1 | [tuiDocHeader] { 2 | color: var(--tui-text-primary); 3 | background: var(--tui-background-base); 4 | } 5 | 6 | .t-links { 7 | display: flex; 8 | gap: 0.625rem; 9 | align-items: center; 10 | font: var(--tui-font-text-s); 11 | } 12 | 13 | .t-links > * { 14 | display: flex; 15 | } 16 | 17 | .t-link { 18 | margin-left: 1rem; 19 | } 20 | 21 | .t-stackblitz:active { 22 | transform: scale(0.98); 23 | box-shadow: 0.1875rem 0.125rem 1.375rem 0.0625rem rgba(0, 0, 0, 0.24); 24 | } 25 | 26 | .t-stackblitz_mobile { 27 | display: none; 28 | } 29 | 30 | header { 31 | position: unset; 32 | margin-left: 16.25rem; 33 | box-shadow: none; 34 | } 35 | 36 | @media only screen and (max-width: 768px) { 37 | header { 38 | margin-left: 0; 39 | } 40 | 41 | .t-stackblitz { 42 | display: none; 43 | } 44 | 45 | .t-stackblitz_mobile { 46 | display: block; 47 | padding-left: 12px; 48 | } 49 | 50 | .t-app-version { 51 | display: none !important; 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /projects/demo/src/app/pages/anchors/examples/1/index.html: -------------------------------------------------------------------------------- 1 | 6 | 7 |

HTML:

8 | 9 | 10 |

Text:

11 |

{{ control.value }}

12 | -------------------------------------------------------------------------------- /projects/demo/src/app/pages/anchors/index.html: -------------------------------------------------------------------------------- 1 | 5 | 12 | 13 | -------------------------------------------------------------------------------- /projects/demo/src/app/pages/anchors/index.ts: -------------------------------------------------------------------------------- 1 | import {ChangeDetectionStrategy, Component} from '@angular/core'; 2 | import {RouterLink} from '@angular/router'; 3 | import {TuiAddonDoc} from '@taiga-ui/addon-doc'; 4 | import {TuiLink} from '@taiga-ui/core'; 5 | import {TUI_EDITOR_DEFAULT_EXTENSIONS, TUI_EDITOR_EXTENSIONS} from '@taiga-ui/editor'; 6 | 7 | @Component({ 8 | standalone: true, 9 | selector: 'editor-anchors', 10 | imports: [RouterLink, TuiAddonDoc, TuiLink], 11 | templateUrl: './index.html', 12 | changeDetection: ChangeDetectionStrategy.OnPush, 13 | providers: [ 14 | { 15 | provide: TUI_EDITOR_EXTENSIONS, 16 | useValue: TUI_EDITOR_DEFAULT_EXTENSIONS, 17 | }, 18 | ], 19 | }) 20 | export default class Example { 21 | protected readonly component1 = import('./examples/1'); 22 | 23 | protected readonly example1 = { 24 | TypeScript: import('./examples/1/index.ts?raw'), 25 | HTML: import('./examples/1/index.html?raw'), 26 | }; 27 | } 28 | -------------------------------------------------------------------------------- /projects/demo/src/app/pages/appearance/examples/1/index.html: -------------------------------------------------------------------------------- 1 | 6 | -------------------------------------------------------------------------------- /projects/demo/src/app/pages/appearance/examples/1/index.less: -------------------------------------------------------------------------------- 1 | [tuiAppearance][data-appearance='no-border'] { 2 | outline: none; 3 | } 4 | -------------------------------------------------------------------------------- /projects/demo/src/app/pages/appearance/examples/1/index.ts: -------------------------------------------------------------------------------- 1 | import {ChangeDetectionStrategy, Component} from '@angular/core'; 2 | import {FormControl, ReactiveFormsModule} from '@angular/forms'; 3 | import {TuiEditor} from '@taiga-ui/editor'; 4 | 5 | @Component({ 6 | standalone: true, 7 | imports: [ReactiveFormsModule, TuiEditor], 8 | templateUrl: './index.html', 9 | styleUrls: ['./index.less'], 10 | changeDetection: ChangeDetectionStrategy.OnPush, 11 | }) 12 | export default class Example { 13 | protected readonly control = new FormControl(''); 14 | protected readonly builtInTools = []; 15 | } 16 | -------------------------------------------------------------------------------- /projects/demo/src/app/pages/appearance/index.html: -------------------------------------------------------------------------------- 1 | 5 | 11 | 12 | -------------------------------------------------------------------------------- /projects/demo/src/app/pages/appearance/index.ts: -------------------------------------------------------------------------------- 1 | import {ChangeDetectionStrategy, Component} from '@angular/core'; 2 | import {TuiAddonDoc} from '@taiga-ui/addon-doc'; 3 | import {TUI_EDITOR_DEFAULT_EXTENSIONS, TUI_EDITOR_EXTENSIONS} from '@taiga-ui/editor'; 4 | 5 | @Component({ 6 | standalone: true, 7 | imports: [TuiAddonDoc], 8 | templateUrl: './index.html', 9 | changeDetection: ChangeDetectionStrategy.OnPush, 10 | providers: [ 11 | { 12 | provide: TUI_EDITOR_EXTENSIONS, 13 | useValue: TUI_EDITOR_DEFAULT_EXTENSIONS, 14 | }, 15 | ], 16 | }) 17 | export default class Example { 18 | protected readonly component1 = import('./examples/1'); 19 | 20 | protected readonly example1 = { 21 | TypeScript: import('./examples/1/index.ts?raw'), 22 | HTML: import('./examples/1/index.html?raw'), 23 | LESS: import('./examples/1/index.less?raw'), 24 | }; 25 | } 26 | -------------------------------------------------------------------------------- /projects/demo/src/app/pages/checkbox/examples/1/index.html: -------------------------------------------------------------------------------- 1 | 5 | 6 |

HTML:

7 | 8 | 9 |

Text:

10 |

{{ control.value }}

11 | -------------------------------------------------------------------------------- /projects/demo/src/app/pages/checkbox/index.html: -------------------------------------------------------------------------------- 1 | 5 | 11 | 12 | -------------------------------------------------------------------------------- /projects/demo/src/app/pages/checkbox/index.ts: -------------------------------------------------------------------------------- 1 | import {ChangeDetectionStrategy, Component} from '@angular/core'; 2 | import {TuiAddonDoc} from '@taiga-ui/addon-doc'; 3 | 4 | @Component({ 5 | standalone: true, 6 | imports: [TuiAddonDoc], 7 | templateUrl: './index.html', 8 | changeDetection: ChangeDetectionStrategy.OnPush, 9 | }) 10 | export default class Example { 11 | protected readonly component1 = import('./examples/1'); 12 | protected readonly example1 = { 13 | TypeScript: import('./examples/1/index.ts?raw'), 14 | HTML: import('./examples/1/index.html?raw'), 15 | }; 16 | } 17 | -------------------------------------------------------------------------------- /projects/demo/src/app/pages/custom-tool/color-picker/examples/1/custom-color-picker/index.html: -------------------------------------------------------------------------------- 1 |
2 | 15 |
19 |
20 | 21 | 22 | 27 | 28 | 38 | 39 | -------------------------------------------------------------------------------- /projects/demo/src/app/pages/custom-tool/color-picker/examples/1/custom-color-picker/index.less: -------------------------------------------------------------------------------- 1 | :host { 2 | position: relative; 3 | } 4 | 5 | .t-color-save { 6 | position: sticky; 7 | left: 0; 8 | bottom: 0; 9 | inline-size: 100%; 10 | box-shadow: inset 0 0.0625rem var(--tui-background-neutral-1-hover); 11 | background: var(--tui-background-base) !important; 12 | } 13 | -------------------------------------------------------------------------------- /projects/demo/src/app/pages/custom-tool/color-picker/examples/1/index.html: -------------------------------------------------------------------------------- 1 | 6 | 7 | 11 | 12 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /projects/demo/src/app/pages/custom-tool/color-picker/index.html: -------------------------------------------------------------------------------- 1 | 5 | 11 | 12 | -------------------------------------------------------------------------------- /projects/demo/src/app/pages/custom-tool/color-picker/index.ts: -------------------------------------------------------------------------------- 1 | import {ChangeDetectionStrategy, Component} from '@angular/core'; 2 | import {RouterLink} from '@angular/router'; 3 | import {TuiAddonDoc} from '@taiga-ui/addon-doc'; 4 | import {TuiLink} from '@taiga-ui/core'; 5 | 6 | @Component({ 7 | standalone: true, 8 | imports: [RouterLink, TuiAddonDoc, TuiLink], 9 | templateUrl: './index.html', 10 | changeDetection: ChangeDetectionStrategy.OnPush, 11 | }) 12 | export default class Example { 13 | protected readonly component1 = import('./examples/1'); 14 | protected readonly example1 = { 15 | HTML: import('./examples/1/index.html?raw'), 16 | TypeScript: import('./examples/1/index.ts?raw'), 17 | './custom-color-picker/custom-color-picker.component.ts': import( 18 | './examples/1/custom-color-picker/index.ts?raw' 19 | ), 20 | './custom-color-picker/custom-color-picker.component.less': import( 21 | './examples/1/custom-color-picker/index.less?raw' 22 | ), 23 | './custom-color-picker/custom-color-picker.component.html': import( 24 | './examples/1/custom-color-picker/index.html?raw' 25 | ), 26 | }; 27 | } 28 | -------------------------------------------------------------------------------- /projects/demo/src/app/pages/custom-tool/paste-emoji/examples/1/index.html: -------------------------------------------------------------------------------- 1 | 6 | Smiles are custom tool. Try it. 7 | 8 | 9 | 10 | 11 | 12 | 13 | click it 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /projects/demo/src/app/pages/custom-tool/paste-emoji/examples/1/index.less: -------------------------------------------------------------------------------- 1 | .hint { 2 | display: flex; 3 | color: var(--tui-border-hover); 4 | block-size: 100%; 5 | align-items: center; 6 | } 7 | -------------------------------------------------------------------------------- /projects/demo/src/app/pages/custom-tool/paste-emoji/examples/1/smiles-tool/emoji.extension.ts: -------------------------------------------------------------------------------- 1 | import type {GlobalAttributes} from '@tiptap/core'; 2 | import {Extension} from '@tiptap/core'; 3 | 4 | export const EmojiExtension = Extension.create({ 5 | name: 'emoji', 6 | addGlobalAttributes(): GlobalAttributes { 7 | return [ 8 | { 9 | types: ['paragraph'], 10 | attributes: { 11 | dataType: { 12 | default: '', 13 | keepOnSplit: false, 14 | renderHTML: ({dataType}) => 15 | dataType === 'emoji' 16 | ? { 17 | style: 'display: inline', 18 | } 19 | : null, 20 | parseHTML: (element) => element.getAttribute('data-type'), 21 | }, 22 | }, 23 | }, 24 | ]; 25 | }, 26 | }); 27 | -------------------------------------------------------------------------------- /projects/demo/src/app/pages/custom-tool/paste-emoji/examples/1/smiles-tool/smiles-tool.styles.less: -------------------------------------------------------------------------------- 1 | @import '@taiga-ui/core/styles/taiga-ui-local.less'; 2 | 3 | .tool-button { 4 | .transition(background); 5 | 6 | &:hover { 7 | background: var(--tui-background-neutral-1-hover); 8 | } 9 | } 10 | 11 | .smiles { 12 | display: flex; 13 | max-inline-size: 18rem; 14 | flex-wrap: wrap; 15 | justify-content: space-around; 16 | align-items: center; 17 | } 18 | 19 | .smile { 20 | .transition(background); 21 | 22 | appearance: none; 23 | border: 0; 24 | background: none; 25 | text-decoration: none; 26 | flex: 1 0 21%; 27 | cursor: pointer; 28 | border-radius: var(--tui-radius-s); 29 | font: var(--tui-font-heading-4); 30 | padding: 1rem; 31 | 32 | &:hover { 33 | background: var(--tui-background-neutral-1-hover); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /projects/demo/src/app/pages/custom-tool/paste-emoji/examples/1/smiles-tool/smiles-tool.template.html: -------------------------------------------------------------------------------- 1 |
8 | 20 | 21 |
22 | 29 |
30 |
31 |
32 | -------------------------------------------------------------------------------- /projects/demo/src/app/pages/custom-tool/paste-image/examples/1/image-tool/image-tool.styles.less: -------------------------------------------------------------------------------- 1 | @import '@taiga-ui/core/styles/taiga-ui-local.less'; 2 | 3 | .tool-button { 4 | .transition(background); 5 | 6 | &:hover { 7 | background: var(--tui-background-neutral-1-hover); 8 | } 9 | } 10 | 11 | .youtube-tool-content { 12 | display: flex; 13 | min-block-size: 4rem; 14 | align-items: center; 15 | padding-right: 0.75rem; 16 | } 17 | 18 | .t-label { 19 | block-size: var(--tui-height-l); 20 | box-sizing: border-box; 21 | padding: 0.4375rem 1rem; 22 | min-inline-size: 12.5rem; 23 | max-inline-size: 25rem; 24 | inline-size: 100%; 25 | } 26 | 27 | .t-input { 28 | flex: 1; 29 | color: var(--tui-text-secondary); 30 | 31 | &_filled { 32 | color: var(--tui-text-primary); 33 | } 34 | } 35 | 36 | .t-url { 37 | max-inline-size: 12.5rem; 38 | min-inline-size: 12.5rem; 39 | overflow: hidden; 40 | text-overflow: ellipsis; 41 | white-space: nowrap; 42 | } 43 | -------------------------------------------------------------------------------- /projects/demo/src/app/pages/custom-tool/paste-image/examples/1/index.html: -------------------------------------------------------------------------------- 1 | 6 | 7 | 8 | 9 | 10 | 11 | click it 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /projects/demo/src/app/pages/custom-tool/paste-image/examples/1/index.less: -------------------------------------------------------------------------------- 1 | .hint { 2 | display: flex; 3 | color: var(--tui-border-hover); 4 | block-size: 100%; 5 | align-items: center; 6 | } 7 | -------------------------------------------------------------------------------- /projects/demo/src/app/pages/embed/html5/examples/1/index.html: -------------------------------------------------------------------------------- 1 | 7 | 8 |

HTML:

9 |
10 | 11 |

Text:

12 |

{{ control.value }}

13 | -------------------------------------------------------------------------------- /projects/demo/src/app/pages/embed/html5/examples/1/index.less: -------------------------------------------------------------------------------- 1 | :host { 2 | &._e2e ::ng-deep { 3 | //noinspection CssInvalidPseudoSelector 4 | video::-webkit-media-controls-current-time-display, 5 | video::-webkit-media-controls-time-remaining-display, 6 | audio::-webkit-media-controls-current-time-display, 7 | audio::-webkit-media-controls-time-remaining-display { 8 | display: none; 9 | } 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /projects/demo/src/app/pages/embed/html5/index.html: -------------------------------------------------------------------------------- 1 | 5 | 11 | 12 | -------------------------------------------------------------------------------- /projects/demo/src/app/pages/embed/html5/index.ts: -------------------------------------------------------------------------------- 1 | import {ChangeDetectionStrategy, Component} from '@angular/core'; 2 | import {TuiAddonDoc} from '@taiga-ui/addon-doc'; 3 | import {TUI_EDITOR_DEFAULT_EXTENSIONS, TUI_EDITOR_EXTENSIONS} from '@taiga-ui/editor'; 4 | 5 | @Component({ 6 | standalone: true, 7 | imports: [TuiAddonDoc], 8 | templateUrl: './index.html', 9 | changeDetection: ChangeDetectionStrategy.OnPush, 10 | providers: [ 11 | { 12 | provide: TUI_EDITOR_EXTENSIONS, 13 | useValue: TUI_EDITOR_DEFAULT_EXTENSIONS, 14 | }, 15 | ], 16 | }) 17 | export default class Example { 18 | protected readonly component1 = import('./examples/1'); 19 | protected readonly example1 = { 20 | HTML: import('./examples/1/index.html?raw'), 21 | TypeScript: import('./examples/1/index.ts?raw'), 22 | }; 23 | } 24 | -------------------------------------------------------------------------------- /projects/demo/src/app/pages/embed/iframe/examples/1/embed-tool/embed-tool.styles.less: -------------------------------------------------------------------------------- 1 | @import '@taiga-ui/core/styles/taiga-ui-local.less'; 2 | 3 | .tool-button { 4 | .transition(background); 5 | 6 | &:hover { 7 | background: var(--tui-background-neutral-1-hover); 8 | } 9 | } 10 | 11 | .embed-tool-content { 12 | display: flex; 13 | min-block-size: 4rem; 14 | align-items: center; 15 | padding-right: 0.75rem; 16 | } 17 | 18 | .t-label { 19 | block-size: var(--tui-height-l); 20 | box-sizing: border-box; 21 | padding: 0.4375rem 1rem; 22 | min-inline-size: 12.5rem; 23 | max-inline-size: 25rem; 24 | inline-size: 100%; 25 | } 26 | 27 | .t-input { 28 | flex: 1; 29 | color: var(--tui-text-secondary); 30 | 31 | &_filled { 32 | color: var(--tui-text-primary); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /projects/demo/src/app/pages/embed/iframe/examples/1/index.html: -------------------------------------------------------------------------------- 1 | 6 | 7 | 8 | 9 | 10 | 11 | click it 12 | 13 | 14 | 15 | 16 |

HTML:

17 |
18 | 19 |

Text:

20 |

{{ control.value }}

21 | -------------------------------------------------------------------------------- /projects/demo/src/app/pages/embed/iframe/examples/1/index.less: -------------------------------------------------------------------------------- 1 | .hint { 2 | display: flex; 3 | color: var(--tui-border-hover); 4 | block-size: 100%; 5 | align-items: center; 6 | } 7 | -------------------------------------------------------------------------------- /projects/demo/src/app/pages/embed/iframe/index.html: -------------------------------------------------------------------------------- 1 | 5 | 11 | 12 | -------------------------------------------------------------------------------- /projects/demo/src/app/pages/embed/pdf/examples/1/index.html: -------------------------------------------------------------------------------- 1 | 7 | 8 |

HTML:

9 |
10 | 11 |

Text:

12 |

{{ control.value }}

13 | -------------------------------------------------------------------------------- /projects/demo/src/app/pages/embed/pdf/examples/1/index.less: -------------------------------------------------------------------------------- 1 | .editor { 2 | min-block-size: 43.75rem; 3 | } 4 | 5 | ::ng-deep iframe { 6 | border: 0.0625rem solid var(--tui-background-accent-1); 7 | } 8 | -------------------------------------------------------------------------------- /projects/demo/src/app/pages/embed/pdf/index.html: -------------------------------------------------------------------------------- 1 | 5 | 11 | 12 | -------------------------------------------------------------------------------- /projects/demo/src/app/pages/embed/pdf/index.ts: -------------------------------------------------------------------------------- 1 | import {ChangeDetectionStrategy, Component} from '@angular/core'; 2 | import {TuiAddonDoc} from '@taiga-ui/addon-doc'; 3 | import {TUI_EDITOR_DEFAULT_EXTENSIONS, TUI_EDITOR_EXTENSIONS} from '@taiga-ui/editor'; 4 | 5 | @Component({ 6 | standalone: true, 7 | imports: [TuiAddonDoc], 8 | templateUrl: './index.html', 9 | changeDetection: ChangeDetectionStrategy.OnPush, 10 | providers: [ 11 | { 12 | provide: TUI_EDITOR_EXTENSIONS, 13 | useValue: TUI_EDITOR_DEFAULT_EXTENSIONS, 14 | }, 15 | ], 16 | }) 17 | export default class Example { 18 | protected readonly component1 = import('./examples/1'); 19 | protected readonly example1 = { 20 | HTML: import('./examples/1/index.html?raw'), 21 | TypeScript: import('./examples/1/index.ts?raw'), 22 | }; 23 | } 24 | -------------------------------------------------------------------------------- /projects/demo/src/app/pages/embed/youtube/examples/1/index.html: -------------------------------------------------------------------------------- 1 | 6 | 7 | 8 | 9 | 10 | 11 | click it 12 | 13 | 14 | 15 | 16 |

HTML:

17 |
18 | 19 |

Text:

20 |

{{ control.value }}

21 | -------------------------------------------------------------------------------- /projects/demo/src/app/pages/embed/youtube/examples/1/index.less: -------------------------------------------------------------------------------- 1 | .hint { 2 | display: flex; 3 | color: var(--tui-border-hover); 4 | block-size: 100%; 5 | align-items: center; 6 | } 7 | -------------------------------------------------------------------------------- /projects/demo/src/app/pages/embed/youtube/examples/1/youtube-tool/youtube-tool.styles.less: -------------------------------------------------------------------------------- 1 | @import '@taiga-ui/core/styles/taiga-ui-local.less'; 2 | 3 | .tool-button { 4 | .transition(background); 5 | 6 | &:hover { 7 | background: var(--tui-background-neutral-1-hover); 8 | } 9 | } 10 | 11 | .youtube-tool-content { 12 | display: flex; 13 | min-block-size: 4rem; 14 | align-items: center; 15 | padding-right: 0.75rem; 16 | } 17 | 18 | .t-label { 19 | block-size: var(--tui-height-l); 20 | box-sizing: border-box; 21 | padding: 0.4375rem 1rem; 22 | min-inline-size: 12.5rem; 23 | max-inline-size: 25rem; 24 | inline-size: 100%; 25 | } 26 | 27 | .t-input { 28 | flex: 1; 29 | color: var(--tui-text-secondary); 30 | 31 | &_filled { 32 | color: var(--tui-text-primary); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /projects/demo/src/app/pages/embed/youtube/index.html: -------------------------------------------------------------------------------- 1 | 5 | 11 | 12 | -------------------------------------------------------------------------------- /projects/demo/src/app/pages/focus/examples/1/index.html: -------------------------------------------------------------------------------- 1 | 6 | 7 |

HTML:

8 | 9 | 10 |

Text:

11 |

{{ control.value }}

12 | -------------------------------------------------------------------------------- /projects/demo/src/app/pages/focus/examples/1/index.less: -------------------------------------------------------------------------------- 1 | ::ng-deep { 2 | .has-focus:not(tui-image-editor), 3 | tui-image-editor.has-focus img { 4 | border-radius: 0.1875rem; 5 | box-shadow: 0 0 0 0.1875rem #68cef8; 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /projects/demo/src/app/pages/focus/index.html: -------------------------------------------------------------------------------- 1 | 5 | 12 | 13 | -------------------------------------------------------------------------------- /projects/demo/src/app/pages/focus/index.ts: -------------------------------------------------------------------------------- 1 | import {ChangeDetectionStrategy, Component} from '@angular/core'; 2 | import {TuiAddonDoc} from '@taiga-ui/addon-doc'; 3 | import {TUI_EDITOR_DEFAULT_EXTENSIONS, TUI_EDITOR_EXTENSIONS} from '@taiga-ui/editor'; 4 | 5 | @Component({ 6 | standalone: true, 7 | imports: [TuiAddonDoc], 8 | templateUrl: './index.html', 9 | changeDetection: ChangeDetectionStrategy.OnPush, 10 | providers: [ 11 | { 12 | provide: TUI_EDITOR_EXTENSIONS, 13 | useValue: TUI_EDITOR_DEFAULT_EXTENSIONS, 14 | }, 15 | ], 16 | }) 17 | export default class Example { 18 | protected readonly component1 = import('./examples/1'); 19 | protected readonly example1 = { 20 | TypeScript: import('./examples/1/index.ts?raw'), 21 | HTML: import('./examples/1/index.html?raw'), 22 | }; 23 | } 24 | -------------------------------------------------------------------------------- /projects/demo/src/app/pages/font/examples/1/index.html: -------------------------------------------------------------------------------- 1 | 6 | 7 |

HTML:

8 | 9 | 10 |

Text:

11 |

{{ control.value }}

12 | -------------------------------------------------------------------------------- /projects/demo/src/app/pages/font/examples/1/index.less: -------------------------------------------------------------------------------- 1 | :host ::ng-deep { 2 | .text-h1, 3 | .text-h2, 4 | .text-h3, 5 | .text-h4, 6 | .text-h5, 7 | .text-h6 { 8 | margin: 0.3125rem 0; 9 | } 10 | 11 | .text-h1, 12 | .text-h1[tuiOption] { 13 | color: #f00; 14 | } 15 | 16 | .text-h2, 17 | .text-h2[tuiOption] { 18 | color: #00f; 19 | } 20 | 21 | .text-h3, 22 | .text-h3[tuiOption] { 23 | color: #008000; 24 | } 25 | 26 | .text-h4, 27 | .text-h4[tuiOption] { 28 | color: #808080; 29 | } 30 | 31 | .text-h5, 32 | .text-h5[tuiOption] { 33 | color: #ffc0cb; 34 | } 35 | 36 | .text-h6, 37 | .text-h6[tuiOption] { 38 | color: #8a2be2; 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /projects/demo/src/app/pages/font/examples/2/index.html: -------------------------------------------------------------------------------- 1 | 6 | 7 |

HTML:

8 | 9 | 10 |

Text:

11 |

{{ control.value }}

12 | -------------------------------------------------------------------------------- /projects/demo/src/app/pages/font/examples/3/font-size-tool/index.less: -------------------------------------------------------------------------------- 1 | :host { 2 | display: flex; 3 | } 4 | -------------------------------------------------------------------------------- /projects/demo/src/app/pages/font/examples/3/index.html: -------------------------------------------------------------------------------- 1 | 6 | 7 | 8 | 9 | 10 | 11 |

HTML:

12 | 13 | 14 |

Text:

15 |

{{ control.value }}

16 | -------------------------------------------------------------------------------- /projects/demo/src/app/pages/font/examples/4/index.html: -------------------------------------------------------------------------------- 1 | 5 | -------------------------------------------------------------------------------- /projects/demo/src/app/pages/font/examples/4/index.ts: -------------------------------------------------------------------------------- 1 | import {ChangeDetectionStrategy, Component} from '@angular/core'; 2 | import {FormControl, ReactiveFormsModule} from '@angular/forms'; 3 | import {TUI_EDITOR_EXTENSIONS, TuiEditor, TuiEditorTool} from '@taiga-ui/editor'; 4 | 5 | @Component({ 6 | standalone: true, 7 | imports: [ReactiveFormsModule, TuiEditor], 8 | templateUrl: './index.html', 9 | changeDetection: ChangeDetectionStrategy.OnPush, 10 | providers: [ 11 | { 12 | provide: TUI_EDITOR_EXTENSIONS, 13 | useValue: [ 14 | import('@taiga-ui/editor').then(({TuiStarterKit}) => 15 | TuiStarterKit.configure({ 16 | // Configure default tiptap extensions 17 | bold: false, 18 | italic: false, 19 | strike: false, 20 | }), 21 | ), 22 | ], 23 | }, 24 | ], 25 | }) 26 | export default class Example { 27 | protected readonly builtInTools = [TuiEditorTool.Undo]; 28 | 29 | protected control = new FormControl('

Hello

'); 30 | } 31 | -------------------------------------------------------------------------------- /projects/demo/src/app/pages/groups/examples/1/index.html: -------------------------------------------------------------------------------- 1 | 6 | 7 |

HTML:

8 | 9 | 10 |

Text:

11 |

{{ control.value }}

12 | -------------------------------------------------------------------------------- /projects/demo/src/app/pages/groups/examples/2/index.html: -------------------------------------------------------------------------------- 1 | 6 | 7 |

HTML:

8 | 9 | 10 |

Text:

11 |

{{ control.value }}

12 | -------------------------------------------------------------------------------- /projects/demo/src/app/pages/groups/examples/2/index.less: -------------------------------------------------------------------------------- 1 | :host { 2 | ::ng-deep .tui-editor-socket [data-type='group'] { 3 | flex-direction: column; 4 | padding: 0.5rem; 5 | margin: 0.5rem 0; 6 | border: 0.0625rem solid var(--tui-border-normal); 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /projects/demo/src/app/pages/groups/examples/3/index.html: -------------------------------------------------------------------------------- 1 | 6 | 7 |

HTML:

8 | 9 | 10 |

Text:

11 |

{{ control.value }}

12 | -------------------------------------------------------------------------------- /projects/demo/src/app/pages/groups/examples/3/index.less: -------------------------------------------------------------------------------- 1 | .editor { 2 | min-block-size: 30rem; 3 | 4 | ::ng-deep .group { 5 | position: relative; 6 | display: flex; 7 | flex-direction: column; 8 | padding: 0.5rem; 9 | margin: 0.5rem 0; 10 | border-radius: 0.5rem; 11 | border: 0.0625rem solid var(--tui-border-normal); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /projects/demo/src/app/pages/groups/index.html: -------------------------------------------------------------------------------- 1 | 5 | 11 | 12 | 18 | 19 | 25 | 26 | -------------------------------------------------------------------------------- /projects/demo/src/app/pages/highlight/code/examples/1/example.md: -------------------------------------------------------------------------------- 1 | ```html 2 |
Hello world
3 | Tell me please, why? 4 |
class Greeter {
 5 |   greeting: string;
 6 | 
 7 |   constructor(message: string) {
 8 |     this.greeting = message;
 9 |   }
10 | 
11 |   greet() {
12 |     return "Hello, " + this.greeting;
13 |   }
14 | }
15 | 
16 | let greeter = new Greeter("world");
17 | 
18 |
https://github.com/taiga-family/taiga-ui
19 |

20 | ``` 21 | -------------------------------------------------------------------------------- /projects/demo/src/app/pages/highlight/code/examples/1/index.html: -------------------------------------------------------------------------------- 1 | 6 | 7 |

HTML:

8 | 9 | 10 |

Text:

11 |

{{ control.value }}

12 | -------------------------------------------------------------------------------- /projects/demo/src/app/pages/highlight/code/index.html: -------------------------------------------------------------------------------- 1 | 5 | 11 | 12 | -------------------------------------------------------------------------------- /projects/demo/src/app/pages/highlight/code/index.ts: -------------------------------------------------------------------------------- 1 | import {ChangeDetectionStrategy, Component} from '@angular/core'; 2 | import {TuiAddonDoc} from '@taiga-ui/addon-doc'; 3 | 4 | @Component({ 5 | standalone: true, 6 | imports: [TuiAddonDoc], 7 | templateUrl: './index.html', 8 | changeDetection: ChangeDetectionStrategy.OnPush, 9 | }) 10 | export default class Example { 11 | protected readonly component1 = import('./examples/1'); 12 | protected readonly example1 = { 13 | TypeScript: import('./examples/1/index.ts?raw'), 14 | HTML: import('./examples/1/index.html?raw'), 15 | LESS: import('./examples/1/index.less?raw'), 16 | './example.md': import('./examples/1/example.md?raw'), 17 | }; 18 | } 19 | -------------------------------------------------------------------------------- /projects/demo/src/app/pages/highlight/text/examples/1/index.html: -------------------------------------------------------------------------------- 1 | 6 | 7 | 19 | 20 | 21 | 22 |

HTML:

23 | 24 | 25 |

Text:

26 |

{{ control.value }}

27 | -------------------------------------------------------------------------------- /projects/demo/src/app/pages/highlight/text/examples/1/index.less: -------------------------------------------------------------------------------- 1 | ::ng-deep .marked { 2 | padding: 0.3125rem; 3 | border-radius: 0.3125rem; 4 | margin: 0 0.3125rem; 5 | box-shadow: 0 0 0 0.125rem rgba(0, 123, 255, 0.25); 6 | line-height: 2.6rem; 7 | } 8 | -------------------------------------------------------------------------------- /projects/demo/src/app/pages/highlight/text/index.html: -------------------------------------------------------------------------------- 1 | 5 | 12 | 13 | -------------------------------------------------------------------------------- /projects/demo/src/app/pages/highlight/text/index.ts: -------------------------------------------------------------------------------- 1 | import {ChangeDetectionStrategy, Component} from '@angular/core'; 2 | import {TuiAddonDoc} from '@taiga-ui/addon-doc'; 3 | 4 | @Component({ 5 | standalone: true, 6 | imports: [TuiAddonDoc], 7 | templateUrl: './index.html', 8 | changeDetection: ChangeDetectionStrategy.OnPush, 9 | }) 10 | export default class Example { 11 | protected readonly component1 = import('./examples/1'); 12 | protected readonly example1 = { 13 | TypeScript: import('./examples/1/index.ts?raw'), 14 | HTML: import('./examples/1/index.html?raw'), 15 | LESS: import('./examples/1/index.less?raw'), 16 | }; 17 | } 18 | -------------------------------------------------------------------------------- /projects/demo/src/app/pages/images/preview/examples/1/image-preview/image-preview.component.ts: -------------------------------------------------------------------------------- 1 | import {NgIf} from '@angular/common'; 2 | import type {TemplateRef} from '@angular/core'; 3 | import {ChangeDetectionStrategy, Component, inject, ViewChild} from '@angular/core'; 4 | import type {TuiDialogContext} from '@taiga-ui/core'; 5 | import {TuiButton} from '@taiga-ui/core'; 6 | import {TuiPreview, TuiPreviewDialogService} from '@taiga-ui/kit'; 7 | 8 | @Component({ 9 | standalone: true, 10 | selector: 'image-preview-example', 11 | imports: [NgIf, TuiButton, TuiPreview], 12 | templateUrl: './image-preview.template.html', 13 | styleUrls: ['./image-preview.style.less'], 14 | changeDetection: ChangeDetectionStrategy.OnPush, 15 | }) 16 | export class ImagePreviewExample { 17 | private readonly dialogs = inject(TuiPreviewDialogService); 18 | 19 | @ViewChild('previewImages') 20 | protected template?: TemplateRef; 21 | 22 | protected image?: HTMLImageElement; 23 | 24 | public showImage(image: HTMLImageElement): void { 25 | this.image = image; 26 | this.dialogs.open(this.template || '').subscribe(); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /projects/demo/src/app/pages/images/preview/examples/1/image-preview/image-preview.style.less: -------------------------------------------------------------------------------- 1 | .t-image-preview { 2 | inline-size: 100%; 3 | } 4 | -------------------------------------------------------------------------------- /projects/demo/src/app/pages/images/preview/examples/1/image-preview/image-preview.template.html: -------------------------------------------------------------------------------- 1 | 5 | 6 | 13 | 14 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /projects/demo/src/app/pages/images/preview/examples/1/index.html: -------------------------------------------------------------------------------- 1 | 6 | 7 |

HTML:

8 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /projects/demo/src/app/pages/images/preview/index.html: -------------------------------------------------------------------------------- 1 | 5 | 11 | 12 | -------------------------------------------------------------------------------- /projects/demo/src/app/pages/images/resizable/examples/1/index.html: -------------------------------------------------------------------------------- 1 | 6 | 7 |

HTML:

8 | 9 | 10 |

Text:

11 |

{{ control.value }}

12 | -------------------------------------------------------------------------------- /projects/demo/src/app/pages/images/resizable/index.html: -------------------------------------------------------------------------------- 1 | 5 | 11 | 12 | -------------------------------------------------------------------------------- /projects/demo/src/app/pages/images/resizable/index.ts: -------------------------------------------------------------------------------- 1 | import {ChangeDetectionStrategy, Component} from '@angular/core'; 2 | import {TuiAddonDoc} from '@taiga-ui/addon-doc'; 3 | import {TUI_EDITOR_DEFAULT_EXTENSIONS, TUI_EDITOR_EXTENSIONS} from '@taiga-ui/editor'; 4 | 5 | @Component({ 6 | standalone: true, 7 | imports: [TuiAddonDoc], 8 | templateUrl: './index.html', 9 | changeDetection: ChangeDetectionStrategy.OnPush, 10 | providers: [ 11 | { 12 | provide: TUI_EDITOR_EXTENSIONS, 13 | useValue: TUI_EDITOR_DEFAULT_EXTENSIONS, 14 | }, 15 | ], 16 | }) 17 | export default class Example { 18 | protected readonly component1 = import('./examples/1'); 19 | protected readonly example1 = { 20 | TypeScript: import('./examples/1/index.ts?raw'), 21 | HTML: import('./examples/1/index.html?raw'), 22 | }; 23 | } 24 | -------------------------------------------------------------------------------- /projects/demo/src/app/pages/images/upload/examples/1/image-loader.ts: -------------------------------------------------------------------------------- 1 | import type {Observable} from 'rxjs'; 2 | import {delay, finalize, fromEvent, map, switchMap} from 'rxjs'; 3 | 4 | import type {ImgbbService} from './imgbb.service'; 5 | 6 | export function imageLoader(service: ImgbbService): (file: File) => Observable { 7 | return (file: File) => { 8 | const fileReader = new FileReader(); 9 | 10 | service.loading$.next(true); 11 | fileReader.readAsDataURL(file); 12 | 13 | return fromEvent(fileReader, 'load').pipe( 14 | delay(2000), // for simulate throttle network 15 | map(() => String(fileReader.result)), 16 | switchMap((base64) => service.save(base64)), 17 | finalize(() => service.loading$.next(false)), 18 | ); 19 | }; 20 | } 21 | -------------------------------------------------------------------------------- /projects/demo/src/app/pages/images/upload/examples/1/index.html: -------------------------------------------------------------------------------- 1 | 5 | 11 | 12 | 13 | 17 | 18 |

HTML:

19 | 20 | 21 |
22 |
23 |
24 |
25 |
26 | 27 |

Text:

28 |

{{ control.value }}

29 | -------------------------------------------------------------------------------- /projects/demo/src/app/pages/images/upload/index.html: -------------------------------------------------------------------------------- 1 | 5 | 11 | 12 | -------------------------------------------------------------------------------- /projects/demo/src/app/pages/images/upload/index.ts: -------------------------------------------------------------------------------- 1 | import {ChangeDetectionStrategy, Component} from '@angular/core'; 2 | import {TuiAddonDoc} from '@taiga-ui/addon-doc'; 3 | import {TUI_EDITOR_DEFAULT_EXTENSIONS, TUI_EDITOR_EXTENSIONS} from '@taiga-ui/editor'; 4 | 5 | @Component({ 6 | standalone: true, 7 | imports: [TuiAddonDoc], 8 | templateUrl: './index.html', 9 | changeDetection: ChangeDetectionStrategy.OnPush, 10 | providers: [ 11 | { 12 | provide: TUI_EDITOR_EXTENSIONS, 13 | useValue: TUI_EDITOR_DEFAULT_EXTENSIONS, 14 | }, 15 | ], 16 | }) 17 | export default class Example { 18 | protected readonly component1 = import('./examples/1'); 19 | protected readonly example1 = { 20 | TypeScript: import('./examples/1/index.ts?raw'), 21 | HTML: import('./examples/1/index.html?raw'), 22 | 'image-loader.ts': import('./examples/1/image-loader.ts?raw'), 23 | 'imgbb.service.ts': import('./examples/1/imgbb.service.ts?raw'), 24 | }; 25 | } 26 | -------------------------------------------------------------------------------- /projects/demo/src/app/pages/mention/examples/1/index.html: -------------------------------------------------------------------------------- 1 | 5 | 6 | 11 | 12 | 13 | 14 |

HTML:

15 | 16 | 17 |

Text:

18 |

{{ control.value }}

19 | -------------------------------------------------------------------------------- /projects/demo/src/app/pages/mention/examples/1/index.less: -------------------------------------------------------------------------------- 1 | :host { 2 | ::ng-deep .my-mention { 3 | background: var(--tui-autofill); 4 | border: 0.0625rem solid var(--tui-background-accent-opposite-hover); 5 | border-radius: 0.4rem; 6 | padding: 0.1rem 0.3rem; 7 | cursor: default; 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /projects/demo/src/app/pages/mention/examples/1/mention/index.html: -------------------------------------------------------------------------------- 1 | 2 |
3 | 17 |
18 |
19 | -------------------------------------------------------------------------------- /projects/demo/src/app/pages/mention/index.html: -------------------------------------------------------------------------------- 1 | 5 | 11 | 12 | -------------------------------------------------------------------------------- /projects/demo/src/app/pages/mention/index.ts: -------------------------------------------------------------------------------- 1 | import {ChangeDetectionStrategy, Component} from '@angular/core'; 2 | import {TuiAddonDoc} from '@taiga-ui/addon-doc'; 3 | 4 | @Component({ 5 | standalone: true, 6 | imports: [TuiAddonDoc], 7 | templateUrl: './index.html', 8 | changeDetection: ChangeDetectionStrategy.OnPush, 9 | }) 10 | export default class Example { 11 | protected readonly component1 = import('./examples/1'); 12 | protected readonly example1 = { 13 | TypeScript: import('./examples/1/index.ts?raw'), 14 | HTML: import('./examples/1/index.html?raw'), 15 | LESS: import('./examples/1/index.less?raw'), 16 | 'mention.ts': import('./examples/1/mention/index.ts?raw'), 17 | 'mention.html': import('./examples/1/mention/index.html?raw'), 18 | }; 19 | } 20 | -------------------------------------------------------------------------------- /projects/demo/src/app/pages/options/examples/1/index.html: -------------------------------------------------------------------------------- 1 | 6 | 7 |

HTML:

8 | 9 | 10 |

Text:

11 |

{{ control.value }}

12 | -------------------------------------------------------------------------------- /projects/demo/src/app/pages/options/examples/1/index.ts: -------------------------------------------------------------------------------- 1 | import {ChangeDetectionStrategy, Component} from '@angular/core'; 2 | import {FormControl, ReactiveFormsModule} from '@angular/forms'; 3 | import { 4 | TUI_EDITOR_EXTENSIONS, 5 | TuiEditor, 6 | tuiEditorOptionsProvider, 7 | TuiEditorSocket, 8 | TuiEditorTool, 9 | } from '@taiga-ui/editor'; 10 | 11 | @Component({ 12 | standalone: true, 13 | imports: [ReactiveFormsModule, TuiEditor, TuiEditorSocket], 14 | templateUrl: './index.html', 15 | changeDetection: ChangeDetectionStrategy.OnPush, 16 | providers: [ 17 | tuiEditorOptionsProvider({ 18 | parseOptions: { 19 | preserveWhitespace: 'full', 20 | }, 21 | }), 22 | { 23 | provide: TUI_EDITOR_EXTENSIONS, 24 | useValue: [ 25 | import('@taiga-ui/editor').then(({TuiStarterKit}) => TuiStarterKit), 26 | ], 27 | }, 28 | ], 29 | }) 30 | export default class Example { 31 | protected readonly builtInTools = [TuiEditorTool.Undo]; 32 | 33 | protected control = new FormControl('test text\n\rtest text 2'); 34 | } 35 | -------------------------------------------------------------------------------- /projects/demo/src/app/pages/options/examples/2/index.html: -------------------------------------------------------------------------------- 1 | 5 | 6 |

HTML:

7 | 8 | 9 |

Text:

10 |

{{ control.value }}

11 | -------------------------------------------------------------------------------- /projects/demo/src/app/pages/options/import/tokens/options.md: -------------------------------------------------------------------------------- 1 | ```ts 2 | import {tuiEditorOptionsProvider, TuiEditorOptions} from '@taiga-ui/editor'; 3 | 4 | @Component({ 5 | standalone: true, 6 | //... 7 | providers: [ 8 | // .. 9 | tuiEditorOptionsProvider({ 10 | colors: new Map([ 11 | ['red', 'rgba(244, 87, 37, 1)'], 12 | ['blue', 'var(--tui-background-accent-1)'], 13 | ]), 14 | //... 15 | }), 16 | ], 17 | }) 18 | export class Example {} 19 | ``` 20 | -------------------------------------------------------------------------------- /projects/demo/src/app/pages/options/index.ts: -------------------------------------------------------------------------------- 1 | import {ChangeDetectionStrategy, Component} from '@angular/core'; 2 | import {TuiAddonDoc} from '@taiga-ui/addon-doc'; 3 | import {TuiLink} from '@taiga-ui/core'; 4 | 5 | @Component({ 6 | standalone: true, 7 | imports: [TuiAddonDoc, TuiLink], 8 | templateUrl: './index.html', 9 | changeDetection: ChangeDetectionStrategy.OnPush, 10 | }) 11 | export default class Example { 12 | protected readonly exampleOptions = import('./import/tokens/options.md?raw'); 13 | 14 | protected readonly component1 = import('./examples/1'); 15 | 16 | protected readonly example1 = { 17 | TypeScript: import('./examples/1/index.ts?raw'), 18 | HTML: import('./examples/1/index.html?raw'), 19 | }; 20 | 21 | protected readonly component2 = import('./examples/2'); 22 | 23 | protected readonly example2 = { 24 | TypeScript: import('./examples/2/index.ts?raw'), 25 | HTML: import('./examples/2/index.html?raw'), 26 | }; 27 | } 28 | -------------------------------------------------------------------------------- /projects/demo/src/app/pages/processing/cleanup-html/examples/1/index.html: -------------------------------------------------------------------------------- 1 | 5 | Placeholder 6 | 7 | 8 |

HTML:

9 | 13 | 14 |

Text:

15 |

{{ control.value }}

16 | -------------------------------------------------------------------------------- /projects/demo/src/app/pages/processing/cleanup-html/examples/1/index.ts: -------------------------------------------------------------------------------- 1 | import {ChangeDetectionStrategy, Component} from '@angular/core'; 2 | import {FormControl, ReactiveFormsModule, Validators} from '@angular/forms'; 3 | import { 4 | TUI_EDITOR_DEFAULT_EXTENSIONS, 5 | TUI_EDITOR_EXTENSIONS, 6 | TUI_EDITOR_VALUE_TRANSFORMER, 7 | TuiEditor, 8 | TuiEditorSocket, 9 | } from '@taiga-ui/editor'; 10 | 11 | import {ExampleEditorCleanupHtmlTransformer} from './transformer'; 12 | 13 | @Component({ 14 | standalone: true, 15 | imports: [ReactiveFormsModule, TuiEditor, TuiEditorSocket], 16 | templateUrl: './index.html', 17 | changeDetection: ChangeDetectionStrategy.OnPush, 18 | providers: [ 19 | { 20 | provide: TUI_EDITOR_EXTENSIONS, 21 | useValue: TUI_EDITOR_DEFAULT_EXTENSIONS, 22 | }, 23 | { 24 | provide: TUI_EDITOR_VALUE_TRANSFORMER, 25 | useClass: ExampleEditorCleanupHtmlTransformer, 26 | }, 27 | ], 28 | }) 29 | export default class Example { 30 | protected control = new FormControl( 31 | '

TipTap Editor

', 32 | Validators.required, 33 | ); 34 | } 35 | -------------------------------------------------------------------------------- /projects/demo/src/app/pages/processing/cleanup-html/examples/1/transformer.ts: -------------------------------------------------------------------------------- 1 | import {Injectable} from '@angular/core'; 2 | import {TuiValueTransformer} from '@taiga-ui/cdk'; 3 | 4 | @Injectable() 5 | export class ExampleEditorCleanupHtmlTransformer extends TuiValueTransformer { 6 | public fromControlValue(controlValue: string): string { 7 | return controlValue; 8 | } 9 | 10 | public toControlValue(componentValue: string): string { 11 | const tree = new DOMParser().parseFromString(componentValue, 'text/html'); 12 | 13 | tree.body.querySelectorAll('*').forEach((element) => { 14 | // now we can manipulate with any elements 15 | // and cleanup any attributes (class for example) 16 | element.removeAttribute('class'); 17 | element.removeAttribute('style'); 18 | }); 19 | 20 | return tree.body.innerHTML; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /projects/demo/src/app/pages/processing/cleanup-html/index.html: -------------------------------------------------------------------------------- 1 | 5 | 11 | 12 | -------------------------------------------------------------------------------- /projects/demo/src/app/pages/processing/cleanup-html/index.ts: -------------------------------------------------------------------------------- 1 | import {ChangeDetectionStrategy, Component} from '@angular/core'; 2 | import {TuiAddonDoc} from '@taiga-ui/addon-doc'; 3 | 4 | @Component({ 5 | standalone: true, 6 | imports: [TuiAddonDoc], 7 | templateUrl: './index.html', 8 | changeDetection: ChangeDetectionStrategy.OnPush, 9 | }) 10 | export default class Example { 11 | protected readonly component1 = import('./examples/1'); 12 | protected readonly example1 = { 13 | TypeScript: import('./examples/1/index.ts?raw'), 14 | HTML: import('./examples/1/index.html?raw'), 15 | './transformer.ts': import('./examples/1/transformer.ts?raw'), 16 | }; 17 | } 18 | -------------------------------------------------------------------------------- /projects/demo/src/app/pages/processing/legacy-html/examples/1/index.html: -------------------------------------------------------------------------------- 1 | 5 | Placeholder 6 | 7 | 8 |

HTML:

9 | 13 | 14 |

Text:

15 |

{{ control.value }}

16 | -------------------------------------------------------------------------------- /projects/demo/src/app/pages/processing/legacy-html/examples/1/transformer.ts: -------------------------------------------------------------------------------- 1 | import {Injectable} from '@angular/core'; 2 | import {TuiValueTransformer} from '@taiga-ui/cdk'; 3 | import {tuiLegacyEditorConverter} from '@taiga-ui/editor'; 4 | 5 | @Injectable() 6 | export class ExampleEditorConvertLegacyHtmlTransformer extends TuiValueTransformer { 7 | public fromControlValue(controlValue: string): string { 8 | return tuiLegacyEditorConverter(controlValue); 9 | } 10 | 11 | public toControlValue(componentValue: string): string { 12 | return componentValue; 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /projects/demo/src/app/pages/processing/legacy-html/index.html: -------------------------------------------------------------------------------- 1 | 5 | 11 | 12 | -------------------------------------------------------------------------------- /projects/demo/src/app/pages/processing/legacy-html/index.ts: -------------------------------------------------------------------------------- 1 | import {ChangeDetectionStrategy, Component} from '@angular/core'; 2 | import {TuiAddonDoc} from '@taiga-ui/addon-doc'; 3 | 4 | @Component({ 5 | standalone: true, 6 | imports: [TuiAddonDoc], 7 | templateUrl: './index.html', 8 | changeDetection: ChangeDetectionStrategy.OnPush, 9 | }) 10 | export default class Example { 11 | protected readonly component1 = import('./examples/1'); 12 | protected readonly example1 = { 13 | TypeScript: import('./examples/1/index.ts?raw'), 14 | HTML: import('./examples/1/index.html?raw'), 15 | './transformer.ts': import('./examples/1/transformer.ts?raw'), 16 | './legacy-editor.ts': import( 17 | '../../../../../../editor/utils/legacy-converter.ts?raw' 18 | ), 19 | }; 20 | } 21 | -------------------------------------------------------------------------------- /projects/demo/src/app/pages/processing/markdown-extension/examples/1/index.html: -------------------------------------------------------------------------------- 1 | 5 | Placeholder 6 | 7 | 8 | 14 | Markdown 15 | 16 | -------------------------------------------------------------------------------- /projects/demo/src/app/pages/processing/markdown-extension/index.html: -------------------------------------------------------------------------------- 1 | 5 | 11 | 12 | -------------------------------------------------------------------------------- /projects/demo/src/app/pages/processing/markdown-extension/index.ts: -------------------------------------------------------------------------------- 1 | import {ChangeDetectionStrategy, Component} from '@angular/core'; 2 | import {TuiAddonDoc} from '@taiga-ui/addon-doc'; 3 | 4 | @Component({ 5 | standalone: true, 6 | imports: [TuiAddonDoc], 7 | templateUrl: './index.html', 8 | changeDetection: ChangeDetectionStrategy.OnPush, 9 | }) 10 | export default class Example { 11 | protected component1 = import('./examples/1'); 12 | protected readonly example1 = { 13 | TypeScript: import('./examples/1/index.ts?raw'), 14 | HTML: import('./examples/1/index.html?raw'), 15 | }; 16 | } 17 | -------------------------------------------------------------------------------- /projects/demo/src/app/pages/processing/markdown/examples/1/index.html: -------------------------------------------------------------------------------- 1 | 7 | Placeholder 8 | 9 | 10 |

HTML:

11 | 15 | 16 |

Markdown:

17 |
{{ toMarkdown(control.value) }}
18 | -------------------------------------------------------------------------------- /projects/demo/src/app/pages/processing/markdown/examples/1/index.less: -------------------------------------------------------------------------------- 1 | .editor { 2 | min-block-size: 30rem; 3 | } 4 | 5 | .markdown { 6 | font-family: monospace; 7 | padding: 0.375rem 0.5rem; 8 | background: var(--tui-background-base-alt); 9 | color: var(--tui-text-secondary); 10 | box-shadow: inset 0 -0.125rem var(--tui-background-neutral-1); 11 | font-size: 0.875rem; 12 | white-space: break-spaces; 13 | } 14 | -------------------------------------------------------------------------------- /projects/demo/src/app/pages/processing/markdown/examples/1/transformer.ts: -------------------------------------------------------------------------------- 1 | import {Injectable} from '@angular/core'; 2 | import {TuiValueTransformer} from '@taiga-ui/cdk'; 3 | import MarkdownIt from 'markdown-it'; 4 | 5 | @Injectable() 6 | export class ExampleTransformer extends TuiValueTransformer { 7 | public fromControlValue(markdown: string): string { 8 | return new MarkdownIt().render(markdown); 9 | } 10 | 11 | public toControlValue(componentValue: string): string { 12 | return componentValue; 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /projects/demo/src/app/pages/processing/markdown/index.html: -------------------------------------------------------------------------------- 1 | 5 | 12 | 13 | -------------------------------------------------------------------------------- /projects/demo/src/app/pages/starter/import/angular.json.md: -------------------------------------------------------------------------------- 1 | ```json5 2 | { 3 | //... 4 | assets: [ 5 | { 6 | glob: '**/*', 7 | input: 'node_modules/@taiga-ui/icons/src', 8 | output: 'assets/taiga-ui/icons', 9 | }, 10 | ], 11 | } 12 | ``` 13 | -------------------------------------------------------------------------------- /projects/demo/src/app/pages/starter/import/component.md: -------------------------------------------------------------------------------- 1 | ```typescript 2 | import {TUI_EDITOR_DEFAULT_TOOLS} from '@taiga-ui/editor'; 3 | // ... 4 | 5 | @Component({ 6 | standalone: true, 7 | imports: [ 8 | // .. 9 | TuiEditor, 10 | ReactiveFormsModule, 11 | ], 12 | providers: [ 13 | { 14 | provide: TUI_EDITOR_EXTENSIONS, 15 | deps: [Injector], 16 | useFactory: (injector: Injector) => [ 17 | ...TUI_EDITOR_DEFAULT_EXTENSIONS, 18 | import('@taiga-ui/editor').then(({setup}) => setup({injector})), 19 | ], 20 | }, 21 | ], 22 | // ... 23 | }) 24 | export class App { 25 | readonly tools = TUI_EDITOR_DEFAULT_TOOLS; 26 | readonly control = new FormControl(); 27 | } 28 | ``` 29 | -------------------------------------------------------------------------------- /projects/demo/src/app/pages/starter/import/styles.less.md: -------------------------------------------------------------------------------- 1 | ```less 2 | @import '@taiga-ui/core/styles/taiga-ui-theme.less'; 3 | ``` 4 | -------------------------------------------------------------------------------- /projects/demo/src/app/pages/starter/import/template.md: -------------------------------------------------------------------------------- 1 | ```html 2 | 3 | 7 | Placeholder 8 | 9 | 10 | ``` 11 | -------------------------------------------------------------------------------- /projects/demo/src/app/pages/starter/index.less: -------------------------------------------------------------------------------- 1 | .container { 2 | display: flex; 3 | flex-direction: column; 4 | } 5 | -------------------------------------------------------------------------------- /projects/demo/src/app/pages/toolbar/bottom/examples/1/index.html: -------------------------------------------------------------------------------- 1 | 6 | Write message... 7 | 8 | 9 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /projects/demo/src/app/pages/toolbar/bottom/examples/1/index.less: -------------------------------------------------------------------------------- 1 | .editor { 2 | min-block-size: 12.5rem; 3 | max-block-size: 12.5rem; 4 | 5 | ::ng-deep { 6 | tui-toolbar { 7 | position: absolute; 8 | left: 0; 9 | bottom: 0; 10 | z-index: 1; 11 | inline-size: 100%; 12 | 13 | [tuiToolbar] { 14 | box-shadow: 0 -0.0625rem 0.0625rem var(--tui-background-neutral-1-hover); 15 | } 16 | } 17 | 18 | tui-scrollbar { 19 | margin-bottom: 2.875rem; 20 | padding: 0; 21 | } 22 | 23 | .t-placeholder { 24 | margin-top: 1.625rem; 25 | } 26 | 27 | [tuiToolbarBlock]:last-child { 28 | margin-left: auto; 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /projects/demo/src/app/pages/toolbar/bottom/index.html: -------------------------------------------------------------------------------- 1 | 5 | 11 | 12 | -------------------------------------------------------------------------------- /projects/demo/src/app/pages/toolbar/bottom/index.ts: -------------------------------------------------------------------------------- 1 | import {ChangeDetectionStrategy, Component} from '@angular/core'; 2 | import {TuiAddonDoc} from '@taiga-ui/addon-doc'; 3 | import {TUI_EDITOR_DEFAULT_EXTENSIONS, TUI_EDITOR_EXTENSIONS} from '@taiga-ui/editor'; 4 | 5 | @Component({ 6 | standalone: true, 7 | imports: [TuiAddonDoc], 8 | templateUrl: './index.html', 9 | changeDetection: ChangeDetectionStrategy.OnPush, 10 | providers: [ 11 | { 12 | provide: TUI_EDITOR_EXTENSIONS, 13 | useValue: TUI_EDITOR_DEFAULT_EXTENSIONS, 14 | }, 15 | ], 16 | }) 17 | export default class Example { 18 | protected readonly component1 = import('./examples/1'); 19 | protected readonly example1 = { 20 | HTML: import('./examples/1/index.html?raw'), 21 | TypeScript: import('./examples/1/index.ts?raw'), 22 | LESS: import('./examples/1/index.less?raw'), 23 | }; 24 | } 25 | -------------------------------------------------------------------------------- /projects/demo/src/app/pages/toolbar/custom/examples/1/custom-toolbar/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 |
4 | 9 | 10 | 14 | 15 | 19 |
20 |
21 | -------------------------------------------------------------------------------- /projects/demo/src/app/pages/toolbar/custom/examples/1/custom-toolbar/index.less: -------------------------------------------------------------------------------- 1 | @import (inline) '@taiga-ui/editor/styles/editor-toolbar.css'; 2 | -------------------------------------------------------------------------------- /projects/demo/src/app/pages/toolbar/custom/examples/1/index.html: -------------------------------------------------------------------------------- 1 | 5 | 6 | 10 | 14 | 15 | -------------------------------------------------------------------------------- /projects/demo/src/app/pages/toolbar/custom/index.html: -------------------------------------------------------------------------------- 1 | 5 | 12 | 13 | -------------------------------------------------------------------------------- /projects/demo/src/app/pages/toolbar/custom/index.ts: -------------------------------------------------------------------------------- 1 | import {ChangeDetectionStrategy, Component} from '@angular/core'; 2 | import {TuiAddonDoc} from '@taiga-ui/addon-doc'; 3 | import {TUI_EDITOR_DEFAULT_EXTENSIONS, TUI_EDITOR_EXTENSIONS} from '@taiga-ui/editor'; 4 | 5 | @Component({ 6 | standalone: true, 7 | imports: [TuiAddonDoc], 8 | templateUrl: './index.html', 9 | changeDetection: ChangeDetectionStrategy.OnPush, 10 | providers: [ 11 | { 12 | provide: TUI_EDITOR_EXTENSIONS, 13 | useValue: TUI_EDITOR_DEFAULT_EXTENSIONS, 14 | }, 15 | ], 16 | }) 17 | export default class Example { 18 | protected readonly component1 = import('./examples/1'); 19 | protected readonly example1 = { 20 | TypeScript: import('./examples/1/index.ts?raw'), 21 | HTML: import('./examples/1/index.html?raw'), 22 | './custom-toolbar.ts': import('./examples/1/custom-toolbar/index.ts?raw'), 23 | './custom-toolbar.html': import('./examples/1/custom-toolbar/index.html?raw'), 24 | './custom-toolbar.less': import('./examples/1/custom-toolbar/index.less?raw'), 25 | }; 26 | } 27 | -------------------------------------------------------------------------------- /projects/demo/src/app/pages/toolbar/floating/examples/1/index.less: -------------------------------------------------------------------------------- 1 | [tuiAppearance][data-appearance='no-border'] { 2 | outline: none; 3 | } 4 | -------------------------------------------------------------------------------- /projects/demo/src/app/pages/toolbar/floating/index.html: -------------------------------------------------------------------------------- 1 | 5 | 11 | 12 | -------------------------------------------------------------------------------- /projects/demo/src/app/pages/toolbar/floating/index.less: -------------------------------------------------------------------------------- 1 | [heading='Floating']::ng-deep .t-demo { 2 | padding: 0; 3 | } 4 | -------------------------------------------------------------------------------- /projects/demo/src/app/pages/toolbar/floating/index.ts: -------------------------------------------------------------------------------- 1 | import {ChangeDetectionStrategy, Component} from '@angular/core'; 2 | import {TuiAddonDoc} from '@taiga-ui/addon-doc'; 3 | import {TUI_EDITOR_DEFAULT_EXTENSIONS, TUI_EDITOR_EXTENSIONS} from '@taiga-ui/editor'; 4 | 5 | @Component({ 6 | standalone: true, 7 | imports: [TuiAddonDoc], 8 | templateUrl: './index.html', 9 | styleUrls: ['./index.less'], 10 | changeDetection: ChangeDetectionStrategy.OnPush, 11 | providers: [ 12 | { 13 | provide: TUI_EDITOR_EXTENSIONS, 14 | useValue: TUI_EDITOR_DEFAULT_EXTENSIONS, 15 | }, 16 | ], 17 | }) 18 | export default class Example { 19 | protected readonly component1 = import('./examples/1'); 20 | protected readonly example1 = { 21 | TypeScript: import('./examples/1/index.ts?raw'), 22 | HTML: import('./examples/1/index.html?raw'), 23 | }; 24 | } 25 | -------------------------------------------------------------------------------- /projects/demo/src/app/pages/toolbar/shared/examples/1/index.less: -------------------------------------------------------------------------------- 1 | :host { 2 | display: flex; 3 | flex-direction: column; 4 | gap: 0; 5 | } 6 | 7 | tui-toolbar.toolbar { 8 | position: sticky; 9 | top: 0; 10 | z-index: 1; 11 | background: var(--tui-base-01); 12 | } 13 | 14 | tui-editor.editor { 15 | min-block-size: auto; 16 | border-radius: 0; 17 | } 18 | -------------------------------------------------------------------------------- /projects/demo/src/app/pages/toolbar/shared/index.html: -------------------------------------------------------------------------------- 1 | 5 | 12 | 13 | -------------------------------------------------------------------------------- /projects/demo/src/app/pages/toolbar/shared/index.ts: -------------------------------------------------------------------------------- 1 | import {ChangeDetectionStrategy, Component} from '@angular/core'; 2 | import {TuiAddonDoc} from '@taiga-ui/addon-doc'; 3 | import {TUI_EDITOR_DEFAULT_EXTENSIONS, TUI_EDITOR_EXTENSIONS} from '@taiga-ui/editor'; 4 | 5 | @Component({ 6 | standalone: true, 7 | imports: [TuiAddonDoc], 8 | templateUrl: './index.html', 9 | changeDetection: ChangeDetectionStrategy.OnPush, 10 | providers: [ 11 | { 12 | provide: TUI_EDITOR_EXTENSIONS, 13 | useValue: TUI_EDITOR_DEFAULT_EXTENSIONS, 14 | }, 15 | ], 16 | }) 17 | export default class Example { 18 | protected readonly component1 = import('./examples/1'); 19 | protected readonly example1 = { 20 | TypeScript: import('./examples/1/index.ts?raw'), 21 | HTML: import('./examples/1/index.html?raw'), 22 | LESS: import('./examples/1/index.less?raw'), 23 | }; 24 | } 25 | -------------------------------------------------------------------------------- /projects/demo/src/app/pages/upload-files/examples/1/file-loader.ts: -------------------------------------------------------------------------------- 1 | import type {TuiEditorAttachedFile} from '@taiga-ui/editor'; 2 | import type {Observable} from 'rxjs'; 3 | import {finalize, forkJoin} from 'rxjs'; 4 | 5 | import type {FileIoService} from './filesio.service'; 6 | 7 | export function fileLoader( 8 | service: FileIoService, 9 | ): (files: File[]) => Observable { 10 | return (files: File[]) => { 11 | service.loading$.next(true); 12 | 13 | return forkJoin(files.map((file) => service.upload(file))).pipe( 14 | finalize(() => service.loading$.next(false)), 15 | ); 16 | }; 17 | } 18 | -------------------------------------------------------------------------------- /projects/demo/src/app/pages/upload-files/examples/1/index.html: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 10 |

HTML:

11 | 12 | 13 |

Text:

14 |

{{ control.value }}

15 | -------------------------------------------------------------------------------- /projects/demo/src/app/pages/upload-files/examples/1/index.less: -------------------------------------------------------------------------------- 1 | .editor { 2 | ::ng-deep .tui-editor-socket .ProseMirror { 3 | min-block-size: 10rem; 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /projects/demo/src/app/pages/upload-files/index.html: -------------------------------------------------------------------------------- 1 | 5 | 10 | 11 | You can use 12 | TUI_ATTACH_FILES_LOADER 13 | and 14 | TUI_ATTACH_FILES_OPTIONS 15 | tokens for attach any files in your editor 16 | 17 | 18 | 19 | 20 | 27 | 28 | -------------------------------------------------------------------------------- /projects/demo/src/app/pages/upload-files/index.ts: -------------------------------------------------------------------------------- 1 | import {ChangeDetectionStrategy, Component} from '@angular/core'; 2 | import {TuiAddonDoc} from '@taiga-ui/addon-doc'; 3 | import {TUI_EDITOR_DEFAULT_EXTENSIONS, TUI_EDITOR_EXTENSIONS} from '@taiga-ui/editor'; 4 | 5 | @Component({ 6 | standalone: true, 7 | imports: [TuiAddonDoc], 8 | templateUrl: './index.html', 9 | changeDetection: ChangeDetectionStrategy.OnPush, 10 | providers: [ 11 | { 12 | provide: TUI_EDITOR_EXTENSIONS, 13 | useValue: TUI_EDITOR_DEFAULT_EXTENSIONS, 14 | }, 15 | ], 16 | }) 17 | export default class Example { 18 | protected readonly exampleFileAttach = import('./file-attach.md?raw'); 19 | protected readonly component1 = import('./examples/1'); 20 | protected readonly example1 = { 21 | TypeScript: import('./examples/1/index.ts?raw'), 22 | HTML: import('./examples/1/index.html?raw'), 23 | LESS: import('./examples/1/index.less?raw'), 24 | './filesio.service.ts': import('./examples/1/filesio.service.ts?raw'), 25 | './file-loader.ts': import('./examples/1/file-loader.ts?raw'), 26 | }; 27 | } 28 | -------------------------------------------------------------------------------- /projects/demo/src/app/shared/logo/index.html: -------------------------------------------------------------------------------- 1 | 6 | 7 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /projects/demo/src/app/shared/logo/index.less: -------------------------------------------------------------------------------- 1 | :host { 2 | display: flex; 3 | align-items: center; 4 | 5 | :host-context(tui-root._mobile) { 6 | font-size: 0; 7 | } 8 | } 9 | 10 | .logo-link { 11 | display: flex; 12 | } 13 | 14 | .logo-name { 15 | display: flex; 16 | color: var(--tui-text-primary); 17 | } 18 | 19 | .logo-icon { 20 | min-inline-size: 11.25rem; 21 | min-block-size: 1.875rem; 22 | color: #eb5a3d; 23 | 24 | &:host-context([tuiTheme='dark']) & { 25 | color: #fff; 26 | } 27 | } 28 | 29 | .logo { 30 | margin-right: 0.625rem; 31 | } 32 | 33 | .by { 34 | margin-left: 0.875rem; 35 | 36 | :host-context(tui-root._mobile) & { 37 | display: none; 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /projects/demo/src/app/shared/logo/index.ts: -------------------------------------------------------------------------------- 1 | import {ChangeDetectionStrategy, Component} from '@angular/core'; 2 | import {RouterLink} from '@angular/router'; 3 | import {TuiIcon, TuiLink} from '@taiga-ui/core'; 4 | import {PolymorpheusComponent} from '@taiga-ui/polymorpheus'; 5 | 6 | @Component({ 7 | standalone: true, 8 | selector: 'logo', 9 | imports: [RouterLink, TuiIcon, TuiLink], 10 | templateUrl: './index.html', 11 | styleUrls: ['./index.less'], 12 | changeDetection: ChangeDetectionStrategy.OnPush, 13 | }) 14 | export class TuiLogo {} 15 | 16 | export const TUI_LOGO_CONTENT = new PolymorpheusComponent(TuiLogo); 17 | -------------------------------------------------------------------------------- /projects/demo/src/assets/icons/stackblitz.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /projects/demo/src/assets/images/angular.svg: -------------------------------------------------------------------------------- 1 | 2 | 6 | 10 | 14 | 15 | -------------------------------------------------------------------------------- /projects/demo/src/assets/images/big-wallpaper.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/taiga-family/editor/9a95ffc5564fd972beec4719efbddc885906d532/projects/demo/src/assets/images/big-wallpaper.jpg -------------------------------------------------------------------------------- /projects/demo/src/assets/images/lumberjack.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/taiga-family/editor/9a95ffc5564fd972beec4719efbddc885906d532/projects/demo/src/assets/images/lumberjack.png -------------------------------------------------------------------------------- /projects/demo/src/assets/images/piece-and-war.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/taiga-family/editor/9a95ffc5564fd972beec4719efbddc885906d532/projects/demo/src/assets/images/piece-and-war.jpg -------------------------------------------------------------------------------- /projects/demo/src/assets/manifest.webmanifest: -------------------------------------------------------------------------------- 1 | { 2 | "icons": [ 3 | {"src": "/favicon-192.png", "type": "image/png", "sizes": "192x192"}, 4 | {"src": "/favicon-512.png", "type": "image/png", "sizes": "512x512"} 5 | ] 6 | } 7 | -------------------------------------------------------------------------------- /projects/demo/src/environments/environment.prod.ts: -------------------------------------------------------------------------------- 1 | export const environment = { 2 | production: true, 3 | }; 4 | -------------------------------------------------------------------------------- /projects/demo/src/environments/environment.ts: -------------------------------------------------------------------------------- 1 | export const environment = { 2 | production: false, 3 | }; 4 | -------------------------------------------------------------------------------- /projects/demo/src/main.server.ts: -------------------------------------------------------------------------------- 1 | import type {ApplicationRef} from '@angular/core'; 2 | import {importProvidersFrom, mergeApplicationConfig} from '@angular/core'; 3 | import {bootstrapApplication} from '@angular/platform-browser'; 4 | import {provideServerRendering, ServerModule} from '@angular/platform-server'; 5 | import {UNIVERSAL_PROVIDERS} from '@ng-web-apis/universal'; 6 | 7 | import {AppComponent} from './app/app.component'; 8 | import {appConfig} from './app/app.config'; 9 | 10 | const serverConfig = mergeApplicationConfig(appConfig, { 11 | providers: [ 12 | importProvidersFrom(ServerModule), 13 | provideServerRendering(), 14 | UNIVERSAL_PROVIDERS, 15 | ], 16 | }); 17 | 18 | export default async (): Promise => 19 | bootstrapApplication(AppComponent, serverConfig); 20 | -------------------------------------------------------------------------------- /projects/demo/src/main.ts: -------------------------------------------------------------------------------- 1 | import {enableProdMode} from '@angular/core'; 2 | import {bootstrapApplication} from '@angular/platform-browser'; 3 | 4 | import {AppComponent} from './app/app.component'; 5 | import {appConfig} from './app/app.config'; 6 | import {environment} from './environments/environment'; 7 | 8 | if (environment.production) { 9 | enableProdMode(); 10 | } 11 | 12 | bootstrapApplication(AppComponent, appConfig).catch((err: unknown) => console.error(err)); 13 | -------------------------------------------------------------------------------- /projects/demo/src/polyfills.ts: -------------------------------------------------------------------------------- 1 | import 'zone.js'; 2 | -------------------------------------------------------------------------------- /projects/demo/src/styles.less: -------------------------------------------------------------------------------- 1 | @import '@taiga-ui/styles/taiga-ui-global.less'; 2 | @import '@taiga-ui/core/styles/taiga-ui-theme.less'; 3 | @import '~highlight.js/styles/github.css'; 4 | 5 | body { 6 | margin: 0; 7 | } 8 | 9 | html, 10 | body { 11 | block-size: 100%; 12 | } 13 | 14 | tui-doc-main [tuiDocHeader] { 15 | display: none; 16 | } 17 | 18 | tui-doc-navigation.tui-doc-navigation { 19 | top: 0 !important; 20 | 21 | tui-scrollbar { 22 | overscroll-behavior: none; 23 | } 24 | } 25 | 26 | .tui-doc-page { 27 | padding-top: 0 !important; 28 | } 29 | 30 | tui-root::before { 31 | position: absolute !important; 32 | } 33 | 34 | tui-doc-example:first-child { 35 | padding-top: 0 !important; 36 | } 37 | -------------------------------------------------------------------------------- /projects/demo/src/typings.d.ts: -------------------------------------------------------------------------------- 1 | /* Import file's content as string. 2 | To understand how it works, see `projects/demo/webpack.config.ts`. 3 | */ 4 | declare module '*?raw' { 5 | const result: string; 6 | 7 | export default result; 8 | } 9 | -------------------------------------------------------------------------------- /projects/demo/tsconfig.app.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.json", 3 | "angularCompilerOptions": { 4 | "compilationMode": "full" 5 | }, 6 | "compilerOptions": { 7 | "outDir": "../out-tsc/app" 8 | }, 9 | "files": ["./src/main.ts", "./src/polyfills.ts"], 10 | "include": ["src/**/*.d.ts"] 11 | } 12 | -------------------------------------------------------------------------------- /projects/demo/tsconfig.server.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.json", 3 | "files": ["./src/main.server.ts", "server/server.ts", "./src/polyfills.ts"], 4 | "include": ["src/**/*.d.ts"], 5 | "angularCompilerOptions": { 6 | "compilationMode": "full" 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /projects/editor/common/attached.ts: -------------------------------------------------------------------------------- 1 | import type {TuiLinkAttributes} from './tui-link-attributes'; 2 | 3 | export interface TuiEditorAttachedFile { 4 | attrs?: T; 5 | link: string; 6 | name: string; 7 | } 8 | 9 | export interface TuiEditorAttachOptions { 10 | accept: string; 11 | multiple: boolean; 12 | } 13 | -------------------------------------------------------------------------------- /projects/editor/common/default-editor-colors.ts: -------------------------------------------------------------------------------- 1 | export const TUI_EDITOR_DEFAULT_EDITOR_COLORS = new Map([ 2 | ['color-black-100', '#909090'], 3 | ['color-black-200', '#666666'], 4 | ['color-black-300', '#333333'], 5 | ['color-blue-100', '#1771e6'], 6 | ['color-blue-200', '#1464cc'], 7 | ['color-blue-300', '#0953b3'], 8 | ['color-gray-100', '#f5f5f6'], 9 | ['color-gray-200', '#e7e8ea'], 10 | ['color-gray-300', '#cbcfd3'], 11 | ['color-gray-400', '#959ba4'], 12 | ['color-gray-500', '#79818c'], 13 | ['color-gray-600', '#616871'], 14 | ['color-green-100', '#39b54a'], 15 | ['color-green-200', '#2ca53a'], 16 | ['color-green-300', '#168a21'], 17 | ['color-light-blue-100', '#ecf1f7'], 18 | ['color-light-blue-200', '#e4ebf3'], 19 | ['color-light-blue-300', '#dde4ed'], 20 | ['color-red-100', '#e01f19'], 21 | ['color-red-200', '#d3120e'], 22 | ['color-red-300', '#c40b08'], 23 | ['color-yellow-100', '#FFDD2C'], 24 | ['color-yellow-200', '#FCC521'], 25 | ['color-yellow-300', '#FAB618'], 26 | ['transparent', 'transparent'], 27 | ]); 28 | 29 | export const EDITOR_BLANK_COLOR = 'rgb(51, 51, 51)'; 30 | -------------------------------------------------------------------------------- /projects/editor/common/default-editor-tools.ts: -------------------------------------------------------------------------------- 1 | import {TuiEditorTool} from './editor-tool'; 2 | 3 | export const TUI_EDITOR_DEFAULT_TOOLS = new Set([ 4 | TuiEditorTool.Align, 5 | TuiEditorTool.Anchor, 6 | TuiEditorTool.Bold, 7 | TuiEditorTool.CellColor, 8 | TuiEditorTool.Clear, 9 | TuiEditorTool.Code, 10 | TuiEditorTool.Color, 11 | TuiEditorTool.Details, 12 | TuiEditorTool.Hilite, 13 | TuiEditorTool.HR, 14 | TuiEditorTool.Img, 15 | TuiEditorTool.Italic, 16 | TuiEditorTool.Link, 17 | TuiEditorTool.List, 18 | TuiEditorTool.Quote, 19 | TuiEditorTool.Size, 20 | TuiEditorTool.Strikethrough, 21 | TuiEditorTool.Sub, 22 | TuiEditorTool.Sup, 23 | TuiEditorTool.Table, 24 | TuiEditorTool.Underline, 25 | TuiEditorTool.Undo, 26 | ]); 27 | -------------------------------------------------------------------------------- /projects/editor/common/default-events.ts: -------------------------------------------------------------------------------- 1 | export const TUI_EDITOR_RESIZE_EVENT = 'tui_editor_resize'; 2 | -------------------------------------------------------------------------------- /projects/editor/common/default-font-options-handler.ts: -------------------------------------------------------------------------------- 1 | import type {TuiLanguageEditor} from '@taiga-ui/i18n'; 2 | 3 | import type {TuiEditorFontOption} from './editor-font-option'; 4 | 5 | export function tuiDefaultFontOptionsHandler( 6 | texts: TuiLanguageEditor['editorFontOptions'], 7 | ): ReadonlyArray> { 8 | return [ 9 | { 10 | px: 13, 11 | name: texts.small, 12 | }, 13 | { 14 | px: 15, 15 | name: texts.normal, 16 | }, 17 | { 18 | px: 17, 19 | name: texts.large, 20 | }, 21 | { 22 | px: 24, 23 | family: 'var(--tui-font-heading)', 24 | name: texts.subtitle, 25 | headingLevel: 2, 26 | weight: 'bold', 27 | }, 28 | { 29 | px: 30, 30 | family: 'var(--tui-font-heading)', 31 | name: texts.title, 32 | headingLevel: 1, 33 | weight: 'bold', 34 | }, 35 | ]; 36 | } 37 | -------------------------------------------------------------------------------- /projects/editor/common/default-html5-media-attributes.ts: -------------------------------------------------------------------------------- 1 | export const TUI_DEFAULT_HTML5_MEDIA_ATTRIBUTES = [ 2 | 'id', 3 | 'class', 4 | 'src', 5 | 'style', 6 | 'controls', 7 | 'loop', 8 | 'muted', 9 | 'preload', 10 | 'autoplay', 11 | 'width', 12 | 'height', 13 | 'controlsList', 14 | ]; 15 | -------------------------------------------------------------------------------- /projects/editor/common/default-link-options-handler.ts: -------------------------------------------------------------------------------- 1 | export const TUI_EDITOR_LINK_HASH_PREFIX = '#'; 2 | export const TUI_EDITOR_LINK_HTTP_PREFIX = 'http://'; 3 | export const TUI_EDITOR_LINK_HTTPS_PREFIX = 'https://'; 4 | export const TUI_EDITOR_LINK_SIMPLE_PROTOCOL_DIVIDER = ':'; 5 | export const TUI_EDITOR_LINK_OSI_PROTOCOL_DIVIDER = '://'; 6 | 7 | export type TuiEditorLinkProtocol = 8 | | `${string}${typeof TUI_EDITOR_LINK_OSI_PROTOCOL_DIVIDER}` 9 | | `${string}${typeof TUI_EDITOR_LINK_SIMPLE_PROTOCOL_DIVIDER}`; 10 | 11 | export type TuiEditorLinkPrefix = 12 | | TuiEditorLinkProtocol 13 | | typeof TUI_EDITOR_LINK_HASH_PREFIX; 14 | 15 | export interface TuiEditorLinkOptions { 16 | readonly protocol: TuiEditorLinkProtocol; 17 | } 18 | 19 | export const TUI_DEFAULT_LINK_OPTIONS = { 20 | protocol: TUI_EDITOR_LINK_HTTPS_PREFIX, 21 | } as const; 22 | -------------------------------------------------------------------------------- /projects/editor/common/editor-extensions.ts: -------------------------------------------------------------------------------- 1 | import {InjectionToken} from '@angular/core'; 2 | import type {Extension, Mark, Node} from '@tiptap/core'; 3 | import type {Observable} from 'rxjs'; 4 | 5 | /** 6 | * Extensions for editor 7 | */ 8 | export const TUI_EDITOR_EXTENSIONS = new InjectionToken< 9 | ReadonlyArray> 10 | >('[TUI_EDITOR_EXTENSIONS]'); 11 | 12 | /** 13 | * lazy extensions 14 | */ 15 | export const LAZY_EDITOR_EXTENSIONS = new InjectionToken< 16 | Observable> 17 | >('[LAZY_EDITOR_EXTENSIONS]'); 18 | -------------------------------------------------------------------------------- /projects/editor/common/editor-font-option.ts: -------------------------------------------------------------------------------- 1 | // @bad TODO: Make customizable 2 | export interface TuiEditorFontOption { 3 | family?: string; 4 | headingLevel?: 1 | 2 | 3 | 4 | 5 | 6; 5 | name: string; 6 | ngClass?: Record | Set | string[] | string; 7 | ngStyle?: Record; 8 | px?: number; 9 | weight?: string; 10 | } 11 | -------------------------------------------------------------------------------- /projects/editor/common/editor-sanitizer.ts: -------------------------------------------------------------------------------- 1 | import type {Sanitizer} from '@angular/core'; 2 | import {tuiCreateTokenFromFactory} from '@taiga-ui/cdk'; 3 | 4 | export const TUI_EDITOR_SANITIZER = tuiCreateTokenFromFactory( 5 | () => null, 6 | ); 7 | -------------------------------------------------------------------------------- /projects/editor/common/editor-tool.ts: -------------------------------------------------------------------------------- 1 | export const TuiEditorTool = { 2 | Align: 'justify', 3 | Anchor: 'anchor', 4 | Attach: 'attach', 5 | Bold: 'bold', 6 | Clear: 'clear', 7 | Code: 'code', 8 | Color: 'foreColor', 9 | Details: 'details', 10 | Group: 'group', 11 | HR: 'insertHorizontalRule', 12 | Hilite: 'hiliteColor', 13 | Img: 'image', 14 | Italic: 'italic', 15 | Link: 'link', 16 | List: 'list', 17 | MergeCells: 'mergeCells', 18 | Quote: 'quote', 19 | RowsColumnsManaging: 'rowsColumnsManaging', 20 | Size: 'fontSize', 21 | SplitCells: 'splitCells', 22 | Strikethrough: 'strikeThrough', 23 | Sub: 'subscript', 24 | Sup: 'superscript', 25 | Table: 'insertTable', 26 | CellColor: 'cellColor', 27 | Tex: 'tex', 28 | Underline: 'underline', 29 | Undo: 'undo', 30 | } as const; 31 | 32 | export type TuiEditorToolType = (typeof TuiEditorTool)[keyof typeof TuiEditorTool]; 33 | -------------------------------------------------------------------------------- /projects/editor/common/editor-value-transformer.ts: -------------------------------------------------------------------------------- 1 | import {InjectionToken} from '@angular/core'; 2 | import type {TuiValueTransformer} from '@taiga-ui/cdk'; 3 | 4 | export const TUI_EDITOR_VALUE_TRANSFORMER = new InjectionToken< 5 | TuiValueTransformer 6 | >('[TUI_EDITOR_VALUE_TRANSFORMER]'); 7 | -------------------------------------------------------------------------------- /projects/editor/common/files-loader.ts: -------------------------------------------------------------------------------- 1 | import {InjectionToken} from '@angular/core'; 2 | import type {TuiHandler} from '@taiga-ui/cdk'; 3 | import type {Observable} from 'rxjs'; 4 | 5 | import type {TuiEditorAttachedFile, TuiEditorAttachOptions} from './attached'; 6 | 7 | /** 8 | * files loader options 9 | */ 10 | export const TUI_ATTACH_FILES_OPTIONS: InjectionToken = 11 | new InjectionToken('[TUI_ATTACH_FILES_OPTIONS]', { 12 | factory: () => ({accept: '*/*', multiple: true}), 13 | }); 14 | 15 | /** 16 | * files loader handler 17 | */ 18 | export const TUI_ATTACH_FILES_LOADER: InjectionToken< 19 | TuiHandler> 20 | > = new InjectionToken>>( 21 | '[TUI_ATTACH_FILES_LOADER]', 22 | ); 23 | -------------------------------------------------------------------------------- /projects/editor/common/gradient-direction.ts: -------------------------------------------------------------------------------- 1 | export type TuiGradientDirection = 2 | | 'to bottom left' 3 | | 'to bottom right' 4 | | 'to bottom' 5 | | 'to left' 6 | | 'to right' 7 | | 'to top left' 8 | | 'to top right' 9 | | 'to top'; 10 | -------------------------------------------------------------------------------- /projects/editor/common/hack.ts: -------------------------------------------------------------------------------- 1 | export const TUI_TIPTAP_WHITESPACE_HACK = ' '; // require: `@tiptap/extension-text-style` 2 | -------------------------------------------------------------------------------- /projects/editor/common/iframe.ts: -------------------------------------------------------------------------------- 1 | export interface TuiEditableIframeOptions { 2 | maxHeight: number; 3 | maxWidth: number; 4 | minHeight: number; 5 | minWidth: number; 6 | } 7 | 8 | export interface TuiEditableIframe { 9 | allowfullscreen?: boolean | null; 10 | frameborder?: number | null; 11 | height?: number | string | null; 12 | src: string | null; 13 | width?: number | string | null; 14 | } 15 | -------------------------------------------------------------------------------- /projects/editor/common/image-loader.ts: -------------------------------------------------------------------------------- 1 | import {InjectionToken} from '@angular/core'; 2 | import type {TuiHandler} from '@taiga-ui/cdk'; 3 | import {tuiTypedFromEvent} from '@taiga-ui/cdk'; 4 | import type {Observable} from 'rxjs'; 5 | import {map} from 'rxjs'; 6 | 7 | /** 8 | * Image loader handler 9 | */ 10 | export const TUI_IMAGE_LOADER: InjectionToken< 11 | TuiHandler> 12 | > = new InjectionToken>>( 13 | '[TUI_IMAGE_LOADER]', 14 | { 15 | factory: () => (file) => { 16 | const fileReader = new FileReader(); 17 | 18 | fileReader.readAsDataURL(file); 19 | 20 | return tuiTypedFromEvent(fileReader, 'load').pipe( 21 | map(() => String(fileReader.result)), 22 | ); 23 | }, 24 | }, 25 | ); 26 | -------------------------------------------------------------------------------- /projects/editor/common/image.ts: -------------------------------------------------------------------------------- 1 | export interface TuiImageEditorOptions { 2 | maxWidth: number | null; 3 | minWidth: number | null; 4 | } 5 | 6 | export interface TuiEditableImage { 7 | alt?: string; 8 | draggable?: '' | null; 9 | src: string; 10 | title?: string; 11 | width?: number | string | null; 12 | style?: string | null; 13 | } 14 | -------------------------------------------------------------------------------- /projects/editor/common/index.ts: -------------------------------------------------------------------------------- 1 | export type * from './attached'; 2 | export * from './default-editor-colors'; 3 | export * from './default-editor-tools'; 4 | export * from './default-events'; 5 | export * from './default-font-options-handler'; 6 | export * from './default-html5-media-attributes'; 7 | export * from './default-link-options-handler'; 8 | export * from './editor-adapter'; 9 | export * from './editor-extensions'; 10 | export type * from './editor-font-option'; 11 | export * from './editor-options'; 12 | export * from './editor-sanitizer'; 13 | export * from './editor-tool'; 14 | export * from './editor-value-transformer'; 15 | export * from './files-loader'; 16 | export type * from './gradient-direction'; 17 | export * from './hack'; 18 | export * from './i18n'; 19 | export type * from './iframe'; 20 | export type * from './image'; 21 | export * from './image-loader'; 22 | export type * from './parsed-gradient'; 23 | export * from './tiptap-editor'; 24 | export type * from './tui-link-attributes'; 25 | export type * from './youtube'; 26 | -------------------------------------------------------------------------------- /projects/editor/common/ng-package.json: -------------------------------------------------------------------------------- 1 | { 2 | "lib": { 3 | "entryFile": "index.ts" 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /projects/editor/common/parsed-gradient.ts: -------------------------------------------------------------------------------- 1 | import type {TuiGradientDirection} from './gradient-direction'; 2 | 3 | export interface TuiParsedGradient { 4 | readonly side: TuiGradientDirection; 5 | readonly stops: ReadonlyArray<{ 6 | readonly color: string; 7 | readonly position: string; 8 | }>; 9 | } 10 | -------------------------------------------------------------------------------- /projects/editor/common/tiptap-editor.ts: -------------------------------------------------------------------------------- 1 | import {InjectionToken} from '@angular/core'; 2 | import type {Editor} from '@tiptap/core'; 3 | import type {Observable} from 'rxjs'; 4 | import {ReplaySubject} from 'rxjs'; 5 | 6 | /** 7 | * Token for Tiptap Editor 8 | */ 9 | export const TIPTAP_EDITOR = new InjectionToken>('[TIPTAP_EDITOR]'); 10 | 11 | /** 12 | * Lazy loaded Editor 13 | */ 14 | export const LAZY_TIPTAP_EDITOR = new InjectionToken('[LAZY_TIPTAP_EDITOR]', { 15 | factory: () => { 16 | const editor$ = new ReplaySubject(1); 17 | 18 | import('@tiptap/core') 19 | .then(({Editor}) => editor$.next(Editor)) 20 | .catch(() => editor$.complete()); 21 | 22 | return editor$; 23 | }, 24 | }); 25 | 26 | /** 27 | * The container in which the tip-tap editor is initialized 28 | */ 29 | export const INITIALIZATION_TIPTAP_CONTAINER = new InjectionToken( 30 | '[INITIALIZATION_TIPTAP_CONTAINER]', 31 | ); 32 | -------------------------------------------------------------------------------- /projects/editor/common/tui-link-attributes.ts: -------------------------------------------------------------------------------- 1 | export interface TuiLinkAttributes { 2 | target?: string | null; 3 | rel?: string | null; 4 | class?: string | null; 5 | } 6 | -------------------------------------------------------------------------------- /projects/editor/common/types/ng-package.json: -------------------------------------------------------------------------------- 1 | { 2 | "lib": { 3 | "entryFile": "index.ts" 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /projects/editor/common/youtube.ts: -------------------------------------------------------------------------------- 1 | export interface TuiYoutubeOptions { 2 | height?: number | string; 3 | src: string; 4 | start?: number; 5 | width?: number | string; 6 | } 7 | -------------------------------------------------------------------------------- /projects/editor/components/edit-link/index.ts: -------------------------------------------------------------------------------- 1 | export * from './edit-link.component'; 2 | export * from './pipes/filter-anchors.pipe'; 3 | export * from './pipes/short-url.pipe'; 4 | export * from './utils/edit-link-parse-url'; 5 | -------------------------------------------------------------------------------- /projects/editor/components/edit-link/ng-package.json: -------------------------------------------------------------------------------- 1 | { 2 | "lib": { 3 | "entryFile": "index.ts" 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /projects/editor/components/edit-link/pipes/filter-anchors.pipe.ts: -------------------------------------------------------------------------------- 1 | import type {PipeTransform} from '@angular/core'; 2 | import {Pipe} from '@angular/core'; 3 | 4 | @Pipe({ 5 | standalone: true, 6 | name: 'tuiFilterAnchors', 7 | }) 8 | export class TuiFilterAnchorsPipe implements PipeTransform { 9 | public transform(anchors: string[], prefix: string, currentUrl: string): string[] { 10 | return prefix === '#' 11 | ? anchors.filter((anchor) => anchor !== currentUrl) 12 | : anchors; 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /projects/editor/components/edit-link/pipes/short-url.pipe.ts: -------------------------------------------------------------------------------- 1 | import type {PipeTransform} from '@angular/core'; 2 | import {Pipe} from '@angular/core'; 3 | 4 | const MAX_LENGTH = 60; 5 | const START = MAX_LENGTH - 20; 6 | const END = MAX_LENGTH - START - 10; 7 | 8 | @Pipe({ 9 | standalone: true, 10 | name: 'tuiShortUrl', 11 | }) 12 | export class TuiShortUrlPipe implements PipeTransform { 13 | public transform(url: string): string { 14 | return url.length < MAX_LENGTH 15 | ? url 16 | : `${url.slice(0, Math.max(0, START))}...${url.slice(url.length - END)}`; 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /projects/editor/components/editor-resizable/editor-resizable.component.html: -------------------------------------------------------------------------------- 1 |
7 | 8 | 9 |
14 | 15 |
21 | 22 |
27 |
28 | -------------------------------------------------------------------------------- /projects/editor/components/editor-resizable/index.ts: -------------------------------------------------------------------------------- 1 | export * from './editor-resizable.abstract'; 2 | export * from './editor-resizable.component'; 3 | -------------------------------------------------------------------------------- /projects/editor/components/editor-resizable/ng-package.json: -------------------------------------------------------------------------------- 1 | { 2 | "lib": { 3 | "entryFile": "index.ts" 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /projects/editor/components/editor-socket/index.ts: -------------------------------------------------------------------------------- 1 | export * from './editor-socket.component'; 2 | -------------------------------------------------------------------------------- /projects/editor/components/editor-socket/ng-package.json: -------------------------------------------------------------------------------- 1 | { 2 | "lib": { 3 | "entryFile": "index.ts" 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /projects/editor/components/editor-socket/styles/code-blocks.less: -------------------------------------------------------------------------------- 1 | @import '@taiga-ui/core/styles/taiga-ui-local.less'; 2 | 3 | pre { 4 | white-space: pre-wrap; 5 | word-break: break-word; 6 | border-radius: 0.25rem; 7 | margin: 1rem 0; 8 | padding: 0.75rem 1rem; 9 | font-family: Courier, monospace; 10 | color: var(--tui-text-secondary); 11 | background: var(--tui-background-base-alt); 12 | } 13 | -------------------------------------------------------------------------------- /projects/editor/components/editor-socket/styles/link.less: -------------------------------------------------------------------------------- 1 | @import '@taiga-ui/core/styles/taiga-ui-local.less'; 2 | 3 | a:not([data-type='jump-anchor']) { 4 | color: var(--tui-text-action); 5 | text-decoration: none; 6 | outline: none; 7 | 8 | &:hover { 9 | color: var(--tui-text-action-hover); 10 | text-decoration: underline; 11 | } 12 | 13 | &:active { 14 | color: var(--tui-background-accent-1-pressed); 15 | } 16 | } 17 | 18 | .ProseMirror { 19 | a[data-type='jump-anchor'] { 20 | text-decoration: underline; 21 | text-decoration-color: var(--tui-text-action); 22 | 23 | &::before { 24 | content: '#'; 25 | } 26 | 27 | &:hover { 28 | color: var(--tui-text-action); 29 | } 30 | } 31 | 32 | a:hover img[contenteditable='false'] { 33 | cursor: pointer; 34 | outline: 0.25rem solid var(--tui-text-action); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /projects/editor/components/editor-socket/styles/media.less: -------------------------------------------------------------------------------- 1 | @import '@taiga-ui/core/styles/taiga-ui-local.less'; 2 | 3 | &._preview-image img { 4 | cursor: pointer; 5 | } 6 | 7 | .ProseMirror { 8 | video, 9 | audio { 10 | pointer-events: none; 11 | } 12 | } 13 | 14 | img.ProseMirror-selectednode { 15 | outline: 0.25rem solid var(--tui-background-accent-1-hover); 16 | } 17 | 18 | &[tuiTiptapEditor] tui-image-editor:hover { 19 | outline: 0.0625rem dashed var(--tui-background-accent-1-hover); 20 | } 21 | -------------------------------------------------------------------------------- /projects/editor/components/editor-socket/styles/placeholder.less: -------------------------------------------------------------------------------- 1 | @import '@taiga-ui/core/styles/taiga-ui-local.less'; 2 | 3 | .t-editor-placeholder:not(tr):not(th):not(td):not(ul):not(ol):not(li):first-child::before { 4 | content: attr(data-placeholder); 5 | position: absolute; 6 | float: inline-start; 7 | color: var(--tui-border-hover); 8 | pointer-events: none; 9 | } 10 | -------------------------------------------------------------------------------- /projects/editor/components/editor/editor.component.less: -------------------------------------------------------------------------------- 1 | @import (inline) '@taiga-ui/editor/styles/editor.css'; 2 | 3 | tui-editor .t-inner-socket { 4 | display: none; 5 | } 6 | -------------------------------------------------------------------------------- /projects/editor/components/editor/index.ts: -------------------------------------------------------------------------------- 1 | export * from './dropdown/dropdown-toolbar.directive'; 2 | export * from './editor.component'; 3 | export * from './editor.providers'; 4 | export * from './portal/editor-portal.directive'; 5 | export * from './portal/editor-portal.service'; 6 | export * from './portal/editor-portal-host.component'; 7 | -------------------------------------------------------------------------------- /projects/editor/components/editor/ng-package.json: -------------------------------------------------------------------------------- 1 | { 2 | "lib": { 3 | "entryFile": "index.ts" 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /projects/editor/components/editor/portal/editor-portal-host.component.ts: -------------------------------------------------------------------------------- 1 | import {ChangeDetectionStrategy, Component} from '@angular/core'; 2 | import {TuiPortals, TuiPortalService} from '@taiga-ui/cdk'; 3 | import {TuiDropdownService} from '@taiga-ui/core'; 4 | 5 | @Component({ 6 | standalone: true, 7 | selector: 'tui-editor-portal-host', 8 | template: ` 9 | 10 | `, 11 | styleUrls: ['./editor-portal-host.style.less'], 12 | changeDetection: ChangeDetectionStrategy.OnPush, 13 | providers: [ 14 | {provide: TuiPortalService, useExisting: TuiDropdownService}, 15 | { 16 | provide: TuiPortals, 17 | useExisting: TuiEditorPortalHost, 18 | }, 19 | ], 20 | }) 21 | export class TuiEditorPortalHost extends TuiPortals {} 22 | -------------------------------------------------------------------------------- /projects/editor/components/editor/portal/editor-portal-host.style.less: -------------------------------------------------------------------------------- 1 | :host { 2 | position: absolute; 3 | top: 0; 4 | left: 0; 5 | display: block; 6 | block-size: 100%; 7 | inline-size: 100%; 8 | pointer-events: none; 9 | } 10 | -------------------------------------------------------------------------------- /projects/editor/components/editor/portal/editor-portal.directive.ts: -------------------------------------------------------------------------------- 1 | import {Directive, ElementRef, inject} from '@angular/core'; 2 | import {tuiAsViewport, TuiDropdownService, TuiRectAccessor} from '@taiga-ui/core'; 3 | 4 | import {TuiEditorPortalService} from './editor-portal.service'; 5 | 6 | @Directive({ 7 | standalone: true, 8 | selector: '[tuiEditorPortal]', 9 | providers: [ 10 | {provide: TuiDropdownService, useExisting: TuiEditorPortalService}, 11 | tuiAsViewport(TuiEditorPortal), 12 | ], 13 | }) 14 | export class TuiEditorPortal extends TuiRectAccessor { 15 | private readonly el: HTMLElement = inject(ElementRef).nativeElement; 16 | 17 | public readonly type = 'viewport'; 18 | 19 | public getClientRect(): ClientRect { 20 | return this.el.getBoundingClientRect(); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /projects/editor/components/editor/portal/editor-portal.service.ts: -------------------------------------------------------------------------------- 1 | import {Injectable} from '@angular/core'; 2 | import {TuiPortalService} from '@taiga-ui/cdk'; 3 | 4 | @Injectable() 5 | export class TuiEditorPortalService extends TuiPortalService {} 6 | -------------------------------------------------------------------------------- /projects/editor/components/index.ts: -------------------------------------------------------------------------------- 1 | export * from '@taiga-ui/editor/components/edit-link'; 2 | export * from '@taiga-ui/editor/components/editor'; 3 | export * from '@taiga-ui/editor/components/editor-resizable'; 4 | export * from '@taiga-ui/editor/components/editor-socket'; 5 | export * from '@taiga-ui/editor/components/toolbar'; 6 | export * from '@taiga-ui/editor/components/toolbar-host'; 7 | export * from '@taiga-ui/editor/components/toolbar-tools'; 8 | -------------------------------------------------------------------------------- /projects/editor/components/ng-package.json: -------------------------------------------------------------------------------- 1 | { 2 | "lib": { 3 | "entryFile": "index.ts" 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /projects/editor/components/toolbar-host/index.ts: -------------------------------------------------------------------------------- 1 | export * from './toolbar-host.component'; 2 | export * from './toolbar-navigation-manager.directive'; 3 | -------------------------------------------------------------------------------- /projects/editor/components/toolbar-host/ng-package.json: -------------------------------------------------------------------------------- 1 | { 2 | "lib": { 3 | "entryFile": "index.ts" 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /projects/editor/components/toolbar-host/toolbar-host.component.html: -------------------------------------------------------------------------------- 1 | 2 | 8 |
12 |
16 |
17 | 18 |
22 | 23 |
24 | -------------------------------------------------------------------------------- /projects/editor/components/toolbar-host/toolbar-host.style.less: -------------------------------------------------------------------------------- 1 | [tuiToolbarHost] { 2 | position: relative; 3 | display: block; 4 | isolation: isolate; 5 | } 6 | 7 | [tuiToolbarHost]._disabled { 8 | pointer-events: none; 9 | } 10 | 11 | [tuiToolbarHost] button[disabled] { 12 | pointer-events: none; 13 | } 14 | 15 | [tuiToolbarNavigationManager] { 16 | padding: 0; 17 | margin: 0; 18 | border: 0; 19 | } 20 | -------------------------------------------------------------------------------- /projects/editor/components/toolbar-tools/align-content/ng-package.json: -------------------------------------------------------------------------------- 1 | { 2 | "lib": { 3 | "entryFile": "index.ts" 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /projects/editor/components/toolbar-tools/anchor/index.html: -------------------------------------------------------------------------------- 1 | 2 | 17 | -------------------------------------------------------------------------------- /projects/editor/components/toolbar-tools/anchor/ng-package.json: -------------------------------------------------------------------------------- 1 | { 2 | "lib": { 3 | "entryFile": "index.ts" 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /projects/editor/components/toolbar-tools/attach/index.html: -------------------------------------------------------------------------------- 1 | 2 | 10 | 24 | -------------------------------------------------------------------------------- /projects/editor/components/toolbar-tools/attach/ng-package.json: -------------------------------------------------------------------------------- 1 | { 2 | "lib": { 3 | "entryFile": "index.ts" 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /projects/editor/components/toolbar-tools/clear/index.html: -------------------------------------------------------------------------------- 1 | 2 | 16 | -------------------------------------------------------------------------------- /projects/editor/components/toolbar-tools/clear/index.ts: -------------------------------------------------------------------------------- 1 | import {AsyncPipe, NgIf} from '@angular/common'; 2 | import {ChangeDetectionStrategy, Component, inject, Input} from '@angular/core'; 3 | import {TuiItem} from '@taiga-ui/cdk'; 4 | import {TuiButton, TuiHint} from '@taiga-ui/core'; 5 | import type {AbstractTuiEditor} from '@taiga-ui/editor/common'; 6 | import {TUI_EDITOR_OPTIONS, TUI_EDITOR_TOOLBAR_TEXTS} from '@taiga-ui/editor/common'; 7 | import {TuiTiptapEditorService} from '@taiga-ui/editor/directives/tiptap-editor'; 8 | 9 | @Component({ 10 | standalone: true, 11 | selector: 'tui-clear-tool', 12 | imports: [AsyncPipe, NgIf, TuiButton, TuiHint, TuiItem], 13 | templateUrl: './index.html', 14 | changeDetection: ChangeDetectionStrategy.OnPush, 15 | }) 16 | export class TuiClearTool { 17 | protected readonly texts$ = inject(TUI_EDITOR_TOOLBAR_TEXTS); 18 | protected readonly options = inject(TUI_EDITOR_OPTIONS); 19 | 20 | @Input() 21 | public editor: AbstractTuiEditor | null = inject(TuiTiptapEditorService, { 22 | optional: true, 23 | }); 24 | } 25 | -------------------------------------------------------------------------------- /projects/editor/components/toolbar-tools/clear/ng-package.json: -------------------------------------------------------------------------------- 1 | { 2 | "lib": { 3 | "entryFile": "index.ts" 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /projects/editor/components/toolbar-tools/code/index.html: -------------------------------------------------------------------------------- 1 |
6 | 20 | 21 | 22 | 30 | 31 | 32 |
33 | -------------------------------------------------------------------------------- /projects/editor/components/toolbar-tools/code/ng-package.json: -------------------------------------------------------------------------------- 1 | { 2 | "lib": { 3 | "entryFile": "index.ts" 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /projects/editor/components/toolbar-tools/details-remove/index.html: -------------------------------------------------------------------------------- 1 | 17 | -------------------------------------------------------------------------------- /projects/editor/components/toolbar-tools/details-remove/ng-package.json: -------------------------------------------------------------------------------- 1 | { 2 | "lib": { 3 | "entryFile": "index.ts" 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /projects/editor/components/toolbar-tools/details/index.html: -------------------------------------------------------------------------------- 1 | 16 | -------------------------------------------------------------------------------- /projects/editor/components/toolbar-tools/details/ng-package.json: -------------------------------------------------------------------------------- 1 | { 2 | "lib": { 3 | "entryFile": "index.ts" 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /projects/editor/components/toolbar-tools/font-size/ng-package.json: -------------------------------------------------------------------------------- 1 | { 2 | "lib": { 3 | "entryFile": "index.ts" 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /projects/editor/components/toolbar-tools/font-style/ng-package.json: -------------------------------------------------------------------------------- 1 | { 2 | "lib": { 3 | "entryFile": "index.ts" 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /projects/editor/components/toolbar-tools/group/index.html: -------------------------------------------------------------------------------- 1 | 17 | 18 | 35 | -------------------------------------------------------------------------------- /projects/editor/components/toolbar-tools/group/ng-package.json: -------------------------------------------------------------------------------- 1 | { 2 | "lib": { 3 | "entryFile": "index.ts" 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /projects/editor/components/toolbar-tools/highlight-color/index.html: -------------------------------------------------------------------------------- 1 |
5 | 20 |
25 | 26 | 31 | 32 |
33 | -------------------------------------------------------------------------------- /projects/editor/components/toolbar-tools/highlight-color/ng-package.json: -------------------------------------------------------------------------------- 1 | { 2 | "lib": { 3 | "entryFile": "index.ts" 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /projects/editor/components/toolbar-tools/hr/index.html: -------------------------------------------------------------------------------- 1 | 2 | 16 | -------------------------------------------------------------------------------- /projects/editor/components/toolbar-tools/hr/index.ts: -------------------------------------------------------------------------------- 1 | import {AsyncPipe, NgIf} from '@angular/common'; 2 | import {ChangeDetectionStrategy, Component, inject, Input} from '@angular/core'; 3 | import {TuiItem} from '@taiga-ui/cdk'; 4 | import {TuiButton, TuiHint} from '@taiga-ui/core'; 5 | import type {AbstractTuiEditor} from '@taiga-ui/editor/common'; 6 | import {TUI_EDITOR_OPTIONS, TUI_EDITOR_TOOLBAR_TEXTS} from '@taiga-ui/editor/common'; 7 | import {TuiTiptapEditorService} from '@taiga-ui/editor/directives/tiptap-editor'; 8 | 9 | @Component({ 10 | standalone: true, 11 | selector: 'tui-hr-tool', 12 | imports: [AsyncPipe, NgIf, TuiButton, TuiHint, TuiItem], 13 | templateUrl: './index.html', 14 | changeDetection: ChangeDetectionStrategy.OnPush, 15 | }) 16 | export class TuiHrTool { 17 | protected readonly texts$ = inject(TUI_EDITOR_TOOLBAR_TEXTS); 18 | protected readonly options = inject(TUI_EDITOR_OPTIONS); 19 | 20 | @Input() 21 | public editor: AbstractTuiEditor | null = inject(TuiTiptapEditorService, { 22 | optional: true, 23 | }); 24 | } 25 | -------------------------------------------------------------------------------- /projects/editor/components/toolbar-tools/hr/ng-package.json: -------------------------------------------------------------------------------- 1 | { 2 | "lib": { 3 | "entryFile": "index.ts" 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /projects/editor/components/toolbar-tools/image/index.html: -------------------------------------------------------------------------------- 1 | 2 | 8 | 22 | -------------------------------------------------------------------------------- /projects/editor/components/toolbar-tools/image/ng-package.json: -------------------------------------------------------------------------------- 1 | { 2 | "lib": { 3 | "entryFile": "index.ts" 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /projects/editor/components/toolbar-tools/link/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 19 | -------------------------------------------------------------------------------- /projects/editor/components/toolbar-tools/link/ng-package.json: -------------------------------------------------------------------------------- 1 | { 2 | "lib": { 3 | "entryFile": "index.ts" 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /projects/editor/components/toolbar-tools/list-configs/ng-package.json: -------------------------------------------------------------------------------- 1 | { 2 | "lib": { 3 | "entryFile": "index.ts" 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /projects/editor/components/toolbar-tools/ng-package.json: -------------------------------------------------------------------------------- 1 | { 2 | "lib": { 3 | "entryFile": "index.ts" 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /projects/editor/components/toolbar-tools/quote/index.html: -------------------------------------------------------------------------------- 1 | 2 | 18 | -------------------------------------------------------------------------------- /projects/editor/components/toolbar-tools/quote/ng-package.json: -------------------------------------------------------------------------------- 1 | { 2 | "lib": { 3 | "entryFile": "index.ts" 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /projects/editor/components/toolbar-tools/redo/index.html: -------------------------------------------------------------------------------- 1 | 2 | 18 | -------------------------------------------------------------------------------- /projects/editor/components/toolbar-tools/redo/index.ts: -------------------------------------------------------------------------------- 1 | import {AsyncPipe, NgIf} from '@angular/common'; 2 | import {ChangeDetectionStrategy, Component, inject, Input} from '@angular/core'; 3 | import {TuiItem} from '@taiga-ui/cdk'; 4 | import {TuiButton, TuiHint} from '@taiga-ui/core'; 5 | import type {AbstractTuiEditor} from '@taiga-ui/editor/common'; 6 | import {TUI_EDITOR_OPTIONS, TUI_EDITOR_TOOLBAR_TEXTS} from '@taiga-ui/editor/common'; 7 | import {TuiTiptapEditorService} from '@taiga-ui/editor/directives/tiptap-editor'; 8 | 9 | @Component({ 10 | standalone: true, 11 | selector: 'tui-redo-tool', 12 | imports: [AsyncPipe, NgIf, TuiButton, TuiHint, TuiItem], 13 | templateUrl: './index.html', 14 | changeDetection: ChangeDetectionStrategy.OnPush, 15 | }) 16 | export class TuiRedoTool { 17 | protected readonly texts$ = inject(TUI_EDITOR_TOOLBAR_TEXTS); 18 | protected readonly options = inject(TUI_EDITOR_OPTIONS); 19 | 20 | @Input() 21 | public editor: AbstractTuiEditor | null = inject(TuiTiptapEditorService, { 22 | optional: true, 23 | }); 24 | } 25 | -------------------------------------------------------------------------------- /projects/editor/components/toolbar-tools/redo/ng-package.json: -------------------------------------------------------------------------------- 1 | { 2 | "lib": { 3 | "entryFile": "index.ts" 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /projects/editor/components/toolbar-tools/subscript/index.html: -------------------------------------------------------------------------------- 1 | 2 | 16 | -------------------------------------------------------------------------------- /projects/editor/components/toolbar-tools/subscript/index.ts: -------------------------------------------------------------------------------- 1 | import {AsyncPipe, NgIf} from '@angular/common'; 2 | import {ChangeDetectionStrategy, Component, inject, Input} from '@angular/core'; 3 | import {TuiItem} from '@taiga-ui/cdk'; 4 | import {TuiButton, TuiHint} from '@taiga-ui/core'; 5 | import type {AbstractTuiEditor} from '@taiga-ui/editor/common'; 6 | import {TUI_EDITOR_OPTIONS, TUI_EDITOR_TOOLBAR_TEXTS} from '@taiga-ui/editor/common'; 7 | import {TuiTiptapEditorService} from '@taiga-ui/editor/directives/tiptap-editor'; 8 | 9 | @Component({ 10 | standalone: true, 11 | selector: 'tui-subscript-tool', 12 | imports: [AsyncPipe, NgIf, TuiButton, TuiHint, TuiItem], 13 | templateUrl: './index.html', 14 | changeDetection: ChangeDetectionStrategy.OnPush, 15 | }) 16 | export class TuiSubscriptTool { 17 | protected readonly texts$ = inject(TUI_EDITOR_TOOLBAR_TEXTS); 18 | protected readonly options = inject(TUI_EDITOR_OPTIONS); 19 | 20 | @Input() 21 | public editor: AbstractTuiEditor | null = inject(TuiTiptapEditorService, { 22 | optional: true, 23 | }); 24 | } 25 | -------------------------------------------------------------------------------- /projects/editor/components/toolbar-tools/subscript/ng-package.json: -------------------------------------------------------------------------------- 1 | { 2 | "lib": { 3 | "entryFile": "index.ts" 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /projects/editor/components/toolbar-tools/superscript/index.html: -------------------------------------------------------------------------------- 1 | 2 | 16 | -------------------------------------------------------------------------------- /projects/editor/components/toolbar-tools/superscript/index.ts: -------------------------------------------------------------------------------- 1 | import {AsyncPipe, NgIf} from '@angular/common'; 2 | import {ChangeDetectionStrategy, Component, inject, Input} from '@angular/core'; 3 | import {TuiItem} from '@taiga-ui/cdk'; 4 | import {TuiButton, TuiHint} from '@taiga-ui/core'; 5 | import type {AbstractTuiEditor} from '@taiga-ui/editor/common'; 6 | import {TUI_EDITOR_OPTIONS, TUI_EDITOR_TOOLBAR_TEXTS} from '@taiga-ui/editor/common'; 7 | import {TuiTiptapEditorService} from '@taiga-ui/editor/directives/tiptap-editor'; 8 | 9 | @Component({ 10 | standalone: true, 11 | selector: 'tui-superscript-tool', 12 | imports: [AsyncPipe, NgIf, TuiButton, TuiHint, TuiItem], 13 | templateUrl: './index.html', 14 | changeDetection: ChangeDetectionStrategy.OnPush, 15 | }) 16 | export class TuiSuperscriptTool { 17 | protected readonly texts$ = inject(TUI_EDITOR_TOOLBAR_TEXTS); 18 | protected readonly options = inject(TUI_EDITOR_OPTIONS); 19 | 20 | @Input() 21 | public editor: AbstractTuiEditor | null = inject(TuiTiptapEditorService, { 22 | optional: true, 23 | }); 24 | } 25 | -------------------------------------------------------------------------------- /projects/editor/components/toolbar-tools/superscript/ng-package.json: -------------------------------------------------------------------------------- 1 | { 2 | "lib": { 3 | "entryFile": "index.ts" 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /projects/editor/components/toolbar-tools/table-cell-color/index.html: -------------------------------------------------------------------------------- 1 | 19 |
24 | 25 | 30 | 31 | -------------------------------------------------------------------------------- /projects/editor/components/toolbar-tools/table-cell-color/ng-package.json: -------------------------------------------------------------------------------- 1 | { 2 | "lib": { 3 | "entryFile": "index.ts" 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /projects/editor/components/toolbar-tools/table-create/index.html: -------------------------------------------------------------------------------- 1 |
8 | 23 |
24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /projects/editor/components/toolbar-tools/table-create/ng-package.json: -------------------------------------------------------------------------------- 1 | { 2 | "lib": { 3 | "entryFile": "index.ts" 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /projects/editor/components/toolbar-tools/table-create/table-size-selector/index.html: -------------------------------------------------------------------------------- 1 |
5 |
12 |
13 |
{{ tableSize.cols }}×{{ tableSize.rows }}
14 | -------------------------------------------------------------------------------- /projects/editor/components/toolbar-tools/table-create/table-size-selector/index.less: -------------------------------------------------------------------------------- 1 | :host { 2 | display: block; 3 | padding: 0.75rem; 4 | } 5 | 6 | .t-cell { 7 | display: inline-block; 8 | background-color: var(--tui-background-base); 9 | inline-size: 1.25rem; 10 | block-size: 1.25rem; 11 | border-radius: 0.25rem; 12 | margin: 0.125rem; 13 | border: 0.0625rem solid var(--tui-border-normal); 14 | cursor: pointer; 15 | 16 | &_hovered { 17 | background-color: var(--tui-background-base-alt); 18 | } 19 | } 20 | 21 | .t-column { 22 | white-space: nowrap; 23 | } 24 | 25 | .t-description { 26 | text-align: center; 27 | } 28 | -------------------------------------------------------------------------------- /projects/editor/components/toolbar-tools/table-create/table-size-selector/ng-package.json: -------------------------------------------------------------------------------- 1 | { 2 | "lib": { 3 | "entryFile": "index.ts" 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /projects/editor/components/toolbar-tools/table-merge-cells/index.html: -------------------------------------------------------------------------------- 1 | 17 | -------------------------------------------------------------------------------- /projects/editor/components/toolbar-tools/table-merge-cells/ng-package.json: -------------------------------------------------------------------------------- 1 | { 2 | "lib": { 3 | "entryFile": "index.ts" 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /projects/editor/components/toolbar-tools/table-row-column-manager/ng-package.json: -------------------------------------------------------------------------------- 1 | { 2 | "lib": { 3 | "entryFile": "index.ts" 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /projects/editor/components/toolbar-tools/tex/index.html: -------------------------------------------------------------------------------- 1 | 2 | 16 | -------------------------------------------------------------------------------- /projects/editor/components/toolbar-tools/tex/ng-package.json: -------------------------------------------------------------------------------- 1 | { 2 | "lib": { 3 | "entryFile": "index.ts" 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /projects/editor/components/toolbar-tools/text-color/index.html: -------------------------------------------------------------------------------- 1 |
5 | 21 | 22 | 27 | 28 |
29 | -------------------------------------------------------------------------------- /projects/editor/components/toolbar-tools/text-color/ng-package.json: -------------------------------------------------------------------------------- 1 | { 2 | "lib": { 3 | "entryFile": "index.ts" 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /projects/editor/components/toolbar-tools/undo/index.html: -------------------------------------------------------------------------------- 1 | 2 | 18 | -------------------------------------------------------------------------------- /projects/editor/components/toolbar-tools/undo/index.ts: -------------------------------------------------------------------------------- 1 | import {AsyncPipe, NgIf} from '@angular/common'; 2 | import {ChangeDetectionStrategy, Component, inject, Input} from '@angular/core'; 3 | import {TuiItem} from '@taiga-ui/cdk'; 4 | import {TuiButton, TuiHint} from '@taiga-ui/core'; 5 | import type {AbstractTuiEditor} from '@taiga-ui/editor/common'; 6 | import {TUI_EDITOR_OPTIONS, TUI_EDITOR_TOOLBAR_TEXTS} from '@taiga-ui/editor/common'; 7 | import {TuiTiptapEditorService} from '@taiga-ui/editor/directives/tiptap-editor'; 8 | 9 | @Component({ 10 | standalone: true, 11 | selector: 'tui-undo-tool', 12 | imports: [AsyncPipe, NgIf, TuiButton, TuiHint, TuiItem], 13 | templateUrl: './index.html', 14 | changeDetection: ChangeDetectionStrategy.OnPush, 15 | }) 16 | export class TuiUndoTool { 17 | protected readonly texts$ = inject(TUI_EDITOR_TOOLBAR_TEXTS); 18 | protected readonly options = inject(TUI_EDITOR_OPTIONS); 19 | 20 | @Input() 21 | public editor: AbstractTuiEditor | null = inject(TuiTiptapEditorService, { 22 | optional: true, 23 | }); 24 | } 25 | -------------------------------------------------------------------------------- /projects/editor/components/toolbar-tools/undo/ng-package.json: -------------------------------------------------------------------------------- 1 | { 2 | "lib": { 3 | "entryFile": "index.ts" 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /projects/editor/components/toolbar/index.ts: -------------------------------------------------------------------------------- 1 | export * from './toolbar.component'; 2 | -------------------------------------------------------------------------------- /projects/editor/components/toolbar/ng-package.json: -------------------------------------------------------------------------------- 1 | { 2 | "lib": { 3 | "entryFile": "index.ts" 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /projects/editor/components/toolbar/toolbar.style.less: -------------------------------------------------------------------------------- 1 | @import (inline) '@taiga-ui/editor/styles/editor-toolbar.css'; 2 | -------------------------------------------------------------------------------- /projects/editor/directives/image-preview/index.ts: -------------------------------------------------------------------------------- 1 | import {Directive, EventEmitter, Output} from '@angular/core'; 2 | 3 | @Directive({ 4 | standalone: true, 5 | selector: 'tui-editor-socket[imagePreview]', 6 | host: { 7 | class: '_preview-image', 8 | '(click)': 'click($event.target)', 9 | }, 10 | }) 11 | export class TuiEditorImagePreview { 12 | @Output() 13 | public readonly imagePreview = new EventEmitter(); 14 | 15 | protected click(target: HTMLElement): void { 16 | if (target instanceof HTMLImageElement) { 17 | this.imagePreview.emit(target); 18 | } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /projects/editor/directives/image-preview/ng-package.json: -------------------------------------------------------------------------------- 1 | { 2 | "lib": { 3 | "entryFile": "index.ts" 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /projects/editor/directives/index.ts: -------------------------------------------------------------------------------- 1 | export * from '@taiga-ui/editor/directives/image-preview'; 2 | export * from '@taiga-ui/editor/directives/tiptap-editor'; 3 | -------------------------------------------------------------------------------- /projects/editor/directives/ng-package.json: -------------------------------------------------------------------------------- 1 | { 2 | "lib": { 3 | "entryFile": "index.ts" 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /projects/editor/directives/tiptap-editor/index.ts: -------------------------------------------------------------------------------- 1 | export * from './tiptap-editor.directive'; 2 | export * from './tiptap-editor.service'; 3 | export * from './utils/is-empty-paragraph'; 4 | -------------------------------------------------------------------------------- /projects/editor/directives/tiptap-editor/ng-package.json: -------------------------------------------------------------------------------- 1 | { 2 | "lib": { 3 | "entryFile": "index.ts" 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /projects/editor/directives/tiptap-editor/utils/is-empty-paragraph.ts: -------------------------------------------------------------------------------- 1 | import type {JSONContent} from '@tiptap/core'; 2 | 3 | export function tuiIsEmptyParagraph(json?: JSONContent[]): boolean { 4 | return ( 5 | Array.isArray(json) && 6 | json.length === 1 && 7 | json[0]?.type === 'paragraph' && 8 | !json[0]?.hasOwnProperty('content') 9 | ); 10 | } 11 | -------------------------------------------------------------------------------- /projects/editor/extensions/background-color/ng-package.json: -------------------------------------------------------------------------------- 1 | { 2 | "lib": { 3 | "entryFile": "index.ts" 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /projects/editor/extensions/default-editor-extensions/ng-package.json: -------------------------------------------------------------------------------- 1 | { 2 | "lib": { 3 | "entryFile": "index.ts" 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /projects/editor/extensions/details/ng-package.json: -------------------------------------------------------------------------------- 1 | { 2 | "lib": { 3 | "entryFile": "index.ts" 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /projects/editor/extensions/enter/index.ts: -------------------------------------------------------------------------------- 1 | import {Extension} from '@tiptap/core'; 2 | 3 | export const TuiCustomEnter = Extension.create({ 4 | name: 'customEnter', 5 | addKeyboardShortcuts() { 6 | return { 7 | Enter: ({editor}) => { 8 | if (editor.isActive('summary')) { 9 | editor.commands.selectNodeForward(); 10 | editor?.commands.focus((editor?.state.selection.anchor ?? 0) + 1); 11 | 12 | if (globalThis.document) { 13 | editor.view 14 | .nodeDOM(editor.state.selection.anchor) 15 | ?.parentElement?.closest('details') 16 | ?.querySelector('[data-type="details-content"]') 17 | ?.prepend(document.createElement('p')); 18 | } 19 | 20 | return false; 21 | } 22 | 23 | return this.editor.chain().createParagraphNear().run(); 24 | }, 25 | }; 26 | }, 27 | }); 28 | -------------------------------------------------------------------------------- /projects/editor/extensions/enter/ng-package.json: -------------------------------------------------------------------------------- 1 | { 2 | "lib": { 3 | "entryFile": "index.ts" 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /projects/editor/extensions/file-link/ng-package.json: -------------------------------------------------------------------------------- 1 | { 2 | "lib": { 3 | "entryFile": "index.ts" 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /projects/editor/extensions/font-color/ng-package.json: -------------------------------------------------------------------------------- 1 | { 2 | "lib": { 3 | "entryFile": "index.ts" 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /projects/editor/extensions/font-size/ng-package.json: -------------------------------------------------------------------------------- 1 | { 2 | "lib": { 3 | "entryFile": "index.ts" 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /projects/editor/extensions/group/ng-package.json: -------------------------------------------------------------------------------- 1 | { 2 | "lib": { 3 | "entryFile": "index.ts" 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /projects/editor/extensions/horizontal/ng-package.json: -------------------------------------------------------------------------------- 1 | { 2 | "lib": { 3 | "entryFile": "index.ts" 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /projects/editor/extensions/iframe-editor/iframe-editor.component.html: -------------------------------------------------------------------------------- 1 | 6 | 16 | 17 | -------------------------------------------------------------------------------- /projects/editor/extensions/iframe-editor/iframe-editor.component.less: -------------------------------------------------------------------------------- 1 | iframe { 2 | display: inline; 3 | pointer-events: none; 4 | } 5 | -------------------------------------------------------------------------------- /projects/editor/extensions/iframe-editor/iframe-editor.options.ts: -------------------------------------------------------------------------------- 1 | import {InjectionToken} from '@angular/core'; 2 | import type {TuiEditableIframeOptions} from '@taiga-ui/editor/common'; 3 | 4 | /** 5 | * Size of resizable iframe inside editor 6 | */ 7 | export const TUI_IFRAME_EDITOR_OPTIONS = new InjectionToken( 8 | '[TUI_IFRAME_EDITOR_OPTIONS]', 9 | { 10 | factory: () => ({ 11 | minWidth: 100, 12 | maxWidth: Infinity, 13 | minHeight: 100, 14 | maxHeight: Infinity, 15 | }), 16 | }, 17 | ); 18 | -------------------------------------------------------------------------------- /projects/editor/extensions/iframe-editor/index.ts: -------------------------------------------------------------------------------- 1 | export * from './iframe-editor.component'; 2 | export * from './iframe-editor.extension'; 3 | export * from './iframe-editor.options'; 4 | -------------------------------------------------------------------------------- /projects/editor/extensions/iframe-editor/ng-package.json: -------------------------------------------------------------------------------- 1 | { 2 | "lib": { 3 | "entryFile": "index.ts" 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /projects/editor/extensions/image-editor/image-editor.component.less: -------------------------------------------------------------------------------- 1 | @import '@taiga-ui/core/styles/taiga-ui-local.less'; 2 | 3 | :host { 4 | display: inline-block; 5 | 6 | &[data-drag-handle] { 7 | cursor: move; 8 | } 9 | 10 | &:hover { 11 | .t-image-options { 12 | opacity: 1; 13 | } 14 | } 15 | } 16 | 17 | img { 18 | pointer-events: none; 19 | } 20 | 21 | .t-hosted { 22 | position: relative; 23 | } 24 | 25 | .t-image-options { 26 | position: absolute; 27 | top: 0.625rem; 28 | right: 0.625rem; 29 | z-index: 1; 30 | background: var(--tui-background-base); 31 | } 32 | 33 | .t-image-options:not(._open) { 34 | .transition(opacity); 35 | 36 | opacity: 0; 37 | } 38 | 39 | .t-align-list { 40 | display: flex; 41 | gap: 0.3125rem; 42 | padding: 0.125rem; 43 | } 44 | -------------------------------------------------------------------------------- /projects/editor/extensions/image-editor/image-editor.options.ts: -------------------------------------------------------------------------------- 1 | import {InjectionToken} from '@angular/core'; 2 | import type {TuiImageEditorOptions} from '@taiga-ui/editor/common'; 3 | 4 | /** 5 | * Size of resizable image inside editor 6 | */ 7 | export const TUI_IMAGE_EDITOR_OPTIONS = new InjectionToken( 8 | '[TUI_IMAGE_EDITOR_OPTIONS]', 9 | { 10 | factory: () => ({ 11 | minWidth: null, 12 | maxWidth: Infinity, 13 | }), 14 | }, 15 | ); 16 | -------------------------------------------------------------------------------- /projects/editor/extensions/image-editor/image-options-position.directive.ts: -------------------------------------------------------------------------------- 1 | import {Directive, ElementRef, inject} from '@angular/core'; 2 | import type {TuiPoint} from '@taiga-ui/core'; 3 | import {tuiAsPositionAccessor, TuiPositionAccessor} from '@taiga-ui/core'; 4 | 5 | @Directive({ 6 | standalone: true, 7 | selector: '[tuiImageOptionsPosition]', 8 | providers: [tuiAsPositionAccessor(TuiImageOptionsPosition)], 9 | }) 10 | export class TuiImageOptionsPosition extends TuiPositionAccessor { 11 | private readonly el: ElementRef = inject(ElementRef); 12 | public readonly type = 'dropdown'; 13 | 14 | public getPosition({width, height}: DOMRect): TuiPoint { 15 | const {right, top} = this.el.nativeElement.getBoundingClientRect(); 16 | 17 | return [top + height + 5, right - width / 2]; 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /projects/editor/extensions/image-editor/index.ts: -------------------------------------------------------------------------------- 1 | export * from './image-editor.component'; 2 | export * from './image-editor.extension'; 3 | export * from './image-editor.options'; 4 | export * from './image-options-position.directive'; 5 | -------------------------------------------------------------------------------- /projects/editor/extensions/image-editor/ng-package.json: -------------------------------------------------------------------------------- 1 | { 2 | "lib": { 3 | "entryFile": "index.ts" 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /projects/editor/extensions/image-editor/options/image-align/image-align.component.less: -------------------------------------------------------------------------------- 1 | .t-align-list { 2 | display: flex; 3 | gap: 0.3125rem; 4 | padding: 0.125rem; 5 | } 6 | -------------------------------------------------------------------------------- /projects/editor/extensions/indent-outdent/index.ts: -------------------------------------------------------------------------------- 1 | import type {Editor, KeyboardShortcutCommand} from '@tiptap/core'; 2 | import {Extension} from '@tiptap/core'; 3 | 4 | export function tuiIsListActive(editor: Editor): boolean { 5 | return ( 6 | editor.isActive('bulletList') || 7 | editor.isActive('orderedList') || 8 | editor.isActive('taskList') 9 | ); 10 | } 11 | 12 | export const TuiTabExtension = Extension.create({ 13 | name: 'indent', 14 | 15 | addKeyboardShortcuts(): Record { 16 | return { 17 | Tab: () => 18 | tuiIsListActive(this.editor) 19 | ? false 20 | : this.editor.commands.insertContent('\t'), 21 | }; 22 | }, 23 | }); 24 | -------------------------------------------------------------------------------- /projects/editor/extensions/indent-outdent/ng-package.json: -------------------------------------------------------------------------------- 1 | { 2 | "lib": { 3 | "entryFile": "index.ts" 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /projects/editor/extensions/jump-anchor/ng-package.json: -------------------------------------------------------------------------------- 1 | { 2 | "lib": { 3 | "entryFile": "index.ts" 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /projects/editor/extensions/link/ng-package.json: -------------------------------------------------------------------------------- 1 | { 2 | "lib": { 3 | "entryFile": "index.ts" 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /projects/editor/extensions/markdown/extensions/index.ts: -------------------------------------------------------------------------------- 1 | export type * from './marks'; 2 | export type * from './nodes'; 3 | export * from './util'; 4 | -------------------------------------------------------------------------------- /projects/editor/extensions/markdown/extensions/marks/bold/index.ts: -------------------------------------------------------------------------------- 1 | import {Mark} from '@tiptap/core'; 2 | import {defaultMarkdownSerializer} from '@tiptap/pm/markdown'; 3 | 4 | export default Mark.create({ 5 | name: 'bold', 6 | }).extend({ 7 | addStorage() { 8 | return { 9 | markdown: { 10 | serialize: defaultMarkdownSerializer.marks.strong, 11 | parse: { 12 | // handled by markdown-it 13 | }, 14 | }, 15 | }; 16 | }, 17 | }); 18 | -------------------------------------------------------------------------------- /projects/editor/extensions/markdown/extensions/marks/code/index.ts: -------------------------------------------------------------------------------- 1 | import {Mark} from '@tiptap/core'; 2 | import {defaultMarkdownSerializer} from '@tiptap/pm/markdown'; 3 | 4 | export default Mark.create({ 5 | name: 'code', 6 | }).extend({ 7 | addStorage() { 8 | return { 9 | markdown: { 10 | serialize: defaultMarkdownSerializer.marks.code, 11 | parse: { 12 | // handled by markdown-it 13 | }, 14 | }, 15 | }; 16 | }, 17 | }); 18 | -------------------------------------------------------------------------------- /projects/editor/extensions/markdown/extensions/marks/index.ts: -------------------------------------------------------------------------------- 1 | export * from './bold'; 2 | export * from './code'; 3 | export * from './html'; 4 | export * from './italic'; 5 | export * from './link'; 6 | export * from './strike'; 7 | -------------------------------------------------------------------------------- /projects/editor/extensions/markdown/extensions/marks/italic/index.ts: -------------------------------------------------------------------------------- 1 | import {Mark} from '@tiptap/core'; 2 | import {defaultMarkdownSerializer} from '@tiptap/pm/markdown'; 3 | 4 | export default Mark.create({ 5 | name: 'italic', 6 | }).extend({ 7 | addStorage() { 8 | return { 9 | markdown: { 10 | serialize: defaultMarkdownSerializer.marks.em, 11 | parse: { 12 | // handled by markdown-it 13 | }, 14 | }, 15 | }; 16 | }, 17 | }); 18 | -------------------------------------------------------------------------------- /projects/editor/extensions/markdown/extensions/marks/link/index.ts: -------------------------------------------------------------------------------- 1 | import {Mark} from '@tiptap/core'; 2 | import {defaultMarkdownSerializer} from '@tiptap/pm/markdown'; 3 | 4 | export default Mark.create({ 5 | name: 'link', 6 | }).extend({ 7 | addStorage() { 8 | return { 9 | markdown: { 10 | serialize: defaultMarkdownSerializer.marks.link, 11 | parse: { 12 | // handled by markdown-it 13 | }, 14 | }, 15 | }; 16 | }, 17 | }); 18 | -------------------------------------------------------------------------------- /projects/editor/extensions/markdown/extensions/marks/strike/index.ts: -------------------------------------------------------------------------------- 1 | import {Mark} from '@tiptap/core'; 2 | 3 | export default Mark.create({ 4 | name: 'strike', 5 | }).extend({ 6 | addStorage() { 7 | return { 8 | markdown: { 9 | serialize: {open: '~~', close: '~~', expelEnclosingWhitespace: true}, 10 | parse: { 11 | // handled by markdown-it 12 | }, 13 | }, 14 | }; 15 | }, 16 | }); 17 | -------------------------------------------------------------------------------- /projects/editor/extensions/markdown/extensions/nodes/blockquote/index.ts: -------------------------------------------------------------------------------- 1 | import {Node} from '@tiptap/core'; 2 | import {defaultMarkdownSerializer} from '@tiptap/pm/markdown'; 3 | 4 | export default Node.create({ 5 | name: 'blockquote', 6 | }).extend({ 7 | addStorage() { 8 | return { 9 | markdown: { 10 | serialize: defaultMarkdownSerializer.nodes.blockquote, 11 | parse: { 12 | // handled by markdown-it 13 | }, 14 | }, 15 | }; 16 | }, 17 | }); 18 | -------------------------------------------------------------------------------- /projects/editor/extensions/markdown/extensions/nodes/bullet-list/index.ts: -------------------------------------------------------------------------------- 1 | import type {Editor} from '@tiptap/core'; 2 | import {Node} from '@tiptap/core'; 3 | import type {Node as ProseNode} from '@tiptap/pm/model'; 4 | 5 | export default Node.create({ 6 | name: 'bulletList', 7 | }).extend({ 8 | addStorage() { 9 | return { 10 | markdown: { 11 | serialize(state: any, node: ProseNode) { 12 | return state.renderList( 13 | node, 14 | ' ', 15 | () => 16 | `${ 17 | ((this as any)?.editor as Editor)?.storage.markdown 18 | .options.bulletListMarker || '-' 19 | } `, 20 | ); 21 | }, 22 | parse: { 23 | // handled by markdown-it 24 | }, 25 | }, 26 | }; 27 | }, 28 | }); 29 | -------------------------------------------------------------------------------- /projects/editor/extensions/markdown/extensions/nodes/heading/index.ts: -------------------------------------------------------------------------------- 1 | import {Node} from '@tiptap/core'; 2 | import {defaultMarkdownSerializer} from '@tiptap/pm/markdown'; 3 | 4 | export default Node.create({ 5 | name: 'heading', 6 | }).extend({ 7 | addStorage() { 8 | return { 9 | markdown: { 10 | serialize: defaultMarkdownSerializer.nodes.heading, 11 | parse: { 12 | // handled by markdown-it 13 | }, 14 | }, 15 | }; 16 | }, 17 | }); 18 | -------------------------------------------------------------------------------- /projects/editor/extensions/markdown/extensions/nodes/horizontal-rule/index.ts: -------------------------------------------------------------------------------- 1 | import {Node} from '@tiptap/core'; 2 | import {defaultMarkdownSerializer} from '@tiptap/pm/markdown'; 3 | 4 | export default Node.create({ 5 | name: 'horizontalRule', 6 | }).extend({ 7 | addStorage() { 8 | return { 9 | markdown: { 10 | serialize: defaultMarkdownSerializer.nodes.horizontal_rule, 11 | parse: { 12 | // handled by markdown-it 13 | }, 14 | }, 15 | }; 16 | }, 17 | }); 18 | -------------------------------------------------------------------------------- /projects/editor/extensions/markdown/extensions/nodes/image/index.ts: -------------------------------------------------------------------------------- 1 | import {Node} from '@tiptap/core'; 2 | import {defaultMarkdownSerializer} from '@tiptap/pm/markdown'; 3 | 4 | export default Node.create({ 5 | name: 'image', 6 | }).extend({ 7 | addStorage() { 8 | return { 9 | markdown: { 10 | serialize: defaultMarkdownSerializer.nodes.image, 11 | parse: { 12 | // handled by markdown-it 13 | }, 14 | }, 15 | }; 16 | }, 17 | }); 18 | -------------------------------------------------------------------------------- /projects/editor/extensions/markdown/extensions/nodes/index.ts: -------------------------------------------------------------------------------- 1 | export * from './blockquote'; 2 | export * from './bullet-list'; 3 | export * from './code-block'; 4 | export * from './hard-break'; 5 | export * from './heading'; 6 | export * from './horizontal-rule'; 7 | export * from './html'; 8 | export * from './image'; 9 | export * from './list-item'; 10 | export * from './ordered-list'; 11 | export * from './paragraph'; 12 | export * from './table'; 13 | export * from './task-item'; 14 | export * from './task-list'; 15 | export * from './text'; 16 | -------------------------------------------------------------------------------- /projects/editor/extensions/markdown/extensions/nodes/list-item/index.ts: -------------------------------------------------------------------------------- 1 | import {Node} from '@tiptap/core'; 2 | import {defaultMarkdownSerializer} from '@tiptap/pm/markdown'; 3 | 4 | export default Node.create({ 5 | name: 'listItem', 6 | }).extend({ 7 | addStorage() { 8 | return { 9 | markdown: { 10 | serialize: defaultMarkdownSerializer.nodes.list_item, 11 | parse: { 12 | // handled by markdown-it 13 | }, 14 | }, 15 | }; 16 | }, 17 | }); 18 | -------------------------------------------------------------------------------- /projects/editor/extensions/markdown/extensions/nodes/paragraph/index.ts: -------------------------------------------------------------------------------- 1 | import {Node} from '@tiptap/core'; 2 | import {defaultMarkdownSerializer} from '@tiptap/pm/markdown'; 3 | 4 | export default Node.create({ 5 | name: 'paragraph', 6 | }).extend({ 7 | addStorage() { 8 | return { 9 | markdown: { 10 | serialize: defaultMarkdownSerializer.nodes.paragraph, 11 | parse: { 12 | // handled by markdown-it 13 | }, 14 | }, 15 | }; 16 | }, 17 | }); 18 | -------------------------------------------------------------------------------- /projects/editor/extensions/markdown/extensions/nodes/task-list/index.ts: -------------------------------------------------------------------------------- 1 | import {Node} from '@tiptap/core'; 2 | import type MarkdownIt from 'markdown-it'; 3 | 4 | import {tuiMarkdownItTaskList} from '../../../util/markdown-it-task-lists'; 5 | import BulletList from '../bullet-list'; 6 | 7 | export default Node.create({ 8 | name: 'taskList', 9 | }).extend({ 10 | addStorage() { 11 | return { 12 | markdown: { 13 | serialize: BulletList.storage.markdown.serialize, 14 | parse: { 15 | setup(markdown: MarkdownIt) { 16 | markdown.use(tuiMarkdownItTaskList); 17 | }, 18 | updateDOM(element: Element) { 19 | Array.from( 20 | element.querySelectorAll('.contains-task-list'), 21 | ).forEach((list) => { 22 | list.setAttribute('data-type', 'taskList'); 23 | }); 24 | }, 25 | }, 26 | }, 27 | }; 28 | }, 29 | }); 30 | -------------------------------------------------------------------------------- /projects/editor/extensions/markdown/extensions/nodes/text/index.ts: -------------------------------------------------------------------------------- 1 | import {Node} from '@tiptap/core'; 2 | import type {Node as ProseNode} from '@tiptap/pm/model'; 3 | 4 | import {tuiEscapeHTML} from '../../../util/dom'; 5 | 6 | export default Node.create({ 7 | name: 'text', 8 | }).extend({ 9 | addStorage() { 10 | return { 11 | markdown: { 12 | serialize(state: any, node: ProseNode) { 13 | state.text(tuiEscapeHTML(node.text)); 14 | }, 15 | parse: { 16 | // handled by markdown-it 17 | }, 18 | }, 19 | }; 20 | }, 21 | }); 22 | -------------------------------------------------------------------------------- /projects/editor/extensions/markdown/index.ts: -------------------------------------------------------------------------------- 1 | export * from './clipboard'; 2 | export * from './extension'; 3 | export * from './extensions'; 4 | export * from './parse'; 5 | export * from './serialize'; 6 | export * from './tight-lists'; 7 | export * from './util'; 8 | -------------------------------------------------------------------------------- /projects/editor/extensions/markdown/ng-package.json: -------------------------------------------------------------------------------- 1 | { 2 | "lib": { 3 | "entryFile": "index.ts" 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /projects/editor/extensions/markdown/serialize/index.ts: -------------------------------------------------------------------------------- 1 | export * from './markdown-serializer'; 2 | export * from './state'; 3 | -------------------------------------------------------------------------------- /projects/editor/extensions/markdown/util/index.ts: -------------------------------------------------------------------------------- 1 | export * from './dom'; 2 | export * from './markdown'; 3 | export * from './markdown-it-task-lists'; 4 | export * from './prosemirror'; 5 | -------------------------------------------------------------------------------- /projects/editor/extensions/markdown/util/prosemirror.ts: -------------------------------------------------------------------------------- 1 | export function tuiChildNodes(node: any): any[] { 2 | return node?.content?.content ?? []; 3 | } 4 | -------------------------------------------------------------------------------- /projects/editor/extensions/media/ng-package.json: -------------------------------------------------------------------------------- 1 | { 2 | "lib": { 3 | "entryFile": "index.ts" 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /projects/editor/extensions/mention/ng-package.json: -------------------------------------------------------------------------------- 1 | { 2 | "lib": { 3 | "entryFile": "index.ts" 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /projects/editor/extensions/ng-package.json: -------------------------------------------------------------------------------- 1 | { 2 | "lib": { 3 | "entryFile": "index.ts" 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /projects/editor/extensions/starter-kit/ng-package.json: -------------------------------------------------------------------------------- 1 | { 2 | "lib": { 3 | "entryFile": "index.ts" 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /projects/editor/extensions/table-cell-background/ng-package.json: -------------------------------------------------------------------------------- 1 | { 2 | "lib": { 3 | "entryFile": "index.ts" 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /projects/editor/extensions/table-cell/index.ts: -------------------------------------------------------------------------------- 1 | import {mergeAttributes} from '@tiptap/core'; 2 | import {TableCell} from '@tiptap/extension-table-cell'; 3 | 4 | export const TuiTableCell = TableCell.extend({ 5 | renderHTML({HTMLAttributes}) { 6 | const attrs = mergeAttributes(this.options.HTMLAttributes, HTMLAttributes); 7 | 8 | if (attrs.colwidth) { 9 | attrs.style = `width: ${attrs.colwidth}px; ${attrs.style}`; 10 | } 11 | 12 | return ['td', attrs, 0]; 13 | }, 14 | }); 15 | -------------------------------------------------------------------------------- /projects/editor/extensions/table-cell/ng-package.json: -------------------------------------------------------------------------------- 1 | { 2 | "lib": { 3 | "entryFile": "index.ts" 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /projects/editor/extensions/table/index.ts: -------------------------------------------------------------------------------- 1 | import {mergeAttributes} from '@tiptap/core'; 2 | import {createColGroup, Table} from '@tiptap/extension-table'; 3 | 4 | export const TuiTable = Table.extend({ 5 | renderHTML({node, HTMLAttributes}) { 6 | const {colgroup, tableWidth, tableMinWidth} = createColGroup( 7 | node, 8 | this.options.cellMinWidth, 9 | ); 10 | 11 | return [ 12 | 'div', 13 | {class: 'tui-table-wrapper'}, 14 | [ 15 | 'table', 16 | mergeAttributes(this.options.HTMLAttributes, HTMLAttributes, { 17 | style: tableWidth 18 | ? `width: ${tableWidth}` 19 | : `min-width: ${tableMinWidth}`, 20 | }), 21 | colgroup, 22 | ['tbody', 0], 23 | ], 24 | ]; 25 | }, 26 | }) 27 | .configure({ 28 | resizable: true, 29 | lastColumnResizable: false, 30 | allowTableNodeSelection: true, 31 | }) 32 | .extend(); 33 | -------------------------------------------------------------------------------- /projects/editor/extensions/table/ng-package.json: -------------------------------------------------------------------------------- 1 | { 2 | "lib": { 3 | "entryFile": "index.ts" 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /projects/editor/extensions/tiptap-node-view/ng-package.json: -------------------------------------------------------------------------------- 1 | { 2 | "lib": { 3 | "entryFile": "index.ts" 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /projects/editor/extensions/youtube/index.ts: -------------------------------------------------------------------------------- 1 | import {Youtube} from '@tiptap/extension-youtube'; 2 | 3 | export const TuiYoutube = Youtube.extend({}).configure({ 4 | autoplay: false, 5 | ccLanguage: 'en', 6 | interfaceLanguage: 'en', 7 | allowFullscreen: true, 8 | disableKBcontrols: true, 9 | }); 10 | -------------------------------------------------------------------------------- /projects/editor/extensions/youtube/ng-package.json: -------------------------------------------------------------------------------- 1 | { 2 | "lib": { 3 | "entryFile": "index.ts" 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /projects/editor/index.ts: -------------------------------------------------------------------------------- 1 | export * from '@taiga-ui/editor/common'; 2 | export * from '@taiga-ui/editor/components'; 3 | export * from '@taiga-ui/editor/directives'; 4 | export * from '@taiga-ui/editor/extensions'; 5 | export * from '@taiga-ui/editor/utils'; 6 | -------------------------------------------------------------------------------- /projects/editor/jest.config.ts: -------------------------------------------------------------------------------- 1 | import type {Config} from 'jest'; 2 | 3 | import rootConfig from '../../jest.config'; 4 | 5 | const config: Config = { 6 | ...rootConfig, 7 | coverageDirectory: '/coverage/editor', 8 | testMatch: ['/projects/editor/**/*.spec.ts'], 9 | collectCoverageFrom: ['/projects/editor/**/*.ts'], 10 | }; 11 | 12 | export default config; 13 | -------------------------------------------------------------------------------- /projects/editor/ng-package.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "../../node_modules/ng-packagr/ng-package.schema.json", 3 | "assets": [ 4 | "styles" 5 | ], 6 | "allowedNonPeerDependencies": [ 7 | "." 8 | ], 9 | "dest": "../../dist/editor", 10 | "lib": { 11 | "entryFile": "index.ts" 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /projects/editor/styles/editor-socket.css: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/taiga-family/editor/9a95ffc5564fd972beec4719efbddc885906d532/projects/editor/styles/editor-socket.css -------------------------------------------------------------------------------- /projects/editor/utils/delete-nodes.ts: -------------------------------------------------------------------------------- 1 | import type {CommandProps} from '@tiptap/core'; 2 | import type {EditorState} from '@tiptap/pm/state'; 3 | 4 | export function tuiDeleteNode( 5 | state: EditorState, 6 | dispatch: CommandProps['dispatch'], 7 | nodeName: string, 8 | ): boolean { 9 | const position = state.selection.$anchor; 10 | 11 | for (let depth = position.depth; depth > 0; depth--) { 12 | const node = position.node(depth); 13 | 14 | if (node.type.name === nodeName) { 15 | if (dispatch) { 16 | dispatch( 17 | state.tr 18 | .delete(position.before(depth), position.after(depth)) 19 | .scrollIntoView(), 20 | ); 21 | } 22 | 23 | return true; 24 | } 25 | } 26 | 27 | return false; 28 | } 29 | -------------------------------------------------------------------------------- /projects/editor/utils/get-nested-nodes.ts: -------------------------------------------------------------------------------- 1 | import type {Attrs} from '@tiptap/pm/model'; 2 | import {Node as NodeElement} from '@tiptap/pm/model'; 3 | 4 | export function tuiGetNestedNodes(node: NodeElement): Array> { 5 | const nodes: Array> = []; 6 | 7 | // @note: the content field is not array type 8 | node.content.forEach((child) => { 9 | if (child instanceof NodeElement) { 10 | nodes.push([child.type.name, child.attrs]); 11 | } 12 | }); 13 | 14 | return nodes; 15 | } 16 | -------------------------------------------------------------------------------- /projects/editor/utils/get-selected-content.ts: -------------------------------------------------------------------------------- 1 | import {getHTMLFromFragment} from '@tiptap/core'; 2 | import type {EditorState} from '@tiptap/pm/state'; 3 | 4 | export function tuiGetSelectedContent(state: EditorState, current?: string): string { 5 | const currentNodeContent = current ?? state.selection.$head.parent.textContent; 6 | const selected = state.tr.doc.cut(state.tr.selection.from, state.tr.selection.to); 7 | 8 | return selected.content.size 9 | ? getHTMLFromFragment(selected.content, state.schema) 10 | : currentNodeContent; 11 | } 12 | -------------------------------------------------------------------------------- /projects/editor/utils/get-selection-state.ts: -------------------------------------------------------------------------------- 1 | import type {AbstractTuiEditor} from '@taiga-ui/editor/common'; 2 | 3 | export interface TuiSelectionState { 4 | before: string; 5 | after: string; 6 | } 7 | 8 | export function tuiGetSelectionState( 9 | editor: AbstractTuiEditor | null, 10 | ): TuiSelectionState { 11 | if (!editor?.state?.selection) { 12 | return {before: '', after: ''}; 13 | } 14 | 15 | const {$from, $to} = editor.state.selection; 16 | 17 | let before = $from.nodeBefore?.textContent; 18 | 19 | before = ( 20 | before?.slice( 21 | ((before?.lastIndexOf(' ') || before?.lastIndexOf('\n')) ?? 0) + 1, 22 | ) ?? '' 23 | ).trim(); 24 | 25 | const after = $to.nodeAfter?.textContent.trim() ?? ''; 26 | 27 | return {before, after}; 28 | } 29 | -------------------------------------------------------------------------------- /projects/editor/utils/get-sliced-fragment.ts: -------------------------------------------------------------------------------- 1 | import {getHTMLFromFragment} from '@tiptap/core'; 2 | import type {Fragment} from '@tiptap/pm/model'; 3 | import type {EditorState} from '@tiptap/pm/state'; 4 | 5 | export function tuiGetSlicedFragment({schema, tr}: EditorState): string { 6 | const selected = tr.doc.cut(tr.selection.from, tr.selection.to); 7 | 8 | return tuiGetHtmlFromFragment(selected.content, schema); 9 | } 10 | 11 | export function tuiGetHtmlFromFragment( 12 | fragment: Fragment, 13 | schema: EditorState['schema'], 14 | ): string { 15 | return getHTMLFromFragment(fragment, schema).replaceAll(/<\/?[^>]+(>|$)/g, ''); 16 | } 17 | -------------------------------------------------------------------------------- /projects/editor/utils/index.ts: -------------------------------------------------------------------------------- 1 | export * from './delete-nodes'; 2 | export * from './get-current-word-bounds'; 3 | export * from './get-mark-range'; 4 | export * from './get-nested-nodes'; 5 | export * from './get-selected-content'; 6 | export * from './get-selection-state'; 7 | export * from './get-sliced-fragment'; 8 | export * from './is-selection-in'; 9 | export * from './legacy-converter'; 10 | export * from './parse-node-attributes'; 11 | export * from './parse-style'; 12 | export * from './safe-link-range'; 13 | export * from './to-gradient'; 14 | -------------------------------------------------------------------------------- /projects/editor/utils/is-selection-in.ts: -------------------------------------------------------------------------------- 1 | import {tuiIsNodeIn} from '@taiga-ui/cdk'; 2 | 3 | /** 4 | * Checks if selection is inside a specific selector 5 | * @param selection 6 | * @param selector 7 | * @return true if selection is completely inside a particular selector 8 | */ 9 | export function tuiIsSelectionIn( 10 | {anchorNode, focusNode}: Selection, 11 | selector: string, 12 | ): boolean { 13 | // Even though focusNode/anchor-node are defined as Node, they can be null on initial nested document query 14 | return ( 15 | !!anchorNode && 16 | !!focusNode && 17 | tuiIsNodeIn(anchorNode, selector) && 18 | tuiIsNodeIn(focusNode, selector) 19 | ); 20 | } 21 | -------------------------------------------------------------------------------- /projects/editor/utils/ng-package.json: -------------------------------------------------------------------------------- 1 | { 2 | "lib": { 3 | "entryFile": "index.ts" 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /projects/editor/utils/parse-node-attributes.ts: -------------------------------------------------------------------------------- 1 | import type {Attribute} from '@tiptap/core'; 2 | 3 | export function tuiParseNodeAttributes( 4 | attrs: string[], 5 | ): Record> { 6 | return attrs.reduce>>((result, attribute) => { 7 | result[attribute] = { 8 | parseHTML: (element) => element?.getAttribute(`${attribute}`), 9 | }; 10 | 11 | return result; 12 | }, {}); 13 | } 14 | -------------------------------------------------------------------------------- /projects/editor/utils/parse-style.ts: -------------------------------------------------------------------------------- 1 | export function tuiParseStyle(style: string): Record { 2 | return style 3 | .split(';') 4 | .reduce((ruleMap: Record, ruleString: string) => { 5 | const [left, right] = ruleString.split(':') ?? []; 6 | 7 | if (left && right) { 8 | ruleMap[left.trim()] = right.trim(); 9 | } 10 | 11 | return ruleMap; 12 | }, {}); 13 | } 14 | -------------------------------------------------------------------------------- /projects/editor/utils/safe-link-range.ts: -------------------------------------------------------------------------------- 1 | export function tuiIsSafeLinkRange(range: Range): boolean { 2 | const textNodeLength = range.endContainer.nodeValue?.length ?? 0; 3 | 4 | return ( 5 | range.endOffset - range.startOffset > 0 || 6 | (range.endOffset - range.startOffset === 0 && textNodeLength === 1) || 7 | (range.startOffset !== 0 && 8 | textNodeLength > 1 && 9 | range.endOffset !== textNodeLength) 10 | ); 11 | } 12 | -------------------------------------------------------------------------------- /projects/editor/utils/to-gradient.ts: -------------------------------------------------------------------------------- 1 | import {tuiParseColor} from '@taiga-ui/cdk'; 2 | import type {TuiParsedGradient} from '@taiga-ui/editor/common'; 3 | 4 | export function tuiToGradient({stops, side}: TuiParsedGradient): string { 5 | return `linear-gradient(${side}, ${stops 6 | .map( 7 | ({color, position}) => `rgba(${tuiParseColor(color).join(', ')}) ${position}`, 8 | ) 9 | .join(', ')})`; 10 | } 11 | -------------------------------------------------------------------------------- /scripts/visual-testing/combine-snapshots.ts: -------------------------------------------------------------------------------- 1 | // @ts-nocheck It is used in CI only! 2 | /** 3 | * Canvas has difficult installation guide for ARM CPU, including an Apple M1 or M2 4 | * (not friendly for our external contributors). 5 | * https://github.com/Automattic/node-canvas/issues/1511 6 | */ 7 | import {createCanvas, loadImage} from 'canvas'; 8 | 9 | export async function combineSnapshots( 10 | imagesPaths: string[], 11 | ): Promise { 12 | const images = await Promise.all(imagesPaths.map(loadImage)); 13 | const totalWidth = images.reduce((acc: number, {width}) => acc + width, 0); 14 | const maxHeight = Math.max(...images.map(({height}) => height)); 15 | const canvas = createCanvas(totalWidth, maxHeight); 16 | const ctx = canvas.getContext('2d'); 17 | 18 | let prevWidth = 0; 19 | 20 | images.forEach((image) => { 21 | ctx.drawImage(image, prevWidth, 0); 22 | prevWidth += image.width; 23 | }); 24 | 25 | return canvas.toBuffer('image/png'); 26 | } 27 | -------------------------------------------------------------------------------- /setup-jest.ts: -------------------------------------------------------------------------------- 1 | import '@taiga-ui/testing/setup-jest'; 2 | -------------------------------------------------------------------------------- /tsconfig.build.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "exclude": ["**/*.spec.ts"], 4 | "compilerOptions": { 5 | "outDir": "./dist/out-tsc/lib", 6 | "paths": { 7 | "@taiga-ui/editor": ["./dist/tui-editor"], 8 | "@taiga-ui/editor/*": ["./dist/tui-editor/*"] 9 | } 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "@taiga-ui/tsconfig", 3 | "compilerOptions": { 4 | "baseUrl": "./", 5 | "outDir": "./dist", 6 | "paths": { 7 | "@taiga-ui/editor": ["./projects/editor"], 8 | "@taiga-ui/editor/*": ["./projects/editor/*"] 9 | } 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /tsconfig.spec.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "./out-tsc/spec", 5 | "types": ["jest"] 6 | }, 7 | "exclude": ["**/schematics/**/*", "projects/demo-playwrights", "taiga-ui"] 8 | } 9 | --------------------------------------------------------------------------------