mirror of
https://github.com/siyuan-note/siyuan.git
synced 2025-12-17 07:00:12 +01:00
Merge remote-tracking branch 'origin/dev' into dev
This commit is contained in:
commit
8ca9f119d0
15 changed files with 474 additions and 410 deletions
|
|
@ -553,7 +553,7 @@
|
|||
"embedBlockBreadcrumbTip": "بعد التمكين، تم عرض شريط التنقل في الكتل المضمنة؛ تم مجاهلة هذا الخيار عند شريط التنقل للكتل المضمنة في الكتل الخارقة ولا يظهر أبداً",
|
||||
"appearanceMode": "وضع المظهر",
|
||||
"editReadonly": "وضع القراءة فقط",
|
||||
"editReadonlyTip": "بعد التمكين، سيقوم المحرر بتحميل المستند في وضع القراءة فقط",
|
||||
"editReadonlyTip": "عند التمكين، سيكون المستند للقراءة فقط افتراضيًا",
|
||||
"generateConflictDoc": "توليد مستندات التعارض عند ظهور تضاربات المزامنة",
|
||||
"generateConflictDocTip": "بعد التمكين، سيتم إنشاء مستندات التعارض عند حدوث تضاربات المزامنة، بحيث يمكن فتحها وعرضها مباشرة. سيتم تسجيل مستندات التعارض في [تاريخ البيانات] سواء تمكين هذا الخيار أو عدم تمكينه",
|
||||
"deleteOpConfirm": "⚠️ تأكيد عملية حذف",
|
||||
|
|
|
|||
|
|
@ -553,7 +553,7 @@
|
|||
"embedBlockBreadcrumbTip": "Nach der Aktivierung zeigen eingebettete Blöcke Breadcrumbs an, eingebettete Blöcke in Superblöcken ignorieren diese Option und zeigen niemals Breadcrumbs.",
|
||||
"appearanceMode": "Darstellungsmodus",
|
||||
"editReadonly": "Schreibgeschützter Modus",
|
||||
"editReadonlyTip": "Nach der Aktivierung wird das Dokument im schreibgeschützten Modus geladen.",
|
||||
"editReadonlyTip": "Nach der Aktivierung ist das Dokument standardmäßig schreibgeschützt.",
|
||||
"generateConflictDoc": "Konfliktdokument generieren, wenn Synchronisationskonflikte auftreten.",
|
||||
"generateConflictDocTip": "Nach der Aktivierung wird ein Konfliktdokument generiert, wenn ein Synchronisationskonflikt auftritt, sodass es direkt geöffnet und angezeigt werden kann. Egal ob aktiviert oder nicht, die [Datenhistorie] wird das Konfliktdokument aufzeichnen.",
|
||||
"deleteOpConfirm": "⚠️ Bestätigung der Löschoperation",
|
||||
|
|
|
|||
|
|
@ -553,7 +553,7 @@
|
|||
"embedBlockBreadcrumbTip": "When enabled, embed blocks will display breadcrumbs, embed blocks in super blocks ignore this option and never show breadcrumbs",
|
||||
"appearanceMode": "Appearance Mode",
|
||||
"editReadonly": "Read-only mode",
|
||||
"editReadonlyTip": "When enabled, the editor will load the document in read-only mode",
|
||||
"editReadonlyTip": "When enabled, the document will be read-only by default",
|
||||
"generateConflictDoc": "Generate conflict documentation when syncing conflicts",
|
||||
"generateConflictDocTip": "When enabled, a conflict document will be generated when a synchronization conflict occurs, so that it can be opened and viewed directly. Whether enabled or not, the [Data History] will record the conflict document",
|
||||
"deleteOpConfirm": "⚠️ Delete operation confirmation",
|
||||
|
|
|
|||
|
|
@ -553,7 +553,7 @@
|
|||
"embedBlockBreadcrumbTip": "Después de habilitar los bloques incrustados, se mostrarán migas de pan, incrustar bloques en superbloques ignora esta opción y nunca muestra migas de pan",
|
||||
"appearanceMode": "Modo de apariencia",
|
||||
"editReadonly": "Modo de solo lectura",
|
||||
"editReadonlyTip": "Después de habilitarlo, el editor cargará el documento en modo de solo lectura",
|
||||
"editReadonlyTip": "Después de habilitarlo, el documento será de solo lectura por defecto",
|
||||
"generateConflictDoc": "Generar documentación de conflicto al sincronizar conflictos",
|
||||
"generateConflictDocTip": "Después de habilitarlo, se generará un documento de conflicto cuando ocurra un conflicto de sincronización, para que pueda abrirse y verse directamente. Ya sea que esté habilitado o no, el [Historial de datos] registrará el documento de conflicto",
|
||||
"deleteOpConfirm": "⚠️ Confirmación de operación de eliminación",
|
||||
|
|
|
|||
|
|
@ -553,7 +553,7 @@
|
|||
"embedBlockBreadcrumbTip": "Après avoir activé l'intégration, les blocs afficheront le fil d'Ariane, intégrer des blocs dans des super blocs ignorent cette option et n'affichent jamais le fil d'Ariane",
|
||||
"appearanceMode": "Mode d'apparence",
|
||||
"editReadonly": "Mode lecture seule",
|
||||
"editReadonlyTip": "Lorsqu'il est activé, l'éditeur charge le document en mode lecture seule",
|
||||
"editReadonlyTip": "Lorsqu'il est activé, le document sera en lecture seule par défaut",
|
||||
"generateConflictDoc": "Générer une documentation sur les conflits lors de la synchronisation des conflits",
|
||||
"generateConflictDocTip": "Lorsqu'il est activé, un document de conflit sera généré lorsqu'un conflit de synchronisation se produit, afin qu'il puisse être ouvert et visualisé directement. Qu'il soit activé ou non, l'historique des données enregistrera le document de conflit",
|
||||
"deleteOpConfirm": "⚠️ Supprimer la confirmation de l'opération",
|
||||
|
|
|
|||
|
|
@ -553,7 +553,7 @@
|
|||
"embedBlockBreadcrumbTip": "לאחר הפעולה, בלוקים מוטמעים יראו נתיב, בלוקים מוטמעים בבלוקים עליוניים מתעלמים מהאפשרות הזו ולא יראו נתיבים",
|
||||
"appearanceMode": "מצב מראה",
|
||||
"editReadonly": "מצב קריאה בלבד",
|
||||
"editReadonlyTip": "לאחר ההפעלה, העורך יטען את המסמך במצב קריאה בלבד",
|
||||
"editReadonlyTip": "כאשר מופעל, המסמך יהיה לקריאה בלבד כברירת מחדל",
|
||||
"generateConflictDoc": "צור תיעוד שהוקסם כאשר נוצרו סכסוכים",
|
||||
"generateConflictDocTip": "לאחר ההפעלה, תיעוד סכסוך ייווצר כאשר מתהווה סכסוך סנכרון, כך שהוא יכול להיפתח ולהיות נצפה ישירות. בין אם מופעל ובין אם לא, היסטוריית הנתונים תקלוט את תיעוד הסכסוך",
|
||||
"deleteOpConfirm": "⚠️ אישור פעולה מחיקה",
|
||||
|
|
|
|||
|
|
@ -553,7 +553,7 @@
|
|||
"embedBlockBreadcrumbTip": "Dopo l'abilitazione, i blocchi incorporati mostreranno i breadcrumb. I blocchi incorporati nei super blocchi ignorano questa opzione e non mostreranno mai breadcrumb",
|
||||
"appearanceMode": "Modalità aspetto",
|
||||
"editReadonly": "Modalità di sola lettura",
|
||||
"editReadonlyTip": "Dopo l'abilitazione, l'editor caricherà il documento in modalità di sola lettura",
|
||||
"editReadonlyTip": "Quando abilitato, il documento sarà in sola lettura per impostazione predefinita",
|
||||
"generateConflictDoc": "Genera documento di conflitto quando si verificano conflitti di sincronizzazione",
|
||||
"generateConflictDocTip": "Dopo l'abilitazione, verrà generato un documento di conflitto quando si verifica un conflitto di sincronizzazione, in modo che possa essere aperto e visualizzato direttamente. Sia che sia abilitato o meno, la [Cronologia dati] registrerà il documento di conflitto",
|
||||
"deleteOpConfirm": "⚠️ Conferma operazione di eliminazione",
|
||||
|
|
|
|||
|
|
@ -553,7 +553,7 @@
|
|||
"embedBlockBreadcrumbTip": "埋め込みブロックにパンくずリストを表示します<br>この設定にかかわらずスーパーブロック内の埋め込みブロックは常にパンくずリストが表示されません",
|
||||
"appearanceMode": "表示モード",
|
||||
"editReadonly": "読み取り専用モード",
|
||||
"editReadonlyTip": "エディタが読み取り専用モードでドキュメントを読み込みます",
|
||||
"editReadonlyTip": "有効にすると、ドキュメントはデフォルトで読み取り専用になります",
|
||||
"generateConflictDoc": "同期の競合時に競合ドキュメントを生成する",
|
||||
"generateConflictDocTip": "同期の競合が発生した場合は競合ドキュメントが生成され、直接開いて表示できます。この設定に関わらず [データ履歴] には競合ドキュメントが記録されます",
|
||||
"deleteOpConfirm": "⚠️ 削除操作の確認",
|
||||
|
|
|
|||
|
|
@ -553,7 +553,7 @@
|
|||
"embedBlockBreadcrumbTip": "Po włączeniu wbudowane bloki będą wyświetlać okruszki, wbudowane bloki w super blokach ignorują tę opcję i nigdy nie pokazują okruszków",
|
||||
"appearanceMode": "Tryb wyglądu",
|
||||
"editReadonly": "Tryb tylko do odczytu",
|
||||
"editReadonlyTip": "Po włączeniu edytor załaduje dokument w trybie tylko do odczytu",
|
||||
"editReadonlyTip": "Po włączeniu dokument będzie domyślnie tylko do odczytu",
|
||||
"generateConflictDoc": "Generuj dokument konfliktowy podczas synchronizacji konfliktów",
|
||||
"generateConflictDocTip": "Po włączeniu, dokument konfliktowy zostanie wygenerowany, gdy wystąpi konflikt synchronizacji, aby można go było otworzyć i przeglądać bezpośrednio. Niezależnie od tego, czy włączone, [Historia danych] zarejestruje dokument konfliktowy",
|
||||
"deleteOpConfirm": "⚠️ Potwierdzenie operacji usunięcia",
|
||||
|
|
|
|||
|
|
@ -553,7 +553,7 @@
|
|||
"embedBlockBreadcrumbTip": "Quando ativado, blocos incorporados exibirão navegação estrutural, blocos incorporados em super blocos ignoram esta opção e nunca mostram navegação estrutural",
|
||||
"appearanceMode": "Modo de Aparência",
|
||||
"editReadonly": "Modo somente leitura",
|
||||
"editReadonlyTip": "Quando ativado, o editor carregará o documento em modo somente leitura",
|
||||
"editReadonlyTip": "Quando ativado, o documento ficará somente leitura por padrão",
|
||||
"generateConflictDoc": "Gerar documentação de conflito quando houver conflitos de sincronização",
|
||||
"generateConflictDocTip": "Quando ativado, um documento de conflito será gerado quando ocorrer um conflito de sincronização, para que possa ser aberto e visualizado diretamente. Independentemente de estar ativado ou não, o [Histórico de Dados] registrará o documento de conflito",
|
||||
"deleteOpConfirm": "⚠️ Confirmação de operação de exclusão",
|
||||
|
|
|
|||
|
|
@ -553,7 +553,7 @@
|
|||
"embedBlockBreadcrumbTip": "После включения вложенные блоки будут отображать крошки, вложенные блоки в супер блоках игнорируют эту опцию и никогда не показывают крошки",
|
||||
"appearanceMode": "Режим внешнего вида",
|
||||
"editReadonly": "Режим только для чтения",
|
||||
"editReadonlyTip": "После включения редактор загрузит документ в режиме только для чтения",
|
||||
"editReadonlyTip": "После включения документ будет по умолчанию только для чтения",
|
||||
"generateConflictDoc": "Создавать документацию конфликтов при возникновении конфликтов синхронизации",
|
||||
"generateConflictDocTip": "После включения при возникновении конфликта синхронизации будет генерироваться документ конфликта, чтобы его можно было открывать и просматривать напрямую. Независимо от включения или нет, [История данных] зарегистрирует документ конфликта",
|
||||
"deleteOpConfirm": "⚠️ Подтверждение операции удаления",
|
||||
|
|
|
|||
|
|
@ -553,7 +553,7 @@
|
|||
"embedBlockBreadcrumbTip": "啟用後嵌入塊將顯示導覽路徑,在超級塊中的嵌入塊忽略該選項,始終不顯示導覽路徑",
|
||||
"appearanceMode": "外觀模式",
|
||||
"editReadonly": "只讀模式",
|
||||
"editReadonlyTip": "啟用後編輯器將以只讀模式載入文檔",
|
||||
"editReadonlyTip": "啟用後文檔將預設鎖定編輯",
|
||||
"generateConflictDoc": "同步衝突時生成衝突文檔",
|
||||
"generateConflictDocTip": "啟用後當同步發生衝突時會生成衝突文檔,以便直接打開查看。無論是否啟用,[資料歷史] 都會記錄衝突文檔",
|
||||
"deleteOpConfirm": "⚠️ Delete operation confirmation",
|
||||
|
|
|
|||
|
|
@ -553,7 +553,7 @@
|
|||
"embedBlockBreadcrumbTip": "启用后嵌入块将显示面包屑,在超级块中的嵌入块忽略该选项,始终不显示面包屑",
|
||||
"appearanceMode": "外观模式",
|
||||
"editReadonly": "只读模式",
|
||||
"editReadonlyTip": "启用后编辑器将以只读模式载入文档",
|
||||
"editReadonlyTip": "启用后文档将默认锁定编辑",
|
||||
"generateConflictDoc": "同步冲突时生成冲突文档",
|
||||
"generateConflictDocTip": "启用后当同步发生冲突时会生成冲突文档,以便直接打开查看。无论是否启用,[数据历史] 都会记录冲突文档",
|
||||
"deleteOpConfirm": "⚠️ 删除操作确认",
|
||||
|
|
|
|||
|
|
@ -32,7 +32,6 @@ import (
|
|||
"github.com/88250/lute/ast"
|
||||
"github.com/88250/lute/parse"
|
||||
"github.com/jinzhu/copier"
|
||||
"github.com/siyuan-note/dejavu/entity"
|
||||
"github.com/siyuan-note/filelock"
|
||||
"github.com/siyuan-note/logging"
|
||||
"github.com/siyuan-note/siyuan/kernel/av"
|
||||
|
|
@ -1553,352 +1552,6 @@ func GetBlockAttributeViewKeys(blockID string) (ret []*BlockAttributeViewKeys) {
|
|||
return
|
||||
}
|
||||
|
||||
func RenderRepoSnapshotAttributeView(indexID, avID string) (viewable av.Viewable, attrView *av.AttributeView, err error) {
|
||||
repo, err := newRepository()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
index, err := repo.GetIndex(indexID)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
files, err := repo.GetFiles(index)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
var avFile *entity.File
|
||||
for _, f := range files {
|
||||
if "/storage/av/"+avID+".json" == f.Path {
|
||||
avFile = f
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if nil == avFile {
|
||||
attrView = av.NewAttributeView(avID)
|
||||
} else {
|
||||
data, readErr := repo.OpenFile(avFile)
|
||||
if nil != readErr {
|
||||
logging.LogErrorf("read attribute view [%s] failed: %s", avID, readErr)
|
||||
return
|
||||
}
|
||||
|
||||
attrView = &av.AttributeView{}
|
||||
if err = gulu.JSON.UnmarshalJSON(data, attrView); err != nil {
|
||||
logging.LogErrorf("unmarshal attribute view [%s] failed: %s", avID, err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
viewable, err = renderAttributeView(attrView, "", "", "", 1, -1, nil)
|
||||
return
|
||||
}
|
||||
|
||||
func RenderHistoryAttributeView(avID, created string) (viewable av.Viewable, attrView *av.AttributeView, err error) {
|
||||
createdUnix, parseErr := strconv.ParseInt(created, 10, 64)
|
||||
if nil != parseErr {
|
||||
logging.LogErrorf("parse created [%s] failed: %s", created, parseErr)
|
||||
return
|
||||
}
|
||||
|
||||
dirPrefix := time.Unix(createdUnix, 0).Format("2006-01-02-150405")
|
||||
globPath := filepath.Join(util.HistoryDir, dirPrefix+"*")
|
||||
matches, err := filepath.Glob(globPath)
|
||||
if err != nil {
|
||||
logging.LogErrorf("glob [%s] failed: %s", globPath, err)
|
||||
return
|
||||
}
|
||||
if 1 > len(matches) {
|
||||
return
|
||||
}
|
||||
|
||||
historyDir := matches[0]
|
||||
avJSONPath := filepath.Join(historyDir, "storage", "av", avID+".json")
|
||||
if !gulu.File.IsExist(avJSONPath) {
|
||||
avJSONPath = filepath.Join(util.DataDir, "storage", "av", avID+".json")
|
||||
}
|
||||
if !gulu.File.IsExist(avJSONPath) {
|
||||
attrView = av.NewAttributeView(avID)
|
||||
} else {
|
||||
data, readErr := os.ReadFile(avJSONPath)
|
||||
if nil != readErr {
|
||||
logging.LogErrorf("read attribute view [%s] failed: %s", avID, readErr)
|
||||
return
|
||||
}
|
||||
|
||||
attrView = &av.AttributeView{}
|
||||
if err = gulu.JSON.UnmarshalJSON(data, attrView); err != nil {
|
||||
logging.LogErrorf("unmarshal attribute view [%s] failed: %s", avID, err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
viewable, err = renderAttributeView(attrView, "", "", "", 1, -1, nil)
|
||||
return
|
||||
}
|
||||
|
||||
func RenderAttributeView(blockID, avID, viewID, query string, page, pageSize int, groupPaging map[string]interface{}) (viewable av.Viewable, attrView *av.AttributeView, err error) {
|
||||
waitForSyncingStorages()
|
||||
|
||||
if avJSONPath := av.GetAttributeViewDataPath(avID); !filelock.IsExist(avJSONPath) {
|
||||
attrView = av.NewAttributeView(avID)
|
||||
if err = av.SaveAttributeView(attrView); err != nil {
|
||||
logging.LogErrorf("save attribute view [%s] failed: %s", avID, err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
attrView, err = av.ParseAttributeView(avID)
|
||||
if err != nil {
|
||||
logging.LogErrorf("parse attribute view [%s] failed: %s", avID, err)
|
||||
return
|
||||
}
|
||||
|
||||
viewable, err = renderAttributeView(attrView, blockID, viewID, query, page, pageSize, groupPaging)
|
||||
return
|
||||
}
|
||||
|
||||
const (
|
||||
groupValueDefault = "_@default@_" // 默认分组值(值为空的默认分组)
|
||||
groupValueNotInRange = "_@notInRange@_" // 不再范围内的分组值(只有数字类型的分组才可能是该值)
|
||||
groupValueLast30Days, groupValueLast7Days = "_@last30Days@_", "_@last7Days@_"
|
||||
groupValueYesterday, groupValueToday, groupValueTomorrow = "_@yesterday@_", "_@today@_", "_@tomorrow@_"
|
||||
groupValueNext7Days, groupValueNext30Days = "_@next7Days@_", "_@next30Days@_"
|
||||
)
|
||||
|
||||
func renderAttributeView(attrView *av.AttributeView, blockID, viewID, query string, page, pageSize int, groupPaging map[string]interface{}) (viewable av.Viewable, err error) {
|
||||
if 1 > len(attrView.Views) {
|
||||
view, _, _ := av.NewTableViewWithBlockKey(ast.NewNodeID())
|
||||
attrView.Views = append(attrView.Views, view)
|
||||
attrView.ViewID = view.ID
|
||||
if err = av.SaveAttributeView(attrView); err != nil {
|
||||
logging.LogErrorf("save attribute view [%s] failed: %s", attrView.ID, err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
if "" == viewID && "" != blockID {
|
||||
node, _, getErr := getNodeByBlockID(nil, blockID)
|
||||
if nil != getErr {
|
||||
logging.LogWarnf("get node by block ID [%s] failed: %s", blockID, getErr)
|
||||
} else {
|
||||
viewID = node.IALAttr(av.NodeAttrView)
|
||||
}
|
||||
}
|
||||
|
||||
var view *av.View
|
||||
if "" != viewID {
|
||||
view, _ = attrView.GetCurrentView(viewID)
|
||||
if nil != view && view.ID != attrView.ViewID {
|
||||
attrView.ViewID = view.ID
|
||||
if err = av.SaveAttributeView(attrView); err != nil {
|
||||
logging.LogErrorf("save attribute view [%s] failed: %s", attrView.ID, err)
|
||||
return
|
||||
}
|
||||
}
|
||||
} else {
|
||||
view = attrView.GetView(attrView.ViewID)
|
||||
}
|
||||
|
||||
if nil == view {
|
||||
view = attrView.Views[0]
|
||||
}
|
||||
|
||||
// 做一些数据兼容和订正处理
|
||||
checkAttrView(attrView, view)
|
||||
upgradeAttributeViewSpec(attrView)
|
||||
|
||||
viewable = sql.RenderView(attrView, view, query)
|
||||
err = renderViewableInstance(viewable, view, attrView, page, pageSize)
|
||||
if nil != err {
|
||||
return
|
||||
}
|
||||
|
||||
// 当前日期可能会变,所以如果是按日期分组则需要重新生成分组
|
||||
if isGroupByDate(view) {
|
||||
createdDate := time.UnixMilli(view.GroupCreated).Format("2006-01-02")
|
||||
if time.Now().Format("2006-01-02") != createdDate {
|
||||
regenAttrViewViewGroups(attrView, "force")
|
||||
av.SaveAttributeView(attrView)
|
||||
}
|
||||
}
|
||||
|
||||
fixDev := false
|
||||
// 如果存在分组的话渲染分组视图
|
||||
if groupKey := view.GetGroupKey(attrView); nil != groupKey {
|
||||
for _, groupView := range view.Groups {
|
||||
if "" == groupView.GetGroupValue() && !fixDev {
|
||||
// TODO 分组上线后删除,预计 2025 年 9 月后可以删除
|
||||
regenAttrViewViewGroups(attrView, "force")
|
||||
av.SaveAttributeView(attrView)
|
||||
fixDev = true
|
||||
}
|
||||
|
||||
switch groupView.GetGroupValue() {
|
||||
case groupValueDefault:
|
||||
groupView.Name = fmt.Sprintf(Conf.language(264), groupKey.Name)
|
||||
case groupValueNotInRange:
|
||||
groupView.Name = Conf.language(265)
|
||||
case groupValueLast30Days:
|
||||
groupView.Name = fmt.Sprintf(Conf.language(259), 30)
|
||||
case groupValueLast7Days:
|
||||
groupView.Name = fmt.Sprintf(Conf.language(259), 7)
|
||||
case groupValueYesterday:
|
||||
groupView.Name = Conf.language(260)
|
||||
case groupValueToday:
|
||||
groupView.Name = Conf.language(261)
|
||||
case groupValueTomorrow:
|
||||
groupView.Name = Conf.language(262)
|
||||
case groupValueNext7Days:
|
||||
groupView.Name = fmt.Sprintf(Conf.language(263), 7)
|
||||
case groupValueNext30Days:
|
||||
groupView.Name = fmt.Sprintf(Conf.language(263), 30)
|
||||
default:
|
||||
groupView.Name = groupView.GetGroupValue()
|
||||
}
|
||||
}
|
||||
|
||||
todayStart := time.Now()
|
||||
todayStart = time.Date(todayStart.Year(), todayStart.Month(), todayStart.Day(), 0, 0, 0, 0, time.Local)
|
||||
sortGroupViews(todayStart, view)
|
||||
|
||||
var groups []av.Viewable
|
||||
for _, groupView := range view.Groups {
|
||||
groupViewable := sql.RenderGroupView(attrView, view, groupView, query)
|
||||
|
||||
groupPage, groupPageSize := page, pageSize
|
||||
if nil != groupPaging {
|
||||
if paging := groupPaging[groupView.ID]; nil != paging {
|
||||
pagingMap := paging.(map[string]interface{})
|
||||
if nil != pagingMap["page"] {
|
||||
groupPage = int(pagingMap["page"].(float64))
|
||||
}
|
||||
if nil != pagingMap["pageSize"] {
|
||||
groupPageSize = int(pagingMap["pageSize"].(float64))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
err = renderViewableInstance(groupViewable, view, attrView, groupPage, groupPageSize)
|
||||
if nil != err {
|
||||
return
|
||||
}
|
||||
groups = append(groups, groupViewable)
|
||||
|
||||
// 将分组视图的分组字段清空,减少冗余(字段信息可以在总的视图 view 对象上获取到)
|
||||
switch groupView.LayoutType {
|
||||
case av.LayoutTypeTable:
|
||||
groupView.Table.Columns = nil
|
||||
case av.LayoutTypeGallery:
|
||||
groupView.Gallery.CardFields = nil
|
||||
}
|
||||
}
|
||||
viewable.SetGroups(groups)
|
||||
|
||||
// 将总的视图上的项目清空,减少冗余
|
||||
viewable.(av.Collection).SetItems(nil)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func sortGroupViews(todayStart time.Time, view *av.View) {
|
||||
if av.GroupOrderMan == view.Group.Order {
|
||||
sort.Slice(view.Groups, func(i, j int) bool { return view.Groups[i].GroupSort < view.Groups[j].GroupSort })
|
||||
return
|
||||
}
|
||||
|
||||
if av.GroupMethodDateRelative == view.Group.Method {
|
||||
var relativeDateGroups []*av.View
|
||||
var last30Days, last7Days, yesterday, today, tomorrow, next7Days, next30Days, defaultGroup *av.View
|
||||
for _, groupView := range view.Groups {
|
||||
_, err := time.Parse("2006-01", groupView.GetGroupValue())
|
||||
if nil == err { // 如果能解析出来说明是 30 天之前或 30 天之后的分组形式
|
||||
relativeDateGroups = append(relativeDateGroups, groupView)
|
||||
} else { // 否则是相对日期分组形式
|
||||
switch groupView.GetGroupValue() {
|
||||
case groupValueLast30Days:
|
||||
last30Days = groupView
|
||||
case groupValueLast7Days:
|
||||
last7Days = groupView
|
||||
case groupValueYesterday:
|
||||
yesterday = groupView
|
||||
case groupValueToday:
|
||||
today = groupView
|
||||
case groupValueTomorrow:
|
||||
tomorrow = groupView
|
||||
case groupValueNext7Days:
|
||||
next7Days = groupView
|
||||
case groupValueNext30Days:
|
||||
next30Days = groupView
|
||||
case groupValueDefault:
|
||||
defaultGroup = groupView
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
sort.SliceStable(relativeDateGroups, func(i, j int) bool {
|
||||
return relativeDateGroups[i].GetGroupValue() < relativeDateGroups[j].GetGroupValue()
|
||||
})
|
||||
|
||||
var lastNext30Days []*av.View
|
||||
if nil != next30Days {
|
||||
lastNext30Days = append(lastNext30Days, next30Days)
|
||||
}
|
||||
if nil != next7Days {
|
||||
lastNext30Days = append(lastNext30Days, next7Days)
|
||||
}
|
||||
if nil != tomorrow {
|
||||
lastNext30Days = append(lastNext30Days, tomorrow)
|
||||
}
|
||||
if nil != today {
|
||||
lastNext30Days = append(lastNext30Days, today)
|
||||
}
|
||||
if nil != yesterday {
|
||||
lastNext30Days = append(lastNext30Days, yesterday)
|
||||
}
|
||||
|
||||
if nil != last7Days {
|
||||
lastNext30Days = append(lastNext30Days, last7Days)
|
||||
}
|
||||
if nil != last30Days {
|
||||
lastNext30Days = append(lastNext30Days, last30Days)
|
||||
}
|
||||
|
||||
startIdx := -1
|
||||
thisMonth := todayStart.Format("2006-01")
|
||||
for i, monthGroup := range relativeDateGroups {
|
||||
if monthGroup.GetGroupValue() < thisMonth {
|
||||
startIdx = i + 1
|
||||
}
|
||||
}
|
||||
if -1 == startIdx {
|
||||
startIdx = 0
|
||||
}
|
||||
for _, g := range lastNext30Days {
|
||||
relativeDateGroups = util.InsertElem(relativeDateGroups, startIdx, g)
|
||||
}
|
||||
if nil != defaultGroup {
|
||||
relativeDateGroups = append([]*av.View{defaultGroup}, relativeDateGroups...)
|
||||
}
|
||||
|
||||
if av.GroupOrderDesc == view.Group.Order {
|
||||
slices.Reverse(relativeDateGroups)
|
||||
}
|
||||
|
||||
view.Groups = relativeDateGroups
|
||||
} else {
|
||||
sort.SliceStable(view.Groups, func(i, j int) bool {
|
||||
iVal, jVal := view.Groups[i].GetGroupValue(), view.Groups[j].GetGroupValue()
|
||||
if av.GroupOrderAsc == view.Group.Order {
|
||||
return util.NaturalCompare(iVal, jVal)
|
||||
}
|
||||
return util.NaturalCompare(jVal, iVal)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func genAttrViewViewGroups(view *av.View, attrView *av.AttributeView) {
|
||||
if nil == view.Group {
|
||||
return
|
||||
|
|
@ -2117,56 +1770,6 @@ func setAttrViewGroupStates(view *av.View, groupStates map[string]*GroupState) {
|
|||
}
|
||||
}
|
||||
|
||||
func isGroupByDate(view *av.View) bool {
|
||||
if nil == view.Group {
|
||||
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 renderViewableInstance(viewable av.Viewable, view *av.View, attrView *av.AttributeView, page, pageSize int) (err error) {
|
||||
if nil == viewable {
|
||||
err = av.ErrViewNotFound
|
||||
logging.LogErrorf("render attribute view [%s] failed", attrView.ID)
|
||||
return
|
||||
}
|
||||
|
||||
av.Filter(viewable, attrView)
|
||||
av.Sort(viewable, attrView)
|
||||
av.Calc(viewable, attrView)
|
||||
|
||||
// 分页
|
||||
switch viewable.GetType() {
|
||||
case av.LayoutTypeTable:
|
||||
table := viewable.(*av.Table)
|
||||
table.RowCount = len(table.Rows)
|
||||
table.PageSize = view.PageSize
|
||||
if 1 > pageSize {
|
||||
pageSize = table.PageSize
|
||||
}
|
||||
start := (page - 1) * pageSize
|
||||
end := start + pageSize
|
||||
if len(table.Rows) < end {
|
||||
end = len(table.Rows)
|
||||
}
|
||||
table.Rows = table.Rows[start:end]
|
||||
case av.LayoutTypeGallery:
|
||||
gallery := viewable.(*av.Gallery)
|
||||
gallery.CardCount = len(gallery.Cards)
|
||||
gallery.PageSize = view.PageSize
|
||||
if 1 > pageSize {
|
||||
pageSize = gallery.PageSize
|
||||
}
|
||||
start := (page - 1) * pageSize
|
||||
end := start + pageSize
|
||||
if len(gallery.Cards) < end {
|
||||
end = len(gallery.Cards)
|
||||
}
|
||||
gallery.Cards = gallery.Cards[start:end]
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func GetCurrentAttributeViewImages(avID, viewID, query string) (ret []string, err error) {
|
||||
var attrView *av.AttributeView
|
||||
attrView, err = av.ParseAttributeView(avID)
|
||||
|
|
|
|||
461
kernel/model/attribute_view_render.go
Normal file
461
kernel/model/attribute_view_render.go
Normal file
|
|
@ -0,0 +1,461 @@
|
|||
// 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 model
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"slices"
|
||||
"sort"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/88250/gulu"
|
||||
"github.com/88250/lute/ast"
|
||||
"github.com/siyuan-note/dejavu/entity"
|
||||
"github.com/siyuan-note/filelock"
|
||||
"github.com/siyuan-note/logging"
|
||||
"github.com/siyuan-note/siyuan/kernel/av"
|
||||
"github.com/siyuan-note/siyuan/kernel/sql"
|
||||
"github.com/siyuan-note/siyuan/kernel/util"
|
||||
)
|
||||
|
||||
func RenderAttributeView(blockID, avID, viewID, query string, page, pageSize int, groupPaging map[string]interface{}) (viewable av.Viewable, attrView *av.AttributeView, err error) {
|
||||
waitForSyncingStorages()
|
||||
|
||||
if avJSONPath := av.GetAttributeViewDataPath(avID); !filelock.IsExist(avJSONPath) {
|
||||
attrView = av.NewAttributeView(avID)
|
||||
if err = av.SaveAttributeView(attrView); err != nil {
|
||||
logging.LogErrorf("save attribute view [%s] failed: %s", avID, err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
attrView, err = av.ParseAttributeView(avID)
|
||||
if err != nil {
|
||||
logging.LogErrorf("parse attribute view [%s] failed: %s", avID, err)
|
||||
return
|
||||
}
|
||||
|
||||
viewable, err = renderAttributeView(attrView, blockID, viewID, query, page, pageSize, groupPaging)
|
||||
return
|
||||
}
|
||||
|
||||
const (
|
||||
groupValueDefault = "_@default@_" // 默认分组值(值为空的默认分组)
|
||||
groupValueNotInRange = "_@notInRange@_" // 不再范围内的分组值(只有数字类型的分组才可能是该值)
|
||||
groupValueLast30Days, groupValueLast7Days = "_@last30Days@_", "_@last7Days@_"
|
||||
groupValueYesterday, groupValueToday, groupValueTomorrow = "_@yesterday@_", "_@today@_", "_@tomorrow@_"
|
||||
groupValueNext7Days, groupValueNext30Days = "_@next7Days@_", "_@next30Days@_"
|
||||
)
|
||||
|
||||
func renderAttributeView(attrView *av.AttributeView, blockID, viewID, query string, page, pageSize int, groupPaging map[string]interface{}) (viewable av.Viewable, err error) {
|
||||
// 获取待渲染的视图
|
||||
view, err := getRenderAttributeViewView(attrView, blockID, viewID)
|
||||
if nil != err {
|
||||
return
|
||||
}
|
||||
|
||||
// 做一些数据兼容和订正处理
|
||||
checkAttrView(attrView, view)
|
||||
upgradeAttributeViewSpec(attrView)
|
||||
|
||||
// 渲染视图
|
||||
viewable = sql.RenderView(attrView, view, query)
|
||||
err = renderViewableInstance(viewable, view, attrView, page, pageSize)
|
||||
if nil != err {
|
||||
return
|
||||
}
|
||||
|
||||
// 渲染分组视图
|
||||
err = renderAttributeViewGroups(viewable, attrView, view, query, page, pageSize, groupPaging)
|
||||
return
|
||||
}
|
||||
|
||||
func renderAttributeViewGroups(viewable av.Viewable, attrView *av.AttributeView, view *av.View, query string, page, pageSize int, groupPaging map[string]interface{}) (err error) {
|
||||
// 当前日期可能会变,所以如果是按日期分组则需要重新生成分组
|
||||
if isGroupByDate(view) {
|
||||
createdDate := time.UnixMilli(view.GroupCreated).Format("2006-01-02")
|
||||
if time.Now().Format("2006-01-02") != createdDate {
|
||||
regenAttrViewViewGroups(attrView, "force")
|
||||
av.SaveAttributeView(attrView)
|
||||
}
|
||||
}
|
||||
|
||||
groupKey := view.GetGroupKey(attrView)
|
||||
if nil == groupKey {
|
||||
return
|
||||
}
|
||||
|
||||
// 如果存在分组的话渲染分组视图
|
||||
|
||||
fixDev := false
|
||||
for _, groupView := range view.Groups {
|
||||
if "" == groupView.GetGroupValue() && !fixDev {
|
||||
// TODO 分组上线后删除,预计 2025 年 9 月后可以删除
|
||||
regenAttrViewViewGroups(attrView, "force")
|
||||
av.SaveAttributeView(attrView)
|
||||
fixDev = true
|
||||
}
|
||||
|
||||
switch groupView.GetGroupValue() {
|
||||
case groupValueDefault:
|
||||
groupView.Name = fmt.Sprintf(Conf.language(264), groupKey.Name)
|
||||
case groupValueNotInRange:
|
||||
groupView.Name = Conf.language(265)
|
||||
case groupValueLast30Days:
|
||||
groupView.Name = fmt.Sprintf(Conf.language(259), 30)
|
||||
case groupValueLast7Days:
|
||||
groupView.Name = fmt.Sprintf(Conf.language(259), 7)
|
||||
case groupValueYesterday:
|
||||
groupView.Name = Conf.language(260)
|
||||
case groupValueToday:
|
||||
groupView.Name = Conf.language(261)
|
||||
case groupValueTomorrow:
|
||||
groupView.Name = Conf.language(262)
|
||||
case groupValueNext7Days:
|
||||
groupView.Name = fmt.Sprintf(Conf.language(263), 7)
|
||||
case groupValueNext30Days:
|
||||
groupView.Name = fmt.Sprintf(Conf.language(263), 30)
|
||||
default:
|
||||
groupView.Name = groupView.GetGroupValue()
|
||||
}
|
||||
}
|
||||
|
||||
todayStart := time.Now()
|
||||
todayStart = time.Date(todayStart.Year(), todayStart.Month(), todayStart.Day(), 0, 0, 0, 0, time.Local)
|
||||
sortGroupViews(todayStart, view)
|
||||
|
||||
var groups []av.Viewable
|
||||
for _, groupView := range view.Groups {
|
||||
groupViewable := sql.RenderGroupView(attrView, view, groupView, query)
|
||||
|
||||
groupPage, groupPageSize := page, pageSize
|
||||
if nil != groupPaging {
|
||||
if paging := groupPaging[groupView.ID]; nil != paging {
|
||||
pagingMap := paging.(map[string]interface{})
|
||||
if nil != pagingMap["page"] {
|
||||
groupPage = int(pagingMap["page"].(float64))
|
||||
}
|
||||
if nil != pagingMap["pageSize"] {
|
||||
groupPageSize = int(pagingMap["pageSize"].(float64))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
err = renderViewableInstance(groupViewable, view, attrView, groupPage, groupPageSize)
|
||||
if nil != err {
|
||||
return
|
||||
}
|
||||
groups = append(groups, groupViewable)
|
||||
|
||||
// 将分组视图的分组字段清空,减少冗余(字段信息可以在总的视图 view 对象上获取到)
|
||||
switch groupView.LayoutType {
|
||||
case av.LayoutTypeTable:
|
||||
groupView.Table.Columns = nil
|
||||
case av.LayoutTypeGallery:
|
||||
groupView.Gallery.CardFields = nil
|
||||
}
|
||||
}
|
||||
viewable.SetGroups(groups)
|
||||
|
||||
// 将总的视图上的项目清空,减少冗余
|
||||
viewable.(av.Collection).SetItems(nil)
|
||||
return
|
||||
}
|
||||
|
||||
func sortGroupViews(todayStart time.Time, view *av.View) {
|
||||
if av.GroupOrderMan == view.Group.Order {
|
||||
sort.Slice(view.Groups, func(i, j int) bool { return view.Groups[i].GroupSort < view.Groups[j].GroupSort })
|
||||
return
|
||||
}
|
||||
|
||||
if av.GroupMethodDateRelative == view.Group.Method { // 相对日期分组排序
|
||||
var relativeDateGroups []*av.View
|
||||
var last30Days, last7Days, yesterday, today, tomorrow, next7Days, next30Days, defaultGroup *av.View
|
||||
for _, groupView := range view.Groups {
|
||||
_, err := time.Parse("2006-01", groupView.GetGroupValue())
|
||||
if nil == err { // 如果能解析出来说明是 30 天之前或 30 天之后的分组形式
|
||||
relativeDateGroups = append(relativeDateGroups, groupView)
|
||||
} else { // 否则是相对日期分组形式
|
||||
switch groupView.GetGroupValue() {
|
||||
case groupValueLast30Days:
|
||||
last30Days = groupView
|
||||
case groupValueLast7Days:
|
||||
last7Days = groupView
|
||||
case groupValueYesterday:
|
||||
yesterday = groupView
|
||||
case groupValueToday:
|
||||
today = groupView
|
||||
case groupValueTomorrow:
|
||||
tomorrow = groupView
|
||||
case groupValueNext7Days:
|
||||
next7Days = groupView
|
||||
case groupValueNext30Days:
|
||||
next30Days = groupView
|
||||
case groupValueDefault:
|
||||
defaultGroup = groupView
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
sort.SliceStable(relativeDateGroups, func(i, j int) bool {
|
||||
return relativeDateGroups[i].GetGroupValue() < relativeDateGroups[j].GetGroupValue()
|
||||
})
|
||||
|
||||
var lastNext30Days []*av.View
|
||||
if nil != next30Days {
|
||||
lastNext30Days = append(lastNext30Days, next30Days)
|
||||
}
|
||||
if nil != next7Days {
|
||||
lastNext30Days = append(lastNext30Days, next7Days)
|
||||
}
|
||||
if nil != tomorrow {
|
||||
lastNext30Days = append(lastNext30Days, tomorrow)
|
||||
}
|
||||
if nil != today {
|
||||
lastNext30Days = append(lastNext30Days, today)
|
||||
}
|
||||
if nil != yesterday {
|
||||
lastNext30Days = append(lastNext30Days, yesterday)
|
||||
}
|
||||
|
||||
if nil != last7Days {
|
||||
lastNext30Days = append(lastNext30Days, last7Days)
|
||||
}
|
||||
if nil != last30Days {
|
||||
lastNext30Days = append(lastNext30Days, last30Days)
|
||||
}
|
||||
|
||||
startIdx := -1
|
||||
thisMonth := todayStart.Format("2006-01")
|
||||
for i, monthGroup := range relativeDateGroups {
|
||||
if monthGroup.GetGroupValue() < thisMonth {
|
||||
startIdx = i + 1
|
||||
}
|
||||
}
|
||||
if -1 == startIdx {
|
||||
startIdx = 0
|
||||
}
|
||||
for _, g := range lastNext30Days {
|
||||
relativeDateGroups = util.InsertElem(relativeDateGroups, startIdx, g)
|
||||
}
|
||||
|
||||
if av.GroupOrderDesc == view.Group.Order {
|
||||
slices.Reverse(relativeDateGroups)
|
||||
}
|
||||
|
||||
if nil != defaultGroup {
|
||||
relativeDateGroups = append(relativeDateGroups, defaultGroup)
|
||||
}
|
||||
|
||||
view.Groups = relativeDateGroups
|
||||
} else { // 升序/降序
|
||||
defaultGroup := view.GetGroupByGroupValue(groupValueDefault)
|
||||
if nil != defaultGroup {
|
||||
view.RemoveGroupByID(defaultGroup.ID)
|
||||
}
|
||||
|
||||
sort.SliceStable(view.Groups, func(i, j int) bool {
|
||||
iVal, jVal := view.Groups[i].GetGroupValue(), view.Groups[j].GetGroupValue()
|
||||
if av.GroupOrderAsc == view.Group.Order {
|
||||
return util.NaturalCompare(iVal, jVal)
|
||||
}
|
||||
return util.NaturalCompare(jVal, iVal)
|
||||
})
|
||||
|
||||
if nil != defaultGroup {
|
||||
view.Groups = append(view.Groups, defaultGroup)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func isGroupByDate(view *av.View) bool {
|
||||
if nil == view.Group {
|
||||
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 renderViewableInstance(viewable av.Viewable, view *av.View, attrView *av.AttributeView, page, pageSize int) (err error) {
|
||||
if nil == viewable {
|
||||
err = av.ErrViewNotFound
|
||||
logging.LogErrorf("render attribute view [%s] failed", attrView.ID)
|
||||
return
|
||||
}
|
||||
|
||||
av.Filter(viewable, attrView)
|
||||
av.Sort(viewable, attrView)
|
||||
av.Calc(viewable, attrView)
|
||||
|
||||
// 分页
|
||||
switch viewable.GetType() {
|
||||
case av.LayoutTypeTable:
|
||||
table := viewable.(*av.Table)
|
||||
table.RowCount = len(table.Rows)
|
||||
table.PageSize = view.PageSize
|
||||
if 1 > pageSize {
|
||||
pageSize = table.PageSize
|
||||
}
|
||||
start := (page - 1) * pageSize
|
||||
end := start + pageSize
|
||||
if len(table.Rows) < end {
|
||||
end = len(table.Rows)
|
||||
}
|
||||
table.Rows = table.Rows[start:end]
|
||||
case av.LayoutTypeGallery:
|
||||
gallery := viewable.(*av.Gallery)
|
||||
gallery.CardCount = len(gallery.Cards)
|
||||
gallery.PageSize = view.PageSize
|
||||
if 1 > pageSize {
|
||||
pageSize = gallery.PageSize
|
||||
}
|
||||
start := (page - 1) * pageSize
|
||||
end := start + pageSize
|
||||
if len(gallery.Cards) < end {
|
||||
end = len(gallery.Cards)
|
||||
}
|
||||
gallery.Cards = gallery.Cards[start:end]
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func getRenderAttributeViewView(attrView *av.AttributeView, viewID, blockID string) (ret *av.View, err error) {
|
||||
if 1 > len(attrView.Views) {
|
||||
view, _, _ := av.NewTableViewWithBlockKey(ast.NewNodeID())
|
||||
attrView.Views = append(attrView.Views, view)
|
||||
attrView.ViewID = view.ID
|
||||
if err = av.SaveAttributeView(attrView); err != nil {
|
||||
logging.LogErrorf("save attribute view [%s] failed: %s", attrView.ID, err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
if "" == viewID && "" != blockID {
|
||||
node, _, getErr := getNodeByBlockID(nil, blockID)
|
||||
if nil != getErr {
|
||||
logging.LogWarnf("get node by block ID [%s] failed: %s", blockID, getErr)
|
||||
} else {
|
||||
viewID = node.IALAttr(av.NodeAttrView)
|
||||
}
|
||||
}
|
||||
|
||||
if "" != viewID {
|
||||
ret, _ = attrView.GetCurrentView(viewID)
|
||||
if nil != ret && ret.ID != attrView.ViewID {
|
||||
attrView.ViewID = ret.ID
|
||||
if err = av.SaveAttributeView(attrView); err != nil {
|
||||
logging.LogErrorf("save attribute view [%s] failed: %s", attrView.ID, err)
|
||||
return
|
||||
}
|
||||
}
|
||||
} else {
|
||||
ret = attrView.GetView(attrView.ViewID)
|
||||
}
|
||||
|
||||
if nil == ret {
|
||||
ret = attrView.Views[0]
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func RenderRepoSnapshotAttributeView(indexID, avID string) (viewable av.Viewable, attrView *av.AttributeView, err error) {
|
||||
repo, err := newRepository()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
index, err := repo.GetIndex(indexID)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
files, err := repo.GetFiles(index)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
var avFile *entity.File
|
||||
for _, f := range files {
|
||||
if "/storage/av/"+avID+".json" == f.Path {
|
||||
avFile = f
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if nil == avFile {
|
||||
attrView = av.NewAttributeView(avID)
|
||||
} else {
|
||||
data, readErr := repo.OpenFile(avFile)
|
||||
if nil != readErr {
|
||||
logging.LogErrorf("read attribute view [%s] failed: %s", avID, readErr)
|
||||
return
|
||||
}
|
||||
|
||||
attrView = &av.AttributeView{}
|
||||
if err = gulu.JSON.UnmarshalJSON(data, attrView); err != nil {
|
||||
logging.LogErrorf("unmarshal attribute view [%s] failed: %s", avID, err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
viewable, err = renderAttributeView(attrView, "", "", "", 1, -1, nil)
|
||||
return
|
||||
}
|
||||
|
||||
func RenderHistoryAttributeView(avID, created string) (viewable av.Viewable, attrView *av.AttributeView, err error) {
|
||||
createdUnix, parseErr := strconv.ParseInt(created, 10, 64)
|
||||
if nil != parseErr {
|
||||
logging.LogErrorf("parse created [%s] failed: %s", created, parseErr)
|
||||
return
|
||||
}
|
||||
|
||||
dirPrefix := time.Unix(createdUnix, 0).Format("2006-01-02-150405")
|
||||
globPath := filepath.Join(util.HistoryDir, dirPrefix+"*")
|
||||
matches, err := filepath.Glob(globPath)
|
||||
if err != nil {
|
||||
logging.LogErrorf("glob [%s] failed: %s", globPath, err)
|
||||
return
|
||||
}
|
||||
if 1 > len(matches) {
|
||||
return
|
||||
}
|
||||
|
||||
historyDir := matches[0]
|
||||
avJSONPath := filepath.Join(historyDir, "storage", "av", avID+".json")
|
||||
if !gulu.File.IsExist(avJSONPath) {
|
||||
avJSONPath = filepath.Join(util.DataDir, "storage", "av", avID+".json")
|
||||
}
|
||||
if !gulu.File.IsExist(avJSONPath) {
|
||||
attrView = av.NewAttributeView(avID)
|
||||
} else {
|
||||
data, readErr := os.ReadFile(avJSONPath)
|
||||
if nil != readErr {
|
||||
logging.LogErrorf("read attribute view [%s] failed: %s", avID, readErr)
|
||||
return
|
||||
}
|
||||
|
||||
attrView = &av.AttributeView{}
|
||||
if err = gulu.JSON.UnmarshalJSON(data, attrView); err != nil {
|
||||
logging.LogErrorf("unmarshal attribute view [%s] failed: %s", avID, err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
viewable, err = renderAttributeView(attrView, "", "", "", 1, -1, nil)
|
||||
return
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue