Compare commits

...

8 commits

Author SHA1 Message Date
Daniel
9d569ad37b
🎨 Support arm64 version in Microsoft Store https://github.com/siyuan-note/siyuan/issues/15836
Signed-off-by: Daniel <845765@qq.com>
2025-09-12 22:30:36 +08:00
Daniel
eca318c4ff
🎨 https://github.com/siyuan-note/siyuan/issues/15833 point 2
Signed-off-by: Daniel <845765@qq.com>
2025-09-12 19:49:09 +08:00
QYLexpired
9f76274747
为面包屑解锁/锁定按钮增加属性 (#15820)
* Update index.ts

* Update onGet.ts

* Update onGet.ts
2025-09-12 19:48:25 +08:00
Vanessa
582f60f574 🐛 https://github.com/siyuan-note/siyuan/issues/15827 2025-09-12 19:42:21 +08:00
Vanessa
a2329077e7 Merge remote-tracking branch 'origin/dev' into dev 2025-09-12 19:25:53 +08:00
Vanessa
a63686cb05 🎨 https://github.com/siyuan-note/siyuan/issues/15833 2025-09-12 19:25:38 +08:00
Daniel
d6e7d0163a
Database kanban view https://github.com/siyuan-note/siyuan/issues/8873
Signed-off-by: Daniel <845765@qq.com>
2025-09-12 19:23:09 +08:00
Daniel
6cc6ef66f9
🎨 Improve database date fields to automatically fill in creation time https://github.com/siyuan-note/siyuan/issues/15828
🎨 Improve database date fields to automatically fill in creation time https://github.com/siyuan-note/siyuan/issues/15828
2025-09-12 19:23:09 +08:00
14 changed files with 284 additions and 30 deletions

View file

@ -0,0 +1,49 @@
<?xml version="1.0" encoding="utf-8"?>
<!--suppress XmlUnusedNamespaceDeclaration -->
<Package
xmlns="http://schemas.microsoft.com/appx/manifest/foundation/windows10"
xmlns:uap="http://schemas.microsoft.com/appx/manifest/uap/windows10"
xmlns:desktop="http://schemas.microsoft.com/appx/manifest/desktop/windows10"
xmlns:rescap="http://schemas.microsoft.com/appx/manifest/foundation/windows10/restrictedcapabilities">
<!-- use single quotes to avoid double quotes escaping in the publisher value -->
<Identity Name="89C2A984.SiYuan"
ProcessorArchitecture="arm64"
Publisher="CN=087C656E-C1D9-42D8-8807-CED45A74FC0F"
Version="3.3.2.0"/>
<Properties>
<DisplayName>SiYuan</DisplayName>
<PublisherDisplayName>云南链滴科技有限公司</PublisherDisplayName>
<Description>Refactor your thinking</Description>
<Logo>assets\StoreLogo.png</Logo>
</Properties>
<Resources>
<Resource Language="en-US"/>
<Resource Language="zh-CN"/>
</Resources>
<Dependencies>
<TargetDeviceFamily Name="Windows.Desktop" MinVersion="10.0.14316.0" MaxVersionTested="10.0.14316.0"/>
</Dependencies>
<Capabilities>
<rescap:Capability Name="runFullTrust"/>
</Capabilities>
<Applications>
<Application Id="SiYuan" Executable="app\SiYuan.exe" EntryPoint="Windows.FullTrustApplication">
<uap:VisualElements
BackgroundColor="transparent"
DisplayName="SiYuan"
Square150x150Logo="assets\Square150x150Logo.png"
Square44x44Logo="assets\Square44x44Logo.png"
Description="Refactor your thinking">
<uap:DefaultTile Wide310x150Logo="assets\Wide310x150Logo.png"/>
</uap:VisualElements>
<Extensions>
<uap:Extension Category="windows.protocol">
<uap:Protocol Name="siyuan"/>
</uap:Extension>
</Extensions>
</Application>
</Applications>
</Package>

View file

@ -18,7 +18,6 @@
"build:export": "webpack --mode production --config webpack.export.js",
"gen:types": "tsc -d",
"start": "NODE_ENV=development electron ./electron/main.js",
"dist-appx": "ELECTRON_MIRROR=https://npmmirror.com/mirrors/electron/ electron-builder --config electron-appx-builder.yml",
"dist": "ELECTRON_MIRROR=https://npmmirror.com/mirrors/electron/ electron-builder --config electron-builder.yml --publish=never",
"dist-arm64": "ELECTRON_MIRROR=https://npmmirror.com/mirrors/electron/ electron-builder --arm64 --config electron-builder-arm64.yml --publish=never",
"dist-darwin": "ELECTRON_MIRROR=https://npmmirror.com/mirrors/electron/ electron-builder --mac --config electron-builder-darwin.yml --publish=never",

View file

@ -58,7 +58,7 @@ export class Breadcrumb {
<span class="protyle-breadcrumb__space"></span>
<button class="protyle-breadcrumb__icon fn__none ariaLabel" aria-label="${updateHotkeyTip(window.siyuan.config.keymap.editor.general.exitFocus.custom)}" data-type="exit-focus">${window.siyuan.languages.exitFocus}</button>
${padHTML}
<button class="block__icon fn__flex-center ariaLabel${window.siyuan.config.readonly ? " fn__none" : ""}" aria-label="${window.siyuan.languages.lockEdit}" data-type="readonly"><svg><use xlink:href="#iconUnlock"></use></svg></button>
<button class="block__icon fn__flex-center ariaLabel${window.siyuan.config.readonly ? " fn__none" : ""}" aria-label="${window.siyuan.languages.lockEdit}" data-type="readonly" data-subtype="unlock"><svg><use xlink:href="#iconUnlock"></use></svg></button>
<button class="block__icon fn__flex-center ariaLabel" data-type="doc" aria-label="${isMac() ? window.siyuan.languages.gutterTip2 : window.siyuan.languages.gutterTip2.replace("", "Shift+")}"><svg><use xlink:href="#iconFile"></use></svg></button>
<button class="block__icon fn__flex-center ariaLabel" data-type="more" aria-label="${window.siyuan.languages.more}"><svg><use xlink:href="#iconMore"></use></svg></button>
<button class="block__icon fn__flex-center fn__none ariaLabel" data-type="context" aria-label="${window.siyuan.languages.context}"><svg><use xlink:href="#iconAlignCenter"></use></svg></button>`;

View file

@ -1802,15 +1802,6 @@ export class Gutter {
transferBlockRef(id);
}
}
window.siyuan.menus.menu.append(new MenuItem({
id: "jumpToParentNext",
label: window.siyuan.languages.jumpToParentNext,
accelerator: window.siyuan.config.keymap.editor.general.jumpToParentNext.custom,
click() {
hideElements(["select"], protyle);
jumpToParent(protyle, nodeElement, "next");
}
}).element);
window.siyuan.menus.menu.append(new MenuItem({
id: "jumpToParentPrev",
label: window.siyuan.languages.jumpToParentPrev,
@ -1820,6 +1811,15 @@ export class Gutter {
jumpToParent(protyle, nodeElement, "previous");
}
}).element);
window.siyuan.menus.menu.append(new MenuItem({
id: "jumpToParentNext",
label: window.siyuan.languages.jumpToParentNext,
accelerator: window.siyuan.config.keymap.editor.general.jumpToParentNext.custom,
click() {
hideElements(["select"], protyle);
jumpToParent(protyle, nodeElement, "next");
}
}).element);
window.siyuan.menus.menu.append(new MenuItem({
id: "jumpToParent",
label: window.siyuan.languages.jumpToParent,

View file

@ -761,6 +761,7 @@ export const refreshAV = (protyle: IProtyle, operation: IOperation) => {
const attrElement = document.querySelector(`.b3-dialog--open[data-key="${Constants.DIALOG_ATTR}"] .custom-attr > [data-av-id="${avID}"]`) as HTMLElement;
if (attrElement) {
// 更新属性面板
attrElement.removeAttribute("data-rendering");
renderAVAttribute(attrElement.parentElement, attrElement.dataset.nodeId, protyle);
} else {
if (operation.action === "insertAttrViewBlock" && operation.context?.ignoreTip !== "true") {

View file

@ -368,8 +368,10 @@ export const disabledProtyle = (protyle: IProtyle) => {
item.setAttribute("draggable", "false");
});
if (protyle.breadcrumb) {
protyle.breadcrumb.element.parentElement.querySelector('[data-type="readonly"] use').setAttribute("xlink:href", "#iconLock");
protyle.breadcrumb.element.parentElement.querySelector('[data-type="readonly"]').setAttribute("aria-label", window.siyuan.config.editor.readOnly ? window.siyuan.languages.tempUnlock : window.siyuan.languages.unlockEdit);
const readonlyButton = protyle.breadcrumb.element.parentElement.querySelector('[data-type="readonly"]');
readonlyButton.querySelector("use").setAttribute("xlink:href", "#iconLock");
readonlyButton.setAttribute("aria-label", window.siyuan.config.editor.readOnly ? window.siyuan.languages.tempUnlock : window.siyuan.languages.unlockEdit);
readonlyButton.setAttribute("data-subtype", "lock");
const undoElement = protyle.breadcrumb.element.parentElement.querySelector('[data-type="undo"]');
if (undoElement && !undoElement.classList.contains("fn__none")) {
undoElement.classList.add("fn__none");
@ -426,8 +428,10 @@ export const enableProtyle = (protyle: IProtyle) => {
}
});
if (protyle.breadcrumb) {
protyle.breadcrumb.element.parentElement.querySelector('[data-type="readonly"] use').setAttribute("xlink:href", "#iconUnlock");
protyle.breadcrumb.element.parentElement.querySelector('[data-type="readonly"]').setAttribute("aria-label", window.siyuan.config.editor.readOnly ? window.siyuan.languages.cancelTempUnlock : window.siyuan.languages.lockEdit);
const readonlyButton = protyle.breadcrumb.element.parentElement.querySelector('[data-type="readonly"]');
readonlyButton.querySelector("use").setAttribute("xlink:href", "#iconUnlock");
readonlyButton.setAttribute("aria-label", window.siyuan.config.editor.readOnly ? window.siyuan.languages.cancelTempUnlock : window.siyuan.languages.lockEdit);
readonlyButton.setAttribute("data-subtype", "unlock");
const undoElement = protyle.breadcrumb.element.parentElement.querySelector('[data-type="undo"]');
if (undoElement && undoElement.classList.contains("fn__none")) {
undoElement.classList.remove("fn__none");

View file

@ -221,6 +221,10 @@ type View struct {
GroupSort int `json:"groupSort"` // 分组排序值,用于手动排序
}
func (view *View) IsGroupView() bool {
return nil != view.Group && "" != view.Group.Field
}
// GetGroupValue 获取分组视图的分组值。
func (view *View) GetGroupValue() string {
if nil == view.GroupVal {
@ -270,7 +274,7 @@ func (view *View) RemoveGroupByID(groupID string) {
// GetGroupKey 获取分组视图的分组字段。
func (view *View) GetGroupKey(attrView *AttributeView) (ret *Key) {
if nil == view.Group || "" == view.Group.Field {
if !view.IsGroupView() {
return
}
@ -295,6 +299,7 @@ type LayoutType string
const (
LayoutTypeTable LayoutType = "table" // 属性视图类型 - 表格
LayoutTypeGallery LayoutType = "gallery" // 属性视图类型 - 卡片
LayoutTypeKanban LayoutType = "kanban" // 属性视图类型 - 看板
)
const (
@ -530,6 +535,15 @@ func SaveAttributeView(av *AttributeView) (err error) {
}
}
// 清理渲染回填值
for _, kv := range av.KeyValues {
for i := len(kv.Values) - 1; i >= 0; i-- {
if kv.Values[i].IsRenderAutoFill {
kv.Values = append(kv.Values[:i], kv.Values[i+1:]...)
}
}
}
var data []byte
if util.UseSingleLineSave {
data, err = gulu.JSON.MarshalJSON(av)

149
kernel/av/layout_kanban.go Normal file
View file

@ -0,0 +1,149 @@
// SiYuan - Refactor your thinking
// Copyright (c) 2020-present, b3log.org
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
package av
import (
"github.com/88250/lute/ast"
)
// LayoutKanban 描述了看板视图的结构。
type LayoutKanban struct {
*BaseLayout
GroupFields []*ViewKanbanField `json:"field"` // 字段
}
func NewLayoutKanban() *LayoutKanban {
return &LayoutKanban{
BaseLayout: &BaseLayout{
Spec: 0,
ID: ast.NewNodeID(),
ShowIcon: true,
},
}
}
// ViewKanbanField 描述了看板字段的结构。
type ViewKanbanField struct {
*BaseField
}
// Kanban 描述了看板视图实例的结构。
type Kanban struct {
*BaseInstance
Fields []*KanbanField `json:"fields"` // 卡片字段
Cards []*KanbanCard `json:"cards"` // 卡片
CardCount int `json:"rowCount"` // 总卡片数
}
// KanbanCard 描述了看板实例卡片的结构。
type KanbanCard struct {
ID string `json:"id"` // 卡片 ID
Values []*KanbanFieldValue `json:"values"` // 卡片字段值
}
// KanbanField 描述了看板实例字段的结构。
type KanbanField struct {
*BaseInstanceField
}
// KanbanFieldValue 描述了卡片字段实例值的结构。
type KanbanFieldValue struct {
*BaseValue
}
func (card *KanbanCard) GetID() string {
return card.ID
}
func (card *KanbanCard) GetBlockValue() (ret *Value) {
for _, v := range card.Values {
if KeyTypeBlock == v.ValueType {
ret = v.Value
break
}
}
return
}
func (card *KanbanCard) GetValues() (ret []*Value) {
ret = []*Value{}
for _, v := range card.Values {
ret = append(ret, v.Value)
}
return
}
func (card *KanbanCard) GetValue(keyID string) (ret *Value) {
for _, value := range card.Values {
if nil != value.Value && keyID == value.Value.KeyID {
ret = value.Value
break
}
}
return
}
func (kanban *Kanban) GetItems() (ret []Item) {
ret = []Item{}
for _, card := range kanban.Cards {
ret = append(ret, card)
}
return
}
func (kanban *Kanban) SetItems(items []Item) {
kanban.Cards = []*KanbanCard{}
for _, item := range items {
kanban.Cards = append(kanban.Cards, item.(*KanbanCard))
}
}
func (kanban *Kanban) CountItems() int {
return len(kanban.Cards)
}
func (kanban *Kanban) GetFields() (ret []Field) {
ret = []Field{}
for _, field := range kanban.Fields {
ret = append(ret, field)
}
return ret
}
func (kanban *Kanban) GetField(id string) (ret Field, fieldIndex int) {
for i, field := range kanban.Fields {
if field.ID == id {
return field, i
}
}
return nil, -1
}
func (kanban *Kanban) GetValue(itemID, keyID string) (ret *Value) {
for _, card := range kanban.Cards {
if card.ID == itemID {
return card.GetValue(keyID)
}
}
return nil
}
func (kanban *Kanban) GetType() LayoutType {
return LayoutTypeKanban
}

View file

@ -57,6 +57,8 @@ type Value struct {
Checkbox *ValueCheckbox `json:"checkbox,omitempty"`
Relation *ValueRelation `json:"relation,omitempty"`
Rollup *ValueRollup `json:"rollup,omitempty"`
IsRenderAutoFill bool `json:"-"` // 标识是否是渲染阶段自动填充的值,保存数据的时候要删掉
}
func (value *Value) SetUpdatedAt(mills int64) {

View file

@ -101,7 +101,7 @@ func GetAttrViewAddingBlockDefaultValues(avID, viewID, groupID, previousBlockID,
return
}
if 1 > len(view.Filters) && nil == view.Group {
if 1 > len(view.Filters) && !view.IsGroupView() {
// 没有过滤条件也没有分组条件时忽略
return
}
@ -128,7 +128,7 @@ func GetAttrViewAddingBlockDefaultValues(avID, viewID, groupID, previousBlockID,
func getAttrViewAddingBlockDefaultValues(attrView *av.AttributeView, view, groupView *av.View, previousItemID, addingItemID string) (ret map[string]*av.Value) {
ret = map[string]*av.Value{}
if 1 > len(view.Filters) && nil == view.Group {
if 1 > len(view.Filters) && !view.IsGroupView() {
// 没有过滤条件也没有分组条件时忽略
return
}
@ -533,7 +533,7 @@ func foldAttrViewGroup(avID, blockID, groupID string, folded bool) (err error) {
return err
}
if nil == view.Group {
if !view.IsGroupView() {
return
}
@ -1032,7 +1032,7 @@ func AppendAttributeViewDetachedBlocksWithValues(avID string, blocksValues [][]*
v.IsDetached = true
v.CreatedAt = now
v.UpdatedAt = now
v.IsRenderAutoFill = false
keyValues.Values = append(keyValues.Values, v)
if av.KeyTypeSelect == v.Type || av.KeyTypeMSelect == v.Type {
@ -1725,7 +1725,7 @@ func GetBlockAttributeViewKeys(nodeID string) (ret []*BlockAttributeViewKeys) {
}
func genAttrViewGroups(view *av.View, attrView *av.AttributeView) {
if nil == view.Group {
if !view.IsGroupView() {
return
}
@ -1969,7 +1969,7 @@ type GroupState struct {
func getAttrViewGroupStates(view *av.View) (groupStates map[string]*GroupState) {
groupStates = map[string]*GroupState{}
if nil == view.Group {
if !view.IsGroupView() {
return
}
@ -2329,6 +2329,7 @@ func updateAttributeViewColRelation(operation *Operation) (err error) {
destVal.Relation = &av.ValueRelation{}
}
destVal.UpdatedAt = now
destVal.IsRenderAutoFill = false
}
destVal.Relation.BlockIDs = append(destVal.Relation.BlockIDs, srcVal.BlockID)
destVal.Relation.BlockIDs = gulu.Str.RemoveDuplicatedElem(destVal.Relation.BlockIDs)
@ -3145,12 +3146,19 @@ func addAttributeViewBlock(now int64, avID, dbBlockID, viewID, groupID, previous
// The database date field supports filling the current time by default https://github.com/siyuan-note/siyuan/issues/10823
for _, keyValues := range attrView.KeyValues {
if av.KeyTypeDate == keyValues.Key.Type && nil != keyValues.Key.Date && keyValues.Key.Date.AutoFillNow {
if nil == keyValues.GetValue(addingItemID) { // 避免覆盖已有值(可能前面已经通过过滤或者分组条件填充了值)
val := keyValues.GetValue(addingItemID)
if nil == val { // 避免覆盖已有值(可能前面已经通过过滤或者分组条件填充了值)
dateVal := &av.Value{
ID: ast.NewNodeID(), KeyID: keyValues.Key.ID, BlockID: addingItemID, Type: av.KeyTypeDate, IsDetached: isDetached, CreatedAt: now, UpdatedAt: now + 1000,
Date: &av.ValueDate{Content: now, IsNotEmpty: true},
}
keyValues.Values = append(keyValues.Values, dateVal)
} else {
if val.IsRenderAutoFill {
val.CreatedAt, val.UpdatedAt = now, now+1000
val.Date.Content, val.Date.IsNotEmpty = now, true
val.IsRenderAutoFill = false
}
}
}
}
@ -3230,11 +3238,13 @@ func fillDefaultValue(attrView *av.AttributeView, view, groupView *av.View, prev
existingVal := keyValues.GetValue(addingItemID)
if nil == existingVal {
newValue.IsRenderAutoFill = false
keyValues.Values = append(keyValues.Values, newValue)
} else {
newValueRaw := newValue.GetValByType(keyValues.Key.Type)
if av.KeyTypeBlock != existingVal.Type || (av.KeyTypeBlock == existingVal.Type && existingVal.IsDetached) {
// 非主键的值直接覆盖,主键的值只覆盖非绑定块
existingVal.IsRenderAutoFill = false
existingVal.SetValByType(keyValues.Key.Type, newValueRaw)
}
}

View file

@ -177,7 +177,7 @@ func renderAttributeViewGroups(viewable av.Viewable, attrView *av.AttributeView,
}
func hideEmptyGroupViews(view *av.View, viewable av.Viewable) {
if nil == view.Group {
if !view.IsGroupView() {
return
}
@ -343,14 +343,14 @@ func sortGroupsBySelectOption(view *av.View, groupKey *av.Key) {
}
func isGroupByDate(view *av.View) bool {
if nil == view.Group {
if !view.IsGroupView() {
return false
}
return av.GroupMethodDateDay == view.Group.Method || av.GroupMethodDateWeek == view.Group.Method || av.GroupMethodDateMonth == view.Group.Method || av.GroupMethodDateYear == view.Group.Method || av.GroupMethodDateRelative == view.Group.Method
}
func isGroupByTemplate(attrView *av.AttributeView, view *av.View) bool {
if nil == view.Group {
if !view.IsGroupView() {
return false
}

View file

@ -234,6 +234,22 @@ func GetBlockSiblingID(id string) (parent, previous, next string) {
next = flb.ID
}
}
if "" == previous && "" == next && nil != current {
parent = current.ID
if nil != current.Previous {
previous = current.Previous.ID
if flb := treenode.FirstChildBlock(current.Previous); nil != flb {
previous = flb.ID
}
}
if nil != current.Next {
next = current.Next.ID
if flb := treenode.FirstChildBlock(current.Next); nil != flb {
next = flb.ID
}
}
}
return
}

View file

@ -624,6 +624,7 @@ func fillAttributeViewKeyValues(attrView *av.AttributeView, collection av.Collec
}
}
if !exist {
val.IsRenderAutoFill = true
keyValues.Values = append(keyValues.Values, val)
}
}

View file

@ -14,9 +14,9 @@ if errorlevel 1 (
cd ..
echo 'Cleaning Builds'
del /S /Q /F app\build 1>nul
del /S /Q /F app\kernel 1>nul
del /S /Q /F app\kernel-arm64 1>nul
rmdir /S /Q app\build 1>nul
rmdir /S /Q app\kernel 1>nul
rmdir /S /Q app\kernel-arm64 1>nul
echo 'Building Kernel'
@REM the C compiler "gcc" is necessary https://sourceforge.net/projects/mingw-w64/files/mingw-w64/
@ -68,4 +68,13 @@ cd ..
echo 'Building Appx'
echo 'Building Appx should be disabled if you do not need it. Not configured correctly will lead to build failures'
cd . > app\build\win-unpacked\resources\ms-store
electron-windows-store --input-directory app\build\win-unpacked --output-directory app\build\ --package-version 1.0.0.0 --package-name SiYuan --manifest app\appx\AppxManifest.xml --assets app\appx\assets\ --make-pri true
call electron-windows-store --input-directory app\build\win-unpacked --output-directory app\build\ --package-version 1.0.0.0 --package-name SiYuan --manifest app\appx\AppxManifest.xml --assets app\appx\assets\ --make-pri true
rmdir /S /Q app\build\pre-appx 1>nul
echo 'Building Appx arm64'
echo 'Building Appx arm64 should be disabled if you do not need it. Not configured correctly will lead to build failures'
cd . > app\build\win-arm64-unpacked\resources\ms-store
call electron-windows-store --input-directory app\build\win-arm64-unpacked --output-directory app\build\ --package-version 1.0.0.0 --package-name SiYuan-arm64 --manifest app\appx\AppxManifest-arm64.xml --assets app\appx\assets\ --make-pri true
rmdir /S /Q app\build\pre-appx 1>nul