Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 0 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -71,8 +71,6 @@
"eslint-plugin-prettier": "^3.1.2",
"eslint-plugin-react": "^7.7.0",
"prettier": "^1.19.1",
"react": "^16.8.6",
"react-dom": "^16.8.6",
"source-map-loader": "^0.2.4",
"ts-loader": "^6.2.1",
"webpack": "^4.29.6",
Expand Down
228 changes: 228 additions & 0 deletions src/components/TagMenu.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,228 @@
import * as React from 'react';
import { findChildren } from 'prosemirror-utils';
import { EditorView } from "prosemirror-view";
import { v4 as uuidv4 } from 'uuid';
import styled from "styled-components";
import { CloseIcon } from "outline-icons";

type Props = {
view: EditorView;
};

type TagListProps = {
key: string;
selectedKey: string;
tagName: string;
deleteTag: () => void
}

class TagMenu extends React.Component<Props> {

menuRef = React.createRef<HTMLDivElement>();

state = {
left: 20,
top: -1000,
tag: "",
position: -1000,
existingAttrs: {},
selectedTagKey: ""
};

componentDidUpdate(prevProps,prevState) {
const nextStyle = this.calculatePosition(this.props);
if(prevState.top != nextStyle.top){
this.setState({ top: nextStyle.top, left: nextStyle.left, tag: "", position: nextStyle.position, existingAttrs: nextStyle.existingAttrs, selectedTagIndex: "" });
}
}

handleTagChange(event) {
this.setState({tag: event.target.value})
}

addTag(event){
const { view } = this.props;
const { state } = view;
const { tr, selection } = state
const tagName = this.state.tag
var newTags = {}
for (var key in this.state.existingAttrs){
newTags[key] = this.state.existingAttrs[key]
}
newTags[uuidv4()] = tagName;
const transaction = tr.setNodeMarkup(this.state.position, undefined, {tags: newTags});
view.dispatch(transaction);
this.setState({tag: "", existingAttrs: newTags})
}

deleteTag(keyToDelete){
const { view } = this.props;
const { state } = view;
const { tr, selection } = state
const tagName = this.state.tag
var newTags = {}
for (var key in this.state.existingAttrs){
if(key !== keyToDelete){
newTags[key] = this.state.existingAttrs[key]
}
}
const transaction = tr.setNodeMarkup(this.state.position, undefined, {tags: newTags});
view.dispatch(transaction);
this.setState({existingAttrs: newTags})
}
render() {
return (
<div>
<div style={{top: this.state.top,
left: this.state.left,
position: "absolute",
background: "#f2f2f4",
height: "40px",
width: "250px"}}
ref={this.menuRef}>
<div style={{width:"90%",
margin: "auto",
paddingTop: "3%"}}>
<input placeholder="Tag Name" value={this.state.tag} onChange={this.handleTagChange.bind(this)}/>
<button style={{float:"right"}} onClick={this.addTag.bind(this)}>Add Tag</button>
</div>
<nav style={{top: "0px",
left: "250px",
position: "absolute"}}>
<ul style={{overflow: "hidden",
overflowY: "scroll",
listStyle: "none",
paddingLeft: "5px",
marginTop: "0px"}}>
{Object.keys(this.state.existingAttrs).map((key, index) => {
return <TagItem key={key} selectedKey={this.state.selectedTagKey} tagName={this.state.existingAttrs[key]} deleteTag={() => this.deleteTag(key)}/>
})}
</ul>
</nav>
</div>
</div>
)
}

getScrollTop() {
return (
window.pageYOffset ||
document.documentElement.scrollTop ||
document.body.scrollTop ||
0
);
}

getScrollRight(){
return (
window.pageXOffset ||
document.documentElement.scrollLeft ||
document.body.scrollLeft ||
0
);
}

getOffset(element) {
const rect = element.getBoundingClientRect();
return {
top: rect.top + this.getScrollTop(),
right: rect.right
}
}

calculatePosition(props) {
const { view } = props;
const { state } = view;
const { selection } = state;

if (!selection) {
return {
top: -1000
}
}

const { $anchor } = selection;
const resolvedPos = state.doc.resolve($anchor.pos) as any;
const rowNumber = resolvedPos.path[1];
let i = 0;
if ($anchor.pos === 0) {
return {
top: -1000
}
}
const [firstNode] = findChildren(
state.doc,
_node => {
if (rowNumber === i || rowNumber + 1 === i) {
i++
return true
}
i++
return false
},
false
)
if (!firstNode) {
return {
top: -1000
}
}

const coords = view.coordsAtPos(firstNode.pos);
const dom = view.nodeDOM(firstNode.pos) as HTMLElement;
const element = this.getOffset(dom);

if (coords.top === 0) {
return {
top: -1000
}
}
return {
left: element.right - 250,
top: element.top - 40,
position: firstNode.pos,
existingAttrs: firstNode.node.attrs.tags
}
}
}

class TagItem extends React.Component<TagListProps> {
render() {
return(
<li
key={ this.props.key }
className="tagList"
style={{width: "100px"}}
>
<div
style = {{
display: "flex",
justifyContent: "center",
alignItems: "center",
border: "1px solid rgb(78, 92, 110)",
borderRadius: "5px"
}}
>
<div
style = {{
fontFamily: "'SFMono-Regular',Consolas,'Liberation Mono',Menlo,Courier,monospace",
color: "#4E5C6E",
fontSize: "13px",
width: "80%",
textAlign: "center"
}}>
{this.props.tagName}
</div>
<div
style={{borderLeft: "1px solid rgb(78, 92, 110)",
cursor: "pointer"}}
onClick={() => this.props.deleteTag()}
>
<CloseIcon/>
</div>
</div>
</li>
)
}
}
export default TagMenu;
10 changes: 10 additions & 0 deletions src/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import { SearchResult } from "./components/LinkEditor";
import { EmbedDescriptor } from "./types";
import FloatingToolbar from "./components/FloatingToolbar";
import BlockMenu from "./components/BlockMenu";
import TagMenu from "./components/TagMenu";
import Tooltip from "./components/Tooltip";
import Extension from "./lib/Extension";
import ExtensionManager from "./lib/ExtensionManager";
Expand Down Expand Up @@ -63,6 +64,7 @@ import SmartText from "./plugins/SmartText";
import TrailingNode from "./plugins/TrailingNode";
import MarkdownPaste from "./plugins/MarkdownPaste";
import TagFiltering from "./plugins/TagFiltering";
import SelectedNode from "./plugins/SelectedNode";

export { schema, parser, serializer } from "./server";

Expand Down Expand Up @@ -264,6 +266,7 @@ class RichMarkdownEditor extends React.PureComponent<Props, State> {
new TrailingNode(),
new MarkdownPaste(),
new TagFiltering(),
new SelectedNode(),
new Keys({
onSave: this.handleSave,
onSaveAndExit: this.handleSaveAndExit,
Expand Down Expand Up @@ -567,6 +570,9 @@ class RichMarkdownEditor extends React.PureComponent<Props, State> {
onShowToast={this.props.onShowToast}
embeds={this.props.embeds}
/>
<TagMenu
view={this.view}
/>
</React.Fragment>
)}
</React.Fragment>
Expand Down Expand Up @@ -620,6 +626,10 @@ const StyledEditor = styled("div")<{ readOnly: boolean }>`
opacity: 0.5;
}

.selectedNode{
background: #f2f2f4
}

.ProseMirror-hideselection *::selection {
background: transparent;
}
Expand Down
5 changes: 5 additions & 0 deletions src/nodes/Blockquote.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,11 @@ export default class Blockquote extends Node {

get schema() {
return {
attrs: {
tags: {
default: {}
}
},
content: "block+",
group: "block",
parseDOM: [{ tag: "blockquote" }],
Expand Down
5 changes: 5 additions & 0 deletions src/nodes/BulletList.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,11 @@ export default class BulletList extends Node {

get schema() {
return {
attrs: {
tags: {
default: {}
}
},
content: "list_item+",
group: "block",
parseDOM: [{ tag: "ul" }],
Expand Down
3 changes: 3 additions & 0 deletions src/nodes/CheckboxItem.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,9 @@ export default class CheckboxItem extends Node {
checked: {
default: false,
},
tags: {
default: {}
}
},
content: "paragraph block*",
defining: true,
Expand Down
5 changes: 5 additions & 0 deletions src/nodes/CheckboxList.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,11 @@ export default class CheckboxList extends Node {

get schema() {
return {
attrs: {
tags: {
default: {}
}
},
group: "block",
content: "checkbox_item+",
toDOM: () => ["ul", { class: this.name }, 0],
Expand Down
3 changes: 3 additions & 0 deletions src/nodes/CodeFence.ts
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,9 @@ export default class CodeFence extends Node {
language: {
default: "javascript",
},
tags: {
default: {}
}
},
content: "text*",
marks: "",
Expand Down
3 changes: 3 additions & 0 deletions src/nodes/Embed.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,9 @@ export default class Embed extends Node {
href: {},
component: {},
matches: {},
tags: {
default: {}
}
},
parseDOM: [{ tag: "iframe" }],
toDOM: node => [
Expand Down
4 changes: 4 additions & 0 deletions src/nodes/Heading.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,9 @@ export default class Heading extends Node {
level: {
default: 1,
},
tags: {
default: {}
}
},
content: "inline*",
group: "block",
Expand All @@ -46,6 +49,7 @@ export default class Heading extends Node {
button.className = "heading-anchor";
button.addEventListener("click", this.handleCopyLink());
// TODO (carl) hiding then unhiding changes h level to 1 somehow
// add the text node to the newly created div
if (node.attrs.hidden) {
return [
`h${node.attrs.level + (this.options.offset || 0)}`,
Expand Down
3 changes: 3 additions & 0 deletions src/nodes/Image.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,9 @@ export default class Image extends Node {
alt: {
default: null,
},
tags:{
default: {}
}
},
content: "text*",
marks: "",
Expand Down
3 changes: 3 additions & 0 deletions src/nodes/ListItem.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,9 @@ export default class ListItem extends Node {
hidden: {
default: false,
},
tags: {
default: {}
}
},
content: "paragraph block*",
defining: true,
Expand Down
3 changes: 3 additions & 0 deletions src/nodes/OrderedList.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,9 @@ export default class OrderedList extends Node {
order: {
default: 1,
},
tags: {
default: {}
}
},
content: "list_item+",
group: "block",
Expand Down
Loading