添加项目文件。

This commit is contained in:
Zel
2025-01-22 23:31:03 +08:00
parent 1b8ba6771f
commit 2ae76476fb
894 changed files with 774558 additions and 0 deletions

View File

@@ -0,0 +1,80 @@
@using TightWiki.Models
@model TightWiki.Models.ViewModels.Page.PageCommentsViewModel
@{
Layout = "/Views/Shared/_Layout.cshtml";
var sessionState = ViewData["SessionState"] as TightWiki.SessionState ?? throw new Exception("Wiki State Context cannot be null.");
}
<h3>
Comments for <a href="@GlobalConfiguration.BasePath/@sessionState.PageNavigation">@sessionState.Page.Name</a>.
</h3>
<p>
All comment made on the given page.
</p>
@if (!string.IsNullOrEmpty(Model.ErrorMessage))
{
<div class="alert alert-danger">@Html.Raw(Model.ErrorMessage)</div>
}
@if (!string.IsNullOrEmpty(Model.SuccessMessage))
{
<div class="alert alert-success">@Html.Raw(Model.SuccessMessage)</div>
}
@using (Html.BeginForm(null, null, FormMethod.Post, new { action = $"{GlobalConfiguration.BasePath}{Context.Request.Path}" }))
{
if (sessionState.IsAuthenticated == true)
{
<div class="container">
<div class="d-flex justify-content-end mb-4">
<div class="flex-grow-1 me-2">
<input type="text" name="Comment" id="Comment" class="form-control" placeholder="Type comment..." />
</div>
<button type="submit" value="Find" class="btn btn-primary">Post</button>
</div>
</div>
}
else
{
@:<a href="@GlobalConfiguration.BasePath/Identity/Account/Login?returnUrl=@Context.Request.Path.Value">Login to leave a comment</a>.
}
if (Model.Comments.Count > 0)
{
<div class="container mt-4">
@foreach (var h in Model.Comments)
{
<div class="border rounded mb-2 p-2">
<p class="mb-1">@Html.Raw(h.Body)</p>
<div class="d-flex justify-content-between align-items-center">
<div class="d-flex align-items-center small text-muted mb-0">
<img src="/Profile/@h.UserNavigation/Avatar?Exact=16" class="rounded-circle me-2" alt="Avatar" />
@if (TightWiki.Models.GlobalConfiguration.EnablePublicProfiles)
{
<a href="@GlobalConfiguration.BasePath/Profile/@h.UserNavigation/Public" class="small text-decoration-none mb-0">@Html.DisplayTextFor(x => h.UserName)</a>
}
else
{
<span class="small text-decoration-none mb-0">@Html.DisplayTextFor(x => h.UserName)</span>
}
&nbsp;&nbsp;<p class="small text-decoration-none text-muted mb-0">@Html.DisplayTextFor(x => h.CreatedDate)</p>
</div>
@if (sessionState.CanModerate == true || h.UserId == sessionState.Profile?.UserId)
{
<a href="?Delete=@h.Id" class="small text-danger text-decoration-none" onclick="return confirm('Are you sure you want to delete this comment?')">Delete</a>
}
</div>
</div>
}
</div>
}
else
{
<div class="d-flex small text-muted mb-0">
<strong>The page does not have any comments, why don't you add one?</strong>
</div>
}
@Html.Raw(TightWiki.Library.PageSelectorGenerator.Generate(Context.Request.QueryString, Model.PaginationPageCount))
}

View File

@@ -0,0 +1,38 @@
@using TightWiki.Models
@model TightWiki.Models.ViewModels.Page.PageDeleteViewModel
@{
Layout = "/Views/Shared/_Layout.cshtml";
var sessionState = ViewData["SessionState"] as TightWiki.SessionState ?? throw new Exception("Wiki State Context cannot be null.");
}
@if (!string.IsNullOrEmpty(Model.ErrorMessage))
{
<div class="alert alert-danger">@Html.Raw(Model.ErrorMessage)</div>
}
@if (!string.IsNullOrEmpty(Model.SuccessMessage))
{
<div class="alert alert-success">@Html.Raw(Model.SuccessMessage)</div>
}
@if (string.IsNullOrEmpty(Model.ErrorMessage))
{
<div class="card border-danger mb-3">
<div class="card-header bg-danger text-white">
<strong>Delete page "@Model.PageName"?</strong>
</div>
<div class="card-body">
Deleting "@Model.PageName" will move the page, all @Model.PageRevision revisions and @Model.CountOfAttachments file attachments to the deletion queue.
This action can only be undone by an administrator or moderator.
<br /><br />
Are you sure you want to continue with this deletion?<br /><br />
@using (Html.BeginForm(null, null, FormMethod.Post, new { action = $"{GlobalConfiguration.BasePath}{Context.Request.Path}" }))
{
<div class="form-group"><button type="submit" class="btn btn-danger rounded-0" name="IsActionConfirmed" value="true">Yes</button>&nbsp;&nbsp;<button type="submit" class="btn btn-success rounded-0" name="IsActionConfirmed" value="false">No</button></div>
}
</div>
</div>
}

View File

@@ -0,0 +1,98 @@
@using TightWiki.Models
@model TightWiki.Models.ViewModels.Page.PageDisplayViewModel
@{
Layout = "/Views/Shared/_Layout.cshtml";
var sessionState = ViewData["SessionState"] as TightWiki.SessionState ?? throw new Exception("Wiki State Context cannot be null.");
}
@if (Model.Revision < Model.MostCurrentRevision)
{
<div class="card border-info mb-3">
<div class="card-header bg-info">
<strong>Viewing a historical version</strong>
</div>
<div class="card-body">
<p class="card-text">
You are viewing revision @Model.Revision of "@Model.Name" modified by @Model.ModifiedByUserName on @Model.ModifiedDate.<br />
<a class="btn btn-success btn-thin" href="@GlobalConfiguration.BasePath/@Model.Navigation">View latest revision</a>
@if(sessionState.CanModerate)
{
<a class="btn btn-warning btn-thin" href="@GlobalConfiguration.BasePath/@Model.Navigation/revert/@Model.Revision">Revert to revision @Model.Revision</a>
}
</p>
</div>
</div>
}
@Html.Raw(Model.Body)
@if (TightWiki.Models.GlobalConfiguration.ShowLastModifiedOnPageFooter && string.IsNullOrWhiteSpace(@Model.ModifiedByUserName) == false && Model.HideFooterLastModified != true)
{
<br />
if (TightWiki.Models.GlobalConfiguration.EnablePublicProfiles)
{
<small><cite title="Modified By">Last modified by <a href="@GlobalConfiguration.BasePath/Profile/@Model.ModifiedByUserName/Public">@Model.ModifiedByUserName</a> @@ @Model.ModifiedDate</cite></small>
}
else
{
<small><cite title="Modified By">Last modified by @Model.ModifiedByUserName @@ @Model.ModifiedDate</cite></small>
}
<br />
}
@if (Model.HideFooterComments == false)
{
@if (TightWiki.Models.GlobalConfiguration.EnablePageComments && TightWiki.Models.GlobalConfiguration.ShowCommentsOnPageFooter)
{
<hr class="mt-5 mb-5">
<h2>Comments</h2>
}
@if (TightWiki.Models.GlobalConfiguration.EnablePageComments && TightWiki.Models.GlobalConfiguration.ShowCommentsOnPageFooter)
{
if (@sessionState.IsAuthenticated == true)
{
<form method="post" action="@GlobalConfiguration.BasePath/@sessionState.PageNavigation/Comments">
<div class="container">
<div class="d-flex justify-content-end mb-4">
<div class="flex-grow-1 me-2">
<input type="text" name="Comment" id="Comment" class="form-control" placeholder="Type comment..." />
</div>
<button type="submit" value="Post" class="btn btn-primary">Post</button>
</div>
</div>
</form>
}
else
{
@:<a href="@GlobalConfiguration.BasePath/Identity/Account/Login?returnUrl=@Context.Request.Path.Value">Login to leave a comment</a>.
<br />
}
}
@if (TightWiki.Models.GlobalConfiguration.EnablePageComments && TightWiki.Models.GlobalConfiguration.ShowCommentsOnPageFooter && Model.Comments != null && Model.Comments.Count > 0)
{
<div class="container mt-4">
@foreach (var h in Model.Comments)
{
<div class="border rounded mb-2 p-2">
<p class="mb-1">@Html.Raw(h.Body)</p>
<div class="d-flex justify-content-between align-items-center">
<div class="d-flex align-items-center small text-muted mb-0">
<img src="@GlobalConfiguration.BasePath/Profile/@h.UserNavigation/Avatar?Exact=16" class="rounded-circle me-2" alt="Avatar" />
<a href="@GlobalConfiguration.BasePath/Profile/@h.UserNavigation/Public" class="small text-decoration-none mb-0">@Html.DisplayTextFor(x => h.UserName)</a>
&nbsp;&nbsp;<p class="small text-decoration-none text-muted mb-0">@Html.DisplayTextFor(x => h.CreatedDate)</p>
</div>
@if (sessionState.CanModerate == true || h.UserId == sessionState.Profile?.UserId)
{
<a href="@GlobalConfiguration.BasePath/@sessionState.PageNavigation/Comments?Delete=@h.Id" class="small text-danger text-decoration-none" onclick="return confirm('Are you sure you want to delete this comment?')">Delete</a>
}
</div>
</div>
}
</div>
}
@if (TightWiki.Models.GlobalConfiguration.EnablePageComments && TightWiki.Models.GlobalConfiguration.ShowCommentsOnPageFooter)
{
<a href="@GlobalConfiguration.BasePath/@sessionState.PageNavigation/Comments">View all comments</a>
}
}

View File

@@ -0,0 +1,218 @@
@using TightWiki.Models
@model TightWiki.Models.ViewModels.Page.PageEditViewModel
@{
Layout = "/Views/Shared/_Layout.cshtml";
var sessionState = ViewData["SessionState"] as TightWiki.SessionState ?? throw new Exception("Wiki State Context cannot be null.");
}
<link rel="stylesheet" href=@Url.Content("~/codemirror/codemirror.css")>
<link rel="stylesheet" href=@Url.Content("~/codemirror/theme/light.css")>
<link rel="stylesheet" href=@Url.Content("~/codemirror/theme/dark.css")>
<link rel="stylesheet" href=@Url.Content("~/codemirror/addon/scroll/simplescrollbars.css")>
<script src="@Url.Content("~/codemirror/codemirror.js")"></script>
<script src="@Url.Content("~/codemirror/addon/edit/matchbrackets.js")"></script>
<script src="@Url.Content("~/codemirror/mode/tightwiki/tightwiki.js")"></script>
<script src="@Url.Content("~/codemirror/addon/scroll/simplescrollbars.js")"></script>
<style type="text/css">
.CodeMirror {
border: 1px solid #eee;
height: 50em;
}
.active {
background-color: lightgreen !important;
}
#dropSection {
width: 100%; /* Makes the section fully responsive */
height: auto; /* Allow the height to adjust with content */
min-height: 150px; /* Minimum height to allow space for content */
border: 2px dashed #007bff;
border-radius: 5px;
text-align: center;
font-size: 18px;
color: #007bff;
margin-bottom: 20px;
padding: 20px;
background-color: var(--bs-card-bg); /* Uses Bootstrap background color */
word-wrap: break-word; /* Forces long words to break and wrap */
white-space: normal; /* Ensures normal text wrapping */
}
#dropSection span {
display: inline-block; /* Ensure the text behaves like inline elements */
max-width: 100%; /* Restrict the width to 100% of the container */
line-height: normal; /* Use normal line height for multi-line text */
}
#dropSection.dragover {
background-color: #e9ecef;
border-color: #007bff;
color: #007bff;
}
</style>
<script>
function readyFn(jQuery) {
$("#uploadedFiles").load("@GlobalConfiguration.BasePath/File/PageAttachments/@sessionState.PageNavigationEscaped");
}
$(document).ready(readyFn);
document.addEventListener("DOMContentLoaded", function () {
const dropSection = document.getElementById('dropSection');
const fileInput = document.getElementById('fileInput');
const uploadStatus = document.getElementById('uploadStatus');
// Handle dragover event
dropSection.addEventListener('dragover', (e) => {
e.preventDefault();
dropSection.classList.add('dragover');
dropSection.textContent = "Drop files here...";
});
// Handle dragleave event
dropSection.addEventListener('dragleave', () => {
dropSection.classList.remove('dragover');
dropSection.textContent = "Attach files by dropping them here or by manually selecting them below:";
});
// Handle drop event
dropSection.addEventListener('drop', (e) => {
e.preventDefault();
dropSection.classList.remove('dragover');
dropSection.textContent = "Attach files by dropping them here or by manually selecting them below:";
const files = e.dataTransfer.files;
handleFileUpload(files);
});
// Open file input dialog on click
dropSection.addEventListener('click', () => {
fileInput.click();
});
// Handle file input change (manual upload)
fileInput.addEventListener('change', () => {
const files = fileInput.files;
handleFileUpload(files);
});
// Function to handle file upload
function handleFileUpload(files) {
const formData = new FormData();
for (let i = 0; i < files.length; i++) {
formData.append('postedFiles', files[i]);
}
uploadFiles(formData);
}
// Function to upload files using fetch API
function uploadFiles(formData) {
fetch('@Url.Action("UploadDragDrop", "File")/@sessionState.PageNavigationEscaped', {
method: 'POST',
body: formData
})
.then(response => response.json()) // Read the response as JSON
.then(result => {
if (result.success) {
uploadStatus.innerHTML = `<p>Upload successful: ${result.message}</p>`;
} else {
uploadStatus.innerHTML = `<p>Error: ${result.message}</p>`;
}
$("#uploadedFiles").load("@GlobalConfiguration.BasePath/File/PageAttachments/@sessionState.PageNavigationEscaped");
})
.catch(error => {
uploadStatus.innerHTML = `<p>Unexpected Error: ${error.message}</p>`;
});
}
});
</script>
<div class="bodyDiv">
<div class="card">
<div class="card-header"><h3>Content</h3></div>
<div class="card-body">
@if (!string.IsNullOrEmpty(Model.ErrorMessage))
{
<div class="alert alert-danger">@Html.Raw(Model.ErrorMessage)</div>
}
@if (!string.IsNullOrEmpty(Model.SuccessMessage))
{
<div class="alert alert-success">@Html.Raw(Model.SuccessMessage)</div>
}
@using (Html.BeginForm(null, null, FormMethod.Post, new { action = $"{GlobalConfiguration.BasePath}{Context.Request.Path}" }))
{
@Html.HiddenFor(x => x.Id)
<div class="form-group">
<button type="submit" class="btn btn-primary rounded-0">Save</button>
<a href="@GlobalConfiguration.BasePath/@sessionState.PageNavigation" target="_blank" rel="noopener" class="btn btn-success rounded-0" role="button">View</a>
</div>
<br />
<strong>@Html.LabelFor(x => x.Name)</strong>
<br />
@Html.TextBoxFor(x => x.Name, new { style = "width:50%" })
<div class="text-danger">@Html.ValidationMessageFor(m => m.Name)</div>
<br />
<strong>@Html.LabelFor(x => x.Description)</strong>
<br />
@Html.TextBoxFor(x => x.Description, new { style = "width:90%" })
<div class="text-danger">@Html.ValidationMessageFor(m => m.Description)</div>
<br />
<strong>@Html.LabelFor(x => x.Body)</strong>
<br />
<div class="text-danger">@Html.ValidationMessageFor(m => m.Body)</div>
<textarea id=Body name="Body">@Model.Body</textarea>
<script>
var editor = CodeMirror.fromTextArea(document.getElementById("Body"), {
theme: "@sessionState.UserTheme.EditorTheme",
lineNumbers: true,
mode: "text/x-tightwiki",
matchBrackets: true,
viewportMargin: Infinity,
lineWrapping: true,
scrollbarStyle: "simple"
});
</script>
<br />
<div class="form-group">
<button type="submit" class="btn btn-primary rounded-0">Save</button>
<a href="@GlobalConfiguration.BasePath/@sessionState.PageNavigation" target="_blank" rel="noopener" class="btn btn-success rounded-0" role="button">View</a>
</div>
}
</div>
</div>
<br />
<div class="card">
<div class="card-header"><h3>Attachments</h3></div>
<div class="card-body">
@if (Model?.Id > 0 && @sessionState.CanCreate == true)
{
<div id="dropSection" class="dropSection">
<span class="d-inline-block">Drop file attachments here or click to upload manually.</span>
</div>
<input type="file" id="fileInput" style="display:none" multiple>
<div id="uploadStatus"></div>
<div id="uploadedFiles"></div>
}
else
{
<div>Save the page before uploading files.</div>
}
</div>
</div>
</div>

View File

@@ -0,0 +1,31 @@
@using TightWiki.Models
@model TightWiki.Models.ViewModels.Page.PageRevertViewModel
@{
Layout = "/Views/Shared/_Layout.cshtml";
var sessionState = ViewData["SessionState"] as TightWiki.SessionState ?? throw new Exception("Wiki State Context cannot be null.");
}
@if (!string.IsNullOrEmpty(Model.ErrorMessage))
{
<div class="alert alert-danger">@Html.Raw(Model.ErrorMessage)</div>
}
@if (!string.IsNullOrEmpty(Model.SuccessMessage))
{
<div class="alert alert-success">@Html.Raw(Model.SuccessMessage)</div>
}
<div class="card border-warning mb-3">
<div class="card-header bg-warning">
<strong>Revert to revision @sessionState.Page.Revision?</strong>
</div>
<div class="card-body">
Reverting "@Model.PageName" from revision @sessionState.Page.MostCurrentRevision to @sessionState.Page.Revision will rollback @Model.HigherRevisionCount changes.</><br />
Reverting does not mean that changes will be lost however, the revert process will create a new revision with the reverted changes.<br /><br />
Are you sure you want to continue?<br /><br />
@using (Html.BeginForm(null, null, FormMethod.Post, new { action = $"{GlobalConfiguration.BasePath}{Context.Request.Path}" }))
{
<div class="form-group"><button type="submit" class="btn btn-warning rounded-0" name="IsActionConfirmed" value="true">Yes</button>&nbsp;&nbsp;<button type="submit" class="btn btn-success rounded-0" name="IsActionConfirmed" value="false">No</button></div>
}
</div>
</div>

View File

@@ -0,0 +1,63 @@
@model TightWiki.Models.ViewModels.Page.RevisionsViewModel
@using TightWiki.Library
@using TightWiki.Models
@{
Layout = "/Views/Shared/_Layout.cshtml";
var sessionState = ViewData["SessionState"] as TightWiki.SessionState ?? throw new Exception("Wiki State Context cannot be null.");
}
<h3>
Page Revisions for <a href="@GlobalConfiguration.BasePath/@sessionState.PageNavigation">@sessionState.Page.Name</a>.
</h3>
<p>
All changes that have been made to the page. <br /><br />
</p>
@if (!string.IsNullOrEmpty(Model.ErrorMessage))
{
<div class="alert alert-danger">@Html.Raw(Model.ErrorMessage)</div>
}
@if (!string.IsNullOrEmpty(Model.SuccessMessage))
{
<div class="alert alert-success">@Html.Raw(Model.SuccessMessage)</div>
}
@using (Html.BeginForm(null, null, FormMethod.Get, new { action = $"{GlobalConfiguration.BasePath}{Context.Request.Path}" }))
{
@if (Model.Revisions.Count > 0)
{
<table class="table fixedTable100 table-striped" border="0" cellspacing="0" cellpadding="0">
<thead>
<tr>
<td><strong><a href="?@QueryStringConverter.OrderHelper(sessionState, "Revision")">Revision</a></strong></td>
<td><strong><a href="?@QueryStringConverter.OrderHelper(sessionState, "ModifiedBy")">Modified By</a></strong></td>
<td><strong><a href="?@QueryStringConverter.OrderHelper(sessionState, "ModifiedDate")">Modified Date</a></strong></td>
<td><strong>Summary</strong></td>
</tr>
</thead>
<tbody>
@foreach (var h in Model.Revisions)
{
<tr>
<td><a href="@GlobalConfiguration.BasePath/@sessionState.PageNavigation/@h.Revision" target="_blank" rel="noopener">@Html.DisplayTextFor(x => h.Revision)</a></td>
<td><a href="@GlobalConfiguration.BasePath/Profile/@h.ModifiedByUserName/Public">@Html.DisplayTextFor(x => h.ModifiedByUserName)</a></td>
<td>@Html.DisplayTextFor(x => h.ModifiedDate)</td>
<td>@Html.DisplayTextFor(x => h.ChangeSummary)</td>
</tr>
}
</tbody>
</table>
@Html.Raw(TightWiki.Library.PageSelectorGenerator.Generate(Context.Request.QueryString, Model.PaginationPageCount))
}
else
{
<div class="d-flex small text-muted mb-0">
<strong>
The selected page does not have any revisions.
</strong>
</div>
}
}

View File

@@ -0,0 +1,67 @@
@using TightWiki.Models
@model TightWiki.Models.ViewModels.Page.PageSearchViewModel
@{
Layout = "/Views/Shared/_Layout.cshtml";
var sessionState = ViewData["SessionState"] as TightWiki.SessionState ?? throw new Exception("Wiki State Context cannot be null.");
}
<h3>
Page Search
</h3>
<p>
If it's here, you should be able to find it.<br /><br />
</p>
@if (!string.IsNullOrEmpty(Model.ErrorMessage))
{
<div class="alert alert-danger">@Html.Raw(Model.ErrorMessage)</div>
}
@if (!string.IsNullOrEmpty(Model.SuccessMessage))
{
<div class="alert alert-success">@Html.Raw(Model.SuccessMessage)</div>
}
@using (Html.BeginForm(null, null, FormMethod.Get, new { action = $"{GlobalConfiguration.BasePath}{Context.Request.Path}" }))
{
<div class="container">
<div class="d-flex justify-content-end mb-4">
<div class="flex-grow-1 me-2">
@Html.TextBoxFor(x => x.SearchString, new { @class = "form-control", style = "width:100%" })
</div>
<button type="submit" value="Find" class="btn btn-primary">Search</button>
</div>
</div>
<br />
<table class="table fixedTable100 table-striped" border="0" cellspacing="0" cellpadding="0">
@foreach (var p in Model.Pages)
{
<tr>
<td>
@if ((p.Namespace ?? "") != "")
{
<font class="text-muted">@p.Namespace ::</font>
}<a href="@GlobalConfiguration.BasePath/@p.Navigation">@p.Title</a>@(String.IsNullOrEmpty(@p.Description) ? "" : " : ") @p.Description
</td>
</tr>
}
</table>
if (Model.Pages.Count == 0)
{
<div class="d-flex small text-muted mb-0">
<strong>
Your search criteria resulted in no pages, simplify your search text.
</strong>
</div>
}
@Html.Raw(TightWiki.Library.PageSelectorGenerator.Generate(Context.Request.QueryString, Model.PaginationPageCount))
}
<script>
window.onload = function () {
document.getElementById("SearchString").focus();
}
</script>