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 ================================================
[![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]
Logo

logseq Schrödinger

An awesome Logseq plugin to jumpstart your digital garden 🌱!
Explore the docs »

View Demo · Report Bug · Request Feature

Table of Contents
  1. About The Project
  2. Installation
  3. Configuration
  4. Issues
  5. Contributing
  6. License
  7. Contact
  8. Acknowledgments
## 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 issues and questions.

(back to top)

## 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.

(back to top)

## 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)

(back to top)

### 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 ```

Configuring Hugo

[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 }} {{ else }} {{ end }} ``` ### 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
{{ .Inner | $.Page.RenderString }}
```

(back to top)

## 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/).

(back to top)

## 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

(back to top)

## 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

(back to top)

## License Distributed under the MIT License. See `LICENSE.txt` for more information.

(back to top)

## Contact Aryan Sawhney - [@Aryan Sawhney](https://twitter.com/aryansawhney17) Project Link: [https://github.com/sawhney17/logseq-schrodinger](https://github.com/sawhney17/logseq-schrodinger)

(back to top)

## 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 😁! [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 ================================================ logseq-custom-workflows
================================================ 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, index ) => { const { name, value } = event.target; let targettedValues = [...mappedtagsValues]; targettedValues[index].tags = value; setMappedtagsValues(targettedValues); }; const handleCategoryChange = ( event: React.ChangeEvent, index ) => { const { name, value } = event.target; let targettedValues = [...mappedCategoryValues]; targettedValues[index].category = value; setMappedCategoryValues(targettedValues); }; const handleChange = (event: React.ChangeEvent) => { 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 (

Hugo Export



Note Name

Hugo File Name

Original Date

Updated Post Date

Tags

{mappedtagsValues.map((val, index) => { return (
handletagsChange(e, index)} >
); })}

Categories

{mappedCategoryValues.map((val, index) => { return (
handleCategoryChange(e, index)} >
); })}
); }; 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 , 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: ` `, }); logseq.App.registerUIItem("toolbar", { key: "export-public-pages-to-hugo", template: ` `, }); 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 ![image.png](../assets/image_1650196318593_0.png){: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" }, }