瀏覽代碼

Ipython notebook support (#4070)

* added marked and notebookjs javascript libraries

* added ipython notebook render support using javascript libraries

* recompiled gogs.css to include ipynb-related css

* removed superflous javascript library files
Herbert 8 年之前
父節點
當前提交
9af0dd23dd

+ 97 - 22
public/css/gogs.css

@@ -812,7 +812,7 @@ footer .ui.language .menu {
   border: solid 1px #ccc;
   border: solid 1px #ccc;
   border-bottom-color: #bbb;
   border-bottom-color: #bbb;
   border-radius: 3px;
   border-radius: 3px;
-  box-shadow: inset 0 -1px 0 #bbb;
+  box-shadow: inset 0 -1px 0 #bbbbbb;
 }
 }
 .markdown:not(code) input[type="checkbox"] {
 .markdown:not(code) input[type="checkbox"] {
   vertical-align: middle !important;
   vertical-align: middle !important;
@@ -883,7 +883,7 @@ footer .ui.language .menu {
 }
 }
 .install form label {
 .install form label {
   text-align: right;
   text-align: right;
-  width: 320px !important;
+  width: 320px;
 }
 }
 .install form input {
 .install form input {
   width: 35% !important;
   width: 35% !important;
@@ -892,7 +892,7 @@ footer .ui.language .menu {
   text-align: left;
   text-align: left;
 }
 }
 .install form .field .help {
 .install form .field .help {
-  margin-left: 335px !important;
+  margin-left: 335px;
 }
 }
 .install form .field.optional .title {
 .install form .field.optional .title {
   margin-left: 38%;
   margin-left: 38%;
@@ -928,18 +928,18 @@ footer .ui.language .menu {
   text-align: center;
   text-align: center;
 }
 }
 #create-page-form form .header {
 #create-page-form form .header {
-  padding-left: 280px !important;
+  padding-left: 280px;
 }
 }
 #create-page-form form .inline.field > label {
 #create-page-form form .inline.field > label {
   text-align: right;
   text-align: right;
-  width: 250px !important;
+  width: 250px;
   word-wrap: break-word;
   word-wrap: break-word;
 }
 }
 #create-page-form form .help {
 #create-page-form form .help {
-  margin-left: 265px !important;
+  margin-left: 265px;
 }
 }
 #create-page-form form .optional .title {
 #create-page-form form .optional .title {
-  margin-left: 250px !important;
+  margin-left: 250px;
 }
 }
 #create-page-form form input,
 #create-page-form form input,
 #create-page-form form textarea {
 #create-page-form form textarea {
@@ -965,7 +965,7 @@ footer .ui.language .menu {
 .user.reset.password form .header,
 .user.reset.password form .header,
 .user.signin form .header,
 .user.signin form .header,
 .user.signup form .header {
 .user.signup form .header {
-  padding-left: 280px !important;
+  padding-left: 280px;
 }
 }
 .user.activate form .inline.field > label,
 .user.activate form .inline.field > label,
 .user.forgot.password form .inline.field > label,
 .user.forgot.password form .inline.field > label,
@@ -973,7 +973,7 @@ footer .ui.language .menu {
 .user.signin form .inline.field > label,
 .user.signin form .inline.field > label,
 .user.signup form .inline.field > label {
 .user.signup form .inline.field > label {
   text-align: right;
   text-align: right;
-  width: 250px !important;
+  width: 250px;
   word-wrap: break-word;
   word-wrap: break-word;
 }
 }
 .user.activate form .help,
 .user.activate form .help,
@@ -981,14 +981,14 @@ footer .ui.language .menu {
 .user.reset.password form .help,
 .user.reset.password form .help,
 .user.signin form .help,
 .user.signin form .help,
 .user.signup form .help {
 .user.signup form .help {
-  margin-left: 265px !important;
+  margin-left: 265px;
 }
 }
 .user.activate form .optional .title,
 .user.activate form .optional .title,
 .user.forgot.password form .optional .title,
 .user.forgot.password form .optional .title,
 .user.reset.password form .optional .title,
 .user.reset.password form .optional .title,
 .user.signin form .optional .title,
 .user.signin form .optional .title,
 .user.signup form .optional .title {
 .user.signup form .optional .title {
-  margin-left: 250px !important;
+  margin-left: 250px;
 }
 }
 .user.activate form input,
 .user.activate form input,
 .user.forgot.password form input,
 .user.forgot.password form input,
@@ -1014,14 +1014,14 @@ footer .ui.language .menu {
 .user.reset.password form .header,
 .user.reset.password form .header,
 .user.signin form .header,
 .user.signin form .header,
 .user.signup form .header {
 .user.signup form .header {
-  padding-left: 230px !important;
+  padding-left: 230px;
 }
 }
 .user.activate form .inline.field > label,
 .user.activate form .inline.field > label,
 .user.forgot.password form .inline.field > label,
 .user.forgot.password form .inline.field > label,
 .user.reset.password form .inline.field > label,
 .user.reset.password form .inline.field > label,
 .user.signin form .inline.field > label,
 .user.signin form .inline.field > label,
 .user.signup form .inline.field > label {
 .user.signup form .inline.field > label {
-  width: 200px !important;
+  width: 200px;
 }
 }
 .repository.new.repo form,
 .repository.new.repo form,
 .repository.new.migrate form,
 .repository.new.migrate form,
@@ -1037,24 +1037,24 @@ footer .ui.language .menu {
 .repository.new.repo form .header,
 .repository.new.repo form .header,
 .repository.new.migrate form .header,
 .repository.new.migrate form .header,
 .repository.new.fork form .header {
 .repository.new.fork form .header {
-  padding-left: 280px !important;
+  padding-left: 280px;
 }
 }
 .repository.new.repo form .inline.field > label,
 .repository.new.repo form .inline.field > label,
 .repository.new.migrate form .inline.field > label,
 .repository.new.migrate form .inline.field > label,
 .repository.new.fork form .inline.field > label {
 .repository.new.fork form .inline.field > label {
   text-align: right;
   text-align: right;
-  width: 250px !important;
+  width: 250px;
   word-wrap: break-word;
   word-wrap: break-word;
 }
 }
 .repository.new.repo form .help,
 .repository.new.repo form .help,
 .repository.new.migrate form .help,
 .repository.new.migrate form .help,
 .repository.new.fork form .help {
 .repository.new.fork form .help {
-  margin-left: 265px !important;
+  margin-left: 265px;
 }
 }
 .repository.new.repo form .optional .title,
 .repository.new.repo form .optional .title,
 .repository.new.migrate form .optional .title,
 .repository.new.migrate form .optional .title,
 .repository.new.fork form .optional .title {
 .repository.new.fork form .optional .title {
-  margin-left: 250px !important;
+  margin-left: 250px;
 }
 }
 .repository.new.repo form input,
 .repository.new.repo form input,
 .repository.new.migrate form input,
 .repository.new.migrate form input,
@@ -1083,7 +1083,7 @@ footer .ui.language .menu {
   width: 50%!important;
   width: 50%!important;
 }
 }
 .repository.new.repo .ui.form #auto-init {
 .repository.new.repo .ui.form #auto-init {
-  margin-left: 265px !important;
+  margin-left: 265px;
 }
 }
 .new.webhook form .help {
 .new.webhook form .help {
   margin-left: 25px;
   margin-left: 25px;
@@ -1306,6 +1306,81 @@ footer .ui.language .menu {
 .repository.file.list #file-content .view-raw img {
 .repository.file.list #file-content .view-raw img {
   margin-bottom: -5px;
   margin-bottom: -5px;
 }
 }
+.repository.file.list #file-content #ipython-notebook {
+  margin-left: 80px;
+}
+.repository.file.list #file-content #ipython-notebook .nb-notebook {
+  line-height: 1.5;
+}
+.repository.file.list #file-content #ipython-notebook .nb-stdout,
+.repository.file.list #file-content #ipython-notebook .nb-stderr {
+  white-space: pre-wrap;
+  margin: 1em 0;
+  padding: 0.1em 0.5em;
+}
+.repository.file.list #file-content #ipython-notebook .nb-stderr {
+  background-color: #FAA;
+}
+.repository.file.list #file-content #ipython-notebook .nb-cell + .nb-cell {
+  margin-top: 0.5em;
+}
+.repository.file.list #file-content #ipython-notebook .nb-output table {
+  border: 1px solid #000;
+  border-collapse: collapse;
+}
+.repository.file.list #file-content #ipython-notebook .nb-output th {
+  font-weight: bold;
+}
+.repository.file.list #file-content #ipython-notebook .nb-output th,
+.repository.file.list #file-content #ipython-notebook .nb-output td {
+  border: 1px solid #000;
+  padding: 0.25em;
+  text-align: left;
+  vertical-align: middle;
+  border-collapse: collapse;
+}
+.repository.file.list #file-content #ipython-notebook .nb-cell {
+  position: relative;
+}
+.repository.file.list #file-content #ipython-notebook .nb-raw-cell {
+  white-space: pre-wrap;
+  background-color: #f5f2f0;
+  font-family: Consolas, Monaco, 'Andale Mono', monospace;
+  padding: 1em;
+  margin: .5em 0;
+}
+.repository.file.list #file-content #ipython-notebook .nb-output {
+  min-height: 1em;
+  width: 100%;
+  overflow-x: scroll;
+  border-right: 1px dotted #CCC;
+}
+.repository.file.list #file-content #ipython-notebook .nb-output img {
+  max-width: 100%;
+}
+.repository.file.list #file-content #ipython-notebook .nb-output:before,
+.repository.file.list #file-content #ipython-notebook .nb-input:before {
+  position: absolute;
+  font-family: monospace;
+  color: #999;
+  left: -7.5em;
+  width: 7em;
+  text-align: right;
+}
+.repository.file.list #file-content #ipython-notebook .nb-input:before {
+  content: "In [" attr(data-prompt-number) "]:";
+}
+.repository.file.list #file-content #ipython-notebook .nb-output:before {
+  content: "Out [" attr(data-prompt-number) "]:";
+}
+.repository.file.list #file-content #ipython-notebook .nb-markdown-cell {
+  background-color: #eee;
+  margin-left: -80px;
+  padding: 11.5px 10px 19.5px 80px;
+}
+.repository.file.list #file-content #ipython-notebook div[style="max-height:1000px;max-width:1500px;overflow:auto;"] {
+  max-height: none !important;
+}
 .repository.file.list #file-content .plain-text {
 .repository.file.list #file-content .plain-text {
   font-size: 14px;
   font-size: 14px;
   padding: 10px 15px;
   padding: 10px 15px;
@@ -2539,18 +2614,18 @@ footer .ui.language .menu {
   text-align: center;
   text-align: center;
 }
 }
 .organization.new.org form .header {
 .organization.new.org form .header {
-  padding-left: 280px !important;
+  padding-left: 280px;
 }
 }
 .organization.new.org form .inline.field > label {
 .organization.new.org form .inline.field > label {
   text-align: right;
   text-align: right;
-  width: 250px !important;
+  width: 250px;
   word-wrap: break-word;
   word-wrap: break-word;
 }
 }
 .organization.new.org form .help {
 .organization.new.org form .help {
-  margin-left: 265px !important;
+  margin-left: 265px;
 }
 }
 .organization.new.org form .optional .title {
 .organization.new.org form .optional .title {
-  margin-left: 250px !important;
+  margin-left: 250px;
 }
 }
 .organization.new.org form input,
 .organization.new.org form input,
 .organization.new.org form textarea {
 .organization.new.org form textarea {

+ 89 - 0
public/less/_repository.less

@@ -251,6 +251,95 @@
 				}
 				}
 			}
 			}
 
 
+			#ipython-notebook {
+				margin-left: 80px;
+
+				.nb-notebook {
+					line-height: 1.5;    
+				}
+
+				.nb-stdout, .nb-stderr {
+					white-space: pre-wrap;
+					margin: 1em 0;
+					padding: 0.1em 0.5em;
+				}
+
+				.nb-stderr {
+					background-color: #FAA;
+				}
+
+				.nb-cell + .nb-cell {
+					margin-top: 0.5em;
+				}
+
+				.nb-output table {
+					border: 1px solid #000;
+					border-collapse: collapse;
+				}
+
+				.nb-output th {
+					font-weight: bold;
+				}
+
+				.nb-output th, .nb-output td {
+					border: 1px solid #000;
+					padding: 0.25em;    
+					text-align: left;
+					vertical-align: middle;
+					border-collapse: collapse;
+				}
+
+				.nb-cell {
+					position: relative;    
+				}
+
+				.nb-raw-cell {
+					white-space: pre-wrap;
+					background-color: #f5f2f0;
+					font-family: Consolas, Monaco, 'Andale Mono', monospace;
+					padding: 1em;
+					margin: .5em 0;
+				}
+
+				.nb-output {
+					min-height: 1em;    
+					width: 100%;
+					overflow-x: scroll;
+					border-right: 1px dotted #CCC;
+				}
+
+				.nb-output img {
+					max-width: 100%;    
+				}
+
+				.nb-output:before, .nb-input:before {
+					position: absolute;    
+					font-family: monospace;
+					color: #999;
+					left: -7.5em;
+					width: 7em;
+					text-align: right;
+				}
+
+				.nb-input:before {
+					content: "In [" attr(data-prompt-number) "]:";
+				}
+				.nb-output:before {
+					content: "Out [" attr(data-prompt-number) "]:";
+				}
+
+				.nb-markdown-cell {
+					background-color: #eee;
+					margin-left: -80px;
+					padding: 11.5px 10px 19.5px 80px;
+				}
+
+				// Fix pandas dataframe formatting
+				div[style="max-height:1000px;max-width:1500px;overflow:auto;"] {
+					max-height: none !important;    
+				}
+			}
+
 			.plain-text {
 			.plain-text {
 				font-size: 14px;
 				font-size: 14px;
 				padding: 10px 15px;
 				padding: 10px 15px;

+ 19 - 0
public/plugins/marked-0.3.6/LICENSE

@@ -0,0 +1,19 @@
+Copyright (c) 2011-2014, Christopher Jeffrey (https://github.com/chjj/)
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.

文件差異過大導致無法顯示
+ 5 - 0
public/plugins/marked-0.3.6/marked.min.js


+ 21 - 0
public/plugins/notebookjs-0.2.6/LICENSE.txt

@@ -0,0 +1,21 @@
+The MIT License (MIT)
+
+Copyright (c) 2014, Jeremy Singer-Vine
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.

文件差異過大導致無法顯示
+ 0 - 0
public/plugins/notebookjs-0.2.6/notebook.min.js


+ 2 - 0
routers/repo/view.go

@@ -156,6 +156,8 @@ func renderFile(ctx *context.Context, entry *git.TreeEntry, treeLink, rawLink st
 		ctx.Data["IsMarkdown"] = isMarkdown
 		ctx.Data["IsMarkdown"] = isMarkdown
 		ctx.Data["ReadmeExist"] = isMarkdown && markdown.IsReadmeFile(blob.Name())
 		ctx.Data["ReadmeExist"] = isMarkdown && markdown.IsReadmeFile(blob.Name())
 
 
+		ctx.Data["IsIPyNB"] = strings.HasSuffix(blob.Name(), ".ipynb")
+
 		if isMarkdown {
 		if isMarkdown {
 			ctx.Data["FileContent"] = string(markdown.Render(buf, path.Dir(treeLink), ctx.Repo.Repository.ComposeMetas()))
 			ctx.Data["FileContent"] = string(markdown.Render(buf, path.Dir(treeLink), ctx.Repo.Repository.ComposeMetas()))
 		} else {
 		} else {

+ 6 - 0
templates/base/head.tmpl

@@ -21,6 +21,12 @@
 	<link rel="stylesheet" href="{{AppSubUrl}}/assets/font-awesome-4.6.3/css/font-awesome.min.css">
 	<link rel="stylesheet" href="{{AppSubUrl}}/assets/font-awesome-4.6.3/css/font-awesome.min.css">
 	<link rel="stylesheet" href="{{AppSubUrl}}/assets/octicons-4.3.0/octicons.min.css">
 	<link rel="stylesheet" href="{{AppSubUrl}}/assets/octicons-4.3.0/octicons.min.css">
 
 
+	<!-- notebook.js for rendering ipython notebooks and marked.js for rendering markdown in notebooks -->
+	{{if .IsIPyNB }}
+		<script src="{{AppSubUrl}}/plugins/notebookjs-0.2.6/notebook.min.js"></script>
+		<script src="{{AppSubUrl}}/plugins/marked-0.3.6/marked.min.js"></script>
+	{{end}}
+
 	{{if .RequireSimpleMDE}}
 	{{if .RequireSimpleMDE}}
 		<link rel="stylesheet" href="{{AppSubUrl}}/plugins/simplemde-1.10.1/simplemde.min.css">
 		<link rel="stylesheet" href="{{AppSubUrl}}/plugins/simplemde-1.10.1/simplemde.min.css">
 		<script src="{{AppSubUrl}}/plugins/simplemde-1.10.1/simplemde.min.js"></script>
 		<script src="{{AppSubUrl}}/plugins/simplemde-1.10.1/simplemde.min.js"></script>

+ 20 - 0
templates/repo/view_file.tmpl

@@ -39,6 +39,26 @@
 		<div class="file-view {{if .IsMarkdown}}markdown{{else if .ReadmeInList}}plain-text{{else if .IsTextFile}}code-view{{end}} has-emoji">
 		<div class="file-view {{if .IsMarkdown}}markdown{{else if .ReadmeInList}}plain-text{{else if .IsTextFile}}code-view{{end}} has-emoji">
 			{{if or .IsMarkdown .ReadmeInList}}
 			{{if or .IsMarkdown .ReadmeInList}}
 				{{if .FileContent}}{{.FileContent | Str2html}}{{end}}
 				{{if .FileContent}}{{.FileContent | Str2html}}{{end}}
+			{{else if .IsIPyNB}}
+				{{if .FileContent}}
+					<div id="ipython-notebook"></div>
+					<script>
+						var rendered = null;
+						$.getJSON("{{.RawFileLink}}", null, function(notebook_json) {
+							var notebook = nb.parse(notebook_json);
+							rendered = notebook.render();
+							$("#ipython-notebook").append(rendered);
+							$("#ipython-notebook code").each(function(i, block) {
+								$(block).addClass("py").addClass("python");
+								hljs.highlightBlock(block);
+							});
+
+							$("#ipython-notebook .nb-markdown-cell").each(function(i, markdown) {
+								$(markdown).html(marked($(markdown).html()));
+							});
+						});
+					</script>
+				{{end}}
 			{{else if not .IsTextFile}}
 			{{else if not .IsTextFile}}
 				<div class="view-raw ui center">
 				<div class="view-raw ui center">
 					{{if .IsImageFile}}
 					{{if .IsImageFile}}

部分文件因文件數量過多而無法顯示