aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/client/util/CurrentUserUtils.ts2
-rw-r--r--src/client/util/Scripting.ts65
-rw-r--r--src/client/views/DocumentDecorations.tsx15
-rw-r--r--src/fields/ScriptField.ts169
4 files changed, 138 insertions, 113 deletions
diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts
index 77f9958f7..ce32595d4 100644
--- a/src/client/util/CurrentUserUtils.ts
+++ b/src/client/util/CurrentUserUtils.ts
@@ -288,7 +288,7 @@ export class CurrentUserUtils {
{ title: "Tools", target: this.setupToolsBtnPanel(doc, "myTools"), icon: "wrench", funcs: {hidden: "IsNoviceMode()"} },
{ title: "Imports", target: this.setupImportSidebar(doc, "myImports"), icon: "upload", },
{ title: "Recently Closed", target: this.setupRecentlyClosed(doc, "myRecentlyClosed"), icon: "archive", },
- { title: "Shared Docs", target: Doc.MySharedDocs, icon: "users", funcs:{badgeValue:badgeValue}},
+ { title: "Shared Docs", target: Doc.MySharedDocs, icon: "users", funcs: {badgeValue:badgeValue}},
{ title: "Trails", target: this.setupTrails(doc, "myTrails"), icon: "pres-trail", },
{ title: "User Doc View", target: this.setupUserDocView(doc, "myUserDocView"), icon: "address-card",funcs: {hidden: "IsNoviceMode()"} },
].map(tuple => ({...tuple, scripts:{onClick: 'selectMainMenu(self)'}}));
diff --git a/src/client/util/Scripting.ts b/src/client/util/Scripting.ts
index 3791bec73..ea2bf6551 100644
--- a/src/client/util/Scripting.ts
+++ b/src/client/util/Scripting.ts
@@ -5,12 +5,11 @@
// import * as typescriptes5 from '!!raw-loader!../../../node_modules/typescript/lib/lib.es5.d.ts'
// @ts-ignore
import * as typescriptlib from '!!raw-loader!./type_decls.d';
-import * as ts from "typescript";
-import { Doc, Field } from "../../fields/Doc";
-import { scriptingGlobals, ScriptingGlobals } from "./ScriptingGlobals";
+import * as ts from 'typescript';
+import { Doc, Field } from '../../fields/Doc';
+import { scriptingGlobals, ScriptingGlobals } from './ScriptingGlobals';
export { ts };
-
export interface ScriptSuccess {
success: true;
result: any;
@@ -46,7 +45,6 @@ export function isCompileError(toBeDetermined: CompileResult): toBeDetermined is
return false;
}
-
function Run(script: string | undefined, customParams: string[], diagnostics: any[], originalScript: string, options: ScriptOptions): CompileResult {
const errors = diagnostics.filter(diag => diag.category === ts.DiagnosticCategory.Error);
if ((options.typecheck !== false && errors.length) || !script) {
@@ -63,7 +61,7 @@ function Run(script: string | undefined, customParams: string[], diagnostics: an
const run = (args: { [name: string]: any } = {}, onError?: (e: any) => void, errorVal?: any): ScriptResult => {
const argsArray: any[] = [];
for (const name of customParams) {
- if (name === "this") {
+ if (name === 'this') {
continue;
}
if (name in args) {
@@ -86,11 +84,10 @@ function Run(script: string | undefined, customParams: string[], diagnostics: an
return { success: true, result };
} catch (error) {
-
if (batch) {
batch.end();
}
- onError?.(script + " " + error);
+ onError?.(script + ' ' + error);
return { success: false, error, result: errorVal };
}
};
@@ -151,16 +148,16 @@ class ScriptingCompilerHost {
}
export type Traverser = (node: ts.Node, indentation: string) => boolean | void;
-export type TraverserParam = Traverser | { onEnter: Traverser, onLeave: Traverser };
+export type TraverserParam = Traverser | { onEnter: Traverser; onLeave: Traverser };
export type Transformer = {
- transformer: ts.TransformerFactory<ts.SourceFile>,
- getVars?: () => { capturedVariables: { [name: string]: Field } }
+ transformer: ts.TransformerFactory<ts.SourceFile>;
+ getVars?: () => { capturedVariables: { [name: string]: Field } };
};
export interface ScriptOptions {
requiredType?: string; // does function required a typed return value
- addReturn?: boolean; // does the compiler automatically add a return statement
+ addReturn?: boolean; // does the compiler automatically add a return statement
params?: { [name: string]: string }; // list of function parameters and their types
- capturedVariables?: { [name: string]: Field }; // list of captured variables
+ capturedVariables?: { [name: string]: Doc | number | string | boolean }; // list of captured variables
typecheck?: boolean; // should the compiler perform typechecking
editable?: boolean; // can the script edit Docs
traverser?: TraverserParam;
@@ -169,24 +166,28 @@ export interface ScriptOptions {
}
// function forEachNode(node:ts.Node, fn:(node:any) => void);
-function forEachNode(node: ts.Node, onEnter: Traverser, onExit?: Traverser, indentation = "") {
- return onEnter(node, indentation) || ts.forEachChild(node, (n: any) => {
- forEachNode(n, onEnter, onExit, indentation + " ");
- }) || (onExit && onExit(node, indentation));
+function forEachNode(node: ts.Node, onEnter: Traverser, onExit?: Traverser, indentation = '') {
+ return (
+ onEnter(node, indentation) ||
+ ts.forEachChild(node, (n: any) => {
+ forEachNode(n, onEnter, onExit, indentation + ' ');
+ }) ||
+ (onExit && onExit(node, indentation))
+ );
}
export function CompileScript(script: string, options: ScriptOptions = {}): CompileResult {
- const { requiredType = "", addReturn = false, params = {}, capturedVariables = {}, typecheck = true } = options;
+ const { requiredType = '', addReturn = false, params = {}, capturedVariables = {}, typecheck = true } = options;
if (options.params && !options.params.this) options.params.this = Doc.name;
if (options.params && !options.params.self) options.params.self = Doc.name;
if (options.globals) {
ScriptingGlobals.setScriptingGlobals(options.globals);
}
- const host = new ScriptingCompilerHost;
+ const host = new ScriptingCompilerHost();
if (options.traverser) {
const sourceFile = ts.createSourceFile('script.ts', script, ts.ScriptTarget.ES2015, true);
- const onEnter = typeof options.traverser === "object" ? options.traverser.onEnter : options.traverser;
- const onLeave = typeof options.traverser === "object" ? options.traverser.onLeave : undefined;
+ const onEnter = typeof options.traverser === 'object' ? options.traverser.onEnter : options.traverser;
+ const onLeave = typeof options.traverser === 'object' ? options.traverser.onLeave : undefined;
forEachNode(sourceFile, onEnter, onLeave);
}
if (options.transformer) {
@@ -199,17 +200,17 @@ export function CompileScript(script: string, options: ScriptOptions = {}): Comp
}
const transformed = result.transformed;
const printer = ts.createPrinter({
- newLine: ts.NewLineKind.LineFeed
+ newLine: ts.NewLineKind.LineFeed,
});
script = printer.printFile(transformed[0]);
result.dispose();
}
const paramNames: string[] = [];
- if ("this" in params || "this" in capturedVariables) {
- paramNames.push("this");
+ if ('this' in params || 'this' in capturedVariables) {
+ paramNames.push('this');
}
for (const key in params) {
- if (key === "this") continue;
+ if (key === 'this') continue;
paramNames.push(key);
}
const paramList = paramNames.map(key => {
@@ -217,21 +218,21 @@ export function CompileScript(script: string, options: ScriptOptions = {}): Comp
return `${key}: ${val}`;
});
for (const key in capturedVariables) {
- if (key === "this") continue;
+ if (key === 'this') continue;
const val = capturedVariables[key];
paramNames.push(key);
- paramList.push(`${key}: ${typeof val === "object" ? Object.getPrototypeOf(val).constructor.name : typeof val}`);
+ paramList.push(`${key}: ${typeof val === 'object' ? Object.getPrototypeOf(val).constructor.name : typeof val}`);
}
- const paramString = paramList.join(", ");
- const body = addReturn && !script.startsWith("{ return") ? `return ${script};` : script;
+ const paramString = paramList.join(', ');
+ const body = addReturn && !script.startsWith('{ return') ? `return ${script};` : script;
const reqTypes = requiredType ? `: ${requiredType}` : '';
const funcScript = `(function(${paramString})${reqTypes} { ${body} })`;
- host.writeFile("file.ts", funcScript);
+ host.writeFile('file.ts', funcScript);
if (typecheck) host.writeFile('node_modules/typescript/lib/lib.d.ts', typescriptlib);
- const program = ts.createProgram(["file.ts"], {}, host);
+ const program = ts.createProgram(['file.ts'], {}, host);
const testResult = program.emit();
- const outputText = host.readFile("file.js");
+ const outputText = host.readFile('file.js');
const diagnostics = ts.getPreEmitDiagnostics(program).concat(testResult.diagnostics);
diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx
index 432dd360a..c53e61699 100644
--- a/src/client/views/DocumentDecorations.tsx
+++ b/src/client/views/DocumentDecorations.tsx
@@ -157,6 +157,7 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number; P
@action
onBackgroundMove = (dragTitle: boolean, e: PointerEvent): boolean => {
const dragDocView = SelectionManager.Views()[0];
+ if (DocListCast(Doc.MyOverlayDocs.data).includes(dragDocView.rootDoc)) return false;
const { left, top } = dragDocView.getBounds() || { left: 0, top: 0 };
const dragData = new DragManager.DocumentDragData(
SelectionManager.Views().map(dv => dv.props.Document),
@@ -639,21 +640,21 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number; P
);
const colorScheme = StrCast(Doc.ActiveDashboard?.colorScheme);
- const titleArea = hideTitle ? null : this._editingTitle ? (
+ const titleArea = this._editingTitle ? (
<input
ref={this._keyinput}
className={`documentDecorations-title${colorScheme}`}
type="text"
name="dynbox"
autoComplete="on"
- value={this._accumulatedTitle}
- onBlur={e => this.titleBlur()}
- onChange={action(e => (this._accumulatedTitle = e.target.value))}
- onKeyDown={this.titleEntered}
+ value={hideTitle ? '' : this._accumulatedTitle}
+ onBlur={e => !hideTitle && this.titleBlur()}
+ onChange={action(e => !hideTitle && (this._accumulatedTitle = e.target.value))}
+ onKeyDown={hideTitle ? emptyFunction : this.titleEntered}
/>
) : (
<div className="documentDecorations-title" key="title" onPointerDown={this.onTitleDown}>
- <span className={`documentDecorations-titleSpan${colorScheme}`}>{`${this.selectionTitle}`}</span>
+ <span className={`documentDecorations-titleSpan${colorScheme}`}>{`${hideTitle ? '' : this.selectionTitle}`}</span>
</div>
);
@@ -710,7 +711,7 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number; P
height: bounds.b - bounds.y + this._resizeBorderWidth + this._titleHeight + 'px',
}}>
{hideDeleteButton ? <div /> : topBtn('close', this.hasIcons ? 'times' : 'window-maximize', undefined, e => this.onCloseClick(this.hasIcons ? true : undefined), 'Close')}
- {hideTitle ? null : titleArea}
+ {titleArea}
{hideOpenButton ? null : topBtn('open', 'external-link-alt', this.onMaximizeDown, undefined, 'Open in Tab (ctrl: as alias, shift: in new collection)')}
{hideResizers ? null : (
<>
diff --git a/src/fields/ScriptField.ts b/src/fields/ScriptField.ts
index 40ca0ce22..2b4b1ef4c 100644
--- a/src/fields/ScriptField.ts
+++ b/src/fields/ScriptField.ts
@@ -1,29 +1,32 @@
-import { computedFn } from "mobx-utils";
-import { createSimpleSchema, custom, map, object, primitive, PropSchema, serializable, SKIP } from "serializr";
-import { CompiledScript, CompileScript } from "../client/util/Scripting";
-import { scriptingGlobal, ScriptingGlobals } from "../client/util/ScriptingGlobals";
-import { autoObject, Deserializable } from "../client/util/SerializationHelper";
-import { numberRange } from "../Utils";
-import { Doc, Field, Opt } from "./Doc";
-import { Copy, ToScriptString, ToString } from "./FieldSymbols";
-import { List } from "./List";
-import { ObjectField } from "./ObjectField";
-import { ProxyField } from "./Proxy";
-import { Cast, NumCast } from "./Types";
-import { Plugins } from "./util";
+import { computedFn } from 'mobx-utils';
+import { createSimpleSchema, custom, map, object, primitive, PropSchema, serializable, SKIP } from 'serializr';
+import { DocServer } from '../client/DocServer';
+import { CompiledScript, CompileScript, ScriptOptions } from '../client/util/Scripting';
+import { scriptingGlobal, ScriptingGlobals } from '../client/util/ScriptingGlobals';
+import { autoObject, Deserializable } from '../client/util/SerializationHelper';
+import { numberRange } from '../Utils';
+import { Doc, Field, Opt } from './Doc';
+import { Copy, Id, ToScriptString, ToString } from './FieldSymbols';
+import { List } from './List';
+import { ObjectField } from './ObjectField';
+import { Cast, NumCast } from './Types';
+import { Plugins } from './util';
function optional(propSchema: PropSchema) {
- return custom(value => {
- if (value !== undefined) {
- return propSchema.serializer(value);
- }
- return SKIP;
- }, (jsonValue: any, context: any, oldValue: any, callback: (err: any, result: any) => void) => {
- if (jsonValue !== undefined) {
- return propSchema.deserializer(jsonValue, callback, context, oldValue);
+ return custom(
+ value => {
+ if (value !== undefined) {
+ return propSchema.serializer(value);
+ }
+ return SKIP;
+ },
+ (jsonValue: any, context: any, oldValue: any, callback: (err: any, result: any) => void) => {
+ if (jsonValue !== undefined) {
+ return propSchema.deserializer(jsonValue, callback, context, oldValue);
+ }
+ return SKIP;
}
- return SKIP;
- });
+ );
}
const optionsSchema = createSimpleSchema({
@@ -32,31 +35,19 @@ const optionsSchema = createSimpleSchema({
typecheck: true,
editable: true,
readonly: true,
- params: optional(map(primitive()))
+ params: optional(map(primitive())),
});
const scriptSchema = createSimpleSchema({
options: object(optionsSchema),
- originalScript: true
+ originalScript: true,
});
-async function deserializeScript(script: ScriptField) {
- const captures: ProxyField<Doc> = (script as any).captures;
- const cache = captures ? undefined : ScriptField.GetScriptFieldCache(script.script.originalScript);
- if (cache) return (script as any).script = cache;
- if (captures) {
- const doc = (await captures.value())!;
- const captured: any = {};
- const keys = Object.keys(doc);
- const vals = await Promise.all(keys.map(key => doc[key]) as any);
- keys.forEach((key, i) => captured[key] = vals[i]);
- (script.script.options as any).capturedVariables = captured;
- }
+function finalizeScript(script: ScriptField, captures: boolean) {
const comp = CompileScript(script.script.originalScript, script.script.options);
if (!comp.compiled) {
throw new Error("Couldn't compile loaded script");
}
- (script as any).script = comp;
!captures && ScriptField._scriptFieldCache.set(script.script.originalScript, comp);
if (script.setterscript) {
const compset = CompileScript(script.setterscript?.originalScript, script.setterscript.options);
@@ -65,10 +56,30 @@ async function deserializeScript(script: ScriptField) {
}
(script as any).setterscript = compset;
}
+ return comp;
+}
+async function deserializeScript(script: ScriptField) {
+ if (script.captures) {
+ const captured: any = {};
+ (script.script.options as ScriptOptions).capturedVariables = captured;
+ Promise.all(
+ script.captures.map(async capture => {
+ const key = capture.split(':')[0];
+ const val = capture.split(':')[1];
+ if (val === 'true') captured[key] = true;
+ else if (val === 'false') captured[key] = false;
+ else if (val.startsWith('ID->')) captured[key] = await DocServer.GetRefField(val.replace('ID->', ''));
+ else if (!isNaN(Number(val))) captured[key] = Number(val);
+ else captured[key] = val;
+ })
+ ).then(() => ((script as any).script = finalizeScript(script, true)));
+ } else {
+ (script as any).script = ScriptField.GetScriptFieldCache(script.script.originalScript) ?? finalizeScript(script, false);
+ }
}
@scriptingGlobal
-@Deserializable("script", deserializeScript)
+@Deserializable('script', deserializeScript)
export class ScriptField extends ObjectField {
@serializable(object(scriptSchema))
readonly script: CompiledScript;
@@ -76,18 +87,19 @@ export class ScriptField extends ObjectField {
readonly setterscript: CompiledScript | undefined;
@serializable(autoObject())
- private captures?: ProxyField<Doc>;
+ captures?: List<string>;
public static _scriptFieldCache: Map<string, Opt<CompiledScript>> = new Map();
- public static GetScriptFieldCache(field: string) { return this._scriptFieldCache.get(field); }
+ public static GetScriptFieldCache(field: string) {
+ return this._scriptFieldCache.get(field);
+ }
constructor(script: CompiledScript, setterscript?: CompiledScript) {
super();
- if (script?.options.capturedVariables) {
- const doc = Doc.assign(new Doc, script.options.capturedVariables);
- doc.system = true;
- this.captures = new ProxyField(doc);
+ const captured = script?.options.capturedVariables;
+ if (captured) {
+ this.captures = new List<string>(Object.keys(captured).map(key => key + ':' + (captured[key] instanceof Doc ? 'ID->' + (captured[key] as Doc)[Id] : captured[key].toString())));
}
this.setterscript = setterscript;
this.script = script;
@@ -122,46 +134,45 @@ export class ScriptField extends ObjectField {
}
[ToScriptString]() {
- return "script field";
+ return 'script field';
}
[ToString]() {
return this.script.originalScript;
}
- public static CompileScript(script: string, params: object = {}, addReturn = false, capturedVariables?: { [name: string]: Field }) {
+ public static CompileScript(script: string, params: object = {}, addReturn = false, capturedVariables?: { [name: string]: Doc | string | number | boolean }) {
const compiled = CompileScript(script, {
params: {
- this: Doc?.name || "Doc", // this is the doc that executes the script
- self: Doc?.name || "Doc", // self is the root doc of the doc that executes the script
- _last_: "any", // _last_ is the previous value of a computed field when it is being triggered to re-run.
- _readOnly_: "boolean", // _readOnly_ is set when a computed field is executed to indicate that it should not have mobx side-effects. used for checking the value of a set function (see FontIconBox)
- ...params
+ this: Doc?.name || 'Doc', // this is the doc that executes the script
+ self: Doc?.name || 'Doc', // self is the root doc of the doc that executes the script
+ _last_: 'any', // _last_ is the previous value of a computed field when it is being triggered to re-run.
+ _readOnly_: 'boolean', // _readOnly_ is set when a computed field is executed to indicate that it should not have mobx side-effects. used for checking the value of a set function (see FontIconBox)
+ ...params,
},
typecheck: false,
editable: true,
addReturn: addReturn,
- capturedVariables
+ capturedVariables,
});
return compiled;
}
- public static MakeFunction(script: string, params: object = {}, capturedVariables?: { [name: string]: Field }) {
+ public static MakeFunction(script: string, params: object = {}, capturedVariables?: { [name: string]: Doc | string | number | boolean }) {
const compiled = ScriptField.CompileScript(script, params, true, capturedVariables);
return compiled.compiled ? new ScriptField(compiled) : undefined;
}
- public static MakeScript(script: string, params: object = {}, capturedVariables?: { [name: string]: Field }) {
+ public static MakeScript(script: string, params: object = {}, capturedVariables?: { [name: string]: Doc | string | number | boolean }) {
const compiled = ScriptField.CompileScript(script, params, false, capturedVariables);
return compiled.compiled ? new ScriptField(compiled) : undefined;
}
}
@scriptingGlobal
-@Deserializable("computed", deserializeScript)
+@Deserializable('computed', deserializeScript)
export class ComputedField extends ScriptField {
_lastComputedResult: any;
//TODO maybe add an observable cache based on what is passed in for doc, considering there shouldn't really be that many possible values for doc
value = computedFn((doc: Doc) => this._valueOutsideReaction(doc));
- _valueOutsideReaction = (doc: Doc) => this._lastComputedResult = this.script.run({ this: doc, self: Cast(doc.rootDocument, Doc, null) || doc, _last_: this._lastComputedResult, _readOnly_: true }, console.log).result;
-
+ _valueOutsideReaction = (doc: Doc) => (this._lastComputedResult = this.script.run({ this: doc, self: Cast(doc.rootDocument, Doc, null) || doc, _last_: this._lastComputedResult, _readOnly_: true }, console.log).result);
[Copy](): ObjectField {
return new ComputedField(this.script, this.setterscript);
@@ -171,7 +182,7 @@ export class ComputedField extends ScriptField {
const compiled = ScriptField.CompileScript(script, params, false);
return compiled.compiled ? new ComputedField(compiled) : undefined;
}
- public static MakeFunction(script: string, params: object = {}, capturedVariables?: { [name: string]: Field }) {
+ public static MakeFunction(script: string, params: object = {}, capturedVariables?: { [name: string]: Doc | string | number | boolean }) {
const compiled = ScriptField.CompileScript(script, params, true, capturedVariables);
return compiled.compiled ? new ComputedField(compiled) : undefined;
}
@@ -182,7 +193,7 @@ export class ComputedField extends ScriptField {
doc[`${fieldKey}-indexed`] = flist;
}
const getField = ScriptField.CompileScript(`getIndexVal(self['${fieldKey}-indexed'], self.${interpolatorKey})`, {}, true, {});
- const setField = ScriptField.CompileScript(`setIndexVal(self['${fieldKey}-indexed'], self.${interpolatorKey}, value)`, { value: "any" }, true, {});
+ const setField = ScriptField.CompileScript(`setIndexVal(self['${fieldKey}-indexed'], self.${interpolatorKey}, value)`, { value: 'any' }, true, {});
return getField.compiled ? new ComputedField(getField, setField?.compiled ? setField : undefined) : undefined;
}
}
@@ -196,7 +207,7 @@ export namespace ComputedField {
useComputed = true;
}
- export const undefined = "__undefined";
+ export const undefined = '__undefined';
export function WithoutComputed<T>(fn: () => T) {
DisableComputedFields();
@@ -216,15 +227,27 @@ export namespace ComputedField {
}
}
-ScriptingGlobals.add(function setIndexVal(list: any[], index: number, value: any) {
- while (list.length <= index) list.push(undefined);
- list[index] = value;
-}, "sets the value at a given index of a list", "(list: any[], index: number, value: any)");
-
-ScriptingGlobals.add(function getIndexVal(list: any[], index: number) {
- return list?.reduce((p, x, i) => (i <= index && x !== undefined) || p === undefined ? x : p, undefined as any);
-}, "returns the value at a given index of a list", "(list: any[], index: number)");
-
-ScriptingGlobals.add(function makeScript(script: string) {
- return ScriptField.MakeScript(script);
-}, "returns the value at a given index of a list", "(list: any[], index: number)");
+ScriptingGlobals.add(
+ function setIndexVal(list: any[], index: number, value: any) {
+ while (list.length <= index) list.push(undefined);
+ list[index] = value;
+ },
+ 'sets the value at a given index of a list',
+ '(list: any[], index: number, value: any)'
+);
+
+ScriptingGlobals.add(
+ function getIndexVal(list: any[], index: number) {
+ return list?.reduce((p, x, i) => ((i <= index && x !== undefined) || p === undefined ? x : p), undefined as any);
+ },
+ 'returns the value at a given index of a list',
+ '(list: any[], index: number)'
+);
+
+ScriptingGlobals.add(
+ function makeScript(script: string) {
+ return ScriptField.MakeScript(script);
+ },
+ 'returns the value at a given index of a list',
+ '(list: any[], index: number)'
+);