From bedfd7e30c4c331628968ea62fef736a1d2cd542 Mon Sep 17 00:00:00 2001 From: Fawn Date: Thu, 10 Oct 2019 10:35:11 -0400 Subject: added manager for managing brush tool state on rich text editor + updated styling to match more with pdf --- src/client/util/TooltipTextMenu.scss | 1006 ++++++++++++++------ src/client/util/TooltipTextMenu.tsx | 566 +++++++---- .../buxton/source/.Bill_Notes_NewO.docx.icloud | Bin 172 -> 0 bytes .../buxton/source/.Bill_Notes_OLPC.docx.icloud | Bin 172 -> 0 bytes 4 files changed, 1112 insertions(+), 460 deletions(-) delete mode 100644 src/scraping/buxton/source/.Bill_Notes_NewO.docx.icloud delete mode 100644 src/scraping/buxton/source/.Bill_Notes_OLPC.docx.icloud (limited to 'src') diff --git a/src/client/util/TooltipTextMenu.scss b/src/client/util/TooltipTextMenu.scss index ebf833dbe..6bafdbbfd 100644 --- a/src/client/util/TooltipTextMenu.scss +++ b/src/client/util/TooltipTextMenu.scss @@ -1,100 +1,68 @@ @import "../views/globalCssVariables"; - -.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; +.ProseMirror-menu-dropdown-wrap { + // margin: 0 4px; display: inline-block; - z-index: 50000; position: relative; - } - - .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 { +.ProseMirror-menu-dropdown { vertical-align: 1px; cursor: pointer; position: relative; - padding-right: 15px; - margin: 3px; + padding: 0 15px 0 4px; background: white; - border-radius: 3px; - text-align: center; - } - - .ProseMirror-menu-dropdown-wrap { - padding: 1px 0 1px 4px; - display: inline-block; + border-radius: 2px; + text-align: left; + font-size: 12px; + white-space: nowrap; + margin-right: 4px; + + &: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-submenu-wrap { 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 { - background: $dark-color; - color:white; + margin-right: -4px; +} + +.ProseMirror-menu-dropdown-menu, +.ProseMirror-menu-submenu { + font-size: 12px; + background: white; border: 1px solid rgb(223, 223, 223); - padding: 2px; - } - - .ProseMirror-menu-dropdown-menu { + min-width: 50px; z-index: 50000; - min-width: 6em; - background: white; position: absolute; - } - - .linking { - text-align: center; - } - .ProseMirror-menu-dropdown-item { - cursor: pointer; - padding: 2px 8px 2px 4px; - width: auto; - z-index: 100000; - } - - .ProseMirror-menu-dropdown-item:hover { - background: white; - } - - .ProseMirror-menu-submenu-wrap { - position: relative; - margin-right: -4px; - } - - .ProseMirror-menu-submenu-label:after { + .ProseMirror-menu-dropdown-item { + cursor: pointer; + z-index: 100000; + text-align: left; + padding: 3px; + + &:hover { + background-color: $light-color-secondary; + } + } +} + +// .ProseMirror-menu-submenu { +// display: none; +// min-width: 4em; +// left: 100%; +// top: -3px; +// } + +.ProseMirror-menu-submenu-label:after { content: ""; border-top: 4px solid transparent; border-bottom: 4px solid transparent; @@ -103,153 +71,144 @@ 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: 10px 10px; - 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-menu-active { +// background: #eee; +// border-radius: 4px; +// } + +// .ProseMirror-menu-disabled { +// opacity: .3; +// } + + +// .ProseMirror-menubar { +// border-top-left-radius: inherit; +// border-top-right-radius: inherit; +// position: relative; +// min-height: 1em; +// color: white; +// padding: 10px 10px; +// 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; + // 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:white; - height: 1em; - } - - .ProseMirror-icon span { - vertical-align: text-top; - } - - .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 white(255, 255, 255, 0.2); - } - - .ProseMirror-prompt h5 { - margin: 0; - font-weight: normal; - font-size: 100%; - color: #444; - } - - .ProseMirror-prompt input[type="text"], - .ProseMirror-prompt textarea { - background: white; - 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; + + &.ProseMirror-menu-disabled { + cursor: default; + } + + svg { + fill:white; + height: 1em; + } + + span { + vertical-align: text-top; + } + } + +// .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 white(255, 255, 255, 0.2); +// } + +// .ProseMirror-prompt h5 { +// margin: 0; +// font-weight: normal; +// font-size: 100%; +// color: #444; +// } + +// .ProseMirror-prompt input[type="text"], +// .ProseMirror-prompt textarea { +// background: white; +// 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; +// } + +.wrapper { position: absolute; - min-width: 10em; - } - - .ProseMirror-prompt-buttons { - margin-top: 5px; - display: none; - } + pointer-events: all; +} .tooltipMenu { position: absolute; z-index: 20000; - background: #121721; - border: 1px solid silver; - border-radius: 15px; - //height: 60px; - //padding: 2px 10px; - //margin-top: 100px; - //-webkit-transform: translateX(-50%); - //transform: translateX(-50%); + background: #323232; + border-radius: 6px; transform: translateY(-85px); pointer-events: all; - height: fit-content; - width:550px; + height: 35px; + width: 650px; + padding: 3px; + box-shadow: 3px 3px 3px rgba(0, 0, 0, 0.25); + display: flex; + align-items: center; + .ProseMirror-example-setup-style hr { padding: 2px 10px; border: none; @@ -265,54 +224,96 @@ } } -.tooltipExtras { - position: absolute; - z-index: 20000; - background: #121721; - border: 1px solid silver; - border-radius: 15px; - //height: 60px; - //padding: 2px 10px; - //margin-top: 100px; - //-webkit-transform: translateX(-50%); - //transform: translateX(-50%); - transform: translateY(-115px); - pointer-events: all; +// .tooltipExtras { +// position: absolute; +// z-index: 20000; +// background: #121721; +// border: 1px solid silver; +// border-radius: 15px; +// //height: 60px; +// //padding: 2px 10px; +// //margin-top: 100px; +// //-webkit-transform: translateX(-50%); +// //transform: translateX(-50%); +// transform: translateY(-115px); +// pointer-events: all; +// height: 25px; +// width:fit-content; +// .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; +// } +// } + +#link-drag { + background-color: #323232; +} + +.menuicon { + width: 25px; height: 25px; - width:fit-content; - .ProseMirror-example-setup-style hr { - padding: 2px 10px; - border: none; - margin: 1em 0; + cursor: pointer; + text-align: center; + line-height: 25px; + margin: 0 2px; + border-radius: 3px; + + &:hover { + background-color: black; + + #link-drag { + background-color: black; + } } - - .ProseMirror-example-setup-style hr:after { - content: ""; - display: block; - height: 1px; - background-color: silver; - line-height: 2px; + + &> * { + margin-top: 50%; + margin-left: 50%; + transform: translate(-50%, -50%); } -} -.wrapper { - position: absolute; - pointer-events: all; + svg { + fill: white; + width: 18px; + height: 18px; + } } - .menuicon { - display: inline-block; - border-right: 1px solid white(0, 0, 0, 0.2); - //color: rgb(19, 18, 18); - color: rgb(226, 21, 21); - line-height: 1; - padding: 0px 2px; - margin: 1px; +.menuicon-active { + width: 25px; + height: 25px; cursor: pointer; text-align: center; - min-width: 10px; - - } + line-height: 25px; + margin: 0 2px; + border-radius: 3px; + + &:hover { + background-color: black; + } + + &> * { + margin-top: 50%; + margin-left: 50%; + transform: translate(-50%, -50%); + } + + svg { + fill: greenyellow; + width: 18px; + height: 18px; + } +} + .strong, .heading { font-weight: bold; } .em { font-style: italic; } .underline {text-decoration: underline} @@ -328,8 +329,9 @@ height: 20px; text-align: center; } + - .brush{ +.brush{ display: inline-block; width: 1em; height: 1em; @@ -337,19 +339,487 @@ stroke: currentColor; fill: currentColor; margin-right: 15px; - } +} - .brush-active{ +.brush-active{ display: inline-block; width: 1em; height: 1em; stroke-width: 3; - stroke: greenyellow; + // stroke: greenyellow; fill: greenyellow; margin-right: 15px; - } +} + +.dragger { + color: #eee; + margin-left: 5px; + width: 16px; + height: 22px; + display: inline-block; + cursor: grab; + + .dragger-wrapper { + width: 100%; + height: 100%; + display: flex; + justify-content: space-evenly; + } + + .dragger-line { + width: 2px; + height: 100%; + background-color: black; + } +} + +// .menuicon:hover + .ProseMirror-menu-dropdown-wrap .buttonSettings-dropdown, +// .menuicon-active:hover + .ProseMirror-menu-dropdown-wrap .buttonSettings-dropdown { +// background-color: black; +// } + +.button-dropdown-wrapper { + display: flex; + align-content: center; + + &:hover { + background-color: black; + } +} + +.buttonSettings-dropdown { + + &.ProseMirror-menu-dropdown { + width: 10px; + height: 25px; + margin: 0; + padding: 0 2px; + background-color: #323232; + text-align: center; + + &:after { + border-top: 4px solid white; + right: 2px; + } + + &:hover { + background-color: black; + } + } + + &.ProseMirror-menu-dropdown-menu { + min-width: 150px; + left: -27px; + top: 31px; + background-color: #323232; + color: $light-color-secondary; + border: none; + // border: 1px solid $light-color-secondary; + border-radius: 0 6px 6px 6px; + padding: 3px; + box-shadow: 3px 3px 3px rgba(0, 0, 0, 0.25); + + .ProseMirror-menu-dropdown-item{ + cursor: default; + + &:last-child { + border-bottom: none; + } + + &:hover { + background-color: #323232; + } + + .button-setting, .button-setting-disabled { + padding: 2px; + border-radius: 2px; + } + + .button-setting:hover { + cursor: pointer; + background-color: black; + } + + .separated-button { + border-top: 1px solid $light-color-secondary; + padding-top: 6px; + } + + input { + color: black; + border: none; + border-radius: 1px; + padding: 3px; + } + + button { + padding: 6px; + background-color: #323232; + border: 1px solid black; + border-radius: 1px; + + &:hover { + background-color: black; + } + } + } + + + } +} + +// @import "../views/globalCssVariables"; + +// .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; +// z-index: 50000; +// position: relative; +// } + +// .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; +// margin: 3px; +// background: white; +// border-radius: 3px; +// text-align: center; +// } + +// .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 { +// background: $dark-color; +// color:white; +// border: 1px solid rgb(223, 223, 223); +// padding: 2px; +// } + +// .ProseMirror-menu-dropdown-menu { +// z-index: 50000; +// min-width: 6em; +// background: white; +// position: absolute; +// } + +// .linking { +// text-align: center; +// } + +// .ProseMirror-menu-dropdown-item { +// cursor: pointer; +// padding: 2px 8px 2px 4px; +// width: auto; +// z-index: 100000; +// } + +// .ProseMirror-menu-dropdown-item:hover { +// background: white; +// } + +// .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: 10px 10px; +// 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:white; +// height: 1em; +// } + +// .ProseMirror-icon span { +// vertical-align: text-top; +// } + +// .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 white(255, 255, 255, 0.2); +// } + +// .ProseMirror-prompt h5 { +// margin: 0; +// font-weight: normal; +// font-size: 100%; +// color: #444; +// } + +// .ProseMirror-prompt input[type="text"], +// .ProseMirror-prompt textarea { +// background: white; +// 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: 20000; +// background: #121721; +// border: 1px solid silver; +// border-radius: 15px; +// //height: 60px; +// //padding: 2px 10px; +// //margin-top: 100px; +// //-webkit-transform: translateX(-50%); +// //transform: translateX(-50%); +// transform: translateY(-85px); +// pointer-events: all; +// height: fit-content; +// width:550px; +// .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; +// } +// } + +// .tooltipExtras { +// position: absolute; +// z-index: 20000; +// background: #121721; +// border: 1px solid silver; +// border-radius: 15px; +// //height: 60px; +// //padding: 2px 10px; +// //margin-top: 100px; +// //-webkit-transform: translateX(-50%); +// //transform: translateX(-50%); +// transform: translateY(-115px); +// pointer-events: all; +// height: 25px; +// width:fit-content; +// .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; +// } +// } + +// .wrapper { +// position: absolute; +// pointer-events: all; +// } + +// .menuicon { +// display: inline-block; +// border-right: 1px solid white(0, 0, 0, 0.2); +// //color: rgb(19, 18, 18); +// color: rgb(226, 21, 21); +// line-height: 1; +// padding: 0px 2px; +// margin: 1px; +// cursor: pointer; +// text-align: center; +// min-width: 10px; + +// } +// .strong, .heading { font-weight: bold; } +// .em { font-style: italic; } +// .underline {text-decoration: underline} +// .superscript {vertical-align:super} +// .subscript { vertical-align:sub } +// .strikethrough {text-decoration-line:line-through} +// .font-size-indicator { +// font-size: 12px; +// padding-right: 0px; +// } +// .summarize{ +// color: white; +// height: 20px; +// text-align: center; +// } + +// .brush{ +// display: inline-block; +// width: 1em; +// height: 1em; +// stroke-width: 0; +// stroke: currentColor; +// fill: currentColor; +// margin-right: 15px; +// } + +// .brush-active{ +// display: inline-block; +// width: 1em; +// height: 1em; +// stroke-width: 3; +// stroke: greenyellow; +// fill: greenyellow; +// margin-right: 15px; +// } - .dragger{ - color: #eee; - margin-left: 5px; - } \ No newline at end of file +// .dragger{ +// color: #eee; +// margin-left: 5px; +// } \ No newline at end of file diff --git a/src/client/util/TooltipTextMenu.tsx b/src/client/util/TooltipTextMenu.tsx index c82d3bc63..6070d2ef4 100644 --- a/src/client/util/TooltipTextMenu.tsx +++ b/src/client/util/TooltipTextMenu.tsx @@ -17,79 +17,99 @@ import { DragManager } from "./DragManager"; import { LinkManager } from "./LinkManager"; import { schema } from "./RichTextSchema"; import "./TooltipTextMenu.scss"; -import { Cast, NumCast } from '../../new_fields/Types'; +import { Cast, NumCast, StrCast } from '../../new_fields/Types'; import { updateBullets } from './ProsemirrorExampleTransfer'; import { DocumentDecorations } from '../views/DocumentDecorations'; +import { SelectionManager } from './SelectionManager'; const { toggleMark, setBlockType } = require("prosemirror-commands"); const { openPrompt, TextField } = require("./ProsemirrorCopy/prompt.js"); //appears above a selection of text in a RichTextBox to give user options such as Bold, Italics, etc. export class TooltipTextMenu { - public tooltip: HTMLElement; + // editor state private view: EditorView; private editorProps: FieldViewProps & FormattedTextBoxProps | undefined; - private fontStyles: MarkType[]; - private fontSizes: MarkType[]; - private listTypes: (NodeType | any)[]; - private fontSizeToNum: Map; - private fontStylesToName: Map; - private listTypeToIcon: Map; + private fontStyles: MarkType[] = []; + private fontSizes: MarkType[] = []; + private listTypes: (NodeType | any)[] = []; + private fontSizeToNum: Map = new Map(); + private fontStylesToName: Map = new Map(); + private listTypeToIcon: Map = new Map(); + private _activeMarks: Mark[] = []; + private _marksToDoms: Map = new Map(); + // private _brushMarks?: Set; //private link: HTMLAnchorElement; - private wrapper: HTMLDivElement; - private extras: HTMLDivElement; + + // editor doms + public tooltip: HTMLElement = document.createElement("div"); + private wrapper: HTMLDivElement = document.createElement("div"); private linkEditor?: HTMLDivElement; private linkText?: HTMLDivElement; private linkDrag?: HTMLImageElement; - //dropdown doms + private _linkDropdownDom?: Node; + private _brushdom?: Node; + private fontSizeDom?: Node; private fontStyleDom?: Node; private listTypeBtnDom?: Node; - private _activeMarks: Mark[] = []; - - private _collapseBtn?: MenuItem; + private _collapsed: boolean = false; + // private _collapseBtn?: MenuItem; + // private _brushIsEmpty: boolean = true; - private _brushMarks?: Set; - private _brushIsEmpty: boolean = true; - private _brushdom?: Node; - private _marksToDoms: Map = new Map(); + public static Toolbar: HTMLDivElement | undefined; - private _collapsed: boolean = false; constructor(view: EditorView) { this.view = view; - this.wrapper = document.createElement("div"); - this.tooltip = document.createElement("div"); - this.extras = document.createElement("div"); + // replace old active menu with this + if (TooltipTextMenuManager.Instance.activeMenu) { + TooltipTextMenuManager.Instance.activeMenu.wrapper.remove(); + } + TooltipTextMenuManager.Instance.activeMenu = this; - this.wrapper.appendChild(this.extras); - this.wrapper.appendChild(this.tooltip); + // initialize the tooltip + this.createTooltip(view); - this.tooltip.className = "tooltipMenu"; - this.extras.className = "tooltipExtras"; + // initialize the wrapper + this.wrapper = document.createElement("div"); this.wrapper.className = "wrapper"; + this.wrapper.appendChild(this.tooltip); - const dragger = document.createElement("span"); - dragger.className = "dragger"; - dragger.textContent = ">>>"; - this.extras.appendChild(dragger); - - this.dragElement(dragger); + // positioning? + TooltipTextMenu.Toolbar = this.wrapper; - // this.createCollapse(); - // if (this._collapseBtn) { - // this.tooltip.appendChild(this._collapseBtn.render(this.view).dom); + // // position wrapper + // if (TooltipTextMenuManager.Instance.isPinned) { + // this.wrapper.style.position = "absolute"; + // this.wrapper.style.left = TooltipTextMenuManager.Instance.pinnedX + "px"; + // this.wrapper.style.top = TooltipTextMenuManager.Instance.pinnedY + "px"; + + // // if pinned, append to mainview + // const mainView = document.querySelector("#main-div"); + // mainView && mainView.appendChild(this.wrapper); + // } else { + // this.wrapper.style.position = "absolute"; + // this.wrapper.style.top = (this.wrapper.offsetTop + TooltipTextMenuManager.Instance.unpinnedY) + "px"; + // this.wrapper.style.left = (this.wrapper.offsetLeft + TooltipTextMenuManager.Instance.unpinnedX) + "px"; + + // // add tooltip to outerdiv to circumvent scaling problem + // const outer_div = this.editorProps.outer_div; + // outer_div && outer_div(this.wrapper); // } - //add the div which is the tooltip - //view.dom.parentNode!.parentNode!.appendChild(this.tooltip); - //add additional icons - library.add(faListUl); - //add the buttons to the tooltip + } + + private async createTooltip(view: EditorView) { + // initialize element + this.tooltip = document.createElement("div"); + this.tooltip.className = "tooltipMenu"; + + // init buttons to the tooltip let items = [ { command: toggleMark(schema.marks.strong), dom: this.icon("B", "strong", "Bold") }, { command: toggleMark(schema.marks.em), dom: this.icon("i", "em", "Italic") }, @@ -100,8 +120,8 @@ export class TooltipTextMenu { { command: toggleMark(schema.marks.highlight), dom: this.icon("H", 'blue', 'Blue') } ]; + // add menu items this._marksToDoms = new Map(); - //add menu items items.forEach(({ dom, command }) => { this.tooltip.appendChild(dom); switch (dom.title) { @@ -131,10 +151,46 @@ export class TooltipTextMenu { }); }); + + // link menu this.updateLinkMenu(); + let dropdown = await this.createLinkDropdown(); + let newLinkDropdowndom = dropdown.render(this.view).dom; + this._linkDropdownDom && this.tooltip.replaceChild(newLinkDropdowndom, this._linkDropdownDom); + this._linkDropdownDom = newLinkDropdowndom; + + // list of font styles + this.initFontStyles(); + + // font sizes + this.initFontSizes(); + + // list types + this.initListTypes(); + + // init brush tool + this._brushdom = this.createBrush().render(this.view).dom; + this.tooltip.appendChild(this._brushdom); + // this._brushDropdownDom = this.createBrushDropdown().render(this.view).dom; + // this.tooltip.appendChild(this._brushDropdownDom); + + // star and pin + this.tooltip.appendChild(this.createLink().render(this.view).dom); + this.tooltip.appendChild(this.createStar().render(this.view).dom); + // + this.updateListItemDropdown(":", this.listTypeBtnDom); + + // + this.updateFromDash(view, undefined, undefined); + // TooltipTextMenu.Toolbar = this.wrapper; - //list of font styles + // dragger + // TODO: onclick handler in drag handles collapsing + this.createDragger(); + } + + initFontStyles() { this.fontStylesToName = new Map(); this.fontStylesToName.set(schema.marks.timesNewRoman, "Times New Roman"); this.fontStylesToName.set(schema.marks.arial, "Arial"); @@ -144,8 +200,9 @@ export class TooltipTextMenu { this.fontStylesToName.set(schema.marks.impact, "Impact"); this.fontStylesToName.set(schema.marks.crimson, "Crimson Text"); this.fontStyles = Array.from(this.fontStylesToName.keys()); + } - //font sizes + initFontSizes() { this.fontSizeToNum = new Map(); this.fontSizeToNum.set(schema.marks.p10, 10); this.fontSizeToNum.set(schema.marks.p12, 12); @@ -158,39 +215,41 @@ export class TooltipTextMenu { this.fontSizeToNum.set(schema.marks.p48, 48); this.fontSizeToNum.set(schema.marks.p72, 72); this.fontSizeToNum.set(schema.marks.pFontSize, 10); - // this.fontSizeToNum.set(schema.marks.pFontSize, 12); - // this.fontSizeToNum.set(schema.marks.pFontSize, 14); - // this.fontSizeToNum.set(schema.marks.pFontSize, 16); - // this.fontSizeToNum.set(schema.marks.pFontSize, 18); - // this.fontSizeToNum.set(schema.marks.pFontSize, 20); - // this.fontSizeToNum.set(schema.marks.pFontSize, 24); - // this.fontSizeToNum.set(schema.marks.pFontSize, 32); - // this.fontSizeToNum.set(schema.marks.pFontSize, 48); - // this.fontSizeToNum.set(schema.marks.pFontSize, 72); this.fontSizes = Array.from(this.fontSizeToNum.keys()); + } - //list types + initListTypes() { this.listTypeToIcon = new Map(); this.listTypeToIcon.set(schema.nodes.bullet_list, ":"); this.listTypeToIcon.set(schema.nodes.ordered_list.create({ mapStyle: "decimal" }), "1.1"); this.listTypeToIcon.set(schema.nodes.ordered_list.create({ mapStyle: "multi" }), "1.A"); // this.listTypeToIcon.set(schema.nodes.bullet_list, "⬜"); this.listTypes = Array.from(this.listTypeToIcon.keys()); + } - //custom tools - // this.tooltip.appendChild(this.createLink().render(this.view).dom); + createDragger() { + const dragger = document.createElement("div"); + dragger.className = "dragger"; - this._brushdom = this.createBrush().render(this.view).dom; - this.tooltip.appendChild(this._brushdom); - this.tooltip.appendChild(this.createLink().render(this.view).dom); - this.tooltip.appendChild(this.createStar().render(this.view).dom); + let draggerWrapper = document.createElement("div"); + draggerWrapper.className = "dragger-wrapper"; - this.updateListItemDropdown(":", this.listTypeBtnDom); + let line1 = document.createElement("span"); + line1.className = "dragger-line"; + let line2 = document.createElement("span"); + line2.className = "dragger-line"; + let line3 = document.createElement("span"); + line3.className = "dragger-line"; - this.updateFromDash(view, undefined, undefined); - TooltipTextMenu.Toolbar = this.wrapper; + draggerWrapper.appendChild(line1); + draggerWrapper.appendChild(line2); + draggerWrapper.appendChild(line3); + + dragger.appendChild(draggerWrapper); + + this.tooltip.appendChild(dragger); + this.dragElement(dragger); } - public static Toolbar: HTMLDivElement | undefined; //label of dropdown will change to given label updateFontSizeDropdown(label: string) { @@ -243,24 +302,13 @@ export class TooltipTextMenu { if (!this.linkEditor || !this.linkText) { this.linkEditor = document.createElement("div"); this.linkEditor.className = "ProseMirror-icon menuicon"; - this.linkEditor.style.color = "black"; - this.linkText = document.createElement("div"); - this.linkText.style.cssFloat = "left"; - this.linkText.style.marginRight = "5px"; - this.linkText.style.marginLeft = "5px"; - this.linkText.setAttribute("contenteditable", "true"); - this.linkText.style.whiteSpace = "nowrap"; - this.linkText.style.width = "150px"; - this.linkText.style.overflow = "hidden"; - this.linkText.style.color = "white"; - this.linkText.onpointerdown = (e: PointerEvent) => { e.stopPropagation(); }; - let linkBtn = document.createElement("div"); - linkBtn.textContent = ">>"; - linkBtn.style.width = "10px"; - linkBtn.style.height = "10px"; - linkBtn.style.color = "white"; - linkBtn.style.cssFloat = "left"; - linkBtn.onpointerdown = (e: PointerEvent) => { + this.linkDrag = document.createElement("img"); + this.linkDrag.src = "https://seogurusnyc.com/wp-content/uploads/2016/12/link-1.png"; + this.linkDrag.style.width = "13px"; + this.linkDrag.style.height = "13px"; + this.linkDrag.title = "Drag to create link"; + this.linkDrag.id = "link-drag"; + this.linkDrag.onpointerdown = (e: PointerEvent) => { let node = this.view.state.selection.$from.nodeAfter; let link = node && node.marks.find(m => m.type.name === "link"); if (link) { @@ -281,62 +329,137 @@ export class TooltipTextMenu { e.preventDefault(); } }; - this.linkDrag = document.createElement("img"); - this.linkDrag.src = "https://seogurusnyc.com/wp-content/uploads/2016/12/link-1.png"; - this.linkDrag.style.width = "15px"; - this.linkDrag.style.height = "15px"; - this.linkDrag.title = "Drag to create link"; - this.linkDrag.style.color = "black"; - this.linkDrag.style.background = "black"; - this.linkDrag.style.cssFloat = "left"; - this.linkDrag.onpointerdown = (e: PointerEvent) => { - if (!this.editorProps) return; - let dragData = new DragManager.LinkDragData(this.editorProps.Document); - dragData.dontClearTextBox = true; - // hack to get source context -sy - let docView = DocumentManager.Instance.getDocumentView(this.editorProps.Document); - e.stopPropagation(); - let ctrlKey = e.ctrlKey; - DragManager.StartLinkDrag(this.linkDrag!, dragData, e.clientX, e.clientY, - { - handlers: { - dragComplete: action(() => { - if (dragData.linkDocument) { - let linkDoc = dragData.linkDocument; - let proto = Doc.GetProto(linkDoc); - if (proto && docView) { - proto.sourceContext = docView.props.ContainingCollectionDoc; - } - let text = this.makeLink(linkDoc, ctrlKey ? "onRight" : "inTab"); - if (linkDoc instanceof Doc && linkDoc.anchor2 instanceof Doc) { - proto.title = text === "" ? proto.title : text + " to " + linkDoc.anchor2.title; // TODODO open to more descriptive descriptions of following in text link - } - } - }), - }, - hideSource: false - }); - e.stopPropagation(); - e.preventDefault(); - }; this.linkEditor.appendChild(this.linkDrag); this.tooltip.appendChild(this.linkEditor); } + // let node = this.view.state.selection.$from.nodeAfter; + // let link = node && node.marks.find(m => m.type.name === "link"); + // this.linkText.textContent = link ? link.attrs.href : "-empty-"; + + // this.linkText.onkeydown = (e: KeyboardEvent) => { + // if (e.key === "Enter") { + // // this.makeLink(this.linkText!.textContent!); + // e.stopPropagation(); + // e.preventDefault(); + // } + // }; + // // this.tooltip.appendChild(this.linkEditor); + } + + async getTextLinkTargetTitle() { let node = this.view.state.selection.$from.nodeAfter; let link = node && node.marks.find(m => m.type.name === "link"); - this.linkText.textContent = link ? link.attrs.href : "-empty-"; + if (link) { + let href = link.attrs.href; + if (href) { + if (href.indexOf(Utils.prepend("/doc/")) === 0) { + const linkclicked = href.replace(Utils.prepend("/doc/"), "").split("?")[0]; + if (linkclicked) { + let linkDoc = await DocServer.GetRefField(linkclicked); + if (linkDoc instanceof Doc) { + let anchor1 = await Cast(linkDoc.anchor1, Doc); + let anchor2 = await Cast(linkDoc.anchor2, Doc); + let currentDoc = SelectionManager.SelectedDocuments().length && SelectionManager.SelectedDocuments()[0].props.Document; + if (currentDoc && anchor1 && anchor2) { + if (Doc.AreProtosEqual(currentDoc, anchor1)) return StrCast(anchor2.title); + if (Doc.AreProtosEqual(currentDoc, anchor2)) return StrCast(anchor2.title); + } + } + } + } else { + return href; + } + } + } + } - this.linkText.onkeydown = (e: KeyboardEvent) => { - if (e.key === "Enter") { - // this.makeLink(this.linkText!.textContent!); - e.stopPropagation(); - e.preventDefault(); + async createLinkDropdown() { + let targetTitle = await this.getTextLinkTargetTitle(); + let input = document.createElement("input"); + + // menu item for input for hyperlink url + // TODO: integrate search to allow users to search for a doc to link to + let linkInfo = new MenuItem({ + title: "", + execEvent: "", + class: "button-setting-disabled", + css: "", + render() { + let p = document.createElement("p"); + p.textContent = "Linked to:"; + + input.type = "text"; + input.placeholder = "Enter URL"; + if (targetTitle) input.value = targetTitle; + input.onclick = (e: MouseEvent) => { + input.select(); + input.focus(); + }; + + let div = document.createElement("div"); + div.appendChild(p); + div.appendChild(input); + return div; + }, + enable() { return false; }, + run(p1, p2, p3, event) { + event.stopPropagation(); } - }; - // this.tooltip.appendChild(this.linkEditor); + }); + + // menu item to update/apply the hyperlink to the selected text + let linkApply = new MenuItem({ + title: "", + execEvent: "", + class: "", + css: "", + render() { + let button = document.createElement("button"); + button.className = "link-url-button"; + button.textContent = "Apply hyperlink"; + return button; + }, + enable() { return false; }, + run: (state, dispatch, view, event) => { + event.stopPropagation(); + this.makeLink(input.value, "onRight"); + } + }); + + // menu item to remove the link + // TODO: allow this to be undoable + let self = this; + let deleteLink = new MenuItem({ + title: "Delete link", + execEvent: "", + class: "separated-button", + css: "", + render() { + let button = document.createElement("button"); + button.textContent = "Remove link"; + + let wrapper = document.createElement("div"); + wrapper.appendChild(button); + return wrapper; + }, + enable() { return true; }, + async run() { + self.deleteLink(); + // update link dropdown + let dropdown = await self.createLinkDropdown(); + let newLinkDropdowndom = dropdown.render(self.view).dom; + self._linkDropdownDom && self.tooltip.replaceChild(newLinkDropdowndom, self._linkDropdownDom); + self._linkDropdownDom = newLinkDropdowndom; + } + }); + + + let linkDropdown = new Dropdown(targetTitle ? [linkInfo, linkApply, deleteLink] : [linkInfo, linkApply], { class: "buttonSettings-dropdown" }) as MenuItem; + return linkDropdown; } + dragElement(elmnt: HTMLElement) { var pos1 = 0, pos2 = 0, pos3 = 0, pos4 = 0; if (elmnt) { @@ -627,15 +750,15 @@ export class TooltipTextMenu { // selectionchanged event handler brush_function(state: EditorState, dispatch: any) { - if (this._brushIsEmpty) { + if (TooltipTextMenuManager.Instance._brushIsEmpty) { const selected_marks = this.getMarksInSelection(this.view.state); if (this._brushdom) { if (selected_marks.size >= 0) { - this._brushMarks = selected_marks; + TooltipTextMenuManager.Instance._brushMarks = selected_marks; const newbrush = this.createBrush(true).render(this.view).dom; this.tooltip.replaceChild(newbrush, this._brushdom); this._brushdom = newbrush; - this._brushIsEmpty = !this._brushIsEmpty; + TooltipTextMenuManager.Instance._brushIsEmpty = !TooltipTextMenuManager.Instance._brushIsEmpty; } } } @@ -643,9 +766,9 @@ export class TooltipTextMenu { let { from, to, $from } = this.view.state.selection; if (this._brushdom) { if (!this.view.state.selection.empty && $from && $from.nodeAfter) { - if (this._brushMarks && to - from > 0) { + if (TooltipTextMenuManager.Instance._brushMarks && to - from > 0) { this.view.dispatch(this.view.state.tr.removeMark(from, to)); - Array.from(this._brushMarks).filter(m => m.type !== schema.marks.user_mark).forEach((mark: Mark) => { + Array.from(TooltipTextMenuManager.Instance._brushMarks).filter(m => m.type !== schema.marks.user_mark).forEach((mark: Mark) => { const markType = mark.type; this.changeToMarkInGroup(markType, this.view, []); }); @@ -655,7 +778,7 @@ export class TooltipTextMenu { const newbrush = this.createBrush(false).render(this.view).dom; this.tooltip.replaceChild(newbrush, this._brushdom); this._brushdom = newbrush; - this._brushIsEmpty = !this._brushIsEmpty; + TooltipTextMenuManager.Instance._brushIsEmpty = !TooltipTextMenuManager.Instance._brushIsEmpty; } } } @@ -663,46 +786,46 @@ export class TooltipTextMenu { } - createCollapse() { - this._collapseBtn = new MenuItem({ - title: "Collapse", - //label: "Collapse", - icon: icons.join, - execEvent: "", - css: "color:white;", - class: "summarize", - run: () => { - this.collapseToolTip(); - } - }); - } + // createCollapse() { + // this._collapseBtn = new MenuItem({ + // title: "Collapse", + // //label: "Collapse", + // icon: icons.join, + // execEvent: "", + // css: "color:white;", + // class: "summarize", + // run: () => { + // this.collapseToolTip(); + // } + // }); + // } - collapseToolTip() { - if (this._collapseBtn) { - if (this._collapseBtn.spec.title === "Collapse") { - // const newcollapseBtn = new MenuItem({ - // title: "Expand", - // icon: icons.join, - // execEvent: "", - // css: "color:white;", - // class: "summarize", - // run: (state, dispatch, view) => { - // this.collapseToolTip(); - // } - // }); - // this.tooltip.replaceChild(newcollapseBtn.render(this.view).dom, this._collapseBtn.render(this.view).dom); - // this._collapseBtn = newcollapseBtn; - this.tooltip.style.width = "30px"; - this._collapseBtn.spec.title = "Expand"; - this._collapseBtn.render(this.view); - } - else { - this._collapseBtn.spec.title = "Collapse"; - this.tooltip.style.width = "550px"; - this._collapseBtn.render(this.view); - } - } - } + // collapseToolTip() { + // if (this._collapseBtn) { + // if (this._collapseBtn.spec.title === "Collapse") { + // // const newcollapseBtn = new MenuItem({ + // // title: "Expand", + // // icon: icons.join, + // // execEvent: "", + // // css: "color:white;", + // // class: "summarize", + // // run: (state, dispatch, view) => { + // // this.collapseToolTip(); + // // } + // // }); + // // this.tooltip.replaceChild(newcollapseBtn.render(this.view).dom, this._collapseBtn.render(this.view).dom); + // // this._collapseBtn = newcollapseBtn; + // this.tooltip.style.width = "30px"; + // this._collapseBtn.spec.title = "Expand"; + // this._collapseBtn.render(this.view); + // } + // else { + // this._collapseBtn.spec.title = "Collapse"; + // this.tooltip.style.width = "550px"; + // this._collapseBtn.render(this.view); + // } + // } + // } createLink() { let markType = schema.marks.link; @@ -913,32 +1036,41 @@ export class TooltipTextMenu { update_mark_doms() { this.reset_mark_doms(); let foundlink = false; - let children = this.extras.childNodes; + // let children = this.extras.childNodes; this._activeMarks.forEach((mark) => { if (this._marksToDoms.has(mark)) { let dom = this._marksToDoms.get(mark); if (dom) dom.style.color = "greenyellow"; } - if (children.length > 1) { - foundlink = true; - } - if (mark.type.name === "link" && children.length === 1) { - // let del = document.createElement("button"); - // del.textContent = "X"; - // del.style.color = "red"; - // del.style.height = "10px"; - // del.style.width = "10px"; - // del.style.marginLeft = "5px"; - // del.onclick = this.deleteLink; - // this.extras.appendChild(del); - let del = this.deleteLinkItem().render(this.view).dom; - this.extras.appendChild(del); - foundlink = true; - } + // if (children.length > 1) { + // foundlink = true; + // } + // if (mark.type.name === "link" && children.length === 1) { + // // let del = document.createElement("button"); + // // del.textContent = "X"; + // // del.style.color = "red"; + // // del.style.height = "10px"; + // // del.style.width = "10px"; + // // del.style.marginLeft = "5px"; + // // del.onclick = this.deleteLink; + // // this.extras.appendChild(del); + // let del = this.deleteLinkItem().render(this.view).dom; + // this.extras.appendChild(del); + // foundlink = true; + // } }); - if (!foundlink) { - if (children.length > 1) { - this.extras.removeChild(children[1]); + // if (!foundlink) { + // if (children.length > 1) { + // this.extras.removeChild(children[1]); + // } + // } + + // keeps brush tool highlighted if active when switching between textboxes + if (!TooltipTextMenuManager.Instance._brushIsEmpty) { + if (this._brushdom) { + const newbrush = this.createBrush(true).render(this.view).dom; + this.tooltip.replaceChild(newbrush, this._brushdom); + this._brushdom = newbrush; } } @@ -1022,3 +1154,53 @@ export class TooltipTextMenu { this.wrapper.remove(); } } + + +class TooltipTextMenuManager { + private static _instance: TooltipTextMenuManager; + + public pinnedX: number = 0; + public pinnedY: number = 0; + public unpinnedX: number = 0; + public unpinnedY: number = 0; + private _isPinned: boolean = false; + + public _brushMarks: Set | undefined; + public _brushIsEmpty: boolean = true; + + public activeMenu: TooltipTextMenu | undefined; + + static get Instance() { + if (!TooltipTextMenuManager._instance) { + TooltipTextMenuManager._instance = new TooltipTextMenuManager(); + } + return TooltipTextMenuManager._instance; + } + + // private pinnedToUnpinned() { + // let position = MainOverlayTextBox.Instance.position; + + // this.unpinnedX = this.pinnedX - position[0]; + // this.unpinnedY = this.pinnedY - position[1]; + // } + + // private unpinnedToPinned() { + // let position = MainOverlayTextBox.Instance.position; + + // this.pinnedX = position[0] + this.unpinnedX; + // this.pinnedY = position[1] + this.unpinnedY; + // } + + public get isPinned() { + return this._isPinned; + } + + public toggleIsPinned() { + // if (this._isPinned) { + // this.pinnedToUnpinned(); + // } else { + // this.unpinnedToPinned(); + // } + this._isPinned = !this._isPinned; + } +} diff --git a/src/scraping/buxton/source/.Bill_Notes_NewO.docx.icloud b/src/scraping/buxton/source/.Bill_Notes_NewO.docx.icloud deleted file mode 100644 index f71886d8c..000000000 Binary files a/src/scraping/buxton/source/.Bill_Notes_NewO.docx.icloud and /dev/null differ diff --git a/src/scraping/buxton/source/.Bill_Notes_OLPC.docx.icloud b/src/scraping/buxton/source/.Bill_Notes_OLPC.docx.icloud deleted file mode 100644 index 30ddb3091..000000000 Binary files a/src/scraping/buxton/source/.Bill_Notes_OLPC.docx.icloud and /dev/null differ -- cgit v1.2.3-70-g09d2 From dbf41b6eaaebe16567289ba4814316b2c79e0b65 Mon Sep 17 00:00:00 2001 From: Fawn Date: Thu, 10 Oct 2019 10:50:53 -0400 Subject: richtexteditor menuicon styling --- src/client/util/TooltipTextMenu.scss | 132 ------------- src/client/util/TooltipTextMenu.tsx | 272 ++++++++++++++------------- src/client/views/nodes/FormattedTextBox.scss | 22 +-- 3 files changed, 153 insertions(+), 273 deletions(-) (limited to 'src') diff --git a/src/client/util/TooltipTextMenu.scss b/src/client/util/TooltipTextMenu.scss index 6bafdbbfd..11c937471 100644 --- a/src/client/util/TooltipTextMenu.scss +++ b/src/client/util/TooltipTextMenu.scss @@ -55,12 +55,6 @@ } } -// .ProseMirror-menu-submenu { -// display: none; -// min-width: 4em; -// left: 100%; -// top: -3px; -// } .ProseMirror-menu-submenu-label:after { content: ""; @@ -72,34 +66,6 @@ right: 4px; top: calc(50% - 4px); } - - - -// .ProseMirror-menu-active { -// background: #eee; -// border-radius: 4px; -// } - -// .ProseMirror-menu-disabled { -// opacity: .3; -// } - - -// .ProseMirror-menubar { -// border-top-left-radius: inherit; -// border-top-right-radius: inherit; -// position: relative; -// min-height: 1em; -// color: white; -// padding: 10px 10px; -// 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; @@ -121,74 +87,6 @@ vertical-align: text-top; } } - -// .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 white(255, 255, 255, 0.2); -// } - -// .ProseMirror-prompt h5 { -// margin: 0; -// font-weight: normal; -// font-size: 100%; -// color: #444; -// } - -// .ProseMirror-prompt input[type="text"], -// .ProseMirror-prompt textarea { -// background: white; -// 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; -// } .wrapper { position: absolute; @@ -224,36 +122,6 @@ } } -// .tooltipExtras { -// position: absolute; -// z-index: 20000; -// background: #121721; -// border: 1px solid silver; -// border-radius: 15px; -// //height: 60px; -// //padding: 2px 10px; -// //margin-top: 100px; -// //-webkit-transform: translateX(-50%); -// //transform: translateX(-50%); -// transform: translateY(-115px); -// pointer-events: all; -// height: 25px; -// width:fit-content; -// .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; -// } -// } - #link-drag { background-color: #323232; } diff --git a/src/client/util/TooltipTextMenu.tsx b/src/client/util/TooltipTextMenu.tsx index 6070d2ef4..d0bb753ba 100644 --- a/src/client/util/TooltipTextMenu.tsx +++ b/src/client/util/TooltipTextMenu.tsx @@ -154,10 +154,10 @@ export class TooltipTextMenu { // link menu this.updateLinkMenu(); - let dropdown = await this.createLinkDropdown(); - let newLinkDropdowndom = dropdown.render(this.view).dom; - this._linkDropdownDom && this.tooltip.replaceChild(newLinkDropdowndom, this._linkDropdownDom); - this._linkDropdownDom = newLinkDropdowndom; + // let dropdown = await this.createLinkDropdown(); + // let newLinkDropdowndom = dropdown.render(this.view).dom; + // this._linkDropdownDom && this.tooltip.replaceChild(newLinkDropdowndom, this._linkDropdownDom); + // this._linkDropdownDom = newLinkDropdowndom; // list of font styles this.initFontStyles(); @@ -302,13 +302,24 @@ export class TooltipTextMenu { if (!this.linkEditor || !this.linkText) { this.linkEditor = document.createElement("div"); this.linkEditor.className = "ProseMirror-icon menuicon"; - this.linkDrag = document.createElement("img"); - this.linkDrag.src = "https://seogurusnyc.com/wp-content/uploads/2016/12/link-1.png"; - this.linkDrag.style.width = "13px"; - this.linkDrag.style.height = "13px"; - this.linkDrag.title = "Drag to create link"; - this.linkDrag.id = "link-drag"; - this.linkDrag.onpointerdown = (e: PointerEvent) => { + this.linkEditor.style.color = "black"; + this.linkText = document.createElement("div"); + this.linkText.style.cssFloat = "left"; + this.linkText.style.marginRight = "5px"; + this.linkText.style.marginLeft = "5px"; + this.linkText.setAttribute("contenteditable", "true"); + this.linkText.style.whiteSpace = "nowrap"; + this.linkText.style.width = "150px"; + this.linkText.style.overflow = "hidden"; + this.linkText.style.color = "white"; + this.linkText.onpointerdown = (e: PointerEvent) => { e.stopPropagation(); }; + let linkBtn = document.createElement("div"); + linkBtn.textContent = ">>"; + linkBtn.style.width = "10px"; + linkBtn.style.height = "10px"; + linkBtn.style.color = "white"; + linkBtn.style.cssFloat = "left"; + linkBtn.onpointerdown = (e: PointerEvent) => { let node = this.view.state.selection.$from.nodeAfter; let link = node && node.marks.find(m => m.type.name === "link"); if (link) { @@ -329,136 +340,137 @@ export class TooltipTextMenu { e.preventDefault(); } }; + this.linkDrag = document.createElement("img"); + this.linkDrag.src = "https://seogurusnyc.com/wp-content/uploads/2016/12/link-1.png"; + this.linkDrag.style.width = "15px"; + this.linkDrag.style.height = "15px"; + this.linkDrag.title = "Drag to create link"; + this.linkDrag.style.color = "black"; + this.linkDrag.style.background = "black"; + this.linkDrag.style.cssFloat = "left"; + this.linkDrag.onpointerdown = (e: PointerEvent) => { + if (!this.editorProps) return; + let dragData = new DragManager.LinkDragData(this.editorProps.Document); + dragData.dontClearTextBox = true; + // hack to get source context -sy + let docView = DocumentManager.Instance.getDocumentView(this.editorProps.Document); + e.stopPropagation(); + let ctrlKey = e.ctrlKey; + DragManager.StartLinkDrag(this.linkDrag!, dragData, e.clientX, e.clientY, + { + handlers: { + dragComplete: action(() => { + if (dragData.linkDocument) { + let linkDoc = dragData.linkDocument; + let proto = Doc.GetProto(linkDoc); + if (proto && docView) { + proto.sourceContext = docView.props.ContainingCollectionDoc; + } + let text = this.makeLink(linkDoc, ctrlKey ? "onRight" : "inTab"); + if (linkDoc instanceof Doc && linkDoc.anchor2 instanceof Doc) { + proto.title = text === "" ? proto.title : text + " to " + linkDoc.anchor2.title; // TODODO open to more descriptive descriptions of following in text link + } + } + }), + }, + hideSource: false + }); + e.stopPropagation(); + e.preventDefault(); + }; this.linkEditor.appendChild(this.linkDrag); this.tooltip.appendChild(this.linkEditor); } - // let node = this.view.state.selection.$from.nodeAfter; - // let link = node && node.marks.find(m => m.type.name === "link"); - // this.linkText.textContent = link ? link.attrs.href : "-empty-"; - - // this.linkText.onkeydown = (e: KeyboardEvent) => { - // if (e.key === "Enter") { - // // this.makeLink(this.linkText!.textContent!); - // e.stopPropagation(); - // e.preventDefault(); - // } - // }; - // // this.tooltip.appendChild(this.linkEditor); - } - - async getTextLinkTargetTitle() { let node = this.view.state.selection.$from.nodeAfter; let link = node && node.marks.find(m => m.type.name === "link"); - if (link) { - let href = link.attrs.href; - if (href) { - if (href.indexOf(Utils.prepend("/doc/")) === 0) { - const linkclicked = href.replace(Utils.prepend("/doc/"), "").split("?")[0]; - if (linkclicked) { - let linkDoc = await DocServer.GetRefField(linkclicked); - if (linkDoc instanceof Doc) { - let anchor1 = await Cast(linkDoc.anchor1, Doc); - let anchor2 = await Cast(linkDoc.anchor2, Doc); - let currentDoc = SelectionManager.SelectedDocuments().length && SelectionManager.SelectedDocuments()[0].props.Document; - if (currentDoc && anchor1 && anchor2) { - if (Doc.AreProtosEqual(currentDoc, anchor1)) return StrCast(anchor2.title); - if (Doc.AreProtosEqual(currentDoc, anchor2)) return StrCast(anchor2.title); - } - } - } - } else { - return href; - } - } - } - } - - async createLinkDropdown() { - let targetTitle = await this.getTextLinkTargetTitle(); - let input = document.createElement("input"); + this.linkText.textContent = link ? link.attrs.href : "-empty-"; - // menu item for input for hyperlink url - // TODO: integrate search to allow users to search for a doc to link to - let linkInfo = new MenuItem({ - title: "", - execEvent: "", - class: "button-setting-disabled", - css: "", - render() { - let p = document.createElement("p"); - p.textContent = "Linked to:"; - - input.type = "text"; - input.placeholder = "Enter URL"; - if (targetTitle) input.value = targetTitle; - input.onclick = (e: MouseEvent) => { - input.select(); - input.focus(); - }; - - let div = document.createElement("div"); - div.appendChild(p); - div.appendChild(input); - return div; - }, - enable() { return false; }, - run(p1, p2, p3, event) { - event.stopPropagation(); - } - }); - - // menu item to update/apply the hyperlink to the selected text - let linkApply = new MenuItem({ - title: "", - execEvent: "", - class: "", - css: "", - render() { - let button = document.createElement("button"); - button.className = "link-url-button"; - button.textContent = "Apply hyperlink"; - return button; - }, - enable() { return false; }, - run: (state, dispatch, view, event) => { - event.stopPropagation(); - this.makeLink(input.value, "onRight"); - } - }); - - // menu item to remove the link - // TODO: allow this to be undoable - let self = this; - let deleteLink = new MenuItem({ - title: "Delete link", - execEvent: "", - class: "separated-button", - css: "", - render() { - let button = document.createElement("button"); - button.textContent = "Remove link"; - - let wrapper = document.createElement("div"); - wrapper.appendChild(button); - return wrapper; - }, - enable() { return true; }, - async run() { - self.deleteLink(); - // update link dropdown - let dropdown = await self.createLinkDropdown(); - let newLinkDropdowndom = dropdown.render(self.view).dom; - self._linkDropdownDom && self.tooltip.replaceChild(newLinkDropdowndom, self._linkDropdownDom); - self._linkDropdownDom = newLinkDropdowndom; + this.linkText.onkeydown = (e: KeyboardEvent) => { + if (e.key === "Enter") { + // this.makeLink(this.linkText!.textContent!); + e.stopPropagation(); + e.preventDefault(); } - }); + }; + // this.tooltip.appendChild(this.linkEditor); + } + // updateLinkMenu() { + // if (!this.linkEditor || !this.linkText) { + // this.linkEditor = document.createElement("div"); + // this.linkEditor.className = "ProseMirror-icon menuicon"; + // this.linkDrag = document.createElement("img"); + // this.linkDrag.src = "https://seogurusnyc.com/wp-content/uploads/2016/12/link-1.png"; + // this.linkDrag.style.width = "13px"; + // this.linkDrag.style.height = "13px"; + // this.linkDrag.title = "Drag to create link"; + // this.linkDrag.id = "link-drag"; + // this.linkDrag.onpointerdown = (e: PointerEvent) => { + // let node = this.view.state.selection.$from.nodeAfter; + // let link = node && node.marks.find(m => m.type.name === "link"); + // if (link) { + // let href: string = link.attrs.href; + // if (href.indexOf(Utils.prepend("/doc/")) === 0) { + // let docid = href.replace(Utils.prepend("/doc/"), ""); + // DocServer.GetRefField(docid).then(action((f: Opt) => { + // if (f instanceof Doc) { + // if (DocumentManager.Instance.getDocumentView(f)) { + // DocumentManager.Instance.getDocumentView(f)!.props.focus(f, false); + // } + // else this.editorProps && this.editorProps.addDocTab(f, undefined, "onRight"); + // } + // })); + // } + // // TODO This should have an else to handle external links + // e.stopPropagation(); + // e.preventDefault(); + // } + // }; + // this.linkEditor.appendChild(this.linkDrag); + // this.tooltip.appendChild(this.linkEditor); + // } - let linkDropdown = new Dropdown(targetTitle ? [linkInfo, linkApply, deleteLink] : [linkInfo, linkApply], { class: "buttonSettings-dropdown" }) as MenuItem; - return linkDropdown; - } + // // let node = this.view.state.selection.$from.nodeAfter; + // // let link = node && node.marks.find(m => m.type.name === "link"); + // // this.linkText.textContent = link ? link.attrs.href : "-empty-"; + + // // this.linkText.onkeydown = (e: KeyboardEvent) => { + // // if (e.key === "Enter") { + // // // this.makeLink(this.linkText!.textContent!); + // // e.stopPropagation(); + // // e.preventDefault(); + // // } + // // }; + // // // this.tooltip.appendChild(this.linkEditor); + // } + // async getTextLinkTargetTitle() { + // let node = this.view.state.selection.$from.nodeAfter; + // let link = node && node.marks.find(m => m.type.name === "link"); + // if (link) { + // let href = link.attrs.href; + // if (href) { + // if (href.indexOf(Utils.prepend("/doc/")) === 0) { + // const linkclicked = href.replace(Utils.prepend("/doc/"), "").split("?")[0]; + // if (linkclicked) { + // let linkDoc = await DocServer.GetRefField(linkclicked); + // if (linkDoc instanceof Doc) { + // let anchor1 = await Cast(linkDoc.anchor1, Doc); + // let anchor2 = await Cast(linkDoc.anchor2, Doc); + // let currentDoc = SelectionManager.SelectedDocuments().length && SelectionManager.SelectedDocuments()[0].props.Document; + // if (currentDoc && anchor1 && anchor2) { + // if (Doc.AreProtosEqual(currentDoc, anchor1)) return StrCast(anchor2.title); + // if (Doc.AreProtosEqual(currentDoc, anchor2)) return StrCast(anchor2.title); + // } + // } + // } + // } else { + // return href; + // } + // } + // } + // } dragElement(elmnt: HTMLElement) { var pos1 = 0, pos2 = 0, pos3 = 0, pos4 = 0; diff --git a/src/client/views/nodes/FormattedTextBox.scss b/src/client/views/nodes/FormattedTextBox.scss index 29e8b14a8..f92ccf9f5 100644 --- a/src/client/views/nodes/FormattedTextBox.scss +++ b/src/client/views/nodes/FormattedTextBox.scss @@ -46,17 +46,17 @@ height: 100%; } -.menuicon { - display: inline-block; - border-right: 1px solid rgba(0, 0, 0, 0.2); - color: #888; - line-height: 1; - padding: 0 7px; - margin: 1px; - cursor: pointer; - text-align: center; - min-width: 1.4em; -} +// .menuicon { +// display: inline-block; +// border-right: 1px solid rgba(0, 0, 0, 0.2); +// color: #888; +// line-height: 1; +// padding: 0 7px; +// margin: 1px; +// cursor: pointer; +// text-align: center; +// min-width: 1.4em; +// } .strong, .heading { -- cgit v1.2.3-70-g09d2 From 3a5e787e3a3c42ec3208f9f75bd8bfbac71aeb81 Mon Sep 17 00:00:00 2001 From: Fawn Date: Thu, 10 Oct 2019 11:44:11 -0400 Subject: added dropdowns to link tool and brush tool --- src/client/util/TooltipTextMenu.tsx | 315 +++++++++++++++++++++++++----------- 1 file changed, 221 insertions(+), 94 deletions(-) (limited to 'src') diff --git a/src/client/util/TooltipTextMenu.tsx b/src/client/util/TooltipTextMenu.tsx index d0bb753ba..ce0237527 100644 --- a/src/client/util/TooltipTextMenu.tsx +++ b/src/client/util/TooltipTextMenu.tsx @@ -50,6 +50,7 @@ export class TooltipTextMenu { private linkDrag?: HTMLImageElement; private _linkDropdownDom?: Node; private _brushdom?: Node; + private _brushDropdownDom?: Node; private fontSizeDom?: Node; private fontStyleDom?: Node; @@ -154,10 +155,9 @@ export class TooltipTextMenu { // link menu this.updateLinkMenu(); - // let dropdown = await this.createLinkDropdown(); - // let newLinkDropdowndom = dropdown.render(this.view).dom; - // this._linkDropdownDom && this.tooltip.replaceChild(newLinkDropdowndom, this._linkDropdownDom); - // this._linkDropdownDom = newLinkDropdowndom; + let dropdown = await this.createLinkDropdown(); + this._linkDropdownDom = dropdown.render(this.view).dom; + this.tooltip.appendChild(this._linkDropdownDom); // list of font styles this.initFontStyles(); @@ -171,18 +171,18 @@ export class TooltipTextMenu { // init brush tool this._brushdom = this.createBrush().render(this.view).dom; this.tooltip.appendChild(this._brushdom); - // this._brushDropdownDom = this.createBrushDropdown().render(this.view).dom; - // this.tooltip.appendChild(this._brushDropdownDom); + this._brushDropdownDom = this.createBrushDropdown().render(this.view).dom; + this.tooltip.appendChild(this._brushDropdownDom); - // star and pin - this.tooltip.appendChild(this.createLink().render(this.view).dom); + // star + // this.tooltip.appendChild(this.createLink().render(this.view).dom); this.tooltip.appendChild(this.createStar().render(this.view).dom); // this.updateListItemDropdown(":", this.listTypeBtnDom); // - this.updateFromDash(view, undefined, undefined); + await this.updateFromDash(view, undefined, undefined); // TooltipTextMenu.Toolbar = this.wrapper; // dragger @@ -302,11 +302,11 @@ export class TooltipTextMenu { if (!this.linkEditor || !this.linkText) { this.linkEditor = document.createElement("div"); this.linkEditor.className = "ProseMirror-icon menuicon"; - this.linkEditor.style.color = "black"; + // this.linkEditor.style.color = "black"; this.linkText = document.createElement("div"); - this.linkText.style.cssFloat = "left"; - this.linkText.style.marginRight = "5px"; - this.linkText.style.marginLeft = "5px"; + // this.linkText.style.cssFloat = "left"; + // this.linkText.style.marginRight = "5px"; + // this.linkText.style.marginLeft = "5px"; this.linkText.setAttribute("contenteditable", "true"); this.linkText.style.whiteSpace = "nowrap"; this.linkText.style.width = "150px"; @@ -345,9 +345,10 @@ export class TooltipTextMenu { this.linkDrag.style.width = "15px"; this.linkDrag.style.height = "15px"; this.linkDrag.title = "Drag to create link"; - this.linkDrag.style.color = "black"; - this.linkDrag.style.background = "black"; - this.linkDrag.style.cssFloat = "left"; + this.linkDrag.id = "link-drag"; + // this.linkDrag.style.color = "black"; + // this.linkDrag.style.background = "black"; + // this.linkDrag.style.cssFloat = "left"; this.linkDrag.onpointerdown = (e: PointerEvent) => { if (!this.editorProps) return; let dragData = new DragManager.LinkDragData(this.editorProps.Document); @@ -366,7 +367,7 @@ export class TooltipTextMenu { if (proto && docView) { proto.sourceContext = docView.props.ContainingCollectionDoc; } - let text = this.makeLink(linkDoc, ctrlKey ? "onRight" : "inTab"); + let text = this.makeLinkToDoc(linkDoc, ctrlKey ? "onRight" : "inTab"); if (linkDoc instanceof Doc && linkDoc.anchor2 instanceof Doc) { proto.title = text === "" ? proto.title : text + " to " + linkDoc.anchor2.title; // TODODO open to more descriptive descriptions of following in text link } @@ -396,81 +397,125 @@ export class TooltipTextMenu { // this.tooltip.appendChild(this.linkEditor); } - // updateLinkMenu() { - // if (!this.linkEditor || !this.linkText) { - // this.linkEditor = document.createElement("div"); - // this.linkEditor.className = "ProseMirror-icon menuicon"; - // this.linkDrag = document.createElement("img"); - // this.linkDrag.src = "https://seogurusnyc.com/wp-content/uploads/2016/12/link-1.png"; - // this.linkDrag.style.width = "13px"; - // this.linkDrag.style.height = "13px"; - // this.linkDrag.title = "Drag to create link"; - // this.linkDrag.id = "link-drag"; - // this.linkDrag.onpointerdown = (e: PointerEvent) => { - // let node = this.view.state.selection.$from.nodeAfter; - // let link = node && node.marks.find(m => m.type.name === "link"); - // if (link) { - // let href: string = link.attrs.href; - // if (href.indexOf(Utils.prepend("/doc/")) === 0) { - // let docid = href.replace(Utils.prepend("/doc/"), ""); - // DocServer.GetRefField(docid).then(action((f: Opt) => { - // if (f instanceof Doc) { - // if (DocumentManager.Instance.getDocumentView(f)) { - // DocumentManager.Instance.getDocumentView(f)!.props.focus(f, false); - // } - // else this.editorProps && this.editorProps.addDocTab(f, undefined, "onRight"); - // } - // })); - // } - // // TODO This should have an else to handle external links - // e.stopPropagation(); - // e.preventDefault(); - // } - // }; - // this.linkEditor.appendChild(this.linkDrag); - // this.tooltip.appendChild(this.linkEditor); - // } + async getTextLinkTargetTitle() { + let node = this.view.state.selection.$from.nodeAfter; + let link = node && node.marks.find(m => m.type.name === "link"); + // let href = link!.attrs.href; + if (link) { + let href = link.attrs.href; + if (href) { + if (href.indexOf(Utils.prepend("/doc/")) === 0) { + const linkclicked = href.replace(Utils.prepend("/doc/"), "").split("?")[0]; + if (linkclicked) { + let linkDoc = await DocServer.GetRefField(linkclicked); + if (linkDoc instanceof Doc) { + let anchor1 = await Cast(linkDoc.anchor1, Doc); + let anchor2 = await Cast(linkDoc.anchor2, Doc); + let currentDoc = SelectionManager.SelectedDocuments().length && SelectionManager.SelectedDocuments()[0].props.Document; + if (currentDoc && anchor1 && anchor2) { + if (Doc.AreProtosEqual(currentDoc, anchor1)) { + return StrCast(anchor2.title); + } + if (Doc.AreProtosEqual(currentDoc, anchor2)) { + return StrCast(anchor1.title); + } + } + } + } + } else { + return href; + } + } else { + return link.attrs.title; + } + } + } - // // let node = this.view.state.selection.$from.nodeAfter; - // // let link = node && node.marks.find(m => m.type.name === "link"); - // // this.linkText.textContent = link ? link.attrs.href : "-empty-"; - - // // this.linkText.onkeydown = (e: KeyboardEvent) => { - // // if (e.key === "Enter") { - // // // this.makeLink(this.linkText!.textContent!); - // // e.stopPropagation(); - // // e.preventDefault(); - // // } - // // }; - // // // this.tooltip.appendChild(this.linkEditor); - // } + async createLinkDropdown() { + let targetTitle = await this.getTextLinkTargetTitle(); + let input = document.createElement("input"); + + // menu item for input for hyperlink url + // TODO: integrate search to allow users to search for a doc to link to + let linkInfo = new MenuItem({ + title: "", + execEvent: "", + class: "button-setting-disabled", + css: "", + render() { + let p = document.createElement("p"); + p.textContent = "Linked to:"; + + input.type = "text"; + input.placeholder = "Enter URL"; + console.log(targetTitle); + if (targetTitle) input.value = targetTitle; + input.onclick = (e: MouseEvent) => { + input.select(); + input.focus(); + }; + + let div = document.createElement("div"); + div.appendChild(p); + div.appendChild(input); + return div; + }, + enable() { return false; }, + run(p1, p2, p3, event) { + event.stopPropagation(); + } + }); + + // menu item to update/apply the hyperlink to the selected text + let linkApply = new MenuItem({ + title: "", + execEvent: "", + class: "", + css: "", + render() { + let button = document.createElement("button"); + button.className = "link-url-button"; + button.textContent = "Apply hyperlink"; + return button; + }, + enable() { return false; }, + run: (state, dispatch, view, event) => { + event.stopPropagation(); + this.makeLinkToURL(input.value, "onRight"); + } + }); + + // menu item to remove the link + // TODO: allow this to be undoable + let self = this; + let deleteLink = new MenuItem({ + title: "Delete link", + execEvent: "", + class: "separated-button", + css: "", + render() { + let button = document.createElement("button"); + button.textContent = "Remove link"; + + let wrapper = document.createElement("div"); + wrapper.appendChild(button); + return wrapper; + }, + enable() { return true; }, + async run() { + self.deleteLink(); + // update link dropdown + let dropdown = await self.createLinkDropdown(); + let newLinkDropdowndom = dropdown.render(self.view).dom; + self._linkDropdownDom && self.tooltip.replaceChild(newLinkDropdowndom, self._linkDropdownDom); + self._linkDropdownDom = newLinkDropdowndom; + } + }); - // async getTextLinkTargetTitle() { - // let node = this.view.state.selection.$from.nodeAfter; - // let link = node && node.marks.find(m => m.type.name === "link"); - // if (link) { - // let href = link.attrs.href; - // if (href) { - // if (href.indexOf(Utils.prepend("/doc/")) === 0) { - // const linkclicked = href.replace(Utils.prepend("/doc/"), "").split("?")[0]; - // if (linkclicked) { - // let linkDoc = await DocServer.GetRefField(linkclicked); - // if (linkDoc instanceof Doc) { - // let anchor1 = await Cast(linkDoc.anchor1, Doc); - // let anchor2 = await Cast(linkDoc.anchor2, Doc); - // let currentDoc = SelectionManager.SelectedDocuments().length && SelectionManager.SelectedDocuments()[0].props.Document; - // if (currentDoc && anchor1 && anchor2) { - // if (Doc.AreProtosEqual(currentDoc, anchor1)) return StrCast(anchor2.title); - // if (Doc.AreProtosEqual(currentDoc, anchor2)) return StrCast(anchor2.title); - // } - // } - // } - // } else { - // return href; - // } - // } - // } - // } + + let linkDropdown = new Dropdown(targetTitle ? [linkInfo, linkApply, deleteLink] : [linkInfo, linkApply], { class: "buttonSettings-dropdown" }) as MenuItem; + return linkDropdown; + } dragElement(elmnt: HTMLElement) { var pos1 = 0, pos2 = 0, pos3 = 0, pos4 = 0; @@ -532,7 +577,7 @@ export class TooltipTextMenu { // let link = state.schema.mark(state.schema.marks.link, { href: target, location: location }); // } - makeLink = (targetDoc: Doc, location: string): string => { + makeLinkToDoc = (targetDoc: Doc, location: string): string => { let target = Utils.prepend("/doc/" + targetDoc[Id]); let node = this.view.state.selection.$from.nodeAfter; let link = this.view.state.schema.mark(this.view.state.schema.marks.link, { href: target, location: location, guid: targetDoc[Id] }); @@ -548,6 +593,15 @@ export class TooltipTextMenu { return ""; } + makeLinkToURL = (target: String, lcoation: string) => { + let node = this.view.state.selection.$from.nodeAfter; + let link = this.view.state.schema.mark(this.view.state.schema.marks.link, { href: target, location: location }); + this.view.dispatch(this.view.state.tr.removeMark(this.view.state.selection.from, this.view.state.selection.to, this.view.state.schema.marks.link)); + this.view.dispatch(this.view.state.tr.addMark(this.view.state.selection.from, this.view.state.selection.to, link)); + node = this.view.state.selection.$from.nodeAfter; + link = node && node.marks.find(m => m.type.name === "link"); + } + deleteLink = () => { let node = this.view.state.selection.$from.nodeAfter; let link = node && node.marks.find(m => m.type.name === "link"); @@ -711,7 +765,7 @@ export class TooltipTextMenu { label: "Summarize", icon: icons.join, css: "color:white;", - class: "summarize", + class: "menuicon", execEvent: "", run: (state, dispatch) => { TooltipTextMenu.insertStar(this.view.state, this.view.dispatch); @@ -743,15 +797,21 @@ export class TooltipTextMenu { height: 32, width: 32, path: "M30.828 1.172c-1.562-1.562-4.095-1.562-5.657 0l-5.379 5.379-3.793-3.793-4.243 4.243 3.326 3.326-14.754 14.754c-0.252 0.252-0.358 0.592-0.322 0.921h-0.008v5c0 0.552 0.448 1 1 1h5c0 0 0.083 0 0.125 0 0.288 0 0.576-0.11 0.795-0.329l14.754-14.754 3.326 3.326 4.243-4.243-3.793-3.793 5.379-5.379c1.562-1.562 1.562-4.095 0-5.657zM5.409 30h-3.409v-3.409l14.674-14.674 3.409 3.409-14.674 14.674z" }; + let self = this; return new MenuItem({ title: "Brush tool", label: "Brush tool", icon: icon, css: "color:white;", - class: active ? "brush-active" : "brush", + class: active ? "menuicon-active" : "menuicon", execEvent: "", run: (state, dispatch) => { this.brush_function(state, dispatch); + + // update dropdown with marks + let newBrushDropdowndom = self.createBrushDropdown().render(self.view).dom; + self._brushDropdownDom && self.tooltip.replaceChild(newBrushDropdowndom, self._brushDropdownDom); + self._brushDropdownDom = newBrushDropdowndom; }, active: (state) => { return true; @@ -798,6 +858,67 @@ export class TooltipTextMenu { } + createBrushDropdown(active: boolean = false) { + let label = "Stored marks: "; + if (TooltipTextMenuManager.Instance._brushMarks && TooltipTextMenuManager.Instance._brushMarks.size > 0) { + TooltipTextMenuManager.Instance._brushMarks.forEach((mark: Mark) => { + const markType = mark.type; + label += markType.name; + label += ", "; + }); + label = label.substring(0, label.length - 2); + } else { + label = "No marks are currently stored"; + } + + + let brushInfo = new MenuItem({ + title: "", + label: label, + execEvent: "", + class: "button-setting-disabled", + css: "", + enable() { return false; }, + run(p1, p2, p3, event) { + event.stopPropagation(); + } + }); + + let self = this; + let clearBrush = new MenuItem({ + title: "Clear brush", + execEvent: "", + class: "separated-button", + css: "", + render() { + let button = document.createElement("button"); + button.textContent = "Clear brush"; + + let wrapper = document.createElement("div"); + wrapper.appendChild(button); + return wrapper; + }, + enable() { return true; }, + run() { + TooltipTextMenuManager.Instance._brushIsEmpty = true; + TooltipTextMenuManager.Instance._brushMarks = new Set(); + + // update brush tool + // TODO: this probably isn't very clean + let newBrushdom = self.createBrush().render(self.view).dom; + self._brushdom && self.tooltip.replaceChild(newBrushdom, self._brushdom); + self._brushdom = newBrushdom; + let newBrushDropdowndom = self.createBrushDropdown().render(self.view).dom; + self._brushDropdownDom && self.tooltip.replaceChild(newBrushDropdowndom, self._brushDropdownDom); + self._brushDropdownDom = newBrushDropdowndom; + } + }); + + let hasMarks = TooltipTextMenuManager.Instance._brushMarks && TooltipTextMenuManager.Instance._brushMarks.size > 0; + let brushDom = new Dropdown(hasMarks ? [brushInfo, clearBrush] : [brushInfo], { class: "buttonSettings-dropdown" }) as MenuItem; + return brushDom; + } + // createCollapse() { // this._collapseBtn = new MenuItem({ // title: "Collapse", @@ -978,7 +1099,7 @@ export class TooltipTextMenu { update(view: EditorView, lastState: EditorState | undefined) { this.updateFromDash(view, lastState, this.editorProps); } //updates the tooltip menu when the selection changes - public updateFromDash(view: EditorView, lastState: EditorState | undefined, props: any) { + public async updateFromDash(view: EditorView, lastState: EditorState | undefined, props: any) { if (!view) { console.log("no editor? why?"); return; @@ -1000,6 +1121,12 @@ export class TooltipTextMenu { } //UPDATE LIST ITEM DROPDOWN + // update link dropdown + let dropdown = await this.createLinkDropdown(); + let newLinkDropdowndom = dropdown.render(this.view).dom; + this._linkDropdownDom && this.tooltip.replaceChild(newLinkDropdowndom, this._linkDropdownDom); + this._linkDropdownDom = newLinkDropdowndom; + //UPDATE FONT STYLE DROPDOWN let activeStyles = this.activeMarksOnSelection(this.fontStyles); if (activeStyles !== undefined) { -- cgit v1.2.3-70-g09d2 From 1b0f0e5e434bc23128b50303ffc2b13244ba6325 Mon Sep 17 00:00:00 2001 From: Fawn Date: Thu, 10 Oct 2019 12:01:35 -0400 Subject: started colors on rich text editor --- src/client/util/TooltipTextMenu.tsx | 100 ++++++++++++++++++++++++++++++++---- 1 file changed, 90 insertions(+), 10 deletions(-) (limited to 'src') diff --git a/src/client/util/TooltipTextMenu.tsx b/src/client/util/TooltipTextMenu.tsx index ce0237527..9dd8e6688 100644 --- a/src/client/util/TooltipTextMenu.tsx +++ b/src/client/util/TooltipTextMenu.tsx @@ -21,6 +21,7 @@ import { Cast, NumCast, StrCast } from '../../new_fields/Types'; import { updateBullets } from './ProsemirrorExampleTransfer'; import { DocumentDecorations } from '../views/DocumentDecorations'; import { SelectionManager } from './SelectionManager'; +import { PastelSchemaPalette } from '../../new_fields/SchemaHeaderField'; const { toggleMark, setBlockType } = require("prosemirror-commands"); const { openPrompt, TextField } = require("./ProsemirrorCopy/prompt.js"); @@ -623,16 +624,6 @@ export class TooltipTextMenu { } - public static insertStar(state: EditorState, dispatch: any) { - if (state.selection.empty) return false; - let mark = state.schema.marks.highlight.create(); - let tr = state.tr; - tr.addMark(state.selection.from, state.selection.to, mark); - let content = tr.selection.content(); - let newNode = state.schema.nodes.star.create({ visibility: false, text: content, textslice: content.toJSON() }); - dispatch && dispatch(tr.replaceSelectionWith(newNode).removeMark(tr.selection.from - 1, tr.selection.from, mark)); - return true; - } //will display a remove-list-type button if selection is in list, otherwise will show list type dropdown updateListItemDropdown(label: string, listTypeBtn: any) { @@ -774,6 +765,93 @@ export class TooltipTextMenu { }); } + public static insertStar(state: EditorState, dispatch: any) { + if (state.selection.empty) return false; + let mark = state.schema.marks.highlight.create(); + let tr = state.tr; + tr.addMark(state.selection.from, state.selection.to, mark); + let content = tr.selection.content(); + let newNode = state.schema.nodes.star.create({ visibility: false, text: content, textslice: content.toJSON() }); + dispatch && dispatch(tr.replaceSelectionWith(newNode).removeMark(tr.selection.from - 1, tr.selection.from, mark)); + return true; + } + + createColorTool() { + return new MenuItem({ + title: "Summarize", + label: "Summarize", + icon: icons.join, + css: "color:white;", + class: "menuicon", + execEvent: "", + run: (state, dispatch) => { + TooltipTextMenu.insertStar(this.view.state, this.view.dispatch); + } + + }); + } + + insertColor(color: String, state: EditorState, dispatch: any) { + + } + + createColorDropdown() { + // menu item for color picker + let colors = new MenuItem({ + title: "", + execEvent: "", + class: "button-setting-disabled", + css: "", + render() { + let p = document.createElement("p"); + p.textContent = "Change color:"; + + // input.type = "text"; + // input.placeholder = "Enter URL"; + // console.log(targetTitle); + // if (targetTitle) input.value = targetTitle; + // input.onclick = (e: MouseEvent) => { + // input.select(); + // input.focus(); + // }; + + let colorsWrapper = document.createElement("div"); + colorsWrapper.className = "colorPicker-wrapper"; + + let colors = [ + PastelSchemaPalette.get("pink2"), + PastelSchemaPalette.get("purple4"), + PastelSchemaPalette.get("bluegreen1"), + PastelSchemaPalette.get("yellow4"), + PastelSchemaPalette.get("red2"), + PastelSchemaPalette.get("bluegreen7"), + PastelSchemaPalette.get("bluegreen5"), + PastelSchemaPalette.get("orange1"), + "#f1efeb", + "#000" + ]; + + colors.forEach(color => { + let button = document.createElement("button"); + button.className = color === TooltipTextMenuManager.Instance.color ? "colorPicker active" : "colorPicker"; + if (color) button.style.backgroundColor = color; + + }); + + let div = document.createElement("div"); + div.appendChild(p); + return div; + }, + enable() { return false; }, + run(p1, p2, p3, event) { + event.stopPropagation(); + } + }); + + let colorDropdown = new Dropdown([colors], { class: "buttonSettings-dropdown" }) as MenuItem; + return colorDropdown; + } + deleteLinkItem() { const icon = { height: 16, width: 16, @@ -1307,6 +1385,8 @@ class TooltipTextMenuManager { public _brushMarks: Set | undefined; public _brushIsEmpty: boolean = true; + public color: String = "#000"; + public activeMenu: TooltipTextMenu | undefined; static get Instance() { -- cgit v1.2.3-70-g09d2 From e8095539c208169b1883e8e73686382d8d8c6336 Mon Sep 17 00:00:00 2001 From: Fawn Date: Sat, 12 Oct 2019 17:18:58 -0400 Subject: created color picker for text on rich text menu --- src/client/util/RichTextSchema.tsx | 18 +++++++++ src/client/util/TooltipTextMenu.scss | 21 +++++++++++ src/client/util/TooltipTextMenu.tsx | 73 ++++++++++++++---------------------- 3 files changed, 67 insertions(+), 45 deletions(-) (limited to 'src') diff --git a/src/client/util/RichTextSchema.tsx b/src/client/util/RichTextSchema.tsx index a5502577b..e943b71e8 100644 --- a/src/client/util/RichTextSchema.tsx +++ b/src/client/util/RichTextSchema.tsx @@ -308,6 +308,24 @@ export const marks: { [index: string]: MarkSpec } = { } }, + // :: MarkSpec Coloring on text. Has `color` attribute that defined the color of the marked text. + color: { + attrs: { + color: { default: "#000" } + }, + inclusive: false, + parseDOM: [{ + tag: "span", getAttrs(dom: any) { + return { color: dom.getAttribute("color") }; + } + }], + toDOM(node: any) { + return node.attrs.color ? ['span', { style: 'color:' + node.attrs.color }] : ['span', { style: 'color: black' }]; + // ["div", ["span", `"`], ["span", 0], ["span", `"`], ["br"], ["a", { ...node.attrs, class: "prosemirror-attribution" }, node.attrs.title], ["br"]] : + // ["a", { ...node.attrs }, 0]; + } + }, + // :: MarkSpec An emphasis mark. Rendered as an `` element. // Has parse rules that also match `` and `font-style: italic`. em: { diff --git a/src/client/util/TooltipTextMenu.scss b/src/client/util/TooltipTextMenu.scss index 11c937471..f94799936 100644 --- a/src/client/util/TooltipTextMenu.scss +++ b/src/client/util/TooltipTextMenu.scss @@ -336,6 +336,27 @@ } } +.colorPicker-wrapper { + display: flex; + flex-wrap: wrap; + justify-content: space-between; + margin-top: 3px; + margin-left: -3px; + width: calc(100% + 6px); +} + +button.colorPicker { + width: 20px; + height: 20px; + border-radius: 15px !important; + margin: 3px; + border: none !important; + + &.active { + border: 2px solid white !important; + } +} + // @import "../views/globalCssVariables"; // .ProseMirror-textblock-dropdown { diff --git a/src/client/util/TooltipTextMenu.tsx b/src/client/util/TooltipTextMenu.tsx index 9dd8e6688..32a910497 100644 --- a/src/client/util/TooltipTextMenu.tsx +++ b/src/client/util/TooltipTextMenu.tsx @@ -28,9 +28,11 @@ const { openPrompt, TextField } = require("./ProsemirrorCopy/prompt.js"); //appears above a selection of text in a RichTextBox to give user options such as Bold, Italics, etc. export class TooltipTextMenu { + public static Toolbar: HTMLDivElement | undefined; + // editor state private view: EditorView; - private editorProps: FieldViewProps & FormattedTextBoxProps | undefined; + // private editorProps: FieldViewProps & FormattedTextBoxProps | undefined; private fontStyles: MarkType[] = []; private fontSizes: MarkType[] = []; private listTypes: (NodeType | any)[] = []; @@ -39,40 +41,38 @@ export class TooltipTextMenu { private listTypeToIcon: Map = new Map(); private _activeMarks: Mark[] = []; private _marksToDoms: Map = new Map(); - // private _brushMarks?: Set; + private _collapsed: boolean = false; //private link: HTMLAnchorElement; // editor doms public tooltip: HTMLElement = document.createElement("div"); private wrapper: HTMLDivElement = document.createElement("div"); + // editor button doms private linkEditor?: HTMLDivElement; private linkText?: HTMLDivElement; private linkDrag?: HTMLImageElement; private _linkDropdownDom?: Node; private _brushdom?: Node; private _brushDropdownDom?: Node; - private fontSizeDom?: Node; private fontStyleDom?: Node; private listTypeBtnDom?: Node; - private _collapsed: boolean = false; + // private _collapseBtn?: MenuItem; // private _brushIsEmpty: boolean = true; - public static Toolbar: HTMLDivElement | undefined; - constructor(view: EditorView) { this.view = view; - // replace old active menu with this - if (TooltipTextMenuManager.Instance.activeMenu) { - TooltipTextMenuManager.Instance.activeMenu.wrapper.remove(); - } - TooltipTextMenuManager.Instance.activeMenu = this; + // // replace old active menu with this + // if (TooltipTextMenuManager.Instance.activeMenu) { + // TooltipTextMenuManager.Instance.activeMenu.wrapper.remove(); + // } + // TooltipTextMenuManager.Instance.activeMenu = this; // initialize the tooltip this.createTooltip(view); @@ -84,30 +84,10 @@ export class TooltipTextMenu { // positioning? TooltipTextMenu.Toolbar = this.wrapper; - - // // position wrapper - // if (TooltipTextMenuManager.Instance.isPinned) { - // this.wrapper.style.position = "absolute"; - // this.wrapper.style.left = TooltipTextMenuManager.Instance.pinnedX + "px"; - // this.wrapper.style.top = TooltipTextMenuManager.Instance.pinnedY + "px"; - - // // if pinned, append to mainview - // const mainView = document.querySelector("#main-div"); - // mainView && mainView.appendChild(this.wrapper); - // } else { - // this.wrapper.style.position = "absolute"; - // this.wrapper.style.top = (this.wrapper.offsetTop + TooltipTextMenuManager.Instance.unpinnedY) + "px"; - // this.wrapper.style.left = (this.wrapper.offsetLeft + TooltipTextMenuManager.Instance.unpinnedX) + "px"; - - // // add tooltip to outerdiv to circumvent scaling problem - // const outer_div = this.editorProps.outer_div; - // outer_div && outer_div(this.wrapper); - // } - } private async createTooltip(view: EditorView) { - // initialize element + // initialize tooltip dom this.tooltip = document.createElement("div"); this.tooltip.className = "tooltipMenu"; @@ -154,6 +134,10 @@ export class TooltipTextMenu { }); + // color menu + this.tooltip.appendChild(this.createColorTool().render(this.view).dom); + this.tooltip.appendChild(this.createColorDropdown().render(this.view).dom); + // link menu this.updateLinkMenu(); let dropdown = await this.createLinkDropdown(); @@ -785,18 +769,22 @@ export class TooltipTextMenu { class: "menuicon", execEvent: "", run: (state, dispatch) => { - TooltipTextMenu.insertStar(this.view.state, this.view.dispatch); + TooltipTextMenu.insertColor(TooltipTextMenuManager.Instance.color, this.view.state, this.view.dispatch); } }); } - insertColor(color: String, state: EditorState, dispatch: any) { + public static insertColor(color: String, state: EditorState, dispatch: any) { + if (state.selection.empty) return false; + let colorMark = state.schema.mark(state.schema.marks.color, { color: color }); + dispatch(state.tr.addMark(state.selection.from, state.selection.to, colorMark)); } createColorDropdown() { // menu item for color picker + let self = this; let colors = new MenuItem({ title: "", execEvent: "", @@ -806,15 +794,6 @@ export class TooltipTextMenu { let p = document.createElement("p"); p.textContent = "Change color:"; - // input.type = "text"; - // input.placeholder = "Enter URL"; - // console.log(targetTitle); - // if (targetTitle) input.value = targetTitle; - // input.onclick = (e: MouseEvent) => { - // input.select(); - // input.focus(); - // }; - let colorsWrapper = document.createElement("div"); colorsWrapper.className = "colorPicker-wrapper"; @@ -834,12 +813,16 @@ export class TooltipTextMenu { colors.forEach(color => { let button = document.createElement("button"); button.className = color === TooltipTextMenuManager.Instance.color ? "colorPicker active" : "colorPicker"; - if (color) button.style.backgroundColor = color; - + if (color) { + button.style.backgroundColor = color; + button.onclick = e => TooltipTextMenuManager.Instance.color = color; + } + colorsWrapper.appendChild(button); }); let div = document.createElement("div"); div.appendChild(p); + div.appendChild(colorsWrapper); return div; }, enable() { return false; }, -- cgit v1.2.3-70-g09d2 From 43b09cb2aded66e67006d99e544d90637a62c048 Mon Sep 17 00:00:00 2001 From: Fawn Date: Wed, 23 Oct 2019 21:54:10 -0400 Subject: improved styling and colors of color picker on richtexteditor --- src/client/util/TooltipTextMenu.scss | 18 +++++++++ src/client/util/TooltipTextMenu.tsx | 75 +++++++++++++++++++++++++++--------- src/new_fields/SchemaHeaderField.ts | 11 ++++++ 3 files changed, 85 insertions(+), 19 deletions(-) (limited to 'src') diff --git a/src/client/util/TooltipTextMenu.scss b/src/client/util/TooltipTextMenu.scss index f94799936..794998a01 100644 --- a/src/client/util/TooltipTextMenu.scss +++ b/src/client/util/TooltipTextMenu.scss @@ -182,6 +182,24 @@ } } +#colorPicker { + position: relative; + + svg { + width: 18px; + height: 18px; + } + + .buttonColor { + position: absolute; + top: 24px; + left: 1px; + width: 24px; + height: 4px; + margin-top: 0; + } +} + .strong, .heading { font-weight: bold; } .em { font-style: italic; } .underline {text-decoration: underline} diff --git a/src/client/util/TooltipTextMenu.tsx b/src/client/util/TooltipTextMenu.tsx index 32a910497..92765b1e0 100644 --- a/src/client/util/TooltipTextMenu.tsx +++ b/src/client/util/TooltipTextMenu.tsx @@ -21,7 +21,7 @@ import { Cast, NumCast, StrCast } from '../../new_fields/Types'; import { updateBullets } from './ProsemirrorExampleTransfer'; import { DocumentDecorations } from '../views/DocumentDecorations'; import { SelectionManager } from './SelectionManager'; -import { PastelSchemaPalette } from '../../new_fields/SchemaHeaderField'; +import { PastelSchemaPalette, DarkPastelSchemaPalette } from '../../new_fields/SchemaHeaderField'; const { toggleMark, setBlockType } = require("prosemirror-commands"); const { openPrompt, TextField } = require("./ProsemirrorCopy/prompt.js"); @@ -58,6 +58,8 @@ export class TooltipTextMenu { private fontSizeDom?: Node; private fontStyleDom?: Node; private listTypeBtnDom?: Node; + private colorDom?: Node; + private colorDropdownDom?: Node; // private _collapseBtn?: MenuItem; @@ -99,7 +101,7 @@ export class TooltipTextMenu { { command: toggleMark(schema.marks.strikethrough), dom: this.icon("S", "strikethrough", "Strikethrough") }, { command: toggleMark(schema.marks.superscript), dom: this.icon("s", "superscript", "Superscript") }, { command: toggleMark(schema.marks.subscript), dom: this.icon("s", "subscript", "Subscript") }, - { command: toggleMark(schema.marks.highlight), dom: this.icon("H", 'blue', 'Blue') } + // { command: toggleMark(schema.marks.highlight), dom: this.icon("H", 'blue', 'Blue') } ]; // add menu items @@ -135,8 +137,10 @@ export class TooltipTextMenu { }); // color menu - this.tooltip.appendChild(this.createColorTool().render(this.view).dom); - this.tooltip.appendChild(this.createColorDropdown().render(this.view).dom); + this.colorDom = this.createColorTool().render(this.view).dom; + this.colorDropdownDom = this.createColorDropdown().render(this.view).dom; + this.tooltip.appendChild(this.colorDom); + this.tooltip.appendChild(this.colorDropdownDom); // link menu this.updateLinkMenu(); @@ -760,14 +764,35 @@ export class TooltipTextMenu { return true; } + createHighlightTool() { + + } + createColorTool() { return new MenuItem({ - title: "Summarize", - label: "Summarize", - icon: icons.join, + title: "Color", + // label: "Color", + // icon: icon, css: "color:white;", class: "menuicon", execEvent: "", + render() { + let svg = document.createElementNS("http://www.w3.org/2000/svg", "svg"); + svg.setAttribute("viewBox", "-100 -100 650 650"); + let path = document.createElementNS('http://www.w3.org/2000/svg', "path"); + path.setAttributeNS(null, "d", "M204.3 5C104.9 24.4 24.8 104.3 5.2 203.4c-37 187 131.7 326.4 258.8 306.7 41.2-6.4 61.4-54.6 42.5-91.7-23.1-45.4 9.9-98.4 60.9-98.4h79.7c35.8 0 64.8-29.6 64.9-65.3C511.5 97.1 368.1-26.9 204.3 5zM96 320c-17.7 0-32-14.3-32-32s14.3-32 32-32 32 14.3 32 32-14.3 32-32 32zm32-128c-17.7 0-32-14.3-32-32s14.3-32 32-32 32 14.3 32 32-14.3 32-32 32zm128-64c-17.7 0-32-14.3-32-32s14.3-32 32-32 32 14.3 32 32-14.3 32-32 32zm128 64c-17.7 0-32-14.3-32-32s14.3-32 32-32 32 14.3 32 32-14.3 32-32 32z"); + svg.appendChild(path); + + let color = document.createElement("div"); + color.className = "buttonColor"; + color.style.backgroundColor = TooltipTextMenuManager.Instance.color.toString(); + + let wrapper = document.createElement("div"); + wrapper.id = "colorPicker"; + wrapper.appendChild(svg); + wrapper.appendChild(color); + return wrapper; + }, run: (state, dispatch) => { TooltipTextMenu.insertColor(TooltipTextMenuManager.Instance.color, this.view.state, this.view.dispatch); } @@ -798,15 +823,15 @@ export class TooltipTextMenu { colorsWrapper.className = "colorPicker-wrapper"; let colors = [ - PastelSchemaPalette.get("pink2"), - PastelSchemaPalette.get("purple4"), - PastelSchemaPalette.get("bluegreen1"), - PastelSchemaPalette.get("yellow4"), - PastelSchemaPalette.get("red2"), - PastelSchemaPalette.get("bluegreen7"), - PastelSchemaPalette.get("bluegreen5"), - PastelSchemaPalette.get("orange1"), - "#f1efeb", + DarkPastelSchemaPalette.get("pink2"), + DarkPastelSchemaPalette.get("purple4"), + DarkPastelSchemaPalette.get("bluegreen1"), + DarkPastelSchemaPalette.get("yellow4"), + DarkPastelSchemaPalette.get("red2"), + DarkPastelSchemaPalette.get("bluegreen7"), + DarkPastelSchemaPalette.get("bluegreen5"), + DarkPastelSchemaPalette.get("orange1"), + "#757472", "#000" ]; @@ -815,7 +840,19 @@ export class TooltipTextMenu { button.className = color === TooltipTextMenuManager.Instance.color ? "colorPicker active" : "colorPicker"; if (color) { button.style.backgroundColor = color; - button.onclick = e => TooltipTextMenuManager.Instance.color = color; + button.onclick = e => { + TooltipTextMenuManager.Instance.color = color; + + TooltipTextMenu.insertColor(TooltipTextMenuManager.Instance.color, self.view.state, self.view.dispatch); + + // update color menu + let colorDom = self.createColorTool().render(self.view).dom; + let colorDropdownDom = self.createColorDropdown().render(self.view).dom; + self.colorDom && self.tooltip.replaceChild(colorDom, self.colorDom); + self.colorDropdownDom && self.tooltip.replaceChild(colorDropdownDom, self.colorDropdownDom); + self.colorDom = colorDom; + self.colorDropdownDom = colorDropdownDom; + }; } colorsWrapper.appendChild(button); }); @@ -1183,8 +1220,8 @@ export class TooltipTextMenu { //UPDATE LIST ITEM DROPDOWN // update link dropdown - let dropdown = await this.createLinkDropdown(); - let newLinkDropdowndom = dropdown.render(this.view).dom; + let linkDropdown = await this.createLinkDropdown(); + let newLinkDropdowndom = linkDropdown.render(this.view).dom; this._linkDropdownDom && this.tooltip.replaceChild(newLinkDropdowndom, this._linkDropdownDom); this._linkDropdownDom = newLinkDropdowndom; diff --git a/src/new_fields/SchemaHeaderField.ts b/src/new_fields/SchemaHeaderField.ts index 92d0aec9a..42a8485ac 100644 --- a/src/new_fields/SchemaHeaderField.ts +++ b/src/new_fields/SchemaHeaderField.ts @@ -41,6 +41,17 @@ export const PastelSchemaPalette = new Map([ export const RandomPastel = () => Array.from(PastelSchemaPalette.values())[Math.floor(Math.random() * PastelSchemaPalette.size)]; +export const DarkPastelSchemaPalette = new Map([ + ["pink2", "#c932b0"], + ["purple4", "#913ad6"], + ["bluegreen1", "#3978ed"], + ["bluegreen7", "#2adb3e"], + ["bluegreen5", "#21b0eb"], + ["yellow4", "#edcc0c"], + ["red2", "#eb3636"], + ["orange1", "#f2740f"], +]); + @scriptingGlobal @Deserializable("schemaheader") export class SchemaHeaderField extends ObjectField { -- cgit v1.2.3-70-g09d2 From bd817ac118c53845c882b89c4b8a110dc8fe10c2 Mon Sep 17 00:00:00 2001 From: Fawn Date: Wed, 23 Oct 2019 22:22:34 -0400 Subject: added dropdown to highlighter tool to choose colors --- src/client/util/RichTextSchema.tsx | 17 +++++- src/client/util/TooltipTextMenu.tsx | 113 ++++++++++++++++++++++++++++++++++-- 2 files changed, 124 insertions(+), 6 deletions(-) (limited to 'src') diff --git a/src/client/util/RichTextSchema.tsx b/src/client/util/RichTextSchema.tsx index e943b71e8..22756be24 100644 --- a/src/client/util/RichTextSchema.tsx +++ b/src/client/util/RichTextSchema.tsx @@ -321,8 +321,21 @@ export const marks: { [index: string]: MarkSpec } = { }], toDOM(node: any) { return node.attrs.color ? ['span', { style: 'color:' + node.attrs.color }] : ['span', { style: 'color: black' }]; - // ["div", ["span", `"`], ["span", 0], ["span", `"`], ["br"], ["a", { ...node.attrs, class: "prosemirror-attribution" }, node.attrs.title], ["br"]] : - // ["a", { ...node.attrs }, 0]; + } + }, + + highlight2: { + attrs: { + highlight: { default: "transparent" } + }, + inclusive: false, + parseDOM: [{ + tag: "span", getAttrs(dom: any) { + return { highlight: dom.getAttribute("backgroundColor") }; + } + }], + toDOM(node: any) { + return node.attrs.highlight ? ['span', { style: 'background-color:' + node.attrs.highlight }] : ['span', { style: 'background-color: transparent' }]; } }, diff --git a/src/client/util/TooltipTextMenu.tsx b/src/client/util/TooltipTextMenu.tsx index 92765b1e0..0626eb504 100644 --- a/src/client/util/TooltipTextMenu.tsx +++ b/src/client/util/TooltipTextMenu.tsx @@ -1,4 +1,4 @@ -import { library } from '@fortawesome/fontawesome-svg-core'; +import { library, dom } from '@fortawesome/fontawesome-svg-core'; import { faListUl } from '@fortawesome/free-solid-svg-icons'; import { action, observable } from "mobx"; import { Dropdown, icons, MenuItem } from "prosemirror-menu"; //no import css @@ -60,6 +60,8 @@ export class TooltipTextMenu { private listTypeBtnDom?: Node; private colorDom?: Node; private colorDropdownDom?: Node; + private highlightDom?: Node; + private highlightDropdownDom?: Node; // private _collapseBtn?: MenuItem; @@ -136,6 +138,12 @@ export class TooltipTextMenu { }); + // highlight menu + this.highlightDom = this.createHighlightTool().render(this.view).dom; + this.highlightDropdownDom = this.createHighlightDropdown().render(this.view).dom; + this.tooltip.appendChild(this.highlightDom); + this.tooltip.appendChild(this.highlightDropdownDom); + // color menu this.colorDom = this.createColorTool().render(this.view).dom; this.colorDropdownDom = this.createColorDropdown().render(this.view).dom; @@ -765,14 +773,111 @@ export class TooltipTextMenu { } createHighlightTool() { + return new MenuItem({ + title: "Highlight", + css: "color:white;", + class: "menuicon", + execEvent: "", + render() { + let svg = document.createElementNS("http://www.w3.org/2000/svg", "svg"); + svg.setAttribute("viewBox", "-100 -100 650 650"); + let path = document.createElementNS('http://www.w3.org/2000/svg', "path"); + path.setAttributeNS(null, "d", "M0 479.98L99.92 512l35.45-35.45-67.04-67.04L0 479.98zm124.61-240.01a36.592 36.592 0 0 0-10.79 38.1l13.05 42.83-50.93 50.94 96.23 96.23 50.86-50.86 42.74 13.08c13.73 4.2 28.65-.01 38.15-10.78l35.55-41.64-173.34-173.34-41.52 35.44zm403.31-160.7l-63.2-63.2c-20.49-20.49-53.38-21.52-75.12-2.35L190.55 183.68l169.77 169.78L530.27 154.4c19.18-21.74 18.15-54.63-2.35-75.13z"); + svg.appendChild(path); + + let color = document.createElement("div"); + color.className = "buttonColor"; + color.style.backgroundColor = TooltipTextMenuManager.Instance.highlight.toString() === "transparent" ? "#fff" : TooltipTextMenuManager.Instance.highlight.toString(); + + let wrapper = document.createElement("div"); + wrapper.id = "colorPicker"; + wrapper.appendChild(svg); + wrapper.appendChild(color); + return wrapper; + }, + run: (state, dispatch) => { + TooltipTextMenu.insertHighlight(TooltipTextMenuManager.Instance.highlight, this.view.state, this.view.dispatch); + } + }); + } + + public static insertHighlight(color: String, state: EditorState, dispatch: any) { + if (state.selection.empty) return false; + + let highlightMark = state.schema.mark(state.schema.marks.highlight2, { highlight: color }); + dispatch(state.tr.addMark(state.selection.from, state.selection.to, highlightMark)); + } + + createHighlightDropdown() { + // menu item for color picker + let self = this; + let colors = new MenuItem({ + title: "", + execEvent: "", + class: "button-setting-disabled", + css: "", + render() { + let p = document.createElement("p"); + p.textContent = "Change highlight:"; + + let colorsWrapper = document.createElement("div"); + colorsWrapper.className = "colorPicker-wrapper"; + + let colors = [ + PastelSchemaPalette.get("pink2"), + PastelSchemaPalette.get("purple4"), + PastelSchemaPalette.get("bluegreen1"), + PastelSchemaPalette.get("yellow4"), + PastelSchemaPalette.get("red2"), + PastelSchemaPalette.get("bluegreen7"), + PastelSchemaPalette.get("bluegreen5"), + PastelSchemaPalette.get("orange1"), + // "#f1f0eb", + "transparent" + ]; + colors.forEach(color => { + let button = document.createElement("button"); + button.className = color === TooltipTextMenuManager.Instance.highlight ? "colorPicker active" : "colorPicker"; + if (color) { + button.style.backgroundColor = color === "transparent" ? "#fff" : color; + button.style.color = "black"; + button.textContent = color === "transparent" ? "X" : ""; + button.onclick = e => { + TooltipTextMenuManager.Instance.highlight = color; + + TooltipTextMenu.insertHighlight(TooltipTextMenuManager.Instance.highlight, self.view.state, self.view.dispatch); + + // update color menu + let highlightDom = self.createHighlightTool().render(self.view).dom; + let highlightDropdownDom = self.createHighlightDropdown().render(self.view).dom; + self.highlightDom && self.tooltip.replaceChild(highlightDom, self.highlightDom); + self.highlightDropdownDom && self.tooltip.replaceChild(highlightDropdownDom, self.highlightDropdownDom); + self.highlightDom = highlightDom; + self.highlightDropdownDom = highlightDropdownDom; + }; + } + colorsWrapper.appendChild(button); + }); + + let div = document.createElement("div"); + div.appendChild(p); + div.appendChild(colorsWrapper); + return div; + }, + enable() { return false; }, + run(p1, p2, p3, event) { + event.stopPropagation(); + } + }); + + let colorDropdown = new Dropdown([colors], { class: "buttonSettings-dropdown" }) as MenuItem; + return colorDropdown; } createColorTool() { return new MenuItem({ title: "Color", - // label: "Color", - // icon: icon, css: "color:white;", class: "menuicon", execEvent: "", @@ -796,7 +901,6 @@ export class TooltipTextMenu { run: (state, dispatch) => { TooltipTextMenu.insertColor(TooltipTextMenuManager.Instance.color, this.view.state, this.view.dispatch); } - }); } @@ -1406,6 +1510,7 @@ class TooltipTextMenuManager { public _brushIsEmpty: boolean = true; public color: String = "#000"; + public highlight: String = "transparent"; public activeMenu: TooltipTextMenu | undefined; -- cgit v1.2.3-70-g09d2 From 66a15a7ac6a6907b398f142554f5d1a66988b630 Mon Sep 17 00:00:00 2001 From: Fawn Date: Wed, 23 Oct 2019 22:57:02 -0400 Subject: switched out text formatting buttons on rich text editor for fontawesome svg --- src/client/util/TooltipTextMenu.scss | 1 + src/client/util/TooltipTextMenu.tsx | 36 +++++++++++++++++++++++++----------- 2 files changed, 26 insertions(+), 11 deletions(-) (limited to 'src') diff --git a/src/client/util/TooltipTextMenu.scss b/src/client/util/TooltipTextMenu.scss index 794998a01..16585d20b 100644 --- a/src/client/util/TooltipTextMenu.scss +++ b/src/client/util/TooltipTextMenu.scss @@ -188,6 +188,7 @@ svg { width: 18px; height: 18px; + margin-top: 11px; } .buttonColor { diff --git a/src/client/util/TooltipTextMenu.tsx b/src/client/util/TooltipTextMenu.tsx index 0626eb504..d32889dd0 100644 --- a/src/client/util/TooltipTextMenu.tsx +++ b/src/client/util/TooltipTextMenu.tsx @@ -95,14 +95,14 @@ export class TooltipTextMenu { this.tooltip = document.createElement("div"); this.tooltip.className = "tooltipMenu"; - // init buttons to the tooltip + // init buttons to the tooltip -- paths to svgs are obtained from fontawesome let items = [ - { command: toggleMark(schema.marks.strong), dom: this.icon("B", "strong", "Bold") }, - { command: toggleMark(schema.marks.em), dom: this.icon("i", "em", "Italic") }, - { command: toggleMark(schema.marks.underline), dom: this.icon("U", "underline", "Underline") }, - { command: toggleMark(schema.marks.strikethrough), dom: this.icon("S", "strikethrough", "Strikethrough") }, - { command: toggleMark(schema.marks.superscript), dom: this.icon("s", "superscript", "Superscript") }, - { command: toggleMark(schema.marks.subscript), dom: this.icon("s", "subscript", "Subscript") }, + { command: toggleMark(schema.marks.strong), dom: this.svgIcon("strong", "Bold", "M333.49 238a122 122 0 0 0 27-65.21C367.87 96.49 308 32 233.42 32H34a16 16 0 0 0-16 16v48a16 16 0 0 0 16 16h31.87v288H34a16 16 0 0 0-16 16v48a16 16 0 0 0 16 16h209.32c70.8 0 134.14-51.75 141-122.4 4.74-48.45-16.39-92.06-50.83-119.6zM145.66 112h87.76a48 48 0 0 1 0 96h-87.76zm87.76 288h-87.76V288h87.76a56 56 0 0 1 0 112z") }, + { command: toggleMark(schema.marks.em), dom: this.svgIcon("em", "Italic", "M320 48v32a16 16 0 0 1-16 16h-62.76l-80 320H208a16 16 0 0 1 16 16v32a16 16 0 0 1-16 16H16a16 16 0 0 1-16-16v-32a16 16 0 0 1 16-16h62.76l80-320H112a16 16 0 0 1-16-16V48a16 16 0 0 1 16-16h192a16 16 0 0 1 16 16z") }, + { command: toggleMark(schema.marks.underline), dom: this.svgIcon("underline", "Underline", "M32 64h32v160c0 88.22 71.78 160 160 160s160-71.78 160-160V64h32a16 16 0 0 0 16-16V16a16 16 0 0 0-16-16H272a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h32v160a80 80 0 0 1-160 0V64h32a16 16 0 0 0 16-16V16a16 16 0 0 0-16-16H32a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16zm400 384H16a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h416a16 16 0 0 0 16-16v-32a16 16 0 0 0-16-16z") }, + { command: toggleMark(schema.marks.strikethrough), dom: this.svgIcon("strikethrough", "Strikethrough", "M496 224H293.9l-87.17-26.83A43.55 43.55 0 0 1 219.55 112h66.79A49.89 49.89 0 0 1 331 139.58a16 16 0 0 0 21.46 7.15l42.94-21.47a16 16 0 0 0 7.16-21.46l-.53-1A128 128 0 0 0 287.51 32h-68a123.68 123.68 0 0 0-123 135.64c2 20.89 10.1 39.83 21.78 56.36H16a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h480a16 16 0 0 0 16-16v-32a16 16 0 0 0-16-16zm-180.24 96A43 43 0 0 1 336 356.45 43.59 43.59 0 0 1 292.45 400h-66.79A49.89 49.89 0 0 1 181 372.42a16 16 0 0 0-21.46-7.15l-42.94 21.47a16 16 0 0 0-7.16 21.46l.53 1A128 128 0 0 0 224.49 480h68a123.68 123.68 0 0 0 123-135.64 114.25 114.25 0 0 0-5.34-24.36z") }, + { command: toggleMark(schema.marks.superscript), dom: this.svgIcon("superscript", "Superscript", "M496 160h-16V16a16 16 0 0 0-16-16h-48a16 16 0 0 0-14.29 8.83l-16 32A16 16 0 0 0 400 64h16v96h-16a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h96a16 16 0 0 0 16-16v-32a16 16 0 0 0-16-16zM336 64h-67a16 16 0 0 0-13.14 6.87l-79.9 115-79.9-115A16 16 0 0 0 83 64H16A16 16 0 0 0 0 80v48a16 16 0 0 0 16 16h33.48l77.81 112-77.81 112H16a16 16 0 0 0-16 16v48a16 16 0 0 0 16 16h67a16 16 0 0 0 13.14-6.87l79.9-115 79.9 115A16 16 0 0 0 269 448h67a16 16 0 0 0 16-16v-48a16 16 0 0 0-16-16h-33.48l-77.81-112 77.81-112H336a16 16 0 0 0 16-16V80a16 16 0 0 0-16-16z") }, + { command: toggleMark(schema.marks.subscript), dom: this.svgIcon("subscript", "Subscript", "M496 448h-16V304a16 16 0 0 0-16-16h-48a16 16 0 0 0-14.29 8.83l-16 32A16 16 0 0 0 400 352h16v96h-16a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h96a16 16 0 0 0 16-16v-32a16 16 0 0 0-16-16zM336 64h-67a16 16 0 0 0-13.14 6.87l-79.9 115-79.9-115A16 16 0 0 0 83 64H16A16 16 0 0 0 0 80v48a16 16 0 0 0 16 16h33.48l77.81 112-77.81 112H16a16 16 0 0 0-16 16v48a16 16 0 0 0 16 16h67a16 16 0 0 0 13.14-6.87l79.9-115 79.9 115A16 16 0 0 0 269 448h67a16 16 0 0 0 16-16v-48a16 16 0 0 0-16-16h-33.48l-77.81-112 77.81-112H336a16 16 0 0 0 16-16V80a16 16 0 0 0-16-16z") }, // { command: toggleMark(schema.marks.highlight), dom: this.icon("H", 'blue', 'Blue') } ]; @@ -787,7 +787,7 @@ export class TooltipTextMenu { let color = document.createElement("div"); color.className = "buttonColor"; - color.style.backgroundColor = TooltipTextMenuManager.Instance.highlight.toString() === "transparent" ? "#fff" : TooltipTextMenuManager.Instance.highlight.toString(); + color.style.backgroundColor = TooltipTextMenuManager.Instance.highlight.toString(); let wrapper = document.createElement("div"); wrapper.id = "colorPicker"; @@ -832,7 +832,7 @@ export class TooltipTextMenu { PastelSchemaPalette.get("bluegreen7"), PastelSchemaPalette.get("bluegreen5"), PastelSchemaPalette.get("orange1"), - // "#f1f0eb", + "white", "transparent" ]; @@ -840,8 +840,7 @@ export class TooltipTextMenu { let button = document.createElement("button"); button.className = color === TooltipTextMenuManager.Instance.highlight ? "colorPicker active" : "colorPicker"; if (color) { - button.style.backgroundColor = color === "transparent" ? "#fff" : color; - button.style.color = "black"; + button.style.backgroundColor = color; button.textContent = color === "transparent" ? "X" : ""; button.onclick = e => { TooltipTextMenuManager.Instance.highlight = color; @@ -1239,6 +1238,21 @@ export class TooltipTextMenu { return span; } + svgIcon(name: string, title: string = name, dpath: string) { + let svg = document.createElementNS("http://www.w3.org/2000/svg", "svg"); + svg.setAttribute("viewBox", "-100 -100 650 650"); + let path = document.createElementNS('http://www.w3.org/2000/svg', "path"); + path.setAttributeNS(null, "d", dpath); + svg.appendChild(path); + + let span = document.createElement("span"); + span.className = name + " menuicon"; + span.title = title; + span.appendChild(svg); + + return span; + } + //method for checking whether node can be inserted canInsert(state: EditorState, nodeType: NodeType>) { let $from = state.selection.$from; -- cgit v1.2.3-70-g09d2 From 45074de50007e0693df8835643464da962e62620 Mon Sep 17 00:00:00 2001 From: Fawn Date: Thu, 24 Oct 2019 10:43:49 -0400 Subject: fixed width of rich text editor --- src/client/util/TooltipTextMenu.scss | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) (limited to 'src') diff --git a/src/client/util/TooltipTextMenu.scss b/src/client/util/TooltipTextMenu.scss index 16585d20b..1f619b4f5 100644 --- a/src/client/util/TooltipTextMenu.scss +++ b/src/client/util/TooltipTextMenu.scss @@ -101,8 +101,9 @@ transform: translateY(-85px); pointer-events: all; height: 35px; - width: 650px; + // width: 650px; padding: 3px; + padding-bottom: 5px; box-shadow: 3px 3px 3px rgba(0, 0, 0, 0.25); display: flex; align-items: center; @@ -122,10 +123,6 @@ } } -#link-drag { - background-color: #323232; -} - .menuicon { width: 25px; height: 25px; @@ -188,7 +185,7 @@ svg { width: 18px; height: 18px; - margin-top: 11px; + // margin-top: 11px; } .buttonColor { @@ -201,12 +198,14 @@ } } - .strong, .heading { font-weight: bold; } - .em { font-style: italic; } - .underline {text-decoration: underline} - .superscript {vertical-align:super} - .subscript { vertical-align:sub } - .strikethrough {text-decoration-line:line-through} +#link-drag { + background-color: #323232; +} + +.underline svg { + margin-top: 15px; +} + .font-size-indicator { font-size: 12px; padding-right: 0px; @@ -299,8 +298,9 @@ left: -27px; top: 31px; background-color: #323232; + border: 1px solid #4d4d4d; color: $light-color-secondary; - border: none; + // border: none; // border: 1px solid $light-color-secondary; border-radius: 0 6px 6px 6px; padding: 3px; -- cgit v1.2.3-70-g09d2 From b625719e71bcfc09e905b8f164b3ed76994b1d59 Mon Sep 17 00:00:00 2001 From: Bob Zeleznik Date: Sun, 10 Nov 2019 19:16:21 -0500 Subject: fixed changing bullet type --- src/client/util/TooltipTextMenu.tsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'src') diff --git a/src/client/util/TooltipTextMenu.tsx b/src/client/util/TooltipTextMenu.tsx index 31d98887f..b1bd73e54 100644 --- a/src/client/util/TooltipTextMenu.tsx +++ b/src/client/util/TooltipTextMenu.tsx @@ -531,8 +531,9 @@ export class TooltipTextMenu { } //remove all node typeand apply the passed-in one to the selected text - changeToNodeType = (nodeType: NodeType | undefined, view: EditorView) => { + changeToNodeType = (nodeType: NodeType | undefined) => { //remove oldif (nodeType) { //add new + let view = this.view; if (nodeType === schema.nodes.bullet_list) { wrapInList(nodeType)(view.state, view.dispatch); } else { -- cgit v1.2.3-70-g09d2 From f4ca517725714d770096d683478a6f5cc995c0c3 Mon Sep 17 00:00:00 2001 From: Bob Zeleznik Date: Sun, 10 Nov 2019 19:46:32 -0500 Subject: fixed switching between ordered list and bullets. --- src/client/util/RichTextSchema.tsx | 3 ++- src/client/util/TooltipTextMenu.tsx | 7 ++++--- 2 files changed, 6 insertions(+), 4 deletions(-) (limited to 'src') diff --git a/src/client/util/RichTextSchema.tsx b/src/client/util/RichTextSchema.tsx index cc3548e1a..a224c57eb 100644 --- a/src/client/util/RichTextSchema.tsx +++ b/src/client/util/RichTextSchema.tsx @@ -241,6 +241,7 @@ export const nodes: { [index: string]: NodeSpec } = { }, toDOM(node: Node) { const bs = node.attrs.bulletStyle; + if (node.attrs.mapStyle === "bullet") return ['ul', 0]; const decMap = bs ? "decimal" + bs : ""; const multiMap = bs === 1 ? "decimal1" : bs === 2 ? "upper-alpha" : bs === 3 ? "lower-roman" : bs === 4 ? "lower-alpha" : ""; let map = node.attrs.mapStyle === "decimal" ? decMap : multiMap; @@ -273,7 +274,7 @@ export const nodes: { [index: string]: NodeSpec } = { const bs = node.attrs.bulletStyle; const decMap = bs ? "decimal" + bs : ""; const multiMap = bs === 1 ? "decimal1" : bs === 2 ? "upper-alpha" : bs === 3 ? "lower-roman" : bs === 4 ? "lower-alpha" : ""; - let map = node.attrs.mapStyle === "decimal" ? decMap : multiMap; + let map = node.attrs.mapStyle === "decimal" ? decMap : node.attrs.mapStyle === "multi" ? multiMap : ""; return node.attrs.visibility ? ["li", { class: `${map}` }, 0] : ["li", { class: `${map}` }, "..."]; //return ["li", { class: `${map}` }, 0]; } diff --git a/src/client/util/TooltipTextMenu.tsx b/src/client/util/TooltipTextMenu.tsx index b1bd73e54..5cbf8b761 100644 --- a/src/client/util/TooltipTextMenu.tsx +++ b/src/client/util/TooltipTextMenu.tsx @@ -171,7 +171,8 @@ export class TooltipTextMenu { //list types this.listTypeToIcon = new Map(); - this.listTypeToIcon.set(schema.nodes.bullet_list, ":"); + //this.listTypeToIcon.set(schema.nodes.bullet_list, ":"); + this.listTypeToIcon.set(schema.nodes.ordered_list.create({ mapStyle: "bullet" }), ":"); this.listTypeToIcon.set(schema.nodes.ordered_list.create({ mapStyle: "decimal" }), "1.1"); this.listTypeToIcon.set(schema.nodes.ordered_list.create({ mapStyle: "multi" }), "1.A"); // this.listTypeToIcon.set(schema.nodes.bullet_list, "⬜"); @@ -539,14 +540,14 @@ export class TooltipTextMenu { } else { var marks = view.state.storedMarks || (view.state.selection.$to.parentOffset && view.state.selection.$from.marks()); if (!wrapInList(schema.nodes.ordered_list)(view.state, (tx2: any) => { - let tx3 = updateBullets(tx2, schema, (nodeType as any).attrs.mapStyle); + let tx3 = updateBullets(tx2, schema, nodeType && (nodeType as any).attrs.mapStyle); marks && tx3.ensureMarks([...marks]); marks && tx3.setStoredMarks([...marks]); view.dispatch(tx2); })) { let tx2 = view.state.tr; - let tx3 = nodeType ? updateBullets(tx2, schema, (nodeType as any).attrs.mapStyle) : tx2; + let tx3 = updateBullets(tx2, schema, nodeType && (nodeType as any).attrs.mapStyle); marks && tx3.ensureMarks([...marks]); marks && tx3.setStoredMarks([...marks]); -- cgit v1.2.3-70-g09d2 From d220b23867050fb6565fe639c3be28080f589aa2 Mon Sep 17 00:00:00 2001 From: Bob Zeleznik Date: Mon, 11 Nov 2019 18:39:23 -0500 Subject: made font size and font style text marks be parametrized to simplify api. --- src/client/util/RichTextSchema.tsx | 154 ++-------------- src/client/util/TooltipTextMenu.tsx | 277 +++++++++++++++++----------- src/client/views/nodes/FormattedTextBox.tsx | 15 +- 3 files changed, 190 insertions(+), 256 deletions(-) (limited to 'src') diff --git a/src/client/util/RichTextSchema.tsx b/src/client/util/RichTextSchema.tsx index a224c57eb..a5e0ca720 100644 --- a/src/client/util/RichTextSchema.tsx +++ b/src/client/util/RichTextSchema.tsx @@ -483,72 +483,26 @@ export const marks: { [index: string]: MarkSpec } = { toDOM() { return codeDOM; } }, - // pFontFamily: { - // attrs: { - // style: { default: 'font-family: "Times New Roman", Times, serif;' }, - // }, - // parseDOM: [{ - // tag: "span", getAttrs(dom: any) { - // if (getComputedStyle(dom).font === "Times New Roman") return { style: `font-family: "Times New Roman", Times, serif;` }; - // if (getComputedStyle(dom).font === "Arial, Helvetica") return { style: `font-family: Arial, Helvetica, sans-serif;` }; - // if (getComputedStyle(dom).font === "Georgia") return { style: `font-family: Georgia, serif;` }; - // if (getComputedStyle(dom).font === "Comic Sans") return { style: `font-family: "Comic Sans MS", cursive, sans-serif;` }; - // if (getComputedStyle(dom).font === "Tahoma, Geneva") return { style: `font-family: Tahoma, Geneva, sans-serif;` }; - // } - // }], - // toDOM: (node: any) => ['span', { - // style: node.attrs.style - // }] - // }, - - /* 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;' - }] - }, - - crimson: { - parseDOM: [{ style: 'font-family: "Crimson Text", sans-serif;' }], - toDOM: () => ['span', { - style: 'font-family: "Crimson Text", sans-serif;' + pFontFamily: { + attrs: { + family: { default: "Crimson Text" }, + }, + parseDOM: [{ + tag: "span", getAttrs(dom: any) { + let cstyle = getComputedStyle(dom); + if (cstyle.font) { + if (cstyle.font.indexOf("Times New Roman") !== -1) return { family: "Times New Roman" }; + if (cstyle.font.indexOf("Arial") !== -1) return { family: "Arial" }; + if (cstyle.font.indexOf("Georgia") !== -1) return { family: "Georgia" }; + if (cstyle.font.indexOf("Comic Sans") !== -1) return { family: "Comic Sans MS" }; + if (cstyle.font.indexOf("Tahoma") !== -1) return { family: "Tahoma" }; + if (cstyle.font.indexOf("Crimson") !== -1) return { family: "Crimson Text" }; + } + } + }], + toDOM: (node) => ['span', { + style: `font-family: "${node.attrs.family}";` }] }, @@ -574,76 +528,6 @@ export const marks: { [index: string]: MarkSpec } = { style: `font-size: ${node.attrs.fontSize}px;` }] }, - - p10: { - parseDOM: [{ style: 'font-size: 10px;' }], - toDOM: () => ['span', { - style: 'font-size: 10px;' - }] - }, - - p12: { - parseDOM: [{ style: 'font-size: 12px;' }], - toDOM: () => ['span', { - style: 'font-size: 12px;' - }] - }, - - p14: { - parseDOM: [{ style: 'font-size: 14px;' }], - toDOM: () => ['span', { - style: 'font-size: 14px;' - }] - }, - - p16: { - parseDOM: [{ style: 'font-size: 16px;' }], - toDOM: () => ['span', { - style: 'font-size: 16px;' - }] - }, - - p18: { - parseDOM: [{ style: 'font-size: 18px;' }], - toDOM: () => ['span', { - style: 'font-size: 18px;' - }] - }, - - p20: { - parseDOM: [{ style: 'font-size: 20px;' }], - toDOM: () => ['span', { - style: 'font-size: 20px;' - }] - }, - - 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;' - }] - }, }; export class ImageResizeView { diff --git a/src/client/util/TooltipTextMenu.tsx b/src/client/util/TooltipTextMenu.tsx index 5cbf8b761..9ce7acec8 100644 --- a/src/client/util/TooltipTextMenu.tsx +++ b/src/client/util/TooltipTextMenu.tsx @@ -29,11 +29,9 @@ export class TooltipTextMenu { public tooltip: HTMLElement; private view: EditorView; private editorProps: FieldViewProps & FormattedTextBoxProps | undefined; - private fontStyles: MarkType[]; - private fontSizes: MarkType[]; private listTypes: (NodeType | any)[]; - private fontSizeToNum: Map; - private fontStylesToName: Map; + private fontSizes: Mark[] = []; + private fontStyles: Mark[] = []; private listTypeToIcon: Map; //private link: HTMLAnchorElement; private wrapper: HTMLDivElement; @@ -135,46 +133,32 @@ export class TooltipTextMenu { //list of font styles - this.fontStylesToName = new Map(); - this.fontStylesToName.set(schema.marks.timesNewRoman, "Times New Roman"); - this.fontStylesToName.set(schema.marks.arial, "Arial"); - this.fontStylesToName.set(schema.marks.georgia, "Georgia"); - this.fontStylesToName.set(schema.marks.comicSans, "Comic Sans MS"); - this.fontStylesToName.set(schema.marks.tahoma, "Tahoma"); - this.fontStylesToName.set(schema.marks.impact, "Impact"); - this.fontStylesToName.set(schema.marks.crimson, "Crimson Text"); - this.fontStyles = Array.from(this.fontStylesToName.keys()); + this.fontStyles.push(schema.marks.pFontFamily.create({ family: "Times New Roman" })); + this.fontStyles.push(schema.marks.pFontFamily.create({ family: "Arial" })); + this.fontStyles.push(schema.marks.pFontFamily.create({ family: "Georgia" })); + this.fontStyles.push(schema.marks.pFontFamily.create({ family: "Comic Sans MS" })); + this.fontStyles.push(schema.marks.pFontFamily.create({ family: "Tahoma" })); + this.fontStyles.push(schema.marks.pFontFamily.create({ family: "Impact" })); + this.fontStyles.push(schema.marks.pFontFamily.create({ family: "Crimson Text" })); //font sizes - this.fontSizeToNum = new Map(); - this.fontSizeToNum.set(schema.marks.p10, 10); - this.fontSizeToNum.set(schema.marks.p12, 12); - this.fontSizeToNum.set(schema.marks.p14, 14); - this.fontSizeToNum.set(schema.marks.p16, 16); - this.fontSizeToNum.set(schema.marks.p18, 18); - this.fontSizeToNum.set(schema.marks.p20, 20); - this.fontSizeToNum.set(schema.marks.p24, 24); - this.fontSizeToNum.set(schema.marks.p32, 32); - this.fontSizeToNum.set(schema.marks.p48, 48); - this.fontSizeToNum.set(schema.marks.p72, 72); - this.fontSizeToNum.set(schema.marks.pFontSize, 10); - // this.fontSizeToNum.set(schema.marks.pFontSize, 12); - // this.fontSizeToNum.set(schema.marks.pFontSize, 14); - // this.fontSizeToNum.set(schema.marks.pFontSize, 16); - // this.fontSizeToNum.set(schema.marks.pFontSize, 18); - // this.fontSizeToNum.set(schema.marks.pFontSize, 20); - // this.fontSizeToNum.set(schema.marks.pFontSize, 24); - // this.fontSizeToNum.set(schema.marks.pFontSize, 32); - // this.fontSizeToNum.set(schema.marks.pFontSize, 48); - // this.fontSizeToNum.set(schema.marks.pFontSize, 72); - this.fontSizes = Array.from(this.fontSizeToNum.keys()); + this.fontSizes.push(schema.marks.pFontSize.create({ fontSize: 10 })); + this.fontSizes.push(schema.marks.pFontSize.create({ fontSize: 12 })); + this.fontSizes.push(schema.marks.pFontSize.create({ fontSize: 14 })); + this.fontSizes.push(schema.marks.pFontSize.create({ fontSize: 16 })); + this.fontSizes.push(schema.marks.pFontSize.create({ fontSize: 18 })); + this.fontSizes.push(schema.marks.pFontSize.create({ fontSize: 20 })); + this.fontSizes.push(schema.marks.pFontSize.create({ fontSize: 24 })); + this.fontSizes.push(schema.marks.pFontSize.create({ fontSize: 32 })); + this.fontSizes.push(schema.marks.pFontSize.create({ fontSize: 48 })); + this.fontSizes.push(schema.marks.pFontSize.create({ fontSize: 72 })); //list types this.listTypeToIcon = new Map(); //this.listTypeToIcon.set(schema.nodes.bullet_list, ":"); this.listTypeToIcon.set(schema.nodes.ordered_list.create({ mapStyle: "bullet" }), ":"); - this.listTypeToIcon.set(schema.nodes.ordered_list.create({ mapStyle: "decimal" }), "1.1"); - this.listTypeToIcon.set(schema.nodes.ordered_list.create({ mapStyle: "multi" }), "1.A"); + this.listTypeToIcon.set(schema.nodes.ordered_list.create({ mapStyle: "decimal" }), "1.1)"); + this.listTypeToIcon.set(schema.nodes.ordered_list.create({ mapStyle: "multi" }), "1.A)"); // this.listTypeToIcon.set(schema.nodes.bullet_list, "⬜"); this.listTypes = Array.from(this.listTypeToIcon.keys()); @@ -195,16 +179,13 @@ export class TooltipTextMenu { //label of dropdown will change to given label updateFontSizeDropdown(label: string) { - //filtering function - might be unecessary - let cut = (arr: MenuItem[]) => arr.filter(x => x); - //font SIZES let fontSizeBtns: MenuItem[] = []; - this.fontSizeToNum.forEach((number, mark) => { - fontSizeBtns.push(this.dropdownMarkBtn(String(number), "color: black; width: 50px;", mark, this.view, this.changeToMarkInGroup, this.fontSizes)); + this.fontSizes.forEach(mark => { + fontSizeBtns.push(this.dropdownFontSizeBtn(String(mark.attrs.fontSize), "color: black; width: 50px;", mark, this.view, this.changeToFontSize)); }); - let newfontSizeDom = (new Dropdown(cut(fontSizeBtns), { + let newfontSizeDom = (new Dropdown(fontSizeBtns, { label: label, css: "color:black; min-width: 60px; padding-left: 5px; margin-right: 0;" }) as MenuItem).render(this.view).dom; @@ -215,20 +196,15 @@ export class TooltipTextMenu { this.fontSizeDom = newfontSizeDom; } - // Make the DIV element draggable - //label of dropdown will change to given label updateFontStyleDropdown(label: string) { - //filtering function - might be unecessary - let cut = (arr: MenuItem[]) => arr.filter(x => x); - //font STYLES let fontBtns: MenuItem[] = []; - this.fontStylesToName.forEach((name, mark) => { - fontBtns.push(this.dropdownMarkBtn(name, "color: black; font-family: " + name + ", sans-serif; width: 125px;", mark, this.view, this.changeToMarkInGroup, this.fontStyles)); + this.fontStyles.forEach((mark) => { + fontBtns.push(this.dropdownFontFamilyBtn(mark.attrs.family, "color: black; font-family: " + mark.attrs.family + ", sans-serif; width: 125px;", mark, this.view, this.changeToFontFamily)); }); - let newfontStyleDom = (new Dropdown(cut(fontBtns), { + let newfontStyleDom = (new Dropdown(fontBtns, { label: label, css: "color:black; width: 125px; margin-left: -3px; padding-left: 2px;" }) as MenuItem).render(this.view).dom; @@ -468,8 +444,7 @@ export class TooltipTextMenu { this.tooltip.appendChild(listTypeBtn); return listTypeBtn; } - - //for a specific grouping of marks (passed in), remove all and apply the passed-in one to the selected text + //for a specific grouping of marks (passed in), remove all and apply the passed-in one to the selected textchangeToMarkInGroup = (markType: MarkType | undefined, view: EditorView, fontMarks: MarkType[]) => { changeToMarkInGroup = (markType: MarkType | undefined, view: EditorView, fontMarks: MarkType[]) => { let { $cursor, ranges } = view.state.selection as TextSelection; let state = view.state; @@ -498,29 +473,6 @@ export class TooltipTextMenu { }); if (markType) { - // fontsize - if (markType.name[0] === 'p') { - let size = this.fontSizeToNum.get(markType); - if (size) { this.updateFontSizeDropdown(String(size) + " pt"); } - if (this.editorProps) { - let ruleProvider = this.editorProps.ruleProvider; - let heading = NumCast(this.editorProps.Document.heading); - if (ruleProvider && heading) { - ruleProvider["ruleSize_" + heading] = size; - } - } - } - else { - let fontName = this.fontStylesToName.get(markType); - if (fontName) { this.updateFontStyleDropdown(fontName); } - if (this.editorProps) { - let ruleProvider = this.editorProps.ruleProvider; - let heading = NumCast(this.editorProps.Document.heading); - if (ruleProvider && heading) { - ruleProvider["ruleFont_" + heading] = fontName; - } - } - } //actually apply font if ((view.state.selection as any).node && (view.state.selection as any).node.type === view.state.schema.nodes.ordered_list) { let status = updateBullets(view.state.tr.setNodeMarkup(view.state.selection.from, (view.state.selection as any).node.type, @@ -531,6 +483,90 @@ export class TooltipTextMenu { } } + changeToFontFamily = (mark: Mark, view: EditorView) => { + let { $cursor, ranges } = view.state.selection as TextSelection; + let state = view.state; + let dispatch = view.dispatch; + + //remove all other active font marks + if ($cursor) { + if (view.state.schema.marks.pFontFamily.isInSet(state.storedMarks || $cursor.marks())) { + dispatch(state.tr.removeStoredMark(view.state.schema.marks.pFontFamily)); + } + } else { + let has = false; + for (let i = 0; !has && i < ranges.length; i++) { + let { $from, $to } = ranges[i]; + has = state.doc.rangeHasMark($from.pos, $to.pos, view.state.schema.marks.pFontFamily); + } + for (let i of ranges) { + if (has) { + toggleMark(view.state.schema.marks.pFontFamily)(view.state, view.dispatch, view); + } + } + } + + let fontName = mark.attrs.family; + if (fontName) { this.updateFontStyleDropdown(fontName); } + if (this.editorProps) { + let ruleProvider = this.editorProps.ruleProvider; + let heading = NumCast(this.editorProps.Document.heading); + if (ruleProvider && heading) { + ruleProvider["ruleFont_" + heading] = fontName; + } + } + //actually apply font + if ((view.state.selection as any).node && (view.state.selection as any).node.type === view.state.schema.nodes.ordered_list) { + let status = updateBullets(view.state.tr.setNodeMarkup(view.state.selection.from, (view.state.selection as any).node.type, + { ...(view.state.selection as NodeSelection).node.attrs, setFontFamily: fontName }), view.state.schema); + view.dispatch(status.setSelection(new NodeSelection(status.doc.resolve(view.state.selection.from)))); + } + else view.dispatch(view.state.tr.addMark(view.state.selection.from, view.state.selection.to, view.state.schema.marks.pFontFamily.create({ family: fontName }))); + view.state.storedMarks = [...(view.state.storedMarks || []), view.state.schema.marks.pFontFamily.create({ family: fontName })]; + } + + changeToFontSize = (mark: Mark, view: EditorView) => { + let { $cursor, ranges } = view.state.selection as TextSelection; + let state = view.state; + let dispatch = view.dispatch; + + //remove all other active font marks + if ($cursor) { + if (view.state.schema.marks.pFontSize.isInSet(state.storedMarks || $cursor.marks())) { + dispatch(state.tr.removeStoredMark(view.state.schema.marks.pFontSize)); + } + } else { + let has = false; + for (let i = 0; !has && i < ranges.length; i++) { + let { $from, $to } = ranges[i]; + has = state.doc.rangeHasMark($from.pos, $to.pos, view.state.schema.marks.pFontSize); + } + for (let i of ranges) { + if (has) { + toggleMark(view.state.schema.marks.pFontSize)(view.state, view.dispatch, view); + } + } + } + + let size = mark.attrs.fontSize; + if (size) { this.updateFontSizeDropdown(String(size) + " pt"); } + if (this.editorProps) { + let ruleProvider = this.editorProps.ruleProvider; + let heading = NumCast(this.editorProps.Document.heading); + if (ruleProvider && heading) { + ruleProvider["ruleSize_" + heading] = size; + } + } + //actually apply font + if ((view.state.selection as any).node && (view.state.selection as any).node.type === view.state.schema.nodes.ordered_list) { + let status = updateBullets(view.state.tr.setNodeMarkup(view.state.selection.from, (view.state.selection as any).node.type, + { ...(view.state.selection as NodeSelection).node.attrs, setFontSize: size }), view.state.schema); + view.dispatch(status.setSelection(new NodeSelection(status.doc.resolve(view.state.selection.from)))); + } + else view.dispatch(view.state.tr.addMark(view.state.selection.from, view.state.selection.to, view.state.schema.marks.pFontSize.create({ fontSize: size }))); + view.state.storedMarks = [...(view.state.storedMarks || []), view.state.schema.marks.pFontSize.create({ fontSize: size })]; + } + //remove all node typeand apply the passed-in one to the selected text changeToNodeType = (nodeType: NodeType | undefined) => { //remove oldif (nodeType) { //add new @@ -558,7 +594,7 @@ export class TooltipTextMenu { //makes a button for the drop down FOR MARKS //css is the style you want applied to the button - dropdownMarkBtn(label: string, css: string, markType: MarkType, view: EditorView, changeToMarkInGroup: (markType: MarkType, view: EditorView, groupMarks: MarkType[]) => any, groupMarks: MarkType[]) { + dropdownFontFamilyBtn(label: string, css: string, mark: Mark, view: EditorView, changeFontFamily: (mark: Mark, view: EditorView) => any) { return new MenuItem({ title: "", label: label, @@ -567,7 +603,22 @@ export class TooltipTextMenu { css: css, enable() { return true; }, run() { - changeToMarkInGroup(markType, view, groupMarks); + changeFontFamily(mark, view); + } + }); + } + //makes a button for the drop down FOR MARKS + //css is the style you want applied to the button + dropdownFontSizeBtn(label: string, css: string, mark: Mark, view: EditorView, changeFontSize: (markType: Mark, view: EditorView) => any) { + return new MenuItem({ + title: "", + label: label, + execEvent: "", + class: "menuicon", + css: css, + enable() { return true; }, + run() { + changeFontSize(mark, view); } }); } @@ -649,7 +700,9 @@ export class TooltipTextMenu { this.view.dispatch(this.view.state.tr.removeMark(from, to)); Array.from(this._brushMarks).filter(m => m.type !== schema.marks.user_mark).forEach((mark: Mark) => { const markType = mark.type; - this.changeToMarkInGroup(markType, this.view, []); + if (mark.type === schema.marks.pFontFamily) this.changeToFontFamily(mark, this.view); + else if (mark.type === schema.marks.pFontSize) this.changeToFontSize(mark, this.view); + else this.changeToMarkInGroup(markType, this.view, []); }); } } @@ -868,50 +921,23 @@ export class TooltipTextMenu { //UPDATE LIST ITEM DROPDOWN //UPDATE FONT STYLE DROPDOWN - let activeStyles = this.activeMarksOnSelection(this.fontStyles); + let activeStyles = this.activeFontFamilyOnSelection(); if (activeStyles !== undefined) { - // activeStyles.forEach((markType) => { - // this._activeMarks.push(this.view.state.schema.mark(markType)); - // }); if (activeStyles.length === 1) { - // if we want to update something somewhere with active font name - let fontName = this.fontStylesToName.get(activeStyles[0]); - if (fontName) { this.updateFontStyleDropdown(fontName); } - } else if (activeStyles.length === 0) { - //crimson on default - this.updateFontStyleDropdown("Crimson Text"); - } else { - this.updateFontStyleDropdown("Various"); - } + activeStyles[0] && this.updateFontStyleDropdown(activeStyles[0]); + } else this.updateFontStyleDropdown(activeStyles.length ? "various" : "default"); } //UPDATE FONT SIZE DROPDOWN - let activeSizes = this.activeMarksOnSelection(this.fontSizes); + let activeSizes = this.activeFontSizeOnSelection(); if (activeSizes !== undefined) { if (activeSizes.length === 1) { //if there's only one active font size - // activeSizes.forEach((markType) => { - // this._activeMarks.push(this.view.state.schema.mark(markType)); - // }); - let size = this.fontSizeToNum.get(activeSizes[0]); - if (size) { this.updateFontSizeDropdown(String(size) + " pt"); } - } else if (activeSizes.length === 0) { - //should be 14 on default - this.updateFontSizeDropdown("14 pt"); - } else { //multiple font sizes selected - this.updateFontSizeDropdown("Various"); - } + activeSizes[0] && this.updateFontSizeDropdown(String(activeSizes[0]) + " pt"); + } else this.updateFontSizeDropdown(activeSizes.length ? "various" : "default"); } this.update_mark_doms(); } - - public mark_key_pressed(marks: Mark[]) { - if (this.view.state.selection.empty) { - if (marks) this._activeMarks = marks; - this.update_mark_doms(); - } - } - update_mark_doms() { this.reset_mark_doms(); let foundlink = false; @@ -946,6 +972,30 @@ export class TooltipTextMenu { } + //finds fontSize at start of selection + activeFontSizeOnSelection() { + //current selection + let state = this.view.state; + let activeSizes: number[] = []; + const pos = this.view.state.selection.$from; + const ref_node: ProsNode = this.reference_node(pos); + if (ref_node && ref_node !== this.view.state.doc && ref_node.isText) { + ref_node.marks.forEach(m => m.type === state.schema.marks.pFontSize && activeSizes.push(m.attrs.fontSize)); + } + return activeSizes; + } + //finds fontSize at start of selection + activeFontFamilyOnSelection() { + //current selection + let state = this.view.state; + let activeFamilies: string[] = []; + const pos = this.view.state.selection.$from; + const ref_node: ProsNode = this.reference_node(pos); + if (ref_node && ref_node !== this.view.state.doc && ref_node.isText) { + ref_node.marks.forEach(m => m.type === state.schema.marks.pFontFamily && activeFamilies.push(m.attrs.family)); + } + return activeFamilies; + } //finds all active marks on selection in given group activeMarksOnSelection(markGroup: MarkType[]) { //current selection @@ -980,6 +1030,9 @@ export class TooltipTextMenu { this._activeMarks = ref_node.marks; activeMarks = markGroup.filter(mark_type => { if (dispatch) { + if (mark_type === state.schema.marks.pFontSize) { + return ref_node.marks.some(m => m.type.name === state.schema.marks.pFontSize.name); + } let mark = state.schema.mark(mark_type); return ref_node.marks.includes(mark); } diff --git a/src/client/views/nodes/FormattedTextBox.tsx b/src/client/views/nodes/FormattedTextBox.tsx index 037c92a62..abf26826c 100644 --- a/src/client/views/nodes/FormattedTextBox.tsx +++ b/src/client/views/nodes/FormattedTextBox.tsx @@ -93,8 +93,8 @@ export class FormattedTextBox extends DocExtendableComponent<(FieldViewProps & F private _pushReactionDisposer: Opt; private dropDisposer?: DragManager.DragDropDisposer; - @observable private _fontSize = 13; - @observable private _fontFamily = "Arial"; + @observable private _ruleFontSize = 0; + @observable private _ruleFontFamily = "Arial"; @observable private _fontAlign = ""; @observable private _entered = false; public static SelectOnLoad = ""; @@ -187,9 +187,6 @@ export class FormattedTextBox extends DocExtendableComponent<(FieldViewProps & F } const state = this._editorView.state.apply(tx); this._editorView.updateState(state); - if (state.selection.empty && FormattedTextBox._toolTipTextMenu && tx.storedMarks) { - FormattedTextBox._toolTipTextMenu.mark_key_pressed(tx.storedMarks); - } let tsel = this._editorView.state.selection.$from; tsel.marks().filter(m => m.type === this._editorView!.state.schema.marks.user_mark).map(m => AudioBox.SetScrubTime(Math.max(0, m.attrs.modified * 5000 - 1000))); @@ -552,8 +549,8 @@ export class FormattedTextBox extends DocExtendableComponent<(FieldViewProps & F return undefined; }, action((rules: any) => { - this._fontFamily = rules ? rules.font : "Arial"; - this._fontSize = rules ? rules.size : NumCast(this.layoutDoc.fontSize, 13); + this._ruleFontFamily = rules ? rules.font : "Arial"; + this._ruleFontSize = rules ? rules.size : 0; rules && setTimeout(() => { const view = this._editorView!; if (this._proseRef) { @@ -1035,8 +1032,8 @@ export class FormattedTextBox extends DocExtendableComponent<(FieldViewProps & F opacity: this.props.hideOnLeave ? (this._entered ? 1 : 0.1) : 1, color: this.props.color ? this.props.color : this.props.hideOnLeave ? "white" : "inherit", pointerEvents: interactive, - fontSize: this._fontSize, - fontFamily: this._fontFamily, + fontSize: this._ruleFontSize ? this._ruleFontSize : NumCast(this.layoutDoc.fontSize, 13), + fontFamily: this._ruleFontFamily ? this._ruleFontFamily : StrCast(this.layoutDoc.fontFamily, "Crimson Text"), }} onContextMenu={this.specificContextMenu} onKeyDown={this.onKeyPress} -- cgit v1.2.3-70-g09d2 From d7150995d62c498ab8435de986b90d98bdca020c Mon Sep 17 00:00:00 2001 From: bob Date: Tue, 12 Nov 2019 15:07:03 -0500 Subject: a bunch of cleanup and bug fixes to text links and doculink buttons --- src/client/documents/Documents.ts | 5 +- src/client/util/LinkManager.ts | 2 - src/client/util/RichTextSchema.tsx | 4 +- src/client/util/TooltipTextMenu.tsx | 23 +++--- src/client/views/DocumentDecorations.tsx | 10 ++- src/client/views/GlobalKeyHandler.ts | 2 +- src/client/views/Main.tsx | 24 ------- src/client/views/MainView.scss | 2 +- src/client/views/MainView.tsx | 8 +-- src/client/views/linking/LinkMenuItem.tsx | 1 - src/client/views/nodes/FormattedTextBox.tsx | 9 ++- src/client/views/nodes/FormattedTextBoxComment.tsx | 83 ++++++++++++---------- src/client/views/nodes/PDFBox.scss | 2 + src/client/views/nodes/PDFBox.tsx | 76 +++++++++++--------- src/client/views/pdf/Annotation.tsx | 6 +- src/client/views/pdf/PDFViewer.tsx | 14 +--- src/new_fields/Doc.ts | 2 +- .../authentication/models/current_user_utils.ts | 1 - 18 files changed, 127 insertions(+), 147 deletions(-) (limited to 'src') diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts index d1fcabc4a..ba9f87025 100644 --- a/src/client/documents/Documents.ts +++ b/src/client/documents/Documents.ts @@ -257,6 +257,9 @@ export namespace Docs { return PrototypeMap.get(type)!; } + /** + * A collection of all links in the database. Ideally, this would be a search, but for now all links are cached here. + */ export function MainLinkDocument() { return Prototypes.get(DocumentType.LINKDOC); } @@ -703,6 +706,7 @@ export namespace DocUtils { linkDocProto.title = title === "" ? source.doc.title + " to " + target.doc.title : title; linkDocProto.linkDescription = description; + linkDocProto.isPrototype = true; linkDocProto.anchor1 = source.doc; linkDocProto.anchor2 = target.doc; @@ -714,7 +718,6 @@ export namespace DocUtils { linkDocProto.anchor2Timecode = target.doc.currentTimecode; linkDocProto.layoutKey1 = DocuLinkBox.LayoutString("anchor1"); linkDocProto.layoutKey2 = DocuLinkBox.LayoutString("anchor2"); - linkDocProto.borderRounding = "20"; linkDocProto.width = linkDocProto.height = 0; linkDocProto.isBackground = true; linkDocProto.isButton = true; diff --git a/src/client/util/LinkManager.ts b/src/client/util/LinkManager.ts index ee2f2dadc..eedc4967d 100644 --- a/src/client/util/LinkManager.ts +++ b/src/client/util/LinkManager.ts @@ -34,8 +34,6 @@ export class LinkManager { // the linkmanagerdoc stores a list of docs representing all linkdocs in 'allLinks' and a list of strings representing all group types in 'allGroupTypes' // lists of strings representing the metadata keys for each group type is stored under a key that is the same as the group type public get LinkManagerDoc(): Doc | undefined { - // return FieldValue(Cast(CurrentUserUtils.UserDocument.linkManagerDoc, Doc)); - return Docs.Prototypes.MainLinkDocument(); } diff --git a/src/client/util/RichTextSchema.tsx b/src/client/util/RichTextSchema.tsx index a5e0ca720..76b8aeaa1 100644 --- a/src/client/util/RichTextSchema.tsx +++ b/src/client/util/RichTextSchema.tsx @@ -305,8 +305,8 @@ export const marks: { [index: string]: MarkSpec } = { }], toDOM(node: any) { return node.attrs.docref && node.attrs.title ? - ["div", ["span", `"`], ["span", 0], ["span", `"`], ["br"], ["a", { ...node.attrs, class: "prosemirror-attribution" }, node.attrs.title], ["br"]] : - ["a", { ...node.attrs }, 0]; + ["div", ["span", `"`], ["span", 0], ["span", `"`], ["br"], ["a", { ...node.attrs, class: "prosemirror-attribution", title: `${node.attrs.title}` }, node.attrs.title], ["br"]] : + ["a", { ...node.attrs, title: `${node.attrs.title}` }, 0]; } }, diff --git a/src/client/util/TooltipTextMenu.tsx b/src/client/util/TooltipTextMenu.tsx index 9ce7acec8..38471a955 100644 --- a/src/client/util/TooltipTextMenu.tsx +++ b/src/client/util/TooltipTextMenu.tsx @@ -17,7 +17,7 @@ import { DragManager } from "./DragManager"; import { LinkManager } from "./LinkManager"; import { schema } from "./RichTextSchema"; import "./TooltipTextMenu.scss"; -import { Cast, NumCast } from '../../new_fields/Types'; +import { Cast, NumCast, StrCast } from '../../new_fields/Types'; import { updateBullets } from './ProsemirrorExampleTransfer'; import { DocumentDecorations } from '../views/DocumentDecorations'; const { toggleMark, setBlockType } = require("prosemirror-commands"); @@ -284,7 +284,7 @@ export class TooltipTextMenu { if (proto && docView) { proto.sourceContext = docView.props.ContainingCollectionDoc; } - let text = this.makeLink(linkDoc, ctrlKey ? "onRight" : "inTab"); + let text = this.makeLink(linkDoc, StrCast(linkDoc.anchor2.title), ctrlKey ? "onRight" : "inTab"); if (linkDoc instanceof Doc && linkDoc.anchor2 instanceof Doc) { proto.title = text === "" ? proto.title : text + " to " + linkDoc.anchor2.title; // TODODO open to more descriptive descriptions of following in text link } @@ -374,25 +374,20 @@ export class TooltipTextMenu { // let link = state.schema.mark(state.schema.marks.link, { href: target, location: location }); // } - makeLink = (targetDoc: Doc, location: string): string => { - let target = Utils.prepend("/doc/" + targetDoc[Id]); + makeLink = (targetDoc: Doc, title: string, location: string): string => { + let link = this.view.state.schema.marks.link.create({ href: Utils.prepend("/doc/" + targetDoc[Id]), title: title, location: location }); + this.view.dispatch(this.view.state.tr.removeMark(this.view.state.selection.from, this.view.state.selection.to, this.view.state.schema.marks.link). + addMark(this.view.state.selection.from, this.view.state.selection.to, link)); let node = this.view.state.selection.$from.nodeAfter; - let link = this.view.state.schema.mark(this.view.state.schema.marks.link, { href: target, location: location, guid: targetDoc[Id] }); - this.view.dispatch(this.view.state.tr.removeMark(this.view.state.selection.from, this.view.state.selection.to, this.view.state.schema.marks.link)); - this.view.dispatch(this.view.state.tr.addMark(this.view.state.selection.from, this.view.state.selection.to, link)); - node = this.view.state.selection.$from.nodeAfter; - link = node && node.marks.find(m => m.type.name === "link"); - if (node) { - if (node.text) { - return node.text; - } + if (node && node.text) { + return node.text; } return ""; } deleteLink = () => { let node = this.view.state.selection.$from.nodeAfter; - let link = node && node.marks.find(m => m.type.name === "link"); + let link = node && node.marks.find(m => m.type === this.view.state.schema.marks.link); let href = link!.attrs.href; if (href) { if (href.indexOf(Utils.prepend("/doc/")) === 0) { diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx index 0336440d5..55c211d1d 100644 --- a/src/client/views/DocumentDecorations.tsx +++ b/src/client/views/DocumentDecorations.tsx @@ -24,6 +24,7 @@ import { DocumentView } from "./nodes/DocumentView"; import { FieldView } from "./nodes/FieldView"; import { IconBox } from "./nodes/IconBox"; import React = require("react"); +import { DocumentType } from '../documents/DocumentTypes'; const higflyout = require("@hig/flyout"); export const { anchorPoints } = higflyout; export const Flyout = higflyout.default; @@ -112,7 +113,7 @@ export class DocumentDecorations extends React.Component<{}, { value: string }> } else { if (SelectionManager.SelectedDocuments().length > 0) { - SelectionManager.SelectedDocuments()[0].props.Document.customTitle = true; + SelectionManager.SelectedDocuments()[0].props.Document.customTitle = !this._title.startsWith("-"); let field = SelectionManager.SelectedDocuments()[0].props.Document[this._fieldKey]; if (typeof field === "number") { SelectionManager.SelectedDocuments().forEach(d => { @@ -174,6 +175,13 @@ export class DocumentDecorations extends React.Component<{}, { value: string }> var [sptX, sptY] = transform.transformPoint(0, 0); let [bptX, bptY] = transform.transformPoint(documentView.props.PanelWidth(), documentView.props.PanelHeight()); + if (documentView.props.Document.type === DocumentType.LINK) { + let rect = documentView.ContentDiv!.getElementsByClassName("docuLinkBox-cont")[0].getBoundingClientRect(); + sptX = rect.left; + sptY = rect.top; + bptX = rect.right; + bptY = rect.bottom; + } return { x: Math.min(sptX, bounds.x), y: Math.min(sptY, bounds.y), r: Math.max(bptX, bounds.r), b: Math.max(bptY, bounds.b) diff --git a/src/client/views/GlobalKeyHandler.ts b/src/client/views/GlobalKeyHandler.ts index 9ca9fc163..8f397e331 100644 --- a/src/client/views/GlobalKeyHandler.ts +++ b/src/client/views/GlobalKeyHandler.ts @@ -165,7 +165,7 @@ export default class KeyManager { } } break; - case "c": + case "t": PromiseValue(Cast(CurrentUserUtils.UserDocument.Create, Doc)).then(pv => pv && (pv.onClick as ScriptField).script.run({ this: pv })); if (MainView.Instance.flyoutWidth === 240) { MainView.Instance.flyoutWidth = 0; diff --git a/src/client/views/Main.tsx b/src/client/views/Main.tsx index a91a2b69e..b21eb9c8f 100644 --- a/src/client/views/Main.tsx +++ b/src/client/views/Main.tsx @@ -3,34 +3,11 @@ import { Docs } from "../documents/Documents"; import { CurrentUserUtils } from "../../server/authentication/models/current_user_utils"; import * as ReactDOM from 'react-dom'; import * as React from 'react'; -import { Cast } from "../../new_fields/Types"; -import { Doc, DocListCastAsync } from "../../new_fields/Doc"; -import { List } from "../../new_fields/List"; import { DocServer } from "../DocServer"; import { AssignAllExtensions } from "../../extensions/General/Extensions"; AssignAllExtensions(); -let swapDocs = async () => { - let oldDoc = await Cast(CurrentUserUtils.UserDocument.linkManagerDoc, Doc); - // Docs.Prototypes.MainLinkDocument().allLinks = new List(); - if (oldDoc) { - let links = await DocListCastAsync(oldDoc.allLinks); - // if (links && DocListCast(links)) { - if (links && links.length) { - let data = await DocListCastAsync(Docs.Prototypes.MainLinkDocument().allLinks); - if (data) { - data.push(...links.filter(i => data!.indexOf(i) === -1)); - Docs.Prototypes.MainLinkDocument().allLinks = new List(data.filter((i, idx) => data!.indexOf(i) === idx)); - } - else { - Docs.Prototypes.MainLinkDocument().allLinks = new List(links); - } - } - CurrentUserUtils.UserDocument.linkManagerDoc = undefined; - } -}; - (async () => { const info = await CurrentUserUtils.loadCurrentUser(); DocServer.init(window.location.protocol, window.location.hostname, 4321, info.email); @@ -38,7 +15,6 @@ let swapDocs = async () => { if (info.id !== "__guest__") { // a guest will not have an id registered await CurrentUserUtils.loadUserDocument(info); - await swapDocs(); } document.getElementById('root')!.addEventListener('wheel', event => { if (event.ctrlKey) { diff --git a/src/client/views/MainView.scss b/src/client/views/MainView.scss index 21b135c49..a858a73c7 100644 --- a/src/client/views/MainView.scss +++ b/src/client/views/MainView.scss @@ -13,7 +13,7 @@ left: 250px; } -.mainView-container { +#mainView-container { width: 100%; height: 100%; position: absolute; diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx index 773da05df..83dbb433b 100644 --- a/src/client/views/MainView.tsx +++ b/src/client/views/MainView.tsx @@ -37,7 +37,6 @@ import { OverlayView } from './OverlayView'; import PDFMenu from './pdf/PDFMenu'; import { PreviewCursor } from './PreviewCursor'; import { Scripting } from '../util/Scripting'; -import { LinkManager } from '../util/LinkManager'; import { AudioBox } from './nodes/AudioBox'; @observer @@ -197,11 +196,6 @@ export class MainView extends React.Component { var dockingLayout = { content: [{ type: 'row', content: [CollectionDockingView.makeDocumentConfig(freeformDoc, freeformDoc, 600)] }] }; let mainDoc = Docs.Create.DockDocument([freeformDoc], JSON.stringify(dockingLayout), {}, id); if (this.userDoc && ((workspaces = Cast(this.userDoc.workspaces, Doc)) instanceof Doc)) { - if (!this.userDoc.linkManagerDoc) { - let linkManagerDoc = new Doc(); - linkManagerDoc.allLinks = new List([]); - this.userDoc.linkManagerDoc = linkManagerDoc; - } Doc.AddDocToList(workspaces, "data", mainDoc); mainDoc.title = `Workspace ${DocListCast(workspaces.data).length}`; } @@ -504,7 +498,7 @@ export class MainView extends React.Component { } render() { - return (
+ return (
diff --git a/src/client/views/linking/LinkMenuItem.tsx b/src/client/views/linking/LinkMenuItem.tsx index a6ee9c2c6..238660de3 100644 --- a/src/client/views/linking/LinkMenuItem.tsx +++ b/src/client/views/linking/LinkMenuItem.tsx @@ -8,7 +8,6 @@ import { Cast, StrCast } from '../../../new_fields/Types'; import { DragLinkAsDocument } from '../../util/DragManager'; import { LinkManager } from '../../util/LinkManager'; import { ContextMenu } from '../ContextMenu'; -import { MainView } from '../MainView'; import { LinkFollowBox } from './LinkFollowBox'; import './LinkMenu.scss'; import React = require("react"); diff --git a/src/client/views/nodes/FormattedTextBox.tsx b/src/client/views/nodes/FormattedTextBox.tsx index abf26826c..a0f8523a2 100644 --- a/src/client/views/nodes/FormattedTextBox.tsx +++ b/src/client/views/nodes/FormattedTextBox.tsx @@ -842,6 +842,7 @@ export class FormattedTextBox extends DocExtendableComponent<(FieldViewProps & F this._editorView && this._editorView.destroy(); } onPointerDown = (e: React.PointerEvent): void => { + FormattedTextBoxComment.textBox = this; let pos = this._editorView!.posAtCoords({ left: e.clientX, top: e.clientY }); pos && (this._nodeClicked = this._editorView!.state.doc.nodeAt(pos.pos)); if (this.props.onClick && e.button === 0) { @@ -1018,11 +1019,13 @@ export class FormattedTextBox extends DocExtendableComponent<(FieldViewProps & F } render() { + trace(); let rounded = StrCast(this.layoutDoc.borderRounding) === "100%" ? "-rounded" : ""; - let interactive: "all" | "none" = InkingControl.Instance.selectedTool || this.layoutDoc.isBackground - ? "none" : "all"; + let interactive = InkingControl.Instance.selectedTool || this.layoutDoc.isBackground; if (this.props.isSelected()) { FormattedTextBox._toolTipTextMenu!.updateFromDash(this._editorView!, undefined, this.props); + } else if (FormattedTextBoxComment.textBox === this) { + FormattedTextBoxComment.Hide(); } return (
{ let keep = e.target && (e.target as any).type === "checkbox" ? true : false; @@ -91,62 +93,67 @@ export class FormattedTextBoxComment { let state = view.state; // Don't do anything if the document/selection didn't change if (lastState && lastState.doc.eq(state.doc) && - lastState.selection.eq(state.selection)) return; + lastState.selection.eq(state.selection)) { + return; + } - if (!FormattedTextBoxComment.textBox || !FormattedTextBoxComment.textBox.props || !FormattedTextBoxComment.textBox.props.isSelected()) return; + const textBox = FormattedTextBoxComment.textBox; + if (!textBox || !textBox.props) { + return; + } let set = "none"; - if (FormattedTextBoxComment.textBox && state.selection.$from) { - let nbef = findStartOfMark(state.selection.$from, view, findOtherUserMark); + let nbef = 0; + // this section checks to see if the insertion point is over text entered by a different user. If so, it sets ths comment text to indicate the user and the modification date + if (state.selection.$from) { + nbef = findStartOfMark(state.selection.$from, view, findOtherUserMark); let naft = findEndOfMark(state.selection.$from, view, findOtherUserMark); - const spos = state.selection.$from.pos - nbef; - const epos = state.selection.$from.pos + naft; - let child = state.selection.$from.nodeBefore; - let mark = child && findOtherUserMark(child.marks); let noselection = view.state.selection.$from === view.state.selection.$to; + let child: any = null; + state.doc.nodesBetween(state.selection.from, state.selection.to, (node: any, pos: number, parent: any) => !child && node.marks.length && (child = node)); + let mark = child && findOtherUserMark(child.marks); if (mark && child && (nbef || naft) && (!mark.attrs.opened || noselection)) { - FormattedTextBoxComment.SetState(this, mark.attrs.opened, spos, epos, mark); + FormattedTextBoxComment.SetState(FormattedTextBoxComment.textBox, mark.attrs.opened, state.selection.$from.pos - nbef, state.selection.$from.pos + naft, mark); } - if (mark && child && nbef && naft) { - FormattedTextBoxComment.tooltipText.textContent = mark.attrs.userid + " " + mark.attrs.modified; - // These are in screen coordinates - // let start = view.coordsAtPos(state.selection.from), end = view.coordsAtPos(state.selection.to); - let start = view.coordsAtPos(state.selection.from - nbef), end = view.coordsAtPos(state.selection.from - nbef); - // The box in which the tooltip is positioned, to use as base - let box = (document.getElementById("main-div") as any).getBoundingClientRect(); - // Find a center-ish x position from the selection endpoints (when - // crossing lines, end may be more to the left) - let left = Math.max((start.left + end.left) / 2, start.left + 3); - FormattedTextBoxComment.tooltip.style.left = (left - box.left) + "px"; - FormattedTextBoxComment.tooltip.style.bottom = (box.bottom - start.top) + "px"; + if (mark && child && ((nbef && naft) || !noselection)) { + FormattedTextBoxComment.tooltipText.textContent = mark.attrs.userid + " date=" + (new Date(mark.attrs.modified * 5000)).toDateString(); set = ""; } } + // this checks if the selection is a hyperlink. If so, it displays the target doc's text for internal links, and the url of the target for external links. if (set === "none" && state.selection.$from) { - FormattedTextBoxComment.textBox = undefined; - let nbef = findStartOfMark(state.selection.$from, view, findLinkMark); + nbef = findStartOfMark(state.selection.$from, view, findLinkMark); let naft = findEndOfMark(state.selection.$from, view, findLinkMark); - let child = state.selection.$from.nodeBefore; + let child: any = null; + state.doc.nodesBetween(state.selection.from, state.selection.to, (node: any, pos: number, parent: any) => !child && node.marks.length && (child = node)); let mark = child && findLinkMark(child.marks); if (mark && child && nbef && naft) { - FormattedTextBoxComment.tooltipText.textContent = "link : " + (mark.attrs.title || mark.attrs.href); + FormattedTextBoxComment.tooltipText.textContent = "external => " + mark.attrs.href; if (mark.attrs.href.indexOf(Utils.prepend("/doc/")) === 0) { let docTarget = mark.attrs.href.replace(Utils.prepend("/doc/"), "").split("?")[0]; - docTarget && DocServer.GetRefField(docTarget).then(linkDoc => - (linkDoc as Doc) && (FormattedTextBoxComment.tooltipText.textContent = "link :" + StrCast((linkDoc as Doc).title))); + docTarget && DocServer.GetRefField(docTarget).then(linkDoc => { + if (linkDoc instanceof Doc) { + let target = FieldValue(Doc.AreProtosEqual(FieldValue(Cast(linkDoc.anchor1, Doc)), textBox.props.Document) ? Cast(linkDoc.anchor2, Doc) : Cast(linkDoc.anchor1, Doc)); + let ext = (target && Doc.fieldExtensionDoc(target, "data")) || target; // try guessing that the target doc's data is in the 'data' field. probably need an 'overviewLayout' and then just display the target Document .... + let text = ext && StrCast(ext.text); + ext && (FormattedTextBoxComment.tooltipText.textContent = "=> " + (text || StrCast(ext.title))); + } + }); } - // These are in screen coordinates - // let start = view.coordsAtPos(state.selection.from), end = view.coordsAtPos(state.selection.to); - let start = view.coordsAtPos(state.selection.from - nbef), end = view.coordsAtPos(state.selection.from - nbef); - // The box in which the tooltip is positioned, to use as base - let box = (document.getElementById("main-div") as any).getBoundingClientRect(); - // Find a center-ish x position from the selection endpoints (when - // crossing lines, end may be more to the left) - let left = Math.max((start.left + end.left) / 2, start.left + 3); - FormattedTextBoxComment.tooltip.style.left = (left - box.left) + "px"; - FormattedTextBoxComment.tooltip.style.bottom = (box.bottom - start.top) + "px"; set = ""; } } + if (set !== "none") { + // These are in screen coordinates + // let start = view.coordsAtPos(state.selection.from), end = view.coordsAtPos(state.selection.to); + let start = view.coordsAtPos(state.selection.from - nbef), end = view.coordsAtPos(state.selection.from - nbef); + // The box in which the tooltip is positioned, to use as base + let box = (document.getElementById("mainView-container") as any).getBoundingClientRect(); + // Find a center-ish x position from the selection endpoints (when + // crossing lines, end may be more to the left) + let left = Math.max((start.left + end.left) / 2, start.left + 3); + FormattedTextBoxComment.tooltip.style.left = (left - box.left) + "px"; + FormattedTextBoxComment.tooltip.style.bottom = (box.bottom - start.top) + "px"; + } FormattedTextBoxComment.tooltip && (FormattedTextBoxComment.tooltip.style.display = set); } diff --git a/src/client/views/nodes/PDFBox.scss b/src/client/views/nodes/PDFBox.scss index 963205206..2d92c9581 100644 --- a/src/client/views/nodes/PDFBox.scss +++ b/src/client/views/nodes/PDFBox.scss @@ -48,6 +48,7 @@ } .pdfViewer-text { .textLayer { + will-change: transform; span { user-select: none; } @@ -59,6 +60,7 @@ pointer-events: all; .pdfViewer-text { .textLayer { + will-change: transform; span { user-select: text; } diff --git a/src/client/views/nodes/PDFBox.tsx b/src/client/views/nodes/PDFBox.tsx index 3baa6eb09..8e0515f8a 100644 --- a/src/client/views/nodes/PDFBox.tsx +++ b/src/client/views/nodes/PDFBox.tsx @@ -1,5 +1,5 @@ import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; -import { action, observable, runInAction, reaction, IReactionDisposer, trace } from 'mobx'; +import { action, observable, runInAction, reaction, IReactionDisposer, trace, untracked, computed } from 'mobx'; import { observer } from "mobx-react"; import * as Pdfjs from "pdfjs-dist"; import "pdfjs-dist/web/pdf_viewer.css"; @@ -32,7 +32,7 @@ export class PDFBox extends DocAnnotatableComponent private _valueValue: string = ""; private _scriptValue: string = ""; private _searchString: string = ""; - private _initialScale: number | undefined; // the initial scale of the PDF when first rendered which determines whether the document will be live on startup or not. Getting bigger after startup won't make it automatically be live. + private _initialScale: number = 0; // the initial scale of the PDF when first rendered which determines whether the document will be live on startup or not. Getting bigger after startup won't make it automatically be live. private _everActive = false; // has this box ever had its contents activated -- if so, stop drawing the overlay title private _pdfViewer: PDFViewer | undefined; private _searchRef = React.createRef(); @@ -46,6 +46,11 @@ export class PDFBox extends DocAnnotatableComponent @observable private _pdf: Opt; @observable private _pageControls = false; + constructor(props: any) { + super(props); + this._initialScale = this.props.ScreenToLocalTransform().Scale; + } + componentWillUnmount() { this._selectReactionDisposer && this._selectReactionDisposer(); } @@ -190,39 +195,46 @@ export class PDFBox extends DocAnnotatableComponent ContextMenu.Instance.addItem({ description: "Pdf Funcs...", subitems: funcs, icon: "asterisk" }); } - render() { + @computed get renderTitleBox() { + let classname = "pdfBox-cont" + (this.active() ? "-interactive" : ""); + return
+
+ {` ${this.props.Document.title}`} +
+
; + } + + @computed get renderPdfView() { trace(); const pdfUrl = Cast(this.dataDoc[this.props.fieldKey], PdfField); let classname = "pdfBox-cont" + (this.active() ? "-interactive" : ""); - let noPdf = !(pdfUrl instanceof PdfField) || !this._pdf; - if (this._initialScale === undefined) this._initialScale = this.props.ScreenToLocalTransform().Scale; + return
{ + let hit = document.elementFromPoint(e.clientX, e.clientY); + if (hit && hit.localName === "span" && this.props.isSelected()) { // drag selecting text stops propagation + e.button === 0 && e.stopPropagation(); + } + }}> + + {this.settingsPanel()} +
; + } + + render() { + const pdfUrl = Cast(this.dataDoc[this.props.fieldKey], PdfField, null); if (this.props.isSelected() || this.props.Document.scrollY !== undefined) this._everActive = true; - return (!this.extensionDoc || noPdf || (!this._everActive && this.props.ScreenToLocalTransform().Scale > 2.5) ? -
-
- {` ${this.props.Document.title}`} -
-
: -
{ - let hit = document.elementFromPoint(e.clientX, e.clientY); - if (hit && hit.localName === "span" && this.props.isSelected()) { // drag selecting text stops propagation - e.button === 0 && e.stopPropagation(); - } - }}> - - {this.settingsPanel()} -
); + return !pdfUrl || !this._pdf || !this.extensionDoc || (!this._everActive && this.props.ScreenToLocalTransform().Scale > 2.5) ? + this.renderTitleBox : this.renderPdfView; } } \ No newline at end of file diff --git a/src/client/views/pdf/Annotation.tsx b/src/client/views/pdf/Annotation.tsx index 2d8f47666..936af9ab8 100644 --- a/src/client/views/pdf/Annotation.tsx +++ b/src/client/views/pdf/Annotation.tsx @@ -52,11 +52,7 @@ class RegionAnnotation extends React.Component { this._brushDisposer = reaction( () => FieldValue(Cast(this.props.document.group, Doc)) && Doc.isBrushedHighlightedDegree(FieldValue(Cast(this.props.document.group, Doc))!), - (brushed) => { - if (brushed !== undefined) { - runInAction(() => this._brushed = brushed !== 0); - } - } + brushed => brushed !== undefined && runInAction(() => this._brushed = brushed !== 0) ); } diff --git a/src/client/views/pdf/PDFViewer.tsx b/src/client/views/pdf/PDFViewer.tsx index 060ba8613..0cb671156 100644 --- a/src/client/views/pdf/PDFViewer.tsx +++ b/src/client/views/pdf/PDFViewer.tsx @@ -199,7 +199,7 @@ export class PDFViewer extends DocAnnotatableComponent this.extensionDoc && DocListCast(this.extensionDoc.annotations), - annotations => annotations && annotations.length && this.renderAnnotations(annotations, true), + annotations => annotations && annotations.length && (this._annotations = annotations), { fireImmediately: true }); this._filterReactionDisposer = reaction( @@ -297,18 +297,6 @@ export class PDFViewer extends DocAnnotatableComponent { - if (removeOldAnnotations) { - this._annotations = annotations; - } - else { - this._annotations.push(...annotations); - this._annotations = new Array(...this._annotations); - } - } - @action prevAnnotation = () => { this.Index = Math.max(this.Index - 1, 0); diff --git a/src/new_fields/Doc.ts b/src/new_fields/Doc.ts index 6aad4a6be..3bf1129b5 100644 --- a/src/new_fields/Doc.ts +++ b/src/new_fields/Doc.ts @@ -669,8 +669,8 @@ export namespace Doc { return doc; } - export function LinkEndpoint(linkDoc: Doc, anchorDoc: Doc) { return Doc.AreProtosEqual(anchorDoc, Cast(linkDoc.anchor1, Doc) as Doc) ? "layoutKey1" : "layoutKey2"; } + export function LinkEndpoint(linkDoc: Doc, anchorDoc: Doc) { return Doc.AreProtosEqual(anchorDoc, Cast(linkDoc.anchor1, Doc) as Doc) ? "layoutKey1" : "layoutKey2"; } export function linkFollowUnhighlight() { Doc.UnhighlightAll(); document.removeEventListener("pointerdown", linkFollowUnhighlight); diff --git a/src/server/authentication/models/current_user_utils.ts b/src/server/authentication/models/current_user_utils.ts index 56ea5bfe1..833e44bf6 100644 --- a/src/server/authentication/models/current_user_utils.ts +++ b/src/server/authentication/models/current_user_utils.ts @@ -15,7 +15,6 @@ import { RouteStore } from "../../RouteStore"; import { InkingControl } from "../../../client/views/InkingControl"; import { DragManager } from "../../../client/util/DragManager"; import { nullAudio } from "../../../new_fields/URLField"; -import { LinkManager } from "../../../client/util/LinkManager"; export class CurrentUserUtils { private static curr_id: string; -- cgit v1.2.3-70-g09d2 From ecbe527575bab2cb5f1ced278039ec0a6fc50809 Mon Sep 17 00:00:00 2001 From: bob Date: Tue, 12 Nov 2019 16:04:05 -0500 Subject: switched text hyper link following to go through pop-up comment box now. --- src/client/views/nodes/FormattedTextBox.tsx | 64 +++++++++++----------- src/client/views/nodes/FormattedTextBoxComment.tsx | 15 ++++- 2 files changed, 45 insertions(+), 34 deletions(-) (limited to 'src') diff --git a/src/client/views/nodes/FormattedTextBox.tsx b/src/client/views/nodes/FormattedTextBox.tsx index a0f8523a2..24d6f2509 100644 --- a/src/client/views/nodes/FormattedTextBox.tsx +++ b/src/client/views/nodes/FormattedTextBox.tsx @@ -884,38 +884,38 @@ export class FormattedTextBox extends DocExtendableComponent<(FieldViewProps & F onClick = (e: React.MouseEvent): void => { if ((e.nativeEvent as any).formattedHandled) { e.stopPropagation(); return; } (e.nativeEvent as any).formattedHandled = true; - if (e.button === 0 && ((!this.props.isSelected() && !e.ctrlKey) || (this.props.isSelected() && e.ctrlKey)) && !e.metaKey && e.target) { - let href = (e.target as any).href; - let location: string; - if ((e.target as any).attributes.location) { - location = (e.target as any).attributes.location.value; - } - let pcords = this._editorView!.posAtCoords({ left: e.clientX, top: e.clientY }); - let node = pcords && this._editorView!.state.doc.nodeAt(pcords.pos); - if (node) { - let link = node.marks.find(m => m.type === this._editorView!.state.schema.marks.link); - if (link && !(link.attrs.docref && link.attrs.title)) { // bcz: getting hacky. this indicates that we clicked on a PDF excerpt quotation. In this case, we don't want to follow the link (we follow only the actual hyperlink for the quotation which is handled above). - href = link && link.attrs.href; - location = link && link.attrs.location; - } - } - if (href) { - if (href.indexOf(Utils.prepend("/doc/")) === 0) { - let linkClicked = href.replace(Utils.prepend("/doc/"), "").split("?")[0]; - if (linkClicked) { - DocServer.GetRefField(linkClicked).then(async linkDoc => { - (linkDoc instanceof Doc) && - DocumentManager.Instance.FollowLink(linkDoc, this.props.Document, document => this.props.addDocTab(document, undefined, location ? location : "inTab"), false); - }); - } - } else { - let webDoc = Docs.Create.WebDocument(href, { x: NumCast(this.layoutDoc.x, 0) + NumCast(this.layoutDoc.width, 0), y: NumCast(this.layoutDoc.y) }); - this.props.addDocument && this.props.addDocument(webDoc); - } - e.stopPropagation(); - e.preventDefault(); - } - } + // if (e.button === 0 && ((!this.props.isSelected() && !e.ctrlKey) || (this.props.isSelected() && e.ctrlKey)) && !e.metaKey && e.target) { + // let href = (e.target as any).href; + // let location: string; + // if ((e.target as any).attributes.location) { + // location = (e.target as any).attributes.location.value; + // } + // let pcords = this._editorView!.posAtCoords({ left: e.clientX, top: e.clientY }); + // let node = pcords && this._editorView!.state.doc.nodeAt(pcords.pos); + // if (node) { + // let link = node.marks.find(m => m.type === this._editorView!.state.schema.marks.link); + // if (link && !(link.attrs.docref && link.attrs.title)) { // bcz: getting hacky. this indicates that we clicked on a PDF excerpt quotation. In this case, we don't want to follow the link (we follow only the actual hyperlink for the quotation which is handled above). + // href = link && link.attrs.href; + // location = link && link.attrs.location; + // } + // } + // if (href) { + // if (href.indexOf(Utils.prepend("/doc/")) === 0) { + // let linkClicked = href.replace(Utils.prepend("/doc/"), "").split("?")[0]; + // if (linkClicked) { + // DocServer.GetRefField(linkClicked).then(async linkDoc => { + // (linkDoc instanceof Doc) && + // DocumentManager.Instance.FollowLink(linkDoc, this.props.Document, document => this.props.addDocTab(document, undefined, location ? location : "inTab"), false); + // }); + // } + // } else { + // let webDoc = Docs.Create.WebDocument(href, { x: NumCast(this.layoutDoc.x, 0) + NumCast(this.layoutDoc.width, 0), y: NumCast(this.layoutDoc.y) }); + // this.props.addDocument && this.props.addDocument(webDoc); + // } + // e.stopPropagation(); + // e.preventDefault(); + // } + // } this.hitBulletTargets(e.clientX, e.clientY, e.nativeEvent.offsetX, e.shiftKey); if (this._recording) setTimeout(() => { this.stopDictation(true); setTimeout(() => this.recordDictation(), 500); }, 500); diff --git a/src/client/views/nodes/FormattedTextBoxComment.tsx b/src/client/views/nodes/FormattedTextBoxComment.tsx index 2a7467108..f35ca502f 100644 --- a/src/client/views/nodes/FormattedTextBoxComment.tsx +++ b/src/client/views/nodes/FormattedTextBoxComment.tsx @@ -9,6 +9,9 @@ import { Utils } from "../../../Utils"; import { StrCast, Cast, FieldValue } from "../../../new_fields/Types"; import { FormattedTextBox } from "./FormattedTextBox"; import { DocUtils } from "../../documents/Documents"; +import { isBuffer } from "util"; +import { DocumentManager } from "../../util/DocumentManager"; +import { DocumentType } from "../../documents/DocumentTypes"; export let formattedTextBoxCommentPlugin = new Plugin({ view(editorView) { return new FormattedTextBoxComment(editorView); } @@ -52,6 +55,7 @@ export class FormattedTextBoxComment { static mark: Mark; static opened: boolean; static textBox: FormattedTextBox | undefined; + static linkDoc: Doc | undefined; constructor(view: any) { if (!FormattedTextBoxComment.tooltip) { const root = document.getElementById("root"); @@ -66,10 +70,15 @@ export class FormattedTextBoxComment { FormattedTextBoxComment.tooltip.appendChild(input); FormattedTextBoxComment.tooltip.onpointerdown = (e: PointerEvent) => { let keep = e.target && (e.target as any).type === "checkbox" ? true : false; + if (FormattedTextBoxComment.linkDoc && !keep && FormattedTextBoxComment.textBox) { + DocumentManager.Instance.FollowLink(FormattedTextBoxComment.linkDoc, FormattedTextBoxComment.textBox.props.Document, + (doc: Doc, maxLocation: string) => FormattedTextBoxComment.textBox!.props.addDocTab(doc, undefined, e.ctrlKey ? "inTab" : "onRight")); + } FormattedTextBoxComment.opened = keep || !FormattedTextBoxComment.opened; FormattedTextBoxComment.textBox && FormattedTextBoxComment.start !== undefined && FormattedTextBoxComment.textBox.setAnnotation( FormattedTextBoxComment.start, FormattedTextBoxComment.end, FormattedTextBoxComment.mark, FormattedTextBoxComment.opened, keep); + e.stopPropagation(); }; root && root.appendChild(FormattedTextBoxComment.tooltip); } @@ -96,6 +105,7 @@ export class FormattedTextBoxComment { lastState.selection.eq(state.selection)) { return; } + FormattedTextBoxComment.linkDoc = undefined; const textBox = FormattedTextBoxComment.textBox; if (!textBox || !textBox.props) { @@ -132,10 +142,11 @@ export class FormattedTextBoxComment { let docTarget = mark.attrs.href.replace(Utils.prepend("/doc/"), "").split("?")[0]; docTarget && DocServer.GetRefField(docTarget).then(linkDoc => { if (linkDoc instanceof Doc) { + FormattedTextBoxComment.linkDoc = linkDoc; let target = FieldValue(Doc.AreProtosEqual(FieldValue(Cast(linkDoc.anchor1, Doc)), textBox.props.Document) ? Cast(linkDoc.anchor2, Doc) : Cast(linkDoc.anchor1, Doc)); - let ext = (target && Doc.fieldExtensionDoc(target, "data")) || target; // try guessing that the target doc's data is in the 'data' field. probably need an 'overviewLayout' and then just display the target Document .... + let ext = (target && target.type !== DocumentType.PDFANNO && Doc.fieldExtensionDoc(target, "data")) || target; // try guessing that the target doc's data is in the 'data' field. probably need an 'overviewLayout' and then just display the target Document .... let text = ext && StrCast(ext.text); - ext && (FormattedTextBoxComment.tooltipText.textContent = "=> " + (text || StrCast(ext.title))); + ext && (FormattedTextBoxComment.tooltipText.textContent = (target && target.type === DocumentType.PDFANNO ? "Quoted from " : "") + "=> " + (text || StrCast(ext.title))); } }); } -- cgit v1.2.3-70-g09d2 From 76411ba009c4265751431d255ab3cc9a3cfab69f Mon Sep 17 00:00:00 2001 From: Bob Zeleznik Date: Tue, 12 Nov 2019 21:28:39 -0500 Subject: switched link preview to be a document view --- src/client/util/RichTextSchema.tsx | 1 - .../views/nodes/ContentFittingDocumentView.tsx | 2 + src/client/views/nodes/DocumentView.tsx | 6 ++- src/client/views/nodes/FormattedTextBoxComment.tsx | 62 +++++++++++++++++----- 4 files changed, 54 insertions(+), 17 deletions(-) (limited to 'src') diff --git a/src/client/util/RichTextSchema.tsx b/src/client/util/RichTextSchema.tsx index 76b8aeaa1..1004cb3d4 100644 --- a/src/client/util/RichTextSchema.tsx +++ b/src/client/util/RichTextSchema.tsx @@ -18,7 +18,6 @@ import { Transform } from "./Transform"; import React = require("react"); import { BoolCast, NumCast } from "../../new_fields/Types"; import { FormattedTextBox } from "../views/nodes/FormattedTextBox"; -import { CurrentUserUtils } from "../../server/authentication/models/current_user_utils"; const pDOM: DOMOutputSpecArray = ["p", 0], blockquoteDOM: DOMOutputSpecArray = ["blockquote", 0], hrDOM: DOMOutputSpecArray = ["hr"], preDOM: DOMOutputSpecArray = ["pre", ["code", 0]], brDOM: DOMOutputSpecArray = ["br"], ulDOM: DOMOutputSpecArray = ["ul", 0]; diff --git a/src/client/views/nodes/ContentFittingDocumentView.tsx b/src/client/views/nodes/ContentFittingDocumentView.tsx index f41c4fc91..751355403 100644 --- a/src/client/views/nodes/ContentFittingDocumentView.tsx +++ b/src/client/views/nodes/ContentFittingDocumentView.tsx @@ -37,6 +37,7 @@ interface ContentFittingDocumentViewProps { whenActiveChanged: (isActive: boolean) => void; addDocTab: (document: Doc, dataDoc: Doc | undefined, where: string) => boolean; pinToPres: (document: Doc) => void; + dontRegisterView?: boolean; setPreviewScript: (script: string) => void; previewScript?: string; } @@ -108,6 +109,7 @@ export class ContentFittingDocumentView extends React.Component diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx index 93052ea73..4fbb0516f 100644 --- a/src/client/views/nodes/DocumentView.tsx +++ b/src/client/views/nodes/DocumentView.tsx @@ -74,6 +74,7 @@ export interface DocumentViewProps { getScale: () => number; animateBetweenIcon?: (maximize: boolean, target: number[]) => void; ChromeHeight?: () => number; + dontRegisterView?: boolean; layoutKey?: string; } @@ -98,7 +99,8 @@ export class DocumentView extends DocComponent(Docu @action componentDidMount() { this._mainCont.current && (this._dropDisposer = DragManager.MakeDropTarget(this._mainCont.current, { handlers: { drop: this.drop.bind(this) } })); - DocumentManager.Instance.DocumentViews.push(this); + + !this.props.dontRegisterView && DocumentManager.Instance.DocumentViews.push(this); } @action @@ -111,7 +113,7 @@ export class DocumentView extends DocComponent(Docu componentWillUnmount() { this._dropDisposer && this._dropDisposer(); Doc.UnBrushDoc(this.props.Document); - DocumentManager.Instance.DocumentViews.splice(DocumentManager.Instance.DocumentViews.indexOf(this), 1); + !this.props.dontRegisterView && DocumentManager.Instance.DocumentViews.splice(DocumentManager.Instance.DocumentViews.indexOf(this), 1); } startDragging(x: number, y: number, dropAction: dropActionType, applyAsTemplate?: boolean) { diff --git a/src/client/views/nodes/FormattedTextBoxComment.tsx b/src/client/views/nodes/FormattedTextBoxComment.tsx index f35ca502f..98cd17972 100644 --- a/src/client/views/nodes/FormattedTextBoxComment.tsx +++ b/src/client/views/nodes/FormattedTextBoxComment.tsx @@ -2,16 +2,19 @@ import { Plugin, EditorState } from "prosemirror-state"; import './FormattedTextBoxComment.scss'; import { ResolvedPos, Mark } from "prosemirror-model"; import { EditorView } from "prosemirror-view"; -import { Doc } from "../../../new_fields/Doc"; +import { Doc, WidthSym } from "../../../new_fields/Doc"; import { schema } from "../../util/RichTextSchema"; import { DocServer } from "../../DocServer"; -import { Utils } from "../../../Utils"; -import { StrCast, Cast, FieldValue } from "../../../new_fields/Types"; +import { Utils, returnTrue, returnFalse, emptyFunction, returnEmptyString, returnOne } from "../../../Utils"; +import { StrCast, Cast, FieldValue, NumCast } from "../../../new_fields/Types"; import { FormattedTextBox } from "./FormattedTextBox"; -import { DocUtils } from "../../documents/Documents"; -import { isBuffer } from "util"; import { DocumentManager } from "../../util/DocumentManager"; import { DocumentType } from "../../documents/DocumentTypes"; +import { DocumentView } from "./DocumentView"; +import React = require("react"); +import * as ReactDOM from 'react-dom'; +import { Transform } from "../../util/Transform"; +import { ContentFittingDocumentView } from "./ContentFittingDocumentView"; export let formattedTextBoxCommentPlugin = new Plugin({ view(editorView) { return new FormattedTextBoxComment(editorView); } @@ -67,22 +70,28 @@ export class FormattedTextBoxComment { FormattedTextBoxComment.tooltip.className = "FormattedTextBox-tooltip"; FormattedTextBoxComment.tooltip.style.pointerEvents = "all"; FormattedTextBoxComment.tooltip.style.maxWidth = "350px"; + FormattedTextBoxComment.tooltip.style.maxHeight = "250px"; + FormattedTextBoxComment.tooltip.style.width = "100%"; + FormattedTextBoxComment.tooltip.style.height = "100%"; + FormattedTextBoxComment.tooltip.style.overflow = "hidden"; + FormattedTextBoxComment.tooltip.style.display = "none"; FormattedTextBoxComment.tooltip.appendChild(input); FormattedTextBoxComment.tooltip.onpointerdown = (e: PointerEvent) => { let keep = e.target && (e.target as any).type === "checkbox" ? true : false; - if (FormattedTextBoxComment.linkDoc && !keep && FormattedTextBoxComment.textBox) { - DocumentManager.Instance.FollowLink(FormattedTextBoxComment.linkDoc, FormattedTextBoxComment.textBox.props.Document, - (doc: Doc, maxLocation: string) => FormattedTextBoxComment.textBox!.props.addDocTab(doc, undefined, e.ctrlKey ? "inTab" : "onRight")); + const textBox = FormattedTextBoxComment.textBox; + if (FormattedTextBoxComment.linkDoc && !keep && textBox) { + DocumentManager.Instance.FollowLink(FormattedTextBoxComment.linkDoc, textBox.props.Document, + (doc: Doc, maxLocation: string) => textBox.props.addDocTab(doc, undefined, e.ctrlKey ? "inTab" : "onRight")); } FormattedTextBoxComment.opened = keep || !FormattedTextBoxComment.opened; - FormattedTextBoxComment.textBox && FormattedTextBoxComment.start !== undefined && FormattedTextBoxComment.textBox.setAnnotation( + textBox && FormattedTextBoxComment.start !== undefined && textBox.setAnnotation( FormattedTextBoxComment.start, FormattedTextBoxComment.end, FormattedTextBoxComment.mark, FormattedTextBoxComment.opened, keep); e.stopPropagation(); }; root && root.appendChild(FormattedTextBoxComment.tooltip); } - this.update(view, undefined); + //this.update(view, undefined); } public static Hide() { @@ -144,9 +153,34 @@ export class FormattedTextBoxComment { if (linkDoc instanceof Doc) { FormattedTextBoxComment.linkDoc = linkDoc; let target = FieldValue(Doc.AreProtosEqual(FieldValue(Cast(linkDoc.anchor1, Doc)), textBox.props.Document) ? Cast(linkDoc.anchor2, Doc) : Cast(linkDoc.anchor1, Doc)); - let ext = (target && target.type !== DocumentType.PDFANNO && Doc.fieldExtensionDoc(target, "data")) || target; // try guessing that the target doc's data is in the 'data' field. probably need an 'overviewLayout' and then just display the target Document .... - let text = ext && StrCast(ext.text); - ext && (FormattedTextBoxComment.tooltipText.textContent = (target && target.type === DocumentType.PDFANNO ? "Quoted from " : "") + "=> " + (text || StrCast(ext.title))); + try { + ReactDOM.unmountComponentAtNode(FormattedTextBoxComment.tooltipText); + } catch (e) { + + } + target && ReactDOM.render( 350} + PanelHeight={() => 250} + focus={emptyFunction} + whenActiveChanged={returnFalse} + />, FormattedTextBoxComment.tooltipText); + // let ext = (target && target.type !== DocumentType.PDFANNO && Doc.fieldExtensionDoc(target, "data")) || target; // try guessing that the target doc's data is in the 'data' field. probably need an 'overviewLayout' and then just display the target Document .... + // let text = ext && StrCast(ext.text); + // ext && (FormattedTextBoxComment.tooltipText.textContent = (target && target.type === DocumentType.PDFANNO ? "Quoted from " : "") + "=> " + (text || StrCast(ext.title))); } }); } @@ -168,5 +202,5 @@ export class FormattedTextBoxComment { FormattedTextBoxComment.tooltip && (FormattedTextBoxComment.tooltip.style.display = set); } - destroy() { FormattedTextBoxComment.tooltip.style.display = "none"; } + destroy() { }//FormattedTextBoxComment.tooltip.style.display = "none"; } } -- cgit v1.2.3-70-g09d2 From e1930a62304db60d9eca542148995c5d03b79aea Mon Sep 17 00:00:00 2001 From: bob Date: Wed, 13 Nov 2019 11:44:52 -0500 Subject: fixed search on client/server to work with richTextFields. got rid of 'text' field on richtextfield extensions. --- .vscode/settings.json | 3 ++- src/client/views/DocComponent.tsx | 2 +- src/client/views/nodes/DocumentView.tsx | 8 ++++---- src/client/views/nodes/FormattedTextBox.tsx | 18 ++---------------- src/client/views/search/FilterBox.tsx | 2 +- src/new_fields/Doc.ts | 5 +++-- src/new_fields/RichTextField.ts | 10 +++++++--- src/new_fields/RichTextUtils.ts | 2 +- src/server/index.ts | 1 + 9 files changed, 22 insertions(+), 29 deletions(-) (limited to 'src') diff --git a/.vscode/settings.json b/.vscode/settings.json index 5df697fee..c999af8b8 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -10,5 +10,6 @@ "editor.detectIndentation": false, "typescript.format.insertSpaceAfterOpeningAndBeforeClosingNonemptyBrackets": false, "typescript.format.insertSpaceAfterOpeningAndBeforeClosingNonemptyBraces": true, - "search.usePCRE2": true + "search.usePCRE2": true, + "typescript.tsdk": "node_modules\\typescript\\lib" } \ No newline at end of file diff --git a/src/client/views/DocComponent.tsx b/src/client/views/DocComponent.tsx index ae4b7cf3a..92c947fe6 100644 --- a/src/client/views/DocComponent.tsx +++ b/src/client/views/DocComponent.tsx @@ -34,7 +34,7 @@ export function DocExtendableComponent

(schemaCt //TODO This might be pretty inefficient if doc isn't observed, because computed doesn't cache then @computed get Document(): T { return schemaCtor(this.props.Document); } @computed get layoutDoc() { return Doc.Layout(this.props.Document); } - @computed get dataDoc() { return (this.props.DataDoc && this.props.Document.isTemplateField ? this.props.DataDoc : Doc.GetProto(this.props.Document)) as Doc; } + @computed get dataDoc() { return (this.props.DataDoc && (this.props.Document.isTemplateField || this.props.Document.isTemplateDoc) ? this.props.DataDoc : Doc.GetProto(this.props.Document)) as Doc; } @computed get extensionDoc() { return Doc.fieldExtensionDoc(this.dataDoc, this.props.fieldKey); } active = () => !this.props.Document.isBackground && (this.props.Document.forceActive || this.props.isSelected() || this.props.renderDepth === 0);// && !InkingControl.Instance.selectedTool; // bcz: inking state shouldn't affect static tools } diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx index 4fbb0516f..7e81cd673 100644 --- a/src/client/views/nodes/DocumentView.tsx +++ b/src/client/views/nodes/DocumentView.tsx @@ -138,7 +138,7 @@ export class DocumentView extends DocComponent(Docu (Math.abs(e.clientX - this._downX) < Utils.DRAG_THRESHOLD && Math.abs(e.clientY - this._downY) < Utils.DRAG_THRESHOLD)) { e.stopPropagation(); let preventDefault = true; - if (this._doubleTap && this.props.renderDepth && (!this.onClickHandler || !this.onClickHandler.script)) { // disable double-click to show full screen for things that have an on click behavior since clicking them twice can be misinterpreted as a double click + if (this._doubleTap && this.props.renderDepth && !this.onClickHandler?.script) { // disable double-click to show full screen for things that have an on click behavior since clicking them twice can be misinterpreted as a double click let fullScreenAlias = Doc.MakeAlias(this.props.Document); if (StrCast(fullScreenAlias.layoutKey) !== "layoutCustom" && fullScreenAlias.layoutCustom !== undefined) { fullScreenAlias.layoutKey = "layoutCustom"; @@ -352,9 +352,9 @@ export class DocumentView extends DocComponent(Docu @undoBatch @action setCustomView = (custom: boolean): void => { - if (this.props.ContainingCollectionView && this.props.ContainingCollectionView.props.DataDoc) { - Doc.MakeMetadataFieldTemplate(this.props.Document, this.props.ContainingCollectionView.props.DataDoc); - } else { // bcz: not robust -- for now documents with string layout are native documents, and those with Doc layouts are customized + if (this.props.ContainingCollectionView?.props.DataDoc || this.props.ContainingCollectionView?.props.Document.isTemplateDoc) { + Doc.MakeMetadataFieldTemplate(this.props.Document, this.props.ContainingCollectionView.props.Document); + } else { custom ? DocumentView.makeCustomViewClicked(this.props.Document, this.props.DataDoc) : DocumentView.makeNativeViewClicked(this.props.Document); } } diff --git a/src/client/views/nodes/FormattedTextBox.tsx b/src/client/views/nodes/FormattedTextBox.tsx index 24d6f2509..65a51b357 100644 --- a/src/client/views/nodes/FormattedTextBox.tsx +++ b/src/client/views/nodes/FormattedTextBox.tsx @@ -85,7 +85,6 @@ export class FormattedTextBox extends DocExtendableComponent<(FieldViewProps & F private _searchReactionDisposer?: Lambda; private _scrollToRegionReactionDisposer: Opt; private _reactionDisposer: Opt; - private _textReactionDisposer: Opt; private _heightReactionDisposer: Opt; private _rulesReactionDisposer: Opt; private _proxyReactionDisposer: Opt; @@ -191,9 +190,8 @@ export class FormattedTextBox extends DocExtendableComponent<(FieldViewProps & F let tsel = this._editorView.state.selection.$from; tsel.marks().filter(m => m.type === this._editorView!.state.schema.marks.user_mark).map(m => AudioBox.SetScrubTime(Math.max(0, m.attrs.modified * 5000 - 1000))); this._applyingChange = true; - this.extensionDoc && (this.extensionDoc.text = state.doc.textBetween(0, state.doc.content.size, "\n\n")); this.extensionDoc && (this.extensionDoc.lastModified = new DateField(new Date(Date.now()))); - this.dataDoc[this.props.fieldKey] = new RichTextField(JSON.stringify(state.toJSON())); + this.dataDoc[this.props.fieldKey] = new RichTextField(JSON.stringify(state.toJSON()), state.doc.textBetween(0, state.doc.content.size, "\n\n")); this._applyingChange = false; this.updateTitle(); this.tryUpdateHeight(); @@ -250,7 +248,7 @@ export class FormattedTextBox extends DocExtendableComponent<(FieldViewProps & F // replace text contents whend dragging with Alt if (draggedDoc && draggedDoc.type === DocumentType.TEXT && !Doc.AreProtosEqual(draggedDoc, this.props.Document) && de.mods === "AltKey") { if (draggedDoc.data instanceof RichTextField) { - Doc.GetProto(this.dataDoc)[this.props.fieldKey] = new RichTextField(draggedDoc.data.Data); + Doc.GetProto(this.dataDoc)[this.props.fieldKey] = new RichTextField(draggedDoc.data.Data, draggedDoc.data.Text); e.stopPropagation(); } // apply as template when dragging with Meta @@ -510,17 +508,6 @@ export class FormattedTextBox extends DocExtendableComponent<(FieldViewProps & F () => this.tryUpdateHeight() ); - this._textReactionDisposer = reaction( - () => this.extensionDoc, - () => { - if (this.extensionDoc && (this.dataDoc.text || this.dataDoc.lastModified)) { - this.extensionDoc.text = this.dataDoc.text; - this.extensionDoc.lastModified = DateCast(this.dataDoc.lastModified)[Copy](); - this.dataDoc.text = undefined; - this.dataDoc.lastModified = undefined; - } - }, { fireImmediately: true }); - this.setupEditor(this.config, this.dataDoc, this.props.fieldKey); @@ -834,7 +821,6 @@ export class FormattedTextBox extends DocExtendableComponent<(FieldViewProps & F this._rulesReactionDisposer && this._rulesReactionDisposer(); this._reactionDisposer && this._reactionDisposer(); this._proxyReactionDisposer && this._proxyReactionDisposer(); - this._textReactionDisposer && this._textReactionDisposer(); this._pushReactionDisposer && this._pushReactionDisposer(); this._pullReactionDisposer && this._pullReactionDisposer(); this._heightReactionDisposer && this._heightReactionDisposer(); diff --git a/src/client/views/search/FilterBox.tsx b/src/client/views/search/FilterBox.tsx index b841190d4..6e7e4ee1b 100644 --- a/src/client/views/search/FilterBox.tsx +++ b/src/client/views/search/FilterBox.tsx @@ -33,7 +33,7 @@ export enum Keys { export class FilterBox extends React.Component { static Instance: FilterBox; - public _allIcons: string[] = [DocumentType.AUDIO, DocumentType.COL, DocumentType.IMG, DocumentType.LINK, DocumentType.PDF, DocumentType.TEXT, DocumentType.VID, DocumentType.WEB]; + public _allIcons: string[] = [DocumentType.AUDIO, DocumentType.COL, DocumentType.IMG, DocumentType.LINK, DocumentType.PDF, DocumentType.TEXT, DocumentType.VID, DocumentType.WEB, DocumentType.TEMPLATE]; //if true, any keywords can be used. if false, all keywords are required. //this also serves as an indicator if the word status filter is applied diff --git a/src/new_fields/Doc.ts b/src/new_fields/Doc.ts index 3bf1129b5..4531fd5e0 100644 --- a/src/new_fields/Doc.ts +++ b/src/new_fields/Doc.ts @@ -473,8 +473,9 @@ export namespace Doc { export function CreateDocumentExtensionForField(doc: Doc, fieldKey: string) { let docExtensionForField = new Doc(doc[Id] + fieldKey, true); - docExtensionForField.title = fieldKey + ".ext"; + docExtensionForField.title = fieldKey + ".ext"; // courtesy field--- shouldn't be needed except maybe for debugging docExtensionForField.extendsDoc = doc; // this is used by search to map field matches on the extension doc back to the document it extends. + docExtensionForField.extendsField = fieldKey; // this can be used by search to map matches on the extension doc back to the field that was extended. docExtensionForField.type = DocumentType.EXTENSION; let proto: Doc | undefined = doc; while (proto && !Doc.IsPrototype(proto) && proto.proto) { @@ -568,7 +569,7 @@ export namespace Doc { let layoutCustomLayout = Doc.MakeDelegate(templateDoc); titleTarget && (Doc.GetProto(target).title = titleTarget); - target.type = DocumentType.TEMPLATE; + Doc.GetProto(target).type = DocumentType.TEMPLATE; target.onClick = templateDoc.onClick instanceof ObjectField && templateDoc.onClick[Copy](); Doc.GetProto(target)[targetKey] = layoutCustomLayout; diff --git a/src/new_fields/RichTextField.ts b/src/new_fields/RichTextField.ts index d2f76c969..fd5459876 100644 --- a/src/new_fields/RichTextField.ts +++ b/src/new_fields/RichTextField.ts @@ -10,17 +10,21 @@ export class RichTextField extends ObjectField { @serializable(true) readonly Data: string; - constructor(data: string) { + @serializable(true) + readonly Text: string; + + constructor(data: string, text: string = "") { super(); this.Data = data; + this.Text = text; } [Copy]() { - return new RichTextField(this.Data); + return new RichTextField(this.Data, this.Text); } [ToScriptString]() { - return `new RichTextField("${this.Data}")`; + return `new RichTextField("${this.Data}", "${this.Text}")`; } } \ No newline at end of file diff --git a/src/new_fields/RichTextUtils.ts b/src/new_fields/RichTextUtils.ts index 601939ed2..c2cca859c 100644 --- a/src/new_fields/RichTextUtils.ts +++ b/src/new_fields/RichTextUtils.ts @@ -52,7 +52,7 @@ export namespace RichTextUtils { }; export const Synthesize = (plainText: string, oldState?: RichTextField) => { - return new RichTextField(ToProsemirrorState(plainText, oldState)); + return new RichTextField(ToProsemirrorState(plainText, oldState), plainText); }; export const ToPlainText = (state: EditorState) => { diff --git a/src/server/index.ts b/src/server/index.ts index 1595781dc..ddd909479 100644 --- a/src/server/index.ts +++ b/src/server/index.ts @@ -1163,6 +1163,7 @@ const suffixMap: { [type: string]: (string | [string, string | ((json: any) => a "pdf": ["_t", "url"], "audio": ["_t", "url"], "web": ["_t", "url"], + "RichTextField": ["_t", value => value.Text], "date": ["_d", value => new Date(value.date).toISOString()], "proxy": ["_i", "fieldId"], "list": ["_l", list => { -- cgit v1.2.3-70-g09d2 From a93ee99c9dd3b5ecc5160f98f9dffeb119b4dcce Mon Sep 17 00:00:00 2001 From: bob Date: Wed, 13 Nov 2019 13:46:23 -0500 Subject: fixed recentlyClosed on startup and added clearAll. fixed textbox comment layout a bit. --- src/client/util/SearchUtil.ts | 7 ++- .../views/collections/CollectionStackingView.tsx | 1 - .../views/collections/CollectionTreeView.tsx | 13 ++-- .../views/nodes/ContentFittingDocumentView.tsx | 1 - src/client/views/nodes/FormattedTextBox.tsx | 5 +- .../views/nodes/FormattedTextBoxComment.scss | 1 - src/client/views/nodes/FormattedTextBoxComment.tsx | 71 +++++++++++----------- .../views/presentationview/PresElementBox.tsx | 1 - .../authentication/models/current_user_utils.ts | 2 + 9 files changed, 56 insertions(+), 46 deletions(-) (limited to 'src') diff --git a/src/client/util/SearchUtil.ts b/src/client/util/SearchUtil.ts index 6706dcb89..2cf13680a 100644 --- a/src/client/util/SearchUtil.ts +++ b/src/client/util/SearchUtil.ts @@ -28,6 +28,7 @@ export namespace SearchUtil { start?: number; rows?: number; fq?: string; + allowAliases?: boolean; } export function Search(query: string, returnDocs: true, options?: SearchParams): Promise; export function Search(query: string, returnDocs: false, options?: SearchParams): Promise; @@ -73,7 +74,7 @@ export namespace SearchUtil { const docs = ids.map((id: string) => docMap[id]).map(doc => doc as Doc); for (let i = 0; i < ids.length; i++) { let testDoc = docs[i]; - if (testDoc instanceof Doc && testDoc.type !== DocumentType.KVP && theDocs.findIndex(d => Doc.AreProtosEqual(d, testDoc)) === -1) { + if (testDoc instanceof Doc && testDoc.type !== DocumentType.KVP && (options.allowAliases || theDocs.findIndex(d => Doc.AreProtosEqual(d, testDoc)) === -1)) { theDocs.push(testDoc); theLines.push([]); } @@ -88,9 +89,9 @@ export namespace SearchUtil { const proto = Doc.GetProto(doc); const protoId = proto[Id]; if (returnDocs) { - return (await Search("", returnDocs, { fq: `proto_i:"${protoId}"` })).docs; + return (await Search("", returnDocs, { fq: `proto_i:"${protoId}"`, allowAliases: true })).docs; } else { - return (await Search("", returnDocs, { fq: `proto_i:"${protoId}"` })).ids; + return (await Search("", returnDocs, { fq: `proto_i:"${protoId}"`, allowAliases: true })).ids; } // return Search(`{!join from=id to=proto_i}id:${protoId}`, true); } diff --git a/src/client/views/collections/CollectionStackingView.tsx b/src/client/views/collections/CollectionStackingView.tsx index 15033e51a..be3bfca0a 100644 --- a/src/client/views/collections/CollectionStackingView.tsx +++ b/src/client/views/collections/CollectionStackingView.tsx @@ -168,7 +168,6 @@ export class CollectionStackingView extends CollectionSubView(doc => doc) { return { />) onWorkspaceContextMenu = (e: React.MouseEvent): void => { - if (!e.isPropagationStopped()) { // need to test this because GoldenLayout causes a parallel hierarchy in the React DOM for its children and the main document view7 - if (NumCast(this.props.document.viewType) !== CollectionViewType.Docking && this.props.document !== CurrentUserUtils.UserDocument.workspaces) { + if (!e.isPropagationStopped()) { // need to test this because GoldenLayout causes a parallel hierarchy in the React DOM for its children and the main document view + if (this.props.document === CurrentUserUtils.UserDocument.recentlyClosed) { + ContextMenu.Instance.addItem({ description: "Clear All", event: () => Doc.GetProto(CurrentUserUtils.UserDocument.recentlyClosed as Doc).data = new List(), icon: "plus" }); + } else if (this.props.document !== CurrentUserUtils.UserDocument.workspaces) { ContextMenu.Instance.addItem({ description: "Pin to Presentation", event: () => this.props.pinToPres(this.props.document), icon: "tv" }); ContextMenu.Instance.addItem({ description: "Open Tab", event: () => this.props.addDocTab(this.props.document, this.templateDataDoc, "inTab"), icon: "folder" }); ContextMenu.Instance.addItem({ description: "Open Right", event: () => this.props.addDocTab(this.props.document, this.templateDataDoc, "onRight"), icon: "caret-square-right" }); @@ -317,7 +318,6 @@ class TreeView extends React.Component { CurrentUserUtils.UserDocument.recentlyClosed = new List(), icon: "plus" }); + e.stopPropagation(); + e.preventDefault(); + ContextMenu.Instance.displayMenu(e.pageX - 15, e.pageY - 15); } else { let layoutItems: ContextMenuProps[] = []; layoutItems.push({ description: this.props.Document.preventTreeViewOpen ? "Persist Treeview State" : "Abandon Treeview State", event: () => this.props.Document.preventTreeViewOpen = !this.props.Document.preventTreeViewOpen, icon: "paint-brush" }); diff --git a/src/client/views/nodes/ContentFittingDocumentView.tsx b/src/client/views/nodes/ContentFittingDocumentView.tsx index 751355403..c8255b6fe 100644 --- a/src/client/views/nodes/ContentFittingDocumentView.tsx +++ b/src/client/views/nodes/ContentFittingDocumentView.tsx @@ -20,7 +20,6 @@ interface ContentFittingDocumentViewProps { childDocs?: Doc[]; renderDepth: number; fitToBox?: boolean; - fieldKey: string; PanelWidth: () => number; PanelHeight: () => number; ruleProvider: Doc | undefined; diff --git a/src/client/views/nodes/FormattedTextBox.tsx b/src/client/views/nodes/FormattedTextBox.tsx index 65a51b357..8b1e65663 100644 --- a/src/client/views/nodes/FormattedTextBox.tsx +++ b/src/client/views/nodes/FormattedTextBox.tsx @@ -843,7 +843,10 @@ export class FormattedTextBox extends DocExtendableComponent<(FieldViewProps & F } onPointerUp = (e: React.PointerEvent): void => { - if (!(e.nativeEvent as any).formattedHandled) { FormattedTextBoxComment.textBox = this; } + if (!(e.nativeEvent as any).formattedHandled) { + FormattedTextBoxComment.textBox = this; + FormattedTextBoxComment.update(this._editorView!); + } (e.nativeEvent as any).formattedHandled = true; if (e.buttons === 1 && this.props.isSelected() && !e.altKey) { diff --git a/src/client/views/nodes/FormattedTextBoxComment.scss b/src/client/views/nodes/FormattedTextBoxComment.scss index 792cee182..2dd63ec21 100644 --- a/src/client/views/nodes/FormattedTextBoxComment.scss +++ b/src/client/views/nodes/FormattedTextBoxComment.scss @@ -5,7 +5,6 @@ background: white; border: 1px solid silver; border-radius: 2px; - padding: 2px 10px; margin-bottom: 7px; -webkit-transform: translateX(-50%); transform: translateX(-50%); diff --git a/src/client/views/nodes/FormattedTextBoxComment.tsx b/src/client/views/nodes/FormattedTextBoxComment.tsx index 98cd17972..29b4b6383 100644 --- a/src/client/views/nodes/FormattedTextBoxComment.tsx +++ b/src/client/views/nodes/FormattedTextBoxComment.tsx @@ -1,20 +1,18 @@ -import { Plugin, EditorState } from "prosemirror-state"; -import './FormattedTextBoxComment.scss'; -import { ResolvedPos, Mark } from "prosemirror-model"; +import { Mark, ResolvedPos } from "prosemirror-model"; +import { EditorState, Plugin } from "prosemirror-state"; import { EditorView } from "prosemirror-view"; -import { Doc, WidthSym } from "../../../new_fields/Doc"; -import { schema } from "../../util/RichTextSchema"; +import * as ReactDOM from 'react-dom'; +import { Doc } from "../../../new_fields/Doc"; +import { Cast, FieldValue, NumCast } from "../../../new_fields/Types"; +import { emptyFunction, returnEmptyString, returnFalse, Utils } from "../../../Utils"; import { DocServer } from "../../DocServer"; -import { Utils, returnTrue, returnFalse, emptyFunction, returnEmptyString, returnOne } from "../../../Utils"; -import { StrCast, Cast, FieldValue, NumCast } from "../../../new_fields/Types"; -import { FormattedTextBox } from "./FormattedTextBox"; import { DocumentManager } from "../../util/DocumentManager"; -import { DocumentType } from "../../documents/DocumentTypes"; -import { DocumentView } from "./DocumentView"; -import React = require("react"); -import * as ReactDOM from 'react-dom'; +import { schema } from "../../util/RichTextSchema"; import { Transform } from "../../util/Transform"; import { ContentFittingDocumentView } from "./ContentFittingDocumentView"; +import { FormattedTextBox } from "./FormattedTextBox"; +import './FormattedTextBoxComment.scss'; +import React = require("react"); export let formattedTextBoxCommentPlugin = new Plugin({ view(editorView) { return new FormattedTextBoxComment(editorView); } @@ -107,7 +105,7 @@ export class FormattedTextBoxComment { FormattedTextBoxComment.tooltip && (FormattedTextBoxComment.tooltip.style.display = ""); } - update(view: EditorView, lastState?: EditorState) { + static update(view: EditorView, lastState?: EditorState) { let state = view.state; // Don't do anything if the document/selection didn't change if (lastState && lastState.doc.eq(state.doc) && @@ -122,6 +120,8 @@ export class FormattedTextBoxComment { } let set = "none"; let nbef = 0; + FormattedTextBoxComment.tooltip.style.width = ""; + FormattedTextBoxComment.tooltip.style.height = ""; // this section checks to see if the insertion point is over text entered by a different user. If so, it sets ths comment text to indicate the user and the modification date if (state.selection.$from) { nbef = findStartOfMark(state.selection.$from, view, findOtherUserMark); @@ -152,32 +152,35 @@ export class FormattedTextBoxComment { docTarget && DocServer.GetRefField(docTarget).then(linkDoc => { if (linkDoc instanceof Doc) { FormattedTextBoxComment.linkDoc = linkDoc; - let target = FieldValue(Doc.AreProtosEqual(FieldValue(Cast(linkDoc.anchor1, Doc)), textBox.props.Document) ? Cast(linkDoc.anchor2, Doc) : Cast(linkDoc.anchor1, Doc)); + const target = FieldValue(Doc.AreProtosEqual(FieldValue(Cast(linkDoc.anchor1, Doc)), textBox.props.Document) ? Cast(linkDoc.anchor2, Doc) : Cast(linkDoc.anchor1, Doc)); try { ReactDOM.unmountComponentAtNode(FormattedTextBoxComment.tooltipText); } catch (e) { } - target && ReactDOM.render( 350} - PanelHeight={() => 250} - focus={emptyFunction} - whenActiveChanged={returnFalse} - />, FormattedTextBoxComment.tooltipText); + if (target) { + ReactDOM.render( Math.min(350, NumCast(target.width, 350))} + PanelHeight={() => Math.min(250, NumCast(target.height, 250))} + focus={emptyFunction} + whenActiveChanged={returnFalse} + />, FormattedTextBoxComment.tooltipText); + FormattedTextBoxComment.tooltip.style.width = NumCast(target.width) ? `${NumCast(target.width)}` : "100%"; + FormattedTextBoxComment.tooltip.style.height = NumCast(target.height) ? `${NumCast(target.height)}` : "100%"; + } // let ext = (target && target.type !== DocumentType.PDFANNO && Doc.fieldExtensionDoc(target, "data")) || target; // try guessing that the target doc's data is in the 'data' field. probably need an 'overviewLayout' and then just display the target Document .... // let text = ext && StrCast(ext.text); // ext && (FormattedTextBoxComment.tooltipText.textContent = (target && target.type === DocumentType.PDFANNO ? "Quoted from " : "") + "=> " + (text || StrCast(ext.title))); diff --git a/src/client/views/presentationview/PresElementBox.tsx b/src/client/views/presentationview/PresElementBox.tsx index 17b2094ec..f50a3a0ef 100644 --- a/src/client/views/presentationview/PresElementBox.tsx +++ b/src/client/views/presentationview/PresElementBox.tsx @@ -172,7 +172,6 @@ export class PresElementBox extends DocComponent(P recent && PromiseValue(recent.data).then(DocListCast)); // this is equivalent to using PrefetchProxies to make sure all the sidebarButtons and noteType internal Doc's have been retrieved. PromiseValue(Cast(doc.noteTypes, Doc)).then(noteTypes => noteTypes && PromiseValue(noteTypes.data).then(DocListCast)); PromiseValue(Cast(doc.sidebarButtons, Doc)).then(stackingDoc => { -- cgit v1.2.3-70-g09d2 From e87b4b99323875afce2d9847f3bddd4196b85b81 Mon Sep 17 00:00:00 2001 From: bob Date: Wed, 13 Nov 2019 14:48:57 -0500 Subject: added a lockedTransform field to lock pan/zoom. fixed text scrollintoview to scroll only when necessary. --- src/client/documents/Documents.ts | 3 ++- src/client/util/RichTextSchema.tsx | 1 + .../collections/collectionFreeForm/CollectionFreeFormView.tsx | 4 ++-- src/client/views/nodes/DocumentView.tsx | 7 +++++++ src/client/views/nodes/FormattedTextBox.tsx | 4 +++- src/new_fields/documentSchemas.ts | 3 ++- 6 files changed, 17 insertions(+), 5 deletions(-) (limited to 'src') diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts index ba9f87025..1a9d67d83 100644 --- a/src/client/documents/Documents.ts +++ b/src/client/documents/Documents.ts @@ -76,7 +76,8 @@ export interface DocumentOptions { viewType?: number; backgroundColor?: string; ignoreClick?: boolean; - lockedPosition?: boolean; + lockedPosition?: boolean; // lock the x,y coordinates of the document so that it can't be dragged + lockedTransform?: boolean; // lock the panx,pany and scale parameters of the document so that it be panned/zoomed opacity?: number; defaultBackgroundColor?: string; dropAction?: dropActionType; diff --git a/src/client/util/RichTextSchema.tsx b/src/client/util/RichTextSchema.tsx index 1004cb3d4..0d1ae3841 100644 --- a/src/client/util/RichTextSchema.tsx +++ b/src/client/util/RichTextSchema.tsx @@ -679,6 +679,7 @@ export class DashDocView { bringToFront={emptyFunction} zoomToScale={emptyFunction} getScale={returnOne} + dontRegisterView={true} ContainingCollectionView={undefined} ContainingCollectionDoc={undefined} ContentScaling={this.contentScaling} diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx index 6e0f75bc1..0c5f4ec80 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx @@ -333,7 +333,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { @action onPointerWheel = (e: React.WheelEvent): void => { - if (this.props.Document.lockedPosition || this.props.Document.inOverlay) return; + if (this.props.Document.lockedTransform || this.props.Document.inOverlay) return; if (!e.ctrlKey && this.props.Document.scrollHeight !== undefined) { // things that can scroll vertically should do that instead of zooming e.stopPropagation(); } @@ -355,7 +355,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { @action setPan(panX: number, panY: number, panType: string = "None") { - if (!this.Document.lockedPosition || this.Document.inOverlay) { + if (!this.Document.lockedTransform || this.Document.inOverlay) { this.Document.panTransformType = panType; var scale = this.getLocalTransform().inverse().Scale; const newPanX = Math.min((1 - 1 / scale) * this.nativeWidth, Math.max(0, panX)); diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx index 7e81cd673..98c610c68 100644 --- a/src/client/views/nodes/DocumentView.tsx +++ b/src/client/views/nodes/DocumentView.tsx @@ -372,6 +372,12 @@ export class DocumentView extends DocComponent(Docu this.Document.lockedPosition = this.Document.lockedPosition ? undefined : true; } + @undoBatch + @action + toggleLockTransform = (): void => { + this.Document.lockedTransform = this.Document.lockedTransform ? undefined : true; + } + listen = async () => { Doc.GetProto(this.props.Document).transcript = await DictationManager.Controls.listen({ continuous: { indefinite: true }, @@ -444,6 +450,7 @@ export class DocumentView extends DocComponent(Docu layoutItems.push({ description: `${this.Document.autoHeight ? "Variable Height" : "Auto Height"}`, event: () => this.layoutDoc.autoHeight = !this.layoutDoc.autoHeight, icon: "plus" }); layoutItems.push({ description: this.Document.ignoreAspect || !this.Document.nativeWidth || !this.Document.nativeHeight ? "Freeze" : "Unfreeze", event: this.freezeNativeDimensions, icon: "snowflake" }); layoutItems.push({ description: this.Document.lockedPosition ? "Unlock Position" : "Lock Position", event: this.toggleLockPosition, icon: BoolCast(this.Document.lockedPosition) ? "unlock" : "lock" }); + layoutItems.push({ description: this.Document.lockedTransform ? "Unlock Transform" : "Lock Transform", event: this.toggleLockTransform, icon: BoolCast(this.Document.lockedTransform) ? "unlock" : "lock" }); layoutItems.push({ description: "Center View", event: () => this.props.focus(this.props.Document, false), icon: "crosshairs" }); layoutItems.push({ description: "Zoom to Document", event: () => this.props.focus(this.props.Document, true), icon: "search" }); if (this.Document.type !== DocumentType.COL && this.Document.type !== DocumentType.TEMPLATE) { diff --git a/src/client/views/nodes/FormattedTextBox.tsx b/src/client/views/nodes/FormattedTextBox.tsx index 8b1e65663..015a21fd2 100644 --- a/src/client/views/nodes/FormattedTextBox.tsx +++ b/src/client/views/nodes/FormattedTextBox.tsx @@ -774,7 +774,9 @@ export class FormattedTextBox extends DocExtendableComponent<(FieldViewProps & F while (refNode && !("getBoundingClientRect" in refNode)) refNode = refNode.parentElement; let r1 = refNode && refNode.getBoundingClientRect(); let r3 = self._ref.current!.getBoundingClientRect(); - r1 && (self._ref.current!.scrollTop += (r1.top - r3.top) * self.props.ScreenToLocalTransform().Scale); + if (r1.top < r3.top || r1.top > r3.bottom) { + r1 && (self._ref.current!.scrollTop += (r1.top - r3.top) * self.props.ScreenToLocalTransform().Scale); + } return true; }, dispatchTransaction: this.dispatchTransaction, diff --git a/src/new_fields/documentSchemas.ts b/src/new_fields/documentSchemas.ts index e2730914f..fa47374f1 100644 --- a/src/new_fields/documentSchemas.ts +++ b/src/new_fields/documentSchemas.ts @@ -31,7 +31,8 @@ export const documentSchema = createSchema({ summarizedDocs: listSpec(Doc), // documents that are summarized by this document (and which will typically be opened by clicking this document) maximizedDocs: listSpec(Doc), // documents to maximize when clicking this document (generally this document will be an icon) maximizeLocation: "string", // flag for where to place content when following a click interaction (e.g., onRight, inPlace, inTab) - lockedPosition: "boolean", // whether the document can be spatially manipulated + lockedPosition: "boolean", // whether the document can be moved (dragged) + lockedTransform: "boolean", // whether the document can be panned/zoomed inOverlay: "boolean", // whether the document is rendered in an OverlayView which handles selection/dragging differently borderRounding: "string", // border radius rounding of document searchFields: "string", // the search fields to display when this document matches a search in its metadata -- cgit v1.2.3-70-g09d2 From c6b602e2ab00d3a38e56164ecc928d5db8c12c72 Mon Sep 17 00:00:00 2001 From: bob Date: Thu, 14 Nov 2019 11:35:33 -0500 Subject: fixed warning/errors. cleaned up link following box a bit. --- src/client/util/RichTextSchema.tsx | 4 ++-- src/client/util/TooltipTextMenu.tsx | 20 ++++++++----------- .../views/collections/CollectionSchemaView.tsx | 1 - src/client/views/nodes/DocumentView.tsx | 2 +- src/client/views/nodes/FontIconBox.tsx | 2 +- src/client/views/nodes/FormattedTextBox.tsx | 1 + src/client/views/nodes/FormattedTextBoxComment.tsx | 23 ++++++++++++++++------ src/client/views/search/FilterBox.tsx | 4 ++-- 8 files changed, 32 insertions(+), 25 deletions(-) (limited to 'src') diff --git a/src/client/util/RichTextSchema.tsx b/src/client/util/RichTextSchema.tsx index 0d1ae3841..0fc526ca7 100644 --- a/src/client/util/RichTextSchema.tsx +++ b/src/client/util/RichTextSchema.tsx @@ -575,8 +575,8 @@ export class ImageResizeView { this._handle.onpointerdown = function (e: any) { e.preventDefault(); e.stopPropagation(); - let wid = Number(getComputedStyle(self._img).width!.replace(/px/, "")); - let hgt = Number(getComputedStyle(self._img).height!.replace(/px/, "")); + let wid = Number(getComputedStyle(self._img).width.replace(/px/, "")); + let hgt = Number(getComputedStyle(self._img).height.replace(/px/, "")); const startX = e.pageX; const startWidth = parseFloat(node.attrs.width); const onpointermove = (e: any) => { diff --git a/src/client/util/TooltipTextMenu.tsx b/src/client/util/TooltipTextMenu.tsx index 38471a955..5e11ae653 100644 --- a/src/client/util/TooltipTextMenu.tsx +++ b/src/client/util/TooltipTextMenu.tsx @@ -1000,12 +1000,10 @@ export class TooltipTextMenu { let activeMarks: MarkType[]; if (!empty) { activeMarks = markGroup.filter(mark => { - if (dispatch) { - let has = false; - for (let i = 0; !has && i < ranges.length; i++) { - let { $from, $to } = ranges[i]; - return state.doc.rangeHasMark($from.pos, $to.pos, mark); - } + let has = false; + for (let i = 0; !has && i < ranges.length; i++) { + let { $from, $to } = ranges[i]; + return state.doc.rangeHasMark($from.pos, $to.pos, mark); } return false; }); @@ -1024,13 +1022,11 @@ export class TooltipTextMenu { } this._activeMarks = ref_node.marks; activeMarks = markGroup.filter(mark_type => { - if (dispatch) { - if (mark_type === state.schema.marks.pFontSize) { - return ref_node.marks.some(m => m.type.name === state.schema.marks.pFontSize.name); - } - let mark = state.schema.mark(mark_type); - return ref_node.marks.includes(mark); + if (mark_type === state.schema.marks.pFontSize) { + return ref_node.marks.some(m => m.type.name === state.schema.marks.pFontSize.name); } + let mark = state.schema.mark(mark_type); + return ref_node.marks.includes(mark); return false; }); } diff --git a/src/client/views/collections/CollectionSchemaView.tsx b/src/client/views/collections/CollectionSchemaView.tsx index 203c68463..ebd47fd19 100644 --- a/src/client/views/collections/CollectionSchemaView.tsx +++ b/src/client/views/collections/CollectionSchemaView.tsx @@ -141,7 +141,6 @@ export class CollectionSchemaView extends CollectionSubView(doc => doc) { (Docu {searchHighlight}

} - + ; } render() { if (!this.props.Document) return (null); diff --git a/src/client/views/nodes/FontIconBox.tsx b/src/client/views/nodes/FontIconBox.tsx index ae9273639..83ecc4657 100644 --- a/src/client/views/nodes/FontIconBox.tsx +++ b/src/client/views/nodes/FontIconBox.tsx @@ -25,7 +25,7 @@ export class FontIconBox extends DocComponent( this._backgroundReaction = reaction(() => this.props.Document.backgroundColor, () => { if (this._ref && this._ref.current) { - let col = Utils.fromRGBAstr(getComputedStyle(this._ref.current).backgroundColor!); + let col = Utils.fromRGBAstr(getComputedStyle(this._ref.current).backgroundColor); let colsum = (col.r + col.g + col.b); if (colsum / col.a > 600 || col.a < 0.25) runInAction(() => this._foregroundColor = "black"); else if (colsum / col.a <= 600 || col.a >= .25) runInAction(() => this._foregroundColor = "white"); diff --git a/src/client/views/nodes/FormattedTextBox.tsx b/src/client/views/nodes/FormattedTextBox.tsx index 015a21fd2..6d60e8f77 100644 --- a/src/client/views/nodes/FormattedTextBox.tsx +++ b/src/client/views/nodes/FormattedTextBox.tsx @@ -288,6 +288,7 @@ export class FormattedTextBox extends DocExtendableComponent<(FieldViewProps & F if (context === node) return { from: offset, to: offset + node.nodeSize }; if (node.isBlock) { + // tslint:disable-next-line: prefer-for-of for (let i = 0; i < (context.content as any).content.length; i++) { let result = this.getNodeEndpoints((context.content as any).content[i], node); if (result) { diff --git a/src/client/views/nodes/FormattedTextBoxComment.tsx b/src/client/views/nodes/FormattedTextBoxComment.tsx index 29b4b6383..32efb16cf 100644 --- a/src/client/views/nodes/FormattedTextBoxComment.tsx +++ b/src/client/views/nodes/FormattedTextBoxComment.tsx @@ -13,6 +13,7 @@ import { ContentFittingDocumentView } from "./ContentFittingDocumentView"; import { FormattedTextBox } from "./FormattedTextBox"; import './FormattedTextBoxComment.scss'; import React = require("react"); +import { Docs } from "../../documents/Documents"; export let formattedTextBoxCommentPlugin = new Plugin({ view(editorView) { return new FormattedTextBoxComment(editorView); } @@ -51,6 +52,7 @@ export function findEndOfMark(rpos: ResolvedPos, view: EditorView, finder: (mark export class FormattedTextBoxComment { static tooltip: HTMLElement; static tooltipText: HTMLElement; + static tooltipInput: HTMLInputElement; static start: number; static end: number; static mark: Mark; @@ -60,10 +62,15 @@ export class FormattedTextBoxComment { constructor(view: any) { if (!FormattedTextBoxComment.tooltip) { const root = document.getElementById("root"); - let input = document.createElement("input"); - input.type = "checkbox"; + FormattedTextBoxComment.tooltipInput = document.createElement("input"); + FormattedTextBoxComment.tooltipInput.type = "checkbox"; FormattedTextBoxComment.tooltip = document.createElement("div"); FormattedTextBoxComment.tooltipText = document.createElement("div"); + FormattedTextBoxComment.tooltipText.style.whiteSpace = "pre"; + FormattedTextBoxComment.tooltipText.style.overflow = "hidden"; + FormattedTextBoxComment.tooltipText.style.width = "100%"; + FormattedTextBoxComment.tooltipText.style.height = "100%"; + FormattedTextBoxComment.tooltipText.style.textOverflow = "ellipsis"; FormattedTextBoxComment.tooltip.appendChild(FormattedTextBoxComment.tooltipText); FormattedTextBoxComment.tooltip.className = "FormattedTextBox-tooltip"; FormattedTextBoxComment.tooltip.style.pointerEvents = "all"; @@ -73,13 +80,15 @@ export class FormattedTextBoxComment { FormattedTextBoxComment.tooltip.style.height = "100%"; FormattedTextBoxComment.tooltip.style.overflow = "hidden"; FormattedTextBoxComment.tooltip.style.display = "none"; - FormattedTextBoxComment.tooltip.appendChild(input); + FormattedTextBoxComment.tooltip.appendChild(FormattedTextBoxComment.tooltipInput); FormattedTextBoxComment.tooltip.onpointerdown = (e: PointerEvent) => { let keep = e.target && (e.target as any).type === "checkbox" ? true : false; const textBox = FormattedTextBoxComment.textBox; if (FormattedTextBoxComment.linkDoc && !keep && textBox) { DocumentManager.Instance.FollowLink(FormattedTextBoxComment.linkDoc, textBox.props.Document, (doc: Doc, maxLocation: string) => textBox.props.addDocTab(doc, undefined, e.ctrlKey ? "inTab" : "onRight")); + } else if (textBox && (FormattedTextBoxComment.tooltipText as any).href) { + textBox.props.addDocTab(Docs.Create.WebDocument((FormattedTextBoxComment.tooltipText as any).href, { width: 200, height: 400 }), undefined, "onRight"); } FormattedTextBoxComment.opened = keep || !FormattedTextBoxComment.opened; textBox && FormattedTextBoxComment.start !== undefined && textBox.setAnnotation( @@ -120,8 +129,10 @@ export class FormattedTextBoxComment { } let set = "none"; let nbef = 0; + FormattedTextBoxComment.tooltipInput.style.display = "none"; FormattedTextBoxComment.tooltip.style.width = ""; FormattedTextBoxComment.tooltip.style.height = ""; + (FormattedTextBoxComment.tooltipText as any).href = ""; // this section checks to see if the insertion point is over text entered by a different user. If so, it sets ths comment text to indicate the user and the modification date if (state.selection.$from) { nbef = findStartOfMark(state.selection.$from, view, findOtherUserMark); @@ -136,6 +147,7 @@ export class FormattedTextBoxComment { if (mark && child && ((nbef && naft) || !noselection)) { FormattedTextBoxComment.tooltipText.textContent = mark.attrs.userid + " date=" + (new Date(mark.attrs.modified * 5000)).toDateString(); set = ""; + FormattedTextBoxComment.tooltipInput.style.display = ""; } } // this checks if the selection is a hyperlink. If so, it displays the target doc's text for internal links, and the url of the target for external links. @@ -147,6 +159,7 @@ export class FormattedTextBoxComment { let mark = child && findLinkMark(child.marks); if (mark && child && nbef && naft) { FormattedTextBoxComment.tooltipText.textContent = "external => " + mark.attrs.href; + (FormattedTextBoxComment.tooltipText as any).href = mark.attrs.href; if (mark.attrs.href.indexOf(Utils.prepend("/doc/")) === 0) { let docTarget = mark.attrs.href.replace(Utils.prepend("/doc/"), "").split("?")[0]; docTarget && DocServer.GetRefField(docTarget).then(linkDoc => { @@ -155,9 +168,7 @@ export class FormattedTextBoxComment { const target = FieldValue(Doc.AreProtosEqual(FieldValue(Cast(linkDoc.anchor1, Doc)), textBox.props.Document) ? Cast(linkDoc.anchor2, Doc) : Cast(linkDoc.anchor1, Doc)); try { ReactDOM.unmountComponentAtNode(FormattedTextBoxComment.tooltipText); - } catch (e) { - - } + } catch (e) { } if (target) { ReactDOM.render( { @@ -114,7 +114,7 @@ export class FilterBox extends React.Component { acc[i].classList.toggle("active"); var panel = acc[i].nextElementSibling as HTMLElement; panel.style.overflow = "hidden"; - panel.style.maxHeight = null; + panel.style.maxHeight = ""; } } }); -- cgit v1.2.3-70-g09d2 From 71275370c00112f87b0ec1044a26fe644b5bfba5 Mon Sep 17 00:00:00 2001 From: bob Date: Thu, 14 Nov 2019 11:59:17 -0500 Subject: tweaks to text box comment --- package.json | 1 + src/client/views/nodes/FormattedTextBoxComment.tsx | 11 +++++++++-- 2 files changed, 10 insertions(+), 2 deletions(-) (limited to 'src') diff --git a/package.json b/package.json index 40bf40c17..591c8cff3 100644 --- a/package.json +++ b/package.json @@ -221,6 +221,7 @@ "typescript-collections": "^1.3.2", "url-loader": "^1.1.2", "uuid": "^3.3.2", + "wikijs": "^6.0.1", "words-to-numbers": "^1.5.1", "xoauth2": "^1.2.0", "youtube": "^0.1.0" diff --git a/src/client/views/nodes/FormattedTextBoxComment.tsx b/src/client/views/nodes/FormattedTextBoxComment.tsx index 32efb16cf..04958525c 100644 --- a/src/client/views/nodes/FormattedTextBoxComment.tsx +++ b/src/client/views/nodes/FormattedTextBoxComment.tsx @@ -14,6 +14,7 @@ import { FormattedTextBox } from "./FormattedTextBox"; import './FormattedTextBoxComment.scss'; import React = require("react"); import { Docs } from "../../documents/Documents"; +import wiki from "wikijs"; export let formattedTextBoxCommentPlugin = new Plugin({ view(editorView) { return new FormattedTextBoxComment(editorView); } @@ -66,8 +67,6 @@ export class FormattedTextBoxComment { FormattedTextBoxComment.tooltipInput.type = "checkbox"; FormattedTextBoxComment.tooltip = document.createElement("div"); FormattedTextBoxComment.tooltipText = document.createElement("div"); - FormattedTextBoxComment.tooltipText.style.whiteSpace = "pre"; - FormattedTextBoxComment.tooltipText.style.overflow = "hidden"; FormattedTextBoxComment.tooltipText.style.width = "100%"; FormattedTextBoxComment.tooltipText.style.height = "100%"; FormattedTextBoxComment.tooltipText.style.textOverflow = "ellipsis"; @@ -133,6 +132,8 @@ export class FormattedTextBoxComment { FormattedTextBoxComment.tooltip.style.width = ""; FormattedTextBoxComment.tooltip.style.height = ""; (FormattedTextBoxComment.tooltipText as any).href = ""; + FormattedTextBoxComment.tooltipText.style.whiteSpace = ""; + FormattedTextBoxComment.tooltipText.style.overflow = ""; // this section checks to see if the insertion point is over text entered by a different user. If so, it sets ths comment text to indicate the user and the modification date if (state.selection.$from) { nbef = findStartOfMark(state.selection.$from, view, findOtherUserMark); @@ -159,6 +160,12 @@ export class FormattedTextBoxComment { let mark = child && findLinkMark(child.marks); if (mark && child && nbef && naft) { FormattedTextBoxComment.tooltipText.textContent = "external => " + mark.attrs.href; + if (mark.attrs.href.startsWith("https://en.wikipedia.org/wiki/")) { + wiki().page(mark.attrs.href.replace("https://en.wikipedia.org/wiki/", "")).then(page => page.summary().then(summary => FormattedTextBoxComment.tooltipText.textContent = summary.substring(0, 500))); + } else { + FormattedTextBoxComment.tooltipText.style.whiteSpace = "pre"; + FormattedTextBoxComment.tooltipText.style.overflow = "hidden"; + } (FormattedTextBoxComment.tooltipText as any).href = mark.attrs.href; if (mark.attrs.href.indexOf(Utils.prepend("/doc/")) === 0) { let docTarget = mark.attrs.href.replace(Utils.prepend("/doc/"), "").split("?")[0]; -- cgit v1.2.3-70-g09d2 From c9fbdb9cfd5fcf35f7a599706cb41d4e7e586e19 Mon Sep 17 00:00:00 2001 From: bob Date: Thu, 14 Nov 2019 16:23:57 -0500 Subject: added optional sidebar for text notes --- src/client/views/DocumentDecorations.tsx | 11 +----- src/client/views/nodes/FormattedTextBox.scss | 12 +++++++ src/client/views/nodes/FormattedTextBox.tsx | 39 +++++++++++++++++++--- src/client/views/nodes/FormattedTextBoxComment.tsx | 5 ++- 4 files changed, 49 insertions(+), 18 deletions(-) (limited to 'src') diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx index 55c211d1d..49921170f 100644 --- a/src/client/views/DocumentDecorations.tsx +++ b/src/client/views/DocumentDecorations.tsx @@ -157,22 +157,14 @@ export class DocumentDecorations extends React.Component<{}, { value: string }> this.onBackgroundUp(e); } - @observable _forceUpdate = 0; - _lastBox = { x: 0, y: 0, r: 0, b: 0 }; @computed get Bounds(): { x: number, y: number, b: number, r: number } { - let x = this._forceUpdate; - this._lastBox = SelectionManager.SelectedDocuments().reduce((bounds, documentView) => { + return SelectionManager.SelectedDocuments().reduce((bounds, documentView) => { if (documentView.props.renderDepth === 0 || Doc.AreProtosEqual(documentView.props.Document, CurrentUserUtils.UserDocument)) { return bounds; } let transform = (documentView.props.ScreenToLocalTransform().scale(documentView.props.ContentScaling())).inverse(); - if (transform.TranslateX === 0 && transform.TranslateY === 0) { - setTimeout(action(() => this._forceUpdate++), 0); // bcz: fix CollectionStackingView's getTransform() somehow...without this, resizing things in the library view, for instance, show the wrong bounds - return this._lastBox; - } - var [sptX, sptY] = transform.transformPoint(0, 0); let [bptX, bptY] = transform.transformPoint(documentView.props.PanelWidth(), documentView.props.PanelHeight()); if (documentView.props.Document.type === DocumentType.LINK) { @@ -187,7 +179,6 @@ export class DocumentDecorations extends React.Component<{}, { value: string }> r: Math.max(bptX, bounds.r), b: Math.max(bptY, bounds.b) }; }, { x: Number.MAX_VALUE, y: Number.MAX_VALUE, r: Number.MIN_VALUE, b: Number.MIN_VALUE }); - return this._lastBox; } onBackgroundDown = (e: React.PointerEvent): void => { diff --git a/src/client/views/nodes/FormattedTextBox.scss b/src/client/views/nodes/FormattedTextBox.scss index a4acd3b82..98b57bd84 100644 --- a/src/client/views/nodes/FormattedTextBox.scss +++ b/src/client/views/nodes/FormattedTextBox.scss @@ -36,6 +36,18 @@ } } +.collectionfreeformview-container { + position: relative; +} +.formattedTextBox-outer { + position: relative; + overflow: auto; + display: inline-block; + padding: 10px 10px; + width: 100%; + height: 100%; +} + .formattedTextBox-inner-rounded { height: 70%; width: 85%; diff --git a/src/client/views/nodes/FormattedTextBox.tsx b/src/client/views/nodes/FormattedTextBox.tsx index 6d60e8f77..b7df3fb0e 100644 --- a/src/client/views/nodes/FormattedTextBox.tsx +++ b/src/client/views/nodes/FormattedTextBox.tsx @@ -18,7 +18,7 @@ import { RichTextField } from "../../../new_fields/RichTextField"; import { RichTextUtils } from '../../../new_fields/RichTextUtils'; import { createSchema, makeInterface } from "../../../new_fields/Schema"; import { Cast, DateCast, NumCast, StrCast } from "../../../new_fields/Types"; -import { numberRange, Utils, addStyleSheet, addStyleSheetRule, clearStyleSheetRules } from '../../../Utils'; +import { numberRange, Utils, addStyleSheet, addStyleSheetRule, clearStyleSheetRules, emptyFunction, returnOne } from '../../../Utils'; import { GoogleApiClientUtils, Pulls, Pushes } from '../../apis/google_docs/GoogleApiClientUtils'; import { DocServer } from "../../DocServer"; import { Docs, DocUtils } from '../../documents/Documents'; @@ -33,7 +33,7 @@ import { SelectionManager } from "../../util/SelectionManager"; import { TooltipLinkingMenu } from "../../util/TooltipLinkingMenu"; import { TooltipTextMenu } from "../../util/TooltipTextMenu"; import { undoBatch, UndoManager } from "../../util/UndoManager"; -import { DocExtendableComponent } from "../DocComponent"; +import { DocAnnotatableComponent } from "../DocComponent"; import { DocumentButtonBar } from '../DocumentButtonBar'; import { DocumentDecorations } from '../DocumentDecorations'; import { InkingControl } from "../InkingControl"; @@ -46,6 +46,7 @@ import { ContextMenu } from '../ContextMenu'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { documentSchema } from '../../../new_fields/documentSchemas'; import { AudioBox } from './AudioBox'; +import { CollectionFreeFormView } from '../collections/collectionFreeForm/CollectionFreeFormView'; library.add(faEdit); library.add(faSmile, faTextHeight, faUpload); @@ -70,7 +71,7 @@ const RichTextDocument = makeInterface(richTextSchema, documentSchema); type PullHandler = (exportState: Opt, dataDoc: Doc) => void; @observer -export class FormattedTextBox extends DocExtendableComponent<(FieldViewProps & FormattedTextBoxProps), RichTextDocument>(RichTextDocument) { +export class FormattedTextBox extends DocAnnotatableComponent<(FieldViewProps & FormattedTextBoxProps), RichTextDocument>(RichTextDocument) { public static LayoutString(fieldStr: string) { return FieldView.LayoutString(FormattedTextBox, fieldStr); } public static blankState = () => EditorState.create(FormattedTextBox.Instance.config); public static Instance: FormattedTextBox; @@ -359,6 +360,7 @@ export class FormattedTextBox extends DocExtendableComponent<(FieldViewProps & F specificContextMenu = (e: React.MouseEvent): void => { let funcs: ContextMenuProps[] = []; + funcs.push({ description: "Toggle Sidebar", event: () => { e.stopPropagation(); this.props.Document.sidebarWidthPercent = StrCast(this.props.Document.sidebarWidthPercent, "0%") === "0%" ? "25%" : "0%"; }, icon: "expand-arrows-alt" }); funcs.push({ description: "Record Bullet", event: () => { e.stopPropagation(); this.recordBullet(); }, icon: "expand-arrows-alt" }); ["My Text", "Text from Others", "Todo Items", "Important Items", "Ignore Items", "Disagree Items", "By Recent Minute", "By Recent Hour"].forEach(option => funcs.push({ @@ -1010,6 +1012,9 @@ export class FormattedTextBox extends DocExtendableComponent<(FieldViewProps & F } } + @computed get sidebarWidthPercent() { return StrCast(this.props.Document.sidebarWidth, "0%"); } + @computed get sidebarWidth() { return Number(this.sidebarWidthPercent.substring(0, this.sidebarWidthPercent.length - 1)) / 100 * this.props.PanelWidth(); } + @computed get annotationsKey() { return "annotations"; } render() { trace(); let rounded = StrCast(this.layoutDoc.borderRounding) === "100%" ? "-rounded" : ""; @@ -1042,8 +1047,32 @@ export class FormattedTextBox extends DocExtendableComponent<(FieldViewProps & F onPointerEnter={action(() => this._entered = true)} onPointerLeave={action(() => this._entered = false)} > -
- +
+
+
+ {this.sidebarWidthPercent === "0%" ? (null) :
+ this.props.PanelHeight()} + PanelWidth={() => this.sidebarWidth} + annotationsKey={this.annotationsKey} + isAnnotationOverlay={true} + focus={this.props.focus} + isSelected={this.props.isSelected} + select={emptyFunction} + active={this.active} + ContentScaling={returnOne} + whenActiveChanged={this.whenActiveChanged} + removeDocument={this.removeDocument} + moveDocument={this.moveDocument} + addDocument={this.addDocument} + CollectionView={undefined} + ScreenToLocalTransform={() => this.props.ScreenToLocalTransform().translate(-(this.props.PanelWidth() - this.sidebarWidth), 0)} + ruleProvider={undefined} + renderDepth={this.props.renderDepth + 1} + ContainingCollectionDoc={this.props.ContainingCollectionDoc} + chromeCollapsed={true}> + +
}
{ this._recording ? this.stopDictation(true) : this.recordDictation(); diff --git a/src/client/views/nodes/FormattedTextBoxComment.tsx b/src/client/views/nodes/FormattedTextBoxComment.tsx index 04958525c..c076fd34a 100644 --- a/src/client/views/nodes/FormattedTextBoxComment.tsx +++ b/src/client/views/nodes/FormattedTextBoxComment.tsx @@ -87,7 +87,7 @@ export class FormattedTextBoxComment { DocumentManager.Instance.FollowLink(FormattedTextBoxComment.linkDoc, textBox.props.Document, (doc: Doc, maxLocation: string) => textBox.props.addDocTab(doc, undefined, e.ctrlKey ? "inTab" : "onRight")); } else if (textBox && (FormattedTextBoxComment.tooltipText as any).href) { - textBox.props.addDocTab(Docs.Create.WebDocument((FormattedTextBoxComment.tooltipText as any).href, { width: 200, height: 400 }), undefined, "onRight"); + textBox.props.addDocTab(Docs.Create.WebDocument((FormattedTextBoxComment.tooltipText as any).href, { title: (FormattedTextBoxComment.tooltipText as any).href, width: 200, height: 400 }), undefined, "onRight"); } FormattedTextBoxComment.opened = keep || !FormattedTextBoxComment.opened; textBox && FormattedTextBoxComment.start !== undefined && textBox.setAnnotation( @@ -97,7 +97,6 @@ export class FormattedTextBoxComment { }; root && root.appendChild(FormattedTextBoxComment.tooltip); } - //this.update(view, undefined); } public static Hide() { @@ -223,5 +222,5 @@ export class FormattedTextBoxComment { FormattedTextBoxComment.tooltip && (FormattedTextBoxComment.tooltip.style.display = set); } - destroy() { }//FormattedTextBoxComment.tooltip.style.display = "none"; } + destroy() { } } -- cgit v1.2.3-70-g09d2 From e3f06e390f98cc5b97d63fc287daff994d5fef6f Mon Sep 17 00:00:00 2001 From: Fawn Date: Thu, 14 Nov 2019 23:01:49 -0500 Subject: double cliking dragger collapses menu --- src/client/util/RichTextSchema.tsx | 1 + src/client/util/TooltipTextMenu.scss | 399 ++--------------------- src/client/util/TooltipTextMenu.tsx | 610 +++++++++++++++-------------------- 3 files changed, 278 insertions(+), 732 deletions(-) (limited to 'src') diff --git a/src/client/util/RichTextSchema.tsx b/src/client/util/RichTextSchema.tsx index 1eea529d2..69f9a4c53 100644 --- a/src/client/util/RichTextSchema.tsx +++ b/src/client/util/RichTextSchema.tsx @@ -130,6 +130,7 @@ export const nodes: { [index: string]: NodeSpec } = { // } // }] }, + // :: NodeSpec An inline image (``) node. Supports `src`, // `alt`, and `href` attributes. The latter two default to the empty // string. diff --git a/src/client/util/TooltipTextMenu.scss b/src/client/util/TooltipTextMenu.scss index 1f619b4f5..8310a0da6 100644 --- a/src/client/util/TooltipTextMenu.scss +++ b/src/client/util/TooltipTextMenu.scss @@ -1,6 +1,5 @@ @import "../views/globalCssVariables"; .ProseMirror-menu-dropdown-wrap { - // margin: 0 4px; display: inline-block; position: relative; } @@ -39,9 +38,10 @@ font-size: 12px; background: white; border: 1px solid rgb(223, 223, 223); - min-width: 50px; + min-width: 40px; z-index: 50000; position: absolute; + box-sizing: content-box; .ProseMirror-menu-dropdown-item { cursor: pointer; @@ -91,20 +91,22 @@ .wrapper { position: absolute; pointer-events: all; -} + display: flex; + align-items: center; + transform: translateY(-85px); -.tooltipMenu { - position: absolute; - z-index: 20000; + height: 35px; background: #323232; border-radius: 6px; - transform: translateY(-85px); + box-shadow: 3px 3px 3px rgba(0, 0, 0, 0.25); + +} + +.tooltipMenu, .basic-tools { + z-index: 20000; pointer-events: all; - height: 35px; - // width: 650px; padding: 3px; padding-bottom: 5px; - box-shadow: 3px 3px 3px rgba(0, 0, 0, 0.25); display: flex; align-items: center; @@ -203,7 +205,7 @@ } .underline svg { - margin-top: 15px; + margin-top: 13px; } .font-size-indicator { @@ -232,21 +234,19 @@ width: 1em; height: 1em; stroke-width: 3; - // stroke: greenyellow; fill: greenyellow; margin-right: 15px; } -.dragger { +.dragger-wrapper { color: #eee; - margin-left: 5px; - width: 16px; height: 22px; - display: inline-block; + padding: 0 5px; + box-sizing: content-box; cursor: grab; - .dragger-wrapper { - width: 100%; + .dragger { + width: 18px; height: 100%; display: flex; justify-content: space-evenly; @@ -259,11 +259,6 @@ } } -// .menuicon:hover + .ProseMirror-menu-dropdown-wrap .buttonSettings-dropdown, -// .menuicon-active:hover + .ProseMirror-menu-dropdown-wrap .buttonSettings-dropdown { -// background-color: black; -// } - .button-dropdown-wrapper { display: flex; align-content: center; @@ -374,360 +369,4 @@ button.colorPicker { &.active { border: 2px solid white !important; } -} - -// @import "../views/globalCssVariables"; - -// .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; -// z-index: 50000; -// position: relative; -// } - -// .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; -// margin: 3px; -// background: white; -// border-radius: 3px; -// text-align: center; -// } - -// .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 { -// background: $dark-color; -// color:white; -// border: 1px solid rgb(223, 223, 223); -// padding: 2px; -// } - -// .ProseMirror-menu-dropdown-menu { -// z-index: 50000; -// min-width: 6em; -// background: white; -// position: absolute; -// } - -// .linking { -// text-align: center; -// } - -// .ProseMirror-menu-dropdown-item { -// cursor: pointer; -// padding: 2px 8px 2px 4px; -// width: auto; -// z-index: 100000; -// } - -// .ProseMirror-menu-dropdown-item:hover { -// background: white; -// } - -// .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: 10px 10px; -// 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:white; -// height: 1em; -// } - -// .ProseMirror-icon span { -// vertical-align: text-top; -// } - -// .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 white(255, 255, 255, 0.2); -// } - -// .ProseMirror-prompt h5 { -// margin: 0; -// font-weight: normal; -// font-size: 100%; -// color: #444; -// } - -// .ProseMirror-prompt input[type="text"], -// .ProseMirror-prompt textarea { -// background: white; -// 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: 20000; -// background: #121721; -// border: 1px solid silver; -// border-radius: 15px; -// //height: 60px; -// //padding: 2px 10px; -// //margin-top: 100px; -// //-webkit-transform: translateX(-50%); -// //transform: translateX(-50%); -// transform: translateY(-85px); -// pointer-events: all; -// height: fit-content; -// width:550px; -// .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; -// } -// } - -// .tooltipExtras { -// position: absolute; -// z-index: 20000; -// background: #121721; -// border: 1px solid silver; -// border-radius: 15px; -// //height: 60px; -// //padding: 2px 10px; -// //margin-top: 100px; -// //-webkit-transform: translateX(-50%); -// //transform: translateX(-50%); -// transform: translateY(-115px); -// pointer-events: all; -// height: 25px; -// width:fit-content; -// .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; -// } -// } - -// .wrapper { -// position: absolute; -// pointer-events: all; -// } - -// .menuicon { -// display: inline-block; -// border-right: 1px solid white(0, 0, 0, 0.2); -// //color: rgb(19, 18, 18); -// color: rgb(226, 21, 21); -// line-height: 1; -// padding: 0px 2px; -// margin: 1px; -// cursor: pointer; -// text-align: center; -// min-width: 10px; - -// } -// .strong, .heading { font-weight: bold; } -// .em { font-style: italic; } -// .underline {text-decoration: underline} -// .superscript {vertical-align:super} -// .subscript { vertical-align:sub } -// .strikethrough {text-decoration-line:line-through} -// .font-size-indicator { -// font-size: 12px; -// padding-right: 0px; -// } -// .summarize{ -// color: white; -// height: 20px; -// text-align: center; -// } - -// .brush{ -// display: inline-block; -// width: 1em; -// height: 1em; -// stroke-width: 0; -// stroke: currentColor; -// fill: currentColor; -// margin-right: 15px; -// } - -// .brush-active{ -// display: inline-block; -// width: 1em; -// height: 1em; -// stroke-width: 3; -// stroke: greenyellow; -// fill: greenyellow; -// margin-right: 15px; -// } - -// .dragger{ -// color: #eee; -// margin-left: 5px; -// } \ No newline at end of file +} \ No newline at end of file diff --git a/src/client/util/TooltipTextMenu.tsx b/src/client/util/TooltipTextMenu.tsx index 564d9f0df..6001f9840 100644 --- a/src/client/util/TooltipTextMenu.tsx +++ b/src/client/util/TooltipTextMenu.tsx @@ -30,9 +30,8 @@ export class TooltipTextMenu { public static Toolbar: HTMLDivElement | undefined; - // editor state + // editor state properties private view: EditorView; - // private editorProps: FieldViewProps & FormattedTextBoxProps | undefined; private fontStyles: MarkType[] = []; private fontSizes: MarkType[] = []; private listTypes: (NodeType | any)[] = []; @@ -42,13 +41,16 @@ export class TooltipTextMenu { private _activeMarks: Mark[] = []; private _marksToDoms: Map = new Map(); private _collapsed: boolean = false; - //private link: HTMLAnchorElement; // editor doms public tooltip: HTMLElement = document.createElement("div"); private wrapper: HTMLDivElement = document.createElement("div"); // editor button doms + private colorDom?: Node; + private colorDropdownDom?: Node; + private highlightDom?: Node; + private highlightDropdownDom?: Node; private linkEditor?: HTMLDivElement; private linkText?: HTMLDivElement; private linkDrag?: HTMLImageElement; @@ -58,42 +60,32 @@ export class TooltipTextMenu { private fontSizeDom?: Node; private fontStyleDom?: Node; private listTypeBtnDom?: Node; - private colorDom?: Node; - private colorDropdownDom?: Node; - private highlightDom?: Node; - private highlightDropdownDom?: Node; - - - // private _collapseBtn?: MenuItem; - // private _brushIsEmpty: boolean = true; - + private basicTools?: HTMLElement; constructor(view: EditorView) { this.view = view; - // // replace old active menu with this - // if (TooltipTextMenuManager.Instance.activeMenu) { - // TooltipTextMenuManager.Instance.activeMenu.wrapper.remove(); - // } - // TooltipTextMenuManager.Instance.activeMenu = this; - - // initialize the tooltip - this.createTooltip(view); + // initialize the tooltip -- sets this.tooltip + this.initTooltip(view); // initialize the wrapper this.wrapper = document.createElement("div"); this.wrapper.className = "wrapper"; this.wrapper.appendChild(this.tooltip); - // positioning? + // initialize the dragger -- appends it to the wrapper + this.createDragger(); + TooltipTextMenu.Toolbar = this.wrapper; } - private async createTooltip(view: EditorView) { + private async initTooltip(view: EditorView) { // initialize tooltip dom this.tooltip = document.createElement("div"); this.tooltip.className = "tooltipMenu"; + this.basicTools = document.createElement("div"); + this.basicTools.className = "basic-tools"; // init buttons to the tooltip -- paths to svgs are obtained from fontawesome let items = [ @@ -113,12 +105,15 @@ export class TooltipTextMenu { switch (dom.title) { case "Bold": this._marksToDoms.set(schema.mark(schema.marks.strong), dom); + this.basicTools && this.basicTools.appendChild(dom.cloneNode(true)); break; case "Italic": this._marksToDoms.set(schema.mark(schema.marks.em), dom); + this.basicTools && this.basicTools.appendChild(dom.cloneNode(true)); break; case "Underline": this._marksToDoms.set(schema.mark(schema.marks.underline), dom); + this.basicTools && this.basicTools.appendChild(dom.cloneNode(true)); break; } @@ -172,19 +167,12 @@ export class TooltipTextMenu { this.tooltip.appendChild(this._brushDropdownDom); // star - // this.tooltip.appendChild(this.createLink().render(this.view).dom); this.tooltip.appendChild(this.createStar().render(this.view).dom); - // + // list types dropdown this.updateListItemDropdown(":", this.listTypeBtnDom); - // await this.updateFromDash(view, undefined, undefined); - // TooltipTextMenu.Toolbar = this.wrapper; - - // dragger - // TODO: onclick handler in drag handles collapsing - this.createDragger(); } initFontStyles() { @@ -224,13 +212,15 @@ export class TooltipTextMenu { this.listTypes = Array.from(this.listTypeToIcon.keys()); } + // creates dragger element that allows dragging and collapsing (on double click) + // of editor and appends it to the wrapper createDragger() { - const dragger = document.createElement("div"); - dragger.className = "dragger"; - let draggerWrapper = document.createElement("div"); draggerWrapper.className = "dragger-wrapper"; + let dragger = document.createElement("div"); + dragger.className = "dragger"; + let line1 = document.createElement("span"); line1.className = "dragger-line"; let line2 = document.createElement("span"); @@ -238,14 +228,72 @@ export class TooltipTextMenu { let line3 = document.createElement("span"); line3.className = "dragger-line"; - draggerWrapper.appendChild(line1); - draggerWrapper.appendChild(line2); - draggerWrapper.appendChild(line3); + dragger.appendChild(line1); + dragger.appendChild(line2); + dragger.appendChild(line3); - dragger.appendChild(draggerWrapper); + draggerWrapper.appendChild(dragger); - this.tooltip.appendChild(dragger); - this.dragElement(dragger); + this.wrapper.appendChild(draggerWrapper); + this.dragElement(draggerWrapper); + } + + dragElement(elmnt: HTMLElement) { + var pos1 = 0, pos2 = 0, pos3 = 0, pos4 = 0; + if (elmnt) { + // if present, the header is where you move the DIV from: + elmnt.onpointerdown = dragMouseDown; + elmnt.ondblclick = onClick; + } + const self = this; + + function dragMouseDown(e: PointerEvent) { + e = e || window.event; + //e.preventDefault(); + // get the mouse cursor position at startup: + pos3 = e.clientX; + pos4 = e.clientY; + document.onpointerup = closeDragElement; + // call a function whenever the cursor moves: + document.onpointermove = elementDrag; + } + + function onClick(e: MouseEvent) { + self._collapsed = !self._collapsed; + const children = self.wrapper.childNodes; + if (self._collapsed && children.length > 0) { + self.wrapper.removeChild(self.tooltip); + self.basicTools && self.wrapper.prepend(self.basicTools); + } + else { + self.wrapper.prepend(self.tooltip); + self.basicTools && self.wrapper.removeChild(self.basicTools); + } + } + + function elementDrag(e: PointerEvent) { + e = e || window.event; + //e.preventDefault(); + // calculate the new cursor position: + pos1 = pos3 - e.clientX; + pos2 = pos4 - e.clientY; + pos3 = e.clientX; + pos4 = e.clientY; + // set the element's new position: + // elmnt.style.top = (elmnt.offsetTop - pos2) + "px"; + // elmnt.style.left = (elmnt.offsetLeft - pos1) + "px"; + + self.wrapper.style.top = (self.wrapper.offsetTop - pos2) + "px"; + self.wrapper.style.left = (self.wrapper.offsetLeft - pos1) + "px"; + } + + function closeDragElement() { + // stop moving when mouse button is released: + document.onpointerup = null; + document.onpointermove = null; + //self.highlightSearchTerms(self.state, ["hello"]); + //FormattedTextBox.Instance.unhighlightSearchTerms(); + } } //label of dropdown will change to given label @@ -261,7 +309,7 @@ export class TooltipTextMenu { let newfontSizeDom = (new Dropdown(cut(fontSizeBtns), { label: label, - css: "color:black; min-width: 60px; padding-left: 5px; margin-right: 0;" + css: "color:black; min-width: 60px;" }) as MenuItem).render(this.view).dom; if (this.fontSizeDom) { this.tooltip.replaceChild(newfontSizeDom, this.fontSizeDom); } else { @@ -270,8 +318,6 @@ export class TooltipTextMenu { this.fontSizeDom = newfontSizeDom; } - // Make the DIV element draggable - //label of dropdown will change to given label updateFontStyleDropdown(label: string) { //filtering function - might be unecessary @@ -285,7 +331,7 @@ export class TooltipTextMenu { let newfontStyleDom = (new Dropdown(cut(fontBtns), { label: label, - css: "color:black; width: 125px; margin-left: -3px; padding-left: 2px;" + css: "color:black; width: 125px;" }) as MenuItem).render(this.view).dom; if (this.fontStyleDom) { this.tooltip.replaceChild(newfontStyleDom, this.fontStyleDom); } else { @@ -299,11 +345,7 @@ export class TooltipTextMenu { if (!this.linkEditor || !this.linkText) { this.linkEditor = document.createElement("div"); this.linkEditor.className = "ProseMirror-icon menuicon"; - // this.linkEditor.style.color = "black"; this.linkText = document.createElement("div"); - // this.linkText.style.cssFloat = "left"; - // this.linkText.style.marginRight = "5px"; - // this.linkText.style.marginLeft = "5px"; this.linkText.setAttribute("contenteditable", "true"); this.linkText.style.whiteSpace = "nowrap"; this.linkText.style.width = "150px"; @@ -391,13 +433,11 @@ export class TooltipTextMenu { e.preventDefault(); } }; - // this.tooltip.appendChild(this.linkEditor); } async getTextLinkTargetTitle() { let node = this.view.state.selection.$from.nodeAfter; let link = node && node.marks.find(m => m.type.name === "link"); - // let href = link!.attrs.href; if (link) { let href = link.attrs.href; if (href) { @@ -445,7 +485,6 @@ export class TooltipTextMenu { input.type = "text"; input.placeholder = "Enter URL"; - console.log(targetTitle); if (targetTitle) input.value = targetTitle; input.onclick = (e: MouseEvent) => { input.select(); @@ -514,62 +553,6 @@ export class TooltipTextMenu { return linkDropdown; } - dragElement(elmnt: HTMLElement) { - var pos1 = 0, pos2 = 0, pos3 = 0, pos4 = 0; - if (elmnt) { - // if present, the header is where you move the DIV from: - elmnt.onpointerdown = dragMouseDown; - elmnt.ondblclick = onClick; - } - const self = this; - - function dragMouseDown(e: PointerEvent) { - e = e || window.event; - //e.preventDefault(); - // get the mouse cursor position at startup: - pos3 = e.clientX; - pos4 = e.clientY; - document.onpointerup = closeDragElement; - // call a function whenever the cursor moves: - document.onpointermove = elementDrag; - } - - function onClick(e: MouseEvent) { - self._collapsed = !self._collapsed; - const children = self.wrapper.childNodes; - if (self._collapsed && children.length > 1) { - self.wrapper.removeChild(self.tooltip); - } - else { - self.wrapper.appendChild(self.tooltip); - } - } - - function elementDrag(e: PointerEvent) { - e = e || window.event; - //e.preventDefault(); - // calculate the new cursor position: - pos1 = pos3 - e.clientX; - pos2 = pos4 - e.clientY; - pos3 = e.clientX; - pos4 = e.clientY; - // set the element's new position: - // elmnt.style.top = (elmnt.offsetTop - pos2) + "px"; - // elmnt.style.left = (elmnt.offsetLeft - pos1) + "px"; - - self.wrapper.style.top = (self.wrapper.offsetTop - pos2) + "px"; - self.wrapper.style.left = (self.wrapper.offsetLeft - pos1) + "px"; - } - - function closeDragElement() { - // stop moving when mouse button is released: - document.onpointerup = null; - document.onpointermove = null; - //self.highlightSearchTerms(self.state, ["hello"]); - //FormattedTextBox.Instance.unhighlightSearchTerms(); - } - } - // makeLinkWithState = (state: EditorState, target: string, location: string) => { // let link = state.schema.mark(state.schema.marks.link, { href: target, location: location }); // } @@ -616,10 +599,70 @@ export class TooltipTextMenu { } } } + } - + deleteLinkItem() { + const icon = { + height: 16, width: 16, + path: "M15.898,4.045c-0.271-0.272-0.713-0.272-0.986,0l-4.71,4.711L5.493,4.045c-0.272-0.272-0.714-0.272-0.986,0s-0.272,0.714,0,0.986l4.709,4.711l-4.71,4.711c-0.272,0.271-0.272,0.713,0,0.986c0.136,0.136,0.314,0.203,0.492,0.203c0.179,0,0.357-0.067,0.493-0.203l4.711-4.711l4.71,4.711c0.137,0.136,0.314,0.203,0.494,0.203c0.178,0,0.355-0.067,0.492-0.203c0.273-0.273,0.273-0.715,0-0.986l-4.711-4.711l4.711-4.711C16.172,4.759,16.172,4.317,15.898,4.045z" + }; + return new MenuItem({ + title: "Delete Link", + label: "X", + icon: icon, + css: "color: red", + class: "summarize", + execEvent: "", + run: (state, dispatch) => { + this.deleteLink(); + } + }); } + createLink() { + let markType = schema.marks.link; + return new MenuItem({ + title: "Add or remove link", + label: "Add or remove link", + execEvent: "", + icon: icons.link, + css: "color:white;", + class: "menuicon", + enable(state) { return !state.selection.empty; }, + run: (state, dispatch, view) => { + // to remove link + let curLink = ""; + if (this.markActive(state, markType)) { + + let { from, $from, to, empty } = state.selection; + let node = state.doc.nodeAt(from); + node && node.marks.map(m => { + m.type === markType && (curLink = m.attrs.href); + }); + //toggleMark(markType)(state, dispatch); + //return true; + } + // to create link + openPrompt({ + title: "Create a link", + fields: { + href: new TextField({ + value: curLink, + label: "Link Target", + required: true + }), + title: new TextField({ label: "Title" }) + }, + callback(attrs: any) { + toggleMark(markType, attrs)(view.state, view.dispatch); + view.focus(); + }, + flyout_top: 0, + flyout_left: 0 + }); + } + }); + } //will display a remove-list-type button if selection is in list, otherwise will show list type dropdown updateListItemDropdown(label: string, listTypeBtn: any) { @@ -644,108 +687,6 @@ export class TooltipTextMenu { return listTypeBtn; } - //for a specific grouping of marks (passed in), remove all and apply the passed-in one to the selected text - changeToMarkInGroup = (markType: MarkType | undefined, view: EditorView, fontMarks: MarkType[]) => { - let { $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; - 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 of ranges) { - if (has) { - toggleMark(type)(view.state, view.dispatch, view); - } - } - } - } - }); - - if (markType) { - // fontsize - if (markType.name[0] === 'p') { - let size = this.fontSizeToNum.get(markType); - if (size) { this.updateFontSizeDropdown(String(size) + " pt"); } - if (this.editorProps) { - let ruleProvider = this.editorProps.ruleProvider; - let heading = NumCast(this.editorProps.Document.heading); - if (ruleProvider && heading) { - ruleProvider["ruleSize_" + heading] = size; - } - } - } - else { - let fontName = this.fontStylesToName.get(markType); - if (fontName) { this.updateFontStyleDropdown(fontName); } - if (this.editorProps) { - let ruleProvider = this.editorProps.ruleProvider; - let heading = NumCast(this.editorProps.Document.heading); - if (ruleProvider && heading) { - ruleProvider["ruleFont_" + heading] = fontName; - } - } - } - //actually apply font - if ((view.state.selection as any).node && (view.state.selection as any).node.type === view.state.schema.nodes.ordered_list) { - let status = updateBullets(view.state.tr.setNodeMarkup(view.state.selection.from, (view.state.selection as any).node.type, - { ...(view.state.selection as NodeSelection).node.attrs, setFontFamily: markType.name, setFontSize: Number(markType.name.replace(/p/, "")) }), view.state.schema); - view.dispatch(status.setSelection(new NodeSelection(status.doc.resolve(view.state.selection.from)))); - } - else toggleMark(markType)(view.state, view.dispatch, view); - } - } - - //remove all node typeand apply the passed-in one to the selected text - changeToNodeType = (nodeType: NodeType | undefined, view: EditorView) => { - //remove oldif (nodeType) { //add new - if (nodeType === schema.nodes.bullet_list) { - wrapInList(nodeType)(view.state, view.dispatch); - } else { - var marks = view.state.storedMarks || (view.state.selection.$to.parentOffset && view.state.selection.$from.marks()); - if (!wrapInList(schema.nodes.ordered_list)(view.state, (tx2: any) => { - let tx3 = updateBullets(tx2, schema, (nodeType as any).attrs.mapStyle); - marks && tx3.ensureMarks([...marks]); - marks && tx3.setStoredMarks([...marks]); - - view.dispatch(tx2); - })) { - let tx2 = view.state.tr; - let tx3 = nodeType ? updateBullets(tx2, schema, (nodeType as any).attrs.mapStyle) : tx2; - marks && tx3.ensureMarks([...marks]); - marks && tx3.setStoredMarks([...marks]); - - view.dispatch(tx3); - } - } - } - - //makes a button for the drop down FOR MARKS - //css is the style you want applied to the button - dropdownMarkBtn(label: string, css: string, markType: MarkType, view: EditorView, changeToMarkInGroup: (markType: MarkType, view: EditorView, groupMarks: MarkType[]) => any, groupMarks: MarkType[]) { - return new MenuItem({ - title: "", - label: label, - execEvent: "", - class: "menuicon", - css: css, - enable() { return true; }, - run() { - changeToMarkInGroup(markType, view, groupMarks); - } - }); - } - createStar() { return new MenuItem({ title: "Summarize", @@ -772,6 +713,17 @@ export class TooltipTextMenu { return true; } + public static insertComment(state: EditorState, dispatch: any) { + if (state.selection.empty) return false; + let mark = state.schema.marks.highlight.create(); + let tr = state.tr; + tr.addMark(state.selection.from, state.selection.to, mark); + let content = tr.selection.content(); + let newNode = state.schema.nodes.star.create({ visibility: false, text: content, textslice: content.toJSON() }); + dispatch && dispatch(tr.replaceSelectionWith(newNode).removeMark(tr.selection.from - 1, tr.selection.from, mark)); + return true; + } + createHighlightTool() { return new MenuItem({ title: "Highlight", @@ -975,24 +927,6 @@ export class TooltipTextMenu { return colorDropdown; } - deleteLinkItem() { - const icon = { - height: 16, width: 16, - path: "M15.898,4.045c-0.271-0.272-0.713-0.272-0.986,0l-4.71,4.711L5.493,4.045c-0.272-0.272-0.714-0.272-0.986,0s-0.272,0.714,0,0.986l4.709,4.711l-4.71,4.711c-0.272,0.271-0.272,0.713,0,0.986c0.136,0.136,0.314,0.203,0.492,0.203c0.179,0,0.357-0.067,0.493-0.203l4.711-4.711l4.71,4.711c0.137,0.136,0.314,0.203,0.494,0.203c0.178,0,0.355-0.067,0.492-0.203c0.273-0.273,0.273-0.715,0-0.986l-4.711-4.711l4.711-4.711C16.172,4.759,16.172,4.317,15.898,4.045z" - }; - return new MenuItem({ - title: "Delete Link", - label: "X", - icon: icon, - css: "color: red", - class: "summarize", - execEvent: "", - run: (state, dispatch) => { - this.deleteLink(); - } - }); - } - createBrush(active: boolean = false) { const icon = { height: 32, width: 32, @@ -1020,8 +954,6 @@ export class TooltipTextMenu { }); } - // selectionchanged event handler - brush_function(state: EditorState, dispatch: any) { if (TooltipTextMenuManager.Instance._brushIsEmpty) { const selected_marks = this.getMarksInSelection(this.view.state); @@ -1055,8 +987,6 @@ export class TooltipTextMenu { } } } - - } createBrushDropdown(active: boolean = false) { @@ -1120,88 +1050,106 @@ export class TooltipTextMenu { return brushDom; } - // createCollapse() { - // this._collapseBtn = new MenuItem({ - // title: "Collapse", - // //label: "Collapse", - // icon: icons.join, - // execEvent: "", - // css: "color:white;", - // class: "summarize", - // run: () => { - // this.collapseToolTip(); - // } - // }); - // } - // collapseToolTip() { - // if (this._collapseBtn) { - // if (this._collapseBtn.spec.title === "Collapse") { - // // const newcollapseBtn = new MenuItem({ - // // title: "Expand", - // // icon: icons.join, - // // execEvent: "", - // // css: "color:white;", - // // class: "summarize", - // // run: (state, dispatch, view) => { - // // this.collapseToolTip(); - // // } - // // }); - // // this.tooltip.replaceChild(newcollapseBtn.render(this.view).dom, this._collapseBtn.render(this.view).dom); - // // this._collapseBtn = newcollapseBtn; - // this.tooltip.style.width = "30px"; - // this._collapseBtn.spec.title = "Expand"; - // this._collapseBtn.render(this.view); - // } - // else { - // this._collapseBtn.spec.title = "Collapse"; - // this.tooltip.style.width = "550px"; - // this._collapseBtn.render(this.view); - // } - // } - // } - createLink() { - let markType = schema.marks.link; - return new MenuItem({ - title: "Add or remove link", - label: "Add or remove link", - execEvent: "", - icon: icons.link, - css: "color:white;", - class: "menuicon", - enable(state) { return !state.selection.empty; }, - run: (state, dispatch, view) => { - // to remove link - let curLink = ""; - if (this.markActive(state, markType)) { + //for a specific grouping of marks (passed in), remove all and apply the passed-in one to the selected text + changeToMarkInGroup = (markType: MarkType | undefined, view: EditorView, fontMarks: MarkType[]) => { + let { $cursor, ranges } = view.state.selection as TextSelection; + let state = view.state; + let dispatch = view.dispatch; - let { from, $from, to, empty } = state.selection; - let node = state.doc.nodeAt(from); - node && node.marks.map(m => { - m.type === markType && (curLink = m.attrs.href); - }); - //toggleMark(markType)(state, dispatch); - //return true; + //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; + 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 of ranges) { + if (has) { + toggleMark(type)(view.state, view.dispatch, view); + } + } } - // to create link - openPrompt({ - title: "Create a link", - fields: { - href: new TextField({ - value: curLink, - label: "Link Target", - required: true - }), - title: new TextField({ label: "Title" }) - }, - callback(attrs: any) { - toggleMark(markType, attrs)(view.state, view.dispatch); - view.focus(); - }, - flyout_top: 0, - flyout_left: 0 - }); + } + }); + + if (markType) { + // fontsize + if (markType.name[0] === 'p') { + let size = this.fontSizeToNum.get(markType); + if (size) { this.updateFontSizeDropdown(String(size) + " pt"); } + if (this.editorProps) { + let ruleProvider = this.editorProps.ruleProvider; + let heading = NumCast(this.editorProps.Document.heading); + if (ruleProvider && heading) { + ruleProvider["ruleSize_" + heading] = size; + } + } + } + else { + let fontName = this.fontStylesToName.get(markType); + if (fontName) { this.updateFontStyleDropdown(fontName); } + if (this.editorProps) { + let ruleProvider = this.editorProps.ruleProvider; + let heading = NumCast(this.editorProps.Document.heading); + if (ruleProvider && heading) { + ruleProvider["ruleFont_" + heading] = fontName; + } + } + } + //actually apply font + if ((view.state.selection as any).node && (view.state.selection as any).node.type === view.state.schema.nodes.ordered_list) { + let status = updateBullets(view.state.tr.setNodeMarkup(view.state.selection.from, (view.state.selection as any).node.type, + { ...(view.state.selection as NodeSelection).node.attrs, setFontFamily: markType.name, setFontSize: Number(markType.name.replace(/p/, "")) }), view.state.schema); + view.dispatch(status.setSelection(new NodeSelection(status.doc.resolve(view.state.selection.from)))); + } + else toggleMark(markType)(view.state, view.dispatch, view); + } + } + + //remove all node typeand apply the passed-in one to the selected text + changeToNodeType = (nodeType: NodeType | undefined, view: EditorView) => { + //remove oldif (nodeType) { //add new + if (nodeType === schema.nodes.bullet_list) { + wrapInList(nodeType)(view.state, view.dispatch); + } else { + var marks = view.state.storedMarks || (view.state.selection.$to.parentOffset && view.state.selection.$from.marks()); + if (!wrapInList(schema.nodes.ordered_list)(view.state, (tx2: any) => { + let tx3 = updateBullets(tx2, schema, (nodeType as any).attrs.mapStyle); + marks && tx3.ensureMarks([...marks]); + marks && tx3.setStoredMarks([...marks]); + + view.dispatch(tx2); + })) { + let tx2 = view.state.tr; + let tx3 = nodeType ? updateBullets(tx2, schema, (nodeType as any).attrs.mapStyle) : tx2; + marks && tx3.ensureMarks([...marks]); + marks && tx3.setStoredMarks([...marks]); + + view.dispatch(tx3); + } + } + } + + //makes a button for the drop down FOR MARKS + //css is the style you want applied to the button + dropdownMarkBtn(label: string, css: string, markType: MarkType, view: EditorView, changeToMarkInGroup: (markType: MarkType, view: EditorView, groupMarks: MarkType[]) => any, groupMarks: MarkType[]) { + return new MenuItem({ + title: "", + label: label, + execEvent: "", + class: "dropdown-item", + css: css, + enable() { return true; }, + run() { + changeToMarkInGroup(markType, view, groupMarks); } }); } @@ -1213,7 +1161,7 @@ export class TooltipTextMenu { title: "", label: label, execEvent: "", - class: "menuicon", + class: "dropdown-item", css: css, enable() { return true; }, run() { @@ -1390,35 +1338,12 @@ export class TooltipTextMenu { update_mark_doms() { this.reset_mark_doms(); - let foundlink = false; - // let children = this.extras.childNodes; this._activeMarks.forEach((mark) => { if (this._marksToDoms.has(mark)) { let dom = this._marksToDoms.get(mark); if (dom) dom.style.color = "greenyellow"; } - // if (children.length > 1) { - // foundlink = true; - // } - // if (mark.type.name === "link" && children.length === 1) { - // // let del = document.createElement("button"); - // // del.textContent = "X"; - // // del.style.color = "red"; - // // del.style.height = "10px"; - // // del.style.width = "10px"; - // // del.style.marginLeft = "5px"; - // // del.onclick = this.deleteLink; - // // this.extras.appendChild(del); - // let del = this.deleteLinkItem().render(this.view).dom; - // this.extras.appendChild(del); - // foundlink = true; - // } }); - // if (!foundlink) { - // if (children.length > 1) { - // this.extras.removeChild(children[1]); - // } - // } // keeps brush tool highlighted if active when switching between textboxes if (!TooltipTextMenuManager.Instance._brushIsEmpty) { @@ -1535,30 +1460,11 @@ class TooltipTextMenuManager { return TooltipTextMenuManager._instance; } - // private pinnedToUnpinned() { - // let position = MainOverlayTextBox.Instance.position; - - // this.unpinnedX = this.pinnedX - position[0]; - // this.unpinnedY = this.pinnedY - position[1]; - // } - - // private unpinnedToPinned() { - // let position = MainOverlayTextBox.Instance.position; - - // this.pinnedX = position[0] + this.unpinnedX; - // this.pinnedY = position[1] + this.unpinnedY; - // } - public get isPinned() { return this._isPinned; } public toggleIsPinned() { - // if (this._isPinned) { - // this.pinnedToUnpinned(); - // } else { - // this.unpinnedToPinned(); - // } this._isPinned = !this._isPinned; } } -- cgit v1.2.3-70-g09d2 From 1f1707c8756c858e45e1f89cd0ff035231e15225 Mon Sep 17 00:00:00 2001 From: Bob Zeleznik Date: Fri, 15 Nov 2019 13:52:23 -0500 Subject: fixed errors --- .vscode/settings.json | 2 +- src/client/util/TooltipTextMenu.tsx | 8 +++----- 2 files changed, 4 insertions(+), 6 deletions(-) (limited to 'src') diff --git a/.vscode/settings.json b/.vscode/settings.json index c999af8b8..0b45c3f46 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -11,5 +11,5 @@ "typescript.format.insertSpaceAfterOpeningAndBeforeClosingNonemptyBrackets": false, "typescript.format.insertSpaceAfterOpeningAndBeforeClosingNonemptyBraces": true, "search.usePCRE2": true, - "typescript.tsdk": "node_modules\\typescript\\lib" + "typescript.tsdk": "node_modules/typescript/lib" } \ No newline at end of file diff --git a/src/client/util/TooltipTextMenu.tsx b/src/client/util/TooltipTextMenu.tsx index 78917c779..0dc63668b 100644 --- a/src/client/util/TooltipTextMenu.tsx +++ b/src/client/util/TooltipTextMenu.tsx @@ -33,12 +33,10 @@ export class TooltipTextMenu { // editor state properties private view: EditorView; private editorProps: FieldViewProps & FormattedTextBoxProps | undefined; - - private fontStyles: MarkType[] = []; - private fontSizes: MarkType[] = []; + + private fontStyles: Mark[] = []; + private fontSizes: Mark[] = []; private listTypes: (NodeType | any)[] = []; - private fontSizeToNum: Map = new Map(); - private fontStylesToName: Map = new Map(); private listTypeToIcon: Map = new Map(); private _activeMarks: Mark[] = []; private _marksToDoms: Map = new Map(); -- cgit v1.2.3-70-g09d2 From 1dd4ab8bfdacb50512d3ec9bce6945e476d9686f Mon Sep 17 00:00:00 2001 From: Bob Zeleznik Date: Fri, 15 Nov 2019 14:15:38 -0500 Subject: switched making text links to the standard doc decs link button. --- src/client/views/DocumentButtonBar.tsx | 26 ++++++++++++++++++++------ src/client/views/nodes/FormattedTextBox.tsx | 9 ++++----- 2 files changed, 24 insertions(+), 11 deletions(-) (limited to 'src') diff --git a/src/client/views/DocumentButtonBar.tsx b/src/client/views/DocumentButtonBar.tsx index ba87ecfb4..1412316f9 100644 --- a/src/client/views/DocumentButtonBar.tsx +++ b/src/client/views/DocumentButtonBar.tsx @@ -5,7 +5,7 @@ import { action, observable, runInAction } from "mobx"; import { observer } from "mobx-react"; import { Doc } from "../../new_fields/Doc"; import { RichTextField } from '../../new_fields/RichTextField'; -import { NumCast } from "../../new_fields/Types"; +import { NumCast, StrCast } from "../../new_fields/Types"; import { emptyFunction } from "../../Utils"; import { Pulls, Pushes } from '../apis/google_docs/GoogleApiClientUtils'; import { DragLinksAsDocuments, DragManager } from "../util/DragManager"; @@ -135,13 +135,27 @@ export class DocumentButtonBar extends React.Component<{ views: DocumentView[], if (this._linkerButton.current !== null) { document.removeEventListener("pointermove", this.onLinkerButtonMoved); document.removeEventListener("pointerup", this.onLinkerButtonUp); - let selDoc = this.props.views[0]; - let container = selDoc.props.ContainingCollectionDoc ? selDoc.props.ContainingCollectionDoc.proto : undefined; - let dragData = new DragManager.LinkDragData(selDoc.props.Document, container ? [container] : []); - let _linkDrag = UndoManager.StartBatch("Drag Link"); + let docView = this.props.views[0]; + let container = docView.props.ContainingCollectionDoc ? docView.props.ContainingCollectionDoc.proto : undefined; + let dragData = new DragManager.LinkDragData(docView.props.Document, container ? [container] : []); + let linkDrag = UndoManager.StartBatch("Drag Link"); DragManager.StartLinkDrag(this._linkerButton.current, dragData, e.pageX, e.pageY, { handlers: { - dragComplete: () => _linkDrag && _linkDrag.end() + dragComplete: () => { + let tooltipmenu = FormattedTextBox.ToolTipTextMenu; + let linkDoc = dragData.linkDocument; + if (linkDoc && tooltipmenu) { + let proto = Doc.GetProto(linkDoc); + if (proto && docView) { + proto.sourceContext = docView.props.ContainingCollectionDoc; + } + let text = tooltipmenu.makeLink(linkDoc, StrCast(linkDoc.anchor2.title), e.ctrlKey ? "onRight" : "inTab"); + if (linkDoc instanceof Doc && linkDoc.anchor2 instanceof Doc) { + proto.title = text === "" ? proto.title : text + " to " + linkDoc.anchor2.title; // TODODO open to more descriptive descriptions of following in text link + } + } + linkDrag && linkDrag.end(); + } }, hideSource: false }); diff --git a/src/client/views/nodes/FormattedTextBox.tsx b/src/client/views/nodes/FormattedTextBox.tsx index b7df3fb0e..0a535045b 100644 --- a/src/client/views/nodes/FormattedTextBox.tsx +++ b/src/client/views/nodes/FormattedTextBox.tsx @@ -24,7 +24,6 @@ import { DocServer } from "../../DocServer"; import { Docs, DocUtils } from '../../documents/Documents'; import { DocumentType } from '../../documents/DocumentTypes'; import { DictationManager } from '../../util/DictationManager'; -import { DocumentManager } from '../../util/DocumentManager'; import { DragManager } from "../../util/DragManager"; import buildKeymap from "../../util/ProsemirrorExampleTransfer"; import { inpRules } from "../../util/RichTextRules"; @@ -75,7 +74,7 @@ export class FormattedTextBox extends DocAnnotatableComponent<(FieldViewProps & public static LayoutString(fieldStr: string) { return FieldView.LayoutString(FormattedTextBox, fieldStr); } public static blankState = () => EditorState.create(FormattedTextBox.Instance.config); public static Instance: FormattedTextBox; - private static _toolTipTextMenu: TooltipTextMenu | undefined = undefined; + public static ToolTipTextMenu: TooltipTextMenu | undefined = undefined; private _ref: React.RefObject = React.createRef(); private _proseRef?: HTMLDivElement; private _editorView: Opt; @@ -120,7 +119,7 @@ export class FormattedTextBox extends DocAnnotatableComponent<(FieldViewProps & } public static getToolTip(ev: EditorView) { - return this._toolTipTextMenu ? this._toolTipTextMenu : this._toolTipTextMenu = new TooltipTextMenu(ev); + return this.ToolTipTextMenu ? this.ToolTipTextMenu : this.ToolTipTextMenu = new TooltipTextMenu(ev); } @undoBatch @@ -962,7 +961,7 @@ export class FormattedTextBox extends DocAnnotatableComponent<(FieldViewProps & let self = FormattedTextBox; return new Plugin({ view(newView) { - return self._toolTipTextMenu = FormattedTextBox.getToolTip(newView); + return self.ToolTipTextMenu = FormattedTextBox.getToolTip(newView); } }); } @@ -1020,7 +1019,7 @@ export class FormattedTextBox extends DocAnnotatableComponent<(FieldViewProps & let rounded = StrCast(this.layoutDoc.borderRounding) === "100%" ? "-rounded" : ""; let interactive = InkingControl.Instance.selectedTool || this.layoutDoc.isBackground; if (this.props.isSelected()) { - FormattedTextBox._toolTipTextMenu!.updateFromDash(this._editorView!, undefined, this.props); + FormattedTextBox.ToolTipTextMenu!.updateFromDash(this._editorView!, undefined, this.props); } else if (FormattedTextBoxComment.textBox === this) { FormattedTextBoxComment.Hide(); } -- cgit v1.2.3-70-g09d2 From 79c66275c60001e6d7aa997d9802ec0fed893205 Mon Sep 17 00:00:00 2001 From: Bob Zeleznik Date: Fri, 15 Nov 2019 15:03:08 -0500 Subject: fixed toolbar disappearing with multiple text boxes. --- src/client/util/TooltipTextMenu.tsx | 6 ++---- src/client/views/DocumentDecorations.tsx | 4 ++-- src/client/views/nodes/FormattedTextBox.tsx | 2 +- 3 files changed, 5 insertions(+), 7 deletions(-) (limited to 'src') diff --git a/src/client/util/TooltipTextMenu.tsx b/src/client/util/TooltipTextMenu.tsx index 0dc63668b..14f8b2bd4 100644 --- a/src/client/util/TooltipTextMenu.tsx +++ b/src/client/util/TooltipTextMenu.tsx @@ -1,6 +1,4 @@ -import { library, dom } from '@fortawesome/fontawesome-svg-core'; -import { faListUl } from '@fortawesome/free-solid-svg-icons'; -import { action, observable } from "mobx"; +import { action } from "mobx"; import { Dropdown, icons, MenuItem } from "prosemirror-menu"; //no import css import { Mark, MarkType, Node as ProsNode, NodeType, ResolvedPos, Schema } from "prosemirror-model"; import { wrapInList } from 'prosemirror-schema-list'; @@ -1482,7 +1480,7 @@ export class TooltipTextMenu { } destroy() { - this.wrapper.remove(); + // this.wrapper.remove(); } } diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx index 49921170f..890c32bcb 100644 --- a/src/client/views/DocumentDecorations.tsx +++ b/src/client/views/DocumentDecorations.tsx @@ -548,8 +548,8 @@ export class DocumentDecorations extends React.Component<{}, { value: string }> } } public showTextBar = () => { - if (this.TextBar) { - TooltipTextMenu.Toolbar && Array.from(this.TextBar.childNodes).indexOf(TooltipTextMenu.Toolbar) === -1 && this.TextBar.appendChild(TooltipTextMenu.Toolbar); + if (this.TextBar && TooltipTextMenu.Toolbar && Array.from(this.TextBar.childNodes).indexOf(TooltipTextMenu.Toolbar) === -1) { + this.TextBar.appendChild(TooltipTextMenu.Toolbar); } } render() { diff --git a/src/client/views/nodes/FormattedTextBox.tsx b/src/client/views/nodes/FormattedTextBox.tsx index 0a535045b..35212b732 100644 --- a/src/client/views/nodes/FormattedTextBox.tsx +++ b/src/client/views/nodes/FormattedTextBox.tsx @@ -17,7 +17,7 @@ import { Copy, Id } from '../../../new_fields/FieldSymbols'; import { RichTextField } from "../../../new_fields/RichTextField"; import { RichTextUtils } from '../../../new_fields/RichTextUtils'; import { createSchema, makeInterface } from "../../../new_fields/Schema"; -import { Cast, DateCast, NumCast, StrCast } from "../../../new_fields/Types"; +import { Cast, NumCast, StrCast } from "../../../new_fields/Types"; import { numberRange, Utils, addStyleSheet, addStyleSheetRule, clearStyleSheetRules, emptyFunction, returnOne } from '../../../Utils'; import { GoogleApiClientUtils, Pulls, Pushes } from '../../apis/google_docs/GoogleApiClientUtils'; import { DocServer } from "../../DocServer"; -- cgit v1.2.3-70-g09d2 From 2ab22e64e9b95e6b76566c493ae90f5951475e72 Mon Sep 17 00:00:00 2001 From: Bob Zeleznik Date: Fri, 15 Nov 2019 15:11:26 -0500 Subject: fixed naming of 'marker' mark --- src/client/util/RichTextSchema.tsx | 2 +- src/client/util/TooltipTextMenu.tsx | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) (limited to 'src') diff --git a/src/client/util/RichTextSchema.tsx b/src/client/util/RichTextSchema.tsx index 0e8c9f14c..0a717dff1 100644 --- a/src/client/util/RichTextSchema.tsx +++ b/src/client/util/RichTextSchema.tsx @@ -326,7 +326,7 @@ export const marks: { [index: string]: MarkSpec } = { } }, - highlight2: { + marker: { attrs: { highlight: { default: "transparent" } }, diff --git a/src/client/util/TooltipTextMenu.tsx b/src/client/util/TooltipTextMenu.tsx index 14f8b2bd4..5136089b3 100644 --- a/src/client/util/TooltipTextMenu.tsx +++ b/src/client/util/TooltipTextMenu.tsx @@ -735,7 +735,7 @@ export class TooltipTextMenu { public static insertHighlight(color: String, state: EditorState, dispatch: any) { if (state.selection.empty) return false; - let highlightMark = state.schema.mark(state.schema.marks.highlight2, { highlight: color }); + let highlightMark = state.schema.mark(state.schema.marks.marker, { highlight: color }); dispatch(state.tr.addMark(state.selection.from, state.selection.to, highlightMark)); } @@ -1223,7 +1223,7 @@ export class TooltipTextMenu { }); } - markActive = function (state: EditorState, type: MarkType>) { + markActive = function(state: EditorState, type: MarkType>) { let { from, $from, to, empty } = state.selection; if (empty) return type.isInSet(state.storedMarks || $from.marks()); else return state.doc.rangeHasMark(from, to, type); -- cgit v1.2.3-70-g09d2