--- app/assets/stylesheets/less/_page.less
+++ app/assets/stylesheets/less/_page.less
... | ... | @@ -6766,3 +6766,21 @@ |
6766 | 6766 |
padding-top: 5px; |
6767 | 6767 |
clear: both; |
6768 | 6768 |
} |
6769 |
+ |
|
6770 |
+.file-path-wrap { |
|
6771 |
+ font-size: 16px; |
|
6772 |
+ .new-file-name { |
|
6773 |
+ padding: 0 0; |
|
6774 |
+ margin-bottom: 0; |
|
6775 |
+ margin-left: 3px; |
|
6776 |
+ font-size: 16px; |
|
6777 |
+ border: none; |
|
6778 |
+ border-bottom: 1px solid #999; |
|
6779 |
+ border-radius: 0 !important; |
|
6780 |
+ height: 36px; |
|
6781 |
+ } |
|
6782 |
+} |
|
6783 |
+ |
|
6784 |
+#new-file-link { |
|
6785 |
+ margin: 0 5px; |
|
6786 |
+} |
--- app/controllers/BoardApp.java
+++ app/controllers/BoardApp.java
... | ... | @@ -18,7 +18,6 @@ |
18 | 18 |
import org.apache.commons.collections.CollectionUtils; |
19 | 19 |
import org.apache.commons.lang3.StringUtils; |
20 | 20 |
import org.eclipse.jgit.lib.ObjectId; |
21 |
-import play.api.data.validation.ValidationError; |
|
22 | 21 |
import play.data.Form; |
23 | 22 |
import play.db.ebean.Transactional; |
24 | 23 |
import play.libs.Json; |
... | ... | @@ -173,6 +172,11 @@ |
173 | 172 |
preparedBodyText = StringUtils.defaultIfBlank(project.getIssueTemplate(), ""); |
174 | 173 |
} |
175 | 174 |
|
175 |
+ if (textFileEditRequested()) { |
|
176 |
+ preparedBodyText = GitUtil.getReadTextFile(project, |
|
177 |
+ getBranchNameFromQueryString(), request().getQueryString("path")); |
|
178 |
+ } |
|
179 |
+ |
|
176 | 180 |
return ok(create.render("post.new", new Form<>(Posting.class), project, isAllowedToNotice, preparedBodyText)); |
177 | 181 |
} |
178 | 182 |
|
... | ... | @@ -186,6 +190,14 @@ |
186 | 190 |
|
187 | 191 |
private static boolean issueTemplateEditRequested() { |
188 | 192 |
return request().getQueryString("issueTemplate") != null; |
193 |
+ } |
|
194 |
+ |
|
195 |
+ private static boolean textFileEditRequested() { |
|
196 |
+ return request().getQueryString("path") != null; |
|
197 |
+ } |
|
198 |
+ |
|
199 |
+ private static String getBranchNameFromQueryString() { |
|
200 |
+ return request().getQueryString("branch"); |
|
189 | 201 |
} |
190 | 202 |
|
191 | 203 |
@Transactional |
... | ... | @@ -225,6 +237,11 @@ |
225 | 237 |
return redirect(routes.ProjectApp.project(project.owner, projectName)); |
226 | 238 |
} |
227 | 239 |
|
240 |
+ if(StringUtils.isNotEmpty(post.path) && UserApp.currentUser().isMemberOf(project)){ |
|
241 |
+ GitUtil.commitTextFile(project, post.branch, post.path, post.body, post.title); |
|
242 |
+ return redirect(routes.CodeApp.codeBrowserWithBranch(project.owner, project.name, post.branch, HttpUtil.getEncodeEachPathName(post.path))); |
|
243 |
+ } |
|
244 |
+ |
|
228 | 245 |
post.save(); |
229 | 246 |
attachUploadFilesToPost(post.asResource()); |
230 | 247 |
NotificationEvent.afterNewPost(post); |
--- app/controllers/CodeApp.java
+++ app/controllers/CodeApp.java
... | ... | @@ -1,23 +1,9 @@ |
1 | 1 |
/** |
2 |
- * Yobi, Project Hosting SW |
|
3 |
- * |
|
4 |
- * Copyright 2012 NAVER Corp. |
|
5 |
- * http://yobi.io |
|
6 |
- * |
|
7 |
- * @author Ahn Hyeok Jun |
|
8 |
- * |
|
9 |
- * Licensed under the Apache License, Version 2.0 (the "License"); |
|
10 |
- * you may not use this file except in compliance with the License. |
|
11 |
- * You may obtain a copy of the License at |
|
12 |
- * |
|
13 |
- * http://www.apache.org/licenses/LICENSE-2.0 |
|
14 |
- * |
|
15 |
- * Unless required by applicable law or agreed to in writing, software |
|
16 |
- * distributed under the License is distributed on an "AS IS" BASIS, |
|
17 |
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|
18 |
- * See the License for the specific language governing permissions and |
|
19 |
- * limitations under the License. |
|
20 |
- */ |
|
2 |
+ * Yona, 21st Century Project Hosting SW |
|
3 |
+ * <p> |
|
4 |
+ * Copyright Yona & Yobi Authors & NAVER Corp. |
|
5 |
+ * https://yona.io |
|
6 |
+ **/ |
|
21 | 7 |
package controllers; |
22 | 8 |
|
23 | 9 |
import actions.CodeAccessCheckAction; |
--- app/models/Posting.java
+++ app/models/Posting.java
... | ... | @@ -29,6 +29,14 @@ |
29 | 29 |
@Transient |
30 | 30 |
public String issueTemplate; |
31 | 31 |
|
32 |
+ //ToDo: Sperate it from posting for online commit |
|
33 |
+ @Transient |
|
34 |
+ public String path; |
|
35 |
+ |
|
36 |
+ //ToDo: Sperate it from posting for online commit |
|
37 |
+ @Transient |
|
38 |
+ public String branch; |
|
39 |
+ |
|
32 | 40 |
@OneToMany(cascade = CascadeType.ALL) |
33 | 41 |
public List<PostingComment> comments; |
34 | 42 |
|
--- app/playRepository/BareCommit.java
+++ app/playRepository/BareCommit.java
... | ... | @@ -22,15 +22,27 @@ |
22 | 22 |
|
23 | 23 |
import models.Project; |
24 | 24 |
import models.User; |
25 |
+import org.eclipse.jgit.api.Git; |
|
26 |
+import org.eclipse.jgit.api.errors.ConcurrentRefUpdateException; |
|
27 |
+import org.eclipse.jgit.api.errors.JGitInternalException; |
|
28 |
+import org.eclipse.jgit.dircache.DirCache; |
|
29 |
+import org.eclipse.jgit.dircache.DirCacheBuilder; |
|
30 |
+import org.eclipse.jgit.dircache.DirCacheEditor; |
|
31 |
+import org.eclipse.jgit.dircache.DirCacheEntry; |
|
32 |
+import org.eclipse.jgit.internal.JGitText; |
|
25 | 33 |
import org.eclipse.jgit.lib.*; |
26 | 34 |
import org.eclipse.jgit.revwalk.RevCommit; |
27 | 35 |
import org.eclipse.jgit.revwalk.RevWalk; |
28 | 36 |
import org.eclipse.jgit.treewalk.CanonicalTreeParser; |
37 |
+import org.eclipse.jgit.treewalk.TreeWalk; |
|
29 | 38 |
import utils.Config; |
30 | 39 |
|
31 | 40 |
import java.io.File; |
41 |
+import java.io.FileInputStream; |
|
32 | 42 |
import java.io.IOException; |
43 |
+import java.io.InputStream; |
|
33 | 44 |
import java.nio.channels.OverlappingFileLockException; |
45 |
+import java.text.MessageFormat; |
|
34 | 46 |
|
35 | 47 |
import static org.eclipse.jgit.lib.Constants.HEAD; |
36 | 48 |
import static org.eclipse.jgit.lib.Constants.OBJ_BLOB; |
... | ... | @@ -232,4 +244,134 @@ |
232 | 244 |
public void setRefName(String refName){ |
233 | 245 |
this.refName = refName; |
234 | 246 |
} |
247 |
+ |
|
248 |
+ // Bare commit. It is referenced from https://gist.github.com/porcelli/3882505 |
|
249 |
+ public ObjectId commitTextFile(final String branchName, final String path, String text, final String message) throws IOException { |
|
250 |
+ this.file = new File(this.repository.getDirectory(), path); |
|
251 |
+ org.apache.commons.io.FileUtils.write(this.file, text); |
|
252 |
+ |
|
253 |
+ ObjectId commitId = null; |
|
254 |
+ Git git = new Git(this.repository); |
|
255 |
+ try (final ObjectInserter inserter = git.getRepository().newObjectInserter()) { |
|
256 |
+ // Create the in-memory index of the new/updated issue. |
|
257 |
+ this.headObjectId = git.getRepository().resolve(this.refName + "^{commit}"); |
|
258 |
+ final DirCache index = createTemporaryIndex(git, this.headObjectId, path, file); |
|
259 |
+ final ObjectId indexTreeId = index.writeTree(inserter); |
|
260 |
+ |
|
261 |
+ // Create a commit object |
|
262 |
+ final CommitBuilder commit = getCommitBuilder(message, indexTreeId); |
|
263 |
+ |
|
264 |
+ // Insert the commit into the repository |
|
265 |
+ commitId = inserter.insert(commit); |
|
266 |
+ inserter.flush(); |
|
267 |
+ |
|
268 |
+ final RefUpdate ru = getRefUpdate(branchName, commitId, git); |
|
269 |
+ final RefUpdate.Result rc = ru.forceUpdate(); |
|
270 |
+ switch (rc) { |
|
271 |
+ case NEW: |
|
272 |
+ case FORCED: |
|
273 |
+ case FAST_FORWARD: |
|
274 |
+ break; |
|
275 |
+ case REJECTED: |
|
276 |
+ case LOCK_FAILURE: |
|
277 |
+ throw new ConcurrentRefUpdateException(JGitText.get().couldNotLockHEAD, ru.getRef(), rc); |
|
278 |
+ default: |
|
279 |
+ throw new JGitInternalException(MessageFormat.format(JGitText.get().updatingRefFailed, Constants.HEAD, commitId.toString(), rc)); |
|
280 |
+ } |
|
281 |
+ } catch (final Throwable t) { |
|
282 |
+ throw new RuntimeException(t); |
|
283 |
+ } |
|
284 |
+ |
|
285 |
+ return commitId; |
|
286 |
+ } |
|
287 |
+ |
|
288 |
+ private RefUpdate getRefUpdate(String branchName, ObjectId commitId, Git git) throws IOException { |
|
289 |
+ final RevWalk revWalk = new RevWalk(git.getRepository()); |
|
290 |
+ final RevCommit revCommit = revWalk.parseCommit(commitId); |
|
291 |
+ final RefUpdate ru = git.getRepository().updateRef("refs/heads/" + branchName); |
|
292 |
+ if (this.headObjectId == null) { |
|
293 |
+ ru.setExpectedOldObjectId(ObjectId.zeroId()); |
|
294 |
+ } else { |
|
295 |
+ ru.setExpectedOldObjectId(this.headObjectId); |
|
296 |
+ } |
|
297 |
+ ru.setNewObjectId(commitId); |
|
298 |
+ ru.setRefLogMessage("commit: " + revCommit.getShortMessage(), false); |
|
299 |
+ revWalk.close(); |
|
300 |
+ return ru; |
|
301 |
+ } |
|
302 |
+ |
|
303 |
+ private CommitBuilder getCommitBuilder(String message, ObjectId indexTreeId) { |
|
304 |
+ final CommitBuilder commit = new CommitBuilder(); |
|
305 |
+ commit.setAuthor(this.getPersonIdent()); |
|
306 |
+ commit.setCommitter(this.getPersonIdent()); |
|
307 |
+ commit.setEncoding(Constants.CHARACTER_ENCODING); |
|
308 |
+ commit.setMessage(message); |
|
309 |
+ //headId can be null if the repository has no commit yet |
|
310 |
+ if (this.headObjectId != null) { |
|
311 |
+ commit.setParentId(this.headObjectId); |
|
312 |
+ } |
|
313 |
+ commit.setTreeId(indexTreeId); |
|
314 |
+ return commit; |
|
315 |
+ } |
|
316 |
+ |
|
317 |
+ private static DirCache createTemporaryIndex(final Git git, final ObjectId headId, final String path, final File file) { |
|
318 |
+ final DirCache inCoreIndex = DirCache.newInCore(); |
|
319 |
+ final DirCacheBuilder dcBuilder = inCoreIndex.builder(); |
|
320 |
+ final ObjectInserter inserter = git.getRepository().newObjectInserter(); |
|
321 |
+ |
|
322 |
+ try { |
|
323 |
+ if (file != null) { |
|
324 |
+ final DirCacheEntry dcEntry = new DirCacheEntry(path); |
|
325 |
+ dcEntry.setLength(file.length()); |
|
326 |
+ dcEntry.setLastModified(file.lastModified()); |
|
327 |
+ dcEntry.setFileMode(FileMode.REGULAR_FILE); |
|
328 |
+ |
|
329 |
+ final InputStream inputStream = new FileInputStream(file); |
|
330 |
+ try { |
|
331 |
+ dcEntry.setObjectId(inserter.insert(Constants.OBJ_BLOB, file.length(), inputStream)); |
|
332 |
+ } finally { |
|
333 |
+ inputStream.close(); |
|
334 |
+ } |
|
335 |
+ |
|
336 |
+ dcBuilder.add(dcEntry); |
|
337 |
+ } |
|
338 |
+ |
|
339 |
+ if (headId != null) { |
|
340 |
+ final TreeWalk treeWalk = new TreeWalk(git.getRepository()); |
|
341 |
+ final int hIdx = treeWalk.addTree(new RevWalk(git.getRepository()).parseTree(headId)); |
|
342 |
+ treeWalk.setRecursive(true); |
|
343 |
+ |
|
344 |
+ while (treeWalk.next()) { |
|
345 |
+ final String walkPath = treeWalk.getPathString(); |
|
346 |
+ final CanonicalTreeParser hTree = treeWalk.getTree(hIdx, CanonicalTreeParser.class); |
|
347 |
+ |
|
348 |
+ if (!walkPath.equals(path)) { |
|
349 |
+ // add entries from HEAD for all other paths |
|
350 |
+ // create a new DirCacheEntry with data retrieved from HEAD |
|
351 |
+ final DirCacheEntry dcEntry = new DirCacheEntry(walkPath); |
|
352 |
+ dcEntry.setObjectId(hTree.getEntryObjectId()); |
|
353 |
+ dcEntry.setFileMode(hTree.getEntryFileMode()); |
|
354 |
+ |
|
355 |
+ // add to temporary in-core index |
|
356 |
+ dcBuilder.add(dcEntry); |
|
357 |
+ } |
|
358 |
+ } |
|
359 |
+ treeWalk.close(); |
|
360 |
+ } |
|
361 |
+ |
|
362 |
+ dcBuilder.finish(); |
|
363 |
+ } catch (Exception e) { |
|
364 |
+ throw new RuntimeException(e); |
|
365 |
+ } finally { |
|
366 |
+ inserter.close(); |
|
367 |
+ } |
|
368 |
+ |
|
369 |
+ if (file == null) { |
|
370 |
+ final DirCacheEditor editor = inCoreIndex.editor(); |
|
371 |
+ editor.add(new DirCacheEditor.DeleteTree(path)); |
|
372 |
+ editor.finish(); |
|
373 |
+ } |
|
374 |
+ |
|
375 |
+ return inCoreIndex; |
|
376 |
+ } |
|
235 | 377 |
} |
+++ app/utils/GitUtil.java
... | ... | @@ -0,0 +1,40 @@ |
1 | +/** | |
2 | + * Yona, 21st Century Project Hosting SW | |
3 | + * <p> | |
4 | + * Copyright Yona & Yobi Authors & NAVER Corp. | |
5 | + * https://yona.io | |
6 | + **/ | |
7 | +package utils; | |
8 | + | |
9 | +import controllers.UserApp; | |
10 | +import models.Project; | |
11 | +import org.eclipse.jgit.lib.ObjectId; | |
12 | +import playRepository.BareCommit; | |
13 | +import playRepository.RepositoryService; | |
14 | + | |
15 | +import java.io.IOException; | |
16 | + | |
17 | +public class GitUtil { | |
18 | + public static String getReadTextFile(Project project, String branchName, String filenameWithPath) { | |
19 | + try { | |
20 | + byte[] bytes = RepositoryService.getRepository(project) | |
21 | + .getRawFile(branchName, filenameWithPath); | |
22 | + return new String(bytes, FileUtil.detectCharset(bytes)); | |
23 | + } catch (Exception e) { | |
24 | + return null; | |
25 | + } | |
26 | + } | |
27 | + | |
28 | + public synchronized static void commitTextFile(Project project, String branchName, String path, String text, String msg) { | |
29 | + BareCommit bare = new BareCommit(project, UserApp.currentUser()); | |
30 | + bare.setRefName(branchName); | |
31 | + ObjectId objectId = null; | |
32 | + try { | |
33 | + objectId = bare.commitTextFile(branchName, path, text, msg); | |
34 | + } catch (IOException e) { | |
35 | + e.printStackTrace(); | |
36 | + } | |
37 | + play.Logger.debug("Online Commit: " + project.name + ":" + path + ":" + objectId); | |
38 | + } | |
39 | + | |
40 | +} |
--- app/utils/HttpUtil.java
+++ app/utils/HttpUtil.java
... | ... | @@ -218,4 +218,14 @@ |
218 | 218 |
return targetStr; |
219 | 219 |
} |
220 | 220 |
} |
221 |
+ |
|
222 |
+ // It is made for path which contains UTF8 chars |
|
223 |
+ public static String getEncodeEachPathName(String path){ |
|
224 |
+ String[] paths = path.split("/"); |
|
225 |
+ String[] encodedPaths = new String[paths.length]; |
|
226 |
+ for ( int i = 0; i < paths.length; i++ ) { |
|
227 |
+ encodedPaths[i] = HttpUtil.encodeUrlString(paths[i]); |
|
228 |
+ } |
|
229 |
+ return String.join("/", encodedPaths); |
|
230 |
+ } |
|
221 | 231 |
} |
--- app/views/board/create.scala.html
+++ app/views/board/create.scala.html
... | ... | @@ -12,15 +12,34 @@ |
12 | 12 |
@import utils.TemplateHelper._ |
13 | 13 |
@import utils.HtmlUtil._ |
14 | 14 |
@import models.enumeration._ |
15 |
-@implicitField = @{ helper.FieldConstructor(simpleForm) } |
|
16 | 15 |
|
17 |
-@titleMessage = @{ |
|
18 |
- if( !requestHeader.getQueryString("readme").equals(None) ) { |
|
19 |
- "Update README.md" |
|
20 |
- } else if( !requestHeader.getQueryString("issueTemplate").equals(None) ) { |
|
21 |
- "ISSUE_TEMPLATE.md: Project Issue Template" |
|
22 |
- } |
|
23 |
-} |
|
16 |
+ @implicitField = @{ |
|
17 |
+ helper.FieldConstructor(simpleForm) |
|
18 |
+ } |
|
19 |
+ |
|
20 |
+ @issueTemplate = @{ |
|
21 |
+ requestHeader.getQueryString("issueTemplate") |
|
22 |
+ } |
|
23 |
+ |
|
24 |
+ @titleMessage = @{ |
|
25 |
+ if(!requestHeader.getQueryString("readme").equals(None)) { |
|
26 |
+ "Update README.md" |
|
27 |
+ } else if(!issueTemplate.equals(None)) { |
|
28 |
+ "ISSUE_TEMPLATE.md: Project Issue Template" |
|
29 |
+ } |
|
30 |
+ } |
|
31 |
+ |
|
32 |
+ @path = @{ |
|
33 |
+ requestHeader.getQueryString("path") |
|
34 |
+ } |
|
35 |
+ |
|
36 |
+ @branch = @{ |
|
37 |
+ requestHeader.getQueryString("branch") |
|
38 |
+ } |
|
39 |
+ |
|
40 |
+ @isEdit = @{ |
|
41 |
+ !requestHeader.getQueryString("edit").equals(None) |
|
42 |
+ } |
|
24 | 43 |
|
25 | 44 |
@projectLayout(title, project, utils.MenuType.BOARD) { |
26 | 45 |
@projectMenu(project, utils.MenuType.BOARD, "main-menu-only") |
... | ... | @@ -29,12 +48,9 @@ |
29 | 48 |
<form action="@routes.BoardApp.newPost(project.owner, project.name)" method="post" enctype="multipart/form-data" class="nm"> |
30 | 49 |
<div class="content-wrap frm-wrap"> |
31 | 50 |
<dl> |
32 |
- <dt> |
|
33 |
- <label for="title">@Messages("title")</label> |
|
34 |
- </dt> |
|
35 | 51 |
<dd> |
36 | 52 |
@defining(form.errors().get("title")) { errors => |
37 |
- <input type="text" id="title" name="title" class="zen-mode text title @if(errors != null) {error}" maxlength="250" tabindex="1" value="@titleMessage"/> |
|
53 |
+ <input type="text" id="title" name="title" class="zen-mode text title @if(errors != null) {error}" maxlength="250" tabindex="1" value="@titleMessage" placeholder="@if(path.equals(None)){@Messages("title")}else{@Messages("code.commitMsg")}"/> |
|
38 | 54 |
@if(errors != null) { |
39 | 55 |
<div class="message"> |
40 | 56 |
@for(error <- errors) { |
... | ... | @@ -45,18 +61,23 @@ |
45 | 61 |
} |
46 | 62 |
</dd> |
47 | 63 |
<dd> |
48 |
- @if(!requestHeader.getQueryString("issueTemplate").equals(None)){ |
|
64 |
+ @if(!issueTemplate.equals(None)){ |
|
49 | 65 |
<div class="attach-wrap"> |
50 | 66 |
<span class="help help-droppable">@Messages("issue.template.no.attachment.allow")</span> |
51 | 67 |
</div> |
52 | 68 |
} |
69 |
+ @if(!path.equals(None)){ |
|
70 |
+ <div class="file-path-wrap"> |
|
71 |
+ <span class="help file-path">@branch: /@path @if(!isEdit){<input type="text" name="new-file-name" class="new-file-name" value="" placeholder="filename.." tabindex="2">}</span> |
|
72 |
+ </div> |
|
73 |
+ } |
|
53 | 74 |
</dd> |
54 | 75 |
<dd style="position: relative;"> |
55 |
- @common.editor("body", preparedPostBody, "tabindex=2") |
|
76 |
+ @common.editor("body", preparedPostBody, "tabindex=3") |
|
56 | 77 |
</dd> |
57 | 78 |
</dl> |
58 | 79 |
@** fileUploader **@ |
59 |
- @if(requestHeader.getQueryString("issueTemplate").equals(None)) { |
|
80 |
+ @if(issueTemplate.equals(None) && path.equals(None)) { |
|
60 | 81 |
@if(!UserApp.currentUser.isAnonymous) { |
61 | 82 |
@common.fileUploader(ResourceType.BOARD_POST, null) |
62 | 83 |
} |
... | ... | @@ -64,14 +85,16 @@ |
64 | 85 |
@** end of fileUploader **@ |
65 | 86 |
|
66 | 87 |
<div class="right-txt mt10 mb10"> |
67 |
- @if(isAllowedToNotice && requestHeader.getQueryString("issueTemplate").equals(None)){ |
|
88 |
+ @if(isAllowedToNotice && issueTemplate.equals(None) && path.equals(None)){ |
|
68 | 89 |
<label class="checkbox"> |
69 | 90 |
<input type="checkbox" id="notice" name="notice"> |
70 | 91 |
@Messages("post.notice.label") |
71 | 92 |
</label> |
72 | 93 |
} |
73 | 94 |
|
74 |
- <input type="hidden" id="issueTemplate" name="issueTemplate" value="@requestHeader.getQueryString("issueTemplate")"> |
|
95 |
+ <input type="hidden" id="issueTemplate" name="issueTemplate" value="@issueTemplate"> |
|
96 |
+ <input type="hidden" id="branch" name="branch" value="@branch"> |
|
97 |
+ <input type="hidden" id="path" name="path" value="@path"> |
|
75 | 98 |
@if(isProjectResourceCreatable(UserApp.currentUser(), project, ResourceType.COMMIT)){ |
76 | 99 |
@if(project.isGit && !requestHeader.getQueryString("readme").equals(None)){ |
77 | 100 |
<label class="checkbox"> |
... | ... | @@ -107,6 +130,27 @@ |
107 | 130 |
"target": 'textarea[id^=editor-]', |
108 | 131 |
"url" : "@Html(routes.ProjectApp.mentionList(project.owner, project.name).toString())" |
109 | 132 |
}); |
133 |
+ |
|
134 |
+ // Followings are used for online commit |
|
135 |
+ @if(!path.equals(None)) { |
|
136 |
+ var textarea = $(".textarea-box > textarea"); |
|
137 |
+ if (textarea.val() !== "") { |
|
138 |
+ textarea.css("overflow", "hidden"); |
|
139 |
+ textarea.css("height", "1px"); |
|
140 |
+ var newHeight = (25 + textarea.prop('scrollHeight')); |
|
141 |
+ var minHeight = 300; |
|
142 |
+ console.log(newHeight); |
|
143 |
+ textarea.css("height", (minHeight > newHeight ? minHeight: newHeight) + "px"); |
|
144 |
+ textarea.css("overflow", "auto"); |
|
145 |
+ } |
|
146 |
+ |
|
147 |
+ $(".new-file-name").on("keyup", function () { |
|
148 |
+ $("#path").val("@path" + $(this).val()); |
|
149 |
+ }); |
|
150 |
+ |
|
151 |
+ $(".project-menu-gruop > li").removeClass("active"); |
|
152 |
+ $(".code-menu").addClass("active"); |
|
153 |
+ } |
|
110 | 154 |
}); |
111 | 155 |
</script> |
112 | 156 |
} |
--- app/views/code/partial_view_file.scala.html
+++ app/views/code/partial_view_file.scala.html
... | ... | @@ -59,6 +59,10 @@ |
59 | 59 |
<a href="@filePath" class="ybtn" target="_blank"> |
60 | 60 |
<i class="yobicon-download-alt yobicon-white vmiddle"></i> Raw |
61 | 61 |
</a> |
62 |
+ @if(!UserApp.currentUser().isAnonymous) { |
|
63 |
+ <a href="@routes.BoardApp.newPostForm(project.owner, project.name)?path=@path&branch=@branchItemName(branch)&edit=true" class="ybtn"> |
|
64 |
+ Edit</a> |
|
65 |
+ } |
|
62 | 66 |
} |
63 | 67 |
<a id="open-in-browser" href="@pathToOpenFile" class="ybtn" target="_blank" data-content='@Messages("code.open.desc")'> |
64 | 68 |
<i class="yobicon-download-alt yobicon-white vmiddle"></i> @Messages("code.open") |
--- app/views/code/view.scala.html
+++ app/views/code/view.scala.html
... | ... | @@ -5,6 +5,7 @@ |
5 | 5 |
* https://yona.io |
6 | 6 |
**@ |
7 | 7 |
@import com.fasterxml.jackson.databind.node.ObjectNode |
8 |
+@import org.apache.commons.io._ |
|
8 | 9 |
@(project:Project, branches:List[String], recursiveData:List[ObjectNode], branch:String, path:String) |
9 | 10 |
|
10 | 11 |
@import utils.TemplateHelper._ |
... | ... | @@ -34,6 +35,14 @@ |
34 | 35 |
} |
35 | 36 |
Html("<a href=\"" + getCorrectedPath(basePath, partialPath) + "\">" + p + "</a>") |
36 | 37 |
} |
38 |
+ } |
|
39 |
+} |
|
40 |
+ |
|
41 |
+@dir = @{ |
|
42 |
+ if(fieldText(recursiveData.last, "type").eq("folder") && path.length != 0){ |
|
43 |
+ path + "/" |
|
44 |
+ } else { |
|
45 |
+ FilenameUtils.getPath(path) |
|
37 | 46 |
} |
38 | 47 |
} |
39 | 48 |
|
... | ... | @@ -80,6 +89,11 @@ |
80 | 89 |
<a href="@routes.CodeApp.download(project.owner, project.name, URLEncoder.encode(branch, "UTF-8"))" class="ybtn"> |
81 | 90 |
@Messages("code.download")</a> |
82 | 91 |
</div> |
92 |
+ @if(!UserApp.currentUser().isAnonymous) { |
|
93 |
+ <div class="pull-right"> |
|
94 |
+ <a id="new-file-link" href="@routes.BoardApp.newPostForm(project.owner, project.name)?path=@dir&branch=@branchItemName(branch)" class="ybtn">@Messages("code.new.file")</a> |
|
95 |
+ </div> |
|
96 |
+ } |
|
83 | 97 |
} |
84 | 98 |
</div> |
85 | 99 |
|
--- app/views/projectMenu.scala.html
+++ app/views/projectMenu.scala.html
... | ... | @@ -58,7 +58,7 @@ |
58 | 58 |
@defining(project.menuSetting){ menuSetting => |
59 | 59 |
@if(menuSetting.code) { |
60 | 60 |
@if(!project.isCodeAccessibleMemberOnly || project.hasMember(UserApp.currentUser())) { |
61 |
- <li class="@isActiveMenu(MenuType.CODE)"> |
|
61 |
+ <li class="code-menu @isActiveMenu(MenuType.CODE)"> |
|
62 | 62 |
<a href="@routes.CodeApp.codeBrowser(project.owner, project.name)"> |
63 | 63 |
<span class="menu-name">@Messages("menu.code")</span><span class="short-menu">C</span> |
64 | 64 |
</a> |
--- conf/messages
+++ conf/messages
... | ... | @@ -109,6 +109,7 @@ |
109 | 109 |
code.history = Change history |
110 | 110 |
code.isBinary = Binary file is not shown |
111 | 111 |
code.looseFileSizeLimitForCodeBrowser = Site Administrator can loose the limit by modifying "application.codeBrowser.viewer.maxFileSize" in the configuration file. |
112 |
+code.new.file = New file |
|
112 | 113 |
code.newer = Newer |
113 | 114 |
code.noChanges = No changes |
114 | 115 |
code.nocommits = No commit exists |
--- conf/messages.ko-KR
+++ conf/messages.ko-KR
... | ... | @@ -110,6 +110,7 @@ |
110 | 110 |
code.history = 변경이력 |
111 | 111 |
code.isBinary = 이진 파일입니다 |
112 | 112 |
code.looseFileSizeLimitForCodeBrowser = 사이트 관리자는 설정 파일의 "application.codeBrowser.viewer.maxFileSize" 값을 고쳐서 파일 크기 제한을 조절할 수 있습니다. |
113 |
+code.new.file = 새 파일 |
|
113 | 114 |
code.newer = 이전 |
114 | 115 |
code.noChanges = 변경 없음 |
115 | 116 |
code.nocommits = 커밋이 존재하지 않습니다 |
--- public/javascripts/service/yobi.board.Write.js
+++ public/javascripts/service/yobi.board.Write.js
... | ... | @@ -81,7 +81,9 @@ |
81 | 81 |
*/ |
82 | 82 |
function _onSubmitForm(){ |
83 | 83 |
if(htElement.welInputTitle.val() == ""){ |
84 |
- $yobi.showAlert(Messages("post.error.emptyTitle")); |
|
84 |
+ $yobi.showAlert(Messages("post.error.emptyTitle"), function() { |
|
85 |
+ $("#title").focus(); |
|
86 |
+ }); |
|
85 | 87 |
return false; |
86 | 88 |
} |
87 | 89 |
|
--- public/javascripts/service/yobi.code.Browser.js
+++ public/javascripts/service/yobi.code.Browser.js
... | ... | @@ -519,6 +519,23 @@ |
519 | 519 |
var breadcrumb = $yobi.xssClean(aCrumbs.join("")); |
520 | 520 |
|
521 | 521 |
htElement.welBreadCrumbs.html(breadcrumb); |
522 |
+ |
|
523 |
+ var path = window.location.hash.substr(1); |
|
524 |
+ var $newFileLink = $("#new-file-link"); |
|
525 |
+ var newPath = updateQueryStringParameter($newFileLink.attr("href"), "path", path + "/"); |
|
526 |
+ |
|
527 |
+ $newFileLink.attr("href", newPath); |
|
528 |
+ } |
|
529 |
+ |
|
530 |
+ function updateQueryStringParameter(uri, key, value) { |
|
531 |
+ var re = new RegExp("([?&])" + key + "=.*?(&|$)", "i"); |
|
532 |
+ var separator = uri.indexOf('?') !== -1 ? "&" : "?"; |
|
533 |
+ if (uri.match(re)) { |
|
534 |
+ return uri.replace(re, '$1' + key + "=" + value + '$2'); |
|
535 |
+ } |
|
536 |
+ else { |
|
537 |
+ return uri + separator + key + "=" + value; |
|
538 |
+ } |
|
522 | 539 |
} |
523 | 540 |
|
524 | 541 |
_init(htOptions || {}); |
Add a comment
Delete comment
Once you delete this comment, you won't be able to recover it. Are you sure you want to delete this comment?