├── .github ├── FUNDING.yml └── workflows │ └── build.yaml ├── src ├── components │ ├── extensions │ │ ├── index.ts │ │ └── snippet.ts │ ├── index.ts │ ├── HelloWorld.vue │ ├── ThemeSwitchButton.vue │ ├── Tiptap.vue │ └── SnippetExample.vue ├── assets │ └── logo.png ├── env.d.ts ├── main.ts ├── App.vue └── styles │ └── main.scss ├── .vscode └── extensions.json ├── public └── favicon.ico ├── tsconfig.node.json ├── .gitignore ├── index.html ├── tsconfig.json ├── vite.config.ts ├── package.json ├── components.d.ts ├── LICENSE └── README.md /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | github: sereneinserenade 2 | -------------------------------------------------------------------------------- /src/components/extensions/index.ts: -------------------------------------------------------------------------------- 1 | export * from './snippet' 2 | -------------------------------------------------------------------------------- /.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | "recommendations": ["Vue.volar"] 3 | } 4 | -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sereneinserenade/tiptap-snippets-extension/HEAD/public/favicon.ico -------------------------------------------------------------------------------- /src/assets/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sereneinserenade/tiptap-snippets-extension/HEAD/src/assets/logo.png -------------------------------------------------------------------------------- /src/components/index.ts: -------------------------------------------------------------------------------- 1 | export { default as ThemeSwitchButton } from './ThemeSwitchButton.vue' 2 | export { default as SnippetExample } from './SnippetExample.vue' 3 | -------------------------------------------------------------------------------- /tsconfig.node.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "composite": true, 4 | "module": "esnext", 5 | "moduleResolution": "node" 6 | }, 7 | "include": ["vite.config.ts"] 8 | } 9 | -------------------------------------------------------------------------------- /src/env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | 3 | declare module '*.vue' { 4 | import type { DefineComponent } from 'vue' 5 | // eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/ban-types 6 | const component: DefineComponent<{}, {}, any> 7 | export default component 8 | } 9 | -------------------------------------------------------------------------------- /src/main.ts: -------------------------------------------------------------------------------- 1 | import { createApp } from 'vue' 2 | import App from './App.vue' 3 | 4 | import { Inkline, components } from '@inkline/inkline'; 5 | import '@inkline/inkline/inkline.scss'; 6 | 7 | import "./styles/main.scss"; 8 | 9 | createApp(App).use(Inkline, { 10 | components, 11 | colorMode: 'system', 12 | locale: 'en', 13 | }).mount('#app') 14 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | pnpm-debug.log* 8 | lerna-debug.log* 9 | 10 | node_modules 11 | dist 12 | dist-ssr 13 | *.local 14 | 15 | # Editor directories and files 16 | .vscode/* 17 | !.vscode/extensions.json 18 | .idea 19 | .DS_Store 20 | *.suo 21 | *.ntvs* 22 | *.njsproj 23 | *.sln 24 | *.sw? 25 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Vite App 8 | 9 | 10 |
11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /src/App.vue: -------------------------------------------------------------------------------- 1 | 5 | 6 | 10 | 11 | 21 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "esnext", 4 | "useDefineForClassFields": true, 5 | "module": "esnext", 6 | "moduleResolution": "node", 7 | "strict": true, 8 | "jsx": "preserve", 9 | "sourceMap": true, 10 | "resolveJsonModule": true, 11 | "isolatedModules": true, 12 | "esModuleInterop": true, 13 | "lib": ["esnext", "dom"], 14 | "skipLibCheck": true 15 | }, 16 | "include": ["src/**/*.ts", "src/**/*.d.ts", "src/**/*.tsx", "src/**/*.vue"], 17 | "references": [{ "path": "./tsconfig.node.json" }] 18 | } 19 | -------------------------------------------------------------------------------- /vite.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'vite' 2 | import vue from '@vitejs/plugin-vue' 3 | 4 | import Icons from 'unplugin-icons/vite' 5 | import IconsResolver from 'unplugin-icons/resolver' 6 | import Components from 'unplugin-vue-components/vite' 7 | 8 | export default defineConfig(({ mode }) => { 9 | return { 10 | plugins: [ 11 | vue(), 12 | Components({ 13 | resolvers: [ 14 | IconsResolver({ 15 | prefix: 'icon' 16 | }), 17 | ], 18 | }), 19 | Icons(), 20 | ], 21 | base: mode === 'production' ? '/tiptap-snippets-extension/' : '/' 22 | } 23 | }) 24 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "tiptap-snippets-extension", 3 | "private": true, 4 | "version": "0.0.0", 5 | "scripts": { 6 | "dev": "vite", 7 | "build": "vue-tsc --noEmit && vite build", 8 | "preview": "vite preview" 9 | }, 10 | "dependencies": { 11 | "@inkline/inkline": "^3.0.9", 12 | "@tiptap/starter-kit": "^2.0.0-beta.185", 13 | "@tiptap/vue-3": "^2.0.0-beta.91", 14 | "vue": "^3.2.25" 15 | }, 16 | "devDependencies": { 17 | "@iconify-json/mdi": "^1.1.19", 18 | "@vitejs/plugin-vue": "^2.3.3", 19 | "sass": "^1.52.3", 20 | "typescript": "^4.5.4", 21 | "unplugin-icons": "^0.14.3", 22 | "unplugin-vue-components": "^0.19.6", 23 | "vite": "^2.9.9", 24 | "vue-tsc": "^0.34.7" 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /components.d.ts: -------------------------------------------------------------------------------- 1 | // generated by unplugin-vue-components 2 | // We suggest you to commit this file into source control 3 | // Read more: https://github.com/vuejs/vue-next/pull/3399 4 | import '@vue/runtime-core' 5 | 6 | declare module '@vue/runtime-core' { 7 | export interface GlobalComponents { 8 | HelloWorld: typeof import('./src/components/HelloWorld.vue')['default'] 9 | IconMdiMoonWaningCrescent: typeof import('~icons/mdi/moon-waning-crescent')['default'] 10 | IconMdiWhiteBalanceSunny: typeof import('~icons/mdi/white-balance-sunny')['default'] 11 | SnippetExample: typeof import('./src/components/SnippetExample.vue')['default'] 12 | ThemeSwitchButton: typeof import('./src/components/ThemeSwitchButton.vue')['default'] 13 | Tiptap: typeof import('./src/components/Tiptap.vue')['default'] 14 | } 15 | } 16 | 17 | export {} 18 | -------------------------------------------------------------------------------- /src/styles/main.scss: -------------------------------------------------------------------------------- 1 | .container, .container-fluid { 2 | padding-right: 15px; 3 | padding-left: 15px; 4 | margin-right: auto; 5 | margin-left: auto; 6 | } 7 | 8 | @media (min-width: 768px) { 9 | .container { 10 | width: 750px; 11 | } 12 | } 13 | 14 | @media (min-width: 992px) { 15 | .container { 16 | width: 970px; 17 | } 18 | } 19 | 20 | @media (min-width: 1200px) { 21 | .container { 22 | width: 1170px; 23 | } 24 | } 25 | 26 | .container { 27 | &::before, &::after { 28 | display: table; 29 | content: " "; 30 | } 31 | } 32 | 33 | .container-fluid { 34 | &::before, &::after { 35 | display: table; 36 | content: " "; 37 | } 38 | } 39 | 40 | .container:after, .container-fluid:after { 41 | clear: both; 42 | } 43 | 44 | .flex { 45 | display: flex; 46 | } 47 | 48 | .gap-1rem { 49 | gap: 1rem; 50 | } 51 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 Jeet Mandaliya 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 | -------------------------------------------------------------------------------- /.github/workflows/build.yaml: -------------------------------------------------------------------------------- 1 | name: Shipp it baby 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | 8 | jobs: 9 | build-and-deploy: 10 | concurrency: ci-${{ github.ref }} # Recommended if you intend to make multiple deployments in quick succession. 11 | runs-on: ubuntu-latest 12 | steps: 13 | - name: Checkout 🛎️ 14 | uses: actions/checkout@v3 15 | 16 | - name: Setup Node 16 17 | uses: actions/setup-node@v3 18 | with: 19 | node-version: '16' 20 | 21 | - name: Gimme node_modules Cache 22 | uses: actions/cache@v3 23 | id: gimme_cache 24 | with: 25 | path: '**/node_modules' 26 | key: ${{ runner.os }}-modules-${{ hashFiles('**/package-lock.json') }} 27 | 28 | - name: Install dependencies 29 | if: steps.gimme_cache.outputs.cache-hit == false 30 | run: npm install 31 | 32 | - name: Build 🔧 33 | run: npm run build 34 | 35 | - name: Deploy 🚀 36 | uses: JamesIves/github-pages-deploy-action@v4.3.3 37 | with: 38 | branch: gh-pages # The branch the action should deploy to. 39 | folder: dist # The folder the action should deploy. 40 | -------------------------------------------------------------------------------- /src/components/HelloWorld.vue: -------------------------------------------------------------------------------- 1 | 8 | 9 | 35 | 36 | 53 | -------------------------------------------------------------------------------- /src/components/ThemeSwitchButton.vue: -------------------------------------------------------------------------------- 1 | 21 | 22 | 31 | 32 | 40 | -------------------------------------------------------------------------------- /src/components/extensions/snippet.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable import/no-extraneous-dependencies */ 2 | import { Extension } from '@tiptap/core' 3 | import { Plugin } from 'prosemirror-state' 4 | import { DOMParser } from 'prosemirror-model' 5 | 6 | function wrapHtmlInTemplate(value: string): HTMLSpanElement { 7 | const element = document.createElement('span') 8 | element.innerHTML = `${value.trim()}` 9 | return element 10 | } 11 | 12 | export const SnippetExtension = Extension.create({ 13 | name: 'snippet', 14 | addProseMirrorPlugins() { 15 | return [ 16 | new Plugin({ 17 | props: { 18 | handleDrop(view, event: DragEvent | any): boolean { 19 | if (!event) return false 20 | 21 | event.preventDefault() 22 | 23 | const coordinates = view.posAtCoords({ 24 | left: event.clientX, 25 | top: event.clientY, 26 | }) 27 | 28 | const snippetContent = event.dataTransfer.getData('snippet') 29 | 30 | const parsedContent = DOMParser 31 | .fromSchema(view.state.schema) 32 | .parseSlice(wrapHtmlInTemplate(snippetContent)) 33 | 34 | if (coordinates) { 35 | const dropTransaction = view.state.tr.insert(coordinates.pos, parsedContent.content) 36 | 37 | dropTransaction.setMeta('isSnippetDropTransaction', true) 38 | 39 | view.dispatch(dropTransaction) 40 | } 41 | 42 | return false 43 | }, 44 | }, 45 | }), 46 | ] 47 | }, 48 | }) 49 | -------------------------------------------------------------------------------- /src/components/Tiptap.vue: -------------------------------------------------------------------------------- 1 | 34 | 35 | 48 | 49 | 56 | -------------------------------------------------------------------------------- /src/components/SnippetExample.vue: -------------------------------------------------------------------------------- 1 | 31 | 32 | 51 | 52 | 80 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # tiptap-snippets-extension 2 | 3 | [Tiptap 2 Extension](https://tiptap.dev) for adding snippets. 4 | 5 | A ⭐️ to the repo if you 👍 / ❤️ what I'm doing would be much appreciated. If you're using this extension and making money from it, it'd be very kind of you to [:heart: Sponsor me](https://github.com/sponsors/sereneinserenade). If you're looking for a **dev to work you on your project's Rich Text Editor** with or as **a frontend developer, [DM me on Discord/Twitter/LinkedIn](https://github.com/sereneinserenade)👨‍💻🤩**. 6 | 7 | I've made a bunch of extensions for Tiptap 2, some of them are **Google Docs like Commenting**, **Search and Replace**, **LanguageTool integration** with tiptap. You can check it our here https://github.com/sereneinserenade#a-glance-of-my-projects. 8 | 9 | # Live Demo 10 | 11 | Try it out live at https://sereneinserenade.github.io/tiptap-snippets-extension, and/or take a look at a demo-video below. 12 | 13 | https://user-images.githubusercontent.com/45892659/173204796-0743a2c9-4465-46a2-bf57-8f1696d4353f.mov 14 | 15 | 16 | ## How to use 17 | 18 | Copy-paste [`snippet.ts`](src/components/extensions/snippet.ts) file in your own repo and import `SnippetExtension` from that file and use that as an extension. For more details see [`Tiptap.vue`](src/components/Tiptap.vue) or the example implementations below. If you have any question, feel free to [open an issue](https://github.com/sereneinserenade/tiptap-snippets-extension/issues). 19 | 20 | ## Contributing 21 | 22 | Show your ❤️ by ⭐️ing this repository! It means a lot. 23 | 24 | Clone the repo, do something, make a PR(or not). You know what's the drill. Looking forward to your PRs, you amazing devs. 25 | 26 | ## Awesome peeps, who've starred this repo 🚀! Thank you! 27 | [![Stargazers repo roster for @sereneinserenade/tiptap-snippets-extension](https://reporoster.com/stars/dark/sereneinserenade/tiptap-snippets-extension)](https://github.com/sereneinserenade/tiptap-snippets-extension/stargazers) 28 | 29 | 30 | --- 31 | 32 |
33 | Project Setup 34 | 35 | # Vue 3 + TypeScript + Vite 36 | 37 | This template should help get you started developing with Vue 3 and TypeScript in Vite. The template uses Vue 3 `