Compare commits

...

12 commits

Author SHA1 Message Date
Simon Willison
677e040979 --cors for /name.db downloads, refs #1057 2020-10-27 13:39:07 -07:00
Simon Willison
a7b2aabd51 Fixed broken footer test 2020-10-27 12:34:35 -07:00
Simon Willison
114e0be826 No underline on nav links in header 2020-10-27 12:30:40 -07:00
Simon Willison
0b786b023b Off-white yellow is now off-white blue 2020-10-27 12:28:50 -07:00
Simon Willison
a32216c197 Mobile view cards now have rounded corners 2020-10-27 12:27:14 -07:00
Simon Willison
b667503944 word-break: break-word; 2020-10-27 12:20:28 -07:00
Simon Willison
bd80c43b39 White cards on mobile 2020-10-27 12:07:25 -07:00
Simon Willison
083d9e41a0 Tighten up table column CSS 2020-10-27 12:01:44 -07:00
Simon Willison
70566769f1 Styled facets with different bullets 2020-10-27 11:57:34 -07:00
Natalie Downe
9f1c844e73 Implemented new Natalie design 2020-10-27 11:49:20 -07:00
Natalie Downe
ae9cffaf5b Visited link colours 2020-10-27 11:49:18 -07:00
Natalie Downe
18b2061cff New header and footer 2020-10-27 11:48:30 -07:00
10 changed files with 665 additions and 200 deletions

View file

@ -1,3 +1,69 @@
/* Reset and Page Setup ==================================================== */
/* Reset from http://meyerweb.com/eric/tools/css/reset/
v2.0 | 20110126
License: none (public domain)
*/
html, body, div, span, applet, object, iframe,
h1, h2, h3, h4, h5, h6, p, blockquote, pre,
a, abbr, acronym, address, big, cite, code,
del, dfn, em, img, ins, kbd, q, s, samp,
small, strike, strong, sub, sup, tt, var,
b, u, i, center,
dl, dt, dd, ol, ul, li,
fieldset, form, label, legend,
table, caption, tbody, tfoot, thead, tr, th, td,
article, aside, canvas, details, embed,
figure, figcaption, footer, header, hgroup,
menu, nav, output, ruby, section, summary,
time, mark, audio, video {
margin: 0;
padding: 0;
border: 0;
font-size: 100%;
font: inherit;
vertical-align: baseline;
}
/* HTML5 display-role reset for older browsers */
article, aside, details, figcaption, figure,
footer, header, hgroup, menu, nav, section {
display: block;
}
body {
line-height: 1;
}
ol,
ul {
list-style: none;
}
blockquote,
q {
quotes: none;
}
blockquote:before,
blockquote:after,
q:before,
q:after {
content: '';
content: none;
}
table {
border-collapse: collapse;
border-spacing: 0;
}
th {
padding-right: 1em;
white-space: nowrap;
}
strong {
font-weight: bold;
}
em {
font-style: italic;
}
/* end reset */
body {
margin: 0;
padding: 0;
@ -5,12 +71,294 @@ body {
font-size: 1rem;
font-weight: 400;
line-height: 1.5;
color: #212529;
color: #111A35;
text-align: left;
background-color: #fff;
background-color: #F8FAFB;
}
.bd {
margin: 0 1em;
/* Helper Styles ===========================================================*/
.intro {
font-size: 1rem;
}
p {
margin: 0 0 0.75rem 0;
padding: 0;
}
.meta {
color: rgba(0,0,0,0.3);
font-size: 0.75rem
}
.intro {
font-size: 1.5rem;
margin-bottom: 0.75rem;
}
.context-text {
/* for accessibility and hiden from sight */
text-indent: -999em;
display: block;
width:0;
overflow: hidden;
margin: 0;
padding: 0;
line-height: 0;
}
h1,
h2,
h3,
h4,
h5,
h6,
.header1,
.header2,
.header3,
.header4,
.header5,
.header6 {
font-weight: 700;
font-size: 1rem;
margin: 0;
padding: 0;
word-break: break-word;
}
h1,
.header1 {
font-size: 2rem;
margin-bottom: 0.75rem;
margin-top: 1rem;
}
h2,
.header2 {
font-size: 1.5rem;
margin-bottom: 0.75rem;
margin-top: 1rem;
}
h3,
.header3 {
font-size: 1.25rem;
margin: 1rem 0 0.25rem 0;
}
h4,
.header4 {
margin: 1rem 0 0.25rem 0;
font-weight: 400;
text-decoration: underline;
}
h5,
.header5 {
margin: 1rem 0 0.25rem 0;
font-weight: 700;
text-decoration: underline;
}
h6,
.header6 {
margin: 1rem 0 0.25rem 0;
font-weight: 400;
font-style: italic;
text-decoration: underline;
}
div,
section,
article,
header,
nav,
footer,
.wrapper {
display: block;
box-sizing: border-box;
}
a:link {
color: #276890;
text-decoration: underline;
}
a:visited {
color: #54AC8E;
text-decoration: underline;
}
a:hover,
a:focus,
a:active {
color: #67C98D;
text-decoration: underline;
}
button.button-as-link {
background: none;
border: none;
padding: 0;
color: #276890;
text-decoration: underline;
cursor: pointer;
font-size: 1rem;
}
button.button-as-link:hover,
button.button-as-link:focus {
color: #67C98D;
}
a img {
display: block;
max-width: 100%;
border: 0;
}
code,
pre {
font-family: monospace;
}
ul.bullets,
ul.tight-bullets,
ul.spaced,
ol.spaced {
margin-bottom: 0.8rem;
}
ul.bullets,
ul.tight-bullets {
padding-left: 1.25rem;
}
ul.bullets li,
ul.spaced li,
ol.spaced li {
margin-bottom: 0.4rem;
}
ul.bullets li {
list-style-type: circle;
}
ul.tight-bullets li {
list-style-type: disc;
margin-bottom: 0;
}
a.not-underlined {
text-decoration: none;
}
.not-underlined .underlined {
text-decoration: underline;
}
/* Page Furniture ========================================================= */
/* Header */
header,
footer {
padding: 0.6rem 1rem 0.5rem 1rem;
background-color: #276890;
color: rgba(255,255,244,0.9);
overflow: hidden;
box-sizing: border-box;
min-height: 2.6rem;
}
header p,
footer p {
margin: 0;
padding: 0;
}
header .crumbs {
float: left;
}
header .logout {
float: right;
text-align: right;
padding-left: 1rem;
}
header .logout form {
display: inline;
}
footer a:link,
footer a:visited,
footer a:hover,
footer a:focus,
footer a:active,
footer button.button-as-link {
color: rgba(255,255,244,0.8);
}
header a:link,
header a:visited,
header a:hover,
header a:focus,
header a:active,
header button.button-as-link {
color: rgba(255,255,244,0.8);
text-decoration: none;
}
footer a:hover,
footer a:focus,
footer a:active,
footer.button-as-link:hover,
footer.button-as-link:focus,
header a:hover,
header a:focus,
header a:active,
button.button-as-link:hover,
button.button-as-link:focus {
color: rgba(255,255,244,1);
}
/* Body */
section.content {
margin: 0 1rem;
}
/* Footer */
footer {
margin-top: 1rem;
}
/* Components ============================================================== */
h2 em {
font-style: normal;
font-weight: lighter;
}
/* Messages */
.message-info,
.message-warning,
.message-error {
padding: 1rem;
margin-bottom: 1rem;
background-color: rgba(103,201,141,0.3);
}
.message-warning {
background-color: rgba(245,166,35,0.3);
}
.message-error {
background-color: rgba(208,2,27,0.3);
}
.pattern-heading {
padding: 1rem;
margin-top: 2rem;
border-top: 1px solid rgba(208,2,27,0.8);
border-bottom: 1px solid rgba(208,2,27,0.8);
background-color: rgba(208,2,27,0.2)
}
/* URL arguments */
.extra-wheres ul,
.extra-wheres li {
list-style-type: none;
padding: 0;
margin: 0;
}
.wrapped-sql {
white-space: pre-wrap;
margin: 1rem 0;
font-family: monospace;
}
/* Tables ================================================================== */
.table-wrapper {
overflow-x: auto;
}
table {
border-collapse: collapse;
@ -32,100 +380,19 @@ td em {
}
th {
padding-right: 1em;
white-space: nowrap;
}
table a:link {
text-decoration: none;
color: #445ac8;
}
table a:visited {
color: #8f54c4;
}
.table-wrapper {
overflow-x: auto;
}
.small-screen-only,
.select-wrapper.small-screen-only {
display: none;
}
@media only screen and (max-width: 576px) {
.small-screen-only {
display: initial;
}
/* Force table to not be like tables anymore */
table.rows-and-columns,
.rows-and-columns thead,
.rows-and-columns tbody,
.rows-and-columns th,
.rows-and-columns td,
.rows-and-columns tr {
display: block;
}
/* Hide table headers (but not display: none;, for accessibility) */
.rows-and-columns thead tr {
position: absolute;
top: -9999px;
left: -9999px;
}
.rows-and-columns tr {
border: 1px solid #ccc;
margin-bottom: 1em;
}
.rows-and-columns td {
/* Behave like a "row" */
border: none;
border-bottom: 1px solid #eee;
padding: 0;
padding-left: 10%;
}
.rows-and-columns td:before {
display: block;
color: black;
margin-left: -10%;
font-size: 0.8em;
}
a.blob-download {
display: inline-block;
}
}
.hd {
border-bottom: 2px solid #ccc;
padding: 0.2em 1em;
background-color: #eee;
overflow: hidden;
box-sizing: border-box;
min-height: 2rem;
}
.hd p {
margin: 0;
padding: 0;
}
.hd .crumbs {
float: left;
}
.hd .logout {
float: right;
text-align: right;
padding-left: 1em;
}
.hd .logout form {
display: inline;
}
.ft {
margin: 1em 0;
padding: 0.5em 1em 0 1em;
border-top: 1px solid #ccc;
.rows-and-columns td:before {
display: block;
color: black;
margin-left: -10%;
font-size: 0.8em;
}
.hd :link {
text-decoration: none;
a.blob-download {
display: inline-block;
}
.db-table p {
margin-top: 0;
margin-bottom: 0.3em;
@ -135,15 +402,10 @@ table a:visited {
margin-bottom: 0;
}
h2 em {
font-style: normal;
font-weight: lighter;
}
.extra-wheres ul, .extra-wheres li {
list-style-type: none;
padding: 0;
margin: 0;
}
/* Forms =================================================================== */
form.sql textarea {
border: 1px solid #ccc;
width: 70%;
@ -189,11 +451,7 @@ input[type="search"]::-webkit-search-results-button,
input[type="search"]::-webkit-search-results-decoration {
display: none;
}
@media only screen and (max-width: 576px) {
form.sql textarea {
width: 95%;
}
}
form input[type=submit], form button[type=button] {
font-weight: 400;
cursor: pointer;
@ -292,27 +550,9 @@ form button[type=button] {
font-size: 1em;
font-family: Helvetica, sans-serif;
}
@media only screen and (max-width: 576px) {
.select-wrapper.small-screen-only {
display: inline-block;
}
.select-wrapper {
width: 100px;
}
.select-wrapper.filter-op {
width: 60px;
}
.filters input.filter-value {
width: 140px;
}
}
a.not-underlined {
text-decoration: none;
}
.not-underlined .underlined {
text-decoration: underline;
}
.facet-results {
display: flex;
@ -345,6 +585,7 @@ a.not-underlined {
width: auto;
display: inline-block;
box-shadow: 1px 2px 8px 2px rgba(0,0,0,0.08);
background-color: white;
}
.download-sqlite em {
@ -352,9 +593,7 @@ a.not-underlined {
font-size: 0.8em;
}
pre.wrapped-sql {
white-space: pre-wrap;
}
p.zero-results {
border: 2px solid #ccc;
@ -368,30 +607,78 @@ p.zero-results {
color: #666;
}
.message-info {
padding: 1em;
border: 1px solid green;
background-color: #c7fbc7;
}
.message-warning {
padding: 1em;
border: 1px solid #ae7100;
background-color: #fbdda5;
}
.message-error {
padding: 1em;
border: 1px solid red;
background-color: pink;
}
button.button-as-link {
background: none;
border: none;
padding: 0;
color: blue;
text-decoration: none;
cursor: pointer;
font-size: 1em;
/* Overrides ===============================================================*/
.small-screen-only,
.select-wrapper.small-screen-only {
display: none;
}
@media only screen and (max-width: 576px) {
.small-screen-only {
display: initial;
}
.select-wrapper.small-screen-only {
display: inline-block;
}
form.sql textarea {
width: 95%;
}
/* Force table to not be like tables anymore */
table.rows-and-columns,
.rows-and-columns thead,
.rows-and-columns tbody,
.rows-and-columns th,
.rows-and-columns td,
.rows-and-columns tr {
display: block;
}
/* Hide table headers (but not display: none;, for accessibility) */
.rows-and-columns thead tr {
position: absolute;
top: -9999px;
left: -9999px;
}
.rows-and-columns tr {
border: 1px solid #ccc;
margin-bottom: 1em;
border-radius: 10px;
background-color: white;
padding: 0.2rem;
}
.rows-and-columns td {
/* Behave like a "row" */
border: none;
border-bottom: 1px solid #eee;
padding: 0;
padding-left: 10%;
}
.rows-and-columns td:before {
display: block;
color: black;
margin-left: -10%;
font-size: 0.8em;
}
.select-wrapper {
width: 100px;
}
.select-wrapper.filter-op {
width: 60px;
}
.filters input.filter-value {
width: 140px;
}
}
svg.dropdown-menu-icon {

View file

@ -14,7 +14,7 @@
</head>
<body class="{% block body_class %}{% endblock %}">
<nav class="hd">{% block nav %}
<header><nav>{% block nav %}
{% if actor %}
<div class="logout">
<strong>{{ display_actor(actor) }}</strong>{% if show_logout %} &middot;
@ -24,9 +24,9 @@
</form>{% endif %}
</div>
{% endif %}
{% endblock %}</nav>
{% endblock %}</nav></header>
<div class="bd">
<section class="content">
{% block messages %}
{% if show_messages %}
{% for message, message_type in show_messages() %}
@ -37,9 +37,9 @@
{% block content %}
{% endblock %}
</div>
</section>
<div class="ft">{% block footer %}{% include "_footer.html" %}{% endblock %}</div>
<footer class="ft">{% block footer %}{% include "_footer.html" %}{% endblock %}</footer>
{% for body_script in body_scripts %}
<script>{{ body_script }}</script>

View file

@ -49,7 +49,7 @@
{% if views %}
<h2 id="views">Views</h2>
<ul>
<ul class="bullets">
{% for view in views %}
<li><a href="{{ urls.database(database) }}/{{ view.name|urlencode }}">{{ view.name }}</a>{% if view.private %} 🔒{% endif %}</li>
{% endfor %}
@ -58,7 +58,7 @@
{% if queries %}
<h2 id="queries">Queries</h2>
<ul>
<ul class="bullets">
{% for query in queries %}
<li><a href="{{ urls.query(database, query.name) }}{% if query.fragment %}#{{ query.fragment }}{% endif %}" title="{{ query.description or query.sql }}">{{ query.title or query.name }}</a>{% if query.private %} 🔒{% endif %}</li>
{% endfor %}

View file

@ -5,35 +5,63 @@
<link rel="stylesheet" href="{{ base_url }}-/static/app.css?{{ app_css_hash }}">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<meta name="robots" content="noindex">
<style>.pattern-heading { padding: 0.4em; border-top: 1px solid red; border-bottom: 1px solid red; background-color: pink }</style>
<style></style>
</head>
<body>
<nav class="hd"></nav>
<div class="bd">
<header>
</header>
<br>
<!-- .hd is now header -->
<header>
<nav></nav>
</header>
<!-- div.bd is now section.content -->
<section class="content">
<h1>Pattern Portfolio</h1>
</div>
<h2 class="pattern-heading">.hd for /database/table/row</h2>
<nav class="hd">
<p class="crumbs">
<a href="/">home</a> /
<a href="/fixtures">fixtures</a> /
<a href="/fixtures/attraction_characteristic">attraction_characteristic</a>
</p>
<div class="logout">
<strong>testuser</strong> &middot;
<form action="/-/logout" method="post">
<button class="button-as-link">Log out</button>
</form>
</div>
</nav>
<h2 class="pattern-heading">Messages</h2>
<div class="bd">
<p class="message-info">Example message</p>
<p class="message-warning">Example message</p>
<p class="message-error">Example message</p>
</div>
</section>
<h2 class="pattern-heading">Header for /database/table/row and Messages</h2>
<header>
<nav>
<p class="crumbs">
<a href="/">home</a> /
<a href="/fixtures">fixtures</a> /
<a href="/fixtures/attraction_characteristic">attraction_characteristic</a>
</p>
<div class="logout">
<strong>testuser</strong> &middot;
<form action="/-/logout" method="post">
<button class="button-as-link">Log out</button>
</form>
</div>
</nav>
</header>
<p class="message-info">Example message</p>
<p class="message-warning">Example message</p>
<p class="message-error">Example message</p>
<h2 class="pattern-heading">.bd for /</h2>
<div class="bd">
<section class="content">
<h1>Datasette Fixtures</h1>
<div class="metadata-description">
An example SQLite database demonstrating Datasette
@ -60,9 +88,16 @@
6 rows in 2 tables
</p>
<p><a href="/data/names" title="6 rows">names</a>, <a href="/data/foo">foo</a></p>
</div>
</section>
<h2 class="pattern-heading">.bd for /database</h2>
<div class="bd">
<section class="content">
<h1 style="padding-left: 10px; border-left: 10px solid #ff0000">fixtures</h1>
<div class="metadata-description">
Test tables description
@ -102,9 +137,15 @@
<p><em>pk, name</em></p>
<p>2 rows</p>
</div>
</div>
</section>
<h2 class="pattern-heading">.bd for /database/table</h2>
<div class="bd">
<section class="content">
<h1 style="padding-left: 10px; border-left: 10px solid #ff0000">roadside_attraction_characteristics</h1>
<p>
Data license:
@ -203,9 +244,109 @@
<label class="sort_by_desc small-screen-only"><input type="checkbox" name="_sort_by_desc"> descending</label>
<input type="submit" value="Apply">
</div>
</form>
<p><a class="not-underlined" title="select rowid, attraction_id, characteristic_id from roadside_attraction_characteristics where &#34;characteristic_id&#34; = :p0 order by rowid limit 101" href="/fixtures?sql=select+rowid%2C+attraction_id%2C+characteristic_id+from+roadside_attraction_characteristics+where+%22characteristic_id%22+%3D+%3Ap0+order+by+rowid+limit+101&amp;p0=2">&#x270e; <span class="underlined">View and edit SQL</span></a></p>
<p class="export-links">This data as <a href="/fixtures/roadside_attraction_characteristics.json?characteristic_id=2&amp;_labels=on">json</a>, <a href="/fixtures/roadside_attraction_characteristics.csv?characteristic_id=2&amp;_labels=on&amp;_size=max">CSV</a> (<a href="#export">advanced</a>)</p>
</form>
<div class="extra-wheres">
<h3>2 extra where clauses</h3>
<ul>
<li><code>planet_int=1</code> [<a href="/fixtures/facetable?_where=state%3D%27CA%27">remove</a>]</li>
<li><code>state='CA'</code> [<a href="/fixtures/facetable?_where=planet_int%3D1">remove</a>]</li>
</ul>
</div>
<p><a class="not-underlined" title="select rowid, attraction_id, characteristic_id from roadside_attraction_characteristics where &#34;characteristic_id&#34; = :p0 order by rowid limit 101" href="/fixtures?sql=select+rowid%2C+attraction_id%2C+characteristic_id+from+roadside_attraction_characteristics+where+%22characteristic_id%22+%3D+%3Ap0+order+by+rowid+limit+101&amp;p0=2">&#x270e; <span class="underlined">View and edit SQL</span></a></p>
<p class="export-links">This data as <a href="/fixtures/roadside_attraction_characteristics.json?characteristic_id=2&amp;_labels=on">json</a>, <a href="/fixtures/roadside_attraction_characteristics.csv?characteristic_id=2&amp;_labels=on&amp;_size=max">CSV</a> (<a href="#export">advanced</a>)</p>
<p class="suggested-facets">
Suggested facets: <a href="http://latest.datasette.io/fixtures/facetable?_where=planet_int%3D1&amp;_where=state%3D%27CA%27&amp;_facet=city_id&amp;_facet=created&amp;_facet=complex_array&amp;_facet=tags#facet-tags">tags</a>, <a href="http://latest.datasette.io/fixtures/facetable?_where=planet_int%3D1&amp;_where=state%3D%27CA%27&amp;_facet=city_id&amp;_facet=created&amp;_facet=complex_array&amp;_facet_date=created#facet-created">created</a> (date), <a href="http://latest.datasette.io/fixtures/facetable?_where=planet_int%3D1&amp;_where=state%3D%27CA%27&amp;_facet=city_id&amp;_facet=created&amp;_facet=complex_array&amp;_facet_array=tags#facet-tags">tags</a> (array)
</p>
<div class="facet-results">
<div class="facet-info facet-fixtures-facetable-tags" id="facet-tags">
<p class="facet-info-name">
<strong>tags (array)</strong>
<a href="/fixtures/facetable?_where=planet_int%3D1&amp;_where=state%3D%27CA%27&amp;_facet=city_id&amp;_facet=created" class="cross"></a>
</p>
<ul>
<li><a href="http://latest.datasette.io/fixtures/facetable?_where=planet_int%3D1&amp;_where=state%3D%27CA%27&amp;_facet=city_id&amp;_facet=created&amp;_facet_array=tags&amp;tags__arraycontains=tag1">tag1</a> 2</li>
<li><a href="http://latest.datasette.io/fixtures/facetable?_where=planet_int%3D1&amp;_where=state%3D%27CA%27&amp;_facet=city_id&amp;_facet=created&amp;_facet_array=tags&amp;tags__arraycontains=tag2">tag2</a> 1</li>
<li><a href="http://latest.datasette.io/fixtures/facetable?_where=planet_int%3D1&amp;_where=state%3D%27CA%27&amp;_facet=city_id&amp;_facet=created&amp;_facet_array=tags&amp;tags__arraycontains=tag3">tag3</a> 1</li>
</ul>
</div>
<div class="facet-info facet-fixtures-facetable-created" id="facet-created">
<p class="facet-info-name">
<strong>created</strong>
<a href="/fixtures/facetable?_where=planet_int%3D1&amp;_where=state%3D%27CA%27&amp;_facet=city_id&amp;_facet_array=tags" class="cross"></a>
</p>
<ul>
<li><a href="http://latest.datasette.io/fixtures/facetable?_where=planet_int%3D1&amp;_where=state%3D%27CA%27&amp;_facet=city_id&amp;_facet=created&amp;_facet_array=tags&amp;created=2019-01-14+08%3A00%3A00">2019-01-14 08:00:00</a> 4</li>
<li><a href="http://latest.datasette.io/fixtures/facetable?_where=planet_int%3D1&amp;_where=state%3D%27CA%27&amp;_facet=city_id&amp;_facet=created&amp;_facet_array=tags&amp;created=2019-01-15+08%3A00%3A00">2019-01-15 08:00:00</a> 4</li>
<li><a href="http://latest.datasette.io/fixtures/facetable?_where=planet_int%3D1&amp;_where=state%3D%27CA%27&amp;_facet=city_id&amp;_facet=created&amp;_facet_array=tags&amp;created=2019-01-16+08%3A00%3A00">2019-01-16 08:00:00</a> 2</li>
</ul>
</div>
<div class="facet-info facet-fixtures-facetable-city_id" id="facet-city_id">
<p class="facet-info-name">
<strong>city_id</strong>
<a href="/fixtures/facetable?_where=planet_int%3D1&amp;_where=state%3D%27CA%27&amp;_facet=created&amp;_facet_array=tags" class="cross"></a>
</p>
<ul>
<li><a href="http://latest.datasette.io/fixtures/facetable?_where=planet_int%3D1&amp;_where=state%3D%27CA%27&amp;_facet=city_id&amp;_facet=created&amp;_facet_array=tags&amp;city_id=1">San Francisco</a> 6</li>
<li><a href="http://latest.datasette.io/fixtures/facetable?_where=planet_int%3D1&amp;_where=state%3D%27CA%27&amp;_facet=city_id&amp;_facet=created&amp;_facet_array=tags&amp;city_id=2">Los Angeles</a> 4</li>
</ul>
</div>
</div>
<table class="rows-and-columns">
<thead>
<tr>
@ -266,9 +407,20 @@
attraction_id INTEGER REFERENCES roadside_attractions(pk),
characteristic_id INTEGER REFERENCES attraction_characteristic(pk)
);</pre>
</div>
</section>
<h2 class="pattern-heading">.bd for /database/table/row</h2>
<div class="bd">
<section class="content">
<h1 style="padding-left: 10px; border-left: 10px solid #ff0000">roadside_attractions: 2</h1>
<p>This data as <a href="/fixtures/roadside_attractions/2.json">json</a></p>
<table class="rows-and-columns">
@ -309,9 +461,21 @@
from attraction_id in roadside_attraction_characteristics
</li>
</ul>
</div>
</section>
<h2 class="pattern-heading">.ft</h2>
<div class="ft">Powered by <a href="https://github.com/simonw/datasette" title="Datasette v0+unknown">Datasette</a>
<footer class="ft">Powered by <a href="https://github.com/simonw/datasette" title="Datasette v0+unknown">Datasette</a>
&middot; Data license:
<a href="https://github.com/simonw/datasette/blob/master/LICENSE">Apache License 2.0</a>
&middot;
@ -322,6 +486,6 @@
About:
<a href="https://github.com/simonw/datasette">
About Datasette</a>
</div>
</footer>
</body>
</html>

View file

@ -8,9 +8,9 @@
<script src="{{ urls.static('table.js') }}" defer></script>
<style>
@media only screen and (max-width: 576px) {
{% for column in display_columns %}
{% for column in display_columns -%}
.rows-and-columns td:nth-of-type({{ loop.index }}):before { content: "{{ column.name|escape_css_string }}"; }
{% endfor %}
{% endfor %}}
</style>
{% endblock %}
@ -132,7 +132,7 @@
<a href="{{ facet_info.toggle_url }}" class="cross">&#x2716;</a>
{% endif %}
</p>
<ul>
<ul class="tight-bullets">
{% for facet_value in facet_info.results %}
{% if not facet_value.selected %}
<li><a href="{{ facet_value.toggle_url }}">{{ (facet_value.label if facet_value.label is not none else "_") }}</a> {{ "{:,}".format(facet_value.count) }}</li>

View file

@ -247,9 +247,9 @@ async def asgi_start(send, status, headers=None, content_type="text/plain"):
async def asgi_send_file(
send, filepath, filename=None, content_type=None, chunk_size=4096
send, filepath, filename=None, content_type=None, chunk_size=4096, headers=None
):
headers = {}
headers = headers or {}
if filename:
headers["content-disposition"] = 'attachment; filename="{}"'.format(filename)
first = True
@ -395,13 +395,22 @@ class Response:
class AsgiFileDownload:
def __init__(
self, filepath, filename=None, content_type="application/octet-stream"
self,
filepath,
filename=None,
content_type="application/octet-stream",
headers=None,
):
self.headers = headers or {}
self.filepath = filepath
self.filename = filename
self.content_type = content_type
async def asgi_send(self, send):
return await asgi_send_file(
send, self.filepath, filename=self.filename, content_type=self.content_type
send,
self.filepath,
filename=self.filename,
content_type=self.content_type,
headers=self.headers,
)

View file

@ -144,10 +144,14 @@ class DatabaseDownload(DataView):
if not db.path:
raise DatasetteError("Cannot download database", status=404)
filepath = db.path
headers = {}
if self.ds.cors:
headers["Access-Control-Allow-Origin"] = "*"
return AsgiFileDownload(
filepath,
filename=os.path.basename(filepath),
content_type="application/octet-stream",
headers=headers,
)

View file

@ -227,7 +227,7 @@ def app_client_with_dot():
@pytest.fixture(scope="session")
def app_client_with_cors():
with make_app_client(cors=True) as client:
with make_app_client(is_immutable=True, cors=True) as client:
yield client

View file

@ -1739,6 +1739,7 @@ def test_trace(app_client):
@pytest.mark.parametrize(
"path,status_code",
[
("/fixtures.db", 200),
("/fixtures.json", 200),
("/fixtures/no_primary_key.json", 200),
# A 400 invalid SQL query should still have the header:

View file

@ -1058,7 +1058,7 @@ def assert_querystring_equal(expected, actual):
def assert_footer_links(soup):
footer_links = soup.find("div", {"class": "ft"}).findAll("a")
footer_links = soup.find("footer").findAll("a")
assert 4 == len(footer_links)
datasette_link, license_link, source_link, about_link = footer_links
assert "Datasette" == datasette_link.text.strip()