(core) updates from grist-core

This commit is contained in:
Paul Fitzpatrick 2023-11-20 11:28:50 -05:00
commit cea0404a22
10 changed files with 183 additions and 134 deletions

107
README.md
View File

@ -1,23 +1,12 @@
# Grist
Grist is a modern relational spreadsheet. It combines the flexibility of a spreadsheet with the
robustness of a database to organize your data and make you more productive.
Grist is a modern relational spreadsheet. It combines the flexibility of a spreadsheet with the robustness of a database to organize your data and make you more productive.
This repository, `grist-core`, is the heart of Grist, and has what you
need to run a powerful spreadsheet hosting server. If you wish to view and edit
spreadsheets stored locally, another option is to use the
[`grist-electron`](https://github.com/gristlabs/grist-electron) desktop app for Linux, Mac, and Windows. And to show Grist spreadsheets on a website
without any special back-end support, your options include
[`grist-static`](https://github.com/gristlabs/grist-static),
a fully in-browser build of Grist.
The `grist-core` repository is the basis for all these options, and
for the hosted spreadsheet services offered by
[`Grist Labs`](https://getgrist.com), an NYC-based company 🇺🇸 that is the main developer of Grist, and by
[`ANCT Données et Territoires`](https://donnees.incubateur.anct.gouv.fr/toolbox/grist),
a French government agency 🇫🇷 whose developers have made many
contributions to the code-base.
The `grist-core`, `grist-electron`, and `grist-static` repositories
are all open-source (Apache License, Version 2.0).
This repository, `grist-core`, is the heart of Grist, and has what you need to run a powerful spreadsheet hosting server. If you wish to view and edit spreadsheets stored locally, another option is to use the [`grist-electron`](https://github.com/gristlabs/grist-electron) desktop app for Linux, Mac, and Windows. And to show Grist spreadsheets on a website without any special back-end support, you can use [`grist-static`](https://github.com/gristlabs/grist-static), a fully in-browser build of Grist.
The `grist-core` repository is the basis for all these options, and for the hosted spreadsheet services offered by [Grist Labs](https://getgrist.com), an NYC-based company 🇺🇸 that is the main developer of Grist, and by [ANCT Données et Territoires](https://donnees.incubateur.anct.gouv.fr/toolbox/grist), a French government agency 🇫🇷 whose developers have made many contributions to the codebase.
The `grist-core`, `grist-electron`, and `grist-static` repositories are all open source (Apache License, Version 2.0).
https://user-images.githubusercontent.com/118367/151245587-892e50a6-41f5-4b74-9786-fe3566f6b1fb.mp4
@ -25,81 +14,69 @@ https://user-images.githubusercontent.com/118367/151245587-892e50a6-41f5-4b74-97
Grist is a hybrid database/spreadsheet, meaning that:
- Columns work like they do in databases. They are named, and hold one kind of data.
- Columns work like they do in databases: they are named, and they hold one kind of data.
- Columns can be filled by formula, spreadsheet-style, with automatic updates when referenced cells change.
This difference can confuse people coming directly from Excel or Google Sheets. Give it a chance!
If you are coming from Airtable, you'll find the model familiar though (and there's a
[Grist vs Airtable](https://www.getgrist.com/blog/grist-v-airtable/) article that might interest you).
This difference can confuse people coming directly from Excel or Google Sheets. Give it a chance! If you are coming from Airtable, you'll find the model familiar though (and there's a [Grist vs Airtable](https://www.getgrist.com/blog/grist-v-airtable/) article that might interest you).
Here are some specific feature highlights of Grist:
* Python formulas.
- Full [Python syntax is supported](https://support.getgrist.com/formulas/#python), and the standard library.
- Full [Python syntax is supported](https://support.getgrist.com/formulas/#python), including the standard library.
- Many [Excel functions](https://support.getgrist.com/functions/) also available.
- An [AI Assistant](https://www.getgrist.com/ai-formula-assistant/) specifically tuned for formula generation (using OpenAI gpt-3.5-turbo or [Llama](https://ai.meta.com/llama/) via <a href="https://github.com/abetlen/llama-cpp-python">llama-cpp-python</a>).
* A portable, self-contained format.
- Based on SQLite, the most widely deployed database engine.
- Any tool that can read SQLite can read numeric and text data from a Grist file.
- Great format for [backups](https://support.getgrist.com/exports/#backing-up-an-entire-document) that you can be confident you can restore in full.
- Great format for moving between different hosts.
- Can be displayed on a static website with [grist-static](https://github.com/gristlabs/grist-static), no special server needed.
- There's a self-contained desktop app available for viewing and editing: [grist-electron](https://github.com/gristlabs/grist-electron).
- Enables [backups](https://support.getgrist.com/exports/#backing-up-an-entire-document) that you can confidently restore in full.
- Great for moving between different hosts.
* Can be displayed on a static website with [`grist-static`](https://github.com/gristlabs/grist-static) no special server needed.
* A self-contained desktop app for viewing and editing locally: [`grist-electron`](https://github.com/gristlabs/grist-electron).
* Convenient editing and formatting features.
- Choices and [choice lists](https://support.getgrist.com/col-types/#choice-list-columns), for adding colorful tags to records without fuss.
- Choices and [choice lists](https://support.getgrist.com/col-types/#choice-list-columns), for adding colorful tags to records.
- [References](https://support.getgrist.com/col-refs/#creating-a-new-reference-list-column) and reference lists, for cross-referencing records in other tables.
- [Attachments](https://support.getgrist.com/col-types/#attachment-columns), to include media or document files in records.
- Dates and times, toggles, and special numerics such as currency all have specialized editors and formatting options.
- [Conditional Formatting](https://support.getgrist.com/conditional-formatting/), letting you control the style of cells with formulas, to draw attention to important information.
* Great for dashboards, visualizations, and data entry.
- [Conditional Formatting](https://support.getgrist.com/conditional-formatting/), letting you control the style of cells with formulas to draw attention to important information.
* Drag-and-drop dashboards.
- [Charts](https://support.getgrist.com/widget-chart/) for visualization.
- [Summary tables](https://support.getgrist.com/summary-tables/) for summing and counting across groups.
- [Widget linking](https://support.getgrist.com/linking-widgets/) streamlines filtering and editing data.
Grist has a unique approach to visualization, where you can lay out and link distinct widgets to show together,
without cramming mixed material into a table.
- The [Filter bar](https://support.getgrist.com/search-sort-filter/#filter-buttons) is great for quick slicing and dicing.
- [Filter bar](https://support.getgrist.com/search-sort-filter/#filter-buttons) for quick slicing and dicing.
* [Incremental imports](https://support.getgrist.com/imports/#updating-existing-records).
- So you can import a CSV of the last three months activity from your bank...
- ... and import new activity a month later without fuss or duplicates.
- Import a CSV of the last three months activity from your bank...
- ...and import new activity a month later without fuss or duplication.
* Integrations.
- A [REST API](https://support.getgrist.com/api/), [Zapier actions/triggers](https://support.getgrist.com/integrators/#integrations-via-zapier), and support from similar [integrators](https://support.getgrist.com/integrators/).
- Import/export to Google drive, Excel format, CSV.
- Can link data with custom widgets hosted externally.
- You can set up outgoing webhooks.
- Link data with [custom widgets](https://support.getgrist.com/widget-custom/#_top), hosted externally.
- Configurable outgoing webhooks.
* [Many templates](https://templates.getgrist.com/) to get you started, from investment research to organizing treasure hunts.
* Access control options.
- (You'll need SSO logins set up to make use of these options; [grist-omnibus](https://github.com/gristlabs/grist-omnibus) has a prepackaged solution if configuring this feels daunting)
- Share [individual documents](https://support.getgrist.com/sharing/), or workspaces, or [team sites](https://support.getgrist.com/team-sharing/).
- (You'll need SSO logins set up to make use of these options; [`grist-omnibus`](https://github.com/gristlabs/grist-omnibus) has a prepackaged solution if configuring this feels daunting)
- Share [individual documents](https://support.getgrist.com/sharing/), workspaces, or [team sites](https://support.getgrist.com/team-sharing/).
- Control access to [individual rows, columns, and tables](https://support.getgrist.com/access-rules/).
- Control access based on cell values and user attributes.
* Can be self-maintained.
* Self-maintainable.
- Useful for intranet operation and specific compliance requirements.
* Sandboxing options for untrusted documents.
- On Linux or with docker, you can enable
[gVisor](https://github.com/google/gvisor) sandboxing at the individual
document level.
- On OSX, you can use native sandboxing.
- On Linux or with Docker, you can enable [gVisor](https://github.com/google/gvisor) sandboxing at the individual document level.
- On macOS, you can use native sandboxing.
- On any OS, including Windows, you can use a wasm-based sandbox.
* Translated to many languages.
* Support for an AI Formula Assistant (using OpenAI gpt-3.5-turbo or comparable models).
* `F1` key brings up some quick help. This used to go without saying. In general Grist has good keyboard support.
* We post progress on [𝕏 or Twitter or whatever](https://twitter.com/getgrist).
If you are curious about where Grist is going heading,
see [our roadmap](https://github.com/gristlabs/grist-core/projects/1), drop a
question in [our forum](https://community.getgrist.com),
or browse [our extensive documentation](https://support.getgrist.com).
* `F1` key brings up some quick help. This used to go without saying, but in general Grist has good keyboard support.
* We post progress on [𝕏 or Twitter or whatever](https://twitter.com/getgrist) and publish [monthly newsletters](https://support.getgrist.com/newsletters/).
If you are curious about where Grist is heading, see [our roadmap](https://github.com/gristlabs/grist-core/projects/1), drop a question in [our forum](https://community.getgrist.com), or browse [our extensive documentation](https://support.getgrist.com).
## Using Grist
If you just want a quick demo of Grist:
* You can try Grist out at the hosted service run
by Grist Labs at [docs.getgrist.com](https://docs.getgrist.com)
(no registration needed).
* Or you can see an experimental fully in-browser build of Grist
at [gristlabs.github.io/grist-static](https://gristlabs.github.io/grist-static/).
* You can try Grist out at the hosted service run by Grist Labs at [docs.getgrist.com](https://docs.getgrist.com) (no registration needed).
* Or you can see a fully in-browser build of Grist at [gristlabs.github.io/grist-static](https://gristlabs.github.io/grist-static/).
* Or you can download Grist as a desktop app from [github.com/gristlabs/grist-electron](https://github.com/gristlabs/grist-electron).
To get `grist-core` running on your computer with [Docker](https://www.docker.com/get-started), do:
@ -150,7 +127,7 @@ Grist formulas in documents will be run using Python executed directly on your
machine. You can configure sandboxing using a `GRIST_SANDBOX_FLAVOR`
environment variable.
* On OSX, `export GRIST_SANDBOX_FLAVOR=macSandboxExec`
* On macOS, `export GRIST_SANDBOX_FLAVOR=macSandboxExec`
uses the native `sandbox-exec` command for sandboxing.
* On Linux with [gVisor's runsc](https://github.com/google/gvisor)
installed, `export GRIST_SANDBOX_FLAVOR=gvisor` is an option.
@ -195,18 +172,15 @@ did the hard work of making a good chunk of the application localizable. Merci b
This repository, [grist-core](https://github.com/gristlabs/grist-core), is maintained by Grist
Labs. Our flagship product available at [getgrist.com](https://www.getgrist.com) is built from the code you see
here, combined with business-specific software designed to scale it to many users, handle billing,
here, combined with business-specific software designed to scale to many users, handle billing,
etc.
Grist Labs is an open-core company. We offer Grist hosting as a
service, with free and paid plans. We also develop and sell
features related to Grist using a proprietary license, targeted at the
needs of enterprises with large self-managed installations. We see
data portability and autonomy as a key value Grist can bring to our
users, and `grist-core` as an essential means to deliver that. We are
committed to maintaining and improving the `grist-core` codebase, and
to be thoughtful about how proprietary offerings impact data portability
and autonomy.
needs of enterprises with large self-managed installations.
We see data portability and autonomy as a key value, and `grist-core` is an essential part of that. We are committed to maintaining and improving the `grist-core` codebase, and to be thoughtful about how proprietary offerings impact data portability and autonomy.
By opening its source code and offering an [OSI](https://opensource.org/)-approved free license,
Grist benefits its users:
@ -223,10 +197,9 @@ Grist benefits its users:
- **Price flexibility.** If you are low on funds but have time to invest, self-hosting is a great
option to have. And DIY users may have the technical savvy and motivation to delve in and make improvements,
which can benefit all users of Grist.
- **Extensibility.** For developers, having the source open makes it easier to build extensions (such as the
experimental [Custom Widget](https://support.getgrist.com/widget-custom/)). You can more easily
include Grist in your pipeline. And if a feature is missing, you can just take the source code and
build on top of it.
- **Extensibility.** For developers, having the source open makes it easier to build extensions (such as [Custom Widgets](https://support.getgrist.com/widget-custom/)). You can more easily include Grist in your pipeline. And if a feature is missing, you can just take the source code and build on top of it.
For more on Grist Labs' history and principles, see our [About Us](https://www.getgrist.com/about/) page.
## Sponsors

View File

@ -72,6 +72,8 @@ export interface SessionObj {
oidc?: {
// codeVerifier is used during OIDC authentication, to protect against attacks like CSRF.
codeVerifier?: string;
state?: string;
targetUrl?: string;
}
}

View File

@ -36,7 +36,7 @@ export async function collectTableSchemaInFrictionlessFormat(
req: express.Request,
options: DownloadOptions
): Promise<FrictionlessFormat> {
const {tableId} = options;
const {tableId, header} = options;
if (!activeDoc.docData) {
throw new Error('No docData in active document');
}
@ -50,24 +50,15 @@ export async function collectTableSchemaInFrictionlessFormat(
throw new ApiError(`Table ${tableId} not found.`, 404);
}
const data = await exportTable(activeDoc, tableRef, req);
const tableSchema = columnsToTableSchema(tableId, data, settings.locale);
return tableSchema;
}
function columnsToTableSchema(
tableId: string,
{tableName, columns}: {tableName: string, columns: ExportColumn[]},
locale: string,
): FrictionlessFormat {
const {tableName, columns} = await exportTable(activeDoc, tableRef, req);
return {
name: tableId.toLowerCase().replace(/_/g, '-'),
title: tableName,
schema: {
fields: columns.map(col => ({
name: col.label,
name: col[header || "label"],
...(col.description ? {description: col.description} : {}),
...buildTypeField(col, locale),
...buildTypeField(col, settings.locale),
})),
}
};

View File

@ -78,6 +78,7 @@ export class OIDCConfig {
redirect_uris: [ this._redirectUrl ],
response_types: [ 'code' ],
});
log.info(`OIDCConfig: initialized with issuer ${issuerUrl}`);
}
public addEndpoints(app: express.Application, sessions: Sessions): void {
@ -85,15 +86,18 @@ export class OIDCConfig {
}
public async handleCallback(sessions: Sessions, req: express.Request, res: express.Response): Promise<void> {
const mreq = req as RequestWithLogin;
try {
const params = this._client.callbackParams(req);
const { state } = params;
const { state, targetUrl } = mreq.session?.oidc ?? {};
if (!state) {
throw new Error('Login or logout failed to complete');
}
const codeVerifier = await this._retrieveCodeVerifierFromSession(req);
// The callback function will compare the state present in the params and the one we retrieved from the session.
// If they don't match, it will throw an error.
const tokenSet = await this._client.callback(
this._redirectUrl,
params,
@ -102,23 +106,29 @@ export class OIDCConfig {
const userInfo = await this._client.userinfo(tokenSet);
const profile = this._makeUserProfileFromUserInfo(userInfo);
log.info(`OIDCConfig: got OIDC response for ${profile.email} (${profile.name}) redirecting to ${targetUrl}`);
const scopedSession = sessions.getOrCreateSessionFromRequest(req);
await scopedSession.operateOnScopedSession(req, async (user) => Object.assign(user, {
profile,
}));
res.redirect('/');
delete mreq.session.oidc;
res.redirect(targetUrl ?? '/');
} catch (err) {
log.error(`OIDC callback failed: ${err.message}`);
res.status(500).send(`OIDC callback failed: ${err.message}`);
log.error(`OIDC callback failed: ${err.stack}`);
// Delete the session data even if the login failed.
// This way, we prevent several login attempts.
//
// Also session deletion must be done before sending the response.
delete mreq.session.oidc;
res.status(500).send(`OIDC callback failed.`);
}
}
public async getLoginRedirectUrl(req: express.Request): Promise<string> {
const codeVerifier = await this._generateAndStoreCodeVerifier(req);
public async getLoginRedirectUrl(req: express.Request, targetUrl: URL): Promise<string> {
const { codeVerifier, state } = await this._generateAndStoreConnectionInfo(req, targetUrl.href);
const codeChallenge = generators.codeChallenge(codeVerifier);
const state = generators.state();
const authUrl = this._client.authorizationUrl({
scope: process.env.GRIST_OIDC_IDP_SCOPES || 'openid email profile',
@ -135,15 +145,18 @@ export class OIDCConfig {
});
}
private async _generateAndStoreCodeVerifier(req: express.Request) {
private async _generateAndStoreConnectionInfo(req: express.Request, targetUrl: string) {
const mreq = req as RequestWithLogin;
if (!mreq.session) { throw new Error('no session available'); }
const codeVerifier = generators.codeVerifier();
const state = generators.state();
mreq.session.oidc = {
codeVerifier,
state,
targetUrl
};
return codeVerifier;
return { codeVerifier, state };
}
private async _retrieveCodeVerifierFromSession(req: express.Request) {
@ -151,7 +164,6 @@ export class OIDCConfig {
if (!mreq.session) { throw new Error('no session available'); }
const codeVerifier = mreq.session.oidc?.codeVerifier;
if (!codeVerifier) { throw new Error('Login is stale'); }
delete mreq.session.oidc?.codeVerifier;
return codeVerifier;
}

View File

@ -1,6 +1,6 @@
{
"name": "grist-core",
"version": "1.1.7",
"version": "1.1.8",
"license": "Apache-2.0",
"description": "Grist is the evolution of spreadsheets",
"homepage": "https://github.com/gristlabs/grist-core",

View File

@ -17,7 +17,9 @@
"Everyone": "Všichni",
"Allow everyone to copy the entire document, or view it in full in fiddle mode.\nUseful for examples and templates, but not for sensitive data.": "Umožni všem kopírovat celý dokument, nebo zobrazit plně v \"fiddle\" režimu.\nUžiitečné pro ukázky a šablony, ale ne pro citlivá data.",
"Add Default Rule": "Přidej Základní Pravidlo",
"Checking...": "Kontroluji…"
"Checking...": "Kontroluji…",
"Permissions": "Povolení",
"Permission to view Access Rules": "Povolení na zobrazení Přistupových Pravidel"
},
"ACUserManager": {
"Invite new member": "Pozvi nového uživatele",

View File

@ -1055,7 +1055,8 @@
"Can't find the right columns? Click 'Change Widget' to select the table with events data.": "Не можете найти нужные столбцы? Нажмите «Изменить виджет», чтобы выбрать таблицу с данными о событиях.",
"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.": "Используйте ссылочные столбцы для сопоставления данных в разных таблицах."
"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-адрес."
},
"DescriptionConfig": {
"DESCRIPTION": "ОПИСАНИЕ"

View File

@ -19,19 +19,19 @@
"Type a message...": "Vnesite sporočilo…",
"User Attributes": "Atributi uporabnika",
"View As": "Poglej kot",
"When adding table rules, automatically add a rule to grant OWNER full access.": "Pri dodajanju pravil za tabele samodejno dodajte pravilo, ki lastniku omogoča popoln dostop.",
"When adding table rules, automatically add a rule to grant OWNER full access.": "Pri dodajanju pravil za tabele samodejno dodaj pravilo, ki lastniku omogoča popoln dostop.",
"Permission to edit document structure": "Dovoljenje za urejanje strukture dokumenta",
"Everyone": "Vsi",
"Everyone Else": "Vsi ostali",
"Checking...": "Preverjanje…",
"Condition": "Stanje",
"Enter Condition": "Vnesite pogoj",
"Add Column Rule": "Dodajanje pravila za stolpce",
"Add Column Rule": "Dodaj pravila za stolpec",
"Add Default Rule": "Dodaj privzeto pravilo",
"Add Table Rules": "Dodajanje pravil tabele",
"Add User Attributes": "Dodajanje atributov uporabnika",
"Add Table Rules": "Dodaj pravila za tabelo",
"Add User Attributes": "Dodaj atribute za uporabnika",
"Allow everyone to copy the entire document, or view it in full in fiddle mode.\nUseful for examples and templates, but not for sensitive data.": "Vsakomur omogočite kopiranje celotnega dokumenta ali pa si ga oglejte v celoti v načinu fiddle.\nUporabno za primere in predloge, ne pa za občutljive podatke.",
"Allow everyone to view Access Rules.": "Vsakomur omogočite ogled pravil za dostop.",
"Allow everyone to view Access Rules.": "Omogoči vsakomur ogled pravil za dostop.",
"Attribute name": "Ime atributa",
"Attribute to Look Up": "Atribut za iskanje",
"Lookup Table": "Preglednica za iskanje",
@ -40,8 +40,8 @@
},
"ACUserManager": {
"We'll email an invite to {{email}}": "Vabilo bomo poslali po e-pošti {{email}}",
"Enter email address": "Vnesite e-poštni naslov",
"Invite new member": "Povabite novega člana"
"Enter email address": "Vnesi e-poštni naslov",
"Invite new member": "Povabi novega člana"
},
"AccountPage": {
"API": "API",
@ -110,7 +110,7 @@
"Translators: please translate this only when your language is ready to be offered to users": "Prevajalci: prosimo, prevedite to šele, ko bo vaš jezik pripravljen, da se ponudi uporabnikom"
},
"CellContextMenu": {
"Delete {{count}} columns_one": "Brisanje stolpca",
"Delete {{count}} columns_one": "Briši stolpec",
"Delete {{count}} columns_other": "Brisanje stolpcev {{count}}",
"Delete {{count}} rows_one": "Brisanje vrstice",
"Delete {{count}} rows_other": "Brisanje vrstic {{count}}",
@ -118,12 +118,12 @@
"Copy anchor link": "Kopiranje sidrne povezave",
"Duplicate rows_one": "Podvoji vrstico",
"Duplicate rows_other": "Podvoji vrstice",
"Insert column to the right": "Vstavi stolpec na desno",
"Insert column to the left": "Vstavi stolpec na levo",
"Insert column to the right": "Vstavi stolpec na desno stran",
"Insert column to the left": "Vstavi stolpec na levo stran",
"Insert row": "Vstavljanje vrstice",
"Insert row above": "Vstavite vrstico zgoraj",
"Insert row below": "Vstavi vrstico spodaj",
"Reset {{count}} columns_one": "Ponastavitev stolpca",
"Reset {{count}} columns_one": "Ponastavi stolpec",
"Reset {{count}} columns_other": "Ponastavit {{count}} stolpcev",
"Reset {{count}} entire columns_one": "Ponastavi celote stolpec",
"Reset {{count}} entire columns_other": "Ponastavi {{count}} celotnih stolpcev",
@ -177,7 +177,7 @@
},
"GridViewMenus": {
"Rename column": "Preimenuj stolpec",
"Delete {{count}} columns_one": "Brisanje stolpca",
"Delete {{count}} columns_one": "Briši stolpec",
"Delete {{count}} columns_other": "Brisanje stolpcev {{count}}",
"Unfreeze {{count}} columns_one": "Odmrzni ta stolpec",
"Sorted (#{{count}})_one": "Razvrščeno (#{{count}})",
@ -189,7 +189,7 @@
"Filter Data": "Filtriranje podatkov",
"Hide {{count}} columns_other": "Skrij {{count}} stolpcev",
"Add Column": "Dodaj stolpec",
"Reset {{count}} columns_one": "Ponastavitev stolpca",
"Reset {{count}} columns_one": "Ponastavi stolpec",
"Freeze {{count}} columns_one": "Zamrzni stolpec",
"More sort options ...": "Več možnosti razvrščanja…",
"Freeze {{count}} more columns_one": "Zamrznite še en stolpec",
@ -200,10 +200,10 @@
"Freeze {{count}} more columns_other": "Zamrznite še {{count}} stolpcev",
"Hide {{count}} columns_one": "Skrij stolpec",
"Sorted (#{{count}})_other": "Razvrščeno (#{{count}})",
"Insert column to the {{to}}": "Vstavi stolpec v {{to}}",
"Insert column to the {{to}}": "Vstavi stolpec na {{to}}",
"Reset {{count}} entire columns_other": "Ponastavi {{count}} stolpcev",
"Unfreeze {{count}} columns_other": "Odmrznite {{count}} stolpcev",
"Insert column to the right": "Vstavi stolpec na desno",
"Insert column to the right": "Vstavi stolpec na desno stran",
"Reset {{count}} entire columns_one": "Ponastavi celoten stolpec",
"Insert column to the left": "Vstavi stolpec na levo",
"Shortcuts": "Bližnjice",
@ -270,7 +270,7 @@
"Access Rules": "Pravila dostopa",
"Code View": "Pogled kode",
"Raw Data": "Neobdelani podatki",
"Document History": "Zgodovina dokumentov",
"Document History": "Zgodovina Dokumentov",
"Validate Data": "Potrdi podatke",
"How-to Tutorial": "Vadnica kako narediti",
"Tour of this Document": "Ogled tega dokumenta",
@ -278,7 +278,7 @@
},
"pages": {
"Rename": "Preimenuj",
"Duplicate Page": "Podvojena stran",
"Duplicate Page": "Podvoji stran",
"You do not have edit access to this document": "Nimate dovoljenja za urejanje tega dokumenta",
"Remove": "Odstrani"
},
@ -293,7 +293,7 @@
"Add New": "Dodaj"
},
"DataTables": {
"Delete {{formattedTableName}} data, and remove it from all pages?": "Izbrišite podatke {{formattedTableName}} in jih odstranite z vseh strani?",
"Delete {{formattedTableName}} data, and remove it from all pages?": "Izbriši podatke {{formattedTableName}} in jih odstrani z vseh strani?",
"Click to copy": "Kliknite za kopiranje",
"Duplicate Table": "Podvojena tabela",
"Table ID copied to clipboard": "ID tabele kopiran v odložišče",
@ -305,8 +305,8 @@
"Delete widget": "Izbriši gradnik",
"Advanced Sort & Filter": "Napredno razvrščanje in filtriranje",
"Data selection": "Izbira podatkov",
"Download as XLSX": "Prenesite kot XLSX",
"Download as CSV": "Prenesite kot CSV",
"Download as XLSX": "Prenesi kot XLSX",
"Download as CSV": "Prenesi kot CSV",
"Widget options": "Možnosti gradnika",
"Print widget": "Gradnik za tiskanje",
"Open configuration": "Odpri konfiguracijo",
@ -423,13 +423,13 @@
"Formula Columns_other": "Stolpci formule",
"Formula Columns_one": "Stolpec formule",
"Enter formula": "Vnesite formulo",
"Clear and make into formula": "Brišite in pretvorite v formulo",
"Clear and make into formula": "Briši in pretvori v formulo",
"Mixed Behavior": "Mešano vedenje",
"Convert to trigger formula": "Pretvori v sprožitveno formulo",
"Data Columns_one": "Stolpec podatkov",
"TRIGGER FORMULA": "SPROŽILNA FORMULA",
"Set trigger formula": "Nastavite sprožitveno formulo",
"Make into data column": "Spremenite v podatkovni stolpec",
"Set trigger formula": "Nastavi sprožitveno formulo",
"Make into data column": "Spremeni v podatkovni stolpec",
"COLUMN BEHAVIOR": "OBNAŠANJE STOLPCA"
},
"DuplicateTable": {
@ -440,7 +440,7 @@
},
"DocPageModel": {
"Sorry, access to this document has been denied. [{{error}}]": "Žal je bil dostop do tega dokumenta zavrnjen. [{{error}}]",
"Add Empty Table": "Dodajte prazno tabelo",
"Add Empty Table": "Dodaj prazno tabelo",
"You do not have edit access to this document": "Nimate dostopa za urejanje tega dokumenta",
"Add Widget to Page": "Dodaj widget na stran",
"Add Page": "Dodaj stran",
@ -552,7 +552,7 @@
"SOURCE DATA": "IZVORNI PODATKI",
"CHART TYPE": "VRSTA DIAGRAMA",
"Detach": "Odklopi",
"Change Widget": "Spremite widget",
"Change Widget": "Spremeni Pripomoček",
"Columns_one": "Stolpec",
"Series_other": "Serija",
"Fields_other": "Polja",
@ -661,8 +661,8 @@
"Apply conditional formatting to cells in this column when formula conditions are met.": "Uporabi pogojno oblikovanje za celice v tem stolpcu, ko so izpolnjeni pogoji formule.",
"To make an anchor link that takes the user to a specific cell, click on a row and press {{shortcut}}.": "Če želite narediti sidrno povezavo, ki uporabnika pripelje do določene celice, kliknite vrstico in pritisnite {{shortcut}}.",
"Unpin to hide the the button while keeping the filter.": "Odpnite, da skrijete gumb in obdržite filter.",
"Apply conditional formatting to rows based on formulas.": "Uporabite pogojno oblikovanje za vrstice na podlagi formul.",
"Click on “Open row styles” to apply conditional formatting to rows.": "Kliknite »Odpri sloge vrstic«, da za vrstice uporabite pogojno oblikovanje.",
"Apply conditional formatting to rows based on formulas.": "Uporabi pogojno oblikovanje za vrstice na podlagi formul.",
"Click on “Open row styles” to apply conditional formatting to rows.": "Klikni »Odpri sloge vrstic«, da za vrstice uporabiš pogojno oblikovanje.",
"Pinned filters are displayed as buttons above the widget.": "Pripeti filtri so prikazani kot gumbi nad pripomočkom.",
"Link your new widget to an existing widget on this page.": "Povežite svoj novi pripomoček z obstoječim pripomočkom na tej strani.",
"Use the \\u{1D6BA} icon to create summary (or pivot) tables, for totals or subtotals.": "Uporabite ikono \\u{1D6BA} za ustvarjanje povzetkov (ali vrtilnih) tabel za vsote ali delne vsote.",
@ -688,7 +688,7 @@
"User has view access to {{resource}} resulting from manually-set access to resources inside. If removed here, this user will lose access to resources inside.": "Uporabnik ima vpogled v {{resource}}, ki je posledica ročno nastavljenega dostopa do virov v njem. Če ga tukaj odstranite, bo ta uporabnik izgubil dostop do notranjih virov.",
"User may not modify their own access.": "Uporabnik ne more spreminjati lastnega dostopa.",
"member": "član",
"Add {{member}} to your team": "Dodajte {{member}} v svojo ekipo",
"Add {{member}} to your team": "Dodaj {{member}} v svojo ekipo",
"Collaborator": "Sodelavec",
"Link copied to clipboard": "Povezava kopirana v odložišče",
"team site": "spletno mesto ekipe",
@ -758,7 +758,7 @@
},
"ColumnTitle": {
"Column ID copied to clipboard": "ID stolpca kopiran v odložišče",
"Add description": "Dodajte opis",
"Add description": "Dodaj opis",
"Column description": "Opis stolpca",
"Provide a column label": "Navedite oznako stolpca",
"Close": "Zapri",
@ -870,7 +870,7 @@
},
"RecordLayoutEditor": {
"Show field {{- label}}": "Prikaži polje {{- label}}",
"Add Field": "Dodajte polje",
"Add Field": "Dodaj polje",
"Save Layout": "Shrani postavitev",
"Cancel": "Prekliči",
"Create New Field": "Ustvari novo polje"
@ -926,7 +926,7 @@
"Switch appearance automatically to match system": "Samodejno preklopite videz, da se ujema s sistemom"
},
"SiteSwitcher": {
"Switch Sites": "Preklopite mesta",
"Switch Sites": "Preklopi mesta",
"Create new team site": "Ustvarite novo spletno mesto ekipe"
},
"WebhookPage": {
@ -1016,7 +1016,7 @@
"Reference List": "Referenčni seznam",
"* Workspaces are available on team plans. ": "* Delovni prostori so na voljo v skupinskih načrtih. ",
"Upgrade now": "Nadgradi zdaj",
"Toggle": "Preklopi",
"Toggle": "Preklop",
"Choice List": "Izbirni seznam",
"Any": "Katerikoli",
"DateTime": "Datum čas",
@ -1170,8 +1170,8 @@
"ConditionalStyle": {
"Rule must return True or False": "Pravilo mora vrniti True ali False",
"Row Style": "Slog vrstice",
"Add another rule": "Dodajte še eno pravilo",
"Add conditional style": "Dodajte pogojni slog",
"Add another rule": "Dodaj dodatno pravilo",
"Add conditional style": "Dodaj pogojni slog",
"Error in style rule": "Napaka v slogovnem pravilu"
},
"EditorTooltip": {
@ -1219,7 +1219,7 @@
},
"duplicatePage": {
"Note that this does not copy data, but creates another view of the same data.": "Upoštevajte, da s tem ne kopirate podatkov, ampak ustvarite drug pogled istih podatkov.",
"Duplicate page {{pageName}}": "Podvojena stran {{pageName}}"
"Duplicate page {{pageName}}": "Podvoji stran {{pageName}}"
},
"CurrencyPicker": {
"Invalid currency": "Neveljavna valuta"

View File

@ -151,7 +151,27 @@
"Unfreeze {{count}} columns_one": "Відкріпити цей стовпець",
"Unfreeze {{count}} columns_other": "Відкріпити {{count}} стовпців",
"Insert column to the right": "Вставити стовпець праворуч",
"Insert column to the left": "Вставити стовпець ліворуч"
"Insert column to the left": "Вставити стовпець ліворуч",
"Detect Duplicates in...": "Виявлення дублікатів у...",
"UUID": "UUID",
"Shortcuts": "Ярлики",
"Show hidden columns": "Показати приховані стовпці",
"Created At": "Дата створення",
"Authorship": "Авторство",
"Last Updated By": "Автор останньої зміни",
"Hidden Columns": "Приховані стовпці",
"Lookups": "Lookups",
"No reference columns.": "Немає довідкових стовпців.",
"Apply on record changes": "Застосовувати при зміні записів",
"Duplicate in {{- label}}": "Дублікат у {{- label}}\"",
"Created By": "Автор створення",
"Last Updated At": "Дата останньої зміни",
"Apply to new records": "Застосувати до нових записів",
"Search columns": "Шукати стовпці",
"Timestamp": "Часова мітка",
"no reference column": "немає довідкових стовпців",
"Adding UUID column": "Adding UUID column",
"Adding duplicates column": "Adding duplicates column"
},
"ThemeConfig": {
"Switch appearance automatically to match system": "Автоматично змінювати оформлення відповідно до системи",
@ -820,7 +840,8 @@
"Reference": "Посилання",
"DateTime": "Дата і час",
"Reference List": "Список посилань",
"Attachment": "Вкладення"
"Attachment": "Вкладення",
"Search columns": "Шукати стовпці"
},
"modals": {
"Cancel": "Відмінити",
@ -913,14 +934,15 @@
},
"FieldBuilder": {
"CELL FORMAT": "ФОРМАТ КЛІТИНКИ",
"Changing multiple column types": "Зміна кількох типів стовпців",
"Changing multiple column types": "Changing multiple column types",
"DATA FROM TABLE": "ДАНІ З ТАБЛИЦІ",
"Mixed format": "Змішаний формат",
"Mixed types": "Змішані типи",
"Use separate field settings for {{colId}}": "Використати окремі налаштування поля для {{colId}}",
"Apply Formula to Data": "Застосувати формулу до даних",
"Revert field settings for {{colId}} to common": "Змінити налаштування поля для {{colId}} на загальні",
"Save field settings for {{colId}} as common": "Зберегти налаштування поля для {{colId}} як загальні"
"Save field settings for {{colId}} as common": "Зберегти налаштування поля для {{colId}} як загальні",
"Changing column type": "Changing column type"
},
"FieldEditor": {
"Unable to finish saving edited cell": "Неможливо зберегти відредаговану клітинку",
@ -931,7 +953,11 @@
"Error in the cell": "Помилка в клітинці",
"Errors in all {{numErrors}} cells": "Помилки в усіх клітинках {{numErrors}}",
"editingFormula is required": "editingFormula обов'язкове",
"Errors in {{numErrors}} of {{numCells}} cells": "Помилки в {{numErrors}} в {{numCells}} клітинках"
"Errors in {{numErrors}} of {{numCells}} cells": "Помилки в {{numErrors}} в {{numCells}} клітинках",
"Enter formula or {{button}}.": "Введіть формулу або {{button}}.",
"Enter formula.": "Введіть формулу.",
"Expand Editor": "Розгорнути редактор",
"use AI Assistant": "Використати AI Помічника"
},
"HyperLinkEditor": {
"[link label] url": "[мітка посилання] URL"
@ -1017,9 +1043,14 @@
"Useful for storing the timestamp or author of a new record, data cleaning, and more.": "Корисно для збереження позначки часу або автора нового запису, очищення даних тощо.",
"You can filter by more than one column.": "Ви можете фільтрувати за кількома стовпцями.",
"Try out changes in a copy, then decide whether to replace the original with your edits.": "Спробуйте змінити копію, а потім вирішіть, чи слід замінювати оригінал своїми правками.",
"Access rules give you the power to create nuanced rules to determine who can see or edit which parts of your document.": "Правила доступу дають вам можливість створювати детальні правила, щоб визначити, хто може бачити або редагувати (та які) частини вашого документа."
"Access rules give you the power to create nuanced rules to determine who can see or edit which parts of your document.": "Правила доступу дають вам можливість створювати детальні правила, щоб визначити, хто може бачити або редагувати (та які) частини вашого документа.",
"To make an anchor link that takes the user to a specific cell, click on a row and press {{shortcut}}.": "Щоб створити якірне посилання, яке перенаправляє користувача до певної комірки, клацніть на рядку та натисніть {{shortcut}}.",
"Anchor Links": "Якірні посилання"
},
"DescriptionConfig": {
"DESCRIPTION": "ОПИС"
},
"FieldContextMenu": {
"Copy anchor link": "Скопіювати якірне посилання"
}
}

View File

@ -2700,6 +2700,43 @@ function testDocApi() {
assert.equal(resp2.data, 'A,B\nSanta,1\nBob,11\nAlice,2\nFelix,22\n');
});
it("GET /docs/{did}/download/table-schema serves table-schema-encoded document with header=colId", async function () {
const { docUrl, tableUrl } = await generateDocAndUrl('tableSchemaWithColIdAsHeader');
const columns = [
{
id: 'Some_ID',
fields: {
label: 'Some Label',
type: 'Text',
}
},
];
const setupColResp = await axios.put(`${tableUrl}/columns`, { columns }, {...chimpy, params: { replaceall: true }});
assert.equal(setupColResp.status, 200);
const resp = await axios.get(`${docUrl}/download/table-schema?tableId=Table1&header=colId`, chimpy);
assert.equal(resp.status, 200);
const expected = {
format: "csv",
mediatype: "text/csv",
encoding: "utf-8",
dialect: {
delimiter: ",",
doubleQuote: true,
},
name: 'table1',
title: 'Table1',
schema: {
fields: [{
name: 'Some_ID',
type: 'string',
format: 'default',
}]
}
};
assert.deepInclude(resp.data, expected);
});
it("GET /docs/{did}/download/table-schema respects permissions", async function () {
// kiwi has no access to TestDoc
const resp = await axios.get(`${serverUrl}/api/docs/${docIds.TestDoc}/download/table-schema?tableId=Table1`, kiwi);