forked from worktile/slate-angular
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
da9a251
commit 0390117
Showing
11 changed files
with
366 additions
and
13 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
26 changes: 26 additions & 0 deletions
26
demo/app/components/editable-button/editable-button.component.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,26 @@ | ||
import { ChangeDetectionStrategy, Component, HostListener } from '@angular/core'; | ||
import { ButtonElement } from '../../../../custom-types'; | ||
import { BaseElementComponent } from 'slate-angular'; | ||
|
||
@Component({ | ||
selector: 'span[demo-element-button]', | ||
template: ` | ||
<span contenteditable="false" style="font-size: 0;">{{ inlineChromiumBugfix }}</span> | ||
<slate-children [children]="children" [context]="childrenContext" [viewContext]="viewContext"></slate-children> | ||
<span contenteditable="false" style="font-size: 0;">{{ inlineChromiumBugfix }}</span> | ||
`, | ||
host: { | ||
class: 'demo-element-button' | ||
}, | ||
changeDetection: ChangeDetectionStrategy.OnPush | ||
}) | ||
export class DemoElementEditableButtonComponent extends BaseElementComponent<ButtonElement> { | ||
// Put this at the start and end of an inline component to work around this Chromium bug: | ||
// https://bugs.chromium.org/p/chromium/issues/detail?id=1249405 | ||
inlineChromiumBugfix = '$' + String.fromCodePoint(160); | ||
|
||
@HostListener('click', ['$event']) | ||
click(event: MouseEvent) { | ||
event.preventDefault(); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,34 @@ | ||
import { ChangeDetectionStrategy, Component, HostBinding, HostListener } from '@angular/core'; | ||
import { ButtonElement, LinkElement } from '../../../../custom-types'; | ||
import { BaseElementComponent } from 'slate-angular'; | ||
import { element } from 'protractor'; | ||
|
||
@Component({ | ||
selector: 'a[demo-element-link]', | ||
template: ` | ||
<span contenteditable="false" style="font-size: 0;">{{ inlineChromiumBugfix }}</span> | ||
<slate-children [children]="children" [context]="childrenContext" [viewContext]="viewContext"></slate-children> | ||
<span contenteditable="false" style="font-size: 0;">{{ inlineChromiumBugfix }}</span> | ||
`, | ||
changeDetection: ChangeDetectionStrategy.OnPush | ||
}) | ||
export class DemoElementLinkComponent extends BaseElementComponent<LinkElement> { | ||
// Put this at the start and end of an inline component to work around this Chromium bug: | ||
// https://bugs.chromium.org/p/chromium/issues/detail?id=1249405 | ||
inlineChromiumBugfix = '$' + String.fromCodePoint(160); | ||
|
||
@HostBinding('class.demo-element-link-active') | ||
get active() { | ||
return this.isCollapsed; | ||
} | ||
|
||
@HostBinding("attr.href") | ||
get herf() { | ||
return this.element.url; | ||
} | ||
|
||
@HostListener('click', ['$event']) | ||
click(event: MouseEvent) { | ||
event.preventDefault(); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
<div class="demo-rich-editor-wrapper" style="position: relative;"> | ||
<div class="demo-global-toolbar"> | ||
<demo-button *ngFor="let toolbarItem of toolbarItems" [active]="toolbarItem.active()" (onMouseDown)="toolbarItem.action($event)"><span class="material-icons">{{ toolbarItem.icon | ||
}}</span></demo-button> | ||
</div> | ||
<slate-editable class="demo-slate-angular-editor" [editor]="editor" [(ngModel)]="value" (ngModelChange)="valueChange($event)" [renderElement]="renderElement" [keydown]="onKeydown"> | ||
</slate-editable> | ||
</div> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,254 @@ | ||
|
||
import { Component, OnInit } from '@angular/core'; | ||
import { Editor, Transforms, createEditor, Element as SlateElement, Range, Descendant } from 'slate'; | ||
import { withHistory } from 'slate-history'; | ||
import { withAngular } from 'slate-angular'; | ||
import { LinkElement, ButtonElement } from 'custom-types'; | ||
import isUrl from 'is-url'; | ||
import { isKeyHotkey } from 'is-hotkey'; | ||
import { DemoElementEditableButtonComponent } from '../components/editable-button/editable-button.component'; | ||
import { DemoElementLinkComponent } from '../components/link/link.component'; | ||
|
||
@Component({ | ||
selector: 'demo-inlines', | ||
templateUrl: 'inlines.component.html' | ||
}) | ||
export class DemoInlinesComponent implements OnInit { | ||
value = initialValue; | ||
|
||
editor = withInlines(withHistory(withAngular(createEditor()))); | ||
|
||
toolbarItems = [ | ||
{ | ||
icon: 'link', | ||
active: () => { | ||
return isLinkActive(this.editor); | ||
}, | ||
action: (event) => { | ||
event.preventDefault() | ||
const url = window.prompt('Enter the URL of the link:') | ||
if (!url) return | ||
insertLink(this.editor, url); | ||
} | ||
}, | ||
{ | ||
icon: 'link_off', | ||
active: (event) => { | ||
return isLinkActive(this.editor); | ||
}, | ||
action: () => { | ||
if (isLinkActive(this.editor)) { | ||
unwrapLink(this.editor); | ||
} | ||
} | ||
}, | ||
{ | ||
icon: 'smart_button', | ||
active: () => { | ||
return true; | ||
}, | ||
action: (event) => { | ||
event.preventDefault() | ||
if (isButtonActive(this.editor)) { | ||
unwrapButton(this.editor); | ||
} else { | ||
insertButton(this.editor); | ||
} | ||
} | ||
} | ||
]; | ||
|
||
constructor() { } | ||
|
||
ngOnInit(): void { | ||
} | ||
|
||
renderElement = (element: SlateElement) => { | ||
if (element.type === 'button') { | ||
return DemoElementEditableButtonComponent; | ||
} else if (element.type === 'link') { | ||
return DemoElementLinkComponent; | ||
} | ||
}; | ||
|
||
onKeydown = (event: KeyboardEvent) => { | ||
const { selection } = this.editor; | ||
|
||
// Default left/right behavior is unit:'character'. | ||
// This fails to distinguish between two cursor positions, such as | ||
// <inline>foo<cursor/></inline> vs <inline>foo</inline><cursor/>. | ||
// Here we modify the behavior to unit:'offset'. | ||
// This lets the user step into and out of the inline without stepping over characters. | ||
// You may wish to customize this further to only use unit:'offset' in specific cases. | ||
if (selection && Range.isCollapsed(selection)) { | ||
const nativeEvent = event | ||
if (isKeyHotkey('left', nativeEvent)) { | ||
event.preventDefault() | ||
Transforms.move(this.editor, { unit: 'offset', reverse: true }) | ||
return | ||
} | ||
if (isKeyHotkey('right', nativeEvent)) { | ||
event.preventDefault() | ||
Transforms.move(this.editor, { unit: 'offset' }) | ||
return | ||
} | ||
} | ||
}; | ||
|
||
valueChange(value: Element[]) { | ||
} | ||
} | ||
|
||
const withInlines = editor => { | ||
const { insertData, insertText, isInline } = editor | ||
|
||
editor.isInline = element => | ||
['link', 'button'].includes(element.type) || isInline(element) | ||
|
||
editor.insertText = text => { | ||
if (text && isUrl(text)) { | ||
wrapLink(editor, text) | ||
} else { | ||
insertText(text) | ||
} | ||
} | ||
|
||
editor.insertData = data => { | ||
const text = data.getData('text/plain') | ||
|
||
if (text && isUrl(text)) { | ||
wrapLink(editor, text) | ||
} else { | ||
insertData(data) | ||
} | ||
} | ||
|
||
return editor | ||
} | ||
|
||
const insertLink = (editor, url) => { | ||
if (editor.selection) { | ||
wrapLink(editor, url) | ||
} | ||
} | ||
|
||
const insertButton = editor => { | ||
if (editor.selection) { | ||
wrapButton(editor) | ||
} | ||
} | ||
|
||
const isLinkActive = editor => { | ||
const [link] = Editor.nodes(editor, { | ||
match: n => | ||
!Editor.isEditor(n) && SlateElement.isElement(n) && n.type === 'link', | ||
}) | ||
return !!link | ||
} | ||
|
||
const isButtonActive = editor => { | ||
const [button] = Editor.nodes(editor, { | ||
match: n => | ||
!Editor.isEditor(n) && SlateElement.isElement(n) && n.type === 'button', | ||
}) | ||
return !!button | ||
} | ||
|
||
const unwrapLink = editor => { | ||
Transforms.unwrapNodes(editor, { | ||
match: n => | ||
!Editor.isEditor(n) && SlateElement.isElement(n) && n.type === 'link', | ||
}) | ||
} | ||
|
||
const unwrapButton = editor => { | ||
Transforms.unwrapNodes(editor, { | ||
match: n => | ||
!Editor.isEditor(n) && SlateElement.isElement(n) && n.type === 'button', | ||
}) | ||
} | ||
|
||
const wrapLink = (editor, url: string) => { | ||
if (isLinkActive(editor)) { | ||
unwrapLink(editor) | ||
} | ||
|
||
const { selection } = editor | ||
const isCollapsed = selection && Range.isCollapsed(selection) | ||
const link: LinkElement = { | ||
type: 'link', | ||
url, | ||
children: isCollapsed ? [{ text: url }] : [], | ||
} | ||
|
||
if (isCollapsed) { | ||
Transforms.insertNodes(editor, link) | ||
} else { | ||
Transforms.wrapNodes(editor, link, { split: true }) | ||
Transforms.collapse(editor, { edge: 'end' }) | ||
} | ||
} | ||
|
||
const wrapButton = editor => { | ||
if (isButtonActive(editor)) { | ||
unwrapButton(editor) | ||
} | ||
|
||
const { selection } = editor | ||
const isCollapsed = selection && Range.isCollapsed(selection) | ||
const button: ButtonElement = { | ||
type: 'button', | ||
children: isCollapsed ? [{ text: 'Edit me!' }] : [], | ||
} | ||
|
||
if (isCollapsed) { | ||
Transforms.insertNodes(editor, button) | ||
} else { | ||
Transforms.wrapNodes(editor, button, { split: true }) | ||
Transforms.collapse(editor, { edge: 'end' }) | ||
} | ||
} | ||
|
||
const initialValue: Descendant[] = [ | ||
{ | ||
type: 'paragraph', | ||
children: [ | ||
{ | ||
text: | ||
'In addition to block nodes, you can create inline nodes. Here is a ', | ||
}, | ||
{ | ||
type: 'link', | ||
url: 'https://en.wikipedia.org/wiki/Hypertext', | ||
children: [{ text: 'hyperlink' }], | ||
}, | ||
{ | ||
text: ', and here is a more unusual inline: an ', | ||
}, | ||
{ | ||
type: 'button', | ||
children: [{ text: 'editable button' }], | ||
}, | ||
{ | ||
text: '!', | ||
}, | ||
], | ||
}, | ||
{ | ||
type: 'paragraph', | ||
children: [ | ||
{ | ||
text: | ||
'There are two ways to add links. You can either add a link via the toolbar icon above, or if you want in on a little secret, copy a URL to your keyboard and paste it while a range of text is selected. ', | ||
}, | ||
// The following is an example of an inline at the end of a block. | ||
// This is an edge case that can cause issues. | ||
{ | ||
type: 'link', | ||
url: 'https://twitter.com/JustMissEmma/status/1448679899531726852', | ||
children: [{ text: 'Finally, here is our favorite dog video.' }], | ||
}, | ||
{ text: '' }, | ||
], | ||
}, | ||
] |
Oops, something went wrong.