diff --git a/datasette/default_column_types.py b/datasette/default_column_types.py index 24493994..f90a733e 100644 --- a/datasette/default_column_types.py +++ b/datasette/default_column_types.py @@ -76,6 +76,12 @@ class JsonColumnType(ColumnType): return None +class TextareaColumnType(ColumnType): + name = "textarea" + description = "Multiline text" + sqlite_types = (SQLiteType.TEXT,) + + @hookimpl def register_column_types(datasette): - return [UrlColumnType, EmailColumnType, JsonColumnType] + return [UrlColumnType, EmailColumnType, JsonColumnType, TextareaColumnType] diff --git a/datasette/static/table.js b/datasette/static/table.js index 7d8f591d..f762ca40 100644 --- a/datasette/static/table.js +++ b/datasette/static/table.js @@ -792,12 +792,15 @@ function valueToEditText(value) { return String(value); } -function shouldUseTextarea(value) { +function shouldUseTextarea(value, columnType) { + if (columnType && columnType.type === "textarea") { + return true; + } if (value && typeof value === "object") { return true; } var text = valueToEditText(value); - return text.length > 80 || text.indexOf("\n") !== -1; + return text.length > 80 || /[\r\n]/.test(text); } function rowEditValueType(value) { @@ -835,7 +838,7 @@ function createRowEditField(column, value, isPk, columnType, index, options) { var controlWrap = document.createElement("div"); controlWrap.className = "row-edit-control-wrap"; - var control = shouldUseTextarea(value) + var control = shouldUseTextarea(value, columnType) ? document.createElement("textarea") : document.createElement("input"); control.className = "row-edit-input"; diff --git a/docs/configuration.rst b/docs/configuration.rst index 9213a640..d01f5153 100644 --- a/docs/configuration.rst +++ b/docs/configuration.rst @@ -1102,9 +1102,9 @@ These configure :ref:`full-text search ` for a table or view. ``column_types`` ^^^^^^^^^^^^^^^^ -You can assign semantic column types to columns, which affect how values are rendered, validated, and transformed. Built-in column types include ``url``, ``email``, and ``json``. Plugins can register additional column types using the :ref:`register_column_types ` plugin hook. +You can assign semantic column types to columns, which affect how values are rendered, validated, transformed, and edited. Built-in column types include ``url``, ``email``, ``json``, and ``textarea``. Plugins can register additional column types using the :ref:`register_column_types ` plugin hook. -Column types can optionally declare which SQLite column types they apply to using ``sqlite_types``. Datasette will reject incompatible assignments. The built-in ``url``, ``email``, and ``json`` column types are all restricted to ``TEXT`` columns. +Column types can optionally declare which SQLite column types they apply to using ``sqlite_types``. Datasette will reject incompatible assignments. The built-in ``url``, ``email``, ``json``, and ``textarea`` column types are all restricted to ``TEXT`` columns. The simplest form maps column names to type name strings: @@ -1119,6 +1119,7 @@ The simplest form maps column names to type name strings: website: url contact: email extra_data: json + notes: textarea """).strip() ) .. ]]] @@ -1135,6 +1136,7 @@ The simplest form maps column names to type name strings: website: url contact: email extra_data: json + notes: textarea .. tab:: datasette.json @@ -1148,7 +1150,8 @@ The simplest form maps column names to type name strings: "column_types": { "website": "url", "contact": "email", - "extra_data": "json" + "extra_data": "json", + "notes": "textarea" } } } diff --git a/docs/plugin_hooks.rst b/docs/plugin_hooks.rst index 2a0ddc93..7ae52a68 100644 --- a/docs/plugin_hooks.rst +++ b/docs/plugin_hooks.rst @@ -1092,7 +1092,7 @@ Column types are assigned to columns via the :ref:`column_types `` control for that column. .. _plugin_asgi_wrapper: diff --git a/tests/test_column_types.py b/tests/test_column_types.py index d77f2cf5..0c6b0dd4 100644 --- a/tests/test_column_types.py +++ b/tests/test_column_types.py @@ -494,6 +494,7 @@ async def test_builtin_column_types_registered(ds_ct): assert "url" in ds_ct._column_types assert "email" in ds_ct._column_types assert "json" in ds_ct._column_types + assert "textarea" in ds_ct._column_types assert "nonexistent" not in ds_ct._column_types @@ -510,6 +511,10 @@ async def test_column_type_class_attributes(ds_ct): assert email_cls.sqlite_types == (SQLiteType.TEXT,) json_cls = ds_ct._column_types["json"] assert json_cls.sqlite_types == (SQLiteType.TEXT,) + textarea_cls = ds_ct._column_types["textarea"] + assert textarea_cls.name == "textarea" + assert textarea_cls.description == "Multiline text" + assert textarea_cls.sqlite_types == (SQLiteType.TEXT,) def test_sqlite_type_from_declared_type(): @@ -941,6 +946,7 @@ async def test_set_column_type_ui_data_includes_applicable_types( "options": [ {"name": "email", "description": "Email address"}, {"name": "json", "description": "JSON data"}, + {"name": "textarea", "description": "Multiline text"}, {"name": "url", "description": "URL"}, ], } @@ -949,6 +955,7 @@ async def test_set_column_type_ui_data_includes_applicable_types( "options": [ {"name": "email", "description": "Email address"}, {"name": "json", "description": "JSON data"}, + {"name": "textarea", "description": "Multiline text"}, {"name": "url", "description": "URL"}, ], } diff --git a/tests/test_table_html.py b/tests/test_table_html.py index 699c1c1c..c0af996f 100644 --- a/tests/test_table_html.py +++ b/tests/test_table_html.py @@ -942,6 +942,7 @@ async def test_table_insert_action_button_and_data(): "permissions": { "insert-row": {"id": "root"}, }, + "column_types": {"body": "textarea"}, }, }, }, @@ -993,6 +994,7 @@ async def test_table_insert_action_button_and_data(): assert created["default"] == "datetime('now')" assert created["has_default"] assert body["value_type"] == "string" + assert body["column_type"] == {"type": "textarea", "config": None} finally: ds.close()