mirror of
https://github.com/wekan/wekan.git
synced 2026-02-16 21:18:06 +01:00
Admin Panel/Settings/Layout, for PWA: Custom head meta, link, icons, assetlinks.json, site.webmanifest.
Thanks to xet7 !
This commit is contained in:
parent
dace6b78c0
commit
b5a13f0206
16 changed files with 1016 additions and 3 deletions
|
|
@ -230,3 +230,22 @@ li.has-error .form-group .wekan-form-control {
|
|||
border-color: #a94442;
|
||||
box-shadow: inset 0 1px 1px rgba(0,0,0,0.075);
|
||||
}
|
||||
|
||||
/* Constrain textarea elements with balanced left/right spacing */
|
||||
.setting-content .content-body .main-body textarea.wekan-form-control {
|
||||
width: calc(100% - 20px);
|
||||
margin: 0 10px;
|
||||
max-width: calc(100vw - 320px);
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
/* For nested custom head/manifest/assetlinks sections */
|
||||
.setting-content .content-body .main-body .custom-head-settings textarea.wekan-form-control,
|
||||
.setting-content .content-body .main-body .custom-manifest-settings textarea.wekan-form-control,
|
||||
.setting-content .content-body .main-body .custom-assetlinks-settings textarea.wekan-form-control {
|
||||
width: calc(100% - 20px);
|
||||
margin-left: 0;
|
||||
margin-right: 10px;
|
||||
max-width: calc(100vw - 240px);
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -460,6 +460,41 @@ template(name='layoutSettings')
|
|||
textarea#support-page-text.wekan-form-control= currentSetting.supportPageText
|
||||
li
|
||||
button.js-support-save.primary {{_ 'save'}}
|
||||
li
|
||||
a.flex.js-toggle-custom-head
|
||||
.materialCheckBox(class="{{#if currentSetting.customHeadEnabled}}is-checked{{/if}}")
|
||||
span {{_ 'custom-head-tags-enabled'}}
|
||||
li
|
||||
.custom-head-settings(class="{{#if currentSetting.customHeadEnabled}}{{else}}hide{{/if}}")
|
||||
ul
|
||||
li
|
||||
.title {{_ 'custom-head-meta-tags'}}
|
||||
textarea#custom-head-meta.wekan-form-control= customHeadMetaTagsValue
|
||||
li
|
||||
.title {{_ 'custom-head-link-tags'}}
|
||||
textarea#custom-head-links.wekan-form-control= customHeadLinkTagsValue
|
||||
li
|
||||
a.flex.js-toggle-custom-manifest
|
||||
.materialCheckBox(class="{{#if currentSetting.customManifestEnabled}}is-checked{{/if}}")
|
||||
span {{_ 'custom-manifest-enabled'}}
|
||||
li
|
||||
.custom-manifest-settings(class="{{#if currentSetting.customManifestEnabled}}{{else}}hide{{/if}}")
|
||||
.title {{_ 'custom-head-manifest-content'}}
|
||||
textarea#custom-manifest-content.wekan-form-control= customManifestContentValue
|
||||
li
|
||||
button.js-custom-head-save.primary {{_ 'save'}}
|
||||
li
|
||||
a.flex.js-toggle-custom-assetlinks
|
||||
.materialCheckBox(class="{{#if currentSetting.customAssetLinksEnabled}}is-checked{{/if}}")
|
||||
span {{_ 'custom-assetlinks-enabled'}}
|
||||
li
|
||||
.custom-assetlinks-settings(class="{{#if currentSetting.customAssetLinksEnabled}}{{else}}hide{{/if}}")
|
||||
ul
|
||||
li
|
||||
.title {{_ 'custom-assetlinks-content'}}
|
||||
textarea#custom-assetlinks-content.wekan-form-control= customAssetLinksContentValue
|
||||
li
|
||||
button.js-custom-assetlinks-save.primary {{_ 'save'}}
|
||||
li.layout-form
|
||||
.title {{_ 'oidc-button-text'}}
|
||||
.form-group
|
||||
|
|
|
|||
|
|
@ -759,6 +759,182 @@ BlazeComponent.extendComponent({
|
|||
this.setLoading(false);
|
||||
},
|
||||
|
||||
toggleCustomHead() {
|
||||
this.setLoading(true);
|
||||
const customHeadEnabled = !$('.js-toggle-custom-head .materialCheckBox').hasClass('is-checked');
|
||||
$('.js-toggle-custom-head .materialCheckBox').toggleClass('is-checked');
|
||||
$('.custom-head-settings').toggleClass('hide');
|
||||
Settings.update(ReactiveCache.getCurrentSetting()._id, {
|
||||
$set: { customHeadEnabled },
|
||||
});
|
||||
this.setLoading(false);
|
||||
},
|
||||
|
||||
toggleCustomManifest() {
|
||||
this.setLoading(true);
|
||||
const customManifestEnabled = !$('.js-toggle-custom-manifest .materialCheckBox').hasClass('is-checked');
|
||||
$('.js-toggle-custom-manifest .materialCheckBox').toggleClass('is-checked');
|
||||
$('.custom-manifest-settings').toggleClass('hide');
|
||||
Settings.update(ReactiveCache.getCurrentSetting()._id, {
|
||||
$set: { customManifestEnabled },
|
||||
});
|
||||
this.setLoading(false);
|
||||
},
|
||||
|
||||
saveCustomHeadSettings() {
|
||||
this.setLoading(true);
|
||||
const customHeadMetaTags = $('#custom-head-meta').val() || '';
|
||||
let customManifestContent = $('#custom-manifest-content').val() || '';
|
||||
|
||||
// Validate and clean JSON if present
|
||||
if (customManifestContent.trim()) {
|
||||
const cleanResult = this.cleanAndValidateJSON(customManifestContent);
|
||||
if (cleanResult.error) {
|
||||
this.setLoading(false);
|
||||
alert(`Invalid manifest JSON: ${cleanResult.error}`);
|
||||
return;
|
||||
}
|
||||
customManifestContent = cleanResult.json;
|
||||
// Update the textarea with cleaned version
|
||||
$('#custom-manifest-content').val(customManifestContent);
|
||||
}
|
||||
|
||||
const customHeadLinkTags = $('#custom-head-links').val() || '';
|
||||
|
||||
try {
|
||||
Settings.update(ReactiveCache.getCurrentSetting()._id, {
|
||||
$set: {
|
||||
customHeadMetaTags,
|
||||
customHeadLinkTags,
|
||||
customManifestContent,
|
||||
},
|
||||
});
|
||||
} catch (e) {
|
||||
return;
|
||||
} finally {
|
||||
this.setLoading(false);
|
||||
}
|
||||
},
|
||||
|
||||
cleanAndValidateJSON(content) {
|
||||
if (!content || !content.trim()) {
|
||||
return { json: content };
|
||||
}
|
||||
|
||||
try {
|
||||
// Try to parse as-is
|
||||
const parsed = JSON.parse(content);
|
||||
return { json: JSON.stringify(parsed, null, 2) };
|
||||
} catch (e) {
|
||||
const errorMsg = e.message;
|
||||
|
||||
// If error is "unexpected non-whitespace character after JSON data"
|
||||
if (errorMsg.includes('unexpected non-whitespace character after JSON data')) {
|
||||
try {
|
||||
// Try to find and extract valid JSON by finding matching braces/brackets
|
||||
const trimmed = content.trim();
|
||||
let depth = 0;
|
||||
let endPos = -1;
|
||||
let inString = false;
|
||||
let escapeNext = false;
|
||||
|
||||
for (let i = 0; i < trimmed.length; i++) {
|
||||
const char = trimmed[i];
|
||||
|
||||
if (escapeNext) {
|
||||
escapeNext = false;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (char === '\\') {
|
||||
escapeNext = true;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (char === '"' && !escapeNext) {
|
||||
inString = !inString;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (inString) continue;
|
||||
|
||||
if (char === '{' || char === '[') {
|
||||
depth++;
|
||||
} else if (char === '}' || char === ']') {
|
||||
depth--;
|
||||
if (depth === 0) {
|
||||
endPos = i + 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (endPos > 0) {
|
||||
const cleanedContent = trimmed.substring(0, endPos);
|
||||
const parsed = JSON.parse(cleanedContent);
|
||||
return { json: JSON.stringify(parsed, null, 2) };
|
||||
}
|
||||
} catch (fixError) {
|
||||
// If fix attempt fails, return original error
|
||||
}
|
||||
}
|
||||
|
||||
// Remove trailing commas (common error)
|
||||
if (errorMsg.includes('Unexpected token')) {
|
||||
try {
|
||||
const fixed = content.replace(/,(\s*[}\]])/g, '$1');
|
||||
const parsed = JSON.parse(fixed);
|
||||
return { json: JSON.stringify(parsed, null, 2) };
|
||||
} catch (fixError) {
|
||||
// Continue to error return
|
||||
}
|
||||
}
|
||||
|
||||
return { error: errorMsg };
|
||||
}
|
||||
},
|
||||
|
||||
toggleCustomAssetLinks() {
|
||||
this.setLoading(true);
|
||||
const customAssetLinksEnabled = !$('.js-toggle-custom-assetlinks .materialCheckBox').hasClass('is-checked');
|
||||
$('.js-toggle-custom-assetlinks .materialCheckBox').toggleClass('is-checked');
|
||||
$('.custom-assetlinks-settings').toggleClass('hide');
|
||||
Settings.update(ReactiveCache.getCurrentSetting()._id, {
|
||||
$set: { customAssetLinksEnabled },
|
||||
});
|
||||
this.setLoading(false);
|
||||
},
|
||||
|
||||
saveCustomAssetLinksSettings() {
|
||||
this.setLoading(true);
|
||||
let customAssetLinksContent = $('#custom-assetlinks-content').val() || '';
|
||||
|
||||
// Validate and clean JSON if present
|
||||
if (customAssetLinksContent.trim()) {
|
||||
const cleanResult = this.cleanAndValidateJSON(customAssetLinksContent);
|
||||
if (cleanResult.error) {
|
||||
this.setLoading(false);
|
||||
alert(`Invalid assetlinks JSON: ${cleanResult.error}`);
|
||||
return;
|
||||
}
|
||||
customAssetLinksContent = cleanResult.json;
|
||||
// Update the textarea with cleaned version
|
||||
$('#custom-assetlinks-content').val(customAssetLinksContent);
|
||||
}
|
||||
|
||||
try {
|
||||
Settings.update(ReactiveCache.getCurrentSetting()._id, {
|
||||
$set: {
|
||||
customAssetLinksContent,
|
||||
},
|
||||
});
|
||||
} catch (e) {
|
||||
return;
|
||||
} finally {
|
||||
this.setLoading(false);
|
||||
}
|
||||
},
|
||||
|
||||
saveSupportSettings() {
|
||||
this.setLoading(true);
|
||||
const supportTitle = ($('#support-title').val() || '').trim();
|
||||
|
|
@ -808,6 +984,11 @@ BlazeComponent.extendComponent({
|
|||
'click a.js-toggle-support': this.toggleSupportPage,
|
||||
'click a.js-toggle-support-public': this.toggleSupportPublic,
|
||||
'click button.js-support-save': this.saveSupportSettings,
|
||||
'click a.js-toggle-custom-head': this.toggleCustomHead,
|
||||
'click a.js-toggle-custom-manifest': this.toggleCustomManifest,
|
||||
'click button.js-custom-head-save': this.saveCustomHeadSettings,
|
||||
'click a.js-toggle-custom-assetlinks': this.toggleCustomAssetLinks,
|
||||
'click button.js-custom-assetlinks-save': this.saveCustomAssetLinksSettings,
|
||||
'click a.js-toggle-display-authentication-method': this
|
||||
.toggleDisplayAuthenticationMethod,
|
||||
},
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue