| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728 |
- package agi
- import (
- "encoding/json"
- "errors"
- "fmt"
- "os"
- "path/filepath"
- "sort"
- "github.com/robertkrimen/otto"
- "imuslab.com/arozos/mod/agi/static"
- "imuslab.com/arozos/mod/info/logger"
- "imuslab.com/arozos/mod/sharedspace"
- )
- /*
- AGI SharedSpace Library
- Exposes the shared collaboration space manager to AGI scripts. A shared
- space is an area where multiple different users can share texts, images,
- files and collaboratively edited documents together. Spaces carry an
- access mode - "open" (the random space ID acts as the capability),
- "public" (discoverable, self-join) or "private" (members only) - plus
- a member list with owner / admin / member roles, a metadata store and
- an optional persistent flag (survives restarts). Loaded with
- requirelib("sharedspace").
- MeetRoom binds one space to every meeting room (see agi.meetroom.go),
- so this library is also how scripts read a live meeting's chat and post
- messages / files into it.
- */
- // agiOriginTag marks items posted through this library so consumers (e.g.
- // the MeetRoom space item bridge) can tell them apart from their own posts.
- const agiOriginTag = "agi"
- func (g *Gateway) SharedSpaceLibRegister() {
- err := g.RegisterLib("sharedspace", g.injectSharedSpaceFunctions)
- if err != nil {
- logger.PrintAndLog("Agi", fmt.Sprint(err), nil)
- os.Exit(1)
- }
- }
- // agiDescribeSpace renders the space fields shared with AGI scripts.
- func agiDescribeSpace(space *sharedspace.Space) map[string]interface{} {
- return map[string]interface{}{
- "spaceid": space.ID,
- "name": space.Name,
- "owner": space.Owner,
- "access": space.AccessMode(),
- "persistent": space.Persistent,
- "items": space.ItemCount(),
- "docs": space.DocCount(),
- "members": space.MemberCount(),
- "metadata": space.Metadata(),
- "createdat": space.CreatedAt.Unix(),
- }
- }
- // agiDescribeItem renders a space item for AGI scripts. Text content is
- // included inline for text items; blobs report their display name and size.
- func agiDescribeItem(item *sharedspace.Item) map[string]interface{} {
- return map[string]interface{}{
- "itemid": item.ID,
- "type": item.Type,
- "name": item.Name,
- "text": item.Text,
- "size": item.Size,
- "uploader": item.Uploader,
- "origin": item.Origin,
- "time": item.CreatedAt.Unix(),
- }
- }
- // agiDescribeDoc renders a document snapshot for AGI scripts.
- func agiDescribeDoc(doc *sharedspace.DocSnapshot, includeContent bool) map[string]interface{} {
- desc := map[string]interface{}{
- "docid": doc.ID,
- "name": doc.Name,
- "creator": doc.Creator,
- "revision": doc.Revision,
- "createdat": doc.CreatedAt.Unix(),
- "updatedat": doc.UpdatedAt.Unix(),
- "updatedby": doc.UpdatedBy,
- }
- if includeContent {
- desc["content"] = doc.Content
- }
- return desc
- }
- // resolveNotifyRecipients computes who should receive a space notification.
- // It starts from the space members, optionally narrows to an explicit target
- // set (nil = every member), and always drops the sender and anyone currently
- // connected to the space (they are already receiving the live message). The
- // result is sorted for deterministic delivery and testing.
- func resolveNotifyRecipients(members map[string]string, present map[string]bool, sender string, requested []string) []string {
- var requestedSet map[string]bool
- if requested != nil {
- requestedSet = make(map[string]bool, len(requested))
- for _, name := range requested {
- if name != "" {
- requestedSet[name] = true
- }
- }
- }
- recipients := []string{}
- for username := range members {
- if username == "" || username == sender {
- continue
- }
- if present != nil && present[username] {
- continue
- }
- if requestedSet != nil && !requestedSet[username] {
- continue
- }
- recipients = append(recipients, username)
- }
- sort.Strings(recipients)
- return recipients
- }
- func (g *Gateway) injectSharedSpaceFunctions(payload *static.AgiLibInjectionPayload) {
- vm := payload.VM
- u := payload.User
- scriptFsh := payload.ScriptFsh
- manager := g.Option.SharedSpaceManager
- if manager == nil || u == nil {
- return
- }
- //The sender label shown to the user is the script's module root (e.g.
- //"Chatspace"), matching the AGI notification library's convention.
- senderLabel := "Shared Space"
- if payload.ScriptPath != "" {
- senderLabel = static.GetScriptRoot(payload.ScriptPath, "./web/")
- }
- jsonReply := func(v interface{}) otto.Value {
- js, err := json.Marshal(v)
- if err != nil {
- return otto.NullValue()
- }
- val, _ := vm.ToValue(string(js))
- return val
- }
- //getSpace resolves the first argument to a live space without any
- //access check (used where the called method enforces its own ACL).
- getSpace := func(call otto.FunctionCall) (*sharedspace.Space, bool) {
- spaceID, err := call.Argument(0).ToString()
- if err != nil {
- return nil, false
- }
- return manager.GetSpace(spaceID)
- }
- //getReadableSpace additionally requires read access for the calling
- //user - private spaces stay invisible to outsiders.
- getReadableSpace := func(call otto.FunctionCall) (*sharedspace.Space, bool) {
- space, ok := getSpace(call)
- if !ok || !space.CanRead(u.Username) {
- return nil, false
- }
- return space, true
- }
- //optionalString reads an argument that may be omitted in the script.
- optionalString := func(call otto.FunctionCall, idx int) string {
- arg := call.Argument(idx)
- if !arg.IsDefined() {
- return ""
- }
- s, _ := arg.ToString()
- if s == "undefined" || s == "null" {
- return ""
- }
- return s
- }
- //Create a new open, ephemeral space owned by the calling user
- vm.Set("_sharedspace_createSpace", func(call otto.FunctionCall) otto.Value {
- name := optionalString(call, 0)
- space := manager.CreateSpace(u.Username, name)
- return jsonReply(agiDescribeSpace(space))
- })
- //Create a space with options: {access, persistent, metadata}
- vm.Set("_sharedspace_createSpaceAdvanced", func(call otto.FunctionCall) otto.Value {
- name := optionalString(call, 0)
- optionsJSON := optionalString(call, 1)
- options := struct {
- Access string `json:"access"`
- Persistent bool `json:"persistent"`
- Metadata map[string]string `json:"metadata"`
- }{}
- if optionsJSON != "" {
- json.Unmarshal([]byte(optionsJSON), &options)
- }
- space, err := manager.CreateSpaceWithOptions(u.Username, name, sharedspace.SpaceOptions{
- Access: options.Access,
- Persistent: options.Persistent,
- Metadata: options.Metadata,
- })
- if err != nil {
- g.RaiseError(err)
- return otto.NullValue()
- }
- return jsonReply(agiDescribeSpace(space))
- })
- //Delete a space; owner or space admin only
- vm.Set("_sharedspace_deleteSpace", func(call otto.FunctionCall) otto.Value {
- space, ok := getSpace(call)
- if !ok || !space.CanManage(u.Username) {
- return otto.FalseValue()
- }
- //Give live channel subscribers the shutdown notice first
- space.Channel().Broadcast([]byte(`{"type":"space-closed"}`), -1)
- manager.DeleteSpace(space.ID)
- return otto.TrueValue()
- })
- //List the spaces owned by the calling user
- vm.Set("_sharedspace_listMySpaces", func(call otto.FunctionCall) otto.Value {
- owned := manager.ListSpacesByOwner(u.Username)
- list := make([]map[string]interface{}, 0, len(owned))
- for _, space := range owned {
- list = append(list, agiDescribeSpace(space))
- }
- return jsonReply(list)
- })
- //List the spaces the calling user has joined (owner, admin or member)
- vm.Set("_sharedspace_listJoinedSpaces", func(call otto.FunctionCall) otto.Value {
- joined := manager.ListSpacesByMember(u.Username)
- list := make([]map[string]interface{}, 0, len(joined))
- for _, space := range joined {
- list = append(list, agiDescribeSpace(space))
- }
- return jsonReply(list)
- })
- //List every public space (the discovery directory)
- vm.Set("_sharedspace_listPublicSpaces", func(call otto.FunctionCall) otto.Value {
- public := manager.ListPublicSpaces()
- list := make([]map[string]interface{}, 0, len(public))
- for _, space := range public {
- list = append(list, agiDescribeSpace(space))
- }
- return jsonReply(list)
- })
- //Describe a space by ID
- vm.Set("_sharedspace_getSpaceInfo", func(call otto.FunctionCall) otto.Value {
- space, ok := getReadableSpace(call)
- if !ok {
- return jsonReply(map[string]interface{}{"exists": false})
- }
- desc := agiDescribeSpace(space)
- desc["exists"] = true
- role, isMember := space.Role(u.Username)
- desc["myrole"] = role
- desc["ismember"] = isMember
- return jsonReply(desc)
- })
- //Self-join a public (or open) space
- vm.Set("_sharedspace_joinSpace", func(call otto.FunctionCall) otto.Value {
- space, ok := getSpace(call)
- if !ok {
- return otto.FalseValue()
- }
- if err := space.JoinPublic(u.Username); err != nil {
- return otto.FalseValue()
- }
- return otto.TrueValue()
- })
- //Leave a space
- vm.Set("_sharedspace_leaveSpace", func(call otto.FunctionCall) otto.Value {
- space, ok := getSpace(call)
- if !ok {
- return otto.FalseValue()
- }
- if err := space.RemoveMember(u.Username, u.Username); err != nil {
- return otto.FalseValue()
- }
- return otto.TrueValue()
- })
- //Change the access mode; space managers only
- vm.Set("_sharedspace_setAccess", func(call otto.FunctionCall) otto.Value {
- space, ok := getSpace(call)
- if !ok {
- return otto.FalseValue()
- }
- access, err := call.Argument(1).ToString()
- if err != nil {
- return otto.FalseValue()
- }
- if err := space.SetAccess(u.Username, access); err != nil {
- return otto.FalseValue()
- }
- return otto.TrueValue()
- })
- //Set (or with an empty value delete) a metadata entry; managers only
- vm.Set("_sharedspace_setMeta", func(call otto.FunctionCall) otto.Value {
- space, ok := getSpace(call)
- if !ok {
- return otto.FalseValue()
- }
- key, err := call.Argument(1).ToString()
- if err != nil {
- return otto.FalseValue()
- }
- value := optionalString(call, 2)
- if err := space.SetMeta(u.Username, key, value); err != nil {
- return otto.FalseValue()
- }
- return otto.TrueValue()
- })
- //Read the metadata of a space
- vm.Set("_sharedspace_getMeta", func(call otto.FunctionCall) otto.Value {
- space, ok := getReadableSpace(call)
- if !ok {
- return otto.NullValue()
- }
- return jsonReply(space.Metadata())
- })
- //Invite a user; managers only
- vm.Set("_sharedspace_addMember", func(call otto.FunctionCall) otto.Value {
- space, ok := getSpace(call)
- if !ok {
- return otto.FalseValue()
- }
- target, err := call.Argument(1).ToString()
- if err != nil {
- return otto.FalseValue()
- }
- role := optionalString(call, 2)
- if role == "" {
- role = sharedspace.RoleMember
- }
- if err := space.AddMember(u.Username, target, role); err != nil {
- return otto.FalseValue()
- }
- return otto.TrueValue()
- })
- //Remove a member; managers (or the member themselves)
- vm.Set("_sharedspace_removeMember", func(call otto.FunctionCall) otto.Value {
- space, ok := getSpace(call)
- if !ok {
- return otto.FalseValue()
- }
- target, err := call.Argument(1).ToString()
- if err != nil {
- return otto.FalseValue()
- }
- if err := space.RemoveMember(u.Username, target); err != nil {
- return otto.FalseValue()
- }
- return otto.TrueValue()
- })
- //List the members of a space; members and managers only
- vm.Set("_sharedspace_listMembers", func(call otto.FunctionCall) otto.Value {
- space, ok := getReadableSpace(call)
- if !ok {
- return otto.NullValue()
- }
- if _, isMember := space.Role(u.Username); !isMember && !space.CanManage(u.Username) {
- return otto.NullValue()
- }
- return jsonReply(space.Members())
- })
- //Post a text snippet into a space
- vm.Set("_sharedspace_addText", func(call otto.FunctionCall) otto.Value {
- space, ok := getSpace(call)
- if !ok || !space.CanPost(u.Username) {
- return otto.NullValue()
- }
- text, err := call.Argument(1).ToString()
- if err != nil {
- return otto.NullValue()
- }
- item, err := space.AddText(u.Username, text, agiOriginTag)
- if err != nil {
- return otto.NullValue()
- }
- val, _ := vm.ToValue(item.ID)
- return val
- })
- //Raise a notification to fellow members of a space through the ArozOS
- //notification system. Membership-scoped: only a member (or manager) may
- //notify, and only current members can be reached, so this cannot spam
- //arbitrary users. Members currently connected to the space's realtime
- //channel are skipped (they already receive the live message). Delivery
- //per recipient follows that user's own notification preferences (desktop,
- //Telegram, email, webhook). Returns the number of members notified, or -1
- //on error.
- // args: (spaceid, title, [message], [priority], [targetsJSON])
- vm.Set("_sharedspace_notifyMembers", func(call otto.FunctionCall) otto.Value {
- intReply := func(n int) otto.Value { v, _ := vm.ToValue(n); return v }
- space, ok := getSpace(call)
- if !ok {
- g.RaiseError(errors.New("space not found"))
- return intReply(-1)
- }
- if _, isMember := space.Role(u.Username); !isMember && !space.CanManage(u.Username) {
- g.RaiseError(errors.New("permission denied: not a member of this space"))
- return intReply(-1)
- }
- title, err := call.Argument(1).ToString()
- if err != nil || title == "undefined" || title == "" {
- g.RaiseError(errors.New("notification title cannot be empty"))
- return intReply(-1)
- }
- message := optionalString(call, 2)
- priority := optionalString(call, 3)
- if priority == "" {
- priority = "medium"
- }
- //An explicit target list (JSON array) narrows the recipients to those
- //members; omitting it notifies every other member. A malformed list
- //fails safe to "notify nobody".
- var requested []string
- if targetsJSON := optionalString(call, 4); targetsJSON != "" {
- if jerr := json.Unmarshal([]byte(targetsJSON), &requested); jerr != nil {
- requested = []string{}
- }
- }
- present := map[string]bool{}
- for _, sub := range space.Channel().Subscribers() {
- present[sub.Username] = true
- }
- recipients := resolveNotifyRecipients(space.Members(), present, u.Username, requested)
- if len(recipients) == 0 {
- //Nobody to notify (everyone is present, or none matched).
- return intReply(0)
- }
- if err := g.buildAndSendNotification(senderLabel, recipients, title, message, priority); err != nil {
- g.RaiseError(err)
- return intReply(-1)
- }
- return intReply(len(recipients))
- })
- //Share a file from the calling user's storage into a space
- vm.Set("_sharedspace_addFile", func(call otto.FunctionCall) otto.Value {
- space, ok := getSpace(call)
- if !ok || !space.CanPost(u.Username) {
- return otto.NullValue()
- }
- vpath, err := call.Argument(1).ToString()
- if err != nil {
- return otto.NullValue()
- }
- vpath = static.RelativeVpathRewrite(scriptFsh, vpath, vm, u)
- if !u.CanRead(vpath) {
- panic(vm.MakeCustomError("PermissionDenied", "Path access denied: "+vpath))
- }
- fsh, rpath, err := static.VirtualPathToRealPath(vpath, u)
- if err != nil {
- g.RaiseError(err)
- return otto.NullValue()
- }
- src, err := fsh.FileSystemAbstraction.ReadStream(rpath)
- if err != nil {
- g.RaiseError(err)
- return otto.NullValue()
- }
- defer src.Close()
- name := filepath.Base(vpath)
- itemType := sharedspace.ItemTypeFile
- if sharedspace.IsImageName(name) {
- itemType = sharedspace.ItemTypeImage
- }
- item, err := space.SaveBlob(itemType, name, u.Username, agiOriginTag, src, g.Option.SharedSpaceManager.MaxUpload())
- if err != nil {
- g.RaiseError(err)
- return otto.NullValue()
- }
- val, _ := vm.ToValue(item.ID)
- return val
- })
- //List the items in a space
- vm.Set("_sharedspace_listItems", func(call otto.FunctionCall) otto.Value {
- space, ok := getReadableSpace(call)
- if !ok {
- return otto.NullValue()
- }
- items := space.Items()
- list := make([]map[string]interface{}, 0, len(items))
- for _, item := range items {
- list = append(list, agiDescribeItem(item))
- }
- return jsonReply(list)
- })
- //Read the content of a text item
- vm.Set("_sharedspace_getText", func(call otto.FunctionCall) otto.Value {
- space, ok := getReadableSpace(call)
- if !ok {
- return otto.NullValue()
- }
- itemID, err := call.Argument(1).ToString()
- if err != nil {
- return otto.NullValue()
- }
- item, ok := space.GetItem(itemID)
- if !ok || item.Type != sharedspace.ItemTypeText {
- return otto.NullValue()
- }
- val, _ := vm.ToValue(item.Text)
- return val
- })
- //Copy an image / file item into the calling user's storage
- vm.Set("_sharedspace_saveFileTo", func(call otto.FunctionCall) otto.Value {
- space, ok := getReadableSpace(call)
- if !ok {
- return otto.FalseValue()
- }
- itemID, err := call.Argument(1).ToString()
- if err != nil {
- return otto.FalseValue()
- }
- item, ok := space.GetItem(itemID)
- if !ok || item.DiskPath == "" {
- return otto.FalseValue()
- }
- destVpath, err := call.Argument(2).ToString()
- if err != nil {
- return otto.FalseValue()
- }
- destVpath = static.RelativeVpathRewrite(scriptFsh, destVpath, vm, u)
- if !u.CanWrite(destVpath) {
- panic(vm.MakeCustomError("PermissionDenied", "Path access denied: "+destVpath))
- }
- fsh, rpath, err := static.VirtualPathToRealPath(destVpath, u)
- if err != nil {
- g.RaiseError(err)
- return otto.FalseValue()
- }
- src, err := os.Open(item.DiskPath)
- if err != nil {
- g.RaiseError(err)
- return otto.FalseValue()
- }
- defer src.Close()
- err = fsh.FileSystemAbstraction.WriteStream(rpath, src, 0775)
- if err != nil {
- g.RaiseError(err)
- return otto.FalseValue()
- }
- return otto.TrueValue()
- })
- //Remove an item; item uploader, space admin or owner only
- vm.Set("_sharedspace_removeItem", func(call otto.FunctionCall) otto.Value {
- space, ok := getSpace(call)
- if !ok {
- return otto.FalseValue()
- }
- itemID, err := call.Argument(1).ToString()
- if err != nil {
- return otto.FalseValue()
- }
- if err := space.RemoveItem(itemID, u.Username); err != nil {
- return otto.FalseValue()
- }
- return otto.TrueValue()
- })
- //Create a collaborative document
- vm.Set("_sharedspace_createDoc", func(call otto.FunctionCall) otto.Value {
- space, ok := getSpace(call)
- if !ok {
- return otto.NullValue()
- }
- name := optionalString(call, 1)
- doc, err := space.CreateDoc(u.Username, name)
- if err != nil {
- return otto.NullValue()
- }
- return jsonReply(agiDescribeDoc(doc, true))
- })
- //List documents (without contents)
- vm.Set("_sharedspace_listDocs", func(call otto.FunctionCall) otto.Value {
- space, ok := getReadableSpace(call)
- if !ok {
- return otto.NullValue()
- }
- docs := space.ListDocs()
- list := make([]map[string]interface{}, 0, len(docs))
- for _, doc := range docs {
- list = append(list, agiDescribeDoc(doc, false))
- }
- return jsonReply(list)
- })
- //Fetch one document with content
- vm.Set("_sharedspace_getDoc", func(call otto.FunctionCall) otto.Value {
- space, ok := getReadableSpace(call)
- if !ok {
- return otto.NullValue()
- }
- docID, err := call.Argument(1).ToString()
- if err != nil {
- return otto.NullValue()
- }
- doc, ok := space.GetDoc(docID)
- if !ok {
- return otto.NullValue()
- }
- return jsonReply(agiDescribeDoc(doc, true))
- })
- //Compare-and-swap document update
- vm.Set("_sharedspace_updateDoc", func(call otto.FunctionCall) otto.Value {
- space, ok := getSpace(call)
- if !ok {
- return otto.NullValue()
- }
- docID, err := call.Argument(1).ToString()
- if err != nil {
- return otto.NullValue()
- }
- baseRev, err := call.Argument(2).ToInteger()
- if err != nil {
- return otto.NullValue()
- }
- content, _ := call.Argument(3).ToString()
- doc, err := space.UpdateDoc(u.Username, docID, baseRev, content)
- if err == sharedspace.ErrRevisionConflict {
- current, _ := space.GetDoc(docID)
- revision := int64(0)
- if current != nil {
- revision = current.Revision
- }
- return jsonReply(map[string]interface{}{
- "ok": false,
- "conflict": true,
- "revision": revision,
- })
- }
- if err != nil {
- return otto.NullValue()
- }
- return jsonReply(map[string]interface{}{
- "ok": true,
- "revision": doc.Revision,
- })
- })
- //Delete a document; creator or space managers
- vm.Set("_sharedspace_deleteDoc", func(call otto.FunctionCall) otto.Value {
- space, ok := getSpace(call)
- if !ok {
- return otto.FalseValue()
- }
- docID, err := call.Argument(1).ToString()
- if err != nil {
- return otto.FalseValue()
- }
- if err := space.DeleteDoc(u.Username, docID); err != nil {
- return otto.FalseValue()
- }
- return otto.TrueValue()
- })
- //Wrap the native functions into a sharedspace class
- vm.Run(`
- var sharedspace = {};
- sharedspace.createSpace = function(name){ return JSON.parse(_sharedspace_createSpace(name)); };
- sharedspace.createSpaceAdvanced = function(name, options){ var r = _sharedspace_createSpaceAdvanced(name, options === undefined ? "" : JSON.stringify(options)); return r === null ? null : JSON.parse(r); };
- sharedspace.deleteSpace = _sharedspace_deleteSpace;
- sharedspace.listMySpaces = function(){ return JSON.parse(_sharedspace_listMySpaces()); };
- sharedspace.listJoinedSpaces = function(){ return JSON.parse(_sharedspace_listJoinedSpaces()); };
- sharedspace.listPublicSpaces = function(){ return JSON.parse(_sharedspace_listPublicSpaces()); };
- sharedspace.getSpaceInfo = function(spaceid){ return JSON.parse(_sharedspace_getSpaceInfo(spaceid)); };
- sharedspace.joinSpace = _sharedspace_joinSpace;
- sharedspace.leaveSpace = _sharedspace_leaveSpace;
- sharedspace.setAccess = _sharedspace_setAccess;
- sharedspace.setMeta = _sharedspace_setMeta;
- sharedspace.getMeta = function(spaceid){ var r = _sharedspace_getMeta(spaceid); return r === null ? null : JSON.parse(r); };
- sharedspace.addMember = _sharedspace_addMember;
- sharedspace.removeMember = _sharedspace_removeMember;
- sharedspace.listMembers = function(spaceid){ var r = _sharedspace_listMembers(spaceid); return r === null ? null : JSON.parse(r); };
- sharedspace.addText = _sharedspace_addText;
- sharedspace.notifyMembers = function(spaceid, title, message, priority, usernames){
- return _sharedspace_notifyMembers(spaceid, title,
- message === undefined ? "" : message,
- priority === undefined ? "" : priority,
- (usernames === undefined || usernames === null) ? "" : JSON.stringify(usernames));
- };
- sharedspace.addFile = _sharedspace_addFile;
- sharedspace.listItems = function(spaceid){ var r = _sharedspace_listItems(spaceid); return r === null ? null : JSON.parse(r); };
- sharedspace.getText = _sharedspace_getText;
- sharedspace.saveFileTo = _sharedspace_saveFileTo;
- sharedspace.removeItem = _sharedspace_removeItem;
- sharedspace.createDoc = function(spaceid, name){ var r = _sharedspace_createDoc(spaceid, name); return r === null ? null : JSON.parse(r); };
- sharedspace.listDocs = function(spaceid){ var r = _sharedspace_listDocs(spaceid); return r === null ? null : JSON.parse(r); };
- sharedspace.getDoc = function(spaceid, docid){ var r = _sharedspace_getDoc(spaceid, docid); return r === null ? null : JSON.parse(r); };
- sharedspace.updateDoc = function(spaceid, docid, baserev, content){ var r = _sharedspace_updateDoc(spaceid, docid, baserev, content); return r === null ? null : JSON.parse(r); };
- sharedspace.deleteDoc = _sharedspace_deleteDoc;
- `)
- }
|