Merge pull request #16 from soundslikeinfo/8-read-checkbox

8 read checkbox
This commit is contained in:
Sounds Like Info 2025-02-04 10:01:48 -07:00 committed by GitHub
commit 3de7cf2052
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 256 additions and 22 deletions

View file

@ -19,6 +19,7 @@ interface Article {
contentTags: string[];
basename: string;
content: string;
read?: boolean;
}
interface SortConfig {
@ -146,7 +147,8 @@ const ClipperCatalog: React.FC<ClipperCatalogProps> = ({ app, plugin }) => {
if (metadata?.frontmatter) {
const source = metadata.frontmatter[plugin.settings.sourcePropertyName];
const read = metadata.frontmatter[plugin.settings.readPropertyName] === true;
if (source) {
const content = await app.vault.read(file);
let frontmatterTags: string[] = [];
@ -191,7 +193,8 @@ const ClipperCatalog: React.FC<ClipperCatalogProps> = ({ app, plugin }) => {
frontmatterTags,
contentTags,
basename: file.basename,
content: content
content: content,
read
});
}
}
@ -508,14 +511,22 @@ const ClipperCatalog: React.FC<ClipperCatalogProps> = ({ app, plugin }) => {
<div className="cc-overflow-x-auto cc-min-h-[120px]">
<table className="cc-w-full cc-text-sm">
<colgroup>
{plugin.settings.readPropertyName && (
<col className="cc-w-[3%]" />
)}
<col className="cc-w-[30%]" />
<col className="cc-w-[15%] cc-narrow-view-hidden" />
<col className="cc-w-[22%] cc-narrow-view-hidden" />
<col className="cc-w-[20%]" />
<col className="cc-w-[13%]" />
<col className="cc-w-[20%] cc-narrow-view-hidden" />
<col className="cc-w-[22%]" />
<col className="cc-w-[10%]" />
</colgroup>
<thead>
<tr className="clipper-catalog-header-row">
{plugin.settings.readPropertyName && (
<th className="cc-px-4 cc-py-2 cc-text-left clipper-catalog-header-cell">
{/* Read */}
</th>
)}
<th
onClick={() => handleSort('title')}
className="cc-px-4 cc-py-2 cc-text-left cc-cursor-pointer clipper-catalog-header-cell"
@ -558,10 +569,49 @@ const ClipperCatalog: React.FC<ClipperCatalogProps> = ({ app, plugin }) => {
<tbody>
{filteredArticles.map((article) => (
<tr key={article.path} className="clipper-catalog-row">
{plugin.settings.readPropertyName && (
<td className="cc-px-4 cc-py-2" onClick={(e) => e.stopPropagation()}>
<input
type="checkbox"
className={plugin.settings.useNativeCheckbox ? 'clipper-catalog-compatible-checkbox' : 'clipper-catalog-checkbox'}
checked={article.read === true}
onChange={async (e) => {
const isChecked = e.target.checked;
const file = app.vault.getAbstractFileByPath(article.path);
if (!(file instanceof TFile)) return;
try {
setArticles(prev => prev.map(a =>
a.path === article.path ? {...a, read: isChecked} : a
));
const metadata = app.metadataCache.getFileCache(file);
const content = await app.vault.read(file);
if (!metadata?.frontmatter) {
const newContent = `---\n${plugin.settings.readPropertyName}: ${isChecked}\n---\n${content}`;
await app.vault.modify(file, newContent);
} else {
await app.fileManager.processFrontMatter(file, (frontmatter) => {
frontmatter[plugin.settings.readPropertyName] = isChecked;
});
}
} catch (error) {
console.error('Error:', error);
setArticles(prev => prev.map(a =>
a.path === article.path ? {...a, read: !isChecked} : a
));
}
}}
/>
</td>
)}
<td className="cc-px-4 cc-py-2">
<span
onClick={(event) => openArticle(article.path, event)}
className="cc-flex cc-items-center cc-gap-2 cc-cursor-pointer cc-transition-colors cc-min-h-[1.5rem] clipper-catalog-title"
className={`cc-flex cc-items-center cc-gap-2 cc-cursor-pointer cc-transition-colors cc-min-h-[1.5rem] clipper-catalog-title ${
(plugin.settings.readPropertyName && !article.read) ? 'cc-font-bold' : ''
}`}
>
<svg
className="cc-h-4 cc-w-4 cc-flex-shrink-0 clipper-catalog-icon"
@ -572,6 +622,7 @@ const ClipperCatalog: React.FC<ClipperCatalogProps> = ({ app, plugin }) => {
>
<path d="M9 13h6m-3-3v6m5 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z" />
</svg>
{(() => {
const abstractFile = app.vault.getAbstractFileByPath(article.path);
if (abstractFile instanceof TFile) {

View file

@ -9,6 +9,8 @@ interface ObsidianClipperCatalogSettings {
isAdvancedSettingsExpanded: boolean;
includeFrontmatterTags: boolean;
openInSameLeaf: boolean;
readPropertyName: string;
useNativeCheckbox: boolean;
}
interface ObsidianSettings {
@ -24,7 +26,9 @@ const DEFAULT_SETTINGS: ObsidianClipperCatalogSettings = {
ignoredDirectories: [],
isAdvancedSettingsExpanded: false,
includeFrontmatterTags: true,
openInSameLeaf: false
openInSameLeaf: false,
readPropertyName: '',
useNativeCheckbox: false
}
export default class ObsidianClipperCatalog extends Plugin {
@ -121,14 +125,21 @@ class ClipperCatalogSettingTab extends PluginSettingTab {
}));
new Setting(containerEl)
.setName('Include frontmatter tags')
.setDesc('Include tags from the frontmatter "tags" field')
.addToggle(toggle => toggle
.setValue(this.plugin.settings.includeFrontmatterTags)
.onChange(async (value) => {
this.plugin.settings.includeFrontmatterTags = value;
await this.plugin.saveSettings();
}));
.setName('Include frontmatter tags')
.setDesc('Include tags from the frontmatter "tags" field')
.addToggle(toggle => toggle
.setValue(this.plugin.settings.includeFrontmatterTags)
.onChange(async (value) => {
this.plugin.settings.includeFrontmatterTags = value;
await this.plugin.saveSettings();
// Refresh all articles
this.app.workspace.getLeavesOfType(VIEW_TYPE_CLIPPER_CATALOG).forEach(leaf => {
if (leaf.view instanceof ClipperCatalogView) {
// Force reload articles
leaf.view.onOpen();
}
});
}));
new Setting(containerEl)
.setName('Open notes in same window')
@ -139,5 +150,37 @@ class ClipperCatalogSettingTab extends PluginSettingTab {
this.plugin.settings.openInSameLeaf = value;
await this.plugin.saveSettings();
}));
}
containerEl.createEl('div', {
text: '⚠️ Warning: The next setting will allow the catalog to overwrite any property you set here. Proceed if you know what you are doing.',
cls: 'setting-item-description warning-text'
}).style.color = 'var(--text-warning)';
new Setting(containerEl)
.setName('Read status property name')
.setDesc('Leave blank to hide checkboxes. If set, specifies which frontmatter property tracks read status (e.g., "read", "completed"). Note: Changing this affects new changes only and will not erase any values.')
.addText(text => text
.setPlaceholder('e.g., read')
.setValue(this.plugin.settings.readPropertyName)
.onChange(async (value) => {
this.plugin.settings.readPropertyName = value;
await this.plugin.saveSettings();
}));
new Setting(containerEl)
.setName('Use a compatible checkbox')
.setDesc('Experimental: Enable for better compatibility with themes where the custom checkbox is not visible.')
.addToggle(toggle => toggle
.setValue(this.plugin.settings.useNativeCheckbox)
.onChange(async (value) => {
this.plugin.settings.useNativeCheckbox = value;
await this.plugin.saveSettings();
// Refresh all clipper catalog views
this.app.workspace.getLeavesOfType(VIEW_TYPE_CLIPPER_CATALOG).forEach(leaf => {
if (leaf.view) {
leaf.view.onResize();
}
});
}));
}
}

View file

@ -190,9 +190,73 @@
.clipper-catalog-title {
color: var(--text-normal);
.cc-font-bold {
font-weight: 600;
}
}
/* Checkbox styles */
.clipper-catalog-checkbox {
appearance: none;
-webkit-appearance: none;
background-color: var(--background-primary);
border: 1px solid var(--checkbox-border-color, var(--text-muted));
border-radius: 4px;
width: 18px;
height: 18px;
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
transition: all 0.2s ease;
margin: 0;
padding: 0;
}
.clipper-catalog-checkbox:checked {
background-color: var(--interactive-accent);
border-color: var(--interactive-accent);
}
.clipper-catalog-checkbox:checked::before {
content: "";
width: 10px;
height: 10px;
background-color: var(--text-on-accent, white);
clip-path: polygon(14% 44%, 0 65%, 50% 100%, 100% 16%, 80% 0%, 43% 62%);
}
/* Compatibility for themes */
/* Dark Theme */
.theme-dark{
input.clipper-catalog-checkbox[type=checkbox]:checked:after{
background-color: transparent;
}
}
/* Compatible checkbox enhancements */
.clipper-catalog-compatible-checkbox {
width: 16px;
height: 16px;
cursor: pointer;
border: 1px solid var(--checkbox-border-color);
border-radius: 3px;
background-color: var(--background-primary);
vertical-align: middle;
padding: 0;
margin: 0;
}
.clipper-catalog-compatible-checkbox:checked {
background-color: var(--interactive-accent);
border-color: var(--interactive-accent);
}
input.clipper-catalog-compatible-checkbox[type=checkbox]:checked {
/* --checkbox-color: var(--checkbox-color); */
background-color: var(--interactive-accent);
}
/* Tag styles */
/* Tag styles */
.clipper-catalog-tag,
.clipper-catalog-frontmatter-tag {
@ -328,4 +392,4 @@
max-width: 150px;
font-size: 10px;
}
}
}

View file

@ -87,8 +87,8 @@
width: 1rem;
}
.cc-w-\[13\%\] {
width: 13%;
.cc-w-\[10\%\] {
width: 10%;
}
.cc-w-\[15\%\] {
@ -103,6 +103,10 @@
width: 22%;
}
.cc-w-\[3\%\] {
width: 3%;
}
.cc-w-\[30\%\] {
width: 30%;
}
@ -301,6 +305,10 @@
line-height: 1rem;
}
.cc-font-bold {
font-weight: 700;
}
.cc-font-medium {
font-weight: 500;
}
@ -573,9 +581,77 @@
.clipper-catalog-title {
color: var(--text-normal);
.cc-font-bold {
font-weight: 600;
}
}
/* Tag styles */
/* Checkbox styles */
.clipper-catalog-checkbox {
-moz-appearance: none;
appearance: none;
-webkit-appearance: none;
background-color: var(--background-primary);
border: 1px solid var(--checkbox-border-color, var(--text-muted));
border-radius: 4px;
width: 18px;
height: 18px;
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
transition: all 0.2s ease;
margin: 0;
padding: 0;
}
.clipper-catalog-checkbox:checked {
background-color: var(--interactive-accent);
border-color: var(--interactive-accent);
}
.clipper-catalog-checkbox:checked::before {
content: "";
width: 10px;
height: 10px;
background-color: var(--text-on-accent, white);
clip-path: polygon(14% 44%, 0 65%, 50% 100%, 100% 16%, 80% 0%, 43% 62%);
}
/* Compatibility for themes */
/* Dark Theme */
.theme-dark{
input.clipper-catalog-checkbox[type=checkbox]:checked:after{
background-color: transparent;
}
}
/* Compatible checkbox enhancements */
.clipper-catalog-compatible-checkbox {
width: 16px;
height: 16px;
cursor: pointer;
border: 1px solid var(--checkbox-border-color);
border-radius: 3px;
background-color: var(--background-primary);
vertical-align: middle;
padding: 0;
margin: 0;
}
.clipper-catalog-compatible-checkbox:checked {
background-color: var(--interactive-accent);
border-color: var(--interactive-accent);
}
input.clipper-catalog-compatible-checkbox[type=checkbox]:checked {
/* --checkbox-color: var(--checkbox-color); */
background-color: var(--interactive-accent);
}
/* Tag styles */
@ -807,4 +883,4 @@
.dark\:hover\:cc-text-gray-300:hover:is(.cc-dark *) {
--tw-text-opacity: 1;
color: rgb(209 213 219 / var(--tw-text-opacity, 1));
}
}