diff --git a/app/Http/Controllers/UserController.php b/app/Http/Controllers/UserController.php index 2c9203687..9a2f2c867 100644 --- a/app/Http/Controllers/UserController.php +++ b/app/Http/Controllers/UserController.php @@ -289,6 +289,27 @@ class UserController extends Controller return response('', 204); } + public function updateCodeLanguageFavourite(Request $request) + { + $validated = $this->validate($request, [ + 'language' => ['required', 'string', 'max:20'], + 'active' => ['required', 'bool'], + ]); + + $currentFavoritesStr = setting()->getForCurrentUser('code-language-favourites', ''); + $currentFavorites = array_filter(explode(',', $currentFavoritesStr)); + + $isFav = in_array($validated['language'], $currentFavorites); + if (!$isFav && $validated['active']) { + $currentFavorites[] = $validated['language']; + } else if ($isFav && !$validated['active']) { + $index = array_search($validated['language'], $currentFavorites); + array_splice($currentFavorites, $index, 1); + } + + setting()->putUser(user(), 'code-language-favourites', implode(',', $currentFavorites)); + } + /** * Changed the stored preference for a list sort order. */ diff --git a/resources/js/code.mjs b/resources/js/code.mjs index 5d4186dd0..eca941f1c 100644 --- a/resources/js/code.mjs +++ b/resources/js/code.mjs @@ -39,6 +39,7 @@ import 'codemirror/addon/scroll/scrollpastend'; // Value can be a mode string or a function that will receive the code content & return the mode string. // The function option is used in the event the exact mode could be dynamic depending on the code. const modeMap = { + bash: 'shell', css: 'css', c: 'text/x-csrc', java: 'text/x-java', @@ -88,7 +89,6 @@ const modeMap = { shell: 'shell', sh: 'shell', stext: 'text/x-stex', - bash: 'shell', toml: 'toml', ts: 'text/typescript', typescript: 'text/typescript', diff --git a/resources/js/components/code-editor.js b/resources/js/components/code-editor.js index 27ff56395..2d8031205 100644 --- a/resources/js/components/code-editor.js +++ b/resources/js/components/code-editor.js @@ -10,17 +10,20 @@ class CodeEditor { this.container = this.$refs.container; this.popup = this.$el; this.editorInput = this.$refs.editor; - this.languageLinks = this.$manyRefs.languageLink; + this.languageButtons = this.$manyRefs.languageButton; + this.languageOptionsContainer = this.$refs.languageOptionsContainer; this.saveButton = this.$refs.saveButton; this.languageInput = this.$refs.languageInput; this.historyDropDown = this.$refs.historyDropDown; this.historyList = this.$refs.historyList; + this.favourites = new Set(this.$opts.favourites.split(',')); this.callback = null; this.editor = null; this.history = {}; this.historyKey = 'code_history'; this.setupListeners(); + this.setupFavourites(); } setupListeners() { @@ -30,7 +33,7 @@ class CodeEditor { } }); - onSelect(this.languageLinks, event => { + onSelect(this.languageButtons, event => { const language = event.target.dataset.lang; this.languageInput.value = language; this.languageInputChange(language); @@ -49,6 +52,58 @@ class CodeEditor { }); } + setupFavourites() { + for (const button of this.languageButtons) { + this.setupFavouritesForButton(button); + } + + this.sortLanguageList(); + } + + /** + * @param {HTMLButtonElement} button + */ + setupFavouritesForButton(button) { + const language = button.dataset.lang; + let isFavorite = this.favourites.has(language); + button.setAttribute('data-favourite', isFavorite ? 'true' : 'false'); + + onChildEvent(button.parentElement, '.lang-option-favorite-toggle', 'click', () => { + isFavorite = !isFavorite; + isFavorite ? this.favourites.add(language) : this.favourites.delete(language); + button.setAttribute('data-favourite', isFavorite ? 'true' : 'false'); + + window.$http.patch('/settings/users/update-code-language-favourite', { + language: language, + active: isFavorite + }); + + this.sortLanguageList(); + if (isFavorite) { + button.scrollIntoView({block: "center", behavior: "smooth"}); + } + }); + } + + sortLanguageList() { + const sortedParents = this.languageButtons.sort((a, b) => { + const aFav = a.dataset.favourite === 'true'; + const bFav = b.dataset.favourite === 'true'; + + if (aFav && !bFav) { + return -1; + } else if (bFav && !aFav) { + return 1; + } + + return a.dataset.lang > b.dataset.lang ? 1 : -1; + }).map(button => button.parentElement); + + for (const parent of sortedParents) { + this.languageOptionsContainer.append(parent); + } + } + save() { if (this.callback) { this.callback(this.editor.getValue(), this.languageInput.value); @@ -94,15 +149,13 @@ class CodeEditor { languageInputChange(language) { this.updateEditorMode(language); const inputLang = language.toLowerCase(); - let matched = false; - for (const link of this.languageLinks) { + for (const link of this.languageButtons) { const lang = link.dataset.lang.toLowerCase().trim(); - const isMatch = inputLang && lang.startsWith(inputLang); + const isMatch = inputLang === lang; link.classList.toggle('active', isMatch); - if (isMatch && !matched) { + if (isMatch) { link.scrollIntoView({block: "center", behavior: "smooth"}); - matched = true; } } } diff --git a/resources/sass/_components.scss b/resources/sass/_components.scss index 4509c2b9a..cbaf17760 100644 --- a/resources/sass/_components.scss +++ b/resources/sass/_components.scss @@ -666,17 +666,48 @@ body.flexbox-support #entity-selector-wrap .popup-body .form-group { text-align: left; font-family: $mono; font-size: 0.7rem; + padding-left: 24px + $-xs; &:hover, &.active { background-color: var(--color-primary-light); color: var(--color-primary); } } +.code-editor button.lang-option-favorite-toggle { + position: absolute; + top: 0; + left: 0; + width: 28px; + font-size: 1rem; + border: 0; + line-height: 1; + padding: 2px; + z-index: 2; + height: 100%; + text-align: center; + color: var(--color-primary); + svg { + margin: 0; + } +} + +.code-editor button[data-favourite="true"] ~ .action-favourite, +.code-editor button[data-favourite="false"] ~ .action-unfavourite { + display: none; +} + +.code-editor .action-favourite { + opacity: 0.5; +} +.code-editor button:hover ~ .action-favourite { + opacity: 1; +} + .code-editor label { background-color: var(--color-primary-light); width: 100%; color: var(--color-primary); - padding: $-xxs $-m; + padding: $-xxs $-s; margin-bottom: 0; } @@ -691,7 +722,8 @@ body.flexbox-support #entity-selector-wrap .popup-body .form-group { border-radius: 0; border: 0; border-bottom: 1px solid #DDD; - padding: $-xs $-m; + padding: $-xs $-s; + height: auto; } .code-editor-main { @@ -705,6 +737,10 @@ body.flexbox-support #entity-selector-wrap .popup-body .form-group { } } +.code-editor-body-wrap { + height: 80vh; +} + @include smaller-than($s) { .code-editor .lang-options { display: none; diff --git a/resources/views/pages/parts/code-editor.blade.php b/resources/views/pages/parts/code-editor.blade.php index 4ac688692..e86282d73 100644 --- a/resources/views/pages/parts/code-editor.blade.php +++ b/resources/views/pages/parts/code-editor.blade.php @@ -1,5 +1,7 @@
-