mirror of
https://github.com/gristlabs/grist-core.git
synced 2026-03-02 04:09:24 +00:00
(core) Add initial tutorials implementation
Summary: Documents can now be flagged as tutorials, which causes them to display Markdown-formatted slides from a special GristDocTutorial table. Tutorial documents are forked on open, and remember the last slide a user was on. They can be restarted too, which prepares a new fork of the tutorial. Test Plan: Browser tests. Reviewers: jarek Reviewed By: jarek Differential Revision: https://phab.getgrist.com/D3813
This commit is contained in:
@@ -231,8 +231,12 @@ export function attachAppEndpoint(options: AttachOptions): void {
|
||||
// Query DB for the doc metadata, to include in the page (as a pre-fetch of getDoc() call),
|
||||
// and to get fresh (uncached) access info.
|
||||
doc = await dbManager.getDoc({userId, org: mreq.org, urlId});
|
||||
const slug = getSlugIfNeeded(doc);
|
||||
if (isAnonymousUser(mreq) && doc.type === 'tutorial') {
|
||||
// Tutorials require users to be signed in.
|
||||
throw new ApiError('You must be signed in to access a tutorial.', 403);
|
||||
}
|
||||
|
||||
const slug = getSlugIfNeeded(doc);
|
||||
const slugMismatch = (req.params.slug || null) !== (slug || null);
|
||||
const preferredUrlId = doc.urlId || doc.id;
|
||||
if (urlId !== preferredUrlId || slugMismatch) {
|
||||
@@ -263,8 +267,8 @@ export function attachAppEndpoint(options: AttachOptions): void {
|
||||
// First check if anonymous user has access to this org. If so, we don't propose
|
||||
// that they log in. This is the same check made in redirectToLogin() middleware.
|
||||
const result = await dbManager.getOrg({userId: getUserId(mreq)}, mreq.org || null);
|
||||
if (result.status !== 200) {
|
||||
// Anonymous user does not have any access to this org, or to this doc.
|
||||
if (result.status !== 200 || doc?.type === 'tutorial') {
|
||||
// Anonymous user does not have any access to this org, doc, or tutorial.
|
||||
// Redirect to log in.
|
||||
return forceLogin(req, res, next);
|
||||
}
|
||||
|
||||
@@ -9,7 +9,7 @@ import {SortFunc} from 'app/common/SortFunc';
|
||||
import {Sort} from 'app/common/SortSpec';
|
||||
import {MetaRowRecord} from 'app/common/TableData';
|
||||
import {DocReplacementOptions, DocState, DocStateComparison, DocStates, NEW_DOCUMENT_CODE} from 'app/common/UserAPI';
|
||||
import {HomeDBManager, makeDocAuthResult, QueryResult} from 'app/gen-server/lib/HomeDBManager';
|
||||
import {HomeDBManager, makeDocAuthResult} from 'app/gen-server/lib/HomeDBManager';
|
||||
import * as Types from "app/plugin/DocApiTypes";
|
||||
import DocApiTypesTI from "app/plugin/DocApiTypes-ti";
|
||||
import GristDataTI from 'app/plugin/GristData-ti';
|
||||
@@ -784,6 +784,27 @@ export class DocWorkerApi {
|
||||
'Content-Type': 'application/json',
|
||||
}
|
||||
});
|
||||
if (req.body.resetTutorialMetadata) {
|
||||
const scope = getDocScope(req);
|
||||
const tutorialTrunkId = options.sourceDocId;
|
||||
await this._dbManager.connection.transaction(async (manager) => {
|
||||
// Fetch the tutorial trunk doc so we can replace the tutorial doc's name.
|
||||
const tutorialTrunk = await this._dbManager.getRawDocById(tutorialTrunkId, manager);
|
||||
await this._dbManager.updateDocument(
|
||||
scope,
|
||||
{
|
||||
name: tutorialTrunk.name,
|
||||
options: {
|
||||
tutorial: {
|
||||
// For now, the only state we need to reset is the slide position.
|
||||
lastSlideIndex: 0,
|
||||
},
|
||||
},
|
||||
},
|
||||
manager
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
||||
if (req.body.snapshotId) {
|
||||
options.snapshotId = String(req.body.snapshotId);
|
||||
@@ -1216,12 +1237,7 @@ export class DocWorkerApi {
|
||||
];
|
||||
await Promise.all(docsToDelete.map(docName => this._docManager.deleteDoc(null, docName, true)));
|
||||
// Permanently delete from database.
|
||||
let query: QueryResult<number>;
|
||||
if (forkId) {
|
||||
query = await this._dbManager.deleteFork({...scope, urlId: forkId});
|
||||
} else {
|
||||
query = await this._dbManager.deleteDocument(scope);
|
||||
}
|
||||
const query = await this._dbManager.deleteDocument(scope);
|
||||
this._dbManager.checkQueryResult(query);
|
||||
await sendReply(req, res, query);
|
||||
} else {
|
||||
|
||||
Reference in New Issue
Block a user