Skip to content

Commit

Permalink
Use response targets extension
Browse files Browse the repository at this point in the history
  • Loading branch information
svera committed Jul 14, 2024
1 parent 717b6bb commit 0cba307
Show file tree
Hide file tree
Showing 7 changed files with 194 additions and 54 deletions.
4 changes: 2 additions & 2 deletions internal/webserver/controller/user/update.go
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ func (u *Controller) updateUserData(c *fiber.Ctx, user *model.User, session mode
"MinPasswordLength": u.config.MinPasswordLength,
"UsernamePattern": model.UsernamePattern,
"Errors": validationErrs,
}, "layout")
}, "partials/main")
}

if err := u.repository.Update(user); err != nil {
Expand Down Expand Up @@ -89,7 +89,7 @@ func (u *Controller) updateUserData(c *fiber.Ctx, user *model.User, session mode
"UsernamePattern": model.UsernamePattern,
"Errors": validationErrs,
"Message": "Profile updated",
}, "layout")
}, "partials/main")
}

func (u *Controller) validate(c *fiber.Ctx, user *model.User, session model.Session) (map[string]string, error) {
Expand Down
2 changes: 1 addition & 1 deletion internal/webserver/embedded/js/foliate-js/reader.js
Original file line number Diff line number Diff line change
Expand Up @@ -245,7 +245,7 @@ const url = document.getElementById('url').value
if (url) fetch(url)
.then(res => {
if (res.status == 403) {
location.reload()
return location.reload()
}
return res.blob()
})
Expand Down
129 changes: 129 additions & 0 deletions internal/webserver/embedded/js/response-targets.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
(function() {
/** @type {import("../htmx").HtmxInternalApi} */
var api

var attrPrefix = 'hx-target-'

// IE11 doesn't support string.startsWith
function startsWith(str, prefix) {
return str.substring(0, prefix.length) === prefix
}

/**
* @param {HTMLElement} elt
* @param {number} respCode
* @returns {HTMLElement | null}
*/
function getRespCodeTarget(elt, respCodeNumber) {
if (!elt || !respCodeNumber) return null

var respCode = respCodeNumber.toString()

// '*' 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'
]
if (startsWith(respCode, '4') || startsWith(respCode, '5')) {
attrPossibilities.push('error')
}

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)
}
}
}

return null
}

/** @param {Event} evt */
function handleErrorFlag(evt) {
if (evt.detail.isError) {
if (htmx.config.responseTargetUnsetsError) {
evt.detail.isError = false
}
} else if (htmx.config.responseTargetSetsError) {
evt.detail.isError = true
}
}

htmx.defineExtension('response-targets', {

/** @param {import("../htmx").HtmxInternalApi} apiRef */
init: function(apiRef) {
api = apiRef

if (htmx.config.responseTargetUnsetsError === undefined) {
htmx.config.responseTargetUnsetsError = true
}
if (htmx.config.responseTargetSetsError === undefined) {
htmx.config.responseTargetSetsError = false
}
if (htmx.config.responseTargetPrefersExisting === undefined) {
htmx.config.responseTargetPrefersExisting = false
}
if (htmx.config.responseTargetPrefersRetargetHeader === undefined) {
htmx.config.responseTargetPrefersRetargetHeader = true
}
},

/**
* @param {string} name
* @param {Event} evt
*/
onEvent: function(name, evt) {
if (name === 'htmx:beforeSwap' &&
evt.detail.xhr &&
evt.detail.xhr.status !== 200) {
if (evt.detail.target) {
if (htmx.config.responseTargetPrefersExisting) {
evt.detail.shouldSwap = true
handleErrorFlag(evt)
return true
}
if (htmx.config.responseTargetPrefersRetargetHeader &&
evt.detail.xhr.getAllResponseHeaders().match(/HX-Retarget:/i)) {
evt.detail.shouldSwap = true
handleErrorFlag(evt)
return true
}
}
if (!evt.detail.requestConfig) {
return true
}
var target = getRespCodeTarget(evt.detail.requestConfig.elt, evt.detail.xhr.status)
if (target) {
handleErrorFlag(evt)
evt.detail.shouldSwap = true
evt.detail.target = target
}
return true
}
}
})
})()
6 changes: 5 additions & 1 deletion internal/webserver/embedded/js/xh-error-check.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,12 @@ document.body.addEventListener('htmx:afterRequest', function (evt) {
// Server error with response contents, equivalent to htmx:responseError
const xhr = evt.detail.xhr;
if (xhr.status == "403") {
location.reload();
location.reload()
}
if (xhr.status == "400") {
return
}

console.warn("Server error", evt.detail)
errorTarget.innerText = `Unexpected server error: ${xhr.status} - ${xhr.statusText}`;
errorTarget.removeAttribute("hidden");
Expand Down
57 changes: 10 additions & 47 deletions internal/webserver/embedded/views/layout.html
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@
<script src="/js/color-mode-toggler.js"></script>
</head>

<body class="d-flex flex-column h-100">
<body class="d-flex flex-column h-100" hx-ext="response-targets">
<header>
<nav class="navbar navbar-expand-md fixed-top">
<div class="container">
Expand Down Expand Up @@ -130,54 +130,16 @@ <h5 class="offcanvas-title" id="offcanvasNavbarLabel">Coreander</h5>
</div>
</nav>
</header>

<main class="mt-5">
<div class="container mt-5">
{{if .RemainingIndexingTime}}
<div class="row text-start">
<div class="alert alert-warning" role="alert">
<p>{{t .Lang "Indexing in progress, search results may not be accurate."}}</p>
<div class="progress" role="progressbar" aria-label='{{t .Lang "Indexing progress"}}' aria-valuenow="0" aria-valuemin="0" aria-valuemax="100">
<div class="progress-bar" style="width: {{.IndexingProgressPercentage}}%">{{.IndexingProgressPercentage}}%</div>
</div>
<p class="text-end"><small>{{t .Lang "Remaining time: %s minutes" .RemainingIndexingTime}}</small></p>
</div>
</div>
{{end}}

<div class="row text-start">
<div class="alert alert-danger" role="alert" hidden id="box-error">
</div>
</div>

{{if .Error}}
<div class="row text-start">
<div class="alert alert-danger" role="alert">
{{t .Lang .Error}}
</div>
</div>
{{end}}

{{if .Warning}}
<div class="row text-start">
<div class="alert alert-warning" role="alert">
{{t .Lang .Warning}}
</div>
</div>
{{end}}

{{if .Message}}
<div class="row text-start">
<div class="alert alert-success" role="alert">
{{t .Lang .Message}}
</div>
</div>
{{end}}

{{embed}}
</div>
{{template "partials/main"
dict "Lang" .Lang
"RemainingIndexingTime" .RemainingIndexingTime
"IndexingProgressPercentage" .IndexingProgressPercentage
"Error" .Error
"Warning" .Warning
"Message" .Message
"Embed" .Embed}}
</main>

<footer class="footer mt-auto py-5">
<div class="container">
<span class="text-muted"><small>{{t .Lang "Made with words by Sergio Vera"}} - <a
Expand All @@ -187,6 +149,7 @@ <h5 class="offcanvas-title" id="offcanvasNavbarLabel">Coreander</h5>
</footer>
<script src="/js/bootstrap.bundle.min.js"></script>
<script src="/js/htmx.min.js"></script>
<script src="/js/response-targets.js"></script>
</body>

</html>
44 changes: 44 additions & 0 deletions internal/webserver/embedded/views/partials/main.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
<div class="container mt-5">
{{if .RemainingIndexingTime}}
<div class="row text-start">
<div class="alert alert-warning" role="alert">
<p>{{t .Lang "Indexing in progress, search results may not be accurate."}}</p>
<div class="progress" role="progressbar" aria-label='{{t .Lang "Indexing progress"}}' aria-valuenow="0" aria-valuemin="0" aria-valuemax="100">
<div class="progress-bar" style="width: {{.IndexingProgressPercentage}}%">{{.IndexingProgressPercentage}}%</div>
</div>
<p class="text-end"><small>{{t .Lang "Remaining time: %s minutes" .RemainingIndexingTime}}</small></p>
</div>
</div>
{{end}}

<div class="row text-start">
<div class="alert alert-danger" role="alert" hidden id="box-error">
</div>
</div>

{{if .Error}}
<div class="row text-start">
<div class="alert alert-danger" role="alert">
{{t .Lang .Error}}
</div>
</div>
{{end}}

{{if .Warning}}
<div class="row text-start">
<div class="alert alert-warning" role="alert">
{{t .Lang .Warning}}
</div>
</div>
{{end}}

{{if .Message}}
<div class="row text-start">
<div class="alert alert-success" role="alert">
{{t .Lang .Message}}
</div>
</div>
{{end}}

{{embed}}
</div>
6 changes: 3 additions & 3 deletions internal/webserver/embedded/views/users/edit.html
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,10 @@
type="button" role="tab" aria-controls="profile-tab-pane" aria-selected="false">{{t .Lang "Change password"}}</button>
</li>
</ul>
<div class="tab-content ">
<div class="tab-content">
<div class='tab-pane fade p-3 {{if ne .ActiveTab "password"}}show active{{end}}' id="profile-tab-pane" role="tabpanel" aria-labelledby="profile-tab"
tabindex="0">
<form hx-put="/{{.Lang}}/users/{{.User.Username}}" hx-target="body">
<form hx-put="/{{.Lang}}/users/{{.User.Username}}" hx-target="main" hx-target-400="main">
<div class="mb-3">
<label for="name" class="form-label">{{t .Lang "Name"}}</label>
<input type="text" name="name" class='form-control {{if ne (index .Errors "name") ""}}is-invalid{{end}}' id="name" required="required" value="{{.User.Name}}" maxlength="50">
Expand Down Expand Up @@ -103,4 +103,4 @@
</div>
</div>

<script type="text/javascript" src="/js/xh-error-check.js"></script>
<!--<script type="text/javascript" src="/js/xh-error-check.js"></script>-->

0 comments on commit 0cba307

Please sign in to comment.