/// <summary> /// Removes an item from collection /// </summary> /// <param name="topic"></param> /// <returns></returns> public void DeleteTopic(DocTopic topic) { if (topic == null) { return; } if (topic.Topics.Count > 0) { var childTopics = topic.Topics.ToList(); foreach (var childTopic in childTopics) { DeleteTopic(childTopic); } } topic.DeleteTopicFile(); if (topic.Parent?.Topics != null) { topic.Parent.Topics.Remove(topic); } else { Topics.Remove(topic); } topic = null; }
/// <summary> /// Finds a topic in the tree by comparing the link /// rather than an object reference. This allows finding /// topics that were not in the original project references /// </summary> /// <param name="topic"></param> /// <param name="rootTopics"></param> /// <returns></returns> public DocTopic FindTopicInTreeByValue(DocTopic topic, ObservableCollection <DocTopic> rootTopics = null) { if (rootTopics == null) { rootTopics = Topics; } if (rootTopics == null) { return(null); } foreach (var top in rootTopics) { if (top.Link == topic.Link) { return(top); } var foundTopic = FindTopicInTree(top, top.Topics); if (foundTopic != null) { return(foundTopic); } } return(null); }
public DocTopic FindTopicInFlatTree(DocTopic topic, ObservableCollection <DocTopic> rootTopics = null) { if (rootTopics == null) { rootTopics = Topics; } return(rootTopics?.FirstOrDefault(t => t.Id == topic.Id)); }
/// <summary> /// Updates a topic from a markdown document /// </summary> /// <param name="doc">document to update from (from disk)</param> /// <param name="topic">topic to update</param> public void UpdateTopicFromMarkdown(MarkdownDocument doc, DocTopic topic, bool noBody = false) { var fileTopic = new DocTopic(); fileTopic.Project = this; fileTopic.DisplayType = string.Empty; fileTopic.Type = null; fileTopic.LoadTopicFile(doc.Filename); // make sure we have latest if (!string.IsNullOrEmpty(fileTopic.Title)) { topic.Title = fileTopic.Title; } if (!string.IsNullOrEmpty(fileTopic.Slug)) { topic.Slug = fileTopic.Slug; } if (!string.IsNullOrEmpty(fileTopic.Link)) { topic.Link = fileTopic.Link; } // TODO: Figure out how we can detect whether this has changed or is the default // Problem is that DisplayType gets set to a default if n if (!string.IsNullOrEmpty(fileTopic.DisplayType)) { topic.DisplayType = fileTopic.DisplayType; } if (!string.IsNullOrEmpty(fileTopic.Type)) { topic.Type = fileTopic.Type; } if (!noBody) { topic.Body = fileTopic.Body; } }
/// <summary> /// Retrieves a topic but doesn't load it into Active Topic /// based on a topic id, slug or html link /// </summary> /// <param name="topicId"></param> /// <returns>topic or null</returns> public DocTopic GetTopic(string id) { if (string.IsNullOrEmpty(id)) { return(null); } DocTopic topic = null; if (id.StartsWith("_")) { string strippedSlugId = id; if (!string.IsNullOrEmpty(strippedSlugId) && strippedSlugId.StartsWith("_")) { strippedSlugId = id.Substring(1); } topic = Topics .FirstOrDefault(t => t.Id == id || t.Slug == id || t.Slug == strippedSlugId); return(topic); } if (id.StartsWith("msdn:")) { // for now just return as is throw new NotSupportedException(); } else if (id.StartsWith("sig:")) { throw new NotSupportedException(); } // Must be topic title topic = Topics .FirstOrDefault(t => t.Title != null && t.Title.ToLower() == id.ToLower() || t.Slug != null && t.Slug.ToLower() == id.ToLower()); return(topic); }
/// <summary> /// Recursively fills all topics with subtopics /// reorders them and assigns the appropriate /// hierarchical ids and dependencies /// </summary> /// <param name="topic"></param> /// <param name="topicList"></param> /// <returns></returns> public void GetChildTopicsForTopic(DocTopic topic) { if (topic.Topics == null) { topic.Topics = new ObservableCollection <DocTopic>(); return; } var children = topic.Topics .OrderByDescending(t => t.SortOrder) .ThenBy(t => t.DisplayType) .ThenBy(t => t.Title).ToList(); foreach (var childTopic in children) { GetChildTopicsForTopic(childTopic); childTopic.Parent = topic; childTopic.ParentId = topic.Id; childTopic.Project = this; } }
/// <summary> /// Common code that performs after topic loading logic /// </summary> /// <param name="topic"></param> /// <returns></returns> protected DocTopic AfterTopicLoaded(DocTopic topic) { if (topic == null) { Topic = null; SetError("Topic not found."); } topic.Project = this; topic.TopicState.IsDirty = false; topic.TopicState.OldLink = null; topic.TopicState.OldSlug = null; if (!topic.LoadTopicFile()) // load disk content { SetError("Topic body content not found."); return(null); } return(topic); }
/// <summary> /// Creates a new project structure in the ProjectFolder /// specified. /// </summary> /// <returns></returns> public DocProject CreateProject() { if (string.IsNullOrEmpty(ProjectFolder)) { SetError("Please provide a Project Folder."); return(null); } if (string.IsNullOrEmpty(Title)) { SetError("Please provide a Title for the new project."); return(null); } if (!IsTargetFolderMissingOrEmpty(ProjectFolder)) { SetError("Couldn't create new project: Project exists already - please use another folder."); return(null); } string filename = "_toc.json"; if (string.IsNullOrEmpty(filename)) { filename = Path.Combine(ProjectFolder, "_toc.json"); } else { filename = Path.Combine(ProjectFolder, filename); } var project = new DocProject() { Title = Title, Filename = filename, Owner = Owner }; if (!CopyProjectAssets(project)) { return(null); } string body = @"## Welcome to your new Documentation Project Here are a few tips to get started: * Press **ctrl-n** to create a new Topic * Enter text into the main text area using [Markdown formatting](https://documentationmonster.west-wind.com) * Use the Topic Editor on the right to enter topic data * Use drag and drop in the Topic tree to move topics around * Use the Image toolbar icon to select images from disk * Paste images from from the clipboard into your text Time to get going! "; var topic = new DocTopic(project) { Title = Title, Body = body, DisplayType = "index" }; project.Topics.Add(topic); project.SaveProject(); return(project); }
/// <summary> /// Recursively fills all topics with subtopics /// </summary> /// <param name="topics"></param> /// <param name="top"></param> /// <returns></returns> public void GetChildTopicsForTopicFromFlatList(ObservableCollection <DocTopic> topics, DocTopic topic) { if (topics == null) { topics = new ObservableCollection <DocTopic>(); } var query = topics.Where(t => t.ParentId == topic.Id); if (ProjectSettings.AutoSortTopics) { query = query .OrderByDescending(t => t.SortOrder) .ThenBy(t => t.DisplayType) .ThenBy(t => t.Title); } var children = query.ToList(); if (topic.Topics != null) { topic.Topics.Clear(); } else { topic.Topics = new ObservableCollection <DocTopic>(); } foreach (var childTopic in children) { childTopic.Parent = topic; childTopic.ParentId = topic.Id; childTopic.Project = this; string slug = childTopic.CreateSlug(); string baseSlug = topic.Slug; if (!string.IsNullOrEmpty(baseSlug)) { baseSlug += "/"; } childTopic.Slug = baseSlug + slug; if (childTopic.IsHeaderTopic()) { childTopic.Link = baseSlug + slug + "/" + slug + ".md"; } else { childTopic.Link = baseSlug + slug + ".md"; } topic.Topics.Add(childTopic); childTopic.SaveTopicFile(); } }
/// <summary> /// Saves a topic safely into the topic list. /// </summary> /// <param name="topic"></param> /// <returns></returns> /// <remarks> /// Note: This DOES NOT save the topic to disk tree to disk. /// The tree has to be explicitly updated /// </remarks> public bool SaveTopic(DocTopic topic = null) { if (topic == null) { topic = Topic; } if (topic == null) { return(false); } var loadTopic = FindTopicInTree(topic, Topics); if (string.IsNullOrEmpty(topic.Title)) { var lines = StringUtils.GetLines(topic.Body); var titleLine = lines.FirstOrDefault(l => l.TrimStart().StartsWith("# ")); if (!string.IsNullOrEmpty(titleLine) && titleLine.Length > 2) { topic.Title = titleLine.Trim().Substring(2); } } if (loadTopic == null) { using (var updateLock = new TopicFileUpdateLock()) { if (topic.Parent != null) { topic.Parent.Topics.Add(topic); } else { Topics.Add(topic); } if (topic.SaveTopicFile()) { topic.TopicState.IsDirty = false; topic.TopicState.OldLink = null; topic.TopicState.OldSlug = null; return(true); } return(false); } } if (loadTopic.Id == topic.Id) { var topics = loadTopic.Parent?.Topics; if (topic == null) { topics = Topics; } // Replace topic for (int i = 0; i < topics.Count; i++) { var tpc = topics[i]; if (tpc == null) { using (var updateLock = new TopicFileUpdateLock()) { topics.RemoveAt(i); } continue; } if (topics[i].Id == topic.Id) { using (var updateLock = new TopicFileUpdateLock()) { topics[i] = topic; if (topic.SaveTopicFile()) { topic.TopicState.IsDirty = false; topic.TopicState.OldLink = null; topic.TopicState.OldSlug = null; return(true); } return(false); } } } } return(false); }
/// <summary> /// Creates a new topic and assigns it to the Topic property. /// </summary> /// <returns></returns> public DocTopic NewTopic() { Topic = new DocTopic(this); return(Topic); }