tabs to spaces whitespace

This commit is contained in:
Philipp Kühn
2018-11-08 22:03:10 +01:00
parent b8b82220ba
commit f04a6be6c1
114 changed files with 4214 additions and 4214 deletions

View File

@@ -1,7 +1,7 @@
<template>
<a class="ad" href="https://scrumpy.io/" target="_blank">
<img class="ad__image" src="https://drop.philipp-kuehn.com/api98lPyuw.png" alt="Scrumpy. Agile Planning, Made Simple. Get Started." />
</a>
<a class="ad" href="https://scrumpy.io/" target="_blank">
<img class="ad__image" src="https://drop.philipp-kuehn.com/api98lPyuw.png" alt="Scrumpy. Agile Planning, Made Simple. Get Started." />
</a>
</template>
<style lang="scss" src="./style.scss" scoped></style>

View File

@@ -1,42 +1,42 @@
@import "~variables";
.ad {
display: block;
padding: 1rem;
transition: 0.2s transform;
margin: 3rem auto 0 auto;
width: 15rem;
display: block;
padding: 1rem;
transition: 0.2s transform;
margin: 3rem auto 0 auto;
width: 15rem;
@media (min-width: 1020px) {
position: fixed;
left: 0;
bottom: 0;
margin-top: 0;
}
@media (min-width: 1020px) {
position: fixed;
left: 0;
bottom: 0;
margin-top: 0;
}
&__image {
display: block;
width: 100%;
height: auto;
border-radius: 5px;
overflow: hidden;
transition: 0.2s box-shadow;
box-shadow:
0 2px 4px 0 rgba(black, 0.05),
0 2px 10px 0 rgba(black, 0.07)
;
}
&__image {
display: block;
width: 100%;
height: auto;
border-radius: 5px;
overflow: hidden;
transition: 0.2s box-shadow;
box-shadow:
0 2px 4px 0 rgba(black, 0.05),
0 2px 10px 0 rgba(black, 0.07)
;
}
&:hover {
transform: translateY(-5px);
}
&:hover {
transform: translateY(-5px);
}
&:hover &__image {
box-shadow:
0 2px 1px 0 rgba(black, 0.07),
0 5px 20px 0 rgba(black, 0.06),
0 8px 40px 0 rgba(black, 0.04)
;
}
&:hover &__image {
box-shadow:
0 2px 1px 0 rgba(black, 0.07),
0 5px 20px 0 rgba(black, 0.06),
0 8px 40px 0 rgba(black, 0.04)
;
}
}
}

View File

@@ -2,8 +2,8 @@
.page {
&__content {
padding: 4rem 1rem;
&__content {
padding: 4rem 1rem;
}
&__footer {
@@ -212,4 +212,4 @@ li[data-done="true"] .todo-checkbox {
li[data-done="false"] {
text-decoration: none;
}
}

View File

@@ -1,17 +1,17 @@
<template>
<div class="hero">
<div class="hero__inner">
<svg class="hero__logo" xmlns="http://www.w3.org/2000/svg" width="150" height="147" viewBox="0 0 150 147">
<path fill-rule="evenodd" d="M26.078305,62.3613338 C25.7098682,65.5071869 26.7651499,68.7890473 29.2305323,71.1698395 C31.8780402,73.7265081 35.5095868,74.6264505 38.835659,73.8909922 C40.1685862,69.1852287 42.5994079,65.0738098 46.1185082,61.5792271 C46.7063387,60.995492 47.6560804,60.9988119 48.2398155,61.5866425 C48.8235507,62.1744731 48.8202307,63.1242147 48.2324001,63.7079499 C45.1597213,66.7592214 43.0224884,70.3159362 41.8118122,74.3988864 C42.3036563,76.1343979 43.2448226,77.7695377 44.6376666,79.1145915 C48.8244385,83.1577101 55.4712308,83.06028 59.4841755,78.9047541 C61.5413401,76.7744978 62.5014774,73.9953585 62.3942536,71.2471292 C64.9641607,68.2381852 67.396202,63.4176619 68.4179079,58.0024937 C80.3874868,49.1562654 93.8426151,32.568523 104.371445,5.59032072 C93.9174954,41.5317695 86.0252453,64.5167382 80.6946894,74.5452392 C78.8496917,78.0162771 77.2336561,81.3879265 75.8950435,84.6284349 C70.1999494,87.1232884 65.6357246,91.845809 62.9452121,95.9367699 C59.846433,96.4572059 57.0236331,98.34297 55.4397322,101.321854 C52.7260669,106.425517 54.6851579,112.773997 59.824176,115.506462 C61.0978472,116.183685 62.4470058,116.571386 63.7975953,116.696179 C64.912262,112.255705 67.1815584,108.483174 70.5954127,105.413494 C71.2114278,104.859583 72.15984,104.909929 72.7137505,105.525944 C73.267661,106.141959 73.2173152,107.090371 72.6013001,107.644282 C68.8800506,110.990367 66.7377922,115.262277 66.1547346,120.528599 C66.6676865,123.65414 68.5866912,126.517989 71.6128022,128.127001 C74.862469,129.854879 78.6014006,129.718966 81.5959091,128.095201 C81.5801274,123.204349 82.7835226,118.582195 85.2030509,114.25301 C85.6072107,113.52986 86.5210761,113.271267 87.2442264,113.675427 C87.9673767,114.079586 88.2259696,114.993452 87.8218099,115.716602 C85.7092106,119.496609 84.635134,123.504634 84.5967664,127.763114 C85.5479283,129.295814 86.9033354,130.608181 88.6129606,131.517205 C93.7519787,134.249669 100.11443,132.323909 102.826502,127.223244 C104.216797,124.608478 104.373706,121.672349 103.513121,119.060136 C104.520365,116.84997 105.289548,113.998031 105.615371,110.855954 C119.212334,100.723957 134.689881,80.6105128 145.607087,46.648385 C148.450672,54.5670114 150,63.1025732 150,72 C150,113.421356 116.421356,147 75,147 C33.5786438,147 0,113.421356 0,72 C0,38.4830506 21.9858616,10.1011743 52.3224198,0.48953606 C48.9402683,10.0685574 46.0643582,17.0871252 43.6946894,21.5452392 C41.7000425,25.2978155 39.9730067,28.9342266 38.5748172,32.4143498 C35.1216793,33.8554208 32.0687555,35.8637935 29.77154,37.837492 C26.6493508,37.4836279 23.4161144,38.5182716 21.0724792,40.9451768 C17.0571768,45.1031442 17.1904975,51.7456943 21.3772695,55.7888129 C22.4149324,56.7908723 23.6049613,57.5354331 24.8688331,58.0276644 C27.16428,54.0664499 30.3855184,51.0655629 34.5132451,49.0557819 C35.2580753,48.6931257 36.1558704,49.0029389 36.5185265,49.7477691 C36.8811827,50.4925993 36.5713695,51.3903944 35.8265393,51.7530505 C31.3271397,53.9437983 28.0903713,57.4597339 26.078305,62.3613338 Z"/>
</svg>
<h1>
tiptap a renderless rich-text editor for Vue.js
</h1>
<p>
This editor is based on <a href="https://prosemirror.net">Prosemirror</a>, <em>fully extendable</em> and renderless. You can easily add custom nodes as <strong>Vue components</strong>.
</p>
</div>
</div>
<div class="hero">
<div class="hero__inner">
<svg class="hero__logo" xmlns="http://www.w3.org/2000/svg" width="150" height="147" viewBox="0 0 150 147">
<path fill-rule="evenodd" d="M26.078305,62.3613338 C25.7098682,65.5071869 26.7651499,68.7890473 29.2305323,71.1698395 C31.8780402,73.7265081 35.5095868,74.6264505 38.835659,73.8909922 C40.1685862,69.1852287 42.5994079,65.0738098 46.1185082,61.5792271 C46.7063387,60.995492 47.6560804,60.9988119 48.2398155,61.5866425 C48.8235507,62.1744731 48.8202307,63.1242147 48.2324001,63.7079499 C45.1597213,66.7592214 43.0224884,70.3159362 41.8118122,74.3988864 C42.3036563,76.1343979 43.2448226,77.7695377 44.6376666,79.1145915 C48.8244385,83.1577101 55.4712308,83.06028 59.4841755,78.9047541 C61.5413401,76.7744978 62.5014774,73.9953585 62.3942536,71.2471292 C64.9641607,68.2381852 67.396202,63.4176619 68.4179079,58.0024937 C80.3874868,49.1562654 93.8426151,32.568523 104.371445,5.59032072 C93.9174954,41.5317695 86.0252453,64.5167382 80.6946894,74.5452392 C78.8496917,78.0162771 77.2336561,81.3879265 75.8950435,84.6284349 C70.1999494,87.1232884 65.6357246,91.845809 62.9452121,95.9367699 C59.846433,96.4572059 57.0236331,98.34297 55.4397322,101.321854 C52.7260669,106.425517 54.6851579,112.773997 59.824176,115.506462 C61.0978472,116.183685 62.4470058,116.571386 63.7975953,116.696179 C64.912262,112.255705 67.1815584,108.483174 70.5954127,105.413494 C71.2114278,104.859583 72.15984,104.909929 72.7137505,105.525944 C73.267661,106.141959 73.2173152,107.090371 72.6013001,107.644282 C68.8800506,110.990367 66.7377922,115.262277 66.1547346,120.528599 C66.6676865,123.65414 68.5866912,126.517989 71.6128022,128.127001 C74.862469,129.854879 78.6014006,129.718966 81.5959091,128.095201 C81.5801274,123.204349 82.7835226,118.582195 85.2030509,114.25301 C85.6072107,113.52986 86.5210761,113.271267 87.2442264,113.675427 C87.9673767,114.079586 88.2259696,114.993452 87.8218099,115.716602 C85.7092106,119.496609 84.635134,123.504634 84.5967664,127.763114 C85.5479283,129.295814 86.9033354,130.608181 88.6129606,131.517205 C93.7519787,134.249669 100.11443,132.323909 102.826502,127.223244 C104.216797,124.608478 104.373706,121.672349 103.513121,119.060136 C104.520365,116.84997 105.289548,113.998031 105.615371,110.855954 C119.212334,100.723957 134.689881,80.6105128 145.607087,46.648385 C148.450672,54.5670114 150,63.1025732 150,72 C150,113.421356 116.421356,147 75,147 C33.5786438,147 0,113.421356 0,72 C0,38.4830506 21.9858616,10.1011743 52.3224198,0.48953606 C48.9402683,10.0685574 46.0643582,17.0871252 43.6946894,21.5452392 C41.7000425,25.2978155 39.9730067,28.9342266 38.5748172,32.4143498 C35.1216793,33.8554208 32.0687555,35.8637935 29.77154,37.837492 C26.6493508,37.4836279 23.4161144,38.5182716 21.0724792,40.9451768 C17.0571768,45.1031442 17.1904975,51.7456943 21.3772695,55.7888129 C22.4149324,56.7908723 23.6049613,57.5354331 24.8688331,58.0276644 C27.16428,54.0664499 30.3855184,51.0655629 34.5132451,49.0557819 C35.2580753,48.6931257 36.1558704,49.0029389 36.5185265,49.7477691 C36.8811827,50.4925993 36.5713695,51.3903944 35.8265393,51.7530505 C31.3271397,53.9437983 28.0903713,57.4597339 26.078305,62.3613338 Z"/>
</svg>
<h1>
tiptap a renderless rich-text editor for Vue.js
</h1>
<p>
This editor is based on <a href="https://prosemirror.net">Prosemirror</a>, <em>fully extendable</em> and renderless. You can easily add custom nodes as <strong>Vue components</strong>.
</p>
</div>
</div>
</template>
<style lang="scss" src="./style.scss" scoped></style>

View File

@@ -2,23 +2,23 @@
.hero {
background-color: $color-black;
color: $color-white;
text-align: center;
padding: 3rem 1rem;
background-color: $color-black;
color: $color-white;
text-align: center;
padding: 3rem 1rem;
&__inner {
margin: 0 auto;
max-width: 30rem;
}
&__inner {
margin: 0 auto;
max-width: 30rem;
}
&__logo {
width: 4rem;
height: 4rem;
&__logo {
width: 4rem;
height: 4rem;
path {
fill: $color-white;
}
}
path {
fill: $color-white;
}
}
}
}

View File

@@ -1,57 +1,57 @@
<template>
<div class="icon" :class="[`icon--${name}`, `icon--${size}`, { 'has-align-fix': fixAlign }]">
<svg class="icon__svg">
<use xmlns:xlink="http://www.w3.org/1999/xlink" :xlink:href="'#icon--' + name"></use>
</svg>
</div>
<div class="icon" :class="[`icon--${name}`, `icon--${size}`, { 'has-align-fix': fixAlign }]">
<svg class="icon__svg">
<use xmlns:xlink="http://www.w3.org/1999/xlink" :xlink:href="'#icon--' + name"></use>
</svg>
</div>
</template>
<script>
export default {
props: {
name: {},
size: {
default: 'normal',
},
modifier: {
default: null,
},
fixAlign: {
default: true,
},
},
props: {
name: {},
size: {
default: 'normal',
},
modifier: {
default: null,
},
fixAlign: {
default: true,
},
},
}
</script>
<style lang="scss" scoped>
.icon {
position: relative;
display: inline-block;
vertical-align: middle;
width: 0.8rem;
height: 0.8rem;
margin: 0 .3rem;
top: -.05rem;
fill: currentColor;
position: relative;
display: inline-block;
vertical-align: middle;
width: 0.8rem;
height: 0.8rem;
margin: 0 .3rem;
top: -.05rem;
fill: currentColor;
// &.has-align-fix {
// top: -.1rem;
// }
// &.has-align-fix {
// top: -.1rem;
// }
&__svg {
display: inline-block;
vertical-align: top;
width: 100%;
height: 100%;
}
&__svg {
display: inline-block;
vertical-align: top;
width: 100%;
height: 100%;
}
&:first-child {
margin-left: 0;
}
&:first-child {
margin-left: 0;
}
&:last-child {
margin-right: 0;
}
&:last-child {
margin-right: 0;
}
}
@@ -59,16 +59,16 @@ export default {
body > svg,
.icon use > svg,
symbol {
path,
rect,
circle,
g {
fill: currentColor;
stroke: none;
}
path,
rect,
circle,
g {
fill: currentColor;
stroke: none;
}
*[d="M0 0h24v24H0z"] {
display: none;
}
*[d="M0 0h24v24H0z"] {
display: none;
}
}
</style>

View File

@@ -1,30 +1,30 @@
<template>
<div class="navigation">
<div class="navigation">
<h1 class="navigation__logo">
tiptap <span class="navigation__beta">beta</span>
</h1>
<h1 class="navigation__logo">
tiptap <span class="navigation__beta">beta</span>
</h1>
<div>
<a class="navigation__link" href="https://github.com/heyscrumpy/tiptap/blob/master/CONTRIBUTING.md" target="_blank">
Contribute
</a>
<a class="navigation__github-link" href="https://github.com/heyscrumpy/tiptap" target="_blank">
<icon class="navigation__icon" name="github" />
</a>
</div>
<div>
<a class="navigation__link" href="https://github.com/heyscrumpy/tiptap/blob/master/CONTRIBUTING.md" target="_blank">
Contribute
</a>
<a class="navigation__github-link" href="https://github.com/heyscrumpy/tiptap" target="_blank">
<icon class="navigation__icon" name="github" />
</a>
</div>
</div>
</div>
</template>
<script>
import Icon from 'Components/Icon'
export default {
components: {
Icon,
},
components: {
Icon,
},
}
</script>
<style lang="scss" src="./style.scss" scoped></style>
<style lang="scss" src="./style.scss" scoped></style>

View File

@@ -2,54 +2,54 @@
.navigation {
display: flex;
justify-content: space-between;
align-items: center;
padding: 0.75rem;
background-color: $color-black;
color: $color-white;
display: flex;
justify-content: space-between;
align-items: center;
padding: 0.75rem;
background-color: $color-black;
color: $color-white;
&__logo {
font-size: 1.1rem;
font-weight: bold;
margin: 0;
}
&__logo {
font-size: 1.1rem;
font-weight: bold;
margin: 0;
}
&__icon {
width: 1.5rem;
height: 1.5rem;
}
&__icon {
width: 1.5rem;
height: 1.5rem;
}
&__beta {
display: inline-block;
vertical-align: middle;
background-color: $color-white;
color: $color-black;
font-size: 0.6rem;
font-weight: bold;
text-transform: uppercase;
letter-spacing: 0.05rem;
padding: 0.1rem 0.2rem;
border-radius: 5px;
}
&__beta {
display: inline-block;
vertical-align: middle;
background-color: $color-white;
color: $color-black;
font-size: 0.6rem;
font-weight: bold;
text-transform: uppercase;
letter-spacing: 0.05rem;
padding: 0.1rem 0.2rem;
border-radius: 5px;
}
&__link {
display: inline-block;
color: rgba($color-white, 0.5);
text-decoration: none;
font-weight: bold;
font-size: 0.9rem;
padding: 0.1rem 0.5rem;
border-radius: 3px;
&__link {
display: inline-block;
color: rgba($color-white, 0.5);
text-decoration: none;
font-weight: bold;
font-size: 0.9rem;
padding: 0.1rem 0.5rem;
border-radius: 3px;
&:hover {
color: $color-white;
&:hover {
color: $color-white;
background-color: rgba($color-white, 0.1);
}
}
}
&__github-link {
margin-left: 0.5rem;
}
&__github-link {
margin-left: 0.5rem;
}
}
}

View File

@@ -1,209 +1,209 @@
<template>
<div class="editor">
<menu-bar :editor="editor">
<div class="menubar" slot-scope="{ commands, isActive }">
<div class="editor">
<menu-bar :editor="editor">
<div class="menubar" slot-scope="{ commands, isActive }">
<button
class="menubar__button"
:class="{ 'is-active': isActive('bold') }"
@click="commands.bold"
>
<icon name="bold" />
</button>
<button
class="menubar__button"
:class="{ 'is-active': isActive('bold') }"
@click="commands.bold"
>
<icon name="bold" />
</button>
<button
class="menubar__button"
:class="{ 'is-active': isActive('italic') }"
@click="commands.italic"
>
<icon name="italic" />
</button>
<button
class="menubar__button"
:class="{ 'is-active': isActive('italic') }"
@click="commands.italic"
>
<icon name="italic" />
</button>
<button
class="menubar__button"
:class="{ 'is-active': isActive('strike') }"
@click="commands.strike"
>
<icon name="strike" />
</button>
<button
class="menubar__button"
:class="{ 'is-active': isActive('strike') }"
@click="commands.strike"
>
<icon name="strike" />
</button>
<button
class="menubar__button"
:class="{ 'is-active': isActive('underline') }"
@click="commands.underline"
>
<icon name="underline" />
</button>
<button
class="menubar__button"
:class="{ 'is-active': isActive('underline') }"
@click="commands.underline"
>
<icon name="underline" />
</button>
<button
class="menubar__button"
:class="{ 'is-active': isActive('code') }"
@click="commands.code"
>
<icon name="code" />
</button>
<button
class="menubar__button"
:class="{ 'is-active': isActive('code') }"
@click="commands.code"
>
<icon name="code" />
</button>
<button
class="menubar__button"
:class="{ 'is-active': isActive('paragraph') }"
@click="commands.paragraph"
>
<icon name="paragraph" />
</button>
<button
class="menubar__button"
:class="{ 'is-active': isActive('paragraph') }"
@click="commands.paragraph"
>
<icon name="paragraph" />
</button>
<button
class="menubar__button"
:class="{ 'is-active': isActive('heading', { level: 1 }) }"
@click="commands.heading({ level: 1 })"
>
H1
</button>
<button
class="menubar__button"
:class="{ 'is-active': isActive('heading', { level: 1 }) }"
@click="commands.heading({ level: 1 })"
>
H1
</button>
<button
class="menubar__button"
:class="{ 'is-active': isActive('heading', { level: 2 }) }"
@click="commands.heading({ level: 2 })"
>
H2
</button>
<button
class="menubar__button"
:class="{ 'is-active': isActive('heading', { level: 2 }) }"
@click="commands.heading({ level: 2 })"
>
H2
</button>
<button
class="menubar__button"
:class="{ 'is-active': isActive('heading', { level: 3 }) }"
@click="commands.heading({ level: 3 })"
>
H3
</button>
<button
class="menubar__button"
:class="{ 'is-active': isActive('heading', { level: 3 }) }"
@click="commands.heading({ level: 3 })"
>
H3
</button>
<button
class="menubar__button"
:class="{ 'is-active': isActive('bullet_list') }"
@click="commands.bullet_list"
>
<icon name="ul" />
</button>
<button
class="menubar__button"
:class="{ 'is-active': isActive('bullet_list') }"
@click="commands.bullet_list"
>
<icon name="ul" />
</button>
<button
class="menubar__button"
:class="{ 'is-active': isActive('ordered_list') }"
@click="commands.ordered_list"
>
<icon name="ol" />
</button>
<button
class="menubar__button"
:class="{ 'is-active': isActive('ordered_list') }"
@click="commands.ordered_list"
>
<icon name="ol" />
</button>
<button
class="menubar__button"
:class="{ 'is-active': isActive('blockquote') }"
@click="commands.blockquote"
>
<icon name="quote" />
</button>
<button
class="menubar__button"
:class="{ 'is-active': isActive('blockquote') }"
@click="commands.blockquote"
>
<icon name="quote" />
</button>
<button
class="menubar__button"
:class="{ 'is-active': isActive('code_block') }"
@click="commands.code_block"
>
<icon name="code" />
</button>
<button
class="menubar__button"
:class="{ 'is-active': isActive('code_block') }"
@click="commands.code_block"
>
<icon name="code" />
</button>
<button
class="menubar__button"
@click="commands.undo"
>
<icon name="undo" />
</button>
<button
class="menubar__button"
@click="commands.undo"
>
<icon name="undo" />
</button>
<button
class="menubar__button"
@click="commands.redo"
>
<icon name="redo" />
</button>
<button
class="menubar__button"
@click="commands.redo"
>
<icon name="redo" />
</button>
</div>
</menu-bar>
</div>
</menu-bar>
<editor-content class="editor__content" :editor="editor" />
</div>
<editor-content class="editor__content" :editor="editor" />
</div>
</template>
<script>
import Icon from 'Components/Icon'
import { Editor, EditorContent, MenuBar } from 'tiptap'
import {
Blockquote,
CodeBlock,
HardBreak,
Heading,
OrderedList,
BulletList,
ListItem,
TodoItem,
TodoList,
Bold,
Code,
Italic,
Link,
Strike,
Underline,
History,
Blockquote,
CodeBlock,
HardBreak,
Heading,
OrderedList,
BulletList,
ListItem,
TodoItem,
TodoList,
Bold,
Code,
Italic,
Link,
Strike,
Underline,
History,
} from 'tiptap-extensions'
export default {
components: {
EditorContent,
MenuBar,
Icon,
},
data() {
return {
editor: new Editor({
extensions: [
new Blockquote(),
new BulletList(),
new CodeBlock(),
new HardBreak(),
new Heading({ levels: [1, 2, 3] }),
new ListItem(),
new OrderedList(),
new TodoItem(),
new TodoList(),
new Bold(),
new Code(),
new Italic(),
new Link(),
new Strike(),
new Underline(),
new History(),
],
content: `
<h2>
Hi there,
</h2>
<p>
this is a very <em>basic</em> example of tiptap.
</p>
<pre><code>body { display: none; }</code></pre>
<ul>
<li>
A regular list
</li>
<li>
With regular items
</li>
</ul>
<blockquote>
It's amazing 👏
<br />
mom
</blockquote>
`,
}),
}
},
beforeDestroy() {
this.editor.destroy()
},
components: {
EditorContent,
MenuBar,
Icon,
},
data() {
return {
editor: new Editor({
extensions: [
new Blockquote(),
new BulletList(),
new CodeBlock(),
new HardBreak(),
new Heading({ levels: [1, 2, 3] }),
new ListItem(),
new OrderedList(),
new TodoItem(),
new TodoList(),
new Bold(),
new Code(),
new Italic(),
new Link(),
new Strike(),
new Underline(),
new History(),
],
content: `
<h2>
Hi there,
</h2>
<p>
this is a very <em>basic</em> example of tiptap.
</p>
<pre><code>body { display: none; }</code></pre>
<ul>
<li>
A regular list
</li>
<li>
With regular items
</li>
</ul>
<blockquote>
It's amazing 👏
<br />
mom
</blockquote>
`,
}),
}
},
beforeDestroy() {
this.editor.destroy()
},
}
</script>
</script>

View File

@@ -1,136 +1,136 @@
<template>
<div class="editor">
<editor-content class="editor__content" :editor="editor" />
</div>
<div class="editor">
<editor-content class="editor__content" :editor="editor" />
</div>
</template>
<script>
import { Editor, EditorContent } from 'tiptap'
import {
CodeBlockHighlight,
HardBreak,
Heading,
Bold,
Code,
Italic,
CodeBlockHighlight,
HardBreak,
Heading,
Bold,
Code,
Italic,
} from 'tiptap-extensions'
import javascript from 'highlight.js/lib/languages/javascript'
import css from 'highlight.js/lib/languages/css'
import {
JavaScriptExample,
CSSExample,
ExplicitImportExample,
JavaScriptExample,
CSSExample,
ExplicitImportExample,
} from './examples'
export default {
components: {
EditorContent,
},
data() {
return {
editor: new Editor({
extensions: [
new CodeBlockHighlight({
languages: {
javascript,
css,
},
}),
new HardBreak(),
new Heading({ levels: [1, 2, 3] }),
new Bold(),
new Code(),
new Italic(),
],
content: `
<h2>
Code Highlighting
</h2>
<p>
These are code blocks with <strong>automatic syntax highlighting</strong> based on highlight.js.
</p>
<pre><code>${JavaScriptExample}</code></pre>
<pre><code>${CSSExample}</code></pre>
components: {
EditorContent,
},
data() {
return {
editor: new Editor({
extensions: [
new CodeBlockHighlight({
languages: {
javascript,
css,
},
}),
new HardBreak(),
new Heading({ levels: [1, 2, 3] }),
new Bold(),
new Code(),
new Italic(),
],
content: `
<h2>
Code Highlighting
</h2>
<p>
These are code blocks with <strong>automatic syntax highlighting</strong> based on highlight.js.
</p>
<pre><code>${JavaScriptExample}</code></pre>
<pre><code>${CSSExample}</code></pre>
<p>
Note: tiptap doesn't import syntax highlighting language definitions from highlight.js. You
<strong>must</strong> import them and initialize the extension with all languages you want to support:
</p>
<pre><code>${ExplicitImportExample}</code></pre>
`,
}),
}
},
<p>
Note: tiptap doesn't import syntax highlighting language definitions from highlight.js. You
<strong>must</strong> import them and initialize the extension with all languages you want to support:
</p>
<pre><code>${ExplicitImportExample}</code></pre>
`,
}),
}
},
}
</script>
<style lang="scss">
pre {
&::before {
content: attr(data-language);
text-transform: uppercase;
display: block;
text-align: right;
font-weight: bold;
font-size: 0.6rem;
}
&::before {
content: attr(data-language);
text-transform: uppercase;
display: block;
text-align: right;
font-weight: bold;
font-size: 0.6rem;
}
code {
code {
.hljs-comment,
.hljs-quote {
color: #999999;
}
.hljs-comment,
.hljs-quote {
color: #999999;
}
.hljs-variable,
.hljs-template-variable,
.hljs-attribute,
.hljs-tag,
.hljs-name,
.hljs-regexp,
.hljs-link,
.hljs-name,
.hljs-selector-id,
.hljs-selector-class {
color: #f2777a;
}
.hljs-variable,
.hljs-template-variable,
.hljs-attribute,
.hljs-tag,
.hljs-name,
.hljs-regexp,
.hljs-link,
.hljs-name,
.hljs-selector-id,
.hljs-selector-class {
color: #f2777a;
}
.hljs-number,
.hljs-meta,
.hljs-built_in,
.hljs-builtin-name,
.hljs-literal,
.hljs-type,
.hljs-params {
color: #f99157;
}
.hljs-number,
.hljs-meta,
.hljs-built_in,
.hljs-builtin-name,
.hljs-literal,
.hljs-type,
.hljs-params {
color: #f99157;
}
.hljs-string,
.hljs-symbol,
.hljs-bullet {
color: #99cc99;
}
.hljs-string,
.hljs-symbol,
.hljs-bullet {
color: #99cc99;
}
.hljs-title,
.hljs-section {
color: #ffcc66;
}
.hljs-title,
.hljs-section {
color: #ffcc66;
}
.hljs-keyword,
.hljs-selector-tag {
color: #6699cc;
}
.hljs-keyword,
.hljs-selector-tag {
color: #6699cc;
}
.hljs-emphasis {
font-style: italic;
}
.hljs-emphasis {
font-style: italic;
}
.hljs-strong {
font-weight: 700;
}
}
.hljs-strong {
font-weight: 700;
}
}
}
</style>

View File

@@ -2,57 +2,57 @@ import { Node } from 'tiptap'
export default class Iframe extends Node {
get name() {
return 'iframe'
}
get name() {
return 'iframe'
}
get schema() {
return {
attrs: {
src: {
default: null,
},
},
group: 'block',
selectable: false,
parseDOM: [{
tag: 'iframe',
getAttrs: dom => ({
src: dom.getAttribute('src'),
}),
}],
toDOM: node => ['iframe', {
src: node.attrs.src,
frameborder: 0,
allowfullscreen: 'true',
}],
}
}
get schema() {
return {
attrs: {
src: {
default: null,
},
},
group: 'block',
selectable: false,
parseDOM: [{
tag: 'iframe',
getAttrs: dom => ({
src: dom.getAttribute('src'),
}),
}],
toDOM: node => ['iframe', {
src: node.attrs.src,
frameborder: 0,
allowfullscreen: 'true',
}],
}
}
get view() {
return {
props: ['node', 'updateAttrs', 'editable'],
data() {
return {
url: this.node.attrs.src,
}
},
methods: {
onChange(event) {
this.url = event.target.value
get view() {
return {
props: ['node', 'updateAttrs', 'editable'],
data() {
return {
url: this.node.attrs.src,
}
},
methods: {
onChange(event) {
this.url = event.target.value
this.updateAttrs({
src: this.url,
})
},
},
template: `
<div class="iframe">
<iframe class="iframe__embed" :src="url"></iframe>
<input class="iframe__input" type="text" :value="url" @input="onChange" v-if="editable" />
</div>
`,
}
}
this.updateAttrs({
src: this.url,
})
},
},
template: `
<div class="iframe">
<iframe class="iframe__embed" :src="url"></iframe>
<input class="iframe__input" type="text" :value="url" @input="onChange" v-if="editable" />
</div>
`,
}
}
}

View File

@@ -1,51 +1,51 @@
<template>
<div class="editor">
<editor-content class="editor__content" :editor="editor" />
</div>
<div class="editor">
<editor-content class="editor__content" :editor="editor" />
</div>
</template>
<script>
import { Editor, EditorContent } from 'tiptap'
import {
HardBreak,
Heading,
Bold,
Italic,
History,
HardBreak,
Heading,
Bold,
Italic,
History,
} from 'tiptap-extensions'
import Iframe from './Iframe.js'
export default {
components: {
EditorContent,
},
data() {
return {
editor: new Editor({
extensions: [
new HardBreak(),
new Heading({ levels: [1, 2, 3] }),
new Bold(),
new Italic(),
new History(),
// custom extension
new Iframe(),
],
content: `
<h2>
Embeds
</h2>
<p>
This is an example of a custom iframe node. This iframe is rendered as a <strong>vue component</strong>. This makes it possible to render the input below to change its source.
</p>
<iframe src="https://www.youtube.com/embed/XIMLoLxmTDw" frameborder="0" allowfullscreen></iframe>
`,
}),
}
},
beforeDestroy() {
this.editor.destroy()
},
components: {
EditorContent,
},
data() {
return {
editor: new Editor({
extensions: [
new HardBreak(),
new Heading({ levels: [1, 2, 3] }),
new Bold(),
new Italic(),
new History(),
// custom extension
new Iframe(),
],
content: `
<h2>
Embeds
</h2>
<p>
This is an example of a custom iframe node. This iframe is rendered as a <strong>vue component</strong>. This makes it possible to render the input below to change its source.
</p>
<iframe src="https://www.youtube.com/embed/XIMLoLxmTDw" frameborder="0" allowfullscreen></iframe>
`,
}),
}
},
beforeDestroy() {
this.editor.destroy()
},
}
</script>
@@ -53,20 +53,20 @@ export default {
@import "~variables";
.iframe {
&__embed {
width: 100%;
height: 15rem;
border: 0;
}
&__embed {
width: 100%;
height: 15rem;
border: 0;
}
&__input {
display: block;
width: 100%;
font: inherit;
border: 0;
border-radius: 5px;
background-color: rgba($color-black, 0.1);
padding: 0.3rem 0.5rem;
}
&__input {
display: block;
width: 100%;
font: inherit;
border: 0;
border-radius: 5px;
background-color: rgba($color-black, 0.1);
padding: 0.3rem 0.5rem;
}
}
</style>

View File

@@ -1,203 +1,203 @@
<template>
<div>
<div class="editor">
<menu-bar :editor="editor">
<div class="menubar" slot-scope="{ commands, isActive }">
<div>
<div class="editor">
<menu-bar :editor="editor">
<div class="menubar" slot-scope="{ commands, isActive }">
<button
class="menubar__button"
:class="{ 'is-active': isActive('bold') }"
@click="commands.bold"
>
<icon name="bold" />
</button>
<button
class="menubar__button"
:class="{ 'is-active': isActive('bold') }"
@click="commands.bold"
>
<icon name="bold" />
</button>
<button
class="menubar__button"
:class="{ 'is-active': isActive('italic') }"
@click="commands.italic"
>
<icon name="italic" />
</button>
<button
class="menubar__button"
:class="{ 'is-active': isActive('italic') }"
@click="commands.italic"
>
<icon name="italic" />
</button>
<button
class="menubar__button"
:class="{ 'is-active': isActive('code') }"
@click="commands.code"
>
<icon name="code" />
</button>
<button
class="menubar__button"
:class="{ 'is-active': isActive('code') }"
@click="commands.code"
>
<icon name="code" />
</button>
<button
class="menubar__button"
:class="{ 'is-active': isActive('paragraph') }"
@click="commands.paragraph"
>
<icon name="paragraph" />
</button>
<button
class="menubar__button"
:class="{ 'is-active': isActive('paragraph') }"
@click="commands.paragraph"
>
<icon name="paragraph" />
</button>
<button
class="menubar__button"
:class="{ 'is-active': isActive('heading', { level: 1 }) }"
@click="commands.heading({ level: 1 })"
>
H1
</button>
<button
class="menubar__button"
:class="{ 'is-active': isActive('heading', { level: 1 }) }"
@click="commands.heading({ level: 1 })"
>
H1
</button>
<button
class="menubar__button"
:class="{ 'is-active': isActive('heading', { level: 2 }) }"
@click="commands.heading({ level: 2 })"
>
H2
</button>
<button
class="menubar__button"
:class="{ 'is-active': isActive('heading', { level: 2 }) }"
@click="commands.heading({ level: 2 })"
>
H2
</button>
<button
class="menubar__button"
:class="{ 'is-active': isActive('heading', { level: 3 }) }"
@click="commands.heading({ level: 3 })"
>
H3
</button>
<button
class="menubar__button"
:class="{ 'is-active': isActive('heading', { level: 3 }) }"
@click="commands.heading({ level: 3 })"
>
H3
</button>
<button
class="menubar__button"
:class="{ 'is-active': isActive('bullet_list') }"
@click="commands.bullet_list"
>
<icon name="ul" />
</button>
<button
class="menubar__button"
:class="{ 'is-active': isActive('bullet_list') }"
@click="commands.bullet_list"
>
<icon name="ul" />
</button>
<button
class="menubar__button"
:class="{ 'is-active': isActive('ordered_list') }"
@click="commands.ordered_list"
>
<icon name="ol" />
</button>
<button
class="menubar__button"
:class="{ 'is-active': isActive('ordered_list') }"
@click="commands.ordered_list"
>
<icon name="ol" />
</button>
<button
class="menubar__button"
:class="{ 'is-active': isActive('code_block') }"
@click="commands.code_block"
>
<icon name="code" />
</button>
<button
class="menubar__button"
:class="{ 'is-active': isActive('code_block') }"
@click="commands.code_block"
>
<icon name="code" />
</button>
</div>
</menu-bar>
</div>
</menu-bar>
<editor-content class="editor__content" :editor="editor" />
<editor-content class="editor__content" :editor="editor" />
</div>
</div>
<div class="actions">
<button class="button" @click="clearContent">
Clear Content
</button>
<button class="button" @click="setContent">
Set Content
</button>
</div>
<div class="actions">
<button class="button" @click="clearContent">
Clear Content
</button>
<button class="button" @click="setContent">
Set Content
</button>
</div>
<div class="export">
<h3>JSON</h3>
<pre><code v-html="json"></code></pre>
<div class="export">
<h3>JSON</h3>
<pre><code v-html="json"></code></pre>
<h3>HTML</h3>
<pre><code>{{ html }}</code></pre>
</div>
</div>
<h3>HTML</h3>
<pre><code>{{ html }}</code></pre>
</div>
</div>
</template>
<script>
import Icon from 'Components/Icon'
import { Editor, EditorContent, MenuBar } from 'tiptap'
import {
Blockquote,
CodeBlock,
HardBreak,
Heading,
OrderedList,
BulletList,
ListItem,
TodoItem,
TodoList,
Bold,
Code,
Italic,
Link,
History,
Blockquote,
CodeBlock,
HardBreak,
Heading,
OrderedList,
BulletList,
ListItem,
TodoItem,
TodoList,
Bold,
Code,
Italic,
Link,
History,
} from 'tiptap-extensions'
export default {
components: {
EditorContent,
MenuBar,
Icon,
},
data() {
return {
editor: new Editor({
extensions: [
new Blockquote(),
new BulletList(),
new CodeBlock(),
new HardBreak(),
new Heading({ levels: [1, 2, 3] }),
new ListItem(),
new OrderedList(),
new TodoItem(),
new TodoList(),
new Bold(),
new Code(),
new Italic(),
new Link(),
new History(),
],
content: `
<h2>
Export HTML or JSON
</h2>
<p>
You are able to export your data as <code>HTML</code> or <code>JSON</code>. To pass <code>HTML</code> to the editor use the <code>content</code> slot. To pass <code>JSON</code> to the editor use the <code>doc</code> prop.
</p>
`,
onUpdate: ({ getJSON, getHTML }) => {
this.json = getJSON()
this.html = getHTML()
},
}),
json: 'Update content to see changes',
html: 'Update content to see changes',
}
},
methods: {
clearContent() {
this.editor.clearContent(true)
this.editor.focus()
},
setContent() {
// you can pass a json document
this.editor.setContent({
type: 'doc',
content: [{
type: 'paragraph',
content: [
{
type: 'text',
text: 'This is some inserted text. 👋',
},
],
}],
}, true)
components: {
EditorContent,
MenuBar,
Icon,
},
data() {
return {
editor: new Editor({
extensions: [
new Blockquote(),
new BulletList(),
new CodeBlock(),
new HardBreak(),
new Heading({ levels: [1, 2, 3] }),
new ListItem(),
new OrderedList(),
new TodoItem(),
new TodoList(),
new Bold(),
new Code(),
new Italic(),
new Link(),
new History(),
],
content: `
<h2>
Export HTML or JSON
</h2>
<p>
You are able to export your data as <code>HTML</code> or <code>JSON</code>. To pass <code>HTML</code> to the editor use the <code>content</code> slot. To pass <code>JSON</code> to the editor use the <code>doc</code> prop.
</p>
`,
onUpdate: ({ getJSON, getHTML }) => {
this.json = getJSON()
this.html = getHTML()
},
}),
json: 'Update content to see changes',
html: 'Update content to see changes',
}
},
methods: {
clearContent() {
this.editor.clearContent(true)
this.editor.focus()
},
setContent() {
// you can pass a json document
this.editor.setContent({
type: 'doc',
content: [{
type: 'paragraph',
content: [
{
type: 'text',
text: 'This is some inserted text. 👋',
},
],
}],
}, true)
// HTML string is also supported
// this.editor.setContent('<p>This is some inserted text. 👋</p>')
// HTML string is also supported
// this.editor.setContent('<p>This is some inserted text. 👋</p>')
this.editor.focus()
},
},
this.editor.focus()
},
},
}
</script>
@@ -205,27 +205,27 @@ export default {
@import "~variables";
.actions {
max-width: 30rem;
margin: 0 auto 2rem auto;
max-width: 30rem;
margin: 0 auto 2rem auto;
}
.export {
max-width: 30rem;
margin: 0 auto 2rem auto;
max-width: 30rem;
margin: 0 auto 2rem auto;
pre {
padding: 1rem;
border-radius: 5px;
font-size: 0.8rem;
font-weight: bold;
background: rgba($color-black, 0.05);
color: rgba($color-black, 0.8);
}
pre {
padding: 1rem;
border-radius: 5px;
font-size: 0.8rem;
font-weight: bold;
background: rgba($color-black, 0.05);
color: rgba($color-black, 0.8);
}
code {
display: block;
white-space: pre-wrap;
}
code {
display: block;
white-space: pre-wrap;
}
}
</style>

View File

@@ -1,132 +1,132 @@
<template>
<div class="editor">
<floating-menu :editor="editor">
<div
slot-scope="{ commands, isActive, menu }"
class="editor__floating-menu"
:class="{ 'is-active': menu.isActive }"
:style="`top: ${menu.top}px`"
>
<div class="editor">
<floating-menu :editor="editor">
<div
slot-scope="{ commands, isActive, menu }"
class="editor__floating-menu"
:class="{ 'is-active': menu.isActive }"
:style="`top: ${menu.top}px`"
>
<button
class="menubar__button"
:class="{ 'is-active': isActive('heading', { level: 1 }) }"
@click="commands.heading({ level: 1 })"
>
H1
</button>
<button
class="menubar__button"
:class="{ 'is-active': isActive('heading', { level: 1 }) }"
@click="commands.heading({ level: 1 })"
>
H1
</button>
<button
class="menubar__button"
:class="{ 'is-active': isActive('heading', { level: 2 }) }"
@click="commands.heading({ level: 2 })"
>
H2
</button>
<button
class="menubar__button"
:class="{ 'is-active': isActive('heading', { level: 2 }) }"
@click="commands.heading({ level: 2 })"
>
H2
</button>
<button
class="menubar__button"
:class="{ 'is-active': isActive('heading', { level: 3 }) }"
@click="commands.heading({ level: 3 })"
>
H3
</button>
<button
class="menubar__button"
:class="{ 'is-active': isActive('heading', { level: 3 }) }"
@click="commands.heading({ level: 3 })"
>
H3
</button>
<button
class="menubar__button"
:class="{ 'is-active': isActive('bullet_list') }"
@click="commands.bullet_list"
>
<icon name="ul" />
</button>
<button
class="menubar__button"
:class="{ 'is-active': isActive('bullet_list') }"
@click="commands.bullet_list"
>
<icon name="ul" />
</button>
<button
class="menubar__button"
:class="{ 'is-active': isActive('ordered_list') }"
@click="commands.ordered_list"
>
<icon name="ol" />
</button>
<button
class="menubar__button"
:class="{ 'is-active': isActive('ordered_list') }"
@click="commands.ordered_list"
>
<icon name="ol" />
</button>
<button
class="menubar__button"
:class="{ 'is-active': isActive('blockquote') }"
@click="commands.blockquote"
>
<icon name="quote" />
</button>
<button
class="menubar__button"
:class="{ 'is-active': isActive('blockquote') }"
@click="commands.blockquote"
>
<icon name="quote" />
</button>
<button
class="menubar__button"
:class="{ 'is-active': isActive('code_block') }"
@click="commands.code_block"
>
<icon name="code" />
</button>
<button
class="menubar__button"
:class="{ 'is-active': isActive('code_block') }"
@click="commands.code_block"
>
<icon name="code" />
</button>
</div>
</floating-menu>
</div>
</floating-menu>
<editor-content class="editor__content" :editor="editor" />
</div>
<editor-content class="editor__content" :editor="editor" />
</div>
</template>
<script>
import Icon from 'Components/Icon'
import { Editor, EditorContent, FloatingMenu } from 'tiptap'
import {
Blockquote,
BulletList,
CodeBlock,
HardBreak,
Heading,
ListItem,
OrderedList,
TodoItem,
TodoList,
Bold,
Code,
Italic,
Link,
History,
Blockquote,
BulletList,
CodeBlock,
HardBreak,
Heading,
ListItem,
OrderedList,
TodoItem,
TodoList,
Bold,
Code,
Italic,
Link,
History,
} from 'tiptap-extensions'
export default {
components: {
EditorContent,
FloatingMenu,
Icon,
},
data() {
return {
editor: new Editor({
extensions: [
new Blockquote(),
new BulletList(),
new CodeBlock(),
new HardBreak(),
new Heading({ levels: [1, 2, 3] }),
new ListItem(),
new OrderedList(),
new TodoItem(),
new TodoList(),
new Bold(),
new Code(),
new Italic(),
new Link(),
new History(),
],
content: `
<h2>
Floating Menu
</h2>
<p>
This is an example of a medium-like editor. Enter a new line and some buttons will appear.
</p>
`,
}),
}
},
components: {
EditorContent,
FloatingMenu,
Icon,
},
data() {
return {
editor: new Editor({
extensions: [
new Blockquote(),
new BulletList(),
new CodeBlock(),
new HardBreak(),
new Heading({ levels: [1, 2, 3] }),
new ListItem(),
new OrderedList(),
new TodoItem(),
new TodoList(),
new Bold(),
new Code(),
new Italic(),
new Link(),
new History(),
],
content: `
<h2>
Floating Menu
</h2>
<p>
This is an example of a medium-like editor. Enter a new line and some buttons will appear.
</p>
`,
}),
}
},
}
</script>
@@ -135,20 +135,20 @@ export default {
.editor {
position: relative;
position: relative;
&__floating-menu {
position: absolute;
margin-top: -0.25rem;
visibility: hidden;
opacity: 0;
transition: opacity 0.2s, visibility 0.2s;
&__floating-menu {
position: absolute;
margin-top: -0.25rem;
visibility: hidden;
opacity: 0;
transition: opacity 0.2s, visibility 0.2s;
&.is-active {
opacity: 1;
visibility: visible;
}
}
&.is-active {
opacity: 1;
visibility: visible;
}
}
}
</style>
</style>

View File

@@ -1,185 +1,185 @@
<template>
<div class="editor">
<menu-bar :editor="editor">
<div
class="menubar is-hidden"
:class="{ 'is-focused': focused }"
slot-scope="{ commands, isActive, focused }"
>
<div class="editor">
<menu-bar :editor="editor">
<div
class="menubar is-hidden"
:class="{ 'is-focused': focused }"
slot-scope="{ commands, isActive, focused }"
>
<button
class="menubar__button"
:class="{ 'is-active': isActive('bold') }"
@click="commands.bold"
>
<icon name="bold" />
</button>
<button
class="menubar__button"
:class="{ 'is-active': isActive('bold') }"
@click="commands.bold"
>
<icon name="bold" />
</button>
<button
class="menubar__button"
:class="{ 'is-active': isActive('italic') }"
@click="commands.italic"
>
<icon name="italic" />
</button>
<button
class="menubar__button"
:class="{ 'is-active': isActive('italic') }"
@click="commands.italic"
>
<icon name="italic" />
</button>
<button
class="menubar__button"
:class="{ 'is-active': isActive('strike') }"
@click="commands.strike"
>
<icon name="strike" />
</button>
<button
class="menubar__button"
:class="{ 'is-active': isActive('strike') }"
@click="commands.strike"
>
<icon name="strike" />
</button>
<button
class="menubar__button"
:class="{ 'is-active': isActive('underline') }"
@click="commands.underline"
>
<icon name="underline" />
</button>
<button
class="menubar__button"
:class="{ 'is-active': isActive('underline') }"
@click="commands.underline"
>
<icon name="underline" />
</button>
<button
class="menubar__button"
:class="{ 'is-active': isActive('code') }"
@click="commands.code"
>
<icon name="code" />
</button>
<button
class="menubar__button"
:class="{ 'is-active': isActive('code') }"
@click="commands.code"
>
<icon name="code" />
</button>
<button
class="menubar__button"
:class="{ 'is-active': isActive('paragraph') }"
@click="commands.paragraph"
>
<icon name="paragraph" />
</button>
<button
class="menubar__button"
:class="{ 'is-active': isActive('paragraph') }"
@click="commands.paragraph"
>
<icon name="paragraph" />
</button>
<button
class="menubar__button"
:class="{ 'is-active': isActive('heading', { level: 1 }) }"
@click="commands.heading({ level: 1 })"
>
H1
</button>
<button
class="menubar__button"
:class="{ 'is-active': isActive('heading', { level: 1 }) }"
@click="commands.heading({ level: 1 })"
>
H1
</button>
<button
class="menubar__button"
:class="{ 'is-active': isActive('heading', { level: 2 }) }"
@click="commands.heading({ level: 2 })"
>
H2
</button>
<button
class="menubar__button"
:class="{ 'is-active': isActive('heading', { level: 2 }) }"
@click="commands.heading({ level: 2 })"
>
H2
</button>
<button
class="menubar__button"
:class="{ 'is-active': isActive('heading', { level: 3 }) }"
@click="commands.heading({ level: 3 })"
>
H3
</button>
<button
class="menubar__button"
:class="{ 'is-active': isActive('heading', { level: 3 }) }"
@click="commands.heading({ level: 3 })"
>
H3
</button>
<button
class="menubar__button"
:class="{ 'is-active': isActive('bullet_list') }"
@click="commands.bullet_list"
>
<icon name="ul" />
</button>
<button
class="menubar__button"
:class="{ 'is-active': isActive('bullet_list') }"
@click="commands.bullet_list"
>
<icon name="ul" />
</button>
<button
class="menubar__button"
:class="{ 'is-active': isActive('ordered_list') }"
@click="commands.ordered_list"
>
<icon name="ol" />
</button>
<button
class="menubar__button"
:class="{ 'is-active': isActive('ordered_list') }"
@click="commands.ordered_list"
>
<icon name="ol" />
</button>
<button
class="menubar__button"
:class="{ 'is-active': isActive('blockquote') }"
@click="commands.blockquote"
>
<icon name="quote" />
</button>
<button
class="menubar__button"
:class="{ 'is-active': isActive('blockquote') }"
@click="commands.blockquote"
>
<icon name="quote" />
</button>
<button
class="menubar__button"
:class="{ 'is-active': isActive('code_block') }"
@click="commands.code_block"
>
<icon name="code" />
</button>
<button
class="menubar__button"
:class="{ 'is-active': isActive('code_block') }"
@click="commands.code_block"
>
<icon name="code" />
</button>
</div>
</menu-bar>
</div>
</menu-bar>
<editor-content class="editor__content" :editor="editor" />
</div>
<editor-content class="editor__content" :editor="editor" />
</div>
</template>
<script>
import Icon from 'Components/Icon'
import { Editor, EditorContent, MenuBar } from 'tiptap'
import {
Blockquote,
BulletList,
CodeBlock,
HardBreak,
Heading,
ListItem,
OrderedList,
TodoItem,
TodoList,
Bold,
Code,
Italic,
Link,
Strike,
Underline,
History,
Blockquote,
BulletList,
CodeBlock,
HardBreak,
Heading,
ListItem,
OrderedList,
TodoItem,
TodoList,
Bold,
Code,
Italic,
Link,
Strike,
Underline,
History,
} from 'tiptap-extensions'
export default {
components: {
EditorContent,
MenuBar,
Icon,
},
data() {
return {
editor: new Editor({
extensions: [
new Blockquote(),
new BulletList(),
new CodeBlock(),
new HardBreak(),
new Heading({ levels: [1, 2, 3] }),
new ListItem(),
new OrderedList(),
new TodoItem(),
new TodoList(),
new Bold(),
new Code(),
new Italic(),
new Link(),
new Strike(),
new Underline(),
new History(),
],
content: `
<h2>
Hiding Menu Bar
</h2>
<p>
Click into this text to see the menu. Click outside and the menu will disappear. It's like magic.
</p>
`,
}),
}
},
beforeDestroy() {
this.editor.destroy()
},
components: {
EditorContent,
MenuBar,
Icon,
},
data() {
return {
editor: new Editor({
extensions: [
new Blockquote(),
new BulletList(),
new CodeBlock(),
new HardBreak(),
new Heading({ levels: [1, 2, 3] }),
new ListItem(),
new OrderedList(),
new TodoItem(),
new TodoList(),
new Bold(),
new Code(),
new Italic(),
new Link(),
new Strike(),
new Underline(),
new History(),
],
content: `
<h2>
Hiding Menu Bar
</h2>
<p>
Click into this text to see the menu. Click outside and the menu will disappear. It's like magic.
</p>
`,
}),
}
},
beforeDestroy() {
this.editor.destroy()
},
}
</script>
</script>

View File

@@ -1,68 +1,68 @@
<template>
<div class="editor">
<menu-bar :editor="editor">
<div class="menubar" slot-scope="{ commands }">
<button
class="menubar__button"
@click="showImagePrompt(commands.image)"
>
<icon name="image" />
</button>
</div>
</menu-bar>
<editor-content class="editor__content" :editor="editor" />
</div>
<div class="editor">
<menu-bar :editor="editor">
<div class="menubar" slot-scope="{ commands }">
<button
class="menubar__button"
@click="showImagePrompt(commands.image)"
>
<icon name="image" />
</button>
</div>
</menu-bar>
<editor-content class="editor__content" :editor="editor" />
</div>
</template>
<script>
import Icon from 'Components/Icon'
import { Editor, EditorContent, MenuBar } from 'tiptap'
import {
HardBreak,
Heading,
Image,
Bold,
Code,
Italic,
HardBreak,
Heading,
Image,
Bold,
Code,
Italic,
} from 'tiptap-extensions'
export default {
components: {
Icon,
EditorContent,
MenuBar,
},
data() {
return {
editor: new Editor({
extensions: [
new HardBreak(),
new Heading({ levels: [1, 2, 3] }),
new Image(),
new Bold(),
new Code(),
new Italic(),
],
content: `
<h2>
Images
</h2>
<p>
This is basic example of implementing images. Try to drop new images here. Reordering also works.
</p>
<img src="https://ljdchost.com/8I2DeFn.gif" />
`,
}),
}
},
methods: {
showImagePrompt(command) {
const src = prompt('Enter the url of your image here')
if (src !== null) {
command({ src })
}
},
},
components: {
Icon,
EditorContent,
MenuBar,
},
data() {
return {
editor: new Editor({
extensions: [
new HardBreak(),
new Heading({ levels: [1, 2, 3] }),
new Image(),
new Bold(),
new Code(),
new Italic(),
],
content: `
<h2>
Images
</h2>
<p>
This is basic example of implementing images. Try to drop new images here. Reordering also works.
</p>
<img src="https://ljdchost.com/8I2DeFn.gif" />
`,
}),
}
},
methods: {
showImagePrompt(command) {
const src = prompt('Enter the url of your image here')
if (src !== null) {
command({ src })
}
},
},
}
</script>
</script>

View File

@@ -1,113 +1,113 @@
<template>
<div class="editor">
<menu-bubble class="menububble" :editor="editor">
<div
slot-scope="{ commands, isActive, markAttrs, menu }"
class="menububble"
:class="{ 'is-active': menu.isActive }"
:style="`left: ${menu.left}px; bottom: ${menu.bottom}px;`"
>
<div class="editor">
<menu-bubble class="menububble" :editor="editor">
<div
slot-scope="{ commands, isActive, markAttrs, menu }"
class="menububble"
:class="{ 'is-active': menu.isActive }"
:style="`left: ${menu.left}px; bottom: ${menu.bottom}px;`"
>
<form class="menububble__form" v-if="linkMenuIsActive" @submit.prevent="setLinkUrl(commands.link, linkUrl)">
<input class="menububble__input" type="text" v-model="linkUrl" placeholder="https://" ref="linkInput" @keydown.esc="hideLinkMenu"/>
<button class="menububble__button" @click="setLinkUrl(commands.link, null)" type="button">
<icon name="remove" />
</button>
</form>
<form class="menububble__form" v-if="linkMenuIsActive" @submit.prevent="setLinkUrl(commands.link, linkUrl)">
<input class="menububble__input" type="text" v-model="linkUrl" placeholder="https://" ref="linkInput" @keydown.esc="hideLinkMenu"/>
<button class="menububble__button" @click="setLinkUrl(commands.link, null)" type="button">
<icon name="remove" />
</button>
</form>
<template v-else>
<button
class="menububble__button"
@click="showLinkMenu(markAttrs('link'))"
:class="{ 'is-active': isActive('link') }"
>
<span>Add Link</span>
<icon name="link" />
</button>
</template>
<template v-else>
<button
class="menububble__button"
@click="showLinkMenu(markAttrs('link'))"
:class="{ 'is-active': isActive('link') }"
>
<span>Add Link</span>
<icon name="link" />
</button>
</template>
</div>
</menu-bubble>
</div>
</menu-bubble>
<editor-content class="editor__content" :editor="editor" />
</div>
<editor-content class="editor__content" :editor="editor" />
</div>
</template>
<script>
import Icon from 'Components/Icon'
import { Editor, EditorContent, MenuBubble } from 'tiptap'
import {
Blockquote,
BulletList,
CodeBlock,
HardBreak,
Heading,
ListItem,
OrderedList,
TodoItem,
TodoList,
Bold,
Code,
Italic,
Link,
History,
Blockquote,
BulletList,
CodeBlock,
HardBreak,
Heading,
ListItem,
OrderedList,
TodoItem,
TodoList,
Bold,
Code,
Italic,
Link,
History,
} from 'tiptap-extensions'
export default {
components: {
EditorContent,
MenuBubble,
Icon,
},
data() {
return {
editor: new Editor({
extensions: [
new Blockquote(),
new BulletList(),
new CodeBlock(),
new HardBreak(),
new Heading({ levels: [1, 2, 3] }),
new ListItem(),
new OrderedList(),
new TodoItem(),
new TodoList(),
new Bold(),
new Code(),
new Italic(),
new Link(),
new History(),
],
content: `
<h2>
Links
</h2>
<p>
Try to add some links to the <a href="https://en.wikipedia.org/wiki/World_Wide_Web">world wide web</a>. By default every link will get a <code>rel="noopener noreferrer nofollow"</code> attribute.
</p>
`,
}),
linkUrl: null,
linkMenuIsActive: false,
}
},
methods: {
showLinkMenu(attrs) {
this.linkUrl = attrs.href
this.linkMenuIsActive = true
this.$nextTick(() => {
this.$refs.linkInput.focus()
})
},
hideLinkMenu() {
this.linkUrl = null
this.linkMenuIsActive = false
},
setLinkUrl(command, url) {
command({ href: url })
this.hideLinkMenu()
this.editor.focus()
},
},
components: {
EditorContent,
MenuBubble,
Icon,
},
data() {
return {
editor: new Editor({
extensions: [
new Blockquote(),
new BulletList(),
new CodeBlock(),
new HardBreak(),
new Heading({ levels: [1, 2, 3] }),
new ListItem(),
new OrderedList(),
new TodoItem(),
new TodoList(),
new Bold(),
new Code(),
new Italic(),
new Link(),
new History(),
],
content: `
<h2>
Links
</h2>
<p>
Try to add some links to the <a href="https://en.wikipedia.org/wiki/World_Wide_Web">world wide web</a>. By default every link will get a <code>rel="noopener noreferrer nofollow"</code> attribute.
</p>
`,
}),
linkUrl: null,
linkMenuIsActive: false,
}
},
methods: {
showLinkMenu(attrs) {
this.linkUrl = attrs.href
this.linkMenuIsActive = true
this.$nextTick(() => {
this.$refs.linkInput.focus()
})
},
hideLinkMenu() {
this.linkUrl = null
this.linkMenuIsActive = false
},
setLinkUrl(command, url) {
command({ href: url })
this.hideLinkMenu()
this.editor.focus()
},
},
}
</script>
</script>

View File

@@ -1,69 +1,69 @@
<template>
<div class="editor">
<editor-content class="editor__content" :editor="editor" />
</div>
<div class="editor">
<editor-content class="editor__content" :editor="editor" />
</div>
</template>
<script>
import Icon from 'Components/Icon'
import { Editor, EditorContent } from 'tiptap'
import {
Blockquote,
CodeBlock,
HardBreak,
Heading,
OrderedList,
BulletList,
ListItem,
TodoItem,
TodoList,
Bold,
Code,
Italic,
Link,
History,
Blockquote,
CodeBlock,
HardBreak,
Heading,
OrderedList,
BulletList,
ListItem,
TodoItem,
TodoList,
Bold,
Code,
Italic,
Link,
History,
} from 'tiptap-extensions'
export default {
components: {
EditorContent,
Icon,
},
data() {
return {
editor: new Editor({
extensions: [
new Blockquote(),
new BulletList(),
new CodeBlock(),
new HardBreak(),
new Heading({ levels: [1, 2, 3] }),
new ListItem(),
new OrderedList(),
new TodoItem(),
new TodoList(),
new Bold(),
new Code(),
new Italic(),
new Link(),
new History(),
],
content: `
<h2>
Markdown Shortcuts
</h2>
<p>
Start a new line and type <code>#</code> followed by a <code>space</code> and you will get an H1 headline.
</p>
<p>
This feature is called <strong>input rules</strong>. There are some of these shortcuts for the most basic nodes enabled by default. Try <code>#, ##, ###, …</code> for headlines, <code>></code> for blockquotes, <code>- or +</code> for bullet lists. And of course you can add your own input rules.
</p>
`,
}),
}
},
beforeDestroy() {
this.editor.destroy()
},
components: {
EditorContent,
Icon,
},
data() {
return {
editor: new Editor({
extensions: [
new Blockquote(),
new BulletList(),
new CodeBlock(),
new HardBreak(),
new Heading({ levels: [1, 2, 3] }),
new ListItem(),
new OrderedList(),
new TodoItem(),
new TodoList(),
new Bold(),
new Code(),
new Italic(),
new Link(),
new History(),
],
content: `
<h2>
Markdown Shortcuts
</h2>
<p>
Start a new line and type <code>#</code> followed by a <code>space</code> and you will get an H1 headline.
</p>
<p>
This feature is called <strong>input rules</strong>. There are some of these shortcuts for the most basic nodes enabled by default. Try <code>#, ##, ###, …</code> for headlines, <code>></code> for blockquotes, <code>- or +</code> for bullet lists. And of course you can add your own input rules.
</p>
`,
}),
}
},
beforeDestroy() {
this.editor.destroy()
},
}
</script>
</script>

View File

@@ -1,106 +1,106 @@
<template>
<div class="editor">
<menu-bubble :editor="editor">
<div
slot-scope="{ commands, isActive, menu }"
class="menububble"
:class="{ 'is-active': menu.isActive }"
:style="`left: ${menu.left}px; bottom: ${menu.bottom}px;`"
>
<div class="editor">
<menu-bubble :editor="editor">
<div
slot-scope="{ commands, isActive, menu }"
class="menububble"
:class="{ 'is-active': menu.isActive }"
:style="`left: ${menu.left}px; bottom: ${menu.bottom}px;`"
>
<button
class="menububble__button"
:class="{ 'is-active': isActive('bold') }"
@click="commands.bold"
>
<icon name="bold" />
</button>
<button
class="menububble__button"
:class="{ 'is-active': isActive('bold') }"
@click="commands.bold"
>
<icon name="bold" />
</button>
<button
class="menububble__button"
:class="{ 'is-active': isActive('italic') }"
@click="commands.italic"
>
<icon name="italic" />
</button>
<button
class="menububble__button"
:class="{ 'is-active': isActive('italic') }"
@click="commands.italic"
>
<icon name="italic" />
</button>
<button
class="menububble__button"
:class="{ 'is-active': isActive('code') }"
@click="commands.code"
>
<icon name="code" />
</button>
<button
class="menububble__button"
:class="{ 'is-active': isActive('code') }"
@click="commands.code"
>
<icon name="code" />
</button>
</div>
</menu-bubble>
</div>
</menu-bubble>
<editor-content class="editor__content" :editor="editor" />
</div>
<editor-content class="editor__content" :editor="editor" />
</div>
</template>
<script>
import Icon from 'Components/Icon'
import { Editor, EditorContent, MenuBubble } from 'tiptap'
import {
Blockquote,
BulletList,
CodeBlock,
HardBreak,
Heading,
ListItem,
OrderedList,
TodoItem,
TodoList,
Bold,
Code,
Italic,
Link,
Strike,
Underline,
History,
Blockquote,
BulletList,
CodeBlock,
HardBreak,
Heading,
ListItem,
OrderedList,
TodoItem,
TodoList,
Bold,
Code,
Italic,
Link,
Strike,
Underline,
History,
} from 'tiptap-extensions'
export default {
components: {
EditorContent,
MenuBubble,
Icon,
},
data() {
return {
editor: new Editor({
extensions: [
new Blockquote(),
new BulletList(),
new CodeBlock(),
new HardBreak(),
new Heading({ levels: [1, 2, 3] }),
new ListItem(),
new OrderedList(),
new TodoItem(),
new TodoList(),
new Bold(),
new Code(),
new Italic(),
new Link(),
new Strike(),
new Underline(),
new History(),
],
content: `
<h2>
Menu Bubble
</h2>
<p>
Hey, try to select some text here. There will popup a menu for selecting some inline styles. <em>Remember:</em> you have full control about content and styling of this menu.
</p>
`,
}),
}
},
beforeDestroy() {
this.editor.destroy()
},
components: {
EditorContent,
MenuBubble,
Icon,
},
data() {
return {
editor: new Editor({
extensions: [
new Blockquote(),
new BulletList(),
new CodeBlock(),
new HardBreak(),
new Heading({ levels: [1, 2, 3] }),
new ListItem(),
new OrderedList(),
new TodoItem(),
new TodoList(),
new Bold(),
new Code(),
new Italic(),
new Link(),
new Strike(),
new Underline(),
new History(),
],
content: `
<h2>
Menu Bubble
</h2>
<p>
Hey, try to select some text here. There will popup a menu for selecting some inline styles. <em>Remember:</em> you have full control about content and styling of this menu.
</p>
`,
}),
}
},
beforeDestroy() {
this.editor.destroy()
},
}
</script>
</script>

View File

@@ -1,47 +1,47 @@
<template>
<div class="editor">
<editor-content class="editor__content" :editor="editor" />
</div>
<div class="editor">
<editor-content class="editor__content" :editor="editor" />
</div>
</template>
<script>
import { Editor, EditorContent } from 'tiptap'
import {
BulletList,
ListItem,
Placeholder,
BulletList,
ListItem,
Placeholder,
} from 'tiptap-extensions'
export default {
components: {
EditorContent,
},
data() {
return {
editor: new Editor({
extensions: [
new BulletList(),
new ListItem(),
new Placeholder({
emptyClass: 'is-empty',
}),
],
}),
}
},
beforeDestroy() {
this.editor.destroy()
},
components: {
EditorContent,
},
data() {
return {
editor: new Editor({
extensions: [
new BulletList(),
new ListItem(),
new Placeholder({
emptyClass: 'is-empty',
}),
],
}),
}
},
beforeDestroy() {
this.editor.destroy()
},
}
</script>
<style lang="scss">
.editor p.is-empty:first-child::before {
content: 'Start typing…';
float: left;
color: #aaa;
pointer-events: none;
height: 0;
font-style: italic;
content: 'Start typing…';
float: left;
color: #aaa;
pointer-events: none;
height: 0;
font-style: italic;
}
</style>

View File

@@ -1,49 +1,49 @@
<template>
<div class="editor">
<editor-content class="editor__content" :editor="editor" />
</div>
<div class="editor">
<editor-content class="editor__content" :editor="editor" />
</div>
</template>
<script>
import { Editor, EditorContent } from 'tiptap'
import {
HardBreak,
Heading,
Bold,
Code,
Italic,
Link,
HardBreak,
Heading,
Bold,
Code,
Italic,
Link,
} from 'tiptap-extensions'
export default {
components: {
EditorContent,
},
data() {
return {
editor: new Editor({
editable: false,
extensions: [
new HardBreak(),
new Heading({ levels: [1, 2, 3] }),
new Bold(),
new Code(),
new Italic(),
new Link(),
],
content: `
<h2>
Read-Only
</h2>
<p>
This text is <strong>read-only</strong>. You are not able to edit something. <a href="https://scrumpy.io/">Links to fancy websites</a> are still working.
</p>
`,
}),
}
},
beforeDestroy() {
this.editor.destroy()
},
components: {
EditorContent,
},
data() {
return {
editor: new Editor({
editable: false,
extensions: [
new HardBreak(),
new Heading({ levels: [1, 2, 3] }),
new Bold(),
new Code(),
new Italic(),
new Link(),
],
content: `
<h2>
Read-Only
</h2>
<p>
This text is <strong>read-only</strong>. You are not able to edit something. <a href="https://scrumpy.io/">Links to fancy websites</a> are still working.
</p>
`,
}),
}
},
beforeDestroy() {
this.editor.destroy()
},
}
</script>
</script>

View File

@@ -1,28 +1,28 @@
<template>
<div>
<div>
<div class="editor">
<editor-content class="editor__content" :editor="editor" />
</div>
<div class="editor">
<editor-content class="editor__content" :editor="editor" />
</div>
<div class="suggestion-list" v-show="showSuggestions" ref="suggestions">
<template v-if="hasResults">
<div
v-for="(user, index) in filteredUsers"
:key="user.id"
class="suggestion-list__item"
:class="{ 'is-selected': navigatedUserIndex === index }"
@click="selectUser(user)"
>
{{ user.name }}
</div>
</template>
<div v-else class="suggestion-list__item is-empty">
No users found
</div>
</div>
<div class="suggestion-list" v-show="showSuggestions" ref="suggestions">
<template v-if="hasResults">
<div
v-for="(user, index) in filteredUsers"
:key="user.id"
class="suggestion-list__item"
:class="{ 'is-selected': navigatedUserIndex === index }"
@click="selectUser(user)"
>
{{ user.name }}
</div>
</template>
<div v-else class="suggestion-list__item is-empty">
No users found
</div>
</div>
</div>
</div>
</template>
<script>
@@ -30,204 +30,204 @@ import Fuse from 'fuse.js'
import tippy from 'tippy.js'
import { Editor, EditorContent } from 'tiptap'
import {
HardBreak,
Heading,
Mention,
Code,
Bold,
Italic,
HardBreak,
Heading,
Mention,
Code,
Bold,
Italic,
} from 'tiptap-extensions'
export default {
components: {
EditorContent,
},
components: {
EditorContent,
},
data() {
return {
editor: new Editor({
extensions: [
new HardBreak(),
new Heading({ levels: [1, 2, 3] }),
new Mention({
// a list of all suggested items
items: () => [
{ id: 1, name: 'Philipp Kühn' },
{ id: 2, name: 'Hans Pagel' },
{ id: 3, name: 'Kris Siepert' },
{ id: 4, name: 'Justin Schueler' },
],
// is called when a suggestion starts
onEnter: ({
items, query, range, command, virtualNode,
}) => {
this.query = query
this.filteredUsers = items
this.suggestionRange = range
this.renderPopup(virtualNode)
// we save the command for inserting a selected mention
// this allows us to call it inside of our custom popup
// via keyboard navigation and on click
this.insertMention = command
},
// is called when a suggestion has changed
onChange: ({
items, query, range, virtualNode,
}) => {
this.query = query
this.filteredUsers = items
this.suggestionRange = range
this.navigatedUserIndex = 0
this.renderPopup(virtualNode)
},
// is called when a suggestion is cancelled
onExit: () => {
// reset all saved values
this.query = null
this.filteredUsers = []
this.suggestionRange = null
this.navigatedUserIndex = 0
this.destroyPopup()
},
// is called on every keyDown event while a suggestion is active
onKeyDown: ({ event }) => {
// pressing up arrow
if (event.keyCode === 38) {
this.upHandler()
return true
}
// pressing down arrow
if (event.keyCode === 40) {
this.downHandler()
return true
}
// pressing enter
if (event.keyCode === 13) {
this.enterHandler()
return true
}
data() {
return {
editor: new Editor({
extensions: [
new HardBreak(),
new Heading({ levels: [1, 2, 3] }),
new Mention({
// a list of all suggested items
items: () => [
{ id: 1, name: 'Philipp Kühn' },
{ id: 2, name: 'Hans Pagel' },
{ id: 3, name: 'Kris Siepert' },
{ id: 4, name: 'Justin Schueler' },
],
// is called when a suggestion starts
onEnter: ({
items, query, range, command, virtualNode,
}) => {
this.query = query
this.filteredUsers = items
this.suggestionRange = range
this.renderPopup(virtualNode)
// we save the command for inserting a selected mention
// this allows us to call it inside of our custom popup
// via keyboard navigation and on click
this.insertMention = command
},
// is called when a suggestion has changed
onChange: ({
items, query, range, virtualNode,
}) => {
this.query = query
this.filteredUsers = items
this.suggestionRange = range
this.navigatedUserIndex = 0
this.renderPopup(virtualNode)
},
// is called when a suggestion is cancelled
onExit: () => {
// reset all saved values
this.query = null
this.filteredUsers = []
this.suggestionRange = null
this.navigatedUserIndex = 0
this.destroyPopup()
},
// is called on every keyDown event while a suggestion is active
onKeyDown: ({ event }) => {
// pressing up arrow
if (event.keyCode === 38) {
this.upHandler()
return true
}
// pressing down arrow
if (event.keyCode === 40) {
this.downHandler()
return true
}
// pressing enter
if (event.keyCode === 13) {
this.enterHandler()
return true
}
return false
},
// is called when a suggestion has changed
// this function is optional because there is basic filtering built-in
// you can overwrite it if you prefer your own filtering
// in this example we use fuse.js with support for fuzzy search
onFilter: (items, query) => {
if (!query) {
return items
}
return false
},
// is called when a suggestion has changed
// this function is optional because there is basic filtering built-in
// you can overwrite it if you prefer your own filtering
// in this example we use fuse.js with support for fuzzy search
onFilter: (items, query) => {
if (!query) {
return items
}
const fuse = new Fuse(items, {
threshold: 0.2,
keys: ['name'],
})
const fuse = new Fuse(items, {
threshold: 0.2,
keys: ['name'],
})
return fuse.search(query)
},
}),
new Code(),
new Bold(),
new Italic(),
],
content: `
<h2>
Suggestions
</h2>
<p>
Sometimes it's useful to <strong>mention</strong> someone. That's a feature we're very used to. Under the hood this technique can also be used for other features likes <strong>hashtags</strong> and <strong>commands</strong> lets call it <em>suggestions</em>.
</p>
<p>
This is an example how to mention some users like <span data-mention-id="1">Philipp Kühn</span> or <span data-mention-id="2">Hans Pagel</span>. Try to type <code>@</code> and a popup (rendered with tippy.js) will appear. You can navigate with arrow keys through a list of suggestions.
</p>
`,
}),
query: null,
suggestionRange: null,
filteredUsers: [],
navigatedUserIndex: 0,
insertMention: () => {},
}
},
return fuse.search(query)
},
}),
new Code(),
new Bold(),
new Italic(),
],
content: `
<h2>
Suggestions
</h2>
<p>
Sometimes it's useful to <strong>mention</strong> someone. That's a feature we're very used to. Under the hood this technique can also be used for other features likes <strong>hashtags</strong> and <strong>commands</strong> lets call it <em>suggestions</em>.
</p>
<p>
This is an example how to mention some users like <span data-mention-id="1">Philipp Kühn</span> or <span data-mention-id="2">Hans Pagel</span>. Try to type <code>@</code> and a popup (rendered with tippy.js) will appear. You can navigate with arrow keys through a list of suggestions.
</p>
`,
}),
query: null,
suggestionRange: null,
filteredUsers: [],
navigatedUserIndex: 0,
insertMention: () => {},
}
},
computed: {
computed: {
hasResults() {
return this.filteredUsers.length
},
hasResults() {
return this.filteredUsers.length
},
showSuggestions() {
return this.query || this.hasResults
},
showSuggestions() {
return this.query || this.hasResults
},
},
},
methods: {
methods: {
// navigate to the previous item
// if it's the first item, navigate to the last one
upHandler() {
this.navigatedUserIndex = ((this.navigatedUserIndex + this.filteredUsers.length) - 1) % this.filteredUsers.length
},
// navigate to the previous item
// if it's the first item, navigate to the last one
upHandler() {
this.navigatedUserIndex = ((this.navigatedUserIndex + this.filteredUsers.length) - 1) % this.filteredUsers.length
},
// navigate to the next item
// if it's the last item, navigate to the first one
downHandler() {
this.navigatedUserIndex = (this.navigatedUserIndex + 1) % this.filteredUsers.length
},
// navigate to the next item
// if it's the last item, navigate to the first one
downHandler() {
this.navigatedUserIndex = (this.navigatedUserIndex + 1) % this.filteredUsers.length
},
enterHandler() {
const user = this.filteredUsers[this.navigatedUserIndex]
enterHandler() {
const user = this.filteredUsers[this.navigatedUserIndex]
if (user) {
this.selectUser(user)
}
},
if (user) {
this.selectUser(user)
}
},
// we have to replace our suggestion text with a mention
// so it's important to pass also the position of your suggestion text
selectUser(user) {
this.insertMention({
range: this.suggestionRange,
attrs: {
id: user.id,
label: user.name,
},
})
this.editor.focus()
},
// we have to replace our suggestion text with a mention
// so it's important to pass also the position of your suggestion text
selectUser(user) {
this.insertMention({
range: this.suggestionRange,
attrs: {
id: user.id,
label: user.name,
},
})
this.editor.focus()
},
// renders a popup with suggestions
// tiptap provides a virtualNode object for using popper.js (or tippy.js) for popups
renderPopup(node) {
if (this.popup) {
return
}
// renders a popup with suggestions
// tiptap provides a virtualNode object for using popper.js (or tippy.js) for popups
renderPopup(node) {
if (this.popup) {
return
}
this.popup = tippy(node, {
content: this.$refs.suggestions,
trigger: 'mouseenter',
interactive: true,
theme: 'dark',
placement: 'top-start',
performance: true,
inertia: true,
duration: [400, 200],
showOnInit: true,
arrow: true,
arrowType: 'round',
})
},
this.popup = tippy(node, {
content: this.$refs.suggestions,
trigger: 'mouseenter',
interactive: true,
theme: 'dark',
placement: 'top-start',
performance: true,
inertia: true,
duration: [400, 200],
showOnInit: true,
arrow: true,
arrowType: 'round',
})
},
destroyPopup() {
if (this.popup) {
this.popup.destroyAll()
this.popup = null
}
},
destroyPopup() {
if (this.popup) {
this.popup.destroyAll()
this.popup = null
}
},
},
},
}
</script>
@@ -242,74 +242,74 @@ export default {
font-weight: bold;
border-radius: 5px;
padding: 0.2rem 0.5rem;
white-space: nowrap;
white-space: nowrap;
}
.mention-suggestion {
color: rgba($color-black, 0.6);
color: rgba($color-black, 0.6);
}
.suggestion-list {
padding: 0.2rem;
border: 2px solid rgba($color-black, 0.1);
font-size: 0.8rem;
font-weight: bold;
padding: 0.2rem;
border: 2px solid rgba($color-black, 0.1);
font-size: 0.8rem;
font-weight: bold;
&__no-results {
padding: 0.2rem 0.5rem;
}
&__no-results {
padding: 0.2rem 0.5rem;
}
&__item {
border-radius: 5px;
padding: 0.2rem 0.5rem;
margin-bottom: 0.2rem;
cursor: pointer;
&__item {
border-radius: 5px;
padding: 0.2rem 0.5rem;
margin-bottom: 0.2rem;
cursor: pointer;
&:last-child {
margin-bottom: 0;
}
&:last-child {
margin-bottom: 0;
}
&.is-selected,
&:hover {
background-color: rgba($color-white, 0.2);
}
&.is-selected,
&:hover {
background-color: rgba($color-white, 0.2);
}
&.is-empty {
opacity: 0.5;
}
}
&.is-empty {
opacity: 0.5;
}
}
}
.tippy-tooltip.dark-theme {
background-color: $color-black;
padding: 0;
font-size: 1rem;
text-align: inherit;
color: $color-white;
border-radius: 5px;
background-color: $color-black;
padding: 0;
font-size: 1rem;
text-align: inherit;
color: $color-white;
border-radius: 5px;
.tippy-backdrop {
display: none;
}
.tippy-backdrop {
display: none;
}
.tippy-roundarrow {
fill: $color-black;
}
.tippy-roundarrow {
fill: $color-black;
}
.tippy-popper[x-placement^=top] & .tippy-arrow {
border-top-color: $color-black;
}
.tippy-popper[x-placement^=top] & .tippy-arrow {
border-top-color: $color-black;
}
.tippy-popper[x-placement^=bottom] & .tippy-arrow {
border-bottom-color: $color-black;
}
.tippy-popper[x-placement^=bottom] & .tippy-arrow {
border-bottom-color: $color-black;
}
.tippy-popper[x-placement^=left] & .tippy-arrow {
border-left-color: $color-black;
}
.tippy-popper[x-placement^=left] & .tippy-arrow {
border-left-color: $color-black;
}
.tippy-popper[x-placement^=right] & .tippy-arrow {
border-right-color: $color-black;
}
.tippy-popper[x-placement^=right] & .tippy-arrow {
border-right-color: $color-black;
}
}
</style>

View File

@@ -3,32 +3,32 @@ import { Node } from 'tiptap'
export default class Paragraph extends Node {
get name() {
return 'paragraph'
}
get name() {
return 'paragraph'
}
get schema() {
return {
attrs: {
textAlign: {
default: 'left',
},
},
content: 'inline*',
group: 'block',
draggable: false,
parseDOM: [{
tag: 'p',
getAttrs: node => ({
textAlign: node.style.textAlign,
}),
}],
toDOM: node => ['p', { style: `text-align: ${node.attrs.textAlign}` }, 0],
}
}
get schema() {
return {
attrs: {
textAlign: {
default: 'left',
},
},
content: 'inline*',
group: 'block',
draggable: false,
parseDOM: [{
tag: 'p',
getAttrs: node => ({
textAlign: node.style.textAlign,
}),
}],
toDOM: node => ['p', { style: `text-align: ${node.attrs.textAlign}` }, 0],
}
}
commands({ type }) {
return attrs => setBlockType(type, attrs)
}
commands({ type }) {
return attrs => setBlockType(type, attrs)
}
}

View File

@@ -1,93 +1,93 @@
<template>
<div class="editor">
<menu-bar :editor="editor">
<div class="menubar" slot-scope="{ commands, isActive }">
<div class="editor">
<menu-bar :editor="editor">
<div class="menubar" slot-scope="{ commands, isActive }">
<button
class="menubar__button"
:class="{ 'is-active': isActive('paragraph', { textAlign: 'left' }) }"
@click="commands.paragraph({ textAlign: 'left' })"
>
<icon name="align-left" />
</button>
<button
class="menubar__button"
:class="{ 'is-active': isActive('paragraph', { textAlign: 'left' }) }"
@click="commands.paragraph({ textAlign: 'left' })"
>
<icon name="align-left" />
</button>
<button
class="menubar__button"
:class="{ 'is-active': isActive('paragraph', { textAlign: 'center' }) }"
@click="commands.paragraph({ textAlign: 'center' })"
>
<icon name="align-center" />
</button>
<button
class="menubar__button"
:class="{ 'is-active': isActive('paragraph', { textAlign: 'center' }) }"
@click="commands.paragraph({ textAlign: 'center' })"
>
<icon name="align-center" />
</button>
<button
class="menubar__button"
:class="{ 'is-active': isActive('paragraph', { textAlign: 'right' }) }"
@click="commands.paragraph({ textAlign: 'right' })"
>
<icon name="align-right" />
</button>
</div>
</menu-bar>
<button
class="menubar__button"
:class="{ 'is-active': isActive('paragraph', { textAlign: 'right' }) }"
@click="commands.paragraph({ textAlign: 'right' })"
>
<icon name="align-right" />
</button>
<editor-content class="editor__content" :editor="editor" />
</div>
</div>
</menu-bar>
<editor-content class="editor__content" :editor="editor" />
</div>
</template>
<script>
import Icon from 'Components/Icon'
import { Editor, EditorContent, MenuBar } from 'tiptap'
import {
HardBreak,
Code,
HardBreak,
Code,
} from 'tiptap-extensions'
import ParagraphAlignment from './Paragraph.js'
export default {
components: {
EditorContent,
MenuBar,
Icon,
},
data() {
return {
editor: new Editor({
extensions: [
new HardBreak(),
new Code(),
new ParagraphAlignment(),
],
content: `
<p style="text-align: left">
Maybe you want to implement text alignment. If so, you're able to overwrite the default <code>ParagraphNode</code>. You can define some classes oder inline styles in your schema to achive that.
</p>
<p style="text-align: right">
Have fun! 🙌
</p>
`,
}),
}
},
beforeDestroy() {
this.editor.destroy()
},
components: {
EditorContent,
MenuBar,
Icon,
},
data() {
return {
editor: new Editor({
extensions: [
new HardBreak(),
new Code(),
new ParagraphAlignment(),
],
content: `
<p style="text-align: left">
Maybe you want to implement text alignment. If so, you're able to overwrite the default <code>ParagraphNode</code>. You can define some classes oder inline styles in your schema to achive that.
</p>
<p style="text-align: right">
Have fun! 🙌
</p>
`,
}),
}
},
beforeDestroy() {
this.editor.destroy()
},
}
</script>
<style lang="scss">
.text-align {
&--left {
text-align: left;
}
&--left {
text-align: left;
}
&--center {
text-align: center;
}
&--center {
text-align: center;
}
&--right {
text-align: right;
}
&--right {
text-align: right;
}
}
</style>

View File

@@ -1,108 +1,108 @@
<template>
<div class="editor">
<menu-bar :editor="editor">
<div class="menubar" slot-scope="{ commands, isActive }">
<div class="editor">
<menu-bar :editor="editor">
<div class="menubar" slot-scope="{ commands, isActive }">
<button
class="menubar__button"
:class="{ 'is-active': isActive('bold') }"
@click="commands.bold"
>
<icon name="bold" />
</button>
<button
class="menubar__button"
:class="{ 'is-active': isActive('bold') }"
@click="commands.bold"
>
<icon name="bold" />
</button>
<button
class="menubar__button"
:class="{ 'is-active': isActive('italic') }"
@click="commands.italic"
>
<icon name="italic" />
</button>
<button
class="menubar__button"
:class="{ 'is-active': isActive('italic') }"
@click="commands.italic"
>
<icon name="italic" />
</button>
<button
class="menubar__button"
:class="{ 'is-active': isActive('code') }"
@click="commands.code"
>
<icon name="code" />
</button>
<button
class="menubar__button"
:class="{ 'is-active': isActive('code') }"
@click="commands.code"
>
<icon name="code" />
</button>
<button
class="menubar__button"
:class="{ 'is-active': isActive('todo_list') }"
@click="commands.todo_list"
>
<icon name="checklist" />
</button>
<button
class="menubar__button"
:class="{ 'is-active': isActive('todo_list') }"
@click="commands.todo_list"
>
<icon name="checklist" />
</button>
</div>
</menu-bar>
</div>
</menu-bar>
<editor-content class="editor__content" :editor="editor" />
</div>
<editor-content class="editor__content" :editor="editor" />
</div>
</template>
<script>
import Icon from 'Components/Icon'
import { Editor, EditorContent, MenuBar } from 'tiptap'
import {
CodeBlock,
HardBreak,
Heading,
TodoItem,
TodoList,
Bold,
Code,
Italic,
CodeBlock,
HardBreak,
Heading,
TodoItem,
TodoList,
Bold,
Code,
Italic,
} from 'tiptap-extensions'
export default {
components: {
EditorContent,
MenuBar,
Icon,
},
data() {
return {
editor: new Editor({
extensions: [
new CodeBlock(),
new HardBreak(),
new Heading({ levels: [1, 2, 3] }),
new TodoItem(),
new TodoList(),
new Bold(),
new Code(),
new Italic(),
],
content: `
<h2>
Todo List
</h2>
<p>
There is always something to do. Thankfully, there are checklists for that. Don't forget to call mom.
</p>
<ul data-type="todo_list">
<li data-type="todo_item" data-done="true">
Buy beer
</li>
<li data-type="todo_item" data-done="true">
Buy meat
</li>
<li data-type="todo_item" data-done="true">
Buy milk
</li>
<li data-type="todo_item" data-done="false">
Call mom
</li>
</ul>
`,
}),
}
},
beforeDestroy() {
this.editor.destroy()
},
components: {
EditorContent,
MenuBar,
Icon,
},
data() {
return {
editor: new Editor({
extensions: [
new CodeBlock(),
new HardBreak(),
new Heading({ levels: [1, 2, 3] }),
new TodoItem(),
new TodoList(),
new Bold(),
new Code(),
new Italic(),
],
content: `
<h2>
Todo List
</h2>
<p>
There is always something to do. Thankfully, there are checklists for that. Don't forget to call mom.
</p>
<ul data-type="todo_list">
<li data-type="todo_item" data-done="true">
Buy beer
</li>
<li data-type="todo_item" data-done="true">
Buy meat
</li>
<li data-type="todo_item" data-done="true">
Buy milk
</li>
<li data-type="todo_item" data-done="false">
Call mom
</li>
</ul>
`,
}),
}
},
beforeDestroy() {
this.editor.destroy()
},
}
</script>
@@ -148,4 +148,4 @@ li[data-done="true"] .todo-checkbox {
li[data-done="false"] {
text-decoration: none;
}
</style>
</style>

View File

@@ -1,51 +1,51 @@
<template>
<div class="subnavigation">
<router-link class="subnavigation__link" to="/">
Basic
</router-link>
<router-link class="subnavigation__link" to="/menu-bubble">
Menu Bubble
</router-link>
<router-link class="subnavigation__link" to="/floating-menu">
Floating Menu
</router-link>
<router-link class="subnavigation__link" to="/links">
Links
</router-link>
<router-link class="subnavigation__link" to="/images">
Images
</router-link>
<router-link class="subnavigation__link" to="/text-align">
Text Align
</router-link>
<router-link class="subnavigation__link" to="/hiding-menu-bar">
Hiding Menu Bar
</router-link>
<router-link class="subnavigation__link" to="/todo-list">
Todo List
</router-link>
<router-link class="subnavigation__link" to="/suggestions">
Suggestions
</router-link>
<router-link class="subnavigation__link" to="/markdown-shortcuts">
Markdown Shortcuts
</router-link>
<router-link class="subnavigation__link" to="/code-highlighting">
Code Highlighting
</router-link>
<router-link class="subnavigation__link" to="/read-only">
Read-Only
</router-link>
<router-link class="subnavigation__link" to="/embeds">
Embeds
</router-link>
<router-link class="subnavigation__link" to="/placeholder">
Placeholder
</router-link>
<router-link class="subnavigation__link" to="/export">
Export HTML or JSON
</router-link>
</div>
<div class="subnavigation">
<router-link class="subnavigation__link" to="/">
Basic
</router-link>
<router-link class="subnavigation__link" to="/menu-bubble">
Menu Bubble
</router-link>
<router-link class="subnavigation__link" to="/floating-menu">
Floating Menu
</router-link>
<router-link class="subnavigation__link" to="/links">
Links
</router-link>
<router-link class="subnavigation__link" to="/images">
Images
</router-link>
<router-link class="subnavigation__link" to="/text-align">
Text Align
</router-link>
<router-link class="subnavigation__link" to="/hiding-menu-bar">
Hiding Menu Bar
</router-link>
<router-link class="subnavigation__link" to="/todo-list">
Todo List
</router-link>
<router-link class="subnavigation__link" to="/suggestions">
Suggestions
</router-link>
<router-link class="subnavigation__link" to="/markdown-shortcuts">
Markdown Shortcuts
</router-link>
<router-link class="subnavigation__link" to="/code-highlighting">
Code Highlighting
</router-link>
<router-link class="subnavigation__link" to="/read-only">
Read-Only
</router-link>
<router-link class="subnavigation__link" to="/embeds">
Embeds
</router-link>
<router-link class="subnavigation__link" to="/placeholder">
Placeholder
</router-link>
<router-link class="subnavigation__link" to="/export">
Export HTML or JSON
</router-link>
</div>
</template>
<style lang="scss" src="./style.scss" scoped></style>
<style lang="scss" src="./style.scss" scoped></style>

View File

@@ -2,35 +2,35 @@
.subnavigation {
padding: 0.5rem;
background-color: rgba($color-black, 0.9);
color: $color-white;
text-align: center;
padding: 0.5rem;
background-color: rgba($color-black, 0.9);
color: $color-white;
text-align: center;
@media (min-width: 600px) {
position: sticky;
top: 0;
z-index: 1000;
}
@media (min-width: 600px) {
position: sticky;
top: 0;
z-index: 1000;
}
&__link {
display: inline-block;
color: rgba($color-white, 0.5);
text-decoration: none;
font-weight: bold;
font-size: 0.9rem;
padding: 0.1rem 0.5rem;
border-radius: 3px;
&__link {
display: inline-block;
color: rgba($color-white, 0.5);
text-decoration: none;
font-weight: bold;
font-size: 0.9rem;
padding: 0.1rem 0.5rem;
border-radius: 3px;
&:hover {
color: $color-white;
&:hover {
color: $color-white;
background-color: rgba($color-white, 0.1);
}
&.is-exact-active {
color: $color-white;
color: $color-white;
background-color: rgba($color-white, 0.2);
}
}
}
}
}