mirror of
https://codeberg.org/keyoxide/keyoxide-web.git
synced 2024-12-22 14:59:29 -07:00
feat: support profile theming
This commit is contained in:
parent
df48b92732
commit
99217f6a3d
8 changed files with 109 additions and 16 deletions
|
@ -8,7 +8,8 @@
|
|||
"ajv": "^8.6.3",
|
||||
"bent": "^7.3.12",
|
||||
"body-parser": "^1.19.0",
|
||||
"doipjs": "^1.2.4",
|
||||
"colorjs.io": "^0.4.5",
|
||||
"doipjs": "^1.2.5",
|
||||
"dotenv": "^16.0.3",
|
||||
"express": "^4.17.1",
|
||||
"express-http-context2": "^1.0.0",
|
||||
|
|
|
@ -32,7 +32,7 @@ import bodyParserImport from 'body-parser'
|
|||
import { rateLimit } from 'express-rate-limit'
|
||||
import { generateSignatureProfile, utils, generateWKDProfile, generateHKPProfile, generateAutoProfile, generateKeybaseProfile } from '../server/index.js'
|
||||
import { Profile } from 'doipjs'
|
||||
import { getMetaFromReq } from '../server/utils.js'
|
||||
import { generateProfileTheme, getMetaFromReq } from '../server/utils.js'
|
||||
import logger from '../log.js'
|
||||
|
||||
const router = express.Router()
|
||||
|
@ -133,6 +133,7 @@ router.get('/keybase/:username/:fingerprint', profileRateLimiter, async (req, re
|
|||
|
||||
router.get('/:id', profileRateLimiter, async (req, res) => {
|
||||
const data = await generateAutoProfile(req.params.id)
|
||||
const theme = generateProfileTheme(data)
|
||||
const title = utils.generatePageTitle('profile', data)
|
||||
res.set('ariadne-identity-proof', data.identifier)
|
||||
res.render('profile', {
|
||||
|
@ -140,6 +141,7 @@ router.get('/:id', profileRateLimiter, async (req, res) => {
|
|||
data: data instanceof Profile ? data.toJSON() : data,
|
||||
enable_message_encryption: false,
|
||||
enable_signature_verification: false,
|
||||
theme,
|
||||
meta: getMetaFromReq(req)
|
||||
})
|
||||
})
|
||||
|
|
|
@ -28,6 +28,8 @@ if any, to sign a "copyright disclaimer" for the program, if necessary. For
|
|||
more information on this, and how to apply and follow the GNU AGPL, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
import { webcrypto as crypto } from 'crypto'
|
||||
import { Profile } from 'doipjs'
|
||||
import Color from 'colorjs.io'
|
||||
|
||||
export async function computeWKDLocalPart (localPart) {
|
||||
const localPartEncoded = new TextEncoder().encode(localPart.toLowerCase())
|
||||
|
@ -87,3 +89,51 @@ export function getMetaFromReq (req) {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function generateProfileTheme (/** @type {Profile} */ profile) {
|
||||
if (!(profile && profile instanceof Profile)) return null
|
||||
|
||||
if (!profile.personas[profile.primaryPersonaIndex].themeColor) return null
|
||||
|
||||
let base
|
||||
try {
|
||||
base = new Color(profile.personas[profile.primaryPersonaIndex].themeColor)
|
||||
} catch (_) {
|
||||
return null
|
||||
}
|
||||
|
||||
if (base.to('hsl').hsl[0].isNaN) return null
|
||||
if (base.to('hsl').hsl[2] === 0) return null
|
||||
|
||||
const primaryLight = base.to('hsl')
|
||||
primaryLight.hsl[2] = 40
|
||||
const primaryDark = base.to('hsl')
|
||||
primaryDark.hsl[2] = 80
|
||||
|
||||
const primarySubtleLight = base.to('hsl')
|
||||
primarySubtleLight.hsl[2] = 50
|
||||
const primarySubtleDark = base.to('hsl')
|
||||
primarySubtleDark.hsl[2] = 70
|
||||
|
||||
const backgroundLight = base.to('hsl')
|
||||
backgroundLight.hsl[2] = 98
|
||||
const backgroundDark = base.to('hsl')
|
||||
backgroundDark.hsl[1] = 20
|
||||
backgroundDark.hsl[2] = 5
|
||||
|
||||
return {
|
||||
base: base.toString({ format: 'hex' }),
|
||||
primary: {
|
||||
light: primaryLight.toString(),
|
||||
dark: primaryDark.toString()
|
||||
},
|
||||
primarySubtle: {
|
||||
light: primarySubtleLight.toString(),
|
||||
dark: primarySubtleDark.toString()
|
||||
},
|
||||
background: {
|
||||
light: backgroundLight.toString(),
|
||||
dark: backgroundDark.toString()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -58,8 +58,9 @@ kx-claim {
|
|||
}
|
||||
|
||||
hr {
|
||||
margin: 8px 0;
|
||||
border: none;
|
||||
border-top: 2px solid var(--background-color);
|
||||
border-top: 2px solid var(--header-background-color);
|
||||
}
|
||||
|
||||
.content {
|
||||
|
|
|
@ -108,6 +108,18 @@ a.button {
|
|||
border-color: var(--button-border-color-hover);
|
||||
color: var(--button-text-color-hover);
|
||||
}
|
||||
|
||||
&.themed {
|
||||
padding: 8px 12px;
|
||||
color: var(--text-color-inverse);
|
||||
background-color: var(--primary-color);
|
||||
border: 0;
|
||||
|
||||
&:hover {
|
||||
color: var(--text-color-inverse);
|
||||
background-color: var(--primary-color-subtle);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
button {
|
||||
|
|
|
@ -64,14 +64,25 @@ more information on this, and how to apply and follow the GNU AGPL, see <https:/
|
|||
}
|
||||
|
||||
:root {
|
||||
--primary-color: var(--purple-700);
|
||||
--primary-color-subtle: var(--purple-400);
|
||||
--primary-color-light: var(--purple-700);
|
||||
--primary-color-dark: var(--purple-300);
|
||||
|
||||
--body-background-color: #fafafa;
|
||||
--primary-color-subtle-light: var(--purple-400);
|
||||
--primary-color-subtle-dark: var(--purple-500);
|
||||
|
||||
--background-color-light: var(#fafafa);
|
||||
--background-color-dark: var(#0a0a0a);
|
||||
}
|
||||
:root {
|
||||
--primary-color: var(--primary-color-light);
|
||||
--primary-color-subtle: var(--primary-color-subtle-light);
|
||||
|
||||
--body-background-color: var(--background-color-light);
|
||||
--section-background-color: var(--white);
|
||||
|
||||
--text-color: var(--grey-800);
|
||||
--text-color-subtle: var(--grey-500);
|
||||
--text-color-inverse: var(--white);
|
||||
--h1-color: var(--text-color);
|
||||
--h2-color: var(--text-color);
|
||||
--h2-small-color: var(--primary-color-subtle);
|
||||
|
@ -82,7 +93,7 @@ more information on this, and how to apply and follow the GNU AGPL, see <https:/
|
|||
|
||||
--link-color: var(--blue-700);
|
||||
--link-color-subtle: var(--text-color);
|
||||
--link-color-hover: var(--purple-700);
|
||||
--link-color-hover: var(--primary-color);
|
||||
|
||||
--line-color-subtle: var(--grey-200);
|
||||
|
||||
|
@ -105,14 +116,15 @@ more information on this, and how to apply and follow the GNU AGPL, see <https:/
|
|||
@media (prefers-color-scheme: dark) {
|
||||
color-scheme: dark;
|
||||
|
||||
--primary-color: var(--purple-300);
|
||||
--primary-color-subtle: var(--purple-500);
|
||||
--primary-color: var(--primary-color-dark);
|
||||
--primary-color-subtle: var(--primary-color-subtle-dark);
|
||||
|
||||
--body-background-color: #0a0a0a;
|
||||
--body-background-color: var(--background-color-dark);
|
||||
--section-background-color: var(--grey-900);
|
||||
|
||||
--text-color: var(--grey-50);
|
||||
--text-color-subtle: var(--grey-300);
|
||||
--text-color-inverse: var(--grey-800);
|
||||
--h1-color: var(--text-color);
|
||||
--h2-color: var(--text-color);
|
||||
--h2-small-color: var(--primary-color-subtle);
|
||||
|
|
|
@ -111,6 +111,16 @@ block content
|
|||
input(type='submit', name='submit', value='Generate profile')
|
||||
|
||||
unless (isSignature && !signature)
|
||||
if (theme)
|
||||
style
|
||||
| :root {
|
||||
| --primary-color-light: #{theme.primary.light};
|
||||
| --primary-color-dark: #{theme.primary.dark};
|
||||
| --primary-color-subtle-light: #{theme.primarySubtle.light};
|
||||
| --primary-color-subtle-dark: #{theme.primarySubtle.dark};
|
||||
| --background-color-light: #{theme.background.light};
|
||||
| --background-color-dark: #{theme.background.dark};
|
||||
| }
|
||||
.profile__header
|
||||
img.profile__avatar.u-logo(src=data.personas[data.primaryPersonaIndex].avatarUrl alt="avatar")
|
||||
|
||||
|
@ -132,8 +142,8 @@ block content
|
|||
+generatePersona(persona, false)
|
||||
h2 Profile
|
||||
if data.verifiers.length > 0
|
||||
button(onClick=`showQR('${data.verifiers[0].url}', 'profile_verifier_url')` aria-label='Show profile ID QR') Keyoxide profile QR
|
||||
button(onClick=`showQR('${data.identifier}', 'profile_identifier')` aria-label='Show profile ID QR') Profile ID QR
|
||||
button.themed(onClick=`showQR('${data.verifiers[0].url}', 'profile_verifier_url')` aria-label='Show profile ID QR') Keyoxide profile QR
|
||||
button.themed(onClick=`showQR('${data.identifier}', 'profile_identifier')` aria-label='Show profile ID QR') Profile ID QR
|
||||
|
||||
section
|
||||
h2 Profile information
|
||||
|
|
13
yarn.lock
13
yarn.lock
|
@ -1579,6 +1579,11 @@ colorette@^2.0.14:
|
|||
resolved "https://registry.yarnpkg.com/colorette/-/colorette-2.0.20.tgz#9eb793e6833067f7235902fcd3b09917a000a95a"
|
||||
integrity sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==
|
||||
|
||||
colorjs.io@^0.4.5:
|
||||
version "0.4.5"
|
||||
resolved "https://registry.yarnpkg.com/colorjs.io/-/colorjs.io-0.4.5.tgz#7775f787ff90aca7a38f6edb7b7c0f8cce1e6418"
|
||||
integrity sha512-yCtUNCmge7llyfd/Wou19PMAcf5yC3XXhgFoAh6zsO2pGswhUPBaaUh8jzgHnXtXuZyFKzXZNAnyF5i+apICow==
|
||||
|
||||
colorspace@1.1.x:
|
||||
version "1.1.4"
|
||||
resolved "https://registry.yarnpkg.com/colorspace/-/colorspace-1.1.4.tgz#8d442d1186152f60453bf8070cd66eb364e59243"
|
||||
|
@ -1837,10 +1842,10 @@ doctypes@^1.1.0:
|
|||
resolved "https://registry.yarnpkg.com/doctypes/-/doctypes-1.1.0.tgz#ea80b106a87538774e8a3a4a5afe293de489e0a9"
|
||||
integrity sha512-LLBi6pEqS6Do3EKQ3J0NqHWV5hhb78Pi8vvESYwyOy2c31ZEZVdtitdzsQsKb7878PEERhzUk0ftqGhG6Mz+pQ==
|
||||
|
||||
doipjs@^1.2.4:
|
||||
version "1.2.4"
|
||||
resolved "https://registry.yarnpkg.com/doipjs/-/doipjs-1.2.4.tgz#f980ac4a3ca71b4496530a1e877230b7b6a4cec0"
|
||||
integrity sha512-zs/kL/Yc3H4X8SrWW0zf6w6RLWDw2LQ6w/tgx8Kirqf7eMtOO0rur9/0ASCeHvoqRr7O6W7dXnW7nD/I5ZgxDg==
|
||||
doipjs@^1.2.5:
|
||||
version "1.2.5"
|
||||
resolved "https://registry.yarnpkg.com/doipjs/-/doipjs-1.2.5.tgz#98d67dad67ec0d0afafcda4e4740c93a964eecd3"
|
||||
integrity sha512-366rS4LpzrGUgf1wfxjLpS46T4whB15m4dW8DfVYLLFYqNhwDVirRhysaexCQ8B4Ns+eCSa7qfpVszSxwGmgkA==
|
||||
dependencies:
|
||||
"@openpgp/hkp-client" "^0.0.3"
|
||||
"@openpgp/wkd-client" "^0.0.4"
|
||||
|
|
Loading…
Reference in a new issue