diff options
author | laurawilsonri <laura_wilson@brown.edu> | 2019-04-11 12:06:46 -0400 |
---|---|---|
committer | laurawilsonri <laura_wilson@brown.edu> | 2019-04-11 12:06:46 -0400 |
commit | 15514b0f3d685764d1bd7ebeac9cdee1f778e184 (patch) | |
tree | 0f9ddac2881bac4b0a15c7f460050f59bd787d08 | |
parent | 74411165f21b6c616fa98e0676c6f4568c2d4564 (diff) |
font style and size dropdowns work
-rw-r--r-- | package.json | 332 | ||||
-rw-r--r-- | src/client/util/RichTextSchema.tsx | 507 | ||||
-rw-r--r-- | src/client/util/TooltipTextMenu.scss | 238 | ||||
-rw-r--r-- | src/client/util/TooltipTextMenu.tsx | 173 | ||||
-rw-r--r-- | src/client/views/nodes/FormattedTextBox.tsx | 7 |
5 files changed, 830 insertions, 427 deletions
diff --git a/package.json b/package.json index 135bd4a91..11eb6f0f5 100644 --- a/package.json +++ b/package.json @@ -1,168 +1,168 @@ { - "name": "dash", - "version": "1.0.0", - "description": "", - "main": "index.js", - "scripts": { - "start": "nodemon --watch src/server/**/*.ts --exec ts-node src/server/index.ts", - "build": "webpack --env production", - "test": "mocha -r ts-node/register test/**/*.ts", - "tsc": "tsc" - }, - "devDependencies": { - "@types/chai": "^4.1.7", - "@types/mocha": "^5.2.6", - "@types/react-dom": "^16.8.2", - "@types/webpack-dev-middleware": "^2.0.2", - "@types/webpack-hot-middleware": "^2.16.4", - "awesome-typescript-loader": "^5.2.1", - "chai": "^4.2.0", - "copy-webpack-plugin": "^4.6.0", - "css-loader": "^2.1.1", - "file-loader": "^3.0.1", - "mocha": "^5.2.0", - "sass-loader": "^7.1.0", - "scss-loader": "0.0.1", - "style-loader": "^0.23.1", - "ts-node": "^7.0.1", - "typescript": "^3.4.1", - "webpack": "^4.29.6", - "webpack-cli": "^3.2.3", - "webpack-dev-middleware": "^3.6.1", - "webpack-dev-server": "^3.2.1", - "webpack-hot-middleware": "^2.24.3" - }, - "dependencies": { - "@fortawesome/fontawesome-free-solid": "^5.0.13", - "@fortawesome/fontawesome-svg-core": "^1.2.15", - "@fortawesome/free-solid-svg-icons": "^5.7.2", - "@fortawesome/react-fontawesome": "^0.1.4", - "@hig/flyout": "^1.0.3", - "@hig/theme-context": "^2.1.3", - "@hig/theme-data": "^2.3.3", - "@trendmicro/react-dropdown": "^1.3.0", - "@types/async": "^2.4.1", - "@types/bcrypt-nodejs": "0.0.30", - "@types/bluebird": "^3.5.25", - "@types/body-parser": "^1.17.0", - "@types/connect-flash": "0.0.34", - "@types/cookie-parser": "^1.4.1", - "@types/cookie-session": "^2.0.36", - "@types/d3-format": "^1.3.1", - "@types/express": "^4.16.1", - "@types/express-flash": "0.0.0", - "@types/express-session": "^1.15.12", - "@types/express-validator": "^3.0.0", - "@types/formidable": "^1.0.31", - "@types/jquery": "^3.3.29", - "@types/jsonwebtoken": "^8.3.2", - "@types/lodash": "^4.14.121", - "@types/mobile-detect": "^1.3.4", - "@types/mongodb": "^3.1.22", - "@types/mongoose": "^5.3.21", - "@types/node": "^10.12.30", - "@types/nodemailer": "^4.6.6", - "@types/passport": "^1.0.0", - "@types/passport-local": "^1.0.33", - "@types/prosemirror-commands": "^1.0.1", - "@types/prosemirror-history": "^1.0.1", - "@types/prosemirror-inputrules": "^1.0.2", - "@types/prosemirror-keymap": "^1.0.1", - "@types/prosemirror-menu": "^1.0.1", - "@types/prosemirror-model": "^1.7.0", - "@types/prosemirror-schema-basic": "^1.0.1", - "@types/prosemirror-schema-list": "^1.0.1", - "@types/prosemirror-state": "^1.2.3", - "@types/prosemirror-transform": "^1.1.0", - "@types/prosemirror-view": "^1.3.1", - "@types/pug": "^2.0.4", - "@types/react": "^16.8.7", - "@types/react-color": "^2.14.1", - "@types/react-measure": "^2.0.4", - "@types/react-table": "^6.7.22", - "@types/request": "^2.48.1", - "@types/request-promise": "^4.1.42", - "@types/socket.io": "^2.1.2", - "@types/socket.io-client": "^1.4.32", - "@types/typescript": "^2.0.0", - "@types/uuid": "^3.4.4", - "@types/webpack": "^4.4.25", - "async": "^2.6.2", - "babel-runtime": "^6.26.0", - "bcrypt-nodejs": "0.0.3", - "bluebird": "^3.5.3", - "body-parser": "^1.18.3", - "bootstrap": "^4.3.1", - "class-transformer": "^0.2.0", - "connect-flash": "^0.1.1", - "connect-mongo": "^2.0.3", - "cookie-parser": "^1.4.4", - "cookie-session": "^2.0.0-beta.3", - "crypto-browserify": "^3.11.0", - "d3-format": "^1.3.2", - "express": "^4.16.4", - "express-flash": "0.0.2", - "express-session": "^1.15.6", - "express-validator": "^5.3.1", - "expressjs": "^1.0.1", - "flexlayout-react": "^0.3.3", - "font-awesome": "^4.7.0", - "formidable": "^1.2.1", - "golden-layout": "^1.5.9", - "html-to-image": "^0.1.0", - "i": "^0.3.6", - "jsonwebtoken": "^8.5.0", - "jsx-to-string": "^1.4.0", - "lodash": "^4.17.11", - "mobile-detect": "^1.4.3", - "mobx": "^5.9.0", - "mobx-react": "^5.3.5", - "mobx-react-devtools": "^6.1.1", - "mongodb": "^3.1.13", - "mongoose": "^5.4.18", - "node-sass": "^4.11.0", - "nodemailer": "^5.1.1", - "nodemon": "^1.18.10", - "normalize.css": "^8.0.1", - "npm": "^6.9.0", - "passport": "^0.4.0", - "passport-local": "^1.0.0", - "prosemirror-commands": "^1.0.7", - "prosemirror-example-setup": "^1.0.1", - "prosemirror-history": "^1.0.4", - "prosemirror-keymap": "^1.0.1", - "prosemirror-model": "^1.7.0", - "prosemirror-schema-basic": "^1.0.0", - "prosemirror-schema-list": "^1.0.2", - "prosemirror-state": "^1.2.2", - "prosemirror-transform": "^1.1.3", - "prosemirror-view": "^1.8.3", - "pug": "^2.0.3", - "raw-loader": "^1.0.0", - "react": "^16.8.4", - "react-bootstrap": "^1.0.0-beta.5", - "react-bootstrap-dropdown-menu": "^1.1.15", - "react-color": "^2.17.0", - "react-dimensions": "^1.3.1", - "react-dom": "^16.8.4", - "react-golden-layout": "^1.0.6", - "react-image-lightbox": "^5.1.0", - "react-jsx-parser": "^1.15.0", - "react-measure": "^2.2.4", - "react-mosaic": "0.0.20", - "react-pdf": "^4.0.2", - "react-pdf-highlighter": "^2.1.2", - "react-pdf-js": "^4.0.2", - "react-simple-dropdown": "^3.2.3", - "react-split-pane": "^0.1.85", - "react-table": "^6.9.2", - "request": "^2.88.0", - "request-promise": "^4.2.4", - "socket.io": "^2.2.0", - "socket.io-client": "^2.2.0", - "typescript-collections": "^1.3.2", - "url-loader": "^1.1.2", - "uuid": "^3.3.2", - "xoauth2": "^1.2.0" - } + "name": "dash", + "version": "1.0.0", + "description": "", + "main": "index.js", + "scripts": { + "start": "nodemon --watch src/server/**/*.ts --exec ts-node src/server/index.ts", + "build": "webpack --env production", + "test": "mocha -r ts-node/register test/**/*.ts", + "tsc": "tsc" + }, + "devDependencies": { + "@types/chai": "^4.1.7", + "@types/mocha": "^5.2.6", + "@types/react-dom": "^16.8.2", + "@types/webpack-dev-middleware": "^2.0.2", + "@types/webpack-hot-middleware": "^2.16.4", + "awesome-typescript-loader": "^5.2.1", + "chai": "^4.2.0", + "copy-webpack-plugin": "^4.6.0", + "css-loader": "^2.1.1", + "file-loader": "^3.0.1", + "mocha": "^5.2.0", + "sass-loader": "^7.1.0", + "scss-loader": "0.0.1", + "style-loader": "^0.23.1", + "ts-node": "^7.0.1", + "typescript": "^3.4.1", + "webpack": "^4.29.6", + "webpack-cli": "^3.2.3", + "webpack-dev-middleware": "^3.6.1", + "webpack-dev-server": "^3.2.1", + "webpack-hot-middleware": "^2.24.3" + }, + "dependencies": { + "@fortawesome/fontawesome-free-solid": "^5.0.13", + "@fortawesome/fontawesome-svg-core": "^1.2.15", + "@fortawesome/free-solid-svg-icons": "^5.7.2", + "@fortawesome/react-fontawesome": "^0.1.4", + "@hig/flyout": "^1.0.3", + "@hig/theme-context": "^2.1.3", + "@hig/theme-data": "^2.3.3", + "@trendmicro/react-dropdown": "^1.3.0", + "@types/async": "^2.4.1", + "@types/bcrypt-nodejs": "0.0.30", + "@types/bluebird": "^3.5.25", + "@types/body-parser": "^1.17.0", + "@types/connect-flash": "0.0.34", + "@types/cookie-parser": "^1.4.1", + "@types/cookie-session": "^2.0.36", + "@types/d3-format": "^1.3.1", + "@types/express": "^4.16.1", + "@types/express-flash": "0.0.0", + "@types/express-session": "^1.15.12", + "@types/express-validator": "^3.0.0", + "@types/formidable": "^1.0.31", + "@types/jquery": "^3.3.29", + "@types/jsonwebtoken": "^8.3.2", + "@types/lodash": "^4.14.121", + "@types/mobile-detect": "^1.3.4", + "@types/mongodb": "^3.1.22", + "@types/mongoose": "^5.3.21", + "@types/node": "^10.12.30", + "@types/nodemailer": "^4.6.6", + "@types/passport": "^1.0.0", + "@types/passport-local": "^1.0.33", + "@types/prosemirror-commands": "^1.0.1", + "@types/prosemirror-history": "^1.0.1", + "@types/prosemirror-inputrules": "^1.0.2", + "@types/prosemirror-keymap": "^1.0.1", + "@types/prosemirror-menu": "^1.0.1", + "@types/prosemirror-model": "^1.7.0", + "@types/prosemirror-schema-basic": "^1.0.1", + "@types/prosemirror-schema-list": "^1.0.1", + "@types/prosemirror-state": "^1.2.3", + "@types/prosemirror-transform": "^1.1.0", + "@types/prosemirror-view": "^1.3.1", + "@types/pug": "^2.0.4", + "@types/react": "^16.8.7", + "@types/react-color": "^2.14.1", + "@types/react-measure": "^2.0.4", + "@types/react-table": "^6.7.22", + "@types/request": "^2.48.1", + "@types/request-promise": "^4.1.42", + "@types/socket.io": "^2.1.2", + "@types/socket.io-client": "^1.4.32", + "@types/typescript": "^2.0.0", + "@types/uuid": "^3.4.4", + "@types/webpack": "^4.4.25", + "async": "^2.6.2", + "babel-runtime": "^6.26.0", + "bcrypt-nodejs": "0.0.3", + "bluebird": "^3.5.3", + "body-parser": "^1.18.3", + "bootstrap": "^4.3.1", + "class-transformer": "^0.2.0", + "connect-flash": "^0.1.1", + "connect-mongo": "^2.0.3", + "cookie-parser": "^1.4.4", + "cookie-session": "^2.0.0-beta.3", + "crypto-browserify": "^3.11.0", + "d3-format": "^1.3.2", + "express": "^4.16.4", + "express-flash": "0.0.2", + "express-session": "^1.15.6", + "express-validator": "^5.3.1", + "expressjs": "^1.0.1", + "flexlayout-react": "^0.3.3", + "font-awesome": "^4.7.0", + "formidable": "^1.2.1", + "golden-layout": "^1.5.9", + "html-to-image": "^0.1.0", + "i": "^0.3.6", + "jsonwebtoken": "^8.5.0", + "jsx-to-string": "^1.4.0", + "lodash": "^4.17.11", + "mobile-detect": "^1.4.3", + "mobx": "^5.9.0", + "mobx-react": "^5.3.5", + "mobx-react-devtools": "^6.1.1", + "mongodb": "^3.1.13", + "mongoose": "^5.4.18", + "node-sass": "^4.11.0", + "nodemailer": "^5.1.1", + "nodemon": "^1.18.10", + "normalize.css": "^8.0.1", + "npm": "^6.9.0", + "passport": "^0.4.0", + "passport-local": "^1.0.0", + "prosemirror-commands": "^1.0.7", + "prosemirror-example-setup": "^1.0.1", + "prosemirror-history": "^1.0.4", + "prosemirror-keymap": "^1.0.1", + "prosemirror-model": "^1.7.0", + "prosemirror-schema-basic": "^1.0.0", + "prosemirror-schema-list": "^1.0.2", + "prosemirror-state": "^1.2.2", + "prosemirror-transform": "^1.1.3", + "prosemirror-view": "^1.8.3", + "pug": "^2.0.3", + "raw-loader": "^1.0.0", + "react": "^16.8.4", + "react-bootstrap": "^1.0.0-beta.5", + "react-bootstrap-dropdown-menu": "^1.1.15", + "react-color": "^2.17.0", + "react-dimensions": "^1.3.1", + "react-dom": "^16.8.4", + "react-golden-layout": "^1.0.6", + "react-image-lightbox": "^5.1.0", + "react-jsx-parser": "^1.15.0", + "react-measure": "^2.2.4", + "react-mosaic": "0.0.20", + "react-pdf": "^4.0.2", + "react-pdf-highlighter": "^2.1.2", + "react-pdf-js": "^4.0.2", + "react-simple-dropdown": "^3.2.3", + "react-split-pane": "^0.1.85", + "react-table": "^6.9.2", + "request": "^2.88.0", + "request-promise": "^4.2.4", + "socket.io": "^2.2.0", + "socket.io-client": "^2.2.0", + "typescript-collections": "^1.3.2", + "url-loader": "^1.1.2", + "uuid": "^3.3.2", + "xoauth2": "^1.2.0" + } } diff --git a/src/client/util/RichTextSchema.tsx b/src/client/util/RichTextSchema.tsx index 489c22a57..765ac0ae3 100644 --- a/src/client/util/RichTextSchema.tsx +++ b/src/client/util/RichTextSchema.tsx @@ -7,134 +7,134 @@ import { EditorState, Transaction, NodeSelection, } from "prosemirror-state"; import { EditorView, } from "prosemirror-view"; const pDOM: DOMOutputSpecArray = ["p", 0], blockquoteDOM: DOMOutputSpecArray = ["blockquote", 0], hrDOM: DOMOutputSpecArray = ["hr"], - preDOM: DOMOutputSpecArray = ["pre", ["code", 0]], brDOM: DOMOutputSpecArray = ["br"], ulDOM: DOMOutputSpecArray = ["ul", 0] + preDOM: DOMOutputSpecArray = ["pre", ["code", 0]], brDOM: DOMOutputSpecArray = ["br"], ulDOM: DOMOutputSpecArray = ["ul", 0] // :: Object // [Specs](#model.NodeSpec) for the nodes defined in this schema. export const nodes: { [index: string]: NodeSpec } = { - // :: NodeSpec The top level document node. - doc: { - content: "block+" - }, - - // :: NodeSpec A plain paragraph textblock. Represented in the DOM - // as a `<p>` element. - paragraph: { - content: "inline*", - group: "block", - parseDOM: [{ tag: "p" }], - toDOM() { return pDOM } - }, - - // :: NodeSpec A blockquote (`<blockquote>`) wrapping one or more blocks. - blockquote: { - content: "block+", - group: "block", - defining: true, - parseDOM: [{ tag: "blockquote" }], - toDOM() { return blockquoteDOM } - }, - - // :: NodeSpec A horizontal rule (`<hr>`). - horizontal_rule: { - group: "block", - parseDOM: [{ tag: "hr" }], - toDOM() { return hrDOM } - }, - - // :: NodeSpec A heading textblock, with a `level` attribute that - // should hold the number 1 to 6. Parsed and serialized as `<h1>` to - // `<h6>` elements. - heading: { - attrs: { level: { default: 1 } }, - content: "inline*", - group: "block", - defining: true, - parseDOM: [{ tag: "h1", attrs: { level: 1 } }, - { tag: "h2", attrs: { level: 2 } }, - { tag: "h3", attrs: { level: 3 } }, - { tag: "h4", attrs: { level: 4 } }, - { tag: "h5", attrs: { level: 5 } }, - { tag: "h6", attrs: { level: 6 } }], - toDOM(node: any) { return ["h" + node.attrs.level, 0] } - }, - - // :: NodeSpec A code listing. Disallows marks or non-text inline - // nodes by default. Represented as a `<pre>` element with a - // `<code>` element inside of it. - code_block: { - content: "text*", - marks: "", - group: "block", - code: true, - defining: true, - parseDOM: [{ tag: "pre", preserveWhitespace: "full" }], - toDOM() { return preDOM } - }, - - // :: NodeSpec The text node. - text: { - group: "inline" - }, - - // :: NodeSpec An inline image (`<img>`) node. Supports `src`, - // `alt`, and `href` attributes. The latter two default to the empty - // string. - image: { - inline: true, - attrs: { - src: {}, - alt: { default: null }, - title: { default: null } - }, - group: "inline", - draggable: true, - parseDOM: [{ - tag: "img[src]", getAttrs(dom: any) { - return { - src: dom.getAttribute("src"), - title: dom.getAttribute("title"), - alt: dom.getAttribute("alt") - } - } - }], - toDOM(node: any) { return ["img", node.attrs] } - }, - - // :: NodeSpec A hard line break, represented in the DOM as `<br>`. - hard_break: { - inline: true, - group: "inline", - selectable: false, - parseDOM: [{ tag: "br" }], - toDOM() { return brDOM } - }, - - ordered_list: { - ...orderedList, - content: 'list_item+', - group: 'block' - }, - //this doesn't currently work for some reason - bullet_list: { - ...bulletList, - content: 'list_item+', - group: 'block', - // parseDOM: [{ tag: "ul" }, { style: 'list-style-type=disc' }], - // toDOM() { return ulDOM } - }, - //bullet_list: { - // content: 'list_item+', - // group: 'block', - //active: blockActive(schema.nodes.bullet_list), - //enable: wrapInList(schema.nodes.bullet_list), - //run: wrapInList(schema.nodes.bullet_list), - //select: state => true, - // }, - list_item: { - ...listItem, - content: 'paragraph block*' - } + // :: NodeSpec The top level document node. + doc: { + content: "block+" + }, + + // :: NodeSpec A plain paragraph textblock. Represented in the DOM + // as a `<p>` element. + paragraph: { + content: "inline*", + group: "block", + parseDOM: [{ tag: "p" }], + toDOM() { return pDOM } + }, + + // :: NodeSpec A blockquote (`<blockquote>`) wrapping one or more blocks. + blockquote: { + content: "block+", + group: "block", + defining: true, + parseDOM: [{ tag: "blockquote" }], + toDOM() { return blockquoteDOM } + }, + + // :: NodeSpec A horizontal rule (`<hr>`). + horizontal_rule: { + group: "block", + parseDOM: [{ tag: "hr" }], + toDOM() { return hrDOM } + }, + + // :: NodeSpec A heading textblock, with a `level` attribute that + // should hold the number 1 to 6. Parsed and serialized as `<h1>` to + // `<h6>` elements. + heading: { + attrs: { level: { default: 1 } }, + content: "inline*", + group: "block", + defining: true, + parseDOM: [{ tag: "h1", attrs: { level: 1 } }, + { tag: "h2", attrs: { level: 2 } }, + { tag: "h3", attrs: { level: 3 } }, + { tag: "h4", attrs: { level: 4 } }, + { tag: "h5", attrs: { level: 5 } }, + { tag: "h6", attrs: { level: 6 } }], + toDOM(node: any) { return ["h" + node.attrs.level, 0] } + }, + + // :: NodeSpec A code listing. Disallows marks or non-text inline + // nodes by default. Represented as a `<pre>` element with a + // `<code>` element inside of it. + code_block: { + content: "text*", + marks: "", + group: "block", + code: true, + defining: true, + parseDOM: [{ tag: "pre", preserveWhitespace: "full" }], + toDOM() { return preDOM } + }, + + // :: NodeSpec The text node. + text: { + group: "inline" + }, + + // :: NodeSpec An inline image (`<img>`) node. Supports `src`, + // `alt`, and `href` attributes. The latter two default to the empty + // string. + image: { + inline: true, + attrs: { + src: {}, + alt: { default: null }, + title: { default: null } + }, + group: "inline", + draggable: true, + parseDOM: [{ + tag: "img[src]", getAttrs(dom: any) { + return { + src: dom.getAttribute("src"), + title: dom.getAttribute("title"), + alt: dom.getAttribute("alt") + } + } + }], + toDOM(node: any) { return ["img", node.attrs] } + }, + + // :: NodeSpec A hard line break, represented in the DOM as `<br>`. + hard_break: { + inline: true, + group: "inline", + selectable: false, + parseDOM: [{ tag: "br" }], + toDOM() { return brDOM } + }, + + ordered_list: { + ...orderedList, + content: 'list_item+', + group: 'block' + }, + //this doesn't currently work for some reason + bullet_list: { + ...bulletList, + content: 'list_item+', + group: 'block', + // parseDOM: [{ tag: "ul" }, { style: 'list-style-type=disc' }], + // toDOM() { return ulDOM } + }, + //bullet_list: { + // content: 'list_item+', + // group: 'block', + //active: blockActive(schema.nodes.bullet_list), + //enable: wrapInList(schema.nodes.bullet_list), + //run: wrapInList(schema.nodes.bullet_list), + //select: state => true, + // }, + list_item: { + ...listItem, + content: 'paragraph block*' + } } const emDOM: DOMOutputSpecArray = ["em", 0]; @@ -144,92 +144,179 @@ const underlineDOM: DOMOutputSpecArray = ["underline", 0]; // :: Object [Specs](#model.MarkSpec) for the marks in the schema. export const marks: { [index: string]: MarkSpec } = { - // :: MarkSpec A link. Has `href` and `title` attributes. `title` - // defaults to the empty string. Rendered and parsed as an `<a>` - // element. - link: { - attrs: { - href: {}, - title: { default: null } - }, - inclusive: false, - parseDOM: [{ - tag: "a[href]", getAttrs(dom: any) { - return { href: dom.getAttribute("href"), title: dom.getAttribute("title") } - } - }], - toDOM(node: any) { return ["a", node.attrs, 0] } - }, - - // :: MarkSpec An emphasis mark. Rendered as an `<em>` element. - // Has parse rules that also match `<i>` and `font-style: italic`. - em: { - parseDOM: [{ tag: "i" }, { tag: "em" }, { style: "font-style=italic" }], - toDOM() { return emDOM } - }, - - // :: MarkSpec A strong mark. Rendered as `<strong>`, parse rules - // also match `<b>` and `font-weight: bold`. - strong: { - parseDOM: [{ tag: "strong" }, - { tag: "b" }, - { style: "font-weight" }], - toDOM() { return strongDOM } - }, - - underline: { - parseDOM: [ - { tag: 'u' }, - { style: 'text-decoration=underline' } - ], - toDOM: () => ['span', { - style: 'text-decoration:underline' - }] - }, - - strikethrough: { - parseDOM: [ - { tag: 'strike' }, - { style: 'text-decoration=line-through' }, - { style: 'text-decoration-line=line-through' } - ], - toDOM: () => ['span', { - style: 'text-decoration-line:line-through' - }] - }, - - subscript: { - excludes: 'superscript', - parseDOM: [ - { tag: 'sub' }, - { style: 'vertical-align=sub' } - ], - toDOM: () => ['sub'] - }, - - superscript: { - excludes: 'subscript', - parseDOM: [ - { tag: 'sup' }, - { style: 'vertical-align=super' } - ], - toDOM: () => ['sup'] - }, - - - // :: MarkSpec Code font mark. Represented as a `<code>` element. - code: { - parseDOM: [{ tag: "code" }], - toDOM() { return codeDOM } - }, - - - timesNewRoman: { - parseDOM: [{ style: 'font-family: "Times New Roman", Times, serif;' }], - toDOM: () => ['span', { - style: 'font-family: "Times New Roman", Times, serif;' - }] - }, + // :: MarkSpec A link. Has `href` and `title` attributes. `title` + // defaults to the empty string. Rendered and parsed as an `<a>` + // element. + link: { + attrs: { + href: {}, + title: { default: null } + }, + inclusive: false, + parseDOM: [{ + tag: "a[href]", getAttrs(dom: any) { + return { href: dom.getAttribute("href"), title: dom.getAttribute("title") } + } + }], + toDOM(node: any) { return ["a", node.attrs, 0] } + }, + + // :: MarkSpec An emphasis mark. Rendered as an `<em>` element. + // Has parse rules that also match `<i>` and `font-style: italic`. + em: { + parseDOM: [{ tag: "i" }, { tag: "em" }, { style: "font-style=italic" }], + toDOM() { return emDOM } + }, + + // :: MarkSpec A strong mark. Rendered as `<strong>`, parse rules + // also match `<b>` and `font-weight: bold`. + strong: { + parseDOM: [{ tag: "strong" }, + { tag: "b" }, + { style: "font-weight" }], + toDOM() { return strongDOM } + }, + + underline: { + parseDOM: [ + { tag: 'u' }, + { style: 'text-decoration=underline' } + ], + toDOM: () => ['span', { + style: 'text-decoration:underline' + }] + }, + + strikethrough: { + parseDOM: [ + { tag: 'strike' }, + { style: 'text-decoration=line-through' }, + { style: 'text-decoration-line=line-through' } + ], + toDOM: () => ['span', { + style: 'text-decoration-line:line-through' + }] + }, + + subscript: { + excludes: 'superscript', + parseDOM: [ + { tag: 'sub' }, + { style: 'vertical-align=sub' } + ], + toDOM: () => ['sub'] + }, + + superscript: { + excludes: 'subscript', + parseDOM: [ + { tag: 'sup' }, + { style: 'vertical-align=super' } + ], + toDOM: () => ['sup'] + }, + + + // :: MarkSpec Code font mark. Represented as a `<code>` element. + code: { + parseDOM: [{ tag: "code" }], + toDOM() { return codeDOM } + }, + + + /* FONTS */ + timesNewRoman: { + parseDOM: [{ style: 'font-family: "Times New Roman", Times, serif;' }], + toDOM: () => ['span', { + style: 'font-family: "Times New Roman", Times, serif;' + }] + }, + + arial: { + parseDOM: [{ style: 'font-family: Arial, Helvetica, sans-serif;' }], + toDOM: () => ['span', { + style: 'font-family: Arial, Helvetica, sans-serif;' + }] + }, + + georgia: { + parseDOM: [{ style: 'font-family: Georgia, serif;' }], + toDOM: () => ['span', { + style: 'font-family: Georgia, serif;' + }] + }, + + comicSans: { + parseDOM: [{ style: 'font-family: "Comic Sans MS", cursive, sans-serif;' }], + toDOM: () => ['span', { + style: 'font-family: "Comic Sans MS", cursive, sans-serif;' + }] + }, + + tahoma: { + parseDOM: [{ style: 'font-family: Tahoma, Geneva, sans-serif;' }], + toDOM: () => ['span', { + style: 'font-family: Tahoma, Geneva, sans-serif;' + }] + }, + + impact: { + parseDOM: [{ style: 'font-family: Impact, Charcoal, sans-serif;' }], + toDOM: () => ['span', { + style: 'font-family: Impact, Charcoal, sans-serif;' + }] + }, + + /** FONT SIZES */ + + p10: { + parseDOM: [{ style: 'font-size: 10px;' }], + toDOM: () => ['span', { + style: 'font-size: 10px;' + }] + }, + + p12: { + parseDOM: [{ style: 'font-size: 12px;' }], + toDOM: () => ['span', { + style: 'font-size: 12px;' + }] + }, + + p16: { + parseDOM: [{ style: 'font-size: 16px;' }], + toDOM: () => ['span', { + style: 'font-size: 16px;' + }] + }, + + p24: { + parseDOM: [{ style: 'font-size: 24px;' }], + toDOM: () => ['span', { + style: 'font-size: 24px;' + }] + }, + + p32: { + parseDOM: [{ style: 'font-size: 32px;' }], + toDOM: () => ['span', { + style: 'font-size: 32px;' + }] + }, + + p48: { + parseDOM: [{ style: 'font-size: 48px;' }], + toDOM: () => ['span', { + style: 'font-size: 48px;' + }] + }, + + p72: { + parseDOM: [{ style: 'font-size: 72px;' }], + toDOM: () => ['span', { + style: 'font-size: 72px;' + }] + }, } // :: Schema diff --git a/src/client/util/TooltipTextMenu.scss b/src/client/util/TooltipTextMenu.scss index ea580d104..b23074239 100644 --- a/src/client/util/TooltipTextMenu.scss +++ b/src/client/util/TooltipTextMenu.scss @@ -1,5 +1,241 @@ @import "../views/global_variables"; +.ProseMirror-textblock-dropdown { + min-width: 3em; + } + + .ProseMirror-menu { + margin: 0 -4px; + line-height: 1; + } + + .ProseMirror-tooltip .ProseMirror-menu { + width: -webkit-fit-content; + width: fit-content; + white-space: pre; + } + + .ProseMirror-menuitem { + margin-right: 3px; + display: inline-block; + } + + .ProseMirror-menuseparator { + // border-right: 1px solid #ddd; + margin-right: 3px; + } + + .ProseMirror-menu-dropdown, .ProseMirror-menu-dropdown-menu { + font-size: 90%; + white-space: nowrap; + } + + .ProseMirror-menu-dropdown { + vertical-align: 1px; + cursor: pointer; + position: relative; + padding-right: 15px; + } + + .ProseMirror-menu-dropdown-wrap { + padding: 1px 0 1px 4px; + display: inline-block; + position: relative; + } + + .ProseMirror-menu-dropdown:after { + content: ""; + border-left: 4px solid transparent; + border-right: 4px solid transparent; + border-top: 4px solid currentColor; + opacity: .6; + position: absolute; + right: 4px; + top: calc(50% - 2px); + } + + .ProseMirror-menu-dropdown-menu, .ProseMirror-menu-submenu { + position: absolute; + background: $dark-color; + color:white; + border: 1px solid #aaa; + padding: 2px; + } + + .ProseMirror-menu-dropdown-menu { + z-index: 15; + min-width: 6em; + } + + .ProseMirror-menu-dropdown-item { + cursor: pointer; + padding: 2px 8px 2px 4px; + width: auto; + } + + .ProseMirror-menu-dropdown-item:hover { + background: #2e2b2b; + } + + .ProseMirror-menu-submenu-wrap { + position: relative; + margin-right: -4px; + } + + .ProseMirror-menu-submenu-label:after { + content: ""; + border-top: 4px solid transparent; + border-bottom: 4px solid transparent; + border-left: 4px solid currentColor; + opacity: .6; + position: absolute; + right: 4px; + top: calc(50% - 4px); + } + + .ProseMirror-menu-submenu { + display: none; + min-width: 4em; + left: 100%; + top: -3px; + } + + .ProseMirror-menu-active { + background: #eee; + border-radius: 4px; + } + + .ProseMirror-menu-active { + background: #eee; + border-radius: 4px; + } + + .ProseMirror-menu-disabled { + opacity: .3; + } + + .ProseMirror-menu-submenu-wrap:hover .ProseMirror-menu-submenu, .ProseMirror-menu-submenu-wrap-active .ProseMirror-menu-submenu { + display: block; + } + + .ProseMirror-menubar { + border-top-left-radius: inherit; + border-top-right-radius: inherit; + position: relative; + min-height: 1em; + color: white; + padding: 1px 6px; + top: 0; left: 0; right: 0; + border-bottom: 1px solid silver; + background:$dark-color; + z-index: 10; + -moz-box-sizing: border-box; + box-sizing: border-box; + overflow: visible; + } + + .ProseMirror-icon { + display: inline-block; + line-height: .8; + vertical-align: -2px; /* Compensate for padding */ + padding: 2px 8px; + cursor: pointer; + } + + .ProseMirror-menu-disabled.ProseMirror-icon { + cursor: default; + } + + .ProseMirror-icon svg { + fill: currentColor; + height: 1em; + } + + .ProseMirror-icon span { + vertical-align: text-top; + } +.ProseMirror-example-setup-style hr { + padding: 2px 10px; + border: none; + margin: 1em 0; + } + + .ProseMirror-example-setup-style hr:after { + content: ""; + display: block; + height: 1px; + background-color: silver; + line-height: 2px; + } + + .ProseMirror ul, .ProseMirror ol { + padding-left: 30px; + } + + .ProseMirror blockquote { + padding-left: 1em; + border-left: 3px solid #eee; + margin-left: 0; margin-right: 0; + } + + .ProseMirror-example-setup-style img { + cursor: default; + } + + .ProseMirror-prompt { + background: white; + padding: 5px 10px 5px 15px; + border: 1px solid silver; + position: fixed; + border-radius: 3px; + z-index: 11; + box-shadow: -.5px 2px 5px rgba(0, 0, 0, .2); + } + + .ProseMirror-prompt h5 { + margin: 0; + font-weight: normal; + font-size: 100%; + color: #444; + } + + .ProseMirror-prompt input[type="text"], + .ProseMirror-prompt textarea { + background: #eee; + border: none; + outline: none; + } + + .ProseMirror-prompt input[type="text"] { + padding: 0 4px; + } + + .ProseMirror-prompt-close { + position: absolute; + left: 2px; top: 1px; + color: #666; + border: none; background: transparent; padding: 0; + } + + .ProseMirror-prompt-close:after { + content: "✕"; + font-size: 12px; + } + + .ProseMirror-invalid { + background: #ffc; + border: 1px solid #cc7; + border-radius: 4px; + padding: 5px 10px; + position: absolute; + min-width: 10em; + } + + .ProseMirror-prompt-buttons { + margin-top: 5px; + display: none; + } + .tooltipMenu { position: absolute; z-index: 20; @@ -39,7 +275,7 @@ display: inline-block; border-right: 1px solid rgba(0, 0, 0, 0.2); //color: rgb(19, 18, 18); - color: $light-color; + color: white; line-height: 1; padding: 0px 2px; margin: 1px; diff --git a/src/client/util/TooltipTextMenu.tsx b/src/client/util/TooltipTextMenu.tsx index 82e9d1bac..777abe030 100644 --- a/src/client/util/TooltipTextMenu.tsx +++ b/src/client/util/TooltipTextMenu.tsx @@ -1,12 +1,12 @@ import { action, IReactionDisposer, reaction } from "mobx"; -import { Dropdown, DropdownSubmenu, MenuItem } from "prosemirror-menu"; +import { Dropdown, DropdownSubmenu, MenuItem, MenuItemSpec, renderGrouped, icons, } from "prosemirror-menu"; //no import css import { baseKeymap, lift } from "prosemirror-commands"; import { history, redo, undo } from "prosemirror-history"; import { keymap } from "prosemirror-keymap"; -import { EditorState, Transaction, NodeSelection } from "prosemirror-state"; +import { EditorState, Transaction, NodeSelection, TextSelection } from "prosemirror-state"; import { EditorView } from "prosemirror-view"; import { schema } from "./RichTextSchema"; -import { Schema, NodeType } from "prosemirror-model"; +import { Schema, NodeType, MarkType } from "prosemirror-model"; import React = require("react"); import "./TooltipTextMenu.scss"; const { toggleMark, setBlockType, wrapIn } = require("prosemirror-commands"); @@ -24,8 +24,12 @@ export class TooltipTextMenu { private tooltip: HTMLElement; private num_icons = 0; + private view: EditorView; + private fontStyles: MarkType[]; + private fontSizes: MarkType[]; constructor(view: EditorView) { + this.view = view; this.tooltip = document.createElement("div"); this.tooltip.className = "tooltipMenu"; @@ -34,7 +38,6 @@ export class TooltipTextMenu { //add additional icons library.add(faListUl); - //add the buttons to the tooltip let items = [ { command: toggleMark(schema.marks.strong), dom: this.icon("B", "strong") }, @@ -44,44 +47,142 @@ export class TooltipTextMenu { { command: toggleMark(schema.marks.superscript), dom: this.icon("s", "superscript") }, { command: toggleMark(schema.marks.subscript), dom: this.icon("s", "subscript") }, { command: wrapInList(schema.nodes.bullet_list), dom: this.icon(":", "bullets") }, - { command: toggleMark(schema.marks.timesNewRoman), dom: this.icon("x", "TNR") }, { command: lift, dom: this.icon("<", "lift") }, ] //add menu items items.forEach(({ dom, command }) => { this.tooltip.appendChild(dom); - }); - //add dropdowns - - //pointer down handler to activate button effects - this.tooltip.addEventListener("pointerdown", e => { - e.preventDefault(); - view.focus(); - //update view of icons - this.num_icons = 0; - items.forEach(({ command, dom }) => { - if (e.srcElement && dom.contains(e.srcElement as Node)) { - //let active = command(view.state, view.dispatch, view); - let active = command(view.state, view.dispatch, view); - //uncomment this if we want the bullet button to disappear if current selection is bulleted - //dom.style.display = active ? "" : "none"; - } + //pointer down handler to activate button effects + dom.addEventListener("pointerdown", e => { + e.preventDefault(); + view.focus(); + command(view.state, view.dispatch, view); }) - }) + + }); + + //dropdowns + //list of font btns to add + this.fontStyles = [ + schema.marks.timesNewRoman, + schema.marks.arial, + schema.marks.georgia, + schema.marks.comicSans, + schema.marks.tahoma, + schema.marks.impact, + ] + this.fontSizes = [ + schema.marks.p10, + schema.marks.p12, + schema.marks.p16, + schema.marks.p24, + schema.marks.p32, + schema.marks.p48, + schema.marks.p72, + ] + this.addFontDropdowns(); this.update(view, undefined); } + //adds font size and font style dropdowns + addFontDropdowns() { + //filtering function - might be unecessary + let cut = (arr: MenuItem[]) => arr.filter(x => x); + let fontBtns = [ + this.dropdownBtn("Times New Roman", "font-family: Times New Roman, Times, serif; width: 120px;", schema.marks.timesNewRoman, this.view, this.changeToMarkInGroup, this.fontStyles), + this.dropdownBtn("Arial", "font-family: Arial, Helvetica, sans-serif; width: 120px;", schema.marks.arial, this.view, this.changeToMarkInGroup, this.fontStyles), + this.dropdownBtn("Georgia", "font-family: Georgia, serif; width: 120px; width: 120px;", schema.marks.georgia, this.view, this.changeToMarkInGroup, this.fontStyles), + this.dropdownBtn("ComicSans", "font-family: Comic Sans MS, cursive, sans-serif; width: 120px;", schema.marks.comicSans, this.view, this.changeToMarkInGroup, this.fontStyles), + this.dropdownBtn("Tahoma", "font-family: Tahoma, Geneva, sans-serif; width: 120px;", schema.marks.tahoma, this.view, this.changeToMarkInGroup, this.fontStyles), + this.dropdownBtn("Impact", "font-family: Impact, Charcoal, sans-serif; width: 120px;", schema.marks.impact, this.view, this.changeToMarkInGroup, this.fontStyles), + ] + + let fontSizeBtns = [ + this.dropdownBtn("10", "width: 50px;", schema.marks.p10, this.view, this.changeToMarkInGroup, this.fontSizes), + this.dropdownBtn("12", "width: 50px;", schema.marks.p12, this.view, this.changeToMarkInGroup, this.fontSizes), + this.dropdownBtn("16", "width: 50px;", schema.marks.p16, this.view, this.changeToMarkInGroup, this.fontSizes), + this.dropdownBtn("24", "width: 50px;", schema.marks.p24, this.view, this.changeToMarkInGroup, this.fontSizes), + this.dropdownBtn("32", "width: 50px;", schema.marks.p32, this.view, this.changeToMarkInGroup, this.fontSizes), + this.dropdownBtn("48", "width: 50px;", schema.marks.p48, this.view, this.changeToMarkInGroup, this.fontSizes), + this.dropdownBtn("72", "width: 50px;", schema.marks.p72, this.view, this.changeToMarkInGroup, this.fontSizes), + ] + + //dropdown to hold font btns + let dd_fontStyle = new Dropdown(cut(fontBtns), { label: "Font Style", css: "color:white;" }) as MenuItem; + let dd_fontSize = new Dropdown(cut(fontSizeBtns), { label: "Font Size", css: "color:white;" }) as MenuItem; + this.tooltip.appendChild(dd_fontStyle.render(this.view).dom); + this.tooltip.appendChild(dd_fontSize.render(this.view).dom); + } + + //for a specific grouping of marks (passed in), remove all and apply the passed-in one to the selected text + changeToMarkInGroup(markType: MarkType, view: EditorView, fontMarks: MarkType[]) { + let { empty, $cursor, ranges } = view.state.selection as TextSelection; + let state = view.state; + let dispatch = view.dispatch; + + //remove all other active font marks + fontMarks.forEach((type) => { + if (dispatch) { + if ($cursor) { + if (type.isInSet(state.storedMarks || $cursor.marks())) { + dispatch(state.tr.removeStoredMark(type)); + } + } else { + let has = false, tr = state.tr + for (let i = 0; !has && i < ranges.length; i++) { + let { $from, $to } = ranges[i] + has = state.doc.rangeHasMark($from.pos, $to.pos, type) + } + for (let i = 0; i < ranges.length; i++) { + let { $from, $to } = ranges[i] + if (has) { + toggleMark(type)(view.state, view.dispatch, view); + } + } + } + } + }); //actually apply font + return toggleMark(markType)(view.state, view.dispatch, view); + } + + //makes a button for the drop down + //css is the style you want applied to the button + dropdownBtn(label: string, css: string, markType: MarkType, view: EditorView, changeToMarkInGroup: (markType: MarkType<any>, view: EditorView, groupMarks: MarkType[]) => any, groupMarks: MarkType[]) { + return new MenuItem({ + title: "", + label: label, + execEvent: "", + class: "menuicon", + css: css, + enable(state) { return true; }, + run() { + changeToMarkInGroup(markType, view, groupMarks); + } + }); + } // Helper function to create menu icons icon(text: string, name: string) { let span = document.createElement("span"); span.className = "menuicon " + name; span.title = name; span.textContent = text; + span.style.color = "white"; return span; } + //method for checking whether node can be inserted + canInsert(state: EditorState, nodeType: NodeType<Schema<string, string>>) { + let $from = state.selection.$from + for (let d = $from.depth; d >= 0; d--) { + let index = $from.index(d) + if ($from.node(d).canReplaceWith(index, index, nodeType)) return true + } + return false + } + + //adapted this method - use it to check if block has a tag (ie bulleting) blockActive(type: NodeType<Schema<string, string>>, state: EditorState) { let attrs = {}; @@ -100,30 +201,6 @@ export class TooltipTextMenu { } } - //this doesn't currently work but could be used to use icons for buttons - unorderedListIcon(): HTMLSpanElement { - let span = document.createElement("span"); - //let icon = document.createElement("FontAwesomeIcon"); - //icon.className = "menuicon"; - //icon.style.color = "white"; - //span.appendChild(<i style={{ color: "white" }} icon="list-ul" size="lg" />); - let i = document.createElement("i"); - i.className = "fa falist-ul"; - span.appendChild(i); - //span.appendChild(icon); - //return liftItem.spec.icon.sty - - //let sym = document.createElementNS(SVG, "symbol") - // sym.id = name - //sym.style.color = "white"; - //width then height - //sym.setAttribute("viewBox", "0 0 " + 1024 + " " + 1024); - //let path = sym.appendChild(document.createElementNS(SVG, "path")); - //path.setAttribute("d", "M219 310v329q0 7-5 12t-12 5q-8 0-13-5l-164-164q-5-5-5-13t5-13l164-164q5-5 13-5 7 0 12 5t5 12zM1024 749v109q0 7-5 12t-12 5h-987q-7 0-12-5t-5-12v-109q0-7 5-12t12-5h987q7 0 12 5t5 12zM1024 530v109q0 7-5 12t-12 5h-621q-7 0-12-5t-5-12v-109q0-7 5-12t12-5h621q7 0 12 5t5 12zM1024 310v109q0 7-5 12t-12 5h-621q-7 0-12-5t-5-12v-109q0-7 5-12t12-5h621q7 0 12 5t5 12zM1024 91v109q0 7-5 12t-12 5h-987q-7 0-12-5t-5-12v-109q0-7 5-12t12-5h987q7 0 12 5t5 12z"); - //span.appendChild(sym); - return span; - } - // Create an icon for a heading at the given level heading(level: number) { return { @@ -156,10 +233,8 @@ export class TooltipTextMenu { let left = Math.max((start.left + end.left) / 2, start.left + 3) this.tooltip.style.left = (left - box.left) + "px" //let width = Math.abs(start.left - end.left) / 2; - let width = 8 * 16 + 15; + let width = 220; let mid = Math.min(start.left, end.left) + width; - - //THIS WIDTH IS 15 * NUMBER OF ICONS + 15 this.tooltip.style.width = width + "px"; this.tooltip.style.bottom = (box.bottom - start.top) + "px"; } diff --git a/src/client/views/nodes/FormattedTextBox.tsx b/src/client/views/nodes/FormattedTextBox.tsx index e59179874..fdb1b97be 100644 --- a/src/client/views/nodes/FormattedTextBox.tsx +++ b/src/client/views/nodes/FormattedTextBox.tsx @@ -74,7 +74,12 @@ export class FormattedTextBox extends React.Component<FieldViewProps> { history(), keymap({ "Mod-z": undo, "Mod-y": redo }), keymap(baseKeymap), - this.tooltipMenuPlugin() + this.tooltipMenuPlugin(), + new Plugin({ + props: { + attributes: { class: "ProseMirror-example-setup-style" } + } + }) ] }; |