package local import ( "fmt" "path/filepath" "strings" "github.com/google/uuid" jsoniter "github.com/json-iterator/go" "github.com/yaoapp/kun/log" "github.com/yaoapp/yao/sui/core" ) // Pages get the pages func (tmpl *Template) Pages() ([]core.IPage, error) { exts := []string{"*.sui", "*.html", "*.htm", "*.page"} pages := []core.IPage{} tmpl.local.fs.Walk(tmpl.Root, func(root, file string, isdir bool) error { name := filepath.Base(file) if isdir { if strings.HasPrefix(name, "__") || name == ".tmp" { return filepath.SkipDir } return nil } if strings.HasPrefix(name, "__") { return nil } page, err := tmpl.getPageFrom(file) if err != nil { log.Error("Get page error: %v", err) return nil } pages = append(pages, page) return nil }, exts...) return pages, nil } // PageTree gets the page tree. func (tmpl *Template) PageTree(route string) ([]*core.PageTreeNode, error) { exts := []string{"*.sui", "*.html", "*.htm", "*.page"} rootNode := &core.PageTreeNode{ Name: tmpl.Name, IsDir: true, Expand: true, Children: []*core.PageTreeNode{}, // 初始为空的切片 } tmpl.local.fs.Walk(tmpl.Root, func(root, file string, isdir bool) error { name := filepath.Base(file) relPath := file log.Debug("[PageTree] Walk | file: %s isdir: %v name: %v", relPath, isdir, name) if isdir { if strings.HasPrefix(name, "__") || name == ".tmp" { return filepath.SkipDir } // Create directory nodes in the tree structure. currentDir := rootNode dirs := strings.Split(relPath, string(filepath.Separator)) for _, dir := range dirs { if dir == "" { continue } // Check if the directory node already exists. var found bool for _, child := range currentDir.Children { if child.Name == dir { currentDir = child found = true break } } log.Debug("[PageTree] Walk | dirs: %s found: %v", dir, found) // If not found, create a new directory node. if !found { newDir := &core.PageTreeNode{ Name: dir, IsDir: true, Children: []*core.PageTreeNode{}, Expand: true, } currentDir.Children = append(currentDir.Children, newDir) currentDir = newDir } } return nil } if strings.HasPrefix(name, "__") { return nil } log.Debug("[PageTree] getPageFrom | file: %s", file) page, err := tmpl.getPageFrom(file) if err != nil { log.Error("Get page error: %v", err) return err } pageInfo := page.Get() active := route == pageInfo.Route log.Debug("[PageTree] getPageFrom |\t pageInfo.Name: %s", pageInfo.Name) // Attach the page to the appropriate directory node. dirs := strings.Split(relPath, string(filepath.Separator)) currentDir := rootNode log.Debug("[PageTree] currentDir | name: %s Children: %d", currentDir.Name, len(currentDir.Children)) for _, dir := range dirs { for _, child := range currentDir.Children { log.Debug("[PageTree] currentDir.Children | child.Name: %s dir:%s", child.Name, dir) if child.Name == dir { currentDir = child break } } } currentDir.Expand = active currentDir.Children = append(currentDir.Children, &core.PageTreeNode{ Name: tmpl.getPageBase(currentDir.Name), IsDir: false, IPage: page, Active: active, }) return nil }, exts...) return rootNode.Children, nil } // Page get the page func (tmpl *Template) Page(route string) (core.IPage, error) { path := tmpl.getPagePath(route) exts := []string{".sui", ".html", ".htm", ".page"} for _, ext := range exts { file := fmt.Sprintf("%s%s", path, ext) if exist, _ := tmpl.local.fs.Exists(file); exist { page, err := tmpl.getPageFrom(file) if err != nil { return nil, err } // Load the page source code err = page.Load() if err != nil { return nil, err } return page, nil } } return nil, fmt.Errorf("%s not found", route) } // PageExist check if the page exist func (tmpl *Template) PageExist(route string) bool { path := tmpl.getPagePath(route) exts := []string{".sui", ".html", ".htm", ".page"} for _, ext := range exts { file := fmt.Sprintf("%s%s", path, ext) if exist, _ := tmpl.local.fs.Exists(file); exist { return true } } return false } // RemovePage remove the page func (tmpl *Template) RemovePage(route string) error { if !tmpl.PageExist(route) { return nil } path := filepath.Join(tmpl.Root, route) name := filepath.Base(path) + ".*" name = strings.ReplaceAll(name, "[", "\\[") name = strings.ReplaceAll(name, "]", "\\]") err := tmpl.local.fs.Walk(path, func(root, file string, isdir bool) error { if isdir { return nil } return tmpl.local.fs.Remove(file) }, name) if err != nil { return err } // Remove .tmp directory tmpPath := filepath.Join(tmpl.Root, route, ".tmp") if exist, _ := tmpl.local.fs.Exists(tmpPath); exist { err = tmpl.local.fs.RemoveAll(tmpPath) if err != nil { return err } } return tmpl.removeEmptyPath(path) } func (tmpl *Template) removeEmptyPath(path string) error { dirs, err := tmpl.local.fs.ReadDir(path, false) if err != nil { return err } if len(dirs) == 0 { err = tmpl.local.fs.RemoveAll(path) if err != nil { return err } parent := filepath.Dir(path) if parent == tmpl.Root { return nil } return tmpl.removeEmptyPath(parent) } return nil } // SaveAs save the page as func (page *Page) SaveAs(route string, setting *core.PageSetting) (core.IPage, error) { if page.tmpl.PageExist(route) { return nil, fmt.Errorf("Page %s already exist", route) } root := page.tmpl.Root target := filepath.Join(root, route) targetBaseName := filepath.Base(target) baseName := filepath.Base(page.Path) patterns := []string{"*.js", "*.ts", "*.html", "*.css", "*.config", "*.json"} err := page.tmpl.local.fs.Walk(page.Path, func(root, file string, isdir bool) error { if isdir { return nil } if filepath.Base(filepath.Dir(file)) != baseName { return nil } fileName := filepath.Base(file) targetFileName := strings.Replace(fileName, baseName, targetBaseName, 1) targetFile := filepath.Join(target, targetFileName) // Copy the file return page.tmpl.local.fs.Copy(file, targetFile) }, patterns...) if err != nil { return nil, err } return page.tmpl.Page(route) } // CreatePage create a new page by the source func (tmpl *Template) CreatePage(source string) core.IPage { name := uuid.New().String() route := "/" + uuid.New().String() return &Page{ tmpl: tmpl, Page: &core.Page{ Route: route, TemplateID: tmpl.ID, SuiID: tmpl.local.ID, Path: filepath.Join(tmpl.Root, route), Name: name, Codes: core.SourceCodes{ HTML: core.Source{File: fmt.Sprintf("%s.html", name), Code: source}, CSS: core.Source{File: fmt.Sprintf("%s.css", name)}, JS: core.Source{File: fmt.Sprintf("%s.js", name)}, TS: core.Source{File: fmt.Sprintf("%s.ts", name)}, LESS: core.Source{File: fmt.Sprintf("%s.less", name)}, CONF: core.Source{File: fmt.Sprintf("%s.config", name)}, }, }, } } // CreateEmptyPage create a new empty func (tmpl *Template) CreateEmptyPage(route string, setting *core.PageSetting) (core.IPage, error) { if tmpl.PageExist(route) { return nil, fmt.Errorf("Page %s already exist", route) } // Create the page directory name := tmpl.getPageBase(route) page := &Page{ tmpl: tmpl, Page: &core.Page{ Route: route, TemplateID: tmpl.ID, SuiID: tmpl.local.ID, Path: filepath.Join(tmpl.Root, route), Name: name, Codes: core.SourceCodes{ HTML: core.Source{File: fmt.Sprintf("%s.html", name)}, CSS: core.Source{File: fmt.Sprintf("%s.css", name)}, JS: core.Source{File: fmt.Sprintf("%s.js", name)}, TS: core.Source{File: fmt.Sprintf("%s.ts", name)}, LESS: core.Source{File: fmt.Sprintf("%s.less", name)}, CONF: core.Source{File: fmt.Sprintf("%s.config", name)}, }, }, } title := route if setting != nil { title = setting.Title } err := page.Save(&core.RequestSource{ Page: &core.SourceData{Source: fmt.Sprintf("
%s
", title), Language: "html"}, Setting: setting, NeedToSave: core.ReqeustSourceNeedToSave{Page: true, Setting: true}, }) if err != nil { return nil, err } return page, nil } // Remove remove the page func (page *Page) Remove() error { return page.tmpl.RemovePage(page.Route) } // GetPageFromAsset get the page from the asset func (tmpl *Template) GetPageFromAsset(file string) (core.IPage, error) { route := filepath.Dir(file) name := tmpl.getPageBase(route) return &Page{ tmpl: tmpl, Page: &core.Page{ Route: route, TemplateID: tmpl.ID, SuiID: tmpl.local.ID, Path: filepath.Join(tmpl.Root, route), Name: name, Codes: core.SourceCodes{ CSS: core.Source{File: fmt.Sprintf("%s.css", name)}, JS: core.Source{File: fmt.Sprintf("%s.js", name)}, TS: core.Source{File: fmt.Sprintf("%s.ts", name)}, LESS: core.Source{File: fmt.Sprintf("%s.less", name)}, }, }, }, nil } func (tmpl *Template) getPageFrom(file string) (core.IPage, error) { route := tmpl.getPageRoute(file) return tmpl.getPage(route, file) } func (tmpl *Template) getPage(route, file string) (core.IPage, error) { path := filepath.Dir(file) name := tmpl.getPageBase(route) return &Page{ tmpl: tmpl, Page: &core.Page{ Route: route, Path: path, Name: name, TemplateID: tmpl.ID, SuiID: tmpl.local.ID, Codes: core.SourceCodes{ HTML: core.Source{File: fmt.Sprintf("%s%s", name, filepath.Ext(file))}, CSS: core.Source{File: fmt.Sprintf("%s.css", name)}, JS: core.Source{File: fmt.Sprintf("%s.js", name)}, DATA: core.Source{File: fmt.Sprintf("%s.json", name)}, TS: core.Source{File: fmt.Sprintf("%s.ts", name)}, LESS: core.Source{File: fmt.Sprintf("%s.less", name)}, CONF: core.Source{File: fmt.Sprintf("%s.config", name)}, }, }, }, nil } func (tmpl *Template) getPageRoute(file string) string { return filepath.Dir(file[len(tmpl.Root):]) } func (tmpl *Template) getPagePath(route string) string { name := tmpl.getPageBase(route) return filepath.Join(tmpl.Root, route, name) } func (tmpl *Template) getPageBase(route string) string { return filepath.Base(route) } // Load get the page from the storage func (page *Page) Load() error { // Read the Script code // Type script is the default language tsFile := filepath.Join(page.Path, page.Codes.TS.File) if exist, _ := page.tmpl.local.fs.Exists(tsFile); exist { tsCode, err := page.tmpl.local.fs.ReadFile(tsFile) if err != nil { return err } page.Codes.TS.Code = string(tsCode) } else { jsFile := filepath.Join(page.Path, page.Codes.JS.File) if exist, _ := page.tmpl.local.fs.Exists(jsFile); exist { jsCode, err := page.tmpl.local.fs.ReadFile(jsFile) if err != nil { return err } page.Codes.JS.Code = string(jsCode) } } // Read the HTML code htmlFile := filepath.Join(page.Path, page.Codes.HTML.File) if exist, _ := page.tmpl.local.fs.Exists(htmlFile); exist { htmlCode, err := page.tmpl.local.fs.ReadFile(htmlFile) if err != nil { return err } page.Codes.HTML.Code = string(htmlCode) } // Read the CSS code // @todo: Less support cssFile := filepath.Join(page.Path, page.Codes.CSS.File) if exist, _ := page.tmpl.local.fs.Exists(cssFile); exist { cssCode, err := page.tmpl.local.fs.ReadFile(cssFile) if err != nil { return err } page.Codes.CSS.Code = string(cssCode) } // Read the JSON code dataFile := filepath.Join(page.Path, page.Codes.DATA.File) if exist, _ := page.tmpl.local.fs.Exists(dataFile); exist { dataCode, err := page.tmpl.local.fs.ReadFile(dataFile) if err != nil { return err } page.Codes.DATA.Code = string(dataCode) } // Read the config code confFile := filepath.Join(page.Path, page.Codes.CONF.File) if exist, _ := page.tmpl.local.fs.Exists(confFile); exist { confCode, err := page.tmpl.local.fs.ReadFile(confFile) if err != nil { return err } page.Codes.CONF.Code = string(confCode) } // Set the page CacheStore page.CacheStore = page.tmpl.local.DSL.CacheStore // Set the page document page.Document = page.tmpl.Document // Set the page global data page.GlobalData = page.tmpl.GlobalData // Load the backend script err := page.loadBackendScript() if err != nil { return err } return nil } // SaveTemp save page to the temp file func (page *Page) SaveTemp(request *core.RequestSource) error { tempPath := filepath.Join(page.Path, ".tmp", request.UID) return page.save(tempPath, request) } // Save save page to the storage, if the page not exist, create it func (page *Page) Save(request *core.RequestSource) error { path := page.Path err := page.save(path, request) if err != nil { return err } // Remove the temp file tempPath := filepath.Join(page.Path, ".tmp", request.UID) if exist, _ := page.tmpl.local.fs.Exists(tempPath); exist { err = page.tmpl.local.fs.RemoveAll(tempPath) if err != nil { return err } dirs, err := page.tmpl.local.fs.ReadDir(filepath.Join(page.Path, ".tmp"), false) if err != nil { return err } if len(dirs) == 0 { return page.tmpl.local.fs.Remove(filepath.Join(page.Path, ".tmp")) } } return nil } // save page to the temp file func (page *Page) save(path string, request *core.RequestSource) error { if request.NeedToSave.Board { err := page.saveBoard(path, request.Board) if err != nil { return err } } if request.NeedToSave.Page { err := page.savePage(path, request.Page) if err != nil { return err } } if request.NeedToSave.Style { err := page.saveStyle(path, request.Style) if err != nil { return err } } if request.NeedToSave.Script { err := page.saveScript(path, request.Script) if err != nil { return err } } if request.NeedToSave.Data { err := page.saveData(path, request.Data) if err != nil { return err } } if request.NeedToSave.Setting || request.NeedToSave.Mock { err := page.saveSetting(path, request.Setting, request.Mock) if err != nil { return err } } return nil } // saveBoard save the board to the storage func (page *Page) saveBoard(path string, board *core.BoardSourceData) error { htmlFile := filepath.Join(path, page.Codes.HTML.File) _, err := page.tmpl.local.fs.WriteFile(htmlFile, []byte(board.HTML), 0644) if err != nil { return err } cssFile := filepath.Join(path, page.Codes.CSS.File) _, err = page.tmpl.local.fs.WriteFile(cssFile, []byte(board.Style), 0644) return err } func (page *Page) savePage(path string, src *core.SourceData) error { if src.Language != "html" { return fmt.Errorf("Page %s language not support", page.Route) } htmlFile := filepath.Join(path, page.Codes.HTML.File) _, err := page.tmpl.local.fs.WriteFile(htmlFile, []byte(src.Source), 0644) return err } func (page *Page) saveStyle(path string, src *core.SourceData) error { if src.Language != "css" { return fmt.Errorf("Page %s language not support", page.Route) } cssFile := filepath.Join(path, page.Codes.CSS.File) _, err := page.tmpl.local.fs.WriteFile(cssFile, []byte(src.Source), 0644) return err } func (page *Page) saveScript(path string, src *core.SourceData) error { switch src.Language { case "typescript": tsFile := filepath.Join(path, page.Codes.TS.File) _, err := page.tmpl.local.fs.WriteFile(tsFile, []byte(src.Source), 0644) return err case "javascript": jsFile := filepath.Join(path, page.Codes.JS.File) _, err := page.tmpl.local.fs.WriteFile(jsFile, []byte(src.Source), 0644) return err default: return fmt.Errorf("Page %s language not support", page.Route) } } func (page *Page) saveData(path string, src *core.SourceData) error { if src.Language != "json" { return fmt.Errorf("Page %s language not support", page.Route) } dataFile := filepath.Join(path, page.Codes.DATA.File) _, err := page.tmpl.local.fs.WriteFile(dataFile, []byte(src.Source), 0644) return err } func (page *Page) saveSetting(path string, setting *core.PageSetting, mock *core.PageMock) error { config := core.PageConfig{Mock: mock} if setting != nil { config.PageSetting = *setting } configBytes, err := jsoniter.MarshalIndent(config, "", " ") if err != nil { return err } if setting != nil || mock != nil { dataFile := filepath.Join(path, page.Codes.CONF.File) _, err = page.tmpl.local.fs.WriteFile(dataFile, configBytes, 0644) return err } return nil } // AssetScript get the script func (page *Page) AssetScript() (*core.Asset, error) { // Read the Script code // Type script is the default language tsFile := filepath.Join(page.Path, page.Codes.TS.File) if exist, _ := page.tmpl.local.fs.Exists(tsFile); exist { tsCode, err := page.tmpl.local.fs.ReadFile(tsFile) if err != nil { return nil, err } jsCode, _, err := page.CompileTS(tsCode, false) if err != nil { return nil, err } return &core.Asset{ Type: "text/javascript; charset=utf-8", Content: []byte(jsCode), }, nil } jsFile := filepath.Join(page.Path, page.Codes.JS.File) if exist, _ := page.tmpl.local.fs.Exists(jsFile); exist { jsCode, err := page.tmpl.local.fs.ReadFile(jsFile) if err != nil { return nil, err } jsCode, _, err = page.CompileJS(jsCode, false) if err != nil { return nil, err } return &core.Asset{ Type: "text/javascript; charset=utf-8", Content: jsCode, }, nil } return nil, fmt.Errorf("%s script not found", page.Route) } // AssetStyle get the style func (page *Page) AssetStyle() (*core.Asset, error) { cssFile := filepath.Join(page.Path, page.Codes.CSS.File) if exist, _ := page.tmpl.local.fs.Exists(cssFile); exist { cssCode, err := page.tmpl.local.fs.ReadFile(cssFile) if err != nil { return nil, err } cssCode, err = page.CompileCSS(cssCode, false) if err != nil { return nil, err } return &core.Asset{ Type: "text/css; charset=utf-8", Content: cssCode, }, nil } return nil, fmt.Errorf("%s style not found", page.Route) }