Repository: sawhney17/logseq-schrodinger
Branch: main
Commit: 032fdd9409bf
Files: 16
Total size: 48.3 KB
Directory structure:
gitextract_vgv2rhpk/
├── .gitattributes
├── .github/
│ └── workflows/
│ └── publish.yml
├── .gitignore
├── .postcssrc
├── LICENSE
├── README.md
├── index.html
├── package.json
├── src/
│ ├── App.css
│ ├── App.tsx
│ ├── handleClosePopup.ts
│ ├── index.tsx
│ ├── tailwind.css
│ └── utils.tsx
├── tailwind.config.js
└── tsconfig.json
================================================
FILE CONTENTS
================================================
================================================
FILE: .gitattributes
================================================
# Auto detect text files and perform LF normalization
* text=auto
================================================
FILE: .github/workflows/publish.yml
================================================
name: Build plugin
on:
push:
# Sequence of patterns matched against refs/tags
tags:
- '*' # Push events to matching any tag format, i.e. 1.0, 20.15.10
env:
PLUGIN_NAME: logseq-hugo-plugin
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Use Node.js
uses: actions/setup-node@v1
with:
node-version: '16.x' # You might need to adjust this value to your own version
- name: Build
id: build
run: |
npm i && npm run build
mkdir ${{ env.PLUGIN_NAME }}
cp README.md package.json icon.png ${{ env.PLUGIN_NAME }}
mv dist ${{ env.PLUGIN_NAME }}
zip -r ${{ env.PLUGIN_NAME }}.zip ${{ env.PLUGIN_NAME }}
ls
echo "::set-output name=tag_name::$(git tag --sort version:refname | tail -n 1)"
- name: Create Release
uses: ncipollo/release-action@v1
id: create_release
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
VERSION: ${{ github.ref }}
with:
allowUpdates: true
draft: false
prerelease: false
- name: Upload zip file
id: upload_zip
uses: actions/upload-release-asset@v1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
upload_url: ${{ steps.create_release.outputs.upload_url }}
asset_path: ./${{ env.PLUGIN_NAME }}.zip
asset_name: ${{ env.PLUGIN_NAME }}-${{ steps.build.outputs.tag_name }}.zip
asset_content_type: application/zip
- name: Upload package.json
id: upload_metadata
uses: actions/upload-release-asset@v1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
upload_url: ${{ steps.create_release.outputs.upload_url }}
asset_path: ./package.json
asset_name: package.json
asset_content_type: application/json
================================================
FILE: .gitignore
================================================
.DS_Store
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
lerna-debug.log*
.pnpm-debug.log*
# Diagnostic reports (https://nodejs.org/api/report.html)
report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
# Runtime data
pids
*.pid
*.seed
*.pid.lock
# Directory for instrumented libs generated by jscoverage/JSCover
lib-cov
# Coverage directory used by tools like istanbul
coverage
*.lcov
# nyc test coverage
.nyc_output
# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
.grunt
# Bower dependency directory (https://bower.io/)
bower_components
# node-waf configuration
.lock-wscript
# Compiled binary addons (https://nodejs.org/api/addons.html)
build/Release
# Dependency directories
node_modules/
jspm_packages/
# Snowpack dependency directory (https://snowpack.dev/)
web_modules/
# TypeScript cache
*.tsbuildinfo
# Optional npm cache directory
.npm
# Optional eslint cache
.eslintcache
# Microbundle cache
.rpt2_cache/
.rts2_cache_cjs/
.rts2_cache_es/
.rts2_cache_umd/
# Optional REPL history
.node_repl_history
# Output of 'npm pack'
*.tgz
# Yarn Integrity file
.yarn-integrity
# dotenv environment variables file
.env
.env.test
.env.production
# parcel-bundler cache (https://parceljs.org/)
.cache
.parcel-cache
# Next.js build output
.next
out
# Nuxt.js build / generate output
.nuxt
dist
# Gatsby files
.cache/
# Comment in the public line in if your project uses Gatsby and not Next.js
# https://nextjs.org/blog/next-9-1#public-directory-support
# public
# vuepress build output
.vuepress/dist
# Serverless directories
.serverless/
# FuseBox cache
.fusebox/
# DynamoDB Local files
.dynamodb/
# TernJS port file
.tern-port
# Stores VSCode versions used for testing VSCode extensions
.vscode-test
# yarn v2
.yarn/cache
.yarn/unplugged
.yarn/build-state.yml
.yarn/install-state.gz
package-lock.json
yarn.lock
================================================
FILE: .postcssrc
================================================
{
"plugins": {
"postcss-import": {},
"tailwindcss/nesting": {},
"tailwindcss": {}
}
}
================================================
FILE: LICENSE
================================================
MIT License
Copyright (c) 2022 sawhney17
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
================================================
FILE: README.md
================================================
<div id="top"></div>
<!-- PROJECT SHIELDS -->
[![Contributors][contributors-shield]][contributors-url]
[![Forks][forks-shield]][forks-url]
[![Stargazers][stars-shield]][stars-url]
[![Issues][issues-shield]][issues-url]
[![MIT License][license-shield]][license-url]
<!-- PROJECT LOGO -->
<br />
<div align="center">
<a href="https://github.com/sawhney17/logseq-schrodinger">
<img src="icon.png" alt="Logo" width="80" height="80">
</a>
<h3 align="center">logseq Schrödinger</h3>
<p align="center">
An awesome <a href="https://logseq.com">Logseq</a> plugin to jumpstart your digital garden 🌱!
<br />
<a href="https://github.com/sawhney17/logseq-schrodinger"><strong>Explore the docs »</strong></a>
<br />
<br />
<a href="https://aryansawhney.com/">View Demo</a>
·
<a href="https://github.com/sawhney17/logseq-schrodinger/issues">Report Bug</a>
·
<a href="https://github.com/sawhney17/logseq-schrodinger/issues">Request Feature</a>
</p>
</div>
<!-- TABLE OF CONTENTS -->
<details>
<summary>Table of Contents</summary>
<ol>
<li><a href="#about-the-project">About The Project</a></li>
<li><a href="#installation">Installation</a></li>
<li><a href="#configuration">Configuration</a>
<ul>
<li><a href="#meta-data">Meta-data</a></li>
<li><a href="#configuring_hugo">Configuring Hugo</a></li>
<li><a href="#admonitions">Admonitions</a></li>
</ul>
</li>
<li><a href="#issues">Issues</a></li>
<li><a href="#contributing">Contributing</a></li>
<li><a href="#license">License</a></li>
<li><a href="#contact">Contact</a></li>
<li><a href="#acknowledgments">Acknowledgments</a></li>
</ol>
</details>
<!-- ABOUT THE PROJECT -->
## About The Project
[![Product Name Screen Shot][product-screenshot]](https://github.com/sawhney17/logseq-schrodinger/)
[Logseq](https://logseq.com) is a great PKM (personal knowledge management) tool, but keeping your knowledge for yourself only gets you so far. As [Erwin Schrödinger](https://simple.wikipedia.org/wiki/Erwin_Schrödinger) stated:
> If a note is not published, does it really exist? — Erwin Schrödinger
Knowledge is meant to be treasured and expanded, but before all shared. This plugin helps to make that possible, or at least easier.
**Note:** This project is very much a work-in-progress. Please report <a href="#issues">issues</a> and questions.
<p align="right">(<a href="#top">back to top</a>)</p>
<!-- GETTING STARTED -->
## Installation
### Preparation
- Click the 3 dots in the righthand corner and go to **Settings**.
- Go to **Advanced** and enable **Plug-in system**.
- Restart the application.
- Click 3 dots and go to Plugins (or `Esc t p`).
### Install plugin from the Marketplace (recommended)
- Click the `Marketplace` button and then click `Plugins`.
- Find the plugin and click `Install`.
### Install plugin manually
- Download a released version assets from Github.
- Unzip it.
- Click Load unpacked plugin, and select destination directory to the unzipped folder.
<p align="right">(<a href="#top">back to top</a>)</p>
<!-- Configuration -->
## Configuration
- Click the 3 dots in the righthand corner and go to **Settings**.
- Go to **Plugin Settings**.
- Select correct plugin.
[![Configuration screen][configuration-screenshot]](##configuration)
<p align="right">(<a href="#top">back to top</a>)</p>
### Meta-data
This plugin uses YAML for the Hugo [front-matter](https://gohugo.io/content-management/front-matter/). It will convert Logseq page-properties to Hugo front matter.
Logseq _keywords_ are lowercase converted to Hugo keywords, and **category** in Logseq is translated to _categories_ for use with Hugo. Logseq _links_ (`[[like_this]]`) are stripped of `[[` and `]]`.
All other _keywords_ are just converted to Hugo _keywords_.
For now you _must_ add **date** with the posts date in the form of "2012-04-06" to your Logseq page-properties.
```markdown
date:: 2012-04-06
```
<h3 id="configuring_hugo">Configuring Hugo</h3>
[Hugo][hugo] does not by default support backlinks. Use a snippet like the following to simulate backlinks. It will parse every page for local links. This snippet should be placed in `~yourhugo/layouts/partials/backlinks.html`.
```html
{{ $re := $.File.BaseFileName }} {{ $backlinks := slice }} {{ range where
.Site.RegularPages "Type" "page" }} {{ if and (findRE $re .RawContent) (not (eq
$re .File.BaseFileName)) }} {{ $backlinks = $backlinks | append . }} {{ end }}
{{ end }} {{ if gt (len $backlinks) 0 }}
<aside>
<h3>Backlinks</h3>
<div class="backlinks">
<ul>
{{ range $backlinks }}
<li class="capitalize"><a href="{{ .RelPermalink }}">{{ .Title }}</a></li>
{{ end }}
</ul>
</div>
</aside>
{{ else }}
<aside>
<h4>No notes link to this note</h4>
</aside>
{{ end }}
<aside class="related">
{{ $related := .Site.RegularPages.Related . | complement $backlinks | first 3
-}} {{ with $related -}}
<h3>slightly related</h3>
<ul>
{{ range . -}}
<li class="capitalize"><a href="{{ .RelPermalink }}">{{ .Title }}</a></li>
{{ end -}}
</ul>
{{ end -}}
</aside>
```
<img src="./images/backlinks.png" width="200px">
### Admonitions
Logseq has several built-in adminitions, namely:
- caution
- example
- important
- note
- pinned
- tip
- quote
- warning
These get converted to:
```markdown
{{< logseq/orgCAUTION >}}Caution here{{< / logseq/orgCAUTION >}}
{{< logseq/orgEXAMPLE >}}This is an example{{< / logseq/orgEXAMPLE >}}
{{< logseq/orgIMPORTANT >}}This is important{{< / logseq/orgIMPORTANT >}}
{{< logseq/orgNOTE >}}This is a note{{< / logseq/orgNOTE >}}
{{< logseq/orgPINNED >}}This is pinned{{< / logseq/orgPINNED >}}
{{< logseq/orgTIP >}}This is a tip{{< / logseq/orgTIP >}}
{{< logseq/orgQUOTE >}}This is a quote{{< / logseq/orgQUOTE >}}
{{< logseq/orgWARNING >}}This is a warning{{< / logseq/orgWARNING >}}
```
So Hugo needs those in `~yourhugo/layouts/shortcodes/logseq/`:
```txt
orgCAUTION.html
orgEXAMPLE.html
orgIMPORTANT.html
orgNOTE.html
orgPINNED.html
orgQUOTE.html
orgTIP.html
orgWARNING.html
```
And they should contain something along the lines of:
```html
<div class="caution {{ .Get 0 }}">{{ .Inner | $.Page.RenderString }}</div>
```
<p align="right">(<a href="#top">back to top</a>)</p>
## Website templates
There are some basic website templates you can take it as a reference.
1. [logseq-hugo-template](https://github.com/sawhney17/logseq-hugo-template/), by [sawhney17](https://github.com/sawhney17).
1. You can host your personal website with your favorite web hosting providers.
2. [Logseq-Hugo-Template](https://github.com/CharlesChiuGit/Logseq-Hugo-Template), by [CharlesChiuGit](https://github.com/CharlesChiuGit).
1. Host your personal website with free [GitHub pages](https://pages.github.com/).
<p align="right">(<a href="#top">back to top</a>)</p>
<!-- Issues -->
## Issues
See the [open issues](https://github.com/sawhney17/logseq-schrodinger/issues) for a full list of proposed features (and known issues).
### What works
- Local Hugo links (but Logseq uses one folder for everything, so Hugo does too)
- Block refs(!) — On conversion the block is pulled from the other location
- Images
- Basic Markdown styling (including highlighting!)
### What is known to _not_ work
- Indentation Logseq ➡ Hugo is still a work-in-progress
<p align="right">(<a href="#top">back to top</a>)</p>
<!-- CONTRIBUTING -->
## Contributing
Contributions are what make the open source community such an amazing place to learn, inspire, and create. Any contributions you make are **greatly appreciated**.
If you have a suggestion that would make this better, please fork the repo and create a pull request. You can also simply open an issue with the tag "enhancement".
Don't forget to give the project a star! Thanks again!
1. Fork the Project
2. Create your Feature Branch (`git checkout -b feature/AmazingFeature`)
3. Commit your Changes (`git commit -m 'Add some AmazingFeature'`)
4. Push to the Branch (`git push origin feature/AmazingFeature`)
5. Open a Pull Request
<p align="right">(<a href="#top">back to top</a>)</p>
<!-- LICENSE -->
## License
Distributed under the MIT License. See `LICENSE.txt` for more information.
<p align="right">(<a href="#top">back to top</a>)</p>
<!-- CONTACT -->
## Contact
Aryan Sawhney - [@Aryan Sawhney](https://twitter.com/aryansawhney17)
Project Link: [https://github.com/sawhney17/logseq-schrodinger](https://github.com/sawhney17/logseq-schrodinger)
<p align="right">(<a href="#top">back to top</a>)</p>
## Acknowledgments
I would like to thank Alex Qwxlea ([@twitter_handle](https://twitter.com/QwxleaA)) for the idea to write this Logseq plugin. Also for breaking the plugin after I wrote it. And finally, thank him for adding this note: Qwxlea, you're the best 😁!
<!-- MARKDOWN LINKS & IMAGES -->
<!-- https://www.markdownguide.org/basic-syntax/#reference-style-links -->
[contributors-shield]: https://img.shields.io/github/contributors/sawhney17/logseq-schrodinger.svg?style=for-the-badge
[contributors-url]: https://github.com/sawhney17/logseq-schrodinger/graphs/contributors
[forks-shield]: https://img.shields.io/github/forks/sawhney17/logseq-schrodinger.svg?style=for-the-badge
[forks-url]: https://github.com/sawhney17/logseq-schrodinger/network/members
[stars-shield]: https://img.shields.io/github/stars/sawhney17/logseq-schrodinger.svg?style=for-the-badge
[stars-url]: https://github.com/sawhney17/logseq-schrodinger/stargazers
[issues-shield]: https://img.shields.io/github/issues/sawhney17/logseq-schrodinger.svg?style=for-the-badge
[issues-url]: https://github.com/sawhney17/logseq-schrodinger/issues
[license-shield]: https://img.shields.io/github/license/sawhney17/logseq-schrodinger.svg?style=for-the-badge
[license-url]: https://github.com/sawhney17/logseq-schrodinger/blob/master/LICENSE.txt
[product-screenshot]: images/screenshot.jpg
[configuration-screenshot]: ./images/configuration.png
[hugo]: https://gohugo.io
[logseq]: https://logseq.com
================================================
FILE: index.html
================================================
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>logseq-custom-workflows</title>
</head>
<body>
<div id="app"></div>
<script src="./src/index.tsx" type="module"></script>
</body>
</html>
================================================
FILE: package.json
================================================
{
"name": "logseq-schrodinger",
"version": "1.3.2",
"description": "An awesome logseq plugin to export to Hugo Static sites and jumpstart your digital garden 🌱!",
"main": "dist/index.html",
"targets": {
"main": false
},
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"build": "parcel build --no-source-maps index.html --public-url ./"
},
"keywords": [],
"author": "Aryan Sawhney & Alex Qwxlea",
"license": "MIT",
"dependencies": {
"@logseq/libs": "^0.0.14",
"file-saver": "^2.0.5",
"jszip": "^3.8.0",
"postcss": "^8.4.12",
"react": "^18.0.0",
"react-dom": "^18.0.0",
"tailwindcss": "^3.0.23"
},
"logseq": {
"id": "logseq-hugo-plugin",
"title": "logseq-hugo-plugin",
"icon": "./icon.png"
},
"devDependencies": {
"@types/react": "^17.0.38",
"@types/react-dom": "^17.0.11",
"autoprefix": "^1.0.1",
"autoprefixer": "^10.4.2",
"buffer": "^6.0.3",
"parcel": "^2.0.0",
"postcss-cli": "^9.1.0",
"postcss-import": "^14.0.2"
}
}
================================================
FILE: src/App.css
================================================
html {
line-height: 1.5; /* 1 */
-webkit-text-size-adjust: 100%; /* 2 */
-moz-tab-size: 4; /* 3 */
-o-tab-size: 4;
tab-size: 4; /* 3 */
font-family: ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans", sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji"; /* 4 */;
--cl-charcoal: #3f3f3f;
--cl-darkjungle: #1e1e1e;
--cl-thunder: #4c4c4c;
}
.overlay {
position: fixed;
top: 0;
left: 0;
width: 100vw;
height: 100vh;
background: hsla(0, 0%, 10%, 0.5);
z-index: 9;
}
.smartblock-popup {
padding: 1em;
background: rgb(40, 157, 165);
z-index: 9;
border-radius: 10px;
/* -ms-transform: translate(-50%, -50%);
transform: translate(-50%, -50%); */
}
.popup-content {
z-index: 10;
}
.centered-element {
margin: 0;
position: absolute;
top: 30%;
transform: translateY(-50%);
}
.grid-container {
display: grid;
grid-template-columns: 1fr 1fr;
grid-gap: 20px;
padding-top: 10px;
}
.label-class {
color: whitesmoke;
font-family: system-ui;
padding-bottom: 10px;
font-size: larger;
}
.smartblock-inserter {
background-color: var(--cl-darkjungle);
padding: 1em;
/* background: rgb(40, 157, 165); */
z-index: 9;
border-radius: 15px;
/* top: 10%; */
width: 300px;
display: flex;
transform: translate(0, 5px);
flex-direction: column;
color: #ffffff;
/* align-items: center; */
background-color: var(--cl-darkjungle) !important;
border-top: 1px solid var(--cl-charcoal);
border-right: 1px solid var(--cl-charcoal);
border-bottom: 1px solid var(--cl-charcoal);
border-left: 1px solid var(--cl-charcoal);
font-size: 1.2em;
font-family: system-ui;
padding: 0.5em;
border-radius: 5px;
outline: none;
/* box-shadow: 0 0 0 0 transparent; */
/* transition: box-shadow 0.3s ease-in-out; */
}
.cp__palette-input {
width: 100%;
height: 100%;
border: none;
background: transparent;
color: #ffffff;
font-size: 1.2em;
font-family: system-ui;
padding: 0.5em;
border-radius: 5px;
outline: none;
box-shadow: 0 0 0 0 transparent;
transition: box-shadow 0.3s ease-in-out;
}
.searchItem {
color: #ffffff;
}
.full-width {
width: 100%;
}
================================================
FILE: src/App.tsx
================================================
import React, { useRef, useState } from "react";
import ReactDOM from "react-dom";
import "./App.css";
import "./tailwind.css";
import { getBlocksInPage } from "./utils";
const App: React.FC = () => {
const [noteName, setNoteName] = useState(() => {
logseq.Editor.getCurrentPage().then((page) => {
setNoteName(page.originalName);
setHugoFileName(page.name)
});
return "noteName";
});
const [hugoFileName, setHugoFileName] = useState("");
const [originalDate, setOriginalDate] = useState("");
const [updatedDate, setUpdatedDate] = useState("");
const [mappedtagsValues, setMappedtagsValues] = useState([{ tags: "tag" }]);
const [mappedCategoryValues, setMappedCategoryValues] = useState([
{ category: "category" },
]);
//create a function to handle change of inputs
const handletagsChange = (
event: React.ChangeEvent<HTMLInputElement>,
index
) => {
const { name, value } = event.target;
let targettedValues = [...mappedtagsValues];
targettedValues[index].tags = value;
setMappedtagsValues(targettedValues);
};
const handleCategoryChange = (
event: React.ChangeEvent<HTMLInputElement>,
index
) => {
const { name, value } = event.target;
let targettedValues = [...mappedCategoryValues];
targettedValues[index].category = value;
setMappedCategoryValues(targettedValues);
};
const handleChange = (event: React.ChangeEvent<HTMLInputElement>) => {
const { name, value } = event.target;
if (name === "noteName") {
setNoteName(value);
} else if (name === "hugoFileName") {
setHugoFileName(value);
} else if (name === "originalDate") {
setOriginalDate(value);
} else if (name === "updatedDate") {
setUpdatedDate(value);
}
};
const createNewtags = () => {
setMappedtagsValues([...mappedtagsValues, { tags: "tag" }]);
};
const deletetags = (index: number) => {
let targettedValues = [...mappedtagsValues];
targettedValues.splice(index, 1);
setMappedtagsValues(targettedValues);
};
const createNewCategory = () => {
setMappedCategoryValues([...mappedCategoryValues, { category: "category" }]);
};
const deleteCategory = (index: number) => {
let targettedValues = [...mappedCategoryValues];
targettedValues.splice(index, 1);
setMappedCategoryValues(targettedValues);
};
return (
<div>
<div>
<div className="flex justify-center">
<div className="smartblock-inserter">
<h1 className="full-width text-left text-2xl font-bold">
Hugo Export
</h1>
<br></br>
<div className="overflow-auto h-80">
<div className="-">
<div className="grid justify-between gap-2 px-2">
<p>Note Name</p>
<input
name="noteName"
type="text"
className="text-black rounded-md"
value={noteName}
onChange={handleChange}
/>
<p>Hugo File Name</p>
<input
name="hugoFileName"
type="text"
className="text-black rounded-md"
value={hugoFileName}
onChange={handleChange}
/>
<p>Original Date</p>
<input
name="originalDate"
type="date"
className="text-black rounded-md"
value={originalDate}
onChange={handleChange}
/>
<p>Updated Post Date</p>
<input
name="updatedDate"
type="date"
className="text-black rounded-md"
value={updatedDate}
onChange={handleChange}
/>
<div className="flex justify-between">
<p className="inline-block">Tags</p>
<div className="inline-block px-2">
<button
onClick={createNewtags}
className="bg-white text-black font-bold px-2 rounded-sm h-max"
>
+
</button>
</div>
</div>
{mappedtagsValues.map((val, index) => {
return (
<div className="flex justify-between pb-2">
<input
name={index.toString()}
type="text"
className="text-black rounded-md"
value={mappedtagsValues[index].tags}
onChange={(e) => handletagsChange(e, index)}
></input>
<div className="px-2">
<button
onClick={() => {
deletetags(index);
}}
className="bg-white text-black font-bold px-3 pr-2 rounded-sm h-max"
>
-
</button>
</div>
</div>
);
})}
<div>
<div className="flex justify-between pb-2"></div>
</div>
<div>
<div className="flex justify-between">
<p className="inline-block">Categories</p>
<div className="p-2 inline-block">
<button
onClick={createNewCategory}
className="bg-white text-black font-bold px-2 pr-2 rounded-sm h-max"
>
+
</button>
</div>
</div>
{mappedCategoryValues.map((val, index) => {
return (
<div className="flex justify-between pb-2">
<input
name={index.toString()}
type="Categories"
className="text-black rounded-md"
value={mappedCategoryValues[index].category}
onChange={(e) => handleCategoryChange(e, index)}
></input>
<div className="px-2">
<button
onClick={() => {
deleteCategory(index);
}}
className="bg-white text-black font-bold px-3 pr-2 rounded-sm h-max"
>
-
</button>
</div>
</div>
);
})}
</div>
</div>
</div>
</div>
<div className="flex justify-center">
<button
className=" border-light-300 border-2 w-200px hover:bg-black p-3 px-5 rounded-lg"
onClick={async () => {
getBlocksInPage(
{ page: await ( logseq.Editor.getCurrentPage()) },
true,
true,
mappedtagsValues,
[
{ updatedDate: updatedDate },
{ originalDate: originalDate },
],
[{ noteName: noteName }, { hugoFileName: hugoFileName }],
mappedCategoryValues
);
}}
>
Convert
</button>
</div>
</div>
</div>
</div>
</div>
);
};
export default App;
================================================
FILE: src/handleClosePopup.ts
================================================
export const handleClosePopup = () => {
//ESC
document.addEventListener(
'keydown',
function (e) {
if (e.keyCode === 27) {
logseq.hideMainUI({ restoreEditingCursor: true });
}
e.stopPropagation();
},
false
);
};
================================================
FILE: src/index.tsx
================================================
import "@logseq/libs";
import {
BlockEntity,
PageEntity,
SettingSchemaDesc,
} from "@logseq/libs/dist/LSPlugin";
import React from "react";
import ReactDOM from "react-dom";
import App from "./App";
import { handleClosePopup } from "./handleClosePopup";
import { getAllPublicPages, getBlocksInPage } from "./utils";
export var path = "";
const linkFormats = ["[[Logseq Format]]", "Without brackets"];
let settings: SettingSchemaDesc[] = [
{
key: "linkFormat",
type: "enum",
enumChoices: linkFormats,
enumPicker: "radio",
title: "How would you like links to be formatted",
description:
"Do you want your exported links with or without brackets? If a page that is linked to is public, the link will automatically be hyperlinked in the hugo export, otherwise, this setting will be applied",
default: linkFormats[0],
},
{
key: "bulletHandling",
type: "enum",
enumChoices: ["Convert Bullets", "Remove All Bullets"],
enumPicker: "radio",
title: "How would you like Logseq's bullets to be handled",
description:
"How would you like Logseq's bullets to be handled, convert to hugo's native style or remove all bullets?",
default: "Convert Bullets",
},
{
key: "exportTasks",
type: "boolean",
title: "Do you want tasks to exported to Hugo?",
description:
"Yes, blocks with tasks will be exported: (TODO DOING DONE LATER NOW WAITING)",
default: false,
},
{
key: "leafTitle",
type: "boolean",
title: "Do you want to use only the leaf title?",
description:
"Use the end of title (what is after the last / in Logseq page title) as title instead of the whole title hierarchy." +
"\nExample: for 'parent/branch/leaf', title would be 'leaf'",
default: false,
},
{
key: "tagsFromTitle",
type: "boolean",
title: "Do you want to add Logseq title hierarchy to tags?",
description: "Add title hierarchy to tags?",
default: false,
},
{
key: "assetsPath",
type: "string",
title: "Path to assets",
description: "Path of each assets will start with the following",
default: "assets",
},
{
key: "pagesPath",
type: "string",
title: "Path to pages",
description: "Path of each pages will start with the following",
default: "content/pages",
},
{
key: "journalPath",
type: "string",
title: "Path to journal pages",
description: "Path of each journal pages will start with the following",
default: "content/posts",
},
];
const main = async () => {
console.log("Logseq Schrödinger plugin loaded");
ReactDOM.render(
//Render react component
<React.StrictMode>
<App />
</React.StrictMode>,
document.getElementById("app"),
);
logseq.setMainUIInlineStyle({
position: "fixed",
zIndex: 999,
transform: "translateX(-50%)",
});
function createModel() {
return {
show(e: any) {
const { rect } = e;
logseq.setMainUIInlineStyle({
top: `${rect.top + 25}px`,
left: `${rect.right - 17}px`,
});
logseq.toggleMainUI();
handleClosePopup();
},
export() {
getAllPublicPages();
},
};
}
logseq.provideModel(createModel());
logseq.setMainUIInlineStyle({
zIndex: 11,
});
logseq.App.registerPageMenuItem(
"Export all public pages to hugo",
getAllPublicPages,
);
logseq.App.registerUIItem("toolbar", {
key: "hugo-single-export",
template: `
<a class="button" data-on-click="show" data-rect>
<i class="ti ti-file-zip"></i>
</a>
`,
});
logseq.App.registerUIItem("toolbar", {
key: "export-public-pages-to-hugo",
template: `
<a class="button" data-on-click="export" data-rect>
<i class="ti ti-database-export"></i>
</a>
`,
});
logseq.useSettingsSchema(settings);
path = (await logseq.App.getCurrentGraph()).path;
};
logseq.ready(main).catch(console.error);
================================================
FILE: src/tailwind.css
================================================
@tailwind base;
@tailwind components;
@tailwind utilities;
================================================
FILE: src/utils.tsx
================================================
import "@logseq/libs";
import { saveAs } from "file-saver";
import JSZip, { file } from "jszip";
import { title } from "process";
import React from "react";
import ReactDOM from "react-dom";
import {
BlockEntity,
PageEntity,
SettingSchemaDesc,
} from "@logseq/libs/dist/LSPlugin";
import App from "./App";
import { handleClosePopup } from "./handleClosePopup";
import { linkFormats, path } from "./index";
export var blocks2 = [];
var errorTracker = [];
var zip = new JSZip();
var imageTracker = [];
let allPublicPages;
let allPublicLinks = []; //list of all exported pages
//Retired function
//I kept on missing pages?!?!?!
//Never figured out why
export async function getAllPublicPages_orig() {
errorTracker = [];
logseq.DB.q("(page-property public)").then((result) => {
const mappedResults = result.map((page) => {
return page.name;
});
for (const x in mappedResults) {
if (x != `${mappedResults.length - 1}`) {
getBlocksInPage({ page: mappedResults[x] }, false, false);
} else {
getBlocksInPage({ page: mappedResults[x] }, false, true);
}
}
});
}
export async function getAllPublicPages() {
//needs to be both public, and a page (with a name)
const query =
"[:find (pull ?p [*]) :where [?p :block/properties ?pr] [(get ?pr :public) ?t] [(= true ?t)][?p :block/name ?n]]";
allPublicPages = await logseq.DB.datascriptQuery(query);
allPublicPages = allPublicPages?.flat(); //FIXME is this needed?
for (const x of allPublicPages) {
allPublicLinks.push(x["original-name"].toLowerCase());
}
for (const x in allPublicPages) {
if (x != `${allPublicPages.length - 1}`) {
await getBlocksInPage({ page: allPublicPages[x] }, false, false);
} else {
await getBlocksInPage({ page: allPublicPages[x] }, false, true);
}
}
}
function hugoDate(timestamp) {
let date = new Date(timestamp);
//if date.getdate does not have a zero, add A ZERO BEFORE IT
let month;
if (date.getMonth() + 1 < 10) {
month = `0${date.getMonth() + 1}`;
} else {
month = `${date.getMonth() + 1}`;
}
let day;
if (date.getDate() < 10) {
day = `0${date.getDate()}`;
} else {
day = `${date.getDate()}`;
}
return `${date.getFullYear()}-${month}-${day}`;
}
//parse files meta-data
async function parseMeta(
curPage,
tagsArray = [],
dateArray = [],
titleDetails = [],
categoriesArray = [],
) {
let propList = [];
//get all properties - fix later
if (curPage?.page.properties != undefined) {
propList = curPage?.page.properties;
}
//Title
//FIXME is filename used?
if (logseq.settings.leafTitle) {
propList.title = curPage.page["original-name"].split("/").slice(-1)[0];
} else {
propList.title = curPage.page["original-name"];
}
if (titleDetails.length > 0) {
propList.title = titleDetails[0].noteName;
propList.fileName = titleDetails[1].hugoFileName;
}
//Tags
propList.tags = curPage?.page.properties.tags
? curPage?.page.properties.tags
: [];
for (const tag in tagsArray) {
propList.tags.push(tagsArray[tag].tags);
}
// Add tags from title
if (logseq.settings.tagsFromTitle) {
curPage.page["original-name"].split("/").slice(0, -1).forEach((tag) =>
propList.tags.push(tag)
);
}
//Categories - 2 possible spellings!
const tmpCat = curPage?.page.properties.category
? curPage?.page.properties.category
: [];
propList.categories = curPage?.page.properties.categories
? curPage?.page.properties.categories
: tmpCat;
if (categoriesArray != []) {
let formattedCategoriesArray = [];
for (const category in categoriesArray) {
formattedCategoriesArray.push(categoriesArray[category].category);
}
if (propList.categories != undefined) {
for (const category in formattedCategoriesArray) {
propList.categories.push(formattedCategoriesArray[category]);
}
} else {
propList.categories = formattedCategoriesArray;
}
}
//Date - if not defined, convert Logseq timestamp
propList.date = curPage?.page.properties.date
? curPage?.page.properties.date
: hugoDate(curPage.page["created-at"]);
propList.lastMod = curPage?.page.properties.lastmod
? curPage?.page.properties.lastmod
: hugoDate(curPage.page["updated-at"]);
if (dateArray.length > 0) {
propList.date = dateArray[1].originalDate;
propList.lastMod = dateArray[0].updatedDate;
}
//these properties should not be exported to Hugo
const nope = ["filters", "public"];
for (const nono of nope) {
delete propList[nono];
}
//convert propList to Hugo yaml
// https://gohugo.io/content-management/front-matter/
let ret = `---`;
for (let [prop, value] of Object.entries(propList)) {
if (Array.isArray(value)) {
ret += `\n${prop}:`;
value.forEach((element) => (ret += `\n- ${element}`));
} else {
ret += `\n${prop}: ${value}`;
}
}
ret += "\n---";
return ret;
}
export async function getBlocksInPage(
e,
singleFile,
isLast,
tagsArray = [],
dateArray = [],
titleDetails = [],
categoriesArray = [],
allPublicPages = [],
) {
//if e.page.originalName is undefined, set page to equal e.page.original-name
let curPage = e.page;
if (curPage.originalName != undefined) {
curPage["original-name"] = curPage.originalName;
}
const docTree = await logseq.Editor.getPageBlocksTree(
curPage["original-name"],
);
const metaData = await parseMeta(
e,
tagsArray,
dateArray,
titleDetails,
categoriesArray,
);
// parse page-content
let finalString = await parsePage(metaData, docTree);
// FIXME ??
if (singleFile) {
logseq.hideMainUI();
handleClosePopup();
download(`${titleDetails[1].hugoFileName}.md`, finalString);
} else {
// console.log(`e["original-name"]: ${e["original-name"]}`);
//page looks better in the URL
let path = curPage["journal?"]
? logseq.settings.journalPath
: logseq.settings.pagesPath;
path = path.replace(/\/$/, ""); // remove trailing slash
zip.file(
`${path}/${
curPage["original-name"].replaceAll(
/([\u2700-\u27BF]|[\uE000-\uF8FF]|\uD83C[\uDC00-\uDFFF]|\uD83D[\uDC00-\uDFFF]|[\u2011-\u26FF]|\uD83E[\uDD10-\uDDFF])/g,
"",
)
}.md`,
finalString,
);
if (isLast) {
setTimeout(() => {
console.log(zip);
zip.generateAsync({ type: "blob" }).then(function (content) {
// see FileSaver.js
saveAs(content, "publicExport.zip");
//wait one second
// setTimeout(() => {
// saveAs(content, "publicExport.zip");
// }, 1000);
zip = new JSZip();
});
}, imageTracker.length * 102);
}
}
}
async function parsePage(finalString: string, docTree) {
// console.log("DB parsePage")
for (const x in docTree) {
// skip meta-data
if (!(parseInt(x) === 0 && docTree[x].level === 1)) {
//parseText will return 'undefined' if a block skipped
const ret = await parseText(docTree[x]);
if (typeof ret != "undefined") {
finalString = `${finalString} \n${ret} `;
}
if (docTree[x].children.length > 0) {
finalString = await parsePage(finalString, docTree[x].children);
}
}
}
return finalString;
}
function parseLinks_old(text: string, allPublicPages) {
//returns text withh all links converted
// FIXME This needs to be rewritten (later) so we don't loop all the pages twice
// conversion of links to hugo syntax https://gohugo.io/content-management/cross-references/
// Two kinds of links: [[a link]]
// [A description]([[a link]])
// Regular links are done by Hugo [logseq](https://logseq.com)
const reLink: RegExp = /\[\[.*?\]\]/g;
const reDescrLink: RegExp = /\[([a-zA-Z ]*?)\]\(\[\[(.*?)\]\]\)/g;
//[garden]([[digital garden]])
if (logseq.settings.linkFormat == "Hugo Format") {
if (reDescrLink.test(text)) {
text = text.replaceAll(reDescrLink, (result) => {
for (const x in allPublicPages) {
if (
result[2].toLowerCase ==
allPublicPages[x]["original-name"].toLowerCase
) {
const txt = reDescrLink.exec(result);
return txt
? `[${txt[1]}]({{ < ref "${txt[2]}" >}
})`
: "";
// return (txt) ? `[${ txt[1] }]({{ < ref "${txt[2].replaceAll(" ","_")}" >}})` : ""
}
}
});
}
text = text.replaceAll(reLink, (match) => {
const txt = match.substring(2, match.length - 2);
for (const x in allPublicPages) {
if (
txt.toUpperCase() == allPublicPages[x]["original-name"].toUpperCase()
) {
return `[${txt}]({{
< ref "${
allPublicPages[x]["original-name"].replaceAll(
" ",
" ",
)
} " >}})`;
}
}
return txt;
});
}
if (logseq.settings.linkFormat == "Without brackets") {
text = text.replaceAll("[[", "");
text = text.replaceAll("]]", "");
}
return text;
}
function parseLinks(text: string, allPublicPages) {
//returns text with all links converted
// conversion of links to hugo syntax https://gohugo.io/content-management/cross-references/
// Two kinds of links: [[a link]]
// [A description]([[a link]])
// Regular links are done by Hugo [logseq](https://logseq.com)
const reLink: RegExp = /\[\[(.*?)\]\]/gmi;
const reDescrLink: RegExp = /\[([a-zA-Z ]*?)\]\(\[\[(.*?)\]\]\)/gmi;
// FIXME why doesn't this work?
// if (! reDescrLink.test(text) && ! reLink.test(text)) return text
let result;
while (result = reDescrLink.exec(text) || reLink.exec(text)) {
if (allPublicLinks.includes(result[result.length - 1].toLowerCase())) {
text = text.replace(
result[0],
`[${result[1]}]({{< ref "/pages/${result[result.length - 1]}" >}})`,
);
}
}
if (logseq.settings.linkFormat == "Without brackets") {
text = text.replaceAll("[[", "");
text = text.replaceAll("]]", "");
}
return text;
}
async function parseNamespaces(text: string, blockLevel: number) {
const namespace: RegExp = /{{namespace\s([^}]+)}}/gmi;
let result;
while (result = namespace.exec(text)) {
const currentNamespaceName = result[result.length - 1];
const query =
`[:find (pull ?c [*]) :where [?p :block/name "${currentNamespaceName.toLowerCase()}"] [?c :block/namespace ?p]]`;
let namespacePages = await logseq.DB.datascriptQuery(query);
namespacePages = namespacePages?.flat(); //FIXME is this needed?
let txtBeforeNamespacePage: string = "";
if (logseq.settings.bulletHandling == "Convert Bullets") {
txtBeforeNamespacePage = " ".repeat(blockLevel * 2) + "+ ";
}
let namespaceContent = `**Namespace [[${currentNamespaceName}]]**\n\n`;
if (allPublicLinks.includes(currentNamespaceName.toLowerCase())) {
namespaceContent = namespaceContent.replace(
`[[${currentNamespaceName}]]`,
`[${currentNamespaceName}]({{< ref "/pages/${currentNamespaceName}" >}})`,
);
}
for (const page of namespacePages) {
const pageOrigName = page["original-name"];
if (allPublicLinks.includes(page["original-name"].toLowerCase())) {
const pageName = pageOrigName.replace(`${currentNamespaceName}/`, "");
namespaceContent = namespaceContent.concat(
txtBeforeNamespacePage +
`[${pageName}]({{< ref "/pages/${pageOrigName}" >}})\n\n`,
);
}
}
text = text.replace(result[0], namespaceContent);
}
return text;
}
function secondsToHms(d) {
d = Number(d);
var h = Math.floor(d / 3600);
var m = Math.floor(d % 3600 / 60);
var s = Math.floor(d % 3600 % 60);
var hDisplay = h > 9 ? String(h) : "0" + String(h);
var mDisplay = m > 9 ? String(m) : "0" + String(m);
var sDisplay = s > 9 ? String(s) : "0" + String(s);
return hDisplay + ":" + mDisplay + ":" + sDisplay;
}
async function parseText(block: BlockEntity) {
//returns either a hugo block or `undefined`
let re: RegExp;
let text = block.content;
// console.log("block", block)
let txtBefore: string = "";
let txtAfter: string = "\n";
const prevBlock: BlockEntity = await logseq.Editor.getBlock(block.left.id, {
includeChildren: false,
});
//Block refs - needs to be at the beginning so the block gets parsed
//FIXME they need some indicator that it *was* an embed
const rxGetId =
/\(\(([0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})\)\)/;
const rxGetEd =
/{{embed \(\(([0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})\)\)}}/;
const blockId = rxGetEd.exec(text) || rxGetId.exec(text);
if (blockId != null) {
const block = await logseq.Editor.getBlock(blockId[1], {
includeChildren: true,
});
if (block != null) {
// console.log("DB blockId", blockId)
text = text.replace(
blockId[0],
block.content.substring(0, block.content.indexOf("id::")),
);
}
}
//task markers - skip
if (block.marker && !logseq.settings.exportTasks) return;
//Images
//FIXME {:class medium, :height 506, :width 321}
//Logseq has extra info: height and width that can be used in an image template
//Get regex to check if text contains a md image
const reImage = /!\[.*?\]\((.*?)\)/g;
try {
text.match(reImage).forEach((element) => {
element.match(/(?<=!\[.*\])(.*)/g).forEach((match) => {
let finalLink = match.substring(1, match.length - 1);
// return (match.substring(1, match.length - 1))
text = text.replace(match, match.toLowerCase());
if (!finalLink.includes("http") || !finalLink.includes(".pdf")) {
text = text.replace("../", "/");
imageTracker.push(finalLink);
addImageToZip(finalLink);
}
});
});
} catch (error) {}
// FIXME for now all indention is stripped out
// Add indention — level zero is stripped of "-", rest are lists
// Experiment, no more lists, unless + or numbers
// (unless they're not)
if (logseq.settings.bulletHandling == "Convert Bullets") {
if (block.level > 1) {
txtBefore = " ".repeat((block.level - 1) * 2) + "+ ";
// txtBefore = "\n" + txtBefore
if (prevBlock.level === block.level) txtAfter = "";
}
}
if (prevBlock.level === block.level) txtAfter = "";
//exceptions (logseq has "-" before every block, Hugo doesn't)
if (text.substring(0, 3) === "```") txtBefore = "";
// Don't - indent images
if (reImage.test(text)) txtBefore = "";
//indent text + add newline after block
text = txtBefore + text + txtAfter;
//internal links
text = parseLinks(text, allPublicPages);
//namespaces
text = await parseNamespaces(text, block.level);
//Change {{youtube-timestamp ts}} via regex
const yTimestamps = /{{youtube-timestamp (.*?)}}/g;
text = text.replaceAll(yTimestamps, (match) => {
const timestampRegex = /{{youtube-timestamp ([0-9]+)}}/;
const timestamp = timestampRegex.exec(match);
if (timestamp != null) {
return `@${secondsToHms(timestamp[1])}`;
}
});
//youtube embed
//Change {{youtube url}} via regex
const reYoutube = /{{youtube(.*?)}}/g;
text = text.replaceAll(reYoutube, (match) => {
const youtubeRegex = /(youtu(?:.*\/v\/|.*v\=|\.be\/))([A-Za-z0-9_\-]{11})/;
const youtubeId = youtubeRegex.exec(match);
if (youtubeId != null) {
return `{{< youtube ${youtubeId[2]} >}}`;
}
});
//height and width syntax regex
// {:height 239, :width 363}
const heightWidthRegex = /{:height\s*[0-9]*,\s*:width\s*[0-9]*}/g;
text = text.replaceAll(heightWidthRegex, "");
//highlighted text, not supported in hugo by default!
re = /(==(.*?)==)/gm;
text = text.replace(re, "{{< logseq/mark >}}$2{{< / logseq/mark >}}");
re = /#\+BEGIN_([A-Z]*)[^\n]*\n(.*)#\+END_[^\n]*/gms;
text = text.replace(re, "{{< logseq/org$1 >}}$2{{< / logseq/org$1 >}}");
// text = text.toLowerCase();
text = text.replace(/:LOGBOOK:|collapsed:: true/gi, "");
if (text.includes("CLOCK: [")) {
text = text.substring(0, text.indexOf("CLOCK: ["));
}
if (text.indexOf(`\nid:: `) === -1) {
return text;
} else {
return text.substring(0, text.indexOf(`\nid:: `));
}
}
function getBase64Image(img) {
var canvas = document.createElement("canvas");
canvas.width = img.width;
canvas.height = img.height;
var ctx = canvas.getContext("2d");
ctx.drawImage(img, 0, 0);
var dataURL = canvas.toDataURL("image/png");
return dataURL.replace(/^data:image\/(png|jpg);base64,/, "");
}
function addImageToZip(filePath) {
var element = document.createElement("img");
let formattedFilePath = filePath.replace("..", path);
element.setAttribute("src", formattedFilePath);
element.style.display = "none";
document.body.appendChild(element);
setTimeout(() => {
var base64 = getBase64Image(element);
document.body.removeChild(element);
console.log(base64);
if (base64 != "data:,") {
zip.file(
logseq.settings.assetsPath + "/" +
filePath.split("/")[filePath.split("/").length - 1].toLowerCase(),
base64,
{ base64: true },
);
} else {
// console.log(base64);
}
}, 100);
}
//FIXME don't get it, but it works
function download(filename, text) {
var element = document.createElement("a");
element.setAttribute(
"href",
"data:text/plain;charset=utf-8," + encodeURIComponent(text),
);
// element.setAttribute('download', filename);
element.setAttribute("download", filename);
element.style.display = "none";
document.body.appendChild(element);
element.click();
document.body.removeChild(element);
}
================================================
FILE: tailwind.config.js
================================================
module.exports = {
content: ['./src/**/*.{vue,js,ts,jsx,tsx,hbs,html}'],
darkMode: 'media', // or 'media' or 'class'
theme: {
extend: {
spacing: {
100: '50rem',
},
},
},
variants: {
extend: {},
},
plugins: [],
};
================================================
FILE: tsconfig.json
================================================
{
"ts-node": {
"target": "es2021"
},
}
gitextract_vgv2rhpk/ ├── .gitattributes ├── .github/ │ └── workflows/ │ └── publish.yml ├── .gitignore ├── .postcssrc ├── LICENSE ├── README.md ├── index.html ├── package.json ├── src/ │ ├── App.css │ ├── App.tsx │ ├── handleClosePopup.ts │ ├── index.tsx │ ├── tailwind.css │ └── utils.tsx ├── tailwind.config.js └── tsconfig.json
SYMBOL INDEX (15 symbols across 2 files)
FILE: src/index.tsx
function createModel (line 99) | function createModel() {
FILE: src/utils.tsx
function getAllPublicPages_orig (line 29) | async function getAllPublicPages_orig() {
function getAllPublicPages (line 45) | async function getAllPublicPages() {
function hugoDate (line 65) | function hugoDate(timestamp) {
function parseMeta (line 86) | async function parseMeta(
function getBlocksInPage (line 179) | async function getBlocksInPage(
function parsePage (line 249) | async function parsePage(finalString: string, docTree) {
function parseLinks_old (line 268) | function parseLinks_old(text: string, allPublicPages) {
function parseLinks (line 322) | function parseLinks(text: string, allPublicPages) {
function parseNamespaces (line 351) | async function parseNamespaces(text: string, blockLevel: number) {
function secondsToHms (line 393) | function secondsToHms(d) {
function parseText (line 404) | async function parseText(block: BlockEntity) {
function getBase64Image (line 530) | function getBase64Image(img) {
function addImageToZip (line 540) | function addImageToZip(filePath) {
function download (line 565) | function download(filename, text) {
Condensed preview — 16 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (53K chars).
[
{
"path": ".gitattributes",
"chars": 66,
"preview": "# Auto detect text files and perform LF normalization\n* text=auto\n"
},
{
"path": ".github/workflows/publish.yml",
"chars": 1966,
"preview": "name: Build plugin\n\non:\n push:\n # Sequence of patterns matched against refs/tags\n tags:\n - '*' # Push events"
},
{
"path": ".gitignore",
"chars": 1881,
"preview": ".DS_Store\n# Logs\nlogs\n*.log\nnpm-debug.log*\nyarn-debug.log*\nyarn-error.log*\nlerna-debug.log*\n.pnpm-debug.log*\n\n# Diagnost"
},
{
"path": ".postcssrc",
"chars": 103,
"preview": "{\n \"plugins\": {\n \"postcss-import\": {},\n \"tailwindcss/nesting\": {},\n \"tailwindcss\": {}\n\n }\n}\n"
},
{
"path": "LICENSE",
"chars": 1066,
"preview": "MIT License\n\nCopyright (c) 2022 sawhney17\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\n"
},
{
"path": "README.md",
"chars": 10075,
"preview": "<div id=\"top\"></div>\n<!-- PROJECT SHIELDS -->\n\n[![Contributors][contributors-shield]][contributors-url]\n[![Forks][forks-"
},
{
"path": "index.html",
"chars": 371,
"preview": "<!DOCTYPE html>\n<html lang=\"en\">\n <head>\n <meta charset=\"UTF-8\" />\n <meta http-equiv=\"X-UA-Compatible\" content=\"I"
},
{
"path": "package.json",
"chars": 1058,
"preview": "{\n \"name\": \"logseq-schrodinger\",\n \"version\": \"1.3.2\",\n \"description\": \"An awesome logseq plugin to export to Hugo Sta"
},
{
"path": "src/App.css",
"chars": 2252,
"preview": "html {\n line-height: 1.5; /* 1 */\n -webkit-text-size-adjust: 100%; /* 2 */\n -moz-tab-size: 4; /* 3 */\n -o-tab-size: "
},
{
"path": "src/App.tsx",
"chars": 8120,
"preview": "import React, { useRef, useState } from \"react\";\nimport ReactDOM from \"react-dom\";\nimport \"./App.css\";\nimport \"./tailwin"
},
{
"path": "src/handleClosePopup.ts",
"chars": 260,
"preview": "export const handleClosePopup = () => {\n //ESC\n document.addEventListener(\n 'keydown',\n function (e) {\n if "
},
{
"path": "src/index.tsx",
"chars": 3997,
"preview": "import \"@logseq/libs\";\nimport {\n BlockEntity,\n PageEntity,\n SettingSchemaDesc,\n} from \"@logseq/libs/dist/LSPlugin\";\ni"
},
{
"path": "src/tailwind.css",
"chars": 59,
"preview": "@tailwind base;\n@tailwind components;\n@tailwind utilities;\n"
},
{
"path": "src/utils.tsx",
"chars": 17889,
"preview": "import \"@logseq/libs\";\n\nimport { saveAs } from \"file-saver\";\nimport JSZip, { file } from \"jszip\";\nimport { title } from "
},
{
"path": "tailwind.config.js",
"chars": 259,
"preview": "module.exports = {\n content: ['./src/**/*.{vue,js,ts,jsx,tsx,hbs,html}'],\n darkMode: 'media', // or 'media' or 'class'"
},
{
"path": "tsconfig.json",
"chars": 54,
"preview": "{\n \"ts-node\": {\n \"target\": \"es2021\"\n },\n}"
}
]
About this extraction
This page contains the full source code of the sawhney17/logseq-schrodinger GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 16 files (48.3 KB), approximately 13.5k tokens, and a symbol index with 15 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.
Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.