giteascripts/main.user.js
Коробцев Виталий Сергеевич 1ed638c38b Please enter the commit message for your changes. Lines starting
with '' will be ignored, and an empty message aborts the commit.

 On branch main

 Initial commit

 Changes to be committed:
	new file:   README.md
	new file:   main.user.js
	new file:   screenshot1.png
	new file:   screenshot2.png
	new file:   screenshot3.webp
2025-06-09 10:53:40 +05:00

238 lines
No EOL
10 KiB
JavaScript

// ==UserScript==
// @match https://*/*
// @name Enhancements for Forgejo
// @description Enhancements for Forgejo
// @grant GM.getValue
// @grant GM.setValue
// @version 0.2.1
// @author KaKi87
// @license MIT
// @namespace https://git.kaki87.net/KaKi87/userscripts/src/branch/master/enhancementsForForgejo
// @require https://cdn.jsdelivr.net/npm/vue@3.3.4/dist/vue.global.prod.js
// ==/UserScript==
const
APP_ID = 'enhancementsForForgejo',
APP_NAME = 'Enhancements for Forgejo';
(async () => {
if(!document.querySelector('[href$="/explore/repos"]')) return;
unsafeWindow.Vue = Vue;
let
tweaksModalAppMountedPromiseResolve,
setTweakModalActive,
getSettings;
const
tweaksModalElement = document.createElement('div'),
tweaksModalAppMountedPromise = new Promise(resolve => tweaksModalAppMountedPromiseResolve = resolve),
tweaksModalApp = Vue.createApp({
data: () => ({
isActive: false,
settings: {
isFixCommentButtonsAlignment: true,
isNavbarSticky: false,
isCodeViewScrollable: true,
isFixIssuesTabConsistency: true,
isRestoreFeatureRichMarkdownEditor: true,
isSortAdminUsersByNewestByDefault: true
}
}),
methods: {
refreshSettings: async function(){
const settings = await GM.getValue('settings');
if(settings)
Object.assign(this.settings, settings);
},
applySettings: function(){
document.body.classList.toggle(`__${APP_ID}--isFixCommentButtonsAlignment`, this.settings.isFixCommentButtonsAlignment);
document.body.classList.toggle(`__${APP_ID}--isNavbarSticky`, this.settings.isNavbarSticky);
document.body.classList.toggle(`__${APP_ID}--isCodeViewScrollable`, this.settings.isCodeViewScrollable);
}
},
watch: {
isActive: async function(isActive, wasActive){
document.body.classList.toggle('dimmable', isActive);
document.body.classList.toggle('dimmed', isActive);
if(!wasActive && isActive)
await this.refreshSettings();
},
settings: {
deep: true,
handler: async function(){
await GM.setValue('settings', this.settings);
this.applySettings();
}
}
},
mounted: async function(){
await this.refreshSettings();
setTweakModalActive = isActive => this.isActive = isActive;
getSettings = () => this.settings;
tweaksModalAppMountedPromiseResolve();
this.applySettings();
},
template: `
<div
class="__${APP_ID}__tweaksModal__container ui dimmer"
:class="{ 'active': isActive }"
@click="event => isActive = event.target !== event.currentTarget"
><div
class="ui small modal active"
>
<div class="__${APP_ID}__tweaksModal__header header">
<span>${APP_NAME}</span>
<button
class="__${APP_ID}__tweaksModal__header__close"
@click.stop="isActive = false"
><i
class="bx bx-x"
></i></button>
</div>
<form class="__${APP_ID}__tweaksModal__form content">
<div
class="ui checkbox"
><input
v-model="settings.isFixCommentButtonsAlignment"
type="checkbox"
><label>Fix comment buttons alignment</label></div>
<div
class="ui checkbox"
><input
v-model="settings.isNavbarSticky"
type="checkbox"
><label>Sticky navbar</label></div>
<div
class="ui checkbox"
><input
v-model="settings.isCodeViewScrollable"
type="checkbox"
><label>Scrollable code view</label></div>
<div
class="ui checkbox"
><input
v-model="settings.isFixIssuesTabConsistency"
type="checkbox"
><label>Fix issues tab consistency</label></div>
<div
class="ui checkbox"
><input
v-model="settings.isRestoreFeatureRichMarkdownEditor"
type="checkbox"
><label>Restore feature-rich Markdown editor</label></div>
<div
class="ui checkbox"
><input
v-model="settings.isSortAdminUsersByNewestByDefault"
type="checkbox"
><label>Sort <code class="inline-code-block">/admin/users</code> by newest by default</label></div>
</form>
</div></div>
`
});
tweaksModalElement.setAttribute('class', `__${APP_ID}__tweaksModal`);
await Promise.all([
tweaksModalApp.mount(tweaksModalElement),
tweaksModalAppMountedPromise
]);
document.body.append(tweaksModalElement);
for(const userMenuNotificationsElement of document.querySelectorAll('[href="/notifications"]')){
const userMenuTweaksElement = document.createElement(userMenuNotificationsElement.tagName);
userMenuTweaksElement.style.setProperty('--width', `${userMenuNotificationsElement.offsetWidth}px`);
userMenuTweaksElement.style.setProperty('--height', `${userMenuNotificationsElement.offsetHeight}px`);
userMenuTweaksElement.setAttribute('class', `__${APP_ID}__tweaksMenu ${userMenuNotificationsElement.className}`);
userMenuTweaksElement.setAttribute('data-tooltip-content', APP_NAME);
userMenuTweaksElement.setAttribute('href', '#');
userMenuTweaksElement.innerHTML = `<i class='bx bxs-extension'></i>`;
userMenuTweaksElement.addEventListener(
'click',
event => {
event.preventDefault();
setTweakModalActive(true);
}
);
userMenuNotificationsElement.parentElement.insertBefore(userMenuTweaksElement, userMenuNotificationsElement);
}
{
const issuesTabElement = document.querySelector(`[href="${window.location.pathname}/issues"]`);
if(issuesTabElement && getSettings().isFixIssuesTabConsistency)
issuesTabElement.setAttribute('href', `${issuesTabElement.getAttribute('href')}?state=open`);
}
if(getSettings().isRestoreFeatureRichMarkdownEditor){
localStorage.setItem('markdown-editor-default', 'easymde');
localStorage.setItem('markdown-editor-wiki', 'easymde');
}
if(window.location.pathname.startsWith('/admin') && getSettings().isSortAdminUsersByNewestByDefault)
document.querySelector('[href="/admin/users"]').setAttribute('href', '/admin/users?sort=newest');
document.documentElement.style.setProperty(
`--${APP_ID}-navbar-height`,
`${document.querySelector('#navbar').offsetHeight}px`
);
document.head.append(Object.assign(
document.createElement('link'),
{ 'rel': 'stylesheet', 'href': 'https://cdn.jsdelivr.net/npm/boxicons@2.1.4/css/boxicons.min.css' }
));
{
const styles = new CSSStyleSheet();
// language=CSS
await styles.replace(`
.__${APP_ID}__tweaksMenu {
padding: 0 !important;
justify-content: center;
width: var(--width);
height: var(--height);
font-size: 1.25rem;
color: inherit;
}
.__${APP_ID}__tweaksModal__container {
position: fixed !important;
}
.__${APP_ID}__tweaksModal__header {
display: flex !important;
justify-content: space-between;
}
.__${APP_ID}__tweaksModal__header__close {
border: none;
background: none;
padding: 0;
font-size: 1.5rem;
cursor: pointer;
}
.__${APP_ID}__tweaksModal__form {
display: flex !important;
flex-direction: column;
row-gap: 0.5rem;
}
.__${APP_ID}__tweaksModal__form .checkbox label {
user-select: initial;
}
.__${APP_ID}--isFixCommentButtonsAlignment div:has(> #status-button) {
display: flex;
justify-content: flex-end;
column-gap: 0.5rem;
}
.__${APP_ID}--isFixCommentButtonsAlignment #status-button {
margin-bottom: 0;
display: flex;
align-items: center;
justify-content: center;
}
.__${APP_ID}--isNavbarSticky [role="main"] {
padding-top: var(--${APP_ID}-navbar-height);
}
.__${APP_ID}--isNavbarSticky #navbar {
position: fixed;
width: 100%;
z-index: 1;
}
.__${APP_ID}--isCodeViewScrollable .code-view,
.__${APP_ID}--isCodeViewScrollable .code-block {
overflow: auto;
}
.__${APP_ID}--isCodeViewScrollable .code-view .code-inner,
.__${APP_ID}--isCodeViewScrollable .code-block .chroma {
white-space: pre;
}
`);
document.adoptedStyleSheets = [...document.adoptedStyleSheets, styles];
}
})();