Merge branch 'requarks:main' into tyclipso

pull/7734/head
Carl Richter 3 years ago committed by GitHub
commit 023fb0ce8c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -28,7 +28,7 @@ It is also always helpful to have some context for your pull request. What was t
Use the feature request board to submit new ideas and vote on which ideas should be integrated first. Use the feature request board to submit new ideas and vote on which ideas should be integrated first.
:triangular_flag_on_post: [https://wiki.js.org/feedback/](https://wiki.js.org/feedback/) :triangular_flag_on_post: [https://js.wiki/feedback/](https://js.wiki/feedback/)
*Do not use GitHub issues to submit new feature ideas, as it will closed and you'll be asked to use the feature request board above. GitHub Issues are limited to bugs / issues / help*. *Do not use GitHub issues to submit new feature ideas, as it will closed and you'll be asked to use the feature request board above. GitHub Issues are limited to bugs / issues / help*.

@ -358,6 +358,23 @@ jobs:
env: env:
SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }} SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }}
SLACK_WEBHOOK_TYPE: INCOMING_WEBHOOK SLACK_WEBHOOK_TYPE: INCOMING_WEBHOOK
- name: Notify Telegram Channel
uses: appleboy/telegram-action@v0.1.1
with:
to: ${{ secrets.TELEGRAM_TO }}
token: ${{ secrets.TELEGRAM_TOKEN }}
format: markdown
disable_web_page_preview: true
message: |
Wiki.js *${{ github.ref_name }}* has been released!
See [release notes](https://github.com/requarks/wiki/releases) for details.
- name: Notify Discord Channel
uses: sebastianpopp/discord-action@v1.0
with:
webhook: ${{ secrets.DISCORD_WEBHOOK }}
message: Wiki.js ${{ github.ref_name }} has been released! See https://github.com/requarks/wiki/releases for details.
build-do-image: build-do-image:
name: Build DigitalOcean Image name: Build DigitalOcean Image

@ -10,11 +10,13 @@
[![Build + Publish](https://github.com/Requarks/wiki/actions/workflows/build.yml/badge.svg)](https://github.com/Requarks/wiki/actions/workflows/build.yml) [![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) [![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)
[![Subscribe to Newsletter](https://img.shields.io/badge/newsletter-subscribe-yellow.svg?style=flat&logo=mailchimp&logoColor=white)](https://blog.js.wiki/subscribe)
[![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)
[![Twitter Follow](https://img.shields.io/badge/follow-%40requarks-blue.svg?style=flat&logo=twitter)](https://twitter.com/requarks) [![Follow on Twitter](https://img.shields.io/badge/twitter-%40requarks-blue.svg?style=flat&logo=twitter&logoColor=white)](https://twitter.com/requarks)
[![Follow on Telegram](https://img.shields.io/badge/telegram-%40wiki__js-blue.svg?style=flat&logo=telegram)](https://t.me/wiki_js)
[![Chat on Discord](https://img.shields.io/badge/discord-join-8D96F6.svg?style=flat&logo=discord&logoColor=white)](https://discord.gg/rcxt9QS2jd)
[![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)
##### A modern, lightweight and powerful wiki app built on NodeJS ##### A modern, lightweight and powerful wiki app built on NodeJS
@ -39,7 +41,7 @@
<div align="center"> <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`). Wiki.js is an open source project that has been made possible due to the generous contributions by community [backers](https://js.wiki/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`).
[![Become a Sponsor](https://img.shields.io/badge/donate-github-ea4aaa.svg?style=popout&logo=github)](https://github.com/users/NGPixel/sponsorship) [![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) [![Become a Patron](https://img.shields.io/badge/donate-patreon-orange.svg?style=popout&logo=patreon)](https://www.patreon.com/requarks)
@ -137,12 +139,14 @@ Support this project by becoming a sponsor. Your name will show up in the Contri
<img width="441" height="1" /> <img width="441" height="1" />
- Akira Suenami ([@a-suenami](https://github.com/a-suenami)) - Akira Suenami ([@a-suenami](https://github.com/a-suenami))
- Armin Reiter ([@arminreiter](https://github.com/arminreiter))
- Arnaud Marchand ([@snuids](https://github.com/snuids)) - Arnaud Marchand ([@snuids](https://github.com/snuids))
- Brian Douglass ([@bhdouglass](https://github.com/bhdouglass)) - Brian Douglass ([@bhdouglass](https://github.com/bhdouglass))
- Bryon Vandiver ([@asterick](https://github.com/asterick)) - Bryon Vandiver ([@asterick](https://github.com/asterick))
- Cameron Steele ([@ATechAdventurer](https://github.com/ATechAdventurer)) - Cameron Steele ([@ATechAdventurer](https://github.com/ATechAdventurer))
- Charlie Schliesser ([@charlie-s](https://github.com/charlie-s)) - Charlie Schliesser ([@charlie-s](https://github.com/charlie-s))
- Cloud Data Hosting LLC ([@CloudDataHostingLLC](https://github.com/CloudDataHostingLLC)) - Cloud Data Hosting LLC ([@CloudDataHostingLLC](https://github.com/CloudDataHostingLLC))
- Cole Manning ([@RVRX](https://github.com/RVRX))
- CrazyMarvin ([@CrazyMarvin](https://github.com/CrazyMarvin)) - CrazyMarvin ([@CrazyMarvin](https://github.com/CrazyMarvin))
- David Christian Holin ([@SirGibihm](https://github.com/SirGibihm)) - David Christian Holin ([@SirGibihm](https://github.com/SirGibihm))
- Dragan Espenschied ([@despens](https://github.com/despens)) - Dragan Espenschied ([@despens](https://github.com/despens))
@ -161,11 +165,11 @@ Support this project by becoming a sponsor. Your name will show up in the Contri
- MaFarine ([@MaFarine](https://github.com/MaFarine)) - MaFarine ([@MaFarine](https://github.com/MaFarine))
- Marcilio Leite Neto ([@marclneto](https://github.com/marclneto)) - Marcilio Leite Neto ([@marclneto](https://github.com/marclneto))
- Mattias Johnson ([@mattiasJohnson](https://github.com/mattiasJohnson)) - Mattias Johnson ([@mattiasJohnson](https://github.com/mattiasJohnson))
- Max Ricketts-Uy ([@MaxRickettsUy](https://github.com/MaxRickettsUy))
</td><td> </td><td>
<img width="441" height="1" /> <img width="441" height="1" />
- Max Ricketts-Uy ([@MaxRickettsUy](https://github.com/MaxRickettsUy))
- Mickael Asseline ([@PAPAMICA](https://github.com/PAPAMICA)) - Mickael Asseline ([@PAPAMICA](https://github.com/PAPAMICA))
- Mitchell Rowton ([@mrowton](https://github.com/mrowton)) - Mitchell Rowton ([@mrowton](https://github.com/mrowton))
- M. Scott Ford ([@mscottford](https://github.com/mscottford)) - M. Scott Ford ([@mscottford](https://github.com/mscottford))
@ -176,6 +180,7 @@ Support this project by becoming a sponsor. Your name will show up in the Contri
- Oleksandr Koltsov ([@crambo](https://github.com/crambo)) - Oleksandr Koltsov ([@crambo](https://github.com/crambo))
- Philipp Schmitt ([@pschmitt](https://github.com/pschmitt)) - Philipp Schmitt ([@pschmitt](https://github.com/pschmitt))
- Robert Lanzke ([@winkelement](https://github.com/winkelement)) - Robert Lanzke ([@winkelement](https://github.com/winkelement))
- Ruizhe Li ([@liruizhe1995](https://github.com/liruizhe1995))
- Sam Martin ([@ABitMoreDepth](https://github.com/ABitMoreDepth)) - Sam Martin ([@ABitMoreDepth](https://github.com/ABitMoreDepth))
- Sean Coffey ([@seanecoffey](https://github.com/seanecoffey)) - Sean Coffey ([@seanecoffey](https://github.com/seanecoffey))
- Stephan Kristyn ([@stevek-pro](https://github.com/stevek-pro)) - Stephan Kristyn ([@stevek-pro](https://github.com/stevek-pro))
@ -188,6 +193,7 @@ Support this project by becoming a sponsor. Your name will show up in the Contri
- chaee ([@chaee](https://github.com/chaee)) - chaee ([@chaee](https://github.com/chaee))
- magicpotato ([@fortheday](https://github.com/fortheday)) - magicpotato ([@fortheday](https://github.com/fortheday))
- motoacs ([@motoacs](https://github.com/motoacs)) - motoacs ([@motoacs](https://github.com/motoacs))
- muzian666 ([@muzian666](https://github.com/muzian666))
- rburckner ([@rburckner](https://github.com/rburckner)) - rburckner ([@rburckner](https://github.com/rburckner))
- scorpion ([@scorpion](https://github.com/scorpion)) - scorpion ([@scorpion](https://github.com/scorpion))
- valantien ([@valantien](https://github.com/valantien)) - valantien ([@valantien](https://github.com/valantien))
@ -354,6 +360,7 @@ Thank you to all our patrons! 🙏 [[Become a patron](https://www.patreon.com/re
- Alex Balabanov - Alex Balabanov
- Alex Zen - Alex Zen
- Arti Zirk - Arti Zirk
- Ave
- Brandon Curtis - Brandon Curtis
- Dave 'Sri' Seah - Dave 'Sri' Seah
- djagoo - djagoo
@ -407,9 +414,6 @@ This project exists thanks to all the people who contribute. [[Contribute]](http
<h2 align="center">Special Thanks</h2> <h2 align="center">Special Thanks</h2>
![Algolia](https://js.wiki/legacy/logo_algolia.png)
[Algolia](https://www.algolia.com/) for providing access to their incredible search engine.
![Browserstack](https://js.wiki/legacy/logo_browserstack.png) ![Browserstack](https://js.wiki/legacy/logo_browserstack.png)
[Browserstack](https://www.browserstack.com/) for providing access to their great cross-browser testing tools. [Browserstack](https://www.browserstack.com/) for providing access to their great cross-browser testing tools.
@ -417,16 +421,16 @@ This project exists thanks to all the people who contribute. [[Contribute]](http
[Cloudflare](https://www.cloudflare.com/) for providing their great CDN, SSL and advanced networking services. [Cloudflare](https://www.cloudflare.com/) for providing their great CDN, SSL and advanced networking services.
![DigitalOcean](https://js.wiki/legacy/logo_digitalocean.png) ![DigitalOcean](https://js.wiki/legacy/logo_digitalocean.png)
[DigitalOcean](https://m.do.co/c/5f7445bfa4d0) for providing hosting of the Wiki.js documentation site. [DigitalOcean](https://m.do.co/c/5f7445bfa4d0) for providing hosting of the Wiki.js documentation site and APIs.
![Icons8](https://static.requarks.io/logo/icons8-text-h40.png) ![Icons8](https://static.requarks.io/logo/icons8-text-h40.png)
[Icons8](https://icons8.com/) for providing beautiful icon sets. [Icons8](https://icons8.com/) for providing access to their beautiful icon sets.
![Lokalise](https://static.requarks.io/logo/lokalise-text-h40.png) ![Lokalise](https://static.requarks.io/logo/lokalise-text-h40.png)
[Lokalise](https://lokalise.com/) for providing access to their great localization tool. [Lokalise](https://lokalise.com/) for providing access to their great localization tool.
![Netlify](https://js.wiki/legacy/logo_netlify.png) ![Netlify](https://js.wiki/legacy/logo_netlify.png)
[Netlify](https://www.netlify.com) for providing hosting for landings and blog websites. [Netlify](https://www.netlify.com) for providing hosting for our website.
![ngrok](https://static.requarks.io/logo/ngrok-h40.png) ![ngrok](https://static.requarks.io/logo/ngrok-h40.png)
[ngrok](https://ngrok.com) for providing access to their great HTTP tunneling services. [ngrok](https://ngrok.com) for providing access to their great HTTP tunneling services.

@ -144,7 +144,7 @@
//- ) //- )
//- v-divider.mt-3 //- v-divider.mt-3
v-switch( v-switch.mt-0(
inset inset
label='Comments' label='Comments'
color='indigo' color='indigo'
@ -164,6 +164,89 @@
//- disabled //- disabled
//- ) //- )
v-card.mt-5.animated.fadeInUp.wait-p6s
v-toolbar(color='primary', dark, dense, flat)
v-toolbar-title.subtitle-1 URL Handling
v-card-text
v-text-field(
outlined
:label='$t(`admin:general.pageExtensions`)'
v-model='config.pageExtensions'
prepend-icon='mdi-format-text-wrapping-overflow'
:hint='$t(`admin:general.pageExtensionsHint`)'
persistent-hint
)
v-card.mt-5.animated.fadeInUp.wait-p7s
v-toolbar(color='primary', dark, dense, flat)
v-toolbar-title.subtitle-1 {{$t('admin:general.editShortcuts')}}
v-card-text
v-switch.mt-0(
inset
:label='$t(`admin:general.editFab`)'
color='primary'
v-model='config.editFab'
persistent-hint
:hint='$t(`admin:general.editFabHint`)'
)
v-divider
.overline.grey--text.pa-4 {{$t('admin:general.editMenuBar')}}
.px-3.pb-3
v-switch.mt-0.ml-1(
inset
:label='$t(`admin:general.displayEditMenuBar`)'
color='primary'
v-model='config.editMenuBar'
persistent-hint
:hint='$t(`admin:general.displayEditMenuBarHint`)'
)
v-switch.mt-4.ml-1(
v-if='config.editMenuBar'
inset
:label='$t(`admin:general.displayEditMenuBtn`)'
color='primary'
v-model='config.editMenuBtn'
persistent-hint
:hint='$t(`admin:general.displayEditMenuBtnHint`)'
)
v-switch.mt-4.ml-1(
v-if='config.editMenuBar'
inset
:label='$t(`admin:general.displayEditMenuExternalBtn`)'
color='primary'
v-model='config.editMenuExternalBtn'
persistent-hint
:hint='$t(`admin:general.displayEditMenuExternalBtnHint`)'
)
template(v-if='config.editMenuBar && config.editMenuExternalBtn')
v-divider
.overline.grey--text.pa-4 External Edit Button
.px-3.pb-3
v-text-field(
outlined
:label='$t(`admin:general.editMenuExternalName`)'
v-model='config.editMenuExternalName'
prepend-icon='mdi-format-title'
:hint='$t(`admin:general.editMenuExternalNameHint`)'
persistent-hint
)
v-text-field.mt-3(
outlined
:label='$t(`admin:general.editMenuExternalIcon`)'
v-model='config.editMenuExternalIcon'
prepend-icon='mdi-dice-5'
:hint='$t(`admin:general.editMenuExternalIconHint`)'
persistent-hint
)
v-text-field.mt-3(
outlined
:label='$t(`admin:general.editMenuExternalUrl`)'
v-model='config.editMenuExternalUrl'
prepend-icon='mdi-near-me'
:hint='$t(`admin:general.editMenuExternalUrlHint`)'
persistent-hint
)
component(:is='activeModal') component(:is='activeModal')
</template> </template>
@ -202,7 +285,15 @@ export default {
featurePageRatings: false, featurePageRatings: false,
featurePageComments: false, featurePageComments: false,
featurePersonalWikis: false, featurePersonalWikis: false,
featureTinyPNG: false featureTinyPNG: false,
pageExtensions: '',
editFab: false,
editMenuBar: false,
editMenuBtn: false,
editMenuExternalBtn: false,
editMenuExternalName: '',
editMenuExternalIcon: '',
editMenuExternalUrl: ''
}, },
metaRobots: [ metaRobots: [
{ text: 'Index', value: 'index' }, { text: 'Index', value: 'index' },
@ -247,33 +338,49 @@ export default {
await this.$apollo.mutate({ await this.$apollo.mutate({
mutation: gql` mutation: gql`
mutation ( mutation (
$host: String! $host: String
$title: String! $title: String
$description: String! $description: String
$robots: [String]! $robots: [String]
$analyticsService: String! $analyticsService: String
$analyticsId: String! $analyticsId: String
$company: String! $company: String
$contentLicense: String! $contentLicense: String
$logoUrl: String! $logoUrl: String
$featurePageRatings: Boolean! $pageExtensions: String
$featurePageComments: Boolean! $featurePageRatings: Boolean
$featurePersonalWikis: Boolean! $featurePageComments: Boolean
$featurePersonalWikis: Boolean
$editFab: Boolean
$editMenuBar: Boolean
$editMenuBtn: Boolean
$editMenuExternalBtn: Boolean
$editMenuExternalName: String
$editMenuExternalIcon: String
$editMenuExternalUrl: String
) { ) {
site { site {
updateConfig( updateConfig(
host: $host, host: $host
title: $title, title: $title
description: $description, description: $description
robots: $robots, robots: $robots
analyticsService: $analyticsService, analyticsService: $analyticsService
analyticsId: $analyticsId, analyticsId: $analyticsId
company: $company, company: $company
contentLicense: $contentLicense, contentLicense: $contentLicense
logoUrl: $logoUrl, logoUrl: $logoUrl
featurePageRatings: $featurePageRatings, pageExtensions: $pageExtensions
featurePageComments: $featurePageComments, featurePageRatings: $featurePageRatings
featurePageComments: $featurePageComments
featurePersonalWikis: $featurePersonalWikis featurePersonalWikis: $featurePersonalWikis
editFab: $editFab
editMenuBar: $editMenuBar
editMenuBtn: $editMenuBtn
editMenuExternalBtn: $editMenuExternalBtn
editMenuExternalName: $editMenuExternalName
editMenuExternalIcon: $editMenuExternalIcon
editMenuExternalUrl: $editMenuExternalUrl
) { ) {
responseResult { responseResult {
succeeded succeeded
@ -295,9 +402,17 @@ export default {
company: _.get(this.config, 'company', ''), company: _.get(this.config, 'company', ''),
contentLicense: _.get(this.config, 'contentLicense', ''), contentLicense: _.get(this.config, 'contentLicense', ''),
logoUrl: _.get(this.config, 'logoUrl', ''), logoUrl: _.get(this.config, 'logoUrl', ''),
pageExtensions: _.get(this.config, 'pageExtensions', ''),
featurePageRatings: _.get(this.config, 'featurePageRatings', false), featurePageRatings: _.get(this.config, 'featurePageRatings', false),
featurePageComments: _.get(this.config, 'featurePageComments', false), featurePageComments: _.get(this.config, 'featurePageComments', false),
featurePersonalWikis: _.get(this.config, 'featurePersonalWikis', false) featurePersonalWikis: _.get(this.config, 'featurePersonalWikis', false),
editFab: _.get(this.config, 'editFab', false),
editMenuBar: _.get(this.config, 'editMenuBar', false),
editMenuBtn: _.get(this.config, 'editMenuBtn', false),
editMenuExternalBtn: _.get(this.config, 'editMenuExternalBtn', false),
editMenuExternalName: _.get(this.config, 'editMenuExternalName', ''),
editMenuExternalIcon: _.get(this.config, 'editMenuExternalIcon', ''),
editMenuExternalUrl: _.get(this.config, 'editMenuExternalUrl', '')
}, },
watchLoading (isLoading) { watchLoading (isLoading) {
this.$store.commit(`loading${isLoading ? 'Start' : 'Stop'}`, 'admin-site-update') this.$store.commit(`loading${isLoading ? 'Start' : 'Stop'}`, 'admin-site-update')
@ -347,9 +462,17 @@ export default {
company company
contentLicense contentLicense
logoUrl logoUrl
pageExtensions
featurePageRatings featurePageRatings
featurePageComments featurePageComments
featurePersonalWikis featurePersonalWikis
editFab
editMenuBar
editMenuBtn
editMenuExternalBtn
editMenuExternalName
editMenuExternalIcon
editMenuExternalUrl
} }
} }
} }

@ -214,8 +214,8 @@ export default {
return { return {
roles: [ roles: [
{ text: 'Read Pages', value: 'read:pages', icon: 'mdi-file-eye-outline' }, { text: 'Read Pages', value: 'read:pages', icon: 'mdi-file-eye-outline' },
{ text: 'Create Pages', value: 'write:pages', icon: 'mdi-file-plus-outline' }, { text: 'Create + Edit Pages', value: 'write:pages', icon: 'mdi-file-plus-outline' },
{ text: 'Edit + Move Pages', value: 'manage:pages', icon: 'mdi-file-document-edit-outline' }, { text: 'Rename / Move Pages', value: 'manage:pages', icon: 'mdi-file-document-edit-outline' },
{ text: 'Delete Pages', value: 'delete:pages', icon: 'mdi-file-remove-outline' }, { text: 'Delete Pages', value: 'delete:pages', icon: 'mdi-file-remove-outline' },
{ text: 'View Pages Source', value: 'read:source', icon: 'mdi-code-tags' }, { text: 'View Pages Source', value: 'read:source', icon: 'mdi-code-tags' },
{ text: 'View Pages History', value: 'read:history', icon: 'mdi-history' }, { text: 'View Pages History', value: 'read:history', icon: 'mdi-history' },

@ -57,6 +57,16 @@
:hint='$t(`admin:mail.smtpPortHint`)' :hint='$t(`admin:mail.smtpPortHint`)'
style='max-width: 300px;' style='max-width: 300px;'
) )
v-text-field(
outlined
v-model='config.name'
:label='$t(`admin:mail.smtpName`)'
required
:counter='255'
prepend-icon='mdi-server'
persistent-hint
:hint='$t(`admin:mail.smtpNameHint`)'
)
v-switch( v-switch(
v-model='config.secure' v-model='config.secure'
:label='$t(`admin:mail.smtpTLS`)' :label='$t(`admin:mail.smtpTLS`)'
@ -169,6 +179,7 @@ export default {
senderEmail: '', senderEmail: '',
host: '', host: '',
port: 0, port: 0,
name: '',
secure: false, secure: false,
verifySSL: false, verifySSL: false,
user: '', user: '',
@ -192,6 +203,7 @@ export default {
senderEmail: this.config.senderEmail || '', senderEmail: this.config.senderEmail || '',
host: this.config.host || '', host: this.config.host || '',
port: _.toSafeInteger(this.config.port) || 0, port: _.toSafeInteger(this.config.port) || 0,
name: this.config.name || '',
secure: this.config.secure || false, secure: this.config.secure || false,
verifySSL: this.config.verifySSL || false, verifySSL: this.config.verifySSL || false,
user: this.config.user || '', user: this.config.user || '',

@ -105,6 +105,9 @@ export default {
} else { } else {
this.searchIsLoading = true this.searchIsLoading = true
} }
},
results() {
this.cursor = 0
} }
}, },
mounted() { mounted() {
@ -153,6 +156,9 @@ export default {
skip() { skip() {
return !this.search || this.search.length < 2 return !this.search || this.search.length < 2
}, },
result() {
this.pagination = 1
},
update: (data) => _.get(data, 'pages.search', {}), update: (data) => _.get(data, 'pages.search', {}),
watchLoading (isLoading) { watchLoading (isLoading) {
this.searchIsLoading = isLoading this.searchIsLoading = isLoading

@ -1,30 +1,32 @@
mutation ( mutation (
$senderName: String!, $senderName: String!
$senderEmail: String!, $senderEmail: String!
$host: String!, $host: String!
$port: Int!, $port: Int!
$secure: Boolean!, $name: String!
$verifySSL: Boolean!, $secure: Boolean!
$user: String!, $verifySSL: Boolean!
$pass: String!, $user: String!
$useDKIM: Boolean!, $pass: String!
$dkimDomainName: String!, $useDKIM: Boolean!
$dkimKeySelector: String!, $dkimDomainName: String!
$dkimKeySelector: String!
$dkimPrivateKey: String! $dkimPrivateKey: String!
) { ) {
mail { mail {
updateConfig( updateConfig(
senderName: $senderName, senderName: $senderName
senderEmail: $senderEmail, senderEmail: $senderEmail
host: $host, host: $host
port: $port, port: $port
secure: $secure, name: $name
verifySSL: $verifySSL, secure: $secure
user: $user, verifySSL: $verifySSL
pass: $pass, user: $user
useDKIM: $useDKIM, pass: $pass
dkimDomainName: $dkimDomainName, useDKIM: $useDKIM
dkimKeySelector: $dkimKeySelector, dkimDomainName: $dkimDomainName
dkimKeySelector: $dkimKeySelector
dkimPrivateKey: $dkimPrivateKey dkimPrivateKey: $dkimPrivateKey
) { ) {
responseResult { responseResult {

@ -5,6 +5,7 @@
senderEmail senderEmail
host host
port port
name
secure secure
verifySSL verifySSL
user user

@ -30,6 +30,12 @@ html {
} }
} }
@media only screen and (min-width:960px) {
.v-application .v-footer {
padding-left: 272px
}
}
#root .v-application { #root .v-application {
.overline { .overline {
line-height: 1rem; line-height: 1rem;

@ -41,7 +41,16 @@ const state = {
manage: false manage: false
} }
}, },
commentsCount: 0 commentsCount: 0,
editShortcuts: {
editFab: false,
editMenuBar: false,
editMenuBtn: false,
editMenuExternalBtn: false,
editMenuExternalName: '',
editMenuExternalIcon: '',
editMenuExternalUrl: ''
}
} }
export default { export default {

@ -49,10 +49,28 @@
status-indicator.ml-3(negative, pulse) status-indicator.ml-3(negative, pulse)
v-divider v-divider
v-container.grey.pa-0(fluid, :class='$vuetify.theme.dark ? `darken-4-l3` : `lighten-4`') v-container.grey.pa-0(fluid, :class='$vuetify.theme.dark ? `darken-4-l3` : `lighten-4`')
v-row(no-gutters, align-content='center', style='height: 90px;') v-row.page-header-section(no-gutters, align-content='center', style='height: 90px;')
v-col.page-col-content.is-page-header(offset-xl='2', offset-lg='3', style='margin-top: auto; margin-bottom: auto;', :class='$vuetify.rtl ? `pr-4` : `pl-4`') v-col.page-col-content.is-page-header(offset-xl='2', offset-lg='3', style='margin-top: auto; margin-bottom: auto;', :class='$vuetify.rtl ? `pr-4` : `pl-4`')
.headline.grey--text(:class='$vuetify.theme.dark ? `text--lighten-2` : `text--darken-3`') {{title}} .headline.grey--text(:class='$vuetify.theme.dark ? `text--lighten-2` : `text--darken-3`') {{title}}
.caption.grey--text.text--darken-1 {{description}} .caption.grey--text.text--darken-1 {{description}}
.page-edit-shortcuts(v-if='editShortcutsObj.editMenuBar')
v-btn(
v-if='editShortcutsObj.editMenuBtn'
@click='pageEdit'
depressed
small
)
v-icon.mr-2(small) mdi-pencil
span.text-none {{$t(`common:actions.edit`)}}
v-btn(
v-if='editShortcutsObj.editMenuExternalBtn'
:href='editMenuExternalUrl'
target='_blank'
depressed
small
)
v-icon.mr-2(small) {{ editShortcutsObj.editMenuExternalIcon }}
span.text-none {{$t(`common:page.editExternal`, { name: editShortcutsObj.editMenuExternalName })}}
v-divider v-divider
v-container.pl-5.pt-4(fluid, grid-list-xl) v-container.pl-5.pt-4(fluid, grid-list-xl)
v-layout(row) v-layout(row)
@ -186,7 +204,7 @@
v-spacer v-spacer
v-flex.page-col-content(xs12, lg9, xl10) v-flex.page-col-content(xs12, lg9, xl10)
v-tooltip(:right='$vuetify.rtl', :left='!$vuetify.rtl', v-if='hasAnyPagePermissions') v-tooltip(:right='$vuetify.rtl', :left='!$vuetify.rtl', v-if='hasAnyPagePermissions && editShortcutsObj.editFab')
template(v-slot:activator='{ on: onEditActivator }') template(v-slot:activator='{ on: onEditActivator }')
v-speed-dial( v-speed-dial(
v-model='pageEditFab' v-model='pageEditFab'
@ -440,6 +458,14 @@ export default {
commentsExternal: { commentsExternal: {
type: Boolean, type: Boolean,
default: false default: false
},
editShortcuts: {
type: String,
default: ''
},
filename: {
type: String,
default: ''
} }
}, },
data() { data() {
@ -478,6 +504,7 @@ export default {
isAuthenticated: get('user/authenticated'), isAuthenticated: get('user/authenticated'),
commentsCount: get('page/commentsCount'), commentsCount: get('page/commentsCount'),
commentsPerms: get('page/effectivePermissions@comments'), commentsPerms: get('page/effectivePermissions@comments'),
editShortcutsObj: get('page/editShortcuts'),
rating: { rating: {
get () { get () {
return 3.5 return 3.5
@ -519,7 +546,14 @@ export default {
return this.hasAdminPermission || this.hasWritePagesPermission || this.hasManagePagesPermission || return this.hasAdminPermission || this.hasWritePagesPermission || this.hasManagePagesPermission ||
this.hasDeletePagesPermission || this.hasReadSourcePermission || this.hasReadHistoryPermission this.hasDeletePagesPermission || this.hasReadSourcePermission || this.hasReadHistoryPermission
}, },
printView: sync('site/printView') printView: sync('site/printView'),
editMenuExternalUrl () {
if (this.editShortcutsObj.editMenuBar && this.editShortcutsObj.editMenuExternalBtn) {
return this.editShortcutsObj.editMenuExternalUrl.replace('{filename}', this.filename)
} else {
return ''
}
}
}, },
created() { created() {
this.$store.set('page/authorId', this.authorId) this.$store.set('page/authorId', this.authorId)
@ -537,6 +571,9 @@ export default {
if (this.effectivePermissions) { if (this.effectivePermissions) {
this.$store.set('page/effectivePermissions', JSON.parse(Buffer.from(this.effectivePermissions, 'base64').toString())) this.$store.set('page/effectivePermissions', JSON.parse(Buffer.from(this.effectivePermissions, 'base64').toString()))
} }
if (this.editShortcuts) {
this.$store.set('page/editShortcuts', JSON.parse(Buffer.from(this.editShortcuts, 'base64').toString()))
}
this.$store.set('page/mode', 'view') this.$store.set('page/mode', 'view')
}, },
@ -676,4 +713,43 @@ export default {
display: none; display: none;
} }
.page-header-section {
position: relative;
.page-edit-shortcuts {
position: absolute;
bottom: -14px;
right: 10px;
.v-btn {
border-right: 1px solid #DDD !important;
border-bottom: 1px solid #DDD !important;
border-radius: 0;
color: #777;
background-color: #FFF !important;
@at-root .theme--dark & {
background-color: #222 !important;
border-right-color: #444 !important;
border-bottom-color: #444 !important;
color: #CCC;
}
.v-icon {
color: mc('blue', '700');
}
&:first-child {
border-top-left-radius: 5px;
border-bottom-left-radius: 5px;
}
&:last-child {
border-top-right-radius: 5px;
border-bottom-right-radius: 5px;
}
}
}
}
</style> </style>

@ -1036,4 +1036,8 @@
.comments-container { .comments-container {
display: none; display: none;
} }
.page-edit-shortcuts {
display: none;
}
} }

@ -2,7 +2,7 @@
{{- if .Values.ingress.enabled }} {{- if .Values.ingress.enabled }}
{{- range $host := .Values.ingress.hosts }} {{- range $host := .Values.ingress.hosts }}
{{- range .paths }} {{- range .paths }}
http{{ if $.Values.ingress.tls }}s{{ end }}://{{ $host.host }}{{ . }} http{{ if $.Values.ingress.tls }}s{{ end }}://{{ $host.host }}{{ .path }}
{{- end }} {{- end }}
{{- end }} {{- end }}
{{- else if contains "NodePort" .Values.service.type }} {{- else if contains "NodePort" .Values.service.type }}

@ -41,6 +41,12 @@ spec:
env: env:
- name: DB_TYPE - name: DB_TYPE
value: postgres value: postgres
{{- if (.Values.externalPostgresql).databaseURL }}
- name: DATABASE_URL
value: {{ .Values.externalPostgresql.databaseURL }}
- name: NODE_TLS_REJECT_UNAUTHORIZED
value: {{ default "1" .Values.externalPostgresql.NODE_TLS_REJECT_UNAUTHORIZED | quote }}
{{- else }}
- name: DB_HOST - name: DB_HOST
value: {{ template "wiki.postgresql.host" . }} value: {{ template "wiki.postgresql.host" . }}
- name: DB_PORT - name: DB_PORT
@ -62,6 +68,7 @@ spec:
name: {{ template "wiki.postgresql.secret" . }} name: {{ template "wiki.postgresql.secret" . }}
{{- end }} {{- end }}
key: {{ template "wiki.postgresql.secretKey" . }} key: {{ template "wiki.postgresql.secretKey" . }}
{{- end }}
- name: HA_ACTIVE - name: HA_ACTIVE
value: {{ .Values.replicaCount | int | le 2 | quote }} value: {{ .Values.replicaCount | int | le 2 | quote }}
{{- with .Values.volumeMounts }} {{- with .Values.volumeMounts }}
@ -76,6 +83,8 @@ spec:
{{- toYaml .Values.livenessProbe | nindent 12 }} {{- toYaml .Values.livenessProbe | nindent 12 }}
readinessProbe: readinessProbe:
{{- toYaml .Values.readinessProbe | nindent 12 }} {{- toYaml .Values.readinessProbe | nindent 12 }}
startupProbe:
{{- toYaml .Values.startupProbe | nindent 12 }}
resources: resources:
{{- toYaml .Values.resources | nindent 12 }} {{- toYaml .Values.resources | nindent 12 }}
{{- with .Values.nodeSelector }} {{- with .Values.nodeSelector }}

@ -32,6 +32,16 @@ readinessProbe:
path: /healthz path: /healthz
port: http port: http
startupProbe:
initialDelaySeconds: 15
periodSeconds: 5
timeoutSeconds: 5
successThreshold: 1
failureThreshold: 60
httpGet:
path: /healthz
port: http
podSecurityContext: {} podSecurityContext: {}
# fsGroup: 2000 # fsGroup: 2000
@ -102,6 +112,13 @@ sideload:
# - name: HTTPS_PROXY # - name: HTTPS_PROXY
# value: http://my.proxy.com:3128 # value: http://my.proxy.com:3128
## This will override the postgresql chart values
# externalPostgresql:
# # note: ?sslmode=require => ?ssl=true
# databaseURL: postgresql://postgres:postgres@postgres:5432/wiki?ssl=true
# # For self signed CAs, like DigitalOcean
# NODE_TLS_REJECT_UNAUTHORIZED: "0"
## Configuration values for the postgresql dependency. ## Configuration values for the postgresql dependency.
## ref: https://github.com/kubernetes/charts/blob/master/stable/postgresql/README.md ## ref: https://github.com/kubernetes/charts/blob/master/stable/postgresql/README.md
## ##

@ -45,6 +45,10 @@ defaults:
company: '' company: ''
contentLicense: '' contentLicense: ''
logoUrl: https://static.requarks.io/logo/wikijs-butterfly.svg logoUrl: https://static.requarks.io/logo/wikijs-butterfly.svg
pageExtensions:
- md
- html
- txt
mail: mail:
host: '' host: ''
secure: true secure: true
@ -63,6 +67,14 @@ defaults:
audience: 'urn:wiki.js' audience: 'urn:wiki.js'
tokenExpiration: '30m' tokenExpiration: '30m'
tokenRenewal: '14d' tokenRenewal: '14d'
editShortcuts:
editFab: true
editMenuBar: false
editMenuBtn: true
editMenuExternalBtn: true
editMenuExternalName: 'GitHub'
editMenuExternalIcon: 'mdi-github'
editMenuExternalUrl: 'https://github.com/org/repo/blob/main/{filename}'
features: features:
featurePageRatings: true featurePageRatings: true
featurePageComments: true featurePageComments: true
@ -152,8 +164,4 @@ reservedPaths:
- img - img
- js - js
- svg - svg
pageExtensions:
- md
- html
- txt
# --------------------------------- # ---------------------------------

@ -414,7 +414,7 @@ router.get('/_userav/:uid', async (req, res, next) => {
* View document / asset * View document / asset
*/ */
router.get('/*', async (req, res, next) => { router.get('/*', async (req, res, next) => {
const stripExt = _.some(WIKI.data.pageExtensions, ext => _.endsWith(req.path, `.${ext}`)) const stripExt = _.some(WIKI.config.pageExtensions, ext => _.endsWith(req.path, `.${ext}`))
const pageArgs = pageHelper.parsePath(req.path, { stripExt }) const pageArgs = pageHelper.parsePath(req.path, { stripExt })
const isPage = (stripExt || pageArgs.path.indexOf('.') === -1) const isPage = (stripExt || pageArgs.path.indexOf('.') === -1)
@ -542,13 +542,18 @@ router.get('/*', async (req, res, next) => {
}) })
} }
// -> Page Filename (for edit on external repo button)
let pageFilename = WIKI.config.lang.namespacing ? `${pageArgs.locale}/${page.path}` : page.path
pageFilename += page.contentType === 'markdown' ? '.md' : '.html'
// -> Render view // -> Render view
res.render('page', { res.render('page', {
page, page,
sidebar, sidebar,
injectCode, injectCode,
comments: commentTmpl, comments: commentTmpl,
effectivePermissions effectivePermissions,
pageFilename
}) })
} }
} else if (pageArgs.path === 'home') { } else if (pageArgs.path === 'home') {

@ -13,6 +13,7 @@ module.exports = {
let conf = { let conf = {
host: WIKI.config.mail.host, host: WIKI.config.mail.host,
port: WIKI.config.mail.port, port: WIKI.config.mail.port,
name: WIKI.config.mail.name,
secure: WIKI.config.mail.secure, secure: WIKI.config.mail.secure,
tls: { tls: {
rejectUnauthorized: !(WIKI.config.mail.verifySSL === false) rejectUnauthorized: !(WIKI.config.mail.verifySSL === false)

@ -49,6 +49,7 @@ module.exports = {
senderEmail: args.senderEmail, senderEmail: args.senderEmail,
host: args.host, host: args.host,
port: args.port, port: args.port,
name: args.name,
secure: args.secure, secure: args.secure,
verifySSL: args.verifySSL, verifySSL: args.verifySSL,
user: args.user, user: args.user,

@ -18,7 +18,9 @@ module.exports = {
company: WIKI.config.company, company: WIKI.config.company,
contentLicense: WIKI.config.contentLicense, contentLicense: WIKI.config.contentLicense,
logoUrl: WIKI.config.logoUrl, logoUrl: WIKI.config.logoUrl,
pageExtensions: WIKI.config.pageExtensions.join(', '),
...WIKI.config.seo, ...WIKI.config.seo,
...WIKI.config.editShortcuts,
...WIKI.config.features, ...WIKI.config.features,
...WIKI.config.security, ...WIKI.config.security,
authAutoLogin: WIKI.config.auth.autoLogin, authAutoLogin: WIKI.config.auth.autoLogin,
@ -62,6 +64,10 @@ module.exports = {
WIKI.config.logoUrl = _.trim(args.logoUrl) WIKI.config.logoUrl = _.trim(args.logoUrl)
} }
if (args.hasOwnProperty('pageExtensions')) {
WIKI.config.pageExtensions = _.trim(args.pageExtensions).split(',').map(p => p.trim().toLowerCase()).filter(p => p !== '')
}
WIKI.config.seo = { WIKI.config.seo = {
description: _.get(args, 'description', WIKI.config.seo.description), description: _.get(args, 'description', WIKI.config.seo.description),
robots: _.get(args, 'robots', WIKI.config.seo.robots), robots: _.get(args, 'robots', WIKI.config.seo.robots),
@ -79,6 +85,16 @@ module.exports = {
tokenRenewal: _.get(args, 'authJwtRenewablePeriod', WIKI.config.auth.tokenRenewal) tokenRenewal: _.get(args, 'authJwtRenewablePeriod', WIKI.config.auth.tokenRenewal)
} }
WIKI.config.editShortcuts = {
editFab: _.get(args, 'editFab', WIKI.config.editShortcuts.editFab),
editMenuBar: _.get(args, 'editMenuBar', WIKI.config.editShortcuts.editMenuBar),
editMenuBtn: _.get(args, 'editMenuBtn', WIKI.config.editShortcuts.editMenuBtn),
editMenuExternalBtn: _.get(args, 'editMenuExternalBtn', WIKI.config.editShortcuts.editMenuExternalBtn),
editMenuExternalName: _.get(args, 'editMenuExternalName', WIKI.config.editShortcuts.editMenuExternalName),
editMenuExternalIcon: _.get(args, 'editMenuExternalIcon', WIKI.config.editShortcuts.editMenuExternalIcon),
editMenuExternalUrl: _.get(args, 'editMenuExternalUrl', WIKI.config.editShortcuts.editMenuExternalUrl)
}
WIKI.config.features = { WIKI.config.features = {
featurePageRatings: _.get(args, 'featurePageRatings', WIKI.config.features.featurePageRatings), featurePageRatings: _.get(args, 'featurePageRatings', WIKI.config.features.featurePageRatings),
featurePageComments: _.get(args, 'featurePageComments', WIKI.config.features.featurePageComments), featurePageComments: _.get(args, 'featurePageComments', WIKI.config.features.featurePageComments),
@ -104,7 +120,7 @@ module.exports = {
forceDownload: _.get(args, 'uploadForceDownload', WIKI.config.uploads.forceDownload) forceDownload: _.get(args, 'uploadForceDownload', WIKI.config.uploads.forceDownload)
} }
await WIKI.configSvc.saveToDb(['host', 'title', 'company', 'contentLicense', 'seo', 'logoUrl', 'auth', 'features', 'security', 'uploads']) await WIKI.configSvc.saveToDb(['host', 'title', 'company', 'contentLicense', 'seo', 'logoUrl', 'pageExtensions', 'auth', 'editShortcuts', 'features', 'security', 'uploads'])
if (WIKI.config.security.securityTrustProxy) { if (WIKI.config.security.securityTrustProxy) {
WIKI.app.enable('trust proxy') WIKI.app.enable('trust proxy')

@ -32,6 +32,7 @@ type MailMutation {
senderEmail: String! senderEmail: String!
host: String! host: String!
port: Int! port: Int!
name: String!
secure: Boolean! secure: Boolean!
verifySSL: Boolean! verifySSL: Boolean!
user: String! user: String!
@ -48,16 +49,17 @@ type MailMutation {
# ----------------------------------------------- # -----------------------------------------------
type MailConfig { type MailConfig {
senderName: String! senderName: String
senderEmail: String! senderEmail: String
host: String! host: String
port: Int! port: Int
secure: Boolean! name: String
verifySSL: Boolean! secure: Boolean
user: String! verifySSL: Boolean
pass: String! user: String
useDKIM: Boolean! pass: String
dkimDomainName: String! useDKIM: Boolean
dkimKeySelector: String! dkimDomainName: String
dkimPrivateKey: String! dkimKeySelector: String
dkimPrivateKey: String
} }

@ -33,6 +33,7 @@ type SiteMutation {
company: String company: String
contentLicense: String contentLicense: String
logoUrl: String logoUrl: String
pageExtensions: String
authAutoLogin: Boolean authAutoLogin: Boolean
authEnforce2FA: Boolean authEnforce2FA: Boolean
authHideLocal: Boolean authHideLocal: Boolean
@ -40,6 +41,13 @@ type SiteMutation {
authJwtAudience: String authJwtAudience: String
authJwtExpiration: String authJwtExpiration: String
authJwtRenewablePeriod: String authJwtRenewablePeriod: String
editFab: Boolean
editMenuBar: Boolean
editMenuBtn: Boolean
editMenuExternalBtn: Boolean
editMenuExternalName: String
editMenuExternalIcon: String
editMenuExternalUrl: String
featurePageRatings: Boolean featurePageRatings: Boolean
featurePageComments: Boolean featurePageComments: Boolean
featurePersonalWikis: Boolean featurePersonalWikis: Boolean
@ -74,6 +82,7 @@ type SiteConfig {
company: String company: String
contentLicense: String contentLicense: String
logoUrl: String logoUrl: String
pageExtensions: String
authAutoLogin: Boolean authAutoLogin: Boolean
authEnforce2FA: Boolean authEnforce2FA: Boolean
authHideLocal: Boolean authHideLocal: Boolean
@ -81,6 +90,13 @@ type SiteConfig {
authJwtAudience: String authJwtAudience: String
authJwtExpiration: String authJwtExpiration: String
authJwtRenewablePeriod: String authJwtRenewablePeriod: String
editFab: Boolean
editMenuBar: Boolean
editMenuBtn: Boolean
editMenuExternalBtn: Boolean
editMenuExternalName: String
editMenuExternalIcon: String
editMenuExternalUrl: String
featurePageRatings: Boolean featurePageRatings: Boolean
featurePageComments: Boolean featurePageComments: Boolean
featurePersonalWikis: Boolean featurePersonalWikis: Boolean

@ -38,6 +38,9 @@ WIKI.kernel.init()
// Register exit handler // Register exit handler
// ---------------------------------------- // ----------------------------------------
process.on('SIGTERM', () => {
WIKI.kernel.shutdown()
})
process.on('SIGINT', () => { process.on('SIGINT', () => {
WIKI.kernel.shutdown() WIKI.kernel.shutdown()
}) })

@ -148,6 +148,7 @@ module.exports = class Page extends Model {
isPublished: 'boolean', isPublished: 'boolean',
publishEndDate: 'string', publishEndDate: 'string',
publishStartDate: 'string', publishStartDate: 'string',
contentType: 'string',
render: 'string', render: 'string',
tags: [ tags: [
{ {
@ -787,7 +788,7 @@ module.exports = class Page extends Model {
* @returns {Promise} Promise with no value * @returns {Promise} Promise with no value
*/ */
static async deletePage(opts) { static async deletePage(opts) {
const page = await WIKI.models.pages.getPageFromDb(_.has(opts, 'id') ? opts.id : opts); const page = await WIKI.models.pages.getPageFromDb(_.has(opts, 'id') ? opts.id : opts)
if (!page) { if (!page) {
throw new WIKI.Error.PageNotFound() throw new WIKI.Error.PageNotFound()
} }
@ -1067,6 +1068,7 @@ module.exports = class Page extends Model {
isPublished: page.isPublished === 1 || page.isPublished === true, isPublished: page.isPublished === 1 || page.isPublished === true,
publishEndDate: page.publishEndDate, publishEndDate: page.publishEndDate,
publishStartDate: page.publishStartDate, publishStartDate: page.publishStartDate,
contentType: page.contentType,
render: page.render, render: page.render,
tags: page.tags.map(t => _.pick(t, ['tag', 'title'])), tags: page.tags.map(t => _.pick(t, ['tag', 'title'])),
title: page.title, title: page.title,

@ -27,5 +27,8 @@ module.exports = {
} }
} }
)) ))
},
logout (conf) {
return `https://${conf.domain}/v2/logout?${new URLSearchParams({ client_id: conf.clientId, returnTo: WIKI.config.host }).toString()}`
} }
} }

@ -1,3 +1,4 @@
const _ = require('lodash')
/* global WIKI */ /* global WIKI */
// ------------------------------------ // ------------------------------------
@ -10,15 +11,24 @@ module.exports = {
init (passport, conf) { init (passport, conf) {
passport.use(conf.key, passport.use(conf.key,
new CASStrategy({ new CASStrategy({
ssoBaseURL: conf.ssoBaseURL, version: conf.casVersion,
serverBaseURL: conf.serverBaseURL, ssoBaseURL: conf.casUrl,
serverBaseURL: conf.baseUrl,
serviceURL: conf.callbackURL,
passReqToCallback: true passReqToCallback: true
}, async (req, profile, cb) => { }, async (req, profile, cb) => {
try { try {
const user = await WIKI.models.users.processProfile({ const user = await WIKI.models.users.processProfile({
providerKey: req.params.strategy, providerKey: req.params.strategy,
profile profile: {
...profile,
id: _.get(profile.attributes, conf.uniqueIdAttribute, profile.user),
email: _.get(profile.attributes, conf.emailAttribute),
name: _.get(profile.attributes, conf.displayNameAttribute, profile.user),
picture: ''
}
}) })
cb(null, user) cb(null, user)
} catch (err) { } catch (err) {
cb(err, null) cb(err, null)

@ -6,6 +6,37 @@ logo: https://static.requarks.io/logo/cas.svg
color: green darken-2 color: green darken-2
website: https://apereo.github.io/cas/ website: https://apereo.github.io/cas/
useForm: false useForm: false
isAvailable: true
props: props:
ssoBaseURL: String baseUrl:
serverBaseURL: String type: String
title: Base URL
hint: 'Base-URL of your WikiJS (for example: https://wiki.example.com)'
order: 1
casUrl:
type: String
title: URL to the CAS Server
hint: 'Base-URL of the CAS server, including context path. (for example: https://login.company.com/cas)'
order: 2
casVersion:
type: String
title: CAS Version
hint: 'The version of CAS to use'
order: 3
enum:
- CAS3.0
- CAS1.0
default: 'CAS3.0'
emailAttribute:
type: String
title: Attribute key which contains the users email
default: email
order: 4
displayNameAttribute:
type: String
title: Attribute key which contains the users display name (leave empty if there is none)
order: 5
uniqueIdAttribute:
type: String
title: Attribute key which contains the unique identifier of a user. (if empty, username will be used)
order: 6

@ -1,3 +1,5 @@
const bcrypt = require('bcryptjs-then')
/* global WIKI */ /* global WIKI */
// ------------------------------------ // ------------------------------------
@ -28,6 +30,9 @@ module.exports = {
done(null, user) done(null, user)
} }
} else { } else {
// Fake verify password to mask timing differences
await bcrypt.compare((Math.random() + 1).toString(36), '$2a$12$irXbAcQSY59pcQQfNQpY8uyhfSw48nzDikAmr60drI501nR.PuBx2')
done(new WIKI.Error.AuthLoginFailed(), null) done(new WIKI.Error.AuthLoginFailed(), null)
} }
} catch (err) { } catch (err) {

@ -29,6 +29,19 @@ module.exports = {
email: _.get(profile, '_json.' + conf.emailClaim) email: _.get(profile, '_json.' + conf.emailClaim)
} }
}) })
if (conf.mapGroups) {
const groups = _.get(profile, '_json.' + conf.groupsClaim)
if (groups && _.isArray(groups)) {
const currentGroups = (await user.$relatedQuery('groups').select('groups.id')).groups.map(g => g.id)
const expectedGroups = Object.values(WIKI.auth.groups).filter(g => groups.includes(g.name)).map(g => g.id)
for (const groupId of _.difference(expectedGroups, currentGroups)) {
await user.$relatedQuery('groups').relate(groupId)
}
for (const groupId of _.difference(currentGroups, expectedGroups)) {
await user.$relatedQuery('groups').unrelate(groupId)
}
}
}
cb(null, user) cb(null, user)
} catch (err) { } catch (err) {
cb(err, null) cb(err, null)

@ -49,8 +49,21 @@ props:
default: email default: email
maxWidth: 500 maxWidth: 500
order: 7 order: 7
mapGroups:
type: Boolean
title: Map Groups
hint: Map groups matching names from the groups claim value
default: false
order: 8
groupsClaim:
type: String
title: Groups Claim
hint: Field containing the group names
default: groups
maxWidth: 500
order: 9
logoutURL: logoutURL:
type: String type: String
title: Logout URL title: Logout URL
hint: (optional) Logout URL on the OAuth2 provider where the user will be redirected to complete the logout process. hint: (optional) Logout URL on the OAuth2 provider where the user will be redirected to complete the logout process.
order: 8 order: 10

@ -14,14 +14,14 @@ module.exports = {
callbackUrl: conf.callbackURL, callbackUrl: conf.callbackURL,
entryPoint: conf.entryPoint, entryPoint: conf.entryPoint,
issuer: conf.issuer, issuer: conf.issuer,
cert: _.split(conf.cert || '', '|'), cert: (conf.cert || '').split('|'),
signatureAlgorithm: conf.signatureAlgorithm, signatureAlgorithm: conf.signatureAlgorithm,
digestAlgorithm: conf.digestAlgorithm, digestAlgorithm: conf.digestAlgorithm,
identifierFormat: conf.identifierFormat, identifierFormat: conf.identifierFormat,
wantAssertionsSigned: conf.wantAssertionsSigned, wantAssertionsSigned: conf.wantAssertionsSigned,
acceptedClockSkewMs: _.toSafeInteger(conf.acceptedClockSkewMs), acceptedClockSkewMs: _.toSafeInteger(conf.acceptedClockSkewMs),
disableRequestedAuthnContext: conf.disableRequestedAuthnContext, disableRequestedAuthnContext: conf.disableRequestedAuthnContext,
authnContext: _.split(conf.authnContext, '|'), authnContext: (conf.authnContext || '').split('|'),
racComparison: conf.racComparison, racComparison: conf.racComparison,
forceAuthn: conf.forceAuthn, forceAuthn: conf.forceAuthn,
passive: conf.passive, passive: conf.passive,

@ -20,28 +20,37 @@ props:
title: Host(s) title: Host(s)
hint: Comma-separated list of Elasticsearch hosts to connect to, including the port, username and password if necessary. (e.g. http://localhost:9200, https://user:pass@es1.example.com:9200) hint: Comma-separated list of Elasticsearch hosts to connect to, including the port, username and password if necessary. (e.g. http://localhost:9200, https://user:pass@es1.example.com:9200)
order: 2 order: 2
verifyTLSCertificate:
title: Verify TLS Certificate
type: Boolean
default: true
order: 3
tlsCertPath:
title: TLS Certificate Path
type: String
hint: Absolute path to the TLS certificate on the server.
order: 4
indexName: indexName:
type: String type: String
title: Index Name title: Index Name
hint: The index name to use during creation hint: The index name to use during creation
default: wiki default: wiki
order: 3 order: 5
analyzer: analyzer:
type: String type: String
title: Analyzer title: Analyzer
hint: 'The token analyzer in elasticsearch' hint: 'The token analyzer in elasticsearch'
default: simple default: simple
order: 4 order: 6
sniffOnStart: sniffOnStart:
type: Boolean type: Boolean
title: Sniff on start title: Sniff on start
hint: 'Should Wiki.js attempt to detect the rest of the cluster on first connect? (Default: off)' hint: 'Should Wiki.js attempt to detect the rest of the cluster on first connect? (Default: off)'
default: false default: false
order: 5 order: 7
sniffInterval: sniffInterval:
type: Number type: Number
title: Sniff Interval title: Sniff Interval
hint: '0 = disabled, Interval in seconds to check for updated list of nodes in cluster. (Default: 0)' hint: '0 = disabled, Interval in seconds to check for updated list of nodes in cluster. (Default: 0)'
default: 0 default: 0
order: 6 order: 8

@ -1,6 +1,7 @@
const _ = require('lodash') const _ = require('lodash')
const stream = require('stream') const stream = require('stream')
const Promise = require('bluebird') const Promise = require('bluebird')
const fs = require('fs')
const pipeline = Promise.promisify(stream.pipeline) const pipeline = Promise.promisify(stream.pipeline)
/* global WIKI */ /* global WIKI */
@ -24,6 +25,7 @@ module.exports = {
nodes: this.config.hosts.split(',').map(_.trim), nodes: this.config.hosts.split(',').map(_.trim),
sniffOnStart: this.config.sniffOnStart, sniffOnStart: this.config.sniffOnStart,
sniffInterval: (this.config.sniffInterval > 0) ? this.config.sniffInterval : false, sniffInterval: (this.config.sniffInterval > 0) ? this.config.sniffInterval : false,
ssl: getTlsOptions(this.config),
name: 'wiki-js' name: 'wiki-js'
}) })
break break
@ -33,6 +35,7 @@ module.exports = {
nodes: this.config.hosts.split(',').map(_.trim), nodes: this.config.hosts.split(',').map(_.trim),
sniffOnStart: this.config.sniffOnStart, sniffOnStart: this.config.sniffOnStart,
sniffInterval: (this.config.sniffInterval > 0) ? this.config.sniffInterval : false, sniffInterval: (this.config.sniffInterval > 0) ? this.config.sniffInterval : false,
ssl: getTlsOptions(this.config),
name: 'wiki-js' name: 'wiki-js'
}) })
break break
@ -351,3 +354,21 @@ module.exports = {
WIKI.logger.info(`(SEARCH/ELASTICSEARCH) Index rebuilt successfully.`) WIKI.logger.info(`(SEARCH/ELASTICSEARCH) Index rebuilt successfully.`)
} }
} }
function getTlsOptions(conf) {
if (!conf.tlsCertPath) {
return {
rejectUnauthorized: conf.verifyTLSCertificate
}
}
const caList = []
if (conf.verifyTLSCertificate) {
caList.push(fs.readFileSync(conf.tlsCertPath))
}
return {
rejectUnauthorized: conf.verifyTLSCertificate,
ca: caList
}
}

@ -41,4 +41,4 @@ props:
actions: actions:
- handler: exportAll - handler: exportAll
label: Export All label: Export All
hint: Output all content from the DB to Azure Blog Storage, overwriting any existing data. If you enabled Azure Blog Storage after content was created or you temporarily disabled it, you'll want to execute this action to add the missing content. hint: Output all content from the DB to Azure Blob Storage, overwriting any existing data. If you enabled Azure Blob Storage after content was created or you temporarily disabled it, you'll want to execute this action to add the missing content.

@ -103,6 +103,7 @@ module.exports = () => {
senderEmail: '', senderEmail: '',
host: '', host: '',
port: 465, port: 465,
name: '',
secure: true, secure: true,
verifySSL: true, verifySSL: true,
user: '', user: '',

@ -29,6 +29,8 @@ block body
comments-enabled=config.features.featurePageComments comments-enabled=config.features.featurePageComments
effective-permissions=Buffer.from(JSON.stringify(effectivePermissions)).toString('base64') effective-permissions=Buffer.from(JSON.stringify(effectivePermissions)).toString('base64')
comments-external=comments.codeTemplate comments-external=comments.codeTemplate
edit-shortcuts=Buffer.from(JSON.stringify(config.editShortcuts)).toString('base64')
filename=pageFilename
) )
template(slot='contents') template(slot='contents')
div!= page.render div!= page.render

Loading…
Cancel
Save