(core) updates from grist-core

This commit is contained in:
Paul Fitzpatrick 2023-11-27 04:25:34 -05:00
commit 65a1863015
6 changed files with 158 additions and 29 deletions

View File

@ -6,8 +6,8 @@
* IdP is the "Identity Provider", somewhere users log into, e.g. Okta or Google Apps. * IdP is the "Identity Provider", somewhere users log into, e.g. Okta or Google Apps.
* *
* We also use optional attributes for the user's name, for which we accept any of: * We also use optional attributes for the user's name, for which we accept any of:
* given_name * given_name + family_name
* family_name * name
* *
* Expected environment variables: * Expected environment variables:
* env GRIST_OIDC_SP_HOST=https://<your-domain> * env GRIST_OIDC_SP_HOST=https://<your-domain>
@ -21,6 +21,17 @@
* The client secret for the application, as registered with the IdP. * The client secret for the application, as registered with the IdP.
* env GRIST_OIDC_IDP_SCOPES * env GRIST_OIDC_IDP_SCOPES
* The scopes to request from the IdP, as a space-separated list. Defaults to "openid email profile". * The scopes to request from the IdP, as a space-separated list. Defaults to "openid email profile".
* env GRIST_OIDC_SP_PROFILE_NAME_ATTR
* The key of the attribute to use for the user's name.
* If omitted, the name will either be the concatenation of "given_name" + "family_name" or the "name" attribute.
* env GRIST_OIDC_SP_PROFILE_EMAIL_ATTR
* The key of the attribute to use for the user's email. Defaults to "email".
* env GRIST_OIDC_IDP_SKIP_END_SESSION_ENDPOINT
* If set to "true", on logout, there won't be any attempt to call the IdP's end_session_endpoint
* (the user will remain logged in in the IdP).
* env GRIST_OIDC_SP_IGNORE_EMAIL_VERIFIED
* If set to "true", the user will be allowed to login even if the email is not verified by the IDP.
* Defaults to false.
* *
* This version of OIDCConfig has been tested with Keycloak OIDC IdP following the instructions * This version of OIDCConfig has been tested with Keycloak OIDC IdP following the instructions
* at: * at:
@ -43,12 +54,17 @@ import { Sessions } from './Sessions';
import log from 'app/server/lib/log'; import log from 'app/server/lib/log';
import { appSettings } from './AppSettings'; import { appSettings } from './AppSettings';
import { RequestWithLogin } from './Authorizer'; import { RequestWithLogin } from './Authorizer';
import { UserProfile } from 'app/common/LoginSessionAPI';
const CALLBACK_URL = '/oauth2/callback'; const CALLBACK_URL = '/oauth2/callback';
export class OIDCConfig { export class OIDCConfig {
private _client: Client; private _client: Client;
private _redirectUrl: string; private _redirectUrl: string;
private _namePropertyKey?: string;
private _emailPropertyKey: string;
private _skipEndSessionEndpoint: boolean;
private _ignoreEmailVerified: boolean;
public constructor() { public constructor() {
} }
@ -69,6 +85,24 @@ export class OIDCConfig {
envVar: 'GRIST_OIDC_IDP_CLIENT_SECRET', envVar: 'GRIST_OIDC_IDP_CLIENT_SECRET',
censor: true, censor: true,
}); });
this._namePropertyKey = section.flag('namePropertyKey').readString({
envVar: 'GRIST_OIDC_SP_PROFILE_NAME_ATTR',
});
this._emailPropertyKey = section.flag('emailPropertyKey').requireString({
envVar: 'GRIST_OIDC_SP_PROFILE_EMAIL_ATTR',
defaultValue: 'email',
});
this._skipEndSessionEndpoint = section.flag('endSessionEndpoint').readBool({
envVar: 'GRIST_OIDC_IDP_SKIP_END_SESSION_ENDPOINT',
defaultValue: false,
})!;
this._ignoreEmailVerified = section.flag('ignoreEmailVerified').readBool({
envVar: 'GRIST_OIDC_SP_IGNORE_EMAIL_VERIFIED',
defaultValue: false,
})!;
const issuer = await Issuer.discover(issuerUrl); const issuer = await Issuer.discover(issuerUrl);
this._redirectUrl = new URL(CALLBACK_URL, spHost).href; this._redirectUrl = new URL(CALLBACK_URL, spHost).href;
@ -78,6 +112,10 @@ export class OIDCConfig {
redirect_uris: [ this._redirectUrl ], redirect_uris: [ this._redirectUrl ],
response_types: [ 'code' ], response_types: [ 'code' ],
}); });
if (this._client.issuer.metadata.end_session_endpoint === undefined && !this._skipEndSessionEndpoint) {
throw new Error('The Identity provider does not propose end_session_endpoint. ' +
'If that is expected, please set GRIST_OIDC_IDP_SKIP_END_SESSION_ENDPOINT=true');
}
log.info(`OIDCConfig: initialized with issuer ${issuerUrl}`); log.info(`OIDCConfig: initialized with issuer ${issuerUrl}`);
} }
@ -105,6 +143,11 @@ export class OIDCConfig {
); );
const userInfo = await this._client.userinfo(tokenSet); const userInfo = await this._client.userinfo(tokenSet);
if (!this._ignoreEmailVerified && userInfo.email_verified !== true) {
throw new Error(`OIDCConfig: email not verified for ${userInfo.email}`);
}
const profile = this._makeUserProfileFromUserInfo(userInfo); const profile = this._makeUserProfileFromUserInfo(userInfo);
log.info(`OIDCConfig: got OIDC response for ${profile.email} (${profile.name}) redirecting to ${targetUrl}`); log.info(`OIDCConfig: got OIDC response for ${profile.email} (${profile.name}) redirecting to ${targetUrl}`);
@ -140,6 +183,10 @@ export class OIDCConfig {
} }
public async getLogoutRedirectUrl(req: express.Request, redirectUrl: URL): Promise<string> { public async getLogoutRedirectUrl(req: express.Request, redirectUrl: URL): Promise<string> {
// For IdPs that don't have end_session_endpoint, we just redirect to the logout page.
if (this._skipEndSessionEndpoint) {
return redirectUrl.href;
}
return this._client.endSessionUrl({ return this._client.endSessionUrl({
post_logout_redirect_uri: redirectUrl.href post_logout_redirect_uri: redirectUrl.href
}); });
@ -167,14 +214,21 @@ export class OIDCConfig {
return codeVerifier; return codeVerifier;
} }
private _makeUserProfileFromUserInfo(userInfo: UserinfoResponse) { private _makeUserProfileFromUserInfo(userInfo: UserinfoResponse): Partial<UserProfile> {
const email = userInfo.email; return {
const fname = userInfo.given_name ?? ''; email: String(userInfo[ this._emailPropertyKey ]),
const lname = userInfo.family_name ?? ''; name: this._extractName(userInfo)
return { };
email, }
name: `${fname} ${lname}`.trim(),
}; private _extractName(userInfo: UserinfoResponse): string|undefined {
if (this._namePropertyKey) {
return (userInfo[ this._namePropertyKey ] as any)?.toString();
}
const fname = userInfo.given_name ?? '';
const lname = userInfo.family_name ?? '';
return `${fname} ${lname}`.trim() || userInfo.name;
} }
} }

View File

@ -217,7 +217,13 @@
"Duplicate Table": "Tabelle duplizieren", "Duplicate Table": "Tabelle duplizieren",
"Raw Data Tables": "Rohdaten-Tabellen", "Raw Data Tables": "Rohdaten-Tabellen",
"Table ID copied to clipboard": "Tabellen-ID in die Zwischenablage kopiert", "Table ID copied to clipboard": "Tabellen-ID in die Zwischenablage kopiert",
"You do not have edit access to this document": "Sie haben keinen Bearbeitungszugriff auf dieses Dokument" "You do not have edit access to this document": "Sie haben keinen Bearbeitungszugriff auf dieses Dokument",
"Edit Record Card": "Karteikarte bearbeiten",
"Rename Table": "Tabelle umbenennen",
"{{action}} Record Card": "{{action}} Karteikarte",
"Record Card": "Karteikarte",
"Remove Table": "Tabelle entfernen",
"Record Card Disabled": "Karteikarte Deaktiviert"
}, },
"DocHistory": { "DocHistory": {
"Activity": "Aktivität", "Activity": "Aktivität",
@ -663,7 +669,8 @@
"Insert row above": "Zeile oben einfügen", "Insert row above": "Zeile oben einfügen",
"Insert row below": "Zeile unten einfügen", "Insert row below": "Zeile unten einfügen",
"Duplicate rows_one": "Zeile duplizieren", "Duplicate rows_one": "Zeile duplizieren",
"Duplicate rows_other": "Zeilen duplizieren" "Duplicate rows_other": "Zeilen duplizieren",
"View as card": "Ansicht als Karte"
}, },
"SelectionSummary": { "SelectionSummary": {
"Copied to clipboard": "In die Zwischenablage kopiert" "Copied to clipboard": "In die Zwischenablage kopiert"
@ -1295,5 +1302,13 @@
}, },
"searchDropdown": { "searchDropdown": {
"Search": "Suchen" "Search": "Suchen"
},
"CardContextMenu": {
"Insert card above": "Karte oben einfügen",
"Duplicate card": "Karte duplizieren",
"Insert card below": "Karte unten einfügen",
"Delete card": "Karte löschen",
"Copy anchor link": "Ankerlink kopieren",
"Insert card": "Karte einfügen"
} }
} }

View File

@ -543,7 +543,8 @@
"Insert row above": "Insertar fila arriba", "Insert row above": "Insertar fila arriba",
"Insert row below": "Insertar fila debajo", "Insert row below": "Insertar fila debajo",
"Duplicate rows_one": "Duplicar fila", "Duplicate rows_one": "Duplicar fila",
"Duplicate rows_other": "Duplicar filas" "Duplicate rows_other": "Duplicar filas",
"View as card": "Ver como tarjeta"
}, },
"ShareMenu": { "ShareMenu": {
"Access Details": "Detalles de Acceso", "Access Details": "Detalles de Acceso",
@ -718,7 +719,13 @@
"Duplicate Table": "Duplicar tabla", "Duplicate Table": "Duplicar tabla",
"Raw Data Tables": "Tablas de datos brutos", "Raw Data Tables": "Tablas de datos brutos",
"Table ID copied to clipboard": "ID de tabla copiado al portapapeles", "Table ID copied to clipboard": "ID de tabla copiado al portapapeles",
"Click to copy": "Haga clic para copiar" "Click to copy": "Haga clic para copiar",
"Edit Record Card": "Editar la ficha del registro",
"Rename Table": "Cambiar el nombre de la tabla",
"{{action}} Record Card": "{{action}} Ficha",
"Record Card": "Ficha de registro",
"Remove Table": "Quitar la tabla",
"Record Card Disabled": "Tarjeta de registro desactivada"
}, },
"DocPageModel": { "DocPageModel": {
"Add Empty Table": "Agregar tabla vacía", "Add Empty Table": "Agregar tabla vacía",
@ -1285,5 +1292,13 @@
"FloatingPopup": { "FloatingPopup": {
"Maximize": "Maximizar", "Maximize": "Maximizar",
"Minimize": "Minimizar" "Minimize": "Minimizar"
},
"CardContextMenu": {
"Insert card above": "Inserte la tarjeta arriba",
"Duplicate card": "Tarjeta duplicada",
"Insert card below": "Inserte la tarjeta a continuación",
"Delete card": "Borrar la tarjeta",
"Copy anchor link": "Copiar enlace fijado",
"Insert card": "Insertar la tarjeta"
} }
} }

View File

@ -217,7 +217,13 @@
"Duplicate Table": "Duplicar a Tabela", "Duplicate Table": "Duplicar a Tabela",
"Raw Data Tables": "Tabelas de Dados Primários", "Raw Data Tables": "Tabelas de Dados Primários",
"Table ID copied to clipboard": "ID da Tabela copiada para a área de transferência", "Table ID copied to clipboard": "ID da Tabela copiada para a área de transferência",
"You do not have edit access to this document": "Você não tem permissão de edição desse documento" "You do not have edit access to this document": "Você não tem permissão de edição desse documento",
"Edit Record Card": "Editar cartão de registro",
"Rename Table": "Renomear tabela",
"{{action}} Record Card": "{{action}} Cartão de registro",
"Record Card": "Cartão de registro",
"Remove Table": "Remover tabela",
"Record Card Disabled": "Cartão de registro desabilitado"
}, },
"DocHistory": { "DocHistory": {
"Activity": "Atividade", "Activity": "Atividade",
@ -663,7 +669,8 @@
"Insert row above": "Inserir linha acima", "Insert row above": "Inserir linha acima",
"Insert row below": "Inserir linha abaixo", "Insert row below": "Inserir linha abaixo",
"Duplicate rows_one": "Duplicar linha", "Duplicate rows_one": "Duplicar linha",
"Duplicate rows_other": "Duplicar linhas" "Duplicate rows_other": "Duplicar linhas",
"View as card": "Ver como cartão"
}, },
"SelectionSummary": { "SelectionSummary": {
"Copied to clipboard": "Copiado para a área de transferência" "Copied to clipboard": "Copiado para a área de transferência"
@ -1295,5 +1302,13 @@
"FloatingPopup": { "FloatingPopup": {
"Maximize": "Maximizar", "Maximize": "Maximizar",
"Minimize": "Minimizar" "Minimize": "Minimizar"
},
"CardContextMenu": {
"Insert card above": "Inserir cartão acima",
"Duplicate card": "Duplicar o cartão",
"Insert card below": "Inserir cartão abaixo",
"Delete card": "Excluir cartão",
"Copy anchor link": "Copiar link de ancoragem",
"Insert card": "Inserir cartão"
} }
} }

View File

@ -334,7 +334,13 @@
"Duplicate Table": "Дублировать таблицу", "Duplicate Table": "Дублировать таблицу",
"Table ID copied to clipboard": "Идентификатор таблицы скопирован в буфер обмена", "Table ID copied to clipboard": "Идентификатор таблицы скопирован в буфер обмена",
"You do not have edit access to this document": "У вас нет доступа к редактированию этого документа", "You do not have edit access to this document": "У вас нет доступа к редактированию этого документа",
"Delete {{formattedTableName}} data, and remove it from all pages?": "Удалить {{formattedTableName}} данные, и удалить их со всех страниц?" "Delete {{formattedTableName}} data, and remove it from all pages?": "Удалить {{formattedTableName}} данные, и удалить их со всех страниц?",
"Edit Record Card": "Редактировать карточку записи",
"Rename Table": "Переименовать Таблицу",
"{{action}} Record Card": "{{action}} Карточка записи",
"Record Card": "Карточка записи",
"Remove Table": "Удалить Таблицу",
"Record Card Disabled": "Карточка записи отключена"
}, },
"DocHistory": { "DocHistory": {
"Snapshots": "Снимки", "Snapshots": "Снимки",
@ -603,7 +609,8 @@
"Insert row below": "Вставить строку ниже", "Insert row below": "Вставить строку ниже",
"Insert row": "Вставить строку", "Insert row": "Вставить строку",
"Insert row above": "Вставить строку выше", "Insert row above": "Вставить строку выше",
"Delete": "Удалить" "Delete": "Удалить",
"View as card": "Посмотреть как карточку"
}, },
"RecordLayout": { "RecordLayout": {
"Updating record layout.": "Обновление макета записи." "Updating record layout.": "Обновление макета записи."
@ -1231,5 +1238,13 @@
"FloatingPopup": { "FloatingPopup": {
"Maximize": "Максимизировать", "Maximize": "Максимизировать",
"Minimize": "Минимизировать" "Minimize": "Минимизировать"
},
"CardContextMenu": {
"Insert card above": "Вставить карточку выше",
"Duplicate card": "Дублировать карточку",
"Insert card below": "Вставить карточку ниже",
"Delete card": "Удалить карточку",
"Copy anchor link": "Скопировать якорную ссылку",
"Insert card": "Вставить карточку"
} }
} }

View File

@ -146,7 +146,7 @@
"Deleted {{at}}": "Izbrisano {{at}}", "Deleted {{at}}": "Izbrisano {{at}}",
"Delete {{name}}": "Izbriši {{name}}", "Delete {{name}}": "Izbriši {{name}}",
"Document will be permanently deleted.": "Dokument bo trajno izbrisan.", "Document will be permanently deleted.": "Dokument bo trajno izbrisan.",
"Permanently Delete \"{{name}}\"?": "Trajno izbrisati \"{{name}}\"?", "Permanently Delete \"{{name}}\"?": "Trajno izbrišem \"{{name}}\"?",
"(The organization needs a paid plan)": "(Organizacija potrebuje plačljiv načrt)", "(The organization needs a paid plan)": "(Organizacija potrebuje plačljiv načrt)",
"Access Details": "Podrobnosti o dostopu", "Access Details": "Podrobnosti o dostopu",
"All Documents": "Vsi dokumenti", "All Documents": "Vsi dokumenti",
@ -168,7 +168,7 @@
"Requires edit permissions": "Zahteva dovoljenja za urejanje", "Requires edit permissions": "Zahteva dovoljenja za urejanje",
"Other Sites": "Druga spletna mesta", "Other Sites": "Druga spletna mesta",
"Pinned Documents": "Pripeti dokumenti", "Pinned Documents": "Pripeti dokumenti",
"To restore this document, restore the workspace first.": "Če želite obnoviti ta dokument, najprej obnovite delovni prostor.", "To restore this document, restore the workspace first.": "Če želiš obnoviti ta dokument, najprej obnovi delovni prostor.",
"You are on your personal site. You also have access to the following sites:": "Nahajate se na svojem osebnem spletnem mestu. Prav tako imate dostop do naslednjih spletnih mest:", "You are on your personal site. You also have access to the following sites:": "Nahajate se na svojem osebnem spletnem mestu. Prav tako imate dostop do naslednjih spletnih mest:",
"Restore": "Obnovi", "Restore": "Obnovi",
"Move {{name}} to workspace": "Premakni {{name}} v delovni prostor", "Move {{name}} to workspace": "Premakni {{name}} v delovni prostor",
@ -260,7 +260,8 @@
"Copy anchor link": "Kopiraj sidrno povezavo", "Copy anchor link": "Kopiraj sidrno povezavo",
"Duplicate rows_one": "Podvoji vrstico", "Duplicate rows_one": "Podvoji vrstico",
"Duplicate rows_other": "Podvoji vrstice", "Duplicate rows_other": "Podvoji vrstice",
"Insert row above": "Vstavi vrstico zgoraj" "Insert row above": "Vstavi vrstico zgoraj",
"View as card": "Kartični pogled"
}, },
"Tools": { "Tools": {
"Delete": "Izbriši", "Delete": "Izbriši",
@ -298,7 +299,13 @@
"Duplicate Table": "Podvojena tabela", "Duplicate Table": "Podvojena tabela",
"Table ID copied to clipboard": "ID tabele kopiran v odložišče", "Table ID copied to clipboard": "ID tabele kopiran v odložišče",
"You do not have edit access to this document": "Nimate dostopa za urejanje tega dokumenta", "You do not have edit access to this document": "Nimate dostopa za urejanje tega dokumenta",
"Raw Data Tables": "Neobdelana tabela" "Raw Data Tables": "Neobdelana tabela",
"Edit Record Card": "Uredi evidenčno kartico",
"Rename Table": "Preimenuj Tabelo",
"{{action}} Record Card": "{{action}} Evidenčno Kartico",
"Record Card": "Evidenčna kartica",
"Remove Table": "Odstrani Tabelo",
"Record Card Disabled": "Evidenčna kartica onemogočena"
}, },
"ViewLayoutMenu": { "ViewLayoutMenu": {
"Delete record": "Brisanje zapisa", "Delete record": "Brisanje zapisa",
@ -590,7 +597,7 @@
"Organization": "Organizacija", "Organization": "Organizacija",
"Replacing the original requires editing rights on the original document.": "Za zamenjavo izvirnika so potrebne pravice za urejanje izvirnega dokumenta.", "Replacing the original requires editing rights on the original document.": "Za zamenjavo izvirnika so potrebne pravice za urejanje izvirnega dokumenta.",
"Remove document history (can significantly reduce file size)": "Odstranitev zgodovine dokumenta (lahko znatno zmanjša velikost datoteke)", "Remove document history (can significantly reduce file size)": "Odstranitev zgodovine dokumenta (lahko znatno zmanjša velikost datoteke)",
"To save your changes, please sign up, then reload this page.": "Če želite shraniti spremembe, se prijavite in nato ponovno naložite to stran.", "To save your changes, please sign up, then reload this page.": "Če želiš shraniti spremembe, se prijavi in nato ponovno naloži to stran.",
"The original version of this document will be updated.": "Prvotna različica tega dokumenta bo posodobljena.", "The original version of this document will be updated.": "Prvotna različica tega dokumenta bo posodobljena.",
"However, it appears to be already identical.": "Vendar se zdi, da je že identična.", "However, it appears to be already identical.": "Vendar se zdi, da je že identična.",
"Update Original": "Posodobitev izvirnika", "Update Original": "Posodobitev izvirnika",
@ -642,7 +649,7 @@
"Access rules give you the power to create nuanced rules to determine who can see or edit which parts of your document.": "Pravila dostopa vam omogočajo, da ustvarite podrobna pravila, s katerimi določite, kdo lahko vidi ali ureja posamezne dele dokumenta.", "Access rules give you the power to create nuanced rules to determine who can see or edit which parts of your document.": "Pravila dostopa vam omogočajo, da ustvarite podrobna pravila, s katerimi določite, kdo lahko vidi ali ureja posamezne dele dokumenta.",
"Rearrange the fields in your card by dragging and resizing cells.": "Z vlečenjem in spreminjanjem velikosti polj na kartici spremenite njihovo razporeditev.", "Rearrange the fields in your card by dragging and resizing cells.": "Z vlečenjem in spreminjanjem velikosti polj na kartici spremenite njihovo razporeditev.",
"Useful for storing the timestamp or author of a new record, data cleaning, and more.": "Uporabno za shranjevanje časovnega žiga ali avtorja novega zapisa, čiščenje podatkov in drugo.", "Useful for storing the timestamp or author of a new record, data cleaning, and more.": "Uporabno za shranjevanje časovnega žiga ali avtorja novega zapisa, čiščenje podatkov in drugo.",
"Click the Add New button to create new documents or workspaces, or import data.": "Če želite ustvariti nove dokumente ali delovne prostore ali uvoziti podatke, kliknite gumb Dodaj.", "Click the Add New button to create new documents or workspaces, or import data.": "Klikni gumb dodaj novega, če želiš ustvariti nove dokumente, delovne prostore ali uvoziti podatke,",
"Nested Filtering": "Vgnezdeno filtriranje", "Nested Filtering": "Vgnezdeno filtriranje",
"Only those rows will appear which match all of the filters.": "Prikazane bodo samo tiste vrstice, ki ustrezajo vsem filtrom.", "Only those rows will appear which match all of the filters.": "Prikazane bodo samo tiste vrstice, ki ustrezajo vsem filtrom.",
"Editing Card Layout": "Urejanje postavitve kartice", "Editing Card Layout": "Urejanje postavitve kartice",
@ -655,11 +662,11 @@
"They allow for one record to point (or refer) to another.": "Omogočajo, da en zapis kaže (ali se sklicuje) na drugega.", "They allow for one record to point (or refer) to another.": "Omogočajo, da en zapis kaže (ali se sklicuje) na drugega.",
"Reference Columns": "Referenčni stolpci", "Reference Columns": "Referenčni stolpci",
"To configure your calendar, select columns for start": { "To configure your calendar, select columns for start": {
"end dates and event titles. Note each column's type.": "Če želite konfigurirati svoj koledar, izberite stolpce za začetne/končne datume in naslove dogodkov. Upoštevajte vrsto vsakega stolpca." "end dates and event titles. Note each column's type.": "Če želiš konfigurirati svoj koledar, izberi stolpce za začetne/končne datume in naslove dogodkov. Upoštevaj vrsto vsakega stolpca."
}, },
"Calendar": "Koledar", "Calendar": "Koledar",
"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.", "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}}.", "To make an anchor link that takes the user to a specific cell, click on a row and press {{shortcut}}.": "Če želiš narediti sidrno povezavo, ki uporabnika pripelje do določene celice, klikni vrstico in pritisni {{shortcut}}.",
"Unpin to hide the the button while keeping the filter.": "Odpnite, da skrijete gumb in obdržite filter.", "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.": "Uporabi pogojno oblikovanje za vrstice na podlagi formul.", "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.", "Click on “Open row styles” to apply conditional formatting to rows.": "Klikni »Odpri sloge vrstic«, da za vrstice uporabiš pogojno oblikovanje.",
@ -707,14 +714,14 @@
"Close": "Zapri", "Close": "Zapri",
"Allow anyone with the link to open.": "Omogočite odprtje vsakomur, ki ima povezavo.", "Allow anyone with the link to open.": "Omogočite odprtje vsakomur, ki ima povezavo.",
"Invite people to {{resourceType}}": "Povabite ljudi k {{resourceType}}", "Invite people to {{resourceType}}": "Povabite ljudi k {{resourceType}}",
"Public access inherited from {{parent}}. To remove, set 'Inherit access' option to 'None'.": "Uporabnik podeduje dovoljenja od {{parent}}. Če jih želite odstraniti, nastavite možnost \"Podeduje dostop\" na \"Ne\".", "Public access inherited from {{parent}}. To remove, set 'Inherit access' option to 'None'.": "Uporabnik podeduje dovoljenja od {{parent}}. Če jih želiš odstraniti, nastavi možnost \"Podeduje dostop\" na \"Ne\".",
"Remove my access": "Odstranitev mojega dostopa", "Remove my access": "Odstranitev mojega dostopa",
"Public access": "Javni dostop", "Public access": "Javni dostop",
"Public Access": "Javni dostop", "Public Access": "Javni dostop",
"Cancel": "Prekliči", "Cancel": "Prekliči",
"Grist support": "Grist podpora", "Grist support": "Grist podpora",
"You are about to remove your own access to this {{resourceType}}": "Odstranili boste svoj dostop do tega {{resourceType}}", "You are about to remove your own access to this {{resourceType}}": "Odstranili boste svoj dostop do tega {{resourceType}}",
"User inherits permissions from {{parent}}. To remove, set 'Inherit access' option to 'None'.": "Uporabnik podeduje dovoljenja od {{parent}}. Če jih želite odstraniti, nastavite možnost \"Podeduje dostop\" na \"Ni\".", "User inherits permissions from {{parent}}. To remove, set 'Inherit access' option to 'None'.": "Uporabnik podeduje dovoljenja od {{parent}}. Če jih želiš odstraniti, nastavi možnost \"Podeduje dostop\" na \"Ni\".",
"Guest": "Gost", "Guest": "Gost",
"Invite multiple": "Povabite več", "Invite multiple": "Povabite več",
"Confirm": "Potrdite", "Confirm": "Potrdite",
@ -813,7 +820,7 @@
"Welcome to Grist!": "Dobrodošli v Gristu!", "Welcome to Grist!": "Dobrodošli v Gristu!",
"Visit our {{link}} to learn more about Grist.": "Obiščite našo spletno stran {{link}} da izveste več o Grisstu.", "Visit our {{link}} to learn more about Grist.": "Obiščite našo spletno stran {{link}} da izveste več o Grisstu.",
"Sign in": "Prijavi se", "Sign in": "Prijavi se",
"To use Grist, please either sign up or sign in.": "Če želite uporabljati Grist, se prijavite ali prvič prijavite." "To use Grist, please either sign up or sign in.": "Če želiš uporabljati Grist, se prijavi ali prvič prijavi."
}, },
"WelcomeSitePicker": { "WelcomeSitePicker": {
"You have access to the following Grist sites.": "Imate dostop do naslednjih Grist spletnih mest .", "You have access to the following Grist sites.": "Imate dostop do naslednjih Grist spletnih mest .",
@ -1231,5 +1238,13 @@
}, },
"sendToDrive": { "sendToDrive": {
"Sending file to Google Drive": "Pošiljanje datoteke v Google Drive" "Sending file to Google Drive": "Pošiljanje datoteke v Google Drive"
},
"CardContextMenu": {
"Insert card above": "Vstavi kartico zgoraj",
"Duplicate card": "Podvoji kartico",
"Insert card below": "Vstavi kartico spodaj",
"Delete card": "Briši kartico",
"Copy anchor link": "Kopiraj sidrno povezavo",
"Insert card": "Vstavi kartico"
} }
} }