Skip to content

Commit 82965a8

Browse files
committed
feat: add city seach
1 parent 09c27ed commit 82965a8

File tree

1 file changed

+287
-0
lines changed

1 file changed

+287
-0
lines changed

index.html

+287
Original file line numberDiff line numberDiff line change
@@ -845,6 +845,184 @@ <h3>Loading Error</h3>
845845
// Initialize theme
846846
initializeThemeSelector();
847847

848+
// Cities search functionality
849+
let citiesData = [];
850+
let searchTimeout = null;
851+
852+
// Load cities data
853+
async function loadCitiesData() {
854+
try {
855+
const response = await fetch(
856+
"https://unpkg.com/[email protected]/cities.json",
857+
);
858+
citiesData = await response.json();
859+
console.log(
860+
"Cities data loaded:",
861+
citiesData.length,
862+
"cities",
863+
);
864+
} catch (error) {
865+
console.error("Error loading cities data:", error);
866+
showError("Failed to load cities data");
867+
}
868+
}
869+
870+
// Search cities function
871+
function searchCities(query) {
872+
if (!query || query.length < 2) {
873+
return [];
874+
}
875+
876+
query = query.toLowerCase();
877+
return citiesData
878+
.filter((city) => city.name.toLowerCase().includes(query))
879+
.slice(0, 10); // Limit to 10 results
880+
}
881+
882+
// Display search results
883+
function displaySearchResults(results) {
884+
const resultsContainer = document.getElementById(
885+
"city-search-results",
886+
);
887+
resultsContainer.innerHTML = "";
888+
889+
if (results.length === 0) {
890+
resultsContainer.innerHTML =
891+
'<div class="city-result">No cities found</div>';
892+
resultsContainer.classList.add("active");
893+
return;
894+
}
895+
896+
results.forEach((city) => {
897+
const resultElement = document.createElement("div");
898+
resultElement.className = "city-result";
899+
resultElement.innerHTML = `
900+
<div class="city-name">${city.name}</div>
901+
<div class="city-info">${city.country}${city.lat}, ${city.lng}</div>
902+
`;
903+
904+
resultElement.addEventListener("click", () => {
905+
showTimezone(
906+
parseFloat(city.lat),
907+
parseFloat(city.lng),
908+
map,
909+
);
910+
document.getElementById("city-search").value = "";
911+
resultsContainer.classList.remove("active");
912+
});
913+
914+
resultsContainer.appendChild(resultElement);
915+
});
916+
917+
resultsContainer.classList.add("active");
918+
}
919+
920+
// Handle search input
921+
document.getElementById("city-search").addEventListener(
922+
"input",
923+
(e) => {
924+
const query = e.target.value.trim();
925+
const resultsContainer = document.getElementById(
926+
"city-search-results",
927+
);
928+
929+
if (!query) {
930+
resultsContainer.classList.remove("active");
931+
return;
932+
}
933+
934+
if (searchTimeout) {
935+
clearTimeout(searchTimeout);
936+
}
937+
938+
searchTimeout = setTimeout(() => {
939+
const results = searchCities(query);
940+
displaySearchResults(results);
941+
}, 300);
942+
},
943+
);
944+
945+
// Close search results when clicking outside
946+
document.addEventListener("click", (e) => {
947+
const resultsContainer = document.getElementById(
948+
"city-search-results",
949+
);
950+
const searchInput = document.getElementById("city-search");
951+
952+
if (
953+
!resultsContainer.contains(e.target) &&
954+
e.target !== searchInput
955+
) {
956+
resultsContainer.classList.remove("active");
957+
}
958+
});
959+
960+
// Load cities data when the page loads
961+
loadCitiesData();
962+
963+
// Add keyboard navigation for search results
964+
document.getElementById("city-search").addEventListener(
965+
"keydown",
966+
(e) => {
967+
const resultsContainer = document.getElementById(
968+
"city-search-results",
969+
);
970+
const results = resultsContainer.getElementsByClassName(
971+
"city-result",
972+
);
973+
let currentFocus = -1;
974+
975+
// Get currently focused element
976+
for (let i = 0; i < results.length; i++) {
977+
if (results[i].classList.contains("focused")) {
978+
currentFocus = i;
979+
break;
980+
}
981+
}
982+
983+
if (e.key === "ArrowDown") {
984+
e.preventDefault();
985+
currentFocus++;
986+
if (currentFocus >= results.length) currentFocus = 0;
987+
} else if (e.key === "ArrowUp") {
988+
e.preventDefault();
989+
currentFocus--;
990+
if (currentFocus < 0) currentFocus = results.length - 1;
991+
} else if (e.key === "Enter" && currentFocus > -1) {
992+
e.preventDefault();
993+
results[currentFocus].click();
994+
} else if (e.key === "Escape") {
995+
resultsContainer.classList.remove("active");
996+
e.target.blur();
997+
} else {
998+
return;
999+
}
1000+
1001+
// Update focus
1002+
Array.from(results).forEach((result) =>
1003+
result.classList.remove("focused")
1004+
);
1005+
if (currentFocus > -1) {
1006+
results[currentFocus].classList.add("focused");
1007+
results[currentFocus].scrollIntoView({ block: "nearest" });
1008+
}
1009+
},
1010+
);
1011+
1012+
// Add focus style for keyboard navigation
1013+
const focusStyle = document.createElement("style");
1014+
focusStyle.textContent = `
1015+
.city-result.focused {
1016+
background-color: var(--button-bg);
1017+
color: white;
1018+
}
1019+
[data-theme="high-contrast"] .city-result.focused {
1020+
background-color: yellow;
1021+
color: black;
1022+
}
1023+
`;
1024+
document.head.appendChild(focusStyle);
1025+
8481026
// 添加键盘快捷键面板切换功能
8491027
window.toggleShortcutsHelp = function () {
8501028
const panel = document.getElementById("shortcuts-panel");
@@ -2649,6 +2827,98 @@ <h3>Loading Error</h3>
26492827
~ .leaflet-tile-container {
26502828
filter: brightness(0.65);
26512829
}
2830+
2831+
/* City Search Styles */
2832+
.city-search-results {
2833+
max-height: 200px;
2834+
overflow-y: auto;
2835+
margin-top: 8px;
2836+
border: 1px solid var(--input-border);
2837+
border-radius: 4px;
2838+
background: var(--panel-bg);
2839+
display: none;
2840+
}
2841+
2842+
.city-search-results.active {
2843+
display: block;
2844+
}
2845+
2846+
.city-result {
2847+
padding: 8px 12px;
2848+
cursor: pointer;
2849+
border-bottom: 1px solid var(--input-border);
2850+
transition: background-color 0.2s;
2851+
}
2852+
2853+
.city-result:last-child {
2854+
border-bottom: none;
2855+
}
2856+
2857+
.city-result:hover {
2858+
background-color: var(--button-bg);
2859+
color: white;
2860+
}
2861+
2862+
.city-name {
2863+
font-weight: 500;
2864+
margin-bottom: 2px;
2865+
}
2866+
2867+
.city-info {
2868+
font-size: 12px;
2869+
opacity: 0.8;
2870+
}
2871+
2872+
[data-theme="dark"] .city-result:hover {
2873+
background-color: var(--button-hover);
2874+
}
2875+
2876+
[data-theme="high-contrast"] .city-result:hover {
2877+
background-color: yellow;
2878+
color: black;
2879+
}
2880+
2881+
#city-search {
2882+
width: 100%;
2883+
padding: 8px 12px;
2884+
border: 1px solid var(--input-border);
2885+
border-radius: 4px;
2886+
font-size: 14px;
2887+
background: var(--panel-bg);
2888+
color: var(--text-color);
2889+
}
2890+
2891+
#city-search:focus {
2892+
outline: none;
2893+
border-color: var(--input-focus);
2894+
box-shadow: 0 0 0 3px rgba(66, 153, 225, 0.1);
2895+
}
2896+
2897+
/* Scrollbar styles for city search results */
2898+
.city-search-results::-webkit-scrollbar {
2899+
width: 8px;
2900+
}
2901+
2902+
.city-search-results::-webkit-scrollbar-track {
2903+
background: var(--panel-bg);
2904+
}
2905+
2906+
.city-search-results::-webkit-scrollbar-thumb {
2907+
background: var(--input-border);
2908+
border-radius: 4px;
2909+
}
2910+
2911+
.city-search-results::-webkit-scrollbar-thumb:hover {
2912+
background: var(--button-bg);
2913+
}
2914+
2915+
/* Loading indicator for city search */
2916+
.city-search-loading {
2917+
padding: 8px 12px;
2918+
text-align: center;
2919+
color: var(--text-color);
2920+
font-style: italic;
2921+
}
26522922
</style>
26532923
</head>
26542924

@@ -2729,6 +2999,23 @@ <h2 id="coordinates-title" class="panel-section-title">
27292999
</button>
27303000
</section>
27313001

3002+
<section class="panel-section" aria-labelledby="city-search-title">
3003+
<h2 id="city-search-title" class="panel-section-title">
3004+
City Search
3005+
</h2>
3006+
<div class="input-group">
3007+
<input
3008+
type="text"
3009+
id="city-search"
3010+
name="city-search"
3011+
placeholder="Search for a city..."
3012+
aria-label="Search for a city"
3013+
autocomplete="off"
3014+
/>
3015+
</div>
3016+
<div id="city-search-results" class="city-search-results"></div>
3017+
</section>
3018+
27323019
<section class="panel-section" aria-labelledby="marker-controls-title">
27333020
<h2 id="marker-controls-title" class="panel-section-title">
27343021
Marker Controls

0 commit comments

Comments
 (0)