Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add folder and tag context menu options for Cards View #12

Open
wants to merge 12 commits into
base: main
Choose a base branch
from
Open
13 changes: 13 additions & 0 deletions .prettierrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
{
"semi": true,
"singleQuote": false,
"tabWidth": 2,
"useTabs": true,
"printWidth": 80,
"trailingComma": "all",
"bracketSpacing": true,
"arrowParens": "always",
"endOfLine": "lf",
"flatChainOperators": false,
"breakChains": true
}
148 changes: 74 additions & 74 deletions components/Card.svelte
Original file line number Diff line number Diff line change
@@ -1,95 +1,95 @@
<script lang="ts">
import {setIcon, TFile} from "obsidian";
import {createEventDispatcher, onMount} from "svelte";
import {skipNextTransition} from "./store";
import {setIcon, TFile} from "obsidian";
import {createEventDispatcher, onMount} from "svelte";
import {skipNextTransition} from "./store";

export let file: TFile;
export let openFile: () => void;
export let renderFile: (el: HTMLElement) => Promise<void>;
export let trashFile: () => void;
export let file: TFile;
export let openFile: () => void;
export let renderFile: (el: HTMLElement) => Promise<void>;
export let trashFile: () => void;

let contentDiv: HTMLElement;
let contentDiv: HTMLElement;

const trashIcon = (element: HTMLElement) => setIcon(element, 'trash')
const folderIcon = (element: HTMLElement) => setIcon(element, 'folder');
const trashIcon = (element: HTMLElement) => setIcon(element, 'trash')
const folderIcon = (element: HTMLElement) => setIcon(element, 'folder');

const dispatch = createEventDispatcher();
onMount(async () => {
await renderFile(contentDiv)
dispatch('loaded')
});
const dispatch = createEventDispatcher();
onMount(async () => {
await renderFile(contentDiv)
dispatch('loaded')
});
</script>

<div class="card" class:skip-transition={$skipNextTransition} on:click={openFile} role="link" on:keydown={openFile} tabindex="0">
<h3>{file.basename}</h3>
<div bind:this={contentDiv} />
<div class="card-info">
{#if file.parent != null && file.parent.path !== '/'}
<span use:folderIcon /><span class="folder-name">{file.parent.path}</span>
{/if}
<button class="clickable-icon" use:trashIcon on:click|stopPropagation={trashFile} />
</div>
<h3>{file.basename}</h3>
<div bind:this={contentDiv} />
<div class="card-info">
{#if file.parent != null && file.parent.path !== '/'}
<span use:folderIcon /><span class="folder-name">{file.parent.path}</span>
{/if}
<button class="clickable-icon" use:trashIcon on:click|stopPropagation={trashFile} />
</div>
</div>

<style>
.card {
position: absolute;
background-color: var(--background-primary-alt);
border: 1px solid var(--background-modifier-border);
padding: var(--card-padding);
word-wrap: break-word;
overflow-y: hidden;
margin: 0;
transition-property: transform;
transition-duration: 0.4s;
transform: translate(0, 100vh);
}
.card {
position: absolute;
background-color: var(--background-primary-alt);
border: 1px solid var(--background-modifier-border);
padding: var(--card-padding);
word-wrap: break-word;
overflow-y: hidden;
margin: 0;
transition-property: transform;
transition-duration: 0.4s;
transform: translate(0, 100vh);
}

.card.skip-transition {
transition: none;
}
.card.skip-transition {
transition: none;
}

.card {
font-size: 0.8rem;
}
.card {
font-size: 0.8rem;
}

.card :global(p),
.card :global(ul) {
margin: 0.3rem 0;
}
.card :global(p),
.card :global(ul) {
margin: 0.3rem 0;
}

.card :global(h1),
.card :global(h2),
.card :global(h3) {
margin: 0 0 0.3rem;
}
.card :global(h1),
.card :global(h2),
.card :global(h3) {
margin: 0 0 0.3rem;
}

.card :global(ul) {
padding-left: var(--size-4-5);
}
.card :global(ul) {
padding-left: var(--size-4-5);
}

.card:hover {
border-color: var(--background-modifier-border-hover);
}
.card:hover {
border-color: var(--background-modifier-border-hover);
}

.card h3 {
word-wrap: break-word;
}
.card h3 {
word-wrap: break-word;
}

.card .card-info {
margin: calc(-1 * var(--card-padding));
margin-top: 0;
border-top: 1px solid var(--background-modifier-border);
padding: var(--size-4-1) var(--card-padding);
background-color: var(--background-primary);
display: flex;
flex-direction: row;
align-items: center;
justify-content: end;
gap: var(--size-4-1);
}
.card .card-info {
margin: calc(-1 * var(--card-padding));
margin-top: 0;
border-top: 1px solid var(--background-modifier-border);
padding: var(--size-4-1) var(--card-padding);
background-color: var(--background-primary);
display: flex;
flex-direction: row;
align-items: center;
justify-content: end;
gap: var(--size-4-1);
}

.card .card-info .folder-name {
flex-grow: 1;
}
.card .card-info .folder-name {
flex-grow: 1;
}
</style>
121 changes: 93 additions & 28 deletions components/Root.svelte
Original file line number Diff line number Diff line change
@@ -1,53 +1,112 @@
<script lang="ts">
import {debounce, Menu, SearchComponent, setIcon, TFile} from "obsidian";
import {afterUpdate, onMount} from "svelte";
import { debounce, Menu, SearchComponent, setIcon, TFile } from "obsidian";
import { afterUpdate, onMount } from "svelte";
import MiniMasonry from "minimasonry";

import type {CardsViewSettings} from "../settings";
import type { CardsViewSettings } from "../settings";
import Card from "./Card.svelte";
import {displayedFiles, searchQuery, skipNextTransition, Sort, sort, viewIsVisible} from "./store";
import {
displayedFiles,
searchQuery,
skipNextTransition,
Sort,
sort,
viewIsVisible,
} from "./store";

export let settings: CardsViewSettings;

export let renderFile: (file: TFile, el: HTMLElement) => Promise<void>;
export let openFile: (file: TFile) => void;
export let trashFile: (file: TFile) => Promise<void>;
export let saveSettings: () => Promise<void>;

let notesGrid: MiniMasonry;
let viewContent: HTMLElement;
let cardsContainer: HTMLElement;
let columns: number
let columns: number;

const sortIcon = (element: HTMLElement) => {
setIcon(element, "arrow-down-wide-narrow");
}
};

const searchInput = (element: HTMLElement) => {
(new SearchComponent(element)).onChange((value) => {
$searchQuery =value;
new SearchComponent(element).onChange((value) => {
$searchQuery = value;
});
}
};

function sortMenu(event: MouseEvent) {
const sortMenu = new Menu();

// 文件名排序
sortMenu.addItem((item) => {
item.setTitle("Title (A-Z)");
item.setChecked($sort == Sort.NameAsc);
item.onClick(async () => {
$sort = Sort.NameAsc;
settings.defaultSort = Sort.NameAsc;
await saveSettings();
});
});
sortMenu.addItem((item) => {
item.setTitle("Title (Z-A)");
item.setChecked($sort == Sort.NameDesc);
item.onClick(async () => {
$sort = Sort.NameDesc;
settings.defaultSort = Sort.NameDesc;
await saveSettings();
});
});

sortMenu.addSeparator();

// 编辑时间排序
sortMenu.addItem((item) => {
item.setTitle("Last created");
item.setChecked($sort == Sort.Created);
item.setTitle("Edited (Newest First)");
item.setChecked($sort == Sort.EditedDesc);
item.onClick(async () => {
$sort = Sort.Created;
$sort = Sort.EditedDesc;
settings.defaultSort = Sort.EditedDesc;
await saveSettings();
});
});
sortMenu.addItem((item) => {
item.setTitle("Last modified");
item.setChecked($sort == Sort.Modified);
item.setTitle("Edited (Oldest First)");
item.setChecked($sort == Sort.EditedAsc);
item.onClick(async () => {
$sort = Sort.Modified;
$sort = Sort.EditedAsc;
settings.defaultSort = Sort.EditedAsc;
await saveSettings();
});
})
});

sortMenu.addSeparator();

// 创建时间排序
sortMenu.addItem((item) => {
item.setTitle("Created (Newest First)");
item.setChecked($sort == Sort.CreatedDesc);
item.onClick(async () => {
$sort = Sort.CreatedDesc;
settings.defaultSort = Sort.CreatedDesc;
await saveSettings();
});
});
sortMenu.addItem((item) => {
item.setTitle("Created (Oldest First)");
item.setChecked($sort == Sort.CreatedAsc);
item.onClick(async () => {
$sort = Sort.CreatedAsc;
settings.defaultSort = Sort.CreatedAsc;
await saveSettings();
});
});

sortMenu.showAtMouseEvent(event);
}

onMount(() => {
$sort = settings.defaultSort;
columns = Math.floor(viewContent.clientWidth / settings.minCardWidth);
notesGrid = new MiniMasonry({
container: cardsContainer,
Expand All @@ -60,28 +119,34 @@

return () => {
notesGrid.destroy();
}
};
});

afterUpdate(debounce(async () => {
if (!$viewIsVisible) {
$skipNextTransition = true;
return;
}
afterUpdate(
debounce(async () => {
if (!$viewIsVisible) {
$skipNextTransition = true;
return;
}

notesGrid.layout();
$skipNextTransition = false;
}));
notesGrid.layout();
$skipNextTransition = false;
}),
);
</script>

<div class="action-bar" bind:this={viewContent}>
<div use:searchInput />
<button class="clickable-icon sort-button" use:sortIcon on:click={sortMenu} />
</div>
<div class="cards-container" bind:this={cardsContainer} style:--columns={columns}>
<div
class="cards-container"
bind:this={cardsContainer}
style:--columns={columns}
>
{#each $displayedFiles as file (file.path + file.stat.mtime)}
<Card
file={file}
{file}
renderFile={(el) => renderFile(file, el)}
openFile={() => openFile(file)}
trashFile={() => trashFile(file)}
Expand Down
Loading