Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion templates/repo/diff/box.tmpl
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
</button>
<script>
// Default to true if unset
const diffTreeVisible = localStorage?.getItem('diff_file_tree_visible') !== 'false';
const diffTreeVisible = window.localUserSettings.getBoolean('diff_file_tree_visible', true);
const diffTreeBtn = document.querySelector('.diff-toggle-file-tree-button');
const diffTreeIcon = `.octicon-sidebar-${diffTreeVisible ? 'expand' : 'collapse'}`;
diffTreeBtn.querySelector(diffTreeIcon).classList.remove('tw-hidden');
Expand Down
2 changes: 1 addition & 1 deletion templates/shared/combomarkdowneditor.tmpl
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@
>{{.TextareaContent}}</textarea>
</text-expander>
<script>
if (localStorage?.getItem('markdown-editor-monospace') === 'true') {
if (window.localUserSettings.getBoolean('markdown-editor-monospace')) {
document.querySelector('.markdown-text-editor').classList.add('tw-font-mono');
Comment thread
wxiaoguang marked this conversation as resolved.
}
</script>
Expand Down
5 changes: 3 additions & 2 deletions web_src/js/components/DiffFileTree.vue
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,15 @@ import {toggleElem} from '../utils/dom.ts';
import {diffTreeStore} from '../modules/diff-file.ts';
import {setFileFolding} from '../features/file-fold.ts';
import {onMounted, onUnmounted} from 'vue';
import {localUserSettings} from '../modules/user-settings.ts';

const LOCAL_STORAGE_KEY = 'diff_file_tree_visible';

const store = diffTreeStore();

onMounted(() => {
// Default to true if unset
store.fileTreeIsVisible = localStorage.getItem(LOCAL_STORAGE_KEY) !== 'false';
store.fileTreeIsVisible = localUserSettings.getBoolean(LOCAL_STORAGE_KEY, true);
document.querySelector('.diff-toggle-file-tree-button')!.addEventListener('click', toggleVisibility);

hashChangeListener();
Expand Down Expand Up @@ -43,7 +44,7 @@ function toggleVisibility() {

function updateVisibility(visible: boolean) {
store.fileTreeIsVisible = visible;
localStorage.setItem(LOCAL_STORAGE_KEY, store.fileTreeIsVisible.toString());
localUserSettings.setBoolean(LOCAL_STORAGE_KEY, store.fileTreeIsVisible);
updateState(store.fileTreeIsVisible);
}

Expand Down
15 changes: 4 additions & 11 deletions web_src/js/components/RepoActionView.vue
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import {renderAnsi} from '../render/ansi.ts';
import {POST, DELETE} from '../modules/fetch.ts';
import type {IntervalId} from '../types.ts';
import {toggleFullScreen} from '../utils.ts';
import {localUserSettings} from '../modules/user-settings.ts';

// see "models/actions/status.go", if it needs to be used somewhere else, move it to a shared file like "types/actions.ts"
type RunStatus = 'unknown' | 'waiting' | 'running' | 'success' | 'failure' | 'cancelled' | 'skipped' | 'blocked';
Expand Down Expand Up @@ -71,15 +72,6 @@ type LocaleStorageOptions = {
expandRunning: boolean;
};

function getLocaleStorageOptions(): LocaleStorageOptions {
try {
const optsJson = localStorage.getItem('actions-view-options');
if (optsJson) return JSON.parse(optsJson);
} catch {}
// if no options in localStorage, or failed to parse, return default options
return {autoScroll: true, expandRunning: false};
}

export default defineComponent({
name: 'RepoActionView',
components: {
Expand All @@ -106,7 +98,8 @@ export default defineComponent({
},

data() {
const {autoScroll, expandRunning} = getLocaleStorageOptions();
const defaultViewOptions: LocaleStorageOptions = {autoScroll: true, expandRunning: false};
const {autoScroll, expandRunning} = localUserSettings.getJsonObject('actions-view-options', defaultViewOptions);
return {
// internal state
loadingAbortController: null as AbortController | null,
Expand Down Expand Up @@ -224,7 +217,7 @@ export default defineComponent({
methods: {
saveLocaleStorageOptions() {
const opts: LocaleStorageOptions = {autoScroll: this.optionAlwaysAutoScroll, expandRunning: this.optionAlwaysExpandRunning};
localStorage.setItem('actions-view-options', JSON.stringify(opts));
localUserSettings.setJsonObject('actions-view-options', opts);
},

// get the job step logs container ('.job-step-logs')
Expand Down
7 changes: 4 additions & 3 deletions web_src/js/features/citation.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import {getCurrentLocale} from '../utils.ts';
import {fomanticQuery} from '../modules/fomantic/base.ts';
import {localUserSettings} from '../modules/user-settings.ts';

const {pageData} = window.config;

Expand Down Expand Up @@ -38,7 +39,7 @@ export async function initCitationFileCopyContent() {
if ((!citationCopyApa && !citationCopyBibtex) || !inputContent) return;

const updateUi = () => {
const isBibtex = (localStorage.getItem('citation-copy-format') || defaultCitationFormat) === 'bibtex';
const isBibtex = localUserSettings.getString('citation-copy-format', defaultCitationFormat) === 'bibtex';
const copyContent = (isBibtex ? citationCopyBibtex : citationCopyApa).getAttribute('data-text')!;
inputContent.value = copyContent;
citationCopyBibtex.classList.toggle('primary', isBibtex);
Expand All @@ -55,12 +56,12 @@ export async function initCitationFileCopyContent() {
updateUi();

citationCopyApa.addEventListener('click', () => {
localStorage.setItem('citation-copy-format', 'apa');
localUserSettings.setString('citation-copy-format', 'apa');
updateUi();
});

citationCopyBibtex.addEventListener('click', () => {
localStorage.setItem('citation-copy-format', 'bibtex');
localUserSettings.setString('citation-copy-format', 'bibtex');
updateUi();
});

Expand Down
42 changes: 28 additions & 14 deletions web_src/js/features/comp/ComboMarkdownEditor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import {DropzoneCustomEventReloadFiles, initDropzone} from '../dropzone.ts';
import {createTippy} from '../../modules/tippy.ts';
import {fomanticQuery} from '../../modules/fomantic/base.ts';
import type EasyMDE from 'easymde';
import {localUserSettings} from '../../modules/user-settings.ts';

/**
* validate if the given textarea is non-empty.
Expand Down Expand Up @@ -81,6 +82,8 @@ export class ComboMarkdownEditor {
textareaMarkdownToolbar: HTMLElement;
textareaAutosize: any;

buttonMonospace: HTMLButtonElement;

dropzone: HTMLElement | null;
attachedDropzoneInst: any;

Expand Down Expand Up @@ -140,19 +143,13 @@ export class ComboMarkdownEditor {
if (el.nodeName === 'BUTTON' && !el.getAttribute('type')) el.setAttribute('type', 'button');
}

const monospaceButton = this.container.querySelector('.markdown-switch-monospace')!;
const monospaceEnabled = localStorage?.getItem('markdown-editor-monospace') === 'true';
const monospaceText = monospaceButton.getAttribute(monospaceEnabled ? 'data-disable-text' : 'data-enable-text')!;
monospaceButton.setAttribute('data-tooltip-content', monospaceText);
monospaceButton.setAttribute('aria-checked', String(monospaceEnabled));
monospaceButton.addEventListener('click', (e) => {
this.buttonMonospace = this.container.querySelector('.markdown-switch-monospace')!;
this.applyMonospace();
this.buttonMonospace.addEventListener('click', (e) => {
e.preventDefault();
const enabled = localStorage?.getItem('markdown-editor-monospace') !== 'true';
localStorage.setItem('markdown-editor-monospace', String(enabled));
this.textarea.classList.toggle('tw-font-mono', enabled);
const text = monospaceButton.getAttribute(enabled ? 'data-disable-text' : 'data-enable-text')!;
monospaceButton.setAttribute('data-tooltip-content', text);
monospaceButton.setAttribute('aria-checked', String(enabled));
const enabled = !localUserSettings.getBoolean('markdown-editor-monospace');
localUserSettings.setBoolean('markdown-editor-monospace', enabled);
applyMonospaceToAllEditors();
});

if (this.supportEasyMDE) {
Expand Down Expand Up @@ -403,10 +400,27 @@ export class ComboMarkdownEditor {
}

get userPreferredEditor(): string {
return window.localStorage.getItem(`markdown-editor-${this.previewMode ?? 'default'}`) || '';
return localUserSettings.getString(`markdown-editor-${this.previewMode ?? 'default'}`);
}

set userPreferredEditor(s: string) {
window.localStorage.setItem(`markdown-editor-${this.previewMode ?? 'default'}`, s);
localUserSettings.setString(`markdown-editor-${this.previewMode ?? 'default'}`, s);
}

applyMonospace() {
const enabled = localUserSettings.getBoolean('markdown-editor-monospace');
const text = this.buttonMonospace.getAttribute(enabled ? 'data-disable-text' : 'data-enable-text')!;
this.textarea.classList.toggle('tw-font-mono', enabled);
this.buttonMonospace.setAttribute('data-tooltip-content', text);
this.buttonMonospace.setAttribute('aria-checked', String(enabled));
}
}

function applyMonospaceToAllEditors() {
const editors = document.querySelectorAll<ComboMarkdownEditorContainer>('.combo-markdown-editor');
for (const editorContainer of editors) {
const editor = getComboMarkdownEditor(editorContainer);
if (editor) editor.applyMonospace();
}
}

Expand Down
9 changes: 5 additions & 4 deletions web_src/js/features/repo-common.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import RepoActivityTopAuthors from '../components/RepoActivityTopAuthors.vue';
import {createApp} from 'vue';
import {toOriginUrl} from '../utils/url.ts';
import {createTippy} from '../modules/tippy.ts';
import {localUserSettings} from '../modules/user-settings.ts';

async function onDownloadArchive(e: Event) {
e.preventDefault();
Expand Down Expand Up @@ -57,7 +58,7 @@ function initCloneSchemeUrlSelection(parent: Element) {
const tabSsh = parent.querySelector('.repo-clone-ssh');
const tabTea = parent.querySelector('.repo-clone-tea');
const updateClonePanelUi = function() {
let scheme = localStorage.getItem('repo-clone-protocol')!;
let scheme = localUserSettings.getString('repo-clone-protocol');
if (!['https', 'ssh', 'tea'].includes(scheme)) {
scheme = 'https';
}
Expand Down Expand Up @@ -114,15 +115,15 @@ function initCloneSchemeUrlSelection(parent: Element) {
updateClonePanelUi();
// tabSsh or tabHttps might not both exist, eg: guest view, or one is disabled by the server
tabHttps?.addEventListener('click', () => {
localStorage.setItem('repo-clone-protocol', 'https');
localUserSettings.setString('repo-clone-protocol', 'https');
updateClonePanelUi();
});
tabSsh?.addEventListener('click', () => {
localStorage.setItem('repo-clone-protocol', 'ssh');
localUserSettings.setString('repo-clone-protocol', 'ssh');
updateClonePanelUi();
});
tabTea?.addEventListener('click', () => {
localStorage.setItem('repo-clone-protocol', 'tea');
localUserSettings.setString('repo-clone-protocol', 'tea');
updateClonePanelUi();
});
elCloneUrlInput.addEventListener('focus', () => {
Expand Down
1 change: 1 addition & 0 deletions web_src/js/globals.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,7 @@ interface Window {
push: (e: ErrorEvent & PromiseRejectionEvent) => void | number,
},
codeEditors: any[], // export editor for customization
localUserSettings: typeof import('./modules/user-settings.ts').localUserSettings,
Comment thread
silverwind marked this conversation as resolved.

// various captcha plugins
grecaptcha: any,
Expand Down
1 change: 1 addition & 0 deletions web_src/js/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import './bootstrap.ts';
import './globals.ts';

import './webcomponents/index.ts';
import './modules/user-settings.ts'; // templates also need to use localUserSettings in inline scripts
import {onDomReady} from './utils/dom.ts';

// TODO: There is a bug in htmx, it incorrectly checks "readyState === 'complete'" when the DOM tree is ready and won't trigger DOMContentLoaded
Expand Down
72 changes: 72 additions & 0 deletions web_src/js/modules/user-settings.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
// Some people deploy Gitea under a subpath, so it needs prefix to avoid local storage key conflicts.
// And these keys are for user settings only, it also needs a specific prefix,
// in case in the future there are other uses of local storage, and/or we need to clear some keys when the quota is exceeded.
const itemKeyPrefix = 'gitea:setting:';

function handleLocalStorageError(e: any) {
// in the future, maybe we need to handle quota exceeded errors differently
console.error('Error using local storage for user settings', e);
}

function getLocalStorageUserSetting(settingKey: string): string | null {
const legacyKey = settingKey;
const itemKey = `${itemKeyPrefix}${settingKey}`;
try {
const legacyValue = localStorage?.getItem(legacyKey) ?? null;
const value = localStorage?.getItem(itemKey) ?? null; // avoid undefined
if (value !== null && legacyValue !== null) {
// if both values exist, remove the legacy one
localStorage?.removeItem(legacyKey);
} else if (value === null && legacyValue !== null) {
// migrate legacy value to new key
localStorage?.removeItem(legacyKey);
localStorage?.setItem(itemKey, legacyValue);
return legacyValue;
}
return value;
} catch (e) {
handleLocalStorageError(e);
}
return null;
}

function setLocalStorageUserSetting(settingKey: string, value: string) {
const legacyKey = settingKey;
const itemKey = `${itemKeyPrefix}${settingKey}`;
try {
localStorage?.removeItem(legacyKey);
localStorage?.setItem(itemKey, value);
} catch (e) {
handleLocalStorageError(e);
}
}

export const localUserSettings = {
getString: (key: string, def: string = ''): string => {
return getLocalStorageUserSetting(key) ?? def;
},
setString: (key: string, value: string) => {
setLocalStorageUserSetting(key, value);
},
getBoolean: (key: string, def: boolean = false): boolean => {
return localUserSettings.getString(key, String(def)) === 'true';
},
setBoolean: (key: string, value: boolean) => {
localUserSettings.setString(key, String(value));
},
getJsonObject: <T extends Record<string, any>>(key: string, def: T): T => {
const value = getLocalStorageUserSetting(key);
try {
const decoded = value !== null ? JSON.parse(value) : def;
return decoded ?? def;
} catch (e) {
console.error(`Unable to parse JSON value for local user settings ${key}=${value}`, e);
}
return def;
},
setJsonObject: <T extends Record<string, any>>(key: string, value: T) => {
localUserSettings.setString(key, JSON.stringify(value));
},
};

window.localUserSettings = localUserSettings;
Comment thread
wxiaoguang marked this conversation as resolved.