Skip to content
2 changes: 1 addition & 1 deletion app/client/components/BaseView.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,13 +34,13 @@ import {closeRegisteredMenu} from 'app/client/ui2018/menus';
import type {CommentText} from 'app/client/widgets/MentionTextBox';
import {BuildEditorOptions, createAllFieldWidgets, FieldBuilder} from 'app/client/widgets/FieldBuilder';
import {BulkColValues, CellValue, DocAction, UserAction} from 'app/common/DocActions';
import {DocStateComparison} from 'app/common/DocState';
import {DismissedPopup} from 'app/common/Prefs';
import {SortFunc} from 'app/common/SortFunc';
import {Sort} from 'app/common/SortSpec';
import * as gristTypes from 'app/common/gristTypes';
import {IGristUrlState} from 'app/common/gristUrls';
import {arrayRepeat, nativeCompare, roundDownToMultiple, waitObs} from 'app/common/gutil';
import {DocStateComparison} from 'app/common/UserAPI';
import {CursorPos, UIRowId} from 'app/plugin/GristAPI';

import {Events as BackboneEvents} from 'backbone';
Expand Down
1 change: 1 addition & 0 deletions app/client/components/DocComm.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ export class DocComm extends Disposable implements ActiveDocAPI {
public stopTiming = this._wrapMethod("stopTiming");
public getAssistantState = this._wrapMethod("getAssistantState");
public listActiveUserProfiles = this._wrapMethod("listActiveUserProfiles");
public applyProposal = this._wrapMethod("applyProposal");

public changeUrlIdEmitter = this.autoDispose(new Emitter());

Expand Down
3 changes: 2 additions & 1 deletion app/client/components/GristDoc.ts
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@ import {delay} from 'app/common/delay';
import {DisposableWithEvents} from 'app/common/DisposableWithEvents';
import {isSchemaAction, UserAction} from 'app/common/DocActions';
import {OpenLocalDocResult} from 'app/common/DocListAPI';
import {DocStateComparison} from 'app/common/DocState';
import {isList, isListType, isRefListType} from 'app/common/gristTypes';
import {HashLink, IDocPage, isViewDocPage, parseUrlId, SpecialDocPage, ViewDocPage} from 'app/common/gristUrls';
import {undef, waitObs} from 'app/common/gutil';
Expand All @@ -79,7 +80,7 @@ import type {UserOrgPrefs} from 'app/common/Prefs';
import {StringUnion} from 'app/common/StringUnion';
import {TableData} from 'app/common/TableData';
import {getGristConfig} from 'app/common/urlUtils';
import {AttachmentTransferStatus, DocAPI, DocStateComparison, ExtendedUser} from 'app/common/UserAPI';
import {AttachmentTransferStatus, DocAPI, ExtendedUser} from 'app/common/UserAPI';
import {AttachedCustomWidgets, IAttachedCustomWidget, IWidgetType, WidgetType} from 'app/common/widgetTypes';
import {CursorPos} from 'app/plugin/GristAPI';
import {
Expand Down
9 changes: 8 additions & 1 deletion app/client/lib/UrlState.ts
Original file line number Diff line number Diff line change
Expand Up @@ -125,17 +125,24 @@ export class UrlState<IUrlState extends object> extends Disposable {
* Applies to an <a> element to create a smart link, e.g. dom('a', setLinkUrl({ws: wsId})). It
* both sets the href (e.g. to allow the link to be opened to a new tab), AND intercepts plain
* clicks on it to "follow" the link without reloading the page.
*
* If a "beforeChange" option is passed in, it will be run just before changing the URL.
*/
public setLinkUrl(
urlState: IUrlState|UpdateFunc<IUrlState>,
options?: {replace?: boolean, avoidReload?: boolean}
options?: {
replace?: boolean,
avoidReload?: boolean,
beforeChange?: () => void;
}
): DomElementMethod[] {
return [
dom.attr('href', (use) => this.makeUrl(urlState, use)),
dom.on('click', (ev) => {
// Only override plain-vanilla clicks.
if (ev.shiftKey || ev.metaKey || ev.ctrlKey || ev.altKey) { return; }
ev.preventDefault();
options?.beforeChange?.();
return this.pushUrl(urlState, options);
}),
];
Expand Down
5 changes: 2 additions & 3 deletions app/client/models/DataTableModelWithDiff.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,9 @@ import { TableData } from 'app/client/models/TableData';
import { createEmptyTableDelta, getTableIdAfter, getTableIdBefore, TableDelta } from 'app/common/ActionSummary';
import { DisposableWithEvents } from 'app/common/DisposableWithEvents';
import { CellVersions, UserAction } from 'app/common/DocActions';
import { GristObjCode } from 'app/plugin/GristData';
import { DocStateComparisonDetails } from 'app/common/DocState';
import { CellDelta } from 'app/common/TabularDiff';
import { DocStateComparisonDetails } from 'app/common/UserAPI';
import { CellValue } from 'app/plugin/GristData';
import { CellValue, GristObjCode } from 'app/plugin/GristData';

// A special row id, representing omitted rows.
const ROW_ID_SKIP = -1;
Expand Down
32 changes: 31 additions & 1 deletion app/client/ui/DocumentSettings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ import {openFilePicker} from 'app/client/ui/FileDialog';
import {buildNotificationsConfig} from 'app/client/ui/Notifications';
import {hoverTooltip, showTransientTooltip, withInfoTooltip} from 'app/client/ui/tooltips';
import {bigBasicButton, bigPrimaryButton} from 'app/client/ui2018/buttons';
import {cssRadioCheckboxOptions, radioCheckboxOption} from 'app/client/ui2018/checkbox';
import {cssRadioCheckboxOptions, labeledSquareCheckbox, radioCheckboxOption} from 'app/client/ui2018/checkbox';
import {colors, mediaSmall, theme} from 'app/client/ui2018/cssVars';
import {icon} from 'app/client/ui2018/icons';
import {cssLink} from 'app/client/ui2018/links';
Expand Down Expand Up @@ -62,6 +62,10 @@ export class DocSettingsPage extends Disposable {
private _timezone = this._docInfo.timezone;
private _locale: KoSaveableObservable<string> = this._docInfo.documentSettingsJson.prop('locale');
private _currency: KoSaveableObservable<string|undefined> = this._docInfo.documentSettingsJson.prop('currency');
private _acceptProposals = Observable.create(
this,
Boolean(this._gristDoc.docPageModel.currentDoc.get()?.options?.proposedChanges?.acceptProposals)
);

constructor(private _gristDoc: GristDoc) {
super();
Expand All @@ -72,6 +76,7 @@ export class DocSettingsPage extends Disposable {
const isTimingOn = this._gristDoc.isTimingOn;
const isDocOwner = isOwner(docPageModel.currentDoc.get());
const isDocEditor = isOwnerOrEditor(docPageModel.currentDoc.get());
const proposedChangesEnabled = docPageModel.appModel?.experiments?.isEnabled('proposedChangesPage');

return cssContainer({tabIndex: '-1'},
dom.create(AdminSection, t('Document Settings'), [
Expand Down Expand Up @@ -112,6 +117,31 @@ export class DocSettingsPage extends Disposable {
),
disabled: isDocOwner ? false : t('Only available to document owners'),
}),
proposedChangesEnabled ? dom.create(AdminSectionItem, {
id: 'acceptProposals',
name: t('Proposals'),
description: t('Allow others to propose changes'),
value: labeledSquareCheckbox(
this._acceptProposals,
t("Show proposals"),
dom.on('click', () => {
// A tiny delay just to make reload feel less jarring.
setTimeout(async () => {
const docId = docPageModel.currentDocId.get();
if (!docId) {
// Should never happen, don't bother translating.
reportError(new Error('Document not found'));
return;
}
const acceptProposals = this._acceptProposals.get();
await docPageModel.appModel.api.updateDoc(docId, {options: {proposedChanges: {acceptProposals}}});
window.location.reload();
}, 250);
}),
testId('accept-proposals'),
),
disabled: isDocOwner ? false : t('Only available to document owners'),
}) : null,
]),

dom.create(buildNotificationsConfig, this._gristDoc.docApi, docPageModel.currentDoc.get()),
Expand Down
1 change: 1 addition & 0 deletions app/client/ui/Experiments.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ const G = getBrowserGlobals('document', 'window');

const EXPERIMENTS = {
newRecordButton: () => t('New record button'),
proposedChangesPage: () => t('Proposed changes page'),
};

type Experiment = keyof typeof EXPERIMENTS;
Expand Down
Loading