(core) Add support for auto-copying docs on signup

Summary:
The new "copyDoc" query parameter on the login page sets a short-lived cookie, which is
then read when welcoming a new user to copy that document to their Home workspace, and
redirect to it. Currently, only templates and bare forks set this parameter.

A new API endpoint for copying a document to a workspace was also added.

Test Plan: Browser tests.

Reviewers: paulfitz

Reviewed By: paulfitz

Differential Revision: https://phab.getgrist.com/D3992
This commit is contained in:
George Gevoian
2023-09-06 14:35:46 -04:00
parent 90fb4434cc
commit 3dadf93c98
26 changed files with 1057 additions and 276 deletions

View File

@@ -1,5 +1,6 @@
import {GristDoc} from 'app/client/components/GristDoc';
import {IUndoState} from 'app/client/components/UndoStack';
import {UnsavedChange} from 'app/client/components/UnsavedChanges';
import {loadGristDoc} from 'app/client/lib/imports';
import {AppModel, getOrgNameOrGuest, reportError} from 'app/client/models/AppModel';
import {getDoc} from 'app/client/models/gristConfigCache';
@@ -94,6 +95,7 @@ export interface DocPageModel {
// the error that prompted the offer. If user is not owner, just flag that
// document needs attention of an owner.
offerRecovery(err: Error): void;
clearUnsavedChanges(): void;
}
export interface ImportSource {
@@ -154,6 +156,15 @@ export class DocPageModelImpl extends Disposable implements DocPageModel {
// (with the previous promise cancelled) when _openerDocKey changes.
private _openerHolder = Holder.create<FlowRunner>(this);
private readonly _unsavedChangeHolder = Holder.create<UnsavedChange>(this);
private readonly _isUnsavedFork = Computed.create(this,
this.isFork,
this.isSnapshot,
this.isTutorialFork,
(use, isFork, isSnapshot, isTutorialFork) => isFork && !isSnapshot && !isTutorialFork
);
constructor(private _appObj: App, public readonly appModel: AppModel, private _api: UserAPI = appModel.api) {
super();
@@ -185,6 +196,14 @@ export class DocPageModelImpl extends Disposable implements DocPageModel {
this.currentProduct.set(org?.billingAccount?.product ?? null);
}
}));
this.autoDispose(this._isUnsavedFork.addListener((isUnsavedFork) => {
if (isUnsavedFork) {
UnsavedChange.create(this._unsavedChangeHolder);
} else {
this._unsavedChangeHolder.clear();
}
}));
}
public createLeftPane(leftPanelOpen: Observable<boolean>) {
@@ -289,6 +308,10 @@ It also disables formulas. [{{error}}]", {error: err.message})
);
}
public clearUnsavedChanges(): void {
this._unsavedChangeHolder.clear();
}
private _onOpenError(err: Error) {
if (err instanceof CancelledError) {
// This means that we started loading a new doc before the previous one finished loading.