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.
: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*.

@ -359,6 +359,23 @@ jobs:
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
runs-on: ubuntu-latest

@ -11,10 +11,12 @@
[![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)
[![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 @@
<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 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" />
- 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))
</td><td>
<img width="441" height="1" />
- 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
<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://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.

@ -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')
</template>
@ -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
}
}
}

@ -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' },

@ -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 || '',

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

@ -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 {

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

@ -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;

@ -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 {

@ -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;
}
}
}
}
</style>

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

@ -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 }}

@ -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 }}

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

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

@ -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') {

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

@ -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,

@ -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')

@ -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
}

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

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

@ -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,

@ -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 */
// ------------------------------------
@ -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)

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

@ -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) {

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

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

@ -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,

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

@ -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
}
}

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

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

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

Loading…
Cancel
Save