mirror of
https://github.com/0PandaDEV/Qopy.git
synced 2025-04-20 12:54:05 +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;
|
||||
$mutedtext: #78756f;
|
||||
|
||||
.bg {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
$search-height: 56px;
|
||||
$sidebar-width: 286px;
|
||||
$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;
|
||||
border: 1px solid $divider;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
border-radius: 12px;
|
||||
z-index: -1;
|
||||
position: fixed;
|
||||
outline: none;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.search {
|
||||
.content {
|
||||
height: 376px;
|
||||
width: 100%;
|
||||
position: fixed;
|
||||
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;
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.results {
|
||||
position: absolute;
|
||||
width: 286px;
|
||||
top: 55px;
|
||||
left: 0;
|
||||
height: 417px;
|
||||
border-right: 1px solid $divider;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
padding-inline: 8px;
|
||||
padding-bottom: 8px;
|
||||
overflow-y: auto;
|
||||
overflow-x: hidden;
|
||||
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;
|
||||
}
|
||||
}
|
||||
padding: 14px 8px;
|
||||
gap: 8px;
|
||||
width: 286px;
|
||||
border-right: 1px solid var(--border);
|
||||
|
||||
.time-separator {
|
||||
font-size: 12px;
|
||||
color: $text2;
|
||||
font-family: SFRoundedSemiBold;
|
||||
padding-left: 8px;
|
||||
padding-bottom: 8px;
|
||||
padding-top: 14px;
|
||||
}
|
||||
|
||||
.favicon {
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
.group {
|
||||
& + .group {
|
||||
margin-top: 16px;
|
||||
}
|
||||
|
||||
.image {
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
.time-separator {
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.results-group {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
}
|
||||
|
||||
.favicon,
|
||||
.image,
|
||||
.icon {
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
}
|
||||
}
|
||||
|
||||
.content {
|
||||
position: absolute;
|
||||
top: 55px;
|
||||
left: 285px;
|
||||
height: 220px;
|
||||
font-family: CommitMono !important;
|
||||
font-size: 12px;
|
||||
letter-spacing: 1;
|
||||
border-radius: 10px;
|
||||
width: 465px;
|
||||
white-space: pre-wrap;
|
||||
word-wrap: break-word;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
overflow: hidden;
|
||||
z-index: 2;
|
||||
color: $text;
|
||||
// .bg {
|
||||
// width: 100%;
|
||||
// height: 100%;
|
||||
// background-color: $primary;
|
||||
// border: 1px solid $divider;
|
||||
// border-radius: 12px;
|
||||
// z-index: -1;
|
||||
// position: fixed;
|
||||
// outline: none;
|
||||
// display: flex;
|
||||
// flex-direction: column;
|
||||
// }
|
||||
|
||||
&:not(:has(.image)) {
|
||||
padding: 8px;
|
||||
}
|
||||
// .search {
|
||||
// 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 {
|
||||
font-family: CommitMono !important;
|
||||
}
|
||||
// .main-container {
|
||||
// display: flex;
|
||||
// flex: 1;
|
||||
// }
|
||||
|
||||
.image {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: contain;
|
||||
object-position: center;
|
||||
}
|
||||
}
|
||||
// .results {
|
||||
// width: $sidebar-width;
|
||||
// height: calc(100vh - $search-height - $bottom-bar-height);
|
||||
// border-right: 1px solid $divider;
|
||||
// display: flex;
|
||||
// flex-direction: column;
|
||||
// padding-inline: 8px;
|
||||
// padding-bottom: 8px;
|
||||
// overflow-y: auto;
|
||||
// overflow-x: hidden;
|
||||
// z-index: 3;
|
||||
|
||||
.bottom-bar {
|
||||
height: 39px;
|
||||
width: calc(100vw - 2px);
|
||||
backdrop-filter: blur(18px);
|
||||
background-color: hsla(40, 3%, 16%, 0.8);
|
||||
position: fixed;
|
||||
bottom: 1px;
|
||||
left: 1px;
|
||||
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;
|
||||
// .result {
|
||||
// height: 40px;
|
||||
// font-size: 14px;
|
||||
// display: flex;
|
||||
// align-items: center;
|
||||
// padding: 10px;
|
||||
// letter-spacing: 0.5px;
|
||||
// gap: 10px;
|
||||
// overflow: hidden;
|
||||
// text-overflow: clip;
|
||||
// white-space: nowrap;
|
||||
// color: $text;
|
||||
// cursor: pointer;
|
||||
|
||||
p {
|
||||
color: $text2;
|
||||
}
|
||||
// &.selected {
|
||||
// background-color: $divider;
|
||||
// }
|
||||
// }
|
||||
|
||||
.left {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
// .time-separator {
|
||||
// font-size: 12px;
|
||||
// color: $text2;
|
||||
// font-family: SFRoundedSemiBold;
|
||||
// padding-left: 8px;
|
||||
// padding-bottom: 8px;
|
||||
// padding-top: 14px;
|
||||
// }
|
||||
|
||||
.logo {
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
}
|
||||
}
|
||||
// .favicon,
|
||||
// .image,
|
||||
// .icon {
|
||||
// width: 18px;
|
||||
// height: 18px;
|
||||
// }
|
||||
// }
|
||||
|
||||
.right {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
// .right-panel {
|
||||
// display: flex;
|
||||
// flex-direction: column;
|
||||
// flex: 1;
|
||||
// }
|
||||
|
||||
.paste p {
|
||||
color: $text;
|
||||
}
|
||||
// .content {
|
||||
// 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 {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 2px;
|
||||
}
|
||||
// &:not(:has(.image)) {
|
||||
// padding: 8px;
|
||||
// }
|
||||
|
||||
.divider {
|
||||
width: 2px;
|
||||
height: 12px;
|
||||
background-color: $divider;
|
||||
margin-left: 8px;
|
||||
margin-right: 4px;
|
||||
transition: all 0.2s;
|
||||
}
|
||||
// span {
|
||||
// font-family: CommitMono !important;
|
||||
// }
|
||||
|
||||
.paste,
|
||||
.actions {
|
||||
padding: 4px;
|
||||
padding-left: 8px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
border-radius: 7px;
|
||||
background-color: transparent;
|
||||
transition: all 0.2s;
|
||||
cursor: pointer;
|
||||
}
|
||||
// .image {
|
||||
// width: 100%;
|
||||
// height: 100%;
|
||||
// object-fit: contain;
|
||||
// object-position: center;
|
||||
// }
|
||||
// }
|
||||
|
||||
.paste:hover,
|
||||
.actions:hover {
|
||||
background-color: $divider;
|
||||
}
|
||||
// .bottom-bar {
|
||||
// height: $bottom-bar-height;
|
||||
// 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,
|
||||
&:hover .actions:hover ~ .divider {
|
||||
opacity: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
// p {
|
||||
// color: $text2;
|
||||
// }
|
||||
|
||||
.information {
|
||||
position: absolute;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 14px;
|
||||
bottom: 39px;
|
||||
left: 285px;
|
||||
height: 160px;
|
||||
width: 465px;
|
||||
border-top: 1px solid $divider;
|
||||
background-color: $primary;
|
||||
padding: 14px;
|
||||
z-index: 1;
|
||||
// .left {
|
||||
// display: flex;
|
||||
// align-items: center;
|
||||
// gap: 8px;
|
||||
|
||||
.title {
|
||||
font-family: SFRoundedSemiBold;
|
||||
font-size: 12px;
|
||||
letter-spacing: 0.6px;
|
||||
color: $text;
|
||||
}
|
||||
// .logo {
|
||||
// width: 18px;
|
||||
// height: 18px;
|
||||
// }
|
||||
// }
|
||||
|
||||
.info-content {
|
||||
display: flex;
|
||||
gap: 0;
|
||||
flex-direction: column;
|
||||
// .right {
|
||||
// display: flex;
|
||||
// align-items: center;
|
||||
|
||||
.info-row {
|
||||
display: flex;
|
||||
width: 100%;
|
||||
font-size: 12px;
|
||||
justify-content: space-between;
|
||||
padding: 8px 0;
|
||||
border-bottom: 1px solid $divider;
|
||||
line-height: 1;
|
||||
// .paste p {
|
||||
// color: $text;
|
||||
// }
|
||||
|
||||
&:last-child {
|
||||
border-bottom: none;
|
||||
padding-bottom: 0;
|
||||
}
|
||||
// .actions div {
|
||||
// display: flex;
|
||||
// align-items: center;
|
||||
// gap: 2px;
|
||||
// }
|
||||
|
||||
&:first-child {
|
||||
padding-top: 22px;
|
||||
}
|
||||
// .divider {
|
||||
// width: 2px;
|
||||
// height: 12px;
|
||||
// background-color: $divider;
|
||||
// margin-left: 8px;
|
||||
// margin-right: 4px;
|
||||
// transition: all 0.2s;
|
||||
// }
|
||||
|
||||
p {
|
||||
font-family: SFRoundedMedium;
|
||||
color: $text2;
|
||||
font-weight: 500;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
// .paste,
|
||||
// .actions {
|
||||
// padding: 4px;
|
||||
// padding-left: 8px;
|
||||
// display: flex;
|
||||
// align-items: center;
|
||||
// gap: 8px;
|
||||
// border-radius: 7px;
|
||||
// background-color: transparent;
|
||||
// transition: all 0.2s;
|
||||
// cursor: pointer;
|
||||
// }
|
||||
|
||||
span {
|
||||
font-family: CommitMono;
|
||||
color: $text;
|
||||
text-overflow: ellipsis;
|
||||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
margin-left: 32px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// .paste:hover,
|
||||
// .actions:hover {
|
||||
// background-color: $divider;
|
||||
// }
|
||||
|
||||
.clothoid-corner {
|
||||
clip-path: polygon(
|
||||
13.890123px 0px,
|
||||
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,
|
||||
calc(100% - 8.079275px) 0.382298px,
|
||||
calc(100% - 6.947448px) 0.662609px,
|
||||
calc(100% - 5.844179px) 1.039291px,
|
||||
calc(100% - 4.793324px) 1.542842px,
|
||||
calc(100% - 3.811369px) 2.169728px,
|
||||
calc(100% - 2.926417px) 2.926417px,
|
||||
calc(100% - 2.169728px) 3.811369px,
|
||||
calc(100% - 1.542842px) 4.793324px,
|
||||
calc(100% - 1.039291px) 5.844179px,
|
||||
calc(100% - 0.662609px) 6.947448px,
|
||||
calc(100% - 0.382298px) 8.079275px,
|
||||
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
|
||||
);
|
||||
}
|
||||
// &:hover .paste:hover ~ .divider,
|
||||
// &:hover .actions:hover ~ .divider {
|
||||
// opacity: 0;
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
// .information {
|
||||
// height: $info-panel-height;
|
||||
// width: calc(100% - $sidebar-width);
|
||||
// border-top: 1px solid $divider;
|
||||
// background-color: $primary;
|
||||
// padding: 14px;
|
||||
// z-index: 1;
|
||||
// display: flex;
|
||||
// flex-direction: column;
|
||||
// gap: 14px;
|
||||
|
||||
// .title {
|
||||
// font-family: SFRoundedSemiBold;
|
||||
// font-size: 12px;
|
||||
// letter-spacing: 0.6px;
|
||||
// color: $text;
|
||||
// }
|
||||
|
||||
// .info-content {
|
||||
// display: flex;
|
||||
// gap: 0;
|
||||
// flex-direction: column;
|
||||
|
||||
// .info-row {
|
||||
// display: flex;
|
||||
// width: 100%;
|
||||
// font-size: 12px;
|
||||
// justify-content: space-between;
|
||||
// padding: 8px 0;
|
||||
// border-bottom: 1px solid $divider;
|
||||
// line-height: 1;
|
||||
|
||||
// &:last-child {
|
||||
// border-bottom: none;
|
||||
// padding-bottom: 0;
|
||||
// }
|
||||
|
||||
// &:first-child {
|
||||
// padding-top: 22px;
|
||||
// }
|
||||
|
||||
// p {
|
||||
// font-family: SFRoundedMedium;
|
||||
// color: $text2;
|
||||
// font-weight: 500;
|
||||
// flex-shrink: 0;
|
||||
// }
|
||||
|
||||
// span {
|
||||
// font-family: CommitMono;
|
||||
// color: $text;
|
||||
// text-overflow: ellipsis;
|
||||
// overflow: hidden;
|
||||
// white-space: nowrap;
|
||||
// margin-left: 32px;
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
// .clothoid-corner {
|
||||
// clip-path: polygon(
|
||||
// 13.890123px 0px,
|
||||
// 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,
|
||||
// calc(100% - 8.079275px) 0.382298px,
|
||||
// calc(100% - 6.947448px) 0.662609px,
|
||||
// calc(100% - 5.844179px) 1.039291px,
|
||||
// calc(100% - 4.793324px) 1.542842px,
|
||||
// calc(100% - 3.811369px) 2.169728px,
|
||||
// calc(100% - 2.926417px) 2.926417px,
|
||||
// calc(100% - 2.169728px) 3.811369px,
|
||||
// calc(100% - 1.542842px) 4.793324px,
|
||||
// calc(100% - 1.039291px) 5.844179px,
|
||||
// calc(100% - 0.662609px) 6.947448px,
|
||||
// calc(100% - 0.382298px) 8.079275px,
|
||||
// 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
|
||||
}
|
||||
}
|
217
pages/index.vue
217
pages/index.vue
|
@ -1,5 +1,41 @@
|
|||
<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
|
||||
ref="searchInput"
|
||||
v-model="searchQuery"
|
||||
|
@ -10,30 +46,8 @@
|
|||
class="search"
|
||||
type="text"
|
||||
placeholder="Type to filter entries..." />
|
||||
<div class="bottom-bar">
|
||||
<div class="left">
|
||||
<img class="logo" width="18px" src="../public/logo.png" alt="" />
|
||||
<p>Qopy</p>
|
||||
</div>
|
||||
<div class="right">
|
||||
<div class="paste" @click="pasteSelectedItem">
|
||||
<p>Paste</p>
|
||||
<img src="../public/enter.svg" alt="" />
|
||||
</div>
|
||||
<div class="divider"></div>
|
||||
<div class="actions">
|
||||
<p>Actions</p>
|
||||
<div>
|
||||
<img
|
||||
v-if="os === 'windows' || os === 'linux'"
|
||||
src="../public/ctrl.svg"
|
||||
alt="" />
|
||||
<img v-if="os === 'macos'" src="../public/cmd.svg" alt="" />
|
||||
<img src="../public/k.svg" alt="" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="main-container">
|
||||
<OverlayScrollbarsComponent
|
||||
class="results"
|
||||
ref="resultsContainer"
|
||||
|
@ -115,6 +129,7 @@
|
|||
</template>
|
||||
</OverlayScrollbarsComponent>
|
||||
|
||||
<div class="right-panel">
|
||||
<div
|
||||
class="content"
|
||||
v-if="selectedItem?.content_type === ContentType.Image">
|
||||
|
@ -152,8 +167,35 @@
|
|||
</div>
|
||||
</div>
|
||||
</OverlayScrollbarsComponent>
|
||||
<Noise />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="bottom-bar">
|
||||
<div class="left">
|
||||
<img class="logo" width="18px" src="../public/logo.png" alt="" />
|
||||
<p>Qopy</p>
|
||||
</div>
|
||||
<div class="right">
|
||||
<div class="paste" @click="pasteSelectedItem">
|
||||
<p>Paste</p>
|
||||
<img src="../public/enter.svg" alt="" />
|
||||
</div>
|
||||
<div class="divider"></div>
|
||||
<div class="actions">
|
||||
<p>Actions</p>
|
||||
<div>
|
||||
<img
|
||||
v-if="os === 'windows' || os === 'linux'"
|
||||
src="../public/ctrl.svg"
|
||||
alt="" />
|
||||
<img v-if="os === 'macos'" src="../public/cmd.svg" alt="" />
|
||||
<img src="../public/k.svg" alt="" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<Noise />
|
||||
</div> -->
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
|
@ -175,6 +217,7 @@ import type {
|
|||
InfoCode,
|
||||
} from "~/types/types";
|
||||
import { Key, keyboard } from "wrdu-keyboard";
|
||||
import { selectedGroupIndex, selectedItemIndex, selectedElement, useSelectedResult } from '~/lib/selectedResult'
|
||||
|
||||
interface GroupedHistory {
|
||||
label: string;
|
||||
|
@ -182,8 +225,11 @@ interface GroupedHistory {
|
|||
}
|
||||
|
||||
const { $history } = useNuxtApp();
|
||||
|
||||
const CHUNK_SIZE = 50;
|
||||
const SCROLL_THRESHOLD = 100;
|
||||
const SCROLL_PADDING = 8;
|
||||
const TOP_SCROLL_PADDING = 37;
|
||||
|
||||
const history = shallowRef<HistoryItem[]>([]);
|
||||
let offset = 0;
|
||||
|
@ -193,9 +239,6 @@ const resultsContainer = shallowRef<InstanceType<
|
|||
typeof OverlayScrollbarsComponent
|
||||
> | null>(null);
|
||||
const searchQuery = ref("");
|
||||
const selectedGroupIndex = ref(0);
|
||||
const selectedItemIndex = ref(0);
|
||||
const selectedElement = shallowRef<HTMLElement | null>(null);
|
||||
const searchInput = ref<HTMLInputElement | null>(null);
|
||||
const os = ref<string>("");
|
||||
const imageUrls = shallowRef<Record<string, string>>({});
|
||||
|
@ -207,6 +250,8 @@ const imageLoading = ref<boolean>(false);
|
|||
const pageTitle = ref<string>("");
|
||||
const pageOgImage = ref<string>("");
|
||||
|
||||
const topBar = ref<{ searchInput: HTMLInputElement | null } | null>(null)
|
||||
|
||||
const isSameDay = (date1: Date, date2: Date): boolean => {
|
||||
return (
|
||||
date1.getFullYear() === date2.getFullYear() &&
|
||||
|
@ -268,10 +313,7 @@ const groupedHistory = computed<GroupedHistory[]>(() => {
|
|||
.map(([label, items]) => ({ label, items }));
|
||||
});
|
||||
|
||||
const selectedItem = computed<HistoryItem | null>(() => {
|
||||
const group = groupedHistory.value[selectedGroupIndex.value];
|
||||
return group?.items[selectedItemIndex.value] ?? null;
|
||||
});
|
||||
const { selectedItem, isSelected, selectNext, selectPrevious, selectItem } = useSelectedResult(groupedHistory)
|
||||
|
||||
const loadHistoryChunk = async (): Promise<void> => {
|
||||
if (isLoading) return;
|
||||
|
@ -352,81 +394,64 @@ const handleScroll = (): void => {
|
|||
}
|
||||
};
|
||||
|
||||
const scrollToSelectedItem = (forceScrollTop: boolean = false): void => {
|
||||
const scrollToSelectedItem = (): void => {
|
||||
nextTick(() => {
|
||||
const osInstance = resultsContainer.value?.osInstance();
|
||||
const viewport = osInstance?.elements().viewport;
|
||||
const viewport = resultsContainer.value?.osInstance()?.elements().viewport;
|
||||
if (!selectedElement.value || !viewport) return;
|
||||
|
||||
if (!forceScrollTop) {
|
||||
setTimeout(() => {
|
||||
if (!selectedElement.value) return;
|
||||
|
||||
const viewportRect = viewport.getBoundingClientRect();
|
||||
const elementRect = selectedElement.value.getBoundingClientRect();
|
||||
|
||||
const isAbove = elementRect.top < viewportRect.top;
|
||||
const isBelow = elementRect.bottom > viewportRect.bottom - 8;
|
||||
const isFirstItemInGroup = selectedItemIndex.value === 0;
|
||||
const isAbove = elementRect.top < viewportRect.top + SCROLL_PADDING;
|
||||
const isBelow = elementRect.bottom > viewportRect.bottom - SCROLL_PADDING;
|
||||
|
||||
if (isAbove || isBelow) {
|
||||
const scrollOffset = isAbove
|
||||
? elementRect.top -
|
||||
viewportRect.top -
|
||||
(selectedItemIndex.value === 0 ? 36 : 8)
|
||||
: elementRect.bottom - viewportRect.bottom + 9;
|
||||
|
||||
viewport.scrollBy({ top: scrollOffset, behavior: "smooth" });
|
||||
}
|
||||
if (isAbove) {
|
||||
viewport.scrollTo({
|
||||
top: viewport.scrollTop + (elementRect.top - viewportRect.top) - (isFirstItemInGroup ? TOP_SCROLL_PADDING : SCROLL_PADDING),
|
||||
behavior: "smooth"
|
||||
});
|
||||
} else if (isBelow) {
|
||||
viewport.scrollTo({
|
||||
top: viewport.scrollTop + (elementRect.bottom - viewportRect.bottom) + SCROLL_PADDING,
|
||||
behavior: "smooth"
|
||||
});
|
||||
}
|
||||
}, 10);
|
||||
});
|
||||
};
|
||||
|
||||
const isSelected = (groupIndex: number, itemIndex: number): boolean => {
|
||||
return (
|
||||
selectedGroupIndex.value === groupIndex &&
|
||||
selectedItemIndex.value === itemIndex
|
||||
);
|
||||
};
|
||||
const searchHistory = async (query: string): Promise<void> => {
|
||||
searchQuery.value = query
|
||||
if (!query.trim()) {
|
||||
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) =>
|
||||
Object.assign(
|
||||
new HistoryItem(
|
||||
item.source,
|
||||
item.content_type,
|
||||
item.content,
|
||||
item.favicon
|
||||
item.favicon,
|
||||
item.source_icon,
|
||||
item.language
|
||||
),
|
||||
{ id: item.id, timestamp: new Date(item.timestamp) }
|
||||
)
|
||||
);
|
||||
};
|
||||
)
|
||||
|
||||
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;
|
||||
if (groupedHistory.value.length > 0) {
|
||||
handleSelection(0, 0, false)
|
||||
}
|
||||
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> => {
|
||||
if (!selectedItem.value) return;
|
||||
|
@ -554,10 +579,9 @@ const handleSelection = (
|
|||
itemIndex: number,
|
||||
shouldScroll: boolean = true
|
||||
): void => {
|
||||
selectedGroupIndex.value = groupIndex;
|
||||
selectedItemIndex.value = itemIndex;
|
||||
if (shouldScroll) scrollToSelectedItem();
|
||||
};
|
||||
selectItem(groupIndex, itemIndex)
|
||||
if (shouldScroll) scrollToSelectedItem()
|
||||
}
|
||||
|
||||
const setupEventListeners = async (): Promise<void> => {
|
||||
await listen("clipboard-content-updated", async () => {
|
||||
|
@ -592,7 +616,6 @@ const setupEventListeners = async (): Promise<void> => {
|
|||
}
|
||||
focusSearchInput();
|
||||
|
||||
// Re-register keyboard shortcuts on focus
|
||||
keyboard.clear();
|
||||
keyboard.prevent.down([Key.DownArrow], () => {
|
||||
selectNext();
|
||||
|
@ -666,9 +689,9 @@ const hideApp = async (): Promise<void> => {
|
|||
|
||||
const focusSearchInput = (): void => {
|
||||
nextTick(() => {
|
||||
searchInput.value?.focus();
|
||||
});
|
||||
};
|
||||
topBar.value?.searchInput?.focus()
|
||||
})
|
||||
}
|
||||
|
||||
const onImageError = (): void => {
|
||||
imageLoadError.value = true;
|
||||
|
@ -677,10 +700,10 @@ const onImageError = (): void => {
|
|||
|
||||
watch([selectedGroupIndex, selectedItemIndex], () => {
|
||||
scrollToSelectedItem();
|
||||
});
|
||||
}, { flush: 'post' });
|
||||
|
||||
watch(searchQuery, () => {
|
||||
searchHistory();
|
||||
searchHistory(searchQuery.value);
|
||||
});
|
||||
|
||||
onMounted(async () => {
|
||||
|
@ -699,10 +722,6 @@ onMounted(async () => {
|
|||
}
|
||||
});
|
||||
|
||||
watch([selectedGroupIndex, selectedItemIndex], () =>
|
||||
scrollToSelectedItem(false)
|
||||
);
|
||||
|
||||
const getFormattedDate = computed(() => {
|
||||
if (!selectedItem.value?.timestamp) return "";
|
||||
return new Intl.DateTimeFormat("en-US", {
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue