Summary: - Remove modules related to old login / profile that we don't plan to bring back. - Remove old unused DocListModel. - Remove ext* tests that have been skipped and don't work. - Remove old ModalDialog, and switch its one remaining usage to the newer way. Test Plan: All tests should pass, and as many as before. Reviewers: paulfitz Reviewed By: paulfitz Differential Revision: https://phab.getgrist.com/D2668pull/3/head
parent
2e22966289
commit
f24a82e8d4
@ -1,30 +0,0 @@
|
||||
// Grist client libs
|
||||
import * as ModalDialog from 'app/client/components/ModalDialog';
|
||||
import * as dom from 'app/client/lib/dom';
|
||||
import * as kd from 'app/client/lib/koDom';
|
||||
import * as kf from 'app/client/lib/koForm';
|
||||
|
||||
export function showConfirmDialog(title: string, btnText: string, onConfirm: () => Promise<void>,
|
||||
explanation?: Element|string): void {
|
||||
const body = dom('div.confirm',
|
||||
explanation ? kf.row(explanation, kd.style('margin-bottom', '2rem')) : null,
|
||||
kf.row(
|
||||
1, kf.buttonGroup(
|
||||
kf.button(() => dialog.hide(), 'Cancel')
|
||||
),
|
||||
1, kf.buttonGroup(
|
||||
kf.accentButton(async () => {
|
||||
await onConfirm();
|
||||
dialog.hide();
|
||||
}, btnText)
|
||||
)
|
||||
)
|
||||
);
|
||||
const dialog = ModalDialog.create({
|
||||
title,
|
||||
body,
|
||||
width: '300px',
|
||||
show: true
|
||||
});
|
||||
dialog.once('close', () => dialog.dispose());
|
||||
}
|
@ -1,205 +0,0 @@
|
||||
// External dependencies
|
||||
const _ = require('underscore');
|
||||
const ko = require('knockout');
|
||||
const BackboneEvents = require('backbone').Events;
|
||||
|
||||
// Grist client libs
|
||||
const dispose = require('../lib/dispose');
|
||||
const dom = require('../lib/dom');
|
||||
const kd = require('../lib/koDom');
|
||||
const kf = require('../lib/koForm');
|
||||
const ModalDialog = require('./ModalDialog');
|
||||
const gutil = require('app/common/gutil');
|
||||
|
||||
const BASE_URL = 'https://syvvdfor2a.execute-api.us-east-1.amazonaws.com/test';
|
||||
|
||||
/**
|
||||
* EmbedForm - Handles logic and dom for the modal embedding instruction box.
|
||||
*/
|
||||
function EmbedForm(gristDoc) {
|
||||
this._docComm = gristDoc.docComm;
|
||||
this._login = gristDoc.app.login;
|
||||
this._basketId = gristDoc.docInfo.basketId;
|
||||
this._tableIds = gristDoc.docModel.allTableIds.peek().sort();
|
||||
|
||||
// Arrays of published and unpublished tables, initialized in this._refreshTables()
|
||||
this._published = ko.observable([]);
|
||||
this._unpublished = ko.observable([]);
|
||||
|
||||
// Notify strings which are displayed to the user when set
|
||||
this._errorNotify = ko.observable();
|
||||
this._updateNotify = ko.observable();
|
||||
|
||||
// The state of initialization, either 'connecting', 'failed', or 'done'.
|
||||
this._initState = ko.observable('connecting');
|
||||
|
||||
this._embedDialog = this.autoDispose(ModalDialog.create({
|
||||
title: 'Upload for External Embedding',
|
||||
body: this._buildEmbedDom(),
|
||||
width: '420px'
|
||||
}));
|
||||
this._embedDialog.show();
|
||||
|
||||
this.listenTo(this._embedDialog, 'close', () => this.dispose());
|
||||
|
||||
// Perform the initial fetch to see which tables are published.
|
||||
this._initFetch();
|
||||
}
|
||||
_.extend(EmbedForm.prototype, BackboneEvents);
|
||||
dispose.makeDisposable(EmbedForm);
|
||||
|
||||
/**
|
||||
* Performs the initial fetch to see which tables are published.
|
||||
* Times out after 4 seconds, giving the user the option to retry.
|
||||
*/
|
||||
EmbedForm.prototype._initFetch = function() {
|
||||
this._initState('connecting');
|
||||
return this._refreshTables()
|
||||
.timeout(4000)
|
||||
.then(() => {
|
||||
this._initState('done');
|
||||
})
|
||||
.catch(err => {
|
||||
console.error("EmbedForm._initFetch failed", err);
|
||||
this._initState('failed');
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Calls on basket to see which tables are published, then updates the published
|
||||
* and unpublished local observables.
|
||||
*/
|
||||
EmbedForm.prototype._refreshTables = function() {
|
||||
// Fetch the tables from the basket
|
||||
return this._login.getBasketTables(this._docComm)
|
||||
.then(basketTableIds => {
|
||||
let published = [];
|
||||
let unpublished = [];
|
||||
gutil.sortedScan(this._tableIds, basketTableIds.sort(), (local, cloud) => {
|
||||
let item = {
|
||||
tableId: local || cloud,
|
||||
local: Boolean(local),
|
||||
cloud: Boolean(cloud)
|
||||
};
|
||||
if (cloud) {
|
||||
published.push(item);
|
||||
} else {
|
||||
unpublished.push(item);
|
||||
}
|
||||
});
|
||||
this._published(published);
|
||||
this._unpublished(unpublished);
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Builds the part of the form showing the table names and their status, and
|
||||
* the buttons to change their status.
|
||||
*/
|
||||
EmbedForm.prototype._buildTablesDom = function() {
|
||||
return dom('div.embed-form-tables',
|
||||
kd.scope(this._published, published => {
|
||||
return published.length > 0 ? dom('div.embed-form-published',
|
||||
dom('div.embed-form-desc', `Published to Basket (basketId: ${this._basketId()})`),
|
||||
published.map(t => {
|
||||
return kf.row(
|
||||
16, dom('a.embed-form-table-id', { href: this._getUrl(t.tableId), target: "_blank" },
|
||||
t.tableId),
|
||||
8, t.local ? this._makeButton('Update', t.tableId, 'update') : 'Only in Basket',
|
||||
1, dom('div'),
|
||||
2, this._makeButton('x', t.tableId, 'delete')
|
||||
);
|
||||
})
|
||||
) : null;
|
||||
}),
|
||||
dom('div.embed-form-unpublished',
|
||||
kd.scope(this._unpublished, unpublished => {
|
||||
return unpublished.map(t => {
|
||||
return kf.row(
|
||||
16, dom('span.embed-form-table-id', t.tableId),
|
||||
8, this._makeButton('Publish', t.tableId, 'add'),
|
||||
3, dom('div')
|
||||
);
|
||||
});
|
||||
})
|
||||
)
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* Builds the body of the table publishing modal form.
|
||||
*/
|
||||
EmbedForm.prototype._buildEmbedDom = function() {
|
||||
// TODO: Include links to the npm page and to download basket-api.js.
|
||||
return dom('div.embed-form',
|
||||
kd.scope(this._initState, state => {
|
||||
switch (state) {
|
||||
case 'connecting':
|
||||
return dom('div.embed-form-connect', 'Connecting...');
|
||||
case 'failed':
|
||||
return dom('div',
|
||||
dom('div.embed-form-connect', 'Connection to Basket failed'),
|
||||
kf.buttonGroup(
|
||||
kf.button(() => {
|
||||
this._initFetch();
|
||||
}, 'Retry')
|
||||
)
|
||||
);
|
||||
case 'done':
|
||||
return dom('div',
|
||||
dom('div.embed-form-desc', 'Manage tables published to the cloud via Grist Basket.'),
|
||||
dom('div.embed-form-desc', 'Note that by default, published tables are public.'),
|
||||
this._buildTablesDom(),
|
||||
dom('div.embed-form-desc', 'Basket is used to provide easy access to cloud-synced data:'),
|
||||
dom('div.embed-form-link',
|
||||
dom('a', { href: 'https://github.com/gristlabs/basket-api', target: "_blank" },
|
||||
'Basket API on GitHub')
|
||||
)
|
||||
);
|
||||
}
|
||||
}),
|
||||
kd.maybe(this._updateNotify, update => {
|
||||
return dom('div.login-success-notify',
|
||||
dom('div.login-success-text', update)
|
||||
);
|
||||
}),
|
||||
kd.maybe(this._errorNotify, err => {
|
||||
return dom('div.login-error-notify',
|
||||
dom('div.login-error-text', err)
|
||||
);
|
||||
})
|
||||
);
|
||||
};
|
||||
|
||||
// Helper to perform embedAction ('add' | 'update' | 'delete') on tableId.
|
||||
EmbedForm.prototype._embedTable = function(tableId, embedAction) {
|
||||
this._errorNotify('');
|
||||
this._updateNotify('');
|
||||
return this._docComm.embedTable(tableId, embedAction)
|
||||
.then(() => {
|
||||
return this._refreshTables();
|
||||
})
|
||||
.then(() => {
|
||||
if (embedAction === 'update') {
|
||||
this._updateNotify(`Updated table ${tableId}`);
|
||||
}
|
||||
})
|
||||
.catch(err => {
|
||||
this._errorNotify(err.message);
|
||||
});
|
||||
};
|
||||
|
||||
// Helper to make a button with text, that when pressed performs embedAction
|
||||
// ('add' | 'update' | 'delete') on tableId.
|
||||
EmbedForm.prototype._makeButton = function(text, tableId, embedAction) {
|
||||
return kf.buttonGroup(
|
||||
kf.button(() => this._embedTable(tableId, embedAction), text)
|
||||
);
|
||||
};
|
||||
|
||||
// Returns the URL to see the hosted data for tableId.
|
||||
EmbedForm.prototype._getUrl = function(tableId) {
|
||||
return `${BASE_URL}/${this._basketId()}/tables/${tableId}`;
|
||||
};
|
||||
|
||||
module.exports = EmbedForm;
|
@ -1,129 +0,0 @@
|
||||
/* global window */
|
||||
|
||||
|
||||
// External dependencies
|
||||
const Promise = require('bluebird');
|
||||
const ko = require('knockout');
|
||||
|
||||
// Grist client libs
|
||||
const dispose = require('../lib/dispose');
|
||||
const ProfileForm = require('./ProfileForm');
|
||||
|
||||
/**
|
||||
* Login - Handles dom and settings for the login box.
|
||||
* @param {app} app - The app instance.
|
||||
*/
|
||||
function Login(app) {
|
||||
this.app = app;
|
||||
this.comm = this.app.comm;
|
||||
|
||||
// When logged in, an object containing user profile properties.
|
||||
this._profile = ko.observable();
|
||||
this.isLoggedIn = ko.observable(false);
|
||||
this.emailObs = this.autoDispose(ko.computed(() => ((this._profile() && this._profile().email) || '')));
|
||||
this.nameObs = this.autoDispose(ko.computed(() => ((this._profile() && this._profile().name) || '')));
|
||||
this.buttonText = this.autoDispose(ko.computed(() =>
|
||||
this.isLoggedIn() ? this.emailObs() : 'Log in'));
|
||||
|
||||
// Instantialized with createLoginForm() and createProfileForm()
|
||||
this.profileForm = null;
|
||||
}
|
||||
dispose.makeDisposable(Login);
|
||||
|
||||
/**
|
||||
* Returns the current profile object.
|
||||
*/
|
||||
Login.prototype.getProfile = function() {
|
||||
return this._profile();
|
||||
};
|
||||
|
||||
/**
|
||||
* Opens the Cognito login form in a new browser window to allow the user to log in.
|
||||
* The login tokens are sent back to the server to which this client belongs.
|
||||
*/
|
||||
Login.prototype.login = function() {
|
||||
if (window.isRunningUnderElectron) {
|
||||
// Under electron, we open the login URL (it opens in a user's default browser).
|
||||
// With null for redirectUrl, it will close automatically when login completes.
|
||||
return this.comm.getLoginUrl(null)
|
||||
.then((loginUrl) => window.open(loginUrl));
|
||||
} else {
|
||||
// In hosted / dev version, we redirect to the login URL, and it will redirect back to the
|
||||
// starting URL when login completes.
|
||||
return this.comm.getLoginUrl(window.location.href)
|
||||
.then((loginUrl) => { window.location.href = loginUrl; });
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Tells the server to log out, and also opens a new window to the logout URL to get Cognito to
|
||||
* clear its cookies. The new window will hit our page which will close the window automatically.
|
||||
*/
|
||||
Login.prototype.logout = function() {
|
||||
// We both log out the server, and for hosted version, visit a logout URL to clear AWS cookies.
|
||||
if (window.isRunningUnderElectron) {
|
||||
// Under electron, only clear the server state. Don't open the user's default browser
|
||||
// to clear cookies there because it serves dubious purpose and is annoying to the user.
|
||||
return this.comm.logout(null);
|
||||
} else {
|
||||
// In hosted / dev version, we redirect to the logout URL, which will clear cookies and
|
||||
// redirect back to the starting URL when logout completes.
|
||||
return this.comm.logout(window.location.href)
|
||||
.then((logoutUrl) => { window.location.href = logoutUrl; });
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Retrieves the updated user profile from DynamoDB and creates the profile form.
|
||||
* Also sends the fetched user profile to the server to keep it up to date.
|
||||
*/
|
||||
Login.prototype.createProfileForm = function() {
|
||||
// ProfileForm disposes itself, no need to handle disposal.
|
||||
this.profileForm = ProfileForm.create(this);
|
||||
};
|
||||
|
||||
// Called when the user logs out in this or another tab.
|
||||
Login.prototype.onLogout = function() {
|
||||
this._profile(null);
|
||||
this.isLoggedIn(false);
|
||||
};
|
||||
|
||||
/**
|
||||
* Update the internally-stored profile given a profile object from the server.
|
||||
*/
|
||||
Login.prototype.updateProfileFromServer = function(profileObj) {
|
||||
this._profile(profileObj);
|
||||
this.isLoggedIn(Boolean(this._profile.peek()));
|
||||
};
|
||||
|
||||
Login.prototype.setProfileItem = function(key, val) {
|
||||
return this.comm.updateProfile({[key]: val});
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns an array of tableIds in the basket of the current document. If the current
|
||||
* document has no basket, an empty array is returned.
|
||||
*/
|
||||
Login.prototype.getBasketTables = function(docComm) {
|
||||
return docComm.getBasketTables();
|
||||
};
|
||||
|
||||
// Execute func if the user is logged in. Otherwise, prompt the user to log in
|
||||
// and then execute the function. Attempts refresh if the token is expired.
|
||||
Login.prototype.tryWithLogin = function(func) {
|
||||
return Promise.try(() => {
|
||||
if (!this.isLoggedIn()) {
|
||||
return this.login();
|
||||
}
|
||||
})
|
||||
.then(() => func())
|
||||
.catch(err => {
|
||||
if (err.code === 'LoginClosedError') {
|
||||
console.log("Login#tryWithLogin", err);
|
||||
} else {
|
||||
throw err;
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
module.exports = Login;
|
@ -1,160 +0,0 @@
|
||||
/* global document */
|
||||
|
||||
// External dependencies
|
||||
const _ = require('underscore');
|
||||
const ko = require('knockout');
|
||||
const BackboneEvents = require('backbone').Events;
|
||||
|
||||
// Grist client libs
|
||||
const dispose = require('../lib/dispose');
|
||||
const dom = require('../lib/dom');
|
||||
const kf = require('../lib/koForm');
|
||||
const kd = require('../lib/koDom');
|
||||
const ModalDialog = require('./ModalDialog');
|
||||
|
||||
/**
|
||||
* ProfileForm - Handles dom and settings for the profile box.
|
||||
* @param {Login} login - The login instance.
|
||||
*/
|
||||
function ProfileForm(login) {
|
||||
this._login = login;
|
||||
this._comm = this._login.comm;
|
||||
this._gristLogin = this._login.gristLogin;
|
||||
this._errorNotify = ko.observable();
|
||||
this._successNotify = ko.observable();
|
||||
|
||||
// Form data which may be filled in when modifying profile information.
|
||||
this._newName = ko.observable('');
|
||||
|
||||
// Counter used to provide each edit profile sub-form with an id which indicates
|
||||
// when it is visible.
|
||||
this._formId = 1;
|
||||
this._editingId = ko.observable(null);
|
||||
|
||||
this._profileDialog = this.autoDispose(ModalDialog.create({
|
||||
title: 'User profile',
|
||||
body: this._buildProfileDom(),
|
||||
width: '420px'
|
||||
}));
|
||||
this._profileDialog.show();
|
||||
|
||||
// TODO: Some indication is necessary that verification is occurring between
|
||||
// submitting the form and waiting for the box to close.
|
||||
this.listenTo(this._comm, 'clientLogout', () => this.dispose());
|
||||
this.listenTo(this._profileDialog, 'close', () => this.dispose());
|
||||
}
|
||||
_.extend(ProfileForm.prototype, BackboneEvents);
|
||||
dispose.makeDisposable(ProfileForm);
|
||||
|
||||
/**
|
||||
* Builds the body of the profile modal form.
|
||||
*/
|
||||
ProfileForm.prototype._buildProfileDom = function() {
|
||||
return dom('div.profile-form',
|
||||
// Email
|
||||
// TODO: Allow changing email
|
||||
this._buildProfileRow('Email', {
|
||||
buildDisplayFunc: () => dom('div',
|
||||
kd.text(this._login.emailObs),
|
||||
dom.testId('ProfileForm_viewEmail')
|
||||
)
|
||||
}),
|
||||
// Name
|
||||
this._buildProfileRow('Name', {
|
||||
buildDisplayFunc: () => dom('div',
|
||||
kd.text(this._login.nameObs),
|
||||
dom.testId('ProfileForm_viewName')
|
||||
),
|
||||
buildEditFunc: () => dom('div',
|
||||
kf.label('New name'),
|
||||
kf.text(this._newName, {}, dom.testId('ProfileForm_newName'))
|
||||
),
|
||||
submitFunc: () => this._submitNameChange()
|
||||
}),
|
||||
// TODO: Allow editing profile image.
|
||||
kd.maybe(this._successNotify, success => {
|
||||
return dom('div.login-success-notify',
|
||||
dom('div.login-success-text', success)
|
||||
);
|
||||
}),
|
||||
kd.maybe(this._errorNotify, err => {
|
||||
return dom('div.login-error-notify',
|
||||
dom('div.login-error-text', err)
|
||||
);
|
||||
})
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* Builds a row of the profile form.
|
||||
* @param {String} label - Indicates the profile item displayed by the row.
|
||||
* @param {Function} options.buildDisplayFunc - A function which returns dom representing
|
||||
* the value of the profile item to be displayed. If omitted, no value is visible.
|
||||
* @param {Function} options.buildEditFunc - A function which returns dom to change the
|
||||
* value of the profile item. If omitted, the profile item may not be edited.
|
||||
* @param {Function} options.submitFunc - A function to call to save changes to the
|
||||
* profile item. MUST be included if buildEditFunc is included.
|
||||
*/
|
||||
ProfileForm.prototype._buildProfileRow = function(label, options) {
|
||||
options = options || {};
|
||||
let formId = this._formId++;
|
||||
|
||||
return dom('div.profile-row',
|
||||
kf.row(
|
||||
2, kf.label(label),
|
||||
5, options.buildDisplayFunc ? options.buildDisplayFunc() : '',
|
||||
1, dom('div.btn.edit-profile.glyphicon.glyphicon-pencil',
|
||||
{ style: `visibility: ${options.buildEditFunc ? 'visible' : 'hidden'}` },
|
||||
dom.testId(`ProfileForm_edit${label}`),
|
||||
dom.on('click', () => {
|
||||
this._editingId(this._editingId() === formId ? null : formId);
|
||||
})
|
||||
)
|
||||
),
|
||||
kd.maybe(() => this._editingId() === formId, () => {
|
||||
return dom('div',
|
||||
dom.on('keydown', e => {
|
||||
if (e.keyCode === 13) {
|
||||
// Current element is likely a knockout text field with changes that haven't yet been
|
||||
// saved to the observable. Blur the current element to ensure its value is saved.
|
||||
document.activeElement.blur();
|
||||
options.submitFunc();
|
||||
}
|
||||
}),
|
||||
dom('div.edit-profile-form',
|
||||
options.buildEditFunc(),
|
||||
dom('div.login-btns.flexhbox',
|
||||
kf.buttonGroup(
|
||||
kf.button(() => this._editingId(null), 'Cancel',
|
||||
dom.testId('ProfileForm_cancel'))
|
||||
),
|
||||
kf.buttonGroup(
|
||||
kf.accentButton(() => options.submitFunc(), 'Submit',
|
||||
dom.testId('ProfileForm_submit'))
|
||||
)
|
||||
)
|
||||
)
|
||||
);
|
||||
})
|
||||
);
|
||||
};
|
||||
|
||||
// Submits the profile name change form.
|
||||
ProfileForm.prototype._submitNameChange = function() {
|
||||
if (!this._newName()) {
|
||||
throw new Error('Name may not be blank.');
|
||||
}
|
||||
return this._login.setProfileItem('name', this._newName())
|
||||
// TODO: attemptRefreshToken() should be handled in a general way for all methods
|
||||
// which require using tokens after sign in.
|
||||
.then(() => {
|
||||
this._editingId(null);
|
||||
this._successNotify('Successfully changed name.');
|
||||
})
|
||||
.catch(err => {
|
||||
console.error('Error changing name', err);
|
||||
this._errorNotify(err.message);
|
||||
});
|
||||
};
|
||||
|
||||
module.exports = ProfileForm;
|
@ -1,118 +0,0 @@
|
||||
var koArray = require('../lib/koArray');
|
||||
var dispose = require('../lib/dispose');
|
||||
var _ = require('underscore');
|
||||
var BackboneEvents = require('backbone').Events;
|
||||
var {pageHasDocList} = require('app/common/urlUtils');
|
||||
|
||||
/**
|
||||
* Constructor for DocListModel
|
||||
* @param {Object} comm: A map of server methods availble on this document.
|
||||
*/
|
||||
function DocListModel(app) {
|
||||
this.app = app;
|
||||
this.comm = this.app.comm;
|
||||
this.docs = koArray();
|
||||
this.docInvites = koArray();
|
||||
|
||||
if (pageHasDocList()) {
|
||||
this.listenTo(this.comm, 'docListAction', this.docListActionHandler);
|
||||
this.listenTo(this.comm, 'receiveInvites', () => this.refreshDocList());
|
||||
|
||||
// Initialize the DocListModel
|
||||
this.refreshDocList();
|
||||
} else {
|
||||
console.log("Page has no DocList support");
|
||||
}
|
||||
}
|
||||
dispose.makeDisposable(DocListModel);
|
||||
_.extend(DocListModel.prototype, BackboneEvents);
|
||||
|
||||
/**
|
||||
* Rebuilds DocListModel with a direct call to the server.
|
||||
*/
|
||||
DocListModel.prototype.refreshDocList = function() {
|
||||
return this.comm.getDocList()
|
||||
.then(docListObj => {
|
||||
this.docs.assign(docListObj.docs);
|
||||
this.docInvites.assign(docListObj.docInvites);
|
||||
})
|
||||
.catch((err) => {
|
||||
console.error('Failed to load DocListModel: %s', err);
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Updates the DocListModel docs and docInvites arrays in response to docListAction events
|
||||
* @param {Object} message: A docListAction message received from the server.
|
||||
*/
|
||||
DocListModel.prototype.docListActionHandler = function(message) {
|
||||
console.log('docListActionHandler message', message);
|
||||
if (message && message.data) {
|
||||
_.each(message.data.addDocs, this.addDoc, this);
|
||||
_.each(message.data.removeDocs, this.removeDoc, this);
|
||||
_.each(message.data.changeDocs, this.changeDoc, this);
|
||||
_.each(message.data.addInvites, this.addInvite, this);
|
||||
_.each(message.data.removeInvites, this.removeInvite, this);
|
||||
// DocListModel can ignore rename events since renames also broadcast add/remove events.
|
||||
} else {
|
||||
console.error('Unrecognized message', message);
|
||||
}
|
||||
};
|
||||
|
||||
DocListModel.prototype._removeAtIndex = function(collection, index) {
|
||||
collection.splice(index, 1);
|
||||
};
|
||||
|
||||
DocListModel.prototype._removeItem = function(collection, name) {
|
||||
var index = this._findItem(collection, name);
|
||||
if (index !== -1) {
|
||||
this._removeAtIndex(collection, index);
|
||||
}
|
||||
};
|
||||
|
||||
// Binary search is disabled in _.indexOf because the docs may not be sorted by name.
|
||||
DocListModel.prototype._findItem = function(collection, name) {
|
||||
var matchIndex = _.indexOf(collection.all().map(item => item.name), name, false);
|
||||
if (matchIndex === -1) {
|
||||
console.error('DocListModel does not contain name:', name);
|
||||
}
|
||||
return matchIndex;
|
||||
};
|
||||
|
||||
DocListModel.prototype.removeDoc = function(name) {
|
||||
this._removeItem(this.docs, name);
|
||||
};
|
||||
|
||||
// TODO: removeInvite is unused
|
||||
DocListModel.prototype.removeInvite = function(name) {
|
||||
this._removeItem(this.docInvites, name);
|
||||
};
|
||||
|
||||
DocListModel.prototype._addItem = function(collection, fileObj) {
|
||||
var insertIndex = _.sortedIndex(collection.all(), fileObj, 'name');
|
||||
this._addItemAtIndex(collection, insertIndex, fileObj);
|
||||
};
|
||||
|
||||
DocListModel.prototype._addItemAtIndex = function(collection, index, fileObj) {
|
||||
collection.splice(index, 0, fileObj);
|
||||
};
|
||||
|
||||
DocListModel.prototype.addDoc = function(fileObj) {
|
||||
this._addItem(this.docs, fileObj);
|
||||
};
|
||||
|
||||
// TODO: addInvite is unused
|
||||
DocListModel.prototype.addInvite = function(fileObj) {
|
||||
this._addItem(this.docInvites, fileObj);
|
||||
};
|
||||
|
||||
// Called when the metadata for a doc changes.
|
||||
DocListModel.prototype.changeDoc = function(fileObj) {
|
||||
let idx = this._findItem(this.docs, fileObj.name);
|
||||
if (idx !== -1) {
|
||||
this._removeAtIndex(this.docs, idx);
|
||||
this._addItem(this.docs, fileObj);
|
||||
}
|
||||
};
|
||||
|
||||
module.exports = DocListModel;
|
Loading…
Reference in new issue