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
66 changes: 60 additions & 6 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ on:
workflow_dispatch:
inputs:
tag:
description: 'Release tag to publish (for example, v0.1.2)'
description: 'Release tag to publish from the selected branch/tag (must match package.json version)'
required: true
type: string

Expand All @@ -29,18 +29,22 @@ jobs:
- os: macos-latest
artifact: macos
output: Muse
target: darwin-arm64
- os: windows-latest
artifact: windows
output: Muse.exe
target: windows-amd64
- os: ubuntu-latest
artifact: linux
output: Muse-linux
target: linux-amd64

steps:
- name: Check out source
uses: actions/checkout@v7
with:
ref: ${{ github.ref_type == 'tag' && github.ref_name || inputs.tag }}
# Build the triggering commit so manual runs include fixes from the selected ref.
ref: ${{ github.sha }}

- name: Set up pnpm
uses: pnpm/action-setup@v6
Expand Down Expand Up @@ -73,6 +77,9 @@ jobs:
- name: Build frontend
run: pnpm run build:web

- name: Typecheck frontend
run: pnpm run typecheck

- name: Lint (gofmt)
working-directory: native
shell: bash
Expand All @@ -82,11 +89,15 @@ jobs:
working-directory: native
run: go vet ./...

- name: Build binary
- name: Test native services
working-directory: native
run: go test ./...

- name: Build binary
env:
CGO_ENABLED: '0'
run: go build -ldflags="-s -w" -o ${{ matrix.output }} .
GOOS: ${{ matrix.artifact == 'macos' && 'darwin' || matrix.artifact }}
GOARCH: ${{ matrix.artifact == 'macos' && 'arm64' || 'amd64' }}
run: node scripts/build-native.mjs ${{ matrix.output }}

- name: Compress with UPX (Linux/Windows)
if: matrix.os != 'macos-latest'
Expand All @@ -104,14 +115,56 @@ jobs:
echo "UPX not available, skipping compression"
fi

- name: Upload binary
- name: Build macOS DMG
if: matrix.os == 'macos-latest'
shell: bash
env:
RELEASE_TAG: ${{ github.ref_type == 'tag' && github.ref_name || inputs.tag }}
run: |
# 安装 create-dmg(比裸 hdiutil 好看得多)
brew install create-dmg || true
chmod +x scripts/build-macos-app.sh
scripts/build-macos-app.sh "native/${{ matrix.output }}" "resources/icon.icns" "${RELEASE_TAG#v}"

- name: Sign automatic update
shell: bash
env:
MYGO_UPDATER_PRIVATE_KEY: ${{ secrets.MYGO_UPDATER_PRIVATE_KEY }}
run: |
if [ "$RUNNER_OS" = "macOS" ]; then
node scripts/package-update.mjs dist/stage Muse.app "${{ matrix.target }}"
else
node scripts/package-update.mjs native "${{ matrix.output }}" "${{ matrix.target }}"
fi

- name: Upload signed update
uses: actions/upload-artifact@v7
with:
name: ${{ matrix.artifact }}-update
path: |
dist/Muse-*.tar.gz
dist/update-*.json
if-no-files-found: error
retention-days: 7

- name: Upload binary (Linux/Windows)
if: matrix.os != 'macos-latest'
uses: actions/upload-artifact@v7
with:
name: ${{ matrix.artifact }}-release
path: native/${{ matrix.output }}
if-no-files-found: error
retention-days: 7

- name: Upload DMG (macOS)
if: matrix.os == 'macos-latest'
uses: actions/upload-artifact@v7
with:
name: macos-release
path: dist/Muse-*-macOS.dmg
if-no-files-found: error
retention-days: 7

publish:
name: Publish GitHub Release
needs: build
Expand All @@ -129,4 +182,5 @@ jobs:
tag_name: ${{ github.ref_type == 'tag' && github.ref_name || inputs.tag }}
name: Muse ${{ github.ref_type == 'tag' && github.ref_name || inputs.tag }}
generate_release_notes: true
make_latest: true
files: release-assets/*
10 changes: 10 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
# Changelog

## 0.2.1

- Add signed automatic updates: daily background checks, release notes, download progress, install and restart, and optional automatic downloads. / 新增签名自动更新:每天后台检查,查看更新说明、下载进度、安装并重启,可选择自动下载安装。
- Add Magpie local gateway preset and discover models through `/v1/models`, preserving custom model IDs on refresh. / 新增 Magpie 本地网关预设,自动获取模型,刷新时保留自定义 ID。
- Wrap long document titles and tokens, widen the responsive editor, and keep tables within the page. / 修复长标题截断,优化正文宽度、长链接与表格换行。
- Load the Muse logo explicitly in the Dock. / Dock 显式加载 Muse Logo。

Install 0.2.1 manually once if upgrading from 0.2.0; future versions can update in the app. macOS builds support Apple Silicon; Windows and Linux builds support x64. / 从 0.2.0 升级需手动安装一次 0.2.1,后续可在应用内更新。macOS 支持 Apple Silicon,Windows 与 Linux 支持 x64。
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,8 @@
* **AI tool calling (agent)** — describe a document problem in natural language and the AI edits the document directly through tools (read / replace selection / insert at caret / append to end / replace all); ⌘Z undoes it. Selecting text reveals a quick action bar: Polish / Expand / Summarize / Translate
* **Find & replace** — ⌘F to open, ⌘G / ⇧⌘G for prev/next, highlighted matches
* **Word count** — live stats in the status bar
* **Automatic updates (0.2.1+)** — signed daily update checks, in-app download/install and restart; Settings > App Updates controls automatic checks/downloads. Install 0.2.1 manually once when upgrading from 0.2.0.

* **Settings panel** — font size / line height, applied instantly and persisted
* **File management** — open / save / save as / new via native menus and shortcuts (⌘N / ⌘O / ⌘S / ⌘⇧S)
* Drag & drop to open files
Expand Down
2 changes: 2 additions & 0 deletions README.zh-CN.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,8 @@

一个**所见即所得(WYSIWYG)的 Markdown 桌面编辑器**:打字时 `#` 立刻变成标题、`**加粗**` 即时生效、代码块用 **Shiki** 实时语法高亮。基于 **Milkdown 7**(ProseMirror)构建,未来计划接入 **AI 流式输出**(Muse 的真正主线)。

* **自动更新(0.2.1 起)** — 每天后台检查、签名验证、应用内下载安装并重启;设置 → 应用更新可控制自动检查和自动下载。从 0.2.0 升级需手动安装一次 0.2.1。

## ✨ 功能特性

* **所见即所得** — Milkdown 7 内核,边打字边渲染 Markdown(标题 / 加粗 / 列表 / 引用 / 表格 / 任务列表等)
Expand Down
4 changes: 2 additions & 2 deletions app/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@muse/app",
"version": "0.1.2",
"version": "0.2.1",
"private": true,
"description": "Muse · Typora 式所见即所得 Markdown 编辑器(Shiki 高亮 + Milkdown 7)",
"license": "MIT",
Expand Down Expand Up @@ -49,4 +49,4 @@
"vue": "^3.5.0",
"vue-tsc": "^3.0.0"
}
}
}
12 changes: 5 additions & 7 deletions app/src/App.vue
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { XProvider } from '@antdv-next/x'
import type { XProviderProps } from '@antdv-next/x'
import MilkdownEditor from './editor/MilkdownEditor.vue'
import TitleBar from './components/TitleBar.vue'
import DocumentTitle from './components/DocumentTitle.vue'
import SidePanel from './components/SidePanel.vue'
import OutlinePanel from './components/OutlinePanel.vue'
import ChatPanel from './chat/ChatPanel.vue'
Expand Down Expand Up @@ -283,7 +284,7 @@ function onTreeRenamed(e: Event): void {

const showSettings = ref(false)
const recentFiles = ref<string[]>([])
const titleInputRef = ref<HTMLInputElement | null>(null)
const titleInputRef = ref<InstanceType<typeof DocumentTitle> | null>(null)
function focusTitleInput(): void {
titleInputRef.value?.focus()
titleInputRef.value?.select()
Expand Down Expand Up @@ -754,15 +755,12 @@ const getDocContext = (): string => fullContent.value
:class="{ 'focus-mode': viewMode.focus }"
@scroll.passive="onEditorScroll"
>
<div class="mx-auto max-w-[46rem] px-12 pt-8 pb-32">
<input
<div class="document-page">
<DocumentTitle
v-show="!viewMode.source"
ref="titleInputRef"
v-model="titleText"
placeholder="无标题"
class="title-input w-full bg-transparent outline-none border-none text-[30px] font-bold leading-tight placeholder:text-[var(--fg-soft)] placeholder:opacity-60 mb-4 text-left"
spellcheck="false"
@keydown.enter="onTitleEnter"
@enter="onTitleEnter"
/>
<div v-show="!viewMode.source">
<MilkdownEditor v-model="doc" />
Expand Down
2 changes: 1 addition & 1 deletion app/src/chat/useChat.ts
Original file line number Diff line number Diff line change
Expand Up @@ -139,7 +139,7 @@ export function useChat() {
(p) => {
setChatConnection(p ? { baseUrl: p.baseUrl ?? '', apiKey: p.apiKey } : null)
},
{ immediate: true }
{ immediate: true, deep: true }
)

// ===== Provider(真实请求走主进程 IPC;无供应商时回退本地 mock)=====
Expand Down
3 changes: 3 additions & 0 deletions app/src/components.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,9 +26,11 @@ declare module 'vue' {
ARow: typeof import('antdv-next')['Row']
ASegmented: typeof import('antdv-next')['Segmented']
ASlider: typeof import('antdv-next')['Slider']
ASwitch: typeof import('antdv-next')['Switch']
ATag: typeof import('antdv-next')['Tag']
ATooltip: typeof import('antdv-next')['Tooltip']
ATypographyText: typeof import('antdv-next')['TypographyText']
DocumentTitle: typeof import('./components/DocumentTitle.vue')['default']
EditorContextMenu: typeof import('./components/EditorContextMenu.vue')['default']
EntryScreen: typeof import('./components/EntryScreen.vue')['default']
FileTreePanel: typeof import('./components/FileTreePanel.vue')['default']
Expand All @@ -42,5 +44,6 @@ declare module 'vue' {
SourceEditor: typeof import('./components/SourceEditor.vue')['default']
StatusBar: typeof import('./components/StatusBar.vue')['default']
TitleBar: typeof import('./components/TitleBar.vue')['default']
UpdateSettings: typeof import('./components/UpdateSettings.vue')['default']
}
}
78 changes: 78 additions & 0 deletions app/src/components/DocumentTitle.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
<script setup lang="ts">
import { onMounted, onUnmounted, ref, watch } from 'vue'

const title = defineModel<string>({ required: true })
const emit = defineEmits<{ enter: [event: KeyboardEvent] }>()
const area = ref<HTMLTextAreaElement | null>(null)
let observer: ResizeObserver | undefined
let lastWidth = 0
let frame = 0

function resize(): void {
if (!area.value) return
area.value.style.height = '0px'
area.value.style.height = `${area.value.scrollHeight}px`
}

function onInput(event: Event): void {
const element = event.target as HTMLTextAreaElement
// The title stays one Markdown heading even when pasted text contains newlines.
title.value = element.value.replace(/[\r\n]+/g, ' ')
element.value = title.value
resize()
}

watch(title, resize, { flush: 'post' })
onMounted(() => {
resize()
observer = new ResizeObserver(([entry]) => {
if (entry && entry.contentRect.width !== lastWidth) {
lastWidth = entry.contentRect.width
cancelAnimationFrame(frame)
frame = requestAnimationFrame(resize)
}
})
if (area.value) observer.observe(area.value)
})
onUnmounted(() => {
observer?.disconnect()
cancelAnimationFrame(frame)
})
defineExpose({ focus: () => area.value?.focus(), select: () => area.value?.select() })
</script>

<template>
<textarea
ref="area"
:value="title"
class="title-input"
placeholder="无标题"
aria-label="文档标题"
rows="1"
spellcheck="false"
@input="onInput"
@keydown.enter="emit('enter', $event)"
/>
</template>

<style scoped>
.title-input {
display: block;
width: 100%;
min-height: 38px;
margin-bottom: 16px;
padding: 0;
resize: none;
overflow: hidden;
border: none;
outline: none;
background: transparent;
color: var(--fg);
font: 700 30px/1.3 var(--font);
overflow-wrap: anywhere;
}
.title-input::placeholder {
color: var(--fg-soft);
opacity: 0.6;
}
</style>
Loading
Loading