mirror of
https://github.com/gristlabs/grist-core.git
synced 2026-03-02 04:09:24 +00:00
(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:
@@ -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.
|
||||
|
||||
@@ -66,24 +66,30 @@ export function getMainOrgUrl(): string { return urlState().makeUrl({}); }
|
||||
// When on a document URL, returns the URL with just the doc ID, omitting other bits (like page).
|
||||
export function getCurrentDocUrl(): string { return urlState().makeUrl({docPage: undefined}); }
|
||||
|
||||
// Get url for the login page, which will then redirect to `nextUrl` (current page by default).
|
||||
export function getLoginUrl(nextUrl: string | null = _getCurrentUrl()): string {
|
||||
return _getLoginLogoutUrl('login', nextUrl);
|
||||
export interface GetLoginOrSignupUrlOptions {
|
||||
srcDocId?: string | null;
|
||||
/** Defaults to the current URL. */
|
||||
nextUrl?: string | null;
|
||||
}
|
||||
|
||||
// Get url for the signup page, which will then redirect to `nextUrl` (current page by default).
|
||||
export function getSignupUrl(nextUrl: string = _getCurrentUrl()): string {
|
||||
return _getLoginLogoutUrl('signup', nextUrl);
|
||||
// Get URL for the login page.
|
||||
export function getLoginUrl(options: GetLoginOrSignupUrlOptions = {}): string {
|
||||
return _getLoginLogoutUrl('login', options);
|
||||
}
|
||||
|
||||
// Get url for the logout page.
|
||||
// Get URL for the signup page.
|
||||
export function getSignupUrl(options: GetLoginOrSignupUrlOptions = {}): string {
|
||||
return _getLoginLogoutUrl('signup', options);
|
||||
}
|
||||
|
||||
// Get URL for the logout page.
|
||||
export function getLogoutUrl(): string {
|
||||
return _getLoginLogoutUrl('logout');
|
||||
}
|
||||
|
||||
// Get url for the signin page, which will then redirect to `nextUrl` (current page by default).
|
||||
export function getLoginOrSignupUrl(nextUrl: string = _getCurrentUrl()): string {
|
||||
return _getLoginLogoutUrl('signin', nextUrl);
|
||||
// Get URL for the signin page.
|
||||
export function getLoginOrSignupUrl(options: GetLoginOrSignupUrlOptions = {}): string {
|
||||
return _getLoginLogoutUrl('signin', options);
|
||||
}
|
||||
|
||||
export function getWelcomeHomeUrl() {
|
||||
@@ -100,9 +106,14 @@ function _getCurrentUrl(): string {
|
||||
return parseFirstUrlPart('o', pathname).path + search + hash;
|
||||
}
|
||||
|
||||
// Returns the URL for the given login page, with 'next' param optionally set.
|
||||
function _getLoginLogoutUrl(page: 'login'|'logout'|'signin'|'signup', nextUrl?: string | null): string {
|
||||
// Returns the URL for the given login page.
|
||||
function _getLoginLogoutUrl(
|
||||
page: 'login'|'logout'|'signin'|'signup',
|
||||
options: GetLoginOrSignupUrlOptions = {}
|
||||
): string {
|
||||
const {srcDocId, nextUrl = _getCurrentUrl()} = options;
|
||||
const startUrl = _buildUrl(page);
|
||||
if (srcDocId) { startUrl.searchParams.set('srcDocId', srcDocId); }
|
||||
if (nextUrl) { startUrl.searchParams.set('next', nextUrl); }
|
||||
return startUrl.href;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user