Merge branch 'master' into dev

This commit is contained in:
Carson Gross 2023-08-17 10:39:59 -06:00
commit 7247e8abd7
38 changed files with 464 additions and 209 deletions

View File

@ -3,43 +3,55 @@
/** @type {import("../htmx").HtmxInternalApi} */
var api;
const targetAttrPrefix = 'hx-target-';
const targetAttrMinLen = targetAttrPrefix.length - 1;
var attrPrefix = 'hx-target-';
/**
* @param {HTMLElement} elt
* @param {number} respCode
* @returns {HTMLElement | null}
*/
function getRespCodeTarget(elt, respCode) {
if (!elt || !respCode) return null;
function getRespCodeTarget(elt, respCodeNumber) {
if (!elt || !respCodeNumber) return null;
var targetAttr = targetAttrPrefix + respCode;
var targetStr = api.getClosestAttributeValue(elt, targetAttr);
var respCode = respCodeNumber.toString();
if (targetStr) {
if (targetStr === "this") {
return api.findThisElement(elt, targetAttr);
} else {
return api.querySelectorExt(elt, targetStr);
}
} else {
for (let l = targetAttr.length - 1; l > targetAttrMinLen; l--) {
targetAttr = targetAttr.substring(0, l) + '*';
targetStr = api.getClosestAttributeValue(elt, targetAttr);
if (targetStr) break;
// '*' is the original syntax, as the obvious character for a wildcard.
// The 'x' alternative was added for maximum compatibility with HTML
// templating engines, due to ambiguity around which characters are
// supported in HTML attributes.
//
// Start with the most specific possible attribute and generalize from
// there.
var attrPossibilities = [
respCode,
respCode.substr(0, 2) + '*',
respCode.substr(0, 2) + 'x',
respCode.substr(0, 1) + '*',
respCode.substr(0, 1) + 'x',
respCode.substr(0, 1) + '**',
respCode.substr(0, 1) + 'xx',
'*',
'x',
'***',
'xxx',
];
for (var i = 0; i < attrPossibilities.length; i++) {
var attr = attrPrefix + attrPossibilities[i];
var attrValue = api.getClosestAttributeValue(elt, attr);
if (attrValue) {
if (attrValue === "this") {
return api.findThisElement(elt, attr);
} else {
return api.querySelectorExt(elt, attrValue);
}
}
}
if (targetStr) {
if (targetStr === "this") {
return api.findThisElement(elt, targetAttr);
} else {
return api.querySelectorExt(elt, targetStr);
}
} else {
return null;
}
return null;
}
/** @param {Event} evt */

View File

@ -263,4 +263,38 @@ describe("response-targets extension", function() {
htmx.config.responseTargetPrefersExisting = false;
}
});
describe('status code formatting', function()
{
var attributes = [
"hx-target-404",
"hx-target-40*",
"hx-target-40x",
"hx-target-4*",
"hx-target-4x",
"hx-target-4**",
"hx-target-4xx",
"hx-target-*",
"hx-target-x",
"hx-target-***",
"hx-target-xxx",
];
// String replacement because IE11 doesn't support template literals
var btnMarkup = '<button hx-ext="response-targets" HX_TARGET="#d1" hx-get="/test">Click Me!</button>';
// forEach because IE11 doesn't play nice with closures inside for loops
attributes.forEach(function(attribute) {
it('supports ' + attribute, function() {
this.server.respondWith("GET", "/test", [404, {}, "Not found!"]);
var btn = make(btnMarkup.replace("HX_TARGET", attribute));
var div1 = make('<div id="d1"></div>')
btn.click();
this.server.respond();
div1.innerHTML.should.equal("Not found!");
});
});
});
});

View File

@ -7,7 +7,8 @@ build_search_index = false
generate_feed = true
taxonomies = [
{ name = "tag", render = false, feed = true }
{ name = "tag", render = false, feed = true },
{ name = "author", render = false, feed = false }
]
[markdown]
@ -35,4 +36,4 @@ paths_keep_dates = true
# Tomorrow
# two-dark
# visual-studio-dark
# zenburn
# zenburn

View File

@ -73,6 +73,16 @@ Thank you to all our generous <a href="https://github.com/sponsors/bigskysoftwar
text-align: center;
padding: 16px;
}
@media only screen and (max-width: 760px) {
/* Force table to not be like tables anymore */
table, thead, tbody, th, td, tr {
display: block;
}
}
</style>
<div style="overflow-x: auto">
<table id="sponsor-table">
@ -81,11 +91,14 @@ Thank you to all our generous <a href="https://github.com/sponsors/bigskysoftwar
<a href="https://www.jetbrains.com//"><img src="/img/jetbrains.png" style="max-width:30%;min-width:200px;"></a>
</td>
<td>
<a href="https://github.blog/2023-04-12-github-accelerator-our-first-cohort-and-whats-next//"><img src="/img/Github_Logo.png" style="max-width:30%;min-width:200px;"></a>
<a href="https://www.nuclei.ai/"><img src="/img/nuclei_logo_with_text.svg" style="max-width:50%;min-width:200px;"></a>
</td>
<tr>
<td colspan="2">
<a href="https://www.commspace.co.za/"><img src="/img/commspace.svg" style="width:100%;max-width:500px"></a>
<td>
<a href="https://github.blog/2023-04-12-github-accelerator-our-first-cohort-and-whats-next//"><img src="/img/Github_Logo.png" style="max-width:30%;min-width:200px;"></a>
</td>
<td>
<a href="https://www.commspace.co.za/"><img src="/img/commspace.svg" style="width:100%;max-width:600px"></a>
</td>
</tr>
<tr>
@ -98,16 +111,6 @@ Thank you to all our generous <a href="https://github.com/sponsors/bigskysoftwar
</a>
</td>
</tr>
<tr>
<td>
<a href="https://www.peakcrypto.com/">
<img alt="Peak Crypto" src="/img/peakcrypto.png" style="width:100%;max-width:65px">
</a>
</td>
<td>
<a href="https://bigsky.software"><img src="/img/bss.png" style="width:100%;max-width:150px"></a>
</td>
</tr>
</table>
</div>

View File

@ -7,11 +7,10 @@ The `hx-on` attribute allows you to embed scripts inline to respond to events di
`hx-on` improves upon `onevent` by enabling the handling of any event for enhanced [Locality of Behaviour (LoB)](/essays/locality-of-behaviour/). This also enables you to handle any htmx event.
There are two forms of this attribute, one in which you specify the event as part of the attribute name
after a colon (`hx-on:click`, for example), and one that uses the `hx-on` attribute directly. The
latter form should only be used if IE11 support is required.
after a colon (`hx-on:click`, for example), and a deprecated form that uses the `hx-on` attribute directly. The
latter should only be used if IE11 support is required.
### Forms
#### hx-on:* (recommended)
### hx-on:* (recommended)
The event name follows a colon `:` in the attribute, and the attribute value is the script to be executed:
```html
@ -40,22 +39,16 @@ events, and omit the "htmx" part:
Adding multiple handlers is easy, you just specify additional attributes:
```html
<button hx-get="/info"
hx-on::before-request="alert('Making a request!'")
hx-on::before-request="alert('Making a request!')"
hx-on::after-request="alert('Done making a request!')">
Get Info!
</button>
```
#### hx-on (deprecated, except for IE11 support)
### hx-on (deprecated)
The value is an event name, followed by a colon `:`, followed by the script:
```html
<div hx-on="click: alert('Clicked!')">Click</div>
```
And htmx events:
```html
<button hx-get="/info" hx-on="htmx:beforeRequest: alert('Making a request!')">
Get Info!

View File

@ -24,6 +24,23 @@ This button will issue a `GET` to `/info` and then select the element with the i
which will replace the entire button in the DOM, and, in addition, pick out an element with the id `alert`
in the response and swap it in for div in the DOM with the same ID.
Each value in the comma separated list of values can specify any valid [`hx-swap`](@/attributes/hx-swap.md)
strategy by separating the selector and the swap strategy with a `:`.
For example, to prepend the alert content instead of replacing it:
```html
<div>
<div id="alert"></div>
<button hx-get="/info"
hx-select="#info-details"
hx-swap="outerHTML"
hx-select-oob="#alert:afterbegin">
Get Info!
</button>
</div>
```
## Notes
* `hx-select-oob` is inherited and can be placed on a parent element

View File

@ -3,6 +3,7 @@ title = "10 Tips For Building SSR/HDA applications"
date = 2022-06-13
updated = 2023-06-13
[taxonomies]
author = ["Carson Gross"]
tag = ["posts"]
+++
@ -156,4 +157,4 @@ developer.
Hopefully these tips help you adopt hypermedia and server-side rendering as a tool more effectively and smoothly. It
isn't a perfect client-server architecture, and it involves explicit tradeoffs, but it can be extremely effective for
many web applications (far more than most web developers today suspect) and provides a much simpler overall development
experience in those cases.
experience in those cases.

View File

@ -1,6 +1,7 @@
+++
title = "Essays"
insert_anchor_links = "left"
page_template = "essay.html"
+++
### Hypermedia and REST

View File

@ -3,6 +3,7 @@ title = "A Real World React -> htmx Port"
date = 2022-09-29
updated = 2022-10-15
[taxonomies]
author = ["Carson Gross"]
tag = ["posts"]
+++

View File

@ -3,6 +3,7 @@ title = "A Response To &quot;Have Single-Page Apps Ruined the Web?&quot;"
date = 2021-12-24
updated = 2022-05-27
[taxonomies]
author = ["Carson Gross"]
tag = ["posts"]
+++

View File

@ -3,6 +3,7 @@ title = "Architectural Sympathy"
date = 2023-04-06
updated = 2023-04-06
[taxonomies]
author = ["Carson Gross"]
+++
@ -90,4 +91,4 @@ way, but these typically did not sacrifice the conceptual coherence of the whole
Adopting an architecturally sympathetic mindset in software development often means sacrificing how you would like to
do things in favor of how an original piece of software did things. While this constraint can chafe at times, it can
also produce well crafted software that is harmonious and that dovetails well with existing software.
also produce well crafted software that is harmonious and that dovetails well with existing software.

View File

@ -3,6 +3,7 @@ title = "Complexity Budget"
date = 2020-10-29
updated = 2022-02-06
[taxonomies]
author = ["Carson Gross"]
tag = ["posts"]
+++

View File

@ -3,16 +3,18 @@ title = "HATEOAS"
date = 2021-10-16
updated = 2022-02-06
[taxonomies]
author = ["Carson Gross"]
tag = ["posts"]
[extra]
show_title = false
show_author = false
+++
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Lexend+Zetta:wght@900&display=swap&text=HATEOAS" rel="stylesheet">
<link href="https://fonts.googleapis.com/css2?family=Lexend+Zetta:wght@900&display=swap" rel="stylesheet">
<link href="https://fonts.googleapis.com/css2?family=Source+Serif+Pro:ital,wght@0,400;0,600;0,700;1,400;1,700&display=swap" rel="stylesheet">
<link href="https://fonts.googleapis.com/css2?family=Lexend+Zetta:wght@900&display=swap" rel="stylesheet">
<link href="https://fonts.googleapis.com/css2?family=Source+Serif+Pro:ital,wght@0,400;0,600;0,700;1,400;1,700&display=swap" rel="stylesheet">
<h1>HATEOAS</h1>
@ -31,13 +33,13 @@ Hypermedia as the Engine of Application State (HATEOAS) is a constraint of the [
With HATEOAS, a client interacts with a network application whose application servers provide information dynamically through [*hypermedia*](https://en.wikipedia.org/wiki/Hypermedia). A REST client needs little to no prior knowledge about how to interact with an application or server beyond a generic understanding of hypermedia.
By contrast, today JSON-based web clients typically interact through a fixed interface shared through documentation via a tool
such as [swagger](https://swagger.io/).
such as [swagger](https://swagger.io/).
The restrictions imposed by HATEOAS decouples client and server. This enables server functionality to evolve independently.
## Example
A user-agent that implements HTTP makes a HTTP request of a REST end point through a simple URL. All subsequent requests the user-agent may make are discovered within the hypermedia responses to each request. The media types used for these representations, and the link relations they may contain, are standardized. The client transitions through application states by selecting from links within a hypermedia representation or by manipulating the representation in other ways afforded by its media type.
A user-agent that implements HTTP makes a HTTP request of a REST end point through a simple URL. All subsequent requests the user-agent may make are discovered within the hypermedia responses to each request. The media types used for these representations, and the link relations they may contain, are standardized. The client transitions through application states by selecting from links within a hypermedia representation or by manipulating the representation in other ways afforded by its media type.
In this way, RESTful interaction is driven by hypermedia, rather than out-of-band information.
@ -89,10 +91,10 @@ Only one link is available: to deposit more money. In the accounts current overd
this fact is reflected internally in *the hypermedia*. The web browser does not know about the concept of an overdrawn account or,
indeed, even what an account is. It simply knows how to present hypermedia representations to a user.
Hence we have the notion of the Hypermedia being the Engine of Application State. What actions are possible varies as the
Hence we have the notion of the Hypermedia being the Engine of Application State. What actions are possible varies as the
state of the resource varies and this information is encoded in the hypermedia.
Contrast the HTML response above with a typical JSON API which, instead, might return a representation of the account with a
Contrast the HTML response above with a typical JSON API which, instead, might return a representation of the account with a
status field:
```json
@ -111,8 +113,8 @@ HTTP/1.1 200 OK
```
Here we can see that the client must know specifically what the value of the `status` field means and how it might affect
the rendering of a user interface, and what actions can be taken with it. The client must also know what URLs must be used
for manipulation of this resource since they are not encoded in the response. This would typically be achieved by
the rendering of a user interface, and what actions can be taken with it. The client must also know what URLs must be used
for manipulation of this resource since they are not encoded in the response. This would typically be achieved by
consulting documentation for the JSON API.
It is this requirement of out-of-band information that distinguishes this JSON API from a RESTful API that implements
@ -164,13 +166,13 @@ HTTP/1.1 200 OK
Here, the "hypermedia controls" are encoded in a `links` property on the account object.
Unfortunately, the client of this API still needs to know quite a bit of additional information:
Unfortunately, the client of this API still needs to know quite a bit of additional information:
* What http methods can be used against these URLs?
* Can it issue a `GET` to these URLs in order to get a representation of the mutation in question?
* If it can `POST` to a given URL, what values are expected?
Compare the above JSON with the following HTTP response, retrieved by a browser after a user has clicked on the
Compare the above JSON with the following HTTP response, retrieved by a browser after a user has clicked on the
link to `/accounts/12345/deposits` found in the first HTML example:
```html
@ -187,7 +189,7 @@ HTTP/1.1 200 OK
```
Note that this HTML response encodes all the information necessary to update the account balance, providing a `form` with a `method`
and `action` attribute, as well as the inputs necessary for updating the resource correctly.
and `action` attribute, as well as the inputs necessary for updating the resource correctly.
The JSON representation does not have the same self-contained "uniform interface" as the HTML representation does.

View File

@ -1,14 +1,12 @@
+++
title = "How Did REST Come To Mean The Opposite of REST?"
author = "Carson Gross <carson@bigsky.software>"
date = 2022-07-18
updated = 2022-11-26
[taxonomies]
author = ["Carson Gross"]
tag = ["posts"]
+++
<h4>18 July 2022</h4>
<style>
pre {
margin: 32px !important;
@ -20,51 +18,51 @@ tag = ["posts"]
<img src="/img/tap-the-sign.png" alt="You are wrong" style="width: 80%;margin-left:10%; margin-top: 16px;margin-bottom: 16px">
> I am getting frustrated by the number of people calling any HTTP-based interface a REST API. Todays example is the
> SocialSite REST API. That is RPC. It screams RPC. There is so much coupling on display that it should be given an
> SocialSite REST API. That is RPC. It screams RPC. There is so much coupling on display that it should be given an
> X rating.
>
> What needs to be done to make the REST architectural style clear on the notion that hypertext is a constraint? In
> other words, if the engine of application state (and hence the API) is not being driven by hypertext, then it cannot
>
> What needs to be done to make the REST architectural style clear on the notion that hypertext is a constraint? In
> other words, if the engine of application state (and hence the API) is not being driven by hypertext, then it cannot
> be RESTful and cannot be a REST API. Period. Is there some broken manual somewhere that needs to be fixed?
>
>
> _--Roy Fielding, Creator of the term REST_
>
>
> _&nbsp;&nbsp;&nbsp;[REST APIs must be hypertext-driven](https://roy.gbiv.com/untangled/2008/rest-apis-must-be-hypertext-driven)_
[REST](https://en.wikipedia.org/wiki/Representational_state_transfer) must be the most broadly misused technical term
in computer programming history.
[REST](https://en.wikipedia.org/wiki/Representational_state_transfer) must be the most broadly misused technical term
in computer programming history.
I can't think of anything else that comes close.
I can't think of anything else that comes close.
Today, when someone uses the term REST, they are nearly always discussing a JSON-based API using HTTP.
When you see a job post mentioning REST or a company discussing [REST Guidelines](https://github.com/microsoft/api-guidelines/blob/vNext/Guidelines.md)
When you see a job post mentioning REST or a company discussing [REST Guidelines](https://github.com/microsoft/api-guidelines/blob/vNext/Guidelines.md)
they will rarely mention either hypertext or hypermedia: they will instead mention JSON, GraphQL(!) and the like.
Only a few obstinate folks grumble: but these JSON APIs aren't RESTful!
Only a few obstinate folks grumble: but these JSON APIs aren't RESTful!
<iframe src="https://www.youtube.com/embed/HOK6mE7sdvs" title="Doesn't anyone notice this?"
<iframe src="https://www.youtube.com/embed/HOK6mE7sdvs" title="Doesn't anyone notice this?"
frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope"
style="width: 400px;height:300px;margin-left:15%;margin-top: 16px;margin-bottom: 16px">
</iframe>
In this post, I'd like to give you a [brief, incomplete and mostly wrong](https://james-iry.blogspot.com/2009/05/brief-incomplete-and-mostly-wrong.html)
In this post, I'd like to give you a [brief, incomplete and mostly wrong](https://james-iry.blogspot.com/2009/05/brief-incomplete-and-mostly-wrong.html)
history of REST, and how we got to a place where its meaning has been nearly perfectly inverted to mean what REST was
original contrasted with: RPC.
## Where Did REST Come From?
The term REST, short for REpresentational State Transfer, came from
The term REST, short for REpresentational State Transfer, came from
[Chapter 5 of Fielding's PhD Dissertation](https://www.ics.uci.edu/~fielding/pubs/dissertation/rest_arch_style.htm).
Fielding was describing the network architecture of the (then new) world wide web, and contrasting it with other possible
network architectures, particularly RPC-style network architectures.
It is important to understand that, at the time of his writing (1999-2000), there were no JSON APIs: he was describing
the web as it existed at that time, with HTML being exchanged over HTTP as people "surfed the web". JSON hadn't been
It is important to understand that, at the time of his writing (1999-2000), there were no JSON APIs: he was describing
the web as it existed at that time, with HTML being exchanged over HTTP as people "surfed the web". JSON hadn't been
created yet, and broad adoption of JSON was a decade off.
REST described a _network architecture_, and it was defined in terms of _constraints_ on an API, constraints that
REST described a _network architecture_, and it was defined in terms of _constraints_ on an API, constraints that
needed to be met in order to be considered a RESTful API. The language is academic, which has contributed to the
confusion around the topic, but it is clear enough that most developers should be able to understand it.
@ -74,7 +72,7 @@ REST has many constraints and concepts within it, but there is one crucial idea
most distinguishing characteristic of REST, when contrasted with other possible network architectures.
This is known as the [uniform interface constraint](https://en.wikipedia.org/wiki/Representational_state_transfer#Uniform_interface),
and more specifically within that concept, the idea of [Hypermedia As The Engine of Application State (HATEOAS)](https://htmx.org/essays/hateoas/)
and more specifically within that concept, the idea of [Hypermedia As The Engine of Application State (HATEOAS)](https://htmx.org/essays/hateoas/)
or as Fielding prefers to call it, the hypermedia constraint.
In order to understand this uniform interface constraint, lets consider two HTTP responses returning information about a
@ -114,32 +112,32 @@ HTTP/1.1 200 OK
}
```
The crucial difference between these two responses, and why the _HTML response_ is RESTful, but the
The crucial difference between these two responses, and why the _HTML response_ is RESTful, but the
_JSON response_ is not, is this:
<p style="margin:32px;text-align: center;font-weight: bold">The HTML response is entirely self-describing.</p>
A proper hypermedia client that receives this response does not know what a bank account is, what a
balance is, etc. It simply knows how to render a hypermedia, HTML.
A proper hypermedia client that receives this response does not know what a bank account is, what a
balance is, etc. It simply knows how to render a hypermedia, HTML.
The client knows nothing about the API end points associated with this data, except via URLs and hypermedia controls
The client knows nothing about the API end points associated with this data, except via URLs and hypermedia controls
(links and forms) discoverable within the HTML itself. If the state of the resource changes such that the allowable
actions available on that resource change (for example, if the account goes into overdraft) then the HTML response would
change to show the new set of actions available.
actions available on that resource change (for example, if the account goes into overdraft) then the HTML response would
change to show the new set of actions available.
The client would render this new HTML, totally unaware of what "overdraft" means or, indeed, even what a bank account is.
The client would render this new HTML, totally unaware of what "overdraft" means or, indeed, even what a bank account is.
It is in this manner that hypertext is the engine of application state: the HTML response "carries along" all the API
information necessary to continue interacting with the system directly within itself.
Now, contrast that with the second JSON response.
Now, contrast that with the second JSON response.
In this case the message is _not_ self describing. Rather, the client must know how to interpret the `status` field to
display an appropriate user interface. Further, the client must know what actions are available on the account based on
"out-of-band" information, that is, information on the URLs, parameters and so forth, derived from another source of
information _outside of the response_, such as swagger API documentation.
display an appropriate user interface. Further, the client must know what actions are available on the account based on
"out-of-band" information, that is, information on the URLs, parameters and so forth, derived from another source of
information _outside of the response_, such as swagger API documentation.
The JSON response is not self-describing and does not encode the state of the resource within a hypermedia. It therefore
The JSON response is not self-describing and does not encode the state of the resource within a hypermedia. It therefore
fails the uniform interface constraint of REST, and, thus, is not RESTful.
### Inventor: RESTful APIs Must Be Hypermedia Driven
@ -147,18 +145,18 @@ fails the uniform interface constraint of REST, and, thus, is not RESTful.
In [Rest APIs Must Be Hypermedia Driven](https://roy.gbiv.com/untangled/2008/rest-apis-must-be-hypertext-driven), Fielding
goes on to say:
> A REST API should be entered with no prior knowledge beyond the initial URI (bookmark) and set of standardized media
> A REST API should be entered with no prior knowledge beyond the initial URI (bookmark) and set of standardized media
> types that are appropriate for the intended audience (i.e., expected to be understood by any client that might use the
> API). From that point on, all application state transitions must be driven by client selection of server-provided
> API). From that point on, all application state transitions must be driven by client selection of server-provided
> choices that are present in the received representations or implied by the users manipulation of those representations.
So, in a RESTful system, you should be able to enter the system through a single URL and, from that point on, all navigation
and actions taken within the system should be entirely provided through self-describing hypermedia: through links and
forms in HTML, for example. Beyond the entry point, in a proper RESTful system, the API client shouldn't need any
and actions taken within the system should be entirely provided through self-describing hypermedia: through links and
forms in HTML, for example. Beyond the entry point, in a proper RESTful system, the API client shouldn't need any
additional information about your API.
This is the source of the incredible flexibility of RESTful systems: since all responses are self describing and
encode all the currently available actions available there is no need to worry about, for example, versioning your API!
encode all the currently available actions available there is no need to worry about, for example, versioning your API!
In fact, you don't even need to document it!
If things change, the hypermedia responses change, and that's it.
@ -167,7 +165,7 @@ It's an incredibly flexible and innovative concept for building distributed syst
### Industry: Lol, No, RESTful APIs Are JSON
Today, most web developers and most companies would call the _second example_ a RESTful API.
Today, most web developers and most companies would call the _second example_ a RESTful API.
They probably wouldn't even regard the first response _as an API response_. It's just HTML!
@ -177,17 +175,17 @@ APIs are always JSON or maybe, if you are fancy, something like Protobuf, right?
<img src="/img/you-are-wrong.png" alt="You are wrong" style="width: 80%;margin-left:10%; margin-top: 16px;margin-bottom: 16px">
Wrong.
Wrong.
You are all wrong and you should feel bad.
The first response _is_ an API response, and, in fact, the one that is RESTful!
The second response is, in fact, a _Remote Procedure Call_ (RPC) style of API. The client and the server are coupled,
just like the SocialSite API Fielding complained about back in 2008: a client needs to have additional knowledge about
the resource it is working with that must be derived from some other source beyond the JSON response itself.
The second response is, in fact, a _Remote Procedure Call_ (RPC) style of API. The client and the server are coupled,
just like the SocialSite API Fielding complained about back in 2008: a client needs to have additional knowledge about
the resource it is working with that must be derived from some other source beyond the JSON response itself.
This API is, in spirit, nearly the opposite of REST.
This API is, in spirit, nearly the opposite of REST.
Let's call this style of API "RESTless".
@ -201,14 +199,14 @@ It's a funny story:
Roy Fielding published his dissertation in 2000.
Around the same time, [XML-RPC](https://en.wikipedia.org/wiki/XML-RPC), an explicitly RPC-inspired protocol was released
and started to gather steam as a method to build APIs using HTTP. XML-RPC was part of a larger project called
and started to gather steam as a method to build APIs using HTTP. XML-RPC was part of a larger project called
[SOAP](https://en.wikipedia.org/wiki/SOAP), from Microsoft. XML-RPC came out of a long tradition of RPC-style
protocols, mainly from the enterprise world, with a lot of static typing and early XML-maximalism thrown in as well.
Also arriving at this moment was [AJAX](https://en.wikipedia.org/wiki/Ajax_(programming)), or Asynchronous
JavaScript and XML. Note well the XML here. AJAX, as everyone now knows, allows browsers to issue HTTP requests
to the server in the background and process the response directly in JavaScript, opening up a whole new world of
programming for the web.
programming for the web.
The question was: what should those requests look like? They were obviously going to be XML. Look, it's right there
in the name. And this new SOAP/XML-RPC standard was out, maybe that was the right thing?
@ -220,21 +218,21 @@ if REST, rather than SOAP, should be the preferred mechanism for accessing what
The web was proving to be extremely flexible and growing gang busters, so maybe the same network architecture, REST, that
was working so well for browsers & humans would work well for APIs.
It sounded plausible, especially when XML was the format for APIs: XML sure _looks_ an awful lot like HTML, doesn't it?
You can imagine an XML API satisfying all of the RESTful constraints, up to and including the uniform interface.
It sounded plausible, especially when XML was the format for APIs: XML sure _looks_ an awful lot like HTML, doesn't it?
You can imagine an XML API satisfying all of the RESTful constraints, up to and including the uniform interface.
So people began exploring this route as well.
While all this was happening, another important technology was in the process of being born: [JSON](https://www.json.org/json-en.html)
JSON was (literally) JavaScript to SOAP/RPC-XML's Java: simple, dynamic and easy. It's hard to believe now,
when JSON is the dominant format for most web APIs, but it actually took a while for JSON to catch on. As late as 2008,
when JSON is the dominant format for most web APIs, but it actually took a while for JSON to catch on. As late as 2008,
discussions around API development were mainly around XML, not JSON.
### Formalizing REST APIs
In 2008, Martin Fowler published an article popularizing the [Richardson Maturity Model](https://martinfowler.com/articles/richardsonMaturityModel.html),
a model to determine how RESTful a given API was.
a model to determine how RESTful a given API was.
The model proposed four "levels", with the first level being Plain Old XML, or The Swamp of POX.
@ -246,33 +244,33 @@ From there, an API could be considered more "mature" as a REST API as it adopted
* Level 2: HTTP Verbs (using `GET`, `POST`, `DELETE`, etc. properly)
* Level 3: Hypermedia Controls (e.g. links)
Level 3 is where the uniform interface comes in, which is why this level is considered the most mature and truly "The
Level 3 is where the uniform interface comes in, which is why this level is considered the most mature and truly "The
Glory of REST"
### "REST" Wins, Kinda...
Unfortunately for the term REST, two things happened at this time:
Unfortunately for the term REST, two things happened at this time:
* Everyone switched to JSON
* Everyone stopped at Level 2 of the RMM
JSON rapidly took over the web service/API world because SOAP/XML-RPC was so dramatically over-engineered. JSON was simple,
"just worked" and was easy to read and understand.
JSON rapidly took over the web service/API world because SOAP/XML-RPC was so dramatically over-engineered. JSON was simple,
"just worked" and was easy to read and understand.
With this change, the web development world threw off the shackles of the
With this change, the web development world threw off the shackles of the
[J2EE mindset](https://en.wikipedia.org/wiki/Jakarta_EE) conclusively, relegating SOAP/XML-RPC to an enterprise-only affair.
Since the REST approach wasn't as tied to XML as SOAP/XML-RPC was, and since it didn't impose as much formality on
end points, REST was the natural place for JSON to take over. And it did so, rapidly.
Since the REST approach wasn't as tied to XML as SOAP/XML-RPC was, and since it didn't impose as much formality on
end points, REST was the natural place for JSON to take over. And it did so, rapidly.
During this crucial change, something became increasingly clear: most JSON APIs were stopping at Level 2 of the RMM.
Some pushed through to Level 3 by incorporating hypermedia controls in their responses, but nearly all these APIs still
needed to publish documentation, indicating that the "Glory of REST" was not being achieved.
JSON taking over as the response format should have been a strong hint as well: JSON is obviously not a hypertext. You
can impose hypermedia controls on top of it, but it isn't natural. XML at least _looked_ like HTML, kinda, so it was
plausible that you could create a hypermedia with it.
JSON taking over as the response format should have been a strong hint as well: JSON is obviously not a hypertext. You
can impose hypermedia controls on top of it, but it isn't natural. XML at least _looked_ like HTML, kinda, so it was
plausible that you could create a hypermedia with it.
JSON was just... data. Adding hypermedia controls was awkward, non-standardized and rarely used in the manner described
by the uniform interface constraint.
@ -286,20 +284,20 @@ That's the one sentence version of how we got here.
Despite the JSON API world never consistently achieving truly RESTful APIs, there were plenty of fights over whether
or not the RESTless APIs being created were "RESTful": arguments over URL layouts, over which HTTP verb was
appropriate for a given action, flame wars about media types, and so forth.
appropriate for a given action, flame wars about media types, and so forth.
I was young at the time, and the whole thing struck me as opaque, puritanical and alienating, so I pretty much gave up
on the whole idea of REST: it was something condescending people fought about on the internet.
What I rarely saw mentioned (or, when I did, what I didn't understand) was the concept of the uniform interface and how
crucial it is to a RESTful system.
crucial it is to a RESTful system.
It wasn't until I created [intercooler.js](https://intercoolerjs.org) and a few smart folks started telling me that it was
RESTful that I got interested in the idea again.
RESTful? That's a JSON API thing, how could my hack of a front-end library be RESTful?
So I looked into it, reread Fielding's dissertation with fresh eyes, and discovered, lo and behold, not only was
So I looked into it, reread Fielding's dissertation with fresh eyes, and discovered, lo and behold, not only was
intercooler RESTful, but all the "RESTful" JSON APIs I was dealing with weren't RESTful at all!
And, with that, I began boring the internet to tears:
@ -316,17 +314,17 @@ And, with that, I began boring the internet to tears:
### The State of REST Today
Eventually most people got tired of trying to add hypermedia controls to JSON APIs and gave up on it. While these controls
worked well in certain specialized situations (e.g. paging), they never achieved the broad, obvious utility
worked well in certain specialized situations (e.g. paging), they never achieved the broad, obvious utility
that REST found in the general, human-oriented internet. [(I have a theory why that is.)](https://intercoolerjs.org/2016/05/08/hatoeas-is-for-humans.html)
Things settled into this intermediate RESTless state, with REST slowly cementing its meaning as a JSON API at Level 1
Things settled into this intermediate RESTless state, with REST slowly cementing its meaning as a JSON API at Level 1
or 2 of the RMM. But there was always the possibility that we would break through to Level 3 and the glory of REST.
Then Single Page Applications (SPAs) hit.
When SPAs hit, web development became disconnected entirely from the original underlying RESTful architecture. The *entire
networking architecture* of SPA applications moved over to the JSON RPC style. Additionally, due to the complexity of these
applications, developers specialized into front end and back end.
networking architecture* of SPA applications moved over to the JSON RPC style. Additionally, due to the complexity of these
applications, developers specialized into front end and back end.
The front end developers were obviously _not_ doing anything RESTful: they were working with JavaScript, building DOM
object, and calling AJAX APIs when needed. This was much more like thick-client authoring than anything like the
@ -335,13 +333,13 @@ early web.
The back end engineers were still concerned with the network architecture to an extent, and they continued to use the
term "REST" to describe what they were doing.
Even though they were doing things like publishing swagger documentation for their RESTful APIs or [complaining about API
Even though they were doing things like publishing swagger documentation for their RESTful APIs or [complaining about API
churn of their RESTful APIs](https://www.infoq.com/articles/no-more-mvc-frameworks/), things that wouldn't be occurring
if they were actually creating RESTful APIs.
Finally, in the late 2010s, people had had enough: REST, even in its RESTless form, simply wasn't keep up with the needs
of increasingly complex SPA applications. The applications were becoming more and more like thick clients, and thick
client problems need thick client solutions, not bastardized hypermedia client solutions.
of increasingly complex SPA applications. The applications were becoming more and more like thick clients, and thick
client problems need thick client solutions, not bastardized hypermedia client solutions.
The dam really broke when [GraphQL](https://en.wikipedia.org/wiki/GraphQL) was released.
@ -349,27 +347,27 @@ GraphQL couldn't be less RESTful: you absolutely *have to have* documentation to
that uses GraphQL. The client and the server are extremely tightly coupled. There are no native hypermedia controls
in it. It offers schemas and, in many ways, feels a lot like an updated and stripped-down version of XML-RPC.
And here I want to say: that's OK!
And here I want to say: that's OK!
People really, really like GraphQL in many cases and, if you are building a thick client style application, that makes a
lot of sense:
> The short answer to this question is that HATEOAS isnt a good fit for most modern use cases for APIs. That is why after
> almost 20 years, HATEOAS still hasnt gained wide adoption among developers. GraphQL on the other hand is spreading
> almost 20 years, HATEOAS still hasnt gained wide adoption among developers. GraphQL on the other hand is spreading
> like wildfire because it solves real-world problems.
>
> _[GraphQL and REST Level 3 (HATEOAS)](https://techblog.commercetools.com/graphql-and-rest-level-3-hateoas-70904ff1f9cf)_
So GraphQL isn't REST, it doesn't claim to be REST, it doesn't want to be REST.
But, as of today, the vast majority of developers and companies, even as they excitedly add GraphQL functionality to
But, as of today, the vast majority of developers and companies, even as they excitedly add GraphQL functionality to
their APIs, continue to use the term REST to describe what they are building.
## OK, What Can We Do About This Situation?
Unfortunately, [voidfunc](https://news.ycombinator.com/item?id=32073545) is probably right:
> You can tap the sign as much as you want, that battle was lost a long time ago. REST is just the common term people
> You can tap the sign as much as you want, that battle was lost a long time ago. REST is just the common term people
> use for HTTP+JSON RPC.
We are going to keep calling _obviously_ non-RESTful JSON APIs REST because that's just what everyone calls them now.
@ -382,15 +380,15 @@ jobs for working on v138 of their RESTful JSON API's swagger documentation.
[The situation is hopeless, but not serious.](https://wwnorton.com/books/9780393310214)
Regardless, there is an opportunity here to explain REST and, in particular, the uniform interface to a new generation of web
developers who may have never heard of those concepts in their original context, and who assume REST === JSON APIs.
developers who may have never heard of those concepts in their original context, and who assume REST === JSON APIs.
[People sense something is wrong](@/essays/a-response-to-rich-harris.md), and maybe REST, real, actual REST,
[People sense something is wrong](@/essays/a-response-to-rich-harris.md), and maybe REST, real, actual REST,
not RESTless, could be a part of [the answer to that](@/essays/spa-alternative.md).
At the very least, the ideas behind REST are interesting and worth knowing just as general software engineering knowledge.
There is a larger meta-point here too: even a relatively smart group of people (early web developers), with the benefit
There is a larger meta-point here too: even a relatively smart group of people (early web developers), with the benefit
of the internet, and with a pretty clear (if at times academic) specification for the term REST, were unable to keep the
meaning consistent with its original meaning over period of two decades.
If we can get this so obviously wrong, what else could we be wrong about?
If we can get this so obviously wrong, what else could we be wrong about?

View File

@ -3,6 +3,7 @@ title = "Hypermedia APIs vs. Data APIs"
date = 2021-07-17
updated = 2022-04-07
[taxonomies]
author = ["Carson Gross"]
tag = ["posts"]
+++

View File

@ -3,6 +3,7 @@ title = "Hypermedia Clients"
date = 2023-01-28
updated = 2023-01-29
[taxonomies]
author = ["Carson Gross"]
tag = ["posts"]
+++
@ -220,4 +221,4 @@ Except for a few people like [Mike](https://training.amundsen.com/), we've been
<img src="/img/creating-client.png" alt="Creating A Hypermedia Client Is Hard Joke" style="max-width: 95%">
</div>
</div>

View File

@ -3,6 +3,7 @@ title = "Hypermedia-Driven Applications"
date = 2022-02-06
updated = 2022-10-18
[taxonomies]
author = ["Carson Gross"]
tag = ["posts"]
+++
@ -39,7 +40,7 @@ does not.
In particular, HDAs continue to use [Hypermedia As The Engine of Application State (HATEOAS)](@/essays/hateoas.md), whereas
most SPAs abandon HATEOAS in favor of a client-side model and data (rather than hypermedia) APIs.
## An Example SPA fragment
## An Example HDA fragment
Consider the htmx [Active Search](@/examples/active-search.md) example:

View File

@ -3,6 +3,7 @@ title = "Hypermedia-Friendly Scripting"
date = 2022-11-17
updated = 2022-11-29
[taxonomies]
author = ["Carson Gross"]
tag = ["posts"]
+++

View File

@ -2,6 +2,7 @@
title = "Hypermedia On Whatever you'd Like"
date = 2023-05-23
[taxonomies]
author = ["Carson Gross"]
tag = ["posts"]
+++

View File

@ -3,6 +3,7 @@ title = "Locality of Behaviour (LoB)"
date = 2020-05-29
updated = 2023-01-20
[taxonomies]
author = ["Carson Gross"]
tag = ["posts"]
+++

View File

@ -3,9 +3,11 @@ title = "REST Copypasta"
date = 2023-06-26
updated = 2023-06-26
[taxonomies]
author = ["Carson Gross"]
tag = ["posts"]
[extra]
show_title = false
show_author = false
+++
## REST copy-pastas
@ -16,8 +18,8 @@ show_title = false
I'd just like to interject for a moment. What you're referring to as REST,
is in fact, JSON/RPC, or as I've recently taken to calling it, REST-less.
JSON is not a hypermedia unto itself, but rather a plain data format made
useful by out of band information as defined by swagger documentation or
JSON is not a hypermedia unto itself, but rather a plain data format made
useful by out of band information as defined by swagger documentation or
similar.
Many computer users work with a canonical version of REST every day,
@ -27,7 +29,7 @@ not aware that it is basically the REST-ful architecture, defined by Roy Fieldin
There really is a REST, and these people are using it, but it is just a
part of The Web they use. REST is the network architecture: hypermedia encodes the state
of resources for hypermedia clients. JSON is an essential part of Single Page Applications,
of resources for hypermedia clients. JSON is an essential part of Single Page Applications,
but useless by itself; it can only function in the context of a complete API specification.
JSON is normally used in combination with SPA libraries: the whole system
is basically RPC with JSON added, or JSON/RPC. All these so-called "REST-ful"
@ -93,4 +95,4 @@ Copy
put '' into the next <output/>">
Copy For HN
</button>
<output></output>
<output></output>

View File

@ -3,6 +3,7 @@ title = "SPA Alternative"
date = 2020-10-29
updated = 2022-02-06
[taxonomies]
author = ["Carson Gross"]
tag = ["posts"]
+++

View File

@ -3,6 +3,7 @@ title = "Splitting Your Data &amp; Application APIs: Going Further"
date = 2021-09-16
updated = 2022-02-06
[taxonomies]
author = ["Carson Gross"]
tag = ["posts"]
+++

View File

@ -3,6 +3,7 @@ title = "Template Fragments"
date = 2022-08-03
updated = 2023-03-18
[taxonomies]
author = ["Carson Gross"]
tag = ["posts"]
+++

View File

@ -3,6 +3,7 @@ title = "Two Approaches To Decoupling"
date = 2022-05-01
updated = 2022-05-01
[taxonomies]
author = ["Carson Gross"]
tag = ["posts"]
+++
@ -219,4 +220,4 @@ In this essay we looked at two different types of decoupling:
* Network-architecture decoupling via REST/HATEOAS in a hypermedia system
And we saw that, despite the tighter application-level coupling found in a hypermedia-based application, it is the
hypermedia system that handles changes more gracefully.
hypermedia system that handles changes more gracefully.

View File

@ -3,6 +3,7 @@ template = "demo.html"
title = "View Transitions"
date = 2023-04-11
[taxonomies]
author = ["Carson Gross"]
tag = ["posts"]
+++

View File

@ -3,6 +3,7 @@ title = "When Should You Use Hypermedia?"
date = 2022-10-23
updated = 2023-02-03
[taxonomies]
author = ["Carson Gross"]
tag = ["posts"]
+++

View File

@ -2,7 +2,7 @@
title = "response-targets"
+++
This extension allows to specify different target elements to be swapped when
This extension allows you to specify different target elements to be swapped when
different HTTP response codes are received.
It uses attribute names in a form of ``hx-target-[CODE]`` where `[CODE]` is a numeric
@ -101,6 +101,10 @@ be looked up (in the given order):
* `hx-target-4*`
* `hx-target-*`.
_If you are using tools that do not support asterisks in HTML attributes, you
may instead use the `x` character, e.g., `hx-target-4xx`._
## Notes
* `hx-target-…` is inherited and can be placed on a parent element.

View File

@ -14,7 +14,7 @@ Use the following attributes to configure how WebSockets behave:
host and port to have browsers send cookies via websockets.
* `ws-send` - Sends a message to the nearest websocket based on the trigger value for the element (either the natural
event
of the event specified by [`hx-trigger`])
or the event specified by [`hx-trigger`])
## Install

View File

@ -1,13 +1,21 @@
Name,URL
Devmode.fm - Dynamic HTML with htmx,https://devmode.fm/episodes/dynamic-html-with-htmx
devMode.fm - Dynamic HTML with htmx,https://devmode.fm/episodes/dynamic-html-with-htmx
JS Party - Less JavaScript more htmx,https://changelog.com/jsparty/171
Software Breakthroughs for the 21s Century,https://www.youtube.com/watch?v=O4ZFIx1ckSg
Software Breakthroughs for the 21st Century,https://www.youtube.com/watch?v=O4ZFIx1ckSg
HTML All The Things,https://www.htmlallthethings.com/blog-posts/htmx-hyperscript-and-more
Django Chat,https://djangochat.com/episodes/htmx-carson-gross
Talk Python,https://talkpython.fm/episodes/show/321/htmx-clean-dynamic-html-pages
.NET Rocks!,https://www.dotnetrocks.com/?show=1749
"PyCharmIDE - Simple, Fast Frontends With htmx",https://www.youtube.com/watch?v=cBfz4W_KvEI
JetBrainsTV - htmx: Writing JavaScript to Avoid Writing JavaScript,https://www.youtube.com/watch?v=u2rjnLJ1M98
"DjangoConUS - REST, HATEOAS & Django - It's OK to not use JSON... or Javascript",https://www.youtube.com/watch?v=L_UWY-zHlOA
devMode.fm - Hype for Hyperscript,https://devmode.fm/episodes/hype-for-hyperscript
JavaScript Jabber,https://topenddevs.com/podcasts/javascript-jabber/episodes/htmx-and-intercooler-ft-carson-gross-jsj-513
Chariot TechCast,https://chariotsolutions.com/podcast/techchat-tuesdays-48-carson-gross-and-htmx/
"airhacks.fm - HATEOAS, Data APIs, Java and How htmx Happened",https://airhacks.fm/#episode_200
ChariotSolutions - Return To Hypermedia: Solving Javascript Fatigue Using Fundamental Web Architecture,https://www.youtube.com/watch?v=LRrrxQXWdhI
Go Time - Is htmx the way to Go?,https://changelog.com/gotime/266
Kompilator - Complexity very very bad,https://kompilator.se/067
GitHub - Accelerator: Open Source Demo Day,https://www.youtube.com/watch?v=Gj6Bez2182k&t=1821s
Unfiltered Build - The HOWL stack is your new tech stack,https://podcast.unfilteredbuild.com/episodes/ep24-howl-stack-and-htmx-carson-gross/
FrontendRheinMain - htmx: Building modern web applications without JS,https://www.youtube.com/watch?v=Jodkvyo5DbA

1 Name URL
2 Devmode.fm - Dynamic HTML with htmx devMode.fm - Dynamic HTML with htmx https://devmode.fm/episodes/dynamic-html-with-htmx
3 JS Party - Less JavaScript more htmx https://changelog.com/jsparty/171
4 Software Breakthroughs for the 21s Century Software Breakthroughs for the 21st Century https://www.youtube.com/watch?v=O4ZFIx1ckSg
5 HTML All The Things https://www.htmlallthethings.com/blog-posts/htmx-hyperscript-and-more
6 Django Chat https://djangochat.com/episodes/htmx-carson-gross
7 Talk Python https://talkpython.fm/episodes/show/321/htmx-clean-dynamic-html-pages
8 .NET Rocks! https://www.dotnetrocks.com/?show=1749
9 PyCharmIDE - Simple, Fast Frontends With htmx https://www.youtube.com/watch?v=cBfz4W_KvEI
10 JetBrainsTV - htmx: Writing JavaScript to Avoid Writing JavaScript https://www.youtube.com/watch?v=u2rjnLJ1M98
11 DjangoConUS - REST, HATEOAS & Django - It's OK to not use JSON... or Javascript https://www.youtube.com/watch?v=L_UWY-zHlOA
12 devMode.fm - Hype for Hyperscript https://devmode.fm/episodes/hype-for-hyperscript
13 JavaScript Jabber https://topenddevs.com/podcasts/javascript-jabber/episodes/htmx-and-intercooler-ft-carson-gross-jsj-513
14 Chariot TechCast https://chariotsolutions.com/podcast/techchat-tuesdays-48-carson-gross-and-htmx/
15 airhacks.fm - HATEOAS, Data APIs, Java and How htmx Happened https://airhacks.fm/#episode_200
16 ChariotSolutions - Return To Hypermedia: Solving Javascript Fatigue Using Fundamental Web Architecture https://www.youtube.com/watch?v=LRrrxQXWdhI
17 Go Time - Is htmx the way to Go? https://changelog.com/gotime/266
18 Kompilator - Complexity very very bad https://kompilator.se/067
19 GitHub - Accelerator: Open Source Demo Day https://www.youtube.com/watch?v=Gj6Bez2182k&t=1821s
20 Unfiltered Build - The HOWL stack is your new tech stack https://podcast.unfilteredbuild.com/episodes/ep24-howl-stack-and-htmx-carson-gross/
21 FrontendRheinMain - htmx: Building modern web applications without JS https://www.youtube.com/watch?v=Jodkvyo5DbA

View File

@ -0,0 +1,88 @@
<?xml version="1.0" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 20010904//EN"
"http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd">
<svg version="1.0" xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 5563.000000 1400.000000"
preserveAspectRatio="xMidYMid meet">
<style>
path { fill: black; }
</style>
<g transform="translate(0.000000,1400.000000) scale(0.100000,-0.100000)"
stroke="none">
<path d="M6770 13354 c-239 -51 -430 -159 -636 -357 -273 -262 -496 -606 -703
-1083 -364 -842 -635 -2040 -746 -3304 -8 -96 -18 -198 -21 -227 l-5 -51 -182
-128 c-484 -338 -932 -692 -1372 -1083 -177 -157 -655 -636 -795 -796 -552
-634 -865 -1166 -972 -1656 -19 -89 -22 -133 -22 -299 0 -180 2 -203 27 -295
126 -468 517 -759 1157 -860 139 -22 586 -31 775 -16 299 24 614 73 941 147
200 44 535 132 679 178 l75 24 6 -27 c216 -911 484 -1601 817 -2101 468 -703
1058 -950 1623 -681 116 56 197 108 305 197 408 338 784 1024 1060 1932 70
234 65 318 -27 421 -59 65 -127 95 -219 94 -76 0 -116 -14 -182 -62 -56 -41
-85 -94 -127 -237 -183 -616 -420 -1136 -667 -1461 -84 -110 -244 -265 -325
-313 -137 -82 -285 -102 -415 -56 -403 142 -829 850 -1125 1866 -46 158 -154
582 -154 605 0 8 21 21 48 31 298 108 802 319 1160 485 118 55 217 99 220 99
3 0 69 -30 146 -66 1192 -564 2381 -937 3356 -1055 249 -31 758 -34 945 -6
610 89 988 345 1145 774 80 221 82 498 5 790 -74 280 -256 644 -487 973 -249
353 -610 766 -983 1122 l-140 133 128 120 c243 229 577 590 775 837 752 943
955 1731 588 2281 -338 508 -1118 685 -2246 511 -1340 -207 -3122 -925 -4745
-1912 -142 -87 -150 -90 -153 -68 -2 13 11 136 28 272 183 1477 557 2688 1026
3321 77 104 255 280 326 322 118 69 173 85 283 85 85 0 109 -3 163 -26 227
-94 430 -313 642 -692 144 -258 267 -550 391 -931 98 -302 99 -304 149 -359
124 -134 340 -118 452 33 35 48 58 117 58 172 0 85 -183 623 -318 934 -209
485 -431 828 -706 1092 -208 200 -402 309 -638 357 -95 20 -298 20 -388 0z
m4410 -3134 c243 -19 396 -53 545 -124 105 -50 206 -136 248 -214 87 -161 71
-402 -47 -692 -187 -457 -655 -1069 -1259 -1643 l-157 -150 -52 44 c-234 197
-667 527 -983 749 -332 234 -871 583 -951 616 -57 24 -175 16 -238 -16 -175
-89 -212 -338 -70 -471 22 -20 120 -87 219 -149 353 -221 808 -535 1135 -783
201 -153 480 -377 480 -385 -1 -10 -318 -261 -535 -424 -587 -439 -1243 -862
-1936 -1248 -481 -267 -1215 -623 -1684 -815 -224 -92 -468 -185 -473 -181
-13 14 -93 598 -127 928 -56 551 -75 904 -82 1531 -5 464 8 1146 23 1199 7 26
502 335 906 566 1481 849 3028 1444 4199 1616 327 48 606 63 839 46z m-6564
-2957 c-20 -902 57 -2027 200 -2906 20 -121 33 -221 31 -224 -9 -9 -301 -94
-492 -143 -352 -91 -664 -150 -980 -187 -211 -24 -638 -24 -790 0 -536 84
-741 303 -670 713 119 677 979 1725 2215 2695 114 90 483 368 489 369 2 0 1
-143 -3 -317z m6173 -926 c715 -710 1143 -1347 1227 -1827 68 -392 -130 -615
-626 -705 -127 -23 -513 -32 -703 -16 -780 66 -1741 336 -2801 788 -172 74
-245 109 -235 115 8 5 107 60 220 123 918 507 1807 1111 2608 1771 l26 22 30
-25 c16 -13 131 -124 254 -246z"/>
<path d="M6835 7745 c-296 -54 -520 -264 -602 -566 -24 -87 -23 -270 1 -358
96 -354 409 -585 766 -568 198 10 352 79 496 222 77 76 98 105 137 185 57 115
76 186 84 305 24 381 -258 724 -645 784 -88 14 -147 13 -237 -4z" style="fill:#2188ff"/>
<path d="M2667 10810 c-740 -67 -1187 -369 -1329 -898 -26 -97 -35 -330 -19
-456 62 -463 357 -1020 861 -1623 88 -107 110 -127 165 -154 152 -74 325 -15
396 135 51 107 36 224 -39 318 -20 25 -84 105 -142 176 -360 443 -588 864
-645 1188 -36 209 -2 356 112 475 137 144 360 221 718 249 322 26 807 -22
1280 -126 88 -19 191 -42 229 -50 84 -18 146 -8 224 36 119 69 172 220 122
352 -24 66 -69 121 -123 153 -92 53 -665 166 -1067 210 -171 19 -603 27 -743
15z"/>
<path d="M34665 10654 c-265 -27 -422 -55 -605 -111 -972 -294 -1631 -1120
-1835 -2300 -57 -332 -60 -382 -60 -1233 0 -852 4 -926 61 -1255 200 -1145
838 -1968 1763 -2274 286 -95 496 -125 866 -125 355 0 577 26 852 100 425 114
751 299 1044 593 203 204 331 388 450 651 117 255 215 618 244 903 l7 67 -301
0 -301 0 -4 -22 c-3 -13 -10 -57 -16 -98 -78 -511 -263 -924 -540 -1200 -328
-328 -777 -482 -1415 -484 -263 -1 -356 8 -547 55 -335 82 -600 237 -853 500
-323 336 -548 809 -645 1359 -51 293 -53 320 -57 1125 -5 794 -1 920 38 1190
52 360 133 632 274 920 121 248 263 445 448 621 283 270 599 423 1012 491 151
24 538 24 710 -1 404 -57 714 -190 967 -412 308 -271 524 -713 603 -1239 10
-60 19 -116 21 -122 3 -10 72 -13 305 -13 l301 0 -7 68 c-40 390 -174 817
-349 1117 -128 218 -329 456 -513 607 -332 271 -748 437 -1263 503 -118 16
-563 28 -655 19z"/>
<path d="M16740 7005 l0 -3555 300 0 300 0 0 3035 c0 1669 3 3035 8 3035 4 -1
923 -1365 2043 -3033 l2036 -3032 301 -3 302 -2 0 3555 0 3555 -295 0 -295 0
0 -3042 c-1 -2877 -2 -3042 -18 -3022 -9 11 -930 1380 -2047 3042 l-2030 3022
-302 0 -303 0 0 -3555z"/>
<path d="M24730 8078 c0 -1609 4 -2525 10 -2603 30 -345 103 -627 231 -894
194 -405 529 -740 950 -951 397 -200 868 -292 1404 -277 386 11 687 68 997
189 544 211 937 568 1178 1071 118 246 174 444 222 787 9 63 12 729 15 2623
l4 2537 -305 0 -306 0 -3 -2542 c-3 -2787 1 -2582 -59 -2843 -134 -579 -531
-1017 -1093 -1203 -236 -78 -433 -107 -745 -107 -394 0 -684 59 -962 194 -466
227 -773 626 -887 1154 -52 242 -51 161 -51 2849 l0 2498 -300 0 -300 0 0
-2482z"/>
<path d="M39910 7005 l0 -3555 2060 0 2060 0 0 255 0 255 -1760 0 -1760 0 0
3300 0 3300 -300 0 -300 0 0 -3555z"/>
<path d="M46180 7005 l0 -3555 2210 0 2210 0 0 255 0 255 -1910 0 -1910 0 0
1450 0 1450 1660 0 1660 0 0 255 0 255 -1660 0 -1660 0 0 1340 0 1340 1900 0
1900 0 0 255 0 255 -2200 0 -2200 0 0 -3555z"/>
<path d="M53000 7005 l0 -3555 300 0 300 0 0 3555 0 3555 -300 0 -300 0 0
-3555z"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 5.5 KiB

View File

@ -7,7 +7,7 @@ require (
github.com/benpate/htmlconv v0.3.0
github.com/labstack/echo/v4 v4.9.0
github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8
golang.org/x/net v0.0.0-20211015210444-4f30a5c0130f
golang.org/x/net v0.7.0
)
require (
@ -16,7 +16,7 @@ require (
github.com/mattn/go-isatty v0.0.14 // indirect
github.com/valyala/bytebufferpool v1.0.0 // indirect
github.com/valyala/fasttemplate v1.2.1 // indirect
golang.org/x/crypto v0.0.0-20210817164053-32db794688a5 // indirect
golang.org/x/sys v0.0.0-20211103235746-7861aae1554b // indirect
golang.org/x/text v0.3.7 // indirect
golang.org/x/crypto v0.1.0 // indirect
golang.org/x/sys v0.5.0 // indirect
golang.org/x/text v0.7.0 // indirect
)

View File

@ -26,26 +26,51 @@ github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6Kllzaw
github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
github.com/valyala/fasttemplate v1.2.1 h1:TVEnxayobAdVkhQfrfes2IzOB6o+z4roRkPF52WA1u4=
github.com/valyala/fasttemplate v1.2.1/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+qRAEEKiv+SiQ=
golang.org/x/crypto v0.0.0-20210817164053-32db794688a5 h1:HWj/xjIHfjYU5nVXpTM0s39J9CbLn7Cc5a7IC5rwsMQ=
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20210817164053-32db794688a5/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.1.0 h1:MDRAIl0xIo9Io2xV565hzXHw3zVseKrJKodhohM5CjU=
golang.org/x/crypto v0.1.0/go.mod h1:RecgLatLF4+eUMCP1PoPZQb+cVrJcOPbHkTkbkB9sbw=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20211015210444-4f30a5c0130f h1:OfiFi4JbukWwe3lzw+xunroH1mnC1e2Gy5cxNJApiSY=
golang.org/x/net v0.0.0-20211015210444-4f30a5c0130f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/net v0.1.0/go.mod h1:Cx3nUiGt4eDBEyega/BKRp+/AlGL8hYe7U9odMt2Cco=
golang.org/x/net v0.7.0 h1:rJrUqqhjsgNp7KqAIc25s9pZnjU7TUcSY7HcVZjdn1g=
golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210616045830-e2b7044e8c71/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20211103235746-7861aae1554b h1:1VkfZQv42XQlA/jchYumAnv1UPo6RgF9rJFkTgZIxO4=
golang.org/x/sys v0.0.0-20211103235746-7861aae1554b/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.5.0 h1:MUK/U/4lj1t1oPg0HfuXDN/Z1wv31ZJ/YcPiGccS4DU=
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.1.0/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.7.0 h1:4BRB4x83lYWy72KwLD/qYDuTu7q9PjSagHvijDw7cLo=
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/time v0.0.0-20201208040808-7e3f01d25324/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo=

View File

@ -3,21 +3,19 @@ module github.com/benpate/ghost
go 1.17
require (
github.com/labstack/echo/v4 v4.3.0
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4
github.com/labstack/echo/v4 v4.9.0
golang.org/x/net v0.7.0
)
require (
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/dgrijalva/jwt-go v3.2.0+incompatible // indirect
github.com/labstack/gommon v0.3.0 // indirect
github.com/mattn/go-colorable v0.1.8 // indirect
github.com/mattn/go-isatty v0.0.12 // indirect
github.com/stretchr/testify v1.7.0 // indirect
github.com/golang-jwt/jwt v3.2.2+incompatible // indirect
github.com/labstack/gommon v0.3.1 // indirect
github.com/mattn/go-colorable v0.1.11 // indirect
github.com/mattn/go-isatty v0.0.14 // indirect
github.com/valyala/bytebufferpool v1.0.0 // indirect
github.com/valyala/fasttemplate v1.2.1 // indirect
golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2 // indirect
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c // indirect
golang.org/x/text v0.3.6 // indirect
golang.org/x/crypto v0.1.0 // indirect
golang.org/x/sys v0.5.0 // indirect
golang.org/x/text v0.7.0 // indirect
golang.org/x/time v0.0.0-20201208040808-7e3f01d25324 // indirect
)

View File

@ -1,52 +1,71 @@
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/dgrijalva/jwt-go v3.2.0+incompatible h1:7qlOGliEKZXTDg6OTjfoBKDXWrumCAMpl/TFQ4/5kLM=
github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
github.com/labstack/echo/v4 v4.3.0 h1:DCP6cbtT+Zu++K6evHOJzSgA2115cPMuCx0xg55q1EQ=
github.com/labstack/echo/v4 v4.3.0/go.mod h1:PvmtTvhVqKDzDQy4d3bWzPjZLzom4iQbAZy2sgZ/qI8=
github.com/labstack/gommon v0.3.0 h1:JEeO0bvc78PKdyHxloTKiF8BD5iGrH8T6MSeGvSgob0=
github.com/labstack/gommon v0.3.0/go.mod h1:MULnywXg0yavhxWKc+lOruYdAhDwPK9wf0OL7NoOu+k=
github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
github.com/mattn/go-colorable v0.1.8 h1:c1ghPdyEDarC70ftn0y+A/Ee++9zz8ljHG1b13eJ0s8=
github.com/mattn/go-colorable v0.1.8/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
github.com/mattn/go-isatty v0.0.9/go.mod h1:YNRxwqDuOph6SZLI9vUUz6OYw3QyUt7WiY2yME+cCiQ=
github.com/mattn/go-isatty v0.0.12 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHXY=
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
github.com/golang-jwt/jwt v3.2.2+incompatible h1:IfV12K8xAKAnZqdXVzCZ+TOjboZ2keLg81eXfW3O+oY=
github.com/golang-jwt/jwt v3.2.2+incompatible/go.mod h1:8pz2t5EyA70fFQQSrl6XZXzqecmYZeUEB8OUGHkxJ+I=
github.com/labstack/echo/v4 v4.9.0 h1:wPOF1CE6gvt/kmbMR4dGzWvHMPT+sAEUJOwOTtvITVY=
github.com/labstack/echo/v4 v4.9.0/go.mod h1:xkCDAdFCIf8jsFQ5NnbK7oqaF/yU1A1X20Ltm0OvSks=
github.com/labstack/gommon v0.3.1 h1:OomWaJXm7xR6L1HmEtGyQf26TEn7V6X88mktX9kee9o=
github.com/labstack/gommon v0.3.1/go.mod h1:uW6kP17uPlLJsD3ijUYn3/M5bAxtlZhMI6m3MFxTMTM=
github.com/mattn/go-colorable v0.1.11 h1:nQ+aFkoE2TMGc0b68U2OKSexC+eq46+XwZzWXHRmPYs=
github.com/mattn/go-colorable v0.1.11/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4=
github.com/mattn/go-isatty v0.0.14 h1:yVuAays6BHfxijgZPzw+3Zlu5yQgKGP2/hcQbHb7S9Y=
github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw=
github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
github.com/valyala/fasttemplate v1.0.1/go.mod h1:UQGH1tvbgY+Nz5t2n7tXsz52dQxojPUpymEIMZ47gx8=
github.com/valyala/fasttemplate v1.2.1 h1:TVEnxayobAdVkhQfrfes2IzOB6o+z4roRkPF52WA1u4=
github.com/valyala/fasttemplate v1.2.1/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+qRAEEKiv+SiQ=
golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2 h1:It14KIkyBFYkHkwZ7k45minvA9aorojkyjGk9KJ5B/w=
golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4=
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20210817164053-32db794688a5/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.1.0 h1:MDRAIl0xIo9Io2xV565hzXHw3zVseKrJKodhohM5CjU=
golang.org/x/crypto v0.1.0/go.mod h1:RecgLatLF4+eUMCP1PoPZQb+cVrJcOPbHkTkbkB9sbw=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4 h1:4nGaVu0QrbjT/AK2PRLuQfQuh6DJve+pELhqTdAj3x0=
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/net v0.0.0-20211015210444-4f30a5c0130f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/net v0.1.0/go.mod h1:Cx3nUiGt4eDBEyega/BKRp+/AlGL8hYe7U9odMt2Cco=
golang.org/x/net v0.7.0 h1:rJrUqqhjsgNp7KqAIc25s9pZnjU7TUcSY7HcVZjdn1g=
golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210403161142-5e06dd20ab57/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c h1:F1jZWGFhYfh0Ci55sIpILtKKK8p3i2/krTr0H1rg74I=
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20211103235746-7861aae1554b/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.5.0 h1:MUK/U/4lj1t1oPg0HfuXDN/Z1wv31ZJ/YcPiGccS4DU=
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.1.0/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.6 h1:aRYxNxv6iGQlyVaZmk6ZgYEDa+Jg18DxebPSrd6bg1M=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.7.0 h1:4BRB4x83lYWy72KwLD/qYDuTu7q9PjSagHvijDw7cLo=
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/time v0.0.0-20201208040808-7e3f01d25324 h1:Hir2P/De0WpUhtrKGGjvSb2YxUgyZ7EFOSLIcSSpiwE=
golang.org/x/time v0.0.0-20201208040808-7e3f01d25324/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo=
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

View File

@ -129,6 +129,10 @@ table {
line-height: 1em;
}
time {
font-weight: bold;
}
a {
text-decoration: none;
color: var(--midBlue)

View File

@ -0,0 +1,34 @@
{% extends "base.html" %}
{% block title %}
{% if page.title -%}
{% set html_title = "&lt;/&gt; htmx ~ " ~ page.title -%}
{% endif -%}
{% endblock title %}
{% block content %}
{% if page.extra and page.extra.show_title is defined -%}
{% set show_title = page.extra.show_title -%}
{% else -%}
{% set show_title = true -%}
{% endif -%}
{% if page.extra and page.extra.show_author is defined -%}
{% set show_author = page.extra.show_author -%}
{% else -%}
{% set show_author = true -%}
{% endif -%}
{% set page_title = page.title -%}
{% if show_title %}<h1>{{ page_title | safe }}</h1>{% endif %}
{% if show_author and page.taxonomies.author %}
<address>{{ page.taxonomies.author | join(sep=", ") }}</address>
<time>{{ page.date | date(format="%B %d, %Y") }}</time>
{% endif %}
{{ page.content | safe }}
<div style="padding-top: 120px;padding-bottom:40px;text-align: center">
&lt;/&gt;
</div>
{% endblock content %}

View File

@ -27,9 +27,4 @@
{% endif -%}
{% if show_title %}<h1>{{ page_title | safe }}</h1>{% endif %}
{{ page.content | safe }}
{% if page.path is starting_with("/essays/") -%}
<div style="padding-top: 120px;padding-bottom:40px;text-align: center">
&lt;/&gt;
</div>
{% endif -%}
{% endblock content %}