prep 1.9.8 release

This commit is contained in:
Carson Gross 2023-11-06 18:03:43 -07:00
parent 37a864ea0f
commit 6fe62580e0
15 changed files with 37 additions and 257 deletions

View File

@ -1,5 +1,9 @@
# Changelog
## [1.9.8] - 2023-11-06
* Fixed a few npm & build related issues
## [1.9.7] - 2023-11-03
* Fixed a bug where a button associated with a form that is swapped out of the DOM caused errors

View File

@ -33,7 +33,7 @@ By removing these arbitrary constraints htmx completes HTML as a
## quick start
```html
<script src="https://unpkg.com/htmx.org@1.9.7"></script>
<script src="https://unpkg.com/htmx.org@1.9.8"></script>
<!-- have a button POST a click via AJAX -->
<button hx-post="/clicked" hx-swap="outerHTML">
Click Me

12
dist/htmx.d.ts vendored
View File

@ -343,7 +343,7 @@ export interface HtmxConfig {
requestClass?: "htmx-request" | string;
/**
* The class to temporarily place on elements that htmx has added to the DOM.
* @default "htmx-added"
* @default "htmx-added"
*/
addedClass?: "htmx-added" | string;
/**
@ -385,6 +385,16 @@ export interface HtmxConfig {
disableSelector?: "[hx-disable], [data-hx-disable]" | string;
/** @default "smooth" */
scrollBehavior?: "smooth" | "auto";
/**
* If set to false, disables the interpretation of script tags.
* @default true
*/
allowScriptTags?: boolean;
/**
* If set to true, disables htmx-based requests to non-origin hosts.
* @default false
*/
selfRequestsOnly?: boolean;
}
/**

2
dist/htmx.js vendored
View File

@ -87,7 +87,7 @@ return (function () {
sock.binaryType = htmx.config.wsBinaryType;
return sock;
},
version: "1.9.7"
version: "1.9.8"
};
/** @type {import("./htmx").HtmxInternalApi} */

2
dist/htmx.min.js vendored

File diff suppressed because one or more lines are too long

BIN
dist/htmx.min.js.gz vendored

Binary file not shown.

View File

@ -5,7 +5,7 @@
"AJAX",
"HTML"
],
"version": "1.9.7",
"version": "1.9.8",
"homepage": "https://htmx.org/",
"bugs": {
"url": "https://github.com/bigskysoftware/htmx/issues"

View File

@ -87,7 +87,7 @@ return (function () {
sock.binaryType = htmx.config.wsBinaryType;
return sock;
},
version: "1.9.7"
version: "1.9.8"
};
/** @type {import("./htmx").HtmxInternalApi} */

View File

@ -35,7 +35,7 @@ By removing these arbitrary constraints, htmx completes HTML as a [hypertext](ht
<h2>quick start</h2>
```html
<script src="https://unpkg.com/htmx.org@1.9.7"></script>
<script src="https://unpkg.com/htmx.org@1.9.8"></script>
<!-- have a button POST a click via AJAX -->
<button hx-post="/clicked" hx-swap="outerHTML">
Click Me

View File

@ -114,7 +114,7 @@ The fastest way to get going with htmx is to load it via a CDN. You can simply a
and get going:
```html
<script src="https://unpkg.com/htmx.org@1.9.7" integrity="sha384-EAzY246d6BpbWR7sQ8+WEm40J8c3dHFsqC58IgPlh4kMbRRI6P6WA+LA/qGAyAu8" crossorigin="anonymous"></script>
<script src="https://unpkg.com/htmx.org@1.9.8" integrity="sha384-EAzY246d6BpbWR7sQ8+WEm40J8c3dHFsqC58IgPlh4kMbRRI6P6WA+LA/qGAyAu8" crossorigin="anonymous"></script>
```
While the CDN approach is extremely simple, you may want to consider [not using CDNs in production](https://blog.wesleyac.com/posts/why-not-javascript-cdn).

View File

@ -343,7 +343,7 @@ export interface HtmxConfig {
requestClass?: "htmx-request" | string;
/**
* The class to temporarily place on elements that htmx has added to the DOM.
* @default "htmx-added"
* @default "htmx-added"
*/
addedClass?: "htmx-added" | string;
/**
@ -385,6 +385,16 @@ export interface HtmxConfig {
disableSelector?: "[hx-disable], [data-hx-disable]" | string;
/** @default "smooth" */
scrollBehavior?: "smooth" | "auto";
/**
* If set to false, disables the interpretation of script tags.
* @default true
*/
allowScriptTags?: boolean;
/**
* If set to true, disables htmx-based requests to non-origin hosts.
* @default false
*/
selfRequestsOnly?: boolean;
}
/**

View File

@ -87,7 +87,7 @@ return (function () {
sock.binaryType = htmx.config.wsBinaryType;
return sock;
},
version: "1.9.7"
version: "1.9.8"
};
/** @type {import("./htmx").HtmxInternalApi} */

View File

@ -2,10 +2,10 @@
This package implements a realtime server for testing WebSockets and Server Sent Events (SSE) in htmx.
## How to Use This Server
From this directory, run `node server.js`.
From this directory, run `node server.mjs`.
If you want to hotreload, I recommend using `nodemon`, which you can install with `npm install -g
nodemon`, and then run `nodemon -e js,html server.js`.
nodemon`, and then run `nodemon -e js,html server.mjs`.
## Web Sockets
When htmx receives messages from any WebSocket client, it responds with that same content in a way that htmx can process. This means, that the response message will look like this: `<div id="idMessage" hx-swap-oob="true">{your message here}</div>`

View File

@ -1,244 +0,0 @@
import * as http from 'node:http'
import * as path from 'node:path'
import * as fs from 'node:fs/promises'
import { WebSocketServer } from 'ws'
// Define some string and number constants
const HOSTNAME = '127.0.0.1';
const PORT = 8080;
const DATA = JSON.parse(await fs.readFile('./static/data.json'))
const SITE_BASE = (await fs.readFile('./static/site-base.html')).toString()
// Define the websockets
const ECHO_WS = createWebSocket((ws) => {
ws.on('message', (message) => {
const data = JSON.parse(message.toString())
ws.send(`<div id=idMessage>${data.message}</div>`)
})
})
const HEARTBEAT_WS = createWebSocket((ws) => {
ws.interval = setInterval(() => {
const num = Math.trunc(Math.random() * 10**10)
ws.send(`<div id=idMessage>${num}</div>`)
}, 1000)
}, (ws) => clearInterval(ws.interval))
// Define the server
const server = http.createServer(async (req, res) => {
try {
await handleRequest(req, res)
} catch (error) {
console.error(`Error serving ${req.url}`)
console.error(req.body)
console.error(error)
}
})
// This handles all the non-websocket requests
async function handleRequest (req, res) {
// If the URL starts with htmx, serve the src/ root version of htmx
if (req.url.startsWith('/htmx')) {
const resource = req.url.substring(6)
res.setHeader('Content-Type', 'text/javascript')
const fp = path.join('../../src', resource)
return serveFile(res, fp)
}
// If the URL matches one of these, it's an event stream
if (req.url.startsWith("/posts.html")) return servePosts(req, res)
if (req.url === "/comments.html") return makeStream(req, res, DATA.comments, formatComment)
if (req.url === "/albums.html") return makeStream(req, res, DATA.albums, formatAlbum)
if (req.url === "/todos.html") return makeStream(req, res, DATA.todos, formatTodo)
if (req.url === "/users.html") return makeStream(req, res, DATA.users, formatUser)
// Randomly-generated HTML
if (req.url === "/page/random") return serveRandomHtml(req, res)
// Otherwise, attempt to serve the file from ./static and return a 404 on failure
try {
await serveFileFromStatic(req, res)
} catch (error) {
sendNotFound(res)
}
}
// Attach the websockets
server.on('upgrade', (request, socket, head) => {
if (request.url === '/echo') ECHO_WS.handle(request, socket, head)
if (request.url === '/heartbeat') HEARTBEAT_WS.handle(request, socket, head)
})
// Start listening
server.listen(PORT, HOSTNAME, () => {
console.log('Loading the WebSocket / Server-Side Event Tests...');
console.log(`You can run them at http://${HOSTNAME}:${PORT}/`);
})
function createWebSocket (connectionFunc, closeFunc) {
const server = new WebSocketServer({ noServer: true })
server.on('connection', connectionFunc)
if (closeFunc) server.on('close', closeFunc)
const handle = (request, socket, head) => {
server.handleUpgrade(request, socket, head, (ws) => {
server.emit('connection', ws, request)
})
}
return { handle }
}
async function serveFileFromStatic (req, res) {
// For the root, serve the static index.html file
const resource = req.url === '/' ? '/index.html' : req.url
let fp = path.join('./static/', resource)
let lstat = await fs.lstat(fp)
// If it's a directory, re-set the fp to be the index.html of that directory
if (lstat.isDirectory()) {
fp = path.join(fp, 'index.html')
lstat = await fs.lstat(fp)
}
if (!lstat.isFile) return sendNotFound(res)
const withBase = fp.endsWith('.html')
return serveFile(res, fp, withBase)
}
async function serveFile (res, fp, withBase) {
try {
const file = await fs.readFile(fp)
let text = file.toString()
if (withBase) text = SITE_BASE + text
res.end(text)
} catch (error) {
console.error(error)
sendNotFound(res)
}
}
function servePosts (req, res) {
// Why do we have to specify a fake protocol here? Because WHATWG doesn't support relative URLs
// Maddening discussion here: https://github.com/whatwg/url/issues/531
const url = new URL(req.url, "thismessage:/")
const types = url.searchParams?.get('types')
const numEvents = types ? types.split(',').length : 0
makeStream(req, res, DATA.posts, formatPost, numEvents)
}
function sendNotFound(res) {
res.statusCode = 404
res.setHeader('Content-Type', 'text/plain')
res.end('404 NOT FOUND')
}
function makeStream(req, res, arr, formatFunc, numEvents = 0) {
res.writeHead(200, {
'Content-Type': 'text/event-stream',
Connection: 'keep-alive',
'Cache-Control': 'no-cache'
})
// Make the intervals somewhat random, between 200 and 400ms
// We have some tests that create multiple streams at once, so this ensures they're all visibile
const intervalLength = Math.floor(Math.random() * 200) + 200
let i = 0
const interval = setInterval(() => {
if (i == arr.length) i = 0
const item = arr[i]
try {
const evenNum = Math.floor(Math.random() * numEvents) + 1
const eventName = numEvents > 0 ? `Event${evenNum}` : '(none)'
item.event = eventName
const formattedData = formatFunc(item).replace(/\n/g, ' ')
const event = `${numEvents > 0 ? `event: ${eventName}\n` : ''}data: ${formattedData}\n\n`
res.write(event)
i++
} catch (error) {
// Stop the interval if it errors for any reason
clearInterval(interval)
}
}, intervalLength)
req.on('close', () => {
res.end('OK')
clearInterval(interval)
})
}
function serveRandomHtml(_req, res) {
const page_num = Math.trunc(Math.random() * 10**10)
const html_num = Math.trunc(Math.random() * 10**10)
const html = `
<div>
This is page ${page_num}
<br><br>
Randomly generated <b>HTML</b> ${html_num}
<br><br>
I wish I were a haiku.
</div>
`
res.end(html)
}
function formatPost (post) {
return `
<div>
<div class="bold">Post: ${post.title}</div>
<div>${post.body}</div>
<div>id: ${post.id}</div>
<div>user: ${post.userId}</div>
<div>event: ${post.event}</div>
</div>
`
}
function formatComment (comment) {
return `
<div>
<div class="bold">Comment: ${comment.name}</div>
<div>${comment.email}</div>
<div>id: ${comment.body}</div>
<div>event: ${comment.event}</div>
</div>
`
}
function formatAlbum (album) {
return `
<div>
<div class="bold">Album: ${album.title}</div>
<div>id: ${album.id}</div>
<div>event: ${album.event}</div>
</div>
`
}
function formatTodo (todo) {
return `
<div>
<div class="bold">To-Do: ${todo.title}</div>
<div>complete? ${todo.completed}</div>
<div>event: ${todo.event}</div>
</div>
`
}
function formatUser (user) {
return `
<div>
<div class="bold">User: ${user.name}</div>
<div>${user.email}</div>
<div>${user.address.street} ${user.address.suite}<br>${user.address.city}, ${user.address.zipcode}</div>
<div>event: ${user.event}</div>
</div>
`
}

View File

@ -87,7 +87,7 @@ return (function () {
sock.binaryType = htmx.config.wsBinaryType;
return sock;
},
version: "1.9.7"
version: "1.9.8"
};
/** @type {import("./htmx").HtmxInternalApi} */