文件
chen 301194963b
一些检查失败了
automerge / automerge (push) Has been cancelled
react-native-android-build-apk / AssembleRelease (push) Has been cancelled
Build macOS M1 / Main (push) Has been cancelled
Joplin Continuous Integration / Main (macos-15-intel) (push) Has been cancelled
Joplin Continuous Integration / Main (ubuntu-22.04) (push) Has been cancelled
Joplin Continuous Integration / Main (ubuntu-22.04-arm) (push) Has been cancelled
Joplin Continuous Integration / Main (windows-11-vs2026-arm) (push) Has been cancelled
Joplin Continuous Integration / Main (windows-2025) (push) Has been cancelled
Joplin Continuous Integration / ServerDockerImage (ubuntu-22.04) (push) Has been cancelled
Joplin Continuous Integration / ServerDockerImage (ubuntu-22.04-arm) (push) Has been cancelled
Joplin UI tests / Main (ubuntu-22.04) (push) Has been cancelled
Joplin UI tests / Main (windows-2025) (push) Has been cancelled
Joplin UI tests / Mobile (push) Has been cancelled
Close stale issues / ProcessStaleIssues (push) Has been cancelled
feat: Initial clean release of CHM
2026-11-04 11:29:39 +08:00

498 行
13 KiB
SCSS

$selection-color: #4a90e2;
$selection-shadow: rgba(74, 144, 226, 0.25);
$card-shadow: 0 1px 3px rgba(0, 0, 0, 0.08);
$card-shadow-selected: 0 4px 12px $selection-shadow;
$active-bg: rgba(74, 144, 226, 0.18);
$active-fg: #2766b8;
$hover-bg: rgba(127, 127, 127, 0.12);
.whiteboard-editor {
display: flex;
flex-direction: column;
flex: 1;
min-height: 0;
> .error {
display: flex;
flex-direction: column;
flex: 1;
min-height: 0;
padding: 24px;
overflow: auto;
> .panel {
background-color: var(--joplin-warning-background-color);
color: var(--joplin-color);
padding: 16px;
border-radius: 4px;
display: flex;
flex-direction: column;
gap: 12px;
> .title { font-weight: 600; }
> .detail { white-space: pre-wrap; font-family: monospace; font-size: 12px; }
}
}
}
.whiteboard-surface {
position: relative;
flex: 1;
width: 100%;
height: 100%;
background: var(--joplin-background-color3);
outline: none;
.react-flow {
--xy-background-color-default: var(--joplin-background-color3);
--xy-background-pattern-dots-color-default: var(--joplin-divider-color);
--xy-edge-stroke-default: var(--joplin-divider-color);
--xy-edge-stroke-selected-default: #{$selection-color};
--xy-connectionline-stroke-default: var(--joplin-color-faded);
--xy-attribution-background-color-default: var(--joplin-background-color);
--xy-minimap-background-color-default: var(--joplin-background-color);
--xy-minimap-mask-background-color-default: var(--joplin-background-color3);
--xy-minimap-node-background-color-default: var(--joplin-color-faded);
--xy-node-color-default: var(--joplin-color);
--xy-node-background-color-default: var(--joplin-background-color);
--xy-controls-button-background-color-default: var(--joplin-background-color);
--xy-controls-button-color-default: var(--joplin-color);
--xy-controls-button-border-color-default: var(--joplin-divider-color);
}
// Only bump the stroke width on selection — the stroke colour is set
// inline (edge's own colour, or the default blue when unset) so we don't
// override it here.
.react-flow__edge.selected .react-flow__edge-path,
.react-flow__edge:focus .react-flow__edge-path,
.react-flow__edge:focus-visible .react-flow__edge-path {
stroke-width: 2 !important;
}
.react-flow__edge.selected .react-flow__edge-textbg { fill: $selection-color; }
.react-flow__edge.selected .react-flow__edge-text { fill: #ffffff; }
.react-flow__node .react-flow__handle {
opacity: 0;
transition: opacity 120ms ease;
background: var(--joplin-color-faded);
}
.react-flow__node:hover .react-flow__handle,
.react-flow__node.selected .react-flow__handle,
.react-flow__handle.connectingfrom,
.react-flow__handle.connectingto { opacity: 1; }
.react-flow__minimap { width: 160px; height: 100px; }
}
.whiteboard-action-panel {
position: absolute;
z-index: 10;
display: flex;
align-items: center;
gap: 0;
padding: 4px;
background: var(--joplin-background-color);
border: 1px solid var(--joplin-divider-color);
border-radius: 8px;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.10);
font-size: 12px;
height: 36px;
color: var(--joplin-color);
user-select: none;
> .input { user-select: text; }
&.-bottom-center { bottom: 16px; left: 50%; transform: translateX(-50%); }
&.-top-center { top: 16px; left: 50%; transform: translateX(-50%); }
&.-top-right { top: 8px; right: 8px; }
> .caption {
color: var(--joplin-color-faded);
padding: 0 10px;
white-space: nowrap;
}
> .divider {
width: 1px;
height: 20px;
background: var(--joplin-divider-color);
margin: 0 4px;
}
> .button {
height: 28px;
padding: 0 10px;
font-size: 12px;
border: none;
background: transparent;
color: var(--joplin-color);
cursor: pointer;
border-radius: 6px;
display: inline-flex;
align-items: center;
gap: 4px;
white-space: nowrap;
user-select: none;
&:hover:not(:disabled):not(.-active) { background: $hover-bg; }
&.-active { background: $active-bg; color: $active-fg; }
&:disabled { opacity: 0.45; cursor: default; }
}
> .input {
height: 24px;
padding: 0 8px;
font-size: 12px;
border: 1px solid var(--joplin-divider-color);
border-radius: 4px;
margin: 0 4px;
background: var(--joplin-background-color);
color: var(--joplin-color);
}
> .swatch {
width: 18px;
height: 18px;
border-radius: 50%;
border: 1px solid var(--joplin-divider-color);
padding: 0;
margin: 0 2px;
cursor: pointer;
background: transparent;
&:hover { transform: scale(1.15); }
&.-active { outline: 2px solid $selection-color; outline-offset: 1px; }
// "No colour" swatch: diagonal slash on empty background.
&.-none {
position: relative;
background: var(--joplin-background-color);
}
&.-none::after {
content: '';
position: absolute;
top: 50%;
left: -1px;
right: -1px;
height: 1px;
background: var(--joplin-color-faded);
transform: rotate(-45deg);
}
}
}
.whiteboard-node {
width: 100%;
height: 100%;
border: 1px solid var(--joplin-divider-color);
border-radius: 6px;
background: var(--joplin-background-color);
overflow: hidden;
box-shadow: $card-shadow;
box-sizing: border-box;
display: flex;
flex-direction: column;
color: var(--joplin-color);
// Only bump the border width and shadow on selection — the border colour
// is either the node's own colour (inline style) or falls back to the
// default selection blue set inline by the renderer.
&.-selected {
border-width: 2px;
border-style: solid;
box-shadow: $card-shadow-selected;
}
&.-text { overflow: auto; padding: 8px; font-size: 13px; line-height: 1.4; }
> .header {
font-size: 11px;
color: var(--joplin-color-faded);
padding: 4px 8px;
border-bottom: 1px solid var(--joplin-divider-color);
text-transform: uppercase;
letter-spacing: 0.5px;
flex-shrink: 0;
&.-note-title {
font-size: 12px;
font-weight: 600;
color: var(--joplin-color);
padding: 5px 8px;
text-transform: none;
letter-spacing: 0;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
}
> .body {
flex: 1;
padding: 8px;
overflow: auto;
word-break: break-word;
font-size: 13px;
line-height: 1.4;
color: var(--joplin-color);
&.-deleted, &.-locked { color: var(--joplin-color-faded); font-style: italic; }
&.-url { word-break: break-all; }
}
> .image {
max-width: 100%;
max-height: 100%;
object-fit: contain;
align-self: center;
flex: 1;
}
> .editor {
width: 100%;
height: 100%;
border: none;
outline: none;
resize: none;
font-family: inherit;
font-size: 13px;
line-height: 1.4;
color: var(--joplin-color);
background: transparent;
}
> .empty { color: var(--joplin-color-faded); }
}
// Groups are visual containers. pointer-events:none is set inline on the
// React Flow wrapper via canvasNodeToFlowNode (CSS classes can't override
// React Flow's own inline style on the wrapper). The label and handle below
// re-enable pointer events so they catch select / drag / rename.
.whiteboard-group {
position: relative;
width: 100%;
height: 100%;
// Two-layer construction: .background is the rounded fill (no pointer
// events). .ring is the dashed border with no background — because
// pointer-events:auto on an unpainted element only catches the painted
// pixels, the interior of .ring doesn't catch clicks. Only the border
// line does, which is exactly what we want: drag the line to move the
// group, click anywhere else to pan.
> .background {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
border-radius: 8px;
background: rgba(127, 127, 127, 0.06);
pointer-events: none;
}
// Four edge strips form the dashed border. Each strip is a separate
// painted element with pointer-events:auto, so clicks on the border line
// catch drags. The interior has no overlay at all, so clicks there fall
// through to the pane (pan / deselect) or to a card on top.
$edge-thickness: 8px;
> .edge {
position: absolute;
pointer-events: auto;
cursor: grab;
&:active { cursor: grabbing; }
&.-top {
top: -2px;
left: 0;
right: 0;
height: $edge-thickness;
border-top: 2px dashed var(--joplin-divider-color);
}
&.-bottom {
bottom: -2px;
left: 0;
right: 0;
height: $edge-thickness;
border-bottom: 2px dashed var(--joplin-divider-color);
}
&.-left {
top: 0;
bottom: 0;
left: -2px;
width: $edge-thickness;
border-left: 2px dashed var(--joplin-divider-color);
}
&.-right {
top: 0;
bottom: 0;
right: -2px;
width: $edge-thickness;
border-right: 2px dashed var(--joplin-divider-color);
}
}
&.-selected > .edge.-top { border-top-color: $selection-color; }
&.-selected > .edge.-bottom { border-bottom-color: $selection-color; }
&.-selected > .edge.-left { border-left-color: $selection-color; }
&.-selected > .edge.-right { border-right-color: $selection-color; }
> .anchor {
position: absolute;
top: -30px;
left: -30px;
width: 24px;
height: 24px;
border-radius: 4px;
background: var(--joplin-background-color);
border: 1px solid var(--joplin-divider-color);
color: var(--joplin-color-faded);
display: flex;
align-items: center;
justify-content: center;
font-size: 12px;
pointer-events: auto;
cursor: grab;
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.10);
&:hover { background: $selection-color; color: #fff; border-color: $selection-color; }
&:active { cursor: grabbing; }
}
&.-selected > .anchor { border-color: $selection-color; color: $selection-color; }
&.-selected > .anchor:hover { color: #fff; }
> .label {
position: absolute;
top: 6px;
left: 10px;
max-width: calc(100% - 20px);
font-size: 12px;
font-weight: 600;
color: var(--joplin-color-faded);
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
pointer-events: auto;
cursor: grab;
padding: 1px 4px;
border-radius: 3px;
&:hover { background: rgba(127, 127, 127, 0.10); }
&:active { cursor: grabbing; }
> .placeholder { font-weight: 400; font-style: italic; }
}
> input.label {
border: 1px solid var(--joplin-divider-color);
border-radius: 4px;
padding: 1px 6px;
background: var(--joplin-background-color);
color: var(--joplin-color);
font-weight: 600;
outline: none;
pointer-events: auto;
cursor: text;
}
}
// Re-enable pointer events on the resize handles when a group is selected,
// since the wrapper itself is pointer-transparent.
.react-flow__node-wbGroup.selected .react-flow__resize-control {
pointer-events: auto;
}
.whiteboard-node-promote {
position: absolute;
top: -10px;
right: -10px;
padding: 2px 8px;
font-size: 11px;
border: 1px solid $selection-color;
border-radius: 10px;
background: var(--joplin-background-color);
color: $selection-color;
cursor: pointer;
z-index: 5;
}
.wb-card-md {
flex: 1;
overflow: auto;
word-break: break-word;
font-size: 13px;
line-height: 1.4;
h1 { font-size: 16px; margin: 4px 0; }
h2 { font-size: 15px; margin: 4px 0; }
h3 { font-size: 14px; margin: 3px 0; }
h4, h5, h6 { font-size: 13px; margin: 3px 0; }
p { margin: 8px 0; }
// Drop the top margin on whatever comes first so a leading heading /
// paragraph / list sits flush against the card's own 8px padding
// instead of stacking margins on top of it. The renderer wraps its
// output in a `#rendered-md` div, so we also target the first child
// inside that wrapper — `:first-child` alone would only match the div.
> :first-child, > * > :first-child { margin-top: 0; }
ul, ol { margin: 4px 0; padding-left: 20px; }
li { margin: 2px 0; }
li.md-checkbox, li.joplin-checkbox { list-style: none; margin-left: -20px; }
li.md-checkbox input[type=checkbox] { margin-right: 6px; vertical-align: middle; }
.checkbox-wrapper { display: inline; }
pre {
margin: 4px 0;
padding: 6px;
font-size: 12px;
background: var(--joplin-code-background-color);
border: 1px solid var(--joplin-code-border-color);
border-radius: 4px;
overflow: auto;
color: var(--joplin-code-color);
}
code {
font-size: 12px;
background: var(--joplin-code-background-color);
color: var(--joplin-code-color);
border: 1px solid var(--joplin-code-border-color);
padding: 0 3px;
border-radius: 3px;
}
pre code { background: transparent; padding: 0; border: none; }
blockquote {
margin: 4px 0;
padding-left: 8px;
border-left: 3px solid var(--joplin-code-border-color);
color: var(--joplin-color-faded);
}
img { max-width: 100%; height: auto; }
table {
font-size: 12px;
border-collapse: collapse;
border: 1px solid var(--joplin-code-border-color);
}
th, td { padding: 2px 6px; border: 1px solid var(--joplin-code-border-color); }
th {
background: var(--joplin-background-color-hover3, var(--joplin-background-color3));
border-bottom-width: 2px;
}
// Only zebra rows for tables with an explicit thead — matches the viewer
// (see noteStyle.ts) so freeform key/value tables don't get striped.
table:has(thead) tr:nth-child(even) {
background: var(--joplin-background-color-hover3, var(--joplin-background-color3));
}
hr { margin: 6px 0; border: none; border-top: 2px solid var(--joplin-divider-color); }
a {
color: var(--joplin-url-color, #{$selection-color});
&:hover { text-decoration: underline; }
}
mark {
background: var(--joplin-search-marker-background-color);
color: var(--joplin-search-marker-color);
}
// Match the viewer's checked-checkbox affordance so completed items visually
// recede without disappearing.
.md-checkbox input[type=checkbox]:checked { opacity: 0.7; }
.md-checkbox .checkbox-label-checked { opacity: 0.5; }
}