mirror of
https://github.com/gristlabs/grist-core.git
synced 2024-10-27 20:44:07 +00:00
(core) fix form URL when team is encoded in domain
Summary: This moves the `formUrl` logic to `encodeUrl`, which is more aware of how the URL is constructed than UserAPI. UserAPI can only reliably construct API URLs. Test Plan: extended tests Reviewers: georgegevoian Reviewed By: georgegevoian Subscribers: georgegevoian Differential Revision: https://phab.getgrist.com/D4171
This commit is contained in:
parent
95c0441d84
commit
dba3a59486
@ -15,6 +15,7 @@ import DataTableModel from 'app/client/models/DataTableModel';
|
|||||||
import {ViewFieldRec, ViewSectionRec} from 'app/client/models/DocModel';
|
import {ViewFieldRec, ViewSectionRec} from 'app/client/models/DocModel';
|
||||||
import {ShareRec} from 'app/client/models/entities/ShareRec';
|
import {ShareRec} from 'app/client/models/entities/ShareRec';
|
||||||
import {InsertColOptions} from 'app/client/models/entities/ViewSectionRec';
|
import {InsertColOptions} from 'app/client/models/entities/ViewSectionRec';
|
||||||
|
import {urlState} from 'app/client/models/gristUrlState';
|
||||||
import {SortedRowSet} from 'app/client/models/rowset';
|
import {SortedRowSet} from 'app/client/models/rowset';
|
||||||
import {showTransientTooltip} from 'app/client/ui/tooltips';
|
import {showTransientTooltip} from 'app/client/ui/tooltips';
|
||||||
import {cssButton} from 'app/client/ui2018/buttons';
|
import {cssButton} from 'app/client/ui2018/buttons';
|
||||||
@ -299,9 +300,12 @@ export class FormView extends Disposable {
|
|||||||
this._url = Computed.create(this, use => {
|
this._url = Computed.create(this, use => {
|
||||||
const doc = use(this.gristDoc.docPageModel.currentDoc);
|
const doc = use(this.gristDoc.docPageModel.currentDoc);
|
||||||
if (!doc) { return ''; }
|
if (!doc) { return ''; }
|
||||||
const url = this.gristDoc.app.topAppModel.api.formUrl({
|
const url = urlState().makeUrl({
|
||||||
urlId: doc.id,
|
api: true,
|
||||||
vsId: use(this.viewSection.id),
|
doc: doc.id,
|
||||||
|
form: {
|
||||||
|
vsId: use(this.viewSection.id),
|
||||||
|
},
|
||||||
});
|
});
|
||||||
return url;
|
return url;
|
||||||
});
|
});
|
||||||
@ -580,9 +584,12 @@ export class FormView extends Disposable {
|
|||||||
throw new Error('Unable to copy link: form is not published');
|
throw new Error('Unable to copy link: form is not published');
|
||||||
}
|
}
|
||||||
|
|
||||||
const url = this.gristDoc.app.topAppModel.api.formUrl({
|
const url = urlState().makeUrl({
|
||||||
shareKey:remoteShare.key,
|
doc: undefined,
|
||||||
vsId: this.viewSection.id(),
|
form: {
|
||||||
|
shareKey: remoteShare.key,
|
||||||
|
vsId: this.viewSection.id(),
|
||||||
|
},
|
||||||
});
|
});
|
||||||
await copyToClipboard(url);
|
await copyToClipboard(url);
|
||||||
showTransientTooltip(element, 'Link copied to clipboard', {key: 'copy-form-link'});
|
showTransientTooltip(element, 'Link copied to clipboard', {key: 'copy-form-link'});
|
||||||
|
@ -419,33 +419,6 @@ export interface UserAPI {
|
|||||||
* is specific to Grist installation, and might not be supported.
|
* is specific to Grist installation, and might not be supported.
|
||||||
*/
|
*/
|
||||||
closeOrg(): Promise<void>;
|
closeOrg(): Promise<void>;
|
||||||
/**
|
|
||||||
* Creates publicly shared URL for a rendered form.
|
|
||||||
*/
|
|
||||||
formUrl(options: FormUrlOptions): string;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface FormUrlOptions {
|
|
||||||
vsId: number;
|
|
||||||
/**
|
|
||||||
* The canonical URL or document ID.
|
|
||||||
*
|
|
||||||
* If set, the returned form URL will only be accessible by users with access to the
|
|
||||||
* document. This is currently only used for the preview functionality in the widget,
|
|
||||||
* where document access is a pre-requisite.
|
|
||||||
*
|
|
||||||
* Only one of `urlId` or `shareKey` should be set.
|
|
||||||
*/
|
|
||||||
urlId?: string;
|
|
||||||
/**
|
|
||||||
* The key of the Share granting access to the form.
|
|
||||||
*
|
|
||||||
* If set, the returned form URL will be accessible by anyone, so long as the form
|
|
||||||
* is published.
|
|
||||||
*
|
|
||||||
* Only one of `urlId` or `shareKey` should be set.
|
|
||||||
*/
|
|
||||||
shareKey?: string;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -537,19 +510,6 @@ export class UserAPIImpl extends BaseAPI implements UserAPI {
|
|||||||
super(_options);
|
super(_options);
|
||||||
}
|
}
|
||||||
|
|
||||||
public formUrl(options: FormUrlOptions): string {
|
|
||||||
const {urlId, shareKey, vsId} = options;
|
|
||||||
if (!urlId && !shareKey) {
|
|
||||||
throw new Error('Invalid form URL: missing urlId or shareKey');
|
|
||||||
}
|
|
||||||
|
|
||||||
if (urlId) {
|
|
||||||
return `${this._url}/api/docs/${urlId}/forms/${vsId}`;
|
|
||||||
} else {
|
|
||||||
return `${this._url}/forms/${shareKey}/${vsId}`;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public forRemoved(): UserAPI {
|
public forRemoved(): UserAPI {
|
||||||
const extraParameters = new Map<string, string>([['showRemoved', '1']]);
|
const extraParameters = new Map<string, string>([['showRemoved', '1']]);
|
||||||
return new UserAPIImpl(this._homeUrl, {...this._options, extraParameters});
|
return new UserAPIImpl(this._homeUrl, {...this._options, extraParameters});
|
||||||
|
@ -145,6 +145,12 @@ export interface IGristUrlState {
|
|||||||
// But this barely works, and is suitable only for documents. For decoding it
|
// But this barely works, and is suitable only for documents. For decoding it
|
||||||
// indicates that the URL probably points to an API endpoint.
|
// indicates that the URL probably points to an API endpoint.
|
||||||
viaShare?: boolean; // Accessing document via a special share.
|
viaShare?: boolean; // Accessing document via a special share.
|
||||||
|
|
||||||
|
// Form URLs can currently be encoded but not decoded.
|
||||||
|
form?: {
|
||||||
|
vsId: number; // a view section id of a form.
|
||||||
|
shareKey?: string; // only one of shareKey or doc should be set.
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
// Subset of GristLoadConfig used by getOrgUrlInfo(), which affects the interpretation of the
|
// Subset of GristLoadConfig used by getOrgUrlInfo(), which affects the interpretation of the
|
||||||
@ -280,6 +286,27 @@ export function encodeUrl(gristConfig: Partial<GristLoadConfig>,
|
|||||||
parts.push(`p/${state.homePage}`);
|
parts.push(`p/${state.homePage}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Form URLS can take two forms. If a docId/urlId is set, rather than
|
||||||
|
* a share key, the returned form URL will only be accessible by users
|
||||||
|
* with access to the document. This is currently only used for the
|
||||||
|
* preview functionality in the widget, where document access is a
|
||||||
|
* pre-requisite.
|
||||||
|
*
|
||||||
|
* When a share key is set, the returned form URL will be accessible
|
||||||
|
* by anyone, so long as the form is published.
|
||||||
|
*
|
||||||
|
* Only one of `doc` (docId/urlId) or `shareKey` should be set.
|
||||||
|
*/
|
||||||
|
if (state.form) {
|
||||||
|
if (state.doc) { parts.push('/'); }
|
||||||
|
parts.push('forms/');
|
||||||
|
if (state.form.shareKey) {
|
||||||
|
parts.push(state.form.shareKey + '/');
|
||||||
|
}
|
||||||
|
parts.push(String(state.form.vsId));
|
||||||
|
}
|
||||||
|
|
||||||
if (state.account) {
|
if (state.account) {
|
||||||
parts.push(state.account === 'account' ? 'account' : `account/${state.account}`);
|
parts.push(state.account === 'account' ? 'account' : `account/${state.account}`);
|
||||||
}
|
}
|
||||||
|
@ -236,6 +236,16 @@ describe('gristUrlState', function() {
|
|||||||
assert.equal(mockWindow.location.href, 'https://foo.example.com/ws/12/');
|
assert.equal(mockWindow.location.href, 'https://foo.example.com/ws/12/');
|
||||||
assert.deepEqual(state.state.get(), {org: 'foo', ws: 12});
|
assert.deepEqual(state.state.get(), {org: 'foo', ws: 12});
|
||||||
assert.equal(state.makeUrl({ws: 4}), 'https://foo.example.com/ws/4/');
|
assert.equal(state.makeUrl({ws: 4}), 'https://foo.example.com/ws/4/');
|
||||||
|
|
||||||
|
// Check form URLs in prod setup. They are produced on document pages.
|
||||||
|
await state.pushUrl({org: 'foo', doc: 'abc'});
|
||||||
|
state.loadState();
|
||||||
|
assert.equal(state.makeUrl({doc: undefined, form: { vsId: 4, shareKey: 'key' }}),
|
||||||
|
'https://foo.example.com/forms/key/4');
|
||||||
|
assert.equal(state.makeUrl({api: true, doc: 'abc', form: { vsId: 4 }}),
|
||||||
|
'https://foo.example.com/api/docs/abc/forms/4');
|
||||||
|
assert.equal(state.makeUrl({api: true, form: { vsId: 4 }}),
|
||||||
|
'https://foo.example.com/api/docs/abc/forms/4');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should produce correct results with single-org config', async function() {
|
it('should produce correct results with single-org config', async function() {
|
||||||
@ -265,6 +275,16 @@ describe('gristUrlState', function() {
|
|||||||
assert.equal(mockWindow.location.href, 'https://example.com/o/foo/');
|
assert.equal(mockWindow.location.href, 'https://example.com/o/foo/');
|
||||||
assert.deepEqual(state.state.get(), {org: 'foo'});
|
assert.deepEqual(state.state.get(), {org: 'foo'});
|
||||||
assert.equal(link.getAttribute('href'), 'https://example.com/o/foo/ws/4/');
|
assert.equal(link.getAttribute('href'), 'https://example.com/o/foo/ws/4/');
|
||||||
|
|
||||||
|
// Check form URLs in single org setup from document pages.
|
||||||
|
await state.pushUrl({org: 'foo', doc: 'abc'});
|
||||||
|
state.loadState();
|
||||||
|
assert.equal(state.makeUrl({doc: undefined, form: { vsId: 4, shareKey: 'key' }}),
|
||||||
|
'https://example.com/o/foo/forms/key/4');
|
||||||
|
assert.equal(state.makeUrl({api: true, doc: 'abc', form: { vsId: 4 }}),
|
||||||
|
'https://example.com/o/foo/api/docs/abc/forms/4');
|
||||||
|
assert.equal(state.makeUrl({api: true, form: { vsId: 4 }}),
|
||||||
|
'https://example.com/o/foo/api/docs/abc/forms/4');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should produce correct results with custom config', async function() {
|
it('should produce correct results with custom config', async function() {
|
||||||
|
Loading…
Reference in New Issue
Block a user