sihorton / appjs-TiddlyWiki

TiddlyWiki plugin for AppJS
5 stars 0 forks source link

Support save on non-index.html files #15

Closed cawoodm closed 11 years ago

cawoodm commented 11 years ago

When running multiple wikis (https://github.com/sihorton/appjs-TiddlyWiki/issues/14) you get only readonly access and when saving you get "You need to save this TiddlyWiki to a file...".

It seems that window.allowSave is only set to true once and in the main index.html.

Ideas?

One would need to listen for the navigation event and set it on each page navigated to. Is window.on('ready',....only fired once by appjs?

cawoodm commented 11 years ago

If I hardcode window.allowSave = true; into the appjs plugin on each of my wikis I then get the cantSaveError "It's not possible to save changes. Possible reasons include: - your browser doesn't support saving...".

  1. I know the browser supports it since I have already tested saving with appJS before I went multi
  2. The path is not the issue I am sure
sihorton commented 11 years ago

Maybe because you are using an iframe then the document is not changing for AppJS so it is not adding in the save code correctly. This is quite easy to fix however. What about adding an "onload" event handler to your iframe? In the onload event handler you can have the same JS code as in TiddlyWiki.js, (so copy the contents of window.on('ready') function in TiddlyWiki.js so you create the window.externalJsPath, window.externalJsSave and window.externalJsLoad functions.

sihorton commented 11 years ago

so something like:

<body>
    <div><a target="main" href="tw1.html">Wiki 1</a> | <a target="main" href="tw2.html">Wiki 2</a></div>
    <iframe id="main" frameborder="0" name="main" width="100%" height="95%" onload="
var frameWindow = document.getElementById('main').contentWindow;
 frameWindow.externalJsPath = function(fileUrl) {
    //console.log("JsPath in: " + fileUrl);
    if(fileUrl.toLowerCase().indexOf(wikiUrl.toLowerCase() + "/") == 0) {
        fileUrl = fileUrl.substr(wikiUrl.length + 1);
    }else if(fileUrl.toLowerCase().indexOf("file://") == 0) {
        fileUrl = fileUrl.substr(7);
        if(fileUrl.charAt(2) == ':') fileUrl = fileUrl.substr(1);
    }
    fileUrl = path.resolve(wikiDir, fileUrl);
    fileUrl = fileUrl.split("/").join(path.sep);
    //console.log("JsPath out: " + fileUrl);
    return fileUrl;
  }
   frameWindow.externalJsSave = function(filePath, content) {
    //console.log("JsSave: " + filePath);
    try{
        fs.writeFileSync(filePath, content);
        return true;
    }catch (err){
        return false;
    }
  }
  frameWindow.externalJsLoad = function(filePath) {
    //console.log("JsLoad: " + filePath);
    return fs.readFileSync(filePath).toString('UTF-8');
  }
"></iframe>
</body>

Might have to experiment with that ".contentWindow" part to get the correct syntax to use.

cawoodm commented 11 years ago

You're right of course - w/o the iframe it works fine.

Your proposed code doesn't quite do it yet but I'm wondering if we can't put that code (once it works) into the appJS plugin?

cawoodm commented 11 years ago

You're right of course - w/o the iframe it works.

Here's a fixed version of your code (with " replaced by '):

<iframe id="main" frameborder="0" name="main" width="100%" height="95%" onload="
        var frameWindow = document.getElementById('main').contentWindow;
         frameWindow.externalJsPath = function(fileUrl) {
            //console.log('JsPath in: ' + fileUrl);
            if(fileUrl.toLowerCase().indexOf(wikiUrl.toLowerCase() + '/') == 0) {
                fileUrl = fileUrl.substr(wikiUrl.length + 1);
            }else if(fileUrl.toLowerCase().indexOf('file://') == 0) {
                fileUrl = fileUrl.substr(7);
                if(fileUrl.charAt(2) == ':') fileUrl = fileUrl.substr(1);
            }
            fileUrl = path.resolve(wikiDir, fileUrl);
            fileUrl = fileUrl.split('/').join(path.sep);
            //console.log('JsPath out: ' + fileUrl);
            return fileUrl;
          }
           frameWindow.externalJsSave = function(filePath, content) {
            //console.log('JsSave: ' + filePath);
            try{
                fs.writeFileSync(filePath, content);
                return true;
            }catch (err){
                return false;
            }
          }
          frameWindow.externalJsLoad = function(filePath) {
            //console.log('JsLoad: ' + filePath);
            return fs.readFileSync(filePath).toString('UTF-8');
          }
        "></iframe>

The error seems to be now the unhelful inter-frame communication "security":

Unsafe JavaScript attempt to access frame with URL file:///C:/test/tw1.html
from frame with URL file:///C:/test/index.html.
Domains, protocols and ports must match.

Does appJS run in file:// mode?

How to enable the debugger?

cawoodm commented 11 years ago

It seems likely that the inter-frame/cross-domain stuff won't let us pass: http://stackoverflow.com/questions/6060786/file-url-cross-domain-issue-in-chrome-unexpected)

Funny that appJS runs client JS with full restrictions. Ironically, running http would free us from this issue because the domain would match.

I still wonder though, can't we add externalJsSave code to the plugin?

sihorton commented 11 years ago

You can turn off the security easily, change:-

var window = app.createWindow(wikiUrl + "/" + wikiFile, { url : wikiUrl, icons : __dirname + '/content/icons' });

to:

var window = app.createWindow(wikiUrl + "/" + wikiFile, { url : wikiUrl, icons : __dirname + '/content/icons' ,disableSecurity:true });

Hopefully that fixes the cross domain issues.

sihorton commented 11 years ago

F12 should open the debugger

sihorton commented 11 years ago

We can probably find a more beautiful way to tidy up the code once we have a working solution. I like your idea of having a fancy menu or tabs or whatever you want to be the overall user interface defined simply in the index.html. Maybe it is enough to just provide an appjs function that adds the extra functions when passed a window object, so maybe we could do:-

<iframe ..... onload="window.tiddlyLoad(this.contentWindow);"/>

and that tiddlyLoad function can do window.externalJSLoad = function stuff for us. John says our suggestions for improvements to the TiddlyWiki api have made their way into the latest TiddlyWiki so that might make the plug much simpler to use as well.

cawoodm commented 11 years ago

Got F12 now thanks - unfortunately the breakpoints don't take, neither does the debugger; statement. Also, some code blocks (http://appjs/) seem inaccessible to CDT. I guess it's just me not understanding the context of the nodejs code.

Uncaught ReferenceError: fs is not defined http://appjs/:89
Uncaught ReferenceError: fs is not defined http://appjs/:89
frameWindow.externalJsLoad http://appjs/:89
window.loadFile
loadOriginal sfdc.html:10430
window.saveChanges
config.macros.saveChanges.onClick sfdc.html:6305
cawoodm commented 11 years ago

The cross domain issues I only saw when I ran my index.html in Chrome. It seems they don't appear inside appjs and my issues are unfortunately not affected by adding disableSecurity: true.

sihorton commented 11 years ago

Ok but maybe I did not explain myself very well, the html links should not use file:// they should stay with the appjs so that means the links should all be relative, from the error message it looks like the iframe has been directed to a local file on your machine, but it should instead be a file that is served by appjs then you should not get any cross domain issues.

cawoodm commented 11 years ago

I am indeed using relative links <a target="main" href="tw1.html"> - I seem to have muddied the waters by running the index.html in Chrome before I knew how to start CDT inside appJS. I checked and we're running http so forgetabout the whole cross-domain stuff for now :-)

Maybe I could send you a .zip of the problem and you could take a look? Could you ping me on cawoodm at (google's mail) dot com. Alternatively you could construct the situation locally.

sihorton commented 11 years ago

Yes ok, sorry I am also a little distracted working on another project at the same time. I think we should be able to fix this so that it works relatively quickly. I am just creating a multiple TiddlyWiki on my machine and I can copy your index page and see if I can get it working locally.

sihorton commented 11 years ago

Ok I now have a working version for me locally. Also the code is much simpler than before. Change the iframe tag to be as follows:

<iframe id="main" frameborder="0" name="main" width="100%" height="95%" onload="
        var frameWindow = document.getElementById('main').contentWindow;
    frameWindow.getLocalPath = window.externalJsPath;
    frameWindow.loadFile = window.externalJsLoad;
    frameWindow.allowSave = function() {return true;};
    frameWindow.readOnly = false;    
"></iframe>

so when the iframe loads we just wire up all of the functions to the functions that have been added to the main window. Try and hopefully that works for you. If not get back to me and I can look at your code and see what is wrong since it is working for me now.

cawoodm commented 11 years ago

Thanks, I appreciate your help so far!

Unfortunately it's still not saving because:

Uncaught TypeError: Property 'getLocalPath' of object [object Window] is not a function
window.saveChanges
config.macros.saveChanges.onClick sfdc.html:6307

I did alert(getLocalPath) (in the appjs plugin - see below) and it's undefined. Do I need a newer version of the appjs plugin?

/**
AppJS Support tested with TikiWiki 2.6.5 October 6, 2011
**/
window.allowSave = true;
window.saveChanges = function(onlyIfDirty,tiddlers)
{
    if(onlyIfDirty &amp;&amp; !store.isDirty())
        return;
    clearMessage();
    var t0 = new Date();
    var msg = config.messages;
    var originalPath = document.location.toString();
    /** AppJS added !window['allowSave']**/
    if ((originalPath.substr(0,5) != 'file:') &amp;&amp; !window['allowSave']) { 
        alert(msg.notFileUrlError);
        if(store.tiddlerExists(msg.saveInstructions))
            story.displayTiddler(null,msg.saveInstructions);
        return;
    }
alert(getLocalPath);
alert(window.getLocalPath);
cawoodm commented 11 years ago

OK, It seems I was running an older version downloaded here (https://github.com/downloads/sihorton/appjs-TiddlyWiki/appjs-tiddlywiki-win32.zip). I've just switched to https://github.com/sihorton/appjs-TiddlyWiki/blob/master/TiddlyWiki.js now and it's looking better but still failing with mainFailed: "Failed to save main TiddlyWiki file. Your changes have not been saved".

sihorton commented 11 years ago

I will send you an email with a download link to my version (windows). You can test with that and then copy over changes to your code. WinMerge is an amazing free windows program that can give you a graphical diff of two different directories so you can use that to copy the code over to your version.

cawoodm commented 11 years ago

Thanks, I just upgraded my TW to the github master version to no avail so I'll need to see what you're doing differently.

sihorton commented 11 years ago

Yes I think you are so close to have a working version it is best to just look at what is working for me and copy it. If it takes too long then send me a zip and I can take a look.

cawoodm commented 11 years ago

Thanks for this Simon, yours worked of course though it took me 30min to identify what I had different.

It seems you have a completely different release of appjs-TiddlyWiki to me. As I mentioned I downloaded the windows build from your github home so that must be old.

The difference seemed to come down to:

  1. The line frameWindow.saveFile = window.externalJsSave; inside index.html (iframe onload) was missing in my app
  2. the node_modules folders were vastly different (this was the major difference)
  3. possibly using an old appjs plugin in TW

It was a difficult process and I am still especially confused as to why I sometimes got TWs opened in readonly mode and sometimes not. Also the CDT debugging seems so handicapped in appJS which didn't help.

Final step in the solution: use the latest build!

Thanks a million!

sihorton commented 11 years ago

Ok I had just used v20 of AppJS because that was what I had locally, however that should not make any difference, the missing "frameWindow.saveFile=window.externalJsSave" code would cause the save to fail, the other lines of code that were there would cause it to not be in readOnly mode. I will update the wiki page with the end code and I think that should work well.

sihorton commented 11 years ago

Ok sorry to say when I wrote in the comments to you the code to use I somehow missed the "frameWindow.saveFile=window.externalJsSave" line and that is quite important for the save to work. Great that we got it working in the end though, I have updated the wiki page so it shows the end code.