initial commit
441
examples/App.vue
Normal file
@@ -0,0 +1,441 @@
|
||||
<template>
|
||||
<div id="app" spellcheck="false">
|
||||
|
||||
<editor :editable="true" class="editor" :doc="data" :extensions="plugins" @update="onUpdate">
|
||||
|
||||
<div class="menububble" slot="menububble" slot-scope="{ marks, focus }">
|
||||
<template v-if="marks">
|
||||
<form class="menububble__form" v-if="linkMenuIsActive" @submit.prevent="setLinkUrl(linkUrl, marks.link, focus)">
|
||||
<input class="menububble__input" type="text" v-model="linkUrl" placeholder="https://" ref="linkInput" @keydown.esc="hideLinkMenu"/>
|
||||
<button class="menububble__button" @click="setLinkUrl(null, marks.link, focus)" type="button">
|
||||
<icon name="remove" />
|
||||
</button>
|
||||
</form>
|
||||
<template v-else>
|
||||
<button class="menububble__button" @click="marks.bold.command" :class="{ 'is-active': marks.bold.active() }">
|
||||
<icon name="bold" />
|
||||
</button>
|
||||
<button class="menububble__button" @click="marks.italic.command" :class="{ 'is-active': marks.italic.active() }">
|
||||
<icon name="italic" />
|
||||
</button>
|
||||
<button class="menububble__button" @click="marks.code.command" :class="{ 'is-active': marks.code.active() }">
|
||||
<icon name="code" />
|
||||
</button>
|
||||
<button class="menububble__button" @click="showLinkMenu(marks.link)" :class="{ 'is-active': marks.link.active() }">
|
||||
<icon name="link" />
|
||||
</button>
|
||||
</template>
|
||||
</template>
|
||||
</div>
|
||||
<div class="menubar" :class="{ 'is-focused': focused }" slot="menubar" slot-scope="{ nodes, focused }">
|
||||
<div v-if="nodes">
|
||||
<button class="menubar__button" @click="nodes.paragraph.command" :class="{ 'is-active': nodes.paragraph.active() }">
|
||||
<icon name="paragraph" />
|
||||
</button>
|
||||
<button class="menubar__button" @click="nodes.heading.command({ level: 1 })" :class="{ 'is-active': nodes.heading.active({ level: 1 }) }">
|
||||
H1
|
||||
</button>
|
||||
<button class="menubar__button" @click="nodes.heading.command({ level: 2 })" :class="{ 'is-active': nodes.heading.active({ level: 2 }) }">
|
||||
H2
|
||||
</button>
|
||||
<button class="menubar__button" @click="nodes.heading.command({ level: 3 })" :class="{ 'is-active': nodes.heading.active({ level: 3 }) }">
|
||||
H3
|
||||
</button>
|
||||
<button class="menubar__button" @click="nodes.bullet_list.command" :class="{ 'is-active': nodes.bullet_list.active() }">
|
||||
<icon name="ul" />
|
||||
</button>
|
||||
<button class="menubar__button" @click="nodes.ordered_list.command" :class="{ 'is-active': nodes.ordered_list.active() }">
|
||||
<icon name="ol" />
|
||||
</button>
|
||||
<button class="menubar__button" @click="nodes.code_block.command" :class="{ 'is-active': nodes.code_block.active() }">
|
||||
<icon name="code" />
|
||||
</button>
|
||||
<button class="menubar__button" @click="nodes.todo_list.command" :class="{ 'is-active': nodes.todo_list.active() }">
|
||||
<icon name="checklist" />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="editor__content" slot="content" slot-scope="props"></div>
|
||||
</editor>
|
||||
|
||||
<pre>{{ data }}</pre>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import Icon from 'Components/Icon'
|
||||
import { Editor } from 'vue-mirror'
|
||||
import MentionPlugin from './plugins/Mention.js'
|
||||
|
||||
export default {
|
||||
components: {
|
||||
Editor,
|
||||
Icon,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
linkUrl: null,
|
||||
linkMenuIsActive: false,
|
||||
plugins: [
|
||||
new MentionPlugin(),
|
||||
],
|
||||
data: {
|
||||
"type": "doc",
|
||||
"content": [
|
||||
{
|
||||
"type": "heading",
|
||||
"attrs": {
|
||||
"level": 1,
|
||||
},
|
||||
"content": [
|
||||
{
|
||||
"type": "text",
|
||||
"text": "A renderless rich-text editor for Vue.js "
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
"type": "paragraph",
|
||||
"content": [
|
||||
{
|
||||
"type": "text",
|
||||
"text": "This editor is based on "
|
||||
},
|
||||
{
|
||||
"type": "text",
|
||||
"marks": [
|
||||
{
|
||||
"type": "link",
|
||||
"attrs": {
|
||||
"href": "https://prosemirror.net"
|
||||
}
|
||||
}
|
||||
],
|
||||
"text": "Prosemirror"
|
||||
},
|
||||
{
|
||||
"type": "text",
|
||||
"text": ", "
|
||||
},
|
||||
{
|
||||
"type": "text",
|
||||
"marks": [
|
||||
{
|
||||
"type": "italic"
|
||||
}
|
||||
],
|
||||
"text": "fully extendable "
|
||||
},
|
||||
{
|
||||
"type": "text",
|
||||
"text": "and renderless. There is a plugin system that lets you render each node as "
|
||||
},
|
||||
{
|
||||
"type": "text",
|
||||
"marks": [
|
||||
{
|
||||
"type": "bold"
|
||||
}
|
||||
],
|
||||
"text": "a vue component. "
|
||||
},
|
||||
{
|
||||
"type": "text",
|
||||
"text": "Things like mentions "
|
||||
},
|
||||
{
|
||||
"type": "mention",
|
||||
"attrs": {
|
||||
"id": "Philipp"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "text",
|
||||
"text": " are also supported."
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "code_block",
|
||||
"content": [
|
||||
{
|
||||
"type": "text",
|
||||
"text": "body {\n display: none;\n}"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "todo_list",
|
||||
"content": [
|
||||
{
|
||||
"type": "todo_item",
|
||||
"attrs": {
|
||||
"done": true
|
||||
},
|
||||
"content": [
|
||||
{
|
||||
"type": "paragraph",
|
||||
"content": [
|
||||
{
|
||||
"type": "text",
|
||||
"text": "There is always something to do"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "todo_item",
|
||||
"attrs": {
|
||||
"done": false
|
||||
},
|
||||
"content": [
|
||||
{
|
||||
"type": "paragraph",
|
||||
"content": [
|
||||
{
|
||||
"type": "text",
|
||||
"text": "This list will never end"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "bullet_list",
|
||||
"content": [
|
||||
{
|
||||
"type": "list_item",
|
||||
"content": [
|
||||
{
|
||||
"type": "paragraph",
|
||||
"content": [
|
||||
{
|
||||
"type": "text",
|
||||
"text": "A regular list"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "list_item",
|
||||
"content": [
|
||||
{
|
||||
"type": "paragraph",
|
||||
"content": [
|
||||
{
|
||||
"type": "text",
|
||||
"text": "With regular items"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "paragraph",
|
||||
"content": [
|
||||
{
|
||||
"type": "text",
|
||||
"text": "It's amazing 👏"
|
||||
},
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
showLinkMenu(type) {
|
||||
this.linkUrl = type.attrs.href
|
||||
this.linkMenuIsActive = true
|
||||
this.$nextTick(() => {
|
||||
this.$refs.linkInput.focus()
|
||||
})
|
||||
},
|
||||
hideLinkMenu() {
|
||||
this.linkUrl = null
|
||||
this.linkMenuIsActive = false
|
||||
},
|
||||
setLinkUrl(url, type, focus) {
|
||||
type.command({ href: url })
|
||||
this.hideLinkMenu()
|
||||
focus()
|
||||
},
|
||||
onUpdate(state) {
|
||||
this.data = state.doc.toJSON()
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
@import "~variables";
|
||||
|
||||
.editor {
|
||||
position: relative;
|
||||
max-width: 30rem;
|
||||
margin: 0 auto 5rem auto;
|
||||
|
||||
&__content {
|
||||
pre {
|
||||
padding: 0.7rem 1rem;
|
||||
border-radius: 5px;
|
||||
background: $color-black;
|
||||
color: $color-white;
|
||||
font-size: 0.8rem;
|
||||
|
||||
code {
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
|
||||
ul,
|
||||
ol {
|
||||
padding-left: 1rem;
|
||||
}
|
||||
|
||||
a {
|
||||
color: inherit;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.menububble {
|
||||
position: absolute;
|
||||
display: flex;
|
||||
z-index: 20;
|
||||
background: $color-black;
|
||||
border-radius: 5px;
|
||||
padding: 0.2rem;
|
||||
margin-bottom: 0.5rem;
|
||||
transform: translateX(-50%);
|
||||
visibility: hidden;
|
||||
opacity: 0;
|
||||
transition: opacity 0.2s, visibility 0.2s;
|
||||
|
||||
&__button {
|
||||
display: inline-flex;
|
||||
background: transparent;
|
||||
border: 0;
|
||||
color: $color-white;
|
||||
padding: 0.2rem 0.5rem;
|
||||
margin-right: 0.2rem;
|
||||
border-radius: 3px;
|
||||
cursor: pointer;
|
||||
|
||||
&:last-child {
|
||||
margin-right: 0;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background-color: rgba($color-white, 0.1);
|
||||
}
|
||||
|
||||
&.is-active {
|
||||
background-color: rgba($color-white, 0.2);
|
||||
}
|
||||
}
|
||||
|
||||
&__form {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
&__input {
|
||||
font: inherit;
|
||||
border: none;
|
||||
background: transparent;
|
||||
color: $color-white;
|
||||
}
|
||||
}
|
||||
|
||||
.menubar {
|
||||
|
||||
display: flex;
|
||||
margin-bottom: 1rem;
|
||||
visibility: hidden;
|
||||
opacity: 0;
|
||||
transition: visibility 0.2s 0.4s, opacity 0.2s 0.4s;
|
||||
|
||||
&.is-focused {
|
||||
visibility: visible;
|
||||
opacity: 1;
|
||||
transition: visibility 0.2s, opacity 0.2s;
|
||||
}
|
||||
|
||||
&__button {
|
||||
font-weight: bold;
|
||||
display: inline-flex;
|
||||
background: transparent;
|
||||
border: 0;
|
||||
color: $color-black;
|
||||
padding: 0.2rem 0.5rem;
|
||||
margin-right: 0.2rem;
|
||||
border-radius: 3px;
|
||||
cursor: pointer;
|
||||
|
||||
&:hover {
|
||||
background-color: rgba($color-black, 0.05);
|
||||
}
|
||||
|
||||
&.is-active {
|
||||
background-color: rgba($color-black, 0.1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.mention {
|
||||
background: rgba($color-black, 0.1);
|
||||
color: rgba($color-black, 0.6);
|
||||
font-size: 0.8rem;
|
||||
font-weight: bold;
|
||||
border-radius: 5px;
|
||||
padding: 0.2rem 0.5rem;
|
||||
}
|
||||
|
||||
ul[data-type="todo_list"] {
|
||||
padding-left: 0;
|
||||
}
|
||||
|
||||
li[data-type="todo_item"] {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
}
|
||||
|
||||
.todo-checkbox {
|
||||
border: 2px solid $color-black;
|
||||
height: 0.9em;
|
||||
width: 0.9em;
|
||||
box-sizing: border-box;
|
||||
margin-right: 10px;
|
||||
margin-top: 0.3rem;
|
||||
user-select: none;
|
||||
-webkit-user-select: none;
|
||||
cursor: pointer;
|
||||
border-radius: 0.2em;
|
||||
background-color: transparent;
|
||||
transition: 0.4s background;
|
||||
}
|
||||
|
||||
.todo-content {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
li[data-done="true"] {
|
||||
text-decoration: line-through;
|
||||
}
|
||||
|
||||
li[data-done="true"] .todo-checkbox {
|
||||
background-color: $color-black;
|
||||
}
|
||||
|
||||
li[data-done="false"] {
|
||||
text-decoration: none;
|
||||
}
|
||||
</style>
|
||||
74
examples/Components/Icon/index.vue
Normal file
@@ -0,0 +1,74 @@
|
||||
<template>
|
||||
<div class="icon" :class="[`icon--${name}`, `icon--${size}`, modifierClasses('icon'), { '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,
|
||||
},
|
||||
},
|
||||
}
|
||||
</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;
|
||||
|
||||
// &.has-align-fix {
|
||||
// top: -.1rem;
|
||||
// }
|
||||
|
||||
&__svg {
|
||||
display: inline-block;
|
||||
vertical-align: top;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
&:first-child {
|
||||
margin-left: 0;
|
||||
}
|
||||
|
||||
&:last-child {
|
||||
margin-right: 0;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// svg sprite
|
||||
body > svg,
|
||||
.icon use > svg,
|
||||
symbol {
|
||||
path,
|
||||
rect,
|
||||
circle,
|
||||
g {
|
||||
fill: currentColor;
|
||||
stroke: none;
|
||||
}
|
||||
|
||||
*[d="M0 0h24v24H0z"] {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
1
examples/assets/images/icons/bold.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><title>text-bold</title><path d="M17.194,10.962A6.271,6.271,0,0,0,12.844.248H4.3a1.25,1.25,0,0,0,0,2.5H5.313a.25.25,0,0,1,.25.25V21a.25.25,0,0,1-.25.25H4.3a1.25,1.25,0,1,0,0,2.5h9.963a6.742,6.742,0,0,0,2.93-12.786Zm-4.35-8.214a3.762,3.762,0,0,1,0,7.523H8.313a.25.25,0,0,1-.25-.25V3a.25.25,0,0,1,.25-.25Zm1.42,18.5H8.313a.25.25,0,0,1-.25-.25V13.021a.25.25,0,0,1,.25-.25h4.531c.017,0,.033,0,.049,0l.013,0h1.358a4.239,4.239,0,0,1,0,8.477Z"/></svg>
|
||||
|
After Width: | Height: | Size: 504 B |
1
examples/assets/images/icons/checklist.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><title>checklist-alternate</title><path d="M21,0H3A3,3,0,0,0,0,3V21a3,3,0,0,0,3,3H21a3,3,0,0,0,3-3V3A3,3,0,0,0,21,0Zm1,21a1,1,0,0,1-1,1H3a1,1,0,0,1-1-1V3A1,1,0,0,1,3,2H21a1,1,0,0,1,1,1Z"/><path d="M11.249,4.5a1.251,1.251,0,0,0-1.75.25L7.365,7.6l-.482-.481A1.25,1.25,0,0,0,5.116,8.883l1.5,1.5A1.262,1.262,0,0,0,8.5,10.249l3-4A1.25,1.25,0,0,0,11.249,4.5Z"/><path d="M11.249,13.5a1.251,1.251,0,0,0-1.75.25L7.365,16.6l-.482-.481a1.25,1.25,0,1,0-1.767,1.768l1.5,1.5A1.265,1.265,0,0,0,8.5,19.249l3-4A1.25,1.25,0,0,0,11.249,13.5Z"/><path d="M18.5,7.749H14a1.25,1.25,0,0,0,0,2.5h4.5a1.25,1.25,0,0,0,0-2.5Z"/><path d="M18.5,15.749H14a1.25,1.25,0,0,0,0,2.5h4.5a1.25,1.25,0,1,0,0-2.5Z"/></svg>
|
||||
|
After Width: | Height: | Size: 742 B |
1
examples/assets/images/icons/code.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><title>angle-brackets</title><path d="M9.147,21.552a1.244,1.244,0,0,1-.895-.378L.84,13.561a2.257,2.257,0,0,1,0-3.125L8.252,2.823a1.25,1.25,0,0,1,1.791,1.744l-6.9,7.083a.5.5,0,0,0,0,.7l6.9,7.082a1.25,1.25,0,0,1-.9,2.122Z"/><path d="M14.854,21.552a1.25,1.25,0,0,1-.9-2.122l6.9-7.083a.5.5,0,0,0,0-.7l-6.9-7.082a1.25,1.25,0,0,1,1.791-1.744l7.411,7.612a2.257,2.257,0,0,1,0,3.125l-7.412,7.614A1.244,1.244,0,0,1,14.854,21.552Zm6.514-9.373h0Z"/></svg>
|
||||
|
After Width: | Height: | Size: 503 B |
1
examples/assets/images/icons/italic.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><title>text-italic</title><path d="M22.5.248H14.863a1.25,1.25,0,0,0,0,2.5h1.086a.25.25,0,0,1,.211.384L4.78,21.017a.5.5,0,0,1-.422.231H1.5a1.25,1.25,0,0,0,0,2.5H9.137a1.25,1.25,0,0,0,0-2.5H8.051a.25.25,0,0,1-.211-.384L19.22,2.98a.5.5,0,0,1,.422-.232H22.5a1.25,1.25,0,0,0,0-2.5Z"/></svg>
|
||||
|
After Width: | Height: | Size: 345 B |
1
examples/assets/images/icons/link.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><title>hyperlink-2</title><path d="M12.406,14.905a1,1,0,0,0-.543,1.307,1,1,0,0,1-.217,1.09L8.818,20.131a2,2,0,0,1-2.828,0L3.868,18.01a2,2,0,0,1,0-2.829L6.7,12.353a1.013,1.013,0,0,1,1.091-.217,1,1,0,0,0,.763-1.849,3.034,3.034,0,0,0-3.268.652L2.454,13.767a4.006,4.006,0,0,0,0,5.657l2.122,2.121a4,4,0,0,0,5.656,0l2.829-2.828a3.008,3.008,0,0,0,.651-3.27A1,1,0,0,0,12.406,14.905Z"/><path d="M7.757,16.241a1.011,1.011,0,0,0,1.414,0L16.95,8.463a1,1,0,0,0-1.414-1.414L7.757,14.827A1,1,0,0,0,7.757,16.241Z"/><path d="M21.546,4.574,19.425,2.453a4.006,4.006,0,0,0-5.657,0L10.939,5.281a3.006,3.006,0,0,0-.651,3.269,1,1,0,1,0,1.849-.764A1,1,0,0,1,12.354,6.7l2.828-2.828a2,2,0,0,1,2.829,0l2.121,2.121a2,2,0,0,1,0,2.829L17.3,11.645a1.015,1.015,0,0,1-1.091.217,1,1,0,0,0-.765,1.849,3.026,3.026,0,0,0,3.27-.651l2.828-2.828A4.007,4.007,0,0,0,21.546,4.574Z"/></svg>
|
||||
|
After Width: | Height: | Size: 906 B |
1
examples/assets/images/icons/ol.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><title>list-numbers</title><path d="M7.75,4.5h15a1,1,0,0,0,0-2h-15a1,1,0,0,0,0,2Z"/><path d="M22.75,11h-15a1,1,0,1,0,0,2h15a1,1,0,0,0,0-2Z"/><path d="M22.75,19.5h-15a1,1,0,0,0,0,2h15a1,1,0,0,0,0-2Z"/><path d="M2.212,17.248A2,2,0,0,0,.279,18.732a.75.75,0,1,0,1.45.386.5.5,0,1,1,.483.63.75.75,0,1,0,0,1.5.5.5,0,1,1-.482.635.75.75,0,1,0-1.445.4,2,2,0,1,0,3.589-1.648.251.251,0,0,1,0-.278,2,2,0,0,0-1.662-3.111Z"/><path d="M4.25,10.748a2,2,0,0,0-4,0,.75.75,0,0,0,1.5,0,.5.5,0,0,1,1,0,1.031,1.031,0,0,1-.227.645L.414,14.029A.75.75,0,0,0,1,15.248H3.5a.75.75,0,0,0,0-1.5H3.081a.249.249,0,0,1-.195-.406L3.7,12.33A2.544,2.544,0,0,0,4.25,10.748Z"/><path d="M4,5.248H3.75A.25.25,0,0,1,3.5,5V1.623A1.377,1.377,0,0,0,2.125.248H1.5a.75.75,0,0,0,0,1.5h.25A.25.25,0,0,1,2,2V5a.25.25,0,0,1-.25.25H1.5a.75.75,0,0,0,0,1.5H4a.75.75,0,0,0,0-1.5Z"/></svg>
|
||||
|
After Width: | Height: | Size: 893 B |
1
examples/assets/images/icons/paragraph.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><title>paragraph</title><path d="M22.5.248H7.228a6.977,6.977,0,1,0,0,13.954H9.546a.25.25,0,0,1,.25.25V22.5a1.25,1.25,0,0,0,2.5,0V3a.25.25,0,0,1,.25-.25h3.682a.25.25,0,0,1,.25.25V22.5a1.25,1.25,0,0,0,2.5,0V3a.249.249,0,0,1,.25-.25H22.5a1.25,1.25,0,0,0,0-2.5ZM9.8,11.452a.25.25,0,0,1-.25.25H7.228a4.477,4.477,0,1,1,0-8.954H9.546A.25.25,0,0,1,9.8,3Z"/></svg>
|
||||
|
After Width: | Height: | Size: 415 B |
1
examples/assets/images/icons/remove.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><title>delete-2-alternate</title><path d="M20.485,3.511A12.01,12.01,0,1,0,24,12,12.009,12.009,0,0,0,20.485,3.511Zm-1.767,15.21A9.51,9.51,0,1,1,21.5,12,9.508,9.508,0,0,1,18.718,18.721Z"/><path d="M16.987,7.01a1.275,1.275,0,0,0-1.8,0l-3.177,3.177L8.829,7.01A1.277,1.277,0,0,0,7.024,8.816L10.2,11.993,7.024,15.171a1.277,1.277,0,0,0,1.805,1.806L12.005,13.8l3.177,3.178a1.277,1.277,0,0,0,1.8-1.806l-3.176-3.178,3.176-3.177A1.278,1.278,0,0,0,16.987,7.01Z"/></svg>
|
||||
|
After Width: | Height: | Size: 517 B |
1
examples/assets/images/icons/ul.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><title>list-bullets</title><circle cx="2.5" cy="3.998" r="2.5"/><path d="M8.5,5H23a1,1,0,0,0,0-2H8.5a1,1,0,0,0,0,2Z"/><circle cx="2.5" cy="11.998" r="2.5"/><path d="M23,11H8.5a1,1,0,0,0,0,2H23a1,1,0,0,0,0-2Z"/><circle cx="2.5" cy="19.998" r="2.5"/><path d="M23,19H8.5a1,1,0,0,0,0,2H23a1,1,0,0,0,0-2Z"/></svg>
|
||||
|
After Width: | Height: | Size: 368 B |
52
examples/assets/sass/main.scss
Normal file
@@ -0,0 +1,52 @@
|
||||
@import "~variables";
|
||||
|
||||
* {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
text-size-adjust: 100%;
|
||||
-webkit-tap-highlight-color: rgba(0, 0, 0, 0);
|
||||
-webkit-touch-callout: none;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
text-rendering: optimizeLegibility;
|
||||
|
||||
&:focus {
|
||||
outline: none;
|
||||
}
|
||||
}
|
||||
|
||||
*::before,
|
||||
*::after {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
html {
|
||||
font-family: -apple-system, BlinkMacSystemFont, San Francisco, Roboto, Segoe UI, Helvetica Neue, sans-serif;
|
||||
font-size: 18px;
|
||||
color: $color-black;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
body {
|
||||
margin: 0;
|
||||
padding: 10% 20%;
|
||||
}
|
||||
|
||||
|
||||
h1,
|
||||
h2,
|
||||
p,
|
||||
ul,
|
||||
ol,
|
||||
pre {
|
||||
margin: 1rem 0;
|
||||
|
||||
&:first-child {
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
&:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
}
|
||||
2
examples/assets/sass/variables.scss
Normal file
@@ -0,0 +1,2 @@
|
||||
$color-black: #222222;
|
||||
$color-white: #ffffff;
|
||||
80
examples/helpers/svg-sprite-loader.js
Normal file
@@ -0,0 +1,80 @@
|
||||
;(function(window, document) {
|
||||
'use strict';
|
||||
|
||||
var isSvg = document.createElementNS && document.createElementNS( 'http://www.w3.org/2000/svg', 'svg' ).createSVGRect;
|
||||
var localStorage = 'localStorage' in window && window['localStorage'] !== null ? window.localStorage : false;
|
||||
|
||||
function svgSpriteInjector(source, opts) {
|
||||
var file;
|
||||
opts = opts || {};
|
||||
|
||||
if (source instanceof Node) {
|
||||
file = source.getAttribute('data-svg-sprite');
|
||||
opts.revision = source.getAttribute('data-svg-sprite-revision') || opts.revision;
|
||||
} else if (typeof source === 'string') {
|
||||
file = source;
|
||||
}
|
||||
|
||||
if (isSvg) {
|
||||
if (file) {
|
||||
injector(file, opts);
|
||||
} else {
|
||||
console.error('svg-sprite-injector: undefined sprite filename!');
|
||||
}
|
||||
} else {
|
||||
console.error('svg-sprite-injector require ie9 or greater!');
|
||||
}
|
||||
};
|
||||
|
||||
function injector(filepath, opts) {
|
||||
var name = 'injectedSVGSprite' + filepath,
|
||||
revision = opts.revision,
|
||||
request;
|
||||
|
||||
// localStorage cache
|
||||
if (revision !== undefined && localStorage && localStorage[name + 'Rev'] == revision) {
|
||||
return injectOnLoad(localStorage[name]);
|
||||
}
|
||||
|
||||
// Async load
|
||||
request = new XMLHttpRequest();
|
||||
request.open('GET', filepath, true);
|
||||
request.onreadystatechange = function (e) {
|
||||
var data;
|
||||
|
||||
if (request.readyState === 4 && request.status >= 200 && request.status < 400) {
|
||||
injectOnLoad(data = request.responseText);
|
||||
if (revision !== undefined && localStorage) {
|
||||
localStorage[name] = data;
|
||||
localStorage[name + 'Rev'] = revision;
|
||||
}
|
||||
}
|
||||
};
|
||||
request.send();
|
||||
}
|
||||
|
||||
function injectOnLoad(data) {
|
||||
if (data) {
|
||||
if (document.body) {
|
||||
injectData(data);
|
||||
} else {
|
||||
document.addEventListener('DOMContentLoaded', injectData.bind(null, data));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function injectData(data) {
|
||||
var body = document.body;
|
||||
body.insertAdjacentHTML('afterbegin', data);
|
||||
if (body.firstChild.tagName === 'svg') {
|
||||
body.firstChild.style.display = 'none';
|
||||
}
|
||||
}
|
||||
|
||||
if (typeof exports === 'object') {
|
||||
module.exports = svgSpriteInjector;
|
||||
} else {
|
||||
window.svgSpriteInjector = svgSpriteInjector;
|
||||
}
|
||||
|
||||
} (window, document));
|
||||
13
examples/index.html
Normal file
@@ -0,0 +1,13 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta http-equiv="x-ua-compatible" content="ie=edge">
|
||||
<title>Editor</title>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<meta name="mobile-web-app-capable" content="yes">
|
||||
</head>
|
||||
<body>
|
||||
<div id="app"></div>
|
||||
</body>
|
||||
</html>
|
||||
27
examples/main.js
Normal file
@@ -0,0 +1,27 @@
|
||||
import 'babel-polyfill'
|
||||
import Vue from 'vue'
|
||||
import svgSpriteLoader from 'helpers/svg-sprite-loader'
|
||||
import App from './App.vue'
|
||||
|
||||
const __svg__ = { path: './assets/images/icons/*.svg', name: 'assets/images/[hash].sprite.svg' }
|
||||
svgSpriteLoader(__svg__.filename)
|
||||
|
||||
Vue.config.productionTip = false
|
||||
|
||||
Vue.mixin({
|
||||
methods: {
|
||||
modifierClasses(base, modifier = this.modifier) {
|
||||
const classList = [modifier].flatten()
|
||||
|
||||
if (classList.length === 0) {
|
||||
return null
|
||||
}
|
||||
|
||||
return `${base}--${classList.join(` ${base}--`)}`
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
new Vue({
|
||||
render: h => h(App),
|
||||
}).$mount('#app')
|
||||
30
examples/plugins/Mention.js
Normal file
@@ -0,0 +1,30 @@
|
||||
import { Node } from 'vue-mirror/utils'
|
||||
|
||||
export default class MentionNode extends Node {
|
||||
|
||||
get name() {
|
||||
return 'mention'
|
||||
}
|
||||
|
||||
get schema() {
|
||||
return {
|
||||
attrs: {
|
||||
id: {
|
||||
default: null,
|
||||
},
|
||||
},
|
||||
group: 'inline',
|
||||
inline: true,
|
||||
draggable: true,
|
||||
toDOM: node => [
|
||||
'span',
|
||||
{
|
||||
dataId: node.attrs.id,
|
||||
class: 'mention',
|
||||
},
|
||||
`@${node.attrs.id}`,
|
||||
],
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||