aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/client/util/RichTextSchema.tsx11
-rw-r--r--src/client/util/TooltipTextMenu.scss57
-rw-r--r--src/client/util/TooltipTextMenu.tsx251
-rw-r--r--src/client/util/request-image-size.js2
-rw-r--r--src/client/views/MainOverlayTextBox.tsx3
-rw-r--r--src/client/views/MainView.tsx3
-rw-r--r--src/client/views/SearchItem.tsx69
-rw-r--r--src/client/views/nodes/FormattedTextBox.tsx91
-rw-r--r--src/client/views/search/FilterBox.scss56
-rw-r--r--src/client/views/search/FilterBox.tsx73
-rw-r--r--src/client/views/search/Pager.tsx78
-rw-r--r--src/client/views/search/SearchBox.scss5
-rw-r--r--src/client/views/search/SearchItem.tsx120
-rw-r--r--src/client/views/search/SelectorContextMenu.scss1
-rw-r--r--src/client/views/search/ToggleBar.tsx1
-rw-r--r--src/debug/Viewer.tsx3
-rw-r--r--src/new_fields/RichTextField.ts2
-rw-r--r--src/server/index.ts3
18 files changed, 709 insertions, 120 deletions
diff --git a/src/client/util/RichTextSchema.tsx b/src/client/util/RichTextSchema.tsx
index 269de0f42..ce9e29b26 100644
--- a/src/client/util/RichTextSchema.tsx
+++ b/src/client/util/RichTextSchema.tsx
@@ -283,7 +283,7 @@ export const marks: { [index: string]: MarkSpec } = {
},
highlight: {
- parseDOM: [{ style: 'background: #d9dbdd' }],
+ parseDOM: [{ style: 'color: blue' }],
toDOM() {
return ['span', {
style: 'color: blue'
@@ -291,6 +291,15 @@ export const marks: { [index: string]: MarkSpec } = {
}
},
+ search_highlight: {
+ parseDOM: [{ style: 'background: yellow' }],
+ toDOM() {
+ return ['span', {
+ style: 'background: yellow'
+ }];
+ }
+ },
+
// :: MarkSpec Code font mark. Represented as a `<code>` element.
code: {
diff --git a/src/client/util/TooltipTextMenu.scss b/src/client/util/TooltipTextMenu.scss
index 40ac3abb9..864c17cac 100644
--- a/src/client/util/TooltipTextMenu.scss
+++ b/src/client/util/TooltipTextMenu.scss
@@ -18,7 +18,8 @@
.ProseMirror-menuitem {
margin-right: 3px;
display: inline-block;
- z-index: 100000;
+ z-index: 50000;
+ position: relative;
}
.ProseMirror-menuseparator {
@@ -67,7 +68,7 @@
}
.ProseMirror-menu-dropdown-menu {
- z-index: 100000;
+ z-index: 50000;
min-width: 6em;
background: white;
position: absolute;
@@ -235,8 +236,8 @@
}
.tooltipMenu {
- position: relative;
- z-index: 2000;
+ position: absolute;
+ z-index: 20000;
background: #121721;
border: 1px solid silver;
border-radius: 15px;
@@ -247,7 +248,7 @@
//transform: translateX(-50%);
transform: translateY(-85px);
pointer-events: all;
- height: 30px;
+ height: fit-content;
width:550px;
.ProseMirror-example-setup-style hr {
padding: 2px 10px;
@@ -264,29 +265,6 @@
}
}
-// .tooltipMenu:before {
-// content: "";
-// height: 0; width: 0;
-// position: absolute;
-// left: 50%;
-// margin-left: -5px;
-// bottom: -6px;
-// border: 5px solid transparent;
-// border-bottom-width: 0;
-// border-top-color: silver;
-// }
-// .tooltipMenu:after {
-// content: "";
-// height: 0; width: 0;
-// position: absolute;
-// left: 50%;
-// margin-left: -5px;
-// bottom: -4.5px;
-// border: 5px solid transparent;
-// border-bottom-width: 0;
-// border-top-color: $dark-color;
-// }
-
.menuicon {
display: inline-block;
border-right: 1px solid white(0, 0, 0, 0.2);
@@ -298,6 +276,7 @@
cursor: pointer;
text-align: center;
min-width: 10px;
+
}
.strong, .heading { font-weight: bold; }
.em { font-style: italic; }
@@ -310,9 +289,27 @@
padding-right: 0px;
}
.summarize{
- //margin-left: 15px;
color: white;
height: 20px;
- // background-color: white;
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;
} \ No newline at end of file
diff --git a/src/client/util/TooltipTextMenu.tsx b/src/client/util/TooltipTextMenu.tsx
index a4c053de2..b3b7848e8 100644
--- a/src/client/util/TooltipTextMenu.tsx
+++ b/src/client/util/TooltipTextMenu.tsx
@@ -1,4 +1,6 @@
import { action } from "mobx";
+import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
+import { faArrowUp, faTag, faPlus } from '@fortawesome/free-solid-svg-icons';
import { Dropdown, MenuItem, icons, } from "prosemirror-menu"; //no import css
import { EditorState, NodeSelection, TextSelection } from "prosemirror-state";
import { EditorView } from "prosemirror-view";
@@ -18,7 +20,10 @@ import { DocServer } from "../DocServer";
import { CollectionDockingView } from "../views/collections/CollectionDockingView";
import { DocumentManager } from "./DocumentManager";
import { Id } from "../../new_fields/FieldSymbols";
-import { FormattedTextBoxProps } from "../views/nodes/FormattedTextBox";
+import { FormattedTextBoxProps, FormattedTextBox } from "../views/nodes/FormattedTextBox";
+import { typeAlias } from "babel-types";
+import React from "react";
+import ReactDOM from "react-dom";
import { Utils } from "../../Utils";
//appears above a selection of text in a RichTextBox to give user options such as Bold, Italics, etc.
@@ -33,6 +38,8 @@ export class TooltipTextMenu {
private fontSizeToNum: Map<MarkType, number>;
private fontStylesToName: Map<MarkType, string>;
private listTypeToIcon: Map<NodeType, string>;
+ //private link: HTMLAnchorElement;
+ //private wrapper: HTMLDivElement;
private linkEditor?: HTMLDivElement;
private linkText?: HTMLDivElement;
@@ -46,11 +53,19 @@ export class TooltipTextMenu {
private _collapseBtn?: MenuItem;
+ private _brushMarks?: Set<Mark>;
+ private _brushIsEmpty: boolean = true;
+ private _brushdom?: Node;
+
+ private _marksToDoms: Map<Mark, HTMLSpanElement> = new Map();
+
constructor(view: EditorView, editorProps: FieldViewProps & FormattedTextBoxProps) {
this.view = view;
this.editorProps = editorProps;
+ //this.wrapper = document.createElement("div");
this.tooltip = document.createElement("div");
this.tooltip.className = "tooltipMenu";
+ this.dragElement(this.tooltip);
this.dragElement(this.tooltip);
// this.createCollapse();
@@ -71,13 +86,23 @@ export class TooltipTextMenu {
{ 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: wrapInList(schema.nodes.bullet_list), dom: this.icon(":", "bullets") },
- // { command: wrapInList(schema.nodes.ordered_list), dom: this.icon("1)", "bullets") },
- // { command: lift, dom: this.icon("<", "lift") },
];
+
+ this._marksToDoms = new Map();
//add menu items
items.forEach(({ dom, command }) => {
this.tooltip.appendChild(dom);
+ switch (dom.title) {
+ case "Bold":
+ this._marksToDoms.set(schema.mark(schema.marks.strong), dom);
+ break;
+ case "Italic":
+ this._marksToDoms.set(schema.mark(schema.marks.em), dom);
+ break;
+ case "Underline":
+ this._marksToDoms.set(schema.mark(schema.marks.underline), dom);
+ break;
+ }
//pointer down handler to activate button effects
dom.addEventListener("pointerdown", e => {
@@ -86,12 +111,17 @@ export class TooltipTextMenu {
if (dom.contains(e.target as Node)) {
e.stopPropagation();
command(view.state, view.dispatch, view);
+ if (this.view.state.selection.empty) {
+ if (dom.style.color === "white") { dom.style.color = "greenyellow"; }
+ else { dom.style.color = "white"; }
+ }
}
});
});
this.updateLinkMenu();
+
//list of font styles
this.fontStylesToName = new Map();
this.fontStylesToName.set(schema.marks.timesNewRoman, "Times New Roman");
@@ -125,19 +155,21 @@ export class TooltipTextMenu {
this.listTypeToIcon.set(schema.nodes.ordered_list, "1)");
this.listTypes = Array.from(this.listTypeToIcon.keys());
+ //custom tools
// this.tooltip.appendChild(this.createLink().render(this.view).dom);
+ 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);
-
-
this.updateListItemDropdown(":", this.listTypeBtnDom);
this.update(view, undefined);
//view.dom.parentNode!.parentNode!.insertBefore(this.tooltip, view.dom.parentNode);
- // quick and dirty null check
+ // add tooltip to outerdiv to circumvent scaling problem
const outer_div = this.editorProps.outer_div;
outer_div && outer_div(this.tooltip);
}
@@ -164,6 +196,49 @@ export class TooltipTextMenu {
this.fontSizeDom = newfontSizeDom;
}
+ // Make the DIV element draggable:
+
+ 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;
+ }
+ 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 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";
+ }
+
+ 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
updateFontStyleDropdown(label: string) {
//filtering function - might be unecessary
@@ -259,6 +334,8 @@ export class TooltipTextMenu {
},
hideSource: false
});
+ e.stopPropagation();
+ e.preventDefault();
};
this.linkEditor.appendChild(this.linkDrag);
// this.linkEditor.appendChild(this.linkText);
@@ -280,47 +357,6 @@ export class TooltipTextMenu {
// this.tooltip.appendChild(this.linkEditor);
}
- 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;
- }
- 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 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";
- }
-
- function closeDragElement() {
- // stop moving when mouse button is released:
- document.onpointerup = null;
- document.onpointermove = null;
- //self.highlightSearchTerms(self.state, ["hello"]);
- //FormattedTextBox.Instance.unhighlightSearchTerms();
- }
- }
-
makeLink = (target: string, location: 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 });
@@ -363,7 +399,7 @@ export class TooltipTextMenu {
}
//for a specific grouping of marks (passed in), remove all and apply the passed-in one to the selected text
- changeToMarkInGroup = (markType: MarkType, view: EditorView, fontMarks: MarkType[]) => {
+ changeToMarkInGroup = (markType: MarkType | undefined, view: EditorView, fontMarks: MarkType[]) => {
let { $cursor, ranges } = view.state.selection as TextSelection;
let state = view.state;
let dispatch = view.dispatch;
@@ -389,17 +425,23 @@ export class TooltipTextMenu {
}
}
});
- // fontsize
- if (markType.name[0] === 'p') {
- let size = this.fontSizeToNum.get(markType);
- if (size) { this.updateFontSizeDropdown(String(size) + " pt"); }
+
+ if (markType) {
+ // fontsize
+ if (markType.name[0] === 'p') {
+ let size = this.fontSizeToNum.get(markType);
+ if (size) { this.updateFontSizeDropdown(String(size) + " pt"); }
+ }
+ else {
+ let fontName = this.fontStylesToName.get(markType);
+ if (fontName) { this.updateFontStyleDropdown(fontName); }
+ }
+ //actually apply font
+ return toggleMark(markType)(view.state, view.dispatch, view);
}
else {
- let fontName = this.fontStylesToName.get(markType);
- if (fontName) { this.updateFontStyleDropdown(fontName); }
+ return;
}
- //actually apply font
- return toggleMark(markType)(view.state, view.dispatch, view);
}
//remove all node typeand apply the passed-in one to the selected text
@@ -442,6 +484,62 @@ export class TooltipTextMenu {
});
}
+ createBrush(active: boolean = false) {
+ const icon = {
+ 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"
+ };
+ return new MenuItem({
+ title: "Brush tool",
+ label: "Brush tool",
+ icon: icon,
+ css: "color:white;",
+ class: active ? "brush-active" : "brush",
+ execEvent: "",
+ run: (state, dispatch) => {
+ this.brush_function(state, dispatch);
+ },
+ active: (state) => {
+ return true;
+ }
+ });
+ }
+
+ // selectionchanged event handler
+
+ brush_function(state: EditorState<any>, dispatch: any) {
+ if (this._brushIsEmpty) {
+ const selected_marks = this.getMarksInSelection(this.view.state);
+ if (selected_marks.size > 0 && this._brushdom) {
+ this._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;
+ }
+ }
+ else {
+ let { from, to, $from } = this.view.state.selection;
+ if ($from && $from.nodeAfter && this._brushdom) {
+ if (this._brushMarks && to - from > 0) {
+ this.view.dispatch(this.view.state.tr.removeMark(from, to));
+ this._brushMarks.forEach((mark: Mark) => {
+ const markType = mark.type;
+ this.changeToMarkInGroup(markType, this.view, []);
+
+ });
+ }
+
+ const newbrush = this.createBrush(false).render(this.view).dom;
+ this.tooltip.replaceChild(newbrush, this._brushdom);
+ this._brushdom = newbrush;
+ this._brushIsEmpty = !this._brushIsEmpty;
+ }
+ }
+
+
+ }
+
createCollapse() {
this._collapseBtn = new MenuItem({
title: "Collapse",
@@ -597,14 +695,14 @@ export class TooltipTextMenu {
};
}
- getMarksInSelection(state: EditorState<any>, targets: MarkType<any>[]) {
- let found: Mark<any>[] = [];
+ getMarksInSelection(state: EditorState<any>) {
+ let found = new Set<Mark>();
let { from, to } = state.selection as TextSelection;
state.doc.nodesBetween(from, to, (node) => {
let marks = node.marks;
if (marks) {
marks.forEach(m => {
- if (targets.includes(m.type)) found.push(m);
+ found.add(m);
});
}
});
@@ -618,13 +716,18 @@ export class TooltipTextMenu {
if (lastState && lastState.doc.eq(state.doc) &&
lastState.selection.eq(state.selection)) return;
+ let iterator = this._marksToDoms.values();
+ let next = iterator.next();
+ while (!next.done) {
+ next.value.style.color = "white";
+ next = iterator.next();
+ }
+
// Hide the tooltip if the selection is empty
if (state.selection.empty) {
//this.tooltip.style.display = "none";
//return;
}
-
-
//UPDATE LIST ITEM DROPDOWN
//UPDATE FONT STYLE DROPDOWN
@@ -662,12 +765,18 @@ export class TooltipTextMenu {
}
}
this.view.dispatch(this.view.state.tr.setStoredMarks(this._activeMarks));
+ this._activeMarks.forEach((mark) => {
+ if (this._marksToDoms.has(mark)) {
+ let dom = this._marksToDoms.get(mark);
+ if (dom) dom.style.color = "greenyellow";
+ }
+ });
}
//finds all active marks on selection in given group
activeMarksOnSelection(markGroup: MarkType[]) {
//current selection
- let { empty, ranges } = this.view.state.selection as TextSelection;
+ let { empty, ranges, $to } = this.view.state.selection as TextSelection;
let state = this.view.state;
let dispatch = this.view.dispatch;
let activeMarks: MarkType[];
@@ -682,6 +791,9 @@ export class TooltipTextMenu {
}
return false;
});
+
+ const refnode = this.reference_node($to);
+ this._activeMarks = refnode.marks;
}
else {
const pos = this.view.state.selection.$from;
@@ -692,9 +804,7 @@ export class TooltipTextMenu {
else {
return [];
}
-
this._activeMarks = ref_node.marks;
-
activeMarks = markGroup.filter(mark_type => {
if (dispatch) {
let mark = state.schema.mark(mark_type);
@@ -713,12 +823,12 @@ export class TooltipTextMenu {
reference_node(pos: ResolvedPos<any>): ProsNode {
let ref_node: ProsNode = this.view.state.doc;
- if (pos.nodeAfter !== null && pos.nodeAfter !== undefined) {
- ref_node = pos.nodeAfter;
- }
- else if (pos.nodeBefore !== null && pos.nodeBefore !== undefined) {
+ if (pos.nodeBefore !== null && pos.nodeBefore !== undefined) {
ref_node = pos.nodeBefore;
}
+ else if (pos.nodeAfter !== null && pos.nodeAfter !== undefined) {
+ ref_node = pos.nodeAfter;
+ }
else if (pos.pos > 0) {
let skip = false;
for (let i: number = pos.pos - 1; i > 0; i--) {
@@ -731,6 +841,9 @@ export class TooltipTextMenu {
});
}
}
+ if (!ref_node.isLeaf) {
+ ref_node = ref_node.child(0);
+ }
return ref_node;
}
diff --git a/src/client/util/request-image-size.js b/src/client/util/request-image-size.js
index 27605d167..257990811 100644
--- a/src/client/util/request-image-size.js
+++ b/src/client/util/request-image-size.js
@@ -10,7 +10,7 @@
*/
const request = require('request');
-const imageSize = require('image-size');
+// const imageSize = require('image-size');
const HttpError = require('standard-http-error');
module.exports = function requestImageSize(options) {
diff --git a/src/client/views/MainOverlayTextBox.tsx b/src/client/views/MainOverlayTextBox.tsx
index 126efd11c..b2fb11afe 100644
--- a/src/client/views/MainOverlayTextBox.tsx
+++ b/src/client/views/MainOverlayTextBox.tsx
@@ -12,6 +12,7 @@ import "./MainOverlayTextBox.scss";
import { FormattedTextBox } from './nodes/FormattedTextBox';
interface MainOverlayTextBoxProps {
+ firstinstance?: boolean;
}
@observer
@@ -141,7 +142,7 @@ export class MainOverlayTextBox extends React.Component<MainOverlayTextBoxProps>
DataDoc={FormattedTextBox.InputBoxOverlay.props.DataDoc}
isSelected={returnTrue} select={emptyFunction} renderDepth={0} selectOnLoad={true}
ContainingCollectionView={undefined} whenActiveChanged={emptyFunction} active={returnTrue}
- ScreenToLocalTransform={this._textXf} PanelWidth={returnZero} PanelHeight={returnZero} focus={emptyFunction} addDocTab={this.addDocTab} outer_div={(tooltip: HTMLElement) => { this._tooltip = tooltip; this.updateTooltip(); }} />
+ ScreenToLocalTransform={this._textXf} PanelWidth={returnZero} PanelHeight={returnZero} focus={emptyFunction} addDocTab={this.addDocTab} firstinstance={this.props.firstinstance} outer_div={(tooltip: HTMLElement) => { this._tooltip = tooltip; this.updateTooltip(); }} />
</div>
</div>
</div>
diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx
index 94a4835a1..beb038f5b 100644
--- a/src/client/views/MainView.tsx
+++ b/src/client/views/MainView.tsx
@@ -441,6 +441,7 @@ export class MainView extends React.Component {
@observable isSearchVisible = false;
@action
toggleSearch = () => {
+ // console.log("search toggling")
this.isSearchVisible = !this.isSearchVisible;
}
@@ -455,7 +456,7 @@ export class MainView extends React.Component {
{this.nodesMenu()}
{this.miscButtons}
<PDFMenu />
- <MainOverlayTextBox />
+ <MainOverlayTextBox firstinstance={true} />
<OverlayView />
</div >
);
diff --git a/src/client/views/SearchItem.tsx b/src/client/views/SearchItem.tsx
new file mode 100644
index 000000000..13e4b88f7
--- /dev/null
+++ b/src/client/views/SearchItem.tsx
@@ -0,0 +1,69 @@
+import React = require("react");
+import { library } from '@fortawesome/fontawesome-svg-core';
+import { faCaretUp, faFilePdf, faFilm, faImage, faObjectGroup, faStickyNote } from '@fortawesome/free-solid-svg-icons';
+import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
+import { Doc } from "../../new_fields/Doc";
+import { DocumentManager } from "../util/DocumentManager";
+import { SetupDrag } from "../util/DragManager";
+
+
+export interface SearchProps {
+ doc: Doc;
+}
+
+library.add(faCaretUp);
+library.add(faObjectGroup);
+library.add(faStickyNote);
+library.add(faFilePdf);
+library.add(faFilm);
+
+export class SearchItem extends React.Component<SearchProps> {
+
+ onClick = () => {
+ DocumentManager.Instance.jumpToDocument(this.props.doc, false);
+ }
+
+ //needs help
+ // @computed get layout(): string { const field = Cast(this.props.doc[fieldKey], IconField); return field ? field.icon : "<p>Error loading icon data</p>"; }
+
+
+ public static DocumentIcon(layout: string) {
+ let button = layout.indexOf("PDFBox") !== -1 ? faFilePdf :
+ layout.indexOf("ImageBox") !== -1 ? faImage :
+ layout.indexOf("Formatted") !== -1 ? faStickyNote :
+ layout.indexOf("Video") !== -1 ? faFilm :
+ layout.indexOf("Collection") !== -1 ? faObjectGroup :
+ faCaretUp;
+ return <FontAwesomeIcon icon={button} className="documentView-minimizedIcon" />;
+ }
+ onPointerEnter = (e: React.PointerEvent) => {
+ this.props.doc.libraryBrush = true;
+ Doc.SetOnPrototype(this.props.doc, "protoBrush", true);
+ }
+ onPointerLeave = (e: React.PointerEvent) => {
+ this.props.doc.libraryBrush = false;
+ Doc.SetOnPrototype(this.props.doc, "protoBrush", false);
+ }
+
+ collectionRef = React.createRef<HTMLDivElement>();
+ startDocDrag = () => {
+ let doc = this.props.doc;
+ const isProto = Doc.GetT(doc, "isPrototype", "boolean", true);
+ if (isProto) {
+ return Doc.MakeDelegate(doc);
+ } else {
+ return Doc.MakeAlias(doc);
+ }
+ }
+ render() {
+ return (
+ <div className="search-item" ref={this.collectionRef} id="result"
+ onPointerEnter={this.onPointerEnter} onPointerLeave={this.onPointerLeave}
+ onClick={this.onClick} onPointerDown={SetupDrag(this.collectionRef, this.startDocDrag)} >
+ <div className="search-title" id="result" >title: {this.props.doc.title}</div>
+ {/* <div className="search-type" id="result" >Type: {this.props.doc.layout}</div> */}
+ {/* <div className="search-type" >{SearchItem.DocumentIcon(this.layout)}</div> */}
+ </div>
+ );
+ }
+} \ No newline at end of file
diff --git a/src/client/views/nodes/FormattedTextBox.tsx b/src/client/views/nodes/FormattedTextBox.tsx
index 0a79677e2..524500f35 100644
--- a/src/client/views/nodes/FormattedTextBox.tsx
+++ b/src/client/views/nodes/FormattedTextBox.tsx
@@ -1,11 +1,12 @@
import { library } from '@fortawesome/fontawesome-svg-core';
import { faEdit, faSmile } from '@fortawesome/free-solid-svg-icons';
-import { action, IReactionDisposer, observable, reaction, runInAction, computed, trace } from "mobx";
+import { action, IReactionDisposer, observable, reaction, runInAction, computed, Lambda, trace } from "mobx";
import { observer } from "mobx-react";
import { baseKeymap } from "prosemirror-commands";
import { history } from "prosemirror-history";
import { keymap } from "prosemirror-keymap";
import { NodeType } from 'prosemirror-model';
+import { Node as ProsNode } from "prosemirror-model";
import { EditorState, Plugin, Transaction } from "prosemirror-state";
import { EditorView } from "prosemirror-view";
import { Doc, Opt } from "../../../new_fields/Doc";
@@ -33,6 +34,7 @@ import { Templates } from '../Templates';
import { FieldView, FieldViewProps } from "./FieldView";
import "./FormattedTextBox.scss";
import React = require("react");
+import { For } from 'babel-types';
import { DateField } from '../../../new_fields/DateField';
import { thisExpression } from 'babel-types';
import { Utils } from '../../../Utils';
@@ -49,6 +51,7 @@ export interface FormattedTextBoxProps {
height?: string;
color?: string;
outer_div?: (domminus: HTMLElement) => void;
+ firstinstance?: boolean;
}
const richTextSchema = createSchema({
@@ -63,14 +66,16 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
public static LayoutString(fieldStr: string = "data") {
return FieldView.LayoutString(FormattedTextBox, fieldStr);
}
+ public static Instance: FormattedTextBox;
private _ref: React.RefObject<HTMLDivElement>;
private _outerdiv?: (dominus: HTMLElement) => void;
private _proseRef?: HTMLDivElement;
private _editorView: Opt<EditorView>;
- private _toolTipTextMenu: TooltipTextMenu | undefined = undefined;
+ private static _toolTipTextMenu: TooltipTextMenu | undefined = undefined;
private _applyingChange: boolean = false;
private _linkClicked = "";
private _reactionDisposer: Opt<IReactionDisposer>;
+ private _searchReactionDisposer?: Lambda;
private _textReactionDisposer: Opt<IReactionDisposer>;
private _proxyReactionDisposer: Opt<IReactionDisposer>;
private dropDisposer?: DragManager.DragDropDisposer;
@@ -100,6 +105,10 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
return "";
}
+ public static getToolTip() {
+ return this._toolTipTextMenu;
+ }
+
@undoBatch
public setFontColor(color: string) {
let self = this;
@@ -115,6 +124,9 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
constructor(props: FieldViewProps) {
super(props);
+ //if (this.props.firstinstance) {
+ FormattedTextBox.Instance = this;
+ //}
if (this.props.outer_div) {
this._outerdiv = this.props.outer_div;
}
@@ -134,6 +146,15 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
const state = this._editorView.state.apply(tx);
this._editorView.updateState(state);
this._applyingChange = true;
+ const fieldkey = "preview";
+ if (Object.keys(this.dataDoc).indexOf(fieldkey) !== -1) {
+ this.dataDoc[this.props.fieldKey] = new RichTextField(JSON.stringify(state.toJSON()));
+ this.dataDoc[this.props.fieldKey + "_text"] = state.doc.textBetween(0, state.doc.content.size, "\n\n");
+ }
+ else {
+ Doc.GetProto(this.dataDoc)[this.props.fieldKey] = new RichTextField(JSON.stringify(state.toJSON()));
+ Doc.GetProto(this.dataDoc)[this.props.fieldKey + "_text"] = state.doc.textBetween(0, state.doc.content.size, "\n\n");
+ }
if (this.extensionDoc) this.extensionDoc.text = state.doc.textBetween(0, state.doc.content.size, "\n\n");
if (this.extensionDoc) this.extensionDoc.lastModified = new DateField(new Date(Date.now()));
this.dataDoc[this.props.fieldKey] = new RichTextField(JSON.stringify(state.toJSON()));
@@ -147,6 +168,50 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
}
}
+ public highlightSearchTerms = (terms: String[]) => {
+ if (this._editorView && (this._editorView as any).docView) {
+ const fieldkey = "preview";
+ const doc = this._editorView.state.doc;
+ const mark = this._editorView.state.schema.mark(this._editorView.state.schema.marks.search_highlight);
+ doc.nodesBetween(0, doc.content.size, (node: ProsNode, pos: number, parent: ProsNode, index: number) => {
+ if (node.isLeaf && node.isText && node.text) {
+ let nodeText: String = node.text;
+ let tokens = nodeText.split(" ");
+ let start = pos;
+ tokens.forEach((word) => {
+ if (terms.includes(word) && this._editorView) {
+ this._editorView.dispatch(this._editorView.state.tr.addMark(start, start + word.length, mark).removeStoredMark(mark));
+ // else {
+ // this._editorView.state.tr.addMark(start, start + word.length, mark).removeStoredMark(mark);
+ // }
+ }
+ start += word.length + 1;
+ });
+ }
+ });
+ }
+ }
+
+ public unhighlightSearchTerms = () => {
+ if (this._editorView && (this._editorView as any).docView) {
+ const doc = this._editorView.state.doc;
+ const mark = this._editorView.state.schema.mark(this._editorView.state.schema.marks.search_highlight);
+ doc.nodesBetween(0, doc.content.size, (node: ProsNode, pos: number, parent: ProsNode, index: number) => {
+ if (node.isLeaf && node.isText && node.text) {
+ if (node.marks.includes(mark) && this._editorView) {
+ this._editorView.dispatch(this._editorView.state.tr.removeMark(pos, pos + node.nodeSize, mark));
+ }
+ }
+ });
+ // const fieldkey = 'search_string';
+ // if (Object.keys(this.props.Document).indexOf(fieldkey) !== -1) {
+ // this.props.Document[fieldkey] = undefined;
+ // }
+ // else this.props.Document.proto![fieldkey] = undefined;
+ // }
+ }
+ }
+
protected createDropTarget = (ele: HTMLDivElement) => {
this._proseRef = ele;
if (this.dropDisposer) {
@@ -249,6 +314,22 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
}
}, { fireImmediately: true });
this.setupEditor(config, this.dataDoc, this.props.fieldKey);
+
+ this._searchReactionDisposer = reaction(() => {
+ return StrCast(this.props.Document.search_string);
+ }, searchString => {
+ const fieldkey = 'preview';
+ let preview = false;
+ // if (!this._editorView && Object.keys(this.props.Document).indexOf(fieldkey) !== -1) {
+ // preview = true;
+ // }
+ if (searchString) {
+ this.highlightSearchTerms([searchString]);
+ }
+ else {
+ this.unhighlightSearchTerms();
+ }
+ }, { fireImmediately: true });
}
private setupEditor(config: any, doc: Doc, fieldKey: string) {
@@ -295,7 +376,7 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
onPointerDown = (e: React.PointerEvent): void => {
if (e.button === 0 && this.props.isSelected() && !e.altKey && !e.ctrlKey && !e.metaKey) {
e.stopPropagation();
- if (this._toolTipTextMenu && this._toolTipTextMenu.tooltip) {
+ if (FormattedTextBox._toolTipTextMenu && FormattedTextBox._toolTipTextMenu.tooltip) {
//this._toolTipTextMenu.tooltip.style.opacity = "0";
}
}
@@ -348,7 +429,7 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
}
}
onPointerUp = (e: React.PointerEvent): void => {
- if (this._toolTipTextMenu && this._toolTipTextMenu.tooltip) {
+ if (FormattedTextBox._toolTipTextMenu && FormattedTextBox._toolTipTextMenu.tooltip) {
//this._toolTipTextMenu.tooltip.style.opacity = "1";
}
if (e.buttons === 1 && this.props.isSelected() && !e.altKey) {
@@ -389,7 +470,7 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
tooltipTextMenuPlugin() {
let myprops = this.props;
- let self = this;
+ let self = FormattedTextBox;
return new Plugin({
view(_editorView) {
return self._toolTipTextMenu = new TooltipTextMenu(_editorView, myprops);
diff --git a/src/client/views/search/FilterBox.scss b/src/client/views/search/FilterBox.scss
index 1eb8963d7..1b73916c9 100644
--- a/src/client/views/search/FilterBox.scss
+++ b/src/client/views/search/FilterBox.scss
@@ -105,4 +105,60 @@
border-top-style: solid;
padding-top: 10px;
}
+}
+
+.active-filters {
+ display: flex;
+ flex-direction: row-reverse;
+ justify-content: flex-end;
+ width: 100%;
+ margin-right: 30px;
+ position: relative;
+
+ .active-icon {
+ max-width: 40px;
+ flex: initial;
+
+ &.icon{
+ width: 40px;
+ text-align: center;
+ margin-bottom: 5px;
+ position: absolute;
+ }
+
+ &.container {
+ display: flex;
+ flex-direction: column;
+ width: 40px;
+ }
+
+ &.description {
+ text-align: center;
+ top: 40px;
+ position: absolute;
+ width: 40px;
+ font-size: 9px;
+ opacity: 0;
+ -webkit-transition: all 0.2s ease-in-out;
+ -moz-transition: all 0.2s ease-in-out;
+ -o-transition: all 0.2s ease-in-out;
+ transition: all 0.2s ease-in-out;
+ }
+
+ &.icon:hover + .description {
+ opacity: 1;
+ }
+ }
+
+ .col-icon {
+ height: 35px;
+ margin-left: 5px;
+ width: 35px;
+ background-color: black;
+ color: white;
+ border-radius: 50%;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ }
} \ No newline at end of file
diff --git a/src/client/views/search/FilterBox.tsx b/src/client/views/search/FilterBox.tsx
index 706d1eb7f..4b1887339 100644
--- a/src/client/views/search/FilterBox.tsx
+++ b/src/client/views/search/FilterBox.tsx
@@ -2,7 +2,7 @@ import * as React from 'react';
import { observer } from 'mobx-react';
import { observable, action } from 'mobx';
import "./SearchBox.scss";
-import { faTimes } from '@fortawesome/free-solid-svg-icons';
+import { faTimes, faCheckCircle, faObjectGroup } from '@fortawesome/free-solid-svg-icons';
import { library } from '@fortawesome/fontawesome-svg-core';
import { Doc } from '../../../new_fields/Doc';
import { Id } from '../../../new_fields/FieldSymbols';
@@ -19,8 +19,11 @@ import { NaviconButton } from './NaviconButton';
import * as $ from 'jquery';
import "./FilterBox.scss";
import { SearchBox } from './SearchBox';
+import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
library.add(faTimes);
+library.add(faCheckCircle);
+library.add(faObjectGroup);
export enum Keys {
TITLE = "title",
@@ -35,11 +38,16 @@ export class FilterBox extends React.Component {
public _allIcons: string[] = [DocumentType.AUDIO, DocumentType.COL, DocumentType.HIST, DocumentType.IMG, DocumentType.LINK, DocumentType.PDF, DocumentType.TEXT, DocumentType.VID, DocumentType.WEB];
//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
@observable private _basicWordStatus: boolean = true;
@observable private _filterOpen: boolean = false;
+ //if icons = all icons, then no icon filter is applied
@observable private _icons: string[] = this._allIcons;
+ //if all of these are true, no key filter is applied
@observable private _titleFieldStatus: boolean = true;
@observable private _authorFieldStatus: boolean = true;
+ @observable private _dataFieldStatus: boolean = true;
+ //this also serves as an indicator if the collection status filter is applied
@observable public _deletedDocsStatus: boolean = false;
@observable private _collectionStatus = false;
@observable private _collectionSelfStatus = true;
@@ -259,6 +267,40 @@ export class FilterBox extends React.Component {
return finalDocs;
}
+ getABCicon() {
+ return (
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 87.8 87.8" height="35">
+ <path d="M25.4 47.9c-1.3 1.3-1.9 2.8-1.9 4.8 0 3.8 2.3 6.1 6.1 6.1 5.1 0 8-3.3 9-6.2 0.2-0.7 0.4-1.4 0.4-2.1v-6.1c-0.1 0-0.1 0-0.2 0C32.2 44.5 27.7 45.6 25.4 47.9z" />
+ <path d="M64.5 28.6c-2.2 0-4.1 1.5-4.7 3.8l0 0.2c-0.1 0.3-0.1 0.7-0.1 1.1v3.3c0 0.4 0.1 0.8 0.2 1.1 0.6 2.2 2.4 3.6 4.6 3.6 3.2 0 5.2-2.6 5.2-6.7C69.5 31.8 68 28.6 64.5 28.6z" />
+ <path d="M43.9 0C19.7 0 0 19.7 0 43.9s19.7 43.9 43.9 43.9 43.9-19.6 43.9-43.9S68.1 0 43.9 0zM40.1 65.5l-0.5-4c-3 3.1-7.4 4.9-12.1 4.9 -6.8 0-13.6-4.4-13.6-12.8 0-4 1.3-7.4 4-10 4.1-4.1 11.1-6.2 20.8-6.3 0-5.5-2.9-8.4-8.3-8.4 -3.6 0-7.4 1.1-10.2 2.9l-1.1 0.7 -2.4-6.9 0.7-0.4c3.7-2.4 8.9-3.8 14.1-3.8 10.9 0 16.7 6.2 16.7 17.9V54.6c0 4.1 0.2 7.2 0.7 9.7L49 65.5H40.1zM65.5 67.5c1.8 0 3-0.5 4-0.9l0.5-0.2 0.8 3.4 -0.3 0.2c-1 0.5-3 1.1-5.5 1.1 -5.8 0-9.7-4-9.7-9.9 0-6.1 4.3-10.3 10.4-10.3 2.1 0 4 0.5 4.9 1l0.3 0.2 -1 3.5 -0.5-0.3c-0.7-0.4-1.8-0.8-3.7-0.8 -3.7 0-6.1 2.6-6.1 6.6C59.5 64.8 61.9 67.5 65.5 67.5zM65 45.3c-2.5 0-4.5-0.9-5.9-2.7l-0.1 2.3h-3.8l0-0.5c0.1-1.2 0.2-3.1 0.2-4.8V16.7h4.3v10.8c1.4-1.6 3.5-2.5 6-2.5 2.2 0 4.1 0.8 5.5 2.3 1.8 1.8 2.8 4.5 2.8 7.7C73.8 42.1 69.3 45.3 65 45.3z" />
+ </svg>
+ );
+ }
+
+ getTypeIcon() {
+ return (
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 87.8 87.8" height="35">
+ <path d="M43.9 0C19.7 0 0 19.7 0 43.9s19.7 43.9 43.9 43.9 43.9-19.6 43.9-43.9S68.1 0 43.9 0zM43.9 12.2c4.1 0 7.5 3.4 7.5 7.5 0 4.1-3.4 7.5-7.5 7.5 -4.1 0-7.5-3.4-7.5-7.5C36.4 15.5 39.7 12.2 43.9 12.2zM11.9 50.4l7.5-13 7.5 13H11.9zM47.6 75.7h-7.5l-3.7-6.5 3.8-6.5h7.5l3.8 6.5L47.6 75.7zM70.7 70.7c-0.2 0.2-0.4 0.3-0.7 0.3s-0.5-0.1-0.7-0.3l-25.4-25.4 -25.4 25.4c-0.2 0.2-0.4 0.3-0.7 0.3s-0.5-0.1-0.7-0.3c-0.4-0.4-0.4-1 0-1.4l25.4-25.4 -25.4-25.4c-0.4-0.4-0.4-1 0-1.4s1-0.4 1.4 0l25.4 25.4 25.4-25.4c0.4-0.4 1-0.4 1.4 0s0.4 1 0 1.4l-25.4 25.4 25.4 25.4C71.1 69.7 71.1 70.3 70.7 70.7zM61.4 51.4v-15h15v15H61.4z" />
+ </svg>
+ );
+ }
+
+ getKeyIcon() {
+ return (
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 87.8 87.8" height="35">
+ <path d="M38.5 32.4c0 3.4-2.7 6.1-6.1 6.1 -3.4 0-6.1-2.7-6.1-6.1 0-3.4 2.8-6.1 6.1-6.1C35.8 26.3 38.5 29 38.5 32.4zM87.8 43.9c0 24.2-19.6 43.9-43.9 43.9S0 68.1 0 43.9C0 19.7 19.7 0 43.9 0S87.8 19.7 87.8 43.9zM66.8 60.3L50.2 43.7c-0.5-0.5-0.6-1.2-0.4-1.8 2.4-5.6 1.1-12.1-3.2-16.5 -5.9-5.8-15.4-5.8-21.2 0l0 0c-4.3 4.3-5.6 10.8-3.2 16.5 3.2 7.6 12 11.2 19.7 8 0.6-0.3 1.4-0.1 1.8 0.4l3.1 3.1h3.9c1.2 0 2.2 1 2.2 2.2v3.6h3.6c1.2 0 2.2 1 2.2 2.2v4l1.6 1.6h6.5V60.3z" />
+ </svg>
+ );
+ }
+
+ getColIcon() {
+ return (
+ <div className="col-icon">
+ <FontAwesomeIcon icon={faObjectGroup} size="lg" />
+ </div>
+ );
+ }
+
@action.bound
openFilter = () => {
this._filterOpen = !this._filterOpen;
@@ -326,6 +368,31 @@ export class FilterBox extends React.Component {
getAuthorStatus() { return this._authorFieldStatus; }
getDataStatus() { return this._deletedDocsStatus; }
+ getActiveFilters() {
+ console.log(this._authorFieldStatus, this._titleFieldStatus, this._dataFieldStatus);
+ return (
+ <div className="active-filters">
+ {!this._basicWordStatus ? <div className="active-icon container">
+ <div className="active-icon icon">{this.getABCicon()}</div>
+ <div className="active-icon description">Required Words Applied</div>
+ </div> : undefined}
+ {!(this._icons.length === 9) ? <div className="active-icon container">
+ <div className="active-icon icon">{this.getTypeIcon()}</div>
+ <div className="active-icon description">Type Filters Applied</div>
+ </div> : undefined}
+ {!(this._authorFieldStatus && this._dataFieldStatus && this._titleFieldStatus) ?
+ <div className="active-icon container">
+ <div className="active-icon icon">{this.getKeyIcon()}</div>
+ <div className="active-icon description">Field Filters Applied</div>
+ </div> : undefined}
+ {this._collectionStatus ? <div className="active-icon container">
+ <div className="active-icon icon">{this.getColIcon()}</div>
+ <div className="active-icon description">Collection Filters Active</div>
+ </div> : undefined}
+ </div>
+ )
+ }
+
// Useful queries:
// Delegates of a document: {!join from=id to=proto_i}id:{protoId}
// Documents in a collection: {!join from=data_l to=id}id:{collectionProtoId} //id of collections prototype
@@ -334,6 +401,7 @@ export class FilterBox extends React.Component {
<div>
<div style={{ display: "flex", flexDirection: "row-reverse" }}>
<SearchBox />
+ {this.getActiveFilters()}
</div>
{this._filterOpen ? (
<div className="filter-form" onPointerDown={this.stopProp} id="filter-form" style={this._filterOpen ? { display: "flex" } : { display: "none" }}>
@@ -387,7 +455,8 @@ export class FilterBox extends React.Component {
<button className="reset-filter" onClick={this.resetFilters}>Reset Filters</button>
</div>
</div>
- ) : undefined}
+ ) :
+ undefined}
</div>
);
}
diff --git a/src/client/views/search/Pager.tsx b/src/client/views/search/Pager.tsx
new file mode 100644
index 000000000..9bcfb1676
--- /dev/null
+++ b/src/client/views/search/Pager.tsx
@@ -0,0 +1,78 @@
+import * as React from 'react';
+import { observer } from 'mobx-react';
+import { faArrowCircleRight, faArrowCircleLeft } from '@fortawesome/free-solid-svg-icons';
+import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
+import { library } from '@fortawesome/fontawesome-svg-core';
+import "./Pager.scss";
+import { SearchBox } from './SearchBox';
+import { observable, action } from 'mobx';
+import { FilterBox } from './FilterBox';
+
+library.add(faArrowCircleRight);
+library.add(faArrowCircleLeft);
+
+@observer
+export class Pager extends React.Component {
+
+ @observable _leftHover: boolean = false;
+ @observable _rightHover: boolean = false;
+
+ @action
+ onLeftClick(e: React.PointerEvent) {
+ FilterBox.Instance._pointerTime = e.timeStamp;
+ if (SearchBox.Instance._pageNum > 0) {
+ SearchBox.Instance._pageNum -= 1;
+ }
+ }
+
+ @action
+ onRightClick(e: React.PointerEvent) {
+ FilterBox.Instance._pointerTime = e.timeStamp;
+ if (SearchBox.Instance._pageNum + 1 < SearchBox.Instance._maxNum) {
+ SearchBox.Instance._pageNum += 1;
+ }
+ }
+
+ @action.bound
+ mouseInLeft() {
+ this._leftHover = true;
+ }
+
+ @action.bound
+ mouseOutLeft() {
+ this._leftHover = false;
+ }
+
+ @action.bound
+ mouseInRight() {
+ this._rightHover = true;
+ }
+
+ @action.bound
+ mouseOutRight() {
+ this._rightHover = false;
+ }
+
+ render() {
+ return (
+ <div className="search-pager">
+ <div className="search-arrows">
+ <div className = "arrow"
+ onPointerDown = {this.onLeftClick} style = {SearchBox.Instance._pageNum === 0 ? {opacity: .2} : this._leftHover ? {opacity: 1} : {opacity: .7}}
+ onMouseEnter = {this.mouseInLeft} onMouseLeave = {this.mouseOutLeft}>
+ <FontAwesomeIcon className="fontawesome-icon" icon={faArrowCircleLeft} />
+ </div>
+ <div className="pager-title">
+ page {SearchBox.Instance._pageNum + 1} of {SearchBox.Instance._maxNum}
+ </div>
+ <div className = "arrow"
+ onPointerDown = {this.onRightClick} style = {SearchBox.Instance._pageNum === SearchBox.Instance._maxNum-1 ? {opacity: .2} : this._rightHover ? {opacity: 1} : {opacity: .7}}
+ onMouseEnter = {this.mouseInRight} onMouseLeave = {this.mouseOutRight}>
+ <FontAwesomeIcon className="fontawesome-icon" icon={faArrowCircleRight} />
+ </div>
+ </div>
+ </div>
+ );
+ }
+
+} \ No newline at end of file
diff --git a/src/client/views/search/SearchBox.scss b/src/client/views/search/SearchBox.scss
index 109b88ac9..fcdc79220 100644
--- a/src/client/views/search/SearchBox.scss
+++ b/src/client/views/search/SearchBox.scss
@@ -45,6 +45,11 @@
top: 300px;
display: flex;
flex-direction: column;
+ margin-right: 72px;
+ // height: 560px;
+ height: 100%;
+ // overflow: hidden;
+ // overflow-y: auto;
max-height: 560px;
overflow: hidden;
overflow-y: auto;
diff --git a/src/client/views/search/SearchItem.tsx b/src/client/views/search/SearchItem.tsx
index a995140e2..5b0e20348 100644
--- a/src/client/views/search/SearchItem.tsx
+++ b/src/client/views/search/SearchItem.tsx
@@ -21,11 +21,18 @@ import { DocumentView } from "../nodes/DocumentView";
import { SearchBox } from "./SearchBox";
import "./SearchItem.scss";
import "./SelectorContextMenu.scss";
+import { RichTextField } from "../../../new_fields/RichTextField";
+import { FormattedTextBox } from "../nodes/FormattedTextBox";
+import { MarqueeView } from "../collections/collectionFreeForm/MarqueeView";
+import { SelectionManager } from "../../util/SelectionManager";
+import { ObjectField } from "../../../new_fields/ObjectField";
import { ContextMenu } from "../ContextMenu";
import { faFile } from '@fortawesome/free-solid-svg-icons';
+import { DocServer } from "../../DocServer";
export interface SearchItemProps {
doc: Doc;
+ query?: string;
highlighting: string[];
}
@@ -86,7 +93,7 @@ export class SelectorContextMenu extends React.Component<SearchItemProps> {
SetupDrag(item, () => doc.col, undefined, undefined, undefined, undefined, () => SearchBox.Instance.closeSearch())}>
<FontAwesomeIcon icon={faStickyNote} />
</div>
- <a className="title" onClick={this.getOnClick(doc)}>{doc.col.title}</a>
+ <a onClick={this.getOnClick(doc)}>{doc.col.title}</a>
</div>;
})}
</div>
@@ -94,27 +101,115 @@ export class SelectorContextMenu extends React.Component<SearchItemProps> {
}
}
+export interface LinkMenuProps {
+ doc1: Doc;
+ doc2: Doc;
+}
+
+@observer
+export class LinkContextMenu extends React.Component<LinkMenuProps> {
+
+ highlightDoc = (doc: Doc) => {
+ return () => {
+ doc.libraryBrush = true;
+ };
+ }
+
+ unHighlightDoc = (doc: Doc) => {
+ return () => {
+ doc.libraryBrush = false;
+ };
+ }
+
+ getOnClick(col: Doc) {
+ return () => {
+ CollectionDockingView.Instance.AddRightSplit(col, undefined);
+ };
+ }
+
+ render() {
+ return (
+ <div className="parents">
+ <p className="contexts">Anchors:</p>
+ <div className="collection"><a onMouseEnter={this.highlightDoc(this.props.doc1)} onMouseLeave={this.unHighlightDoc(this.props.doc1)} onClick={this.getOnClick(this.props.doc1)}>Doc 1: {this.props.doc2.title}</a></div>
+ <div><a onMouseEnter={this.highlightDoc(this.props.doc2)} onMouseLeave={this.unHighlightDoc(this.props.doc2)} onClick={this.getOnClick(this.props.doc2)}>Doc 2: {this.props.doc1.title}</a></div>
+ </div>
+ )
+ }
+
+}
+
@observer
export class SearchItem extends React.Component<SearchItemProps> {
@observable _selected: boolean = false;
+ private _previewDoc?: Doc;
onClick = () => {
// I dont think this is the best functionality because clicking the name of the collection does that. Change it back if you'd like
DocumentManager.Instance.jumpToDocument(this.props.doc, false);
+ if (this.props.doc.data instanceof RichTextField) {
+ this.highlightTextBox(this.props.doc);
+ }
// CollectionDockingView.Instance.AddRightSplit(this.props.doc, undefined);
}
@observable _useIcons = true;
@observable _displayDim = 50;
- @computed
- public get DocumentIcon() {
+ highlightTextBox = (doc: Doc) => {
+ if (this.props.query) {
+ const fieldkey = 'search_string';
+ if (Object.keys(doc).indexOf(fieldkey) === -1) {
+ doc.search_string = this.props.query;
+ }
+ else {
+ doc.search_string = undefined;
+ }
+
+ }
+ }
+
+ fitToBox = () => {
+ let bounds = Doc.ComputeContentBounds([this.props.doc]);
+ return [(bounds.x + bounds.r) / 2, (bounds.y + bounds.b) / 2, Number(SEARCH_THUMBNAIL_SIZE) / Math.max((bounds.b - bounds.y), (bounds.r - bounds.x)), this._displayDim];
+ }
+
+ componentWillUnmount() {
+ if (this._previewDoc) {
+ DocServer.DeleteDocument(this._previewDoc[Id]);
+ }
+ }
+
+
+ //@computed
+ @action
+ public DocumentIcon() {
+ let layoutresult = StrCast(this.props.doc.type);
if (!this._useIcons) {
+ let renderDoc = this.props.doc;
+ //let box: number[] = [];
+ if (layoutresult.indexOf(DocumentType.COL) !== -1) {
+ renderDoc = Doc.MakeDelegate(renderDoc);
+ let bounds = DocListCast(renderDoc.data).reduce((bounds, doc) => {
+ var [sptX, sptY] = [NumCast(doc.x), NumCast(doc.y)];
+ let [bptX, bptY] = [sptX + doc[WidthSym](), sptY + doc[HeightSym]()];
+ 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)
+ };
+ }, { x: Number.MAX_VALUE, y: Number.MAX_VALUE, r: Number.MIN_VALUE, b: Number.MIN_VALUE });
+ let box = () => [(bounds.x + bounds.r) / 2, (bounds.y + bounds.b) / 2, Number(SEARCH_THUMBNAIL_SIZE) / (bounds.r - bounds.x), this._displayDim];
+ }
let returnXDimension = () => this._useIcons ? 50 : Number(SEARCH_THUMBNAIL_SIZE);
let returnYDimension = () => this._displayDim;
- let scale = () => returnXDimension() / NumCast(this.props.doc.nativeWidth, returnXDimension());
- return <div
- onPointerDown={action(() => { this._useIcons = !this._useIcons; this._displayDim = this._useIcons ? 50 : Number(SEARCH_THUMBNAIL_SIZE); })}
+ let scale = () => returnXDimension() / NumCast(renderDoc.nativeWidth, returnXDimension());
+ let newRenderDoc = Doc.MakeDelegate(renderDoc); /// newRenderDoc -> renderDoc -> render"data"Doc -> TextProt
+ this._previewDoc = newRenderDoc;
+ const docview = <div
+ onPointerDown={action(() => {
+ this._useIcons = !this._useIcons;
+ this._displayDim = this._useIcons ? 50 : Number(SEARCH_THUMBNAIL_SIZE);
+ })}
onPointerEnter={action(() => this._displayDim = this._useIcons ? 50 : Number(SEARCH_THUMBNAIL_SIZE))}
onPointerLeave={action(() => this._displayDim = 50)} >
<DocumentView
@@ -138,9 +233,15 @@ export class SearchItem extends React.Component<SearchItemProps> {
ContentScaling={scale}
/>
</div>;
+ const data = renderDoc.data;
+ if (data instanceof ObjectField) newRenderDoc.data = ObjectField.MakeCopy(data);
+ newRenderDoc.preview = true;
+ newRenderDoc.search_string = this.props.query;
+ return docview;
+ }
+ if (this._previewDoc) {
+ DocServer.DeleteDocument(this._previewDoc[Id]);
}
-
- let layoutresult = StrCast(this.props.doc.type);
let button = layoutresult.indexOf(DocumentType.PDF) !== -1 ? faFilePdf :
layoutresult.indexOf(DocumentType.IMG) !== -1 ? faImage :
layoutresult.indexOf(DocumentType.TEXT) !== -1 ? faStickyNote :
@@ -261,7 +362,8 @@ export class SearchItem extends React.Component<SearchItemProps> {
</div>
</div>
<div className="searchBox-instances">
- <SelectorContextMenu {...this.props} />
+ {this.props.doc.type === DocumentType.LINK ? <LinkContextMenu doc1={Cast(this.props.doc.anchor1, Doc, new Doc())} doc2={Cast(this.props.doc.anchor2, Doc, new Doc())} /> :
+ <SelectorContextMenu {...this.props} />}
</div>
</div>
);
diff --git a/src/client/views/search/SelectorContextMenu.scss b/src/client/views/search/SelectorContextMenu.scss
index 49f77b9bf..48cacc608 100644
--- a/src/client/views/search/SelectorContextMenu.scss
+++ b/src/client/views/search/SelectorContextMenu.scss
@@ -3,6 +3,7 @@
.parents {
background: $lighter-alt-accent;
padding: 10px;
+ // width: 300px;
.contexts {
text-transform: uppercase;
diff --git a/src/client/views/search/ToggleBar.tsx b/src/client/views/search/ToggleBar.tsx
index 178578c5c..a30104089 100644
--- a/src/client/views/search/ToggleBar.tsx
+++ b/src/client/views/search/ToggleBar.tsx
@@ -59,6 +59,7 @@ export class ToggleBar extends React.Component<ToggleBarProps>{
this._forwardTimeline.play();
this._forwardTimeline.reverse();
this.props.handleChange();
+ console.log(this.props.getStatus())
}
@action.bound
diff --git a/src/debug/Viewer.tsx b/src/debug/Viewer.tsx
index 2b3eed154..24db3f934 100644
--- a/src/debug/Viewer.tsx
+++ b/src/debug/Viewer.tsx
@@ -10,6 +10,7 @@ import { List } from '../new_fields/List';
import { URLField } from '../new_fields/URLField';
import { EditableView } from '../client/views/EditableView';
import { CompileScript } from '../client/util/Scripting';
+import { RichTextField } from '../new_fields/RichTextField';
import { DateField } from '../new_fields/DateField';
import { ScriptField } from '../new_fields/ScriptField';
import CursorField from '../new_fields/CursorField';
@@ -126,6 +127,8 @@ class DebugViewer extends React.Component<{ field: FieldResult, setValue(value:
content = <p>"{field}"</p>;
} else if (typeof field === "number" || typeof field === "boolean") {
content = <p>{field}</p>;
+ } else if (field instanceof RichTextField) {
+ content = <p>RTF: {field.Data}</p>;
} else if (field instanceof URLField) {
content = <p>{field.url.href}</p>;
} else if (field instanceof Promise) {
diff --git a/src/new_fields/RichTextField.ts b/src/new_fields/RichTextField.ts
index 78a3a4067..89799b2af 100644
--- a/src/new_fields/RichTextField.ts
+++ b/src/new_fields/RichTextField.ts
@@ -20,6 +20,6 @@ export class RichTextField extends ObjectField {
}
[ToScriptString]() {
- return "invalid";
+ return `new RichTextField("${this.Data}")`;
}
} \ No newline at end of file
diff --git a/src/server/index.ts b/src/server/index.ts
index 5b086a2cf..80dbd8a79 100644
--- a/src/server/index.ts
+++ b/src/server/index.ts
@@ -43,6 +43,8 @@ import { Response } from 'express-serve-static-core';
const MongoStore = require('connect-mongo')(session);
const mongoose = require('mongoose');
const probe = require("probe-image-size");
+var SolrNode = require('solr-node');
+var shell = require('shelljs');
const download = (url: string, dest: fs.PathLike) => request.get(url).pipe(fs.createWriteStream(dest));
@@ -140,6 +142,7 @@ app.get("/pull", (req, res) =>
}));
// SEARCH
+const solrURL = "http://localhost:8983/solr/#/dash";
// GETTERS