tabs to spaces whitespace
@@ -5,20 +5,20 @@ import config from './webpack.config'
|
||||
const spinner = ora('Building …')
|
||||
|
||||
export default new Promise((resolve, reject) => {
|
||||
spinner.start()
|
||||
spinner.start()
|
||||
|
||||
webpack(config, (error, stats) => {
|
||||
if (error) {
|
||||
return reject(error)
|
||||
}
|
||||
webpack(config, (error, stats) => {
|
||||
if (error) {
|
||||
return reject(error)
|
||||
}
|
||||
|
||||
if (stats.hasErrors()) {
|
||||
process.stdout.write(stats.toString() + "\n");
|
||||
return reject(new Error('Build failed with errors.'))
|
||||
}
|
||||
if (stats.hasErrors()) {
|
||||
process.stdout.write(stats.toString() + "\n");
|
||||
return reject(new Error('Build failed with errors.'))
|
||||
}
|
||||
|
||||
return resolve('Build complete.')
|
||||
})
|
||||
return resolve('Build complete.')
|
||||
})
|
||||
})
|
||||
.then(success => spinner.succeed(success))
|
||||
.catch(error => spinner.fail(error))
|
||||
|
||||
@@ -16,11 +16,11 @@ middlewares.push(historyApiFallbackMiddleware())
|
||||
|
||||
// add webpack stuff
|
||||
middlewares.push(webpackDevMiddleware(bundler, {
|
||||
publicPath: config.output.publicPath,
|
||||
stats: {
|
||||
colors: true,
|
||||
chunks: false,
|
||||
},
|
||||
publicPath: config.output.publicPath,
|
||||
stats: {
|
||||
colors: true,
|
||||
chunks: false,
|
||||
},
|
||||
}))
|
||||
|
||||
// add hot reloading
|
||||
@@ -30,25 +30,25 @@ middlewares.push(webpackHotMiddleware(bundler))
|
||||
const url = 'http://localhost'
|
||||
const bs = browserSync.create()
|
||||
const server = bs.init({
|
||||
server: {
|
||||
baseDir: `${srcPath}/`,
|
||||
middleware: middlewares,
|
||||
},
|
||||
files: [],
|
||||
logLevel: 'silent',
|
||||
open: false,
|
||||
notify: false,
|
||||
injectChanges: false,
|
||||
ghostMode: {
|
||||
clicks: false,
|
||||
forms: false,
|
||||
scroll: false,
|
||||
},
|
||||
server: {
|
||||
baseDir: `${srcPath}/`,
|
||||
middleware: middlewares,
|
||||
},
|
||||
files: [],
|
||||
logLevel: 'silent',
|
||||
open: false,
|
||||
notify: false,
|
||||
injectChanges: false,
|
||||
ghostMode: {
|
||||
clicks: false,
|
||||
forms: false,
|
||||
scroll: false,
|
||||
},
|
||||
})
|
||||
|
||||
console.log(`${url}:${server.options.get('port')}`)
|
||||
|
||||
// sass import
|
||||
bs.watch(path.join(sassImportPath, '**/!(index|index_sub).scss'), { ignoreInitial: true }, () => {
|
||||
sassImport(sassImportPath)
|
||||
sassImport(sassImportPath)
|
||||
})
|
||||
|
||||
@@ -6,11 +6,11 @@ import minimist from 'minimist'
|
||||
let argv = minimist(process.argv.slice(2))
|
||||
|
||||
export function removeEmpty(array) {
|
||||
return array.filter(entry => !!entry)
|
||||
return array.filter(entry => !!entry)
|
||||
}
|
||||
|
||||
export function ifElse(condition) {
|
||||
return (then, otherwise) => (condition ? then : otherwise)
|
||||
return (then, otherwise) => (condition ? then : otherwise)
|
||||
}
|
||||
|
||||
export const env = argv.env || 'development'
|
||||
@@ -23,25 +23,25 @@ export const ifProd = ifElse(isProd)
|
||||
export const ifTest = ifElse(isTest)
|
||||
|
||||
export function sassImport(basePath) {
|
||||
const indexFileName = 'index.scss'
|
||||
glob.sync(`${basePath}/**/${indexFileName}`).forEach(sourceFile => {
|
||||
fs.writeFileSync(sourceFile, '// This is a dynamically generated file \n\n')
|
||||
glob.sync(`${path.dirname(sourceFile)}/*.scss`).forEach(file => {
|
||||
if (path.basename(file) !== indexFileName) {
|
||||
fs.appendFileSync(sourceFile, `@import "${path.basename(file)}";\n`)
|
||||
}
|
||||
})
|
||||
})
|
||||
const indexFileName = 'index.scss'
|
||||
glob.sync(`${basePath}/**/${indexFileName}`).forEach(sourceFile => {
|
||||
fs.writeFileSync(sourceFile, '// This is a dynamically generated file \n\n')
|
||||
glob.sync(`${path.dirname(sourceFile)}/*.scss`).forEach(file => {
|
||||
if (path.basename(file) !== indexFileName) {
|
||||
fs.appendFileSync(sourceFile, `@import "${path.basename(file)}";\n`)
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
const indexSubFileName = 'index_sub.scss'
|
||||
glob.sync(`${basePath}/**/${indexSubFileName}`).forEach(sourceFile => {
|
||||
fs.writeFileSync(sourceFile, '// This is a dynamically generated file \n\n')
|
||||
glob.sync(`${path.dirname(sourceFile)}/**/*.scss`).forEach(file => {
|
||||
if (path.basename(file) !== indexSubFileName) {
|
||||
let importPath = (path.dirname(sourceFile) === path.dirname(file)) ? path.basename(file) : file
|
||||
importPath = importPath.replace(`${path.dirname(sourceFile)}/`, '')
|
||||
fs.appendFileSync(sourceFile, `@import "${importPath}";\n`)
|
||||
}
|
||||
})
|
||||
})
|
||||
const indexSubFileName = 'index_sub.scss'
|
||||
glob.sync(`${basePath}/**/${indexSubFileName}`).forEach(sourceFile => {
|
||||
fs.writeFileSync(sourceFile, '// This is a dynamically generated file \n\n')
|
||||
glob.sync(`${path.dirname(sourceFile)}/**/*.scss`).forEach(file => {
|
||||
if (path.basename(file) !== indexSubFileName) {
|
||||
let importPath = (path.dirname(sourceFile) === path.dirname(file)) ? path.basename(file) : file
|
||||
importPath = importPath.replace(`${path.dirname(sourceFile)}/`, '')
|
||||
fs.appendFileSync(sourceFile, `@import "${importPath}";\n`)
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
@@ -13,214 +13,214 @@ import { rootPath, srcPath, buildPath } from './paths'
|
||||
|
||||
export default {
|
||||
|
||||
mode: ifDev('development', 'production'),
|
||||
mode: ifDev('development', 'production'),
|
||||
|
||||
entry: {
|
||||
app: removeEmpty([
|
||||
ifDev('webpack-hot-middleware/client?reload=true'),
|
||||
`${srcPath}/assets/sass/main.scss`,
|
||||
`${srcPath}/main.js`,
|
||||
]),
|
||||
},
|
||||
entry: {
|
||||
app: removeEmpty([
|
||||
ifDev('webpack-hot-middleware/client?reload=true'),
|
||||
`${srcPath}/assets/sass/main.scss`,
|
||||
`${srcPath}/main.js`,
|
||||
]),
|
||||
},
|
||||
|
||||
output: {
|
||||
path: `${buildPath}/`,
|
||||
filename: `assets/js/[name]${ifProd('.[hash]', '')}.js`,
|
||||
chunkFilename: `assets/js/[name]${ifProd('.[chunkhash]', '')}.js`,
|
||||
publicPath: '/',
|
||||
},
|
||||
output: {
|
||||
path: `${buildPath}/`,
|
||||
filename: `assets/js/[name]${ifProd('.[hash]', '')}.js`,
|
||||
chunkFilename: `assets/js/[name]${ifProd('.[chunkhash]', '')}.js`,
|
||||
publicPath: '/',
|
||||
},
|
||||
|
||||
resolve: {
|
||||
extensions: ['.js', '.scss', '.vue'],
|
||||
alias: {
|
||||
vue$: 'vue/dist/vue.esm.js',
|
||||
modules: path.resolve(rootPath, '../node_modules'),
|
||||
images: `${srcPath}/assets/images`,
|
||||
fonts: `${srcPath}/assets/fonts`,
|
||||
variables: `${srcPath}/assets/sass/variables`,
|
||||
tiptap: path.resolve(rootPath, '../packages/tiptap/src'),
|
||||
'tiptap-commands': path.resolve(rootPath, '../packages/tiptap-commands/src'),
|
||||
'tiptap-utils': path.resolve(rootPath, '../packages/tiptap-utils/src'),
|
||||
'tiptap-models': path.resolve(rootPath, '../packages/tiptap-models/src'),
|
||||
'tiptap-extensions': path.resolve(rootPath, '../packages/tiptap-extensions/src'),
|
||||
},
|
||||
modules: [
|
||||
srcPath,
|
||||
path.resolve(rootPath, '../node_modules'),
|
||||
],
|
||||
},
|
||||
resolve: {
|
||||
extensions: ['.js', '.scss', '.vue'],
|
||||
alias: {
|
||||
vue$: 'vue/dist/vue.esm.js',
|
||||
modules: path.resolve(rootPath, '../node_modules'),
|
||||
images: `${srcPath}/assets/images`,
|
||||
fonts: `${srcPath}/assets/fonts`,
|
||||
variables: `${srcPath}/assets/sass/variables`,
|
||||
tiptap: path.resolve(rootPath, '../packages/tiptap/src'),
|
||||
'tiptap-commands': path.resolve(rootPath, '../packages/tiptap-commands/src'),
|
||||
'tiptap-utils': path.resolve(rootPath, '../packages/tiptap-utils/src'),
|
||||
'tiptap-models': path.resolve(rootPath, '../packages/tiptap-models/src'),
|
||||
'tiptap-extensions': path.resolve(rootPath, '../packages/tiptap-extensions/src'),
|
||||
},
|
||||
modules: [
|
||||
srcPath,
|
||||
path.resolve(rootPath, '../node_modules'),
|
||||
],
|
||||
},
|
||||
|
||||
devtool: ifDev('eval-source-map', 'source-map'),
|
||||
devtool: ifDev('eval-source-map', 'source-map'),
|
||||
|
||||
module: {
|
||||
rules: [
|
||||
{
|
||||
test: /\.vue$/,
|
||||
loader: 'vue-loader',
|
||||
},
|
||||
{
|
||||
test: /\.js$/,
|
||||
exclude: [/node_modules/],
|
||||
use: {
|
||||
loader: ifDev('babel-loader?cacheDirectory=true', 'babel-loader'),
|
||||
options: {
|
||||
presets: [
|
||||
'@babel/preset-env',
|
||||
],
|
||||
plugins: [
|
||||
'@babel/plugin-syntax-dynamic-import',
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
test: /\.css$/,
|
||||
use: removeEmpty([
|
||||
ifDev('vue-style-loader', MiniCssExtractPlugin.loader),
|
||||
'css-loader',
|
||||
'postcss-loader',
|
||||
]),
|
||||
},
|
||||
{
|
||||
test: /\.scss$/,
|
||||
use: removeEmpty([
|
||||
ifDev('vue-style-loader', MiniCssExtractPlugin.loader),
|
||||
'css-loader',
|
||||
'postcss-loader',
|
||||
'sass-loader',
|
||||
]),
|
||||
},
|
||||
{
|
||||
test: /\.(png|jpe?g|gif|svg|ico)(\?.*)?$/,
|
||||
use: {
|
||||
loader: 'file-loader',
|
||||
options: {
|
||||
name: `assets/images/[name]${ifProd('.[hash]', '')}.[ext]`,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
test: /\.(woff2?|eot|ttf|otf)(\?.*)?$/,
|
||||
use: {
|
||||
loader: 'file-loader',
|
||||
options: {
|
||||
name: `assets/fonts/[name]${ifProd('.[hash]', '')}.[ext]`,
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
module: {
|
||||
rules: [
|
||||
{
|
||||
test: /\.vue$/,
|
||||
loader: 'vue-loader',
|
||||
},
|
||||
{
|
||||
test: /\.js$/,
|
||||
exclude: [/node_modules/],
|
||||
use: {
|
||||
loader: ifDev('babel-loader?cacheDirectory=true', 'babel-loader'),
|
||||
options: {
|
||||
presets: [
|
||||
'@babel/preset-env',
|
||||
],
|
||||
plugins: [
|
||||
'@babel/plugin-syntax-dynamic-import',
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
test: /\.css$/,
|
||||
use: removeEmpty([
|
||||
ifDev('vue-style-loader', MiniCssExtractPlugin.loader),
|
||||
'css-loader',
|
||||
'postcss-loader',
|
||||
]),
|
||||
},
|
||||
{
|
||||
test: /\.scss$/,
|
||||
use: removeEmpty([
|
||||
ifDev('vue-style-loader', MiniCssExtractPlugin.loader),
|
||||
'css-loader',
|
||||
'postcss-loader',
|
||||
'sass-loader',
|
||||
]),
|
||||
},
|
||||
{
|
||||
test: /\.(png|jpe?g|gif|svg|ico)(\?.*)?$/,
|
||||
use: {
|
||||
loader: 'file-loader',
|
||||
options: {
|
||||
name: `assets/images/[name]${ifProd('.[hash]', '')}.[ext]`,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
test: /\.(woff2?|eot|ttf|otf)(\?.*)?$/,
|
||||
use: {
|
||||
loader: 'file-loader',
|
||||
options: {
|
||||
name: `assets/fonts/[name]${ifProd('.[hash]', '')}.[ext]`,
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
|
||||
// splitting out the vendor
|
||||
optimization: {
|
||||
namedModules: true,
|
||||
splitChunks: {
|
||||
name: 'vendor',
|
||||
minChunks: 2,
|
||||
},
|
||||
noEmitOnErrors: true,
|
||||
// concatenateModules: true,
|
||||
},
|
||||
// splitting out the vendor
|
||||
optimization: {
|
||||
namedModules: true,
|
||||
splitChunks: {
|
||||
name: 'vendor',
|
||||
minChunks: 2,
|
||||
},
|
||||
noEmitOnErrors: true,
|
||||
// concatenateModules: true,
|
||||
},
|
||||
|
||||
plugins: removeEmpty([
|
||||
plugins: removeEmpty([
|
||||
|
||||
// create manifest file for server-side asset manipulation
|
||||
new ManifestPlugin({
|
||||
fileName: 'assets/manifest.json',
|
||||
writeToFileEmit: true,
|
||||
}),
|
||||
// create manifest file for server-side asset manipulation
|
||||
new ManifestPlugin({
|
||||
fileName: 'assets/manifest.json',
|
||||
writeToFileEmit: true,
|
||||
}),
|
||||
|
||||
// define env
|
||||
// new webpack.DefinePlugin({
|
||||
// 'process.env': {},
|
||||
// }),
|
||||
// define env
|
||||
// new webpack.DefinePlugin({
|
||||
// 'process.env': {},
|
||||
// }),
|
||||
|
||||
// copy static files
|
||||
new CopyWebpackPlugin([
|
||||
{
|
||||
context: `${srcPath}/assets/static`,
|
||||
from: { glob: '**/*', dot: false },
|
||||
to: `${buildPath}/assets`,
|
||||
},
|
||||
{
|
||||
context: `${srcPath}/assets/static`,
|
||||
from: { glob: '**/*', dot: false },
|
||||
to: `${buildPath}/assets/[path][name].[hash].[ext]`,
|
||||
},
|
||||
]),
|
||||
// copy static files
|
||||
new CopyWebpackPlugin([
|
||||
{
|
||||
context: `${srcPath}/assets/static`,
|
||||
from: { glob: '**/*', dot: false },
|
||||
to: `${buildPath}/assets`,
|
||||
},
|
||||
{
|
||||
context: `${srcPath}/assets/static`,
|
||||
from: { glob: '**/*', dot: false },
|
||||
to: `${buildPath}/assets/[path][name].[hash].[ext]`,
|
||||
},
|
||||
]),
|
||||
|
||||
// enable hot reloading
|
||||
ifDev(new webpack.HotModuleReplacementPlugin()),
|
||||
// enable hot reloading
|
||||
ifDev(new webpack.HotModuleReplacementPlugin()),
|
||||
|
||||
// html
|
||||
new HtmlWebpackPlugin({
|
||||
filename: 'index.html',
|
||||
template: `${srcPath}/index.html`,
|
||||
inject: true,
|
||||
minify: ifProd({
|
||||
removeComments: true,
|
||||
collapseWhitespace: true,
|
||||
removeAttributeQuotes: true,
|
||||
}),
|
||||
buildVersion: new Date().valueOf(),
|
||||
chunksSortMode: 'none',
|
||||
}),
|
||||
// html
|
||||
new HtmlWebpackPlugin({
|
||||
filename: 'index.html',
|
||||
template: `${srcPath}/index.html`,
|
||||
inject: true,
|
||||
minify: ifProd({
|
||||
removeComments: true,
|
||||
collapseWhitespace: true,
|
||||
removeAttributeQuotes: true,
|
||||
}),
|
||||
buildVersion: new Date().valueOf(),
|
||||
chunksSortMode: 'none',
|
||||
}),
|
||||
|
||||
new VueLoaderPlugin(),
|
||||
new VueLoaderPlugin(),
|
||||
|
||||
// create css files
|
||||
ifProd(new MiniCssExtractPlugin({
|
||||
filename: `assets/css/[name]${ifProd('.[hash]', '')}.css`,
|
||||
chunkFilename: `assets/css/[name]${ifProd('.[hash]', '')}.css`,
|
||||
})),
|
||||
// create css files
|
||||
ifProd(new MiniCssExtractPlugin({
|
||||
filename: `assets/css/[name]${ifProd('.[hash]', '')}.css`,
|
||||
chunkFilename: `assets/css/[name]${ifProd('.[hash]', '')}.css`,
|
||||
})),
|
||||
|
||||
// minify css files
|
||||
ifProd(new OptimizeCssAssetsPlugin({
|
||||
cssProcessorOptions: {
|
||||
reduceIdents: false,
|
||||
autoprefixer: false,
|
||||
zindex: false,
|
||||
discardComments: {
|
||||
removeAll: true,
|
||||
},
|
||||
},
|
||||
})),
|
||||
// minify css files
|
||||
ifProd(new OptimizeCssAssetsPlugin({
|
||||
cssProcessorOptions: {
|
||||
reduceIdents: false,
|
||||
autoprefixer: false,
|
||||
zindex: false,
|
||||
discardComments: {
|
||||
removeAll: true,
|
||||
},
|
||||
},
|
||||
})),
|
||||
|
||||
// svg icons
|
||||
new SvgStore({
|
||||
prefix: 'icon--',
|
||||
svgoOptions: {
|
||||
plugins: [
|
||||
{ cleanupIDs: false },
|
||||
{ collapseGroups: false },
|
||||
{ removeTitle: true },
|
||||
],
|
||||
},
|
||||
}),
|
||||
// svg icons
|
||||
new SvgStore({
|
||||
prefix: 'icon--',
|
||||
svgoOptions: {
|
||||
plugins: [
|
||||
{ cleanupIDs: false },
|
||||
{ collapseGroups: false },
|
||||
{ removeTitle: true },
|
||||
],
|
||||
},
|
||||
}),
|
||||
|
||||
// image optimization
|
||||
new ImageminWebpackPlugin({
|
||||
optipng: ifDev(null, {
|
||||
optimizationLevel: 3,
|
||||
}),
|
||||
jpegtran: ifDev(null, {
|
||||
progressive: true,
|
||||
quality: 80,
|
||||
}),
|
||||
svgo: ifDev(null, {
|
||||
plugins: [
|
||||
{ cleanupIDs: false },
|
||||
{ removeViewBox: false },
|
||||
{ removeUselessStrokeAndFill: false },
|
||||
{ removeEmptyAttrs: false },
|
||||
],
|
||||
}),
|
||||
}),
|
||||
// image optimization
|
||||
new ImageminWebpackPlugin({
|
||||
optipng: ifDev(null, {
|
||||
optimizationLevel: 3,
|
||||
}),
|
||||
jpegtran: ifDev(null, {
|
||||
progressive: true,
|
||||
quality: 80,
|
||||
}),
|
||||
svgo: ifDev(null, {
|
||||
plugins: [
|
||||
{ cleanupIDs: false },
|
||||
{ removeViewBox: false },
|
||||
{ removeUselessStrokeAndFill: false },
|
||||
{ removeEmptyAttrs: false },
|
||||
],
|
||||
}),
|
||||
}),
|
||||
|
||||
]),
|
||||
]),
|
||||
|
||||
node: {
|
||||
fs: 'empty',
|
||||
},
|
||||
node: {
|
||||
fs: 'empty',
|
||||
},
|
||||
|
||||
}
|
||||
|
||||
@@ -9,7 +9,7 @@ import packagejson from '../../package.json'
|
||||
|
||||
const { version } = packagejson
|
||||
const banner = `
|
||||
/*!
|
||||
/*!
|
||||
* tiptap v${version}
|
||||
* (c) ${new Date().getFullYear()} Scrumpy UG (limited liability)
|
||||
* @license MIT
|
||||
@@ -23,19 +23,19 @@ function genConfig(opts) {
|
||||
input: {
|
||||
input: opts.input,
|
||||
plugins: [
|
||||
flow(),
|
||||
flow(),
|
||||
node(),
|
||||
cjs(),
|
||||
vue({
|
||||
css: true,
|
||||
compileTemplate: true,
|
||||
}),
|
||||
vue({
|
||||
css: true,
|
||||
compileTemplate: true,
|
||||
}),
|
||||
replace({
|
||||
__VERSION__: version,
|
||||
}),
|
||||
buble({
|
||||
objectAssign: 'Object.assign',
|
||||
}),
|
||||
objectAssign: 'Object.assign',
|
||||
}),
|
||||
],
|
||||
external(id) { return !/^[\.\/]/.test(id) },
|
||||
},
|
||||
@@ -44,7 +44,7 @@ function genConfig(opts) {
|
||||
format: opts.format,
|
||||
banner,
|
||||
name: 'tiptap',
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
if (opts.env) {
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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)
|
||||
;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
`,
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><title>paragraph-center-align-alternate</title><path d="M23,22H1a1,1,0,1,0,0,2H23a1,1,0,0,0,0-2Z"/><path d="M19,18.5a1,1,0,0,0,0-2H4.5a1,1,0,1,0,0,2Z"/><path d="M23,11H1a1,1,0,0,0,0,2H23a1,1,0,0,0,0-2Z"/><path d="M4.5,5.5a1,1,0,1,0,0,2H19a1,1,0,0,0,0-2Z"/><path d="M1,2H23a1,1,0,0,0,0-2H1A1,1,0,0,0,1,2Z"/></svg>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><title>paragraph-center-align-alternate</title><path d="M23,22H1a1,1,0,1,0,0,2H23a1,1,0,0,0,0-2Z"/><path d="M19,18.5a1,1,0,0,0,0-2H4.5a1,1,0,1,0,0,2Z"/><path d="M23,11H1a1,1,0,0,0,0,2H23a1,1,0,0,0,0-2Z"/><path d="M4.5,5.5a1,1,0,1,0,0,2H19a1,1,0,0,0,0-2Z"/><path d="M1,2H23a1,1,0,0,0,0-2H1A1,1,0,0,0,1,2Z"/></svg>
|
||||
|
||||
|
Before Width: | Height: | Size: 372 B After Width: | Height: | Size: 373 B |
@@ -1 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><title>paragraph-left-align-alternate</title><path d="M23,22H1a1,1,0,1,0,0,2H23a1,1,0,0,0,0-2Z"/><path d="M1,18.5H19a1,1,0,0,0,0-2H1a1,1,0,1,0,0,2Z"/><path d="M23,11H1a1,1,0,0,0,0,2H23a1,1,0,0,0,0-2Z"/><path d="M1,7.5H19a1,1,0,0,0,0-2H1a1,1,0,0,0,0,2Z"/><path d="M1,2H23a1,1,0,0,0,0-2H1A1,1,0,0,0,1,2Z"/></svg>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><title>paragraph-left-align-alternate</title><path d="M23,22H1a1,1,0,1,0,0,2H23a1,1,0,0,0,0-2Z"/><path d="M1,18.5H19a1,1,0,0,0,0-2H1a1,1,0,1,0,0,2Z"/><path d="M23,11H1a1,1,0,0,0,0,2H23a1,1,0,0,0,0-2Z"/><path d="M1,7.5H19a1,1,0,0,0,0-2H1a1,1,0,0,0,0,2Z"/><path d="M1,2H23a1,1,0,0,0,0-2H1A1,1,0,0,0,1,2Z"/></svg>
|
||||
|
||||
|
Before Width: | Height: | Size: 370 B After Width: | Height: | Size: 371 B |
@@ -1 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><title>paragraph-right-align-alternate</title><path d="M23,22H1a1,1,0,1,0,0,2H23a1,1,0,0,0,0-2Z"/><path d="M23,16.5H4.5a1,1,0,1,0,0,2H23a1,1,0,0,0,0-2Z"/><path d="M23,11H1a1,1,0,0,0,0,2H23a1,1,0,0,0,0-2Z"/><path d="M23,5.5H4.5a1,1,0,1,0,0,2H23a1,1,0,0,0,0-2Z"/><path d="M1,2H23a1,1,0,0,0,0-2H1A1,1,0,0,0,1,2Z"/></svg>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><title>paragraph-right-align-alternate</title><path d="M23,22H1a1,1,0,1,0,0,2H23a1,1,0,0,0,0-2Z"/><path d="M23,16.5H4.5a1,1,0,1,0,0,2H23a1,1,0,0,0,0-2Z"/><path d="M23,11H1a1,1,0,0,0,0,2H23a1,1,0,0,0,0-2Z"/><path d="M23,5.5H4.5a1,1,0,1,0,0,2H23a1,1,0,0,0,0-2Z"/><path d="M1,2H23a1,1,0,0,0,0-2H1A1,1,0,0,0,1,2Z"/></svg>
|
||||
|
||||
|
Before Width: | Height: | Size: 377 B After Width: | Height: | Size: 378 B |
@@ -1 +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>
|
||||
<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>
|
||||
|
||||
|
Before Width: | Height: | Size: 504 B After Width: | Height: | Size: 505 B |
@@ -1 +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>
|
||||
<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>
|
||||
|
||||
|
Before Width: | Height: | Size: 742 B After Width: | Height: | Size: 743 B |
@@ -1 +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>
|
||||
<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>
|
||||
|
||||
|
Before Width: | Height: | Size: 503 B After Width: | Height: | Size: 504 B |
@@ -1 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><title>paginate-filter-picture-alternate</title><circle cx="9.75" cy="6.247" r="2.25"/><path d="M16.916,8.71A1.027,1.027,0,0,0,16,8.158a1.007,1.007,0,0,0-.892.586L13.55,12.178a.249.249,0,0,1-.422.053l-.82-1.024a1,1,0,0,0-.813-.376,1.007,1.007,0,0,0-.787.426L7.59,15.71A.5.5,0,0,0,8,16.5H20a.5.5,0,0,0,.425-.237.5.5,0,0,0,.022-.486Z"/><path d="M22,0H5.5a2,2,0,0,0-2,2V18.5a2,2,0,0,0,2,2H22a2,2,0,0,0,2-2V2A2,2,0,0,0,22,0Zm-.145,18.354a.5.5,0,0,1-.354.146H6a.5.5,0,0,1-.5-.5V2.5A.5.5,0,0,1,6,2H21.5a.5.5,0,0,1,.5.5V18A.5.5,0,0,1,21.855,18.351Z"/><path d="M19.5,22H2.5a.5.5,0,0,1-.5-.5V4.5a1,1,0,0,0-2,0V22a2,2,0,0,0,2,2H19.5a1,1,0,0,0,0-2Z"/></svg>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><title>paginate-filter-picture-alternate</title><circle cx="9.75" cy="6.247" r="2.25"/><path d="M16.916,8.71A1.027,1.027,0,0,0,16,8.158a1.007,1.007,0,0,0-.892.586L13.55,12.178a.249.249,0,0,1-.422.053l-.82-1.024a1,1,0,0,0-.813-.376,1.007,1.007,0,0,0-.787.426L7.59,15.71A.5.5,0,0,0,8,16.5H20a.5.5,0,0,0,.425-.237.5.5,0,0,0,.022-.486Z"/><path d="M22,0H5.5a2,2,0,0,0-2,2V18.5a2,2,0,0,0,2,2H22a2,2,0,0,0,2-2V2A2,2,0,0,0,22,0Zm-.145,18.354a.5.5,0,0,1-.354.146H6a.5.5,0,0,1-.5-.5V2.5A.5.5,0,0,1,6,2H21.5a.5.5,0,0,1,.5.5V18A.5.5,0,0,1,21.855,18.351Z"/><path d="M19.5,22H2.5a.5.5,0,0,1-.5-.5V4.5a1,1,0,0,0-2,0V22a2,2,0,0,0,2,2H19.5a1,1,0,0,0,0-2Z"/></svg>
|
||||
|
||||
|
Before Width: | Height: | Size: 706 B After Width: | Height: | Size: 707 B |
@@ -1 +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>
|
||||
<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>
|
||||
|
||||
|
Before Width: | Height: | Size: 345 B After Width: | Height: | Size: 346 B |
@@ -1 +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>
|
||||
<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>
|
||||
|
||||
|
Before Width: | Height: | Size: 906 B After Width: | Height: | Size: 907 B |
@@ -1 +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>
|
||||
<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>
|
||||
|
||||
|
Before Width: | Height: | Size: 893 B After Width: | Height: | Size: 894 B |
@@ -1 +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>
|
||||
<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>
|
||||
|
||||
|
Before Width: | Height: | Size: 415 B After Width: | Height: | Size: 416 B |
@@ -1 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><title>close-quote</title><path d="M18.559,3.932a4.942,4.942,0,1,0,0,9.883,4.609,4.609,0,0,0,1.115-.141.25.25,0,0,1,.276.368,6.83,6.83,0,0,1-5.878,3.523,1.25,1.25,0,0,0,0,2.5,9.71,9.71,0,0,0,9.428-9.95V8.873A4.947,4.947,0,0,0,18.559,3.932Z"/><path d="M6.236,3.932a4.942,4.942,0,0,0,0,9.883,4.6,4.6,0,0,0,1.115-.141.25.25,0,0,1,.277.368A6.83,6.83,0,0,1,1.75,17.565a1.25,1.25,0,0,0,0,2.5,9.711,9.711,0,0,0,9.428-9.95V8.873A4.947,4.947,0,0,0,6.236,3.932Z"/></svg>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><title>close-quote</title><path d="M18.559,3.932a4.942,4.942,0,1,0,0,9.883,4.609,4.609,0,0,0,1.115-.141.25.25,0,0,1,.276.368,6.83,6.83,0,0,1-5.878,3.523,1.25,1.25,0,0,0,0,2.5,9.71,9.71,0,0,0,9.428-9.95V8.873A4.947,4.947,0,0,0,18.559,3.932Z"/><path d="M6.236,3.932a4.942,4.942,0,0,0,0,9.883,4.6,4.6,0,0,0,1.115-.141.25.25,0,0,1,.277.368A6.83,6.83,0,0,1,1.75,17.565a1.25,1.25,0,0,0,0,2.5,9.711,9.711,0,0,0,9.428-9.95V8.873A4.947,4.947,0,0,0,6.236,3.932Z"/></svg>
|
||||
|
||||
|
Before Width: | Height: | Size: 520 B After Width: | Height: | Size: 521 B |
@@ -1 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><title>redo</title><path d="M22.608.161a.5.5,0,0,0-.545.108L19.472,2.86a.25.25,0,0,1-.292.045A12.537,12.537,0,0,0,6.214,3.77,12.259,12.259,0,0,0,6.1,23.632a1.25,1.25,0,0,0,1.476-2.018A9.759,9.759,0,0,1,7.667,5.805a10,10,0,0,1,9.466-1.1.25.25,0,0,1,.084.409l-1.85,1.85a.5.5,0,0,0,.354.853h6.7a.5.5,0,0,0,.5-.5V.623A.5.5,0,0,0,22.608.161Z"/></svg>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><title>redo</title><path d="M22.608.161a.5.5,0,0,0-.545.108L19.472,2.86a.25.25,0,0,1-.292.045A12.537,12.537,0,0,0,6.214,3.77,12.259,12.259,0,0,0,6.1,23.632a1.25,1.25,0,0,0,1.476-2.018A9.759,9.759,0,0,1,7.667,5.805a10,10,0,0,1,9.466-1.1.25.25,0,0,1,.084.409l-1.85,1.85a.5.5,0,0,0,.354.853h6.7a.5.5,0,0,0,.5-.5V.623A.5.5,0,0,0,22.608.161Z"/></svg>
|
||||
|
||||
|
Before Width: | Height: | Size: 405 B After Width: | Height: | Size: 406 B |
@@ -1 +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>
|
||||
<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>
|
||||
|
||||
|
Before Width: | Height: | Size: 517 B After Width: | Height: | Size: 518 B |
@@ -1 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><title>text-strike-through</title><path d="M23.75,12.952A1.25,1.25,0,0,0,22.5,11.7H13.564a.492.492,0,0,1-.282-.09c-.722-.513-1.482-.981-2.218-1.432-2.8-1.715-4.5-2.9-4.5-4.863,0-2.235,2.207-2.569,3.523-2.569a4.54,4.54,0,0,1,3.081.764A2.662,2.662,0,0,1,13.615,5.5l0,.3a1.25,1.25,0,1,0,2.5,0l0-.268A4.887,4.887,0,0,0,14.95,1.755C13.949.741,12.359.248,10.091.248c-3.658,0-6.023,1.989-6.023,5.069,0,2.773,1.892,4.512,4,5.927a.25.25,0,0,1-.139.458H1.5a1.25,1.25,0,0,0,0,2.5H12.477a.251.251,0,0,1,.159.058,4.339,4.339,0,0,1,1.932,3.466c0,3.268-3.426,3.522-4.477,3.522-1.814,0-3.139-.405-3.834-1.173a3.394,3.394,0,0,1-.65-2.7,1.25,1.25,0,0,0-2.488-.246A5.76,5.76,0,0,0,4.4,21.753c1.2,1.324,3.114,2,5.688,2,4.174,0,6.977-2.42,6.977-6.022a6.059,6.059,0,0,0-.849-3.147.25.25,0,0,1,.216-.377H22.5A1.25,1.25,0,0,0,23.75,12.952Z"/></svg>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><title>text-strike-through</title><path d="M23.75,12.952A1.25,1.25,0,0,0,22.5,11.7H13.564a.492.492,0,0,1-.282-.09c-.722-.513-1.482-.981-2.218-1.432-2.8-1.715-4.5-2.9-4.5-4.863,0-2.235,2.207-2.569,3.523-2.569a4.54,4.54,0,0,1,3.081.764A2.662,2.662,0,0,1,13.615,5.5l0,.3a1.25,1.25,0,1,0,2.5,0l0-.268A4.887,4.887,0,0,0,14.95,1.755C13.949.741,12.359.248,10.091.248c-3.658,0-6.023,1.989-6.023,5.069,0,2.773,1.892,4.512,4,5.927a.25.25,0,0,1-.139.458H1.5a1.25,1.25,0,0,0,0,2.5H12.477a.251.251,0,0,1,.159.058,4.339,4.339,0,0,1,1.932,3.466c0,3.268-3.426,3.522-4.477,3.522-1.814,0-3.139-.405-3.834-1.173a3.394,3.394,0,0,1-.65-2.7,1.25,1.25,0,0,0-2.488-.246A5.76,5.76,0,0,0,4.4,21.753c1.2,1.324,3.114,2,5.688,2,4.174,0,6.977-2.42,6.977-6.022a6.059,6.059,0,0,0-.849-3.147.25.25,0,0,1,.216-.377H22.5A1.25,1.25,0,0,0,23.75,12.952Z"/></svg>
|
||||
|
||||
|
Before Width: | Height: | Size: 884 B After Width: | Height: | Size: 885 B |
@@ -1 +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>
|
||||
<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>
|
||||
|
||||
|
Before Width: | Height: | Size: 368 B After Width: | Height: | Size: 369 B |
@@ -1 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><title>text-underline</title><path d="M22.5,21.248H1.5a1.25,1.25,0,0,0,0,2.5h21a1.25,1.25,0,0,0,0-2.5Z"/><path d="M1.978,2.748H3.341a.25.25,0,0,1,.25.25v8.523a8.409,8.409,0,0,0,16.818,0V3a.25.25,0,0,1,.25-.25h1.363a1.25,1.25,0,0,0,0-2.5H16.3a1.25,1.25,0,0,0,0,2.5h1.363a.25.25,0,0,1,.25.25v8.523a5.909,5.909,0,0,1-11.818,0V3a.25.25,0,0,1,.25-.25H7.7a1.25,1.25,0,1,0,0-2.5H1.978a1.25,1.25,0,0,0,0,2.5Z"/></svg>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><title>text-underline</title><path d="M22.5,21.248H1.5a1.25,1.25,0,0,0,0,2.5h21a1.25,1.25,0,0,0,0-2.5Z"/><path d="M1.978,2.748H3.341a.25.25,0,0,1,.25.25v8.523a8.409,8.409,0,0,0,16.818,0V3a.25.25,0,0,1,.25-.25h1.363a1.25,1.25,0,0,0,0-2.5H16.3a1.25,1.25,0,0,0,0,2.5h1.363a.25.25,0,0,1,.25.25v8.523a5.909,5.909,0,0,1-11.818,0V3a.25.25,0,0,1,.25-.25H7.7a1.25,1.25,0,1,0,0-2.5H1.978a1.25,1.25,0,0,0,0,2.5Z"/></svg>
|
||||
|
||||
|
Before Width: | Height: | Size: 469 B After Width: | Height: | Size: 470 B |
@@ -1 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><title>undo</title><path d="M17.786,3.77A12.542,12.542,0,0,0,4.821,2.905a.249.249,0,0,1-.292-.045L1.937.269A.507.507,0,0,0,1.392.16a.5.5,0,0,0-.308.462v6.7a.5.5,0,0,0,.5.5h6.7a.5.5,0,0,0,.354-.854L6.783,5.115a.253.253,0,0,1-.068-.228.249.249,0,0,1,.152-.181,10,10,0,0,1,9.466,1.1,9.759,9.759,0,0,1,.094,15.809A1.25,1.25,0,0,0,17.9,23.631a12.122,12.122,0,0,0,5.013-9.961A12.125,12.125,0,0,0,17.786,3.77Z"/></svg>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><title>undo</title><path d="M17.786,3.77A12.542,12.542,0,0,0,4.821,2.905a.249.249,0,0,1-.292-.045L1.937.269A.507.507,0,0,0,1.392.16a.5.5,0,0,0-.308.462v6.7a.5.5,0,0,0,.5.5h6.7a.5.5,0,0,0,.354-.854L6.783,5.115a.253.253,0,0,1-.068-.228.249.249,0,0,1,.152-.181,10,10,0,0,1,9.466,1.1,9.759,9.759,0,0,1,.094,15.809A1.25,1.25,0,0,0,17.9,23.631a12.122,12.122,0,0,0,5.013-9.961A12.125,12.125,0,0,0,17.786,3.77Z"/></svg>
|
||||
|
||||
|
Before Width: | Height: | Size: 471 B After Width: | Height: | Size: 472 B |
@@ -53,4 +53,4 @@
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,39 +1,39 @@
|
||||
@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;
|
||||
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;
|
||||
}
|
||||
&:focus {
|
||||
outline: none;
|
||||
}
|
||||
}
|
||||
|
||||
*::before,
|
||||
*::after {
|
||||
box-sizing: border-box;
|
||||
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;
|
||||
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;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
a {
|
||||
color: inherit;
|
||||
color: inherit;
|
||||
}
|
||||
|
||||
h1,
|
||||
@@ -44,11 +44,11 @@ ul,
|
||||
ol,
|
||||
pre,
|
||||
blockquote {
|
||||
margin: 1rem 0;
|
||||
margin: 1rem 0;
|
||||
|
||||
&:first-child {
|
||||
margin-top: 0;
|
||||
}
|
||||
}
|
||||
|
||||
&:last-child {
|
||||
margin-bottom: 0;
|
||||
@@ -58,22 +58,22 @@ blockquote {
|
||||
h1,
|
||||
h2,
|
||||
h3 {
|
||||
line-height: 1.3;
|
||||
line-height: 1.3;
|
||||
}
|
||||
|
||||
.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;
|
||||
background-color: rgba($color-black, 0.1);
|
||||
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;
|
||||
background-color: rgba($color-black, 0.1);
|
||||
}
|
||||
|
||||
@import "./editor";
|
||||
@import "./menubar";
|
||||
@import "./menububble";
|
||||
@import "./menububble";
|
||||
|
||||
@@ -34,4 +34,4 @@
|
||||
background-color: rgba($color-black, 0.1);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -45,4 +45,4 @@
|
||||
background: transparent;
|
||||
color: $color-white;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,2 +1,2 @@
|
||||
$color-black: #000000;
|
||||
$color-white: #ffffff;
|
||||
$color-white: #ffffff;
|
||||
|
||||
@@ -1,80 +1,80 @@
|
||||
;(function(window, document) {
|
||||
'use strict';
|
||||
'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;
|
||||
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 || {};
|
||||
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 (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!');
|
||||
}
|
||||
};
|
||||
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;
|
||||
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]);
|
||||
}
|
||||
// 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;
|
||||
// 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();
|
||||
}
|
||||
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 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';
|
||||
}
|
||||
}
|
||||
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;
|
||||
}
|
||||
if (typeof exports === 'object') {
|
||||
module.exports = svgSpriteInjector;
|
||||
} else {
|
||||
window.svgSpriteInjector = svgSpriteInjector;
|
||||
}
|
||||
|
||||
} (window, document));
|
||||
} (window, document));
|
||||
|
||||
@@ -1,16 +1,16 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta http-equiv="x-ua-compatible" content="ie=edge">
|
||||
<title>tiptap</title>
|
||||
<meta name="description" content="A renderless & extendable rich-text editor for Vue.js">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<meta name="mobile-web-app-capable" content="yes">
|
||||
<link rel="shortcut icon" href="/assets/images/favicon.ico">
|
||||
<meta property="og:image" content="/assets/images/open-graph.png">
|
||||
</head>
|
||||
<body>
|
||||
<div id="app"></div>
|
||||
</body>
|
||||
</html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta http-equiv="x-ua-compatible" content="ie=edge">
|
||||
<title>tiptap</title>
|
||||
<meta name="description" content="A renderless & extendable rich-text editor for Vue.js">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<meta name="mobile-web-app-capable" content="yes">
|
||||
<link rel="shortcut icon" href="/assets/images/favicon.ico">
|
||||
<meta property="og:image" content="/assets/images/open-graph.png">
|
||||
</head>
|
||||
<body>
|
||||
<div id="app"></div>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
[[redirects]]
|
||||
from = "/*"
|
||||
to = "/index.html"
|
||||
status = 200
|
||||
status = 200
|
||||
|
||||
@@ -4,4 +4,4 @@ This is a collection of commands for [tiptap](https://www.npmjs.com/package/tipt
|
||||
[](https://www.npmjs.com/package/tiptap-commands)
|
||||
[](https://npmcharts.com/compare/tiptap-commands?minimal=true)
|
||||
[](https://www.npmjs.com/package/tiptap-commands)
|
||||
[](https://www.npmjs.com/package/tiptap-commands)
|
||||
[](https://www.npmjs.com/package/tiptap-commands)
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
export default function (text = '') {
|
||||
return (state, dispatch) => {
|
||||
const { $from } = state.selection
|
||||
const { pos } = $from.pos
|
||||
return (state, dispatch) => {
|
||||
const { $from } = state.selection
|
||||
const { pos } = $from.pos
|
||||
|
||||
dispatch(state.tr.insertText(text, pos))
|
||||
dispatch(state.tr.insertText(text, pos))
|
||||
|
||||
return true
|
||||
}
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,26 +1,26 @@
|
||||
import { InputRule } from 'prosemirror-inputrules'
|
||||
|
||||
export default function (regexp, markType, getAttrs) {
|
||||
return new InputRule(regexp, (state, match, start, end) => {
|
||||
const attrs = getAttrs instanceof Function ? getAttrs(match) : getAttrs
|
||||
return new InputRule(regexp, (state, match, start, end) => {
|
||||
const attrs = getAttrs instanceof Function ? getAttrs(match) : getAttrs
|
||||
const { tr } = state
|
||||
let markEnd = end
|
||||
|
||||
if (match[1]) {
|
||||
const startSpaces = match[0].search(/\S/)
|
||||
const textStart = start + match[0].indexOf(match[1])
|
||||
const textEnd = textStart + match[1].length
|
||||
if (textEnd < end) {
|
||||
tr.delete(textEnd, end)
|
||||
}
|
||||
if (textStart > start) {
|
||||
tr.delete(start + startSpaces, textStart)
|
||||
}
|
||||
markEnd = start + startSpaces + match[1].length
|
||||
}
|
||||
if (match[1]) {
|
||||
const startSpaces = match[0].search(/\S/)
|
||||
const textStart = start + match[0].indexOf(match[1])
|
||||
const textEnd = textStart + match[1].length
|
||||
if (textEnd < end) {
|
||||
tr.delete(textEnd, end)
|
||||
}
|
||||
if (textStart > start) {
|
||||
tr.delete(start + startSpaces, textStart)
|
||||
}
|
||||
markEnd = start + startSpaces + match[1].length
|
||||
}
|
||||
|
||||
tr.addMark(start, markEnd, markType.create(attrs))
|
||||
tr.removeStoredMark(markType) // Do not continue with mark.
|
||||
return tr
|
||||
})
|
||||
tr.addMark(start, markEnd, markType.create(attrs))
|
||||
tr.removeStoredMark(markType) // Do not continue with mark.
|
||||
return tr
|
||||
})
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
export default function (type) {
|
||||
return (state, dispatch) => {
|
||||
const { from, to } = state.selection
|
||||
return dispatch(state.tr.removeMark(from, to, type))
|
||||
}
|
||||
const { from, to } = state.selection
|
||||
return dispatch(state.tr.removeMark(from, to, type))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,16 +1,16 @@
|
||||
export default function (range, type, attrs = {}) {
|
||||
return (state, dispatch) => {
|
||||
const { $from } = state.selection
|
||||
const index = $from.index()
|
||||
return (state, dispatch) => {
|
||||
const { $from } = state.selection
|
||||
const index = $from.index()
|
||||
|
||||
if (!$from.parent.canReplaceWith(index, index, type)) {
|
||||
return false
|
||||
}
|
||||
if (!$from.parent.canReplaceWith(index, index, type)) {
|
||||
return false
|
||||
}
|
||||
|
||||
if (dispatch) {
|
||||
dispatch(state.tr.replaceWith(range.from, range.to, type.create(attrs)))
|
||||
}
|
||||
if (dispatch) {
|
||||
dispatch(state.tr.replaceWith(range.from, range.to, type.create(attrs)))
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,16 +1,16 @@
|
||||
export default function (type, attrs = {}) {
|
||||
return (state, dispatch) => {
|
||||
const { $from } = state.selection
|
||||
const index = $from.index()
|
||||
return (state, dispatch) => {
|
||||
const { $from } = state.selection
|
||||
const index = $from.index()
|
||||
|
||||
if (!$from.parent.canReplaceWith(index, index, type)) {
|
||||
return false
|
||||
}
|
||||
if (!$from.parent.canReplaceWith(index, index, type)) {
|
||||
return false
|
||||
}
|
||||
|
||||
if (dispatch) {
|
||||
dispatch(state.tr.replaceSelectionWith(type.create(attrs)))
|
||||
}
|
||||
if (dispatch) {
|
||||
dispatch(state.tr.replaceSelectionWith(type.create(attrs)))
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,13 +16,13 @@ index = $pos.index(d)
|
||||
if (node.type.spec.isolating) return false
|
||||
let rest = node.content.cutByIndex(index, node.childCount)
|
||||
const after = (typesAfter && typesAfter[i]) || node
|
||||
if (after != node) rest = rest.replaceChild(0, after.type.create(after.attrs))
|
||||
if (after != node) rest = rest.replaceChild(0, after.type.create(after.attrs))
|
||||
|
||||
/* Change starts from here */
|
||||
// if (!node.canReplace(index + 1, node.childCount) || !after.type.validContent(rest))
|
||||
/* Change starts from here */
|
||||
// if (!node.canReplace(index + 1, node.childCount) || !after.type.validContent(rest))
|
||||
// return false
|
||||
if (!node.canReplace(index + 1, node.childCount)) return false
|
||||
/* Change ends here */
|
||||
/* Change ends here */
|
||||
}
|
||||
const index = $pos.indexAfter(base)
|
||||
const baseType = typesAfter && typesAfter[0]
|
||||
@@ -43,7 +43,7 @@ export default function splitListItem(itemType) {
|
||||
// list item should be split. Otherwise, bail out and let next
|
||||
// command handle lifting.
|
||||
if ($from.depth == 2 || $from.node(-3).type != itemType
|
||||
|| $from.index(-2) != $from.node(-2).childCount - 1) return false
|
||||
|| $from.index(-2) != $from.node(-2).childCount - 1) return false
|
||||
|
||||
if (dispatch) {
|
||||
let wrap = Fragment.empty; const
|
||||
@@ -52,23 +52,23 @@ keepItem = $from.index(-1) > 0
|
||||
// from the outer list item to the parent node of the cursor
|
||||
for (let d = $from.depth - (keepItem ? 1 : 2); d >= $from.depth - 3; d--) wrap = Fragment.from($from.node(d).copy(wrap))
|
||||
// Add a second list item with an empty default start node
|
||||
wrap = wrap.append(Fragment.from(itemType.createAndFill()))
|
||||
wrap = wrap.append(Fragment.from(itemType.createAndFill()))
|
||||
const tr = state.tr.replace($from.before(keepItem ? null : -1), $from.after(-3), new Slice(wrap, keepItem ? 3 : 2, 2))
|
||||
tr.setSelection(state.selection.constructor.near(tr.doc.resolve($from.pos + (keepItem ? 3 : 2))))
|
||||
dispatch(tr.scrollIntoView())
|
||||
}
|
||||
return true
|
||||
}
|
||||
const nextType = $to.pos == $from.end() ? grandParent.contentMatchAt($from.indexAfter(-1)).defaultType : null
|
||||
const tr = state.tr.delete($from.pos, $to.pos)
|
||||
const nextType = $to.pos == $from.end() ? grandParent.contentMatchAt($from.indexAfter(-1)).defaultType : null
|
||||
const tr = state.tr.delete($from.pos, $to.pos)
|
||||
|
||||
/* Change starts from here */
|
||||
// let types = nextType && [null, {type: nextType}]
|
||||
/* Change starts from here */
|
||||
// let types = nextType && [null, {type: nextType}]
|
||||
let types = nextType && [{ type: itemType }, { type: nextType }]
|
||||
if (!types) types = [{ type: itemType }, null]
|
||||
/* Change ends here */
|
||||
/* Change ends here */
|
||||
|
||||
if (!canSplit(tr.doc, $from.pos, 2, types)) return false
|
||||
if (!canSplit(tr.doc, $from.pos, 2, types)) return false
|
||||
if (dispatch) dispatch(tr.split($from.pos, 2, [{ type: state.schema.nodes.todo_item, attrs: { done: false } }]).scrollIntoView())
|
||||
return true
|
||||
}
|
||||
|
||||
@@ -2,13 +2,13 @@ import { setBlockType } from 'prosemirror-commands'
|
||||
import { nodeIsActive } from 'tiptap-utils'
|
||||
|
||||
export default function (type, toggletype, attrs = {}) {
|
||||
return (state, dispatch, view) => {
|
||||
const isActive = nodeIsActive(state, type, attrs)
|
||||
return (state, dispatch, view) => {
|
||||
const isActive = nodeIsActive(state, type, attrs)
|
||||
|
||||
if (isActive) {
|
||||
return setBlockType(toggletype)(state, dispatch, view)
|
||||
}
|
||||
if (isActive) {
|
||||
return setBlockType(toggletype)(state, dispatch, view)
|
||||
}
|
||||
|
||||
return setBlockType(type, attrs)(state, dispatch, view)
|
||||
}
|
||||
return setBlockType(type, attrs)(state, dispatch, view)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,13 +2,13 @@ import { wrapIn, lift } from 'prosemirror-commands'
|
||||
import { nodeIsActive } from 'tiptap-utils'
|
||||
|
||||
export default function (type) {
|
||||
return (state, dispatch, view) => {
|
||||
const isActive = nodeIsActive(state, type)
|
||||
return (state, dispatch, view) => {
|
||||
const isActive = nodeIsActive(state, type)
|
||||
|
||||
if (isActive) {
|
||||
return lift(state, dispatch)
|
||||
}
|
||||
if (isActive) {
|
||||
return lift(state, dispatch)
|
||||
}
|
||||
|
||||
return wrapIn(type)(state, dispatch, view)
|
||||
}
|
||||
return wrapIn(type)(state, dispatch, view)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
export default function (type, attrs) {
|
||||
return (state, dispatch) => {
|
||||
const { from, to } = state.selection
|
||||
return dispatch(state.tr.addMark(from, to, type.create(attrs)))
|
||||
}
|
||||
const { from, to } = state.selection
|
||||
return dispatch(state.tr.addMark(from, to, type.create(attrs)))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,41 +1,41 @@
|
||||
import {
|
||||
chainCommands,
|
||||
deleteSelection,
|
||||
joinBackward,
|
||||
selectNodeBackward,
|
||||
joinForward,
|
||||
selectNodeForward,
|
||||
joinUp,
|
||||
joinDown,
|
||||
lift,
|
||||
newlineInCode,
|
||||
exitCode,
|
||||
createParagraphNear,
|
||||
liftEmptyBlock,
|
||||
splitBlock,
|
||||
splitBlockKeepMarks,
|
||||
selectParentNode,
|
||||
selectAll,
|
||||
wrapIn,
|
||||
setBlockType,
|
||||
toggleMark,
|
||||
autoJoin,
|
||||
baseKeymap,
|
||||
pcBaseKeymap,
|
||||
macBaseKeymap,
|
||||
chainCommands,
|
||||
deleteSelection,
|
||||
joinBackward,
|
||||
selectNodeBackward,
|
||||
joinForward,
|
||||
selectNodeForward,
|
||||
joinUp,
|
||||
joinDown,
|
||||
lift,
|
||||
newlineInCode,
|
||||
exitCode,
|
||||
createParagraphNear,
|
||||
liftEmptyBlock,
|
||||
splitBlock,
|
||||
splitBlockKeepMarks,
|
||||
selectParentNode,
|
||||
selectAll,
|
||||
wrapIn,
|
||||
setBlockType,
|
||||
toggleMark,
|
||||
autoJoin,
|
||||
baseKeymap,
|
||||
pcBaseKeymap,
|
||||
macBaseKeymap,
|
||||
} from 'prosemirror-commands'
|
||||
|
||||
import {
|
||||
addListNodes,
|
||||
wrapInList,
|
||||
splitListItem,
|
||||
liftListItem,
|
||||
sinkListItem,
|
||||
addListNodes,
|
||||
wrapInList,
|
||||
splitListItem,
|
||||
liftListItem,
|
||||
sinkListItem,
|
||||
} from 'prosemirror-schema-list'
|
||||
|
||||
import {
|
||||
wrappingInputRule,
|
||||
textblockTypeInputRule,
|
||||
wrappingInputRule,
|
||||
textblockTypeInputRule,
|
||||
} from 'prosemirror-inputrules'
|
||||
|
||||
import insertText from './commands/insertText'
|
||||
@@ -50,52 +50,52 @@ import toggleWrap from './commands/toggleWrap'
|
||||
import updateMark from './commands/updateMark'
|
||||
|
||||
export {
|
||||
// prosemirror-commands
|
||||
chainCommands,
|
||||
deleteSelection,
|
||||
joinBackward,
|
||||
selectNodeBackward,
|
||||
joinForward,
|
||||
selectNodeForward,
|
||||
joinUp,
|
||||
joinDown,
|
||||
lift,
|
||||
newlineInCode,
|
||||
exitCode,
|
||||
createParagraphNear,
|
||||
liftEmptyBlock,
|
||||
splitBlock,
|
||||
splitBlockKeepMarks,
|
||||
selectParentNode,
|
||||
selectAll,
|
||||
wrapIn,
|
||||
setBlockType,
|
||||
toggleMark,
|
||||
autoJoin,
|
||||
baseKeymap,
|
||||
pcBaseKeymap,
|
||||
macBaseKeymap,
|
||||
// prosemirror-commands
|
||||
chainCommands,
|
||||
deleteSelection,
|
||||
joinBackward,
|
||||
selectNodeBackward,
|
||||
joinForward,
|
||||
selectNodeForward,
|
||||
joinUp,
|
||||
joinDown,
|
||||
lift,
|
||||
newlineInCode,
|
||||
exitCode,
|
||||
createParagraphNear,
|
||||
liftEmptyBlock,
|
||||
splitBlock,
|
||||
splitBlockKeepMarks,
|
||||
selectParentNode,
|
||||
selectAll,
|
||||
wrapIn,
|
||||
setBlockType,
|
||||
toggleMark,
|
||||
autoJoin,
|
||||
baseKeymap,
|
||||
pcBaseKeymap,
|
||||
macBaseKeymap,
|
||||
|
||||
// prosemirror-schema-list
|
||||
addListNodes,
|
||||
wrapInList,
|
||||
splitListItem,
|
||||
liftListItem,
|
||||
sinkListItem,
|
||||
// prosemirror-schema-list
|
||||
addListNodes,
|
||||
wrapInList,
|
||||
splitListItem,
|
||||
liftListItem,
|
||||
sinkListItem,
|
||||
|
||||
// prosemirror-inputrules
|
||||
wrappingInputRule,
|
||||
textblockTypeInputRule,
|
||||
// prosemirror-inputrules
|
||||
wrappingInputRule,
|
||||
textblockTypeInputRule,
|
||||
|
||||
// custom
|
||||
insertText,
|
||||
markInputRule,
|
||||
removeMark,
|
||||
replaceText,
|
||||
setInlineBlockType,
|
||||
splitToDefaultListItem,
|
||||
toggleBlockType,
|
||||
toggleList,
|
||||
toggleWrap,
|
||||
updateMark,
|
||||
// custom
|
||||
insertText,
|
||||
markInputRule,
|
||||
removeMark,
|
||||
replaceText,
|
||||
setInlineBlockType,
|
||||
splitToDefaultListItem,
|
||||
toggleBlockType,
|
||||
toggleList,
|
||||
toggleWrap,
|
||||
updateMark,
|
||||
}
|
||||
|
||||
@@ -4,4 +4,4 @@ This is a collection of extensions for [tiptap](https://www.npmjs.com/package/ti
|
||||
[](https://www.npmjs.com/package/tiptap-extensions)
|
||||
[](https://npmcharts.com/compare/tiptap-extensions?minimal=true)
|
||||
[](https://www.npmjs.com/package/tiptap-extensions)
|
||||
[](https://www.npmjs.com/package/tiptap-extensions)
|
||||
[](https://www.npmjs.com/package/tiptap-extensions)
|
||||
|
||||
@@ -3,35 +3,35 @@ import { history, undo, redo } from 'prosemirror-history'
|
||||
|
||||
export default class History extends Extension {
|
||||
|
||||
get name() {
|
||||
return 'history'
|
||||
}
|
||||
get name() {
|
||||
return 'history'
|
||||
}
|
||||
|
||||
keys() {
|
||||
const isMac = typeof navigator !== 'undefined' ? /Mac/.test(navigator.platform) : false
|
||||
const keymap = {
|
||||
'Mod-z': undo,
|
||||
'Shift-Mod-z': redo,
|
||||
}
|
||||
keys() {
|
||||
const isMac = typeof navigator !== 'undefined' ? /Mac/.test(navigator.platform) : false
|
||||
const keymap = {
|
||||
'Mod-z': undo,
|
||||
'Shift-Mod-z': redo,
|
||||
}
|
||||
|
||||
if (!isMac) {
|
||||
keymap['Mod-y'] = redo
|
||||
}
|
||||
if (!isMac) {
|
||||
keymap['Mod-y'] = redo
|
||||
}
|
||||
|
||||
return keymap
|
||||
}
|
||||
return keymap
|
||||
}
|
||||
|
||||
get plugins() {
|
||||
return [
|
||||
history(),
|
||||
]
|
||||
}
|
||||
get plugins() {
|
||||
return [
|
||||
history(),
|
||||
]
|
||||
}
|
||||
|
||||
commands() {
|
||||
return {
|
||||
undo: () => undo,
|
||||
redo: () => redo,
|
||||
}
|
||||
}
|
||||
commands() {
|
||||
return {
|
||||
undo: () => undo,
|
||||
redo: () => redo,
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -3,40 +3,40 @@ import { Decoration, DecorationSet } from 'prosemirror-view'
|
||||
|
||||
export default class Placeholder extends Extension {
|
||||
|
||||
get name() {
|
||||
return 'placeholder'
|
||||
}
|
||||
get name() {
|
||||
return 'placeholder'
|
||||
}
|
||||
|
||||
get defaultOptions() {
|
||||
return {
|
||||
emptyNodeClass: 'is-empty',
|
||||
}
|
||||
}
|
||||
get defaultOptions() {
|
||||
return {
|
||||
emptyNodeClass: 'is-empty',
|
||||
}
|
||||
}
|
||||
|
||||
get plugins() {
|
||||
return [
|
||||
new Plugin({
|
||||
props: {
|
||||
decorations: ({ doc }) => {
|
||||
const decorations = []
|
||||
const completelyEmpty = doc.textContent === '' && doc.childCount <= 1 && doc.content.size <= 2
|
||||
get plugins() {
|
||||
return [
|
||||
new Plugin({
|
||||
props: {
|
||||
decorations: ({ doc }) => {
|
||||
const decorations = []
|
||||
const completelyEmpty = doc.textContent === '' && doc.childCount <= 1 && doc.content.size <= 2
|
||||
|
||||
doc.descendants((node, pos) => {
|
||||
if (!completelyEmpty) {
|
||||
return
|
||||
}
|
||||
doc.descendants((node, pos) => {
|
||||
if (!completelyEmpty) {
|
||||
return
|
||||
}
|
||||
|
||||
const decoration = Decoration.node(pos, pos + node.nodeSize, {
|
||||
class: this.options.emptyNodeClass,
|
||||
})
|
||||
decorations.push(decoration)
|
||||
})
|
||||
const decoration = Decoration.node(pos, pos + node.nodeSize, {
|
||||
class: this.options.emptyNodeClass,
|
||||
})
|
||||
decorations.push(decoration)
|
||||
})
|
||||
|
||||
return DecorationSet.create(doc, decorations)
|
||||
},
|
||||
},
|
||||
}),
|
||||
]
|
||||
}
|
||||
return DecorationSet.create(doc, decorations)
|
||||
},
|
||||
},
|
||||
}),
|
||||
]
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -3,43 +3,43 @@ import { toggleMark, markInputRule } from 'tiptap-commands'
|
||||
|
||||
export default class Bold extends Mark {
|
||||
|
||||
get name() {
|
||||
return 'bold'
|
||||
}
|
||||
get name() {
|
||||
return 'bold'
|
||||
}
|
||||
|
||||
get schema() {
|
||||
return {
|
||||
parseDOM: [
|
||||
{
|
||||
tag: 'strong',
|
||||
},
|
||||
{
|
||||
tag: 'b',
|
||||
getAttrs: node => node.style.fontWeight !== 'normal' && null,
|
||||
},
|
||||
{
|
||||
style: 'font-weight',
|
||||
getAttrs: value => /^(bold(er)?|[5-9]\d{2,})$/.test(value) && null,
|
||||
},
|
||||
],
|
||||
toDOM: () => ['strong', 0],
|
||||
}
|
||||
}
|
||||
get schema() {
|
||||
return {
|
||||
parseDOM: [
|
||||
{
|
||||
tag: 'strong',
|
||||
},
|
||||
{
|
||||
tag: 'b',
|
||||
getAttrs: node => node.style.fontWeight !== 'normal' && null,
|
||||
},
|
||||
{
|
||||
style: 'font-weight',
|
||||
getAttrs: value => /^(bold(er)?|[5-9]\d{2,})$/.test(value) && null,
|
||||
},
|
||||
],
|
||||
toDOM: () => ['strong', 0],
|
||||
}
|
||||
}
|
||||
|
||||
keys({ type }) {
|
||||
return {
|
||||
'Mod-b': toggleMark(type),
|
||||
}
|
||||
}
|
||||
keys({ type }) {
|
||||
return {
|
||||
'Mod-b': toggleMark(type),
|
||||
}
|
||||
}
|
||||
|
||||
commands({ type }) {
|
||||
return () => toggleMark(type)
|
||||
}
|
||||
commands({ type }) {
|
||||
return () => toggleMark(type)
|
||||
}
|
||||
|
||||
inputRules({ type }) {
|
||||
return [
|
||||
markInputRule(/(?:\*\*|__)([^*_]+)(?:\*\*|__)$/, type),
|
||||
]
|
||||
}
|
||||
inputRules({ type }) {
|
||||
return [
|
||||
markInputRule(/(?:\*\*|__)([^*_]+)(?:\*\*|__)$/, type),
|
||||
]
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -3,33 +3,33 @@ import { toggleMark, markInputRule } from 'tiptap-commands'
|
||||
|
||||
export default class Code extends Mark {
|
||||
|
||||
get name() {
|
||||
return 'code'
|
||||
}
|
||||
get name() {
|
||||
return 'code'
|
||||
}
|
||||
|
||||
get schema() {
|
||||
return {
|
||||
parseDOM: [
|
||||
{ tag: 'code' },
|
||||
],
|
||||
toDOM: () => ['code', 0],
|
||||
}
|
||||
}
|
||||
get schema() {
|
||||
return {
|
||||
parseDOM: [
|
||||
{ tag: 'code' },
|
||||
],
|
||||
toDOM: () => ['code', 0],
|
||||
}
|
||||
}
|
||||
|
||||
keys({ type }) {
|
||||
return {
|
||||
'Mod-`': toggleMark(type),
|
||||
}
|
||||
}
|
||||
keys({ type }) {
|
||||
return {
|
||||
'Mod-`': toggleMark(type),
|
||||
}
|
||||
}
|
||||
|
||||
commands({ type }) {
|
||||
return () => toggleMark(type)
|
||||
}
|
||||
commands({ type }) {
|
||||
return () => toggleMark(type)
|
||||
}
|
||||
|
||||
inputRules({ type }) {
|
||||
return [
|
||||
markInputRule(/(?:`)([^`]+)(?:`)$/, type),
|
||||
]
|
||||
}
|
||||
inputRules({ type }) {
|
||||
return [
|
||||
markInputRule(/(?:`)([^`]+)(?:`)$/, type),
|
||||
]
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -3,35 +3,35 @@ import { toggleMark, markInputRule } from 'tiptap-commands'
|
||||
|
||||
export default class Italic extends Mark {
|
||||
|
||||
get name() {
|
||||
return 'italic'
|
||||
}
|
||||
get name() {
|
||||
return 'italic'
|
||||
}
|
||||
|
||||
get schema() {
|
||||
return {
|
||||
parseDOM: [
|
||||
{ tag: 'i' },
|
||||
{ tag: 'em' },
|
||||
{ style: 'font-style=italic' },
|
||||
],
|
||||
toDOM: () => ['em', 0],
|
||||
}
|
||||
}
|
||||
get schema() {
|
||||
return {
|
||||
parseDOM: [
|
||||
{ tag: 'i' },
|
||||
{ tag: 'em' },
|
||||
{ style: 'font-style=italic' },
|
||||
],
|
||||
toDOM: () => ['em', 0],
|
||||
}
|
||||
}
|
||||
|
||||
keys({ type }) {
|
||||
return {
|
||||
'Mod-i': toggleMark(type),
|
||||
}
|
||||
}
|
||||
keys({ type }) {
|
||||
return {
|
||||
'Mod-i': toggleMark(type),
|
||||
}
|
||||
}
|
||||
|
||||
commands({ type }) {
|
||||
return () => toggleMark(type)
|
||||
}
|
||||
commands({ type }) {
|
||||
return () => toggleMark(type)
|
||||
}
|
||||
|
||||
inputRules({ type }) {
|
||||
return [
|
||||
markInputRule(/(?:^|[^*_])(?:\*|_)([^*_]+)(?:\*|_)$/, type),
|
||||
]
|
||||
}
|
||||
inputRules({ type }) {
|
||||
return [
|
||||
markInputRule(/(?:^|[^*_])(?:\*|_)([^*_]+)(?:\*|_)$/, type),
|
||||
]
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -3,41 +3,41 @@ import { updateMark, removeMark } from 'tiptap-commands'
|
||||
|
||||
export default class Link extends Mark {
|
||||
|
||||
get name() {
|
||||
return 'link'
|
||||
}
|
||||
get name() {
|
||||
return 'link'
|
||||
}
|
||||
|
||||
get schema() {
|
||||
return {
|
||||
attrs: {
|
||||
href: {
|
||||
default: null,
|
||||
},
|
||||
},
|
||||
inclusive: false,
|
||||
parseDOM: [
|
||||
{
|
||||
tag: 'a[href]',
|
||||
getAttrs: dom => ({
|
||||
href: dom.getAttribute('href'),
|
||||
}),
|
||||
},
|
||||
],
|
||||
toDOM: node => ['a', {
|
||||
...node.attrs,
|
||||
rel: 'noopener noreferrer nofollow',
|
||||
}, 0],
|
||||
}
|
||||
}
|
||||
get schema() {
|
||||
return {
|
||||
attrs: {
|
||||
href: {
|
||||
default: null,
|
||||
},
|
||||
},
|
||||
inclusive: false,
|
||||
parseDOM: [
|
||||
{
|
||||
tag: 'a[href]',
|
||||
getAttrs: dom => ({
|
||||
href: dom.getAttribute('href'),
|
||||
}),
|
||||
},
|
||||
],
|
||||
toDOM: node => ['a', {
|
||||
...node.attrs,
|
||||
rel: 'noopener noreferrer nofollow',
|
||||
}, 0],
|
||||
}
|
||||
}
|
||||
|
||||
commands({ type }) {
|
||||
return attrs => {
|
||||
if (attrs.href) {
|
||||
return updateMark(type, attrs)
|
||||
}
|
||||
commands({ type }) {
|
||||
return attrs => {
|
||||
if (attrs.href) {
|
||||
return updateMark(type, attrs)
|
||||
}
|
||||
|
||||
return removeMark(type)
|
||||
}
|
||||
}
|
||||
return removeMark(type)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -3,45 +3,45 @@ import { toggleMark, markInputRule } from 'tiptap-commands'
|
||||
|
||||
export default class Strike extends Mark {
|
||||
|
||||
get name() {
|
||||
return 'strike'
|
||||
}
|
||||
get name() {
|
||||
return 'strike'
|
||||
}
|
||||
|
||||
get schema() {
|
||||
return {
|
||||
parseDOM: [
|
||||
{
|
||||
tag: 's',
|
||||
},
|
||||
{
|
||||
tag: 'del',
|
||||
},
|
||||
{
|
||||
tag: 'strike',
|
||||
},
|
||||
{
|
||||
style: 'text-decoration',
|
||||
getAttrs: value => value === 'line-through',
|
||||
},
|
||||
],
|
||||
toDOM: () => ['s', 0],
|
||||
}
|
||||
}
|
||||
get schema() {
|
||||
return {
|
||||
parseDOM: [
|
||||
{
|
||||
tag: 's',
|
||||
},
|
||||
{
|
||||
tag: 'del',
|
||||
},
|
||||
{
|
||||
tag: 'strike',
|
||||
},
|
||||
{
|
||||
style: 'text-decoration',
|
||||
getAttrs: value => value === 'line-through',
|
||||
},
|
||||
],
|
||||
toDOM: () => ['s', 0],
|
||||
}
|
||||
}
|
||||
|
||||
keys({ type }) {
|
||||
return {
|
||||
'Mod-d': toggleMark(type),
|
||||
}
|
||||
}
|
||||
keys({ type }) {
|
||||
return {
|
||||
'Mod-d': toggleMark(type),
|
||||
}
|
||||
}
|
||||
|
||||
commands({ type }) {
|
||||
return () => toggleMark(type)
|
||||
}
|
||||
commands({ type }) {
|
||||
return () => toggleMark(type)
|
||||
}
|
||||
|
||||
inputRules({ type }) {
|
||||
return [
|
||||
markInputRule(/~([^~]+)~$/, type),
|
||||
]
|
||||
}
|
||||
inputRules({ type }) {
|
||||
return [
|
||||
markInputRule(/~([^~]+)~$/, type),
|
||||
]
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -3,33 +3,33 @@ import { toggleMark } from 'tiptap-commands'
|
||||
|
||||
export default class Underline extends Mark {
|
||||
|
||||
get name() {
|
||||
return 'underline'
|
||||
}
|
||||
get name() {
|
||||
return 'underline'
|
||||
}
|
||||
|
||||
get schema() {
|
||||
return {
|
||||
parseDOM: [
|
||||
{
|
||||
tag: 'u',
|
||||
},
|
||||
{
|
||||
style: 'text-decoration',
|
||||
getAttrs: value => value === 'underline',
|
||||
},
|
||||
],
|
||||
toDOM: () => ['u', 0],
|
||||
}
|
||||
}
|
||||
get schema() {
|
||||
return {
|
||||
parseDOM: [
|
||||
{
|
||||
tag: 'u',
|
||||
},
|
||||
{
|
||||
style: 'text-decoration',
|
||||
getAttrs: value => value === 'underline',
|
||||
},
|
||||
],
|
||||
toDOM: () => ['u', 0],
|
||||
}
|
||||
}
|
||||
|
||||
keys({ type }) {
|
||||
return {
|
||||
'Mod-u': toggleMark(type),
|
||||
}
|
||||
}
|
||||
keys({ type }) {
|
||||
return {
|
||||
'Mod-u': toggleMark(type),
|
||||
}
|
||||
}
|
||||
|
||||
commands({ type }) {
|
||||
return () => toggleMark(type)
|
||||
}
|
||||
commands({ type }) {
|
||||
return () => toggleMark(type)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -3,37 +3,37 @@ import { wrappingInputRule, toggleWrap } from 'tiptap-commands'
|
||||
|
||||
export default class Blockquote extends Node {
|
||||
|
||||
get name() {
|
||||
return 'blockquote'
|
||||
}
|
||||
get name() {
|
||||
return 'blockquote'
|
||||
}
|
||||
|
||||
get schema() {
|
||||
return {
|
||||
content: 'block*',
|
||||
group: 'block',
|
||||
defining: true,
|
||||
draggable: false,
|
||||
parseDOM: [
|
||||
{ tag: 'blockquote' },
|
||||
],
|
||||
toDOM: () => ['blockquote', 0],
|
||||
}
|
||||
}
|
||||
get schema() {
|
||||
return {
|
||||
content: 'block*',
|
||||
group: 'block',
|
||||
defining: true,
|
||||
draggable: false,
|
||||
parseDOM: [
|
||||
{ tag: 'blockquote' },
|
||||
],
|
||||
toDOM: () => ['blockquote', 0],
|
||||
}
|
||||
}
|
||||
|
||||
commands({ type, schema }) {
|
||||
return () => toggleWrap(type, schema.nodes.paragraph)
|
||||
}
|
||||
commands({ type, schema }) {
|
||||
return () => toggleWrap(type, schema.nodes.paragraph)
|
||||
}
|
||||
|
||||
keys({ type }) {
|
||||
return {
|
||||
'Ctrl->': toggleWrap(type),
|
||||
}
|
||||
}
|
||||
keys({ type }) {
|
||||
return {
|
||||
'Ctrl->': toggleWrap(type),
|
||||
}
|
||||
}
|
||||
|
||||
inputRules({ type }) {
|
||||
return [
|
||||
wrappingInputRule(/^\s*>\s$/, type),
|
||||
]
|
||||
}
|
||||
inputRules({ type }) {
|
||||
return [
|
||||
wrappingInputRule(/^\s*>\s$/, type),
|
||||
]
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -3,35 +3,35 @@ import { wrappingInputRule, toggleList } from 'tiptap-commands'
|
||||
|
||||
export default class Bullet extends Node {
|
||||
|
||||
get name() {
|
||||
return 'bullet_list'
|
||||
}
|
||||
get name() {
|
||||
return 'bullet_list'
|
||||
}
|
||||
|
||||
get schema() {
|
||||
return {
|
||||
content: 'list_item+',
|
||||
group: 'block',
|
||||
parseDOM: [
|
||||
{ tag: 'ul' },
|
||||
],
|
||||
toDOM: () => ['ul', 0],
|
||||
}
|
||||
}
|
||||
get schema() {
|
||||
return {
|
||||
content: 'list_item+',
|
||||
group: 'block',
|
||||
parseDOM: [
|
||||
{ tag: 'ul' },
|
||||
],
|
||||
toDOM: () => ['ul', 0],
|
||||
}
|
||||
}
|
||||
|
||||
commands({ type, schema }) {
|
||||
return () => toggleList(type, schema.nodes.list_item)
|
||||
}
|
||||
commands({ type, schema }) {
|
||||
return () => toggleList(type, schema.nodes.list_item)
|
||||
}
|
||||
|
||||
keys({ type, schema }) {
|
||||
return {
|
||||
'Shift-Ctrl-8': toggleList(type, schema.nodes.list_item),
|
||||
}
|
||||
}
|
||||
keys({ type, schema }) {
|
||||
return {
|
||||
'Shift-Ctrl-8': toggleList(type, schema.nodes.list_item),
|
||||
}
|
||||
}
|
||||
|
||||
inputRules({ type }) {
|
||||
return [
|
||||
wrappingInputRule(/^\s*([-+*])\s$/, type),
|
||||
]
|
||||
}
|
||||
inputRules({ type }) {
|
||||
return [
|
||||
wrappingInputRule(/^\s*([-+*])\s$/, type),
|
||||
]
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -3,39 +3,39 @@ import { toggleBlockType, setBlockType, textblockTypeInputRule } from 'tiptap-co
|
||||
|
||||
export default class CodeBlock extends Node {
|
||||
|
||||
get name() {
|
||||
return 'code_block'
|
||||
}
|
||||
get name() {
|
||||
return 'code_block'
|
||||
}
|
||||
|
||||
get schema() {
|
||||
return {
|
||||
content: 'text*',
|
||||
marks: '',
|
||||
group: 'block',
|
||||
code: true,
|
||||
defining: true,
|
||||
draggable: false,
|
||||
parseDOM: [
|
||||
{ tag: 'pre', preserveWhitespace: 'full' },
|
||||
],
|
||||
toDOM: () => ['pre', ['code', 0]],
|
||||
}
|
||||
}
|
||||
get schema() {
|
||||
return {
|
||||
content: 'text*',
|
||||
marks: '',
|
||||
group: 'block',
|
||||
code: true,
|
||||
defining: true,
|
||||
draggable: false,
|
||||
parseDOM: [
|
||||
{ tag: 'pre', preserveWhitespace: 'full' },
|
||||
],
|
||||
toDOM: () => ['pre', ['code', 0]],
|
||||
}
|
||||
}
|
||||
|
||||
commands({ type, schema }) {
|
||||
return () => toggleBlockType(type, schema.nodes.paragraph)
|
||||
}
|
||||
commands({ type, schema }) {
|
||||
return () => toggleBlockType(type, schema.nodes.paragraph)
|
||||
}
|
||||
|
||||
keys({ type }) {
|
||||
return {
|
||||
'Shift-Ctrl-\\': setBlockType(type),
|
||||
}
|
||||
}
|
||||
keys({ type }) {
|
||||
return {
|
||||
'Shift-Ctrl-\\': setBlockType(type),
|
||||
}
|
||||
}
|
||||
|
||||
inputRules({ type }) {
|
||||
return [
|
||||
textblockTypeInputRule(/^```$/, type),
|
||||
]
|
||||
}
|
||||
inputRules({ type }) {
|
||||
return [
|
||||
textblockTypeInputRule(/^```$/, type),
|
||||
]
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -5,139 +5,139 @@ import { findBlockNodes } from 'prosemirror-utils'
|
||||
import low from 'lowlight/lib/core'
|
||||
|
||||
function getDecorations(doc) {
|
||||
const decorations = []
|
||||
const decorations = []
|
||||
|
||||
const blocks = findBlockNodes(doc)
|
||||
.filter(item => item.node.type.name === 'code_block')
|
||||
const blocks = findBlockNodes(doc)
|
||||
.filter(item => item.node.type.name === 'code_block')
|
||||
|
||||
const flatten = list => list.reduce(
|
||||
(a, b) => a.concat(Array.isArray(b) ? flatten(b) : b), [],
|
||||
)
|
||||
const flatten = list => list.reduce(
|
||||
(a, b) => a.concat(Array.isArray(b) ? flatten(b) : b), [],
|
||||
)
|
||||
|
||||
function parseNodes(nodes, className = []) {
|
||||
return nodes.map(node => {
|
||||
function parseNodes(nodes, className = []) {
|
||||
return nodes.map(node => {
|
||||
|
||||
const classes = [
|
||||
...className,
|
||||
...node.properties ? node.properties.className : [],
|
||||
]
|
||||
const classes = [
|
||||
...className,
|
||||
...node.properties ? node.properties.className : [],
|
||||
]
|
||||
|
||||
if (node.children) {
|
||||
return parseNodes(node.children, classes)
|
||||
}
|
||||
if (node.children) {
|
||||
return parseNodes(node.children, classes)
|
||||
}
|
||||
|
||||
return {
|
||||
text: node.value,
|
||||
classes,
|
||||
}
|
||||
})
|
||||
}
|
||||
return {
|
||||
text: node.value,
|
||||
classes,
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
blocks.forEach(block => {
|
||||
let startPos = block.pos + 1
|
||||
const nodes = low.highlightAuto(block.node.textContent).value
|
||||
blocks.forEach(block => {
|
||||
let startPos = block.pos + 1
|
||||
const nodes = low.highlightAuto(block.node.textContent).value
|
||||
|
||||
flatten(parseNodes(nodes))
|
||||
.map(node => {
|
||||
const from = startPos
|
||||
const to = from + node.text.length
|
||||
flatten(parseNodes(nodes))
|
||||
.map(node => {
|
||||
const from = startPos
|
||||
const to = from + node.text.length
|
||||
|
||||
startPos = to
|
||||
startPos = to
|
||||
|
||||
return {
|
||||
...node,
|
||||
from,
|
||||
to,
|
||||
}
|
||||
})
|
||||
.forEach(node => {
|
||||
const decoration = Decoration.inline(node.from, node.to, {
|
||||
class: node.classes.join(' '),
|
||||
})
|
||||
decorations.push(decoration)
|
||||
})
|
||||
})
|
||||
return {
|
||||
...node,
|
||||
from,
|
||||
to,
|
||||
}
|
||||
})
|
||||
.forEach(node => {
|
||||
const decoration = Decoration.inline(node.from, node.to, {
|
||||
class: node.classes.join(' '),
|
||||
})
|
||||
decorations.push(decoration)
|
||||
})
|
||||
})
|
||||
|
||||
return DecorationSet.create(doc, decorations)
|
||||
return DecorationSet.create(doc, decorations)
|
||||
}
|
||||
|
||||
export default class CodeBlockHighlight extends Node {
|
||||
|
||||
constructor(options = {}) {
|
||||
super(options)
|
||||
try {
|
||||
Object.entries(this.options.languages).forEach(([name, mapping]) => {
|
||||
low.registerLanguage(name, mapping)
|
||||
})
|
||||
} catch (err) {
|
||||
throw new Error('Invalid syntax highlight definitions: define at least one highlight.js language mapping')
|
||||
}
|
||||
}
|
||||
constructor(options = {}) {
|
||||
super(options)
|
||||
try {
|
||||
Object.entries(this.options.languages).forEach(([name, mapping]) => {
|
||||
low.registerLanguage(name, mapping)
|
||||
})
|
||||
} catch (err) {
|
||||
throw new Error('Invalid syntax highlight definitions: define at least one highlight.js language mapping')
|
||||
}
|
||||
}
|
||||
|
||||
get defaultOptions() {
|
||||
return {
|
||||
languages: {},
|
||||
}
|
||||
}
|
||||
get defaultOptions() {
|
||||
return {
|
||||
languages: {},
|
||||
}
|
||||
}
|
||||
|
||||
get name() {
|
||||
return 'code_block'
|
||||
}
|
||||
get name() {
|
||||
return 'code_block'
|
||||
}
|
||||
|
||||
get schema() {
|
||||
return {
|
||||
content: 'text*',
|
||||
marks: '',
|
||||
group: 'block',
|
||||
code: true,
|
||||
defining: true,
|
||||
draggable: false,
|
||||
parseDOM: [
|
||||
{ tag: 'pre', preserveWhitespace: 'full' },
|
||||
],
|
||||
toDOM: () => ['pre', ['code', 0]],
|
||||
}
|
||||
}
|
||||
get schema() {
|
||||
return {
|
||||
content: 'text*',
|
||||
marks: '',
|
||||
group: 'block',
|
||||
code: true,
|
||||
defining: true,
|
||||
draggable: false,
|
||||
parseDOM: [
|
||||
{ tag: 'pre', preserveWhitespace: 'full' },
|
||||
],
|
||||
toDOM: () => ['pre', ['code', 0]],
|
||||
}
|
||||
}
|
||||
|
||||
commands({ type, schema }) {
|
||||
return () => toggleBlockType(type, schema.nodes.paragraph)
|
||||
}
|
||||
commands({ type, schema }) {
|
||||
return () => toggleBlockType(type, schema.nodes.paragraph)
|
||||
}
|
||||
|
||||
keys({ type }) {
|
||||
return {
|
||||
'Shift-Ctrl-\\': setBlockType(type),
|
||||
}
|
||||
}
|
||||
keys({ type }) {
|
||||
return {
|
||||
'Shift-Ctrl-\\': setBlockType(type),
|
||||
}
|
||||
}
|
||||
|
||||
inputRules({ type }) {
|
||||
return [
|
||||
textblockTypeInputRule(/^```$/, type),
|
||||
]
|
||||
}
|
||||
inputRules({ type }) {
|
||||
return [
|
||||
textblockTypeInputRule(/^```$/, type),
|
||||
]
|
||||
}
|
||||
|
||||
get plugins() {
|
||||
return [
|
||||
new Plugin({
|
||||
state: {
|
||||
init(_, { doc }) {
|
||||
return getDecorations(doc)
|
||||
},
|
||||
apply(tr, set) {
|
||||
// TODO: find way to cache decorations
|
||||
// see: https://discuss.prosemirror.net/t/how-to-update-multiple-inline-decorations-on-node-change/1493
|
||||
if (tr.docChanged) {
|
||||
return getDecorations(tr.doc)
|
||||
}
|
||||
return set.map(tr.mapping, tr.doc)
|
||||
},
|
||||
},
|
||||
props: {
|
||||
decorations(state) {
|
||||
return this.getState(state)
|
||||
},
|
||||
},
|
||||
}),
|
||||
]
|
||||
}
|
||||
get plugins() {
|
||||
return [
|
||||
new Plugin({
|
||||
state: {
|
||||
init(_, { doc }) {
|
||||
return getDecorations(doc)
|
||||
},
|
||||
apply(tr, set) {
|
||||
// TODO: find way to cache decorations
|
||||
// see: https://discuss.prosemirror.net/t/how-to-update-multiple-inline-decorations-on-node-change/1493
|
||||
if (tr.docChanged) {
|
||||
return getDecorations(tr.doc)
|
||||
}
|
||||
return set.map(tr.mapping, tr.doc)
|
||||
},
|
||||
},
|
||||
props: {
|
||||
decorations(state) {
|
||||
return this.getState(state)
|
||||
},
|
||||
},
|
||||
}),
|
||||
]
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -3,31 +3,31 @@ import { chainCommands, exitCode } from 'tiptap-commands'
|
||||
|
||||
export default class HardBreak extends Node {
|
||||
|
||||
get name() {
|
||||
return 'hard_break'
|
||||
}
|
||||
get name() {
|
||||
return 'hard_break'
|
||||
}
|
||||
|
||||
get schema() {
|
||||
return {
|
||||
inline: true,
|
||||
group: 'inline',
|
||||
selectable: false,
|
||||
parseDOM: [
|
||||
{ tag: 'br' },
|
||||
],
|
||||
toDOM: () => ['br'],
|
||||
}
|
||||
}
|
||||
get schema() {
|
||||
return {
|
||||
inline: true,
|
||||
group: 'inline',
|
||||
selectable: false,
|
||||
parseDOM: [
|
||||
{ tag: 'br' },
|
||||
],
|
||||
toDOM: () => ['br'],
|
||||
}
|
||||
}
|
||||
|
||||
keys({ type }) {
|
||||
const command = chainCommands(exitCode, (state, dispatch) => {
|
||||
dispatch(state.tr.replaceSelectionWith(type.create()).scrollIntoView())
|
||||
return true
|
||||
})
|
||||
return {
|
||||
'Mod-Enter': command,
|
||||
'Shift-Enter': command,
|
||||
}
|
||||
}
|
||||
keys({ type }) {
|
||||
const command = chainCommands(exitCode, (state, dispatch) => {
|
||||
dispatch(state.tr.replaceSelectionWith(type.create()).scrollIntoView())
|
||||
return true
|
||||
})
|
||||
return {
|
||||
'Mod-Enter': command,
|
||||
'Shift-Enter': command,
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -3,55 +3,55 @@ import { setBlockType, textblockTypeInputRule, toggleBlockType } from 'tiptap-co
|
||||
|
||||
export default class Heading extends Node {
|
||||
|
||||
get name() {
|
||||
return 'heading'
|
||||
}
|
||||
get name() {
|
||||
return 'heading'
|
||||
}
|
||||
|
||||
get defaultOptions() {
|
||||
return {
|
||||
levels: [1, 2, 3, 4, 5, 6],
|
||||
}
|
||||
}
|
||||
get defaultOptions() {
|
||||
return {
|
||||
levels: [1, 2, 3, 4, 5, 6],
|
||||
}
|
||||
}
|
||||
|
||||
get schema() {
|
||||
return {
|
||||
attrs: {
|
||||
level: {
|
||||
default: 1,
|
||||
},
|
||||
},
|
||||
content: 'inline*',
|
||||
group: 'block',
|
||||
defining: true,
|
||||
draggable: false,
|
||||
parseDOM: this.options.levels
|
||||
.map(level => ({
|
||||
tag: `h${level}`,
|
||||
attrs: { level },
|
||||
})),
|
||||
toDOM: node => [`h${node.attrs.level}`, 0],
|
||||
}
|
||||
}
|
||||
get schema() {
|
||||
return {
|
||||
attrs: {
|
||||
level: {
|
||||
default: 1,
|
||||
},
|
||||
},
|
||||
content: 'inline*',
|
||||
group: 'block',
|
||||
defining: true,
|
||||
draggable: false,
|
||||
parseDOM: this.options.levels
|
||||
.map(level => ({
|
||||
tag: `h${level}`,
|
||||
attrs: { level },
|
||||
})),
|
||||
toDOM: node => [`h${node.attrs.level}`, 0],
|
||||
}
|
||||
}
|
||||
|
||||
commands({ type, schema }) {
|
||||
return attrs => toggleBlockType(type, schema.nodes.paragraph, attrs)
|
||||
}
|
||||
commands({ type, schema }) {
|
||||
return attrs => toggleBlockType(type, schema.nodes.paragraph, attrs)
|
||||
}
|
||||
|
||||
keys({ type }) {
|
||||
return this.options.levels.reduce((items, level) => ({
|
||||
...items,
|
||||
...{
|
||||
[`Shift-Ctrl-${level}`]: setBlockType(type, { level }),
|
||||
},
|
||||
}), {})
|
||||
}
|
||||
keys({ type }) {
|
||||
return this.options.levels.reduce((items, level) => ({
|
||||
...items,
|
||||
...{
|
||||
[`Shift-Ctrl-${level}`]: setBlockType(type, { level }),
|
||||
},
|
||||
}), {})
|
||||
}
|
||||
|
||||
inputRules({ type }) {
|
||||
return this.options.levels.map(level => textblockTypeInputRule(
|
||||
new RegExp(`^(#{1,${level}})\\s$`),
|
||||
type,
|
||||
match => ({ level }),
|
||||
))
|
||||
}
|
||||
inputRules({ type }) {
|
||||
return this.options.levels.map(level => textblockTypeInputRule(
|
||||
new RegExp(`^(#{1,${level}})\\s$`),
|
||||
type,
|
||||
match => ({ level }),
|
||||
))
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -2,92 +2,92 @@ import { Node, Plugin } from 'tiptap'
|
||||
|
||||
export default class Image extends Node {
|
||||
|
||||
get name() {
|
||||
return 'image'
|
||||
}
|
||||
get name() {
|
||||
return 'image'
|
||||
}
|
||||
|
||||
get schema() {
|
||||
return {
|
||||
inline: true,
|
||||
attrs: {
|
||||
src: {},
|
||||
alt: {
|
||||
default: null,
|
||||
},
|
||||
title: {
|
||||
default: null,
|
||||
},
|
||||
},
|
||||
group: 'inline',
|
||||
draggable: true,
|
||||
parseDOM: [
|
||||
{
|
||||
tag: 'img[src]',
|
||||
getAttrs: dom => ({
|
||||
src: dom.getAttribute('src'),
|
||||
title: dom.getAttribute('title'),
|
||||
alt: dom.getAttribute('alt'),
|
||||
}),
|
||||
},
|
||||
],
|
||||
toDOM: node => ['img', node.attrs],
|
||||
}
|
||||
}
|
||||
get schema() {
|
||||
return {
|
||||
inline: true,
|
||||
attrs: {
|
||||
src: {},
|
||||
alt: {
|
||||
default: null,
|
||||
},
|
||||
title: {
|
||||
default: null,
|
||||
},
|
||||
},
|
||||
group: 'inline',
|
||||
draggable: true,
|
||||
parseDOM: [
|
||||
{
|
||||
tag: 'img[src]',
|
||||
getAttrs: dom => ({
|
||||
src: dom.getAttribute('src'),
|
||||
title: dom.getAttribute('title'),
|
||||
alt: dom.getAttribute('alt'),
|
||||
}),
|
||||
},
|
||||
],
|
||||
toDOM: node => ['img', node.attrs],
|
||||
}
|
||||
}
|
||||
|
||||
commands({ type }) {
|
||||
return attrs => (state, dispatch) => {
|
||||
const { selection } = state
|
||||
const position = selection.$cursor ? selection.$cursor.pos : selection.$to.pos
|
||||
const node = type.create(attrs)
|
||||
const transaction = state.tr.insert(position, node)
|
||||
dispatch(transaction)
|
||||
}
|
||||
}
|
||||
commands({ type }) {
|
||||
return attrs => (state, dispatch) => {
|
||||
const { selection } = state
|
||||
const position = selection.$cursor ? selection.$cursor.pos : selection.$to.pos
|
||||
const node = type.create(attrs)
|
||||
const transaction = state.tr.insert(position, node)
|
||||
dispatch(transaction)
|
||||
}
|
||||
}
|
||||
|
||||
get plugins() {
|
||||
return [
|
||||
new Plugin({
|
||||
props: {
|
||||
handleDOMEvents: {
|
||||
drop(view, event) {
|
||||
const hasFiles = event.dataTransfer
|
||||
&& event.dataTransfer.files
|
||||
&& event.dataTransfer.files.length
|
||||
get plugins() {
|
||||
return [
|
||||
new Plugin({
|
||||
props: {
|
||||
handleDOMEvents: {
|
||||
drop(view, event) {
|
||||
const hasFiles = event.dataTransfer
|
||||
&& event.dataTransfer.files
|
||||
&& event.dataTransfer.files.length
|
||||
|
||||
if (!hasFiles) {
|
||||
return
|
||||
}
|
||||
if (!hasFiles) {
|
||||
return
|
||||
}
|
||||
|
||||
const images = Array
|
||||
.from(event.dataTransfer.files)
|
||||
.filter(file => (/image/i).test(file.type))
|
||||
const images = Array
|
||||
.from(event.dataTransfer.files)
|
||||
.filter(file => (/image/i).test(file.type))
|
||||
|
||||
if (images.length === 0) {
|
||||
return
|
||||
}
|
||||
if (images.length === 0) {
|
||||
return
|
||||
}
|
||||
|
||||
event.preventDefault()
|
||||
event.preventDefault()
|
||||
|
||||
const { schema } = view.state
|
||||
const coordinates = view.posAtCoords({ left: event.clientX, top: event.clientY })
|
||||
const { schema } = view.state
|
||||
const coordinates = view.posAtCoords({ left: event.clientX, top: event.clientY })
|
||||
|
||||
images.forEach(image => {
|
||||
const reader = new FileReader()
|
||||
images.forEach(image => {
|
||||
const reader = new FileReader()
|
||||
|
||||
reader.onload = readerEvent => {
|
||||
const node = schema.nodes.image.create({
|
||||
src: readerEvent.target.result,
|
||||
})
|
||||
const transaction = view.state.tr.insert(coordinates.pos, node)
|
||||
view.dispatch(transaction)
|
||||
}
|
||||
reader.readAsDataURL(image)
|
||||
})
|
||||
},
|
||||
},
|
||||
},
|
||||
}),
|
||||
]
|
||||
}
|
||||
reader.onload = readerEvent => {
|
||||
const node = schema.nodes.image.create({
|
||||
src: readerEvent.target.result,
|
||||
})
|
||||
const transaction = view.state.tr.insert(coordinates.pos, node)
|
||||
view.dispatch(transaction)
|
||||
}
|
||||
reader.readAsDataURL(image)
|
||||
})
|
||||
},
|
||||
},
|
||||
},
|
||||
}),
|
||||
]
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -3,28 +3,28 @@ import { splitListItem, liftListItem, sinkListItem } from 'tiptap-commands'
|
||||
|
||||
export default class ListItem extends Node {
|
||||
|
||||
get name() {
|
||||
return 'list_item'
|
||||
}
|
||||
get name() {
|
||||
return 'list_item'
|
||||
}
|
||||
|
||||
get schema() {
|
||||
return {
|
||||
content: 'paragraph block*',
|
||||
defining: true,
|
||||
draggable: false,
|
||||
parseDOM: [
|
||||
{ tag: 'li' },
|
||||
],
|
||||
toDOM: () => ['li', 0],
|
||||
}
|
||||
}
|
||||
get schema() {
|
||||
return {
|
||||
content: 'paragraph block*',
|
||||
defining: true,
|
||||
draggable: false,
|
||||
parseDOM: [
|
||||
{ tag: 'li' },
|
||||
],
|
||||
toDOM: () => ['li', 0],
|
||||
}
|
||||
}
|
||||
|
||||
keys({ type }) {
|
||||
return {
|
||||
Enter: splitListItem(type),
|
||||
Tab: sinkListItem(type),
|
||||
'Shift-Tab': liftListItem(type),
|
||||
}
|
||||
}
|
||||
keys({ type }) {
|
||||
return {
|
||||
Enter: splitListItem(type),
|
||||
Tab: sinkListItem(type),
|
||||
'Shift-Tab': liftListItem(type),
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -4,68 +4,68 @@ import SuggestionsPlugin from '../plugins/Suggestions'
|
||||
|
||||
export default class Mention extends Node {
|
||||
|
||||
get name() {
|
||||
return 'mention'
|
||||
}
|
||||
get name() {
|
||||
return 'mention'
|
||||
}
|
||||
|
||||
get defaultOptions() {
|
||||
return {
|
||||
matcher: {
|
||||
char: '@',
|
||||
allowSpaces: false,
|
||||
startOfLine: false,
|
||||
},
|
||||
mentionClass: 'mention',
|
||||
suggestionClass: 'mention-suggestion',
|
||||
}
|
||||
}
|
||||
get defaultOptions() {
|
||||
return {
|
||||
matcher: {
|
||||
char: '@',
|
||||
allowSpaces: false,
|
||||
startOfLine: false,
|
||||
},
|
||||
mentionClass: 'mention',
|
||||
suggestionClass: 'mention-suggestion',
|
||||
}
|
||||
}
|
||||
|
||||
get schema() {
|
||||
return {
|
||||
attrs: {
|
||||
id: {},
|
||||
label: {},
|
||||
},
|
||||
group: 'inline',
|
||||
inline: true,
|
||||
selectable: false,
|
||||
atom: true,
|
||||
toDOM: node => [
|
||||
'span',
|
||||
{
|
||||
class: this.options.mentionClass,
|
||||
'data-mention-id': node.attrs.id,
|
||||
},
|
||||
`${this.options.matcher.char}${node.attrs.label}`,
|
||||
],
|
||||
parseDOM: [
|
||||
{
|
||||
tag: 'span[data-mention-id]',
|
||||
getAttrs: dom => {
|
||||
const id = dom.getAttribute('data-mention-id')
|
||||
const label = dom.innerText.split(this.options.matcher.char).join('')
|
||||
return { id, label }
|
||||
},
|
||||
},
|
||||
],
|
||||
}
|
||||
}
|
||||
get schema() {
|
||||
return {
|
||||
attrs: {
|
||||
id: {},
|
||||
label: {},
|
||||
},
|
||||
group: 'inline',
|
||||
inline: true,
|
||||
selectable: false,
|
||||
atom: true,
|
||||
toDOM: node => [
|
||||
'span',
|
||||
{
|
||||
class: this.options.mentionClass,
|
||||
'data-mention-id': node.attrs.id,
|
||||
},
|
||||
`${this.options.matcher.char}${node.attrs.label}`,
|
||||
],
|
||||
parseDOM: [
|
||||
{
|
||||
tag: 'span[data-mention-id]',
|
||||
getAttrs: dom => {
|
||||
const id = dom.getAttribute('data-mention-id')
|
||||
const label = dom.innerText.split(this.options.matcher.char).join('')
|
||||
return { id, label }
|
||||
},
|
||||
},
|
||||
],
|
||||
}
|
||||
}
|
||||
|
||||
get plugins() {
|
||||
return [
|
||||
SuggestionsPlugin({
|
||||
command: ({ range, attrs, schema }) => replaceText(range, schema.nodes.mention, attrs),
|
||||
appendText: ' ',
|
||||
matcher: this.options.matcher,
|
||||
items: this.options.items,
|
||||
onEnter: this.options.onEnter,
|
||||
onChange: this.options.onChange,
|
||||
onExit: this.options.onExit,
|
||||
onKeyDown: this.options.onKeyDown,
|
||||
onFilter: this.options.onFilter,
|
||||
suggestionClass: this.options.suggestionClass,
|
||||
}),
|
||||
]
|
||||
}
|
||||
get plugins() {
|
||||
return [
|
||||
SuggestionsPlugin({
|
||||
command: ({ range, attrs, schema }) => replaceText(range, schema.nodes.mention, attrs),
|
||||
appendText: ' ',
|
||||
matcher: this.options.matcher,
|
||||
items: this.options.items,
|
||||
onEnter: this.options.onEnter,
|
||||
onChange: this.options.onChange,
|
||||
onExit: this.options.onExit,
|
||||
onKeyDown: this.options.onKeyDown,
|
||||
onFilter: this.options.onFilter,
|
||||
suggestionClass: this.options.suggestionClass,
|
||||
}),
|
||||
]
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -3,50 +3,50 @@ import { wrappingInputRule, toggleList } from 'tiptap-commands'
|
||||
|
||||
export default class OrderedList extends Node {
|
||||
|
||||
get name() {
|
||||
return 'ordered_list'
|
||||
}
|
||||
get name() {
|
||||
return 'ordered_list'
|
||||
}
|
||||
|
||||
get schema() {
|
||||
return {
|
||||
attrs: {
|
||||
order: {
|
||||
default: 1,
|
||||
},
|
||||
},
|
||||
content: 'list_item+',
|
||||
group: 'block',
|
||||
parseDOM: [
|
||||
{
|
||||
tag: 'ol',
|
||||
getAttrs: dom => ({
|
||||
order: dom.hasAttribute('start') ? +dom.getAttribute('start') : 1,
|
||||
}),
|
||||
},
|
||||
],
|
||||
toDOM: node => (node.attrs.order === 1 ? ['ol', 0] : ['ol', { start: node.attrs.order }, 0]),
|
||||
}
|
||||
}
|
||||
get schema() {
|
||||
return {
|
||||
attrs: {
|
||||
order: {
|
||||
default: 1,
|
||||
},
|
||||
},
|
||||
content: 'list_item+',
|
||||
group: 'block',
|
||||
parseDOM: [
|
||||
{
|
||||
tag: 'ol',
|
||||
getAttrs: dom => ({
|
||||
order: dom.hasAttribute('start') ? +dom.getAttribute('start') : 1,
|
||||
}),
|
||||
},
|
||||
],
|
||||
toDOM: node => (node.attrs.order === 1 ? ['ol', 0] : ['ol', { start: node.attrs.order }, 0]),
|
||||
}
|
||||
}
|
||||
|
||||
commands({ type, schema }) {
|
||||
return () => toggleList(type, schema.nodes.list_item)
|
||||
}
|
||||
commands({ type, schema }) {
|
||||
return () => toggleList(type, schema.nodes.list_item)
|
||||
}
|
||||
|
||||
keys({ type, schema }) {
|
||||
return {
|
||||
'Shift-Ctrl-9': toggleList(type, schema.nodes.list_item),
|
||||
}
|
||||
}
|
||||
keys({ type, schema }) {
|
||||
return {
|
||||
'Shift-Ctrl-9': toggleList(type, schema.nodes.list_item),
|
||||
}
|
||||
}
|
||||
|
||||
inputRules({ type }) {
|
||||
return [
|
||||
wrappingInputRule(
|
||||
/^(\d+)\.\s$/,
|
||||
type,
|
||||
match => ({ order: +match[1] }),
|
||||
(match, node) => node.childCount + node.attrs.order === +match[1],
|
||||
),
|
||||
]
|
||||
}
|
||||
inputRules({ type }) {
|
||||
return [
|
||||
wrappingInputRule(
|
||||
/^(\d+)\.\s$/,
|
||||
type,
|
||||
match => ({ order: +match[1] }),
|
||||
(match, node) => node.childCount + node.attrs.order === +match[1],
|
||||
),
|
||||
]
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -3,64 +3,64 @@ import { splitToDefaultListItem, liftListItem } from 'tiptap-commands'
|
||||
|
||||
export default class TodoItem extends Node {
|
||||
|
||||
get name() {
|
||||
return 'todo_item'
|
||||
}
|
||||
get name() {
|
||||
return 'todo_item'
|
||||
}
|
||||
|
||||
get view() {
|
||||
return {
|
||||
props: ['node', 'updateAttrs', 'editable'],
|
||||
methods: {
|
||||
onChange() {
|
||||
this.updateAttrs({
|
||||
done: !this.node.attrs.done,
|
||||
})
|
||||
},
|
||||
},
|
||||
template: `
|
||||
<li data-type="todo_item" :data-done="node.attrs.done.toString()">
|
||||
<span class="todo-checkbox" contenteditable="false" @click="onChange"></span>
|
||||
<div class="todo-content" ref="content" :contenteditable="editable.toString()"></div>
|
||||
</li>
|
||||
`,
|
||||
}
|
||||
}
|
||||
get view() {
|
||||
return {
|
||||
props: ['node', 'updateAttrs', 'editable'],
|
||||
methods: {
|
||||
onChange() {
|
||||
this.updateAttrs({
|
||||
done: !this.node.attrs.done,
|
||||
})
|
||||
},
|
||||
},
|
||||
template: `
|
||||
<li data-type="todo_item" :data-done="node.attrs.done.toString()">
|
||||
<span class="todo-checkbox" contenteditable="false" @click="onChange"></span>
|
||||
<div class="todo-content" ref="content" :contenteditable="editable.toString()"></div>
|
||||
</li>
|
||||
`,
|
||||
}
|
||||
}
|
||||
|
||||
get schema() {
|
||||
return {
|
||||
attrs: {
|
||||
done: {
|
||||
default: false,
|
||||
},
|
||||
},
|
||||
draggable: false,
|
||||
content: 'paragraph',
|
||||
toDOM(node) {
|
||||
const { done } = node.attrs
|
||||
get schema() {
|
||||
return {
|
||||
attrs: {
|
||||
done: {
|
||||
default: false,
|
||||
},
|
||||
},
|
||||
draggable: false,
|
||||
content: 'paragraph',
|
||||
toDOM(node) {
|
||||
const { done } = node.attrs
|
||||
|
||||
return ['li', {
|
||||
'data-type': 'todo_item',
|
||||
'data-done': done.toString(),
|
||||
},
|
||||
['span', { class: 'todo-checkbox', contenteditable: 'false' }],
|
||||
['div', { class: 'todo-content' }, 0],
|
||||
]
|
||||
},
|
||||
parseDOM: [{
|
||||
priority: 51,
|
||||
tag: '[data-type="todo_item"]',
|
||||
getAttrs: dom => ({
|
||||
done: dom.getAttribute('data-done') === 'true',
|
||||
}),
|
||||
}],
|
||||
}
|
||||
}
|
||||
return ['li', {
|
||||
'data-type': 'todo_item',
|
||||
'data-done': done.toString(),
|
||||
},
|
||||
['span', { class: 'todo-checkbox', contenteditable: 'false' }],
|
||||
['div', { class: 'todo-content' }, 0],
|
||||
]
|
||||
},
|
||||
parseDOM: [{
|
||||
priority: 51,
|
||||
tag: '[data-type="todo_item"]',
|
||||
getAttrs: dom => ({
|
||||
done: dom.getAttribute('data-done') === 'true',
|
||||
}),
|
||||
}],
|
||||
}
|
||||
}
|
||||
|
||||
keys({ type }) {
|
||||
return {
|
||||
Enter: splitToDefaultListItem(type),
|
||||
'Shift-Tab': liftListItem(type),
|
||||
}
|
||||
}
|
||||
keys({ type }) {
|
||||
return {
|
||||
Enter: splitToDefaultListItem(type),
|
||||
'Shift-Tab': liftListItem(type),
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -3,30 +3,30 @@ import { wrapInList, wrappingInputRule } from 'tiptap-commands'
|
||||
|
||||
export default class TodoList extends Node {
|
||||
|
||||
get name() {
|
||||
return 'todo_list'
|
||||
}
|
||||
get name() {
|
||||
return 'todo_list'
|
||||
}
|
||||
|
||||
get schema() {
|
||||
return {
|
||||
group: 'block',
|
||||
content: 'todo_item+',
|
||||
toDOM: () => ['ul', { 'data-type': 'todo_list' }, 0],
|
||||
parseDOM: [{
|
||||
priority: 51,
|
||||
tag: '[data-type="todo_list"]',
|
||||
}],
|
||||
}
|
||||
}
|
||||
get schema() {
|
||||
return {
|
||||
group: 'block',
|
||||
content: 'todo_item+',
|
||||
toDOM: () => ['ul', { 'data-type': 'todo_list' }, 0],
|
||||
parseDOM: [{
|
||||
priority: 51,
|
||||
tag: '[data-type="todo_list"]',
|
||||
}],
|
||||
}
|
||||
}
|
||||
|
||||
commands({ type }) {
|
||||
return () => wrapInList(type)
|
||||
}
|
||||
commands({ type }) {
|
||||
return () => wrapInList(type)
|
||||
}
|
||||
|
||||
inputRules({ type }) {
|
||||
return [
|
||||
wrappingInputRule(/^\s*(\[ \])\s$/, type),
|
||||
]
|
||||
}
|
||||
inputRules({ type }) {
|
||||
return [
|
||||
wrappingInputRule(/^\s*(\[ \])\s$/, type),
|
||||
]
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -4,239 +4,239 @@ import { insertText } from 'tiptap-commands'
|
||||
|
||||
// Create a matcher that matches when a specific character is typed. Useful for @mentions and #tags.
|
||||
function triggerCharacter({
|
||||
char = '@',
|
||||
allowSpaces = false,
|
||||
startOfLine = false,
|
||||
char = '@',
|
||||
allowSpaces = false,
|
||||
startOfLine = false,
|
||||
}) {
|
||||
|
||||
return $position => {
|
||||
// Matching expressions used for later
|
||||
const suffix = new RegExp(`\\s${char}$`)
|
||||
const prefix = startOfLine ? '^' : ''
|
||||
const regexp = allowSpaces
|
||||
? new RegExp(`${prefix}${char}.*?(?=\\s${char}|$)`, 'gm')
|
||||
: new RegExp(`${prefix}(?:^)?${char}[^\\s${char}]*`, 'gm')
|
||||
return $position => {
|
||||
// Matching expressions used for later
|
||||
const suffix = new RegExp(`\\s${char}$`)
|
||||
const prefix = startOfLine ? '^' : ''
|
||||
const regexp = allowSpaces
|
||||
? new RegExp(`${prefix}${char}.*?(?=\\s${char}|$)`, 'gm')
|
||||
: new RegExp(`${prefix}(?:^)?${char}[^\\s${char}]*`, 'gm')
|
||||
|
||||
// Lookup the boundaries of the current node
|
||||
const textFrom = $position.before()
|
||||
const textTo = $position.end()
|
||||
const text = $position.doc.textBetween(textFrom, textTo, '\0', '\0')
|
||||
// Lookup the boundaries of the current node
|
||||
const textFrom = $position.before()
|
||||
const textTo = $position.end()
|
||||
const text = $position.doc.textBetween(textFrom, textTo, '\0', '\0')
|
||||
|
||||
let match = regexp.exec(text)
|
||||
let position
|
||||
while (match !== null) {
|
||||
// JavaScript doesn't have lookbehinds; this hacks a check that first character is " "
|
||||
// or the line beginning
|
||||
const matchPrefix = match.input.slice(Math.max(0, match.index - 1), match.index)
|
||||
let match = regexp.exec(text)
|
||||
let position
|
||||
while (match !== null) {
|
||||
// JavaScript doesn't have lookbehinds; this hacks a check that first character is " "
|
||||
// or the line beginning
|
||||
const matchPrefix = match.input.slice(Math.max(0, match.index - 1), match.index)
|
||||
|
||||
if (/^[\s\0]?$/.test(matchPrefix)) {
|
||||
// The absolute position of the match in the document
|
||||
const from = match.index + $position.start()
|
||||
let to = from + match[0].length
|
||||
if (/^[\s\0]?$/.test(matchPrefix)) {
|
||||
// The absolute position of the match in the document
|
||||
const from = match.index + $position.start()
|
||||
let to = from + match[0].length
|
||||
|
||||
// Edge case handling; if spaces are allowed and we're directly in between
|
||||
// two triggers
|
||||
if (allowSpaces && suffix.test(text.slice(to - 1, to + 1))) {
|
||||
match[0] += ' '
|
||||
to += 1
|
||||
}
|
||||
// Edge case handling; if spaces are allowed and we're directly in between
|
||||
// two triggers
|
||||
if (allowSpaces && suffix.test(text.slice(to - 1, to + 1))) {
|
||||
match[0] += ' '
|
||||
to += 1
|
||||
}
|
||||
|
||||
// If the $position is located within the matched substring, return that range
|
||||
if (from < $position.pos && to >= $position.pos) {
|
||||
position = {
|
||||
range: {
|
||||
from,
|
||||
to,
|
||||
},
|
||||
query: match[0].slice(char.length),
|
||||
text: match[0],
|
||||
}
|
||||
}
|
||||
}
|
||||
// If the $position is located within the matched substring, return that range
|
||||
if (from < $position.pos && to >= $position.pos) {
|
||||
position = {
|
||||
range: {
|
||||
from,
|
||||
to,
|
||||
},
|
||||
query: match[0].slice(char.length),
|
||||
text: match[0],
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
match = regexp.exec(text)
|
||||
}
|
||||
}
|
||||
|
||||
return position
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default function SuggestionsPlugin({
|
||||
matcher = {
|
||||
char: '@',
|
||||
allowSpaces: false,
|
||||
startOfLine: false,
|
||||
},
|
||||
appendText = null,
|
||||
suggestionClass = 'suggestion',
|
||||
command = () => false,
|
||||
items = [],
|
||||
onEnter = () => false,
|
||||
onChange = () => false,
|
||||
onExit = () => false,
|
||||
onKeyDown = () => false,
|
||||
onFilter = (searchItems, query) => {
|
||||
if (!query) {
|
||||
return searchItems
|
||||
}
|
||||
matcher = {
|
||||
char: '@',
|
||||
allowSpaces: false,
|
||||
startOfLine: false,
|
||||
},
|
||||
appendText = null,
|
||||
suggestionClass = 'suggestion',
|
||||
command = () => false,
|
||||
items = [],
|
||||
onEnter = () => false,
|
||||
onChange = () => false,
|
||||
onExit = () => false,
|
||||
onKeyDown = () => false,
|
||||
onFilter = (searchItems, query) => {
|
||||
if (!query) {
|
||||
return searchItems
|
||||
}
|
||||
|
||||
return searchItems
|
||||
.filter(item => JSON.stringify(item).toLowerCase().includes(query.toLowerCase()))
|
||||
},
|
||||
return searchItems
|
||||
.filter(item => JSON.stringify(item).toLowerCase().includes(query.toLowerCase()))
|
||||
},
|
||||
}) {
|
||||
return new Plugin({
|
||||
key: new PluginKey('suggestions'),
|
||||
return new Plugin({
|
||||
key: new PluginKey('suggestions'),
|
||||
|
||||
view() {
|
||||
return {
|
||||
update: (view, prevState) => {
|
||||
const prev = this.key.getState(prevState)
|
||||
const next = this.key.getState(view.state)
|
||||
view() {
|
||||
return {
|
||||
update: (view, prevState) => {
|
||||
const prev = this.key.getState(prevState)
|
||||
const next = this.key.getState(view.state)
|
||||
|
||||
// See how the state changed
|
||||
const moved = prev.active && next.active && prev.range.from !== next.range.from
|
||||
const started = !prev.active && next.active
|
||||
const stopped = prev.active && !next.active
|
||||
const changed = !started && !stopped && prev.query !== next.query
|
||||
const handleStart = started || moved
|
||||
const handleChange = changed && !moved
|
||||
const handleExit = stopped || moved
|
||||
// See how the state changed
|
||||
const moved = prev.active && next.active && prev.range.from !== next.range.from
|
||||
const started = !prev.active && next.active
|
||||
const stopped = prev.active && !next.active
|
||||
const changed = !started && !stopped && prev.query !== next.query
|
||||
const handleStart = started || moved
|
||||
const handleChange = changed && !moved
|
||||
const handleExit = stopped || moved
|
||||
|
||||
// Cancel when suggestion isn't active
|
||||
if (!handleStart && !handleChange && !handleExit) {
|
||||
return
|
||||
}
|
||||
// Cancel when suggestion isn't active
|
||||
if (!handleStart && !handleChange && !handleExit) {
|
||||
return
|
||||
}
|
||||
|
||||
const state = handleExit ? prev : next
|
||||
const decorationNode = document.querySelector(`[data-decoration-id="${state.decorationId}"]`)
|
||||
const state = handleExit ? prev : next
|
||||
const decorationNode = document.querySelector(`[data-decoration-id="${state.decorationId}"]`)
|
||||
|
||||
// build a virtual node for popper.js or tippy.js
|
||||
// this can be used for building popups without a DOM node
|
||||
const virtualNode = decorationNode ? {
|
||||
getBoundingClientRect() {
|
||||
return decorationNode.getBoundingClientRect()
|
||||
},
|
||||
clientWidth: decorationNode.clientWidth,
|
||||
clientHeight: decorationNode.clientHeight,
|
||||
} : null
|
||||
// build a virtual node for popper.js or tippy.js
|
||||
// this can be used for building popups without a DOM node
|
||||
const virtualNode = decorationNode ? {
|
||||
getBoundingClientRect() {
|
||||
return decorationNode.getBoundingClientRect()
|
||||
},
|
||||
clientWidth: decorationNode.clientWidth,
|
||||
clientHeight: decorationNode.clientHeight,
|
||||
} : null
|
||||
|
||||
const props = {
|
||||
view,
|
||||
range: state.range,
|
||||
query: state.query,
|
||||
text: state.text,
|
||||
decorationNode,
|
||||
virtualNode,
|
||||
items: onFilter(Array.isArray(items) ? items : items(), state.query),
|
||||
command: ({ range, attrs }) => {
|
||||
command({
|
||||
range,
|
||||
attrs,
|
||||
schema: view.state.schema,
|
||||
})(view.state, view.dispatch, view)
|
||||
const props = {
|
||||
view,
|
||||
range: state.range,
|
||||
query: state.query,
|
||||
text: state.text,
|
||||
decorationNode,
|
||||
virtualNode,
|
||||
items: onFilter(Array.isArray(items) ? items : items(), state.query),
|
||||
command: ({ range, attrs }) => {
|
||||
command({
|
||||
range,
|
||||
attrs,
|
||||
schema: view.state.schema,
|
||||
})(view.state, view.dispatch, view)
|
||||
|
||||
if (appendText) {
|
||||
insertText(appendText)(view.state, view.dispatch, view)
|
||||
}
|
||||
},
|
||||
}
|
||||
if (appendText) {
|
||||
insertText(appendText)(view.state, view.dispatch, view)
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
// Trigger the hooks when necessary
|
||||
if (handleExit) {
|
||||
onExit(props)
|
||||
}
|
||||
// Trigger the hooks when necessary
|
||||
if (handleExit) {
|
||||
onExit(props)
|
||||
}
|
||||
|
||||
if (handleChange) {
|
||||
onChange(props)
|
||||
}
|
||||
if (handleChange) {
|
||||
onChange(props)
|
||||
}
|
||||
|
||||
if (handleStart) {
|
||||
onEnter(props)
|
||||
}
|
||||
},
|
||||
}
|
||||
},
|
||||
if (handleStart) {
|
||||
onEnter(props)
|
||||
}
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
state: {
|
||||
state: {
|
||||
|
||||
// Initialize the plugin's internal state.
|
||||
init() {
|
||||
return {
|
||||
active: false,
|
||||
range: {},
|
||||
query: null,
|
||||
text: null,
|
||||
}
|
||||
},
|
||||
// Initialize the plugin's internal state.
|
||||
init() {
|
||||
return {
|
||||
active: false,
|
||||
range: {},
|
||||
query: null,
|
||||
text: null,
|
||||
}
|
||||
},
|
||||
|
||||
// Apply changes to the plugin state from a view transaction.
|
||||
apply(tr, prev) {
|
||||
const { selection } = tr
|
||||
const next = { ...prev }
|
||||
// Apply changes to the plugin state from a view transaction.
|
||||
apply(tr, prev) {
|
||||
const { selection } = tr
|
||||
const next = { ...prev }
|
||||
|
||||
// We can only be suggesting if there is no selection
|
||||
if (selection.from === selection.to) {
|
||||
// Reset active state if we just left the previous suggestion range
|
||||
if (selection.from < prev.range.from || selection.from > prev.range.to) {
|
||||
next.active = false
|
||||
}
|
||||
// We can only be suggesting if there is no selection
|
||||
if (selection.from === selection.to) {
|
||||
// Reset active state if we just left the previous suggestion range
|
||||
if (selection.from < prev.range.from || selection.from > prev.range.to) {
|
||||
next.active = false
|
||||
}
|
||||
|
||||
// Try to match against where our cursor currently is
|
||||
const $position = selection.$from
|
||||
const match = triggerCharacter(matcher)($position)
|
||||
const decorationId = (Math.random() + 1).toString(36).substr(2, 5)
|
||||
// Try to match against where our cursor currently is
|
||||
const $position = selection.$from
|
||||
const match = triggerCharacter(matcher)($position)
|
||||
const decorationId = (Math.random() + 1).toString(36).substr(2, 5)
|
||||
|
||||
// If we found a match, update the current state to show it
|
||||
if (match) {
|
||||
next.active = true
|
||||
next.decorationId = prev.decorationId ? prev.decorationId : decorationId
|
||||
next.range = match.range
|
||||
next.query = match.query
|
||||
next.text = match.text
|
||||
} else {
|
||||
next.active = false
|
||||
}
|
||||
} else {
|
||||
next.active = false
|
||||
}
|
||||
// If we found a match, update the current state to show it
|
||||
if (match) {
|
||||
next.active = true
|
||||
next.decorationId = prev.decorationId ? prev.decorationId : decorationId
|
||||
next.range = match.range
|
||||
next.query = match.query
|
||||
next.text = match.text
|
||||
} else {
|
||||
next.active = false
|
||||
}
|
||||
} else {
|
||||
next.active = false
|
||||
}
|
||||
|
||||
// Make sure to empty the range if suggestion is inactive
|
||||
if (!next.active) {
|
||||
next.decorationId = null
|
||||
next.range = {}
|
||||
next.query = null
|
||||
next.text = null
|
||||
}
|
||||
// Make sure to empty the range if suggestion is inactive
|
||||
if (!next.active) {
|
||||
next.decorationId = null
|
||||
next.range = {}
|
||||
next.query = null
|
||||
next.text = null
|
||||
}
|
||||
|
||||
return next
|
||||
},
|
||||
},
|
||||
return next
|
||||
},
|
||||
},
|
||||
|
||||
props: {
|
||||
props: {
|
||||
|
||||
// Call the keydown hook if suggestion is active.
|
||||
handleKeyDown(view, event) {
|
||||
const { active, range } = this.getState(view.state)
|
||||
// Call the keydown hook if suggestion is active.
|
||||
handleKeyDown(view, event) {
|
||||
const { active, range } = this.getState(view.state)
|
||||
|
||||
if (!active) return false
|
||||
if (!active) return false
|
||||
|
||||
return onKeyDown({ view, event, range })
|
||||
},
|
||||
return onKeyDown({ view, event, range })
|
||||
},
|
||||
|
||||
// Setup decorator on the currently active suggestion.
|
||||
decorations(editorState) {
|
||||
const { active, range, decorationId } = this.getState(editorState)
|
||||
// Setup decorator on the currently active suggestion.
|
||||
decorations(editorState) {
|
||||
const { active, range, decorationId } = this.getState(editorState)
|
||||
|
||||
if (!active) return null
|
||||
if (!active) return null
|
||||
|
||||
return DecorationSet.create(editorState.doc, [
|
||||
Decoration.inline(range.from, range.to, {
|
||||
nodeName: 'span',
|
||||
class: suggestionClass,
|
||||
'data-decoration-id': decorationId,
|
||||
}),
|
||||
])
|
||||
},
|
||||
},
|
||||
})
|
||||
return DecorationSet.create(editorState.doc, [
|
||||
Decoration.inline(range.from, range.to, {
|
||||
nodeName: 'span',
|
||||
class: suggestionClass,
|
||||
'data-decoration-id': decorationId,
|
||||
}),
|
||||
])
|
||||
},
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
@@ -4,4 +4,4 @@ This is a collection of utility functions for [tiptap](https://www.npmjs.com/pac
|
||||
[](https://www.npmjs.com/package/tiptap-utils)
|
||||
[](https://npmcharts.com/compare/tiptap-utils?minimal=true)
|
||||
[](https://www.npmjs.com/package/tiptap-utils)
|
||||
[](https://www.npmjs.com/package/tiptap-utils)
|
||||
[](https://www.npmjs.com/package/tiptap-utils)
|
||||
|
||||
@@ -1,16 +1,16 @@
|
||||
export default function (state, type) {
|
||||
const { from, to } = state.selection
|
||||
let marks = []
|
||||
const { from, to } = state.selection
|
||||
let marks = []
|
||||
|
||||
state.doc.nodesBetween(from, to, node => {
|
||||
marks = [...marks, ...node.marks]
|
||||
})
|
||||
state.doc.nodesBetween(from, to, node => {
|
||||
marks = [...marks, ...node.marks]
|
||||
})
|
||||
|
||||
const mark = marks.find(markItem => markItem.type.name === type.name)
|
||||
const mark = marks.find(markItem => markItem.type.name === type.name)
|
||||
|
||||
if (mark) {
|
||||
return mark.attrs
|
||||
}
|
||||
if (mark) {
|
||||
return mark.attrs
|
||||
}
|
||||
|
||||
return {}
|
||||
return {}
|
||||
}
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
export default function (state, type) {
|
||||
const {
|
||||
from,
|
||||
$from,
|
||||
to,
|
||||
empty,
|
||||
} = state.selection
|
||||
const {
|
||||
from,
|
||||
$from,
|
||||
to,
|
||||
empty,
|
||||
} = state.selection
|
||||
|
||||
if (empty) {
|
||||
return !!type.isInSet(state.storedMarks || $from.marks())
|
||||
}
|
||||
if (empty) {
|
||||
return !!type.isInSet(state.storedMarks || $from.marks())
|
||||
}
|
||||
|
||||
return !!state.doc.rangeHasMark(from, to, type)
|
||||
return !!state.doc.rangeHasMark(from, to, type)
|
||||
}
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
import { findParentNode } from 'prosemirror-utils'
|
||||
|
||||
export default function (state, type, attrs = {}) {
|
||||
const predicate = node => node.type === type
|
||||
const parent = findParentNode(predicate)(state.selection)
|
||||
const predicate = node => node.type === type
|
||||
const parent = findParentNode(predicate)(state.selection)
|
||||
|
||||
if (!Object.keys(attrs).length || !parent) {
|
||||
return !!parent
|
||||
}
|
||||
if (!Object.keys(attrs).length || !parent) {
|
||||
return !!parent
|
||||
}
|
||||
|
||||
return parent.node.hasMarkup(type, attrs)
|
||||
return parent.node.hasMarkup(type, attrs)
|
||||
}
|
||||
|
||||
@@ -4,4 +4,4 @@ This is the core package of [tiptap](https://www.npmjs.com/package/tiptap).
|
||||
[](https://www.npmjs.com/package/tiptap)
|
||||
[](https://npmcharts.com/compare/tiptap?minimal=true)
|
||||
[](https://www.npmjs.com/package/tiptap)
|
||||
[](https://www.npmjs.com/package/tiptap)
|
||||
[](https://www.npmjs.com/package/tiptap)
|
||||
|
||||
@@ -1,21 +1,21 @@
|
||||
export default {
|
||||
props: {
|
||||
editor: {
|
||||
default: null,
|
||||
type: Object,
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
'editor.element': {
|
||||
immediate: true,
|
||||
handler(element) {
|
||||
if (element) {
|
||||
this.$nextTick(() => this.$el.append(element.firstChild))
|
||||
}
|
||||
},
|
||||
},
|
||||
},
|
||||
render(createElement) {
|
||||
return createElement('div')
|
||||
},
|
||||
props: {
|
||||
editor: {
|
||||
default: null,
|
||||
type: Object,
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
'editor.element': {
|
||||
immediate: true,
|
||||
handler(element) {
|
||||
if (element) {
|
||||
this.$nextTick(() => this.$el.append(element.firstChild))
|
||||
}
|
||||
},
|
||||
},
|
||||
},
|
||||
render(createElement) {
|
||||
return createElement('div')
|
||||
},
|
||||
}
|
||||
|
||||
@@ -1,50 +1,50 @@
|
||||
import FloatingMenu from '../Utils/FloatingMenu'
|
||||
|
||||
export default {
|
||||
props: {
|
||||
editor: {
|
||||
default: null,
|
||||
type: Object,
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
menu: {
|
||||
isActive: false,
|
||||
left: 0,
|
||||
bottom: 0,
|
||||
},
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
editor: {
|
||||
immediate: true,
|
||||
handler(editor) {
|
||||
if (editor) {
|
||||
this.$nextTick(() => {
|
||||
editor.registerPlugin(FloatingMenu({
|
||||
element: this.$el,
|
||||
onUpdate: menu => {
|
||||
this.menu = menu
|
||||
},
|
||||
}))
|
||||
})
|
||||
}
|
||||
},
|
||||
},
|
||||
},
|
||||
render() {
|
||||
if (!this.editor) {
|
||||
return null
|
||||
}
|
||||
props: {
|
||||
editor: {
|
||||
default: null,
|
||||
type: Object,
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
menu: {
|
||||
isActive: false,
|
||||
left: 0,
|
||||
bottom: 0,
|
||||
},
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
editor: {
|
||||
immediate: true,
|
||||
handler(editor) {
|
||||
if (editor) {
|
||||
this.$nextTick(() => {
|
||||
editor.registerPlugin(FloatingMenu({
|
||||
element: this.$el,
|
||||
onUpdate: menu => {
|
||||
this.menu = menu
|
||||
},
|
||||
}))
|
||||
})
|
||||
}
|
||||
},
|
||||
},
|
||||
},
|
||||
render() {
|
||||
if (!this.editor) {
|
||||
return null
|
||||
}
|
||||
|
||||
return this.$scopedSlots.default({
|
||||
focused: this.editor.view.focused,
|
||||
focus: this.editor.focus,
|
||||
commands: this.editor.commands,
|
||||
isActive: this.editor.isActive.bind(this.editor),
|
||||
markAttrs: this.editor.markAttrs.bind(this.editor),
|
||||
menu: this.menu,
|
||||
})
|
||||
},
|
||||
return this.$scopedSlots.default({
|
||||
focused: this.editor.view.focused,
|
||||
focus: this.editor.focus,
|
||||
commands: this.editor.commands,
|
||||
isActive: this.editor.isActive.bind(this.editor),
|
||||
markAttrs: this.editor.markAttrs.bind(this.editor),
|
||||
menu: this.menu,
|
||||
})
|
||||
},
|
||||
}
|
||||
|
||||
@@ -1,21 +1,21 @@
|
||||
export default {
|
||||
props: {
|
||||
editor: {
|
||||
default: null,
|
||||
type: Object,
|
||||
},
|
||||
},
|
||||
render() {
|
||||
if (!this.editor) {
|
||||
return null
|
||||
}
|
||||
props: {
|
||||
editor: {
|
||||
default: null,
|
||||
type: Object,
|
||||
},
|
||||
},
|
||||
render() {
|
||||
if (!this.editor) {
|
||||
return null
|
||||
}
|
||||
|
||||
return this.$scopedSlots.default({
|
||||
focused: this.editor.view.focused,
|
||||
focus: this.editor.focus,
|
||||
commands: this.editor.commands,
|
||||
isActive: this.editor.isActive.bind(this.editor),
|
||||
markAttrs: this.editor.markAttrs.bind(this.editor),
|
||||
})
|
||||
},
|
||||
return this.$scopedSlots.default({
|
||||
focused: this.editor.view.focused,
|
||||
focus: this.editor.focus,
|
||||
commands: this.editor.commands,
|
||||
isActive: this.editor.isActive.bind(this.editor),
|
||||
markAttrs: this.editor.markAttrs.bind(this.editor),
|
||||
})
|
||||
},
|
||||
}
|
||||
|
||||
@@ -1,50 +1,50 @@
|
||||
import MenuBubble from '../Utils/MenuBubble'
|
||||
|
||||
export default {
|
||||
props: {
|
||||
editor: {
|
||||
default: null,
|
||||
type: Object,
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
menu: {
|
||||
isActive: false,
|
||||
left: 0,
|
||||
bottom: 0,
|
||||
},
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
editor: {
|
||||
immediate: true,
|
||||
handler(editor) {
|
||||
if (editor) {
|
||||
this.$nextTick(() => {
|
||||
editor.registerPlugin(MenuBubble({
|
||||
element: this.$el,
|
||||
onUpdate: menu => {
|
||||
this.menu = menu
|
||||
},
|
||||
}))
|
||||
})
|
||||
}
|
||||
},
|
||||
},
|
||||
},
|
||||
render() {
|
||||
if (!this.editor) {
|
||||
return null
|
||||
}
|
||||
props: {
|
||||
editor: {
|
||||
default: null,
|
||||
type: Object,
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
menu: {
|
||||
isActive: false,
|
||||
left: 0,
|
||||
bottom: 0,
|
||||
},
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
editor: {
|
||||
immediate: true,
|
||||
handler(editor) {
|
||||
if (editor) {
|
||||
this.$nextTick(() => {
|
||||
editor.registerPlugin(MenuBubble({
|
||||
element: this.$el,
|
||||
onUpdate: menu => {
|
||||
this.menu = menu
|
||||
},
|
||||
}))
|
||||
})
|
||||
}
|
||||
},
|
||||
},
|
||||
},
|
||||
render() {
|
||||
if (!this.editor) {
|
||||
return null
|
||||
}
|
||||
|
||||
return this.$scopedSlots.default({
|
||||
focused: this.editor.view.focused,
|
||||
focus: this.editor.focus,
|
||||
commands: this.editor.commands,
|
||||
isActive: this.editor.isActive.bind(this.editor),
|
||||
markAttrs: this.editor.markAttrs.bind(this.editor),
|
||||
menu: this.menu,
|
||||
})
|
||||
},
|
||||
return this.$scopedSlots.default({
|
||||
focused: this.editor.view.focused,
|
||||
focus: this.editor.focus,
|
||||
commands: this.editor.commands,
|
||||
isActive: this.editor.isActive.bind(this.editor),
|
||||
markAttrs: this.editor.markAttrs.bind(this.editor),
|
||||
menu: this.menu,
|
||||
})
|
||||
},
|
||||
}
|
||||
|
||||