feat: page edit + setup instructions

pull/6078/head
Nicolas Giard 2 years ago
parent 7128b160dd
commit 8cbdc88ac6
No known key found for this signature in database
GPG Key ID: 85061B8F9D55B7C8

@ -2,13 +2,8 @@
<img src="https://static.requarks.io/logo/wikijs-full.svg" alt="Wiki.js" width="600" /> <img src="https://static.requarks.io/logo/wikijs-full.svg" alt="Wiki.js" width="600" />
[![Release](https://img.shields.io/github/release/Requarks/wiki.svg?style=flat&maxAge=3600)](https://github.com/Requarks/wiki/releases)
[![License](https://img.shields.io/badge/license-AGPLv3-blue.svg?style=flat)](https://github.com/requarks/wiki/blob/master/LICENSE) [![License](https://img.shields.io/badge/license-AGPLv3-blue.svg?style=flat)](https://github.com/requarks/wiki/blob/master/LICENSE)
[![Standard - JavaScript Style Guide](https://img.shields.io/badge/code%20style-standard-green.svg?style=flat&logo=javascript&logoColor=white)](http://standardjs.com/) [![Standard - JavaScript Style Guide](https://img.shields.io/badge/code%20style-standard-green.svg?style=flat&logo=javascript&logoColor=white)](http://standardjs.com/)
[![Downloads](https://img.shields.io/github/downloads/Requarks/wiki/total.svg?style=flat&logo=github)](https://github.com/Requarks/wiki/releases)
[![Docker Pulls](https://img.shields.io/docker/pulls/requarks/wiki.svg?logo=docker&logoColor=white)](https://hub.docker.com/r/requarks/wiki/)
[![Build + Publish](https://github.com/Requarks/wiki/actions/workflows/build.yml/badge.svg)](https://github.com/Requarks/wiki/actions/workflows/build.yml)
[![Huntr](https://img.shields.io/badge/security%20bounty-disclose-brightgreen.svg?style=flat&logo=cachet&logoColor=white)](https://huntr.dev/bounties/disclose)
[![GitHub Sponsors](https://img.shields.io/github/sponsors/ngpixel?logo=github&color=ea4aaa)](https://github.com/users/NGPixel/sponsorship) [![GitHub Sponsors](https://img.shields.io/github/sponsors/ngpixel?logo=github&color=ea4aaa)](https://github.com/users/NGPixel/sponsorship)
[![Open Collective backers and sponsors](https://img.shields.io/opencollective/all/wikijs?label=backers&color=218bff&logo=opencollective&logoColor=white)](https://opencollective.com/wikijs) [![Open Collective backers and sponsors](https://img.shields.io/opencollective/all/wikijs?label=backers&color=218bff&logo=opencollective&logoColor=white)](https://opencollective.com/wikijs)
[![Chat on Slack](https://img.shields.io/badge/slack-requarks-CC2B5E.svg?style=flat&logo=slack)](https://wiki.requarks.io/slack) [![Chat on Slack](https://img.shields.io/badge/slack-requarks-CC2B5E.svg?style=flat&logo=slack)](https://wiki.requarks.io/slack)
@ -16,412 +11,135 @@
[![Reddit](https://img.shields.io/badge/reddit-%2Fr%2Fwikijs-orange?logo=reddit&logoColor=white)](https://www.reddit.com/r/wikijs/) [![Reddit](https://img.shields.io/badge/reddit-%2Fr%2Fwikijs-orange?logo=reddit&logoColor=white)](https://www.reddit.com/r/wikijs/)
[![Subscribe to Newsletter](https://img.shields.io/badge/newsletter-subscribe-yellow.svg?style=flat&logo=mailchimp)](https://blog.js.wiki/subscribe) [![Subscribe to Newsletter](https://img.shields.io/badge/newsletter-subscribe-yellow.svg?style=flat&logo=mailchimp)](https://blog.js.wiki/subscribe)
##### A modern, lightweight and powerful wiki app built on NodeJS ##### Next Generation Open Source Wiki
</div> </div>
- **[Official Website](https://js.wiki/)** - **[Official Website](https://next.js.wiki/)**
- **[Documentation](https://docs.requarks.io/)** - **[Documentation](https://next.js.wiki/docs/)**
- [Requirements](https://docs.requarks.io/install/requirements)
- [Installation](https://docs.requarks.io/install)
- [Demo](https://docs.requarks.io/demo)
- [Changelog](https://docs.requarks.io/releases)
- [Feature Requests](https://feedback.js.wiki/wiki)
- [Chat with us on Slack](https://wiki.requarks.io/slack)
- [Translations](https://docs.requarks.io/dev/translations) *(We need your help!)*
- [E2E Testing Results](https://dashboard.cypress.io/projects/r7qxah/runs)
- [Special Thanks](#special-thanks)
- [Contribute](#contributors)
[Follow our Twitter feed](https://twitter.com/requarks) to learn about upcoming updates and new releases! :warning: :warning: **THIS IS A VERY BUGGY, INCOMPLETE AND NON-SECURE DEVELOPMENT BRANCH! USE AT YOUR OWN RISK! THERE'S NO UPGRADE PATH FROM THIS BUILD.** :warning: :warning:
<h2 align="center">Donate</h2> The current stable release (2.x) is available at https://js.wiki
<div align="center"> ---
Wiki.js is an open source project that has been made possible due to the generous contributions by community [backers](https://wiki.js.org/about). If you are interested in supporting this project, please consider [becoming a sponsor](https://github.com/users/NGPixel/sponsorship), [becoming a patron](https://www.patreon.com/requarks), donating to our [OpenCollective](https://opencollective.com/wikijs), via [Paypal](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=FLV5X255Z9CJU&source=url) or via Ethereum (`0xe1d55c19ae86f6bcbfb17e7f06ace96bdbb22cb5`). ## Requirements
[![Become a Sponsor](https://img.shields.io/badge/donate-github-ea4aaa.svg?style=popout&logo=github)](https://github.com/users/NGPixel/sponsorship)
[![Become a Patron](https://img.shields.io/badge/donate-patreon-orange.svg?style=popout&logo=patreon)](https://www.patreon.com/requarks)
[![Donate on OpenCollective](https://img.shields.io/badge/donate-open%20collective-blue.svg?style=popout&logo=data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiPz48c3ZnIHdpZHRoPSIyNTZweCIgaGVpZ2h0PSIyNTZweCIgdmlld0JveD0iMCAwIDI1NiAyNTYiIHZlcnNpb249IjEuMSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB4bWxuczp4bGluaz0iaHR0cDovL3d3dy53My5vcmcvMTk5OS94bGluayIgcHJlc2VydmVBc3BlY3RSYXRpbz0ieE1pZFlNaWQiPjxnPjxwYXRoIGQ9Ik0yMDkuNzY1MTQ0LDEyOC4xNDk5NzkgQzIwOS43NjUxNDQsMTQ0LjE2MzMgMjA0Ljg2NDM4MSwxNTkuNDg5ODkgMTk2LjQ5ODc0NywxNzIuNzI1MDcyIEwyMjkuOTQ1Njc1LDIwNi4xNzE5OTkgQzI0Ni42ODIxMDUsMTgzLjg1Njc1OSAyNTUuNzI5MzA3LDE1Ni43MTUxNTIgMjU1LjcyOTMwNywxMjguODIxMTAyIEMyNTUuNzI5MzA3LDk5LjU1Njk5MTcgMjQ1Ljk3NDYwMyw3My4wNzEwMjA3IDIyOS4yNTg5NDQsNTEuNDg1ODEyOCBMMTk2LjQ4MzE0LDg0LjIxNDc5NCBDMjA1LjEyMjU2MSw5Ny4yMjI0NjgzIDIwOS43MzY5MDcsMTEyLjQ4NzgxIDIwOS43NDk1MzcsMTI4LjEwMzE1NiBMMjA5Ljc2NTE0NCwxMjguMTQ5OTc5IFoiIGZpbGw9IiNCOEQzRjQiPjwvcGF0aD48cGF0aCBkPSJNMTI3LjUxMzQ4NCwyMTAuMzU0ODE2IEM4Mi4xNDYwODcyLDIxMC4yNjg5NTggNDUuMzg3NTA5NCwxNzMuNTE3MzU4IDQ1LjI5MzAzOTMsMTI4LjE0OTk3OSBDNDUuMzYxNzUwMiw4Mi43NjQzMTM4IDgyLjEyNzg0ODcsNDUuOTg0MjU3IDEyNy41MTM0ODQsNDUuODk4MzE4NiBDMTQ0LjI0NDc1Miw0NS44OTgzMTg2IDE1OS41NzEzNDIsNTAuNzk5MDgxNyAxNzIuMTE5NzkyLDU5LjE2NDcxNTQgTDIwNC44NjQzODEsMjYuMzg4OTExNiBDMTgyLjU0MzY1LDkuNjY2NjUxMjkgMTU1LjQwMzQyOSwwLjYzMDg2MzI5OCAxMjcuNTEzNDg0LDAuNjM2NDk0NDAzIEM1Ny4xMjM1NDM3LDAuNjM2NDk0NDAzIDAsNTcuNzYwMDM4MSAwLDEyOC4xNDk5NzkgQzAsMTk4LjUwODcwNCA1Ny4xMjM1NDM3LDI1NS42NjM0NjMgMTI3LjUxMzQ4NCwyNTUuNjYzNDYzIEMxNTUuNTM3MzUyLDI1NS43NDA4NzYgMTgyLjc3NTk4OSwyNDYuNDA4NTEgMjA0Ljg2NDM4MSwyMjkuMTYxODg0IEwxNzEuNDE3NDU0LDE5NS43MzA1NjQgQzE1OS41NTU3MzQsMjA1LjQ4NTI2OCAxNDQuMjYwMzU5LDIxMC4zNTQ4MTYgMTI3LjUxMzQ4NCwyMTAuMzU0ODE2IEwxMjcuNTEzNDg0LDIxMC4zNTQ4MTYgWiIgZmlsbD0iIzdGQURGMiI+PC9wYXRoPjwvZz48L3N2Zz4=)](https://opencollective.com/wikijs)
[![Donate via Paypal](https://img.shields.io/badge/donate-paypal-blue.svg?style=popout&logo=paypal)](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=FLV5X255Z9CJU&source=url)
[![Donate via Ethereum](https://img.shields.io/badge/donate-ethereum-999.svg?style=popout&logo=ethereum&logoColor=CCC)](https://etherscan.io/address/0xe1d55c19ae86f6bcbfb17e7f06ace96bdbb22cb5)
[![Donate via Bitcoin](https://img.shields.io/badge/donate-bitcoin-ff9900.svg?style=popout&logo=bitcoin&logoColor=CCC)](https://checkout.opennode.com/p/2553c612-f863-4407-82b3-1a7685268747)
[![Buy a T-Shirt](https://img.shields.io/badge/buy-t--shirts-teal.svg?style=popout&logo=data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHg9IjBweCIgeT0iMHB4Igp3aWR0aD0iMjQiIGhlaWdodD0iMjQiCnZpZXdCb3g9IjAgMCAxOTIgMTkyIgpzdHlsZT0iIGZpbGw6IzAwMDAwMDsiPjxnIGZpbGw9Im5vbmUiIGZpbGwtcnVsZT0ibm9uemVybyIgc3Ryb2tlPSJub25lIiBzdHJva2Utd2lkdGg9IjEiIHN0cm9rZS1saW5lY2FwPSJidXR0IiBzdHJva2UtbGluZWpvaW49Im1pdGVyIiBzdHJva2UtbWl0ZXJsaW1pdD0iMTAiIHN0cm9rZS1kYXNoYXJyYXk9IiIgc3Ryb2tlLWRhc2hvZmZzZXQ9IjAiIGZvbnQtZmFtaWx5PSJub25lIiBmb250LXdlaWdodD0ibm9uZSIgZm9udC1zaXplPSJub25lIiB0ZXh0LWFuY2hvcj0ibm9uZSIgc3R5bGU9Im1peC1ibGVuZC1tb2RlOiBub3JtYWwiPjxwYXRoIGQ9Ik0wLDE5MnYtMTkyaDE5MnYxOTJ6IiBmaWxsPSJub25lIj48L3BhdGg+PGcgZmlsbD0iIzFhYmM5YyI+PGcgaWQ9InN1cmZhY2UxIj48cGF0aCBkPSJNOTYsMGMtMTUuMjE4NzUsMCAtMjQuNjg3NSwzLjY1NjI1IC0yNS41LDRsLTIyLjUsNy4yNWMtMTAuNDA2MjUsMy4xODc1IC0xOS4wOTM3NSw5LjQzNzUgLTI1LjUsMTguMjVsLTIyLjUsNDIuNWwyNy4yNSwxNi43NWwxMi43NSwtMjR2MTE5LjI1YzAsNC40MDYyNSAyNS4wNjI1LDggNTYsOGMzMC45Mzc1LDAgNTYsLTMuNTkzNzUgNTYsLTh2LTExOS4yNWwxMi43NSwyNGwyNy4yNSwtMTYuNzVsLTIyLjUsLTQyLjVjLTYuNDA2MjUsLTguODEyNSAtMTUuMTU2MjUsLTE1LjA2MjUgLTI0Ljc1LC0xOC4yNWwtMjIuMjUsLTcuMjVjLTAuMTg3NSwwIC0xLjAzMTI1LDEuMzEyNSAtMiwyLjc1bDEuMjUsLTIuNWMwLDAgLTkuODQzNzUsLTQuMjUgLTI1Ljc1LC00LjI1ek05Niw4YzExLjQwNjI1LDAgMTguNDM3NSwyLjI1IDIxLDMuMjVjLTQuNDY4NzUsNS43NSAtMTEuNDA2MjUsMTIuNzUgLTIxLDEyLjc1Yy05LjQwNjI1LDAgLTE2LjQwNjI1LC03LjA2MjUgLTIwLjc1LC0xMi43NWMyLjg3NSwtMS4wNjI1IDkuODc1LC0zLjI1IDIwLjc1LC0zLjI1eiI+PC9wYXRoPjwvZz48L2c+PC9nPjwvc3ZnPg==)](https://wikijs.threadless.com)
</div> - Node.js **18.x** or later
- Yarn
- PostgreSQL **11** or later
<h2 align="center">Gold Tier Sponsors</h2> ## Setup
<div align="center"> 1. Clone the project
<table> 1. Make a copy of `config.sample.yml` and rename it to `config.yml`
<tbody> 1. Edit `config.yml` and fill in the database details. **You need an empty PostgreSQL database.**
<tr> 1. Run the following commands to install dependencies and generate the client assets:
<td align="center" valign="middle" width="444"> ```sh
<a href="https://trans-zero.com/" target="_blank"> yarn
<img src="https://cdn.js.wiki/images/sponsors/transzero.png"> yarn legacy:build
</a> cd ux
</td> yarn
</tr> yarn build
</tbody> cd ..
</table> ```
</div> 1. Run this command to start the server:
```sh
node server
```
1. In your browser, navigate to `http://localhost:3000` *(or the IP/hostname of the server and the PORT you defined earlier.)*
1. Login using the default administrator user:
- Email: `admin@example.com`
- Password: `12345678`
<h2 align="center">GitHub Sponsors</h2> > **DO NOT** report bugs. This build is **VERY** buggy and **VERY** incomplete. Absolutely **NO** support is provided either.
Support this project by becoming a sponsor. Your name will show up in the Contribute page of all Wiki.js installations as well as here with a link to your website! [[Become a sponsor](https://github.com/users/NGPixel/sponsorship)] ## Using VS Code Dev Environment
<div align="center"> ### Requirements
<table>
<tbody>
<tr>
<td align="center" valign="middle" width="444">
<a href="https://www.stellarhosted.com/" target="_blank">
<img src="https://cdn.js.wiki/images/sponsors/stellarhosted.png">
</a>
</td>
<td align="center" valign="middle" width="444">
<a href="https://www.hostwiki.com/" target="_blank">
<img src="https://cdn.js.wiki/images/sponsors/hostwiki.png">
</a>
</td>
</tr>
</tbody>
</table>
</div>
<div align="center"> - VS Code
<table> - Docker Desktop
<tbody> - **Windows-only:** WSL 2 + WSL Integration enabled in Docker Desktop
<tr>
<td align="center" valign="middle" width="148">
<a href="https://github.com/alexksso" target="_blank">
Alexander Casassovici<br />(@alexksso)
</a>
</td>
<td align="center" valign="middle" width="148">
<a href="https://github.com/broxen" target="_blank">
Broxen<br />(@broxen)
</a>
</td>
<td align="center" valign="middle" width="148">
<a href="https://github.com/xDacon" target="_blank">
Dacon<br />(@xDacon)
</a>
</td>
<td align="center" valign="middle" width="148">
<a href="https://github.com/GigabiteLabs" target="_blank">
<img src="https://static.requarks.io/sponsors/gigabitelabs-148x129.png">
</a>
</td>
<td align="center" valign="middle" width="148">
<a href="https://github.com/JayDaley" target="_blank">
Jay Daley<br />(@JayDaley)
</a>
</td>
<td align="center" valign="middle" width="148">
<a href="https://github.com/idokka" target="_blank">
Oleksii<br />(@idokka)
</a>
</td>
<!--<td align="center" valign="middle" width="148">
<a href="https://github.com/sponsors/NGPixel" target="_blank">
<img src="https://static.requarks.io/sponsors/become-148x72.png">
</a>
</td>-->
</tr>
</tbody>
</table>
<table><tbody><tr><td>
<img width="441" height="1" />
- Akira Suenami ([@a-suenami](https://github.com/a-suenami))
- Arnaud Marchand ([@snuids](https://github.com/snuids))
- Brian Douglass ([@bhdouglass](https://github.com/bhdouglass))
- Bryon Vandiver ([@asterick](https://github.com/asterick))
- Cameron Steele ([@ATechAdventurer](https://github.com/ATechAdventurer))
- Cloud Data Hosting LLC ([@CloudDataHostingLLC](https://github.com/CloudDataHostingLLC))
- CrazyMarvin ([@CrazyMarvin](https://github.com/CrazyMarvin))
- David Christian Holin ([@SirGibihm](https://github.com/SirGibihm))
- Dragan Espenschied ([@despens](https://github.com/despens))
- Elijah Zobenko ([@he110](https://github.com/he110))
- Ernie ([@iamernie](https://github.com/iamernie))
- Fabio Ferrari ([@devxops](https://github.com/devxops))
- Finsa S.p.A. ([@finsaspa](https://github.com/finsaspa))
- Florian Moss ([@florianmoss](https://github.com/florianmoss))
- GoodCorporateCitizen ([@GoodCorporateCitizen](https://github.com/GoodCorporateCitizen))
- HeavenBay ([@HeavenBay](https://github.com/heavenbay))
- Ian Hyzy ([@ianhyzy](https://github.com/ianhyzy))
- Jaimyn Mayer ([@jabelone](https://github.com/jabelone))
- Jay Lee ([@polyglotm](https://github.com/polyglotm))
- Kelly Wardrop ([@dropcoded](https://github.com/dropcoded))
- Loki ([@binaryloki](https://github.com/binaryloki))
- MaFarine ([@MaFarine](https://github.com/MaFarine))
- Marcilio Leite Neto ([@marclneto](https://github.com/marclneto))
</td><td>
<img width="441" height="1" />
- Mattias Johnson ([@mattiasJohnson](https://github.com/mattiasJohnson))
- Max Ricketts-Uy ([@MaxRickettsUy](https://github.com/MaxRickettsUy))
- Mitchell Rowton ([@mrowton](https://github.com/mrowton))
- M. Scott Ford ([@mscottford](https://github.com/mscottford))
- Nick Halase ([@nhalase](https://github.com/nhalase))
- Nina Reynolds ([@cutecycle](https://github.com/cutecycle))
- Noel Cower ([@nilium](https://github.com/nilium))
- Philipp Schmitt ([@pschmitt](https://github.com/pschmitt))
- Robert Lanzke ([@winkelement](https://github.com/winkelement))
- Sam Martin ([@ABitMoreDepth](https://github.com/ABitMoreDepth))
- Sean Coffey ([@seanecoffey](https://github.com/seanecoffey))
- Stephan Kristyn ([@stevek-pro](https://github.com/stevek-pro))
- Theodore Chu ([@TheodoreChu](https://github.com/TheodoreChu))
- Tyler Denman ([@tylerguy](https://github.com/tylerguy))
- Victor Bilgin ([@vbilgin](https://github.com/vbilgin))
- VMO Solutions ([@vmosolutions](https://github.com/vmosolutions))
- aniketpanjwani ([@aniketpanjwani](https://github.com/aniketpanjwani))
- aytaa ([@aytaa](https://github.com/aytaa))
- magicpotato ([@fortheday](https://github.com/fortheday))
- motoacs ([@motoacs](https://github.com/motoacs))
- rburckner ([@rburckner](https://github.com/rburckner))
- scorpion ([@scorpion](https://github.com/scorpion))
- valantien ([@valantien](https://github.com/valantien))
</td></tr></tbody></table>
</div>
<h2 align="center">OpenCollective Sponsors</h2> ### Usage
Support this project by becoming a sponsor. Your logo will show up in the Contribute page of all Wiki.js installations as well as here with a link to your website! [[Become a sponsor](https://opencollective.com/wikijs#sponsor)] 1. Clone the project
1. Open the project in VS Code
1. Make sure you have **Dev Containers** extension installed. (On Windows, you need the **WSL** VS Code extension as well.)
1. Reopen the project in container (from the popup in the lower-right corner of the screen when opening the project, or via the Command Palette (Ctrl+Shift+P) afterwards).
1. Once in container mode, run the task "Create terminals" from the Command Palette:
- Launch the Command Palette (Ctrl+Shift+P)
- Type "Run Task" and press Enter
- Select the task "Create terminals" and press Enter
1. Two terminals will launch in split-screen mode at the bottom of the screen. **Server** on the left and **UX** on the right.
1. In the left-side terminal (Server), run the command:
```sh
yarn legacy:build
```
1. In the right-side terminal (UX), run the command:
```sh
yarn build
```
1. Back in the left-side terminal (Server), run the command:
```sh
yarn dev
```
1. Open your browser to `http://localhost:3000`
1. Login using the default administrator user:
- Email: `admin@example.com`
- Password: `12345678`
<div align="center"> > **DO NOT** report bugs. This build is **VERY** buggy and **VERY** incomplete. Absolutely **NO** support is provided either.
<table>
<tbody>
<tr>
<td align="center" valign="middle">
<a href="https://opencollective.com/wikijs/sponsor/0/website" target="_blank"><img src="https://opencollective.com/wikijs/sponsor/0/avatar.svg"></a>
</td>
<td align="center" valign="middle">
<a href="https://opencollective.com/wikijs/sponsor/1/website" target="_blank"><img src="https://opencollective.com/wikijs/sponsor/1/avatar.svg"></a>
</td>
<td align="center" valign="middle">
<a href="https://opencollective.com/wikijs/sponsor/2/website" target="_blank"><img src="https://opencollective.com/wikijs/sponsor/2/avatar.svg"></a>
</td>
<td align="center" valign="middle">
<a href="https://opencollective.com/wikijs/sponsor/3/website" target="_blank"><img src="https://opencollective.com/wikijs/sponsor/3/avatar.svg"></a>
</td>
<td align="center" valign="middle">
<a href="https://opencollective.com/wikijs/sponsor/4/website" target="_blank"><img src="https://opencollective.com/wikijs/sponsor/4/avatar.svg"></a>
</td>
</tr>
<tr>
<td align="center" valign="middle">
<a href="https://opencollective.com/wikijs/sponsor/5/website" target="_blank"><img src="https://opencollective.com/wikijs/sponsor/5/avatar.svg"></a>
</td>
<td align="center" valign="middle">
<a href="https://opencollective.com/wikijs/sponsor/6/website" target="_blank"><img src="https://opencollective.com/wikijs/sponsor/6/avatar.svg"></a>
</td>
<td align="center" valign="middle">
<a href="https://opencollective.com/wikijs/sponsor/7/website" target="_blank"><img src="https://opencollective.com/wikijs/sponsor/7/avatar.svg"></a>
</td>
<td align="center" valign="middle">
<a href="https://opencollective.com/wikijs/sponsor/8/website" target="_blank"><img src="https://opencollective.com/wikijs/sponsor/8/avatar.svg"></a>
</td>
<td align="center" valign="middle">
<a href="https://opencollective.com/wikijs/sponsor/9/website" target="_blank"><img src="https://opencollective.com/wikijs/sponsor/9/avatar.svg"></a>
</td>
</tr>
<tr>
<td align="center" valign="middle">
<a href="https://opencollective.com/wikijs/sponsor/10/website" target="_blank"><img src="https://opencollective.com/wikijs/sponsor/10/avatar.svg"></a>
</td>
<td align="center" valign="middle">
<a href="https://opencollective.com/wikijs/sponsor/11/website" target="_blank"><img src="https://opencollective.com/wikijs/sponsor/11/avatar.svg"></a>
</td>
<td align="center" valign="middle">
<a href="https://opencollective.com/wikijs/sponsor/12/website" target="_blank"><img src="https://opencollective.com/wikijs/sponsor/12/avatar.svg"></a>
</td>
<td align="center" valign="middle">
<a href="https://opencollective.com/wikijs/sponsor/13/website" target="_blank"><img src="https://opencollective.com/wikijs/sponsor/13/avatar.svg"></a>
</td>
<td align="center" valign="middle">
<a href="https://opencollective.com/wikijs/sponsor/14/website" target="_blank"><img src="https://opencollective.com/wikijs/sponsor/14/avatar.svg"></a>
</td>
</tr>
<tr>
<td align="center" valign="middle">
<a href="https://opencollective.com/wikijs/sponsor/15/website" target="_blank"><img src="https://opencollective.com/wikijs/sponsor/15/avatar.svg"></a>
</td>
<td align="center" valign="middle">
<a href="https://opencollective.com/wikijs/sponsor/16/website" target="_blank"><img src="https://opencollective.com/wikijs/sponsor/16/avatar.svg"></a>
</td>
<td align="center" valign="middle">
<a href="https://opencollective.com/wikijs/sponsor/17/website" target="_blank"><img src="https://opencollective.com/wikijs/sponsor/17/avatar.svg"></a>
</td>
<td align="center" valign="middle">
<a href="https://opencollective.com/wikijs/sponsor/18/website" target="_blank"><img src="https://opencollective.com/wikijs/sponsor/18/avatar.svg"></a>
</td>
<td align="center" valign="middle">
<a href="https://opencollective.com/wikijs/sponsor/19/website" target="_blank"><img src="https://opencollective.com/wikijs/sponsor/19/avatar.svg"></a>
</td>
</tr>
<tr>
<td align="center" valign="middle">
<a href="https://opencollective.com/wikijs/sponsor/20/website" target="_blank"><img src="https://opencollective.com/wikijs/sponsor/20/avatar.svg"></a>
</td>
<td align="center" valign="middle">
<a href="https://opencollective.com/wikijs/sponsor/21/website" target="_blank"><img src="https://opencollective.com/wikijs/sponsor/21/avatar.svg"></a>
</td>
<td align="center" valign="middle">
<a href="https://opencollective.com/wikijs/sponsor/22/website" target="_blank"><img src="https://opencollective.com/wikijs/sponsor/22/avatar.svg"></a>
</td>
<td align="center" valign="middle">
<a href="https://opencollective.com/wikijs/sponsor/23/website" target="_blank"><img src="https://opencollective.com/wikijs/sponsor/23/avatar.svg"></a>
</td>
<td align="center" valign="middle">
<a href="https://opencollective.com/wikijs/sponsor/24/website" target="_blank"><img src="https://opencollective.com/wikijs/sponsor/24/avatar.svg"></a>
</td>
</tr>
<tr>
<td align="center" valign="middle">
<a href="https://opencollective.com/wikijs/sponsor/25/website" target="_blank"><img src="https://opencollective.com/wikijs/sponsor/25/avatar.svg"></a>
</td>
<td align="center" valign="middle">
<a href="https://opencollective.com/wikijs/sponsor/26/website" target="_blank"><img src="https://opencollective.com/wikijs/sponsor/26/avatar.svg"></a>
</td>
<td align="center" valign="middle">
<a href="https://opencollective.com/wikijs/sponsor/27/website" target="_blank"><img src="https://opencollective.com/wikijs/sponsor/27/avatar.svg"></a>
</td>
<td align="center" valign="middle">
<a href="https://opencollective.com/wikijs/sponsor/28/website" target="_blank"><img src="https://opencollective.com/wikijs/sponsor/28/avatar.svg"></a>
</td>
<td align="center" valign="middle">
<a href="https://opencollective.com/wikijs/sponsor/29/website" target="_blank"><img src="https://opencollective.com/wikijs/sponsor/29/avatar.svg"></a>
</td>
</tr>
<tr>
<td align="center" valign="middle">
<a href="https://opencollective.com/wikijs/sponsor/30/website" target="_blank"><img src="https://opencollective.com/wikijs/sponsor/30/avatar.svg"></a>
</td>
<td align="center" valign="middle">
<a href="https://opencollective.com/wikijs/sponsor/31/website" target="_blank"><img src="https://opencollective.com/wikijs/sponsor/31/avatar.svg"></a>
</td>
<td align="center" valign="middle">
<a href="https://opencollective.com/wikijs/sponsor/32/website" target="_blank"><img src="https://opencollective.com/wikijs/sponsor/32/avatar.svg"></a>
</td>
<td align="center" valign="middle">
<a href="https://opencollective.com/wikijs/sponsor/33/website" target="_blank"><img src="https://opencollective.com/wikijs/sponsor/33/avatar.svg"></a>
</td>
<td align="center" valign="middle">
<a href="https://opencollective.com/wikijs/sponsor/34/website" target="_blank"><img src="https://opencollective.com/wikijs/sponsor/34/avatar.svg"></a>
</td>
</tr>
<tr>
<td align="center" valign="middle">
<a href="https://opencollective.com/wikijs/sponsor/35/website" target="_blank"><img src="https://opencollective.com/wikijs/sponsor/35/avatar.svg"></a>
</td>
<td align="center" valign="middle">
<a href="https://opencollective.com/wikijs/sponsor/36/website" target="_blank"><img src="https://opencollective.com/wikijs/sponsor/36/avatar.svg"></a>
</td>
<td align="center" valign="middle">
<a href="https://opencollective.com/wikijs/sponsor/37/website" target="_blank"><img src="https://opencollective.com/wikijs/sponsor/37/avatar.svg"></a>
</td>
<td align="center" valign="middle">
<a href="https://opencollective.com/wikijs/sponsor/38/website" target="_blank"><img src="https://opencollective.com/wikijs/sponsor/38/avatar.svg"></a>
</td>
<td align="center" valign="middle">
<a href="https://opencollective.com/wikijs/sponsor/39/website" target="_blank"><img src="https://opencollective.com/wikijs/sponsor/39/avatar.svg"></a>
</td>
</tr>
</tbody>
</table>
</div>
<h2 align="center">Patreon Backers</h2> ### Server Development
Thank you to all our patrons! 🙏 [[Become a patron](https://www.patreon.com/requarks)] From the left-side terminal (Server), run the command:
<div align="center"> ```sh
<table><tbody><tr><td> yarn dev
<img width="441" height="1" /> ```
- Al Romano This will launch the server and automatically restart upon modification of any server files.
- Alex Balabanov
- Alex Zen Only precompiled client assets are served in this mode. See the sections below on how to modify the frontend and run in SPA (Single Page Application) mode.
- Arti Zirk
- Brandon Curtis
- Dave 'Sri' Seah
- djagoo
- Douglas Lassance
- Ernie Reid
- Etienne
- Flemis Jurgenheimer
- Florent
- Günter Pavlas
- hong
- Hope
- Ian
</td><td>
<img width="441" height="1" />
- Iskander Callos
- Josh Stewart
- Justin Dunsworth
- Keir
- Loïc CRAMPON
- Ludgeir Ibanez
- Mark Mansur
- Matt Gedigian
- Patryk
- Philipp Schürch
- Tracey Duffy
- Richeir
- Shad Narcher
- SmartNET.works
- Stepan Sokolovskyi
- Zach Maynard
- 张白驹
</td></tr></tbody></table>
</div>
<h2 align="center">OpenCollective Backers</h2> ### Frontend Development (Quasar/Vue 3)
Thank you to all our backers! 🙏 [[Become a backer](https://opencollective.com/wikijs#backer)] > Make sure you are running `yarn dev` in the left-side terminal (Server) first! Requests still need to be forwarded to the server, even in SPA mode!
<a href="https://opencollective.com/wikijs#backers" target="_blank"><img src="https://opencollective.com/wikijs/backers.svg?width=890"></a> If you wish to modify any frontend content (under `/ux`), you need to start the Quasar Dev Server in the right-side terminal (UX):
<h2 align="center">Contributors</h2> ```sh
yarn dev
```
This project exists thanks to all the people who contribute. [[Contribute]](https://github.com/Requarks/wiki/blob/master/.github/CONTRIBUTING.md). You can then access the site at `http://localhost:3001`. Notice the port being `3001` rather than `3000`. The app runs in a SPA (single-page application) mode and automatically hot-reload any modified component. Any requests made to the `/graphql` endpoint are automatically forwarded to the server running on port `3000`, which is why both must be running at the same time.
<a href="https://github.com/Requarks/wiki/graphs/contributors"><img src="https://opencollective.com/wikijs/contributors.svg?width=890" /></a>
<h2 align="center">Special Thanks</h2> Note that not all sections/features are available from this mode, notably the page editing features which still relies on the old client code (Vuetify/Vue 2). For example, trying to edit a page will simply not work. You must use the normal mode (port 3000) to edit pages as it relies on legacy client code. As more features gets ported / developed for Vue 3, they will become available in the SPA mode.
![Algolia](https://js.wiki/legacy/logo_algolia.png) Any change you make to the frontend will not be reflected on port 3000 until you run the command `yarn build` in the right-side terminal.
[Algolia](https://www.algolia.com/) for providing access to their incredible search engine.
![Browserstack](https://js.wiki/legacy/logo_browserstack.png) ### Legacy Frontend Development (Vuetify/Vue 2)
[Browserstack](https://www.browserstack.com/) for providing access to their great cross-browser testing tools.
![Cloudflare](https://js.wiki/legacy/logo_cloudflare.png) Client code from Wiki.js 2.x is located under `/client`. Some sections still rely on this legacy code (notably the page editing features). Code is gradually being removed from this location and replaced with newer code in `/ux`.
[Cloudflare](https://www.cloudflare.com/) for providing their great CDN, SSL and advanced networking services.
![DigitalOcean](https://js.wiki/legacy/logo_digitalocean.png) In the unlikely event that you need to modify legacy code and regenerate the old client files, you can do so by running in this command in the left-side terminal (Server):
[DigitalOcean](https://m.do.co/c/5f7445bfa4d0) for providing hosting of the Wiki.js documentation site. ```sh
yarn legacy:build
```
![Icons8](https://static.requarks.io/logo/icons8-text-h40.png) Then run `yarn dev` to start the server again.
[Icons8](https://icons8.com/) for providing beautiful icon sets.
![Lokalise](https://static.requarks.io/logo/lokalise-text-h40.png) ### pgAdmin
[Lokalise](https://lokalise.com/) for providing access to their great localization tool.
![Netlify](https://js.wiki/legacy/logo_netlify.png) A web version of pgAdmin (a PostgreSQL administration tool) is available at `http://localhost:8000`. Use the login `dev` / `123123` to login.
[Netlify](https://www.netlify.com) for providing hosting for landings and blog websites.
![ngrok](https://static.requarks.io/logo/ngrok-h40.png) The server **dev** should already be available under **Servers**. If that's not the case, add a new one with the following settings:
[ngrok](https://ngrok.com) for providing access to their great HTTP tunneling services.
![Porkbun](https://static.requarks.io/logo/porkbun.png) - Hostname: `db`
[Porkbun](https://www.porkbun.com) for providing domain registration services. - Port: `5432`
- Username: `postgres`
- Password: `postgres`
- Database: `postgres`

@ -141,8 +141,8 @@ export default {
default: null default: null
}, },
pageId: { pageId: {
type: Number, type: String,
default: 0 default: ''
}, },
checkoutDate: { checkoutDate: {
type: String, type: String,
@ -369,33 +369,32 @@ export default {
// -> UPDATE EXISTING PAGE // -> UPDATE EXISTING PAGE
// -------------------------------------------- // --------------------------------------------
const conflictResp = await this.$apollo.query({ // const conflictResp = await this.$apollo.query({
query: gql` // query: gql`
query ($id: Int!, $checkoutDate: Date!) { // query ($id: Int!, $checkoutDate: Date!) {
pages { // pages {
checkConflicts(id: $id, checkoutDate: $checkoutDate) // checkConflicts(id: $id, checkoutDate: $checkoutDate)
} // }
} // }
`, // `,
fetchPolicy: 'network-only', // fetchPolicy: 'network-only',
variables: { // variables: {
id: this.pageId, // id: this.pageId,
checkoutDate: this.checkoutDateActive // checkoutDate: this.checkoutDateActive
} // }
}) // })
if (_.get(conflictResp, 'data.pages.checkConflicts', false)) { // if (_.get(conflictResp, 'data.pages.checkConflicts', false)) {
this.$root.$emit('saveConflict') // this.$root.$emit('saveConflict')
throw new Error(this.$t('editor:conflict.warning')) // throw new Error(this.$t('editor:conflict.warning'))
} // }
let resp = await this.$apollo.mutate({ let resp = await this.$apollo.mutate({
mutation: gql` mutation: gql`
mutation ( mutation (
$id: Int! $id: UUID!
$content: String $content: String
$description: String $description: String
$editor: String $editor: String
$isPrivate: Boolean
$isPublished: Boolean $isPublished: Boolean
$locale: String $locale: String
$path: String $path: String
@ -406,32 +405,27 @@ export default {
$tags: [String] $tags: [String]
$title: String $title: String
) { ) {
pages { updatePage(
update( id: $id
id: $id content: $content
content: $content description: $description
description: $description editor: $editor
editor: $editor isPublished: $isPublished
isPrivate: $isPrivate locale: $locale
isPublished: $isPublished path: $path
locale: $locale publishEndDate: $publishEndDate
path: $path publishStartDate: $publishStartDate
publishEndDate: $publishEndDate scriptCss: $scriptCss
publishStartDate: $publishStartDate scriptJs: $scriptJs
scriptCss: $scriptCss tags: $tags
scriptJs: $scriptJs title: $title
tags: $tags ) {
title: $title operation {
) { succeeded
operation { message
succeeded }
errorCode page {
slug updatedAt
message
}
page {
updatedAt
}
} }
} }
} }
@ -442,7 +436,6 @@ export default {
description: this.$store.get('page/description'), description: this.$store.get('page/description'),
editor: this.$store.get('editor/editorKey'), editor: this.$store.get('editor/editorKey'),
locale: this.$store.get('page/locale'), locale: this.$store.get('page/locale'),
isPrivate: false,
isPublished: this.$store.get('page/isPublished'), isPublished: this.$store.get('page/isPublished'),
path: this.$store.get('page/path'), path: this.$store.get('page/path'),
publishEndDate: this.$store.get('page/publishEndDate') || '', publishEndDate: this.$store.get('page/publishEndDate') || '',
@ -453,7 +446,7 @@ export default {
title: this.$store.get('page/title') title: this.$store.get('page/title')
} }
}) })
resp = _.get(resp, 'data.pages.update', {}) resp = _.get(resp, 'data.updatePage', {})
if (_.get(resp, 'operation.succeeded')) { if (_.get(resp, 'operation.succeeded')) {
this.checkoutDateActive = _.get(resp, 'page.updatedAt', this.checkoutDateActive) this.checkoutDateActive = _.get(resp, 'page.updatedAt', this.checkoutDateActive)
this.isConflict = false this.isConflict = false
@ -547,30 +540,30 @@ export default {
styl.appendChild(document.createTextNode(css)) styl.appendChild(document.createTextNode(css))
} }
}, 1000) }, 1000)
},
apollo: {
isConflict: {
query: gql`
query ($id: Int!, $checkoutDate: Date!) {
pages {
checkConflicts(id: $id, checkoutDate: $checkoutDate)
}
}
`,
fetchPolicy: 'network-only',
pollInterval: 5000,
variables () {
return {
id: this.pageId,
checkoutDate: this.checkoutDateActive
}
},
update: (data) => _.cloneDeep(data.pages.checkConflicts),
skip () {
return this.mode === 'create' || this.isSaving || !this.isDirty
}
}
} }
// apollo: {
// isConflict: {
// query: gql`
// query ($id: Int!, $checkoutDate: Date!) {
// pages {
// checkConflicts(id: $id, checkoutDate: $checkoutDate)
// }
// }
// `,
// fetchPolicy: 'network-only',
// pollInterval: 5000,
// variables () {
// return {
// id: this.pageId,
// checkoutDate: this.checkoutDateActive
// }
// },
// update: (data) => _.cloneDeep(data.pages.checkConflicts),
// skip () {
// return this.mode === 'create' || this.isSaving || !this.isDirty
// }
// }
// }
} }
</script> </script>

@ -238,51 +238,6 @@ router.get(['/_edit', '/_edit/*'], async (req, res, next) => {
js: '' js: ''
} }
} }
// -> From Template
if (req.query.from && tmplCreateRegex.test(req.query.from)) {
let tmplPageId = 0
let tmplVersionId = 0
if (req.query.from.indexOf(',')) {
const q = req.query.from.split(',')
tmplPageId = _.toSafeInteger(q[0])
tmplVersionId = _.toSafeInteger(q[1])
} else {
tmplPageId = _.toSafeInteger(req.query.from)
}
if (tmplVersionId > 0) {
// -> From Page Version
const pageVersion = await WIKI.db.pageHistory.getVersion({ pageId: tmplPageId, versionId: tmplVersionId })
if (!pageVersion) {
_.set(res.locals, 'pageMeta.title', 'Page Not Found')
return res.status(404).render('notfound', { action: 'template' })
}
if (!WIKI.auth.checkAccess(req.user, ['read:history'], { path: pageVersion.path, locale: pageVersion.locale })) {
_.set(res.locals, 'pageMeta.title', 'Unauthorized')
return res.render('unauthorized', { action: 'sourceVersion' })
}
page.content = Buffer.from(pageVersion.content).toString('base64')
page.editorKey = pageVersion.editor
page.title = pageVersion.title
page.description = pageVersion.description
} else {
// -> From Page Live
const pageOriginal = await WIKI.db.pages.query().findById(tmplPageId)
if (!pageOriginal) {
_.set(res.locals, 'pageMeta.title', 'Page Not Found')
return res.status(404).render('notfound', { action: 'template' })
}
if (!WIKI.auth.checkAccess(req.user, ['read:source'], { path: pageOriginal.path, locale: pageOriginal.locale })) {
_.set(res.locals, 'pageMeta.title', 'Unauthorized')
return res.render('unauthorized', { action: 'source' })
}
page.content = Buffer.from(pageOriginal.content).toString('base64')
page.editorKey = pageOriginal.editorKey
page.title = pageOriginal.title
page.description = pageOriginal.description
}
}
} }
res.render('editor', { page, injectCode, effectivePermissions }) res.render('editor', { page, injectCode, effectivePermissions })

@ -9,7 +9,7 @@ const fs = require('fs')
*/ */
const packages = { const packages = {
'twemoji': path.join(WIKI.ROOTPATH, `assets/svg/twemoji.asar`) 'twemoji': path.join(WIKI.ROOTPATH, `assets-legacy/svg/twemoji.asar`)
} }
module.exports = { module.exports = {

@ -603,7 +603,8 @@ exports.up = async knex => {
auth: { auth: {
[authModuleId]: { [authModuleId]: {
password: await bcrypt.hash(process.env.ADMIN_PASS || '12345678', 12), password: await bcrypt.hash(process.env.ADMIN_PASS || '12345678', 12),
mustChangePwd: !process.env.ADMIN_PASS, mustChangePwd: false, // TODO: Revert to true (below) once change password flow is implemented
// mustChangePwd: !process.env.ADMIN_PASS,
restrictLogin: false, restrictLogin: false,
tfaRequired: false, tfaRequired: false,
tfaSecret: '' tfaSecret: ''

@ -1,5 +1,6 @@
const _ = require('lodash') const _ = require('lodash')
const graphHelper = require('../../helpers/graph') const graphHelper = require('../../helpers/graph')
const pageHelper = require('../../helpers/page')
module.exports = { module.exports = {
Query: { Query: {
@ -139,7 +140,7 @@ module.exports = {
return results return results
}, },
/** /**
* FETCH SINGLE PAGE * FETCH SINGLE PAGE BY ID
*/ */
async pageById (obj, args, context, info) { async pageById (obj, args, context, info) {
let page = await WIKI.db.pages.getPageFromDb(args.id) let page = await WIKI.db.pages.getPageFromDb(args.id)
@ -160,6 +161,22 @@ module.exports = {
throw new WIKI.Error.PageNotFound() throw new WIKI.Error.PageNotFound()
} }
}, },
/**
* FETCH SINGLE PAGE BY PATH
*/
async pageByPath (obj, args, context, info) {
const pageArgs = pageHelper.parsePath(args.path)
let page = await WIKI.db.pages.getPageFromDb(pageArgs)
if (page) {
return {
...page,
locale: page.localeCode,
editor: page.editorKey
}
} else {
throw new Error('ERR_PAGE_NOT_FOUND')
}
},
/** /**
* FETCH TAGS * FETCH TAGS
*/ */
@ -366,7 +383,7 @@ module.exports = {
user: context.req.user user: context.req.user
}) })
return { return {
responseResult: graphHelper.generateSuccess('Page created successfully.'), operation: graphHelper.generateSuccess('Page created successfully.'),
page page
} }
} catch (err) { } catch (err) {
@ -383,7 +400,7 @@ module.exports = {
user: context.req.user user: context.req.user
}) })
return { return {
responseResult: graphHelper.generateSuccess('Page has been updated.'), operation: graphHelper.generateSuccess('Page has been updated.'),
page page
} }
} catch (err) { } catch (err) {

@ -34,6 +34,10 @@ extend type Query {
id: Int! id: Int!
): Page ): Page
pageByPath(
path: String!
): Page
tags: [PageTag]! tags: [PageTag]!
searchTags( searchTags(
@ -80,7 +84,7 @@ extend type Mutation {
): PageResponse ): PageResponse
updatePage( updatePage(
id: Int! id: UUID!
content: String content: String
description: String description: String
editor: String editor: String
@ -158,7 +162,7 @@ type PageMigrationResponse {
} }
type Page { type Page {
id: Int id: UUID
path: String path: String
hash: String hash: String
title: String title: String

@ -19,7 +19,7 @@ module.exports = class PageHistory extends Model {
hash: {type: 'string'}, hash: {type: 'string'},
title: {type: 'string'}, title: {type: 'string'},
description: {type: 'string'}, description: {type: 'string'},
isPublished: {type: 'boolean'}, publishState: {type: 'string'},
publishStartDate: {type: 'string'}, publishStartDate: {type: 'string'},
publishEndDate: {type: 'string'}, publishEndDate: {type: 'string'},
content: {type: 'string'}, content: {type: 'string'},
@ -60,14 +60,6 @@ module.exports = class PageHistory extends Model {
to: 'users.id' to: 'users.id'
} }
}, },
editor: {
relation: Model.BelongsToOneRelation,
modelClass: require('./editors'),
join: {
from: 'pageHistory.editorKey',
to: 'editors.key'
}
},
locale: { locale: {
relation: Model.BelongsToOneRelation, relation: Model.BelongsToOneRelation,
modelClass: require('./locales'), modelClass: require('./locales'),
@ -89,18 +81,18 @@ module.exports = class PageHistory extends Model {
static async addVersion(opts) { static async addVersion(opts) {
await WIKI.db.pageHistory.query().insert({ await WIKI.db.pageHistory.query().insert({
pageId: opts.id, pageId: opts.id,
siteId: opts.siteId,
authorId: opts.authorId, authorId: opts.authorId,
content: opts.content, content: opts.content,
contentType: opts.contentType, contentType: opts.contentType,
description: opts.description, description: opts.description,
editorKey: opts.editorKey, editor: opts.editor,
hash: opts.hash, hash: opts.hash,
isPrivate: (opts.isPrivate === true || opts.isPrivate === 1), publishState: opts.publishState,
isPublished: (opts.isPublished === true || opts.isPublished === 1),
localeCode: opts.localeCode, localeCode: opts.localeCode,
path: opts.path, path: opts.path,
publishEndDate: opts.publishEndDate || '', publishEndDate: opts.publishEndDate?.toISO(),
publishStartDate: opts.publishStartDate || '', publishStartDate: opts.publishStartDate?.toISO(),
title: opts.title, title: opts.title,
action: opts.action || 'updated', action: opts.action || 'updated',
versionDate: opts.versionDate versionDate: opts.versionDate
@ -116,7 +108,6 @@ module.exports = class PageHistory extends Model {
'pageHistory.path', 'pageHistory.path',
'pageHistory.title', 'pageHistory.title',
'pageHistory.description', 'pageHistory.description',
'pageHistory.isPrivate',
'pageHistory.isPublished', 'pageHistory.isPublished',
'pageHistory.publishStartDate', 'pageHistory.publishStartDate',
'pageHistory.publishEndDate', 'pageHistory.publishEndDate',

@ -421,8 +421,8 @@ module.exports = class Page extends Model {
content: opts.content, content: opts.content,
description: opts.description, description: opts.description,
publishState: opts.publishState, publishState: opts.publishState,
publishEndDate: opts.publishEndDate || '', publishEndDate: opts.publishEndDate?.toISO(),
publishStartDate: opts.publishStartDate || '', publishStartDate: opts.publishStartDate?.toISO(),
title: opts.title, title: opts.title,
extra: JSON.stringify({ extra: JSON.stringify({
...ogPage.extra, ...ogPage.extra,
@ -439,18 +439,18 @@ module.exports = class Page extends Model {
await WIKI.db.pages.renderPage(page) await WIKI.db.pages.renderPage(page)
WIKI.events.outbound.emit('deletePageFromCache', page.hash) WIKI.events.outbound.emit('deletePageFromCache', page.hash)
// -> Update Search Index // // -> Update Search Index
const pageContents = await WIKI.db.pages.query().findById(page.id).select('render') // const pageContents = await WIKI.db.pages.query().findById(page.id).select('render')
page.safeContent = WIKI.db.pages.cleanHTML(pageContents.render) // page.safeContent = WIKI.db.pages.cleanHTML(pageContents.render)
await WIKI.data.searchEngine.updated(page) // await WIKI.data.searchEngine.updated(page)
// -> Update on Storage // -> Update on Storage
if (!opts.skipStorage) { // if (!opts.skipStorage) {
await WIKI.db.storage.pageEvent({ // await WIKI.db.storage.pageEvent({
event: 'updated', // event: 'updated',
page // page
}) // })
} // }
// -> Perform move? // -> Perform move?
if ((opts.locale && opts.locale !== page.localeCode) || (opts.path && opts.path !== page.path)) { if ((opts.locale && opts.locale !== page.localeCode) || (opts.path && opts.path !== page.path)) {

@ -40,28 +40,20 @@ html(lang=siteConfig.lang)
//- CSS //- CSS
link(
type='text/css'
rel='stylesheet'
href='/_assets-legacy/css/app.629ebe3c082227dbee31.css'
)
//- JS //- JS
script( script(
type='text/javascript' type='text/javascript'
src='/_assets-legacy/js/runtime.js?1664769154' src='/_assets-legacy/js/runtime.js'
) )
script( script(
type='text/javascript' type='text/javascript'
src='/_assets-legacy/js/app.js?1664769154' src='/_assets-legacy/js/app.js'
) )

@ -7,7 +7,7 @@ block head
block body block body
#root #root
editor( editor(
:page-id=page.id page-id=page.id
locale=page.localeCode locale=page.localeCode
path=page.path path=page.path
title=page.title title=page.title

@ -5,18 +5,12 @@ enableTelemetry: false
nodeLinker: node-modules nodeLinker: node-modules
packageExtensions: packageExtensions:
'@quasar/vite-plugin@*':
dependencies:
'quasar': '*'
'rollup-plugin-visualizer@*': 'rollup-plugin-visualizer@*':
dependencies: dependencies:
'rollup': '*' 'rollup': '*'
'v-network-graph@*': 'v-network-graph@*':
dependencies: dependencies:
'd3-force': '*' 'd3-force': '*'
'@intlify/vite-plugin-vue-i18n@*':
dependencies:
'vite': '*'
plugins: plugins:
- path: .yarn/plugins/@yarnpkg/plugin-interactive-tools.cjs - path: .yarn/plugins/@yarnpkg/plugin-interactive-tools.cjs

@ -77,6 +77,7 @@ module.exports = configure(function (/* ctx */) {
extendViteConf (viteConf) { extendViteConf (viteConf) {
viteConf.build.assetsDir = '_assets' viteConf.build.assetsDir = '_assets'
// viteConf.resolve.alias.vue = '/workspace/ux/node_modules/vue/dist/vue.esm-bundler.js'
// viteConf.build.rollupOptions = { // viteConf.build.rollupOptions = {
// ...viteConf.build.rollupOptions ?? {}, // ...viteConf.build.rollupOptions ?? {},
// external: [ // external: [

@ -5,7 +5,7 @@ q-item-section(avatar)
:text-color='avatarTextColor' :text-color='avatarTextColor'
font-size='14px' font-size='14px'
rounded rounded
:style='hueRotate !== 0 ? `filter: hue-rotate(` + hueRotate + `deg)` : ``' :style='props.hueRotate !== 0 ? `filter: hue-rotate(` + props.hueRotate + `deg)` : ``'
) )
q-badge( q-badge(
v-if='indicatorDot' v-if='indicatorDot'
@ -13,57 +13,57 @@ q-item-section(avatar)
:color='indicatorDot' :color='indicatorDot'
floating floating
) )
q-tooltip(v-if='indicatorText') {{indicatorText}} q-tooltip(v-if='props.indicatorText') {{props.indicatorText}}
q-icon( q-icon(
v-if='!textMode' v-if='!textMode'
:name='`img:/_assets/icons/ultraviolet-` + icon + `.svg`' :name='`img:/_assets/icons/ultraviolet-` + icon + `.svg`'
size='sm' size='sm'
) )
span.text-uppercase(v-else) {{text}} span.text-uppercase(v-else) {{props.text}}
</template> </template>
<script> <script setup>
export default { import { computed } from 'vue'
name: 'BlueprintIcon', import { useQuasar } from 'quasar'
props: {
icon: { const props = defineProps({
type: String, icon: {
default: '' type: String,
}, default: ''
dark: { },
type: Boolean, dark: {
default: false type: Boolean,
}, default: false
indicator: { },
type: String, indicator: {
default: null type: String,
}, default: null
indicatorText: { },
type: String, indicatorText: {
default: null type: String,
}, default: null
hueRotate: {
type: Number,
default: 0
},
text: {
type: String,
default: null
}
}, },
data () { hueRotate: {
return { type: Number,
imgPath: null default: 0
}
}, },
computed: { text: {
textMode () { return this.text !== null }, type: String,
avatarBgColor () { return this.$q.dark.isActive || this.dark ? 'dark-4' : 'blue-1' }, default: null
avatarTextColor () { return this.$q.dark.isActive || this.dark ? 'white' : 'blue-7' },
indicatorDot () {
if (this.indicator === null) { return null }
return (this.indicator === '') ? 'pink' : this.indicator
}
} }
} })
// QUASAR
const $q = useQuasar()
// COMPUTED
const textMode = computed(() => { return props.text !== null })
const avatarBgColor = computed(() => { return $q.dark.isActive || props.dark ? 'dark-4' : 'blue-1' })
const avatarTextColor = computed(() => { return $q.dark.isActive || props.dark ? 'white' : 'blue-7' })
const indicatorDot = computed(() => {
if (props.indicator === null) { return null }
return (props.indicator === '') ? 'pink' : props.indicator
})
</script> </script>

@ -1,7 +1,7 @@
<template lang="pug"> <template lang="pug">
q-card.icon-picker(flat, style='width: 400px;') q-card.icon-picker(flat, style='width: 400px;')
q-tabs.text-primary( q-tabs.text-primary(
v-model='currentTab' v-model='state.currentTab'
no-caps no-caps
inline-label inline-label
) )
@ -17,12 +17,12 @@ q-card.icon-picker(flat, style='width: 400px;')
) )
q-separator q-separator
q-tab-panels( q-tab-panels(
v-model='currentTab' v-model='state.currentTab'
) )
q-tab-panel(name='icon') q-tab-panel(name='icon')
q-select( q-select(
:options='iconPacks' :options='iconPacks'
v-model='selPack' v-model='state.selPack'
emit-value emit-value
map-options map-options
outlined outlined
@ -52,7 +52,7 @@ q-card.icon-picker(flat, style='width: 400px;')
size='sm' size='sm'
) {{scope.opt.subset.toUpperCase()}} ) {{scope.opt.subset.toUpperCase()}}
q-input.q-mt-md( q-input.q-mt-md(
v-model='selIcon' v-model='state.selIcon'
outlined outlined
label='Icon Name' label='Icon Name'
dense dense
@ -96,7 +96,7 @@ q-card.icon-picker(flat, style='width: 400px;')
q-img( q-img(
transition='jump-down' transition='jump-down'
:ratio='1' :ratio='1'
:src='imgPath' :src='state.imgPath'
) )
q-separator q-separator
q-card-actions q-card-actions
@ -118,67 +118,80 @@ q-card.icon-picker(flat, style='width: 400px;')
) )
</template> </template>
<script> <script setup>
import { find } from 'lodash-es' import { find } from 'lodash-es'
import { computed, onMounted, reactive } from 'vue'
export default { // PROPS
props: {
value: { const props = defineProps({
type: String, value: {
required: true type: String,
} required: true
}, }
data () { })
return {
currentTab: 'icon', // EMITS
selPack: 'las',
selIcon: '', const emit = defineEmits(['input'])
imgPath: 'https://placeimg.com/64/64/nature',
iconPacks: [ // DATA
{ value: 'las', label: 'Line Awesome (solid)', name: 'Line Awesome', subset: 'solid', prefix: 'las la-', reference: 'https://icons8.com/line-awesome' },
{ value: 'lab', label: 'Line Awesome (brands)', name: 'Line Awesome', subset: 'brands', prefix: 'lab la-', reference: 'https://icons8.com/line-awesome' }, const state = reactive({
{ value: 'mdi', label: 'Material Design Icons', name: 'Material Design Icons', prefix: 'mdi-', reference: 'https://materialdesignicons.com' }, currentTab: 'icon',
{ value: 'fas', label: 'Font Awesome (solid)', name: 'Font Awesome', subset: 'solid', prefix: 'fas fa-', reference: 'https://fontawesome.com/icons' }, selPack: 'las',
{ value: 'far', label: 'Font Awesome (regular)', name: 'Font Awesome', subset: 'regular', prefix: 'far fa-', reference: 'https://fontawesome.com/icons' }, selIcon: '',
{ value: 'fal', label: 'Font Awesome (light)', name: 'Font Awesome', subset: 'light', prefix: 'fal fa-', reference: 'https://fontawesome.com/icons' }, imgPath: 'https://placeimg.com/64/64/nature'
{ value: 'fad', label: 'Font Awesome (duotone)', name: 'Font Awesome', subset: 'duotone', prefix: 'fad fa-', reference: 'https://fontawesome.com/icons' }, })
{ value: 'fab', label: 'Font Awesome (brands)', name: 'Font Awesome', subset: 'brands', prefix: 'fab fa-', reference: 'https://fontawesome.com/icons' }
] const iconPacks = [
} { value: 'las', label: 'Line Awesome (solid)', name: 'Line Awesome', subset: 'solid', prefix: 'las la-', reference: 'https://icons8.com/line-awesome' },
}, { value: 'lab', label: 'Line Awesome (brands)', name: 'Line Awesome', subset: 'brands', prefix: 'lab la-', reference: 'https://icons8.com/line-awesome' },
computed: { { value: 'mdi', label: 'Material Design Icons', name: 'Material Design Icons', prefix: 'mdi-', reference: 'https://materialdesignicons.com' },
iconName () { { value: 'fas', label: 'Font Awesome (solid)', name: 'Font Awesome', subset: 'solid', prefix: 'fas fa-', reference: 'https://fontawesome.com/icons' },
return find(this.iconPacks, ['value', this.selPack]).prefix + this.selIcon { value: 'far', label: 'Font Awesome (regular)', name: 'Font Awesome', subset: 'regular', prefix: 'far fa-', reference: 'https://fontawesome.com/icons' },
}, { value: 'fal', label: 'Font Awesome (light)', name: 'Font Awesome', subset: 'light', prefix: 'fal fa-', reference: 'https://fontawesome.com/icons' },
iconPackRefWebsite () { { value: 'fad', label: 'Font Awesome (duotone)', name: 'Font Awesome', subset: 'duotone', prefix: 'fad fa-', reference: 'https://fontawesome.com/icons' },
return find(this.iconPacks, ['value', this.selPack]).reference { value: 'fab', label: 'Font Awesome (brands)', name: 'Font Awesome', subset: 'brands', prefix: 'fab fa-', reference: 'https://fontawesome.com/icons' }
} ]
},
mounted () { // COMPUTED
if (this.value?.startsWith('img:')) {
this.currentTab = 'img' const iconName = computed(() => {
this.imgPath = this.value.substring(4) return find(iconPacks, ['value', state.selPack]).prefix + state.selIcon
} else { })
this.currentTab = 'icon'
for (const pack of this.iconPacks) { const iconPackRefWebsite = computed(() => {
if (this.value?.startsWith(pack.prefix)) { return find(iconPacks, ['value', state.selPack]).reference
this.selPack = pack.value })
this.selIcon = this.value.substring(pack.prefix.length)
break // METHODS
}
} function apply () {
} if (state.currentTab === 'img') {
}, emit('input', `img:${state.imgPath}`)
methods: { } else {
apply () { emit('input', state.iconName)
if (this.currentTab === 'img') { }
this.$emit('input', `img:${this.imgPath}`) }
} else {
this.$emit('input', this.iconName) // MOUNTED
onMounted(() => {
if (props.value?.startsWith('img:')) {
state.currentTab = 'img'
state.imgPath = props.value.substring(4)
} else {
state.currentTab = 'icon'
for (const pack of iconPacks) {
if (props.value?.startsWith(pack.prefix)) {
state.selPack = pack.value
state.selIcon = props.value.substring(pack.prefix.length)
break
} }
} }
} }
} })
</script> </script>
<style lang="scss"> <style lang="scss">

@ -1,6 +1,6 @@
<template lang="pug"> <template lang="pug">
q-card.page-properties-dialog q-card.page-properties-dialog
.floating-sidepanel-quickaccess.animated.fadeIn(v-if='showQuickAccess', style='right: 486px;') .floating-sidepanel-quickaccess.animated.fadeIn(v-if='state.showQuickAccess', style='right: 486px;')
template(v-for='(qa, idx) of quickaccess', :key='`qa-` + qa.key') template(v-for='(qa, idx) of quickaccess', :key='`qa-` + qa.key')
q-btn( q-btn(
:icon='qa.icon' :icon='qa.icon'
@ -12,7 +12,7 @@ q-card.page-properties-dialog
q-tooltip(anchor='center left' self='center right') {{qa.label}} q-tooltip(anchor='center left' self='center right') {{qa.label}}
q-separator(dark, v-if='idx < quickaccess.length - 1') q-separator(dark, v-if='idx < quickaccess.length - 1')
q-toolbar.bg-primary.text-white.flex q-toolbar.bg-primary.text-white.flex
.text-subtitle2 {{$t('editor.props.pageProperties')}} .text-subtitle2 {{t('editor.props.pageProperties')}}
q-space q-space
q-btn( q-btn(
icon='las la-times' icon='las la-times'
@ -22,61 +22,61 @@ q-card.page-properties-dialog
) )
q-scroll-area( q-scroll-area(
ref='scrollArea' ref='scrollArea'
:thumb-style='thumbStyle' :thumb-style='siteStore.thumbStyle'
:bar-style='barStyle' :bar-style='siteStore.barStyle'
style='height: calc(100% - 50px);' style='height: calc(100% - 50px);'
) )
q-card-section(ref='card-info') q-card-section(id='refCardInfo')
.text-overline.items-center.flex #[q-icon.q-mr-sm(name='las la-info-circle', size='xs')] {{$t('editor.props.info')}} .text-overline.items-center.flex #[q-icon.q-mr-sm(name='las la-info-circle', size='xs')] {{t('editor.props.info')}}
q-form.q-gutter-sm q-form.q-gutter-sm
q-input( q-input(
v-model='title' v-model='pageStore.title'
:label='$t(`editor.props.title`)' :label='t(`editor.props.title`)'
outlined outlined
dense dense
) )
q-input( q-input(
v-model='description' v-model='pageStore.description'
:label='$t(`editor.props.shortDescription`)' :label='t(`editor.props.shortDescription`)'
outlined outlined
dense dense
) )
q-card-section.alt-card(ref='card-publishstate') q-card-section.alt-card(id='refCardPublishState')
.text-overline.q-pb-xs.items-center.flex #[q-icon.q-mr-sm(name='las la-power-off', size='xs')] {{$t('editor.props.publishState')}} .text-overline.q-pb-xs.items-center.flex #[q-icon.q-mr-sm(name='las la-power-off', size='xs')] {{t('editor.props.publishState')}}
q-form.q-gutter-md q-form.q-gutter-md
div div
q-btn-toggle( q-btn-toggle(
v-model='isPublished' v-model='pageStore.isPublished'
push push
glossy glossy
no-caps no-caps
toggle-color='primary' toggle-color='primary'
:options=`[ :options=`[
{ label: $t('editor.props.draft'), value: false }, { label: t('editor.props.draft'), value: false },
{ label: $t('editor.props.published'), value: true }, { label: t('editor.props.published'), value: true },
{ label: $t('editor.props.dateRange'), value: null } { label: t('editor.props.dateRange'), value: null }
]` ]`
) )
.text-caption(v-if='isPublished'): em {{$t('editor.props.publishedHint')}} .text-caption(v-if='pageStore.isPublished'): em {{t('editor.props.publishedHint')}}
.text-caption(v-else-if='isPublished === false'): em {{$t('editor.props.draftHint')}} .text-caption(v-else-if='pageStore.isPublished === false'): em {{t('editor.props.draftHint')}}
template(v-else-if='isPublished === null') template(v-else-if='pageStore.isPublished === null')
.text-caption: em {{$t('editor.props.dateRangeHint')}} .text-caption: em {{t('editor.props.dateRangeHint')}}
q-date( q-date(
v-model='publishingRange' v-model='pageStore.publishingRange'
range range
flat flat
bordered bordered
landscape landscape
minimal minimal
) )
q-card-section(ref='card-relations') q-card-section(id='refCardRelations')
.text-overline.items-center.flex #[q-icon.q-mr-sm(name='las la-sun', size='xs')] {{$t('editor.props.relations')}} .text-overline.items-center.flex #[q-icon.q-mr-sm(name='las la-sun', size='xs')] {{t('editor.props.relations')}}
q-list.rounded-borders.q-mb-sm.bg-white( q-list.rounded-borders.q-mb-sm.bg-white(
v-if='relations.length > 0' v-if='pageStore.relations.length > 0'
separator separator
bordered bordered
) )
q-item(v-for='rel of relations', :key='`rel-id-` + rel.id') q-item(v-for='rel of pageStore.relations', :key='`rel-id-` + rel.id')
q-item-section(side) q-item-section(side)
q-icon(:name='rel.icon') q-icon(:name='rel.icon')
q-item-section q-item-section
@ -107,130 +107,130 @@ q-card.page-properties-dialog
@click='removeRelation(rel)' @click='removeRelation(rel)'
) )
q-btn.full-width( q-btn.full-width(
:label='$t(`editor.props.relationAdd`)' :label='t(`editor.props.relationAdd`)'
icon='las la-plus' icon='las la-plus'
no-caps no-caps
unelevated unelevated
color='secondary' color='secondary'
@click='newRelation' @click='newRelation'
) )
q-tooltip {{$t('editor.props.relationAddHint')}} q-tooltip {{t('editor.props.relationAddHint')}}
q-card-section.alt-card(ref='card-scripts') q-card-section.alt-card(id='refCardScripts')
.text-overline.items-center.flex #[q-icon.q-mr-sm(name='las la-code', size='xs')] {{$t('editor.props.scripts')}} .text-overline.items-center.flex #[q-icon.q-mr-sm(name='las la-code', size='xs')] {{t('editor.props.scripts')}}
q-btn.full-width( q-btn.full-width(
:label='$t(`editor.props.jsLoad`)' :label='t(`editor.props.jsLoad`)'
icon='lab la-js-square' icon='lab la-js-square'
no-caps no-caps
unelevated unelevated
color='secondary' color='secondary'
@click='editScripts(`jsLoad`)' @click='editScripts(`jsLoad`)'
) )
q-tooltip {{$t('editor.props.jsLoadHint')}} q-tooltip {{t('editor.props.jsLoadHint')}}
q-btn.full-width.q-mt-sm( q-btn.full-width.q-mt-sm(
:label='$t(`editor.props.jsUnload`)' :label='t(`editor.props.jsUnload`)'
icon='lab la-js-square' icon='lab la-js-square'
no-caps no-caps
unelevated unelevated
color='secondary' color='secondary'
@click='editScripts(`jsUnload`)' @click='editScripts(`jsUnload`)'
) )
q-tooltip {{$t('editor.props.jsUnloadHint')}} q-tooltip {{t('editor.props.jsUnloadHint')}}
q-btn.full-width.q-mt-sm( q-btn.full-width.q-mt-sm(
:label='$t(`editor.props.styles`)' :label='t(`editor.props.styles`)'
icon='lab la-css3-alt' icon='lab la-css3-alt'
no-caps no-caps
unelevated unelevated
color='secondary' color='secondary'
@click='editScripts(`styles`)' @click='editScripts(`styles`)'
) )
q-tooltip {{$t('editor.props.stylesHint')}} q-tooltip {{t('editor.props.stylesHint')}}
q-card-section.q-pb-lg(ref='card-sidebar') q-card-section.q-pb-lg(id='refCardSidebar')
.text-overline.items-center.flex #[q-icon.q-mr-sm(name='las la-ruler-vertical', size='xs')] {{$t('editor.props.sidebar')}} .text-overline.items-center.flex #[q-icon.q-mr-sm(name='las la-ruler-vertical', size='xs')] {{t('editor.props.sidebar')}}
q-form.q-gutter-md.q-pt-sm q-form.q-gutter-md.q-pt-sm
div div
q-toggle( q-toggle(
v-model='showSidebar' v-model='pageStore.showSidebar'
dense dense
:label='$t(`editor.props.showSidebar`)' :label='t(`editor.props.showSidebar`)'
color='primary' color='primary'
checked-icon='las la-check' checked-icon='las la-check'
unchecked-icon='las la-times' unchecked-icon='las la-times'
) )
div div
q-toggle( q-toggle(
v-if='showSidebar' v-if='pageStore.showSidebar'
v-model='showToc' v-model='pageStore.showToc'
dense dense
:label='$t(`editor.props.showToc`)' :label='t(`editor.props.showToc`)'
color='primary' color='primary'
checked-icon='las la-check' checked-icon='las la-check'
unchecked-icon='las la-times' unchecked-icon='las la-times'
) )
div( div(
v-if='showSidebar && showToc' v-if='pageStore.showSidebar && pageStore.showToc'
style='padding-left: 40px;' style='padding-left: 40px;'
) )
.text-caption {{$t('editor.props.tocMinMaxDepth')}} #[strong (H{{tocDepth.min}} &rarr; H{{tocDepth.max}})] .text-caption {{t('editor.props.tocMinMaxDepth')}} #[strong (H{{pageStore.tocDepth.min}} &rarr; H{{pageStore.tocDepth.max}})]
q-range( q-range(
v-model='tocDepth' v-model='pageStore.tocDepth'
:min='1' :min='1'
:max='6' :max='6'
color='primary' color='primary'
:left-label-value='`H` + tocDepth.min' :left-label-value='`H` + pageStore.tocDepth.min'
:right-label-value='`H` + tocDepth.max' :right-label-value='`H` + pageStore.tocDepth.max'
snap snap
label label
markers markers
) )
div div
q-toggle( q-toggle(
v-if='showSidebar' v-if='pageStore.showSidebar'
v-model='showTags' v-model='pageStore.showTags'
dense dense
:label='$t(`editor.props.showTags`)' :label='t(`editor.props.showTags`)'
color='primary' color='primary'
checked-icon='las la-check' checked-icon='las la-check'
unchecked-icon='las la-times' unchecked-icon='las la-times'
) )
q-card-section.alt-card.q-pb-lg(ref='card-social') q-card-section.alt-card.q-pb-lg(id='refCardSocial')
.text-overline.items-center.flex #[q-icon.q-mr-sm(name='las la-comments', size='xs')] {{$t('editor.props.social')}} .text-overline.items-center.flex #[q-icon.q-mr-sm(name='las la-comments', size='xs')] {{t('editor.props.social')}}
q-form.q-gutter-md.q-pt-sm q-form.q-gutter-md.q-pt-sm
div div
q-toggle( q-toggle(
v-model='allowComments' v-model='pageStore.allowComments'
dense dense
:label='$t(`editor.props.allowComments`)' :label='t(`editor.props.allowComments`)'
color='primary' color='primary'
checked-icon='las la-check' checked-icon='las la-check'
unchecked-icon='las la-times' unchecked-icon='las la-times'
) )
div div
q-toggle( q-toggle(
v-model='allowContributions' v-model='pageStore.allowContributions'
dense dense
:label='$t(`editor.props.allowContributions`)' :label='t(`editor.props.allowContributions`)'
color='primary' color='primary'
checked-icon='las la-check' checked-icon='las la-check'
unchecked-icon='las la-times' unchecked-icon='las la-times'
) )
div div
q-toggle( q-toggle(
v-model='allowRatings' v-model='pageStore.allowRatings'
dense dense
:label='$t(`editor.props.allowRatings`)' :label='t(`editor.props.allowRatings`)'
color='primary' color='primary'
checked-icon='las la-check' checked-icon='las la-check'
unchecked-icon='las la-times' unchecked-icon='las la-times'
) )
q-card-section.q-pb-lg(ref='card-tags') q-card-section.q-pb-lg(id='refCardTags')
.text-overline.items-center.flex #[q-icon.q-mr-sm(name='las la-tags', size='xs')] {{$t('editor.props.tags')}} .text-overline.items-center.flex #[q-icon.q-mr-sm(name='las la-tags', size='xs')] {{t('editor.props.tags')}}
page-tags(edit) page-tags(edit)
q-card-section.alt-card.q-pb-lg(ref='card-visibility') q-card-section.alt-card.q-pb-lg(id='refCardVisibility')
.text-overline.items-center.flex #[q-icon.q-mr-sm(name='las la-eye', size='xs')] {{$t('editor.props.visibility')}} .text-overline.items-center.flex #[q-icon.q-mr-sm(name='las la-eye', size='xs')] {{t('editor.props.visibility')}}
q-form.q-gutter-md.q-pt-sm q-form.q-gutter-md.q-pt-sm
div div
q-toggle( q-toggle(
v-model='showInTree' v-model='pageStore.showInTree'
dense dense
:label='$t(`editor.props.showInTree`)' :label='$t(`editor.props.showInTree`)'
color='primary' color='primary'
@ -239,7 +239,7 @@ q-card.page-properties-dialog
) )
div div
q-toggle( q-toggle(
v-model='requirePassword' v-model='state.requirePassword'
dense dense
:label='$t(`editor.props.requirePassword`)' :label='$t(`editor.props.requirePassword`)'
color='primary' color='primary'
@ -247,119 +247,118 @@ q-card.page-properties-dialog
unchecked-icon='las la-times' unchecked-icon='las la-times'
) )
div( div(
v-if='requirePassword' v-if='state.requirePassword'
style='padding-left: 40px;' style='padding-left: 40px;'
) )
q-input( q-input(
ref='iptPagePassword' ref='iptPagePassword'
v-model='password' v-model='state.password'
:label='$t(`editor.props.password`)' :label='t(`editor.props.password`)'
:hint='$t(`editor.props.passwordHint`)' :hint='t(`editor.props.passwordHint`)'
outlined outlined
dense dense
) )
q-dialog( q-dialog(
v-model='showRelationDialog' v-model='state.showRelationDialog'
) )
page-relation-dialog(:edit-id='editRelationId') page-relation-dialog(:edit-id='state.editRelationId')
q-dialog( q-dialog(
v-model='showScriptsDialog' v-model='state.showScriptsDialog'
) )
page-scripts-dialog(:mode='pageScriptsMode') page-scripts-dialog(:mode='state.pageScriptsMode')
</template> </template>
<script> <script setup>
import { get, sync } from 'vuex-pathify' import { usePageStore } from 'src/stores/page'
import { useSiteStore } from 'src/stores/site'
import { useI18n } from 'vue-i18n'
import { useQuasar } from 'quasar'
import { nextTick, onMounted, reactive, ref, watch } from 'vue'
import PageRelationDialog from './PageRelationDialog.vue' import PageRelationDialog from './PageRelationDialog.vue'
import PageScriptsDialog from './PageScriptsDialog.vue' import PageScriptsDialog from './PageScriptsDialog.vue'
import PageTags from './PageTags.vue' import PageTags from './PageTags.vue'
export default { // QUASAR
components: {
PageRelationDialog, const $q = useQuasar()
PageScriptsDialog,
PageTags // STORES
},
data () { const pageStore = usePageStore()
return { const siteStore = useSiteStore()
showRelationDialog: false,
showScriptsDialog: false, // I18N
publishingRange: {},
requirePassword: false, const { t } = useI18n()
password: '',
editRelationId: null, // DATA
pageScriptsMode: 'jsLoad',
showQuickAccess: true const state = reactive({
} showRelationDialog: false,
}, showScriptsDialog: false,
computed: { publishingRange: {},
title: sync('page/title', false), requirePassword: false,
description: sync('page/description', false), password: '',
showInTree: sync('page/showInTree', false), editRelationId: null,
isPublished: sync('page/isPublished', false), pageScriptsMode: 'jsLoad',
relations: sync('page/relations', false), showQuickAccess: true
showSidebar: sync('page/showSidebar', false), })
showToc: sync('page/showToc', false),
showTags: sync('page/showTags', false), const quickaccess = [
tocDepth: sync('page/tocDepth', false), { key: 'refCardInfo', icon: 'las la-info-circle', label: t('editor.props.info') },
allowComments: sync('page/allowComments', false), { key: 'refCardPublishState', icon: 'las la-power-off', label: t('editor.props.publishState') },
allowContributions: sync('page/allowContributions', false), { key: 'refCardRelations', icon: 'las la-sun', label: t('editor.props.relations') },
allowRatings: sync('page/allowRatings', false), { key: 'refCardScripts', icon: 'las la-code', label: t('editor.props.scripts') },
thumbStyle: get('site/thumbStyle', false), { key: 'refCardSidebar', icon: 'las la-ruler-vertical', label: t('editor.props.sidebar') },
barStyle: get('site/barStyle', false), { key: 'refCardSocial', icon: 'las la-comments', label: t('editor.props.social') },
quickaccess () { { key: 'refCardTags', icon: 'las la-tags', label: t('editor.props.tags') },
return [ { key: 'refCardVisibility', icon: 'las la-eye', label: t('editor.props.visibility') }
{ key: 'info', icon: 'las la-info-circle', label: this.$t('editor.props.info') }, ]
{ key: 'publishstate', icon: 'las la-power-off', label: this.$t('editor.props.publishState') },
{ key: 'relations', icon: 'las la-sun', label: this.$t('editor.props.relations') }, // WATCHERS
{ key: 'scripts', icon: 'las la-code', label: this.$t('editor.props.scripts') },
{ key: 'sidebar', icon: 'las la-ruler-vertical', label: this.$t('editor.props.sidebar') }, watch(() => state.requirePassword, (newValue) => {
{ key: 'social', icon: 'las la-comments', label: this.$t('editor.props.social') }, if (newValue) {
{ key: 'tags', icon: 'las la-tags', label: this.$t('editor.props.tags') }, nextTick(() => {
{ key: 'visibility', icon: 'las la-eye', label: this.$t('editor.props.visibility') } this.$refs.iptPagePassword.focus()
] this.$refs.iptPagePassword.$el.scrollIntoView({
}
},
watch: {
requirePassword (newValue) {
if (newValue) {
this.$nextTick(() => {
this.$refs.iptPagePassword.focus()
this.$refs.iptPagePassword.$el.scrollIntoView({
behavior: 'smooth'
})
})
}
}
},
mounted () {
setTimeout(() => {
this.showQuickAccess = true
}, 300)
},
methods: {
editScripts (mode) {
this.pageScriptsMode = mode
this.showScriptsDialog = true
},
newRelation () {
this.editRelationId = null
this.showRelationDialog = true
},
editRelation (rel) {
this.editRelationId = rel.id
this.showRelationDialog = true
},
removeRelation (rel) {
this.relations = this.$store.get('page/relations').filter(r => r.id !== rel.id)
},
jumpToSection (id) {
this.$refs[`card-${id}`].$el.scrollIntoView({
behavior: 'smooth' behavior: 'smooth'
}) })
// this.$refs.scrollArea.setScrollPosition(offset, 600) })
}
} }
})
// METHODS
function editScripts (mode) {
state.pageScriptsMode = mode
state.showScriptsDialog = true
}
function newRelation () {
state.editRelationId = null
state.showRelationDialog = true
}
function editRelation (rel) {
state.editRelationId = rel.id
state.showRelationDialog = true
} }
function removeRelation (rel) {
pageStore.relations = pageStore.relations.filter(r => r.id !== rel.id)
}
function jumpToSection (id) {
document.querySelector(`#${id}`).scrollIntoView({
behavior: 'smooth'
})
}
// MOUNTED
onMounted(() => {
setTimeout(() => {
state.showQuickAccess = true
}, 300)
})
</script> </script>

@ -1,88 +1,88 @@
<template lang="pug"> <template lang="pug">
q-card.page-relation-dialog(style='width: 500px;') q-card.page-relation-dialog(style='width: 500px;')
q-toolbar.bg-primary.text-white q-toolbar.bg-primary.text-white
.text-subtitle2(v-if='isEditMode') {{$t('editor.pageRel.titleEdit')}} .text-subtitle2(v-if='isEditMode') {{t('editor.pageRel.titleEdit')}}
.text-subtitle2(v-else) {{$t('editor.pageRel.title')}} .text-subtitle2(v-else) {{t('editor.pageRel.title')}}
q-card-section q-card-section
.text-overline {{$t('editor.pageRel.position')}} .text-overline {{t('editor.pageRel.position')}}
q-form.q-gutter-md.q-pt-md q-form.q-gutter-md.q-pt-md
div div
q-btn-toggle( q-btn-toggle(
v-model='pos' v-model='state.pos'
push push
glossy glossy
no-caps no-caps
toggle-color='primary' toggle-color='primary'
:options=`[ :options=`[
{ label: $t('editor.pageRel.left'), value: 'left' }, { label: t('editor.pageRel.left'), value: 'left' },
{ label: $t('editor.pageRel.center'), value: 'center' }, { label: t('editor.pageRel.center'), value: 'center' },
{ label: $t('editor.pageRel.right'), value: 'right' } { label: t('editor.pageRel.right'), value: 'right' }
]` ]`
) )
.text-overline {{$t('editor.pageRel.button')}} .text-overline {{t('editor.pageRel.button')}}
q-input( q-input(
ref='iptRelLabel' ref='iptRelLabel'
outlined outlined
dense dense
:label='$t(`editor.pageRel.label`)' :label='t(`editor.pageRel.label`)'
v-model='label' v-model='state.label'
) )
template(v-if='pos !== `center`') template(v-if='state.pos !== `center`')
q-input( q-input(
outlined outlined
dense dense
:label='$t(`editor.pageRel.caption`)' :label='t(`editor.pageRel.caption`)'
v-model='caption' v-model='state.caption'
) )
q-btn.rounded-borders( q-btn.rounded-borders(
:label='$t(`editor.pageRel.selectIcon`)' :label='t(`editor.pageRel.selectIcon`)'
color='primary' color='primary'
outline outline
) )
q-menu(content-class='shadow-7') q-menu(content-class='shadow-7')
icon-picker-dialog(v-model='icon') icon-picker-dialog(v-model='state.icon')
.text-overline {{$t('editor.pageRel.target')}} .text-overline {{t('editor.pageRel.target')}}
q-btn.rounded-borders( q-btn.rounded-borders(
:label='$t(`editor.pageRel.selectPage`)' :label='t(`editor.pageRel.selectPage`)'
color='primary' color='primary'
outline outline
) )
.text-overline {{$t('editor.pageRel.preview')}} .text-overline {{t('editor.pageRel.preview')}}
q-btn( q-btn(
v-if='pos === `left`' v-if='state.pos === `left`'
padding='sm md' padding='sm md'
outline outline
:icon='icon' :icon='state.icon'
no-caps no-caps
color='primary' color='primary'
) )
.column.text-left.q-pl-md .column.text-left.q-pl-md
.text-body2: strong {{label}} .text-body2: strong {{state.label}}
.text-caption {{caption}} .text-caption {{state.caption}}
q-btn.full-width( q-btn.full-width(
v-else-if='pos === `center`' v-else-if='state.pos === `center`'
:label='label' :label='state.label'
color='primary' color='primary'
flat flat
no-caps no-caps
:icon='icon' :icon='state.icon'
) )
q-btn( q-btn(
v-else-if='pos === `right`' v-else-if='state.pos === `right`'
padding='sm md' padding='sm md'
outline outline
:icon-right='icon' :icon-right='state.icon'
no-caps no-caps
color='primary' color='primary'
) )
.column.text-left.q-pr-md .column.text-left.q-pr-md
.text-body2: strong {{label}} .text-body2: strong {{state.label}}
.text-caption {{caption}} .text-caption {{state.caption}}
q-card-actions.card-actions q-card-actions.card-actions
q-space q-space
q-btn.acrylic-btn( q-btn.acrylic-btn(
icon='las la-times' icon='las la-times'
:label='$t(`common.actions.discard`)' :label='t(`common.actions.discard`)'
color='grey-7' color='grey-7'
padding='xs md' padding='xs md'
v-close-popup v-close-popup
@ -92,7 +92,7 @@ q-card.page-relation-dialog(style='width: 500px;')
v-if='isEditMode' v-if='isEditMode'
:disabled='!canSubmit' :disabled='!canSubmit'
icon='las la-check' icon='las la-check'
:label='$t(`common.actions.save`)' :label='t(`common.actions.save`)'
unelevated unelevated
color='primary' color='primary'
padding='xs md' padding='xs md'
@ -103,7 +103,7 @@ q-card.page-relation-dialog(style='width: 500px;')
v-else v-else
:disabled='!canSubmit' :disabled='!canSubmit'
icon='las la-plus' icon='las la-plus'
:label='$t(`common.actions.create`)' :label='t(`common.actions.create`)'
unelevated unelevated
color='primary' color='primary'
padding='xs md' padding='xs md'
@ -112,99 +112,127 @@ q-card.page-relation-dialog(style='width: 500px;')
) )
</template> </template>
<script> <script setup>
import { v4 as uuid } from 'uuid' import { v4 as uuid } from 'uuid'
import { cloneDeep, find } from 'lodash-es' import { cloneDeep, find } from 'lodash-es'
import { useQuasar } from 'quasar'
import { useI18n } from 'vue-i18n'
import { computed, nextTick, onMounted, reactive, ref, watch } from 'vue'
import IconPickerDialog from './IconPickerDialog.vue' import IconPickerDialog from './IconPickerDialog.vue'
export default { import { usePageStore } from 'src/stores/page'
components: { import { useSiteStore } from 'src/stores/site'
IconPickerDialog
}, // PROPS
props: {
editId: { const props = defineProps({
type: String, editId: {
default: null type: String,
} default: null
}, }
data () { })
return {
pos: 'left', // QUASAR
label: '',
caption: '', const $q = useQuasar()
icon: 'las la-arrow-left',
target: '' // STORES
}
}, const pageStore = usePageStore()
computed: { const siteStore = useSiteStore()
canSubmit () {
return this.label.length > 0 // I18N
},
isEditMode () { const { t } = useI18n()
return Boolean(this.editId)
// DATA
const state = reactive({
pos: 'left',
label: '',
caption: '',
icon: 'las la-arrow-left',
target: ''
})
// REFS
const iptRelLabel = ref(null)
// COMPUTED
const canSubmit = computed(() => state.label.length > 0)
const isEditMode = computed(() => Boolean(props.editId))
// WATCHERS
watch(() => state.pos, (newValue) => {
switch (newValue) {
case 'left': {
state.icon = 'las la-arrow-left'
break
} }
}, case 'center': {
watch: { state.icon = 'las la-book'
pos (newValue) { break
switch (newValue) {
case 'left': {
this.icon = 'las la-arrow-left'
break
}
case 'center': {
this.icon = 'las la-book'
break
}
case 'right': {
this.icon = 'las la-arrow-right'
break
}
}
} }
}, case 'right': {
mounted () { state.icon = 'las la-arrow-right'
if (this.editId) { break
const rel = find(this.$store.get('page/relations'), ['id', this.editId])
if (rel) {
this.pos = rel.position
this.label = rel.label
this.caption = rel.caption || ''
this.icon = rel.icon
this.target = rel.target
}
} }
this.$nextTick(() => { }
this.$refs.iptRelLabel.focus() })
})
}, // METHODS
methods: {
create () { function create () {
this.$store.set('page/relations', [ pageStore.$patch({
...this.$store.get('page/relations'), relations: [
{ ...pageStore.relations,
id: uuid(), {
position: this.pos, id: uuid(),
label: this.label, position: state.pos,
...(this.pos !== 'center' ? { caption: this.caption } : {}), label: state.label,
icon: this.icon, ...(state.pos !== 'center' ? { caption: state.caption } : {}),
target: this.target icon: state.icon,
} target: state.target
])
},
persist () {
const rels = cloneDeep(this.$store.get('page/relations'))
for (const rel of rels) {
if (rel.id === this.editId) {
rel.position = this.pos
rel.label = this.label
rel.caption = this.caption
rel.icon = this.icon
rel.target = this.target
}
} }
this.$store.set('page/relations', rels) ]
})
}
function persist () {
const rels = cloneDeep(pageStore.relations)
for (const rel of rels) {
if (rel.id === state.editId) {
rel.position = state.pos
rel.label = state.label
rel.caption = state.caption
rel.icon = state.icon
rel.target = state.target
} }
} }
pageStore.$patch({
relations: rels
})
} }
// MOUNTED
onMounted(() => {
if (props.editId) {
const rel = find(pageStore.relations, ['id', props.editId])
if (rel) {
state.pos = rel.position
state.label = rel.label
state.caption = rel.caption || ''
state.icon = rel.icon
state.target = rel.target
}
}
nextTick(() => {
iptRelLabel.value.focus()
})
})
</script> </script>

@ -222,7 +222,9 @@ body::-webkit-scrollbar-thumb {
@import './animation.scss'; @import './animation.scss';
@import 'v-network-graph/lib/style.css' @import 'v-network-graph/lib/style.css';
@import './page-contents.scss';
// @import '~codemirror/lib/codemirror.css'; // @import '~codemirror/lib/codemirror.css';
// @import '~codemirror/theme/elegant.css'; // @import '~codemirror/theme/elegant.css';

@ -0,0 +1,62 @@
.page-contents {
color: #424242;
font-size: 14px;
// ---------------------------------
// HEADERS
// ---------------------------------
h1, h2, h3, h4, h5, h6 {
padding: 0;
margin: 0;
position: relative;
line-height: normal;
&:first-child {
padding-top: 0;
}
&:hover {
.toc-anchor {
display: block;
}
}
}
* + h1, * + h2, * + h3 {
border-top: 1px solid #DDD;
}
h1 {
font-size: 3em;
font-weight: 500;
padding: 12px 0;
}
h2 {
font-size: 2.4em;
padding: 12px 0;
}
h3 {
font-size: 2em;
padding: 12px 0;
}
h4 {
font-size: 1.75em;
}
h5 {
font-size: 1.5em;
}
h6 {
font-size: 1.25em;
}
.toc-anchor {
display: none;
position: absolute;
right: 1rem;
bottom: .5rem;
font-size: 1.25rem;
text-decoration: none;
color: #666;
}
}

@ -18,13 +18,13 @@ q-page.column
:icon='brd.icon' :icon='brd.icon'
:label='brd.title' :label='brd.title'
:aria-label='brd.title' :aria-label='brd.title'
:to='getFullPath(brd)' :to='brd.path'
) )
.col-auto.flex.items-center.justify-end .col-auto.flex.items-center.justify-end
template(v-if='!pageStore.isPublished') template(v-if='!pageStore.isPublished')
.text-caption.text-accent: strong Unpublished .text-caption.text-accent: strong Unpublished
q-separator.q-mx-sm(vertical) q-separator.q-mx-sm(vertical)
.text-caption.text-grey-6 Last modified on #[strong September 5th, 2020] .text-caption.text-grey-6 Last modified on #[strong {{lastModified}}]
.page-header.row .page-header.row
//- PAGE ICON //- PAGE ICON
.col-auto.q-pl-md.flex.items-center .col-auto.q-pl-md.flex.items-center
@ -90,7 +90,7 @@ q-page.column
style='height: 100%;' style='height: 100%;'
) )
.q-pa-md .q-pa-md
div(v-html='pageStore.render') .page-contents(v-html='pageStore.render')
template(v-if='pageStore.relations && pageStore.relations.length > 0') template(v-if='pageStore.relations && pageStore.relations.length > 0')
q-separator.q-my-lg q-separator.q-my-lg
.row.align-center .row.align-center
@ -288,14 +288,14 @@ q-page.column
transition-hide='jump-right' transition-hide='jump-right'
class='floating-sidepanel' class='floating-sidepanel'
) )
component(:is='state.sideDialogComponent') component(:is='sideDialogs[state.sideDialogComponent]')
q-dialog( q-dialog(
v-model='state.showGlobalDialog' v-model='state.showGlobalDialog'
transition-show='jump-up' transition-show='jump-up'
transition-hide='jump-down' transition-hide='jump-down'
) )
component(:is='state.globalDialogComponent') component(:is='globalDialogs[state.globalDialogComponent]')
</template> </template>
<script setup> <script setup>
@ -303,17 +303,23 @@ import { useMeta, useQuasar, setCssVar } from 'quasar'
import { computed, defineAsyncComponent, onMounted, reactive, ref, watch } from 'vue' import { computed, defineAsyncComponent, onMounted, reactive, ref, watch } from 'vue'
import { useRouter, useRoute } from 'vue-router' import { useRouter, useRoute } from 'vue-router'
import { useI18n } from 'vue-i18n' import { useI18n } from 'vue-i18n'
import { DateTime } from 'luxon'
import { usePageStore } from 'src/stores/page' import { usePageStore } from 'src/stores/page'
import { useSiteStore } from '../stores/site' import { useSiteStore } from 'src/stores/site'
// COMPONENTS // COMPONENTS
import SocialSharingMenu from '../components/SocialSharingMenu.vue' import SocialSharingMenu from '../components/SocialSharingMenu.vue'
import PageDataDialog from '../components/PageDataDialog.vue'
import PageTags from '../components/PageTags.vue' import PageTags from '../components/PageTags.vue'
import PagePropertiesDialog from '../components/PagePropertiesDialog.vue'
import PageSaveDialog from '../components/PageSaveDialog.vue' const sideDialogs = {
PageDataDialog: defineAsyncComponent(() => import('../components/PageDataDialog.vue')),
PagePropertiesDialog: defineAsyncComponent(() => import('../components/PagePropertiesDialog.vue'))
}
const globalDialogs = {
PageSaveDialog: defineAsyncComponent(() => import('../components/PageSaveDialog.vue'))
}
// QUASAR // QUASAR
@ -441,22 +447,36 @@ const editUrl = computed(() => {
pagePath += !pageStore.path ? 'home' : pageStore.path pagePath += !pageStore.path ? 'home' : pageStore.path
return `/_edit/${pagePath}` return `/_edit/${pagePath}`
}) })
const lastModified = computed(() => {
return pageStore.updatedAt ? DateTime.fromISO(pageStore.updatedAt).toLocaleString(DateTime.DATETIME_MED) : 'N/A'
})
// WATCHERS // WATCHERS
watch(() => route.path, async (newValue) => {
if (newValue.startsWith('/_')) { return }
try {
await pageStore.pageLoad({ path: newValue })
} catch (err) {
if (err.message === 'ERR_PAGE_NOT_FOUND') {
$q.notify({
type: 'negative',
message: 'This page does not exist (yet)!'
})
} else {
$q.notify({
type: 'negative',
message: err.message
})
}
}
}, { immediate: true })
watch(() => state.toc, refreshTocExpanded) watch(() => state.toc, refreshTocExpanded)
watch(() => pageStore.tocDepth, refreshTocExpanded) watch(() => pageStore.tocDepth, refreshTocExpanded)
// METHODS // METHODS
function getFullPath ({ locale, path }) {
if (siteStore.useLocales) {
return `/${locale}/${path}`
} else {
return `/${path}`
}
}
function togglePageProperties () { function togglePageProperties () {
state.sideDialogComponent = 'PagePropertiesDialog' state.sideDialogComponent = 'PagePropertiesDialog'
state.showSideDialog = true state.showSideDialog = true

@ -1,24 +1,29 @@
import { defineStore } from 'pinia' import { defineStore } from 'pinia'
import gql from 'graphql-tag'
import { cloneDeep, last, transform } from 'lodash-es'
import { useSiteStore } from './site'
export const usePageStore = defineStore('page', { export const usePageStore = defineStore('page', {
state: () => ({ state: () => ({
isLoading: true,
mode: 'view', mode: 'view',
editor: 'wysiwyg', editor: 'wysiwyg',
editorMode: 'edit', editorMode: 'edit',
id: 0, id: 0,
authorId: 0, authorId: 0,
authorName: 'Unknown', authorName: '',
createdAt: '', createdAt: '',
description: 'How to install Wiki.js on Ubuntu 18.04 / 20.04', description: '',
isPublished: true, isPublished: true,
showInTree: true, showInTree: true,
locale: 'en', locale: 'en',
path: '', path: '',
publishEndDate: '', publishEndDate: '',
publishStartDate: '', publishStartDate: '',
tags: ['cities', 'canada'], tags: [],
title: 'Ubuntu', title: '',
icon: 'lab la-empire', icon: 'las la-file-alt',
updatedAt: '', updatedAt: '',
relations: [], relations: [],
scriptJsLoad: '', scriptJsLoad: '',
@ -35,20 +40,20 @@ export const usePageStore = defineStore('page', {
max: 2 max: 2
}, },
breadcrumbs: [ breadcrumbs: [
{ // {
id: 1, // id: 1,
title: 'Installation', // title: 'Installation',
icon: 'las la-file-alt', // icon: 'las la-file-alt',
locale: 'en', // locale: 'en',
path: 'installation' // path: 'installation'
}, // },
{ // {
id: 2, // id: 2,
title: 'Ubuntu', // title: 'Ubuntu',
icon: 'lab la-ubuntu', // icon: 'lab la-ubuntu',
locale: 'en', // locale: 'en',
path: 'installation/ubuntu' // path: 'installation/ubuntu'
} // }
], ],
effectivePermissions: { effectivePermissions: {
comments: { comments: {
@ -75,10 +80,61 @@ export const usePageStore = defineStore('page', {
}, },
commentsCount: 0, commentsCount: 0,
content: '', content: '',
render: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.' render: ''
}), }),
getters: {}, getters: {},
actions: { actions: {
/**
* PAGE - LOAD
*/
async pageLoad ({ path, id }) {
const siteStore = useSiteStore()
try {
const resp = await APOLLO_CLIENT.query({
query: gql`
query loadPage (
$path: String!
) {
pageByPath(
path: $path
) {
id
title
description
path
locale
updatedAt
render
}
}
`,
variables: {
path
},
fetchPolicy: 'network-only'
})
const pageData = cloneDeep(resp?.data?.pageByPath ?? {})
if (!pageData?.id) {
throw new Error('ERR_PAGE_NOT_FOUND')
}
const pathPrefix = siteStore.useLocales ? `/${pageData.locale}` : ''
this.$patch({
...pageData,
breadcrumbs: transform(pageData.path.split('/'), (result, value, key) => {
result.push({
id: key,
title: value,
icon: 'las la-file-alt',
locale: 'en',
path: (last(result)?.path || pathPrefix) + `/${value}`
})
}, [])
})
} catch (err) {
console.warn(err)
throw err
}
},
/** /**
* PAGE - CREATE * PAGE - CREATE
*/ */

@ -5899,7 +5899,7 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"postcss@npm:^8.1.10, postcss@npm:^8.4.12, postcss@npm:^8.4.4": "postcss@npm:^8.1.10, postcss@npm:^8.4.4":
version: 8.4.12 version: 8.4.12
resolution: "postcss@npm:8.4.12" resolution: "postcss@npm:8.4.12"
dependencies: dependencies:
@ -6240,13 +6240,6 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"quasar@npm:*":
version: 2.6.6
resolution: "quasar@npm:2.6.6"
checksum: 0ef62a7e916f88cea06d3e5320cec9a0eddebd829891b7bc0ee11a4ec47653b1ae08d414241c046a8c65657e9f12cf2ead4fc214f6dc43d57816535f5c216160
languageName: node
linkType: hard
"quasar@npm:2.7.7": "quasar@npm:2.7.7":
version: 2.7.7 version: 2.7.7
resolution: "quasar@npm:2.7.7" resolution: "quasar@npm:2.7.7"
@ -6460,8 +6453,8 @@ __metadata:
linkType: hard linkType: hard
"rollup@npm:*": "rollup@npm:*":
version: 2.71.1 version: 3.2.3
resolution: "rollup@npm:2.71.1" resolution: "rollup@npm:3.2.3"
dependencies: dependencies:
fsevents: ~2.3.2 fsevents: ~2.3.2
dependenciesMeta: dependenciesMeta:
@ -6469,7 +6462,7 @@ __metadata:
optional: true optional: true
bin: bin:
rollup: dist/bin/rollup rollup: dist/bin/rollup
checksum: fe2b2fda7bf53c86e970f3b026b784c00e2237089b802755b3e43725db88f5d1869c1f81f8c5257e9b68b0fd1840dcbd3897d2f19768cce97a37c70e1a563dce checksum: e4b4f3b70fad4b8f7dabc579fb8bbe14399d5aed0cc1fcee39f15ae81804d6acd0e1063b653e6cf5ef50e8e954801689e2c822e99ed31ca18f1b1fbbea8075e5
languageName: node languageName: node
linkType: hard linkType: hard
@ -6487,20 +6480,6 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"rollup@npm:^2.59.0":
version: 2.70.1
resolution: "rollup@npm:2.70.1"
dependencies:
fsevents: ~2.3.2
dependenciesMeta:
fsevents:
optional: true
bin:
rollup: dist/bin/rollup
checksum: 06c62933e6e81a1c8c684d7d576e507081aabdb63cc0c91bca86b7348b66df03b77827068e4990b8b6c738bd3ef66dcc8c7ed7e0ea40b736068e7618f693133e
languageName: node
linkType: hard
"rope-sequence@npm:^1.3.0": "rope-sequence@npm:^1.3.0":
version: 1.3.2 version: 1.3.2
resolution: "rope-sequence@npm:1.3.2" resolution: "rope-sequence@npm:1.3.2"
@ -7326,35 +7305,6 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"vite@npm:*":
version: 2.9.6
resolution: "vite@npm:2.9.6"
dependencies:
esbuild: ^0.14.27
fsevents: ~2.3.2
postcss: ^8.4.12
resolve: ^1.22.0
rollup: ^2.59.0
peerDependencies:
less: "*"
sass: "*"
stylus: "*"
dependenciesMeta:
fsevents:
optional: true
peerDependenciesMeta:
less:
optional: true
sass:
optional: true
stylus:
optional: true
bin:
vite: bin/vite.js
checksum: 79bbf516547f4adb1a297ac8648f8818b3e2a7bb113a7e12acc2355498d247556f58fc50d39eec4891c07250e7420e72773e358eeb8dc62af62fc1ff32c70877
languageName: node
linkType: hard
"vite@npm:^2.9.13": "vite@npm:^2.9.13":
version: 2.9.15 version: 2.9.15
resolution: "vite@npm:2.9.15" resolution: "vite@npm:2.9.15"

Loading…
Cancel
Save