├── src
├── vite-env.d.ts
├── logger.ts
├── i18n
│ ├── ja.json
│ ├── en.json
│ └── configs.ts
├── db.ts
├── main.css
├── App.css
├── main.tsx
└── App.tsx
├── icon.png
├── images
├── pagetag.png
├── screen-main.png
├── cardbox_small.png
└── screen-launch.png
├── tsconfig.node.json
├── .gitignore
├── vite.config.ts
├── index.html
├── .eslintrc.cjs
├── tsconfig.json
├── package.json
├── .github
└── workflows
│ └── publish.yml
├── README.md
└── LICENSE
/src/vite-env.d.ts:
--------------------------------------------------------------------------------
1 | ///
2 |
--------------------------------------------------------------------------------
/icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sosuisen/logseq-cardbox/HEAD/icon.png
--------------------------------------------------------------------------------
/images/pagetag.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sosuisen/logseq-cardbox/HEAD/images/pagetag.png
--------------------------------------------------------------------------------
/images/screen-main.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sosuisen/logseq-cardbox/HEAD/images/screen-main.png
--------------------------------------------------------------------------------
/images/cardbox_small.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sosuisen/logseq-cardbox/HEAD/images/cardbox_small.png
--------------------------------------------------------------------------------
/images/screen-launch.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sosuisen/logseq-cardbox/HEAD/images/screen-launch.png
--------------------------------------------------------------------------------
/tsconfig.node.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "composite": true,
4 | "skipLibCheck": true,
5 | "module": "ESNext",
6 | "moduleResolution": "bundler",
7 | "allowSyntheticDefaultImports": true
8 | },
9 | "include": ["vite.config.ts"]
10 | }
11 |
--------------------------------------------------------------------------------
/src/logger.ts:
--------------------------------------------------------------------------------
1 | import { Logger } from "tslog";
2 |
3 | // eslint-disable-next-line @typescript-eslint/ban-ts-comment
4 | // @ts-ignore
5 | const isDebug = __DEBUG__;
6 | console.log("DEBUG:", isDebug);
7 | // Default severities are: 0: silly, 1: trace, 2: debug, 3: info, 4: warn, 5: error, 6: fatal
8 | export const logger = new Logger({
9 | minLevel: isDebug ? 0 : 3,
10 | });
11 |
--------------------------------------------------------------------------------
/src/i18n/ja.json:
--------------------------------------------------------------------------------
1 | {
2 | "rebuild": "再構築",
3 | "open-pages-btn": "pagesフォルダを選択",
4 | "open-pages-btn-label": "Logseqのグラフ保存先フォルダにあるpagesフォルダを選択してください。",
5 | "footer": "本文のないページは表示されません。クリックまたはカーソル移動+Enterキーで開きます。",
6 | "loading": "構築中...",
7 | "please-select-pages": "pagesという名前のフォルダを選択してください。",
8 | "filter-by-page-tag": "ページタグで絞り込み",
9 | "cancel": "キャンセル"
10 | }
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Logs
2 | logs
3 | *.log
4 | npm-debug.log*
5 | yarn-debug.log*
6 | yarn-error.log*
7 | pnpm-debug.log*
8 | lerna-debug.log*
9 |
10 | node_modules
11 | dist
12 | dist-ssr
13 | *.local
14 |
15 | # Editor directories and files
16 | .vscode/*
17 | !.vscode/extensions.json
18 | .idea
19 | .DS_Store
20 | *.suo
21 | *.ntvs*
22 | *.njsproj
23 | *.sln
24 | *.sw?
25 |
26 | # Release files
27 | release
--------------------------------------------------------------------------------
/vite.config.ts:
--------------------------------------------------------------------------------
1 | import { defineConfig } from 'vite'
2 | import react from '@vitejs/plugin-react-swc'
3 |
4 | // https://vitejs.dev/config/
5 | export default defineConfig(({ mode }) => {
6 | const isDebug = mode === 'development';
7 | return {
8 | define: {
9 | '__DEBUG__': isDebug,
10 | },
11 | base: './',
12 | plugins: [
13 | react(),
14 | ],
15 | };
16 | });
17 |
18 |
--------------------------------------------------------------------------------
/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | CardBox
8 |
9 |
10 |
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/src/i18n/en.json:
--------------------------------------------------------------------------------
1 | {
2 | "rebuild": "Rebuild",
3 | "open-pages-btn": "Select pages folder",
4 | "open-pages-btn-label": "Please select the pages folder in the Logseq graph storage folder." ,
5 | "footer": "Empty pages are not displayed. Click or move the cursor and press Enter to open the page.",
6 | "loading" : "Building...",
7 | "please-select-pages": "Select the folder named 'pages'",
8 | "filter-by-page-tag": "Filter by Page Tag",
9 | "cancel": "Cancel"
10 | }
--------------------------------------------------------------------------------
/.eslintrc.cjs:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | root: true,
3 | env: { browser: true, es2020: true },
4 | extends: [
5 | 'eslint:recommended',
6 | 'plugin:@typescript-eslint/recommended',
7 | 'plugin:react-hooks/recommended',
8 | ],
9 | ignorePatterns: ['dist', '.eslintrc.cjs'],
10 | parser: '@typescript-eslint/parser',
11 | plugins: ['react-refresh'],
12 | rules: {
13 | 'react-refresh/only-export-components': [
14 | 'warn',
15 | { allowConstantExport: true },
16 | ],
17 | },
18 | }
19 |
--------------------------------------------------------------------------------
/src/i18n/configs.ts:
--------------------------------------------------------------------------------
1 | import i18n from "i18next";
2 | import { initReactI18next } from "react-i18next";
3 |
4 | // 言語jsonファイルのimport
5 | import translation_en from "./en.json";
6 | import translation_ja from "./ja.json";
7 |
8 | const resources = {
9 | ja: {
10 | translation: translation_ja
11 | },
12 | en: {
13 | translation: translation_en
14 | }
15 | };
16 |
17 | i18n
18 | .use(initReactI18next)
19 | .init({
20 | resources,
21 | fallbackLng: "en",
22 | interpolation: {
23 | escapeValue: false,
24 | }
25 | });
26 |
27 | export default i18n;
--------------------------------------------------------------------------------
/src/db.ts:
--------------------------------------------------------------------------------
1 | import Dexie, { Table } from 'dexie';
2 |
3 | export interface Box {
4 | graph: string; // graph name in Logseq db
5 | name: string; // originalName in Logseq db
6 | uuid: string; // uuid in Logseq db
7 | time: number; // Unix time
8 | summary: string[];
9 | image: string;
10 | }
11 |
12 | export class CardBoxDexie extends Dexie {
13 | box!: Table;
14 |
15 | constructor(dbName: string) {
16 | super(dbName);
17 | this.version(1).stores({
18 | box: '[graph+name], graph, time' // [graph+name] is the compound primary key, and time is an indexed property.
19 | });
20 | }
21 | }
22 |
23 | export const db = new CardBoxDexie('logseq-cardbox-plugin');
24 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "ES2020",
4 | "useDefineForClassFields": true,
5 | "lib": ["ES2020", "DOM", "DOM.Iterable"],
6 | "module": "ESNext",
7 | "skipLibCheck": true,
8 |
9 | /* Bundler mode */
10 | "moduleResolution": "bundler",
11 | "allowImportingTsExtensions": true,
12 | "resolveJsonModule": true,
13 | "isolatedModules": true,
14 | "noEmit": true,
15 | "jsx": "react-jsx",
16 |
17 | /* Linting */
18 | "strict": true,
19 | "noUnusedLocals": true,
20 | "noUnusedParameters": true,
21 | "noFallthroughCasesInSwitch": true,
22 |
23 | "types": [ "@types/wicg-file-system-access"]
24 |
25 | },
26 | "include": ["src"],
27 | "references": [{ "path": "./tsconfig.node.json" }]
28 | }
29 |
--------------------------------------------------------------------------------
/src/main.css:
--------------------------------------------------------------------------------
1 | /* :root is pseudo class */
2 | :root {
3 | font-family: 'Roboto', 'Noto Sans JP', sans-serif;
4 | line-height: 1.5;
5 | font-weight: 400;
6 |
7 | font-synthesis: none;
8 | text-rendering: optimizeLegibility;
9 | -webkit-font-smoothing: antialiased;
10 | -moz-osx-font-smoothing: grayscale;
11 | -webkit-text-size-adjust: 100%;
12 | user-select: none;
13 | }
14 |
15 | a {
16 | font-weight: 500;
17 | color: #646cff;
18 | text-decoration: inherit;
19 | }
20 | a:hover {
21 | color: #747bff;
22 | }
23 |
24 | body {
25 | margin: 0;
26 | place-items: center;
27 | min-width: 320px;
28 | min-height: 100vh;
29 | width: 100%;
30 | height: 100%;
31 | overflow: hidden;
32 | background-color: transparent;
33 | }
34 |
35 | h1 {
36 | font-size: 3.2em;
37 | line-height: 1.1;
38 | }
39 |
40 | button {
41 | border-radius: 4px;
42 | border: 1px solid transparent;
43 | padding: 0.3em 0.6em;
44 | font-size: 1em;
45 | font-weight: 500;
46 | font-family: inherit;
47 | background-color: #f9f9f9;
48 | cursor: pointer;
49 | transition: border-color 0.25s;
50 | }
51 | button:hover {
52 | border-color: #646cff;
53 | }
54 | button:focus,
55 | button:focus-visible {
56 | outline: 4px auto -webkit-focus-ring-color;
57 | }
58 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "logseq-cardbox",
3 | "description": "Plugin to add thumbnail cards to Logseq that are displayed in order of update.",
4 | "version": "0.2.0",
5 | "type": "module",
6 | "main": "dist/index.html",
7 | "license": "MPL-2.0",
8 | "repository": "sosuisen/logseq-cardbox",
9 | "scripts": {
10 | "dev": "vite",
11 | "build:dev": "tsc && vite build --mode development",
12 | "build": "tsc && vite build",
13 | "lint": "eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0",
14 | "preview": "vite preview",
15 | "release": "npm run build && rm -r release && mkdir release && cp -r dist images release && cp icon.png LICENSE package.json README.md release"
16 | },
17 | "dependencies": {
18 | "@emotion/react": "^11.11.3",
19 | "@emotion/styled": "^11.11.0",
20 | "@fontsource/noto-sans-jp": "^5.0.17",
21 | "@fontsource/roboto": "^5.0.8",
22 | "@logseq/libs": "^0.0.15",
23 | "@mui/icons-material": "^5.15.2",
24 | "@mui/material": "^5.15.2",
25 | "date-fns": "^2.30.0",
26 | "dexie": "^3.2.4",
27 | "dexie-react-hooks": "^1.1.7",
28 | "i18next": "^23.6.0",
29 | "react": "^18.2.0",
30 | "react-dom": "^18.2.0",
31 | "react-i18next": "^13.3.1",
32 | "react-remove-scroll": "^2.5.7",
33 | "tslog": "^4.9.2"
34 | },
35 | "devDependencies": {
36 | "@types/node": "^20.9.0",
37 | "@types/react": "^18.2.15",
38 | "@types/react-dom": "^18.2.7",
39 | "@types/wicg-file-system-access": "^2023.10.3",
40 | "@typescript-eslint/eslint-plugin": "^6.0.0",
41 | "@typescript-eslint/parser": "^6.0.0",
42 | "@vitejs/plugin-react-swc": "^3.3.2",
43 | "eslint": "^8.45.0",
44 | "eslint-plugin-react-hooks": "^4.6.0",
45 | "eslint-plugin-react-refresh": "^0.4.3",
46 | "typescript": "^5.0.2",
47 | "vite": "^4.4.5",
48 | "vite-plugin-logseq": "^1.1.2"
49 | },
50 | "logseq": {
51 | "id": "logseq-cardbox_3sulqr0v8",
52 | "title": "Logseq CardBox",
53 | "icon": "./icon.png"
54 | },
55 | "author": "Hidekazu Kubota"
56 | }
57 |
--------------------------------------------------------------------------------
/.github/workflows/publish.yml:
--------------------------------------------------------------------------------
1 | name: Build plugin
2 |
3 | on:
4 | push:
5 | # Sequence of patterns matched against refs/tags
6 | tags:
7 | - "*" # Push events to matching any tag format, i.e. 1.0, 20.15.10
8 |
9 | env:
10 | PLUGIN_NAME: logseq-cardbox
11 |
12 | jobs:
13 | build:
14 | runs-on: ubuntu-latest
15 |
16 | steps:
17 | - uses: actions/checkout@v2
18 | - name: Use Node.js
19 | uses: actions/setup-node@v1
20 | with:
21 | node-version: "18.x" # You might need to adjust this value to your own version
22 | - name: Build
23 | id: build
24 | run: |
25 | npm install
26 | npm run build
27 | mkdir ${{ env.PLUGIN_NAME }}
28 | cp icon.png LICENSE package.json README.md ${{ env.PLUGIN_NAME }}
29 | mv dist images ${{ env.PLUGIN_NAME }}
30 | zip -r ${{ env.PLUGIN_NAME }}.zip ${{ env.PLUGIN_NAME }}
31 | ls
32 | echo "::set-output name=tag_name::$(git tag --sort version:refname | tail -n 1)"
33 |
34 | - name: Create Release
35 | uses: ncipollo/release-action@v1
36 | id: create_release
37 | env:
38 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
39 | VERSION: ${{ github.ref }}
40 | with:
41 | allowUpdates: true
42 | draft: false
43 | prerelease: false
44 |
45 | - name: Upload zip file
46 | id: upload_zip
47 | uses: actions/upload-release-asset@v1
48 | env:
49 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
50 | with:
51 | upload_url: ${{ steps.create_release.outputs.upload_url }}
52 | asset_path: ./${{ env.PLUGIN_NAME }}.zip
53 | asset_name: ${{ env.PLUGIN_NAME }}-${{ steps.build.outputs.tag_name }}.zip
54 | asset_content_type: application/zip
55 |
56 | - name: Upload package.json
57 | id: upload_metadata
58 | uses: actions/upload-release-asset@v1
59 | env:
60 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
61 | with:
62 | upload_url: ${{ steps.create_release.outputs.upload_url }}
63 | asset_path: ./package.json
64 | asset_name: package.json
65 | asset_content_type: application/json
--------------------------------------------------------------------------------
/src/App.css:
--------------------------------------------------------------------------------
1 | #app {
2 | margin: 0 auto;
3 | text-align: center;
4 | width: 96%;
5 | height: 92%;
6 | margin-top: 2%;
7 | margin-left: 2%;
8 | position: absolute;
9 | background-color: #dcdde0;
10 | box-shadow: 5px 5px 0 0 #c0c0c0;
11 | border-radius: 3px;
12 | display: grid;
13 | grid-template-rows: 60px auto 20px;
14 | }
15 |
16 | .control {
17 | margin-top: 7px;
18 | margin-bottom: 5px;
19 | display: grid;
20 | grid-template-columns: 1fr 60px;
21 | }
22 |
23 | .loading {
24 | color: rgba(144, 0, 0, 0.7);
25 | text-align: left;
26 | margin-left: 24px;
27 | margin-top: 7px;
28 | float: left;
29 | font-size: 24px;
30 | }
31 |
32 | .card-number {
33 | color: rgba(0, 0, 0, 0.7);
34 | text-align: left;
35 | margin-left: 24px;
36 | margin-top: 7px;
37 | float: left;
38 | font-size: 24px;
39 | }
40 |
41 | #tile {
42 | padding-left: 24px;
43 | padding-right: 24px;
44 | display: grid;
45 | grid-template-columns: repeat(auto-fill, minmax(150px, 1fr));
46 | overflow-y: auto;
47 | }
48 |
49 | .box {
50 | cursor: pointer;
51 | border-radius: 0px 0px 3px 3px;
52 | margin: 10px auto 10px 10px;
53 | width: 140px;
54 | height: 140px;
55 | box-shadow: 2px 3px 0 0px #d0d0d0;
56 | background-color: #ffffff;
57 | border-top: 5px solid #90c0ff;
58 | overflow-y: hidden;
59 | box-sizing: border-box;
60 | display: flex;
61 | flex-direction: column;
62 | }
63 |
64 | .selectedBox {
65 | background-color: #f0f0ff;
66 | }
67 |
68 | .box:hover {
69 | background-color: #e0e0e0;
70 | }
71 |
72 | .box-title {
73 | width: 100%;
74 | font-weight: 700;
75 | font-size: 14px;
76 | overflow: hidden;
77 | border-bottom: 1px solid #90c0ff;
78 | flex-shrink: 0;
79 | }
80 |
81 | .box-summary {
82 | font-size: 12px;
83 | color: #606060;
84 | overflow: hidden;
85 | text-align: left;
86 | padding: 7px;
87 | white-space: pre-wrap;
88 | flex-grow: 1;
89 | }
90 |
91 | .box-image {
92 | overflow: hidden;
93 | padding-top: 7px;
94 | display: none;
95 | flex-grow: 1;
96 | }
97 |
98 | .box-date {
99 | font-size: 10px;
100 | }
101 |
102 | .footer {
103 | color: rgba(0, 0, 0, 0.7);
104 | font-size: 14px;
105 | width: 100%;
106 | background-color: #dcdde0;
107 | }
108 |
109 | .tag-label {
110 | float: left;
111 | margin-top: 7px;
112 | margin-left: 24px;
113 | }
114 |
115 | .tag-input {
116 | float: left;
117 | }
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Logseq CardBox Plugin [
](https://www.buymeacoffee.com/hidekaz)
2 |
3 | English / [日本語](https://scrapbox.io/logseq-ja/Logseq_Cardbox_Plugin%2F%E4%BD%BF%E3%81%84%E6%96%B9)
4 |
5 | Plugin to add thumbnail cards to [Logseq](https://github.com/logseq/logseq) that are displayed in order of update.
6 |
7 | 
8 |
9 | ## How to launch
10 | Any of the following:
11 | - "CardBox" button on the left sidebar
12 | - Click the CardBox icon in the top right corner of the page
13 | - Enter "Open CardBox" in the command palette
14 | - Shortcut:
15 | - (Windows) Ctrl + Shift + Enter
16 | - (macOS) Cmd + Shift + Enter
17 |
18 | 
19 |
20 | - Immediately after installation, CardBox will automatically build the database. During this time, the message "Building..." will appear in the upper left corner of the CardBox. Do not exit Logseq until this message disappears.
21 |
22 | ## Selecting a card
23 | - The thumbnail cards of the pages are ordered by the date of the last update, starting from the top left.
24 | - Only pages are displayed. The journal and whiteboard are not displayed.
25 | - Click on a page or use the cursor keys to move the selection and press Enter to open it.
26 | - If you hold down Shift while performing the open operation, the page will open in the sidebar.
27 |
28 | ## Filtering using page tags
29 | - Type a character key to filter cards by a page tag
30 | - What is a page tag? See below:
31 |
32 | 
33 |
34 | ## Closing the CardBox
35 | - Close CardBox by pressing the X button in the top right-hand corner or pressing the Esc key.
36 |
37 | ## Key bindings
38 | - Any character keys: Input a page tag
39 | - Up, Down, Left, Right: Move the cursor to select a card
40 | - Enter: Open the selected card
41 | - Shift+Enter: Open the selected card in the sidebar
42 | - Esc: Close the CardBox
43 |
44 | ## Languages supported
45 | - English
46 | - Japanese
47 |
48 | You need to restart Logseq for the language change to take effect.
49 |
50 | ## Limitations
51 | - CardBox will not display pages without body text.
52 | - Logseq does not create a .md file for a page with only a title without body text. This plug-in reads the modification time of the .md file directly, so it cannot display pages with no file.
53 | - Since Logseq is currently in beta, it cannot correctly manage the modification time of pages. If the modification time is incorrect, please press the "Rebuild" button (it will get the modification time directly from the specified pages folder).
54 | - See https://github.com/logseq/logseq/issues/8556
55 | - Changes made directly to the .md file while Logseq is not running will not be reflected in the CardBox.
56 | - To reflect them, press the 'Rebuild' button.
57 | - After executing the "Re-index" in Logseq, press the "Rebuild" button in the CardBox.
58 | - If you do not rebuild, pages may not be displayed in the correct time order.
59 | - If you do not rebuild, you cannot open the page in the sidebar from CardBox.
60 | - Pages with a slash at the end of the title (e.g. MyPage/ ) will not display correctly.
61 |
62 | # Support
63 |
64 | If you like it, please donate to me to continue the development.
65 |
66 | [](https://www.buymeacoffee.com/hidekaz)
67 |
--------------------------------------------------------------------------------
/src/main.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import ReactDOM from 'react-dom/client'
3 | import App from './App.tsx'
4 | import "@fontsource/roboto"
5 | import "@fontsource/roboto/700.css"
6 | import '@fontsource/noto-sans-jp'
7 | import '@fontsource/noto-sans-jp/700.css'
8 | import './main.css'
9 | import '@logseq/libs'
10 | import './i18n/configs';
11 | import { SimpleCommandKeybinding } from '@logseq/libs/dist/LSPlugin'
12 |
13 | const openCardBox = () => {
14 | logseq.showMainUI();
15 | }
16 |
17 | function main() {
18 | // Ctrl+Shift+Enter or Command+Shift+Enter
19 | /*
20 | logseq.App.registerCommandShortcut(
21 | { binding: 'mod+shift+enter' },
22 | () => logseq.showMainUI(),
23 | );
24 | */
25 | // It might be more in line with the Logseq way to register it in the command palette.
26 | // In this case, it's also possible to assign a name to the shortcut."
27 | const command: {
28 | key: string;
29 | keybinding: SimpleCommandKeybinding
30 | label: string;
31 | } = {
32 | key: 'cardbox:open',
33 | keybinding: {
34 | binding: 'mod+shift+enter',
35 | mode: 'global',
36 | },
37 | label: 'Open CardBox',
38 | };
39 | logseq.App.registerCommandPalette(command, openCardBox);
40 |
41 | logseq.provideStyle(`
42 | @import url("https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:opsz,wght,FILL,GRAD@24,400,0,0");
43 | `)
44 |
45 | logseq.setMainUIInlineStyle({
46 | position: 'fixed',
47 | zIndex: 20,
48 | })
49 |
50 | logseq.App.registerUIItem('pagebar', {
51 | key: 'cardbox',
52 | template: `
53 |
54 |
55 | grid_view
56 |
57 |
58 | `,
59 | });
60 |
61 | const cardboxDiv = document.createElement('div');
62 | cardboxDiv.innerHTML = `
63 |
64 |
65 |
66 |
67 | CardBox
68 |
69 | `;
70 | cardboxDiv.className = `cardbox-nav`;
71 | cardboxDiv.addEventListener('click', openCardBox);
72 |
73 | const navHeader = window.parent.document.querySelector('.nav-header');
74 | const cardboxNav = navHeader!.querySelector(`.cardbox-nav`);
75 | if (cardboxNav) {
76 | navHeader!.removeChild(cardboxNav);
77 | }
78 | navHeader!.insertBefore(cardboxDiv, navHeader!.lastChild);
79 |
80 | document.body.addEventListener('click', (e) => {
81 | if ((e.target as HTMLElement).classList.length === 0) {
82 | // stopPropagation on