├── .babelrc ├── src ├── index.ts └── lib │ └── indent.ts ├── package.json ├── tsconfig.json ├── tsconfig.spec.json ├── .eslintrc.json ├── CONTRIBUTING.md ├── tsconfig.lib.json ├── jest.config.js ├── LICENSE.md ├── project.json └── README.md /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [["@nrwl/web/babel", { "useBuiltIns": "usage" }]] 3 | } 4 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | import { Indent } from './lib/indent'; 2 | 3 | export * from './lib/indent'; 4 | 5 | export default Indent; 6 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@tiptaptop/extension-indent", 3 | "description": "indent extension for tiptap", 4 | "version": "2.0.0-beta.2", 5 | "peerDependencies": { 6 | "@tiptap/core": "^2.0.0-beta.117" 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../../tsconfig.base.json", 3 | "files": [], 4 | "include": [], 5 | "references": [ 6 | { 7 | "path": "./tsconfig.lib.json" 8 | }, 9 | { 10 | "path": "./tsconfig.spec.json" 11 | } 12 | ] 13 | } 14 | -------------------------------------------------------------------------------- /tsconfig.spec.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "../../../dist/out-tsc", 5 | "module": "commonjs", 6 | "types": ["jest", "node"] 7 | }, 8 | "files": ["src/test-setup.ts"], 9 | "include": ["**/*.spec.ts", "**/*.d.ts"] 10 | } 11 | -------------------------------------------------------------------------------- /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["../../../.eslintrc.json"], 3 | "ignorePatterns": ["!**/*"], 4 | "overrides": [ 5 | { 6 | "files": ["*.ts", "*.tsx"], 7 | "parserOptions": { 8 | "project": ["./tsconfig.*?.json"] 9 | }, 10 | "rules": { "@typescript-eslint/naming-convention": "off" } 11 | } 12 | ] 13 | } 14 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | Because this project is developed within a private monorepo, the typescript config will likely break if you try to run in yourself. It _should_ be enough to just remove the 3 tsconfig files when working locally, or modify the tsconfig.json, but please don't commit those changes when making a Pull Request. 2 | 3 | Other than that, contributions are welcome! 4 | -------------------------------------------------------------------------------- /tsconfig.lib.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "../../../dist/out-tsc", 5 | "target": "es2020", 6 | "declaration": true, 7 | "declarationMap": true, 8 | "inlineSources": true, 9 | "types": [], 10 | "lib": ["dom", "es2020"] 11 | }, 12 | "exclude": ["src/test-setup.ts", "**/*.spec.ts"], 13 | "include": ["**/*.ts"] 14 | } 15 | -------------------------------------------------------------------------------- /jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | displayName: 'tiptaptop-extension-indent', 3 | preset: '../../../jest.preset.js', 4 | globals: { 5 | 'ts-jest': { 6 | tsconfig: '/tsconfig.spec.json', 7 | }, 8 | }, 9 | transform: { 10 | '^.+\\.[tj]sx?$': 'ts-jest', 11 | }, 12 | moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx'], 13 | coverageDirectory: '../../../coverage/libs/tiptaptop/extension-indent', 14 | }; 15 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 Evan Payne 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /project.json: -------------------------------------------------------------------------------- 1 | { 2 | "root": "libs/tiptaptop/extension-indent", 3 | "sourceRoot": "libs/tiptaptop/extension-indent/src", 4 | "projectType": "library", 5 | "targets": { 6 | "build": { 7 | "executor": "@nrwl/workspace:tsc", 8 | "outputs": ["{options.outputPath}"], 9 | "options": { 10 | "outputPath": "dist/libs/tiptaptop/extension-indent", 11 | "tsConfig": "libs/tiptaptop/extension-indent/tsconfig.lib.json", 12 | "packageJson": "libs/tiptaptop/extension-indent/package.json", 13 | "main": "libs/tiptaptop/extension-indent/src/index.ts", 14 | "assets": ["libs/tiptaptop/extension-indent/*.md"] 15 | } 16 | }, 17 | "lint": { 18 | "executor": "@nrwl/linter:eslint", 19 | "outputs": ["{options.outputFile}"], 20 | "options": { 21 | "lintFilePatterns": ["libs/tiptaptop/extension-indent/**/*.ts"] 22 | } 23 | }, 24 | "test": { 25 | "executor": "@nrwl/jest:jest", 26 | "outputs": ["coverage/libs/tiptaptop/extension-indent"], 27 | "options": { 28 | "jestConfig": "libs/tiptaptop/extension-indent/jest.config.js", 29 | "passWithNoTests": true 30 | } 31 | } 32 | }, 33 | "tags": ["scope:shared"] 34 | } 35 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # @tiptaptop/extension-indent 2 | 3 | Hi! This is an extension for [tiptap](https://github.com/ueberdosis/tiptap). It adds an indentation command and lets you indent/outdent certain elements. 4 | 5 | Credit for original implementation of this goes to [@sereneinserenade](https://github.com/sereneinserenade), who shared it [here](https://github.com/ueberdosis/tiptap/issues/1036), and [@Leecason](https://github.com/Leecason), [here](https://github.com/Leecason/element-tiptap/blob/b2cb42f683d868ef901f1233ddab591a5aa11824/src/utils/indent.ts) 6 | 7 | By default, `paragraph` and `listItem` types can have indentation applied. The way the are applied is simple, it just adds a `data-indent` property to the element in question. The styling is up to you, but here is a SCSS snippet that I've found useful: 8 | 9 | ```scss 10 | @for $i from 1 through 8 { 11 | [data-indent='#{$i}'] { 12 | $val: $i * 3rem; 13 | padding-left: $val; 14 | } 15 | } 16 | ``` 17 | 18 | -- 19 | 20 | ## Usage 21 | 22 | I'm primarily sharing this repo to get some help, but I will eventually publish an installable version on npm. Until then, you can just copy and paste the indent.ts file into your own project, and import it wherever you use a tiptap editor. For example: 23 | 24 | ```ts 25 | import { Indent } from '...path...to/indent'; 26 | ... 27 | const editor = new Editor({ 28 | extensions: [ 29 | Underline, 30 | Indent.configure({ 31 | types: ['listItem', 'paragraph'], 32 | minLevel: 0, 33 | maxLevel: 8, 34 | }), 35 | ], 36 | }); 37 | ``` 38 | -------------------------------------------------------------------------------- /src/lib/indent.ts: -------------------------------------------------------------------------------- 1 | import { Command, Extension } from '@tiptap/core'; 2 | import { AllSelection, TextSelection, Transaction } from 'prosemirror-state'; 3 | 4 | export interface IndentOptions { 5 | types: string[]; 6 | minLevel: number; 7 | maxLevel: number; 8 | } 9 | 10 | declare module '@tiptap/core' { 11 | interface Commands { 12 | indent: { 13 | indent: () => ReturnType; 14 | outdent: () => ReturnType; 15 | }; 16 | } 17 | } 18 | 19 | export const Indent = Extension.create({ 20 | name: 'indent', 21 | 22 | defaultOptions: { 23 | types: ['listItem', 'paragraph'], 24 | minLevel: 0, 25 | maxLevel: 8, 26 | }, 27 | 28 | addGlobalAttributes() { 29 | return [ 30 | { 31 | types: this.options.types, 32 | attributes: { 33 | indent: { 34 | renderHTML: attributes => { 35 | return attributes?.indent > this.options.minLevel ? { 'data-indent': attributes.indent } : null; 36 | }, 37 | parseHTML: element => { 38 | const level = Number(element.getAttribute('data-indent')); 39 | return level && level > this.options.minLevel ? level : null; 40 | }, 41 | }, 42 | }, 43 | }, 44 | ]; 45 | }, 46 | 47 | addCommands() { 48 | const setNodeIndentMarkup = (tr: Transaction, pos: number, delta: number): Transaction => { 49 | const node = tr?.doc?.nodeAt(pos); 50 | 51 | if (node) { 52 | const nextLevel = (node.attrs.indent || 0) + delta; 53 | const { minLevel, maxLevel } = this.options; 54 | const indent = nextLevel < minLevel ? minLevel : nextLevel > maxLevel ? maxLevel : nextLevel; 55 | 56 | if (indent !== node.attrs.indent) { 57 | const { indent: oldIndent, ...currentAttrs } = node.attrs; 58 | const nodeAttrs = indent > minLevel ? { ...currentAttrs, indent } : currentAttrs; 59 | return tr.setNodeMarkup(pos, node.type, nodeAttrs, node.marks); 60 | } 61 | } 62 | return tr; 63 | }; 64 | 65 | const updateIndentLevel = (tr: Transaction, delta: number): Transaction => { 66 | const { doc, selection } = tr; 67 | 68 | if (doc && selection && (selection instanceof TextSelection || selection instanceof AllSelection)) { 69 | const { from, to } = selection; 70 | doc.nodesBetween(from, to, (node, pos) => { 71 | if (this.options.types.includes(node.type.name)) { 72 | tr = setNodeIndentMarkup(tr, pos, delta); 73 | return false; 74 | } 75 | 76 | return true; 77 | }); 78 | } 79 | 80 | return tr; 81 | }; 82 | const applyIndent: (direction: number) => () => Command = 83 | direction => 84 | () => 85 | ({ tr, state, dispatch }) => { 86 | const { selection } = state; 87 | tr = tr.setSelection(selection); 88 | tr = updateIndentLevel(tr, direction); 89 | 90 | if (tr.docChanged) { 91 | dispatch?.(tr); 92 | return true; 93 | } 94 | 95 | return false; 96 | }; 97 | 98 | return { 99 | indent: applyIndent(1), 100 | outdent: applyIndent(-1), 101 | }; 102 | }, 103 | 104 | addKeyboardShortcuts() { 105 | return { 106 | Tab: () => { 107 | return this.editor.commands.indent(); 108 | }, 109 | 'Shift-Tab': () => { 110 | return this.editor.commands.outdent(); 111 | }, 112 | }; 113 | }, 114 | }); 115 | --------------------------------------------------------------------------------