aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/client/documents/Documents.ts5
-rw-r--r--src/client/util/DictationManager.ts439
-rw-r--r--src/client/views/DocumentDecorations.tsx1
-rw-r--r--src/client/views/linking/LinkMenuItem.tsx4
-rw-r--r--src/client/views/nodes/formattedText/FormattedTextBox.tsx96
5 files changed, 282 insertions, 263 deletions
diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts
index 9d00664ad..c7ea04839 100644
--- a/src/client/documents/Documents.ts
+++ b/src/client/documents/Documents.ts
@@ -1287,10 +1287,11 @@ export namespace DocUtils {
export let ActiveRecordings: { props: FieldViewProps; getAnchor: () => Doc }[] = [];
- export function MakeLinkToActiveAudio(getSourceDoc: () => Doc, broadcastEvent = true) {
+ export function MakeLinkToActiveAudio(getSourceDoc: () => Doc | undefined, broadcastEvent = true) {
broadcastEvent && runInAction(() => (DocumentManager.Instance.RecordingEvent = DocumentManager.Instance.RecordingEvent + 1));
return DocUtils.ActiveRecordings.map(audio => {
- const link = DocUtils.MakeLink({ doc: getSourceDoc() }, { doc: audio.getAnchor() || audio.props.Document }, 'recording annotation:linked recording', 'recording timeline');
+ const sourceDoc = getSourceDoc();
+ const link = sourceDoc && DocUtils.MakeLink({ doc: sourceDoc }, { doc: audio.getAnchor() || audio.props.Document }, 'recording annotation:linked recording', 'recording timeline');
link && (link.followLinkLocation = 'add:right');
return link;
});
diff --git a/src/client/util/DictationManager.ts b/src/client/util/DictationManager.ts
index a6dcda4bc..13f036838 100644
--- a/src/client/util/DictationManager.ts
+++ b/src/client/util/DictationManager.ts
@@ -1,37 +1,35 @@
-import * as interpreter from "words-to-numbers";
+import * as interpreter from 'words-to-numbers';
// @ts-ignore bcz: how are you supposed to include these definitions since dom-speech-recognition isn't a module?
-import type { } from "@types/dom-speech-recognition";
-import { Doc, Opt } from "../../fields/Doc";
-import { List } from "../../fields/List";
-import { RichTextField } from "../../fields/RichTextField";
-import { listSpec } from "../../fields/Schema";
-import { Cast, CastCtor } from "../../fields/Types";
-import { AudioField, ImageField } from "../../fields/URLField";
-import { Utils } from "../../Utils";
-import { Docs } from "../documents/Documents";
-import { DocumentType } from "../documents/DocumentTypes";
-import { DictationOverlay } from "../views/DictationOverlay";
-import { DocumentView } from "../views/nodes/DocumentView";
-import { SelectionManager } from "./SelectionManager";
-import { UndoManager } from "./UndoManager";
-
+import type {} from '@types/dom-speech-recognition';
+import { Doc, Opt } from '../../fields/Doc';
+import { List } from '../../fields/List';
+import { RichTextField } from '../../fields/RichTextField';
+import { listSpec } from '../../fields/Schema';
+import { Cast, CastCtor } from '../../fields/Types';
+import { AudioField, ImageField } from '../../fields/URLField';
+import { Utils } from '../../Utils';
+import { Docs } from '../documents/Documents';
+import { DocumentType } from '../documents/DocumentTypes';
+import { DictationOverlay } from '../views/DictationOverlay';
+import { DocumentView } from '../views/nodes/DocumentView';
+import { SelectionManager } from './SelectionManager';
+import { UndoManager } from './UndoManager';
/**
* This namespace provides a singleton instance of a manager that
* handles the listening and text-conversion of user speech.
- *
+ *
* The basic manager functionality can be attained by the DictationManager.Controls namespace, which provide
* a simple recording operation that returns the interpreted text as a string.
- *
+ *
* Additionally, however, the DictationManager also exposes the ability to execute voice commands within Dash.
* It stores a default library of registered commands that can be triggered by listen()'ing for a phrase and then
* passing the results into the execute() function.
- *
+ *
* In addition to compile-time default commands, you can invoke DictationManager.Commands.Register(Independent|Dependent)
* to add new commands as classes or components are constructed.
*/
export namespace DictationManager {
-
/**
* Some type maneuvering to access Webkit's built-in
* speech recognizer.
@@ -42,27 +40,26 @@ export namespace DictationManager {
}
}
const { webkitSpeechRecognition }: CORE.IWindow = window as any as CORE.IWindow;
- export const placeholder = "Listening...";
+ export const placeholder = 'Listening...';
export namespace Controls {
-
- export const Infringed = "unable to process: dictation manager still involved in previous session";
+ export const Infringed = 'unable to process: dictation manager still involved in previous session';
const browser = (() => {
const identifier = navigator.userAgent.toLowerCase();
- if (identifier.indexOf("safari") >= 0) {
- return "Safari";
+ if (identifier.indexOf('safari') >= 0) {
+ return 'Safari';
}
- if (identifier.indexOf("chrome") >= 0) {
- return "Chrome";
+ if (identifier.indexOf('chrome') >= 0) {
+ return 'Chrome';
}
- if (identifier.indexOf("firefox") >= 0) {
- return "Firefox";
+ if (identifier.indexOf('firefox') >= 0) {
+ return 'Firefox';
}
- return "Unidentified Browser";
+ return 'Unidentified Browser';
})();
const unsupported = `listening is not supported in ${browser}`;
- const intraSession = ". ";
- const interSession = " ... ";
+ const intraSession = '. ';
+ const interSession = ' ... ';
export let isListening = false;
let isManuallyStopped = false;
@@ -74,7 +71,7 @@ export namespace DictationManager {
export type InterimResultHandler = (results: string) => any;
export type ContinuityArgs = { indefinite: boolean } | false;
- export type DelimiterArgs = { inter: string, intra: string };
+ export type DelimiterArgs = { inter: string; intra: string };
export type ListeningUIStatus = { interim: boolean } | false;
export interface ListeningOptions {
@@ -105,21 +102,21 @@ export namespace DictationManager {
try {
results = await (pendingListen = listenImpl(options));
pendingListen = undefined;
- // if (results) {
- // Utils.CopyText(results);
- // if (overlay) {
- // DictationOverlay.Instance.isListening = false;
- // const execute = options?.tryExecute;
- // DictationOverlay.Instance.dictatedPhrase = execute ? results.toLowerCase() : results;
- // DictationOverlay.Instance.dictationSuccess = execute ? await DictationManager.Commands.execute(results) : true;
- // }
- // options?.tryExecute && await DictationManager.Commands.execute(results);
- // }
+ if (results) {
+ Utils.CopyText(results);
+ if (overlay) {
+ DictationOverlay.Instance.isListening = false;
+ const execute = options?.tryExecute;
+ DictationOverlay.Instance.dictatedPhrase = execute ? results.toLowerCase() : results;
+ DictationOverlay.Instance.dictationSuccess = execute ? await DictationManager.Commands.execute(results) : true;
+ }
+ options?.tryExecute && (await DictationManager.Commands.execute(results));
+ }
} catch (e: any) {
console.log(e);
if (overlay) {
DictationOverlay.Instance.isListening = false;
- DictationOverlay.Instance.dictatedPhrase = results = `dictation error: ${"error" in e ? e.error : "unknown error"}`;
+ DictationOverlay.Instance.dictatedPhrase = results = `dictation error: ${'error' in e ? e.error : 'unknown error'}`;
DictationOverlay.Instance.dictationSuccess = false;
}
} finally {
@@ -131,7 +128,7 @@ export namespace DictationManager {
const listenImpl = (options?: Partial<ListeningOptions>) => {
if (!recognizer) {
- console.log("DictationManager:" + unsupported);
+ console.log('DictationManager:' + unsupported);
return unsupported;
}
if (isListening) {
@@ -146,16 +143,18 @@ export namespace DictationManager {
const intra = options?.delimiters?.intra;
const inter = options?.delimiters?.inter;
- recognizer.onstart = () => console.log("initiating speech recognition session...");
+ recognizer.onstart = () => console.log('initiating speech recognition session...');
recognizer.interimResults = handler !== undefined;
recognizer.continuous = continuous === undefined ? false : continuous !== false;
- recognizer.lang = language === undefined ? "en-US" : language;
+ recognizer.lang = language === undefined ? 'en-US' : language;
recognizer.start();
return new Promise<string>((resolve, reject) => {
- recognizer.onerror = (e: any) => { // e is SpeechRecognitionError but where is that defined?
- if (!(indefinite && e.error === "no-speech")) {
+ recognizer.onerror = (e: any) => {
+ console.log('SPEECH error:', encodeURIComponent);
+ // e is SpeechRecognitionError but where is that defined?
+ if (!(indefinite && e.error === 'no-speech')) {
recognizer.stop();
resolve(e);
//reject(e);
@@ -163,9 +162,10 @@ export namespace DictationManager {
};
recognizer.onresult = (e: SpeechRecognitionEvent) => {
+ console.log('RESULT: ', e);
current = synthesize(e, intra);
let matchedTerminator: string | undefined;
- if (options?.terminators && (matchedTerminator = options.terminators.find(end => current ? current.trim().toLowerCase().endsWith(end.toLowerCase()) : false))) {
+ if (options?.terminators && (matchedTerminator = options.terminators.find(end => (current ? current.trim().toLowerCase().endsWith(end.toLowerCase()) : false)))) {
current = matchedTerminator;
recognizer.abort();
return complete();
@@ -175,6 +175,7 @@ export namespace DictationManager {
};
recognizer.onend = (e: Event) => {
+ console.log('END: ', e);
if (!indefinite || isManuallyStopped) {
return complete();
}
@@ -187,11 +188,12 @@ export namespace DictationManager {
};
const complete = () => {
+ console.log('COMPLETE:');
if (indefinite) {
current && sessionResults.push(current);
sessionResults.length && resolve(sessionResults.join(inter || interSession));
} else {
- resolve(current || "");
+ resolve(current || '');
}
current = undefined;
sessionResults = [];
@@ -201,7 +203,6 @@ export namespace DictationManager {
recognizer.onerror = null;
recognizer.onend = null;
};
-
});
};
@@ -222,171 +223,173 @@ export namespace DictationManager {
}
return transcripts.join(delimiter || intraSession);
};
-
}
- // export namespace Commands {
-
- // export const dictationFadeDuration = 2000;
-
- // export type IndependentAction = (target: DocumentView) => any | Promise<any>;
- // export type IndependentEntry = { action: IndependentAction, restrictTo?: DocumentType[] };
-
- // export type DependentAction = (target: DocumentView, matches: RegExpExecArray) => any | Promise<any>;
- // export type DependentEntry = { expression: RegExp, action: DependentAction, restrictTo?: DocumentType[] };
-
- // export const RegisterIndependent = (key: string, value: IndependentEntry) => Independent.set(key, value);
- // export const RegisterDependent = (entry: DependentEntry) => Dependent.push(entry);
-
- // export const execute = async (phrase: string) => {
- // return UndoManager.RunInBatch(async () => {
- // const targets = SelectionManager.Views();
- // if (!targets || !targets.length) {
- // return;
- // }
-
- // phrase = phrase.toLowerCase();
- // const entry = Independent.get(phrase);
-
- // if (entry) {
- // let success = false;
- // const restrictTo = entry.restrictTo;
- // for (const target of targets) {
- // if (!restrictTo || validate(target, restrictTo)) {
- // await entry.action(target);
- // success = true;
- // }
- // }
- // return success;
- // }
-
- // for (const entry of Dependent) {
- // const regex = entry.expression;
- // const matches = regex.exec(phrase);
- // regex.lastIndex = 0;
- // if (matches !== null) {
- // let success = false;
- // const restrictTo = entry.restrictTo;
- // for (const target of targets) {
- // if (!restrictTo || validate(target, restrictTo)) {
- // await entry.action(target, matches);
- // success = true;
- // }
- // }
- // return success;
- // }
- // }
-
- // return false;
- // }, "Execute Command");
- // };
-
- // const ConstructorMap = new Map<DocumentType, CastCtor>([
- // [DocumentType.COL, listSpec(Doc)],
- // [DocumentType.AUDIO, AudioField],
- // [DocumentType.IMG, ImageField],
- // [DocumentType.IMPORT, listSpec(Doc)],
- // [DocumentType.RTF, "string"]
- // ]);
-
- // const tryCast = (view: DocumentView, type: DocumentType) => {
- // const ctor = ConstructorMap.get(type);
- // if (!ctor) {
- // return false;
- // }
- // return Cast(Doc.GetProto(view.props.Document).data, ctor) !== undefined;
- // };
-
- // const validate = (target: DocumentView, types: DocumentType[]) => {
- // for (const type of types) {
- // if (tryCast(target, type)) {
- // return true;
- // }
- // }
- // return false;
- // };
-
- // const interpretNumber = (number: string) => {
- // const initial = parseInt(number);
- // if (!isNaN(initial)) {
- // return initial;
- // }
- // const converted = interpreter.wordsToNumbers(number, { fuzzy: true });
- // if (converted === null) {
- // return NaN;
- // }
- // return typeof converted === "string" ? parseInt(converted) : converted;
- // };
-
- // const Independent = new Map<string, IndependentEntry>([
-
- // ["clear", {
- // action: (target: DocumentView) => Doc.GetProto(target.props.Document).data = new List(),
- // restrictTo: [DocumentType.COL]
- // }],
-
- // ["open fields", {
- // action: (target: DocumentView) => {
- // const kvp = Docs.Create.KVPDocument(target.props.Document, { _width: 300, _height: 300 });
- // target.props.addDocTab(kvp, "add:right");
- // }
- // }],
-
- // ["new outline", {
- // action: (target: DocumentView) => {
- // const newBox = Docs.Create.TextDocument("", { _width: 400, _height: 200, title: "My Outline", _autoHeight: true });
- // const proto = newBox.proto!;
- // const prompt = "Press alt + r to start dictating here...";
- // const head = 3;
- // const anchor = head + prompt.length;
- // const proseMirrorState = `{"doc":{"type":"doc","content":[{"type":"ordered_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"type":"text","text":"${prompt}"}]}]}]}]},"selection":{"type":"text","anchor":${anchor},"head":${head}}}`;
- // proto.data = new RichTextField(proseMirrorState);
- // proto.backgroundColor = "#eeffff";
- // target.props.addDocTab(newBox, "add:right");
- // }
- // }]
-
- // ]);
-
- // const Dependent = new Array<DependentEntry>(
-
- // {
- // expression: /create (\w+) documents of type (image|nested collection)/g,
- // action: (target: DocumentView, matches: RegExpExecArray) => {
- // const count = interpretNumber(matches[1]);
- // const what = matches[2];
- // const dataDoc = Doc.GetProto(target.props.Document);
- // const fieldKey = "data";
- // if (isNaN(count)) {
- // return;
- // }
- // for (let i = 0; i < count; i++) {
- // let created: Doc | undefined;
- // switch (what) {
- // case "image":
- // created = Docs.Create.ImageDocument("https://upload.wikimedia.org/wikipedia/commons/thumb/3/3a/Cat03.jpg/1200px-Cat03.jpg");
- // break;
- // case "nested collection":
- // created = Docs.Create.FreeformDocument([], {});
- // break;
- // }
- // created && Doc.AddDocToList(dataDoc, fieldKey, created);
- // }
- // },
- // restrictTo: [DocumentType.COL]
- // },
-
- // {
- // expression: /view as (freeform|stacking|masonry|schema|tree)/g,
- // action: (target: DocumentView, matches: RegExpExecArray) => {
- // const mode = matches[1];
- // mode && (target.props.Document._viewType = mode);
- // },
- // restrictTo: [DocumentType.COL]
- // }
-
- // );
-
- // }
-
-} \ No newline at end of file
+ export namespace Commands {
+ export const dictationFadeDuration = 2000;
+
+ export type IndependentAction = (target: DocumentView) => any | Promise<any>;
+ export type IndependentEntry = { action: IndependentAction; restrictTo?: DocumentType[] };
+
+ export type DependentAction = (target: DocumentView, matches: RegExpExecArray) => any | Promise<any>;
+ export type DependentEntry = { expression: RegExp; action: DependentAction; restrictTo?: DocumentType[] };
+
+ export const RegisterIndependent = (key: string, value: IndependentEntry) => Independent.set(key, value);
+ export const RegisterDependent = (entry: DependentEntry) => Dependent.push(entry);
+
+ export const execute = async (phrase: string) => {
+ return UndoManager.RunInBatch(async () => {
+ console.log('PHRASE: ' + phrase);
+ const targets = SelectionManager.Views();
+ if (!targets || !targets.length) {
+ return;
+ }
+
+ phrase = phrase.toLowerCase();
+ const entry = Independent.get(phrase);
+
+ if (entry) {
+ let success = false;
+ const restrictTo = entry.restrictTo;
+ for (const target of targets) {
+ if (!restrictTo || validate(target, restrictTo)) {
+ await entry.action(target);
+ success = true;
+ }
+ }
+ return success;
+ }
+
+ for (const entry of Dependent) {
+ const regex = entry.expression;
+ const matches = regex.exec(phrase);
+ regex.lastIndex = 0;
+ if (matches !== null) {
+ let success = false;
+ const restrictTo = entry.restrictTo;
+ for (const target of targets) {
+ if (!restrictTo || validate(target, restrictTo)) {
+ await entry.action(target, matches);
+ success = true;
+ }
+ }
+ return success;
+ }
+ }
+
+ return false;
+ }, 'Execute Command');
+ };
+
+ const ConstructorMap = new Map<DocumentType, CastCtor>([
+ [DocumentType.COL, listSpec(Doc)],
+ [DocumentType.AUDIO, AudioField],
+ [DocumentType.IMG, ImageField],
+ [DocumentType.IMPORT, listSpec(Doc)],
+ [DocumentType.RTF, 'string'],
+ ]);
+
+ const tryCast = (view: DocumentView, type: DocumentType) => {
+ const ctor = ConstructorMap.get(type);
+ if (!ctor) {
+ return false;
+ }
+ return Cast(Doc.GetProto(view.props.Document).data, ctor) !== undefined;
+ };
+
+ const validate = (target: DocumentView, types: DocumentType[]) => {
+ for (const type of types) {
+ if (tryCast(target, type)) {
+ return true;
+ }
+ }
+ return false;
+ };
+
+ const interpretNumber = (number: string) => {
+ const initial = parseInt(number);
+ if (!isNaN(initial)) {
+ return initial;
+ }
+ const converted = interpreter.wordsToNumbers(number, { fuzzy: true });
+ if (converted === null) {
+ return NaN;
+ }
+ return typeof converted === 'string' ? parseInt(converted) : converted;
+ };
+
+ const Independent = new Map<string, IndependentEntry>([
+ [
+ 'clear',
+ {
+ action: (target: DocumentView) => (Doc.GetProto(target.props.Document).data = new List()),
+ restrictTo: [DocumentType.COL],
+ },
+ ],
+
+ [
+ 'open fields',
+ {
+ action: (target: DocumentView) => {
+ const kvp = Docs.Create.KVPDocument(target.props.Document, { _width: 300, _height: 300 });
+ target.props.addDocTab(kvp, 'add:right');
+ },
+ },
+ ],
+
+ [
+ 'new outline',
+ {
+ action: (target: DocumentView) => {
+ const newBox = Docs.Create.TextDocument('', { _width: 400, _height: 200, title: 'My Outline', _autoHeight: true });
+ const proto = newBox.proto!;
+ const prompt = 'Press alt + r to start dictating here...';
+ const head = 3;
+ const anchor = head + prompt.length;
+ const proseMirrorState = `{"doc":{"type":"doc","content":[{"type":"ordered_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"type":"text","text":"${prompt}"}]}]}]}]},"selection":{"type":"text","anchor":${anchor},"head":${head}}}`;
+ proto.data = new RichTextField(proseMirrorState);
+ proto.backgroundColor = '#eeffff';
+ target.props.addDocTab(newBox, 'add:right');
+ },
+ },
+ ],
+ ]);
+
+ const Dependent = new Array<DependentEntry>(
+ {
+ expression: /create (\w+) documents of type (image|nested collection)/g,
+ action: (target: DocumentView, matches: RegExpExecArray) => {
+ const count = interpretNumber(matches[1]);
+ const what = matches[2];
+ const dataDoc = Doc.GetProto(target.props.Document);
+ const fieldKey = 'data';
+ if (isNaN(count)) {
+ return;
+ }
+ for (let i = 0; i < count; i++) {
+ let created: Doc | undefined;
+ switch (what) {
+ case 'image':
+ created = Docs.Create.ImageDocument('https://upload.wikimedia.org/wikipedia/commons/thumb/3/3a/Cat03.jpg/1200px-Cat03.jpg');
+ break;
+ case 'nested collection':
+ created = Docs.Create.FreeformDocument([], {});
+ break;
+ }
+ created && Doc.AddDocToList(dataDoc, fieldKey, created);
+ }
+ },
+ restrictTo: [DocumentType.COL],
+ },
+
+ {
+ expression: /view as (freeform|stacking|masonry|schema|tree)/g,
+ action: (target: DocumentView, matches: RegExpExecArray) => {
+ const mode = matches[1];
+ mode && (target.props.Document._viewType = mode);
+ },
+ restrictTo: [DocumentType.COL],
+ }
+ );
+ }
+}
diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx
index 0db9eab69..7628beef2 100644
--- a/src/client/views/DocumentDecorations.tsx
+++ b/src/client/views/DocumentDecorations.tsx
@@ -491,7 +491,6 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number; P
let actualdW = Math.max(width + dW * scale, 20);
let actualdH = Math.max(height + dH * scale, 20);
const fixedAspect = nwidth && nheight && (!doc._fitWidth || e.ctrlKey || doc.nativeHeightUnfrozen);
- console.log(fixedAspect);
if (fixedAspect) {
if ((Math.abs(dW) > Math.abs(dH) && ((!dragBottom && !dragTop) || !modifyNativeDim)) || dragRight) {
if (dragRight && modifyNativeDim) {
diff --git a/src/client/views/linking/LinkMenuItem.tsx b/src/client/views/linking/LinkMenuItem.tsx
index 60428fc98..3f9db2612 100644
--- a/src/client/views/linking/LinkMenuItem.tsx
+++ b/src/client/views/linking/LinkMenuItem.tsx
@@ -6,14 +6,12 @@ import { observer } from 'mobx-react';
import { Doc, DocListCast } from '../../../fields/Doc';
import { Cast, StrCast } from '../../../fields/Types';
import { WebField } from '../../../fields/URLField';
-import { emptyFunction, returnFalse, setupMoveUpEvents } from '../../../Utils';
+import { emptyFunction, setupMoveUpEvents } from '../../../Utils';
import { DocumentType } from '../../documents/DocumentTypes';
import { DocumentManager } from '../../util/DocumentManager';
import { DragManager } from '../../util/DragManager';
-import { Hypothesis } from '../../util/HypothesisUtils';
import { LinkFollower } from '../../util/LinkFollower';
import { LinkManager } from '../../util/LinkManager';
-import { undoBatch } from '../../util/UndoManager';
import { DocumentView } from '../nodes/DocumentView';
import { LinkDocPreview } from '../nodes/LinkDocPreview';
import './LinkMenuItem.scss';
diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
index 40b0d285c..849deb04e 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx
+++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
@@ -426,28 +426,26 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
const editorView = this._editorView;
if (editorView && (editorView as any).docView && !Doc.AreProtosEqual(target, this.rootDoc)) {
const autoLinkTerm = StrCast(target.title).replace(/^@/, '');
- const flattened1 = this.findInNode(editorView, editorView.state.doc, autoLinkTerm);
var alink: Doc | undefined;
- flattened1.forEach((flat, i) => {
- const flattened = this.findInNode(this._editorView!, this._editorView!.state.doc, autoLinkTerm);
- this._searchIndex = ++this._searchIndex > flattened.length - 1 ? 0 : this._searchIndex;
+ this.findInNode(editorView, editorView.state.doc, autoLinkTerm).forEach(sel => {
const splitter = editorView.state.schema.marks.splitter.create({ id: Utils.GenerateGuid() });
- const sel = flattened[i];
- tr = tr.addMark(sel.from, sel.to, splitter);
- tr.doc.nodesBetween(sel.from, sel.to, (node: any, pos: number, parent: any) => {
- if (node.firstChild === null && !node.marks.find((m: Mark) => m.type.name === schema.marks.noAutoLinkAnchor.name) && node.marks.find((m: Mark) => m.type.name === schema.marks.splitter.name)) {
- alink =
- alink ??
- (DocListCast(this.Document.links).find(link => Doc.AreProtosEqual(Cast(link.anchor1, Doc, null), this.rootDoc) && Doc.AreProtosEqual(Cast(link.anchor2, Doc, null), target)) ||
- DocUtils.MakeLink({ doc: this.props.Document }, { doc: target }, LinkManager.AutoKeywords)!);
- newAutoLinks.add(alink);
- const allAnchors = [{ href: Doc.localServerPath(target), title: 'a link', anchorId: this.props.Document[Id] }];
- allAnchors.push(...(node.marks.find((m: Mark) => m.type.name === schema.marks.autoLinkAnchor.name)?.attrs.allAnchors ?? []));
- const link = editorView.state.schema.marks.autoLinkAnchor.create({ allAnchors, title: 'auto term', location: 'add:right' });
- tr = tr.addMark(pos, pos + node.nodeSize, link);
- }
- });
- tr = tr.removeMark(sel.from, sel.to, splitter);
+ if (!sel.$anchor.pos || editorView.state.doc.textBetween(sel.$anchor.pos - 1, sel.$to.pos).trim() === autoLinkTerm) {
+ tr = tr.addMark(sel.from, sel.to, splitter);
+ tr.doc.nodesBetween(sel.from, sel.to, (node: any, pos: number, parent: any) => {
+ if (node.firstChild === null && !node.marks.find((m: Mark) => m.type.name === schema.marks.noAutoLinkAnchor.name) && node.marks.find((m: Mark) => m.type.name === schema.marks.splitter.name)) {
+ alink =
+ alink ??
+ (DocListCast(this.Document.links).find(link => Doc.AreProtosEqual(Cast(link.anchor1, Doc, null), this.rootDoc) && Doc.AreProtosEqual(Cast(link.anchor2, Doc, null), target)) ||
+ DocUtils.MakeLink({ doc: this.props.Document }, { doc: target }, LinkManager.AutoKeywords)!);
+ newAutoLinks.add(alink);
+ const allAnchors = [{ href: Doc.localServerPath(target), title: 'a link', anchorId: this.props.Document[Id] }];
+ allAnchors.push(...(node.marks.find((m: Mark) => m.type.name === schema.marks.autoLinkAnchor.name)?.attrs.allAnchors ?? []));
+ const link = editorView.state.schema.marks.autoLinkAnchor.create({ allAnchors, title: 'auto term', location: 'add:right' });
+ tr = tr.addMark(pos, pos + node.nodeSize, link);
+ }
+ });
+ tr = tr.removeMark(sel.from, sel.to, splitter);
+ }
});
}
return tr;
@@ -841,6 +839,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
}
};
recordDictation = () => {
+ console.log('RECORD DICTATIN:');
DictationManager.Controls.listen({
interimHandler: this.setDictationContent,
continuous: { indefinite: false },
@@ -853,26 +852,35 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
stopDictation = (abort: boolean) => DictationManager.Controls.stop(!abort);
setDictationContent = (value: string) => {
+ console.log('DICTATION CONETNT: ' + value);
if (this._editorView && this._recordingStart) {
+ console.log('STEP 1');
if (this._break) {
- const textanchor = Docs.Create.TextanchorDocument({ title: 'dictation anchor' });
- this.addDocument(textanchor);
- const link = DocUtils.MakeLinkToActiveAudio(() => textanchor, false).lastElement();
- link && (Doc.GetProto(link).isDictation = true);
- if (!link) return;
- const audioanchor = Cast(link.anchor2, Doc, null);
- if (!audioanchor) return;
- audioanchor.backgroundColor = 'tan';
- const audiotag = this._editorView.state.schema.nodes.audiotag.create({
- timeCode: NumCast(audioanchor._timecodeToShow),
- audioId: audioanchor[Id],
- textId: textanchor[Id],
- });
- Doc.GetProto(textanchor).title = 'dictation:' + audiotag.attrs.timeCode;
- const tr = this._editorView.state.tr.insert(this._editorView.state.doc.content.size, audiotag);
- const tr2 = tr.setSelection(TextSelection.create(tr.doc, tr.doc.content.size));
- this._editorView.dispatch(tr.setSelection(TextSelection.create(tr2.doc, tr2.doc.content.size)));
+ console.log('BREAK');
+ const textanchorFunc = () => {
+ const tanch = Docs.Create.TextanchorDocument({ title: 'dictation anchor' });
+ return this.addDocument(tanch) ? tanch : undefined;
+ };
+ const link = DocUtils.MakeLinkToActiveAudio(textanchorFunc, false).lastElement();
+ if (link) {
+ Doc.GetProto(link).isDictation = true;
+ const audioanchor = Cast(link.anchor2, Doc, null);
+ const textanchor = Cast(link.anchor1, Doc, null);
+ if (audioanchor) {
+ audioanchor.backgroundColor = 'tan';
+ const audiotag = this._editorView.state.schema.nodes.audiotag.create({
+ timeCode: NumCast(audioanchor._timecodeToShow),
+ audioId: audioanchor[Id],
+ textId: textanchor[Id],
+ });
+ Doc.GetProto(textanchor).title = 'dictation:' + audiotag.attrs.timeCode;
+ const tr = this._editorView.state.tr.insert(this._editorView.state.doc.content.size, audiotag);
+ const tr2 = tr.setSelection(TextSelection.create(tr.doc, tr.doc.content.size));
+ this._editorView.dispatch(tr.setSelection(TextSelection.create(tr2.doc, tr2.doc.content.size)));
+ }
+ }
}
+ console.log('FINALIziNG');
const from = this._editorView.state.selection.from;
this._break = false;
const tr = this._editorView.state.tr.insertText(value);
@@ -1468,7 +1476,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
const editor = this._editorView!;
const pcords = editor.posAtCoords({ left: e.clientX, top: e.clientY });
!this.props.isSelected(true) && editor.dispatch(editor.state.tr.setSelection(new TextSelection(editor.state.doc.resolve(pcords?.pos || 0))));
- let target = (e.target as any).parentElement; // hrefs are stored on the database of the <a> node that wraps the hyerlink <span>
+ let target = e.target as any; // hrefs are stored on the dataset of the <a> node that wraps the hyerlink <span>
while (target && !target.dataset?.targethrefs) target = target.parentElement;
FormattedTextBoxComment.update(this, editor, undefined, target?.dataset?.targethrefs, target?.dataset.linkdoc);
}
@@ -1725,7 +1733,17 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
@computed get audioHandle() {
return (
- <div className="formattedTextBox-dictation" onClick={action(e => (this._recording = !this._recording))}>
+ <div
+ className="formattedTextBox-dictation"
+ onPointerDown={e =>
+ setupMoveUpEvents(
+ this,
+ e,
+ returnFalse,
+ emptyFunction,
+ action(e => (this._recording = !this._recording))
+ )
+ }>
<FontAwesomeIcon className="formattedTextBox-audioFont" style={{ color: this._recording ? 'red' : 'blue', transitionDelay: '0.6s', opacity: this._recording ? 1 : 0.25 }} icon={'microphone'} size="sm" />
</div>
);