fix(release): address automated review findings

This commit is contained in:
Luiz Gustavo 2026-07-18 10:54:43 -03:00
parent 38e4641250
commit f9bdfc042d
12 changed files with 73 additions and 38 deletions

View file

@ -8,6 +8,19 @@ Leif é um plugin para [Obsidian](https://obsidian.md/) que organiza estudos par
Ele reúne concursos, matérias, tópicos do edital, materiais e sessões de estudo para mostrar o que estudar agora e acompanhar o progresso sem sair do Obsidian.
## Instalação
### Pelo Obsidian
Quando o Leif estiver disponível no diretório oficial, abra **Configurações → Plugins da comunidade → Explorar**, procure por **Leif**, selecione **Instalar** e depois **Ativar**.
### Instalação manual
1. Baixe `main.js`, `manifest.json` e `styles.css` da mesma versão na página de releases.
2. Crie a pasta `<seu-vault>/.obsidian/plugins/leif` e coloque os três arquivos nela.
3. Reinicie o Obsidian ou recarregue os plugins em **Configurações → Plugins da comunidade**.
4. Ative o **Leif**.
## Como usar
Ative o Leif em **Plugins da comunidade** e abra o painel pelo ícone da faixa lateral ou pelo comando **Abrir painel**. Crie um concurso, organize suas matérias e recursos, registre cada sessão e consulte **Hoje** para seguir o ciclo.

View file

@ -29,7 +29,7 @@ const ptBR = {
export type TranslationKey = keyof typeof ptBR;
const bundle: Record<string, string> = ptBR as unknown as Record<string, string>;
const bundle: Record<string, string> = ptBR;
export function t(key: TranslationKey): string {
return bundle[key] ?? key;

View file

@ -133,8 +133,8 @@ export class LeifView extends ItemView {
label.textContent = tab.label;
button.append(icon, label);
button.dataset.tab = tab.id;
button.addEventListener("click", async () => {
await this.selectTab(tab.id);
button.addEventListener("click", () => {
void this.selectTab(tab.id);
});
button.setAttribute("role", "tab");
button.id = `leif-tab-${tab.id}`;

View file

@ -209,21 +209,21 @@ export class SessionsTab {
fromInput.dataset.sessionFilter = "from";
toInput.dataset.sessionFilter = "to";
subjectSelect.addEventListener("change", async () => {
subjectSelect.addEventListener("change", () => {
this.historySubjectFilter = subjectSelect.value;
await this.onUpdate();
void this.onUpdate();
});
typeSelect.addEventListener("change", async () => {
typeSelect.addEventListener("change", () => {
this.historyTypeFilter = typeSelect.value;
await this.onUpdate();
void this.onUpdate();
});
fromInput.addEventListener("change", async () => {
fromInput.addEventListener("change", () => {
this.historyFromFilter = fromInput.value;
await this.onUpdate();
void this.onUpdate();
});
toInput.addEventListener("change", async () => {
toInput.addEventListener("change", () => {
this.historyToFilter = toInput.value;
await this.onUpdate();
void this.onUpdate();
});
const filters = DomHelpers.createElement("div", "leif-session-filters");

View file

@ -10,9 +10,11 @@ export const LEIF_ICON = "compass";
export function registerLeifView(plugin: Obsidian.Plugin, dataStore: PluginDataStore): void {
plugin.registerView(LEIF_VIEW_TYPE, (leaf) => new LeifView(leaf, dataStore));
plugin.addRibbonIcon(LEIF_ICON, "Abrir Leif", () => openLeifView(plugin));
plugin.addRibbonIcon(LEIF_ICON, "Abrir Leif", () => {
void openLeifView(plugin);
});
plugin.addCommand({
id: "leif-open-view",
id: "open-view",
name: t("command.openView"),
callback: async () => {
await openLeifView(plugin);
@ -29,5 +31,5 @@ export async function openLeifView(plugin: Obsidian.Plugin): Promise<void> {
active: true
});
await plugin.app.workspace.revealLeaf(leaf);
plugin.app.workspace.setActiveLeaf(leaf, { focus: true });
}

View file

@ -15,11 +15,7 @@ export class DomHelpers {
tagName: K,
className?: string
): HTMLElementTagNameMap[K] {
const element = document.createElement(tagName);
if (className) {
element.className = className;
}
return element;
return createEl(tagName, className ? { cls: className } : undefined);
}
/**
@ -98,7 +94,7 @@ export class DomHelpers {
* Creates a strong (bold) text element.
*/
static createStrong(text: string): HTMLElement {
const strong = document.createElement("strong");
const strong = this.createElement("strong");
strong.textContent = text;
return strong;
}
@ -134,7 +130,7 @@ export class DomHelpers {
* Creates an input element.
*/
static createInput(type: string, placeholder: string, value = ""): HTMLInputElement {
const input = document.createElement("input");
const input = this.createElement("input");
input.type = type;
input.placeholder = placeholder;
input.value = value;
@ -147,10 +143,10 @@ export class DomHelpers {
* @param selectedValue - Optional value to pre-select
*/
static createSelect(options: Array<[string, string]>, selectedValue?: string): HTMLSelectElement {
const select = document.createElement("select");
const select = this.createElement("select");
options.forEach(([value, label]) => {
const option = document.createElement("option");
const option = this.createElement("option");
option.value = value;
option.textContent = label;
if (value === selectedValue) {
@ -166,7 +162,7 @@ export class DomHelpers {
* Creates a textarea element.
*/
static createTextarea(placeholder: string, value = ""): HTMLTextAreaElement {
const textarea = document.createElement("textarea");
const textarea = this.createElement("textarea");
textarea.placeholder = placeholder;
textarea.value = value;
return textarea;
@ -302,7 +298,7 @@ export class DomHelpers {
type?: "button" | "submit" | "reset";
} = {}
): HTMLButtonElement {
const button = document.createElement("button");
const button = this.createElement("button");
button.type = options.type || "button";
button.className = options.className || "";
@ -319,7 +315,9 @@ export class DomHelpers {
}
if (options.onClick) {
button.addEventListener("click", options.onClick);
button.addEventListener("click", (event) => {
void options.onClick?.(event);
});
}
return button;
@ -341,7 +339,7 @@ export class DomHelpers {
onClick?: (event: MouseEvent) => void | Promise<void>;
} = {}
): HTMLButtonElement {
const button = document.createElement("button");
const button = this.createElement("button");
button.type = "button";
button.className = options.className || "clickable-icon";
button.setAttribute("aria-label", title);
@ -354,7 +352,9 @@ export class DomHelpers {
}
if (options.onClick) {
button.addEventListener("click", options.onClick);
button.addEventListener("click", (event) => {
void options.onClick?.(event);
});
}
if (typeof setTooltip === "function") {
@ -370,11 +370,11 @@ export class DomHelpers {
* Creates a form element.
*/
static createForm(onSubmit?: (event: Event) => void | Promise<void>): HTMLFormElement {
const form = document.createElement("form");
const form = this.createElement("form");
if (onSubmit) {
form.addEventListener("submit", (event) => {
event.preventDefault();
onSubmit(event);
void onSubmit(event);
});
}
return form;
@ -393,7 +393,7 @@ export class DomHelpers {
): void {
select.innerHTML = "";
options.forEach(([value, label]) => {
const option = document.createElement("option");
const option = this.createElement("option");
option.value = value;
option.textContent = label;
if (selectedValue !== undefined && value === selectedValue) {

View file

@ -1123,7 +1123,6 @@ form.leif-card,
gap: 4px;
overflow-x: auto;
padding-bottom: 3px;
scrollbar-width: thin;
}
.leif-view.is-narrow .leif-tab-button {
@ -1277,7 +1276,6 @@ form.leif-card,
gap: 4px;
overflow-x: auto;
padding-bottom: 3px;
scrollbar-width: thin;
}
.leif-tab-button {

View file

@ -16,9 +16,19 @@ describe("community release readiness", () => {
expect(versions[version]).toBe(manifest.minAppVersion);
expect(manifest.description).not.toBe("A bússola do seu estudo.");
expect(manifest.authorUrl).toBe("https://github.com/ttusk");
expect(read("README.md")).toContain("## Instalação");
expect(read("README.md")).toContain("## Como usar");
});
it("uses APIs and DOM patterns supported by the declared Obsidian version", () => {
const viewRegistration = read("src/ui/view/registerLeifView.ts");
const domHelpers = read("src/ui/view/shared/DomHelpers.ts");
expect(viewRegistration).not.toContain("workspace.revealLeaf");
expect(domHelpers).not.toContain("document.createElement");
expect(domHelpers).not.toMatch(/addEventListener\([^,]+,\s*options\.onClick\)/);
});
it("keeps demo and compiled artifacts out of the source repository", () => {
expect(existsSync(resolve(root, "sample-vault/.obsidian/plugins/leif/main.js"))).toBe(false);
expect(existsSync(resolve(root, "docs/improvements-audit.md"))).toBe(false);

View file

@ -1,6 +1,17 @@
const notices: string[] = [];
const registeredIcons = new Map<string, string>();
globalThis.createEl = ((tagName, options) => {
const element = document.createElement(tagName);
const className = typeof options === "string" ? options : options?.cls;
if (className) {
element.className = Array.isArray(className) ? className.join(" ") : className;
}
return element;
}) as typeof createEl;
type ViewCreator = (leaf: WorkspaceLeaf) => ItemView;
export class Notice {
@ -72,7 +83,7 @@ export class Workspace {
return this.getLeaf();
}
async revealLeaf(_leaf: WorkspaceLeaf): Promise<void> {
setActiveLeaf(_leaf: WorkspaceLeaf, _params?: { focus?: boolean }): void {
// Test stub.
}

View file

@ -14,7 +14,7 @@ describe("LeifPlugin", () => {
const registeredPlugin = plugin as unknown as Plugin;
expect(registeredPlugin.settingTabs).toHaveLength(0);
expect(registeredPlugin.commands.map((command) => command.id)).toEqual(["leif-open-view"]);
expect(registeredPlugin.commands.map((command) => command.id)).toEqual(["open-view"]);
expect(registeredPlugin.commands[0]?.name).toBe("Abrir painel");
});
});

View file

@ -80,7 +80,7 @@ async function openLeifView(dataStore: PluginDataStore): Promise<{
const plugin = new Plugin(app);
registerLeifView(plugin as never, dataStore);
const openCommand = plugin.commands.find((command) => command.id === "leif-open-view");
const openCommand = plugin.commands.find((command) => command.id === "open-view");
if (!openCommand) {
throw new Error("Open view command was not registered.");
@ -155,8 +155,8 @@ describe("LeifView", () => {
expect(plugin.ribbonIcons).toHaveLength(1);
expect(plugin.ribbonIcons[0]?.title).toBe("Abrir Leif");
expect(plugin.commands.map((command) => command.id)).toContain("leif-open-view");
expect(plugin.commands.find((command) => command.id === "leif-open-view")?.name).toBe(
expect(plugin.commands.map((command) => command.id)).toContain("open-view");
expect(plugin.commands.find((command) => command.id === "open-view")?.name).toBe(
"Abrir painel"
);
expect(leaf.containerEl.textContent).toContain("Hoje");

View file

@ -43,6 +43,7 @@ describe("Leif visual system", () => {
expect(styles).toMatch(
/\.leif-view\.is-narrow\s+\.leif-tab-bar\s*{[^}]*flex-direction:\s*row;[^}]*overflow-x:\s*auto;/s
);
expect(styles).not.toContain("scrollbar-width");
});
it("gives semantic tabs a native, visible active state", () => {