mirror of
https://github.com/0PandaDEV/Qopy.git
synced 2025-04-21 13:14:04 +02:00
feat: implement selected result management with selection logic and UI integration
This commit is contained in:
parent
2d3d92f3c8
commit
7b624bd352
3 changed files with 627 additions and 505 deletions
|
@ -6,363 +6,412 @@ $text: #e5dfd5;
|
||||||
$text2: #ada9a1;
|
$text2: #ada9a1;
|
||||||
$mutedtext: #78756f;
|
$mutedtext: #78756f;
|
||||||
|
|
||||||
.bg {
|
$search-height: 56px;
|
||||||
width: 100%;
|
$sidebar-width: 286px;
|
||||||
height: 100%;
|
$bottom-bar-height: 39px;
|
||||||
|
$info-panel-height: 160px;
|
||||||
|
$content-view-height: calc(
|
||||||
|
100% - $search-height - $info-panel-height - $bottom-bar-height
|
||||||
|
);
|
||||||
|
|
||||||
|
main {
|
||||||
|
width: 100vw;
|
||||||
|
height: 100vh;
|
||||||
background-color: $primary;
|
background-color: $primary;
|
||||||
border: 1px solid $divider;
|
border: 1px solid $divider;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
border-radius: 12px;
|
border-radius: 12px;
|
||||||
z-index: -1;
|
justify-content: space-between;
|
||||||
position: fixed;
|
|
||||||
outline: none;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.search {
|
.content {
|
||||||
|
height: 376px;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
position: fixed;
|
display: flex;
|
||||||
top: 0;
|
|
||||||
left: 0;
|
|
||||||
height: 56px;
|
|
||||||
background-color: transparent;
|
|
||||||
outline: none;
|
|
||||||
border: none;
|
|
||||||
font-size: 18px;
|
|
||||||
color: $text;
|
|
||||||
padding-inline: 16px;
|
|
||||||
border-bottom: 1px solid $divider;
|
|
||||||
font-family: SFRoundedMedium;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.results {
|
.results {
|
||||||
position: absolute;
|
|
||||||
width: 286px;
|
|
||||||
top: 55px;
|
|
||||||
left: 0;
|
|
||||||
height: 417px;
|
|
||||||
border-right: 1px solid $divider;
|
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
padding-inline: 8px;
|
padding: 14px 8px;
|
||||||
padding-bottom: 8px;
|
gap: 8px;
|
||||||
overflow-y: auto;
|
width: 286px;
|
||||||
overflow-x: hidden;
|
border-right: 1px solid var(--border);
|
||||||
z-index: 3;
|
|
||||||
|
|
||||||
.result {
|
|
||||||
height: 40px;
|
|
||||||
font-size: 14px;
|
|
||||||
align-items: center;
|
|
||||||
display: flex;
|
|
||||||
padding: 10px;
|
|
||||||
padding-inline: 10px;
|
|
||||||
letter-spacing: 0.5px;
|
|
||||||
gap: 10px;
|
|
||||||
overflow: hidden;
|
|
||||||
text-overflow: clip;
|
|
||||||
white-space: nowrap;
|
|
||||||
color: $text;
|
|
||||||
}
|
|
||||||
|
|
||||||
.result {
|
|
||||||
cursor: pointer;
|
|
||||||
|
|
||||||
&.selected {
|
|
||||||
background-color: $divider;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.time-separator {
|
.time-separator {
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
color: $text2;
|
color: $text2;
|
||||||
font-family: SFRoundedSemiBold;
|
font-family: SFRoundedSemiBold;
|
||||||
padding-left: 8px;
|
padding-left: 8px;
|
||||||
padding-bottom: 8px;
|
|
||||||
padding-top: 14px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.favicon {
|
.group {
|
||||||
width: 18px;
|
& + .group {
|
||||||
height: 18px;
|
margin-top: 16px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.image {
|
.time-separator {
|
||||||
width: 18px;
|
margin-bottom: 8px;
|
||||||
height: 18px;
|
}
|
||||||
|
|
||||||
|
.results-group {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.favicon,
|
||||||
|
.image,
|
||||||
.icon {
|
.icon {
|
||||||
width: 18px;
|
width: 18px;
|
||||||
height: 18px;
|
height: 18px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.content {
|
// .bg {
|
||||||
position: absolute;
|
// width: 100%;
|
||||||
top: 55px;
|
// height: 100%;
|
||||||
left: 285px;
|
// background-color: $primary;
|
||||||
height: 220px;
|
// border: 1px solid $divider;
|
||||||
font-family: CommitMono !important;
|
// border-radius: 12px;
|
||||||
font-size: 12px;
|
// z-index: -1;
|
||||||
letter-spacing: 1;
|
// position: fixed;
|
||||||
border-radius: 10px;
|
// outline: none;
|
||||||
width: 465px;
|
// display: flex;
|
||||||
white-space: pre-wrap;
|
// flex-direction: column;
|
||||||
word-wrap: break-word;
|
// }
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
align-items: center;
|
|
||||||
overflow: hidden;
|
|
||||||
z-index: 2;
|
|
||||||
color: $text;
|
|
||||||
|
|
||||||
&:not(:has(.image)) {
|
// .search {
|
||||||
padding: 8px;
|
// width: 100%;
|
||||||
}
|
// height: $search-height;
|
||||||
|
// background-color: transparent;
|
||||||
|
// outline: none;
|
||||||
|
// border: none;
|
||||||
|
// font-size: 18px;
|
||||||
|
// color: $text;
|
||||||
|
// padding-inline: 16px;
|
||||||
|
// border-bottom: 1px solid $divider;
|
||||||
|
// font-family: SFRoundedMedium;
|
||||||
|
// }
|
||||||
|
|
||||||
span {
|
// .main-container {
|
||||||
font-family: CommitMono !important;
|
// display: flex;
|
||||||
}
|
// flex: 1;
|
||||||
|
// }
|
||||||
|
|
||||||
.image {
|
// .results {
|
||||||
width: 100%;
|
// width: $sidebar-width;
|
||||||
height: 100%;
|
// height: calc(100vh - $search-height - $bottom-bar-height);
|
||||||
object-fit: contain;
|
// border-right: 1px solid $divider;
|
||||||
object-position: center;
|
// display: flex;
|
||||||
}
|
// flex-direction: column;
|
||||||
}
|
// padding-inline: 8px;
|
||||||
|
// padding-bottom: 8px;
|
||||||
|
// overflow-y: auto;
|
||||||
|
// overflow-x: hidden;
|
||||||
|
// z-index: 3;
|
||||||
|
|
||||||
.bottom-bar {
|
// .result {
|
||||||
height: 39px;
|
// height: 40px;
|
||||||
width: calc(100vw - 2px);
|
// font-size: 14px;
|
||||||
backdrop-filter: blur(18px);
|
// display: flex;
|
||||||
background-color: hsla(40, 3%, 16%, 0.8);
|
// align-items: center;
|
||||||
position: fixed;
|
// padding: 10px;
|
||||||
bottom: 1px;
|
// letter-spacing: 0.5px;
|
||||||
left: 1px;
|
// gap: 10px;
|
||||||
z-index: 100;
|
// overflow: hidden;
|
||||||
border-radius: 0 0 12px 12px;
|
// text-overflow: clip;
|
||||||
display: flex;
|
// white-space: nowrap;
|
||||||
flex-direction: row;
|
// color: $text;
|
||||||
justify-content: space-between;
|
// cursor: pointer;
|
||||||
padding-inline: 12px;
|
|
||||||
padding-right: 6px;
|
|
||||||
padding-top: 1px;
|
|
||||||
align-items: center;
|
|
||||||
font-size: 14px;
|
|
||||||
border-top: 1px solid $divider;
|
|
||||||
|
|
||||||
p {
|
// &.selected {
|
||||||
color: $text2;
|
// background-color: $divider;
|
||||||
}
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
.left {
|
// .time-separator {
|
||||||
display: flex;
|
// font-size: 12px;
|
||||||
align-items: center;
|
// color: $text2;
|
||||||
gap: 8px;
|
// font-family: SFRoundedSemiBold;
|
||||||
|
// padding-left: 8px;
|
||||||
|
// padding-bottom: 8px;
|
||||||
|
// padding-top: 14px;
|
||||||
|
// }
|
||||||
|
|
||||||
.logo {
|
// .favicon,
|
||||||
width: 18px;
|
// .image,
|
||||||
height: 18px;
|
// .icon {
|
||||||
}
|
// width: 18px;
|
||||||
}
|
// height: 18px;
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
.right {
|
// .right-panel {
|
||||||
display: flex;
|
// display: flex;
|
||||||
align-items: center;
|
// flex-direction: column;
|
||||||
|
// flex: 1;
|
||||||
|
// }
|
||||||
|
|
||||||
.paste p {
|
// .content {
|
||||||
color: $text;
|
// height: $content-view-height;
|
||||||
}
|
// font-family: CommitMono !important;
|
||||||
|
// font-size: 12px;
|
||||||
|
// letter-spacing: 1;
|
||||||
|
// border-radius: 10px;
|
||||||
|
// width: calc(100% - $sidebar-width);
|
||||||
|
// white-space: pre-wrap;
|
||||||
|
// word-wrap: break-word;
|
||||||
|
// display: flex;
|
||||||
|
// flex-direction: column;
|
||||||
|
// align-items: center;
|
||||||
|
// overflow: hidden;
|
||||||
|
// z-index: 2;
|
||||||
|
// color: $text;
|
||||||
|
|
||||||
.actions div {
|
// &:not(:has(.image)) {
|
||||||
display: flex;
|
// padding: 8px;
|
||||||
align-items: center;
|
// }
|
||||||
gap: 2px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.divider {
|
// span {
|
||||||
width: 2px;
|
// font-family: CommitMono !important;
|
||||||
height: 12px;
|
// }
|
||||||
background-color: $divider;
|
|
||||||
margin-left: 8px;
|
|
||||||
margin-right: 4px;
|
|
||||||
transition: all 0.2s;
|
|
||||||
}
|
|
||||||
|
|
||||||
.paste,
|
// .image {
|
||||||
.actions {
|
// width: 100%;
|
||||||
padding: 4px;
|
// height: 100%;
|
||||||
padding-left: 8px;
|
// object-fit: contain;
|
||||||
display: flex;
|
// object-position: center;
|
||||||
align-items: center;
|
// }
|
||||||
gap: 8px;
|
// }
|
||||||
border-radius: 7px;
|
|
||||||
background-color: transparent;
|
|
||||||
transition: all 0.2s;
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
|
|
||||||
.paste:hover,
|
// .bottom-bar {
|
||||||
.actions:hover {
|
// height: $bottom-bar-height;
|
||||||
background-color: $divider;
|
// width: 100%;
|
||||||
}
|
// backdrop-filter: blur(18px);
|
||||||
|
// background-color: hsla(40, 3%, 16%, 0.8);
|
||||||
|
// z-index: 100;
|
||||||
|
// border-radius: 0 0 12px 12px;
|
||||||
|
// display: flex;
|
||||||
|
// flex-direction: row;
|
||||||
|
// justify-content: space-between;
|
||||||
|
// padding-inline: 12px;
|
||||||
|
// padding-right: 6px;
|
||||||
|
// padding-top: 1px;
|
||||||
|
// align-items: center;
|
||||||
|
// font-size: 14px;
|
||||||
|
// border-top: 1px solid $divider;
|
||||||
|
|
||||||
&:hover .paste:hover ~ .divider,
|
// p {
|
||||||
&:hover .actions:hover ~ .divider {
|
// color: $text2;
|
||||||
opacity: 0;
|
// }
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.information {
|
// .left {
|
||||||
position: absolute;
|
// display: flex;
|
||||||
display: flex;
|
// align-items: center;
|
||||||
flex-direction: column;
|
// gap: 8px;
|
||||||
gap: 14px;
|
|
||||||
bottom: 39px;
|
|
||||||
left: 285px;
|
|
||||||
height: 160px;
|
|
||||||
width: 465px;
|
|
||||||
border-top: 1px solid $divider;
|
|
||||||
background-color: $primary;
|
|
||||||
padding: 14px;
|
|
||||||
z-index: 1;
|
|
||||||
|
|
||||||
.title {
|
// .logo {
|
||||||
font-family: SFRoundedSemiBold;
|
// width: 18px;
|
||||||
font-size: 12px;
|
// height: 18px;
|
||||||
letter-spacing: 0.6px;
|
// }
|
||||||
color: $text;
|
// }
|
||||||
}
|
|
||||||
|
|
||||||
.info-content {
|
// .right {
|
||||||
display: flex;
|
// display: flex;
|
||||||
gap: 0;
|
// align-items: center;
|
||||||
flex-direction: column;
|
|
||||||
|
|
||||||
.info-row {
|
// .paste p {
|
||||||
display: flex;
|
// color: $text;
|
||||||
width: 100%;
|
// }
|
||||||
font-size: 12px;
|
|
||||||
justify-content: space-between;
|
|
||||||
padding: 8px 0;
|
|
||||||
border-bottom: 1px solid $divider;
|
|
||||||
line-height: 1;
|
|
||||||
|
|
||||||
&:last-child {
|
// .actions div {
|
||||||
border-bottom: none;
|
// display: flex;
|
||||||
padding-bottom: 0;
|
// align-items: center;
|
||||||
}
|
// gap: 2px;
|
||||||
|
// }
|
||||||
|
|
||||||
&:first-child {
|
// .divider {
|
||||||
padding-top: 22px;
|
// width: 2px;
|
||||||
}
|
// height: 12px;
|
||||||
|
// background-color: $divider;
|
||||||
|
// margin-left: 8px;
|
||||||
|
// margin-right: 4px;
|
||||||
|
// transition: all 0.2s;
|
||||||
|
// }
|
||||||
|
|
||||||
p {
|
// .paste,
|
||||||
font-family: SFRoundedMedium;
|
// .actions {
|
||||||
color: $text2;
|
// padding: 4px;
|
||||||
font-weight: 500;
|
// padding-left: 8px;
|
||||||
flex-shrink: 0;
|
// display: flex;
|
||||||
}
|
// align-items: center;
|
||||||
|
// gap: 8px;
|
||||||
|
// border-radius: 7px;
|
||||||
|
// background-color: transparent;
|
||||||
|
// transition: all 0.2s;
|
||||||
|
// cursor: pointer;
|
||||||
|
// }
|
||||||
|
|
||||||
span {
|
// .paste:hover,
|
||||||
font-family: CommitMono;
|
// .actions:hover {
|
||||||
color: $text;
|
// background-color: $divider;
|
||||||
text-overflow: ellipsis;
|
// }
|
||||||
overflow: hidden;
|
|
||||||
white-space: nowrap;
|
|
||||||
margin-left: 32px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.clothoid-corner {
|
// &:hover .paste:hover ~ .divider,
|
||||||
clip-path: polygon(
|
// &:hover .actions:hover ~ .divider {
|
||||||
13.890123px 0px,
|
// opacity: 0;
|
||||||
calc(100% - 13.890123px) 0px,
|
// }
|
||||||
calc(100% - 12.723414px) 0.004211px,
|
// }
|
||||||
calc(100% - 11.556933px) 0.025635px,
|
// }
|
||||||
calc(100% - 10.391895px) 0.085062px,
|
|
||||||
calc(100% - 9.231074px) 0.199291px,
|
// .information {
|
||||||
calc(100% - 8.079275px) 0.382298px,
|
// height: $info-panel-height;
|
||||||
calc(100% - 6.947448px) 0.662609px,
|
// width: calc(100% - $sidebar-width);
|
||||||
calc(100% - 5.844179px) 1.039291px,
|
// border-top: 1px solid $divider;
|
||||||
calc(100% - 4.793324px) 1.542842px,
|
// background-color: $primary;
|
||||||
calc(100% - 3.811369px) 2.169728px,
|
// padding: 14px;
|
||||||
calc(100% - 2.926417px) 2.926417px,
|
// z-index: 1;
|
||||||
calc(100% - 2.169728px) 3.811369px,
|
// display: flex;
|
||||||
calc(100% - 1.542842px) 4.793324px,
|
// flex-direction: column;
|
||||||
calc(100% - 1.039291px) 5.844179px,
|
// gap: 14px;
|
||||||
calc(100% - 0.662609px) 6.947448px,
|
|
||||||
calc(100% - 0.382298px) 8.079275px,
|
// .title {
|
||||||
calc(100% - 0.199291px) 9.231074px,
|
// font-family: SFRoundedSemiBold;
|
||||||
calc(100% - 0.085062px) 10.391895px,
|
// font-size: 12px;
|
||||||
calc(100% - 0.025635px) 11.556933px,
|
// letter-spacing: 0.6px;
|
||||||
calc(100% - 0.004211px) 12.723414px,
|
// color: $text;
|
||||||
100% 13.890123px,
|
// }
|
||||||
100% calc(100% - 13.890123px),
|
|
||||||
calc(100% - 0.004211px) calc(100% - 12.723414px),
|
// .info-content {
|
||||||
calc(100% - 0.025635px) calc(100% - 11.556933px),
|
// display: flex;
|
||||||
calc(100% - 0.085062px) calc(100% - 10.391895px),
|
// gap: 0;
|
||||||
calc(100% - 0.199291px) calc(100% - 9.231074px),
|
// flex-direction: column;
|
||||||
calc(100% - 0.382298px) calc(100% - 8.079275px),
|
|
||||||
calc(100% - 0.662609px) calc(100% - 6.947448px),
|
// .info-row {
|
||||||
calc(100% - 1.039291px) calc(100% - 5.844179px),
|
// display: flex;
|
||||||
calc(100% - 1.542842px) calc(100% - 4.793324px),
|
// width: 100%;
|
||||||
calc(100% - 2.169728px) calc(100% - 3.811369px),
|
// font-size: 12px;
|
||||||
calc(100% - 2.926417px) calc(100% - 2.926417px),
|
// justify-content: space-between;
|
||||||
calc(100% - 3.811369px) calc(100% - 2.169728px),
|
// padding: 8px 0;
|
||||||
calc(100% - 4.793324px) calc(100% - 1.542842px),
|
// border-bottom: 1px solid $divider;
|
||||||
calc(100% - 5.844179px) calc(100% - 1.039291px),
|
// line-height: 1;
|
||||||
calc(100% - 6.947448px) calc(100% - 0.662609px),
|
|
||||||
calc(100% - 8.079275px) calc(100% - 0.382298px),
|
// &:last-child {
|
||||||
calc(100% - 9.231074px) calc(100% - 0.199291px),
|
// border-bottom: none;
|
||||||
calc(100% - 10.391895px) calc(100% - 0.085062px),
|
// padding-bottom: 0;
|
||||||
calc(100% - 11.556933px) calc(100% - 0.025635px),
|
// }
|
||||||
calc(100% - 12.723414px) calc(100% - 0.004211px),
|
|
||||||
calc(100% - 13.890123px) 100%,
|
// &:first-child {
|
||||||
13.890123px 100%,
|
// padding-top: 22px;
|
||||||
12.723414px calc(100% - 0.004211px),
|
// }
|
||||||
11.556933px calc(100% - 0.025635px),
|
|
||||||
10.391895px calc(100% - 0.085062px),
|
// p {
|
||||||
9.231074px calc(100% - 0.199291px),
|
// font-family: SFRoundedMedium;
|
||||||
8.079275px calc(100% - 0.382298px),
|
// color: $text2;
|
||||||
6.947448px calc(100% - 0.662609px),
|
// font-weight: 500;
|
||||||
5.844179px calc(100% - 1.039291px),
|
// flex-shrink: 0;
|
||||||
4.793324px calc(100% - 1.542842px),
|
// }
|
||||||
3.811369px calc(100% - 2.169728px),
|
|
||||||
2.926417px calc(100% - 2.926417px),
|
// span {
|
||||||
2.169728px calc(100% - 3.811369px),
|
// font-family: CommitMono;
|
||||||
1.542842px calc(100% - 4.793324px),
|
// color: $text;
|
||||||
1.039291px calc(100% - 5.844179px),
|
// text-overflow: ellipsis;
|
||||||
0.662609px calc(100% - 6.947448px),
|
// overflow: hidden;
|
||||||
0.382298px calc(100% - 8.079275px),
|
// white-space: nowrap;
|
||||||
0.199291px calc(100% - 9.231074px),
|
// margin-left: 32px;
|
||||||
0.085062px calc(100% - 10.391895px),
|
// }
|
||||||
0.025635px calc(100% - 11.556933px),
|
// }
|
||||||
0.004211px calc(100% - 12.723414px),
|
// }
|
||||||
0px calc(100% - 13.890123px),
|
// }
|
||||||
0px 13.890123px,
|
|
||||||
0.004211px 12.723414px,
|
// .clothoid-corner {
|
||||||
0.025635px 11.556933px,
|
// clip-path: polygon(
|
||||||
0.085062px 10.391895px,
|
// 13.890123px 0px,
|
||||||
0.199291px 9.231074px,
|
// calc(100% - 13.890123px) 0px,
|
||||||
0.382298px 8.079275px,
|
// calc(100% - 12.723414px) 0.004211px,
|
||||||
0.662609px 6.947448px,
|
// calc(100% - 11.556933px) 0.025635px,
|
||||||
1.039291px 5.844179px,
|
// calc(100% - 10.391895px) 0.085062px,
|
||||||
1.542842px 4.793324px,
|
// calc(100% - 9.231074px) 0.199291px,
|
||||||
2.169728px 3.811369px,
|
// calc(100% - 8.079275px) 0.382298px,
|
||||||
2.926417px 2.926417px,
|
// calc(100% - 6.947448px) 0.662609px,
|
||||||
3.811369px 2.169728px,
|
// calc(100% - 5.844179px) 1.039291px,
|
||||||
4.793324px 1.542842px,
|
// calc(100% - 4.793324px) 1.542842px,
|
||||||
5.844179px 1.039291px,
|
// calc(100% - 3.811369px) 2.169728px,
|
||||||
6.947448px 0.662609px,
|
// calc(100% - 2.926417px) 2.926417px,
|
||||||
8.079275px 0.382298px,
|
// calc(100% - 2.169728px) 3.811369px,
|
||||||
9.231074px 0.199291px,
|
// calc(100% - 1.542842px) 4.793324px,
|
||||||
10.391895px 0.085062px,
|
// calc(100% - 1.039291px) 5.844179px,
|
||||||
11.556933px 0.025635px,
|
// calc(100% - 0.662609px) 6.947448px,
|
||||||
12.723414px 0.004211px,
|
// calc(100% - 0.382298px) 8.079275px,
|
||||||
13.890123px 0px
|
// calc(100% - 0.199291px) 9.231074px,
|
||||||
);
|
// calc(100% - 0.085062px) 10.391895px,
|
||||||
}
|
// calc(100% - 0.025635px) 11.556933px,
|
||||||
|
// calc(100% - 0.004211px) 12.723414px,
|
||||||
|
// 100% 13.890123px,
|
||||||
|
// 100% calc(100% - 13.890123px),
|
||||||
|
// calc(100% - 0.004211px) calc(100% - 12.723414px),
|
||||||
|
// calc(100% - 0.025635px) calc(100% - 11.556933px),
|
||||||
|
// calc(100% - 0.085062px) calc(100% - 10.391895px),
|
||||||
|
// calc(100% - 0.199291px) calc(100% - 9.231074px),
|
||||||
|
// calc(100% - 0.382298px) calc(100% - 8.079275px),
|
||||||
|
// calc(100% - 0.662609px) calc(100% - 6.947448px),
|
||||||
|
// calc(100% - 1.039291px) calc(100% - 5.844179px),
|
||||||
|
// calc(100% - 1.542842px) calc(100% - 4.793324px),
|
||||||
|
// calc(100% - 2.169728px) calc(100% - 3.811369px),
|
||||||
|
// calc(100% - 2.926417px) calc(100% - 2.926417px),
|
||||||
|
// calc(100% - 3.811369px) calc(100% - 2.169728px),
|
||||||
|
// calc(100% - 4.793324px) calc(100% - 1.542842px),
|
||||||
|
// calc(100% - 5.844179px) calc(100% - 1.039291px),
|
||||||
|
// calc(100% - 6.947448px) calc(100% - 0.662609px),
|
||||||
|
// calc(100% - 8.079275px) calc(100% - 0.382298px),
|
||||||
|
// calc(100% - 9.231074px) calc(100% - 0.199291px),
|
||||||
|
// calc(100% - 10.391895px) calc(100% - 0.085062px),
|
||||||
|
// calc(100% - 11.556933px) calc(100% - 0.025635px),
|
||||||
|
// calc(100% - 12.723414px) calc(100% - 0.004211px),
|
||||||
|
// calc(100% - 13.890123px) 100%,
|
||||||
|
// 13.890123px 100%,
|
||||||
|
// 12.723414px calc(100% - 0.004211px),
|
||||||
|
// 11.556933px calc(100% - 0.025635px),
|
||||||
|
// 10.391895px calc(100% - 0.085062px),
|
||||||
|
// 9.231074px calc(100% - 0.199291px),
|
||||||
|
// 8.079275px calc(100% - 0.382298px),
|
||||||
|
// 6.947448px calc(100% - 0.662609px),
|
||||||
|
// 5.844179px calc(100% - 1.039291px),
|
||||||
|
// 4.793324px calc(100% - 1.542842px),
|
||||||
|
// 3.811369px calc(100% - 2.169728px),
|
||||||
|
// 2.926417px calc(100% - 2.926417px),
|
||||||
|
// 2.169728px calc(100% - 3.811369px),
|
||||||
|
// 1.542842px calc(100% - 4.793324px),
|
||||||
|
// 1.039291px calc(100% - 5.844179px),
|
||||||
|
// 0.662609px calc(100% - 6.947448px),
|
||||||
|
// 0.382298px calc(100% - 8.079275px),
|
||||||
|
// 0.199291px calc(100% - 9.231074px),
|
||||||
|
// 0.085062px calc(100% - 10.391895px),
|
||||||
|
// 0.025635px calc(100% - 11.556933px),
|
||||||
|
// 0.004211px calc(100% - 12.723414px),
|
||||||
|
// 0px calc(100% - 13.890123px),
|
||||||
|
// 0px 13.890123px,
|
||||||
|
// 0.004211px 12.723414px,
|
||||||
|
// 0.025635px 11.556933px,
|
||||||
|
// 0.085062px 10.391895px,
|
||||||
|
// 0.199291px 9.231074px,
|
||||||
|
// 0.382298px 8.079275px,
|
||||||
|
// 0.662609px 6.947448px,
|
||||||
|
// 1.039291px 5.844179px,
|
||||||
|
// 1.542842px 4.793324px,
|
||||||
|
// 2.169728px 3.811369px,
|
||||||
|
// 2.926417px 2.926417px,
|
||||||
|
// 3.811369px 2.169728px,
|
||||||
|
// 4.793324px 1.542842px,
|
||||||
|
// 5.844179px 1.039291px,
|
||||||
|
// 6.947448px 0.662609px,
|
||||||
|
// 8.079275px 0.382298px,
|
||||||
|
// 9.231074px 0.199291px,
|
||||||
|
// 10.391895px 0.085062px,
|
||||||
|
// 11.556933px 0.025635px,
|
||||||
|
// 12.723414px 0.004211px,
|
||||||
|
// 13.890123px 0px
|
||||||
|
// );
|
||||||
|
// }
|
||||||
|
|
54
lib/selectedResult.ts
Normal file
54
lib/selectedResult.ts
Normal file
|
@ -0,0 +1,54 @@
|
||||||
|
import type { HistoryItem } from '~/types/types'
|
||||||
|
|
||||||
|
interface GroupedHistory {
|
||||||
|
label: string
|
||||||
|
items: HistoryItem[]
|
||||||
|
}
|
||||||
|
|
||||||
|
export const selectedGroupIndex = ref(0)
|
||||||
|
export const selectedItemIndex = ref(0)
|
||||||
|
export const selectedElement = ref<HTMLElement | null>(null)
|
||||||
|
|
||||||
|
export const useSelectedResult = (groupedHistory: Ref<GroupedHistory[]>) => {
|
||||||
|
const selectedItem = computed<HistoryItem | null>(() => {
|
||||||
|
const group = groupedHistory.value[selectedGroupIndex.value]
|
||||||
|
return group?.items[selectedItemIndex.value] ?? null
|
||||||
|
})
|
||||||
|
|
||||||
|
const isSelected = (groupIndex: number, itemIndex: number): boolean => {
|
||||||
|
return selectedGroupIndex.value === groupIndex && selectedItemIndex.value === itemIndex
|
||||||
|
}
|
||||||
|
|
||||||
|
const selectNext = (): void => {
|
||||||
|
const currentGroup = groupedHistory.value[selectedGroupIndex.value]
|
||||||
|
if (selectedItemIndex.value < currentGroup.items.length - 1) {
|
||||||
|
selectedItemIndex.value++
|
||||||
|
} else if (selectedGroupIndex.value < groupedHistory.value.length - 1) {
|
||||||
|
selectedGroupIndex.value++
|
||||||
|
selectedItemIndex.value = 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const selectPrevious = (): void => {
|
||||||
|
if (selectedItemIndex.value > 0) {
|
||||||
|
selectedItemIndex.value--
|
||||||
|
} else if (selectedGroupIndex.value > 0) {
|
||||||
|
selectedGroupIndex.value--
|
||||||
|
selectedItemIndex.value = groupedHistory.value[selectedGroupIndex.value].items.length - 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const selectItem = (groupIndex: number, itemIndex: number): void => {
|
||||||
|
selectedGroupIndex.value = groupIndex
|
||||||
|
selectedItemIndex.value = itemIndex
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
selectedItem,
|
||||||
|
isSelected,
|
||||||
|
selectNext,
|
||||||
|
selectPrevious,
|
||||||
|
selectItem,
|
||||||
|
selectedElement
|
||||||
|
}
|
||||||
|
}
|
409
pages/index.vue
409
pages/index.vue
|
@ -1,5 +1,41 @@
|
||||||
<template>
|
<template>
|
||||||
<div class="bg" tabindex="0">
|
<main>
|
||||||
|
<TopBar
|
||||||
|
ref="topBar"
|
||||||
|
@search="searchHistory" />
|
||||||
|
<div class="content">
|
||||||
|
<OverlayScrollbarsComponent
|
||||||
|
class="results"
|
||||||
|
ref="resultsContainer"
|
||||||
|
:options="{ scrollbars: { autoHide: 'scroll' } }">
|
||||||
|
<div
|
||||||
|
v-for="(group, groupIndex) in groupedHistory"
|
||||||
|
:key="groupIndex"
|
||||||
|
class="group">
|
||||||
|
<div class="time-separator">{{ group.label }}</div>
|
||||||
|
<div class="results-group">
|
||||||
|
<Result
|
||||||
|
v-for="(item, index) in group.items"
|
||||||
|
:key="item.id"
|
||||||
|
:item="item"
|
||||||
|
:selected="isSelected(groupIndex, index)"
|
||||||
|
:image-url="imageUrls[item.id]"
|
||||||
|
:dimensions="imageDimensions[item.id]"
|
||||||
|
@select="selectItem(groupIndex, index)"
|
||||||
|
@image-error="onImageError"
|
||||||
|
@setRef="el => selectedElement = el" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</OverlayScrollbarsComponent>
|
||||||
|
<div class="right">
|
||||||
|
<div class="content"></div>
|
||||||
|
<div class="information"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<BottomBar />
|
||||||
|
</main>
|
||||||
|
|
||||||
|
<!-- <div class="bg" tabindex="0">
|
||||||
<input
|
<input
|
||||||
ref="searchInput"
|
ref="searchInput"
|
||||||
v-model="searchQuery"
|
v-model="searchQuery"
|
||||||
|
@ -10,6 +46,130 @@
|
||||||
class="search"
|
class="search"
|
||||||
type="text"
|
type="text"
|
||||||
placeholder="Type to filter entries..." />
|
placeholder="Type to filter entries..." />
|
||||||
|
|
||||||
|
<div class="main-container">
|
||||||
|
<OverlayScrollbarsComponent
|
||||||
|
class="results"
|
||||||
|
ref="resultsContainer"
|
||||||
|
:options="{ scrollbars: { autoHide: 'scroll' } }">
|
||||||
|
<template v-for="(group, groupIndex) in groupedHistory" :key="groupIndex">
|
||||||
|
<div class="time-separator">{{ group.label }}</div>
|
||||||
|
<div
|
||||||
|
v-for="(item, index) in group.items"
|
||||||
|
:key="item.id"
|
||||||
|
:class="[
|
||||||
|
'result clothoid-corner',
|
||||||
|
{ selected: isSelected(groupIndex, index) },
|
||||||
|
]"
|
||||||
|
@click="selectItem(groupIndex, index)"
|
||||||
|
:ref="
|
||||||
|
(el: any) => {
|
||||||
|
if (isSelected(groupIndex, index))
|
||||||
|
selectedElement = el as HTMLElement;
|
||||||
|
}
|
||||||
|
">
|
||||||
|
<template v-if="item.content_type === 'image'">
|
||||||
|
<img
|
||||||
|
v-if="imageUrls[item.id]"
|
||||||
|
:src="imageUrls[item.id]"
|
||||||
|
alt="Image"
|
||||||
|
class="image"
|
||||||
|
@error="onImageError" />
|
||||||
|
<img v-else src="../public/icons/Image.svg" class="icon" />
|
||||||
|
</template>
|
||||||
|
<template v-else-if="hasFavicon(item.favicon ?? '')">
|
||||||
|
<img
|
||||||
|
:src="
|
||||||
|
item.favicon
|
||||||
|
? getFaviconFromDb(item.favicon)
|
||||||
|
: '../public/icons/Link.svg'
|
||||||
|
"
|
||||||
|
alt="Favicon"
|
||||||
|
class="favicon"
|
||||||
|
@error="
|
||||||
|
($event.target as HTMLImageElement).src =
|
||||||
|
'../public/icons/Link.svg'
|
||||||
|
" />
|
||||||
|
</template>
|
||||||
|
<img
|
||||||
|
src="../public/icons/File.svg"
|
||||||
|
class="icon"
|
||||||
|
v-else-if="item.content_type === ContentType.File" />
|
||||||
|
<img
|
||||||
|
src="../public/icons/Text.svg"
|
||||||
|
class="icon"
|
||||||
|
v-else-if="item.content_type === ContentType.Text" />
|
||||||
|
<div v-else-if="item.content_type === ContentType.Color">
|
||||||
|
<svg
|
||||||
|
width="18"
|
||||||
|
height="18"
|
||||||
|
viewBox="0 0 18 18"
|
||||||
|
fill="none"
|
||||||
|
xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<g>
|
||||||
|
<rect width="18" height="18" />
|
||||||
|
<path
|
||||||
|
d="M9 18C12.2154 18 15.1865 16.2846 16.7942 13.5C18.4019 10.7154 18.4019 7.28461 16.7942 4.5C15.1865 1.71539 12.2154 -1.22615e-06 9 0C5.78461 0 2.81347 1.71539 1.20577 4.5C-0.401925 7.28461 -0.401923 10.7154 1.20577 13.5C2.81347 16.2846 5.78461 18 9 18Z"
|
||||||
|
fill="#E5DFD5" />
|
||||||
|
<path
|
||||||
|
d="M9 16C7.14348 16 5.36301 15.2625 4.05025 13.9497C2.7375 12.637 2 10.8565 2 9C2 7.14348 2.7375 5.36301 4.05025 4.05025C5.36301 2.7375 7.14348 2 9 2C10.8565 2 12.637 2.7375 13.9497 4.05025C15.2625 5.36301 16 7.14348 16 9C16 10.8565 15.2625 12.637 13.9497 13.9497C12.637 15.2625 10.8565 16 9 16Z"
|
||||||
|
:fill="item.content" />
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
<img
|
||||||
|
src="../public/icons/Code.svg"
|
||||||
|
class="icon"
|
||||||
|
v-else-if="item.content_type === ContentType.Code" />
|
||||||
|
<span v-if="item.content_type === ContentType.Image">
|
||||||
|
Image ({{ imageDimensions[item.id] || "Loading..." }})
|
||||||
|
</span>
|
||||||
|
<span v-else>{{ truncateContent(item.content) }}</span>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</OverlayScrollbarsComponent>
|
||||||
|
|
||||||
|
<div class="right-panel">
|
||||||
|
<div
|
||||||
|
class="content"
|
||||||
|
v-if="selectedItem?.content_type === ContentType.Image">
|
||||||
|
<img :src="imageUrls[selectedItem.id]" alt="Image" class="image" />
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
v-else-if="selectedItem && isYoutubeWatchUrl(selectedItem.content)"
|
||||||
|
class="content">
|
||||||
|
<img
|
||||||
|
class="image"
|
||||||
|
:src="getYoutubeThumbnail(selectedItem.content)"
|
||||||
|
alt="YouTube Thumbnail" />
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="content"
|
||||||
|
v-else-if="
|
||||||
|
selectedItem?.content_type === ContentType.Link && pageOgImage
|
||||||
|
">
|
||||||
|
<img :src="pageOgImage" alt="Image" class="image" />
|
||||||
|
</div>
|
||||||
|
<OverlayScrollbarsComponent v-else class="content">
|
||||||
|
<span>{{ selectedItem?.content || "" }}</span>
|
||||||
|
</OverlayScrollbarsComponent>
|
||||||
|
|
||||||
|
<OverlayScrollbarsComponent
|
||||||
|
class="information"
|
||||||
|
:options="{ scrollbars: { autoHide: 'scroll' } }">
|
||||||
|
<div class="title">Information</div>
|
||||||
|
<div class="info-content" v-if="selectedItem && getInfo">
|
||||||
|
<div class="info-row" v-for="(row, index) in infoRows" :key="index">
|
||||||
|
<p class="label">{{ row.label }}</p>
|
||||||
|
<span :class="{ 'url-truncate': row.isUrl }" :data-text="row.value">
|
||||||
|
{{ row.value }}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</OverlayScrollbarsComponent>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="bottom-bar">
|
<div class="bottom-bar">
|
||||||
<div class="left">
|
<div class="left">
|
||||||
<img class="logo" width="18px" src="../public/logo.png" alt="" />
|
<img class="logo" width="18px" src="../public/logo.png" alt="" />
|
||||||
|
@ -34,126 +194,8 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<OverlayScrollbarsComponent
|
|
||||||
class="results"
|
|
||||||
ref="resultsContainer"
|
|
||||||
:options="{ scrollbars: { autoHide: 'scroll' } }">
|
|
||||||
<template v-for="(group, groupIndex) in groupedHistory" :key="groupIndex">
|
|
||||||
<div class="time-separator">{{ group.label }}</div>
|
|
||||||
<div
|
|
||||||
v-for="(item, index) in group.items"
|
|
||||||
:key="item.id"
|
|
||||||
:class="[
|
|
||||||
'result clothoid-corner',
|
|
||||||
{ selected: isSelected(groupIndex, index) },
|
|
||||||
]"
|
|
||||||
@click="selectItem(groupIndex, index)"
|
|
||||||
:ref="
|
|
||||||
(el: any) => {
|
|
||||||
if (isSelected(groupIndex, index))
|
|
||||||
selectedElement = el as HTMLElement;
|
|
||||||
}
|
|
||||||
">
|
|
||||||
<template v-if="item.content_type === 'image'">
|
|
||||||
<img
|
|
||||||
v-if="imageUrls[item.id]"
|
|
||||||
:src="imageUrls[item.id]"
|
|
||||||
alt="Image"
|
|
||||||
class="image"
|
|
||||||
@error="onImageError" />
|
|
||||||
<img v-else src="../public/icons/Image.svg" class="icon" />
|
|
||||||
</template>
|
|
||||||
<template v-else-if="hasFavicon(item.favicon ?? '')">
|
|
||||||
<img
|
|
||||||
:src="
|
|
||||||
item.favicon
|
|
||||||
? getFaviconFromDb(item.favicon)
|
|
||||||
: '../public/icons/Link.svg'
|
|
||||||
"
|
|
||||||
alt="Favicon"
|
|
||||||
class="favicon"
|
|
||||||
@error="
|
|
||||||
($event.target as HTMLImageElement).src =
|
|
||||||
'../public/icons/Link.svg'
|
|
||||||
" />
|
|
||||||
</template>
|
|
||||||
<img
|
|
||||||
src="../public/icons/File.svg"
|
|
||||||
class="icon"
|
|
||||||
v-else-if="item.content_type === ContentType.File" />
|
|
||||||
<img
|
|
||||||
src="../public/icons/Text.svg"
|
|
||||||
class="icon"
|
|
||||||
v-else-if="item.content_type === ContentType.Text" />
|
|
||||||
<div v-else-if="item.content_type === ContentType.Color">
|
|
||||||
<svg
|
|
||||||
width="18"
|
|
||||||
height="18"
|
|
||||||
viewBox="0 0 18 18"
|
|
||||||
fill="none"
|
|
||||||
xmlns="http://www.w3.org/2000/svg">
|
|
||||||
<g>
|
|
||||||
<rect width="18" height="18" />
|
|
||||||
<path
|
|
||||||
d="M9 18C12.2154 18 15.1865 16.2846 16.7942 13.5C18.4019 10.7154 18.4019 7.28461 16.7942 4.5C15.1865 1.71539 12.2154 -1.22615e-06 9 0C5.78461 0 2.81347 1.71539 1.20577 4.5C-0.401925 7.28461 -0.401923 10.7154 1.20577 13.5C2.81347 16.2846 5.78461 18 9 18Z"
|
|
||||||
fill="#E5DFD5" />
|
|
||||||
<path
|
|
||||||
d="M9 16C7.14348 16 5.36301 15.2625 4.05025 13.9497C2.7375 12.637 2 10.8565 2 9C2 7.14348 2.7375 5.36301 4.05025 4.05025C5.36301 2.7375 7.14348 2 9 2C10.8565 2 12.637 2.7375 13.9497 4.05025C15.2625 5.36301 16 7.14348 16 9C16 10.8565 15.2625 12.637 13.9497 13.9497C12.637 15.2625 10.8565 16 9 16Z"
|
|
||||||
:fill="item.content" />
|
|
||||||
</g>
|
|
||||||
</svg>
|
|
||||||
</div>
|
|
||||||
<img
|
|
||||||
src="../public/icons/Code.svg"
|
|
||||||
class="icon"
|
|
||||||
v-else-if="item.content_type === ContentType.Code" />
|
|
||||||
<span v-if="item.content_type === ContentType.Image">
|
|
||||||
Image ({{ imageDimensions[item.id] || "Loading..." }})
|
|
||||||
</span>
|
|
||||||
<span v-else>{{ truncateContent(item.content) }}</span>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
</OverlayScrollbarsComponent>
|
|
||||||
|
|
||||||
<div
|
|
||||||
class="content"
|
|
||||||
v-if="selectedItem?.content_type === ContentType.Image">
|
|
||||||
<img :src="imageUrls[selectedItem.id]" alt="Image" class="image" />
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
v-else-if="selectedItem && isYoutubeWatchUrl(selectedItem.content)"
|
|
||||||
class="content">
|
|
||||||
<img
|
|
||||||
class="image"
|
|
||||||
:src="getYoutubeThumbnail(selectedItem.content)"
|
|
||||||
alt="YouTube Thumbnail" />
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
class="content"
|
|
||||||
v-else-if="
|
|
||||||
selectedItem?.content_type === ContentType.Link && pageOgImage
|
|
||||||
">
|
|
||||||
<img :src="pageOgImage" alt="Image" class="image" />
|
|
||||||
</div>
|
|
||||||
<OverlayScrollbarsComponent v-else class="content">
|
|
||||||
<span>{{ selectedItem?.content || "" }}</span>
|
|
||||||
</OverlayScrollbarsComponent>
|
|
||||||
|
|
||||||
<OverlayScrollbarsComponent
|
|
||||||
class="information"
|
|
||||||
:options="{ scrollbars: { autoHide: 'scroll' } }">
|
|
||||||
<div class="title">Information</div>
|
|
||||||
<div class="info-content" v-if="selectedItem && getInfo">
|
|
||||||
<div class="info-row" v-for="(row, index) in infoRows" :key="index">
|
|
||||||
<p class="label">{{ row.label }}</p>
|
|
||||||
<span :class="{ 'url-truncate': row.isUrl }" :data-text="row.value">
|
|
||||||
{{ row.value }}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</OverlayScrollbarsComponent>
|
|
||||||
<Noise />
|
<Noise />
|
||||||
</div>
|
</div> -->
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
@ -175,6 +217,7 @@ import type {
|
||||||
InfoCode,
|
InfoCode,
|
||||||
} from "~/types/types";
|
} from "~/types/types";
|
||||||
import { Key, keyboard } from "wrdu-keyboard";
|
import { Key, keyboard } from "wrdu-keyboard";
|
||||||
|
import { selectedGroupIndex, selectedItemIndex, selectedElement, useSelectedResult } from '~/lib/selectedResult'
|
||||||
|
|
||||||
interface GroupedHistory {
|
interface GroupedHistory {
|
||||||
label: string;
|
label: string;
|
||||||
|
@ -182,8 +225,11 @@ interface GroupedHistory {
|
||||||
}
|
}
|
||||||
|
|
||||||
const { $history } = useNuxtApp();
|
const { $history } = useNuxtApp();
|
||||||
|
|
||||||
const CHUNK_SIZE = 50;
|
const CHUNK_SIZE = 50;
|
||||||
const SCROLL_THRESHOLD = 100;
|
const SCROLL_THRESHOLD = 100;
|
||||||
|
const SCROLL_PADDING = 8;
|
||||||
|
const TOP_SCROLL_PADDING = 37;
|
||||||
|
|
||||||
const history = shallowRef<HistoryItem[]>([]);
|
const history = shallowRef<HistoryItem[]>([]);
|
||||||
let offset = 0;
|
let offset = 0;
|
||||||
|
@ -193,9 +239,6 @@ const resultsContainer = shallowRef<InstanceType<
|
||||||
typeof OverlayScrollbarsComponent
|
typeof OverlayScrollbarsComponent
|
||||||
> | null>(null);
|
> | null>(null);
|
||||||
const searchQuery = ref("");
|
const searchQuery = ref("");
|
||||||
const selectedGroupIndex = ref(0);
|
|
||||||
const selectedItemIndex = ref(0);
|
|
||||||
const selectedElement = shallowRef<HTMLElement | null>(null);
|
|
||||||
const searchInput = ref<HTMLInputElement | null>(null);
|
const searchInput = ref<HTMLInputElement | null>(null);
|
||||||
const os = ref<string>("");
|
const os = ref<string>("");
|
||||||
const imageUrls = shallowRef<Record<string, string>>({});
|
const imageUrls = shallowRef<Record<string, string>>({});
|
||||||
|
@ -207,6 +250,8 @@ const imageLoading = ref<boolean>(false);
|
||||||
const pageTitle = ref<string>("");
|
const pageTitle = ref<string>("");
|
||||||
const pageOgImage = ref<string>("");
|
const pageOgImage = ref<string>("");
|
||||||
|
|
||||||
|
const topBar = ref<{ searchInput: HTMLInputElement | null } | null>(null)
|
||||||
|
|
||||||
const isSameDay = (date1: Date, date2: Date): boolean => {
|
const isSameDay = (date1: Date, date2: Date): boolean => {
|
||||||
return (
|
return (
|
||||||
date1.getFullYear() === date2.getFullYear() &&
|
date1.getFullYear() === date2.getFullYear() &&
|
||||||
|
@ -268,10 +313,7 @@ const groupedHistory = computed<GroupedHistory[]>(() => {
|
||||||
.map(([label, items]) => ({ label, items }));
|
.map(([label, items]) => ({ label, items }));
|
||||||
});
|
});
|
||||||
|
|
||||||
const selectedItem = computed<HistoryItem | null>(() => {
|
const { selectedItem, isSelected, selectNext, selectPrevious, selectItem } = useSelectedResult(groupedHistory)
|
||||||
const group = groupedHistory.value[selectedGroupIndex.value];
|
|
||||||
return group?.items[selectedItemIndex.value] ?? null;
|
|
||||||
});
|
|
||||||
|
|
||||||
const loadHistoryChunk = async (): Promise<void> => {
|
const loadHistoryChunk = async (): Promise<void> => {
|
||||||
if (isLoading) return;
|
if (isLoading) return;
|
||||||
|
@ -352,81 +394,64 @@ const handleScroll = (): void => {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const scrollToSelectedItem = (forceScrollTop: boolean = false): void => {
|
const scrollToSelectedItem = (): void => {
|
||||||
nextTick(() => {
|
nextTick(() => {
|
||||||
const osInstance = resultsContainer.value?.osInstance();
|
const viewport = resultsContainer.value?.osInstance()?.elements().viewport;
|
||||||
const viewport = osInstance?.elements().viewport;
|
|
||||||
if (!selectedElement.value || !viewport) return;
|
if (!selectedElement.value || !viewport) return;
|
||||||
|
|
||||||
if (!forceScrollTop) {
|
setTimeout(() => {
|
||||||
|
if (!selectedElement.value) return;
|
||||||
|
|
||||||
const viewportRect = viewport.getBoundingClientRect();
|
const viewportRect = viewport.getBoundingClientRect();
|
||||||
const elementRect = selectedElement.value.getBoundingClientRect();
|
const elementRect = selectedElement.value.getBoundingClientRect();
|
||||||
|
|
||||||
const isAbove = elementRect.top < viewportRect.top;
|
const isFirstItemInGroup = selectedItemIndex.value === 0;
|
||||||
const isBelow = elementRect.bottom > viewportRect.bottom - 8;
|
const isAbove = elementRect.top < viewportRect.top + SCROLL_PADDING;
|
||||||
|
const isBelow = elementRect.bottom > viewportRect.bottom - SCROLL_PADDING;
|
||||||
|
|
||||||
if (isAbove || isBelow) {
|
if (isAbove) {
|
||||||
const scrollOffset = isAbove
|
viewport.scrollTo({
|
||||||
? elementRect.top -
|
top: viewport.scrollTop + (elementRect.top - viewportRect.top) - (isFirstItemInGroup ? TOP_SCROLL_PADDING : SCROLL_PADDING),
|
||||||
viewportRect.top -
|
behavior: "smooth"
|
||||||
(selectedItemIndex.value === 0 ? 36 : 8)
|
});
|
||||||
: elementRect.bottom - viewportRect.bottom + 9;
|
} else if (isBelow) {
|
||||||
|
viewport.scrollTo({
|
||||||
viewport.scrollBy({ top: scrollOffset, behavior: "smooth" });
|
top: viewport.scrollTop + (elementRect.bottom - viewportRect.bottom) + SCROLL_PADDING,
|
||||||
|
behavior: "smooth"
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}, 10);
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const isSelected = (groupIndex: number, itemIndex: number): boolean => {
|
const searchHistory = async (query: string): Promise<void> => {
|
||||||
return (
|
searchQuery.value = query
|
||||||
selectedGroupIndex.value === groupIndex &&
|
if (!query.trim()) {
|
||||||
selectedItemIndex.value === itemIndex
|
history.value = []
|
||||||
);
|
offset = 0
|
||||||
};
|
await loadHistoryChunk()
|
||||||
|
return
|
||||||
const searchHistory = async (): Promise<void> => {
|
}
|
||||||
const results = await $history.searchHistory(searchQuery.value);
|
|
||||||
|
const results = await $history.searchHistory(query)
|
||||||
history.value = results.map((item) =>
|
history.value = results.map((item) =>
|
||||||
Object.assign(
|
Object.assign(
|
||||||
new HistoryItem(
|
new HistoryItem(
|
||||||
item.source,
|
item.source,
|
||||||
item.content_type,
|
item.content_type,
|
||||||
item.content,
|
item.content,
|
||||||
item.favicon
|
item.favicon,
|
||||||
|
item.source_icon,
|
||||||
|
item.language
|
||||||
),
|
),
|
||||||
{ id: item.id, timestamp: new Date(item.timestamp) }
|
{ id: item.id, timestamp: new Date(item.timestamp) }
|
||||||
)
|
)
|
||||||
);
|
)
|
||||||
};
|
|
||||||
|
|
||||||
const selectNext = (): void => {
|
if (groupedHistory.value.length > 0) {
|
||||||
const currentGroup = groupedHistory.value[selectedGroupIndex.value];
|
handleSelection(0, 0, false)
|
||||||
if (selectedItemIndex.value < currentGroup.items.length - 1) {
|
|
||||||
selectedItemIndex.value++;
|
|
||||||
} else if (selectedGroupIndex.value < groupedHistory.value.length - 1) {
|
|
||||||
selectedGroupIndex.value++;
|
|
||||||
selectedItemIndex.value = 0;
|
|
||||||
}
|
}
|
||||||
scrollToSelectedItem();
|
}
|
||||||
};
|
|
||||||
|
|
||||||
const selectPrevious = (): void => {
|
|
||||||
if (selectedItemIndex.value > 0) {
|
|
||||||
selectedItemIndex.value--;
|
|
||||||
} else if (selectedGroupIndex.value > 0) {
|
|
||||||
selectedGroupIndex.value--;
|
|
||||||
selectedItemIndex.value =
|
|
||||||
groupedHistory.value[selectedGroupIndex.value].items.length - 1;
|
|
||||||
}
|
|
||||||
scrollToSelectedItem();
|
|
||||||
};
|
|
||||||
|
|
||||||
const selectItem = (groupIndex: number, itemIndex: number): void => {
|
|
||||||
selectedGroupIndex.value = groupIndex;
|
|
||||||
selectedItemIndex.value = itemIndex;
|
|
||||||
scrollToSelectedItem();
|
|
||||||
};
|
|
||||||
|
|
||||||
const pasteSelectedItem = async (): Promise<void> => {
|
const pasteSelectedItem = async (): Promise<void> => {
|
||||||
if (!selectedItem.value) return;
|
if (!selectedItem.value) return;
|
||||||
|
@ -554,10 +579,9 @@ const handleSelection = (
|
||||||
itemIndex: number,
|
itemIndex: number,
|
||||||
shouldScroll: boolean = true
|
shouldScroll: boolean = true
|
||||||
): void => {
|
): void => {
|
||||||
selectedGroupIndex.value = groupIndex;
|
selectItem(groupIndex, itemIndex)
|
||||||
selectedItemIndex.value = itemIndex;
|
if (shouldScroll) scrollToSelectedItem()
|
||||||
if (shouldScroll) scrollToSelectedItem();
|
}
|
||||||
};
|
|
||||||
|
|
||||||
const setupEventListeners = async (): Promise<void> => {
|
const setupEventListeners = async (): Promise<void> => {
|
||||||
await listen("clipboard-content-updated", async () => {
|
await listen("clipboard-content-updated", async () => {
|
||||||
|
@ -591,8 +615,7 @@ const setupEventListeners = async (): Promise<void> => {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
focusSearchInput();
|
focusSearchInput();
|
||||||
|
|
||||||
// Re-register keyboard shortcuts on focus
|
|
||||||
keyboard.clear();
|
keyboard.clear();
|
||||||
keyboard.prevent.down([Key.DownArrow], () => {
|
keyboard.prevent.down([Key.DownArrow], () => {
|
||||||
selectNext();
|
selectNext();
|
||||||
|
@ -666,9 +689,9 @@ const hideApp = async (): Promise<void> => {
|
||||||
|
|
||||||
const focusSearchInput = (): void => {
|
const focusSearchInput = (): void => {
|
||||||
nextTick(() => {
|
nextTick(() => {
|
||||||
searchInput.value?.focus();
|
topBar.value?.searchInput?.focus()
|
||||||
});
|
})
|
||||||
};
|
}
|
||||||
|
|
||||||
const onImageError = (): void => {
|
const onImageError = (): void => {
|
||||||
imageLoadError.value = true;
|
imageLoadError.value = true;
|
||||||
|
@ -677,10 +700,10 @@ const onImageError = (): void => {
|
||||||
|
|
||||||
watch([selectedGroupIndex, selectedItemIndex], () => {
|
watch([selectedGroupIndex, selectedItemIndex], () => {
|
||||||
scrollToSelectedItem();
|
scrollToSelectedItem();
|
||||||
});
|
}, { flush: 'post' });
|
||||||
|
|
||||||
watch(searchQuery, () => {
|
watch(searchQuery, () => {
|
||||||
searchHistory();
|
searchHistory(searchQuery.value);
|
||||||
});
|
});
|
||||||
|
|
||||||
onMounted(async () => {
|
onMounted(async () => {
|
||||||
|
@ -699,10 +722,6 @@ onMounted(async () => {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
watch([selectedGroupIndex, selectedItemIndex], () =>
|
|
||||||
scrollToSelectedItem(false)
|
|
||||||
);
|
|
||||||
|
|
||||||
const getFormattedDate = computed(() => {
|
const getFormattedDate = computed(() => {
|
||||||
if (!selectedItem.value?.timestamp) return "";
|
if (!selectedItem.value?.timestamp) return "";
|
||||||
return new Intl.DateTimeFormat("en-US", {
|
return new Intl.DateTimeFormat("en-US", {
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue