diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md index 6f8041d7..824c3e9d 100644 --- a/.github/CONTRIBUTING.md +++ b/.github/CONTRIBUTING.md @@ -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. -: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*. diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index a5b6702a..d38dea8e 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -358,6 +358,23 @@ jobs: env: SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }} 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: name: Build DigitalOcean Image diff --git a/README.md b/README.md index b1d5ac24..9a9480ca 100644 --- a/README.md +++ b/README.md @@ -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) [![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) -[![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) -[![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/) -[![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 @@ -39,7 +41,7 @@
-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 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 - Akira Suenami ([@a-suenami](https://github.com/a-suenami)) +- Armin Reiter ([@arminreiter](https://github.com/arminreiter)) - 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)) - Charlie Schliesser ([@charlie-s](https://github.com/charlie-s)) - Cloud Data Hosting LLC ([@CloudDataHostingLLC](https://github.com/CloudDataHostingLLC)) +- Cole Manning ([@RVRX](https://github.com/RVRX)) - CrazyMarvin ([@CrazyMarvin](https://github.com/CrazyMarvin)) - David Christian Holin ([@SirGibihm](https://github.com/SirGibihm)) - 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)) - Marcilio Leite Neto ([@marclneto](https://github.com/marclneto)) - Mattias Johnson ([@mattiasJohnson](https://github.com/mattiasJohnson)) -- Max Ricketts-Uy ([@MaxRickettsUy](https://github.com/MaxRickettsUy)) +- Max Ricketts-Uy ([@MaxRickettsUy](https://github.com/MaxRickettsUy)) - Mickael Asseline ([@PAPAMICA](https://github.com/PAPAMICA)) - Mitchell Rowton ([@mrowton](https://github.com/mrowton)) - 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)) - Philipp Schmitt ([@pschmitt](https://github.com/pschmitt)) - Robert Lanzke ([@winkelement](https://github.com/winkelement)) +- Ruizhe Li ([@liruizhe1995](https://github.com/liruizhe1995)) - Sam Martin ([@ABitMoreDepth](https://github.com/ABitMoreDepth)) - Sean Coffey ([@seanecoffey](https://github.com/seanecoffey)) - 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)) - magicpotato ([@fortheday](https://github.com/fortheday)) - motoacs ([@motoacs](https://github.com/motoacs)) +- muzian666 ([@muzian666](https://github.com/muzian666)) - rburckner ([@rburckner](https://github.com/rburckner)) - scorpion ([@scorpion](https://github.com/scorpion)) - 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 Zen - Arti Zirk +- Ave - Brandon Curtis - Dave 'Sri' Seah - djagoo @@ -407,9 +414,6 @@ This project exists thanks to all the people who contribute. [[Contribute]](http

Special Thanks

-![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://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. ![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://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://lokalise.com/) for providing access to their great localization tool. ![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://ngrok.com) for providing access to their great HTTP tunneling services. diff --git a/client/components/admin/admin-general.vue b/client/components/admin/admin-general.vue index 24829c8c..8804ee39 100644 --- a/client/components/admin/admin-general.vue +++ b/client/components/admin/admin-general.vue @@ -144,7 +144,7 @@ //- ) //- v-divider.mt-3 - v-switch( + v-switch.mt-0( inset label='Comments' color='indigo' @@ -164,6 +164,89 @@ //- 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') @@ -202,7 +285,15 @@ export default { featurePageRatings: false, featurePageComments: false, featurePersonalWikis: false, - featureTinyPNG: false + featureTinyPNG: false, + pageExtensions: '', + editFab: false, + editMenuBar: false, + editMenuBtn: false, + editMenuExternalBtn: false, + editMenuExternalName: '', + editMenuExternalIcon: '', + editMenuExternalUrl: '' }, metaRobots: [ { text: 'Index', value: 'index' }, @@ -247,33 +338,49 @@ export default { await this.$apollo.mutate({ mutation: gql` mutation ( - $host: String! - $title: String! - $description: String! - $robots: [String]! - $analyticsService: String! - $analyticsId: String! - $company: String! - $contentLicense: String! - $logoUrl: String! - $featurePageRatings: Boolean! - $featurePageComments: Boolean! - $featurePersonalWikis: Boolean! + $host: String + $title: String + $description: String + $robots: [String] + $analyticsService: String + $analyticsId: String + $company: String + $contentLicense: String + $logoUrl: String + $pageExtensions: String + $featurePageRatings: Boolean + $featurePageComments: Boolean + $featurePersonalWikis: Boolean + $editFab: Boolean + $editMenuBar: Boolean + $editMenuBtn: Boolean + $editMenuExternalBtn: Boolean + $editMenuExternalName: String + $editMenuExternalIcon: String + $editMenuExternalUrl: String ) { site { updateConfig( - host: $host, - title: $title, - description: $description, - robots: $robots, - analyticsService: $analyticsService, - analyticsId: $analyticsId, - company: $company, - contentLicense: $contentLicense, - logoUrl: $logoUrl, - featurePageRatings: $featurePageRatings, - featurePageComments: $featurePageComments, + host: $host + title: $title + description: $description + robots: $robots + analyticsService: $analyticsService + analyticsId: $analyticsId + company: $company + contentLicense: $contentLicense + logoUrl: $logoUrl + pageExtensions: $pageExtensions + featurePageRatings: $featurePageRatings + featurePageComments: $featurePageComments featurePersonalWikis: $featurePersonalWikis + editFab: $editFab + editMenuBar: $editMenuBar + editMenuBtn: $editMenuBtn + editMenuExternalBtn: $editMenuExternalBtn + editMenuExternalName: $editMenuExternalName + editMenuExternalIcon: $editMenuExternalIcon + editMenuExternalUrl: $editMenuExternalUrl ) { responseResult { succeeded @@ -295,9 +402,17 @@ export default { company: _.get(this.config, 'company', ''), contentLicense: _.get(this.config, 'contentLicense', ''), logoUrl: _.get(this.config, 'logoUrl', ''), + pageExtensions: _.get(this.config, 'pageExtensions', ''), featurePageRatings: _.get(this.config, 'featurePageRatings', 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) { this.$store.commit(`loading${isLoading ? 'Start' : 'Stop'}`, 'admin-site-update') @@ -347,9 +462,17 @@ export default { company contentLicense logoUrl + pageExtensions featurePageRatings featurePageComments featurePersonalWikis + editFab + editMenuBar + editMenuBtn + editMenuExternalBtn + editMenuExternalName + editMenuExternalIcon + editMenuExternalUrl } } } diff --git a/client/components/admin/admin-groups-edit-rules.vue b/client/components/admin/admin-groups-edit-rules.vue index 89d97db4..f52d9265 100644 --- a/client/components/admin/admin-groups-edit-rules.vue +++ b/client/components/admin/admin-groups-edit-rules.vue @@ -214,8 +214,8 @@ export default { return { roles: [ { text: 'Read Pages', value: 'read:pages', icon: 'mdi-file-eye-outline' }, - { text: 'Create Pages', value: 'write:pages', icon: 'mdi-file-plus-outline' }, - { text: 'Edit + Move Pages', value: 'manage:pages', icon: 'mdi-file-document-edit-outline' }, + { text: 'Create + Edit Pages', value: 'write:pages', icon: 'mdi-file-plus-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: 'View Pages Source', value: 'read:source', icon: 'mdi-code-tags' }, { text: 'View Pages History', value: 'read:history', icon: 'mdi-history' }, diff --git a/client/components/admin/admin-mail.vue b/client/components/admin/admin-mail.vue index 660660f9..4db028af 100644 --- a/client/components/admin/admin-mail.vue +++ b/client/components/admin/admin-mail.vue @@ -57,6 +57,16 @@ :hint='$t(`admin:mail.smtpPortHint`)' 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-model='config.secure' :label='$t(`admin:mail.smtpTLS`)' @@ -169,6 +179,7 @@ export default { senderEmail: '', host: '', port: 0, + name: '', secure: false, verifySSL: false, user: '', @@ -192,6 +203,7 @@ export default { senderEmail: this.config.senderEmail || '', host: this.config.host || '', port: _.toSafeInteger(this.config.port) || 0, + name: this.config.name || '', secure: this.config.secure || false, verifySSL: this.config.verifySSL || false, user: this.config.user || '', diff --git a/client/components/common/search-results.vue b/client/components/common/search-results.vue index 64c57f41..ddc33208 100644 --- a/client/components/common/search-results.vue +++ b/client/components/common/search-results.vue @@ -105,6 +105,9 @@ export default { } else { this.searchIsLoading = true } + }, + results() { + this.cursor = 0 } }, mounted() { @@ -153,6 +156,9 @@ export default { skip() { return !this.search || this.search.length < 2 }, + result() { + this.pagination = 1 + }, update: (data) => _.get(data, 'pages.search', {}), watchLoading (isLoading) { this.searchIsLoading = isLoading diff --git a/client/graph/admin/mail/mail-mutation-save-config.gql b/client/graph/admin/mail/mail-mutation-save-config.gql index 3b8f6999..611a1c0a 100644 --- a/client/graph/admin/mail/mail-mutation-save-config.gql +++ b/client/graph/admin/mail/mail-mutation-save-config.gql @@ -1,30 +1,32 @@ mutation ( - $senderName: String!, - $senderEmail: String!, - $host: String!, - $port: Int!, - $secure: Boolean!, - $verifySSL: Boolean!, - $user: String!, - $pass: String!, - $useDKIM: Boolean!, - $dkimDomainName: String!, - $dkimKeySelector: String!, + $senderName: String! + $senderEmail: String! + $host: String! + $port: Int! + $name: String! + $secure: Boolean! + $verifySSL: Boolean! + $user: String! + $pass: String! + $useDKIM: Boolean! + $dkimDomainName: String! + $dkimKeySelector: String! $dkimPrivateKey: String! ) { mail { updateConfig( - senderName: $senderName, - senderEmail: $senderEmail, - host: $host, - port: $port, - secure: $secure, - verifySSL: $verifySSL, - user: $user, - pass: $pass, - useDKIM: $useDKIM, - dkimDomainName: $dkimDomainName, - dkimKeySelector: $dkimKeySelector, + senderName: $senderName + senderEmail: $senderEmail + host: $host + port: $port + name: $name + secure: $secure + verifySSL: $verifySSL + user: $user + pass: $pass + useDKIM: $useDKIM + dkimDomainName: $dkimDomainName + dkimKeySelector: $dkimKeySelector dkimPrivateKey: $dkimPrivateKey ) { responseResult { diff --git a/client/graph/admin/mail/mail-query-config.gql b/client/graph/admin/mail/mail-query-config.gql index 66232acb..5d7091aa 100644 --- a/client/graph/admin/mail/mail-query-config.gql +++ b/client/graph/admin/mail/mail-query-config.gql @@ -5,6 +5,7 @@ senderEmail host port + name secure verifySSL user diff --git a/client/scss/base/base.scss b/client/scss/base/base.scss index f06afeea..03224354 100644 --- a/client/scss/base/base.scss +++ b/client/scss/base/base.scss @@ -30,6 +30,12 @@ html { } } +@media only screen and (min-width:960px) { + .v-application .v-footer { + padding-left: 272px + } +} + #root .v-application { .overline { line-height: 1rem; diff --git a/client/store/page.js b/client/store/page.js index 0a72798e..92979647 100644 --- a/client/store/page.js +++ b/client/store/page.js @@ -41,7 +41,16 @@ const state = { manage: false } }, - commentsCount: 0 + commentsCount: 0, + editShortcuts: { + editFab: false, + editMenuBar: false, + editMenuBtn: false, + editMenuExternalBtn: false, + editMenuExternalName: '', + editMenuExternalIcon: '', + editMenuExternalUrl: '' + } } export default { diff --git a/client/themes/default/components/page.vue b/client/themes/default/components/page.vue index 42d0b2c5..0993c9ec 100644 --- a/client/themes/default/components/page.vue +++ b/client/themes/default/components/page.vue @@ -49,10 +49,28 @@ status-indicator.ml-3(negative, pulse) v-divider 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`') .headline.grey--text(:class='$vuetify.theme.dark ? `text--lighten-2` : `text--darken-3`') {{title}} .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-container.pl-5.pt-4(fluid, grid-list-xl) v-layout(row) @@ -186,7 +204,7 @@ v-spacer 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 }') v-speed-dial( v-model='pageEditFab' @@ -440,6 +458,14 @@ export default { commentsExternal: { type: Boolean, default: false + }, + editShortcuts: { + type: String, + default: '' + }, + filename: { + type: String, + default: '' } }, data() { @@ -478,6 +504,7 @@ export default { isAuthenticated: get('user/authenticated'), commentsCount: get('page/commentsCount'), commentsPerms: get('page/effectivePermissions@comments'), + editShortcutsObj: get('page/editShortcuts'), rating: { get () { return 3.5 @@ -519,7 +546,14 @@ export default { return this.hasAdminPermission || this.hasWritePagesPermission || this.hasManagePagesPermission || 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() { this.$store.set('page/authorId', this.authorId) @@ -537,6 +571,9 @@ export default { if (this.effectivePermissions) { 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') }, @@ -676,4 +713,43 @@ export default { 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; + } + } + } +} + diff --git a/client/themes/default/scss/app.scss b/client/themes/default/scss/app.scss index 2b51ed72..99f406db 100644 --- a/client/themes/default/scss/app.scss +++ b/client/themes/default/scss/app.scss @@ -1036,4 +1036,8 @@ .comments-container { display: none; } + + .page-edit-shortcuts { + display: none; + } } diff --git a/dev/helm/templates/NOTES.txt b/dev/helm/templates/NOTES.txt index 5199428e..93c0d356 100644 --- a/dev/helm/templates/NOTES.txt +++ b/dev/helm/templates/NOTES.txt @@ -2,7 +2,7 @@ {{- if .Values.ingress.enabled }} {{- range $host := .Values.ingress.hosts }} {{- range .paths }} - http{{ if $.Values.ingress.tls }}s{{ end }}://{{ $host.host }}{{ . }} + http{{ if $.Values.ingress.tls }}s{{ end }}://{{ $host.host }}{{ .path }} {{- end }} {{- end }} {{- else if contains "NodePort" .Values.service.type }} diff --git a/dev/helm/templates/deployment.yaml b/dev/helm/templates/deployment.yaml index 24910f2b..62f02bc7 100644 --- a/dev/helm/templates/deployment.yaml +++ b/dev/helm/templates/deployment.yaml @@ -41,6 +41,12 @@ spec: env: - name: DB_TYPE 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 value: {{ template "wiki.postgresql.host" . }} - name: DB_PORT @@ -62,6 +68,7 @@ spec: name: {{ template "wiki.postgresql.secret" . }} {{- end }} key: {{ template "wiki.postgresql.secretKey" . }} + {{- end }} - name: HA_ACTIVE value: {{ .Values.replicaCount | int | le 2 | quote }} {{- with .Values.volumeMounts }} @@ -76,6 +83,8 @@ spec: {{- toYaml .Values.livenessProbe | nindent 12 }} readinessProbe: {{- toYaml .Values.readinessProbe | nindent 12 }} + startupProbe: + {{- toYaml .Values.startupProbe | nindent 12 }} resources: {{- toYaml .Values.resources | nindent 12 }} {{- with .Values.nodeSelector }} diff --git a/dev/helm/values.yaml b/dev/helm/values.yaml index 6b7296a7..1a450d6e 100644 --- a/dev/helm/values.yaml +++ b/dev/helm/values.yaml @@ -32,6 +32,16 @@ readinessProbe: path: /healthz port: http +startupProbe: + initialDelaySeconds: 15 + periodSeconds: 5 + timeoutSeconds: 5 + successThreshold: 1 + failureThreshold: 60 + httpGet: + path: /healthz + port: http + podSecurityContext: {} # fsGroup: 2000 @@ -102,6 +112,13 @@ sideload: # - name: HTTPS_PROXY # 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. ## ref: https://github.com/kubernetes/charts/blob/master/stable/postgresql/README.md ## diff --git a/server/app/data.yml b/server/app/data.yml index ca48a5ea..54482c99 100644 --- a/server/app/data.yml +++ b/server/app/data.yml @@ -45,6 +45,10 @@ defaults: company: '' contentLicense: '' logoUrl: https://static.requarks.io/logo/wikijs-butterfly.svg + pageExtensions: + - md + - html + - txt mail: host: '' secure: true @@ -63,6 +67,14 @@ defaults: audience: 'urn:wiki.js' tokenExpiration: '30m' 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: featurePageRatings: true featurePageComments: true @@ -152,8 +164,4 @@ reservedPaths: - img - js - svg -pageExtensions: - - md - - html - - txt # --------------------------------- diff --git a/server/controllers/common.js b/server/controllers/common.js index 052c3757..3bcfcd94 100644 --- a/server/controllers/common.js +++ b/server/controllers/common.js @@ -414,7 +414,7 @@ router.get('/_userav/:uid', async (req, res, next) => { * View document / asset */ 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 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 res.render('page', { page, sidebar, injectCode, comments: commentTmpl, - effectivePermissions + effectivePermissions, + pageFilename }) } } else if (pageArgs.path === 'home') { diff --git a/server/core/mail.js b/server/core/mail.js index 9dad973a..4af28b65 100644 --- a/server/core/mail.js +++ b/server/core/mail.js @@ -13,6 +13,7 @@ module.exports = { let conf = { host: WIKI.config.mail.host, port: WIKI.config.mail.port, + name: WIKI.config.mail.name, secure: WIKI.config.mail.secure, tls: { rejectUnauthorized: !(WIKI.config.mail.verifySSL === false) diff --git a/server/graph/resolvers/mail.js b/server/graph/resolvers/mail.js index d7e87397..7305e686 100644 --- a/server/graph/resolvers/mail.js +++ b/server/graph/resolvers/mail.js @@ -49,6 +49,7 @@ module.exports = { senderEmail: args.senderEmail, host: args.host, port: args.port, + name: args.name, secure: args.secure, verifySSL: args.verifySSL, user: args.user, diff --git a/server/graph/resolvers/site.js b/server/graph/resolvers/site.js index f36b2f7f..db538a21 100644 --- a/server/graph/resolvers/site.js +++ b/server/graph/resolvers/site.js @@ -18,7 +18,9 @@ module.exports = { company: WIKI.config.company, contentLicense: WIKI.config.contentLicense, logoUrl: WIKI.config.logoUrl, + pageExtensions: WIKI.config.pageExtensions.join(', '), ...WIKI.config.seo, + ...WIKI.config.editShortcuts, ...WIKI.config.features, ...WIKI.config.security, authAutoLogin: WIKI.config.auth.autoLogin, @@ -62,6 +64,10 @@ module.exports = { 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 = { description: _.get(args, 'description', WIKI.config.seo.description), robots: _.get(args, 'robots', WIKI.config.seo.robots), @@ -79,6 +85,16 @@ module.exports = { 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 = { featurePageRatings: _.get(args, 'featurePageRatings', WIKI.config.features.featurePageRatings), featurePageComments: _.get(args, 'featurePageComments', WIKI.config.features.featurePageComments), @@ -104,7 +120,7 @@ module.exports = { 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) { WIKI.app.enable('trust proxy') diff --git a/server/graph/schemas/mail.graphql b/server/graph/schemas/mail.graphql index f935c5ac..eed57c88 100644 --- a/server/graph/schemas/mail.graphql +++ b/server/graph/schemas/mail.graphql @@ -32,6 +32,7 @@ type MailMutation { senderEmail: String! host: String! port: Int! + name: String! secure: Boolean! verifySSL: Boolean! user: String! @@ -48,16 +49,17 @@ type MailMutation { # ----------------------------------------------- type MailConfig { - senderName: String! - senderEmail: String! - host: String! - port: Int! - secure: Boolean! - verifySSL: Boolean! - user: String! - pass: String! - useDKIM: Boolean! - dkimDomainName: String! - dkimKeySelector: String! - dkimPrivateKey: String! + senderName: String + senderEmail: String + host: String + port: Int + name: String + secure: Boolean + verifySSL: Boolean + user: String + pass: String + useDKIM: Boolean + dkimDomainName: String + dkimKeySelector: String + dkimPrivateKey: String } diff --git a/server/graph/schemas/site.graphql b/server/graph/schemas/site.graphql index 5544fffe..4cd36d76 100644 --- a/server/graph/schemas/site.graphql +++ b/server/graph/schemas/site.graphql @@ -33,6 +33,7 @@ type SiteMutation { company: String contentLicense: String logoUrl: String + pageExtensions: String authAutoLogin: Boolean authEnforce2FA: Boolean authHideLocal: Boolean @@ -40,6 +41,13 @@ type SiteMutation { authJwtAudience: String authJwtExpiration: String authJwtRenewablePeriod: String + editFab: Boolean + editMenuBar: Boolean + editMenuBtn: Boolean + editMenuExternalBtn: Boolean + editMenuExternalName: String + editMenuExternalIcon: String + editMenuExternalUrl: String featurePageRatings: Boolean featurePageComments: Boolean featurePersonalWikis: Boolean @@ -74,6 +82,7 @@ type SiteConfig { company: String contentLicense: String logoUrl: String + pageExtensions: String authAutoLogin: Boolean authEnforce2FA: Boolean authHideLocal: Boolean @@ -81,6 +90,13 @@ type SiteConfig { authJwtAudience: String authJwtExpiration: String authJwtRenewablePeriod: String + editFab: Boolean + editMenuBar: Boolean + editMenuBtn: Boolean + editMenuExternalBtn: Boolean + editMenuExternalName: String + editMenuExternalIcon: String + editMenuExternalUrl: String featurePageRatings: Boolean featurePageComments: Boolean featurePersonalWikis: Boolean diff --git a/server/index.js b/server/index.js index 87eff16b..96f5e4a6 100644 --- a/server/index.js +++ b/server/index.js @@ -38,6 +38,9 @@ WIKI.kernel.init() // Register exit handler // ---------------------------------------- +process.on('SIGTERM', () => { + WIKI.kernel.shutdown() +}) process.on('SIGINT', () => { WIKI.kernel.shutdown() }) diff --git a/server/models/pages.js b/server/models/pages.js index 44b3825f..9bb5d397 100644 --- a/server/models/pages.js +++ b/server/models/pages.js @@ -148,6 +148,7 @@ module.exports = class Page extends Model { isPublished: 'boolean', publishEndDate: 'string', publishStartDate: 'string', + contentType: 'string', render: 'string', tags: [ { @@ -787,7 +788,7 @@ module.exports = class Page extends Model { * @returns {Promise} Promise with no value */ 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) { throw new WIKI.Error.PageNotFound() } @@ -1067,6 +1068,7 @@ module.exports = class Page extends Model { isPublished: page.isPublished === 1 || page.isPublished === true, publishEndDate: page.publishEndDate, publishStartDate: page.publishStartDate, + contentType: page.contentType, render: page.render, tags: page.tags.map(t => _.pick(t, ['tag', 'title'])), title: page.title, diff --git a/server/modules/authentication/auth0/authentication.js b/server/modules/authentication/auth0/authentication.js index 9eade81a..388139bc 100644 --- a/server/modules/authentication/auth0/authentication.js +++ b/server/modules/authentication/auth0/authentication.js @@ -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()}` } } diff --git a/server/modules/authentication/cas/authentication.js b/server/modules/authentication/cas/authentication.js index eae89aff..9255d02e 100644 --- a/server/modules/authentication/cas/authentication.js +++ b/server/modules/authentication/cas/authentication.js @@ -1,3 +1,4 @@ +const _ = require('lodash') /* global WIKI */ // ------------------------------------ @@ -10,15 +11,24 @@ module.exports = { init (passport, conf) { passport.use(conf.key, new CASStrategy({ - ssoBaseURL: conf.ssoBaseURL, - serverBaseURL: conf.serverBaseURL, + version: conf.casVersion, + ssoBaseURL: conf.casUrl, + serverBaseURL: conf.baseUrl, + serviceURL: conf.callbackURL, passReqToCallback: true }, async (req, profile, cb) => { try { const user = await WIKI.models.users.processProfile({ 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) } catch (err) { cb(err, null) diff --git a/server/modules/authentication/cas/definition.yml b/server/modules/authentication/cas/definition.yml index 912840eb..a8c6e4fd 100644 --- a/server/modules/authentication/cas/definition.yml +++ b/server/modules/authentication/cas/definition.yml @@ -6,6 +6,37 @@ logo: https://static.requarks.io/logo/cas.svg color: green darken-2 website: https://apereo.github.io/cas/ useForm: false +isAvailable: true props: - ssoBaseURL: String - serverBaseURL: String + baseUrl: + 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 diff --git a/server/modules/authentication/local/authentication.js b/server/modules/authentication/local/authentication.js index e6fa75d3..ea2bf1d0 100644 --- a/server/modules/authentication/local/authentication.js +++ b/server/modules/authentication/local/authentication.js @@ -1,3 +1,5 @@ +const bcrypt = require('bcryptjs-then') + /* global WIKI */ // ------------------------------------ @@ -28,6 +30,9 @@ module.exports = { done(null, user) } } 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) } } catch (err) { diff --git a/server/modules/authentication/oidc/authentication.js b/server/modules/authentication/oidc/authentication.js index 6bd244fe..ae381111 100644 --- a/server/modules/authentication/oidc/authentication.js +++ b/server/modules/authentication/oidc/authentication.js @@ -29,6 +29,19 @@ module.exports = { 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) } catch (err) { cb(err, null) diff --git a/server/modules/authentication/oidc/definition.yml b/server/modules/authentication/oidc/definition.yml index 02812c4f..ae1c636a 100644 --- a/server/modules/authentication/oidc/definition.yml +++ b/server/modules/authentication/oidc/definition.yml @@ -49,8 +49,21 @@ props: default: email maxWidth: 500 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: type: String title: Logout URL hint: (optional) Logout URL on the OAuth2 provider where the user will be redirected to complete the logout process. - order: 8 + order: 10 diff --git a/server/modules/authentication/saml/authentication.js b/server/modules/authentication/saml/authentication.js index e1c75d15..6eeef27a 100644 --- a/server/modules/authentication/saml/authentication.js +++ b/server/modules/authentication/saml/authentication.js @@ -14,14 +14,14 @@ module.exports = { callbackUrl: conf.callbackURL, entryPoint: conf.entryPoint, issuer: conf.issuer, - cert: _.split(conf.cert || '', '|'), + cert: (conf.cert || '').split('|'), signatureAlgorithm: conf.signatureAlgorithm, digestAlgorithm: conf.digestAlgorithm, identifierFormat: conf.identifierFormat, wantAssertionsSigned: conf.wantAssertionsSigned, acceptedClockSkewMs: _.toSafeInteger(conf.acceptedClockSkewMs), disableRequestedAuthnContext: conf.disableRequestedAuthnContext, - authnContext: _.split(conf.authnContext, '|'), + authnContext: (conf.authnContext || '').split('|'), racComparison: conf.racComparison, forceAuthn: conf.forceAuthn, passive: conf.passive, diff --git a/server/modules/search/elasticsearch/definition.yml b/server/modules/search/elasticsearch/definition.yml index 856f651b..8475c0b5 100644 --- a/server/modules/search/elasticsearch/definition.yml +++ b/server/modules/search/elasticsearch/definition.yml @@ -20,28 +20,37 @@ props: 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) 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: type: String title: Index Name hint: The index name to use during creation default: wiki - order: 3 + order: 5 analyzer: type: String title: Analyzer hint: 'The token analyzer in elasticsearch' default: simple - order: 4 + order: 6 sniffOnStart: type: Boolean title: Sniff on start hint: 'Should Wiki.js attempt to detect the rest of the cluster on first connect? (Default: off)' default: false - order: 5 + order: 7 sniffInterval: type: Number title: Sniff Interval hint: '0 = disabled, Interval in seconds to check for updated list of nodes in cluster. (Default: 0)' default: 0 - order: 6 - + order: 8 diff --git a/server/modules/search/elasticsearch/engine.js b/server/modules/search/elasticsearch/engine.js index 4a41df88..4a96b2bb 100644 --- a/server/modules/search/elasticsearch/engine.js +++ b/server/modules/search/elasticsearch/engine.js @@ -1,6 +1,7 @@ const _ = require('lodash') const stream = require('stream') const Promise = require('bluebird') +const fs = require('fs') const pipeline = Promise.promisify(stream.pipeline) /* global WIKI */ @@ -24,6 +25,7 @@ module.exports = { nodes: this.config.hosts.split(',').map(_.trim), sniffOnStart: this.config.sniffOnStart, sniffInterval: (this.config.sniffInterval > 0) ? this.config.sniffInterval : false, + ssl: getTlsOptions(this.config), name: 'wiki-js' }) break @@ -33,6 +35,7 @@ module.exports = { nodes: this.config.hosts.split(',').map(_.trim), sniffOnStart: this.config.sniffOnStart, sniffInterval: (this.config.sniffInterval > 0) ? this.config.sniffInterval : false, + ssl: getTlsOptions(this.config), name: 'wiki-js' }) break @@ -351,3 +354,21 @@ module.exports = { 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 + } +} diff --git a/server/modules/storage/azure/definition.yml b/server/modules/storage/azure/definition.yml index 13eb92e0..631bef37 100644 --- a/server/modules/storage/azure/definition.yml +++ b/server/modules/storage/azure/definition.yml @@ -41,4 +41,4 @@ props: actions: - handler: exportAll 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. diff --git a/server/setup.js b/server/setup.js index 6d87a29f..44da308b 100644 --- a/server/setup.js +++ b/server/setup.js @@ -103,6 +103,7 @@ module.exports = () => { senderEmail: '', host: '', port: 465, + name: '', secure: true, verifySSL: true, user: '', diff --git a/server/views/page.pug b/server/views/page.pug index cc19fbae..32649fbe 100644 --- a/server/views/page.pug +++ b/server/views/page.pug @@ -29,6 +29,8 @@ block body comments-enabled=config.features.featurePageComments effective-permissions=Buffer.from(JSON.stringify(effectivePermissions)).toString('base64') comments-external=comments.codeTemplate + edit-shortcuts=Buffer.from(JSON.stringify(config.editShortcuts)).toString('base64') + filename=pageFilename ) template(slot='contents') div!= page.render