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
19 changes: 19 additions & 0 deletions next.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,25 @@ const nextConfig: NextConfig = {
},
trailingSlash: true,
sassOptions: {
// Silence Sass deprecation warnings emitted by dependencies in node_modules.
quietDeps: true,
// Suppress known upstream/toolchain deprecations until Carbon/Next update internals.
silenceDeprecations: ['mixed-decls', 'legacy-js-api'],
},
webpack: (config) => {
config.ignoreWarnings = [
...(config.ignoreWarnings ?? []),
{
// Suppress Dart Sass deprecation noise from current Next/Sass integration.
message: /Deprecation.*legacy JS API is deprecated/i,
},
{
// Suppress upstream Carbon mixed declarations deprecation output.
message: /Deprecation Warning.*mixed-decls/i,
},
];

return config;
},
}

Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "carbon-type",
"version": "0.2.0",
"version": "0.3.0",
"private": true,
"scripts": {
"dev": "next dev",
Expand Down
1 change: 0 additions & 1 deletion src/app/globals.scss
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
@use "@carbon/react";
@use "./styles/base";
@use "./styles/title-bar";
@use "./styles/document";
@use "./styles/status";
@use "./styles/mobile";

Expand Down
228 changes: 121 additions & 107 deletions src/app/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,9 @@
import React, { useState, useRef, useCallback, useEffect } from 'react';
import { asBlob } from 'html-docx-js/dist/html-docx';
import Ribbon from '@/components/Ribbon';
import DocumentEditor from '@/components/DocumentEditor/DocumentEditor';
import WordTitleBar from '@/components/WordTitleBar/WordTitleBar';
import WordStatusBar from '@/components/WordStatusBar/WordStatusBar';
import { DocumentTextStyle, getStylePresets } from '@/components/Ribbon/ribbonConfig';
import DocumentEditor, { DocumentSurface } from '@/components/DocumentEditor';
import TitleBar from '@/components/TitleBar/TitleBar';
import { DocumentTextStyle, getStylePresets, StylePreset } from '@/components/Ribbon/ribbonConfig';

type TextAlignment = 'left' | 'center' | 'right' | 'justify';
type CaseMode = 'sentence' | 'lowercase' | 'uppercase' | 'capitalize' | 'toggle';
Expand Down Expand Up @@ -204,6 +203,74 @@ export default function WordProcessor() {
if (!el) return;
el.focus();

const applyStylePresetToSelection = (preset: StylePreset) => {
const applyTextStyle = (block: HTMLElement, textStyle: DocumentTextStyle) => {
block.style.fontFamily = textStyle.fontFamily;
block.style.fontSize = `${textStyle.fontSizePt}pt`;
block.style.fontWeight = textStyle.fontWeight;
block.style.fontStyle = textStyle.fontStyle ?? 'normal';
block.style.color = textStyle.color;
};

const getBlockAncestor = (node: Node): HTMLElement | null => {
let current: Node | null = node;
while (current && current !== el) {
if (
current.nodeType === Node.ELEMENT_NODE &&
['P', 'H1', 'H2', 'H3'].includes((current as Element).tagName)
) {
return current as HTMLElement;
}
current = current.parentNode;
}
return null;
};

const selection = window.getSelection();
if (!selection?.rangeCount) {
return;
}

const range = selection.getRangeAt(0);
const blocks = new Set<HTMLElement>();
const startBlock = getBlockAncestor(range.startContainer);
const endBlock = getBlockAncestor(range.endContainer);
if (startBlock) blocks.add(startBlock);
if (endBlock) blocks.add(endBlock);

const ancestor = range.commonAncestorContainer;
const walkerRoot =
ancestor.nodeType === Node.ELEMENT_NODE
? (ancestor as Element)
: (ancestor.parentElement ?? el);
const walker = document.createTreeWalker(walkerRoot, NodeFilter.SHOW_ELEMENT, {
acceptNode(node) {
const tag = (node as Element).tagName;
return ['P', 'H1', 'H2', 'H3'].includes(tag)
? NodeFilter.FILTER_ACCEPT
: NodeFilter.FILTER_SKIP;
},
});

while (walker.nextNode()) {
const node = walker.currentNode as HTMLElement;
if (range.intersectsNode(node)) blocks.add(node);
}

blocks.forEach((block) => applyTextStyle(block, preset.textStyle));
};

if (command === 'applyStylePreset' && value) {
try {
const preset = JSON.parse(value) as StylePreset;
document.execCommand('formatBlock', false, preset.val);
applyStylePresetToSelection(preset);
} catch {
// Ignore malformed style payloads and keep editor stable.
}
return;
}

if (command === 'changeCase') {
const mode = value as CaseMode | undefined;
if (!mode) return;
Expand Down Expand Up @@ -249,58 +316,7 @@ export default function WordProcessor() {
if (command === 'formatBlock' && value) {
const selectedStyle = getStylePresets(citationStyle).find((style) => style.val === value);
if (selectedStyle) {
const applyTextStyle = (block: HTMLElement, textStyle: DocumentTextStyle) => {
block.style.fontFamily = textStyle.fontFamily;
block.style.fontSize = `${textStyle.fontSizePt}pt`;
block.style.fontWeight = textStyle.fontWeight;
block.style.fontStyle = textStyle.fontStyle ?? 'normal';
block.style.color = textStyle.color;
};

const getBlockAncestor = (node: Node): HTMLElement | null => {
let current: Node | null = node;
while (current && current !== el) {
if (
current.nodeType === Node.ELEMENT_NODE &&
['P', 'H1', 'H2', 'H3'].includes((current as Element).tagName)
) {
return current as HTMLElement;
}
current = current.parentNode;
}
return null;
};

const selection = window.getSelection();
if (selection?.rangeCount) {
const range = selection.getRangeAt(0);
const blocks = new Set<HTMLElement>();
const startBlock = getBlockAncestor(range.startContainer);
const endBlock = getBlockAncestor(range.endContainer);
if (startBlock) blocks.add(startBlock);
if (endBlock) blocks.add(endBlock);

const ancestor = range.commonAncestorContainer;
const walkerRoot =
ancestor.nodeType === Node.ELEMENT_NODE
? (ancestor as Element)
: (ancestor.parentElement ?? el);
const walker = document.createTreeWalker(walkerRoot, NodeFilter.SHOW_ELEMENT, {
acceptNode(node) {
const tag = (node as Element).tagName;
return ['P', 'H1', 'H2', 'H3'].includes(tag)
? NodeFilter.FILTER_ACCEPT
: NodeFilter.FILTER_SKIP;
},
});

while (walker.nextNode()) {
const node = walker.currentNode as HTMLElement;
if (range.intersectsNode(node)) blocks.add(node);
}

blocks.forEach((block) => applyTextStyle(block, selectedStyle.textStyle));
}
applyStylePresetToSelection(selectedStyle);
}
}

Expand Down Expand Up @@ -742,7 +758,7 @@ export default function WordProcessor() {

return (
<div className="word-processor">
<WordTitleBar
<TitleBar
documentName={documentName}
autosaveEnabled={autosaveEnabled}
autosaveStatus={autosaveStatus}
Expand All @@ -753,59 +769,57 @@ export default function WordProcessor() {
setAutosaveStatus('');
}
}}
onSave={handleSave}
onUndo={() => handleFormat('undo')}
onRedo={() => handleFormat('redo')}
onPrint={handlePrint}
/>

{/* Ribbon */}
<Ribbon
onFormat={handleFormat}
fontSize={fontSize}
fontFamily={fontFamily}
onFontSizeChange={handleFontSizeChange}
onFontFamilyChange={handleFontFamilyChange}
isBold={isBold}
isItalic={isItalic}
isUnderline={isUnderline}
isStrikethrough={isStrikethrough}
isSubscript={isSubscript}
isSuperscript={isSuperscript}
isUnorderedList={isUnorderedList}
isOrderedList={isOrderedList}
alignment={alignment}
onPrint={handlePrint}
onZoom={handleZoom}
lineSpacing={lineSpacing}
onLineSpacingChange={handleLineSpacingChange}
citationStyle={citationStyle}
onCitationStyleChange={handleCitationStyleChange}
/>

{/* Document Canvas — grey background, scrollable */}
<div className="document-canvas">
{/* Document Page — white paper, scaled for zoom */}
<div
className="document-page"
style={{
transform: `scale(${scaleFactor})`,
transformOrigin: 'top center',
// Compensate layout space so the canvas scrollbar reflects true content height
marginBottom: `${(scaleFactor - 1) * 1056}px`,
}}
>
<DocumentEditor
ref={editorRef}
onWordCountChange={setWordCount}
onAutosave={handleAutosave}
onAutosaveStart={handleAutosaveStart}
autosaveEnabled={autosaveEnabled}
<DocumentSurface
scaleFactor={scaleFactor}
toolbar={(
<Ribbon
onFormat={handleFormat}
onNew={() => {
const el = editorRef.current;
if (!el) return;
el.innerHTML = '<div><br></div>';
el.focus();
setWordCount(0);
}}
onOpen={() => {
window.alert('Open is not implemented yet.');
}}
onDownload={handleSave}
onPageSetup={() => {
window.alert('Page Setup is not implemented yet.');
}}
fontSize={fontSize}
fontFamily={fontFamily}
onFontSizeChange={handleFontSizeChange}
onFontFamilyChange={handleFontFamilyChange}
isBold={isBold}
isItalic={isItalic}
isUnderline={isUnderline}
isStrikethrough={isStrikethrough}
isSubscript={isSubscript}
isSuperscript={isSuperscript}
isUnorderedList={isUnorderedList}
isOrderedList={isOrderedList}
alignment={alignment}
onPrint={handlePrint}
onZoom={handleZoom}
lineSpacing={lineSpacing}
onLineSpacingChange={handleLineSpacingChange}
citationStyle={citationStyle}
onCitationStyleChange={handleCitationStyleChange}
/>
</div>
</div>

<WordStatusBar wordCount={wordCount} zoom={zoom} />
)}
>
<DocumentEditor
ref={editorRef}
onWordCountChange={setWordCount}
onAutosave={handleAutosave}
onAutosaveStart={handleAutosaveStart}
autosaveEnabled={autosaveEnabled}
/>
</DocumentSurface>
</div>
);
}
1 change: 1 addition & 0 deletions src/app/styles/_base.scss
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ html {
}

.word-processor {
background-color: transparent;
display: flex;
flex-direction: column;
height: 100dvh;
Expand Down
4 changes: 2 additions & 2 deletions src/app/styles/_status.scss
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,8 @@
font-size: 10px;
font-weight: 700;
letter-spacing: 0.12em;
color: rgba(255, 60, 60, 0.55);
border: 1px solid rgba(255, 60, 60, 0.35);
color: rgba(255, 0, 0, 1);
border: 1px solid rgba(255, 0, 0, 0.75);
border-radius: 3px;
padding: 1px 5px;
pointer-events: none;
Expand Down
11 changes: 7 additions & 4 deletions src/app/styles/_title-bar.scss
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
.word-title-bar {
display: flex;
align-items: center;
background: #2b5797;
color: #fff;
height: 34px;
background: #525252;
height: 40px;
padding: 0 4px;
flex-shrink: 0;
gap: 4px;
Expand Down Expand Up @@ -34,7 +33,7 @@
text-align: center;
font-size: 13px;
font-weight: 400;
color: #fff;
color: #edf5ff;
pointer-events: none;
user-select: none;
}
Expand Down Expand Up @@ -70,6 +69,10 @@
margin: 0;
}

.cds--toggle__switch--checked {
background-color: #0f62fe;
}

.cds--toggle__label {
margin: 0;
}
Expand Down
Loading
Loading