mirror of
https://github.com/gristlabs/grist-core.git
synced 2024-10-27 20:44:07 +00:00
(core) updates from grist-core
This commit is contained in:
commit
d89e008a75
@ -260,6 +260,7 @@ GRIST_SERVERS | the types of server to setup. Comma separated values which may c
|
||||
GRIST_SESSION_COOKIE | if set, overrides the name of Grist's cookie
|
||||
GRIST_SESSION_DOMAIN | if set, associates the cookie with the given domain - otherwise defaults to GRIST_DOMAIN
|
||||
GRIST_SESSION_SECRET | a key used to encode sessions
|
||||
GRIST_SKIP_BUNDLED_WIDGETS | if set, Grist will ignore any bundled widgets included via NPM packages.
|
||||
GRIST_ANON_PLAYGROUND | When set to 'false' deny anonymous users access to the home page
|
||||
GRIST_FORCE_LOGIN | Much like GRIST_ANON_PLAYGROUND but don't support anonymous access at all (features like sharing docs publicly requires authentication)
|
||||
GRIST_SINGLE_ORG | set to an org "domain" to pin client to that org
|
||||
@ -396,6 +397,7 @@ TYPEORM_PASSWORD | password to use
|
||||
TYPEORM_PORT | port number for db if not the default for that db type
|
||||
TYPEORM_TYPE | set to 'sqlite' or 'postgres'
|
||||
TYPEORM_USERNAME | username to connect as
|
||||
TYPEORM_EXTRA | any other properties to pass to TypeORM in JSON format
|
||||
|
||||
#### Testing:
|
||||
|
||||
|
@ -41,7 +41,7 @@ export type PageType =
|
||||
| "billing"
|
||||
| "welcome"
|
||||
| "account"
|
||||
| "support-grist"
|
||||
| "support"
|
||||
| "activation";
|
||||
|
||||
const G = getBrowserGlobals('document', 'window');
|
||||
@ -316,7 +316,7 @@ export class AppModelImpl extends Disposable implements AppModel {
|
||||
} else if (state.account) {
|
||||
return 'account';
|
||||
} else if (state.supportGrist) {
|
||||
return 'support-grist';
|
||||
return 'support';
|
||||
} else if (state.activation) {
|
||||
return 'activation';
|
||||
} else {
|
||||
|
@ -226,7 +226,7 @@ export class AccountWidget extends Disposable {
|
||||
return menuItemLink(
|
||||
t('Support Grist'),
|
||||
cssHeartIcon('💛'),
|
||||
urlState().setLinkUrl({supportGrist: 'support-grist'}),
|
||||
urlState().setLinkUrl({supportGrist: 'support'}),
|
||||
testId('usermenu-support-grist'),
|
||||
);
|
||||
}
|
||||
|
@ -77,7 +77,7 @@ function createMainPage(appModel: AppModel, appObj: App) {
|
||||
return dom.create(WelcomePage, appModel);
|
||||
} else if (pageType === 'account') {
|
||||
return domAsync(loadAccountPage().then(ap => dom.create(ap.AccountPage, appModel)));
|
||||
} else if (pageType === 'support-grist') {
|
||||
} else if (pageType === 'support') {
|
||||
return domAsync(loadSupportGristPage().then(sgp => dom.create(sgp.SupportGristPage, appModel)));
|
||||
} else if (pageType === 'activation') {
|
||||
return domAsync(loadActivationPage().then(ap => dom.create(ap.ActivationPage, appModel)));
|
||||
|
@ -22,7 +22,7 @@ type ButtonState =
|
||||
| 'expanded';
|
||||
|
||||
type CardPage =
|
||||
| 'support-grist'
|
||||
| 'support'
|
||||
| 'opted-in';
|
||||
|
||||
/**
|
||||
@ -45,7 +45,7 @@ export class SupportGristNudge extends Disposable {
|
||||
this._buttonState = localStorageObs(
|
||||
`u=${this._appModel.currentValidUser?.id ?? 0};supportGristNudge`, 'expanded'
|
||||
) as Observable<ButtonState>;
|
||||
this._currentPage = Observable.create(null, 'support-grist');
|
||||
this._currentPage = Observable.create(null, 'support');
|
||||
this._isClosed = Observable.create(this, false);
|
||||
}
|
||||
|
||||
@ -122,7 +122,7 @@ export class SupportGristNudge extends Disposable {
|
||||
private _buildCard() {
|
||||
return cssCard(
|
||||
dom.domComputed(this._currentPage, page => {
|
||||
if (page === 'support-grist') {
|
||||
if (page === 'support') {
|
||||
return this._buildSupportGristCardContent();
|
||||
} else {
|
||||
return this._buildOptedInCardContent();
|
||||
@ -205,7 +205,7 @@ function helpCenterLink() {
|
||||
function supportGristLink() {
|
||||
return cssLink(
|
||||
t('Support Grist page'),
|
||||
{href: urlState().makeUrl({supportGrist: 'support-grist'}), target: '_blank'},
|
||||
{href: urlState().makeUrl({supportGrist: 'support'}), target: '_blank'},
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -194,7 +194,7 @@ export class SupportGristPage extends Disposable {
|
||||
const suffix = getPageTitleSuffix(getGristConfig());
|
||||
switch (page) {
|
||||
case undefined:
|
||||
case 'support-grist': {
|
||||
case 'support': {
|
||||
return document.title = `Support Grist${suffix}`;
|
||||
}
|
||||
}
|
||||
|
@ -43,7 +43,7 @@ export type ActivationPage = typeof ActivationPage.type;
|
||||
export const LoginPage = StringUnion('signup', 'login', 'verified', 'forgot-password');
|
||||
export type LoginPage = typeof LoginPage.type;
|
||||
|
||||
export const SupportGristPage = StringUnion('support-grist');
|
||||
export const SupportGristPage = StringUnion('support');
|
||||
export type SupportGristPage = typeof SupportGristPage.type;
|
||||
|
||||
// Overall UI style. "full" is normal, "singlePage" is a single page focused, panels hidden experience.
|
||||
@ -408,8 +408,8 @@ export function decodeUrl(gristConfig: Partial<GristLoadConfig>, location: Locat
|
||||
state.activation = ActivationPage.parse(map.get('activation')) || 'activation';
|
||||
}
|
||||
if (map.has('welcome')) { state.welcome = WelcomePage.parse(map.get('welcome')); }
|
||||
if (map.has('support-grist')) {
|
||||
state.supportGrist = SupportGristPage.parse(map.get('support-grist')) || 'support-grist';
|
||||
if (map.has('support')) {
|
||||
state.supportGrist = SupportGristPage.parse(map.get('support')) || 'support';
|
||||
}
|
||||
if (sp.has('planType')) { state.params!.planType = sp.get('planType')!; }
|
||||
if (sp.has('billingPlan')) { state.params!.billingPlan = sp.get('billingPlan')!; }
|
||||
|
@ -212,6 +212,6 @@ export function attachAppEndpoint(options: AttachOptions): void {
|
||||
// The * is a wildcard in express 4, rather than a regex symbol.
|
||||
// See https://expressjs.com/en/guide/routing.html
|
||||
app.get('/doc/:urlId([^/]+):remainder(*)', ...docMiddleware, docHandler);
|
||||
app.get('/:urlId([^/]{12,})/:slug([^/]+):remainder(*)',
|
||||
app.get('/:urlId([^/]{12,})(/:slug([^/]+):remainder(*))?',
|
||||
...docMiddleware, docHandler);
|
||||
}
|
||||
|
@ -1998,8 +1998,13 @@ export class FlexServer implements GristServer {
|
||||
// Only used as {userRoot}/plugins as a place for plugins in addition to {appRoot}/plugins
|
||||
const userRoot = path.resolve(process.env.GRIST_USER_ROOT || getAppPathTo(this.appRoot, '.grist'));
|
||||
this.info.push(['userRoot', userRoot]);
|
||||
|
||||
const pluginManager = new PluginManager(this.appRoot, userRoot);
|
||||
// Some custom widgets may be included as an npm package called @gristlabs/grist-widget.
|
||||
const bundledRoot = isAffirmative(process.env.GRIST_SKIP_BUNDLED_WIDGETS) ? undefined : path.join(
|
||||
getAppPathTo(this.appRoot, 'node_modules'),
|
||||
'@gristlabs', 'grist-widget', 'dist'
|
||||
);
|
||||
this.info.push(['bundledRoot', bundledRoot]);
|
||||
const pluginManager = new PluginManager(this.appRoot, userRoot, bundledRoot);
|
||||
// `initialize()` is asynchronous and reads plugins manifests; if PluginManager is used before it
|
||||
// finishes, it will act as if there are no plugins.
|
||||
// ^ I think this comment was here to justify calling initialize without waiting for
|
||||
|
@ -46,7 +46,8 @@ function servePluginContent(req: express.Request, res: express.Response,
|
||||
req.get('X-From-Plugin-WebView') === "true" ||
|
||||
mimeTypes.lookup(path.extname(pluginPath)) === "application/javascript") {
|
||||
const dirs = pluginManager.dirs();
|
||||
const contentRoot = pluginKind === "installed" ? dirs.installed : dirs.builtIn;
|
||||
const contentRoot = pluginKind === "installed" ? dirs.installed :
|
||||
(pluginKind === "builtIn" ? dirs.builtIn : dirs.bundled);
|
||||
// Note that pluginPath may not be safe, but `sendFile` with the "root" option restricts
|
||||
// relative paths to be within the root folder (see the 3rd party library unit-test:
|
||||
// https://github.com/pillarjs/send/blob/3daa901cf731b86187e4449fa2c52f971e0b3dbc/test/send.js#L1363)
|
||||
|
@ -14,9 +14,14 @@ export interface PluginDirectories {
|
||||
*/
|
||||
readonly builtIn?: string;
|
||||
/**
|
||||
* Directory where user installed plugins are localted.
|
||||
* Directory where user installed plugins are located.
|
||||
*/
|
||||
readonly installed?: string;
|
||||
/**
|
||||
* Yet another option, for plugins that are included
|
||||
* during a build but not part of the codebase itself.
|
||||
*/
|
||||
readonly bundled?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -44,10 +49,12 @@ export class PluginManager {
|
||||
* @param {string} userRoot: path to user's grist directory; `null` is allowed, to only uses built in plugins.
|
||||
*
|
||||
*/
|
||||
public constructor(public appRoot?: string, userRoot?: string) {
|
||||
public constructor(public appRoot?: string, userRoot?: string,
|
||||
public bundledRoot?: string) {
|
||||
this._dirs = {
|
||||
installed: userRoot ? path.join(userRoot, 'plugins') : undefined,
|
||||
builtIn: appRoot ? getAppPathTo(appRoot, 'plugins') : undefined
|
||||
builtIn: appRoot ? getAppPathTo(appRoot, 'plugins') : undefined,
|
||||
bundled: bundledRoot ? getAppPathTo(bundledRoot, 'plugins') : undefined,
|
||||
};
|
||||
}
|
||||
|
||||
@ -91,6 +98,11 @@ export class PluginManager {
|
||||
this._entries.push(...await scanDirectory(this._dirs.builtIn, "builtIn"));
|
||||
}
|
||||
|
||||
// Load bundled plugins
|
||||
if (this._dirs.bundled) {
|
||||
this._entries.push(...await scanDirectory(this._dirs.bundled, "bundled"));
|
||||
}
|
||||
|
||||
if (!process.env.GRIST_EXPERIMENTAL_PLUGINS ||
|
||||
process.env.GRIST_EXPERIMENTAL_PLUGINS === '0') {
|
||||
// Remove experimental plugins
|
||||
@ -130,7 +142,7 @@ export class PluginManager {
|
||||
}
|
||||
|
||||
|
||||
async function scanDirectory(dir: string, kind: "installed"|"builtIn"): Promise<DirectoryScanEntry[]> {
|
||||
async function scanDirectory(dir: string, kind: "installed"|"builtIn"|"bundled"): Promise<DirectoryScanEntry[]> {
|
||||
const plugins: DirectoryScanEntry[] = [];
|
||||
let listDir;
|
||||
|
||||
|
@ -187,7 +187,7 @@ export class Telemetry implements ITelemetry {
|
||||
|
||||
public addPages(app: express.Application, middleware: express.RequestHandler[]) {
|
||||
if (this._deploymentType === 'core') {
|
||||
app.get('/support-grist', ...middleware, expressWrap(async (req, resp) => {
|
||||
app.get('/support', ...middleware, expressWrap(async (req, resp) => {
|
||||
return this._gristServer.sendAppPage(req, resp,
|
||||
{path: 'app.html', status: 200, config: {}});
|
||||
}));
|
||||
|
@ -142,6 +142,7 @@ export function getTypeORMSettings(): DataSourceOptions {
|
||||
"subscribers": [
|
||||
`${codeRoot}/app/gen-server/subscriber/*.js`
|
||||
],
|
||||
...JSON.parse(process.env.TYPEORM_EXTRA || "{}"),
|
||||
...cache,
|
||||
};
|
||||
}
|
||||
|
@ -13,8 +13,8 @@
|
||||
"install:python3": "buildtools/prepare_python3.sh",
|
||||
"build:prod": "buildtools/build.sh",
|
||||
"start:prod": "sandbox/run.sh",
|
||||
"test": "GRIST_SESSION_COOKIE=grist_test_cookie GRIST_TEST_LOGIN=1 TEST_SUPPORT_API_KEY=api_key_for_support TEST_CLEAN_DATABASE=true mocha ${DEBUG:+-b --no-exit} --slow 8000 $([ -z $DEBUG ] && echo --forbid-only) -g \"${GREP_TESTS}\" '_build/test/common/*.js' '_build/test/client/*.js' '_build/test/nbrowser/*.js' '_build/test/server/**/*.js' '_build/test/gen-server/**/*.js'",
|
||||
"test:nbrowser": "TEST_SUITE=nbrowser TEST_SUITE_FOR_TIMINGS=nbrowser TIMINGS_FILE=test/timings/nbrowser.txt GRIST_SESSION_COOKIE=grist_test_cookie GRIST_TEST_LOGIN=1 TEST_SUPPORT_API_KEY=api_key_for_support TEST_CLEAN_DATABASE=true mocha ${DEBUG:+-b --no-exit} $([ -z $DEBUG ] && echo --forbid-only) -g \"${GREP_TESTS}\" --slow 8000 -R test/xunit-file '_build/test/nbrowser/**/*.js'",
|
||||
"test": "GRIST_SESSION_COOKIE=grist_test_cookie GRIST_TEST_LOGIN=1 TEST_SUPPORT_API_KEY=api_key_for_support TEST_CLEAN_DATABASE=true LANGUAGE=en_US mocha ${DEBUG:+-b --no-exit} --slow 8000 $([ -z $DEBUG ] && echo --forbid-only) -g \"${GREP_TESTS}\" '_build/test/common/*.js' '_build/test/client/*.js' '_build/test/nbrowser/*.js' '_build/test/server/**/*.js' '_build/test/gen-server/**/*.js'",
|
||||
"test:nbrowser": "TEST_SUITE=nbrowser TEST_SUITE_FOR_TIMINGS=nbrowser TIMINGS_FILE=test/timings/nbrowser.txt GRIST_SESSION_COOKIE=grist_test_cookie GRIST_TEST_LOGIN=1 TEST_SUPPORT_API_KEY=api_key_for_support TEST_CLEAN_DATABASE=true LANGUAGE=en_US mocha ${DEBUG:+-b --no-exit} $([ -z $DEBUG ] && echo --forbid-only) -g \"${GREP_TESTS}\" --slow 8000 -R test/xunit-file '_build/test/nbrowser/**/*.js'",
|
||||
"test:client": "GRIST_SESSION_COOKIE=grist_test_cookie mocha ${DEBUG:+'-b'} '_build/test/client/**/*.js'",
|
||||
"test:common": "GRIST_SESSION_COOKIE=grist_test_cookie mocha ${DEBUG:+'-b'} '_build/test/common/**/*.js'",
|
||||
"test:server": "TEST_SUITE=server TEST_SUITE_FOR_TIMINGS=server TIMINGS_FILE=test/timings/server.txt GRIST_SESSION_COOKIE=grist_test_cookie mocha ${DEBUG:+'-b'} -R test/xunit-file '_build/test/server/**/*.js' '_build/test/gen-server/**/*.js'",
|
||||
@ -114,6 +114,7 @@
|
||||
"@googleapis/oauth2": "0.2.0",
|
||||
"@gristlabs/connect-sqlite3": "0.9.11-grist.5",
|
||||
"@gristlabs/express-session": "1.17.0",
|
||||
"@gristlabs/grist-widget": "^0.0.4",
|
||||
"@gristlabs/moment-guess": "1.2.4-grist.1",
|
||||
"@gristlabs/pidusage": "2.0.17",
|
||||
"@gristlabs/sqlite3": "5.1.4-grist.8",
|
||||
|
@ -31,7 +31,7 @@ function check_gvisor {
|
||||
return
|
||||
fi
|
||||
# Check if a trivial command works under gvisor with the proposed flags.
|
||||
if runsc --network none "$@" do true 2> /dev/null; then
|
||||
if runsc --network none "$@" "do" true 2> /dev/null; then
|
||||
export GVISOR_FLAGS="$@"
|
||||
export GVISOR_AVAILABLE=1
|
||||
fi
|
||||
@ -40,9 +40,9 @@ function check_gvisor {
|
||||
check_gvisor --unprivileged --ignore-cgroups
|
||||
check_gvisor --unprivileged
|
||||
|
||||
# If we can't use --unprivileged, stick with --rootless and no checkpoint
|
||||
if [[ -z "$GVISOR_FLAGS" ]]; then
|
||||
# If we can't use --unprivileged, stick with --rootless. We will not make a checkpoint.
|
||||
check_gvisor --rootless
|
||||
else
|
||||
|
||||
if [[ "$GVISOR_FLAGS" =~ "-unprivileged" ]]; then
|
||||
export GRIST_CHECKPOINT=/tmp/engine_$(echo $PWD | sed "s/[^a-zA-Z0-9]/_/g")
|
||||
fi
|
||||
|
@ -3,8 +3,17 @@
|
||||
set -e
|
||||
|
||||
if [[ "$GRIST_SANDBOX_FLAVOR" = "gvisor" ]]; then
|
||||
./sandbox/gvisor/update_engine_checkpoint.sh
|
||||
source ./sandbox/gvisor/get_checkpoint_path.sh
|
||||
|
||||
# Check GVISOR_FLAGS we ended up with. Don't ignore the output, it may be helpful in troubleshooting.
|
||||
if runsc --network none $GVISOR_FLAGS "do" true; then
|
||||
echo "gvisor check ok (flags: ${GVISOR_FLAGS})"
|
||||
else
|
||||
echo "gvisor check failed (flags: ${GVISOR_FLAGS}); consider different GVISOR_FLAGS or GRIST_SANDBOX_FLAVOR"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
./sandbox/gvisor/update_engine_checkpoint.sh
|
||||
fi
|
||||
|
||||
NODE_PATH=_build:_build/stubs:_build/ext node _build/stubs/app/server/server.js
|
||||
|
@ -425,7 +425,9 @@
|
||||
"Duplicate in {{- label}}": "Duplicate in {{- label}}",
|
||||
"No reference columns.": "No reference columns.",
|
||||
"Search columns": "Search columns",
|
||||
"UUID": "UUID"
|
||||
"UUID": "UUID",
|
||||
"Add column with type": "Add column with type",
|
||||
"Add formula column": "Add formula column"
|
||||
},
|
||||
"GristDoc": {
|
||||
"Added new linked section to view {{viewName}}": "Added new linked section to view {{viewName}}",
|
||||
@ -1063,7 +1065,8 @@
|
||||
"A UUID is a randomly-generated string that is useful for unique identifiers and link keys.": "A UUID is a randomly-generated string that is useful for unique identifiers and link keys.",
|
||||
"Lookups return data from related tables.": "Lookups return data from related tables.",
|
||||
"Use reference columns to relate data in different tables.": "Use reference columns to relate data in different tables.",
|
||||
"You can choose from widgets available to you in the dropdown, or embed your own by providing its full URL.": "You can choose from widgets available to you in the dropdown, or embed your own by providing its full URL."
|
||||
"You can choose from widgets available to you in the dropdown, or embed your own by providing its full URL.": "You can choose from widgets available to you in the dropdown, or embed your own by providing its full URL.",
|
||||
"Formulas support many Excel functions, full Python syntax, and include a helpful AI Assistant.": "Formulas support many Excel functions, full Python syntax, and include a helpful AI Assistant."
|
||||
},
|
||||
"DescriptionConfig": {
|
||||
"DESCRIPTION": "DESCRIPTION"
|
||||
|
@ -205,7 +205,13 @@
|
||||
"Table ID copied to clipboard": "Identifiant de table copié",
|
||||
"Duplicate Table": "Dupliquer la table",
|
||||
"You do not have edit access to this document": "Vous n’avez pas accès en écriture à ce document",
|
||||
"Delete {{formattedTableName}} data, and remove it from all pages?": "Supprimer les données de {{formattedTableName}} et les supprimer de toutes les pages ?"
|
||||
"Delete {{formattedTableName}} data, and remove it from all pages?": "Supprimer les données de {{formattedTableName}} et les supprimer de toutes les pages ?",
|
||||
"Edit Record Card": "Modifier la vue carte",
|
||||
"Rename Table": "Renommer la table",
|
||||
"{{action}} Record Card": "{{action}} la vue carte",
|
||||
"Record Card": "Vue carte",
|
||||
"Remove Table": "Supprimer la table",
|
||||
"Record Card Disabled": "Vue carte désactivée"
|
||||
},
|
||||
"DocHistory": {
|
||||
"Activity": "Activité",
|
||||
@ -416,7 +422,9 @@
|
||||
"Timestamp": "Horodatage",
|
||||
"no reference column": "pas de colonne de référence",
|
||||
"Adding UUID column": "Ajout d'une colonne UUID",
|
||||
"Adding duplicates column": "Ajouter des colonnes dupliquées"
|
||||
"Adding duplicates column": "Ajouter des colonnes dupliquées",
|
||||
"Add formula column": "Ajouter une colonne formule",
|
||||
"Add column with type": "Ajouter une colonne de type"
|
||||
},
|
||||
"GristDoc": {
|
||||
"Import from file": "Importer depuis un fichier",
|
||||
@ -613,7 +621,8 @@
|
||||
"Duplicate rows_one": "Dupliquer la ligne",
|
||||
"Duplicate rows_other": "Dupliquer les lignes",
|
||||
"Delete": "Supprimer",
|
||||
"Copy anchor link": "Copier l'ancre"
|
||||
"Copy anchor link": "Copier l'ancre",
|
||||
"View as card": "Voir en carte"
|
||||
},
|
||||
"SelectionSummary": {
|
||||
"Copied to clipboard": "Copié dans le presse-papier"
|
||||
@ -907,7 +916,8 @@
|
||||
"Mixed format": "Format composite",
|
||||
"Revert field settings for {{colId}} to common": "Réinitialiser les paramètres par défaut pour {{colId}}",
|
||||
"Save field settings for {{colId}} as common": "Sauvegarder les paramètres pour {{colId}}",
|
||||
"Use separate field settings for {{colId}}": "Utiliser des paramètres spécifiques pour {{colId}}"
|
||||
"Use separate field settings for {{colId}}": "Utiliser des paramètres spécifiques pour {{colId}}",
|
||||
"Changing column type": "Changement du type de colonne"
|
||||
},
|
||||
"WelcomeTour": {
|
||||
"Customizing columns": "Personnaliser les colonnes",
|
||||
@ -1053,7 +1063,12 @@
|
||||
"end dates and event titles. Note each column's type.": "Pour configurer votre calendrier, sélectionnez les colonnes pour les dates de début/fin et le nom de l'évènement. Notez le type de chaque colonne."
|
||||
},
|
||||
"Calendar": "Calendrier",
|
||||
"Can't find the right columns? Click 'Change Widget' to select the table with events data.": "Impossible de trouver les bonnes colonnes ? Cliquez sur \"Changer de vue\" pour sélectionner la table contenant les évènements."
|
||||
"Can't find the right columns? Click 'Change Widget' to select the table with events data.": "Impossible de trouver les bonnes colonnes ? Cliquez sur \"Changer de vue\" pour sélectionner la table contenant les évènements.",
|
||||
"A UUID is a randomly-generated string that is useful for unique identifiers and link keys.": "Un UUID est un texte généré aléatoirement qui est utile pour les identifiants uniques et les clés de jointures.",
|
||||
"Formulas support many Excel functions, full Python syntax, and include a helpful AI Assistant.": "Les formules supportent beaucoup de fonctions Excel et la syntaxe Python complète. Un assistant IA est disponible sur certaines instances.",
|
||||
"Lookups return data from related tables.": "Récupère les données d'une table liée.",
|
||||
"You can choose from widgets available to you in the dropdown, or embed your own by providing its full URL.": "Vous pouvez choisir parmi les widgets disponibles dans le menu déroulant, ou utilisez le votre en fournissant son URL complète.",
|
||||
"Use reference columns to relate data in different tables.": "Utilisez les colonnes de type Référence pour lier différentes tables entre elles."
|
||||
},
|
||||
"ColumnTitle": {
|
||||
"Add description": "Ajouter une description",
|
||||
@ -1228,5 +1243,13 @@
|
||||
"Welcome back": "Bon retour parmi nous",
|
||||
"You can always switch sites using the account menu.": "Vous pouvez toujours changer d'espace en utilisant le menu du compte.",
|
||||
"You have access to the following Grist sites.": "Vous avez accès aux espaces Grist suivants."
|
||||
},
|
||||
"CardContextMenu": {
|
||||
"Insert card above": "Insérer une carte au dessus",
|
||||
"Duplicate card": "Dupliquer la carte",
|
||||
"Insert card below": "Insérer une carte en dessous",
|
||||
"Delete card": "Supprimer la carte",
|
||||
"Copy anchor link": "Copier le lien d'ancrage",
|
||||
"Insert card": "Insérer une carte"
|
||||
}
|
||||
}
|
||||
|
@ -564,7 +564,9 @@
|
||||
"Timestamp": "Метка времени",
|
||||
"Adding UUID column": "Добавление столбца UUID",
|
||||
"Adding duplicates column": "Добавление столбца дубликатов",
|
||||
"Lookups": "Lookups"
|
||||
"Lookups": "Lookups",
|
||||
"Add formula column": "Добавить вычисляемый столбец",
|
||||
"Add column with type": "Добавить столбец с типом"
|
||||
},
|
||||
"FilterBar": {
|
||||
"SearchColumns": "Столбцы поиска",
|
||||
@ -1063,7 +1065,8 @@
|
||||
"A UUID is a randomly-generated string that is useful for unique identifiers and link keys.": "UUID - это случайно сгенерированная строка, которая полезна для уникальных идентификаторов и ключевых ссылок.",
|
||||
"Lookups return data from related tables.": "Lookups возвращают данные из связанных таблиц.",
|
||||
"Use reference columns to relate data in different tables.": "Используйте ссылочные столбцы для сопоставления данных в разных таблицах.",
|
||||
"You can choose from widgets available to you in the dropdown, or embed your own by providing its full URL.": "Вы можете выбрать виджеты, доступные вам в раскрывающемся списке, или встроить свой собственный, указав его полный URL-адрес."
|
||||
"You can choose from widgets available to you in the dropdown, or embed your own by providing its full URL.": "Вы можете выбрать виджеты, доступные вам в раскрывающемся списке, или встроить свой собственный, указав его полный URL-адрес.",
|
||||
"Formulas support many Excel functions, full Python syntax, and include a helpful AI Assistant.": "Формулы поддерживают множество функций Excel, полный синтаксис Python и включает полезного помощника AI."
|
||||
},
|
||||
"DescriptionConfig": {
|
||||
"DESCRIPTION": "ОПИСАНИЕ"
|
||||
|
@ -99,8 +99,8 @@
|
||||
"You're about to delete an API key. This will cause all future requests using this API key to be rejected. Do you still want to delete?": "Želite izbrisati ključ API. To bo povzročilo zavrnitev vseh prihodnjih zahtevkov, ki bodo uporabljali ta ključ API. Ali še vedno želite izbrisati?",
|
||||
"Click to show": "Kliknite za prikaz",
|
||||
"Remove API Key": "Odstranite API ključ",
|
||||
"This API key can be used to access this account anonymously via the API.": "Ta API ključ lahko uporabite za anonimen dostop do tega računa prek vmesnika API.",
|
||||
"This API key can be used to access your account via the API. Don’t share your API key with anyone.": "Ta ključ API lahko uporabite za dostop do svojega računa prek vmesnika API. Svojega ključa API ne delite z nikomer.",
|
||||
"This API key can be used to access this account anonymously via the API.": "Ta ključ lahko uporabite za anonimen dostop do tega računa prek vmesnika API.",
|
||||
"This API key can be used to access your account via the API. Don’t share your API key with anyone.": "API ključ lahko uporabite za dostop do svojega računa prek API vmesnika. Svojega API ključa ne delite z nikomer.",
|
||||
"By generating an API key, you will be able to make API calls for your own account.": "Z ustvarjanjem API ključa boste lahko uporabljali klice API funkcij za svoj račun."
|
||||
},
|
||||
"App": {
|
||||
@ -225,7 +225,9 @@
|
||||
"Duplicate in {{- label}}": "Dvojnik v {{- label}}",
|
||||
"Search columns": "Preišči stolpce",
|
||||
"Adding UUID column": "Dodajanje UUID stolpca",
|
||||
"Adding duplicates column": "Dodajanje podvojenega stolpca"
|
||||
"Adding duplicates column": "Dodajanje podvojenega stolpca",
|
||||
"Add formula column": "Dodaj stolpec z formulo",
|
||||
"Add column with type": "Dodaj stolpec z tipom"
|
||||
},
|
||||
"HomeLeftPane": {
|
||||
"Trash": "Koš",
|
||||
@ -269,7 +271,7 @@
|
||||
"TOOLS": "ORODJA",
|
||||
"Settings": "Nastavitve",
|
||||
"Access Rules": "Pravila dostopa",
|
||||
"Code View": "Pogled kode",
|
||||
"Code View": "Koda",
|
||||
"Raw Data": "Neobdelani podatki",
|
||||
"Document History": "Zgodovina Dokumentov",
|
||||
"Validate Data": "Potrdi podatke",
|
||||
@ -406,7 +408,7 @@
|
||||
},
|
||||
"CodeEditorPanel": {
|
||||
"Access denied": "Dostop zavrnjen",
|
||||
"Code View is available only when you have full document access.": "Pogled kode je na voljo le, če imate popoln dostop do dokumenta."
|
||||
"Code View is available only when you have full document access.": "Koda je na voljo le, če imate popoln dostop do dokumenta."
|
||||
},
|
||||
"ColorSelect": {
|
||||
"Apply": "Uporabi",
|
||||
@ -685,7 +687,8 @@
|
||||
"A UUID is a randomly-generated string that is useful for unique identifiers and link keys.": "UUID je naključno ustvarjen niz, ki je uporaben za edinstvene identifikatorje in ključe povezav.",
|
||||
"Lookups return data from related tables.": "Iskanje vrne podatke iz povezanih tabel.",
|
||||
"Use reference columns to relate data in different tables.": "Uporabite referenčne stolpce za povezavo podatkov v različnih tabelah.",
|
||||
"You can choose from widgets available to you in the dropdown, or embed your own by providing its full URL.": "Izbirate lahko med pripomočki, ki so vam na voljo v spustnem meniju, ali vdelate svojega tako, da navedete njegov polni URL."
|
||||
"You can choose from widgets available to you in the dropdown, or embed your own by providing its full URL.": "Izbirate lahko med pripomočki, ki so vam na voljo v spustnem meniju, ali vdelate svojega tako, da navedete njegov polni URL.",
|
||||
"Formulas support many Excel functions, full Python syntax, and include a helpful AI Assistant.": "Formule podpirajo številne Excelove funkcije, polno Pythonovo sintakso in vključujejo koristnega AI pomočnika."
|
||||
},
|
||||
"UserManager": {
|
||||
"Anyone with link ": "Vsakdo s povezavo ",
|
||||
|
@ -449,7 +449,7 @@ class Seed {
|
||||
const d = new Document();
|
||||
d.name = doc;
|
||||
d.workspace = w;
|
||||
d.id = `sample_${docId}`;
|
||||
d.id = `sampledocid_${docId}`;
|
||||
docId++;
|
||||
await d.save();
|
||||
const dgrps = await this.createGroups(w);
|
||||
|
@ -28,19 +28,19 @@ describe('FillLinkedRecords.ntest', function() {
|
||||
|
||||
// Link the sections first since the sample document start with no links.
|
||||
// Connect Friends -> Films
|
||||
await gu.getSection('Films record').click();
|
||||
await gu.actions.viewSection('Films record').selectSection();
|
||||
await $('.test-right-select-by').click();
|
||||
await $('.test-select-row:contains(Friends record)').click();
|
||||
await gu.waitForServer();
|
||||
|
||||
// Connect Films -> Performances grid
|
||||
await gu.getSection('Performances record').click();
|
||||
await gu.actions.viewSection('Performances record').selectSection();
|
||||
await $('.test-right-select-by').click();
|
||||
await $('.test-select-row:contains(Films record)').click();
|
||||
await gu.waitForServer();
|
||||
|
||||
// Connect Films -> Performances detail
|
||||
await gu.getSection('Performances detail').click();
|
||||
await gu.actions.viewSection('Performances detail').selectSection();
|
||||
await $('.test-right-select-by').click();
|
||||
await $('.test-select-row:contains(Films record)').click();
|
||||
await gu.waitForServer();
|
||||
|
@ -25,7 +25,7 @@ describe('SavePosition.ntest', function() {
|
||||
await $('.test-config-data').click();
|
||||
|
||||
// Connect CITY -> CITY Card List
|
||||
await gu.getSection('CITY Card List').click();
|
||||
await gu.actions.viewSection('CITY Card List').selectSection();
|
||||
await $('.test-right-select-by').click();
|
||||
await $('.test-select-row:contains(CITY)').click();
|
||||
await gu.waitForServer();
|
||||
|
@ -291,12 +291,12 @@ describe('TypeChange.ntest', function() {
|
||||
|
||||
it('should trigger a transform when reference table is changed', async function() {
|
||||
// Set up conditions for the test
|
||||
await gu.getSection('Table1').click();
|
||||
await gu.actions.viewSection('Table1').selectSection();
|
||||
await gu.enterGridValues(2, 3, [['red', 'yellow']]);
|
||||
await gu.actions.addNewSection('New', 'Table');
|
||||
await gu.getSection('TABLE3').click();
|
||||
await gu.actions.viewSection('TABLE3').selectSection();
|
||||
await gu.enterGridValues(0, 1, [['yellow', 'red', 'green', 'blue']]);
|
||||
await gu.getSection('Table1').click();
|
||||
await gu.actions.viewSection('Table1').selectSection();
|
||||
await gu.clickCellRC(0, 3);
|
||||
await gu.openSidePane('field');
|
||||
await gu.setType('Reference');
|
||||
@ -365,7 +365,7 @@ describe('TypeChange.ntest', function() {
|
||||
// column were mistaken for row ids and converted to row values instead of AltText values.
|
||||
it('should properly convert from integer to reference', async function() {
|
||||
// Set up conditions for the test
|
||||
await gu.getSection('TABLE3').click();
|
||||
await gu.actions.viewSection('TABLE3').selectSection();
|
||||
await gu.enterGridValues(0, 2, [['3', '3', '4', '1']]);
|
||||
await gu.waitForServer();
|
||||
await gu.setType('Integer');
|
||||
|
@ -111,7 +111,7 @@ describe('Views.ntest', function() {
|
||||
// Reference: https://phab.getgrist.com/T327
|
||||
await gu.actions.addNewSection('New', 'Table');
|
||||
await gu.waitForServer();
|
||||
await gu.getSection('TABLE4').click();
|
||||
await gu.actions.viewSection('TABLE4').selectSection();
|
||||
// Delete the section
|
||||
await gu.actions.viewSection('TABLE4').selectMenuOption('viewLayout', 'Delete widget');
|
||||
await gu.waitForServer();
|
||||
|
@ -101,40 +101,46 @@ describe('Authorizer', function() {
|
||||
it.skip("viewer gets redirect by title", async function() {
|
||||
const resp = await axios.get(`${serverUrl}/o/pr/doc/Bananas`, chimpy);
|
||||
assert.equal(resp.status, 200);
|
||||
assert.equal(getGristConfig(resp.data).assignmentId, 'sample_6');
|
||||
assert.match(resp.request.res.responseUrl, /\/doc\/sample_6$/);
|
||||
assert.equal(getGristConfig(resp.data).assignmentId, 'sampledocid_6');
|
||||
assert.match(resp.request.res.responseUrl, /\/doc\/sampledocid_6$/);
|
||||
const resp2 = await axios.get(`${serverUrl}/o/nasa/doc/Pluto`, chimpy);
|
||||
assert.equal(resp2.status, 200);
|
||||
assert.equal(getGristConfig(resp2.data).assignmentId, 'sample_2');
|
||||
assert.match(resp2.request.res.responseUrl, /\/doc\/sample_2$/);
|
||||
assert.equal(getGristConfig(resp2.data).assignmentId, 'sampledocid_2');
|
||||
assert.match(resp2.request.res.responseUrl, /\/doc\/sampledocid_2$/);
|
||||
});
|
||||
|
||||
it('viewer loads document without slug in the URL', async function () {
|
||||
const docId = docs.Bananas.id;
|
||||
const resp = await axios.get(`${serverUrl}/o/pr/${docId}`, chimpy);
|
||||
assert.equal(resp.status, 200);
|
||||
});
|
||||
|
||||
it("stranger gets consistent refusal regardless of title", async function() {
|
||||
const resp = await axios.get(`${serverUrl}/o/pr/doc/Bananas`, charon);
|
||||
assert.equal(resp.status, 404);
|
||||
assert.notMatch(resp.data, /sample_6/);
|
||||
assert.notMatch(resp.data, /sampledocid_6/);
|
||||
const resp2 = await axios.get(`${serverUrl}/o/pr/doc/Bananas2`, charon);
|
||||
assert.equal(resp2.status, 404);
|
||||
assert.notMatch(resp.data, /sample_6/);
|
||||
assert.notMatch(resp.data, /sampledocid_6/);
|
||||
assert.deepEqual(withoutTimestamp(resp.data),
|
||||
withoutTimestamp(resp2.data));
|
||||
});
|
||||
|
||||
it("viewer can access title", async function() {
|
||||
const resp = await axios.get(`${serverUrl}/o/pr/doc/sample_6`, chimpy);
|
||||
const resp = await axios.get(`${serverUrl}/o/pr/doc/sampledocid_6`, chimpy);
|
||||
assert.equal(resp.status, 200);
|
||||
const config = getGristConfig(resp.data);
|
||||
assert.equal(config.getDoc![config.assignmentId!].name, 'Bananas');
|
||||
});
|
||||
|
||||
it("stranger cannot access title", async function() {
|
||||
const resp = await axios.get(`${serverUrl}/o/pr/doc/sample_6`, charon);
|
||||
const resp = await axios.get(`${serverUrl}/o/pr/doc/sampledocid_6`, charon);
|
||||
assert.equal(resp.status, 403);
|
||||
assert.notMatch(resp.data, /Bananas/);
|
||||
});
|
||||
|
||||
it("viewer cannot access document from wrong org", async function() {
|
||||
const resp = await axios.get(`${serverUrl}/o/nasa/doc/sample_6`, chimpy);
|
||||
const resp = await axios.get(`${serverUrl}/o/nasa/doc/sampledocid_6`, chimpy);
|
||||
assert.equal(resp.status, 404);
|
||||
});
|
||||
|
||||
@ -142,7 +148,7 @@ describe('Authorizer', function() {
|
||||
const cli = await openClient(server, 'chimpy@getgrist.com', 'pr');
|
||||
cli.ignoreTrivialActions();
|
||||
assert.equal((await cli.readMessage()).type, 'clientConnect');
|
||||
const openDoc = await cli.send("openDoc", "sample_6");
|
||||
const openDoc = await cli.send("openDoc", "sampledocid_6");
|
||||
assert.equal(openDoc.error, undefined);
|
||||
assert.match(JSON.stringify(openDoc.data), /Table1/);
|
||||
await cli.close();
|
||||
@ -152,7 +158,7 @@ describe('Authorizer', function() {
|
||||
const cli = await openClient(server, 'charon@getgrist.com', 'pr');
|
||||
cli.ignoreTrivialActions();
|
||||
assert.equal((await cli.readMessage()).type, 'clientConnect');
|
||||
const openDoc = await cli.send("openDoc", "sample_6");
|
||||
const openDoc = await cli.send("openDoc", "sampledocid_6");
|
||||
assert.match(openDoc.error!, /No view access/);
|
||||
assert.equal(openDoc.data, undefined);
|
||||
assert.match(openDoc.errorCode!, /AUTH_NO_VIEW/);
|
||||
@ -163,7 +169,7 @@ describe('Authorizer', function() {
|
||||
const cli = await openClient(server, 'charon@getgrist.com', 'nasa');
|
||||
cli.ignoreTrivialActions();
|
||||
assert.equal((await cli.readMessage()).type, 'clientConnect');
|
||||
const openDoc = await cli.openDocOnConnect("sample_2");
|
||||
const openDoc = await cli.openDocOnConnect("sampledocid_2");
|
||||
assert.equal(openDoc.error, undefined);
|
||||
const nonce = uuidv4();
|
||||
const applyUserActions = await cli.send("applyUserActions",
|
||||
@ -182,7 +188,7 @@ describe('Authorizer', function() {
|
||||
const cli = await openClient(server, 'chimpy@getgrist.com', 'nasa');
|
||||
cli.ignoreTrivialActions();
|
||||
assert.equal((await cli.readMessage()).type, 'clientConnect');
|
||||
const openDoc = await cli.openDocOnConnect("sample_2");
|
||||
const openDoc = await cli.openDocOnConnect("sampledocid_2");
|
||||
assert.equal(openDoc.error, undefined);
|
||||
const nonce = uuidv4();
|
||||
const applyUserActions = await cli.send("applyUserActions",
|
||||
@ -209,9 +215,9 @@ describe('Authorizer', function() {
|
||||
editor.ignoreTrivialActions();
|
||||
viewer.ignoreTrivialActions();
|
||||
stranger.ignoreTrivialActions();
|
||||
assert.equal((await editor.send("openDoc", "sample_2")).error, undefined);
|
||||
assert.equal((await viewer.send("openDoc", "sample_2")).error, undefined);
|
||||
assert.match((await stranger.send("openDoc", "sample_2")).error!, /No view access/);
|
||||
assert.equal((await editor.send("openDoc", "sampledocid_2")).error, undefined);
|
||||
assert.equal((await viewer.send("openDoc", "sampledocid_2")).error, undefined);
|
||||
assert.match((await stranger.send("openDoc", "sampledocid_2")).error!, /No view access/);
|
||||
|
||||
const action = [0, [["UpdateRecord", "Table1", 1, {A: "foo"}]]];
|
||||
assert.equal((await editor.send("applyUserActions", ...action)).error, undefined);
|
||||
@ -224,7 +230,7 @@ describe('Authorizer', function() {
|
||||
const cli = await openClient(server, 'thumbnail@getgrist.com', 'nasa');
|
||||
cli.ignoreTrivialActions();
|
||||
assert.equal((await cli.readMessage()).type, 'clientConnect');
|
||||
const openDoc = await cli.send("openDoc", "sample_2");
|
||||
const openDoc = await cli.send("openDoc", "sampledocid_2");
|
||||
assert.equal(openDoc.error, undefined);
|
||||
const nonce = uuidv4();
|
||||
const applyUserActions = await cli.send("applyUserActions",
|
||||
@ -243,12 +249,12 @@ describe('Authorizer', function() {
|
||||
const cli = await openClient(server, 'charon@getgrist.com', 'nasa');
|
||||
cli.ignoreTrivialActions();
|
||||
assert.equal((await cli.readMessage()).type, 'clientConnect');
|
||||
const openDoc = await cli.send("openDoc", "sample_2");
|
||||
const openDoc = await cli.send("openDoc", "sampledocid_2");
|
||||
assert.equal(openDoc.error, undefined);
|
||||
const result = await cli.send("fork", 0);
|
||||
assert.equal(result.data.docId, result.data.urlId);
|
||||
const parts = parseUrlId(result.data.docId);
|
||||
assert.equal(parts.trunkId, "sample_2");
|
||||
assert.equal(parts.trunkId, "sampledocid_2");
|
||||
assert.isAbove(parts.forkId!.length, 4);
|
||||
assert.equal(parts.forkUserId, await dbManager.testGetId('Charon') as number);
|
||||
});
|
||||
@ -258,31 +264,31 @@ describe('Authorizer', function() {
|
||||
const cli = await openClient(server, 'anon@getgrist.com', 'nasa');
|
||||
cli.ignoreTrivialActions();
|
||||
assert.equal((await cli.readMessage()).type, 'clientConnect');
|
||||
let openDoc = await cli.send("openDoc", "sample_2");
|
||||
let openDoc = await cli.send("openDoc", "sampledocid_2");
|
||||
assert.match(openDoc.error!, /No view access/);
|
||||
|
||||
// grant anon access to doc and retry
|
||||
await dbManager.updateDocPermissions({
|
||||
userId: await dbManager.testGetId('Chimpy') as number,
|
||||
urlId: 'sample_2',
|
||||
urlId: 'sampledocid_2',
|
||||
org: 'nasa'
|
||||
}, {users: {"anon@getgrist.com": "viewers"}});
|
||||
dbManager.flushDocAuthCache();
|
||||
openDoc = await cli.send("openDoc", "sample_2");
|
||||
openDoc = await cli.send("openDoc", "sampledocid_2");
|
||||
assert.equal(openDoc.error, undefined);
|
||||
|
||||
// make a fork
|
||||
const result = await cli.send("fork", 0);
|
||||
assert.equal(result.data.docId, result.data.urlId);
|
||||
const parts = parseUrlId(result.data.docId);
|
||||
assert.equal(parts.trunkId, "sample_2");
|
||||
assert.equal(parts.trunkId, "sampledocid_2");
|
||||
assert.isAbove(parts.forkId!.length, 4);
|
||||
assert.equal(parts.forkUserId, undefined);
|
||||
});
|
||||
|
||||
it("can set user via GRIST_PROXY_AUTH_HEADER", async function() {
|
||||
// User can access a doc by setting header.
|
||||
const docUrl = `${serverUrl}/o/pr/api/docs/sample_6`;
|
||||
const docUrl = `${serverUrl}/o/pr/api/docs/sampledocid_6`;
|
||||
const resp = await axios.get(docUrl, {
|
||||
headers: {'X-email': 'chimpy@getgrist.com'}
|
||||
});
|
||||
@ -297,7 +303,7 @@ describe('Authorizer', function() {
|
||||
let cli = await openClient(server, 'chimpy@getgrist.com', 'pr', 'X-email');
|
||||
cli.ignoreTrivialActions();
|
||||
assert.equal((await cli.readMessage()).type, 'clientConnect');
|
||||
let openDoc = await cli.send("openDoc", "sample_6");
|
||||
let openDoc = await cli.send("openDoc", "sampledocid_6");
|
||||
assert.equal(openDoc.error, undefined);
|
||||
assert.match(JSON.stringify(openDoc.data), /Table1/);
|
||||
await cli.close();
|
||||
@ -306,7 +312,7 @@ describe('Authorizer', function() {
|
||||
cli = await openClient(server, 'notchimpy@getgrist.com', 'pr', 'X-email');
|
||||
cli.ignoreTrivialActions();
|
||||
assert.equal((await cli.readMessage()).type, 'clientConnect');
|
||||
openDoc = await cli.send("openDoc", "sample_6");
|
||||
openDoc = await cli.send("openDoc", "sampledocid_6");
|
||||
assert.match(openDoc.error!, /No view access/);
|
||||
assert.equal(openDoc.data, undefined);
|
||||
assert.match(openDoc.errorCode!, /AUTH_NO_VIEW/);
|
||||
|
@ -49,10 +49,10 @@ const support = configForUser('support');
|
||||
|
||||
// some doc ids
|
||||
const docIds: { [name: string]: string } = {
|
||||
ApiDataRecordsTest: 'sample_7',
|
||||
Timesheets: 'sample_13',
|
||||
Bananas: 'sample_6',
|
||||
Antartic: 'sample_11'
|
||||
ApiDataRecordsTest: 'sampledocid_7',
|
||||
Timesheets: 'sampledocid_13',
|
||||
Bananas: 'sampledocid_6',
|
||||
Antartic: 'sampledocid_11'
|
||||
};
|
||||
|
||||
// A testDir of the form grist_test_{USER}_{SERVER_NAME}
|
||||
|
@ -21,10 +21,10 @@ const chimpy = configForUser('Chimpy');
|
||||
|
||||
// some doc ids
|
||||
const docIds: { [name: string]: string } = {
|
||||
ApiDataRecordsTest: 'sample_7',
|
||||
Timesheets: 'sample_13',
|
||||
Bananas: 'sample_6',
|
||||
Antartic: 'sample_11'
|
||||
ApiDataRecordsTest: 'sampledocid_7',
|
||||
Timesheets: 'sampledocid_13',
|
||||
Bananas: 'sampledocid_6',
|
||||
Antartic: 'sampledocid_11',
|
||||
};
|
||||
|
||||
let dataDir: string;
|
||||
|
@ -303,6 +303,11 @@
|
||||
safe-buffer "5.2.0"
|
||||
uid-safe "~2.1.5"
|
||||
|
||||
"@gristlabs/grist-widget@^0.0.4":
|
||||
version "0.0.4"
|
||||
resolved "https://registry.yarnpkg.com/@gristlabs/grist-widget/-/grist-widget-0.0.4.tgz#df50d988bcdf8fc26a876cf23b82e258bbdb0ccc"
|
||||
integrity sha512-Q0k+GuudU2+0JkuvVkB9UZzqeUKJH8PsaO9ZfxKuqL9/ssIXUd080msB+PJLXB0TU9BkpzPSl7+kLqXTBSnA5g==
|
||||
|
||||
"@gristlabs/moment-guess@1.2.4-grist.1":
|
||||
version "1.2.4-grist.1"
|
||||
resolved "https://registry.npmjs.org/@gristlabs/moment-guess/-/moment-guess-1.2.4-grist.1.tgz"
|
||||
|
Loading…
Reference in New Issue
Block a user