Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
112 changes: 102 additions & 10 deletions src/client/components/Header.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,12 @@
import { css } from "@emotion/react";
import { useState } from "react";
import { Link, useNavigate } from "react-router";
import { apiItemsUpdatePath, itemsShowPath } from "../../lib/qiita-cli-url";
import {
apiItemsUpdatePath,
apiSlidesUpdatePath,
itemsShowPath,
slidesShowPath,
} from "../../lib/qiita-cli-url";
import { breakpoint, pointerFine } from "../lib/mixins";
import { Colors, Typography, Weight, getSpace } from "../lib/variables";
import { useWindowSize } from "../lib/window-size";
Expand Down Expand Up @@ -115,17 +120,66 @@ export const Header = ({
};

export const HeaderSlide = ({
id,
basename,
isSlidePublishable,
isOlderThanRemote,
slidePath,
presentPath,
handleMobileOpen,
}: {
id: string;
basename: string | null;
isSlidePublishable: boolean;
isOlderThanRemote: boolean;
slidePath: string;
presentPath: string;
handleMobileOpen: () => void;
}) => {
const navigate = useNavigate();
const [snackbarMessage, setSnackbarMessage] =
useState<null | SnackbarMessage>(null);
const { currentWidth } = useWindowSize();
const mobileSize = currentWidth <= breakpoint.S;

const handlePublish = () => {
if (isOlderThanRemote) {
if (
!window.confirm(
"このスライドはQiita上のスライドより古い可能性があります。上書きしますか?",
)
) {
return;
}
}

fetch(apiSlidesUpdatePath(id), {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({ basename: basename }),
})
.then((response) => response.json())
.then((data) => {
if (!data.uuid) {
setSnackbarMessage({
type: "error",
message: "投稿に失敗しました",
});

return;
}

setSnackbarMessage({
type: "success",
message: "スライドが投稿されました",
});

navigate(slidesShowPath(data.uuid));
});
};

return (
<header css={headerStyle}>
{mobileSize ? (
Expand All @@ -148,15 +202,32 @@ export const HeaderSlide = ({
<CopyButton text={slidePath} />
</div>
)}
<Link
css={headerButtonStyle}
to={presentPath}
target="_blank"
rel="noopener noreferrer"
>
{mobileSize ? "プレゼンモード" : "プレゼンテーションモードで開く"}
<MaterialSymbol>slideshow</MaterialSymbol>
</Link>
<div css={headerActionsStyle}>
<Link
aria-label={mobileSize ? "スライドショーを開始" : undefined}
css={[
headerButtonStyle,

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

headerButtonStyleのhover時に textDecoration: "none", を追加したいです。

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

追加しました(50e2d28)。

hover 時に「スライドショーを開始」(<a>)に下線が付いていたのが消えることを、Playwright で確認しています(text-decoration-line が underline → none)。Before / After のスクリーンショットは PR 本文の「ヘッダーのボタンリンクに hover で下線が付かないようにした」に貼りました。<button> のボタンは、もともと下線が付かないので変化はありません。

🤖 Generated by Claude Code

headerGrayButtonStyle,
mobileSize && headerIconButtonStyle,
]}
to={presentPath}
target="_blank"
rel="noopener noreferrer"
>
{!mobileSize && "スライドショーを開始"}
<MaterialSymbol>slideshow</MaterialSymbol>
</Link>
<button
aria-label={mobileSize ? "スライドを投稿する" : undefined}
css={[headerButtonStyle, mobileSize && headerIconButtonStyle]}
disabled={!isSlidePublishable}
onClick={handlePublish}
>
{!mobileSize && "スライドを投稿する"}
<MaterialSymbol>publish</MaterialSymbol>
</button>
Comment on lines +206 to +228

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

このボタン2つのサイズが合ってなさそう

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

直しました(f36199a)。

<button> だけが UA スタイルのフォント(Arial 13.33px)のままで、フォントを継承する <a> より低くなっていました(1920px で 33px と 40.8px)。headerButtonStyle に font: inherit を足して、どちらも 40.8px に揃えています。375px はもともと両方 48×48 です。

このスタイルは記事の「記事を投稿する」ボタンでも使っているので、そちらも 16px・40.8px に変わります(PR 本文に追記しました)。

🤖 Generated by Claude Code

</div>
<Snackbar message={snackbarMessage} setMessage={setSnackbarMessage} />
</header>
);
};
Expand Down Expand Up @@ -220,6 +291,7 @@ const headerButtonStyle = css({
borderRadius: 8,
color: Colors.green80,
display: "flex",
font: "inherit",
fontWeight: Weight.bold,
gap: `0 ${getSpace(1 / 2)}px`,
padding: `${getSpace(1 / 2)}px ${getSpace(2)}px`,
Expand All @@ -235,10 +307,30 @@ const headerButtonStyle = css({
...pointerFine({
"&:hover": {
backgroundColor: Colors.gray20,
textDecoration: "none",
},
}),
});

const headerActionsStyle = css({
alignItems: "center",
display: "flex",
gap: getSpace(1),
});

const headerGrayButtonStyle = css({
borderColor: Colors.gray80,
color: Colors.gray80,
});

const headerIconButtonStyle = css({
boxSizing: "border-box",
height: 48,
justifyContent: "center",
padding: 0,
width: 48,
});

const headerLogoWrapperStyle = css({
display: "flex",
});
Expand Down
34 changes: 1 addition & 33 deletions src/client/components/MarpSlidePresenter.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ import {
useEffect,
useState,
} from "react";
import { Colors, Typography, getSpace } from "../lib/variables";
import { MarpSlideShadowContent } from "./MarpSlideShadowContent";

const LEFT_KEY = 37;
Expand All @@ -19,12 +18,11 @@ interface SlidePage {
interface Props {
pages: SlidePage[];
slideCss: string;
title?: string;
}

// `slideCss`, not `css`: the Emotion JSX pragma (jsxImportSource) intercepts
// any prop literally named `css` on every element, including custom components.
export const MarpSlidePresenter = ({ pages, slideCss, title }: Props) => {
export const MarpSlidePresenter = ({ pages, slideCss }: Props) => {
const [currentPageIndex, setCurrentPageIndex] = useState(0);
const totalPage = pages.length;

Expand Down Expand Up @@ -79,14 +77,6 @@ export const MarpSlidePresenter = ({ pages, slideCss, title }: Props) => {
<div css={contentStyle} onClick={handleClickScreen}>
<MarpSlideShadowContent slideCss={slideCss} html={page?.html ?? ""} />
</div>
<div css={footerStyle}>
<span css={titleStyle}>{title}</span>
{totalPage > 0 && (
<span css={pageCounterStyle}>
{currentPageIndex + 1} / {totalPage}
</span>
)}
</div>
</div>
);
};
Expand All @@ -102,25 +92,3 @@ const contentStyle = css({
flex: 1,
overflow: "auto",
});

const footerStyle = css({
alignItems: "center",
backgroundColor: Colors.gray0,
borderTop: `1px solid ${Colors.divider}`,
display: "flex",
fontSize: Typography.body2,
justifyContent: "space-between",
padding: `${getSpace(1)}px ${getSpace(2)}px`,
});

const titleStyle = css({
overflow: "hidden",
textOverflow: "ellipsis",
whiteSpace: "nowrap",
});

const pageCounterStyle = css({
color: Colors.mediumEmphasis,
flexShrink: 0,
marginLeft: getSpace(2),
});
51 changes: 39 additions & 12 deletions src/client/components/MarpSlideViewer/index.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
import { css } from "@emotion/react";
import { Colors, Typography, getSpace } from "../../lib/variables";
import {
Colors,
LineHeight,
Typography,
Weight,
getSpace,
} from "../../lib/variables";
import { MarpSlideShadowContent } from "../MarpSlideShadowContent";

interface SlidePage {
Expand All @@ -15,18 +21,23 @@ interface Props {
// `slideCss`, not `css`: the Emotion JSX pragma (jsxImportSource) intercepts
// any prop literally named `css` on every element, including custom components.
export const MarpSlideViewer = ({ pages, slideCss }: Props) => {
const totalPage = pages.length;

return (
<div css={listStyle}>
{pages.map((page, index) => (
<div key={index} css={pageWrapperStyle}>
<div css={pageStyle}>
<MarpSlideShadowContent slideCss={slideCss} html={page.html} />
</div>
<span css={pageNumberStyle}>
{index + 1} / {totalPage}
</span>
{page.speaker_note.length > 0 && (
<section css={speakerNoteStyle}>
<h2 css={speakerNoteLabelStyle}>スピーカーノート</h2>
{page.speaker_note.map((note, noteIndex) => (
<p key={noteIndex} css={speakerNoteBodyStyle}>
{note}
</p>
))}
</section>
)}
</div>
))}
</div>
Expand All @@ -43,19 +54,35 @@ const listStyle = css({
const pageWrapperStyle = css({
display: "flex",
flexDirection: "column",
gap: getSpace(1 / 2),
gap: getSpace(2),
width: "100%",
});

const pageStyle = css({
aspectRatio: "16 / 9",
boxShadow: "0 1px 4px rgba(0, 0, 0, 0.2)",
border: `1px solid ${Colors.divider}`,
boxSizing: "border-box",
overflow: "hidden",
width: "100%",
});

const pageNumberStyle = css({
alignSelf: "flex-end",
color: Colors.mediumEmphasis,
fontSize: Typography.body2,
const speakerNoteStyle = css({
backgroundColor: Colors.surfaceVariant,
borderRadius: 8,
display: "flex",
flexDirection: "column",
gap: getSpace(1),
padding: `${getSpace(3 / 2)}px ${getSpace(2)}px`,
});

const speakerNoteLabelStyle = css({
fontSize: Typography.body1,
fontWeight: Weight.bold,
lineHeight: LineHeight.body,
});

const speakerNoteBodyStyle = css({
fontSize: Typography.body1,
lineHeight: LineHeight.body,
whiteSpace: "pre-wrap",
});
2 changes: 1 addition & 1 deletion src/client/lib/variables.ts
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@ export const Colors = {
// Base color
background: "var(--color-background)",
surface: "var(--color-surface)",
surfaceVariant: "var(--color-surface-variant)",
surfaceVariant: "var(--color-surfaceVariant)",

// Divider
divider: "var(--color-divider)",
Expand Down
14 changes: 8 additions & 6 deletions src/client/pages/slides/show.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -71,11 +71,7 @@ export const SlidesShow = () => {
if (isPresentationMode) {
return slide ? (
<div css={presentationScreenStyle}>
<MarpSlidePresenter
pages={slide.pages}
slideCss={slide.css}
title={slide.title}
/>
<MarpSlidePresenter pages={slide.pages} slideCss={slide.css} />
</div>
) : (
<div css={messageContainerStyle}>
Expand All @@ -98,9 +94,15 @@ export const SlidesShow = () => {
</Sidebar>

<Contents>
{slide ? (
{id && slide ? (
<>
<HeaderSlide
id={id}
basename={basename}
isSlidePublishable={
slide.modified && slide.error_messages.length === 0
}
isOlderThanRemote={slide.is_older_than_remote}
handleMobileOpen={handleMobileOpen}
slidePath={slide.slide_path}
presentPath={presentPath}
Expand Down
4 changes: 4 additions & 0 deletions src/lib/qiita-cli-url.ts
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,10 @@ export const apiItemsUpdatePath = (itemId: string): string => {
return `/api/items/${itemId === "show" ? "post" : itemId}`;
};

export const apiSlidesUpdatePath = (slideId: string): string => {
return `/api/slides/${slideId === "show" ? "post" : slideId}`;
};

export const apiReadmeShowPath = () => {
return "/api/readme";
};
2 changes: 2 additions & 0 deletions src/lib/view-models/slides.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ export type SlidesShowViewModel = {
slides_show_path: string;
slide_path: string;
published: boolean;
modified: boolean;
is_older_than_remote: boolean;
theme: string | null;
};

Expand Down
Loading
Loading