Skip to content

Commit

Permalink
feat: 🚧 support text_align
Browse files Browse the repository at this point in the history
  • Loading branch information
Leecason committed Dec 3, 2019
1 parent c94f7e4 commit c47bec6
Show file tree
Hide file tree
Showing 8 changed files with 333 additions and 5 deletions.
45 changes: 44 additions & 1 deletion src/components/ElTiptap.vue
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,50 @@ export default {
ul,
ol {
padding-left: 24px;
counter-reset: none;
list-style-type: none;
margin-left: 24px;
}
li > p {
&::before {
content: counter(el-tiptap-counter) '.';
display: inline-block;
font-size: 1em;
line-height: 1em;
position: relative;
text-align: right;
top: 0;
left: -5px;
margin-left: -24px;
width: 20px;
}
}
ul li > p {
&::before {
content: '\2022';
}
}
ol {
counter-reset: el-tiptap-counter;
li > p::before {
counter-increment: el-tiptap-counter;
}
}
*[data-text-align=right] {
text-align: right!important;
}
*[data-text-align=center] {
text-align: center!important;
}
*[data-text-align=justify] {
text-align: justify!important;
}
}
}
Expand Down
35 changes: 35 additions & 0 deletions src/components/MenuBar/index.vue
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,33 @@
icon="strikethrough"
/>

<command-button
:command="editorContext.commands.align_left"
tooltip="Align left"
icon="align-left"
/>

<command-button
:is-active="isTextAlignActive('center')"
:command="editorContext.commands.align_center"
tooltip="Align center"
icon="align-center"
/>

<command-button
:is-active="isTextAlignActive('right')"
:command="editorContext.commands.align_right"
tooltip="Align right"
icon="align-right"
/>

<command-button
:is-active="isTextAlignActive('justify')"
:command="editorContext.commands.align_justify"
tooltip="Align justify"
icon="align-justify"
/>

<command-button
:is-active="editorContext.isActive.bullet_list()"
:command="editorContext.commands.bullet_list"
Expand Down Expand Up @@ -67,6 +94,8 @@

<script>
import { Editor, EditorMenuBar } from 'tiptap';
import { isTextAlignActive } from '@/extensions/text_align';
import HeadingDropdown from './HeadingDropdown.vue';
import CommandButton from './CommandButton.vue';
Expand All @@ -91,6 +120,12 @@ export default {
editor: this.editor,
};
},
methods: {
isTextAlignActive (align) {
return isTextAlignActive(this.editor.state, align);
},
},
};
</script>

Expand Down
39 changes: 39 additions & 0 deletions src/extensions/heading.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import { Heading as TiptapHeading } from 'tiptap-extensions';

import { ParagraphNodeSpec, getParagraphNodeAttrs, toParagraphDOM } from './paragraph';

function getAttrs (dom) {
const attrs = getParagraphNodeAttrs(dom);
const level = dom.nodeName.match(/[H|h](\d)/)[1];
attrs.level = level;
return attrs;
}

function toDOM (node) {
const dom = toParagraphDOM(node);
const level = node.attrs.level || 1;
dom[0] = `h${level}`;
return dom;
}

export default class Heading extends TiptapHeading {
get schema () {
return {
...ParagraphNodeSpec,
attrs: {
...ParagraphNodeSpec.attrs,
level: {
default: 1,
},
},
defining: true,
draggable: false,
parseDOM: this.options.levels
.map(level => ({
tag: `h${level}`,
getAttrs,
})),
toDOM,
};
}
}
7 changes: 7 additions & 0 deletions src/extensions/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
// nodes
export { default as Paragraph } from './paragraph';
export { default as Heading } from './heading';
export { default as ListItem } from './list_item';

// extensions
export { default as TextAlign } from './text_align';
49 changes: 49 additions & 0 deletions src/extensions/list_item.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import { ListItem as TiptapListItem } from 'tiptap-extensions';

import { ALIGN_PATTERN } from './text_align';

const ListItemNodeSpec = {
attrs: {
textAlign: { default: null },
},
content: 'paragraph block*',
defining: true,
draggable: false,
parseDOM: [{
tag: 'li',
getAttrs,
}],
toDOM,
};

function getAttrs (dom) {
const attrs = {};
const { textAlign } = dom.style;
let align = dom.getAttribute('data-text-align') || textAlign || '';
align = ALIGN_PATTERN.test(align) ? align : null;

if (align) {
attrs.textAlign = align;
}
return attrs;
}

function toDOM (node) {
const {
textAlign,
} = node.attrs;

const attrs = {};

if (textAlign && textAlign !== 'left') {
attrs['data-text-align'] = textAlign;
}

return ['li', attrs, 0];
}

export default class ListItem extends TiptapListItem {
get schema () {
return ListItemNodeSpec;
}
}
52 changes: 52 additions & 0 deletions src/extensions/paragraph.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import { Paragraph as TiptapParagraph } from 'tiptap';

import { ALIGN_PATTERN } from './text_align';

export const ParagraphNodeSpec = {
attrs: {
textAlign: { default: null },
},
content: 'inline*',
group: 'block',
parseDOM: [{
tag: 'p',
getAttrs,
}],
toDOM,
};

function getAttrs (dom) {
const {
textAlign,
} = dom.style;

let align = dom.getAttribute('align') || textAlign || '';
align = ALIGN_PATTERN.test(align) ? align : null;

return {
textAlign: align,
};
}

function toDOM (node) {
const {
textAlign,
} = node.attrs;

const attrs = {};

if (textAlign && textAlign !== 'left') {
attrs['data-text-align'] = textAlign;
}

return ['p', attrs, 0];
}

export default class Paragraph extends TiptapParagraph {
get schema () {
return ParagraphNodeSpec;
}
}

export const toParagraphDOM = toDOM;
export const getParagraphNodeAttrs = getAttrs;
97 changes: 97 additions & 0 deletions src/extensions/text_align.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
import { Extension } from 'tiptap';

const ALLOWED_NODE_TYPES = [
'paragraph',
'heading',
'list_item',
];

export const ALIGN_PATTERN = /(left|right|center|justify)/;

export default class TextAlign extends Extension {
get name () {
return 'text_align';
}

commands () {
return {
align_left: this.setTextAlign({ alignment: 'null' }),
align_center: this.setTextAlign({ alignment: 'center' }),
align_right: this.setTextAlign({ alignment: 'right' }),
align_justify: this.setTextAlign({ alignment: 'justify' }),
};
}

setTextAlign ({ alignment }) {
return () => (state, dispatch) => {
let { tr } = state;
const { selection, doc } = tr;

if (!selection || !doc) {
return tr;
}

const { from, to } = selection;

const jobs = [];
alignment = alignment || null;

doc.nodesBetween(from, to, (node, pos) => {
const nodeType = node.type;
const align = node.attrs.align || null;
if (align !== alignment && ALLOWED_NODE_TYPES.includes(nodeType.name)) {
jobs.push({
node,
pos,
nodeType,
});
}
return true;
});

if (!jobs.length) return tr;

jobs.forEach(job => {
const { node, pos, nodeType } = job;
let { attrs } = node;
if (alignment) {
attrs = {
...attrs,
textAlign: alignment,
};
} else {
attrs = {
...attrs,
textAlign: null,
};
}
tr = tr.setNodeMarkup(pos, nodeType, attrs, node.marks);
});

if (tr.docChanged) {
dispatch && dispatch(tr);
return true;
}

return false;
};
}
}

export function isTextAlignActive (state, align) {
const { selection, doc } = state;
const { from, to } = selection;

let keepLooking = true;
let active = false;

doc.nodesBetween(from, to, (node, _pos) => {
if (keepLooking && node.attrs.textAlign === align) {
keepLooking = false;
active = true;
}
return keepLooking;
});

return active;
}
14 changes: 10 additions & 4 deletions src/views/Index.vue
Original file line number Diff line number Diff line change
Expand Up @@ -13,24 +13,28 @@
import {
Doc,
Text,
Paragraph,
} from 'tiptap';
import {
Heading,
Bold,
Underline,
Italic,
Strike,
ListItem,
BulletList,
OrderedList,
History,
} from 'tiptap-extensions';
import {
Paragraph,
Heading,
ListItem,
TextAlign,
} from '@/extensions';
import ElTiptap from '../components/ElTiptap';
export default {
Expand All @@ -47,6 +51,8 @@ export default {
new Text(),
new Paragraph(),
new TextAlign(),
new Heading({ level: 5 }),
new Bold(),
Expand Down

0 comments on commit c47bec6

Please sign in to comment.