mirror of
				https://github.com/gristlabs/grist-core.git
				synced 2025-06-13 20:53:59 +00:00 
			
		
		
		
	(core) updates from grist-core
This commit is contained in:
		
						commit
						0c05f4cdc4
					
				@ -60,7 +60,7 @@ RUN \
 | 
			
		||||
# Fetch gvisor-based sandbox. Note, to enable it to run within default
 | 
			
		||||
# unprivileged docker, layers of protection that require privilege have
 | 
			
		||||
# been stripped away, see https://github.com/google/gvisor/issues/4371
 | 
			
		||||
FROM gristlabs/gvisor-unprivileged:buster as sandbox
 | 
			
		||||
FROM docker.io/gristlabs/gvisor-unprivileged:buster as sandbox
 | 
			
		||||
 | 
			
		||||
################################################################################
 | 
			
		||||
## Run-time stage
 | 
			
		||||
 | 
			
		||||
@ -267,11 +267,11 @@ GRIST_DOMAIN        | in hosted Grist, Grist is served from subdomains of this d
 | 
			
		||||
GRIST_EXPERIMENTAL_PLUGINS | enables experimental plugins
 | 
			
		||||
GRIST_ENABLE_REQUEST_FUNCTION | enables the REQUEST function. This function performs HTTP requests in a similar way to `requests.request`. This function presents a significant security risk, since it can let users call internal endpoints when Grist is available publicly. This function can also cause performance issues. Unset by default.
 | 
			
		||||
GRIST_HIDE_UI_ELEMENTS | comma-separated list of UI features to disable. Allowed names of parts: `helpCenter,billing,templates,createSite,multiSite,multiAccounts,sendToDrive,tutorials`. If a part also exists in GRIST_UI_FEATURES, it will still be disabled.
 | 
			
		||||
GRIST_HOME_INCLUDE_STATIC | if set, home server also serves static resources
 | 
			
		||||
GRIST_HOST          | hostname to use when listening on a port.
 | 
			
		||||
GRIST_HTTPS_PROXY   | if set, use this proxy for webhook payload delivery.
 | 
			
		||||
GRIST_ID_PREFIX | for subdomains of form o-*, expect or produce o-${GRIST_ID_PREFIX}*.
 | 
			
		||||
GRIST_IGNORE_SESSION | if set, Grist will not use a session for authentication.
 | 
			
		||||
GRIST_INCLUDE_CUSTOM_SCRIPT_URL | if set, will load the referenced URL in a `<script>` tag on all app pages.
 | 
			
		||||
GRIST_INST_DIR      | path to Grist instance configuration files, for Grist server.
 | 
			
		||||
GRIST_LIST_PUBLIC_SITES | if set to true, sites shared with the public will be listed for anonymous users. Defaults to false.
 | 
			
		||||
GRIST_MANAGED_WORKERS | if set, Grist can assume that if a url targeted at a doc worker returns a 404, that worker is gone
 | 
			
		||||
 | 
			
		||||
@ -55,6 +55,7 @@ const {NEW_FILTER_JSON} = require('app/client/models/ColumnFilter');
 | 
			
		||||
const {CombinedStyle} = require("app/client/models/Styles");
 | 
			
		||||
const {buildRenameColumn} = require('app/client/ui/ColumnTitle');
 | 
			
		||||
const {makeT} = require('app/client/lib/localization');
 | 
			
		||||
const { isList } = require('app/common/gristTypes');
 | 
			
		||||
 | 
			
		||||
const t = makeT('GridView');
 | 
			
		||||
 | 
			
		||||
@ -345,6 +346,7 @@ GridView.gridCommands = {
 | 
			
		||||
      this.insertColumn(null, {index: this.cursor.fieldIndex() + 1});
 | 
			
		||||
    }
 | 
			
		||||
  },
 | 
			
		||||
  makeHeadersFromRow: function() { this.makeHeadersFromRow(this.getSelection()); },
 | 
			
		||||
  renameField: function() { this.renameColumn(this.cursor.fieldIndex()); },
 | 
			
		||||
  hideFields: function() { this.hideFields(this.getSelection()); },
 | 
			
		||||
  deleteFields: function() {
 | 
			
		||||
@ -902,6 +904,38 @@ GridView.prototype.insertColumn = async function(colId = null, options = {}) {
 | 
			
		||||
  return newColInfo;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
GridView.prototype.makeHeadersFromRow = async function(selection) {
 | 
			
		||||
  if (this._getCellContextMenuOptions().disableMakeHeadersFromRow){
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
  const record = this.tableModel.tableData.getRecord(selection.rowIds[0]);
 | 
			
		||||
  const actions = this.viewSection.viewFields().peek().reduce((acc, field) => {
 | 
			
		||||
    const col = field.column();
 | 
			
		||||
    const colId = col.colId.peek();
 | 
			
		||||
    let formatter = field.formatter();
 | 
			
		||||
    let newColLabel = record[colId];
 | 
			
		||||
    // Manage column that are references
 | 
			
		||||
    if (col.refTable()) {
 | 
			
		||||
      const refTableDisplayCol = this.gristDoc.docModel.columns.getRowModel(col.displayCol());
 | 
			
		||||
      newColLabel =  record[refTableDisplayCol.colId()];
 | 
			
		||||
      formatter = field.visibleColFormatter();
 | 
			
		||||
    }
 | 
			
		||||
    // Manage column that are lists
 | 
			
		||||
    if (isList(newColLabel)) {
 | 
			
		||||
      newColLabel = newColLabel[1];
 | 
			
		||||
    }
 | 
			
		||||
    if (typeof newColLabel === 'string') {
 | 
			
		||||
      newColLabel = newColLabel.trim();
 | 
			
		||||
    }
 | 
			
		||||
    // Check value is not empty but accept 0 and false as valid values
 | 
			
		||||
    if (newColLabel !== null && newColLabel !== undefined && newColLabel !== "") {
 | 
			
		||||
      return [...acc, ['ModifyColumn', colId, {"label": formatter.formatAny(newColLabel)}]];
 | 
			
		||||
    }
 | 
			
		||||
    return acc
 | 
			
		||||
  }, []);
 | 
			
		||||
  this.tableModel.sendTableActions(actions, "Use as table headers");
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
GridView.prototype.renameColumn = function(index) {
 | 
			
		||||
  this.currentEditingColumnIndex(index);
 | 
			
		||||
};
 | 
			
		||||
@ -1974,6 +2008,9 @@ GridView.prototype._getCellContextMenuOptions = function() {
 | 
			
		||||
      this.viewSection.disableAddRemoveRows() ||
 | 
			
		||||
      this.getSelection().onlyAddRowSelected()
 | 
			
		||||
    ),
 | 
			
		||||
    disableMakeHeadersFromRow: Boolean (
 | 
			
		||||
      this.gristDoc.isReadonly.get() || this.getSelection().rowIds.length !== 1 || this.getSelection().onlyAddRowSelected()
 | 
			
		||||
    ),
 | 
			
		||||
    isViewSorted: this.viewSection.activeSortSpec.peek().length > 0,
 | 
			
		||||
    numRows: this.getSelection().rowIds.length,
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
@ -84,6 +84,7 @@ export type CommandName =
 | 
			
		||||
  | 'deleteRecords'
 | 
			
		||||
  | 'insertFieldBefore'
 | 
			
		||||
  | 'insertFieldAfter'
 | 
			
		||||
  | 'makeHeadersFromRow'
 | 
			
		||||
  | 'renameField'
 | 
			
		||||
  | 'hideFields'
 | 
			
		||||
  | 'hideCardFields'
 | 
			
		||||
@ -562,6 +563,10 @@ export const groups: CommendGroupDef[] = [{
 | 
			
		||||
      name: 'insertFieldAfter',
 | 
			
		||||
      keys: ['Alt+='],
 | 
			
		||||
      desc: 'Insert a new column, after the currently selected one'
 | 
			
		||||
    }, {
 | 
			
		||||
      name: 'makeHeadersFromRow',
 | 
			
		||||
      keys: ['Mod+Shift+H'],
 | 
			
		||||
      desc: 'Use currently selected line as table headers'
 | 
			
		||||
    }, {
 | 
			
		||||
      name: 'renameField',
 | 
			
		||||
      keys: ['Ctrl+m'],
 | 
			
		||||
 | 
			
		||||
@ -8,6 +8,7 @@ const t = makeT('RowContextMenu');
 | 
			
		||||
export interface IRowContextMenu {
 | 
			
		||||
  disableInsert: boolean;
 | 
			
		||||
  disableDelete: boolean;
 | 
			
		||||
  disableMakeHeadersFromRow: boolean;
 | 
			
		||||
  disableShowRecordCard: boolean;
 | 
			
		||||
  isViewSorted: boolean;
 | 
			
		||||
  numRows: number;
 | 
			
		||||
@ -16,6 +17,7 @@ export interface IRowContextMenu {
 | 
			
		||||
export function RowContextMenu({
 | 
			
		||||
  disableInsert,
 | 
			
		||||
  disableDelete,
 | 
			
		||||
  disableMakeHeadersFromRow,
 | 
			
		||||
  disableShowRecordCard,
 | 
			
		||||
  isViewSorted,
 | 
			
		||||
  numRows
 | 
			
		||||
@ -51,6 +53,11 @@ export function RowContextMenu({
 | 
			
		||||
    menuItemCmd(allCommands.duplicateRows, t('Duplicate rows', { count: numRows }),
 | 
			
		||||
      dom.cls('disabled', disableInsert || numRows === 0)),
 | 
			
		||||
  );
 | 
			
		||||
  result.push(
 | 
			
		||||
    menuDivider(),
 | 
			
		||||
    menuItemCmd(allCommands.makeHeadersFromRow, t("Use as table headers"),
 | 
			
		||||
      dom.cls('disabled', disableMakeHeadersFromRow)),
 | 
			
		||||
  );
 | 
			
		||||
  result.push(
 | 
			
		||||
    menuDivider(),
 | 
			
		||||
    // TODO: should show `Delete ${num} rows` when multiple are selected
 | 
			
		||||
 | 
			
		||||
@ -6,6 +6,8 @@
 | 
			
		||||
var log = require('app/server/lib/log');
 | 
			
		||||
var Promise = require('bluebird');
 | 
			
		||||
 | 
			
		||||
var os = require('os');
 | 
			
		||||
 | 
			
		||||
var cleanupHandlers = [];
 | 
			
		||||
 | 
			
		||||
var signalsHandled = {};
 | 
			
		||||
@ -65,8 +67,8 @@ function runCleanupHandlers() {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Internal helper to exit on a signal. It runs the cleanup handlers, and then re-sends the same
 | 
			
		||||
 * signal, which will no longer get caught.
 | 
			
		||||
 * Internal helper to exit on a signal. It runs the cleanup handlers, and then
 | 
			
		||||
 * exits propagating the same signal code than the one caught.
 | 
			
		||||
 */
 | 
			
		||||
function signalExit(signal) {
 | 
			
		||||
  var prog = 'grist[' + process.pid + ']';
 | 
			
		||||
@ -81,7 +83,12 @@ function signalExit(signal) {
 | 
			
		||||
    log.info("Server %s exiting on %s", prog, signal);
 | 
			
		||||
    process.removeListener(signal, dup);
 | 
			
		||||
    delete signalsHandled[signal];
 | 
			
		||||
    process.kill(process.pid, signal);
 | 
			
		||||
    // Exit with the expected exit code for being killed by this signal.
 | 
			
		||||
    // Unlike re-sending the same signal, the explicit exit works even
 | 
			
		||||
    // in a situation when Grist is the init (pid 1) process in a container
 | 
			
		||||
    // See https://github.com/gristlabs/grist-core/pull/830 (and #892)
 | 
			
		||||
    const signalNumber = os.constants.signals[signal];
 | 
			
		||||
    process.exit(process.pid, 128 + signalNumber);
 | 
			
		||||
  });
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -665,7 +665,8 @@
 | 
			
		||||
        "Insert row": "Insert row",
 | 
			
		||||
        "Insert row above": "Insert row above",
 | 
			
		||||
        "Insert row below": "Insert row below",
 | 
			
		||||
        "View as card": "View as card"
 | 
			
		||||
        "View as card": "View as card",
 | 
			
		||||
        "Use as table headers": "Use as table headers"
 | 
			
		||||
    },
 | 
			
		||||
    "SelectionSummary": {
 | 
			
		||||
        "Copied to clipboard": "Copied to clipboard"
 | 
			
		||||
 | 
			
		||||
@ -586,7 +586,8 @@
 | 
			
		||||
        "Insert row below": "Insertar fila debajo",
 | 
			
		||||
        "Duplicate rows_one": "Duplicar fila",
 | 
			
		||||
        "Duplicate rows_other": "Duplicar filas",
 | 
			
		||||
        "View as card": "Ver como tarjeta"
 | 
			
		||||
        "View as card": "Ver como tarjeta",
 | 
			
		||||
        "Use as table headers": "Usar como encabezados de la tabla"
 | 
			
		||||
    },
 | 
			
		||||
    "ShareMenu": {
 | 
			
		||||
        "Access Details": "Detalles de Acceso",
 | 
			
		||||
 | 
			
		||||
@ -1379,5 +1379,15 @@
 | 
			
		||||
    "FormSuccessPage": {
 | 
			
		||||
        "Form Submitted": "Formulaire envoyé",
 | 
			
		||||
        "Thank you! Your response has been recorded.": "Nous vous remercions. Votre réponse a été enregistrée."
 | 
			
		||||
    },
 | 
			
		||||
    "DateRangeOptions": {
 | 
			
		||||
        "Last 30 days": "30 derniers jours",
 | 
			
		||||
        "Next 7 days": "7 prochains jours",
 | 
			
		||||
        "Last 7 days": "7 derniers jours",
 | 
			
		||||
        "Last Week": "Semaine passée",
 | 
			
		||||
        "This month": "Ce mois-ci",
 | 
			
		||||
        "This week": "Cette semaine",
 | 
			
		||||
        "This year": "Cette année",
 | 
			
		||||
        "Today": "Aujourd'hui"
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -268,7 +268,9 @@
 | 
			
		||||
        "You do not have write access to this site": "У вас нет права записи для этого сайта",
 | 
			
		||||
        "Remove all data but keep the structure to use as a template": "Удалить все данные, но сохранить структуру для использования в качестве шаблона.",
 | 
			
		||||
        "Download full document and history": "Скачать полный документ и историю",
 | 
			
		||||
        "Remove document history (can significantly reduce file size)": "Удалить историю документа (может значительно уменьшить размер файла)"
 | 
			
		||||
        "Remove document history (can significantly reduce file size)": "Удалить историю документа (может значительно уменьшить размер файла)",
 | 
			
		||||
        "Download": "Скачать",
 | 
			
		||||
        "Download document": "Скачать документ"
 | 
			
		||||
    },
 | 
			
		||||
    "ShareMenu": {
 | 
			
		||||
        "Back to Current": "Вернуться к текущему",
 | 
			
		||||
@ -494,7 +496,7 @@
 | 
			
		||||
        "Welcome to {{orgName}}": "Добро пожаловать в {{orgName}}",
 | 
			
		||||
        "personal site": "личный сайт",
 | 
			
		||||
        "You have read-only access to this site. Currently there are no documents.": "Вы имеете доступ к этому сайту только для просмотра. В настоящее время документов нет.",
 | 
			
		||||
        "{{signUp}} to save your work. ": "{{signUp}} сохранить свою работу. ",
 | 
			
		||||
        "{{signUp}} to save your work. ": "{{signUp}} для сохранения своих данных. ",
 | 
			
		||||
        "Welcome to Grist, {{- name}}!": "Добро пожаловать в Grist, {{- name}}!",
 | 
			
		||||
        "Welcome to {{- orgName}}": "Добро пожаловать в {{- orgName}}",
 | 
			
		||||
        "Visit our {{link}} to learn more about Grist.": "Посетите наш {{link}} чтобы узнать больше о Grist.",
 | 
			
		||||
@ -1375,5 +1377,15 @@
 | 
			
		||||
    "FormSuccessPage": {
 | 
			
		||||
        "Form Submitted": "Форма отправлена",
 | 
			
		||||
        "Thank you! Your response has been recorded.": "Спасибо! Ваш ответ учтен."
 | 
			
		||||
    },
 | 
			
		||||
    "DateRangeOptions": {
 | 
			
		||||
        "Today": "Сегодня",
 | 
			
		||||
        "Last 30 days": "Последние 30 дней",
 | 
			
		||||
        "Last 7 days": "Последние 7 дней",
 | 
			
		||||
        "Last Week": "Последняя неделя",
 | 
			
		||||
        "Next 7 days": "Следующие 7 дней",
 | 
			
		||||
        "This month": "Этот месяц",
 | 
			
		||||
        "This week": "Эта неделя",
 | 
			
		||||
        "This year": "Текущий год"
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -72,11 +72,22 @@ describe('RowMenu', function() {
 | 
			
		||||
    assert.isFalse(await driver.find('.grist-floating-menu').isPresent());
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  it('can rename headers from the selected line', async function() {
 | 
			
		||||
    assert.notEqual(await gu.getColumnHeader({col: 0}).getText(), await gu.getCell(0, 1).getText());
 | 
			
		||||
    assert.notEqual(await gu.getColumnHeader({col: 1}).getText(), await gu.getCell(1, 1).getText());
 | 
			
		||||
    await (await gu.openRowMenu(1)).findContent('li', /Use as table headers/).click();
 | 
			
		||||
    await gu.waitForServer();
 | 
			
		||||
    assert.equal(await gu.getColumnHeader({col: 0}).getText(), await gu.getCell(0, 1).getText());
 | 
			
		||||
    assert.equal(await gu.getColumnHeader({col: 1}).getText(), await gu.getCell(1, 1).getText());
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  it('should work even when no columns are visible', async function() {
 | 
			
		||||
    // Previously, a bug would cause an error to be thrown instead.
 | 
			
		||||
    await gu.openColumnMenu('A', 'Hide column');
 | 
			
		||||
    await gu.openColumnMenu('B', 'Hide column');
 | 
			
		||||
    await gu.openColumnMenu({col: 0}, 'Hide column');
 | 
			
		||||
    // After hiding the first column, the second one will be the new first column.
 | 
			
		||||
    await gu.openColumnMenu({col: 0}, 'Hide column');
 | 
			
		||||
    await assertRowMenuOpensAndCloses();
 | 
			
		||||
    await assertRowMenuOpensWithRightClick();
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
		Loading…
	
		Reference in New Issue
	
	Block a user