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
58 changes: 43 additions & 15 deletions .github/workflows/deploy-macos.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -7,29 +7,57 @@ on:
jobs:
build:
runs-on: macos-latest
env:
APP_PATH: src-tauri/target/universal-apple-darwin/release/bundle/macos/Scriptio.app
PKG_PATH: Scriptio.pkg
steps:
- uses: actions/checkout@v6
- uses: actions/checkout@v4

- name: Setup Node.js
uses: actions/setup-node@v6
- name: Import Apple certificates
uses: apple-actions/import-codesign-certs@v3
with:
node-version: "25.2.1"
p12-file-base64: ${{ secrets.APPLE_CERT_P12_COMBINED_BASE64 }}
p12-password: ${{ secrets.APPLE_CERT_PASSWORD }}

- name: Setup Rust
uses: dtolnay/rust-toolchain@stable
- name: Download provisioning profile
uses: apple-actions/download-provisioning-profiles@v3
with:
targets: aarch64-apple-darwin, x86_64-apple-darwin
bundle-id: "ArkoLogic.Scriptio"
profile-type: "MAC_APP_STORE"
issuer-id: ${{ secrets.APPSTORE_ISSUER_ID }}
api-key-id: ${{ secrets.APPSTORE_KEY_ID }}
api-key-content: ${{ secrets.APPSTORE_API_KEY_P8 }}

- uses: actions/setup-node@v4
with: { node-version: "22" }
- uses: dtolnay/rust-toolchain@stable
with: { targets: "aarch64-apple-darwin, x86_64-apple-darwin" }

- name: Install dependencies
run: npm install

- name: Build MacOS bundle
run: npm run build:macos
- name: Build and sign .app with Tauri
run: npm run tauri build -- --bundles app --target universal-apple-darwin
env:
APPLE_SIGNING_IDENTITY: ${{ secrets.APPLE_SIGNING_IDENTITY }}

- name: Upload to App Store Connect
- name: Embed provisioning profile
run: |
PROFILE=$(ls "$HOME/Library/MobileDevice/Provisioning Profiles/"*.provisionprofile | head -n 1)
cp "$PROFILE" "$APP_PATH/Contents/embedded.provisionprofile"

- name: Build signed .pkg installer
run: |
xcrun altool --upload-package \
--file "Scriptio.pkg" \
--type osx \
--apiKey "${{ secrets.APPSTORE_KEY_ID }}" \
--apiIssuer "${{ secrets.APPSTORE_ISSUER_ID }}"
xcrun productbuild \
--component "$APP_PATH" /Applications \
--sign "${{ secrets.APPLE_INSTALLER_IDENTITY }}" \
"$PKG_PATH"

- name: Upload to App Store Connect
uses: apple-actions/upload-testflight-build@v1
with:
app-path: ${{ env.PKG_PATH }}
issuer-id: ${{ secrets.APPSTORE_ISSUER_ID }}
api-key-id: ${{ secrets.APPSTORE_KEY_ID }}
api-key-content: ${{ secrets.APPSTORE_API_KEY_P8 }}
app-id: ${{ secrets.APPLE_APP_ID }}
27 changes: 12 additions & 15 deletions components/dashboard/account/DashboardAuth.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -92,30 +92,27 @@ const DashboardAuth = () => {
<div className={`${styles.message} ${styles.info}`}>
{tAuth("checkInbox", { email })}
</div>
<button
type="button"
className={styles.authSwitchLink}
style={{ alignSelf: "flex-end" }}
onClick={() => {
setSubmitted(false);
setPollingDesktop(false);
setMessage(null);
}}
>
{tAuth("useDifferentEmail")}
</button>
{pollingDesktop && (
<p className={styles.authInfoText}>{tAuth("waitingForClick")}</p>
)}
<div className={styles.authLinks}>
<button
type="button"
className={styles.authSwitchLink}
onClick={() => {
setSubmitted(false);
setPollingDesktop(false);
setMessage(null);
}}
>
{tAuth("useDifferentEmail")}
</button>
</div>
</div>
);
}

return (
<form className={`${sharedStyles.settingsForm} ${styles.authForm}`} onSubmit={handleSubmit}>
<p className={styles.authInfoText}>{tAuth("intro")}</p>

<div className={sharedStyles.formGroup}>
<label htmlFor="auth-email" className={form.label}>
{tAuth("emailLabel")}
Expand Down
2 changes: 1 addition & 1 deletion components/dashboard/account/OAuthButtons.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ const OAuthButtons = ({ callbackUrl = "/" }: Props) => {

const nonce = generateBridgeNonce();
const apiBase = process.env.NEXT_PUBLIC_API_URL || window.location.origin;
const bridgeUrl = `${apiBase}/desktop-auth/start?provider=${provider}&nonce=${encodeURIComponent(nonce)}`;
const bridgeUrl = `${apiBase}/desktop-oauth/start?provider=${provider}&nonce=${encodeURIComponent(nonce)}`;
await openUrl(bridgeUrl);

const token = await pollBridgeToken(nonce);
Expand Down
96 changes: 76 additions & 20 deletions components/editor/DocumentEditorPanel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,8 @@ const DocumentEditorPanel = ({
// Resolve the comments Y.Map for this document
const projectState = repository?.getState();
const commentsMap = useMemo(
() => (projectState && config.features.comments ? config.getCommentsMap(projectState) : null),
() =>
projectState && config.features.comments ? config.getCommentsMap(projectState) : null,
// Re-derive only when projectState identity changes (Yjs doc swap on project change)
// eslint-disable-next-line react-hooks/exhaustive-deps
[projectState],
Expand Down Expand Up @@ -118,7 +119,9 @@ const DocumentEditorPanel = ({
// Register the editor instance with the parent wrapper
useEffect(() => {
onEditorCreated?.(editor);
return () => { onEditorCreated?.(null); };
return () => {
onEditorCreated?.(null);
};
}, [editor, onEditorCreated]);

// Ready state
Expand Down Expand Up @@ -158,7 +161,15 @@ const DocumentEditorPanel = ({
editorElement.style.setProperty("--contd-label", `"${contdLabel}"`);
editorElement.style.setProperty("--more-label", `"${moreLabel}"`);

const elementKeys = ["action", "scene", "character", "dialogue", "parenthetical", "transition", "section"] as const;
const elementKeys = [
"action",
"scene",
"character",
"dialogue",
"parenthetical",
"transition",
"section",
] as const;
for (const key of elementKeys) {
const m = elementMargins[key] ?? DEFAULT_ELEMENT_MARGINS[key];
// Element CSS vars = page margin + element offset (total from page edge)
Expand All @@ -170,8 +181,14 @@ const DocumentEditorPanel = ({
editorElement.style.setProperty(`--${key}-align`, s.align ?? "left");
editorElement.style.setProperty(`--${key}-weight`, s.bold ? "bold" : "normal");
editorElement.style.setProperty(`--${key}-style`, s.italic ? "italic" : "normal");
editorElement.style.setProperty(`--${key}-decoration`, s.underline ? "underline" : "none");
editorElement.style.setProperty(`--${key}-transform`, s.uppercase ? "uppercase" : "none");
editorElement.style.setProperty(
`--${key}-decoration`,
s.underline ? "underline" : "none",
);
editorElement.style.setProperty(
`--${key}-transform`,
s.uppercase ? "uppercase" : "none",
);
}

// Compute startNewPage types from element styles
Expand All @@ -197,13 +214,25 @@ const DocumentEditorPanel = ({
right: pageMargins.right * 96,
})
.run();

}

if (isVisible) {
editor.commands.focus();
}
}, [editor, isVisible, config.type, pageFormat, pageMargins, displaySceneNumbers, sceneHeadingSpacing, sceneNumberOnRight, contdLabel, moreLabel, elementMargins, elementStyles]);
}, [
editor,
isVisible,
config.type,
pageFormat,
pageMargins,
displaySceneNumbers,
sceneHeadingSpacing,
sceneNumberOnRight,
contdLabel,
moreLabel,
elementMargins,
elementStyles,
]);

// ---- Pagination update (title page only) ----
useEffect(() => {
Expand All @@ -228,9 +257,15 @@ const DocumentEditorPanel = ({
const updateContextMenuRef = useRef(updateContextMenu);
const updateSuggestionsRef = useRef(updateSuggestions);

useEffect(() => { selectedElementRef.current = selectedElement; }, [selectedElement]);
useEffect(() => { updateContextMenuRef.current = updateContextMenu; }, [updateContextMenu]);
useEffect(() => { updateSuggestionsRef.current = updateSuggestions; }, [updateSuggestions]);
useEffect(() => {
selectedElementRef.current = selectedElement;
}, [selectedElement]);
useEffect(() => {
updateContextMenuRef.current = updateContextMenu;
}, [updateContextMenu]);
useEffect(() => {
updateSuggestionsRef.current = updateSuggestions;
}, [updateSuggestions]);

const setActiveElement = useCallback(
(element: ScreenplayElement, applyStyle = true) => {
Expand All @@ -241,7 +276,9 @@ const DocumentEditorPanel = ({
);

const setActiveElementRef = useRef(setActiveElement);
useEffect(() => { setActiveElementRef.current = setActiveElement; }, [setActiveElement]);
useEffect(() => {
setActiveElementRef.current = setActiveElement;
}, [setActiveElement]);

useEffect(() => {
if (!editor || config.type !== "screenplay") return;
Expand All @@ -258,7 +295,8 @@ const DocumentEditorPanel = ({
if (event.key === "Backspace") {
// Inside a dual_dialogue_column: let the column node handle it.
for (let d = selection.$anchor.depth; d >= 1; d--) {
if (selection.$anchor.node(d).type.name === DUAL_DIALOGUE_COLUMN) return false;
if (selection.$anchor.node(d).type.name === DUAL_DIALOGUE_COLUMN)
return false;
}
if (nodeSize === 1 && nodePos === 1) {
const tr = view.state.tr.delete(selection.from - 1, selection.from);
Expand All @@ -269,7 +307,10 @@ const DocumentEditorPanel = ({
}

if (event.code === "Space") {
if (currNode === ScreenplayElement.Action && node.textContent.match(/^\b(int|ext)\./gi)) {
if (
currNode === ScreenplayElement.Action &&
node.textContent.match(/^\b(int|ext)\./gi)
) {
setActiveElementRef.current(ScreenplayElement.Scene);
}
return false;
Expand All @@ -290,7 +331,11 @@ const DocumentEditorPanel = ({
if ($anchor.node(d).type.name === DUAL_DIALOGUE_COLUMN) return false;
}

if (currNode === ScreenplayElement.Dialogue && nodePos > 0 && nodePos < nodeSize) {
if (
currNode === ScreenplayElement.Dialogue &&
nodePos > 0 &&
nodePos < nodeSize
) {
const doc = view.state.doc;
const $anchor = selection.$anchor;

Expand All @@ -302,7 +347,11 @@ const DocumentEditorPanel = ({
charName = child.textContent;
break;
}
if (child.attrs.class !== ScreenplayElement.Parenthetical && child.attrs.class !== ScreenplayElement.Dialogue) break;
if (
child.attrs.class !== ScreenplayElement.Parenthetical &&
child.attrs.class !== ScreenplayElement.Dialogue
)
break;
}

const schema = view.state.schema;
Expand All @@ -321,7 +370,9 @@ const DocumentEditorPanel = ({
tr.delete($anchor.pos, $anchor.end(1));
const insertPos = tr.mapping.map($anchor.after(1));
tr.insert(insertPos, [charNode, newDialogue]);
tr.setSelection(TextSelection.create(tr.doc, insertPos + charNode.nodeSize + 1));
tr.setSelection(
TextSelection.create(tr.doc, insertPos + charNode.nodeSize + 1),
);
tr.scrollIntoView();
view.dispatch(tr);
return true;
Expand Down Expand Up @@ -390,7 +441,7 @@ const DocumentEditorPanel = ({

addEventListener("keydown", pressedKeyEvent);
return () => removeEventListener("keydown", pressedKeyEvent);
}, [isVisible, config.type]);
}, [isVisible, config.type, editor]);

// ---- Context menu ----
const onEditorContextMenu = useCallback(
Expand Down Expand Up @@ -464,7 +515,8 @@ const DocumentEditorPanel = ({
setIsScrolled(scrollTop > 0);
};

const focusType = focusedTypeOverride ?? (config.type === "screenplay" ? "screenplay" : "title");
const focusType =
focusedTypeOverride ?? (config.type === "screenplay" ? "screenplay" : "title");

const isLocalAccess = isTauri() || isLocalOnly;
if (!isLocalAccess && (!membership || isLoading)) return <Loading />;
Expand All @@ -477,8 +529,12 @@ const DocumentEditorPanel = ({
onMouseDown={handleContainerMouseDown}
onFocus={() => setFocusedEditorType(focusType)}
>
<div className={`${styles.editor_wrapper} ${isEndlessScroll ? styles.endless_scroll : ""}`}>
<div className={join(styles.editor_shadow, isScrolled ? styles.show_shadow : "")} />
<div
className={`${styles.editor_wrapper} ${isEndlessScroll ? styles.endless_scroll : ""}`}
>
<div
className={join(styles.editor_shadow, isScrolled ? styles.show_shadow : "")}
/>
<div onContextMenu={onEditorContextMenu}>
<EditorContent editor={editor} spellCheck={false} />
</div>
Expand Down
44 changes: 39 additions & 5 deletions components/editor/sidebar/ShelfSidebarItem.module.css
Original file line number Diff line number Diff line change
Expand Up @@ -13,20 +13,54 @@
flex-direction: row;
align-items: center;
gap: 8px;
padding-inline: 20px;
padding-left: 12px;
padding-right: 20px;
padding-block: 10px;
}

.header:hover,
.header_active {
.header:hover {
background-color: var(--editor-sidebar-hover);
}

.chevron {
flex-shrink: 0;
color: var(--secondary-text);
transition: transform 0.2s ease;
}

.chevron_expanded {
transform: rotate(90deg);
}

.type_icon {
flex-shrink: 0;
color: var(--secondary-text);
}

.goto_btn {
display: flex;
align-items: center;
justify-content: center;
padding: 3px;
border-radius: 4px;
border: none;
background: none;
color: var(--secondary-text);
cursor: pointer;
flex-shrink: 0;
opacity: 0;
transition: opacity 0.15s;
}

.header:hover .goto_btn {
opacity: 1;
}

.goto_btn:hover {
background-color: var(--editor-style-bg-hover);
color: var(--primary-text);
}

.title {
flex: 1;
min-width: 0;
Expand Down Expand Up @@ -57,8 +91,8 @@
align-items: center;
gap: 8px;
padding-right: 20px;
padding-left: 20px;
padding-block: 10px;
padding-left: 36px;
padding-block: 8px;
font-size: 12px;
color: var(--secondary-text);
cursor: pointer;
Expand Down
Loading