pux-to-html
Version:
Converts 1PUX (export from 1Password) file to HTML files
148 lines (132 loc) • 4.93 kB
JavaScript
function getCopyButton(value) {
return `<button
type="button"
title="Copy to clipboard"
class="bg-gray-200 text-gray-900 hover:bg-gray-800 hover:text-white p-2 rounded-full print:hidden cursor-pointer"
onclick=navigator.clipboard.writeText(${JSON.stringify(value)})>
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="size-4 lucide lucide-clipboard-copy-icon lucide-clipboard-copy">
<rect width="8" height="4" x="8" y="2" rx="1" ry="1"/>
<path d="M8 4H6a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2v-2"/>
<path d="M16 4h2a2 2 0 0 1 2 2v4"/>
<path d="M21 14H11"/>
<path d="m15 10-4 4 4 4"/>
</svg>
</button>`;
}
function getSection({ title, fields }) {
return fields && fields.length > 0
? `
${title ? `<h6 class="uppercase font-semibold">${title}</h6>` : ""}
<ul class="divide-y divide-gray-300 border border-gray-300 rounded-xl">
${fields.map(getField).join("")}
</ul>`
: "";
}
function getNotes({ notesPlain }) {
return notesPlain
? `<h6 class="font-semibold">Note</h6><pre class="text-gray-900 bg-gray-200 rounded p-4 m-2">${notesPlain}</pre>`
: "";
}
function getUrl(url, _label) {
try {
const link = new URL(url);
return `<a href="${url}" target="_blank" class="hover:underline hover:text-sky-700" rel="noopener noreferrer">${link.hostname}</a>`;
} catch {
return `<code>${url}</code>`;
}
}
function getSecret(value) {
return `<code>${value}</code> ${getCopyButton(value)}`;
}
function getOTP(url) {
const link = new URL(url);
const secret = link.searchParams.get("secret");
const issuer = link.searchParams.get("issuer");
return `<table class="min-w-full border border-gray-300 rounded-lg overflow-hidden mb-2">
<tbody>
<tr class="bg-gray-50">
<th class="whitespace-nowrap p-2 text-left font-semibold text-gray-700 text-right w-64">Issuer:</th>
<td class="p-2"><code>${issuer || "-"}</code></td>
</tr>
<tr>
<th class="whitespace-nowrap p-2 font-semibold text-gray-700 text-right">Secret:</th>
<td class="p-2"><code class="bg-gray-100 text-gray-900 rounded px-1">${secret || "-"}</code></td>
</tr>
<tr class="bg-gray-50">
<th class="whitespace-nowrap p-2 font-semibold text-gray-700 text-right truncate">URL:</th>
<td class="p-2 text-wrap">${getCopyButton(url)}</td>
</tr>
<tr>
<th class="whitespace-nowrap p-2 font-semibold text-gray-700 text-right">OTP code:</th>
<td class="p-2">
<code data-secret="${secret}" class="text-lg font-mono bg-gray-900 text-green-400 px-2 py-1 rounded"></code>
</td>
</tr>
</tbody>
</table>`;
}
function getField({ value, name, title, url, label, designation }) {
if (value instanceof Object) {
const { url, string, totp, concealed, file } = value;
if (url) {
value = getUrl(value.url);
} else if (string) {
value = `<code>${value.string}</code>`;
} else if (totp) {
label = "one-time password";
value = getOTP(value.totp);
} else if (concealed) {
value = getSecret(value.concealed);
} else if (file) {
label = "file";
value = `<code>${value.file.fileName}</code>`;
} else {
value = undefined; // Unknown type
}
}
// Password or username fields
if (designation === "password" || designation === "username") {
value = getSecret(value);
}
// If the field is a URL, display it as a link
if (url) {
label = "website";
value = getUrl(url);
}
return value
? `<li class="p-3 flex flex-col flex-wrap gap-1">
<div><span class="text-gray-500">${designation || name || title || label}:</span></div>
<div class="flex items-center gap-3">${value}</div>
</li>`
: "";
}
export function getItemHtml({
state,
details,
overview,
_createdAt,
_updatedAt,
}) {
const loginSection = {
fields: [
...details.loginFields.filter(
(item) =>
item.designation === "password" || item.designation === "username",
),
...(overview.urls || []),
],
};
return state === "active"
? `
<article class="border border-gray-300 rounded-lg p-6 print:p-4 my-4 break-inside-avoid flex flex-col gap-3">
<h2 class="text-3xl print:text-lg font-semibold">${overview.title}</h2>
<!-- login section -->
${getSection(loginSection)}
<!-- other sections -->
${details.sections.map(getSection).join("")}
<!-- notes -->
${getNotes(details)}
</article>
`
: "";
}