Skip to content

Commit

Permalink
Bookmark folders
Browse files Browse the repository at this point in the history
The user can now create bookmark folders, and drag bookmarks into them. Folders can also be nested.

The bookmark sorting menu item sorts inside the chosen folder/root.

Remote bookmark sources appear also as folders, although they cannot be sorted/edited.

IssueID #339
  • Loading branch information
skyjake committed Sep 24, 2021
1 parent 446df26 commit 53f0596
Show file tree
Hide file tree
Showing 25 changed files with 247 additions and 60 deletions.
4 changes: 3 additions & 1 deletion po/compile.py
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,9 @@ def parse_po(src):
def compile_string(msg_id, msg_str):
return msg_id.encode('utf-8') + bytes([0]) + \
msg_str.encode('utf-8') + bytes([0])



os.chdir(os.path.dirname(__file__))

if MODE == 'compile':
BASE_STRINGS = {}
Expand Down
23 changes: 22 additions & 1 deletion po/en.po
Original file line number Diff line number Diff line change
Expand Up @@ -225,7 +225,13 @@ msgid "menu.zoom.reset"
msgstr "Reset Zoom"

msgid "menu.view.split"
msgstr "Split View..."
msgstr "Split View…"

msgid "menu.newfolder"
msgstr "New Folder…"

msgid "menu.sort.alpha"
msgstr "Sort Alphabetically"

msgid "menu.bookmarks.list"
msgstr "List All Bookmarks"
Expand Down Expand Up @@ -1189,6 +1195,18 @@ msgstr "Icon:"
msgid "heading.bookmark.tags"
msgstr "SPECIAL TAGS"

msgid "heading.addfolder"
msgstr "ADD FOLDER"

msgid "dlg.addfolder.defaulttitle"
msgstr "New Folder"

msgid "dlg.addfolder.prompt"
msgstr "Enter the name of the new folder:"

msgid "dlg.addfolder"
msgstr "Add Folder"

msgid "heading.prefs"
msgstr "PREFERENCES"

Expand Down Expand Up @@ -1564,6 +1582,9 @@ msgstr "Next set of home row key links"
msgid "keys.bookmark.add"
msgstr "Add bookmark"

msgid "keys.bookmark.addfolder"
msgstr "Add bookmark folder"

msgid "keys.subscribe"
msgstr "Subscribe to page"

Expand Down
Binary file modified res/lang/de.bin
Binary file not shown.
Binary file modified res/lang/en.bin
Binary file not shown.
Binary file modified res/lang/es.bin
Binary file not shown.
Binary file modified res/lang/fi.bin
Binary file not shown.
Binary file modified res/lang/fr.bin
Binary file not shown.
Binary file modified res/lang/ia.bin
Binary file not shown.
Binary file modified res/lang/ie.bin
Binary file not shown.
Binary file modified res/lang/pl.bin
Binary file not shown.
Binary file modified res/lang/ru.bin
Binary file not shown.
Binary file modified res/lang/sr.bin
Binary file not shown.
Binary file modified res/lang/tok.bin
Binary file not shown.
Binary file modified res/lang/zh_Hans.bin
Binary file not shown.
Binary file modified res/lang/zh_Hant.bin
Binary file not shown.
20 changes: 20 additions & 0 deletions src/app.c
Original file line number Diff line number Diff line change
Expand Up @@ -2033,6 +2033,7 @@ static void resetFonts_App_(iApp *d) {
iBool handleCommand_App(const char *cmd) {
iApp *d = &app_;
const iBool isFrozen = !d->window || d->window->isDrawFrozen;
/* TODO: Maybe break this up a little bit? There's a very long list of ifs here. */
if (equal_Command(cmd, "config.error")) {
makeSimpleMessage_Widget(uiTextCaution_ColorEscape "CONFIG ERROR",
format_CStr("Error in config file: %s\n"
Expand Down Expand Up @@ -2764,6 +2765,25 @@ iBool handleCommand_App(const char *cmd) {
makeFeedSettings_Widget(findUrl_Bookmarks(d->bookmarks, url));
return iTrue;
}
else if (equal_Command(cmd, "bookmarks.addfolder")) {
if (suffixPtr_Command(cmd, "value")) {
add_Bookmarks(d->bookmarks, NULL, collect_String(suffix_Command(cmd, "value")), NULL, 0);
postCommand_App("bookmarks.changed");
}
else {
iWidget *dlg = makeValueInput_Widget(get_Root()->widget,
collectNewCStr_String(cstr_Lang("dlg.addfolder.defaulttitle")),
uiHeading_ColorEscape "${heading.addfolder}", "${dlg.addfolder.prompt}",
uiTextAction_ColorEscape "${dlg.addfolder}", "bookmarks.addfolder");
setSelectAllOnFocus_InputWidget(findChild_Widget(dlg, "input"), iTrue);
}
return iTrue;
}
else if (equal_Command(cmd, "bookmarks.sort")) {
sort_Bookmarks(d->bookmarks, arg_Command(cmd), cmpTitleAscending_Bookmark);
postCommand_App("bookmarks.changed");
return iTrue;
}
else if (equal_Command(cmd, "bookmarks.reload.remote")) {
fetchRemote_Bookmarks(bookmarks_App());
return iTrue;
Expand Down
30 changes: 21 additions & 9 deletions src/bookmarks.c
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ static int cmpTimeDescending_Bookmark_(const iBookmark **a, const iBookmark **b)
return iCmp(seconds_Time(&(*b)->when), seconds_Time(&(*a)->when));
}

static int cmpTitleAscending_Bookmark_(const iBookmark **a, const iBookmark **b) {
int cmpTitleAscending_Bookmark(const iBookmark **a, const iBookmark **b) {
return cmpStringCase_String(&(*a)->title, &(*b)->title);
}

Expand Down Expand Up @@ -250,19 +250,29 @@ static void load_BookmarkLoader(iBookmarkLoader *d, iFile *file) {
iDefineTypeConstructionArgs(BookmarkLoader, (iBookmarks *b), b)

/*----------------------------------------------------------------------------------------------*/


static iBool isMatchingParent_Bookmark_(void *context, const iBookmark *bm) {
return bm->parentId == *(const uint32_t *) context;
}

void sort_Bookmarks(iBookmarks *d, uint32_t parentId, iBookmarksCompareFunc cmp) {
lock_Mutex(d->mtx);
iConstForEach(PtrArray, i, list_Bookmarks(d, cmp, isMatchingParent_Bookmark_, &parentId)) {
iBookmark *bm = i.ptr;
bm->order = index_PtrArrayConstIterator(&i) + 1;
}
unlock_Mutex(d->mtx);
}

void load_Bookmarks(iBookmarks *d, const char *dirPath) {
clear_Bookmarks(d);
/* Load new .ini bookmarks, if present. */
iFile *f = iClob(newCStr_File(concatPath_CStr(dirPath, fileName_Bookmarks_)));
if (!open_File(f, readOnly_FileMode | text_FileMode)) {
/* As a fallback, try loading the v1.6 bookmarks file. */
loadOldFormat_Bookmarks(d, dirPath);
/* Set ordering based on titles. */
iConstForEach(PtrArray, i, list_Bookmarks(d, cmpTitleAscending_Bookmark_, NULL, NULL)) {
iBookmark *bm = i.ptr;
bm->order = index_PtrArrayConstIterator(&i) + 1;
}
/* Old format has an implicit alphabetic sort order. */
sort_Bookmarks(d, 0, cmpTitleAscending_Bookmark);
return;
}
iBookmarkLoader loader;
Expand Down Expand Up @@ -317,7 +327,9 @@ uint32_t add_Bookmarks(iBookmarks *d, const iString *url, const iString *title,
iChar icon) {
lock_Mutex(d->mtx);
iBookmark *bm = new_Bookmark();
set_String(&bm->url, canonicalUrl_String(url));
if (url) {
set_String(&bm->url, canonicalUrl_String(url));
}
set_String(&bm->title, title);
if (tags) {
set_String(&bm->tags, tags);
Expand Down Expand Up @@ -471,7 +483,7 @@ const iString *bookmarkListPage_Bookmarks(const iBookmarks *d, enum iBookmarkLis
const iPtrArray *bmList = list_Bookmarks(d,
listType == listByCreationTime_BookmarkListType
? cmpTimeDescending_Bookmark_
: cmpTitleAscending_Bookmark_,
: cmpTitleAscending_Bookmark,
NULL,
NULL);
iConstForEach(PtrArray, i, bmList) {
Expand Down
13 changes: 9 additions & 4 deletions src/bookmarks.h
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,8 @@ struct Impl_Bookmark {
int order; /* sort order */
};

iLocalDef uint32_t id_Bookmark (const iBookmark *d) { return d->node.key; }
iLocalDef uint32_t id_Bookmark (const iBookmark *d) { return d->node.key; }
iLocalDef iBool isFolder_Bookmark (const iBookmark *d) { return isEmpty_String(&d->url); }

iBool hasTag_Bookmark (const iBookmark *, const char *tag);
void addTag_Bookmark (iBookmark *, const char *tag);
Expand All @@ -73,11 +74,17 @@ iLocalDef void addOrRemoveTag_Bookmark(iBookmark *d, const char *tag, iBool add)
}
}

int cmpTitleAscending_Bookmark (const iBookmark **, const iBookmark **);
int cmpTree_Bookmark (const iBookmark **, const iBookmark **);

/*----------------------------------------------------------------------------------------------*/

iDeclareType(Bookmarks)
iDeclareTypeConstruction(Bookmarks)

typedef iBool (*iBookmarksFilterFunc) (void *context, const iBookmark *);
typedef int (*iBookmarksCompareFunc) (const iBookmark **, const iBookmark **);

void clear_Bookmarks (iBookmarks *);
void load_Bookmarks (iBookmarks *, const char *dirPath);
void save_Bookmarks (const iBookmarks *, const char *dirPath);
Expand All @@ -88,15 +95,13 @@ iBool remove_Bookmarks (iBookmarks *, uint32_t id);
iBookmark * get_Bookmarks (iBookmarks *, uint32_t id);
void reorder_Bookmarks (iBookmarks *, uint32_t id, int newOrder);
iBool updateBookmarkIcon_Bookmarks(iBookmarks *, const iString *url, iChar icon);
void sort_Bookmarks (iBookmarks *, uint32_t parentId, iBookmarksCompareFunc cmp);
void fetchRemote_Bookmarks (iBookmarks *);
void requestFinished_Bookmarks (iBookmarks *, iGmRequest *req);

iChar siteIcon_Bookmarks (const iBookmarks *, const iString *url);
uint32_t findUrl_Bookmarks (const iBookmarks *, const iString *url); /* O(n) */

typedef iBool (*iBookmarksFilterFunc) (void *context, const iBookmark *);
typedef int (*iBookmarksCompareFunc)(const iBookmark **, const iBookmark **);

iBool filterTagsRegExp_Bookmarks (void *regExp, const iBookmark *);

/**
Expand Down
2 changes: 2 additions & 0 deletions src/defs.h
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,7 @@ iLocalDef int acceptKeyMod_ReturnKeyBehavior(int behavior) {
#define rightArrow_Icon "\u279e"
#define barLeftArrow_Icon "\u21a4"
#define barRightArrow_Icon "\u21a6"
#define upDownArrow_Icon "\u21c5"
#define clock_Icon "\U0001f553"
#define pin_Icon "\U0001f588"
#define star_Icon "\u2605"
Expand Down Expand Up @@ -155,6 +156,7 @@ iLocalDef int acceptKeyMod_ReturnKeyBehavior(int behavior) {
#define return_Icon "\u23ce"
#define undo_Icon "\u23ea"
#define select_Icon "\u2b1a"
#define downAngle_Icon "\ufe40"

#if defined (iPlatformApple)
# define shift_Icon "\u21e7"
Expand Down
1 change: 1 addition & 0 deletions src/ui/keys.c
Original file line number Diff line number Diff line change
Expand Up @@ -213,6 +213,7 @@ static const struct { int id; iMenuItem bind; int flags; } defaultBindings_[] =
{ 46, { "${keys.link.homerow.hover}", 'h', 0, "document.linkkeys arg:1 hover:1" }, 0 },
{ 47, { "${keys.link.homerow.next}", '.', 0, "document.linkkeys more:1" }, 0 },
{ 50, { "${keys.bookmark.add}", 'd', KMOD_PRIMARY, "bookmark.add" }, 0 },
{ 51, { "${keys.bookmark.addfolder}", 'n', KMOD_SHIFT, "bookmarks.addfolder" }, 0 },
{ 55, { "${keys.subscribe}", subscribeToPage_KeyModifier, "feeds.subscribe" }, 0 },
{ 60, { "${keys.findtext}", 'f', KMOD_PRIMARY, "focus.set id:find.input" }, 0 },
{ 70, { "${keys.zoom.in}", SDLK_EQUALS, KMOD_PRIMARY, "zoom.delta arg:10" }, 0 },
Expand Down
30 changes: 17 additions & 13 deletions src/ui/listwidget.c
Original file line number Diff line number Diff line change
Expand Up @@ -331,8 +331,8 @@ static size_t resolveDragDestination_ListWidget_(const iListWidget *d, iInt2 dst
const iRect rect = itemRect_ListWidget(d, index);
const iRangei span = ySpan_Rect(rect);
if (item->isDropTarget) {
const int pad = size_Range(&span) / 4;
if (dstPos.y >= span.start + pad && dstPos.y < span.end) {
const int pad = size_Range(&span) / 3;
if (dstPos.y >= span.start + pad && dstPos.y < span.end - pad) {
*isOnto = iTrue;
return index;
}
Expand All @@ -352,11 +352,13 @@ static iBool endDrag_ListWidget_(iListWidget *d, iInt2 endPos) {
stop_Anim(&d->scrollY.pos);
iBool isOnto;
const size_t index = resolveDragDestination_ListWidget_(d, endPos, &isOnto);
if (isOnto) {
postCommand_Widget(d, "list.dragged arg:%zu onto:%zu", d->dragItem, index);
}
else if (index != d->dragItem) {
postCommand_Widget(d, "list.dragged arg:%zu before:%zu", d->dragItem, index);
if (index != d->dragItem) {
if (isOnto) {
postCommand_Widget(d, "list.dragged arg:%zu onto:%zu", d->dragItem, index);
}
else {
postCommand_Widget(d, "list.dragged arg:%zu before:%zu", d->dragItem, index);
}
}
invalidateItem_ListWidget(d, d->dragItem);
d->dragItem = iInvalidPos;
Expand Down Expand Up @@ -394,7 +396,7 @@ static iBool processEvent_ListWidget_(iListWidget *d, const SDL_Event *ev) {
}
else if (d->dragItem != iInvalidPos) {
/* Start scrolling if near the ends. */
const int zone = d->itemHeight;
const int zone = 2 * d->itemHeight;
const iRect bounds = bounds_Widget(w);
float scrollSpeed = 0.0f;
if (mousePos.y > bottom_Rect(bounds) - zone) {
Expand All @@ -410,7 +412,7 @@ static iBool processEvent_ListWidget_(iListWidget *d, const SDL_Event *ev) {
}
else {
setValueSpeed_Anim(&d->scrollY.pos, scrollSpeed < 0 ? 0 : scrollMax_ListWidget_(d),
iAbs(scrollSpeed * gap_UI * 100));
scrollSpeed * scrollSpeed * gap_UI * 400);
refreshWhileScrolling_ListWidget_(d);
}
}
Expand Down Expand Up @@ -451,7 +453,7 @@ static iBool processEvent_ListWidget_(iListWidget *d, const SDL_Event *ev) {
((const iListItem *) item_ListWidget(d, over))->isDraggable) {
d->dragItem = over;
d->dragOrigin = sub_I2(topLeft_Rect(itemRect_ListWidget(d, over)),
pos_Click(&d->click));
d->click.startPos);
invalidateItem_ListWidget(d, d->dragItem);
}
}
Expand Down Expand Up @@ -569,20 +571,22 @@ static void draw_ListWidget_(const iListWidget *d) {
SDL_SetRenderDrawBlendMode(renderer_Window(get_Window()), SDL_BLENDMODE_BLEND);
iBool dstOnto;
const size_t dstIndex = resolveDragDestination_ListWidget_(d, mousePos, &dstOnto);
if (dstIndex != d->dragItem && dstIndex != d->dragItem + 1) {
if (dstIndex != d->dragItem) {
const iRect dstRect = itemRect_ListWidget(d, dstIndex);
p.alpha = 0xff;
if (dstOnto) {
fillRect_Paint(&p, dstRect, uiTextAction_ColorId);
drawRectThickness_Paint(&p, dstRect, gap_UI / 2, uiTextAction_ColorId);
}
else {
else if (dstIndex != d->dragItem + 1) {
fillRect_Paint(&p, (iRect){ addY_I2(dstRect.pos, -gap_UI / 4),
init_I2(width_Rect(dstRect), gap_UI / 2) },
uiTextAction_ColorId);
}
}
p.alpha = 0x80;
setOpacity_Text(0.5f);
class_ListItem(item)->draw(item, &p, itemRect, d);
setOpacity_Text(1.0f);
SDL_SetRenderDrawBlendMode(renderer_Window(get_Window()), SDL_BLENDMODE_NONE);
}
unsetClip_Paint(&p);
Expand Down
3 changes: 3 additions & 0 deletions src/ui/lookupwidget.c
Original file line number Diff line number Diff line change
Expand Up @@ -171,6 +171,9 @@ static float scoreMatch_(const iRegExp *pattern, iRangecc text) {
}

static float bookmarkRelevance_LookupJob_(const iLookupJob *d, const iBookmark *bm) {
if (isFolder_Bookmark(bm)) {
return 0.0f;
}
iUrl parts;
init_Url(&parts, &bm->url);
const float t = scoreMatch_(d->term, range_String(&bm->title));
Expand Down
Loading

0 comments on commit 53f0596

Please sign in to comment.