diff --git a/backend/api/authentication.ts b/backend/api/authentication.ts
index dc4a601ea..a862b7e07 100644
--- a/backend/api/authentication.ts
+++ b/backend/api/authentication.ts
@@ -100,6 +100,11 @@ async function routes(app: FastifyInstance) {
registration: {
type: 'boolean'
},
+ allowForgotPassword: {
+ type: 'boolean',
+ description:
+ 'Whether this strategy offers a password reset from the login screen. False for a strategy whose module has no such setting.'
+ },
strategy: {
type: 'object',
properties: {
@@ -136,7 +141,14 @@ async function routes(app: FastifyInstance) {
if (!site) {
return reply.badRequest('Invalid Site ID')
}
- const activeStrategies = await WIKI.models.authentication.getStrategies({ enabledOnly: true })
+ /*
+ `getActiveStrategies` rather than the raw rows: it completes each config from the module's
+ declared defaults, so a prop added to a module after a strategy was configured reads as its
+ default here instead of as a missing key.
+ */
+ const activeStrategies = (await WIKI.models.authentication.getActiveStrategies()).filter(
+ (str: any) => str.isEnabled
+ )
// -> A site created before it had strategies configured has no list at all
const configuredStrategies = site.config.authStrategies ?? []
const siteStrategies = activeStrategies
@@ -150,6 +162,14 @@ async function routes(app: FastifyInstance) {
activeStrategy: {
displayName: str.displayName,
registration: str.registration,
+ /*
+ Named explicitly, like every other field here: this endpoint is public and a strategy's
+ config is where an OAuth client secret lives, so nothing may reach it by spreading.
+
+ A module that declares no such prop reads as false, which is correct rather than a
+ default -- a strategy with no password of its own has no password to reset.
+ */
+ allowForgotPassword: str.config?.allowForgotPassword === true,
strategy: {
key: authModule?.key ?? str.module,
title: authModule?.title ?? str.module,
diff --git a/backend/locales/en.json b/backend/locales/en.json
index 6c938a7e7..b0f779732 100644
--- a/backend/locales/en.json
+++ b/backend/locales/en.json
@@ -1737,10 +1737,10 @@
"editor.emoji.smileysEmotion": "Smileys & Emotion",
"editor.emoji.symbols": "Symbols",
"editor.emoji.travelPlaces": "Travel & Places",
- "editor.markup.admonitionDanger": "Danger / Caution Admonition",
+ "editor.markup.admonitionDanger": "Caution Admonition",
"editor.markup.admonitionImportant": "Important Admonition",
- "editor.markup.admonitionInfo": "Info / Note Admonition",
- "editor.markup.admonitionSuccess": "Tip / Success Admonition",
+ "editor.markup.admonitionInfo": "Note Admonition",
+ "editor.markup.admonitionSuccess": "Tip Admonition",
"editor.markup.admonitionWarning": "Warning Admonition",
"editor.markup.blockquote": "Blockquote",
"editor.markup.blockquoteAdmonitions": "Blockquote / Admonition",
@@ -1750,6 +1750,7 @@
"editor.markup.blockquoteWarning": "Warning Blockquote",
"editor.markup.bold": "Bold",
"editor.markup.distractionFreeMode": "Distraction Free Mode",
+ "editor.markup.editTable": "Edit in Table Editor",
"editor.markup.header": "Header",
"editor.markup.headerLevel": "Header {level}",
"editor.markup.heading": "Heading {level}",
@@ -1763,7 +1764,6 @@
"editor.markup.insertHorizontalBar": "Insert Horizontal Bar",
"editor.markup.insertLink": "Insert Link",
"editor.markup.insertMathExpression": "Insert Math Expression",
- "editor.markup.editTable": "Edit in Table Editor",
"editor.markup.insertTable": "Insert Table",
"editor.markup.insertTabset": "Insert Tabset",
"editor.markup.insertVideoAudio": "Insert Video / Audio",
diff --git a/backend/modules/authentication/local/definition.yml b/backend/modules/authentication/local/definition.yml
index 85ff0ebd8..bc92a2245 100644
--- a/backend/modules/authentication/local/definition.yml
+++ b/backend/modules/authentication/local/definition.yml
@@ -23,3 +23,9 @@ props:
hint: Send a verification email to the user with a validation link when registering (if registration is enabled).
icon: received
default: true
+ allowForgotPassword:
+ type: Boolean
+ title: Allow Forgot Password
+ hint: Users who have forgotten their password can request a reset link by email from the login screen. Turn this off where passwords are handed out rather than chosen.
+ icon: password-reset
+ default: true
diff --git a/frontend/public/_assets/icons/fluent-access.svg b/frontend/public/_assets/icons/fluent-access.svg
new file mode 100644
index 000000000..d15d3dd64
--- /dev/null
+++ b/frontend/public/_assets/icons/fluent-access.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/frontend/public/_assets/icons/fluent-history.svg b/frontend/public/_assets/icons/fluent-history.svg
new file mode 100644
index 000000000..71a044764
--- /dev/null
+++ b/frontend/public/_assets/icons/fluent-history.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/frontend/public/_assets/icons/fluent-home.svg b/frontend/public/_assets/icons/fluent-home.svg
new file mode 100644
index 000000000..407c9df7e
--- /dev/null
+++ b/frontend/public/_assets/icons/fluent-home.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/frontend/public/_assets/icons/fluent-live.svg b/frontend/public/_assets/icons/fluent-live.svg
new file mode 100644
index 000000000..8d77f12b8
--- /dev/null
+++ b/frontend/public/_assets/icons/fluent-live.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/frontend/public/_assets/icons/fluent-source-code.svg b/frontend/public/_assets/icons/fluent-source-code.svg
new file mode 100644
index 000000000..85a9ccc21
--- /dev/null
+++ b/frontend/public/_assets/icons/fluent-source-code.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/frontend/public/_assets/icons/fluent-task.svg b/frontend/public/_assets/icons/fluent-task.svg
new file mode 100644
index 000000000..00007f39b
--- /dev/null
+++ b/frontend/public/_assets/icons/fluent-task.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/frontend/public/_assets/icons/fluent-unfold.svg b/frontend/public/_assets/icons/fluent-unfold.svg
new file mode 100644
index 000000000..2414fc02a
--- /dev/null
+++ b/frontend/public/_assets/icons/fluent-unfold.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/frontend/public/_assets/icons/fluent-web-design.svg b/frontend/public/_assets/icons/fluent-web-design.svg
new file mode 100644
index 000000000..6e84497a6
--- /dev/null
+++ b/frontend/public/_assets/icons/fluent-web-design.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/frontend/src/components/AuthLoginPanel.vue b/frontend/src/components/AuthLoginPanel.vue
index fa4bf0bdf..6b38fda9a 100644
--- a/frontend/src/components/AuthLoginPanel.vue
+++ b/frontend/src/components/AuthLoginPanel.vue
@@ -112,8 +112,11 @@
no-caps
icon="la:user-plus"
@click="switchTo(`register`)" />
+
-
+ {{
t('editor.markup.blockquoteAdmonitions')
}}
diff --git a/frontend/src/components/PageHeader.vue b/frontend/src/components/PageHeader.vue
index f95f41d5d..949cf9cf8 100644
--- a/frontend/src/components/PageHeader.vue
+++ b/frontend/src/components/PageHeader.vue
@@ -13,7 +13,11 @@
style="min-height: 64px">
-
+
+
+
+
@@ -451,6 +455,19 @@ async function seedEditables() {
syncEditable(descriptionEl.value, pageStore.description)
}
+/**
+ * The icon picked from the header.
+ *
+ * The same two steps the title and the description take below, and for the same reason: an icon is part
+ * of the page, so changing it has to leave the editor holding an unsaved change. Bound through the event
+ * rather than `v-model` because that wrote the store and nothing else -- Save Changes stayed disabled
+ * until something else was edited, and closing the editor threw the new icon away without a word.
+ */
+function setIcon(icon) {
+ pageStore.icon = icon
+ editorStore.lastChangeTimestamp = Temporal.Now.instant()
+}
+
function onEditableInput(field, event) {
// -> Clearing the field leaves a browser-inserted ` ` behind, which contributes nothing to the
// text but does hold a second line open under the placeholder
diff --git a/frontend/src/css/_page-contents.scss b/frontend/src/css/_page-contents.scss
index b092d6fec..f439aa023 100644
--- a/frontend/src/css/_page-contents.scss
+++ b/frontend/src/css/_page-contents.scss
@@ -765,7 +765,20 @@
&::before {
content: '';
position: absolute;
- top: 1em;
+ /*
+ Centred on the CAP BAND of the first line, not on the line box that holds it.
+
+ A line box is ascent plus descent, and a title like "Note" or "Caution" inks only the part
+ between the cap line and the baseline -- so an icon centred on the line came out a pixel high,
+ and at this size that reads as its bottom edge running flush with the baseline while its top
+ towers over the capitals. Which is why the warning triangle looked right and nothing else did:
+ its mass is all at the base, so a bottom-flush box still reads as balanced.
+
+ `0.9em` is the padding above the first line, `0.79em` the middle of the cap band within it
+ (half-leading plus ascent, less half the cap height, for the app's Roboto at 1.6 line-height),
+ and `0.625em` half the icon.
+ */
+ top: calc(0.9em + 0.79em - 0.625em);
left: 1.1em;
width: 1.25em;
height: 1.25em;
diff --git a/frontend/src/renderers/modules/github-alerts.js b/frontend/src/renderers/modules/github-alerts.js
index 7c14cbe3d..447182e98 100644
--- a/frontend/src/renderers/modules/github-alerts.js
+++ b/frontend/src/renderers/modules/github-alerts.js
@@ -22,26 +22,32 @@ const KINDS = new Map([
])
/**
- * The marker, which has to be the whole of the blockquote's first line.
+ * The marker, and whatever the author wrote after it on the same line.
*
- * Anything after it on that line means the author wrote a blockquote that happens to open with
- * brackets, which is what GitHub decides too — and the line is then left exactly as it was typed.
+ * That remainder is the admonition's title: `> [!NOTE] Read this first` is headed "Read this first"
+ * rather than "Note". A deliberate step past GitHub, which renders those words as the first line of the
+ * quote instead — and the step every other implementation of these takes, because a marker's own label
+ * says only what kind of aside it is, never what this one is about.
+ *
+ * Left off, the kind's own label stands in, exactly as before. What is captured is raw markdown and is
+ * parsed as such (see `titleTokens`), so a title may hold a link or a `code` span like any other line.
*/
-const MARKER = /^\[!([a-z]+)\][ \t]*(?:\n|$)/i
+const MARKER = /^\[!([a-z]+)\][ \t]*([^\n]*)(?:\n|$)/i
/**
- * The label, as three tokens: a paragraph carrying a class, its inline content, and the close.
+ * The title, as three tokens: a paragraph carrying a class, its inline content, and the close.
*
* The inline token is left with nothing but `content`; the core `inline` rule runs after this one and
- * is what turns that into children, the same as for every other paragraph on the page.
+ * is what turns that into children, the same as for every other paragraph on the page — which is also
+ * what lets an author's own title carry markdown, and what keeps it escaped if it carries anything else.
*/
-function labelTokens(state, label) {
+function titleTokens(state, title) {
const open = new state.Token('paragraph_open', 'p', 1)
open.attrSet('class', 'alert-title')
open.block = true
const inline = new state.Token('inline', '', 0)
- inline.content = label
+ inline.content = title
inline.children = []
const close = new state.Token('paragraph_close', 'p', -1)
@@ -79,13 +85,16 @@ export default (md) => {
// top of that is what the stylesheet is written to expect
tokens[i].attrJoin('class', kind.className)
+ // -> A title of nothing but spaces is no title: the kind says what it is instead
+ const title = marker[2].trim() || kind.label
+
const rest = tokens[i + 2].content.slice(marker[0].length)
if (rest) {
tokens[i + 2].content = rest
- tokens.splice(i + 1, 0, ...labelTokens(state, kind.label))
+ tokens.splice(i + 1, 0, ...titleTokens(state, title))
} else {
- // -> The marker was the whole paragraph, so the label takes its place rather than joining it
- tokens.splice(i + 1, 3, ...labelTokens(state, kind.label))
+ // -> The marker line was the whole paragraph, so the title takes its place rather than joining it
+ tokens.splice(i + 1, 3, ...titleTokens(state, title))
}
}
})