25 |
26 |
69 |
70 |
71 |
79 |
80 |
81 |
82 |
83 |
84 |
Welcome to SNotePad!
85 |
Start by adding a folder using the button below:
86 |
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 |
98 |
99 |
100 |
101 |
105 |
Message here
106 |
110 |
111 |
112 |
113 |
114 |
115 |
116 |
117 |
118 |
119 |
120 |
--------------------------------------------------------------------------------
/html/js/backend.js:
--------------------------------------------------------------------------------
1 | (function($) {
2 | "use strict"; // Start of use strict
3 | const VERSION = 3
4 |
5 | const DEFAULT_VALUES = {
6 | 'paths': {}
7 | }
8 |
9 | const DEBUG = (new URLSearchParams(window.location.search)).get('debug') === 'true'
10 |
11 | console.log('version', VERSION)
12 |
13 | setTimeout(() => {
14 | if (typeof AndroidInterface === 'undefined' && DEBUG === false) {
15 | window.showToast("Android interface not available. Probably I'm incompatible with your phone altogether :-(");
16 | }
17 | },
18 | 1000
19 | )
20 |
21 | let callbacks = {}
22 |
23 | window.requestFolderSelection = () => {
24 | if (DEBUG) {
25 | let i = Math.floor(Math.random() * 9999) + 1
26 | window.requestFolderSelectionSuccess('/storage/path-to-folder-' + i + '/Folder ' + i)
27 | return
28 | }
29 |
30 | AndroidInterface.initiateFolderSelection();
31 | }
32 |
33 | window.requestFolderSelectionSuccess = (path) => {
34 | let paths = JSON.parse(window.readPreferences('paths'));
35 | if (paths === null) {
36 | paths = []
37 | }
38 |
39 | paths.push(path)
40 |
41 | window.writePreferences('paths', JSON.stringify(paths))
42 | window.uiUpdateFolders(paths)
43 | window.lunchFolderView(path)
44 | }
45 |
46 | window.requestScanFolder = (path, callback) => {
47 | callbacks['scanFolder' + path] = callback
48 | if (DEBUG) {
49 | let files = []
50 | const numberWords = [
51 | "", "one", "two", "three", "four", "five",
52 | "six", "seven", "eight", "nine", "ten"
53 | ];
54 | for (let i = 1; i <= 10; i++) {
55 | const filename = 'File ' + i
56 | const content = numberWords[i]
57 | const date = new Date(); // Gets the current date and time
58 | date.setDate(date.getDate() - i)
59 | files.push({
60 | 'filename': filename,
61 | 'content': content,
62 | 'date': date.toLocaleDateString()
63 | })
64 | }
65 | window.scanFolderCallback(path, JSON.stringify(files))
66 |
67 | return
68 | }
69 |
70 | AndroidInterface.initiateReadFolder(path, true)
71 | }
72 |
73 | window.scanFolderCallback = (path, folderContentJson, isError) => {
74 | callbacks['scanFolder' + path](path, JSON.parse(folderContentJson), isError)
75 | delete callbacks['scanFolder' + path]
76 | }
77 |
78 | window.releaseFolder = (path) => {
79 | const pathsJson = window.readPreferences('paths')
80 | let paths = JSON.parse(pathsJson);
81 | paths.splice(
82 | $.inArray(path, paths),
83 | 1
84 | )
85 | if (!DEBUG) {
86 | AndroidInterface.releaseFolder(path);
87 | }
88 |
89 | window.writePreferences('paths', JSON.stringify(paths))
90 | window.uiUpdateFolders(paths)
91 | }
92 |
93 | window.deleteFile = (path) => {
94 | if (!DEBUG) {
95 | AndroidInterface.deleteFile(path);
96 | }
97 | }
98 |
99 | window.readPreferences = (key) => {
100 | result = window.localStorage.getItem(key)
101 | if (result === undefined) {
102 | result = DEFAULT_VALUES[key]
103 | }
104 |
105 | return result
106 | }
107 |
108 | window.writePreferences = (key, value) => {
109 | window.localStorage.setItem(key, value);
110 | }
111 |
112 | window.requestReadFolder = (path, callback) => {
113 | callbacks['readFolder' + path] = callback
114 | if (DEBUG) {
115 | let files = []
116 | for (let i = 1; i <= 10; i++) {
117 | const filename = 'File ' + i
118 | const date = new Date(); // Gets the current date and time
119 | date.setDate(date.getDate() - i)
120 | files.push({
121 | 'filename': filename,
122 | 'date': date.toLocaleDateString()
123 | })
124 | }
125 | window.readFolderCallback(path, JSON.stringify(files))
126 |
127 | return
128 | }
129 |
130 | AndroidInterface.initiateReadFolder(path, false)
131 | }
132 |
133 | window.readFolderCallback = (path, folderContentJson, isError) => {
134 | callbacks['readFolder' + path](
135 | path,
136 | isError ? folderContentJson : JSON.parse(folderContentJson),
137 | isError
138 | )
139 | delete callbacks['readFolder' + path]
140 | }
141 |
142 | window.requestReadFile = (path, callback) => {
143 | callbacks['readFile' + path] = callback
144 | if (DEBUG) {
145 | let content = '# Sample Content\n\n *' + path + '*\n\n'
146 | for (let i = 1; i <= 30; i++) {
147 | content = content + '- Line ' + i + '\n'
148 | }
149 |
150 | window.readFileCallback(path, content)
151 |
152 | return
153 | }
154 |
155 | AndroidInterface.initiateReadFile(path)
156 | }
157 |
158 | window.readFileCallback = (path, fileContent, isError) => {
159 | callbacks['readFile' + path](path, fileContent, isError)
160 | delete callbacks['readFile' + path]
161 | }
162 |
163 | window.requestWriteFile = (path, fileContent, callback) => {
164 | callbacks['writeFile' + path] = callback
165 | if (DEBUG) {
166 | window.writeFileCallback(path, path)
167 |
168 | return
169 | }
170 |
171 | AndroidInterface.initiateWriteFile(path, fileContent)
172 | }
173 |
174 | window.writeFileCallback = (originalPath, path, isError) => {
175 | callbacks['writeFile' + originalPath](path, isError)
176 | delete callbacks['writeFile' + originalPath]
177 | }
178 |
179 | if (DEBUG) {
180 | $("#btnBack").removeClass('d-none')
181 | }
182 | })(jQuery); // End of use strict
183 |
--------------------------------------------------------------------------------
/html/js/confirmModal.js:
--------------------------------------------------------------------------------
1 | (function($) {
2 | "use strict"; // Start of use strict
3 | // Get the modal instance
4 | const confirmModal = new bootstrap.Modal($('#confirmModal'));
5 |
6 | window.confirmModal = (
7 | title,
8 | message,
9 | confirmCallback,
10 | cancelCallback
11 | ) => {
12 | $('#confirmModalLabel').text(title)
13 | $('#confirmModalMessage').html(message)
14 |
15 | // Handle the click on the final confirmation button inside the modal
16 | $('#confirmBtn').off('click').on('click', () => {
17 | // Hide the modal
18 | confirmModal.hide()
19 |
20 | confirmCallback()
21 | });
22 |
23 | // Handle the click on the final confirmation button inside the modal
24 | let cancelButton = $('#cancelBtn');
25 | cancelButton.off('click');
26 | if (cancelCallback) {
27 | cancelButton.on('click', cancelCallback);
28 | }
29 |
30 | // Show the modal
31 | confirmModal.show();
32 | }
33 | })(jQuery); // End of use strict
34 |
--------------------------------------------------------------------------------
/html/js/folderView.js:
--------------------------------------------------------------------------------
1 | (function($) {
2 | "use strict"; // Start of use strict
3 |
4 | // Fuse.js options
5 | const FUSE_OPTIONS = {
6 | // includeScore: true, // Uncomment to see scores for debugging/ranking
7 | // threshold: 0.4, // Adjusts fuzziness (0=exact match, 1=match anything). Default 0.6 is often fine.
8 | keys: [
9 | {
10 | name: 'filename', // Field to search
11 | weight: 0.7 // Higher weight = more importance
12 | },
13 | {
14 | name: 'content', // Field to search
15 | weight: 0.3 // Lower weight = less importance
16 | }
17 | // 'date' is intentionally omitted, so it won't be searched
18 | ]
19 | };
20 |
21 | var currentPath = ''
22 | var folderContent = []
23 |
24 | // Function to set the initial/default icon for the sort dropdown toggle
25 | function setDefaultSortIcon(sort, order) {
26 |
27 | $('#sortDropdown i').removeClass().addClass(
28 | 'fas fa-'
29 | + (
30 | sort === 'filename'
31 | ? (order === 'asc' ? 'arrow-down-a-z' : 'arrow-up-z-a')
32 | : (order === 'asc' ? 'arrow-down-short-wide' : 'arrow-up-wide-short')
33 | )
34 | + ' fa-fw'
35 | ); // Remove existing classes, add new ones
36 | }
37 |
38 | function renderFolderContent() {
39 | const $itemsDiv = $("#items");
40 | $itemsDiv.empty()
41 | $.each(folderContent, (i, file) => {
42 | $itemsDiv.append(
43 | window.renderTemplate(
44 | {
45 | 'path': currentPath + '/' + file.filename,
46 | 'basename': file.filename,
47 | 'date': file.date,
48 | 'id': i
49 | },
50 | 'folderView-file'
51 | )
52 | )
53 | })
54 |
55 | let fileToDeleteId = null; // Variable to store the ID of the file to delete
56 | let $itemToDelete = null; // Variable to store the jQuery element to remove
57 | let fileToDeleteName = null
58 |
59 | $('.btn-delete').on('click', function(button) {
60 | fileToDeleteId = $(this).data('id')
61 | $itemToDelete = $('#div-file-' + fileToDeleteId)
62 | fileToDeleteName = $('#div-filename-' + fileToDeleteId).text()
63 |
64 | window.confirmModal(
65 | 'Delete File',
66 | 'Are you sure you want to permanently delete this file?
' + fileToDeleteName,
67 | () => {
68 | if (fileToDeleteId !== null && $itemToDelete) {
69 | let path = $('#div-file-path-' + fileToDeleteId).text()
70 | console.log("Deleting file with path:", path);
71 | window.deleteFile(path)
72 |
73 | $itemToDelete.fadeOut(300, function() {
74 | $(this).remove();
75 | });
76 |
77 | window.showToast('File deleted:
' + fileToDeleteName)
78 | // Reset the stored variables
79 | fileToDeleteId = null;
80 | $itemToDelete = null;
81 | } else {
82 | console.error("Could not find file ID or element to delete.");
83 | }
84 | }
85 | )
86 | })
87 |
88 | $('.div-file-open').on('click', function(button) {
89 | let id = $(this).data('id')
90 | let path = $('#div-file-path-' + id).text()
91 | window.editorOpenFile(path)
92 | })
93 | }
94 |
95 | function arrangeitems(sort, order) {
96 | folderContent = window.sortArray(folderContent, sort, order)
97 | renderFolderContent()
98 | window.writePreferences('sort', sort)
99 | window.writePreferences('order', order)
100 |
101 | setDefaultSortIcon(sort, order)
102 | }
103 |
104 | function performSearch(fuse, data, searchTerm) {
105 | searchTerm = searchTerm.trim(); // Remove leading/trailing whitespace
106 | console.log("Search triggered. Term:", searchTerm);
107 |
108 | let results = [];
109 | if (searchTerm) {
110 | // Perform the search using Fuse.js
111 | // Fuse returns an array of objects: { item: originalObject, refIndex: ..., score: ... }
112 | folderContent = fuse.search(searchTerm).map(result => result.item);;
113 | } else {
114 | folderContent = data;
115 | }
116 |
117 | // Display the results
118 | renderFolderContent();
119 | }
120 |
121 | let confirmExit = (callback) => {
122 | callback(true)
123 | }
124 |
125 | // Define the function that should be called for settings
126 | window.lunchFolderView = (path) => {
127 | currentPath = path
128 | window.showLoading('Loading folder...')
129 | console.log("lunchFolderView function called!");
130 |
131 | window.setNavBar('navbar-folderView', {});
132 |
133 | window.setPage(
134 | 'folderView',
135 | {
136 | 'path': path,
137 | 'basename': window.getHumanReadableBasename(path)
138 | }
139 | );
140 | $('#btn-add-file').on('click', function() {
141 | let path = $("#div-folderView-path").text()
142 | window.editorNewFile(path)
143 | });
144 |
145 | window.requestReadFolder(path, window.folderViewReadFolderCallback)
146 | window.writePreferences('lastPath', path)
147 | window.historyPush(
148 | lunchFolderView,
149 | [path],
150 | confirmExit
151 | )
152 | }
153 |
154 | window.folderViewReadFolderCallback = (path, content, isError) => {
155 | if (isError) {
156 | window.showToast(content, isError)
157 |
158 | return
159 | }
160 |
161 | folderContent = content
162 | const lastSort = window.readPreferences('sort')
163 | const lastOrder = window.readPreferences('order')
164 | arrangeitems(
165 | lastSort === null ? 'date' : lastSort,
166 | lastOrder === null ? 'desc' : lastOrder
167 | )
168 |
169 | // Event handler for when a sort option is clicked
170 | $('[aria-labelledby="sortDropdown"] .dropdown-item').on('click', function(e) {
171 | e.preventDefault(); // Prevent default anchor behavior
172 |
173 | // Find the text of the selected option
174 | let sort = $(this).find('.font-weight-bold').text();
175 | switch (sort) {
176 | case 'Name A - Z': arrangeitems('filename', 'asc'); break
177 | case 'Name Z - A': arrangeitems('filename', 'desc'); break
178 | case 'Date Newest First': arrangeitems('date', 'desc'); break
179 | default: arrangeitems('date', 'asc'); break
180 | }
181 | });
182 |
183 | var data = null;
184 | var fuse = null;
185 | let searchHandler = function() {
186 | let searchTerm = '';
187 | if ($(this).is('input')) {
188 | searchTerm = $(this).val();
189 | } else { // It's the button click
190 | searchTerm = $(this).closest('.input-group').find('.folder-search-input').val();
191 | }
192 |
193 | if (fuse === null) {
194 | // Initialize Fuse with the data and options
195 | // This creates the index the first time.
196 | let scanSuccessHandler = (path, scannedData, isError) => {
197 | if (isError) {
198 | window.showToast(path, isError)
199 |
200 | return
201 | }
202 |
203 | data = scannedData;
204 | fuse = new Fuse(data, FUSE_OPTIONS);
205 | window.hideLoading()
206 | performSearch(fuse, data, searchTerm)
207 | }
208 | window.showLoading('Indexing folder...')
209 | window.requestScanFolder(currentPath, scanSuccessHandler)
210 |
211 | return
212 | }
213 |
214 | performSearch(fuse, data, searchTerm)
215 | }
216 |
217 | // Attach event listener for typing in the search input fields
218 | $('.folder-search-input').on('input', searchHandler);
219 |
220 | // Attach event listener for clicking the search buttons
221 | // Select buttons within the input group append divs
222 | $('.input-group-append .btn').on('click', searchHandler);
223 |
224 | // Also handle form submission (e.g., pressing Enter in the input)
225 | $('form.navbar-search').on('submit', function(event) {
226 | event.preventDefault(); // Prevent default form submission
227 | searchHandler.call($(this).find('.folder-search-input'));
228 | });
229 |
230 | window.hideSidebar()
231 | window.hideLoading()
232 | }
233 |
234 | })(jQuery); // End of use strict
235 |
--------------------------------------------------------------------------------
/html/js/history.js:
--------------------------------------------------------------------------------
1 | (function($) {
2 | "use strict"; // Start of use strict
3 | let history = []
4 |
5 | window.historyPush = (
6 | actionHandler,
7 | args,
8 | confirmExit
9 | ) => {
10 | history.push({
11 | 'actionHandler': actionHandler,
12 | 'args': args,
13 | 'confirmExit': confirmExit
14 | })
15 | console.log('historyPush', history)
16 | }
17 |
18 | const confirmExitResultHandler = (allowed) => {
19 | console.log('confirmExitResultHandler', allowed, history)
20 | if (!allowed) {
21 | return
22 | }
23 |
24 | if (history.length < 2) {
25 | return //Nothing to go back to
26 | }
27 |
28 | //First pop the current action as we don't need it anymore
29 | history.pop()
30 | //Now we get to the action the user was doing before the current one:
31 | lastAction = history.pop()
32 | if (lastAction === undefined) {
33 | return
34 | }
35 |
36 | lastAction['actionHandler'](...lastAction['args'])
37 | }
38 |
39 | window.handleBackPress = () => {
40 | console.log('handleBackPress', history)
41 | //The last item in the history stack, is always the current action.
42 | const currentAction = history.at(-1)
43 | if (currentAction === undefined) {
44 | return
45 | }
46 |
47 | currentAction['confirmExit'](confirmExitResultHandler)
48 | }
49 |
50 | window.getCurrentAction = () => {
51 | lastHistoryItem = history.at(-1)
52 | if (lastHistoryItem === undefined) {
53 | return null
54 | }
55 |
56 | return lastHistoryItem['actionHandler'] ?? null
57 | }
58 |
59 | $("#btnBack").on('click', window.handleBackPress)
60 | })(jQuery); // End of use strict
61 |
--------------------------------------------------------------------------------
/html/js/loading.js:
--------------------------------------------------------------------------------
1 | (function($) {
2 | "use strict"; // Start of use strict
3 | window.showLoading = (prompt) => {
4 | const $loadingOverlay = $('#loading-overlay');
5 | $('#loading-prompt').text(prompt)
6 | $loadingOverlay.removeClass('hidden');
7 | }
8 |
9 | window.hideLoading = () => {
10 | $('#loading-overlay').addClass('hidden');
11 | }
12 | })(jQuery); // End of use strict
13 |
--------------------------------------------------------------------------------
/html/js/page.js:
--------------------------------------------------------------------------------
1 | (function($) {
2 | let confirmExit = (callback) => {
3 | callback(true)
4 | }
5 |
6 | window.lunchPage = (page) => {
7 | console.log("lunchPage function called: " + page);
8 |
9 | const title = page[0].toUpperCase() + page.slice(1)
10 | window.setNavBar('navbar-page', {'title': title});
11 |
12 | window.setPage(page, {});
13 |
14 | window.hideSidebar()
15 | window.historyPush(
16 | window.lunchPage,
17 | [page],
18 | confirmExit
19 | )
20 | }
21 | })(jQuery); // End of use strict
22 |
23 |
--------------------------------------------------------------------------------
/html/js/settings.js:
--------------------------------------------------------------------------------
1 | (function($) {
2 | let confirmExit = (callback) => {
3 | callback(true)
4 | }
5 |
6 | // --- Update UI Controls ---
7 | const updateAppearanceUI = (selectedTheme, selectedStyle, selectedMode) => {
8 | // Update Theme Selector
9 | const themeSelect = document.getElementById('themeSelect');
10 | if (themeSelect) {
11 | themeSelect.value = selectedTheme;
12 | } else {
13 | console.warn("Theme select element not found");
14 | }
15 |
16 | const styleSelect = document.getElementById('styleSelect');
17 | if (styleSelect) {
18 | styleSelect.value = selectedStyle;
19 | } else {
20 | console.warn("Style select element not found");
21 | }
22 |
23 | // Update Mode Radios
24 | const modeRadio = document.querySelector(`input[name="ui-mode"][value="${selectedMode}"]`);
25 | if (modeRadio) {
26 | modeRadio.checked = true;
27 | } else {
28 | console.warn(`Mode radio for value "${selectedMode}" not found`);
29 | // Fallback check 'auto' if invalid value somehow stored
30 | const autoRadio = document.querySelector('input[name="ui-mode"][value="auto"]');
31 | if (autoRadio) autoRadio.checked = true;
32 | }
33 | };
34 |
35 | window.lunchSettings = () => {
36 | console.log("lunchSettings function called!");
37 |
38 | window.setNavBar('navbar-settings', {});
39 |
40 | window.setPage('settings', {});
41 |
42 | // --- Initialize Appearance ---
43 | const initialTheme = window.getStoredTheme();
44 | const initialStyle = window.getStoredStyle();
45 | const initialMode = window.getStoredMode();
46 | updateAppearanceUI(initialTheme, initialStyle, initialMode);
47 |
48 | // --- Event Listener for Theme Selector ---
49 | const themeSelect = document.getElementById('themeSelect');
50 | if(themeSelect) {
51 | themeSelect.addEventListener('change', (event) => {
52 | const newTheme = event.target.value;
53 | const currentStyle = window.getStoredStyle();
54 | const currentMode = window.getStoredMode(); // Get current mode setting
55 | window.setStoredTheme(newTheme); // Store the new theme choice
56 | window.applyAppearance(newTheme, currentStyle, currentMode); // Apply the new theme with current mode
57 | });
58 | } else {
59 | console.error("Theme select element not found during event listener setup");
60 | }
61 |
62 | // --- Event Listener for Style Selector ---
63 | const styleSelect = document.getElementById('styleSelect');
64 | if(styleSelect) {
65 | styleSelect.addEventListener('change', (event) => {
66 | const currentTheme = window.getStoredTheme();
67 | const newStyle = event.target.value;
68 | const currentMode = window.getStoredMode(); // Get current mode setting
69 | window.setStoredStyle(newStyle); // Store the new theme choice
70 | window.applyAppearance(currentTheme, newStyle, currentMode); // Apply the new theme with current mode
71 | });
72 | } else {
73 | console.error("Style select element not found during event listener setup");
74 | }
75 |
76 | // --- Event Listeners for Mode Radio Buttons ---
77 | document.querySelectorAll('input[name="ui-mode"]').forEach(radio => {
78 | radio.addEventListener('change', (event) => {
79 | const newMode = event.target.value;
80 | const currentStyle = window.getStoredStyle();
81 | const currentTheme = window.getStoredTheme(); // Get current theme setting
82 | window.setStoredMode(newMode); // Store the new mode choice
83 | window.applyAppearance(currentTheme, currentStyle, newMode); // Apply the current theme with new mode
84 | });
85 | });
86 |
87 | $('#btn-add-folder').on('click', function() {
88 | window.requestFolderSelection()
89 | })
90 |
91 | window.settingsUpdateFolders(
92 | JSON.parse(
93 | window.readPreferences('paths')
94 | )
95 | )
96 |
97 | window.hideSidebar()
98 | window.historyPush(
99 | window.lunchSettings,
100 | [],
101 | confirmExit
102 | )
103 | }
104 |
105 | window.settingsUpdateFolders = (paths) => {
106 | const $foldersDiv = $("#folders");
107 | if ($foldersDiv === undefined) {
108 | return
109 | }
110 |
111 | $foldersDiv.empty()
112 | $.each(paths, (i, path) => {
113 | $foldersDiv.append(
114 | window.renderTemplate(
115 | {
116 | 'basename': window.getHumanReadableBasename(path),
117 | 'path': path,
118 | 'id': i
119 | },
120 | 'settings-folder'
121 | )
122 | )
123 | })
124 |
125 |
126 | let folderToReleaseId = null; // Variable to store the ID of the file to delete
127 | let $itemToDelete = null; // Variable to store the jQuery element to remove
128 | let folderToReleaseName = null
129 |
130 | $('.btn-delete').on('click', function(button) {
131 | folderToReleaseId = $(this).data('id')
132 | $itemToDelete = $('#div-folder-' + folderToReleaseId)
133 | folderToReleaseName = $('#div-folder-name-' + folderToReleaseId).text()
134 |
135 | window.confirmModal(
136 | 'Release Folder',
137 | 'Are you sure you want to release this folder?
Folder and its contents still remain on your device' + folderToReleaseName,
138 | () => {
139 | if (folderToReleaseId !== null && $itemToDelete) {
140 | let path = $('#code-folder-path-' + folderToReleaseId).text()
141 | console.log("Releasing folder with path:", path);
142 | window.releaseFolder(path)
143 |
144 | $itemToDelete.fadeOut(300, function() {
145 | $(this).remove();
146 | });
147 |
148 |
149 | window.showToast('Folder released:
' + folderToReleaseName)
150 | // Reset the stored variables
151 | folderToReleaseId = null;
152 | $itemToDelete = null;
153 | } else {
154 | console.error("Could not find folder ID or element to delete.");
155 | }
156 | }
157 | )
158 | })
159 | // Destroy previous instance if it exists to prevent duplicates
160 | if (window.settingsSortableInstance) {
161 | window.settingsSortableInstance.destroy();
162 | }
163 | window.settingsSortableInstance = new Sortable(($("#folders")[0]), {
164 | handle: '.drag-handle',
165 | animation: 150,
166 | onEnd: () => {
167 | let paths = [];
168 |
169 | $('#folders').find('.folder-path').each(function() {
170 | paths.push($(this).text())
171 | })
172 | window.writePreferences('paths', JSON.stringify(paths))
173 | window.sidebarUpdateFolders(paths)
174 | }
175 | })
176 | }
177 | })(jQuery); // End of use strict
178 |
179 |
--------------------------------------------------------------------------------
/html/js/sidebar.js:
--------------------------------------------------------------------------------
1 | (function($) {
2 | "use strict"; // Start of use strict
3 | const sidebar = $("#accordionSidebar")
4 |
5 | // Toggle the side navigation
6 | $("#sidebarToggle, #sidebarToggleTop").on('click', function(e) {
7 | $("body").toggleClass("sidebar-toggled");
8 | sidebar.toggleClass("toggled");
9 | if (sidebar.hasClass("toggled")) {
10 | $('.sidebar .collapse').collapse('hide');
11 | };
12 | });
13 |
14 | // Close any open menu accordions when window is resized below 768px
15 | $(window).resize(function() {
16 | if ($(window).width() < 768) {
17 | $('.sidebar .collapse').collapse('hide');
18 | };
19 |
20 | // Toggle the side navigation when window is resized below 480px
21 | if ($(window).width() < 480 && !sidebar.hasClass("toggled")) {
22 | $("body").addClass("sidebar-toggled");
23 | sidebar.addClass("toggled");
24 | $('.sidebar .collapse').collapse('hide');
25 | };
26 | });
27 |
28 | // Prevent the content wrapper from scrolling when the fixed side navigation hovered over
29 | $('body.fixed-nav .sidebar').on('mousewheel DOMMouseScroll wheel', function(e) {
30 | if ($(window).width() > 768) {
31 | var e0 = e.originalEvent,
32 | delta = e0.wheelDelta || -e0.detail;
33 | this.scrollTop += (delta < 0 ? 1 : -1) * 30;
34 | e.preventDefault();
35 | }
36 | });
37 |
38 | window.sidebarUpdateFolders = (paths) => {
39 | sidebar.find(".sidebar-folder").remove();
40 | const $div = $("#sidebarFoldersBegin");
41 |
42 | // Insert the new HTML content provided in the 'template' variable
43 | // immediately after the button.
44 | let $lastElement = $div
45 | $.each(paths, (i, path) => {
46 | let active = ''
47 | let editorCurrentPath = window.getEditorCurrentPath()
48 | if (editorCurrentPath !== null) {
49 | if (path === window.dirname(editorCurrentPath)) {
50 | active = 'active'
51 | }
52 | } else {
53 | let folderViewCurrentPath = window.getFolderViewCurrentPath()
54 | if (folderViewCurrentPath !== null && path === folderViewCurrentPath) {
55 | active = 'active'
56 | }
57 | }
58 |
59 | const $newItem = $(
60 | window.renderTemplate(
61 | {
62 | 'basename': window.getHumanReadableBasename(path),
63 | 'path': path,
64 | 'id': i,
65 | 'active': active
66 | },
67 | 'sidebar-folder'
68 | )
69 | )
70 |
71 | $lastElement.after(
72 | $newItem
73 | )
74 | $lastElement = $newItem
75 | })
76 | }
77 |
78 | window.sidebarHighlightItem = ($item) => {
79 | // Find the parent li.nav-item of the clicked link
80 | let $parentLi = $item.closest('li.nav-item')
81 |
82 | // Find all li.nav-item elements within the same parent container
83 | // (e.g., all direct children of the surrounding UL/OL)
84 | // and remove the 'active' class from all of them.
85 | // This ensures only one item is active at a time within this group.
86 | sidebar.children('li.nav-item').removeClass('active')
87 |
88 | // Add the 'active' class specifically to the parent li of the clicked link.
89 | $parentLi.addClass('active')
90 | }
91 |
92 | $(document).on('click', ".sidebar .nav-link", function(event) {
93 | // Prevent the default link behavior (e.g., navigating to '#')
94 | event.preventDefault();
95 | window.sidebarHighlightItem($(this))
96 | })
97 | $(document).on('click', '.sidebar-folder', function(event) {
98 | // Prevent the default link behavior (e.g., navigating to '#')
99 | event.preventDefault();
100 |
101 | // 'this' refers to the specific '.sidebar-folder' link that was clicked.
102 | let $clickedLink = $(this);
103 |
104 |
105 | // Get the value from the 'data-id' attribute of the clicked link
106 | let folderId = $clickedLink.data('id'); // e.g., "123"
107 |
108 | // Construct the specific ID for the hidden div using the retrieved folderId
109 | // This creates a selector like "#sidebar-folder-item-path-123"
110 | let pathDivSelector = '#sidebar-folder-item-path-' + folderId;
111 |
112 | // Find the specific hidden div using its ID, searching only *within*
113 | // the clicked link's descendants for efficiency and context.
114 | let $pathDiv = $clickedLink.find(pathDivSelector);
115 |
116 | let pathValue = $pathDiv.text();
117 |
118 | // Display the path in an alert dialog
119 | window.lunchFolderView(pathValue)
120 | });
121 |
122 | window.hideSidebar = () => {
123 | const $button = $("#sidebarToggleTop");
124 | var isSmallScreen = $button.is(':visible');
125 |
126 | // Check if the sidebar is currently shown (does not have 'toggled' class)
127 | var isSidebarVisible = !$('#accordionSidebar').hasClass('toggled');
128 |
129 | // If it's a small screen AND the sidebar is visible
130 | if (isSmallScreen && isSidebarVisible) {
131 | // Trigger a click on the button to hide the sidebar
132 | $button.trigger('click');
133 | }
134 | }
135 |
136 | $('.sidebar-item-simple-page').on('click', function() {
137 | var pageId = $(this).attr('id');
138 | lunchPage(pageId);
139 | });
140 | })(jQuery); // End of use strict
141 |
--------------------------------------------------------------------------------
/html/js/sort.js:
--------------------------------------------------------------------------------
1 | (function($) {
2 | "use strict"; // Start of use strict
3 |
4 | /**
5 | * Sorts the array by filename in ascending order (A-Z).
6 | * Note: Modifies the original array in place.
7 | * @param {Array