docs: demos styling

This commit is contained in:
Sven Adlung 2024-06-12 11:52:54 +02:00 committed by GitHub
parent 171924ee49
commit aba6657d70
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
309 changed files with 11469 additions and 5716 deletions

View File

@ -1,4 +1,23 @@
$colorBlack: #000; /* Base HTML and global element styles*/
:root {
--white: #FFF;
--black: #2E2B29;
--black-contrast: #110F0E;
--gray-1: rgba(61, 37, 20, 0.05);
--gray-2: rgba(61, 37, 20, 0.08);
--gray-3: rgba(61, 37, 20, 0.12);
--gray-4: rgba(53, 38, 28, 0.30);
--gray-5: rgba(28, 25, 23, 0.60);
--purple: #6A00F5;
--purple-contrast: #5800CC;
--purple-light: rgba(88, 5, 255, 0.05);
--yellow-contrast: #FACC15;
--yellow: rgba(250, 204, 21, 0.4);
--yellow-light: #FFFAE5;
--red: #FF5C33;
--red-light: #FFEBE5;
--shadow: 0px 12px 33px 0px rgba(0, 0, 0, 0.06), 0px 3.618px 9.949px 0px rgba(0, 0, 0, 0.04);
}
*, *,
*::before, *::before,
@ -9,32 +28,46 @@ $colorBlack: #000;
html { html {
font-family: Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans", sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji"; font-family: Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans", sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";
line-height: 1.5; line-height: 1.5;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale; -moz-osx-font-smoothing: grayscale;
-webkit-font-smoothing: antialiased;
} }
body { body {
margin: 1rem;
min-height: 10rem; min-height: 10rem;
margin: 0;
} }
:first-child {
margin-top: 0;
}
.tiptap {
caret-color: var(--purple);
margin: 1.5rem;
&:focus {
outline: none;
}
}
/* Custom scrollbar styles */
::-webkit-scrollbar { ::-webkit-scrollbar {
width: 14px;
height: 14px; height: 14px;
width: 14px;
} }
::-webkit-scrollbar-track { ::-webkit-scrollbar-track {
border: 4px solid transparent;
background-clip: padding-box; background-clip: padding-box;
border-radius: 8px;
background-color: transparent; background-color: transparent;
border: 4px solid transparent;
border-radius: 8px;
} }
::-webkit-scrollbar-thumb { ::-webkit-scrollbar-thumb {
border: 4px solid rgba(0, 0, 0, 0);
background-clip: padding-box; background-clip: padding-box;
border-radius: 8px;
background-color: rgba(0, 0, 0, 0); background-color: rgba(0, 0, 0, 0);
border: 4px solid rgba(0, 0, 0, 0);
border-radius: 8px;
} }
:hover::-webkit-scrollbar-thumb { :hover::-webkit-scrollbar-thumb {
@ -47,37 +80,276 @@ body {
::-webkit-scrollbar-button { ::-webkit-scrollbar-button {
display: none; display: none;
width: 0;
height: 0; height: 0;
width: 0;
} }
::-webkit-scrollbar-corner { ::-webkit-scrollbar-corner {
background-color: transparent; background-color: transparent;
} }
/* Specific element and component styles */
button, button,
input, input,
select { select,
font-size: inherit; textarea {
background: var(--gray-2);
border-radius: 0.5rem;
border: none;
color: var(--black);
font-family: inherit; font-family: inherit;
color: black; font-size: 0.875rem;
margin: 0.1rem; font-weight: 500;
border: 1px solid black; line-height: 1.15;
border-radius: 0.3rem; margin: none;
padding: 0.1rem 0.4rem; padding: 0.375rem 0.625rem;
background: white; transition: all 0.2s cubic-bezier(0.65,0.05,0.36,1);
accent-color: black;
&:hover {
background-color: var(--gray-3);
color: var(--black-contrast);
}
&[disabled] { &[disabled] {
opacity: 0.3; background: var(--gray-1);
color: var(--gray-4);
}
&:checked {
accent-color: var(--purple);
}
&.primary {
background: var(--black);
color: var(--white);
&:hover {
background-color: var(--black-contrast);
}
&[disabled] {
background: var(--gray-1);
color: var(--gray-4);
}
}
&.is-active {
background: var(--purple);
color: var(--white);
&:hover {
background-color: var(--purple-contrast);
color: var(--white);
}
} }
} }
.tiptap:focus { button:not([disabled]),
outline: none; select:not([disabled]) {
cursor: pointer;
} }
.is-active { input[type="text"],
background: black; textarea {
color: white; background-color: unset;
border: 1px solid var(--gray-3);
border-radius: 0.5rem;
color: var(--black);
&::placeholder {
color: var(--gray-4);
}
&:hover {
background-color: unset;
border-color: var(--gray-4);
}
&:focus-visible, &:focus {
border-color: var(--purple);
outline: none;
}
}
select {
/* reset */
appearance: none;
-webkit-appearance: none;
-moz-appearance: none;
background-image: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path fill="Gray" d="M7 10l5 5 5-5z"/></svg>');
background-repeat: no-repeat;
background-position: right 0.1rem center;
background-size: 1.25rem 1.25rem;
padding-right: 1.25rem;
&:focus {
outline: 0;
}
}
form {
align-items: flex-start;
display: flex;
flex-direction: column;
gap: 0.25rem;
}
.hint {
align-items: center;
background-color: var(--yellow-light);
border-radius: 0.5rem;
border: 1px solid var(--gray-2);
display: flex;
flex-direction: row;
font-size: 0.75rem;
gap: 0.5rem;
line-height: 1.15;
min-height: 1.75rem;
padding: 0.25rem 0.5rem;
&.purple-spinner,
&.error {
justify-content: center;
text-align: center;
width: 100%;
}
&.purple-spinner {
background-color: var(--purple-light);
&::after {
content: "";
background-image: url("data:image/svg+xml;utf8,<svg version='1.1' id='Layer_1' xmlns='http://www.w3.org/2000/svg' xmlns:xlink='http://www.w3.org/1999/xlink' x='0px' y='0px' width='28px' height='30px' viewBox='0 0 24 30' style='enable-background:new 0 0 50 50;' xml:space='preserve'><rect x='0' y='10' width='6' height='10' fill='%236A00F5' rx='3' ry='3'><animateTransform attributeType='xml' attributeName='transform' type='translate' values='0 0; 0 5; 0 -5; 0 0' begin='0' dur='0.6s' repeatCount='indefinite'/></rect><rect x='10' y='10' width='6' height='10' fill='%236A00F5' rx='3' ry='3'><animateTransform attributeType='xml' attributeName='transform' type='translate' values='0 0; 0 5; 0 -5; 0 0' begin='0.2s' dur='0.6s' repeatCount='indefinite'/></rect><rect x='20' y='10' width='6' height='10' fill='%236A00F5' rx='3' ry='3'><animateTransform attributeType='xml' attributeName='transform' type='translate' values='0 0; 0 5; 0 -5; 0 0' begin='0.4s' dur='0.6s' repeatCount='indefinite'/></rect></svg>");
background-size: cover;
background-repeat: no-repeat;
background-position: center;
height: 1rem;
width: 1rem;
}
}
&.error {
background-color: var(--red-light);
}
}
.label,
.label-small,
.label-large {
color: var(--black);
font-size: 0.8125rem;
font-weight: 500;
line-height: 1.15;
}
.label-small {
color: var(--gray-5);
font-size: 0.75rem;
font-weight: 400;
}
.label-large {
font-size: 0.875rem;
font-weight: 700;
}
hr {
border: none;
border-top: 1px solid var(--gray-3);
margin: 0;
width: 100%;
}
/* Layout and structure */
#app,
.container {
display: flex;
flex-direction: column;
}
.button-group {
display: flex;
flex-wrap: wrap;
gap: 0.25rem;
}
.control-group {
align-items: flex-start;
display: flex;
flex-direction: column;
gap: 1rem;
margin: 1.5rem;
}
[data-node-view-wrapper] > .control-group {
margin: 1.5rem 0;
}
.flex-row {
display: flex;
flex-direction: row;
flex-wrap: wrap;
gap: 1rem;
justify-content: space-between;
width: 100%;
}
.switch-group {
align-items: center;
background: var(--gray-2);
border-radius: 0.5rem;
display: flex;
flex-direction: row;
flex-wrap: wrap;
flex: 0 1 auto;
justify-content: flex-start;
padding: 0.125rem;
label {
align-items: center;
border-radius: 0.375rem;
color: var(--gray-5);
cursor: pointer;
display: flex;
flex-direction: row;
font-size: 0.75rem;
font-weight: 500;
gap: 0.25rem;
line-height: 1.15;
min-height: 1.5rem;
padding: 0 0.375rem;
transition: all 0.2s cubic-bezier(0.65,0.05,0.36,1);
&:has(input:checked) {
background-color: var(--white);
color: var(--black-contrast);
}
&:hover {
color: var(--black);
}
input {
display: none;
margin: unset;
}
}
}
.output-group {
background-color: var(--gray-1);
display: flex;
flex-direction: column;
font-family: 'JetBrainsMono', monospace;
font-size: 0.75rem;
gap: 1rem;
margin-top: 2.5rem;
padding: 1.5rem;
label {
color: var(--black);
font-size: 0.875rem;
font-weight: 700;
line-height: 1.15;
}
} }

View File

@ -21,10 +21,12 @@ const MenuBar = ({ editor }) => {
}, [editor]) }, [editor])
return ( return (
<> <div className="control-group">
<div className="button-group">
<button onClick={onCutToStart}>Cut content to start of document</button> <button onClick={onCutToStart}>Cut content to start of document</button>
<button onClick={onCutToEnd}>Cut content to end of document</button> <button onClick={onCutToEnd}>Cut content to end of document</button>
</> </div>
</div>
) )
} }
@ -49,7 +51,7 @@ export default () => {
Hi there, Hi there,
</h2> </h2>
<p> <p>
this is a <em>basic</em> example of <strong>tiptap</strong>. Sure, there are all kind of basic text styles youd probably expect from a text editor. But wait until you see the lists: this is a <em>basic</em> example of <strong>Tiptap</strong>. Sure, there are all kind of basic text styles youd probably expect from a text editor. But wait until you see the lists:
</p> </p>
<ul> <ul>
<li> <li>
@ -77,9 +79,9 @@ export default () => {
}) })
return ( return (
<div> <>
<MenuBar editor={editor} /> <MenuBar editor={editor} />
<EditorContent editor={editor} /> <EditorContent editor={editor} />
</div> </>
) )
} }

View File

@ -21,41 +21,41 @@ context('/src/Examples/Default/React/', () => {
}) })
const buttonMarks = [ const buttonMarks = [
{ label: 'bold', tag: 'strong' }, { label: 'Bold', tag: 'strong' },
{ label: 'italic', tag: 'em' }, { label: 'Italic', tag: 'em' },
{ label: 'strike', tag: 's' }, { label: 'Strike', tag: 's' },
] ]
buttonMarks.forEach(m => { buttonMarks.forEach(m => {
it(`should disable ${m.label} when the code tag is enabled for cursor`, () => { it(`should disable ${m.label} when the code tag is enabled for cursor`, () => {
cy.get('.tiptap').type('{selectall}Hello world') cy.get('.tiptap').type('{selectall}Hello world')
cy.get('button').contains('code').click() cy.get('button').contains('Code').click()
cy.get('button').contains(m.label).should('be.disabled') cy.get('button').contains(m.label).should('be.disabled')
}) })
it(`should enable ${m.label} when the code tag is disabled for cursor`, () => { it(`should enable ${m.label} when the code tag is disabled for cursor`, () => {
cy.get('.tiptap').type('{selectall}Hello world') cy.get('.tiptap').type('{selectall}Hello world')
cy.get('button').contains('code').click() cy.get('button').contains('Code').click()
cy.get('button').contains('code').click() cy.get('button').contains('Code').click()
cy.get('button').contains(m.label).should('not.be.disabled') cy.get('button').contains(m.label).should('not.be.disabled')
}) })
it(`should disable ${m.label} when the code tag is enabled for selection`, () => { it(`should disable ${m.label} when the code tag is enabled for selection`, () => {
cy.get('.tiptap').type('{selectall}Hello world{selectall}') cy.get('.tiptap').type('{selectall}Hello world{selectall}')
cy.get('button').contains('code').click() cy.get('button').contains('Code').click()
cy.get('button').contains(m.label).should('be.disabled') cy.get('button').contains(m.label).should('be.disabled')
}) })
it(`should enable ${m.label} when the code tag is disabled for selection`, () => { it(`should enable ${m.label} when the code tag is disabled for selection`, () => {
cy.get('.tiptap').type('{selectall}Hello world{selectall}') cy.get('.tiptap').type('{selectall}Hello world{selectall}')
cy.get('button').contains('code').click() cy.get('button').contains('Code').click()
cy.get('button').contains('code').click() cy.get('button').contains('Code').click()
cy.get('button').contains(m.label).should('not.be.disabled') cy.get('button').contains(m.label).should('not.be.disabled')
}) })
it(`should apply ${m.label} when the button is pressed`, () => { it(`should apply ${m.label} when the button is pressed`, () => {
cy.get('.tiptap').type('{selectall}Hello world') cy.get('.tiptap').type('{selectall}Hello world')
cy.get('button').contains('paragraph').click() cy.get('button').contains('Paragraph').click()
cy.get('.tiptap').type('{selectall}') cy.get('.tiptap').type('{selectall}')
cy.get('button').contains(m.label).click() cy.get('button').contains(m.label).click()
cy.get(`.tiptap ${m.tag}`).should('exist').should('have.text', 'Hello world') cy.get(`.tiptap ${m.tag}`).should('exist').should('have.text', 'Hello world')
@ -64,40 +64,40 @@ context('/src/Examples/Default/React/', () => {
it('should clear marks when the button is pressed', () => { it('should clear marks when the button is pressed', () => {
cy.get('.tiptap').type('{selectall}Hello world') cy.get('.tiptap').type('{selectall}Hello world')
cy.get('button').contains('paragraph').click() cy.get('button').contains('Paragraph').click()
cy.get('.tiptap').type('{selectall}') cy.get('.tiptap').type('{selectall}')
cy.get('button').contains('bold').click() cy.get('button').contains('Bold').click()
cy.get('.tiptap strong').should('exist').should('have.text', 'Hello world') cy.get('.tiptap strong').should('exist').should('have.text', 'Hello world')
cy.get('button').contains('clear marks').click() cy.get('button').contains('Clear marks').click()
cy.get('.tiptap strong').should('not.exist') cy.get('.tiptap strong').should('not.exist')
}) })
it('should clear nodes when the button is pressed', () => { it('should clear nodes when the button is pressed', () => {
cy.get('.tiptap').type('{selectall}Hello world') cy.get('.tiptap').type('{selectall}Hello world')
cy.get('button').contains('bullet list').click() cy.get('button').contains('Bullet list').click()
cy.get('.tiptap ul').should('exist').should('have.text', 'Hello world') cy.get('.tiptap ul').should('exist').should('have.text', 'Hello world')
cy.get('.tiptap').type('{enter}A second item{enter}A third item{selectall}') cy.get('.tiptap').type('{enter}A second item{enter}A third item{selectall}')
cy.get('button').contains('clear nodes').click() cy.get('button').contains('Clear nodes').click()
cy.get('.tiptap ul').should('not.exist') cy.get('.tiptap ul').should('not.exist')
cy.get('.tiptap p').should('have.length', 3) cy.get('.tiptap p').should('have.length', 3)
}) })
const buttonNodes = [ const buttonNodes = [
{ label: 'h1', tag: 'h1' }, { label: 'H1', tag: 'h1' },
{ label: 'h2', tag: 'h2' }, { label: 'H2', tag: 'h2' },
{ label: 'h3', tag: 'h3' }, { label: 'H3', tag: 'h3' },
{ label: 'h4', tag: 'h4' }, { label: 'H4', tag: 'h4' },
{ label: 'h5', tag: 'h5' }, { label: 'H5', tag: 'h5' },
{ label: 'h6', tag: 'h6' }, { label: 'H6', tag: 'h6' },
{ label: 'bullet list', tag: 'ul' }, { label: 'Bullet list', tag: 'ul' },
{ label: 'ordered list', tag: 'ol' }, { label: 'Ordered list', tag: 'ol' },
{ label: 'code block', tag: 'pre code' }, { label: 'Code block', tag: 'pre code' },
{ label: 'blockquote', tag: 'blockquote' }, { label: 'Blockquote', tag: 'blockquote' },
] ]
buttonNodes.forEach(n => { buttonNodes.forEach(n => {
it(`should set ${n.label} when the button is pressed`, () => { it(`should set ${n.label} when the button is pressed`, () => {
cy.get('button').contains('paragraph').click() cy.get('button').contains('Paragraph').click()
cy.get('.tiptap').type('{selectall}Hello world{selectall}') cy.get('.tiptap').type('{selectall}Hello world{selectall}')
cy.get('button').contains(n.label).click() cy.get('button').contains(n.label).click()
@ -109,35 +109,35 @@ context('/src/Examples/Default/React/', () => {
it('should add a hr when on the same line as a node', () => { it('should add a hr when on the same line as a node', () => {
cy.get('.tiptap').type('{rightArrow}') cy.get('.tiptap').type('{rightArrow}')
cy.get('button').contains('horizontal rule').click() cy.get('button').contains('Horizontal rule').click()
cy.get('.tiptap hr').should('exist') cy.get('.tiptap hr').should('exist')
cy.get('.tiptap h1').should('exist') cy.get('.tiptap h1').should('exist')
}) })
it('should add a hr when on a new line', () => { it('should add a hr when on a new line', () => {
cy.get('.tiptap').type('{rightArrow}{enter}') cy.get('.tiptap').type('{rightArrow}{enter}')
cy.get('button').contains('horizontal rule').click() cy.get('button').contains('Horizontal rule').click()
cy.get('.tiptap hr').should('exist') cy.get('.tiptap hr').should('exist')
cy.get('.tiptap h1').should('exist') cy.get('.tiptap h1').should('exist')
}) })
it('should add a br', () => { it('should add a br', () => {
cy.get('.tiptap').type('{rightArrow}') cy.get('.tiptap').type('{rightArrow}')
cy.get('button').contains('hard break').click() cy.get('button').contains('Hard break').click()
cy.get('.tiptap h1 br').should('exist') cy.get('.tiptap h1 br').should('exist')
}) })
it('should undo', () => { it('should undo', () => {
cy.get('.tiptap').type('{selectall}{backspace}') cy.get('.tiptap').type('{selectall}{backspace}')
cy.get('button').contains('undo').click() cy.get('button').contains('Undo').click()
cy.get('.tiptap').should('contain', 'Hello world') cy.get('.tiptap').should('contain', 'Hello world')
}) })
it('should redo', () => { it('should redo', () => {
cy.get('.tiptap').type('{selectall}{backspace}') cy.get('.tiptap').type('{selectall}{backspace}')
cy.get('button').contains('undo').click() cy.get('button').contains('Undo').click()
cy.get('.tiptap').should('contain', 'Hello world') cy.get('.tiptap').should('contain', 'Hello world')
cy.get('button').contains('redo').click() cy.get('button').contains('Redo').click()
cy.get('.tiptap').should('not.contain', 'Hello world') cy.get('.tiptap').should('not.contain', 'Hello world')
}) })
}) })

View File

@ -1,14 +1,22 @@
/* Basic editor styles */ /* Basic editor styles */
.tiptap { .tiptap {
> * + * { :first-child {
margin-top: 0.75em; margin-top: 0;
} }
/* List styles */
ul, ul,
ol { ol {
padding: 0 1rem; padding: 0 1rem;
margin: 1.25rem 1rem 1.25rem 0.4rem;
li p {
margin-top: 0.25em;
margin-bottom: 0.25em;
}
} }
/* Heading styles */
h1, h1,
h2, h2,
h3, h3,
@ -16,41 +24,68 @@
h5, h5,
h6 { h6 {
line-height: 1.1; line-height: 1.1;
margin-top: 2.5rem;
text-wrap: pretty;
} }
h1,
h2 {
margin-top: 3.5rem;
margin-bottom: 1.5rem;
}
h1 {
font-size: 1.4rem;
}
h2 {
font-size: 1.2rem;
}
h3 {
font-size: 1.1rem;
}
h4,
h5,
h6 {
font-size: 1rem;
}
/* Code and preformatted text styles */
code { code {
background-color: rgba(#616161, 0.1); background-color: var(--purple-light);
color: #616161; border-radius: 0.4rem;
color: var(--black);
font-size: 0.85rem;
padding: 0.25em 0.3em;
} }
pre { pre {
background: #0D0D0D; background: var(--black);
color: #FFF;
font-family: 'JetBrainsMono', monospace;
padding: 0.75rem 1rem;
border-radius: 0.5rem; border-radius: 0.5rem;
color: var(--white);
font-family: 'JetBrainsMono', monospace;
margin: 1.5rem 0;
padding: 0.75rem 1rem;
code { code {
color: inherit;
padding: 0;
background: none; background: none;
color: inherit;
font-size: 0.8rem; font-size: 0.8rem;
padding: 0;
} }
} }
img {
max-width: 100%;
height: auto;
}
blockquote { blockquote {
border-left: 3px solid var(--gray-3);
margin: 1.5rem 0;
padding-left: 1rem; padding-left: 1rem;
border-left: 2px solid rgba(#0D0D0D, 0.1);
} }
hr { hr {
border: none; border: none;
border-top: 2px solid rgba(#0D0D0D, 0.1); border-top: 1px solid var(--gray-2);
margin: 2rem 0; margin: 2rem 0;
} }
} }

View File

@ -29,17 +29,19 @@ const MenuBar = () => {
} }
return ( return (
<> <div className="control-group">
<div className="button-group">
<button data-test-id="html-content" onClick={() => editor.chain().insertContent(htmlContent).focus().run()}> <button data-test-id="html-content" onClick={() => editor.chain().insertContent(htmlContent).focus().run()}>
Insert html content Insert HTML content
</button> </button>
<button data-test-id="html-content-spans" onClick={() => editor.chain().insertContent('<p><b>Hello</b> <i>World</i></p>').focus().run()}> <button data-test-id="html-content-spans" onClick={() => editor.chain().insertContent('<p><b>Hello</b> <i>World</i></p>').focus().run()}>
Insert html with span tags content Insert HTML with span tags content
</button> </button>
<button data-test-id="text-content" onClick={() => editor.chain().insertContent(textContent).focus().run()}> <button data-test-id="text-content" onClick={() => editor.chain().insertContent(textContent).focus().run()}>
Insert text content Insert text content
</button> </button>
</> </div>
</div>
) )
} }

View File

@ -1,14 +1,22 @@
/* Basic editor styles */ /* Basic editor styles */
.tiptap { .tiptap {
> * + * { :first-child {
margin-top: 0.75em; margin-top: 0;
} }
/* List styles */
ul, ul,
ol { ol {
padding: 0 1rem; padding: 0 1rem;
margin: 1.25rem 1rem 1.25rem 0.4rem;
li p {
margin-top: 0.25em;
margin-bottom: 0.25em;
}
} }
/* Heading styles */
h1, h1,
h2, h2,
h3, h3,
@ -16,41 +24,68 @@
h5, h5,
h6 { h6 {
line-height: 1.1; line-height: 1.1;
margin-top: 2.5rem;
text-wrap: pretty;
} }
h1,
h2 {
margin-top: 3.5rem;
margin-bottom: 1.5rem;
}
h1 {
font-size: 1.4rem;
}
h2 {
font-size: 1.2rem;
}
h3 {
font-size: 1.1rem;
}
h4,
h5,
h6 {
font-size: 1rem;
}
/* Code and preformatted text styles */
code { code {
background-color: rgba(#616161, 0.1); background-color: var(--purple-light);
color: #616161; border-radius: 0.4rem;
color: var(--black);
font-size: 0.85rem;
padding: 0.25em 0.3em;
} }
pre { pre {
background: #0D0D0D; background: var(--black);
color: #FFF;
font-family: 'JetBrainsMono', monospace;
padding: 0.75rem 1rem;
border-radius: 0.5rem; border-radius: 0.5rem;
color: var(--white);
font-family: 'JetBrainsMono', monospace;
margin: 1.5rem 0;
padding: 0.75rem 1rem;
code { code {
color: inherit;
padding: 0;
background: none; background: none;
color: inherit;
font-size: 0.8rem; font-size: 0.8rem;
padding: 0;
} }
} }
img {
max-width: 100%;
height: auto;
}
blockquote { blockquote {
border-left: 3px solid var(--gray-3);
margin: 1.5rem 0;
padding-left: 1rem; padding-left: 1rem;
border-left: 2px solid rgba(#0D0D0D, 0.1);
} }
hr { hr {
border: none; border: none;
border-top: 2px solid rgba(#0D0D0D, 0.1); border-top: 1px solid var(--gray-2);
margin: 2rem 0; margin: 2rem 0;
} }
} }

View File

@ -14,30 +14,27 @@ const MenuBar = () => {
} }
return ( return (
<> <div className="control-group">
<div> <div>
<button <label>
onClick={() => setUseInputRules(prev => !prev)} <input
style={{ type="checkbox"
color: useInputRules ? 'white' : 'inherit', checked={useInputRules}
backgroundColor: useInputRules ? 'black' : 'transparent', onChange={() => setUseInputRules(prev => !prev)}
}} />
> Apply input rules
Apply Input Rules </label>
</button> <label>
<button <input
onClick={() => setUsePasteRules(prev => !prev)} type="checkbox"
style={{ checked={usePasteRules}
color: usePasteRules ? 'white' : 'inherit', onChange={() => setUsePasteRules(prev => !prev)}
backgroundColor: usePasteRules ? 'black' : 'transparent', />
}} Apply paste rules
> </label>
Apply Paste Rules
</button>
</div> </div>
<br /> <div className="button-group">
<button <button
onClick={() => { onClick={() => {
editor editor
@ -161,7 +158,8 @@ const MenuBar = () => {
> >
Insert "*this is a test*" Insert "*this is a test*"
</button> </button>
</> </div>
</div>
) )
} }

View File

@ -1,14 +1,22 @@
/* Basic editor styles */ /* Basic editor styles */
.tiptap { .tiptap {
> * + * { :first-child {
margin-top: 0.75em; margin-top: 0;
} }
/* List styles */
ul, ul,
ol { ol {
padding: 0 1rem; padding: 0 1rem;
margin: 1.25rem 1rem 1.25rem 0.4rem;
li p {
margin-top: 0.25em;
margin-bottom: 0.25em;
}
} }
/* Heading styles */
h1, h1,
h2, h2,
h3, h3,
@ -16,41 +24,68 @@
h5, h5,
h6 { h6 {
line-height: 1.1; line-height: 1.1;
margin-top: 2.5rem;
text-wrap: pretty;
} }
h1,
h2 {
margin-top: 3.5rem;
margin-bottom: 1.5rem;
}
h1 {
font-size: 1.4rem;
}
h2 {
font-size: 1.2rem;
}
h3 {
font-size: 1.1rem;
}
h4,
h5,
h6 {
font-size: 1rem;
}
/* Code and preformatted text styles */
code { code {
background-color: rgba(#616161, 0.1); background-color: var(--purple-light);
color: #616161; border-radius: 0.4rem;
color: var(--black);
font-size: 0.85rem;
padding: 0.25em 0.3em;
} }
pre { pre {
background: #0D0D0D; background: var(--black);
color: #FFF;
font-family: 'JetBrainsMono', monospace;
padding: 0.75rem 1rem;
border-radius: 0.5rem; border-radius: 0.5rem;
color: var(--white);
font-family: 'JetBrainsMono', monospace;
margin: 1.5rem 0;
padding: 0.75rem 1rem;
code { code {
color: inherit;
padding: 0;
background: none; background: none;
color: inherit;
font-size: 0.8rem; font-size: 0.8rem;
padding: 0;
} }
} }
img {
max-width: 100%;
height: auto;
}
blockquote { blockquote {
border-left: 3px solid var(--gray-3);
margin: 1.5rem 0;
padding-left: 1rem; padding-left: 1rem;
border-left: 2px solid rgba(#0D0D0D, 0.1);
} }
hr { hr {
border: none; border: none;
border-top: 2px solid rgba(#0D0D0D, 0.1); border-top: 1px solid var(--gray-2);
margin: 2rem 0; margin: 2rem 0;
} }
} }

View File

@ -50,22 +50,26 @@ export default () => {
} }
return ( return (
<div> <>
<div className="control-group">
<div className="button-group">
<button <button
onClick={setLink} onClick={setLink}
className={editor.isActive('link') ? 'is-active' : ''} className={editor.isActive('link') ? 'is-active' : ''}
data-testid="setLink" data-testid="setLink"
> >
setLink Set link
</button> </button>
<button <button
onClick={() => editor.chain().focus().unsetLink().run()} onClick={() => editor.chain().focus().unsetLink().run()}
disabled={!editor.isActive('link')} disabled={!editor.isActive('link')}
data-testid="unsetLink" data-testid="unsetLink"
> >
unsetLink Unset link
</button> </button>
<EditorContent editor={editor} />
</div> </div>
</div>
<EditorContent editor={editor} />
</>
) )
} }

View File

@ -1,14 +1,22 @@
/* Basic editor styles */ /* Basic editor styles */
.tiptap { .tiptap {
> * + * { :first-child {
margin-top: 0.75em; margin-top: 0;
} }
/* List styles */
ul, ul,
ol { ol {
padding: 0 1rem; padding: 0 1rem;
margin: 1.25rem 1rem 1.25rem 0.4rem;
li p {
margin-top: 0.25em;
margin-bottom: 0.25em;
}
} }
/* Heading styles */
h1, h1,
h2, h2,
h3, h3,
@ -16,39 +24,78 @@
h5, h5,
h6 { h6 {
line-height: 1.1; line-height: 1.1;
margin-top: 2.5rem;
text-wrap: pretty;
} }
h1,
h2 {
margin-top: 3.5rem;
margin-bottom: 1.5rem;
}
h1 {
font-size: 1.4rem;
}
h2 {
font-size: 1.2rem;
}
h3 {
font-size: 1.1rem;
}
h4,
h5,
h6 {
font-size: 1rem;
}
/* Code and preformatted text styles */
code { code {
background-color: rgba(#616161, 0.1); background-color: var(--purple-light);
color: #616161; border-radius: 0.4rem;
color: var(--black);
font-size: 0.85rem;
padding: 0.25em 0.3em;
} }
pre { pre {
background: #0D0D0D; background: var(--black);
color: #FFF;
font-family: 'JetBrainsMono', monospace;
padding: 0.75rem 1rem;
border-radius: 0.5rem; border-radius: 0.5rem;
color: var(--white);
font-family: 'JetBrainsMono', monospace;
margin: 1.5rem 0;
padding: 0.75rem 1rem;
code { code {
color: inherit;
padding: 0;
background: none; background: none;
color: inherit;
font-size: 0.8rem; font-size: 0.8rem;
padding: 0;
} }
} }
img {
max-width: 100%;
height: auto;
}
hr {
margin: 1rem 0;
}
blockquote { blockquote {
border-left: 3px solid var(--gray-3);
margin: 1.5rem 0;
padding-left: 1rem; padding-left: 1rem;
border-left: 2px solid rgba(#0D0D0D, 0.1); }
hr {
border: none;
border-top: 1px solid var(--gray-2);
margin: 2rem 0;
}
/* Link styles */
a {
color: var(--purple);
cursor: pointer;
&:hover {
color: var(--purple-contrast);
}
} }
} }

View File

@ -12,116 +12,118 @@ const MenuBar = ({ editor }) => {
} }
return ( return (
<> <div className="control-group">
<div className="button-group">
<button <button
onClick={() => editor.chain().focus().toggleBold().run()} onClick={() => editor.chain().focus().toggleBold().run()}
className={editor.isActive('bold') ? 'is-active' : ''} className={editor.isActive('bold') ? 'is-active' : ''}
> >
bold Bold
</button> </button>
<button <button
onClick={() => editor.chain().focus().toggleItalic().run()} onClick={() => editor.chain().focus().toggleItalic().run()}
className={editor.isActive('italic') ? 'is-active' : ''} className={editor.isActive('italic') ? 'is-active' : ''}
> >
italic Italic
</button> </button>
<button <button
onClick={() => editor.chain().focus().toggleStrike().run()} onClick={() => editor.chain().focus().toggleStrike().run()}
className={editor.isActive('strike') ? 'is-active' : ''} className={editor.isActive('strike') ? 'is-active' : ''}
> >
strike Strike
</button> </button>
<button <button
onClick={() => editor.chain().focus().toggleCode().run()} onClick={() => editor.chain().focus().toggleCode().run()}
className={editor.isActive('code') ? 'is-active' : ''} className={editor.isActive('code') ? 'is-active' : ''}
> >
code Code
</button> </button>
<button onClick={() => editor.chain().focus().unsetAllMarks().run()}> <button onClick={() => editor.chain().focus().unsetAllMarks().run()}>
clear marks Clear marks
</button> </button>
<button onClick={() => editor.chain().focus().clearNodes().run()}> <button onClick={() => editor.chain().focus().clearNodes().run()}>
clear nodes Clear nodes
</button> </button>
<button <button
onClick={() => editor.chain().focus().setParagraph().run()} onClick={() => editor.chain().focus().setParagraph().run()}
className={editor.isActive('paragraph') ? 'is-active' : ''} className={editor.isActive('paragraph') ? 'is-active' : ''}
> >
paragraph Paragraph
</button> </button>
<button <button
onClick={() => editor.chain().focus().toggleHeading({ level: 1 }).run()} onClick={() => editor.chain().focus().toggleHeading({ level: 1 }).run()}
className={editor.isActive('heading', { level: 1 }) ? 'is-active' : ''} className={editor.isActive('heading', { level: 1 }) ? 'is-active' : ''}
> >
h1 H1
</button> </button>
<button <button
onClick={() => editor.chain().focus().toggleHeading({ level: 2 }).run()} onClick={() => editor.chain().focus().toggleHeading({ level: 2 }).run()}
className={editor.isActive('heading', { level: 2 }) ? 'is-active' : ''} className={editor.isActive('heading', { level: 2 }) ? 'is-active' : ''}
> >
h2 H2
</button> </button>
<button <button
onClick={() => editor.chain().focus().toggleHeading({ level: 3 }).run()} onClick={() => editor.chain().focus().toggleHeading({ level: 3 }).run()}
className={editor.isActive('heading', { level: 3 }) ? 'is-active' : ''} className={editor.isActive('heading', { level: 3 }) ? 'is-active' : ''}
> >
h3 H3
</button> </button>
<button <button
onClick={() => editor.chain().focus().toggleHeading({ level: 4 }).run()} onClick={() => editor.chain().focus().toggleHeading({ level: 4 }).run()}
className={editor.isActive('heading', { level: 4 }) ? 'is-active' : ''} className={editor.isActive('heading', { level: 4 }) ? 'is-active' : ''}
> >
h4 H4
</button> </button>
<button <button
onClick={() => editor.chain().focus().toggleHeading({ level: 5 }).run()} onClick={() => editor.chain().focus().toggleHeading({ level: 5 }).run()}
className={editor.isActive('heading', { level: 5 }) ? 'is-active' : ''} className={editor.isActive('heading', { level: 5 }) ? 'is-active' : ''}
> >
h5 H5
</button> </button>
<button <button
onClick={() => editor.chain().focus().toggleHeading({ level: 6 }).run()} onClick={() => editor.chain().focus().toggleHeading({ level: 6 }).run()}
className={editor.isActive('heading', { level: 6 }) ? 'is-active' : ''} className={editor.isActive('heading', { level: 6 }) ? 'is-active' : ''}
> >
h6 H6
</button> </button>
<button <button
onClick={() => editor.chain().focus().toggleBulletList().run()} onClick={() => editor.chain().focus().toggleBulletList().run()}
className={editor.isActive('bulletList') ? 'is-active' : ''} className={editor.isActive('bulletList') ? 'is-active' : ''}
> >
bullet list Bullet list
</button> </button>
<button <button
onClick={() => editor.chain().focus().toggleOrderedList().run()} onClick={() => editor.chain().focus().toggleOrderedList().run()}
className={editor.isActive('orderedList') ? 'is-active' : ''} className={editor.isActive('orderedList') ? 'is-active' : ''}
> >
ordered list Ordered list
</button> </button>
<button <button
onClick={() => editor.chain().focus().toggleCodeBlock().run()} onClick={() => editor.chain().focus().toggleCodeBlock().run()}
className={editor.isActive('codeBlock') ? 'is-active' : ''} className={editor.isActive('codeBlock') ? 'is-active' : ''}
> >
code block Code block
</button> </button>
<button <button
onClick={() => editor.chain().focus().toggleBlockquote().run()} onClick={() => editor.chain().focus().toggleBlockquote().run()}
className={editor.isActive('blockquote') ? 'is-active' : ''} className={editor.isActive('blockquote') ? 'is-active' : ''}
> >
blockquote Blockquote
</button> </button>
<button onClick={() => editor.chain().focus().setHorizontalRule().run()}> <button onClick={() => editor.chain().focus().setHorizontalRule().run()}>
horizontal rule Horizontal rule
</button> </button>
<button onClick={() => editor.chain().focus().setHardBreak().run()}> <button onClick={() => editor.chain().focus().setHardBreak().run()}>
hard break Hard break
</button> </button>
<button onClick={() => editor.chain().focus().undo().run()}> <button onClick={() => editor.chain().focus().undo().run()}>
undo Undo
</button> </button>
<button onClick={() => editor.chain().focus().redo().run()}> <button onClick={() => editor.chain().focus().redo().run()}>
redo Redo
</button> </button>
</> </div>
</div>
) )
} }
@ -139,9 +141,9 @@ export default () => {
}) })
return ( return (
<div> <>
<MenuBar editor={editor} /> <MenuBar editor={editor} />
<EditorContent editor={editor} /> <EditorContent editor={editor} />
</div> </>
) )
} }

View File

@ -1,14 +1,22 @@
/* Basic editor styles */ /* Basic editor styles */
.tiptap { .tiptap {
> * + * { :first-child {
margin-top: 0.75em; margin-top: 0;
} }
/* List styles */
ul, ul,
ol { ol {
padding: 0 1rem; padding: 0 1rem;
margin: 1.25rem 1rem 1.25rem 0.4rem;
li p {
margin-top: 0.25em;
margin-bottom: 0.25em;
}
} }
/* Heading styles */
h1, h1,
h2, h2,
h3, h3,
@ -16,39 +24,68 @@
h5, h5,
h6 { h6 {
line-height: 1.1; line-height: 1.1;
margin-top: 2.5rem;
text-wrap: pretty;
} }
h1,
h2 {
margin-top: 3.5rem;
margin-bottom: 1.5rem;
}
h1 {
font-size: 1.4rem;
}
h2 {
font-size: 1.2rem;
}
h3 {
font-size: 1.1rem;
}
h4,
h5,
h6 {
font-size: 1rem;
}
/* Code and preformatted text styles */
code { code {
background-color: rgba(#616161, 0.1); background-color: var(--purple-light);
color: #616161; border-radius: 0.4rem;
color: var(--black);
font-size: 0.85rem;
padding: 0.25em 0.3em;
} }
pre { pre {
background: #0D0D0D; background: var(--black);
color: #FFF;
font-family: 'JetBrainsMono', monospace;
padding: 0.75rem 1rem;
border-radius: 0.5rem; border-radius: 0.5rem;
color: var(--white);
font-family: 'JetBrainsMono', monospace;
margin: 1.5rem 0;
padding: 0.75rem 1rem;
code { code {
color: inherit;
padding: 0;
background: none; background: none;
color: inherit;
font-size: 0.8rem; font-size: 0.8rem;
padding: 0;
} }
} }
img {
max-width: 100%;
height: auto;
}
hr {
margin: 1rem 0;
}
blockquote { blockquote {
border-left: 3px solid var(--gray-3);
margin: 1.5rem 0;
padding-left: 1rem; padding-left: 1rem;
border-left: 2px solid rgba(#0D0D0D, 0.1); }
hr {
border: none;
border-top: 1px solid var(--gray-2);
margin: 2rem 0;
} }
} }

View File

@ -1,70 +1,74 @@
<template> <template>
<div v-if="editor"> <div v-if="editor" class="container">
<div class="control-group">
<div class="button-group">
<button @click="editor.chain().focus().toggleBold().run()" :class="{ 'is-active': editor.isActive('bold') }"> <button @click="editor.chain().focus().toggleBold().run()" :class="{ 'is-active': editor.isActive('bold') }">
bold Bold
</button> </button>
<button @click="editor.chain().focus().toggleItalic().run()" :class="{ 'is-active': editor.isActive('italic') }"> <button @click="editor.chain().focus().toggleItalic().run()" :class="{ 'is-active': editor.isActive('italic') }">
italic Italic
</button> </button>
<button @click="editor.chain().focus().toggleStrike().run()" :class="{ 'is-active': editor.isActive('strike') }"> <button @click="editor.chain().focus().toggleStrike().run()" :class="{ 'is-active': editor.isActive('strike') }">
strike Strike
</button> </button>
<button @click="editor.chain().focus().toggleCode().run()" :class="{ 'is-active': editor.isActive('code') }"> <button @click="editor.chain().focus().toggleCode().run()" :class="{ 'is-active': editor.isActive('code') }">
code Code
</button> </button>
<button @click="editor.chain().focus().unsetAllMarks().run()"> <button @click="editor.chain().focus().unsetAllMarks().run()">
clear marks Clear marks
</button> </button>
<button @click="editor.chain().focus().clearNodes().run()"> <button @click="editor.chain().focus().clearNodes().run()">
clear nodes Clear nodes
</button> </button>
<button @click="editor.chain().focus().setParagraph().run()" :class="{ 'is-active': editor.isActive('paragraph') }"> <button @click="editor.chain().focus().setParagraph().run()" :class="{ 'is-active': editor.isActive('paragraph') }">
paragraph Paragraph
</button> </button>
<button @click="editor.chain().focus().toggleHeading({ level: 1 }).run()" :class="{ 'is-active': editor.isActive('heading', { level: 1 }) }"> <button @click="editor.chain().focus().toggleHeading({ level: 1 }).run()" :class="{ 'is-active': editor.isActive('heading', { level: 1 }) }">
h1 H1
</button> </button>
<button @click="editor.chain().focus().toggleHeading({ level: 2 }).run()" :class="{ 'is-active': editor.isActive('heading', { level: 2 }) }"> <button @click="editor.chain().focus().toggleHeading({ level: 2 }).run()" :class="{ 'is-active': editor.isActive('heading', { level: 2 }) }">
h2 H2
</button> </button>
<button @click="editor.chain().focus().toggleHeading({ level: 3 }).run()" :class="{ 'is-active': editor.isActive('heading', { level: 3 }) }"> <button @click="editor.chain().focus().toggleHeading({ level: 3 }).run()" :class="{ 'is-active': editor.isActive('heading', { level: 3 }) }">
h3 H3
</button> </button>
<button @click="editor.chain().focus().toggleHeading({ level: 4 }).run()" :class="{ 'is-active': editor.isActive('heading', { level: 4 }) }"> <button @click="editor.chain().focus().toggleHeading({ level: 4 }).run()" :class="{ 'is-active': editor.isActive('heading', { level: 4 }) }">
h4 H4
</button> </button>
<button @click="editor.chain().focus().toggleHeading({ level: 5 }).run()" :class="{ 'is-active': editor.isActive('heading', { level: 5 }) }"> <button @click="editor.chain().focus().toggleHeading({ level: 5 }).run()" :class="{ 'is-active': editor.isActive('heading', { level: 5 }) }">
h5 H5
</button> </button>
<button @click="editor.chain().focus().toggleHeading({ level: 6 }).run()" :class="{ 'is-active': editor.isActive('heading', { level: 6 }) }"> <button @click="editor.chain().focus().toggleHeading({ level: 6 }).run()" :class="{ 'is-active': editor.isActive('heading', { level: 6 }) }">
h6 H6
</button> </button>
<button @click="editor.chain().focus().toggleBulletList().run()" :class="{ 'is-active': editor.isActive('bulletList') }"> <button @click="editor.chain().focus().toggleBulletList().run()" :class="{ 'is-active': editor.isActive('bulletList') }">
bullet list Bullet list
</button> </button>
<button @click="editor.chain().focus().toggleOrderedList().run()" :class="{ 'is-active': editor.isActive('orderedList') }"> <button @click="editor.chain().focus().toggleOrderedList().run()" :class="{ 'is-active': editor.isActive('orderedList') }">
ordered list Ordered list
</button> </button>
<button @click="editor.chain().focus().toggleCodeBlock().run()" :class="{ 'is-active': editor.isActive('codeBlock') }"> <button @click="editor.chain().focus().toggleCodeBlock().run()" :class="{ 'is-active': editor.isActive('codeBlock') }">
code block Code block
</button> </button>
<button @click="editor.chain().focus().toggleBlockquote().run()" :class="{ 'is-active': editor.isActive('blockquote') }"> <button @click="editor.chain().focus().toggleBlockquote().run()" :class="{ 'is-active': editor.isActive('blockquote') }">
blockquote Blockquote
</button> </button>
<button @click="editor.chain().focus().setHorizontalRule().run()"> <button @click="editor.chain().focus().setHorizontalRule().run()">
horizontal rule Horizontal rule
</button> </button>
<button @click="editor.chain().focus().setHardBreak().run()"> <button @click="editor.chain().focus().setHardBreak().run()">
hard break Hard break
</button> </button>
<button @click="editor.chain().focus().undo().run()"> <button @click="editor.chain().focus().undo().run()">
undo Undo
</button> </button>
<button @click="editor.chain().focus().redo().run()"> <button @click="editor.chain().focus().redo().run()">
redo Redo
</button> </button>
</div> </div>
</div>
<editor-content :editor="editor" /> <editor-content :editor="editor" />
</div>
</template> </template>
<script> <script>
@ -107,15 +111,23 @@ export default {
<style lang="scss"> <style lang="scss">
/* Basic editor styles */ /* Basic editor styles */
.tiptap { .tiptap {
> * + * { :first-child {
margin-top: 0.75em; margin-top: 0;
} }
/* List styles */
ul, ul,
ol { ol {
padding: 0 1rem; padding: 0 1rem;
margin: 1.25rem 1rem 1.25rem 0.4rem;
li p {
margin-top: 0.25em;
margin-bottom: 0.25em;
}
} }
/* Heading styles */
h1, h1,
h2, h2,
h3, h3,
@ -123,40 +135,69 @@ export default {
h5, h5,
h6 { h6 {
line-height: 1.1; line-height: 1.1;
margin-top: 2.5rem;
text-wrap: pretty;
} }
h1,
h2 {
margin-top: 3.5rem;
margin-bottom: 1.5rem;
}
h1 {
font-size: 1.4rem;
}
h2 {
font-size: 1.2rem;
}
h3 {
font-size: 1.1rem;
}
h4,
h5,
h6 {
font-size: 1rem;
}
/* Code and preformatted text styles */
code { code {
background-color: rgba(#616161, 0.1); background-color: var(--purple-light);
color: #616161; border-radius: 0.4rem;
color: var(--black);
font-size: 0.85rem;
padding: 0.25em 0.3em;
} }
pre { pre {
background: #0D0D0D; background: var(--black);
color: #FFF;
font-family: 'JetBrainsMono', monospace;
padding: 0.75rem 1rem;
border-radius: 0.5rem; border-radius: 0.5rem;
color: var(--white);
font-family: 'JetBrainsMono', monospace;
margin: 1.5rem 0;
padding: 0.75rem 1rem;
code { code {
color: inherit;
padding: 0;
background: none; background: none;
color: inherit;
font-size: 0.8rem; font-size: 0.8rem;
padding: 0;
} }
} }
img {
max-width: 100%;
height: auto;
}
hr {
margin: 1rem 0;
}
blockquote { blockquote {
border-left: 3px solid var(--gray-3);
margin: 1.5rem 0;
padding-left: 1rem; padding-left: 1rem;
border-left: 2px solid rgba(#0D0D0D, 0.1); }
hr {
border: none;
border-top: 1px solid var(--gray-2);
margin: 2rem 0;
} }
} }
</style> </style>

View File

@ -17,109 +17,109 @@ const MenuBar = ({ editor }) => {
onClick={() => editor.chain().focus().toggleBold().run()} onClick={() => editor.chain().focus().toggleBold().run()}
className={editor.isActive('bold') ? 'is-active' : ''} className={editor.isActive('bold') ? 'is-active' : ''}
> >
bold Bold
</button> </button>
<button <button
onClick={() => editor.chain().focus().toggleItalic().run()} onClick={() => editor.chain().focus().toggleItalic().run()}
className={editor.isActive('italic') ? 'is-active' : ''} className={editor.isActive('italic') ? 'is-active' : ''}
> >
italic Italic
</button> </button>
<button <button
onClick={() => editor.chain().focus().toggleStrike().run()} onClick={() => editor.chain().focus().toggleStrike().run()}
className={editor.isActive('strike') ? 'is-active' : ''} className={editor.isActive('strike') ? 'is-active' : ''}
> >
strike Strike
</button> </button>
<button <button
onClick={() => editor.chain().focus().toggleCode().run()} onClick={() => editor.chain().focus().toggleCode().run()}
className={editor.isActive('code') ? 'is-active' : ''} className={editor.isActive('code') ? 'is-active' : ''}
> >
code Code
</button> </button>
<button onClick={() => editor.chain().focus().unsetAllMarks().run()}> <button onClick={() => editor.chain().focus().unsetAllMarks().run()}>
clear marks Clear marks
</button> </button>
<button onClick={() => editor.chain().focus().clearNodes().run()}> <button onClick={() => editor.chain().focus().clearNodes().run()}>
clear nodes Clear nodes
</button> </button>
<button <button
onClick={() => editor.chain().focus().setParagraph().run()} onClick={() => editor.chain().focus().setParagraph().run()}
className={editor.isActive('paragraph') ? 'is-active' : ''} className={editor.isActive('paragraph') ? 'is-active' : ''}
> >
paragraph Paragraph
</button> </button>
<button <button
onClick={() => editor.chain().focus().toggleHeading({ level: 1 }).run()} onClick={() => editor.chain().focus().toggleHeading({ level: 1 }).run()}
className={editor.isActive('heading', { level: 1 }) ? 'is-active' : ''} className={editor.isActive('heading', { level: 1 }) ? 'is-active' : ''}
> >
h1 H1
</button> </button>
<button <button
onClick={() => editor.chain().focus().toggleHeading({ level: 2 }).run()} onClick={() => editor.chain().focus().toggleHeading({ level: 2 }).run()}
className={editor.isActive('heading', { level: 2 }) ? 'is-active' : ''} className={editor.isActive('heading', { level: 2 }) ? 'is-active' : ''}
> >
h2 H2
</button> </button>
<button <button
onClick={() => editor.chain().focus().toggleHeading({ level: 3 }).run()} onClick={() => editor.chain().focus().toggleHeading({ level: 3 }).run()}
className={editor.isActive('heading', { level: 3 }) ? 'is-active' : ''} className={editor.isActive('heading', { level: 3 }) ? 'is-active' : ''}
> >
h3 H3
</button> </button>
<button <button
onClick={() => editor.chain().focus().toggleHeading({ level: 4 }).run()} onClick={() => editor.chain().focus().toggleHeading({ level: 4 }).run()}
className={editor.isActive('heading', { level: 4 }) ? 'is-active' : ''} className={editor.isActive('heading', { level: 4 }) ? 'is-active' : ''}
> >
h4 H4
</button> </button>
<button <button
onClick={() => editor.chain().focus().toggleHeading({ level: 5 }).run()} onClick={() => editor.chain().focus().toggleHeading({ level: 5 }).run()}
className={editor.isActive('heading', { level: 5 }) ? 'is-active' : ''} className={editor.isActive('heading', { level: 5 }) ? 'is-active' : ''}
> >
h5 H5
</button> </button>
<button <button
onClick={() => editor.chain().focus().toggleHeading({ level: 6 }).run()} onClick={() => editor.chain().focus().toggleHeading({ level: 6 }).run()}
className={editor.isActive('heading', { level: 6 }) ? 'is-active' : ''} className={editor.isActive('heading', { level: 6 }) ? 'is-active' : ''}
> >
h6 H6
</button> </button>
<button <button
onClick={() => editor.chain().focus().toggleBulletList().run()} onClick={() => editor.chain().focus().toggleBulletList().run()}
className={editor.isActive('bulletList') ? 'is-active' : ''} className={editor.isActive('bulletList') ? 'is-active' : ''}
> >
bullet list Bullet list
</button> </button>
<button <button
onClick={() => editor.chain().focus().toggleOrderedList().run()} onClick={() => editor.chain().focus().toggleOrderedList().run()}
className={editor.isActive('orderedList') ? 'is-active' : ''} className={editor.isActive('orderedList') ? 'is-active' : ''}
> >
ordered list Ordered list
</button> </button>
<button <button
onClick={() => editor.chain().focus().toggleCodeBlock().run()} onClick={() => editor.chain().focus().toggleCodeBlock().run()}
className={editor.isActive('codeBlock') ? 'is-active' : ''} className={editor.isActive('codeBlock') ? 'is-active' : ''}
> >
code block Code block
</button> </button>
<button <button
onClick={() => editor.chain().focus().toggleBlockquote().run()} onClick={() => editor.chain().focus().toggleBlockquote().run()}
className={editor.isActive('blockquote') ? 'is-active' : ''} className={editor.isActive('blockquote') ? 'is-active' : ''}
> >
blockquote Blockquote
</button> </button>
<button onClick={() => editor.chain().focus().setHorizontalRule().run()}> <button onClick={() => editor.chain().focus().setHorizontalRule().run()}>
horizontal rule Horizontal rule
</button> </button>
<button onClick={() => editor.chain().focus().setHardBreak().run()}> <button onClick={() => editor.chain().focus().setHardBreak().run()}>
hard break Hard break
</button> </button>
<button onClick={() => editor.chain().focus().undo().run()}> <button onClick={() => editor.chain().focus().undo().run()}>
undo Undo
</button> </button>
<button onClick={() => editor.chain().focus().redo().run()}> <button onClick={() => editor.chain().focus().redo().run()}>
redo Redo
</button> </button>
</div> </div>
) )
@ -135,7 +135,7 @@ export default () => {
Hi there, Hi there,
</h2> </h2>
<p> <p>
this is a <em>basic</em> example of <strong>tiptap</strong>. Sure, there are all kind of basic text styles youd probably expect from a text editor. But wait until you see the lists: this is a <em>basic</em> example of <strong>Tiptap</strong>. Sure, there are all kind of basic text styles youd probably expect from a text editor. But wait until you see the lists:
</p> </p>
<ul> <ul>
<li> <li>
@ -163,9 +163,9 @@ export default () => {
}) })
return ( return (
<div> <>
<MenuBar editor={editor} /> <MenuBar editor={editor} />
<EditorContent editor={editor} /> <EditorContent editor={editor} />
</div> </>
) )
} }

View File

@ -1,14 +1,22 @@
/* Basic editor styles */ /* Basic editor styles */
.tiptap { .tiptap {
> * + * { :first-child {
margin-top: 0.75em; margin-top: 0;
} }
/* List styles */
ul, ul,
ol { ol {
padding: 0 1rem; padding: 0 1rem;
margin: 1.25rem 1rem 1.25rem 0.4rem;
li p {
margin-top: 0.25em;
margin-bottom: 0.25em;
}
} }
/* Heading styles */
h1, h1,
h2, h2,
h3, h3,
@ -16,41 +24,68 @@
h5, h5,
h6 { h6 {
line-height: 1.1; line-height: 1.1;
margin-top: 2.5rem;
text-wrap: pretty;
} }
h1,
h2 {
margin-top: 3.5rem;
margin-bottom: 1.5rem;
}
h1 {
font-size: 1.4rem;
}
h2 {
font-size: 1.2rem;
}
h3 {
font-size: 1.1rem;
}
h4,
h5,
h6 {
font-size: 1rem;
}
/* Code and preformatted text styles */
code { code {
background-color: rgba(#616161, 0.1); background-color: var(--purple-light);
color: #616161; border-radius: 0.4rem;
color: var(--black);
font-size: 0.85rem;
padding: 0.25em 0.3em;
} }
pre { pre {
background: #0D0D0D; background: var(--black);
color: #FFF;
font-family: 'JetBrainsMono', monospace;
padding: 0.75rem 1rem;
border-radius: 0.5rem; border-radius: 0.5rem;
color: var(--white);
font-family: 'JetBrainsMono', monospace;
margin: 1.5rem 0;
padding: 0.75rem 1rem;
code { code {
color: inherit;
padding: 0;
background: none; background: none;
color: inherit;
font-size: 0.8rem; font-size: 0.8rem;
padding: 0;
} }
} }
img {
max-width: 100%;
height: auto;
}
blockquote { blockquote {
border-left: 3px solid var(--gray-3);
margin: 1.5rem 0;
padding-left: 1rem; padding-left: 1rem;
border-left: 2px solid rgba(#0D0D0D, 0.1);
} }
hr { hr {
border: none; border: none;
border-top: 2px solid rgba(#0D0D0D, 0.1); border-top: 1px solid var(--gray-2);
margin: 2rem 0; margin: 2rem 0;
} }
} }

View File

@ -1,67 +1,67 @@
<template> <template>
<div v-if="editor" class="toolbar" :class="styles.toolbar"> <div v-if="editor" class="toolbar" :class="styles.toolbar">
<button @click="editor.chain().focus().toggleBold().run()" :class="{ 'is-active': editor.isActive('bold') }"> <button @click="editor.chain().focus().toggleBold().run()" :class="{ 'is-active': editor.isActive('bold') }">
bold Bold
</button> </button>
<button @click="editor.chain().focus().toggleItalic().run()" :class="{ 'is-active': editor.isActive('italic') }"> <button @click="editor.chain().focus().toggleItalic().run()" :class="{ 'is-active': editor.isActive('italic') }">
italic Italic
</button> </button>
<button @click="editor.chain().focus().toggleStrike().run()" :class="{ 'is-active': editor.isActive('strike') }"> <button @click="editor.chain().focus().toggleStrike().run()" :class="{ 'is-active': editor.isActive('strike') }">
strike Strike
</button> </button>
<button @click="editor.chain().focus().toggleCode().run()" :class="{ 'is-active': editor.isActive('code') }"> <button @click="editor.chain().focus().toggleCode().run()" :class="{ 'is-active': editor.isActive('code') }">
code Code
</button> </button>
<button @click="editor.chain().focus().unsetAllMarks().run()"> <button @click="editor.chain().focus().unsetAllMarks().run()">
clear marks Clear marks
</button> </button>
<button @click="editor.chain().focus().clearNodes().run()"> <button @click="editor.chain().focus().clearNodes().run()">
clear nodes Clear nodes
</button> </button>
<button @click="editor.chain().focus().setParagraph().run()" :class="{ 'is-active': editor.isActive('paragraph') }"> <button @click="editor.chain().focus().setParagraph().run()" :class="{ 'is-active': editor.isActive('paragraph') }">
paragraph Paragraph
</button> </button>
<button @click="editor.chain().focus().toggleHeading({ level: 1 }).run()" :class="{ 'is-active': editor.isActive('heading', { level: 1 }) }"> <button @click="editor.chain().focus().toggleHeading({ level: 1 }).run()" :class="{ 'is-active': editor.isActive('heading', { level: 1 }) }">
h1 H1
</button> </button>
<button @click="editor.chain().focus().toggleHeading({ level: 2 }).run()" :class="{ 'is-active': editor.isActive('heading', { level: 2 }) }"> <button @click="editor.chain().focus().toggleHeading({ level: 2 }).run()" :class="{ 'is-active': editor.isActive('heading', { level: 2 }) }">
h2 H2
</button> </button>
<button @click="editor.chain().focus().toggleHeading({ level: 3 }).run()" :class="{ 'is-active': editor.isActive('heading', { level: 3 }) }"> <button @click="editor.chain().focus().toggleHeading({ level: 3 }).run()" :class="{ 'is-active': editor.isActive('heading', { level: 3 }) }">
h3 H3
</button> </button>
<button @click="editor.chain().focus().toggleHeading({ level: 4 }).run()" :class="{ 'is-active': editor.isActive('heading', { level: 4 }) }"> <button @click="editor.chain().focus().toggleHeading({ level: 4 }).run()" :class="{ 'is-active': editor.isActive('heading', { level: 4 }) }">
h4 H4
</button> </button>
<button @click="editor.chain().focus().toggleHeading({ level: 5 }).run()" :class="{ 'is-active': editor.isActive('heading', { level: 5 }) }"> <button @click="editor.chain().focus().toggleHeading({ level: 5 }).run()" :class="{ 'is-active': editor.isActive('heading', { level: 5 }) }">
h5 H5
</button> </button>
<button @click="editor.chain().focus().toggleHeading({ level: 6 }).run()" :class="{ 'is-active': editor.isActive('heading', { level: 6 }) }"> <button @click="editor.chain().focus().toggleHeading({ level: 6 }).run()" :class="{ 'is-active': editor.isActive('heading', { level: 6 }) }">
h6 H6
</button> </button>
<button @click="editor.chain().focus().toggleBulletList().run()" :class="{ 'is-active': editor.isActive('bulletList') }"> <button @click="editor.chain().focus().toggleBulletList().run()" :class="{ 'is-active': editor.isActive('bulletList') }">
bullet list Bullet list
</button> </button>
<button @click="editor.chain().focus().toggleOrderedList().run()" :class="{ 'is-active': editor.isActive('orderedList') }"> <button @click="editor.chain().focus().toggleOrderedList().run()" :class="{ 'is-active': editor.isActive('orderedList') }">
ordered list Ordered list
</button> </button>
<button @click="editor.chain().focus().toggleCodeBlock().run()" :class="{ 'is-active': editor.isActive('codeBlock') }"> <button @click="editor.chain().focus().toggleCodeBlock().run()" :class="{ 'is-active': editor.isActive('codeBlock') }">
code block Code block
</button> </button>
<button @click="editor.chain().focus().toggleBlockquote().run()" :class="{ 'is-active': editor.isActive('blockquote') }"> <button @click="editor.chain().focus().toggleBlockquote().run()" :class="{ 'is-active': editor.isActive('blockquote') }">
blockquote Blockquote
</button> </button>
<button @click="editor.chain().focus().setHorizontalRule().run()"> <button @click="editor.chain().focus().setHorizontalRule().run()">
horizontal rule Horizontal rule
</button> </button>
<button @click="editor.chain().focus().setHardBreak().run()"> <button @click="editor.chain().focus().setHardBreak().run()">
hard break Hard break
</button> </button>
<button @click="editor.chain().focus().undo().run()"> <button @click="editor.chain().focus().undo().run()">
undo Undo
</button> </button>
<button @click="editor.chain().focus().redo().run()"> <button @click="editor.chain().focus().redo().run()">
redo Redo
</button> </button>
</div> </div>
<editor-content :editor="editor" /> <editor-content :editor="editor" />
@ -95,7 +95,7 @@ export default {
This is a red headline This is a red headline
</h1> </h1>
<p> <p>
this is a <em>basic</em> example of <strong>tiptap</strong>. Sure, there are all kind of basic text styles youd probably expect from a text editor. But wait until you see the lists: this is a <em>basic</em> example of <strong>Tiptap</strong>. Sure, there are all kind of basic text styles youd probably expect from a text editor. But wait until you see the lists:
</p> </p>
<ul> <ul>
<li> <li>
@ -132,15 +132,23 @@ export default {
<style lang="scss"> <style lang="scss">
/* Basic editor styles */ /* Basic editor styles */
.tiptap { .tiptap {
> * + * { :first-child {
margin-top: 0.75em; margin-top: 0;
} }
/* List styles */
ul, ul,
ol { ol {
padding: 0 1rem; padding: 0 1rem;
margin: 1.25rem 1rem 1.25rem 0.4rem;
li p {
margin-top: 0.25em;
margin-bottom: 0.25em;
}
} }
/* Heading styles */
h1, h1,
h2, h2,
h3, h3,
@ -148,41 +156,68 @@ export default {
h5, h5,
h6 { h6 {
line-height: 1.1; line-height: 1.1;
margin-top: 2.5rem;
text-wrap: pretty;
} }
h1,
h2 {
margin-top: 3.5rem;
margin-bottom: 1.5rem;
}
h1 {
font-size: 1.4rem;
}
h2 {
font-size: 1.2rem;
}
h3 {
font-size: 1.1rem;
}
h4,
h5,
h6 {
font-size: 1rem;
}
/* Code and preformatted text styles */
code { code {
background-color: rgba(#616161, 0.1); background-color: var(--purple-light);
color: #616161; border-radius: 0.4rem;
color: var(--black);
font-size: 0.85rem;
padding: 0.25em 0.3em;
} }
pre { pre {
background: #0D0D0D; background: var(--black);
color: #FFF;
font-family: 'JetBrainsMono', monospace;
padding: 0.75rem 1rem;
border-radius: 0.5rem; border-radius: 0.5rem;
color: var(--white);
font-family: 'JetBrainsMono', monospace;
margin: 1.5rem 0;
padding: 0.75rem 1rem;
code { code {
color: inherit;
padding: 0;
background: none; background: none;
color: inherit;
font-size: 0.8rem; font-size: 0.8rem;
padding: 0;
} }
} }
img {
max-width: 100%;
height: auto;
}
blockquote { blockquote {
border-left: 3px solid var(--gray-3);
margin: 1.5rem 0;
padding-left: 1rem; padding-left: 1rem;
border-left: 2px solid rgba(#0D0D0D, 0.1);
} }
hr { hr {
border: none; border: none;
border-top: 2px solid rgba(#0D0D0D, 0.1); border-top: 1px solid var(--gray-2);
margin: 2rem 0; margin: 2rem 0;
} }
} }

View File

@ -1,9 +1,13 @@
.code-block { .tiptap {
.code-block {
position: relative; position: relative;
select { select {
position: absolute; position: absolute;
background-color: var(--white);
background-image: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path fill="Black" d="M7 10l5 5 5-5z"/></svg>');
right: 0.5rem; right: 0.5rem;
top: 0.5rem; top: 0.5rem;
} }
}
} }

View File

@ -30,9 +30,13 @@ const MenuBar = ({ editor }) => {
} }
return ( return (
<div className="control-group">
<div className="button-group">
<button onClick={() => editor.chain().focus().toggleCodeBlock().run()} className={editor.isActive('codeBlock') ? 'is-active' : ''}> <button onClick={() => editor.chain().focus().toggleCodeBlock().run()} className={editor.isActive('codeBlock') ? 'is-active' : ''}>
code block Toggle code block
</button> </button>
</div>
</div>
) )
} }
@ -72,9 +76,9 @@ export default () => {
}) })
return ( return (
<div> <>
<MenuBar editor={editor} /> <MenuBar editor={editor} />
<EditorContent editor={editor} /> <EditorContent editor={editor} />
</div> </>
) )
} }

View File

@ -1,23 +1,25 @@
/* Basic editor styles */ /* Basic editor styles */
.tiptap { .tiptap {
> * + * { :first-child {
margin-top: 0.75em; margin-top: 0;
} }
pre { pre {
background: #0d0d0d; background: var(--black);
color: #fff;
font-family: "JetBrainsMono", monospace;
padding: 0.75rem 1rem;
border-radius: 0.5rem; border-radius: 0.5rem;
color: var(--white);
font-family: 'JetBrainsMono', monospace;
margin: 1.5rem 0;
padding: 0.75rem 1rem;
code { code {
color: inherit;
padding: 0;
background: none; background: none;
color: inherit;
font-size: 0.8rem; font-size: 0.8rem;
padding: 0;
} }
/* Code styling */
.hljs-comment, .hljs-comment,
.hljs-quote { .hljs-quote {
color: #616161; color: #616161;

View File

@ -46,13 +46,17 @@ export default {
</script> </script>
<style lang="scss"> <style lang="scss">
.code-block { .tiptap {
.code-block {
position: relative; position: relative;
select { select {
position: absolute; position: absolute;
top: 0.5rem; background-color: var(--white);
background-image: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path fill="Black" d="M7 10l5 5 5-5z"/></svg>');
right: 0.5rem; right: 0.5rem;
top: 0.5rem;
}
} }
} }
</style> </style>

View File

@ -1,9 +1,13 @@
<template> <template>
<div v-if="editor"> <div v-if="editor" class="container">
<div class="control-group">
<div class="button-">
<button @click="editor.chain().focus().toggleCodeBlock().run()" :class="{ 'is-active': editor.isActive('codeBlock') }"> <button @click="editor.chain().focus().toggleCodeBlock().run()" :class="{ 'is-active': editor.isActive('codeBlock') }">
code block Toggle code block
</button> </button>
</div> </div>
</div>
</div>
<editor-content :editor="editor" /> <editor-content :editor="editor" />
</template> </template>
@ -88,24 +92,26 @@ export default {
<style lang="scss"> <style lang="scss">
/* Basic editor styles */ /* Basic editor styles */
.tiptap { .tiptap {
> * + * { :first-child {
margin-top: 0.75em; margin-top: 0;
} }
pre { pre {
background: #0D0D0D; background: var(--black);
color: #FFF;
font-family: 'JetBrainsMono', monospace;
padding: 0.75rem 1rem;
border-radius: 0.5rem; border-radius: 0.5rem;
color: var(--white);
font-family: 'JetBrainsMono', monospace;
margin: 1.5rem 0;
padding: 0.75rem 1rem;
code { code {
color: inherit;
padding: 0;
background: none; background: none;
color: inherit;
font-size: 0.8rem; font-size: 0.8rem;
padding: 0;
} }
/* Code styling */
.hljs-comment, .hljs-comment,
.hljs-quote { .hljs-quote {
color: #616161; color: #616161;
@ -121,7 +127,7 @@ export default {
.hljs-name, .hljs-name,
.hljs-selector-id, .hljs-selector-id,
.hljs-selector-class { .hljs-selector-class {
color: #F98181; color: #f98181;
} }
.hljs-number, .hljs-number,
@ -131,23 +137,23 @@ export default {
.hljs-literal, .hljs-literal,
.hljs-type, .hljs-type,
.hljs-params { .hljs-params {
color: #FBBC88; color: #fbbc88;
} }
.hljs-string, .hljs-string,
.hljs-symbol, .hljs-symbol,
.hljs-bullet { .hljs-bullet {
color: #B9F18D; color: #b9f18d;
} }
.hljs-title, .hljs-title,
.hljs-section { .hljs-section {
color: #FAF594; color: #faf594;
} }
.hljs-keyword, .hljs-keyword,
.hljs-selector-tag { .hljs-selector-tag {
color: #70CFF8; color: #70cff8;
} }
.hljs-emphasis { .hljs-emphasis {

View File

@ -59,25 +59,25 @@ export default ({ editor }) => {
}, },
{ {
icon: 'list-unordered', icon: 'list-unordered',
title: 'Bullet List', title: 'Bullet list',
action: () => editor.chain().focus().toggleBulletList().run(), action: () => editor.chain().focus().toggleBulletList().run(),
isActive: () => editor.isActive('bulletList'), isActive: () => editor.isActive('bulletList'),
}, },
{ {
icon: 'list-ordered', icon: 'list-ordered',
title: 'Ordered List', title: 'Ordered list',
action: () => editor.chain().focus().toggleOrderedList().run(), action: () => editor.chain().focus().toggleOrderedList().run(),
isActive: () => editor.isActive('orderedList'), isActive: () => editor.isActive('orderedList'),
}, },
{ {
icon: 'list-check-2', icon: 'list-check-2',
title: 'Task List', title: 'Task list',
action: () => editor.chain().focus().toggleTaskList().run(), action: () => editor.chain().focus().toggleTaskList().run(),
isActive: () => editor.isActive('taskList'), isActive: () => editor.isActive('taskList'),
}, },
{ {
icon: 'code-box-line', icon: 'code-box-line',
title: 'Code Block', title: 'Code block',
action: () => editor.chain().focus().toggleCodeBlock().run(), action: () => editor.chain().focus().toggleCodeBlock().run(),
isActive: () => editor.isActive('codeBlock'), isActive: () => editor.isActive('codeBlock'),
}, },
@ -92,7 +92,7 @@ export default ({ editor }) => {
}, },
{ {
icon: 'separator', icon: 'separator',
title: 'Horizontal Rule', title: 'Horizontal rule',
action: () => editor.chain().focus().setHorizontalRule().run(), action: () => editor.chain().focus().setHorizontalRule().run(),
}, },
{ {
@ -100,12 +100,12 @@ export default ({ editor }) => {
}, },
{ {
icon: 'text-wrap', icon: 'text-wrap',
title: 'Hard Break', title: 'Hard break',
action: () => editor.chain().focus().setHardBreak().run(), action: () => editor.chain().focus().setHardBreak().run(),
}, },
{ {
icon: 'format-clear', icon: 'format-clear',
title: 'Clear Format', title: 'Clear format',
action: () => editor.chain().focus().clearNodes().unsetAllMarks() action: () => editor.chain().focus().clearNodes().unsetAllMarks()
.run(), .run(),
}, },

View File

@ -78,25 +78,25 @@ export default {
}, },
{ {
icon: 'list-unordered', icon: 'list-unordered',
title: 'Bullet List', title: 'Bullet list',
action: () => this.editor.chain().focus().toggleBulletList().run(), action: () => this.editor.chain().focus().toggleBulletList().run(),
isActive: () => this.editor.isActive('bulletList'), isActive: () => this.editor.isActive('bulletList'),
}, },
{ {
icon: 'list-ordered', icon: 'list-ordered',
title: 'Ordered List', title: 'Ordered list',
action: () => this.editor.chain().focus().toggleOrderedList().run(), action: () => this.editor.chain().focus().toggleOrderedList().run(),
isActive: () => this.editor.isActive('orderedList'), isActive: () => this.editor.isActive('orderedList'),
}, },
{ {
icon: 'list-check-2', icon: 'list-check-2',
title: 'Task List', title: 'Task list',
action: () => this.editor.chain().focus().toggleTaskList().run(), action: () => this.editor.chain().focus().toggleTaskList().run(),
isActive: () => this.editor.isActive('taskList'), isActive: () => this.editor.isActive('taskList'),
}, },
{ {
icon: 'code-box-line', icon: 'code-box-line',
title: 'Code Block', title: 'Code block',
action: () => this.editor.chain().focus().toggleCodeBlock().run(), action: () => this.editor.chain().focus().toggleCodeBlock().run(),
isActive: () => this.editor.isActive('codeBlock'), isActive: () => this.editor.isActive('codeBlock'),
}, },
@ -111,7 +111,7 @@ export default {
}, },
{ {
icon: 'separator', icon: 'separator',
title: 'Horizontal Rule', title: 'Horizontal rule',
action: () => this.editor.chain().focus().setHorizontalRule().run(), action: () => this.editor.chain().focus().setHorizontalRule().run(),
}, },
{ {
@ -119,12 +119,12 @@ export default {
}, },
{ {
icon: 'text-wrap', icon: 'text-wrap',
title: 'Hard Break', title: 'Hard break',
action: () => this.editor.chain().focus().setHardBreak().run(), action: () => this.editor.chain().focus().setHardBreak().run(),
}, },
{ {
icon: 'format-clear', icon: 'format-clear',
title: 'Clear Format', title: 'Clear format',
action: () => this.editor.chain() action: () => this.editor.chain()
.focus() .focus()
.clearNodes() .clearNodes()

View File

@ -54,11 +54,11 @@ export const MentionList = forwardRef((props, ref) => {
})) }))
return ( return (
<div className="items"> <div className="dropdown-menu">
{props.items.length {props.items.length
? props.items.map((item, index) => ( ? props.items.map((item, index) => (
<button <button
className={`item ${index === selectedIndex ? 'is-selected' : ''}`} className={index === selectedIndex ? 'is-selected' : ''}
key={index} key={index}
onClick={() => selectItem(index)} onClick={() => selectItem(index)}
> >

View File

@ -1,28 +1,31 @@
.items { /* Dropdown menu */
padding: 0.2rem; .dropdown-menu {
background: var(--white);
border: 1px solid var(--gray-1);
border-radius: 0.7rem;
box-shadow: var(--shadow);
display: flex;
flex-direction: column;
gap: 0.1rem;
overflow: auto;
padding: 0.4rem;
position: relative; position: relative;
border-radius: 0.5rem;
background: #FFF;
color: rgba(0, 0, 0, 0.8);
overflow: hidden;
font-size: 0.9rem;
box-shadow:
0 0 0 1px rgba(0, 0, 0, 0.05),
0px 10px 20px rgba(0, 0, 0, 0.1),
;
}
.item { button {
display: block; align-items: center;
margin: 0; background-color: transparent;
width: 100%; display: flex;
gap: 0.25rem;
text-align: left; text-align: left;
background: transparent; width: 100%;
border-radius: 0.4rem;
border: 1px solid transparent; &:hover,
padding: 0.2rem 0.4rem; &:hover.is-selected {
background-color: var(--gray-3);
}
&.is-selected { &.is-selected {
border-color: #000; background-color: var(--gray-2);
}
} }
} }

View File

@ -40,7 +40,7 @@ export default () => {
: 0 : 0
return ( return (
<div> <>
<EditorContent editor={editor} /> <EditorContent editor={editor} />
{editor {editor
&& <div className={`character-count ${editor.storage.characterCount.characters() === limit ? 'character-count--warning' : ''}`}> && <div className={`character-count ${editor.storage.characterCount.characters() === limit ? 'character-count--warning' : ''}`}>
@ -48,7 +48,6 @@ export default () => {
height="20" height="20"
width="20" width="20"
viewBox="0 0 20 20" viewBox="0 0 20 20"
className="character-count__graph"
> >
<circle <circle
r="10" r="10"
@ -74,11 +73,9 @@ export default () => {
/> />
</svg> </svg>
<div className="character-count__text"> {editor.storage.characterCount.characters()} / {limit} characters
{editor.storage.characterCount.characters()}/{limit} characters
</div>
</div> </div>
} }
</div> </>
) )
} }

View File

@ -4,33 +4,33 @@ context('/src/Examples/Community/React/', () => {
}) })
it('should count the characters correctly', () => { it('should count the characters correctly', () => {
// check if count text is "44/280 characters" // check if count text is "44 / 280 characters"
cy.get('.character-count__text').should('have.text', '44/280 characters') cy.get('.character-count').should('contain', '44 / 280 characters')
// type in .tiptap // type in .tiptap
cy.get('.tiptap').type(' Hello World') cy.get('.tiptap').type(' Hello World')
cy.get('.character-count__text').should('have.text', '56/280 characters') cy.get('.character-count').should('contain', '56 / 280 characters')
// remove content from .tiptap and enter text // remove content from .tiptap and enter text
cy.get('.tiptap').type('{selectall}{backspace}Hello World') cy.get('.tiptap').type('{selectall}{backspace}Hello World')
cy.get('.character-count__text').should('have.text', '11/280 characters') cy.get('.character-count').should('contain', '11 / 280 characters')
}) })
it('should mention a user', () => { it('should mention a user', () => {
cy.get('.tiptap').type('{selectall}{backspace}@') cy.get('.tiptap').type('{selectall}{backspace}@')
// check if the mention autocomplete is visible // check if the mention autocomplete is visible
cy.get('.tippy-content .items').should('be.visible') cy.get('.tippy-content .dropdown-menu').should('be.visible')
// select the first user // select the first user
cy.get('.tippy-content .items .item').first().then($el => { cy.get('.tippy-content .dropdown-menu button').first().then($el => {
const name = $el.text() const name = $el.text()
$el.click() $el.click()
// check if the user is mentioned // check if the user is mentioned
cy.get('.tiptap').should('have.text', `@${name} `) cy.get('.tiptap').should('have.text', `@${name} `)
cy.get('.character-count__text').should('have.text', '2/280 characters') cy.get('.character-count').should('contain', '2 / 280 characters')
}) })
}) })

View File

@ -1,41 +1,33 @@
/* Basic editor styles */ /* Basic editor styles */
.tiptap { .tiptap {
> * + * { :first-child {
margin-top: 0.75em; margin-top: 0;
} }
h1, .mention {
h2, background-color: var(--purple-light);
h3,
h4,
h5,
h6 {
line-height: 1.1;
}
}
.mention {
border: 1px solid #000;
border-radius: 0.4rem; border-radius: 0.4rem;
padding: 0.1rem 0.3rem;
box-decoration-break: clone; box-decoration-break: clone;
color: var(--purple);
padding: 0.1rem 0.3rem;
}
} }
/* Character count */
.character-count { .character-count {
margin-top: 1rem;
display: flex;
align-items: center; align-items: center;
color: #68CEF8; color: var(--gray-5);
display: flex;
font-size: 0.75rem;
gap: .5rem;
margin: 1.5rem;
&--warning { svg {
color: #FB5151; color: var(--purple);
} }
&__graph { &--warning,
margin-right: 0.5rem; &--warning svg {
} color: var(--red);
&__text {
color: #868e96;
} }
} }

View File

@ -1,8 +1,7 @@
<template> <template>
<div class="items"> <div class="dropdown-menu">
<template v-if="items.length"> <template v-if="items.length">
<button <button
class="item"
:class="{ 'is-selected': index === selectedIndex }" :class="{ 'is-selected': index === selectedIndex }"
v-for="(item, index) in items" v-for="(item, index) in items"
:key="index" :key="index"
@ -87,32 +86,35 @@ export default {
</script> </script>
<style lang="scss"> <style lang="scss">
.items { /* Dropdown menu */
padding: 0.2rem; .dropdown-menu {
background: var(--white);
border: 1px solid var(--gray-1);
border-radius: 0.7rem;
box-shadow: var(--shadow);
display: flex;
flex-direction: column;
gap: 0.1rem;
overflow: auto;
padding: 0.4rem;
position: relative; position: relative;
border-radius: 0.5rem;
background: #FFF;
color: rgba(0, 0, 0, 0.8);
overflow: hidden;
font-size: 0.9rem;
box-shadow:
0 0 0 1px rgba(0, 0, 0, 0.05),
0px 10px 20px rgba(0, 0, 0, 0.1),
;
}
.item { button {
display: block; align-items: center;
margin: 0; background-color: transparent;
width: 100%; display: flex;
gap: 0.25rem;
text-align: left; text-align: left;
background: transparent; width: 100%;
border-radius: 0.4rem;
border: 1px solid transparent; &:hover,
padding: 0.2rem 0.4rem; &:hover.is-selected {
background-color: var(--gray-3);
}
&.is-selected { &.is-selected {
border-color: #000; background-color: var(--gray-2);
}
} }
} }
</style> </style>

View File

@ -4,33 +4,33 @@ context('/src/Examples/Community/Vue/', () => {
}) })
it('should count the characters correctly', () => { it('should count the characters correctly', () => {
// check if count text is "44/280 characters" // check if count text is "44 / 280 characters"
cy.get('.character-count__text').should('have.text', '44/280 characters') cy.get('.character-count').should('contain', '44 / 280 characters')
// type in .tiptap // type in .tiptap
cy.get('.tiptap').type(' Hello World') cy.get('.tiptap').type(' Hello World')
cy.get('.character-count__text').should('have.text', '56/280 characters') cy.get('.character-count').should('contain', '56 / 280 characters')
// remove content from .tiptap and enter text // remove content from .tiptap and enter text
cy.get('.tiptap').type('{selectall}{backspace}Hello World') cy.get('.tiptap').type('{selectall}{backspace}Hello World')
cy.get('.character-count__text').should('have.text', '11/280 characters') cy.get('.character-count').should('contain', '11 / 280 characters')
}) })
it('should mention a user', () => { it('should mention a user', () => {
cy.get('.tiptap').type('{selectall}{backspace}@') cy.get('.tiptap').type('{selectall}{backspace}@')
// check if the mention autocomplete is visible // check if the mention autocomplete is visible
cy.get('.tippy-content .items').should('be.visible') cy.get('.tippy-content .dropdown-menu').should('be.visible')
// select the first user // select the first user
cy.get('.tippy-content .items .item').first().then($el => { cy.get('.tippy-content .dropdown-menu button').first().then($el => {
const name = $el.text() const name = $el.text()
$el.click() $el.click()
// check if the user is mentioned // check if the user is mentioned
cy.get('.tiptap').should('have.text', `@${name} `) cy.get('.tiptap').should('have.text', `@${name} `)
cy.get('.character-count__text').should('have.text', '2/280 characters') cy.get('.character-count').should('contain', '2 / 280 characters')
}) })
}) })

View File

@ -6,7 +6,6 @@
height="20" height="20"
width="20" width="20"
viewBox="0 0 20 20" viewBox="0 0 20 20"
class="character-count__graph"
> >
<circle <circle
r="10" r="10"
@ -32,7 +31,7 @@
/> />
</svg> </svg>
<div class="character-count__text">{{ editor.storage.characterCount.characters() }}/{{ limit }} characters</div> {{ editor.storage.characterCount.characters() }} / {{ limit }} characters
</div> </div>
</template> </template>
@ -97,43 +96,35 @@ export default {
<style lang="scss"> <style lang="scss">
/* Basic editor styles */ /* Basic editor styles */
.tiptap { .tiptap {
> * + * { :first-child {
margin-top: 0.75em; margin-top: 0;
} }
h1, .mention {
h2, background-color: var(--purple-light);
h3,
h4,
h5,
h6 {
line-height: 1.1;
}
}
.mention {
border: 1px solid #000;
border-radius: 0.4rem; border-radius: 0.4rem;
padding: 0.1rem 0.3rem;
box-decoration-break: clone; box-decoration-break: clone;
color: var(--purple);
padding: 0.1rem 0.3rem;
}
} }
/* Character count */
.character-count { .character-count {
margin-top: 1rem;
display: flex;
align-items: center; align-items: center;
color: #68CEF8; color: var(--gray-5);
display: flex;
font-size: 0.75rem;
gap: .5rem;
margin: 1.5rem;
&--warning { svg {
color: #FB5151; color: var(--purple);
} }
&__graph { &--warning,
margin-right: 0.5rem; &--warning svg {
} color: var(--red);
&__text {
color: #868e96;
} }
} }
</style> </style>

View File

@ -1,24 +1,109 @@
/* Basic editor styles */ /* Basic editor styles */
.tiptap { .tiptap {
> * + * { :first-child {
margin-top: 0.75em; margin-top: 0;
}
/* List styles */
ul,
ol {
padding: 0 1rem;
margin: 1.25rem 1rem 1.25rem 0.4rem;
li p {
margin-top: 0.25em;
margin-bottom: 0.25em;
}
}
/* Heading styles */
h1,
h2,
h3,
h4,
h5,
h6 {
line-height: 1.1;
margin-top: 2.5rem;
text-wrap: pretty;
}
h1,
h2 {
margin-top: 3.5rem;
margin-bottom: 1.5rem;
}
h1 {
font-size: 1.4rem;
}
h2 {
font-size: 1.2rem;
}
h3 {
font-size: 1.1rem;
}
h4,
h5,
h6 {
font-size: 1rem;
}
/* Code and preformatted text styles */
code {
background-color: var(--purple-light);
border-radius: 0.4rem;
color: var(--black);
font-size: 0.85rem;
padding: 0.25em 0.3em;
}
pre {
background: var(--black);
border-radius: 0.5rem;
color: var(--white);
font-family: 'JetBrainsMono', monospace;
margin: 1.5rem 0;
padding: 0.75rem 1rem;
code {
background: none;
color: inherit;
font-size: 0.8rem;
padding: 0;
}
}
blockquote {
border-left: 3px solid var(--gray-3);
margin: 1.5rem 0;
padding-left: 1rem;
}
hr {
border: none;
border-top: 1px solid var(--gray-2);
margin: 2rem 0;
}
/* Placeholder (at the top) */
/* p.is-editor-empty:first-child::before {
color: var(--gray-4);
content: attr(data-placeholder);
float: left;
height: 0;
pointer-events: none;
}*/
/* Placeholder (on every new line) */
.is-empty::before {
color: var(--gray-4);
content: attr(data-placeholder);
float: left;
height: 0;
pointer-events: none;
} }
} }
/* Placeholder (at the top) */
/*.tiptap p.is-editor-empty:first-child::before {
content: attr(data-placeholder);
float: left;
color: #ced4da;
pointer-events: none;
height: 0;
}*/
/* Placeholder (on every new line) */
.tiptap .is-empty::before {
content: attr(data-placeholder);
float: left;
color: #ced4da;
pointer-events: none;
height: 0;
}

View File

@ -60,26 +60,111 @@ export default {
<style lang="scss"> <style lang="scss">
/* Basic editor styles */ /* Basic editor styles */
.tiptap { .tiptap {
> * + * { :first-child {
margin-top: 0.75em; margin-top: 0;
}
/* List styles */
ul,
ol {
padding: 0 1rem;
margin: 1.25rem 1rem 1.25rem 0.4rem;
li p {
margin-top: 0.25em;
margin-bottom: 0.25em;
}
}
/* Heading styles */
h1,
h2,
h3,
h4,
h5,
h6 {
line-height: 1.1;
margin-top: 2.5rem;
text-wrap: pretty;
}
h1,
h2 {
margin-top: 3.5rem;
margin-bottom: 1.5rem;
}
h1 {
font-size: 1.4rem;
}
h2 {
font-size: 1.2rem;
}
h3 {
font-size: 1.1rem;
}
h4,
h5,
h6 {
font-size: 1rem;
}
/* Code and preformatted text styles */
code {
background-color: var(--purple-light);
border-radius: 0.4rem;
color: var(--black);
font-size: 0.85rem;
padding: 0.25em 0.3em;
}
pre {
background: var(--black);
border-radius: 0.5rem;
color: var(--white);
font-family: 'JetBrainsMono', monospace;
margin: 1.5rem 0;
padding: 0.75rem 1rem;
code {
background: none;
color: inherit;
font-size: 0.8rem;
padding: 0;
}
}
blockquote {
border-left: 3px solid var(--gray-3);
margin: 1.5rem 0;
padding-left: 1rem;
}
hr {
border: none;
border-top: 1px solid var(--gray-2);
margin: 2rem 0;
}
/* Placeholder (at the top) */
/* p.is-editor-empty:first-child::before {
color: var(--gray-4);
content: attr(data-placeholder);
float: left;
height: 0;
pointer-events: none;
}*/
/* Placeholder (on every new line) */
.is-empty::before {
color: var(--gray-4);
content: attr(data-placeholder);
float: left;
height: 0;
pointer-events: none;
} }
} }
/* Placeholder (at the top) */
/*.tiptap p.is-editor-empty:first-child::before {
content: attr(data-placeholder);
float: left;
color: #ced4da;
pointer-events: none;
height: 0;
}*/
/* Placeholder (on every new line) */
.tiptap .is-empty::before {
content: attr(data-placeholder);
float: left;
color: #ced4da;
pointer-events: none;
height: 0;
}
</style> </style>

View File

@ -1,24 +1,91 @@
/* Basic editor styles */ /* Basic editor styles */
.tiptap { .tiptap {
> * + * { :first-child {
margin-top: 0.75em; margin-top: 0;
}
/* List styles */
ul,
ol {
padding: 0 1rem;
margin: 1.25rem 1rem 1.25rem 0.4rem;
li p {
margin-top: 0.25em;
margin-bottom: 0.25em;
}
}
/* Heading styles */
h1,
h2,
h3,
h4,
h5,
h6 {
line-height: 1.1;
margin-top: 2.5rem;
text-wrap: pretty;
}
h1,
h2 {
margin-top: 3.5rem;
margin-bottom: 1.5rem;
}
h1 {
font-size: 1.4rem;
}
h2 {
font-size: 1.2rem;
}
h3 {
font-size: 1.1rem;
}
h4,
h5,
h6 {
font-size: 1rem;
}
/* Code and preformatted text styles */
code {
background-color: var(--purple-light);
border-radius: 0.4rem;
color: var(--black);
font-size: 0.85rem;
padding: 0.25em 0.3em;
}
pre {
background: var(--black);
border-radius: 0.5rem;
color: var(--white);
font-family: 'JetBrainsMono', monospace;
margin: 1.5rem 0;
padding: 0.75rem 1rem;
code {
background: none;
color: inherit;
font-size: 0.8rem;
padding: 0;
}
}
blockquote {
border-left: 3px solid var(--gray-3);
margin: 1.5rem 0;
padding-left: 1rem;
}
hr {
border: none;
border-top: 1px solid var(--gray-2);
margin: 2rem 0;
} }
} }
/* Placeholder (at the top) */
/*.tiptap p.is-editor-empty:first-child::before {
content: attr(data-placeholder);
float: left;
color: #ced4da;
pointer-events: none;
height: 0;
}*/
/* Placeholder (on every new line) */
.tiptap .is-empty::before {
content: attr(data-placeholder);
float: left;
color: #ced4da;
pointer-events: none;
height: 0;
}

View File

@ -15,7 +15,8 @@ const MenuBar = () => {
} }
return ( return (
<> <div className="control-group">
<div className="button-group">
<button <button
onClick={() => editor.chain().focus().toggleBold().run()} onClick={() => editor.chain().focus().toggleBold().run()}
disabled={ disabled={
@ -27,7 +28,7 @@ const MenuBar = () => {
} }
className={editor.isActive('bold') ? 'is-active' : ''} className={editor.isActive('bold') ? 'is-active' : ''}
> >
bold Bold
</button> </button>
<button <button
onClick={() => editor.chain().focus().toggleItalic().run()} onClick={() => editor.chain().focus().toggleItalic().run()}
@ -40,7 +41,7 @@ const MenuBar = () => {
} }
className={editor.isActive('italic') ? 'is-active' : ''} className={editor.isActive('italic') ? 'is-active' : ''}
> >
italic Italic
</button> </button>
<button <button
onClick={() => editor.chain().focus().toggleStrike().run()} onClick={() => editor.chain().focus().toggleStrike().run()}
@ -53,7 +54,7 @@ const MenuBar = () => {
} }
className={editor.isActive('strike') ? 'is-active' : ''} className={editor.isActive('strike') ? 'is-active' : ''}
> >
strike Strike
</button> </button>
<button <button
onClick={() => editor.chain().focus().toggleCode().run()} onClick={() => editor.chain().focus().toggleCode().run()}
@ -66,85 +67,85 @@ const MenuBar = () => {
} }
className={editor.isActive('code') ? 'is-active' : ''} className={editor.isActive('code') ? 'is-active' : ''}
> >
code Code
</button> </button>
<button onClick={() => editor.chain().focus().unsetAllMarks().run()}> <button onClick={() => editor.chain().focus().unsetAllMarks().run()}>
clear marks Clear marks
</button> </button>
<button onClick={() => editor.chain().focus().clearNodes().run()}> <button onClick={() => editor.chain().focus().clearNodes().run()}>
clear nodes Clear nodes
</button> </button>
<button <button
onClick={() => editor.chain().focus().setParagraph().run()} onClick={() => editor.chain().focus().setParagraph().run()}
className={editor.isActive('paragraph') ? 'is-active' : ''} className={editor.isActive('paragraph') ? 'is-active' : ''}
> >
paragraph Paragraph
</button> </button>
<button <button
onClick={() => editor.chain().focus().toggleHeading({ level: 1 }).run()} onClick={() => editor.chain().focus().toggleHeading({ level: 1 }).run()}
className={editor.isActive('heading', { level: 1 }) ? 'is-active' : ''} className={editor.isActive('heading', { level: 1 }) ? 'is-active' : ''}
> >
h1 H1
</button> </button>
<button <button
onClick={() => editor.chain().focus().toggleHeading({ level: 2 }).run()} onClick={() => editor.chain().focus().toggleHeading({ level: 2 }).run()}
className={editor.isActive('heading', { level: 2 }) ? 'is-active' : ''} className={editor.isActive('heading', { level: 2 }) ? 'is-active' : ''}
> >
h2 H2
</button> </button>
<button <button
onClick={() => editor.chain().focus().toggleHeading({ level: 3 }).run()} onClick={() => editor.chain().focus().toggleHeading({ level: 3 }).run()}
className={editor.isActive('heading', { level: 3 }) ? 'is-active' : ''} className={editor.isActive('heading', { level: 3 }) ? 'is-active' : ''}
> >
h3 H3
</button> </button>
<button <button
onClick={() => editor.chain().focus().toggleHeading({ level: 4 }).run()} onClick={() => editor.chain().focus().toggleHeading({ level: 4 }).run()}
className={editor.isActive('heading', { level: 4 }) ? 'is-active' : ''} className={editor.isActive('heading', { level: 4 }) ? 'is-active' : ''}
> >
h4 H4
</button> </button>
<button <button
onClick={() => editor.chain().focus().toggleHeading({ level: 5 }).run()} onClick={() => editor.chain().focus().toggleHeading({ level: 5 }).run()}
className={editor.isActive('heading', { level: 5 }) ? 'is-active' : ''} className={editor.isActive('heading', { level: 5 }) ? 'is-active' : ''}
> >
h5 H5
</button> </button>
<button <button
onClick={() => editor.chain().focus().toggleHeading({ level: 6 }).run()} onClick={() => editor.chain().focus().toggleHeading({ level: 6 }).run()}
className={editor.isActive('heading', { level: 6 }) ? 'is-active' : ''} className={editor.isActive('heading', { level: 6 }) ? 'is-active' : ''}
> >
h6 H6
</button> </button>
<button <button
onClick={() => editor.chain().focus().toggleBulletList().run()} onClick={() => editor.chain().focus().toggleBulletList().run()}
className={editor.isActive('bulletList') ? 'is-active' : ''} className={editor.isActive('bulletList') ? 'is-active' : ''}
> >
bullet list Bullet list
</button> </button>
<button <button
onClick={() => editor.chain().focus().toggleOrderedList().run()} onClick={() => editor.chain().focus().toggleOrderedList().run()}
className={editor.isActive('orderedList') ? 'is-active' : ''} className={editor.isActive('orderedList') ? 'is-active' : ''}
> >
ordered list Ordered list
</button> </button>
<button <button
onClick={() => editor.chain().focus().toggleCodeBlock().run()} onClick={() => editor.chain().focus().toggleCodeBlock().run()}
className={editor.isActive('codeBlock') ? 'is-active' : ''} className={editor.isActive('codeBlock') ? 'is-active' : ''}
> >
code block Code block
</button> </button>
<button <button
onClick={() => editor.chain().focus().toggleBlockquote().run()} onClick={() => editor.chain().focus().toggleBlockquote().run()}
className={editor.isActive('blockquote') ? 'is-active' : ''} className={editor.isActive('blockquote') ? 'is-active' : ''}
> >
blockquote Blockquote
</button> </button>
<button onClick={() => editor.chain().focus().setHorizontalRule().run()}> <button onClick={() => editor.chain().focus().setHorizontalRule().run()}>
horizontal rule Horizontal rule
</button> </button>
<button onClick={() => editor.chain().focus().setHardBreak().run()}> <button onClick={() => editor.chain().focus().setHardBreak().run()}>
hard break Hard break
</button> </button>
<button <button
onClick={() => editor.chain().focus().undo().run()} onClick={() => editor.chain().focus().undo().run()}
@ -156,7 +157,7 @@ const MenuBar = () => {
.run() .run()
} }
> >
undo Undo
</button> </button>
<button <button
onClick={() => editor.chain().focus().redo().run()} onClick={() => editor.chain().focus().redo().run()}
@ -168,15 +169,16 @@ const MenuBar = () => {
.run() .run()
} }
> >
redo Redo
</button> </button>
<button <button
onClick={() => editor.chain().focus().setColor('#958DF1').run()} onClick={() => editor.chain().focus().setColor('#958DF1').run()}
className={editor.isActive('textStyle', { color: '#958DF1' }) ? 'is-active' : ''} className={editor.isActive('textStyle', { color: '#958DF1' }) ? 'is-active' : ''}
> >
purple Purple
</button> </button>
</> </div>
</div>
) )
} }
@ -200,7 +202,7 @@ const content = `
Hi there, Hi there,
</h2> </h2>
<p> <p>
this is a <em>basic</em> example of <strong>tiptap</strong>. Sure, there are all kind of basic text styles youd probably expect from a text editor. But wait until you see the lists: this is a <em>basic</em> example of <strong>Tiptap</strong>. Sure, there are all kind of basic text styles youd probably expect from a text editor. But wait until you see the lists:
</p> </p>
<ul> <ul>
<li> <li>
@ -214,7 +216,7 @@ const content = `
Isnt that great? And all of that is editable. But wait, theres more. Lets try a code block: Isnt that great? And all of that is editable. But wait, theres more. Lets try a code block:
</p> </p>
<pre><code class="language-css">body { <pre><code class="language-css">body {
display: none; display: none;
}</code></pre> }</code></pre>
<p> <p>
I know, I know, this is impressive. Its only the tip of the iceberg though. Give it a try and click a little bit around. Dont forget to check the other examples too. I know, I know, this is impressive. Its only the tip of the iceberg though. Give it a try and click a little bit around. Dont forget to check the other examples too.

View File

@ -21,41 +21,41 @@ context('/src/Examples/Default/React/', () => {
}) })
const buttonMarks = [ const buttonMarks = [
{ label: 'bold', tag: 'strong' }, { label: 'Bold', tag: 'strong' },
{ label: 'italic', tag: 'em' }, { label: 'Italic', tag: 'em' },
{ label: 'strike', tag: 's' }, { label: 'Strike', tag: 's' },
] ]
buttonMarks.forEach(m => { buttonMarks.forEach(m => {
it(`should disable ${m.label} when the code tag is enabled for cursor`, () => { it(`should disable ${m.label} when the code tag is enabled for cursor`, () => {
cy.get('.tiptap').type('{selectall}Hello world') cy.get('.tiptap').type('{selectall}Hello world')
cy.get('button').contains('code').click() cy.get('button').contains('Code').click()
cy.get('button').contains(m.label).should('be.disabled') cy.get('button').contains(m.label).should('be.disabled')
}) })
it(`should enable ${m.label} when the code tag is disabled for cursor`, () => { it(`should enable ${m.label} when the code tag is disabled for cursor`, () => {
cy.get('.tiptap').type('{selectall}Hello world') cy.get('.tiptap').type('{selectall}Hello world')
cy.get('button').contains('code').click() cy.get('button').contains('Code').click()
cy.get('button').contains('code').click() cy.get('button').contains('Code').click()
cy.get('button').contains(m.label).should('not.be.disabled') cy.get('button').contains(m.label).should('not.be.disabled')
}) })
it(`should disable ${m.label} when the code tag is enabled for selection`, () => { it(`should disable ${m.label} when the code tag is enabled for selection`, () => {
cy.get('.tiptap').type('{selectall}Hello world{selectall}') cy.get('.tiptap').type('{selectall}Hello world{selectall}')
cy.get('button').contains('code').click() cy.get('button').contains('Code').click()
cy.get('button').contains(m.label).should('be.disabled') cy.get('button').contains(m.label).should('be.disabled')
}) })
it(`should enable ${m.label} when the code tag is disabled for selection`, () => { it(`should enable ${m.label} when the code tag is disabled for selection`, () => {
cy.get('.tiptap').type('{selectall}Hello world{selectall}') cy.get('.tiptap').type('{selectall}Hello world{selectall}')
cy.get('button').contains('code').click() cy.get('button').contains('Code').click()
cy.get('button').contains('code').click() cy.get('button').contains('Code').click()
cy.get('button').contains(m.label).should('not.be.disabled') cy.get('button').contains(m.label).should('not.be.disabled')
}) })
it(`should apply ${m.label} when the button is pressed`, () => { it(`should apply ${m.label} when the button is pressed`, () => {
cy.get('.tiptap').type('{selectall}Hello world') cy.get('.tiptap').type('{selectall}Hello world')
cy.get('button').contains('paragraph').click() cy.get('button').contains('Paragraph').click()
cy.get('.tiptap').type('{selectall}') cy.get('.tiptap').type('{selectall}')
cy.get('button').contains(m.label).click() cy.get('button').contains(m.label).click()
cy.get(`.tiptap ${m.tag}`).should('exist').should('have.text', 'Hello world') cy.get(`.tiptap ${m.tag}`).should('exist').should('have.text', 'Hello world')
@ -64,40 +64,40 @@ context('/src/Examples/Default/React/', () => {
it('should clear marks when the button is pressed', () => { it('should clear marks when the button is pressed', () => {
cy.get('.tiptap').type('{selectall}Hello world') cy.get('.tiptap').type('{selectall}Hello world')
cy.get('button').contains('paragraph').click() cy.get('button').contains('Paragraph').click()
cy.get('.tiptap').type('{selectall}') cy.get('.tiptap').type('{selectall}')
cy.get('button').contains('bold').click() cy.get('button').contains('Bold').click()
cy.get('.tiptap strong').should('exist').should('have.text', 'Hello world') cy.get('.tiptap strong').should('exist').should('have.text', 'Hello world')
cy.get('button').contains('clear marks').click() cy.get('button').contains('Clear marks').click()
cy.get('.tiptap strong').should('not.exist') cy.get('.tiptap strong').should('not.exist')
}) })
it('should clear nodes when the button is pressed', () => { it('should clear nodes when the button is pressed', () => {
cy.get('.tiptap').type('{selectall}Hello world') cy.get('.tiptap').type('{selectall}Hello world')
cy.get('button').contains('bullet list').click() cy.get('button').contains('Bullet list').click()
cy.get('.tiptap ul').should('exist').should('have.text', 'Hello world') cy.get('.tiptap ul').should('exist').should('have.text', 'Hello world')
cy.get('.tiptap').type('{enter}A second item{enter}A third item{selectall}') cy.get('.tiptap').type('{enter}A second item{enter}A third item{selectall}')
cy.get('button').contains('clear nodes').click() cy.get('button').contains('Clear nodes').click()
cy.get('.tiptap ul').should('not.exist') cy.get('.tiptap ul').should('not.exist')
cy.get('.tiptap p').should('have.length', 3) cy.get('.tiptap p').should('have.length', 3)
}) })
const buttonNodes = [ const buttonNodes = [
{ label: 'h1', tag: 'h1' }, { label: 'H1', tag: 'h1' },
{ label: 'h2', tag: 'h2' }, { label: 'H2', tag: 'h2' },
{ label: 'h3', tag: 'h3' }, { label: 'H3', tag: 'h3' },
{ label: 'h4', tag: 'h4' }, { label: 'H4', tag: 'h4' },
{ label: 'h5', tag: 'h5' }, { label: 'H5', tag: 'h5' },
{ label: 'h6', tag: 'h6' }, { label: 'H6', tag: 'h6' },
{ label: 'bullet list', tag: 'ul' }, { label: 'Bullet list', tag: 'ul' },
{ label: 'ordered list', tag: 'ol' }, { label: 'Ordered list', tag: 'ol' },
{ label: 'code block', tag: 'pre code' }, { label: 'Code block', tag: 'pre code' },
{ label: 'blockquote', tag: 'blockquote' }, { label: 'Blockquote', tag: 'blockquote' },
] ]
buttonNodes.forEach(n => { buttonNodes.forEach(n => {
it(`should set ${n.label} when the button is pressed`, () => { it(`should set ${n.label} when the button is pressed`, () => {
cy.get('button').contains('paragraph').click() cy.get('button').contains('Paragraph').click()
cy.get('.tiptap').type('{selectall}Hello world{selectall}') cy.get('.tiptap').type('{selectall}Hello world{selectall}')
cy.get('button').contains(n.label).click() cy.get('button').contains(n.label).click()
@ -109,35 +109,35 @@ context('/src/Examples/Default/React/', () => {
it('should add a hr when on the same line as a node', () => { it('should add a hr when on the same line as a node', () => {
cy.get('.tiptap').type('{rightArrow}') cy.get('.tiptap').type('{rightArrow}')
cy.get('button').contains('horizontal rule').click() cy.get('button').contains('Horizontal rule').click()
cy.get('.tiptap hr').should('exist') cy.get('.tiptap hr').should('exist')
cy.get('.tiptap h1').should('exist') cy.get('.tiptap h1').should('exist')
}) })
it('should add a hr when on a new line', () => { it('should add a hr when on a new line', () => {
cy.get('.tiptap').type('{rightArrow}{enter}') cy.get('.tiptap').type('{rightArrow}{enter}')
cy.get('button').contains('horizontal rule').click() cy.get('button').contains('Horizontal rule').click()
cy.get('.tiptap hr').should('exist') cy.get('.tiptap hr').should('exist')
cy.get('.tiptap h1').should('exist') cy.get('.tiptap h1').should('exist')
}) })
it('should add a br', () => { it('should add a br', () => {
cy.get('.tiptap').type('{rightArrow}') cy.get('.tiptap').type('{rightArrow}')
cy.get('button').contains('hard break').click() cy.get('button').contains('Hard break').click()
cy.get('.tiptap h1 br').should('exist') cy.get('.tiptap h1 br').should('exist')
}) })
it('should undo', () => { it('should undo', () => {
cy.get('.tiptap').type('{selectall}{backspace}') cy.get('.tiptap').type('{selectall}{backspace}')
cy.get('button').contains('undo').click() cy.get('button').contains('Undo').click()
cy.get('.tiptap').should('contain', 'Hello world') cy.get('.tiptap').should('contain', 'Hello world')
}) })
it('should redo', () => { it('should redo', () => {
cy.get('.tiptap').type('{selectall}{backspace}') cy.get('.tiptap').type('{selectall}{backspace}')
cy.get('button').contains('undo').click() cy.get('button').contains('Undo').click()
cy.get('.tiptap').should('contain', 'Hello world') cy.get('.tiptap').should('contain', 'Hello world')
cy.get('button').contains('redo').click() cy.get('button').contains('Redo').click()
cy.get('.tiptap').should('not.contain', 'Hello world') cy.get('.tiptap').should('not.contain', 'Hello world')
}) })
}) })

View File

@ -1,14 +1,22 @@
/* Basic editor styles */ /* Basic editor styles */
.tiptap { .tiptap {
> * + * { :first-child {
margin-top: 0.75em; margin-top: 0;
} }
/* List styles */
ul, ul,
ol { ol {
padding: 0 1rem; padding: 0 1rem;
margin: 1.25rem 1rem 1.25rem 0.4rem;
li p {
margin-top: 0.25em;
margin-bottom: 0.25em;
}
} }
/* Heading styles */
h1, h1,
h2, h2,
h3, h3,
@ -16,41 +24,68 @@
h5, h5,
h6 { h6 {
line-height: 1.1; line-height: 1.1;
margin-top: 2.5rem;
text-wrap: pretty;
} }
h1,
h2 {
margin-top: 3.5rem;
margin-bottom: 1.5rem;
}
h1 {
font-size: 1.4rem;
}
h2 {
font-size: 1.2rem;
}
h3 {
font-size: 1.1rem;
}
h4,
h5,
h6 {
font-size: 1rem;
}
/* Code and preformatted text styles */
code { code {
background-color: rgba(#616161, 0.1); background-color: var(--purple-light);
color: #616161; border-radius: 0.4rem;
color: var(--black);
font-size: 0.85rem;
padding: 0.25em 0.3em;
} }
pre { pre {
background: #0D0D0D; background: var(--black);
color: #FFF;
font-family: 'JetBrainsMono', monospace;
padding: 0.75rem 1rem;
border-radius: 0.5rem; border-radius: 0.5rem;
color: var(--white);
font-family: 'JetBrainsMono', monospace;
margin: 1.5rem 0;
padding: 0.75rem 1rem;
code { code {
color: inherit;
padding: 0;
background: none; background: none;
color: inherit;
font-size: 0.8rem; font-size: 0.8rem;
padding: 0;
} }
} }
img {
max-width: 100%;
height: auto;
}
blockquote { blockquote {
border-left: 3px solid var(--gray-3);
margin: 1.5rem 0;
padding-left: 1rem; padding-left: 1rem;
border-left: 2px solid rgba(#0D0D0D, 0.1);
} }
hr { hr {
border: none; border: none;
border-top: 2px solid rgba(#0D0D0D, 0.1); border-top: 1px solid var(--gray-2);
margin: 2rem 0; margin: 2rem 0;
} }
} }

View File

@ -21,15 +21,15 @@ context('/src/Examples/Default/React/', () => {
}) })
const buttonMarks = [ const buttonMarks = [
{ label: 'bold', tag: 'strong' }, { label: 'Bold', tag: 'strong' },
{ label: 'italic', tag: 'em' }, { label: 'Italic', tag: 'em' },
{ label: 'strike', tag: 's' }, { label: 'Strike', tag: 's' },
] ]
buttonMarks.forEach(m => { buttonMarks.forEach(m => {
it(`should apply ${m.label} when the button is pressed`, () => { it(`should apply ${m.label} when the button is pressed`, () => {
cy.get('.tiptap').type('{selectall}Hello world') cy.get('.tiptap').type('{selectall}Hello world')
cy.get('button').contains('paragraph').click() cy.get('button').contains('Paragraph').click()
cy.get('.tiptap').type('{selectall}') cy.get('.tiptap').type('{selectall}')
cy.get('button').contains(m.label).click() cy.get('button').contains(m.label).click()
cy.get(`.tiptap ${m.tag}`).should('exist').should('have.text', 'Hello world') cy.get(`.tiptap ${m.tag}`).should('exist').should('have.text', 'Hello world')
@ -38,40 +38,40 @@ context('/src/Examples/Default/React/', () => {
it('should clear marks when the button is pressed', () => { it('should clear marks when the button is pressed', () => {
cy.get('.tiptap').type('{selectall}Hello world') cy.get('.tiptap').type('{selectall}Hello world')
cy.get('button').contains('paragraph').click() cy.get('button').contains('Paragraph').click()
cy.get('.tiptap').type('{selectall}') cy.get('.tiptap').type('{selectall}')
cy.get('button').contains('bold').click() cy.get('button').contains('Bold').click()
cy.get('.tiptap strong').should('exist').should('have.text', 'Hello world') cy.get('.tiptap strong').should('exist').should('have.text', 'Hello world')
cy.get('button').contains('clear marks').click() cy.get('button').contains('Clear marks').click()
cy.get('.tiptap strong').should('not.exist') cy.get('.tiptap strong').should('not.exist')
}) })
it('should clear nodes when the button is pressed', () => { it('should clear nodes when the button is pressed', () => {
cy.get('.tiptap').type('{selectall}Hello world') cy.get('.tiptap').type('{selectall}Hello world')
cy.get('button').contains('bullet list').click() cy.get('button').contains('Bullet list').click()
cy.get('.tiptap ul').should('exist').should('have.text', 'Hello world') cy.get('.tiptap ul').should('exist').should('have.text', 'Hello world')
cy.get('.tiptap').type('{enter}A second item{enter}A third item{selectall}') cy.get('.tiptap').type('{enter}A second item{enter}A third item{selectall}')
cy.get('button').contains('clear nodes').click() cy.get('button').contains('Clear nodes').click()
cy.get('.tiptap ul').should('not.exist') cy.get('.tiptap ul').should('not.exist')
cy.get('.tiptap p').should('have.length', 3) cy.get('.tiptap p').should('have.length', 3)
}) })
const buttonNodes = [ const buttonNodes = [
{ label: 'h1', tag: 'h1' }, { label: 'H1', tag: 'h1' },
{ label: 'h2', tag: 'h2' }, { label: 'H2', tag: 'h2' },
{ label: 'h3', tag: 'h3' }, { label: 'H3', tag: 'h3' },
{ label: 'h4', tag: 'h4' }, { label: 'H4', tag: 'h4' },
{ label: 'h5', tag: 'h5' }, { label: 'H5', tag: 'h5' },
{ label: 'h6', tag: 'h6' }, { label: 'H6', tag: 'h6' },
{ label: 'bullet list', tag: 'ul' }, { label: 'Bullet list', tag: 'ul' },
{ label: 'ordered list', tag: 'ol' }, { label: 'Ordered list', tag: 'ol' },
{ label: 'code block', tag: 'pre code' }, { label: 'Code block', tag: 'pre code' },
{ label: 'blockquote', tag: 'blockquote' }, { label: 'Blockquote', tag: 'blockquote' },
] ]
buttonNodes.forEach(n => { buttonNodes.forEach(n => {
it(`should set ${n.label} when the button is pressed`, () => { it(`should set ${n.label} when the button is pressed`, () => {
cy.get('button').contains('paragraph').click() cy.get('button').contains('Paragraph').click()
cy.get('.tiptap').type('{selectall}Hello world{selectall}') cy.get('.tiptap').type('{selectall}Hello world{selectall}')
cy.get('button').contains(n.label).click() cy.get('button').contains(n.label).click()
@ -83,35 +83,35 @@ context('/src/Examples/Default/React/', () => {
it('should add a hr when on the same line as a node', () => { it('should add a hr when on the same line as a node', () => {
cy.get('.tiptap').type('{rightArrow}') cy.get('.tiptap').type('{rightArrow}')
cy.get('button').contains('horizontal rule').click() cy.get('button').contains('Horizontal rule').click()
cy.get('.tiptap hr').should('exist') cy.get('.tiptap hr').should('exist')
cy.get('.tiptap h1').should('exist') cy.get('.tiptap h1').should('exist')
}) })
it('should add a hr when on a new line', () => { it('should add a hr when on a new line', () => {
cy.get('.tiptap').type('{rightArrow}{enter}') cy.get('.tiptap').type('{rightArrow}{enter}')
cy.get('button').contains('horizontal rule').click() cy.get('button').contains('Horizontal rule').click()
cy.get('.tiptap hr').should('exist') cy.get('.tiptap hr').should('exist')
cy.get('.tiptap h1').should('exist') cy.get('.tiptap h1').should('exist')
}) })
it('should add a br', () => { it('should add a br', () => {
cy.get('.tiptap').type('{rightArrow}') cy.get('.tiptap').type('{rightArrow}')
cy.get('button').contains('hard break').click() cy.get('button').contains('Hard break').click()
cy.get('.tiptap h1 br').should('exist') cy.get('.tiptap h1 br').should('exist')
}) })
it('should undo', () => { it('should undo', () => {
cy.get('.tiptap').type('{selectall}{backspace}') cy.get('.tiptap').type('{selectall}{backspace}')
cy.get('button').contains('undo').click() cy.get('button').contains('Undo').click()
cy.get('.tiptap').should('contain', 'Hello world') cy.get('.tiptap').should('contain', 'Hello world')
}) })
it('should redo', () => { it('should redo', () => {
cy.get('.tiptap').type('{selectall}{backspace}') cy.get('.tiptap').type('{selectall}{backspace}')
cy.get('button').contains('undo').click() cy.get('button').contains('Undo').click()
cy.get('.tiptap').should('contain', 'Hello world') cy.get('.tiptap').should('contain', 'Hello world')
cy.get('button').contains('redo').click() cy.get('button').contains('Redo').click()
cy.get('.tiptap').should('not.contain', 'Hello world') cy.get('.tiptap').should('not.contain', 'Hello world')
}) })
}) })

View File

@ -1,6 +1,9 @@
<script> <script>
import "./styles.scss"; import "./styles.scss";
import { Color } from '@tiptap/extension-color'
import ListItem from '@tiptap/extension-list-item'
import TextStyle from '@tiptap/extension-text-style'
import StarterKit from "@tiptap/starter-kit"; import StarterKit from "@tiptap/starter-kit";
import { Editor } from "@tiptap/core"; import { Editor } from "@tiptap/core";
import { onMount } from "svelte"; import { onMount } from "svelte";
@ -11,13 +14,17 @@
onMount(() => { onMount(() => {
editor = new Editor({ editor = new Editor({
element: element, element: element,
extensions: [StarterKit], extensions: [
Color.configure({ types: [TextStyle.name, ListItem.name] }),
TextStyle.configure({ types: [ListItem.name] }),
StarterKit,
],
content: ` content: `
<h2> <h2>
Hi there, Hi there,
</h2> </h2>
<p> <p>
this is a <em>basic</em> example of <strong>tiptap</strong>. Sure, there are all kind of basic text styles youd probably expect from a text editor. But wait until you see the lists: this is a <em>basic</em> example of <strong>Tiptap</strong>. Sure, there are all kind of basic text styles youd probably expect from a text editor. But wait until you see the lists:
</p> </p>
<ul> <ul>
<li> <li>
@ -32,7 +39,7 @@
</p> </p>
<pre><code class="language-css">body { <pre><code class="language-css">body {
display: none; display: none;
}</code></pre> }</code></pre>
<p> <p>
I know, I know, this is impressive. Its only the tip of the iceberg though. Give it a try and click a little bit around. Dont forget to check the other examples too. I know, I know, this is impressive. Its only the tip of the iceberg though. Give it a try and click a little bit around. Dont forget to check the other examples too.
</p> </p>
@ -51,119 +58,125 @@
</script> </script>
{#if editor} {#if editor}
<div> <div class="control-group">
<div> <div class="button-group">
<button <button
on:click={() => console.log && editor.chain().focus().toggleBold().run()} on:click={() => console.log && editor.chain().focus().toggleBold().run()}
disabled={!editor.can().chain().focus().toggleBold().run()} disabled={!editor.can().chain().focus().toggleBold().run()}
class={editor.isActive("bold") ? "is-active" : ""} class={editor.isActive("bold") ? "is-active" : ""}
> >
bold Bold
</button> </button>
<button <button
on:click={() => editor.chain().focus().toggleItalic().run()} on:click={() => editor.chain().focus().toggleItalic().run()}
disabled={!editor.can().chain().focus().toggleItalic().run()} disabled={!editor.can().chain().focus().toggleItalic().run()}
class={editor.isActive("italic") ? "is-active" : ""} class={editor.isActive("italic") ? "is-active" : ""}
> >
italic Italic
</button> </button>
<button <button
on:click={() => editor.chain().focus().toggleStrike().run()} on:click={() => editor.chain().focus().toggleStrike().run()}
disabled={!editor.can().chain().focus().toggleStrike().run()} disabled={!editor.can().chain().focus().toggleStrike().run()}
class={editor.isActive("strike") ? "is-active" : ""} class={editor.isActive("strike") ? "is-active" : ""}
> >
strike Strike
</button> </button>
<button <button
on:click={() => editor.chain().focus().toggleCode().run()} on:click={() => editor.chain().focus().toggleCode().run()}
disabled={!editor.can().chain().focus().toggleCode().run()} disabled={!editor.can().chain().focus().toggleCode().run()}
class={editor.isActive("code") ? "is-active" : ""} class={editor.isActive("code") ? "is-active" : ""}
> >
code Code
</button> </button>
<button on:click={() => editor.chain().focus().unsetAllMarks().run()}> clear marks </button> <button on:click={() => editor.chain().focus().unsetAllMarks().run()}>Clear marks</button>
<button on:click={() => editor.chain().focus().clearNodes().run()}> clear nodes </button> <button on:click={() => editor.chain().focus().clearNodes().run()}>Clear nodes</button>
<button <button
on:click={() => editor.chain().focus().setParagraph().run()} on:click={() => editor.chain().focus().setParagraph().run()}
class={editor.isActive("paragraph") ? "is-active" : ""} class={editor.isActive("paragraph") ? "is-active" : ""}
> >
paragraph Paragraph
</button> </button>
<button <button
on:click={() => editor.chain().focus().toggleHeading({ level: 1 }).run()} on:click={() => editor.chain().focus().toggleHeading({ level: 1 }).run()}
class={editor.isActive("heading", { level: 1 }) ? "is-active" : ""} class={editor.isActive("heading", { level: 1 }) ? "is-active" : ""}
> >
h1 H1
</button> </button>
<button <button
on:click={() => editor.chain().focus().toggleHeading({ level: 2 }).run()} on:click={() => editor.chain().focus().toggleHeading({ level: 2 }).run()}
class={editor.isActive("heading", { level: 2 }) ? "is-active" : ""} class={editor.isActive("heading", { level: 2 }) ? "is-active" : ""}
> >
h2 H2
</button> </button>
<button <button
on:click={() => editor.chain().focus().toggleHeading({ level: 3 }).run()} on:click={() => editor.chain().focus().toggleHeading({ level: 3 }).run()}
class={editor.isActive("heading", { level: 3 }) ? "is-active" : ""} class={editor.isActive("heading", { level: 3 }) ? "is-active" : ""}
> >
h3 H3
</button> </button>
<button <button
on:click={() => editor.chain().focus().toggleHeading({ level: 4 }).run()} on:click={() => editor.chain().focus().toggleHeading({ level: 4 }).run()}
class={editor.isActive("heading", { level: 4 }) ? "is-active" : ""} class={editor.isActive("heading", { level: 4 }) ? "is-active" : ""}
> >
h4 H4
</button> </button>
<button <button
on:click={() => editor.chain().focus().toggleHeading({ level: 5 }).run()} on:click={() => editor.chain().focus().toggleHeading({ level: 5 }).run()}
class={editor.isActive("heading", { level: 5 }) ? "is-active" : ""} class={editor.isActive("heading", { level: 5 }) ? "is-active" : ""}
> >
h5 H5
</button> </button>
<button <button
on:click={() => editor.chain().focus().toggleHeading({ level: 6 }).run()} on:click={() => editor.chain().focus().toggleHeading({ level: 6 }).run()}
class={editor.isActive("heading", { level: 6 }) ? "is-active" : ""} class={editor.isActive("heading", { level: 6 }) ? "is-active" : ""}
> >
h6 H6
</button> </button>
<button <button
on:click={() => editor.chain().focus().toggleBulletList().run()} on:click={() => editor.chain().focus().toggleBulletList().run()}
class={editor.isActive("bulletList") ? "is-active" : ""} class={editor.isActive("bulletList") ? "is-active" : ""}
> >
bullet list Bullet list
</button> </button>
<button <button
on:click={() => editor.chain().focus().toggleOrderedList().run()} on:click={() => editor.chain().focus().toggleOrderedList().run()}
class={editor.isActive("orderedList") ? "is-active" : ""} class={editor.isActive("orderedList") ? "is-active" : ""}
> >
ordered list Ordered list
</button> </button>
<button <button
on:click={() => editor.chain().focus().toggleCodeBlock().run()} on:click={() => editor.chain().focus().toggleCodeBlock().run()}
class={editor.isActive("codeBlock") ? "is-active" : ""} class={editor.isActive("codeBlock") ? "is-active" : ""}
> >
code block Code block
</button> </button>
<button <button
on:click={() => editor.chain().focus().toggleBlockquote().run()} on:click={() => editor.chain().focus().toggleBlockquote().run()}
class={editor.isActive("blockquote") ? "is-active" : ""} class={editor.isActive("blockquote") ? "is-active" : ""}
> >
blockquote Blockquote
</button> </button>
<button on:click={() => editor.chain().focus().setHorizontalRule().run()}> <button on:click={() => editor.chain().focus().setHorizontalRule().run()}>
horizontal rule Horizontal rule
</button> </button>
<button on:click={() => editor.chain().focus().setHardBreak().run()}> hard break </button> <button on:click={() => editor.chain().focus().setHardBreak().run()}>Hard break</button>
<button <button
on:click={() => editor.chain().focus().undo().run()} on:click={() => editor.chain().focus().undo().run()}
disabled={!editor.can().chain().focus().undo().run()} disabled={!editor.can().chain().focus().undo().run()}
> >
undo Undo
</button> </button>
<button <button
on:click={() => editor.chain().focus().redo().run()} on:click={() => editor.chain().focus().redo().run()}
disabled={!editor.can().chain().focus().redo().run()} disabled={!editor.can().chain().focus().redo().run()}
> >
redo Redo
</button>
<button
on:click={() => editor.chain().focus().setColor('#958DF1').run()}
class={editor.isActive('textStyle', { color: '#958DF1' }) ? 'is-active' : ''}
>
Purple
</button> </button>
</div> </div>
</div> </div>

View File

@ -1,14 +1,22 @@
/* Basic editor styles */ /* Basic editor styles */
.tiptap { .tiptap {
> * + * { :first-child {
margin-top: 0.75em; margin-top: 0;
} }
/* List styles */
ul, ul,
ol { ol {
padding: 0 1rem; padding: 0 1rem;
margin: 1.25rem 1rem 1.25rem 0.4rem;
li p {
margin-top: 0.25em;
margin-bottom: 0.25em;
}
} }
/* Heading styles */
h1, h1,
h2, h2,
h3, h3,
@ -16,41 +24,68 @@
h5, h5,
h6 { h6 {
line-height: 1.1; line-height: 1.1;
margin-top: 2.5rem;
text-wrap: pretty;
} }
h1,
h2 {
margin-top: 3.5rem;
margin-bottom: 1.5rem;
}
h1 {
font-size: 1.4rem;
}
h2 {
font-size: 1.2rem;
}
h3 {
font-size: 1.1rem;
}
h4,
h5,
h6 {
font-size: 1rem;
}
/* Code and preformatted text styles */
code { code {
background-color: rgba(#616161, 0.1); background-color: var(--purple-light);
color: #616161; border-radius: 0.4rem;
color: var(--black);
font-size: 0.85rem;
padding: 0.25em 0.3em;
} }
pre { pre {
background: #0D0D0D; background: var(--black);
color: #FFF;
font-family: 'JetBrainsMono', monospace;
padding: 0.75rem 1rem;
border-radius: 0.5rem; border-radius: 0.5rem;
color: var(--white);
font-family: 'JetBrainsMono', monospace;
margin: 1.5rem 0;
padding: 0.75rem 1rem;
code { code {
color: inherit;
padding: 0;
background: none; background: none;
color: inherit;
font-size: 0.8rem; font-size: 0.8rem;
padding: 0;
} }
} }
img {
max-width: 100%;
height: auto;
}
blockquote { blockquote {
border-left: 3px solid var(--gray-3);
margin: 1.5rem 0;
padding-left: 1rem; padding-left: 1rem;
border-left: 2px solid rgba(#0D0D0D, 0.1);
} }
hr { hr {
border: none; border: none;
border-top: 2px solid rgba(#0D0D0D, 0.1); border-top: 1px solid var(--gray-2);
margin: 2rem 0; margin: 2rem 0;
} }
} }

View File

@ -21,15 +21,15 @@ context('/src/Examples/Default/Vue/', () => {
}) })
const buttonMarks = [ const buttonMarks = [
{ label: 'bold', tag: 'strong' }, { label: 'Bold', tag: 'strong' },
{ label: 'italic', tag: 'em' }, { label: 'Italic', tag: 'em' },
{ label: 'strike', tag: 's' }, { label: 'Strike', tag: 's' },
] ]
buttonMarks.forEach(m => { buttonMarks.forEach(m => {
it(`should apply ${m.label} when the button is pressed`, () => { it(`should apply ${m.label} when the button is pressed`, () => {
cy.get('.tiptap').type('{selectall}Hello world') cy.get('.tiptap').type('{selectall}Hello world')
cy.get('button').contains('paragraph').click() cy.get('button').contains('Paragraph').click()
cy.get('.tiptap').type('{selectall}') cy.get('.tiptap').type('{selectall}')
cy.get('button').contains(m.label).click() cy.get('button').contains(m.label).click()
cy.get(`.tiptap ${m.tag}`).should('exist').should('have.text', 'Hello world') cy.get(`.tiptap ${m.tag}`).should('exist').should('have.text', 'Hello world')
@ -38,40 +38,40 @@ context('/src/Examples/Default/Vue/', () => {
it('should clear marks when the button is pressed', () => { it('should clear marks when the button is pressed', () => {
cy.get('.tiptap').type('{selectall}Hello world') cy.get('.tiptap').type('{selectall}Hello world')
cy.get('button').contains('paragraph').click() cy.get('button').contains('Paragraph').click()
cy.get('.tiptap').type('{selectall}') cy.get('.tiptap').type('{selectall}')
cy.get('button').contains('bold').click() cy.get('button').contains('Bold').click()
cy.get('.tiptap strong').should('exist').should('have.text', 'Hello world') cy.get('.tiptap strong').should('exist').should('have.text', 'Hello world')
cy.get('button').contains('clear marks').click() cy.get('button').contains('Clear marks').click()
cy.get('.tiptap strong').should('not.exist') cy.get('.tiptap strong').should('not.exist')
}) })
it('should clear nodes when the button is pressed', () => { it('should clear nodes when the button is pressed', () => {
cy.get('.tiptap').type('{selectall}Hello world') cy.get('.tiptap').type('{selectall}Hello world')
cy.get('button').contains('bullet list').click() cy.get('button').contains('Bullet list').click()
cy.get('.tiptap ul').should('exist').should('have.text', 'Hello world') cy.get('.tiptap ul').should('exist').should('have.text', 'Hello world')
cy.get('.tiptap').type('{enter}A second item{enter}A third item{selectall}') cy.get('.tiptap').type('{enter}A second item{enter}A third item{selectall}')
cy.get('button').contains('clear nodes').click() cy.get('button').contains('Clear nodes').click()
cy.get('.tiptap ul').should('not.exist') cy.get('.tiptap ul').should('not.exist')
cy.get('.tiptap p').should('have.length', 3) cy.get('.tiptap p').should('have.length', 3)
}) })
const buttonNodes = [ const buttonNodes = [
{ label: 'h1', tag: 'h1' }, { label: 'H1', tag: 'h1' },
{ label: 'h2', tag: 'h2' }, { label: 'H2', tag: 'h2' },
{ label: 'h3', tag: 'h3' }, { label: 'H3', tag: 'h3' },
{ label: 'h4', tag: 'h4' }, { label: 'H4', tag: 'h4' },
{ label: 'h5', tag: 'h5' }, { label: 'H5', tag: 'h5' },
{ label: 'h6', tag: 'h6' }, { label: 'H6', tag: 'h6' },
{ label: 'bullet list', tag: 'ul' }, { label: 'Bullet list', tag: 'ul' },
{ label: 'ordered list', tag: 'ol' }, { label: 'Ordered list', tag: 'ol' },
{ label: 'code block', tag: 'pre code' }, { label: 'Code block', tag: 'pre code' },
{ label: 'blockquote', tag: 'blockquote' }, { label: 'Blockquote', tag: 'blockquote' },
] ]
buttonNodes.forEach(n => { buttonNodes.forEach(n => {
it(`should set ${n.label} when the button is pressed`, () => { it(`should set ${n.label} when the button is pressed`, () => {
cy.get('button').contains('paragraph').click() cy.get('button').contains('Paragraph').click()
cy.get('.tiptap').type('{selectall}Hello world{selectall}') cy.get('.tiptap').type('{selectall}Hello world{selectall}')
cy.get('button').contains(n.label).click() cy.get('button').contains(n.label).click()
@ -83,35 +83,35 @@ context('/src/Examples/Default/Vue/', () => {
it('should add a hr when on the same line as a node', () => { it('should add a hr when on the same line as a node', () => {
cy.get('.tiptap').type('{rightArrow}') cy.get('.tiptap').type('{rightArrow}')
cy.get('button').contains('horizontal rule').click() cy.get('button').contains('Horizontal rule').click()
cy.get('.tiptap hr').should('exist') cy.get('.tiptap hr').should('exist')
cy.get('.tiptap h1').should('exist') cy.get('.tiptap h1').should('exist')
}) })
it('should add a hr when on a new line', () => { it('should add a hr when on a new line', () => {
cy.get('.tiptap').type('{rightArrow}{enter}') cy.get('.tiptap').type('{rightArrow}{enter}')
cy.get('button').contains('horizontal rule').click() cy.get('button').contains('Horizontal rule').click()
cy.get('.tiptap hr').should('exist') cy.get('.tiptap hr').should('exist')
cy.get('.tiptap h1').should('exist') cy.get('.tiptap h1').should('exist')
}) })
it('should add a br', () => { it('should add a br', () => {
cy.get('.tiptap').type('{rightArrow}') cy.get('.tiptap').type('{rightArrow}')
cy.get('button').contains('hard break').click() cy.get('button').contains('Hard break').click()
cy.get('.tiptap h1 br').should('exist') cy.get('.tiptap h1 br').should('exist')
}) })
it('should undo', () => { it('should undo', () => {
cy.get('.tiptap').type('{selectall}{backspace}') cy.get('.tiptap').type('{selectall}{backspace}')
cy.get('button').contains('undo').click() cy.get('button').contains('Undo').click()
cy.get('.tiptap').should('contain', 'Hello world') cy.get('.tiptap').should('contain', 'Hello world')
}) })
it('should redo', () => { it('should redo', () => {
cy.get('.tiptap').type('{selectall}{backspace}') cy.get('.tiptap').type('{selectall}{backspace}')
cy.get('button').contains('undo').click() cy.get('button').contains('Undo').click()
cy.get('.tiptap').should('contain', 'Hello world') cy.get('.tiptap').should('contain', 'Hello world')
cy.get('button').contains('redo').click() cy.get('button').contains('Redo').click()
cy.get('.tiptap').should('not.contain', 'Hello world') cy.get('.tiptap').should('not.contain', 'Hello world')
}) })
}) })

View File

@ -1,73 +1,83 @@
<template> <template>
<div v-if="editor"> <div v-if="editor" class="container">
<div class="control-group">
<div class="button-group">
<button @click="editor.chain().focus().toggleBold().run()" :disabled="!editor.can().chain().focus().toggleBold().run()" :class="{ 'is-active': editor.isActive('bold') }"> <button @click="editor.chain().focus().toggleBold().run()" :disabled="!editor.can().chain().focus().toggleBold().run()" :class="{ 'is-active': editor.isActive('bold') }">
bold Bold
</button> </button>
<button @click="editor.chain().focus().toggleItalic().run()" :disabled="!editor.can().chain().focus().toggleItalic().run()" :class="{ 'is-active': editor.isActive('italic') }"> <button @click="editor.chain().focus().toggleItalic().run()" :disabled="!editor.can().chain().focus().toggleItalic().run()" :class="{ 'is-active': editor.isActive('italic') }">
italic Italic
</button> </button>
<button @click="editor.chain().focus().toggleStrike().run()" :disabled="!editor.can().chain().focus().toggleStrike().run()" :class="{ 'is-active': editor.isActive('strike') }"> <button @click="editor.chain().focus().toggleStrike().run()" :disabled="!editor.can().chain().focus().toggleStrike().run()" :class="{ 'is-active': editor.isActive('strike') }">
strike Strike
</button> </button>
<button @click="editor.chain().focus().toggleCode().run()" :disabled="!editor.can().chain().focus().toggleCode().run()" :class="{ 'is-active': editor.isActive('code') }"> <button @click="editor.chain().focus().toggleCode().run()" :disabled="!editor.can().chain().focus().toggleCode().run()" :class="{ 'is-active': editor.isActive('code') }">
code Code
</button> </button>
<button @click="editor.chain().focus().unsetAllMarks().run()"> <button @click="editor.chain().focus().unsetAllMarks().run()">
clear marks Clear marks
</button> </button>
<button @click="editor.chain().focus().clearNodes().run()"> <button @click="editor.chain().focus().clearNodes().run()">
clear nodes Clear nodes
</button> </button>
<button @click="editor.chain().focus().setParagraph().run()" :class="{ 'is-active': editor.isActive('paragraph') }"> <button @click="editor.chain().focus().setParagraph().run()" :class="{ 'is-active': editor.isActive('paragraph') }">
paragraph Paragraph
</button> </button>
<button @click="editor.chain().focus().toggleHeading({ level: 1 }).run()" :class="{ 'is-active': editor.isActive('heading', { level: 1 }) }"> <button @click="editor.chain().focus().toggleHeading({ level: 1 }).run()" :class="{ 'is-active': editor.isActive('heading', { level: 1 }) }">
h1 H1
</button> </button>
<button @click="editor.chain().focus().toggleHeading({ level: 2 }).run()" :class="{ 'is-active': editor.isActive('heading', { level: 2 }) }"> <button @click="editor.chain().focus().toggleHeading({ level: 2 }).run()" :class="{ 'is-active': editor.isActive('heading', { level: 2 }) }">
h2 H2
</button> </button>
<button @click="editor.chain().focus().toggleHeading({ level: 3 }).run()" :class="{ 'is-active': editor.isActive('heading', { level: 3 }) }"> <button @click="editor.chain().focus().toggleHeading({ level: 3 }).run()" :class="{ 'is-active': editor.isActive('heading', { level: 3 }) }">
h3 H3
</button> </button>
<button @click="editor.chain().focus().toggleHeading({ level: 4 }).run()" :class="{ 'is-active': editor.isActive('heading', { level: 4 }) }"> <button @click="editor.chain().focus().toggleHeading({ level: 4 }).run()" :class="{ 'is-active': editor.isActive('heading', { level: 4 }) }">
h4 H4
</button> </button>
<button @click="editor.chain().focus().toggleHeading({ level: 5 }).run()" :class="{ 'is-active': editor.isActive('heading', { level: 5 }) }"> <button @click="editor.chain().focus().toggleHeading({ level: 5 }).run()" :class="{ 'is-active': editor.isActive('heading', { level: 5 }) }">
h5 H5
</button> </button>
<button @click="editor.chain().focus().toggleHeading({ level: 6 }).run()" :class="{ 'is-active': editor.isActive('heading', { level: 6 }) }"> <button @click="editor.chain().focus().toggleHeading({ level: 6 }).run()" :class="{ 'is-active': editor.isActive('heading', { level: 6 }) }">
h6 H6
</button> </button>
<button @click="editor.chain().focus().toggleBulletList().run()" :class="{ 'is-active': editor.isActive('bulletList') }"> <button @click="editor.chain().focus().toggleBulletList().run()" :class="{ 'is-active': editor.isActive('bulletList') }">
bullet list Bullet list
</button> </button>
<button @click="editor.chain().focus().toggleOrderedList().run()" :class="{ 'is-active': editor.isActive('orderedList') }"> <button @click="editor.chain().focus().toggleOrderedList().run()" :class="{ 'is-active': editor.isActive('orderedList') }">
ordered list Ordered list
</button> </button>
<button @click="editor.chain().focus().toggleCodeBlock().run()" :class="{ 'is-active': editor.isActive('codeBlock') }"> <button @click="editor.chain().focus().toggleCodeBlock().run()" :class="{ 'is-active': editor.isActive('codeBlock') }">
code block Code block
</button> </button>
<button @click="editor.chain().focus().toggleBlockquote().run()" :class="{ 'is-active': editor.isActive('blockquote') }"> <button @click="editor.chain().focus().toggleBlockquote().run()" :class="{ 'is-active': editor.isActive('blockquote') }">
blockquote Blockquote
</button> </button>
<button @click="editor.chain().focus().setHorizontalRule().run()"> <button @click="editor.chain().focus().setHorizontalRule().run()">
horizontal rule Horizontal rule
</button> </button>
<button @click="editor.chain().focus().setHardBreak().run()"> <button @click="editor.chain().focus().setHardBreak().run()">
hard break Hard break
</button> </button>
<button @click="editor.chain().focus().undo().run()" :disabled="!editor.can().chain().focus().undo().run()"> <button @click="editor.chain().focus().undo().run()" :disabled="!editor.can().chain().focus().undo().run()">
undo Undo
</button> </button>
<button @click="editor.chain().focus().redo().run()" :disabled="!editor.can().chain().focus().redo().run()"> <button @click="editor.chain().focus().redo().run()" :disabled="!editor.can().chain().focus().redo().run()">
redo Redo
</button>
<button @click="editor.chain().focus().setColor('#958DF1').run()" :class="{ 'is-active': editor.isActive('textStyle', { color: '#958DF1' }) }">
Purple
</button> </button>
</div> </div>
</div>
<editor-content :editor="editor" /> <editor-content :editor="editor" />
</div>
</template> </template>
<script> <script>
import { Color } from '@tiptap/extension-color'
import ListItem from '@tiptap/extension-list-item'
import TextStyle from '@tiptap/extension-text-style'
import StarterKit from '@tiptap/starter-kit' import StarterKit from '@tiptap/starter-kit'
import { Editor, EditorContent } from '@tiptap/vue-3' import { Editor, EditorContent } from '@tiptap/vue-3'
@ -85,6 +95,8 @@ export default {
mounted() { mounted() {
this.editor = new Editor({ this.editor = new Editor({
extensions: [ extensions: [
Color.configure({ types: [TextStyle.name, ListItem.name] }),
TextStyle.configure({ types: [ListItem.name] }),
StarterKit, StarterKit,
], ],
content: ` content: `
@ -92,7 +104,7 @@ export default {
Hi there, Hi there,
</h2> </h2>
<p> <p>
this is a <em>basic</em> example of <strong>tiptap</strong>. Sure, there are all kind of basic text styles youd probably expect from a text editor. But wait until you see the lists: this is a <em>basic</em> example of <strong>Tiptap</strong>. Sure, there are all kind of basic text styles youd probably expect from a text editor. But wait until you see the lists:
</p> </p>
<ul> <ul>
<li> <li>
@ -129,15 +141,23 @@ export default {
<style lang="scss"> <style lang="scss">
/* Basic editor styles */ /* Basic editor styles */
.tiptap { .tiptap {
> * + * { :first-child {
margin-top: 0.75em; margin-top: 0;
} }
/* List styles */
ul, ul,
ol { ol {
padding: 0 1rem; padding: 0 1rem;
margin: 1.25rem 1rem 1.25rem 0.4rem;
li p {
margin-top: 0.25em;
margin-bottom: 0.25em;
}
} }
/* Heading styles */
h1, h1,
h2, h2,
h3, h3,
@ -145,41 +165,68 @@ export default {
h5, h5,
h6 { h6 {
line-height: 1.1; line-height: 1.1;
margin-top: 2.5rem;
text-wrap: pretty;
} }
h1,
h2 {
margin-top: 3.5rem;
margin-bottom: 1.5rem;
}
h1 {
font-size: 1.4rem;
}
h2 {
font-size: 1.2rem;
}
h3 {
font-size: 1.1rem;
}
h4,
h5,
h6 {
font-size: 1rem;
}
/* Code and preformatted text styles */
code { code {
background-color: rgba(#616161, 0.1); background-color: var(--purple-light);
color: #616161; border-radius: 0.4rem;
color: var(--black);
font-size: 0.85rem;
padding: 0.25em 0.3em;
} }
pre { pre {
background: #0D0D0D; background: var(--black);
color: #FFF;
font-family: 'JetBrainsMono', monospace;
padding: 0.75rem 1rem;
border-radius: 0.5rem; border-radius: 0.5rem;
color: var(--white);
font-family: 'JetBrainsMono', monospace;
margin: 1.5rem 0;
padding: 0.75rem 1rem;
code { code {
color: inherit;
padding: 0;
background: none; background: none;
color: inherit;
font-size: 0.8rem; font-size: 0.8rem;
padding: 0;
} }
} }
img {
max-width: 100%;
height: auto;
}
blockquote { blockquote {
border-left: 3px solid var(--gray-3);
margin: 1.5rem 0;
padding-left: 1rem; padding-left: 1rem;
border-left: 2px solid rgba(#0D0D0D, 0.1);
} }
hr { hr {
border: none; border: none;
border-top: 2px solid rgba(#0D0D0D, 0.1); border-top: 1px solid var(--gray-2);
margin: 2rem 0; margin: 2rem 0;
} }
} }

View File

@ -1,5 +1,7 @@
<template> <template>
<node-view-wrapper class="draw"> <node-view-wrapper class="draw">
<div class="control-group">
<div class="button-group">
<input type="color" v-model="color"> <input type="color" v-model="color">
<input <input
type="number" type="number"
@ -8,8 +10,9 @@
v-model="size" v-model="size"
> >
<button @click="clear"> <button @click="clear">
clear Clear
</button> </button>
</div>
<svg viewBox="0 0 500 250" ref="canvas"> <svg viewBox="0 0 500 250" ref="canvas">
<template v-for="item in node.attrs.lines"> <template v-for="item in node.attrs.lines">
<path <path
@ -22,6 +25,7 @@
/> />
</template> </template>
</svg> </svg>
</div>
</node-view-wrapper> </node-view-wrapper>
</template> </template>

View File

@ -12,44 +12,46 @@ const MenuBar = ({ editor }) => {
} }
return ( return (
<> <div className="control-group">
<div className="button-group">
<button onClick={() => editor.chain().focus().toggleHeading({ level: 1 }).run()} className={editor.isActive('heading', { level: 1 }) ? 'is-active' : ''}> <button onClick={() => editor.chain().focus().toggleHeading({ level: 1 }).run()} className={editor.isActive('heading', { level: 1 }) ? 'is-active' : ''}>
h1 H1
</button> </button>
<button onClick={() => editor.chain().focus().toggleHeading({ level: 2 }).run()} className={editor.isActive('heading', { level: 2 }) ? 'is-active' : ''}> <button onClick={() => editor.chain().focus().toggleHeading({ level: 2 }).run()} className={editor.isActive('heading', { level: 2 }) ? 'is-active' : ''}>
h2 H2
</button> </button>
<button onClick={() => editor.chain().focus().toggleHeading({ level: 3 }).run()} className={editor.isActive('heading', { level: 3 }) ? 'is-active' : ''}> <button onClick={() => editor.chain().focus().toggleHeading({ level: 3 }).run()} className={editor.isActive('heading', { level: 3 }) ? 'is-active' : ''}>
h3 H3
</button> </button>
<button onClick={() => editor.chain().focus().setParagraph().run()} className={editor.isActive('paragraph') ? 'is-active' : ''}> <button onClick={() => editor.chain().focus().setParagraph().run()} className={editor.isActive('paragraph') ? 'is-active' : ''}>
paragraph Paragraph
</button> </button>
<button onClick={() => editor.chain().focus().toggleBold().run()} className={editor.isActive('bold') ? 'is-active' : ''}> <button onClick={() => editor.chain().focus().toggleBold().run()} className={editor.isActive('bold') ? 'is-active' : ''}>
bold Bold
</button> </button>
<button onClick={() => editor.chain().focus().toggleItalic().run()} className={editor.isActive('italic') ? 'is-active' : ''}> <button onClick={() => editor.chain().focus().toggleItalic().run()} className={editor.isActive('italic') ? 'is-active' : ''}>
italic Italic
</button> </button>
<button onClick={() => editor.chain().focus().toggleStrike().run()} className={editor.isActive('strike') ? 'is-active' : ''}> <button onClick={() => editor.chain().focus().toggleStrike().run()} className={editor.isActive('strike') ? 'is-active' : ''}>
strike Strike
</button> </button>
<button onClick={() => editor.chain().focus().toggleHighlight().run()} className={editor.isActive('highlight') ? 'is-active' : ''}> <button onClick={() => editor.chain().focus().toggleHighlight().run()} className={editor.isActive('highlight') ? 'is-active' : ''}>
highlight Highlight
</button> </button>
<button onClick={() => editor.chain().focus().setTextAlign('left').run()} className={editor.isActive({ textAlign: 'left' }) ? 'is-active' : ''}> <button onClick={() => editor.chain().focus().setTextAlign('left').run()} className={editor.isActive({ textAlign: 'left' }) ? 'is-active' : ''}>
left Left
</button> </button>
<button onClick={() => editor.chain().focus().setTextAlign('center').run()} className={editor.isActive({ textAlign: 'center' }) ? 'is-active' : ''}> <button onClick={() => editor.chain().focus().setTextAlign('center').run()} className={editor.isActive({ textAlign: 'center' }) ? 'is-active' : ''}>
center Center
</button> </button>
<button onClick={() => editor.chain().focus().setTextAlign('right').run()} className={editor.isActive({ textAlign: 'right' }) ? 'is-active' : ''}> <button onClick={() => editor.chain().focus().setTextAlign('right').run()} className={editor.isActive({ textAlign: 'right' }) ? 'is-active' : ''}>
right Right
</button> </button>
<button onClick={() => editor.chain().focus().setTextAlign('justify').run()} className={editor.isActive({ textAlign: 'justify' }) ? 'is-active' : ''}> <button onClick={() => editor.chain().focus().setTextAlign('justify').run()} className={editor.isActive({ textAlign: 'justify' }) ? 'is-active' : ''}>
justify Justify
</button> </button>
</> </div>
</div>
) )
} }
@ -91,9 +93,9 @@ export default () => {
}) })
return ( return (
<div> <>
<MenuBar editor={editor} /> <MenuBar editor={editor} />
<EditorContent editor={editor} /> <EditorContent editor={editor} />
</div> </>
) )
} }

View File

@ -8,7 +8,7 @@ context('/src/Examples/Formatting/React/', () => {
}) })
const marks = [ const marks = [
{ label: 'highlight', mark: 'mark' }, { label: 'Highlight', mark: 'mark' },
] ]
marks.forEach(m => { marks.forEach(m => {
@ -20,10 +20,10 @@ context('/src/Examples/Formatting/React/', () => {
}) })
const alignments = [ const alignments = [
{ label: 'left', alignment: 'left' }, { label: 'Left', alignment: 'left' },
{ label: 'center', alignment: 'center' }, { label: 'Center', alignment: 'center' },
{ label: 'right', alignment: 'right' }, { label: 'Right', alignment: 'right' },
{ label: 'justify', alignment: 'justify' }, { label: 'Justify', alignment: 'justify' },
] ]
alignments.forEach(a => { alignments.forEach(a => {

View File

@ -1,16 +1,22 @@
/* Basic editor styles */ /* Basic editor styles */
.tiptap { .tiptap {
margin-top: 1rem; :first-child {
margin-top: 0;
> * + * {
margin-top: 0.75em;
} }
/* List styles */
ul, ul,
ol { ol {
padding: 0 1rem; padding: 0 1rem;
margin: 1.25rem 1rem 1.25rem 0.4rem;
li p {
margin-top: 0.25em;
margin-bottom: 0.25em;
}
} }
/* Heading styles */
h1, h1,
h2, h2,
h3, h3,
@ -18,45 +24,75 @@
h5, h5,
h6 { h6 {
line-height: 1.1; line-height: 1.1;
margin-top: 2.5rem;
text-wrap: pretty;
} }
h1,
h2 {
margin-top: 3.5rem;
margin-bottom: 1.5rem;
}
h1 {
font-size: 1.4rem;
}
h2 {
font-size: 1.2rem;
}
h3 {
font-size: 1.1rem;
}
h4,
h5,
h6 {
font-size: 1rem;
}
/* Code and preformatted text styles */
code { code {
background-color: rgba(#616161, 0.1); background-color: var(--purple-light);
color: #616161; border-radius: 0.4rem;
color: var(--black);
font-size: 0.85rem;
padding: 0.25em 0.3em;
} }
pre { pre {
background: #0D0D0D; background: var(--black);
color: #FFF;
font-family: 'JetBrainsMono', monospace;
padding: 0.75rem 1rem;
border-radius: 0.5rem; border-radius: 0.5rem;
color: var(--white);
font-family: 'JetBrainsMono', monospace;
margin: 1.5rem 0;
padding: 0.75rem 1rem;
code { code {
color: inherit;
padding: 0;
background: none; background: none;
color: inherit;
font-size: 0.8rem; font-size: 0.8rem;
padding: 0;
} }
} }
mark { mark {
background-color: #FAF594; background-color: #FAF594;
} border-radius: 0.4rem;
box-decoration-break: clone;
img { padding: 0.1rem 0.3rem;
max-width: 100%;
height: auto;
} }
blockquote { blockquote {
border-left: 3px solid var(--gray-3);
margin: 1.5rem 0;
padding-left: 1rem; padding-left: 1rem;
border-left: 2px solid rgba(#0D0D0D, 0.1);
} }
hr { hr {
border: none; border: none;
border-top: 2px solid rgba(#0D0D0D, 0.1); border-top: 1px solid var(--gray-2);
margin: 2rem 0; margin: 2rem 0;
} }
} }

View File

@ -8,7 +8,7 @@ context('/src/Examples/Formatting/Vue/', () => {
}) })
const marks = [ const marks = [
{ label: 'highlight', mark: 'mark' }, { label: 'Highlight', mark: 'mark' },
] ]
marks.forEach(m => { marks.forEach(m => {
@ -20,10 +20,10 @@ context('/src/Examples/Formatting/Vue/', () => {
}) })
const alignments = [ const alignments = [
{ label: 'left', alignment: 'left' }, { label: 'Left', alignment: 'left' },
{ label: 'center', alignment: 'center' }, { label: 'Center', alignment: 'center' },
{ label: 'right', alignment: 'right' }, { label: 'Right', alignment: 'right' },
{ label: 'justify', alignment: 'justify' }, { label: 'Justify', alignment: 'justify' },
] ]
alignments.forEach(a => { alignments.forEach(a => {

View File

@ -1,43 +1,47 @@
<template> <template>
<div v-if="editor"> <div v-if="editor" class="container">
<div class="control-group">
<div class="button-group">
<button @click="editor.chain().focus().toggleHeading({ level: 1 }).run()" :class="{ 'is-active': editor.isActive('heading', { level: 1 }) }"> <button @click="editor.chain().focus().toggleHeading({ level: 1 }).run()" :class="{ 'is-active': editor.isActive('heading', { level: 1 }) }">
h1 H1
</button> </button>
<button @click="editor.chain().focus().toggleHeading({ level: 2 }).run()" :class="{ 'is-active': editor.isActive('heading', { level: 2 }) }"> <button @click="editor.chain().focus().toggleHeading({ level: 2 }).run()" :class="{ 'is-active': editor.isActive('heading', { level: 2 }) }">
h2 H2
</button> </button>
<button @click="editor.chain().focus().toggleHeading({ level: 3 }).run()" :class="{ 'is-active': editor.isActive('heading', { level: 3 }) }"> <button @click="editor.chain().focus().toggleHeading({ level: 3 }).run()" :class="{ 'is-active': editor.isActive('heading', { level: 3 }) }">
h3 H3
</button> </button>
<button @click="editor.chain().focus().setParagraph().run()" :class="{ 'is-active': editor.isActive('paragraph') }"> <button @click="editor.chain().focus().setParagraph().run()" :class="{ 'is-active': editor.isActive('paragraph') }">
paragraph Paragraph
</button> </button>
<button @click="editor.chain().focus().toggleBold().run()" :class="{ 'is-active': editor.isActive('bold') }"> <button @click="editor.chain().focus().toggleBold().run()" :class="{ 'is-active': editor.isActive('bold') }">
bold Bold
</button> </button>
<button @click="editor.chain().focus().toggleItalic().run()" :class="{ 'is-active': editor.isActive('italic') }"> <button @click="editor.chain().focus().toggleItalic().run()" :class="{ 'is-active': editor.isActive('italic') }">
italic Italic
</button> </button>
<button @click="editor.chain().focus().toggleStrike().run()" :class="{ 'is-active': editor.isActive('strike') }"> <button @click="editor.chain().focus().toggleStrike().run()" :class="{ 'is-active': editor.isActive('strike') }">
strike Strike
</button> </button>
<button @click="editor.chain().focus().toggleHighlight().run()" :class="{ 'is-active': editor.isActive('highlight') }"> <button @click="editor.chain().focus().toggleHighlight().run()" :class="{ 'is-active': editor.isActive('highlight') }">
highlight Highlight
</button> </button>
<button @click="editor.chain().focus().setTextAlign('left').run()" :class="{ 'is-active': editor.isActive({ textAlign: 'left' }) }"> <button @click="editor.chain().focus().setTextAlign('left').run()" :class="{ 'is-active': editor.isActive({ textAlign: 'left' }) }">
left Left
</button> </button>
<button @click="editor.chain().focus().setTextAlign('center').run()" :class="{ 'is-active': editor.isActive({ textAlign: 'center' }) }"> <button @click="editor.chain().focus().setTextAlign('center').run()" :class="{ 'is-active': editor.isActive({ textAlign: 'center' }) }">
center Center
</button> </button>
<button @click="editor.chain().focus().setTextAlign('right').run()" :class="{ 'is-active': editor.isActive({ textAlign: 'right' }) }"> <button @click="editor.chain().focus().setTextAlign('right').run()" :class="{ 'is-active': editor.isActive({ textAlign: 'right' }) }">
right Right
</button> </button>
<button @click="editor.chain().focus().setTextAlign('justify').run()" :class="{ 'is-active': editor.isActive({ textAlign: 'justify' }) }"> <button @click="editor.chain().focus().setTextAlign('justify').run()" :class="{ 'is-active': editor.isActive({ textAlign: 'justify' }) }">
justify Justify
</button> </button>
</div> </div>
</div>
<editor-content :editor="editor" /> <editor-content :editor="editor" />
</div>
</template> </template>
<script> <script>
@ -104,17 +108,23 @@ export default {
<style lang="scss"> <style lang="scss">
/* Basic editor styles */ /* Basic editor styles */
.tiptap { .tiptap {
margin-top: 1rem; :first-child {
margin-top: 0;
> * + * {
margin-top: 0.75em;
} }
/* List styles */
ul, ul,
ol { ol {
padding: 0 1rem; padding: 0 1rem;
margin: 1.25rem 1rem 1.25rem 0.4rem;
li p {
margin-top: 0.25em;
margin-bottom: 0.25em;
}
} }
/* Heading styles */
h1, h1,
h2, h2,
h3, h3,
@ -122,45 +132,75 @@ export default {
h5, h5,
h6 { h6 {
line-height: 1.1; line-height: 1.1;
margin-top: 2.5rem;
text-wrap: pretty;
} }
h1,
h2 {
margin-top: 3.5rem;
margin-bottom: 1.5rem;
}
h1 {
font-size: 1.4rem;
}
h2 {
font-size: 1.2rem;
}
h3 {
font-size: 1.1rem;
}
h4,
h5,
h6 {
font-size: 1rem;
}
/* Code and preformatted text styles */
code { code {
background-color: rgba(#616161, 0.1); background-color: var(--purple-light);
color: #616161; border-radius: 0.4rem;
color: var(--black);
font-size: 0.85rem;
padding: 0.25em 0.3em;
} }
pre { pre {
background: #0D0D0D; background: var(--black);
color: #FFF;
font-family: 'JetBrainsMono', monospace;
padding: 0.75rem 1rem;
border-radius: 0.5rem; border-radius: 0.5rem;
color: var(--white);
font-family: 'JetBrainsMono', monospace;
margin: 1.5rem 0;
padding: 0.75rem 1rem;
code { code {
color: inherit;
padding: 0;
background: none; background: none;
color: inherit;
font-size: 0.8rem; font-size: 0.8rem;
padding: 0;
} }
} }
mark { mark {
background-color: #FAF594; background-color: #FAF594;
} border-radius: 0.4rem;
box-decoration-break: clone;
img { padding: 0.1rem 0.3rem;
max-width: 100%;
height: auto;
} }
blockquote { blockquote {
border-left: 3px solid var(--gray-3);
margin: 1.5rem 0;
padding-left: 1rem; padding-left: 1rem;
border-left: 2px solid rgba(#0D0D0D, 0.1);
} }
hr { hr {
border: none; border: none;
border-top: 2px solid rgba(#0D0D0D, 0.1); border-top: 1px solid var(--gray-2);
margin: 2rem 0; margin: 2rem 0;
} }
} }

View File

@ -37,9 +37,13 @@ export default () => {
} }
return ( return (
<div> <>
<button onClick={addImage}>add image from URL</button> <div className="control-group">
<EditorContent editor={editor} /> <div className="button-group">
<button onClick={addImage}>Add image from URL</button>
</div> </div>
</div>
<EditorContent editor={editor} />
</>
) )
} }

View File

@ -19,7 +19,7 @@ context('/src/Examples/Images/React/', () => {
cy.stub(win, 'prompt').returns('https://placehold.co/400x400') cy.stub(win, 'prompt').returns('https://placehold.co/400x400')
cy.wait(1000) cy.wait(1000)
cy.get('button').contains('add image from URL').click({ force: false }) cy.get('button').contains('Add image from URL').click({ force: false })
cy.wait(1000) cy.wait(1000)
cy.get('.tiptap img').should('have.length', 3) cy.get('.tiptap img').should('have.length', 3)
}) })

View File

@ -1,15 +1,17 @@
/* Basic editor styles */ /* Basic editor styles */
.tiptap { .tiptap {
> * + * { :first-child {
margin-top: 0.75em; margin-top: 0;
} }
img { img {
max-width: 100%; display: block;
height: auto; height: auto;
margin: 1.5rem 0;
max-width: 100%;
&.ProseMirror-selectednode { &.ProseMirror-selectednode {
outline: 3px solid #68CEF8; outline: 3px solid var(--purple);
} }
} }
} }

View File

@ -20,7 +20,7 @@ context('/src/Examples/Images/Vue/', () => {
cy.stub(win, 'prompt').returns('https://placehold.co/400x400') cy.stub(win, 'prompt').returns('https://placehold.co/400x400')
cy.wait(1000) cy.wait(1000)
cy.get('button').contains('add image from URL').click({ force: false }) cy.get('button').contains('Add image from URL').click({ force: false })
cy.wait(1000) cy.wait(1000)
cy.get('.tiptap img').should('have.length', 3) cy.get('.tiptap img').should('have.length', 3)
}) })

View File

@ -1,8 +1,12 @@
<template> <template>
<div v-if="editor"> <div v-if="editor" class="container">
<button @click="addImage">add image from URL</button> <div class="control-group">
<div class="button-group">
<button @click="addImage">Add image from URL</button>
</div>
</div> </div>
<editor-content :editor="editor" /> <editor-content :editor="editor" />
</div>
</template> </template>
<script> <script>
@ -60,16 +64,18 @@ export default {
<style lang="scss"> <style lang="scss">
/* Basic editor styles */ /* Basic editor styles */
.tiptap { .tiptap {
> * + * { :first-child {
margin-top: 0.75em; margin-top: 0;
} }
img { img {
max-width: 100%; display: block;
height: auto; height: auto;
margin: 1.5rem 0;
max-width: 100%;
&.ProseMirror-selectednode { &.ProseMirror-selectednode {
outline: 3px solid #68CEF8; outline: 3px solid var(--purple);
} }
} }
} }

View File

@ -10,7 +10,7 @@ export default props => {
return ( return (
<NodeViewWrapper className="react-component"> <NodeViewWrapper className="react-component">
<span className="label">React Component</span> <label>React Component</label>
<div className="content"> <div className="content">
<button onClick={increase}> <button onClick={increase}>

View File

@ -1,33 +1,116 @@
/* Basic editor styles */ /* Basic editor styles */
.tiptap { .tiptap {
> * + * { :first-child {
margin-top: 0.75em; margin-top: 0;
} }
}
.react-component { /* List styles */
background: #FAF594; ul,
border: 3px solid #0D0D0D; ol {
padding: 0 1rem;
margin: 1.25rem 1rem 1.25rem 0.4rem;
li p {
margin-top: 0.25em;
margin-bottom: 0.25em;
}
}
/* Heading styles */
h1,
h2,
h3,
h4,
h5,
h6 {
line-height: 1.1;
margin-top: 2.5rem;
text-wrap: pretty;
}
h1,
h2 {
margin-top: 3.5rem;
margin-bottom: 1.5rem;
}
h1 {
font-size: 1.4rem;
}
h2 {
font-size: 1.2rem;
}
h3 {
font-size: 1.1rem;
}
h4,
h5,
h6 {
font-size: 1rem;
}
/* Code and preformatted text styles */
code {
background-color: var(--purple-light);
border-radius: 0.4rem;
color: var(--black);
font-size: 0.85rem;
padding: 0.25em 0.3em;
}
pre {
background: var(--black);
border-radius: 0.5rem; border-radius: 0.5rem;
margin: 1rem 0; color: var(--white);
font-family: 'JetBrainsMono', monospace;
margin: 1.5rem 0;
padding: 0.75rem 1rem;
code {
background: none;
color: inherit;
font-size: 0.8rem;
padding: 0;
}
}
blockquote {
border-left: 3px solid var(--gray-3);
margin: 1.5rem 0;
padding-left: 1rem;
}
hr {
border: none;
border-top: 1px solid var(--gray-2);
margin: 2rem 0;
}
/* React component */
.react-component {
background-color: var(--purple-light);
border: 2px solid var(--purple);
border-radius: 0.5rem;
margin: 2rem 0;
position: relative; position: relative;
.label { label {
margin-left: 1rem; background-color: var(--purple);
background-color: #0D0D0D; border-radius: 0 0 0.5rem 0;
font-size: 0.6rem; color: var(--white);
letter-spacing: 1px; font-size: 0.75rem;
font-weight: bold; font-weight: bold;
text-transform: uppercase; padding: 0.25rem 0.5rem;
color: #fff;
position: absolute; position: absolute;
top: 0; top: 0;
padding: 0.25rem 0.75rem;
border-radius: 0 0 0.5rem 0.5rem;
} }
.content { .content {
margin-top: 1.5rem; margin-top: 1.5rem;
padding: 1rem; padding: 1rem;
} }
}
} }

View File

@ -1,6 +1,6 @@
<template> <template>
<node-view-wrapper class="vue-component"> <node-view-wrapper class="vue-component">
<span class="label">Vue Component</span> <label>Vue Component</label>
<div class="content"> <div class="content">
<button @click="increase">This button has been clicked {{ node.attrs.count }} times.</button> <button @click="increase">This button has been clicked {{ node.attrs.count }} times.</button>
@ -29,30 +29,30 @@ export default {
</script> </script>
<style lang="scss"> <style lang="scss">
.vue-component { .tiptap {
background: #FAF594; /* Vue component */
border: 3px solid #0D0D0D; .vue-component {
background-color: var(--purple-light);
border: 2px solid var(--purple);
border-radius: 0.5rem; border-radius: 0.5rem;
margin: 1rem 0; margin: 2rem 0;
position: relative; position: relative;
}
.label { label {
margin-left: 1rem; background-color: var(--purple);
background-color: #0D0D0D; border-radius: 0 0 0.5rem 0;
font-size: 0.6rem; color: var(--white);
letter-spacing: 1px; font-size: 0.75rem;
font-weight: bold; font-weight: bold;
text-transform: uppercase; padding: 0.25rem 0.5rem;
color: #fff;
position: absolute; position: absolute;
top: 0; top: 0;
padding: 0.25rem 0.75rem; }
border-radius: 0 0 0.5rem 0.5rem;
}
.content { .content {
margin-top: 1.5rem; margin-top: 1.5rem;
padding: 1rem; padding: 1rem;
}
}
} }
</style> </style>

View File

@ -46,8 +46,93 @@ export default {
<style lang="scss"> <style lang="scss">
/* Basic editor styles */ /* Basic editor styles */
.tiptap { .tiptap {
> * + * { :first-child {
margin-top: 0.75em; margin-top: 0;
}
/* List styles */
ul,
ol {
padding: 0 1rem;
margin: 1.25rem 1rem 1.25rem 0.4rem;
li p {
margin-top: 0.25em;
margin-bottom: 0.25em;
}
}
/* Heading styles */
h1,
h2,
h3,
h4,
h5,
h6 {
line-height: 1.1;
margin-top: 2.5rem;
text-wrap: pretty;
}
h1,
h2 {
margin-top: 3.5rem;
margin-bottom: 1.5rem;
}
h1 {
font-size: 1.4rem;
}
h2 {
font-size: 1.2rem;
}
h3 {
font-size: 1.1rem;
}
h4,
h5,
h6 {
font-size: 1rem;
}
/* Code and preformatted text styles */
code {
background-color: var(--purple-light);
border-radius: 0.4rem;
color: var(--black);
font-size: 0.85rem;
padding: 0.25em 0.3em;
}
pre {
background: var(--black);
border-radius: 0.5rem;
color: var(--white);
font-family: 'JetBrainsMono', monospace;
margin: 1.5rem 0;
padding: 0.75rem 1rem;
code {
background: none;
color: inherit;
font-size: 0.8rem;
padding: 0;
}
}
blockquote {
border-left: 3px solid var(--gray-3);
margin: 1.5rem 0;
padding-left: 1rem;
}
hr {
border: none;
border-top: 1px solid var(--gray-2);
margin: 2rem 0;
} }
} }
</style> </style>

View File

@ -3,10 +3,10 @@ import React from 'react'
export default () => { export default () => {
return ( return (
<NodeViewWrapper className="react-component-with-content"> <NodeViewWrapper className="react-component">
<span className="label" contentEditable={false}>React Component</span> <label contentEditable={false}>React Component</label>
<NodeViewContent className="content" /> <NodeViewContent className="content is-editable" />
</NodeViewWrapper> </NodeViewWrapper>
) )
} }

View File

@ -11,12 +11,12 @@ context('/src/Examples/InteractivityComponentContent/React/', () => {
}) })
it('should render a custom node', () => { it('should render a custom node', () => {
cy.get('.ProseMirror .react-component-with-content') cy.get('.ProseMirror .react-component')
.should('have.length', 1) .should('have.length', 1)
}) })
it('should allow text editing inside component', () => { it('should allow text editing inside component', () => {
cy.get('.ProseMirror .react-component-with-content .content div') cy.get('.ProseMirror .react-component .content div')
.invoke('attr', 'contentEditable', true) .invoke('attr', 'contentEditable', true)
.invoke('text', '') .invoke('text', '')
.type('Hello World!') .type('Hello World!')
@ -24,24 +24,24 @@ context('/src/Examples/InteractivityComponentContent/React/', () => {
}) })
it('should allow text editing inside component with markdown text', () => { it('should allow text editing inside component with markdown text', () => {
cy.get('.ProseMirror .react-component-with-content .content div') cy.get('.ProseMirror .react-component .content div')
.invoke('attr', 'contentEditable', true) .invoke('attr', 'contentEditable', true)
.invoke('text', '') .invoke('text', '')
.type('Hello World! This is **bold**.') .type('Hello World! This is **bold**.')
.should('have.text', 'Hello World! This is bold.') .should('have.text', 'Hello World! This is bold.')
cy.get('.ProseMirror .react-component-with-content .content strong') cy.get('.ProseMirror .react-component .content strong')
.should('exist') .should('exist')
}) })
it('should remove node via selectall', () => { it('should remove node via selectall', () => {
cy.get('.ProseMirror .react-component-with-content') cy.get('.ProseMirror .react-component')
.should('have.length', 1) .should('have.length', 1)
cy.get('.ProseMirror') cy.get('.ProseMirror')
.type('{selectall}{backspace}') .type('{selectall}{backspace}')
cy.get('.ProseMirror .react-component-with-content') cy.get('.ProseMirror .react-component')
.should('have.length', 0) .should('have.length', 0)
}) })
}) })

View File

@ -1,36 +1,123 @@
/* Basic editor styles */ /* Basic editor styles */
.tiptap { .tiptap {
> * + * { :first-child {
margin-top: 0.75em; margin-top: 0;
} }
}
.react-component-with-content { /* List styles */
background: #FAF594; ul,
border: 3px solid #0D0D0D; ol {
padding: 0 1rem;
margin: 1.25rem 1rem 1.25rem 0.4rem;
li p {
margin-top: 0.25em;
margin-bottom: 0.25em;
}
}
/* Heading styles */
h1,
h2,
h3,
h4,
h5,
h6 {
line-height: 1.1;
margin-top: 2.5rem;
text-wrap: pretty;
}
h1,
h2 {
margin-top: 3.5rem;
margin-bottom: 1.5rem;
}
h1 {
font-size: 1.4rem;
}
h2 {
font-size: 1.2rem;
}
h3 {
font-size: 1.1rem;
}
h4,
h5,
h6 {
font-size: 1rem;
}
/* Code and preformatted text styles */
code {
background-color: var(--purple-light);
border-radius: 0.4rem;
color: var(--black);
font-size: 0.85rem;
padding: 0.25em 0.3em;
}
pre {
background: var(--black);
border-radius: 0.5rem; border-radius: 0.5rem;
margin: 1rem 0; color: var(--white);
font-family: 'JetBrainsMono', monospace;
margin: 1.5rem 0;
padding: 0.75rem 1rem;
code {
background: none;
color: inherit;
font-size: 0.8rem;
padding: 0;
}
}
blockquote {
border-left: 3px solid var(--gray-3);
margin: 1.5rem 0;
padding-left: 1rem;
}
hr {
border: none;
border-top: 1px solid var(--gray-2);
margin: 2rem 0;
}
/* React component */
.react-component {
background-color: var(--purple-light);
border: 2px solid var(--purple);
border-radius: 0.5rem;
margin: 2rem 0;
position: relative; position: relative;
.label { label {
margin-left: 1rem; background-color: var(--purple);
background-color: #0D0D0D; border-radius: 0 0 0.5rem 0;
font-size: 0.6rem; color: var(--white);
letter-spacing: 1px; font-size: 0.75rem;
font-weight: bold; font-weight: bold;
text-transform: uppercase; padding: 0.25rem 0.5rem;
color: #fff;
position: absolute; position: absolute;
top: 0; top: 0;
padding: 0.25rem 0.75rem;
border-radius: 0 0 0.5rem 0.5rem;
} }
.content { .content {
margin-top: 1.5rem;
padding: 1rem;
&.is-editable {
border: 2px dashed var(--gray-3);
border-radius: 0.5rem;
margin: 2.5rem 1rem 1rem; margin: 2.5rem 1rem 1rem;
padding: 0.5rem; padding: 0.5rem;
border: 2px dashed #0D0D0D20; }
border-radius: 0.5rem; }
} }
} }

View File

@ -1,7 +1,7 @@
<template> <template>
<node-view-wrapper class="vue-component"> <node-view-wrapper class="vue-component">
<span class="label" contenteditable="false">Vue Component</span> <label contenteditable="false">Vue Component</label>
<node-view-content class="content" /> <node-view-content class="content is-editable" />
</node-view-wrapper> </node-view-wrapper>
</template> </template>
@ -19,32 +19,37 @@ export default {
</script> </script>
<style lang="scss"> <style lang="scss">
.vue-component { .tiptap {
background: #FAF594; /* Vue component */
border: 3px solid #0D0D0D; .vue-component {
background-color: var(--purple-light);
border: 2px solid var(--purple);
border-radius: 0.5rem; border-radius: 0.5rem;
margin: 1rem 0; margin: 2rem 0;
position: relative; position: relative;
}
.label { label {
margin-left: 1rem; background-color: var(--purple);
background-color: #0D0D0D; border-radius: 0 0 0.5rem 0;
font-size: 0.6rem; color: var(--white);
letter-spacing: 1px; font-size: 0.75rem;
font-weight: bold; font-weight: bold;
text-transform: uppercase; padding: 0.25rem 0.5rem;
color: #fff;
position: absolute; position: absolute;
top: 0; top: 0;
padding: 0.25rem 0.75rem; }
border-radius: 0 0 0.5rem 0.5rem;
}
.content { .content {
margin-top: 1.5rem;
padding: 1rem;
&.is-editable {
border: 2px dashed var(--gray-3);
border-radius: 0.5rem;
margin: 2.5rem 1rem 1rem; margin: 2.5rem 1rem 1rem;
padding: 0.5rem; padding: 0.5rem;
border: 2px dashed #0D0D0D20; }
border-radius: 0.5rem; }
}
} }
</style> </style>

View File

@ -48,8 +48,93 @@ export default {
<style lang="scss"> <style lang="scss">
/* Basic editor styles */ /* Basic editor styles */
.tiptap { .tiptap {
> * + * { :first-child {
margin-top: 0.75em; margin-top: 0;
}
/* List styles */
ul,
ol {
padding: 0 1rem;
margin: 1.25rem 1rem 1.25rem 0.4rem;
li p {
margin-top: 0.25em;
margin-bottom: 0.25em;
}
}
/* Heading styles */
h1,
h2,
h3,
h4,
h5,
h6 {
line-height: 1.1;
margin-top: 2.5rem;
text-wrap: pretty;
}
h1,
h2 {
margin-top: 3.5rem;
margin-bottom: 1.5rem;
}
h1 {
font-size: 1.4rem;
}
h2 {
font-size: 1.2rem;
}
h3 {
font-size: 1.1rem;
}
h4,
h5,
h6 {
font-size: 1rem;
}
/* Code and preformatted text styles */
code {
background-color: var(--purple-light);
border-radius: 0.4rem;
color: var(--black);
font-size: 0.85rem;
padding: 0.25em 0.3em;
}
pre {
background: var(--black);
border-radius: 0.5rem;
color: var(--white);
font-family: 'JetBrainsMono', monospace;
margin: 1.5rem 0;
padding: 0.75rem 1rem;
code {
background: none;
color: inherit;
font-size: 0.8rem;
padding: 0;
}
}
blockquote {
border-left: 3px solid var(--gray-3);
margin: 1.5rem 0;
padding-left: 1rem;
}
hr {
border: none;
border-top: 1px solid var(--gray-2);
margin: 2rem 0;
} }
} }
</style> </style>

View File

@ -21,7 +21,7 @@ export default () => {
To test that, start a new line and type <code>#</code> followed by a space to get a heading. Try <code>#</code>, <code>##</code>, <code>###</code>, <code>####</code>, <code>#####</code>, <code>######</code> for different levels. To test that, start a new line and type <code>#</code> followed by a space to get a heading. Try <code>#</code>, <code>##</code>, <code>###</code>, <code>####</code>, <code>#####</code>, <code>######</code> for different levels.
</p> </p>
<p> <p>
Those conventions are called input rules in tiptap. Some of them are enabled by default. Try <code>></code> for blockquotes, <code>*</code>, <code>-</code> or <code>+</code> for bullet lists, or <code>\`foobar\`</code> to highlight code, <code>~~tildes~~</code> to strike text, or <code>==equal signs==</code> to highlight text. Those conventions are called input rules in Tiptap. Some of them are enabled by default. Try <code>></code> for blockquotes, <code>*</code>, <code>-</code> or <code>+</code> for bullet lists, or <code>\`foobar\`</code> to highlight code, <code>~~tildes~~</code> to strike text, or <code>==equal signs==</code> to highlight text.
</p> </p>
<p> <p>
You can overwrite existing input rules or add your own to nodes, marks and extensions. You can overwrite existing input rules or add your own to nodes, marks and extensions.

View File

@ -1,13 +1,22 @@
/* Basic editor styles */
.tiptap { .tiptap {
> * + * { :first-child {
margin-top: 0.75em; margin-top: 0;
} }
/* List styles */
ul, ul,
ol { ol {
padding: 0 1rem; padding: 0 1rem;
margin: 1.25rem 1rem 1.25rem 0.4rem;
li p {
margin-top: 0.25em;
margin-bottom: 0.25em;
}
} }
/* Heading styles */
h1, h1,
h2, h2,
h3, h3,
@ -15,39 +24,75 @@
h5, h5,
h6 { h6 {
line-height: 1.1; line-height: 1.1;
margin-top: 2.5rem;
text-wrap: pretty;
} }
h1,
h2 {
margin-top: 3.5rem;
margin-bottom: 1.5rem;
}
h1 {
font-size: 1.4rem;
}
h2 {
font-size: 1.2rem;
}
h3 {
font-size: 1.1rem;
}
h4,
h5,
h6 {
font-size: 1rem;
}
/* Code and preformatted text styles */
code { code {
background-color: rgba(#616161, 0.1); background-color: var(--purple-light);
color: #616161; border-radius: 0.4rem;
color: var(--black);
font-size: 0.85rem;
padding: 0.25em 0.3em;
} }
pre { pre {
background: #0D0D0D; background: var(--black);
color: #FFF;
font-family: 'JetBrainsMono', monospace;
padding: 0.75rem 1rem;
border-radius: 0.5rem; border-radius: 0.5rem;
color: var(--white);
font-family: 'JetBrainsMono', monospace;
margin: 1.5rem 0;
padding: 0.75rem 1rem;
code { code {
color: inherit;
padding: 0;
background: none; background: none;
color: inherit;
font-size: 0.8rem; font-size: 0.8rem;
padding: 0;
} }
} }
img { mark {
max-width: 100%; background-color: #FAF594;
height: auto; border-radius: 0.4rem;
} box-decoration-break: clone;
padding: 0.1rem 0.3rem;
hr {
margin: 1rem 0;
} }
blockquote { blockquote {
border-left: 3px solid var(--gray-3);
margin: 1.5rem 0;
padding-left: 1rem; padding-left: 1rem;
border-left: 2px solid rgba(#0D0D0D, 0.1); }
hr {
border: none;
border-top: 1px solid var(--gray-2);
margin: 2rem 0;
} }
} }

View File

@ -34,7 +34,7 @@ export default {
To test that, start a new line and type <code>#</code> followed by a space to get a heading. Try <code>#</code>, <code>##</code>, <code>###</code>, <code>####</code>, <code>#####</code>, <code>######</code> for different levels. To test that, start a new line and type <code>#</code> followed by a space to get a heading. Try <code>#</code>, <code>##</code>, <code>###</code>, <code>####</code>, <code>#####</code>, <code>######</code> for different levels.
</p> </p>
<p> <p>
Those conventions are called input rules in tiptap. Some of them are enabled by default. Try <code>></code> for blockquotes, <code>*</code>, <code>-</code> or <code>+</code> for bullet lists, or <code>\`foobar\`</code> to highlight code, <code>~~tildes~~</code> to strike text, or <code>==equal signs==</code> to highlight text. Those conventions are called input rules in Tiptap. Some of them are enabled by default. Try <code>></code> for blockquotes, <code>*</code>, <code>-</code> or <code>+</code> for bullet lists, or <code>\`foobar\`</code> to highlight code, <code>~~tildes~~</code> to strike text, or <code>==equal signs==</code> to highlight text.
</p> </p>
<p> <p>
You can overwrite existing input rules or add your own to nodes, marks and extensions. You can overwrite existing input rules or add your own to nodes, marks and extensions.
@ -55,15 +55,23 @@ export default {
<style lang="scss"> <style lang="scss">
/* Basic editor styles */ /* Basic editor styles */
.tiptap { .tiptap {
> * + * { :first-child {
margin-top: 0.75em; margin-top: 0;
} }
/* List styles */
ul, ul,
ol { ol {
padding: 0 1rem; padding: 0 1rem;
margin: 1.25rem 1rem 1.25rem 0.4rem;
li p {
margin-top: 0.25em;
margin-bottom: 0.25em;
}
} }
/* Heading styles */
h1, h1,
h2, h2,
h3, h3,
@ -71,40 +79,76 @@ export default {
h5, h5,
h6 { h6 {
line-height: 1.1; line-height: 1.1;
margin-top: 2.5rem;
text-wrap: pretty;
} }
h1,
h2 {
margin-top: 3.5rem;
margin-bottom: 1.5rem;
}
h1 {
font-size: 1.4rem;
}
h2 {
font-size: 1.2rem;
}
h3 {
font-size: 1.1rem;
}
h4,
h5,
h6 {
font-size: 1rem;
}
/* Code and preformatted text styles */
code { code {
background-color: rgba(#616161, 0.1); background-color: var(--purple-light);
color: #616161; border-radius: 0.4rem;
color: var(--black);
font-size: 0.85rem;
padding: 0.25em 0.3em;
} }
pre { pre {
background: #0D0D0D; background: var(--black);
color: #FFF;
font-family: 'JetBrainsMono', monospace;
padding: 0.75rem 1rem;
border-radius: 0.5rem; border-radius: 0.5rem;
color: var(--white);
font-family: 'JetBrainsMono', monospace;
margin: 1.5rem 0;
padding: 0.75rem 1rem;
code { code {
color: inherit;
padding: 0;
background: none; background: none;
color: inherit;
font-size: 0.8rem; font-size: 0.8rem;
padding: 0;
} }
} }
img { mark {
max-width: 100%; background-color: #FAF594;
height: auto; border-radius: 0.4rem;
} box-decoration-break: clone;
padding: 0.1rem 0.3rem;
hr {
margin: 1rem 0;
} }
blockquote { blockquote {
border-left: 3px solid var(--gray-3);
margin: 1.5rem 0;
padding-left: 1rem; padding-left: 1rem;
border-left: 2px solid rgba(#0D0D0D, 0.1); }
hr {
border: none;
border-top: 1px solid var(--gray-2);
margin: 2rem 0;
} }
} }
</style> </style>

View File

@ -64,7 +64,7 @@ export default () => {
onClick={() => editor.chain().focus().toggleBulletList().run()} onClick={() => editor.chain().focus().toggleBulletList().run()}
className={editor.isActive('bulletList') ? 'is-active' : ''} className={editor.isActive('bulletList') ? 'is-active' : ''}
> >
Bullet List Bullet list
</button> </button>
</FloatingMenu>} </FloatingMenu>}

View File

@ -1,53 +1,144 @@
/* Basic editor styles */
.tiptap { .tiptap {
> * + * { :first-child {
margin-top: 0.75em; margin-top: 0;
} }
/* List styles */
ul, ul,
ol { ol {
padding: 0 1rem; padding: 0 1rem;
margin: 1.25rem 1rem 1.25rem 0.4rem;
li p {
margin-top: 0.25em;
margin-bottom: 0.25em;
}
}
/* Heading styles */
h1,
h2,
h3,
h4,
h5,
h6 {
line-height: 1.1;
margin-top: 2.5rem;
text-wrap: pretty;
}
h1,
h2 {
margin-top: 3.5rem;
margin-bottom: 1.5rem;
}
h1 {
font-size: 1.4rem;
}
h2 {
font-size: 1.2rem;
}
h3 {
font-size: 1.1rem;
}
h4,
h5,
h6 {
font-size: 1rem;
}
/* Code and preformatted text styles */
code {
background-color: var(--purple-light);
border-radius: 0.4rem;
color: var(--black);
font-size: 0.85rem;
padding: 0.25em 0.3em;
}
pre {
background: var(--black);
border-radius: 0.5rem;
color: var(--white);
font-family: 'JetBrainsMono', monospace;
margin: 1.5rem 0;
padding: 0.75rem 1rem;
code {
background: none;
color: inherit;
font-size: 0.8rem;
padding: 0;
}
}
blockquote {
border-left: 3px solid var(--gray-3);
margin: 1.5rem 0;
padding-left: 1rem;
}
hr {
border: none;
border-top: 1px solid var(--gray-2);
margin: 2rem 0;
} }
} }
/* Bubble menu */
.bubble-menu { .bubble-menu {
background-color: var(--white);
border: 1px solid var(--gray-1);
border-radius: 0.7rem;
box-shadow: var(--shadow);
display: flex; display: flex;
background-color: #0D0D0D;
padding: 0.2rem; padding: 0.2rem;
border-radius: 0.5rem;
button { button {
border: none; background-color: unset;
background: none;
color: #FFF; &:hover {
font-size: 0.85rem; background-color: var(--gray-3);
font-weight: 500; }
padding: 0 0.2rem;
opacity: 0.6;
&:hover,
&.is-active { &.is-active {
opacity: 1; background-color: var(--purple);
&:hover {
background-color: var(--purple-contrast);
}
} }
} }
} }
/* Floating menu */
.floating-menu { .floating-menu {
display: flex; display: flex;
background-color: #0D0D0D10; background-color: var(--gray-3);
padding: 0.2rem; padding: 0.1rem;
border-radius: 0.5rem; border-radius: 0.5rem;
button { button {
border: none; background-color: unset;
background: none; padding: 0.275rem 0.425rem;
font-size: 0.85rem; border-radius: 0.3rem;
font-weight: 500;
padding: 0 0.2rem; &:hover {
opacity: 0.6; background-color: var(--gray-3);
}
&:hover,
&.is-active { &.is-active {
opacity: 1; background-color: var(--white);
color: var(--purple);
&:hover {
color: var(--purple-contrast);
}
} }
} }
} }

View File

@ -28,7 +28,7 @@
H2 H2
</button> </button>
<button @click="editor.chain().focus().toggleBulletList().run()" :class="{ 'is-active': editor.isActive('bulletList') }"> <button @click="editor.chain().focus().toggleBulletList().run()" :class="{ 'is-active': editor.isActive('bulletList') }">
Bullet List Bullet list
</button> </button>
</floating-menu> </floating-menu>
</div> </div>
@ -83,60 +83,145 @@ export default {
<style lang="scss"> <style lang="scss">
/* Basic editor styles */ /* Basic editor styles */
.tiptap { .tiptap {
> * + * { :first-child {
margin-top: 0.75em; margin-top: 0;
} }
/* List styles */
ul, ul,
ol { ol {
padding: 0 1rem; padding: 0 1rem;
margin: 1.25rem 1rem 1.25rem 0.4rem;
li p {
margin-top: 0.25em;
margin-bottom: 0.25em;
}
}
/* Heading styles */
h1,
h2,
h3,
h4,
h5,
h6 {
line-height: 1.1;
margin-top: 2.5rem;
text-wrap: pretty;
}
h1,
h2 {
margin-top: 3.5rem;
margin-bottom: 1.5rem;
}
h1 {
font-size: 1.4rem;
}
h2 {
font-size: 1.2rem;
}
h3 {
font-size: 1.1rem;
}
h4,
h5,
h6 {
font-size: 1rem;
}
/* Code and preformatted text styles */
code {
background-color: var(--purple-light);
border-radius: 0.4rem;
color: var(--black);
font-size: 0.85rem;
padding: 0.25em 0.3em;
}
pre {
background: var(--black);
border-radius: 0.5rem;
color: var(--white);
font-family: 'JetBrainsMono', monospace;
margin: 1.5rem 0;
padding: 0.75rem 1rem;
code {
background: none;
color: inherit;
font-size: 0.8rem;
padding: 0;
}
} }
blockquote { blockquote {
border-left: 3px solid var(--gray-3);
margin: 1.5rem 0;
padding-left: 1rem; padding-left: 1rem;
border-left: 2px solid rgba(#0D0D0D, 0.1); }
hr {
border: none;
border-top: 1px solid var(--gray-2);
margin: 2rem 0;
} }
} }
/* Bubble menu */
.bubble-menu { .bubble-menu {
background-color: var(--white);
border: 1px solid var(--gray-1);
border-radius: 0.7rem;
box-shadow: var(--shadow);
display: flex; display: flex;
background-color: #0D0D0D;
padding: 0.2rem; padding: 0.2rem;
border-radius: 0.5rem;
button { button {
border: none; background-color: unset;
background: none;
color: #FFF; &:hover {
font-size: 0.85rem; background-color: var(--gray-3);
font-weight: 500; }
padding: 0 0.2rem;
opacity: 0.6;
&:hover,
&.is-active { &.is-active {
opacity: 1; background-color: var(--purple);
&:hover {
background-color: var(--purple-contrast);
}
} }
} }
} }
/* Floating menu */
.floating-menu { .floating-menu {
display: flex; display: flex;
background-color: #0D0D0D10; background-color: var(--gray-3);
padding: 0.2rem; padding: 0.1rem;
border-radius: 0.5rem; border-radius: 0.5rem;
button { button {
border: none; background-color: unset;
background: none; padding: 0.275rem 0.425rem;
font-size: 0.85rem; border-radius: 0.3rem;
font-weight: 500;
padding: 0 0.2rem; &:hover {
opacity: 0.6; background-color: var(--gray-3);
}
&:hover,
&.is-active { &.is-active {
opacity: 1; background-color: var(--white);
color: var(--purple);
&:hover {
color: var(--purple-contrast);
}
} }
} }
} }

View File

@ -15,7 +15,7 @@ export default () => {
], ],
content: ` content: `
<p> <p>
This is a radically reduced version of tiptap. It has support for a document, with paragraphs and text. Thats it. Its probably too much for real minimalists though. This is a radically reduced version of Tiptap. It has support for a document, with paragraphs and text. Thats it. Its probably too much for real minimalists though.
</p> </p>
<p> <p>
The paragraph extension is not really required, but you need at least one node. Sure, that node can be something different. The paragraph extension is not really required, but you need at least one node. Sure, that node can be something different.

View File

@ -1,6 +1,6 @@
/* Basic editor styles */ /* Basic editor styles */
.tiptap { .tiptap {
> * + * { :first-child {
margin-top: 0.75em; margin-top: 0;
} }
} }

View File

@ -28,7 +28,7 @@ export default {
], ],
content: ` content: `
<p> <p>
This is a radically reduced version of tiptap. It has support for a document, with paragraphs and text. Thats it. Its probably too much for real minimalists though. This is a radically reduced version of Tiptap. It has support for a document, with paragraphs and text. Thats it. Its probably too much for real minimalists though.
</p> </p>
<p> <p>
The paragraph extension is not really required, but you need at least one node. Sure, that node can be something different. The paragraph extension is not really required, but you need at least one node. Sure, that node can be something different.
@ -46,8 +46,8 @@ export default {
<style lang="scss"> <style lang="scss">
/* Basic editor styles */ /* Basic editor styles */
.tiptap { .tiptap {
> * + * { :first-child {
margin-top: 0.75em; margin-top: 0;
} }
} }
</style> </style>

View File

@ -18,7 +18,7 @@ export default () => {
<p> <p>
This is a <strong>simple</strong> paragraph. This is a <strong>simple</strong> paragraph.
</p> </p>
<img src="https://unsplash.it/200/200" alt="A 200x200 square thumbnail from unsplash." /> <img src="https://placehold.co/200x200" alt="A 200x200 square thumbnail from placehold.co." />
<p> <p>
Here is another paragraph inside this document. Here is another paragraph inside this document.
</p> </p>
@ -72,11 +72,11 @@ export default () => {
<blockquote> <blockquote>
<p>Here we have another paragraph inside a blockquote.</p> <p>Here we have another paragraph inside a blockquote.</p>
<blockquote> <blockquote>
<img src="https://unsplash.it/260/200" alt="A 260x200 landscape thumbnail from unsplash." /> <img src="https://placehold.co/260x200" alt="A 260x200 landscape thumbnail from placehold.co." />
<img src="https://unsplash.it/100/200" alt="A 100x200 portrait thumbnail from unsplash." /> <img src="https://placehold.co/100x200" alt="A 100x200 portrait thumbnail from placehold.co." />
</blockquote> </blockquote>
</blockquote> </blockquote>
<img src="https://unsplash.it/260/200" alt="A 260x200 landscape thumbnail from unsplash." /> <img src="https://placehold.co/260x200" alt="A 260x200 landscape thumbnail from placehold.co." />
`, `,
}) })
@ -160,7 +160,7 @@ export default () => {
}, [editor]) }, [editor])
const findSquaredImage = useCallback(() => { const findSquaredImage = useCallback(() => {
const nodePosition = editor.$doc.querySelector('image', { src: 'https://unsplash.it/200/200' }) const nodePosition = editor.$doc.querySelector('image', { src: 'https://placehold.co/200x200' })
if (!nodePosition) { if (!nodePosition) {
setFoundNodes(null) setFoundNodes(null)
@ -171,7 +171,7 @@ export default () => {
}, [editor]) }, [editor])
const findLandscapeImage = useCallback(() => { const findLandscapeImage = useCallback(() => {
const nodePosition = editor.$doc.querySelector('image', { src: 'https://unsplash.it/260/200' }) const nodePosition = editor.$doc.querySelector('image', { src: 'https://placehold.co/260x200' })
if (!nodePosition) { if (!nodePosition) {
setFoundNodes(null) setFoundNodes(null)
@ -182,7 +182,7 @@ export default () => {
}, [editor]) }, [editor])
const findAllLandscapeImages = useCallback(() => { const findAllLandscapeImages = useCallback(() => {
const nodePosition = editor.$doc.querySelectorAll('image', { src: 'https://unsplash.it/260/200' }) const nodePosition = editor.$doc.querySelectorAll('image', { src: 'https://placehold.co/260x200' })
if (!nodePosition) { if (!nodePosition) {
setFoundNodes(null) setFoundNodes(null)
@ -193,7 +193,7 @@ export default () => {
}, [editor]) }, [editor])
const findFirstLandscapeImageWithAllQuery = useCallback(() => { const findFirstLandscapeImageWithAllQuery = useCallback(() => {
const nodePosition = editor.$doc.querySelectorAll('image', { src: 'https://unsplash.it/260/200' }, true) const nodePosition = editor.$doc.querySelectorAll('image', { src: 'https://placehold.co/260x200' }, true)
if (!nodePosition) { if (!nodePosition) {
setFoundNodes(null) setFoundNodes(null)
@ -204,7 +204,7 @@ export default () => {
}, [editor]) }, [editor])
const findPortraitImageInBlockquote = useCallback(() => { const findPortraitImageInBlockquote = useCallback(() => {
const nodePosition = editor.$doc.querySelector('image', { src: 'https://unsplash.it/100/200' }) const nodePosition = editor.$doc.querySelector('image', { src: 'https://placehold.co/100x200' })
if (!nodePosition) { if (!nodePosition) {
setFoundNodes(null) setFoundNodes(null)
@ -259,8 +259,9 @@ export default () => {
}, [editor]) }, [editor])
return ( return (
<div> <>
<div> <div className="control-group">
<div className="button-group">
<button data-testid="find-paragraphs" onClick={findParagraphs}>Find paragraphs</button> <button data-testid="find-paragraphs" onClick={findParagraphs}>Find paragraphs</button>
<button data-testid="find-listitems" onClick={findListItems}>Find list items</button> <button data-testid="find-listitems" onClick={findListItems}>Find list items</button>
<button data-testid="find-bulletlists" onClick={findBulletList}>Find bullet lists</button> <button data-testid="find-bulletlists" onClick={findBulletList}>Find bullet lists</button>
@ -268,7 +269,7 @@ export default () => {
<button data-testid="find-blockquotes" onClick={findBlockquote}>Find blockquotes</button> <button data-testid="find-blockquotes" onClick={findBlockquote}>Find blockquotes</button>
<button data-testid="find-images" onClick={findImages}>Find images</button> <button data-testid="find-images" onClick={findImages}>Find images</button>
</div> </div>
<div> <div className="button-group">
<button data-testid="find-first-blockquote" onClick={findFirstBlockquote}>Find first blockquote</button> <button data-testid="find-first-blockquote" onClick={findFirstBlockquote}>Find first blockquote</button>
<button data-testid="find-squared-image" onClick={findSquaredImage}>Find squared image</button> <button data-testid="find-squared-image" onClick={findSquaredImage}>Find squared image</button>
<button data-testid="find-landscape-image" onClick={findLandscapeImage}>Find landscape image</button> <button data-testid="find-landscape-image" onClick={findLandscapeImage}>Find landscape image</button>
@ -276,16 +277,17 @@ export default () => {
<button data-testid="find-first-landscape-image-with-all-query" onClick={findFirstLandscapeImageWithAllQuery}>Find first landscape image with all query</button> <button data-testid="find-first-landscape-image-with-all-query" onClick={findFirstLandscapeImageWithAllQuery}>Find first landscape image with all query</button>
<button data-testid="find-portrait-image-inside-blockquote" onClick={findPortraitImageInBlockquote}>Find portrait image in blockquote</button> <button data-testid="find-portrait-image-inside-blockquote" onClick={findPortraitImageInBlockquote}>Find portrait image in blockquote</button>
</div> </div>
<div> <div className="button-group">
<button data-testid="find-first-node" onClick={findFirstNode}>Find first node</button> <button data-testid="find-first-node" onClick={findFirstNode}>Find first node</button>
<button data-testid="find-last-node" onClick={findLastNode}>Find last node</button> <button data-testid="find-last-node" onClick={findLastNode}>Find last node</button>
<button data-testid="find-last-node-of-first-bullet-list" onClick={findLastNodeOfFirstBulletList}>Find last node of first bullet list</button> <button data-testid="find-last-node-of-first-bullet-list" onClick={findLastNodeOfFirstBulletList}>Find last node of first bullet list</button>
<button data-testid="find-nonexistent-node" onClick={findNonexistentNode}>Find nonexistent node</button> <button data-testid="find-nonexistent-node" onClick={findNonexistentNode}>Find nonexistent node</button>
</div> </div>
</div>
<EditorContent editor={editor} /> <EditorContent editor={editor} />
{foundNodes ? <div data-testid="found-nodes">{foundNodes.map(n => ( {foundNodes ? <div className="output-group" data-testid="found-nodes">{foundNodes.map(n => (
<div data-testid="found-node" key={n.pos}>{mapNodePosToString(n)}</div> <div data-testid="found-node" key={n.pos}>{mapNodePosToString(n)}</div>
))}</div> : ''} ))}</div> : ''}
</div> </>
) )
} }

View File

@ -66,7 +66,7 @@ context('/src/Examples/NodePos/React/', () => {
cy.get('button[data-testid="find-squared-image"]').click() cy.get('button[data-testid="find-squared-image"]').click()
cy.get('div[data-testid="found-nodes"]').should('exist') cy.get('div[data-testid="found-nodes"]').should('exist')
cy.get('div[data-testid="found-node"]').should('have.length', 1) cy.get('div[data-testid="found-node"]').should('have.length', 1)
cy.get('div[data-testid="found-node"]').should('contain', 'https://unsplash.it/200/200') cy.get('div[data-testid="found-node"]').should('contain', 'https://placehold.co/200x200')
}) })
}) })
@ -75,7 +75,7 @@ context('/src/Examples/NodePos/React/', () => {
cy.get('button[data-testid="find-landscape-image"]').click() cy.get('button[data-testid="find-landscape-image"]').click()
cy.get('div[data-testid="found-nodes"]').should('exist') cy.get('div[data-testid="found-nodes"]').should('exist')
cy.get('div[data-testid="found-node"]').should('have.length', 1) cy.get('div[data-testid="found-node"]').should('have.length', 1)
cy.get('div[data-testid="found-node"]').should('contain', 'https://unsplash.it/260/200') cy.get('div[data-testid="found-node"]').should('contain', 'https://placehold.co/260x200')
}) })
}) })
@ -84,8 +84,8 @@ context('/src/Examples/NodePos/React/', () => {
cy.get('button[data-testid="find-all-landscape-images"]').click() cy.get('button[data-testid="find-all-landscape-images"]').click()
cy.get('div[data-testid="found-nodes"]').should('exist') cy.get('div[data-testid="found-nodes"]').should('exist')
cy.get('div[data-testid="found-node"]').should('have.length', 2) cy.get('div[data-testid="found-node"]').should('have.length', 2)
cy.get('div[data-testid="found-node"]').eq(0).should('contain', 'https://unsplash.it/260/200') cy.get('div[data-testid="found-node"]').eq(0).should('contain', 'https://placehold.co/260x200')
cy.get('div[data-testid="found-node"]').eq(1).should('contain', 'https://unsplash.it/260/200') cy.get('div[data-testid="found-node"]').eq(1).should('contain', 'https://placehold.co/260x200')
}) })
}) })
@ -94,7 +94,7 @@ context('/src/Examples/NodePos/React/', () => {
cy.get('button[data-testid="find-first-landscape-image-with-all-query"]').click() cy.get('button[data-testid="find-first-landscape-image-with-all-query"]').click()
cy.get('div[data-testid="found-nodes"]').should('exist') cy.get('div[data-testid="found-nodes"]').should('exist')
cy.get('div[data-testid="found-node"]').should('have.length', 1) cy.get('div[data-testid="found-node"]').should('have.length', 1)
cy.get('div[data-testid="found-node"]').should('contain', 'https://unsplash.it/260/200') cy.get('div[data-testid="found-node"]').should('contain', 'https://placehold.co/260x200')
}) })
}) })
@ -103,7 +103,7 @@ context('/src/Examples/NodePos/React/', () => {
cy.get('button[data-testid="find-portrait-image-inside-blockquote"]').click() cy.get('button[data-testid="find-portrait-image-inside-blockquote"]').click()
cy.get('div[data-testid="found-nodes"]').should('exist') cy.get('div[data-testid="found-nodes"]').should('exist')
cy.get('div[data-testid="found-node"]').should('have.length', 1) cy.get('div[data-testid="found-node"]').should('have.length', 1)
cy.get('div[data-testid="found-node"]').should('contain', 'https://unsplash.it/100/200') cy.get('div[data-testid="found-node"]').should('contain', 'https://placehold.co/100x200')
}) })
}) })
}) })

View File

@ -1,9 +1,22 @@
/* Basic editor styles */ /* Basic editor styles */
.tiptap { .tiptap {
> * + * { :first-child {
margin-top: 0.75em; margin-top: 0;
} }
/* List styles */
ul,
ol {
padding: 0 1rem;
margin: 1.25rem 1rem 1.25rem 0.4rem;
li p {
margin-top: 0.25em;
margin-bottom: 0.25em;
}
}
/* Heading styles */
h1, h1,
h2, h2,
h3, h3,
@ -11,5 +24,80 @@
h5, h5,
h6 { h6 {
line-height: 1.1; line-height: 1.1;
margin-top: 2.5rem;
text-wrap: pretty;
}
h1,
h2 {
margin-top: 3.5rem;
margin-bottom: 1.5rem;
}
h1 {
font-size: 1.4rem;
}
h2 {
font-size: 1.2rem;
}
h3 {
font-size: 1.1rem;
}
h4,
h5,
h6 {
font-size: 1rem;
}
/* Code and preformatted text styles */
code {
background-color: var(--purple-light);
border-radius: 0.4rem;
color: var(--black);
font-size: 0.85rem;
padding: 0.25em 0.3em;
}
pre {
background: var(--black);
border-radius: 0.5rem;
color: var(--white);
font-family: 'JetBrainsMono', monospace;
margin: 1.5rem 0;
padding: 0.75rem 1rem;
code {
background: none;
color: inherit;
font-size: 0.8rem;
padding: 0;
}
}
blockquote {
border-left: 3px solid var(--gray-3);
margin: 1.5rem 0;
padding-left: 1rem;
}
hr {
border: none;
border-top: 1px solid var(--gray-2);
margin: 2rem 0;
}
/* Image styles */
img {
display: block;
height: auto;
margin: 1.5rem 0;
max-width: 100%;
&.ProseMirror-selectednode {
outline: 3px solid var(--purple);
}
} }
} }

View File

@ -24,7 +24,7 @@ export default () => {
], ],
content: ` content: `
<p> <p>
With the Typography extension, tiptap understands »what you mean« and adds correct characters to your text its like a typography nerd on your side. With the Typography extension, Tiptap understands »what you mean« and adds correct characters to your text its like a typography nerd on your side.
</p> </p>
<p> <p>
Try it out and type <code>(c)</code>, <code>-></code>, <code>>></code>, <code>1/2</code>, <code>!=</code>, <code>--</code> or <code>1x1</code> here: Try it out and type <code>(c)</code>, <code>-></code>, <code>>></code>, <code>1/2</code>, <code>!=</code>, <code>--</code> or <code>1x1</code> here:

View File

@ -1,26 +1,20 @@
/* Basic editor styles */ /* Basic editor styles */
.tiptap { .tiptap {
> * + * { :first-child {
margin-top: 0.75em; margin-top: 0;
}
h1,
h2,
h3,
h4,
h5,
h6 {
line-height: 1.1;
} }
/* Code and preformatted text styles */
code { code {
background-color: rgba(#616161, 0.1); background-color: var(--purple-light);
color: #616161; border-radius: 0.4rem;
color: var(--black);
font-size: 0.85rem;
padding: 0.25em 0.3em;
} }
}
/* Color swatches */ /* Color swatches */
.color { .color {
white-space: nowrap; white-space: nowrap;
&::before { &::before {
@ -35,4 +29,5 @@
vertical-align: middle; vertical-align: middle;
width: 1em; width: 1em;
} }
}
} }

View File

@ -37,7 +37,7 @@ export default {
], ],
content: ` content: `
<p> <p>
With the Typography extension, tiptap understands »what you mean« and adds correct characters to your text its like a typography nerd on your side. With the Typography extension, Tiptap understands »what you mean« and adds correct characters to your text its like a typography nerd on your side.
</p> </p>
<p> <p>
Try it out and type <code>(c)</code>, <code>-></code>, <code>>></code>, <code>1/2</code>, <code>!=</code>, <code>--</code> or <code>1x1</code> here: Try it out and type <code>(c)</code>, <code>-></code>, <code>>></code>, <code>1/2</code>, <code>!=</code>, <code>--</code> or <code>1x1</code> here:
@ -64,40 +64,35 @@ export default {
<style lang="scss"> <style lang="scss">
/* Basic editor styles */ /* Basic editor styles */
.tiptap { .tiptap {
> * + * { :first-child {
margin-top: 0.75em; margin-top: 0;
}
h1,
h2,
h3,
h4,
h5,
h6 {
line-height: 1.1;
} }
/* Code and preformatted text styles */
code { code {
background-color: rgba(#616161, 0.1); background-color: var(--purple-light);
color: #616161; border-radius: 0.4rem;
color: var(--black);
font-size: 0.85rem;
padding: 0.25em 0.3em;
} }
}
/* Color swatches */ /* Color swatches */
.color { .color {
white-space: nowrap; white-space: nowrap;
&::before { &::before {
content: ' ';
display: inline-block;
width: 1em;
height: 1em;
border: 1px solid rgba(128, 128, 128, 0.3);
vertical-align: middle;
margin-right: 0.1em;
margin-bottom: 0.15em;
border-radius: 2px;
background-color: var(--color); background-color: var(--color);
border: 1px solid rgba(128, 128, 128, 0.3);
border-radius: 2px;
content: " ";
display: inline-block;
height: 1em;
margin-bottom: 0.15em;
margin-right: 0.1em;
vertical-align: middle;
width: 1em;
}
} }
} }
</style> </style>

View File

@ -60,69 +60,71 @@ const MenuBar = ({ editor }) => {
} }
return ( return (
<> <div className="control-group">
<div className="button-group">
<button onClick={() => editor.chain().focus().insertTable({ rows: 3, cols: 3, withHeaderRow: true }).run()}> <button onClick={() => editor.chain().focus().insertTable({ rows: 3, cols: 3, withHeaderRow: true }).run()}>
insertTable Insert table
</button> </button>
<button onClick={() => editor.chain().focus().insertContent(tableHTML, { <button onClick={() => editor.chain().focus().insertContent(tableHTML, {
parseOptions: { parseOptions: {
preserveWhitespace: false, preserveWhitespace: false,
}, },
}).run()}> }).run()}>
insertHTMLTable Insert HTML table
</button> </button>
<button onClick={() => editor.chain().focus().addColumnBefore().run()} disabled={!editor.can().addColumnBefore()}> <button onClick={() => editor.chain().focus().addColumnBefore().run()} disabled={!editor.can().addColumnBefore()}>
addColumnBefore Add column before
</button> </button>
<button onClick={() => editor.chain().focus().addColumnAfter().run()} disabled={!editor.can().addColumnAfter()}> <button onClick={() => editor.chain().focus().addColumnAfter().run()} disabled={!editor.can().addColumnAfter()}>
addColumnAfter Add column after
</button> </button>
<button onClick={() => editor.chain().focus().deleteColumn().run()} disabled={!editor.can().deleteColumn()}> <button onClick={() => editor.chain().focus().deleteColumn().run()} disabled={!editor.can().deleteColumn()}>
deleteColumn Delete column
</button> </button>
<button onClick={() => editor.chain().focus().addRowBefore().run()} disabled={!editor.can().addRowBefore()}> <button onClick={() => editor.chain().focus().addRowBefore().run()} disabled={!editor.can().addRowBefore()}>
addRowBefore Add row before
</button> </button>
<button onClick={() => editor.chain().focus().addRowAfter().run()} disabled={!editor.can().addRowAfter()}> <button onClick={() => editor.chain().focus().addRowAfter().run()} disabled={!editor.can().addRowAfter()}>
addRowAfter Add row after
</button> </button>
<button onClick={() => editor.chain().focus().deleteRow().run()} disabled={!editor.can().deleteRow()}> <button onClick={() => editor.chain().focus().deleteRow().run()} disabled={!editor.can().deleteRow()}>
deleteRow Delete row
</button> </button>
<button onClick={() => editor.chain().focus().deleteTable().run()} disabled={!editor.can().deleteTable()}> <button onClick={() => editor.chain().focus().deleteTable().run()} disabled={!editor.can().deleteTable()}>
deleteTable Delete table
</button> </button>
<button onClick={() => editor.chain().focus().mergeCells().run()} disabled={!editor.can().mergeCells()}> <button onClick={() => editor.chain().focus().mergeCells().run()} disabled={!editor.can().mergeCells()}>
mergeCells Merge cells
</button> </button>
<button onClick={() => editor.chain().focus().splitCell().run()} disabled={!editor.can().splitCell()}> <button onClick={() => editor.chain().focus().splitCell().run()} disabled={!editor.can().splitCell()}>
splitCell Split cell
</button> </button>
<button onClick={() => editor.chain().focus().toggleHeaderColumn().run()} disabled={!editor.can().toggleHeaderColumn()}> <button onClick={() => editor.chain().focus().toggleHeaderColumn().run()} disabled={!editor.can().toggleHeaderColumn()}>
toggleHeaderColumn ToggleHeaderColumn
</button> </button>
<button onClick={() => editor.chain().focus().toggleHeaderRow().run()} disabled={!editor.can().toggleHeaderRow()}> <button onClick={() => editor.chain().focus().toggleHeaderRow().run()} disabled={!editor.can().toggleHeaderRow()}>
toggleHeaderRow Toggle header row
</button> </button>
<button onClick={() => editor.chain().focus().toggleHeaderCell().run()} disabled={!editor.can().toggleHeaderCell()}> <button onClick={() => editor.chain().focus().toggleHeaderCell().run()} disabled={!editor.can().toggleHeaderCell()}>
toggleHeaderCell Toggle header cell
</button> </button>
<button onClick={() => editor.chain().focus().mergeOrSplit().run()} disabled={!editor.can().mergeOrSplit()}> <button onClick={() => editor.chain().focus().mergeOrSplit().run()} disabled={!editor.can().mergeOrSplit()}>
mergeOrSplit Merge or split
</button> </button>
<button onClick={() => editor.chain().focus().setCellAttribute('backgroundColor', '#FAF594').run()} disabled={!editor.can().setCellAttribute('backgroundColor', '#FAF594')}> <button onClick={() => editor.chain().focus().setCellAttribute('backgroundColor', '#FAF594').run()} disabled={!editor.can().setCellAttribute('backgroundColor', '#FAF594')}>
setCellAttribute Set cell attribute
</button> </button>
<button onClick={() => editor.chain().focus().fixTables().run()} disabled={!editor.can().fixTables()}> <button onClick={() => editor.chain().focus().fixTables().run()} disabled={!editor.can().fixTables()}>
fixTables Fix tables
</button> </button>
<button onClick={() => editor.chain().focus().goToNextCell().run()} disabled={!editor.can().goToNextCell()}> <button onClick={() => editor.chain().focus().goToNextCell().run()} disabled={!editor.can().goToNextCell()}>
goToNextCell Go to next cell
</button> </button>
<button onClick={() => editor.chain().focus().goToPreviousCell().run()} disabled={!editor.can().goToPreviousCell()}> <button onClick={() => editor.chain().focus().goToPreviousCell().run()} disabled={!editor.can().goToPreviousCell()}>
goToPreviousCell Go to previous cell
</button> </button>
</> </div>
</div>
) )
} }
@ -145,9 +147,9 @@ export default () => {
Have you seen our tables? They are amazing! Have you seen our tables? They are amazing!
</h3> </h3>
<ul> <ul>
<li>tables with rows, cells and headers (optional)</li> <li>Tables with rows, cells and headers (optional)</li>
<li>support for <code>colgroup</code> and <code>rowspan</code></li> <li>Support for <code>colgroup</code> and <code>rowspan</code></li>
<li>and even resizable columns (optional)</li> <li>And even resizable columns (optional)</li>
</ul> </ul>
<p> <p>
Here is an example: Here is an example:
@ -160,20 +162,20 @@ export default () => {
</tr> </tr>
<tr> <tr>
<td>Cyndi Lauper</td> <td>Cyndi Lauper</td>
<td>singer</td> <td>Singer</td>
<td>songwriter</td> <td>Songwriter</td>
<td>actress</td> <td>Actress</td>
</tr> </tr>
<tr> <tr>
<td>Marie Curie</td> <td>Marie Curie</td>
<td>scientist</td> <td>Scientist</td>
<td>chemist</td> <td>Chemist</td>
<td>physicist</td> <td>Physicist</td>
</tr> </tr>
<tr> <tr>
<td>Indira Gandhi</td> <td>Indira Gandhi</td>
<td>prime minister</td> <td>Prime minister</td>
<td colspan="2">politician</td> <td colspan="2">Politician</td>
</tr> </tr>
</tbody> </tbody>
</table> </table>
@ -181,9 +183,9 @@ export default () => {
}) })
return ( return (
<div> <>
<MenuBar editor={editor} /> <MenuBar editor={editor} />
<EditorContent editor={editor} /> <EditorContent editor={editor} />
</div> </>
) )
} }

View File

@ -6,7 +6,7 @@ context('/src/Examples/Tables/React/', () => {
beforeEach(() => { beforeEach(() => {
cy.get('.tiptap').then(([{ editor }]) => { cy.get('.tiptap').then(([{ editor }]) => {
editor.commands.clearContent() editor.commands.clearContent()
cy.get('button').contains('insertTable').click() cy.get('button').contains('Insert table').click()
}) })
}) })
@ -28,15 +28,15 @@ context('/src/Examples/Tables/React/', () => {
}) })
it('adds & delete columns', () => { it('adds & delete columns', () => {
cy.get('button').contains('addColumnBefore').click() cy.get('button').contains('Add column before').click()
cy.get('.tiptap table th') cy.get('.tiptap table th')
.should('have.length', 4) .should('have.length', 4)
cy.get('button').contains('addColumnAfter').click() cy.get('button').contains('Add column after').click()
cy.get('.tiptap table th') cy.get('.tiptap table th')
.should('have.length', 5) .should('have.length', 5)
cy.get('button').contains('deleteColumn') cy.get('button').contains('Delete column')
.click() .click()
.click() .click()
cy.get('.tiptap table th') cy.get('.tiptap table th')
@ -44,15 +44,15 @@ context('/src/Examples/Tables/React/', () => {
}) })
it('adds & delete rows', () => { it('adds & delete rows', () => {
cy.get('button').contains('addRowBefore').click() cy.get('button').contains('Add row before').click()
cy.get('.tiptap table tr') cy.get('.tiptap table tr')
.should('have.length', 4) .should('have.length', 4)
cy.get('button').contains('addRowAfter').click() cy.get('button').contains('Add row after').click()
cy.get('.tiptap table tr') cy.get('.tiptap table tr')
.should('have.length', 5) .should('have.length', 5)
cy.get('button').contains('deleteRow') cy.get('button').contains('Delete row')
.click() .click()
.click() .click()
cy.get('.tiptap table tr') cy.get('.tiptap table tr')
@ -60,21 +60,21 @@ context('/src/Examples/Tables/React/', () => {
}) })
it('should delete table', () => { it('should delete table', () => {
cy.get('button').contains('deleteTable').click() cy.get('button').contains('Delete table').click()
cy.get('.tiptap table').should('not.exist') cy.get('.tiptap table').should('not.exist')
}) })
it('should merge cells', () => { it('should merge cells', () => {
cy.get('.tiptap').type('{shift}{rightArrow}') cy.get('.tiptap').type('{shift}{rightArrow}')
cy.get('button').contains('mergeCells').click() cy.get('button').contains('Merge cells').click()
cy.get('.tiptap table th').should('have.length', 2) cy.get('.tiptap table th').should('have.length', 2)
}) })
it('should split cells', () => { it('should split cells', () => {
cy.get('.tiptap').type('{shift}{rightArrow}') cy.get('.tiptap').type('{shift}{rightArrow}')
cy.get('button').contains('mergeCells').click() cy.get('button').contains('Merge cells').click()
cy.get('.tiptap table th').should('have.length', 2) cy.get('.tiptap table th').should('have.length', 2)
cy.get('button').contains('splitCell').click() cy.get('button').contains('Split cell').click()
cy.get('.tiptap table th').should('have.length', 3) cy.get('.tiptap table th').should('have.length', 3)
}) })
@ -94,11 +94,11 @@ context('/src/Examples/Tables/React/', () => {
it('should merge split', () => { it('should merge split', () => {
cy.get('.tiptap').type('{shift}{rightArrow}') cy.get('.tiptap').type('{shift}{rightArrow}')
cy.get('button').contains('mergeCells').click() cy.get('button').contains('Merge cells').click()
cy.get('.tiptap th[colspan="2"]') cy.get('.tiptap th[colspan="2"]')
.should('exist') .should('exist')
cy.get('button') cy.get('button')
.contains('mergeOrSplit') .contains('Merge or split')
.click() .click()
cy.get('.tiptap th[colspan="2"]') cy.get('.tiptap th[colspan="2"]')
.should('not.exist') .should('not.exist')
@ -106,15 +106,15 @@ context('/src/Examples/Tables/React/', () => {
it('should set cell attribute', () => { it('should set cell attribute', () => {
cy.get('.tiptap').type('{downArrow}') cy.get('.tiptap').type('{downArrow}')
cy.get('button').contains('setCellAttribute').click() cy.get('button').contains('Set cell attribute').click()
cy.get('.tiptap table td[style]').should('have.attr', 'style', 'background-color: #FAF594') cy.get('.tiptap table td[style]').should('have.attr', 'style', 'background-color: #FAF594')
}) })
it('should move focus to next or prev cell', () => { it('should move focus to next or prev cell', () => {
cy.get('.tiptap').type('Column 1') cy.get('.tiptap').type('Column 1')
cy.get('button').contains('goToNextCell').click() cy.get('button').contains('Go to next cell').click()
cy.get('.tiptap').type('Column 2') cy.get('.tiptap').type('Column 2')
cy.get('button').contains('goToPreviousCell').click() cy.get('button').contains('Go to previous cell').click()
cy.get('.tiptap th').then(elements => { cy.get('.tiptap th').then(elements => {
expect(elements[0].innerText).to.equal('Column 1') expect(elements[0].innerText).to.equal('Column 1')

View File

@ -1,16 +1,22 @@
/* Basic editor styles */ /* Basic editor styles */
.tiptap { .tiptap {
margin: 1rem 0; :first-child {
margin-top: 0;
> * + * {
margin-top: 0.75em;
} }
/* List styles */
ul, ul,
ol { ol {
padding: 0 1rem; padding: 0 1rem;
margin: 1.25rem 1rem 1.25rem 0.4rem;
li p {
margin-top: 0.25em;
margin-bottom: 0.25em;
}
} }
/* Heading styles */
h1, h1,
h2, h2,
h3, h3,
@ -18,62 +24,87 @@
h5, h5,
h6 { h6 {
line-height: 1.1; line-height: 1.1;
margin-top: 2.5rem;
text-wrap: pretty;
} }
h1,
h2 {
margin-top: 3.5rem;
margin-bottom: 1.5rem;
}
h1 {
font-size: 1.4rem;
}
h2 {
font-size: 1.2rem;
}
h3 {
font-size: 1.1rem;
}
h4,
h5,
h6 {
font-size: 1rem;
}
/* Code and preformatted text styles */
code { code {
background-color: rgba(#616161, 0.1); background-color: var(--purple-light);
color: #616161; border-radius: 0.4rem;
color: var(--black);
font-size: 0.85rem;
padding: 0.25em 0.3em;
} }
pre { pre {
background: #0D0D0D; background: var(--black);
color: #FFF;
font-family: 'JetBrainsMono', monospace;
padding: 0.75rem 1rem;
border-radius: 0.5rem; border-radius: 0.5rem;
color: var(--white);
font-family: 'JetBrainsMono', monospace;
margin: 1.5rem 0;
padding: 0.75rem 1rem;
code { code {
color: inherit;
padding: 0;
background: none; background: none;
color: inherit;
font-size: 0.8rem; font-size: 0.8rem;
padding: 0;
} }
} }
img {
max-width: 100%;
height: auto;
}
blockquote { blockquote {
border-left: 3px solid var(--gray-3);
margin: 1.5rem 0;
padding-left: 1rem; padding-left: 1rem;
border-left: 2px solid rgba(#0D0D0D, 0.1);
} }
hr { hr {
border: none; border: none;
border-top: 2px solid rgba(#0D0D0D, 0.1); border-top: 1px solid var(--gray-2);
margin: 2rem 0; margin: 2rem 0;
} }
}
/* Table-specific styling */ /* Table-specific styling */
.tiptap {
table { table {
border-collapse: collapse; border-collapse: collapse;
table-layout: fixed;
width: 100%;
margin: 0; margin: 0;
overflow: hidden; overflow: hidden;
table-layout: fixed;
width: 100%;
td, td,
th { th {
min-width: 1em; border: 1px solid var(--gray-3);
border: 2px solid #ced4da;
padding: 3px 5px;
vertical-align: top;
box-sizing: border-box; box-sizing: border-box;
min-width: 1em;
padding: 6px 8px;
position: relative; position: relative;
vertical-align: top;
> * { > * {
margin-bottom: 0; margin-bottom: 0;
@ -81,37 +112,38 @@
} }
th { th {
background-color: var(--gray-1);
font-weight: bold; font-weight: bold;
text-align: left; text-align: left;
background-color: #f1f3f5;
} }
.selectedCell:after { .selectedCell:after {
z-index: 2; background: var(--gray-2);
position: absolute;
content: ""; content: "";
left: 0; right: 0; top: 0; bottom: 0; left: 0; right: 0; top: 0; bottom: 0;
background: rgba(200, 200, 255, 0.4);
pointer-events: none; pointer-events: none;
position: absolute;
z-index: 2;
} }
.column-resize-handle { .column-resize-handle {
background-color: var(--purple);
bottom: -2px;
pointer-events: none;
position: absolute; position: absolute;
right: -2px; right: -2px;
top: 0; top: 0;
bottom: -2px;
width: 4px; width: 4px;
background-color: #adf;
pointer-events: none;
} }
} }
}
.tableWrapper { .tableWrapper {
margin: 1.5rem 0;
overflow-x: auto; overflow-x: auto;
} }
.resize-cursor { .resize-cursor {
cursor: ew-resize; cursor: ew-resize;
cursor: col-resize; cursor: col-resize;
}
} }

View File

@ -6,7 +6,7 @@ context('/src/Examples/Tables/Vue/', () => {
beforeEach(() => { beforeEach(() => {
cy.get('.tiptap').then(([{ editor }]) => { cy.get('.tiptap').then(([{ editor }]) => {
editor.commands.clearContent() editor.commands.clearContent()
cy.get('button').contains('insertTable').click() cy.get('button').contains('Insert table').click()
}) })
}) })
@ -28,15 +28,15 @@ context('/src/Examples/Tables/Vue/', () => {
}) })
it('adds & delete columns', () => { it('adds & delete columns', () => {
cy.get('button').contains('addColumnBefore').click() cy.get('button').contains('Add column before').click()
cy.get('.tiptap table th') cy.get('.tiptap table th')
.should('have.length', 4) .should('have.length', 4)
cy.get('button').contains('addColumnAfter').click() cy.get('button').contains('Add column after').click()
cy.get('.tiptap table th') cy.get('.tiptap table th')
.should('have.length', 5) .should('have.length', 5)
cy.get('button').contains('deleteColumn') cy.get('button').contains('Delete column')
.click() .click()
.click() .click()
cy.get('.tiptap table th') cy.get('.tiptap table th')
@ -44,15 +44,15 @@ context('/src/Examples/Tables/Vue/', () => {
}) })
it('adds & delete rows', () => { it('adds & delete rows', () => {
cy.get('button').contains('addRowBefore').click() cy.get('button').contains('Add row before').click()
cy.get('.tiptap table tr') cy.get('.tiptap table tr')
.should('have.length', 4) .should('have.length', 4)
cy.get('button').contains('addRowAfter').click() cy.get('button').contains('Add row after').click()
cy.get('.tiptap table tr') cy.get('.tiptap table tr')
.should('have.length', 5) .should('have.length', 5)
cy.get('button').contains('deleteRow') cy.get('button').contains('Delete row')
.click() .click()
.click() .click()
cy.get('.tiptap table tr') cy.get('.tiptap table tr')
@ -60,21 +60,21 @@ context('/src/Examples/Tables/Vue/', () => {
}) })
it('should delete table', () => { it('should delete table', () => {
cy.get('button').contains('deleteTable').click() cy.get('button').contains('Delete table').click()
cy.get('.tiptap table').should('not.exist') cy.get('.tiptap table').should('not.exist')
}) })
it('should merge cells', () => { it('should merge cells', () => {
cy.get('.tiptap').type('{shift}{rightArrow}') cy.get('.tiptap').type('{shift}{rightArrow}')
cy.get('button').contains('mergeCells').click() cy.get('button').contains('Merge cells').click()
cy.get('.tiptap table th').should('have.length', 2) cy.get('.tiptap table th').should('have.length', 2)
}) })
it('should split cells', () => { it('should split cells', () => {
cy.get('.tiptap').type('{shift}{rightArrow}') cy.get('.tiptap').type('{shift}{rightArrow}')
cy.get('button').contains('mergeCells').click() cy.get('button').contains('Merge cells').click()
cy.get('.tiptap table th').should('have.length', 2) cy.get('.tiptap table th').should('have.length', 2)
cy.get('button').contains('splitCell').click() cy.get('button').contains('Split cell').click()
cy.get('.tiptap table th').should('have.length', 3) cy.get('.tiptap table th').should('have.length', 3)
}) })
@ -94,11 +94,11 @@ context('/src/Examples/Tables/Vue/', () => {
it('should merge split', () => { it('should merge split', () => {
cy.get('.tiptap').type('{shift}{rightArrow}') cy.get('.tiptap').type('{shift}{rightArrow}')
cy.get('button').contains('mergeCells').click() cy.get('button').contains('Merge cells').click()
cy.get('.tiptap th[colspan="2"]') cy.get('.tiptap th[colspan="2"]')
.should('exist') .should('exist')
cy.get('button') cy.get('button')
.contains('mergeOrSplit') .contains('Merge or split')
.click() .click()
cy.get('.tiptap th[colspan="2"]') cy.get('.tiptap th[colspan="2"]')
.should('not.exist') .should('not.exist')
@ -106,15 +106,15 @@ context('/src/Examples/Tables/Vue/', () => {
it('should set cell attribute', () => { it('should set cell attribute', () => {
cy.get('.tiptap').type('{downArrow}') cy.get('.tiptap').type('{downArrow}')
cy.get('button').contains('setCellAttribute').click() cy.get('button').contains('Set cell attribute').click()
cy.get('.tiptap table td[style]').should('have.attr', 'style', 'background-color: #FAF594') cy.get('.tiptap table td[style]').should('have.attr', 'style', 'background-color: #FAF594')
}) })
it('should move focus to next or prev cell', () => { it('should move focus to next or prev cell', () => {
cy.get('.tiptap').type('Column 1') cy.get('.tiptap').type('Column 1')
cy.get('button').contains('goToNextCell').click() cy.get('button').contains('Go to next cell').click()
cy.get('.tiptap').type('Column 2') cy.get('.tiptap').type('Column 2')
cy.get('button').contains('goToPreviousCell').click() cy.get('button').contains('Go to previous cell').click()
cy.get('.tiptap th').then(elements => { cy.get('.tiptap th').then(elements => {
expect(elements[0].innerText).to.equal('Column 1') expect(elements[0].innerText).to.equal('Column 1')

View File

@ -1,61 +1,70 @@
<template> <template>
<div v-if="editor"> <div v-if="editor" class="container">
<div class="control-group">
<div class="button-group">
<button @click="editor.chain().focus().insertTable({ rows: 3, cols: 3, withHeaderRow: true }).run()"> <button @click="editor.chain().focus().insertTable({ rows: 3, cols: 3, withHeaderRow: true }).run()">
insertTable Insert table
</button>
<button
@click="editor.chain().focus().insertContent(tableHTML, { parseOptions: { preserveWhitespace: false }}).run()"
>
Insert HTML table
</button> </button>
<button @click="editor.chain().focus().addColumnBefore().run()" :disabled="!editor.can().addColumnBefore()"> <button @click="editor.chain().focus().addColumnBefore().run()" :disabled="!editor.can().addColumnBefore()">
addColumnBefore Add column before
</button> </button>
<button @click="editor.chain().focus().addColumnAfter().run()" :disabled="!editor.can().addColumnAfter()"> <button @click="editor.chain().focus().addColumnAfter().run()" :disabled="!editor.can().addColumnAfter()">
addColumnAfter Add column after
</button> </button>
<button @click="editor.chain().focus().deleteColumn().run()" :disabled="!editor.can().deleteColumn()"> <button @click="editor.chain().focus().deleteColumn().run()" :disabled="!editor.can().deleteColumn()">
deleteColumn Delete column
</button> </button>
<button @click="editor.chain().focus().addRowBefore().run()" :disabled="!editor.can().addRowBefore()"> <button @click="editor.chain().focus().addRowBefore().run()" :disabled="!editor.can().addRowBefore()">
addRowBefore Add row before
</button> </button>
<button @click="editor.chain().focus().addRowAfter().run()" :disabled="!editor.can().addRowAfter()"> <button @click="editor.chain().focus().addRowAfter().run()" :disabled="!editor.can().addRowAfter()">
addRowAfter Add row after
</button> </button>
<button @click="editor.chain().focus().deleteRow().run()" :disabled="!editor.can().deleteRow()"> <button @click="editor.chain().focus().deleteRow().run()" :disabled="!editor.can().deleteRow()">
deleteRow Delete row
</button> </button>
<button @click="editor.chain().focus().deleteTable().run()" :disabled="!editor.can().deleteTable()"> <button @click="editor.chain().focus().deleteTable().run()" :disabled="!editor.can().deleteTable()">
deleteTable Delete table
</button> </button>
<button @click="editor.chain().focus().mergeCells().run()" :disabled="!editor.can().mergeCells()"> <button @click="editor.chain().focus().mergeCells().run()" :disabled="!editor.can().mergeCells()">
mergeCells Merge cells
</button> </button>
<button @click="editor.chain().focus().splitCell().run()" :disabled="!editor.can().splitCell()"> <button @click="editor.chain().focus().splitCell().run()" :disabled="!editor.can().splitCell()">
splitCell Split cell
</button> </button>
<button @click="editor.chain().focus().toggleHeaderColumn().run()" :disabled="!editor.can().toggleHeaderColumn()"> <button @click="editor.chain().focus().toggleHeaderColumn().run()" :disabled="!editor.can().toggleHeaderColumn()">
toggleHeaderColumn Toggle header column
</button> </button>
<button @click="editor.chain().focus().toggleHeaderRow().run()" :disabled="!editor.can().toggleHeaderRow()"> <button @click="editor.chain().focus().toggleHeaderRow().run()" :disabled="!editor.can().toggleHeaderRow()">
toggleHeaderRow Toggle header row
</button> </button>
<button @click="editor.chain().focus().toggleHeaderCell().run()" :disabled="!editor.can().toggleHeaderCell()"> <button @click="editor.chain().focus().toggleHeaderCell().run()" :disabled="!editor.can().toggleHeaderCell()">
toggleHeaderCell Toggle header cell
</button> </button>
<button @click="editor.chain().focus().mergeOrSplit().run()" :disabled="!editor.can().mergeOrSplit()"> <button @click="editor.chain().focus().mergeOrSplit().run()" :disabled="!editor.can().mergeOrSplit()">
mergeOrSplit Merge or split
</button> </button>
<button @click="editor.chain().focus().setCellAttribute('backgroundColor', '#FAF594').run()" :disabled="!editor.can().setCellAttribute('backgroundColor', '#FAF594')"> <button @click="editor.chain().focus().setCellAttribute('backgroundColor', '#FAF594').run()" :disabled="!editor.can().setCellAttribute('backgroundColor', '#FAF594')">
setCellAttribute Set cell attribute
</button> </button>
<button @click="editor.chain().focus().fixTables().run()" :disabled="!editor.can().fixTables()"> <button @click="editor.chain().focus().fixTables().run()" :disabled="!editor.can().fixTables()">
fixTables Fix tables
</button> </button>
<button @click="editor.chain().focus().goToNextCell().run()" :disabled="!editor.can().goToNextCell()"> <button @click="editor.chain().focus().goToNextCell().run()" :disabled="!editor.can().goToNextCell()">
goToNextCell Go to next cell
</button> </button>
<button @click="editor.chain().focus().goToPreviousCell().run()" :disabled="!editor.can().goToPreviousCell()"> <button @click="editor.chain().focus().goToPreviousCell().run()" :disabled="!editor.can().goToPreviousCell()">
goToPreviousCell Go to previous cell
</button> </button>
</div> </div>
</div>
<editor-content :editor="editor" /> <editor-content :editor="editor" />
</div>
</template> </template>
<script> <script>
@ -95,6 +104,29 @@ export default {
data() { data() {
return { return {
editor: null, editor: null,
tableHTML: `
<table style="width:100%">
<tr>
<th>Firstname</th>
<th>Lastname</th>
<th>Age</th>
</tr>
<tr>
<td>Jill</td>
<td>Smith</td>
<td>50</td>
</tr>
<tr>
<td>Eve</td>
<td>Jackson</td>
<td>94</td>
</tr>
<tr>
<td>John</td>
<td>Doe</td>
<td>80</td>
</tr>
</table>`,
} }
}, },
@ -117,9 +149,9 @@ export default {
Have you seen our tables? They are amazing! Have you seen our tables? They are amazing!
</h3> </h3>
<ul> <ul>
<li>tables with rows, cells and headers (optional)</li> <li>Tables with rows, cells and headers (optional)</li>
<li>support for <code>colgroup</code> and <code>rowspan</code></li> <li>Support for <code>colgroup</code> and <code>rowspan</code></li>
<li>and even resizable columns (optional)</li> <li>And even resizable columns (optional)</li>
</ul> </ul>
<p> <p>
Here is an example: Here is an example:
@ -132,20 +164,20 @@ export default {
</tr> </tr>
<tr> <tr>
<td>Cyndi Lauper</td> <td>Cyndi Lauper</td>
<td>singer</td> <td>Singer</td>
<td>songwriter</td> <td>Songwriter</td>
<td>actress</td> <td>Actress</td>
</tr> </tr>
<tr> <tr>
<td>Marie Curie</td> <td>Marie Curie</td>
<td>scientist</td> <td>Scientist</td>
<td>chemist</td> <td>Chemist</td>
<td>physicist</td> <td>Physicist</td>
</tr> </tr>
<tr> <tr>
<td>Indira Gandhi</td> <td>Indira Gandhi</td>
<td>prime minister</td> <td>Prime minister</td>
<td colspan="2">politician</td> <td colspan="2">Politician</td>
</tr> </tr>
</tbody> </tbody>
</table> </table>
@ -162,17 +194,23 @@ export default {
<style lang="scss"> <style lang="scss">
/* Basic editor styles */ /* Basic editor styles */
.tiptap { .tiptap {
margin: 1rem 0; :first-child {
margin-top: 0;
> * + * {
margin-top: 0.75em;
} }
/* List styles */
ul, ul,
ol { ol {
padding: 0 1rem; padding: 0 1rem;
margin: 1.25rem 1rem 1.25rem 0.4rem;
li p {
margin-top: 0.25em;
margin-bottom: 0.25em;
}
} }
/* Heading styles */
h1, h1,
h2, h2,
h3, h3,
@ -180,62 +218,87 @@ export default {
h5, h5,
h6 { h6 {
line-height: 1.1; line-height: 1.1;
margin-top: 2.5rem;
text-wrap: pretty;
} }
h1,
h2 {
margin-top: 3.5rem;
margin-bottom: 1.5rem;
}
h1 {
font-size: 1.4rem;
}
h2 {
font-size: 1.2rem;
}
h3 {
font-size: 1.1rem;
}
h4,
h5,
h6 {
font-size: 1rem;
}
/* Code and preformatted text styles */
code { code {
background-color: rgba(#616161, 0.1); background-color: var(--purple-light);
color: #616161; border-radius: 0.4rem;
color: var(--black);
font-size: 0.85rem;
padding: 0.25em 0.3em;
} }
pre { pre {
background: #0D0D0D; background: var(--black);
color: #FFF;
font-family: 'JetBrainsMono', monospace;
padding: 0.75rem 1rem;
border-radius: 0.5rem; border-radius: 0.5rem;
color: var(--white);
font-family: 'JetBrainsMono', monospace;
margin: 1.5rem 0;
padding: 0.75rem 1rem;
code { code {
color: inherit;
padding: 0;
background: none; background: none;
color: inherit;
font-size: 0.8rem; font-size: 0.8rem;
padding: 0;
} }
} }
img {
max-width: 100%;
height: auto;
}
blockquote { blockquote {
border-left: 3px solid var(--gray-3);
margin: 1.5rem 0;
padding-left: 1rem; padding-left: 1rem;
border-left: 2px solid rgba(#0D0D0D, 0.1);
} }
hr { hr {
border: none; border: none;
border-top: 2px solid rgba(#0D0D0D, 0.1); border-top: 1px solid var(--gray-2);
margin: 2rem 0; margin: 2rem 0;
} }
}
/* Table-specific styling */ /* Table-specific styling */
.tiptap {
table { table {
border-collapse: collapse; border-collapse: collapse;
table-layout: fixed;
width: 100%;
margin: 0; margin: 0;
overflow: hidden; overflow: hidden;
table-layout: fixed;
width: 100%;
td, td,
th { th {
min-width: 1em; border: 1px solid var(--gray-3);
border: 2px solid #ced4da;
padding: 3px 5px;
vertical-align: top;
box-sizing: border-box; box-sizing: border-box;
min-width: 1em;
padding: 6px 8px;
position: relative; position: relative;
vertical-align: top;
> * { > * {
margin-bottom: 0; margin-bottom: 0;
@ -243,42 +306,39 @@ export default {
} }
th { th {
background-color: var(--gray-1);
font-weight: bold; font-weight: bold;
text-align: left; text-align: left;
background-color: #f1f3f5;
} }
.selectedCell:after { .selectedCell:after {
z-index: 2; background: var(--gray-2);
position: absolute;
content: ""; content: "";
left: 0; right: 0; top: 0; bottom: 0; left: 0; right: 0; top: 0; bottom: 0;
background: rgba(200, 200, 255, 0.4);
pointer-events: none; pointer-events: none;
position: absolute;
z-index: 2;
} }
.column-resize-handle { .column-resize-handle {
background-color: var(--purple);
bottom: -2px;
pointer-events: none;
position: absolute; position: absolute;
right: -2px; right: -2px;
top: 0; top: 0;
bottom: -2px;
width: 4px; width: 4px;
background-color: #adf; }
pointer-events: none;
} }
p { .tableWrapper {
margin: 0; margin: 1.5rem 0;
}
}
}
.tableWrapper {
overflow-x: auto; overflow-x: auto;
} }
.resize-cursor { .resize-cursor {
cursor: ew-resize; cursor: ew-resize;
cursor: col-resize; cursor: col-resize;
}
} }
</style> </style>

View File

@ -1,10 +1,30 @@
ul[data-type="taskList"] { /* Basic editor styles */
.tiptap {
:first-child {
margin-top: 0;
}
/* List styles */
ul,
ol {
padding: 0 1rem;
margin: 1.25rem 1rem 1.25rem 0.4rem;
li p {
margin-top: 0.25em;
margin-bottom: 0.25em;
}
}
/* Task list specific styles */
ul[data-type="taskList"] {
list-style: none; list-style: none;
margin-left: 0;
padding: 0; padding: 0;
li { li {
display: flex;
align-items: center; align-items: center;
display: flex;
> label { > label {
flex: 0 0 auto; flex: 0 0 auto;
@ -20,4 +40,5 @@ ul[data-type="taskList"] {
input[type="checkbox"] { input[type="checkbox"] {
cursor: pointer; cursor: pointer;
} }
}
} }

View File

@ -59,13 +59,33 @@ export default {
</script> </script>
<style lang="scss"> <style lang="scss">
ul[data-type="taskList"] { /* Basic editor styles */
.tiptap {
:first-child {
margin-top: 0;
}
/* List styles */
ul,
ol {
padding: 0 1rem;
margin: 1.25rem 1rem 1.25rem 0.4rem;
li p {
margin-top: 0.25em;
margin-bottom: 0.25em;
}
}
/* Task list specific styles */
ul[data-type="taskList"] {
list-style: none; list-style: none;
margin-left: 0;
padding: 0; padding: 0;
li { li {
display: flex;
align-items: center; align-items: center;
display: flex;
> label { > label {
flex: 0 0 auto; flex: 0 0 auto;
@ -81,5 +101,6 @@ ul[data-type="taskList"] {
input[type="checkbox"] { input[type="checkbox"] {
cursor: pointer; cursor: pointer;
} }
}
} }
</style> </style>

View File

@ -1,70 +1,72 @@
<template> <template>
<div> <div v-if="editor" class="container">
<div v-if="editor" class="menu"> <div class="control-group">
<div class="button-group">
<button @click="editor.chain().focus().toggleBold().run()" :class="{ 'is-active': editor.isActive('bold') }"> <button @click="editor.chain().focus().toggleBold().run()" :class="{ 'is-active': editor.isActive('bold') }">
bold Bold
</button> </button>
<button @click="editor.chain().focus().toggleItalic().run()" :class="{ 'is-active': editor.isActive('italic') }"> <button @click="editor.chain().focus().toggleItalic().run()" :class="{ 'is-active': editor.isActive('italic') }">
italic Italic
</button> </button>
<button @click="editor.chain().focus().toggleStrike().run()" :class="{ 'is-active': editor.isActive('strike') }"> <button @click="editor.chain().focus().toggleStrike().run()" :class="{ 'is-active': editor.isActive('strike') }">
strike Strike
</button> </button>
<button @click="editor.chain().focus().toggleCode().run()" :class="{ 'is-active': editor.isActive('code') }"> <button @click="editor.chain().focus().toggleCode().run()" :class="{ 'is-active': editor.isActive('code') }">
code Code
</button> </button>
<button @click="editor.chain().focus().unsetAllMarks().run()"> <button @click="editor.chain().focus().unsetAllMarks().run()">
clear marks Clear marks
</button> </button>
<button @click="editor.chain().focus().clearNodes().run()"> <button @click="editor.chain().focus().clearNodes().run()">
clear nodes Clear nodes
</button> </button>
<button @click="editor.chain().focus().setParagraph().run()" :class="{ 'is-active': editor.isActive('paragraph') }"> <button @click="editor.chain().focus().setParagraph().run()" :class="{ 'is-active': editor.isActive('paragraph') }">
paragraph Paragraph
</button> </button>
<button @click="editor.chain().focus().toggleHeading({ level: 1 }).run()" :class="{ 'is-active': editor.isActive('heading', { level: 1 }) }"> <button @click="editor.chain().focus().toggleHeading({ level: 1 }).run()" :class="{ 'is-active': editor.isActive('heading', { level: 1 }) }">
h1 H1
</button> </button>
<button @click="editor.chain().focus().toggleHeading({ level: 2 }).run()" :class="{ 'is-active': editor.isActive('heading', { level: 2 }) }"> <button @click="editor.chain().focus().toggleHeading({ level: 2 }).run()" :class="{ 'is-active': editor.isActive('heading', { level: 2 }) }">
h2 H2
</button> </button>
<button @click="editor.chain().focus().toggleHeading({ level: 3 }).run()" :class="{ 'is-active': editor.isActive('heading', { level: 3 }) }"> <button @click="editor.chain().focus().toggleHeading({ level: 3 }).run()" :class="{ 'is-active': editor.isActive('heading', { level: 3 }) }">
h3 H3
</button> </button>
<button @click="editor.chain().focus().toggleHeading({ level: 4 }).run()" :class="{ 'is-active': editor.isActive('heading', { level: 4 }) }"> <button @click="editor.chain().focus().toggleHeading({ level: 4 }).run()" :class="{ 'is-active': editor.isActive('heading', { level: 4 }) }">
h4 H4
</button> </button>
<button @click="editor.chain().focus().toggleHeading({ level: 5 }).run()" :class="{ 'is-active': editor.isActive('heading', { level: 5 }) }"> <button @click="editor.chain().focus().toggleHeading({ level: 5 }).run()" :class="{ 'is-active': editor.isActive('heading', { level: 5 }) }">
h5 H5
</button> </button>
<button @click="editor.chain().focus().toggleHeading({ level: 6 }).run()" :class="{ 'is-active': editor.isActive('heading', { level: 6 }) }"> <button @click="editor.chain().focus().toggleHeading({ level: 6 }).run()" :class="{ 'is-active': editor.isActive('heading', { level: 6 }) }">
h6 h6
</button> </button>
<button @click="editor.chain().focus().toggleBulletList().run()" :class="{ 'is-active': editor.isActive('bulletList') }"> <button @click="editor.chain().focus().toggleBulletList().run()" :class="{ 'is-active': editor.isActive('bulletList') }">
bullet list Bullet list
</button> </button>
<button @click="editor.chain().focus().toggleOrderedList().run()" :class="{ 'is-active': editor.isActive('orderedList') }"> <button @click="editor.chain().focus().toggleOrderedList().run()" :class="{ 'is-active': editor.isActive('orderedList') }">
ordered list Ordered list
</button> </button>
<button @click="editor.chain().focus().toggleCodeBlock().run()" :class="{ 'is-active': editor.isActive('codeBlock') }"> <button @click="editor.chain().focus().toggleCodeBlock().run()" :class="{ 'is-active': editor.isActive('codeBlock') }">
code block Code block
</button> </button>
<button @click="editor.chain().focus().toggleBlockquote().run()" :class="{ 'is-active': editor.isActive('blockquote') }"> <button @click="editor.chain().focus().toggleBlockquote().run()" :class="{ 'is-active': editor.isActive('blockquote') }">
blockquote Blockquote
</button> </button>
<button @click="editor.chain().focus().setHorizontalRule().run()"> <button @click="editor.chain().focus().setHorizontalRule().run()">
horizontal rule Horizontal rule
</button> </button>
<button @click="editor.chain().focus().setHardBreak().run()"> <button @click="editor.chain().focus().setHardBreak().run()">
hard break Hard break
</button> </button>
<button @click="editor.chain().focus().undo().run()"> <button @click="editor.chain().focus().undo().run()">
undo Undo
</button> </button>
<button @click="editor.chain().focus().redo().run()"> <button @click="editor.chain().focus().redo().run()">
redo Redo
</button> </button>
</div> </div>
</div>
<editor-content :editor="editor" /> <editor-content :editor="editor" />
</div> </div>
</template> </template>
@ -176,7 +178,7 @@ export default {
Hi there, Hi there,
</h2> </h2>
<p> <p>
this is a <em>basic</em> example of <strong>tiptap</strong>. Sure, there are all kind of basic text styles youd probably expect from a text editor. But wait until you see the lists: this is a <em>basic</em> example of <strong>Tiptap</strong>. Sure, there are all kind of basic text styles youd probably expect from a text editor. But wait until you see the lists:
</p> </p>
<ul> <ul>
<li> <li>
@ -232,26 +234,26 @@ export default {
</tr> </tr>
<tr> <tr>
<td>Cyndi Lauper</td> <td>Cyndi Lauper</td>
<td>singer</td> <td>Singer</td>
<td>songwriter</td> <td>Songwriter</td>
<td>actress</td> <td>Actress</td>
</tr> </tr>
<tr> <tr>
<td>Marie Curie</td> <td>Marie Curie</td>
<td>scientist</td> <td>Scientist</td>
<td>chemist</td> <td>Chemist</td>
<td>physicist</td> <td>Physicist</td>
</tr> </tr>
<tr> <tr>
<td>Indira Gandhi</td> <td>Indira Gandhi</td>
<td>prime minister</td> <td>Prime minister</td>
<td colspan="2">politician</td> <td colspan="2">Politician</td>
</tr> </tr>
</tbody> </tbody>
</table> </table>
<p>This is a basic example of implementing images. Drag to re-order.</p> <p>This is a basic example of implementing images. Drag to re-order.</p>
<img src="https://source.unsplash.com/8xznAGy4HcY/800x400" /> <img src="https://placehold.co/800x400" />
<img src="https://source.unsplash.com/K9QHL52rE2k/800x400" /> <img src="https://placehold.co/800x400/6A00F5/white" />
`, `,
}) })
}, },
@ -263,16 +265,73 @@ export default {
</script> </script>
<style lang="scss"> <style lang="scss">
/* Basic editor styles */
.tiptap { .tiptap {
> * + * { :first-child {
margin-top: 0.75em; margin-top: 0;
} }
/* Placeholder (on every new line) */
.is-empty::before {
color: var(--gray-4);
content: attr(data-placeholder);
float: left;
height: 0;
pointer-events: none;
}
a {
color: var(--purple);
cursor: pointer;
&:hover {
color: var(--purple-contrast);
}
}
/* List styles */
ul, ul,
ol { ol {
padding: 0 1rem; padding: 0 1rem;
margin: 1.25rem 1rem 1.25rem 0.4rem;
li p {
margin-top: 0.25em;
margin-bottom: 0.25em;
}
} }
/* Task list specific styles */
ul[data-type="taskList"] {
list-style: none;
margin-left: 0;
padding: 0;
li {
align-items: flex-start;
display: flex;
> label {
flex: 0 0 auto;
margin-right: 0.5rem;
user-select: none;
}
> div {
flex: 1 1 auto;
}
}
input[type="checkbox"] {
cursor: pointer;
}
ul[data-type="taskList"] {
margin: 0;
}
}
/* Heading styles */
h1, h1,
h2, h2,
h3, h3,
@ -280,27 +339,64 @@ export default {
h5, h5,
h6 { h6 {
line-height: 1.1; line-height: 1.1;
margin-top: 2.5rem;
text-wrap: pretty;
} }
h1,
h2 {
margin-top: 3.5rem;
margin-bottom: 1.5rem;
}
h1 {
font-size: 1.4rem;
}
h2 {
font-size: 1.2rem;
}
h3 {
font-size: 1.1rem;
}
h4,
h5,
h6 {
font-size: 1rem;
}
/* Display empty p */
p:empty::before {
content: '\00a0';
}
/* Code and preformatted text styles */
code { code {
background-color: rgba(#616161, 0.1); background-color: var(--purple-light);
color: #616161; border-radius: 0.4rem;
color: var(--black);
font-size: 0.85rem;
padding: 0.25em 0.3em;
} }
pre { pre {
background: #0D0D0D; background: var(--black);
color: #FFF;
font-family: 'JetBrainsMono', monospace;
padding: 0.75rem 1rem;
border-radius: 0.5rem; border-radius: 0.5rem;
color: var(--white);
font-family: 'JetBrainsMono', monospace;
margin: 1.5rem 0;
padding: 0.75rem 1rem;
code { code {
color: inherit;
padding: 0;
background: none; background: none;
color: inherit;
font-size: 0.8rem; font-size: 0.8rem;
padding: 0;
} }
/* Code styling */
.hljs-comment, .hljs-comment,
.hljs-quote { .hljs-quote {
color: #616161; color: #616161;
@ -316,7 +412,7 @@ export default {
.hljs-name, .hljs-name,
.hljs-selector-id, .hljs-selector-id,
.hljs-selector-class { .hljs-selector-class {
color: #F98181; color: #f98181;
} }
.hljs-number, .hljs-number,
@ -326,23 +422,23 @@ export default {
.hljs-literal, .hljs-literal,
.hljs-type, .hljs-type,
.hljs-params { .hljs-params {
color: #FBBC88; color: #fbbc88;
} }
.hljs-string, .hljs-string,
.hljs-symbol, .hljs-symbol,
.hljs-bullet { .hljs-bullet {
color: #B9F18D; color: #b9f18d;
} }
.hljs-title, .hljs-title,
.hljs-section { .hljs-section {
color: #FAF594; color: #faf594;
} }
.hljs-keyword, .hljs-keyword,
.hljs-selector-tag { .hljs-selector-tag {
color: #70CFF8; color: #70cff8;
} }
.hljs-emphasis { .hljs-emphasis {
@ -354,37 +450,91 @@ export default {
} }
} }
.code-block {
position: relative;
select {
position: absolute;
background-color: var(--white);
background-image: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path fill="Black" d="M7 10l5 5 5-5z"/></svg>');
right: 0.5rem;
top: 0.5rem;
}
}
mark {
background-color: #FAF594;
border-radius: 0.4rem;
box-decoration-break: clone;
padding: 0.1rem 0.3rem;
}
img { img {
max-width: 100%; display: block;
height: auto; height: auto;
margin: 1.5rem 0;
max-width: 100%;
&.ProseMirror-selectednode {
outline: 3px solid var(--purple);
transition: outline 0.15s;
}
} }
blockquote { blockquote {
border-left: 3px solid var(--gray-3);
margin: 1.5rem 0;
padding-left: 1rem; padding-left: 1rem;
border-left: 2px solid rgba(#0D0D0D, 0.1);
} }
hr { hr {
border: none; border: none;
border-top: 2px solid rgba(#0D0D0D, 0.1); border-top: 1px solid var(--gray-2);
margin: 2rem 0; margin: 2rem 0;
} }
.mention {
background-color: var(--purple-light);
border-radius: 0.4rem;
box-decoration-break: clone;
color: var(--purple);
padding: 0.1rem 0.3rem;
}
/* Color swatches */
.color {
white-space: nowrap;
&::before {
background-color: var(--color);
border: 1px solid rgba(128, 128, 128, 0.3);
border-radius: 2px;
content: " ";
display: inline-block;
height: 1em;
margin-bottom: 0.15em;
margin-right: 0.1em;
vertical-align: middle;
width: 1em;
}
}
/* Table-specific styling */
table { table {
border-collapse: collapse; border-collapse: collapse;
table-layout: fixed;
width: 100%;
margin: 0; margin: 0;
overflow: hidden; overflow: hidden;
table-layout: fixed;
width: 100%;
td, td,
th { th {
min-width: 1em; border: 1px solid var(--gray-3);
border: 2px solid #ced4da;
padding: 3px 5px;
vertical-align: top;
box-sizing: border-box; box-sizing: border-box;
min-width: 1em;
padding: 6px 8px;
position: relative; position: relative;
vertical-align: top;
> * { > * {
margin-bottom: 0; margin-bottom: 0;
@ -392,87 +542,91 @@ export default {
} }
th { th {
background-color: var(--gray-1);
font-weight: bold; font-weight: bold;
text-align: left; text-align: left;
background-color: #f1f3f5;
} }
.selectedCell:after { .selectedCell:after {
z-index: 2; background: var(--gray-2);
position: absolute;
content: ""; content: "";
left: 0; right: 0; top: 0; bottom: 0; left: 0; right: 0; top: 0; bottom: 0;
background: rgba(200, 200, 255, 0.4);
pointer-events: none; pointer-events: none;
position: absolute;
z-index: 2;
} }
.column-resize-handle { .column-resize-handle {
background-color: var(--purple);
bottom: -2px;
pointer-events: none;
position: absolute; position: absolute;
right: -2px; right: -2px;
top: 0; top: 0;
bottom: -2px;
width: 4px; width: 4px;
background-color: #adf;
pointer-events: none;
}
p {
margin: 0;
} }
} }
ul[data-type="taskList"] { .tableWrapper {
list-style: none; margin: 1.5rem 0;
padding: 0;
p {
margin: 0;
}
li {
display: flex;
> label {
flex: 0 0 auto;
margin-right: 0.5rem;
user-select: none;
}
> div {
flex: 1 1 auto;
}
}
}
}
.tiptap p.is-empty::before {
content: attr(data-placeholder);
float: left;
color: #adb5bd;
pointer-events: none;
height: 0;
}
.tableWrapper {
overflow-x: auto; overflow-x: auto;
} }
.resize-cursor { .resize-cursor {
cursor: ew-resize; cursor: ew-resize;
cursor: col-resize; cursor: col-resize;
}
} }
.mention { /* Floating/Bubble Menus */
border: 1px solid #000; .bubble-menu {
border-radius: 0.4rem; background-color: var(--white);
padding: 0.1rem 0.3rem; border: 1px solid var(--gray-1);
box-decoration-break: clone; border-radius: 0.7rem;
box-shadow: var(--shadow);
display: flex;
padding: 0.2rem;
button {
background-color: unset;
&:hover {
background-color: var(--gray-3);
}
&.is-active {
background-color: var(--purple);
&:hover {
background-color: var(--purple-contrast);
}
}
}
} }
.menu { .floating-menu {
position: sticky; display: flex;
top: 0; background-color: var(--gray-3);
background: #fff; padding: 0.1rem;
z-index: 1; border-radius: 0.5rem;
button {
background-color: unset;
padding: 0.275rem 0.425rem;
border-radius: 0.3rem;
&:hover {
background-color: var(--gray-3);
}
&.is-active {
background-color: var(--white);
color: var(--purple);
&:hover {
color: var(--purple-contrast);
}
}
}
} }
</style> </style>

View File

@ -15,7 +15,7 @@ context('/src/Experiments/CollaborationAnnotation/Vue/', () => {
cy.window().then(win => { cy.window().then(win => {
cy.stub(win, 'prompt', () => 'This is a test comment') cy.stub(win, 'prompt', () => 'This is a test comment')
cy.get('.editor-1 .tiptap').type('{selectall}{backspace}Hello world{selectall}') cy.get('.editor-1 .tiptap').type('{selectall}{backspace}Hello world{selectall}')
cy.get('button').contains('comment').eq(0).click() cy.get('button').contains('Comment').eq(0).click()
cy.get('.editor-1 .tiptap').type('{end}') cy.get('.editor-1 .tiptap').type('{end}')
cy.get('.tiptap .annotation').should('have.length', 2) cy.get('.tiptap .annotation').should('have.length', 2)
cy.get('.comment').should('exist').contains('This is a test comment') cy.get('.comment').should('exist').contains('This is a test comment')
@ -42,11 +42,11 @@ context('/src/Experiments/CollaborationAnnotation/Vue/', () => {
}) })
cy.get('.editor-1 .tiptap').type('{selectall}{backspace}Hello world{selectall}') cy.get('.editor-1 .tiptap').type('{selectall}{backspace}Hello world{selectall}')
cy.get('button').contains('comment').eq(0).click() cy.get('button').contains('Comment').eq(0).click()
cy.wait(1000) cy.wait(1000)
cy.get('.editor-1 .tiptap').find('.annotation').click() cy.get('.editor-1 .tiptap').find('.annotation').click()
cy.get('.comment').should('exist').contains('This is a test comment') cy.get('.comment').should('exist').contains('This is a test comment')
cy.get('button').contains('update').click() cy.get('button').contains('Update').click()
cy.wait(1000) cy.wait(1000)
cy.get('.comment').should('exist').contains('This is the new comment') cy.get('.comment').should('exist').contains('This is the new comment')
}) })
@ -57,11 +57,11 @@ context('/src/Experiments/CollaborationAnnotation/Vue/', () => {
cy.stub(win, 'prompt', () => 'This is a test comment') cy.stub(win, 'prompt', () => 'This is a test comment')
cy.get('.editor-1 .tiptap').type('{selectall}{backspace}Hello world{selectall}') cy.get('.editor-1 .tiptap').type('{selectall}{backspace}Hello world{selectall}')
cy.get('button').contains('comment').eq(0).click() cy.get('button').contains('Comment').eq(0).click()
cy.wait(1000) cy.wait(1000)
cy.get('.editor-1 .tiptap').find('.annotation').click() cy.get('.editor-1 .tiptap').find('.annotation').click()
cy.get('.comment').should('exist').contains('This is a test comment') cy.get('.comment').should('exist').contains('This is a test comment')
cy.get('button').contains('remove').click() cy.get('button').contains('Remove').click()
cy.get('.tiptap .annotation').should('not.exist') cy.get('.tiptap .annotation').should('not.exist')
cy.wait(1000) cy.wait(1000)
cy.get('.comment').should('not.exist') cy.get('.comment').should('not.exist')

View File

@ -1,31 +1,38 @@
<template> <template>
<div> <div>
<div v-if="editor"> <div v-if="editor" class="container">
<h2> <div class="control-group">
Original Editor <h2>Original Editor</h2>
</h2> <div class="button-group">
<button @click="addComment" :disabled="!editor.can().addAnnotation()"> <button @click="addComment" :disabled="!editor.can().addAnnotation()">
comment Comment
</button> </button>
</div>
</div>
<editor-content class="editor-1" :editor="editor" /> <editor-content class="editor-1" :editor="editor" />
<div class="comment" v-for="comment in comments" :key="comment.id">
<div class="output-group" v-for="comment in comments" :key="comment.id">
{{ comment }} {{ comment }}
<div class="button-group">
<button @click="updateComment(comment.id)"> <button @click="updateComment(comment.id)">
update Update
</button> </button>
<button @click="deleteComment(comment.id)"> <button @click="deleteComment(comment.id)">
remove Remove
</button> </button>
</div> </div>
</div>
<h2> <div class="control-group">
Another Editor <h2>Another Editor</h2>
</h2> <div class="button-group">
<button @click="addAnotherComment" :disabled="!anotherEditor.can().addAnnotation()"> <button @click="addAnotherComment" :disabled="!anotherEditor.can().addAnnotation()">
comment Comment
</button> </button>
</div>
</div>
<editor-content class="editor-2" :editor="anotherEditor" /> <editor-content class="editor-2" :editor="anotherEditor" />
</div> </div>
</div> </div>
@ -80,7 +87,7 @@ export default {
Annotations can be used to add additional information to the content, for example comments. They live on a different level than the actual editor content. Annotations can be used to add additional information to the content, for example comments. They live on a different level than the actual editor content.
</p> </p>
<p> <p>
This example allows you to add plain text, but youre free to add more complex data, for example JSON from another tiptap instance. :-) This example allows you to add plain text, but youre free to add more complex data, for example JSON from another Tiptap instance. :-)
</p> </p>
`, `,
}) })
@ -138,8 +145,44 @@ export default {
<style lang="scss"> <style lang="scss">
/* Basic editor styles */ /* Basic editor styles */
.tiptap { .tiptap {
> * + * { :first-child {
margin-top: 0.75em; margin-top: 0;
}
/* Heading styles */
h1,
h2,
h3,
h4,
h5,
h6 {
line-height: 1.1;
margin-top: 2.5rem;
text-wrap: pretty;
}
h1,
h2 {
margin-top: 3.5rem;
margin-bottom: 1.5rem;
}
h1 {
font-size: 1.4rem;
}
h2 {
font-size: 1.2rem;
}
h3 {
font-size: 1.1rem;
}
h4,
h5,
h6 {
font-size: 1rem;
} }
} }

View File

@ -1,14 +1,13 @@
<template> <template>
<div class="items"> <div class="dropdown-menu">
<template v-if="items.length"> <template v-if="items.length">
<button <button
class="item"
:class="{ 'is-selected': index === selectedIndex }" :class="{ 'is-selected': index === selectedIndex }"
v-for="(item, index) in items" v-for="(item, index) in items"
:key="index" :key="index"
@click="selectItem(index)" @click="selectItem(index)"
> >
{{ item }} {{ item.title }}
</button> </button>
</template> </template>
<div class="item" v-else> <div class="item" v-else>
@ -87,32 +86,35 @@ export default {
</script> </script>
<style lang="scss"> <style lang="scss">
.items { /* Dropdown menu */
padding: 0.2rem; .dropdown-menu {
background: var(--white);
border: 1px solid var(--gray-1);
border-radius: 0.7rem;
box-shadow: var(--shadow);
display: flex;
flex-direction: column;
gap: 0.1rem;
overflow: auto;
padding: 0.4rem;
position: relative; position: relative;
border-radius: 0.5rem;
background: #FFF;
color: rgba(0, 0, 0, 0.8);
overflow: hidden;
font-size: 0.9rem;
box-shadow:
0 0 0 1px rgba(0, 0, 0, 0.05),
0px 10px 20px rgba(0, 0, 0, 0.1),
;
}
.item { button {
display: block; align-items: center;
margin: 0; background-color: transparent;
width: 100%; display: flex;
gap: 0.25rem;
text-align: left; text-align: left;
background: transparent; width: 100%;
border-radius: 0.4rem;
border: 1px solid transparent; &:hover,
padding: 0.2rem 0.4rem; &:hover.is-selected {
background-color: var(--gray-3);
}
&.is-selected { &.is-selected {
border-color: #000; background-color: var(--gray-2);
}
} }
} }
</style> </style>

View File

@ -19,8 +19,8 @@ context('/src/Experiments/Commands/Vue/', () => {
items.forEach((item, i) => { items.forEach((item, i) => {
cy.get('.tiptap').type('{selectall}{backspace}/') cy.get('.tiptap').type('{selectall}{backspace}/')
cy.get('.tippy-content .items').should('exist') cy.get('.tippy-content .dropdown-menu').should('exist')
cy.get('.tippy-content .items .item').eq(i).click() cy.get('.tippy-content .dropdown-menu button').eq(i).click()
cy.get('.tiptap').type(`I am a ${item.tag}`) cy.get('.tiptap').type(`I am a ${item.tag}`)
cy.get(`.tiptap ${item.tag}`).should('exist').should('have.text', `I am a ${item.tag}`) cy.get(`.tiptap ${item.tag}`).should('exist').should('have.text', `I am a ${item.tag}`)
}) })
@ -28,17 +28,17 @@ context('/src/Experiments/Commands/Vue/', () => {
it('should close the popup without any command via esc', () => { it('should close the popup without any command via esc', () => {
cy.get('.tiptap').type('{selectall}{backspace}/') cy.get('.tiptap').type('{selectall}{backspace}/')
cy.get('.tippy-content .items').should('exist') cy.get('.tippy-content .dropdown-menu').should('exist')
cy.get('.tiptap').type('{esc}') cy.get('.tiptap').type('{esc}')
cy.get('.tippy-content .items').should('not.exist') cy.get('.tippy-content .dropdown-menu').should('not.exist')
}) })
it('should open the popup when the cursor is after a slash', () => { it('should open the popup when the cursor is after a slash', () => {
cy.get('.tiptap').type('{selectall}{backspace}/') cy.get('.tiptap').type('{selectall}{backspace}/')
cy.get('.tippy-content .items').should('exist') cy.get('.tippy-content .dropdown-menu').should('exist')
cy.get('.tiptap').type('{leftArrow}') cy.get('.tiptap').type('{leftArrow}')
cy.get('.tippy-content .items').should('not.exist') cy.get('.tippy-content .dropdown-menu').should('not.exist')
cy.get('.tiptap').type('{rightArrow}') cy.get('.tiptap').type('{rightArrow}')
cy.get('.tippy-content .items').should('exist') cy.get('.tippy-content .dropdown-menu').should('exist')
}) })
}) })

View File

@ -45,16 +45,95 @@ export default {
</script> </script>
<style lang="scss"> <style lang="scss">
/* Basic editor styles */
.tiptap { .tiptap {
> * + * { :first-child {
margin-top: 0.75em; margin-top: 0;
}
/* List styles */
ul,
ol {
padding: 0 1rem;
margin: 1.25rem 1rem 1.25rem 0.4rem;
li p {
margin-top: 0.25em;
margin-bottom: 0.25em;
}
}
/* Heading styles */
h1,
h2,
h3,
h4,
h5,
h6 {
line-height: 1.1;
margin-top: 2.5rem;
text-wrap: pretty;
}
h1,
h2 {
margin-top: 3.5rem;
margin-bottom: 1.5rem;
}
h1 {
font-size: 1.4rem;
}
h2 {
font-size: 1.2rem;
}
h3 {
font-size: 1.1rem;
}
h4,
h5,
h6 {
font-size: 1rem;
}
/* Code and preformatted text styles */
code {
background-color: var(--purple-light);
border-radius: 0.4rem;
color: var(--black);
font-size: 0.85rem;
padding: 0.25em 0.3em;
}
pre {
background: var(--black);
border-radius: 0.5rem;
color: var(--white);
font-family: 'JetBrainsMono', monospace;
margin: 1.5rem 0;
padding: 0.75rem 1rem;
code {
background: none;
color: inherit;
font-size: 0.8rem;
padding: 0;
}
}
blockquote {
border-left: 3px solid var(--gray-3);
margin: 1.5rem 0;
padding-left: 1rem;
}
hr {
border: none;
border-top: 1px solid var(--gray-2);
margin: 2rem 0;
} }
} }
.mention {
border: 1px solid #000;
border-radius: 0.4rem;
padding: 0.1rem 0.3rem;
box-decoration-break: clone;
}
</style> </style>

View File

@ -7,7 +7,7 @@ export default {
items: ({ query }) => { items: ({ query }) => {
return [ return [
{ {
title: 'H1', title: 'Heading 1',
command: ({ editor, range }) => { command: ({ editor, range }) => {
editor editor
.chain() .chain()
@ -18,7 +18,7 @@ export default {
}, },
}, },
{ {
title: 'H2', title: 'Heading 2',
command: ({ editor, range }) => { command: ({ editor, range }) => {
editor editor
.chain() .chain()
@ -29,7 +29,7 @@ export default {
}, },
}, },
{ {
title: 'bold', title: 'Bold',
command: ({ editor, range }) => { command: ({ editor, range }) => {
editor editor
.chain() .chain()
@ -40,7 +40,7 @@ export default {
}, },
}, },
{ {
title: 'italic', title: 'Italic',
command: ({ editor, range }) => { command: ({ editor, range }) => {
editor editor
.chain() .chain()

View File

@ -1,9 +1,11 @@
<template> <template>
<div v-if="editor"> <div v-if="editor" class="container">
<div class="control-group">
<div class="button-group">
<button @click="() => editor.chain().toggleBold().focus().run()">Make bold</button> <button @click="() => editor.chain().toggleBold().focus().run()">Make bold</button>
<button @click="() => editor.destroy()">Destroy editor</button> <button @click="() => editor.destroy()">Destroy editor</button>
</div> </div>
<div v-if="editor"> </div>
<editor-content :editor="editor" /> <editor-content :editor="editor" />
</div> </div>
</template> </template>
@ -41,9 +43,95 @@ export default {
</script> </script>
<style lang="scss"> <style lang="scss">
/* Basic editor styles */
.tiptap { .tiptap {
> * + * { :first-child {
margin-top: 0.75em; margin-top: 0;
}
/* List styles */
ul,
ol {
padding: 0 1rem;
margin: 1.25rem 1rem 1.25rem 0.4rem;
li p {
margin-top: 0.25em;
margin-bottom: 0.25em;
}
}
/* Heading styles */
h1,
h2,
h3,
h4,
h5,
h6 {
line-height: 1.1;
margin-top: 2.5rem;
text-wrap: pretty;
}
h1,
h2 {
margin-top: 3.5rem;
margin-bottom: 1.5rem;
}
h1 {
font-size: 1.4rem;
}
h2 {
font-size: 1.2rem;
}
h3 {
font-size: 1.1rem;
}
h4,
h5,
h6 {
font-size: 1rem;
}
/* Code and preformatted text styles */
code {
background-color: var(--purple-light);
border-radius: 0.4rem;
color: var(--black);
font-size: 0.85rem;
padding: 0.25em 0.3em;
}
pre {
background: var(--black);
border-radius: 0.5rem;
color: var(--white);
font-family: 'JetBrainsMono', monospace;
margin: 1.5rem 0;
padding: 0.75rem 1rem;
code {
background: none;
color: inherit;
font-size: 0.8rem;
padding: 0;
}
}
blockquote {
border-left: 3px solid var(--gray-3);
margin: 1.5rem 0;
padding-left: 1rem;
}
hr {
border: none;
border-top: 1px solid var(--gray-2);
margin: 2rem 0;
} }
} }
</style> </style>

View File

@ -1,9 +1,12 @@
<template> <template>
<div v-if="editor"> <div v-if="editor" class="container">
<div class="control-group">
<div class="button-group">
<button @click="addIframe"> <button @click="addIframe">
add iframe Add iFrame
</button> </button>
</div>
</div>
<editor-content :editor="editor" /> <editor-content :editor="editor" />
</div> </div>
</template> </template>

View File

@ -18,7 +18,7 @@ export default () => {
], ],
content: ` content: `
<p> <p>
This is a radically reduced version of tiptap. It has support for a document, with paragraphs and text. Thats it. Its probably too much for real minimalists though. This is a radically reduced version of Tiptap. It has support for a document, with paragraphs and text. Thats it. Its probably too much for real minimalists though.
</p> </p>
<p> <p>
The paragraph extension is not really required, but you need at least one node. Sure, that node can be something different. The paragraph extension is not really required, but you need at least one node. Sure, that node can be something different.
@ -28,8 +28,10 @@ export default () => {
return ( return (
<> <>
reactive storage: {editor?.storage.custom.foo}
<EditorContent editor={editor} /> <EditorContent editor={editor} />
<div className="output-group">
Reactive storage: {editor?.storage.custom.foo}
</div>
</> </>
) )
} }

View File

@ -1,6 +1,6 @@
/* Basic editor styles */ /* Basic editor styles */
.tiptap { .tiptap {
> * + * { :first-child {
margin-top: 0.75em; margin-top: 0;
} }
} }

View File

@ -1,6 +1,10 @@
<template> <template>
reactive storage: {{ editor?.storage.custom.foo }} <div class="container">
<editor-content :editor="editor" /> <editor-content :editor="editor" />
<div class="output-group">
Reactive storage: {{ editor?.storage.custom.foo }}
</div>
</div>
</template> </template>
<script> <script>
@ -32,7 +36,7 @@ export default {
], ],
content: ` content: `
<p> <p>
This is a radically reduced version of tiptap. It has support for a document, with paragraphs and text. Thats it. Its probably too much for real minimalists though. This is a radically reduced version of Tiptap. It has support for a document, with paragraphs and text. Thats it. Its probably too much for real minimalists though.
</p> </p>
<p> <p>
The paragraph extension is not really required, but you need at least one node. Sure, that node can be something different. The paragraph extension is not really required, but you need at least one node. Sure, that node can be something different.
@ -50,8 +54,8 @@ export default {
<style lang="scss"> <style lang="scss">
/* Basic editor styles */ /* Basic editor styles */
.tiptap { .tiptap {
> * + * { :first-child {
margin-top: 0.75em; margin-top: 0;
} }
} }
</style> </style>

View File

@ -1,24 +1,30 @@
<template> <template>
<div v-if="editor"> <div v-if="editor" class="container">
<div class="control-group">
<div class="button-group">
<button @click="addFigure"> <button @click="addFigure">
figure Add image with caption
</button> </button>
<button <button
@click="editor.chain().focus().imageToFigure().run()" @click="editor.chain().focus().imageToFigure().run()"
:disabled="!editor.can().imageToFigure()" :disabled="!editor.can().imageToFigure()"
> >
image to figure Add caption to image
</button> </button>
<button <button
@click="editor.chain().focus().figureToImage().run()" @click="editor.chain().focus().figureToImage().run()"
:disabled="!editor.can().figureToImage()" :disabled="!editor.can().figureToImage()"
> >
figure to image Remove caption from image
</button> </button>
</div>
</div>
<editor-content :editor="editor" /> <editor-content :editor="editor" />
<h2>HTML</h2> <div class="output-group">
{{ editor.getHTML() }} <label>HTML</label>
<code>{{ editor.getHTML() }}</code>
</div>
</div> </div>
</template> </template>
@ -65,14 +71,14 @@ export default {
content: ` content: `
<p>Figure + Figcaption</p> <p>Figure + Figcaption</p>
<figure> <figure>
<img src="https://source.unsplash.com/8xznAGy4HcY/800x400" alt="Random photo of something" title="Whos dat?"> <img src="https://placehold.co/800x400/orange/white" alt="Random photo of something" title="Whos dat?">
<figcaption> <figcaption>
<p>Amazing caption</p> <p>Amazing caption</p>
</figcaption> </figcaption>
</figure> </figure>
<img src="https://source.unsplash.com/K9QHL52rE2k/800x400"> <img src="https://placehold.co/800x400/green/white">
<img src="https://source.unsplash.com/8xznAGy4HcY/800x400"> <img src="https://placehold.co/800x400/blue/white">
<img src="https://source.unsplash.com/K9QHL52rE2k/800x400"> <img src="https://placehold.co/800x400/black/white">
<p>Thats it.</p> <p>Thats it.</p>
`, `,
}) })
@ -85,32 +91,136 @@ export default {
</script> </script>
<style lang="scss"> <style lang="scss">
/* Basic editor styles */
.tiptap { .tiptap {
> * + * { :first-child {
margin-top: 0.75em; margin-top: 0;
} }
figure { /* List styles */
max-width: 25rem; ul,
border: 3px solid #0D0D0D; ol {
border-radius: 0.5rem; padding: 0 1rem;
margin: 1rem 0; margin: 1.25rem 1rem 1.25rem 0.4rem;
padding: 0.5rem;
li p {
margin-top: 0.25em;
margin-bottom: 0.25em;
}
} }
figcaption { /* Heading styles */
margin-top: 0.25rem; h1,
text-align: center; h2,
padding: 0.5rem; h3,
border: 2px dashed #0D0D0D20; h4,
h5,
h6 {
line-height: 1.1;
margin-top: 2.5rem;
text-wrap: pretty;
}
h1,
h2 {
margin-top: 3.5rem;
margin-bottom: 1.5rem;
}
h1 {
font-size: 1.4rem;
}
h2 {
font-size: 1.2rem;
}
h3 {
font-size: 1.1rem;
}
h4,
h5,
h6 {
font-size: 1rem;
}
/* Code and preformatted text styles */
code {
background-color: var(--purple-light);
border-radius: 0.4rem;
color: var(--black);
font-size: 0.85rem;
padding: 0.25em 0.3em;
}
pre {
background: var(--black);
border-radius: 0.5rem; border-radius: 0.5rem;
color: var(--white);
font-family: 'JetBrainsMono', monospace;
margin: 1.5rem 0;
padding: 0.75rem 1rem;
code {
background: none;
color: inherit;
font-size: 0.8rem;
padding: 0;
}
}
blockquote {
border-left: 3px solid var(--gray-3);
margin: 1.5rem 0;
padding-left: 1rem;
}
hr {
border: none;
border-top: 1px solid var(--gray-2);
margin: 2rem 0;
} }
img { img {
display: block; display: block;
max-width: min(100%, 25rem);
height: auto; height: auto;
margin: 1.5rem 0;
max-width: 100%;
&.ProseMirror-selectednode {
outline: 3px solid var(--purple);
}
}
/* Figure */
figure {
align-items: start;
border: 2px solid var(--black);
border-radius: 0.5rem; border-radius: 0.5rem;
display: flex;
flex-direction: column;
gap: 0.5rem;
margin: 1rem 0;
padding: 0.5rem;
width: fit-content;
> *:not(figcaption) {
margin: 0;
max-width: 100%;
}
&:has(figcaption:active) {
border-color: var(--purple);
}
figcaption {
border-radius: 0.5rem;
border: 2px dashed #0D0D0D20;
padding: 0.5rem;
text-align: center;
width: 100%;
}
} }
} }
</style> </style>

View File

@ -1,17 +1,21 @@
<template> <template>
<div v-if="editor"> <div v-if="editor" class="container">
<div class="control-group">
<div class="button-group">
<button @click="addCapturedTable"> <button @click="addCapturedTable">
add table Add table with caption
</button> </button>
<button @click="addCapturedImage"> <button @click="addCapturedImage">
add image Add image with caption
</button> </button>
<button @click="removeCapturedTable"> <button @click="removeCapturedTable">
remove table Remove table with caption
</button> </button>
<button @click="removeCapturedImage"> <button @click="removeCapturedImage">
remove image Remove image with caption
</button> </button>
</div>
</div>
<editor-content :editor="editor" /> <editor-content :editor="editor" />
</div> </div>
</template> </template>
@ -62,14 +66,14 @@ export default {
content: [ content: [
{ {
type: 'text', type: 'text',
text: 'image caption', text: 'Image caption',
}, },
], ],
}, },
{ {
type: 'image', type: 'image',
attrs: { attrs: {
src: 'https://source.unsplash.com/K9QHL52rE2k/800x400', src: 'https://placehold.co/800x400/orange/white',
}, },
}, },
], ],
@ -89,7 +93,7 @@ export default {
content: [ content: [
{ {
type: 'text', type: 'text',
text: 'table caption', text: 'Table caption',
}, },
], ],
}, },
@ -107,7 +111,7 @@ export default {
content: [ content: [
{ {
type: 'text', type: 'text',
text: 'cell 1', text: 'Cell 1',
}, },
], ],
}, },
@ -121,7 +125,7 @@ export default {
content: [ content: [
{ {
type: 'text', type: 'text',
text: 'cell 2', text: 'Cell 2',
}, },
], ],
}, },
@ -172,10 +176,10 @@ export default {
<figcaption> <figcaption>
Image caption Image caption
</figcaption> </figcaption>
<img src="https://source.unsplash.com/8xznAGy4HcY/800x400" alt="Random photo of something" title="Whos dat?"> <img src="https://placehold.co/800x400/black/white" alt="Random photo of something" title="Whos dat?">
</figure> </figure>
<p>Some text</p> <p>Some text</p>
<img src="https://source.unsplash.com/K9QHL52rE2k/800x400"> <img src="https://placehold.co/800x400">
<p>Some text</p> <p>Some text</p>
<figure data-type="capturedTable"> <figure data-type="capturedTable">
<figcaption> <figcaption>
@ -189,20 +193,20 @@ export default {
</tr> </tr>
<tr> <tr>
<td>Cyndi Lauper</td> <td>Cyndi Lauper</td>
<td>singer</td> <td>Singer</td>
<td>songwriter</td> <td>Songwriter</td>
<td>actress</td> <td>Actress</td>
</tr> </tr>
<tr> <tr>
<td>Marie Curie</td> <td>Marie Curie</td>
<td>scientist</td> <td>Scientist</td>
<td>chemist</td> <td>Chemist</td>
<td>physicist</td> <td>Physicist</td>
</tr> </tr>
<tr> <tr>
<td>Indira Gandhi</td> <td>Indira Gandhi</td>
<td>prime minister</td> <td>Prime minister</td>
<td colspan="2">politician</td> <td colspan="2">Politician</td>
</tr> </tr>
</tbody> </tbody>
</table> </table>
@ -216,20 +220,20 @@ export default {
</tr> </tr>
<tr> <tr>
<td>Cyndi Lauper</td> <td>Cyndi Lauper</td>
<td>singer</td> <td>Singer</td>
<td>songwriter</td> <td>Songwriter</td>
<td>actress</td> <td>Actress</td>
</tr> </tr>
<tr> <tr>
<td>Marie Curie</td> <td>Marie Curie</td>
<td>scientist</td> <td>Scientist</td>
<td>chemist</td> <td>Chemist</td>
<td>physicist</td> <td>Physicist</td>
</tr> </tr>
<tr> <tr>
<td>Indira Gandhi</td> <td>Indira Gandhi</td>
<td>prime minister</td> <td>Prime minister</td>
<td colspan="2">politician</td> <td colspan="2">Politician</td>
</tr> </tr>
</tbody> </tbody>
</table> </table>
@ -244,57 +248,123 @@ export default {
</script> </script>
<style lang="scss"> <style lang="scss">
/* Basic editor styles */
.tiptap { .tiptap {
> * + * { :first-child {
margin-top: 0.75em;
}
figure {
max-width: 25rem;
border: 3px solid #0D0D0D;
border-radius: 0.5rem;
margin: 1rem 0;
padding: 0.5rem;
}
figcaption {
margin: 0.25rem 0;
text-align: center;
padding: 0.5rem;
border: 2px dashed #0D0D0D20;
border-radius: 0.5rem;
&:first-child {
margin-top: 0; margin-top: 0;
} }
&:last-child { /* List styles */
margin-bottom: 0; ul,
ol {
padding: 0 1rem;
margin: 1.25rem 1rem 1.25rem 0.4rem;
li p {
margin-top: 0.25em;
margin-bottom: 0.25em;
} }
} }
/* Heading styles */
h1,
h2,
h3,
h4,
h5,
h6 {
line-height: 1.1;
margin-top: 2.5rem;
text-wrap: pretty;
}
h1,
h2 {
margin-top: 3.5rem;
margin-bottom: 1.5rem;
}
h1 {
font-size: 1.4rem;
}
h2 {
font-size: 1.2rem;
}
h3 {
font-size: 1.1rem;
}
h4,
h5,
h6 {
font-size: 1rem;
}
/* Code and preformatted text styles */
code {
background-color: var(--purple-light);
border-radius: 0.4rem;
color: var(--black);
font-size: 0.85rem;
padding: 0.25em 0.3em;
}
pre {
background: var(--black);
border-radius: 0.5rem;
color: var(--white);
font-family: 'JetBrainsMono', monospace;
margin: 1.5rem 0;
padding: 0.75rem 1rem;
code {
background: none;
color: inherit;
font-size: 0.8rem;
padding: 0;
}
}
blockquote {
border-left: 3px solid var(--gray-3);
margin: 1.5rem 0;
padding-left: 1rem;
}
hr {
border: none;
border-top: 1px solid var(--gray-2);
margin: 2rem 0;
}
img { img {
display: block; display: block;
max-width: min(100%, 25rem);
height: auto; height: auto;
border-radius: 0.5rem; margin: 1.5rem 0;
max-width: 100%;
&.ProseMirror-selectednode {
outline: 3px solid var(--purple);
}
} }
table { table {
border-collapse: collapse; border-collapse: collapse;
table-layout: fixed;
width: 100%;
margin: 0; margin: 0;
overflow: hidden; overflow: hidden;
table-layout: fixed;
width: 100%;
td, td,
th { th {
min-width: 1em; border: 1px solid var(--gray-3);
border: 2px solid #ced4da;
padding: 3px 5px;
vertical-align: top;
box-sizing: border-box; box-sizing: border-box;
min-width: 1em;
padding: 6px 8px;
position: relative; position: relative;
vertical-align: top;
> * { > * {
margin-bottom: 0; margin-bottom: 0;
@ -302,28 +372,68 @@ export default {
} }
th { th {
background-color: var(--gray-1);
font-weight: bold; font-weight: bold;
text-align: left; text-align: left;
background-color: #f1f3f5;
} }
.selectedCell:after { .selectedCell:after {
z-index: 2; background: var(--gray-2);
position: absolute;
content: ""; content: "";
left: 0; right: 0; top: 0; bottom: 0; left: 0; right: 0; top: 0; bottom: 0;
background: rgba(200, 200, 255, 0.4);
pointer-events: none; pointer-events: none;
position: absolute;
z-index: 2;
} }
.column-resize-handle { .column-resize-handle {
background-color: var(--purple);
bottom: -2px;
pointer-events: none;
position: absolute; position: absolute;
right: -2px; right: -2px;
top: 0; top: 0;
bottom: -2px;
width: 4px; width: 4px;
background-color: #adf; }
pointer-events: none; }
.tableWrapper {
margin: 1.5rem 0;
overflow-x: auto;
}
.resize-cursor {
cursor: ew-resize;
cursor: col-resize;
}
/* Figure */
figure {
align-items: start;
border: 2px solid var(--black);
border-radius: 0.5rem;
display: flex;
flex-direction: column;
gap: 0.5rem;
margin: 1rem 0;
padding: 0.5rem;
width: fit-content;
> *:not(figcaption) {
margin: 0;
max-width: 100%;
}
&:has(figcaption:active) {
border-color: var(--purple);
}
figcaption {
border-radius: 0.5rem;
border: 2px dashed #0D0D0D20;
padding: 0.5rem;
text-align: center;
width: 100%;
} }
} }
} }

View File

@ -28,14 +28,14 @@ export default {
DragHandle, DragHandle,
], ],
content: ` content: `
<p>paragraph 1</p> <p>Paragraph 1</p>
<p>paragraph 2</p> <p>Paragraph 2</p>
<p>paragraph 3</p> <p>Paragraph 3</p>
<ul> <ul>
<li>list item 1</li> <li>List item 1</li>
<li>list item 2</li> <li>List item 2</li>
</ul> </ul>
<pre>code</pre> <pre>Code</pre>
`, `,
onUpdate: () => { onUpdate: () => {
// eslint-disable-next-line // eslint-disable-next-line

View File

@ -33,7 +33,8 @@ const MenuBar = ({ editor }) => {
} }
return ( return (
<> <div className="control-group">
<div className="button-group">
<button <button
onClick={() => editor.chain().focus().toggleBold().run()} onClick={() => editor.chain().focus().toggleBold().run()}
disabled={ disabled={
@ -45,7 +46,7 @@ const MenuBar = ({ editor }) => {
} }
className={editor.isActive('bold') ? 'is-active' : ''} className={editor.isActive('bold') ? 'is-active' : ''}
> >
bold Bold
</button> </button>
<button <button
onClick={() => editor.chain().focus().toggleItalic().run()} onClick={() => editor.chain().focus().toggleItalic().run()}
@ -58,7 +59,7 @@ const MenuBar = ({ editor }) => {
} }
className={editor.isActive('italic') ? 'is-active' : ''} className={editor.isActive('italic') ? 'is-active' : ''}
> >
italic Italic
</button> </button>
<button <button
onClick={() => editor.chain().focus().toggleStrike().run()} onClick={() => editor.chain().focus().toggleStrike().run()}
@ -71,7 +72,7 @@ const MenuBar = ({ editor }) => {
} }
className={editor.isActive('strike') ? 'is-active' : ''} className={editor.isActive('strike') ? 'is-active' : ''}
> >
strike Strike
</button> </button>
<button <button
onClick={() => editor.chain().focus().toggleCode().run()} onClick={() => editor.chain().focus().toggleCode().run()}
@ -84,85 +85,85 @@ const MenuBar = ({ editor }) => {
} }
className={editor.isActive('code') ? 'is-active' : ''} className={editor.isActive('code') ? 'is-active' : ''}
> >
code Code
</button> </button>
<button onClick={() => editor.chain().focus().unsetAllMarks().run()}> <button onClick={() => editor.chain().focus().unsetAllMarks().run()}>
clear marks Clear marks
</button> </button>
<button onClick={() => editor.chain().focus().clearNodes().run()}> <button onClick={() => editor.chain().focus().clearNodes().run()}>
clear nodes Clear nodes
</button> </button>
<button <button
onClick={() => editor.chain().focus().setParagraph().run()} onClick={() => editor.chain().focus().setParagraph().run()}
className={editor.isActive('paragraph') ? 'is-active' : ''} className={editor.isActive('paragraph') ? 'is-active' : ''}
> >
paragraph Paragraph
</button> </button>
<button <button
onClick={() => editor.chain().focus().toggleHeading({ level: 1 }).run()} onClick={() => editor.chain().focus().toggleHeading({ level: 1 }).run()}
className={editor.isActive('heading', { level: 1 }) ? 'is-active' : ''} className={editor.isActive('heading', { level: 1 }) ? 'is-active' : ''}
> >
h1 H1
</button> </button>
<button <button
onClick={() => editor.chain().focus().toggleHeading({ level: 2 }).run()} onClick={() => editor.chain().focus().toggleHeading({ level: 2 }).run()}
className={editor.isActive('heading', { level: 2 }) ? 'is-active' : ''} className={editor.isActive('heading', { level: 2 }) ? 'is-active' : ''}
> >
h2 H2
</button> </button>
<button <button
onClick={() => editor.chain().focus().toggleHeading({ level: 3 }).run()} onClick={() => editor.chain().focus().toggleHeading({ level: 3 }).run()}
className={editor.isActive('heading', { level: 3 }) ? 'is-active' : ''} className={editor.isActive('heading', { level: 3 }) ? 'is-active' : ''}
> >
h3 H3
</button> </button>
<button <button
onClick={() => editor.chain().focus().toggleHeading({ level: 4 }).run()} onClick={() => editor.chain().focus().toggleHeading({ level: 4 }).run()}
className={editor.isActive('heading', { level: 4 }) ? 'is-active' : ''} className={editor.isActive('heading', { level: 4 }) ? 'is-active' : ''}
> >
h4 H4
</button> </button>
<button <button
onClick={() => editor.chain().focus().toggleHeading({ level: 5 }).run()} onClick={() => editor.chain().focus().toggleHeading({ level: 5 }).run()}
className={editor.isActive('heading', { level: 5 }) ? 'is-active' : ''} className={editor.isActive('heading', { level: 5 }) ? 'is-active' : ''}
> >
h5 H5
</button> </button>
<button <button
onClick={() => editor.chain().focus().toggleHeading({ level: 6 }).run()} onClick={() => editor.chain().focus().toggleHeading({ level: 6 }).run()}
className={editor.isActive('heading', { level: 6 }) ? 'is-active' : ''} className={editor.isActive('heading', { level: 6 }) ? 'is-active' : ''}
> >
h6 H6
</button> </button>
<button <button
onClick={() => editor.chain().focus().toggleBulletList().run()} onClick={() => editor.chain().focus().toggleBulletList().run()}
className={editor.isActive('bulletList') ? 'is-active' : ''} className={editor.isActive('bulletList') ? 'is-active' : ''}
> >
bullet list Bullet list
</button> </button>
<button <button
onClick={() => editor.chain().focus().toggleOrderedList().run()} onClick={() => editor.chain().focus().toggleOrderedList().run()}
className={editor.isActive('orderedList') ? 'is-active' : ''} className={editor.isActive('orderedList') ? 'is-active' : ''}
> >
ordered list Ordered list
</button> </button>
<button <button
onClick={() => editor.chain().focus().toggleCodeBlock().run()} onClick={() => editor.chain().focus().toggleCodeBlock().run()}
className={editor.isActive('codeBlock') ? 'is-active' : ''} className={editor.isActive('codeBlock') ? 'is-active' : ''}
> >
code block Code block
</button> </button>
<button <button
onClick={() => editor.chain().focus().toggleBlockquote().run()} onClick={() => editor.chain().focus().toggleBlockquote().run()}
className={editor.isActive('blockquote') ? 'is-active' : ''} className={editor.isActive('blockquote') ? 'is-active' : ''}
> >
blockquote Blockquote
</button> </button>
<button onClick={() => editor.chain().focus().setHorizontalRule().run()}> <button onClick={() => editor.chain().focus().setHorizontalRule().run()}>
horizontal rule Horizontal rule
</button> </button>
<button onClick={() => editor.chain().focus().setHardBreak().run()}> <button onClick={() => editor.chain().focus().setHardBreak().run()}>
hard break Hard break
</button> </button>
<button <button
onClick={() => editor.chain().focus().undo().run()} onClick={() => editor.chain().focus().undo().run()}
@ -174,7 +175,7 @@ const MenuBar = ({ editor }) => {
.run() .run()
} }
> >
undo Undo
</button> </button>
<button <button
onClick={() => editor.chain().focus().redo().run()} onClick={() => editor.chain().focus().redo().run()}
@ -186,15 +187,16 @@ const MenuBar = ({ editor }) => {
.run() .run()
} }
> >
redo Redo
</button> </button>
<button <button
onClick={() => editor.chain().focus().setColor('#958DF1').run()} onClick={() => editor.chain().focus().setColor('#958DF1').run()}
className={editor.isActive('textStyle', { color: '#958DF1' }) ? 'is-active' : ''} className={editor.isActive('textStyle', { color: '#958DF1' }) ? 'is-active' : ''}
> >
purple Purple
</button> </button>
</> </div>
</div>
) )
} }
@ -210,9 +212,9 @@ export default () => {
}) })
return ( return (
<div> <>
<MenuBar editor={editor} /> <MenuBar editor={editor} />
<EditorContent editor={editor} /> <EditorContent editor={editor} />
</div> </>
) )
} }

Some files were not shown because too many files have changed in this diff Show More