feat: integrate @tailwindcss/typography and enhance folder picker functionality
- Added @tailwindcss/typography dependency to package.json and tailwind.config.js. - Updated FolderPicker.vue to include additional properties (isGitRepo, hasDocuments) for directories. - Enhanced Sidebar.vue with folder creation and document handling features, including drag-and-drop functionality. - Implemented createFolder and moveDocument mutations in gql.ts for managing folder and document operations. - Added logic to handle folder creation and document creation prompts in Sidebar.vue. - Updated the layout and interactions in Sidebar.vue to improve user experience.
This commit is contained in:
4
.gitignore
vendored
4
.gitignore
vendored
@@ -5,7 +5,7 @@ backend/*.test
|
||||
backend/archivum
|
||||
|
||||
# Frontend
|
||||
frontend/node_modules/
|
||||
frontend/node_modules/*
|
||||
frontend/dist/
|
||||
frontend/.env
|
||||
frontend/.env.local
|
||||
@@ -32,4 +32,4 @@ Thumbs.db
|
||||
|
||||
# IDE
|
||||
.idea/
|
||||
*.swp
|
||||
*.swp
|
||||
@@ -367,18 +367,29 @@ Archivum/
|
||||
```graphql
|
||||
type Query {
|
||||
systemStatus: SystemStatus! # OK | REQUIRE_SETUP
|
||||
serverDirectories(path: String): [ServerDirectory!]! # Listar mappar (inkl. isGitRepo)
|
||||
documents(prefix: String): [DocumentMeta!]!
|
||||
document(slug: String!): Document
|
||||
folders: [String!]! # Listar alla mappvägar i repositoriet
|
||||
history(slug: String!): [CommitEntry!]!
|
||||
diff(slug: String!, fromHash: String!, toHash: String!): String!
|
||||
}
|
||||
|
||||
type ServerDirectory {
|
||||
name: String!
|
||||
path: String!
|
||||
isGitRepo: Boolean!
|
||||
hasDocuments: Boolean!
|
||||
}
|
||||
|
||||
type Mutation {
|
||||
setup(input: SetupInput!): Boolean!
|
||||
login(username: String!, password: String!): String! # returnerar session-token
|
||||
logout: Boolean!
|
||||
saveDocument(input: SaveDocumentInput!): Document!
|
||||
deleteDocument(slug: String!): Boolean!
|
||||
createFolder(path: String!): Boolean! # Skapar en ny mapp (katalog)
|
||||
moveDocument(oldSlug: String!, newSlug: String!): Boolean! # Flyttar ett dokument till en annan plats
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
@@ -121,6 +121,19 @@ func (r *Repo) CommitAll(message, author, email string) error {
|
||||
return err
|
||||
}
|
||||
|
||||
// Move renames a file from oldPath to newPath and creates a Git commit.
|
||||
func (r *Repo) Move(oldPath, newPath, message, author, email string) error {
|
||||
if _, err := r.run("mv", oldPath, newPath); err != nil {
|
||||
return err
|
||||
}
|
||||
_, err := r.run(
|
||||
"-c", fmt.Sprintf("user.name=%s", author),
|
||||
"-c", fmt.Sprintf("user.email=%s", email),
|
||||
"commit", "-m", message,
|
||||
)
|
||||
return err
|
||||
}
|
||||
|
||||
type LogEntry struct {
|
||||
Hash string
|
||||
Author string
|
||||
|
||||
@@ -14,6 +14,9 @@ type Query {
|
||||
# List documents under an optional path prefix.
|
||||
documents(prefix: String): [DocumentMeta!]!
|
||||
|
||||
# List all relative folder paths in the repository.
|
||||
folders: [String!]!
|
||||
|
||||
# Commit history for a document.
|
||||
history(slug: String!): [CommitEntry!]!
|
||||
|
||||
@@ -25,6 +28,9 @@ type Query {
|
||||
|
||||
# Whether the current storage path has uncommitted files.
|
||||
repoStatus: RepoStatus!
|
||||
|
||||
# List images next to a document.
|
||||
images(slug: String!): [ImageFile!]!
|
||||
}
|
||||
|
||||
type Mutation {
|
||||
@@ -57,6 +63,15 @@ type Mutation {
|
||||
|
||||
# Commit all uncommitted files in the storage path.
|
||||
initCommit(message: String!): Boolean!
|
||||
|
||||
# Delete an image file.
|
||||
deleteImage(slug: String!, filename: String!): Boolean!
|
||||
|
||||
# Create a new folder (directory) at the specified path.
|
||||
createFolder(path: String!): Boolean!
|
||||
|
||||
# Move a document from one location to another.
|
||||
moveDocument(oldSlug: String!, newSlug: String!): Boolean!
|
||||
}
|
||||
|
||||
# ── Types ──────────────────────────────────────────────────────────────────────
|
||||
@@ -79,8 +94,10 @@ type LDAPConfig {
|
||||
}
|
||||
|
||||
type ServerDirectory {
|
||||
name: String!
|
||||
path: String!
|
||||
name: String!
|
||||
path: String!
|
||||
isGitRepo: Boolean!
|
||||
hasDocuments: Boolean!
|
||||
}
|
||||
|
||||
type Document {
|
||||
@@ -114,6 +131,12 @@ type RepoStatus {
|
||||
hasUncommitted: Boolean!
|
||||
}
|
||||
|
||||
type ImageFile {
|
||||
name: String!
|
||||
url: String!
|
||||
size: Int!
|
||||
}
|
||||
|
||||
# ── Inputs ─────────────────────────────────────────────────────────────────────
|
||||
|
||||
input SetupInput {
|
||||
|
||||
@@ -42,6 +42,8 @@ func NewServer(cfg *config.Config, configPath string) http.Handler {
|
||||
mux := http.NewServeMux()
|
||||
mux.HandleFunc("/graphql", s.handleGraphQL)
|
||||
mux.HandleFunc("/health", s.handleHealth)
|
||||
mux.HandleFunc("/api/upload", s.handleUpload)
|
||||
mux.HandleFunc("/media/", s.handleMedia)
|
||||
|
||||
uiDir := os.Getenv("UI_DIR")
|
||||
if uiDir == "" {
|
||||
@@ -185,6 +187,9 @@ func (s *Server) dispatchAuthenticated(
|
||||
case strings.Contains(q, "deleteDocument"):
|
||||
s.handleDeleteDocument(w, req, sess)
|
||||
|
||||
case strings.Contains(q, "deleteImage"):
|
||||
s.handleDeleteImage(w, req, sess)
|
||||
|
||||
case strings.Contains(q, "createUser"):
|
||||
s.handleCreateUser(w, req, sess)
|
||||
|
||||
@@ -218,6 +223,18 @@ func (s *Server) dispatchAuthenticated(
|
||||
case strings.Contains(q, "history"):
|
||||
s.handleHistory(w, req, sess)
|
||||
|
||||
case strings.Contains(q, "images"):
|
||||
s.handleImages(w, req, sess)
|
||||
|
||||
case strings.Contains(q, "createFolder"):
|
||||
s.handleCreateFolder(w, req, sess)
|
||||
|
||||
case strings.Contains(q, "moveDocument"):
|
||||
s.handleMoveDocument(w, req, sess)
|
||||
|
||||
case strings.Contains(q, "folders"):
|
||||
s.handleFolders(w, sess)
|
||||
|
||||
case strings.Contains(q, "diff"):
|
||||
s.handleDiff(w, req, sess)
|
||||
|
||||
@@ -240,18 +257,24 @@ func (s *Server) handleServerDirectories(w http.ResponseWriter, req gqlRequest,
|
||||
// Only allow setup state without auth
|
||||
if cfg != nil {
|
||||
if sess == nil {
|
||||
log.Printf("[unauth] session is nil")
|
||||
writeGQLError(w, "UNAUTHORIZED")
|
||||
return
|
||||
}
|
||||
if sess.Role != "admin" {
|
||||
log.Printf("[unauth] user %s is not admin, role is %q", sess.Username, sess.Role)
|
||||
writeGQLError(w, "UNAUTHORIZED")
|
||||
return
|
||||
}
|
||||
log.Printf("[unauth] session is nil")
|
||||
writeGQLError(w, "UNAUTHORIZED")
|
||||
return
|
||||
}
|
||||
if sess.Role != "admin" {
|
||||
log.Printf("[unauth] user %s is not admin, role is %q", sess.Username, sess.Role)
|
||||
writeGQLError(w, "UNAUTHORIZED")
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
reqPath, _ := req.Variables["path"].(string)
|
||||
if reqPath == "" {
|
||||
// Also try "p" just in case the frontend sends p
|
||||
if p, ok := req.Variables["p"].(string); ok && p != "" {
|
||||
reqPath = p
|
||||
}
|
||||
}
|
||||
if reqPath == "" {
|
||||
if runtime.GOOS == "windows" {
|
||||
reqPath = "C:\\"
|
||||
@@ -268,8 +291,10 @@ func (s *Server) handleServerDirectories(w http.ResponseWriter, req gqlRequest,
|
||||
parent := filepath.Dir(reqPath)
|
||||
if parent != reqPath && parent != "." {
|
||||
out = append(out, map[string]interface{}{
|
||||
"name": "..",
|
||||
"path": parent,
|
||||
"name": "..",
|
||||
"path": parent,
|
||||
"isGitRepo": false,
|
||||
"hasDocuments": false,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -282,9 +307,26 @@ func (s *Server) handleServerDirectories(w http.ResponseWriter, req gqlRequest,
|
||||
|
||||
for _, entry := range entries {
|
||||
if entry.IsDir() {
|
||||
fullPath := filepath.Join(reqPath, entry.Name())
|
||||
isGit := false
|
||||
if _, err := os.Stat(filepath.Join(fullPath, ".git")); err == nil {
|
||||
isGit = true
|
||||
}
|
||||
|
||||
hasDocs := false
|
||||
childEntries, _ := os.ReadDir(fullPath)
|
||||
for _, child := range childEntries {
|
||||
if !child.IsDir() && (strings.HasSuffix(child.Name(), ".md") || strings.HasSuffix(child.Name(), ".adoc")) {
|
||||
hasDocs = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
out = append(out, map[string]interface{}{
|
||||
"name": entry.Name(),
|
||||
"path": filepath.Join(reqPath, entry.Name()),
|
||||
"name": entry.Name(),
|
||||
"path": fullPath,
|
||||
"isGitRepo": isGit,
|
||||
"hasDocuments": hasDocs,
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -299,27 +341,27 @@ func (s *Server) handleServerDirectories(w http.ResponseWriter, req gqlRequest,
|
||||
// ── Session helpers ───────────────────────────────────────────────────────────
|
||||
|
||||
func (s *Server) sessionFromRequest(r *http.Request) *auth.Session {
|
||||
raw := r.Header.Get("Authorization")
|
||||
if !strings.HasPrefix(raw, "Bearer ") {
|
||||
log.Printf("[auth] No Bearer token found in header")
|
||||
return nil
|
||||
}
|
||||
token := strings.TrimPrefix(raw, "Bearer ")
|
||||
raw := r.Header.Get("Authorization")
|
||||
if !strings.HasPrefix(raw, "Bearer ") {
|
||||
log.Printf("[auth] No Bearer token found in header")
|
||||
return nil
|
||||
}
|
||||
token := strings.TrimPrefix(raw, "Bearer ")
|
||||
|
||||
s.mu.RLock()
|
||||
mgr := s.authMgr
|
||||
s.mu.RUnlock()
|
||||
s.mu.RLock()
|
||||
mgr := s.authMgr
|
||||
s.mu.RUnlock()
|
||||
|
||||
if mgr == nil {
|
||||
log.Printf("[auth] authmgr is nil")
|
||||
return nil
|
||||
}
|
||||
sess, err := mgr.Validate(token)
|
||||
if err != nil {
|
||||
log.Printf("[auth] Token validation failed: %v", err)
|
||||
return nil
|
||||
}
|
||||
return sess
|
||||
if mgr == nil {
|
||||
log.Printf("[auth] authmgr is nil")
|
||||
return nil
|
||||
}
|
||||
sess, err := mgr.Validate(token)
|
||||
if err != nil {
|
||||
log.Printf("[auth] Token validation failed: %v", err)
|
||||
return nil
|
||||
}
|
||||
return sess
|
||||
}
|
||||
|
||||
// ── Document handlers ─────────────────────────────────────────────────────────
|
||||
@@ -534,12 +576,12 @@ func (s *Server) handleLogin(w http.ResponseWriter, req gqlRequest) {
|
||||
return
|
||||
}
|
||||
|
||||
log.Printf("[auth] login: %s (%s)", username, role)
|
||||
writeJSONObj(w, map[string]interface{}{
|
||||
"data": map[string]interface{}{
|
||||
"login": token,
|
||||
},
|
||||
})
|
||||
log.Printf("[auth] login: %s (%s)", username, role)
|
||||
writeJSONObj(w, map[string]interface{}{
|
||||
"data": map[string]interface{}{
|
||||
"login": token,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
func (s *Server) handleLogout(w http.ResponseWriter, sess *auth.Session) {
|
||||
@@ -559,15 +601,15 @@ func (s *Server) handleLogout(w http.ResponseWriter, sess *auth.Session) {
|
||||
|
||||
func (s *Server) handleUsers(w http.ResponseWriter, sess *auth.Session) {
|
||||
if sess == nil {
|
||||
log.Printf("[unauth] session is nil")
|
||||
writeGQLError(w, "UNAUTHORIZED")
|
||||
return
|
||||
}
|
||||
if sess.Role != "admin" {
|
||||
log.Printf("[unauth] user %s is not admin, role is %q", sess.Username, sess.Role)
|
||||
writeGQLError(w, "UNAUTHORIZED")
|
||||
return
|
||||
}
|
||||
log.Printf("[unauth] session is nil")
|
||||
writeGQLError(w, "UNAUTHORIZED")
|
||||
return
|
||||
}
|
||||
if sess.Role != "admin" {
|
||||
log.Printf("[unauth] user %s is not admin, role is %q", sess.Username, sess.Role)
|
||||
writeGQLError(w, "UNAUTHORIZED")
|
||||
return
|
||||
}
|
||||
|
||||
s.mu.RLock()
|
||||
database := s.database
|
||||
@@ -600,15 +642,15 @@ func (s *Server) handleUsers(w http.ResponseWriter, sess *auth.Session) {
|
||||
|
||||
func (s *Server) handleCreateUser(w http.ResponseWriter, req gqlRequest, sess *auth.Session) {
|
||||
if sess == nil {
|
||||
log.Printf("[unauth] session is nil")
|
||||
writeGQLError(w, "UNAUTHORIZED")
|
||||
return
|
||||
}
|
||||
if sess.Role != "admin" {
|
||||
log.Printf("[unauth] user %s is not admin, role is %q", sess.Username, sess.Role)
|
||||
writeGQLError(w, "UNAUTHORIZED")
|
||||
return
|
||||
}
|
||||
log.Printf("[unauth] session is nil")
|
||||
writeGQLError(w, "UNAUTHORIZED")
|
||||
return
|
||||
}
|
||||
if sess.Role != "admin" {
|
||||
log.Printf("[unauth] user %s is not admin, role is %q", sess.Username, sess.Role)
|
||||
writeGQLError(w, "UNAUTHORIZED")
|
||||
return
|
||||
}
|
||||
|
||||
username, _ := req.Variables["u"].(string)
|
||||
password, _ := req.Variables["p"].(string)
|
||||
@@ -655,15 +697,15 @@ func (s *Server) handleCreateUser(w http.ResponseWriter, req gqlRequest, sess *a
|
||||
|
||||
func (s *Server) handleDeleteUser(w http.ResponseWriter, req gqlRequest, sess *auth.Session) {
|
||||
if sess == nil {
|
||||
log.Printf("[unauth] session is nil")
|
||||
writeGQLError(w, "UNAUTHORIZED")
|
||||
return
|
||||
}
|
||||
if sess.Role != "admin" {
|
||||
log.Printf("[unauth] user %s is not admin, role is %q", sess.Username, sess.Role)
|
||||
writeGQLError(w, "UNAUTHORIZED")
|
||||
return
|
||||
}
|
||||
log.Printf("[unauth] session is nil")
|
||||
writeGQLError(w, "UNAUTHORIZED")
|
||||
return
|
||||
}
|
||||
if sess.Role != "admin" {
|
||||
log.Printf("[unauth] user %s is not admin, role is %q", sess.Username, sess.Role)
|
||||
writeGQLError(w, "UNAUTHORIZED")
|
||||
return
|
||||
}
|
||||
|
||||
username, _ := req.Variables["username"].(string)
|
||||
if username == "" {
|
||||
@@ -745,15 +787,15 @@ func (s *Server) handleChangePassword(w http.ResponseWriter, req gqlRequest, ses
|
||||
|
||||
func (s *Server) handleUpdateLdapConfig(w http.ResponseWriter, req gqlRequest, sess *auth.Session) {
|
||||
if sess == nil {
|
||||
log.Printf("[unauth] session is nil")
|
||||
writeGQLError(w, "UNAUTHORIZED")
|
||||
return
|
||||
}
|
||||
if sess.Role != "admin" {
|
||||
log.Printf("[unauth] user %s is not admin, role is %q", sess.Username, sess.Role)
|
||||
writeGQLError(w, "UNAUTHORIZED")
|
||||
return
|
||||
}
|
||||
log.Printf("[unauth] session is nil")
|
||||
writeGQLError(w, "UNAUTHORIZED")
|
||||
return
|
||||
}
|
||||
if sess.Role != "admin" {
|
||||
log.Printf("[unauth] user %s is not admin, role is %q", sess.Username, sess.Role)
|
||||
writeGQLError(w, "UNAUTHORIZED")
|
||||
return
|
||||
}
|
||||
|
||||
s.mu.RLock()
|
||||
cfg := s.cfg
|
||||
@@ -801,15 +843,15 @@ func (s *Server) handleUpdateLdapConfig(w http.ResponseWriter, req gqlRequest, s
|
||||
|
||||
func (s *Server) handleConfig(w http.ResponseWriter, sess *auth.Session) {
|
||||
if sess == nil {
|
||||
log.Printf("[unauth] session is nil")
|
||||
writeGQLError(w, "UNAUTHORIZED")
|
||||
return
|
||||
}
|
||||
if sess.Role != "admin" {
|
||||
log.Printf("[unauth] user %s is not admin, role is %q", sess.Username, sess.Role)
|
||||
writeGQLError(w, "UNAUTHORIZED")
|
||||
return
|
||||
}
|
||||
log.Printf("[unauth] session is nil")
|
||||
writeGQLError(w, "UNAUTHORIZED")
|
||||
return
|
||||
}
|
||||
if sess.Role != "admin" {
|
||||
log.Printf("[unauth] user %s is not admin, role is %q", sess.Username, sess.Role)
|
||||
writeGQLError(w, "UNAUTHORIZED")
|
||||
return
|
||||
}
|
||||
|
||||
s.mu.RLock()
|
||||
cfg := s.cfg
|
||||
@@ -899,15 +941,15 @@ func (s *Server) handleInitCommit(w http.ResponseWriter, req gqlRequest, sess *a
|
||||
|
||||
func (s *Server) handleUpdateStoragePath(w http.ResponseWriter, req gqlRequest, sess *auth.Session) {
|
||||
if sess == nil {
|
||||
log.Printf("[unauth] session is nil")
|
||||
writeGQLError(w, "UNAUTHORIZED")
|
||||
return
|
||||
}
|
||||
if sess.Role != "admin" {
|
||||
log.Printf("[unauth] user %s is not admin, role is %q", sess.Username, sess.Role)
|
||||
writeGQLError(w, "UNAUTHORIZED")
|
||||
return
|
||||
}
|
||||
log.Printf("[unauth] session is nil")
|
||||
writeGQLError(w, "UNAUTHORIZED")
|
||||
return
|
||||
}
|
||||
if sess.Role != "admin" {
|
||||
log.Printf("[unauth] user %s is not admin, role is %q", sess.Username, sess.Role)
|
||||
writeGQLError(w, "UNAUTHORIZED")
|
||||
return
|
||||
}
|
||||
|
||||
s.mu.RLock()
|
||||
cfg := s.cfg
|
||||
@@ -1227,78 +1269,359 @@ func resolveDBPath(configPath string) string {
|
||||
return filepath.Join(dirOf(configPath), "archivum.db")
|
||||
}
|
||||
|
||||
|
||||
|
||||
func (s *Server) handleHistory(w http.ResponseWriter, req gqlRequest, sess *auth.Session) {
|
||||
if sess == nil { writeGQLError(w, "UNAUTHORIZED"); return }
|
||||
slug, _ := req.Variables["slug"].(string)
|
||||
if slug == "" { writeGQLError(w, "slug required"); return }
|
||||
if sess == nil {
|
||||
writeGQLError(w, "UNAUTHORIZED")
|
||||
return
|
||||
}
|
||||
slug, _ := req.Variables["slug"].(string)
|
||||
if slug == "" {
|
||||
writeGQLError(w, "slug required")
|
||||
return
|
||||
}
|
||||
|
||||
s.mu.RLock()
|
||||
repo := s.gitRepo
|
||||
s.mu.RUnlock()
|
||||
s.mu.RLock()
|
||||
repo := s.gitRepo
|
||||
s.mu.RUnlock()
|
||||
|
||||
if repo == nil {
|
||||
writeGQLError(w, "git not initialized")
|
||||
return
|
||||
}
|
||||
if repo == nil {
|
||||
writeGQLError(w, "git not initialized")
|
||||
return
|
||||
}
|
||||
|
||||
history, err := repo.Log(slug + ".adoc")
|
||||
if err != nil {
|
||||
writeGQLError(w, err.Error())
|
||||
return
|
||||
}
|
||||
history, err := repo.Log(slug + ".adoc")
|
||||
if err != nil {
|
||||
writeGQLError(w, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
var out []map[string]interface{}
|
||||
for _, entry := range history {
|
||||
out = append(out, map[string]interface{}{
|
||||
"hash": entry.Hash,
|
||||
"author": entry.Author,
|
||||
"email": entry.Email,
|
||||
"date": entry.Date,
|
||||
"subject": entry.Subject,
|
||||
"added": entry.Added,
|
||||
"removed": entry.Removed,
|
||||
})
|
||||
}
|
||||
writeJSONObj(w, map[string]interface{}{ "data": map[string]interface{}{ "history": out } })
|
||||
var out []map[string]interface{}
|
||||
for _, entry := range history {
|
||||
out = append(out, map[string]interface{}{
|
||||
"hash": entry.Hash,
|
||||
"author": entry.Author,
|
||||
"email": entry.Email,
|
||||
"date": entry.Date,
|
||||
"subject": entry.Subject,
|
||||
"added": entry.Added,
|
||||
"removed": entry.Removed,
|
||||
})
|
||||
}
|
||||
writeJSONObj(w, map[string]interface{}{"data": map[string]interface{}{"history": out}})
|
||||
}
|
||||
|
||||
func (s *Server) handleDiff(w http.ResponseWriter, req gqlRequest, sess *auth.Session) {
|
||||
if sess == nil { writeGQLError(w, "UNAUTHORIZED"); return }
|
||||
slug, _ := req.Variables["slug"].(string)
|
||||
fromHash, _ := req.Variables["fromHash"].(string)
|
||||
toHash, _ := req.Variables["toHash"].(string)
|
||||
if sess == nil {
|
||||
writeGQLError(w, "UNAUTHORIZED")
|
||||
return
|
||||
}
|
||||
slug, _ := req.Variables["slug"].(string)
|
||||
fromHash, _ := req.Variables["fromHash"].(string)
|
||||
toHash, _ := req.Variables["toHash"].(string)
|
||||
|
||||
s.mu.RLock()
|
||||
repo := s.gitRepo
|
||||
s.mu.RUnlock()
|
||||
s.mu.RLock()
|
||||
repo := s.gitRepo
|
||||
s.mu.RUnlock()
|
||||
|
||||
if repo == nil { writeGQLError(w, "git not initialized"); return }
|
||||
if repo == nil {
|
||||
writeGQLError(w, "git not initialized")
|
||||
return
|
||||
}
|
||||
|
||||
diff, err := repo.Diff(slug + ".adoc", fromHash, toHash)
|
||||
if err != nil { writeGQLError(w, err.Error()); return }
|
||||
diff, err := repo.Diff(slug+".adoc", fromHash, toHash)
|
||||
if err != nil {
|
||||
writeGQLError(w, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
writeJSONObj(w, map[string]interface{}{ "data": map[string]interface{}{ "diff": diff } })
|
||||
writeJSONObj(w, map[string]interface{}{"data": map[string]interface{}{"diff": diff}})
|
||||
}
|
||||
|
||||
func (s *Server) handleDocumentAtCommit(w http.ResponseWriter, req gqlRequest, sess *auth.Session) {
|
||||
if sess == nil { writeGQLError(w, "UNAUTHORIZED"); return }
|
||||
slug, _ := req.Variables["slug"].(string)
|
||||
hash, _ := req.Variables["hash"].(string)
|
||||
if sess == nil {
|
||||
writeGQLError(w, "UNAUTHORIZED")
|
||||
return
|
||||
}
|
||||
slug, _ := req.Variables["slug"].(string)
|
||||
hash, _ := req.Variables["hash"].(string)
|
||||
|
||||
s.mu.RLock()
|
||||
repo := s.gitRepo
|
||||
s.mu.RUnlock()
|
||||
s.mu.RLock()
|
||||
repo := s.gitRepo
|
||||
s.mu.RUnlock()
|
||||
|
||||
if repo == nil { writeGQLError(w, "git not initialized"); return }
|
||||
if repo == nil {
|
||||
writeGQLError(w, "git not initialized")
|
||||
return
|
||||
}
|
||||
|
||||
content, err := repo.Show(hash, slug + ".adoc")
|
||||
if err != nil { writeGQLError(w, err.Error()); return }
|
||||
content, err := repo.Show(hash, slug+".adoc")
|
||||
if err != nil {
|
||||
writeGQLError(w, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
writeJSONObj(w, map[string]interface{}{ "data": map[string]interface{}{ "documentAtCommit": content } })
|
||||
writeJSONObj(w, map[string]interface{}{"data": map[string]interface{}{"documentAtCommit": content}})
|
||||
}
|
||||
|
||||
func (s *Server) handleCreateFolder(w http.ResponseWriter, req gqlRequest, sess *auth.Session) {
|
||||
if sess == nil {
|
||||
writeGQLError(w, "UNAUTHORIZED")
|
||||
return
|
||||
}
|
||||
|
||||
path, _ := req.Variables["path"].(string)
|
||||
if path == "" {
|
||||
writeGQLError(w, "path is required")
|
||||
return
|
||||
}
|
||||
|
||||
s.mu.RLock()
|
||||
store := s.store
|
||||
s.mu.RUnlock()
|
||||
|
||||
if store == nil {
|
||||
writeGQLError(w, "storage not initialised")
|
||||
return
|
||||
}
|
||||
|
||||
if err := store.CreateFolder(path); err != nil {
|
||||
log.Printf("[storage] failed to create folder %q: %v", path, err)
|
||||
writeGQLError(w, fmt.Sprintf("failed to create folder: %v", err))
|
||||
return
|
||||
}
|
||||
|
||||
log.Printf("[storage] folder created at %q by %s", path, sess.Username)
|
||||
writeJSONObj(w, map[string]interface{}{"data": map[string]interface{}{"createFolder": true}})
|
||||
}
|
||||
|
||||
func (s *Server) handleMoveDocument(w http.ResponseWriter, req gqlRequest, sess *auth.Session) {
|
||||
if sess == nil {
|
||||
writeGQLError(w, "UNAUTHORIZED")
|
||||
return
|
||||
}
|
||||
|
||||
oldSlug, _ := req.Variables["oldSlug"].(string)
|
||||
newSlug, _ := req.Variables["newSlug"].(string)
|
||||
|
||||
if oldSlug == "" {
|
||||
writeGQLError(w, "oldSlug is required")
|
||||
return
|
||||
}
|
||||
if newSlug == "" {
|
||||
writeGQLError(w, "newSlug is required")
|
||||
return
|
||||
}
|
||||
|
||||
s.mu.RLock()
|
||||
store := s.store
|
||||
repo := s.gitRepo
|
||||
s.mu.RUnlock()
|
||||
|
||||
if store == nil {
|
||||
writeGQLError(w, "storage not initialised")
|
||||
return
|
||||
}
|
||||
|
||||
var err error
|
||||
if repo != nil {
|
||||
// Attempt to use git mv
|
||||
dir := filepath.ToSlash(filepath.Dir(newSlug))
|
||||
if dir != "." && dir != "" {
|
||||
_ = store.CreateFolder(dir)
|
||||
}
|
||||
err = repo.Move(oldSlug+".adoc", newSlug+".adoc", "Move "+oldSlug+" to "+newSlug, sess.Username, "")
|
||||
if err != nil {
|
||||
err = store.MoveDocument(oldSlug, newSlug)
|
||||
}
|
||||
} else {
|
||||
err = store.MoveDocument(oldSlug, newSlug)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
log.Printf("[storage] failed to move document from %q to %q: %v", oldSlug, newSlug, err)
|
||||
writeGQLError(w, fmt.Sprintf("failed to move document: %v", err))
|
||||
return
|
||||
}
|
||||
|
||||
log.Printf("[storage] document moved from %q to %q by %s", oldSlug, newSlug, sess.Username)
|
||||
writeJSONObj(w, map[string]interface{}{"data": map[string]interface{}{"moveDocument": true}})
|
||||
}
|
||||
|
||||
func (s *Server) handleUpload(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method != http.MethodPost {
|
||||
http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
|
||||
return
|
||||
}
|
||||
s.mu.RLock()
|
||||
am := s.authMgr
|
||||
store := s.store
|
||||
s.mu.RUnlock()
|
||||
|
||||
// Check auth
|
||||
authHeader := r.Header.Get("Authorization")
|
||||
token := strings.TrimPrefix(authHeader, "Bearer ")
|
||||
if token == "" || am == nil {
|
||||
http.Error(w, "Unauthorized", http.StatusUnauthorized)
|
||||
return
|
||||
}
|
||||
sess, errLogin := am.Validate(token)
|
||||
if errLogin != nil || sess == nil {
|
||||
http.Error(w, "Unauthorized", http.StatusUnauthorized)
|
||||
return
|
||||
}
|
||||
|
||||
err := r.ParseMultipartForm(10 << 20) // 10 MB
|
||||
if err != nil {
|
||||
http.Error(w, "Failed to parse form", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
slug := r.FormValue("slug")
|
||||
if slug == "" {
|
||||
http.Error(w, "Missing slug", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
file, header, err := r.FormFile("image")
|
||||
if err != nil {
|
||||
http.Error(w, "Missing image", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
defer file.Close()
|
||||
|
||||
data, err := io.ReadAll(file)
|
||||
if err != nil {
|
||||
http.Error(w, "Failed to read image", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
if err := store.SaveImage(slug, header.Filename, data); err != nil {
|
||||
http.Error(w, "Failed to save image", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
// Calculate url
|
||||
relPath := strings.TrimPrefix(filepath.ToSlash(filepath.Join(filepath.Dir(slug), header.Filename)), "/")
|
||||
url := "/media/" + relPath
|
||||
|
||||
resp := map[string]interface{}{"url": url, "name": header.Filename, "size": len(data)}
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
json.NewEncoder(w).Encode(resp)
|
||||
}
|
||||
|
||||
func (s *Server) handleMedia(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method != http.MethodGet {
|
||||
http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
|
||||
return
|
||||
}
|
||||
s.mu.RLock()
|
||||
store := s.store
|
||||
s.mu.RUnlock()
|
||||
|
||||
if store == nil {
|
||||
http.Error(w, "Storage not initialized", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
relPath := strings.TrimPrefix(r.URL.Path, "/media/")
|
||||
fullPath, err := store.GetImagePath(relPath)
|
||||
if err != nil {
|
||||
http.Error(w, "Invalid path", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
http.ServeFile(w, r, fullPath)
|
||||
}
|
||||
func (s *Server) handleFolders(w http.ResponseWriter, sess *auth.Session) {
|
||||
if sess == nil {
|
||||
writeGQLError(w, "UNAUTHORIZED")
|
||||
return
|
||||
}
|
||||
|
||||
s.mu.RLock()
|
||||
store := s.store
|
||||
s.mu.RUnlock()
|
||||
|
||||
if store == nil {
|
||||
writeGQLError(w, "storage not initialised")
|
||||
return
|
||||
}
|
||||
|
||||
folders, err := store.ListFolders()
|
||||
if err != nil {
|
||||
log.Printf("[folders] failed to list folders: %v", err)
|
||||
writeGQLError(w, fmt.Sprintf("failed to list folders: %v", err))
|
||||
return
|
||||
}
|
||||
|
||||
log.Printf("[folders] user %q listed folders (found %d folders)", sess.Username, len(folders))
|
||||
|
||||
writeJSONObj(w, map[string]interface{}{
|
||||
"data": map[string]interface{}{
|
||||
"folders": folders,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
func (s *Server) handleImages(w http.ResponseWriter, req gqlRequest, sess *auth.Session) {
|
||||
if sess == nil {
|
||||
writeGQLError(w, "UNAUTHORIZED")
|
||||
return
|
||||
}
|
||||
slug, _ := req.Variables["slug"].(string)
|
||||
if slug == "" {
|
||||
writeGQLError(w, "slug is required")
|
||||
return
|
||||
}
|
||||
|
||||
s.mu.RLock()
|
||||
store := s.store
|
||||
s.mu.RUnlock()
|
||||
|
||||
if store == nil {
|
||||
writeGQLError(w, "storage not initialized")
|
||||
return
|
||||
}
|
||||
|
||||
images, err := store.ListImages(slug)
|
||||
if err != nil {
|
||||
writeGQLError(w, fmt.Sprintf("failed to list images: %v", err))
|
||||
return
|
||||
}
|
||||
|
||||
writeJSONObj(w, map[string]interface{}{"data": map[string]interface{}{"images": images}})
|
||||
}
|
||||
|
||||
func (s *Server) handleDeleteImage(w http.ResponseWriter, req gqlRequest, sess *auth.Session) {
|
||||
if sess == nil {
|
||||
writeGQLError(w, "UNAUTHORIZED")
|
||||
return
|
||||
}
|
||||
|
||||
slug, _ := req.Variables["slug"].(string)
|
||||
filename, _ := req.Variables["filename"].(string)
|
||||
|
||||
if slug == "" || filename == "" {
|
||||
writeGQLError(w, "slug and filename are required")
|
||||
return
|
||||
}
|
||||
|
||||
s.mu.RLock()
|
||||
store := s.store
|
||||
s.mu.RUnlock()
|
||||
|
||||
if store == nil {
|
||||
writeGQLError(w, "storage not initialized")
|
||||
return
|
||||
}
|
||||
|
||||
if err := store.DeleteImage(slug, filename); err != nil {
|
||||
writeGQLError(w, fmt.Sprintf("failed to delete image: %v", err))
|
||||
return
|
||||
}
|
||||
|
||||
log.Printf("[storage] deleted image %q from %q by %s", filename, slug, sess.Username)
|
||||
writeJSON(w, `{"data":{"deleteImage":true}}`)
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -40,6 +40,26 @@ func (s *Store) Write(slug, content string) error {
|
||||
return os.WriteFile(path, []byte(content), 0644)
|
||||
}
|
||||
|
||||
// CreateFolder creates a new folder/directory.
|
||||
func (s *Store) CreateFolder(folderSlug string) error {
|
||||
path := s.folderPath(folderSlug)
|
||||
return os.MkdirAll(path, 0755)
|
||||
}
|
||||
|
||||
// MoveDocument moves a document from one location to another.
|
||||
func (s *Store) MoveDocument(fromSlug, toSlug string) error {
|
||||
fromPath := s.filePath(fromSlug)
|
||||
toPath := s.filePath(toSlug)
|
||||
|
||||
// Ensure target directory exists
|
||||
if err := os.MkdirAll(filepath.Dir(toPath), 0755); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Move the file
|
||||
return os.Rename(fromPath, toPath)
|
||||
}
|
||||
|
||||
// Delete removes a document from disk.
|
||||
func (s *Store) Delete(slug string) error {
|
||||
return os.Remove(s.filePath(slug))
|
||||
@@ -63,8 +83,91 @@ func (s *Store) List(prefix string) ([]string, error) {
|
||||
return slugs, err
|
||||
}
|
||||
|
||||
// ListImages returns all images in the directory of a given slug.
|
||||
func (s *Store) ListImages(slug string) ([]map[string]interface{}, error) {
|
||||
dir := filepath.Dir(s.filePath(slug))
|
||||
var images []map[string]interface{}
|
||||
|
||||
entries, err := os.ReadDir(dir)
|
||||
if err != nil {
|
||||
if errors.Is(err, os.ErrNotExist) {
|
||||
return images, nil
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for _, entry := range entries {
|
||||
if entry.IsDir() {
|
||||
continue
|
||||
}
|
||||
name := entry.Name()
|
||||
ext := strings.ToLower(filepath.Ext(name))
|
||||
if ext == ".png" || ext == ".jpg" || ext == ".jpeg" || ext == ".gif" || ext == ".svg" || ext == ".webp" {
|
||||
info, err := entry.Info()
|
||||
if err == nil {
|
||||
images = append(images, map[string]interface{}{
|
||||
"name": name,
|
||||
"size": info.Size(),
|
||||
// The URL is relative to the media endpoint mapped in the server.
|
||||
"url": "/media/" + strings.TrimPrefix(filepath.ToSlash(filepath.Join(filepath.Dir(slug), name)), "/"),
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
return images, nil
|
||||
}
|
||||
|
||||
// SaveImage saves an uploaded image file into the directory of a slug.
|
||||
func (s *Store) SaveImage(slug, filename string, data []byte) error {
|
||||
dir := filepath.Dir(s.filePath(slug))
|
||||
if err := os.MkdirAll(dir, 0755); err != nil {
|
||||
return err
|
||||
}
|
||||
return os.WriteFile(filepath.Join(dir, filename), data, 0644)
|
||||
}
|
||||
|
||||
// DeleteImage removes an image file from the directory of a slug.
|
||||
func (s *Store) DeleteImage(slug, filename string) error {
|
||||
dir := filepath.Dir(s.filePath(slug))
|
||||
return os.Remove(filepath.Join(dir, filename))
|
||||
}
|
||||
|
||||
// GetImagePath returns the absolute path on disk for a media file.
|
||||
// It prevents directory traversal out of the root storage path.
|
||||
func (s *Store) GetImagePath(relPath string) (string, error) {
|
||||
if strings.Contains(relPath, "..") {
|
||||
return "", errors.New("invalid path")
|
||||
}
|
||||
return filepath.Join(s.root, filepath.FromSlash(relPath)), nil
|
||||
}
|
||||
|
||||
// ListFolders returns all folder paths relative to root, excluding .git.
|
||||
func (s *Store) ListFolders() ([]string, error) {
|
||||
var folders []string
|
||||
err := filepath.WalkDir(s.root, func(path string, d os.DirEntry, err error) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if d.IsDir() {
|
||||
if d.Name() == ".git" {
|
||||
return filepath.SkipDir
|
||||
}
|
||||
if path != s.root {
|
||||
rel, _ := filepath.Rel(s.root, path)
|
||||
folders = append(folders, filepath.ToSlash(rel))
|
||||
}
|
||||
}
|
||||
return nil
|
||||
})
|
||||
return folders, err
|
||||
}
|
||||
|
||||
func (s *Store) filePath(slug string) string {
|
||||
return filepath.Join(s.root, filepath.FromSlash(slug)+".adoc")
|
||||
}
|
||||
|
||||
func (s *Store) folderPath(folderSlug string) string {
|
||||
return filepath.Join(s.root, filepath.FromSlash(folderSlug))
|
||||
}
|
||||
|
||||
var ErrNotFound = errors.New("document not found")
|
||||
|
||||
86
frontend/package-lock.json
generated
86
frontend/package-lock.json
generated
@@ -8,6 +8,7 @@
|
||||
"name": "archivum",
|
||||
"version": "0.1.0",
|
||||
"dependencies": {
|
||||
"@tailwindcss/typography": "^0.5.19",
|
||||
"@tiptap/core": "^2.4.0",
|
||||
"@tiptap/extension-blockquote": "^2.27.2",
|
||||
"@tiptap/extension-bold": "^2.27.2",
|
||||
@@ -54,7 +55,6 @@
|
||||
"version": "5.2.0",
|
||||
"resolved": "https://registry.npmjs.org/@alloc/quick-lru/-/quick-lru-5.2.0.tgz",
|
||||
"integrity": "sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
@@ -2219,7 +2219,6 @@
|
||||
"version": "0.3.13",
|
||||
"resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz",
|
||||
"integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@jridgewell/sourcemap-codec": "^1.5.0",
|
||||
@@ -2241,7 +2240,6 @@
|
||||
"version": "3.1.2",
|
||||
"resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz",
|
||||
"integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=6.0.0"
|
||||
@@ -2268,7 +2266,6 @@
|
||||
"version": "0.3.31",
|
||||
"resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz",
|
||||
"integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@jridgewell/resolve-uri": "^3.1.0",
|
||||
@@ -2356,7 +2353,6 @@
|
||||
"version": "2.1.5",
|
||||
"resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz",
|
||||
"integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@nodelib/fs.stat": "2.0.5",
|
||||
@@ -2370,7 +2366,6 @@
|
||||
"version": "2.0.5",
|
||||
"resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz",
|
||||
"integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 8"
|
||||
@@ -2380,7 +2375,6 @@
|
||||
"version": "1.2.8",
|
||||
"resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz",
|
||||
"integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@nodelib/fs.scandir": "2.1.5",
|
||||
@@ -2863,6 +2857,31 @@
|
||||
"sourcemap-codec": "^1.4.8"
|
||||
}
|
||||
},
|
||||
"node_modules/@tailwindcss/typography": {
|
||||
"version": "0.5.19",
|
||||
"resolved": "https://registry.npmjs.org/@tailwindcss/typography/-/typography-0.5.19.tgz",
|
||||
"integrity": "sha512-w31dd8HOx3k9vPtcQh5QHP9GwKcgbMp87j58qi6xgiBnFFtKEAgCWnDw4qUT8aHwkCp8bKvb/KGKWWHedP0AAg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"postcss-selector-parser": "6.0.10"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"tailwindcss": ">=3.0.0 || insiders || >=4.0.0-alpha.20 || >=4.0.0-beta.1"
|
||||
}
|
||||
},
|
||||
"node_modules/@tailwindcss/typography/node_modules/postcss-selector-parser": {
|
||||
"version": "6.0.10",
|
||||
"resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.0.10.tgz",
|
||||
"integrity": "sha512-IQ7TZdoaqbT+LCpShg46jnZVlhWD2w6iQYAcYXfHARZ7X1t/UGhhceQDs5X0cGqKvYlHNOuv7Oa1xmb0oQuA3w==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"cssesc": "^3.0.0",
|
||||
"util-deprecate": "^1.0.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=4"
|
||||
}
|
||||
},
|
||||
"node_modules/@tiptap/core": {
|
||||
"version": "2.27.2",
|
||||
"resolved": "https://registry.npmjs.org/@tiptap/core/-/core-2.27.2.tgz",
|
||||
@@ -3614,14 +3633,12 @@
|
||||
"version": "1.3.0",
|
||||
"resolved": "https://registry.npmjs.org/any-promise/-/any-promise-1.3.0.tgz",
|
||||
"integrity": "sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/anymatch": {
|
||||
"version": "3.1.3",
|
||||
"resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz",
|
||||
"integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==",
|
||||
"devOptional": true,
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"normalize-path": "^3.0.0",
|
||||
@@ -3635,7 +3652,6 @@
|
||||
"version": "5.0.2",
|
||||
"resolved": "https://registry.npmjs.org/arg/-/arg-5.0.2.tgz",
|
||||
"integrity": "sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/argparse": {
|
||||
@@ -3873,7 +3889,6 @@
|
||||
"version": "2.3.0",
|
||||
"resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz",
|
||||
"integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==",
|
||||
"devOptional": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
@@ -3895,7 +3910,6 @@
|
||||
"version": "3.0.3",
|
||||
"resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz",
|
||||
"integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==",
|
||||
"devOptional": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"fill-range": "^7.1.1"
|
||||
@@ -3997,7 +4011,6 @@
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/camelcase-css/-/camelcase-css-2.0.1.tgz",
|
||||
"integrity": "sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 6"
|
||||
@@ -4037,7 +4050,6 @@
|
||||
"version": "3.6.0",
|
||||
"resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz",
|
||||
"integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==",
|
||||
"devOptional": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"anymatch": "~3.1.2",
|
||||
@@ -4062,7 +4074,6 @@
|
||||
"version": "5.1.2",
|
||||
"resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz",
|
||||
"integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==",
|
||||
"devOptional": true,
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"is-glob": "^4.0.1"
|
||||
@@ -4221,7 +4232,6 @@
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz",
|
||||
"integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"bin": {
|
||||
"cssesc": "bin/cssesc"
|
||||
@@ -4365,14 +4375,12 @@
|
||||
"version": "1.2.2",
|
||||
"resolved": "https://registry.npmjs.org/didyoumean/-/didyoumean-1.2.2.tgz",
|
||||
"integrity": "sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==",
|
||||
"dev": true,
|
||||
"license": "Apache-2.0"
|
||||
},
|
||||
"node_modules/dlv": {
|
||||
"version": "1.1.3",
|
||||
"resolved": "https://registry.npmjs.org/dlv/-/dlv-1.1.3.tgz",
|
||||
"integrity": "sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/doctypes": {
|
||||
@@ -4655,7 +4663,6 @@
|
||||
"version": "3.3.3",
|
||||
"resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz",
|
||||
"integrity": "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@nodelib/fs.stat": "^2.0.2",
|
||||
@@ -4672,7 +4679,6 @@
|
||||
"version": "5.1.2",
|
||||
"resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz",
|
||||
"integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==",
|
||||
"dev": true,
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"is-glob": "^4.0.1"
|
||||
@@ -4709,7 +4715,6 @@
|
||||
"version": "1.20.1",
|
||||
"resolved": "https://registry.npmjs.org/fastq/-/fastq-1.20.1.tgz",
|
||||
"integrity": "sha512-GGToxJ/w1x32s/D2EKND7kTil4n8OVk/9mycTc4VDza13lOvpUZTGX3mFSCtV9ksdGBVzvsyAVLM6mHFThxXxw==",
|
||||
"dev": true,
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"reusify": "^1.0.4"
|
||||
@@ -4728,7 +4733,6 @@
|
||||
"version": "7.1.1",
|
||||
"resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz",
|
||||
"integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==",
|
||||
"devOptional": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"to-regex-range": "^5.0.1"
|
||||
@@ -4975,7 +4979,6 @@
|
||||
"version": "6.0.2",
|
||||
"resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz",
|
||||
"integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==",
|
||||
"dev": true,
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"is-glob": "^4.0.3"
|
||||
@@ -5251,7 +5254,6 @@
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz",
|
||||
"integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==",
|
||||
"devOptional": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"binary-extensions": "^2.0.0"
|
||||
@@ -5354,7 +5356,6 @@
|
||||
"version": "2.1.1",
|
||||
"resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz",
|
||||
"integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==",
|
||||
"devOptional": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
@@ -5409,7 +5410,6 @@
|
||||
"version": "4.0.3",
|
||||
"resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz",
|
||||
"integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==",
|
||||
"devOptional": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"is-extglob": "^2.1.1"
|
||||
@@ -5455,7 +5455,6 @@
|
||||
"version": "7.0.0",
|
||||
"resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz",
|
||||
"integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==",
|
||||
"devOptional": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=0.12.0"
|
||||
@@ -5712,7 +5711,6 @@
|
||||
"version": "1.21.7",
|
||||
"resolved": "https://registry.npmjs.org/jiti/-/jiti-1.21.7.tgz",
|
||||
"integrity": "sha512-/imKNG4EbWNrVjoNC/1H5/9GFy+tqjGBHCaSsN+P2RnPqjsLmv6UD3Ej+Kj8nBWaRAwyk7kK5ZUc+OEatnTR3A==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"bin": {
|
||||
"jiti": "bin/jiti.js"
|
||||
@@ -5811,7 +5809,6 @@
|
||||
"version": "3.1.3",
|
||||
"resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-3.1.3.tgz",
|
||||
"integrity": "sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=14"
|
||||
@@ -5824,7 +5821,6 @@
|
||||
"version": "1.2.4",
|
||||
"resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz",
|
||||
"integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/linkify-it": {
|
||||
@@ -5918,7 +5914,6 @@
|
||||
"version": "1.4.1",
|
||||
"resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz",
|
||||
"integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 8"
|
||||
@@ -5928,7 +5923,6 @@
|
||||
"version": "4.0.8",
|
||||
"resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz",
|
||||
"integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"braces": "^3.0.3",
|
||||
@@ -5987,7 +5981,6 @@
|
||||
"version": "2.7.0",
|
||||
"resolved": "https://registry.npmjs.org/mz/-/mz-2.7.0.tgz",
|
||||
"integrity": "sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"any-promise": "^1.0.0",
|
||||
@@ -6050,7 +6043,6 @@
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz",
|
||||
"integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==",
|
||||
"devOptional": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
@@ -6094,7 +6086,6 @@
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/object-hash/-/object-hash-3.0.0.tgz",
|
||||
"integrity": "sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 6"
|
||||
@@ -6244,7 +6235,6 @@
|
||||
"version": "2.3.2",
|
||||
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.2.tgz",
|
||||
"integrity": "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==",
|
||||
"devOptional": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=8.6"
|
||||
@@ -6257,7 +6247,6 @@
|
||||
"version": "2.3.0",
|
||||
"resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz",
|
||||
"integrity": "sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
@@ -6289,7 +6278,6 @@
|
||||
"version": "4.0.7",
|
||||
"resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.7.tgz",
|
||||
"integrity": "sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 6"
|
||||
@@ -6337,7 +6325,6 @@
|
||||
"version": "15.1.0",
|
||||
"resolved": "https://registry.npmjs.org/postcss-import/-/postcss-import-15.1.0.tgz",
|
||||
"integrity": "sha512-hpr+J05B2FVYUAXHeK1YyI267J/dDDhMU6B6civm8hSY1jYJnBXxzKDKDswzJmtLHryrjhnDjqqp/49t8FALew==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"postcss-value-parser": "^4.0.0",
|
||||
@@ -6355,7 +6342,6 @@
|
||||
"version": "4.1.0",
|
||||
"resolved": "https://registry.npmjs.org/postcss-js/-/postcss-js-4.1.0.tgz",
|
||||
"integrity": "sha512-oIAOTqgIo7q2EOwbhb8UalYePMvYoIeRY2YKntdpFQXNosSu3vLrniGgmH9OKs/qAkfoj5oB3le/7mINW1LCfw==",
|
||||
"dev": true,
|
||||
"funding": [
|
||||
{
|
||||
"type": "opencollective",
|
||||
@@ -6381,7 +6367,6 @@
|
||||
"version": "6.0.1",
|
||||
"resolved": "https://registry.npmjs.org/postcss-load-config/-/postcss-load-config-6.0.1.tgz",
|
||||
"integrity": "sha512-oPtTM4oerL+UXmx+93ytZVN82RrlY/wPUV8IeDxFrzIjXOLF1pN+EmKPLbubvKHT2HC20xXsCAH2Z+CKV6Oz/g==",
|
||||
"dev": true,
|
||||
"funding": [
|
||||
{
|
||||
"type": "opencollective",
|
||||
@@ -6424,7 +6409,6 @@
|
||||
"version": "6.2.0",
|
||||
"resolved": "https://registry.npmjs.org/postcss-nested/-/postcss-nested-6.2.0.tgz",
|
||||
"integrity": "sha512-HQbt28KulC5AJzG+cZtj9kvKB93CFCdLvog1WFLf1D+xmMvPGlBstkpTEZfK5+AN9hfJocyBFCNiqyS48bpgzQ==",
|
||||
"dev": true,
|
||||
"funding": [
|
||||
{
|
||||
"type": "opencollective",
|
||||
@@ -6450,7 +6434,6 @@
|
||||
"version": "6.1.2",
|
||||
"resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.1.2.tgz",
|
||||
"integrity": "sha512-Q8qQfPiZ+THO/3ZrOrO0cJJKfpYCagtMUkXbnEfmgUjwXg6z/WBeOyS9APBBPCTSiDV+s4SwQGu8yFsiMRIudg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"cssesc": "^3.0.0",
|
||||
@@ -6464,7 +6447,6 @@
|
||||
"version": "4.2.0",
|
||||
"resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz",
|
||||
"integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/pretty-bytes": {
|
||||
@@ -6831,7 +6813,6 @@
|
||||
"version": "1.2.3",
|
||||
"resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz",
|
||||
"integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==",
|
||||
"dev": true,
|
||||
"funding": [
|
||||
{
|
||||
"type": "github",
|
||||
@@ -6862,7 +6843,6 @@
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz",
|
||||
"integrity": "sha512-Owdv/Ft7IjOgm/i0xvNDZ1LrRANRfew4b2prF3OWMQLxLfu3bS8FVhCsrSCMK4lR56Y9ya+AThoTpDCTxCmpRA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"pify": "^2.3.0"
|
||||
@@ -6872,7 +6852,6 @@
|
||||
"version": "3.6.0",
|
||||
"resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz",
|
||||
"integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==",
|
||||
"devOptional": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"picomatch": "^2.2.1"
|
||||
@@ -7026,7 +7005,6 @@
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz",
|
||||
"integrity": "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"iojs": ">=1.0.0",
|
||||
@@ -7088,7 +7066,6 @@
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz",
|
||||
"integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==",
|
||||
"dev": true,
|
||||
"funding": [
|
||||
{
|
||||
"type": "github",
|
||||
@@ -7574,7 +7551,6 @@
|
||||
"version": "3.35.1",
|
||||
"resolved": "https://registry.npmjs.org/sucrase/-/sucrase-3.35.1.tgz",
|
||||
"integrity": "sha512-DhuTmvZWux4H1UOnWMB3sk0sbaCVOoQZjv8u1rDoTV0HTdGem9hkAZtl4JZy8P2z4Bg0nT+YMeOFyVr4zcG5Tw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@jridgewell/gen-mapping": "^0.3.2",
|
||||
@@ -7597,7 +7573,6 @@
|
||||
"version": "4.1.1",
|
||||
"resolved": "https://registry.npmjs.org/commander/-/commander-4.1.1.tgz",
|
||||
"integrity": "sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 6"
|
||||
@@ -7619,7 +7594,6 @@
|
||||
"version": "3.4.19",
|
||||
"resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.19.tgz",
|
||||
"integrity": "sha512-3ofp+LL8E+pK/JuPLPggVAIaEuhvIz4qNcf3nA1Xn2o/7fb7s/TYpHhwGDv1ZU3PkBluUVaF8PyCHcm48cKLWQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@alloc/quick-lru": "^5.2.0",
|
||||
@@ -7725,7 +7699,6 @@
|
||||
"version": "3.3.1",
|
||||
"resolved": "https://registry.npmjs.org/thenify/-/thenify-3.3.1.tgz",
|
||||
"integrity": "sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"any-promise": "^1.0.0"
|
||||
@@ -7735,7 +7708,6 @@
|
||||
"version": "1.6.0",
|
||||
"resolved": "https://registry.npmjs.org/thenify-all/-/thenify-all-1.6.0.tgz",
|
||||
"integrity": "sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"thenify": ">= 3.1.0 < 4"
|
||||
@@ -7748,7 +7720,6 @@
|
||||
"version": "0.2.16",
|
||||
"resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.16.tgz",
|
||||
"integrity": "sha512-pn99VhoACYR8nFHhxqix+uvsbXineAasWm5ojXoN8xEwK5Kd3/TrhNn1wByuD52UxWRLy8pu+kRMniEi6Eq9Zg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"fdir": "^6.5.0",
|
||||
@@ -7765,7 +7736,6 @@
|
||||
"version": "6.5.0",
|
||||
"resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz",
|
||||
"integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=12.0.0"
|
||||
@@ -7783,7 +7753,6 @@
|
||||
"version": "4.0.4",
|
||||
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz",
|
||||
"integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
@@ -7805,7 +7774,6 @@
|
||||
"version": "5.0.1",
|
||||
"resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz",
|
||||
"integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==",
|
||||
"devOptional": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"is-number": "^7.0.0"
|
||||
@@ -7830,7 +7798,6 @@
|
||||
"version": "0.1.13",
|
||||
"resolved": "https://registry.npmjs.org/ts-interface-checker/-/ts-interface-checker-0.1.13.tgz",
|
||||
"integrity": "sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==",
|
||||
"dev": true,
|
||||
"license": "Apache-2.0"
|
||||
},
|
||||
"node_modules/type-fest": {
|
||||
@@ -8105,7 +8072,6 @@
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
|
||||
"integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/vite": {
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
"type-check": "vue-tsc --noEmit"
|
||||
},
|
||||
"dependencies": {
|
||||
"@tailwindcss/typography": "^0.5.19",
|
||||
"@tiptap/core": "^2.4.0",
|
||||
"@tiptap/extension-blockquote": "^2.27.2",
|
||||
"@tiptap/extension-bold": "^2.27.2",
|
||||
|
||||
@@ -14,15 +14,15 @@ const emit = defineEmits<{
|
||||
|
||||
const open = ref(false)
|
||||
const currentPath = ref(props.modelValue)
|
||||
const directories = ref<{ name: string; path: string }[]>([])
|
||||
const directories = ref<{ name: string; path: string; isGitRepo: boolean; hasDocuments: boolean }[]>([])
|
||||
const loading = ref(false)
|
||||
|
||||
async function fetchDirectories(path: string) {
|
||||
loading.value = true
|
||||
try {
|
||||
const data = await gql<{ serverDirectories: { name: string; path: string }[] }>(
|
||||
`query Dirs($p: String) { serverDirectories(path: $p) { name path } }`,
|
||||
{ p: path || null }
|
||||
const data = await gql<{ serverDirectories: { name: string; path: string; isGitRepo: boolean; hasDocuments: boolean }[] }>(
|
||||
`query Dirs($path: String) { serverDirectories(path: $path) { name path isGitRepo hasDocuments } }`,
|
||||
{ path: path || null }
|
||||
)
|
||||
directories.value = data.serverDirectories
|
||||
// Update input box to the fetched path if we passed empty and it resolved to root
|
||||
@@ -109,8 +109,10 @@ watch(() => props.modelValue, (val) => {
|
||||
class="w-fulltext-left flex items-center gap-2 px-3 py-2 w-full hover:bg-accent-50 dark:hover:bg-accent-500/10 focus:bg-accent-50 focus:outline-none transition group"
|
||||
@click="navigate(dir.path)"
|
||||
>
|
||||
<svg class="w-4 h-4 text-accent-500 group-hover:text-accent-600" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3 7v10a2 2 0 002 2h14a2 2 0 002-2V9a2 2 0 00-2-2h-6l-2-2H5a2 2 0 00-2 2z"/></svg>
|
||||
<svg class="w-4 h-4 text-accent-500 group-hover:text-accent-600 shrink-0" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3 7v10a2 2 0 002 2h14a2 2 0 002-2V9a2 2 0 00-2-2h-6l-2-2H5a2 2 0 00-2 2z"/></svg>
|
||||
<span class="text-sm font-medium text-slate-700 dark:text-slate-300 truncate">{{ dir.name }}</span>
|
||||
<span v-if="dir.hasDocuments" class="ml-auto text-[10px] font-bold px-1.5 py-0.5 rounded bg-blue-100 text-blue-700 dark:bg-blue-900/30 dark:text-blue-400 shrink-0">docs</span>
|
||||
<span v-if="dir.isGitRepo" :class="dir.hasDocuments ? 'ml-1' : 'ml-auto'" class="text-[10px] font-bold px-1.5 py-0.5 rounded bg-amber-100 text-amber-700 dark:bg-amber-900/30 dark:text-amber-400 shrink-0">git</span>
|
||||
</button>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<script setup lang="ts">
|
||||
import { ref, reactive, onMounted, computed } from 'vue'
|
||||
import { ref, reactive, onMounted, computed, watch } from 'vue'
|
||||
import { useRouter, useRoute } from 'vue-router'
|
||||
import { gql } from '@/lib/gql'
|
||||
import { gql, createFolder, moveDocument } from '@/lib/gql'
|
||||
|
||||
const emit = defineEmits<{ close: [] }>()
|
||||
|
||||
@@ -23,9 +23,32 @@ const loading = ref(true)
|
||||
const router = useRouter()
|
||||
const route = useRoute()
|
||||
const openFolders = reactive<Record<string, boolean>>({})
|
||||
const allFolders = ref<string[]>([])
|
||||
const draggedItem = ref<{ type: 'folder' | 'doc'; path: string } | null>(null)
|
||||
const dropTarget = ref<string | null>(null)
|
||||
const showCreateMenu = reactive<Record<string, boolean>>({})
|
||||
|
||||
function buildTree(docs: DocMeta[]): TreeFolder {
|
||||
watch(() => route.fullPath, () => {
|
||||
for (const key in showCreateMenu) {
|
||||
showCreateMenu[key] = false
|
||||
}
|
||||
})
|
||||
|
||||
function buildTree(docs: DocMeta[], folders: string[]): TreeFolder {
|
||||
const root: TreeFolder = { name: '', path: '', folders: [], docs: [] }
|
||||
for (const f of folders) {
|
||||
const parts = f.split('/')
|
||||
let node = root
|
||||
for (let i = 0; i < parts.length; i++) {
|
||||
const folderPath = parts.slice(0, i + 1).join('/')
|
||||
let child = node.folders.find(c => c.path === folderPath)
|
||||
if (!child) {
|
||||
child = { name: parts[i], path: folderPath, folders: [], docs: [] }
|
||||
node.folders.push(child)
|
||||
}
|
||||
node = child
|
||||
}
|
||||
}
|
||||
for (const doc of docs) {
|
||||
const parts = doc.slug.split('/')
|
||||
if (parts.length === 1) {
|
||||
@@ -47,11 +70,96 @@ function buildTree(docs: DocMeta[]): TreeFolder {
|
||||
return root
|
||||
}
|
||||
|
||||
async function handleCreateRootFolder() {
|
||||
const name = prompt('Enter folder name:')
|
||||
if (!name?.trim()) return
|
||||
try {
|
||||
await createFolder(name)
|
||||
const fData = await gql<{ folders?: string[] }>(`{ folders }`)
|
||||
allFolders.value = fData.folders ?? []
|
||||
} catch (err) {
|
||||
console.error('Failed to create folder:', err)
|
||||
}
|
||||
}
|
||||
|
||||
async function handleCreateFolder(parentPath: string) {
|
||||
const name = prompt('Enter folder name:')
|
||||
if (!name?.trim()) return
|
||||
try {
|
||||
const newPath = parentPath ? `${parentPath}/${name}` : name
|
||||
await createFolder(newPath)
|
||||
const fData = await gql<{ folders?: string[] }>(`{ folders }`)
|
||||
allFolders.value = fData.folders ?? []
|
||||
openFolders[parentPath] = true
|
||||
} catch (err) {
|
||||
console.error('Failed to create folder:', err)
|
||||
}
|
||||
}
|
||||
|
||||
function handleCreateDoc(parentPath: string) {
|
||||
const title = prompt('Enter document title:')
|
||||
if (!title?.trim()) return
|
||||
const slug = parentPath ? `${parentPath}/${title.toLowerCase().replace(/\\s+/g, '-')}` : title.toLowerCase().replace(/\\s+/g, '-')
|
||||
|
||||
// store intended initial slug for the /doc/new page to pick up via state or similar
|
||||
sessionStorage.setItem('newDocSlug', slug)
|
||||
router.push('/doc/new')
|
||||
emit('close')
|
||||
}
|
||||
|
||||
function handleDragStart(e: DragEvent, item: FlatItem) {
|
||||
if (item.type === 'folder') {
|
||||
draggedItem.value = { type: 'folder', path: item.path }
|
||||
} else {
|
||||
draggedItem.value = { type: 'doc', path: item.slug }
|
||||
}
|
||||
e.dataTransfer!.effectAllowed = 'move'
|
||||
}
|
||||
|
||||
function handleDragOver(e: DragEvent, targetPath: string) {
|
||||
e.preventDefault()
|
||||
e.dataTransfer!.dropEffect = 'move'
|
||||
dropTarget.value = targetPath
|
||||
}
|
||||
|
||||
function handleDragLeave() {
|
||||
dropTarget.value = null
|
||||
}
|
||||
|
||||
async function handleDrop(e: DragEvent, targetPath: string) {
|
||||
e.preventDefault()
|
||||
if (!draggedItem.value) return
|
||||
|
||||
try {
|
||||
if (draggedItem.value.type === 'doc') {
|
||||
const parts = draggedItem.value.path.split('/')
|
||||
const docName = parts.pop()
|
||||
const newSlug = targetPath ? `${targetPath}/${docName}` : docName!
|
||||
|
||||
await moveDocument(draggedItem.value.path, newSlug)
|
||||
|
||||
const data = await gql<{ documents?: DocMeta[] }>(`{ documents { slug title } }`)
|
||||
allDocs.value = data.documents ?? []
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('Failed to move item:', err)
|
||||
} finally {
|
||||
draggedItem.value = null
|
||||
dropTarget.value = null
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(async () => {
|
||||
try {
|
||||
const data = await gql<{ documents?: DocMeta[] }>(`{ documents { slug title } }`)
|
||||
allDocs.value = data.documents ?? []
|
||||
const fData = await gql<{ folders?: string[] }>(`{ folders }`)
|
||||
allFolders.value = fData.folders ?? []
|
||||
|
||||
// expand all folders by default
|
||||
for (const f of allFolders.value) {
|
||||
openFolders[f] = true
|
||||
}
|
||||
for (const doc of allDocs.value) {
|
||||
const parts = doc.slug.split('/')
|
||||
for (let i = 1; i < parts.length; i++) {
|
||||
@@ -60,6 +168,7 @@ onMounted(async () => {
|
||||
}
|
||||
} catch {
|
||||
allDocs.value = []
|
||||
allFolders.value = []
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
@@ -67,7 +176,7 @@ onMounted(async () => {
|
||||
|
||||
const flatItems = computed<FlatItem[]>(() => {
|
||||
const items: FlatItem[] = []
|
||||
const tree = buildTree(allDocs.value)
|
||||
const tree = buildTree(allDocs.value, allFolders.value)
|
||||
|
||||
function traverse(node: TreeFolder, depth: number) {
|
||||
for (const folder of node.folders) {
|
||||
@@ -90,6 +199,12 @@ function toggleFolder(path: string) {
|
||||
openFolders[path] = !openFolders[path]
|
||||
}
|
||||
|
||||
function closeAllMenus() {
|
||||
for (const key in showCreateMenu) {
|
||||
showCreateMenu[key] = false
|
||||
}
|
||||
}
|
||||
|
||||
function navigate(slug: string) {
|
||||
router.push(`/doc/${slug}`)
|
||||
emit('close')
|
||||
@@ -104,7 +219,7 @@ function isActive(slug: string) {
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="flex flex-col h-full select-none">
|
||||
<div class="flex flex-col h-full select-none" @click="closeAllMenus">
|
||||
|
||||
<!-- Logo / header -->
|
||||
<div class="flex items-center justify-between px-4 py-4 border-b border-slate-200 dark:border-slate-700/60">
|
||||
@@ -154,35 +269,73 @@ function isActive(slug: string) {
|
||||
<template v-else>
|
||||
<template v-for="item in flatItems" :key="item.type === 'folder' ? 'f:' + item.path : 'd:' + item.slug">
|
||||
<!-- Folder row -->
|
||||
<button
|
||||
<div
|
||||
v-if="item.type === 'folder'"
|
||||
:style="{ paddingLeft: `${0.5 + item.depth * 1}rem` }"
|
||||
class="w-full flex items-center gap-1.5 pr-2 py-1.5 rounded-lg text-sm text-slate-600 dark:text-slate-400 hover:bg-slate-100 dark:hover:bg-slate-700/60 transition"
|
||||
@click="toggleFolder(item.path)"
|
||||
class="flex items-center gap-1 group relative transition-colors duration-200"
|
||||
:class="dropTarget === item.path ? 'bg-accent-500/20 dark:bg-accent-500/30 ring-1 ring-accent-500' : ''"
|
||||
@dragover="handleDragOver($event, item.path)"
|
||||
@dragleave="handleDragLeave"
|
||||
@drop="handleDrop($event, item.path)"
|
||||
draggable="true"
|
||||
@dragstart="handleDragStart($event, item)"
|
||||
>
|
||||
<!-- chevron -->
|
||||
<svg
|
||||
class="w-3.5 h-3.5 flex-shrink-0 transition-transform"
|
||||
:class="openFolders[item.path] ? 'rotate-90' : ''"
|
||||
fill="none" stroke="currentColor" viewBox="0 0 24 24"
|
||||
<button
|
||||
class="flex-1 flex items-center gap-1.5 pr-2 py-1.5 rounded-lg text-sm text-slate-600 dark:text-slate-400 hover:bg-slate-100 dark:hover:bg-slate-700/60 transition"
|
||||
@click="toggleFolder(item.path)"
|
||||
>
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5l7 7-7 7"/>
|
||||
</svg>
|
||||
<!-- folder icon -->
|
||||
<svg class="w-4 h-4 flex-shrink-0 text-amber-500" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
||||
d="M3 7a2 2 0 012-2h4l2 2h8a2 2 0 012 2v9a2 2 0 01-2 2H5a2 2 0 01-2-2V7z"/>
|
||||
</svg>
|
||||
<span class="truncate font-medium">{{ item.name }}</span>
|
||||
</button>
|
||||
<!-- chevron -->
|
||||
<svg
|
||||
class="w-3.5 h-3.5 flex-shrink-0 transition-transform"
|
||||
:class="openFolders[item.path] ? 'rotate-90' : ''"
|
||||
fill="none" stroke="currentColor" viewBox="0 0 24 24"
|
||||
>
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5l7 7-7 7"/>
|
||||
</svg>
|
||||
<!-- folder icon -->
|
||||
<svg class="w-4 h-4 flex-shrink-0 text-amber-500" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
||||
d="M3 7a2 2 0 012-2h4l2 2h8a2 2 0 012 2v9a2 2 0 01-2 2H5a2 2 0 01-2-2V7z"/>
|
||||
</svg>
|
||||
<span class="truncate font-medium">{{ item.name }}</span>
|
||||
</button>
|
||||
<button
|
||||
class="opacity-0 group-hover:opacity-100 transition-opacity p-1 mr-1 rounded hover:bg-slate-200 dark:hover:bg-slate-600"
|
||||
@click.prevent.stop="showCreateMenu[item.path] = !showCreateMenu[item.path]"
|
||||
title="Add into folder"
|
||||
>
|
||||
<svg class="w-4 h-4 text-slate-500" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 4v16m8-8H4"/>
|
||||
</svg>
|
||||
</button>
|
||||
<div
|
||||
v-if="showCreateMenu[item.path]"
|
||||
class="absolute right-0 top-full mt-1 w-32 bg-white dark:bg-slate-800 rounded-lg shadow-lg z-50 border border-slate-200 dark:border-slate-700"
|
||||
>
|
||||
<button
|
||||
@click.stop="handleCreateFolder(item.path); showCreateMenu[item.path] = false"
|
||||
class="w-full text-left px-3 py-2 text-sm hover:bg-slate-100 dark:hover:bg-slate-700 rounded-t-lg"
|
||||
>
|
||||
+ Folder
|
||||
</button>
|
||||
<button
|
||||
@click.stop="handleCreateDoc(item.path); showCreateMenu[item.path] = false"
|
||||
class="w-full text-left px-3 py-2 text-sm hover:bg-slate-100 dark:hover:bg-slate-700 rounded-b-lg"
|
||||
>
|
||||
+ Document
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Document row -->
|
||||
<button
|
||||
v-else
|
||||
draggable="true"
|
||||
@dragstart="handleDragStart($event, item)"
|
||||
:style="{ paddingLeft: `${0.5 + item.depth * 1}rem` }"
|
||||
:class="[
|
||||
'w-full flex items-center gap-1.5 pr-2 py-1.5 rounded-lg text-sm truncate transition',
|
||||
isActive(item.slug)
|
||||
isActive((item as any).slug)
|
||||
? 'bg-accent-500/10 text-accent-600 dark:text-accent-400 font-medium'
|
||||
: 'text-slate-700 dark:text-slate-300 hover:bg-slate-100 dark:hover:bg-slate-700/60'
|
||||
]"
|
||||
@@ -201,9 +354,19 @@ function isActive(slug: string) {
|
||||
|
||||
<!-- Bottom actions -->
|
||||
<div class="p-3 border-t border-slate-200 dark:border-slate-700/60 flex flex-col gap-2">
|
||||
<button
|
||||
class="w-full btn-secondary text-center text-sm py-1.5 flex items-center justify-center gap-1.5"
|
||||
@click="handleCreateRootFolder"
|
||||
>
|
||||
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 4v16m8-8H4"/>
|
||||
</svg>
|
||||
New folder
|
||||
</button>
|
||||
|
||||
<router-link
|
||||
to="/doc/new"
|
||||
class="btn-primary w-full text-center text-sm py-2"
|
||||
class="btn-primary w-full text-center text-sm py-2 flex items-center justify-center gap-1.5"
|
||||
@click="emit('close')"
|
||||
>
|
||||
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
|
||||
@@ -28,4 +28,19 @@ export async function gql<T = unknown>(
|
||||
return json.data as T
|
||||
}
|
||||
|
||||
export async function createFolder(path: string): Promise<{ createFolder: boolean }> {
|
||||
const query = `mutation CreateFolder($path: String!) { createFolder(path: $path) }`
|
||||
return gql(query, { path })
|
||||
}
|
||||
|
||||
export async function moveDocument(oldSlug: string, newSlug: string): Promise<{ moveDocument: boolean }> {
|
||||
const query = `mutation MoveDocument($oldSlug: String!, $newSlug: String!) { moveDocument(oldSlug: $oldSlug, newSlug: $newSlug) }`
|
||||
return gql(query, { oldSlug, newSlug })
|
||||
}
|
||||
|
||||
export async function getFolders(): Promise<{ folders: string[] }> {
|
||||
const query = `query GetFolders { folders }`
|
||||
return gql(query)
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -19,5 +19,7 @@ export default {
|
||||
},
|
||||
},
|
||||
},
|
||||
plugins: [],
|
||||
plugins: [
|
||||
require('@tailwindcss/typography'),
|
||||
],
|
||||
}
|
||||
|
||||
57
node_modules/.package-lock.json
generated
vendored
Normal file
57
node_modules/.package-lock.json
generated
vendored
Normal file
@@ -0,0 +1,57 @@
|
||||
{
|
||||
"name": "Archivum",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"node_modules/@tailwindcss/typography": {
|
||||
"version": "0.5.19",
|
||||
"resolved": "https://registry.npmjs.org/@tailwindcss/typography/-/typography-0.5.19.tgz",
|
||||
"integrity": "sha512-w31dd8HOx3k9vPtcQh5QHP9GwKcgbMp87j58qi6xgiBnFFtKEAgCWnDw4qUT8aHwkCp8bKvb/KGKWWHedP0AAg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"postcss-selector-parser": "6.0.10"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"tailwindcss": ">=3.0.0 || insiders || >=4.0.0-alpha.20 || >=4.0.0-beta.1"
|
||||
}
|
||||
},
|
||||
"node_modules/cssesc": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz",
|
||||
"integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==",
|
||||
"license": "MIT",
|
||||
"bin": {
|
||||
"cssesc": "bin/cssesc"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=4"
|
||||
}
|
||||
},
|
||||
"node_modules/postcss-selector-parser": {
|
||||
"version": "6.0.10",
|
||||
"resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.0.10.tgz",
|
||||
"integrity": "sha512-IQ7TZdoaqbT+LCpShg46jnZVlhWD2w6iQYAcYXfHARZ7X1t/UGhhceQDs5X0cGqKvYlHNOuv7Oa1xmb0oQuA3w==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"cssesc": "^3.0.0",
|
||||
"util-deprecate": "^1.0.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=4"
|
||||
}
|
||||
},
|
||||
"node_modules/tailwindcss": {
|
||||
"version": "4.2.2",
|
||||
"resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.2.2.tgz",
|
||||
"integrity": "sha512-KWBIxs1Xb6NoLdMVqhbhgwZf2PGBpPEiwOqgI4pFIYbNTfBXiKYyWoTsXgBQ9WFg/OlhnvHaY+AEpW7wSmFo2Q==",
|
||||
"license": "MIT",
|
||||
"peer": true
|
||||
},
|
||||
"node_modules/util-deprecate": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
|
||||
"integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==",
|
||||
"license": "MIT"
|
||||
}
|
||||
}
|
||||
}
|
||||
58
package-lock.json
generated
58
package-lock.json
generated
@@ -2,5 +2,61 @@
|
||||
"name": "Archivum",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {}
|
||||
"packages": {
|
||||
"": {
|
||||
"dependencies": {
|
||||
"@tailwindcss/typography": "^0.5.19"
|
||||
}
|
||||
},
|
||||
"node_modules/@tailwindcss/typography": {
|
||||
"version": "0.5.19",
|
||||
"resolved": "https://registry.npmjs.org/@tailwindcss/typography/-/typography-0.5.19.tgz",
|
||||
"integrity": "sha512-w31dd8HOx3k9vPtcQh5QHP9GwKcgbMp87j58qi6xgiBnFFtKEAgCWnDw4qUT8aHwkCp8bKvb/KGKWWHedP0AAg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"postcss-selector-parser": "6.0.10"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"tailwindcss": ">=3.0.0 || insiders || >=4.0.0-alpha.20 || >=4.0.0-beta.1"
|
||||
}
|
||||
},
|
||||
"node_modules/cssesc": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz",
|
||||
"integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==",
|
||||
"license": "MIT",
|
||||
"bin": {
|
||||
"cssesc": "bin/cssesc"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=4"
|
||||
}
|
||||
},
|
||||
"node_modules/postcss-selector-parser": {
|
||||
"version": "6.0.10",
|
||||
"resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.0.10.tgz",
|
||||
"integrity": "sha512-IQ7TZdoaqbT+LCpShg46jnZVlhWD2w6iQYAcYXfHARZ7X1t/UGhhceQDs5X0cGqKvYlHNOuv7Oa1xmb0oQuA3w==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"cssesc": "^3.0.0",
|
||||
"util-deprecate": "^1.0.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=4"
|
||||
}
|
||||
},
|
||||
"node_modules/tailwindcss": {
|
||||
"version": "4.2.2",
|
||||
"resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.2.2.tgz",
|
||||
"integrity": "sha512-KWBIxs1Xb6NoLdMVqhbhgwZf2PGBpPEiwOqgI4pFIYbNTfBXiKYyWoTsXgBQ9WFg/OlhnvHaY+AEpW7wSmFo2Q==",
|
||||
"license": "MIT",
|
||||
"peer": true
|
||||
},
|
||||
"node_modules/util-deprecate": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
|
||||
"integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==",
|
||||
"license": "MIT"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
5
package.json
Normal file
5
package.json
Normal file
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"dependencies": {
|
||||
"@tailwindcss/typography": "^0.5.19"
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user