diff --git a/client/components/settings/settingBody.jade b/client/components/settings/settingBody.jade index 9101e18f7..1df41a599 100644 --- a/client/components/settings/settingBody.jade +++ b/client/components/settings/settingBody.jade @@ -423,6 +423,14 @@ template(name='layoutSettings') .title {{_ 'custom-help-link-url'}} .form-group input.wekan-form-control#custom-help-link-url(type="text", placeholder="" value="{{currentSetting.customHelpLinkUrl}}") + li.layout-form + .title {{_ 'custom-css-url'}} + .form-group + input.wekan-form-control#custom-css-url(type="text", placeholder="" value="{{currentSetting.customCssUrl}}") + li.layout-form + .title {{_ 'custom-css'}} + .form-group + textarea#custom-css.wekan-form-control(rows='5')= currentSetting.customCss li.layout-form .title {{_ 'text-below-custom-login-logo'}} .form-group diff --git a/client/components/settings/settingBody.js b/client/components/settings/settingBody.js index 682479c92..d70b5ea64 100644 --- a/client/components/settings/settingBody.js +++ b/client/components/settings/settingBody.js @@ -469,6 +469,8 @@ BlazeComponent.extendComponent({ const customLoginLogoImageUrl = ($('#custom-login-logo-image-url').val() || '').trim(); const customLoginLogoLinkUrl = ($('#custom-login-logo-link-url').val() || '').trim(); const customHelpLinkUrl = ($('#custom-help-link-url').val() || '').trim(); + const customCssUrl = ($('#custom-css-url').val() || '').trim(); + const customCss = ($('#custom-css').val() || '').trim(); const textBelowCustomLoginLogo = ($('#text-below-custom-login-logo').val() || '').trim(); const automaticLinkedUrlSchemes = ($('#automatic-linked-url-schemes').val() || '').trim(); const customTopLeftCornerLogoImageUrl = ($('#custom-top-left-corner-logo-image-url').val() || '').trim(); @@ -496,6 +498,8 @@ BlazeComponent.extendComponent({ customLoginLogoImageUrl, customLoginLogoLinkUrl, customHelpLinkUrl, + customCssUrl, + customCss, textBelowCustomLoginLogo, customTopLeftCornerLogoImageUrl, customTopLeftCornerLogoLinkUrl, @@ -801,4 +805,3 @@ Template.selectSpinnerName.helpers({ return Template.instance().data.spinnerName === match; }, }); - diff --git a/client/lib/customCss.js b/client/lib/customCss.js new file mode 100644 index 000000000..4312fe532 --- /dev/null +++ b/client/lib/customCss.js @@ -0,0 +1,45 @@ +import { Meteor } from 'meteor/meteor'; +import { Tracker } from 'meteor/tracker'; +import { ReactiveCache } from '/imports/reactiveCache'; + +// Keep external or inline custom CSS from admin settings applied to the document head +Meteor.startup(() => { + const head = document.head || document.getElementsByTagName('head')[0]; + let customCssLink = null; + let customInlineStyle = null; + + Meteor.subscribe('setting'); + + Tracker.autorun(() => { + const setting = ReactiveCache.getCurrentSetting(); + if (!setting) return; + + const cssUrl = (setting.customCssUrl || '').trim(); + const inlineCss = (setting.customCss || '').trim(); + + if (cssUrl) { + if (!customCssLink) { + customCssLink = document.createElement('link'); + customCssLink.rel = 'stylesheet'; + customCssLink.id = 'wekan-custom-css-url'; + head.appendChild(customCssLink); + } + customCssLink.href = cssUrl; + } else if (customCssLink) { + customCssLink.remove(); + customCssLink = null; + } + + if (inlineCss) { + if (!customInlineStyle) { + customInlineStyle = document.createElement('style'); + customInlineStyle.id = 'wekan-custom-inline-css'; + head.appendChild(customInlineStyle); + } + customInlineStyle.textContent = inlineCss; + } else if (customInlineStyle) { + customInlineStyle.remove(); + customInlineStyle = null; + } + }); +}); diff --git a/imports/i18n/data/de.i18n.json b/imports/i18n/data/de.i18n.json index cfb4ea635..36c380e0a 100644 --- a/imports/i18n/data/de.i18n.json +++ b/imports/i18n/data/de.i18n.json @@ -660,6 +660,8 @@ "custom-login-logo-image-url": "Benutzerdefiniertes Login Logo Bild URL", "custom-login-logo-link-url": "Benutzerdefiniertes Login Logo Link URL", "custom-help-link-url": "Benutzerdefinierter URL-Link zur Hilfe", + "custom-css-url": "Externe CSS-URL", + "custom-css": "Benutzerdefiniertes CSS", "text-below-custom-login-logo": "Text unterhalb benutzerdefiniertem Login Logo", "automatic-linked-url-schemes": "Spezielle URL-Schemas, die durch Klick automatisch öffenbar sein sollen. Ein URL-Schema pro Zeile", "username": "Benutzername", diff --git a/imports/i18n/data/en.i18n.json b/imports/i18n/data/en.i18n.json index acf3c6934..45b19b706 100644 --- a/imports/i18n/data/en.i18n.json +++ b/imports/i18n/data/en.i18n.json @@ -660,6 +660,8 @@ "custom-login-logo-image-url": "Custom Login Logo Image URL", "custom-login-logo-link-url": "Custom Login Logo Link URL", "custom-help-link-url": "Custom Help Link URL", + "custom-css-url": "Custom external CSS URL", + "custom-css": "Custom CSS overrides", "text-below-custom-login-logo": "Text below Custom Login Logo", "automatic-linked-url-schemes": "Custom URL Schemes which should automatically be clickable. One URL Scheme per line", "username": "Username", diff --git a/models/settings.js b/models/settings.js index 00349c762..9cf5249a5 100644 --- a/models/settings.js +++ b/models/settings.js @@ -85,6 +85,14 @@ Settings.attachSchema( type: String, optional: true, }, + customCssUrl: { + type: String, + optional: true, + }, + customCss: { + type: String, + optional: true, + }, textBelowCustomLoginLogo: { type: String, optional: true, @@ -202,6 +210,8 @@ if (Meteor.isServer) { modifiedAt: now, displayAuthenticationMethod: true, defaultAuthenticationMethod: 'password', + customCssUrl: '', + customCss: '', }; Settings.insert(defaultSetting); } diff --git a/server/publications/settings.js b/server/publications/settings.js index e2365d523..b9c3499cb 100644 --- a/server/publications/settings.js +++ b/server/publications/settings.js @@ -30,6 +30,8 @@ Meteor.publish('setting', () => { customTopLeftCornerLogoImageUrl: 1, customTopLeftCornerLogoLinkUrl: 1, customTopLeftCornerLogoHeight: 1, + customCssUrl: 1, + customCss: 1, customHTMLafterBodyStart: 1, customHTMLbeforeBodyEnd: 1, displayAuthenticationMethod: 1,