├── .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 |
7 |
8 |
9 |
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 |
10 | {{ msg }}
11 |
12 |
13 | Recommended IDE setup:
14 | VS Code
15 | +
16 | Volar
17 |
18 |
19 | See README.md for more information.
20 |
21 |
22 |
23 | Vite Docs
24 |
25 | |
26 | Vue 3 Docs
27 |
28 |
29 |
30 |
31 | Edit
32 | components/HelloWorld.vue to test hot module replacement.
33 |
34 |
35 |
36 |
53 |
--------------------------------------------------------------------------------
/src/components/ThemeSwitchButton.vue:
--------------------------------------------------------------------------------
1 |
21 |
22 |
23 |
24 |
25 |
26 |
27 | Toggle color mode
28 |
29 |
30 |
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 |
36 |
37 |
38 |
39 | Create new snippet from current editor content.
40 |
41 |
42 |
43 | Create
44 |
45 |
46 |
47 |
48 |
49 |
56 |
--------------------------------------------------------------------------------
/src/components/SnippetExample.vue:
--------------------------------------------------------------------------------
1 |
31 |
32 |
33 |
34 |
37 |
38 |
39 |
41 |
42 | {{ snippet.name }}
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
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 | [](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 `